TowardsDataScience-博客中文翻译-2022-五-

TowardsDataScience 博客中文翻译 2022(五)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

闯入数据科学世界的初学者指南

原文:https://towardsdatascience.com/a-beginners-guide-to-breaking-into-the-world-of-data-science-28e62301b8da

2023 年更新的学*课程和组合项目

图片由作者通过 Canva 提供

我开始数据科学之旅已经 5 年了。我记得当我开始时,我感到多么不确定。在决定从事数据科学职业后,我采访的每个人都告诉我去美国顶尖大学攻读硕士学位。在 2017 年,这似乎是唯一的办法。

大学学费太贵,而且在签证规定下,国际学生面临很大的压力,要找到工作才能偿还学生贷款。因此,相反,我做了我将在本文中介绍的事情。5 年后,在成为数据科学行业的高级专业人员后,你可以看出这很有效。

进入数据科学的基本思维转变

新手常犯的一个错误是过度思考和比较选项,而不是直接开始。老实说,这不是你的错。刚开始的时候,我被所有的资源淹没了。

每隔一周,就有一篇关于成为数据科学家的新博客发表——我们怎么能不期望你想太多而根本不采取行动呢?

简化本指南是我的职责(我会这样做),但首先,跟我重复:成为数据科学家或机器学*工程师有多种途径。

因此,与其浪费时间争论哪个课程是最好的,或者 R 是否比 Python 更好,我建议你遵循这个指南,开始你的旅程。

在我们进入课程之前,我想让你改变一下心态:

  • 在数据科学领域,成功的途径不止一条。
  • 太多的信息,也就是信息超载,淹没了你,让你偏离了所有的道路。
  • 要成为一名数据科学家,你必须始终专注于至少一条道路
  • 你选择的道路必须简单,能帮助你采取行动。

我自己创建的数据科学课程

假设你是一个绝对的初学者,我推荐这些课程和书籍作为你的数据科学课程。没有适合每个人的方法,所以请随意定制并创建您自己的课程。

给予或采取,我期望专门的学*需要大约一年。

编程;编排

  • 密歇根大学的 Python 应用数据科学专业:Python 被广泛应用于数据行业,本课程是更好地理解整体机器学*工作流程的良好开端。
  • 用于数据分析的 SQL:我已经完成了一些其他的 SQL 课程,但是这个课程教授你在数据分析环境中需要知道的一切,并且 100%直接用于我的工作。一些公司甚至整轮面试都集中在 SQL 上。
  • 麻省理工学院 CS 教育的缺失学期:你将学*版本控制、git、IDEs 和命令行环境,这将帮助你熟练掌握工作中使用的工具。

数学:

  • Khan Academy 的线性代数:虽然这些概念会在下面列出的机器学*课程中再次教授,但理解机器学*背后的数学是有帮助的,因为…机器学*就是数学!
  • 汗学院的多变量微积分:当你需要重温数学中被遗忘的概念时,这是一个极好的网站

统计数据:

  • 密歇根大学的统计学专业:当将数据科学应用于商业问题时,统计学是没有商量余地的。本课程通过动手练*教授你需要知道的一切。句号。

机器学*和深度学*:

  • 机器学*专业化 by deeplearning.ai:机器学*的 OG 是 2012 年发布的,大部分都是从这门课迷上这个领域的,包括我。该课程由吴恩达大学于 2022 年 7 月更新,随着课程的进展,你会感觉自己进入了数据科学领域。
  • 深度学*专业化 by deeplearning.ai:这种专业化拥有从深度学*基础到高级计算机视觉和自然语言处理的一切。对本专业的“结构化机器学*项目”课程大声喊出来,这是只有像吴恩达这样的伟人才能教授的瑰宝。

书籍:

  • 从零开始的数据科学作者 Joel Grus:让这本书成为你早期购买的第一本书,因为这是一种有趣的方法,在这本书里,你被聘为数据科学家,并浏览你如何处理交给你的每项任务。一个理想的初学者友好的书肯定!
  • 使用 Scikit-Learn、Keras 和 TensorFlow 进行机器学*作者:Aurélien Géron:数据科学学*的 OG 书籍之一,我在上下班途中阅读这本书,在办公桌上保留一份副本,甚至阅读这本书来重温被遗忘的概念。
  • Emily Robinson 和 Jacqueline Nolis:与上述书籍不同,这是一条你在其他地方找不到的专注于行业的职业建议。如果你的目标是在这个行业找到一份工作,这本书里有很多关于你面试过程和头 90 天工作的有用建议。

我从 Coursera、Udacity、DataCamp 等平台做过很多其他课程。这三个都是很棒的学*平台,帮助我提高了技能。我也读过一堆其他有用的机器学*书籍。你会在这个网站上听到我过去或未来文章中的所有内容。

在这里列出所有东西,这是初学者的路线图,只会让你不知所措。当你达到中级水平时——伸出手,我会给你指出这些资源。

有时候少即是多。

如何在第一份工作前获得经验

我们大多数人在申请数据科学职位时都会失去信心,因为大多数职位描述都要求 1-2 年以上的工作经验。

这就导致了我们令人沮丧的循环:没有经验就不能获得就业,没有就业就不能获得经验。

听着,我明白——几年前我也处在你的位置。我指导过像你这样的人,所以我知道那是什么感觉。

在与通过不同的非传统途径加入该行业的同事交谈并反思我的旅程后,这个循环的解决方案是显而易见的。

另一方面,我们所有人都有一个共同点:通过我称之为“数据组合”的东西,我们都拥有可展示的技能

以下是如何为自己打造一个:

  • Kaggle :我知道人们说 Kaggle 数据集通常是干净的,并且不能代表真实世界的项目。这可能是真的,但是作为一个初学者,你必须从某个地方开始,对吗?Kaggle 有一个非常棒的初学者友好社区和大量的指导教程来帮助你入门。一些公司甚至用这个平台招聘员工(我的公司就是这样),所以这是你熟悉这个平台的额外理由。
  • 你要求真实世界的经验——这就是你想要的。我志愿参加了 Omdena 的几个项目,在那里,从初学者到专家的人工智能实践者致力于解决端到端的现实世界问题。志愿服务很简单,有一份申请和一次对创始人的简短采访。
  • 没有人付钱让我这么说,但是在我看来,Udacity 已经掌握了基于项目的学*的艺术。它不仅仅是一个学*平台,也是一个你可以建立类似真实世界问题的项目并接收对你工作的反馈的平台。如果你觉得他们的项目很贵(试试奖学金吧!),他们所有的项目都在他们的 GitHub 上开源,等着你去完成,添加到你的数据组合里。

虽然可能有其他获得经验的方法,但这三种是我个人用过的,也是我可以推荐的。

关键是在学*数据科学的同时创建自己的数据组合。一旦你准备好了就不会了。不仅仅是在申请工作之前。这需要在学*数据科学的同时发生。

招聘经理喜欢听你如何不遗余力地完成激发你兴趣的项目。数据组合 FTW,我的朋友!

鹤立鸡群

2017 年,我开始学数据科学的时候,没人认识我。

我在宿舍里,疯狂地看着上面概述的在线课程。

只是当我开始在网上公开创建和分享数据科学内容时,人们才注意到我。我继续前进,得到了多份工作机会,自由职业的机会和一群忠实的读者来阅读我写的东西。

我是这样做的(你也可以):

如果我想改变我的旅程,我应该更早开始分享我的作品。人们会听我说什么吗,我有什么可信度公开分享我的旅程?我的冒名顶替综合症发作了,我在早期不断地怀疑自己。

最终,当我分享我的工作时,许多人赞赏并感谢我帮助他们进入数据科学领域。我的个人品牌是在这个过程中建立起来的,我愿意相信我从人群中脱颖而出(这就是你在这里的原因)。

方法是:学*。创造。分享

学*数据科学的第一年将是最艰难的一年。创建数据组合是一件有趣且受好奇心驱使的事情。分享你的旅程是最有意义的。

遗憾的是,大多数人在 6 个月内放弃了他们的数据科学探索。这也没关系,数据科学不一定适合所有人。

这是我现在的时间表:

  • 第一年:在线学*数据科学,参与项目
  • 第二年:获得我的第一份数据科学工作,继续提升技能
  • 第三年:晋升为机器学*工程师,仍在继续提升技能
  • 第 4 年:作为一名高级数据科学家领导团队,开始分享我在旅程中的学*、错误和经验
  • 第五年:探索人工智能中激发我兴趣的小众主题,并把写作作为分享知识的一种形式

没有什么新奇的东西,它与大多数数据专业人员的进步相似。正如我常说的,如果一个来自一个叫做斯里兰卡的小岛的困惑的大学生能做到这一点,你肯定也能。

要获得更多关于进入数据科学、真实体验和学*的有用见解,请考虑 加入我的电子邮件好友私人列表

作为披露,本文中使用了一些附属链接来分享我使用过的最好的资源,并且没有额外的费用。

如果你很看重这类文章,想支持我这个作家,可以考虑 成为中等会员 。每月 5 美元,你可以无限制地阅读媒体上的故事。

文本到图像生成模型的提示设计初学者指南

原文:https://towardsdatascience.com/a-beginners-guide-to-prompt-design-for-text-to-image-generative-models-8242e1361580

在你浪费你的免费试用积分之前,学*这些快速工程技巧

如果你已经体验过文本到图像的生成模型,你就会知道生成你喜欢的图像有多难。

随着稳定扩散中途DALL E2、的发布,人们一直在说提示工程可能会成为一个新职业。因为中途 Discord 服务器 DALL E2 和 StabilityAI 的 DreamStudio 有一个基于信用的定价模型[3,5,7],用户被激励使用尽可能少的提示来获得他们喜欢的图像。

鼓励用户使用尽可能少的提示。

这篇文章将会给你一个快速指南,在你浪费所有的免费试用积分之前,提示工程。这是一个通用指南,DALL E2,稳定扩散,中途有区别。因此,并非所有提示都适用于您正在使用的特定创成式模型。

我们将使用与[11]类似的基本提示“一只戴墨镜的猫”。图像将使用 DreamStudio(用于稳定扩散的 GUI)生成,默认设置为,固定种子为 42 ,以生成相似的图像进行比较。

关于提示工程的更多灵感,你可以看看https://lexica.art/,这是一个提示和它们的稳定扩散产生的结果图像的集合。

文本到图像和文本引导的图像到图像生成的提示设计基础

目前,大多数生成模型要么是文本到图像,要么是文本引导的图像到图像生成模型。在这两种情况下,至少有一个输入是提示,它是对您想要生成的图像的描述。

提示长度

提示应该相对较短。虽然 Midjourney 允许多达 6000 个字符,但提示应该保持在 60 个字以内。类似地,DALL E2 的提示必须保持在 400 个字符以内【9】。

字符集

从统计学的角度来看,你最好的选择是用英语表达你的提示。例如,在 LAION-5B 数据库的子集上训练稳定扩散,该数据库包含 23 亿个英文图像-文本对和来自 100 多种其他语言的 22 亿个图像-文本对[1,4]。

提示:“一只戴墨镜的猫”(图片由作者用 DreamStudio 制作)。

这意味着你不局限于西欧字母表。你可以使用像阿拉伯语或汉语这样的非罗马字符集,你甚至可以使用表情符号。

Prompt: “サングラスをかけた猫” (Japanese for “a cat wearing sunglasses”) (Image made by the author with DreamStudio)

提示: "🐱😎”(作者用 DreamStudio 制作的图片)

然而,正如你所看到的,用日语提示生成的图像和用仅表情符号提示生成的图像都无法为猫生成一副太阳镜。

虽然它的效果可能不如英文提示,但您可以使用它来增强效果(参见重复一节)。

同样,例如,中途不区分大小写【6】。这意味着你是否大写你的文本不会影响生成的图像;因此,您可以用小写字母书写提示。

模板和标记化

提示通常遵循以下模板(由[8]调整而来)。我们将在接下来的章节中讨论每一部分。

[Art form] of [subject] by [artist(s)], [detail 1], ..., [detail n]

在 prompt engineering 的上下文中,标记化描述了将文本分成更小的单元(标记)。对于提示工程,您可以使用逗号(,)、竖线(|)或双冒号(::)作为硬分隔符[6,10]。然而,标记化的直接影响并不总是清楚的[6]。

1.科目

提示最重要的部分是主题。【2,8】你想看什么?虽然这可能是最简单的,但就您想要提供的细节数量而言,这也是最困难的。

提示:“一只戴墨镜的猫”(图片由作者用 DreamStudio 制作)

复数

像“猫”这样模糊的复数词,留下了很大的解释空间[6]。你是说两只猫还是 13 只猫?所以,当你想要多个主语时,用复数名词搭配特定的数字[6]。

提示:“戴墨镜的猫”(图片由作者用 DreamStudio 制作)

然而,据报道,虽然例如 DALL E2 在一个场景中创建多个对象没有问题,但它在将每个对象的某些特征彼此分开方面有所欠缺[11]。

虽然上面的图像是由 Stable Diffusion 的 DreamStudio 生成的,但它在下面的图像中显示了它的挣扎。你可以看到左边的猫没有戴墨镜。反而是那副墨镜好像飘在了猫的身后。

提示:“三只戴墨镜的猫”(作者用 DreamStudio 制作的图像)。

此外,据报道,DALL E2 可以很好地处理多达三个主题的提示,但超过三个主题的提示很难创建,即使你说“12”、“12”、“一打”,或者以多种方式说多次[6]。

在这个问题上,稳定扩散再次显示出与 DALL E2 的差异。然而,它也显示出精确地生成 12 只猫是困难的。

提示:“戴墨镜的十二只猫”(图片由作者用 DreamStudio 制作)

砝码

如果你想给一个特定的主题更重的权重,有各种各样的方法可以做到这一点。

  1. 顺序:靠*提示前面的标记比提示后面的标记权重更大。[10]
  2. 重复:用不同的措辞重复主题会影响其权重【8,12】。我还见过用不同语言或使用表情符号重复主题的提示。
  3. 参数:例如,在中途,您可以在提示的任何部分后面加上::weight来赋予其权重(例如::0.5)【6】。

不包括的项目:如接受服务项目是由投保以前已患有的疾病或伤害引致的

包含否定词如“不是”、“但是”、“除了”和“没有”的提示对于文本到图像生成模型来说是难以理解的[6]。虽然 Midjourney 有一个特殊的命令来处理这种情况(--no ) [7],但是你可以通过避免负面措辞而不是正面措辞你的提示[6]来绕过这个问题。

2.艺术形式

艺术形式是提示的重要组成部分。提示中常用的艺术形式有[2]:

  • 摄影:影楼摄影,拍立得,拍照手机等。

提示:“一只戴着太阳镜的猫的宝丽来照片”(图片由作者用 DreamStudio 制作)

  • 绘画:油画、肖像画、水彩画等。

提示:“戴太阳镜的猫的水彩画”(作者用 DreamStudio 制作的图像)

  • 插画:铅笔画、炭笔素描、蚀刻、漫画、概念艺术、海报等。

提示:“一只戴墨镜的猫的炭笔素描”(图片由作者用 DreamStudio 制作)

  • 数字艺术: 3D 渲染、矢量插图、低聚艺术、像素艺术、扫描等。

提示:“一只戴墨镜的猫的矢量插图”(图片由作者用 DreamStudio 制作)

  • 电影剧照:电影、央视等。

提示:“CCTV 一只戴墨镜的猫的剧照”(图片由作者与梦工厂合作制作)

如你所见,你甚至可以为每种艺术形式定义特定的媒介。例如,对于摄影,您可以通过定义类似[9]的细节来变得非常具体:

  • 胶片类型(黑白、拍立得、35mm 等。),
  • 取景(特写、广角等。),
  • 相机设置(快速快门、微距、鱼眼、运动模糊等。),
  • 照明(黄金时间、工作室照明、自然照明等。)

还有各种其他艺术形式,如贴纸和纹身。更多的灵感,可以看看[11]。

如果艺术形式未在提示中指定,则创成式模型通常会选择它在训练期间最常看到的形式。对许多学科来说,这种艺术形式就是摄影。

3.风格或艺术家

模板的另一部分可以严重影响生成图像的结果是风格或艺术家[6,8]。简单地使用“由[艺术家]”[11]或“以[风格或艺术家]的风格”。

提示:“梵高《戴墨镜的猫》油画”(作者用 DreamStudio 制作的图片)

生成有趣图像的两个技巧是:

  • 混合两个或多个艺术家〔2〕

提示:“梵高和安迪·沃霍尔的油画《戴着太阳镜的猫》(图片由作者用 DreamStudio 制作)

  • 使用虚构的艺术家[12]

提示:“马克斯·穆斯特曼(max mustermann)的一幅戴着太阳镜的猫的油画”(图片由作者用 DreamStudio 制作)

4.组合功能

关于组合艺术家以生成有趣的图像,您也可以组合两个定义明确的概念[6]。您可以尝试以下模板[11]:

- "[subject] made of"
- "[subject] that looks like"
- "[subject] as"

提示:“作为摇滚明星的猫”(图片由作者用 DreamStudio 制作)

5.形容词和质量助推器

添加像形容词和质量助推器这样的细节可以显著地影响你的图像的整体美感。

常用的形容词通常描述:

  • 取景(特写、风景、肖像、广角等。)
  • 配色方案(深色、淡色等。)
  • 灯光(电影灯光、自然光等。)
  • 其他:史诗般的,美丽的,令人敬畏的

但是也有一些社区已经发现的“神奇术语”似乎可以生成更好看的图像[2,8]:

  • “高度详细”

提示:“一只戴墨镜的猫,高细节”(图片由作者用 DreamStudio 制作)

  • “artstation 上的趋势”

提示:“一只戴墨镜的猫,在 artstation 上流行”(图片由作者用 DreamStudio 制作)

  • "在虚幻引擎中渲染"

提示:“一只戴墨镜的猫,在虚幻引擎中渲染”(图片由作者用 DreamStudio 制作)

  • “4k”或“8k”

结论

在本文中,您了解了如何设计一个提示,以更少的尝试生成带有文本到图像生成模型的图像。

我们讨论了如何从一个只包含“一只戴太阳镜的猫”这样的主题的提示中改进一个看起来可以接受的图像。

提示:“一只戴墨镜的猫”(作者用 DreamStudio 制作的图像)。

基本技巧是:

  • 定义精细的艺术形式(例如黑白照片)
  • 添加风格或艺术家(如 Annie Lebovitz)
  • 添加助推形容词(例如,高度详细)。

通过这些简单的技巧,得到的图像已经看起来更有趣了,如下图所示。

提示:“安妮·勒博维茨拍摄的一张戴着太阳镜的猫的黑白照片,非常详细”(图片由作者用 DreamStudio 制作)

喜欢这个故事吗?

以下是我收集的其他生成性人工智能文章:

Leonie Monigatti

莉奥妮·莫尼加蒂

生成人工智能

View list3 storiesA non-technical comparison of DALL·E2, Midjourney, and Stable DiffusionSugar cookies for Christmas

如果你想把我的新故事直接发到你的收件箱, 订阅

成为一名媒体成员,阅读更多来自其他作家和我的故事。报名时可以使用我的 推荐链接 支持我。我将收取佣金,不需要你额外付费。

https://medium.com/@iamleonie/membership

Twitter 上找我LinkedInKaggle**

参考

[1] R. Beaumont,“LAION-5B:开放大规模多模态数据集的新时代”,LAION . ai .https://laion.ai/blog/laion-5b/(2022 年 9 月 10 日访问)

[2] DreamStudio,《提示指南》。https://beta.dreamstudio.ai/prompt-guide(2022 年 9 月 10 日访问)

[3] DreamStudio,《一般问题》。https://beta.dreamstudio.ai/faq(2022 年 9 月 5 日访问)

[4]拥抱脸,“🧨扩散器的稳定扩散”,google.com。https://colab . research . Google . com/github/hugging face/notebooks/blob/main/diffusers/stable _ diffusion . ipynb # scroll to = GD-vx3 CAV oct

[5] J. Jang,“信贷是如何运作的”。openai.com。https://help . open ai . com/en/articles/6399305-how-Dall-e-credits-work(2022 年 9 月 4 日访问)【9】稳定 AI,《稳定扩散梦想工作室 beta 版服务条款》。稳定. ai .https://stability.ai/stablediffusion-terms-of-service(2022 年 9 月 5 日访问)

[6]中途,“医生”,github.com。https://github.com/midjourney/docs/(2022 年 9 月 10 日访问)

[7]中途,“中途文档”。git book . io .https://midjourney.gitbook.io/docs/(2022 年 9 月 4 日访问)

[8] J. Oppenlaender,文本到图像生成的提示修饰符分类(2022), arXiv 预印本 arXiv:2204.13988

[9] G .帕森斯,达尔 E 2 提示书 (2022 年),https://dallery.gallery/the-dalle-2-prompt-book/(2022 年 9 月 10 日访问)

[10]“pxan”,“如何获得不烂的图像:从稳定扩散获得酷图像的初级/中级指南”,reddit.com。https://www . Reddit . com/r/StableDiffusion/comments/x41n 87/how _ to _ get _ images _ that _ dont _ suck _ a/(2022 年 9 月 10 日访问)

[11]“rendo 1 # 6021”和“luc#0002”,“DALL E 2 Prompt 工程指南”,google.com。https://docs . Google . com/document/d/11 wlzjbt 0x rpqhp 9 tfmtxzd 0 q 6 anidhpubkmv-Yb 043 u/edit # heading = h . 8g 22 xmkqjtv 7(2022 年 9 月 10 日访问)

[12] M .泰勒,“提示工程:从文字到艺术”,虎耳草. XYZ .【https://www.saxifrage.xyz/post/prompt-engineering】T4(2022 年 9 月 10 日访问)

为 OCR 处理图像的更好方法

原文:https://towardsdatascience.com/a-better-way-to-process-images-for-ocr-aa634b94d573

我希望我能早点发现斯坎泰勒

在我的上一篇文章中,我介绍了如何使用 Python 代码为 OCR 处理图像。OCR 要求文本视图清晰,可以从左向右阅读。文本看起来越像刚打印出来的,结果就越准确。我最*写了一个更智能的算法,并发现了一个开源软件 ScanTailor,它在处理图像方面做得更好,特别是在几何扭曲(即文本卷曲)方面!我希望这种方法将有助于开发更精确的页面裁剪算法,并重新激起人们对改进 ScanTailor 的兴趣。查看上一篇文章了解图像处理每个步骤的更多细节。

对于相对干净的图像,前面的代码在将图像分割成单个文本列时工作得很好,但是当图像非常失真或“脏”时,性能就很差。该算法依赖于在列之间找到一个干净的空白,但是在所有的边上都有很多黑色的区域,不容易被剪掉。

新的算法方法使用 OpenCV 的 EAST 文本检测器来寻找所有文本的边界框。然后,我基于边界框所有最左边的垂直边缘创建了一个直方图。(边框的右边缘不如左边缘精确,这意味着单词的两端通常会被剪切掉)

“OpenCV 的 EAST text detector 是一种深度学*模型,基于一种新颖的架构和训练模式。它能够(1)在 720p 图像上以 13 FPS 的速度*乎实时地运行,并且(2)获得最先进的文本检测精度。”

OpenCV 的 EAST 文本检测器在扫描的页面上识别边界框。【图文由作者提供。Quién es quién en Colombia,第二版。(波哥大:奥利韦里奥·佩里,1948 年)。]

接下来,我根据最长的连续“空白”空间和边界框中从最高的后续尖峰开始的第二个文本列的起点来搜索白色中心列。第二个文本列的起点是裁剪位置。我们希望裁剪白色中间列的起点和第二个文本列的边缘的起点之间的点。但是,如果我们平均这些位置,作物位置往往太左。(我们这里的工作误差很小!)

图像上每个长度方向位置左边缘的边界框数量的直方图。[作者提供的图片]

但是有些页面是倾斜的,所以在图像旋转之前没有 0 度垂直线。我通过顺时针和逆时针旋转图像 0.5 度来解决这个问题。每次旋转都以最长的连续“空白”空间来计分。获取边界框花费了相当多的时间,所以如果分数≥8,为了加快代码速度,我选择了那个旋转角度。这个选择是基于观察和反复试验。

起初,我真的很高兴这些图像被裁剪得几乎完美,误差率为 0.4%——直到我运行 OCR。结果很糟糕。靠*装订的文本或文本扭曲的页面上的文本均未被读取。OCR 假定它要查找的文本可以放入一个矩形框中。

页面扭曲时,边界框位置的*似性较差。【图文由作者提供;由撰写,第二版。(波哥大:奥利韦里奥·佩里,1948 年)。]

在 Python 上寻找解开文本的方法时,我偶然发现了一个开源软件(直到生产阶段才完成,但自 2014 年以来一直没有维护过),名为 ScanTailor 。它执行页面分割、倾斜校正、内容选择、二进制化和边距修正,而实验版本还可以校正几何失真和斑点。

该软件并不完美,我不得不手动修改许多页面,但它的界面使简单的拖动和点击变得非常容易。我花了大约一个小时来纠正错误,但与花在编写实际代码上的更多时间相比,这是一个很好的权衡。另外,请注意,文本非常少的页面可能会被删除!

在处理之前的图像以修复几何扭曲、分割页面、选择内容和去斑之后,ScanTailor 程序的屏幕截图。【图文由作者提供;第二版。(波哥大:奥利韦里奥·佩里,1948 年)。]

ScanTailor 也快得多,处理我的图像批次需要 1 个小时,而我的代码需要 8 个小时。但是软件稍微不稳定,RAM 用多了就死机,所以我建议把每次运行分解成批次,每次保存。

论坛提供了几个其他选项, https://graphicdesign.stackexchange.com/questions/30948/best-way-to-flatten-a-curled-photographed-book-photograph 但这是我找到的最直接、最好的解决方案。(我用的是 Windows!)

这种无代码的解决方案对于那些没有技术技能的人来说绝对是一个很大的优势,所以我希望这篇文章能帮助你为 OCR 准备图像!

NeurIPS 2022 最大条目的简要指南

原文:https://towardsdatascience.com/a-brief-guide-to-the-biggest-entries-of-neurips-2022-b0f8e76d7f05

NeurIPS'22 提供了许多很好的选择

安德烈·斯特拉图在 Unsplash 上拍摄的照片

神经信息处理系统会议和研讨会(NeurIPS)是机器学*(ML)和计算神经科学方面最受尊敬的国际会议之一。对于neur IPS’22(11 月 28 日—12 月 9 日),新奥尔良被选为活动的主办城市,随后的第二周是虚拟部分。

自 1987 年成立以来,大会已经看到了相当多的突破性提交,包括墨菲 (1988)和神经象棋 (1994),以及最*的 Word2Vec (2013)和 GPT-3 (2020)。今年,* 3000 篇论文被接受。neur IPS’22 的议程如此之多,以下是对您有所帮助的内容——关于特别令人兴奋的主题的简要指南:

#1 联合学*

联合学*是当今的一个热门话题——它是一种解决与训练大型语言模型(如 GPT-3)相关的资源不足问题的方法。这些模型不仅非常昂贵(高达 1 亿美元),而且他们目前的训练方式也是不可持续的。

联合学*是一种涉及在边缘设备上进行 ML 模型训练而无需在它们之间进行数据交换的技术,这使得整个过程更便宜并且计算要求更低。今年有 3 个提交来解决这个问题:

来自阿里巴巴的这篇论文的作者提出了一个个性化联合学*方法的基准。另一篇论文提出了一种使联合和协作学*更有效的理论方法。最后这篇文章解释了如何通过联合学*获得更好的结果。

对于对这个话题感兴趣的人来说,还有一件事值得一试,那就是关于联合学*的国际研讨会。顺便说一句,所有 NeurIPS 研讨会基本上都是在主要活动中更侧重于主题的小型会议,因此您总能找到符合您兴趣的内容。

#2 基础和自回归模型

基础模型是对大量非结构化数据进行训练的模型,随后使用标记数据进行微调,以满足各种应用的需求(例如,BERT)。一个主要问题是,为了微调这些模型,必须引入额外的参数。这意味着在专业集群中持续使用 GPU,这很难获得和融资。

本文提出了一种分散且成本较低的方法来训练大型基础模型。另一篇论文提出了一种新的图像语言和视频语言任务的多模态基础模型。本文作者来自微软,探索如何从图像中提取书面信息,这涉及到连接计算机视觉(CV)和语言模型,从而产生一个能够产生可靠描述性段落的新系统。

还有这个综合性的 FMDM 研讨会,其主题围绕着调查基础模型和决策如何能够一起解决大规模的复杂任务。

#3 具有人类反馈的强化学*

强化学*一直是 NeurIPS 的主旋律。我们今天面临的一个主要问题是,大型模型生成的输出往往不符合用户的需求或意图。

撰写本文的研究人员介绍了他们使用人在回路方法对大型语言模型进行微调的情况,即如何利用管理人群来训练强化学*的奖励模型。这导致下游应用中预测质量的显著提高。另一个优势是预算更少——与最初的 GPT-3 模型相比,需要的可训练参数更少。

同一个主题推动了关于生成模型人工评估的研讨会,即如何成功地进行人工评估,以支持语言和 CV 的生成模型(如 GPT-3、DALL-E、CLIP 和 OPT)。

#4 更多研讨会、教程和比赛

除了我提到的研讨会,还有这个研究如何建立更可扩展的强化学*系统。还有这个深入探讨了如何建立更好的人在回路系统的问题。此外,如果你想从技术话题中抽身出来,看看未来的人工智能研究合作,也可以参加这个研讨会。

neur IPS’22 还提供 13 教程,提供实践培训和实践指导。我推荐查看这个关于数据集构建的教程,这个关于基础模型稳健性的教程,还有这个关于贝叶斯优化的教程。还有关于算法公平和社会责任人工智能的有用教程。

今年的大会有许多有趣的挑战和竞赛。其中一个是关于填充虚拟环境的最有效策略的视频游戏挑战。还有优化建模的自然语言挑战 (NL4Opt),以及大规模图形基准测试的 OGB-LSC ,这两个挑战都很有趣。

# 5 neur IPS’22 Socials

我也强烈推荐今年的社交活动,这是学*新事物的好方法,有机会亲身参与。与大多数研讨会相比,NeuroIPS 的社交活动更为非正式,每个参与者都有机会参与和表达自己的观点。每场社交活动都由一组组织者主持,他们引导讨论,总结所有的意见,然后发表总结性的评论。

neuro IPS’22 充满了有趣的条目——从 ML 和气候变化neuro IPS 的 K-Pop 爱好者(是的,你没听错)。例如,这个圆桌会议是关于注释者授权和数据优化,即如何解决数据标注者之间的分歧,获得采样多样性,以及建立对偏见免疫的 ML 系统。

总结

正如你所看到的,NeurIPS'22 提供了许多很棒的选项,我已经在这里列出了,还有一些我没有空间提及的。希望我的推荐能帮助你更好的组织时间,让你不要错过任何重要的东西。

嵌入式人工智能简史

原文:https://towardsdatascience.com/a-brief-history-of-embedded-ai-6109be99199d

智能手机上的传感器中枢如何帮助人工智能走向边缘

AIoT、TinyML、EdgeAI 和 MLSensors 是人工智能和嵌入式系统社区的最新热门词汇。围绕设计高度优化、低占用空间的机器学*算法有很多宣传,这些算法运行在超低功耗微控制器和 DSP 上,目标是传感应用,包括但不限于音频和视频。这种方法在“始终在线”的应用中特别有用,在这些应用中,功耗需要最小化,并且侵犯隐私是一个严重的问题。

一些现有的应用程序已经利用了嵌入式智能:
-唤醒词检测:“Ok Google”和“Hey Siri”是我们几乎每天都在使用的主要例子
-车祸检测:较新的智能手机可以融合来自麦克风、惯性传感器和 GPS 等多个传感器的数据,以便在发生车祸时检测和警告紧急服务
-计步和锻炼检测:可穿戴设备使用来自惯性和生物传感器的数据以及智能算法来跟踪日常活动。

照片由克利姆·穆萨利莫夫Unsplash 上拍摄

所有这些情况的共同点是使用与传感器紧密耦合的专用低功耗处理硬件,运行高度优化的算法来做出相关的推断。另一个共性是它们都是更复杂设备的子系统。

由于这些发展,在不久的将来,我们可能会在其他独立设备中看到嵌入式智能,例如:
-完全离线的智能摄像头来检测人类的存在【1】
-环境传感器来检测森林火灾【2】和非法砍伐树木【3】

https://blog.tensorflow.org/2019/10/visual-wake-words-with-tensorflow-lite_30.html

嵌入式智能的日益普及和广泛应用令人振奋。
但是这一切是从哪里开始的?以下是我对“传感器中枢”,尤其是智能手机中的传感器中枢如何推动这场运动的快速总结。

但是首先,什么是传感器集线器?
根据定义,它是一个协处理器、微控制器或 DSP,负责整合数据,并提供来自设备中多个低功耗传感器的简化见解,以释放主应用处理器的处理带宽。

传感器集线器最初是智能手机中一个简洁的电源优化技巧。早在 iPhone 5s 和 Nexus 5X 和 6P 的手机分别采用了苹果 M7 协处理器[4]和安卓传感器中枢[5]。苹果使用 M7 来处理加速度计、陀螺仪和指南针等要求苛刻的惯性传感器以及传感器融合算法,而 Android Sensor Hub 也做了同样的事情,并运行高级活动识别算法。
摩托罗拉在惯性传感器功能上进一步创新,用朗朗上口的“快快手势打开手电筒。

Android 传感器中枢介绍(19:28)

我们也开始看到传感器(麦克风)的重叠和运行在低功耗处理器上的机器学*随着像“嘿 Siri”和“Ok Google”这样的唤醒词检测而变得流行。这些功能通过快速短语被推向了更高的高度,现在正在最新的 Pixel 手机上播放。

丹尼尔·罗梅罗在 Unsplash 上的照片

因此,在过去的 6-7 年中,智能手机及其传感器中枢被证明是完美的概念证明,向世界展示了在微控制器和 DSP 等极低功耗计算平台上部署传感器的机器学*算法是可能的。
很高兴看到这项运动以 TinyML、EdgeAI 和 MLSensors 社区的形式获得了自己的名字和独立受众。

有趣的是,半导体巨头如 ADI 公司、TDK 和罗伯特·博世设计并制造了大量用于智能手机的传感器,它们对传感器集线器有自己独特的看法。

而目标仍然是一样的:从多个传感器提供有用的见解,同时消耗尽可能少的功率。应用要广泛得多。由于智能手机已经拥有自己的传感器中枢,因此正在为可穿戴设备、汽车和其他智能设备开发独立的传感器中枢。

智能手机上的传感器集线器最初是分立元件。M7 运动协处理器是一款基于恩智浦 LPC18A1 的独立芯片。但随着时间的推移,这些协处理器被集成到主智能手机 SoC 中。

苹果 A7 与 M7 并排(LPC18A1):来源— 维基共享资源

然而,半导体和传感器制造商仍可提供分立传感器集线器。他们将人工智能和传感器结合起来,以实现像游泳教练【8】这样的利基用例。
通常只有微控制器,通常是 ARM Cortex M 系列,与传感器紧密耦合,并预装算法以支持特定用例。这对这些传感器的制造商来说非常好,因为他们不仅能够将硬件货币化,而且能够将他们为这些传感器开发的算法货币化。
对于使用这些“智能传感器”开发自己的小工具的公司来说,这也很好,因为他们不需要花时间开发利基算法,而是可以专注于系统集成。

传感器集线器仍处于非常初期的阶段,主要使用通用微控制器,但随着硬件的不断改进,可能性是无限的。传感器本身变得越来越精确。ARM v9 及其对 DSP 和 ML 功能的关注将极大地扩展可在嵌入式设备上实现的模型集。Ethos U-55 是 ARM 的一个 microNPU(神经处理单元),它可以很快找到通往已经实施 ARM IPs 的传感器中枢的道路[9]。许多像 Syntiant 这样的初创公司也在开发边缘神经网络推理的专用硬件。

传感器世界即将迎来激动人心的时代!敬请关注更多关于 EdgeAI 和智能传感器的思考…

参考和链接

[1]https://blog . tensor flow . org/2019/10/visual-wake-words-with-tensor flow-lite _ 30 . html
【2】https://www . bosch . com/stories/early-forest-fire-detection-sensors/
【3】https://www.mdpi.com/1424-8220/21/22/7593
【4】https://en.wikipedia.org/wiki/Apple_motion_coprocessors
【5】https://www . androidflow

几何深度学*简介

原文:https://towardsdatascience.com/a-brief-introduction-to-geometric-deep-learning-dae114923ddb

复杂数据的人工智能

西蒙·李在 Unsplash 上的照片

持续学*很难。虽然通用逼*定理表明足够复杂的神经网络原则上可以逼*“任何东西”,但并不能保证我们能找到好的模型。

然而,通过明智地选择模型架构,深度学*取得了巨大进展。这些模型架构对[归纳偏差](https://en.wikipedia.org/wiki/Inductive_bias#:~:text=The inductive bias (also known,predict a certain target output.)进行编码,为模型助一臂之力。最强大的归纳偏见之一是利用几何的概念,产生了几何深度学*的领域。

几何深度学*这个术语首先是由该领域的先驱迈克尔·布朗斯坦提出的(参见他的帖子中关于许多最新深度学*研究的有趣见解,以及该领域的广泛概述)。在这篇文章中,我们将对几何深度学*做一个简单的介绍,而不是深入技术领域。我们在很大程度上遵循了布朗斯坦及其同事的优秀新书[1],但提供了我们自己独特的观点,并专注于高层次的概念,而不是技术细节。

几何先验

从根本上说,几何深度学*涉及将对数据的几何理解编码为深度学*模型中的归纳偏差,以帮助它们。

我们对世界的几何理解通常通过三种类型的几何先验进行编码:

  1. 对称性和不变性
  2. 稳定性
  3. 多尺度表示

最常见的几何先验之一是将对称性和不变性编码为不同类型的变换。在物理学中,对称性通常由物理系统在变换下的不变性来表示。如果我们知道现实世界表现出某些对称性,那么将这些对称性直接编码到我们的深度学*模型中是有意义的。这样我们就能给模型一个有力的帮助,这样它就不必学*对称性,但在某种意义上已经知道了。在我们之前关于 的文章中进一步阐述了在深度学*中利用对称性,爱因斯坦可以教给我们关于机器学*的什么

作为编码对称性和不变性的一个例子,传统的卷积神经网络(CNN)表现出所谓的平移等方差,如下图中猫的脸所示。考虑模型的特征空间(在右边)。如果相机或猫移动,即在图像中被平移,特征空间中的内容应该更相似,即也被平移。这种特性被称为平移等方差,在某种意义上确保了一个模式(猫的脸)只需要学*一次。我们不必在所有可能的位置学*模式,而是通过在模型本身中对平移等变进行编码,确保模式可以在所有位置被识别。

平移等方差图。给定一幅图像(左上),计算特征图(𝒜)(右上),然后平移(𝒯)特征图(右下),相当于首先平移图像(左下),然后计算特征图(右下)。【图表由作者创作,首次呈现此处。]

另一个常见的几何先验是确保表示空间的稳定性。我们可以认为数据实例之间的差异是由于某种失真造成的,这种失真会将一个数据实例映射到另一个数据实例。例如,对于一个分类问题,小的失真会导致一个类内的变化,而较大的失真会将数据实例从一个类映射到另一个类。然后,两个数据实例之间的失真的大小捕获一个数据实例与另一个数据实例有多“接*”或相似。为了使表示空间表现良好并支持有效的深度学*,我们应该保留数据实例之间的相似性度量。为了保持表示空间中的相似性,特征映射必须表现出稳定性。

作为一个代表性的例子,考虑手写数字的分类。原始图像空间及其表示空间如下图所示。小的变形将一个 6 映射到另一个,捕捉手绘 6 的不同实例之间的类内变化。在表示空间中,这些数据实例应该保持接*。然而,更大的失真可以将 6 映射到 8,从而捕捉到类间变化。同样,在表示空间中,相似性的度量应该被保留,因此在表示空间中 6s 和 8s 之间应该有更大的间隔。需要特征映射的稳定性,以确保保持这样的距离,从而促进有效的学*。

到表示空间的映射的稳定性的说明。小失真是类内变化的原因,而大失真是类间变化的原因。需要映射的稳定性,以确保数据实例之间的相似性度量,即它们之间的失真大小,被保存在表示空间中,以便促进有效的学*。[作者为[2]创建的图表。]

第三种常见的几何先验是对数据的多尺度、分级表示进行编码。在一个数据实例中,许多数据不是独立的,而是以复杂的方式相互关联。以一幅图像为例。每个图像像素不是独立的,而是相邻的像素通常是相关的并且非常相似。取决于内容结构,“附*”的不同概念也是可能的。因此,通过捕捉大量数据的多尺度、分层性质,可以构建有效的表示空间。

考虑一个标准的 2D 图像作为例子,例如下面显示的城堡图像。下图显示了图像的多比例、分层表示,左上角是原始图像的低分辨率版本,然后是在图表的其他面板中捕获的不同分辨率的剩余图像内容。这为底层图像提供了更有效的表现方式,事实上,这也是推动 JPEG-2000 图像压缩的技术。可以利用类似的多尺度、分层表示来为学*提供有效的表示空间。

图像的多尺度、分层表示。原始图像的低分辨率版本显示在左上角,然后在图表的其他面板中捕获不同分辨率的剩余图像内容。可以利用类似的表示为学*提供有效的表示空间。【来源维基百科。]

我们已经介绍了几何深度学*中利用的三种主要类型的几何先验。虽然这些提供了几何学*的基本概念,但它们可以应用于许多不同的设置。

几何深度学*的类别

在布朗斯坦的新书[1]中,几何深度学*被分为四个基本类别,如下图所示。

几何深度学*的类别。[图片来源于文章[1],经过许可,添加了带注释的概述和示例。]

布朗斯坦谈到了 5Gs(扩展了 Max Welling [1]首先提出的 4G 分类):网格;团体;图表;测地线和量规。由于这最后两个 g 密切相关,我们只考虑四个不同的类别,即 4g。

网格类别捕获定期采样或网格化的数据,如 2D 图像。这些数据可能通常是经典深度学*的产物。然而,也可以从几何角度解释许多经典的深度学*模型(如 CNN 及其平移等变,如上所述)。

类别涵盖了具有全局对称性的同质空间。这个类别的典型例子是球体(在我们的上一篇文章【3】中有更详细的介绍)。球形数据出现在 myrad 应用中,不仅是在球体上直接获取数据时(如在地球上或通过 360°相机捕捉全景照片和视频),而且在考虑球形对称性时(如在分子化学或磁共振成像中)。虽然球体是最常见的组设置,但也可以考虑其他组及其相应的对称性。

类别包括可以由计算图表示的数据,带有节点和边。网络非常适合这种表示,因此图深度学*在社会网络的研究中得到了广泛的应用。几何深度学*的图形方法提供了很大的灵活性,因为许多数据可以用图形来表示。然而,这种灵活性可能伴随着特异性和所提供的优势的损失。例如,通常可以用图表方法来考虑群体设置,但是在这种情况下,人们失去了群体的潜在知识,而这些知识本来是可以利用的。

最后一个测地线和量规类别涉及对更复杂形状的深度学*,比如更通用的流形和 3D 网格。这种方法在计算机视觉和图形学中非常有用,例如,人们可以利用 3D 模型及其变形进行深度学*。

积木

尽管如上所述,存在许多不同类别的几何深度学*,以及可以利用的不同类型的几何先验,但是几何深度学*的所有方法本质上都采用了以下基础构件的不同体现。

几何深度学*的所有方法都利用了一组核心的基本基础构件。【图片由Unsplash 上拍摄。]

深度学*架构通常由许多层组成,这些层组合在一起形成整体的模型架构。然后经常重复层的组合。几何深度学*模型通常包括以下类型的层。

  1. 线性等变层:几何深度学*模型的核心组件是线性层,比如卷积,它对于某种对称变换是等变的。线性变换本身需要为所考虑的几何范畴而构造,例如球和图形上的卷积是困难的,尽管经常有许多类比。
  2. 非线性等变层:为了确保深度学*模型具有足够的表示能力,它们必须表现出非线性(否则它们只能表示简单的线性映射)。必须引入非线性层来实现这一点,同时还保持等方差。以等变方式引入非线性的标准方法是通过逐点非线性激活函数(如 ReLUs)来实现,尽管有时也考虑其他形式的非线性,特别是针对基础几何形状[3]。
  3. 局部平均:大多数几何深度学*模型也包括一种形式的局部平均,如 CNN 中的 max pooling layers。此类操作会在特定比例下施加局部不变性,从而确保稳定性,并通过堆叠多个图层块来实现多比例、分层的表示。
  4. 全局平均:为了在几何深度学*模型中施加全局不变性,通常采用全局平均层,例如 CNN 中的全局池层。

几何深度学*模型的典型例子是用于 2D 平面图像的传统 CNN。虽然许多人可能认为这是一个经典的深度学*模型,但它可以从几何角度进行解释。事实上,CNN 如此成功的一个关键原因是其架构中的几何属性。下图概述了一个典型的 CNN 架构,其中很明显包括了上面讨论的许多几何深度学*层,重复的层块提供了一个分层的多尺度表示空间。

VGG-16 卷积神经网络(CNN)架构。虽然 CNN 通常被认为是分类的深度学*模型,但它们可以从几何角度解释,利用几何深度学*模型的核心类型。[图片来源。]

未来展望

深度学*现在对于标准类型的数据来说是司空见惯的,例如结构化数据、序列数据和图像数据。然而,为了将深度学*的应用扩展到其他更复杂的几何数据集,这些数据的几何必须编码在深度学*模型中,从而产生了几何深度学*领域。

几何深度学*是一个热门且快速发展的领域,已经取得了很多进展。然而,许多未解决的问题仍然存在,不仅在模型本身,而且围绕可扩展性和实际应用。我们将在接下来的文章中解决这些问题,展示解决这些问题对于释放深度学*在大量新应用中的巨大潜力是如何至关重要的。

参考

[1]布朗斯坦,布鲁纳,科恩,维利科维奇,几何深度学*:网格,群,图,测地线,和量规 (2021), arXix:2104.13478

[2] McEwen,Wallis,Mavor-Parker,可扩展和旋转等变球形 CNN 的球上散射网络,ICLR (2022), arXiv:2102.02828

[3]科布,沃利斯,马沃-帕克,马利涅尔,普莱斯,达韦扎克,麦克尤恩,高效广义球面 CNN,ICLR (2021), arXiv:2010.11661

神经网络简介:一个回归问题

原文:https://towardsdatascience.com/a-brief-introduction-to-neural-networks-a-regression-problem-c58c26e18008

Python 神经网络实用入门指南

照片由细川玉子达摩Unsplash

想学机器学*又不知道从何入手?这个教程就是为你准备的!在本教程中,您将学*机器学*的基础知识:从模型概念开始到验证。

目录

1。
简介 2。库
3。问题理解
4。数据准备和预处理
5。模型构思
5.1 人工神经元模型
5.2 激活功能
5.3 层
5.4 多层模型
6 .训练
7。验证
7.1 学*曲线
7.2 测试集评测
7.3 评测指标
7.4 显示所学函数
8。结论

1.介绍

机器学*的目标是提出一个模型(一种方法)来预测输入实例的值、类或聚类。该模型是通过利用现有数据(数据集)构建的。为了实现这样的模型,考虑一组步骤,即:

  • 问题理解。
  • 数据准备和预处理。
  • 模型构思。
  • 训练模型。
  • 模型评估和验证

2。库

在本教程中,我们将使用:

  • 熊猫:用于数据操作(导入和拆分数据)。
  • keras: 用于模型构思和训练。
  • matplotlib: 用于数据可视化。
  • scikit-learn (sklearn): 用于额外的指标计算。

我们还添加了以下代码行来重现每次执行的结果:

3.问题理解

理解问题是要考虑的第一步。它包括识别问题的性质:它是一个回归问题(预测一个真实值,如用户对不同产品的参与度)还是一个分类问题(预测一个输入实例的类别,如鸢尾花的分类或情感分析)。有时候回归会给出不好的结果,在这种情况下,你要考虑把它变成分类问题的可能性。比如:把预测用户参与度的问题转化为对他的参与度进行分类(0 参与度、低参与度、中参与度、高参与度)的问题。

例如,我创建了一个玩具数据集,你可以从我的 GitHub 库下载。数据集代表一个信号,本例的目的是学*信号函数,以便我们可以将其用于未来的预测。在下一节中,我们将通过代码来探索它。

4.数据准备和预处理

使用神经网络解决给定的问题意味着利用数据集。在此之前,需要对数据集进行预处理。数据预处理通常包括清理(替换缺失值和移除异常值)、离散化(将连续数据转换为数据损失最小的有限区间集)和归一化(如将值归一化为 0 到 1 之间)。这一步使用的技术取决于数据集的性质和/或随后使用的模型的性质。在数据预处理过程中,可视化数据以确保数据得到正确处理是非常重要的。在这一阶段结束时,数据集通常被分成 2 组:

  • 训练集:用于设置神经网络中的权重、线性回归中的系数等模型参数。
  • 验证集:用于评估模型在训练过程中针对新数据(不属于训练集)的行为。

在这篇文章中,我们的主要焦点是神经网络方面。因此,我们将使用不需要预处理的数据集。

让我们从导入数据集开始,打印前 5 行并绘制散点图:

There are 510 instances.
         x       y
0  10.5392  1.2058
1   5.1571  2.6770
2  12.6563  3.1471
3  11.7546  2.3668
4  10.9499  2.3400

初始数据集

数据集包括 510 行(实例)和两列:单个要素(x)和要学*的值,也称为目标(y)。这两个特征都是实数。

现在,我们将数据集随机分为两组:训练集和验证集。30%的数据将用于验证,70%用于培训:

将数据分成训练集和验证集

数据集现在可以用于下一步了。我们将不应用任何进一步的数据预处理。最重要的是没有丢失的值,因为神经网络不能处理丢失的值。我们将在接下来的教程中看到如何处理它们,以及如何应用进一步的数据预处理;同时你可以查看其他媒体文章。注意,我提供了另一个数据集‘test . CSV’作为测试集。这里,测试集是从原始数据集中随机选择的。它将用于测试训练后的模型。

5.模型概念

一旦理解了问题并准备好了数据,模型概念就产生了。设计一个神经网络模型包括:定义每层神经元的数量,定义层数,定义激活函数。在本节中,我们将探索 Keras 提供的不同类型的层。然后,给出了激活函数。最后,我们将创建我们的多层感知器神经网络。但首先,让我们了解一下单个神经元模型(也称为节点)。

5.1 人工神经元模型

下图描述了一个人工神经元模型:

人工神经元模型。

单个神经元模型表示为:

  • 权重(W0、..,Wn)。权重定义了其相关输入对神经元输出的影响。权重在训练之前被初始化,并且在训练期间被更新。
  • 偏见(b) 。偏差是一个与输入无关的常数。我们可以把它看作是用来抵消结果的截距。类似于权重,偏差在训练之前被初始化,并且在训练期间被更新。
  • 激活函数(f)。激活函数根据加权和定义节点的输出。如果激活函数是线性的,则返回加权和。Keras 提供了几个激活函数,我们稍后会看到。

人工神经元的输入(x0,..xn)可以是输入数据集(特征)或其他人工神经元的输出。

5.2 激活功能

Keras 提供了一组丰富的激活函数,其中我们列举了:线性函数、 sigmoid 函数、 tanh 函数、 softplus 函数、 softsign 函数、 ReLU ( 整流线性单元 ) 函数、 SELU ( 比例指数线性单元 ) 函数这些功能如下图所示。我们在[-10,+10]中设置 x。

Keras 提供的常用激活功能。

了解函数的共域很重要,这样您就可以为模型输出定义适当的函数。我一般在选择输出激活功能的时候会参考上图。假设我们的模型要学*一个线性函数(一个来自ℝto ℝ的函数),RELU 不能使用,因为它的余域是正实数的集合。我们将在后面看到激活函数对模型输出的影响。

对于分类问题,还有其他的激活函数。这些将不会在本教程中讨论,而是在下一个教程。但是,您可以在 Keras 激活功能参考中找到更多详细信息。

5.3 层

既然引入了单神经元模型,那么如果我们有多个神经元(节点)呢?具有相同输入的一组节点构建一个层(请参见下图)。同一层的节点通常具有相同的激活函数。

单层模型。

根据文档,Keras 提供多种类型的层。每一层都有自己的参数和功能。有通常用于 2D 和 3D 数据的卷积图层和汇集图层,有通常用于归一化和正则化图层输入的归一化图层和正则化图层,还有核心图层。在本教程中,我们只对作为核心层的输入层和密集层感兴趣。

输入层用于定义模型输入的特征数量(shape)。在 Keras 中,它主要被定义为:

keras.Input(
    shape=None,
    **kwargs
)

密集层是 Keras 中称为单元的一组节点。它主要被定义为:

keras.layers.Dense(
    units,
    activation=None,
    use_bias=True,
    kernel_initializer="glorot_uniform",
    bias_initializer="zeros",
    **kwargs
)

其中:

  • units是这一层的神经元数量。
  • activation是机组的激活功能。
  • use_bias是一个布尔值,用于指定是否使用偏差。默认情况下,它设置为 True。
  • kernal_initializerbias_initializer分别定义如何初始化权重和偏差。默认情况下,它们分别被设置为glorot_uniformzeros

你可以在 Keras layers API 中找到更多关于层的信息。

5.4 多层模型

一系列层定义了一个多层模型。通常,当前层的每个节点都连接到前一层/下一层的所有节点。这种类型的模型称为全连接网络。模型中有三种类型的层:

  • 输入层是第一层,它的输入是实例特征:模型的输入。
  • 输出层是最后一层,其输出是整个模型的输出。该层的激活函数必须根据所需的输出仔细选择。
  • 隐藏层是输入层和输出层之间的层。

现在继续写一些代码,让我们创建我们的模型!该模型包括:定义要素数量的输入层、具有 200 个单元的三个隐藏层以及每个层的 sigmoid 激活函数,最后是具有单个单元和线性激活函数的输出层:

_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense (Dense)               (None, 200)               400       

 dense_1 (Dense)             (None, 200)               40200     

 dense_2 (Dense)             (None, 200)               40200     

 dense_3 (Dense)             (None, 1)                 201

功能model.summary()显示模型架构。对于每个层,显示其类型、输出形状(单位数)和权重数。dense_1的权重的数量计算如下:作为先前密集层的单元的输入的数量是 200,并且每个单元连接到所有先前的单元,因此我们有200 * 200 = 40000权重。此外,每个单位都有偏差,那么我们总共有 200 个偏差。因此,参数总数为40000 + 200 = 40200。请问densedense_3的参数个数是怎么计算出来的?

我们的模型已经准备好接受训练了!

6.培养

那么什么是培训呢?训练是找到尽可能符合训练数据的权重。换句话说,它是更新权重的过程,以使预测值和地面真实值(也称为目标值)之间的差异非常小。当目标是找到最佳权重以最小化模型输出和目标之间的差异时,我们也可以将其视为优化问题。

前馈网络的训练算法

那么训练是如何进行的呢?为了回答这个问题,我们以简单的方式描述不同的步骤。

**1\. Initialize the weights and bias.****2\. Forward pass: compute the model output.****3\. Measure the error between the target and the output using the loss function.****4\. Backward pass: propagate the error and update the weights and bias using an optimizer.****5\. Repeat the steps 2 to 4 for every batch.****6\. Repeat the steps from 2 to 5 *epochs* times.**

我敢打赌,对于绝对初学者来说,有很多新单词。别急,我们来一一介绍一下:

  • 优化器。它会更新可训练重量。Keras 提供了一套优化器,其中有 SGD (随机梯度下降)和 Adam
  • 损失函数。它衡量模型与训练集的拟合程度;换句话说,它测量训练期间预测和实际情况之间的差异。Keras 实现了不同类型的损失函数。在回归损失函数中,有 MSE(均方误差)和 MAE(平均绝对误差)。
  • 批次。批次是用于更新重量的样本数量。在训练期间,数据被分成批次,并且根据每一批次更新权重。
  • 纪元。一个时期是所有训练数据都被使用的时候。时期的数量定义了权重沿着所有训练数据更新的次数。是停止训练的条件之一。
  • 学*率。学*率控制权重如何受到传播误差的影响;一般设置在[0,1]之间,训练时可以更新。例如,SGD 和 Adam 的默认学*率分别等于 0.01 和 0.001。

在 Keras 中,开始训练之前要做的第一件事是编译您的模型,这意味着通过传递一些参数来配置您的模型,如:损失函数、优化器和训练期间要跟踪的指标。在这里,我们指定了将作为字符串使用的优化器,这意味着将使用默认参数,包括学*率。为了训练模型,调用函数[fit()](https://keras.io/api/models/model_training_apis/#fit-method)。在这个例子中,我们传递了训练数据(x_tainy_train)、历元数和批量大小作为参数。

verbose=1、损失(lossval_loss)和度量(maeval_mae)值分别在训练和验证集的每个时期结束时打印时:

Epoch 1749/1750
6/6 [==============================] - 0s 9ms/step - loss: 0.0554 - mae: 0.1955 - val_loss: 0.0596 - val_mae: 0.2085
Epoch 1750/1750
6/6 [==============================] - 0s 6ms/step - loss: 0.0539 - mae: 0.1966 - val_loss: 0.0586 - val_mae: 0.2062

7.确认

显示的上一个时期的指标不足以断定模型是否已经从数据中学*。首先要观察的是学*曲线。然后,我们可以使用其他指标来评估我们的模型。如果可以的话,我们也可以在测试设备上测试它。

7.1 学*曲线

学*曲线是我在培训结束后观察到的第一个指标。它揭示了模型在训练期间对可见数据(训练集)和不可见数据(验证集)的性能。

学*曲线

7.2 测试集的评估

通常,测试集与数据集一起提供。它用于评估训练后的模型。有时,您需要自己将数据集分成训练集、验证集和测试集。但是,当数据集较小时,不考虑测试集。正如我前面提到的,在本教程中,测试集是随数据集一起提供的。

让我们在测试集上评估我们的模型,看看它是如何工作的。我们首先导入测试集,然后调用方法[evaluate()](https://keras.io/api/models/model_training_apis/#evaluate-method),该方法返回训练期间使用的损失和度量:

Test set: - loss: 0.05698258429765701 - mae: 0.20276354253292084

7.3 评估指标

在培训过程中,使用了以下指标:

  • 被用作损失函数的均方差(MSE)
  • 平均绝对误差(MAE) 这是一个额外的指标。

我们可以使用其他指标来评估我们的模型,我选择了另外两个我感兴趣的指标:

  • 中值绝对误差(MedAE) 对异常值稳健,因为它采用中值而不是目标和预测之间所有绝对差异的平均值。
  • 平均绝对百分比误差(MAPE) 对相对误差敏感,不受目标变量全局缩放的影响:它计算误差百分比。
Displaying other metrics:
         MedAE   MAPE
Train:   0.173   0.112
Val  :   0.17    0.119
Test :   0.198   0.115

7.4 显示已学*的功能

即使计算出的指标显示模型很好地符合数据集,直观地看到它有多好也是很重要的。这里,我们用x来画学*过的函数:

已学*的功能。红色:用 x 表示的函数。蓝色:数据集。

该模型已经学*了类似余弦的函数。

8.结论

本文到此为止!在本文中,我们学*了如何创建神经网络,并针对回归问题对其进行训练和验证。我们将在接下来的教程中学*更难的例子。如果你问我这是不是这个问题有史以来最好的模型,我马上说:不是!你可以做得更好!您可以通过改变层数和单元数来开始玩模型架构。您还可以更改训练参数并比较结果。你可以在下面的评论里和我分享你的解决方案!

这是我在机器学*方面的第一篇文章,绝对不是最后一篇!我会写更多关于它的教程(机器学*的数据预处理,分类的神经网络,情感分析,等等)。),敬请期待!

谢谢,我希望你喜欢读这篇文章。你可以在我的 GitHub 库中找到例子。如果你有任何问题或建议,欢迎在下面给我留言。

图像制作者名单

除非另有说明,所有图片均为作者所有。

递归神经网络简介

原文:https://towardsdatascience.com/a-brief-introduction-to-recurrent-neural-networks-638f64a61ff4

RNN、LSTM 和 GRU 及其实施情况介绍

RNN、LSTM 和 GRU 的牢房。

如果您想要对序列或时间序列数据(例如,文本、音频等)进行预测。)传统的神经网络是一个糟糕的选择。但是为什么呢?

在时间序列数据中,当前的观测值依赖于以前的观测值,因此观测值不是相互独立的。然而,传统的神经网络认为每个观察都是独立的,因为网络不能保留过去或历史信息。基本上,他们对过去发生的事情没有记忆。

这导致了递归神经网络(RNNs)的兴起,它通过包括数据点之间的依赖性,将记忆的概念引入神经网络。这样,可以训练 rnn 基于上下文记住概念,即学*重复的模式。

但是 RNN 人是如何获得这种记忆的呢?

RNNs 通过细胞中的反馈回路实现记忆。这是 RNN 和传统神经网络的主要区别。与信息仅在层间传递的前馈神经网络相比,反馈回路允许信息在层内传递。

然后,rnn 必须定义哪些信息足够相关,可以保存在存储器中。为此,不同类型的 RNN 进化而来:

  • 传统递归神经网络(RNN)
  • 长短期记忆递归神经网络(LSTM)
  • 门控递归单元递归神经网络(GRU)

在这篇文章中,我将向你介绍 RNN、LSTM 和格鲁。我将向你展示他们的相同点和不同点以及一些优点和缺点。除了理论基础,我还向您展示了如何使用tensorflow在 Python 中实现每种方法。

递归神经网络(RNN)

通过反馈回路,一个 RNN 单元的输出也被该单元用作输入。因此,每个单元有两个输入:过去和现在。利用过去的信息会产生短期记忆。

为了更好地理解,我们展开 RNN 单元的反馈回路。展开单元的长度等于输入序列的时间步长数。

展开的递归神经网络。

我们可以看到过去的观察是如何通过展开的网络作为隐藏状态传递的。在每个单元中,当前时间步长的输入 x (当前值)、先前时间步长的隐藏状态 h (过去值)和偏差被组合,然后被激活函数限制以确定当前时间步长的隐藏状态。

这里,小而粗的字母代表向量,大写的粗字母代表矩阵。

通过时间反向传播(BPTT)算法更新 RNN 的权重 W

rnn 可用于一对一、一对多、多对一和多对多预测。

RNNs 的优势

由于它们的短期记忆,rnn 可以处理顺序数据并识别历史数据中的模式。此外,rnn 能够处理不同长度的输入。

RNNs 的缺点

RNN 遭受消失梯度下降。在这种情况下,用于在反向传播期间更新权重的梯度变得非常小。将权重与接*零的梯度相乘可以防止网络学*新的权重。这种学*的停止导致 RNN 人忘记了在更长的序列中看到的东西。消失梯度下降的问题增加了网络的层数。

由于 RNN 只保留了最*的信息,这个模型在考虑很久以前的观测数据时存在问题。因此,RNN 倾向于在长序列上丢失信息,因为它只存储最新的信息。因此,RNN 只有短期记忆,而没有长期记忆。

此外,由于 RNN 及时使用反向传播来更新权重,所以网络还遭受爆炸梯度,并且如果使用 ReLu 激活函数,还遭受死 ReLu 单元。前者可能会导致收敛问题,而后者可能会停止学*。

RNNs 在 tensorflow 中的实现

我们可以使用tensorflow在 Python 中轻松实现 RNN。为此,我们使用Sequential模型,它允许我们堆叠 RNN 的层,即SimpleRNN层类和Dense层类。

from tensorflow.keras import Sequential
from tensorflow.keras.layers import SimpleRNN, Dense
from tensorflow.keras.optimizers import Adam

只要我们想使用缺省参数,就没有必要导入优化器。然而,如果我们想要定制优化器的任何参数,我们也需要导入优化器。

为了构建网络,我们定义了一个Sequential模型,然后使用add()方法添加 RNN 层。要添加一个 RNN 层,我们使用SimpleRNN类并传递参数,比如单元数量、退出率或激活函数。对于我们的第一层,我们也可以传递输入序列的形状。

如果我们堆叠 RNN 层,我们需要设置前一层的return_sequence参数为True。这可确保图层的输出对于下一个 RNN 图层具有正确的格式。

为了生成一个输出,我们使用一个Dense层作为我们的最后一层,传递输出的数量。

# define parameters
n_timesteps, n_features, n_outputs = X_train.shape[1], X_train.shape[2], y_train.shape[1]

# define model
rnn_model = Sequential()
rnn_model.add(SimpleRNN(130, dropout=0.2, return_sequences=True, input_shape=(n_timesteps, n_features)))
rnn_model.add(SimpleRNN(110, dropout=0.2, activation="tanh", return_sequences=True))
rnn_model.add(SimpleRNN(130, dropout=0.2, activation="tanh", return_sequences=True))
rnn_model.add(SimpleRNN(100, dropout=0.2, activation="sigmoid", return_sequences=True))
rnn_model.add(SimpleRNN(40, dropout=0.3, activation="tanh"))
rnn_model.add(Dense(n_outputs)) 

在我们定义了我们的 RNN 之后,我们可以使用compile()方法编译这个模型。这里,我们传递损失函数和我们想要使用的优化器。tensorflow提供了一些内置的损失函数优化器

rnn_model.compile(loss='mean_squared_error', optimizer=Adam(learning_rate=0.001))

在我们训练 RNN 之前,我们可以使用summary()方法来看看模型和参数的数量。这可以让我们对模型的复杂性有一个大概的了解。

我们使用[fit()](https://www.tensorflow.org/api_docs/python/tf/keras/Sequential#fit)方法训练模型。这里,我们需要传递训练数据和不同的参数来定制训练,包括时期数、批量大小、验证分割和提前停止。

stop_early = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=5)
rnn_model.fit(X_train, y_train, epochs=30, batch_size=32, validation_split=0.2, callbacks=[stop_early])

为了对我们的测试数据集或任何未知数据进行预测,我们可以使用predict()方法。verbose参数只是说明我们是否想要获得任何关于预测过程状态的信息。在这种情况下,我不想打印出任何状态。

y_pred = rnn_model.predict(X_test, verbose=0)

张量流中 RNNs 的超参数调谐

正如我们所见,RNN 的实现非常简单。然而,找到正确的超参数,如每层的单元数、辍学率或激活函数,要困难得多。

但是我们可以使用keras-tuner库,而不是手动改变超参数。该库有四个调谐器,RandomSearchHyperbandBayesianOptimizationSklearn,用于从给定的搜索空间中识别正确的超参数组合。

要运行调谐器,我们首先需要导入tensorflow和 Keras 调谐器。

import tensorflow as tf
import keras_tuner as kt

然后,我们建立超调模型,其中我们定义了超参数搜索空间。我们可以使用一个函数来构建超级模型,在这个函数中,我们以与上述相同的方式来构建模型。唯一的区别是,我们为每个想要调优的超参数添加了搜索空间。在下面的示例中,我想要调整每个 RNN 图层的单位数、激活函数和辍学率。

def build_RNN_model(hp):

    # define parameters
    n_timesteps, n_features, n_outputs = X_train.shape[1], X_train.shape[2], y_train.shape[1]

    # define model
    model = Sequential()

    model.add(SimpleRNN(hp.Int('input_unit',min_value=50,max_value=150,step=20), return_sequences=True, dropout=hp.Float('in_dropout',min_value=0,max_value=.5,step=0.1), input_shape=(n_timesteps, n_features)))
    model.add(SimpleRNN(hp.Int('layer 1',min_value=50,max_value=150,step=20), activation=hp.Choice("l1_activation", values=["tanh", "relu", "sigmoid"]), dropout=hp.Float('l1_dropout',min_value=0,max_value=.5,step=0.1), return_sequences=True))
    model.add(SimpleRNN(hp.Int('layer 2',min_value=50,max_value=150,step=20), activation=hp.Choice("l2_activation", values=["tanh", "relu", "sigmoid"]), dropout=hp.Float('l2_dropout',min_value=0,max_value=.5,step=0.1), return_sequences=True))
    model.add(SimpleRNN(hp.Int('layer 3',min_value=20,max_value=150,step=20), activation=hp.Choice("l3_activation", values=["tanh", "relu", "sigmoid"]), dropout=hp.Float('l3_dropout',min_value=0,max_value=.5,step=0.1), return_sequences=True))
    model.add(SimpleRNN(hp.Int('layer 4',min_value=20,max_value=150,step=20), activation=hp.Choice("l4_activation", values=["tanh", "relu", "sigmoid"]), dropout=hp.Float('l4_dropout',min_value=0,max_value=.5,step=0.1)))

    # output layer
    model.add(Dense(n_outputs))

    model.compile(loss='mean_squared_error', optimizer=Adam(learning_rate=1e-3))

    return model

为了定义每个变量的搜索空间,我们可以使用不同的方法,如hp.Inthp.Floathp.Choice。前两者的用法非常相似。我们给它们一个名字,一个最小值,一个最大值和一个步长。该名称用于识别超参数,而最小值和最大值定义了我们的值范围。步长参数定义了我们用于调整的范围内的值。hp.Choice可用于调整分类超参数,如激活函数。在这里,我们只需要传递一个我们想要测试的选项列表。

在我们构建了超模型之后,我们需要实例化调谐器并执行超调谐。尽管我们可以选择不同的算法进行调优,但它们的实例化非常相似。我们通常需要指定要优化的目标和要训练的最大历元数。这里,建议将时期设置为比我们预期的时期数稍高的数字,然后使用提前停止。

例如,如果我们想要使用Hyperband调谐器和验证损耗作为目标,我们可以将调谐器构建为

tuner = kt.Hyperband(build_RNN_model,
                     objective="val_loss",
                     max_epochs=100,
                     factor=3,
                     hyperband_iterations=5,
                     directory='kt_dir',
                     project_name='rnn',
                     overwrite=True)

在这里,我还传递了存储结果的目录,以及调谐器迭代整个 Hyperband 算法的频率。

在我们实例化了调谐器之后,我们可以使用search()方法来执行超参数调谐。

stop_early = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=5)
tuner.search(X_train, y_train, validation_split=0.2, callbacks=[stop_early])

为了提取最佳超参数,我们可以使用get_best_hyperparameters()方法和get()方法以及我们调整的每个超参数的名称。

best_hps=tuner.get_best_hyperparameters(num_trials=1)[0]
print(f"input: {best_hps.get('input_unit')}")
print(f"input dropout: {best_hps.get('in_dropout')}")

长短期记忆(LSTM)

LSTMs 是一种特殊类型的 rnn,其解决了简单 rnn 的主要问题,即消失梯度的问题,即丢失更远过去的信息。

展开的长短期记忆细胞。

LSTMs 的关键是单元状态,它从单元的输入传递到输出。因此,单元状态允许信息沿着整个链流动,仅通过三个门进行较小的线性动作。因此,细胞状态代表了 LSTM 的长期记忆。这三个门被称为遗忘门、输入门和输出门。这些门起着过滤器的作用,控制信息的流动,并决定哪些信息被保留或忽略。

遗忘门决定要保留多少长期记忆。为此,使用 sigmoid 函数来说明单元状态的重要性。输出在 0 和 1 之间变化,并表示保留了多少信息,即 0 表示不保留信息,1 表示保留单元状态的所有信息。输出由当前输入 x 、前一时间步的隐藏状态 h 和 a 偏置 b 共同决定。

输入门决定将哪些信息添加到单元状态,从而添加到长期记忆中。这里,sigmoid 图层决定更新哪些值。

输出门决定单元状态的哪些部分建立输出。因此,输出门负责短期记忆。

可以看出,所有三个门都由相同的函数表示。只有权重和偏差不同。单元状态通过遗忘门和输入门更新。

上述等式中的第一项决定了保留多少长期记忆,而第二项向细胞状态添加新信息。

然后,当前时间步长的隐藏状态由输出门和一个双曲正切函数决定,该函数将单元状态限制在-1 和 1 之间。

LSTMs 的优势

LSTM 的优势类似于 RNNs,主要好处是它们可以捕获序列的长期和短期模式。因此,它们是最常用的 rnn。

LSTMs 的缺点

由于其更复杂的结构,LSTMs 在计算上更昂贵,导致更长的训练时间。

因为 LSTM 也使用时间反向传播算法来更新权重,所以 LSTM 遭受反向传播的缺点(例如,死 ReLu 元素、爆炸梯度)。

张量流中 LSTMs 的实现

tensorflow中 LSTMs 的实现非常类似于一个简单的 RNN。唯一的区别是我们导入了LSTM类,而不是SimpleRNN类。

from tensorflow.keras import Sequential
from tensorflow.keras.layers import LSTM, Dense
from tensorflow.keras.optimizers import Adam

我们可以用简单的 RNN 的方法来组合 LSTM 网络。

# define parameters
n_timesteps, n_features, n_outputs = X_train.shape[1], X_train.shape[2], y_train.shape[1]

# define model
lstm_model = Sequential()
lstm_model.add(LSTM(130, return_sequences=True, dropout=0.2, input_shape=(n_timesteps, n_features)))
lstm_model.add(LSTM(70, activation="relu", dropout=0.1, return_sequences=True))
lstm_model.add(LSTM(100, activation="tanh", dropout=0))

# output layer
lstm_model.add(Dense(n_outputs, activation="tanh"))

lstm_model.compile(loss='mean_squared_error', optimizer=Adam(learning_rate=0.001))

stop_early = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=5)
lstm_model.fit(X_train, y_train, epochs=30, batch_size=32, validation_split=0.2, callbacks=[stop_early])

超参数调谐也与简单 RNN 相同。因此,我们只需要对我上面展示的代码片段做一些小的改动。

门控循环单元(GRU)

类似于 LSTMs,GRU 解决了简单 rnn 的消失梯度问题。然而,与 LSTMs 的区别在于 gru 使用较少的门,并且没有单独的内部存储器,即单元状态。因此,GRU 仅仅依靠隐藏状态作为记忆,导致更简单的架构。

展开门控循环单元(GRU)。

重置门负责短期记忆,因为它决定保留和忽略多少过去的信息。

向量 r 中的值由 sigmoid 函数限制在 0 和 1 之间,并取决于前一时间步的隐藏状态 h 和当前输入 x 。使用权重矩阵 W 对二者进行加权。此外,还增加了一个偏差 b

相比之下,更新门负责长期记忆,与 LSTM 的遗忘门相当。

正如我们所看到的,复位和更新门之间的唯一区别是权重 W

当前时间步长的隐藏状态是基于两步过程确定的。首先,确定候选隐藏状态。候选状态是当前输入和前一时间步的隐藏状态以及激活函数的组合。在这个例子中,使用了一个双曲正切函数。前一隐藏状态对候选隐藏状态的影响由复位门控制

第二步,将候选隐藏状态与前一时间步的隐藏状态相结合,生成当前隐藏状态。如何组合先前隐藏状态和候选隐藏状态由更新门决定。

如果更新门给出值 0,则完全忽略先前的隐藏状态,并且当前隐藏状态等于候选隐藏状态。如果更新门给出的值为 1,反之亦然。

GRUs 的优势

由于与 LSTMs 相比更简单的架构(即,两个而不是三个门,一个状态而不是两个),gru 在计算上更有效并且训练更快,因为它们需要更少的存储器。

此外,GRUs haven 被证明对于较小的序列更有效。

GRUs 的缺点

由于 GRUs 没有单独的隐藏细胞状态,它们可能无法考虑远至 LSTM 的观测结果。

类似于 RNN 和 LSTM,GRU 也可能遭受及时反向传播以更新权重的缺点,即,死 ReLu 元素、爆炸梯度。

张量流中 GRUs 的实现

至于 LSTM,GRU 的实现非常类似于简单的 RNN。我们只需要导入GRU类,其余的保持不变。

from tensorflow.keras import Sequential
from tensorflow.keras.layers import GRU, Dense
from tensorflow.keras.optimizers import Adam

# define parameters
n_timesteps, n_features, n_outputs = X_train.shape[1], X_train.shape[2], y_train.shape[1]

# define model
gru_model = Sequential()
gru_model.add(GRU(90,return_sequences=True, dropout=0.2, input_shape=(n_timesteps, n_features)))
gru_model.add(GRU(150, activation="tanh", dropout=0.2, return_sequences=True))
gru_model.add(GRU(60, activation="relu", dropout=0.5))
gru_model.add(Dense(n_outputs))

gru_model.compile(loss='mean_squared_error', optimizer=Adam(learning_rate=0.001))

stop_early = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=5)
gru_model.fit(X_train, y_train, epochs=30, batch_size=32, validation_split=0.2, callbacks=[stop_early])

这同样适用于超参数调谐。

结论

递归神经网络将记忆引入神经网络。这样,序列和时间序列数据中观测值的相关性就包含在我们的预测中。

在本文中,我向您展示了三种类型的递归神经网络,即简单的 RNN、LSTM 和格鲁。我向您展示了它们的工作原理、优缺点以及如何使用tensorflow在 Python 中实现它们。

请让我知道你对这篇文章的看法!

*除非另有说明,所有图片均为作者所有。

时间序列预测的统计方法简介

原文:https://towardsdatascience.com/a-brief-introduction-to-time-series-forecasting-using-statistical-methods-d4ec849658c3

你需要知道的关于 AR,MA,ARMA,ARIMA 和 SARIMA 的一切

费德里科·贝卡里在 Unsplash拍摄的照片

许多数据都是以时间序列的形式提供的,例如,天气数据、股票价格、销售发展等。通常,我们希望使用可用信息来估计这些时间序列的未来发展,以便为我们当前的决策提供信息。

这就是时间序列预测发挥作用的地方。由于时间序列预测是一个大话题,因为它有许多用例,所以有许多方法可供我们选择。这些方法可以分为两组:统计方法和机器学*方法。

在本文中,我将向您概述五种统计方法:

  • 自回归(AR)
  • 移动平均线
  • 自回归移动平均(ARMA)
  • 自回归综合移动平均(ARIMA)
  • 季节性自回归综合移动平均线

我不仅会向您展示它们是如何工作的以及我们何时可以使用它们,还会向您展示如何用 Python 轻松地实现它们。

在我们深入研究统计方法之前,我想快速向你们展示时间序列预测最简单的方法:朴素方法。

天真的方法

顾名思义,朴素方法是一种非常基本的预测方法,因此经常被用作基线/基准模型。简单方法基于最后观察值预测未来值:

如果时间序列有季节成分,我们可以假设一个季节的值与前一个季节的值相同。因此,代替使用时间序列的最后观察值,我们可以使用最后时段 T 的观察值:

通过使用 pandas [shift()](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.shift.html)方法,我们可以很容易地实现这种简单的方法。

自回归(AR)

顾名思义,自回归是时间序列的过去值(“自动”)的线性组合(“回归”),假设当前值和过去值之间存在关系。用于回归的过去值的数量被称为滞后或顺序 p 。基于过去值/滞后和附加白噪声 ε 和常数 c 的加权和来估计未来值:

这里,φ是每个滞后的权重。

为了确定滞后/阶数 p 的数量,我们可以使用自相关函数(ACF)和偏自相关函数(PACF)图。我们可以定义订单 p 如果

  • ACF 呈指数衰减或正弦衰减
  • PACF 在滞后点p有一个显著的峰值,但之后没有

如果 ACF 和 PACF 图没有帮助,我们可以测试不同的阶次 p ,然后根据一个准则选择一个模型,比如赤池的信息准则(AIC)。尽管 AIC 没有找到最佳模型,但它帮助我们识别出可以用最少的参数提供良好结果的模型。因此,我们基本上测试不同的订单,然后选择具有最低 AIC 的模型。但是,请注意,低 AIC 并不一定意味着模型提供了良好的预测结果。

自回归应用于没有趋势和/或季节性的时间序列,限制自回归的顺序。高阶(即滞后的数量)表示我们应该使用额外的参数,例如添加移动平均值(MA)。

我们还需要注意,我们只能按照我们选择的顺序来预测未来的目标。例如,如果我们选择 1 的顺序 p ,我们只能预测下一个时间步。如果我们使用 4 的顺序,我们可以预测未来的四个时间步。

如果我们想要预测多个时间步长,模型会反馈预测值,并将其用于下一个时间步长的预测。

用 Python 实现

我们可以使用 Python 的statsmodels包中的[AutoReg](https://www.statsmodels.org/dev/generated/statsmodels.tsa.ar_model.AutoReg.html)类在 Python 中实现自回归。我们传递订单(滞后数),然后调用fit()方法来训练模型。对于我们的实际预测,我们称之为predict()方法。

from statsmodels.tsa.ar_model import AutoReg

model_ar = AutoReg(train_ts, lags=p, seasonal=True, period=96, trend="n").fit()
y_pred = model_ar.predict(start=len(train_ts), end=len(train_ts)+1)

我们可以将滞后/订单的数量作为一个整数或一个整数列表来传递。如果我们传递一个整数,那么直到这个整数的所有延迟都会被使用。如果我们传递一个列表,那么只使用传递的特定 lag。

如你所见,AutoReg类允许我们预测具有趋势和/或季节性的时间序列。为此,我们需要通过如代码片段所示的季节性周期。

为了进行预测,我们需要确定起点和终点。起点总是根据我们的训练数据集的长度来确定的。例如,如果我们想要预测接下来的两个时间步,开始和结束将分别是我们的训练数据集的长度和我们的训练数据集的长度加 1。如果我们希望有三个时间步长的提前期(即,在未来三个时间步长开始预测),我们使用训练数据集长度的开始加上 2。

有时,ACF 和 PACF 图对确定顺序 p 没有太大帮助。因此,为了帮助我们选择正确的顺序,我们也可以使用ar_select_order类。

from statsmodels.tsa.ar_model import ar_select_order

mod = ar_select_order(train_ts, maxlag=p, seasonal=True, period=96, trend="n")
mod.ar_lags

移动平均线

移动平均是过去预测误差的线性组合。因此,该方法依赖于目标值和先前白噪声误差项之间的相关性。该方法不应与移动平均平滑混淆:

这里, ε 表示白噪声(即预测值与实际值之差) θ 是每个滞后的权重。移动平均的阶数 q 表示移动平均的窗口宽度,可以通过使用自相关函数(ACF)图来确定。我们可以决定顺序,如果

  • ACF 在滞后q时有显著的尖峰,但在之后没有尖峰
  • PACF 呈指数衰减或正弦衰减

一般来说,移动平均可用于平稳时间序列。

用 Python 实现

我们可以通过使用 Python 的statsmodels中的[ARIMA](https://www.statsmodels.org/dev/generated/statsmodels.tsa.arima.model.ARIMA.html)类在 Python 中实现移动平均。因为我们只想建立移动平均模型,所以需要将自回归项和积分项的阶数设置为零。由于 ARIMA 模型将订单参数作为一个形式为 (p,d,q) 的元组,我们将前两个订单设置为零。

然后,ARIMA类的使用类似于AutoReg类,我们调用fit()predict()方法来训练模型并进行预测。

from statsmodels.tsa.arima.model import ARIMA

model_ma = ARIMA(train_ts, order=(0,0,q)).fit()
y_pred = model_ma.predict(start=len(train_ts), end=len(train_ts)+1)

除了使用predict()方法,我们还可以使用forecast()方法。在这里,我们只需要通过时间步数,我们要预测到未来。

自回归移动平均(ARMA)

自回归移动平均将阶为 p 的自回归与阶为 q 的移动平均相结合。因此,该方法描述了时间序列与其自身以及先前时间步的随机噪声之间的关系:

这里,第一个求和项代表自回归部分,第二个求和项代表移动平均值。

为 ARMA 模型中的 pq 分量选择正确的阶数可能相当困难,因为如果两个分量都存在,ACF 和 PACF 图可能对我们没有太大帮助。但是,它们可能会指示顺序,并帮助我们找到超参数调整的良好起点。我们也可以使用网格搜索,测试不同的组合 pq ,然后根据选择的标准选择订单,例如 AIC。

类似于自回归和移动平均,ARMA 方法只对平稳时间序列有效。

用 Python 实现

我们可以使用 Python 的statsmodels中的ARIMA类在 Python 中实现自回归移动平均。为此,我们将自回归和移动平均的次序传递给方法。

from statsmodels.tsa.arima.model import ARIMA

model_arma = ARIMA(train_ts, order=(p,0,q)).fit()
y_pred = model_arma.predict(start=len(train_ts), end=len(train_ts)+1)

自回归综合移动平均(ARIMA)

到目前为止,我只展示了适用于平稳时间序列的方法。但是时间序列通常不是静态的,因为它们具有趋势和/或季节性。为了使时间序列稳定,我们可以使用差分法,从先前的时间步长中减去当前的时间步长。

我们可以通过添加一个差分阶 d 将其直接包括在 ARMA 模型中,而不是在单独的步骤中进行差分,这将我们引入自回归综合移动平均模型。1 的顺序 d 意味着时间序列被差分一次,而 2 的顺序 d 意味着时间序列被差分两次。

为了找到最佳差分顺序,我们可以

  • 画出我们的时间序列并比较不同顺序的影响,
  • 使用统计测试,例如扩展的 Dickey-Fuller (ADF)测试,
  • 看看 ACF 和 PACF 的图,或者
  • 使用auto_arima

ADF 用平稳时间序列的替代假设检验时间序列中存在单位根(非平稳性)的零假设。因此,如果 p 值低于 0.05,并且如果检验统计量是负数,我们可以拒绝零假设,并假设我们的时间序列是平稳的。

如果小滞后的自相关为正且很大,然后缓慢下降,则 ACF 图指示一种趋势。相比之下,季节性则由季节性滞后的较大自相关性来表示。

pmdarima包中的[auto_arima](https://alkaline-ml.com/pmdarima/modules/generated/pmdarima.arima.auto_arima.html)方法可以通过对 pdq 的不同组合进行自动搜索来帮助我们识别正确的参数。如果我们用其默认值运行auto_arima,它会通过模拟范围为 p = 2…5、 d = 0…2 和 q = 2…5 的 AIC 找到“正确”的组合。

import pmdarima as pm

ARIMA_model = pm.auto_arima(train_ts)
ARIMA_model.summary()

虽然 ARIMA 通常提供比上述方法更好的结果,但是 ARIMA 在计算上更昂贵。此外,我们需要调整更多的超参数。

用 Python 实现

我们可以使用 Python 的statsmodels中的ARIMA类在 Python 中实现自回归综合移动平均。对此,我们只是通过模型的 pdq 顺序。

from statsmodels.tsa.arima.model import ARIMA

model_arima = ARIMA(train_ts, order=(p,d,q)).fit()
y_pred = model_arima.predict(start=len(train_ts), end=len(train_ts)+1)

季节性自回归综合移动平均线

如果我们的时间序列有季节性成分,我们可以通过季节性成分扩展 ARIMA 模型。生成的季节性自回归综合移动平均涉及季节性成分的后移,并对季节性成分执行额外的自回归、综合和移动平均。因此,萨日玛可以表示为 (p,D,q)(P,D,Q)m 分量,其中 PDQ 是季节分量 m 的参数。

虽然该模型可以进行更好的预测,但找到正确的参数甚至比 ARIMA 模型更耗时。

同样,我们可以使用auto_arima来帮助我们找到最佳参数。对此,我们只需要添加季节参数 m ,需要手动确定。 m 是季节周期中的时间步数。例如,如果一个季节持续一年,我们有月度数据,那么 m = 12。

import pmdarima as pm 

ARIMA_model = pm.auto_arima(train_ts, m=12)
ARIMA_model.summary()

用 Python 实现

我们可以使用 Python 的statsmodels中的ARIMA类在 Python 中实现季节性自回归综合移动平均线。为此,我们只需传递模型的 pdq 顺序,并添加季节性的参数 (P,D,Q)m

from statsmodels.tsa.arima.model import ARIMA

model_sarima = ARIMA(train_ts, order=(p,d,q), seasonal_order=(P,D,Q,m)).fit()
y_pred = model_sarima.predict(start=len(train_ts), end=len(train_ts)+1)

决定我们模型的顺序

除了使用 AIC 选择正确数量的模型,我们还应该在进行任何预测之前研究模型的残差。理想情况下,残差应该是白噪声。因此,它们不应该有任何自相关。

为了测试自相关性,我们可以应用 Ljung-Box 检验,它测试数据是独立分布的零假设,即没有表现出序列相关性。如果测试的 p 值低于 0.05,残差不相关,我们可以使用我们的模型进行预测。

我们可以通过使用来自statsmodels[acorr_ljungbox](https://www.statsmodels.org/dev/generated/statsmodels.stats.diagnostic.acorr_ljungbox.html)方法在 Python 中实现 Ljung-Box 测试。请记住,我们需要对残差进行测试。

from statsmodels.stats.diagnostic import acorr_ljungbox

ljung_box = acorr_ljungbox(model.resid)
print(ljung_box)

fig, axes = plt.subplots(3, figsize=(10,5))
axes[0].plot(model.resid)
plot_pacf(model.resid, lags=100, ax=axes[1])
plot_acf(model.resid, lags=100, ax=axes[2])

我们也可以看看 ACF 和 PACF 图来支持永盒测试并验证结果。

结论

在这篇简介中,我向你展示了五种统计方法,根据时间序列的特征,你可以很容易地用它们来预测时间序列。

请注意,尽管 Python 中的实现非常简单,但您将花费大量时间预处理时间序列并寻找最佳超参数,即模型的正确阶数。

让我知道你的想法,并留下评论。

机器学*概述

原文:https://towardsdatascience.com/a-brief-overview-of-machine-learning-20abc68cbd4e

了解机器学*和人工智能的基础知识,以及它们的潜在挑战和警告。

当我们在互联网上随机搜索术语时,我们经常会遇到【机器学*】【深度学*】,以及它们如何彻底改变我们的生活方式。目前,从自动驾驶汽车,垃圾邮件检测,我们在网飞亚马逊看到的推荐系统,银行使用的信用卡欺诈检测等等,机器学*几乎无处不在。随着潜在的新应用的出现,这个列表还会继续下去。因此,跟上最新的趋势,理解机器学*实际上是什么,并对一些类型的机器学*有更广泛的理解是非常重要的。在这篇文章中,我将解释机器学*和不同类别的机器学*。此外,我们还将讨论机器学*的一些基本限制。

照片由 Shubham DhageUnsplash 上拍摄

什么是机器学*?

机器学*是教会计算机从数据中学*并做出决策的过程,无需在训练后明确编程。一般来说,我们必须首先训练机器学*模型,然后才能让它们自己做出决定。所以机器学*算法需要的最重要的东西之一就是数据。没有数据,就没有机器学*算法的本质。根据我们提供给机器学*模型的数据,它们会在对新数据做出预测之前获取数据并理解它。因此,我们必须提供反映真实世界的数据。这是因为机器学*模型将完全依赖于我们提供给模型的数据来进行未来预测。

机器学*中通常发生的是一堆数学,其中有乘积特征缩放归一化等等。因此,我们提供给机器学*算法的数据应该是向量或数字的形式,而不是文本或机器无法理解的其他形式。机器不能理解数据的一些例子是文本字母。因此,我们必须将所有文本和字母转换为数字形式,并将其输入到机器学*模型中进行训练和预测。

机器学*方法

最典型的机器学*方法是首先将我们的数据分成两组:训练测试集。我们必须首先将整个数据转换成数学向量的形式,并拆分数据。拆分数据后,训练集被输入到机器学*模型中。在足够数量的迭代或时期(一个时期发送一次整个训练数据)之后,我们将使用机器学*算法从测试集中进行预测。然后,我们将看到机器学*模型在测试集上的表现如何(机器学*模型在测试集上表现良好真的很重要)。

过拟合

照片由 Diana PolekhinaUnsplash 拍摄

机器学*模型有可能在训练集上表现得非常好,而在测试集上表现得不那么好。这是过度拟合的一个例子。在这种情况下,机器学*模型从训练数据中学到了太多东西,而无法概括。因此,它们在训练数据上拟合得非常好,并且它们能够在这个集合上给出很好的预测。然而,当我们试图从测试集获得预测时,机器学*模型会失败,因为它们已经在训练集上学*并拟合了它们的参数,而不是能够在测试数据(新数据)上进行归纳。因此,我们不仅要注意训练集的准确性,还要注意测试集的准确性。

下拟合

凯利·西克玛Unsplash 上拍摄的照片

有时,机器学*模型与训练数据本身并不十分吻合。因此,当我们在分类任务的情况下考虑一些指标如准确度召回精确度时,它们在训练数据上的表现非常差。在回归任务的情况下,他们可能在诸如均方根误差(RMSE)均方误差(MSE)平均绝对误差(MAE) 等指标上表现不佳。这也会导致测试集的性能下降。机器学*模型在训练数据上表现不佳可能是由于数据不足、不相关的特征、不太复杂的模型等等。因此,我们必须确保我们最大限度地训练机器学*模型,并确保它们不仅在训练数据上表现良好,而且在测试数据上也表现良好。

为什么机器学*在最*几年获得了很大的吸引力?

沃洛季米尔·赫里先科在 Unsplash 上拍摄的照片

机器学*算法和神经网络一起被提出。然而,在那些日子里,没有足够的数据来利用这些算法。除此之外,运行这些算法所需的计算能力非常有限。然而今天,公司中产生了大量的数据,可用的计算资源也非常惊人。看看谷歌(Google cloud)和亚马逊(Amazon Web Services)提供的一些服务,会让我们对我们目前拥有的计算能力有一个很好的了解。我们可以使用所有这些技术,而无需为它们的运行设置基础设施(硬件),因为这是由上述公司提供的。因此,对机器学*和深度学*有着巨大的需求。许多公司都在机器学*研究上投入了巨额资金,以提高他们的生产力,增加使用机器学*的产品的收入。

在我们拥有大量数据的世界中,为了获得不同用例的预测,理解机器学*和深度学*非常重要。查看我们拥有的数据以及公司如何利用这些数据,我们会明白,我们学*机器学*模型越多,我们就越能为公司创造价值,并确保他们获得利润。

列宁·艾斯特拉达在 Unsplash 上的照片

机器学*的类型

  1. 监督学*:在这种类型的机器学*中,我们知道输出标签,我们会训练机器学*模型,并根据它们生成的输出对它们进行评估,并将其与实际输出进行比较。这将确保我们基于已知的输出值来训练机器学*模型,因此,我们可以评估机器学*模型的性能。例如,如果我们希望根据包含房价输入特征和输出的先前数据来预测房价,我们将能够训练机器学*模型,并使用实际输出房价来评估其输出。这将确保我们训练机器学*模型直到完美,这被称为监督学*。
  2. 无监督学*:在这种机器学*方法中,我们不知道输出,我们将训练机器学*模型,它们将能够识别模式并理解数据。无监督学*的一个流行例子是客户细分,我们会根据客户在特定场景中的行为对他们进行分组。
  3. 半监督学*:在这种方法中,对于一些数据点,我们将有一些输出数据,而对于其他剩余数据,没有输出。我们将在有输出的训练数据上训练机器学*模型,然后要求模型对没有输出的数据进行分类并自行获取模式。使用半监督学*的一个例子是文本文档分类。我们将首先使用已知的输出进行训练,并要求手头的机器学*模型对输出未知的剩余数据进行分割和分类。

机器学*有哪些局限性?

  1. 一个限制是维数灾难。这意味着,当我们为机器学*模型提供更多功能时,我们会让模型花更多时间来训练和实现。这通常会导致性能低下。因此,培训有所延迟,这将导致生产机器学*模型的开发时间更长。
  2. 过度拟合可能是机器学*中的一个问题。在这个过程中,机器学*模型能够很好地预测训练数据的输出,但当涉及到测试数据时,它们通常无法对我们将用作测试集的新数据进行归纳。这就是所谓的过度拟合。

结论

我们已经介绍了机器学*和机器学*的类型。我们已经看到了机器学*在现实生活中实施时的一些局限性。我们也看到了在一个大的类别下不同类型的机器学*。希望这篇文章给机器学*一个好的直觉。谢谢你。

可赎回的浮动?Python 中的乐趣和创造力

原文:https://towardsdatascience.com/a-callable-float-fun-and-creativity-in-python-7a311ccd742d

PYTHON 编程

为了学*创造性,我们将在 Python 中实现可调用的浮点数

Kai GradertUnsplash 上拍摄的照片

在 Python 的内置数据类型中,我们有许多表示数字的类型,最重要的是intfloat。如同 Python 中的一切,它们的实例是对象;而作为对象,它们有自己的属性和方法。例如,这就是float型报价的实例:

如你所见,float数字提供了许多不同的使用方法。他们没有提供一个.__call__()方法,这意味着你不能调用他们。

你有没有想过为什么我们不能像调用函数一样调用浮点数?看:

>>> i = 10.0012
>>> i()
Traceback (most recent call last):
    ...
TypeError: 'float' object is not callable

它不起作用。为什么它不起作用?!

老实说……我从来没有考虑过为什么 floats 是不可调用的——但是这对我来说非常有意义。他们为什么要这么做?为什么你觉得他们有什么特别的原因吗?我不知道。

但这并不意味着我们不会实现这样的功能——我们肯定会实现。我们为什么要这么做?原因有三:学* Python,有创意,学*有创意。

学*创新是学*编程语言的一个重要方面。有一天,在一个 Python 项目中,你会发现自己处于一个困难的境地,标准的解决方案不起作用;你必须思考有创造性。你的创造力可能会帮助你找出一个创新的、非典型的解决方案,一个能帮助你解决这个奇怪的或者非典型的问题的方案。

因此,在本文中,我们将实现一个可调用的浮点类型;这绝对不是一个典型的场景。我们将逐步进行,我将详细解释每一步。继续读下去的时候,要尽量有创造性。也许这样会帮助你想出自己的想法来改进解决方案。如果有,就去实现它们,并请在评论中分享。

履行

我们将从实现下面的简单方法开始:如果用户调用一个 float,它返回四舍五入到三个十进制数字的数字。现在,让我们简化一下——我将硬编码舍入到三个十进制数字;我们以后会改变这一点。

# callable_float.py
class Float(float):
    def __call__(self):
        return round(self, 3)

这就是我们所需要的!正如您在下面看到的,在正常的用例中,Float的一个实例的行为就像一个常规的float对象:

>>> from callable_float import Float
>>> i = Float(10.0012)
>>> i * 2
20.0024
>>> i - 1
9.0012
>>> round(2 * (i + 1) / 7, 5)
3.14320

然而,与float不同的是,Float是可调用的:

>>> i()
10.001

Voilà —可调用的浮点数。

然而,不需要硬编码要在舍入中使用的小数位数。我们可以让用户决定使用多少个十进制数字。为此,只需向.__call__()方法添加一个digits参数:

# callable_float.py
class Float(float):
    def __call__(self, digits=3):
        return round(self, digits)
>>> i(1)
10.0
>>> i(3)
10.001
>>> i(10)
10.0012

完美!请注意,这正是我们将相应的float数字四舍五入后得到的结果:

>>> j = float(i)
>>> i(1) == round(j, 1)
True
>>> i(3) == round(j, 3)
True
>>> i(10) == round(j, 10)
True

我们可能想返回一个Float对象,而不是一个float:

# callable_float.py
class Float(float):
    def __call__(self, digits=3):
        return Float(round(self, digits))

在这里,Float数字在被调用时唯一能做的就是四舍五入。无聊!让它能够做任何事情

为了实现这样一个通用的概念,我们需要使.__call__()方法成为一个高阶方法,这意味着它接受一个函数(实际上是一个可调用的函数)作为参数。下面的类实现了这个功能:

# callable_float.py
from typing import Callable

class Float(float):
    def __call__(self, func: Callable):
        return func(self)

注意,这一次,我们没有将返回值的类型更改为Float,因为用户可能希望使用返回另一种类型的对象的func()函数。

这个版本的Float是这样工作的:

>>> from callable_float import Float
>>> i = Float(12.105)
>>> 2*i
24.21
>>> i(round)
12
>>> i(lambda x: 200)
200
>>> i(lambda x: x + 1)
13.105
>>> def square_root_of(x):
...     return x**.5
>>> i(square_root_of)
3.479224051422961
>>> i(lambda x: round(square_root_of(x), 5))
3.47922
>>> i = Float(12345.12345)
>>> i(lambda x: Float(str(i)[::-1]))
54321.54321

它像预期的那样工作,但是它有一个明显的缺点:您不能对func()函数使用额外的参数。由于有了*args**kwargs参数,我们可以很容易地实现这个功能,使用户能够提供任何参数:

# callable_float.py
from typing import Callable

class Float(float):
    def __call__(self, func: Callable, *args, **kwargs):
        return func(self, *args, **kwargs)
>>> i(lambda x, digits: round(x**.5, digits), 5)
111.10861
>>> def rounded_square_root_of(x, digits=3):
...     return round(x**.5, digits)
>>> i(rounded_square_root_of)
111.109
>>> i(rounded_square_root_of, 5)
111.10861
>>> j = float(i)
>>> i(rounded_square_root_of, 5) == rounded_square_root_of(j, 5)
True

所有工作都很好。请考虑以下注意事项:

  • 用户提供一个应用于类实例的函数。它可以不带参数,也可以带任意数量的参数,包括位置参数(*args)和关键字参数(**kwargs)。然后将Float的值作为函数调用,将函数func()作为该函数的参数调用,这与将该值作为其参数调用该函数是一样的……如果这不疯狂,那么什么才是疯狂呢?!
  • 这次Float.__call__()的返回值可以是任意类型,并且和func()函数的返回值是同一个类型。

结论

我们已经实现了一个类Float,它可以像函数一样调用浮点数。用一个函数作为参数调用一个Float类的实例意味着用Float实例作为参数调用这个函数。您可以使用任何附加参数,包括位置参数和关键字参数。

我们做这些并不是为了实际使用可调用的Float类。我从未实现或使用过任何类似的东西,我也不也不期望如此。但对我来说,这很有趣,非常有趣——也是使用 Python 的创造性的一课。我希望你也喜欢这篇文章。我认为做这些疯狂的事情有很大的价值:它帮助你学* Python,并且帮助你学*这种语言的复杂性,这是你用其他方法学不到的。

Float类并不重要。重要的是,当你不得不处理非典型情况时,这种头脑风暴可以帮助你在未来的项目中激发创造力。实践这样的实现可以帮助您在 Python 项目中找到并实现非典型解决方案。

感谢阅读。如果你喜欢这篇文章,你也可以喜欢我写的其他文章;你会在这里看到他们。如果你想加入 Medium,请使用我下面的推荐链接:

https://medium.com/@nyggus/membership

启发式案例:为什么简单的解决方案常常在数据科学中胜出

原文:https://towardsdatascience.com/a-case-for-heuristics-why-simple-solutions-often-win-in-data-science-a31967766daa

在为启发式辩护的过程中,我研究了在寻找数据科学产品时,简单的解决方案通常是最佳的停靠港

米奇·奥尼尔在 Unsplash 上的照片

2016 年,futurethink 的首席执行官兼谷歌活动的顶级演讲者 Lisa Bodell 提出“简单正迅速成为我们这个时代的优势”。然而,在数据科学和机器学*领域,我们往往更喜欢更复杂的解决方案,虽然通常会导致难以置信的结果,但也可能导致挫折、失败和漫长的交付时间。

虽然这篇文章并不是号召放弃 Keras 并回归 Excel,它是一个温和的提醒,提醒你考虑利用简单的启发式方法来基线化你的解决方案,甚至让你考虑从开始运送它们,然后构建更高级的东西。

在本文中,我将从最*的一个研究项目中汲取经验,以及我如何在开发数据科学解决方案时使用这些发现来指导我的日常方法。我将深入探究启发式的定义,从 Martin Zinkevich 的机器学*规则 中学*,深入探究最*的一个项目,该项目旨在识别 gif 中危险的光敏癫痫序列,最后总结利用启发式的关键学*。

试探法的简明介绍

机器学*的规则中,Zinkevich 提出启发式是“一个简单而快速实现的问题解决方案”。试探法通常被用作基于规则的算法或指标的总称,允许您对数据进行分类或推断,或做出关于数据的决策。启发式方法可用于解决各种问题,包括:

  • 对垃圾邮件进行分类(例如使用基于规则的方法来检测某些单词)
  • 向用户显示相关结果(例如他们所在国家或地区最受欢迎的结果)
  • 识别应用或游戏中表现最好的用户(通过对行为或参与度进行排名)

虽然试探法被认为简单快捷,但它们可能并不总是数据科学家解决问题的首选。以我的经验来看,启发式方法可以被忽略,而优先考虑更复杂的解决方案,然后当更先进的解决方案失败时,简单性就会取而代之。在我自己的学术和职业生涯中,这是我亲身经历的一种情况。在这篇文章中,我想分享我比较启发式和深度学*解决方案的发现,以及为什么简单的启发式应该是你的第一个选择。

挑战

最*,我有机会进行研究,以了解是否有可能开发出检测视频和 g if 中光敏癫痫触发因素的解决方案。值得强调的是,这项工作从未被指定用于生产,而那些确实希望影响人们并做出决策的人工智能医疗解决方案应该遵循世界卫生组织和其他领先组织的研究人员制定的原则。然而,我的挑战是确定该领域内的可能性,并通过深度学*建立第一轮解决方案。

了解光敏性癫痫

光敏性癫痫属于反射性癫痫的更广泛类别,即该领域的两位主要研究人员 Okudan 和 zkara 指出,已知的“客观特异性”触发因素可能导致癫痫发作。*年来,不幸的是,不良演员在网上恶意针对光敏癫痫社区。例如,记者莉安娜·鲁珀特(Liana Ruppert)在撰写了视频游戏《T4》和《赛博朋克 2077》(T5)中的危险内容后,成为光敏性癫痫触发视频的目标。

《赛博朋克 2077 》,一款以发布时的危险场景为特色的视频游戏。照片由 Stefans02: 来源

South、Saffo 和 Borkin 在他们的论文检测和防范社交媒体中引发扣押的 gif,中开发了一种消费者驱动的方法来检测危险的 gif,这种方法可以用来打击在线目标的增加。为了衡量他们工具的性能,他们还开发了一个危险和安全 gif 数据集。gif 分类如下:

  • 安全:不含光敏性癫痫触发器
  • 闪烁:包含闪烁序列
  • 红色:包含与饱和红色之间的转换
  • 图案:包含重复的图案
  • 危险:包含红色、图案或闪光

该数据集的开发为开发机器学*方法以应对在 gif 中识别光敏癫痫触发因素的挑战提供了机会,这也是 South、Saffo 和 Borkin 确定的未来研究方向。我的目标是开发一个相对简单的深度学*解决方案,使用 2D CNN 架构和转移学*,然后在 South、Saffo 和 Borkin 确定的不同危险类别上测量这种方法的性能。

深度学*方法

对于这个项目,我在方法上有完全的灵活性,一个很长的期限和理解的倾向;这可能吗?从我自己的经验来看,这三个因素的结合往往可以导致数据科学家选择更复杂的解决方案,而不是更简单的解决方案;后者可以保证在某个截止日期前交付。

我开发了一个卷积神经网络(使用 Xception 架构),利用迁移学*将输入的 gif 序列分解为四个图像,然后合并在一起。

为什么选择 2D 卷积神经网络?

作为研究的一部分,我确定了各种 gif 和视频分类架构的优缺点,包括多流和 3D CNN 方法。对于第一遍,我决定将危险的 gif 序列转换成一个图像输入,将四帧 gif 合并成一个图像。CNN 的输入数据示例如下所示:

训练用于检测危险序列的神经网络的输入示例。Gif 来自: South,l . Borkin,m .&Saffo,D. (2022 年 5 月 9 日)。检测和防御社交媒体中诱发癫痫发作的 GIFs】。作为项目的一部分创建的图像。

虽然为这个项目选择合适的架构超出了本文的范围,但我强烈推荐 Rehman 和 Belhaouari 在 2021 年对视频分类深度学*的评论。

训练了四个模型,旨在检测光敏癫痫触发器(红色、图案、闪光和所有这些,分组在危险下)。虽然危险模型表现最好,但几乎所有模型都无法检测出危险的 gif。下面,为被训练来检测危险和饱和红色转变的模型提供分类报告:

危险车型性能

“危险”模型的性能结果。作者创建的图像。

红色车型性能

“红色”型号的性能结果。作者创建的图像。

虽然有了更大的数据集、采样技术的使用和不同的架构选择,这种性能无疑可以得到改善,而且正如大多数类的过度拟合和优化所发生的那样,我在这一点上的目标是简单地建立深度学*的可行性。将来,我会以这种简单的方法为基础(与启发式方法一起)开发不同的解决方案。

启发式方法

作为他们研究的一部分,South、Saffo 和 Borkin 开发了三种基于规则的算法来检测闪光、图案和红色饱和度。每个算法(或启发式算法)都是基于规则的,这使得它既可解释又相对容易实现。例如,为了检测包含红色转换的危险序列,使用以下等式:

计算帧的红色比率,来自 South、Saffo 和 Borkin (2021)的方程。来源

计算框架的纯红色,来自 South、Saffo 和 Borkin (2021)的方程。来源

利用他们的试探法,South、Saffo 和 Borkin 报告了强有力的结果,对于红色饱和度检测具有完美的准确度、召回率和精确度,其中准确度为 100%,召回率为 100%,精确度为 67%。

一旦我的深度学*模型被开发出来,我想看看即使是最基本的启发式方法是否也能胜过开发的深度学*模型。鉴于红色饱和度模型的较差性能,这是一个相当有偏见的练*,然而,开发一个简单的解决方案并检查其性能仍然很有趣。

对于我的启发,我利用 Numpy 和 Matplotlib 来读取为 CNN 创建的图像,然后使用 South、Saffo 和 Borkin 的 RedRatio 方程来计算图像的分数。由于训练和测试之间的数据分离,我开发了危险红色图像的训练数据的截止值,并将其应用于为神经网络方法开发的非常小的测试集。结果如下所示:

在预处理的 gif 上只使用红色比率规则可以提高性能。作者创建的图像。

即使在这个非常小的数据集上,简单的启发式方法也优于深度学*方法。此外,South、Saffo 和 Borkin 更先进的基于规则的启发式算法在识别危险内容方面也表现得非常好。

评估启发式

有了这些发现,一些清晰的思考和结论开始在启发法的力量上发展,以及何时超越它们。我在这一领域的三个主要收获如下。

首先用简单的解决方案进行基准测试

虽然 South、Saffo 和 Borkin 认为未来的研究方法可以结合机器学*,但这里有一个有趣的转折点。在机器学*的规则中,Zinkevich 提出:

“如果你认为机器学*会给你 100%的提升,那么启发式学*会让你达到 50%。”

然而,有了 South、Saffo 和 Borkin 开发的数据集,他们已经能够通过基于规则的算法实现 100%的“既定目标”。在开发机器学*解决方案时,这是一个重要的学*过程:首先学*用一个简单的启发式方法进行基准测试,并考虑这是否足以用于生产。

仔细检查您是否有足够的数据来开发机器学*解决方案

正如津克维奇在机器学*规则中强调的:

“机器学*很酷,但它需要数据。理论上,您可以从不同的问题中获取数据,然后为新产品调整模型,但这可能会落后于基本的 启发式算法

从红色模型的性能结果中,可以看到这一挑战在行动中。“红色”测试数据集中几乎没有任何数据,这意味着模型受到了难以置信的阻碍。当然,尝试过采样技术是可行的(作为这个项目的一部分,我还实施了 Tensorflow 的类不平衡指导)。然而,仅仅克服一小部分少数民族的挑战是不够的。

在这方面,一个简单的胜利是评估不同的班级规模,尤其是当同时开发多个机器学*模型时,不要害怕为一些问题提供启发式算法,在你有更多数据的地方和时间利用机器和深度学*方法。

发展你的启发式,然后走得更远

查看 South、Saffo 和 Borkin 数据集中不同大小的安全和危险(危险、红色、闪光和图案)类别,某些类别的人数比其他类别多。如果您有“大量”数据(根据您的问题,这可能是一个相当模糊的概念),那么超越简单的试探法可以提高您试图预测的结果的性能。

当我的项目专注于“这可能吗””时,完全的自由和很长的期限,在更复杂的解决方案中使用启发式有一个优势:

首先,试探法天生更容易解释。考虑用于识别红色跃迁序列的 South、Saffo 和 Borkin 方程。可解释性在医疗保健人工智能领域有明显的好处,虽然这里开发的模型从来不是为了生产或检查内容是否安全,但如果你正在开发医疗保健人工智能解决方案,可解释性应该是一个关键的焦点。

其次,启发式算法可以快速实现。这意味着,如果您能够识别出可以提高性能的新的规则添加或更改,您就可以相对较快地完成。当您接*严格的截止日期,并希望快速开发数据科学解决方案时,这有明显的好处。

最后,正如 Zinkevich 指出的那样,如果你只有很少的数据,或者没有数据,可以从以前的经验中开发启发式方法,比如来自不同主题领域的数据、用户研究甚至直觉(尽管最后一个值得单独写一篇文章,因为它可能会带来一些重大风险)。

在我们结束之前,值得一提的是,我的模型是基于对问题空间的探索,而不是过度关注优化或提升性能。如果是这样的话,我在这个领域还可以做更多的事情,包括收集更多的数据,使用采样技术,甚至检查所选的架构(例外)是否合适,当然还有处理过度拟合和关注多数类。然而,作为这个问题空间的深度学*可行性的基础,这种方法达到了它的目的。

往前走,启发式?

在这篇文章中,我一直是启发式的明确支持者——当然还有津克维奇的机器学*的规则。然而,作为技术专家,我总是喜欢大胆的新解决方案,并尝试这些方案。Zinkevich 还假设,如果你正在开发复杂的启发式算法,那么它们可能会变得难以维护,这就是机器学*可以介入并执行的地方。

当考虑如何在你的下一个项目中实现启发式时,我强烈推荐使用它们来建立基线,如果最后期限很紧,它甚至可以成为你的解决方案。然而,在机器学*和深度学*解决方案中仍然有明显的价值。使用你的启发式方法建立一个基线来击败,可以为你更复杂的解决方案打下良好的基础。

选题目录

奥库丹,z .和厄兹卡拉,C. (2018)“反射性癫痫:触发因素和管理策略”,神经精神疾病和治疗,第 14 卷,第 327-337 页。doi: 10.2147/ndt.s107669。

South,l .,Saffo,D. Borkin,D. (2021)检测和防御社交媒体中诱发癫痫发作的 gif。2021 年中国计算机学会计算系统中人的因素会议录。美国纽约州纽约市计算机械协会,第 273 条,1–17。DOI:https://DOI . org/10.1145/34135435546

为什么简单的胜利——丽莎·博德尔的书——未来思考(2022)。可在:https://www.futurethink.com/why-simple-wins

Zinkevich,M. (2022)机器学*的规则:|谷歌开发者。可在:https://developers . Google . com/machine-learning/guides/rules-of-ml获得。

通过代码评审优化程序的案例研究

原文:https://towardsdatascience.com/a-case-study-in-optimizing-programs-through-code-review-302f994fe7ec

数据科学项目的持续改进需要坚持不懈的努力

Unsplash 上由 Max Duzij 拍照

拥有相对成熟的数据科学团队的组织的一个更不受重视的方面是持续改进的强大文化。对于负责创建和维护复杂软件解决方案的技术团队来说,参与到提高团队代码库整体质量的实践中尤为重要。无论是已经在生产中的现有过程,还是完成可重复任务的标准化包/代码,定期和严格的代码审查通过减少错误、安全问题和资源使用的可能性,使主要和第三利益相关者都受益。

审查别人写的代码,通常很少有文档或上下文,有时会很困难和不愉快。但香肠就是这么做的。

在这里,我举例说明了一个例子,在这个例子中,我能够显著减少程序运行所需的最大计算空间和总处理时间。真正酷的是,我最初并没有想这么做;它起源于一个旨在重组程序以在一组已建立的 KPI 上表现更好的计划。有时候最好的事情发生在你意想不到的时候。

首先,一些背景。在某一点上,程序(用 Python 编写)从一组对象中随机抽取样本,并记录该组的关键指标。它这样做了很多次。目标是确定一个样本对象,使关键指标的值最大化,供以后使用。在检查代码的正确性时,我发现了一些明显增加了整体运行时和内存使用的东西,这些东西是不需要存在的。

问题在于随机抽样的结果是如何储存的。因为在后面的步骤中需要这个样本,所以程序最初创建了一个列表,使用一个 for 循环来存储每次迭代的每个样本的 pandas 数据帧。虽然这达到了它的目的,但内存中列表的大小是作为两个变量的函数而增加的:for 循环中的迭代次数,以及所取样本的大小。对于相对较小的样本和迭代,该过程运行良好。但是,如果将样本量从 1,000 增加到 100,000,迭代次数从 5,000 增加到 500,000,会发生什么呢?它可以极大地改变所需的内存使用量,而且不管您的技术架构如何,效率低下的程序都会因不必要的计算资源和浪费的时间而给组织带来实实在在的损失。

让我们快速构建一组示例数据来说明这个问题。我们将使用一个特定时间范围内销售的购物者 id 的例子;它可以是特定于一组产品、销售渠道、地理位置等的销售。—发挥你的想象力!

import pandas as pd
import numpy as np# Set how many iterations to run, & size of ID sample in each iteration
n_iters = 100000
smpl_sz = 500000# Create a sample df with 10MM unique IDs
# - Generate some dummy sales data to play with later
id_samp = pd.DataFrame(range(10000000,20000000))
id_samp.columns = ['Customer_ID']
id_samp['Sales_Per_ID'] = np.random.normal(loc = 1000, scale = 150, size = len(id_samp))
print(id_samp.head())

Spyder 中上述代码生成的输出的作者图片

如前所述,程序最初将每个样本存储在主列表中,这将样本大小引入了内存存储等式。对于更昂贵的数据对象,例如字符串或用户创建的对象与整数相比,这种情况会变得更糟。

# Original version
# Initialize data objects to store sample, info for each iteration’s sample
metric_of_interest = []
samples = []# In the loop, store each sample as it's created
for i in range(n_iters):
 samp = id_samp.sample(n = smpl_sz, axis = ‘rows’, random_state = i)
 samples.append(samp)
 # Do something with the sample & record add’l info as necessary
 metric_of_interest.append(samp[‘Sales_Per_ID’].mean())

这里的关键是,我们不需要在创建时存储每个样本,以便以后访问它。我们可以利用随机抽样函数的固有属性,使用一个随机种子来获得可重复性。我不会深究使用随机种子作为最佳实践;但是这里有的另一篇媒体文章有一个相当彻底的解释,在这里你可以看到一些关于使用种子/随机状态的 NumPyPandas 文档。主要的收获是一个整数值可以用来“挑选”采样过程的开始;所以如果你存储了这个值,你就可以复制这个样本。这样,我们能够通过优化我们的存储方法来消除样本大小和数据类型对内存使用的影响

结果将是创建一个随机选择的整数组,每个循环迭代有 1 个值来创建样本。下面我展示了我自己独特的随机选择这些整数的方法,但是这可以通过很多方式来实现。

# Create a random sample of integers for use as ID sample random state seed
#Here, we pull a seq of nums 50x greater than num iters to samp from
rndm_st_sd_pop = pd.Series(range(0,n_iters*50))
rndm_st_sd_samp = rndm_st_sd_pop.sample(n = n_iters, axis = ‘rows’)
del(rndm_st_sd_pop)
print(rndm_st_sd_samp.head())

注意:在本例中,索引和随机种子值相等

Spyder 中上述代码生成的输出的作者图片

现在,您可以遍历您的随机种子样本,并将每个值作为参数提供给采样函数。请记住,无论使用何种语言,任何值得使用的采样函数都应该有这个参数。

# Initialize data object(s) to store info for each iter’s sample
metric_of_interest = []# In the loop, use the random state/seed to produce a sample you can easily reproduce later
for i in rndm_st_sd_samp:
 samp = id_samp.sample(n = smpl_sz, axis = ‘rows’, random_state = i)
 # Do something with the sample & record add’l info as necessary
 metric_of_interest.append(samp[‘Sales_Per_ID’].mean())# Bind the info saved for each iter to resp seed val for easy lookup
sample_data = pd.DataFrame(
    {'Avg_Sales_Per_ID': metric_of_interest,
     'Smpl_Rndm_St_Seed': rndm_st_sd_samp })
sample_data.reset_index(inplace = True)
print(sample_data.head())

Spyder 中上述代码生成的输出的作者图片

在这种情况下,对包和特定使用语言的深刻理解,加上代码审查的标准化过程,导致了运行时性能的实质性提高。这只是影响代码性能和可靠性的一种方式的一个例子,希望能启发你重新审视你很久以前写的东西。至于关于正式审查代码的进一步信息,这里是 Stack Overflow 的博客中对主题的一个非常好的讨论;还有一个专门讨论这个话题的栈交换社区。

随意摆弄图像压缩

原文:https://towardsdatascience.com/a-casual-flirt-with-image-compression-abbb2679f492

mustachescactusUnsplash 上的照片

介绍

想象一下。您的网络运行缓慢。Youtube 已经自动将 720p 设置为您的视频质量,您已经从您的 youtube 建议中随机添加了一些内容。有点标准的场景,对吧?

现在,很可能你喜欢内容,然后你突然意识到视频质量应该更好。将光标移至设置按钮,手动将视频设置为更高的质量。但是,你有没有注意到,当你手动调整视频质量时,youtube 为你加载的任何缓冲都丢失了:(

这到底是怎么回事?手动更改视频质量会发生什么情况?youtube 是如何动态改变视频质量的?所有这些都是非常有趣的问题,需要知道答案。让我们在下一节中尝试理解这些

动态视频质量切换

为了理解动态视频质量切换,理解视频如何从服务器流到我们的浏览器是非常重要的。一个通用的技巧是将每个视频分成多个分辨率,并将每个分辨率分成更小的片段。然后,浏览器负责请求所需的分辨率和代码片段,然后将它们整齐地拼接在一起,显示在屏幕上。让我们来看看更多的细节。

作者形象

源视频在后端被转换成多个分辨率,分成几秒钟(比如 2 秒)的小块。

现在,当用户在浏览器屏幕上打开视频时,youtube 会以默认分辨率启动,并获取一些块来启动视频。在接下来的几秒钟内,客户端可以更好地统计网络容量,并基于此向服务器请求不同质量的数据块,以便为用户提供最佳体验。该统计数据被持续维护,并不断适应不断变化的网络行为。

作者形象

上图是视频质量在客户端如何动态变化的时间轴示例。

使用在所有主流浏览器中实现的媒体源扩展 API,在客户端将块缝合在一起。它提供了支持无插件的基于网络的流媒体的功能。使用 MSE,可以通过 JavaScript 创建媒体流,并使用[<audio>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio)[<video>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video)元素播放。

如果你对整个过程的更多细节感兴趣,请参考这篇中间文章

那么问题是什么呢?

常见的情况是用户在中途明确改变视频质量。下面的事情发生了,播放突然停止,播放器等待下载更多的块。更重要的是,所有下载的缓冲区都被丢弃。现在,这可能与用户无关,但为技术迷带来了一个有趣的声明。

我们能把视频数据构造成本质上是可加的吗?

我来解释一下上面的说法,作为一个大概的估计。

作者图像

上表参考了来自哨声的数据

如果浏览器当前拥有 1 分钟的 360p 缓冲区,然后用户请求 720p 视频质量,则 5MB 数据将被丢弃,另外 25MB 数据用于这 1 分钟的视频。

然而,如果我们有一个附加算法或方法,我们可以只请求服务器只发送大约 20MB 的信息来添加到 360p 视频,并使其成为 720p 视频。那岂不是很酷!

公式化优化问题

让我们试着进一步解决这个问题。因为视频最终是一系列图像,所以我们可以用图像而不是视频来表述这个问题吗?让我们也把范围缩小到黑白图像,因为图像本质上是 3 个通道的融合。

构建图像数据,使其可以切片以创建一个差的图像,但增加更多的数据切片,图像质量会不断提高!

让我们看一些伪代码

base_image **=** get_buffer**(**'my_image'**,** **(**0**,** 20**))**
display**(**base_image**)***# Now let's bring some additive data to improve the image quality* additive_info **=** get_buffer**(**'my_image'**,** **(**20**,** 60**))**
improved_image **=** base_image**.**add**(**additive_info**)**
display**(**improved_image**)**

图像作为矩阵

图像是数字的矩阵。600x400 的黑白图像必然是形状(600,400)的矩阵。一想到矩阵,矩阵代数就暗示一个矩阵可以由一堆其他矩阵组成。

作者形象

原始图像 X (600,400)可以分解成两个矩阵 A (600,c)和 B (c,400 ),使得 A 和 B 的点积产生 X。

此外,矩阵 A 和 B 应该使得任何切片 A(600,d)和 B(d,400)也尽可能精确地产生原始图像 X。

作者形象

基于梯度的优化

一旦清楚地表述了这个问题,用 PyTorch 为它编写一个优化器就非常容易了。我们来看看实现。

让我们把下面的图片作为我们的参考图片。

UnsplashFlipboard 拍摄的照片

让我们加载图像并将其转换为黑白图像。

*# imports* from PIL import Image
import numpy **as** np*# load the image and convert it into a b/w image* img **=** Image**.***open***(**'reference_image.png'**)**
bw_img **=** img**.**convert**(**'L'**)**bw_img**.**show**()***# get the numpy matrix from the image data* data **=** bw_img**.**getdata**()**
data **=** np**.**asarray**(**data**)**

作者形象

现在让我们编写一个模块,将图像分成两个矩阵,这样两个矩阵的点积就可以重建原始图像

*# imports* import torch
import random
from torch.optim import Adam**,** SGD
from torch import nn
from torch.nn.functional import l1_loss *# target variable* Y **=** torch**.**from_numpy**(**data**)**
*# converting b/w data a [0,1] range* Y **=** Y**/**255 **class** **MF(**nn**.**Module**):**
    **def** **__init__(***self***,** H**,** W**,** N**):**
        *super***().**__init__**()**
        *# Defining parameters A and B
*        *self***.**A **=** nn**.**Parameter**(**torch**.**rand**(**H**,** N**))**
        *self***.**B **=** nn**.**Parameter**(**torch**.**rand**(**N**,** W**))**
        torch**.**nn**.**init**.**xavier_uniform_**(***self***.**A**)**
        torch**.**nn**.**init**.**xavier_uniform_**(***self***.**B**)**

    **def** **forward(***self***,** diff**):**
        **if** diff **>** 0**:**
            *# slice matrices 
*            A_i **=** *self***.**A**[:,** **:-**diff**]**
            B_i **=** *self***.**B**[:-**diff**,** **:]**
        **else:**
            A_i **=** *self***.**A
            B_i **=** *self***.**B *# construct image from the matrices
*        y_hat **=** torch**.**sigmoid**(**A_i **@** B_i**)**
        **return** y_hat

上面的模块非常简单。我们定义了形状为 A(H,N)和 B(N,W)的两个矩阵 A 和 B,使得矩阵乘法给出形状为(H,W)的矩阵。

此外,正向方法有助于训练矩阵,使得即使是它们的切片也能产生原始图像。

现在让我们看看所有神奇的事情发生的训练循环。

*# init the model and the optimizer* D = 200
mf **=** MF**(**H**,** W**,** D**)**
optimizer **=** Adam**(**mf**.**parameters**(),** lr**=**0.001**)***# some variables* running_loss **=** 0
STEPS **=** 128 ***** 50
LOG_STEPS **=** 32losses **=** **[]**
*# Training loop* **for** i **in** *range***(**STEPS**):**
    diff **=** random**.**randint**(**0**,** 100**)**
    y_hat **=** mf**.**forward**(**diff**)**    

    *# l1 + l2 loss
*    loss **=** torch**.***abs***(**Y **-** y_hat**).**mean**()** **+** **(**Y **-** y_hat**).**square**().***sum***().**sqrt**()**
    loss **=** loss
    li **=** loss**.**item**()**
    running_loss **=** 0.9 ***** running_loss **+** 0.1 ***** li

    *# optimizer steps and parameter updates
*    loss**.**backward**()**
    optimizer**.**step**()**
    optimizer**.**zero_grad**()**

    **if** i**%**LOG_STEPS **==** 0**:**
        **print(**running_loss**)**

上面的循环随机选择一个数字传递给模型的 forward 方法,该方法在内部对矩阵进行切片并生成原始图像。

它迫使矩阵 A 和 B 的参数如此,使得最初的几个切片具有更多信息,而后面的切片具有丰富图像的附加信息

结果呢

让我们首先定义一些效用函数,就像我们在开始时在伪代码中设想的那样

**def** **get_buffer(**mf**,** rnge**=(**0**,**20**)):**
    *# given the original parameters and a range, return the slices of matrices A and B
*    D = mf.A.shape[1]
    l = int(rnge[0]/100 * D)
    r = int(rnge[1]/100 * D)
    A = mf.A[:, l: r]
    B = mf.B[l: r, :]

    data **=** **(**A**,** B**)**
    **return** data**def** **add_buffers(**buffers**):**
    *# concatenate the buffer slices
*    **return** **(**torch**.**cat**([**b**[**0**]** **for** b **in** buffers**],** dim**=**1**),** torch**.**cat**([**b**[**1**]** **for** b **in** buffers**]))****def** **display(**data**):**
    *# construct image using the buffers
*    img **=** **(**torch**.**sigmoid**(**data**[**0**]@**data**[**1**])***255**).**detach**().**numpy**()**
    **return** Image**.**fromarray**(**np**.**uint8**(**img**))**

现在,让我们从 quality (0,20)获得一个缓冲区并显示它

*# display the first 20% quality* buff_0_20 **=** get_buffer**(**mf**)**
display**(**buff_0_20**)**

作者形象

很糟糕,但还是可以辨认的。现在让我们得到下一组数据来提高这张图像的质量。

*# get additional 40% (20, 60) data and append it to the earlier buffer* buff_20_60 **=** get_buffer**(**mf**,** **(**20**,** 60**))**
added_buffer_0_60 **=** add_buffers**([**buff_0_20**,** buff_20_60**])**
display**(**added_buffer_0_60**)**

作者形象

现在好多了。让我们看看最终的图像,它具有我们所拥有的最好的质量。

*# get the last 40% (60, 100) data and append it to the earlier buffer* buff_60_100 **=** get_buffer**(**mf**,** **(**60**,** 100**))**
added_buffer_0_100 **=** add_buffers**([**added_buffer_0_60**,** buff_60_100**])**
display**(**added_buffer_0_100**)**

作者形象

这个图像看起来更接*我们的基础图像。

结论

在这篇文章中,我们探讨了动态视频质量在 YouTube 这样的网站上是如何工作的,以及前端和后端技术的结合是如何实现的。我们看到了缓冲区不是可加的,因此当用户要求运行视频的更高质量时,缓冲区会被完全丢弃。

我们使用矩阵分解的思想和定制的训练过程来制定加法方法,该训练过程为输入图像 X 构造两个矩阵 A 和 B,使得

A[:,:N] ⋅ B[:N,:]≈X

使得较大的 N 给出图像 X 的更好的*似

参考

在 Python 中处理时间序列数据的必备技巧集合

原文:https://towardsdatascience.com/a-collection-of-must-know-techniques-for-working-with-time-series-data-in-python-7c01d199b184

如何轻松操作和可视化日期时间格式的时间序列数据

xsIJciJN 的插图来自illustrtac

起初,处理时间序列数据可能会令人生畏。时间序列值不是您需要考虑的唯一信息。时间戳还包含信息,尤其是关于值之间关系的信息。

时间戳还包含信息,尤其是关于值之间关系的信息。

与常见的数据类型相比,时间戳有一些独特的特征。虽然乍一看它们像一个字符串,但它们也有数字方面。

本文将为您提供以下处理时间序列数据的必备技巧:

如何处理日期时间格式
读取日期时间格式
将字符串转换为日期时间格式
将 Unix 时间转换为日期时间格式
创建日期范围
更改日期时间格式
如何合成并分解日期时间
分解日期时间
∘ 填充缺失值
用常数值填充缺失值
用最后一个值填充缺失值
用线性插值填充缺失值
如何对时间序列执行运算
获取最小值和最大值
求差
累积 两个时间戳之间的时间差
如何过滤时间序列
过滤特定时间戳上的时间序列
过滤时间范围上的时间序列
如何对时间序列进行重采样
下采样
上采样
绘制时间线
设置时间序列的 x 轴限值
设置时间序列的 x 轴刻度

对于本文,我们将使用一个最小的虚构数据集。它有三列:datecat_featurenum_feature

虚构的最小时间序列数据集作为 pandas DataFrame 加载(图片由作者提供)

如何处理日期时间格式

时间序列数据的基本部分是时间戳。如果这些时间戳采用 Datetime 格式,您可以应用各种操作,我们将在本节中讨论。

读取日期时间格式

默认情况下,当从 CSV 文件中读取时,pandas 将时间戳列作为字符串读入 DataFrame。要将时间戳列作为 datetime 对象(数据类型为datetime64[ns])直接读取,可以使用parse_date参数,如下所示。

import pandas as pddf = pd.read_csv("example.csv", 
                 **parse_dates = ["date"]**) 

虚构的最小时间序列数据集作为 pandas DataFrame 加载(图片由作者提供)

时间戳的数据类型为 datetime64ns

将字符串转换为日期时间

要将字符串转换成datetime64[ns]格式,可以使用[.to_datetime()](https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html)方法。如果在导入过程中不能使用parse_dates参数,这很方便。可以查一下相关的[strftime](https://strftime.org/) 格式。

# By default the date column is imported as string
df = pd.read_csv("example.csv")# Convert to datetime data type
df["date"] = pd.to_datetime(df["date"], 
                            **format = "%Y-%m-%d %H:%M:%S.%f"**)

虚构的最小时间序列数据集作为 pandas DataFrame 加载(图片由作者提供)

将 Unix 时间转换为日期时间格式

如果您的时间戳列是 Unix 时间,那么您可以通过使用unit参数,使用[.to_datetime()](https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html)方法将其转换为人类可读的格式。

# Convert from unix
df["date"] = pd.to_datetime(df["date_unix"], 
                                 **unit = "s"**)# Convert to unix
df["date_unix"] = df["date"]**.view('int64')**

时间戳转换为 unix(图片由作者提供)

创建日期范围

如果要创建一个日期范围,有两种选择:

  • 用开始日期和结束日期定义日期范围
  • 用开始日期、频率(例如,每天、每月等)定义日期范围。),以及周期数。
df["date"] = pd.date_range(start = "2022-01-01", 
                           end = "2022-12-31")df["date"] = pd.date_range(start = "2022-01-01", 
                           periods = 365, 
                           freq = "D")

熊猫系列的一系列日期(图片由作者提供)

更改日期时间格式

要更改时间戳格式,您可以使用[.strftime()](https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior)方法。

# Example: Change "2022-01-01" to "January 1, 2022"
df["date"] = df["date"].dt.strftime("%b %d, %Y")

用 strftime 更改了 datetime 格式(图片由作者提供)

如何合成和分解日期时间

时间戳由许多东西组成,比如日期或时间,或者更细粒度的东西,比如小时或分钟。这一节将讨论如何将日间数据类型分解成它的组成部分,以及如何从包含时间戳组成部分的不同列组成一个日期时间数据类型。

分解日期时间

当您有日期和时间戳时,您可以将它们分解成它们的组成部分,如下所示。

# Splitting date and time
df["dates"] = df["date"].dt.date
df["times"] = df["date"].dt.time

时间戳分解为日期和时间(图片由作者提供)

您还可以将它分解成更小的组件,如下所示。您可以在 pandas DatetimeIndex 文档中找到更多可能的组件。

# Creating datetimeindex features
df["year"] = df["date"].dt.year
df["month"] = df["date"].dt.month
df["day"] = df["date"].dt.day
# etc.

时间戳分解为年、月和日(图片由作者提供)

将多个列组合成一个日期时间

如果您想从年、月和日等组成部分组装一个日期列,也可以使用[.to_datetime()](https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html)方法。

df["date"] = pd.to_datetime(df[["year", "month", "day"]])

将多个列组合成一个日期时间(图片由作者提供)

如何填充缺失值

无论是处理数值、分类还是时间序列数据,填充缺失值都是一项挑战。本节将探讨三种方法来填充时间序列数据中的缺失值。

用常数值填充缺失值

一种方法是使用.fillna()方法用常量值填充缺失值。通常,这样的常数值可以是时间序列的平均值或异常值,如-1 或 999。然而,用常数值填充缺失值通常是不够的。

df["num_feature"] = df["num_feature"].fillna(0)

用常数值填充缺失值(图片由作者通过 Kaggle 提供)

用最后一个值填充缺失值

另一种方法是使用.ffill()方法用最后一个可用的值来填充缺失的值。

df["num_feature"] = df["num_feature"].ffill()

用最后一个值填充缺失值(图片由作者通过 Kaggle 提供)

用线性插值填充缺失值

处理缺失值的一个好方法通常是用.interpolate()方法对缺失值进行线性插值。

df["num_feature"] = df["num_feature"].interpolate()

用线性插值填充缺失值(图片由作者通过 Kaggle 提供)

如何对时间序列执行操作

您可以对时间序列数据执行各种操作,这将在本节中讨论。

获取最小值和最大值

在许多情况下,了解时间序列的开始或结束日期会很有帮助。

df["date"].min()
df["date"].max()

区别

差分是指取时间序列中两个连续值的差。为此,您可以使用.diff()方法。

df["num_feature_diff"] = df["num_feature"].diff()

时间序列数据的差异(图片由作者提供)

累积

与差分相反的是用.cumsum()方法累加时间序列的值。

df["num_feature_cumsum"] = df["num_feature"].cumsum()

时间序列数据的累积(图片由作者提供)

获取滚动平均值

有时你需要时间序列的滚动平均值。您可以使用.rolling()方法,它接受一个在滚动窗口中要考虑的值的数量的参数。在下面的例子中,我们取三个值的平均值。所以前两行是空的,第三行是前三行的平均值。

df["num_feature_mean"] = df["num_feature"].rolling(3).mean()

时间序列数据的滚动平均值(图片由作者提供)

计算两个时间戳之间的时间差

有时候你需要计算两个时间戳之间的时间差。例如,如果您可能需要计算特定日期的时差。

df["time_since_start"] = df["date"] - df["date"].min()

时间戳和第一个时间戳的时间差(图片由作者提供)

或者想知道时间戳是否是等距离分布的。

df["timestamp_difference"] = df["date"].diff()

时间戳之间的时间差(图片由作者提供)

如何过滤时间序列

处理时间序列数据时,您可能需要在特定时间对其进行过滤。要过滤时序数据,必须将日期列设置为索引。一旦有了时间戳索引,就可以在特定的日期甚至特定的时间范围内填写。

df = df.set_index(["date"])

以时间戳为索引的时间序列数据的数据框架(图片由作者提供)

根据特定时间戳过滤时间序列

当您将时间戳设置为熊猫数据帧的索引时,您可以使用loc轻松过滤特定的时间戳。

df.loc["2020-03-30"]

过滤的某一日期的 tme 系列(图片由作者提供)

按时间范围过滤时间序列

与上面过滤特定时间戳的例子类似,当时间戳被设置为 pandas 数据帧的索引时,您也可以使用loc过滤时间范围。

df.loc["2020-04-10":"2020-04-15"]

在某一日期范围内过滤的 tme 系列(图片由作者提供)

如何对时间序列进行重采样

重采样可以提供数据的附加信息。有两种类型的重采样:

向下采样

下采样是指降低采样频率(例如,从几秒到几个月)。可以用.resample()的方法。

upsampled = df.resample("M")["num_feature"].mean()

每月重新采样(缩减采样)值系列(图片由作者提供)

上采样

上采样是指增加采样频率(例如,从几个月到几天)。同样,你可以使用.resample()方法。

upsampled.resample("D").interpolate(method = "linear")

一系列每日重采样(上采样)值(图片由作者提供)

如何绘制时间序列

本节将讨论如何使用 Matplotlib 和 Seaborn 可视化数字和分类时间序列数据。除了pyplot模块,我们将使用dates模块探索不同的可视化技术。

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import seaborn as sns

为了直观显示时间序列的时间顺序,图中的 x 轴通常表示时间,y 轴表示值。

绘制一段时间内的数字数据

大多数时间序列数据是数字的,例如温度或股票价格数据。要可视化数字时间序列数据,可以使用折线图。

sns.lineplot(data = df, 
             x = "date", 
             y = "num_feature")

数字时间序列数据的折线图(图片由作者提供)

绘制一段时间内的分类数据

有时时间序列数据可以是分类的,例如,跟踪不同事件的发生。

在绘制数据之前,您可以对分类列进行标签编码,例如,通过使用 LabelEncoder 或简单的字典,如下所示。

# Label encode the categorical column
enum_dict = {}
for i, cat in enumerate(df.cat_feature.unique()):
    enum_dict[cat] = idf["cat_feature_enum] = df["cat_feature"].replace(enum_dict)

将编码特征“cat_feature”标注为“cat_feature_enum”(图片由作者提供)

要可视化分类时间序列数据,可以使用散点图。

fig, ax = plt.subplots(figsize=(8, 4))sns.scatterplot(data = df,
                x = "date", 
                y = "cat_feature_enum", 
                hue = "cat_feature",
                marker = '.',
                linewidth = 0,
                )ax.set_yticks(np.arange(0, (len(df.cat_feature.unique()) + 1), 1))
ax.set_yticklabels(df.cat_feature.unique())
ax.get_legend().remove() # remove legend - it's not necessary hereplt.show()

带有散点图的分类时间序列数据的事件图(图片由作者提供)

也可以试试 Matplotlib 的 eventplot 演示

绘制时间表

为了绘制时间线,我们将使用上一节的标签编码分类值和vlines

fig, ax = plt.subplots(figsize=(8, 4))ax.vlines(df["date"], 0, df["cat_feature_enum"])plt.show()

带有 vlines 的分类时间序列数据的时间线图(图片由作者提供)

设置时间序列的 X 轴限制

当您想要设置时间序列图的 x 轴界限时,范围必须是datetime64[ns]数据类型。

例如,您可以使用时间序列的最小和最大时间戳:

ax.set_xlim([df.date.min(), df.date.max()])

或者您可以指定一个自定义范围,如下所示:

ax.set_xlim(np.array(["2020-04-01", "2020-04-30"],
                      dtype="datetime64"))

调整后的 x 轴范围(图片由作者提供)

设置时间序列的 X 刻度

为了提高数据可视化的可读性,您可以在特定的时间间隔(例如,每周、每月、每年等)添加主要和次要 x 刻度。)

ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %d"));
ax.xaxis.set_minor_locator(mdates.DayLocator())

自定义 x 轴刻度(图片由作者提供)

结论

当您不熟悉日期时间数据类型时,开始处理时间序列数据可能会很有挑战性。如您所见,日期时间数据类型有许多实用的内置方法,可以轻松操作时间序列数据。本文讨论了从操作时间序列值的时间戳和有价值的操作到可视化时间序列数据的所有内容。

喜欢这个故事吗?

以下是我其他时间序列分析和预测文章的合集:

Leonie Monigatti

莉奥妮·莫尼加蒂

时间序列分析和预测

View list6 storiesHierarchical time series forecastingGIF created with Matplotlib and imageioA time series with lines indicating trend and variance for time series analysis. The last three data points are in a contrast color to indicate that they are predicted values from the time series forecasting model

如果你想把我的新故事直接发到你的收件箱,请务必订阅https://medium.com/subscribe/@iamleonie

成为媒介会员,阅读更多来自我和其他作家的故事。报名时可以使用我的 推荐链接 支持我。我将收取佣金,不需要你额外付费。

*https://medium.com/@iamleonie/membership

Twitter 上找我LinkedInKaggle!***

机器学*项目中要避免的一个常见错误

原文:https://towardsdatascience.com/a-common-mistake-to-avoid-in-machine-learning-projects-b2f32c1a029d

马文·埃斯特夫在 Unsplash 上拍摄的照片

机器学*项目通常非常令人兴奋,如果开发得当,可能会给公司带来很多价值。然而,即使有些项目看起来相似,但每个项目都有其独特的特点,必须谨慎开发以避免错误。

急于解决项目并开始开发模型以获得结果可能会导致一些常见的错误。其中一些错误很容易被检测到,一些库甚至被编程来捕捉这些错误(比如当您向模型输入空值时)。

其他的,更加微妙,你的模型可能运行得很好,但是得到的结果计算不正确,可能不具有代表性。

场景

Y 你是一名数据科学家,任务是开发一个模型来帮助医生分类患者是否有更高的几率患心脏病。

让我们来看看为这个项目开发机器学*模型所采取的步骤,该模型使用在医疗检查中获得的医疗历史信息来分类给定患者在未来是否有机会患心脏病。

您希望看到,使用所有这些信息,您的模型是否能够检测出某个特定患者是否应该得到额外的关注,因为他/她患心脏病的可能性更大。

照片由马库斯·温克勒Unsplash 上拍摄

逐步地

  • 您读取数据:
    -
    信息包含在不同的表中,因此您读取所有的表并执行必要的连接以获得单个数据集。
  • 执行探索性数据分析:
    ——检测数据集中是否存在空值。
    -对于某些特性,您看到移除具有空值的样本是有意义的,因为它们代表可用数据的一小部分,所以您不会丢失太多信息;
    -对于布尔特征,你决定使用列的模式来替换空值;
    -对于一些数字特征,用列的平均值替换空值。
  • 执行一些特征工程:
    ——你正在使用的数据有一个时间戳列,所以你创建一些新的特征比如考试的日、月、年,使用这个特征;
    -您正在使用的数据有一列是受试者的年龄,因此您创建了一个新列,将该患者归类到您创建的几个年龄范围之一。
  • 选择一个机器学*模型:
    ——你把你的数据分成训练集和测试集。然后,使用训练集训练一些模型,并使用测试集评估它们,保存每个模型的结果并选择最佳模型。
    -在评估过程中,您已经使用贝叶斯搜索或 Optuna 优化了每个模型的超参数。
  • 创建一个报告:
    ——你创建一个有几个图形的报告,计算你认为必要的所有指标,以显示这个模型是一个好的工具,应该使用;你的结果很有希望,所以你跑去见你的老板,他已经梦想着新的晋升。

照片由 krakenimagesUnsplash 上拍摄

都好吧?

这种循序渐进的方法看起来是开发所提议模型的一个很好的第一步,但是它包含了一个微妙而关键的错误。你能察觉到吗?

Luis TostaUnsplash 上拍摄的照片

它所包含的问题有一个很有名的名字:数据泄露。

D 数据泄漏是指在训练阶段,您的模型可以访问测试集的信息,而这些信息在这个阶段是不可用的。

这意味着从测试阶段获得的结果很可能被高估,而你的模型在投入生产时很可能表现不佳。

为了检验该模型是否能够很好地概括,必须用以前从未见过的数据对其进行测试。但是,在处理空值时,您会意外地将测试样本中的信息输入到训练样本中。

Jelleke VanooteghemUnsplash 上拍摄的照片

W 当您用列的均值或 de 模式替换空值时,您使用了所有数据集来计算这些指标。因为当您计算均值/模式时,训练集和测试集仍然在一起,所以来自测试数据的信息会泄漏到训练数据中。

稍后,当您的模型使用训练集进行训练时,它可以访问有关测试集的一些信息。这使得模型“知道”一点测试数据,当它后来被测试时,测试集对它来说并不是一个全新的东西。

这似乎是一个小问题,但由于机器学*模型应该能够很好地概括,它们必须用全新的信息进行测试,以模拟生产环境可能的样子。

好了,现在我们如何解决这个问题?

肯·苏亚雷斯在 Unsplash 上拍摄的照片

到避免数据泄露在这种情况下,在替换空值之前,首先你必须把数据分成训练集和测试集。然后,使用训练数据,计算列的平均值和模式。

这些度量将用于替换训练数据中的空值,以及测试数据中的空值。其余的步骤保持不变。

这样做,您只是避免了将有关测试数据的信息输入到训练数据中,而现在,当您的模型被测试时,它会被赋予全新的信息,并且可以对其进行适当的评估。

必须使用训练数据来计算像归一化和空替换这样的预处理步骤。然后,测试数据必须通过相同的步骤,但是使用通过训练数据计算的信息。来自测试数据的信息绝不能与训练数据混合。

照片由 Felicia BuitenwerfUnsplash 上拍摄

这种事可能发生在任何地方,任何人身上,所以如果这种事已经发生在你身上,或者你没有马上发现问题,不要难过。

这个特殊的问题不久前发生在我身上,我想就这个问题阐明一下。

感谢您的阅读,希望对您有所帮助。

欢迎任何意见和建议。

请随时通过我的 Linkedin 联系我,并查看我的 GitHub。

领英

Github

Python 熊猫模块中 GroupBy 和 Pivot 的比较

原文:https://towardsdatascience.com/a-comparison-of-groupby-and-pivot-in-pythons-pandas-module-527909e78d6b

有时,有多种方法来显示来自数据的相同见解。

照片由卢克·切瑟Unsplash 上拍摄

数据科学家的主要工作之一是向观众展示来自数据的关键见解。在这样做的时候,数据科学家必须考虑两件重要的事情:1) 应该传达什么样的关于数据的主要思想,以及 2) 应该如何分析并最终展示它们。

有许多工具用于清理、处理、分析和显示数据。在本文中,我将重点介绍 Pandas,这是 Python 中一个流行的模块,包含各种不同的数据分析工具。

通过 Pandas 显示数据的一个关键方法是计算复杂数据点集合的汇总统计数据。下面,我将比较在相同数据上完成这项任务的两种不同方法:1)使用分组和聚合,2)使用数据透视表。

数据和目标

对于这个任务,我们将使用下面的数据集[1],称为nba_full。它包含了从 1996-97 赛季开始的许多不同赛季中各种 NBA 球员的信息。

图片作者。

原始数据集还有几列,但是我已经提取出了对我们最重要的几列:player_nameteam_abbreviationageptsseason

我们的目标是重新格式化这些数据,以有意义地显示下面的汇总统计:NBA 球队每个赛季的平均球员年龄。例如,假设有人问“2019-20 赛季洛杉矶湖人队的平均球员年龄是多少?”我们应该能够很容易地从重新格式化的数据中读出这个问题的答案。

让我们来看两种可能的方法。

使用 GroupBy 聚合

作为快速复*,Pandas 中的分组和聚合通常是通过使用groupby函数按特定列对值进行分组,然后使用相应的agg函数从不同的列中聚合值。

让我们先看一个更简单的例子,然后再从上面讨论我们的目标,因为这有点高级。假设我们运行下面一行代码:

nba_full.groupby('player_name')[['pts']].agg('mean')

作者图片

这个代码显示了数据中每个球员在所有赛季中的平均得分。这里有几个要点来一步一步地解释代码的作用:

  • 按列'player_name'分组指定该列为我们的索引。然后,所有值将被聚合到由该列中的唯一值指定的组中。
  • 我们选择列'pts'作为我们想要计算聚合值的列,因为默认情况下Pandas 将聚合所有其他列。我们选择带双括号的列,以便 Pandas 返回 DataFrame 而不是 Series。
  • 我们调用.agg('mean')来指定对于每个独特的球员,我们想要计算他们在表格中出现的所有不同时间的平均分数(对应于他们在 NBA 打球的所有不同赛季)。我们可以使用许多其他内置函数(如maxcount),如果我们愿意,我们也可以使用用户定义的函数。

现在,我们准备着手我们的主要任务。回想一下我们的目标:我们想要 NBA 球队每个赛季的平均球员年龄。如果你仔细阅读,你会发现我们实际上想要计算数据中两个列的汇总统计数据:球队和赛季。为了完成这个任务,我们将需要在对groupby的调用中指定我们是按照这两个列进行分组的。

让我们看一下代码和输出,然后我们将一步一步地分解它:

nba_full.groupby(['team_abbreviation', 'season'])[['age']].agg('mean')

图片作者。

代码的一些要点:

  • 我们按两列分组,这给了我们一个多索引的数据框架。
  • 代码的剩余部分类似于我们的第一个例子:我们提取出感兴趣的列'age'进行聚合,然后我们指定聚合函数'mean'
  • 解释代码的方式如下:对于每个独特的球队和赛季组合,计算列'player_age'的平均值。例如,包含ATL1996-97的每一行都被识别,并且这些行中的所有年龄被平均以产生我们上面看到的最后的第一行。对其他组合重复该过程,以产生剩余的行。

作为最后的美学注释,我建议在结尾处重新设置索引,以使输出更具可读性:

nba_full.groupby(['team_abbreviation', 'season'])[['age']].agg('mean').reset_index()

图片作者。

现在你知道了!这就是我们如何使用分组和聚合来实现我们的初始目标。接下来,我们将考虑如何使用数据透视表来完成同样的任务。

使用数据透视表

在 Pandas 中,数据透视表在技术上仍然是一个数据框架,但它通常指的是以某种方式格式化的表。具体来说,数据透视表重新排列数据,使一列的值成为行标签,第二列的值成为列标签,第三列的值为每个组合进行聚合。

这是一个有点复杂的句子,所以让我们把它应用到我们的目标。在我们的例子中,我们可以像下面这样做:

  • 让独特的 NBA 球队成为行标
  • 使独特的季节成为列标签
  • 使用'mean'函数合计每个组合的年龄。

听起来熟悉吗?这正是我们用上面的groupby完成的,但是格式略有不同。让我们来看看完成这项任务的代码:

nba_full.pivot_table(index='team_abbreviation', columns='season', values='age', aggfunc='mean')

图片作者。

任务完成!现在,我们可以查看表中每个条目对应的行和列,以确定它对应的球队和赛季组合。以下是关于代码如何工作的一些要点:

  • 'index'指定我们想要哪个列的唯一标签作为输出数据帧的索引(或行标签)。
  • 'columns'指定我们想要哪个列的唯一值作为输出数据帧的列标签。
  • 'values'指定包含我们要聚合的值的列。
  • 'aggfunc'指定我们想要使用的聚合函数。

这样,我们看到我们遇到了另一种表示相同数据的方法。

为什么重要?

那么,使用一种技术比使用另一种技术有什么不同吗?从严格的定量角度来看,人们可能会认为这并不重要——最终,两个输出表显示的是相同的数据。

然而,好的数据科学不仅仅是数字。展示数据时,重要的是要考虑什么样的展示能最大限度地让观众理解。

没有一种“更好”的技术——这取决于具体情况。例如,许多人可能通常更喜欢pivot_table显示,因为他们发现匹配行和列来查找聚合值更自然。然而,当您的数据有许多唯一的标签时,数据透视表会变得混乱。即使在上面的例子中,我也只能显示输出数据帧的一部分(右边还有更多列),因为它太大了,无法完全粘贴。相比之下,groupby显示在每个单独的行中具有较少的信息,并且导致较少的信息负载。对于较小的数据集,透视显示可能更有用,因为它(可以说)更符合聚合数据的逻辑显示。

最重要的是探索多种演示,并确保最终的决策优先考虑最终将使用、解释或查看这些数据的人的利益。如果你能做到这一点,那么你正在成为一名优秀的数据科学家的路上。

下次见,伙计们!

想擅长 Python? 获取独家,免费获取我简单易懂的指南 。想在介质上无限阅读故事?用我下面的推荐链接注册!

**https://murtaza5152-ali.medium.com/?source=entity_driven_subscription-607fa603b7ce---------------------------------------

参考

[1]https://www.kaggle.com/justinas/nba-players-data**

Python 和 Julia 中继承的比较

原文:https://towardsdatascience.com/a-comparison-of-inheritance-in-python-and-julia-fb7432cd4929

Python 和 Julia 范式的不同继承性概述

(图片由 Pixabay 上的 naobim 拍摄)

介绍

继承已经成为现代编程语言的一个主要特征。这个概念最初是由一种叫做 Simula 的语言实现的,它很快激发了 C++语言的诞生。当然,C++取得了巨大的成功,直到今天仍然如此,所以 C++通过其令人惊叹的泛型编程概念,真正将这个概念以及许多其他概念带到了编程语言设计的前沿。继承的概念贯穿了多年的编程,并最终出现在今天的几种高级编程语言中。集成了这一概念的编程语言的两个例子是 Python 和 Julia,这两种编程语言通常用于当今的计算科学。

虽然这两种语言在 2021 年主要用于类似的应用,但这两种语言也有根本的不同。Julia 是一种考虑到数值计算而创建的编译语言,它处理数字更像 FORTRAN 而不是 C++,Python 是一种为通用脚本创建的解释语言,它处理数字(至少没有 NumPy)的方式更像 C 或 Javascript。除了刚刚列出的技术差异,这两种语言在编程的核心理念上也有所不同。

Python 是一种面向对象的编程语言,尽管它肯定是多范式的,并且对面向对象没有特别严格的要求。我认为这是一件好事。Julia 在这一点上是相似的,你不需要强迫每件事都按照某种方式来编程。然而,Julia 更独特一些,这种语言是用一种新的范式编写的,它的创造者称之为“多调度范式”。在 Julia 中,范式是由语言定义的,所以没有办法说多重分派本身是或不是纯粹的范式,因为这是唯一一个将整个范式建立在多重分派基础上的语言实例。

也就是说,由于 Julia 和 Python 语言之间的这些基本差异,实现语言多范例的泛型编程概念当然需要以不同的方式处理。毕竟,在朱莉娅的情况下;如果一开始就没有类,我们如何继承我们类的子方法呢?这两种语言在语义上非常不同,因此它们内部的概念处理方式也不同。今天我想讨论和比较这两种语言是如何处理继承的,以及每种继承的范例和方法与另一种相比所具有的优势。

差别

从 Julia 的世界开始,继承是用抽象类型来处理的。与 Python 不同,在 Julia 中,抽象类型实际上只是一个名称,可以作为其他名称的别名。我们可以使用子类型操作符创建子类型。这个操作符也是一个 bool 类型的操作符,可以用来确定一个给定的类型是否是一个子类型。

abstract type Pickle endstruct BreadNButter <: 
    flavor::Symbol
end

在这种情况下,类型通过它们的名字来继承。面包巴特是泡菜的一个分支,因此,

BreadNButter <: Pickle

将返回 true,因为面包屑现在是泡菜的一个子类。至于实际上继承了什么,范式现在开始发挥作用。泡菜对面包师的传承是通过多重派遣实现的。我们可以通过简单地将方法分派给我们的抽象类型,而不是我们的类型,来使方法同时分派给几个类型。

flavor(p::Pickle) = p.flavor

Python 与众不同,因为它有一种非常传统的方法,通过类本身来创建方法的子类继承。Python 中的 Pickle 示例如下所示:

class Pickle:
    def __init__(flavor : string):
        self.flavor = flavor
    def flavor():
        return(self.flavor)
class BreadNButter(Pickle):
    def __init__(flavor : string):
        self.flavor = flavor

如果你想学*更多关于 Python 中继承和类型的知识,我也有一整篇关于这个主题的文章,你可以在这里阅读:

这两种实现实现了相似的目标。我们不必多次编写方法,因为它们被带入我们的新类或通过多次分派来分派。然而,这两种方法都有一些区别和优点。

差异的结果

假设继承的这两个实现做类似的事情,但是做的方式不同,那么使用这两个系统可能各有利弊。这些不一定是主观的,但应该注意的是,在大多数情况下,这些是优点还是缺点几乎完全取决于代码实际使用的场景。首先,也是最明显的相似之处是,这两者都继承了方法。然而,要指出的第一个也是最明显的区别是,只有 Pythonic 实现还将继承类型的属性。如果我们希望我们的类型在 Julia 中有相同的字段,那么就要由程序员来编写这些字段。在 Julia 中也有检查一致性的能力,但是不管怎样,每个类型的字段都需要单独编写。

另一个实质性的区别是 Julia 中的方法不是构造类型的子方法。因此,将方法添加到特定的定义中要容易得多,而且还要确保它能与可能被子类化的类型数组一起工作。这些方法完全在子类型之外,实际上是继承——而不是类型本身。至于这样做是否更好,这当然取决于场景,在有些情况下,我认为继承属性是非常重要的事情,例如视频游戏编程或用户界面,但是也有方法为王的情况,继承方法并添加继承的方法将比继承属性更有价值。

总的来说,我认为这是继承方面的一个很大的区别因素,它可以用来做两种语言之间的事情。Python 的例子使继承与类型的关系更加密切。另一方面,Julia 的版本为类型创建了一个简单得多的抽象方法,而是依靠方法中的多态性将这些方法应用于类型。考虑到我是一名数据科学家,继承的核心思想给了 Julia 一个明确的优势。数据科学通常在全球范围内进行,使用许多方法和简单的数据类型,而不是疯狂的结构。然而,对于其他项目,我肯定会看到 Python 占据优势。

总而言之,我认为就像编程语言世界中的任何其他东西一样,没有一种语言在继承方面比另一种语言更好。在某些情况下,不同的方法可能更好,但这类事情总会有所取舍。总而言之,就看你想做什么了。此外,这两种语言只是简单地提供了继承,这是其他不提供继承的语言的一个优势。感谢您的阅读,我希望这个小小的比较有助于确定什么可能是您的下一个项目的最佳选择,该项目以某种能力的继承为特色!

20 周免费自学数据科学基础知识的完整指南

原文:https://towardsdatascience.com/a-complete-20-week-guide-to-self-learn-data-science-fundamentals-for-free-bf5873f5acc6

一步一步的指南,学*所有的关键概念,做好工作准备

安德鲁·尼尔Unsplash 上拍照

介绍

自学数据科学可能会有压力。有许多主题需要学*和练*。许多人无法保持足够的精力来度过最初的学*阶段。许多人失败或将其视为艰难旅程的主要原因是,

  • 对要学*的主题缺乏明确性
  • 没有一个单一的资源/平台适合学*数据科学的所有知识。
  • 互联网上有大量的资源,但是找到最适合你的是一个挑战
  • 人们很容易迷失在细节中
  • 在自学的同时跟踪进度和测试你的技能并不容易

注册数据科学课程的人不会面临这些问题。他们有一个支持系统来帮助和指导他们。自学的人就不一样了。这篇文章将帮助你更好地规划你的学*之旅。这里提到的时间线是基于一个普通人。根据你的教育背景和经验,时间表可能会对你稍有改变。该计划还包括每个主题的免费学*资源。

第 1 至 3 周— Python 编程

学*数据科学的第一步是熟悉编程语言。根据最*的 Kaggle 调查,大约 80%的人主要在工作中使用 Python。如果您是编程新手,那么强烈建议您开始使用 Python。

关于 Python 最好的入门课程之一可以在 Kaggle 中找到。下面是课程的链接。完成本简介课程大约需要 5 个小时。

几乎你在数据科学项目中做的任何事情都会涉及到编码。从读取数据源中的数据、探索数据、提取见解、转换、特征工程、构建模型、评估性能和部署。

强烈建议花足够的时间,熟悉 Python 的各种功能。这不是火箭科学。这很容易通过实践获得。对于很少或没有编码经验的人来说,大约 2-3 周是很好的。但是最重要的一步是继续练*编码。你练*得越多,你就变得越好!

学* python 时需要重点关注的主题是,

  • 基本语法
  • 集合数据类型
  • 控制流
  • 循环和迭代
  • 函数和λ函数

下面是一个免费的互动平台,可以开始学* Python。

https://www.learnpython.org

第 4 周到第 6 周—处理数据和操作

任何数据科学项目的第一步都是从数据的角度理解问题。你得到的数据永远不会完美。这需要大量的操作。能够处理数据和操作的最重要的 Python 库是 Pandas。

熊猫图书馆提供了广泛的功能,使得数据分析变得非常容易。如果你是 Python 或 Pandas 的新手,那么从 PyData 的这个简单的 10 分钟教程开始吧。

https://pandas.pydata.org/docs/user_guide/10min.html

一旦你熟悉了基本的功能,这里有一个来自 Kaggle 的简短课程。这将有助于通过数据集学*熊猫。

提高你的熊猫技能的最好方法是更经常地使用它们。在 Kaggle 上挑选一个有趣的数据集。记下所有你需要答案的有趣问题。然后开始探索数据,得到那些问题的答案。这里挑选一个有趣的数据集很重要。这有助于保持你足够高的兴趣,这对学*很有帮助。

例如,如果您对房价感兴趣,则选择一个房价数据集。记下你的问题。他们可能会说,

  • 房产的平均价格是多少?
  • 该物业的平均楼龄是多少?
  • 随着房产的老化,它会影响整体价格吗?
  • 什么因素推动了房地产价格?

你可能需要关注的各种熊猫概念是,

  • 创建、读取和写入数据框
  • 选择和分配
  • 聚合和分组依据
  • 处理缺失数据
  • 合并不同来源的数据
  • 汇总、交叉表和透视功能

第 7 周到第 9 周—使用阵列

NumPy 是一个能够在数组上高效工作的库。很多时候我们需要处理多维数组。NumPy 有助于提高计算速度和有效利用内存。它支持许多数学函数。不仅如此,它还被许多其他 Python 包使用,如 Pandas、Matplotlib、scikit-learn 等。

如果你是一个绝对的初学者,那么下面的文章将有助于更好地理解 NumPy,所执行的操作,以及可视化输入输出的流行功能。

https://numpy.org/devdocs/user/absolute_beginners.html https://betterprogramming.pub/numpy-illustrated-the-visual-guide-to-numpy-3b1d4976de1d

在许多数据科学项目中,我们会处理数字数据。非数字属性通常也转换成数字数据。因此,对于热衷于数据科学的人来说,学*使用 NumPy 是至关重要的。了解 NumPy 的关键主题是,

  • 创建 1 维、2 维和 3 维数组
  • 索引、切片、连接和分割
  • 迭代和操作
  • 排序、搜索和过滤
  • 数学和统计运算

第 10 周—学*可视化

数据科学项目的成功取决于,

  • 数据科学团队对问题的理解程度如何?
  • 数据科学团队传达见解的清晰程度如何?

有助于这两者的一个关键因素是更好地可视化数据的能力。

人类更擅长从视觉数据中识别模式和趋势。人脑通常不太容易从表格或其他格式的数据中识别模式。学*使用可视化来分析和交流的艺术可以保证成功。

有许多支持可视化的包和库。而不是过多担心不同的选择。如果你能遵循这些简单的步骤,那就足够了,

  • 了解 Matplotlib——它是高度可定制的
  • 了解 Seaborn——它不是那么可定制,但非常容易和快速地构建视觉效果,这是数据分析的一个好选择
  • 构建迭代图表—以便更好地与最终用户沟通

下面的文章可以帮助你找到一条使用 Python 学*可视化的道路。

这里有一个来自 Kaggle 的简短课程,可以帮助你学*可视化。

第 11 至 12 周—数据科学统计学

数据科学项目的每个阶段都会用到统计数据。描述性统计有助于更好地理解数据,并对其进行总结以便于理解。

推理统计对于提取无法通过其他方式识别的洞察力非常有用。例如,如果我们考虑房地产数据,以了解最*的学校的评级或离最*的高速公路的距离是否对房地产价格有更好的影响。不仅仅是在数据分析方面!在建立预测模型时,统计数据对于衡量模型的性能非常有用。

学*统计学时需要理解的一件重要事情是。这不仅仅是几个星期就能覆盖的一个小区域。有人在读统计学的学士和硕士。你的目标应该是学*足够的知识来开始,并根据需要更新你的统计知识。要学*的关键主题是,

  • 描述和推断统计
  • 分配方式
  • 中心极限定理和误差幅度
  • 置信区间和置信水平
  • 因果关系和相关性
  • 统计测试

Khan Academy 的以下课程是一个很好的数据科学统计学入门课程

https://www.khanacademy.org/math/statistics-probability

第 13 至 15 周—学* SQL

许多对学*数据科学感兴趣的人往往不关注 SQL。事实上,SQL 是数据科学家所需的最重要的技能之一。数据主要存储在结构化数据存储中,SQL 知识对处理数据非常有帮助。

那些来自非编程背景的人需要专注并建立 SQL 技能。那些在学术上接触过 SQL 的人也需要更多的实践来更好地理解关键概念。在现实生活中,数据可能以不同的粒度出现在不同的表中。只有具备良好的 SQL 技能,您才能将数据转换成能够回答您的问题的格式。通过处理数据来学* SQL 的一个好平台是,

https://mode.com/sql-tutorial/

如果你正在寻找一个免费的选择,那么 Kaggle 和 Datacamp 提供了一个非常好的免费学* SQL 的课程。以下是课程的链接

下面是一些常用的 SQL 概念,

  • 选择分布在不同表中的数据
  • 过滤所需的数据集
  • 将数据聚合到所需的粒度
  • 使用 Rank()和 Row_Num()从特定序列中选择记录
  • 将复杂的查询分解成子查询

第 16 至 20 周—学*数据分析和特征工程

学*基本概念的最后一步是探索性数据分析和特征工程。在任何一个数据科学项目中,70%以上的时间都会花在数据分析上。在处理预测问题时,特征工程有助于提高准确性。

数据分析和特征工程技能不能仅仅通过阅读或报名参加课程来学*。这些技能只能通过实践获得。你在学*过程中动手越多,你学得越好,停留的时间也越长。

为了更好地理解数据分析中涉及的不同步骤,请阅读下面的文章。

https://careerfoundry.com/en/blog/data-analytics/the-data-analysis-process-step-by-step/

了解探索性数据分析中常用的技术。此外,要知道它们是如何有用的。查看下面的 Kaggle 笔记本,

https://www.kaggle.com/kashnitsky/topic-1-exploratory-data-analysis-with-pandas

在彻底的数据分析之后,下一步是特征工程。像其他任何东西一样,这些数据都不会是完美的。它将有许多问题,并且可能不是为某些算法或模型准备的格式。在这些情况下,我们需要使用合适的转换技术。下面是一个很好的特征工程入门课程,

一些常用的特征工程技术是,

  • 扔掉
  • 缩放比例
  • 热编码
  • 对数变换

对 kaggle 数据集研究得越多,对特征工程的了解就越多。论坛是学*特征工程新技术的好地方。执行特征工程时没有正确的方法或限制。你越创新,你的结果就越好。

为工作做好准备的提示

现在到了学*数据科学的最后阶段。完成基本概念后,下一步是为工作做准备。有一些提示可以帮助你增加被雇用的机会。

组织你的学*并跟踪你的进步

学*数据科学的关键是制定一个结构化的计划,保存有趣的资源以便重读,并跟踪你的进度。当你报名参加数据科学课程时,这些通常会被整理出来。但是自学数据科学非常困难。有许多主题需要学*,大多数好的在线资源都是付费的。确定最佳免费资源并制定计划确实需要时间。

如果你在尝试自学数据科学,寻找路线图。这是我根据 100 多个小时的研究准备的路线图。这个路线图有一个精选的流行免费资源列表,用于学*数据科学中的重要概念,它包括链接分配问题、面试问题和其他相关阅读材料

https://rsharankumar.gumroad.com/l/yufkcc

记住这是一次长途旅行

学*数据科学时,重要的是要明白这是一个漫长的旅程。学*从未停止。我有超过 10 年的经验,我几乎花 10%的工作时间来学*新事物。

学*数据科学不像跑短跑。这更像是跑马拉松。更好地利用你的精力是很重要的。此外,确保你有足够的动力去实现你的目标也很重要。

实践这是一个神奇的词

成功的关键是实践。有这么多可用的数据。所有需要做的就是识别正确的问题和正确的数据集。你在真实数据上练*的越多,你学到的就越多。

建立投资组合

虽然学*很重要,但努力和展示你的技能也很重要。这对数据科学的职业生涯很有帮助。展示你技能的最好方式是使用作品集网站。

建立一个作品集网站非常简单。你不需要任何先前的网站开发经验来为你建立一个组合网站。如果你正在寻找为自己建立一个投资组合网站。查看下面的文章,它解释了为你建立一个的步骤。

保持联系

面向初学者的完整数据科学课程

原文:https://towardsdatascience.com/a-complete-data-science-curriculum-for-beginners-825a39915b54

UCL 数据科学协会:Python 介绍,数据科学家工具包,使用 Python 的数据科学

作者图片

今年,UCL 数据科学协会旨在创建一个完整的数据科学课程,旨在帮助其他学生踏上数据科学之旅。为此,我们在前几年工作的基础上创建了一系列研讨会,涵盖数据科学家旅程的三个主要领域。

  • Python 简介:一系列四个研讨会,涵盖 Python 中使用的基本符号和结构,以便能够理解后面研讨会中使用的编码。
  • 数据科学家工具包:一系列五个研讨会,涵盖 Numpy、Matplotlib 和 Pandas 的任何数据科学家工具包中的三个关键库,以及 Git、GitHub 和 SQL 的关键工具。
  • 使用 Python 的数据科学:一系列九个研讨会,涵盖了机器学*模型的四个主要类别:回归、分类、聚类和通过应用于不同数据集和示例的几种最常用算法进行降维。

这样做的目的是涵盖任何数据科学家在他们的旅程中需要的所有基础知识,而不会太快进入太多细节。下面,您将看到每个研讨会上介绍的所有工具和方法的描述,包括它们的用途、优点和缺点,这些都是您在数据科学之旅中需要了解的。这包括全年创建的涵盖每个主题概述的所有媒体文章的链接,每个文章都有到完整研讨会和问题单的进一步链接。

我们希望这能够在您未来的数据科学之旅中为您提供帮助!

Python 简介

对于任何数据科学初学者来说,您需要能够回答的第一个问题是您将选择哪种语言?虽然有一些选项,包括 Python 和 R,但我们从 Python 开始,因为它的适用性和可用性超越了数据科学,并且有广泛的库可用于支持任何数据科学工作流。在此过程中,我们通过向您介绍 Python 基础、序列、逻辑和面向对象编程等概念,涵盖了任何人都需要能够在 Python 中继续从事数据科学及其他职业的主要基础知识。这为在后面的研讨会中理解代码的作用以及如何找到解决您可能遇到的编码挑战的方法打下了基础。

Python 基础知识

任何学* Python 的人的第一个任务是设置您的环境,然后学* Python 代码代表什么。在本次研讨会中,我们将介绍如何通过 Anaconda 设置您的编程环境,讨论什么是 Jupyter 笔记本,并涵盖 Python 中变量、数据类型和基本操作的基础知识。这将帮助您理解如何阅读 Python 代码,以及如何开始与您自己的代码进行交互。

Python 序列

Python 有各种内置序列,可用于存储多个数据点,而不是在您的环境中创建许多变量。尽早了解这些序列中的每一个能做什么或不能做什么变得很重要,这样您就知道将来如何存储您的数据。至此,我们涵盖了 Python 中列表、元组、集合和字典的主要功能,它们是您在数据科学之旅中将会遇到的主要序列/数据结构。

Python 逻辑

当涉及到构建更高级的程序时,理解 Python 中的逻辑是如何工作的就成为了关键。这包括创建在满足给定条件时运行的代码,或者在不满足给定条件时运行替代代码,在 Python 中执行重复操作,以及定义可在代码中重复使用的代码片段。为此,我们在本次研讨会中介绍了条件语句、逻辑语句(if、else 和 elif)、循环(for 和 while)和函数,您可以看到它们如何单独工作,也可以一起工作来编写更复杂的代码。

Python 面向对象编程

虽然在 Jupyter 笔记本中工作时,大多数数据科学工作流倾向于使用过程化编程,但了解面向对象编程的优势和用例是非常有用的。这是一种编程范例,它构建代码,以便将数据的特征和行为捆绑到一个结构中,并且通常形成您在编程过程中会遇到的大多数库的基础。这意味着,理解这种形式的代码是如何构造的,对于能够与您在数据科学之旅中将接触到的许多库进行交互非常重要。

数据科学家的工具包

一旦你掌握了 Python 的基础知识,你就可以创建基本的程序了,那么学*一些你每天都会用到的工具就变得很重要了。这些工具是其他人已经创建的库和软件,因此您不必重新发明轮子,这将使您的代码更容易阅读和理解。作为数据科学家,您将经常遇到的三个主要库包括 Numpy、Pandas 和 Matplotlib,在您的数据科学之旅中,您也将经常使用 GitHub 和 SQL 工具。

Numpy

Numpy 非常注重数学功能,是一个基础库,支持其他 Python 包中的许多方法和函数。这意味着它是一个基础包,理解起来非常有用。在这方面,我们涵盖了 Numpy 数组的基础知识、软件包中的数学运算以及如何与随机数功能交互。

熊猫

你经常会遇到的第二个基本包装是熊猫。这是一个广泛用于数据科学和分析任务的包,构建在 Numpy 包之上。它是数据科学工作流中最受欢迎的数据争论包之一,并与数据科学工作流中使用的许多其他库(如 SciKit Learn)集成良好。在本次研讨会中,我们将介绍如何创建熊猫系列和熊猫数据框架,如何从该数据结构中访问信息,以及当数据在该结构中时我们可以执行哪些操作。

Matplotlib

对于任何数据科学家来说,能够将数据可视化是一项关键技能,能够将您的结果和发现传达给技术和非技术受众。虽然在 Python 中有许多不同的包可以使用,但您遇到的最主要的包之一是 Matplotlib,这是一个很好的开始。在本次研讨会中,我们将介绍如何构建一个基本图,在同一图表上绘制多组信息,然后跨多个轴绘制信息。

Git 和 Github

除了 Python 中构成数据科学家工具包一部分的库之外,还有许多其他软件和工具在数据科学工作流中非常有用。任何数据科学家都应该熟悉的主要工具之一应该是作为版本控制手段的 Git 和 GitHub。这确保您以受控的方式对结果进行版本控制,而不是将其命名为 draft1、draft2、draft3 等。然后它可以连接到 GitHub,这样你就可以把这些版本存储在除了你的桌面之外的地方,并且允许你和你的团队从世界上任何地方访问这些版本,只要他们有互联网连接。在本次研讨会中,我们将介绍创建本地 Git 存储库、提交对该存储库的更改,然后将其链接到 GitHub 的基础知识。

SQL

我们的数据科学家工具包中的最后一个工具是 SQL。SQL 代表结构化查询语言,是在关系数据库管理系统中处理数据库时使用最广泛的编程语言之一。它用于执行各种不同的操作,例如选择数据、查询数据、分组数据以及从 Python 环境之外的数据中提取汇总度量。这样做的好处是可以处理大量的数据,尤其是当这些数据保存在一个集中管理的系统上时。在本次研讨会中,我们将介绍如何在您自己的机器上设置一个 SQL 实例,然后使用它来操作数据集,包括选择数据、搜索条件、汇总统计数据和分组数据。

使用 Python 的数据科学

一旦您知道并理解如何在数据科学工作流中使用 Python 及其密钥库以及其他关键软件,您就可以继续学*和理解可以使用的每种机器学*算法。为此,机器学*任务之间有两个主要区别,这些任务可以分成四个整体的机器学*组。

第一次分裂是在有监督和无监督的机器学*任务之间。其中第一个意味着我们有一个明确的目标,我们希望朝着这个目标努力,例如检测糖尿病或癌症,模拟房价或模拟 NBA 球员的位置。这通常使用回归或分类机器学*方法来完成,目的是尽可能接*定义的目标。

第二个意思是我们没有一个明确的目标,但我们仍然想要一个结果,比如根据购物*惯对消费者进行分组,或者在一组结果中识别行为。这些任务通常是通过聚类或降维方法来完成的,这些方法旨在识别相似数据点的组或减少维度的数量,以可视化数据或输入到另一个机器学*算法中。

回归

第一组是机器学*中的回归。这是一种对两个或更多数量(如房价和房屋特征或建筑特征的能效)之间的依赖性或关系进行建模的方法。这种方法的目的是找到这些关系的强度和方向,以便根据现有数据对未知结果进行建模,或者理解两个变量之间的关系。

线性回归

在这种情况下,您遇到的第一种方法是线性回归法,它以线性方式模拟变量之间的关系。这种方法的目的是使用最小二乘法缩小实际值和预测值之间的距离,并允许您提取显示它们与目标变量之间关系的强度和方向的参数。在本次研讨会中,我们将介绍如何通过 scikit learn 实现基本回归,以及由谁来实现和解释多元线性回归方程。

逻辑回归

回归的第二个常用方法是逻辑回归。线性回归通常应用于连续变量预测(在给定范围内可以取无限个值的预测),而逻辑回归可以应用于预测分类输出(在给定范围内包含有限个数的点或类别的预测)。这种方法的主要目的是预测一个数据点属于哪个类别或观察值,而不是一个确切的值,例如在患者是否患有糖尿病的情况下?因此,这属于回归范畴,但也是一种使用回归的分类方法。在研讨会中,我们将介绍如何实施和评估一个基本的逻辑回归来模拟糖尿病的发病率。

高级回归

除了线性回归和逻辑回归之外,还有各种其他回归方法,理解这些方法通常很有用。这可以包括 Lasso 和 Ridge Regression,它们建立在基本线性回归方法的基础上,通过引入正则化来尝试和避免过度拟合的问题,或者决策树和随机森林的机器学*方法,它们能够对变量之间的非线性关系进行建模。这些方法既有优点也有缺点,因为这些方法通常能够更好地模拟变量之间的关系,但这可能以增加所需的计算资源或降低可解释性为代价。

分类

回归之后,另一个常见的监督机器学*任务是分类。这样做的目的不是对特定值建模,而是基于我们已知的目标数据集对数据点属于哪个组或类进行建模。这可以包括建模患者是否患有糖尿病,患者是否患有癌症,用户是否会重新订阅,或者基于他们的统计数据对 NBA 球员的位置进行建模。有许多方法属于这一范畴,其中许多方法也可用于分类,但三种常见的方法包括:决策树分类、随机森林分类和支持向量机分类。

决策树分类

决策树遵循一种类似树的结构(因此得名),这种结构类似于你可能在小学或高中时制作的一些决策树。该方法能够通过使用决策来执行分类,以达到关于数据点属于哪个结果的预测。具体来说,它的工作原理是根据不同的属性分割数据集,同时尝试减少给定的选择标准。在本次研讨会中,我们将介绍如何实现一个基本的决策树,如何可视化这是如何执行的,以及如何评估模型的性能。

随机森林分类

随机森林分类器是一种利用决策树分类器算法的集成方法,但不是创建单个决策树,而是创建多个决策树。这样做时,它利用了数据和要素的随机采样来确保模型不会过度拟合,从而产生更好的预测。这遵循了群体的表现优于个人表现的逻辑。如果你能够实现一个决策树,通常最好是实现这个方法,尽管这样会增加计算资源。本课程包括随机森林的基本实现以及如何评估结果。

https://python.plainenglish.io/a-practical-introduction-to-random-forest-classifiers-from-scikit-learn-536e305d8d87

支持向量机分类

除了决策树和随机森林,虽然有许多其他分类方法可以使用,但支持向量机是经常遇到的一种方法。它通过试图在数据中找到一个界限来区分我们试图定义的两个或更多不同的类别,从而实现分类。在这样做时,该模型可用于预测,通过找到点可能位于边界的哪一侧,从而找到该点可能属于哪个组。该算法的有用之处在于,边界可以采取多种不同的形式,无论是线性的、非线性的还是由用户定义的。在本次研讨会中,我们将介绍模型的基本实现以及如何可视化结果。

使聚集

聚类是机器学*的无监督分支的一部分,这意味着我们没有像回归或分类任务那样的明确目标。这些算法的目标是能够识别具有相似特征的不同对象组,例如购物者、电影或商店。这使得决策者能够关注这些群体,看看他们如何能够更好地为他们服务,例如留住客户或激励他们花更多的钱。为此,两种常见的聚类算法包括 k-means 聚类和层次聚类。

K-均值聚类

K-means 聚类是最常用的聚类算法之一。它首先定义要创建的组的目标数量,然后算法根据点和组之间的不同距离度量来定义目标数量。在本次研讨会中,我们将介绍如何实现 Kmeans 聚类算法,如何选择最佳的聚类数,以及如何评估结果。

层次聚类

分层聚类的工作原理是根据用于分隔不同组的距离度量在层次结构中创建这些组。这种算法的独特之处在于,您可以根据选择的距离来确定不同组如何相互适应或相互分离的层次结构。这意味着我们在执行算法之前不需要知道聚类的数量,尽管这可能会增加时间复杂度。在本次研讨会中,我们将讨论如何实施和评估该算法。

降维

最后,我们有降维,这也是(在大多数情况下)无监督机器学*算法的标题下。这种方法的主要目的是减少数据集中的要素数量,从而减少模型所需的资源,或者在执行任何分析之前帮助可视化数据。这是通过减少数据集中属性或变量的数量,同时尽可能多地保留原始数据集中的变化来实现的。这是一个预处理步骤,意味着它通常在我们创建或训练任何模型之前执行。在流形学*的线性代数中有两种主要的形式,在工作坊中我们从前者引入主成分分析,从后者引入 t 分布随机邻居嵌入。

结论

本课程旨在为个人提供一个数据科学的起点,介绍每一种库、软件和技术,其中涵盖了实现的所有基础知识以及它们是如何工作的,而不涉及太多的细节。这将为任何新的数据科学家提供一个平台,让他们可以探索他们更感兴趣的主题,无论是更多的回归、分类、聚类和维度方法,还是对我们已经介绍的每个模型进行更深入的研究。我们祝您在数据科学之旅中好运!

如果您想从我们的社交网站获得更多信息,请随时关注我们的社交网站:

https://www.facebook.com/ucldata

insta gram:【https://www.instagram.com/ucl.datasci/

领英:【https://www.linkedin.com/company/ucldata/

如果你想了解 UCL 数据科学协会和其他优秀作者的最新信息,请使用我下面的推荐代码注册 medium。

https://philip-wilkinson.medium.com/membership

检测和处理异常值的完整指南

原文:https://towardsdatascience.com/a-complete-guide-for-detecting-and-dealing-with-outliers-bad26b1e92b6

威尔·梅尔斯在 Unsplash 上拍照

6 种检测异常值的方法和 4 种处理异常值的方法

离群值可能是数据分析或机器学*中的一个大问题。只有少数异常值可以完全改变机器学*算法的性能或完全破坏可视化。因此,检测异常值并小心处理它们是很重要的。

检测异常值

检测异常值一点也不困难。您可以使用以下方法检测异常值:

  1. 箱线图
  2. 柱状图
  3. 平均值和标准偏差
  4. IQR(四分位数间距)
  5. z 分数
  6. 百分位

在我深入异常值的检测之前,我想介绍一下我将在今天的教程中使用的数据。我们将使用可以从 seaborn 库加载的“tips”数据集:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as snsdf = sns.load_dataset("tips")
df

作者图片

我们将主要关注总账单列。

异常值的检测

有很多不同的方法来检测异常值。有些是非常简单的可视化,它只告诉你数据中是否有异常值。有些是非常具体的计算,告诉你离群值的确切数据。

箱线图

默认情况下,箱线图显示异常值。这是 total_bill 部分的箱线图:

plt.boxplot(df['total_bill'])
plt.title("Boxplot of Total Bill")

作者图片

上端的一些点离得有点远。你可以认为他们是离群值。它没有给出异常值的确切点,但它表明在这一列数据中有异常值。

直方图

查看分布还可以告诉您数据中是否有异常值:

plt.hist(df['total_bill'])
plt.title("Distribution of Total Bill")
plt.show()

作者图片

该分布还显示数据是偏斜的,在右侧有异常值。

从这一点,我们将执行一些特定的计算,找出确切的点是离群值。

均值和标准差法

在这种方法中,我们将使用平均值、标准差和指定因子来找出异常值。

首先,我将把“总账单”列保存为数据:

data = df.total_bill

这里我们将使用系数 3。高于平均值的三个标准差和低于平均值的三个标准差将被视为异常值。首先,获取数据的平均值和标准差:

mean = np.mean(data)
std = np.std(data)

现在,找出高于平均值三个标准差的数据:

outlier_upper = [i for i in data if i > mean+3*std]
outlier_upper

输出:

[48.27, 48.17, 50.81, 48.33]

这里我们找出低于平均值三个标准差的数据:

outlier_lower = [i for i in data if i < mean-3*std]
outlier_lower

输出:

[]

如你所见,我们在上端有一些异常值,但在下端,在这个方法中没有异常值。

这里我用了 3 std。但是如果你愿意,你可以使用任何其他数字的因子。通常使用系数 2、3 或 4。请随意使用 2 或 4 并检查异常值。

四分位间距

在这种方法中,我们需要计算第一个四分位数和第三个四分位数,以获得四分位数间距(IQR)。然后我们将第一个分位数减去 1.5 倍 IQR 作为下限,第三个四分位数加上 1.5 倍 IQR 作为数据的上限。

data1 = sorted(data)
q1 = np.percentile(data1, 25)
q3 = np.percentile(data1, 75)
IQR = q3-q1
lower = q1-(1.5*IQR)
upper = q3 + (1.5*IQR)

如果一个值低于下限而高于上限,它将被视为异常值。

outliers = [i for i in data1 if i > upper or i < lower]

输出:

[40.55, 41.19, 43.11, 44.3, 45.35, 48.17, 48.27, 48.33, 50.81]

这些是该方法中的异常值。

Z 分数

只需固定一个 z 得分阈值,如果 z 得分大于该阈值,则数据为异常值。

thres = 2.5
mean = np.mean(data)
std = np.std(data)outliers = [i for i in data if (i-mean)/std > thres]
outliers

输出:

[48.27, 44.3, 48.17, 50.81, 45.35, 43.11, 48.33]

百分位数计算

你可以简单地确定上限和下限的百分比。在本例中,我们将下限视为第 10 个百分位数,上限视为第 90 个百分位数。

fifth_perc = np.percentile(data, 5)
nintyfifth_perc = np.percentile(data, 95)outliers = [i for i in data1 if i > nintyfifth_perc or i < fifth_perc]
outliers

输出:

​[3.07,
 5.75,
 7.25,
 7.25,
 7.51,
 7.56,
 7.74,
 8.35,
 8.51,
 8.52,
 8.58,
 8.77,
 9.55,
 38.07,
 38.73,
 39.42,
 40.17,
 40.55,
 41.19,
 43.11,
 44.3,
 45.35,
 48.17,
 48.27,
 48.33,
 50.81]

这些是异常值。

这些都是我今天想分享的检测异常值的方法。现在让我们看看如何处理异常值:

处理异常值

移除异常值

这是一种常见的方式。有时候,从数据中删除异常值是很容易的。

在这里,我将删除从最后一个百分点计算中检测到的异常值:

no_outliers = [i for i in data if i not in outliers]

让我们用无异常值数据做一个箱线图:

作者图片

你可以看到离群值消失了。

基于百分位数的地板和封顶

在最后一种异常值检测方法中,计算第五和第九十五百分位来找出异常值。您也可以使用这些百分点来处理异常值。

低于第五百分位的数据可以用第五百分位代替,高于第九十五百分位的数据可以用第九十五百分位值代替。

data_fixed = np.where(data < tenth_perc, tenth_perc, data)
data_fixed = np.where(data_fixed > nineteeth_perc, nineteeth_perc, data_fixed)

让我们用新的数据再次查看箱线图

plt.figure(figsize = (6, 8))
plt.boxplot(data_fixed)

作者图片

不再有离群值。

扔掉

宁滨的数据和分类将完全避免离群值。相反,它将使数据分类。

df['total_bill'] = pd.cut(df['total_bill'], bins = [0, 10, 20, 30, 40, 55], labels = ['Very Low', 'Low', 'Average', 'High', 'Very High'])
df['total_bill']

输出:

0          Low
1          Low
2      Average
3      Average
4      Average
        ...   
239    Average
240    Average
241    Average
242        Low
243        Low
Name: total_bill, Length: 244, dtype: category
Categories (5, object): ['Very Low' < 'Low' < 'Average' < 'High' < 'Very High']

total_bill 列不再是连续变量。现在它是一个分类变量。

考虑空值

处理异常值的另一种方法是将它们视为空值,并使用填充空值的技术来填充它们。在这里,您可以找到处理空值的提示:

</6-tips-for-dealing-with-null-values-e16d1d1a1b33>

结论

本文将重点介绍检测异常值的方法和处理异常值的技巧。我希望这有所帮助。如果你发现任何其他更有用的方法,请在评论区分享。

更多阅读

https://pub.towardsai.net/data-analysis-91a38207c92b </20-very-commonly-used-functions-of-pyspark-rdd-90b8271c25b2>

成为 Tableau 桌面专家认证的完整指南

原文:https://towardsdatascience.com/a-complete-guide-to-becoming-a-tableau-desktop-specialist-certified-56f1e39777d1

第 1/4 部分:连接和准备数据

Tableau 桌面专家考试指南的第 1 部分(图片由作者提供)

考 Tableau 桌面专员认证值得吗?

你最*有没有搜索过数据分析相关领域的工作岗位?如果你这样做了,你会意识到 Tableau 是市场上领先的 BI 工具。与以往任何时候相比,公司正在从 Excel 中的静态表格和爆炸式饼图转向更强大的报告和仪表板。不要误解我,我非常欣赏 Excel 及其功能,但 Tableau 让您的分析和报告更上一层楼。作为 Tableau 桌面专家认证,你可以向世界展示你是这份工作的最佳人选。甚至有一个目录,你可以搜索赚钱的人,看到每一个被认证的人。如果你点击这里,进入“搜索目录”框,搜索古斯塔沃·英夫,点击“收入者”,你会在那里找到我。

认证徽章(由画面

考试费用是 100 美元。这项投资非常便宜,当你考虑到 Tableau 是世界上领先的 BI 工具之一,并且你用它获得的技能对任何进行数据分析的公司都有吸引力时,你就会知道 Tableau 桌面专家认证是完全值得的。

话虽如此,我们开始工作吧!

在这篇文章中,我们将讨论组成考试准备的四个领域中的第一个。请记住,这些域是由 Tableau 自己建立的。在下面找到每个领域的链接,它们在考试中的份额,以及安排考试的链接。

  • 域 1:连接到&准备数据(25%)
  • 领域 2:探索和分析数据(35%)[进行中]
  • 领域 3:分享见解(25%)[进行中]
  • 领域 4:理解 Tableau 概念(15%)[进行中]
  • 安排考试

在第一部分中,我们将逐步详细介绍以下概念:

  1. 创建与数据源的实时连接
  2. 创建和管理数据模型
  3. 管理数据属性

1.创建和保存数据连接

创建到数据源的实时连接

连接到数据集后,在“数据源”选项卡中,可以选择实时连接或数据提取。

创建与数据源的实时连接(图片由作者提供)

解释使用实时连接和提取之间的区别

一个实时连接是 Tableau 的一个强大功能,它允许你实时更新数据。使用实时连接的缺点是大量数据的低性能。如果您正在工作的场景不要求实时更新,您可以避免实时连接并提高速度。向数据源添加筛选器可以最大限度地减少大型数据集的问题。

一种提取物提供优化的性能。它是加载到内存中的数据的快照。它允许您为大量数据构建和处理复杂的可视化和计算。你可以处理几千兆字节,甚至不会注意到任何滞后。提取的缺点是需要刷新数据源来更新数据。这可能需要一些时间。

创建摘录

当选择通过提取连接到数据时,将要求您在本地保存 Tableau 数据提取文件。您可以向提取中添加过滤器以提高性能。点击“编辑”,“添加”,选择你想过滤的字段,然后点击“确定”。

创建摘录并应用过滤器以提高性能(视频由作者提供)。

将元数据属性保存在. TDS 中

元数据是一组数据,用于描述关于另一组数据的信息。在. tds 文件中保存元数据属性意味着保存数据集的结构,包括:

  • 默认聚合
  • 计算字段
  • 自定义集
  • 数据仓

保存元数据导出(按作者分类的图像)

节约。当您处理遵循相同结构的几个数据集时,tds 文件非常有用。例如,如果您为不同公司的项目工作,并且您经常从 Yahoo Finances 中提取他们的财务报表,则数据字段将总是非常相似。您可以构建用于所有这些不同公司的计算字段、默认聚合和集合。您可能需要在某些地方进行调整,以适应特定的独特性,但是大部分工作都可以重用。

通过转到工作表上的数据面板,右键单击感兴趣的数据集,然后单击“添加到保存的数据源”,可以保存元数据属性。现在只要选择目的地。

创建使用多个连接的数据源

您可以轻松地将多个连接组合成一个数据源,以便在项目中使用不同的数据集。仍然在“数据源”选项卡中,单击标题“连接”旁边的“添加”,选择连接类型,然后选择数据。

创建具有多个连接的数据源(图片由作者提供)。

2.创建和管理数据模型

向数据源添加关系

当您想要合并多个数据源而不需要创建连接时,关系非常方便。当您处理已发布的数据源时(它不允许连接),或者如果您只需要项目特定部分的连接时,就会出现这种情况。毕竟,关系基本上是两个数据源之间的左连接(稍后将详细介绍连接)。利用关系的坏处是它会限制你的能力。例如,它不允许您使用辅助数据源中的组或 LOD 表达式。你可以在这里找到更多关于关系的局限性的细节。

要创建这些关系,首先,您需要连接到至少两个具有相似字段作为键的数据源。在工作表中,转到“数据”,“编辑混合关系”,然后选择用于合并这些数据源的字段。辅助数据源中的字段旁边会出现红色的小键。您可以选中或取消选中它们,将它们用作不同工作表中的键。当您有不同粒度级别的视觉效果,并且每个视觉效果的关系都不同时,这非常有用。您只需选中或取消选中每个表单中的必要字段。在下面的视频里看到。

如何融合数据源(视频由作者提供)。

添加联接和联合

联接和联合在概念上非常类似于关系,但是您是在“数据源”选项卡中完成的,并且您将使用联接的结果创建一个新的数据源。这意味着你不会有任何限制,因为你有关系。

连接通过关键字段连接两个数据集。输出是每个数据集中所有其他列的关键字段。 Unions 连接具有相同列的两个数据集,并通过这些列简单地将一个数据集堆叠在另一个数据集之上。看看下面的视频,看看奇迹是如何发生的。我们将把原始数据源与一个包含两个工作表的新连接进行合并。第一个表将连接原始数据,第二个表将合并第一个表。

在数据源中应用连接和联合(视频由作者提供)。

3.管理数据属性

重命名数据字段

重命名数据字段非常简单。在任何工作表中,右键单击要重命名的字段,然后单击“重命名”。

现在,重命名字段和编辑别名是有区别的。这时,我需要向您简要解释一下什么是措施。Tableau 在数据窗格中自动创建两个字段:度量名称和度量值。它们允许你用多种方法构建视觉效果。

测量值包含数据的所有测量(绿卡),而测量名称包含所有这些测量的名称。请参见下面的示例:

使用度量名称和值允许您显示多个度量(按作者排序的图像)

上面的例子显示了我们如何使用这些卡片来显示多个度量。我们将在本系列的域 4 中更详细地讨论度量。现在来说说“编辑别名”。

为数据值分配别名

别名是度量名称中度量的“名称”。因此您可以更改度量值的显示方式,而不是更改字段的名称。在可视化中右键单击测量名称,选择“编辑别名…”。请参见下面的示例:

为度量分配别名(按作者排序的图像)。

看看下面会是什么样子,注意字段的名称还是一样的。您只是在可视化中为该度量创建一个别名。

编辑度量的别名(按作者排序的图像)。

为数据字段分配地理角色

这就是你如何告诉 Tableau 一个字段有一个地理角色(它是一个国家,地区,城市,邮政编码等。).要让它工作,您可以单击数据窗格中字段名称左侧的图标,并选择“地理角色”选项之一。当你这样做时,Tableau 将能够使用该字段构建地图。请参见下图:

为字段分配地理角色(按作者排列的图片)。

更改数据字段的数据类型(数字、日期、字符串、布尔值等)。)

基本上有两种方法可以改变字段的数据类型。您只需单击“数据”窗格中字段名称左侧的图标,然后选择适当的数据类型,或者您可以创建一个新的计算字段来更改其中的数据类型。单击数据窗格右上角的菜单并选择“新计算字段”,然后查看下图,并注意新字段的图标显示为“ABC”,而原来的[Sales]显示为“#”(数字):

创建新的计算字段以更改字段的数据类型(作者图片)。

更改数据字段的默认属性

这是应该让更多分析师知道的特性之一,因为它非常有用,而且节省时间。数据中的每个字段都将以默认属性被接收到 Tableau 中,包括以下内容:

尺寸

  • 颜色
  • 形状
  • 分类

措施

  • 颜色
  • 数字格式
  • 聚合
  • 总使用量

当您将字段拖动到可视区域时,将使用默认属性。例如,要避免更改颜色或数字格式,只需更改默认属性即可。您可以通过右键单击数据窗格中的字段,转到默认属性,然后选择要进行的更改来实现这一点。

更改 Tableau 中字段的默认属性(图片由作者提供)。

这就是第一个域(连接和准备数据)。如果你有任何问题,请在下面的评论中提出。此外,在参加考试并成为 Tableau 桌面专家之前,一定要做好所有 4 个领域的准备。你将准备好钉它!

干杯!

因果推理完全指南

原文:https://towardsdatascience.com/a-complete-guide-to-causal-inference-8d5aaca68a47

你一直忽略的问题汇编,以及如何正确处理

达维德·利伯拉德斯基在 Unsplash 上拍摄的照片

我们为什么需要因果推理?

将测量转化为行动是任何智能系统的核心。数据科学的核心是使用大量的定量测量来采取行动。在私人技术领域,这些测量是用户的活动,我们可以采取的行动是商业行动。我们的业务活动范围广泛,从一般战略方法的内部决策,到直接影响客户的决策,如新产品或改进产品、功能、建议或定价。

数据科学的艺术和科学在于如何正确使用我们的测量来推断正确的行动。如果你曾经这样做过,你就会看到我们必须采取的信念的飞跃。它通常看起来像这样,“嗯,我们知道这一组客户有这个结果,所以让我们把更多的客户放在类似的情况下,以实现类似的结果。”这种信念的飞跃何时实现?数据科学家推荐的行动何时会真正产生预期的结果?

为了对信念的飞跃充满信心,我们必须承认,我们将数据分析转化为商业建议的旧方法有点幼稚。我们听说相关性不是因果关系,但我们实际上忽略了这一区别。充其量,我们承认我们不确定,多挖一点,或者将我们的结果与直觉进行比较,以建立对我们结论的信心。但是这种得出结论的无组织的狂野西部容易受到偏见的影响,尤其是如果我们不确切知道我们在做什么的时候。

什么是因果推断?

幸运的是,人们一直在研究这个基本问题:什么是因果关系,我们如何知道 A 何时引起 B?

这个领域的关键人物之一,Judea Pearl 经常重申理解因果关系是做出好决策的关键。他开发了一个 3 级系统来理解简单观察之间的关系,并真正理解因果关系:

第一层,关联,是关于统计可以衡量的所有关联。这第一个层次包括条件概率、相关性,甚至所有的机器学*,包括神经网络,允许我们从其他数据中“预测”一些数据。

历史上,每个领域的人都使用这些模型来做决策。据说,在第一周完成入职的客户“更有可能”避免第一年的流失,当时衡量的只是相关性或条件概率,而不是因果关系。大多数人还在这样做。

朱迪亚·珀尔的第二层,干预,掌握着我们想要回答的问题。如果我们采取 X 商业行动,那么会发生什么?如果我们给顾客打折,他们会呆多久?如果我们增加这项新功能,客户 LTV 会有多大变化?

如果我们知道所有的 2 级答案,我们就可以做出所有的最优决策(在我们认为要考虑的选择范围内!)

最后,第三级问题是对一个系统的因果关系的全面理解。即使我知道 LTV 会发生多大的变化,我能理解为什么吗?等级 2 是关于任何原因的结果,等级 3 是关于任何结果原因。如果我们能看到发生的任何事情,并完全理解它发生的每一个原因,那么我们就能很容易地回答任何第二级问题,而不仅仅是我们试图估计的答案。

事实上,回答 2 级问题通常是我们在技术领域进行因果推理的直接目标。那么应该怎么做呢?

因果推理的一般方法

每个项目都暗中*似一个因果推理问题。数据科学家所做的一切都是某种形式的决定,即应该对系统进行什么样的改变,以获得用户/客户的最佳结果。

在第 1 级方法中,我们通过一些量化来查看行动和结果指标之间的关联,而不考虑其他因素。有人称之为粗略估计,或方向正确。当然,正如幽灵般的辛普森悖论向我们展示的那样,这可能是完全错误的。

因果推断的关键事实是:对于系统中可能影响测量结果(直接或间接)的任何协变量,您必须确保您的治疗+对照组具有相同数量的这些协变量。

例如,如果你想知道一种药物对一种疾病的效果,你当然需要一组服用该药物的人,而另一组不服用。无论这是通过随机试验还是仅仅观察人群的当前行为,当你比较这两组人时,你需要他们在各方面尽可能相同,除了他们是否服用药物这一事实。

在随机试验中,你会给另一组服用安慰剂,这样两组人都同样地认为他们服用了药物,因为他们的信念会影响他们的结果(无论是直接还是通过他们的行为)。无论是否是随机试验,你都必须确保两组都有相同的性别比例(如果性别以某种方式影响可测量的结果)、遗传倾向等等。

正如朱迪亚·珀尔提醒我们的那样,为了正确地做到这一点,我们真的需要一个我们整个系统的模型。我们想知道影响谁接受治疗的所有因素,结果是什么,以及所有相关的事情。该模型的结构是 DAG(有向无环图),它可以向我们显示相关的变量,以及与它们相关的边的方向。(例如,咖啡机的压力会影响气压计,但气压计不会影响咖啡机的压力)。

我们至少要注意哪些变量是混杂变量(直接或通过其他关系导致治疗和结果)、介体变量(治疗导致结果的途径)以及由治疗或结果导致的变量。

我们需要这个信息,因为我们 确实想要控制 的直接和间接混杂因素,因为不控制它自然会使我们的结果产生偏差,就像在辛普森悖论中一样。然而我们 不想控制 作为中介或效果,因为那样控制会使我们的结果产生偏差。

这些在 Dag 中正确控制变量的规则称为 d-分离,当数据生成过程的真实因果模型包含相互影响的变量(双向箭头)、本身是原因但也充当其他原因的中介的变量以及其他复杂结构时,就会产生问题。

例如,一个帐户拥有的联系人数量可能会直接影响留存率,因为没有联系人的帐户在社交平台上提供的价值更低(例如,新闻订阅源中有趣的帖子更少,可以获得的关于受众的信息更少)。此外,联系人的数量也可能影响用户在平台上采取行动的数量,因为每个联系人都给他们采取行动的机会,而平台上的行动数量也可能直接影响留存率(因为积极使用平台的用户不太可能流失)。

因此,接触的数量可能直接影响保持,也可能通过每种行为的数量的中介影响保持,因为这些行为直接影响保持。那么,我们应该把这两个变量作为混杂变量来处理吗?如果我们这样做,我们将测量什么?通常,我们必须根据我们对哪些因果路径更重要的猜测来做出选择,并且我们必须记住,我们的测量是对一组特定的因果路径的测量,这些路径可能比我们想要测量的路径更多,或者比我们想要测量的路径更少(如果我们想要增加更多成员的效果,我们需要允许它影响所有路径,因此我们不应该控制动作的数量)。

确定 DAG 的另一个棘手的方面是,具有不同因果关系 的个体的任何 子集都算作一个因果变量。例如,如果性别导致某些身体特征改变了不同治疗/结果的可能性(如乳腺癌),那么它就被视为该治疗/结果的原因。同样,如果性别导致了一些影响其在治疗/结果组中出现频率的社会心理环境(例如,女性比男性更多地注册了女性杂志),那么它就是该治疗/结果的原因。

此外,我们可以用代理变量来表示因果系统中的任何变量。有争议的是,我们总是使用代理变量。我们不知道 用户的年龄或性别,但是我们会问他们,用他们的回答来代表真相。同样,我们不知道他们对产品的感受和动机,但我们可以用他们的调查答案来代表事实。我们可以用他们的参与度来代表他们与产品的关系。这样的例子不胜枚举。在因果 DAG 中,使用变量的直接影响作为它的噪声测量总是没问题的。你可以想象一个箭头从每个变量指向它的可测量的代理,但是没有必要把我们的图片复杂化——我们总是使用代理测量。

推断因果关系的框架

一旦我们建立了系统中可测量变量之间非零因果关系的试验性模型,并确定了作为混杂变量的试验性变量集,我们就可以进行因果推理了!

我提出了这个因果推理过程的框架。

包括 4 个步骤:

  1. 随机实验
  2. 观察修剪
  3. 协变量建模
  4. 成果衡量

随机实验是 A/B 检验(或随机对照试验),我们知道它在估计治疗变量的因果效应时非常有用。删减是指删除我们不想包含在测量中的数据的过程。建模就是描述每个协变量对结果的影响。最后,测量是关于观察治疗组和对照组之间的差异,并确定其范围、可靠性和显著性。

前三步中的每一步都可以跳过。我们可以进行因果推断,不需要随机分配,不需要删除最差的数据,也不需要对协变量的影响进行建模。然而,我们几乎肯定应该至少完成这三个步骤中的一个。理想情况下,利用所有的步骤将会得到最好的结果。

因果推断通常指的是准实验,这是一种在没有步骤 1 的随机分配的情况下推断因果关系的艺术,因为 A/B 测试的研究包括使用步骤 1 的项目。但是我在这里要强调的是,这个框架适用于所有有或没有 A/B 测试的因果推理项目。

(是的,即使做 A/B 测试也要考虑剪枝建模!我们将深入了解原因和方法。)

engin akyurtUnsplash 上拍摄的照片

第一步:随机实验(A/B 测试)

A/B 测试并不完美

回到随机对照试验(RCTs)的话题,或者在技术领域被称为 A/B 测试,我们可以开始理解为什么它们在因果关系领域几乎是神奇的。通过随机试验,我们可以确保治疗组和对照组不会因为任何可能影响结果的协变量而以有偏见的方式分配。

平均而言,治疗组和对照组具有相同数量的每种性别、每种素质..每一个混杂因素。我们没有错误地控制任何影响,因为治疗和控制分配发生在治疗发生之前,所以我们不将其影响包括在治疗分配中。**

但是,随机对照试验并不完美。平均而言,治疗组和对照组的每个混杂因素 数量相等,这意味着什么? 这真是 RCTs 的肮脏秘密。这意味着,如果你重复这个实验无数次,每组中混杂因素的平均数将是相等的。但是它 并不意味着在你的实验中混杂因素是相等的。平均而言, 相等,这种方式使得它成为一个无偏的度量。****

但是,成为一个公正的衡量标准并不意味着你就是一个最优的衡量标准。

封堵更好

如果性别影响我们的结果,并且你随机分配的治疗组比你的对照组有更大比例的女性,那会影响你测量的结果吗?当然是了!您测量的治疗组和对照组的结果差异部分是由于这两个组的女性比例不同!因此,实验中治疗的因果效应会被实验的不平衡协变量(如性别)所扭曲。如果你重复这个实验很多次,你测量的平均因果效应将确实是正确的(不像如果你没有 RCT,平均值将是不正确的),但是你的实验的测量是倾斜的。

因此,尽管 RCT 给了我们一个无偏的度量,在某种意义上说,许多假设的实验都趋向于真实,但我们在实验中有一个可修复的误差源,即我们的协变量不平衡。一种方法是将其固定为分层/分组随机试验。这里,我们确保治疗组和对照组有相同数量的协变量。我们不是把所有人平均分配到每个治疗组,而是把所有女性平均分配,然后把所有男性平均分配,等等。

那么两组之间有相等的协变量!由于要考虑许多混杂的变量,我们需要从变量的组合中进行分层。如果我们关注性别和他们的年龄是否超过 40 岁,那么我们有 40 岁以上的男性和 40 岁以下的男性,40 岁以上的女性和 40 岁以下的女性,等等。这些层中的每一层必须在每个治疗组之间平均分配。

从统计学的角度来说,这会产生什么?治疗变量的偶然效应的测量已经是无偏的,但现在它将更加准确:它将有一个更小的方差。也就是说,如果我们多次做这个实验,我们的治疗效果的测量将仍然是无偏的,并且那些测量将具有比简单 RCT 更低的方差。

如果可以的话,做一个封锁的 RCT 吧!

Bin 连续变量启用阻塞

这是我们第一次提到每列数据的类型。想一想你的 DAG 中的节点,你已经选择将它们包含在你的因果推断中,你知道这些变量会影响结果。每一个都可以是连续的、有序的或分类的。有序变量和分类变量在分层方面可以被同样对待,但是连续变量呢?

如果我们想对我们的试验进行分组随机化,我们需要将连续变量绑定到范围中,这样我们就可以将每个范围转化为在治疗组之间平均分配的分组。

宁滨一个连续的范围可以用许多方法来完成。这里我们选择一个混杂变量的表示,所以我们想要捕获一个分类表示,其中连续变量的值在每个类别内不会有太大的变化。当然,箱的数量越大,箱内连续值的范围就越大,不会对结果产生太大影响。但是太多的箱子意味着太多的层。

Scikit-learn 的 KBinsDiscretizer 有一些方法,比如将数据均匀地分类到 k 个箱中(按分位数拆分),均匀地分开设置 k 个边界,或者对数据使用一维 k 均值聚类。实际上,结合查看连续数据样本的直方图和思考领域知识,可能是选择如何对其进行分组以进行块随机试验的最佳选择:

如果直方图有明显的峰,这些可能是好的面元。如果它是一个非常偏斜的分布,您可能希望以几何或对数的方式分割,如宁滨每个数量级(因子为 10)。

稍后,我们仍然可以对连续变量进行建模,因此我们不一定需要对其进行装箱,但在连续变量的装箱版本上阻止随机化我们的试验可能是一个好主意。

功率和样本大小计算的强制位

如果我不多说一点关于进行适当的随机实验的事情,那就是我的失职。

首先,只有当你有足够大的样本时,你的结果才具有统计学意义。嗯,这是概率性的,所以真实的情况是,样本量越大,你就越有可能发现某种影响,如果它确实存在的话。对于一个特定的效应大小,你可以计算出你需要多大的样本量,以确保你能检测到一个效应,如果有效应的话。我们称这些元素为功效、效应大小和样本大小。

让我们用两个独立样本的 t 检验来讨论这一现象,因为它可能需要对更复杂的模型和测量进行一些修改。从频率主义者的角度来看,我们考虑一个零假设,即治疗组之间的差异为零。如果这个零假设为真,并且我们以无偏的方式重复测量组间的差异(做了很多无偏的实验),那么我们将得到的测量值将平均为零,并形成具有一些方差的正态分布(即中心极限定理)。测量方差将与样本大小成反比。我们将基于该分布绘制 alpha 阈值(例如,对于假阳性率为 5%的双尾 alpha,我们可以寻找正常 CDF 为 0.975 的位置,这是 1.96 个标准差)。

然后,我们还考虑了处理组之间存在差异的世界,这样,在许多实验中差异的测量将是相同的正态分布,但具有非零均值。这个非零均值,即这两个高斯函数的均值之差,就是效应大小。在这个世界中,只有当我们的度量超过第一个分布的 1.96 个标准偏差时,我们才会拒绝零假设,所以我们需要第二个分布上的点的位置距离第二个分布的中心有一段距离,这样,如果第二个分布为真,我们将有 P 个机会正确拒绝零假设。这里的 p 称为幂,它是第二个分布下的面积,位于第一个分布的 1.96 个标准差的右侧(假设双尾 5% alpha)。典型的幂数是 80%,在正常的 CDF 上,它与中心有 0.84 个标准偏差。因此,两个正态分布的平均值必须相差 1.96+0.84 个标准差。使用将 z 得分与效应大小、方差和样本大小(的调和平均值)相关联的双样本 t 检验方程,我们发现这种 5%α、80%功效标准情景所需的样本大小约为:

*N = 16 方差/效应 _ 大小

TL;对于具有 80%功效和双尾 5%α的双样本 t 检验,上述公式给出了作为组内个体观察值方差函数的样本大小,以及组间效应大小。我们倾向于称之为最小效应大小——我们将有 80%的能力来检测最小效应,甚至更多的能力来检测任何更大的效应。

根据标准偏差与最小可检测效应的比率,该公式的一些经验法则如下:

  • 10 倍比率->每组需要 1600 个样本
  • 100 倍比率->每组需要 16 万个样本
  • 1000 倍比率->每组需要 16M 样品。

作为一个合理的例子,如果我们有一个标准偏差为 50%的度量,例如平均购买价值为 80 美元,标准偏差为 40 美元,我们关心的是检测治疗组之间 0.5% (40 美分)的影响,那么我们的比率是 100 倍,因此我们需要每组 160,000 个样本。

样本量修改

我们说过,当我们进行一个随机分组试验时,我们得到的测量结果的方差较小。我们将最终合并每个阶层的测量值,最终确实会得到一个较小的方差,这可以让我们用相同数量的样本检测到较小的影响,或者说,我们可以用较少数量的样本检测到真实的影响。为了更好地衡量每个阶层,我们需要足够的样本。

最起码,我们需要在每个治疗组的每个阶层中至少有一个测量值。这个原则叫做积极——如果没有例子,我们就无法衡量效果。实际上,我们需要大量的测量,以便我们可以合理地估计观察总体的方差,并使总体实验结果的不确定性小到足以使我们的效果显著。这意味着我们需要足够的样本来分析每一层。每个阶层的“足够”是基于该阶层内的人口方差。

因此,我们需要每一层的许多样本,每一层需要不同数量的样本。如果我们做一个简单的随机对照试验,那么每一个阶层只会出现在整个样本组中。如果试验中只有 1%的人超过 60 岁,那么我们可能不得不扩大整个样本的规模,以获得足够多的 60 岁以上的人来很好地衡量这一部分。然而,在一个区块实验中,我们将明确地选择我们想要的任何数量的 60 岁以上的人,并且我们将分别选择我们想要的任何数量的人。因此,我们可以扩大一个阶层而不扩大另一个阶层,保持我们的总样本量下降。这对于一些实验是有帮助的(尽管对于其他实验可能没有帮助,在其他实验中,我们只是简单地运行几个星期的试验,然后等待每个阶层都有足够多的人)。

一般来说,我们可以将估计的必要样本量乘以各种因素,通常称为设计效应,以考虑任何因素。如果我们想从单尾测试到双尾测试,多找 20%的人。如果我们有一些缺失的数据,那就找更多的人。我们还需要更大的样本量来检测相互作用效应,主要是一个阶层的治疗效应。

我们还需要更大的样本量来进行最后一种随机试验,即整群随机试验。

网络效应

我们说过这是一个全面的指南,不是吗?所以我们来谈谈网络效应。

本质上,随机试验应该满足稳定单位治疗值假设(SUTVA ),即治疗不影响对照组个体。然而,在我们这个相互联系的世界里,这个假设可能会被违背。

对照组的人可以认识治疗组的人,这可以让他们接触到治疗组的知识、经验、信念、行为、资源等。这降低了实验中对照和治疗结果之间的差异,因此相对于真正单独的对照,测量的效果低估了治疗的真实效果。

另一个违反 SUTVA 的情况正好相反——资源是有限的,所以如果治疗组使用不同数量的资源,那么这会影响对照组可用的资源数量。这将增加实验中处理和对照之间的测量差异,从而使实验高估了处理效果。

我们试图通过将治疗组和对照组分开来解决这个问题。根据不同的情况,我们可以在地理上(治疗组在一个城市,对照组在另一个城市),时间上(治疗组在一个日期/时间,对照组在另一个日期/时间),或者通过朋友组(切割到一个社交网络图,留下彼此联系最少的组)。

根据我们如何分析实验结果,我们可能需要更多的样本量来补偿实验结构。传统的方法让我们将每个集群视为一个单元,因此集群将被计算在样本大小 N 中——N 个集群而不是 N 个人是一个更大的样本大小!

较新的方法将保留单个观察值,并在建模阶段包括它们的聚类,以将聚类的效果与治疗的效果分开。因为观察值是通过它们的聚类相关的,所以在这个意义上它们不是独立的样本,我们需要更大的样本量来补偿。类内相关性(ICC)** 是衡量类内与类间样本相似程度的指标,将决定我们需要多少额外样本。我们将样本大小乘以的设计效应因子是 1+ ICC*(N-1),其中 ICC 的范围是从 0 到 1。**

让我们进入下一阶段。

Georgie CobbsUnsplash 上拍摄

第二步:修剪

修剪的问题

修剪数据是指我们在进行分析之前删除数据点。这是一个有争议的话题,而且理由很充分。剪枝标准的灵活性意味着研究人员可以尝试许多剪枝启发式方法,直到他们得到他们想要的结果(这种在科学中作弊的方式有时被称为 p-hacking) 。为了解决这个问题,让我们首先指出两点:

  1. 你的分析应该在做之前就计划好了。研究人员最*在 arxiv 上发布了他们的分析计划,这样他们就可以公开负责执行一项没有经过调整以获得预期结果的分析。
  2. 如果你确实考虑了几种不同的策略,那么你必须在结果的方差中考虑它。p 值阈值需要被校正,以保持真实的假阳性率与您声称的阈值一致,因为您实际上正在运行不止一个实验。我们稍后将深入探讨 p 值校正。

接下来,让我们通过讨论选择正确的数据来更深入地理解修剪。

当我们修剪时,我们在做什么?

修剪离群值是常见的做法,但是我们为什么要这样做呢?当我们看到一个异常值时,我们知道它会打乱我们的测量,尽管它是一个均值或模型。但是为什么保留离群值是不对的呢?

从根本上说,我们是在说,我们不想对产生这种离群值的现象进行建模。如果离群值是针对坏数据收集的,那么我们说我们不希望我们的模型包含坏数据收集。或者,如果离群值是一个真实但很大的数字,就像房价数据集中的巨型房屋,那么我们是说我们不希望我们的模型包括巨型房屋。

如果我们的目标是因果推断,那么我们必须清楚我们在推断什么。如果数据是由我们不想建模的原因产生的,那么我们应该删除它。数据不需要成为离群值来符合这个定义。任何来自我们不想建模的不同来源的数据,或者关于我们不想建模的人群子集的数据,都应该被排除在外!

这个概念与我们混淆变量的概念完全一致。事实上,统计模型中“概括”到测试人群的“概括”概念通常是对与我们的测试人群相同的原因进行建模。如果我们的数据集/训练数据在某些关键方面不同于我们的测试数据/一般人群,如不同的时间、不同的地点或不同的人口分布,这些都是我们在模型中没有正确捕捉到的混杂原因。模型不能概括很大程度上是一个混淆变量的问题。为了回答我们想要回答的问题,我们必须使用正确的数据来模拟正确的原因。

让我们修剪吧!

首先,欢迎你参加你的常规修剪练*。坏数据当然应该被修复或删除,不客气地说,“我不想对这些极端情况建模。”如果你去掉了前 10%的数据,那么就明确地说你在为后 90%的人建模。如果你剔除 500 万美元以上的房子,那么就说你的模型不适用于这些房子。有时,为每一种制度制定不同的模式是一个很好的解决方案。(例如分段回归)。

现在让我们用这些因果推理概念来扩展你的修剪练*。在随机试验中,我们希望治疗组和对照组具有相同数量的影响结果的每个协变量。在一个简单的随机试验中,我们接*了,但不完全是。如果没有随机试验,情况会变得更糟。如果我们只是从人群中获取数据,一个准实验,那么协变量在每个治疗组之间可能是极不平衡的。

所以,我们可以通过忽略一些测量来模拟一个更加可控的实验。我们可以只保留测量的子集,这样我们选择的数据在得到治疗变量的每个值的人群子集之间有相等的协变量。

例如,如果我们想看看父母的收入对孩子收入的直接影响,我们可能需要控制变量,如他们居住的州。我们不能在这里做任何类型的随机试验,因为我们不能设定父母的收入。

要了解这为什么会是一个问题,考虑不同州的一般人群不成比例地有不同的收入,所以如果州独立居住影响孩子的收入,那么在一般人群中,父母收入高的家庭通常会生活在高收入的州,这将使孩子的收入有偏差(将父母收入和州的影响结合到我们的测量中,当我们只想测量父母收入的直接影响时)

因此,我们可以选择数据,使父母的收入组在每个州平均分配。在我们删减的数据中,每个州的高收入父母与低收入父母的比例应该是相同的。这样,我们就不会把父母收入的影响和居住状态的影响混为一谈。

让我们深入了解如何做到这一点的细节

匹配

无论我们有一个还是多个重要的混杂因素,都有一些好的方法来删减数据。许多修剪技术依赖于某种形式的匹配——我们将每个治疗观察与具有非常相似混杂因素的对照观察进行匹配。

只要有一个混杂因素,我们就可以将治疗观察结果与具有最相似混杂因素值的对照观察结果进行匹配。如果我们有多个混杂因素,那么在选择最佳匹配时,我们必须同时考虑混杂因素的多个维度。

由于我们的混杂因素可以是连续的,也可以是分类的,我们必须对这两种情况都有解决方案。

如果我们的混杂因素是连续的,我们可以将治疗观察值与混杂因素之间的“距离”最小的对照组相匹配。这叫做马氏距离匹配。您可能应该重新调整混杂因素,使混杂因素在单位距离内同等重要,这样我们就能匹配到最接*的值。实际上,我们应该根据每个混杂因素对结果测量的影响程度来衡量。

如果我们有明确的混杂因素,或者把连续的混杂因素分成不同的类别,我们可以再次进行分层(混杂因素的每一种可能的组合)。我们可以删减数据,直到我们从每个层的每个治疗组中得到相同数量的数据点。我们也可以只移除不存在任何治疗组的层中的所有数据点(在该层中有 0 个数据点),并允许在每个层中存在不平衡的治疗与对照——然后我们可以对我们的数据点进行加权,以在层中有效地平衡。这叫做粗化精确匹配

我将简要说明因果推断中的另一种常用方法是倾向匹配。我们将在后面讨论倾向得分,因为在这里我只说基于倾向的修剪是不推荐的。倾向本质上涉及将多维协变量投影到单个维度上,结果是基于更少的信息,因此严格来说比基于阶层的修剪更差。它有时被推荐为解决维数灾难和对阳性的需求(如果有许多混杂因素,很难从每个层次的每个治疗组获得数据)。例如,如果存在例如 n 个混杂因素,每个混杂因素具有 k 个水平,则存在 k^n 层)。参见 https://www.youtube.com/watch?v=rBv39pK1iEs Gary King 关于为什么不值得的讨论:

用于修剪的分类数据表示

让我们再来看看数据类型的问题。我们只能对分类变量进行粗化的精确匹配,所以这可能是我们绑定一些连续变量的时候了。对于给定的变量使用更多的箱将意味着更好的分辨率,但也可能迫使我们删除大量的数据,因为我们需要在该变量的每个箱和所有其他变量的每个箱的每个组合内,来自每个处理组的至少一个样本。一般来说,宁滨变量在 5 倍左右是正常的,但这取决于你的样本大小。看看你被迫删除了多少数据,这是数据仓数量的函数。

此外,如果你的混杂因素更复杂,宁滨分类最终将是一个必要的步骤。例如,你的一个混淆对象可能是文本、音频或图像数据!我们可能正在查看一个社交平台,并希望了解社交沟通的某些方面的治疗效果,但我们需要控制沟通本身的内容,即控制帖子/消息的语言,或其图像或视频!在这种情况下,我们将需要一个有意义的,简单的高维数据表示,以便进行因果推理。

为了将它包含在粗化的精确匹配中,我们需要一个相对低维的表示,以保持合理的块数,这样我们就不会删除太多数据。基于情绪/情感和语义主题,自然语言数据可以被分成少数类别。

接下来,让我们进入第 3 步,在这里我们显式地对每个变量的影响进行建模。

照片由 STILUnsplash 上拍摄

第三步:模拟混杂因素

重复我们关于修剪的讨论,我们实际上不需要数据集/训练数据具有与测试数据完全相同的协变量平衡。我们只需要 知道 所有影响结果的混杂因素,并以某种方式“控制”它们。

即使我们的训练数据是 10%的孩子,而我们的测试数据是 90%的孩子,如果我们已经完全模拟了作为一个孩子的影响,我们可能会做出完美的预测。

建模可以通过多种方式完成。我们可能会认为我们应该关心模型的可解释性。毕竟,关于模型可解释性的讨论本质上是关于因果关系的——我们想知道为什么我们的模型预测了结果 y。哪些特征在预测中发挥了最大的作用?

一般来说,我会说这些关于可解释性的讨论取决于不知道我们应该把哪个变量作为我们的治疗变量。如果我们想考虑每个变量的因果关系,同时使用所有其他变量作为对照,那么是的,可解释性是极好的。我们可以使用简单的线性回归,将所有系数解释为每个变量的因果效应(我们将更严格地讨论这一点)。然而,我们知道多重共线性会增加复杂性,考虑到我们的因果系统的 DAG 模型,我们应该关注“将所有其他变量视为混杂因素”是否合适。

但是,如果我们确实知道我们想要分析哪个变量作为治疗方法(当然,如果我们进行随机实验,这已经设置好了),那么是时候进行适当的因果推理建模了。

关于随机实验,我们可能仍然希望包括建模。在一个简单的随机实验中,我们的协变量并不完全平衡。如果我们不通过修剪和加权来处理这个问题,那么我们可以在建模中处理它。

对于一个随机区组试验,我们可以潜在地测量每一层而不需要任何建模,因为协变量是完全匹配的。然而,我们可能只屏蔽了一些协变量以降低维度,因此我们可以在单个模型或每个阶层的模型中对其余的协变量进行建模。或者,如果我们的分块是基于一个真正的连续变量的宁滨,那么我们可能希望用一个模型为每个阶层的连续变量建模。

最后,对于整群随机试验,如果我们想使用个体观察,我们需要使用建模,因为整群是一个额外的混杂因素。

元学*者

元学*者框架允许我们以同样简单的方式使用任何任意复杂的 ML 模型来衡量治疗效果。s,T 和 X 学*器允许我们将任何机器学*模型翻译成因果推理机。

最简单的框架是 S-learner。首先,我们根据我们的数据训练任何模型,使用治疗和协变量来预测结果。可能是回归,随机森林,kNN,助推机,神经网络,随便你说。

然后使用我们的模型,我们获取数据,用一个治疗组的值替换所有的治疗变量测量,并使用该模型预测结果。这些作为我们对治疗组的观察。然后,我们对每个治疗组重复上述步骤。Viola 就像一个实验,人工生成的数据在每个治疗组中都有相同的协变量——几乎每个观察都在每个治疗组中重复,只有治疗发生了变化。每组的平均值之间的差异是我们的 ATE(平均治疗效果)的量度。

我们还可以通过简单地只查看给定条件下的观察值来获得 CATE(条件平均治疗效果)。如果我们想要男性的治疗效果,我们可以在进行 S-learner 过程时只查看男性的观察结果。

t 和 X 的学*者用一些更强大的控制方式重复这个问题——比如为每个治疗组训练一个模型,并根据模型预测的测量值之间的误差训练额外的模型。这些元学*者都基于这样一种理解,即我们正试图测量反事实,这是如果给定的观察结果只改变了他们的治疗组会发生的事情。

有了这些知识,我们可以跳回解释线性回归为因果关系。事实上,如果我们在 S-learner 框架中使用线性回归,那么治疗组之间的平均差异就是治疗变量前面的系数,因为其他所有变量都减为 0。

因此,如果回归中的其他一切确实是适当的混杂因素,并且我们已经测量了所有混杂因素,并且线性模型捕获了所有变量之间的完整关系,那么这个 S-learner 是因果效应的良好测量。

但是现实并不美好。

异源治疗效果和相互作用

好的建模意味着尽可能准确地评估我们的系统。如果每个变量与结果线性相关,并且每个变量对结果的影响完全独立于其他变量,那么简单的线性模型才是好的模型。你的系统是这样吗?

就因果推断而言,现实往往具有异质的治疗效果——这种治疗是其他变量的函数。也就是说,接受治疗的人决定了治疗的效果。例如,根据人口统计、健康状况、地理位置、知识水平或目标,治疗可能对人产生不同的影响。

****现实也有混杂因素之间的相互作用。性别等混杂因素对结果的影响可能是年龄的函数——也许年轻女性比任何其他年龄和性别的女性更倾向于更好的结果,但老年女性实际上更倾向于任何年龄和性别的最差结果。在这种情况下,线性相加年龄和性别的贡献不会捕捉到这一现象

概括地说,每个变量对结果的影响可以是任意非线性的,也可以是任何其他变量的任意函数。

我们可以使用复杂的模型进行因果推断,这是一件好事。

这里我要说明的是,我们也可以使用许多模型。没有必要用一个单一的训练过的模型来代表一切。为不同范围的输入值训练不同的模型通常是个好主意。如果我们有一些阶层,每个阶层的不同模型可能会很有帮助。

对每个层使用不同的模型有很多好处:我们可以允许每个层的模型的一般形状,甚至类型完全不同。此外,每个模型本质上都捕捉了定义该层的类别和被建模的变量之间的异质和交互术语。

例如,如果我们有一个 40 岁以下女性的阶层,在这个阶层中,我们有一个包含年龄(作为连续变量)、治疗以及她们是否是新使用者的模型,那么我们将捕捉治疗仅对 40 岁以下女性的影响,这是一个异质效应,我们还将捕捉连续年龄和新使用者对 40 岁以下女性的影响,这是一个交互效应。

选择使用多种模型也有不利的一面。首先,每个模型的数据会更少。如果我们有一个包含所有这些相互作用项的单一模型,情况可能会更糟,因为训练模型所需的数据与项的数量呈超线性关系。但是,如果我们有一个单一的模型,有一组更有限的相互作用项,我们可能有更多的每项数据比每层模型的方法。

此外,如果我们有一个单一的模型,它通常建议包括主要影响(非相互作用的条款),每个阶层将有助于该项目的平均估计。但是在每层模型方法中,这一方面是缺失的。

照常提取特征

好的机器学*规则仍然适用。如果我们保持我们的一些数据连续,那么我们需要尝试建模它与结果变量的关系,就像任何 ML 一样。虽然基于决策树的模型(forests,GBMs)将为我们处理连续数据,并且神经网络可以学*任意函数,但一些更简单的模型需要我们的帮助。我们可以从我们的数据中提取新的特征,方法是对连续变量进行多项式拟合,计算它们的比值,或者使用其他特征提取/降维技术来创建新的特征(PCA、ICA、RBF 核等)。).

像在 ML 中一样,如果必要的话,我们应该考虑缩放特性。任何使用距离度量(kNNs、RBF 核函数)的方法都应该具有重定比例的要素,以便距离对每个要素的价值是相等的(或者在您认为合适的时候随意重定比例)。如果我们使用正则化,我们也可能会重新缩放,这将在后面讨论。

将连续变量宁滨成分类变量,或者更确切地说是顺序变量可以有助于正确地对域建模。

在连续变量的线性模型中,我们需要使用多项式和比率来捕捉任何非线性关系。然而,如果我们绑定一个连续变量,我们的一个热编码将迫使我们至少学*每个绑定和结果变量之间的恒定关系。如果我们把它映射回我们最初的连续变量,这就像学*一个分段常数函数。对于足够多的片段,这可以让我们找到比显式建模的连续变量更复杂的关系。

例如,如果我们将用户的社交联系人数量分成几个部分,那么我们可能能够测量出连续体中有多个波峰和波谷——也许 0 个联系人会导致最高的保留率,因为人们会忘记他们的帐户,而 1-5 个联系人则不好。然后,也许 6-25 个联系人也是好的,因为他们使用该平台与同学交流,但 25-200 个联系人是不好的,因为他们所有的薄弱联系都会在 feed 中产生噪音,而拥有 200 个以上的联系人是好的,因为这些用户正在使用该平台开展业务。

宁滨连续变量同样可以帮助我们建模异质和相互作用的关系。如果我们将变量 X 分成 5 个箱[X1…X5],那么我们可以用 X1 和处理 X1*T 的乘积、X2 和处理等来创建项。或者 X 和另一个混杂因素之间的区别。这再次启用了这些交互的分段模型,这提供了很好的灵活性。对于连续变量,我们需要考虑 X 上的比率和多项式与其他项的乘积,这在指定错误的结构时会更加复杂。但是要看你的域!

我们如何选择合适的模型?

我们说过,有了元学*者框架,我们可以使用任何类型的 ML 模型,我们现在也在考虑一些交互/异质术语,以及为不同的体制训练不同模型的选择。

那么,我们如何找出正确的建模方法呢?

好吧,考虑一下,我们只是想要一个能准确捕捉我们系统的模型。这似乎与典型的偏差-方差权衡没有本质区别。这并不奇怪,因为我们说过模型泛化与正确建模我们系统的因果关系有很大关系。

如果我们可以找到一个具有高泛化精度的模型,那么我们可能已经相当好地模拟了我们的因果系统——如果任何可测量的变化都导致可预测的结果,那么我们就理解了我们系统中的力量,至少在测试数据探索的范围内。

所以,我们感兴趣的是通常的——一个测试分数很高的 ML 模型。和通常的最佳实践一样,如果测试分数不可区分,我们应该选择更简单的模型。我们还应该更喜欢重复拟合导致相同模型的模型——线性模型中相似的系数,森林中的重要性分数,以及在使用这些模型进行因果推断时,每次我们再次拟合和测量时相同的估计治疗效果。

与我们所有的建模方法一样,我们应该尝试许多对我们的系统有意义的方法,并比较结果。在结果相同的情况下,我们可以确信我们的模型/数据对假设/框架的微小变化是稳健的。当结果不同时,我们从数据中学到了一些重要的东西——也许某个特定的关系很重要。

让我们考虑一下我们的选择。

正规化

我们已经讨论了一个简单的线性回归。复杂性的下一步,或者更确切地说,降低模型复杂性的更复杂的损失函数,是正则化我们的线性模型。如果正则化提高了我们的测试分数,同时降低了模型的复杂性,那么我们很可能更好地捕捉到了因果关系。

事实上,当用不同程度的正则化拟合回归时,论文已经探索了真实治疗效果和估计效果之间的关系。研究结果通常表明,强正则化倾向于导致更精确的治疗效果。你可以网格搜索,对半搜索,随机搜索,或使用贝叶斯优化来找到一个弹性网的最佳正则化参数,看看什么工作。

亚马逊 2019 年的一篇论文在一些假设下,分析性地确定了正则化的正确量,以便最佳地估计治疗效果。一般来说,它与更严格的正规化相一致。

逆概率加权

正如我们已经讨论过的,因果推理的核心是平衡混杂因素。如果您的治疗组具有相同的混杂值,那么结果的差异将集中在治疗效果上。

我们已经讨论了建模,所以我们在控制混杂因素的任何剩余差异方面做得很好。但是我们可以通过计算我们拥有的数据量来做得更好。

考虑我们的模型正在最小化一个损失函数,该函数对数据集中每个点的误差进行求和。如果我们的数据集在治疗 1 中有许多妇女,但在治疗 2 中没有许多妇女,那么与治疗 2 中的妇女相比,模型将更关心优化治疗 1 中的妇女的预测。我们希望模型平等地关注所有阶层,以及所有异质性/治疗效果,但我们的数据并不代表这一点。

所以我们对数据进行加权。

我们将对数据进行加权,以便在任何混杂因素组合中,每个治疗组都有相同数量的数据点。这种方法已经以多种方式实施,并且在估计治疗效果方面持续显示出改进。

在粗化精确匹配中,我们提到,如果治疗组之间的地层不平衡,我们可以对数据进行加权。在建模之前对数据进行加权甚至比最后简单地对测量结果进行加权更好。

在数学上,对于分类变量,我们可以通过其治疗组频率的倒数来对数据进行加权。例如,如果我们有 3 个处理 1 的例子和 5 个处理 2 的例子,那么我们可以分别将数据点加权 1/3 和 1/5。那么每个处理组具有相同的总重量 1。我们也可以通过点在每个处理组中的概率的倒数来加权。概率分别是 3/8 和 5/8,所以逆概率是 8/3 和 8/5。用这些值对这些点进行加权也将导致每个治疗组的总重量相同。我们称之为逆概率加权

但是如何对模型中的连续变量进行加权呢?

倾向

好吧,这是一个讨论倾向分数的好时机。我们之前拒绝了倾向的概念,因为基于混杂因素的完整信息进行修剪比投射到一个维度更好。但是在这里,我们需要每个治疗组的频率/概率的单个连续权重,因此投影到一维正是我们想要的。

倾向模型可以是任何 ML 模型。我们的“主”ML 模型使用治疗+混杂因素来预测结果,而倾向模型使用混杂因素 来预测治疗。 可以是线性模型,也可以是像森林、kNN、神经网络这样的非线性的东西。我们只需要知道,对于一组给定的混杂因素,在每个治疗组中观察到的可能性是多少。

我们真正想做的是创建一个代理,代表每个治疗组在每个混杂值下有多少观察值。

例如,如果我们的数据集中有很多大约 39 岁的男性(T4),但很少有大约 39 岁的女性,那么这些观察在这个范围内是有偏差的。我们可以在我们的主模型中纠正这种偏差,方法是使用一个倾向模型来估计在这个年龄范围内男性观察值比女性多的程度,并使用我们观察数据的权重来更好地训练我们的主模型。

添加倾向得分作为模型权重使我们的模型更加稳健。由于我们在尝试评估治疗效果时关注如此多的移动部分,这种稳健性是受欢迎的。

此外,由于我们使用倾向模型作为概率,我们希望它得到很好的校准,并避免值过于接*边缘。让我们深入讨论一下:

考虑这样的情况,我们有一个二元处理,如 1 个处理对 1 个对照,用 0 或 1 表示。倾向得分是观察接受治疗的概率。当我们训练一个输出从 0 到 1 的“概率”的二进制分类器时(就像用 predict_proba()方法),我们非常希望这个概率尽可能准确。对于分类,我们通常不关心确切的概率——我们只关心两个类从任何决策边界(在某个概率值)的分离——我们通常只关心单调缩放。但是在这里,我们希望倾向评分的精确概率能够正常工作。

因此,我们应该对我们的倾向分数分类器进行校准。校准只是一种使用一些验证数据来获得完全正确的概率的方法。我们可以通过简单的 sigmoid 曲线来校准我们的预测,或者通过一个非参数的、任意的“等张”函数,如果我们有足够的数据来学*它而不过度拟合的话。Scikit-learn 具有校准功能。

让我们更深入地探讨一下如何选择合适的型号。

输出和广义线性模型的误差结构

虽然我们可以盲目地使用 AutoML 类型的方法来进行机器学*,但领域和数学可以提出一些好的解决方案。

检查结果变量的误差结构总是很重要的。不对称残差让我们知道,如果我们的模型有偏差,也应该检查独立变量不同值的方差。

当结果变量等于模型输出加上简单的正态分布误差ε时,最小二乘成本函数实际上是最大似然估计量:

y =线性模型+ε,

其中ε~N(0,方差)

这意味着一个同方差的事实,即线性模型之外的噪声不是独立变量的函数(y 的方差沿 x 轴都是相同的)

当然,事实未必如此。

当然,分类结果变量不是这种情况,在分类结果变量中,分布是某种二项式/多项式,取 n 个值,独立变量 x 的每个值都有一定的概率。

缩小,广义线性模型方法允许我们指定其他误差结构。我们可以从指数离差族中选择任何分布,指数离差族包括大多数你听说过的分布。

如果我们不喜欢我们的误差结构,或者不想预测一个非常倾斜的结果变量(因为误差的平方在右尾将是巨大的),我们可以考虑转换我们的输出变量。我们只需要为转换后的输出变量指定正确的误差分布。

虽然这本身是有用的,但我认为这将是考虑我们所学参数的 分布的一个好的介绍。

混合/分层模型和广义估计方程

分类变量的标准做法是将其转换为一个热变量。也就是说,对于具有 N 个可能值的类别,我们将每个观察值映射到长度为 N-1 的向量上(以避免完美的多重共线性),方法是在 N-1 个向量元素的每个元素中用指示符变量表示 N-1 个类别,并让所有元素中的 0 状态引用被丢弃的“参考”类别。

当我们进行这种热编码时,我们允许我们的模型学*每个类别的独立参数。让我们考虑一个线性模型。一个类别的参数值几乎不会影响其他类别的参数值(除非它们都可以移动一定的量,如果该量被分解到模型中的其他术语中)。

但是对于一些问题,最好是详细说明这些参数的分布,这些参数代表了分类混杂因素的影响。事实证明,我们需要这一点,以便在群集随机试验中对个体观察进行建模。至少,这是推荐的最佳实践。

混合/分层模型就是这样做的。应用于一个聚类试验的建模,我们指定了聚类效果的系数分布。这样,一个观测值所属的分类就成为了一个额外的误差源,每个分类都有自己的项,这些项的集合形成了一个均值为 0 且有一定方差的正态分布。

任何“相关的”数据都可以用这种方式建模。任何时候,当我们从每个人那里得到多个观察结果时,这种方法可能是合适的,因为个人表现为具有正态分布参数的分组“随机效应”。当我们查看用户对影响者或活动的响应时,我也使用了这种方法,因为我们希望在模型中包括个人响应,但他们通过他们响应的活动或影响者进行关联。

另一种解决同类问题的方法是使用广义估计方程(GEEs)。建议将差异结构指定为可交换的,但您也可以尝试所有的结构。

像混合模型、广义线性模型和广义估计方程这样的复杂模型可以在 Python 中使用 Statsmodels 库来完成。

高级因果推理模型

最后,我们可以谈谈其他几个专门为因果推理设计的模型。

****双稳健模型是对我们在模型中使用倾向分数的讨论的一个小小的扩展。双稳健模型很像元学*者,因为我们使用我们的主模型来进行预测并查看结果。不同的是,我们也使用倾向,并结合他们的预测,为每个观察。用于估计 ATE(平均治疗效果)的最终等式使用因果模型和倾向模型,如果正确指定任一模型,结果将是正确的!因此,模型中的小误差和其中一个模型中的大误差不会过多地干扰我们对治疗效果的估计。

双机器学*(DML)因果森林是另外两种流行的方法。DML 可以使用因果森林或其他任意复杂的 ML 来处理非线性、相互作用和异质效应。DML 采用了一种稳健的方法,包括交替模型拟合,有趣的是,它将结果建模为混杂因素单独的函数,以及一个倾向样模型。

让我们继续第 4 步。

照片由 krakenimagesUnsplash 上拍摄

第四步:测量

再次分层

如果我们不符合任何模型,测量值就是通常的统计检验。也就是说,我们可以对每个治疗组的结果取平均值,并确定这些平均值是否不同。

然而,当我们这样做时,最好进行分层。获取每个阶层的治疗效果,然后将这些测量值汇集在一起(基于它们在我们试图建模的人群中的流行程度),得到我们的平均治疗效果,同时强烈控制那些混杂因素。

因此,如果我们有一个稍微不平衡混杂的随机试验,我们可以使用这种技术。

当然,如果我们只有观察数据,我们应该使用这种技术,特别是如果我们不能很好地修剪。

这里分层的效果可以非常类似于建模,尤其是对一个热变量的交互项进行建模。线性模型可以包含 k 个不同的热混杂变量的 k 维相互作用项,并且这些项将基本上测量相同的东西,就像将数据分层到那些 k 维层中并取数据的平均值。战略性地结合这些技术是有益的。

那么我们用的是哪种统计检验呢?

统计测试

对于连续变量,我们可以利用双样本 t 检验,或多样本扩展。对于分类结果,我们可以使用卡方。

对于连续变量,我们应该考虑拥有不同的均值是否足以回答我们的问题。t 检验通常真正的目的是询问对照组和治疗组的整体分布是否相同——两组是否真正相同。如果是这样,那么您可能希望确保方差也是相同的,并对相同的方差进行统计测试。

您可能还想验证这些分布是正态分布,或者对它们进行修剪/Winsorize,直到它们是正态分布——如果两个分布具有完全不同的形状,则具有相同的平均值并不表示它们相等!使用 QQ 图和夏皮罗-维尔克测试来确保你的分布是正态的,或者修剪 x%的数据,并声明你正在测试每个分布的中间(100-x)%是否相同,如果这是使它们正态的必要条件。

否则,您可以使用非参数统计检验。一般来说,这是对所有数据点的排序,以确定分布是否可能相同。

如果我们匹配数据,那么我们可以考虑配对测试。通常情况下,当一个个体在纵向研究中被测试不止一次时,会使用配对测试,但它也可以应用于此。配对检验有更大的功效,因为个体间差异的方差比整个个体集的方差小得多。是使用成对检验还是更保守的独立检验在数学上与潜在成对观察值之间的相关性相关,即协方差中的真实方差因子,因此它的范围从没有协方差时的独立检验到差异方差为零且您已经知道成对检验拒绝空值的完美协方差。

如果我们做许多统计测试,那么我们也需要控制家庭明智的错误率。如果我们希望我们整个分析的总假阳性率是α,那么我们不能用同一个α做几个测试。Bonferonni 修正是处理这个问题的一种非常保守的方法,因为它假设所有的测试都是独立的,并强制它们都超过最严格的标准(alpha 除以测试次数)。Holm-Bonferonni 修正仍然假设独立测试,但至少允许几个假设被拒绝,只要一个通过最严格的标准。在这种方法和其他相关统计检验方法中,p 值被排序,非常类似于非参数检验中观察值的排序。

此外,Tukey 的诚实测试正确处理了许多双样本 t 测试的家族错误率问题。本质上,相同的 t 分数与相似学生的 t 分布进行比较,涉及分数之间的范围

如果我们符合模型,那么我们就可以像我们讨论的那样进行测量。Metalearner 框架或 Doubly Robust 框架提供了衡量我们平均治疗效果的方法。然而,我们仍然需要这些治疗效果的误差。为此,一个通用的解决方案是自举。

自举获取误差测量值

自举是另一种神奇的技术。我认为它令人惊讶和有用的性质使它与中心极限定理相提并论。在 Bootstrapping 中,我们从样本数据的一些任意复杂的过程中寻找我们的测量中的误差——我们想知道我们对样本数据的测量与对整个人口的测量实际上有多大的差异。与根据样本数据的方差来估计 t 检验中的总体误差的逻辑类似,我们将仅使用样本数据来估计度量中的误差。

Bootstrapping 可用于估计任何测量的误差。我们*惯于得到平均值的误差,但是如果你的测量值是中间值呢?如果是线性回归的系数呢?也许有基于样本数据方差的误差公式,但如果我们的测量是关于随机森林或神经网络的,那会怎样呢——这看起来很棘手!

引导的技术很简单。如果我们有 N 个数据点用于获得我们的度量(通过将一些任意复杂的模型拟合到这 N 个数据点),我们只需从我们的 N 个数据点 中重复采样 N 个数据点,替换 。因此,如果我们的数据点是数字[1,2,3,4,5],一个 bootstrap 采样是[2,2,4,5,5] —它仍然是 5 个数据点,但它是一组不同的数据。我们将使用这个新的“引导数据”再次得到我们的测量。我们重复这样做,也许 50 或 100 次,以得到结果的分布。

神奇之处就在于此:来自自举数据集的测量值的方差是我们原始测量值和真实总体测量值之间误差的一个很好的估计。

难以置信。

还有许多其他的自举方式。一项有趣的技术是获取模型的残差,在数据点之间进行置换,并在拟合新模型之前将其应用于结果。如果你要依赖这种误差测量,最好深入研究不同引导技术的优缺点!

我们也对测量异质因果效应及其误差感兴趣。

异源效应的大小和显著性

同样,异质效应是指治疗的效果依赖于其他协变量,如接受治疗的人群子集。正如我们提到的,如果选择样本量只是为了测量某一特定大小的主要效应,我们的样本量可能无法测量所有这些异质性效应,但我们可以看到是否有统计学意义。

我们可以观察每一层的统计测试,以了解治疗对该层的影响大小和意义。我们还可以对我们的模型采取 CATE 方法,看看个体子集(由一些协变量的范围定义)的治疗效果是否显著,并获得其效果大小。在需要获取错误度量的地方使用引导。

和往常一样,即使真的有效果,我们的结果也可能不是统计信号。如果自举方差大于我们关心的效应大小,那么我们需要更多的数据。

切瑞迪克Unsplash 上拍摄的照片

接下来要学的东西

作为最后一节,让我们快速提一下在因果推理中可能出现的其他几个概念。

首先,我们没有过多地讨论有序数据。事实上,我们归类的所有连续变量都可以保持它们的顺序结构。有序建模方法可以使用不同的阈值来建立单个决策边界,或者在 n 个有序值之间的决策边界上训练 n-1 个不同的模型,并且组合这些模型。这如何与因果推理相联系是另一天的话题。

最*,Judea Pearl 一直在与 Elias Bareinboim 合作进行数据融合,这是我们如何使用多个非常不同的数据源来做出单一的因果推断。例如,我们可能有一个随机对照试验和一个单独的观察数据集,每个数据集测量不同的变量子集。这些怎么结合?

另一项工作是个性化。如果我们只能对所有个体应用相同的治疗方法,那么也许平均的治疗效果就足够了。但是,如果我们能够对不同的个体给予不同的治疗,那么我们需要找出哪一种对每个人来说是最好的。个人层面的因果关系需要一些额外的理论。

我们也可以根据个人的结果来分析不同的制度。除了因果推断之外,分位数回归可以让我们为不同百分位数的结果创建一个模型。如果电力用户提供了我们的大部分收入,我们可能会对模拟最佳结果非常感兴趣,如果避免不利影响是最高优先级,我们可能会对模拟最坏结果非常感兴趣。根据领域的不同,我们应该始终自上而下地考虑什么是有价值的预测,并将其纳入我们正在寻找的因果度量中。然而,我们应该始终小心基于结果的分析——简单地通过干预后变量划分用户会导致有偏见的治疗效果测量。

此外,我们没有特别注意时间序列类型的数据。差异 DiD 方法的差异是检查治疗效果的基线方法,但它们假设每个治疗组的趋势相同(不是每个组的治疗前值的函数)。本质上,DiD 方法只是一个配对测试。我们也可以像处理其他协变量一样,沿时间轴应用分段回归,以允许水平和斜率在每个时间段发生变化。然而,这些方法遗漏了更复杂时间现象模型,如自相关和季节性。考虑这些元素的模型对于正确地对系统建模并获得标准误差的精确估计是必要的。Google 的 CausalImpact 使用状态空间方法来建模时间序列,即贝叶斯结构时间序列(BSTS),这是一种非常灵活的方法,可以捕捉大多数时间现象。

当一个协变量超过某个阈值时,对所有个体进行治疗时,可以使用回归不连续设计。因为在这个协变量中,控制和处理是完全(或模糊)分开的,所以分析通常通过使用边界附*的个体来关注局部平均处理效果。

最后,最*有一项工作将因果关系与深度学*相结合。我期待着朱迪亚·珀尔即将于 2022 年 2 月 25 日举办的研讨会,主题是:
https://www.cs.uci.edu/events/seminar-series/?研讨会 _id=1094

再见

我已经尽了最大努力让这个指南变得全面——这是我在一个周末内组织我的一般因果推理知识的最好机会。我会在未来发表一些关于更具体主题的文章,或者有任何问题随时联系我。希望对你有帮助!

关于简短的相关讨论,请查看我的文章:因果推理中的方差减少:

**https://medium.com/@skylar.kerzner/variance-reduction-in-causal-inference-8145dd9d5f2 **

决策树完全指南

原文:https://towardsdatascience.com/a-complete-guide-to-decision-trees-ac8656a0b4bb

学*关于决策树的所有知识,包括 Python 示例

西蒙·威尔克斯在 Unsplash 上的照片

决策树是一种机器学*算法,其名称来自其树状结构,用于表示多个决策阶段和可能的响应路径。决策树为分类任务或回归分析提供了良好的结果。

我们用决策树做什么?

借助于树形结构,不仅可以将不同的决策层可视化,还可以将它们按一定的顺序排列。对于单个数据点,可以进行预测,例如,通过得出目标值以及分支中的观察值进行分类。

决策树用于根据目标变量进行分类或回归。如果树的最后一个值可以映射到一个连续的尺度上,我们称之为回归树。另一方面,如果目标变量属于一个类别,我们称之为分类树。

由于这种简单的结构,这种类型的决策非常受欢迎,并广泛应用于各种领域:

  • 业务管理:不透明的成本结构可以借助树状结构来说明,并清楚地表明哪些决策需要多少成本。
  • 医学:决策树有助于患者发现自己是否应该寻求医疗帮助。
  • 机器学*和人工智能:在这个领域,决策树被用来学*分类或回归任务,然后做出预测。

决策树的结构

树基本上由三个主要部分组成:根、分支和节点。为了更好地理解这些组成部分,让我们仔细看看一个示例树,它可以帮助我们决定今天是否在户外锻炼。

决策树示例|作者照片

顶层节点“天气”就是所谓的根节点,作为决策的依据。决策树总是只有一个根节点,因此所有决策的入口点都是相同的。在这个节点上挂着所谓的具有决策可能性的分支。在我们的情况下,天气可以是多云、晴天或雨天。其中两个分支(“晴天”和“雨天”)挂着所谓的节点。此时,必须做出新的决定。只有分支“多云”直接导致一个结果(叶)。所以从我们的树上,我们已经可以读到,当天气多云时,我们应该总是去户外运动。

另一方面,在晴天或雨天,我们必须考虑第二个因素,这取决于我们的天气结果。对于节点“湿度”,我们可以在“高”和“正常”之间选择。如果湿度高,我们最终会得到“不”叶子。因此,在阳光充足、湿度较高的天气里,不宜在户外运动。

如果天气下雨,我们就在决策树的另一个分支。那么我们就要在“风”这个节点做出决定了。这里的决策选项是“强”或“弱”。还是那句话,我们可以读两条规则:如果下雨但风力较弱,我们可以在外面做运动。另一方面,如果下雨并伴有大风,我们应该呆在家里。

这个非常简单的例子当然可以进一步扩展和完善。例如,对于节点“湿度”和“风”,可以考虑用具体的规则(强风=风速> 10 公里/小时)来代替主观的决策选项,或者将分支细分得更细。

所谓的修剪是什么?

在现实世界的用例中,决策树会很快变得复杂和混乱,因为在大多数情况下,需要两个以上的决策才能得到一个结果。为了防止这种情况,经过训练的决策树经常被修剪。

减少错误剪枝是一种自下而上的算法,从叶子开始,逐渐向根发展。这包括取出整个决策树,并省去包括决策的一个节点。然后,进行比较以查看截断树的预测精度是否已经恶化。如果不是这种情况,树被这个节点缩短,决策树的复杂度降低。

除了在训练之后缩短树的可能性之外,还有在训练之前或期间保持低复杂度的方法。一个流行的算法是所谓的提前停止规则。在训练期间,在每个创建的节点之后,决定是否在该点继续该树,即它是否是决策节点,或者它是否是结果节点。在许多情况下,所谓的基尼系数被用作一个标准。

简而言之,它表达了如果标签被简单地随机分配,即基于在该节点的分布,标签将在该节点被不正确地设置的概率。这个比率越小,我们就越有可能在这一点上对树进行修剪,而不必担心模型准确性的巨大损失。

决策树的优点和缺点

简单易懂的结构使决策树成为许多用例中的流行选择。但是,在使用该模型之前,应该权衡以下优点和缺点。

决策树的优点和缺点|作者照片

决策树是随机森林的一部分

随机森林是由个体决策树组成的监督机器学*算法。这种类型的模型被称为集合模型,因为独立模型的“集合”被用于计算结果。在实践中,这种算法用于各种分类任务或回归分析。其优点是通常较短的培训时间和程序的可追溯性。

随机森林由大量这些决策树组成,它们作为一个所谓的整体一起工作。每个单独的决策树做出预测,例如分类结果,并且森林使用大多数决策树支持的结果作为整个集合的预测。为什么多个决策树比单个决策树好得多?

随机森林之所以奏效,是因为所谓的的多数人的智慧原则。它说,许多决策树的决策优于一个独特的树的结果。这是一个适用于各种用例的原则,并在 fair 上首次得到认可。

在 20 世纪,在集市上出售牛是很常见的,必须确定它们的重量。1906 年,这样的一头牛被展示给 800 个不同的人,让他们猜这头牛的重量。最终,八百个人猜测的中值距离最终重量只有 1 %左右。没有一个估计与实际结果如此接*,这意味着个人的总和比任何其他人都有更好的估计。

这一发现可以转化为其他领域,如随机森林,这意味着几个决策树及其聚合预测优于单个树。

随机森林的结构|作者照片

然而,这是有一个先决条件的。决策不能与相关,否则单个树的错误不会被另一个树补偿。让我们回到我们公平的例子。

如果所有的参与者都没有达成任何形式的一致,也就是说他们是不相关的,那么权重的中值估计将会比单一的猜测要好。否则,几个人的估计会影响其他人,而这不会产生多数人的智慧。

用 Python 训练决策树

skic it-LearnPython模块提供了数据分析所需的各种工具,包括决策树。其中,它基于 Numpy 已知的数据格式。为了用 Python 创建决策树,我们使用了来自文档的模块和相应的例子。

所谓的虹膜数据集是用于创建分类算法的流行训练数据集。这是一个生物学的例子,涉及到所谓的鸢尾属植物的分类。关于每朵花的长度和宽度的花瓣和所谓的萼片是可用的。基于这四条信息,然后就可以知道这种特定的花是三种虹膜类型中的哪一种。

在 Skicit-Learn 的帮助下,只需几行代码就可以训练出一个决策树:

因此,我们可以通过定义输入变量 X 和要预测的类 Y 来相对容易地训练决策树,并根据它们来训练决策树。利用函数“predict_proba”和具体值,可以进行分类:

因此,根据我们的决策树,具有虚构值的这朵花属于第一类。这个属叫做“刚毛鸢尾”。

如何解读决策树?

在 MatplotLib 的帮助下,可以画出训练好的决策树。

我们数据的最佳决策树共有五个决策级别:

决策树虹膜数据集示例|作者照片

对于这个树的简单解释,我们感兴趣的是第一行和最后一行的值。树是从上到下读的。这意味着,在第一个决策层,我们检查花瓣的长度是否小于或等于 2.45 厘米。条件总是被公式化,使得在左分支中只有“真”,在右分支中只有“假”。

所以,如果一朵混凝土花有一个小于或等于 2.45 厘米的花瓣,我们在左边的分支(橙色瓷砖中),这也是一片结果叶。因此我们知道,在这种情况下,花属于“Setosa”类。

另一方面,如果花瓣更长,我们沿着正确的分支前进,并面临另一个决定,即花瓣是否有 1.75 厘米的最大宽度。我们遍历树,直到得到一个提供分类信息的结果表。

这是你应该带走的东西

  • 决策树是另一种机器学*算法,主要用于分类或回归。
  • 树由起点、所谓的根、代表决策可能性的分支和具有决策级别的节点组成。
  • 为了降低树的复杂性和大小,我们应用所谓的修剪方法来减少节点的数量。
  • 决策树非常适合生动地表现决策,并使其可以解释。
  • 然而,在训练时,为了获得有意义的模型,必须注意许多细节。

如果你喜欢我的作品,请在这里订阅https://medium.com/subscribe/@niklas_lang或者查看我的网站* 数据大本营 !还有,medium 允许你每月免费阅读 3 篇 。如果你希望有无限制的 访问我的文章和数以千计的精彩文章,请不要犹豫,点击我的推荐链接:【https://medium.com/@niklas_lang/membership】每月花$5***获得会员资格**

* https://medium.com/@niklas_lang/membership

原载于 2022 年 1 月 5 日https://database camp . de*。**

Python 词典完全指南

原文:https://towardsdatascience.com/a-complete-guide-to-dictionaries-in-python-5c3f4c132569

什么是词典,创建它们,访问它们,更新它们,以及特殊的方法

Joshua Hoehne 在 Unsplash 上拍摄的照片

什么是字典

继列表、集合和元组之后,字典是 Python 中的下一个内置数据结构。它们通常用于编程,为 Python 中许多不同库中更高级的结构和功能奠定了基础。它们的形式类似于实际的字典,其中的键(单词)有值(描述),因此采用键:值结构。它们的主要特点是:

  • 可变:一旦定义,就可以改变
  • 有序:除非明确改变,否则它们保持它们的顺序
  • 可索引:如果我们知道特定条目在字典中的位置(它们的键),我们就可以访问它们
  • 无重复键:它们不能在键值中包含重复项,尽管它们可以在值中包含。

字典的重要部分是它们的键-值结构,这意味着我们不是像在列表或元组中那样使用数字索引来访问条目,而是使用它们的键来访问它们的值。这样,当我们想要使用特定的关键字访问记录时,就可以使用字典,比如销售记录或登录。这比列表或元组占用更多的空间,但是如果您知道可以用来访问特定项的值的键,它可以允许更有效的搜索。

履行

由于键-值关系,实现字典比创建列表、集合或元组要复杂一些。在这方面有两种主要方式:

  • 使用{}符号,但是当键和值被一个:分开时,比如{key:value}
  • 使用dict()函数,但是只有当我们有一个包含两个条目的元组列表时

这里的关键是确保我们可以使用key:value结构创建一个字典,如下所示:

#create a dict using {}
new_dict = {"Name":"Peter Jones",
           "Age":28,
           "Occupation":"Data Scientist"}#create two lists
keys = ["Name", "Age", "Occupation"]
values = ["Sally Watson", 30, "Technical Director"]
#zip them together
zipped_lists = zip(keys, values)
#turn the zipped lists into a dictionary
new_dict2 = dict(zipped_lists)#print the results
print(new_dict)
print(type(new_dict))print("\n")print(new_dict2)
print(type(new_dict))#out:
{'Name': 'Peter Jones', 'Age': 28, 'Occupation': 'Data Scientist'}
<class 'dict'>

{'Name': 'Sally Watson', 'Age': 30, 'Occupation': 'Technical Director'}
<class 'dict'>

我们可以看到,我们已经能够使用这两种方法创建一个字典。

数据类型

我们在上面已经看到,像列表、元组和集合一样,字典可以包含多种不同的数据类型作为值,但是它们也可以将不同的数据类型作为键,只要它们不是可变的,这意味着您不能使用列表或另一个字典作为键,因为它们是可变的。我们可以将此视为:

mixed_dict = {"number":52,
             "float":3.49,
             "string":"Hello world",
             "list":[12, "Cheese", "Orange", 52],
             "Dictionary":{"Name":"Jemma",
                          "Age":23,
                           "Job":"Scientist"}}print(type(mixed_dict))#out:
<class 'dict'>

是有效的字典。

访问项目

访问字典中的条目不同于访问列表或元组中的条目,因为现在我们不是使用它们的数字索引,而是使用键来访问值。这意味着我们可以像以前一样使用[]符号,但主要有两种方式:

  • 使用[]符号来指定我们想要访问其值的键
  • 使用get()方法来指定我们想要访问的键的值

这两个方法的主要区别在于,如果键不在字典中,第一个方法会引发一个问题,而如果键不在字典中,第二个方法会简单地返回None。当然,方法的选择将取决于您想要对结果做什么,但是每种方法都可以按如下方式实现:

#the first way is as we would with a list
print(new_dict["Name"])#however we can also use .get()
print(new_dict.get("Name"))#the difference between the two is that for get if the key
#does not exist an error will not be triggered, while for 
#the first method an error will be
#try for yourself:
print(new_dict.get("colour"))#out:
Peter Jones
Peter Jones
None

以这种方式访问信息意味着我们不能有重复的键,否则,我们就不知道我们在访问什么。虽然在初始化字典时创建重复的键不会产生错误消息,但它会影响您访问信息的方式。例如:

second_dict = {"Name":"William",
              "Name":"Jessica"}print(second_dict["Name"])#out:
Jessica

我们可以在这里设置两个"Name"键,当试图访问信息时,它只打印第二个值,而不是第一个值。这是因为第二个密钥会覆盖第一个密钥值。

易变的

与列表和集合一样,与元组不同,字典是可变的,这意味着一旦创建了字典,我们就可以更改、添加或删除条目。我们可以用类似的方式来实现这一点,即访问单个项目并使用变量赋值(=)来改变实际值。我们也可以添加新的key:value对,只需指定一个新的键,然后给它赋值(或者不赋值)。最后,我们可以使用update()方法向现有字典添加一个新字典。这些都可以表现为:

#create the dictionary
car1 = {"Make":"Ford",
       "Model":"Focus",
       "year":2012}#print the original year
print(car1["year"])#change the year
car1["year"] = 2013#print the new car year
print(car1["year"])#add new information key
car1["Owner"] = "Jake Hargreave"#print updated car ifnormation
print(car1)#or we can add another dictionary 
#to the existing dictionary using the update function
#this will be added to the end of the existing dictionary
car1.update({"color":"yellow"})
#this can also be used to update an existing key:value pair#print updated versino
print(car1)#out:
2012
2013
{'Make': 'Ford', 'Model': 'Focus', 'year': 2013, 'Owner': 'Jake Hargreave'}
{'Make': 'Ford', 'Model': 'Focus', 'year': 2013, 'Owner': 'Jake Hargreave', 'color': 'yellow'}

因此,我们可以改变字典中包含的信息,尽管除了删除它之外,我们不能改变实际的键。因此,我们可以看到如何从字典中删除条目。

要从字典中删除条目,我们可以使用del方法,尽管我们必须小心,否则我们可能会删除整个字典!我们也可以使用pop()方法来指定我们想要从字典中删除的key。这种方法的一个扩展是popitem()方法,在 Python 3.7+中,该方法从字典中删除最后一项(在此之前,它是一个随机项)。最后,我们可以使用clear()方法从字典中删除所有值。这些可以通过以下方式实现:

scores = {"Steve":68,
         "Juliet":74,
         "William":52,
         "Jessica":48,
         "Peter":82,
         "Holly":90}#we can use the del method
del scores["Steve"]
#although be careful as if you don't specify 
#the key you can delete the whole dictionaryprint(scores)#we can also use the pop method
scores.pop("William")print(scores)#or popitem removes the last time 
#(although in versinos before Python 3.7 
#the removes a random item)
scores.popitem()print(scores)#or we could empty the entire dictionary
scores.clear()print(scores)#out:
{'Juliet': 74, 'William': 52, 'Jessica': 48, 'Peter': 82, 'Holly': 90}
{'Juliet': 74, 'Jessica': 48, 'Peter': 82, 'Holly': 90}
{'Juliet': 74, 'Jessica': 48, 'Peter': 82}
{}

附加功能

因为我们有一个不同于列表、元组或集合的结构,所以字典也有自己的功能来处理它。值得注意的是,您可以使用keys()方法从字典中提取所有键的列表,使用values()方法从字典中提取所有值的列表,使用items()方法提取key:value对的元组列表。最后,你可以使用len()函数来提取字典的长度。我们可以将此视为:

dictionary = {"Score1":12,
             "Score2":53,
             "Score3":74,
             "Score4":62,
             "Score5":88,
             "Score6":34}#access all the keys from the dictionary
print(dictionary.keys())#access all the values form the dictionary
print(dictionary.values())#access a tuple for each key value pair
print(dictionary.items())#get the length of the dictionary
print(len(dictionary))#out:
dict_keys(['Score1', 'Score2', 'Score3', 'Score4', 'Score5', 'Score6'])
<class 'dict_values'>
dict_items([('Score1', 12), ('Score2', 53), ('Score3', 74), ('Score4', 62), ('Score5', 88), ('Score6', 34)])
6

这使您能够检查一个条目是在键中,还是在值中,或者同时在两者中,同时还可以检查字典的列表。

因此,这是一本相当完整的字典指南。字典的独特性质是具有键:值结构,这增加了字典的存储容量,但是如果您知道与值相关联的键,它可以使检索更加准确,这使它成为存储分层信息或跨数据源的信息的有价值的结构。这样,它们为更复杂的数据存储方法奠定了基础,如 Pandas DataFrames、JSON 等。

这是探索数据结构及其在 Python 中的使用和实现系列的第四篇文章。如果您错过了列表、元组和集的前三个,您可以在以下链接中找到它们:

本系列的后续文章将涵盖 Python 中的链表、栈、队列和图形。为了确保您将来不会错过任何内容,请注册以便在发布时收到电子邮件通知:

https://philip-wilkinson.medium.com/subscribe

如果你喜欢你所阅读的内容,并且还不是一个媒体成员,考虑通过使用我下面的推荐代码注册来支持我自己和这个平台上其他了不起的作者:

https://philip-wilkinson.medium.com/membership

初学者 git 完全指南

原文:https://towardsdatascience.com/a-complete-guide-to-git-for-beginners-a31cb1bf7cfc

键盘命令、功能和用法

照片由 Praveen ThirumuruganUnsplash 上拍摄

Git 是所有数据科学家和软件工程师都应该知道如何使用的工具。无论您是单独从事一个项目,还是作为大型分布式团队的一部分,了解如何有效地使用 Git 都可以为您的项目节省大量时间。git 的好处是,它可以作为您工作的版本控制系统,这样您就可以放心地进行编辑、更改和添加新功能,因为您知道如果需要的话,您总是可以回到您工作的前一个版本。这让开发人员和数据科学家有信心继续开发他们的工作,并在现有项目和分析中引入新的系统、功能和工作流,同时能够确保始终有一个工作版本。

本文是对 git 初学者的介绍,涵盖了有效使用 git 所需的基本命令和功能。本文涵盖的主题包括:

  1. 什么是饭桶?
  2. 设置存储库
  3. 进行修改
  4. 创建特征
  5. 修复 bug 或错误
  6. 附加功能
  7. 结论

Git 是什么?

首先,什么是 git?嗯,根据 Git 网站:

Git 是一款 免费开源 分布式版本控制系统,旨在快速高效地处理从小到大的各种项目。

这意味着它是一个管理文件版本的软件系统,主要是代码文件,这样您就不必处理诸如“文件 v.2”、“文件 v.3”和“文件 final final”等版本问题。相反,使用 git 提交使得文件名保持不变,但是如果需要的话,您可以返回到文件的前一个版本,或者多个文件。这避免了相当混乱的工作目录。

设置存储库

git 使用存储库工作,存储库通常由一个单独的文件夹表示,如果需要,还可以有子文件夹。您与 git 的第一个交互将是将您的工作文件夹转换成 git 存储库,允许您利用 git 提供的所有功能。

我们可以通过创建一个新的空文件夹,然后设置一个本地存储库来展示这一点。这假设您已经在系统上安装了git。为此,我们可以使用mkdir命令创建一个新文件夹,如下所示:

mkdir new_project

然后,我们可以使用命令行导航到该文件夹:

cd new_project

您的命令行现在应该显示您位于new_project目录中。一旦出现这种情况,我们就可以继续在文件夹中初始化一个新的 git 存储库。这个命令相对简单,用init命令如下:

git init

一旦你这样做了,一个.git文件夹应该出现在你的目录中(虽然现在看不到)。此外,根据您安装的 git 版本,mastermain应该出现在命令行中您当前所在的目录之后。如果是这样,您现在应该有一个工作的 git 存储库,这样您就可以利用 git 的所有关键功能了!

做出改变

git 的主要好处是能够有效地管理您创建和编辑的文件的不同版本。Git 允许您跟踪所做的更改,同时如果需要的话,还可以返回并编辑或删除它们。这让开发人员有信心进行编辑和更改,而不用担心丢失以前的工作。

要做到这一点,您需要知道如何添加您创建的文件以及您对 git 所做的更改,然后将这些更改存储在系统中。这个过程的第一步是“暂存”文件,这样 git 知道您在特定文件中所做的更改将被记住(提交)。为此,您只需使用以下命令:

git add <filename>

这将“暂存”您在该文件中所做的任何更改,直到您使用该命令。这些变化将会出现在一个“暂存”区域,但是还不会发生任何变化。

为了让 git 区分您希望它记住的对文件的更改,您需要将这些更改“提交”到您的存储库中。“提交”可以清楚地区分你所做的改变,让你能够找到你感兴趣的具体改变。当您提交更改时,您有效地将这些更改保存到本地存储库中,并附带一条消息,说明这些更改意味着什么,无论是功能、代码结构的更改还是新特性的创建。用于此目的的命令是:

git commit -m "<insert clear message here">

这将提交您之前使用add命令进行的所有更改。

重要的是要确保每个提交都有一个明确的附加消息,以便您可以在必要时返回并找到它。虽然这对你来说似乎不那么重要(我当然记得那是什么变化!)如果你是团队的一员,有人可能会看到你所做的改变,这一点很重要。这意味着尽可能简洁明了地描述提交的目的通常是最佳实践。

此时,您在提交中所做的所有更改都将存储在您的本地 git 存储库中。这对于版本控制来说更好,但是如果您的笔记本电脑突然没电了,您想使用不同的笔记本电脑工作,或者您是团队的一员,这可能是一个问题。为此,您可能希望将您机器上的本地存储库链接到一个远程存储库,您或任何人只要有互联网连接就可以从任何地方访问它。

有一些服务可以用于远程存储库,但最常用的是 Github。为了能够将您的本地存储库连接到远程 github one,您需要一个 GitHub 帐户,并且已经设置了一个远程存储库来链接您的本地存储库。一旦设置了远程存储库,您将需要找到远程存储库 URL,它看起来应该像这样:

作者图片

要简单地将本地存储库与远程存储库连接起来,可以使用以下命令添加链接:

git remote add origin <REMOTE URL>

为了验证已经建立了连接,使用git remove -v,它应该会打印出您指定的远程 URL。如果它显示正确的 URL,那么您现在连接到了一个远程存储库!

此时,当您连接到远程存储库时,到目前为止您提交的更改实际上还不会出现在那里。这是因为您必须告诉您的本地存储库,您想要将您的提交“推”到远程存储库。这仅仅需要使用git push命令:

git push

这应该会将您所做的所有提交推送到远程存储库,这意味着您可以从世界上的任何地方访问您所创建的代码!当然前提是你能上网。通过刷新您的远程存储库来确保这已经生效,并确保您所做的更改也出现在那里。

如果您以后想要获取您或者其他人在存储库中所做的任何更改,您可以简单地使用git pull命令。这意味着任何其他人都可以对您的代码做出贡献,并且您可以将这些更改带到您的本地存储库中!

创建特征

特征分支

作为任何项目的一部分,根据您正在开发的产品,将需要添加新的功能或分析。在某些情况下,您不希望在您的存储库的main分支上创建那个特性,因为您想先看看它是如何工作的以及它是否成功。那么最好的方法就是用 git 创建工作分支,将工作流相互分离,确保您的main分支保持“干净”和工作。

分支允许您对您的工作进行增量变更,而不需要对您的存储库的main分支进行变更。维护一个稳定和干净的main分支允许一个连续的稳定构建线,只有当新特性稳定并准备集成到产品中时,新特性才会被添加进去(至少在理论上是这样的!).

可以使用以下命令创建分支:

git branch <branch_name>

然后您可以使用checkout命令导航到其中:

git checkout <branch_name>

如果这样做了,现在应该显示<branch name>,而不是在命令行中显示mastermain,表明您当前正在处理您的分支。这个新分支将把来自main分支的最后一次提交作为它的“根”,你在这个分支中所做的任何改变都将建立在这个原始点上。

这一切都很好,但是当您完成了新特性的创建之后呢?你是如何将这些令人惊奇的工作放回“主”分支的?嗯,主要有两种方法:合并重置

合并

将分支引入到main中最常用的方式,也是最先学会的方式是通过合并。本质上,这就像它所说的那样,将特性分支“合并”到一个现有的分支中,但是这将通过在您的存储库中创建一个新的提交来实现。这样做的好处是,它将维护您已经提交的历史和顺序,包括清楚地标识分支的根在哪里。

要合并一个分支,您需要在您想要将提交引入的分支中。这意味着如果你想将一个特性分支合并到main中,那么你需要检查main分支。然后,您可以使用该命令:

git merge <branch_name>

然后会要求您添加一条提交消息,因为合并过程将通过提交来完成。一个常见的基本提交消息通常是:“merge <branch_name>into main ”,它简单地说明了它做了什么。</branch_name>

如果您使用gitk查看 git 提交历史,您应该会看到类似这样的内容:

作者图片

在第二次提交后特性分支从master分支出来的地方,一个提交给主分支,两个提交给特性分支,并且它们与新提交的创建合并。

重设基础

在特性分支中进行合并的替代方法是改变提交的基础。这将从特性分支获取提交,并简单地将它们添加到主分支的顶部。与合并相比,这样做的好处是确保线性提交历史清晰易读。缺点是提交的历史变得不太清楚。

与合并不同的是,重定基础是从你想要移动的分支执行的,在这种情况下是特征分支。这意味着您需要确保已检出您创建的要素分支,并运行以下命令:

git rebase <main branch name>

其中

可以是 master 或者 main,这取决于您正在使用的 git 版本,甚至是您想要添加提交的另一个分支。然后,您应该会看到这样的内容:

作者图片

为了确保主分支与特性分支所做的更改保持一致,您需要使用以下命令将主分支指针向上拖动到rebase_branch的最新提交:

git branch -f master rebase_branch

此时,rebase_branch 和 master 分支应该指向同一个提交,并且您已经将 rebase_branch 中的所有提交都 rebase 到了 master 中。当您签出主分支时,不能运行此操作,否则强制将不起作用,因为您不能移动当前正在处理的分支。

修复 bug 或错误

有时候在开发时,你会想要撤销你在之前的提交中所做的更改。这可能是由于引入了错误,您所做的更改是多余的,或者您只是想撤销以前所做的。有三个命令可以帮你做到这一点:resetrevertamend。这些命令可以撤销代码或错误,并让开发人员有信心在第一时间提交代码,因为更改总是可以撤销的。

Git 复位

git reset是将分支的当前 tup 移回到先前提交的 tup 的一种方式。这样做可以从当前分支中删除不再需要的提交,或者撤销已经做出的任何更改。

要运行这个,你只需使用git reset和你想重置回。一个例子是将当前分支从分支的当前HEAD向后移动两次提交,使用:

git reset HEAD~2

这将更改提交历史记录:

作者图片

收件人:

作者图片

其中提交 B 和 C 基本上已经从该历史中移除。但是,这对存储库本身的影响将取决于该命令使用的标志:

  • --soft将重置提交,但会将这些提交的更改保存在目录中,并保存这些更改以备重新提交。
  • --mixed(默认)将重置提交,并将提交的更改保存在目录中,但不会将更改存放到提交中。
  • --hard将从目录中完全删除提交中所做的所有更改,就好像它们从未发生过一样,这是最极端的重置。

重要的是git reset改变了提交历史,当提交已经公开时,应该很少使用它。

Git 回复

有时,您已经提交了想要更改的提交。在这种情况下,应该使用git revert。这是因为git reset通过创建一个新的提交来撤销提交的更改,从而保留了之前的提交历史。当提交已经被推送时会更好。

由于git revert没有改变历史,您需要指定您想要撤销的提交。为此,您需要找到想要撤销的提交的提交引用。这可以使用git loggit reflog找到。一旦你有了那个参考,你简单的使用:

git revert <commit reference>

撤消提交中所做的更改。例如,如果我们想要从下面的提交B中恢复更改,对存储库的影响看起来会像这样:

作者图片

其中进行新的提交,撤销 b 中的所有改变。这不改变任何先前的提交,并且保持干净的提交历史。

Git 修正

在某些情况下,您只想更改属于先前提交的代码。在这种情况下,使用git resetgit revert似乎有点愚蠢。相反,您可以利用git amend,它允许您对之前的提交进行更改。

使用它的一个简单方法是更改提交消息,如果你输入错误或者不够清楚的话。为此,您只需使用:

git commit --amend -m "<new commit message>"

这将用新的提交消息替换旧的提交消息。

然而,在其他情况下,您希望更改上次提交的部分代码,例如删除代码或添加更多代码。添加新文件很简单,一旦修改已经完成,就可以使用git commit --amend来完成。这将把更改添加到以前的提交中,而不是创建一个新的。这方面的工作流示例如下所示:

git add some_amazing_python.py
git commit -m "Some amazing work"#edit the files to remove an unecessary import
#and make it cleanergit add some_amazing_python.py
git commit --amend --no-edit

这里添加了--no-edit标志,因为我们不想改变提交消息,只改变文件。

附加功能

git 具有广泛的功能,超出了像本文这样的小文章所能涵盖的范围。然而,另外两个有用的附加功能是git cherry-pickgit stash,因为它们经常在许多 git 工作流中使用。

精挑细选

git cherry-pick是一个有用的工具,它允许您从存储库中的任何地方复制单个提交的更改,并将其放在当前分支的开头。这可能是有用的,例如当您在不同的分支上共同处理类似的功能时,以减少生产中的错误数量,或者从代码库中的其他地方引入额外的功能。

要使用git cherry-pick,您需要知道您感兴趣的变更的提交引用,然后使用:

git cherry-pick <commit reference>

这将把在指定提交中所做的更改放在当前分支的顶部。

Git stash

在其他情况下,你可能正在做一些代码,但是你不得不中途停下来做其他的事情。这可能是因为你的同事在一个 bug 上需要帮助,你有其他任务要完成,或者你想暂时做点别的事情。您所做的更改可能还没有准备好提交,但是您可能不想丢失您所做的更改。为此,可以使用git stash来保存未提交的更改,您稍后可以访问这些更改,而无需进行新的提交。

要想藏起来,你只需打电话:

git stash

这将从您对暂存和跟踪文件所做的所有更改中创建一个存储库。这将把它们从当前工作目录中删除,并恢复到上次提交时的状态。

一旦您创建了存储并想要返回工作,您可以使用两个不同的命令来取回更改:

  • git stash apply——它将获取您存储在存储库中的更改,将它们应用到当前分支上的当前工作目录中,但会保持存储库完整,以备将来使用。
  • git stash pop —它还会将您存储在存储库中的更改应用到正确分支上的当前工作目录中,但在应用更改后会删除存储库。

虽然这很有用,但不应该经常使用,因为存储不像实际提交那样容易维护。

结论

Git 是一个非常有用的工具,所有数据科学家和软件工程师都应该知道如何有效地使用它。这至少应该包括知道如何建立一个存储库,将它连接到一个远程数据源,创建和管理特性分支,以及能够在必要时撤销任何以前的提交。了解所有这些应该会给你信心去创建代码,并成为团队中一个有效的成员。

这是一篇高度概括的文章,总结了在《走向数据科学》中已经发表的其他几篇文章。有关这些主题的详细解释,请访问以下链接:

Python 图形完全指南

原文:https://towardsdatascience.com/a-complete-guide-to-graphs-in-python-845a0a3381a1

在 Python 中构造和使用图形数据结构

照片由 JJ 英Unsplash

介绍

用编程术语来说,图是一种抽象数据类型,它充当数据元素的非线性集合,包含关于元素及其相互连接的信息。这可以用 G 来表示,其中 G = (V,E)V 表示一组顶点, E 是连接这些顶点的一组边。这些边可以简单地以[1,0]的形式表示顶点之间的关系,无论是否存在连接,或者可以表示诸如距离、关系强度或它们交互次数之类的值的给定权重。当我们想要映射对象之间的关系时,特别是当有多个关系时,以及当我们想要搜索完成任务流的最佳方式时,这可能是有用的。

在 Python 中实现这种抽象数据类型有两种常见的方法。第一个是邻接矩阵,第二个是邻接表。出于我们的目的,我们将实现邻接表形式,但有必要理解为什么会这样。

邻接矩阵

二维数组形式的邻接矩阵是将图实现为数据结构的最简单方法之一。这将表示为:

作者图片

其中每一行和每一列代表图中的一个顶点。存储在代表行 v 和列 w 的交叉点的单元中的值指示是否存在从顶点 v 到顶点 w 的边,并且大小代表“重量”。当这些顶点中的两个由一条边连接时,我们可以说它们是相邻的。

邻接矩阵实现的优点是简单,对于小图易于可视化。然而,如果许多单元格是空的,那么我们有一个“稀疏”的矩阵,所以我们使用大量的存储来显示很少的信息。因此,这种实现的问题是所使用的内存存储量。

邻接表

实现稀疏连通图的一个更节省空间的方法是使用邻接表。在这个实现中,我们保存一个图形对象中所有顶点的主列表,然后每个顶点对象维护一个它所连接的其他顶点的列表。

这种实现的优点是,我们可以紧凑地表示稀疏图,以便我们只显示图中包含的顶点以及它们的实际连接,而不是也表示没有连接。使用这种方法还可以让我们轻松地找到所有直接连接到单个顶点的链接。

履行

记住这一点,知道我们希望数据结构如何包含以及我们可能希望如何与它交互,我们可以考虑我们希望如何在实际的数据结构中实现这一点。通常与此相关的主要方法包括:

  • addVertex(vert):在 id 等于 key 的图形中添加一个vertex
  • addEdge(fromVert, toVert):向连接两个顶点的图形添加新的有向边
  • getVertex(vertKey):在名为vertKey的图中找到顶点
  • getVertices():返回图形中所有顶点的列表
  • 如果顶点在图中,则in返回形式为vertex in graph的语句的True,否则返回False

在 Python 中实现这种抽象数据类型有两种常见的方法。第一个是邻接矩阵,第二个是邻接表。出于我们的目的,我们将实现邻接表的形式,但它是值得理解为什么是这样的情况。

顶点类

在创建包含所有顶点的图之前,我们需要创建一个顶点类来表示图中的每个顶点。在这个实现中,每个顶点使用一个字典来跟踪它以connectedTo属性的形式连接到的顶点。当我们创建一个新的顶点时,我们用键的id初始化这个顶点,键通常是一个代表这个顶点的字符串,还有一个空的connectedTo字典。我们还必须向这个Vertex类添加功能,这样我们就可以向我们的字典添加邻居,

Class Vertex: def __init__(self, key):
        self.id = key
        self.connectedTo = {} def addNeighbor(self, nbr, weight = 0):
        self.connectedTo[nbr] = weight def __str__(self):
        return f"{str(self.id)} connected to: {str([x.id for x in
                  self.connectedTo])}" def getConnections(self):
        retun self.connectedTo.keys() def getId(self):
        return self.id def getWeight(self, nbr):
        return self.connectedTo.get(nbr)

图形类

在这种情况下,我们实现图形的方式是,图形包含一个将顶点名称映射到顶点对象的字典。这意味着在构造时,我们只需要创建一个空字典,当它们被添加到图中时,可以用来存储Vertex对象。我们还可以添加一个numVertices属性,它将存储图中顶点的数量,以便在以后需要时可以很容易地提取出来。

class Graph: def __init__(self):
        self.vertList = {}
        self.numVertices = 0

现在我们有了我们的图,我们要添加的第一件事是给我们的图添加一个顶点。在这个简单的实现中,我们将只关注添加一个具有给定 id 的顶点。为此,我们需要使用键的 id 创建一个新的顶点类,使用键作为vertList字典中的键,并将创建的顶点作为值添加到字典中。然后我们可以返回它,这样用户就可以看到他们创建的顶点:

 def addVertex(self, key):
        """
        Add a vertex to the Graph network with the id of key Time complexity is O(1) as we are only adding a single
        new vertex which does not affect any other vertex
        """ #add 1 to the number of vertices attribute
        self.numVertices += 1 #instantiate a new Vertex class
        newVertex = Vertex(key) #add the vertex with the key to the vertList dictionary
        self.vertList[key] = newVertex #return the NewVertex created
        return newVertex

如果你想看一个顶点本身呢?

然后我们可以创建一个方法,如果有匹配的键,它将返回实际的顶点。我们可以使用字典的get()方法来实现,因为如果关键字不在字典中,它将返回None,否则将返回实际的顶点。这将允许用户查看顶点包含的内容,然后查看图形中其他顶点的链接。

 def getVertex(self, key):
        """
        If vertex with key is in Graph then return the Vertex Time complexity is O(1) as we are simply checking whether
        the key exists or not in a dictionary and returning it
        """ #use the get method to return the Vertex if it exists
        #otherwise it will return None
        return self.vertList.get(key)

作为补充,我们可以使用关键字in来检查一个顶点是否在图中。为此,我们使用了__contains__邓德方法,该方法允许用户使用vertex in graph符号检查一个顶点是否在字典中。因为每个键代表一个顶点,那么我们可以简单地返回key是否在vertList中,这将返回TrueFalse

 def __contains__(self, key):
        """
        Check whether vertex with key is in the Graph Time complexity is O(1) as we are simply checking whether 
        the key is in in the dictrionary or not """ #returns True or False depending if in list
        return key in self.vertList

图的一个重要属性是顶点之间的连接或边,它包含了顶点如何连接的信息,无论是它们的距离还是另一种度量。为此,我们可以创建addEdge函数来添加一条连接两个顶点的边,默认权重为 0。

下面我们实现它的方式是建议一个有向图,因为连接只从顶点f到顶点t进行,然而这可以容易地扩展到包括两个方向的连接。我们需要确保的是顶点ft实际上都在图中,所以如果它们还不存在,那么我们可以调用已经创建的addVertex()方法来创建这些顶点。然后我们可以从每个顶点使用addNeighbor()方法来添加从ft的邻居。

 def addEdge(self, f, t, weight = 0):
        """
        Add an edge to connect two vertices of t and f with weight
        assuming directed graph Time complexity of O(1) as adding vertices if they don't 
        exist and then add neighbor
        """ #add vertices if they do not exist
        if f not in self.vertList:
            nv = self.addVertex(f)
        if t not in self.vertList:
            nv = self.addVertex(t) #then add Neighbor from f to t with weight
        self.vertList[f].addNeighbor(self.vertList[t], weight)

我们现在有了图的主要功能,而不是能够添加顶点,检查它们是否存在,以及能够添加顶点之间的连接。因此,我们可以开始添加额外的功能来帮助使用数据结构。

第一个是能够返回图中顶点的完整列表。这将简单地打印出所有的顶点名称,但是当我们想要快速浏览当前结构的图形时,这是很有用的。

 def getVertices(self):
        """
        Return all the vertices in the graph Time complexity is O(1) as we simply return all the keys
        in the vertList dictionary
        """

        return self.vertList.keys()

第二是能够返回图中顶点的数量。这很简单,因为我们在构造函数方法中创建了numVertices属性,然后每当我们调用addVertex方法时,我们就给它加 1。因此,我们简单地返回这个属性来显示图中有多少个顶点。

 def getCount(self):
        """
        Return a count of all vertices in the Graph

        Time complexity O(1) because we just return the count
        attribute
        """ return self.numVertices

然后,我们可以将所有这些放在一起,得到最终产品:

当然,还有其他方法可以扩展这种实现,包括:

  • 能够迭代每个顶点
  • 能够从网络中移除顶点
  • 能够移除或改变网络中的边

但是这个简单的实现可以为这些添加奠定基础。

结论

图是一种非常通用的数据结构,可以用各种方式来表示各种问题。这可以包括一个道路网络,一个友谊网络或者一个棋子在棋盘上的移动。它可用于搜索算法,如广度优先搜索或深度优先搜索,或提取不同图形的共同属性,如最短路径、网络直径或某些节点的中心性。上面的实现经过一些调整就可以用在这些例子中,所以您现在已经有了更详细地研究这些的基础!

这是探索数据结构及其在 Python 中的使用和实现系列的第 8 篇文章。如果您错过了前三篇关于 Python 中的队列、链表和堆栈的文章,您可以在以下链接中找到它们:

本系列的后续文章将涉及链表、队列和图形。为了确保您不会错过任何内容,请注册以便在发布时收到电子邮件通知:

https://philip-wilkinson.medium.com/subscribe

如果你喜欢你所读的,但还不是一个中等会员,那么考虑使用我下面的推荐代码注册,在这个平台上支持我自己和其他了不起的作者:

https://philip-wilkinson.medium.com/membership

感谢您的阅读!

Python 中列表的完整指南

原文:https://towardsdatascience.com/a-complete-guide-to-lists-in-python-d049cf3760d4

关键特性、实现、索引、切片、定位项目、可变性和其他有用的功能

马库斯·温克勒在 Unsplash 上拍摄的照片

Python 中的列表

Python 中经常遇到的第一种数据结构是列表结构。它们可以用来在单个变量中存储多个项目,并有效地像您所期望的那样,像您自己的购物清单一样工作。它们是 Python 中四种内置数据类型之一,可用于存储数据集合以及元组、集合和字典。

它们的主要特点是:

  • 可变的:一旦它们被定义,就可以被改变
  • 有序:除非明确改变,否则它们保持它们的顺序
  • 可索引:如果我们知道它们在列表中的位置,它们保持它们的顺序的事实允许我们访问特定的项目

它们也可能包含重复的记录。这一点很重要,因为它们会影响列表在程序中的使用方式,因为列表可以更改的事实可能意味着如果您希望数据是固定的,您可能不想使用它们,或者您可以通过索引访问信息的事实可能对以后的信息检索有用。

创建列表

要创建列表,我们可以使用两种主要方法:

  • 使用[]将我们想要包含在列表中的内容用逗号分隔
  • 使用list()函数,该函数可用于将其他数据结构转换为列表或以列表作为参数

这些可以简单地实现为:

#create a list of Fruit using the [] notation
fruit_list = ["Apple", "Banana", "Peach"]#creating a list of vegetables using the list() nottation
vegetable_list = list(["Pepper", "Courgette", "Aubergine"])

在使用list()符号时,我们不得不使用[],因为函数只能接受一个参数,当我们想将其他数据类型转换成列表时,这很有用。

然后,我们可以使用type()函数检查这些结果,以确保它们是列表,并打印出列表本身的结果:

#examine the fruit list
print(type(fruit_list))
print(fruit_list)#print a seperate line
print("\n")#print the vegetable list
print(type(vegetable_list))
print(vegetable_list)#out:
<class 'list'>
['Apple', 'Banana', 'Peach']

<class 'list'>
['Pepper', 'Courgette', 'Aubergine']

我们可以看到,这两者的class属性以列表的形式给出。我们还可以看到,当我们打印出列表时,它们被打印在方括号中,并且与我们输入它们的顺序相同,这表明它们确实是列表,并且它们是有序的。

列表中的数据类型

列表的一个关键属性是它们可以在一个实例中包含所有不同类型的数据类型。尽管我们在上面只使用了字符串,但我们也可以在列表中输入数字或浮点数,例如:

#create a list of just numbers
num_list = [1, 2, 3, 4]#create a list of just floats
float_list = [1.2, 2.3, 4.5, 6.8]#print the results
print(type(num_list))
print(num_list)print("\n")print(type(float_list))
print(float_list)#out:
<class 'list'>
[1, 2, 3, 4]

<class 'list'>
[1.2, 2.3, 4.5, 6.8]

我们还可以在同一个列表中输入不同的数据类型,包括列表中的列表!

#different list
random_list = ["Hello", 3, "Cheese", 6.2, [1,2,3]]#print the result
print(type(random_list))
print(random_list)#out:
<class 'list'>
['Hello', 3, 'Cheese', 6.2, [1, 2, 3]]

这样做的好处是,对于更复杂的工作流,我们可以创建 2D 甚至 3D 列表,其中列表中有列表,这可以让我们可视化复杂的关系或模式。

索引

列表的一个重要部分是它们是有序的数据集合。这意味着他们有一个明确定义的顺序,输入到列表中的数据保持不变,除非我们告诉改变顺序。

这种顺序允许我们访问列表中的值,我们知道这些值在该顺序中处于固定位置。例如,如果我们根据每周购物时经过的地方对水果列表进行排序,这样我们就知道列表中的第一个水果将首先出现,而不是它是什么,我们可以简单地使用列表的第一个索引来访问它。当然,因为这是 Python,所以一切都以 0 索引开始,所以我们可以用:

#access the first item from the list
print(fruit_list[0])#out:
Apple

为此,方括号用于放入索引号,这样[0]就是我们访问第一个索引的方式。

按照这个例子,列表中的第二项可以用list[1]访问,第三项用list[2]访问,依此类推。为此,任何解析为数字的内容都可以用来访问列表中的内容,只要该索引属于您试图访问的列表。如果您试图使用列表之外的索引,例如fruit_list[3],那么您将得到一个索引错误,告诉您该索引不在列表中。

这种索引的一个有用的特点是,我们不仅可以向前计数,就像我们对水果列表所做的那样,我们还可以向后计数。这意味着我们可以检查最后添加到列表中的项目,或者如果它们是有序的,那么最高/最低值是多少。例如,如果您创建了一个分数上升的列表,但您对第二大分数感兴趣,您可以这样访问它:

#create a list of scores
scores = [12,42,62,65,73,84,89,91,94]#extract the second highest score
second_highest_score = scores[-2]#print the result
print(second_highest_score)#out:
91

当然,在这样做的时候,不是也从 0 开始(这样会造成混乱),而是希望从-1 开始访问第一个条目,然后从希望访问的结尾开始进一步增加。

列表切片

使用索引来访问列表中的项目的一个好处是,我们还可以使用一个片一次访问多个元素。这一点很重要,因为片允许你通过使用符号list[start_index: end_index]来访问列表中的一系列条目,需要注意的是,片本身并不会返回结束索引。这些例子包括:

#second lowest to fifth lowest
print(scores[1:5])#print second lowest
print(scores[1:2])#print the fifth lowest to the highest
print(scores[5:])#print the third highest to the highest
print(scores[-3:])#print from beginning to end
print(scores[:])#print every 2nd 
print(scores[::2])#print the list in reverse
print(scores[::-1])#out:
[42, 62, 65, 73]
[42]
[84, 89, 91, 94]
[89, 91, 94]
[12, 42, 62, 65, 73, 84, 89, 91, 94]
[12, 62, 73, 89, 94]
[94, 91, 89, 84, 73, 65, 62, 42, 12]

几个不同的规则适用于此,因为:

  • 当使用[1:2]打印第二个最低的项目时,由于最终索引不包含在结果中,因此仅返回一个项目
  • 当打印时,我们没有指定一个结束索引,这就是为什么在第五个索引之后,包括第五个索引,整个列表都被打印出来
  • 当打印[::2]时,使用第二个:允许我们指定索引之间的跳转,这就是为什么每第二个项目被显示

同样重要的是要注意,一个切片将总是返回一个列表,即使它只包含一个项目。这一点很重要,这样您就可以知道产生了什么类型的输出,以及我们可以用它来做什么。

查找项目的位置

从上面我们可以看到,当我们知道项目的索引时,我们可以访问项目,但如果我们只知道列表包含项目,但不知道它在哪里,那该怎么办?例如,在我们的水果列表中,我们知道我们的列表中有一个香蕉,但我们忘记了它在哪里??我们可以使用index()方法找到该项目的位置,如下所示:

#find the index of banan
print(fruit_list.index("Banana"))#find the index of peach
print(fruit_list.index("Peach"))#out:
1
2

如果您忘记了项目的位置,或者如果您的列表中一个列表的顺序与另一个列表的顺序相关,这将非常有用。例如,如果一个分数列表链接到一个姓名列表,您可以找到该姓名的索引,然后使用该索引从另一个列表中访问他们的分数。

唯一的问题是,如果您拼错了项目或项目不在列表中,该方法将抛出一个错误,并停止代码运行。解决这个问题的一个简单方法是使用 if/else 语句,该语句可以使用:

if "Banana" in fruit_list:
    print("Banana is at index:", fruit_list.index("Banana"))
else:
    print("Banana not in list")#out:
Banana is at index: 1

易变性

关于列表的另一个重要的事情是它们是“可变的”,这仅仅意味着列表中的项目和它们的顺序是可以改变的。这可以包括改变单个结果,在列表中间插入新内容,甚至对它们进行排序。

要更改单个项目,我们需要使用该项目的索引,方法如下:

scores = [12,42,62,65,73,84,89,91,94]
#we can change the score at the second index#print the second lowest score
print("Original score:", scores[1])#reassign the score
scores[1] = 52#check the reassignment
print("Changed score:", scores[1])#out:
Original score: 42
Changed score: 52

这里我们知道项目的位置,但是您可以使用上面显示的索引方法来查找特定项目的位置。

我们还可以使用append()将新的分数添加到列表的末尾,或者如果我们想要在特定位置添加一个值,我们可以使用如下的insert()方法:

#we can add a new score at the end using the append function
print("Original scores", scores)#add new score
scores.append(67)#print the new scores
print("New scores", scores)#or add new scores in a specific position
scores.insert(3, 48)#print the newer scores
print("Newer scores", scores)#out:
Original scores [12, 52, 62, 65, 73, 84, 89, 91, 94]
New scores [12, 52, 62, 65, 73, 84, 89, 91, 94, 67]
Newer scores [12, 52, 62, 48, 65, 73, 84, 89, 91, 94, 67]

我们还可以使用remove()方法根据值从列表中删除值,或者使用pop()方法指定它们的位置,或者在极端情况下,我们可以使用clear()方法完全清除列表:

#we can remove a score from the list
print("Original scores", scores)#remove the score of 89
scores.remove(89)#print the new score
print("New scores", scores)#alternative methods for removal include:# the pop() method removes the specified index
# scores.pop(1)# If you do not specify an index the pop() method removes the last item
# scores.pop()# we can also completely clear the list
# scores.clear()#out:
Original scores [12, 52, 62, 48, 65, 73, 84, 89, 91, 94, 67]
New scores [12, 52, 62, 48, 65, 73, 84, 91, 94, 67]

其他有用的功能

列表在 Python 编程中非常有用,它们也是许多其他数据类型的基础。它们的一个有用的方面是它们的嵌入式方法和可以与它们一起使用的函数。

其中一种方法是使用len()函数相对容易地找到列表的长度:

print(len(scores))#out:
10

因此我们知道我们的数据集中有 10 个分数,这可以与循环遍历项目或访问数据中的特定索引结合使用。

当然,到目前为止,除了最初的实现之外,我们已经对scores列表进行了显著的修改。原始列表是按从小到大的顺序排列的(升序)。我们可以使用列表的其他功能来纠正这个问题,或者使用sort()方法,或者使用sorted()函数,如下所示:

#print current scores
print(scores)#we can assign the new sorted list to a new list as follows:
new_sorted_scores = sorted(scores)
print(new_sorted_scores)#or we can sort the list itself
scores.sort()
print(scores)#we can even sort it in descending order
scores.sort(reverse = True)
print(scores)
#or by using scores.reverse()#out:
[12, 52, 62, 48, 65, 73, 84, 91, 94, 67]
[12, 48, 52, 62, 65, 67, 73, 84, 91, 94]
[12, 48, 52, 62, 65, 67, 73, 84, 91, 94]
[94, 91, 84, 73, 67, 65, 62, 52, 48, 12]

其中我们是想要改变原始列表还是创建一个新列表将决定我们最终使用哪个功能。

最后,我们可能希望将列表添加到一起,我们可以简单地使用 python 的+功能,或者我们可以使用extend()方法将列表添加到现有列表,如下所示:

Names1 = ["Peter", "Geneva", "John"]
Names2 = ["Katie", "Suzie", "Scott"]#add lists together using +
added_names = Names1 + Names2
print(added_names)#add lists together by extending one list
Names1.extend(Names2)
print(Names1)#add the same list together by multiplying it by itself
double_names = Names2 * 2
print(double_names)#out:
['Peter', 'Geneva', 'John', 'Katie', 'Suzie', 'Scott']
['Peter', 'Geneva', 'John', 'Katie', 'Suzie', 'Scott']
['Katie', 'Suzie', 'Scott', 'Katie', 'Suzie', 'Scott']

这就是 Python 列表的完整指南!

这是探索数据结构及其在 Python 中的使用和实现的系列文章的第一篇。即将发表的文章将涵盖 Python 中的集合、元组、字典、链表、栈、队列和图形。为了确保您不会错过任何在发布时接收电子邮件通知的注册:

https://philip-wilkinson.medium.com/subscribe

如果您喜欢您所阅读的内容,并且还不是 medium 会员,请随时使用下面的我的推荐代码注册 medium,以支持我自己和这个平台上的其他作者:

https://philip-wilkinson.medium.com/membership

或者考虑看看我的其他媒介文章:

Python 中队列的完整指南

原文:https://towardsdatascience.com/a-complete-guide-to-queues-in-python-cd2baf310ad4

什么是队列以及如何用 Python 实现队列

Melanie Pongratz 在 Unsplash 上的照片

在编程术语中,队列是一种抽象数据类型,它存储项目被添加到结构中的顺序,但它只允许添加到队列的末尾,同时只允许从队列的前面移除。这样做时,它遵循先进先出(FIFO)数据结构。本质上,这就像你所期望的队列在现实中的行为一样,例如当排队进入商店时,那些先到的人会先到。然而与现实生活不同的是,我们以一种确保没有插队的方式来构建它!

Giphy 的 GIF

当我们希望将输出存储在一个结构中并保持其顺序,然后按照给定的顺序执行操作时,可以使用这些方法。编程中常见的例子包括为计算机的 CPU 调度作业、将打印机的作业排队以确保首先发送的作业被执行、为企业处理订单或处理消息。

记住这一点,知道我们希望数据结构做什么以及我们希望如何与它交互,我们就可以开始考虑如何在 Python 的实际数据结构中实现这一点。通常与此相关的主要方法包括:

  • enqueue(item):将项目添加到队列的末尾
  • dequeue():移除并返回队列前面的项目
  • peek():返回队列前面的项目,而不删除它
  • is_empty():如果队列为空,则返回 True
  • size():返回队列中有多少项

这意味着我们需要使用 Python 数据类型来构建它,这种数据类型是可变的,可以用来存储有序的项目集合。我们可以问自己的第一件事是,Python 中是否已经实现了一种数据类型可以为我们做到这一点?

Giphy 的 GIF

一份名单!我们可以用一个列表!

我们知道列表是可变的,它可以被改变,我们可以简单地从开始和结束添加和删除项目,使用列表的内置功能来实现队列。

在使用列表时,我们必须定义一个使用列表的类,但它只具有允许用户以我们想要的方式与之交互的特定功能。为此,我们可以使用 Python 的结构来定义数据结构将包含的信息以及我们想要使用的方法。

接下来要做的第一件事是将命名为Queue,并为这个新类创建构造函数。我们想要的是,一旦创建了对象的新实例,就初始化一个空列表,可以使用.items属性访问该列表。这方面的代码如下:

class Queue: #create the constructor
    def __init__(self): #create an empty list as the items attribute
        self.items = []

这意味着当我们创建一个Queue类的实例时,item属性将代表一个空列表。

然后我们可以开始向Queue类添加主要功能。我们希望能够做的第一件事是向我们的Queue添加一个项目。在这一点上,重要的是能够识别哪一端是我们从中获取数据的队列的前端,哪一端是我们可以向其中添加项目的队列的末端。

这暗示了每个过程将花费多长时间,因为在列表的开始处改变事物导致 O(n)的时间复杂度,因为我们随后必须改变列表中后续项目的所有索引以将所有内容向右移动。然而,如果我们操作列表末尾的内容,那么时间复杂度为 O(1 ),因为我们只在删除或添加项目时改变列表末尾的索引。

Giphy 的 GIF

然而,这并没有太大的关系,因为无论你选择从另一个函数中添加或删除哪一方,都仍然具有另一个时间复杂度。因此,记住这一点,我们可以实现enqueue(item)方法来将一个项目添加到我们的队列中。

 def enqueue(self, item):
        """
        Add item to the left of the list, returns Nothing Runs in linear time O(n) as we change all indices
        as we add to the left of the list
        """ #use the insert method to insert at index 0
        self.items.insert(0, item)

这只会将一个项目添加到我们队列的末尾(左侧)。在这种情况下,我们并不太关心我们在队列中添加了什么,只是我们可以添加它。当然,您可以向其中添加更多的功能,但是现在这样就可以了。

这意味着当我们想从队列类的列表右端移除项目时。在这样做的时候,我们需要注意一个相当简单的边缘情况,即我们的队列是否为空。在这种情况下,如果队列为空,我们可以简单地返回None,这样我们仍然会返回一些东西,程序不会抛出错误,但是如果队列不为空,我们可以返回队列中的“第一个”项并返回它。因为我们使用 Python 的内置 list 类,所以我们可以简单地使用.pop()方法,在 Python 3.6+中,该方法从列表中移除 ist 项并返回它。这在常数时间 O(1)内运行,因为我们只移除列表项,不影响任何其他索引。因此,这可以实现为:

 def dequeue(self):
        """
        Removes the first item from the queue and removes it Runs in constant time O(1) because we are index to
        the end of the list.
        """ #check to see whether there are any items in the queue
        #if so then we can pop the final item
        if self.items:

            return self.items.pop() #else then we return None
        else: return None

因此,我们实现了队列的主要功能,确保我们只在队列的一端添加项目,从队列的另一端移除项目。这将保持队列中项目的顺序,并确保我们按照它们被添加到队列中的顺序来使用这些项目。

然后,我们可以考虑在实际程序中帮助使用队列数据结构的其他补充方法。我们可以添加的第一个附加功能是允许用户查看下一个要从队列中删除的项目,而不需要实际删除它。它的实现将遵循与dequeue()方法相似的结构,但是我们可以暗示用列表中的最后一项来访问该项,而不是使用.pop()。这意味着当我们使用索引来访问条目时,我们的时间复杂度为 O(1 ),这使得它变得简单明了。

 def peek(self):
        """
        Returns the final item in the Queue without removal

        Runs in constant time O(1) as we are only using the index
        """ #if there are items in the Queue 
        if self.items:
            #then return the first item
            return self.items

        #else then return none
        else:
            return Non

我们还可以提供一种检查队列是否为空的方法。如果队列实际上是空的,这将简单地返回布尔值True,如果不是空的,则返回False。这也是在恒定时间内运行的,因为它只是检查Queue中的列表是否存在

 def is_empty(self):
        """
        Returns boolean whether Queue is empty or not Runs in constant time O(1) as does not depend on size
        """ return not self.items

我们可以创建一个返回队列大小的方法。这可以告诉我们队列的大小,就像它对于列表一样,并且告诉我们已经添加了多少项或者队列中还剩下多少项。

 def size(self):
        """
        Returns the size of the stack        Runs in constant time O(1) as only checks size
        """ #len will return 0 if empty
        #so don't need to worry about empty condition
        return len(self.items)

最后,我们要确保我们试图打印出一个Queue类的实例,它对于个人来说是可读的,既可以看到它是队列,也可以看到队列包含的内容。为此,我们使用该类的特殊的__str__ dunder 方法来告诉解释器我们想要如何打印出该类的一个实例。在这种情况下,我们只想返回包含在堆栈中的整个列表,可以实现为:

 def __str__(self):
        """Return a string representation of the Stack"""" return str(self.items)

唷!那么把这些放在一起怎么样?最终产品看起来像这样:

此时,您可能会想为什么我需要知道这些?这种数据结构在编程中的常见应用包括:

  • 在 CPU 中调度作业
  • 图遍历算法
  • 订单处理
  • 打印机队列

当你在构建这些程序时,了解这种数据结构在 Python 中是什么样子,并为你提供能够处理这些挑战的功能,这是很好的。

这也可能出现在软件工程师或数据科学面试中,如要求您构建一个使用队列的打印机,或创建图形遍历算法,如深度优先搜索。

现在,你知道了如何用 Python 实现队列,也知道了它的一些用途!

这是探索数据结构及其在 Python 中的使用和实现系列的第七篇文章。如果您错过了 Python 中的链表、栈和字典的前三部分,您可以在以下链接中找到它们:

本系列的后续文章将涉及链表、队列和图形。为了确保您不会错过任何内容,请注册以便在发布时收到电子邮件通知:

https://philip-wilkinson.medium.com/subscribe

如果你喜欢你所读的,但还不是一个中等会员,那么考虑使用我下面的推荐代码注册,在这个平台上支持我自己和其他了不起的作者:

https://philip-wilkinson.medium.com/membership

感谢您的阅读!

Python 中集合的完整指南

原文:https://towardsdatascience.com/a-complete-guide-to-sets-in-python-99dc595b633d

集合的关键特性、实现集合、访问项目、可变性和附加功能

照片由利比·彭纳Unsplash 上拍摄

Python 中的集合

继列表和元组之后,集合是 Python 中经常遇到的另一种常见数据结构,在工作流中有多种用途。它们可以用于在单个变量中存储多个项目,如列表或元组,但主要区别是它们是无序的,不能包含重复值。这意味着,当您只想存储唯一值,不关心维护数据结构中项的顺序,或者想要检查不同数据源之间的重叠和差异时,它们会很有用。这种数据结构的主要特征是:

  • 可变:一旦创建了集合,就可以对它们进行更改
  • 无序:器械包内物品的顺序没有被记录或保存
  • 未索引:因为它们是无序的,所以我们没有一个可以用来访问特定项目的索引
  • 不能包含重复值:集合不允许包含相同值的多个实例

这些特征会影响它们在程序中的使用。例如,当您希望保持向集合中添加项目的顺序时,您可能不希望使用它们,但是当您只关心添加唯一的项目并希望节省内存空间时,您可能希望使用它们。

履行

要创建集合,我们可以使用两种主要方法:

  • 使用{}将我们想要放入集合中的一组项目用逗号分隔开
  • 使用可用于将其他数据结构转换为集合的set()函数

这可以通过以下方式实现:

#create a set using curly brackets
fruits = {"apple", "banana", "cherry"}#create a set using the set constructor
vegetables = set(("courgette", "potato", "aubergine"))#print the results
print(fruits)
print(type(fruits))print("\n")print(vegetables)
print(type(vegetables))#out:
{'apple', 'banana', 'cherry'}
<class 'set'>

{'aubergine', 'courgette', 'potato'}
<class 'set'>

由此我们可以看到,虽然我们可以使用{}符号从逗号分隔的原始输入中创建一个集合,但是我们已经使用了set()函数将一个元组改变为一个集合,这表明我们也可以将其他数据结构改变为集合。

我们还可以看到,在打印器械包时,它不一定按照输入数据的顺序出现(特别是蔬菜器械包)。这与这样一个事实有关,即它是一个无序的数据结构,所以项目不会总是以相同的顺序出现,所以我们不能以与列表相同的方式访问项目。

访问集合中的项目

因为集合中的条目是无序的,我们根本没有条目的索引,所以我们不能像访问列表或元组那样访问它们。因此,有两种主要的方法来检查一个项目是否在一个集合中。

第一种方法是简单地循环一个集合中的所有项目,以打印所有项目,然后检查它们和/或制定一个编程解决方案,以便在识别出所需项目时停止。这可以通过以下方式完成:

#use a loop to iteratre over the set
for x in fruits:
    print(x)#out:
cherry
apple
banana

这可能计算量很大,如果你必须用眼睛检查所有的值,那么如果你有一个非常大的集合,这可能需要很长时间。

另一种方法是简单地使用 python 中的in关键字来检查您感兴趣的值是否确实在集合中。这可以通过以下方式实现:

#or check whether the fruit you want is in the set
print("apple" in fruits)
#which acts the same way as if it were in a list#out:
True

在这种情况下,返回True,因为“苹果”在集合中。这比遍历整个集合要简单得多,并且节省了计算资源,因此可以用 if、elif 或 else 语句来触发其他代码。

易变性

虽然集合可能是可变的,因为它们可以被改变,但是集合中的实际值不能。这意味着,虽然您可以添加或删除集合中的项目,但您不能更改特定的项目,因为我们不能通过它们的索引来访问项目。

因此,我们可以使用几种不同的方法来改变设置。我们可以首先关注向现有集合添加项目。第一种方法是使用add()方法,该方法可用于向集合中添加特定的项目。第二种方法是使用update()方法,该方法可用于向现有集合添加另一个集合。最后,我们还可以使用update()方法将任何可迭代对象添加到集合中,比如元组或列表,但这只会保留唯一的值。这些可以通过以下方式实现:

#we can add using the add method
fruits.add("cherry")#check the updated set
print(fruits)#we can add another set to the original set
tropical = {"pineapple", "mango", "papaya"}
fruits.update(tropical)#print the updated set
print(fruits)#we can also use the update method to add any 
#iterable object (tuples, lists, dictionaries etc.)
new_veg = ["onion", "celery", "aubergine"]
vegetables.update(new_veg)#print the updated set
print(vegetables)#out:
{'apple', 'banana', 'cherry'}
{'mango', 'papaya', 'banana', 'pineapple', 'apple', 'cherry'}
{'aubergine', 'courgette', 'onion', 'potato', 'celery'}

另一方面,我们也可以从集合中删除我们不再需要的项目。为此,我们可以使用remove()方法从集合中删除一个特定的值。然而,这样做的问题是,如果集合中不存在该项目,那么这将引发一个错误,并停止您的代码运行。因此,我们也可以使用discard()方法,它不会引发错误,从而允许代码继续运行。当然,这些选项的选择将取决于您是否希望在项目是否在集合中时发生特定的动作!最后,我们也可以使用pop()方法,但是由于集合是无序的,我们不知道哪个条目将被删除!

#we can use the remove method
fruits.remove("apple")print(fruits)
#the issue with this is if the item does not 
#exist remove() will raise an error#or the discard method
fruits.discard("mango")
#this does not raise an errorprint(fruits)#finally we can also use the pop method
#but since this is unordered it will remove the last item
#and we also don't know which item will be removed
fruit_removed = fruits.pop()print(fruit_removed)
print(fruits)#finally we can clear the set using teh cleaer method
fruits.clear()
print(fruits)#or delete the set completely
del fruits#out:
{'mango', 'papaya', 'banana', 'pineapple', 'cherry'}
{'papaya', 'banana', 'pineapple', 'cherry'}
papaya
{'banana', 'pineapple', 'cherry'}
set()

我们还提供了clear()方法,如果你愿意,它可以完全清除集合,或者del函数,它可以完全删除集合!

附加功能

集合和其他数据结构的一个重要区别是它们不能包含重复值。例如,当我们希望最小化信息占用的空间时,我们不想要重复的信息,或者我们希望找到信息中包含的独特价值时,这是非常有益的。例如,如果我们想向列表中添加重复项,然后只提取唯一值,我们可以使用一个集合,如下所示:

cars = {"Ford", "Chevrolet", "Toyota", "Hyundai", "Volvo", "Ford"}print(cars)#out:

{'Toyota', 'Ford', 'Chevrolet', 'Hyundai', 'Volvo'}

该集合将只包含传递的项目中的单个值。

这对于我们何时想要将两个集合连接在一起具有重要的意义,并且有多种方法可以这样做。例如,如果我们想要合并集合并保留每个集合的所有唯一值,我们可以使用union()方法创建一个新的集合,或者我们可以使用update()方法更改一个现有的集合,如下所示:

set1 = {1, 2, 3}
set2 = {"one", "two", "three"}#we can use union to return a new set
#with all items from both sets
set3 = set1.union(set2)
print(set3)#or we can use update to insert items in set2 into set 1
set1.update(set2)
print(set1)#out:
{1, 2, 3, 'one', 'two', 'three'}
{1, 2, 3, 'one', 'two', 'three'}

另一种方法是只保留出现在两个集合中的副本,如果我们想创建一个新的集合,可以使用intersection()方法;如果我们想更新一个现有的 et,可以使用intersection_update()方法,如下所示:

fruits = {"apple", "banana", "cherry"}
companies = {"google", "microsoft", "apple"}#y creating a new set that contains only the duplicates
both = fruits.intersection(companies)print(both)#or keep only items that are present in both sets
fruits.intersection_update(companies)print(fruits)#out:
{'apple'}
{'apple'}

或者最后,我们可以做相反的事情,提取除了重复以外的任何内容,这样每个集合的值都是唯一的。当我们想要创建一个新的集合时,可以使用symmetric_difference()方法,或者当我们想要更新一个现有的集合时,可以使用symmetric_difference_update()方法。

fruits = {"apple", "banana", "cherry"}
companies = {"google", "microsoft", "apple"}#y creating a new set that contains no duplicate
both = fruits.symmetric_difference(companies)print(both)#or keep only items that are present in both sets
fruits.symmetric_difference_update(companies)print(fruits)#out:
{'cherry', 'microsoft', 'banana', 'google'}
{'cherry', 'microsoft', 'banana', 'google'}

因此,这是一个完整的 Python 集合指南!由此我们可以看出,集合是无序的、无索引的,并且不允许重复值。后者是一个重要的特征,因为当我们只想从某样东西中提取唯一的项而不是它们的多个实例时,可以使用它们,但是当我们想在数据集中保持某种顺序时,就不能使用它们。

这是探索数据结构及其在 Python 中的使用和实现系列的第二篇文章。如果您错过了 Python 中的第一个列表,可以在以下链接中找到:

未来的帖子将涵盖 Python 中的字典、链表、栈、队列和图形。为了确保您将来不会错过任何内容,请注册以便在发布时收到电子邮件通知:

https://philip-wilkinson.medium.com/subscribe

如果你喜欢你所阅读的内容,并且还不是一个媒体成员,考虑通过使用我下面的推荐代码注册来支持我自己和这个平台上的了不起的作者们:

https://philip-wilkinson.medium.com/membership

Python 中堆栈的完整指南

原文:https://towardsdatascience.com/a-complete-guide-to-stacks-in-python-ee4e2045a704

在 Python 中构造和使用堆栈数据结构

照片由梅姆Unsplash 上拍摄

堆栈是一种抽象数据类型,它存储项目添加到结构中的顺序,但只允许对堆栈顶部进行添加和删除。这遵循了后进先出(LIFO)原则,正如它的名字所说。你可以把这想象成你吃一叠薄煎饼的方式(除非你想一次吃掉一叠薄煎饼中的一部分),或者你吃完晚饭后洗完盘子后使用它们,然后打算重复使用它们的方式(除非出于某种原因你想冒险把它们都打碎!).以下面的 Gif 图片中吉米·法伦扔掉的那堆煎饼为例。

giphy 的 Gif

当我们希望将输出存储在一个保持其顺序的结构中,但我们只希望用户能够访问他们最后添加到该结构中的内容时,可以使用这些方法。常见的例子包括浏览器中的后退按钮,它可以转到最后一个添加到堆栈中的项目,或者文字处理器中的撤销按钮,它可以撤销最后一次添加的项目。

这当然是一个抽象,关注于我们希望数据结构包含什么以及我们希望如何与之交互。然后,我们可以开始考虑如何在 Python 的实际数据结构中实现这一点。

考虑到这一点,我们需要能够定义与该数据结构相关的关键方法。常见操作包括:

  • push(item):将一个项目推到堆栈的顶部
  • pop():移除堆栈的顶部项目并返回它
  • peek():返回堆栈的顶部项目,但不移除它
  • is_empty():如果堆栈为空,则返回 True
  • size():返回堆栈中有多少项

这意味着我们需要使用 Python 数据类型来构建它,这种数据类型是可变的,可以用来存储有序的项目集合。我们可以问自己的第一件事是,是否有一种已经在 Python 中实现的数据类型可以为我们做到这一点?

Giphy 的 Gif

一份名单!我们可以用一个列表!

我们知道列表是可变的,它可以被改变,并且我们可以使用列表已经内置的功能来实现堆栈,轻松简单地从开始和结束添加和删除项目。

在使用列表时,我们必须定义一个使用列表的类,但它只有特定的功能,允许用户以我们想要的方式与之交互。为此,我们可以使用 Python 的结构来定义数据结构将包含的信息以及我们想要使用的方法。

然后要做的第一件事是将命名为Stack,并为这个新类创建构造函数。为此,我们想要的是,一旦创建了对象的新实例,就创建一个可以使用.items属性访问的空列表。我们可以如下实例化该类:

这意味着当Stack类的一个实例被创建时,item属性将代表一个空列表。

然后我们可以开始向我们的Stack 添加主要功能。我们希望能够做的第一件事是将一个项目添加到堆栈的末尾。列表的好处是在.append()方法中有一个内置的方法。这样做的好处是,它具有恒定的时间复杂度 O(1 ),这意味着它运行的时间不会随着列表中已有项目的数量而改变,因为它只将项目添加到末尾。因此,我们可以利用这一点将一个项目推到我们的Stack的末尾,如下所示:

很好很简单对吧!在这种情况下,我们并不关心到底要在栈尾添加什么,只是我们可以添加它。您可以添加更多的功能,但目前这是好的。

从堆栈中移除项目呢?为此,我们首先需要检查堆栈不是空的,因为你不能从空的堆栈中移除项目,对吗?如果堆栈是空的,那么我们可以简单地返回none,这样我们实际上返回了一些东西,但是如果它不是空的,那么我们可以移除最后一个项目并返回它。因为我们使用 Python 的内置列表类,所以我们可以使用.pop()方法,在 Python 3.6+中,该方法从列表中删除最后一项并返回它。这也在常数时间 O(1)中运行,因为我们只删除最后一项,不影响任何其他索引。我们可以这样实现:

弹出前先检查列表是否存在的好处是确保不会抛出错误,否则如果不处理该错误,可能会导致代码停止运行。将它放在数据结构的实际实现中,可以确保如果在使用这个数据结构运行代码时没有想到它,那么代码会在某种程度上继续运行。

我们现在有了与堆栈相关联的主要方法,但是我们还可以添加一些额外的功能,以便让它对我们的最终用户更有用。

我们可以添加的第一件事是一个方法来查看堆栈中的最终值,允许用户在决定是否删除它之前查看该值。这可以遵循类似于pop()方法的实现,但是我们不使用.pop()而是访问列表中最后一个索引的条目。同样,它的时间复杂度为 O(1 ),因为我们使用索引从一个列表中访问一个条目,这样做既简单又方便。

我们还可以提供一种检查堆栈是否为空的方法。如果堆栈为空,这将简单地返回布尔值True,如果不为空,则返回False。这也以恒定时间运行,因为它只是检查堆栈中的列表是否存在。

最后,我们可以创建一个返回堆栈大小的方法。这告诉我们堆栈的长度,就像列表一样,并允许用户查看添加了多少项。

现在,我们拥有了 Stack 类的主要功能以及大部分附加功能。这使得我们的最终用户在我们创建的最终界面中与这个数据结构进行交互变得更加容易和简单。

唯一的问题是,如果我们试图打印该类的一个实例,我们会得到难看的输出

<__main__.Stack object at 0x0000027F2C960FD0>

这看起来不太好,也没有告诉我们这个类的实际实例。然后可以做的是使用该类的特殊的__str__邓德方法来告诉解释器我们想要如何打印出该类的一个实例。在这种情况下,我们只想返回包含在堆栈中的整个列表,可以实现为:

最后要做的是将所有这些放在一起,这样我们就可以在代码中创建这个 Stack 类的实例。最终产品看起来像这样:

此时你一定在想,我为什么需要知道这些?这在编程中的常见应用包括:

  • 评估数学表达式
  • 编程语言的语法分析
  • 存储参数和局部变量
  • 文字处理器中的撤消和重做操作
  • 网站中的回溯

当你在构建这些程序的时候,知道这种数据结构可以给你提供处理这些挑战的功能,这是非常好的。

另一方面,堆栈也可以用在一个常见的涉及如何反转字符串的面试问题中。虽然你可以简单地用一个列表做到这一点,但是如果他们要求你创建一个数据结构并反转一个给定的字符串,那么栈就是一个很好的例子。

为此,我们可以简单地将所有字符添加到一个空堆栈中,以相反的顺序解压缩所有这些字符,将它们连接在一起,然后打印出结果。这方面的一个例子如下:

好了,现在你知道了如何用 Python 实现一个栈,以及如何使用这个数据结构反转一个字符串!恭喜你。

Giphy 的 Gif

这是探索数据结构及其在 Python 中的使用和实现的系列文章的第五篇。如果您错过了前三篇关于 Python 中的字典、元组和集合的文章,您可以在以下链接中找到它们:

本系列的后续文章将涉及链表、队列和图形。为了确保您不会错过任何内容,请注册以便在发布时收到电子邮件通知:

https://philip-wilkinson.medium.com/subscribe

如果你喜欢你所读的内容,但不是一年中的会员,那么考虑使用我下面的推荐代码注册,在这个平台上支持我自己和其他了不起的作者:

https://philip-wilkinson.medium.com/membership

感谢您的阅读!

Python 中元组的完全指南

原文:https://towardsdatascience.com/a-complete-guide-to-tuples-in-python-af76241e8b59

什么是元组、元组实现、数据类型、索引、不变性和扩展功能

照片由Paico officialUnsplash 上拍摄

什么是元组?

元组是一种数据结构,类似于 Python 中的列表,但通常不太为人所知或与之交互。它们共享列表的相同特征,因为它们是有序的和可索引的,但是它们的不同之处在于它们是不可变的,并且它们是使用()符号而不是[]符号创建的。这意味着:

  • 不可变:一旦创建就不能更改
  • 有序:一旦创建,它们就保持它们的顺序
  • 可索引:如果我们知道条目在元组中的位置,就可以访问信息
  • 可以包含重复记录:可以包含相同值的项目,没有任何问题。

这很重要,因为这些特征会影响元组相对于列表的使用方式。不可变的主要区别在于,因为它们一旦被创建就不能被更改,所以它们可以在程序中使用,一旦你设置了一个值,你就不希望它们被意外地更改,同时仍然具有与列表相同的功能。这样的例子包括存储实验结果或设置在整个程序中不变的起始参数。

履行

要创建元组,我们可以使用两种主要方法:

  • 使用()将我们想要包含的信息用逗号分隔的条目包含在元组中
  • 使用tuple()函数,该函数可用于将其他数据结构转换为元组或以列表作为参数

这些可以简单地实现为:

#create the tuple
cars = ("Ford", "Hyundai", "Toyota", "Kia")#create a second tuple
fruits_tuple = tuple(("Strawberry", "peach", "tomato"))#create the third tuple
vegetable_tuple = tuple(["potato", "onion", "celery"])#print the result
print(cars)
print(type(cars))print(fruits_tuple)
print(type(fruits_tuple))print(vegetable_tuple)
print(type(vegetable_tuple))#out:
('Ford', 'Hyundai', 'Toyota', 'Kia')
<class 'tuple'>
('Strawberry', 'peach', 'tomato')
<class 'tuple'>
('potato', 'onion', 'celery')
<class 'tuple'>

这里需要注意的一点是,tuple()函数只接受一个参数,这意味着我们可以将它传递给一个更早实现的数据结构,或者像我们在这里所做的那样,我们可以在()中传递一个元组本身,或者在[]中传递一个列表。

我们还可以看到,我们已经能够检查我们通过使用type()功能创建的数据结构的类型,该功能告诉我们我们有一个tuple类。我们还可以看到,当打印出一个元组时,它由元组本身中的项目周围的()来表示,并且这些项目保留了它们在原始数据结构中的顺序。

元组中的数据类型

由于元组的行为类似于列表,这意味着我们也可以在元组中包含不同的数据类型。虽然我们在上面实现了内部只有字符串的元组,但是我们也可以创建内部有整数和浮点数的元组:

#create a list of just numbers
num_tuple = (1, 2, 3, 4)#create a list of just floats
float_tuple = (1.2, 2.3, 4.5, 6.8)#print the results
print(type(num_tuple))
print(num_tuple)print("\n")print(type(float_tuple))
print(float_tuple)#out:
<class 'tuple'>
(1, 2, 3, 4)

<class 'tuple'>
(1.2, 2.3, 4.5, 6.8)

我们还可以在一个元组中组合不同的数据类型,这样我们就不必拥有一种以上的数据类型。例如,我们可以在里面放一个列表,甚至另一个元组!

#different list
random_tuple = ("Hello", 3, "Cheese", 6.2, [1,2,3], (4,6,7))#print the result
print(type(random_tuple))
print(random_tuple)#out:
<class 'tuple'>
('Hello', 3, 'Cheese', 6.2, [1, 2, 3], (4, 6, 7))

索引

就像列表一样,元组的一个重要特征是它们是项目的有序集合。这意味着一旦创建了元组,它们就有了明确定义的顺序,并且因为它们是不可变的,所以顺序不能改变。

这种顺序允许我们访问元组中的值,我们知道这些值在该顺序中的给定位置。例如,如果我们根据汽车制造商的位置创建了一个我们想要访问的汽车制造商列表,如果我们忘记了我们计划访问的第一个制造商,我们可以通过使用列表的第一个索引来访问第一个制造商。当然,因为这是 Python,所以一切都以 0 索引开始,所以我们可以用以下方式访问元组中的第一项:

#get the first item from the tuple
print(cars[0])#out:
Ford

为此,方括号用于输入索引号,这样tuple[0]就是我们访问第一个索引的方式。

按照这个例子,可以使用tuple[1]访问元组中的第二项,使用tuple[2]访问第三项,依此类推。为此,只要索引属于您尝试访问的元组,任何解析为数字的内容都可以用来访问元组中的内容。如果您试图使用一个超出元组的索引,那么您将得到一个错误,告诉您该索引不在元组中。

这种索引的一个有用的优点是,我们不仅可以向前计数,就像我们对汽车元组所做的那样,我们还可以向后计数。这意味着我们可以检查最后添加到元组中的项。例如,如果我们想要检查我们计划最后访问的制造商,我们将使用:

#get the last item from the tuple
print(cars[-1])#out:
Kia

为此,重要的是要注意倒计数从-1 开始,并随着我们的进行而增加。这是因为如果我们从 0 开始,那么我们将会混淆我们想要列表中的第一项还是最后一项。

最后,我们还可以使用索引同时提取一个元组中的多个项,而不是单个项。我们可以使用与 list 相同的方式来实现这一点,使用tuple[start_item: end_item]的切片符号,这样最后一项就不会出现在返回的元组中。这方面一个例子包括:

#get the second and third from the tuple
print(cars[1:3])#get all from the first index
print(cars[1:])#get all until the fourth one
print(cars[:3])#out:
('Hyundai', 'Toyota')
('Hyundai', 'Toyota', 'Kia')
('Ford', 'Hyundai', 'Toyota')

几个不同的规则适用于此,因为:

  • 当使用[1:3]打印第二个最低的项目时,由于最终索引不包含在结果中,所以只返回两个项目
  • 当打印[1:]时,我们没有指定结束索引,这就是为什么在第四个索引之后,包括第四个索引,整个元组都被打印出来
  • 当打印第四个索引之前的所有内容时,因为我们没有指定开头,所以打印了元组的开头

也像列表一样,每当获取一个切片时,切片的类型将与您获取切片的对象的类型相同。在这里,因为我们获取一个元组的一部分,所以返回一个元组。

查找项目

从上面我们可以看到,当我们知道项目的索引时,我们可以访问项目,但是如果我们只知道元组包含项目,而不知道它的位置,那该怎么办呢?例如,在我们的汽车列表中,我们知道我们必须访问丰田,但不知道我们必须访问制造商的顺序。然后,像列表一样,我们可以使用index()方法找到项目的位置,如下所示:

#get the index for Toyota
print(cars.index("Toyota"))#out:
2

尽管需要注意的是,当访问重复值的索引时,index()方法将只返回该值的第一个索引的索引。

唯一的问题是,如果您拼错了该项或者该项不在元组中,该方法将抛出一个错误,并将停止代码运行。解决这个问题的一个简单方法是使用 if/else 语句,该语句可以使用:

if "Toyota" in cars:
    print("Toyota is at index:", cars.index("Toyota"))
else:
    print("Toyota not in tuple")#out:
Toyota is at index: 2

不变

元组是不可变的意味着它们不能被改变。如果你试图使用索引和赋值来改变一个值,比如cars[0] = "Tesla",那么你将得到一个类型错误,说明“type error:‘tuple’对象不支持项赋值”,这表明你不能改变一个元组。

也就是说,如果你愿意,有一些方法可以解决这个问题,尽管元组的目的是你首先不要这么做。第一种方法是将元组转换为列表,更新值,然后将其转换回元组,如下所示:

#print the tuple
print(cars)#change it to a list
tuple_list = list(cars)#change the value
tuple_list[0] = "Maserati"#reassign back to the tuple
cars = tuple(tuple_list)#print the result
print(cars)#out:
('Ford', 'Hyundai', 'Toyota', 'Kia')
('Maserati', 'Hyundai', 'Toyota', 'Kia')

当然,如果你想这样做,那么你可能应该首先创建一个列表。

改变元组的另一种方法是将两个元组连接在一起形成一个新的元组。这并不一定要改变原来的元组,而是创建一个新的元组,所以这也是一种变通方法。这当然意味着你对一个元组所能做的唯一改变是在末尾或开头添加内容,而不是改变元组本身。这可以通过以下方式实现:

#create new tuples
tuple1 = ("a", "b", "c")
tuple2 = (1,2,3)#add together using the +
tuple3 = tuple1 + tuple2
print(tuple3)#multiply an existing tuple together 
tuple4 = tuple1*2
print(tuple4)#out:
('a', 'b', 'c', 1, 2, 3)
('a', 'b', 'c', 'a', 'b', 'c')

内置功能

最后,我们有几个内置的元组功能,就像我们处理列表一样,可以包括查找元组的长度,打印特定值的实例数,以及查找元组的最小值或最大值。这可以通过以下方式实现:

#print the length of the tuple
print(len(tuple1))#print the count of values within a tuple
print(tuple4.count("a"))#print the maximum value from a tuple
print(max(tuple2))#print the minimum value from a tuple
print(min(tuple2))#out:
3
2
3
1

这就是 Python 中元组的完整指南!元组在本质上明显类似于列表,因为它是有序的和可索引的。但是,它与列表的主要区别在于它不能被更改。这意味着当您不希望任何信息在创建后被更改时,例如当您不希望实验结果被覆盖或出于安全原因时,它会很有用。

这是探索数据结构及其在 Python 中的使用和实现系列的第三篇文章。如果您错过了列表和集合中的前两个,您可以在以下链接中找到它:

未来的帖子将涵盖 Python 中的字典、链表、栈、队列和图形。为了确保您将来不会错过任何内容,请注册以便在发布时收到电子邮件通知:

https://philip-wilkinson.medium.com/subscribe

如果你喜欢你所阅读的内容,并且还不是一个媒体成员,考虑通过使用我下面的推荐代码注册来支持我自己和这个平台上其他了不起的作者:

https://philip-wilkinson.medium.com/membership

理解数据编排的完整指南

原文:https://towardsdatascience.com/a-complete-guide-to-understanding-data-orchestration-87a20b46297c

关于数据编排和 2022 年流行框架,你应该知道的一切

拉丽莎·伯塔Unsplash 上拍摄的照片

请务必点击 订阅此处 千万不要错过另一篇关于数据科学指南、技巧和提示、生活经验等的文章!

现代公司中的数据世界在不断扩大。令人兴奋的是,有比以往更多的数据需要挖掘,但随着更多的数据而来的是更多的治理、同步计划和处理问题。

公司需要打破数据源和存储之间的孤岛,以真正运营他们正在收集的所有信息。然而,仅仅添加新的工具并不能解决问题(实际上会使问题变得更糟)。为了打破数据源之间的孤岛并克服系统蔓延,他们需要更好的数据治理和数据编排。

数据编排使组织能够自动化和简化其数据,实现数据的可操作性,以便利用这些有价值的信息来推动实时业务决策。据估计,数据分析中涉及到的 80%的工作归结为获取和准备数据,这意味着数据编排可以减少数据处理和调度时间的负载。

但是首先…什么是数据编排?

数据流程编排在数据管道的每个阶段看起来都有所不同,因此在本文中,我们将重点关注一个 20,000 英尺长的通用定义,该定义从端到端捕捉数据流程编排:

数据编排是利用软件打破源和存储位置之间的数据孤岛,通过自动化提高现代数据堆栈中的数据可访问性。它改进了数据收集、数据准备和转换、数据统一以及交付和激活。

当一家公司投资购买必要的软件来支持其堆栈中每一层的数据编排时,他们能够更好地连接他们的系统,并确保他们能够相对实时地访问所有信息。

作者创造的形象

请务必点击 订阅此处 千万不要错过另一篇关于数据科学指南、技巧和提示、生活经验等的文章!

数据编排的重要性

正如我们在上面的定义中提到的,数据编排有助于您的整个体系中的四个主要流程:

  1. 数据收集:端到端数据编排服务处理数据接收,从您的客户接触点收集重要数据,通常通过可直接与您的应用集成的 SDK 和 API。
  2. 数据准备和转换:收集完数据后,编排服务会帮助标准化和检查收集点的属性和值。这些值——比如名称、时间和事件——可以进行变异以匹配标准模式。
  3. 数据统一:数据编排服务可以帮助组织将其数据统一到一个池中,这个池比单个输入流本身更有价值。通过将从网站、销售点设备和应用程序收集的数据拼接在一起,这可用于创建单一、统一的客户视图,以帮助您了解个人用户随时间推移的行为。
  4. 交付和激活:一旦创建了统一的客户档案,数据编排服务就可以将这些有价值的信息发送到您的团队每天使用的工具中,包括 BI 平台、数据分析工具和客户管理解决方案。

您可以将您的数据管道想象成一条河流,有一些支流(数据源)流入其中。假设您试图在河口附*创建一个带有大坝的水库(数据仓库),该水库将产生足够的电力(洞察力/价值)为附*的城镇(客户)提供电力。

如果没有数据协调,修建大坝和控制水库水位的人必须手动运行上游,并在每个源头上放水门。这需要几个小时(和大量的工作)。有了数据编排,管理大坝的工作人员可以通过编程运行操作来自动化水流,而无需文字工作。这意味着他们可以花更多的时间来监控产生的能量并将其输送到城镇。

从技术上讲,数据编排解决方案由 Dag(有向无环图形)归档,dag 是您想要运行的任务的集合。这些任务是根据它们的依赖关系来组织和运行的。

作者创造的形象

DAG 的每个节点代表流程中任务的一个步骤。它通常由 Python 脚本定义,这使得数据科学家易于使用。

例如,将提取、加载、转换和同步流程的四个步骤作为 DAG。为了执行转换步骤,必须加载数据,并且为了加载数据,首先必须提取数据。然后,一旦所有这些都发生了,像 Census 这样的反向 ETL 工具就可以通过 API 使用触发器同步来处理下游工作。

更准确地说,数据编排流中的 Dag 可以帮助:

  • 仓库中的数据组织、清理和发布
  • 业务度量计算
  • 通过电子邮件活动对活动目标和用户参与度实施规则
  • 数据基础设施维护
  • 训练机器学*模型

数据编排工具是如何发展的

像所有技术一样,数据编排工具经常变化,以跟上不断发展的组织的数据管理需求。每一代新的数据编排服务都强调越来越具体的用例。

air flow这样的第一代数据编排工具主要致力于通过引入 Python 支持来提高数据科学家的可用性(而以前的工具需要用 JSON 和 YAML 编写查询)。这种改进的 UI 使数据团队更容易管理他们的管道流,而不会陷入流程中。****

Dagster这样的第二代数据编排工具更侧重于数据驱动。他们能够检测 Dag 中的数据类型,并通过预测每种数据类型触发的操作来提高数据意识。****

这些数据驱动的功能可以分为两类:

  1. 在步骤和系统之间传递数据的主动方法。
  2. 被动方法在触发任务之前等待 DAG 之外的事件(提示)发生(对于连续的模型训练特别有用)。

对于大多数用例来说,主动数据编排方法将会工作得很好。然而,随着堆栈和数据流变得越来越复杂,可以利用被动方法来编排这些堆栈。

务必 订阅此处 千万不要错过另一篇关于数据科学指南、技巧和提示、生活经验等的文章!****

编排和未编排的数据有什么区别?

编排的数据堆栈和未编排的数据堆栈之间的区别在于,操作数据以推动未来决策,还是对过去的数据做出反应并进行故障排除。

不幸的是,许多老牌公司和初创公司仍在纠结于他们未经整理的数据。根据 Gartner 的数据,超过 87%的企业商业智能和分析成熟度较低。

这是因为仍处于向云迁移过程中的遗留系统和堆栈往往会产生比其分解的更多的数据孤岛。需要深思熟虑的治理和设计决策来改造这些遗留堆栈,以克服技术债务并在您的数据生态系统中利用数据编排工具。

为什么您应该关注数据编排?

数据编排打破了分隔数据堆栈的孤岛,使您的数据在黑暗的数据漩涡中变得陈旧。许多公司可能会让他们的工程师走上构建 DIY 编排解决方案的道路,但随着堆栈的变化,这些将很快变得无关紧要(此外,它需要大量昂贵的返工,让您的工程师很恼火)。除了节省数据工程时间,流程编排还可以帮助您:

  • 改善数据治理和可见性
  • 利用更新的客户数据
  • 确保数据隐私合规

流程编排为许多公司提供了一种可扩展的方式来保持堆栈的连接,同时保持数据顺畅流动,从而避免了他们所经历的成长烦恼。对于公司来说,这是一件好事:

  • 拥有大量需要整合的数据系统。
  • 已经开始集成现代数据堆栈,并希望从中获得更多的用途。
  • 刚刚开始构建他们的第一个堆栈,并希望建立一个强大的基础来应对未来的规模。

数据编排可确保您和您的团队拥有尽可能最新的数据,而无需您的工程师连夜手动运行作业来为您提供服务。这些服务允许您自动执行同步计划,并使用触发器 API 来更新下游依赖项。

1.改进了工程师和分析师的工作流程

使用手动 cron 作业和 Python 脚本是一种获取所需数据的缓慢方法。在当今的数据世界中,数据收集的速度和数量增长如此之快,以至于数据团队和工程师不可能跟上手动组织的步伐。

与其依赖一个超负荷工作的工程师从多个仓库和存储系统中获取您需要的数据,不如使用数据编排平台自动转换并交付给您。

这减少了工程师和数据科学家在数据收集和转换上花费的时间,并使数据团队能够实时做出更好的决策。

2.改善数据治理和可见性

孤立且分散的数据源很难管理和了解。为了有效地使用编排工具,公司必须审核和组织他们的堆栈,在流程中创建更多的可见性。这使您更容易管理您的数据,并提高数据的整体信心和质量。

从本质上讲,数据编排就是让您的数据和系统对使用它们的系统和人员更有用。

3.利用更新的客户数据

RevOps 的人知道,更好地洞察客户的关键在于数据。端到端的数据流程自动化使数据消费者比以往任何时候都更容易操作他们的数据使用。

借助与您的渠道集成的数据编排功能,来自营销活动、网络广播、网络应用和其他客户数据源的数据可以轻松收集、转换、加载到您的数据仓库,然后通过反向 ETL 工具发送回 Salesforce 和 Marketo 等平台。

这种信息可用性有助于 RevOps 团队对销售线索和客户进行评分,创建每个客户的单一视图,建立受众关联,等等。

4.确保数据隐私合规

巨大的数据带来巨大的责任。 GDPRCCPA 和其他数据隐私法要求组织通过提供证明数据收集正确且符合道德规范的文档,成为客户数据的好管家。

如果您在一个混乱的、不同的堆栈中工作,这种文档记录很难维护和显示。然而,有了配备了数据编排的现代数据堆栈,您可以轻松地详细了解您收集的每个数据点的时间、位置和原因。

这也使得组织更容易根据请求删除信息,如果由于数据流不畅,部分有问题的数据被无限期地保留在上游,这是很难完全做到的。

感谢阅读!

请务必点击 订阅此处 千万不要错过另一篇关于数据科学指南、技巧和提示、生活经验等的文章!

不确定接下来要读什么?我为你挑选了另一篇文章:

****

还有一个:

</8-best-practices-for-writing-top-tier-sql-code-e1023996e459>

-特伦斯·申

这最初由 Allie Beazell 发布在人口普查博客 上。如果您有兴趣阅读更多类似的文章并了解更多关于运营分析的知识,请随时查看!

https://blog.getcensus.com/ ****

在 Docker 和 Compose 中使用环境变量和文件的完整指南

原文:https://towardsdatascience.com/a-complete-guide-to-using-environment-variables-and-files-with-docker-and-compose-4549c21dc6af

完整的 SHAP 教程:如何用 Python 解释任何黑盒 ML 模型

原文:https://towardsdatascience.com/a-complete-shap-tutorial-how-to-explain-any-black-box-ml-model-in-python-7538d11fae94

向非技术人员解释任何黑盒模型

亚历山大·格雷摄影

动机

今天,你不能只是走到你的老板面前说,“这是我最好的模型。投入生产吧,开心点!”。不,现在不是那样了。公司和企业对采用人工智能解决方案非常挑剔,因为它们具有“黑箱”性质。他们要求模型的可解释性。

如果 ML 专家正在开发工具来理解和解释他们创造的模型,那么非技术人员的担心和怀疑是完全有道理的。几年前引入的工具之一是 SHAP。它可以分解任何机器学*模型和神经网络的机制,使它们可以被任何人理解。

今天,我们将学* SHAP 是如何工作的,以及如何在实践中使用它来完成经典的 ML 任务。

https://ibexorigin.medium.com/membership

获得由强大的 AI-Alpha 信号选择和总结的最佳和最新的 ML 和 AI 论文:

https://alphasignal.ai/?referrer=Bex

什么是 SHAP 和沙普利值?

SHAP(SHapley Additive exPlanations)是一个 Python 包,基于 2016 年 NIPS 关于 SHAP 值的论文。本文的前提和 Shapley 值来源于博弈论的方法。

游戏中经常提出的一个问题是,在一群拥有不同技能的玩家中,我们如何分配奖金,以便每个人都能根据他们的技能得到公平的份额?根据玩家的数量,他们加入游戏的时间,以及他们对结果的不同贡献,这种类型的计算会变得非常复杂。

但是博弈论和机器学*有什么关系呢?那么,我们可以重新定义上面的问题,这样它就变成“给定一个预测,我们如何最准确地测量每个特征的贡献?”是的,这就像询问模型的特性重要性,但是 Shapley 值给出的答案要复杂得多。

具体来说,Shapley 价值观可以帮助您:

  1. 全局模型可解释性 —假设你为一家银行工作,为贷款申请建立一个分类模型。你的经理希望你解释什么(以及如何)不同的因素影响你的模型的决策。使用 SHAP 值,您可以给出一个具体的答案,详细说明哪些功能会导致更多的贷款,哪些功能会导致更多的拒绝。你让你的经理很高兴,因为现在,他可以为未来的银行客户起草基本准则,以增加他们获得贷款的机会。更多的贷款意味着更多的钱,更快乐的经理意味着你更高的薪水。

作者图片

  1. 本地可解释性 —您的模型拒绝了几天前提交给银行的一份申请。客户声称他遵循了所有的指导方针,并且肯定能从您的银行获得贷款。现在,你有法律义务解释为什么你的模型拒绝了那个特定的候选人。使用 Shapley 值,可以独立分析每个案例,而不用担心它与数据中其他样本的联系。换句话说,你有本地可解释性。您提取投诉客户的 Shapley 值,并向他们展示应用程序的哪些部分导致了拒绝。你用这样的情节证明他们错了:

作者图片

那么,如何计算强大的 Shapley 值呢?这就是我们开始使用 SHAP 软件包的地方。

如何用 SHAP 计算沙普利值?

计算 Shapley 值的精确数学细节值得单独写一篇文章。因此,现在,我将站在巨人的肩膀上,向你推荐他们的帖子。它们保证巩固你对这些概念的理解( 12——作者苦仁陈)。

然而,在实践中,你很少会提到 Shapley 值背后的数学。原因是所有神奇的细节都很好地包装在 SHAP 内部。让我们看看第一个例子。

使用 Seaborn 中内置的钻石数据集,我们将使用几种物理测量来预测钻石价格。我预先处理了数据集,并将其分为训练集和验证集。以下是训练集:

作者图片

>>> X_train.shape, X_valid.shape((45849, 9), (8091, 9))

切割、颜色和清晰度是分类特征。它们按顺序编码,因为它们的顺序对上下文和最终的模型决策有意义。

作为基线,我们拟合了 XGBRegressor 模型,并使用均方根误差评估了性能:

现在,让我们最后看一眼幕后,并计算训练集的 Shapley 值。

我们首先为我们的模型创建一个解释器对象:

TreeExplainer是 SHAP 的一个特殊类,优化后可用于 Sklearn、XGBoost、LightGBM、CatBoost 等中任何基于树的模型。您可以将KernelExplainer用于任何其他类型的模型,尽管它比树解释器慢。

这个树解释器有很多方法,其中一个是shap_values:

正如我说过的,计算 Shapley 值是一个复杂的过程,这就是为什么在 CPU 上仅 45k 次观测就花费了大约 22 分钟。对于具有数百个要素和数百万个样本的大型现代数据集,计算可能需要数天时间。因此,我们转向 GPU 来计算 SHAP 值。

到目前为止,GPU 支持在 SHAP 还不稳定,但是我们有一个解决办法。核心 XGBoost 模型的predict方法有pred_contribs参数,当设置为 True 时,计算 GPU 上的 SHAP 值:

注意,LightGBM 在其*predict*方法中也有对 SHAP 值的 GPU 支持。在 CatBoost 中,通过在*type*设置为*ShapValues*的模型上调用*get_feature_importances*方法来实现。

在提取 XGBoost 的核心助推器模型后,计算 45k 个样本的 Shapley 值只需要大约一秒钟:

>>> shap_values_xgb.shape(45849, 10)

但是等等——来自树解释器的 Shap 值有九列;这个有 10 个!不用担心;我们现在可以放心地忽略最后一列,因为它只包含 XGBoost 默认添加的偏差项:

作者图片

我们得到了 Shapley 值。现在怎么办?现在,我们开始策划。

SHAP 的全球特征重要性

让我们看看在决定价格时,钻石的哪些物理尺寸最重要:

作者图片

克拉是钻石价格的主要驱动因素。阅读下面的轴标题,我们看到重要性只是一个特征的平均绝对 Shapley 值。我们可以查看以下内容:

但是这与从 XGBoost 中得到的特性重要性图没有太大的不同:

>>> xgb.plot_importance(booster_xgb);

作者图片

这就是我们错的地方。您不能相信 XGBoost 中的特性重要性,因为它们在不同的计算中是不一致的。观察要素重要性如何随计算类型而变化:

作者图片

相比之下,从 Shapley 值获得的特征重要性是一致的和可信的。

我们不会就此止步。在上面的图中,我们只看了重要性的绝对值。我们不知道哪个特性会对模型产生积极或消极的影响。让我们以 SHAP 为例:

作者图片

下面是如何解读上述情节:

  1. 左侧纵轴表示功能名称,根据重要性从上到下排序。
  2. 横轴表示预测的 SHAP 值的大小。
  3. 垂直右轴表示要素在数据集中出现时的实际大小,并对点进行着色。

我们看到,随着克拉的增加,它对模型的影响越来越积极。对于y特性也是如此。xz的特征有点复杂,中心周围有一簇混合的点。

https://ibexorigin.medium.com/membership

用依赖图探索每个特性

通过依赖图,我们可以更深入地了解每个特性对整个数据集的影响。我们来看一个例子,稍后再解释:

作者图片

该图与我们之前在摘要图中看到的一致。随着克拉的增加,其 SHAP 值增加。通过将interaction_index参数更改为auto,我们可以用与克拉交互作用最强的特征为点着色:

作者图片

似乎克拉与钻石净度的相互作用比其他特征更强。

现在,让我们为分类特征创建一个相关性图:

作者图片

这一情节也与摘要情节密切相关。最新的颜色类别在与 carat 互动时会对价格产生负面影响。

我将让您探索以下其他特性的依赖关系图:

作者提供的图片

具有 Shapley 值的特征交互

SHAP 值和沙普利值最奇妙的属性之一是它们能够准确地找到特征之间的关系。在上一节中,当 SHAP 在依赖图中发现最强健的交互特征时,我们已经尝到了这种滋味。

我们可以更进一步,找到按交互强度排序的所有特征交互。为此,我们需要一套不同的价值观——SHAP 互动价值观。

可以使用树解释器对象的shap_interaction_values来计算它们,如下所示:

但是这比常规的 SHAP 值更耗时。因此,我们将使用 XGBoost 再次转向 GPU:

通过设置pred_interactions为真,我们只需要 15 秒就可以得到 SHAP 交互值。这是一个 3D 数组,最后一列轴是偏差项:

现在我们有了互动。我们该怎么办?坦率地说,即使 SHAP 的文档也没有为交互勾勒出一个合理的用例,但是我们从其他人那里得到了帮助。具体来说,我们将使用我从 4x Kaggle 特级大师 Bojan Tunguz 那里学到的一个函数,在我们的数据集中找到最重要的特征交互,并绘制它们:

现在,top_10_inter_feats包含所有可能的特征对之间的 10 个最强的相互作用:

我们可以创建另一个函数,根据它们的相互作用强度绘制这些对:

作者图片

正如我们所见,ycarat之间的相互作用比其他人强得多。即使这个图对我们来说没有任何意义,领域专家也有可能破译这种关系,并使用它来更好地诊断模型。

例如,如果您的模型试图对分子对不同化学刺激的反应进行分类,这样的图会很有帮助,因为它可能会显示分子的哪些化学属性与刺激相互作用。这将告诉运行实验的领域专家很多东西,因为他们知道什么类型的化学物质相互作用,以及该模型是否可以捕捉它们的行为。

局部可解释性

最后,我们到达局部可解释性部分。它是关于解释为什么模型对一个样本做出了一个特定的决定。

让我们随机选择一颗钻石及其预测价格来解释:

好的,看起来我们将会看到训练数据中的第 6559 个菱形。让我们开始:

我们首先使用 explainer 对象重新计算 SHAP 值。这不同于shap_values函数,因为这一次,Shapley 值返回了我们局部可解释性所需的更多属性:

>>> type(shap_explainer_values)shap._explanation.Explanation

现在,让我们解释一下我们用瀑布图挑选的随机钻石:

>>> shap.waterfall_plot(shap_explainer_values[6559])

作者图片

E[f(x)] = 3287.856是列车组钻石价格的平均预测,例如preds.mean()f(x) = 3214.05是钻石的预测价格。

中间的细线表示平均预测值。纵轴显示第 6559 颗钻石的特征值。条形表示每个要素属性如何使价格偏离平均预测值。红色条代表积极的转变;蓝条代表负向移动。

为了完整起见,让我们看另一个钻石:

>>> shap.waterfall_plot(shap_explainer_values[4652])

作者图片

这颗钻石比上一颗便宜很多,主要是因为它的克拉低很多,从上面可以看出来。

还有一个情节可以解释局部的可解释性。SHAP 称之为武力阴谋,它看起来是这样的:

作者图片

这只是一个有序的,有组织的瀑布图。所有正负棒线都被分组到预测价格的两侧。同样,基值显示的是平均价格,条形显示的是每个要素属性移动该值的程度。

>>> shap.force_plot(shap_explainer_values[6559])

作者图片

摘要

现在,你可以走到你的老板面前说:“这是我最好的模型,这是为什么它是最好的以及它是如何工作的解释!”希望你得到的回应会积极得多。感谢您的阅读!

https://ibexorigin.medium.com/membership https://ibexorigin.medium.com/subscribe

您可能也会对…感兴趣

💔-step-feature-selection-guide-in-sklearn-to-superchage-your-models-e994aa50c6d2> [## 使用 Python 和 AWS Lambda 将任何 ML 模型部署为 API 的综合指南

towardsdatascience.com](/comprehensive-guide-to-deploying-any-ml-model-as-apis-with-python-and-aws-lambda-b441d257f1ec) </25-advanced-pandas-functions-people-are-using-without-telling-you-b65fa442f0f4>

Python 初学者综合指南

原文:https://towardsdatascience.com/a-comprehensive-beginners-guide-to-python-cc886f1c2f72

学* Python 的基础知识,包括数据类型、流控制语句、函数和模块

杰佛森·桑多斯在 Unsplash 拍摄的照片

Python 是一种流行的高级编程语言,以其简单性、可读性和灵活性而闻名。它是一种通用语言,可用于广泛的应用,从 web 开发和科学计算到数据分析和人工智能。

入门指南

要开始使用 Python,您需要在您的计算机上安装它。Python 的最新版本可以从 Python 官方网站(https://www.python.org/)下载。一旦您下载并安装了 Python,您就可以通过打开 Python 解释器来开始使用它。

在 Windows 上,你可以通过开始>程序> Python 3 打开解释器。X (其中 X 是你安装的版本号),然后点击“Python 3。X"** 图标。**

在 Mac 上,你可以打开解释器,方法是进入应用>实用程序>终端,然后输入【python 3】,按回车。**

一旦解释器打开,您就可以通过输入命令并立即看到结果来开始使用 Python。

例如,您可以键入print("Hello, World!")并按 Enter 键来查看输出“Hello,World!”

Python 基础

Python 是一种解释型高级通用编程语言。这意味着它在运行时由解释器执行,而不是被编译成机器码,这样便于编写和调试。它也是一种高级语言,这意味着它抽象掉了计算机的许多低级细节,如内存管理和垃圾收集,并为程序员提供了更高级、更直观的界面。

Python 是一种 面向对象的语言 ,这意味着它将数据和对该数据进行操作的功能组织成称为对象的可重用单元。

对象可以被认为是保存数据和对数据进行操作的函数的容器。这允许你编写模块化的、可重用的、易于维护和扩展的代码。

Python 是动态类型的,这意味着在声明变量时不需要指定变量的数据类型。解释器将根据您分配给变量 的值自动 推断数据类型。例如,下面的代码将整数值 10 赋给变量a和字符串值“Hello,World!”变量b,而不指定它们的数据类型:

a = 10
b = "Hello, World!"

它是 动态 因为你可以给同一个变量赋另一个不同数据类型的值;因此,它是动态地 键入的 。这与静态类型的语言相反,在静态类型的语言中,变量只能保存特定于数据类型的值。这种动态类型允许更大的灵活性,并使编写和维护代码更容易。****

数据类型

在 Python 中,有几种内置的数据类型可以用来存储和操作数据。这些数据类型包括:

  • 数字 : Python 支持整数和浮点数。整数是可以是正数、负数或零的整数,而浮点数是带小数点的数字。比如10-50都是整数,而3.141.23e2-2.5都是浮点数。
  • 字符串:字符串是一个字符序列,比如一个单词、一个句子或者一个段落。在 Python 中,字符串用单引号或双引号括起来。比如"Hello, World!"'Python is fun'"123"都是字符串。字符串是不可变的,这意味着它们一旦被创建就不能被修改。
  • 布尔值:布尔值是二进制值,可以是True也可以是False。布尔通常用在条件语句中来控制程序的流程。例如,以下代码使用布尔值来控制是否执行循环:
*flag = True

if flag:
    # code to be executed
else:
    # code to be executed if the flag is False*
  • 列表:列表是一个 有序的 值集合。在 Python 中,列表用方括号([])括起来,列表中的值用逗号分隔。列表可以包含不同数据类型的值,包括其他列表。例如,下面的代码创建一个字符串列表:
*fruits = ["apple", "banana", "cherry"]*
  • 元组:元组类似于列表,但是和字符串一样,它是不可变的,这意味着一旦创建就不能修改它的值。在 Python 中,元组用括号(())括起来,元组中的值用逗号分隔。像列表一样,元组可以包含不同数据类型的值,包括其他元组。例如,下面的代码创建一个数字元组:
*coordinates = (10, 20, 30)*
  • 字典:字典是键值对的集合。在 Python 中,字典用花括号({})括起来,键值对用逗号分隔。字典中的关键字必须是唯一的,它们用于查找相应的值。例如,下面的代码创建了一个颜色及其十六进制值的字典:
*colors = {
    "red": "#ff0000",
    "green": "#00ff00",
    "blue": "#0000ff"
}*

流控制

流控制语句用于控制程序中的执行流。在 Python 中,您可以使用几个流控制语句,包括:

  • if-elif-else:if-elif-else语句用于根据指定的条件执行不同的代码块。if条款指定了被测试的条件,而elif(“else if”的缩写)和else条款指定了条件不满足时采取的替代行动。例如,下面的代码使用一个if-elif-else语句根据变量值打印一条消息:
*score = 75

if score >= 90:
    print("Excellent!")
elif score >= 80:
    print("Good job!")
else:
    print("Keep trying.")*
  • for :循环for用于迭代一系列值。在 Python 中,for循环具有以下语法:
*for variable in sequence:
    # code to be executed*

**variable** **sequence** 中当前值的占位符,循环中的代码针对 **sequence** 中的每个值执行。****

例如,下面的代码使用了一个for循环来打印列表中的元素:

***fruits = ["apple", "banana", "cherry"]

for fruit in fruits:
    print(fruit)***
  • while :当指定条件为True时,while循环用于重复一段代码。在 Python 中,while循环具有以下语法:
***while condition:
    # code to be executed***

执行循环中的代码,直到condition变为False。例如,下面的代码使用了一个while循环来打印从 1 到 10 的数字:

***n = 1

while n <= 10:
    print(n)
    n += 1***

n 达到 10 时,条件 n ≤ 10 评估为,while 循环内的代码执行。****

功能

函数是执行特定任务的可重用代码块。在 Python 中,函数是用def关键字定义的,后面是函数名和函数的 参数 用括号(())括起来。函数中的代码是缩进的,函数以一个指定返回值的return语句结束。例如,下面的代码定义了一个计算矩形面积的函数:

***def rectangle_area(width, height):
    area = width * height
    return area***

要调用一个函数,你只需要使用它的名字,后面跟着用括号括起来的必需参数。例如,下面的代码调用rectangle_area函数并打印结果:

***result = rectangle_area(10, 20)
print(result)  # Output: 200***

模块

一个模块就是一个 Python 文件 ,里面包含了相关函数和变量的集合。您可以使用模块来组织您的代码,使其更具可重用性和可维护性。在 Python 中,可以使用import关键字导入一个模块并访问它的函数和变量。例如,以下代码导入了math模块,并使用其sqrt函数来计算一个数字的平方根:

***import math

result = math.sqrt(9)
print(result)  # Output: 3.0***

您还可以使用from关键字从模块中导入特定的函数或变量。例如,以下代码从math模块导入pi变量,并使用它来计算圆的面积:

***from math import pi

def circle_area(radius):
    area = pi * radius ** 2
    return area

result = circle_area(5)
print(result)  # Output: 78.53981633974483***

结论

本指南简要介绍了 Python,包括其基础知识、数据类型、流控制语句、函数和模块。Python 是一种功能强大的通用语言,可用于多种应用。无论您是想学*一门新的编程语言的初学者,还是想拓展技能的有经验的开发人员,Python 都是一个很好的选择。

如果你喜欢阅读这样的故事,并想支持我成为一名作家,考虑注册成为一名媒体会员。一个月 5 美元, 让你无限制地访问媒体上的故事 。如果你用我的 链接 注册,我会赚一小笔佣金。

*****https://lmatalka90.medium.com/membership *****

神经网络激活功能综合指南:如何,何时,为什么?

原文:https://towardsdatascience.com/a-comprehensive-guide-of-neural-networks-activation-functions-how-when-and-why-54d13506e4b8

使用标题作为提示从稳定扩散生成的图片

在人工神经网络(ANN)中,这些单元被设计成大脑中生物神经元的松散副本。第一个人工神经元是由麦卡洛克和皮茨在 1943 年设计的[1],由一个线性阈值单元组成。这是为了模仿人工神经元不仅仅输出它们接收到的原始输入,而是输出激活功能的结果。这种行为是受生物神经元的启发,其中神经元会根据它接收的输入而触发或不触发。从感知机模型到更现代的深度学*架构,已经使用了各种激活函数,研究人员一直在寻找完美的激活函数。在这篇文章中,我将描述激活函数的经典属性以及何时使用它们。在第二部分中,我将介绍更高级的激活函数,如自适应函数,以及如何获得最佳激活函数。

激活功能特征概述

当你为你的神经网络寻找一个激活函数时,有几个属性必须被考虑进去。

  • 非线性:想到的主要性质就是非线性。众所周知,与线性函数相比,非线性改善了人工神经网络的训练。这主要是由于非线性激活函数允许人工神经网络分离高维非线性数据,而不是局限于线性空间。
  • 计算成本:在模拟过程中的每一个时间步都使用激活函数,特别是在训练过程中的反向传播。因此,必须确保激活函数在计算方面是可跟踪的。
  • 梯度:训练 ANN 时,梯度可能会出现消失或爆炸梯度问题。这是由于激活函数在每一步之后收缩变量的方式,例如,逻辑函数向[0,1]收缩。这可能导致网络在几次迭代后没有梯度可以传播回来。对此的解决方案是使用不饱和激活函数。
  • 可微性:训练算法是反向传播算法,需要保证激活函数的可微性,以保证算法正常工作。

经典激活函数

本节将描述人工神经网络遇到的一些最常见的激活函数,它们的属性以及它们在常见机器学*任务中的性能。

分段线性函数

该功能包括一个最简单的(如果不是最简单的)激活功能。它的活性范围是[0,1],然而,导数在 b 中没有定义。

b=2 的分段线性函数(我)

一般来说,这个激活函数只用作回归问题的输出。

Sigmoid 函数

sigmoid 函数是 20 世纪 90 年代早期最流行的函数选择之一。Hinton 等人[2]曾将其用于自动语音识别。该函数定义为:

这个函数是可微分的,这使得它非常适合于反向传播算法。

Sigmoid 函数(由我提供)

如图所示,sigmoid 函数是有界的,这是它受欢迎的原因。然而,它受制于一个消失梯度问题,并且已经表明[3]神经网络越深,用 sigmoid 作为激活函数对其进行训练的效果就越差。

双曲正切函数

在 21 世纪初,双曲正切函数取代了 sigmoid 函数。该函数定义为:

它的范围是[-1,1],由于以零为中心的特性,这使它适合于回归问题。

双曲正切函数(由我)

这个函数的主要缺点是饱和。事实上,双曲正切饱和得非常快(比 sigmoid 快),这使得人工神经网络很难在训练期间相应地修改权重。然而,值得注意的是,该函数通常用作递归神经网络中隐藏层单元的激活函数。

整流线性单元

校正线性单元或 ReLU 用于克服消失梯度问题。它被定义为

值得注意的是,与 sigmoid 和双曲正切函数相反,ReLU 的导数是单调的。

ReLU 功能(由我提供)

ReLU 函数通常用于分类任务,并且由于其无界的性质而克服了消失梯度问题。此外,由于没有指数函数,计算成本比 sigmoid 更便宜。这个函数用在几乎所有神经网络结构的隐藏层中,但不如输出函数那样常见。主要缺点是该函数对于负值有饱和问题。为了克服这个问题,已经提出使用泄漏 ReLU [4]:

所有这些功能都可以在两个标准分类任务上进行比较: MNISTCIFAR-10 。MNIST 数据库包含手写数字,CIFAR-10 包含来自 10 个类别的对象。我在这里总结了文献中每个激活函数的最佳性能,与架构无关(关于更多激活函数性能的伟大总结,请参见[5])。

设计最佳激活函数

前一节强调了激活函数的选择取决于网络必须解决的任务及其在网络中的位置,例如隐藏层或输出层。因此,与其试图找到最佳的激活函数,不如尝试击败已经存在的函数,因为我们知道何时使用它们。这就是引入自适应激活功能的原因。

参数激活函数

我想讨论的第一种自适应函数是参数激活函数。例如,可以使用参数双曲正切函数[7]:

参数双曲正切函数示例(由我提供)

参数 ab 适用于每个神经元。然后,使用经典的反向传播算法,参数将在训练期间变化。

参数激活函数的优势在于,通过添加参数,几乎可以以这种方式修改任何标准激活函数。当然,增加的参数增加了计算的复杂性,但它导致了更好的性能,并且它们被用于最先进的深度学*架构中。

随机自适应激活函数

引入自适应函数的另一种方法是使用随机方法。首先由 Gulcehre 等人[6]提出,它包括使用结构化和有界噪声,以允许更快的学*。换句话说,在将确定性函数应用于输入之后,会添加一个具有特定偏差和方差的随机噪声。这对于饱和的激活函数特别有用。随机自适应函数的一个例子如下:

随机自适应 ReLU(来自 CC-BY 许可证下的[5])

这种类型的噪声激活函数对于饱和度是有用的,因为噪声是在阈值之后应用的,所以它允许函数高于阈值。与参数激活函数的情况一样,在大多数机器学*任务中,这些函数的表现优于固定激活函数。

下面是几个自适应函数在经典的 MNIST 和 CIFAR-10 任务上的性能的总结:

自适应函数确实比它们的对应物具有更好的性能。

结论

*年来,神经架构变得越来越大,参数数十亿。因此,主要挑战之一是获得训练算法的快速收敛。这可以使用自适应函数来实现,尽管它们的计算成本很高。在分类和回归机器学*任务中都观察到了它们的较高性能。然而,仍然没有找到最佳激活函数的证据是,研究人员现在正在研究量化的自适应激活函数,以便在保持自适应函数快速收敛的同时降低计算成本。

LinkedIn 上与我联系。

参考

[1]麦卡洛克,沃伦和沃尔特皮茨。"对神经活动中固有思想的逻辑演算."数学生物学通报52.1(1990):99–115。

[2] Hinton,Geoffrey 等人,“用于语音识别声学建模的深度神经网络:四个研究小组的共同观点” IEEE 信号处理杂志29.6(2012):82–97。

3 奈尔、维诺德和杰弗里·e·辛顿。"校正的线性单位改进了受限的玻尔兹曼机器." Icml 。2010.

[4]马斯、安德鲁·l、奥尼·汉南和安德鲁·吴。"整流器非线性改进神经网络声学模型." Proc。icml 。第 30 卷。№1.2013.

[5] Jagtap,Ameya D .,和 George Em Karniadakis。“激活函数在回归和分类中有多重要?调查、性能比较和未来方向。” arXiv 预印本 arXiv:2209.02681 (2022)。

[6]古尔西雷,卡格拉尔等,“嘈杂的激活函数。”机器学*国际会议。PMLR,2016。

[7]陈、蔡子聪、张伟德。"一种具有函数形状自动调整的前馈神经网络."神经网络 9.4(1996):627–641。

模型校准综合指南:什么,何时,如何

原文:https://towardsdatascience.com/a-comprehensive-guide-on-model-calibration-part-1-of-4-73466eb5e09a

第 1 部分:了解如何校准机器学*模型,以获得合理且可解释的概率作为输出

阿迪·戈尔茨坦Unsplash 上拍摄的照片

尽管今天人们可以找到太多的博客谈论奇特的机器学*和深度学*模型,但我找不到多少关于模型校准及其重要性的资源。我发现更令人惊讶的是,模型校准对于某些用例来说可能是至关重要的,但却没有得到足够的重视。因此,我将写一个 4 部分的系列来深入研究校准模型。一旦你完成了这个系列,你将会学到一些东西。

学*成果

  • 什么是模型校准,为什么它很重要
  • 何时校准模型,何时不校准模型
  • 如何评估模型是否校准(可靠性曲线)
  • 校准机器学*模型的不同技术
  • 低数据设置中的模型校准
  • 校准多类分类器
  • 在 PyTorch 中校准现代深度学*网络
  • 校准回归变量

在今天的博客中,我们将关注前四个重点。

什么是模型校准?

让我们考虑一个二元分类任务和一个在这个任务上训练的模型。没有任何校准,模型的输出不能被解释为真实的概率。例如,对于猫/狗分类器,如果模型输出对于作为狗的例子的预测值是 0.4,则该值不能被解释为概率。为了用概率来解释这种模型的输出,我们需要校准模型。

令人惊讶的是,大多数开箱即用的模型都没有经过校准,它们的预测值往往过于自信或过于自信。这意味着,在很多情况下,他们预测的值接* 0 和 1,而他们不应该这样做。

解释未校准和校准模型的输出

为了更好地理解为什么我们需要模型校准,让我们看看前面的输出值为 0.4 的例子。理想情况下,我们希望该值代表的是这样一个事实,即如果我们拍摄 10 张这样的照片,并且模型以大约 0.4 的概率将它们分类为狗,那么在现实中,这 10 张照片中的 4 张实际上是狗的照片。这正是我们应该如何解释来自 校准 模型的输出。

然而,如果模型没有被校准,那么我们不应该期望这个分数将意味着 10 张图片中的 4 张将实际上是狗图片。

何时不校准模型

我们校准模型的全部原因是,我们希望输出在解释为独立概率时有意义。然而,对于某些情况,例如根据质量对新闻文章标题进行排序的模型,如果我们的策略是选择最佳标题,我们只需要知道哪个标题得分最高。在这种情况下,校准模型没有多大意义。

何时校准模型以及为什么校准至关重要

假设我们想要对火警是否正确触发进行分类。(我们今天将通过代码来讨论这一点。)这样的任务是至关重要的,因为我们希望彻底了解我们的模型的预测,并改进模型,使其对真实的火灾敏感。假设我们对两个例子进行测试,将火灾的可能性分为 0.3 和 0.9。对于一个未校准的模型,这并不意味着第二个例子可能导致实际火灾的次数是第一个例子的三倍。

此外,在部署该模型并收到一些反馈后,我们现在考虑改进我们的烟雾探测器和传感器。使用我们的新模型运行一些模拟,我们看到以前的示例现在得分为 0.35 和 0.7。

比方说,改进我们的系统需要 20 万美元。我们想知道我们是否应该为每个示例分别 0.05 和 0.2 的得分变化投资这笔钱。对于一个未校准的模型,比较这些数字没有任何意义,因此我们无法正确估计一项投资是否会带来实实在在的收益。但是如果我们的模型被校准,我们可以通过专家指导的基于概率的调查来解决这个难题。

通常,模型校准对于生产中的模型至关重要,这些模型通过不断的学*和反馈得到改进。

评估模型校准

现在我们知道了为什么我们应该校准我们的模型(如果需要的话),让我们看看如何识别我们的模型是否被校准。

那些想直接跳到代码的人可以在这里访问它。

数据集

今天,我们将看看来自 Kaggle 的电信客户流失预测数据集。你可以阅读更多关于协变量和烟雾探测器类型的信息,查看 Kaggle 数据集的描述页面。我们将尝试在此数据上校准 LightGBM 模型,因为 XGBoost 通常是未校准的。

数据集官方来自 IBM,可以在这里 免费下载 。它是在 Apache License 2.0 下授权的,如这里的 中的

可靠性曲线

可靠性曲线是一种很好的可视化方法,可以用来识别我们的模型是否经过校准。首先,我们创建从 0 到 1 的容器。然后,我们根据预测的输出划分数据,并将它们放入这些箱中。例如,如果我们以 0.1 的间隔收集数据,我们将有 10 个介于 0 和 1 之间的箱。假设我们在第一箱中有 5 个数据点,即我们有 5 个点 (0.05,0.05,0.02,0.01,0.02) ,其模型预测范围位于 0 和 0.1 之间。现在,我们在 X 轴上绘制了这些预测的平均值,即 0.03,在 Y 轴上绘制了经验概率,即基本事实等于 1 的数据点的分数。说出来我们的 5 分,1 分有地面真值 1。在这种情况下,我们的 y 值将是 1/5 = 0.2。因此,我们的第一个点的坐标是[0.03,0.2]。我们对所有的箱都这样做,并将这些点连接起来形成一条线。然后我们将这条线与这条线进行比较

y = x 并评估校准。当圆点在这条线以上时,模型低估了真实概率,如果圆点在这条线以下,模型高估了真实概率。

我们可以使用 Sklearn 构建这个图,它看起来像下面的图。

Sklearn 的校准曲线(图片由作者提供)

如你所见,该模型在 0.6 之前过于自信,然后在 0.8 左右低估

然而,Sklearn 的情节有一些缺陷,因此我更喜欢使用布莱恩·卢切纳博士的 ML-insights 中的情节。

该软件包显示了数据点周围的置信区间,还显示了每个区间(每个 bin 中)有多少数据点,因此您可以相应地创建自定义 bin 区间。正如我们还将看到的,有时模型过于自信,预测值非常接* 0 或 1,在这种情况下,软件包有一个方便的 logit-scaling 功能来显示在非常接* 0 或 1 的概率周围发生的事情。

这是与上面使用 Ml-insights 创建的图相同的图。

Ml-insight 的可靠性曲线(图片由作者提供)

如您所见,我们还可以看到每个条柱中数据点的直方图分布以及置信区间。

定量评估模型校准

根据我在阅读该领域的一些文献时收集的信息,捕捉模型校准误差没有完美的方法。文献中经常使用预期校准误差等指标,但我发现(正如你在我的笔记本和代码中看到的),ECE 会随着你选择的频段数量而大幅变化,因此并不总是可靠的。我将在以后更高级的校准博客中更详细地讨论这一指标。你可以在这个博客 这里 阅读更多关于 ECE 的内容。我强烈建议你通过它。

我在这里使用的一个基于卢切纳博士博客的指标是传统的对数损失。这里简单的直觉是,对数损失(或交叉熵)惩罚了在做出错误预测或做出与其真实概率显著不同的预测时过于自信的模型。你可以在这本 n 笔记本中了解更多关于定量模型校准的信息。


校准模型的方法

拆分数据

在我们进行任何校准之前,重要的是理解我们不能校准我们的模型,然后在相同的数据集上测试校准。因此,为了避免数据泄露,我们首先将数据分成三组——训练、验证和测试。

未校准性能

首先,这是我们的未校准 LightGBM 模型在我们的数据上的表现。

普拉特标度

普拉特标度假设模型预测和真实概率之间存在逻辑关系。

剧透——这在很多情况下并不成立。

我们简单地使用逻辑回归来拟合验证集的模型预测,并将验证集的真实概率作为输出。

下面是它的表现。

如我们所见,我们的原木损失在这里明显减少了。由于我们有许多模型预测接* 0 的数据点,我们可以在这里看到使用 Ml-insights 包(及其 logit 缩放功能)的好处。

保序回归

这种方法结合了贝叶斯分类器和决策树来校准模型,当我们有足够的数据来拟合时,它比 Platt scaling 更好。详细算法可以在这里找到

我使用 ml-insights 包来实现保序回归。

对于我们的数据,这似乎比普拉特缩放更有效。尽管在对不同数据分割和随机种子的实验结果进行平均或使用交叉验证(我们将在未来的博客中看到)后得出这样的结论会更明智。

样条校准

这个算法是由 Ml-insights 软件包的作者(布莱恩·卢切纳)给出的,可以在这篇论文中找到。

本质上,该算法使用平滑的三次多项式(选择该多项式是为了最小化某个损失,如本文中为那些对技术细节感兴趣的人所详述的那样),并且适合验证集及其真实概率上的模型预测。

样条线校准在我们的数据中表现最好(至少对于这种分割来说)。

这是他们在一个情节中的表现

预期校准误差及其缺陷

许多当代文献都提到 ECE 是衡量模型校准程度的一个指标。

以下是 ECE 的正式计算方法。

  1. 像我们之前做的那样选择 n 个箱
  2. 对于每个条柱,计算属于该条柱的数据点的模型预测的平均值,并根据该条柱中的数据点数对其进行归一化。
  3. 对于每个箱,还要计算真阳性的比例。
  4. 现在,对于每个箱,计算步骤 3 和步骤 4 中计算的值之间的绝对差,并将该绝对差乘以该箱中的数据点数。
  5. 将步骤 4 中计算的所有箱的结果相加,并通过所有箱中的样本总数对该相加和进行归一化。

计算 ECE 的代码可以在这个博客中找到,并且已经在我的实验中使用。

然而,在我的案例中,数据点在所有箱中的分布并不均匀(因为大多数数据点属于第一个箱),因此必须相应地为 ECE 选择箱。我们可以看到仓的数量如何直接影响算法中的 ECE。

例如,仅对于 5 个箱,未校准的模型似乎比所有其他方法具有更小的校准误差。

然而,当我们增加仓的数量时,我们可以看到模型校准实际上对我们的情况有所帮助。

在下面的代码片段中,可以验证这种效果。请忽略 OE(过度自信误差度量),因为它在文献中没有广泛使用。

对于 5 个箱子,我们有

对于 50 个箱子,我们有

对于 500 个箱子,我们有

对于 5000 个箱子,我们有

结论

在今天的博客中,我们看到了什么是模型校准,如何评估模型的校准以及这样做的一些指标,探讨了 ml-insights 包以及校准模型的一些方法,最后探讨了 ECE 的谬误。

下一次,我们将研究低数据设置的稳健校准,校准深度学*模型,并最终校准回归变量。

查看我的 GitHub 的其他一些项目。可以联系我 这里 感谢您的宝贵时间!

如果你喜欢这个,这里还有一些!

承认

我感谢布莱恩·卢切纳博士对与本博客相关的各种话题的帮助和建议。我还发现他在 YouTube 上关于模型校准的播放列表非常详细和有用,我的大部分实验都是基于他的视频。

参考

  1. https://www.youtube.com/playlist?list = plevfk 5 xtwhybw 22d 52 etymvcpxe 4 qfik
  2. https://cseweb.ucsd.edu/~elkan/calibrated.pdf
  3. https://www . unofficialgogledatascience . com/2021/04/why-model-calibration-matters-and-how . html
  4. https://towards data science . com/classifier-calibration-7d 0 be 1e 05452
  5. https://medium . com/@ wolfram alpha 1.0/evaluate-the-performance-of-a-model-in-high-risk-applications-using-expected-calibration-error-and-DBC 392 c 68318
  6. https://arxiv.org/pdf/1809.07751.pdf

通用机器学*预处理技术综合指南

原文:https://towardsdatascience.com/a-comprehensive-guide-to-common-machine-learning-preprocessing-techniques-a7739f0ab2b5

各种不同预处理技术的概述和故事,以及它们在机器学*中的应用

(图片由 pixabay 上的 padrefilar 拍摄)

一个非常普遍的误解是,新的和有抱负的数据科学家在进入该领域时经常会有一个误解,即大多数数据科学都围绕着人工智能、机器学*和其他流行和晦涩的术语。虽然这些术语中有许多肯定是数据科学的不同方面,而且我们确实在编写机器学*模型,但大多数工作通常都是致力于其他任务。与许多其他技术领域相比,数据科学的一个独特之处在于,数据科学是一个工作领域中不同领域和主题的集合。此外,数据科学可以更加多样化,一些数据科学家可能专门从事数据管道方面的工作,而其他人可能专门从事神经网络方面的工作。一些数据科学家甚至过度专注于可视化、仪表板和统计测试——针对商业、医学、生物学和其他领域。

虽然作为数据科学家只做一件事可能并不常见,但有一件事是所有数据科学家都做的;

处理数据。

处理数据通常涉及几个关键步骤,包括数据争论、清理、格式化、探索和预处理。当开始处理数据并创建某种数据分析时,通常会争论、清理、格式化和探索这些数据。然而,预处理更有趣,因为预处理方法将我们的数据技术带入了一个全新的方向。而对于其他操作,清理、格式化、探索,我们试图将数据解释为人类,在预处理的情况下,我们已经将数据解释为人类,现在正试图使数据更容易被统计算法解释。也就是说,预处理的数量及其有效性会对机器学*模型的性能产生严重影响。鉴于这一点的重要性,今天我们将介绍最流行的预处理技术,解释它们是如何工作的,然后我们将自己编写一些技术,以便更好地演示它们是如何工作的。

https://github.com/emmettgb/Emmetts-DS-NoteBooks/blob/master/Julia/comprehensive machine-learning techniques.ipynb

特征类型—简要回顾

为了真正理解不同的预处理技术,我们首先需要对我们实际使用这些技术的数据有一个起码的了解。在数据世界中,有几种主要包括连续、标注和分类要素的要素类型。连续特征被描述为每个观察值都包含数值的特征。也就是说,连续特征几乎总是实数、虚数或复数,当它们不是实数、虚数或复数时,它们是一个数字的表示。另一方面,分类特征可以包含各种类型,包括不同的数字和字符串。标签也可以是任何类型,但通常是StringDate

连续特征的一个例子是从冰淇淋车观察每天的冰淇淋销售。如果我们也记录最受欢迎的日常口味的冰淇淋,这将是一个分类特征。最后,日期将被视为该数据的标签。当涉及到使用预处理方法时,理解这些不同类型的特性将会非常重要。这对于理解数据也非常重要,因为有些情况下,要素类型乍一看并不明显。

列车测试分离

第一种预处理技术不一定是典型的预处理器。训练测试分割是一种通过创建两个独立样本来测试模型性能的技术。第一个样本是训练集,用于告诉模型您的特征如何与您的目标或您预测的事物相关联。测试集评估模型与特征和目标的关联程度。

训练测试分割相对简单。Python 程序员可能会发现sklearn.model_selection中的train_test_split是用 Python 做这件事的有用方法。就自己编程而言,过程相对简单。我们正在寻找的输入是一个数据帧,我们正在寻找的输出是两个按一定比例分割的数据帧。

using DataFrames
using Random

我的函数将通过抓取一个随机子序列,然后根据该随机子序列分离数据帧值来实现这一点。

function tts(df::DataFrame; at = .75)
    sample = randsubseq(1:size(df,1), at)
    trainingset = df[sample, :]
    notsample = [i for i in 1:size(df,1) if   isempty(searchsorted(sample, i))]
    testset = df[notsample, :]
    return(trainingset,testset)
end

这可以通过简单的理解很容易地实现。现在让我们试试这个函数,看看我们的分割数据:

df = DataFrame(:A => randn(20), :B => randn(20))train, test = tts(df); show(train); show(test)

作者图片

缩放器

我想讨论的下一个预处理技术是定标器。缩放器用于连续要素,将数据形成以某种方式改变数值距离的量。有各种不同的缩放器示例,如统一缩放器和任意重新缩放器,但机器学*中最常用的缩放器类型肯定是标准缩放器。此缩放器使用正态分布来调整要素的数值距离。

在正态分布中,每个值都是与平均值的关系。这种关系是用平均值的标准偏差来衡量的,所以在大多数情况下,数字会变成某种小于 5 的浮点数。抛物线的中心,也就是我们大部分数据所在的地方,被设为 0。具有数值距离的每个值由该值相对于平均值的标准偏差的数量来缩放。这个距离也不是绝对的,但距离的统计显著性是绝对的,因此该值可以是-2 或 2,但-2 和 2 都是统计显著性的。

正态分布对于机器学*的缩放特征非常有用,因为它减少了数值距离,并使用人口中心作为新的参考点。我喜欢把它想象成我们正在创造一个新的零,我们的新零是我们人口的平均值。假设一个标准的定标器只是一个正态分布,那么创建一个定标器的公式非常简单,而且完全相同。首先,我们需要均值和标准差。对于 x 内的每个样本,我们要减去平均值,然后用标准差除差值。

function standardscale(x::Vector{<:Real})
    N::Int64 = length(x)
    σ::Number = std(x)
    μ::Number = mean(x)
    Vector{Real}([i = (i-μ) / σ for i in x])
end

结果与我前面讨论的非常相似,在这个例子中,大多数值都小于 2。所有这些值都低于 2 的原因是因为数据分布非常均匀,因为它只是计数:

x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
standardscale(x)
10-element Vector{Real}:
 -1.9188064472004938
 -1.4924050144892729
 -1.066003581778052
 -0.6396021490668312
 -0.21320071635561041
  0.21320071635561041
  0.6396021490668312
  1.066003581778052
  1.4924050144892729
  1.9188064472004938

进一步展示这个分布,如果我们的第一个数字与人口的平均值相比是完全荒谬的,我们会看到这个值会惊人地增长。这也将成为一个离群值,这将使我们的其余数据偏离均值。这是因为每当增加一个较大的数字时,平均值从零开始增加:

10-element Vector{Real}:
 17.290069740400327
 -3.0348109528238902
 -2.756387929629038
 -2.4779649064341855
 -2.199541883239333
 -1.9211188600444808
 -1.6426958368496287
 -1.3642728136547764
 -1.085849790459924
 -0.8074267672650718

编码器

接下来,我们将讨论编码器。编码器用于将计算机无法量化的数据转换成计算机可以量化的数据。编码通常用于分类特征,但是您也可以对标签进行编码以便由计算机进行解释,这对于日期字符串之类的东西非常有用。可能机器学*中使用的最流行的编码器形式是一键编码器。该编码器将分类观察值转换为二进制特征。考虑以下特性:

greeting = ["hello", "hello", "hi"]
age = [34, 17, 21]

这两个特征的分类是greeting特征。一个热编码将为每个类别创建一个新的二进制特征。在这种情况下,有两个类别,"hello""hi",因此该特征的一个热编码版本将是以下两个特征:

hello = [1, 1, 0]
hi = [0, 0, 1]

我们正在做的是检查每个值是否属于该类别,并根据该信息构造一个 BitArray。为此构建一个函数,就像典型的过滤或屏蔽一样简单。我们只需要为特性集中的每个值创建一组 BitArrays:

function onehot(df::DataFrame, symb::Symbol)
    dfcopy = copy(df)
    [dfcopy[!, Symbol(c)] = dfcopy[!,symb] .== c for c in unique(dfcopy[!,symb])]
    dfcopy
end

我在 Julia 中通过传播==操作符来快速创建一个Vector来实现这一点。

df = DataFrame(:A => ["strawberry", "vanilla", "vanilla", "mango"], 
               :B => [1, 2, 3, 4])
encoded_df = onehot(df, :A)show(encoded_df)

作者图片

稍微容易理解的是顺序编码器。该编码器使用集合的order,以数字方式将每个值作为一个类别。一般来说,顺序编码器是我在分类应用中的第一选择,在这些应用中,需要考虑的类别较少。这种类型的编码通常是这样完成的:为特性集中的每个类别创建一个元素索引引用,然后每当我们遇到那个值时就通过键调用那个引用,检索索引来替换它。

function ordinal(df::DataFrame, symb::Symbol)
    lookup = Dict(v => i for (i,v) in df[!, symb] |> unique |> enumerate)
    map(x->lookup[x], df[!, symb])
endordinal(df, :A)4-element Vector{Int64}:
 1
 2
 2
 3

另一个非常容易理解的编码器是浮点编码器。有时也被称为标签编码器,这个编码器将一个String的每个Char变成一个Float。这样做的过程非常简单,在大多数情况下会留下一种类似于序数编码的编码,但是数字不是索引,而是随机的数字组合。不用说,这对于检查标签更有用,因为在要读取的字符之间可能存在微小的可检测的差异。

function float_encode(df::DataFrame, symb::Symbol)
    floatencoded = Vector{Float64}()
    for observation in df[!, symb]
        s = ""
        [s = s * string(float(c)) for c in observation]
        s = replace(s, "." => "")
        push!(floatencoded, parse(Float64, s))
    end
    floatencoded
end

关于编码器的更多信息,以及解决这些问题的更传统的面向对象方法,您可以在我写的另一篇文章中读到更多关于编码器的信息:

估算者

估算器是一种简单且自动化的方法,可以消除数据中的缺失值。估算者通常用分类问题中的多数类或连续问题中的平均值替换这些缺失值。估算者所做的是获取有问题的值,并将它们转换成某种统计显著性较低的值,这通常是数据的中心。我们可以构建一个简单的均值估算器来替换缺失的连续值,如果缺失,只需用均值替换该值即可。唯一的问题是,每当我们试图得到一个既有缺失值又有缺失数的向量的平均值时,我们都会得到一个错误,因为我们不能得到一个缺失值和一个整数的和。因此,我们需要创建一个奇怪的函数,它给出平均值,但如果数字丢失,则跳过该函数:

missing_mean(x::Vector) = begin
    summation = 0
    len = 0
    for number in x
        if ismissing(number)
            continue
        end
        summation += number
        len += 1
    end
    summation / len
end

然后,我们将通过使用此函数计算平均值来创建估算值,然后用该平均值替换每个缺失值。

function impute_mean(x::AbstractVector)
    μ::Number = missing_mean(x)
    [if ismissing(v) μ else v end for v in x]
endx = [5, 10, missing, 20, missing, 82, missing, missing, 40] impute_mean(x)9-element Vector{Real}:
  5
 10
 31.4
 20
 31.4
 82
 31.4
 31.4
 40

分解

数据处理领域的下一个主要概念是分解。分解允许人们获取具有多个维度的特征,并将这些维度压缩成单个一致的值。这是很有用的,因为它可以用于为模型提供较少的单个特征,同时仍然让这些特征产生统计效果。这对于性能来说也是非常有价值的,并且在机器学*中非常普遍。最流行的分解技术是奇异值分解。还有一种非常酷的技术叫做随机投影。我在这里不打算详细介绍这两种技术,但是我已经写了一些关于这两种技术的文章,可以从中获得更多信息:

[## 深入奇异值分解

towardsdatascience.com](/deep-in-singular-value-decomposition-98cfd9532241)

结论

预处理是数据科学过程中不可或缺的一部分,对于正确处理非常重要。理解不同的预处理技术和它们的应用可以给给定模型的准确性带来真正的性能提升。大多数预处理技术相对简单,但是,它们对于创建更精确的模型仍然非常有效。下次你建模时,如果你想用最少的努力显著提高你的准确性,这些预处理技术真的可以帮上忙!感谢您的阅读!

数据科学硕士应用综合指南

原文:https://towardsdatascience.com/a-comprehensive-guide-to-data-science-masters-application-c9fbefe41825

数据科学本科生之旅第 4 部分

瑞安·霍夫曼在 Unsplash 上的照片

目录

行动时间表
早点拿定主意
知道有哪些项目适合你
早点拿到 GRE、TOEFL、简历和 lor
磨砺目的陈述/个人陈述
明智地选择你的目标项目
申请?!!!
总结算法
申请手册
第一章— SOP/PS
第二章—多样性声明
第三章— LOR
第四章—简历/CV
第五章— GPA、GRE &托福

我希望当我申请数据科学硕士项目时,有人能写一篇这样的博客。作为一名数据科学本科生,我知道我想去读研究生,并继续在 DS 中学*很长一段时间,但实际应用可能会很复杂。从去年十二月到今年二月,我终于完成了所有的申请工作,并被我梦想中的项目录取了!

申请研究生院是一次情感的自省之旅。现在是 2022 年 3 月,所以当我的记忆仍然清晰,斗争仍然感觉真实的时候,我正在写下一切。我希望这篇文章内容丰富,实用,但也是个人的。

在我们开始之前,先简单介绍一下我的背景。

我是一名本科生。我是一名来自上海的留学生。
我计划在美国申请 12 个 DS 硕士项目,但实际上只申请了 10 个,因为我在最后 2 个项目的截止日期前收到了我梦寐以求的录取通知书。
统计:GPA 3.9+,GRE 325+,托福 110+。

行动时间表

早点下定决心

去读研是一个重大的人生决定。我在大学期间已经思考这个问题 4 年了。我应该去读研还是直接去工作?在中型 DS 社区呆了很长时间,看了这么多自学成才的数据科学家的鼓舞人心的故事,我知道接受正规教育并不是打入 DS 领域的唯一途径。但我热爱学术界,在我的本科学*期间,我遇到了许多伟大的研究人员和学者。因此,我选择拒绝一些非常棒的全职工作,转而去读研。

早点下定决心意味着你有更多的时间积累经验,为申请研究生做准备。以下是一些实际的好处:

  • 早点考 GRE(和托福)
    你不能在申请截止日期的前一天参加这些考试,所以最好有充足的时间准备,给 ETS 足够的时间发送你的考试成绩。
  • 保持高平均成绩更容易如果我说平均成绩不重要,那我是在撒谎。如果你申请的是竞争性项目,这一点尤为重要。保持高 GPA 需要多年的持续努力。当你一开始只有 2.0 分时,不可能在一个学期内把你的平均成绩提高到 3.8 分。
  • 有更多的时间与潜在的推荐人建立关系网
    我讨厌听起来这么功利,但让我们面对现实吧,申请研究生院需要至少两封推荐信(LOR ),没有什么比回想一下并意识到你从未与愿意做你推荐人的 DS 专家建立关系更糟糕的了。举例来说,我在学术界有一些了不起的教授,在行业中有一些导师,他们了解我的能力和职业规划,与我讨论读研的事情,帮助我确定读研是正确的选择。我和他们走得很*,熟悉了他们的工作,这一切都激励我进入学术界,成为一名数据科学家。当我需要 lor 的时候,他们很乐意帮忙。然而,保持一种真正的、互惠的、有助于你成长的导师-学员关系需要时间和努力。

大致的时间线是,大二的时候我就知道我要读研了。当然,尽早做好准备更好,但是如果你觉得时间不多了,不要惊慌失措。

知道有什么项目适合你

既然你已经确定参加数据科学硕士课程是正确的选择,那么关注相关信息就很重要了。也许你正在寻找在线课程或兼职课程,或者可能是全职课程(像我一样)。你首先需要知道有哪些选择。例如,我即将成为母校的 NYU 大学有一个特别的 DS 硕士项目,所以我早就知道了。

对我来说,一个大致的时间表是,在我大三的时候,我从高年级学生那里收集了各种关于他们申请并被录取的项目的信息。我很清楚我应该关注哪些项目。我保留了一份谷歌节目单,与同样申请 DS Masters 的朋友们分享。

早点完成 GRE,托福,简历和 LORs

我从来都不喜欢准备 GRE 考试,但是申请的时候需要 GRE 成绩。作为一名国际学生,我不能总是放弃托福,所以我也必须参加。考试需要时间和精力来准备。大三寒假考 GRE(准备了 1 周),大四前一年暑假考托福(前一天准备)。我每门考试只考了一次。也许我可以做得更好,但我想花时间去做生活中更令人兴奋的事情,你知道。我确实有一些朋友,他们纯粹的意志力帮助他们在一年内通过了 4 门 GRE 考试。我绝对敬畏。最终一个满意的分数是否值得所有的麻烦取决于你的期望水平。我建议早点考 GRE 和托福,因为到你申请的时候就不需要担心这些考试了。

我准备了一份 2 页的学术简历,是为 DS 申请量身定做的。它比任何求职申请的简历都要长,而且有更多的细节。这个我以后再讲。

另一方面,LOR 要棘手得多。出于礼貌,询问你的推荐人,确认他们是否愿意尽早给你写信。站在他们的立场上,你会理解他们非常忙,可能有很多其他学生问他们同样的问题。不要等到最后一刻才去问你的教授或导师!!这会降低你获得 LOR 的机会。我在 9 月底(大四秋季学期)问了我所有的推荐人。当然,一个例外是,如果你在高三秋季学期遇到了一个很棒的推荐人。期中考试之后,也就是你被评估的时候,你的教授可能会对你的能力和目标有更好的了解。期中考试后向他们要一个学*成绩仍然不算太晚。

对我来说,一个粗略的时间表是,我在高年级秋季学期开始时完成了这 4 项。

目标陈述/个人陈述

我花了很多个深夜来写我的目标陈述,或者个人陈述,因为它既垃圾又漂亮。是的,我对我的 SOP/PS 又爱又恨。我觉得 SOP 和 PS 没有太大区别。除非学校特别列出了他们论文的要求,否则我使用了同样的 1000 字版本,结合了我的 DS 经验和个人愿望。

大致的时间线是,我开始反思自己过去的经历,思考在大四之前的那个暑假,我要怎么写 SOP。这就是我开始写博客的原因!它帮助我理清思路,组织我的职业经历,写了关于我的大学旅程的前几篇博客。与此同时,我在大学期间一直在积极研究关于 DS 职业的一切,我不断思考为什么我喜欢 DS,为什么 DS 适合我的职业生涯,并反复检查这一匹配对我来说是真正最佳的。我知道很多人从 DS 毕业,从事软件工程师的工作。我真的需要理清为什么 DS 正是我想要的,然后把我的想法写在一篇文章里。

但我实际上并没有打开一个新的 Word 文件,直到高三秋季学期开始才开始写作。我希望写 SOP 像写博客一样简单,但事实并非如此。SOP 需要信息丰富、有趣、简洁。信息丰富,因为你需要回顾 3 年的 DS 经验;娱乐性,因为你不希望你的文章平淡无奇,令人昏昏欲睡;简洁,因为你需要使用正式的英语来讲述你的故事,用 1000 个单词。要产生一个令人满意的 SOP 确实需要大量的修改。

明智地选择你的目标项目

还记得你列出的所有可用程序吗?是时候缩小范围,选择你的目标项目了。这一切都归结为一个匹配的游戏。你在为自己寻找合适的项目,研究生院也在为他们寻找合适的候选人。

了解你的目标项目 研究生院通常会有一个专门的网页,详细介绍他们的 DS 项目。你可以通过阅读网上的信息获得很多信息。在这里,我列出了一些你在选择程序时可能会考虑的方面:

  • 课程
  • 群组规模
  • 学校位置
  • 课程长度
    美国大部分全日制 DS 课程为 1 至 2 年,极少数例外可延长至 3 年。
  • 学费
  • 该项目是以研究为导向还是以职业为导向?如果你想在未来攻读博士学位,以研究为导向的项目可能会适合你。否则,以职业为导向的项目很棒,因为它们经常为学生提供实*机会。
  • 教员研究兴趣
  • 获得签证和 OPT/CPT 容易吗?如果你想作为国际学生在美国工作,获得 OPT 和 CPT 是至关重要的。
  • 这个项目需要 GRE 吗?
  • 机构声誉
  • 你对这所大学和这个项目有感觉吗?
    我觉得这个问题是最重要的一个。离线学*和在大学校园里度过一年多的时间可以塑造新的视角,改变一个人。你同意这所大学的价值观和倡议吗?

检查项目网站已经是大量的工作了,但是如果你真的对某个项目感兴趣,你可以去他们的 zoom info 会议或者和他们的项目学生大使谈谈。

了解自己 这一部分要复杂得多,因为了解自己是一生的努力,但至少你知道最基本的东西。你有学校强加给你的衡量标准,即 GPA、GRE 等。一些大学会在他们的网站上告诉你他们的平均和中等 GPA 和 GRE。你可以快速检查你是否像一个有竞争力的候选人。

现在你已经做了一些研究,你可以再列一个你的安全、匹配和到达学校的清单。我迁移到观念来跟踪一切。还有 20 个标有“不适用”的没有出现在截图上。

作者图片

这是我的目标学校列表,其中有两所是我最喜欢的。经过仔细考虑,我选择了每一个。因为我很在意自己是否与这所学校产生共鸣,所以我与所有这些学校或它们所在的城市都有某种私人联系。我的朋友来自这里列出的 12 所大学中的 10 所,许多我亲*的教授都毕业于这些学校。我个人只去过其中的三所学校,但是我很清楚他们的氛围,我知道我想成为我尊敬的朋友和教授。

申请?!!!

没那么快。当你开始申请的时候,这个列表一定会经历如此之多的更新,以至于你可以称之为你的动态数组。当你在写你的 SOP 时,你会发现自己一遍又一遍地回到项目网站上,仔细检查所有的小细节。

但是你可以从申请过程开始,首先创建一个帐户,并填写基本信息,如姓名和教育背景。我的建议是快速浏览每个申请页面上每个部分的所有内容。如果在某些情况下,您无法在填写当前页面之前转到下一页,只需在输入框中键入一些内容或上传一个空白的 PDF 文件,以迫使系统继续前进。您通常可以在以后更改这些信息,但一定要仔细检查。

全面了解对您的要求有助于您:

  • 进入推荐页面,尽快给你的推荐人发送电子邮件
  • 进入你的考试成绩页面,查看学校是否收到了你的 ETS 官方成绩
  • 找出那些你不能马上填写的棘手的页面,如 SOP、多样性、教员兴趣等,并写下说明。

现在你对正在发生的事情有了一些想法,你可以回去对程序进行研究并润色你的 SOP。我将在本博客的下一部分详细讨论这一点。

汇总算法

这里有一个算法来总结到目前为止的一切,因为我最*为我的顶点研究读了很多论文。我能想到的只有算法。

作者图片

应用手册

第 1 章— SOP/PS

我在一开始就说过,申请是一次自我反省的旅程,而 SOP 应该是你全身心地投入的地方。

  • SOP vs PS
    我个人并不认为 SOP 和 PS 在我的申请中有什么不同,因为我从未被要求同时提交它们。但是从我搜索的结果来看,它们应该是不同的。SOP,或(学术)目的声明,纯粹是关于学术背景、技术能力、研究兴趣和职业规划等事实。PS,个人陈述,是讲述你的人生故事,确定困难,解释弱点(例如,为什么成绩单上有一个 C)。然而,由于没有程序要求我同时提交 SOP 和 PS,我只写了一篇 1000 字左右的 2 页短文,并将两者合并为一篇。
  • SOP/PS
    的要求
    1。描述你过去和现在与这个项目相关的工作
    2。是什么让你有资格参加这个节目
    3。你为什么有兴趣申请这个项目?职业/研究计划。为什么,这个项目将如何帮助你实现你的目标
  • 阅读别人的 SOP 以供参考 如果你不确定如何开始写自己的,你可以很容易地在网上找到别人的 DS SOP。我认为在你开始写作之前阅读一些是没问题的,但最终,你的 SOP 需要是你的,并且只为你量身定制。
  • 引言 基于我上面解释的原因,SOP 不仅仅是罗列你的 DS 经历。相反,它讲述了一个关于你是谁,你有什么能力,你想成为谁的故事。你需要一个强有力的第一段作为扣人心弦的介绍,强调你需要以某种方式连接到 DS 的生命中的一个关键时刻。这可能是你学*的一个转折点,你意识到你原来的专业已经不够用了,你想转而追求 DS。这可能是你 DS 之旅中最大的成就,坚定了你的决心。也可能是非常私人的。可能你想学 DS 的原因是因为你的家庭或者某种人生经历。第一段就像一部好电影的开头。它介绍了主角,你,给观众留下了深刻的印象。
  • 然后,你可以用一种合乎逻辑的方式展示你的经历。所谓逻辑,我的意思是主体段落不需要按时间顺序排列。他们只需要理解并展示你的进步。但如果你所拥有的只是在未来学*中茁壮成长的学术能力,这不会让你在成千上万的申请者中脱颖而出。你需要深入挖掘你的核心,无论是你的特点还是你的信仰。为什么 DS 是你想学*的一个领域?也许是因为你好奇的天性。一定有更深层的东西驱使你走向 DS。考虑到这种联系,很有可能你将来也会坚持使用 DS。
  • 职业/研究计划 我想只有我们几个人知道自己未来到底想做什么,但是在 SOP 里,你的职业或研究计划还是详细一点比较好。所以,写这个 SOP,是一个很好的时间去思考自己这辈子想完成什么。你可以在倒数第二段写下你的答案,但是需要和你之前的经历有一些联系。不然感觉怪怪的,编出来的。残酷的事实是,这个世界倾向于奖励那些对自己的生活有明确想法的人。
  • 在最后一段,你可以确定适合你的研究兴趣的具体课程、你想合作的教授、你想加入的俱乐部,以及任何你认为合适的事情。因为我申请了多个项目,所以除了最后一段专门介绍每个项目之外,其他都保持不变。
  • SOP 的脊柱我认为写 SOP 就像为电影中的主角创作剧本,所以我打算用行话“脊柱”来指代英雄故事的核心(我对电影制作也很感兴趣)。同样,一份强有力的 SOP 展示了申请人的一个或两个关键的可取特征。雄心勃勃、好奇、坚定、积极主动、有组织、勤奋、热情等等,这些都是积极的特征,但你不能在标准操作程序中大声说出来。你需要通过描述与这些形容词相对应的你过去的行为来展示你的“脊梁骨”。否则,你的“电影”可能不够吸引人,“台词”可能听起来像是说教。
  • 你可以请你大学的写作机构或者你的朋友帮忙校对你的 SOP。我觉得有一个专业完全非技术的校对很重要。他们也许能发现一些用其他方法无法发现的东西。例如,他们可能会发现你写的东西太专业,他们根本无法理解。如果是这样,稍微低调一点可能是个好主意。肯定有一些方法可以用更少的技术术语来描述一个项目。虽然招生委员会的每个人都可能来自 STEM 背景,但确保你的 SOP 的可读性仍然很重要。

第 2 章——多元化声明

多元化声明通常是可选的,但我见过学校要求个人声明,而事实上,它要求多元化声明。

通常,你会谈到作为一个多元化的个体,你如何为大学的多元化做出贡献。多样性存在于性别、种族、性取向、宗教、国籍、社会经济地位、生活经历、家庭条件等等。你能写的东西有很大的灵活性。例如,我认为将我的多元化背景与我的专业联系起来会很好,所以我写了关于多元化对消除人工智能偏见如何重要的文章。

第三章——LOR

lor 来自你的教授或实*/工作主管。他们的专业领域与 DS 越相关越好,因为他们能更好地证明你的技术能力。我听人说,一个人可能更喜欢有更大头衔的人来写你的 LOR,但我不认为头衔是一个人应该太在意的东西。相反,我相信你应该找到真正了解你,理解你的职业或研究目标的推荐人。他们对你的熟悉可以很自然的呈现在 lor 里,录取委员会也会感觉到。

  • LOR 内容 LOR 需要表现出候选人有很强的量化技能,扎实的软技能,以及任何让你的推荐者眼前一亮的东西。
  • 是什么造就了强大的 LOR?
    你的推荐人可能之前写了那么多 lor,都已经有模板了。然而,模板对我们学生来说是不好的,因为模板不会让你比别人更有竞争力。因此,这就是为什么找到熟悉你的推荐人会有帮助。真正了解你的人,很可能会写一个量身定制的 LOR,而不是用模板。
  • 我应该放弃访问 lor 的权利吗?
    一般来说,放弃自己的权利会让 LOR 更可信。我有教授专门让我放弃权利,我就全部放弃了。

第 4 章——简历

求职面试的简历通常只有一页,而学术简历可以有两页甚至更多。但是第三页以后可能不会得到太多的关注。所以一个经验法则是先写重要的东西,最好在前两页。

  • 板块 我的简历有 7 个板块:教育、工作经历、研究&发表、奖学金&荣誉、活动、数据科学竞赛&项目和技能。您当然可以根据需要添加更多的部分或删除一些部分。我把这些部分安排成整整两页。
  • 给你的项目添加链接 我唯一想强调的是在你的每个项目和研究旁边添加你的 GitHub 或网站或论文的链接。这表明你确实为项目做出了很大贡献,也给了录取委员会一个进一步评估你技术能力的方法。如果你有可视化项目成果的网站,那是最好的。

第五章——GPA、GRE 和托福

这些数据很重要,但没那么重要。如果他们中的一些人没有你想要的那么高,不要太沮丧。只要学会了 DS 必备的技能,GPA 和 GRE 就仅仅是数字了。从长远来看,他们不会阻碍你成为数据科学家的梦想。然而,获得一个像样的分数确实需要大量的时间和精力,所以还是那句话,最好在实际申请之前就做好一切。

第 6 章—面试

我个人没有申请任何需要面试的项目。但是从我从朋友那里了解到的信息来看,录取面试更多的是行为面试,而不是技术面试。你需要准备在简历中谈论一些项目细节,并回答一些一般性的问题,例如,你是如何克服团队工作中的挑战的,或者你最大的弱点是什么。获得面试是一个积极的信号,表明你离录取更*了一步,所以要自信,准备好聊天。招生和教授也是人。

第 7 章—杂项

  • 投资组合网站
    如果还有什么能让一个应用程序变得更强大,那就是拥有一个 DS 投资组合网站。如果你能建立自己的网站,这也是展示一些软件工程技能的好方法。一个简单又省钱的方法是在 GitHub 上托管你的投资组合网站。
  • 清理 GitHub 页面
    但是我敢打赌大部分正在申请 DS 程序的人都有一个 GitHub 页面,所以你可以通过清理你的 GitHub 页面来脱颖而出:
    1。添加个人资料页面/关于我页面
    2。为每个项目添加一个合适的 README.md
  • 研究生招生主任
    的提问:我迄今为止写的大部分文章都是从申请人的角度出发的(希望你能理解),所以看看招生人员在寻找什么是非常有帮助的。在这个 Reddit Q & A 中,一位研究生招生主任非常慷慨地抽出时间,回答了学生们的许多好问题。主任的建议一般适用于 STEM,但也适用于 DS。

第 8 章——心态

等待申请结果是最糟糕的。每个节目都像是一张一百美元的彩票。我一直在想我是否有资格参加这些项目,并一直在想也许可以申请更多的项目以确保万无一失。我认为有焦虑是完全正常的,但请确保与某人交谈,并以健康的方式处理这些感觉。然后有一天,你会得到你的第一份工作,一切都会变得不同,但是是好的方面。

最后的话

尽管我很想给你展示一些关于 DS Masters 申请的建议(希望是有帮助的),但申请仍然是你自己的自我反思和自我发现之旅。申请过程是你在大学期间停下来思考未来的绝佳机会。我想用这个博客鼓励申请人真正多思考。请不要仅仅因为这个领域看起来很热,就去做自己不喜欢的事情。

对我来说,工作,也就是我所做的,对我的身份和幸福极其重要。我为自己的工作感到自豪,并享受出色完成简单任务的乐趣。因此,我喜欢思考整个申请过程以及与 DS 相关的任何事情。你应该从我写中型博客的事实中了解到这一点。所以,我的主要挑战是我的 SOP 中不包括什么,因为我有一百万件事情要写。

我的应用程序的最新更新:

到今天为止,我已经收到了 6 所学校的回复。我得到了 5 个广告和 1 个拒绝(嘿,人无完人)。我很兴奋能去我梦想中的学校,但真的,我会很高兴得到任何邀请继续我的 DS 之旅。我不打算在这个博客中提到大学的名字,因为重要的是找到适合你所热爱的东西。(如果你真的读到这一部分,感谢你的好奇心,但我相信如果你真的想知道,你可以找到答案。毕竟我们都是科研人员:) )

如果我在这个博客中有什么未能涵盖的,请留下评论或直接联系我。我想尽力帮忙。

感谢您的阅读!希望这对你有帮助。

使用 Pytorch 进行图像增强的综合指南

原文:https://towardsdatascience.com/a-comprehensive-guide-to-image-augmentation-using-pytorch-fb162f2444be

一种增加数据量并使模型更加稳健的方法

照片由丹金Unsplash 上拍摄

最*,在从事我的研究项目时,我开始理解图像增强技术的重要性。这个项目的目的是训练一个健壮的生成模型,能够重建原始图像。

所解决的问题是异常检测,这是一个非常具有挑战性的问题,因为数据量很小,而且模型不足以单独完成所有工作。常见的情况是使用可用于训练的正常图像来训练双网络模型,并在包含正常和异常图像的测试集上评估其性能。

最初的假设是,生成模型应该很好地捕捉正态分布,但同时,它应该不能重构异常样本。怎么可能验证这个假设?我们可以查看重建误差,对于异常图像,该误差应该较高,而对于正常样本,该误差应该较低。

在这篇文章中,我将列出增加数据集中图像大小和多样性的最佳数据扩充技术。主要目标是提高模型的性能和通用性。我们将探索简单的变换,如旋转、裁剪和高斯模糊,以及更复杂的技术,如高斯噪声和随机块。

图像分析技术;

1.简单的转换

  • 调整大小
  • 灰度
  • 使标准化
  • 随机旋转
  • 中间作物
  • 随机作物
  • 高斯模糊

2.更先进的技术

  • 高斯噪声
  • 随机块
  • 中部

1.表面裂纹数据集简介

表面裂纹数据集。作者插图。

在本教程中,我们将使用表面裂纹检测数据集。你可以在这里或者在 Kaggle 上下载数据集。正如您可以从名称中推断的那样,它提供了有裂缝和没有裂缝的表面的图像。因此,它可以用作异常检测任务的数据集,其中异常类由有裂缝的图像表示,而正常类由没有裂缝的表面表示。它包含 4000 张有缺陷和无缺陷表面的彩色图像。这两个类在训练集和测试集中都可用。此外,每个数据集图像以 227×227 像素的分辨率采集。

2.简单的转换

本节包括torchvision.transforms模块中可用的不同转换。在深入之前,我们从训练数据集中导入模块和一个没有缺陷的图像。

让我们显示图像的尺寸:

np.asarray(orig_img).shape  #(227, 227, 3)

这意味着我们有一个 227x227 的图像,有 3 个通道。

调整大小

由于图像具有非常高的高度和宽度,因此需要在将其传递给神经网络之前降低维度。例如,我们可以将 227x227 图像调整为 32x32 和 128x128 图像。

resized_imgs **=** [T**.**Resize(size**=**size)(orig_img) **for** size **in** [32,128]]
plot(resized_imgs,col_title**=**["32x32","128x128"])

已调整大小的图像。作者插图

值得注意的是,当我们获得 32x32 的图像时,分辨率会降低,而 128x128 的尺寸似乎可以保持样本的高分辨率。

灰度

RGB 图像可能很难管理。因此,将图像转换为灰度是很有用的:

gray_img **=** T**.**Grayscale()(orig_img)
plot([gray_img], cmap**=**'gray', col_title**=**["Gray"])

原始图像与灰度图像。作者插图

使标准化

归一化可以构成加速基于神经网络架构的模型中的计算和更快学*的有效方式。标准化图像有两个步骤:

  • 我们从每个输入通道中减去通道平均值
  • 后来,我们用它除以信道标准差。

我们可以显示原始图像及其归一化版本:

原始图像与标准化图像。作者插图

随机旋转

T.RandomRotation方法以随机角度旋转图像。

不同的旋转图像。作者插图

中间作物

我们使用T.CenterCrop方法裁剪图像的中心部分,这里需要指定裁剪的尺寸。

中间作物。作者插图

当图像的边界中有一个大的背景,而这对于分类任务来说是完全不必要的时,这种变换会很有用。

随机作物

我们没有裁剪图像的中心部分,而是通过T.RandomCrop方法随机裁剪图像的一部分,该方法将裁剪的输出大小作为参数。

随机作物。作者插图

高斯模糊

我们使用高斯核对图像应用高斯模糊变换。这种方法有助于使图像变得不太清晰和明显,然后,将得到的图像输入神经网络,神经网络在学*样本模式时变得更加稳健。

高斯模糊。作者插图

3.更先进的技术

前面展示了 PyTorch 提供的简单转换的例子。现在,我们将重点关注从头实现的更复杂的技术。

高斯噪声

高斯噪声是向整个数据集添加噪声的一种常见方式,它迫使模型学*数据中包含的最重要的信息。它包括注入高斯噪声矩阵,该矩阵是从高斯分布中抽取的随机值的矩阵。稍后,我们在 0 和 1 之间剪切样本。噪声系数越高,图像的噪声越大。

高斯噪声。作者插图

随机块

正方形面片作为蒙版随机应用在图像中。这些小块的数量越多,神经网络就会发现解决这个问题越有挑战性。

随机块。作者插图。

中部

这是一个非常简单的技术,可以使模型更加一般化。它包括在图像的中心区域添加一个修补块。

中部地区。作者插图。

最终想法:

我希望这篇教程对你有用。目的是综述图像增强方法,以解决基于神经网络的模型的泛化问题。如果你知道其他有效的技术,请随时评论。我将在下一篇文章中解释如何利用自动编码器来开发这些技术。代码在 Kaggle 上。感谢阅读。祝您愉快!

其他相关文章:

https://medium.com/mlearning-ai/albumentations-a-python-library-for-advanced-image-augmentation-strategies-752bff3a3da0 https://medium.com/mlearning-ai/how-to-quickly-build-your-own-dataset-of-images-for-deep-learning-1cf79073f1bd

免责声明:该数据集由恰拉尔·弗拉特·兹内尔根据 知识共享署名 4.0 国际 (CC BY 4.0)授权。

你喜欢我的文章吗? 成为会员 每天无限获取数据科学新帖!这是一种间接的支持我的方式,不会给你带来任何额外的费用。如果您已经是会员, 订阅 每当我发布新的数据科学和 python 指南时,您都会收到电子邮件!

调节敏感音频内容的综合指南

原文:https://towardsdatascience.com/a-comprehensive-guide-to-moderating-sensitive-audio-content-d9e1f882fd06

内容调节变得简单

照片由奥斯曼·埃克斯帕特尔拍摄。开启防溅

**·** [**Motivation**](#ea23) **·** [**Content Moderation in Audio Files**](#3c09) **·** [**Results**](#6727) **·** [**Conclusion**](#4a15)

动机

用户生成的内容由在线平台根据与该平台相关的规则和政策进行筛选和监控。

换句话说,当用户向网站提交内容时,它通常会经过一个筛选过程(也称为审核过程),以确保它符合网站的规则,并且不是不适当的、骚扰性的、非法的等。

当在线或社交媒体上交换文本时,可以使用通常由人工智能驱动的内容审核模型来检测敏感内容。

严重性预测模型的高级概述(图片由作者提供)

除了从音频或视频来源转录信息之外,一些最好的语音到文本 API 还包括内容审核。

与毒品、酒精、暴力、微妙的社会问题、仇恨言论等相关的主题经常是内容审核 API 要解决的敏感内容。

因此,在这篇文章中,我将演示如何使用 AssemblyAI API 来检测音频文件中的敏感内容。

我们开始吧🚀!

音频文件中的内容审核

在 AssemblyAI 的帮助下,您可以在给定的音频/视频文件中调节和预测任何严重性、药物滥用、仇恨言论等的提及。

下图描述了 AssemblyAI 的转录工作流程。

使用 AssemblyAI API 的转录工作流(图片由作者提供)

下面是使用 AssemblyAI 对音频文件进行内容审核的分步教程。

转录 API 将执行语音到文本的转换,并检测给定文件中的敏感内容(如果有)。其中包括提及事故、灾难、仇恨言论、赌博等。

第一步:获取令牌

首先,我们需要获得 AssemblyAI 的 API 令牌来访问服务。

现在我们已经准备好了 API 令牌,让我们来定义头部。

步骤 2:上传文件

接下来,我们将输入音频文件上传到 AssemblyAI 的托管服务,它将返回一个 URL,用于进一步的请求。

第三步:转录

一旦我们收到来自 AssemblyAI 的 URL,我们就可以继续转录,这也将检测敏感内容。

这里,我们将把content_safety参数指定为True。这将调用内容审核模型。

步骤 4:获取结果

最后一步需要使用 POST 请求中返回的id的 GET 请求。我们将重复发出 GET 请求,直到响应的状态被标记为'completed'或'error'。

步骤 5:存储输出

来自转录服务的响应然后被存储在文本文件中。

结果

现在,我们来解读一下内容审核输出。

内容审核的结果可以在从 AssemblyAI 接收的 JSON 响应的content_safety_labels键下获得。

外部的text字段包含音频文件的文本转录。

此外,如上面的输出所示,内容安全检测的结果将被添加到content_safety_labels键。

content_safety_labels键的按键说明如下:

  • results:这表示被模型分类为敏感内容的音频转录片段的列表。
  • results.text:该字段包含触发内容审核模型的文本转录。
  • results.labels:该字段包含与被检测为敏感内容的句子相对应的所有标签。这个列表中的每个 JSON 对象还包含置信度和严重性度量。
  • summary:该字段包含整个音频文件中每个标签预测结果的置信度得分。
  • severity_score_summary:描述每个结果预测标签对整个音频文件的总影响。

每个投影标签将包括其confidenceseverity值,这两个值是不同的。

severity值描述了标记内容在0–1范围内的严重程度。另一方面,confidence分数揭示了模型在预测输出标签时的置信度。

结论

总之,在本文中,我们讨论了使用 AssemblyAI API 的音频文件的内容审核过程。

API 端点提供了帮助您识别音频和视频文件中的敏感材料的功能。

此外,我演示了如何解释内容审核结果,并识别音频输入中是否检测到任何敏感内容。

感谢阅读!

🚀订阅数据科学每日剂量。在这里,我分享关于数据科学的优雅技巧和诀窍,一天一个技巧。每天在你的收件箱里收到这些提示。

🧑‍💻成为数据科学专家!获取包含 450 多个熊猫、NumPy 和 SQL 问题的免费数据科学掌握工具包。

✉️ 注册我的电子邮件列表 永远不要错过关于数据科学指南、技巧和提示、机器学*、SQL、Python 等的另一篇文章。Medium 会将我的下一篇文章直接发送到你的收件箱。

微软 Swin Transformer 综合指南

原文:https://towardsdatascience.com/a-comprehensive-guide-to-swin-transformer-64965f89d14c

深入的解释和动画

免费使用来自像素的图像。

介绍

Swin Transformer(【刘】等,2021 )是一个基于 Transformer 的深度学*模型,在视觉任务中具有最先进的性能。与之前的视觉转换器(ViT) ( Dosovitskiy 等人,2020 )不同,Swin Transformer 效率高,精度更高。由于这些理想的特性,Swin 变压器被用作当今许多基于视觉的模型架构的主干。

尽管它被广泛采用,但我发现在这个主题中缺乏详细解释的文章。因此,本文旨在使用插图和动画为 Swin 变压器提供全面的指南,以帮助您更好地理解概念。

让我们开始吧!

Swin 变压器:改进 ViT

*年来,变形金刚 (Vaswani et al .,2017) 在自然语言处理(NLP)任务中主导了深度学*架构。《变形金刚》在 NLP 中的巨大成功激发了将《变形金刚》用于视觉任务的研究努力。

2020 年,视觉变压器(ViT)获得了人工智能社区的大量关注,以其在视觉任务中具有良好结果的纯变压器架构而闻名。尽管 vit 很有前途,但仍有几个缺点。最值得注意的是,vit 难以处理高分辨率图像,因为它的计算复杂度是图像大小的二次方。此外,ViTs 中的固定标度标记不适用于视觉元素具有可变标度的视觉任务。

ViT 之后出现了一系列研究工作,其中大部分都对标准变压器架构进行了改进,以解决上述缺点。2021 年,微软研究人员发表了 Swin Transformer ( 刘等人,2021 ),可以说是继最初的 ViT 之后最令人兴奋的研究之一。

Swin 变压器架构和关键概念

Swin Transformer 引入了两个关键概念来解决原始 ViT 面临的问题— 分层特征映射转移窗口注意力。其实 Swin 变压器的名字来源于“ShiftedwinDowTransformer”。Swin 变压器的整体架构如下所示。

整体 Swin 变压器架构。图片作者。改编自刘等,2021 。请注意,在本文中,“补丁分区”被用作第一块。为简单起见,我使用“补丁合并”作为此图中的第一个模块,因为它们的操作是相似的。

正如我们所见,“补丁合并”模块和“Swin 转换器模块”是 Swin 转换器中的两个关键构建模块。在接下来的部分中,我们将详细介绍这两个模块。

分级特征地图

与 ViT 的第一个显著偏差是 Swin Transformer 构建了' 【层次特征地图' 。让我们把它分成两部分,以便更好地理解这意味着什么。

首先,‘特征图’只是从每个连续层生成的中间张量。至于‘分层’,在这个上下文中,是指特征图逐层合并(更多细节在下一节),有效地减少了特征图从一层到另一层的空间维度(即下采样)。

Swin Transformer 中的层次特征图。在每个图层后,要素地图会逐步合并和缩减采样,从而创建具有分层结构的要素地图。注意,为了简单起见,省略了特征图的深度。图片作者。

您可能会注意到,这些等级要素地图的空间分辨率与 ResNet 中的相同。这样做是有意的,以便 Swin 变压器可以方便地取代现有视觉任务方法中的 ResNet 主干网络。

更重要的是,这些分层特征图允许 Swin 转换器应用于需要精细预测的领域,例如语义分割。相比之下,ViT 在其整个架构中使用单一、低分辨率的特征地图。

补丁合并

在上一节中,我们已经了解了如何通过逐步合并和缩减要素地图的空间分辨率来构建等级要素地图。在诸如 ResNet 的卷积神经网络中,使用卷积运算来完成特征图的下采样。那么,我们如何在不使用卷积的情况下,在纯变压器网络中对特征图进行下采样呢?

Swin Transformer 中使用的无卷积下采样技术被称为面片合并。在这种情况下,“面片”是指特征图中的最小单元。换句话说,在 14x14 特征地图中,有 14x14=196 个补丁。

为了通过因子 n 对特征图进行缩减采样,面片合并将每组 n x n 个相邻面片的特征连接起来。我知道这可能很难理解,所以我制作了一个动画来更好地说明这一点。

补片合并操作通过将 n×n 个补片分组并在深度方向上连接补片,以因子 n 对输入进行下采样。图片作者。

正如我们从上面的动画中看到的,面片合并将每个 n x n 相邻的面片分组,并在深度方向上连接它们。这实际上以系数 n 对输入进行了下采样,将输入从形状为HxWxC转换为(H/n)x(W/n)x(n * C),其中 HWC 表示

Swin 变压器组

Swin Transformer 中使用的变压器模块用一个 窗口 MSA (W-MSA) 和一个 移位窗口 MSA (SW-MSA) 模块取代了 ViT 中使用的标准多头自关注(MSA)模块。Swin 变压器模块如下图所示。

Swin 变压器模块,有两个子单元。第一个子单元应用 W-MSA,第二个子单元应用 s W-MSA。图片作者,改编自刘等,2021

Swin 变压器模块由两个子单元组成。每个子单元由一个标准化层、一个注意力模块、另一个标准化层和一个 MLP 层组成。第一个子单元使用窗口 MSA (W-MSA) 模块,而第二个子单元使用移位窗口 MSA (SW-MSA) 模块。

基于窗口的自我关注

ViT 中使用的标准 MSA 执行全局自我关注,并且每个补丁之间的关系是针对所有其他补丁计算的。这导致相对于补片数量的二次复杂度,使其不适用于高分辨率图像。

ViT 中使用的标准 MSA 计算每个补丁相对于所有补丁的注意力。图片作者。

为了解决这个问题,Swin Transformer 使用了一种基于窗口的 MSA 方法。一个窗口只是一个补丁的集合,注意力只在每个窗口内计算。例如,下图使用大小为 2 x 2 的窗口,基于窗口的 MSA 仅在每个窗口内计算注意力。

Swin Transformer 中使用的窗口 MSA 仅在每个窗口中计算注意力。图片作者。

由于窗口大小在整个网络中是固定的,所以基于窗口的 MSA 的复杂度相对于小块的数量(即图像的大小)是线性的,这是对标准 MSA 的二次复杂度的巨大改进。

转移窗口自我注意

然而,基于窗口的 MSA 的一个明显的缺点是将自己的注意力限制在每个窗口限制了网络的建模能力。为了解决这个问题,Swin Transformer 在 W-MSA 模块之后使用了移位窗口 MSA (SW-MSA)模块。

移位窗口 MSA。图片作者。

为了引入跨窗口连接,移动窗口 MSA 将窗口向右下角移动一个因子 M /2,其中 M 是窗口大小(上面动画中的步骤 1)。

然而,这种转变导致不属于任何窗口的“孤立”补丁,以及具有不完整补丁的窗口。Swin Transformer 应用“循环移位”技术(上面动画中的步骤 2),将“孤立”补丁移动到带有不完整补丁的窗口中。注意,在这种移位之后,窗口可能由在原始特征图中不相邻的面片组成,因此在计算期间应用掩码以将自我关注限制到相邻的面片。

这种移动窗口方法在窗口之间引入了重要的交叉连接,并且被发现改善了网络的性能。

刘等,2021

结论

Swin Transformer 可能是继最初的 Vision Transformer 之后最令人兴奋的研究成果。使用分层特征映射和移位窗口 MSA,Swin 变压器解决了困扰原始 ViT 的问题。如今,Swin 变压器通常用作各种视觉任务的主干架构,包括图像分类和物体检测。

我很期待看到基于变压器的架构在计算机视觉领域的未来!

喜欢这篇文章?

感谢您的阅读!我希望这篇文章对你有用。如果您想订阅中级会员,请考虑使用我的链接。这有助于我继续创建对社区有用的内容!😄

CNNs 培训综合指南

原文:https://towardsdatascience.com/a-comprehensive-guide-to-training-cnns-on-tpu-1beac4b0eb1c

机器学*

让您的 TensorFlow 代码可在 TPU 上训练

Enric Moreu 在 Unsplash 上的照片

前故事

我最*在 Kaggle 上了解到 TPU 的可用性,我想在 TPU 上运行我的一个旧笔记本。我原以为这只是一个切换加速器和应用几个微小变化的问题,但结果证明这是一个完整的旅程,在这里我学到了很多。我想分享它来帮助其他人利用 TPUs 来惊人地加快他们的训练,从而提高重复实验的能力。

观众

⚠️ 警告!本文假设您熟悉 ML、CNN 的基础知识,并使用 Tensorflow 和 Keras 对其进行培训,并且刚刚开始使用 TPU 或有一些疑问。

📝。实际的示例笔记本是在 Kaggle 上完成的,但是绝大多数讨论的要点也适用于其他环境。

我希望这篇文章集中在实际应用上,而不要太长,所以让我们简单地谈谈“理论”。

一些 TPUs 背景

使用 TPUs ( 张量处理单元)——在硬件上实现矩阵乘法的深度学*加速器,可以显著提高神经网络训练速度,因此大大减少了计算时间。

要了解更多的背景知识,我建议浏览谷歌的 TPUs 简介,也可以参考 YouTube 的视频

Kaggle 环境

以下是该环境的一些详细信息,供您检查与您自己的环境的相关性:

  1. TPU v3–8
  2. Tensorflow 版本 2.4.1(带 TPU)
  3. 在撰写本文时,Kaggle 上每周有 20 小时的 TPUs 时间(一次最多 9 小时)是免费的

完整 Jupyter 笔记本的链接

大意

让我们先过一遍要点,然后用一些细节和代码片段来回顾它们。

TPU 是游戏规则的改变者,如果他们可用,你需要使用他们

我再怎么强调也不为过,TPU 会疯狂地提高你的训练时间(当然前提是这对你手头的任务有用)。下面是一个表格,比较了我所经历的实验的每个时期的时间。

使用不同硬件的*似运行时间比较

但是任何好的东西都是有代价的,为了能够利用 TPU 的优势,人们需要调整他们的代码和数据管道。有时这意味着数据预处理的完全重写。

能够使用 TPU

  • TPUs 专门从 Google 云存储(GCS)中读取数据。因此,您需要将您的数据放在那里和/或从 GCS 中读取它(如果它已经可用的话)(就像 Kaggle 的情况一样)。
  • 需要使用tf.data.DatasetAPI作为model.fit()的输入
  • 型号必须在TPUStrategy范围内定义****
  • 数据扩充必须在数据准备期间完成(即在 CPU 上),它不能成为训练代码的一部分,因为TPU不支持某些张量流操作。
  • 不能使用改变输入的宽度和高度的数据增强层(例如,RandomWidthRandomHeight和方形图像,或在非方形图像的情况下翻转高度和宽度的数据增强方法)。****
  • 来自 TensorFlow Hub 的模型需要读取未压缩的直接加载到 TPU** ,因为云 TPU 无法访问 TFHub 所依赖的本地文件系统**
  • 等等。

优化 TPU 的使用

有很多事情可以做,以优化 TPU 上的训练,下面的列表并不详尽。主要是,由于 TPU 的速度很快,数据管道很容易成为瓶颈:

  • 根据可用的硬件调整批量大小,并相应地调整学*速率
  • 编译模型时使用steps_per_execution参数
  • 使用数据集缓存、预取和其他数据加载优化来最大化 TPU 负载。
  • 确保您的特征尺寸是 8 或 128 的倍数(取决于选择的批量大小)
  • 以某种方式组织你在 GCS 上的数据
  • 等等。

数据集

本教程中使用的器械包来自 Kaggle 上的植物病理学 2020 竞赛。比赛的目标是将苹果叶子的图片分为 4 个不同的类别——健康、锈病、黑星病或多种疾病。数据集相对较小,在训练集中有 1821 幅 jpg 图像,在测试集中有相同的数量。

好了,该动手了

⚠️重要提示

下面我将主要省略与 TPU 用法不相关的代码。您可以通过提供的链接在笔记本中看到完整版本。

在网络上找到 TPU

首先,我们需要在网络上定位 TPU,并实例化一个负责分布式计算的TPUStrategy。在 TPU 不可用的情况下,代码会退回到 GPU 或 CPU

数据加载

最佳实践是:

📝对于 TPU 培训,将 GCS 中的数据组织成合理数量(10 到 100 个)的合理大文件(10 到 100 个 MB)。如果文件太少,GCS 将没有足够的流来获得最大吞吐量。如果文件太多,访问每个单独的文件会浪费时间。

要实现这一点,需要使用TFRecord文件格式。我不会在这里这样做,因为数据集很小,我们可以将它缓存在 RAM 中。但是我建议你按照这个详细的教程去做。

这里我们将从 GCS 上的文件名中创建一个**tf.data.Dataset** ,而不是使用TFRecord

我们为什么要使用这种方法

在当前的目录结构下(即所有的图片都在一个目录下),我们本可以使用ImageDataGenerator.flow_from_dataframe方法
,但是
似乎不支持 GCS** 给出和错误(不像pd.read_csv()至少在 Kaggle 上支持 GCS)。**

UserWarning: Found 1821 invalid image filename(s) in x_col=”filename”. These filename(s) will be ignored.

当使用本地输入的路径调用时,即../input/plant-pathology-2020-fgvc7/images,它没有任何问题,但是 TPUs 不能访问本地驱动器。

此外,ImageDataGenerator已被弃用,建议使用tf.keras.utils.image_dataset_from_directory,这在我们的情况下不方便,因为所有的图像文件都在一个目录中。

要遵循的步骤

1。设置和帮助功能
批量大小。**最理想的是批量大小为 128
strategy.num_replicas_in_sync但是由于我们的数据集非常小,我们将使用一个通用的经验法则:
一般来说,您的批量大小应该能被 8 或 128 整除。
使用小批量的另一个好处是我们不需要调整学*率,默认的学*率就可以了(看起来确实如此)。
*

****每次执行的步骤。此外,我们将通过使用下面描述的model.compile()steps_per_execution参数来减少小批量:

📝 steps_per_execution指示 Keras 一次发送多个批次到 TPU。有了这个选项,就不再需要为了优化 TPU 性能而将批量设置得很高。

****定位训练数据。此外,我们需要获得 Google 云存储中当前数据集的路径。注意,下面代码片段中的第二行是特定于 Kaggle 的。

****2。准备分层列车验证分割结合标签和文件名

****3。对标签进行一次性编码,以符合竞赛要求的提交格式

4。通过压缩文件名和标签创建训练和验证 **tf.data.Dataset**

5。映射文件名数据集以获得图像数据集

6。为训练优化数据集

我使用上面的代码来提高数据输入的性能。我强烈建议您使用 tf.data API 来检查更好的性能,以使您自己的输入管道高效,从而尽可能减少数据加载瓶颈。

⚠️ 缓存和数据扩充。值得强调的一个重要提示****。确保在数据扩充之前cache()数据集,否则扩充将不会在每个时期重新应用,并且在扩充步骤中几乎没有意义。这种情况的一个症状是训练与验证的过度拟合,当然过度拟合也可能由于其他原因而发生。

在这一步的最后,我们得到了分层的、高性能的训练和验证数据集。

模型创建

****每次执行的步骤。现在是使用我们之前谈到的steps_per_execution 参数的时候了。在这种情况下,32 似乎很管用。

****学*率。同样,如果你决定使用更大的批量,你很可能需要调整学*率,而不是使用默认的学*率。

特征尺寸。在 TPU 上创建训练模型时,另一个需要注意的重要事项是特征尺寸。

📝 注: 特征尺寸是指一个全连通层的隐藏尺寸或一个卷积中输出通道的数量。不是所有的层都能符合这个规则,尤其是网络的第一层和最后一层。这很好,大多数模型需要一些填充量。

您可以在云 TPU 性能指南中详细阅读关于设置特征尺寸的信息,以确定最适合您的模型和数据的方式。

除此之外,你可以像往常一样使用你喜欢的 API 创建你的模型(比如顺序的或者功能的)并在TPUStrategy的范围内实例化它:

在 TPUStrategy 范围内实例化模型

模特培训

像往常一样训练你的模型。

如果您在数据准备管道中使用了ds.repeat(),那么您需要为model.fit()方法提供steps_per_epochvalidation_steps。这适用于任何训练,不仅仅是 TPU。

第一个模型经过 60 个历元的训练,准确率约为 80%(在 TPU 上训练大约需要 2 分钟!!!)但是我们可以注意到一些过度拟合的发生。

培训与验证学*曲线

对抗过度拟合的方法之一是使用数据扩充。让我们开始吧。

数据扩充

TensorFlow 有两种应用数据扩充(以及一般的预处理)的方式(更多细节请见):

  1. 将数据增强图层添加到模型中
  2. 将数据扩充纳入数据集准备管道

当在GPU上运行时,你会想要使用 第一选项 来从 GPU 加速中获益。
当运行在
TPU上时 不得不 使用 第二选项 因为不支持数据增强图层(除了 *Normalization* *Rescaling* )

最初我使用了第一个选项,当然它不起作用。不要像我一样:

****💔故障排除。如果您尝试.fit()一个在 TPU 上包含数据扩充层的模型,您将会得到一条TPU compilation failed消息,显示如下所示的错误:

NotFoundError: 9 root error(s) found.
  (0) Not found: {{function_node __inference_train_function_183323}} No proto found for key <<NO PROGRAM AS COMPILATION FAILED>>
     [[{{node TPUVariableReshard/reshard/_5957770828868222171/_22}}]]
  (1) Invalid argument: {{function_node __inference_train_function_183323}} Compilation failure: Detected unsupported operations when trying to compile graph while/cluster_while_body_181553_10451082903516086736[] on XLA_TPU_JIT: ImageProjectiveTransformV3 (No registered 'ImageProjectiveTransformV3' OpKernel for XLA_TPU_JIT devices compatible with node {{node while/body/_1/while/sequential_28/sequential_27/random_rotation_8/transform/ImageProjectiveTransformV3}}){{node while/body/_1/while/sequential_28/sequential_27/random_rotation_8/transform/ImageProjectiveTransformV3}}One approach is to outside compile the unsupported ops to run on CPUs by enabling soft placement `tf.config.set_soft_device_placement(True)`. This has a potential performance penalty.
    TPU compilation failed 
    etc....

我还试着用在没有任何效果的情况下[tf.config.set_soft_device_placement(True)](https://www.tensorflow.org/versions/r2.4/api_docs/python/tf/config/set_soft_device_placement)将增强层保留在主模型中。看起来这个选项只对 GPU 有效。

⚠️出于上述原因,如果你想让你的模型使用数据增强在任何加速器上有效训练,你需要有两个版本。一个用于 GPU,另一个用于 TPU。

数据扩充转换

不应使用改变输出张量重量或高度的数据增强图层(如RandomWidthRandomHeight)

****💔故障排除。否则,您将再次得到NotFoundErrorTPU compilation failed消息,并显示类似如下的错误:

(6) Invalid argument: {{function_node __inference_train_function_248407}} Compilation failure: Dynamic Spatial Convolution is not supported: lhs shape is f32[<=8,<=274,<=286,3] 
     [[{{node conv2d/Conv2D}}]]
    TPU compilation failed
     [[tpu_compile_succeeded_assert/_3495234026254819081/_5]]
     [[TPUVariableReshard/default_shard_state/_4480269216609879393/_8/_123]]
  (7) Not found: {{function_node __inference_train_function_248407}} No proto found for key <<NO PROGRAM AS COMPILATION FAILED>>
     [[{{node TPUVariableReshard/reshard/_11705470937593059017/_16}}]]
  (8) Invalid argument: {{function_node __inference_train_function_248407}} Compilation failure: Dynamic Spatial Convolution is not supported: lhs shape is f32[<=8,<=274,<=286,3] 
     [[{{node conv2d/Conv2D}}]]
    TPU compilation failed
     [[tpu_compile_succeeded_assert/_3495234026254819081/_5]]

最后(重要且与任何硬件相关):

⚠️ 仅对训练集应用数据扩充

将其应用于验证集是没有意义的。除非你打算使用测试时间增强(TTA) 集成方法,否则将它应用到测试集是没有意义的(但是那样你会做得不同)。

我是这样实现的:

使用具有早期停止的数据扩充(耐心设置为 20 个时期)导致大约 10%的提高的准确性,在大约 120 个时期内跳跃到大约 90%的验证集,而没有任何明显的过度拟合迹象

迁移学*

使用 TensorFlow 和 Keras 进行迁移学*有两种方式:

  1. 使用 TensorFlow Hub 提供的图层。
  2. 使用tf.keras.applications API

就我所知,后者在 TPU 上训练时可以照常使用。但是有一个警告。TPU 自带的 Kaggle Tensorflow 版本是 2.4.1。最新型号(如 Efficientnet v2 系列)不可用。

要使用 TensorFlow Hub,需要做一些调整。

能够在 TPU 上训练使用 TensorFlow Hub 层的模型的最简单方法是指示 TensorFlow 从 GCS 读取未压缩的模型。默认情况下,TensorFlow Hub 会下载压缩模型,并将其缓存到 TPU 无法访问的本地文件系统中。然后可以创建一个层并照常使用

另一种方法是使用tf.saved_model.LoadOptions并将模型直接加载到 TPU:

结论

TPUs 可以惊人地减少执行一个训练步骤所需的时间。但是,使用它们需要一些设置。此外,需要记住的是,由于 TPU 非常快,I/O 操作很容易成为一个限制因素。因此,在 TPU 上实现最高性能需要高效的输入管道。本文主要集中在 CNN 上,但是大部分讨论点也适用于其他类型的深度神经网络。

今天就到这里,希望对你加速训练有所帮助。

不要犹豫留下评论或提出问题。

进一步阅读

显然,我没有涵盖你需要知道的在 TPU 上成功训练你的 ML 模型的一切。以下是一些资源列表,它们将帮助您深化知识,并进一步调整和优化您自己的管道和模型。

TensorFlow 2.0 入门—深度学*简介

原文:https://towardsdatascience.com/a-comprehensive-introduction-to-tensorflows-sequential-api-and-model-for-deep-learning-c5e31aee49fa

了解 TensorFlow 最强大的深度学*工具之一

作者图片

TensorFlow 是谷歌创建的框架,允许机器学*从业者创建深度学*模型,通常是向首次接触深度学*的分析师提出的第一个解决方案。

原因在于tensor flow 顺序 API 的简单性和直观性——它允许分析师创建非常复杂和强大的神经网络,同时保持用户友好。

API 代表应用编程接口,是用户和我们想要使用的应用之间的媒介。

TensorFlow 界面简单、直接,即使是没有做过实际深度学*,只懂理论的人也很容易理解。

TensorFlow 的顺序 API 非常适合初学者,建议作为您深度学*之旅的起点。

在这篇文章中,我提出了一个利用顺序 API 的深度学*介绍,展示了一些例子和代码来帮助读者理解这些概念,并作为一个 In。

深度学*和神经网络背后的直觉

在进入 TensorFlow 及其序列模型如何工作之前,最好了解一下什么是深度学*以及神经网络如何工作的背景。

神经网络是用于深度学*的主要工具。现象的学*是通过网络中存在的神经元的激活和去激活来进行的——这种活动允许网络通过权重和偏差来创建我们想要解决的问题的表示

在每层神经元之间有一个激活函数。这将一个输出转换为下一个输出,对网络概括问题的能力产生各种影响。最常见的激活函数称为 ReLU。

神经网络能够通过比较其预测值与实际值的接*程度来了解其性能是否有所提高。这种行为由损失函数描述。作为机器学*的从业者,我们的目标是想办法把损失降到最低。

作为分析师,我们的目标是尽可能减少损失,同时避免过度拟合

我就不赘述了,因为我在文章 神经网络简介——权重、偏置和激活 中已经涉及到这个话题了。我推荐想扩展对深度学*基础知识理解的读者阅读这篇文章。

然而,这一点上下文将足以帮助理解下面的部分。

什么是 TensorFlow 顺序 API,它是如何工作的?

由于 Keras,TensorFlow 提供了过多的功能。TensorFlow APIs 基于 Keras 的 API,用于定义和训练神经网络。

顺序模型允许我们指定一个神经网络,准确地说,顺序的:从输入到输出,通过一系列的神经层,一个接一个。

TensorFlow 还允许我们使用功能 API 来构建深度学*模型。这种方法被更多的专家用户所使用,并且不如顺序 API 那样用户友好。根本的区别在于,函数式 API 允许我们创建非顺序的神经网络(因此是多输出的,并与其他组件集成)。

在 Python 中,顺序模型以这种方式编码

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential(
 [...] 
)

keras.Sequential接受包含定义神经网络架构的层的列表。数据按顺序流经每一层,直到到达最终输出层。

数据如何通过顺序模型流动。图片作者。

我们可以通过keras.layers指定神经层。

输入层

放入神经网络的第一层是输入层。通过keras这件事很简单。

我们创建了一个 256 个神经元的层,ReLU 激活,输入大小为 4。

# input layer
layers.Dense(256, input_dim=4, activation="relu", name="input")

这一层与其他层不同,因为其他层不需要input_dim参数。

初学者在深度学*中最常见的一个问题就是理解输入的形状是什么。

找到input_dim的值并不简单,因为它取决于我们正在处理的数据集的性质。由于在深度学*中,我们使用张量(包含多维数据的结构),有时很难猜测我们输入到神经网络的数据的形式。

在表格数据集中,我们的输入形状将等于数据集中的列数。对于 Pandas 和 Numpy,只需在对象上使用.shape[-1即可获得该信息。

相反,在图像的情况下,我们需要传递图像的像素总数。例如,在 28 * 28 图像的情况下,输入尺寸将是 784。

对于时间序列,我们需要传递批量大小、时间窗口和特征大小。

假设我们的数据集是表格式的,有 4 列,我们只需要指定 4 作为输入维度。

至于神经元的数量,256 的值是任意的。您必须试验这些参数,并评估哪种架构性能最佳。

输入后的图层

让我们给顺序模型添加层。

model = keras.Sequential(
    [
        layers.Dense(256, input_dim=4, activation="relu", name="input")
        layers.Dense(128, activation="relu", name="layer1"),
        layers.Dense(64, activation="relu", name="layer2"),
        # ...
    ]
)

第一层之后的层不需要指定输入维度,因为在输入层之后,它将是从一层传递到另一层的权重和偏差方面的输入的表示

输出层

输出层不同于其他层,因为它必须反映我们希望从神经网络的输出中接收的值的数量。

例如,如果我们想做一个回归,从而预测一个单一的数字,在最后一层的单位数必须是一。

# output layer
layers.Dense(1, name="output")

在这种情况下,我们不需要指定激活函数,因为没有它,数据的最终表示将是线性的(不受 ReLU 的影响),未经变换。

相反,如果我们想要预测类别,例如对图像中的猫和狗进行分类,我们需要在最后一层使用一个激活函数 Softmax 。Softmax 将神经网络的表示形式映射到数据集中存在的类,为每个类的预测分配一个概率。

对于上面提到的例子,我们的输出层应该是

# output layer
layers.Dense(1, activation="softmax", name="output")

结果将类似于[[0.98,0.02]],其中第一个数字指示神经网络在预测类 0(可能是狗或猫)时的置信度。

打印模型的摘要

让我们把到目前为止看到的代码片段放在一起,给模型添加一个名字,并用.summary()打印出我们架构的概要。

model = keras.Sequential(
    layers=[
        layers.Dense(256, input_dim=4, activation="relu", name="input"),
        layers.Dense(128, activation="relu", name="layer1"),
        layers.Dense(64, activation="relu", name="layer2"),
        layers.Dense(2, activation="softmax", name="output")
    ],
    name="sequential_model1"
)
model.summary()

结果呢

的电话。我们模型上的 summary()。作者图片

这个总结显示了理解我们的神经网络的架构以及数据如何在层间移动的重要信息。

最重要的列是输出形状。在这样一个简单的例子中,它可能看起来不相关,但这个列显示了我们的数据如何在神经网络的各个层中改变形状。

当我们使用卷积神经网络或 LSTMs 时,这个总结变得特别有用。这是因为数据的形状以不容易理解的方式变化。如果出现错误和 bug,这些信息可以帮助我们调试代码。

Param # 栏显示可由神经网络调节的参数数量。用数学术语来说,就是我们优化问题的维数。回想一下,每个神经元都有一个权重和一个偏差参数,因此n_parameters = n_neurons * (n_inputs + 1)

在第一层中,输出和输入是相同的,因此应该是 256 x 5。

增量添加层

还有一种替代方法,完全基于样式,因此可以任意地向顺序模型添加层。

逐渐地,你可以使用model.add()添加一个对象到模型中。

model = keras.Sequential()
model.add(layers.Dense(256, input_dim=4, activation="relu", name="input"))
model.add(layers.Dense(128, activation="relu", name="layer1"))
model.add(layers.Dense(64, activation="relu", name="layer2"))
model.add(layers.Dense(2, activation="softmax", name="output"))

最终结果与之前通过层列表看到的一样,因此您可以使用您喜欢的方法。

编译一个序列模型

现在让我们编译这个模型,这是训练神经网络的必要步骤。

编译模型意味着建立损失函数、优化器和性能评估指标。

一旦建立了网络架构,编译只需要一小段代码。继续狗和猫之间的分类示例,我们将使用分类交叉熵作为损失函数, Adam 作为优化器,准确度作为评估度量。

要了解更多关于这些参数的信息,我邀请您阅读 TensorFlow 中关于二值图像分类的文章。

model.compile(
        loss="categorical_crossentropy", 
        optimizer="adam",
        metrics=["accuracy"]
    )

训练序列模型

要训练顺序模型,只需在编译后使用model.fit()。只需传递 X 和 y,其中 X 是我们的特性集,y 是我们的目标变量。

.fit()中还有其他可通过的参数。以下是一些最重要的:

  • batch_size:允许您在更新模型权重和偏差之前,设置每次训练迭代要评估的样本数量
  • epochs:建立模型处理整个数据集的次数。当数据集中的所有示例都已用于更新模型权重时,处理一个时期
  • validation_data:这里我们传递测试数据集,在其上进行训练评估。
model = keras.Sequential(
    layers=[
        layers.Dense(256, input_dim=4, activation="relu", name="input"),
        layers.Dense(128, activation="relu", name="layer1"),
        layers.Dense(64, activation="relu", name="layer2"),
        layers.Dense(2, activation="softmax", name="output")
    ],
    name="sequential_model1"
)
model.compile(
        loss="categorical_crossentropy", 
        optimizer="adam",
        metrics=["accuracy"]
    )
history = model.fit(X_train, y_train, batch_size=32, epochs=200, validation_data=(X_test, y_test))

从这里开始训练过程,该过程将在终端中显示损耗和性能指标的进度。

张量流中神经网络的训练。图片作者。

我写过一篇关于用 TensorFlow 提前停止的文章,TensorFlow 是一种回调,可以帮助神经网络提高训练性能。

序贯模型的评估

细心的读者会注意到上面代码片段中的一个小细节。我指的是history = model.fit(...)。为什么一定要把培训过程赋给一个变量?原因是因为 **model.fit(...)** 返回一个包含训练表演的对象。

在 TensorFlow 中,使用*.fit(...)*返回具有模型训练性能的对象。该对象可用于可视化这些性能并对其进行详细分析。

我们可以通过探索变量中的历史属性来访问字典中的值。

使用这些数据,我们可以在训练和验证集上可视化训练性能。

def plot_model(metric):
 plt.plot(history.history[metric])
 plt.plot(history.history[f"val_{metric}"])
 plt.title(f"model {metric}")
 plt.ylabel(f"{metric}")
 plt.xlabel("epoch")
 plt.legend(["train", "validation"], loc="upper left")
 plt.show()

plot_model("loss")
plot_model("accuracy")

让我们检查一下损失

损失曲线。图片作者。

这同样适用于所选择的评估指标,在这种情况下是准确性

精确度曲线。图片作者。

给读者🥸:的问题

为什么验证集中的准确度增加了,但是同一组的损失也增加了?这不是一个简单的问题——在下面的评论中分享你的想法

如果我们有验证和测试集,我们可以使用model.evaluate(X_test, y_test)*.*来评估模型

使用序列模型进行预测

一旦训练完毕,就该使用模型进行预测了。

train_predictions = model.predict(X_train)

在这种情况下,API 类似于 Sklearn,神经网络预测被分配给train_predictions

保存和加载张量流模型

最后一步通常是保存我们已经训练好的模型。TensorFlow 的 API 允许您简单地使用

model.save("./path/to/file")

将在指定的磁盘位置创建一个文件夹,其中包含我们的神经网络文件。

要稍后加载模型,只需

model = keras.models.load_model("./path/to/file")

从这里开始,我们可以像前面看到的那样,使用模型进行预测。

何时不使用顺序模型?

如前所述,TensorFlow 允许您通过使用函数式 API 来创建非顺序神经网络。

特别是,在下列情况下,功能方法值得考虑:

  • 我们需要多个输出,所以多输出神经网络
  • 一层需要来自前几层的更多输入
  • 两个神经网络需要相互通信
  • 我们需要一个定制的神经网络,一个不寻常的架构

在所有这些和更多这些情况下,我们需要一个非顺序模型。顺序方法通常非常灵活,因为它允许我们解决许多问题,例如二进制图像分类,但是对于更复杂的问题,这样的架构可能被证明过于简单。

结论

对于深度学*实践者来说,TensorFlow API 和顺序模型是强大且易于使用的工具。

本指南希望让深度学*初学者能够在他/她的个人项目中尝试这些工具,避免迷失并求助于官方文档。

推荐阅读

对于感兴趣的人来说,这里有一个我为每个与 ML 相关的主题推荐的书籍列表。在我看来,有一些必不可少的书籍对我的职业生涯产生了巨大影响。

有用的链接(我写的)

如果你想支持我的内容创作活动,请随时关注我下面的推荐链接,并加入 Medium 的会员计划。我将收到你投资的一部分,你将能够以无缝的方式访问 Medium 的大量数据科学文章。

https://medium.com/@theDrewDag/membership

我希望我对你的教育有所贡献。下次见!👋

谷歌专业机器学*工程师认证综合学*指南

原文:https://towardsdatascience.com/a-comprehensive-study-guide-for-the-google-professional-machine-learning-engineer-certification-1e411db4d2cf

学*指南

谷歌专业机器学*工程师认证综合学*指南

如何准备机器学*工程师的顶级认证之一

GCP 机器学*工程师徽章。由作者获得。

注:本文遵循谷歌认证团队发布的 考试指南 作为其地面真理。虽然我最初是在 2021 年 1 月初获得认证的,但随着学*指南的变化,我将继续更新我的认证,当前版本反映了 2022 年 2 月 22 日之后参加考试的学*指南。新旧版本的主要区别在于,新的考试包括 Vertex AI,而旧的考试是基于 AI 平台——它的前身。由于两者之间的差异很小,任何在 GCP 工作的 MLE 都应该对 AI 平台足够熟悉才能通过,所以我只支持新版本。

本帖最后更新于 2022 年 1 月 1 日。

这个认证是关于什么的?

谷歌最*发布了一项针对机器学*工程师的认证考试。简而言之,机器学*工程专注于构建和改进模型,将它们转移到生产环境中,并在可能的情况下自动化这一过程。考试持续两个小时,测试您生产中 ML 的许多方面,包括最佳实践、流程编排、日志记录和监控、故障排除、测试、MLOps 和各种模型架构。

为什么会得?

  • 你已经是一名 ML 工程师,你想要一些正式的证明你的能力。
  • 你希望从数据科学或数据工程转到 ML 工程,并且已经有了一些 GCP 的经验。
  • 你想学*更多的关于 ML 工程和 MLOps 的知识,GCP 看起来不错。

如何阅读这个帖子

下面我添加了学*指南中每个主题的笔记和资源。子引号是从指南一字不差的抄来的,我的评论一般都在它们下面。我还添加了来自 GCP、TensorFlow、TFX 和 Kubeflow 文档的各种资源的链接,这将有助于您查看这些资源。

从学*指南中复制的任何内容都以这种方式列出

我的想法不是提供一个非常长的教程列表,而是一个资源的集合,你可以从中挑选,以解决你知识中的任何差距。假设你不知道 MLOps 是什么——幸运的是,我有一个链接。该考试涵盖的内容很多,甚至可能超过云架构师和数据工程考试。如果你发现自己对某个话题还一无所知,也不要惊慌。在学*指南部分之后,我还将简要介绍书籍和其他资源。

等等!我只想要一个从零开始学*的指南!你不能给我推荐一个吗?

如果你正在寻找一套课程,我可以推荐官方学*途径。课程经常更新,但会让你损失学分。如果您是合作伙伴公司的一员,您可能能够从您的组织获得这些以及额外的支持。

截图来自谷歌提供的官方 ML 工程师学*路径。来源这里

让我们看看指南。总共有六个部分。

第一部分:ML 问题框架

不要为了使用 ML 而使用 ML

第一部分是关于机器学*的用例。不要把这当作简单的通行证和对 ML 的介绍,因为(重新)构建一个问题需要创造性思维和商业理解。你会惊讶于一个公司经常把 ML 看作一个解决方案,而事实上,一个不使用 ML 的聪明的解决方案也可以为他们服务。本节将详细介绍如何处理这个问题。

在学*材料方面,谷歌的机器学*速成班也涵盖了第 1 部分的大部分内容。

1.1 将业务挑战转化为 ML 用例。考虑因素包括:

基于业务需求选择最佳解决方案(ML 与非 ML、定制与预打包[例如 AutoML、Vision API])

这包括两个部分——首先从非 ml 中确定 ML,其次确定哪个 ML 选项是最好的。

如果你试图预测的事情可以不用最大似然法来计算,那么坚持计算可能比用最大似然法更好。为了快速回顾这一点,请查看 Google 的 ML 规则

如果你对此感到困惑,或者只是想要一个好的介绍,请查看来自的文章/迷你课程为 ML 识别好的问题,以及速成课程关于问题框架的部分,以获得一些一般信息。

您应该了解的 ML 解决方案分为两类:

定制 基本上是自带模型选项:通常你可以在任何你喜欢的框架(PyTorch,TensorFlow,Sklearn 等等)中构建一个定制模型,它比开箱即用的方法性能稍高。对于已经拥有大量专业知识的企业来说,这是一个不错的主意,在这些企业中,分类性能很重要,或者错误有其相关的价格标签。

代价是,与使用预打包的解决方案相比,您几乎总是要在开发上花费更多的时间。

【预包装】 这里的“预包装”只是指你几乎可以直接开箱使用的型号。至少,考虑一下:

定义应该如何使用模型输出来解决业务问题

简而言之:系统或解决方案的用途是什么,应该采取什么步骤使其对业务问题有用?

管理不正确的结果

错误结果的影响是什么,如何减轻后果?你如何向利益相关者解释错误分类?

识别数据源(可用与理想)

要记住的要点是,ML 需要数据。如果您还没有数据,那么最好是构建一个基本的解决方案,并开始收集数据,以便稍后训练模型。如果你有数据,但只是没有标记,你也许可以投资标记它。

1.2 定义 ML 问题。考虑因素包括:

定义问题类型(分类、回归、聚类等。)

除了这些主要类别,还要确保你理解以下任务/方法/想法的要点:对象检测、情感分类、流失预测、语音识别、意图分类、对象跟踪(视频中)、语义分割、协同过滤、命名实体识别、时间序列预测、推荐系统、联合学*和迁移学*。了解这些意味着你已经熟悉了你将会遇到的 80%的人工智能用例。

定义模型预测的结果

在模型层面上,输出应该是什么?用户信息的预期意图,或者播放列表中的下一首歌?它应该预测整个购物篮,还是只预测下一件商品?

定义输入(特征)和预测输出格式

定义什么样的特性进入一个模型并不是一项简单的任务。思考如何组合或转换某些特征,例如通过单词嵌入或稀疏向量。模型的输出可能还需要一点点的转换,例如,通过将一个热点编码转换回一个有用的标签。根据我的经验,实际上没有任何业务应用程序会按照模型提供的精确格式来使用预测。

1.3 定义商业成功的标准。考虑因素包括:

ML 成功指标与业务问题的一致性

我喜欢反过来想。从商业角度来看,什么是成功?案件完全自动化?你能像企业衡量他们的成功一样衡量模型做了什么吗?

主要结果

确定模型何时被视为不成功

这是一个棘手的问题,因为它还涉及到管理来自企业的期望。理想情况下,您可以坐下来,预先确定何时认为模型成功的“通过/不通过”阈值。这并不总是可能的。

1.4 识别洗钱解决方案的可行性和实施风险。考虑因素包括:

评估和传达业务影响

一旦你有了一个合适的系统,你必须能够告诉你的利益相关者它是做什么的,以及你的模型对他们的业务有什么作用。理想情况下,你可以用节省的欧元或美元,或者转化率的增加来衡量成功。

评估 ML 解决方案准备情况

简而言之:能够确定企业在多大程度上适应了 ML,ML 解决方案在多大程度上对企业有用——以及需要采取什么步骤来缩小这些差距。

评估数据准备情况和潜在限制

数据是不是已经有了,可以训练了,可以用了(合法!)又有多正确呢?数据里有没有 bot 流量,量是多少?是否缺少数据条目?

与谷歌负责任的人工智能实践保持一致(例如,不同的偏见)

权力越大,责任越大。你可以在这里找到负责任的人工智能实践。我还想提一下 BBC 的《负责任的人工智能指南》类似但更广泛的内容。

第 2 部分:ML 解决方案架构

2.1 设计可靠、可扩展、高度可用的 ML 解决方案。考虑因素包括:

为用例选择合适的 ML 服务(例如,云构建、Kubeflow)

这是考试中最难的部分,原因很简单,你通常不经常实验你设置的基础,因为新的服务经常发布。幸运的是,Google 有大量的文档可以帮助你学*。主要是:

组件类型(例如,数据收集、数据管理)

看看 Google 在人工智能和人工智能类别下推广的一些示例架构。并不是所有的架构都足够成熟,以至于我可以放心地说它会完全以这种方式部署,但是它们可以用来说明现实生活中的用例。

探索/分析

Dataprep 是一个可视化工具,你可能会在考试中遇到,但根据我的经验,大多数数据科学家都希望直接在笔记本上处理数据。为此,你可以完全依赖于 Vertex AI Workbench 中的笔记本实例(管理的或用户管理的)。

特征工程

除了数据科学流程中的逻辑步骤之外,笔记本电脑之外还有一些产品值得一提。

值得一提的是,谷歌提供了为期两天的功能工程课程作为他们学*路径的一部分。

记录/管理

自动化

管弦乐编曲

监视

顶点 AI 模型监控和在较小程度上顶点可解释 AI 。还可以查看记录和管理部分的工具。

你也可以通过 Google Data Studio 使用云日志来构建一个定制的仪表盘来跟踪你的模型。

服务

现在,你的反应应该是用顶点预测来回答。

除此之外,这将取决于解决方案的需求。期待(至少)在计算引擎上提供服务,可能带有深度学*虚拟机映像供您选择 API 框架,以便您可以访问 GPU,在云函数、应用引擎、云运行、GKE 上提供服务,并作为默认的顶点 AI 预测服务。考虑因素包括预测需要多长时间,你是需要批量预测还是单独预测,你是否需要 GPU,它需要如何扩展等等。查阅每个选项的文档将有助于您在遇到的大多数情况下确定哪一个是最好的。

2.2 选择合适的谷歌云硬件组件。考虑因素包括:

计算和加速器选项评估(例如,CPU、GPU、TPU、边缘设备)

GPU 可以加速模型训练和某些类型的数据处理,是的,但即使在深度学*中,也不是所有的模型都适合利用 GPU。

你可以在这里查找GPU 上的文档。在没有真正定制的情况下,知道在一个 GCP 实例上可以使用的 GPU 数量通常是有限的,例如 1、2、4 或 8 个,这可能也是一件好事。GPU 和 TPU 的使用成本要高得多。

TPU 基本上是一个专门为张量数学开发的高度专业化的 ASIC。

这里的是关于 GPU 使用的“官方”指导,以及如何为谷歌的笔记本电脑启用它们。TPU 也有类似的文件。

谷歌还提供 Edge TPUs,你可以以多种形式购买。最常见的是开发板或 PCI 设备。

最后,还有一个可爱的节日礼物大小的 TPU 加速器,名为 USB 加速器,可以与您的本地机器一起工作。与其他 Edge TPUs 一样,它只能进行推理,而不能进行训练。

2.3 设计符合跨部门/行业安全考虑的架构。

构建安全的 ML 系统(例如,防止数据/模型被无意利用、黑客攻击)

永远不要相信用户的输入。保护模型 api 的方式与保护 web 应用程序 api 的方式相同。即使对于机器学*来说,处理你收到的数据并验证它与你期望收到的输入相对应也是一个好主意。注意对抗性输入的概念。

数据使用和/或收集的隐私影响(例如,处理个人身份信息[PII]和受保护健康信息[PHI]等敏感数据)

这些指的是有关处理个人或其他敏感数据的立法。这些是否适用于您取决于您正在处理的数据类型、您所在的位置以及您使用的服务所在的位置。您可以在这里找到按地区细分的合规文件的完整列表。一些主要的:

谷歌提供 DLP 去识别个人数据。

第 3 节:数据准备和处理

总的来说,这一部分的结构很大程度上基于 TFX 目前的情况和谷歌自己的机器学*速成班的内容。

3.1 探索数据(EDA)。考虑因素包括:

形象化

大规模统计基础

数据质量和可行性评估

建立数据约束(如 TFDV)

这些项目对于任何来自数据科学的人来说都不太可能是陌生的——如果你想获得 MLE 认证,你可能知道什么是 EDA 以及这些统计基础是什么。这里推荐的数据约束工具是 TFX 的数据验证库

3.2 构建数据管道。考虑因素包括:

组织和优化训练数据集

数据有效性

处理缺失数据

处理异常值

对于这些话题,你可以从速成班中获得基本知识。我在第 2 节中列出了一些最有可能的候选工具。请记住,顶点人工智能和张量流/TFX 都实现了你可能会被问到的某种形式的数据流水线。

数据泄露

维基百科有一篇关于数据泄露的优秀文章

3.3 创建输入特征(特征工程)。考虑因素包括:

确保培训和服务之间一致的数据预处理

编码结构化数据类型

同样,这里的答案是 TFX——具体来看一下 tf.data API 和 TFRecords 背后的概念。另请注意,TFX 可以让您轻松保持训练和发球之间的预处理完全相同。

TFX 管道的例子。请注意,中间的转换节点执行实际的“特征工程”。摘自 https://www.tensorflow.org/tfx上的互动示意图

特征选择

如果你想复*一下,可以看看速成班的这一部分。

本文(以及附带的白皮书)还荣誉提及了谷歌云 MLE 罗世新,他讨论了特性选择的方式和原因。

阶级不平衡

查看一下这个教程,再次(我相信)来自速成班。

特征交叉

在这个 5 分钟的视频中详细解释了特征交叉。

变换(张量流变换)

Lak Lakshmanan 的这篇博文提供了一些 BQML 和 TF 2.0 中转换的基本例子。

稍微高级一点也更难理解的是张量流变换文档。

第 4 部分:开发 ML 模型

最后,一些简单的阅读。

4.1 建筑模型。考虑因素包括:

框架和模型的选择

模型类型需要与您需要处理的问题保持一致。我已经在第 1.2 节中列举了一堆。

至于框架,除了 Sklearn、PyTorch、hugging face Transformers和 TensorFlow 等常见的疑点外,看看 BigQuery MLVertex AI AutoML 。考试可能会包括围绕成本/工作/数据权衡的问题:有时你可能会受限于哪些 ML 选项可用。

BigQuery ML 模型备忘单有助于将模型链接到任务。来源:官方 BQML 文件。PDF 版本此处

给定可解释性需求的建模技术

这是一个完整的领域。有一些关于可解释 ML 的非常好的书。然而对于考试,我怀疑这些技巧就是这里列出的方法。也可查看 TensorFlow 的综合梯度

迁移学*

本文 (Keras!)有关于迁移学*的基本介绍。

数据扩充

文件在这里。请注意,虽然这在计算机视觉中很常见,但在其他领域进行任何有意义的增强都要困难得多。

半监督学*

维基百科在解释方面比我做得好得多。

模型泛化和处理过拟合和欠拟合的策略

有几个资源我可以推荐:看看速成班的这个视频,然后继续看维基百科上关于偏差-方差权衡的文章。对于肥胖/肥胖不足,维基百科上也有一篇文章,加上速成班的一些简单读物另一篇关于用 BQML 预防肥胖的文章。

4.2 培训模式。考虑因素包括:

将各种文件类型吸收到培训中(例如,CSV、JSON、IMG、拼花或数据库、Hadoop/Spark)

目前推荐的做法显然是将表格数据存储在 BQ 中。对于其他所有事情,正常的工作流程是对数据集使用云存储。

在不同的环境中将模型训练成一项工作

Vertex 支持它,以及调度笔记本执行(目前在测试中,不会期望在考试中已经有)。你可以将你的代码容器化,并以不同的方式运行它,例如在 Kubernetes 引擎上。此外,您只需提交您的训练代码,并在存储桶上为您的训练数据集指向一个位置。

超参数调谐

使用超参数调优 / 概述【顶点文档上的 。

培训期间跟踪指标

在训练期间,你可以检查Tensorboard——假设你使用 TensorFlow 和 tensor board。如果你正在使用培训工作,你也可以在云监控中找到日志。它还内置在 MLFlow 和 Kubeflow 中。

再培训/调动评估

从开发模型的角度来看,这可能涉及到决定何时重新培训或重新部署。如果你有一个对人类行为(包括书面语言)进行分类的系统,你可能需要经常重新训练以对抗漂移。自然,重新训练是没有意义的,除非你的数据发生了变化(无论是新的数据,还是正在衰退的旧数据)。

4.3 测试模型。考虑因素包括:

模型训练和服务的单元测试

速成班中有的一些材料(5 分钟阅读),但也可以看看 KDnuggets 上的这篇博客文章。如果你不熟悉单元测试,看看这个视频

根据基线、更简单的模型以及时间维度对性能建模

这篇文档有一些非常好的例子。

ML 中的一般概念是从快速简单的基线模型开始,如果性能成为问题,则向更重的模型发展。通过巧妙地划分时间维度,你可以很好地估计现实生活中的表现,同时也意识到旧数据代表了你的问题。对此没有一个包罗万象的解决方案,但是这个 stackexchange 讨论很好地解释了这个问题。我愿意接受关于这个话题的任何建议。

顶点人工智能的模型可解释性

参见文档

4.4 缩放模型训练和服务。考虑因素包括:

分布式培训

查看顶点 AI 上分布式训练的文档,tensor flow 上分布式训练指南。还有这个 qwiklab 但是它已经相当过时了(它提到了 Cloud ML Engine,它或多或少是 AI 平台的前身,也就是我们心爱的 Vertex AI 的前身)。

缩放预测服务(例如,顶点 AI 预测、容器化服务)

我将在 Kubernetes 上推荐这个 1 小时 30 分钟长的 qwiklab。你可以在这里找到

也可以在这里查看关于部署的一般说明,在这里查看关于顶点的具体说明。

第 5 节:自动化和编排 ML 管道

👉这一整节的必读内容是这篇文章关于谷歌对 MLOps 的看法。这将花费你 10 分钟的时间,但是它将提供更多一点的关于在 ML 管道中自动化什么的上下文。

5.1 设计和实施培训管道。考虑因素包括:

确定组件、参数、触发器和计算需求(例如,云构建、云运行)

从本质上来说,对于这一部分,你需要知道是什么启动了培训信号,以及在启动培训工作时需要考虑什么。您还需要能够判断计算资源中可能需要什么,以及模型将如何部署到一定程度。我将列出我在这方面使用的一些服务,但是也要知道,如果你使用 Vertex 或 KubeFlow,很多服务都可以在平台内部进行排序。

云调度器可以帮你建立一个 cron 作业调度。

云构建是 GCP 上的 CI/CD 产品。如果你想在合并中重新训练一个模型,你可以使用云构建来启动它。这里的是关于如何使用云构建和云运行的教程。Cloud run 是一个部署容器的产品。假设您正在部署的容器是一个 flask 或 FastAPI 应用程序,其中包含您的模型。

我还想插云函数云 Pub/Sub 。他们做的正如他们的名字所暗示的:云功能是作为服务的无服务器和无状态的功能,而发布/订阅实现了发布者-订阅者模式。您可以为云函数使用一个存储触发器,这样,如果一个模型被添加到一个存储桶中,您就可以激活一个云函数,用它来做一些很酷的事情。

编排框架(例如,Kubeflow 管道/Vertex AI 管道、Cloud Composer/Apache 气流)

对于这一部分,您需要决定使用什么框架来协调您的培训工作。如果气流已经在使用,并且要求不偏离组织的其他部分,这可能是一个很好的选择——这是我现在在许多项目中使用的。(也是?)简而言之,如果你在关注 Kubernetes,你可以选择 Kubeflow,而 Vertex 则支持使用托管服务。

混合或多云策略

有人说了 Anthos 吗?

采用 TFX 组件/Kubeflow DSL 的系统设计

TFX 的流水线由不同的组件组成,代表不同的 ML 任务。你可以在这里找到更多信息

Kubeflow DSL 是用于带有 Kubeflow pipelines SDK 的管道的包。你可以在这里找到更多的背景

5.2 实现服务管道。考虑因素包括:

服务(在线、批处理、缓存)

这些涉及三种不同的部署模式。在网上,您只是想部署一个模型,以便随需应变。通常这是一个 API。如果你想做批量预测,你只需要每天或每小时运行一次。这里的缓存是指部署在边缘设备上。

谷歌云服务选项

不完全列表:

  • 计算引擎
  • 库伯内特发动机
  • 云函数
  • 云运行
  • 顶点人工智能发球

目标性能测试

标准(而且显而易见?)实践是使用测试/保持设置来验证性能是否得到满足。

配置触发器和管道计划

和以前一样,我是云调度器的忠实粉丝,它也可以和你的顶点管道一起工作。你也可以使用这些触发器气流Kubeflow 有各自的调度选项。

5.3 跟踪和审计元数据。考虑因素包括:

组织和跟踪实验和管道运行

挂钩模型和数据集版本

模型/数据集沿袭

这就是 MLFlow 的用武之地。 Kubeflow 和 Vertex 也有自己内置的跟踪支持。TensorFlow 拥有广泛的元数据支持和 MLMD。

一般来说,由 ML 训练管道生成的所有工件都应该存储在云存储中——包括用于生成那些模型的数据集。虽然 Vertex 确实有数据集支持,但在撰写本文时,GCP 缺少像或厚皮动物那样完整的东西。

第 6 部分:监控、优化和维护 ML 解决方案

6.1 监测和排除 ML 解决方案故障。

ML 模型预测的性能和业务质量

当我学*的时候,我把这理解为在 KPI 中表达预测结果。圣杯是能够准确地向企业报价您为他们节省了多少钱。

日志记录策略

这可能是个人偏好,但是我倾向于这样一种观点,您需要尽可能多地记录日志,以便完全跟踪您部署的模型的执行情况。通常,数据科学家只在模型意外失败时才对日志记录感兴趣,他们不得不在不完全知道问题是什么的情况下进行故障排除。

我知道这一点,因为我曾多次担任数据科学家。我在第 2.1 节中提到了几种记录来自 ML 系统的数据的方法。

建立连续的评估指标(例如,漂移或偏差的评估)

漂移是指你试图预测的事物的性质会随着时间而变化。偏见也是一种有害的力量

了解 Google 云权限模型

关于 IAM 如何工作的文档可以在这里找到,这里是 vertex/AI 平台相关权限的列表

确定适当的再培训政策

一位智者曾经说过,过多的再培训会杀了你——或者至少是你的预算。同样的道理也适用于从不接受再培训的情况。您需要检查您的模型如何受到数据漂移的影响,并确定何时重新训练是有意义的。

常见训练和发球错误(TensorFlow)

Tensorflow 中有一个页面专门讨论常见问题

ML 模型失败和由此产生的偏差

我们都看到了模型失败的灾难性后果。想想歧视性的信用卡欺诈模式,种族主义聊天机器人,亚马逊 Alexa 告诉一个孩子用电刑处死自己。在开发模型时,我们有责任关注坏预测的潜在影响。

6.2 调整生产中培训和服务的 ML 解决方案的性能。

训练输入管道的优化与简化

简化技术

谷歌为 GCP 的人工智能推荐了许多最佳实践(注意,仍然使用人工智能平台)。这里的简化也意味着依赖托管服务(Vertex!)并且尽可能多地不使用服务器。

还可以用什么来备考?

最后

如果你正在读这篇文章,恭喜你!你坚持到了最后。本来这是我刚开始备考的时候为了自己学*做的。不知何故,它设法成长为一份超过 4700 字的文件。

我希望这篇文章也能帮助你准备。请不要把这个学*指南当成待办事项,只是用它来补充你的学*——在你需要的地方借用。祝你好运!

我乐于讨论和添加更多的学*材料,所以如果你觉得我遗漏了什么,请随时留下评论!

为了通过考试,你可以免费订购一些礼品。作者配图。

关于使用 Python 进行立体几何和立体校正的综合教程

原文:https://towardsdatascience.com/a-comprehensive-tutorial-on-stereo-geometry-and-stereo-rectification-with-python-7f368b09924a

关于立体几何你需要知道的一切

对多视图的需求

在相机的针丨孔丨模型中,光线从物体上反射并撞击胶片形成图像。因此,位于同一条射线上的所有点将对应于图像中的单个点。

因此,给定图像中的一个点,不可能确定它在世界上的确切位置,也就是说,我们无法从一张图像中恢复深度。

我们也不能从图像中恢复结构。这方面的一个例子是阴影艺术,艺术家使用手势制作美丽的阴影。只看影子,我们无法对这个姿势做出任何评价。

在本文中,我们将学*使用两种观点来处理这种歧义。

极线约束

假设我给你两张从不同角度拍摄的图像。我在其中一幅图像中显示一个点,并要求您在另一幅图像中找到它。你会怎么做?这里有一个想法:你可以在图像中的点周围取一小块,并将其滑过另一幅图像,看看哪里最匹配。

然而,一旦你看了几何图形,你会意识到你不需要扫描整个图像,因为点必然位于一条线上,如下图所示。

直觉告诉我们,在现实世界中,该点可以位于连接其投影和相机中心的线上的任何位置。因此,我们可以推断,这个点在另一幅图像中的投影可以位于投影线上的任何位置。这条投影线叫做核线。所以现在我们的搜索空间减少到这一行。这被称为极线约束。

在本文中,我们将讨论如何用代数方法求解核线。在此之前,让我们熟悉核几何中的关键定义。

关键术语

  • 基线:连接两个摄像机中心的线。
  • 核线:投影点所在的线。核线成对出现,每条线代表一幅图像。
  • 极线平面:包含基线和世界上一个点的平面。核平面在图像平面的核线处与图像平面相交。
  • 极线:基线与图像平面相交的点。对应于不同点的所有极线在极线上相交。为什么?我们看到核平面在核线处与像平面相交。现在,对应于不同点的每个核平面将具有共同的基线。由于核线是基线与图像平面相交的地方,这意味着所有的核线都将核线作为公共点。换句话说,所有的核线都在核线上相交。此外,核不必位于图像内,它可以位于扩展的图像平面上。

相机矩阵

在这一部分,我们将回顾齐次坐标和相机矩阵,我们将继续使用。如果你已经熟悉它们或者你已经读过我以前关于相机校准的文章,你可以跳过这一节。

齐次坐标

考虑一点(u, v)。为了用它的同质形式来表示它,我们简单地添加另一个维度,就像这样:(u, v, 1)。之所以这样表示,是因为平移和透视投影在齐次空间中变成了线性操作;也就是说,它们可以通过矩阵乘法一次性计算出来。我们将在本文的后面部分详细讨论齐次变换矩阵。

齐次坐标的一个关键特性是它们是尺度不变的;含义,(kx, ky, k)(x, y, 1)代表 k≠0 和 k ∈ R 的同一点。要从齐次表示转换为欧几里得表示,我们只需除以最后一个坐标,如下所示:

齐次的欧几里得表示

如果你想一想,从原点穿过点[x, y, 1]的直线也有k[x, y, 1]的形式。所以我们可以说图像空间中的一个点被表示为均匀空间中的一条射线。

一条线的齐次表示:考虑熟悉的方程ax + by + c = 0。我们知道它代表一条通过点(x, y)的直线的方程。现在,这个等式也可以表示为l⊺p=0其中l(a, b, c) , l⊺l的转置,p(x, y, 1)p本质上是点(x, y)的齐次表示。

现在,l是比例不变的,因为如果你乘以一个常数,等式l⊺p=0不会改变。因此我们可以说l是直线的齐次表示。

总而言之,给定一个齐次点p,等式l⊺p=0(或p⊺l=0)表示p位于直线l上。请记住这一点,因为我们将在讨论基本矩阵时再次讨论它。

相机外部矩阵

相机外部矩阵是将点的坐标从世界坐标系转换到相机坐标系的基矩阵的变化。它让我们从摄像机的角度来看这个世界。它是旋转矩阵和平移矩阵的组合,旋转矩阵确定摄像机的方向,平移矩阵移动摄像机。该等式可以表示为:

这里的符号如下:

关于相机外部矩阵的更多细节,请查看我的另一篇文章。

相机固有矩阵

一旦我们使用相机外部矩阵得到相机上的点的坐标,下一步就是将它们投影到相机的图像平面上以形成图像。这是相机固有矩阵的工作。在我的另一篇文章中已经深入讨论了相机固有矩阵,但是概括来说,相机固有矩阵将相机给出坐标的点投影到相机的图像平面上。它本质上编码了相机胶片的属性。该等式如下所示:

这些符号是:

图像形成流水线

因此,给定世界上的一个点和一个相机,我们以齐次形式表示该点,并乘以外部矩阵来获得它在相机框架中的坐标。然后我们乘以固有矩阵,得到它在摄像机像面上的投影。最后,我们转换回欧几里得坐标,以获得该点在图像中的像素位置。这是图像形成管道,如下所示:

本质矩阵

好的,我们现在将讨论立体几何的基础——本质矩阵。让我们推导一下,看看它有什么作用。

相对位置和方向已知的两台摄像机的校准系统

考虑一个校准系统,其中我们知道两个摄像机的相对位置和方向。让摄像机中心表示为 OcOc’。设 X 为世界上的一点。让我们把 X wrt 摄像机框 Oc 的坐标表示为 Xc ,wrt Oc 表示为Xc’。设 RcTc 为基矩阵从 OcOc’的旋转和平移变化。意思是给定坐标 X wrt Oc ,我们可以找到它们 wrtOc为:

叉积矩阵

先绕个小弯子,说说向量叉积。两个矢量 a,b 的叉积将是一个与它们都垂直的矢量,用a×b = [-a3b2 + a2b3, a3b1-a1b3, -a2b1 + a1b2]表示,其中a = [a1, a2, a3] and b=[b1, b2, b3]

我们可以用矩阵形式来表示:

作为矩阵表示的叉积

这种矩阵形式的叉积表示为[a×]b,其中[a×]是一个 3 × 3 矩阵,b是一个 3 × 1 向量。

**

叉积矩阵

现在,一个向量和它自身的叉积是零。即a×*a* = [a×]a = 0。所以我们可以说[a×]是一个秩为 2 的反对称矩阵。

回到我们的等式:

用向量 Tc 取两边的叉积,我们得到:

Tc × Tc = 0。接下来,我们用向量Xc’在两边取点积:

现在,矢量𝑇 × XcXc 垂直。因此,Xc’。(𝑇×xc’)= 0。

现在, TcRcXc 都是三维向量。因此,我们可以用矩阵形式表示它们的叉积:

最后,我们可以将等式表示为:

**

矩阵 E 被称为本质矩阵,它涉及两个不同摄像机帧中一个点的坐标。

寻找极线

现在,我们如何使用本质矩阵找到核线?让我们更深入地看看这个等式。

本质矩阵方程

这里 Xc 是点 X wrt 摄像机画面 Oc 的坐标。这意味着我们可以将连接 XOc wrt Oc 的直线上的任意一点表示为𝛼 Xc 其中𝛼是某个常数。现在,如果我们在等式中用𝛼xc替换 Xc ,它仍然满足。

类似地,我们可以用𝛽代替 Xc,其中𝛽是某个常数,等式保持不变。

因此,我们可以说本质矩阵方程由位于连接该点和它们各自相机中心的投影射线上的任意两点满足,其中点的坐标用它们的相机框架表示。

x 𝑐和xc’为点 X 在摄像机 Oc 和 o𝑐′.图像平面上的投影它们必须满足基本矩阵方程,因为它们沿着射影射线。所以我们可以写:

现在 xc 是一个 3 × 1 向量,而 E 是一个 3 × 3 矩阵,所以它们的乘积将是一个 3 × 1 向量。我们用 l 来表示吧:

这个等式对你来说应该很熟悉。正如上一节所讨论的,这个等式表示齐性点xc位于直线 l 上我们可以说 l 是对应于点 xc、xc位于这条直线上的核线。这是极线约束的数学形式。**

同样,如上式所示,l是点xcxc*所在的极线。***

因此,给定一个点在一个视图中的投影,我们将其乘以本质矩阵,以获得该点在另一个视图中的投影所在的核线。

这在实践中实现起来有点棘手,但我已经帮你搞定了。

Python 示例

本文的所有代码都可以在这个资源库中找到。设置环境的说明也可以在那里找到。这是本节的笔记本。现在浏览一下,我将在下面提供一步一步的分解。

设置环境

首先,我们为两个摄像机定义外部参数,使它们以一定角度相隔一定距离放置。外部参数包括摄像机的位置和方向。

接下来,我们定义决定图像形成的内在参数。这里我们已经定义了图像平面的大小和焦距,焦距本质上是图像平面离相机中心有多远的度量。

我们还定义了世界上的一个点,使它被两个摄像机捕捉。在本例中,我们将找到该点投影的核线。

在两个视图中捕获的图像如下所示:

计算本质矩阵

为了找到本质矩阵,我们需要找到两个相机之间的相对几何关系,这样,给定一个点在相机 1 上的坐标,我们应该能够找到它在相机 2 上的坐标。

现在,我们知道摄像机外部矩阵是从世界坐标系到摄像机坐标系的基矩阵的变换。使用该信息,从摄像机 1 到摄像机 2 的基矩阵的变化可以计算如下:

**change of basis matrix from camera 1 to camera 2 = (change of basis matrix from world to camera 2) × (change of basis matrix from camera 1 to world)⟹ change of basis matrix from camera 1 to camera 2 = (change of basis matrix from world to camera 2) × (inverse of change of basis matrix from world to camera 1)⟹ change of basis matrix from camera 1 to camera 2 = (Extrinsic Matrix of camera 2) × (inverse of Extrinsic matrix of camera 1)**

一旦我们获得这个基矩阵的变化,我们就可以提取相机的相对方位和偏移。这个 3 × 4 矩阵的前 3 列将给出方向 Rc ,最后一列将给出偏移 Tc

然后我们计算 Tc 的叉积矩阵,并将其乘以 Rc 以获得本质矩阵。

作为健全性检查,我们可以验证基本矩阵方程。

绘制核线

为了找到核线,我们首先需要找到该点在相机图像平面上的投影。在这个例子中,我通过反复试验手工绘制了这些点,但是我们将在后面的部分中看到找到它们的更好的方法。

一旦我们找到这些点,我们将它们与各自的外部矩阵相乘,以获得相机帧的坐标。然后我们把它们代入本质矩阵方程,找到对应的极线。

例如,如果我们知道相机 1 中的投影点,我们将它乘以本质矩阵以获得相机 2 中的核线。然后我们在 2D 图像空间中绘制这条线。为了验证相机 2 中的投影点位于这条线上,我们将其转换为欧几里德坐标,并在相同的空间中绘制。我们对另一台摄像机重复同样的过程。

图像空间中的投影点及其对应的极线如下所示:

图像中的核线

平行图像平面

两台平行摄像机的俯视图

如果图像平面相互平行会发生什么?在上面的系统中,我们有两个相机,它们的光轴沿着 Y 轴彼此平行。让我们假设摄像机中心在 X 轴上,并且相距 b 距离。

由于相机是平行的,它们之间没有相对旋转。因此 Rc 将是一个单位矩阵,并且 Tc 将等于[-b, 0, 0] (-ve 符号,因为 Tc 表示基运算的变化,它是变换运算的逆运算)。因此,我们可以将本质矩阵计算为:

****

平行像平面中的本质矩阵

x 𝑐和xc’为点 X 在摄像机 Oc 和 o𝑐′.图像平面上的投影如果我们假设两台相机的焦距都是 f,我们可以写成*xc = [x, y, f]xc′ = [x′, y′, f]。我们可以将它们代入基本矩阵方程,如下所示:*****

平行像平面的极线方程

我们可以看到核线具有相同的 Y 坐标,这意味着它们沿着 X 轴平行。因此,我们可以说,如果两个相机彼此平行,它们的极线在图像空间中也将是平行的。

下图说明了这一点:

这部分的代码可以在这里找到

如果相机沿 Y 轴平行,图像中的核线将是水平的;如果它们沿着 X 轴平行,核线将是垂直的。

基础矩阵

在现实生活中,我们很少有关于世界上点的位置的信息。但是,我们可以在图像中找到它们的位置。因此,我们需要修改本质矩阵方程,以说明点的图像位置。

现在,给定相机框架上的一个点,固有矩阵负责将其投影到相机的图像平面上。

我们可以把κ送到另一边,把等式改写成:

因此,给定图像中的一个点,我们以齐次形式表示它,并乘以固有矩阵的逆矩阵,以获得该点在世界上的齐次表示。

现在,我们不能从图像中精确定位世界上该点的确切位置,因为齐次坐标是比例不变的,并且该点可以位于射线上的任何位置。

考虑两个摄像机帧 lr ,以及投影在两个图像平面上的点 Pc 。这里的基本矩阵方程是:

在这个等式中,我们可以像这样替换齐次图像坐标:

基本矩阵方程

这被称为基本矩阵方程。

本质矩阵在两个不同的视图中关联同一点的坐标,而基础矩阵在两个不同的图像中关联它们。

在前面的例子中,我们可以计算基本矩阵并绘制核线,如下所示:

****

如果你观察,核线看起来和以前完全一样,但是,这一次它们是使用基本矩阵方程从图像坐标直接计算出来的。

从点对应关系计算基本矩阵

在现实世界中,我们很少使用经过校准的系统。然而,由于基本矩阵直接与图像点相关,我们仍然可以在没有任何世界和相机知识的情况下找到它。以下是方法。

(u, v)(u′, v′)代表两幅不同图像中的同一点。我们可以用齐次形式表示它们,并代入基本矩阵方程:

这里 f1,f2,… 表示基本矩阵的未知参数。上面的等式代表了一个同质系统,我已经在我的另一篇文章这里中深入讨论了它们。然而,我将在这里再次讨论直觉。

第一步是将等式改写为:

以这种方式重写的原因是,我们可以在同一个等式中堆叠许多点对应,如下所示:

这个等式可以用矩阵符号表示为 Af= 0,其中 A 是点对应矩阵,而向量 f ⃗ 是平坦化的基本矩阵。现在, f ⃗可以用它的大小 |f| 将方程两边分开做成单位矢量。

通过计算| ax|⃗|的最小值,可以找到受约束| x|⃗| =1 的类型为 Ax= 0 的方程的解。

在我的另一篇文章中,我已经讨论过,如果单位向量𝑥⃗沿着𝐴⊺𝐴.的最小特征向量,那么|𝐴𝑥⃗|将是最小的

这被称为线性最小二乘估计。

八点算法

想法是找到图像和计算矩阵𝐴 之间的点对应关系。然后通过计算𝐴⊺𝐴的最小特征向量,将其整形为 3×3 矩阵,就可以找到基础矩阵。

计算 f 至少需要多少个点?现在, f 有 9 个未知数,所以你会说我们需要 9 个方程或 9 个点来求解它。但是如果你观察, f 是比例不变的,这意味着我们可以将 f 乘以任何常数,等式 Af= 0 仍然成立所以我们可以用 f 的一个值来除 f,如下所示:

现在看到有 8 个未知数要解,我们最少只需要 8 个点。

我们还需要考虑另一件事。你看,3 ×3 基本矩阵 F 的秩= 2。我们不会在这里讨论证明,但这与叉积矩阵的秩为 2 的事实有关。

因此,考虑到这一点,我们对矩阵 F 执行奇异值分解,使其最后的奇异值为零,并再次重新组合。

八点算法的代码如下所示:

标准化八点算法

现代图像的分辨率高达 4000-6000 像素。这导致点对应中的大量变化,这可能会破坏算法。因此,为了说明这一点,我们在将这些点插入八点算法之前对它们进行归一化。

这个想法是,对于每幅图像,我们计算对应点的质心(平均值),并从每幅图像中减去它。接下来,我们对它们进行缩放,使其与质心的距离(方差)为√2,如下所示:

归一化点对应

接下来,我们创建一个执行上述转换的矩阵,并使用它来转换点,如下面的代码所示:

寻找极点

我们知道核线是图像中所有核线相交的点。数学上它可以表示为:

其中 l1、l2、… 为极线, e 为极线。这可以用矩阵形式表示为:

现在这看起来像一个齐次方程组。因此,为了找到极点,我们可以使用上一节中讨论的线性最小二乘估计来找到 Le= 0 的解决方案。

但是等等,核线可以从基本矩阵中计算出来。因此,我们可以将等式改写为:

现在,为了找到这个方程的解,我们简单地计算 F 的线性最小二乘估计,这是它的最后一个奇异值。

计算核线的代码如下所示:

为了找到另一幅图像的极点,我们找到了 F 转置的线性最小二乘估计。

Python 示例

好的,让我们看看基本矩阵的运行情况。

笔记本可以在这里找到。让我们走一遍。

首先,我们需要同一物体的两幅图像。所以我打开我的虚幻编辑器,放置两个摄像头看着同一个物体,然后截图。

来自左侧摄像机的图像

来自右侧摄像机的图像

接下来,我们需要两幅图像之间的点对应或匹配。在这里,我已经手动标记了它们,但在现实世界中,我们可以使用像 SIFT 这样的算法来自动计算它们。

点对应

好了,现在我们可以用归一化的八点算法从这些点计算基础矩阵。

我们还可以使用计算出的矩阵来验证基本矩阵方程。

让我们为两幅图像绘制核线。

核线

我们可以看到对应于一幅图像的核线穿过另一幅图像中的点。

接下来,让我们找到并绘制极点。

基本矩阵方程也适用于极点,因为它们也是图像平面中的点。

**

极点

我们可以在这里看到核线位于图像之外,所有的核线都在那里相交。

立体校正

因为核线是平行的,所以很容易处理具有平行图像平面的图像。然而,有可能通过战略性地扭曲使它们平行。这个过程叫做立体矫正。我们来看看怎么做。

对于平行图像,极线位于水平轴的无穷远处。所以第一步是创建变换矩阵,将一个点移动到无穷远。

我们需要三个变换矩阵:一个将点旋转到水平轴,一个将点移动到无穷远,一个将原点平移到中心。让我们来看看每一个。

将点旋转到水平轴

给定一个与 X 轴成θ角的点,我们创建一个旋转矩阵,将它旋转-θ,并使它回到 X 轴,如下所示:

将点旋转回 X 轴的旋转矩阵

注意:如果点位于 X 轴的另一侧,符号将会反转。

因为我们在处理齐次坐标,我们需要考虑额外的维度。

齐次坐标的通用变换矩阵,也称为单应矩阵,如下所示:

单应矩阵

因此,将齐次坐标旋转回 X 轴所需的矩阵为:

将点移动到无限远

接下来我们需要将点移动到无穷远处。无穷远处的一点被表示为(∞, 0) or (-∞, 0)。同样可以在齐次坐标中表示为(x, 0, 0),如下所示:

因此,给定 X 轴上的一个点,以齐次形式表示为(x, 0, 1),我们需要一个矩阵将其转换为(x, 0, 0)

下面的矩阵完成了这项工作:

这里的 y 坐标是零,因为我们已经把这个点旋转回 X 轴了

将原点移动到中心

默认情况下,Python 假设图像的原点位于左上角,因此我们需要创建一个平移矩阵来将原点移动到中心。

向中心移动的平移矩阵

在应用转换后,我们可以将它移回原来的位置。

扭曲图像

因此,组合上述矩阵的总变换矩阵由下式给出:

想法是我们用上面的矩阵变换点,然后为了“撤销”效果,我们用矩阵的逆矩阵扭曲整个图像。

使用这种技术,我们可以扭曲一个图像。现在,如何扭曲另一个图像?理查德·哈特利在他的论文中认为,为了获得最好的结果,两幅立体图像需要对齐,这意味着变换后的点对应之间的距离应该最小。

因此,匹配的单应矩阵 H1 可以通过最小化变换后的点对应之间的平方距离的和来找到:

我们不会在这里讨论计算 H1 的证明,但是我在最后的参考文献部分为感兴趣的读者提供了链接。

计算 H1 和 H2 的代码片段如下所示:

好吧,让我们纠正我们的例子中的图像。

一旦我们计算出单应矩阵,就可以使用它们的逆来扭曲图像,如下所示:

使用单应矩阵的扭曲图像

我们现在已经校正了具有平行图像平面的图像,所以核线仅仅是通过这些点的水平线。

扭曲图像的平行核线

经验观察

如果我使用归一化八点算法计算扭曲图像的基本矩阵和核线,结果并不准确,如下所示:

不准确的极线

我不知道确切的原因,但我认为算法被破坏了,因为极点在无穷远处。让我知道你的想法。

校正后的图像现在可以用于各种下游任务,如视差估计、模板匹配等。

使用校正图像计算的视差图

结论

好了,我们到了终点。在本文中,我们研究了处理从两个视图捕捉的图像的技术。我们还看到了如何使用立体校正从两幅图像中估计出某些模糊度,如深度。如果我们有多个视图,我们也可以使用一种叫做的技术从运动中估计场景的结构

此外,随着深度学*的最新进展,所有这些模糊性都可以从单个图像中估计出来。我们可能会在以后的文章中讨论它们。

我希望你喜欢这篇文章。

我们来连线。你也可以通过 LinkedInTwitter 联系我。如果你有任何疑问或建议,请告诉我。

参考

  1. cs231a 注释
  2. https://github.com/chizhang529/cs231a
  3. 理查德·哈特利立体纠正的理论与实践

图像制作者名单

本文展示的图片均为作者。

强化学*的控制理论介绍

原文:https://towardsdatascience.com/a-control-theoretic-introduction-to-reinforcement-learning-3c2972498c17

第 1 部分:搭建舞台

带有芯片的人工神经网络,由利亚姆挂在闪烁的上。许可证 CC-BY 2.0

强化学*是三种基本的机器学*范式之一,与监督非监督学*并列。在过去的十年中,它已经成为解决复杂工程任务的最成功的方法之一。DeepMind 通过 AlphaGoAlphaFold 取得的成功就是最好的例证。但是,尽管它越来越受欢迎,强化学*依赖于数学技术和 vocab,与监督或无监督学*的常规实践者可能*惯的完全不同。这是从熟悉线性最优控制理论的人的角度看强化学*的系列文章的第一篇。通过这样做,我希望揭开几个概念的神秘面纱,澄清关于强化学*的常见误解,并强调它与经典控制理论的紧密联系。

搭建舞台

在深入研究强化学*的数学之前,让我们先来看看将伴随我们整个旅程的机械系统。如下图所示。

平衡车,1976 年左右的简单机器人系统。推车包含一个伺服系统,监控杆的角度,并来回移动推车以保持直立。来自维基百科

俗称小车倒立摆或简称小车。这是控制理论中一个相当普遍的问题。实际上它是如此受欢迎,以至于它进入了 OpenAI 体育馆图书馆的经典控制环境

你们中有物理学背景的人应该对推导这个机械系统的控制方程没有问题。在小角度极限下,这些等式表示为

其中上标点表示时间导数。我们的系统因此具有四个自由度: z (t)是小车的水平位置, ϑ (t)是摆锤相对于垂直方向的角度位置, ϑ=0 是直立位置。另外两个自由度是小车的水平速度和摆的角速度。参数如下: m 为杆的质量, M 为大车的质量, L 为杆的长度, J 为其惯性, b 为大车与地面的摩擦常数。小角度限制的原因是它导致线性运动方程。这样做的主要好处是使我们的问题易于分析,这在试图理解事物时是非常可取的。

为了进一步简化问题,我们还将根据时间离散化这些方程。这将允许我们用和或差来代替积分和微分学,公认更容易操作。这导致下面的线性移位不变(或离散时间)动力系统

使用哪种技术来离散方程并不重要,无论是隐式还是显式欧拉法,一阶还是二阶格式等等。数学仍然是一样的。此外,在你的离散时间步长δt 趋于零的限度内,你应该得到相同的解!

在这个模型中,x[I是系统在 tᵢ 时刻的四维状态向量,而 u [ i 是当时对系统的输入,也就是当前作用在推车上的力。矩阵 A 是一个 4×4 矩阵,描述了小车的自然动态,即在没有外部驱动的情况下它将如何演变。矩阵 B 是一个 4×1 矩阵。描述输入 u [ i 如何影响下一个状态 x [ i+1 ]。最后, C 是描述我们正在进行的测量的 q×4 矩阵,其中 q 是测量的次数,而 y [ i ]是我们在时间 tᵢ 进行的测量的实际向量。这些测量可以简单到观察系统的单个状态变量,或者它们的线性组合。如果 q <为 4,那么我们就面临一个部分观测到的控制问题。另一方面,如果 C 是例如 4×4 单位矩阵,那么我们就处于全状态信息设置中。这些情况中的每一种都需要稍微不同的过程,我们将在本系列的剩余部分中探讨这些过程。**

游戏的名字是什么?

控制理论和强化学*都与设计输入序列{ u [ 0 ], u [ 1 ],…, u [ n ]}有关,以使我们的系统做我们想让它做的任何事情。在我们的例子中,这决定了施加到推车上的脉冲的顺序,使得钟摆稳定在直立位置。在温和的条件下,控制理论和强化学*实际上会给你完全相同的输入序列。它们本质上不同于它们如何到达那里。当控制理论自然地试图最小化操作系统的成本时,强化学*试图最大化回报的概念。然而,在这两种情况下,我们将不得不制定一个优化问题,并找出如何有效地解决它。

如果你想了解更多关于这个问题的控制观点,我强烈建议你看看史蒂夫·布伦顿的 YouTube 频道。史蒂夫现在有 20 多万订户,从我看来,他的频道真的随着他神奇的控制训练营系列而起飞。就我们而言,我们将从强化学*的角度来看待这个问题。然而,为了让那些对控制理论有所了解的人更清楚,我们仍然使用控制词汇。

接下来是什么?

我知道简单的搭建舞台可能会让你们中的很多人感到沮丧,但是请耐心听我说!在学术界,我的时间分为研究、教学和公共宣传。保持这些帖子相对较短(即 5 分钟阅读)有两个主要好处:

  1. 我可以每周抽时间写一两篇,定期给你更多的内容来满足你学*的欲望。
  2. 根据我的教学经验,我发现较短但重点突出的帖子更容易消化内容,最终更好地理解内容。

那么,接下来会发生什么?嗯,我们将逐步引入新的概念,如状态值函数 V ,或质量函数 Q 。我们还将研究价值迭代和策略迭代之间的差异,以及在基于模型和无模型的上下文中讨论 Q-learning 的优缺点。在整个系列中,我们将尝试与控制理论中的重要概念建立联系,如系统的可观测性和可控性,其马尔可夫参数,李亚普诺夫和里卡提方程,或线性二次调节器(LQR)和估计器(LQE),等等。所以系好安全带,下周见!

想看更多这方面的内容?查看我关于低秩结构和数据驱动建模的其他文章或者我的机器学*基础

** **

一份公司的工作可以让你在数据科学领域有一个更好的职业发展

原文:https://towardsdatascience.com/a-corporate-job-could-give-you-a-stronger-career-launch-in-data-science-875e6b6d392d

从一个讨厌公司很久的人那里

来自 Pexels更名城市照片

当我想进入数据科学领域时,我加入了一家初创公司。我对创业公司很着迷,即使是现在。创业公司中的无等级文化、自主的态度和不断增长的满足感。

老实说,我最不想做的事情就是在公司做朝九晚五的工作。但是后来,我加入了一个。快进到现在,我推荐有抱负的数据科学家加入我以前的工作场所,美国最大的公司之一。请听我说完好吗?

事实是:我意识到我错了。将所有企业一概而论是不对的。到处都是好的。从坏中吸取精华是一种超能力。正是这种技能将帮助你在职业生涯中达到高峰。

在这篇文章中,我将向你敞开心扉,告诉你一份公司工作如何能给你一个你一直想要的出色的职业起步。

注意:这篇文章来自于我在数据科学和人工智能行业的工作经验,面向相似的受众。

结构、流程和系统可能是塞翁失马焉知非福

因为我在创业公司和公司都工作过,我可以告诉你他们对待工作的方式有很大的不同,特别是在结构、流程和现有系统方面。

创业公司发展迅速;他们必须推出产品或提供解决方案,并以更快的速度发展。这是有意义的,因为一般来说,他们依赖投资资金,并且迫切需要证明他们值得投资,而不是另一个失败的创业公司。

虽然企业也希望结果越早越好,但他们会优先考虑流程和工作结构,以便长期可持续发展。完成每一项工作的过程有时会令人沮丧,但这种经历塑造了我们以结构化方式处理工作的心态。

当遇到数据科学问题时,您将学*如何提取数据、遵守哪些隐私法规、创建解决方案蓝图、参与解决问题的讨论,以及如何向利益相关方传达商业价值。

你必须知道这些规则,并在早期*惯于它们,甚至在以后轮到你领导的时候打破它们。

你解决问题时最需要的稳定

如果说 2020 年和 2021 年教会了我们一件事,那就是未来是不确定的。我完全赞成冒险,但不是在你的职业生涯开始之前。

在我在一家人工智能初创公司的第一份工作中,我被要求在加入一年内领导项目并处理客户。虽然我在压力和额外的责任中茁壮成长,但这并不适合每个人。

一年后,我们有了一位新的首席执行官,他会解雇表现不好的人,并雇佣资深人士来完成工作。这就是他的领导风格,有时候,除非你擅长身兼数职,否则很难在一家初创公司维持下去。

我在公司的第二份工作中,我看到总有一个资深的人指导你并承担每个项目的所有责任。企业不能让初级员工独自领导一个项目,即使他们有能力,这太冒险了,流程也不允许。

这给了你急需的精神空间和时间来解决问题。参加课程,犯错误,依靠导师,以结构化的方式成长。

你还想要什么?这是最后一件事。

你在职业生涯的早期就需要这个(企业也有!)

可信的声誉。

我坚信你的技能应该只对你的职业发展有影响,但遗憾的是,这个行业并不是这样的——残酷的事实。

雇主发现很难在几个小时的面试中评估你的技能,并依赖你的记录。他们基于你以前的经历和你工作过的公司,对你的技能形成了一种无意识的偏见。

每当我提到我曾在我国的这家领先公司工作过,他们都会印象深刻。这和 LinkedIn 上很多人自称前谷歌、前亚马逊等等的原因是一样的。

不管我们喜欢与否,某些大公司有着可靠的声誉,这可以带来大量的机会。不,我不是要你去追求信誉,而是要你明白一个可信的信誉行业的价值。

这就把我们带到了最后一个问题:在加入一家公司之前,我应该寻找什么?

总结:在一家公司里应该寻找什么?

  1. 学*机会。
  2. 那种工作。
  3. 工作文化。
  4. 赔偿金。

尽管不相信系统和流程,但由于我在公司的工作经历,当我处理数据科学解决方案时,我变得更有组织性和结构化。由于我在公司内部与高级管理层打交道的经验,我现在知道如何与客户打交道。

有时候,我希望我从一开始就有这样的经历——这也是为什么我建议我的国家的初学者加入这家特定的公司。

最后,记住这一点

选择是无穷无尽的:加入一家公司或一家创业公司,甚至自己创业或自由职业。没有一个适合所有人的好办法。

如果你读到这里,你会意识到这并不是创业公司与公司的较量。这是关于保持开放的心态,在评估一个机会之前不要排除它。

在拒绝或接受之前,问问自己:这个特殊的机会适合我吗?如果是的话,那才是最重要的。

我错过了你想补充的关于公司的任何事情吗?请在回复部分告诉我,让我们继续对话。

要获得更多关于进入数据科学、真实体验和学*的有用见解,请考虑 加入我的电子邮件好友私人列表

如果你很看重这类文章,想支持我这个作家,可以考虑 成为中等会员 。每月 5 美元,你可以无限制地阅读媒体上的故事。

https://arunnthevapalan.medium.com/membership

对数据集的批判性分析

原文:https://towardsdatascience.com/a-critical-analysis-of-your-dataset-2b388e7ca01e

停止微调你的模型:你的模型已经很好了,但不是你的数据

布兰登·洛佩兹在 Unsplash 上的照片

人工智能的应用越来越多,模型越来越复杂。与此同时,AI 对数据的渴求,数据的收集和清理仍然是手工方式。

本文汇集了关于为什么适当的数据集收集、清理和评估至关重要的考虑和建议。

数据是新的石油。但是像石油一样,数据也需要提炼

图片来自 Zbynek Burival

*年来,人工智能在医学和商业领域的应用呈爆炸式增长。总的来说,越来越多的公司正在投资人工智能或使用机器学*模型来分析他们的数据。另一方面,整合或训练人工智能模型变得越来越容易。虽然有诸如 PyTorchTensorFlowscikit-learn 等标准库,但一些 autoML 解决方案正在涌现。

然而,90%的公司认为数据是在其业务中发展人工智能战略的最大障碍。事实上,公司往往无法提前确定所需的数据量,如何整合多个来源,如何照顾数据质量,以及对法规的无知。结果是,这会导致成本增加、错过最后期限以及监管机构的问题。此外,Anaconda进行的一项调查显示,超过 60%的时间花在与数据管理相关的操作上(数据加载、清理和可视化)。

在有些领域,获取和管理数据是非常昂贵的。例如,在生物医学领域,查找患者数据(知情同意、许可、样本成本)和给样本贴标签(需要专家和临床医生)都很昂贵。

“拷问数据,它什么都会坦白。”罗纳德·科斯

一般来说,在数据采集和处理过程中做出的选择会影响模型的最终结果(在可靠性和概化层面)。举个例子,在深色皮肤上测试黑色素瘤识别模型时,曲线下的面积 (AUC)下降了 10–15%。事实上,在训练集中很少有暗色调皮肤图像的例子,皮肤科医生自己在注释中犯了更多的错误。

三种皮肤病学算法应用于不同肤色(all、FST I-II 和 FST V-VI)和罕见疾病(all — DDI 和仅常见疾病— DDI-C)的不同皮肤病学图像数据集时的 F1 评分和 ROC-AUC 性能。图片来自原文章:此处

作者指出,通过改进注释和添加更多例子,他们能够实现对深色肤色图像的更好分类。

以数据为中心与以模型为中心的视图

从以模型为中心到以数据为中心的平衡。图片由 Artem KniazUnsplash.com拍摄

Andrew NG 在 youtube 上说99%的文章是以模型为中心的,只有剩下的 1%的文章是以数据为中心的。但是什么是以模型为中心和以数据为中心呢?

  • 以模型为中心的人工智能。数据集被认为是固定的,研究人员的重点是优化模型架构,以便在准确性方面获得最佳结果
  • 以数据为中心的人工智能。相反,目的是关注改进数据管道的方法(选择、注释、清理等等)。

“人吃什么就是什么。”——路德维希费尔巴哈。AI 模型也是这样吗?他们是他们吞噬的数据吗?

大部分文章关注于改进一个模型(架构或训练的变化)并在标准基准数据集上评估它。这些数据集不是没有错误的,不应该在没有关键分析的情况下使用。在以数据为中心的方法中,数据集也在研究人员的眼皮底下,可以修改。

此外,以模型为中心的方法已经实现了人工智能模型的指数级改进,但今天精度的提高往往微乎其微。因此,我们需要新的数据集,但同时需要一种重新评估和改进现有数据集的方法。

大多数文章几乎总是使用相同的标准数据集。图中显示的是主要数据集及其在文章中的使用百分比。图片来源:此处

例如,在收集过程中应更加注意数据质量,但也要用元数据丰富数据集。事实上,皮肤科 90%关于人工智能的文章都没有呈现肤色的信息。因此,在接下来的部分中,我们将讨论如何改进给定数据集的临界点。

数据采集的智能化设计

如何创建智能数据管道?亚当的创作来自米开朗基罗(图片来源:此处)

“数据就像垃圾。在你收集它之前,你最好知道你打算用它做什么。”——马克·吐温

当你想设计一个新的人工智能应用时,你必须清楚任务是什么。虽然选择模型很重要,但是选择数据源也很重要。最常见的情况是,数据集被下载,并且一旦被处理就保持不变。相反,我们应该遵循一种动态的方法,即收集一个初始数据集并进行初始分析以检查偏差。

此外,我们需要确保我们的样本能够代表总体。一个经典的例子是辛普森悖论(当使用整个数据集的可见结果或趋势在将数据分组时消失或逆转)。

辛普森悖论的可视化表示。使用所有数据的回归模型,似乎有一个明显的趋势消失了,但是,单独使用组。图片来源:此处

在收集数据集以允许模型进行概化时,这一点至关重要。不幸的是,大多数数据集有偏见,只在少数几个国家收集(没有充分代表少数群体和其他国家)。这在为医疗应用开发算法时是有问题的。

开放图像数据集中国家的地理分布。图片来源:此处

已经设计了一些方法来补救这种情况:

  • 提高数据覆盖率。让社区参与进来,注重包容性。例如,从设计开始,大科学项目就将包容作为其原则之一。或者还有像共同声音项目这样的项目,它从超过 100,000 名参与者那里收集 76 种语言的语音转录,允许包含通常被忽略的语言。
  • 合成数据。虽然收集医学数据或人脸图像成本高昂,并可能危及隐私,但使用合成数据既能降低成本,又能保护隐私。合成数据的使用似乎在医学、机器人学、计算机视觉等领域大有可为。

头部核磁共振成像的真实和合成样本。图片来源:此处

好消息是,几家公司也在努力提高包容性(一个示例 Meta 与 No Language Left Behind )。尽管如此,这些努力仍处于早期阶段。许多项目一次又一次地使用相同的基准数据集,并且合成数据的使用仍然是次优的(性能是次优的,合成数据本身可能是有偏差的)。

因此,在收集数据集时,关联必须呈现关于性别、种族和地理位置的统计数据的元数据至关重要。类似地,科学期刊和会议都需要这些元数据。

在杰作中雕刻数据

米开朗基罗的大卫。图片来源(此处)

“受到喜爱的数据往往会保留下来。”—库尔特·博拉克

人工智能模型在几个领域表现出色,但它们可能会过度适应训练集偏差和标签噪声。在数据收集过程中,注释和标记被认为是一个瓶颈。事实上,它不仅昂贵而且错误百出。为了降低成本,公司依赖亚马逊 Mechanical Turk 等众包平台,但结果并不总是高质量的。另一方面,医学或激光雷达图像将需要由专家完成注释(甚至更昂贵)。

正在研究几种解决方案来加速注释过程。例如,用户可以提供函数(或通用规则)来注释数据,算法有助于聚合这些初始标签。或者,算法选择最重要的数据点(更大的信息增益,更大的不确定性),然后由人对它们进行注释(人在回路中)。

在其他情况下,问题在于数据稀缺。在计算机视觉中,图像增强技术(旋转、缩放、翻转等等)经常被用来增加数据集中样本的数量。今天也存在库来扩充表格甚至文本数据。

图像增强。图片来源:此处

另一方面,没有一个基准数据集是干净的,没有错误的。事实上,几项研究表明存在几个错误(错误的发音,不正确的标签,等等)。例如, ImageNet (最流行的图像分类数据集之一)的验证集包含至少 6%的错误标签。因此,即使收集了数据集,工作也没有完成,而是应该进行动态检查。

标准基准数据集中的错误示例。图片来源:此处

人们可以尝试不同的正则化技术或使用重新加权技术。不幸的是,这些通常是昂贵的解决方案。另一种方法是使用数据 Shapley 评分,过滤掉质量差的数据,并在清理后的数据集上重新训练模型。这种方法还有一个优点,它允许我们在存在/不存在某些数据的情况下分析模型的行为(从而也评估某些偏差)。

Data Shapley 量化了每个数据点的重要性,以及如果删除该数据点,它对模型性能的影响程度。图片来源:此处

没有简单的考试

图片来自 unsplash.com绿色变色龙

“我们被数据包围,但却渴望洞察。”—杰伊·贝尔

在对模型进行了几次调整,尝试了不同的架构等之后,终于到了梦寐以求的评估时间。范例是将数据集划分为训练集、验证集和测试集。必须将测试设置放在一边,以避免潜在的数据泄漏。我们需要知道这些吗?

实际上,首先需要检查测试集是否具有代表性(例如,包含足够多的各种类的例子)。即使这样有时也不够。

测试集的目的是测试模型的概括能力。例如,研究表明,在一家医院的 x 射线上训练的模型在从其他医院收集的图像上进行测试时无法推广。因此,应该仔细设计一个测试集:图像不应该来自同一个来源,理想情况下,它们应该由不同的专家进行注释。

人工智能模型未能概括,该模型正在寻找图像和它注册的医院之间的相关性。图片来源:此处。许可证:此处

例如,有人指出,看似非常准确的模型可能会因微小变化(背景变化、同一物体的不同背景)而完全错过预测。事实上,据观察,许多人工智能模型实现了所谓的“捷径”或启发式策略。一个典型的例子是,如果在训练集中所有猫的例子都在沙发上,则模型可能将沙发与标签“猫”相关联,并且不能识别在不同上下文中的猫。

神经网络使用的快捷方式示例。图片来源:此处

这些虚假的相关性也可能存在于医学应用中(该模型可能识别与特定医院相关联的事物,然后对来自其他医院的患者图像失效)。此外,这种现象并不局限于计算机视觉,这些捷径也可以在使用自然语言处理(NLP)模型时出现。事实上,该模型经常学*句子的第一部分和第二部分之间的一些关联,这些关联实际上只是虚假的关联。

数据消融研究被认为是一种潜在的补救措施,以了解模型使用什么快捷方式并纠正这种行为。

视觉转换器的数据消融研究示例。在这个例子中,在训练集中,有海鸥(水鸟和背景总是水)和具有森林背景的陆地鸟,如果在测试集中有一只海鸥在森林中,它可能被分类为陆地鸟。这里,为了研究模型的鲁棒性,去除了一些小块。图片来源:此处

此外,通常模型的评估减少到一个数字(一个评估指标,如准确性、AUC 等)。这通常会产生误导:一个典型的例子是不平衡数据集,其中模型可以通过预测最丰富的类来实现高精度。此外,尽管总体准确性可能非常好,但该模型在特定的数据子组中可能存在系统误差。这些错误可能是危险的,例如少数民族、性别和地理来源。

例如,面部识别算法已被证明对少数民族的分类不太准确,存在有害偏见的风险。几项研究已经解决了我们如何减轻偏见和增加算法的公平性。 Multiaccuracy 是一个框架,旨在确保可识别子群之间的准确预测。然而,元数据并不总是可用的,因此开发了一种算法,该算法在模型有出错风险的地方单独识别测试集中的聚类( DOMINO )

此外,还存在数据收集是动态的情况。甚至 AI 任务本身也可以改变(域转移)。例如,自动驾驶车辆需要识别新类型的车辆或新类型的交通信号。重新训练模型的成本相当高,但是进行模型更新也提出了如何评估模型的新问题。因此,今天 MLOps 正在成为发展最快的领域之一。像 TFX 和 MLflow 这样的库包含了针对这些案例的功能,并且能够灵活地分析数据集。

离别的思念

在 unsplash.com,布雷特·乔丹摄于

数据质量是一个好的人工智能应用成功的关键。选择如何收集和操作数据至关重要,尽管在以模型为中心的人工智能数据中,科学家通常专注于如何改进模型。

收集数据或其他操作的方式也引发了道德问题,并可能导致潜在的偏见。此外,这些选择不仅重要,而且在今天越来越相关(正如我在之前的文章中讨论的,机构正在监管人工智能)。事实上,研究表明,即使是标准数据集也不能避免错误和偏见(ImageNet 也包含潜在的攻击性标签)。

此外,模型的评估可能会出现关键问题。正如我们所看到的,该模型可能会因某些团体或利用捷径而不太准确。虽然我们已经看到了几种可能的技术解决方案,但这些问题中的许多都可以通过更好的数据收集和管理来解决。

由于特定群体和类别(少数民族、英语以外的语言、其他国家)的代表性往往不足,因此出现了许多有针对性的项目。例如,COCO 数据集是最著名的数据集之一,用于分割任务,但它呈现典型的西方物体和场景。几年前成立了 COCO Africa,展示在非洲可以遇到的场景和物品。类似地,还有许多其他项目致力于数据集中很少出现的语言。

COCO Africa(图片来源:GitHub 官方资源库)

以模型为中心的方法在过去十年中主导了人工智能,但以数据为中心的方法现在正在增长。事实上,可解释的人工智能越来越重要,以数据为中心是其中的一部分。此外,一个人必须始终对自己的数据(无论是我们自己收集的还是基准数据集)保持批判的态度,因为潜在的错误和偏见可能会被忽略。

如果你觉得有趣:

你可以寻找我的其他文章,你也可以 订阅 在我发表文章时得到通知,你也可以在LinkedIn上连接或联系我。感谢您的支持!

这是我的 GitHub 知识库的链接,我计划在这里收集代码和许多与机器学*、人工智能等相关的资源。

**https://github.com/SalvatoreRa/tutorial

或者随意查看我在 Medium 上的其他文章:

https://pub.towardsai.net/a-new-bloom-in-ai-why-the-bloom-model-can-be-a-gamechanger-380a15b1fba7 **

数据科学候选人在面试中要问的问题列表

原文:https://towardsdatascience.com/a-curated-question-list-for-data-science-candidates-to-ask-during-the-interview-8f6b16af215

职业

数据科学候选人在面试中要问的问题列表

为了更好地了解职业变动

布鲁斯·马斯在 Unsplash 上的照片

动机

数据科学家是最引人注目的工作之一,只要公司还想在业务中很好地利用他们的数据,数据科学家就还会继续发展。然而,围绕数据市场的炒作使得每一个招聘信息和要求看起来都非常相似。因此,候选人很难区分数据科学家在日常运营中做什么。这个问题导致数据科学候选人的希望和公司的期望不一致,导致有价值的数据科学家短期辞职。

在几次个人面试经历后,我得出结论,我们作为数据科学候选人,有时没有问足够多的重要问题来验证我们在日常生活中期望的与公司想要的是一样的。此外,当您做出最终判断时,您可能会错过一些关键的决策变量,从而使评估无法整体完成。

例如,几乎 80%的面试时间是为了检查候选人是否有足够的技术技能来为公司提供解决方案。最后,我们只有 10 到 15 分钟的时间向面试官提问,有时只能回答 2 到 3 个问题,仅此而已。然而,在技术面试后只有有限的时间和疲惫的状态下,候选人很容易忘记问你必须知道的问题,以做出职业移动决定。

我时不时发现这个问题,无论是我自己还是为梦想工作奋斗的朋友同事。这就是为什么我写这篇文章向你介绍数据科学的职业方面,并给你一个我认为你应该在面试中问的问题的精选列表,以减少在转行前做出糟糕决定的风险。

1.数据科学项目和运营

法尔扎德·纳兹菲Unsplash 拍摄的照片

当我开始我的数据科学职业生涯时,我和其他数据科学爱好者一样,试图着陆或转换一份工作到现场,而不关心我必须处理什么样的业务功能。如果你足够幸运,你会得到你想要的工作。然而,它也可以转向其他方向。我发现,没有彻底检查这个话题就进行职业转移是一个很大的错误。

1.1 操作🏃🏻‍♂️

日常生活中所做的事情才是最重要的。首先,你必须核实你在面试中可能面临的日常任务。

精心策划的问题

  • 你能给我介绍一下你们公司数据科学家的日子吗?如果可能的话,我更喜欢听日常生活中糟糕和乏味的部分,而不是令人兴奋的部分。
  • 多少数据科学家参与一个项目? —此问题旨在检查组织如何运营数据科学团队(嵌入特定业务部门的独狼或卓越中心模式)
  • 贵组织过去完成数据科学项目的周期通常是多长(1 至 3 个月还是 6 至 12 个月)? —有时候,你会很快感到厌倦。
  • 你有专门的项目管理团队来处理用户请求吗,或者是一个自我管理项目的数据科学家?——让你知道一天中你要处理多少个会议。
  • 你要花多少时间在会议或特别分析上?——你会平静地生活还是每天上班都要冲刺。真诚地希望面试官告诉你真相,否则你必须找到一个能给你反馈的内部人士。
  • 数据团队目前的规模有多大(包括 DS、DE、MLE 等。),以及你认为未来 1 年、3 年、5 年会变成怎样? —查看数据团队在不久的将来有多重要,如果你调到这个岗位,你要处理多少工作量。

有些问题很难由面试官直接回答,所以你可能必须找到以前的员工或当前团队中的一个人来检查反馈。

这一部分将阐明并可用来与你目前的组织进行比较,看它是否符合你的个人偏好。你也许不能一下子得到所有的答案,但要尽力而为。这些答案是做出最终决定的重要组成部分。

1.2🛠项目

下面的主题是你将参与什么样的项目。在我看来,我们可以将数据科学项目分为两类,即价值创造业务功能。这些类别会导致你在工作旅途中面临不同的日常生活。

价值创造

价值创造是我们划分项目的领域,无论是创收、节约成本还是创新解决方案。你的项目范围很广,你会尽一切努力将业务推向上述任何一个领域。这里有一个你可能在每个项目中工作的例子

  • 创收 —倾向模型、产品推荐、客户终身价值、保留率提高、联合分析等。
  • 成本节约 —供应链优化、路线/工作班次安排、自动化财务报告、数字营销优化、需求预测等。
  • 创新解决方案——通过面部识别改善 KYC(了解你的客户)流程,一个用于回答重复问题的类似人类的聊天机器人,等等。

您可能会注意到,如果您在选择数据科学运营的组织中,您将有机会参与项目的许多领域,并跨不同的业务职能扩展您的知识。

企业机能

业务功能是分离数据科学任务的另一种方式。例如,可以是销售&营销、战略、风险、运营、供应链、欺诈、财务、人力资源等。您的项目将致力于特定的业务部门,您将成为这些领域的专家。这是一个营销项目的例子。

  • 销售&营销 —倾向模型、数字营销优化、产品推荐、营销组合建模等。
  • 风险— 违约概率模型、欺诈&异常检测、拒绝推理模型等。
  • 供应链&运营— 仓储路线优化、库存需求预测、工作班次安排等。

如果你知道自己喜欢什么,想做什么,那就更容易申请到业务职能战略的公司。你将有机会日复一日做你喜欢的事情。

精心策划的问题

  • ⭐️ 如果我被这个职位选中,短期和长期(3 个月到 1 年)的分配项目是什么? —这是一个必问的问题。一些公司甚至不知道在雇佣你的时候你会被分配到什么项目。
  • 您能解释一下数据科学团队处理的项目类型吗,是价值创造还是业务职能基础? —清楚地了解你可能有机会参与的下一个项目。

如果你没有足够的时间参加面试,明星问题是我建议你问招聘经理的最重要的问题。

我最*发现,即使我可以从事公司给我的任何数据科学项目,但仍然有一个项目最符合我内心的激情。如果你能更快找到激情。我相信你有一个光明的未来等待着你。

老实说,在这个领域工作了 4 年后,我最*才意识到我想做什么项目。除非你像我一样尝试所有的东西,否则我不确定找到它的最好方法是什么。

2.性能赋值

Ben Blennerhassett 在 Unsplash 上拍摄的照片

在您对运营和项目有了基本了解之后,绩效评估是您应该了解的下一个方面

我们总是听说许多数据科学用例无法成功交付和部署到生产阶段。在实际生产中部署和维护机器学*模型有很多原因,并且它将占用大量资源来操作系统。

这可能不是你的错。但是,你要知道招聘经理一开始是怎么评价你的表现的。所以知道你在一年中做了什么是你年终评估的一部分是很重要的。

如果你的经理只关心 KPI(结果),而不关心实验的努力(你如何花费时间)。如果你从工作的第一天就知道这一点,这没什么,而不是在你完成一年的工作后,发现它并不在你的绩效评估中。

各种招聘经理都有自己评估下属的风格。你的招聘经理是否有数据方面的背景会影响他们如何审查和评估数据科学家的表现。在加入这个组织之前,你必须仔细评估他们的观点,因为我要说的是,不可能在年底说服他们改变他们评估你表现的方式。

最坏的情况是,你可能会在挫折中度过一年,而没有得到应有的回报。

精心策划的问题

  • 你如何评价数据科学家的表现? —我认为我们应该知道的根本问题是招聘经理如何看待数据科学任务。
  • 您通常监控哪些 KPI 或 okr,并希望数据科学家也对此负责? —更容易了解目标 KPI,以便与我们未来的项目保持一致。例如,一些招聘经理会有一个与财务无关的目标,比如实验次数,这有时对 DS 来说很好。
  • 你会仅仅根据 KPI 来评估性能还是也考虑实验工作? —检查对交付至关重要的因素,无论是多种临时支持还是单个端到端数据科学模型部署。
  • 是否会考虑管道的特别分析和维护部分? —日常生活中总有意想不到的任务需要我们去完成,我们应该知道它有多重要,这样我们才能计划适当的时间去完成它。

3.事业繁荣

卢卡·布拉沃Unsplash 上拍摄的照片

转行往往有机会成本。只有你在整个财政年度都在工作,你才会意识到你的福利。此外,如果你在短时间内有多种职业道路,你可能会被贴上跳槽的标签。这些原因导致了这样一个事实,我们需要的只是一个平静的工作场所来安顿一段时间。而要知道这是不是合适的地方,只能在面试时间里评估。

有些人可能会说,未来随时都可能改变。但是,你面试时问的问题,是你能从组织上找到的最新事实。没有人知道未来,但你可以用你所有的一切为它做准备。

为了评估你的数据职业发展空间,以下是我通常会问面试官的一系列问题。

精心策划的问题

  • 未来 3 年或 5 年,数据团队会是什么样子?— 这是为了检验数据团队只是支持单位还是产品和业务团队合作伙伴。
  • 你计划如何发展数据团队? —这个问题评估了公司的目标资源数量。它反映了他们如何思考数据团队的进一步发展方向。
  • 组织中目前有多少数据科学家、数据分析师和数据工程师? —这是对上述问题的补充,如果仍有余地,这意味着您在组织中有纵向(做一些与数据相关的事情)或横向(提升到专家道路)发展的空间。

最终想法

我总是在进入面试前设置好模板,感谢远程面试文化。把你的视线放在相机上更容易记笔记。我会用我在这里得到的答案来做最后的决定,并与现在的工作场所进行比较。在踏上新的旅程之前,这个流程使我做出了一个更受数据驱动的决定。

我看到很多准备技术面试,但不像这样的一般问题面试。因此,我希望这 15 个精选的问题可以以某种方式让你受益,无论你是这个领域的新手还是正在寻找新挑战的有经验的数据科学家。

帕泰鲁什·西达

如果你喜欢我的工作,想支持我, 成为会员

“没有商业头脑的数据专家就像一把没有柄的剑”

原文:https://towardsdatascience.com/a-data-professional-without-business-acumen-is-like-a-sword-without-a-handle-f6ba9f407983

作者聚焦

Rashi Desai 提供了作为数据分析师获得成功的见解

在 Author Spotlight 系列中,TDS 编辑与我们社区的成员谈论他们在数据科学领域的职业道路、他们的写作以及他们的灵感来源。今天,我们很高兴与 拉希德赛 分享我们的对话。

照片由拉希·德赛提供

Rashi 是芝加哥一家医疗保健公司的高级数据分析师。两年前,她获得了管理信息系统硕士学位,并在研究生院期间在百事全球电子商务公司实*,开始了她的数据科学和分析职业生涯。她喜欢旅行,喜欢阅读和写作关于最新技术的文章。

最*你对数据科学的哪些方面最感兴趣?

数据科学和分析在日常生活中的作用是不断发展的,我喜欢从事触及人类生活的项目;在这些项目中,我们了解人们并创建一个数据科学模型,以满足人们的需求、要求和痛点。

预测建模和数据可视化最让我兴奋!有了预测建模,这些项目让我回到过去,远离(有时)混乱的现在,去诠释一个清晰、更有意义的未来。我们在谈论时间旅行!只有数据专家才能做到这一点。

作为一名数据分析师,用数据讲故事是我最大的优势之一。我喜欢探索数据中隐含的见解,并将清晰的见解传达给企业领导层——将数据转化为业务令人振奋。

你能告诉我们是怎样让你从事这种工作的吗?

我在本科期间于 2017 年接触了数据科学和分析——我当时正在攻读电子工程,正在申请两项专利,对这个领域充满了热情。然而,数据的力量和指数级使用让我着迷。因此,我继续攻读管理信息系统硕士学位,在那里我磨练了技能、工具和技术,以从事数据科学和分析方面的职业。学校的项目是一个很好的起点,人们可以从这个起点开始着手各种各样的初学者项目——回归、聚类、探索性分析、数据可视化等等。

虽然研究生院的课程为我准备了技术技能,但我在百事可乐的一年实*让我接触到了现实世界——业务流程和数据驱动的决策。实*为我打开了学*、探索和试验众多项目的机会海洋,如社交媒体分析、预测建模和预测、相关性解释以及对数据清理的强调。就这样,我继续在 Medium 上写文章,鼓励我贪婪地阅读项目并从中获得灵感。

这段旅程有没有比你预期的更困难?

作为数据科学和分析的初学者,从来没有一个正确的学*和获得入门级工作的方法。你学*,实验,失败,重复。尝试新概念总有一个学*曲线。然而,我想谈的一个挑战是没有意识到数据项目可以承载的价值。一开始,我会漫无目的地做 12 个不同的项目,不知道如何沟通。每个项目都可以讲述一个商业故事,如果你构思你的项目,并在这个方向上创建你自己的问题陈述。

在你职业生涯的几年里,你发现数据科学中有什么技能或领域比其他领域更有用吗?

对 SQL 和 Tableau 的深刻理解会大有帮助!

作为数据专业人员,数据查询是生命周期的第一步。作为一名数据分析师,在过去的一年中,我编写了无数的 SQL 查询,并在内连接和左连接之间进行选择,阅读实体关系图,创建临时表,以及更多类似的基本 SQL 操作。然而,随着越来越多的企业从内部系统迁移到云,我正在花时间学*更多关于云 SQL 和平台的知识,这些知识可以增强我作为分析师的能力。最终,我的目标是从数据库中快速获取信息,节省时间和金钱。作为数据分析师,强大的 SQL 基础是一项强大的举措,可以让我们的工作生活变得轻松!

作为一名分析师,我的工作也是向企业讲述一个情节——一个有数据支持的有影响力的故事,以改善产品和流程,并以一种可接受的形式交流见解。Tableau 作为一种工具已经在高管中获得了很大的人气,因为他们喜欢查看交互式仪表盘来进行有效的决策。我每天都在继续磨练我的画面技巧

有很多关于快速有效的数据建模(随机森林、预测模型、回归等)的低代码/无代码平台的讨论。),我非常乐意尝试一些新的和即将推出的工具。

你为什么开始为更多的读者写作——哪些因素对你的成功贡献最大?

2017 年 7 月,我在探索用户体验设计时,在 Medium 上写了第一篇博文。由于不知道数据科学和分析有一天会成为我的职业,我继续写 UXD。直到 2019 年 6 月,当我开始读研时,我才写了第一篇关于数据的帖子。这种转变一开始非常令人生畏。六个月来,我一直在努力寻找自己的定位。

然后,在 2019 年 12 月,我已经计划了许多内容,用我在学校第一学期学到的知识在寒假期间编写,我从十大数据科学 Python 库开始。那篇博文炸了!仅仅一周内就有 25 万的浏览量,我有了一个真实的时刻。那正是我知道我的目标受众可能是谁的时候。从那以后,我一直在迎合那些具有数据科学和数据分析初级知识的读者。

在过去的两年中,我意识到,随着越来越多的人进入数据世界,他们希望了解如何开始、从事什么项目、数据专业人员的工具和技能、业务经验以及更多初学者友好的内容。在行话和点击诱饵的世界里,人们希望阅读真正的内容。我阅读和互动最多的文章是关于真实世界的经历,其中我谈到了我作为数据分析师的一天,在工作中创造影响,我是如何找到工作的,以及我的经历和学*。我倾向于一种 18 岁和 80 岁的人都能轻松理解的数据语言,这似乎是目前为止效果最好的!

对于正在考虑从事数据科学职业的人,你有什么建议?他们应该问自己什么问题?

数据是否激发你去解决现实世界的问题?

数据科学或分析是否足以激励你接受挑战?

我遇到过很多人,他们考虑数据科学和分析只是因为一些出版物在 2012 年说“数据科学是本世纪最性感的工作”,他们对他们今天的职业生涯不满意。在我成为一名有影响力的数据专家的过程中,我发现三个陈述是一个很好的支点:

  • 确定你职业生涯中喜欢做什么,更重要的是,你不喜欢做什么。**
  • 对数据科学和分析提供的深度感到不知所措是正常的。从小处着手,按照自己的步调逐步完成复杂的项目。阅读人们正在做的事情。这可以激励您,设定期望,并向您介绍数据社区中最新、最棒的产品。
  • 作为一名数据人员,花点时间创造你的价值主张,并努力成为某个领域的主题专家。成为目标的标兵,让人们向你寻求知识、建议或完成工作。

同样,一个没有商业头脑的数据专家就像一把没有柄的剑。在当今世界,将业务问题转化为数据并将其与业务影响联系起来的能力极具吸引力,备受青睐。

如果所有这些还不能与你联系起来,除了数据科学家和分析师之外,数据中还有很多其他角色!如今,对于技术发烧友来说,有很多东西等着他们。

您认为数据科学接下来会走向何方,您在该领域的未来目标是什么?

未来的职业包括任何行业的强大数据科学、采集和分析,包括医疗保健、金融、体育、零售和电子商务、流媒体、航空、约会、营销、天气、教育、政府、旅游以及世界上的所有其他行业。如果你发现数据及其前景令人着迷,现在是时候开始一个项目或进行职业转型了(尽量不要让它不完整)。

在 2022 年及以后,我很高兴看到以人为中心的数据分析、可组合分析以及小数据和数据市场的出现——数据领域正在酝酿许多令人兴奋的事情。

作为一家医疗保健公司的高级数据分析师,我设想促进数据源的集中化、日常数据工作的自动化、对云的更大依赖以及更多地使用预测分析来提升绩效。我非常高兴成为墙上的一只苍蝇,因为未来数据科学和分析领域将有巨大的发展机会。

要了解更多关于拉希的工作和探索她的最新文章,请在媒体推特上关注她。为了快速介绍 Rashi 的 TDS 文章,下面是我们档案中的一些亮点:

想和广大观众分享一些你自己的作品吗?我们很乐意收到您的来信

这个 Q & A 是为了长度和清晰度而稍加编辑的。

一种用 Python 识别非标准字符模式的数据质量测试方法

原文:https://towardsdatascience.com/a-data-quality-test-approach-with-python-to-identify-non-standard-character-patterns-864affcb9195

用 Python 高效执行数据质量测试的用户友好方法

在你的数据中有一些标准是必要的吗?除了标准测试之外,是否要进行全面的数据质量测试?在本帖中,我们将创建一个程序来学*数据中的常见字符模式,并将非标准字符模式报告为数据质量问题。另外,您不会因为额外的配置而浪费时间,因为它与不同的数据集兼容。

照片由艾尔丹姆·卡普奇(作者)拍摄

一拿到数据,我们就直接投入探索。数据探索是任何数据相关项目的重要组成部分,它直接影响模型的效率和结果的准确性。检查数据质量有多种方法,如缺失值插补。对于需要预定义标准的字符字段,还有各种数据质量测试。例如,检查每个单元格的长度以识别过长或过短的单元格,或者搜索非字母字符。

让我们说得更清楚些。您有一个带有国家 ISO 代码和税号的客户数据集。您希望通过分别评估每个国家/地区的税号标准的符合性来确认每个客户的税号的正确性。考虑到您正在处理一个包含多个国家的数据集,第一种可能的方法是找到每个国家的税号标准,将信息嵌入到脚本中,并报告不符合这些标准的记录。如果您想对商店号、客户 id 或其他一些希望以标准格式存储在数据库中的列进行额外的测试,该怎么办呢?为数据的每个附加列定义模式可能会浪费时间。我们试图在这篇文章中提出一个解决这个具体挑战的方案。

下面是我们将要经历的步骤:

  1. 目标
  2. 探索数据
  3. 清除数据(仅针对缺失值)
  4. 拆分记录
  5. 将记录转换为模式关键字
  6. 计算每个国家的模式频率
  7. 识别税号模式频率低于阈值的客户
  8. 报告

1。目标

在大多数情况下,id 号长度相似,由字母和数字组成。在一些罕见的情况下,它们也可以有一些特殊字符,如“-”(破折号)或“*”(星号)。更重要的是,字母、数字和特殊字符在每个 id 中的位置通常是相同的。例如,假设有一个雇员 id 标准,在两个字母字符后有五个数字字符。在这种情况下,我们期望雇员数类似于 AB-12345。这就是为什么我们将每条记录转换成一个模式键,这样 AB-12345 就被编码为 L-L-S-N-N-N-N-N,在这一步之后,我们将计算模式频率。

我们使用:

  • l 代表字母
  • n 代表数字
  • s 代表特殊字符

在这篇文章中,我们将首先识别数据集中的常见模式(如果有的话),然后找出不符合这些模式的记录。

2。探索数据

为了简单起见,我们将只使用一列(税号)来检查是否有非标准格式的记录。由于数据隐私,不可能使用真实税号的数据集,所以我建了一个假的。你可以使用 CSV 文件的副本,在 GitHub 上到达Jupyter笔记本。

我们的数据集包含三列:

  • 客户 Id: 客户的唯一标识符
  • 国家 ISO 代码:两个字母的国家代码
  • 税号:任意字符类型的客户税号

我们将使用pandas库来处理数据。让我们导入数据,重命名列,并快速查看前 5 行。

df 的前 5 行(图片由作者提供)

我们有一个 18060 行 3 列的数据框。

3.清理数据

由于我们已经在致力于数据质量,详细的数据清理是不必要的。这就是为什么我们只删除 Check_Column 中缺少值的行。(潜在的额外清理是删除前导和尾随空格。)

总共有八个值缺失。删除这些行后,我们还剩 18052 行。由于没有税号信息,此八行列表需要在项目结束时作为非标准 id 记录单独报告。

现在,让我们用每个国家的客户数量来研究我们的数据。

客户数量(图片由作者提供)

4.拆分记录

以下是翻译的步骤:

  1. 让我们通过使用map函数来确保 Check_Column 值都是字符串。
  2. 定义一个split函数,我们将使用该函数将每个 VAT 号拆分成其字符。
  3. 拆分校验 _ 列并将字符保存到校验 _ 列 _ 拆分

前 5 行(作者图片)

我们的列表包含了来自 Check_Column 的每个字符。

5.将记录转换为模式关键字

在翻译阶段,我们受益于 ASCII(美国信息交换标准代码)代码。每个字符都有一个 ASCII 码。幸运的是,ASCII 码是根据字符类型排序的。你可以在ascii-code.com网站上找到字符的 ASCII 表示列表。在 Python 中,我们将使用ord函数来查找字符的 ASCII 表示。

首先,我们需要使用 DEC(十进制)表示法对 ASCII 码进行分类:

  • 特殊字符:32–47/58–64/91–96/123+
  • 字母:65–90/97–122
  • 数字:48–57

注:根据您正在使用的语言,您可能需要添加更多的字母。在这种情况下,我们需要添加一个条件。例如,如果要将(( C 带分音符)和(( A 带分音符)翻译成字母,我们需要大写和小写字母 ASCII 表示。

让我们为附加字母创建一个列表,并将它们的 ASCII 表示存储在另一个列表中,而不是一个接一个地翻译附加字母。在脚本的前面部分将附加字母分配给列表会更有效。在本文中,为了清楚起见,我将它添加到了本节中。

有了额外的 ASCII 表示后,让我们创建我们的 ASCII 翻译函数。该函数遍历每个记录的字符,将它们转换成 ASCII 码,并将转换后的字符作为字符串输出。

然后我们使用apply对每条记录运行该函数,并将翻译后的字符保存到Check _ Column _ Translated列中。

前 5 行(作者图片)

此时,我们可以猜测 TT 国家的标准模式,似乎是 L-L-N-N-N-N-N-N-N-N-N,但是当然,现在说什么都为时过早。首先,我们需要计算每个国家的模式频率。

6.计算每个国家的模式频率

要查找每个国家的模式频率:

  1. groupby在国家和模式级别上的 df,这将给我们按国家的模式的数量。
  2. groupby按国家的 df,这将为我们提供客户总数。
  3. merge两个分组的表格都能够计算出频率。
  4. 通过将模式数除以该国家的客户总数来计算模式百分比。

前 5 行(作者图片)

现在我们有了频率,让我们看看每个国家的最高模式频率。

按国家/地区列出的顶级模式频率(按作者列出的图片)

很高兴看到 26 个国家中有 18 个国家的通用模式百分比高于%75。最不常见的百分比是 JJ 国家的%60。可能有几个原因会导致这种情况。要么是有另一种模式也和顶模式一样常见,要么是数据质量根本不高。不过,我们现在不需要担心最常见的模式。我们的重点是识别不常见的模式,以突出数据质量问题。

现在让我们以国家“AA”为例来观察所有的模式。

县“AA”模式频率(图片由作者提供)

似乎在国家“AA”中最常用的模式是 L-L-N-N-N-N-N-N-N-N,大约有%97,还有五种模式很少使用。我们对这些作为潜在数据错误的非常见模式感兴趣。

7.识别模式频率低于阈值的客户

让我们记住我们的两个主表:

1。df :客户级数据

前 5 排 df (图片由作者提供)

2。grp_tbl: 国家/地区和模式级别的数据,以及按国家/地区列出的模式百分比和客户总数信息

grp_tbl 前 5 排(图片由作者提供)

我们合并 CountryCheck _ Column _ Translated列上的 dfgrp_tbl 表,以获得带有模式百分比的客户级别结果。

df_all_temp 表(图片由作者提供)

我们选择一个低阈值,然后过滤掉比该阈值高的 Pattern_Pct 行。这样,我们可以列出具有非通用税务模式的客户。在脚本的早期将阈值分配给变量会更有效。在本文中,为了清楚起见,我将它添加到了本节中。在下面的例子中,我使用 1%作为阈值。

注意:你需要小心阈值。如果您的组项目之一(在我们的示例中,对应于国家)的记录少于 100 条,您将不会有任何 1%阈值的结果。例如,假设国家 AB 有 95 个客户,最常见的模式用于 94 个客户。这意味着只有一个客户有不同的模式。那么,1%阈值将不会突出显示该结果,因为该特定客户的模式百分比将高于%1。

df_all 表格(图片由作者提供)

因此,在 18052 个客户中,有 392 个在我们的数据集中有不常见的税号模式。

8.报告

我们有我们的结果,我们可以称之为异常。现在,让我们准备演示数据。

我们列出了具有非通用税号模式的客户,但是我们的利益相关者可能希望将这些模式与每个客户所在国家最常见的模式进行比较。让我们快速浏览一下我们在第 6 部分创建的 grp_cntry_max 表。

grp_cntry_max: 具有最常见模式和模式百分比信息的国家级数据(此表也是 grp_tbl 的子集)

grp_cntry_max 的前 5 行(图片由作者提供)

现在,我们将 grp_cntry_maxdf_all_temp 合并。在合并之前,我们将重命名这些列,以防止名称冲突并增加清晰度。

df_report 的前 5 行(图片由作者提供)

在这部分之后,还有各种潜在的可视化机会。下面我分享两个不同可视化方法的场景。

注意:许多不同的 Python 包提供了更多的格式化选项。我建议探索它们以获得更吸引人的视觉效果。

场景 1: 我们的利益相关方将审查并修正例外数量最多的前 10 个国家的数据。他们希望了解前 10 个国家中哪个国家的例外比例较高,以便进行有效的计划。

前 10 个国家例外数字饼图(图片由作者提供)

情景 2: 我们的利益相关者希望了解哪些国家的例外情况与其客户总数相比比例更高。

例外百分比条形图(按作者分类的图片)

结论:现在,我们熟悉了我们的数据集,知道了它的弱点。我们准备继续我们的分析,或者将我们的异常作为数据治理项目的结果提出来。

备注 1: 您可以对每个数据集使用这种方法。只需更新列名、定义阈值、创建附加字母列表并运行脚本。您也可以在没有分组的整个数据集上运行此操作(例如按国家分组)。您可以通过用任何静态值(如“A”)创建一个新列来实现。

备注 2: 即使这是一个全面的测试,也可能仍然不够。例如,您可能会遗漏所有 id 都相同的数据集,如“00000000”或“AAAAAA”。

备注 3: 翻译特殊字符是一种保守的做法。如果您的数据没有特殊字符,您可以保留它们的原始值。

非营利咨询的数据科学工具箱

原文:https://towardsdatascience.com/a-data-science-toolbox-for-non-profit-consulting-66a2910c48ea

用于规划和实施可持续解决方案和工作流程的免费工具

车间。图片作者。

解决非营利部门的数据问题不同于企业界和科技行业。非营利组织面临的最常见挑战是捕获、存储和使用管理数据。非营利组织倾向于很少或没有技术预算。你很少会遇到需要机器学*或高级统计的情况。在我与非营利组织的合作中,一些免费工具已经成为原型和数据解决方案的首选。这些工具不是用来分析数据的。相反,我依靠这些工具来规划和实施经济高效且可持续的工作流程。这是我的一套基本咨询工具。

黑曜石(和 Pandoc)

黑曜石对我的咨询、学术研究和个人生活产生了革命性的影响。Obsidian 是一个 markdown 编辑器,具有优秀的链接和搜索笔记的功能。我在非营利组织的工作需要很多会议——并且,黑曜石对于保持一切有序、集中和易于访问是必不可少的。使用黑曜石,我可以使用 Pandoc 轻松地将笔记转换成优雅的幻灯片或其他文件格式。Pandoc 是一个运行在命令行上的“通用文档转换器”。如果我想把一个笔记从 Markdown 转换成 Word 文档,我抓取下面的代码片段(保存在 Obsidian!)使用下面的命令。

pandoc -o output.docx -f markdown -t docx filename.md

黑曜石最好的部分是,该软件集中了我的大部分工作。黑曜石在本地运行,可以跨设备同步,这使得该软件比其他基于云的笔记系统快得多。使用黑曜石作为“第二大脑”的人花了很多时间来创建他们的工作流程——很多时间。不要指望你可以复制别人的工作流程,并希望它开箱即用。相反,使用现有的工作流程作为构建您自己的工作流程的指南。从简单开始,避免安装太多插件。

黑曜石标志。作者截图。

白板

Tabula 是一个基本的生产力工具,用于提取锁定在 PDF 中的数据表。我可以快速抓取一个 PDF 格式的大表格,然后点击几下,将其转换为 CSV 文件进行进一步处理。该软件简单明了,可以在 Mac、Windows 和 Linux 上运行。虽然 Tabula 界面是一个网络浏览器,但你的数据仍然在本地。在处理敏感的组织数据时,将数据保存在本地非常重要。

Tabula 标志。作者截图。

织布机

Loom 是一款基于云的投屏软件,拥有出色的免费增值计划。我使用 Loom 来管理软件演示,并提供不同过程的反馈。我可以通过视频而不是冗长的信息更快地交流复杂的问题。

一个很小但很重要的功能是,当您完成录制和编辑选项时,会为您提供一个可共享的链接。如果您喜欢在其他位置共享或存档视频文件,也可以下载它们。Loom 是一款简单易用的软件,我强烈推荐。

织机标志。作者截图。

谷歌工作空间

我在非营利组织的工作中广泛依赖谷歌。这些软件工具免费提供,易于使用,而且非常强大。我相信每个人都已经熟悉了 Google 表单、表格和文档。非营利组织经常使用调查方法作为监测服务过程和结果的一部分。借助 Google Workspace 免费提供的工具,我可以快速为调查数据构建一个端到端的数据管道。以下是不收集敏感数据的调查的工作流程:

管道示例。图片由作者提供。

假设调查结构遵循最佳实践,Google Forms 是管理调查的一种很好的方式。尽管 Forms 没有自定义调查的跳过逻辑,但它有基本的验证规则来帮助确保您在前端获得干净的数据。Google Forms 可以生成 Google Sheets 来保存调查回复。然后,使用 Google Data Studio,我可以构建一组直接从 Google 工作表读取的定制报告和仪表板。将数据从一个系统转移到另一个系统不需要人工干预。

这条数据管道可能看起来像是技术世界中的一个基本问题和解决方案。但是,在非营利领域,这种类型的数据管道是一项重要的创新,可以为组织节省大量的时间和金钱。

Excalidraw

我已经爱上了 Excalidraw ,一个手绘外观的虚拟白板。

Excalidraw 徽标。作者截图。

Excalidraw 的手绘外观是我最喜欢的功能。许多非营利组织的服务专业人员缺乏软件和技术背景。一个复杂的工作流程可以少一些手绘的样子。Excalidraw 与黑曜石融为一体。您可以对图像进行截图或创建 SVG 文件进行额外的后期处理。

示例图。作者图片

佐特罗

非营利组织面临着提供基于证据的服务的压力。学术文献是信息的主要来源。我经常帮助组织建立 Zotero 来帮助管理期刊文章。 Zotero 是免费的,在组织和分享学术文献和相关数字资产方面非常强大。

Zotero 标志。图片由作者提供。

充气台

我已经成为了 AirTable 的忠实粉丝,这是一个创建定制应用的低代码平台。AirTable 有一个强大的免费增值计划,而高级计划的成本并不高昂。当您开发节约成本的工作流程时,收回成本很容易。我一直在使用 AirTable 帮助组织管理项目、工作流、数据和各种数字资产。

Airtable 徽标。作者截图。

结论和告诫

这篇文章描述了如何使用各种软件工具来支持非营利组织。软件是工具,不是解决方案。有效的咨询需要对问题进行透彻的分析,以及如何建立简单明了且可持续的工作流程。如果你有其他免费或低价的工具建议,请添加到下面的评论中。

数据科学家的一天

原文:https://towardsdatascience.com/a-day-in-the-life-of-a-data-scientist-938d917370b9

剧透一下——我没有创造任何奇特的机器学*模型

安妮·斯普拉特在 Unsplash 上的照片

最*,我遇到了很多对将职业转向数据科学感兴趣的人。他们总是问我的第一件事是,“典型的一天是什么样子的?”。我看到了很多文章概述了科学家使用的技能和工具,但我没有看到多少文章提供了日常任务的真实例子。

虽然每天都不一样,但这些任务代表了我作为一家大型金融机构的高级数据科学家的典型一天。

天一瞥

  • 8:30–9:00——开始我的一天
  • 9:00–10:00—结对编程
  • 10:00–10:30—Scrum
  • 10:30–11:00—准备演示
  • 11:30–12:00—与经理一对一会谈
  • 12:00–1:00—从首席数据科学家那里获得反馈
  • 1:00–4:30—代码!

开始我的一天

我通常在 8 点 20 分起床后,在早上 8 点 30 分左右开始工作。自 2020 年 3 月以来,我一直在远程工作,这对我来说已经改变了游戏规则。我喜欢我的家庭办公室的和平和安静,能够穿着睡衣工作,在等待一些代码运行的时候洗几堆衣服。

我要做的第一件事是查看前一天我可能错过的电子邮件和团队聊天。我每天收到的一封电子邮件包括我团队的一个生产机器学*模型的状态。我检查以确保模型本身或提取数据并将数据加载到我们的数据仓库的相关过程中没有错误。

如果没有错误,我会检查并回复各种消息/请求,然后打开一个名为吉拉的项目/任务跟踪工具,更新我在接下来三周工作的状态——在敏捷软件开发领域,这被称为冲刺。从这里开始,我会优先处理一天的任务。

结对编程

在我过去五年的数据科学家生涯中,我注意到我的工作方式发生了转变。虽然最初几年我专注于自己做这项工作,但现在我花了大量时间帮助和教育经验不足的团队成员。

每周一次,我会与团队中的一名初级数据科学家会面,进行结对编程。在敏捷中,结对编程由两个开发人员组成,他们坐在同一台计算机前,或者共享他们的屏幕,一起进行动态编程。

KOBU 机构Unsplash 上拍摄的照片

我承认,几年前,当一位高级数据科学家向我提出结对编程的想法时,我持怀疑态度。我认为让两个人做完全相同的任务是浪费时间,我害怕当我不知道如何编码时,我会看起来很傻。但是我发现结对编程是向其他开发者学*的好方法。事实上,当我与初级开发人员一起工作时,我几乎总能从他们那里学到一些新东西。

在上午 9:00-10:00 的结对编程会议中,我们从我遇到的一个问题开始。我们俩都在学*一个新的图形数据库工具,初级数据科学家提到了他安装的一个插件,可以帮助他更容易地加载数据。不幸的是,我无法安装它。

通过我们的结对编程会议,我们发现我们的 IT 部门在无法访问互联网的情况下安装了我的工具版本。在初级数据科学家向我展示了他的设置后,我可以安装插件了!

接下来,我们回顾了我编写的在我的图中创建节点和关系的脚本。这位初级数据科学家给了我一些遵循最佳实践的建议,比如全部使用大写字母来标识关系。我太专注于让我的脚本发挥作用,以至于忽略了让它对其他人更具可读性。

在帮助我摆脱困境之后,我们接着转向他面临的挑战。他需要编写一个 SQL 查询来计算几个业务线的当前和以前的指标。

他向我展示了 Excel 最终输出的模型,以帮助我更好地理解这个问题。从那里,我们转移到 SQL Server,我建议通过编写一个简单的查询来返回单个指标和单个业务线的当前结果,从而解决这个问题。在成功检索到该片段后,我们讨论了他如何编写多个小查询并将结果联合在一起以更接*最终输出。

Scrum

当上午 10:00 到来的时候,是我的团队进行每日混战的时候了。

杰森·古德曼在 Unsplash 上的照片

传统上,主持会议的人,scrum master,会问每个人三个问题。

  1. 你昨天做了什么?
  2. 你今天要做什么?
  3. 你有阻滞药吗?

我没有发现状态更新有多大价值,所以我已经推动我的团队采取不同的方法,强调学*。与其说我们昨天做了什么,不如说展示它。

假设昨天我正在处理一些 Python 代码,以确定我的数据集中某个日期的前一天是否是假日。我将分享我的屏幕并浏览代码,而不是口头给出更新。

我们发现这样做有几个好处。很多时候,当我分享时,团队中的其他人会想到更好、更快或更简单的方法来解决问题。虽然这样的事情可能在代码审查中被发现,但是当你写了五行代码而不是 100 行代码时,发现它们通常要容易得多。其他时候,我发现我的团队中有人正在做一些非常相似的事情,并且可以通过重用我的代码而不用重新发明轮子来节省时间。最后,说实话,看到每天的进步而不仅仅是最终产品真的很酷。

准备演讲

在 scrum 之后,我有大约 30 分钟的时间去参加下一次会议。我发现通常没有足够的时间投入到任何真正的“数据科学”任务中,如清理数据或建模,所以我会用这样的少量时间来回复一天中收到的更多电子邮件或为任何即将到来的演示做准备。

照片由 Nghia NguyenUnsplash

作为一名数据科学家,我的大部分工作包括创建演示文稿,向他人介绍数据科学是什么,不是什么。许多高管听到人工智能和机器学*这些时髦词汇后会说,“我们应该这么做!”但事实是,机器学*并不总是答案。通常,基本的报告或简单的自动化将解决大多数团队的问题,我们不应该仅仅说我们正在做,就用机器学*使它过于复杂。

除了教育演示之外,我还可能向我的业务利益相关者演示初始模型的结果。在我作为数据科学家的第一年,我痴迷于在这些演示中尽可能多地包含术语,因为我想听起来聪明。这是一个巨大的错误!

我的利益相关者没有一个有数学学位,也不理解或关心我的模型的 F1 分数是多少。久而久之,我明白了了解你的听众,用通俗的语言说话的重要性。现在,当我讨论模型性能时,即使我在谈论 F1 分数,我也会称之为准确性。技术上精确吗?但是商业利益相关者比精确度和召回率之间的调和平均值更了解精确度。

如果你不能简单地解释它,你就理解得不够好——阿尔伯特·爱因斯坦

1-1 与经理

完成演示文稿编辑后,我会去和我的经理进行一对一的谈话。如果你是商界新人,这些会议是你与你的经理会面并讨论你的职业目标、最*的成功和/或你可能面临的任何挑战的机会。

照片由 LinkedIn 销售解决方案Unsplash 上拍摄

今天会议的议程上有一个挑战,我在团队的开发环境中设置了一个工具。在花了几周时间与另一个团队一起导航权限后,我们到达了一个点,我们都不知道如何正确地配置工具的安全设置。

因为我的经理在我的组织中比我有更广的关系网,所以当我认为我别无选择的时候,他能够给出一些可能会帮助我的人的建议。

从首席数据科学家那里获得反馈

接下来,我将会见我团队中的首席数据科学家,以获得一些关于我正在进行的概念验证的反馈。在过去的一个月里,我一直在使用我在结对编程一节中提到的图形数据库工具探索一个业务领域的数据集。

照片由 Alina GrubnyakUnsplash 上拍摄

在这个项目之前,我作为数据科学家的大部分经验都是在自然语言处理方面,所以我必须对图形数据库进行大量研究,并学*一种新工具。作为一名数据科学家,学*新的工具和技术是我最喜欢的事情之一,所以这是一个有趣的项目。

由于我自己已经做了大约一个月,我需要从头开始,向首席数据科学家展示一个数据样本以及它在网络图中的样子。接下来,我讲述了我编写的一些基本查询,比如确定哪个节点具有最多的关系。之后,我讨论了一些我尝试过的内置算法,比如确定网络中两个节点之间的相似性。

在讨论一些内置算法时,我注意到我无法尝试很多算法,因为我的图在两种类型的节点(二分图)之间有关系,并且算法只适用于具有一种节点类型的图。他建议我重新构建我的图,这样我就可以测试一些其他的算法,然后我们一起看新的数据模型可能是什么样子。

虽然我有点尴尬,因为我自己没有想到这一点,但这是从他人那里获得反馈的最大好处之一!有时候,你在解决一个问题时陷入困境,获得一个新的视角会非常有帮助。会议结束时,我们就我即将进行的业务领域演示集思广益。

代码!

最后,为了结束我的一天,我真的开始写代码了——耶!我最终从网络图转向了另一个业务领域的文档分类项目。我的团队有点像承包商,在多个业务领域的各种项目上工作。我喜欢的一点是工作的多样性。如果我对一个项目感到疲倦或沮丧,我可以换到另一个项目,让我的大脑放松。然而,最大的挑战之一是我需要不断学*新的业务领域和流程。

对于这个项目,我们没有标记的数据,正在尝试一种新的技术,称为主动学*,它本质上允许你用更少的标记数据点创建更好的机器学*模型。我们为公司内部的五个人选择了一个文档样本进行标记,我今天的任务是检查我们所有标记者的注释并确定一致意见。

在查看注释之前,我需要将五个单独的 excel 文件中的数据加载到一个数据框中,并对其进行转换,以便每个标签的结果都有一列。

作者图片

在获得正确格式的数据后(这总是比我预期的要长),我开始思考如何确定标签员之间的一致性。对于一些文档,所有五个注释者都同意这个标签,这使我的决定变得容易,但是对于其他文档,五个注释者中只有三个或更少的人同意。甚至有几次,五个贴标签的人都选择了不同的答案!

我决定从简单开始,采取多数决定的方法。如果三个或更多的标注者选择了同一个答案,它将被用作最终答案,并最终用于训练我们的模型。这让我花了一段时间编写代码,因为我也很想看看是否有这样的场景:三个人彼此同意,但另外两个人都同意不同的答案。我把我的决定做了一些笔记,并记下了明天的一些任务,以此结束了我的一天。

结论

你可能会感到惊讶,作为一名数据科学家,我一天中的大部分时间并没有花在编码上。甚至在我编码的时候,我也不是在制作机器学*模型——我是在清理和分析数据。当我得到第一份数据科学家的工作时,我以为我会花一整天来编写算法和创建复杂的机器学*模型。在实践中,我发现我的大部分时间实际上都花在了为建模准备/清理数据,以及理解生成这些数据的人/过程上。数据科学家通常被认为是一份超级酷和令人兴奋的工作,但事实是它并不像看起来那么迷人。这不一定是件坏事,我只是发现新数据科学家没有意识到他们正在让自己陷入什么。

如果你喜欢这篇文章,并且是一名新的数据科学家,想要了解在学术环境之外从事数据科学是什么样的,请查看我的研讨会,在那里我会教你在学校里学不到的技能。

硅谷数据工程师的一天

原文:https://towardsdatascience.com/a-day-in-the-life-of-a-google-data-engineer-722f1b2206cc

数据工程师在过去的 10 年里越来越受欢迎,但是数据工程师到底是做什么的呢?在我的经验中,数据工程师身兼多职,经常处于商业智能、软件工程和数据科学三角的中间。数据工程师的一个主要角色是与下游团队合作,如商业智能和数据科学,以了解业务的数据需求,并构建数据集成来提供这些数据。另一个角色可以是与软件工程师合作消费应用程序数据;典型的新软件开发工作,或“0 到 1”项目。数据工程师往往隐藏在暗处;监控数据质量仪表板、倾听工程冲刺以及在分析会议中偷听。一个好的数据工程师是你不会经常想到的;您的数据是按照 SLA 到达的,值是干净和有用的,并且您总是能够在任何生产发布中找到新数据。正因为如此,数据工程并不是像数据科学家那样性感的职业,但是如果没有数据工程师为他们提供新鲜干净的数据,数据科学家就无法创造价值。在许多较小的组织中,这对于软件工程师、分析工程师来说是典型的,很少;数据科学家做数据工程师的工作。

数据工程师使用 Java 等工具构建 API,使用 Python 编写分布式 ETL 管道,使用 SQL 访问源系统中的数据并将其移动到目标位置。数据工程师是数据领域的物流专家。

资料来源:www.unsplash.com

数据工程师需要什么技能才能成功?

为了回答这个问题,我最*在做了大量研究,分析了 1000 多份招聘信息。由于数据工程师涉及许多技术领域,技能也各不相同。常见的思路是编程语言:SQL、Python,偶尔还有 Java 极其突出。数据工程师的常用工具包括用于分布式数据处理的 PySpark、Redshift 或 Azure 等数据库以及 Kafka 或 Flink 等数据流技术。随着这些基础技术的建立,需求通常会向一个或多个方向倾斜。一些公司喜欢看到数据工程师配备像 Tableau 或 PowerBI 这样的数据可视化工具。许多其他公司更喜欢精通软件部署和使用 Docker 和 Glue 等工具的数据工程师。

这些技术是现代数据工程师的重要基础,但软技能很少被提及,并且是成功的数据工程师的关键组成部分。数据工程师必须是专业的沟通者,因为我的经验是,我们经常被夹在不同需求的不同团队之间。我们可能正在形成来自商业智能工程师团队的需求,并将它们转化为软件工程师的需求。能够熟练地驾驭这些相互竞争的需求、消除通信中的模糊性以及交付满足跨职能需求的数据管道是普遍面临的挑战。除了交流,数据工程师绝对不能停滞不前。技术在快速发展,今天相关的工具在 5 年前没有这么流行。随着工具的快速发展,数据工程师也必须如此。

数据工程师是创造性的问题解决者;通常打造新的路径来铺设基础设施和支撑架构,以领先于组织的需求。一个优秀的数据工程师还能够预见未来,并规划可扩展的系统,以满足不断发展的业务需求。

硅谷数据工程师的一天

8:00

我的一天从比利时华夫饼开始;这是成功的秘方。鲜切草莓和一杯咖啡让生活变得值得。

我登录查看邮件,希望没有收到任何关于管道故障的邮件通知。不可避免地,我有,这可能是最优先解决的事情,然后再做其他事情。坦率地说,这可以是从 15 分钟的修复到一整天的寻找 bug 的冒险。我个人的原则是:我想在业务之前找到失败的管道。如果企业忘记了我的存在,这又是成功的一天。不要误解我——我喜欢合作,但我更愿意谈论新的开发工作,而不是失败的管道。

九点

希望我能够快速回复电子邮件,快速诊断任何管道故障,然后继续前进。我在早上写代码效率最高,所以我尽量不把会议安排在 1:00 之前。今天,我答应了一个同事一个技术设计文档(TDD ),所以我将把我的注意力集中在那里。在 TDD 中,我填写关于新特性或项目的完整信息,最终在开始大规模开发工作之前进行同行评审,以确保我们在应该如何完成它上保持一致。

12:00

到了中午,我通常会开始对我的关注时间感到疲劳,我会在问题跟踪工具中检查其他承诺。我想确保我及时兑现了我的承诺,所以我确保添加更新,添加到待办事项列表中,创建早上可能发现的任何新的 bug 或功能请求,并将现有项目推向完成。

1:00

每天早上我都向自己承诺“今天我要吃一顿丰盛的午餐”——但这从未发生过。我更喜欢快速地吃我的午餐,以免分散我的注意力;这实际上让我保持高效。我已经学会避免吃高碳水化合物的午餐,更喜欢吃清淡的零食,而不是把自己吃得昏迷不醒。虽然;这种事情发生的次数比我愿意承认的还要多。

1:30

下午,我通常要参加两三个会议。我的第一次会议是我管理的一个多年项目的非技术利益相关者的签到。每周,我通常会研究新的工作成果,将它们转化为技术需求,或者自己完成新的开发,或者经常将它交给另一个数据工程师。我的第二次会议是与另一位工程师的工作会议。我们正在努力了解如何最好地部署管道,以便我们消耗负责任的资源量,并以这样一种方式集成不同的工具,以便更容易地扩展新的数据管道。我今天的最后一次会议是交接会议;我正在将一个项目移交给另一个数据工程师,希望确保我的知识不会在移交过程中丢失。我让他们了解我的代码的位置,我的设计原理图,以及项目的历史或发展。

4:00

我通常会留出一些管理时间,这些时间我会用在很多不同的地方。有时候,我努力指导新的团队成员。其他日子,我可能会利用这一天结束的时间来计划我的第二天早上。这段时间也偶尔用来构思新项目,寻找机会改进现有产品或功能,并寻找机会创造价值。发现机会的一个例子;我注意到一种趋势,即一组特定的文件经常导致违反类型的错误。我利用这一天结束的时间研究解决这个问题的不同方法,给不同的团队发电子邮件,告诉他们处理这个问题的方法,编写新的技术设计,并最终实现这个设计。这个项目节省了我团队中许多工程师的时间,因为它减少了每次出现这种类型违反错误时所造成的技术损失。

6:00

我结束一天的时间变化很大。当我付出 100%时,我的一天就结束了,油箱里什么都没有了。有时候,我 4 点就到了这里。就像通常一样,我会被一些发现所吸引,并一直工作到 8:00 或 9:00。它出来了,我总是可以解决故障或回答问题,这才是最重要的。

医疗保健数据分析师的一天——到底是什么样的?

原文:https://towardsdatascience.com/a-day-in-the-life-of-a-healthcare-data-analyst-what-its-really-like-88b12bacef28

对 2022 年工作角色和职责的现实描述

照片由艾丽丝·鲍德斯Unsplash 上拍摄

如果你正在互联网上搜索数据分析师日常工作的真实工作描述,这里是登陆的最佳地点。

大家好,我是 Rashi,Blue Cross Blue Shield的一名 数据分析师,来自芝加哥,这篇博客讲述了我作为一名分析师的日常工作——使用(和开发)的技能、工具、典型的软技能(包括 PowerPoint ),以及在过去七个月的工作中吸取的经验教训。**

在当今快速发展的技术世界中,这是数据的十年。企业预计,今年将是营销人员如何利用所产生的数据的一个突破。为了使分析战略与业务战略保持一致,企业已经建立了一种叙事来增加对数据证据的依赖,仅仅访问组织内的大量数据湖是不够的。

但在此之前,添加一点背景…

我的数据和分析背景

作为一个电子与通信工程专业的学生,专业并没有和我粘在一起。我一直在阅读技术世界的可能性,早在 2017 年,我第一次接触到数据和数据科学的世界。对这个领域非常陌生,我开辟了一条自学数据科学的道路。我选修了 Python 课程,练*了编码,参加了研讨会,并通过 IBM 数据科学专业认证涉足了数据科学。

当我完成我的顶点课程时,我已经被美国的学校录取为管理信息系统硕士。我在 2019 年开始了我的计划,目标是以敏锐的商业敏锐度学*数据和分析。在我学*金融和会计课程的同时,我还学*了数据挖掘、统计学和网络安全课程。

我作为数据分析实*生在百事公司实*了*一年,从事管道项目,做了多个独立项目,写了一篇研究论文,学生生活以医疗保健的全职工作结束(这是一个我非常热爱的领域)。

我对数据分析师的定义

企业有问题。

商家有数据。

数据分析师是一个讲故事的人,为业务创造和叙述一个情节。

数据分析师通过收集、挖掘、处理、执行数据分析,并以易于接受的格式将见解传达给高管,在公司改进产品和业务方面发挥着关键作用。

就像每天解决一个 1000 块的拼图。你知道更大的问题需要解决,你需要安排好这些部分。

我生活中典型的一天工作

除了确保我在早上 8:30 带着一杯咖啡到达总部,利用午休时间和同事讨论他们的周末计划,我挣工资的工作日包括—

  1. 每天与我的团队一起讨论当天的任务,集体讨论可能的解决方案
  2. 与跨职能团队开会讨论进度、识别风险、满足数据相关请求

>工作职责

  1. 从与索赔、内部业务流程和其他领域相关的来源收集、集成和分析数据
  2. 生成用于分析的数据集和报告,以支持战略需求
  3. 创建仪表板、报告、见解和分析来支持业务
  4. 不断深入了解业务数据,以满足业务需求
  5. 与跨职能团队交流,利用工具并帮助构建在业务环境中可操作的分析
  6. 承担项目领导责任,同时与管理层和业务利益相关者一起制定并坚持项目计划

>所需技能(技术和软件)

  1. 数据收集(识别问题的正确数据源)
  2. 数据查询、清理、挖掘和收集要求
  3. 预测建模、特征工程(选择正确的变量)
  4. 统计学和基础数学
  5. 交流(讲故事)
  6. 团队建设
  7. 适应未来(无论如何强调在工作中重新成为数据专业人员的重要性都不为过)

这份工作没有深度学*、神经网络或复杂的机器学*模型——这是一份数据分析师的工作简介,而不是数据科学家。

>我使用的工具

  1. 数据查询 : SQL Server,Teradata
  2. ETL : Alteryx
  3. 数据分析: Python
  4. 数据可视化: Tableau,用于小数据的 PowerBI
  5. Office 365 工具: Microsoft Excel(宏、数据透视表、vlookup 是 staples、PowerPoint)

每个企业对工具都有自己的偏好,虽然我的工作涉及大多数数据分析工具,但我在这里没有提到一些企业内部的工具。

作为数据分析师的经验教训

现在和未来都是数据。作为一名数据分析师,在你放弃所有那些(头脑中虚构的)竞争来获得一份工作之后,旅程才刚刚开始。

  1. 建立对业务的熟悉并培养敏锐度
  2. 从一开始就用商业语言武装自己,并坚持不懈
  3. 探索如何应对新的数据分析需求和项目
  4. 永远,永远在你的团队之外建立一个同伴网络
  5. 培养负责任的领导力——你在负责任时的行为让你值得被认可

这就是我的博客的结尾。感谢您的阅读!请在评论中让我知道你的数据之旅以及 2022 年的目标!

如果你喜欢看这样的故事,可以考虑从这个 链接 注册成为一名中等会员!

数据帐篷快乐!

Rashi 是一名来自芝加哥的数据奇才,他喜欢将数据可视化,并创造富有洞察力的故事来传达商业见解。她是一名全职的医疗保健数据分析师,周末喝一杯热巧克力,写一些关于数据的博客

深入探讨 ML 的曲线拟合

原文:https://towardsdatascience.com/a-deep-dive-into-curve-fitting-for-ml-7aeef64755d2

曲线拟合是所有机器学*的基础问题

奥斯曼·拉纳Unsplash 上的照片

曲线拟合是机器学*中最具理论挑战性的部分之一,主要是因为它对最终结果有多么重要。虽然在处理具有少量要素的相对简单的数据集时,这可能不会带来挑战,但在更复杂的项目中,不恰当的拟合更有可能出现。

维度的诅咒加入其中,曲线拟合从可能的直观变成不可能的不可及。然而,过度拟合(或欠拟合)会导致模型拙劣,需要投入额外的资源来重做整个过程。

不幸的是,大多数在线教程除了提供常用函数的例子之外,并没有更深入的研究。关于如何思考曲线拟合的内容很少。

欠拟合和过拟合

首先,曲线拟合是一个优化问题。每次的目标都是找到一条正确匹配数据集的曲线。不恰当的做法有两种——欠拟合和过拟合。

几乎每个人都更容易理解不合身。每当函数勉强捕捉到数据在散点图中分布的复杂性时,就会发生这种情况。通常情况下,这些最容易在二维空间中可视化,但曲线拟合通常必须在更多空间中完成。

拟合不足的问题很明显。具有这种曲线的模型会做出错误的预测,因为它试图在很大程度上简化一切。例如,它可能只从几十个数据点中捕获几个。

过度拟合有点复杂。直觉上,你似乎想通过完美地拟合曲线来最大化模型的准确性。在现实世界中,过度拟合会导致在测试模型时出现大量错误。

有许多潜在的方法可以理解为什么过度拟合是一个问题。一种是认为任何数据集都是不完整的。除非您获取了所有现有的数据点,否则将会有一些未知数,这些未知数将具有一些可预测的但不完全相同的分布。一个过度拟合的模型会很好地学*这些模式,以至于它会期望它们在未来是相同的。

最后,人们可能会认为过度拟合使模型更接*决定论,而不是让它具有随机性。合适的合身程度介于合身不足和合身过度之间。

四种情况

所有的曲线拟合(至少对于机器学*来说)都可以根据手头问题的先验知识分为四类:

  1. 完全知晓。不存在拟合问题,因为如果 f(x)已知,则无需任何猜测即可应用。所有未来的数据都会整齐地落在曲线上。
  2. 未知,但结构已知。在这种情况下,曲线可以是已知的,例如直线,但是没有关于其他参数的数据可用。
  3. 未知,但能猜到。在二维数据中,有时我们什么也没有,但由于费用相对简单,我们可以对曲线应该是什么作出合理的假设。
  4. 未知。模型函数 f(x)是完全未知的,没有任何猜测,参数是神秘的。

每一种情况都带来了越来越严峻的挑战。相对而言,你很少会遇到第一种情况,主要是在教程或其他教学材料中。我们与机器学*的大多数接触都将发生在场景二和场景三之间。

场景#2 和#3 将有相似的基本部分。对于前者,正确地猜测参数值将定义拟合的良好程度。对于后者,每个猜测都必须带有相关的参数值,这些参数值的组合将定义良好性。

要记住的一件重要的事情是,每当试图猜测一个函数时,你应该应用奥卡姆剃刀(即,从两条同样拟合的曲线中,你应该选择参数最少的一条)。通常,您应该尝试以尽可能少的参数结束。任何超过这个数字的函数,但同样适合,都是过度复杂的。

评估拟合优度

无论你面对哪种情况,一旦你有了某种拟合,你必须评估选择的曲线是否好。有几种方法可以用来感受这种美好。

在可以通过二维平面上的散点图表示的简单模型中,目视检查通常就足够了。您必须使用所选编程语言中可用的任何库来绘制图形(例如,Python 的 Matplotlib )。

对于相对简单的曲线,简单地在散点图上画出东西,并画出函数,可能会揭示足够多的优点。然而,这样的评估容易出现人为错误,也许还有点自欺欺人。

残差图是一个更好的选择。事实上,这可能是拟合优度可视化的最佳选择。如果剩余图看起来不错,那么它很可能也很合适。这些图中的模式也暗示了线性(随机分布)或非线性回归(成形分布)。

最后,有一些评估拟合优度的数学方法。在大多数教程中,你会发现最常用的是 R 的平方测试。它将在大多数机器学*包和库中可用(例如,Python 的 sklearn.metrics ),允许你做出简单的估计。

R 的平方可以理解为线性模型所解释的方差。换句话说,1 将意味着一个完美的匹配,随着数字的下降,优度也在下降。虽然它很受欢迎,但它仍然会把你引入歧途。

有些情况下,曲线拟合得很好,但数据包含大量无法解释的可变性。在其他情况下,分布可能是有偏的而不是随机的,导致 R 的平方很高。

简化的卡方值或多或少是拟合优度测量的黄金标准。虽然有点复杂,但大多数编程语言包和库都有方法或函数,可以让你插入值并完成它。

简化的卡方产生的结果比用 R 的平方产生的结果稍微复杂一些,因为前者可以产生任何数。接* 1 的结果,拟合良好。如果在 1 以上,还有提升空间。如果低于 1,则模型可能过拟合(或者误差可能太大)。

结论

所有的曲线拟合问题都是一种平衡行为,即找到一个性能相当好的函数,但既不太好也不太差。从某种意义上说,每当必须选择一条初始曲线时,几乎总会涉及一些猜测。然而,关键的一步是在以后评估它的优点。

评估拟合优度可以省去以后的麻烦。对于较小和不太复杂的模型来说,视觉检查可能是值得的,但数学方法不太容易自欺欺人。

R 的平方是估计拟合优度的一个很好的尝试,主要用于数据相对不复杂的线性模型。对于其他的一切,我会使用简化的卡方。还有许多其他方法来评估拟合优度,然而,我概述的两种方法是最常用的。

深入探讨使用主成分分析进行降维

原文:https://towardsdatascience.com/a-deep-dive-into-dimensionality-reduction-with-pca-bc6f026ba95e

一个简单而强大的降维算法的数学深度挖掘

美国地质勘探局在 Unsplash 上拍摄的照片

如果你不熟悉 PCA,它本质上是一种将高维数据集转换为低维数据集的算法。主成分分析(PCA)是一个非常强大的工具,尤其是在处理大型高维数据集时。在这篇博文中,我试图解释 PCA 算法是如何工作的,并说明它与奇异值分解(SVD)的联系,奇异值分解是线性代数中一种重要的矩阵分解。

在深入数学直觉之前,我们先来看看算法有多简单!

PCA 算法

  1. 将数据矩阵 X 居中,使所有列的平均值为 0
  2. 计算 XᵀX
  3. 获得 XᵀX 的特征值,并按降序排列
  4. 找出 XᵀX.的特征向量,特征向量代表变换向量的权重/负载
  5. 将原始数据 X 转换成 PCA 形式 Z

第一次学这个的时候(没有任何证明),好像太神奇了。通过获取看似任意的矩阵 XᵀX 的特征值和特征向量,我们可以找到数据的有效的低维表示。让我们看看为什么这个简单的算法有效。

方差最大化

罗伯特·斯汤普在 Unsplash 上拍摄的照片

在执行降维时,我们希望保留尽可能多的信息。这个目标可以通过最大化变换数据集的方差来优化。

一个令人困惑的问题是为什么我们要最大化方差。高方差可能会让你联想到高方差模型,这通常是不好的,是过度拟合的迹象。在这种情况下,PCA 不进行任何类型的预测或估计。当我们说模型(回归或分类)具有高方差时,这意味着它对坏的训练数据过度敏感。在我们执行降维的情况下,所讨论的“方差”是该数据集中特征的方差。

这里的差异表示数据集中存在的有用信息。如果某个特征的方差为 0(该特征的所有值都相同),那么它对我们的分析就没有用了。

由于 PCA 是用线性变换来表示的,所以我们要做的是找到一个权重 W 的矩阵,它可以变换我们的原始数据集。

对于由 n 行和 k 特征组成的数据集 X ,我们希望将其转换为具有精简的 r 特征集的转换数据集 Z

使用权重矩阵 W 将 X 转换为 Z

利用这一点,权重矩阵 W 自然将具有维度 k × r 。既然我们已经公式化了我们的问题,让我们看看优化标准。

如前所述,我们希望最大化转换数据集 z 的方差。

给定均值= 0 时变换数据集方差的数学表达式

使用上面的方差定义,我们可以导出优化标准。因为我们的原始数据集位于中心,所以特征μ的平均值将为 0。这导致了 WᵀXᵀXW.的优化目标

为了简化优化,我们可以将权重矩阵 W 分成单独的权重向量。

z_i 是指第 I 个特征,是一个大小为 n 的行向量。对于原始数据集中的 k 要素,第 I 个权重向量 w_i 的大小为 k 。为了优化整个矩阵 W ,我们可以对从 1 到 r 的每个向量 w_i 重复优化过程。

“欺骗”优化的一种方式是让 w_i 变得非常大。使用这种方法将导致转换数据集的方差大于原始数据集。因为这不是我们想要的,我们限制 w_i 的值,使得向量|| w_i ||的范数为 1。这使得转换数据集的上限不大于原始数据集。

第 I 个向量的优化目标

这个最优的 w_i 就是 XᵀX (这里证明比较复杂)的特征向量。有趣的是,w_iᵀXᵀXw_i 值取决于与特征向量相关的特征值。如果我们把最大特征值关联的特征向量作为第一特征 w_1 ,它会给我们最好的可能结果。

通过按降序取特征值,对随后的权重向量重复这一过程。

这是因为 XᵀXt39】的对称矩阵。对称矩阵是特殊的,因为它的特征向量构成了标准正交基。这导致我们的新特征彼此正交,这具有新特征 z_i 不相关且独立的优点。

对于每个秩为 n 的矩阵,我们可以将其分解成 n 个独立的正交向量,但这将给我们与原始问题完全相同的维数。这里 PCA 所做的是丢弃具有较低方差(低特征值)的特征。这些复合要素在整个数据集内变化不大,因此它们被确定为不太重要,可以丢弃,对其余数据的影响最小。

奇异值分解

理解 PCA 的另一种方法是线性代数。利用线性代数,我们可以从“矩阵分解透镜”来观察 PCA。在这种情况下,正在进行的矩阵分解是奇异值分解(SVD)。

什么是奇异值分解?

SVD 是一种通用的矩阵分解算法,它将一个矩阵分解为 3 个独立的矩阵。SVD 的妙处在于它既可以用于正方形矩阵,也可以用于矩形矩阵。

这三个矩阵是

  • u:aaᵀ的特征向量
  • σ:奇异值矩阵。这些是 AᵀA 和 AAᵀ特征值的平方根
  • 转置后 AᵀA 的 Vᵀ:特征向量。

奇异值分解的工作方式是利用对称矩阵 AᵀA 和 AAᵀ的某些性质来获得分解的特征向量。通过这种因式分解,我们可以将矩阵 X 表示为多个外积/秩 1 矩阵的和。这里,秩 1 矩阵的重要性由奇异值σ_i 表示。

作为外积和的奇异值分解。σ_1 对应于最大奇异值

让我们来看看这个方法的实际应用!我们可以使用奇异值分解来分解 MNIST 数据集中的样本。因为数据来自 8×8 矩阵,所以总共有 8 个分量组成图像。

作者用 SVD — GIF 压缩图像

我们观察到图像中的大多数重要细节可以在 3 个分量之后观察到。这些分量具有最大的奇异值,并且是最重要的。因此,我们可以安全地丢弃具有低奇异值的矩阵,以减少 x 中的信息。这样的一个应用是图像压缩。我们可以选择存储第一个 r 矩阵,而不是存储一个巨大的 n × n 图像。因为每个矩阵由 2 个 n 大小的向量组成,所以空间的大小将是 2nr 而不是 n

这与 PCA 非常相似,我们采用第一个 r 最重要的特征。关键区别在于,在 SVD 的这种应用中,矩阵乘法的结果具有相同的维数,但它被压缩成更易于存储的向量。

把所有的东西放在一起

现在我们对 SVD 做什么有了一些直觉,让我们更仔细地看看 SVD 公式,并做一些重新排列。

因为 v 是对称矩阵 AᵀA 的特征向量矩阵,所以它将是正交基,并且 VᵀV= I 。后乘以 V 将得到第二行。这里我们看到左手边非常熟悉。

根据我们的 SVD 分解,v 由 XᵀX 的特征向量表示,该特征向量恰好是先前在 PCA 中导出的权重向量。矩阵乘法 XV 是与来自 PCA 的 XW 完全相同的结果。

这允许我们减少 V 的维数,如果我们减少矩阵 V 中的列数,我们就减少了 x V(转换后的数据集)中的特征数。

这说明 PCA 可以解释为矩阵分解的中间步骤!

代码实现

让我们使用相同的 MNIST 数据集来比较不同的主成分分析方法!

我们从预处理步骤开始,使数据集居中,并确保所有特征的平均值为 0。

数据预处理

对于第二个实现,我们将使用博客文章开头的原始算法。首先,我们通过矩阵乘法计算协方差矩阵。然后用np.linalg.eigh函数求出特征向量和特征值。

对于第三个实施方案,我们使用np.linalg.svd函数直接计算特征向量矩阵 v。与之前的实施方案相比,这为我们节省了几行排序和矩阵乘法代码。

现在你知道了!以 3 种不同方式执行 PCA 的数学直觉和代码实现。检查代码并运行它,让自己相信它是可行的!

https://github.com/reoneo97/medium-notebooks/blob/master/pca-svd.ipynb

如果你喜欢这篇文章,请在 Medium 上关注我!
在 LinkedIn 上连接:https://www.linkedin.com/in/reo-neo/

参考文献

[1] G. Strang,线性代数导论,第四。马萨诸塞州韦尔斯利学院:韦尔斯利-剑桥出版社,2009 年。

深入研究广义最小二乘估计

原文:https://towardsdatascience.com/a-deep-dive-into-generalized-least-squares-estimation-8bf5319edd7d

图片由 Clker-Free-Vector-Images 来自 Pixabay ( Pixabay 许可)

详细介绍如何在异方差、自相关数据集上拟合稳健的 GLS 模型

广义最小二乘 ( GLS )估计是普通最小二乘(OLS)估计技术的推广。GLS 特别适用于拟合呈现异方差(即非恒定方差)和/或自相关的数据集的线性模型。真实世界的数据集通常表现出这些特征,这使得 GLS 成为 OLS 估计的非常有用的替代方法。

GLS 估计量的动机

当我们使用普通最小二乘 ( OLS )估计技术来拟合数据集的线性模型时,我们做出两个关键假设:

  1. 回归模型的误差方差是恒定的,即误差为同伦方差,并且
  2. 这些误差彼此之间或它们自身之间没有关联。

但在现实世界的数据集中,这些假设中的一个或两个通常都不成立。例如,考虑以下美国县级贫困模型:

估算县级贫困的线性模型(图片由作者提供)

对于一个给定的数据集,一个统计包,如 statsmodels 可以用来估计这个模型。使用 statsmodels 对来自美国人口普查局的 2015–2019 年美国社区调查(ACS) 5 年估计数据(参见文章底部的使用条款)进行的线性模型的 OLS 估计产生了以下拟合模型:

利用 OLS 估算县级贫困程度(图片由作者提供)

这里, e_i 是回归的残差。

以下是 Statsmodels 生成的模型训练摘要,其中显示了所有系数在 p < .001:

Training summary of the linear model (Image by Author)

At face-value, this fitted model seems sound. But a plot of the residual errors e_i 处相对于相应的预测值Percent _ Households _ Below _ Level 具有统计显著性,这揭示了一个严重的问题:

拟合模型的残差与响应变量的估计值的关系图。显示红线只是为了说明模型残差中方差的增加模式(图片由作者提供)

模型的误差(由拟合模型的残差估计)明显是异方差的。在这种情况下,误差方差作为 y _cap 的函数增加。

这应该让我们怀疑评估软件报告的标准误差、p 值和置信区间。

下面是潜在的问题。

OLS 估计量的问题是

在使用 OLS 估计量时,我们倾向于假设模型的误差是同方差和不相关的。因此,我们用于估计估计系数方差的公式也做了相同的假设。但是正如我们在上面的例子中看到的,如果误差是异方差的和/或相关的,方差公式输出的估计系数的方差值是不正确的。方差的不正确估计会导致以下度量估计的下游误差:

  • 系数的不正确标准误差(标准误差是方差的平方根)。
  • 系数估计值的 z 分数不正确(系数的 z 分数与标准误差成反比)。
  • 系数估计值的 p 值不正确。错误计算的 p 值可能会导致某些系数被错误地报告为具有统计显著性(反之亦然)。
  • 最后,系数估计的置信区间不正确。

总的来说,推理过程产生了不正确的结果,尽管从表面上看,拟合的模型似乎完全没有问题。

可以看出,当模型的误差是异方差的和/或相关的时,OLS 估计器虽然仍然是一致的无偏的,但不再产生模型系数的最低可能方差估计。至少理论上有可能设计另一种估计器,它将产生具有更低方差的系数估计,从而具有更高的精度

简而言之,OLS 估计器不再有效。

补救措施

处理异方差误差的一种常见方法(尽管对于相关误差来说不那么常见)是使用所谓的 White 的异方差一致性估计量。我在我的文章“介绍 White 的异方差一致性估计量”中详细介绍了这个估计量。

但是 White 的 HC 估计量有一些缺点,其中主要的是它只考虑了异方差,而没有考虑误差之间的相关性。第二个问题是,在样本大小的样本中,白色 HC 估计器会低估系数估计中的方差,从而导致与 OLS 估计器相同的问题。

获得 GLS 估计量

处理异方差和/或相关误差的更直接的方法是遵循以下两点计划:

  1. 使用 OLS 拟合数据集的线性模型。使用拟合模型的残差作为线性模型误差的代理,创建一个使用拟合模型残差观察到的异方差性和/或相关性的模型。
  2. 设计一个估计器,在其估计技术中使用这些模型化的方差和相关值。

这种估计量对于模型误差项中的异方差性和相关性都是稳健的。这正是广义最小二乘(GLS) 估计器采用的方法。

GLS 技术的发展

在这一节中,我们将从基本原理出发开发 GLS 估计量,并了解如何使用它。

让我们从下面的线性模型开始:

线性模型(图片由作者提供)

y 是响应变量。对于大小为 n 的数据集, y 是大小为【n×1】的列向量。假设模型有 k 个回归变量,包括截距。 β 为回归系数【β_ 1,β_2,…,β_ k】的列向量,其中 β_1 为截距。 X 是回归变量的矩阵,包括矩阵第 1 列截距的占位符。 X 大小为【n X k】

数据集中第样本为元组: (y_i,x_ I)其中 y_i 为标量(纯数)x_ I为大小为【1 x k】的行向量。

通常,回归模型帮助我们“解释”响应变量【y】中的一些方差。模型无法解释的东西“漏”进了模型的误差项。就像yϵ是大小为【n×1】的列向量。

下面是等式(1)的矩阵形式:

线性模型(图片由作者提供)

可以看出, 的系数向量 β的普通最小二乘(OLS)估计产生以下估计量:

估计系数的向量,使用普通最小二乘法进行估计(图片由作者提供)

上式中, X 'X转置。矩阵转置操作实质上是沿着矩阵的主对角线翻转矩阵,即从左上延伸到右下的对角线。转置操作在概念上翻转矩阵。由于的大小为【n X k】,其转置 X ' 的大小为【k X n】。**

我们在等式(2)中看到的另一种矩阵运算是上标中的 (-1) ,它表示矩阵的逆。矩阵求逆相当于对一个数做 1 的运算。

将等式(1)代入等式(2),我们得到以下结果:

将等式(1)代入等式(2)(图片由作者提供)

在简化了上面的结果位之后(我已经在本文的中详述了简化),我们得到了下面的系数估计的有用公式。以下结果显示了误差项 ϵ 对 OLS 估计的系数值的影响:

的 OLS 估计量作为 X ϵ (图片由作者提供)

由于 β _capβ 的估计值,β_ cap是一个随机变量,它有均值和方差。

可以看出,_ cap的均值(也叫期望值)就是总体水平的值 β。 具体来说,β_ cap X 为条件的期望是 β。****

_ capX 为条件的方差用Var(_ cap| X)表示。为了计算Var(β_ cap| X)*),我们采用以下公式计算(条件)方差:***

一个矩阵随机变量 Z 的条件方差公式,用 Z 表示,其均值(期望值)E( Z )和退化的 Z 的转置(图片由作者提供)**

代入Z=β_ cap,和E()Z)=β,我们得到:**

系数估计值的条件方差(图片由作者提供)

在上述等式的 R.H.S .中,我们将等式(3)中的 β_cap 替换为 β + Aϵ ,经过大量的简化(细节在此)后,我们得到估计系数方差的以下公式:

线性回归模型的 OLS 估计系数的方差公式(图片由作者提供)

中间的术语(蓝色)值得注意。

回想一下 ϵ 是模型的误差项。因此,根据定义,是一个随机变量。 ϵ 是一个大小为【n×1】的矩阵,大小为【1×n】。由此可见,按矩阵乘法的规则是一个大小为、【n×n】的矩阵。 ϵϵ' 也是一个随机变量。****

e[(【ϵϵ'】)| x是随机变量的期望(【ϵϵ')条件制约于**e[(******

模型误差项的条件方差-协方差矩阵(图片由作者提供)

由于假设误差项的平均值为零(线性模型的中心假设),沿主对角线的元素,即从上述矩阵的左上延伸到右下的元素,包含e[(ϵ_iϵ_i)| x],实际上是误差项 ϵ_i、和所有非对角线元素 E[(ϵ_iϵ_j)的条件方差****

由此可见,e[()| x]是方差-协方差矩阵;简而言之,误差项的协方差矩阵。****

在统计文献中,误差的协方差矩阵通常表示如下:

回归误差的协方差矩阵(图片来自作者)

σ是一个比例因子,一个常数,我们从矩阵中提取出来,使得 ω_ij=ρ_ij/σ 。得到的“omegas”的【n×n】矩阵用大写希腊字母ω表示。**

因此,

e[()| x]=σ****ω******

ω的主对角线元素包含误差的(缩放)方差,而ω的所有其他元素包含误差的(缩放)协方差。****

如果模型的误差是同方差的(恒方差),那么的所有主对角线元素都是 1 ,即对于所有 i 来说 ω_ii = 1

如果误差不相关,则ω的所有非对角线元素均为 0。****

因此,对于同分布、不相关的误差,协方差矩阵采用以下形式:

当误差为同方差且非自相关时,回归模型误差的协方差矩阵(图片由作者提供)

在等式(5)中, I 为大小【n×n】的单位矩阵。将等式(5)代入等式(4)并稍微简化,我们得到这个漂亮的小结果:

当模型误差为同方差非自相关时,拟合回归系数的协方差矩阵公式(图片由作者提供)****

但是,如果模型的误差是异方差的和/或相关的,误差的协方差矩阵就不再是σI了,上面的方差公式会产生不正确的结果,导致我们在文章开始时讨论的所有问题。

广义最小二乘法

解决这个问题的直接方法是 GLS 。我们首先定义一个大小为【n x n】的方阵 C 和一个大小为【n x n】的对角矩阵 D ,使得协方差矩阵可以表示为C的乘积******

(图片由作者提供)

在某些条件下(除了说【C】是所谓的正交矩阵,我们不会在这里深入讨论),总是有可能找到两个这样的矩阵 CD 。顺便说一下,对角矩阵是指不沿着主对角线的所有元素都为零的矩阵。下面是 D 的样子:**

D 矩阵(图片作者提供)

主对角线元素 d_ii 可能或可能不都具有相同的值。单位矩阵 I 是所有对角元素都为 1 的对角矩阵的例子。

接下来,我们定义一个矩阵 G 使得它的转置是矩阵 CD 的以下乘法:

矩阵 G(作者图片)

D 的指数版值得解释一下。如果 D 的对角元素是 d_ii ,那么 D 的幂(-1/2)本质上是矩阵的“平方根”的逆,它包含对角元素 1/√d_ii ,如下:

对角矩阵 DD 的逆“平方根”(图片由作者提供)

我们很快就会明白为什么要进行这些神秘的转变。

让我们回忆一下我们在文章开头一直提到的线性模型的等式,在等式(1)中:

线性模型(图片由作者提供)

我们将等式(1)的两边左乘 G 如下:

转换后的线性模型(图片由作者提供)

让我们说服自己,方程(9)中的模型仍然是线性模型。 G 是大小为【n×n】的方阵。由于 y 是一个大小为【n×1】Gy只是大小为【n×1】y 的缩小版。同样,的大小为【n X k】,因此(【GX】)是大小为【n X k】的缩小版。而()gϵ)是尺寸【n×1】的ϵ的缩小版。因此,等式(1)是的缩放(有些人可能会说是变换)版本对的相应缩放(变换)版本的回归。原始线性模型的系数 β 将同样适用于等式(9)的缩放模型。******

为了方便起见,我们将 Gy 替换为 y GX 替换为【x***替换为:******

转换(缩放)的线性模型(图片由作者提供)

*因为等式(10)是有效的线性模型,所以适用于线性模型的所有结果对它都成立。首先,误差项 ϵ 的方差可表述如下:

*缩放线性模型的误差项 ϵ 的方差(图片由作者提供)

*在等式(1)中,我们已经用右侧的 替换了 ϵ 。我们将上述等式的右侧简化如下:

研究比例模型误差协方差矩阵的公式(图片由作者提供)

我们现在将处理等式(12)中的黄色位。为此,我们将使用等式(7)和(8)计算ωG ,我们将在下面复制这些等式:****

(图片由作者提供)

矩阵 G(作者图片)

我们将在等式(12)中使用这些替换,如下所示:

(图片由作者提供)

在等式(13)中,我们使用了恒等式(G')' = G,即转置的转置返回给我们原始矩阵。

让我们简化等式(13)的均方根:

术语的简化GωG '(图片由作者提供)

我们之前提到过 C 是一个正交矩阵。正交矩阵的一个特性是它的转置等于它的逆矩阵:

正交矩阵的转置与其逆矩阵相同(图片由作者提供)

这意味着 CC 的乘积与 C 及其逆的乘积相同。 C 与其逆的乘积就是单位矩阵 I 。这个结果就是 N 乘以 (1/N) 等于 1 的矩阵等效值。

这暗示着CC=I。让我们继续简化GωG’:****

术语GωG’(图片由作者提供)

为了进一步简化方程(14)的 R.H.S .,我们必须回忆对角矩阵【D】D 的幂的性质:****

对角矩阵 DD 的逆“平方根”(图片由作者提供)

此外,对角矩阵的转置是相同的矩阵,因为转置操作简单地围绕主对角线翻转矩阵,并且在对角矩阵中,所有非对角元素都是 0。

理解了这个设置后,可以看出下面的乘积等同于一个大小为【n×n】:的单位矩阵

(图片由作者提供)

将这个结果代入等式(14),我们得到:

GωG '解析为【n×n】单位矩阵(图片由作者提供)****

将等式(15)代入等式(12),然后代入等式(11),我们得到缩放回归模型y*** =xβ+【ϵ其中 y x =GXϵ**** =gϵ. 我们把这个结果总结如下:******

缩放线性模型的误差协方差矩阵y*** =xβ+**ϵ(图片由作者提供)

方程式(16)是 GLS 技术发展中的一个重要结果。它陈述了比例线性模型的误差是同伦的(即,恒定方差)和不相关的。因此,该线性模型β 的最小二乘估计器必然是有效的,即具有最低可能的方差(除了一致和无偏之外)。

这使我们得出一个重要的结果:

即使数据表现出异方差性和/或自相关性,我们开发的缩放(转换)线性回归模型也可以使用高效、一致和无偏的最小二乘估计器进行拟合,换句话说,它将是该模型的BestLlinearUn biasedE估计器。

我们如何为缩尺模型开发这样一个最小二乘估计器?我们的做法如下:

回忆等式(2),等式(2)说明线性模型y=+ϵ的最小二乘估计量由以下公式给出:**

估计系数的向量,使用普通最小二乘法进行估计(图片由作者提供)

*在等式(2)中,如果我们将 y 替换为 y *** 以及将 X 替换为 X ,我们得到缩放线性模型的以下估计量:

βT20【GLS 估计量】(图片由作者提供)

为了简化 Eq (17),我们用y**** =gyx***** =GXϵ***** =gϵ. 我们还利用了(【GX】)’=XG’。我们还利用了ω矩阵的逆是 G'G 的结果。后一个结果由等式(7)和(8)得出。这些替换如下所示:***

GLS 估计量为(图片由作者提供)

等式(18)是 GLS 估计量(也称为艾特肯广义最小二乘估计量)。无论数据集是否表现出异方差性和/或自相关性,它都是一个有效的、一致的、无偏的估计量。换句话说,保证是系数向量*β的BestLlinearUn biasedEestimator。*****

只有一个问题。

它依赖于我们知道误差的协方差矩阵ω。但是ω本质上是不可观测的,因为它包含了实验者无法直接观测到的模型误差项的协方差。******

解决方法是建立一个ω的模型,并进行估算。然后使用 GLS 估计器 使用该估计来估计 β 这种使用ω的估计版本的策略有时被称为可行广义最小二乘 ( FGLS )技术。******

估算ω有几种策略。* 其中一个这样的手法型号ω为:*****

一模一样的ω(图片由作者提供)

在上面的模型中, ω_ii 是数据集中第与第行对应的第与第误差 ϵ_i 的方差。我们估计 n 方差ωII,方法是回归拟合数据集的 OLS 模型中yy预测值的残差(我们将在下周的本文第二部分中看到如何做)。****

ρ 是第和第 (i+1)个误差项之间的相关性,即 ϵ_iϵ_(i+1。我们通过拟合数据集的 OLS 模型残差的自相关图来估计 ρ****

在上述矩阵中,我们假设误差项之间的相关性按照幂律衰减,即ρ 、…等。随着相应数据集行之间的间隔增加。

在本文的第二部分(将于下周发表),我们将通过一个教程来学*如何使用广义最小二乘估计量来拟合 ACS 数据集的线性模型,以估计美国的县级贫困率。

敬请期待!

参考文献、引文和版权

数据集

本文使用的美国社区调查数据集可以从这里 下载 。使用公开可用的API(参见服务条款此链接)可以从美国人口普查局的网站获取完整的 ACS 数据集,或者直接从人口普查局的 社区资源管理器 网站获取。

艾特肯(1936)。四。—关于最小二乘法和观测值的线性组合。爱丁堡皇家学会会议录, 55 ,42–48。土井:10.1017037686687

形象

本文中所有图片的版权归 CC-BY-NC-SA 所有,除非图片下面提到了不同的来源和版权。

如果你喜欢这篇文章,请关注我的Sachin Date获取关于回归、时间序列分析和预测主题的提示、操作方法和编程建议。**

深入了解 Power BI 增量更新功能

原文:https://towardsdatascience.com/a-deep-dive-into-power-bi-incremental-refresh-feature-a5fc8af781ee

它是什么,它是如何工作的,它到底是如何工作的?

图片由作者提供。这张图片中的名字是虚构的,由电脑生成。

如果你正在读这篇文章,你很可能知道什么是力量比(PBI)和它的用途。在本文中,我想更深入地探讨一下他们的增量更新,这无疑给社区和我认识的人带来了很多困惑。

在 PBI,有一个叫做增量刷新的特性,它允许你的数据集被增量刷新。微软也谈到了增量更新的好处,所以在这里继续之前先读一下。与传统的 ETL/ELT 不同,PBI 有一种相当独特的增量刷新数据的方式,这将在后面讨论。

*我只想强调,此增量刷新仅适用于发布到 PBI web 服务的数据集。

入口

ETL/ELT 基础

Stitchdata 写了一篇关于 ETL 过程基础的详细文章,可以在这里找到。

总而言之,ETL 工具通常会使用变更数据捕获 (CDC)来检查新数据。但是,由于各种原因,并非所有数据库都启用了 CDC,在这种情况下,将使用最新的行更新时间戳。然后,新的和更改的数据将被追加到数据仓库中,或者根据业务案例覆盖旧的数据。

微软增量更新理论基础

回头看看微软提供的文档,增量刷新被分解为几个组件,它们是:

  1. 存档数据
  2. 增量数据范围
  3. 检测数据更改

而且文档中也写明了微软基于分区刷新数据。

为了帮助你,我在 Excel 中创建了一个简单的表格来演示理论上的工作方法。让我们假设今天是 2022 年 8 月 2 日。

由作者提供的图像数据集

上面是我们第一次将数据加载到 PBI 服务时,初始数据集的一个小样本集。将自动创建几个分区,如下所示:

初始分区数据集:图片由作者提供

1.存档数据

图片由作者提供:Power BI 增量弹出存档选项截图

在不更新任何数据并将存档数据范围设置为 1 年的情况下,PBI 服务将在 1 年前的今天(2022 年 8 月 2 日)删除所有数据分区。在上面的例子中,分区 2020 将被删除。

2.增量数据范围

图片由作者提供:Power BI 增量弹出增量选项截图

如果我们将增量数据设置为刷新日期前 7 天的范围,实质上 PBI 服务将删除今天之前 7 天的数据,并重新加载所有 7 天的数据。在我们上面的样本数据集中,“Partition 2022–08-xx”中的所有内容都将被删除,并从源中重新加载。

3.检测数据更改

图片由作者提供:Power BI 增量弹出检测数据更改选项截图

现在,当我们选择 detect data changes 时,PBI 服务将只刷新增量范围(在我们的示例中是最后 7 天)的分区,其中最大更新日期被更改。

图片由作者提供

在我们的数据集示例中,如果 F 数据被更改,不会发生刷新,因为分区 2022-Q1 不在我们过去 7 天的增量范围内。

更新的增量刷新范围:图片由作者提供

但是,如果我们在刷新日期之前将增量范围从 7 天更改为 12 个月,分区 2022-Q1 数据(E 和 F)将被删除并重新加载。

最好和最坏的情况

最好的情况看起来像一个事务表,其中添加了新行,旧行没有更新。

正常的情况是,通常添加新行,更新的行在一个分区内。这可能是一个简单的音乐会事件表,其中在很短的时间内发生门票购买和取消。事件发生 1 个月后,数据将不再更新。

最坏的情况是每个分区至少有一次更新。这将导致几乎整个表被重新加载,因为 PBI 增量刷新将重新加载整个分区,可能会重新加载整个表。一个用例可能是一个库存系统,其中项目在几个月或几年前被检入,并且每天被检出。

真实世界测试— Power BI 数据集

现在我们知道了 PBI 服务是如何增量刷新数据集的,那么它在部署时实际上也是这样吗?

作者图片

为了测试这一点,我建立了一个包含 500 行虚拟数据的 MySQL 数据库。创建日期和更新日期,范围从2021–08–152022–08–08

作者图片:增量刷新配置

PBI 增量刷新配置如上截图。

我在 MySQL 上启用了查询日志来记录运行的查询。

SET global general_log = 1;
SET global log_output = 'table';

然后,我将包含该数据集的数据集/Power BI 报告发布到高级工作区,并通过安装在我电脑上的 Power BI Gateway 刷新数据集。

测试用例 PBI01:刷新而不更新任何数据

对于第一个测试用例,我只是再次刷新了数据,而没有更新数据库中的任何数据。

图片由作者提供

select max(`rows`.`update_timestamp`) as `update_timestamp`
from 
(
    select `_`.`update_timestamp`
    from `test`.`inc_t` `_`
    where `_`.`create_timestamp` >= '2022-05-01 00:00:00' and `_`.`create_timestamp` < '2022-06-01 00:00:00'
) `rows`

从日志中,PBI 服务检查每个分区的最大更新,在我们的例子中:

  • 2022–03–01 > 2022–04–01
  • 2022–04–01 > 2022–05–01
  • 2022–05–01 > 2022–06–01
  • 2022–06–01 > 2022–07–01
  • 2022–07–01 > 2022–08–01
  • 2022–08–01 > 2022–09–01

在我们的例子中,因为没有更新数据,所以没有发生刷新,也没有运行额外的查询。第一个测试用例通过了。

测试用例 PBI02:在增量范围内更新行

在第二个测试案例中,我将在 MySQL 源数据库上从创建日期2022–06–01更新 2 行到2022–08–28(需要更新 2 行)。

UPDATE INC_t
SET UPDATE_TIMESTAMP = '2022-08-08 09:29:23'
WHERE DATE(CREATE_TIMESTAMP) = '2022-06-01';-- 2 row(s) affected Rows matched: 2  Changed: 2  Warnings: 0 0.015 sec

然后,当然,手动触发计划的刷新。

select `$Ordered`.`id`,
    `$Ordered`.`name`,
    `$Ordered`.`alphanumeric`,
    `$Ordered`.`phrase`,
    `$Ordered`.`country`,
    `$Ordered`.`update_timestamp`,
    `$Ordered`.`create_timestamp`
from 
(
    select `_`.`id`,
        `_`.`name`,
        `_`.`alphanumeric`,
        `_`.`phrase`,
        `_`.`country`,
        `_`.`update_timestamp`,
        `_`.`create_timestamp`
    from `test`.`inc_t` `_`
    where `_`.`create_timestamp` >= '2022-06-01 00:00:00' and `_`.`create_timestamp` < '2022-07-01 00:00:00'
) `$Ordered`
order by `$Ordered`.`id`

对每个分区运行最大更新的初始检查,然后如上所述运行新的查询。该查询将重新加载从2022–06–012022–07–01的所有数据。

这表明增量刷新符合文档,并且没有加载整个数据集。

真实世界测试— Power BI 数据集市(预览)服务

就在最*,微软推出了数据集市,你可以在这里阅读更多的信息。在数据集市内部,有一个增量刷新选项。为了进行测试,我通过设置的同一个 Power BI 网关将数据集市连接到同一个 MySQL 数据库。

这是在初始数据加载时运行的查询。如图所示,加载所有数据时没有任何 WHERE 子句。

作者图片

在装载了初始数据之后,就该在 datamart 中对该表设置增量刷新了。

作者图片

图片作者:增量刷新配置

配置设置类似于我在 Power BI Desktop 中做的第一个测试用例。然而,使用数据集市,不需要创建 RangeStart 和 RangeEnd 参数,因为它们是自动创建的。

当然,手动触发数据集市的计划刷新。

测试用例 D01:刷新而不更新任何数据

从运行的查询来看,它们是用于检查 MAX update 的 no 查询。相反,查询并加载了过去 6 个月的所有分区。

select `_`.`id`,
    `_`.`name`,
    `_`.`alphanumeric`,
    `_`.`phrase`,
    `_`.`country`,
    `_`.`update_timestamp`,
    `_`.`create_timestamp`
from 
(
    select `_`.`id`,
        `_`.`name`,
        `_`.`alphanumeric`,
        `_`.`phrase`,
        `_`.`country`,
        `_`.`update_timestamp`,
        `_`.`create_timestamp`
    from `test`.`inc_t` `_`
    where `_`.`create_timestamp` >= '2022-05-01 00:00:00' and `_`.`create_timestamp` < '2022-06-01 00:00:00'
) `_`
order by `_`.`update_timestamp` desc
limit 4096

这表明数据集市中刷新更改的数据的行为与 PBI 增量刷新中检测数据更改的行为不同。

为了进一步测试,我将更新我的配置以刷新最* 1 个月的数据,并跳转到第二个测试用例。

作者图片

和预期的一样,1 个月的数据被查询和刷新。

select `_`.`id`,
    `_`.`name`,
    `_`.`alphanumeric`,
    `_`.`phrase`,
    `_`.`country`,
    `_`.`update_timestamp`,
    `_`.`create_timestamp`
from 
(
    select `_`.`id`,
        `_`.`name`,
        `_`.`alphanumeric`,
        `_`.`phrase`,
        `_`.`country`,
        `_`.`update_timestamp`,
        `_`.`create_timestamp`
    from `test`.`inc_t` `_`
    where `_`.`create_timestamp` >= '2022-08-01 00:00:00' and `_`.`create_timestamp` < '2022-09-01 00:00:00'
) `_`
order by `_`.`update_timestamp` desc
limit 4096

测试案例 D02:更新 2022–06–05 的行

鉴于刷新更改的数据与 PBI 服务检测数据更改的行为不同,我将更新创建日期落在2022–06–05的行。

UPDATE INC_t
SET UPDATE_TIMESTAMP = '2022-08-28 23:00:00'
WHERE DATE(CREATE_TIMESTAMP) = '2022-06-05';
-- 4 row(s) affected Rows matched: 4  Changed: 4  Warnings: 0 0.000 sec

运行刷新后,只运行了 1 个查询,即只刷新本月的数据。

作者图片

检查 update timestamp 列,很明显最新的更新没有被刷新到数据集市中。因此,数据集市增量刷新的第二个测试用例被认为是失败的。

数据集市的问题

我用其他变量和数据源(如 Oracle DB)在数据集市上重复了这个测试,但是,结果总是相同的,刷新更改的数据从不考虑。所有增量范围分区的完全刷新总是在发生。

结论

增量刷新对于优化源数据库的数据加载至关重要,可以减少源数据库的开销,同时确保报告数据是最新的。随着 Microsoft datamart 的引入,在不久的将来,一旦超出预览范围,统一各种来源的数据将变得更加容易。希望这篇文章能澄清一些关于增量刷新如何工作的困惑。

Power BI 桌面增量刷新按预期工作,但不幸的是,datamart 增量刷新没有。由于数据集市仍在预览中,我确信微软将很快修复它,这只是数据集市可能暂时影响你的生产数据库的一个 PSA。一旦我再次测试并确认它被修复,或者如果我遗漏了什么,这篇文章也会被更新。我还将测试微软数据流,它应该很快就会被弃用。但是,如果它确实是到数据集市的更好的连接器,我将写另一篇关于设置的文章!

对问题状态的深入探究

原文:https://towardsdatascience.com/a-deep-dive-into-problem-states-498ad0746c98

为你的马尔可夫决策过程定义适当的问题状态。了解高阶 MDP 和信念状态变量,帮助您做出决策。

摄于 Unsplash

如果你曾经试图建立一个马尔可夫决策过程(MDP)模型,第一步可能是定义状态变量 s 。毕竟,状态是用来描述系统,决定可行的行动,计算奖励,管理时间 转换。没有国家,你做不了多少事。

鉴于国家在市场发展计划中的关键作用,令人惊讶的是教科书的定义往往相当含蓄。这篇文章将深入定义好的问题状态的属性,使你能够为你的模型和算法定义有目的的状态。

MDP 国家介绍

出发前:本文在很大程度上依赖沃伦·鲍威尔(普林斯顿大学名誉教授)的观点。为了更详细地评估这里讨论的主题,我建议看一看本文末尾列出的学术著作。

让我们考虑一些常见的定义(就提供的明确定义而言):

维基百科(未注明)——“状态变量是用来描述动力系统的数学‘状态’的一组变量之一”。

贝尔曼(1957)——“…我们有一个物理系统,在任何阶段都由一小组参数,即状态变量来表征。”

普特曼(2014)——“在每个决策时期,系统占据一个状态。”

萨顿&巴尔托(2018)——“…代表做出选择的基础的信号(状态)”

Bertsekas(2018)——“…是系统的状态,某个空间的元素。[……]控制理论中的许多经典问题涉及一种属于欧几里得空间的状态,即实变量的 n 维向量空间,其中 n 是某个正整数。”

在抽象的层面上,很明显,状态提供了系统在给定时间点的数字表示。此外,我们可以推断状态是与决策过程相联系的。然而,很明显,许多开创性的著作——尽管它们很有价值——并没有对“国家”的概念提供一个过于彻底的定义。

鲍威尔对国家的定义如下:

Powell(2022)——状态变量包含了我们所知道的一切,并且仅仅是我们需要知道的,以做出决定并对我们的问题进行建模。状态变量包括物理状态变量 R_t(无人机的位置、库存、股票投资)、关于我们完全了解的参数和数量的其他信息 I_t(如当前价格和天气),以及概率分布形式的信念 B_t,描述我们不完全了解的参数和数量(这可以是对药物将降低新患者血糖多少的估计,或者市场将如何对价格做出反应)。

他认为问题状态有三个目的:

  • 决定行动。因此,状态应该包含智能决策所需的所有信息。
  • 计算转移函数。结合所选动作和外部环境信息,状态定义应足以计算下一个状态。
  • 计算奖励函数。连同所选的动作,状态信息必须足以计算对应于状态-动作对的直接奖励。

一个正确定义的状态应该准确地包含上述目的所需的信息——不多也不少。更少的信息,国家不足以实现其目标。更多的信息,你只需跟踪多余的信息。

信息分类

确定了问题状态的目的后,下一个问题是包含什么信息

许多建模者限制自己只包括系统的物理属性:持有的现金数量、卡车的当前位置、商店中的存货……正如我们将很快看到的,这样的定义可能过于严格,忽略了考虑到问题状态的目的的关键元素。

Powell 认为状态变量可以分为三类:(i) 信念 , (ii) 信息,( iii)物理属性或资源。更准确地说,信念是无所不包的类,其他两个类是受约束的子类。为了便于讨论,可以将它们视为三个独立的类别。

  • 物理:系统可直接观察到的属性,如资源。“身体的”这个术语在这里可以被认为是有点不严格的。
  • 信息:非有形的确定性信息。可以直接观察到,但不一定是系统的物理组件。
  • 信念:非有形的概率知识。具体地,信念可以由分布的参数来表示。

将状态变量分类为信念(概率)、信息(确定性)和物理属性(资源)[图片由作者提供,改编自 Powell (2014)]

一些例子将很快出现,但首先我们需要讨论另一个属性——高阶 MDPs。

高阶 MDP

根据定义,任何 MDP 都满足马尔可夫性质,也就是无记忆性质。这个属性表明决策不依赖于过去的状态,而只依赖于当前的状态。如果问题可以用这种方式表述,我们就可以把极其复杂的决策问题分解成一系列更容易处理的子问题,独立解决。

很自然地将马尔可夫属性解释为‘不利用来自过去的任何信息’。然而,不使用来自过去的状态和来自过去的信息是有区别的。事实上,鲍威尔认为,过去的信息可以完美地包含在当前状态中。

从决策的角度来看,这是有道理的。假设销售额以每年 5%的速度稳步增长。仅有一个单一数据点(最*的销售)无法揭示这一趋势,从而无法采取预期行动。相比之下,合并该州过去的销售数据会显示出明显的上升趋势。

注意,我们不需要参考过去的状态——这确实会违反马尔可夫属性——而只需在当前状态中包含过去的信息。我们将记忆封装在状态中。

在数学术语中,包含历史信息的状态在高阶 MDP中使用。这种模型提供了比一阶 MDPs 允许的更丰富的系统表示(实际上只包括当前信息)。在极端情况下,一个状态甚至可以包含所有的历史信息。

TL;博士:你可以在你的问题状态中包含过去的信息,而且通常会帮助你做出更好的决定!

例子

杰里米·贝赞格在 Unsplash 上的照片

现在我们已经有了定义状态的所有必要因素,让我们提供一些简单的例子吧!

例 1:能源管理

  • 物理:当前电池电量
  • 信息:电价、风速
  • 信念:预测的能量需求分布,剩余电池寿命

示例 2 —仓库管理

  • 实物:库存实物
  • 信息:已预订(未发货)产品,已订购补货
  • 信念:每个产品的销售预测情景

示例 3——投资组合管理

  • 实物:现金持有量、每股持有量
  • 信息:过去和现在的股票价格
  • 信念:未来价格变动的概率模型

虽然根据物理、信息和信念变量进行分类不是必要的,但它有助于阐述做出正确决策所需的有用信息,从而提供对该问题的更广阔的视角。

结束语

国家是市场发展计划的关键要素。不幸的是,许多建模者对这个概念的看法过于狭隘,忽略了有价值的历史数据、无形的信息或概率信息。

以下几点可能有助于设计利用决策的更丰富的状态:

  • 一个状态不仅需要包含系统的物理上可观测的属性,还可以包含无形的信息。
  • 过去的信息可以并入状态,只要基于当前状态做出决策。换句话说,状态可以展示记忆。
  • 即使没有完美的信息(例如,概率知识),我们也可以将信念结合到我们的状态中来帮助行动选择。

对马尔可夫决策过程的其他模型组件也感兴趣吗?看看下面这篇文章:

参考

贝尔曼河(1957 年)。马尔可夫决策过程。数学与力学杂志,679–684。

贝特塞卡斯博士(2019)。强化学*和最优控制。雅典娜科技公司。

Salnikov,v .,Schaub,M. T .,& Lambiotte,R. (2016 年)。使用高阶马尔可夫模型揭示网络中基于流的社区。科学报告6 (1),1–13。

鲍威尔(2014 年)。清除随机优化的丛林。在桥接数据和决策(第 109–137 页)。告知。

鲍威尔,W. B .,&梅塞尔,S. (2015 年)。能源中的随机优化教程——第二部分:能量存储图解。 IEEE 电力系统汇刊31 (2),1468–1475。

鲍威尔 W. B. (2022)。强化学*和随机优化:顺序决策的统一框架。约翰·威利的儿子们。

鲍威尔(未注明日期)。建模。https://castlelab.princeton.edu/modeling/

普特曼博士(2014 年)。马尔可夫决策过程:离散随机动态规划。约翰·威利&的儿子们。

萨顿和巴尔托(2018 年)。强化学*:简介。麻省理工出版社。

维基百科(未注明)状态变量。https://en.wikipedia.org/wiki/State_variable

深入研究堆叠集成机器学*—第一部分

原文:https://towardsdatascience.com/a-deep-dive-into-stacking-ensemble-machine-learning-part-i-10476b2ade3

如何通过充分理解堆叠是什么以及如何工作,在机器学*中有效地使用堆叠

蒂姆·怀尔德史密斯Unsplash 拍摄的照片

背景

我在几本书里读到过关于堆叠的内容,也在 Kaggle 和其他网上搜索过堆叠的例子。

从阅读和研究中可以清楚地看到,堆叠有可能提高预测算法的准确性,改善 Kaggle 上的排行榜结果,以及现实世界中机器学*算法的准确性和影响。

主要的问题是我没有读到足够的解释什么是堆叠或者它是如何工作的。另一个问题是,文章、博客、书籍和文档在详细解释和实现堆叠时相互矛盾。

这让我想知道更多,以满足我自己的好奇心,也让我知道什么时候堆叠作为一种技术是合适的,什么时候应该避免。

由此开始了一段时间的阅读和研究,以及使用 Python 和 Jupyter 笔记本的实际实验。这些是我在调查中发现的一些最有用的来源-

概观

以下一组步骤是我从我研究过的关于堆叠的各种来源中找到的最接*一致意见的直观表示

第一步:将数据拆分成训练和测试/验证数据集

作者图片

第一步是直观的,复制机器学*中常见的第一步。训练数据将用于构建堆叠模型,测试/验证数据将被保留并用于评估性能。

在上图中,代表数据的矩形沿垂直轴一分为二。较大的部分代表特征,而末端较小的部分/列代表目标。

步骤 2a:训练 0 级模型

堆叠是一个两层模型。这张图直观地展示了第一阶段“0 级”的情况

作者图片

本质上,这里正在进行的是一个巧妙的特征工程。

特征工程的一个传统例子是通过将两个预先存在的列“速度”和“时间”相乘来创建一个名为“行驶距离”的新列。设计这种新特征可能会提供提高预测性能的相关信息。

在堆叠的 0 级阶段,在给定训练数据集原始特征的情况下,使用预测模型的集合来独立预测目标的值。然后,这些预测将作为新特征添加到训练数据中。

不同的消息来源对这一阶段的详细工作方式有矛盾。Abhishek Thakur 指出,训练数据应该折叠起来,以生成新功能的预测,而scikit-learn文档则相反

"注意估值器是装在全 X 上的"(https://sci kit-learn . org/stable/modules/generated/sk learn . ensemble . stacking classifier . html)

在实践中,我尝试了两种方法,采用 scikit-learn 方法显著提高了我所使用的数据集的预测性能。我也喜欢 scikit-learn 方法,因为它使阶段 4 更加直观。

步骤 2b:微调步骤 2a

要考虑的最后一个问题是,到底使用 0 级模型中的什么来转换训练数据,有几种选择。

第一个决定是完全替换现有特征,以便训练数据仅包含 0 级模型预测,还是将新特征附加到训练数据上。

上图显示了追加到数据中的预测,实际上,我发现保留原始要素可以显著提高完整叠加模型的性能。

第二个决定是在预测列中使用什么数据。

在回归中直接使用连续的预测值,但是在分类中有更多的选择

第一种选择是简单地使用预测的类。在上述每一列的二进制分类中(显示为橙色、蓝色和绿色),根据 0 级模型预测,每行将包含 1 或 0。

然而,事实证明,如果使用预测概率而不是预测,叠加模型的性能会显著提高。对于二元分类器,可以使用零类或一类的预测概率;它们完全共线,因此选择哪一个对结果没有影响。

总之,根据我的实验,如果机器学*算法正在解决一个二进制分类,那么微调如下-

  1. 保留原始要素并将预测作为附加要素追加。
  2. 使用 class=1 的预测概率作为数据,不要使用直接预测。

第三步:训练一级模型

现在,第 1 步和第 2 步已经添加了新功能,是时候训练“1 级模型”了,在某些来源中也被称为“最终评估者”

作者图片

与前面的步骤相比,这个阶段非常简单,我们现在可以做预测了。

水平 1 模型简单地适合于转换的训练数据,并且我们有我们训练的叠加模型。

第四步:对测试/验证数据进行预测

作者图片

好吧,这看起来有点吓人,但实际上很简单。

用作训练的“1 级模型”的输入的测试/验证数据必须具有与用于拟合它的训练数据相同的形状(就特征的数量和顺序而言),但是碰巧这很容易做到。

将训练/拟合的“0 级模型”顺序应用于测试/验证数据,以添加模型预测作为新特征,这样,训练和测试/验证数据的形状将匹配。

然后将经过训练的“1 级模型”应用于转换后的测试数据,以提供由叠加模型产生的最终一组预测。

结论

堆叠在概念上很难把握,至少那是我在花大量时间阅读、研究和实验之前的体验。

然而,一旦理解了堆叠,在实践中应用起来就相对简单了,这将是本系列文章的第二部分和第三部分的主题。

在本系列文章的第二部分中,我们将使用scikit-learn库实现一个堆栈模型,以提高我们的理解并评估整体性能

第三部分将从零开始完整地构建一个堆叠算法,以完成对堆叠及其详细工作原理的全面而深刻的理解。

一锤定音

我最初当然很难完全理解堆栈,直到我研究了scikit-learn实现,然后从头开始构建我自己的堆栈算法,我才获得了很好的理解。

希望这篇文章以及第二和第三部分将帮助其他人实现这种理解,而不必进行所有的研究,这将使人们能够就在哪里以及如何实现堆叠做出明智的选择,以实现预测机器学*算法的优化性能。

感谢您的阅读!

如果你喜欢读这篇文章,为什么不看看我在https://grahamharrison-86487.medium.com/的其他文章呢?此外,我很乐意听到您对这篇文章、我的任何其他文章或任何与数据科学和数据分析相关的内容的看法。

如果你想联系我讨论这些话题,请在 LinkedIn 上找我—https://www.linkedin.com/in/grahamharrison1或者发邮件给我ghar rison @ Lincoln college . AC . uk

如果你想通过订阅来支持作者和全世界 1000 个为文章写作做出贡献的人,请使用下面的链接(注意:如果你使用这个链接免费注册,作者将收到一定比例的费用)。

https://grahamharrison-86487.medium.com/membership

深入研究堆叠集成机器学*—第二部分

原文:https://towardsdatascience.com/a-deep-dive-into-stacking-ensemble-machine-learning-part-ii-69bfc0d6e53d

如何通过在 Python、Jupyter 和 Scikit-Learn 中实现堆叠分类器,在机器学*中有效地使用堆叠

蒂姆·怀尔德史密斯Unsplash 拍摄的照片

背景

在我最*的一篇关于堆叠的文章中,我通过建立一个可视化的工作流程,探索了什么是堆叠以及它是如何工作的,这个工作流程包含了创建堆叠模型的 4 个主要步骤。

然而,要真正理解堆叠模型内部发生了什么(从而理解何时以及如何在机器学*中采用这种方法),通过编写一些代码来了解算法是必不可少的。

本文使用scikit-learn库构建了一个堆栈模型,然后本系列的第三部分将通过从头开始开发代码来实现堆栈,从而进入最后的细节层次。

入门指南

要开始,需要导入所需的库,并创建一些将在代码主体中使用的常数…

获取一些数据

我们还需要一些数据来处理,为此,我们将使用一个助手函数来轻松地将数据创建为带有标签列的单个数据帧。

请注意,堆叠可以应用于回归和,也可以应用于二元和非二元分类。创建此数据集是为了构建一个对二元分类进行建模的示例,即目标可以正好有两个值…

作者图片

数据被分割如下-

  • X _ train 训练数据集特征
  • y _ train 训练数据集标签/类
  • X _ val 验证数据集要素
  • y _ val 验证数据集标签/类

X_train,y_train 数据集将用于构建叠加模型,而 X_val,y_val 将被保留并仅用于模型评估。

准备堆叠

本系列文章的第一部分展示了堆叠是一个两级模型,其中“0 级”用于生成成为数据中新要素的分类标签,而“1 级”模型用于生成最终标签预测。

以下代码对此进行了设置…

Level 0 已经实现为分类机器学*算法的字典。选择了四种不同类型的模型——逻辑回归、随机森林、XG boost 和额外随机树。

后 3 个都是高性能分类器,逻辑回归提供了一些变化。我们特意选择了高性能算法,以观察叠加模型是否能成功地超越它们。

为“1 级”算法(或“最终估计值”)选择了一个随机森林。实验表明,它是该数据集上性能最高的单个算法,因此被选为 1 级模型,以提高堆叠模型的性能。

使用 Scikit-Learn 创建堆叠模型

scikit-learn提供易于使用的堆栈实现;我们将详细研究输出和数据,以准确理解它在做什么。

因为scikit-learn在 1 级模型/最终估计器中使用折叠来生成最终预测,所以StratifiedKFold作为参数传递

"请注意,估计量是在全 X 上拟合的,而 final_estimator_ 是使用 cross_val_predict 使用基本估计量的交叉验证预测来训练的。"(https://sci kit-learn . org/stable/modules/generated/sk learn . ensemble . stacking classifier . html)

本文的第一部分详细探讨和解释了这种方法。

生成 0 级预测作为工程特征

作者图片

堆叠的下一阶段是使用 0 级模型生成分类预测,并将它们作为特征附加到训练数据。

下面是在scikit-learn实现中是如何做到的...

作者图片

从输出中可以看出,scikit-learn StackingClassifier.fit_transform()方法将特性附加到了passthrough=True处的预测中。如果passthrough被设置为False,输出将只包含预测。

那么 Scikit-Learn 是怎么做到的呢?

一点点实验显示了图书馆到底在做什么。logreg_predictionforest_predictionxgboost_predictionxtrees_prediction栏中的值生成如下-

作者图片

这就是它的全部!scikit-learn堆叠分类器只是简单地按顺序训练每个 0 级分类器,然后使用每个分类器的第二列predict_proba函数来填充新特征

请注意,predict_proba的第二列(使用[:, 1]切片)只是 class=1 的概率(在二进制分类的情况下,可能值为 0 和 1)。

还要注意的是,如果StackingClassifier设置 passthrough=True,分类预测将被附加到数据中,而如果 passthrough=False,则原始特征将被移除,只留下新生成的特征/分类预测。

我的实验表明,通过将原始要素保留在数据集中,下一个代码块将重新训练堆叠分类器以重新训练所有原始要素,性能会得到显著提高…

为测试数据生成 1 级/最终预测

作者图片

这个下一阶段看起来很复杂,但是在scikit-learn.中执行这个操作非常容易

这第一行代码并不是真正必要的,我们可以直接跳到stacking_model.predict(X_val),但是我在上面的图表中加入了它来显示转换后的数据,用灰色的测试数据矩形表示,橙色、蓝色和绿色的预测作为新的特性附加在上面。

作者图片

这行代码当然是必要的;它对包含 0 级预测作为特征的转换后的测试数据执行 1 级模型预测。

y_val_pred在上图中表示为最终的紫色矩形。

array([0, 1, 0, ..., 0, 0, 0])

那么这一切值得吗?

现在是时候通过比较完整的 0 级和 1 级堆叠模型的准确性与我们选择其中一个 0 级模型来预测y_val时看到的性能来评估性能了…

Accuracy of scikit-learn stacking classifier: 0.8825
Accuracy of standalone logreg classifier: 0.737
Accuracy of standalone forest classifier: 0.8675
Accuracy of standalone xgboost classifier: 0.8645
Accuracy of standalone xtrees classifier: 0.8635

全堆积模型的准确率为 88.25%,而性能最高的独立分类器的准确率为 86.75%,提高了 1.5%。

1.5%的精度提升可能看起来并不巨大,但请考虑以下情况-

  1. 在 Kaggle 竞争中,1.5%的改进可能代表在排行榜上的位置发生重大变化。
  2. 如果正在解决的业务问题在操作上至关重要(股票市场预测、预测岩石与矿山等)。)那么百分之几都算数。
  3. 其他压缩改进选项,如超参数调整,可能会实现较小的改进。
  4. 每个 0 级模型和 1 级模型/最终估计值可以单独进行超参数调整,这在与叠加结合时可以产生更多的改进。

我们能做得更好吗?

嗯,我想我们可以。在我的调查过程中,我遇到了一位数据科学家编写的一些非常聪明的代码,这些代码执行了大量计算,以选择 0 级和 1 级分类器的最佳组合。

我真的很喜欢这个想法,但我不禁认为它过于复杂,必须有一种方法来用更少的代码实现相同的结果,这导致了一个使用网格搜索来找到最佳组合的实验。

我们需要的第一件事是一个小函数来帮助生成将在网格搜索中使用的各种参数排列-

这个想法是使用power_set函数生成所有有用的 0 级模型组合,用作网格搜索参数...

[['logreg', 'forest'],
 ['logreg', 'xgboost'],
 ['logreg', 'xtrees'],
 ['forest', 'xgboost'],
 ['forest', 'xtrees'],
 ['xgboost', 'xtrees'],
 ['logreg', 'forest', 'xgboost'],
 ['logreg', 'forest', 'xtrees'],
 ['logreg', 'xgboost', 'xtrees'],
 ['forest', 'xgboost', 'xtrees'],
 ['logreg', 'forest', 'xgboost', 'xtrees']]

级别 1 由单个模型组成,因此网格搜索参数将是单个对象的简单列表…

['logreg', 'forest', 'xgboost', 'xtrees']

有了这些构件,在定义了网格搜索参数后,只需 3 行代码就可以实现堆叠模型的整体优化。

请注意,PredefinedSplit强制网格搜索以与独立叠加模型相同的方式使用训练和测试数据,以便结果可以直接比较…

作者图片

Best accuracy score:  0.8895
Best estimators:  ['logreg', 'forest', 'xgboost', 'xtrees']
Best final estimator:  RandomForestClassifier(random_state=42)
Best passthrough:  True
Best stack method:  predict_proba

结果出来了!

最佳配置是包括所有 4 个分类器作为第 0 级模型,但是如果我们使用一个RandomForestClassifer而不是一个ExtraTreesClassifier作为第 1 级模型,我们可以获得更高的性能。这种组合实现了 88.95%的准确率,比最高性能的单个算法高出整整 2.2%。

非常有趣的是,0 级模型的最佳选择包括LogisticRegression分类器,其性能(73.7%的准确率)比其他分类器低得多。这只是表明,包括较低表现者在内的各种模型可以推动整体表现的提高。

结论

总之,当涉及到提高分类和回归机器学*算法的准确性时,堆叠确实有效。

这不太可能在 5%-10%的范围内产生准确性改进,但根据所解决的业务或竞争问题的背景,这种改进仍然可能是显著的。

使用堆垛时应适当考虑;这将花费更多的时间,并且会增加复杂性。如果额外的复杂性和解释算法如何计算其答案的相关难度是准确度提高< 5%的良好折衷,那么使用堆叠是值得的。

本文提供了一个使用scikit-learn堆栈实现的简单、低代码的工作示例,并详细解释了它的工作原理,这可以帮助数据科学家决定在他们的解决方案中何时使用堆栈,何时不使用堆栈。

感谢您的阅读!

如果你喜欢读这篇文章,为什么不看看我在 https://grahamharrison-86487.medium.com/的其他文章呢?此外,我很乐意听到您对这篇文章、我的任何其他文章或任何与数据科学和数据分析相关的内容的看法。

如果你想联系我讨论这些话题,请在 LinkedIn 上找我—【https://www.linkedin.com/in/grahamharrison1 或者发电子邮件到ghar rison @ Lincoln college . AC . uk

如果你想通过订阅来支持作者和全世界 1000 个为文章写作做出贡献的人,请使用下面的链接(注意:如果你使用这个链接免费注册,作者将收到一定比例的费用)。

https://grahamharrison-86487.medium.com/membership

深入探讨堆叠集成机器学*—第三部分

原文:https://towardsdatascience.com/a-deep-dive-into-stacking-ensemble-machine-learning-part-iii-1ebd6bedc442

如何通过在 Python 和 Jupyter 中实现一个从零开始的堆叠模型来充分理解堆叠并在机器学*中有效地使用它

蒂姆·怀尔德史密斯在 Unsplash 上的照片

背景

在我最*的两篇关于使用堆栈实现集成机器学*算法的文章中,我探讨了堆栈是如何工作的,以及如何在scikit-learn中构建和理解堆栈算法。

本系列的最后一篇文章将通过从头构建一个堆叠算法,并使用流水线来链接 0 级和 1 级模型,进一步加深对这个问题的理解。

入门指南

与本系列的其他文章一样,我们需要导入一组将在代码中使用的库,并创建一些常量…

获取一些数据

我们还将坚持使用其他文章中使用过的相同数据集…

作者图片

第二部分的快速提醒

如果你想知道所有的细节,请回头看看第二部分(或者在进行任何编码之前,回头看看第一部分的原则指南)。

总之,scikit-learn在第二部分中被用于实现堆叠部分,其中 0 级和 1 级模型被组合以产生一个分类器,该分类器通过添加分类预测作为工程特征来提高性能

作者图片

作者图片

下一步是使用经过训练的堆积模型来生成一组预测…

作者图片

array([0, 1, 0, ..., 0, 0, 0])

从头开始构建堆叠分类器

临时构建的分类器的设计如下-

  1. 使用Transformer模式构建一组 0 级模型。
  2. 将级别 1 模型实现为一个简单的分类器。
  3. 使用管道将两者连接在一起。

1.使用 Python 中的面向对象技术构建 0 级和 1 级模型

使用Transformer模式构建 0 级模型,使用Estimator模式构建 1 级模型,如下所示-

2.使用管道训练堆叠模型

作者图片

首先要注意的是,临时构建堆栈模型在训练阶段产生与scikit-learn相同的输出,这是对实现的良好验证。

第二件要注意的事情是,icecream调试输出告诉我们管道是如何工作的(注意:我可以使用print语句,但是当代码被移入外部库时icecream仍然工作,而print只在本地工作)

  • 管道必须由一个或多个Transformer对象组成,后跟一个Estimator对象。
  • 在任何事情发生之前,对每个对象调用init方法。
  • 当在管道上调用fit()方法 id 时...
  • 对于每个Transformer,调用fit()方法,然后调用transform()方法。这是调用训练数据的正确方法顺序。
  • 对于管道中的最后一个对象(Estimator),只调用了fit方法。

我构建Level1Stacker类而不是直接添加Estimator的原因是,我可以向icecream添加调试语句,以准确演示正在调用的方法。

Level0Stacker类不是很复杂。fit()方法只是将 0 级模型中的每个分类器与整个训练数据相匹配。我已经查看了一些代码样本,其中使用了出折叠预测,但是scikit-learn在整个 X 上训练,所以对我来说已经足够好了。

也值得考虑一下copy_datahelper 函数的情况。我发现,当DataFrame对象通过管道传递时,管道会崩溃,除非每一步都严格地处理已经重置了索引的深层副本。

transform()方法简单地在 0 级模型周围迭代,根据stack_method参数的设置调用predictpredict_proba,然后将预测作为新特征添加到数据中。

非常重要的是,用fit()方法拟合 0 级模型,然后用transform()方法进行预测,我们将在下面看到...

3.根据测试数据进行预测

作者图片

array([1, 1, 0, ..., 0, 0, 0])

再次,icecream调试使我们能够看到到底发生了什么。调用管道上的predict()方法依次调用每个Transformertransform()方法,然后调用Estimator对象的predict()方法。

有效果吗?

嗯,我们已经成功地从零开始建立了一个堆叠模型。训练模型并生成作为新数据特征的 0 级预测的步骤肯定有效,因为临时构建的模型的输出与scikit-learn相同。

然而,最终的预测是不一样的…

Accuracy of scikit-learn stacking classifier: 0.8825
Accuracy of scratch built stacking classifier: 0.8735

scikit-learn StackingClassifier的文档说明-

"estimators_在全 X 上拟合,而final_estimator_通过使用cross_val_predict的基础估计器的交叉验证预测进行训练"

然而,当我试图在临时构建堆栈器中复制时,精确度远低于scikit-learn堆栈模型或在最后一步没有交叉折叠验证的临时构建模型

作者图片

Accuracy of scratch built stacking classifier using level 1 cross-validation: 0.8195

这有点令人不满意,但我永远也不会知道scikit-learn堆叠模型的 1 级部分是如何实现的,除非我能看到代码或与其中一名开发人员交谈,而不是能够做到这一点,我对调查和研究实现了其目标感到满意。

结论

这组文章的第一部分旨在提供一个简单易懂的解释,解释什么是堆叠以及它是如何工作的。第二部分通过提供一个使用scikit-learn库的完整工作示例和更详细的解释对此进行了扩展。第三部分通过使用 Python 面向对象和TransformerEstimator编码模式从头构建一个完整的堆栈模型来完成探索。

从零开始构建一个堆叠模型已经证明是一个练*,它完善了我对堆叠如何工作的理解。我能够精确地复制scikit-learn模型的训练阶段,但是我不能完全复制库为最终 1 级预测工作的方式。尽管如此,这项研究让我了解了如何使用堆叠,何时使用以及何时不使用。

将来,只要我认为堆叠在一起的多个模型的复杂性增加以及解释最终模型如何达到其预测的相关困难被驱动更高准确性和改进性能的更大需求所抵消,我就会使用堆叠的scikit-learn实现。

我希望这一系列文章能够帮助其他数据科学家充分理解这种有效而迷人的技术,并消除一些关于堆叠内部究竟发生了什么以及如何工作的神秘。

感谢您的阅读!

如果你喜欢读这篇文章,为什么不去 https://grahamharrison-86487.medium.com/的看看我的其他文章呢?此外,我很乐意听到您对这篇文章、我的任何其他文章或任何与数据科学和数据分析相关的内容的看法。

如果你想联系我讨论这些话题,请在 LinkedIn 上找我—【https://www.linkedin.com/in/grahamharrison1 或者发电子邮件给我ghar rison @ Lincoln college . AC . uk

如果你想通过订阅来支持作者和全世界 1000 个为文章写作做出贡献的人,请使用下面的链接(注意:如果你使用这个链接免费注册,作者将收到一定比例的费用)。

https://grahamharrison-86487.medium.com/membership

深入探究经典线性回归模型的方差-协方差矩阵

原文:https://towardsdatascience.com/a-deep-dive-into-the-variance-covariance-matrices-of-classical-linear-regression-models-4322b2cdc8e6

拟合系数的方差-协方差矩阵(图片由作者提供)

关于如何构造方差-协方差矩阵以及何时对报告的数据持怀疑态度的教程

我写这篇文章是基于一个主题,我希望对于那些有数学倾向的人,以及那些想知道什么是标准误差或者它们来自哪里的人来说,阅读这篇文章是一种享受。

作为概念的快速复*:方差是随机变量围绕其均值(也称为其期望值)的“扩散”或变化的度量,而协方差度量两个随机变量之间的相关性。

标准误差就是误差的标准偏差,其中“误差”有不同的含义,这取决于它是用于回归变量系数的上下文中,还是用于模型整体预测的上下文中。我们很快就会谈到这一点。

方差-协方差矩阵是一个方阵,即它具有相同数量的行和列。沿着矩阵主对角线的元素,即从左上到右下的元素,包含方差,而所有其他元素包含协方差。因此,回归模型的拟合系数的方差-协方差矩阵包含拟合模型的系数估计沿其主对角线的方差,并且包含非对角线元素中的系数估计之间的成对协方差。

类似地,回归模型的误差项的方差-协方差矩阵包含每个误差项沿其主对角线的方差以及其他地方所有误差项对之间的协方差。

在我之前的文章中,我们直观地感受到了为什么拟合模型的系数或误差项表现出方差,以及这些矩阵在回归建模中扮演的角色。

在本文中,我们将使用 C 经典 L 线性 R 回归 M 模型(CLSR)作为工具,展示系数估计的方差-协方差矩阵和误差的方差-协方差矩阵的公式是如何从 CLSR 模型及其解的基本假设中自然出现的。尽管我介绍的材料是针对线性模型的,但总体概念方法贯穿于许多其他普遍使用的回归模型。

本文是关于方差-协方差矩阵的两个系列文章的第 2 部分:

第 1 部分:回归分析中使用的方差-协方差矩阵图解指南
第 2 部分:深入探究经典线性回归模型的方差-协方差矩阵

我们将在本文中遇到一些非常简单的线性代数(阅读:矩阵和矩阵上的运算)。

首先,我们将快速(重新)熟悉一些基本的线性代数概念。

线性代数简明入门

矩阵乘法:如果 A 的列数等于 B 的行数,则两个矩阵 AB 可以相乘。得到的矩阵的维数是 A 的行数乘以 B 的列数。下图说明了该产品的操作。为了简单起见,我们去掉了矩阵的列下标。

Xβ 的矩阵乘法(图片由作者提供)

矩阵的转置:矩阵的转置产生于矩阵的行和列的互换。就好像矩阵被翻转过来了。这里是回归矩阵 X 和它的转置X’(读作 X 质数):

回归矩阵 X 的转置 X’(图片由作者提供)

X 'X : 大小为 (k x n)X' 与大小为 (n x k)相乘得到大小为 (k x 正如我们将很快看到的,矩阵X’X在回归分析中随处可见。**

让我们重温一下我在上一篇关于方差-协方差矩阵的文章中看到的汽车数据集:

汽车数据集的子集(来源:加州大学欧文分校)

我们将创建一个回归矩阵 X ,包含以下 3 个回归变量:

城市 _MPG
发动机 _ 尺寸
整备质量

回归矩阵 X (图片作者提供)

下面是X的样子:

回归矩阵 X 的转置(图片来自作者)

这是我们汽车数据集中 205 号样本的 X'X 矩阵。可以看到,X'X是一个(3×3)矩阵:

X'X 用于自动数据集中大小为 20 的样本(图片由作者提供)

以下是一些与转置操作相关的有用恒等式:

  • 两个矩阵乘积的转置是单个矩阵按逆序:
    (AB)' =
    B'A**'**
  • 转置矩阵的转置返回原始矩阵:(X')' =X******

单位矩阵:单位矩阵沿主对角线(从左上到右下)包含 1,在所有其他元素中包含 0。通常表示为I_ n(I下标 n 或简称为 I ,是一个维数为(n×n)的方阵。单位矩阵在概念上是数字 1 的多维版本。I_ n具有以下属性:

  • I_ n的任意次方等于I_ n .例如对于I=I**
  • 任意矩阵的 Z 的大小(m×n)乘以I_ n返回Z . =**
  • 的逆 I _nI _n 。这在概念上类似于知道 1 除以 1 等于 1。
  • 任何大小为(n×n)的可逆方阵 Z 乘以其逆矩阵的乘积I_ n。最后一个性质将我们带到下一个概念——矩阵的逆。**

矩阵的逆:矩阵的逆在概念上是标量数的逆的多维等价形式 N. 矩阵的逆是用一个有点复杂的公式计算的,我们现在跳过这个公式。在回归分析中,关于矩阵的逆矩阵,需要记住以下几点:

  • X 的倒数用上标-1 表示为x【^(-1】X**
  • 只有“可逆”矩阵才有逆。在标量世界中,零没有有意义的倒数。1/0 = ??。矩阵也是同样的概念。
  • 方阵的逆矩阵x^(-1)乘以 X 等于I_ n。即x【^(-1】x=I .正如在标量世界里, 1/n 乘以 n 就是 1**

现在让我们将这些概念应用于手头的主题,即线性回归模型的协方差矩阵的推导。

回归模型的方差-协方差矩阵的公式

我们将从线性回归模型的方程开始,如下所示:

线性回归模型(图片来自作者)

上式中, y 为因变量, X 为回归变量的矩阵, β 为回归系数的向量 β_1,β_2,β_3,…,β_k 包含各系数的种群水平值(包括截距 β_1 ) y 的观测值与 y 的建模值之差。误差项反映了所有未建模或无法建模的影响。**

我们假设 X 的第一列是为回归的截距 β_1 保留位置的 1 的列。

这是矩阵形式:

线性回归模型的矩阵形式(图片由作者提供)

在大小为 n 的样本上求解(也称为“拟合”)该模型,产生了**βββ的估计值,我们将其表示为β_ cap。 因此,拟合后的模型看起来是这样的:****

拟合线性模型(图片由作者提供)

上式中, e残差(又名残差)的向量。残差 ey 的观测值与拟合模型预测的值 y_cap 之差。

如果使用最小平方最小化技术拟合模型,即所谓的OrdinaryLeastSquares(OLS ),我们寻求最小化以下残差平方和,也称为RESI dualSum ofSquares(RSS):

残差平方和(图片由作者提供)

上式中,X*_ I是回归矩阵 X 的第行。*****

现在,我们将推导出一个基于矩阵的公式,用于表示 S 方的 R esidual S um。

让我们重新检查等式(1)中的误差项 、ϵ 的向量。对于大小为 n的样本,ϵ 是大小为(n×1)的列向量:**

回归误差向量(图片由作者提供)

每个 ϵ_i 都是一个随机变量,它有一个取决于的特定均值(也称为期望),以及一个取决于的围绕该均值的特定方差。****

ϵ 的条件期望的列向量表示为e(ϵ|x):

回归误差的条件期望(图片由作者提供)

从*****中减去e(*****|x)【ϵ得出以下结果:**

平均居中误差项(图片由作者提供)

转置这个以平均值为中心的列向量会产生下面的行向量:

平均居中误差项的转置(图片由作者提供)

接下来,让我们乘(—e(【ϵ】|x))‘同(—e(|x 注意,(【ϵ】—e(【ϵ】|x)'是大小为(1x n)【ϵ—e()遂积(【ϵ】—e(【ϵ】|x)’(**—e()下图举例说明了(**—e(ϵ|x))'与(【ϵ—e(****************

求解程序为(—e(ϵ|x)’(—e(****

上述过程的第三步是我们取第二步中所示的两个矩阵的乘积。

这里我们得到一个重要的洞察:最后一步的求和是( n-k) 乘以随机变量 ϵ 的条件方差。由此,我们得出了以下重要结果:

回归误差的条件方差(图片由作者提供)

现在,线性回归中的一个主要假设如下:

对于回归变量向量 x _i 的任何给定值,误差项的平均值与 x _i 不相关

这种假设被称为回归模型的均值独立性 属性。它暗示以 X 为条件,的均值为零。均值独立意味着在上述方差公式中,术语e(【ϵ|x】)0 其中 0 是一个大小为【n×1】的列矩阵,只包含零因此,上述 ϵ 的误差方差公式简化为:**

误差项的条件方差(图片由作者提供)

让我们重新看看等式(4)中所示的推导。在第一步中,我们将通过用0【ϵ|x**】替换来应用我们的均值独立性假设。同样,在最后一步中,我们将e(ϵ_i| x)0 代替求和中的所有 i 。做这两个替换得到了另一个重要的回归模型误差项平方和的**公式:****

回归误差平方和的公式(图片由作者提供)

注意,在这些推导中,我们没有假设模型是线性的。到目前为止,我们只做了一个假设,即意味着独立

我们现在要做第二个假设:

拟合模型的残差 e 是误差项 ϵ 的无偏估计量。

这个假设允许我们使用残差平方和估计误差平方和。换句话说:

e'e 作为回归误差平方和的无偏估计量(图片由作者提供)

让我们再次回顾一下线性回归模型。为了使用OdLS 平方(OLS)估计技术在大小为 n 的样本上拟合模型,我们需要最小化等式(3)给出的残差平方和。我们还表明,等式(3)的 L.H.S .上的求和可以使用残差转置乘以自身的矩阵乘积来估计,即 e'e。

我们还知道拟合模型的方程可以写成:y=xβ_ cap+e,或者,就 e 而言,e =(y———* 因此,我们需要最小化以下内容,以便使用最小二乘法拟合线性模型:*

(图片由作者提供)

为此,我们将采用上述方程的偏导数,并将其设置为零矩阵 0 ,如下所示:

残差平方和的偏导数 w . r . t .β_ cap**(图片由作者提供)**

由于 β_cap 是一个大小为【k×1】的向量,上述矩阵方程分解为 k 变量中的 k 个方程,很容易求解得到β_ cap=【β_ 1 _ cap,β_ 2 _ cap…β_ k _ 1 我们实际上不会求解这个 k 方程系统。相反,我们将直接陈述最终结果,即最小化残差平方和的 β_cap 的值:**

【图片由作者提供】的最小二乘估计量

上述简洁的封闭形式的方程在单线中给出了使用 OLS 技术拟合的线性回归模型的最小二乘估计量。

让我们回忆一下,我们的目标是得出回归系数 β 的方差-协方差矩阵的公式。为此,我们将遵循格林(2018)建议的方法,并将等式(9)中的 y 替换为+ϵ,如下所示:

+ϵ(图片由作者提供)

让我们在右侧蓝色括号内分配术语:

分发术语(图片由作者提供)

让我们将这两个术语重新着色如下:

(图片由作者提供)

上式第一项中的灰色位是一个矩阵与其自身的乘积,它等于单位矩阵 I 。而我们知道=β。我们将用占位符矩阵 A 替换第二项中的品红色位。

(图片由作者提供)

让我们记住这个结果。

让我们回忆一下 β 的最小二乘估计的方差,即 β_cap 就是我们所追求的。

在本文的前面部分,我们已经看到 β_cap 是一个随机变量。任何随机变量 Z 的方差都可以表示为其均值减去后的值的平方的期望值。

由此,Var(Z)= E[(Z——Z _ bar**)]。**

我们知道 β_cap 的均值就是系数 β的总体水平值。 由于β_cap(k×1)*列向量,我们可以通过取 (β — β_cap) 与其转置 (β和β_ cap)’相乘的期望来简洁地表达方差公式*

β_cap (图片由作者提供)

矩阵(β-β_ cap)的大小为 (k x 1) ,它的转置矩阵(β-β_ cap)’的大小为 (1 x k) ,因此它们的乘积是一个大小为 (k x k) 的方阵,这正是我们想要的协方差矩阵。

在上面的方差方程中,让我们用(【β】+【aϵ)替换 β_cap ,并将结果表达式简化如下:**

(图片由作者提供)

让我们检查最后一行。中心术语e()| x*值得做一些解释。*****

e(【ϵϵ'】)| x的条件期望ϵϵ' 给定 X 。我们知道是一个大小为(n×1)的列向量,因此它的转置是一个大小为(1×n)的行向量。因此,他们的乘积 ϵϵ' 是一个大小为(n×n)的方阵,如下:

模型误差项的条件方差-协方差矩阵(图片由作者提供)

由于假设误差项的均值为零,包含e(ϵ_iϵ_i)| x的对角元素实际上是误差项 ϵ_it81】的 |X 的条件方差非对角元素***

我们现在将做出两个重要的假设:

同方误差

线性回归模型的误差项是同伦齐次的,即它们具有恒定的条件方差。于是,E(ϵ_iϵ_j) |X ]是某个常数 σ 当 i = j 时*

这个假设使得沿着误差的方差-协方差矩阵的主对角线的所有期望项具有某个恒定值 σ

非相关误差

线性回归模型的误差项彼此不相关。因此,当 I 不等于 j 时,E(ϵ_iϵ_j) |X ] = 0*

这种假设使得不沿着误差方差-协方差矩阵的主对角线的所有期望项的值为零。

根据这两个假设,误差的方差-协方差矩阵如下:

同方差和不相关假设下回归模型误差项的方差-协方差矩阵(图片由作者提供)

我们现在将等式(10)中的e()| x替换为*I,以获得最小二乘估计量的方差的以下等式:*****

(图片由作者提供)

为了简化上面的等式,我们将使用标识(AB)' =B'A'来简化 R.H.S .上的灰色位。我们将使用标识a【^(-1】a=Iia=ai=**

OLS 估计量的方差-协方差矩阵的推导(图片由作者提供)

等式(11)是统计建模领域的基本结果之一。

正如我们所看到的,拟合回归系数的方差-协方差矩阵是数据样本中所有回归变量的值和假设为常数的回归误差项的方差的函数。

在等式(11)中,虽然我们知道 X ,但是总体水平方差 σ 不是一个已知量。然而,样本方差 s ,即拟合模型的残差的方差,形成了 σ的无偏估计。根据我们之前计算残差方差的工作,特别是等式 (5)(7) ,我们有:****

残差的方差公式(图片由作者提供)

其中,通常, e'e 是拟合模型的残差向量与其自身的转置的乘积, n 是样本大小, k 是包括截距在内的回归变量的数量。你可能想验证一下在 R.H.S .上的方程是否产生一个标量。残差向量 e 是大小为(n×1)e'的大小为(1×n),因此,e'e的大小为 (1)****

因此,我们得到了拟合的 OLS 回归模型的系数的方差-协方差矩阵的以下无偏估计值:

对拟合的 OLS 回归模型系数的方差-协方差矩阵的估计(图片由作者提供)

如果模型有 k 个包含截距的回归变量,那么方差-协方差矩阵就是大小为(k×k)的方阵。这可以通过注意到×T43【是大小为(n×k)的矩阵来确认,因此×的大小为(k×n),因此乘积×××T55’×是大小为(k×k)的方阵 s 作为该矩阵的比例因子。************

与任何方差-协方差矩阵一样,位于该矩阵主对角线上的元素包含中的 k 回归变量在中各自总体均值周围的方差,而非对角线元素包含β_ cap***_ I*****

回归系数估计的置信区间

方差-协方差矩阵的每个对角元素的平方根是标准偏差,即 β_cap 中各个回归系数估计值的标准误差**

这些标准误差可以直接插入回归系数估计值周围的(1-α)100%置信区间的公式中,如下所示:

回归系数置信区间公式(图片由作者提供)

上式中, β_cap_i 为拟合模型的第与第回归系数的估计值(亦称平均值)。方括号内的 t 值是从具有 (n-k) 自由度的双边学生 t* 分布返回的临界值,其中 n 是样本大小, k 是包括截距在内的回归系数的数量。 se_i_i 是等式(12)中 β_cap 的方差-协方差矩阵中第对角元素的平方根。***

回归系数方差-协方差矩阵公式的基本假设

等式(12)中呈现的方差-协方差矩阵的公式来自我们在推导过程中逐步做出的以下 5 组假设:

  1. 误差的均值独立:模型的误差项是均值独立的:e(ϵ|x)= 0
  2. 线性形式:回归模型有一个线性形式:y =+ϵ******
  3. 拟合模型的残差 e 是误差项 ϵ 的无偏估计。
  4. *同方误差:模型的误差是同方误差:e(ϵ_iϵ_j|x)=σ(一个常数)当 i = j,并且,
  5. *不相关误差:模型的误差彼此不相关:e(ϵ_iϵ_j|x)= 0i!= j

何时对方差-协方差矩阵持怀疑态度

如果违反了上述一组假设,则前面的一组推导不再成立,并且等式(12)不能用于计算模型的方差-协方差矩阵。如果我们选择仍然使用等式(12)来计算方差-协方差矩阵,则矩阵中的方差和协方差值将是不准确的。因此,从矩阵计算的系数的标准误差将是不正确的,并且我们将最终报告错误的回归系数置信区间**

对于给定的数据集,前三个假设(均值独立性、线性形式和残差作为误差项的无偏估计)决定了线性回归模型的可行性。假设(4)和(5)仅描述了模型误差方差的行为。虽然,假设(1)到(5)一起导致线性 OLS 回归模型的方差-协方差矩阵的公式,如果(1)到(3)被满足,但是(4)和/或(5)不被满足,线性回归模型仍然是手边数据集的可行模型。但是在这种情况下,不应该报告使用等式(12)的系数估计的标准误差和置信区间。

当您使用统计建模软件包(如 statsmodels)构建和拟合线性 OLS 模型时,软件通常会使用等式(12)中给出的公式报告标准误差,因此它还会使用这些标准误差报告回归系数估计值的(1-α)100%置信区间。如果使用 OLS 技术拟合模型,假设(2)和(3)-线性形式和残差作为误差项的无偏估计-将被纳入 OLS 技术。

重要的是验证拟合模型满足假设(1)、(4)和(5)——误差项的均值独立性、同方差性和不相关性。但是,如果他们不满意,那么我们就不应该依赖统计软件包报告的标准误差和置信区间。

作为等式(12)的示例,让我们使用它来计算拟合在机动车辆数据集上的线性 OLS 模型的方差-协方差矩阵。

首先,我们将从整个数据集中随机选择 50 个样本来拟合模型。我们将使用 statsmodels 包来构建和拟合模型,如下所示:

*****import** pandas **as** pd
**from** patsy **import** dmatrices
**import** numpy **as** np
**import** scipy.stats
**import** statsmodels.api **as** sm
**import** matplotlib.pyplot **as** plt*#Read the automobiles dataset into a Pandas DataFrame* df = pd.read_csv(**'automobile_uciml_3vars.csv'**, header=0)SAMPLE_SIZE = 50***#Select a random sample of size SAMPLE_SIZE***df_sample = df.**sample**(**n**=SAMPLE_SIZE)***#Setup the model expression in Patsy syntax***model_expr = 'City_MPG ~ Curb_Weight + Engine_Size'***#Carve out the X and y matrices using Patsy*** y_train, X_train = **dmatrices**(model_expr, df_sample, **return_type**=**'**dataframe**'**)***#Build an OLS regression model using Statsmodels*** olsr_model = sm.**OLS**(**endog**=y_train, **exog**=X_train)***#Fit the model on (y, X)*** olsr_results = olsr_model.**fit**()***#Print the training summary of the fitted model* print**(olsr_results.**summary**())***

我们看到以下培训输出:

OLSR 模式的培训总结(图片由作者提供)

可以看出,statsmodels 报告的系数的标准误差如下:

Statsmodels 报告的标准误差(图片由作者提供)

它们的(1–0.05)100%置信区间如下:

statsmodels 报告的 95%置信区间(图片由作者提供)

让我们使用等式(12)手动计算拟合系数的方差-协方差矩阵。

为了使用等式(12),我们需要计算 s ,这是残差的方差。使用公式(e'e)/(n-k)计算如下:**

***e=np.**array**(olsr_results.**resid**)#reshape it to (50 x 1)
e=e.**reshape**(e.**shape**[0],1)e_prime = np.**transpose**(e)resid_variance = np.matmul(e_prime,e)/(50-3)[0]print('Variance of residuals='+str(resid_variance[0][0]))***

我们得到以下输出:

***Variance of residuals=**25.074639949635987*****

方差-协方差矩阵使用等式(12)计算如下:

***X=np.**array**(X_train)X_prime = np.**array**(np.**transpose**(X))coeff_covs_matrix = np.**linalg**.**inv**(np.**matmul**(X_prime, X))*resid_variance**print**('Variance-Covariance Matrix of coefficient estimates=\n'+**str**(coeff_covs_matrix))***

我们得到以下输出:

***Variance-Covariance Matrix of coefficient estimates=
[[ 1.39215336e+01 -7.68038461e-03  5.11395679e-02]
 [-7.68038461e-03  5.77116102e-06 -5.62371161e-05]
 [ 5.11395679e-02 -5.62371161e-05  7.23625275e-04]]***

主对角线元素的平方根是拟合系数的标准误差:

***coeff_std_errors = np.**sqrt**(coeff_covs_matrix)**print**('Standard errors of coefficient estimates=\n'+**str**(coeff_std_errors))***

我们看到以下输出:

***Standard errors of coefficient estimates=
[[**3.73115714e+00**            nan 2.26140593e-01]
 [           nan **2.40232409e-03**            nan]
 [2.26140593e-01            nan **2.69002839e-02**]]***

主要的对角线元素是:

3.73115714、0.00240232409、0.0269***【002839】*****

这些值与训练摘要中 Statsmodels 报告的标准误差相匹配:

Statsmodels 报告的标准误差(图片由作者提供)

参考文献、引文和版权

数据集

汽车数据集 引用: Dua,d .和 Graff,C. (2019)。UCI 机器学*知识库[http://archive . ics . UCI . edu/ml]。加州欧文:加州大学信息与计算机科学学院。 下载链接

纸质和图书链接

威廉·h·格林, 计量经济分析 ,第 8 版 2018,培生****

如果你喜欢这篇文章,请关注我的Sachin Date获取关于回归、时间序列分析和预测主题的提示、操作方法和编程建议。**

深入了解新疫情的字谜热

原文:https://towardsdatascience.com/a-deep-dive-into-wordle-the-new-pandemic-puzzle-craze-9732d97bf723

评估不同的试探法以确定最有效的解决策略,并构建一个人工智能辅助工具来帮助您获胜

自从 2022 年 1 月获得病毒般的地位以来,Wordle 重新点燃了群聊,激发了每个人的竞争精神,并使 Twitter 充满了神秘的表情网格。在内容创作者 Emily Coleman 发布的一条推文中,它被比作从新冠肺炎·疫情开始就渗透到许多人厨房的烘焙痴迷物:酸面团发酵剂。

纽约喜剧演员姜戈·戈尔德(Django Gold)也表达了类似的观点,他在推特上说,在午夜做这个谜题是他日常存在恐惧的一个重要方面,这在某种程度上并不奇怪,在 24000 多名推特用户中引起了共鸣。

现在,在我带你开始令人难以置信的书呆子之旅之前,让我们回答那些不熟悉的人的百万美元问题:到底什么是单词?

截图 via Wordle

Wordle 是一款简单易用的猜字游戏,由布鲁克林的软件工程师 Josh Wardle 开发。他为喜欢玩文字游戏的伴侣设计了这款手机。游戏规则很简单。玩家总共有 6 次机会通过在方框中输入字符来猜测一个五个字母的单词。然后,游戏使用颜色编码来指示玩家他们选择的字母是否在correct点(绿色),在单词中的present但在错误的位置(黄色),或者整个单词中的absent(灰色)。这种反馈系统的一些例子如下:

来源:如何在 Wordle 网站上播放页面

如果你想知道为什么这个游戏的前提对你来说如此熟悉,你可能会想到游戏策划,它与 Wordle 有着相同的目标,只是游戏令牌是彩色的珠子而不是字母。我认为 Wordle 比 Mastermind 更难,因为有一个额外的限制,即单词必须是有效的英语单词,所以有一个类似拼字游戏的元素与游戏的逻辑难题本质交织在一起。Wordle 非常简单,但奇怪的是会让人上瘾。虽然对于任何人来说,创造一个国际互联网轰动最终都令人印象深刻,但最令人着迷的是它背后的“ why ”是什么让它成为如此大的轰动。

https://www.nytimes.com/2022/01/03/technology/wordle-word-game-creator.html?referringSource=articleShare

在上面链接的《纽约时报》文章中,你可以读到更多关于沃德尔本人和游戏起源的故事。在阅读这篇文章时,一个问题出现在我脑海中:为什么这个已经以各种形式存在了几十年的游戏会在短短几周内变得比新冠肺炎病毒传播得更快(好吧……不好的比较,对不起)?

答案是:稀缺性和可共享性!沃德尔破解了如何让益智游戏在注意力经济中茁壮成长的密码。他让人们体验到一种令人上瘾的游戏关系,但他发现,当想到令人上瘾的游戏化模式时,简单地剥夺用户上瘾的动力,会让他们保持参与。《纽约时报》的拼字游戏很早就展示了这个商业、技术和游戏结合的案例研究。我想,稀缺模式还有一个额外的好处,那就是而不是利用用户与技术已经不健康的关系。

鉴于上瘾倾向并不能证明用户跳上 Wordle 平台的指数曲线是合理的,那么下一个候选人就变成了游戏的可分享性,Wardle 也在 NYT 的文章中对此进行了评论。正如你在上面的例子中所看到的,在一行文字和一个简单的总共 30 个表情符号的 5x6 网格中,Wardle 创造了一种简单而令人满意的方式,在那些已经将益智游戏作为日常*惯的用户之间建立联系。

不管怎样,关于 Wordle 作为一个概念已经说得够多了,现在让我们来看看如何策略性地破解这个游戏吧!

如果你想跳过我将要深入的所有乏味的分析,只玩我构建的 web 应用程序,你可以在这里玩它。(完整链接:https://share . streamlit . io/sejaldua/wordle-analysis/main/app . py)

探索性数据分析

对于我们这些文字游戏爱好者来说,我们都很熟悉幸运之轮有奖游戏,在这个游戏中,控制幸运之轮的参赛者最后一次旋转幸运之轮,猜 3 个辅音和一个元音……也就是说,除了英语中最常见的 6 个字母:R、S、T、L、N、e 之外,我想我们可以从验证这些字母是否是最常见的字母开始。当我们把范围限制在只有 5 个字母的英语单词时,命运之轮的数据驱动研究是否成立?

五个字母的英语单词中最常见的八个字母,忽略字母位置(图片由作者提供)

事实证明,在看 5 个字母的单词时,古老的“R,S,T,L,N,E”并不成立。实际上更像是“E,A,R,O,T,L”。当我第一次发现这一点时,我过于简化的外卖是… 嗯,也许我明天应该以“ORATE”作为我的第一个猜测?经过进一步的挖掘,我了解到这样的策略并不是最理想的,因为在单词游戏中位置关系到

最常用字母的热图,根据 5 个字母单词的位置进行细分(图片由作者提供)

上面的热图旨在从上到下阅读,注意每一行中最暗的部分,以了解字母在 5 个字母的单词中的位置分布情况。一个有趣的发现是,尽管“X”不是大多数 5 个字母单词中的一个字母,但它实际上是最常见的最后一个字母之一。至于一个可能有助于我们制定游戏策略的更可行的见解,如果我们直观地遍历这张热图,猜测会产生最多correct(绿色)瓷砖的是类似“SAAEE”的东西……但这显然不是很有用,因为它既不是一个单词,也没有给我们五条独特的信息。是时候通过运行一些模拟来提升这种分析了!

首次猜测策略

最理想的第一猜测是大多数作家激烈争论的话题。为了给它适当的尽职调查,我对密友和 Reddit-ers 做了一个小调查,请他们分享一些他们的首选猜测。我收到的一些答案如下:起航、航线、起航、欺骗、蒸汽、出租、住所、告别、平衡、生物群落、饲养、年、噪音。

观察那些提供评论以配合他们的答案的人的策略:

  • 元音元音元音:再见,路线,出现,住所,风度,噪音
  • 常见辅音:租金(显然来自幸运之轮风扇)、欺骗、牛排、蒸汽
  • 无重复字母:以上所有……从一开始就获取尽可能多的信息很重要。例如,如果我们要猜测“STRUT”,并且我们在两个“T”上都收到了一个黄色块(即,字母“T”出现在单词中,但是在错误的位置),这就是浪费了一个字母,否则我们可以从这个字母中学到更多。

从上面的热图来看,我们应该记住牛排与木桩、住所与土坯、崛起与崛起都是独特的策略,因此必须进行相应的评估。虽然理论上每个变位词对测试的是同一组字母,但位置很重要。希望这篇文章(以及我稍后将讨论的网络工具)能帮助你批判性地思考一个给定单词与其变位词的战略功效。

《fivethirtyeight》的作者 Zach Wissner-Gross 收集了以下数据。他访问并按字母顺序列出了“Wordle's library of 的 2315 个神秘单词以及所有允许你猜的 12972 个单词所以让我们通过编程来确定最佳的初始猜测,好吗?

简单试探法:优化图块分数

对于在所有 2,315 个单词解决方案中,哪一个 5 个字母的单词平均产生最多绿色和黄色的基本分析,让我们编写一些代码来获得绿色(correct)、黄色(present)和灰色(absent)评分试探法。

当在每个解决方案上尝试每个猜测时,获得平均得分启发的代码

下面列出了前五个最佳的第一次猜测策略,优化了你进行猜测时可能获得的绿色瓷砖的平均数量。

前 5 名最佳第一次猜测,按最高平均正确分数(绿色方块的数量)排序

你可能在想,我从来没有在之前的对话中使用过这些词,我不知道这些词是什么意思!然而,参考早期的热图,几乎所有这些单词都遵循以“S”开头,然后是“A”或“O”,然后以“E”结尾的模式,这肯定是有道理的。这是一个很好的方法,可以最大限度地覆盖 5 个字母的单词,并快速获得有价值的信息。面临的挑战是,这些单词中有许多是由重复的字母组成的,正如我们之前讨论的那样,这些字母不能传递最大量的信息。因此,我想我会想试试“SOARE”这个词,意思是“一只年轻的鹰”,如果你好奇的话。

前 5 个最佳的第一次猜测,按最高加权平均牌分数排序(绿色和黄色)

当按平均正确瓦片和平均当前瓦片的加权平均值排序时,我们看到,有趣的是,所有五个最高的初始猜测都由以下字母集合组成:{‘S’, ‘E’, ‘A’, ‘R’}。仅仅通过看这个列表,我就能想起许多使用这些字母的 5 个字母的单词,但是,再一次,我们想避免使用有重复的单词,所以让我们把它们过滤掉。书呆子注意:这只用 3 行 Python 代码就可以完成!

duplicates = [guess for guess in guesses if len(set(guess)) != 5]
df.sort_values(by='avg_tile_score', ascending=False, inplace=True)
df.query("guess not in @duplicates")

前 5 名最佳第一次猜测,按最高加权平均平铺分数排序,排除具有重复字母的单词

这还差不多。这些都是下次你解决日常工作时可以尝试的好方法!

更复杂的方法

公平的警告:如果你不是书呆子,跳过这一部分。我将进入一些技术上更抽象的术语,并列出一些策略。阅读时,你的任务是考虑这些优化技术,在我不给你任何数据的情况下,你自己想想你可能更喜欢哪一种。

  • 最大尺寸优先排序(“为最坏的情况做准备”):即使你选择一个产生所有灰色瓦片的单词(即,在解决单词中没有字母),哪个单词通过穷尽最常见的字母来帮助我们缩短剩余的可能猜测列表?
  • 最大熵优先化(“结果的一致传播”):在不试图给出一个关于或什么是概率质量函数(PMF) 的速成班的情况下,让我们在 Wordle 的上下文中用简单的英语来解开这个问题。使用这个策略,我们想要挑选出产生最大剩余不确定性的单词。这意味着,无论我们得到的是一堆绿色和黄色的瓷砖还是全灰色的瓷砖,我们都将确保所有可能的结果都是相似的,并且没有一个是太差的。这不一定是一种进攻或防守策略,而是,不管单词有多难,它在数学上是最可靠的。
  • 最大分割优先级(“高风险,高回报”):如果我们想获得最大的回报,如果你愿意,我们应该最大化我们可能获得的唯一可能结果的数量。这种方法不是最一致的,但是如果你碰巧猜对了一个单词,得到了一组非常有用的彩色方块,这有助于快速排除选项,潜在地降低了解决这个单词所需的平均猜测次数。

根据我的发现(注:我将把实验分析记录在 Jupyter 笔记本上,供所有书呆子参考),我花了 16 个小时进行模拟,每个策略的最佳猜测如下:

最大大小优先:提高
最大熵优先 : SOARE
最大分裂优先 : TRACE

我的两分钱

我在加注、SOARE 和 ADIEU 之间交替,并在这些首选猜测中取得了一些成功(* =不败)。但我真正的秘密实际上是一种不那么数据驱动的方法,我喜欢称之为“1-2 拳”:

第一步:使用一个有策略的、统计上成功的第一次猜测,这个猜测包含了元音和常见的辅音。

即使你产生了一些绿色和黄色的瓷砖,忽略它们。想一个有 5 个全新字母的单词。理想情况下,用完所有剩余的元音和其他常见的辅音。

举个例子:我最初可能会猜“RAISE”,然后是“MOUNT”。这通常有助于我穷尽所有的元音,并从这两次猜测中获取尽可能多的信息。这样,我就可以通过回忆那些符合我刚刚获得的逻辑难题的 10+标准的单词来继续前进。

这只是我个人的策略!我很想听听你的。如果你愿意分享,请在文章末尾留下评论!

最容易和最难猜的单词

我进一步进行了这种分析,以更好地理解是什么使一个词相对更容易或更难猜测,通过应用前 20 个独特的最佳猜测,由“最大大小”、“最大熵”和“最大分裂”策略计算,并计算得出正确答案平均需要多少次猜测。我们看到,前五个最容易的猜测花费不到 3 次猜测,并且以具有重复字母为特征,其中大多数字母通常是英语中常见的字母。

最难猜的是“用法”、“图层”、“手表”、“吹嘘”和“荷马”。即使使用基于人工智能的策略,上述三种算法也要进行* 10 次猜测才能得出正确答案。我推测其原因是因为这些单词往往有许多“相似”的单词,或者所有字母都相同,但有一个字母被替换的单词。例如,“手表”与“女巫”、“闩锁”、“SATCH”、“批处理”、“CATCH”等非常相似。在游戏的后期阶段,人工智能将无法优先猜测下一个,因为它们中的每一个本质上都提供了相同数量的信息。然而,如果我们扩充数据,根据英语中的使用频率,通过某种流行度分数对每个潜在的猜测进行排序,我们可能会看到这些更严格的 Wordle 字符串的后期效率更高。

Wordle Wizard:一个简化的 Web 应用程序

只是为了好玩,我用我最喜欢的 Python 包做了一个 web 应用。我想让 Wordle 最上瘾的成员做更多的事情,而不仅仅是与他们的朋友分享他们的表情符号格式的结果。如果你能分析你的猜测有多有效呢?有了这个工具,完全可以做到!

或者更好的情况是,如果您第一次猜“加注”,并且在游戏板上获得了彩色的牌,但是您不知道下一次该猜什么呢?我刚才向您介绍的分析应该能够为您提供一些有用的信息,帮助您做出第二个决定。请允许我向你展示…

在“Wordle Assist”模式下,如果你想在一个测试例子中掌握你的策略,你也可以试着从档案、随机 Wordle、甚至手动 Wordle 中找到一个过去的 Wordle。

结论

说到底,我不会说这个游戏可以或者应该用代码破解——那只会让游戏失去乐趣。但我确实很享受学*新的 5 个字母单词的经历,以及学*如何想出一个更聪明的策略,以便从我的猜测序列中获得最有价值的信息。希望这个项目只是增加游戏的乐趣!我真的相信这个游戏没有客观的“最优”策略——这是让用户着迷的原因,对于像我们这样的数据人来说,这是让这个问题变得如此有趣的原因。

我妈,炫耀

为所有为你赢得财富的随机猜测而祝贺自己也是一件有趣的事情。前几天,我妈妈“畏缩”地猜出了 Wordle 的解决方案,似乎毫无根据,仍然有 28 个可行的猜测。

“没有什么人工智能能比得上成为一名球员”
——我的妈妈

链接

Web 应用:https://share . streamlit . io/sejaldua/wordle-analysis/main/app . py

代码库:

https://github.com/sejaldua/wordle-analysis

如果你喜欢这篇文章,请随时查看我的其他作品集作品和/或在 LinkedIn 上与我联系!

在 SageMaker 中构建硬件加速 MLOps 流水线的详细指南

原文:https://towardsdatascience.com/a-detailed-guide-for-building-hardware-accelerated-mlops-pipelines-in-sagemaker-5d32459665b3

了解如何在 SageMaker 中实施英特尔人工智能分析工具包硬件加速库

图像来源

SageMaker 是 AWS 云上的一个完全托管的机器学*服务。这个平台背后的动机是为了方便在托管 AWS 云服务的基础上构建强大的机器学*管道。不幸的是,导致它简单的抽象使得它很难定制。本文将解释如何将定制的训练和推理代码注入到预先构建的 SageMaker 管道中。

我们的主要目标是在 SageMaker 管道中启用英特尔人工智能分析工具包加速软件。AWS 可能会为这些加速包构建公开可用的映像,但同时也欢迎您使用这些模板。

为什么要用 Daal4py 优化 XGBoost?

虽然基准测试超出了本教程的范围,但是其他应用程序已经看到了将 XGBoost 模型转换为 daal4py 格式的明显好处。预测性资产维护 AI 参考套件显示,daal4py 中可用的英特尔优化提供了额外的预测时间加速,与使用案例研究数据集上经过调整的超参数训练的 XGBoost 模型的股票 XGBoost 0.81 相比,总体速度提高了 2.94 倍至 3.75 倍。

图一。XGBoost 和 Daal4py 模型在推理过程中的基准— 图片来源

同样值得注意的是,使用 daal4py 预测没有观察到准确性下降。

本教程是关于使用英特尔人工智能分析工具包构建硬件优化的 SageMaker 端点系列的一部分。你可以在这里 找到整个系列 的代码。

创建 SageMaker 项目

启动 SageMaker 项目非常简单。

  1. 首先在 AWS 中导航到 SageMaker 服务,并从导航面板中选择入门

图二。SageMaker 入门页面—作者图片

2.您需要配置一个 SageMaker 域来启动 Studio。我们将在本教程中使用快速设置选项。

图二。SageMaker 域配置—作者图片

3.从导航面板中选择工作室,打开工作室会话。

4.在 SageMaker Resources 选项卡中,点击 Create project 并选择 MLOps 模板用于模型构建、培训和部署。该模板提供了配置和启动 ML 生命周期管理服务所需的所有代码。我们将编辑该模板的各个方面,以适应我们的自定义训练和推理代码。

图 4。SageMaker 项目创建—作者图片

4.您需要克隆这两个项目存储库。一个对应模型建立,一个是模型部署。CodeCommit 将管理存储库,一个类似于 GitHub 的版本控制服务。

5.我们将把我们的客户流失数据集下载到 AWS S3 存储桶中。我们将在脚本中指向这个桶。要下载数据,在 SageMaker 项目的任何目录下创建一个笔记本,并在 Jupyter 单元格中执行以下代码。

!aws s3 cp s3://sagemaker-sample-files/datasets/tabular/synthetic/churn.txt ./
import os
import boto3
import sagemaker
prefix = 'sagemaker/DEMO-xgboost-churn'
region = boto3.Session().region_name
default_bucket = sagemaker.session.Session().default_bucket()
role = sagemaker.get_execution_role()
RawData = boto3.Session().resource('s3')\
.Bucket(default_bucket).Object(os.path.join(prefix, 'data/RawData.csv'))\
.upload_file('./churn.txt')
print(os.path.join("s3://",default_bucket, prefix, 'data/RawData.csv'))

6.我们需要做一些修改来使 SageMaker 模板适应我们的客户流失解决方案。我们这个客户流失模型的目标是预测用户将来是否会退订某项服务。

图 5。模型构建存储库的文件结构—按作者分类的图像

让我们回顾一下我们将使用的主要文件夹和文件。这些脚本的改编版本可以在 GitHub Repo 中找到。出于时间考虑,您可以随意复制回购中的代码来更新 pipeline.py、preprocess.py、evaluate.py、code build-spec build . yml .

  • pipelines 文件夹包含编排和执行我们的模型构建管道的各种组件的 python 脚本。您需要将该目录中的“鲍鱼”文件夹重命名为“customer_churn”
  • evaluate.py 脚本根据验证数据集评估我们的模型。在这个例子中,我们使用 MSE,但是您可以将它修改为其他合适的度量。
  • preprocess.py 脚本执行各种数据处理和特征工程步骤,如一键编码和标准化。您可以通过注入额外的处理步骤来适应您的解决方案。
  • pipeline.py 脚本协调您的整个 SageMaker 模型构建管道。它加载机器映像,指定计算实例,并从适当的数据源中提取数据。理解这个脚本的来龙去脉可能需要一些时间,但是一旦掌握了窍门,就相对简单了。首先编辑以下内容:
    • S3 位置(第 95 行)
      -自定义映像 URI(第 121 行)—构建自定义映像的步骤在本文中有详细讨论: 使用 oneAPI 和 Docker 在 SageMaker 中实现自定义加速 AI 库的指南。
      -你的管道名称(第 70 行)
  • code build-spec build . yml在将变更推送到 CodeCommit Repo 时配置您的构建。

7.一旦我们完成了对这四个文件的编辑并配置了我们的定制培训/服务映像,我们就可以将我们的更改推送到我们的 repo 中。由于我们的模板预先配置了 CI/CD,这将自动执行管道并训练一个模型。

  • 从侧面导航面板中选择 GIT 选项卡,选择您已修改的文件以添加到暂存区并提交,然后将更改推送到远程存储库。

图 6。提交和推送 Git 面板—作者图片

  • 要检查您的 CI/CD 自动化是否正确触发了构建,您可以转到 AWS CodeCommit 并从导航面板中选择 build history。您应该看到一个带有“进行中”状态的构建运行。

图 6。代码提交构建历史

  • 转到 SageMaker 资源并选择您的项目。从管道页签,可以查看管道执行的状态;您将发现一个成功的作业,它是在我们克隆模板 repos 时自动执行的。您还应该看到一个处于执行状态的管道,您可以双击它来查看关于您的执行的更多细节。

图 7。管道执行仪表板—按作者分类的图像

  • 您可以使用可视化图形表示来监控管线的进度。单击节点将打开一个执行元数据面板,其中包括输入、输出和执行日志。

图 8。管道图界面—作者图片

成功完成管道后,将创建一个模型。您可以在模型组项目选项卡中访问您的模型。

端点和推理作业

SageMaker 端点由您的管道自动创建,负责处理推理组件。

  1. 由于我们的模型批准条件被设置为“手动”,我们将不得不手动批准我们的模型。当一个模型被批准时,这将调用一个云生成栈,它创建一个 SageMaker 模型SageMaker 端点配置SageMaker 推理端点。所有这些组件都可以在中央 SageMaker AWS 控制台中进行跟踪。负责这种自动化的代码可以在我们在本教程开始时克隆的模型部署 repo 中找到。

图 9。模型批准控制台—作者图片

2.在 sagemaker 中检查端点构建的状态。导航至 SageMaker 推理并选择端点。您可以在 CloudWatch 上选择 View Logs 来查看来自您的端点的事件数据(图 11)。

图 10。使用 CloudWatch 来诊断问题,并检查端点 ping 测试是否成功。作为端点 ping 测试的一部分,我们正在加载我们的模型。成功的 ping 指示一个健康的端点。—作者图片

另一个很好的 QC 点是检查您的端点是否在端点仪表板中标记为“运行中”(图 12)。

图 11。SageMaker 终端管理仪表板—作者图片

设置 Lambda 函数来处理 API 请求

AWS Lambda 函数阻止我们设置专用服务器来监控请求和执行像格式化和端点调用这样的小段代码。这有很大的好处,比如只在功能被触发时为计算机付费,而不是专用服务器,后者将向我们收取预留或按需费用。

为这个特定教程构建 lambda 函数的步骤将在本文中详细讨论: 从 ECR 图像构建 AWS Lambda 函数以管理 SageMaker 推理端点的指南。

使用 API 网关构建 REST API

配置 REST API 将允许我们向 SageMaker 端点发送 HTTP 协议请求。以下步骤概述了如何使用 AWS API 网关实现这一点。

图 12。REST API 架构图——作者图片

  1. 导航到 API 网关,并从 REST API 部分选择“构建”

2.选择 RESTNew API ,在 API 设置部分填写您的姓名和端点类型。完成后,点击创建

图 13。REST API 配置设置—按作者分类的图像

3.转到动作下拉菜单并选择创建资源。完成后,点击创建资源。

图 14。资源创建配置-按作者分类的图像

4.转到动作下拉菜单,选择创建方法。选择发布。检索您在上一节中配置的 lambda 函数的名称,并提供与其余资源相同的区域。

图 15。发布请求配置-按作者分类的图像

在创建网关时,会提示您一个 API 架构图。

5.您可以通过点击动作选项卡并选择部署 API 选项来部署 API。这将为您提供一个链接,您可以使用它向您的模型端点发送 Post 请求。

图 16。调用用于向您的 API 发送请求的 URL 按作者排序的图像

测试您的新 API

https://web.postman.co/创建一个免费的邮递员账户

我们可以使用 Postman 创建 REST API 请求来测试我们的新 API。

在 Postman 中创建新的测试,粘贴您从 REST API 创建的链接,选择 Body 作为输入类型,选择 POST 作为请求类型,并提供输入数据。

图 17。邮递员测试仪表板-作者图片

如果您已经完成了本教程中的所有步骤,您应该会从您的 API 得到一个“真实”的响应。

结论和讨论

恭喜你!您已经使用 oneAPI 硬件加速库在 SageMaker 上构建了一个定制的 MLOps 管道。使用本教程中的信息,您可以在 SageMaker 中构建端到端的机器学*管道,并利用 daal4py 等硬件优化的机器学*库。

在未来的文章中,我们打算发布计算机视觉和自然语言处理硬件优化的 SageMaker 管道的代码和演练。

来源:

初学者的差分隐私示例

原文:https://towardsdatascience.com/a-differential-privacy-example-for-beginners-ef3c23f69401

通过抛硬币来保护隐私

图片来源:马頔·安德烈

差分隐私(DP)是一种保护数据集中个人隐私的方法,同时保护这种数据集的整体有用性。理想情况下,人们不应该能够区分一个数据集和删除了一个点的平行数据集。为了做到这一点,随机算法被用来给数据添加噪声。

举个简单的例子,想象一下:你在一所学生总数为 300 人的学校里。你们每个人都会被问到:“你们曾经在考试中作弊吗?”

明白这是一个多么敏感的问题了吧?也许那些作弊的人不愿意回答“是”,因为害怕潜在的影响。那么…我们如何解决这个问题呢?这就是 DP 派上用场的地方。

每个学生掷一枚硬币。如果他们正面着地,他们会说实话。如果它们正面着地,它们会再掷一枚硬币;如果他们正面着地,回答是;反面,没有。

一张图来说明硬币翻转

这样,即使你的调查结果被公开,也有可信的否认来证明你的回答的准确性。同时,随着人数的增加,学校仍然可以利用这些数据;它不会变得完全无用。

我们将看看这个用 Python 代码实现的掷硬币的例子。

骗子与非骗子的硬币游戏

Matplotlib 将用于绘制数据,以便于可视化。需要 Random 来实现随机抛硬币。

第 1 部分:不带 DP

首先,我们将看看没有差分隐私的模拟数据是什么样的。我们的“原始数据”用 0 和 1 表示,其中 0 表示“没有作弊”,1 表示“作弊”。每个二进制数代表不同的学生,他们真实的“作弊状态”是这里唯一衡量的指标。

上面单元格的输出示例

如你所见,100 名学生报告作弊,200 名学生报告没有作弊。

让我们向数据中添加一个新学生。假设他们真的作弊了。现在,运行输出,看看它们的数据点如何影响图形。

因为很难看出如此小的差异,所以使用 matplotlib 注释工具向条形添加数值。

上面单元格的输出示例

在这个传统的调查中,当我们添加一个学生时,很容易看出两个结果之间的差异。如果“作弊”的数量上升,学生作弊。如果“没有作弊”的数量上升,那么他们没有作弊。因为作弊一栏现在是 101 而不是 100,这意味着 301 号学生作弊了。

第 2 部分:使用 DP

为了实现 DP,每个学生掷一枚硬币。如果它落在头上,他们会如实回答。如果是反面着地,他们再掷一枚硬币。如果它落在正面,他们回答说他们没有作弊,如果它落在反面,他们回答说他们作弊了。

如果您多次运行该代码,您会注意到图形每次都是如何变化的。这是因为 DP 算法注入了随机性,就像我们在抛硬币时所做的那样。

上面单元格的输出示例

现在,我们可以说明差分隐私的一个主要目的:不可能从一个数据集与一个平行数据集的结果中看出不同。

让我们向数据中添加另一个新学生。假设他们真的作弊了。

现在,运行输出,看看它们的数据点如何影响图形。如果你这样做多次,图形每次都会改变。

上面单元格的输出示例

正如你所看到的,我们真的不能判断学生#302 是否作弊,因为输出结果包含一定程度的随机性。

由于算法的随机性,这就是拥有更大的数据集派上用场的地方。现在假设我们有 30,000 名学生,而不是 300 名。运行下面的代码。如果您多次运行它,您将观察到 DP 图没有显著变化,因为值遵循计算的概率。

上面单元格的输出示例

因此,通过使用 DP,我们保护了隐私,因为有人无法知道一个人是否在数据库中。

这些结果有多好?

如上所述,数据集越大,结果就越准确,因为随机性被设定的概率抵消了。在掷硬币的情况下,随机性相当高,大约 25%的数据是错误的。然而,知道了这一点,骗子与非骗子的实际分布也同样可以计算出来。

c 是作弊学生的真实比例;那么,1- c 就是没有作弊的学生的真实比例。说 p_y 是学生举报作弊的比例。

p_y 是通过将 3/4 的作弊比例与 1/4 的非作弊比例相加得到的,因为我们假设每个回答中有 1/4 是错误的。

这简化为:

因此 c 估计是回答“是”的比例的两倍,减去 1/2,这表明我们如何估计在运行 DP 后知道结果的骗子的真实比例。请注意抛硬币的随机性是如何被简化为概率的,这表明需要一个大型数据集来保持准确性。

我们的结果显示,大约 125/300(所以 5/12)的人报告作弊——这是 p_y 。然后,计算出的 c 值为 1/3,与真实比例值相同。

结论

这个抛硬币的例子是如何使用 DP 来保护隐私的一个非常简化的版本。通过抛硬币,回答是匿名的,因为每个回答中都有“似是而非的否认”,但它仍然允许大约 75%的回答是真实的。我们能够“反向”计算,以找到每个响应的真实比例的良好估计值——这个数字在足够大的数据集下变得非常准确。

这个例子强调的最大优势是个人隐私——使用 DP,一个用户不可能区分两个不同的数据集,这在个人层面上保证了隐私。

你可以在这里 访问原脚本 自己来贯穿一切。此外,学生版(完成脚本并编写部分代码)以及说明可在此处获得 它们都包含了一个额外的例子,改编自 OpenMinded 的 PyDP 库。

除特别注明外,所有图片均为作者所有。

参考文献:

[1] P. Ippolito, AI 差分隐私和联邦学* (2019)

[2] S. Madhusudhan,(通过抛硬币)如何保护你的数据 (2020 年)

[3]麻省理工学院 ETI,什么是差别隐私? (2021)

PySpark 窗口函数入门

原文:https://towardsdatascience.com/a-dive-into-pyspark-window-functions-a090aee4ff23

许多有待发现的能力

皮特·赖特在 Unsplash 上的照片

在早先的文章中,我提供了 PySpark 窗口函数的快速介绍。这篇早期文章的目的是说明与不太熟悉窗口函数的数据分析师通常使用的复杂变通方法相比,使用窗口函数的好处。本文的目的是更深入一点,说明 PySpark 窗口函数提供的各种可能性。同样,我们在整个示例中使用合成数据集。这使得喜欢边阅读边练*的感兴趣的读者可以很容易地进行实验。本文中包含的代码是使用 Spark 3.2.1 测试的。

导入并启动数据集

我们从必要的进口开始

然后,我们创建一个包含三家虚构公司销售额的合成数据集。为了方便起见,数据集是使用 pandas、numpy 和 scipy 创建的,这些工具通常可供所有使用 Python 的数据从业者使用。考虑到更大的尺寸,起始数据帧可以专门在 PySpark 中构建。然而,本文的目的是说明窗口函数的可能性,而不是将计算扩大到熊猫友好的大小之外。

起始数据框包含三家公司在大约 2.5 年期间的每日销售数据。生成宽格式数据帧的代码是

数据框可通过以下方式显示

产生了

图 1:开始数据集

如何构建起始数据帧的细节并不重要。对于感兴趣的读者,我们创建了两个短列表,分别是 x 点和 y 点,第二个列表被随机排列,以产生三家公司的不同曲线。我们在 x 点之间使用了 scipy 中实现的样条插值,并使用 numpy 添加了一些噪声。为了再现性,我们通过指定种子构建了一个随机数生成器。其中一列是有意为日期类型的,因为时间序列是窗口函数显示的数据类型。剩下的唯一一件事就是使用

方便的是,PySpark 数据帧具有所需的模式,无需显式指定模式。

接下来的四个部分是聚合、排序和分析 PySpark 窗口函数的例子。有了这个,我们就可以出发了!

聚合窗口函数

集合窗口函数通常处理整个窗口。如果目的是处理整个窗口,则不需要在窗口内排序,这是最典型的用例。例如,让我们计算公司 1 每季度销售额最大的日期

为了确认一切正常,我们绘制了结果图

产生了

图 2:公司 1 的季度最高销售额

红点表示最大销售额,垂直虚线表示季度界限。我们可以看到,window 函数正确地计算出了公司 1 在每个季度销售额最高的日期。通过设置轴限制,图表已被放大,这样我们可以更容易地检查结果。在 Spark 和 pandas 数据框之间来回切换在实践中通常不会发生,但是在本教程中,如果所使用的数据框完全在 pandas 的能力范围内,那么这对于绘制需求是很方便的。

一个烦恼是,上面的计算只涉及公司 1。还有两家公司,实际上我们可能有更多公司的更多列。我们可以创建一个循环,或者(更好地)我们可以用长格式而不是宽格式创建起始数据帧。解除 Spark 数据帧的透视也是可能的,尽管这需要更多的努力

其工作方式是创建一个包含结构数组的列(PySpark 中的“dictionary”包含公司名称和销售额),分解数组以创建多行,然后从结构中提取我们需要的列。这有点复杂,但是对于本文的目的来说,细节并不重要。重要的是数据框现在有一行是每个公司和日期的销售数据。

现在,可以一次计算出所有公司每季度最高销售额的日期

公司 1 的结果与之前相同。

可以使用的聚合函数有 min、max、count、sum 等。原则上,我们可以使用一些记录而不是整个窗口,如下所示。这在聚合窗口函数中并不常见,除非我们需要运行求和。例如,如果我们想计算每个公司和季度到给定日期的最大销售额,我们可以使用

我们可以看到,每当遇到更高的销售额时,就会出现新的一行,直到我们到达下一个季度,然后我们再次开始。

排名窗口功能

排序窗口函数很容易概念化。如果不对窗口中的行进行排序,排序函数就没有意义,所以我们需要引入更多的语法。我们将使用一个较小的数据集,特别是在删除一些列后,公司 1 每个季度销售额最高的数据集(名为 res_company1)

如果我们希望对每年的季度进行排名,我们可以使用以下内容

默认的排序是升序,所以并不严格要求显式地指定它,但是它使代码更清晰。函数F.rank()不接受任何参数,并在出现平局时产生非连续整数。如果不希望这样,可以使用F.dense_rank()。还有更多排名功能,如文档中描述的F.percent_rank()F.ntile()F.row_number()等。还有一个函数 F.cume_dist()F.percent_rank()相关,它返回累积密度函数而不是百分比排名,因此严格来说它不是排名函数,而是统计函数,但这主要是语义上的,除非你是面向统计的。如果您有兴趣,可以用一个小的数据框进行实验,并比较这两个函数。

分析窗口功能

最后一组窗口函数允许查看感兴趣的记录。典型地,这种函数与时间序列一起使用,例如,将记录与最*的记录进行比较。

作为第一个例子,我们将计算公司 1 的销售额与前一个日期相比增长最多的日期。用例并不表示需要在变量上创建窗口,因此整个数据框将是唯一的窗口。这是通过不带任何参数调用Window.partitionBy()函数来实现的。尽管如此,我们还是要避免这种情况,因为它会将所有数据移动到同一个分区,从性能的角度来看这是不明智的(尽管我们在本教程中的数据帧太小了,不值得担心)。相反,我们将按年进行分区,这仅意味着第 n+1 年 1 月 1 日的记录不能与第 n 年 12 月 31 日的记录进行比较,因为窗口中没有以前的记录。对于我们的例子来说,这不是一个大问题

这个例子的有趣之处在于,我们在另一个窗口函数中使用了一个窗口函数的结果。你想知道为什么我们创造了另一个窗口吗?我们不能用之前的吗?答案是否定的,原因是排序意味着我们不会使用整个窗口,而只会使用给定记录之前的部分窗口。这是违反直觉的,因为排序通常不会影响计算的范围,但在这种情况下它会影响!我们稍后将回到这一点。

与排序窗口函数相比,F.lag()函数可以在与排序列不同的列上操作。对于F.lead()函数来说也是如此,它向前看而不是向后看。两个函数都使用参数n来控制我们向前或向后移动多少记录,使用参数default来替换接*窗口边界时的空值。

从窗户到窗框

文章开头的图像显示了一个由几个窗口框架组成的窗口。选择该图像与 PySpark 窗口函数进行类比。我们不需要在计算中使用整个窗口,实际上我们可能只需要使用它的一部分。在本文前面计算每个公司和每个季度的最大销售额时,我们第一次看到了这一点。在 PySpark 窗口语言中,我们在运行最大值示例中使用的窗口框架是增长的。它从窗口开始(unboundedPreceding)开始,覆盖订单变量的整个范围,直到当前记录(currentRow)。现在,我们将概括窗口框架的概念,以涵盖所有窗口框架的大小和类型。

如果我们想使用 10 天或 3 天的静态窗口和年度窗口来计算公司 1 每年的运行平均销售额,会怎么样?为了实现这一点,我们将依赖于 Unix 纪元时间,因为该时间用可在范围中使用的数值表示(pandas 在日期范围方面提供了更多功能,但不适合大数据分析;我们不能拥有一切!).Unix 纪元时间以秒表示,因此我们的 3 天窗口对应于 2436003 = 259 200 秒的范围,而 10 天窗口对应于 864 000 秒的范围。为了便于比较,我们还将计算每年销售的移动平均值。

如果我们把结果想象成

我们获得

图 3: 3 天、10 天和运行平均值

如果你对如何在像这种带有新冠肺炎更新的页面中计算运行平均值感到好奇,现在你知道了!除了.rangeBetween()之外,我们也可以使用.rowsBetween(),但是我们把它作为一个练*。

最终想法

PySpark 中的窗口函数可以成为数据分析和特征工程不可或缺的工具。有一个学*曲线,但是一旦掌握了窗口函数,就可以避免难以维护且效率可能较低的变通代码技巧。当你处理一个新问题时,你可以首先考虑目的是汇总、排序还是向前/向后看。其次,你可以考虑你的窗口是否应该全部被使用(无界的)或者你只需要它的一个框架(有界的),在这种情况下,排序几乎肯定也是需要的。回答完这些问题后,使用这篇文章和 PySpark 文档应该可以让你开始了!

Python 依赖注入模式的简短解释

原文:https://towardsdatascience.com/a-fairly-short-explanation-of-the-dependency-injection-pattern-with-python-4f11e7ee27f6

深入研究这种非常有用但经常被忽视的设计模式

一张注射器的照片,因为这是关于依赖性注射,你明白吗?哈哈哈。好吧,我是想开个小玩笑,但恐怕这是一种尝试。你明白了吗?哈哈哈。(不好意思)。戴安娜·波列希纳在 Unsplash 上的照片

依赖注入作为一个概念既不性感也不酷,就像几乎所有的设计模式一样。尽管如此,如果利用得当,它还是非常有用的——同样,几乎和任何设计模式一样。希望在这篇文章结束时,你会有另一个工具来使用你的皮带。

让我们去争取吧。

依赖注入是什么意思?

依赖注入是一个简单概念的花哨术语:给对象它需要的东西,而不是让它自己创建它们。这是有用的,原因有几个:它使我们的代码更加模块化,更容易测试,它可以使我们的程序更加灵活,更容易扩展。

这里有一个例子来说明这个概念。假设我们有一个代表超级英雄的类。这个类有一个name属性和一个power属性,它有一个use_power()方法,该方法打印出一条消息,说明超级英雄如何使用他们的能力。这个类可能是这样的:

class Superhero:
    def __init__(self):
        self.name = 'Spider-Man'
        self.power = 'spider-sense'

    def use_power(self):
        print(f'{self.name} uses their {self.power} power!')

# create a superhero and use their power
spiderman = Superhero()
spiderman.use_power()  # prints "Spider-Man uses their spider-sense power!"

这很好,但是有一个问题:我们的Superhero类正在创建它自己的名字和能力属性。这意味着我们创造的每个超级英雄都将拥有相同的名字和力量,除非我们在创建对象时明确设置它们。这不是很灵活或模块化。

为了解决这个问题,我们可以使用依赖注入。我们可以将它们作为参数传递给__init__()方法,而不是让Superhero类创建自己的名称和能力属性。这可能是这样的:

class Superhero:
    def __init__(self, name, power):
        self.name = name
        self.power = power

    def use_power(self):
        print(f"{self.name} uses their {self.power} power!")

# create a superhero with the name "Superman" and the power "flight"
superman = Superhero("Superman", "flight")

# use the superhero's power
superman.use_power()  # prints "Superman uses their flight power!"

# create a superhero with the name "Batman" and the power "money"
batman = Superhero("Batman", "money")

# use the superhero's power
batman.use_power()  # prints "Batman uses their money power!"

如您所见,使用依赖注入使我们的Superhero类更加灵活和模块化。我们可以创造任何名字和权力的超级英雄,我们可以很容易地交换不同超级英雄的名字和权力。

这方面有哪些真实的用例?

测试

依赖注入使得为我们的代码编写单元测试变得更加容易。因为我们可以将类所依赖的对象作为参数传入,所以我们可以很容易地将真实对象替换为我们可以在测试中控制的模拟或存根对象。这允许我们单独测试我们的类,并确保它的行为符合预期。

配置

依赖注入可以使我们的代码更具可配置性。例如,我们可能有一个发送电子邮件的类。这个类可能依赖于实际发送电子邮件的EmailClient对象。我们可以使用依赖注入将其作为参数传入,而不是将EmailClient对象硬编码到我们的类中。这样,我们可以很容易地改变我们的类使用的EmailClient对象,而不用修改类本身。

这是没有依赖注入的代码的样子:

class EmailSender:
    def __init__(self):
        self.email_client = GoodMailClient()

    def send(self, email_text, recipient):
        return self.email_client.send(email_text, recipient)

    # other methods that use self.mail_client

这是依赖注入的样子:

class EmailSender:
    def __init__(self, email_client):
        self.email_client = email_client

    def send(self, email_text, recipient):
        return self.email_client.send(email_text, recipient)

    # other methods that use self.mail_client

第二种方法允许您非常容易地从这个:

# Create an instance of a good email client
good_email_client = GoodMailClient()

# Create an instance of EmailSender, injecting the dependency
sender = EmailSender(email_client=good_email_client)

# Send the mail
sender.send('Hey', 'you@mail.com')

对此:

# Create an instance of a better email client
better_email_client = BetterMailClient()

# Create an instance of EmailSender, injecting the dependency
sender = EmailSender(email_client=better_email_client)

# Send the mail
sender.send('Hey', 'you@mail.com')

…根本不修改EmailSender类。

扩展

依赖注入可以使我们的代码更具可扩展性。例如,我们可能有一个处理数据的类。这个类可能依赖于一个执行实际数据处理的DataProcessor对象。我们可以使用依赖注入将它作为参数传入,而不是让我们的类创建自己的DataProcessor对象。这样,如果我们想扩展类的功能,我们可以很容易地用不同的对象替换掉DataProcessor对象。

这只是几个例子,但是在现实世界中还有很多其他的依赖注入用例。

听起来很棒,有什么好处?

嗯,没有捕捉到本身,但当然有一些问题。一个主要的缺点是它会使我们的代码更加冗长和难以阅读,特别是当我们注入大量的依赖项时。这会使我们的代码更难理解和维护。

另一个潜在的缺点是依赖注入会使我们的代码更难调试。由于依赖项是作为参数传入的,所以如果出现问题,就很难跟踪错误的来源。

此外,依赖注入会使理解类或模块的依赖关系变得更加困难。由于依赖关系是从外部传入的,所以很难一眼看出一个对象依赖于什么,这就很难理解它是如何工作的。

总的来说,虽然依赖注入有很多好处,但它也有一些缺点。与任何软件设计模式一样,重要的是权衡利弊,并决定它是否是给定情况下的正确方法。

最后的想法

从我的经验来看,我想强调的是,这只是我的拙见,从一开始就使用这种模式通常是个好主意,甚至是 MVP。这听起来可能更复杂(事实也确实如此),但是当你得到这个钻头时,它是一个不需要动脑筋的东西,并且可以灵活地进行你以后可能想要添加的任何进一步的修改。然而,关注注入的依赖项的数量也是一个好主意——您不希望它增长太多。

我个人发现的最常见的缺点是代码变得难以理解,尤其是当团队中有新成员加入时。但是,通过适当的入职培训,这个问题很容易解决:收益大于成本。

参考资料:

[1] M. Seeman,依赖注入是松散耦合的 (2010),Ploeh 博客

[2]哈佛 S .依赖性注入的弊端 (2010),StackOverflow

[3] A .卡尔普,依赖注入设计模式 (2011),MSDN

如果你喜欢阅读这样的故事,你可以直接支持我在 Medium 上的工作,并通过使用我的推荐链接 这里 成为会员而获得无限的访问权限!😃

一系列专门的超级计算机,模拟分子力学,独一无二

原文:https://towardsdatascience.com/a-family-of-specialized-supercomputers-that-simulates-molecular-mechanics-like-no-other-38a6e59f96ef

D. E .肖研究所的安东电脑公司

作为机器学*方法海洋的纯粹基于物理学的替代方法,这项无与伦比的技术能够实现与基础生物学和制药相关的其他不可能的研究

这些超级计算机已经存在了十多年,拥有一个专门的架构,使它们能够非常有效地运行多原子系统的分子动力学模拟。图片由达尔-E-2 世代的作者创作(截至 2022 年 7 月 23 日,可用于包括商业用途在内的所有合法目的,详见https://labs.openai.com/policies/terms)。

分子动力学模拟包括将一种物质描述为数学模型,通常是一个原子一个原子地描述,然后以物理现实的方式计算系统如何随时间演变。与基于最大似然的方法和其他需要大量数据进行训练的方法相反,分子模拟试图从纯粹的物理原理中再现现实,因此允许探索用基于数据的方法很难甚至不可能探索的问题。这里是分子动力学模拟领域的一瞥,重点是只能运行这种计算的超级计算机家族,但速度无与伦比。

为了运行分子动力学模拟,并将这种解释仅限于所谓的“经典原子分子动力学模拟”,科学家为系统中的每个原子指定半径、质量、电荷以及与所有其他原子的物理一致性联系。这些连接包括模拟共价键和角度限制的“键合”连接,将原子保持在分子中,还包括“非键合”相互作用,记录原子如何相互碰撞,被相反或相同符号的电荷吸引或排斥,等等。随着时间的推移,所有这些数学描述的整合产生了一种关于系统中所有原子如何运动的电影。利用这种工具,科学家可以从简单的概念(如分子如何移动或扩散)探索复杂的问题,如药物如何与目标蛋白质结合,蛋白质如何发挥其功能,以及与基础和应用化学和生物学相关的无数其他问题。

分子模拟对计算要求很高

事实证明,在模拟过程中随着时间传播运动并不是一件容易的事情。给定数学模型中原子的初始配置,一个名为“分子动力学引擎”的计算机程序计算出作用在所有原子上的力,然后计算出它们的速度和位置的变化。根据新的位置,程序可以再次计算力,并对速度和位置进行新的更新。然后一次又一次,每一次都创造出一帧描述系统原子如何运动的“电影”。此外,通过确保原子以一致的方式运动,模拟模拟了模拟系统的温度和压力。模拟温度包括产生随机数来影响所有原子的速度;因此,每次运行 MD 模拟时,您都会观察到不同的时间演变。关键(也是希望)在于,在足够长的时间之后,和/或如果一个人可以并行运行给定的模拟多次,那么从模拟中得出的总体结论原则上是相同的。这就是为什么进行长时间模拟很重要,尤其是当你想对一个需要时间才能发生的事件进行采样的时候。

不幸的是,物理学使得“电影”的连续“帧”之间的时间步长非常短。通常只有 2 fs(飞秒),即千分之一秒的百万分之二。当我们回忆起最有趣的化学事件发生在微秒到毫秒的时间尺度时,这个数字就有了一些意义。

为了达到这些时间尺度,MD 程序需要计算数十亿到数万亿步!

更糟糕的是,如果我们考虑到为了收集统计数据,人们理想地想要观察被调查事件的多次发生。

用于分子模拟的专用超级计算机

典型的计算机,包括那些拥有最强大的 GPU 的计算机,在最好的情况下,今天可以模拟几十微秒的小系统。虽然有专门的方法可以通过“强迫”系统经历事件(一系列“增强采样”的“把戏”)来规避这个问题,但十多年前,亿万富翁、前计算机科学教授大卫·艾略特·肖创建了一家私人公司德肖研究,其目标是开发一系列新的超级计算机,专门用于多维模拟,将摆脱当前的限制。

这家新公司的长期目标是通过应用模拟以原子细节理解蛋白质和其他生物系统,加速药物相关化合物的开发研究。作为中间目标,他们从事分子力学力场的优化,即用于描述系统模拟的参数集合。在此之前,他们解决了与优化分子动力学模拟计算相关的工程问题。

第一台计算机由德肖研究所开发,名为安东(我们更愿意称之为“安东 1”,因为随后的型号被称为安东 2、3 等。),2008 年投入工作。它可以模拟原子细节的分子系统,比当时的普通计算机快 100 倍左右。这意味着在该公司使用安东的科学家可以在 2010 年代初模拟即使在今天也没有人可以模拟的分子事件,而无需应用任何增强的采样技巧。不知何故,他们打破了应用于分子模拟的“摩尔定律”。

安东 2 甚至更快,它可以适应更大的系统,并且它比它的前身更具可编程性(因此更通用)(回想一下,这些计算机是硬连线来运行模拟的,所以即使是常规模拟的变体也不会像在常规计算机中一样容易实现)。至少也有一台安东 3 计算机,它更快,更可编程,并且可以很好地扩展到相当大的系统,与最接*的竞争对手 GPU 相反。如果你喜欢模拟并想看一些数字,据报道 Anton 3 每天以大约 200 微秒的速度运行 100k atoms 系统,这意味着你可以在仅仅一周的工作中获得 1 ms 的动态。目前最好的 GPU 运行速度比 Anton 3 慢 10 倍左右,但仅适用于小型系统,不适用于大型系统。因此,Anton 3 具有双重优势:它运行模拟的速度更快,每单位实时产生更多的采样,并且还能够研究更大、更完整和更复杂的系统。

安东电脑如何工作

为了在模拟分子系统时实现卓越的速度,安东计算机集成了专门为 DEShaw 研究项目开发的计算机工程新部件。总的来说,这些发展包括设计硬件来加速分子模拟中的典型计算。因此,安东计算机在整合运动方程时牺牲了效率和速度来换取灵活性。换句话说,它们在运行分子模拟方面非常强大,但它们不能做任何其他事情。它们是高度专业化的硬件。

无需深入细节,从维基百科和上面引用的 DEShaw Res 的文章(以及结尾的更多文章)中总结,Anton 完全在专用电路( ASICs )上运行其计算,而不是在通用主机处理器之间划分计算。特别是,具有专门用于特定计算的内核的强大 ASICs 是安东卓越速度和简化通信的核心。每个安东 ASIC 包含两个子系统,以及自己的 DRAM 库,支持大型仿真。其中一个子系统专门用于计算非结合力;这是一个高通量交互子系统,由几个深度流水线模块组成,排列方式很像一个脉动阵列。其余的计算,包括结合力和各种数学运算,在另一个子系统中执行(该子系统更加灵活,可以容纳各种不同的计算,由专门但可编程的 SIMD 内核组成)。

安东计算机的 ASICs 被排列成一个 3D 圆环,最大限度地提高了它们之间的连通性,并通过每秒传输数十到数百 GB 的高带宽链路实现了最大限度的连接,这些链路由不同方向的信息流组成。没有留下任何机会;如你所见,每个细节在设计时都考虑到了。

在一波前所未有的模拟来证明安东的潜力之后,生物学的应用出现了

2010 年,DEShaw Research 在《科学》的一篇论文中报告了首次使用 Anton(当时的 Anton 1)对蛋白质运动进行的全面研究:

蛋白质结构动力学的原子水平表征 (Shaw 等 Science 2010)

这篇论文报道了原子级详细的分子动力学模拟,每个模拟达到大约 0.1 到 1 毫秒,对于从无序形式折叠成 3D 结构的蛋白质和在几十微秒内经历功能相关运动的折叠蛋白质,因此你需要毫秒模拟来多次采样它们。

安东 1 号在 2010 年已经达到了毫秒的时间尺度,但仍然比今天报道的使用非模拟设计的传统超级计算机进行的典型模拟长约 100 倍。

对于一些更详细的情况,然后请读者参考该论文,该工作提出的模拟为:

  • 小蛋白质 FiP35 和 villin,已知折叠速度非常快(在微秒内),从延伸的构象开始,并监测它们是否以及如何采用已知的 3D 结构。
  • 牛胰腺胰蛋白酶抑制剂(在这种情况下,从实际的折叠 3D 结构开始)在不同构象状态之间相互转化的动力学发生得太慢,常规模拟无法捕捉它们。

蛋白质折叠成 3D 状态的模拟在原则上有可能取代(并且比)基于 ML 的结构预测方法,具有额外的优势,即它们可以解释蛋白质如何折叠。回想一下,即使是最好的 ML 方法,如 AlphaFold,也能预测折叠结构,但不能预测它们是如何实现的,也就是说,它们原则上对折叠途径一无所知。相反,在基于物理的模拟中,人们可以从字面上看到折叠是如何进行的,如果该过程再现了已知的实验数据,那么人们就可以根据模拟推断蛋白质是如何折叠的。

2011 年,DEShaw Res 发表了一篇新论文,研究了无序状态下几种小蛋白质的折叠路径,其中当然包括 Anton:

蛋白质如何快速折叠 (林道夫-拉森等人,科学 2011)

在这之后,我们没有听到公司更多关于使用分子动力学模拟折叠蛋白质的消息。可能,这种方法没有进一步发展,因为即使中等大小的蛋白质也需要几毫秒才能折叠;也可能是因为力场还不够好。此外,基于最大似然法的预测的影响似乎掩盖了像分子模拟这样的基于物理学的方法的作用。事实上,计算化学家和生物学家团体的大部分努力现在主要致力于改进基于 ML 的方法。只要查一下最*这次大会的会谈清单就知道了。

DEShaw Res 现在如何使用安东电脑

该集团将安东计算机的强大功能用于两个主要目标:

  • 改进力场,即在整个模拟过程中更好地描述用于描述原子及其相互作用的参数。这对整个社区来说至关重要,尤其是对 DEShaw Res 来说,因为通过运行如此长时间的模拟,他们可以更好地暴露(遭受,并最终纠正)力场中的问题和偏差。
  • 推进对生物相关性系统的原子级理解,这可能是该公司作为最终创造临床用新分子的手段的最终作用。

改进力场探索了几种途径,但最重要的是集中在两点:调整水的描述,更好地描述折叠蛋白质的无序区域,甚至完全(“内在”)无序的蛋白质。这两个问题其实是纠缠在一起的:

大量水具有非常复杂的特性,很难建模,调整它们有助于(正如 DEShaw Res 和许多其他人所表明的那样)纠正在多蛋白质系统和内在无序蛋白质(它们不采用可明确定义的 3D 结构,但具有很大的生物学相关性)中观察到的问题。我在下面的论文中对此做了全面的研究,包括当时由 DEShaw Res 提出的最新力场:

https://www.sciencedirect.com/science/article/pii/S2001037021001628

化学和生物学的应用

在生物系统的一般应用方面,DEShaw Res 发表了关于药物如何结合其靶蛋白的论文,尤其有趣的是无序蛋白、在蛋白质表面发现可能成为新药靶标的口袋、无序蛋白与其他蛋白的结合,等等。

当然,还有许多与生物学和医学相关蛋白质直接相关的工作。这里只是一小部分选择:

https://www.nature.com/articles/s41467-021-25248-5 https://aacrjournals.org/cancerdiscovery/article/11/7/1716/666533/Exploiting-Allosteric-Properties-of-RAF-and-MEK https://www.nature.com/articles/s41467-018-06632-0

在生物问题的每一个应用中,分子模拟要么提出假设来设计实验,要么解释不能从静态结构中得出的实验结果。这是基于数据的预测工具甚至无法达到的,至少目前是这样。

参考

关于安东 1、2 和 3:

https://dl.acm.org/doi/10.1145/1364782.1364802 https://ieeexplore.ieee.org/document/7012191 https://ieeexplore.ieee.org/abstract/document/9773190 https://dl.acm.org/doi/abs/10.1145/3458817.3487397

www.lucianoabriata.com我写作并拍摄我广泛兴趣范围内的一切事物:自然、科学、技术、编程等等。 成为媒介会员 访问其所有故事(我免费获得小额收入的平台的附属链接)和 订阅获取我的新故事 通过电子邮件 。到 咨询关于小职位 查看我的 服务页面这里 。你可以 这里联系我

使用 Google Colab 为您的作品添加水印的快速简便的方法

原文:https://towardsdatascience.com/a-fast-and-easy-way-to-watermark-your-creations-using-google-colab-d61409265989

这款简单、多功能的笔记本可以帮助您维护图形和人工智能作品的归属。它甚至可以嵌入秘密数据。

“Watermark”带有一个故意可见的水印,由本文中引用的 Colab 笔记本生成。(图片由作者使用稳定扩散)

T o 水印还是不水印,这是个问题。唉,没有简单的答案。你已经花了无数的时间来完善你的数据可视化或艺术作品,以便在网上分享。也许你想帮助建立版权所有权,阻止对你的作品的非授权重用,或者仅仅是为了获得知名度。另一方面,你可能不愿意用难看的文字来玷污你的杰作。

不管怎样,这个笔记本是给你的!这里有一个快速纲要。

前两个代码块将从字体松鼠下载一系列字体,安装你的 Google Drive,并为你想要水印的图片创建目录。然后你会看到这样的形式:

水印笔记本的格式化参数。

1)使用“文件”窗格,将鼠标悬停在 MyDrive/Uploads 中已上传的文件上,然后按下 3 个垂直点。复制路径并将其粘贴到“路径”字段:

如何复制图像路径?

2)在“水印”字段中添加你的名字、化名或秘密信息(稍后会详细介绍)。

3)从下拉菜单中选择一种字体,并输入字体大小。

4)您可以使用“xCoor”和“yCoor”变量来定位水印。点(0,0)位于图像的左上角(不像通常的笛卡尔坐标,正 y 向下)。

5)文本的默认调色板是灰度,由变量“grayVal”决定对于彩色文本,将“textColor”字段中的“grayVal”条目替换为单独的 RGB 值(例如(42,70,99))。

6)设置“不透明度”范围从 0(透明)到 255(不透明)。

7)运行代码块。

就是这样。笔记本会自动下载带水印的图像并预览,这样你就可以在不打开下载图像的情况下进行修改。

使用这个工具,你可以随心所欲地大胆或含蓄。通过低不透明度和明智地使用字体大小和颜色,您的水印实际上是不可见的,但足以建立您的图像的所有权。

例如,假设我为一篇关于千禧一代未来可能租房的文章提供了一个很好的可视化效果(见图 1)。鉴于有关房地产市场趋势的帖子很受欢迎,我认为一旦这些帖子被发布到网上,一些博主(或汽车博主)很可能会“借用”我的见解,并以 50%的正确归因概率来使用这些图片。

所以,我把我的 1600 x 1200 的图片上传到 Google Drive 并运行 watermarker。在快速预览了几个不同的位置后,我决定(852,950)使用 20pt、OpenSans-Regular 字体和 192 的不透明度刚刚好——不显眼,但足以显示我的作者身份。

图一。带水印的图形代表了所选城市千禧年租赁市场的预测受欢迎程度。(图片由作者提供)

事实上,因为你可以像你喜欢的那样微妙,你甚至可以使用笔记本来探索图像隐写术领域——在众目睽睽之下隐藏信息的做法。例如,这里有两个看似相同的图像:

原图。(图片由作者提供)

水印图像。(图片由作者提供)

当我们从带水印的图像(下图)中减去原始图像(上图)并应用伽马校正和二值化时,会发生以下情况:

从上面的水印图像中检索的隐写数据。(图片由作者提供)

所以,继续吧——主张你的知识产权,推广你的品牌,或者只是享受隐写术的乐趣。你可以直接从 GitHub 下载或者打开 Colab 笔记本。尽情享受吧!

如果你喜欢这篇文章,你可以考虑成为 Medium 会员,这样就可以无限制地访问 Medium 上的所有文章。如果你 使用此链接 注册,你也可以支持我成为作家、研究者、创作者。

https://medium.com/@harlan.j.brothers/membership

快速浏览 Spark 结构化流+卡夫卡

原文:https://towardsdatascience.com/a-fast-look-at-spark-structured-streaming-kafka-f0ff64107325

了解如何使用这款强大的 duo 执行流处理任务的基础知识

Unsplash 上由 Nikhita Singhal 拍摄的照片

简介

最*,我开始大量研究 Apache Kafka 和 Apache Spark,这是数据工程领域的两项领先技术。

在过去的几个月里,我用它们做了几个项目;“ 用 Kafka、Debezium、BentoML 进行机器学*串流”就是一个例子。我的重点是学*如何使用这些现代著名工具创建强大的数据管道,并了解它们的优缺点。

在过去的几个月里,我已经讲述了如何使用这两种工具创建 ETL 管道,但是从来没有一起使用过,这就是我今天要填补的空白。

我们的目标是学*使用 Spark+Kafka 构建流应用程序背后的一般思想,并使用真实数据快速浏览其主要概念。

卡夫卡与《一言以蔽之的火花》

这个想法很简单——Apache Kafka 是一个消息流工具,生产者在队列的一端编写消息(称为主题),供另一端的消费者阅读。

但它是一个非常复杂的工具,旨在成为一个弹性的分布式消息服务,具有各种交付保证(一次、一次、任意)、消息存储和消息复制,同时还允许灵活性、可伸缩性和高吞吐量。它有更广泛的用例,如微服务通信、实时事件系统和流式 ETL 管道。

Apache Spark 是一个分布式的基于内存的数据转换引擎。

它也是一个非常复杂的工具,能够连接各种数据库、文件系统和云基础设施。它适合在分布式环境中运行,以在机器之间并行处理,通过使用其惰性评估理念和查询优化来实现高性能转换。

最酷的是,到最后,代码只是您通常的 SQL 查询或(几乎)您的 Python+pandas 脚本,所有的巫术都抽象在一个友好的高级 API 下。

将这两种技术结合起来,我们就可以完美地构建一个流 ETL 管道。

申请

我们将使用来自巴西米纳斯吉拉斯州首府贝洛奥里藏特市交通传感器的数据。这是一个庞大的数据集,包含城市中几个地方的交通流量测量值。每个传感器定期检测在该位置行驶的车辆类型(汽车、摩托车、公共汽车/卡车)、速度和长度(以及其他我们不会使用的信息)。

该数据集精确地代表了流式系统的经典应用之一——一组从现场连续发送读数的传感器。

在这种情况下,Apache Kafka 可以用作传感器和使用其数据的应用程序之间的抽象层。

Kafka 用作源和服务之间的抽象层。图片作者。

有了这种基础设施,就有可能建立各种(所谓的)实时事件驱动系统,就像一个程序,当车辆数量突然增加而平均速度下降时,它可以检测并警告交通堵塞。

这就是 Apache Spark 发挥作用的地方。

它有一个名为 Spark 结构化流的本地流处理模块,可以连接到 Kafka 并处理其消息。

设置环境

你所需要的是 docker 和 docker-compose。

我们将使用基于以下存储库的 docker-compose 文件配置: link sparklink kafka

。/src 卷是我们放置脚本的地方。

要启动环境,只需运行

docker-compose up

所有代码都可以在这个 GitHub 库中找到。

实施

开始研究 Spark 时,我最喜欢的一件事是它的编写代码和我通常使用的 python+pandas 脚本之间的相似性。迁移是非常容易的。

遵循同样的逻辑,spark 的流模块与常见的 Spark 代码非常相似,这使得从批处理应用程序迁移到流应用程序变得很容易。

也就是说,在接下来的几节中,我们将重点学* Spark 结构化流的特性,即它有哪些新功能。

我们的第一份工作

让我们慢慢开始,建立一个玩具的例子

首先要做的是创建一个 Kafka 主题,我们的 spark 作业将从这个主题中消费消息。

这是通过访问 Kafka 容器终端并执行:

kafka-topics.sh --create --bootstrap-server localhost:9092 --topic test_topic

为了模拟一个制作人在这个主题上写消息,让我们使用卡夫卡-控制台-制作人。同样在集装箱内:

kafka-console-producer.sh --bootstrap-server localhost:9092 --topic test_topic --property "parse.key=true" --property "key.separator=:"

从现在开始,在终端中键入的每一行都将作为消息发送到测试主题。字符“:”用于分隔消息的键和值(键:值)。

让我们创造一个火花工作来消费这个话题。

代码需要放在 /src/streaming 文件夹中(没什么特别的,只是我选择的文件夹)。

需要注意的关键是,我们使用属性 readStreamwriteStream,来代替普通的读写。这是 Spark 将我们的工作视为流媒体应用的主要原因。

要连接 Kafka,需要指定服务器和主题。选项starting offsets = "earliest "告诉 Spark 从头开始阅读主题。此外,因为 Kafka 以二进制形式存储它的消息,它们需要被解码成字符串。

将进一步探讨其他选择。

现在,让我们访问 Spark 容器并运行作业。

spark-submit --packages org.apache.spark:spark-sql-kafka-0-10_2.12:3.3.0 /src/streaming/read_test_stream.py

经过几秒钟的配置,它就开始消费话题了。

来自卡夫卡的火花消费信息。图片作者。

Spark Streaming 在微批处理模式下工作,这就是为什么当它使用消息时我们会看到“批处理”信息。

微批处理在某种程度上介于完全“真实”的流和普通的批处理之间,前者所有消息在到达时被单独处理,后者数据保持静态并按需使用。Spark 将等待一段时间,尝试累积消息来一起处理它们,从而减少开销并增加延迟。这可以根据你的需要进行调整。

我打字不是很快,所以 Spark 会先处理消息,然后才能在当前批处理中包含新消息。

这是我们的第一个流媒体工作!

我希望您有这样的感觉:编写一个流处理作业并不难,但是有一些问题。

向 Kafka 流写入数据

现在是时候开始处理传感器数据了。

可以从 2022 年 8 月开始下载 zip 文件,解压到 /data 卷。数据最初是在 JSON 中,需要大约 23Gb 的空间。首先要做的是将其转换为 parquet,以优化磁盘空间和读取时间。

GitHub 资源库中详细介绍了完成这项工作的 spark 作业,您需要做的就是执行它们:

spark-submit /src/transform_json_to_parquet.pyspark-submit /src/join_parquet_files.py

根据您的机器,执行可能需要一些时间。但这是值得的,最终的 parquet 文件大小约为 1Gb(比原来小 20 倍以上),读取速度也快得多。

我们还需要创建 Kafka 主题来接收我们的消息:

kafka-topics.sh --create --replication-factor 1 --bootstrap-server localhost:9092 --topic traffic_sensor

或者,如果您想显示到达的消息,可以设置一个控制台消费者。

kafka-console-consumer.sh --topic traffic_sensor --bootstrap-server localhost:9092

以 Kafka 为主题写数据很容易,但是有一些细节。

在结构化流中,默认行为是不尝试推断数据模式(列及其类型),所以我们需要传递一个。

Kafka 消息只是键值二进制字符串对,所以我们需要用这种格式表示我们的数据。这可以通过将所有行转换为 JSON 字符串、以二进制编码并将结果存储在“value”列中来轻松实现。

将列转换为 JSON 字符串。图片作者。

消息键在 Kafka 中非常重要,但是在我们的测试中没有用,所以所有的消息都有相同的键。

如前所述,这个数据集非常大,所以我将插入的消息数量限制为 500,000。

最后,我们传递 Kafka 服务器和主题以及一个“ checkpointLocation ”,spark 将在其中存储执行进度,这对从错误中恢复很有用。

执行作业:

spark-submit --packages org.apache.spark:spark-sql-kafka-0-10_2.12:3.3.0 /src/streaming/insert_traffic_topic.py

将数据插入卡夫卡。图片作者。

在左边,Spark 作业读取文件,在右边,一个Kafka-控制台-消费者显示到达的消息。

我们的流量主题已经填充完毕,几乎可以处理了。

重要的是要记住,我们使用 spark 作业来填充我们的主题只是出于学*目的。在真实场景中,传感器本身会直接向卡夫卡发送读数。

为了模拟这种动态行为,下面的脚本每 2.5 秒向主题中写入一行。

输出模式——按类型统计车辆数量

接下来,让我们创建一个按类型统计车辆数量的作业。

“分类”栏包含检测到的车辆类型。

当我们阅读主题时,我们需要将 JSON 二进制字符串转换回列格式。

一旦完成,就可以照常构建查询了。有趣的是,查询心脏正好是选择()。分组依据()。计数()序列,其余都是相对于流式逻辑。

所以是时候解决输出模式()选项了。

流应用程序的输出模式指定了当新数据到达时,我们希望如何(重新)计算和写入结果。

它可以假设三个不同的值:

  • 追加:仅向输出添加新记录。
  • 完成:重新计算每个新记录的完整结果。
  • 更新:更新变更记录。

这些模式可能有意义,也可能没有意义,这取决于编写的应用程序。例如,如果执行任何分组或排序,“完整”模式可能没有意义。

让我们在“完成”模式下执行作业,并查看结果。

spark-submit --packages org.apache.spark:spark-sql-kafka-0-10_2.12:3.3.0 /src/streaming/group_by_vehicle_type.py

卡车、汽车、未定义的汽车、公共汽车、摩托车。图片作者。

随着新记录被插入到流中(见右边的终端),作业重新计算完整的结果。这在行排序很重要的情况下很有用,比如排名或竞争。

然而,如果组的数量太大或者单个的改变不影响整体结果,这种方法可能不是最佳的。

因此,另一种选择是使用“更新”输出模式,它只为发生变化的组生成新消息。见下文:

输出模式为“更新”的查询。图片作者。

“append”模式不适用于分组查询,因此我将无法使用相同的作业进行展示。但我认为这是最简单的模式,它总是向输出中添加一条新记录。

如果考虑将结果保存到表中,这些输出模式会更容易理解。在完整输出模式中,对于处理的每个新消息,表都将被重写;在更新模式中,只重写发生了某些更新的行,append 总是在末尾添加一个新行。

翻转时间窗口—使用时间间隔聚合

在流系统中,消息有两个不同的相关时间戳:事件时间(创建消息的时间,在我们的示例中是传感器的读取时间)和处理时间(处理代理读取消息的时间,在我们的示例中是消息到达 Spark 的时间)。

流处理工具的一个重要特性是处理事件时间的能力。滚动窗口是不重叠的固定时间间隔,用于使用事件时间列进行聚合。更简单地说,他们将时间线分割成大小相等的片段,这样每个事件都属于一个时间间隔。

例如,每 5 分钟计算一次,在过去 5 分钟内检测到多少辆车。

5 分钟翻滚窗口。图片作者。

下面的代码说明了这一点:

这种处理在许多情况下非常有用。回到之前提出的交通堵塞检测器,一种可能的方法是测量 10 分钟窗口内车辆的平均速度,并查看它是否低于某个阈值。

排气时间处理是一个复杂的话题。在处理它的时候,任何事情都可能发生,比如消息丢失、到达太晚或者顺序混乱。Spark 有几种机制来尝试缓解这些问题,比如水印,我们不会重点讨论。

时间窗口也可以与 groupBy() 中的其他列结合使用。以下示例按类型统计了 5 分钟窗口内的车辆数量。

滑动时间窗——时间间隔的灵活性

滑动时间窗口是滚动窗口的灵活化。它们允许定义创建每个间隔的频率,而不是创建不重叠的间隔。

例如,每隔 5 分钟,计算在过去 30 分钟内检测到多少车辆。

因此,事件可以属于多个时间间隔,并根据需要进行多次计数。

要定义滑动窗口,只需将更新间隔传递给窗口()函数。

让我们看看输出。

如我们所见,每 5 分钟就有 30 分钟的窗口被创建。

这种灵活性对于定义更具体的业务规则和更复杂的触发器非常有用。例如,我们的交通堵塞检测器可以在过去 10 分钟内每 5 秒钟发送一次响应,并在平均车速低于 20 公里/小时时发出警报

结论

这是对 Spark 结构化流的主要概念以及它们如何应用于 Kafka 的快速浏览。

Apache Kafka 和 Apache Spark 都是可靠和健壮的工具,许多公司使用它们来处理难以置信的大量数据,这使它们成为流处理任务中最强的一对。

我们已经学*了如何使用 Spark jobs 填充、消费和处理 Kafka 主题。这不是一个困难的任务,正如在帖子中提到的,流处理 API 几乎等同于通常的批处理 API,只是做了一些小的调整。

我们还讨论了不同的输出模式,特定于流应用程序的内容,以及每种模式的使用方法。最后但同样重要的是,我们探索了带时间窗的聚合,这是流处理的主要功能之一。

同样,这只是快速浏览,如果你想更深入地探索,我会在下面留下一些参考资料。

希望我有所帮助,感谢您的阅读!😃

参考

所有的代码都在这个 GitHub 资源库 中。
使用的数据—
Contagens volumétricas de Radares公开数据,巴西政府。

[1] 功能深度挖掘:Apache Spark 结构化流中的水印 — Max Fisher 在 Databricks 博客上的文章
[2] Chambers,b .,& Zaharia,M. (2018)。Spark:权威指南:简化大数据处理。奥莱利媒体公司。
【3】与阿帕奇·卡夫卡 https://docs.ksqldb.io/en/latest/tutorials/etl/实时物流、海运、运输——凯·沃纳
【4】以阿帕奇·卡夫卡为特色的网飞工作室和金融世界——汇流博客
【5】星火流媒体&卡夫卡——https://sparkbyexamples.com/

对地理空间数据进行网格化和重采样的几个原因

原文:https://towardsdatascience.com/a-few-reasons-to-grid-and-resample-your-geospatial-data-193105ab7936

一个快速的 5 分钟 Python 教程,展示了一个真实的环境应用

作者图片

介绍

优先采样的地理空间数据的最大问题之一是每个点所代表的不同区域或体积。我以前写过处理优先采样的地理空间数据时的一些挑战,以及如何通过去聚类来克服这些挑战,如果您感兴趣,可以查看下面的文章:

实施去聚类之类的地统计方法可能有些繁琐,因此在本文中,我们将重点关注一种更快、更简单的解决优先采样地理空间的方法。在这里,我们将通过将数据重采样到规则间隔的网格中来解决有偏采样。下面的示意图说明了这种方法,并展示了网格如何帮助我们克服聚类数据。

图解说明了对数据进行网格化和重采样的一般方法,以便网格中的每个块代表相同的区域

虽然规则间距数据解决了优先采样问题,但引入空间格网也带来了许多问题,例如格网间距、块大小和插值方法,这些问题可以基于操作约束进行定义,也可以使用标准地统计方法进行优化求解。

下面是一个环境应用的例子。合成数据集由分散在 2D 空间的 153 个砷污染地下水样品组成,测量单位为微克/升。世界卫生组织(世卫组织)指南规定饮用水的砷浓度不应超过 10 μg/L,因此我们将分析数据集,并验证平均砷浓度低于健康限值,以便安全饮用。我们将使用的所有代码和合成砷数据集都可以在 github 上获得。

首先浏览数据并建立网格

我们可以从使用pandas加载数据和使用matplotlib可视化数据开始。数据集非常简单,只有三列:东坐标(米)、北坐标(米)和砷浓度(μg/L)。我们可以快速可视化数据,以确认我们进行了优先采样。正如我们在下图中看到的,高砷浓度在西北和东南方向存在明显的高采样偏差。

显示西北和东南高砷浓度区域优先采样数据的两个清晰聚类的数据空间图

接下来,我们需要定义一个网格,网格的一个简单实用的块大小可以是 100 x 100m,这足够小,可以保留数据中更好的细节,并突出显示感兴趣的区域以供进一步研究。将块大小增加太多会导致很大程度的平滑,并且很难在每个大块中精确定位感兴趣的区域。太小,例如 10×10 米的区块,会使网格难以操作,并且不会为使用原始数据提供任何显著的好处。

使用numpy,我们能够很容易地在我们的站点上生成一个网格,如我们在 github 上的代码所示。我们可以用matplotlib做一个快速的视觉检查,以确保网格的行为符合我们的预期:

数据的空间图,规则的块网格重叠绘制

接下来,我们需要决定如何对网格中的每个区块进行插值并分配砷浓度。

反距离加权插值法

插值空间数据的一种非常简单而实用的方法是对指定半径内的所有样本进行反距离加权。顾名思义,这只是对定义的半径内到估计点的所有样本进行距离加权平均。在这种情况下,我们的估计点将是块的质心,即块的中心。反距离加权公式如下所示:

反距离加权公式

一种诱人的方法可能是简单地取每个块中所有样本的平均值,但是这仍然会受到优先采样的影响,因为没有考虑数据的空间分布。下面的示意图比较了一个 100 x 100m 的块和四个不同浓度的砷样品以及到块中心的距离。平坦平均值低于 10 μg/L 砷浓度限值,而反距离加权平均值高于限值,这凸显了正确内插空间数据的重要性,因为危险区块可能会被遗漏,或者安全区块会被错误地标记为危险区块。

比较同一 100×100m 区块内四个砷样品的空间平均值,取区块内样品的平平均值和反距离加权(IDW)平均值,

我们可以很容易地使用样本数据和块坐标进行反距离加权。我们将使用 80 米的半径,这样我们可以获取块中的每个样本,并在多个块之间的边界共享样本。我们可以用scipy.spatial's cKDTree和一个简单的for loop给每个块的质心。

最后,我们可以可视化我们的网格,并产生有价值的见解,为进一步的行动提供信息。

使用网格数据通知进一步的行动

让我们从绘制网格开始,并将其与重叠的样本进行比较。下图显示了对格网进行空间插值是如何平滑数据的,但现在高砷和低砷区域在整个站点区域中的分布更加均匀。注意,有 14 个区块没有指定砷浓度,因为在 80 米搜索半径内没有样本。

有和没有样品覆盖的网格砷浓度区块的空间图

通过简单地过滤高于和低于 10 g/L 砷浓度的块,我们可以突出关注区域和安全区域。只绘制样本位置也很方便,因为我们可以看到所有样本是从哪里采集的。下图比较了网格块数据和原始样本数据的简单分析。当我们只分析高于安全限制的数据百分比时,我们会立即看到差异,因为只有 19%的数据块高于限制,而 59%的样本高于限制。获取块的全球平均砷浓度(8.9 微克/升)也将更加可靠,因为每个块代表相同的物理空间量,而样本(12.6 微克/升)给出有偏差的结果。

显示高于和低于砷浓度限值的块(左上)和样品(右上)的图。下面是超出或低于限制的块(左下)和样本(右下)的百分比汇总。红色代表数据超出限值,绿色代表数据低于限值。

另一个潜在有用的检查是多少点被分配给每个块。在 80 米搜索半径内具有更多样本的块比具有较少样本的块更不容易出错。将这一点可视化可以为未来在没有样本但邻*砷浓度高得惊人的地区进行采样提供信息。下图显示了在缺少数据但受西北和东南两个高砷浓度集群限制的区域中部进行额外采样的最大益处。

密度图显示每个区块中的样本数量,并突出显示没有任何样本的区域

摘要

地理空间数据只是空间中的点,用于测量任何种类的变量。通常不规则间隔的点分散在表示面积或体积的物理空间中,因此需要仔细处理和分析它们。考虑数据的空间分布是至关重要的,特别是如果有任何优先采样会导致任何统计测量中的偏差。

最终,有许多方法来处理优先采样的地理空间数据。创建规则格网并将样本空间插值到块质心是生成更能代表研究区域的块的一种快速简便的方法。

只需几行额外的代码,就可以对分散在 3D 空间中的样本执行相同的网格化和可视化方法。使用 3D 网格更加有趣,因为有很多方法可以将数据可视化,从而向最终用户传达可操作的信息。这个故事我们改天再说。

IBM 量子开放科学价格挑战一瞥

原文:https://towardsdatascience.com/a-first-glimpse-at-ibms-quantum-open-science-price-challenge-de4a2f41987e

看到实际的问题是什么是令人惊讶的

量子机器学*要不要入门?看看 动手量子机器学*用 Python

IBM 刚刚公布了其第二届 量子开放科学奖 他们要求解答一个量子模拟问题。他们希望我们在 IBM Quantum 的 7 量子位 Jakarta 系统上使用 Trotterization 模拟一个三粒子系统的海森堡模型哈密顿量。

尽管 IBM 解释了什么是海森堡模型哈密顿量和量子化,但这一切都显得神秘,除非你是物理学家或量子计算资深人士。

幸运的是,他们还提供了他们所期望的工作代码示例。所以,让我们看看我们计算机科学家能从这个挑战中学到什么。

首先,你需要一个 Jupyter 工作环境(见我的上一篇)。二、下载 Jupyter 笔记本(来源)。我们正在制作它的副本,因为我们将做一些修改。你可以在这里找到我的。

所以,让我们简单地看一下代码。首先,我去掉了任何不必要的东西,比如第一节中的经典解释。

所以,我们从一些进口开始。在第 7 行,我添加了Aer包。

原始代码试图在下一步连接到您的 IBM 帐户和 Jakarta 后端。我们跳过这一步,直接从下面几个定义自定义 tterization gate 的单元格开始。值得注意的是,这些单元格被注释“您的 the 化在此处开始—开始(示例的开始)”和“完成(示例的结束)”所包围

显然,IBM 希望我们用我们的解决方案替换这一部分。但是,现在,让我们坚持他们的例子。

我们还没有研究量子化的细节,但是我们继续讨论量子电路。在下面的代码中,我们定义了整个量子电路(qc)并生成了状态层析电路来评估模拟的保真度。保真度是我们旨在优化的绩效得分。保真度范围从 0.0 到 1.0,1.0 是我们能达到的最好结果。

最后,上面的代码显示了我们的电路图。

作者图片

我们的三重门在图中出现了四次。这就是我们在第 5 行中定义的——trotter_steps。评论上说可以大于四。让我们记住这一点,因为这是我们可以使用的一个参数。

我们几乎准备好执行电路。我们只需要弥补没有早点设置后端。所以,我们使用Aer包,让我们选择一个本地模拟后端。具体来说,我们使用qasm_simulator——一个无声的模拟后端。

当你运行这个单元时,你会得到这样的输出。

Job ID 58e33e7a-b539-4275-bd7c-f349f74635e1
Job ID 6fd2d9bc-a945-452b-8f14-233396574582
Job ID 3a09070b-b4e6-4441-b433-32ad7d1362ee
Job ID 6c548328-42e8-4821-8133-256ef596adb1
Job ID 74bad2f3-d821-4d55-bf12-efd884e3ec43
Job ID 577b6965-1dbd-4728-ae91-c4c88868ec53
Job ID 8f5bcc1a-df17-4ea0-952b-a936dce8b921
Job ID 260e575d-6be6-4b3a-81ca-195ab78bccd7

我们看到我们执行了八次作业,这是复制的次数。

最后,我们来评估一下电路。

state tomography fidelity = 0.0003 ± 0.0002

我们看到一个毁灭性的国家层析成像保真度几乎为 0。

那么,让我们看看我们能轻松地做些什么。因为我们将运行和执行代码几次,所以让我们编写一个包装器函数。函数run_and_evaluate_with_steps将遍历的次数stepsbackend作为参数。

我们现在可以用不同数量的 trotter 步骤运行整个代码。我们用八步试试吧。

state tomography fidelity = 0.8543 ± 0.0013

保真度不算太差吧?所以,如果增加快步走的步数有如此显著的效果,为什么我们不尝试更多的步数呢?

state tomography fidelity = 0.9687 ± 0.0006

十二个小跑步,保真度差不多 0.97。那几乎是完美的。因此,示例代码似乎工作得很好。那么,问题出在哪里?

IBM 要求我们在他们的雅加达设备上运行电路。到目前为止,我们模拟了一台完美的量子计算机。但是我们知道实际的量子计算机噪音很大,容易出错。因此,我们来模拟一个实际设备。Qiskit 提供了test.mock包,该包提供了对应于真实设备行为的模拟器,比如FakeJakarta

state tomography fidelity = 0.1442 ± 0.0021

当我们在 Jakarta 设备的噪声模拟上以 12 个快步运行代码时,保真度下降到 0.14。

你可能想玩快步数。但是,你不会得到比 0.2 更好的保真度。

结论

所以,实际的问题不是用 Trotterization 来模拟一个三粒子系统的海森堡模型哈密顿量。相反,问题是要在一个真正的 7 量子位设备上做这件事。

当我们观察示例性的 trotterization 门时,我们可以看到它只使用了三个量子位。其他四个量子位仍未使用。实际的挑战是使用这额外的四个量子位来使电路免受噪声和误差的影响。

量子机器学*要不要入门?看看 动手量子机器学*用 Python

在这里免费获得前三章。

AWS Trainium 初探

原文:https://towardsdatascience.com/a-first-look-at-aws-trainium-1e0605071970

利用专用 DNN 训练芯片的力量—第 3 部分

Balazs Busznyak 在 Unsplash 上的照片

万众期待亚马逊 EC2 TRN1 实例现已可供公众使用。基于 AWS Trainium 芯片,TRN1 实例类型是专门为加速深度学*训练而设计的。本文是在专用人工智能芯片上训练深度学*模型系列的第三部分。在我们之前的帖子中,我们报道了谷歌的云 TPU亚马逊的 EC2 DL1 (由 Habana Gaudi 加速器驱动)。与其他定制人工智能芯片一样,Trainium 提供了显著节约成本的潜力(根据文档高达 50%)。然而,与其他定制的人工智能芯片类似,并不是所有的型号都适合 Trainium,有些型号可能需要调整以实现潜在的节省。在本帖中,我们将评估 TRN1 实例,展示其一些独特的属性,并讨论其使用带来的一些挑战。正如在我们以前的帖子中,我们将把修改 TRN1 用于模型训练的任务分成四个步骤。关于这些步骤的更多细节,见这里

  1. 高级兼容性分析:尽早评估您的工作负载特性是否符合芯片规格和支持软件堆栈。
  2. 调整您的模型以在新芯片上运行:您可能需要对您的模型进行一些调整,例如替换专用人工智能芯片不支持的操作。
  3. 优化新芯片上的运行时性能:为了充分利用芯片,您将需要分析并最大化其利用率。
  4. 调整模型以在新芯片上收敛:为了确保及时收敛,可能需要对模型超参数进行一些修改。

AWS Trainium 上的训练由 AWS Neuron SDK 启用。这篇博文和我们包含的代码片段是基于撰写本文时可用的最新软件栈,版本 2.4

鉴于 Neuron SDK 产品的相对新颖性,新版本可能会包括重要的增强和优化。重要的是,您要使用可用软件栈的最新版本并确保相应地重新评估我们所做的一些陈述。虽然我们会强调,我们所做的某些陈述是真实的在撰写本文时,同样的资格应该适用于本文中的所有内容。我们将重点介绍 Neuron SDK 的 PyTorch 产品(版本 1.11)。然而,我们写的大部分内容对于运行在 Trainium 上的其他机器学*框架来说都是相关的。

在进入正题之前,关于 TRN1 实例产品的类型,我们想强调一个重要的特性。

TRN1 实例类型产品

AWS 提供了两种 TRN1 实例类型,带有单个 Trainium 芯片的 trn1.2xlarge 和带有 16 个 Trainium 芯片的 trn1.32xlarge。(每个 Trainium 芯片由两个内核组成,称为https://awsdocs-neuron.readthedocs-hosted.com/en/latest/general/arch/neuron-hardware/neuroncores-arch.html。)这种双重提供并不是理所当然的,因为基于其他核心架构的实例类型有时仅提供单个多芯片选项。例如,在撰写本文时,亚马逊 EC2 p4d 家族只包括带有八个NVIDIA A100GPU 的实例。单芯片 trn1.2xlarge 设计的可用性有两个主要影响。最明显的一点是它适用于较小的培训工作量,对于这些工作量,32 核解决方案是不必要或不可取的。第二,同样重要的含义与我们的讨论有关:较小的 trn1.2xlarge 实例是评估、调整和调整我们的 Trainium 芯片模型的完美环境。当我们调整我们的模型在 NVIDIA A100 GPUs 上运行时,我们别无选择,只能在一个昂贵的八 GPU 实例上这样做,即使单个 GPU 已经足够了。相比之下,使用 Trainium,我们可以在迁移到一个或多个更大的实例进行全面培训之前,在更便宜的单芯片实例上进行大量的分析、实验和探索。

1.高级兼容性评估

第一步是收集尽可能多的公开信息,以评估 Trainium 产品是否满足了您的培训需求。在此评估过程中,区分 Trainium 硬件的功能和 Neuron SDK 支持的功能非常重要。正如我们将看到的,Trainium 支持许多功能、特性和操作,在撰写本文时,这些功能、特性和操作还没有被支持的软件栈公开。如果您的 ML 项目需要其中的一个,那么您可能希望推迟您的 Trainium 评估,并在公开的 AWS Neuron Roadmap 页面上跟踪软件堆栈的开发。

兼容性评估的主要资源应该是官方的 AWS Neuron 文档。AWS Neuron SDK 及其附带的文档同时支持 AWS Trainium 和 AWS Inferentia ,其中许多功能只支持一种,而不支持另一种(在撰写本文时)。文档的每一页都包含一个标签,说明它是否与 Trn1、Inf1 或两者相关。请仔细注意这些标签。

模型兼容性

模型架构匹配部分开始是一个不错的地方。在这里,您可以找到一个,总结了 Trainium 硬件和当前软件堆栈支持许多流行型号的程度。对于硬件更详细的回顾,请查看 Trainium 架构NeuronCore 架构页面。这些应该让您了解硬件的训练能力,以及内存、计算和其他硬件资源是否满足您的硬件需求。接下来,查看软件文档,验证支持的框架、版本和操作是否满足您的软件需求。Neuron SDK 发行说明将为您提供当前软件支持的概述。SDK 支持几种流行的培训框架,尽管支持的程度有很大的不同(在撰写本文时)。在这篇文章中,我们将关注 PyTorch 支持。请务必查看 PyTorch 开发人员指南以了解 PyTorch 使用模式的概述以及支持的 PyTorch 操作员列表。您可以在 PyTorch Neuron 发行说明中跟踪 PyTorch 相关的 SDK 更新。另外,请务必查看 Neuron github 资源库中的示例。

性能基准

TRN1 性能页面为许多流行的深度学*模型提供了性能基准。这些可以与其他人工智能加速器(例如英伟达 GPU )的公开性能数据进行比较,以获得 Trainium 潜在节省机会的大致想法。公共基准列表仅限于 NLP 模型(在撰写本文时)。当试图根据这些结果预测其他模型的性能时,建议谨慎,因为即使模型或训练环境的微小变化也会对性能产生相当大的影响。我们建议在做出任何决定之前运行您自己的性能比较测试。

MLPerf 是一个流行的人工智能训练基准套件,用于比较多个人工智能加速器的性能。然而,在撰写本文时,最新的结果不包括基于 Trainium 的提交。

第一印象

在下面的要点中,我们将总结一些我们个人对当前 TRN1 产品的印象。该列表并不全面,也不应被视为官方文档的替代品。

  • 异构架构:每个 NeuronCore 结合了四个计算引擎(张量/矢量/标量/GPSIMD)的能力,使其能够在各种工作负载上达到高效率。
  • 高规模数据分布式训练:架构设计,包括用于节点间连接的 NeuronLink 和用于节点内连接的 EFA 支持,允许 Trainium 在扩展到高度分布式训练展示接*线性的结果。
  • 框架支持:Neuron SDK 的当前版本( 2.4 )支持使用 PyTorch 框架在 Trainium 上进行训练。TensorFlow 支持正在开发中。Neuron SDK PyTorch 支持通过 PyTorch/XLA 库公开,其中每个 NeuronCore 都是一个 XLA 设备。使用 PyTorch/XLA 而不是标准 PyTorch 框架有许多含义;其中最值得注意的是使用懒惰张量而不是渴望张量。在之前的一篇文章中,我们对 PyTorch/XLA 的训练主题进行了扩展,并指出它的一些优点和局限性。PyTorch/XLA API 是由 torch-neuronx PyThon 包实现的,在撰写本文时,该包与 PyTorch/XLA API 的 1.11 版本保持一致。
  • 操作员支持:Neuron SDK 没有实现 py torch/XLA API 和使用流程的完整集合。事实上,有许多流行的操作符(如 conv 和排序)、优化器(如 LAMB )、模型(如 FP16 )和基本类型(如FP16)正在等待支持。
  • 自定义内核创建:train ium 芯片的优势之一是它支持创建自定义 C++操作符。与用于 GPU 内核开发的 CUDA toolkit 类似,该特性使用户能够设计、开发和优化专门针对其工作负载需求的低级操作。然而,在撰写本文时,Neuron SW 栈还没有公开这项功能。
  • 内存池:虽然每个 Trainium 芯片包含 32GB,但所有芯片的内存都被集中在一起(参见这里的)。特别是,这意味着您可以选择启用可用 Trainium 芯片的子集,但仍然利用所有芯片的内存。例如,在 trn1.32xlarge 上,您可以选择在 32 个每个 16GB 的工作线程、8 个每个 64GB 的工作线程、2 个 256GB 的工作线程或一个全部 512GB 的工作线程之间分配您的模型(参见此处的)。但是,您应该仔细权衡放弃使用任何芯片的选择,并尽可能选择其他替代方案(例如模型分片)。
  • 模型分布:模型分布是一种常用的技术,用于训练一个非常大的模型,它不适合单个工人分配的内存。模型分布有许多不同的策略,包括张量并行、流水线并行和完全分片数据并行(FSDP)。Neuron SDK 使用 Megatron-LM 库支持张量并行。然而,对其他策略的支持,尤其是 FSDP 的支持还未确定。(有关模型分布式培训策略的简要概述,请查看我们最*的帖子。)
  • 托管培训支持:tr n1 实例家族由亚马逊托管培训服务亚马逊 SageMaker支持。Amazon SageMaker 为机器学*模型开发提供了许多便利,包括管理训练实例的设置和配置,以及训练完成时它们的自动终止。这在多个节点上训练时特别有用,除了设置单个实例,还需要配置节点间连接。

虽然在线文档可以提供 Trainium 产品的一个很好的总体概念,但是除了开始使用它之外,没有更好的方法来获得对其价值的真实感受。

2.调整您的模型以在 TRN1 上运行

在本节中,我们将回顾在 TRN1 实例上启动和运行 PyTorch 模型所需的一些步骤。更多详情请看官方 Neuron SDK PyTorch 文档

TRN1 系统设置

设置 TRN1 PyTorch 环境有许多选项,包括, Amazon EC2AWS ParallelClusterAmazon SageMaker 。最直接的方式,也是获得 TRN1 初步感觉的最佳方式,是用最新的 AWS 深度学* AMI 建立一个 Amazon EC2 trn1.2xlarge 训练实例,如这里所记录的

适应培训流程

修改您的脚本以在 TRN1 上运行的第一步是使其适应 PyTorch/XLA 。所需的适配在 SDK 文档中有详细说明,并且与任何其他类型的 XLA 设备(如 TPU、GPU 或 CPU)相同。在某些情况下,这可能就是在 Trainium 上训练模型所需的全部内容。如果是这样,数一数你的幸运。如果你没有那么幸运,你的模型编译将会失败。请注意,与其他一些加速器(包括 GPU)相反,不受支持的操作将而不是自动卸载到 CPU。请参见 PyTorch Neuron 故障排除指南了解您可能会看到的错误类型。

以下是一些可能需要进行的更改的示例:

替换不支持的数据类型:您的模型可能包含 Trainium 芯片或 Neuron 编译器不支持的数据类型(参见此处的)。在这种情况下,您可能需要调整模型以适应不同基类型的使用。

替换不支持的操作:如果您的型号包含不支持的操作,您将需要进行调整以替换它们。下面,我们将分享一个模型示例,其中我们用逐位精确交替流替换了 conv 层的使用。当然,这并不总是可能的。将来你将能够为缺失的操作符创建自定义内核,但是现在还不支持。

移除具有动态形状的张量:在撰写本文时,对具有动态形状的张量的的支持正在进行中。在之前的帖子中,我们展示了一个如何用包含固定大小张量的位精确替换来替换布尔掩码操作的例子。

多核培训

无论你运行的是 trn1.2xlarge 还是 trn1.32xlarge,你都应该努力最大化的利用所有的神经元。这可以通过在单个内核上运行并行实验或者在多个内核上运行数据分布式训练来实现。参见 Neuron 文档获取关于扩展您的脚本以支持数据分发的说明。

示例 TRN1 上的视觉变压器

在下面的代码块中,我们使用 timm Python 包(版本 0.6.11)构建了一个基本的视觉转换器 (ViT)模型。默认 ViT 的补丁嵌入部分包括一个 conv 层,它没有出现在列表支持的操作符中。幸运的是, ViT 构造函数包含了一个传递补丁嵌入逻辑的选项,使我们能够替换它,这是一个有点精确的无 conv的选择。(实际上,尽管不在支持的运营商列表中,conv 层运行在 TRN1 上。然而,在撰写本文时,它的性能不如我们下面提出的无 conv 选项好。)

*import torch

def build_model():
  from timm.models.vision_transformer import VisionTransformer
  from torch import nn as nn
  from collections.abc import Iterable

  class LinearEmbed(nn.Module):
    def __init__(self, img_size=224, patch_size=16, in_chans=3, 
                       embed_dim=768, norm_layer=None, bias=True):
      super().__init__()
      img_size = img_size if isinstance(img_size, Iterable)\
         else (img_size, img_size)
      patch_size = patch_size if isinstance(patch_size, Iterable)\
         else (patch_size, patch_size)
      self.img_size = img_size
      self.patch_size = patch_size
      self.grid_size = (img_size[0] // patch_size[0],
                        img_size[1] // patch_size[1])
      self.num_patches = self.grid_size[0] * self.grid_size[1]
      self.lin = nn.Linear(patch_size[0] * patch_size[1] * in_chans,
                           embed_dim, bias=bias)
      self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()

    def forward(self, x):
      B, C, H, W = x.shape
      NH = H // self.patch_size[0]
      NW = W // self.patch_size[1]
      x = x.view(B, C, NH, self.patch_size[0], NW, self.patch_size[1]). \
           transpose(3, 4). \
           reshape([B, C, NH * NW, 
                    self.patch_size[0] * self.patch_size[1]]). \
           transpose(1, 2). \
           reshape([B, NH * NW, 
                    self.patch_size[0] * self.patch_size[1] * C])
      x = self.lin(x)
      x = self.norm(x)
      return x

  model_args = {
      "embed_layer": LinearEmbed,
  }

  return VisionTransformer(**model_args)*

在下面的代码块中,我们配置脚本来运行数据分布,将 ViT 模型加载到神经元 XLA 设备,并在一个假数据集上训练 500 步。

*from torch.utils.data import Dataset
import time, os
import torch
import torch_xla.core.xla_model as xm
import torch_xla.distributed.parallel_loader as pl

# use a fake dataset (random data)
class FakeDataset(Dataset):
  def __len__(self):
    return 1000000

  def __getitem__(self, index):
    rand_image = torch.randn([3, 224, 224], dtype=torch.float32)
    label = torch.tensor(data=[index % 1000], dtype=torch.int64)
    return rand_image, label

def train():
  # Initialize XLA process group for torchrun
  import torch_xla.distributed.xla_backend
  torch.distributed.init_process_group('xla')

  # multi-processing: ensure each worker has same initial weights
  torch.manual_seed(0)

  dataset = FakeDataset()
  model = build_model()

  # load model to XLA device  
  device = xm.xla_device()
  model = model.to(device)

  batch_size = 32
  optimizer = torch.optim.Adam(model.parameters())
  data_loader = torch.utils.data.DataLoader(dataset,
                          batch_size=batch_size, num_workers=4)

  data_loader = pl.MpDeviceLoader(data_loader, device)
  loss_function = torch.nn.CrossEntropyLoss()
  t0 = time.perf_counter()
  summ = 0
  count = 0
  for idx, (inputs, target) in enumerate(data_loader, start=1):
    inputs = inputs.to(device)
    targets = torch.squeeze(target.to(device), -1)
    optimizer.zero_grad()
    outputs = model(inputs)
    loss = loss_function(outputs, targets)
    loss.backward()
    xm.optimizer_step(optimizer)
    batch_time = time.perf_counter() - t0
    print(f'step: {idx}: step time is {batch_time}')
    if idx > 10:  # skip first steps
      summ += batch_time
      count += 1
      t0 = time.perf_counter()
    if idx > 500:
      break

  print(f'average step time: {summ/count}')

if __name__ == '__main__':
  os.environ['XLA_USE_BF16'] = '1'
  train()

# Initialization command:
# torchrun --nproc_per_node=2 python train.py*

另请参见神经元训练示例的库中的拥抱脸 ViT 模型(T2 包含 conv 层)。

3.优化 TRN1 上的运行时性能

如果您已经成功到达这里,这意味着您已经成功地在 TRN1 上运行了您的脚本。然而,可能需要额外的步骤来获得芯片的最佳性能。正如我们之前提到的,人工智能芯片的好坏取决于它提供的性能分析和优化工具。除非你能够分析和优化运行时性能,否则你将无法充分利用人工智能芯片。在这一节中,我们将回顾一些您可以使用的技巧和工具,用于监控 TRN1 资源利用、识别性能瓶颈以及优化培训工作量。有关性能分析重要性的更多信息,请参见此处的。

监控资源利用

neuron-top 实用程序是一个很好的工具,可以初步了解系统资源的利用情况。该工具提供了有关内存利用率、NeuronCore 利用率和 vCPU 利用率的基本信息。这些可以用来识别基本的性能问题,例如:一个或多个空闲的神经元、CPU 瓶颈或未充分利用的系统内存。

neuron-top 命令的输出(来源: AWS Neuron SDK 文档

使用神经元监视器工具可以获得系统资源利用率(每个应用程序)的更详细报告。参见本教程中关于如何在培训课程中提取和监控系统指标的示例。

Graphana 仪表板上的 Trainium 资源利用情况(来源: AWS Neuron SDK 文档

剖析性能

PyTorch/XLA 故障排除指南列出了测量应用性能的工具。这些包括生成和分析系统指标使用张量板进行剖析。TensorBoard profiler 是一个非常有用的工具,可以识别和解决应用程序中的瓶颈。在前一篇文章中,我们详细回顾了 profiler 报告的不同部分以及如何使用它们。

TensorBoard profiler 跟踪视图(来源: AWS Neuron SDK 文档

TRN1 培训的优化技巧

为了获得最佳性能,请确保遵循 SDK 定义的最佳实践。Neuron 支持 bfloat16 以及自动混合精度。这种方法既可以减少模型的内存占用,又可以提高步长时间性能。但是,请确保验证这些方法不会影响您的模型收敛。参见此处了解不同神经元浮点类型及其性能权衡的更多细节。

优化模型编译

当使用 PYTorch/XLA 进行训练时,机器学*模型被编译成针对底层 XLA 加速器进行优化的执行图。模型编译会给培训流程带来相当大的开销,最佳实践是尽量减少所需的编译数量。编译开销的一个常见症状是最初的几个训练步骤花费了相对较长的时间(与后续的训练步骤和标准 PyTorch 训练相比)。这种开销随着模型的大小而增加。Neuron SDK 包括用于减少这种开销的Neuron _ parallel _ compile

另一项技术是预加载 Neuron 编译器缓存。如果您使用相同的模型架构和超参数在多个实例上运行多个实验,那么您可以只编译一次模型并简单地复制缓存,而不是为每个实验重新编译模型。下面的代码块演示了如何保存和加载编译器缓存,从而避免编译的开销。

*import tarfile
import boto3
def save_cache():
  if xm.get_local_ordinal() == 0:
    # create tarball from cache
    tar = tarfile.open('/var/tmp/neuron-compile-cache.tar', "w")
    tar.add('/var/tmp/neuron-compile-cache', 'neuron-compile-cache')
    tar.close()
    s3_client = boto3.client("s3")
    s3_client.upload_file(Filename='/var/tmp/neuron-compile-cache.tar', 
                          Bucket=<s3 bucket>, 
                          Key=f'{<path-pref>}/neuron-compile-cache.tar')

def pull_cache(): # should be called after initializtion dist object
  if xm.get_local_ordinal() == 0:
    s3_client = boto3.client("s3")
    s3_client.download_file(Bucket=<s3 bucket>, 
                            Key=f'{<path-pref>}/neuron-compile-cache.tar')
                            Filename='/tmp/neuron-compile-cache.tar')
    with tarfile.open('/tmp/neuron-compile-cache.tar', 'r') as f:
      f.extractall('/var/tmp/')
  xm.rendezvous('sync after pulling cache')*

4.调整您的模型以收敛于 TRN1

至此,您的模型已经过调整和优化,达到了您的满意程度。你现在可以开始训练了。您可能需要对模型进行一些更改,这需要重新调整超参数以确保模型收敛。此类更改可能包括替换某些操作、更改控制流或更改底层数据类型。即使你没有对你的模型做任何改变,你也应该确保你的训练收敛在新的 AI ASIC 上。这是因为不同的硬件加速器以不同的方式实现,并且可能在它们的行为中表现出微小的数值差异。在一个 ASIC 上的收敛并不保证在另一个上的收敛。

有许多资源可供您调试和监控您的训练行为。SDK 提供了关于打印张量的指导,使您能够调试图形中的中间输出。或者,您可以在急切调试模式下运行,其中每个操作都被编译并立即执行,允许您在不同阶段检查模型,就像在标准 PyTorch 中一样。要监控训练进度,您可以按照 SDK 的说明将指标记录到 TensorBoard

结果

在下表中,我们显示了我们的 ViT 模型在不同实例类型上的运行时性能。成本取自亚马逊 EC2 产品详情中的 p4g5trn1 。同样的测试也可以在亚马逊 SageMaker 上进行。(参见此处了解 SageMaker 定价详情。)

ViT 型号的性能比较(越低越好)—作者

在 dual-NeuronCore trn1.2xlarge 实例类型上观察到了最佳的性价比。然而,该模型目前的形式并没有很好地扩展:当移动到 trn1.32xlarge 时,步进时间增加了大约 27%。请注意,这些比较结果非常依赖于模型细节,并且很可能在 ML 项目中有很大差异。此外,鉴于 SW 堆栈的不断改进,这些结果可能会因 Neuron SDK 版本而异。

摘要

随着 Trainium 的发布,AWS 继续扩展其专用培训实例组合,为客户提供更多种类和成本优化机会。TRN1 实例家族特别有趣,因为它的设计是为深度学*量身定制的。同时,由于硬件体系结构和支持软件体系结构的新颖性,使用 Trainium 时应保持适当的心态。达到最佳结果可能需要耐心和韧性。但是,希望回报会超过努力。套用一句流行语录 : “最好的旅程始于乘坐 AWS 列车”

这篇文章仅仅介绍了 TRN1 实例系列培训的几个方面。请务必参考丰富的在线文档以了解更多详细信息。

Amazon SageMaker 项目的灵活的端到端模板

原文:https://towardsdatascience.com/a-flexible-end-to-end-template-for-amazon-sagemaker-projects-72d750b6933

如何为 Amazon SageMaker 项目使用新的“ shapemaker ”模板

shapemaker 的标志。资料来源:Lars Kjeldgaard。

在本文中,我将介绍如何使用新的“ shapemaker ”模板来创建具有最大灵活性的 Amazon SageMaker 项目。

本文的目标读者是对 python、Amazon SageMaker 以及 AWS、docker、shell 脚本和 web 应用程序开发具有中级知识的全栈数据科学家。

您为什么要关心?
Amazon SageMaker 管理 TensorFlow、PyTorch 和 HuggingFace 等流行框架的容器,您可以使用开箱即用来创建模型训练作业和用于推理的端点。这允许开发人员只关注提供训练和推理脚本(即在脚本模式下工作)。

然而,根据我在实际生产环境中的经验,通常情况是,这种方法没有提供足够的灵活性:可能(SageMaker 的现有容器不支持您正在使用的框架,(2)您需要定制培训作业容器,或者(3)您需要定制端点容器(或者如何提供服务)。

解决方案:自带容器 为了解决这个问题,SageMaker 提供了一个名为自带容器 (BYOC)的功能,提供完全的开发者控制。顾名思义,BYOC 意味着您可以为训练作业和推断端点使用 SageMaker 自己的容器,然后您可以通过 SageMaker API 使用这些容器。

乍看之下,与亚马逊 Sagemaker BYOC 合作似乎有点拗口。尤其是如果你*惯于在脚本模式下工作。

为了让 BYOC 更容易理解,我为 Amazon SageMaker 项目打造了一个模板,实现了 BYOC。

推出“shape maker” [shapemaker](https://github.com/smaakage85/shapemaker)是亚马逊 SageMaker AWS 项目的完整端到端模板,旨在实现最大的灵活性。它建立在 BYOC SageMaker 功能的基础上,实现了开发者的完全控制。

该模板包括:

  • 模型代码的极简模板
  • 用于模型训练的 docker 图像模板
  • 一种用于实时推理的端点标记图像模板
  • 用于与模型/端点交互的命令行功能
  • 用于交付和集成带有 SageMaker 的模型的命令行功能
  • 持续集成/交付工作流。

‘shape maker’支持 Linux/macOS。

‘shape maker’一览

您可以使用cookiecutter:‘shape maker’模板创建一个新项目

cookiecutter gh:smaakage85/shapemaker

下面的视频快速介绍了“shape maker”模板的一些最重要的功能以及如何使用它:它介绍了如何从模板创建项目,以及如何使用其内置命令行功能来构建培训和端点图像,以及创建培训作业和端点。此外,我还展示了如何启用[shapemaker](https://github.com/smaakage85/shapemaker) CI/CD 工作流。

注意:对于那些刚接触 AWS 的人来说,如果你想继续下去,请确保在下面的 链接 中做一个帐户。部署过程中会产生成本,尤其是如果您让端点保持运行。

“塑造者”——绝技。资料来源:Lars Kjeldgaard。

“塑造者”深潜

接下来,我将详细介绍一下‘shape maker’模板的一些细节。

关于如何使用‘shape maker’,的更多详细说明,请参考自述文件

  1. 型号代码

模型代码被组织成一个 python 包:modelpkg.

模板附带了一个作为占位符的虚拟模型:一个估计薪水和年龄之间线性关系的模型。

该模型被实现为它自己的类,具有用于(1)模型训练,(2)预测观察,(3)性能评估和(4)加载/保存模型工件的方法。

模型代码

2。模型训练图像 训练脚本train.py借鉴了modelpkg中的模型代码。train.py运行一个参数化的训练任务,生成并保存一个模型工件。

培训脚本。建立模型培训码头工人形象。

modelpkg和上面的训练脚本被构建到能够运行模型训练工作的模型训练 docker 映像中。

3。端点图像 模型端点图像“shape maker”附带一个 Flask web 应用程序,它使用modelpkg来加载模型工件并计算预测,作为应用程序用户请求的答案。

SageMaker 模型端点的 Web 应用程序。

该应用内置于端点 docker 映像中。默认情况下,‘shape maker’实现了一个 NGINX 前端。

端点 docker 图像。

4。使用命令行函数 构建、训练和部署模型所有与模型项目交互相关的任务都在Makefile中作为方便的命令行函数实现,这意味着使用make [target]调用函数,例如make build_training_image

如果您想要即时构建、训练和部署模型,您可以通过调用一系列make目标来实现,即:

  1. make init
  2. make build_training_image
  3. make push_training_image
  4. make create_training_job
  5. make build_endpoint_image
  6. make push_endpoint_image
  7. make create_endpoint

之后,您可以通过调用make delete_endpoint来删除端点。

注意: make + space + tab + tab 列出所有可用的make目标。

5。配置文件 培训工作、端点等的配置。与代码分开,存放在configs文件夹中。这意味着,如果您想要更改端点的配置方式,您只需更改configs/endpoint_config.json:

SageMaker 端点的配置。

6。CI/CD 工作流程 [shapemaker](https://github.com/smaakage85/shapemaker)附带了许多通过 Github 操作实现的自动化(CI/CD)工作流程。

要启用 CI/CD 工作流,请将您的项目上传到 Github,并通过提供您的 AWS 凭据作为Github机密,将 Github 存储库与您的 AWS 帐户连接起来。秘密应该有名字:

  1. AWS_ACCESS_KEY_ID
  2. AWS_SECRET_ACCESS_KEY

默认情况下,每次提交到main都会触发一个工作流./github/workflows/deliver_images.yaml,该工作流运行单元测试,构建并推送训练和端点映像:

持续交付培训/端点 docker 图像。

所有工作流程都可以通过手动运行

结论
BYOC 为配置和调整 SageMaker 项目提供了难以置信的灵活性,但代价是增加了复杂性。

为了让 BYOC 更容易理解,“shape maker”为亚马逊 Sagemaker 项目提供了一个完整的端到端模板,实现了 BYOC。

我希望,你会给【塑造者】一个旋转。如果你这样做了,我会喜欢你的反馈。

资源 这篇博客文章基于(并借用)以下由玛利亚·维克斯拉德拉姆·维吉拉朱写的博客文章——大声喊出来:

https://www.sicara.fr/blog-technique/amazon-sagemaker-model-training https://github.com/smaakage85/shapemaker

构建可信且可操作的人工智能的框架

原文:https://towardsdatascience.com/a-framework-for-building-trustworthy-and-actionable-ai-f740b093cc17

如何将人工智能的风险降至最低并加速其采用

拍摄的罗布·威克斯Unsplash

人工智能已经走过了漫长的道路!曾经是一些著名科幻作家想象中的幻想,如今已经无孔不入。AI 已经成为我们生活的一部分;它到处都在使用,它给我们带来了很多方便。在许多行业,人工智能驱动的认知自动化已经帮助自动化复杂的业务流程,这反过来大大提高了流程效率。人工智能系统也正在被纳入工业和政策领域的各种决策过程中。在医疗保健领域,人工智能带来了一些令人难以置信的突破。然而,在许多情况下,人工智能的使用也有潜在的风险,并且有人工智能被滥用的真实例子,无论是有意还是无意。人工智能可能存在道德问题,或安全性不足,或不安全或不保护个人隐私或敏感数据。此外,人工智能系统是一个黑匣子。它给了我们一个决定,但是它没有告诉我们这个决定背后的基本原理,它是如何做出这个决定的。由于缺乏透明度,人们很难信任人工智能系统的决策。总而言之,尽管人们意识到了人工智能不可思议的力量和好处,但他们同样担心与之相关的潜在风险。这自然要求对人工智能进行监管,并需要可信和可操作的人工智能。

什么是 AI 监管?

人工智能的监管是公共部门政策和法律的发展,以将道德标准纳入人工智能技术的设计和实施。

现在,如何定义 AI 的伦理标准?前提应该是什么?首要原则是确保负责任地使用人工智能。甚至在为一个用例开发一个人工智能系统之前,一个人应该分析与为那个特定用例使用人工智能相关的潜在风险和好处。根据潜在风险的程度及其产生的效益,人们应该决定在系统设计中整合一些严格的要求,以使其安全、可靠、合乎道德、透明和可靠。这正是欧盟委员会在 2021 年 4 月 21 日公布的管制人工智能的提案中提到的内容。来源:https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A52021PC0206

欧盟委员会提到,他们监管人工智能的主要目标是确保安全和遵守基本权利和联盟价值观。委员会建议采用风险为本的方法来规管认可机构,并提出三类风险,即。不可接受的风险、高风险和低或最低风险。当人工智能被用于不可接受的风险时,它应该被禁止,这是欧盟委员会的提议。

不可接受的风险是,如果 AI 系统对安全、生计和权利构成威胁,例如自主武器;或者如果人工智能系统操纵人类行为以规避用户的自由意志,例如,促进暴力、欢呼杀戮和鼓励未成年人危险行为的玩具,或者如果人工智能系统允许政府进行社会评分,这可能导致歧视性结果和某些群体的排斥[1]。

高风险是当 AI 系统用于以下情况时[1]:

1.关键基础设施(交通)->例如自动驾驶汽车。

2.教育培训->如考试评分。

3.员工的选拔、管理->简历筛选系统。

4.产品的安全组件->例如机器人辅助手术。

5.基本的私人和公共服务->如信用评分拒绝公民获得贷款的机会。

6.执法->例如评估证据的可靠性。

与上述各项相关的人工智能系统具有对健康、安全和基本权利造成有害影响的潜在风险。欧盟委员会提出了一个监管框架,对这些高风险 AI 提出了更严格的监管要求。

低风险或最小风险是指人工智能系统对公民权利或安全的风险最小或没有风险,例如网络搜索引擎或产品推荐引擎;或者当人工智能系统需要用户应该知道的特定透明义务时,他们正在与机器进行交互,例如聊天机器人。对于这些低风险或最小风险的人工智能,欧盟委员会已经提出了一个行为准则,由人工智能系统的个人提供商或代表他们的组织自愿制定[1]。

高风险人工智能监管要求

欧盟委员会对高风险人工授精提出了以下监管要求[1]:

1.充分的风险评估和缓解系统

2.数据集的高质量:将风险和歧视性结果降至最低。

3.透明度:向用户提供清晰、充分的信息

4.记录活动以确保结果的可追溯性。

5.高水平的健壮性、安全性和准确性

6.适当的人为监督将风险降至最低的措施。

7.详细文件供当局评估其合规性。

欧盟和美国开始在人工智能监管方面达成一致。国家标准与技术研究所(NIST)正在开发一个人工智能风险管理框架。“该框架旨在促进创新方法的发展,以解决可信赖性的特征,包括准确性、可解释性和可解释性、可靠性、隐私性、稳健性、安全性、安全性(复原力)、减轻无意和/或有害偏见以及有害使用”。来源:https://www.nist.gov/itl/ai-risk-management-framework

欧盟委员会针对高风险人工智能制定的所有上述七项监管要求都符合五项原则 可信且可操作的人工智能:

无论何时我们构建一个人工智能系统,它都应该具有内在的道德和可信赖性,换句话说,我们应该在人工智能系统的构建模块中嵌入公平性、可解释性、安全性、健壮性和可问责性。

如何在商业应用中嵌入可信且可操作的 AI 原则?

理想情况下,我们应该在人工智能生命周期的每个阶段嵌入可信和可操作人工智能的原则,如下所示。

图 1:嵌入可信商业应用生命周期的可信人工智能原理

1。数据是 AI 的核心。人工智能系统需要从数据中学*,以便能够履行其功能。因此,在公平性、一致性和隐私保护方面确保培训数据的质量至关重要。

公平是指在决策过程中,基于个人或群体的内在特征(性别、种族、肤色、宗教、残疾、民族血统、婚姻状况、年龄、经济地位等),对其不偏不倚或没有任何偏见或偏袒。公平性原则对保证训练数据的质量起着至关重要的作用。

答:训练数据的公平性意味着数据应该代表模型将要应用到的人群。更明确地说,数据应该通过结果和敏感属性子组得到公平的表示。如果数据中存在关于敏感属性子组的偏差,人工智能模型将学*该偏差,并针对该组给出有偏差的决策,这是不可接受的。

因此,为了建立一个值得信赖的人工智能系统,在使用数据进行训练之前,检测并减轻数据中的偏差是极其重要的。有几种度量和测试用于检测数据偏差,如差异影响、Fisher 精确测试、独立性卡方测试、类别不平衡等。其需要被适当地应用以检测数据中的偏差。

类似地,存在各种偏差减轻或平衡数据的技术,例如过采样、欠采样、SMOTE 及其变体。

b .训练数据的一致性:训练数据质量的另一个维度是确保数据没有异常。异常指数据中不符合预期行为的模式。异常数据是与大多数数据显著不同的罕见或异常事件。如果训练数据中存在异常数据点,模型会学*异常行为,对数据进行过拟合;它将不能概括,并将对新数据做出不准确的预测。因此,为了建立一个值得信赖的人工智能系统,在使用数据训练机器学*模型之前,检测训练数据中的异常并将其删除或适当处理是极其重要的。有各种非监督、监督和半监督机器学*技术来检测异常,其中流行的是基于密度的技术,如 KNN、隔离森林,基于聚类的技术,如 K-Means、DBSCAN 一类 SVM、神经网络、自动编码器、隐马尔可夫模型,也可以是这些技术的组合。

c .训练数据的隐私保护:从可信数据& AI 的角度来看,这是数据质量的另一个重要维度。我们需要确保培训数据被适当地屏蔽、隐藏或加密,以便维护个人和敏感数据的机密性和完整性。

将可信数据和人工智能的原则应用于训练数据,一旦我们能够确认训练数据的高质量,就没有偏见、异常和保护个人或敏感信息的数据而言,它可以用于训练。

一旦我们确保了训练数据的质量,接下来最重要的事情就是在机器学*工作流的训练过程中融入健壮性、公平性和可解释性的原则

2。鲁棒性是指算法的稳定性。它意味着在新的独立(但相似)数据集上测试时,你的算法有多有效。换句话说,稳健算法是测试误差接*训练误差的算法。鲁棒性的另一个方面是“对噪声的鲁棒性”,它描述了在数据中添加一些噪声后算法性能的稳定性。这种模型稳定性是为了确保 AI 系统免受外部攻击。当有人输入错误的输入和参数,欺骗人工智能模型做出不正确的预测或泄露敏感信息时,它能够处理对抗性攻击。

3。当人工智能用于具有影响某人的生命、健康、安全、自由、教育、职业、专业等风险的决策时,人工智能算法的公平性至关重要。下面是一些真实的例子,人工智能系统对少数群体或历史上处于不利地位的群体做出了不公平或有偏见的决定。

a)有一种叫做 COMPAS(代表替代制裁的矫正罪犯管理概况)的商业工具,由机器学*算法提供动力,预测刑事被告重新犯罪(或累犯)的可能性。2016 年,一项将该工具的预测与实际情况进行比较的研究发现,该工具预测黑人被告的累犯风险比实际情况高,白人被告的累犯风险比实际情况低。https://www . propublica . org/article/machine-bias-risk-assessments-in-criminal-pending

b)微软、Face++和 IBM 分别提供了一些商业人脸识别在线服务,这些服务在确定浅色皮肤男性的性别方面显示出高得多的准确性,而在确定深色皮肤女性的性别方面显示出低得多的准确性。原因是,该算法是在高度不平衡的数据上训练的,这些数据中男性和浅色皮肤的人的图像比例过高。这些系统后来得到了改进。https://news . MIT . edu/2018/study-finds-gender-skin-type-bias-artificial-intelligence-systems-0212https://blogs . Microsoft . com/ai/gender-skin-tone-面部识别-改善/

c)“亚马逊用于给求职者打分的基于人工智能的实验性招聘工具并没有以性别中立的方式给软件开发人员和其他技术职位的求职者打分。原因是,该模型主要是根据男性在 10 年内提交给公司的简历进行训练的,因此该模型对简历中带有男性偏见的单词给予更多权重,并对包含“女性”一词的简历进行惩罚,如“女子象棋俱乐部队长”。https://www . Reuters . com/article/us-Amazon-com-jobs-automation-insight-iduskcn 1 MK 08g

必须确保模型中没有偏见,这样才能做出公平的决策。在模型开发的预处理、处理中或后处理阶段检测并减轻偏差。预处理意味着训练数据中的偏差,这已经在 1a 中讨论过了。处理中意味着算法偏差在训练过程中悄悄出现,这是由于在训练模型时做出的某些假设或由于应用了一些优化约束而发生的。有许多度量标准可以检测算法偏差,如均等的机会、均等的机会、人口统计上的均等等等。算法偏差可以通过各种技术来减轻,例如学*参数的正则化、调整类权重、平衡装袋、对抗性训练程序以及其他一些技术。后处理意味着在训练发生后偏差被减轻。有了模型结果的事后解释能力,就有可能查明偏差的确切原因,因此这对于伦理人工智能是至关重要的。

4。人工智能模型决策的可解释性是关于使“黑盒”机器学*模型的结果即使对一个天真的用户来说也是可解释和可解释的,而不管底层模型的复杂性。机器学*或深度学*模型是非常复杂的非线性模型,无法以可理解的方式解释预测背后的基本原理或如何做出决策。机器或深度学*模型的推理缺乏透明度,这被称为“黑箱”问题。有人可能会想,如果可理解性是机器学*模型的一个问题,那么为什么不使用传统的方法,如回归技术或决策树来产生更易于人类理解的模型呢?这个问题的答案是,今天,随着海量数据和高性能计算在 GPU 的帮助下变得可用,机器学*模型可以更好地利用数据来学*其中高度复杂的非线性模式和关系,并优化调整模型的参数,这减少了模型中的偏差,并显著改善了模型的性能。因此,由于它们的高性能,机器学*模型应该得到利用,但要有可解释性。引用 Pedro Domingos 教授的话,“当一项新技术像机器学*一样无处不在并改变游戏规则时,让它保持黑箱状态是不明智的。不透明为错误和误用打开了大门”。我们需要可解释性来建立对人工智能系统的信任,并在商业应用中指导下游行动。

让我们考虑一个例子。如果像神经网络预测的那样,客户的住房贷款被拒绝,贷方通常不会说这是因为你的贷款价值比、你的拖欠账户总值、你在过去六个月中的最差状态(两次或两次以上拖欠付款)、你在过去三个月中的总未偿余额、你在过去六个月中进行的贷款搜索次数、你的职业和你的信用历史长度的加权比例组合的 sigmoid 等于 0.57。即使这可能是模型决定拒绝客户的方式,贷方通常需要分解复杂的解释,并尝试用简单的术语向客户解释,使用最重要的原始输入变量-例如,声明您的贷款价值比大于 80%,这太高了,您的信用卡在过去三个月中的总未偿余额增加了,并且您是自营职业者。这种简单、易于理解的解释可以通过开发一个代理模型来实现,该代理模型*似于用于预测的经过训练的复杂机器学*模型。这个代理模型被称为可解释的 AI 模型,它是透明的,并为黑盒模型给出的预测结果提供人类可解释的解释。

对于可解释的 AI 有各种方法:全局对局部,模型不可知对模型特定的解释。所谓整体解释是指从整个模型的角度进行解释。它是关于分析特性的重要性,根据它们对模型输出的贡献。局部解释的意思是,解释理解每个单独预测背后的推理。实例级的解释通常比模型级的解释更具可操作性,因为它指导给定事务的解释和操作。例如,两个人可能有很高的贷款违约概率,但原因完全不同。

模型不可知论意味着解释者必须能够解释任何模型,也就是说,将原始模型视为黑盒。这为解释任何分类器提供了灵活性。而特定于模型的解释考虑模型的结构来导出对预测的解释。生成解释的方法并不是通用于所有种类的分类器,而是考虑到算法的内部工作过程而为每个分类器专门开发的。

实例级的解释通常比模型级的解释更具可操作性,因为它指导给定事务的解释和操作。以下是针对特定车型的本地解释的论文列表:

I)可解释 AI:为深度学*预测生成人类可解释解释的混合方法 》,由 ELSEVIER 在 Procedia Computer Science 发表。作者:Tanusree De、Prasenjit Giri、Ahmeduvesh Mevawala、Ramyasri Nemani 和 Arati Deo。

ii)可解释的 NLP:一种为语义文本相似性生成人类可解释的解释的新方法学 ”,由 Springer 在 Advances in Signal Processing and Intelligent Recognition Systems 中发表。作者:Tanusree De 和 Debapriya Mukherjee。

iii)一个可解释的人工智能供电的早期预警系统,以解决患者再入院风险 ”,由 IEEE 在 IEEE Xplore 中发表。作者:Tanusree De、Ahmeduvesh Mevawala 和 Ramyasri Nemani。

iv)xAI 逐层算法与用于息肉分割和分类的归纳聚类的健壮推荐框架的比较研究 ”,由 IEEE 在 IEEE Xplore 中发表。作者:Shiddalingeshwar Mallayya Javali、Raghavender Surya Upadhyayula 和 Tanusree De。**

要实现可信且可操作的人工智能,需要什么?

在这里,我提出了我对可信人工智能实现和治理框架的观点。

图 2:实现可信人工智能的框架

如上所述,构建和运营可信数据和人工智能的五个关键因素是战略和路线图、工具包、流程、人员、审计和治理。

1。人工智能战略&路线图

这个旅程应该从评估使用人工智能的潜在风险开始;并根据评估创建人工智能战略和路线图,以收集正确的数据集,检查和提高训练数据的质量,并将可信数据和人工智能的原则嵌入数据和模型中,如数据偏差缓解、数据隐私保护、模型稳健性、公平性、可解释性和问责制。现在,为了实现这个策略,我们需要一套工具包。

2。工具包

基本上,这是一套资产和加速器,可以让你建立可解释性,也可以测试数据或算法中的任何偏差,并减轻它。组织正在构建各种开源工具和专有工具,这些工具基本上都是低代码/无代码平台。

这些工具包充当加速器,使研究人员和开发人员能够在给定的时间框架内做更多的实验;从而使他们能够更快、更好地构建值得信赖的数据和人工智能解决方案。除了工具,应该有一个定义良好的过程或工作流,开发人员应该遵循它来交付技术上合理和道德的人工智能系统。

3。流程:

有一个适当的过程和围绕它的最佳实践是非常重要的。

****研究:这一进程应从对现有的各种办法和方法的深入研究开始,从这些办法和方法中汲取思想,并创新和设计将产生更好结果的新办法和方法。

(ii) 设计:应该采取以人为中心的方法来设计解决方案。以人类的需求、行为和目标为中心,将有助于构建更加可信、可靠、没有任何偏见的人工智能解决方案。

(iii)ML 工作流的创建:一旦设计了解决方案,就应该创建机器学*工作流,其结合了如图 1 所示的公平性、健壮性、可解释性的元素。

(iv) 开发:在工作流程之后,应该使用工具包并应用最佳实践来执行解决方案开发流程。

(v) 模型风险评估:一旦开发了模型,就偏差、准确性、稳健性、数据隐私、可解释性等而言,与模型相关的风险。应进行评估以最终确定模型。

(六)文档:在这整个过程中,一个非常重要的任务是详细记录设计、工作流程、数据以及所遵循的所有步骤,从数据预处理到模型训练、验证、集成、模型性能度量、性能跟踪机制、模型生命周期各阶段所做的假设、模型各种参数的阈值、模型的局限性以及相关风险等等。文件对于风险评估和人员监督措施非常重要,以便在出现潜在故障迹象时维护系统并解决任何问题。

开发一个健壮的、可解释的和道德的人工智能系统所需要的一切的中心是人。训练和建立人工智能系统的是人类。另一方面,AI 的最终用户也是人类。

4。人:

为了建立一个人工智能系统,我们需要不同的人,即技术、功能、数据和领域专家;我们还需要人工智能系统的潜在用户,或者至少是终端用户的代表。在设计和开发产品时,将最终用户的观点、他们的需求和要求以及他们对可用性的反馈结合起来,以使产品以人为中心并且高效,这是非常重要的。

例如,要建立一个人工智能驱动的疾病检测系统,你需要让医生或来自生命科学的人参与进来,他们可以提供疾病、症状、医疗参数等特定领域的知识。这需要结合到系统的训练中。医生将能够进行正确的基准测试,并提出特定领域的指标来衡量系统在培训和生产过程中的准确性和精确度。

同样,为法律领域建立人工智能系统,应该咨询律师;或者对于基于人工智能的考试复*,人类考官应该参与进来等等。这完全是用人类专家用来完成任务的特定知识来训练系统。

按照类似的逻辑,人力资源、IT、市场营销、风险和合规方面的职能专家应该参与构建人工智能系统,以解决这些职能领域的业务问题。

今天有许多人工智能应用程序纯粹是在海量文本数据上训练的,语言专家和翻译人员可以在为数据预处理、模型微调和验证提供有价值的输入方面发挥重要作用。

构建人工智能系统的团队应该在性别、种族、婚姻状况等方面具有多样性,以便不同人群的观点可以纳入人工智能系统的设计和开发中,这将减少偏见,使人工智能系统更具包容性。

该团队还应该有人扮演人工智能伦理学家的角色,他们可以提供人工智能道德使用的指导,帮助遵守监管规定,确保人工智能系统做出道德决策,并建立问责框架,以解决人工智能系统带来的任何意外风险。

一旦一个人工智能系统用正确的人员、过程和工具包建立起来,下一个最重要的任务就是审计。

5。审计

每个正在建立人工智能系统的组织都应该有一个人工智能审计实践。审计员的职责是审查和检查人工智能生命周期每个阶段遵循的标准和程序,以及对法律、政策和组织战略的遵守情况,以确保系统在技术上是稳健的、安全的、公平的、可解释的、保护隐私的,并且总体上是可靠和负责任的。

更具体地说,这意味着检查:

****a .详细的文档:人工智能生命周期的所有阶段,从数据的详细概述,到数据清理、数据过滤、应用的数据预处理,到随后的建模过程,模型信息和人工智能模型生产中涉及的工件,以及模型结果的分析。

****b .用于设计和开发算法系统的方法:使用了什么算法,算法是如何训练的,做了什么假设,使用了什么样的输入数据,输入数据中是否有敏感变量以及数据中是否有任何偏差被减轻以及如何减轻。

****c .用于测试和验证算法系统的方法:关于用于测试和验证的数据的信息。关于训练-测试差异的信息,以及模型在测试或新数据上的概括程度。在敏感属性方面是否存在过度拟合、欠拟合或偏差。

****d .结果的可解释性:黑箱 AI 模型的结果有可解释性吗?解释的直观性、逻辑性和可理解性如何?这些解释能在多大程度上用于决策?这对人工智能的高风险用例至关重要,如医学图像分析或疾病检测,在这些情况下,可解释性是信任模型结果并采取适当决策的必要条件。

审计人员还应审查数据治理和上市后监控的现有流程。

****e .数据治理:检查用于管理和保护公司数据资产的标准、流程和技术。遵循最佳实践以确保定期清理、更新和清除数据,从而保持数据的高质量。符合数据隐私法律法规。

上市后监控:审计人员应检查,是否有一个稳健的流程来跟踪和报告严重事件和故障,以确保当 AI 系统中存在需要整改的问题时,不会不负责任地使用该系统。

并且,为了确保整体的一致性和法规遵从性,包括审计,必须有治理。

6。治理

人工智能治理是一个系统,用于监督组织实现如上所述的可信人工智能框架的每个元素的方式,以及它们如何确保问责制。基本上,人工智能治理需要涵盖人工智能系统的整个生命周期。应该对以下方面进行治理:

I)从最终用户的角度对 AI 使用进行伦理审查。

ii)确保符合法规要求和组织的战略、文化和愿景。

iii)审查模型设计和开发、模型集成、部署和维护的人工智能风险和方法。

iv)确保训练数据质量、数据准备方法、模型开发过程、测试模型性能和稳健性、偏差检查、模型结果的可解释性和模型维护的适当标准。

人工智能治理的另一个非常重要的方面是,人工智能治理不应该孤立地发生,而是应该成为组织的整体治理系统的一部分。对于有效的人工智能治理,它应该与企业、it 和数据治理相联系。例如,一个组织如何利用人工智能来促进公共利益是由公司治理决定的,与这一目标保持一致是人工智能治理的一个重要方面。同样,当 AI 算法集成到组织的信息系统中时,组织的 AI 治理和 IT 治理应该保持一致。同样,由于人工智能系统建立在数据基础上,另一方面,人工智能也被用来丰富数据,数据治理的某些方面对人工智能治理至关重要,反之亦然,两者应该协同工作。

结论

在数据和人工智能中嵌入透明度、可解释性以及道德标准和价值观是当前减轻风险、建立信任和使人工智能可操作的需要。所有这些最终都将增加人工智能在改造社会和商业方面的应用。

参考

[1]欧洲委员会,欧洲议会和理事会条例提案,制定人工智能协调规则(人工智能法案)并修正某些欧盟立法法案(2021),EUR-Lex,

https://eur-lex.europa.eu/legalcontent/EN/TXT/?uri=CELEX%3A52021PC0206

****[2] E. Ntoutsia 等人,数据驱动的人工智能系统中的偏差——介绍性调查(2020 年),arXiv,【https://arxiv.org/abs/2001.09762 ****

****[3] R. Guidotti 等人,黑盒模型解释方法综述(2018),arXiv,【https://arxiv.org/abs/1802.01933 ****

[4] Nicole Turner Lee 等人,算法偏差检测和缓解:减少消费者伤害的最佳实践和政策(2019 年),布鲁金斯,https://www . BROOKINGS . edu/research/Algorithmic-Bias-Detection-and-migration-Best-Practices-and-Policies-to-reduce-Consumer-harms/

[5]大卫·J·汉德和沙克尔·汗,验证和检验人工智能系统(2020),模式,https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7660449/#

将决策智能嵌入组织的框架

原文:https://towardsdatascience.com/a-framework-for-embedding-decision-intelligence-into-your-organization-f104947651ae

如何实现决策智能

JESHOOTS.COMUnsplash 上拍照

为什么是“决策智能”?

一段时间以来,人们对决策智能(DI)的兴趣越来越大,提出的采用规则多种多样——从原则性和利他性的驱动因素[1],到通过大规模自动化行动到结果的过程[2]实现技术支持的迫切需要的方法,以及介于两者之间的一切。

在我看来,探索这个领域有一系列原因:认识到投资数据以“做出更好的决策”过于模糊;希望改善决策文化,减轻主要基于启发式的非结构化或临时决策中固有的风险;希望将决策智能(DI)视为一个统一的学科,将各种社会科学、定量方法和商业概念的急需影响力结合在一起。显然,采用的驱动因素多种多样,大概取决于您的个人或组织环境。

虽然对于我们这些在分析和商业战略交叉领域工作的人来说,这可能并不奇怪,但也有一系列听起来非常商业的原因。虽然我打算在未来的文章中对这些驱动因素进行深入分析,但从较高的层面来看,它们通常分为以下几类:

数据和分析投资优化

数据、分析、决策产品和工件的优化设计和重用

推动决策支持活动的重点,以实现业务战略和监督

不管你是如何走到这一步的(这些都是令人信服的论点),如果你和我一样,你会问自己“好吧…但是现在呢?”

不同的观点

先说 DI 巩固不足。有多少理由在你自己的业务中采用这个领域,就有多少定义。然而,这一领域的一些思想领袖已经开始围绕至少三种不同的 DI 观点出现,这些观点拥有许多共同的要素:

Lorien Pratt 博士的链接本和 CDD

我热切地等待着 Lorien 在 2019 年的书[1],并希望它将把所有松散收集的 DI 概念、描述和解释集中到一个地方。十多年来,Lorien 一直在倡导 DI,并且已经成为该领域的主要支持者之一。我强烈建议你看看 Lorien 的一些文章、博客、播客和视频,了解一下她对 DI 的看法。

她的书以叙述为基础,总结了人工授精的主要驱动力和原因,并提出了一些核心概念。最值得注意的是,洛里安提倡使用“因果决策图”,或“CDD”。

CDD 是一个有价值的简化工具,它允许用户通过识别三个主要决策组成部分(行动、因果关系、结果)及其对预期结果的累积影响来“设计”决策。链接通过几个例子,甚至介绍了 CDD 设计的方法,强调了 CDD 作为插入数据、信息、分析或 AI/ML 模型的脚手架的效用。

谷歌的 Cassie Kozyrkov 博士,以及她作为“首席决策科学家”的角色

这个领域的另一个主要声音是谷歌的首席决策科学家——凯西·科济尔科夫博士。除了令人印象深刻的简历和背景,Cassie 还是一位非常有天赋的演讲者和口语作家,她积极参与更广泛的数据科学和商业社区,经常利用幽默轶事或互联网模因将她的信息带入生活。和 Lorien 一样,我鼓励你去看看 Cassie 的一些文章和视频。

据我所知,Cassie 对 DI 最全面的概念化描述已经在这篇文章【3】中进行了概述,并提出了行为经济学和心理学、数据科学、统计学和决策科学的巧妙融合。我很欣赏这种关注的广度,以及她如何同时呈现统计严谨性所要求的条件,同时也概述了在不确定性下影响决策的硬因素和软因素的作用。我认为 Cassie 对 DI 的跨学科观点引发了关于偏见、启发和决策文化在现代数据和决策分析应用中的作用的讨论。

Gartner 研究中提到的“工程决策智能”模型

对于那些关注 DI 的人来说,您可能已经注意到该领域出现在“2022 年数据和分析顶级趋势”[4],以及他们的“Gartner Hype Cycle 2021 年分析和商业智能”[5]。

因此,我认为这显然是他们所关注的,他们也为 DI 制定了自己的定义和框架。我还应该指出,去年 5 月,DI 参加了他们的虚拟数据和分析峰会。

Gartner 将决策智能定义为:

“通过明确理解和设计如何制定决策、如何评估结果、如何通过反馈管理和改进结果,来改进决策制定的实用规程。”[6]

要了解更多信息,您可以阅读 Gartner 研究报告(只对 Gartner 订户开放)

遗漏了什么?

尽管如此,对于我们这些希望在业务中嵌入 DI 的人来说,很难找到一个公开可用的指导来源。虽然我们知道组织内部正在利用 DI,但目前还没有这样做的指南。这一领域的大多数出版材料要么过于理论化,要么与专有服务或框架联系过于紧密,通常缺乏“起步”的循序渐进的方法。

本文不会完全解决这个问题,但是它迈出了重要的第一步,提出了一个开源框架,描述了 DI 过程的主要分组,可以作为将 DI 嵌入到所有类型的组织中的线框。

Pierre Bamin 在 Unsplash 上拍摄的照片

嵌入决策智能的集成过程框架

我在这里提出了一个框架,描述了整体决策智能操作模型的主要活动和使能因素。下面介绍的这个模型将来自几个不同的 DI 定义和决策科学的概念联系在一起,并作为在组织内实施 DI 功能的指南:

作者创作的原始图像

该框架围绕两个主要方面构建:

核心活动,包括决策设计、决策支持、决策优化和决策评审子分组,概述了组织内建立的阿迪运营模式的主要活动;

使能因素(组织能力和决策文化)描述了加强或使能核心活动的组织的组成部分。

以下章节从较高层面描述了这些维度,并为该领域的其他人在此框架上构建提供了基础。

核心活动—决策前

优选设计

第一组核心活动发生在 预决策 ,并描述了“设计”决策的过程——也就是说,采取第一个关键步骤为决策提供结构,这将使所有后续的下游步骤能够建立决策基础。

决策框架

非常著名的心理学家 Kahneman 和 Tversky 将决策框架定义如下:

“…决策者对与特定选择相关的行为、结果和偶发事件的概念。” 【七】

我们可以从这个定义中看到,它有效地设定了决策的边界——我的行动(杠杆)、结果(期望的或其他的)和意外情况(风险和外部性)是什么?他们进一步表明,决策偏好高度依赖于框架——即使预期结果是相同的。

显然,恰当地制定决策的过程不仅是定义恰当的决策范围的关键,也是确保已经建立了期望的决策过程的关键。因此,该框架至少应解决以下问题:

我想要的结果是什么?

为了获得这些结果,我愿意投资多少?

我的风险是什么?他们可以忍受吗?

的确,在查看任何数据之前,您应该知道这些问题的答案,因为它们可能会因为您对数据的检查而无效,即使您并不希望如此![8]

将决策框架定义为决策前更广泛的核心活动的一部分的系统方法是锚定下游决策流程的关键组成部分。

决策映射

Lorien Pratt 博士在她的书《链接》和相关文章(如上所述)中出色地向我们介绍了 CDD 的效用。这种方法对于以简单的图形格式捕捉行动、因果联系和结果非常有用,这种格式可以引起讨论、包容、透明和结构化。

存在类似的框架(例如加拿大政府的逻辑模型方法【9】,卡普兰和诺顿的平衡计分卡的一些解释【10】等。),但是这些通常与计划/项目级别的活动相关联,而与决策过程中的行动无关(例如,输入= >活动= >输出)。

决策映射的主要优势有[1]:

它通过图形化地表示行动、中介和期望结果之间的因果关系,减少了您必须“记住”的复杂性

它提供了一个机会,通过“聚集”在 CDD 周围,让更多的人参与决策过程

它提供了因果联系,作为数据和分析产品、指标、信息、预测模型等的集成点。并以这种方式将多个输入组织成单个决策

它为决策后审查提供了重新访问整个决策过程的机会,并提供了决策工件的重用

促进决策规划工作的流程对于通过 CDD 或相关决策流程采用结构化决策至关重要。

决策增强

聚集在“决策支持”下的活动通过评估设计的决策,并清楚地识别数据和/或分析的集成点,来构建“决策设计”步骤。同样,它要求对决策的设计进行分析,以便清楚地识别可能解决或可能不解决的不确定领域。

数据和分析集成

一个设计好的决策——例如,一个呈现一系列行动到结果路径的决策——提供了一个机会来清楚地确定数据或分析在哪里以及如何支持下游的预决策过程(例如,建模),或者如何确定一个行动是否会产生预期的结果。

基本原理很简单:如果你有一个行动——因果联系——结果路径,你真正看到的是一个数学函数。例如,如果我的行动是增加广告支出,而我的结果或中介是销售额的增加,那么就有一个数学函数来描述广告支出和销售额之间的关系。

这样,为了在上述框架内完成决策,我们可以将决策的子组件附加到具体的数据、指标、分析、预测模型等。同样,我们可以考虑子组件的块(例如,动作-链接-结果或类似的集群),已知这些子组件具有由描述动作和结果的单个聚集关系控制的关系。Lorien Pratt 的书 Link 很好地展示了 CDD 作为整合数据和分析的脚手架。

识别不确定性

所有的决策都是在一定程度的不确定性下做出的——尤其是在现代商业环境中,在“VUCA”世界中更是如此[11]。无论这些不确定性来源是来自组织的外部、内部,还是由数据质量的内在问题所驱动,它们都对决策者提出了挑战。在不确定性下做出决策有多种方法和“最佳实践”——从业务流程角度[12]到基于预期效用的统计驱动方法[13]。

从决策智能集成的角度来看,我认为流程的这一部分是关于查看一个设计好的决策(即,一个框架和映射的决策),并评估不确定区域的决策。具体来说,虽然这是在一个有些争议的背景下出现的,但使用已经在其他科学领域找到支持者的拉姆斯菲尔德方法来评估不确定性是有用的[14]:

转载自 安德烈亚·曼托瓦尼关于领导力的文章【15】

框架决策提供了一个机会,可以在高层次上回顾决策环境,并确定我们已知的已知内容、已知的未知内容等。同样,CDD 为我们提供了一个机会,让我们坐下来看看每一个因果联系,并从拉姆斯菲尔德的角度(如上)来识别不确定性,但也从统计学的角度(在数据和分析被整合的地方)来识别不确定性——错误是什么?数据的质量如何?等等。

由于与此流程框架相关,通过主动识别不确定性领域,我们正在准备承认我们决策流程的局限性,并给自己一个机会来系统地识别无法解决的不确定性,或者我们可以降低决策中不确定性水平的领域。

决策优化

经过设计和改进的决策为我们提供了一个机会,让我们考虑是否有机会通过建模来观察行动优化,和/或同意最适合决策环境和设计的决策方法。这一系列过程提供了一个机会,在作出决定之前解决最后的步骤。

建模

这个量化的步骤是可能的,其中良好构造和映射的决策提供了在 CDD(或逻辑模型,或类似模型)内对聚集的行动-结果链接建模的机会。这可能允许评估行动对决策结果(有意或无意)的总体影响。如果数据(或分析)不允许开发一个完全细致入微的模型,那么行动对特定结果或中间结果的总体影响的知识(例如,该行动将导致 x 结果的“更多”或“更少”)仍然可以阐明一系列行动的所有结果的总体影响。

很明显,这是一个复杂的过程,需要投入大量的时间和资源,并且可能适合也可能不适合决策环境。然而,这是一个潜在的有价值的步骤,因为它允许决策优化,其中可以开发一个量化的决策模型,允许针对目标业务结果进行行动优化。

更广泛地说,我们也可以将决策自动化视为一系列活动的潜在目标,这些活动旨在优化行动与结果之间的联系。O'Reilly 详细描述了人工智能编排在决策自动化中的作用,并继续解释人工智能编排的目标应该是决策智能[2]!

无论您是否选择对决策进行建模,都应该清楚,希望对决策进行建模和优化的组织将需要利用内部量化专家(包括数据科学家)和/或聘请外部服务提供商来支持该流程。

方法

虽然有很多“数据驱动”的决策制定方法,但现实是,在许多情况下,它根本不是最佳的,甚至是不可取的。在最*的 Gartner 数据和分析峰会上,Gartner 召开了几次专注于决策的会议。

具体来说,Gartner 分析师 Gareth Herschel 提出“最佳决策是混合的”,“任何方法都有优点和缺点”* [16]😗

逐字转载自 Gartner,经许可使用[16]

我将扩展这一概念,意思是决策要素(例如 CDD 中的行动到结果的联系)也可以被评估为最适合该特定要素的方法。例如,评估一项行动是否会影响结果的本能(即启发式)方法可能适用于对该行动的结果敏感度较低的情况,或者数据收集成本过高的情况。这也是为决策过程中的参与者定制方法的好方法(见上文)。

通过评估一个决策(或决策的组成部分)的最佳方法,我们也邀请讨论 偏见在决策制定中的作用【17】。虽然不是本文的主题,但它是组织决策文化和过程中的主要力量,在这方面有许多深入的参考资料可供评估。

就本框架而言,重要的是要认识到有一系列的方法,并且您的决策过程的一部分应考虑特定决策环境下最合适的方法组合。

Victor RodriguezUnsplash 上拍摄

核心流程—决策后

决策审查

所以——你已经采取了所有你能采取的步骤来设计、增强和优化一个决定——现在你已经做出了这个决定。 即使我们认为我们可能完成了,我们仍然可以利用决策智能为决策制定带来的结构,通过回顾我们的决策和保留决策工件来创造额外的价值。

决策回顾

有一种公认的行为偏好,即通过结果的质量来评估决策的质量。这被称为结果偏差,是我们大多数人都容易犯的代价高昂的错误【18】。事实上,尤其是在资源稀缺且有快速前进的诱惑的组织环境中,结果偏差显然会掩盖关于决策过程的重要事实,这些事实会告知我们如何做出决策。

“即使是最好的决策也有很大概率是错误的。即使是最有效的方法最终也会过时”

彼得·德鲁克【19】**

因此,如果我们承认彼得·德鲁克是正确的,即使是最好的决定也可能是错误的,那么我们必须承认好的决定可能会有坏的结果。换句话说,决策的质量应该根据决策过程的优劣来评估。

决策回顾是一个机会,可以询问决策过程是否合理,如果结果不好,可以确定我们是否还可以做些别的事情。请注意,即使有好的结果,决策过程也可能是糟糕的。决策回顾为我们提供了一个将持续改进引入决策框架的机会。

决策工件的保留

记录良好的决策—包括清晰的框架、清晰的设计、潜在的决策图以及数据和分析、经验教训等的链接。—为将来的决策保留和重用所有这些决策构件提供了丰富的机会。类似地,子组件(例如,通过行动-因果关系-结果链接假设和测试的任何信息)可以作为未来决策图的子组件重用。

虽然不是所有的决策都有助于这种类型的知识保留,但是对于那些这样做的人来说,有机会减少创建类似结构的决策所需的未来工作量。

决策工件的保留——在可能的情况下——提供了重用的机会,这可以显著提高将 DI 应用于未来决策的效率。

安德里克·朗菲尔德在 Unsplash 上拍摄的照片

有利因素

虽然上面的章节描述了可以针对决策智能实施进行定制的流程,但是如果没有一个支持将决策智能技术应用到企业环境中的企业环境,这些流程就不太可能成功。

决策文化

决策权对于各组织内部的问责授权至关重要,并通过支持健全的、往往是参与性的决策过程的文化得到加强。这种文化直接与一个组织的领导能力和建立这种文化的意愿联系在一起[20]。事实上,它可以成为一项竞争优势:

制定和执行决策的领导者几乎总能比他们的竞争对手带来更好的财务结果。[……]以决策为中心的公司的一个关键属性是在整个组织中培养伟大的决策和执行的文化,这是最难发展的,但也是持久成功的最重要因素。”【21】**

贝恩公司

不言而喻,一个组织的决策文化(价值观、对决策透明的渴望、持续改进的文化等。)是支持阿迪决策方法的关键促成因素。

来自麦肯锡的一些(相对)最*的研究强调了决策相关问题的普遍性,并进一步提出了一种使决策实践适应所考虑的决策类型的方法【22】。这是一个关于寻求强调决策过程而不仅仅是结果的决策文化的很好的例子。

组织能力

最后,我们可以考虑组织的能力,以及它们在支持利用 DI 原则的健壮的决策方法中的作用。这里,我们讨论的是“经典的”三个组织维度——人员、流程和技术。

首先,也是最重要的,基于数据或证据的决策方法要求劳动力技能与这一目标相匹配。因此,除了开展核心工作所需的技能之外,一个有利组织的技能清单还将包括以数据为中心的角色(分析、数据科学、信息管理等)。),以及支持支持数据工作的技术基础架构所需的技能。

第二,组织过程可以支持对结构化决策和评审的偏好。特别是,治理、风险管理和计划/项目管理为将支持决策的实践构建到企业流程中提供了一个工具。例如,计划相关工作的福利证明的一部分可以包括决策回顾和分析。类似地,对运营或战略风险的缓解可以包括倡导与阿迪相关的战略决策方法。虽然这个领域是特定于组织的,但它提供了将 DI 或结构化决策过程“硬连线”到组织工作中的方法。

最后,组织的技术能力和基础设施对于确保数据资产能够被存储、转换、可视化和分析等至关重要。类似地,这些功能支持存储/检索决策资产,并提供协作功能和工具,这些功能和工具对于举行对决策智能至关重要的引导式讨论至关重要。

裁剪:如何使用这个框架

所有框架流程的应用都需要大量的时间和资源投入。因此,这个框架的更合适的应用将涉及到根据手头的决策来定制框架。

**这是项目管理的核心概念,它提供了一个类似的场景,促进所有可用过程、方法等子集的适当选择。【23】

这个概念很简单:并非所有的决策都需要所有的流程,简单或影响较低的决策需要更小的子集,而更复杂的决策可能需要更多。

接下来是什么?

这篇文章涉及的领域太多,深度太浅。然而,希望它能在一个简单的框架内提供一组连锁的过程,帮助将一些核心的 DI 概念嵌入到企业环境中。我真诚地希望,其他希望将这些想法整合成一个统一的、巩固的 DI 领域的人可以从这篇文章中得到启发,帮助他们思考——即使这个框架被抛弃/塑造/改编。就我个人而言,我将密切关注这一领域,看看我们如何将 DI 成熟为“生产就绪”的实践,以应对我们共同未来的挑战。

承认

我要感谢许多不可思议的聪明人,他们正积极努力为这个领域做出贡献,并围绕决策智能作为一门学科建立一个社区。特别是,我要感谢 Lorien Pratt 博士和 Cassie Kozyrkov 博士的贡献,前者在尝试捕捉 DI 的范围和应用方面取得了巨大进步,后者利用其在技术领域的权威地位显著提升了这一新兴领域的形象。

注意:这里所有的观点都是我自己的

关于作者:

Erik 是产品开发和分析领域的领导者,在加拿大公共部门拥有超过 15 年的经验。他对将数字和科学原理应用于组织面临的日常挑战很感兴趣。

可以在 LinkedIn 上的 这里找到 Erik,或者直接联系erik.balodis@gmail.com

参考

[1] L. Pratt,Link:决策智能如何为更美好的世界连接数据、行动和结果,Emerald Group Pub Ltd .,2019 年。

[2] O'Reilly Media,“AI 编排实现决策智能”,Medium,2021 年 1 月 19 日。【在线】。可用:https://medium . com/oreillymedia/ai-orchestration-enables-decision-intelligence-2 a88d 8306 AC 9。【2021 年 9 月 26 日访问】。

[3] C. Kozyrkov,“决策智能导论”,走向数据科学,2019 年 8 月 2 日。【在线】。可用:https://towardsdatascience . com/introduction-to-decision-intelligence-5d 147 ddab 767。【2021 年 9 月 26 日访问】。

[4] Gartner,“2022 年数据和分析的主要趋势”,Rita Sallam,2022 年 3 月 11 日。Gartner 是 GARTNER,Inc .和/或其附属公司在美国和国际上的注册商标和服务标志,经许可在此使用。保留所有权利。

[5] Gartner,“2021 年分析和商业智能的炒作周期”,Austin Kronz,Peter Krensky,2021 年 7 月 29 日。Gartner 和 HYPE CYCLE 是 GARTNER,Inc .和/或其附属公司在美国和国际上的注册商标和服务标志,经许可在此使用

[6] Gartner,“决策——由您做出决定”, G. Herschel,Gartner 数据和分析峰会,虚拟,2021 年。

[7] A. Tversky 和 D. Kahneman,“决策的框架和选择的心理学”,《科学》,第 211 卷,第 30 期,第 453-458 页,1981 年。

[8] C .科济尔科夫,“伟大决策者做的第一件事”,《哈佛商业评论》,2019 年 6 月 25 日。【在线】。可用:https://HBR . org/2019/06/the-first-thing-great-decisions-do。【2021 年 9 月 26 日访问】。

[9]加拿大政府,“支持有效的评价:制定绩效衡量战略指南”,[在线]。可用:https://www . Canada . ca/en/treasury-board-secretariat/services/audit-evaluation/centre-Excellence-evaluation/guide-developing-performance-measurement-strategies . html # logic model。【2021 年 9 月 26 日访问】。

[10] R. S. Kaplan 和 D. P. Norton,“平衡计分卡——推动绩效的措施”,《哈佛商业评论》,,1992 年 1 月-2 月。

[11]n·贝内特和 g·j·莱莫因,《VUCA 对你真正意味着什么》,《哈佛商业评论》,【2014 年 1-2 月。**

[12] A .亚历山大、a .德斯梅特和 l .韦斯,“不确定时期的决策”,麦肯锡公司,2020 年 3 月 24 日。【在线】。可用:https://www . McKinsey . com/business-functions/organization/our-insights/decision-making-in-uncertain-times。【2021 年 9 月 26 日进入】。

[13] J. Chen,“预期效用”,Investopedia,2021 年 5 月 7 日。【在线】。可用:https://www.investopedia.com/terms/e/expectedutility.asp.【2021 年 8 月 26 日获取】。

[14] M .谢默,“拉姆斯菲尔德的智慧”,《科学美国人》,2005 年 9 月 1 日。【在线】。可用:https://www . scientific American . com/article/rumsfelds-wisdom/。【2021 年 8 月 26 日获取】。

[15] A. Mantovani,“已知的已知、已知的未知、未知的未知和领导力”,媒体,2020 年 4 月 28 日。【在线】。可用:https://medium . com/@ andreamantovani/known-knowns-knowns-unknowns-unknowns-unknowns-leadership-367 f 346 b 0953。【2021 年 8 月 26 日获取】。

[16] Gartner,“决策——它们是您做出的决策”,G. Herschel,Gartner 数据和分析峰会,虚拟,2021 年。Gartner 是 GARTNER,Inc .和/或其附属公司在美国和国际上的注册商标和服务标志,经许可在此使用。保留所有权利。

[17] J. B. Soll,K. L. Milkman 和 J. W. Payne,“智取你自己的偏见”,《哈佛商业评论》,第 64–71 页,2015 年 5 月。

[18] G. Francesca,“当我们以结果来判断一个决定时,我们错过了什么”,《哈佛商业评论》,2016 年 9 月 2 日。【在线】。可用:https://HBR . org/2016/09/what-we-miss-when-we-judge-a-decision-by-outcome。【访问 2021】。

[19] P. F .德鲁克,“有效决策”,《哈佛商业评论》,* 1967 年。*

[20] D. Waller,“创建数据驱动文化的 10 个步骤”,《哈佛商业评论》,2020 年 2 月 6 日。【在线】。可用:https://HBR . org/2020/02/10-创建数据驱动文化的步骤。

[21] M.W. Blenko、P. Rogers 和 P. Meehan,“创造以决策为中心的文化”,贝恩公司,2011 年 4 月 15 日。【在线】。可用:https://www . bain . com/insights/decision-insights-7-create-a-decision-focused-culture/。

[22] A .德斯梅特、g .约斯特和 l .韦斯,“更快、更好决策的三个关键”,2019 年 5 月 1 日。【在线】。可用:https://www . McKinsey . com/business-functions/people-and-organization-performance/our-insights/three-keys-to-fast-better-decisions。

[23] S. Whitaker,“剪裁的好处:使项目管理方法适合”,PMI 白皮书,2014 年 9 月。【在线】。可用:https://www . PMI . org/learning/library/tailoring-benefits-project-management-methodology-11133。

了解低质量数据如何损害业务绩效的框架

原文:https://towardsdatascience.com/a-framework-to-understand-how-low-quality-data-hurts-business-performance-386c10c4fe1e

图片由作者提供。

作为数据从业者,我们应该如何思考数据质量对我们所支持的业务的重要性和影响?

数据质量问题的具体成本因业务和垂直行业而异。但是,平均而言,低质量的数据每年要花费组织大约 1300 万美元 (Gartner,2021)。

这个数字应该让数据领导者(以及他们所支持的高管层领导者)刮目相看。

虽然数据质量差的负面影响可能会联合各垂直行业的数据领导者,但这些问题的原因与数据团队支持的每个产品或服务一样独特。

正如托尔斯泰所说,“每个幸福的家庭都是一样的,但每个不幸的家庭各有各的不幸。”我以同样的方式考虑数据质量。每一个负面的商业结果都有其负面的一面。

作为一名数据领导者,你有责任在你的业务 KPI 和目标范围内,以一种对业务利益相关者有意义的方式将数据质量放在上下文中。毕竟,权力越大,责任越大,这包括确保低质量的数据不会损害你企业的声誉和底线。

内在(独立于用例)和外在(依赖于用例)数据质量维度的例子。图片由作者提供。

在考虑数据质量的影响时,需要考虑两件事

除了上面那个吓人的大数字,您应该如何看待数据质量对您业务的影响?答案取决于两件事:

  1. 数据本身的质量:虽然完美的数据在现实世界中并不存在,但你应该能够确定什么样的数据对你的公司来说是“足够好”的。
  2. 组织如何整体使用数据:没有单一的“正确方法”来使用数据。相反,您应该创建对您的独特用例有意义的数据管理最佳实践。

例如,B2B SaaS 公司中不正确的客户数据可能会导致错误,比如在错误的时间向某人发送不相关的产品推荐。这损害了你以客户为中心的声誉,从长远来看,还会损失你的收入。然而,医疗保健行业公司的不正确客户数据可能会导致开出引发致命反应的药物,严重伤害患者,引发诉讼和一系列负面宣传。

从这个例子中,我们可以看到,在一个上下文(B2B)中糟糕的数据质量是重要的,但不是致命的。换句话说,有更多的误差空间。另一方面,处理医疗数据的数据团队需要更加严格的数据质量防护。这就是说:并非所有的数据质量后果都是相同的。背景很重要。

下面,我来解释一下:

  • 公司使用数据的四种主要方式
  • 您可以使用三部分框架来确定数据质量如何影响您组织中的业务绩效
  • 如何预防和解决数据质量问题(在它们影响您的业务之前)

公司使用数据的四种主要方式

大多数公司使用数据的方式有四种:忽略数据、将数据用于运营、利用数据制定战略,或者将数据作为产品出售。

除了忽略它(没有不使用数据的“好”方法),数据质量影响所有这些方法。高质量的数据是一种竞争优势,而低质量的数据充其量是一种阻碍。让我们仔细看看这些数据的用途:

  1. 完全不使用数据。尽管围绕“数据驱动型”组织大肆宣传(以及高质量数据提供的竞争优势),但这种情况却出奇地普遍。根本没有不使用数据的好方法,所以这是列表中的异常值。
  2. 将数据用于作战目的。这包括分配广告支出等内部运营和客户沟通等外部运营。高质量的数据有助于您的营销团队锁定最有可能购买的人群,有助于您的物流团队高效地运送产品,并有助于您的客户成功团队提供个性化服务,创造疯狂的粉丝。应用得好,高质量的数据可以最大化收入,同时最小化成本。
  3. 利用数据影响内部产品决策和市场策略。拥有最佳数据的竞争对手首先获得机会。有了可靠、高质量的数据,产品团队可以跟踪趋势,并率先开发客户想要的功能。当风向改变,是时候改变航向时,他们也会有先见之明。好的数据有助于你的公司在最有利的时候采取市场行动。
  4. 将数据作为产品使用和销售。如果你从事销售第三方数据的业务,数据质量的重要性显而易见。干净、安全、可靠的产品可提高客户满意度和忠诚度,减少法律责任,并让您站在监管机构的正确一边。

归结起来,每个垂直领域的每个业务都做三件事:花钱、赚钱、冒险。当您将数据绑定到这些活动之一时,数据的质量就变得至关重要。

如果您的企业使用数据做任何事情,数据质量会影响业务绩效。唯一的问题是怎么做。

一个由三部分组成的框架,用于确定数据质量如何影响您的业务

很容易看出糟糕的数据质量对其他公司的影响。

2021 年,Zillow 的机器学*算法出现问题导致超过 3 亿美元的损失。大约一年前,表格行数的限制导致英国公共卫生局少报了 16000 例新冠肺炎感染病例。当然,还有火星气候轨道飞行器的经典警示故事,一艘价值 1.25 亿美元的宇宙飞船在太空中失踪,原因是公制和英制测量单位之间的差异。

看到糟糕的数据质量如何影响你自己的公司可能会更具挑战性(特别是如果你试图在出现大的公共问题之前提高数据质量)。我发现使用三部分框架来考虑数据质量很有帮助:

1.你根本不用数据,所以数据质量无所谓。在框架的这个层次上有两种公司——不使用数据的公司和(目前)不使用数据的公司。随着现代数据基础设施变得更加可行,以及使用数据的企业开始获得竞争优势,第一阵营的企业正在迅速减少。

第二种方法至少有一个优点:您可以从一开始就将数据质量保证融入到您的业务数据策略中。虽然可以将数据质量保证延迟到问题发生之后,但最好开始收集相关信息以防止问题发生,并在问题(不可避免地)发生时最大化上下文。

2.您使用数据来推动业务决策和战略,这意味着糟糕的数据质量的成本就是错误决策或失去信任的成本。例如,低质量的市场数据可能会让你在一个中等收入不足以支撑他们的地区开设新的地点。或者,如果客户成功经理对其报告仪表板中的数据产生怀疑,那么他向高价值客户分享错误数据是合理的。

像时间一样,战略决策和信任很容易失去,也很难重新获得。如果失去了与业务利益相关者的信任,数据团队必须花费宝贵的精力来回答临时请求并建立额外的保证。如果没有重新获得信任,风险承担者开始构建他们自己的“影子”数据堆栈的情况并不罕见,这可能会完全破坏数据团队的目的。

3.您使用数据来通知业务运营或产品体验,这意味着不正确数据的成本是浪费时间和金钱或客户信任的成本。例如,不完整的客户数据会导致您的营销部门瞄准错误的买家,将广告预算花在无法转化的活动上。在最好的情况下,这会导致浪费花费和时间来纠正问题。在最坏的情况下,收到不相关或不正确信息的客户开始对公司的能力失去信任。

在最高层次上,企业的存在是为了增加收入、减轻风险和降低成本。如果您的公司使用数据来推动运营或产品,那么您就处于一个令人羡慕的位置,可以将数据直接与公司的成功联系起来。然而,权力越大,责任也越大,这意味着数据质量下降的成本现在可能与业务中的其他职能一样重要,甚至更重要。

数据质量如何影响您的业务取决于您是否使用数据以及如何使用数据。图片由作者提供。

值得注意的是,并不是所有的负面因素都有美元符号。低质量的客户数据也会损害你的声誉和关系。ParcelLab 在进行的一项研究中,*一半的受访者表示,当品牌糟糕的数据质量导致推荐他们已经购买的产品时,他们感到沮丧,*四分之一的人表示,他们永远不会再购买向他们发送不相关信息的品牌产品。

通过关注整个组织的数据质量,您降低了业务流程中错误决策的风险,这有助于您做出更明智的战略决策,取悦客户,并且让利益相关者对基于数据的决策更有信心

如果这篇文章引起你的共鸣,我们很乐意聊天!你可以把手伸向 凯文 ,团队在 元位面 ,或者 在这里预定一个时间

一个免费的在线主成分分析工具,提供完整的图形输出

原文:https://towardsdatascience.com/a-free-online-tool-for-principal-components-analysis-with-full-graphical-output-c9b3725b4f98

用它来理解 PCA 并应用到你的工作中

完全在你的浏览器上运行,你不需要下载或安装任何东西,你的数据会留在你的电脑上。该工具是完全免费的,包括两个预设的示例来帮助您了解该技术,并生成全文和图形输出。

跳转到: PCA 概括地说 | 为什么 web?||web app|例 1|例 2|还有什么? | 读数

突然,上周有两个人对我很久以前在网上发布的一个运行主成分分析的网络应用感兴趣,并想了解更多关于这种计算是如何工作的。因此,这是对 web 应用程序进行一些更新的最佳时机,将其恢复原状,并撰写两篇文章:这篇文章介绍了该工具并展示了两个具体的示例,另一篇文章即将推出,以清晰简洁的方式解释数学和代码(有许多页面试图这样做,但没有一个让我满意,显然没有一个对联系我的人来说足够好!).

简单地说,主成分分析

(向下滚动到最后以获得更多资源,或者在这里查看我的 描述 PCA 如何一步一步工作的新文章 )

PCA 是一种用于减少数据集中的维数同时保留最重要信息的技术。为此,它将高维数据线性地投射到其变异的主要成分上,称为主成分(PC)。它可用于识别数据集的底层结构或降低其维度。

在实践中,您使用 PCA 来简化复杂数据的显示,方法是仅在 1、2 或 3 维(最常见的是在 2 维,以二维图的形式)上展开数据;找出哪些原始变量对数据集中的可变性贡献最大;还可以通过从其主 PC 重建数据集并忽略包含更多噪声的那些来去除噪声或压缩数据(例如图像);以及可能的一些其他相关应用。

我开发的 web 应用程序可以在下面的链接中免费使用,它采用一组由变量列表描述的对象,并计算这些变量的线性变换,以产生压缩信息的新变量(PCs)。PC1 将解释数据集中的大部分差异,PC2 将解释剩下的大部分差异,依此类推。web 应用程序在一个文本框中提供所有数字输出,您可以从中复制;它还产生大多数应用程序所需的三种主要图形:投影到两个主 PC 上的原始数据,显示每个原始变量对每个 PC(对于 3 个主 PC)的贡献大小的加载图形,以及测量每个 PC 解释的总方差的特征值。

为什么选择 web?

如果你关注我,你已经知道我是客户端 web 编程的强烈拥护者。从数字数据分析和模拟到数据废弃、文本分析和合成、加密、甚至增强和虚拟现实,我喜欢为网络浏览器编写所有这些代码。几个例子项目和阅读:

https://pub.towardsai.net/interactive-augmented-reality-web-apps-to-enable-immersive-experiences-for-science-education-dce51889473f https://medium.com/age-of-awareness/metaverse-not-sure-but-webxr-hell-yes-12af5b302e08

在线免费 PCA 的网络应用程序

我开发的工具在https://lucianabriata . alter vista . org/jsin science/PCA/PC a5 . html

它的布局是为大屏幕设计的,但它也可能在大屏幕的平板电脑和智能手机上显示 ok。

要使用这个工具,只需进入https://lucianabriata . alter vista . org/jsin science/PCA/PC a5 . html,然后在左边输入数据,在右边输入一些物体名称,点击 运行

更准确地说,该工具执行协方差矩阵的 PCA,这是最标准的 PCA。我还有另一个对未处理的数据本身运行 PCA(实际上只是 SVD)的工具:https://lucianabriata . alter vista . org/jsin science/PCA/PC a3 . html

如何格式化输入的数据?正如您在步骤 1 中看到的红色,数据是作为一个常规表格引入的,您可以从任何电子表格程序中复制粘贴。输入表的每一列是一个对象(或“观察值”,或样本),每一行是一个变量(或“描述符”)。

例如,如果数据集包含从 400 到 800 纳米波长范围内每 1 纳米收集的 20 个样品的光谱(读取 401 个波长),那么矩阵将有 20 列和 401 行。

对象名是干什么用的?当您将鼠标悬停在投影到 PC 空间的点上时,将在结果中显示对象名称。此外,名为 obj1、obj2、obj3、obj4 和 obj5 的对象被指定了特殊颜色,因此您可以使用此功能来查看不同对象在投影尺寸上的映射。例如,如果您预期有两类对象,您可以将它们称为 obj1 和 obj2,然后在投影图中分别以红色或绿色看到它们。

自然,对象名称的数量必须与数据集中对象的数量相匹配。因此,对于从 400 到 800 纳米测量的 20 个光谱的例子,你必须列出所有 20 个物体名称。(注意,如果您没有特殊的名称或标志要分配,您可以同样地调用它们,例如 obj3 以蓝色显示它们)。

运行和解释内置示例

当您访问该网页时,您会看到一个预加载的示例。通过点击加载示例可以加载更大的数据集。让我们来看看这个预加载的小例子。

预加载的示例与您可以在许多关于 PCA 的在线资源中找到的示例相同,包括我在下面留下的一些链接。它由 5 个对象组成,每个对象由 3 个变量描述。

这些对象没有先验关系或分类,因此它们被简单地标记为 obj1 到 obj5。我可以只标注相同的内容,例如 obj1 表示红色,obj3 表示蓝色。请注意,如何标记对象不会影响这里的任何计算,因为 PCA 是一种无监督的方法。

加载了此示例的应用程序如下所示:

带有预加载示例的 web 应用程序,当用户打开它时会显示该示例。这张图和所有其他图都是作者卢西亚诺·阿布利亚塔的。

当你点击 运行 时,你会得到如下结果(缩小一点以适合一个屏幕截图中的所有内容):

让我重新排列这张截图,以便更好地可视化。我现在还将鼠标悬停在 PC 图的一个数据点上,以查看那是什么对象(这里是 obj4)。

我们如何解释这一点?PC 图(PC1 对 PC2)将点从三维空间投影到二维空间。我们看到红色和粉红色的物体看起来最接*,这意味着它们可能是最相似的两个。接下来是蓝色;另外,我们看到它接*平均值(平均值为 0,0,因为程序将数据居中)。青色和绿色似乎是最不同的一对。

第二幅图(标题为“系数”)显示了每个变量对每个 PC 的贡献。例如,PC1(蓝色)由所有三个变量的正权重组成;PC2(红色)由来自变量 1 和 2 的一些负贡献和来自变量 3 的正权重组成。并且 PC3 包含来自变量 1 的负贡献、来自变量 2 的正贡献和来自变量 3 的小贡献。注意,积极和消极在这里并没有什么特别的意义。然而,当贡献为 0 或接* 0 时,这可能有意义;例如,变量主要贡献噪声。

第三个图显示了分解的特征值。较高的柱意味着 PC 解释了数据集中更多的总变化。通常,对于二维投影,您希望第一个和第二个组件尽可能多地解释变化(高杠),不给下游组件留下任何东西(可能是一些噪声)。这种情况在这里发生得很好,但并不总是如此,尤其是因为 PCA 受到其固有的线性性质的限制。

另一个例子

第二个例子包括由 20 个变量描述的 21 个对象。每个对象大概对应于 3 个类中的一个,因此命名为 obj1、obj2 和 obj3。

当你对数据集进行主成分分析时,你实际上获得了清晰的分离:

通过检查绘图中的原始数据集,其中每条曲线根据类别具有一种颜色,您可以更好地理解“系数”绘图的作用:它强调哪些变量导致对象不同,并抑制那些看起来相同的对象:

这是具有相当显著特征的合成数据:第一类对象在变量#6 附*具有单峰,第二类对象在变量#15 附*具有峰,第三类对象具有两个峰。

还有什么?

我们有哪些变化?像现在这样,我的程序只对数据进行均值居中。但是您可以根据输入数据表的性质,以各种方式对其进行预处理。例如,如果您正在处理跨越多个数量级的变量,那么对数变换可能会很有用。标准化(即减去每个变量的平均值,然后除以其标准偏差)也可以使输入数据更加平滑。

另一种变化是不像这里那样运行 PCA,而是按原样运行输入数据的 SVD(奇异值分解)。你可以在我做的另一个工具里做这个

局限性?与任何基于 SVD 的技术一样,计算涉及线性代数,因此 PCA 无法解析非线性关系。虽然输入数据的一些转换可能会对您有所帮助,但对于某些应用程序,您很可能需要一些其他的非线性方法。

另一个限制是我的程序的网络本质所固有的。它在处理大型数据集时效率不高,对于中等规模的数据集可能需要几秒到几分钟。对于更复杂的问题,你可能会发现使用专门的程序会更好。

阅读——直到我发表我的文章

我自己的逐步理解 PCA 的指南,包括低级代码,您可以在 web 浏览器中尝试、编辑和使用,以掌握 PCA 的最佳细节:

这里有一些其他资源。对我来说,没有一个足够清楚地涵盖了整个事情,但是您至少可以获得一些步骤的编写良好的部分:

https://en.wikipedia.org/wiki/Principal_component_analysis#Computing_PCA_using_the_covariance_method

www.lucianoabriata.com我写作并拍摄我广泛兴趣范围内的一切事物:自然、科学、技术、编程等等。 成为中级会员 访问其所有故事和 订阅获取我的新故事 通过电子邮件 (平台的附属链接,我可以免费获得小额收入)。到 咨询关于小职位 查看我的 服务页面这里 。或者 联系我这里

沃尔多的一句话

原文:https://towardsdatascience.com/a-frequency-analysis-on-wordle-9c5778283363

克劳德·里士满在 Unsplash 上的照片

在过去的几周里,这个游戏赢得了社交媒体的青睐。Wordle 基本上是一个单词游戏,玩家尝试在 6 次猜测中猜出一个 5 个字母的单词,玩家逐渐获得更多关于目标单词的信息。这款游戏由艺术家兼工程师乔希·沃德尔创作。当玩家提交第一个 5 个字母的单词时,Wordle 开始。每次提交单词时,都会对提交单词的每个字母提供反馈,指示该字母是否存在于目标单词中,以及该点是否与目标单词中的相匹配。下面是说明截图。

Wordle 的规则(作者截图)

一个好策略

有没有玩游戏的好策略?显然,在输入第一个单词之前,玩家没有关于该单词的信息,它可能是大约 15000 个 5 字母英语单词中的一个。然而,一旦第一个单词被提交,玩家将获得更多关于目标单词中涉及的字母的信息,这取决于输入的单词。一旦玩家开始收到反馈,有没有好的策略?也许有一个。在提供了对第一个单词的反馈后,成功将取决于许多因素,包括玩家的词汇量以及他们如何根据反馈缩小下一个猜测的范围。然而,第一个词的选择与玩家的词汇或语言技能无关。这就是为什么,我们也许可以谈论在第一个单词被提交后提供最佳反馈(尽可能多的信息)的策略。基本上,对于第一个输入的单词,一个好的策略是尽可能多地删除剩余的字母。更好的是,对于第一个输入的单词,一个好的策略是能够确定目标单词的尽可能多的字母,并且这些字母的位置尽可能正确。在这个分析中,我试图找到一个策略,或者更确切地说,一个词,可以服务于这个目的。

仔细看看上面的字

基于维基百科上的这篇文章,韦氏第三版新国际英语词典包含 47 万个词条。但是,这些单词中的一部分已经过时,或者可能不属于只包含字母(没有数字或符号)的有效单词类别。我在 Github 上的这个库里找到了一个这样的词的数据集。该文件包含 370,103 个英语单词,这些单词是单个的,并且只包含字母。从这个列表中只提取了 5 个字母的单词后,我剩下了一个 15,918 个单词的列表。我将研究这个列表,希望能对输入到 Wordle 中的第一个单词有更好的策略。也许与这个小项目无关,但我很好奇,想找到基于字母数量的词频分布,结果如下。显然,频率是单峰的,峰值出现在 9 个字母的单词上。5 个字母的单词仅占该列表中所有单词的大约 4.3%。

词频(图片由作者提供)

接下来,我将回顾两种不同的策略,元音策略和频率策略。我将证明频率策略是一个更好的策略,我们将根据频率策略选择最佳的词。

元音策略

当试图提出一种策略来消除每一轮中的大量单词时,元音起着重要的作用。这是因为这个单词的每个音节中至少有一个元音。元音共有 5 个: AEIOU 。尽管字母 Y 在一些单词中可以充当元音,但我不认为它是元音。从元音开始搜索可能是一个好主意,因为英语中的每个字母都必须至少有一个元音(这并不是 100%正确,稍后我们会发现,我们将能够找到 8 个没有任何元音的单词,尽管这种策略的优点并不存在问题)。

我开始在 5 个字母的单词列表中搜索,找到有一个、两个、三个、四个和五个独特元音的单词的数量。例如,单词 asana 只有一个独特的元音,单词 alibi 有两个。结果是,分别有 6223、8568、1055、18 和 0 个单词有 1、2、3、4 和 5 个独特的元音。例如, adieuauloi (古希腊管乐器 Aulos 的复数)、 Aequi (古代意大利部落)和 uraei (埃及眼镜蛇的直立形态的复数)都有 4 个独特的元音。不用说,没有只由元音组成的 5 个字母的单词。

还有 46 个 5 个字母的单词,其中字母 Y 充当元音,例如,在单词 ghyll ( 英国北部的深谷或狭窄的山谷Scyld (一个传说中的丹麦国王)中。还有 8 个单词没有任何元音,如 crwth ,这是一种弦乐器。

考虑到元音在英语中的重要性,基于元音的策略是使用包含尽可能多的独特元音的第一个单词。这将帮助我们确定目标单词中是否存在尽可能多的元音。如上所述,没有只由元音组成的 5 个字母的单词。然而,有 18 个单词由 4 个独特的元音组成。这些词包括: adieuaequiaoifeaue toauetoauloiaureiavoueheiaukioealouie

有人可能会说,这 18 个单词中的任何一个都是 Wordle 的良好开端。然而,让我们看看这 5 个元音中的任何一个在 5 个字母的单词中是否出现得更多/更少。下面显示了 5 个元音在 5 个字母单词中的出现频率(不包括唯一的出现,即字母 A ,单词 asana 计为 1)。

元音频率(图片由作者提供)

上图显示元音 U 是 5 个元音中出现频率最低的一个。从包含 4 个独特元音的 5 个字母单词列表中过滤掉包含 U 作为元音的单词,我们只剩下两个单词列表, Aoife ( 一个爱尔兰女性名Kioea ( 一种在 19 世纪灭绝的夏威夷鸟)。快速搜索列表显示,辅音 K 出现在 1663 个 5 个字母的单词中,而辅音 F 出现在 1115 个单词中。因此,这种策略会让人联想到“T21”这个词。值得一提的是,这种策略完全忽略了单词中元音的位置,只决定了目标单词中元音的有无。我们将在下一节看到,频率策略如何优于元音策略。

频率策略

之前的策略只关注元音。然而,这个策略将集中在所有的字母上。我们将评估字母表中最常用的字母,还将确定 5 个字母单词中最常用字母的最常见位置。基于这些,我们将决定最好的单词首先进入游戏。

我在数据集中的 5 个字母的单词中找到了字母表中每个字母的出现频率,并将它们从最大到最小排序。下图显示了频率。

信件频率(图片由作者提供)

在上图中,一个单词中出现一个字母计为 1。所以我决定看看每个单词字母的平均频率,看看它是否与上面的有什么不同。查看 5 个字母单词中字母的平均出现频率,我没有看到字母顺序有任何差异,从最常见到最不常见排序(见下文)。

信件的平均频率(图片由作者提供)

这意味着五个字母单词中最常用的字母(就总频率和平均频率而言)是字母 AESORILT 等。我决定关注前六个字母,因为在第六个字母之后,平均频率显著下降。有 96 个单词仅由这些字母组成(允许重复)。然而,如果我们同意第一个字母的目的是消除尽可能多的剩余字母(或确定目标单词中尽可能多的字母),也许我们应该限制字母的重复。如果我们不允许重复,这个列表将减少到只有 12 个单词。这些词是:aesir,aries,rise,rise,ireos,oreas,orias,osier,raise,seora,serai 和 serio。这 12 个单词中哪一个是 Wordle 中最好的第一个单词?

为了回答这个问题,我决定看看前六个字母在 5 个字母单词(第一个字母,第二个字母,等等)的每个位置出现的频率。).结果如下所示。

每个位置的字母频率(图片由作者提供)

我还计算了 5 个字母单词中前 6 个字母的平均频率,看它是否与绝对频率有显著差异,但结果并没有不同。平均频率是通过将绝对频率除以 5 个字母的单词的数量来计算的,其中特定的字母出现在特定的位置。平均频率图如下所示。

每个位置的平均字母频率(图片由作者提供)

这表明,例如,字母 S 经常作为第五个字母出现在 5 个字母的单词中,但它几乎从不作为第三个字母出现。在此基础上,我使用了一个简单的评分系统来给每个单词打分,它基本上由基于上述结果的字母的平均频率之和组成。该评分系统将假设 6 个字母的价值相等,并将只关注每个点的频率。例如,字母 aesir 的分数将被计算为大约 0.1619+0.2928+0.1162+0.2771+0.1840 = 1.032,因为字母 A 在第一点的平均频率是 0.1619,字母 E 在第二点的平均频率是 0.2928,等等。下表和下图显示了列表中所有 12 个单词的计算得分。

热门词汇得分(作者图片)

基于这种分析,单词 白羊座 (拉丁语为 ram) 具有最高的计算得分。研究表明,如果将单词 Aries 作为输入到 Wordle 的第一个单词,平均来看,可以确定目标单词中的最大字母数。

白羊座是拉丁语中公羊的意思。照片由上的车轮带动

测试

为了测试 Aries 识别目标单词中字母的有效性,我从 5 个字母的单词列表中随机选择了 5000 个单词,并计算了当单词 Aries 被用作 Wordle 上的第一个单词时,平均会显示多少个字母。这个过程我重复了 10 次。下面示出了在将白羊座用作第一单词之后识别的目标单词中存在的字母(每个单词)的平均数量在 2.055 和 2.1 之间。请注意,下面的结果不会区分字母,哪些点被正确识别,哪些没有被正确识别。它仅仅包括在目标单词中识别的所有字母。换句话说,输入单词后所有变成金色和绿色的字母。

当 Aries 用作第一个单词时识别的平均字母数的模拟结果

我对 Kioea 这个词进行了同样的分析(这是我们元音策略建议的),结果平均只有 1.79 个字母被识别出来。这表明频率策略在指示目标单词中的字母方面优于元音策略。

接下来,我计算了平均字母数(每个单词),其在目标单词中的实际位置被单词 Aries 正确识别。这意味着,不仅字母被识别,而且它在目标单词中的位置也被正确识别。换句话说,这是输入单词后变成绿色的字母的平均数量。为了进行模拟,我再次使用了 10 个重复,每个重复中随机选择了 5000 个单词。下面显示了白羊座的结果。

当 Aries 用作第一个单词时,正确识别的字母的实际点的平均数量的模拟结果

我对热门词汇列表中的所有 12 个单词进行了同样的分析,看看它们中是否有任何一个能击败白羊座。正如所料,单词 Aries 显示了平均字母数(每个目标单词)的最高值,其斑点被正确识别。对于这一分析,我也使用了 10 次重复和每次重复中随机选择的 5000 个单词,并报告了所有 10 次重复的平均值。

对于顶部单词列表中的所有单词正确识别的字母的实际点的平均数量的模拟结果

正确识别的字母位置的平均数量(作者图片)

基于该研究的结果,如果用作第一个单词,单词 Aries 平均可以正确识别大约 2.07 个字母的存在,并且平均大约 0.6 个字母的正确点将被正确识别。

结论和说明

一个旅舍。mostafa meraji 在 Unsplash 上拍摄的照片

我后来意识到,不幸的是,白羊座不在 Wordle 的可接受单词列表中,也不是列表中的下一个最佳单词欧里亚斯塞里奥(基于上面确定的单词分数)。榜单上第二好的单词是 serai,这是商队旅馆的另一种说法,确实在 Wordle 的被接受单词列表中。名字的由来是波斯语和土耳其语,读音略有不同( saraysarāī,亦见caravan serai))。在我们的测试模型中识别的字母和字母点的平均频率方面, seraiAries 在正确识别的目标单词中具有相同的字母平均频率(平均大约 2.07 个字母)。然而,单词 serai 被正确识别的字母点的平均频率略低(大约为 0.47,相比之下 Aries 为 0.58)。在下面,你会看到 serai 被用作 1 月 16 日单词的第一个单词,识别出 3 个字母的存在,其中两个字母的位置被正确识别。

1 月 16 日,serai 被用作 Wordle 上的第一个单词(图片由作者提供)

总之,我不确定 Wordle 的选词是否是一个完全随机的过程。你可能会争辩说,有些单词可能与日常全球事件有某种关联(参见此处2022 年过去的单词列表)。毕竟,基于分析或策略的游戏可能并不有趣。

祝大家 Wordling 快乐(虽然 Wordling 可能不在 Wordle 的可接受单词列表中)!

半监督学*的友好介绍

原文:https://towardsdatascience.com/a-friendly-intro-to-semi-supervised-learning-3783c0146744

如何在深度学*管道中额外使用未标记的数据来提高模型的性能

Unsplash 上由 Bernd Dittrich 拍摄的照片

在大多数学术或业余爱好深度学*挑战中,你最有可能面临的是众所周知的、已经标记的数据集,这些数据集可以直接输入到你自己的模型中。为了让事情变得更简单,研究人员通常依赖标准数据集,如 CIFAR-10、SVHN、ImageNet、和许多其他数据集来评估他们的架构。这样做的好处是,昂贵的数据收集和标记工作已经由他人完成,因此您不必担心。

然而,在实际用例中,当您需要收集自己的数据时,事情可能会变得更加麻烦。数据收集过程通常是深度学*项目中最耗时和最关键的部分之一。你可能听过这句话

“垃圾进,垃圾出。”

之前,对于神经网络来说尤其如此。错误标记的数据点或训练数据中的偏差会大大降低模型的预测能力,并使整个管道变得无用。因此,通常很难在合理的成本和时间内获得足够多的高质量样本来训练神经网络。

如果仔细观察获取训练数据的过程,就会很快意识到,并非所有部分都同样适用。在许多情况下,记录成千上万个样本是很容易的。只有下面的标记过程使得获得许多样品的成本如此之高。因此,在实践中,只有一小部分可用数据实际上用于训练神经网络,有效地丢弃所有未标记的样本。这种培训通常被称为监督学*。相反,仅使用没有任何标签的数据点(例如用于聚类)被称为无监督学*

随着模型变得越来越大,需要大量的数据才能收敛,问题不可避免地出现了,这些未标记的样本是否可以在训练过程中得到利用。

这种训练计划被称为半监督学*,因为它结合了监督和非监督学*的核心概念。

本文将提供半监督学*的友好介绍,并解释其核心概念。我们开始吧!

基础知识

半监督学*在实践中。粗线表示通过监督学*获得的决策边界。虚线显示了半监督情况的边界。圆点是未标记的数据点,三角形/加号是标记的数据点。图取自 van Engelen 等人(2018 年)

上图显示了所有三种学*方案的实施情况。圆圈代表两个类别的未标记数据点。圆形和三角形对应于标记的样本。为了让未标记的样本有用,我们必须假设它们仍然包含对我们有用的信息。更精确地说:

潜在的边际分布 p(x) 应该提供关于后验 p(y|x)的有用信息。

为了使半监督训练起作用,我们必须依赖三个主要假设:

平滑度假设

它指出,如果两个样本 x1x2 在输入空间中接*,则它们应该共享相同的标签。例如,假设有一个描述汽车重量和油耗的数据集。这两个特征的值较小的样本很可能代表紧凑型车,而值较大的样本往往对应于 SUV。当我们也考虑未标记的数据时,这个假设很方便,因为我们希望它们共享其最*的标记邻居的标记。

低密度假设

从光滑性假设可以直接推导出另一个前提。类别之间的判定边界应该位于输入空间的低密度区域。这意味着它应该位于一个有少量标记和未标记样品的区域。如果它位于具有高密度的区域中,则将违反平滑度假设,因为在输入空间中接*的样本将不再共享相同的标注。

流形假设

机器学*任务的数据通常是高维的。但是,并非所有特征都显示相同的方差水平,这使得它们对模型不太有用。因此,高维数据往往位于一个低得多的维流形上。该信息可用于推断未标记样本的类别。

这三个假设建立了几乎所有半监督学*算法的基础。

它实际上是如何工作的

最*的半监督学*算法的一个特点是,它们都基于两个范例中的一个(有时甚至两个都是)。

第一种范式被称为伪标记,,它使用网络本身来为未标记的数据生成基础事实标签。要做到这一点,模型通常要用需要获得的全标记子集进行预处理。然后将未标记的样本输入网络,并记录它们的分类预测。如果样本的最大类别概率超过设定的阈值,则相应的类别被用作基本事实。然后,这些样本可用于以受监督的方式训练模型。随着模型的性能变得越来越好,人工获得的标签可以使用完全相同的技术迭代地改进。

第二种范式被称为一致性正则化,训练模型在输入同一幅图像的两个略有不同的版本时输出类似的预测。在许多情况下,原始图像的这些扰动版本通常是使用数据增强方法获得的,例如旋转、移位、对比度改变或许多其他技术。这种训练允许模型更好地概括并且更健壮。因为我们只是简单地实施相似的预测,所以在这种情况下不需要类标签。因此,未标记的数据可以照原样使用。

摘要

半监督学*可用于数据容易获得但难以标记的应用中。它利用标记和未标记的数据来生成一个模型,该模型通常比以标准监督方式训练的模型更强大。这些算法通常基于伪标记和/或一致性正则化。

不过,有一个免责声明:即使半监督训练经常比标准监督训练有所改进,也不能保证您自己的应用程序也是如此。研究表明,在某些有限的情况下,它甚至可能导致性能下降。

如果你想了解更多关于半监督学*算法的实际实现,比如 FixMatch ,请关注我未来的文章!

一个广义量子线性函数

原文:https://towardsdatascience.com/a-generalized-quantum-linear-function-e73b6b7c3c80

了解量子数据科学的基本构建模块

量子机器学*要不要入门?看看 动手量子机器学*用 Python

上周,我们开发了我们的算法来创建一个量子线性函数。它获取斜率和截距,并相应地准备量子位测量概率。但是,目前的版本只适用于两个量子位。

在今天的帖子中,我们将该函数推广到任何数量的量子位。

但首先,让我们简单地看看算法如何对两个量子位产生以下结果。

作者图片

我们把所有的值分成相等的块。在最简单的情况下,块的大小代表斜率和截距(当斜率=截距时)。

然后,第一个状态(00)有一个块(因为我们把这个状态解释为值 0),两个后续状态相差一个块。我们每走一步就增加一个模块。下图描述了这种方法。

作者图片

当然,我们不能假设斜率和截距相等。因此,我们可能需要使用更小的块。那么,斜率(即 a)和截距(即 b)可能由多个块组成。但是总的方法保持不变。在下图中,我们使用单个块作为截距(b),多个块作为斜率(a)。状态 00 只有拦截块。然后,我们根据坡度添加相应数量的块。

作者图片

所以,我们的任务由两个要点组成:

  • 计算一个州应该拥有的街区数量
  • 准备量子位元,让状态的测量机率代表对应的数字

第一项任务相当容易。如果我们知道状态的位置,那么块的数量对应于这个位置乘以代表斜率的块加上代表截距的块。在 Python 代码中,该函数是一行程序。

第二个任务稍微复杂一点。但也没那么难。

当我们看状态数时,我们看到它们是位串。每个位置表示相应量子位的值。例如,右边的数字代表最低量子位的值(在电路的位置 0)。相应地,左边的数字代表最高量子位的值。在两个量子位的电路中,这些都是我们拥有的量子位。但是,同样的逻辑适用于具有任意数量量子位的电路。

因此,我们从最高量子位开始,根据总块数划分状态,如下图所示。

作者图片

我们通过使用 RY 门来实现这一点——围绕量子位的 Y 轴旋转。

由于这个门需要一个角度,我们需要将概率(上半部分的块数与下半部分和上半部分之和的总块数之比)转换成相应的角度。这就是prob_to_angle函数的作用。

接下来,我们只看这些上量子位为 1 的状态。然后,我们拆分这些状态,并计算块数。

作者图片

我们通过一个受控的 RY 门来实现这一点。这只适用于控制量子位为 1 的情况——因此只适用于上半部分。请注意,upper 和 lower 的值现在必须只代表子集。

我们对上量子位为 0 的这些状态做同样的事情,把它封装到 X 个门中。对于给定的量子位,X 门翻转 0 和 1。

对于两个量子位的电路,我们现在完成了。如果我们有更多的量子位,我们将需要进一步前进,直到我们遍历所有的量子位。下图描述了此过程。

作者图片

在最顶层,我们将量子位分成两块。在下一级,我们将这两个块中的每一个分成另外两个块。为了只考虑各自的部分,我们增加了上面的量子位作为控制量子位。随着每一层的深入,我们需要添加上层量子位作为控制。这意味着我们基本上使用多重控制的 RY 门。

所以,我们需要的是一个遍历所有层次的函数。当然,有无数种方法可以实现这一点。因为我喜欢递归(调用自己的函数),这里是我的方法。

在我们程序的顶层,我们定义基本常数,创建一个QuantumCircuit和一个QuantumRegister,调用split函数,并执行电路。

所有神奇的事情都发生在 split 函数中,该函数有三个参数:

  • current量子位是量子位在电路中的位置,
  • offsets是所有为 1 的父量子位的列表,
  • controls是所有父量子位的列表。

通过split(QUBITS-1, [], [])调用函数让我们从最高量子位开始,没有父量子位。

那么,让我们来看看函数。

首先,我们计算一些我们需要的值。middle表示当前量子位所跨越的区间的中心位置。例如,我们从第三个量子位(位置 2)开始。因此,middle就是 2^{2+1}/2=8/2=4.这有助于我们决定在哪里分裂国家。

offsetoffsets列表中所有位置的平方和。由于当前为空,因此offset为 0。

现在,我们计算放入下半部分的所有状态的块,我们计算放入上半部分的那些。如上所述,middle作为lower的结束和upper部分的开始。我们现在忽略偏移量,因为它是 0。因此,lower包含状态 0 到 3 的块,而upper包括状态 4 到 7。

下一步,我们添加量子门。在第一次迭代中,controls列表为空。因此,我们直接跳到 RY 门。然后,我们将current量子位旋转一个角度,该角度代表上部块除以总块的比率给出的概率。

最后,由于current大于 0,我们调用 split 函数两次。我们将current减 1,因为我们想处理下一个更低的量子位。在这两个调用中,我们将current添加到controls的列表中。这两个调用之间唯一的区别是,我们在第二个调用中将current添加到了offsets列表中。

所以,让我们看看第二次调用split时会发生什么。我们叫它像split(1, [2], [2])

middle现在设定为 2^{1+1}/2=2.现在的偏移量是 2^2=4.这是我们的起点,因为在这个迭代中,我们只处理状态的上半部分(4-7)。当我们计算lower时,我们从位置 4 开始计算块数,并在位置 4+2=6 之前结束。鞋帮从位置 6 开始,刚好在位置 8 之前结束。

这一次,我们在controls中有了一个量子位。我们循环遍历这个列表。但是由于这个元素也在offsets列表中,我们不应用 X-gate。

此外,这一次,我们应用多控制 RY ( mcry)门,而不是 RY 门。controls列表中的所有位置都用作控制。因此,只有当这些位置的量子位是 1 时,我们才应用旋转。在我们的例子中,如果位置 1 的顶部量子位是 1,我们只应用旋转。这仅意味着第四至第七个州。

我们再次调用分裂函数,这次是针对位置 0 的量子位。这里第一个叫split(0,[2], [2,1])。我们将当前的量子位(位置 1)添加到控制中,但没有添加到偏移中。

因此,当我们研究这个迭代时,offset与之前的(=4)相同。但是由于current位置现在是 0,middle就不同了。现在,我们把状态 4 和状态 5 分开。

现在主要的区别是我们在controls列表中有一个条目不在offsets列表中。这是位置 1。因此,在应用多控制 RY 门之前和之后,我们在这个量子位上应用 X 门。这意味着我们只想对位置 1 的量子位为 0 的情况应用旋转。

最后,让我们来看看运行这条电路的结果。

作者图片

结论

线性函数可能是孩子们在高中遇到的第一个函数。然而,在量子电路中对付它们需要这样或那样的技巧。在你掌握量子机器学*的过程中,你会发现很大一部分工作实际上是经典的问题解决。

量子机器学*要不要入门?看看 动手量子机器学*用 Python

在这里免费获得前三章。

对商业环境中因果关系的温和介绍

原文:https://towardsdatascience.com/a-gentle-intro-to-causality-in-a-business-setting-4285aee4b83

理解相关性不会帮助你在商业环境中进行决策和计划评估。你需要的是对因果关系的深刻理解。

埃文·丹尼斯在 Unsplash 上的照片

无论你是处理决策科学、市场营销、客户科学或有效 A/B 测试的数据科学家,因果推理都是你职业生涯中应该掌握的顶级技能。理清因果关系在商业中通常被忽视,并且很少被理解。许多关键决策都是基于医学证据做出的,许多错误的结论都是由虚假相关性得出的。这可以是无缘无故地向客户发送垃圾邮件,也可以是由于不正确的计划而造成的灾难性的金钱浪费。

事实是,在处理人类行为或社会经济系统——根据定义是混乱和多元的系统——时,掌握因果关系是非常困难的。然而,能够以一种因果的方式框定问题,极大地有助于让混乱变得有序。

让我们从一个用例开始

一个非常常见的用例将有助于将事情弄清楚:

你的媒体公司有一个基于订阅的收入流。你提供每月和每年的计划,通常的东西。问题是,在过去的几个月里,您的续订量大幅下降。每个人对此都很狂热,所以你的营销经理急于为你的客户提供 20%的折扣,以便更新给整个客户群,而没有选择对照组(所以没有随机对照试验)。

活动结束后,你看着一个随机的顾客,猜猜看,他们在接受折扣后又续约了。但问题来了,你怎么知道他们更新了作为你活动的效果如果您随机选择的客户不考虑折扣而续订了他们的订阅,会怎么样?你当然可以去问,但这通常是不可能的。同样不可能的是确定你的单个随机客户的 反事实现实,也就是观察如果你没有给他们折扣他们的计划会发生什么。

你面对的是因果推断的根本问题,那就是你每次只能观察到每个人的一个结果。

每当我们进行没有决定性结果的干预时,我们都会面临这个问题。干预是我们可以操纵的东西,试图得到一个不同的因果结果。例如,决定提供折扣是一种干预,因为我们也可以做出相反的决定。另一方面,性别或种族可能是因果关系,但并不构成干预,因为我们无法改变某人的性别。

在下图中,我们用图形表示了这种情况。在蓝色轮廓中我们可以观察到。您向我们所有的客户提供了折扣,因此您可以观察到两种可能的结果:Y=1 或 Y=0。橙色区域是反事实世界,如果你不提供折扣会发生什么。这是一个虚拟的世界,你无法直接观察,但你可以根据它进行推断。

图 1 —观察与虚拟现实。作者创造的形象

在这一点上,我们可以添加一点符号,这将有助于进一步深入:

图 2 —基本符号。作者创造的形象

从现在开始我们称之为处理的动作可以取两个可能的值,结果也是如此。我们在治疗后观察结果,反事实是我们没有治疗时观察到的结果。因此,我们只有在下列情况下才有因果关系:

图 3——一种治疗是偶然的,只有当你服用后得到的结果不同于不服用时的结果。作者创造的形象

很明显,如果你的随机顾客在观察世界和虚拟世界都更新了他们的会员资格,折扣不一定是导致更新的原因(效果)。

估计因果效应

估计因果关系是审查不可观察的平行现实的工作。或者至少对那里发生的事情做一个有根据的估计。它可以被框定为寻找两个期望值之间的差:

图 4 —平均因果效应。作者创造的形象

这是我们所有客户在现实世界中的平均结果与虚拟世界中的平均结果之间的差异,在虚拟世界中,没有人因为续订而获得折扣。只有通过估算 Y⁰,你才有机会正确衡量这场运动的影响。

因果关系 vs 制约,永无止境的困惑…

在这一点上,你可能会开始想为什么我们对所有这些因果哲学的东西太多了。经过一番挖掘,你发现并不是所有的客户都有折扣,只有那些明确同意联系的客户才有折扣。毕竟,你可能有一个控制小组来衡量活动的效果,这让你的财务经理非常高兴。

图 5 —参加活动的客户与未参加活动的客户的续订率。作者创造的形象

结果如图 5 所示。非常令人鼓舞的是,这一运动获得了成功,两组之间相差 13 个百分点。然而,你的财务经理仍然觉得有些可疑。事实上,我们需要估计一个群体在一个平行的维度上,如果与现实中发生的情况有所不同,会做些什么,我们并不试图比较接受治疗的顾客亚群体与未接受治疗的顾客亚群体。用数学符号表示:

图 6——因果效应不同于两个亚群结果的观察差异。作者创造的形象

平均治疗效果不同于接受治疗的亚群与未接受治疗的亚群之间的结果差异。

本质上,如图 7 所示。我们感兴趣的是推断在虚拟现实中会发生什么。我们需要估计:

  • 如果没有联系到已联系的客户,他们的可能结果是什么。这是我们上面定义的被治疗的 (ATT)的平均治疗效果。
  • 未联系的客户的可能结果是联系了他们。

图 7——有两个小组,治疗组和对照组,现在我们有两个虚拟现实要考虑。作者创造的形象

总的来说,比较这两个亚群是正确的。如果我们从初始人群中创建一个随机对照试验,并在两组之间进行完全随机的分割,那就好了。但在这种情况下,谁得到治疗(行动)取决于提供同意接触。但是谁同意为商业活动进行联系呢?大概是不在乎隐私的人?或者打算更多地参与你的产品或网站的人?

打破不可忽视的假设

进一步分析后,你会发现加入忠诚度计划的人更有可能同意联系并参与更新活动。但你猜怎么着,这些人也更有可能自发更新,不管你的活动。

图 8——一个混淆者溜了进来,忠诚计划的订阅可能会导致大多数续订,而不是活动。作者创造的形象

同意联系会让您的客户加入活动,这反过来会影响续订概率。忠诚计划订阅会影响同意和续订。在这一点上,我们不能忽视每个客户是如何在活动中结束的,因此我们不能假设治疗分配在某种程度上独立于潜在的结果。

因此,忠诚度计划是一个混杂因素,这是一个变量,使你无法弄清楚观察到的结果是由于你的营销能力还是忠诚度计划的一些后门效应。为了消除混淆,您需要通过对该变量进行分层来控制混杂因素。

让我们通过为忠诚度计划订阅添加变量 X 来扩展活动结果。当我们将协变量添加到组中时,情况开始改变。

图 9-地层中的制动处理和控制组,改变了初始图像。这场运动远不如最初预期的成功。作者创造的形象

我们可以通过比较不同阶层的目标和对照组来估计运动的因果效应。对于那些加入忠诚度计划的人来说,该活动只是稍微有效,对于其他人来说,该活动没有影响或稍微有害。在这一点上,我们可以通过对每一层观察到的效果进行加权平均来测量该活动的平均治疗效果,如图 9 所示

识别和控制正确的混杂因素是向反事实的虚拟现实迈进了一步。在这个例子中,你不知道如果没有这个活动,你的活动的客户会有什么样的行为,但是你可以通过观察没有参加这个活动的客户和他们的相似情况,做出一个有根据的猜测。在这种情况下,忠诚度计划的用户没有同意联系(即使在现实中,你可能想分层,为更多的可能混淆)。

结果是,你的活动没有最初预期的成功,甚至可能在赠品折扣上产生比收益更多的成本。最好不要告诉你的财务经理。

参考资料:

  1. 我从杰森·罗伊:https://www.coursera.org/learn/crash-course-in-causality的《因果关系的精彩课程》中学到了大部分观点和符号
  2. 另一个基本的参考点是Judea Pearl 和 Dana MacKenzie 的《为什么:因果的新科学》,企鹅出版社,2019 年。

分支和绑定的简单介绍

原文:https://towardsdatascience.com/a-gentle-introduction-to-branch-bound-d00a4ee1cad

用 Python 解释的最基本的整数和混合整数编程算法

维克多·塔拉舒克在 Unsplash 上拍摄的照片

数值优化问题是定量决策过程中的一个基本工具。假设一个系统可以用一组数学方程来描述,这些方程充分包含了决策变量对目标和约束的影响。在这种情况下,可以使用优化算法来搜索那些产生最佳可能结果的决策变量的值。

描述决策变量如何影响目标和约束的非线性函数的存在与否被用来将优化问题分成两大类:线性和非线性规划。管理科学和运筹学广泛使用线性模型,而非线性规划问题往往在物理科学和工程中自然出现(Nocedal & Wright,2006)。在本文中,重点将放在线性模型上。

在某些问题中,决策变量只采用整数值是有意义的。例如,在人员调度问题中,将 2.57 名员工分配到一个班次是没有意义的;或者在车辆路径问题中,人们不能选择从 ab 走半条路线。这些问题被称为整数或混合整数问题。

在实践中,这些问题中的大多数都是通过结合了实值算法和其他策略的分支定界算法来解决的,例如切割平面、定价和定制试探法。因此,理解 Branch & Bound 如何工作可能有助于我们深入了解如何将其与其他策略相结合,并在解决复杂问题时更好地制定问题。

在本文的第一部分,我们将看到如何用一个两变量问题的可视化例子来描述一个线性规划问题。在接下来的部分中,将对同一问题应用逐步分支定界算法来获得最优整数解。整篇文章中使用的所有代码都可以在这个 示例笔记本 中找到。

线性规划问题的定义

当公式化优化问题时,必须定义一个目标,该目标是向量决策变量 x 的函数,并且可能受到一些等式和不等式约束,这些约束也是 x 的函数。该目标可以定义为最小化最大化,尽管前者是最常见的。注意,通过简单地乘以一个最大化目标的系数 cᵢ ,它可以在一个最小化的意义上被重新公式化。

在线性问题中,决策变量空间必须以某种方式受到限制。否则,决策变量将根据目标函数收敛到正的或负的无穷大值。约束可以由平等或不平等关系来表述。它们通常在问题矩阵中被表示为行。请注意,通过添加非负的松弛变量,不等式约束可能被公式化为等式约束。然而,贯穿本文,让我们区分等式约束矩阵 A_eq 和不等式约束矩阵 A_ub

x 的每个分量的上下边界在公式中可能是明确的,这减少了搜索空间。按照惯例,由于求解技术的原因,决策变量的下限通常默认等于零。这导致了如下的一般问题公式。

线性问题。(图片由作者提供)。

让我们考虑一个数字例子来说明这些概念。

例题。(图片由作者提供)。

可行空间是决策变量空间中所有约束都有效的区域。请注意,在某些情况下,约束之间可能是不一致的。假设我们有一个约束 x₁ + x₂ ≤ 2,另一个 x₁ + x₂ ≥ 3。在这种情况下,问题将被认为是不可行的。然而,在前面定义的数值例子中,我们可以如下直观地表示可行空间。

线性规划实例的可行空间。(图片由作者提供)。

当我们在考虑一个最大化问题时,我们期望在约束所建立的限制内出现最佳解决方案。让我们想象一下目标函数是如何在决策空间中通过等高线图来评估的。

可行空间中目标函数值的图形表示。(图片由作者提供)。

正如我们所看到的,目标的最大值预计出现在两个约束的交集处。在这种情况下,两者都被认为是活动约束。

为了在 Python 中解决这个问题,我们将使用来自 scipy.optimizelinprog 函数。注意,在 scipy 标准形式中,它将被描述为一个最小化问题,将目标系数乘以-1。

import numpy as np
from scipy.optimize import linprog

c = np.array([-5.0, -4.0])

A_ub = np.array(
    [[2.0, 3.0],
     [2.0, 1.0]]
)

b_ub = np.array([12.0, 6.0])

sol_relaxed = linprog(c, A_ub=A_ub, b_ub=b_ub)

这将返回一个具有以下属性的解决方案对象:

  • x:【1.5,3.0】
  • 乐趣 : -19.5

在这个解决方案中, x₁ 有一个分数值,这在某些实际情况下是不可行的。如果我们需要所有变量都取整数值,我们可能已经在 linprog 函数中声明了这个条件(至少从 scipy 版本 1.9 开始)。

sol_int = linprog(c, A_ub=A_ub, b_ub=b_ub, integrality=np.ones(2))

这将返回一个具有以下属性的解决方案对象:

  • x:【2.0,2.0】
  • 乐趣 : -18.0

请注意,目标值现在比在宽松的公式中更差,因为由于完整性约束,可行空间已经减少。我们可以说,问题的松弛形式的解必然是整数解的最优性极限。为了理解下一节中的分支定界算法,让我们首先建立一个简单而有意义的规则。

如果求解一个纯 IP(整数规划)的 LP(线性规划)松弛,得到一个所有变量都是整数的解,那么 LP 松弛的最优解也是 IP 的最优解(Winston & Goldberg,2004)。

现在让我们深入研究完整性约束。

完整性与分枝定界算法

如前所述,如果在松弛形式的线性问题的解决方案中,所有完整性约束都是有效的,则该解决方案对于整数或混合整数问题也是最优的。让我们考虑一下,在上一节的同一个例子中,x₁和 x₂都需要是整数。在这种情况下,我们可以应用分支&定界算法来寻找它的最优整数解。**

找到最优整数解的第一步是在前面的部分中执行的,因为我们找到了问题的松弛形式的解。为了应用分支定界算法,我们现在必须在松弛解 x₁ 的分数变量中划分可行空间。

请注意,在大多数现实世界的问题中,很可能会遇到中间解决方案中有多个整数变量是分数的步骤。然后可以使用几种试探法来定义分支哪个变量。例如,对具有最大经济重要性的分数值变量进行分支通常是最佳策略(Winston & Goldberg,2004)。在我们的简单例子中,这个决定很简单,因为只有一个变量是分数。

然后从前面的问题中产生两个子问题,因此,在这两个子问题中,1 < x₁ < 2 都不是有效解。

  • P1:新约束 x₁ ≤ 1 的原问题。
  • P2:新约束 x₁ ≥ 2 的原问题。

所有已经创建的子问题的显示被称为。每个子问题被称为节点,连接树的两个节点的每条线被称为。与的任何节点相关联的约束是 LP 松弛的约束加上与子问题 1(原始问题)到该节点的相关联的约束。(温斯顿&戈德堡,2004)。在这种结构中,没有完整性限制的原问题通常称为根节点。在这里,我采用了指数 0 来标识它。

在本例中,到目前为止我们已经:

分数决策变量分支产生的根节点和子问题的树表示。(图片由作者提供)。

在 Python 中,它可以用下面的公式表示。

# P1:
A_ub_p1 = np.vstack((A_ub, np.atleast_2d([1.0, 0.0])))
b_ub_p1 = np.append(b_ub, np.floor(sol_relaxed.x[0]))

# P2:
A_ub_p2 = np.vstack((A_ub, np.atleast_2d([-1.0, 0.0])))
b_ub_p2 = np.append(b_ub, -np.ceil(sol_relaxed.x[0]))

也有几种策略来选择先解决哪个子问题。一种替代方法是使用后进先出策略,该策略规定最*创建的子问题应该提前解决。然而,它仍然没有定义如何定义同时产生的两个子问题之间的优先级。在这个例子中,为了便于说明,我们将在 P2 之前任意求解 P1 ,但是在从 P1 产生的子问题之前求解 P2

sol_p1 = linprog(c, A_ub=A_ub_p1, b_ub=b_ub_p1)

现在我们有了另一个分数解[1.0,3.3],目标值为 18.33,这显然比松弛解更差。由于 P2 尚未求解,19.5 仍然是整数解可能呈现的最佳值。让我们将此表示为“最佳约束”

接下来,让我们解决 P2

sol_p2 = linprog(c, A_ub=A_ub_p2, b_ub=b_ub_p2)

现在我们得到了目标值为 18.0 的第一个整数解[2.0,2.0]。从视觉上看,到目前为止的问题是这样的:

在根节点上分支后的部分结果。(图片由作者提供)。

注意,当在一些决策变量上分支时,产生的一个或两个子问题可能是不可行的。在这种情况下,对该节点不再感兴趣,必须选择另一个节点进行探索。如果节点的部分解决方案具有比先前找到的最佳整数解决方案更差的目标,也会发生同样的情况。

由于现在已经研究了从根节点导出的两个子问题,我们知道最佳整数解不会比 18.0(我们当前的最佳整数解)差,也不会比 18.33(最佳界限)好。然后让我们从对应于 P1 的节点创建子问题,看看我们是否可能克服 18.0。由于在 P1、的解中只有 x₂ 假定为分数值,让我们分支在 x₂ 上。

  • P3: P1 ,新约束x2≤3。
  • P4: P1 ,新约束x2≥4。

在 Python 中,

# P3:
A_ub_p3 = np.vstack((A_ub_p1, np.atleast_2d([0.0, 1.0])))
b_ub_p3 = np.append(b_ub_p1, np.floor(sol_p1.x[1]))

# P4:
A_ub_p4 = np.vstack((A_ub_p1, np.atleast_2d([0.0, -1.0])))
b_ub_p4 = np.append(b_ub_p1, -np.ceil(sol_p1.x[1]))

这两个问题都导致整数解,[1.0,3.0]和[0.0,4.0],尽管它们都比从 P2 得到的结果差。因此,我们已经证明了它的最优性,因为树被完全探索并且没有找到更好的整数解(也不可能找到)。并且我们可以通过下图直观的表示出来。

子问题 P1 分支后的最终结果。(图片由作者提供)。

分支定界最终结果的树形表示。(图片由作者提供)。

进一步阅读

有几种类型的问题可以使用数学建模来解决,其中一些是更常见的公式。有兴趣了解更多关于如何用数学表达式描述一些常用规则的人可以参考 Winston & Goldberg (2004)。对于整数规划算法的深入理解,大概 Wolsey (2020)是最好的参考。

很可能,为了描述现实世界的问题,应该使用某种代数建模语言(AML)。Pyomo (Bynum et al .,2021)是一个有趣的 Python 替代品,因为它是开源的,并且与几个解算器兼容。你可以在我之前的一篇关于多维背包问题的文章中看到一个简单的例子。

其他问题无法用线性表达式描述,比如工程设计、金融建模、曲线拟合等。与线性优化问题相比,它们导致非常不同的解决技术。解决它们最常用的替代方法是使用一些经典的凸非线性规划算法,我在另一篇文章中介绍过。

然而,有时这是不够的,因为人们可能会追求多个目标,或者问题违反了一些凸算法的假设。然后,人们应该求助于其他的解决技术,其中元试探法非常有用。Pymoo (Blank & Deb,2020)是这样做的一个令人惊讶的替代方案,因为那里有几个这样的算法。

最*我发表了 Python 库 pymoode ,这是我在整个苯乙烯反应器优化项目 (Leite et al .,2022)中开发的 pymoo、的扩展。你可以在另一篇文章中找到完整的教程。

我打算很快将 pymoode 算法完全整合到 pymoo 中,但是现在,它们是作为一个单独的包提供的。

结论

在整篇文章中,线性整数规划的基本概念是通过分支定界算法的概述来介绍的。一个简单的问题被用来一步一步地用图形说明解释算法。读者可以在示例笔记本中获得所使用的完整 Python 代码。

参考

Blank,j .和 Deb,k .,2020 年。pymoo:Python 中的多目标优化。 IEEE 访问,第 8 卷,第 89497–89509 页。

拜纳姆,马丁等人,2021。pyo mo-python 中的优化建模。施普林格。

莱特,b,科斯塔,a,科斯塔少年,e,2022。基于广义差分进化算法 3 的绝热苯乙烯反应器多目标优化。化学。英语。Sci。 118196。doi:10.1016/j . ces . 2022.118196

Nocedal,j .和 Wright,S. J .,2006 年。数值优化。第二版。纽约:斯普林格纽约。

温斯顿,W. L .和戈德堡,J. B .,2004 年。运筹学:应用与算法。第 4 版。加州贝尔蒙特:汤姆森布鲁克斯/科尔贝尔蒙特。

2020 年洛杉矶沃尔西。整数编程。第二版。约翰·威利的儿子们。

数据湖之家简介

原文:https://towardsdatascience.com/a-gentle-introduction-to-data-lakehouse-fc0f131f90ff

埃伯哈德·🖐·格罗斯加斯泰格在 Unsplash 上拍摄的照片

介绍

数据湖库是一种新的数据架构,在过去的几年中被提及了很多。它的提出是为了解决老的、成熟的数据架构,即数据仓库和数据湖所面临的难题。在本文中,我们将深入探讨这种新的架构,看看它背后的动机是什么,以及它的整体外观。

免责声明 : Data Lakehouse 是一个相当新的概念(白皮书发布于 2021 年)。目前还没有一个被普遍接受的架构和定义。本文所写的内容都是基于作者的研究和解读。它可能与其他来源不同。

背景

在讨论 Data Lakehouse 之前,了解数据架构的前景很重要,因为这将帮助我们理解我们面临的问题以及为什么需要新的架构。

数据仓库

数据仓库是一个系统,公司在其中保存他们的数据,以便创建报告和可视化来支持数据驱动的决策。来自相关关系数据库的数据在被放入单个位置之前被连接并转换成被称为星形模式 的明确定义的结构,以使读取尽可能高效和无缝。

数据湖

数据湖是一个存储库,组织在其中存储来自各种数据源的大量数据以备后用。与数据仓库不同,数据通常按原样存储在数据湖中,这意味着它还支持半结构化数据(XML 和 JSON)和非结构化数据(文件、图像、视频、时间序列等)。).数据湖的使用主要是高级分析,如研究、数据科学和机器学*,它们有能力处理杂乱和非结构化的数据。

数据仓库 vs 数据湖(图片作者提供)。

数据湖和数据仓库的问题

数据湖和数据仓库就像阴阳两极。一个擅长另一个不擅长的,反之亦然。数据湖可以处理大量的数据类型,包括图像和视频,数据仓库对这些格式一无所知。但是,当谈到服务,这是一场噩梦。性能非常差,因为数据不是结构化的,提取相关数据的流程过于复杂,因为数据没有经过预处理,等等。另一方面,数据仓库在服务方面是一头野兽,因为数据已经为此做好了准备。但是,它的用途非常有限,因为数据必须在预定义的模式中,而且它是预先转换和聚合的,所以如果需要,您无法更深入地研究它。

除了数据湖之外,公司通常通过构建数据仓库(或任何其他数据服务)来规避上述问题,然后定期在那里 ETL 相关数据以获得两者的最佳效果。然而,随着数据的不断增长和演变,这种方法由于成本、复杂性、可靠性和陈旧性而开始变得低效。因此,引入了 数据仓库 的概念。

数据仓库

数据湖库的关键概念是将数据湖与所有数据服务连接起来。湖边小屋建筑由以下 5 层组成:

数据湖房屋架构(图片由作者提供)。

  • 数据源任何可能是数据源的东西,如数据库、用户设备、物联网设备和应用程序日志。
  • 摄取层将数据摄取到系统中,并使其可用,例如将其放入有意义的目录结构中。
  • 存储层为海量数据提供持久、可靠、可访问、可扩展的存储。在 Lakehouse 体系结构中,数据仓库和数据湖共享存储以避免相同数据的副本。
  • 元数据层为存储中的结构化和非结构化数据提供元数据,使其易于被用户发现,并跟踪数据模式(如果存在)版本。
  • 处理层通过数据清洗、验证、规范化、反规范化、浓缩,将数据转换成可消费的格式。
  • 服务层
    为所有使用数据目录的用户提供访问存储在湖边小屋中的所有数据的组件。

数据湖库与双层湖+仓库模型的主要区别在于,湖和仓库是集成的,这意味着不再需要移动数据。元数据层是架构的核心,因为它统一了处理层和服务层的数据(结构化和非结构化)消费。

数据仓库最大的挑战可能是查询性能优化。由于数据不是严格的和结构良好的,它如何能像数据仓库一样达到最先进的查询性能?造成这一挑战的因素有几个,例如是否应该转换数据,什么是可接受的延迟,以及有什么资源可用。所以,在这方面没有放之四海而皆准的解决方案。

结论

数据湖之家的想法非常吸引人。拥有一个像数据仓库一样快速有效的一体化数据平台,同时还拥有数据湖的灵活性,这将给任何组织带来巨大的竞争优势。然而,这个概念仍然是相当新的和不断发展的。因此,这可能需要一段时间来看看是否能成为主流。湖边小屋建筑的发展方向是我们都应该感兴趣的。

参考

[1] M .阿姆布鲁斯特,A .古德西,r .辛,m .扎哈利亚,莱克豪斯:统一数据仓库和高级分析的新一代开放平台 (2021),创新数据系统研究会议(CIDR) 2021

[2] P. Kava 和 C. Gong,在 AWS (2021)上建立一个湖屋建筑,AWS 大数据博客

[3] A. Tavakoli-Shiraji,发现数据湖屋 (2022),数据+AI 世界巡回赛虚拟训练

[4] A. Ghosdi,实现数据仓库的愿景 (2020),Databricks

[5] Databricks,什么是 Data Lakehouse?、Databricks.com

通过模拟掷骰子来温和地介绍马尔可夫链

原文:https://towardsdatascience.com/a-gentle-introduction-to-markov-chains-by-modelling-dice-rolls-ed26a249dd0d

(像素)

我为什么要关心马尔可夫链?

两件事:首先,它是隐马尔可夫模型(HMM)和强化学*等几个机器学*概念的关键基础。马尔可夫链也用于其他学科,如金融(股票价格运动)或工程物理(布朗运动)。一个著名的例子是 PageRanks ,这是谷歌用来给页面排名打分的第一个算法,它是基于马尔可夫链的。

其次,我发现这个概念本身很有趣,因为它是一种与你将遇到的大多数数据科学概念完全不同的方法。在机器学*中,你试图根据过去的数据预测未来的事件。有了马尔可夫链,你只关心现在的状态来预测未来(我马上会解释为什么)。

你如何预测天气?

如果让你预测某个城市未来 365 天的天气,你会怎么做?

这个问题的一个解决方案可以是分配雨天的概率和晴天的概率。

例如,如果你住在伦敦,那里一年大约有 1/3 的时间下雨,你可以分配一个概率 P(下雨)= 1/3P(晴天)= 2/3 。现在,您可以简单地在 Python 中运行快速模拟并获得预测。

我们将生成的示例如下所示:

模拟伦敦天气的首次尝试(图片由作者提供)

这种方法有一个主要的警告。事实上,我们知道:

  • 如果某一天下雨,第二天更有可能下雨。
  • 如果某一天天气晴朗,第二天天气晴朗的可能性就大得多。

问题是我们目前的模型没有考虑到这一点。理想情况下,我们希望“告诉”我们的模型,如果某一天下雨,我们希望第二天下雨的概率更高,反之则是晴天。我们可以这样表述:“如果我们的模型处于下雨的状态,我们希望它有很高的概率保持在这种状态,即第二天还会下雨,反之亦然”。

那么,我们该怎么做呢?马尔可夫链。

模拟天气的马尔可夫链

让我们看看这个问题的马尔可夫链是什么样子的:

天气问题的马尔可夫链(图片由作者提供)

马尔可夫链由三部分组成:状态(这里是雨天或晴天)、事件(从晴天到雨天或雨天后的雨天)以及每个事件的概率(从晴天到雨天或雨天到雨天的概率为 0.3,晴天后的雨天或晴天后的晴天的概率为 0.7)。

现在,不是每天都有晴天或雨天的概率,而是有保持离开的概率,即晴天雨天的状态。

更正式地说,我们有两种状态,晴天和雨天,每种状态都有离开状态和转换到另一种状态的概率,以及保持同一状态的概率。

跃迁矩阵

对于模拟,我们不使用这个图,而是使用一个所谓的转换矩阵。我们的会是这样的:

天气问题的转换矩阵(图片由作者提供)

那么,如何解读这个矩阵呢?让我们看看如何阅读左下角的条目。你先读这个矩阵的行,然后是列。因此,对于给定的元素,您将读到:

从(行)转换到(列)的概率是(值)。

在这里,对于左下方的元素,我们将读取:

从晴天转变为雨天的概率是 0.3。

骰子滚动

现在,让我们用掷骰子来处理一个更复杂的例子。假设你有两个玩家 A 和 b,一个有六个面的骰子被连续掷出。

玩家 A 下注,两次总和为 10 的连续掷骰将首先发生。玩家 B 打赌两个连续的 6 会先出现。玩家继续掷骰子,直到一个玩家赢。

A 赢的概率有多大?

我们如何建模?…你猜对了。

掷骰子的马尔可夫链

让我们看看应该如何处理这个问题。

对玩家 A 来说,总和为 10 的连续掷骰子是先掷 5 再掷 5,先掷 4 再掷 6,然后掷 6 再掷 4。

对于玩家 B 来说,唯一的选择就是连续出两个 6。

这意味着得到 1、2 或 3 与我们的问题“无关”:这些卷将构成我们的初始状态 s。其他状态 4、5 和 6 将是“相关”状态。

现在,让我们来看看马尔可夫链的初稿。

我们的马尔可夫链的初稿(图片由作者提供)

首先,我们有状态 S(滚动 1、2 或 3)。我们有 3/6=0.5 的概率停留在状态 S(即重掷 1、2 或 3)。其他状态 4、5 和 6 中的每一个都有 1/6 的概率保持在同一状态或转换到其他两个相关状态,并且有 0.5 的概率转换到状态 s。每个状态的所有概率总和总是 1。

这看起来不错,但是我们遗漏了关于我们感兴趣的骰子滚动组合的信息。让我们补充一下:

掷骰子问题的最终马尔可夫链(图片由作者提供)

这就像上面的一样,但是我添加了最终状态,它们是绿色的(当 A 赢的时候)和红色的(当 B 赢的时候)。这些状态保持同一状态的概率为 1,称为吸收剂。这是因为,一旦你进入这些状态,你就不能离开它们。在我们的例子中,它们是吸收性的,因为它们代表游戏结束的时间(当 A 或 B 赢了的时候)。

需要注意的一点是,你可以从 5 转换到 6 或者从 5 转换到 4,反之亦然,但是你不能从 6 转换到 4,反之亦然。这是因为,如果你掷了一个 6,然后掷了一个 4,你不会回到中间的状态:你到达一个吸收状态,游戏就结束了。这就是为什么这两者之间没有箭头。

这看起来不错,但是我们仍然没有回答我们最初的问题:玩家 A 赢的概率是多少?为此,我们需要使用这个马尔可夫链的转移矩阵进行一些模拟。

马尔可夫链的转移矩阵(图片由作者提供)

除了掷出 1、2、3、4、5 或 6 之外,我还创建了 4 种状态:掷出 4 然后掷出 6 (46),连续掷出两个 5(55),掷出 6 然后掷出 4 (64),连续掷出两个 6(66)。

如果我们到达状态 46、55 和 64,玩家 A 赢,而如果我们到达状态 66,玩家 B 赢。

模拟

让我们看看如何使用 Python 来运行这个转换矩阵的模拟。你可以在我的 GitHub 上找到代码。

定义 s 和 P(图片由作者提供)

首先,我们定义 s ,这是一个向量,表示开始时到达其中一个状态的概率。它的排序方式与转移矩阵相同—第一个值 s[0] 对应于滚动 1,第二个值 s[1] 对应于滚动 2,最后一个值对应于 66,连续滚动两个 6。

前 6 个值的概率为 1/6≈0.1667,因为第一次掷骰子时,我们可以掷出 1、2、3、4、5 或 6。状态 46、55、64 和 66 都有概率为 0,因为我们在第一次掷骰子后无法到达这些状态。

然后,我们将上面的转移矩阵定义为 NumPy 数组。

运行模拟之前的一个快速步骤是检查所有的行的总和是否为 1(检查你是否理解转换矩阵的一个好方法是问你自己为什么我们要检查它)。

P 的行数总和(图片由作者提供)

在我们的例子中,所有的行加起来都是 1(对于前 6 个元素来说稍微高一点,因为我们使用的是 1/6 的*似值)。

现在,让我们运行我们的模拟。为此,我们简单地将 P 乘以自身,然后乘以初始状态向量s。你可以看到这是从初始状态 s 开始,然后乘以 P 很多次,以模拟大量的骰子滚动。

我们得到以下结果:

我们的模拟结果(图片由作者提供)

我们看到,在掷骰子很多次后,我们肯定不会停留在前 6 个状态,因为它们的概率为 0。这是有意义的,因为这意味着 A 或 B 将在某一点上获胜,游戏将停止。

向量中不为 0 的前三个数字对应于状态 46、55 和 64 的概率(玩家 A 获胜)。如果我们把这些概率相加,我们就得到玩家 A 赢得游戏的概率。

A 获胜的概率(图片由作者提供)

现在,我们有了答案:玩家 A 有 0.77 的概率赢得游戏,而 B 有 1–0.77 = 0.23 的概率赢得游戏。

更进一步

我希望你喜欢这第一次介绍马尔可夫链。

我建议尝试一个不同的掷骰子问题,并重复上面的所有步骤(不同的结果,例如 7 人或 3 人等),以确保你完全理解这个概念。您还可以通过重用我的代码来尝试使用 NumPy 运行模拟。

我希望你喜欢这个教程!让我知道你对它的看法。

随时连接上LinkedInGitHub谈论更多关于数据科学和机器学*的内容并关注我上 !也可以用这个 链接 成为中等会员来支持我。

蒙特卡罗方法简介

原文:https://towardsdatascience.com/a-gentle-introduction-to-monte-carlo-methods-98451674018d

带有 python 中的实际代码示例

蒙特卡罗方法是一种广泛的计算算法,依靠重复的随机抽样来获得数值结果。这些方法背后的基本概念是使用随机性来解决原则上可能是确定性的问题。蒙特卡罗方法经常用于物理问题和数学问题,正如我们将在本文中讨论的,在难以甚至不可能使用替代方法的情况下,蒙特卡罗方法有一些独特的优势。

蒙特卡罗方法主要用于三类问题:优化数值积分,以及我们需要从概率分布中产生抽奖的场景。一些例子包括对输入中具有显著不确定性的现象进行建模,例如商业中风险的计算,以及数学中具有复杂边界条件的多维定积分的评估。应用于系统工程问题(太空、石油勘探、飞行器设计等。)、基于蒙特卡洛的失败预测、成本/进度超支通常比人类直觉或替代“软”方法更好。

这篇介绍性文章显然不会涵盖所有这些主题,而是通过几个直观的例子来展示如何使用随机抽样和蒙特卡罗方法来解决一些有趣的问题。到本文结束时,您将有望很好地掌握这些技术的基本假设和应用,以及对这些方法的有用性和优点的评价。

我还包含了复制结果所需的所有 python 代码,所以您也可以随意尝试这些示例!

使用随机数生成器估计圆周率的值

我将以一个相当基本但有趣的例子开始(旁注:这实际上是几年前我在一次求职面试中遇到的一个问题!).任务是找到圆周率的值,但是有一个限制,即我们只能使用一个能够执行基本算术以及能够生成随机数的计算器。

当然,你可以试着画一个圆,测量直径和周长的比率等等。,但这可能不会导致对圆周率的非常精确的估计。也许我们可以用某种方式利用随机数字的能力来代替?

让我们从单位圆(即半径为 1 的圆)的定义开始。如果( x,y )是单位圆圆周上的一点,那么| x |和| y |是斜边长为 1 的直角三角形的边长。这样,由勾股定理xy 满足等式: x + y = 1

图 1:单位圆

这意味着,如果我们从范围[-1,1]中的两个随机数开始,代表 x,y 坐标,我们可以很容易地检查这个点是位于单位圆内部还是外部。幸运的是,这基本上是我们估算圆周率所需的所有信息!

如下图所示,如果我们比较单位圆的面积和正方形的面积,我们看到这个比值是由圆周率的值给出的。重要的是,作为这个比率的*似值,我们可以生成一个大的随机数序列“N_total”,然后检查其中有多少个位于单位圆“n_inside”内。

图 2:使用随机数估计圆周率

现在让我们生成一个大的随机数序列,并检查它!在下图中,我们看到了迭代次数不断增加的输出。单位圆内的所有点( x + y < 1)显示为红色,而单位圆外的点显示为蓝色。通过简单地计算 Nn 之间的比值,我们就可以得到一个越来越好的圆周率估计值。

图 3:使用随机数估计圆周率

或者,由于系统的对称性,我们也可以只关注右上象限,如下图所示:

图 4: 来源

通过生成 N=100.000 对随机的 x,y 坐标,我们可以检查 PI 的*似值如何随着迭代次数的增加而提高(另外,请注意下图中的对数x-轴)。

图 5:评估的准确性与迭代次数的关系

正如我们在左图中看到的,给定足够多的数据点,我们的 PI *似值最终会接*正确值。您可能还会注意到右图中明显的比例定律,我们可以看到,我们估计的误差似乎与迭代次数的平方根成反比。这种缩放行为实际上是蒙特卡罗方法最显著和最重要的特征之一,这是我们稍后将更详细讨论的内容。

在使用蒙特卡罗积分来解决同样的问题之前,我包含了复制上述结果所需的代码。在下面的代码块中,我们从定义函数“PI_Approx”开始,它将迭代次数 N 作为输入。该函数生成一系列 N 个随机数对,然后检查它们是否位于定义的单位圆内。对于每个生成的随机数对,我们在列表“PI_approx”中添加 PI 的更新估计值。在经历了所有的 N 次迭代之后,最终结果作为数据帧返回,除了我们的估计之外,还包括 PI 的真实值以及我们估计的误差。(此外,在 N =100、1000 和 10.000 次迭代之后,下面的代码还会生成如图 3 所示的示例输出)。

图 6:使用随机数估计圆周率

定义了*似 PI 的函数后,我们可以调用此函数进行 N =100.000 次迭代,如下所示。这个代码块还输出了显示我们的估计相对于迭代次数的改进的图形,以及估计误差的比例法则(图 5)。

图 7:使用随机数估计圆周率

已经讨论了使用蒙特卡罗方法和随机抽样来估计圆周率的初始示例,现在让我们继续讨论使用蒙特卡罗积分来解决同一问题的替代方法。

从随机抽样到求解积分

对于那些有数学/科学/工程背景的人来说,解各种各样的积分可能是你非常熟悉的事情。然而,在许多(大多数)情况下,我们不能解析地求解积分,而需要求助于数值替代方案。

与通过寻找反导数的解析积分相反,进行数值积分有几个原因:

  1. 被积函数 f ( x )可能只有在某些点上是已知的,比如通过采样得到。
  2. 被积函数的公式可能是已知的,但是可能很难或者不可能找到作为初等函数的反导数。这种被积函数的一个例子是f(x)= exp(x2),它的反导数(误差函数乘以一个常数)不能写成的初等形式
  3. 计算反导数是可能的,但计算数值*似值可能比计算反导数本身更容易。如果反导数作为一个无穷级数或乘积给出,或者如果它的评估需要一个不可用的特殊函数,就可能出现这种情况。

当然,我们估算圆的面积的简单例子并不需要我们计算非常复杂的积分,我们可以很容易地用解析求解。然而,基本的技术仍然是相同的,因此它是引入蒙特卡罗积分方法的一个很好的起点。这也使得直接评估我们的*似与精确解相比有多精确成为可能。

由于我们系统的对称性,让我们简化问题,只关注右上象限。我们对于单位圆的公式, x + y =1 可以改写如下图所示,这里我们要求解 f(x) 的定积分从 x =0 到 1。

图 8:通过定积分计算圆的面积

在我们继续展示如何使用蒙特卡罗方法求解积分之前,我首先包括定义我们的函数的示例代码, f ( x ),以及绘制上图:

图 9:通过定积分计算圆的面积

怎么才能用随机数解积分?

那么问题是,我们如何用一个随机数序列来估计这个积分的值呢?

图 10:积分=曲线下面积

你可能还记得,函数 f ( x )的积分可以解释为计算函数曲线下方的面积。假设我们现在在[a,b]范围内为x选择一个随机数,如左图所示。然后,我们可以计算这个值的 f ( x ),然后将结果乘以( b-a )以获得积分值的粗略估计。这当然会是一个非常粗略的估计,但是如果我们继续在范围[a,b中的许多不同的随机点 T14 评估函数,然后将所有这些矩形的面积相加,并对总和取平均,我们实际上会越来越接*积分的精确结果。

这个过程如下图所示,这里我们展示了在[ a,b ]内的四个随机点对 f ( x )进行评估的结果。正如我们所看到的,一些结果矩形高估了积分的面积,而另一些则低估了面积。然而,当对所有矩形的总面积求平均值时,这些影响往往会相互抵消,随着样本数量的增加,我们实际上获得了对真实积分的相当好的估计。

图 11:通过随机抽样*似积分。来源

使得蒙特卡罗积分如此强大的标度律

利用蒙特卡罗积分的性质,我们还可以推导出一个比例定律,显示我们的*似解的误差如何随着迭代次数的增加而减少 N. 我们已经在图 5 中举例说明了估算 PI 的第一个例子中的这种比例行为,但是如果您对比例定律的推导感兴趣,您可以看看下面的内容(或者如果您想直接跳到结果,可以随意跳过它!)

图 12:比例法则的推导

实际上,这个比例定律意味着需要四倍多的样本来将估计误差减半。但是蒙特卡罗积分最显著的特点是这个收敛速度与积分维数无关。这不是用于求解积分的其他数值方法的情况(例如黎曼和),其中收敛速度随着积分维数的增加而指数恶化。在许多情况下,我们不得不解决多变量函数的积分(或多重积分),因此蒙特卡罗积分更适合。当然,对于固定数量的样本,逼*的质量会随着维数的增加而降低,但你仍然可以保证以固定的成本(样本数 N )得到一个解。

蒙特卡罗积分:实际例子

执行蒙特卡罗积分的函数在下面的代码块中定义。该函数将迭代次数 N 、要评估的函数以及由[ a,b 定义的积分间隔作为输入。在执行了以上各节中讨论的计算之后,该函数将返回积分的估计值 I_approx。

图 13:蒙特卡罗积分

与我们基于随机采样估计 PI 值的初始示例类似,我们现在可以使用蒙特卡罗积分对我们之前定义的函数 f ( x )执行相同的估计,然后在区间 a =0 到 b =1 上进行积分:

图 14:蒙特卡罗积分

此代码块还输出了下图,说明了随着迭代次数的增加,我们对 PI 的估计如何逐渐变得更好,并显示了与我们前面讨论的相同的误差比例定律。

图 15:使用蒙特卡罗积分估算圆周率

蒙特卡罗积分:示例 2

上面的例子说明了蒙特卡罗积分的使用,但对于一个相当简单的情况,我们实际上可以解析地求解积分以获得精确的解。在这一节中,我们对稍微复杂一点的情况采用同样的程序,在这种情况下,不可能得到封闭形式的解,我们需要借助于积分的数值估计。

我们首先定义函数 f ( x )以及积分区间 a,b为,如下图所示:

图 16:替代函数的蒙特卡罗积分

与前面的例子类似,我们执行相同的蒙特卡罗积分过程,但是这次使用新的函数作为输入。此外,在这种情况下,我们没有精确的解来比较我们的估计,而是使用scipy . integrate . quad方法将我们的解与数值积分进行比较,如下面的代码块所示。

图 17:替代函数的蒙特卡罗积分

我们在这里还输出了一些图形,显示了随着迭代次数的增加,我们对积分的估计如何逐渐变得更好,并显示了与前面示例相同的误差比例定律。

**

图 18:替代函数的蒙特卡罗积分

推广到更高维的积分:

在前面的例子中,我们使用了蒙特卡罗积分来处理 1D 线积分,但是这如何推广到更高维度呢?让我们使用下图左图所示的 3D 函数作为起点,然后定义右图所示的积分间隔。

图 19: 3D 功能

以下代码块包括 3D 函数 f ( x,y )的定义,以及上面图 19 所示的可视化:

图 20:定义我们的 3D 形状并可视化它

然而,在我们能够执行这个蒙特卡罗积分之前,我们首先需要将我们的代码一般化,以处理作为输入的 3D 函数。正如您在下面的简短推导中所看到的,幸运的是,这非常简单。主要区别在于,我们现在对 xy 都在随机点计算f(x),然后乘以积分区间 ( b-a )的长度,而不是在范围 a,b 中的随机点计算f(x),然后乘以

图 21:从 1D 积分推广到 2D 积分

根据这一概括,我们现在可以更新下面代码块中的“Integral_Approx”函数,以接受 3D 函数作为输入,并添加两个附加参数(a2 和 b2)来定义积分区域。由于在这种情况下我们也没有精确的解来比较我们的估计,我们使用scipy . integrate . nquad方法将我们的蒙特卡罗解与同一方程的数值积分进行比较:**

图 22: 2D 蒙特卡罗积分

然后,我们可以执行必要的计算并输出下图,显示随着迭代次数的增加,我们对积分的估计如何逐渐变得更好,以及在这种情况下,误差显示出与前面示例相同的比例定律。

****

图 23: 2D 蒙特卡罗积分

总结:

如前所述,本文的目标是通过几个直观的例子来展示随机抽样和蒙特卡罗方法如何用于解决一些有趣的问题。然而,尽管本文中介绍的方法对于这些相当简单的示例非常有效,但是对于更复杂的问题,还需要考虑一些重要的事项。

尽管其概念和算法简单,但与蒙特卡罗模拟相关的计算成本可能非常高,因为该方法通常需要许多样本来获得良好的*似。尽管对于非常复杂的问题来说,这可能是一个重大的限制,但幸运的是,算法的令人尴尬的并行性质允许通过本地处理器、集群、云计算、GPU、FPGA 等中的并行计算策略将这一巨大成本降低到一个可行的水平。

另一件要记住的事情是,本文中介绍的基本蒙特卡罗方法是基于随机抽样技术的。然而,通过利用特定于问题的采样分布,可以获得算法的显著改进。例如,通过适当选择样本分布,人们可以利用几乎所有高维被积函数都非常局部化并且只有小的子空间对积分有显著贡献的事实。因此,蒙特卡罗文献的很大一部分致力于开发策略,通过巧妙的采样方法来改善误差估计。

这些更高级的主题没有包括在本文中,因为正如文章标题中所述,主要目的是提供“蒙特卡洛方法的简明介绍”。希望您已经对这些技术的假设和应用有了基本的了解,并对这些方法的有用性和优点有了评价。

我还希望您会发现实用的代码示例是有用的,我强烈鼓励您尝试不同的函数和采样策略等。毕竟,没有比亲自动手尝试更好的学*方法了!

祝你好运!

如果你有兴趣了解更多与人工智能/机器学*和数据科学相关的主题,你也可以看看我写的其他一些文章。你会发现他们都列在我的中型作者简介,,你可以在这里找到。

如果你对我以前的文章感兴趣,并且想在新内容发表时得到通知,你也可以注册下面的邮件列表

**https://medium.com/subscribe/@vflovik

而且,如果你想成为一个媒体会员,免费访问平台上的所有资料,你可以使用下面我的推荐链接。(注意:如果您使用此链接注册,我也会收到一部分会员费)

https://medium.com/@vflovik/membership **

对开源贡献的温和介绍

原文:https://towardsdatascience.com/a-gentle-introduction-to-open-source-contributions-4b2a8f1aa5cd

开源 101

为开源项目做贡献的分步指南

照片由扬西·敏Unsplash 上拍摄

随着 Ploomber 成为建设 Jupyter 管道的首选工具,我们看到社区对该项目的贡献越来越大。我们非常荣幸有这样一个充满活力的社区愿意付出额外的努力,特别是因为我们的许多贡献者都是第一次开源贡献者。为了帮助新的开源贡献者,我们准备了这个深入的指南,涵盖了你第一次做出贡献所必须知道的一切。我们开始吧!

请注意,本指南主要关注于 GitHub 上托管的 Python 项目,但是指南也广泛适用。

查找 GitHub 问题

图片作者。

一旦你选择了要贡献的项目,就去问题区。例如,点击这里查看 Ploomber 的问题。GitHub 问题是贡献者需要帮助的事情;复杂性和所涉及的工作各不相同,所以如果你是第一次为一个特定的项目做贡献,通过好的第一期进行筛选是一个好主意;这样的问题适合你的第一期投稿,点这里看 Ploomber 的好的第一期。

考虑花些时间解决这些问题;标签是一种快速识别它们所涉及内容的方法。然后,如果你对其中任何一个感兴趣,点击其中任何一个并查看描述。维护人员通常会提供一个简短的描述,但是如果你感兴趣的话,你可以询问更多的细节。

一旦你确定了一个你想要解决的问题,请维护人员把它分配给你。一旦维护人员回复并分配给你,你就可以开始工作了。在开始工作之前提出问题,以便与维护人员达成共识,这是非常必要的。请记住,他们将审查您的代码。

注意,不同的项目有不同的规则,所以检查存储库中的CONTRIBUTING文档以了解贡献指南。CONTRIBUTING文件在根文件夹中,但是扩展名可能不同(常见的扩展名是.md.rst)。比如这是 Ploomber 的 CONTRIBUTING.md 文件。

通常,项目需要文档方面的帮助;这比编写代码更容易,因为你可以从 GitHub 的在线编辑器中编辑文件。然而,文档对于任何项目都是至关重要的,所以不要低估它的重要性!例如,下面是 Ploomber 的存储库中的文档问题。

派生存储库

图片作者。

现在是时候分叉存储库了。分叉意味着为您的帐户创建一个存储库副本。存储库由用户名(或组织名)和存储库名称标识。例如,Ploomber 的 URL 是github.com/ploomber/ploomber,这意味着它隶属于 Ploomber 组织,存储库名称也是 Ploomber。如果存储库属于个人帐户,它将具有相同的格式;例如,我的项目sklearn-evaluationgithub.com/edublancas/sklearn-evaluation下,其中edublancas是我的帐户,sklearn-evaluation是存储库名称。

一旦你创建了一个库,GitHub 会在你的账户中创建一个副本。例如,如果您派生 Ploomber,它将在github.com/{your-username}/ploomber下创建一个存储库。派生存储库后,在本地克隆它:

请注意,我们正在克隆 fork,而不是原始存储库!这意味着对代码的任何更改都不会影响主存储库,所以请随意试验!

创建新的分支是一个很好的做法;您可以通过以下方式实现:

建立你的开发环境

现在是时候开始编码了!CONTRIBUTING文档通常包含设置开发环境的指令。例如,在 Ploomber 中,一旦您克隆了存储库,您就可以设置开发环境:

CONTRIBUTING文档通常指定如何测试您的环境以确保成功安装。

编码!

一般来说,代码贡献有两种类型:bug 修复和新特性。Bug 修复包括改进已经实现但没有按预期工作的东西,而新特性需要向项目添加独特的特征。在这两种情况下,我们推荐的过程是相同的:首先编写测试。

测试是一个简短的程序,在给定一些输入的情况下,它检查库是否如预期的那样运行。库有分布在许多文件中的测试。通常,它们位于项目根目录下的tests/目录中。例如,这是 Ploomber 的测试目录。组织测试的逻辑发生变化;例如,在 Ploomber 中,项目中的每个模块都有一个文件:这里是 dag 模块,这里是用于测试 dag 模块的测试文件夹。

首先编写一个测试将允许你有一个快速的方法来检查你的实现,另外向维护者展示你的测试将允许你在代码功能方面得到相同的页面。因此,作为第一步,找到在哪里添加测试。通常,您会修改一个现有的测试文件,但是如果有疑问,请询问维护者。

具体来说,假设您正在修复一个名为sum的函数中的一个 bug,这个问题有如下描述:

sum 函数中有一个错误,当被调用为 sum(2,2)时,它应该输出 4,但是它输出 5

通常,你会遇到简短的描述,但你可以积极主动,开始收集所需信息。例如,你可以使用 GitHub 的搜索栏来定位sum函数定义。默认情况下,搜索栏将在现有存储库中查找查询。因此,在这种情况下,您可以搜索sum,或者更好的是,搜索def sum来找到函数的定义。

一旦您确定了您将在哪里进行代码更改,就找到合适的测试文件。所以查看一下tests/文件夹,看看是否能找到维护人员在哪里测试sum函数。如果您找不到它,可以提出来,但是如果您花时间理解代码库布局并识别相关文件,维护人员会非常感激。一旦您确定了测试文件,在本地编辑它并添加一个新的测试;本质上,你需要把问题描述翻译成程序;按照我们的例子,这里有一个简单的测试:

首先,我们添加了一个 import 语句来获取我们正在测试的函数;然后,我们通过定义一个以test_为前缀的函数来创建一个测试,注意我们有一个很长的名字,没关系;有一个描述性的名字是很重要的。

编写好测试的一个好方法是使用 Given-When-Then 模板(或 GWT)。GWT 将我们的测试逻辑分为三部分:给定(现有条件)、何时(要测试的代码)、然后(预期结果)。下面的代码片段显示了使用 GWT 模板显式组织的相同测试:

然而,为了减少冗长,我们经常折叠不同的部分,特别是在简单的情况下,所以您通常会这样写:

我建议您在更广泛的测试中明确地组织不同的 GWT 部分。注意,我们的测试本质上将问题描述转化为代码,因为它正式描述了事情应该如何工作。

关键字assert帮助我们建立测试将评估的规则。如果陈述是真的,什么都不会发生;如果它是假的,它将触发一个错误,导致我们的测试失败。在某些情况下,库提供测试工具来简化这些断言语句的编写。例如,pandas有一个pandas.testing模块,有价值的功能是比较数据帧。了解如何进行测试的一个很好的方法是查看现有的测试,因为同一文件中的测试往往彼此相似。

既然我们没有做出任何改变,测试失败是很自然的。大多数 Python 项目使用pytest来运行测试(这应该在CONTRIBUTING文档中指定)。要运行单个测试:

你会看到测试失败了,这是意料之中的,因为我们还没有修复这个错误!

在真实的场景中,提出样本测试需要更多的工作,所以不要害怕向维护者寻求指导。此外,现有的测试可以帮助您了解如何进行测试。

一旦你有了你的(当前失败的)测试,是时候进行修复了(或者一个新的特性,如果是这样的话)。所以继续下去,看看需要更改的模块/函数/类;假设您找到了以下实现:

检查完代码后,您注意到+ 1似乎不正确,所以您删除了它:

然后,重新运行测试:

这次考验会通过的!

当然,要让你的代码通过还需要做更多的工作。通过与它交互会更容易理解,作为一个建议,您可以通过添加以下内容在您的测试中开始一个交互会话:

或者,您可以使用pdb模块:

使用IPythonpdb的区别在于IPython的行为像一个常规的 Python 会话,而pdb具有调试代码的特定功能。要了解更多信息,请阅读 pdb 文档

IPythonpdb的情况下,您需要将-s标志传递给pytest:

您将看到一个交互式会话开始。现在,您可以交互式地运行一些快速命令来帮助您理解代码。例如,您可以使用不同的参数运行函数:

完成实验后,键入quit并按 enter 键退出会话,并记住删除from IPython import embed; embed()行(如果使用pdb,则删除from pdb import set_trace; set_trace())。

或者,您可能希望让测试运行,并在测试失败后启动调试会话,在这种情况下,不需要向测试源代码添加任何内容。保持这个样子:

然后用--pdb标志执行测试:

这一次,测试将运行,如果在执行过程中出现异常,它将启动一个交互式调试会话。注意,这类似于常规的 Python 会话,但是它有一些额外的东西。这里是您可以执行的命令的列表。Python 调试器是一个方便的工具,可以交互式地探索代码并理解导致失败的条件:变量值、涉及的模块等。要退出调试会话,请键入quit并按回车键。

起草拉取请求

顾名思义,拉请求(或 PR)是一个请求你的代码拉进原来的库。当提交一个时,维护人员可以查看您的代码并决定接受您提议的更改。然而,拉请求不是一次性的过程;如果维护人员有观察,他们会提供反馈和建议,然后你可以对代码进行修改,并要求再次审查。

GitHub 引入了 Draft Pull Requests 作为贡献者获得早期反馈并确保你与维护者保持一致的方式。通过对 GitHub 问题的评论达成共识是很困难的,所以维护人员会非常感谢你起草一份 PR,这样他们就可以看到你的进展并提供反馈。将您的贡献合并到代码库中是一项集体工作,所以要期望有几次这样的来回反馈会议(或者更多,取决于您所做贡献的复杂性)。

如果进行更改,无需打开另一个拉请求;推送到你的 fork,Pull 请求会自动更新。在 Ploomber 的例子中,多次要求反馈是可以的,但是要努力提出具体的问题,这样维护人员就可以快速帮助你。如果有疑问,查看你正在参与的项目的贡献文档,因为它可能包含更具体的说明。

一旦您执行了分叉,打开拟定拉取请求的步骤如下:

  1. 在您的浏览器上打开原始的(不是您的 fork)存储库
  2. 单击导航栏中的“拉动请求”部分
  3. 点击绿色的“新拉动请求”按钮
  4. 点击“跨叉比较”
  5. 在选择的右边,选择你的叉子({your-username}/repository-name)
  6. 选择您提交更改的分支
  7. 点击绿色按钮“创建拉动式请求”
  8. 添加评论
  9. 单击“创建拉式请求”按钮旁边的绿色箭头,然后单击“创建拟定拉式请求”
  10. 单击绿色的“创建拟定拉式请求”按钮进行确认

流程如下所示:

图片作者。

恭喜你。您提交了拉取请求!现在给维护人员一些时间来回顾您的更改并分享他们的反馈。

添加更多测试

根据贡献的复杂程度,您很可能需要添加不止一个测试。随着您对代码的熟悉,考虑一下还有哪些相关的案例需要测试。按照我们的sum示例,您可能想要尝试负数:

当有疑问时,询问维护人员是否能想到其他相关的测试用例;因为他们对代码更熟悉,所以他们有很好的机会提出建议。

通常,测试用例非常相似,除了它们的输入和输出。为了简化您的代码(并假设您所贡献的库使用了pytest,您可以像这样参数化一个测试:

注意,我们重命名了测试用例,因为它现在嵌入了许多测试用例,我们可以选择添加ids参数来标识每个测试用例:

要运行这两个测试用例:

要运行特定的测试用例,请传递 ID,例如:

要了解更多关于pytest参数化的信息,查看文档。

测试异常

在前面的例子中,我们测试了一个函数的输出,但这只是测试的一种类型。在某些情况下,您可能希望测试代码是否会向用户抛出有意义的错误。如果您参与的项目使用了pytest,那么您可以测试是否出现了特定的异常:

更好的是,您可以测试特定的错误消息如下所示:

注意,我们不需要导入ValueError,因为它是一个内置异常;在某些情况下,项目可能会定义它的异常(通常在一个exceptions.py文件中),所以您可能需要导入它:

提交拉取请求

图片作者。

在几个审查周期之后,您将为最终的代码审查做好准备。理想情况下,这应该很快,因为你已经在起草过程中要求反馈。在将拉动请求标记为准备审核之前,请确保:

1。测试在本地通过

确保您添加的测试在本地通过:

2。检查林挺和格式

项目经常使用自动检查代码中特定规则的代码翻译器;确保您的代码符合规则。(贡献文档应该提到项目使用哪个 linter,以便您可以在本地检查它)。尽管为了简化事情,一些自动格式化程序会自动使您的代码与 linter 兼容(查看贡献文档以了解更多细节)。

如果测试和林挺通过,你可以git push并标记你的 PR 为准备审查!

持续集成

您的git push将触发存储库的持续集成系统(或 CI)。CI 是“对每个 PR 运行所有测试”的一种奇特说法运行所有的测试可以确保你的代码没有任何意想不到的副作用。通常,测试套件相当全面,所以可能需要几分钟才能完成。如果测试失败,检查日志中的提示;也许有一些林挺问题或副作用,所以试着理解发生了什么;如果你不能,你可以问维护者。

耐心点。这个过程需要时间。通常,维护人员会自愿利用他们的业余时间来保持项目的活力,所以如果他们需要一点时间来回应,请耐心等待。

如果测试通过,维护人员将执行代码审查,如果一切顺利,他们将合并您的代码!另一方面,如果测试没有通过,您可以点击“详细信息”(见上面的截图)来检查测试。花一些时间来理解测试失败的原因——如果您积极主动地识别根本原因,修复测试,并推动您的更改(推动新的更改将再次触发测试),维护人员将会非常感激。

闭幕词

请继续关注我们的下一篇博文,在那里我们将讨论贡献新特性和一些高级测试技术的思维过程,例如使用 unittest.mock 模块和测试 CLI 应用程序。

如果你准备好迎接挑战,请将你今天学到的知识应用到 Ploomber 中!

最初发布于ploomber . io

最优化的简明介绍

原文:https://towardsdatascience.com/a-gentle-introduction-to-optimization-f95938ce475e

什么是优化,它是如何在幕后工作的?

里卡多·戈麦斯·安吉尔在 Unsplash 上的照片

在本文中,您将学*优化的基础知识,并了解优化在幕后真正是如何工作的。通过两个示例问题,我将说明如何构建一个简单的优化问题,并向您展示问题组成部分的变化如何影响您的解决方案。

我将讨论以下主题:

  • 什么是优化?
  • 为什么重要?
  • 优化用在哪里?
  • 约束优化和无约束优化的区别是什么?
  • 优化模型的三个核心要素是什么?
  • 如何建立一个简单的优化问题(2 个例子)

本文将不涵盖复杂的数学方法或算法、优化软件或不同类型的优化问题。

什么是优化?

最简单地说,优化意味着在所有可用的解决方案中选择最佳的解决方案。

但是,最好是什么意思呢?最佳将取决于你手头的问题。对于你正在解决的问题,最好是否意味着最大的利润?还是最好意味着成本最低?它意味着节省的时间最多还是使用的资源最少?“最好”的定义取决于你试图解决的问题。

什么时候需要优化?只有当不止一个解决方案可用时,使用优化才有意义。

为什么重要?

为什么我们甚至关心优化?为什么重要?

为了理解它的重要性,让我们看看四个不同层次的分析。点击链接此处查看 Gartner 分析优势模型,该模型有助于说明组织的数据成熟度。

x 轴表示难度或复杂程度,y 轴表示价值或影响。四个不同层次的分析从事后到前瞻不等,其中前瞻最为复杂。

第一级分析是描述性分析。它告诉你发生了什么。例如,浏览网站的平均时间或年同比销售增长。

第二级分析是诊断分析。为什么会这样?其特点是深入研究数据,以确定您在数据中看到的潜在原因。

接下来,你有预测分析。会发生什么?为了进行预测,我们使用机器学*模型。也许你可能听说过聚类模型或回归模型。好吧,你猜怎么着,这些机器学*模型,它们依靠最优化来找到它们的答案。

规定性分析下,优化也是它自己的领域。我们做什么决定才能让事情发生?例如,我们如何分配零售货架以实现利润最大化?要将多少产品运送到美国各地的仓库,才能将总成本降至最低,但仍能满足需求?

这种决定将会有巨大的价值,对吗?这将有助于我们提高效率,或者提供竞争优势。优化是非常强大的,因为您可以在战略、运营和战术层面指导组织。

从图表中可以看出,这是最复杂的分析级别,但它提供了如此多的价值。我们会在接下来的几个例子中看到。

优化用在哪里?

照片由 Ravi PalweUnsplash 拍摄

你可能没有想到这一点,但优化是在你身边工作。当你使用 GPS 时,无论是谷歌地图还是苹果地图,它都是在计算到达目的地的最短驾驶距离。这就是优化。

优化不仅在日常问题中发挥作用,而且已经被用于各种行业的各种类型的问题中。下面是两个著名的优化例子。

示例 1: UPS

运输行业使用优化的一个著名例子是 UPS。UPS 希望为他们的司机找到递送包裹的最有效路线,以便节省时间,同时减少燃料消耗。为了节省时间,公司决定司机应该尽可能避免左转。在美国,你需要等绿灯和车辆通过才能左转。因此,取消左转意味着更少的时间浪费和更少的燃料消耗。UPS 创建了一个名为 ORION 的专有优化软件,以帮助减少递送路线中的左转。

来源:荷兰等:UPS 优化递送路线

在上图中,左边是司机对路线的解决方案,右边是猎户座的解决方案。在地图上可以看到,猎户座的解决方案比司机的解决方案效率高很多。使用 ORION 软件节省的 30 英里是有区别的。

说到价值,“自从 ORION 最初部署以来,它每年已经为 UPS 节省了大约 1 亿英里和 1000 万加仑的燃料。”— UPS

这肯定会给他们带来超越竞争对手的竞争优势。正如您在这里看到的,优化为 UPS 提供了巨大的价值。

例子 2:美国陆军

我的下一个例子很经典。最早的优化问题之一可以追溯到 20 世纪 30 年代。在第二次世界大战期间,陆军想找出如何在满足健康饮食的营养需求的同时,最大限度地降低在战场上喂养美国大兵的成本。

马丁·亨德里克斯Unsplash 上拍摄的照片

研究这个问题的经济学家乔治·斯蒂格勒发现,最佳饮食包括以下 5 种食物:370 磅。小麦粉,57 罐奶粉,111 磅。卷心菜,23 磅。菠菜和 285 磅。干海军豆。

这不是听起来最美味的饮食,但它每年只需花费 39.93 美元。有趣的是,按照今天的价格,大约是 831 美元。

来源:维基百科

这种饮食最大限度地降低了成本,同时也满足了以下营养需求。

来源:维基百科

在战争时期,最小化成本是至关重要的,因此,这个优化问题对军队来说具有巨大的价值。

无约束与约束优化

形象地说,如果我们思考优化是什么,它只是寻找最大值点或最小值点。

来源:作者

在上图中,我们有一个无约束优化的例子,其中最高峰是最大值点,最低点是最小值点。

然而在现实中,我们有约束,所以,图表看起来更像这样。

来源:作者

我们将受到限制,因为我们处在一个资源有限的世界。例如,一天只有 24 小时。我的银行账户里只有 X 美元。我们能利用的资源有限。

红线代表约束,由于约束,我们的最大点不再是最高峰,而是在峰的一半左右。上图展示了约束优化,这是我们在讨论优化问题时通常会遇到的优化类型。

优化问题的三个核心要素

现在让我们进入任何优化问题的三个核心要素。我将使用前面提到的斯蒂格勒饮食问题作为这些核心要素的例子。

1.目标函数

2.决策变量

3.限制

目标函数

前面我们讨论了“最好”这个词。谈到优化,我们试图找到最佳解决方案。目标函数将帮助我们衡量什么是最好的。

来源:维基百科

在饮食问题中,“最好”意味着年度总成本最小化。因此,年度总成本是我们衡量解决方案质量的标准。也就是说,年总成本越小,解决方案越好。

决策变量

决策变量是你必须做出决定的事情。这些事情是可以调整的,或者换句话说,这些事情是在你的控制之内的。您不知道最佳值是多少,但优化求解程序会为您选择最佳值。

来源:维基百科

在饮食问题上,斯蒂格勒必须弄清楚给士兵喂什么食物,每种食物吃多少。食物的种类和数量是这个问题的决策变量。

限制

约束是对这些决策的限制。在饮食问题上,斯蒂格勒有以下营养限制。

来源:维基百科

一个成年人每天需要 3000 卡路里,70 克蛋白质,等等。选择的饮食需要满足这些要求。这个元素非常重要,因为软件可以为你计算并找到最佳解决方案,但它不理解现实。你必须为机器翻译现实生活中的约束,否则,你可能会得到一个实际上没有意义的解决方案。

解决办法

我们一直在使用解决方案这个词,所以让我们明确定义一些与解决方案相关的术语。

  • 是每个决策变量的一组值。比如 5 斤菠菜。这可能是一个解决方案。20 磅菠菜。这可以是另一种解决方案。
  • 一个可行的解决方案是一个实际可行的解决方案。也就是满足我们约束的解决方案。如果 20 磅菠菜足以满足营养需求,那么这是一个可行的解决方案。
  • 为我们提供最佳价值的一个可行解决方案是我们的最优解决方案。在饮食问题上,那就是 23 斤菠菜。

最优化问题 1

既然我们已经了解了所有的术语,让我们来看看一个超级简单的玩具优化问题。它应该非常简单,以便帮助你轻松地建立一个优化问题的思维模式。这里的想法是学*使用我们所学的术语来构建一个优化问题,并理解优化是如何工作的,所以不要太担心解决方案。

照片由June gathere coleUnsplash

这是我们手头的问题。

  • 所以毛毛有限责任公司希望利润最大化。
  • 他们有两种产品:可爱的独角兽枕头和丰满的猫娃娃。
  • 它是如此蓬松有限责任公司有足够的雪尼尔材料生产最多 2 个可爱的独角兽枕头。
  • 它是如此蓬松有限责任公司有足够的面料生产最多 3 个丰满的猫娃娃。
  • 可爱的独角兽获利 15 美元,胖猫获利 10 美元。

花一分钟思考一下这个优化问题的三个核心组成部分是什么。这里有一个提醒,如果你忘记了核心组件。

1.目标函数

2.决策变量

3.限制

的目标函数是什么?我们想为公司获取最大利润。利润是独角兽枕头的价格乘以售出的独角兽枕头的数量,再加上猫娃娃的价格乘以售出的猫娃娃的数量。

什么是决策变量?公司可以决定哪些事情?要制作的可爱独角兽枕头的数量和要制作的丰满猫娃娃的数量。

面临的制约有哪些?由于材料限制,最多只能有 2 个可爱的独角兽枕头和 3 个丰满的猫娃娃。

另一件值得注意的事情是,不可能生产少于 0 个独角兽枕头或 0 个猫娃娃。虽然这对我们来说是直观和符合逻辑的,但是将这些约束构建到问题中是一个很好的实践。您希望避免计算机可能输出不合逻辑的解的情况,例如在这种情况下输出负数。

来源:作者

让我们把刚刚想到的组件转换成图形形式,以便可视化问题。

下图中的 X 轴和 Y 轴是我们的决策变量。现在让我们画出我们的约束。在你向下滚动之前,花一点时间考虑一下如何在这个图上画出约束条件。

来源:作者

它应该看起来像下面这样。图上的红线代表我们应用于这个优化问题的约束。我们在 0 和 2 处有一条垂直线,在 0 和 3 处有一条水平线。我用绿色标出了可行解空间,用红色标出了不可行解空间。

来源:作者

在绿色的可行解空间中,我们现在要找到独角兽和猫的最佳数量。记得之前,独角兽枕头的利润是 15 美元,猫娃娃的利润是 10 美元。如果该公司生产 1 个独角兽枕头和 0 个猫娃娃,它将赚 15 美元。如果该公司生产 2 个独角兽枕头和 1 个猫娃娃,它将赚 40 美元。诸如此类。

我已经计算了下图中每个可行方案的利润。那么哪一个是最优解呢?也就是说,哪一个会帮助我们实现利润最大化?

来源:作者

利润最高的是 60 美元,这里制作了 2 个毛绒绒的独角兽枕头和 3 个胖乎乎的猫娃娃。

来源:作者

最优解:蓬松独角兽= 2,丰满猫= 3

解决方案是合乎逻辑的,不需要经过这些步骤,但这里的想法是建立对如何构建优化问题的理解,就像我们刚刚做的那样。这很重要,因为计算机执行计算来为你找到解决方案,但是你必须为计算机正确地建立问题。

最优化问题 2

在问题 1 的基础上,现在让我们给问题添加一个额外的约束。

  • 这个小有限责任公司的人力一天最多只能生产 4 种产品。

这对现在的事情有什么影响?我们有一个额外的约束需要添加到问题中,但其他一切都保持不变。

来源:作者

现在,让我们也将这个约束添加到图形形式中。下面的红色斜线是我们刚刚添加的新约束。由于这个附加约束,绿色矩形右上角的三角形区域不再是绿色的。

来源:作者

因此,在我们可行的解决方案范围内,这是每种解决方案的利润。

来源:作者

最高利润为 50 美元,其中制作了 2 个毛绒绒的独角兽枕头和 2 个丰满的猫娃娃。

来源:作者

最优解:蓬松独角兽= 2,丰满猫= 2

问题 1 对问题 2

如果我们比较问题 1 的解决方案和问题 2 的解决方案,您会注意到什么?

来源:作者

在问题 2 中,我们的可行解空间较小。问题 2 中的利润较低。

发生了什么事?

一个额外的约束减少了我们的可行解空间,使我们的解变得更糟。在设置问题时,记住这一点很重要。你想添加的约束是必须的吗?因为我们越放松约束,优化软件就有越多的空间去寻找更好的解决方案。

感谢您的阅读,希望您在学*优化的过程中获得乐趣。

自学入门

原文:https://towardsdatascience.com/a-gentle-introduction-to-self-learning-5d6d40349f7c

使用 Python 实现

格伦·卡斯滕斯-彼得斯在 Unsplash 上拍摄的照片

在这篇文章中,我们将介绍自我学*的主题,然后从头开始实施两个简单的自我学*策略(几乎)来巩固我们的知识。

这个帖子是基于[1]和[2]的天方夜谭。

但首先,让我们谈谈什么是自我学*,以及作为一名数据科学家,为什么它是一个需要添加到您的工具箱中的相关工具。

我们现在都知道,可用数据的数量已经增长了很多,这导致了深度学*模型的重生,使机器学*领域作为一个整体达到了一个新的性能目标。

然而,即使有大量数据可用,标记的数据也很少。这是因为标注数据困难、昂贵且缓慢。因此,我们有大量未标记的数据,我们必须对它们有所了解。

另一方面,机器学*最成熟的领域是监督学*。正如我们许多人所知,处理无监督学*可能相当困难。正因为如此,什么是更好的,有一点点标记的数据还是去一个完全无监督的方法?

好吧,我没有能力回答这个问题,但因为它,半监督学*的领域已经到来。这个领域的重点是通过使用少量的已标记数据来理解大量的未标记数据。这种情况通常出现在图形问题中。

自我训练是进行半监督学*的一种方法。让我们深入研究一下。

这篇文章的代码可以在我的 GitHubKaggle 上找到。

谈论自我训练

自我训练背后的主要思想是使用标记和未标记的数据,让您的模型(或多个模型)从自身(或彼此)学*。

在这个过程中,你不仅要教会模型数据的模式,还要教会它如何使用未标记的数据来继续学*。

在这篇文章中,我们将探讨两种自我训练的方法:伪标记和双分类器自我训练。

伪标记

伪标记的思想是为未标记的数据生成标记,然后使用这个新的标记数据用比以前看到的更多的信息来重新训练模型。

当然,当您的模型出错时,这可能会有问题,这就是为什么我们不预测整个测试集,然后重新训练模型。我们将通过仅添加我们的模型更有信心的预测来迭代。我们通过选择最高的分类概率来做到这一点,因此,为了做到这一点,我们必须使用校准的分类器[2]。

让我们从定义要使用多少次伪标签迭代开始我们的代码。我们还将加载葡萄酒数据集,并定义一些参数供我们测试,以查看该方法如何改善或恶化结果:

pseudo_label_iters = 20
thresholds = [0.4, 0.6, 0.8, 1]
null_percs = [0.2, 0.5, 0.7]# Data Loading
df = datasets.load_wine()

我们将把结果与没有在伪标记数据上训练的分类器进行比较。我们将迭代空值的百分比和阈值,以定义哪个是高置信度预测。

我们还将校准我们的分类器,并用 GridSearch 超参数优化器对其进行优化。

rf = RandomForestClassifier(n_jobs=-1)parameters = {
    'n_estimators': [10, 50],
    'class_weight': [None, 'balanced'],
    'max_depth': [None, 5, 10]
}results = pd.DataFrame()for threshold in tqdm(thresholds):
    for null_perc in null_percs:

        # Creating a test set for us to validate our results (and compare to a non-self-learning classifier)
        X_train, X_test, y_train, y_test = train_test_split(
            df.data, df.target, test_size=0.3, shuffle=True)

        # Randomly removing null_perc % of labels from training set
        rng = np.random.RandomState()
        random_unlabeled_points = rng.rand(y_train.shape[0]) < null_percy_train[random_unlabeled_points] = -1
        new_y_train = y_train.copy()# Training loop
        for i in range(pseudo_label_iters):# Select the labeled set
            X = X_train[np.where(new_y_train != -1)]
            y = new_y_train[np.where(new_y_train != -1)]# Select the unlabeled set
            X_un = X_train[np.where(new_y_train == -1)]
            y_un = new_y_train[np.where(new_y_train == -1)]if len(y_un) == 0:
                break# Hyperparameter optimization
            rf_ = GridSearchCV(rf, parameters, cv=2).fit(X, y).best_estimator_# Probability Calibration    
            calibrated_clf = CalibratedClassifierCV(base_estimator=rf_,
                                                    cv=2,
                                                    ensemble=False)
            calibrated_clf.fit(X, y)
            preds = calibrated_clf.predict_proba(X_un)# Adding the high confidence labels
            classes = np.argmax(preds, axis=1)
            classes_probabilities = np.max(preds, axis=1)high_confidence_classes = classes[np.where(classes_probabilities >= threshold)]y_un[np.where(classes_probabilities >= threshold)] = high_confidence_classesnew_y_train[np.where(new_y_train == -1)] = y_un# Validation
        X = X_train[np.where(new_y_train != -1)]
        y = new_y_train[np.where(new_y_train != -1)]
        calibrated_clf.fit(X, y)y_pred_self_learning = calibrated_clf.predict(X_test)X = X_train[np.where(y_train != -1)]
        y = y_train[np.where(y_train != -1)]calibrated_clf.fit(X, y)
        y_pred = calibrated_clf.predict(X_test)

        results = pd.concat([results, pd.DataFrame([{
            'threshold': threshold, 'null_perc': null_perc,
            'normal_acc': accuracy_score(y_test, y_pred),
            'pseudo_acc': accuracy_score(y_test, y_pred_self_learning)
        }])])

在这里,我们可以看到改变数据中的空值百分比后的结果:

伪标签的零百分比变化。由作者开发

这里我们有阈值变化:

伪标记的置信阈值变化。由作者开发

正如我们所看到的,自我训练方法开始产生更好的平均结果,并且变化越少,空值越多,我们对预测越有信心。尽管如此,差异并不明显,主要是因为我们处理的是一个相当小的“简单”数据集。

两个分类器自训练

这种类型的自我训练看起来像伪标记,但我们不是每次都使用同一个分类器,而是使用两个分类器。在每一步,我们将在可用数据上训练一个分类器,然后我们将预测下一批新数据。然后,我们切换我们正在使用的分类器,这样做几次。

因此,我们试图使模型更加健壮,因为我们总是根据前一个分类器的输入来提供新的分类器。

对于这个实现,我们将依赖于我们为伪标签制作的概念。

from sklearn.neural_network import MLPClassifier
rf = RandomForestClassifier(n_jobs=-1)
mlp = MLPClassifier()rf_param = {
    'n_estimators': [10, 50],
    'class_weight': [None, 'balanced'],
    'max_depth': [None, 5, 10]
}mlp_param = {
    'hidden_layer_sizes': [(50,), (50, 50), (5, 50, 50)],
    'alpha': [0.0001, 0.001, 0.01]
}results = pd.DataFrame()for threshold in tqdm(thresholds):
    for null_perc in null_percs:

        # Creating a test set for us to validate our results (and compare to a non-self-learning classifier)
        X_train, X_test, y_train, y_test = train_test_split(
            df.data, df.target, test_size=0.3, shuffle=True)

        # Normalizing the data
        scaler = StandardScaler()
        X_train = scaler.fit_transform(X_train)
        X_test = scaler.transform(X_test)

        # Randomly removing null_perc % of labels from training set
        rng = np.random.RandomState()
        random_unlabeled_points = rng.rand(y_train.shape[0]) < null_percy_train[random_unlabeled_points] = -1
        new_y_train = y_train.copy()# Training loop
        for i in range(pseudo_label_iters):
            # Choose the classifier to use
            if i % 2 == 0:
                clf = rf
                parameters = rf_param
            else:
                clf = mlp
                parameters = mlp_param# Select the labeled set
            X = X_train[np.where(new_y_train != -1)]
            y = new_y_train[np.where(new_y_train != -1)]# Select the unlabeled set
            X_un = X_train[np.where(new_y_train == -1)]
            y_un = new_y_train[np.where(new_y_train == -1)]if len(y_un) == 0:
                break# Hyperparameter optimization
            clf_ = GridSearchCV(clf, parameters, cv=2).fit(X, y).best_estimator_# Probability Calibration    
            calibrated_clf = CalibratedClassifierCV(base_estimator=clf_,
                                                    cv=2,
                                                    ensemble=False)
            calibrated_clf.fit(X, y)
            preds = calibrated_clf.predict_proba(X_un)# Adding the high confidence labels
            classes = np.argmax(preds, axis=1)
            classes_probabilities = np.max(preds, axis=1)high_confidence_classes = classes[np.where(classes_probabilities >= threshold)]y_un[np.where(classes_probabilities >= threshold)] = high_confidence_classesnew_y_train[np.where(new_y_train == -1)] = y_un# Validation
        X = X_train[np.where(new_y_train != -1)]
        y = new_y_train[np.where(new_y_train != -1)]
        calibrated_clf.fit(X, y)y_pred_self_learning = calibrated_clf.predict(X_test)X = X_train[np.where(y_train != -1)]
        y = y_train[np.where(y_train != -1)]calibrated_clf.fit(X, y)
        y_pred = calibrated_clf.predict(X_test)

        results = pd.concat([results, pd.DataFrame([{
            'threshold': threshold, 'null_perc': null_perc,
            'normal_acc': accuracy_score(y_test, y_pred),
            'pseudo_acc': accuracy_score(y_test, y_pred_self_learning)
        }])])

至于结果,我们有零百分比变化:

两个分类器的百分比变化为零。由作者开发

阈值变化:

两个分类器的置信阈值变化。由作者开发

由于在这种情况下,我们使用了两个分类器,因此可以合理地预期数据量越大越好,即使它是未标记的。正如我们从结果中看到的,自我训练方法勉强能够克服默认分类器。

结论

正如我常说的,数据科学是关于使用正确的工具解决业务问题,而自我培训是你工具箱中应该有的另一种工具,可以帮助你处理一系列新问题。

关于它还有很多内容要讲,但是我希望这个初步的介绍可以帮助您入门。

[1]阿米尼,马西-雷扎&费奥法诺夫,瓦西里&保莱托,洛伊奇&德维杰夫,艾米莉&马克西莫夫,尤里。(2022).自我训练:一项调查。

[2]https://sci kit-learn . org/stable/modules/calibration . html # calibration

蒙特卡洛模拟的简明介绍

原文:https://towardsdatascience.com/a-gentle-introduction-to-the-monte-carlo-simulation-a3e5518a4aa7

了解如何使用 R 和 Python 创建这个著名的模拟

安德鲁·西曼在 Unsplash 上拍摄的照片

什么是蒙特卡洛模拟?

蒙特卡罗模拟(以下简称 MCS)——也称为多概率模拟——是一种估计不确定事件结果概率的方法。

在数学家 Stanislaw Ulam 在建造原子弹的项目中考虑了这一方法之后,这一方法变得很有名。除了使 MCS 出名的令人讨厌的目的之外,仅仅通过一遍又一遍地模拟问题来获得一些问题的*似结果实际上是非常有用的。随着计算机处理速度的提高,MCS 成为您工具箱中的另一个好资源。

蒙特卡罗模拟可以理解为将同一个实验重复 n 次的过程,使用随机生成的遵循我们数据相同分布的数字来模拟问题的一个变量。

蒙特卡罗模拟和 LLN

支持 MCS 的一个概念是大数定律。这条著名的定律表明,当我们增加一个给定实验的重复次数时,结果的平均值会越来越接*那些事件发生的概率的真实值。

增加重复次数,你更接*真实概率。图片由作者提供。

我们都知道,掷硬币时,正面或反面各有 50%的机会。但是如果你连续两次抛硬币,你可以得到正面(H-H),反面(T-T)或 H-T 或 T-H。嗯,一半的机会是你只会得到正面(100% | 0%)或反面(0% | 100%),这与真实概率相差甚远。现在,如果你把同一个硬币抛几百次,你就知道了。你肯定会看到 50/50 分布的一些东西。

这就是为什么 MCS 多次运行相同的实验。

鱼类问题

想象一个湖。我们想知道里面有多少鱼。一个经典的 MCS 问题解决了这个问题,它说我们最初可以钓 100 条鱼并给它们做标记。现在我们知道有 100 种鱼被标记,但我们不知道它在总数中所占的比例。

因此,我们重复这个过程,再钓 100 条鱼几次,检查有标记和没有标记的鱼的百分比。如果我们知道被标记的鱼的大概百分比,我们需要做的就是计算总数(例如,MCS 给我们 20%被标记的鱼,这意味着在那个湖中被标记的鱼的数量(100)代表总数的 20%)。因此,总数将是 500。)

让我们看看代码。

# Creating the Lake with 100 marked fish and 400 not marked
fishes <- data.frame(num = seq(1:500),
                     marked = rep(c(1,1,0,0,0,0,0,0,0,0), times=50)
                     )# Confirming 100 marked fishes
marked_fish_total <- sum(fishes[fishes$marked==1,2])
print( paste("Marked Fish in the lake:", marked_fish_total) )### Monte Carlo Simulation ###*monte_carlo <- function (reps, dataset){
  "Run the same experiment for n reps and return the average proportion
  * reps: [int] How many repetitions of the same experiment
  * dataset: [data.frame] it is the dataframe with the fish data
  "

# Creating a vector to collect the marked fishes proportion
marked_prop <- c()**#Let's simulate a fishing of 100 fishes, repeated 1,000 times
  for (experiment in seq(1,reps)) {

    # Shuffle the dataset
    shuffled <- sample(nrow(dataset))
    dataset <- dataset[shuffled, ]

    # Create a random index before catching the fish
    index <- sample(1:500, size=100)
    fishing <- dataset[index,]
    # Calculate the proportion
    p <- mean(fishing$marked)

    # Store result in the vector
    marked_prop <- c(marked_prop, p)

  } # close for Loop**# Calculate and return the mean proportion after n repetitions
  return (mean(marked_prop))**} #close the function* # Running the Monte Carlo Simulation
marked_fish_proportion <- monte_carlo(1000, fishes)"If we know the percentage of marked fish and we know that the total 
of marked fish in that lake is 100, then we can calculate the 100%
using this formula:
Marked Fish Total / Marked Fish Proportion "# Total Fish in the lake
marked_fish_total / marked_fish_proportion

将上面的整个代码运行 500 次后,下面是结果的直方图。

看到我们能够在大多数情况下击中 500 条鱼的正确结果,或者非常接*它。图片由作者提供。

嗯,这是维基百科中 MCS 定义的葡萄牙语版本中共享的一个示例问题,但我可能会说这更像是一个自助问题,而不是蒙特卡洛问题,尽管它不是。

MCS 和自举有什么不同?

这两种技术密切相关,因为它们都是基于大数定律和中心极限定理,通过重复来得到估计的数字。

然而,它们之间的最大区别是,Bootstrap 技术将对您已经拥有的数据进行采样,并从中获得估计的统计数据,就像一个奇特的平均计算一样。

现在,MCS 技术将使用与您的真实数据相同的分布创建新的随机数,因此它模拟了一个新的数据集,并帮助您回答这个如果问题:“如果新数据在相同的条件下不断出现,我试图预测的统计数据/数字会发生什么情况?”

使用 R 理解 MCS 逻辑

要应用蒙特卡罗方法,记住以下主要步骤很重要:

1。定义您的输入。你试图预测/估计什么,你的解释变量是什么。

现在,我们受雇于一家餐馆,该餐馆想知道一个月内来就餐的平均顾客人数。业主给我们 90 天的出勤率:平均每晚 193 人。250 或更多的人发生了几次。在最平静的夜晚,他们看到大约 150 人来吃饭。

# Creating a Normal Distribution
n <- rnorm(90, mean=200, sd=40)
n <- as.integer(n)[day1] 171 163 170 153 247 251 213 163 233 277 197 175 165 200 155 238 260 183 253 199 299 236 207 147 219 238 234
256 177 160 143 188 241 230 234 143 152 201 241 180 153 144 163 196 103 177 209 254 240 203 191 105 178 219
263 241 183 176 161 167 291 179 273 186 239 192 199 192 121 270 182 196 162 187 177 168 201 143 185 228 217
149 238 177 201 220 182 190 193 186 [day90]

每晚平均顾客人数。图片由作者提供。

2。定义解释变量的分布(如正态分布、连续均匀分布、指数分布)。

在这种情况下,我创建了一个正态分布,但我们也可以将平均数视为正态分布,因为它依赖于中心极限定理,样本的平均值将收敛到一条*似正态的曲线。

3。我们的下一步是为我们的模拟创建一个分布。因此,我们必须计算累积分布函数,或 CDF,它将告诉我们一个数在给定区间内的概率是多少。

为了使上面的陈述更清楚,让我们画出 CDF。

# Plot the CDF
plot( x= n, 
      xlab= 'Customers',
      y= pnorm(n, mean=193, sd=40),
      ylab= 'Probability',
      col='red', 
      main='CDF for Customers')

累积分布函数。图片由作者提供。

请注意,对于 X 中的每个客户数量,都有一个与 y 相关联的概率。因此,如果您想知道有 50%可能性发生的客户数量,您将看到平均值 193。

4。有了这个分布,现在你可以生成 0 到 1 之间的 N 个随机数。这些数字将代表您可以与步骤 3 中的分布相关联的百分比,从而生成一个模拟的餐厅出席人数。

# Simulated attendance
mcs_customers <- function(simulations){
  "This function takes an integer number and generates that amount of repetitions of the simulation"

  # Create a list to store the results
  mcs_results <- c()

  # Loop
  for (n in 1:simulations){
    # Generate a random number
    r <- runif(1)
    # Use our CDF to capture the simulated quantity of customers
    simulated <- qnorm(r, mean=customer_avg, sd= customer_std)
    # Take the lowest integer rounded
    simulated <- floor(simulated)

    #Store result
    mcs_results <- c(mcs_results, simulated)

  } #end loop

  # Plot histogram
  hist(mcs_results, col='royalblue')

  # Return vector
  return(mcs_results)

}# end function

并且应用上面的函数,就是这个结果。

mcs <- mcs_customers(500)

每晚模拟的平均客户数量。图片由作者提供。

比较两个结果,看看它们有多相似。我们真的可以说,在保持其他变量不变的情况下,每晚的平均顾客数量将在 200 人左右浮动。有了这个数字,你就可以计划人员配备、桌子数量、估计收入和进行其他战略分析。

对比:真实分布|每晚客户的模拟分布。图片由作者提供。

在 Python 中应用 MC 仿真

同样的练*可以用 Python 来完成。我个人认为,R 更适合创建快速 MCS,因为它已经内置了所需的统计工具——比如那些用于计算随机数、CDF 和分布的逆值的工具。

在 Python 中,scipy是和numpy一起做这些事情的最好的库。

#Imports
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import scipy.stats as scs# 90 Days of Clients
customers = [171, 163, 170, 153, 247, 251, 213, 163, 233, 277, 197, 175, 165, 200, 155, 238, 260, 183, 253, 199, 299, 236, 207, 147 ,219, 238, 234, 256, 177, 160, 143, 188, 241, 230, 234, 143, 152,201, 241, 180, 153, 144, 163, 196, 103, 177, 209, 254, 240, 203, 191, 105, 178, 219,263, 241, 183, 176, 161, 167, 291, 179, 273, 186, 239, 192, 199 ,192, 121, 270, 182, 196, 162,187, 177,168, 201, 143, 185, 228, 217, 149, 238, 177, 201, 220, 182, 190, 193, 186]# Histogram of customers
sns.histplot(customers)
plt.title('Histogram of Customers');# Calculating Mean and Std of customers
cust_avg = np.mean(customers)
cust_std = np.std(customers)# Calculating CDF from the distribution
cdf = scs.norm.cdf(customers, loc=cust_avg, scale=cust_std)#Plot CDF
sns.lineplot(x=customers, y=cdf)
plt.annotate(f'mean: {round(cust_avg,1)}', xy=(cust_avg+10, 0.5),
             bbox=dict(boxstyle="larrow, pad=0.4", fc="lightgray", ec="r", lw=0));

客户的累积分布频率(CDF):这是模拟的来源。作者图片

# Function to perform simulations
def mcs(simulations):
 * "Provide integer with number of simulations and the function returns an array and histogram with the results"*
  # List to store results
  results = []
  # Loop
  for n in range(simulations):
    # Generate random number as a probability
    r = np.random.uniform()
    # Calculate the number
    sim_cust = scs.norm.ppf(r, loc=cust_avg, scale=cust_std)
    # Math floor of the number
    sim_cust = int(sim_cust)#Store result
    results.append(sim_cust)# Plot Histogram
  sns.histplot(results)
  plt.title('Histogram of Results MCS')#Return array
  return results# Apply mcs function
r = mcs(500)

原始数字与模拟数字。图片由作者提供。

我们可以看到,这些数字与实际情况非常接*。就像硬币的例子一样,我们模拟得越多,它就越接*真实的概率。

考虑到第 1 天是星期一,我们来看看按工作日划分的模拟客户。

按工作日模拟平均值。图片由作者提供。

看起来我们在周二和周末需要更多的人员(《Fri 太阳报》)。

按工作日模拟。图片由作者提供。

这是非常强大的。

在你走之前

在你走之前,我想把逻辑打印出来,这样你可以练*并发展它来解决更复杂的问题。

我一直在研究蒙特卡洛模拟,我知道还可以做得更多。

  1. 确定你的问题。知道自己要模拟什么。
  2. 找出原始数据的分布(正态分布,均匀分布)。
  3. 计算累积分布频率(CDF)
  4. 生成一个介于 0 和 1 之间的随机数作为你的随机概率。
  5. 使用您的 CDF,该数字将被转换为模拟数字。
  6. 根据需要重复多次,计算出您想要的结果。

如果你喜欢这个内容,你可以使用这个推荐代码订阅 Medium。

你也可以关注我的博客了解更多内容。

https://gustavorsantos.medium.com/

参考

https://en.wikipedia.org/wiki/Law_of_large_numbers

蒙特卡洛模拟的定义:https://tinyurl.com/3nkxpe4p

https://en.wikipedia.org/wiki/Monte_Carlo_method https://www.ibm.com/cloud/learn/monte-carlo-simulation

云 ML 的绿色计划

原文:https://towardsdatascience.com/a-green-initiative-for-cloud-ml-c9e7dee6d058

呼吁服务提供商提供零排放的计算实例

美国国家海洋和大气管理局在 Unsplash 拍摄的照片

让我首先声明,这篇博文中表达的观点仅是我个人的,不代表任何其他实体。

对于注意力不足的同志们,我将从底线开始:
这篇文章呼吁云服务提供商提供由可再生能源驱动的计算实例类型。
如果您同意我的观点,请在下次有机会时向您的云服务提供商提交绿色计算实例的功能请求。

现在来看细节…

管理与云活动相关的碳排放的挑战

你会越来越多地听到公司“走向绿色”;安装太阳能电池板,安装灰水系统,更换塑料制品,减少菜单上的肉类含量,等等。我们都为在这样的公司工作而自豪。不幸的是,尽管您的公司可能真诚地希望减少碳足迹,但当涉及到云计算活动时,它可能会面临一个不可逾越的障碍。它可能会发现,虽然其核心业务严重依赖云服务,但该公司在管理与这些云活动相关的碳排放方面几乎无能为力。例如,如果你在一家为自然语言处理构建大型人工智能模型的“绿色公司”工作,你可能会大开眼界。

云服务提供商越来越多地向他们的客户提供碳足迹计算器——专门用于测量其云活动碳足迹的仪表板(例如,请参见此处的)。这些举措是可喜的,但出于几个原因,它们也是不够的。

  1. 虽然它们可能提供准确的碳排放量估计,但它们并没有为如何减少碳排放这一更为困难的问题提供解决方案。
  2. 它们支持事后使用分析,但不提供控制(如封顶)未来排放的选项。
  3. 除非你在一家小公司,否则审查这些报告的人可能与实际发起排放昂贵的云活动的人非常脱节。

对人工智能的碳成本的认识正在增长

越来越多的人意识到人工智能的碳成本(如此处)。与此同时,人工智能模型的规模、训练它们所需的碳排放成本,以及参与此类模型的团队和公司的数量也出现了极大增长。不幸的是,我担心后者的增长远远超过前者。

最*,我注意到深度学*论文中报告其结果模型的能源需求和碳足迹的趋势越来越多——这证明了人们对人工智能这一副产品的意识越来越强。虽然这是朝着正确方向迈出的一步,但我希望这些论文不仅包括最终模型的碳足迹,还包括创建该模型的研究的碳足迹,有时碳足迹可能会高出几个数量级。

虽然人工智能一直因其碳排放而受到指责,但我相信这也是其他云计算活动非常关注的问题。基于云计算的低成本计算能力的巨大可用性导致了贪婪。如今,将一个人的云计算资源翻倍比将其工作负载的能源需求减半要容易得多(通常也更便宜)。此外,由于计算是远程进行的,因此很容易忽略碳排放成本。我记得曾经读到过华尔街的算法交易员如何因为他们桌子下的电脑产生的极端热量而穿着内裤工作。当你的计算产生的极端热量远在千里之外时,很容易忽略其影响。

云服务提供商对可持续发展的承诺

在过去的几年里,云服务提供商已经发表了宏伟的声明,表示他们打算在 2030 年、2025 年或何时实现 100%无碳排放(例如,见这里这里)。这些承诺非同寻常。我希望并相信——考虑到利害关系,以及这些公司是上市公司的事实——这些项目得到了充分的监督和问责。

然而,在目前的形式下,这些程序并没有给我,也就是客户,提供任何可以从中获益的途径。例如,我不知道我的工作负载是否在已经转换为使用可再生能源的服务器群中运行。

绿色云计算实例请求

我的提议相当简单。今天,当您设置云计算实例或集群时,您需要做出许多选择,包括:机器的类型(及其相关资源)、它将运行的位置、它将运行的网络的属性等等。我想要的是选择由可再生能源供电的实例。这可以是专用实例类型的形式,也可以是现有实例类型的属性。如果无碳计算不能无限期保证,那么我希望得到一个可以保证的估计持续时间。

我认识到提供这种服务可能不是自动的,可能需要一些基础设施和算法上的努力。但这些公司都是巨头,我坚信他们有能力做到。

以下是我认为这项提议的一些好处:

  1. 不需要专家就能意识到,提前在绿色的和肮脏的 T2 之间做出选择的心理与得到一份月底的碳排放报告完全不同。我相信给用户一个选择会增加执行 clean ML 的意识和动机。
  2. 对自己的碳足迹负责的客户将有办法把他们的人工智能培训纳入他们的“走向绿色”倡议中。例如,他们可以根据无碳实例的可用性来规划云计算的时间和持续时间。
  3. 作为一个副作用,这项服务将为世界提供更大的透明度,了解云服务提供商的无碳计划的进展情况,因为据推测,向可再生能源的逐步过渡将反映在可用的绿色实例的数量上。

我认为,在理想的情况下,这种产品的主动权将来自云提供商,没有任何客户干预。但是客户的需求不能伤害…所以如果你在船上,请在你下次方便的时候提交一个绿色实例的功能请求。

#拯救地球

#powertothepeople

#overandout

高级 SQL 窗口函数简易指南

原文:https://towardsdatascience.com/a-guide-to-advanced-sql-window-functions-f63f2642cbf9

小窍门

高级 SQL 窗口函数简易指南

数据科学家和分析师的必备知识

本·米歇尔在 Unsplash 上的照片

本文是关于 SQL 中数据分析的高级窗口函数的指南。这绝对是数据科学家和分析师必须知道的。我将首先介绍什么是窗口函数,为什么你应该使用它们,以及 3 种类型的窗口函数。接下来,我将通过现实生活中的例子来展示如何使用这些函数。

什么是窗口函数?

窗口函数于 2003 年首次引入标准 SQL。根据PostgreSQL 文件:

“一个窗口函数对一组与当前行有某种关联的表行执行计算…在后台,窗口函数能够访问的不仅仅是查询结果的当前行。”

窗口函数类似于 GROUP BY 子句中完成的聚合。但是,行不会组合成一行,每一行都保留它们各自的标识。也就是说,窗口函数可以为每行返回一个值。这是我的意思的一个很好的形象化的例子。

作者图片

请注意图片左侧的 GROUP BY 聚合是如何将三行组合成一行的。图片右侧的窗口函数能够输出每一行的聚合值。这可以使您不必在分组后进行连接。

示例:分组依据与窗口函数

这里有一个简单的例子,让你体会一下窗口函数的作用。

假设我们有一些工资数据,我们想创建一个列,给出每个职位的平均工资。

示例工资数据-按作者分类的图像

分组依据与窗口功能—按作者排序的图像

左边是GROUP BY聚合将返回的内容,右边是窗口函数将返回的内容。如您所见,group by 将我们的数据合并为三行。通过一个窗口函数,我们保留了原来的 11 行,并增加了一个名为 AVG 薪水的新列。如果需要,我们可以选择将每个人的工资与平均工资进行比较。

为什么要使用窗口函数?

窗口函数的一个主要优点是,它允许您同时处理聚合值和非聚合值,因为行不会折叠在一起。

窗口函数也很容易使用和阅读。也就是说,它们可以降低查询的复杂性,这使得以后的维护更加容易。

此外,它们还可以帮助解决性能问题。例如,您可以使用窗口函数,而不必进行自连接或交叉连接。

我保证,窗口函数真的很神奇,知道它真的很棒。

照片由布雷特·乔丹Unsplash 拍摄

重要提示:

在我们开始之前,需要注意的是,根据 SQL 中的操作顺序,窗口函数在列表中排在第六位。

作者图片

这很重要,因为基于这种逻辑顺序,窗口函数在SELECTORDER BY,中是允许的,但在FROMWHEREGROUP BYHAVING子句中是不允许的。

注意:如果您确实需要将它放在 *WHERE* 子句或 *GROUP BY* 子句中,您可以通过使用子查询或 *WITH* 查询来避开这个限制。

窗口函数语法

下面是SELECT子句中窗口函数的一般语法。

作者图片

这里有很多单词,所以让我们来看看一些定义:

  • window_function 是我们要使用的窗口函数的名称;例如,sum、avg 或 row_number(我们将在后面了解更多)
  • 表达式是我们希望窗口函数操作的列的名称。根据所使用的 window_function,这可能不是必需的
  • 上只是表示这是一个窗口功能
  • PARTITION BY 将行划分为多个分区,这样我们可以指定使用哪些行来计算窗口函数
  • partition_list 是我们想要进行分区的列的名称
  • ORDER BY 用于对每个分区中的行进行排序。这是可选的,不必指定
  • order_list 是我们要排序的列的名称
  • 如果我们想进一步限制分区中的行,可以使用行。这是可选的,通常不使用
  • frame_clause 定义从当前行偏移多少

不要担心记住定义和语法,甚至不要担心完全理解它的确切含义。一旦你看了文章中的例子,并对如何编写窗口函数有了直观的理解,一切都会变得更有意义。

快速示例

为了帮助你更好地理解语法的真正工作原理,下面是一个窗口函数在实践中的例子。

这是一个查询,它将生成我们前面看到的关于职位工资的输出。

这里, AVG() 是窗口函数的名称,薪资是表达式, JOB_TITLE 是我们的分区表。我们没有使用 ORDER BY,因为不需要它,我们也不想使用行,因为我们不想进一步限制我们的分区。

作者图片

同样,现在不需要记忆语法。在这个阶段,我希望您理解的一个概念是,窗口函数为“窗口”或“分区”中的每一行计算一个值。窗口可以是多行中的一行,由子句PARTITION BY指定。在我们的例子中,我们按照职位进行了划分。正如你在上面的片段中看到的,我用不同的颜色突出显示了每个职位。每种颜色代表不同的“窗口”或不同的“分区”。窗口函数为每个分区计算一个平均薪金值。

窗口功能列表

现在你知道了语法,让我们看看不同种类的窗口函数,它们可以代替下面的红色字体。

作者图片

有三种主要类型的窗口函数可供使用:聚合函数、排名函数和值函数。在下图中,您可以看到属于每个组的一些函数的名称。

作者图片

下面是对每种窗口函数用途的一个快速概述。

聚合函数:我们可以使用这些函数来计算各种聚合,例如平均值、总行数、最大值或最小值,或者每个窗口或分区内的总和。

排名函数:这些函数对于在其分区内对行进行排名非常有用。

值函数:这些函数允许您比较分区内前一行或后一行的值,或者分区内的第一个或最后一个值。

窗口功能练*

现在让我们开始做一些有趣的练*来帮助你真正掌握窗口函数是如何工作的。我们将对聚合函数、排名函数和价值函数进行各种练*。

数据

对于下面的示例问题,我使用的数据来自位于这个网站上的 Northwind 数据库。参见 Northwind_small.sqlite,该文件也可以在我的 github repo 中找到。

根据下载源,“Northwind 示例数据库随 Microsoft Access 一起提供,作为管理小型企业客户、订单、库存、采购、供应商、运输和员工的教学模式。Northwind 是一个优秀的小型企业 ERP 教程模式,包括客户、订单、库存、采购、供应商、运输、员工和单一条目会计。

数据库的完整模式显示在上面链接的网站上。对于本文中的示例,我将只使用[Order]和[OrderDetail]表。

来源:https://github.com/jpwhite3/northwind-SQLite3

聚合函数

作者图片

练* 1:创建一个新列来计算每个 CustomerId 的平均单价

作者图片

从左边列出的聚合窗口函数列表中,我们可以看到 AVG()将是我们想要使用的窗口函数。我们的表达式将是单价列,因为我们要计算单价的平均值。

接下来,我们需要弄清楚我们想要如何分区。也就是说,为了创建我们的分区,应该如何将这些行组合在一起?练*语句告诉我们找到每个 CustomerId 的平均价格。这告诉我们,我们希望对具有相同 CustomerId 的行进行分组,因此这将是我们的分区列表的一部分。在本练*中,我们不使用 ORDER BY。下面是我们的查询看起来像什么。

SELECT CustomerId, 
       UnitPrice, 
       **AVG(UnitPrice) OVER (PARTITION BY CustomerId) AS “AvgUnitPrice”**
FROM [Order] 
INNER JOIN OrderDetail ON [Order].Id = OrderDetail.OrderId

作者图片

正如您在上面的图像中看到的,计算了 CustomerId 的每个分区的平均单价。

以下是一些你可以自己尝试的练*:

  1. 创建计算每个 CustomerId 的最大折扣的新列
  2. 创建一个新列来计算每个产品 Id 的最小单价

练* 2:创建一个新列来计算每组 CustomerId 和 EmployeeId 的平均单价。

您可以选择按多列进行分区。之前,我们计算了每个 CustomerId 组的平均单价。这一次,我们将添加 EmployeeId。

SELECT CustomerId, 
       EmployeeId, 
       AVG(UnitPrice) OVER (**PARTITION BY CustomerId, EmployeeId**) AS       “AvgUnitPrice”
FROM [Order] 
INNER JOIN OrderDetail ON [Order].Id = OrderDetail.OrderId 

作者图片

请注意分区与之前相比有什么变化。这些计算是针对 CustomerId 和 EmployeeId 的每个唯一组进行的,在表中以不同的颜色直观地显示出来。

排名功能

作者图片

练* 3:创建一个新列,按降序排列每个 CustomerId 的单价。

我们可以用三种不同的方法来完成这个练*。

我们将使用左边列表中的前三个排名函数:ROW_NUMBER、RANK 和 DENSE_RANK。

每一个都有稍微不同的数据排序方式。

行数

我们可以使用这个函数来显示给定行在其分区中的行号。请注意,对于排名函数,我们不需要像之前对聚合函数那样在括号内指定表达式。

另外,既然是做排名,这里顺序很重要。我们必须确保单价列排序正确,以便正确应用排名。为此,我们可以在**PARTITION BY.**之后添加**ORDER BY UnitPrice DESC**作为窗口函数的一部分

SELECT CustomerId, 
       OrderDate, 
       UnitPrice, 
       **ROW_NUMBER()** OVER (PARTITION BY CustomerId **ORDER BY UnitPrice DESC**) AS “UnitRank”
FROM [Order] 
INNER JOIN OrderDetail 
ON [Order].Id = OrderDetail.OrderId

来源:作者

正如您在上面的输出中看到的,我们的 UnitPrice 列是降序排列的,在最后一列中显示了每个客户 id 的单位排名。客户 ALFK 有 12 行,因此等级从 1 到 12。

您可能想知道,如果我在 SQL 语句的末尾而不是在 windows 函数中使用 ORDER BY,会发生什么情况,我会得到相同的结果吗?

花一分钟考虑一下,然后回来。我的顺序是在窗口函数内部还是外部有关系吗?

马库斯·温克勒在 Unsplash 上拍摄的照片

我们来试试吧!将 ORDER BY 从 windows 函数中移除,并将其添加到末尾。

SELECT CustomerId, 
       OrderDate, 
       UnitPrice,    
       ROW_NUMBER() OVER (PARTITION BY CustomerId) AS “UnitRank”
FROM [Order]
INNER JOIN OrderDetail ON [Order].Id = OrderDetail.OrderId
**ORDER BY CustomerId, UnitPrice DESC** 

嗯,看起来我们没有得到和之前一样的结果。单价按降序排列正确,但单位的排名看起来不对。为什么不呢?

回想一下 SQL 的操作顺序。窗口函数第六次处理,而 ORDER BY 第十次处理。

作者图片

所以行号是在单价被订购之前创建的。这就是为什么我们得不到相同的结果!有道理。

排名()

现在,让我们用 RANK()代替 ROW_NUMBER()。

SELECT CustomerId, 
       OrderDate, 
       UnitPrice, 
       **RANK()** OVER (PARTITION BY CustomerId ORDER BY UnitPrice DESC) AS “UnitRank”
FROM [Order] 
INNER JOIN OrderDetail ON [Order].Id = OrderDetail.OrderId

作者图片

现在有什么不同?对于行号,没有重复的数字。但是使用 rank(),如果有多个值具有完全相同的值,RANK 函数会给它们相同的排名。

请注意,在第 2 行和第 3 行中,单价是 45.60,因此这两行的等级都是 2。第 7 行和第 8 行也具有相同的单价,并被赋予相同的等级 7。

还要注意排名跳过了一个数字。例如,在行 3 中,等级跳到 4,因为有两行等级为 2。如果有三行的秩为 2,那么它将跳到秩 5,依此类推。'

如果你不想让它跳过数字呢?嗯,我们可以用 DENSE_RANK() 来代替。

密集等级()

同样,替换我们的窗口函数为 DENSE_RANK() ,其他保持不变。

SELECT CustomerId, 
       OrderDate, 
       UnitPrice, 
       **DENSE_RANK()** OVER (PARTITION BY CustomerId ORDER BY UnitPrice DESC) AS “UnitRank”
FROM [Order] 
INNER JOIN OrderDetail ON [Order].Id = OrderDetail.OrderId

作者图片

它遵循与 RANK()相同的行为,因为如果值相同,那么这些行将被赋予相同的等级。参见第 2 行和第 3 行。请注意,在第 4 行中,等级现在没有跳过一个数字。它是排名 3 而不是 4。

你现在的作业是学* PERCENT_RANK()和 NTILE()是如何工作的,并亲自尝试这些函数。

价值函数

对我来说,值函数可能是窗口函数如此神奇的首要原因。这些函数对于从其他行中提取可能对报表有用的值非常有用。

作者图片

我们可以使用 LAG 或 LEAD 函数来帮助我们创建一个能够从其他行提取值的列。LAG 可以从前面的行返回值,而 LEAD 可以从后面的行返回值。在处理时序数据和计算时间差异时,比较前一行或后一行非常有用。

练* 4:创建一个新列,为每个产品 Id 提供前一个订单日期的数量。

SELECT ProductId, 
       OrderDate, 
       Quantity, 
       **LAG(Quantity) OVER (PARTITION BY ProductId ORDER BY OrderDate) AS "LAG"**
FROM [Order] 
INNER JOIN OrderDetail ON [Order].Id = OrderDetail.OrderId 

我们在 Quantity 列上使用 LAG 来返回前一行的值。就像以前一样,我们需要确保我们的数据在我们的窗口函数中是有序的。我们将按订单日期排序。

作者图片

正如您在上面的图像中看到的,我们得到了一个包含前一个订单日期数量的列。这非常有用,因为我们可以将当前订单日期与之前的订单日期进行比较,并计算两个时间段的差异。在第一行中,没有以前的订单日期,所以它是 NaN 或 null。

练* 5:创建一个新列,为每个产品 Id 提供以下订单日期的数量。

这看起来和我们之前的例子非常相似。但是,这一次,由于我们想要下面的行,我们将使用 LEAD()。

SELECT ProductId, 
       OrderDate, 
       Quantity, 
       **LEAD**(Quantity) OVER (PARTITION BY ProductId ORDER BY OrderDate) AS "LEAD"
FROM [Order] 
INNER JOIN OrderDetail ON [Order].Id = OrderDetail.OrderId

作者图片

如您所见,新的列前导包含下一行的值。

练* 6:创建一个新列,提供每个 ProductId 的首次订购数量。

为了获得第一个数量,我们可以使用 FIRST_VALUE 函数,它将给出一个分区中的第一个值。

SELECT ProductId, 
       OrderDate, 
       Quantity, 
       **FIRST_VALUE**(Quantity) OVER (PARTITION BY ProductId ORDER BY OrderDate) AS "FirstValue"
FROM [Order] 
INNER JOIN OrderDetail ON [Order].Id = OrderDetail.OrderId

作者图片

如图所示,产品 ID 1 的第一个订单是在 2012 年 8 月 20 日,数量为 45,因此我们得到与产品 1 相关的所有行的值为 45。

这里有一个练*让你自己试试。

  1. 创建一个新列,为每个 ProductId 提供第二个订购数量。(提示:使用第 n 个值函数)

使用 frame _ 子句

让我们从练*中稍作休息,学*一个还没有讨论过的新概念。

路博尖塔Unsplash 上拍照

您可能还记得开头的定义,我们可以用 frame_clause 指定行来进一步限制我们的窗口大小。我把这个留到最后,因为人们可能会对此有点困惑。我将快速浏览一下语法及其用法,然后让我们看一个例子来真正理解发生了什么。

下面是通用语法的样子

行之间 <起始 _ 行> <结束 _ 行>

在<starting_row>和中,我们有以下选项可供选择:</starting_row>

  • UNBOUNDED PRECEDING 分区中当前行之前的所有行,即分区的第一行
  • [some #] PRECEDING —当前行之前的行数
  • 当前行—当前行
  • [some #] FOLLOWING —当前行之后的行数
  • UNBOUNDED FOLLOWING 分区中当前行之后的所有行,即分区的最后一行

这里有一些如何写的例子:

  • EN 3 前一行和当前行之间的行-这意味着回顾前 3 行直到当前行。
  • 无界的前一行和后一行之间的行—这意味着从分区的第一行开始查找,直到当前行之后的第一行
  • EN 5 之前的行和 1 之前的行之间-这意味着回顾前 5 行,直到当前行之前的 1 行
  • 无界在前和无界在后之间的行—这意味着从分区的第一行到分区的最后一行

值得注意的是,每当添加 ORDER BY 子句时,SQL 都会将默认窗口设置为未绑定的前一行和当前行之间的行。

练* 7:计算每个 CustomerId 的累积移动平均单价。

为了计算累积移动平均值,我们将利用 frame_clause。

SELECT CustomerId, 
       UnitPrice, 
       AVG(UnitPrice) OVER (PARTITION BY CustomerId 
       ORDER BY CustomerId 
       **ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW**) AS “CumAvg”
FROM [Order]
INNER JOIN OrderDetail ON [Order].Id = OrderDetail.OrderId

作者图片

在上面的输出中,您可以看到每行都重新计算了平均值。在第 1 行中,只有 1 个数字,因此平均值为 45.60。在第 2 行,累计平均值是 45.60 和 18 的平均值。在第 3 行中,累计值是 45.60、18 和 12 的平均值。诸如此类…

以下是一些你可以自己尝试的练*:

  1. 计算每个 CustomerId 的前 5 行到前 3 行的平均数量。
  2. 计算每个 CustomerId 的前 2 行到当前行的最小单价

陈京达Unsplash 上的照片

如果你准备好参加挑战,试试这个奖励游戏。

挑战练*:创建一个新列,提供每个产品 Id 的最后订购数量。(提示:使用 LAST_VALUE()窗口函数并考虑分区)

完成这篇文章非常棒!窗口功能非常强大,非常有趣,所以我希望你喜欢这些练*。感谢您的阅读!如果您有反馈或问题,请在下面留言。

如果你正在寻找一份备忘单,点击这里的链接。

安德鲁·舒尔茨在 Unsplash 上拍摄的照片

构建图像重复查找器系统:指南

原文:https://towardsdatascience.com/a-guide-to-building-an-image-duplicate-finder-system-4a46021410f1

你的零英雄指南检测重复和*似重复的图像

图片由来自 Pixabay 的 Manu9899 提供

您想识别重复或*似重复的图像吗?或者计算数据集中每个图像的副本数?

如果是的话,那么这篇文章就是给你的。

本文的目标有五个方面:

  1. 理解图像重复查找器和基于内容的图像检索系统之间的区别
  2. 演练比较相似图像的 5 种不同方法的概念
  3. 学* python 中的每个方法实现
  4. 确定图像变换对所述算法的整体性能的敏感度
  5. 从速度和准确性(包括实验)方面为选择适合您应用需求的最佳方法铺平道路

基本架构

首先,我需要定义一个重要的术语。 查询图像 是用户输入以获取信息的图像。在相似性块的帮助下,系统在数据集中搜索相似的图像,该数据集计算图像彼此的接*程度。图 1 说明了这些步骤。

图像 1 —图像重复查找器系统的基本结构(作者提供的图像)

在第 3 节中,我们将研究这个相似性模块,并探索实现该功能的最常见方法。

图像重复查找器与基于内容的图像检索系统

这两种系统的主要区别在于,图像重复/*似重复探测器仅检测相同和*似相同的图像(图 2)。另一方面,基于内容的图像检索(CBIR) 系统搜索相似的感兴趣区域,并显示与这些区域最相似的图像(图像 3)。

图片 2 —图片重复查找系统的详细示例(图片由作者提供)

图 3 —基于内容的图像检索系统的详细示例(图片由作者提供)

请注意基于内容的图像检索系统是如何识别苹果并输出不同场景的图像的。

比较相似图像的五种常用方法

本文将考察五种主要方法:

  1. 欧几里得距离
  2. 结构相似性指数度量(SSIM)
  3. 图像哈希
  4. 余弦相似性
  5. 特征的相似性(使用 CNN)

1.欧几里得距离

转到第一种方法,如图 4 所示,欧几里德距离是平面上两个数据点之间的直线距离[ 8 ]。它也被称为 L2 范数距离度量。

图片 4 —欧几里得距离(图片由作者提供)

我们可以用向量来表示图像。矢量是一个有一个起点和一个终点的量[ 4 ]。这两点构成了矢量的两个特征:大小和方向。

在向量空间中,假设我们有两张图片来比较 x =[x1,x2,x3]和 y = [y1,y2,y3]。图 5 显示了通式[ 8 ],而图 6 显示了一个使用示例。

图 5 —欧几里德距离的一般公式(图片由作者提供)

图 6-应用欧几里得距离公式的示例(图片由作者提供)

方法公式很简单。由于它类似于勾股定理公式,所以又称为勾股距离[ 8 ]。

在 python 中,实现非常简单:

  • 实现 1: 使用 Scipy 库

  • 实现 2: 使用 NumPy 的 linalg.norm ( 引用)

2.S 结构相似指数测度 ( SSIM

论文 图像质量评估:从错误可见性到结构相似性1】介绍了 SSIM 在 2004 年。它计算两个给定图像之间的相似度,得出一个介于 0 和 1 之间的值。

除了寻找副本,它的许多应用之一是测量压缩图像如何影响其质量。此外,它还估计数据传输损失如何严重降低质量[ 2 ]。

根据作者的说法,影响该指数的三个主要因素是亮度、对比度和结构。因此,如果这些因素中的一个发生变化,指数也会发生变化。

至于实现,它是如下:

3.图像哈希

另一种计算两幅图像之间相似性的方法是图像哈希(也称为数字指纹)。这是为每个图像分配唯一哈希值的过程。但是,该方法会导致相同的值。平均散列是许多散列类型中的一种。它的工作方式如下[ 6 ]。此外,请参考图 7 进行澄清。

  1. 缩小图像尺寸(例如:8x8)
  2. 将其转换为灰度
  3. 取其平均值
  4. 将每个像素与平均值进行比较。如果像素高于平均值,则为其赋值 1,否则为 0
  5. 构造散列:将 64 位设置为 64 位整数

图 7 —平均哈希步骤(作者图片)

由此产生的 64 位整数 可能是 的样子:

1011111101100001110001110000111101101111100001110000001100001001

可能可能*。我们可以用不同的方式来表现这个形象。从左上角开始列出 0 和 1 位(如上例所示),从右上角开始,依此类推[ 6 ]。*

最重要的是,如果我们改变长宽比,增加或减少亮度或对比度,甚至改变图像的颜色,其哈希值将是相同的[ 7 ],这使其成为比较同一性的最佳方式之一。

比较两幅图像的过程如下[ 7 ]:

  1. 构建每个图像的散列(按照上面的 5 个步骤)
  2. 计算汉明距离。(通过计算与每个散列不同的比特位置的数量)零距离表示相同的图像。(下面的代码块对此有更好的解释)

4.余弦相似性

余弦相似度是一种计算两个向量(可以是图像)相似度的方法,通过取点积并除以每个向量的幅度[ 9 ],如下图 8 所示。

图片 8 —余弦相似性方程(图片由作者提供)

随着两个向量之间的角度变小,相似性变强[ 9 ]。如图 9 所示,与 A 和 B 相比,矢量 C 和 B 具有很高的相似性,因为它们的角度非常小。

图片 9-余弦相似性插图(图片由作者提供)

下面是使用 torch 计算两幅 PIL 图像的度量的代码。

5.特征的相似性(使用 CNN)

最后一种比较图像的方法是计算特征的相似性。众所周知,卷积神经网络 CNN 可以挑选图像的模式并理解它。卷积层有检测模式的过滤器。图像中的不同图案可以是边缘、形状或纹理。这些图案被称为特征

我们可以从 CNN 的卷积层中提取这些特征。图 10 清楚地展示了一个示例架构。通常,我们指定网络的最后一个卷积层用于特征提取。

图 10——一个简单的 CNN 架构(图片由作者提供)

一个伟大的艺术级 CNN 架构是 EfficientNet 。这是一种使用复合系数统一缩放所有维度(深度/宽度/分辨率)的缩放方法。我不会深究它,因为它超出了本文的范围。但是,我将在下面的部分中使用它。

通常,数据科学社区在基于内容的图像检索(CBIR) 系统中广泛使用特征的相似性。实验部分将解释原因。

5.1。EfficientNet-b0 和欧几里德距离

从 EfficientNet 中提取特征后,我应用欧几里德距离来度量查询和数据集图像的特征之间的相似性,以找到最相似的特征。

5.2。EfficientNet-b0 和余弦相似度

计算特征的余弦相似性与上一个非常相似。但是,应用余弦相似度而不是欧几里德距离。

在结束这一部分之前,如果得到的相似度是 250、0.04 或 10809 呢?让一对图像相似的数字是多少?答案如下:您必须基于对您选择的数据集的研究或特殊测试来定义这个阈值。

数据集

在整个实验中,我使用了两个不同的数据集进行评估:

我指定第一个数据集来评估重复/*似重复图像查找器,因为它由每个类别 360 度拍摄的不同水果的图像组成,如图 3 所示。这些框架略有不同;图 4 显示了棕色划痕是如何顺时针移动的。

图 11 —一个水果 360 数据集的样本(图片由作者提供)

图 12——三帧结果 360 数据集之间的差异(图片由作者提供)

在这个数据集上的测试将为我们提供关于图像重复/*似重复查找器性能如何的很好的反馈,因为所有图像都是相邻的帧。这意味着每个独特类别的图片都非常相似。

其次, SFBench 是一个由 40 幅图像组成的数据集,用于评估基于内容的图像检索(CBIR) 系统。

请注意,本文的目的不是构建或评估 CBIR 系统。我们使用这个数据集只是为了测试图像变换(如 3D 投影和旋转)如何影响这些方法的性能。

下面的图 13 展示了数据集的一些样本图像。像第一个数据集一样,它由每个场景的 4 幅图像组成。

图 SFBench 数据集的示例(图片由作者提供)

实验

我按照以下方式使用两个数据集测试了每种方法:

  • 实验 1:速度和准确性
  • 实验 2:对图像变换的弹性
  • 实验 3:科学距离.欧几里得速度与数字线性速度.范数速度

注:所有测试我用的都是 2019 款 MacBook Pro CPU。此外,您可以在 Github 存储库中找到所有的测试。

实验 1:速度和准确性

该测试提出了图像重复查找器系统在速度和准确性方面的最佳方法。接下来的步骤如下:

  • 读取水果 360 数据集的图像。
  • 将它们转换为 RGB
  • 将图像调整到固定大小
  • 使用五种方法
  • 获取与参考图片最相似的 3 个图片。
  • 计算该方法比较一对图像所需的平均时间(秒)
  • 计算准确度(对于每个参考图像,如果检测到 3 个副本/*似副本,则准确度为 100%)

结果(如表 1 所示)清楚地表明,余弦相似性占主导地位,而使用 CNN 计算特征的相似性被认为是寻找图像的副本/*似副本的过度工程化,因为它的运行时间比余弦相似性慢大约 250 倍,同时保持接*的精度。此外,如果速度是一个重要因素,图像哈希是一个很好的选择。

访问这篇文章【5】了解更多关于欧几里德距离何时优于余弦相似度,反之亦然..

表 1 —实验 1 的结果

实验 2:对图像变换的弹性

该测试遵循与实验 1 相同的步骤。唯一的区别是使用的数据集和调整大小的因素;我使用了 SFBench ,注意到图像重复查找器的目的不是检测和识别相似的转换图像。我只是在评估这些方法在 CBIR 系统中潜在用途的弹性。

从逻辑上讲,由于 CNN 在其图层中保存空间信息,因此特征相似度方法表现最佳。表 2 总结了如下结果:

表 2 —实验 2 的结果

实验 3: Scipy distance.euclidean 与 Numpy linalg.norm 速度(Extra)

最后一个实验通过重复大约 2300 次相同的操作来考察 Scipy 和 Numpy 实现之间的比较。这个测试是本文的一个额外步骤,不影响图像重复/*似重复查找器系统的功能。

结果显示,它们的性能相似(表 3)。

表 3-实验 3 的结果

结论

总之,我们浏览了欧几里德距离、SSIM、图像散列、余弦相似性和特征相似性的概念和 Python 代码。此外,我们还确定了图像变换对这些算法性能的敏感性。最后,通过实验,我们根据要求总结出了最佳的方法:速度和精度。

文章的材料

您可以在 Github 资源库中找到所有的数据集、实验和结果。此外,只要每个数据集遵循相同的命名约定,您就可以测试您选择的数据集。

资源

[1],,王,等.图像质量评价:从错误可见性到结构相似性,2004

[2]SSIM Imatest 有限责任公司:结构相似性指数,v.22.1

[3] 达塔,关于结构相似指数的一切(SSIM):py torch 中的理论+代码,2020 年

【4】数学书库, 三角学的进一步应用 : 向量。2021 年

[5] Nagella,余弦相似度 Vs 欧几里德距离,2019

[6] 内容区块链项目,测试不同图像哈希函数,2019

[7] Krawetz,黑客因素博客,看起来像它,2011 年

[8] Gohrani,机器学*中使用的不同类型的距离度量,2019

[9] 克雷,如何计算余弦相似度(附代码),2020

其他有用的资源

使用贝叶斯超参数调整但不过度拟合找到最佳推进模型的指南

原文:https://towardsdatascience.com/a-guide-to-find-the-best-boosting-model-using-bayesian-hyperparameter-tuning-but-without-c98b6a1ecac8

使用增强的决策树算法,如 XGBoost、CatBoost 和 LightBoost,您可能会胜过其他模型,但过度拟合是一个真正的危险。了解如何使用 HGBoost 库拆分数据、优化超参数,以及在不过度训练的情况下找到性能最佳的模型。

图片来自作者。

*年来,梯度推进技术在分类和回归任务中得到了广泛的应用。一个重要的部分是调整超参数,以获得最佳的预测性能。这需要搜索数千个参数组合,这不仅是一项计算密集型任务,而且会很快导致模型过度训练。因此,模型可能无法对新的(看不见的)数据进行归纳,并且性能准确性可能比预期的差。幸运的是,有贝叶斯优化技术可以帮助优化网格搜索并减少计算负担。但是还有更多的原因,因为优化的网格搜索仍然可能导致模型训练过度。在优化超参数时,仔细地将数据分成一个训练集、一个测试集和一个独立的验证集是应该包含的另一个重要部分。HGBoost 库进场了! HGBoost 代表超优化梯度提升,是一个针对 XGBoost、LightBoost、CatBoost 进行超参数优化的 Python 包。它会小心地将数据集分成训练集、测试集和独立的验证集。在训练测试集中,有一个使用贝叶斯优化(使用 hyperopt)优化超参数的内环,以及一个基于 k-fold 交叉验证对性能最佳的模型的概括程度进行评分的外环。因此,它将尽最大努力选择具有最佳性能的最稳健的模型。 在这篇博客中,我将首先简单讨论 boosting 算法和超参数优化。然后,我将讨论如何使用优化的超参数来训练模型。我将演示如何解读和直观地解释优化的超参数空间,以及如何评估模型性能。

升压算法的简单介绍。

极端梯度增强( XGboost )、轻度梯度增强( Lightboost )和 CatBoost 等梯度增强算法是强大的集成机器学*算法,用于预测建模,可应用于表格和连续数据,以及用于分类和回归任务。

增强决策树算法非常受欢迎并不奇怪,因为在 Kaggle [ 1 ]举办的机器学*挑战中,这些算法参与了超过一半的获胜解决方案。梯度推进和决策树的结合在许多应用中提供了最先进的结果。也正是这种组合使得 XGboost、CatBoost 和 Lightboost 之间产生了差异。共同的主题是,每个 boosting 算法都需要为每片叶子找到最佳分裂,并且需要最小化计算成本。高计算成本是因为模型需要为每片叶子找到精确的分裂点,这需要对所有数据进行迭代扫描。这个过程很难优化。

粗略地说,有两种不同的策略来计算树: 逐层和逐叶。逐级策略 逐级增长树。在这种策略中,每个节点分割数据,并对靠*树根的节点进行优先级排序。 逐叶策略 通过在具有最高损失变化的节点处分割数据来增长树。级别式增长通常对较小的数据集更好,而叶式增长在较小的数据集中往往会过拟合。然而,叶方向的增长倾向于在更大的数据集中表现出色,在那里它比水平方向的增长 2 要快得多。让我们从树分裂和计算成本的角度总结一下 XGboost、LightBoost 和 CatBoost 算法。

XGBoost。

极端梯度提升 (XGboost) 是最流行的梯度提升技术类型之一,其中提升的决策树在内部由弱决策树的集合组成。最初 XGBoost 基于逐层增长算法,但最*增加了一个逐叶增长选项,使用直方图实现分裂*似。优点XGBoost 学*有效,泛化性能强。此外,它还可以捕捉非线性关系。缺点是超参数调整可能会很复杂,当使用稀疏数据集时,它可能会很快导致高计算成本(内存方面和时间方面),因为当使用非常大的数据集时需要许多树[ 2 ]。

灯光增强。

LightBoostLightGBM 是一个梯度提升框架,它使用基于树的学*算法,对树进行垂直分割(或逐叶分割)。当生长相同的叶子时,与逐层算法相比,这种方法可以更有效地减少损失。此外, LightBoost 使用基于直方图的算法,将连续的特征值存储到离散的容器中。这加快了训练速度,减少了内存使用。它被设计成高效的,并具有许多优点,例如快速训练、高效率、低内存使用、更好的准确性、支持并行和 GPU 学*、能够处理大规模数据 3 。计算和内存高效的优势使 LightBoost 适用于大量数据。缺点是由于逐叶分裂,对过拟合敏感,并且由于超参数调整,复杂度高。

CatBoost。

CatBoost 也是决策树上梯度提升的高性能方法。 Catboost 使用 不经意 树或对称树生成平衡树,以加快执行速度。这意味着通过创建特征分割对,将 pér 特征、值划分到桶中,例如(temperature,< 0)、(temperature,1–30)、(temperature,> 30),等等。在不经意树的每一层中,带来最低损失(根据惩罚函数)的特征分裂对被选择并用于所有层节点。据描述, CatBoost 使用默认参数提供了很好的结果,因此在参数调整上需要较少的时间[ [4](http://CatBoost is a high-performance open source library for gradient boosting on decision trees) ]。另一个优势CatBoost 允许你使用非数字因子,而不是必须预处理你的数据或者花费时间和精力把它变成数字。的一些缺点是:它需要构建深度决策树来恢复数据中的依赖性,以防具有高基数的特性,并且它不能处理缺失值。

每个提升决策树都有自己的优势(劣势),因此,结合您要开发的应用程序,很好地理解您正在使用的数据集是非常重要的。

超参数还是参数?

简单介绍一下 超参数 的定义,以及它们与 正常参数 的区别。总的来说,我们可以说正常参数是由机器学*模型本身优化的,而超参数 不是 。在回归模型的情况下,输入特征和结果(或目标值)之间的关系是已知的。在回归模型的训练过程中,通过调整权重来优化回归线的斜率(假设与目标值的关系是线性的)。或者换句话说,在训练阶段学*模型参数

参数由机器学*模型本身来优化,而超参数在训练过程之外,需要一个元过程来调整。

然后还有另外一组参数叫做超参数 (在统计学中又称为滋扰参数)。这些值必须在训练过程之外指定,并且是模型的输入参数。一些模型没有任何超参数,其他模型有一些(两个或三个左右)仍然可以手动评估。然后是有很多超参数的模型。我是说,真的很多。增强的决策树算法,如 XGBoost、CatBoost 和 LightBoost 都是有很多超参数的例子,考虑期望的深度、树中的叶子数量等。您可以使用默认的超参数来训练模型,但是调整超参数通常会对训练模型的最终预测精度产生很大影响[ 8 ]。此外,不同的数据集需要不同的超参数。这很麻烦,因为一个模型可以很容易地包含几十个超参数,这随后会产生(几万)个超参数组合,需要对这些组合进行评估以确定模型性能。这被称为搜索空间。因此,计算负担(时间方面和内存方面)可能是巨大的。因此,优化搜索空间是有益的。

仔细分割数据集的情况。

对于受监督的机器学*任务,重要的是将数据分成单独的部分,以避免在学*模型时过拟合。过度拟合是指模型对数据拟合(或学*)得太好,然后无法预测(新的)看不见的数据。最常见的方式是将数据集分成训练集和独立的验证集。然而,当我们也执行超参数调优时,比如在 boosting 算法中,也需要一个测试集。模型现在可以 看到 的数据, 从数据中学* ,最后,我们可以 在看不见的数据上评估 的模型。这种方法不仅可以防止过度拟合,而且有助于确定模型的鲁棒性,即使用 k 折叠交叉验证方法。因此,当我们需要调整超参数时,我们应该将数据分成三部分,即:训练集、测试集和验证集。**

  1. 训练集:这是模型看到并从数据中学*的部分。它通常由 70%或 80%的样本组成,以确定数千个可能的超参数中的最佳拟合(例如,使用k-折叠交叉验证方案)。
  2. 测试集:该集通常包含 20%的样本,可用于评估模型性能,如特定的超参数集。
  3. 验证集:该集通常也包含数据中 20%的样本,但在最终模型被训练之前保持不变。现在可以以公正的方式评估该模型。请务必意识到,本器械包只能使用一次。或者换句话说,如果模型在获得对验证集的见解后得到进一步优化,那么您需要另一个独立的集来确定最终的模型性能。

建立嵌套的交叉验证方法可能很耗时,甚至是一项复杂的任务,但如果您想要创建一个健壮的模型,并防止过度拟合,这是非常重要的。 自己做出这样的做法是很好的练*,不过这些也在HGBoost 库1中实现。* 在我讨论 HGBoost 的工作原理之前,我将首先简要描述一下针对具有不同超参数组合的模型的大规模优化的贝叶斯方法。***

超参数优化是一项复杂的任务。

当使用提升决策树算法时,可能很容易有几十个输入超参数,这可能随后导致需要评估(几万)个超参数组合。这是一项重要的任务,因为超参数的特定组合可以为特定数据集带来更准确的预测。尽管有许多超参数需要优化,但有些参数比其他参数更重要。此外,一些超参数可能对结果影响很小或没有影响,但是如果没有智能方法,需要评估超参数的所有组合,以找到最佳执行模型。这使得它成为计算负担。

超参数优化可以大大提高机器学*模型的准确性。

通常使用网格搜索来搜索参数组合。一般有两种类型: 网格搜索随机搜索 可用于参数整定。 网格搜索 将遍历整个搜索空间,因此非常有效,但也非常非常慢。另一方面, 随机搜索 很快,因为它会在搜索空间中随机迭代,虽然这种方法已经被证明是有效的,但它很容易错过搜索空间中最重要的点。 幸运的是,还有第三种选择:基于序列模型的优化,也称为贝叶斯优化**

贝叶斯优化 进行参数整定是在几次迭代内确定最佳的超参数集。贝叶斯优化技术是一种高效的函数最小化方法,在hyperpt库中实现。该效率使其适合于优化训练缓慢的机器学*算法的超参数[ 5 ]。关于 Hyperopt 的深入细节可以在这个博客中找到[ 6 ]。总的来说,它首先对参数的随机组合进行采样,然后使用交叉验证方案来计算性能。在每次迭代期间,样本分布被更新,并且以这种方式,它变得更有可能对具有良好性能的参数组合进行采样。传统的网格搜索随机搜索、远视之间的巨大对比可以在这个博客中找到。**

贝叶斯优化 效率使其适合于优化训练缓慢的机器学*算法的超参数 5

远视 被并入 HGBoost 的方法来做超参数优化。在下一节中,我将描述如何处理分割数据集和超参数优化的不同部分,包括目标函数、搜索空间和所有试验的评估

超优化梯度增强库。

超优化的梯度增强库( HGBoost ),是一个针对 XGBoostLightBoostCatBoost 进行超参数优化的 Python 包。它将把数据集分成系列测试、独立验证集。在训练测试集中,有使用贝叶斯优化(使用 超点) 来优化超参数的内部循环,以及使用 k 折叠交叉验证来对表现最佳的模型能够概括得多好进行评分的外部循环。这种方法的优点在于,不仅可以选择精度最高的模型,还可以选择最能概括的模型。**

HGBoost 有很多优点,比如;它尽最大努力检测最能概括的模型,从而降低选择过度训练模型的可能性。它通过提供深刻的情节提供可解释的结果,例如:对超参数空间的深入检查、所有评估模型的性能、k 倍交叉验证的准确性、验证集以及最后但同样重要的最佳决策树可以与最重要的特征一起绘制。

hgboost 提供的好处更多:

  1. 它由最流行的决策树算法组成; XGBoost、LightBoost 和 Catboost
  2. 它包括最流行的贝叶斯优化的超参数优化库;远视**
  3. 一种将数据集分为训练测试和独立验证以对模型性能评分的自动化方式。
  4. 流水线有一个嵌套方案,内循环用于超参数优化,外循环使用 k -fold 交叉验证来确定最健壮和性能最好的模型。
  5. 它可以处理分类回归任务。**
  6. 创建一个多类模型或增强决策树模型的集合是很容易的。****
  7. 它处理不平衡的数据集。
  8. 它为超参数搜索空间创建可解释的结果,并通过创建有洞察力的图来模拟性能结果。
  9. 它是开源的。
  10. 有很多例子证明了这点。

走向超优化和稳健模型的步骤。

HGBoost 方法由流水线中的各个步骤组成。图 1 中描绘了示意图。让我们来看看这些步骤。**

图一。HGBoost 的示意图。(图片来自作者)

第一步 是将数据集拆分成训练集,一个测试集,和一个独立验证集请记住,验证集在整个训练过程中保持不变,并在最后用于评估模型性能。训练测试集* ( 图 1A )中,有使用贝叶斯优化来优化超参数的内循环,以及使用外部k-折叠交叉验证来测试最佳性能模型在未发现的测试集上的概括程度的外循环。搜索空间取决于模型类型的可用超参数。需要优化的模型超参数见 代码段 1 。***

代码部分 1。为 XGBoost、LightBoost 和 CatBoost 的分类和回归任务搜索空间。

在贝叶斯优化部分,我们通过探索整个空间来最小化超参数空间上的成本函数。与传统的网格搜索不同,贝叶斯方法搜索最佳参数集,并在每次迭代后优化搜索空间。使用 k 折叠交叉验证方法对模型进行内部评估。或者换句话说,我们可以评估固定数量的函数评估,并采取最佳尝试。默认设置为 250 次评估,因此产生 250 个模型。完成此步骤后,您现在可以选择表现最佳的模型(基于 AUC 或任何其他指标),然后停止。然而,这是非常不鼓励的,因为我们只是在整个搜索空间中搜索了一组现在似乎最适合训练数据的参数,而不知道它概括得有多好。或者换句话说,表现最好的模型可能会,也可能不会过度训练。为了避免找到过度训练的模型,k-fold 交叉验证方案将继续我们寻找最稳健模型的探索。我们进行第二步。****

第二步 是根据指定的评估指标(默认设置为 AUC)对模型(本例中为 250 个)进行排名,然后取表现最好的前 p 模型(默认设置为p= 10)。以这种方式,我们不依赖于具有最佳超参数的单个模型,即可能或可能不会过度拟合。请注意,可以评估所有 250 个模型,但这是计算密集型的,因此我们选择最好的(性能)模型。为了确定前 10 名执行模型如何概括,使用了k-折叠交叉验证方案进行评估。 k 的缺省值为 5 折,这意味着对于每一款【p】车型,我们都将检查跨越 k =5 折的性能。为了确保前p= 10模型之间的交叉验证结果具有可比性,抽样以分层方式进行。总的来说,我们会评价pxk;10x5=50 个模型。**

第三步, 我们有了表现最好的 p 模型,我们用一种k-折叠交叉验证的方法计算了它们的性能。我们现在可以计算跨越-褶皱的平均准确度(例如 AUC),然后对模型进行排序,最后选择排序最高的模型交叉验证方法将有助于选择最稳健的模型,即也能在不同数据集之间推广的模型。*请注意,可能存在其他模型,这些模型在没有交叉验证方法的情况下会产生更好的性能,但这些模型可能会被过度训练。*****

第四步, 我们将在独立验证集上检验模型的准确性。这一套到目前为止还没有接触过,因此将给出一个很好的估计。由于我们广泛的模型选择方法(模型是否可以一般化,并限制选择过度训练模型的可能性),我们不应该期望与我们容易看到的结果相比,在性能上有很大的差异。

在最后一步 中,我们可以使用整个数据集上的优化参数重新训练模型( 图 1C )。下一步( 图 1D )就是对结果的解读,为其创造有见地的情节。

导入并初始化。

在我们可以使用 HGBoost 之前,我们需要先使用命令行安装:**

******pip install hgboost******

安装完成后,我们可以导入 HGBoost 并初始化。我们可以保持输入参数的默认值(在 代码段 2 中描述)或相应地改变它们。在接下来的部分中,我们将讨论一组参数。

代码部分 2。导入并初始化 HGBoost。

每个任务(分类、回归、多类或集成)和每个模型( XGBoost、LightBoost 和 CatBoost )的初始化保持一致,这使得在不同决策树模型甚至任务之间切换变得非常容易。输出也保持一致是一个包含六个键的字典,如 代码段 3 所示。****

代码部分 3:输出参数的描述。

读取和预处理数据集。

为了演示,让我们使用可以使用函数import_example(data=’titanic’)导入的 泰坦尼克号数据集【10】。这个数据集是免费使用的,是 Kaggle 竞赛 机器从灾难中学* 的一部分。可以使用依赖于 df2onehot 库【11】的函数.preprocessing()来执行预处理步骤。该函数将分类值编码成一个 hot,并保持连续值不变。我手动去掉了一些特性,比如 PassengerId名字 (代码段 4) 。请注意,在正常的用例中,建议仔细进行探索性的数据分析、特性清理、特性工程等等。最大的性能提升通常来自这些最初的步骤。****

请记住,当使用 XGBoostLightBoost、CatBoost 训练模型时,预处理步骤可能会有所不同。例如,模型是否需要对特征进行编码,或者它能否处理非数字特征?模型是否可以处理缺失值,或者是否应该移除这些特征?参见第 节升压算法的简要介绍 以了解三种模型之间优势(劣势)的更多详细信息,并相应地进行预处理。

代码部分 4:输入参数以及如何使用 XGBoost、LightBoost 和 Catboost 模型进行分类和回归任务以及多类和集成模型的概述。

训练一个模特。

初始化之后,我们可以使用 XGBoost 决策树来训练一个模型。注意,您也可以使用 LightBoost 或 CatBoost 模型,如代码部分 3 所示。在训练过程中(运行第 4 节中的 代码 ),它将遍历搜索空间并创建 250 个不同的模型(max_eval=250),为这些模型评估特定决策树模型可用的一组超参数的模型准确性(在这种情况下为 XGBoost,代码第 1 节)。接下来,对模型的准确性进行排名,选出前 10 个表现最好的模型top_cv_evals=10。现在使用 5 折交叉验证方案(cv=5)对这些进行进一步研究,以对模型的概括程度进行评分。在这个意义上,我们的目标是防止找到一个过度训练的模型。****

返回不同模型的所有测试超参数,可进一步检查(手动)或使用绘图功能。例如, HGBoost 的输出在 代码段 5 中描述,更多细节在 图 2 中描述。

代码部分 HGBoost 模型返回的结果。

图二。结果输出['summary']显示了所有已执行评估的摘要。(图片来自作者)

使用绘图功能创建可解释的结果。

返回最终模型后,可以创建各种图来更深入地检查模型性能和超参数搜索空间。这有助于获得更好的直觉和关于模型参数相对于性能如何表现的可解释的结果。 可以创建以下情节:

  1. 为了更深入地研究超参数空间。
  2. 总结所有被评估的模型。
  3. 使用交叉验证方案显示性能。
  4. **独立验证集上的结果
  5. 最佳模型和最佳性能特征的决策树图。

代码部分 6:对模型结果的深入研究。

超参数调谐的解释。

为了更深入地研究超参数空间,可以使用功能.plot_params()(图 3 和图 4)** 。让我们从研究在贝叶斯优化过程中如何调整超参数开始两个图都包含多个直方图(或核密度图),其中每个子图是在 250 次模型迭代期间优化的单个参数。直方图底部的小条描绘了 250 个评估,而黑色垂直虚线描绘了前 10 个最佳性能模型中使用的特定参数值。绿色虚线描绘了没有 交叉验证方法的最佳表现模型 ,红色虚线描绘了具有 交叉验证的最佳表现模型 ****

我们来看看 图 3 。左上角有参数col sample _ bytree,取值范围从 0 到 1.2。col sample _ bytree的平均值为 ~0.7,这表明针对该参数使用贝叶斯优化的优化样本分布已移至该值。我们表现最好的模型的值正好是col sample _ bytree =0.7,(红色虚线)。但是还有更多值得关注的。当我们现在查看 图 4 时,我们也有col sample _ bytree,其中每个点都是根据迭代次数排序的 250 个模型之一。水平轴是迭代,垂直轴是优化的参数值。对于该参数,在优化过程的迭代期间有向上的趋势;接* 0.7 的值。这表明在迭代期间,当增加col sample _ bytree的值时,优化了col sample _ bytree的搜索空间,并且看到了更多具有更好准确性的模型。以这种方式,所有的超参数可以相对于不同的模型被解释。****

图 3。在回归模型的 250 次迭代中调整超参数。七个参数的分布。(图片来自作者)

图 4。在回归模型的 250 次迭代中调整超参数。每个点是一次迭代,为其选择特定的值。(图片来自作者)

解释所有评估模型的模型性能。

通过绘图功能.plot(),我们可以深入了解 250 款车型的性能(本例中为 AUC)(图 4*** )。这里,绿色虚线再次描绘了没有 交叉验证方法的表现最好的模型 ,而红色虚线描绘了具有 交叉验证的表现最好的模型 图 5 描绘了与具有默认参数的模型相比,具有优化超参数的最佳表现模型的 ROC 曲线。***

图 4。250 次评估和交叉验证的模型准确性。(图片来自作者)

图 5。与具有默认参数的模型相比,具有优化超参数的最佳性能模型的 ROC 曲线。(图片来自作者)

最佳模型的决策树图。

有了决策树图( 图 6 )我们可以更好的理解模型是如何工作的。它也可以给我们一些直觉,这样一个模型是否可以推广到其他数据集。注意,默认情况下会返回最佳树num_tree=0,但是通过指定输入参数.treeplot(num_trees=1)可以创建许多可以返回的树。此外,我们还可以绘制出性能最佳的特性(此处未显示)。

图 6。最佳模型的决策树图。(图片来自作者)

做预测。

有了最终的训练模型后,我们现在可以用它对新数据进行预测。假设 X 是新数据,并且在训练过程中进行了类似的预处理,那么我们可以使用.predict(X)函数进行预测。该函数返回分类概率和预测标签。示例见 代码第 7 节

代码第 7 部分。预测。

保存并加载模型。

保存和加载模型会变得很方便。为了完成这个,有两个函数:**.save()**和函数**.load()** (代码段 8)

代码第 8 部分。保存和加载模型。

结束了。

我演示了如何通过将数据集分为训练集、测试集和独立验证集来训练具有优化的超参数的模型。在训练测试集中,有使用贝叶斯优化来优化超参数的内部循环,以及基于 k-fold 交叉验证来对表现最好的模型的概括程度进行评分的外部循环。以这种方式,我们尽最大努力选择能够概括和具有最佳准确性的模型。

在训练任何模型之前,一个重要的部分就是做典型的建模工作流程:首先做探索性数据分析(EDA),迭代做清洗,特征工程,特征选择。这些步骤通常会带来最大的性能提升。**

HGBoost 支持学* 分类模型 ,回归模型多类模型,甚至是 boosting 树模型的集合。对于所有任务,应用相同的程序以确保选择最佳性能和最稳健的模型。如果你需要更多关于如何训练一个可解释的梯度推进分类模型的细节,请阅读这个博客【12】。****

此外, HGboost 依靠hyperpt进行贝叶斯优化,这是最流行的超参数优化库之一。最*开发的另一种贝叶斯优化算法叫做 Optuna 。如果你想阅读更多的细节,以及两种方法之间的比较,试试这个博客。

这就是了。我希望你喜欢读它!如果有不清楚的地方或者你有其他建议,请告诉我。

注意安全。保持冷静。

欢呼,E.

如果你觉得这篇文章很有帮助, 关注我 ,Star theHGBoost Github Repo,和/或支持我的内容,并通过使用我的 推荐链接 注册一个媒体会员来访问类似的博客。**

软件

我们连线吧!

参考

  1. 南珠等, XGBoost:在 Spark 和 Flink 中实现 Winningest Kaggle 算法。
  2. 米盖尔·菲耶罗等人 从基准测试快速机器学*算法中吸取的教训
  3. 欢迎来到 LightGBM 的文档
  4. CatBoost 是一个高性能的开源库,用于决策树 上的梯度提升。
  5. James Bergstra 等人,2013, Hyperopt:一个优化机器学*算法超参数的 Python 库。
  6. Kris Wright,2017, 用超视进行参数调谐。
  7. E.Taskesen,2019, Classeval:两类和多类分类器的监督预测评估
  8. 爱丽丝一怔,第四章。超参数调谐
  9. E.Taskesen,2020, 超优化梯度提升
  10. 卡格尔, 机器从灾难中学*
  11. E.Taskesen,2019, df2onehot:将非结构化数据帧转换为结构化数据帧。
  12. E.Taskesen,2022, 使用贝叶斯超参数优化创建可解释的梯度推进分类模型的实践指南。

Python 中深度学*分类的损失函数指南

原文:https://towardsdatascience.com/a-guide-to-loss-functions-for-deep-learning-classification-in-python-e22b37e3d6f6

多类多标签分类的损失函数

图片由马库斯·斯皮斯克像素上拍摄

深度学*模型是人脑中神经元网络的数学表示。这些模型在医疗保健、机器人、流媒体服务等领域有着广泛的应用。例如,深度学*可以解决医疗保健中的问题,如预测患者再次入院。此外,特斯拉的自动驾驶汽车采用深度学*模型进行图像识别。最后,像网飞这样的流媒体服务使用这些模型来分析用户数据,向观众推荐新的相关内容。在所有这些情况下,深度学*模型都属于机器学*模型的一个类别,称为分类。

机器学*分类是根据数据组的特征将离散标签分配给该组的过程。对于流媒体服务平台,这可能意味着将观众分为“喜欢喜剧系列”或“喜欢浪漫电影”等类别。这个过程的一个重要部分是最大限度地减少项目被错误分类和放入错误组的次数。在网飞的例子中,这样的错误分类会使系统错误地向喜剧电影爱好者建议恐怖内容。因此,为了让机器学*模型在最小化错误分类方面做得很好,数据科学家需要为他们试图解决的问题选择正确的损失函数。

“损失函数”是一个奇特的数学术语,用来衡量一个模型做出错误预测的频率。在分类的背景下,他们测量模型错误分类不同组成员的频率。深度学*分类模型最流行的损失函数是二元交叉熵和稀疏分类交叉熵。

二元交叉熵对于二元和多标记分类问题是有用的。例如,预测运动物体是人还是车是一个二元分类问题,因为有两种可能的结果。添加选择并预测对象是人、汽车还是建筑物是一个多标签分类问题。

稀疏分类交叉熵对于多类分类问题是有用的。这通常被框定为一对其余的问题,其中为每个类训练二进制分类器。例如,该模型应该能够预测图像中汽车的存在,以及预测人、建筑物和公园的图像是否不是汽车。还构建了附加的二元分类器来检测人、建筑物和公园。对于第一个分类器,这里的预测标签对应于“汽车”和“非汽车”对于第二分类器,标签是“人”和“不是人”等等。这可用于预测不同的标签(人、汽车、建筑物、公园),在这些标签中,我们使用模型预测的概率最高。如果我们只对区分一个类和所有其他类感兴趣,而不在乎区分其他类,也可以使用它。后者是我们将如何应用这种类型的模型。

这不同于多标记分类,多标记分类可以预测图像是否是汽车,然后进一步区分不是汽车的图像。鉴于选择正确的损失函数取决于手头的问题,对这些函数有一个基本的了解并知道何时使用它们对于任何数据科学家来说都是至关重要的。

Python 中的 Keras 库是一个易于使用的 API,用于构建可扩展的深度学*模型。在模型中定义损失函数很简单,因为它涉及在一个模型函数调用中定义单个参数值。

在这里,我们将看看如何应用不同的损失函数的二进制和多类分类问题。

对于我们的二元分类模型,我们将预测客户流失,这种情况发生在客户取消订阅或不再购买某家公司的产品时。我们将使用虚构的电信客户流失数据集。该数据集受知识共享许可协议约束,可用于数据共享。

对于我们的多类分类问题,我们将建立一个预测手写数字的模型。我们将使用公开可用的 MNIST 数据集,它在 Keras 库中可用,用于我们的多类预测模型。

读入电信客户流失数据

首先,让我们导入 Pandas 库,我们将使用它来读取我们的数据。让我们使用 Pandas head()显示前五行数据:

import pandas as pddf = pd.read_csv('telco_churn.csv')print(df.head())

作者图片

我们将建立一个深度学*模型,预测客户是否会流失。为此,我们需要将 churn 列的值转换成机器可读的标签。无论流失列中的值是“否”,我们都将分配一个整数标签“0”,流失值“是”将有一个整数标签“1”

让我们导入 numpy 包并使用 where()方法来标记我们的数据:

import numpy as npdf['Churn'] = np.where(df['Churn'] == 'Yes', 1, 0)

在我们的深度学*模型中,我们希望同时使用分类和数字特征。与标签类似,我们需要将分类值转换成机器可读的数字,以便用来训练我们的模型。让我们定义一个执行此任务的简单函数:

def convert_categories(cat_list): for col in cat_list: df[col] = df[col].astype('category')
        df[f'{col}_cat'] = df[f'{col}'].cat.codes

接下来,我们将指定分类列的列表,使用该列表调用函数,并显示数据框:

category_list = ['gender', 'Partner', 'Dependents', 'PhoneService', 'MultipleLines', 'InternetService',
                  'OnlineSecurity', 'OnlineBackup', 'DeviceProtection', 'TechSupport', 'StreamingTV',
                  'StreamingMovies', 'Contract', 'PaperlessBilling', 'PaymentMethod']convert_categories(category_list)print(df.head())

作者图片

我们可以看到,我们的数据框现在包含每个分类列的分类代码。

我们需要做的下一件事是强制 TotalCharges 列成为一个 float,因为在这个列中有一些非数字的,也就是坏的值。非数字值被转换为“非数字”值(NaN ),我们将 NaN 值替换为 0:

df['TotalCharges'] = pd.to_numeric(df['TotalCharges'], errors='coerce')
df['TotalCharges'].fillna(0, inplace=True)

接下来,让我们定义我们的输入和输出:

cols = ['gender_cat', 'Partner_cat', 'Dependents_cat', 'PhoneService_cat', 'MultipleLines_cat', 'InternetService_cat',
                  'OnlineSecurity_cat', 'OnlineBackup_cat', 'DeviceProtection_cat', 'TechSupport_cat', 'StreamingTV_cat',
                  'StreamingMovies_cat', 'Contract_cat', 'PaperlessBilling_cat', 'PaymentMethod_cat','MonthlyCharges',
                  'TotalCharges', 'SeniorCitizen']X = df[cols]y= df['Churn']

接下来,让我们为 Scikit-learn 中的模型选择模块导入训练/测试分割方法。让我们也为训练和测试拆分我们的数据

from sklearn.model_selection import train_test_splitX_train, X_test_hold_out, y_train, y_test_hold_out = train_test_split(X, y, test_size=0.33)

用于分类的深度神经网络损失函数

二元交叉熵

为了开始构建网络分类模型,我们将从 Keras 的图层模块中导入密集图层类开始:

from tensorflow.keras.layers import Dense

让我们也从度量模块中导入顺序类和准确性方法:

from tensorflow.keras.models import Sequentialfrom sklearn.metrics import accuracy_score

现在,让我们建立一个具有三个隐藏层和 32 个神经元的神经网络。我们还将使用 20 个历元,这对应于通过训练数据的次数:

我们将首先定义一个名为 model_bce 的变量,它是 sequential 类的一个实例:

model_bce = Sequential()

接下来,我们将在顺序类实例上使用 add()方法来添加密集层。这将是我们的神经网络的输入层,其中我们指定输入的数量,我们如何初始化权重,以及激活函数:

model_bce.add(Dense(len(cols),input_shape=(len(cols),), kernel_initializer='normal', activation='relu'))

接下来,我们将使用 add 方法添加三个隐藏层。这些层将具有 32 个神经元,并且还使用 ReLu 激活功能:

model_bce.add(Dense(32, activation='relu'))
model_bce.add(Dense(32, activation='relu'))
model_bce.add(Dense(32, activation='relu'))

然后我们需要添加输出层,它将有一个神经元和一个 softmax 激活函数。这将允许我们的模型输出类别概率,以预测客户是否会流失:

model_bce.add(Dense(1, activation='softmax'))

最后,我们将在我们的模型实例上使用 compile 方法来指定我们将使用的损失函数。首先,我们将指定二元交叉熵损失函数,它最适合我们在这里工作的机器学*问题的类型。

我们在编译层使用 loss 参数指定二进制交叉熵损失函数。我们简单地将“loss”参数设置为字符串“binary_crossentropy”:

model_bce.compile(optimizer = 'adam',loss='binary_crossentropy', metrics =['accuracy'])

最后,我们可以使我们的模型适合训练数据:

model_bce.fit(X_train, y_train,epochs =20)

作者图片

我们看到,对于 10 个时期中的每一个,我们都有一个损失函数值。我们看到,随着每个时期,我们的损失减少,这意味着我们的模型在训练过程中得到改善。

二元交叉熵对二元分类问题最有用。在我们的客户流失示例中,我们预测了两种结果之一:要么客户会流失,要么不会。但是,如果您正在处理有两个以上预测结果的分类问题,稀疏分类交叉熵是更合适的损失函数。

稀疏 分类交叉熵

为了将分类交叉熵损失函数应用于合适的用例,我们需要使用包含两个以上标签的数据集。这里,我们将使用 MNIST 数据集,它包含 0 到 9 之间的手写数字的图像。让我们从导入数据开始:

from tensorflow.keras.datasets import mnist

接下来,让我们存储输入和输出:

(X_train_mnist, y_train_mnist), (X_test_mnist, y_test_mnist) = mnist.load_data()

我们可以使用 matplotlib 轻松地将数据中的一些数字可视化:

此图像包含五个:

plt.imshow(X_train_mnist[0])plt.show()

作者图片

此图像包含一个零:

plt.imshow(X_train_mnist[1])plt.show()

作者图片

此图像包含一个九:

plt.imshow(X_train_mnist[4])plt.show()

作者图片

让我们重新格式化数据,以便我们可以使用它来训练我们的模型:

X_train_mnist = X_train_mnist.reshape((X_train_mnist.shape[0], 28, 28, 1))X_test_mnist = X_test_mnist.reshape((X_test_mnist.shape[0], 28, 28, 1))

接下来,我们需要从我们的类中生成二进制标签。让我们建立一个模型来预测一个图像是否包含数字 9。我们将为数字为 9 的图像分配一个标签“1”。任何其他不是 9 的数字都将被标记为“0”这种类型的问题不同于多标签分类,在多标签分类中,我们将有 9 个标签对应于 9 个数字中的每一个。

让我们生成二进制标签:

y_train_mnist = np.where(y_train_mnist == 9, 1, 0)y_test_mnist = np.where(y_test_mnist == 9, 1, 0)

接下来,我们将建立一个卷积神经网络,这是图像分类的典型架构。首先,让我们导入层:

from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten

接下来,让我们初始化模型并添加我们的层:

model_cce = Sequential()
model_cce.add(Conv2D(16, (3, 3), activation='relu', kernel_initializer='normal', input_shape=(28, 28, 1)))
model_cce.add(MaxPooling2D((2, 2)))
model_cce.add(Flatten())
model_cce.add(Dense(16, activation='relu', kernel_initializer='normal'))
model_cce.add(Dense(2, activation='softmax'))

在我们的编译层,我们将指定稀疏分类交叉熵损失函数:

model_cce.compile(optimizer = 'SGD',loss='sparse_categorical_crossentropy', metrics =['accuracy'])

符合我们的模型:

model_cce.fit(X_train_mnist, y_train_mnist, epochs =5)

作者图片

这篇文章中的代码可以在 GitHub 上找到。

在我们的图像分类示例中,我们预测标签“包含 9”和“不包含 9”“不包含九”标签由九个不同的类别组成。这与流失预测模型中的反面例子形成对比,其中“不流失”标签正好对应于单个类别。在客户流失的情况下,预测结果是二元的。客户要么搅动要么不搅动,其中“不搅动”标签仅由单个类别组成。

结论

知道对不同类型的分类问题使用哪种损失函数是每个数据科学家的一项重要技能。理解分类类型之间的差异为神经网络模型的损失函数的选择以及机器学*问题如何构建提供了信息。数据科学团队如何构建机器学*问题,会对公司的附加值产生重大影响。考虑具有 3 类流失概率的流失示例:低、中和高。一家公司可能对开发针对每个不同群体的广告活动感兴趣,这需要多标签分类模型。

另一家公司可能只对高流失率的目标客户感兴趣,而不关心区分低流失率和中流失率。这将需要一个多类分类模型。这两种类型的分类问题以不同的方式增加价值,哪种方式最合适取决于业务用例。出于这个原因,了解这些类型的预测问题之间的差异以及哪种损失函数适合每种问题是非常重要的。

如果你有兴趣学* python 编程的基础知识、Pandas 的数据操作以及 python 中的机器学*,请查看Python for Data Science and Machine Learning:Python 编程、Pandas 和 sci kit-初学者学*教程 。我希望你觉得这篇文章有用/有趣。

本帖原载于 内置博客 。原片可以在这里找到https://builtin.com/data-science/loss-functions-deep-learning-python

使用 AutoGluon 2022 新功能制作最佳 ML 管道指南

原文:https://towardsdatascience.com/a-guide-to-making-the-best-ml-pipeline-using-autogluon-2022-new-features-3fcb0b7f47d6

或者如何在不过度拟合的情况下获得最好的 python 开源模型

阿德里安在 unsplash 拍摄的照片

答自动模型选择工具是在监督和非监督机器学*中以简单快速的方式获得最佳预测的最佳方式。在大多数数据科学项目中,选择最佳模型选择是特征选择之后的关键步骤。高级数据科学家需要掌握最先进的 ML 管道方法。在本文中,我们将回顾最佳 Kaggle 获奖者的自动化 ML 管道选择方法 AutoGluon 。AWS 创建的一个开源包,可以用简短的 python 代码实现。

对于本文,我们将使用流失预测数据集创建分类和回归模型管道,您可以在这里找到从 IBM 样本集集合数据集修改而来的。该数据集包含 7043 个客户信息,包括人口统计(性别、任期、合作伙伴)、账户信息(账单、电话服务、多条线路、互联网服务、支付方式等)..),以及二进制标签流失(0:客户离开或 1:没有)。

包含 21 个特征的挑战性数据集与目标特征“流失”相关。

autoglon 提供了开箱即用的自动化监督机器学*,可优化机器学*管道,在几秒钟内自动搜索最佳学*算法(神经网络、SVM、决策树、KNN 等)和最佳超参数。点击此处查看自动生成中可用的估算器/模型的完整列表。

AutoGluon 可以在文本图像时间序列管状数据集上生成模型,自动处理数据集清理特征工程、模型选择、超参数调整等。

完整的自动旋转分析可以在 18 个步骤中完成,您可以在此链接中找到。在本文中,我们将只关注 2022 年新的自动旋转功能。

菲利普·布阿齐兹在 unsplash 上的照片

1。带自动增长的分类

首先,我们需要为训练和测试数据集创建管状数据集,如下所示:

train_data = TabularDataset(train_df)subsample_size = 5000 # subsample subset train_data = train_data.sample(n=subsample_size, random_state=0)train_data.head()test_data = TabularDataset(test_df)subsample_size = 1000 # subsample subset test_data = test_data.sample(n=subsample_size, random_state=0)test_data.head()

下一步包括一次拟合(),以获得具有最佳选择指标的 ML 管道:

label = 'Churn'save_path = 'agModels-predictClass' # specifies folder to store trained modelspredictor = task(label=label, path=save_path).fit(train_data)

带自动旋转的 ML 模型管道 (图片作者)

我们可以评估测试数据集预测的最佳模型,如下所示:

y_test = test_data[label] # values to predicttest_data_nolab = test_data.drop(columns=[label]) # delete label columntest_data_nolab.head()

测试数据 (图片由作者提供)

我们现在可以用最佳拟合模型预测:

predictor = task.load(save_path)y_pred = predictor.predict(test_data_nolab)print(“Predictions: \n”, y_pred)perf = predictor.evaluate_predictions(y_true=y_test, y_pred=y_pred, auxiliary_metrics=True)

****(图片由作者提供)最佳模型测试评价

在一行代码中,我们可以轻松地为我们的 ML 管道制作一个排行榜,以选择最佳模型。

predictor.leaderboard(test_data, silent=True)

(图片由作者提供)

我们可以使用最佳拟合模型对测试数据集进行预测:

**predictor.predict(test_data, model='WeightedEnsemble_L2')**

最佳模型预测使用‘加权邓布尔 _ L2’(图片由作者提供)

菲利普·布阿齐兹在 unsplash 上拍摄的照片

最后,我们可以一步调优最佳模型的超参数:

**time_limit = 60 # for quick demonstration only (in seconds)metric = 'roc_auc' # specify your evaluation metric herepredictor = task(label, eval_metric=metric).fit(train_data, time_limit=time_limit, presets='best_quality')predictor.leaderboard(test_data, silent=True)**

ML 模型超参数 调整管道排行榜(图片由作者提供)

胶子自动分类任务产生了一个'加权的登斯布尔 _L2 '模型,在测试数据集上优化之前的精度为 0.794 ,优化之后的精度为 0.836 ,而没有从验证/测试= 0.85–0.835(0.015)过度拟合,从而在几分钟内调整出最佳模型。

Alessia Cocconi 在 unsplash 上拍摄的照片

2。带自动引导的回归任务

Gluon AutoML 的另一个特性是用几行代码创建 ML 回归管道,如下所示:

**predictor_age = task(label=age_column, path="agModels-predictAge").fit(train_data, time_limit=60)performance = predictor_age.evaluate(test_data)**

回归任务 ML 模型流水线自动生成 (图片由作者提供)

如前所述,我们可以在测试数据集上制作最佳模型预测排行榜:

**predictor_age.leaderboard(test_data, silent=True)**

带自动增长功能的 ML 车型渠道排行榜 (图片由作者提供)

我们可以看到,“KNeighborsUnif”模型在测试数据集( 0.054 )和验证数据集( 0.066 )上显示了接*的精度,没有过度拟合。

Eric Brehem 在 [unsplash](http://photo by Philippe Bouaziz on unsplash) 上拍摄的照片

我们现在可以找到在测试和验证数据集上具有最佳结果的最佳模型的名称:

**predictor_age.persist_models()**

输出:

['KNeighborsUnif ',' NeuralNetFastAI ',' weighted densemble _ L2 ']

年龄预测的最佳模型是' KNeighborsUnif ',其特征重要性列表如下:

**predictor_age.feature_importance(test_data)**

最佳回归模型的重要度排行榜(图片由作者提供)

Alessia Cocconi 在 unsplash 上拍摄的照片

结论

AutoGluon 的 2022 新功能侧重于 ML 管道和最先进的技术,包括模型选择、集成和超参数调整。监督/非监督机器学*中的自动增长原型任务,以及对真实世界数据集(文本、图像、管状)的深度学*,如对 churn 数据集的分析所示。AutoGluon 提供一套独特的 ML 管道,有 20 个模型以及神经网络和组装模型(装袋、堆垛、称重)。只需一个代码行,AutoGluon 就能提供高准确度的客户流失预测,无需繁琐的任务,如数据清理、功能选择、模型工程和超参数调整。

对于没有自动增长的相同数据集的流失预测分析,我建议您阅读这篇文章:

**** ****

总结

这个简短的概述提醒我们在数据科学中使用正确的算法选择方法的重要性。这篇文章涵盖了 AWS 2022 Gluon AutoML Python automate ML pipeline 特性,用于分类和回归任务,并分享了有用的文档。

Philippe Bouaziz 在 unsplash 上的照片

希望你喜欢,继续探索:-)

Python 中日期时间数据管理指南

原文:https://towardsdatascience.com/a-guide-to-managing-datetime-data-in-python-c5b841a50ac4

以下是如何解决你的日期时间,熊猫指数和夏令时噩梦

图片来源:pixabay 上的 SplitShire

使用日期时间数据集可能是计算机编程中最令人沮丧的方面之一。您不仅需要跟踪日期,还需要学*如何用每种语言表示日期和时间,从这些数据点中创建索引,并确保您的所有数据集以相同的方式处理夏令时。幸运的是,这本关于日期时间数据的入门书将帮助您开始使用 Python。

日期时间

Python 中大多数日期和时间的表示都是以 datetime 对象的形式呈现的,这些对象是从 Datetime 包中创建的。这意味着了解 Datetime 包以及如何使用它是至关重要的!

从根本上说,datetime 对象是一个包含日期和时间信息的变量。它还可以包括时区信息,并且有根据需要更改时区的工具。

让我们看几个使用 datetime 包创建的 Datetime 对象的例子。首先,我们可以使用包的命令创建一个存储当前时间的变量,如下所示:

import datetimeimport pytznow = datetime.datetime.now(pytz.timezone(‘US/Pacific’))print(now)print(now.tzinfo)

前两行导入了这个任务的重要包。第一个是 datetime 包,它使我们能够创建和操作 Datetime 对象。第二个是 Pytz 包,它提供时区信息。

第三行调用 datetime.datetime.now 函数来创建一个 datetime 对象,表示我们运行代码的时间。此行还向 datetime 添加了一个时区,说明 datetime 表示美国太平洋时区的时间。

第四行和第五行都是打印输出,用于演示代码的结果。

该代码的输出是:

2022–05–11 09:19:01.859385–07:00US/Pacific

第一个输出显示了变量现在的完整信息。显示该变量创建于 2022 年 5 月 11 日 9 点 19 分 1.86 秒。因为我将时区设置为'美国/太平洋',所以程序将适当的 -7 小时(相对于 UTC)附加到变量上。第二个输出通过打印变量的时区是美国太平洋时区来确认时区信息。

您会注意到,在上面的代码中,我通过调用pytz . time zone(' US/Pacific ')将时区设置为美国太平洋时区。如果您想使用不同的时区,您需要知道正确的代码(尽管它们都遵循相同的格式并引用已知的时区,所以它们是相当容易预测的)。如果您想找到您的 timez one,您可以使用以下命令打印所有选项的列表。

print(pytz.all_timezones)

我们还可以使用 datetime.datetime 函数来创建指定日期的日期时间。请注意前面示例中显示当前时间的 datetime 对象的格式,因为对 datetime.datetime 的输入是以相同的顺序提供的(年、月、日、小时、分钟、秒)。换句话说,如果我们想要创建一个表示美国太平洋时区 2022 年 5 月 11 日 12:11:03 的 datetime 对象,我们可以使用以下代码:

specified_datetime = datetime.datetime(2022, 5, 11, 12, 11, 3).astimezone(pytz.timezone(‘US/Pacific’))print(specified_datetime)print(specified_datetime.tzinfo)

注意上面的 datetime.datetime 的输入是如何出现的。然后是。调用 astimezone 方法将时区设置为美国太平洋时区。

该代码的输出是:

2022–05–11 12:11:03–07:00US/Pacific

结果是代码创建了我们想要的变量。根据需要, specified_datetime 现在返回美国太平洋时区 2022 年 5 月 11 日 12:11:03。

如果我们想在不同的时区表示相同的时间呢?我们可以计算新的时区并相应地创建一个新的 datetime 对象,但是这需要我们知道时差,进行计算并相应地创建新的对象。另一个选项是将时区转换为所需的时区,并将输出保存到一个新变量中。因此,如果我们想将指定日期时间转换为美国东部时区,我们可以使用下面的代码。

eastern_datetime = specified_datetime.astimezone(pytz.timezone(‘US/Eastern’))print(eastern_datetime)print(eastern_datetime.tzinfo)

这段代码调用了。用代表美国东部时区的新时区对象指定日期时间的方法。打印输出为:

2022–05–11 15:11:03–04:00US/Eastern

请注意指定日期时间大西洋日期时间之间的变化。由于美国东部时间比美国太平洋时间早三个小时,时间从 12 点变成了 15 点。时区信息从 -7 变为 -4 ,因为美国东部时间与 UTC 相差 4 小时,而不是相差 7 小时。最后,注意打印的时区信息现在是美国/东部而不是美国/太平洋

熊猫指数

Pandas 数据帧通常使用 datetime 对象作为索引,因为这使数据集能够跟踪记录测量的日期和时间。因此熊猫提供了许多你可以使用的工具。

您第一次介绍带有日期时间索引的数据帧很可能是导入别人的数据集,而别人恰好使用了一个数据集。考虑以下示例和提供的输出:

data = pd.read_csv(r’C:\Users\Peter Grant\Desktop\Sample_Data.csv’, index_col = 0)print(data.index)print(type(data.index))print(type(data.index[0]))

示例数据集中的索引使用 datetime,所以您会认为 dataframe 的索引是 datetime 索引,对吗?很不幸,你错了。让我们来看看输出:

Index([‘10/1/2020 0:00’, ‘10/1/2020 0:00’, ‘10/1/2020 0:00’, ‘10/1/2020 0:01’,‘10/1/2020 0:01’, ‘10/1/2020 0:01’, ‘10/1/2020 0:01’, ‘10/1/2020 0:02’,‘10/1/2020 0:02’, ‘10/1/2020 0:02’,…‘4/1/2021 2:01’, ‘4/1/2021 2:01’, ‘4/1/2021 2:02’, ‘4/1/2021 2:02’,‘4/1/2021 2:02’, ‘4/1/2021 2:02’, ‘4/1/2021 2:03’, ‘4/1/2021 2:03’,‘4/1/2021 2:03’, ‘4/1/2021 2:03’],dtype=’object’, length=1048575)<class ‘pandas.core.indexes.base.Index’><class ‘str’>

指数看起来就像我们预期的那样。每个都表示日期和时间,同时遍历日期和时间,直到索引结束。每 15 秒记录一次样本,每分钟有 4 个,这很好,但是值没有显示秒数,这有点奇怪。

但是事情变得奇怪了。当我们希望索引是日期时间索引时,它的类型是泛型。最后,第一个条目的类型是字符串,而不是日期时间对象。

Pandas 将日期时间索引作为字符串列表读入,而不是日期时间索引。每次都会这样。好在熊猫有一个 to_datetime() 函数解决了这个问题!考虑以下代码:

data.index = pd.to_datetime(data.index)print(data.index)print(type(data.index))print(type(data.index[0]))

以及输出:

DatetimeIndex([‘2020–10–01 00:00:00’, ‘2020–10–01 00:00:00’,‘2020–10–01 00:00:00’, ‘2020–10–01 00:01:00’,‘2020–10–01 00:01:00’, ‘2020–10–01 00:01:00’,‘2020–10–01 00:01:00’, ‘2020–10–01 00:02:00’,‘2020–10–01 00:02:00’, ‘2020–10–01 00:02:00’,…‘2021–04–01 02:01:00’, ‘2021–04–01 02:01:00’,‘2021–04–01 02:02:00’, ‘2021–04–01 02:02:00’,‘2021–04–01 02:02:00’, ‘2021–04–01 02:02:00’,‘2021–04–01 02:03:00’, ‘2021–04–01 02:03:00’,‘2021–04–01 02:03:00’, ‘2021–04–01 02:03:00’],dtype=’datetime64[ns]’, length=1048575, freq=None)<class ‘pandas.core.indexes.datetimes.DatetimeIndex’><class ‘pandas._libs.tslibs.timestamps.Timestamp’>

啊哈。这个看起来好多了。数据帧的索引现在是日期时间索引,第一个条目的类型现在是熊猫时间戳(相当于日期时间对象)。这是我们想要的,也是我们可以努力的。

但是,如果您想创建自己的日期时间索引呢?如果您知道想要创建日期时间索引的日期范围和频率,那么可以使用 Pandas date_range() 函数。这里有一个例子:

index = pd.date_range(datetime.datetime(2022, 1, 1, 0, 0), datetime.datetime(2022, 12, 31, 23, 55), freq = ‘5min’)print(index)

此代码返回以下输出:

DatetimeIndex([‘2022–01–01 00:00:00’, ‘2022–01–01 00:05:00’,‘2022–01–01 00:10:00’, ‘2022–01–01 00:15:00’,‘2022–01–01 00:20:00’, ‘2022–01–01 00:25:00’,‘2022–01–01 00:30:00’, ‘2022–01–01 00:35:00’,‘2022–01–01 00:40:00’, ‘2022–01–01 00:45:00’,…‘2022–12–31 23:10:00’, ‘2022–12–31 23:15:00’,‘2022–12–31 23:20:00’, ‘2022–12–31 23:25:00’,‘2022–12–31 23:30:00’, ‘2022–12–31 23:35:00’,‘2022–12–31 23:40:00’, ‘2022–12–31 23:45:00’,‘2022–12–31 23:50:00’, ‘2022–12–31 23:55:00’],dtype=’datetime64[ns]’, length=105120, freq=’5T’)

将调用 date_range()的代码与函数的文档进行比较,可以看到前两个条目设置了范围的开始和结束日期。开始日期设置为 2022 年 1 月 1 日午夜,结束范围设置为 2022 年 12 月 31 日 23:55:00。第三个条目将日期时间索引的频率设置为五分钟。注意五分钟的代码是‘5min’。为了获得您想要的频率,您需要使用正确的代码来设置频率。幸运的是,有一个熊猫代码列表可用。

用熊猫日期时间索引也可能有点麻烦。乍一看,似乎需要创建复杂的 datetime 对象来引用 dataframe 的正确部分。考虑下面的例子,其中我使用上一个例子中的索引创建了一个新的 dataframe,在 dataframe 中设置一个值并打印该值以确保它正确更新。

df = pd.DataFrame(index = index, columns = [‘Example’])df.loc[datetime.datetime(2022, 1, 1, 0, 0, 0), ‘Example’] = 2print(df.loc[datetime.datetime(2022, 1, 1, 0, 0, 0), ‘Example’])

这段代码的输出正是我想要的。它打印出 2 ,显示 dataframe 在 [datetime.datetime(2022,1,1,0,0,0),‘Example ']的值为所需的 2 。但是一遍又一遍地指定日期时间变得很乏味。

幸运的是,您仍然可以通过位置引用日期时间索引。如果要编辑索引中的第一个条目,可以按如下方式操作。

df = pd.DataFrame(index = index, columns = [‘Example’])df.loc[df.index[0], ‘Example’] = 2print(df.loc[df.index[0], ‘Example’])

请注意这段代码是如何做完全相同的事情的,只是它通过调用索引的第一个值来提供所需的索引值。你甚至不需要知道有什么价值,你只需要知道你想用第一个——或者第二个,或者第三个,或者任何你想要的价值。您只需要相应地更新呼叫。

夏令时

使用夏令时是 Python 中最大的难题之一,并且会导致非常严重的数据分析错误。考虑将物理测量值与理论*似值进行比较的例子。现在你有两个数据集,你想确保他们说的是同一件事。如果一个数据集使用夏令时,而另一个数据集不使用夏令时,该怎么办?突然间,你开始比较相差一小时的数据集。

解决此问题的一种方法是,在夏令时发生的时间内,从具有夏令时的 dataframe 索引中删除一个小时。为了帮助解决这个问题,熊猫的时间戳有一个。dst() 方法,返回任意点的夏令时差。如果时间戳发生在夏令时,它将返回一个小时的 datetime.timedelta 值。如果时间戳在夏令时期间没有出现,它将返回一个零小时的 datetime.timedelta 值。这使我们能够识别夏令时期间出现的时间戳,并相应地从中删除一个小时。

假设您有一个包含夏令时偏移量的 datetime 索引的数据帧。要删除夏令时,可以遍历索引,使索引时区为 naive,并从索引中删除一个小时。不幸的是,索引是不可变的,所以您不能直接编辑它们。您可以做的是创建一个外部值列表,将更新后的索引值添加到该列表中,并在最后用该列表替换索引。这段代码应该是这样的。

temp = []for ix in range(len(df.index)):if df.index[ix].dst() == datetime.timedelta(hours = 1):temp.append(df.index[ix].tz_localize(None) — datetime.timedelta(hours = 1))else:temp.append(df.index[ix].tz_localize(None))df.index = temp

现在你知道了。现在您知道了如何使用 datetime 对象,使用它们来构成 Pandas 数据帧的索引,并从您的数据集中删除夏令时。

Python 中机器学*模型的元启发式优化指南

原文:https://towardsdatascience.com/a-guide-to-metaheuristic-optimization-for-machine-learning-models-in-python-6d85ef8230ee

优化机器学*模型输出的方法

图片由 Cottonbro像素上拍摄

数学优化是寻找使函数输出最大化(或最小化)的最佳输入集的过程。在最优化领域,被优化的函数称为目标函数。有许多现成的工具可以用来解决优化问题,这些工具只适用于性能良好的函数,也称为凸函数。性能良好的函数包含一个最优值,不管它是最大值还是最小值。在这里,一个函数可以被认为是一个只有一个谷(最小值)和/或一个峰(最大值)的曲面。非凸函数可以被认为是具有多个山谷和山丘的曲面。

凸函数的优化,也称为凸优化,已被应用于简单的任务,如投资组合优化,航班调度,最佳广告和机器学*。在机器学*的背景下,凸优化在训练几种机器学*模型时起作用,包括线性回归逻辑回归支持向量机

凸优化的一个限制是它假设目标函数保证有一个谷和/或山。用专业术语来说,凸函数保证有所谓的全局最大值或最小值。然而在实践中,许多目标函数并不具有这种特性,这使得它们是非凸函数。这是因为在实际应用中使用的许多目标函数非常复杂,并且包含许多峰和谷。当目标函数包含许多峰和谷时,通常很难找到最低的谷或最高的峰。

元启发式优化是优化这种非凸函数的最佳方法。这些算法是最佳选择,因为它们不假设函数包含多少个峰和谷。虽然这种方法有助于找到最小值和最大值,但它们对应于所谓的局部最大值(靠*高山)和最小值(靠*低谷)。因此,当使用元启发式优化来优化非凸函数时,记住这些算法不能保证找到全局最大值(最高的山)或最小值(最低的谷)。这是因为当优化具有许多峰和谷的非凸函数时,找到最低的谷或最高的峰通常在计算上不可行。尽管如此,元启发式优化通常是现实生活应用的唯一手段,因为凸优化对函数中存在的单个峰和/或谷做出了强有力的假设。这些算法通常提供接*足够低的山谷或足够高的山丘的解决方案。

元启发式优化对于优化机器学*模型的输出是重要的。例如,如果您为水泥制造商构建一个预测水泥强度的模型,您可以使用元启发式优化来查找使水泥强度最大化的最佳输入值。像这样的模型采用对应于水泥混合物中成分数量的输入值。然后,优化程序将能够找到最大化强度的每种成分的数量。

Python 提供了各种各样的元启发式优化方法。差分进化是一种常用的方法。它的工作原理是提供一系列候选最优解,并通过在搜索空间中移动候选解来迭代改进这些解。Maxlipo 是另一种类似于差分进化的方法,附加了一个假设,即函数是连续的。连续函数是没有间断或跳跃的函数。Maxlipo 是对差分进化的改进,因为它能够以明显更少的函数调用找到最优值。这意味着算法运行更快,同时仍然能够找到接*最优的值。差分进化和 maxlipo 都是遗传算法的变体,遗传算法通过重复修改候选解来工作,直到找到接*最优的解。

这两种元启发式优化算法在 Python 中都有易于使用的库。使用 Python 科学计算库 Scipy 和使用 Python 包装的 C++库dlibmax lipo 可以实现差分进化。

在这里,我们将建立一个简单的机器学*模型,用于预测混凝土强度。然后,我们将构建一个优化器来搜索最大化强度的属性值。我们将使用混凝土强度回归数据集 [1]。

:本数据库的重复使用是不受限制的,保留叶义成教授的版权声明和文章末尾引用的已发表论文。

读入数据

让我们首先导入 Pandas 库并将数据读入 Pandas 数据框:

import pandas as pd
df = pd.read_csv("Concrete_Data_Yeh.csv")

接下来,让我们显示前五行数据:

print(df.head())

作者图片

我们看到数据包含水泥、矿渣、粉煤灰、水、超塑化剂、粗骨料、细骨料、龄期和 CsMPa。最后一列包含对应于混凝土强度的 csMPa 值。我们将构建一个随机森林模型,使用所有前面的输入来预测 csMPa。接下来,让我们定义我们的输入和输出:

X = df[['cement', 'slag', 'flyash', 'water', 'superplasticizer',
       'coarseaggregate', 'fineaggregate', 'age']]y = df['csMPa']

接下来,让我们将数据分为训练和测试两部分:

from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.2, random_state = 42)

接下来,让我们从 Scikit learn 中的 ensemble 模块导入随机森林类:

from sklearn.ensemble import RandomForestRegressor

让我们将随机森林回归类的一个实例存储在一个名为 model 的变量中。我们将定义一个随机森林模型,它有 100 个估计值,最大深度为 100:

model = RandomForestRegressor(n_estimators=100, max_depth=100)

接下来,让我们将模型与通过训练/测试分割方法返回的训练数据相匹配:

model.fit(X_train, y_train)

然后,我们可以对测试集进行预测,并将预测存储在一个名为 y_pred 的变量中:

y_pred = model.predict(X_test)

接下来,让我们评估我们的回归模型的性能。我们可以使用均方根误差(RMSE)来评估我们的模型表现如何:

from sklearn.metrics import mean_squared_errorimport numpy as nprmse = np.sqrt(mean_squared_error(y_test, y_pred))print("RMSE: ", rmse)

作者图片

我们看到我们的随机森林模型给出了不错的性能。我们还可以使用实际对比预测图来直观显示性能。实际值和预测值之间的线性关系表明性能良好:

import matplotlib.pyplot as pltplt.scatter(y_test, y_pred)
plt.title("Actual vs. Predicted")
plt.xlabel("Actual")
plt.ylabel("Predicted")

作者图片

我们看到,我们确实有一个线性关系,我们的 RMSE 很低,所以我们可以对我们的模型的性能感到满意。

既然我们对我们的模型感到满意,我们可以定义一个新的模型对象,我们将在完整的数据集上对其进行训练:

model_full= RandomForestRegressor(n_estimators=100, max_depth=100, random_state =42)model_full.fit(X, y)

model_full 对象有一个 predict 方法,它将作为我们要优化的函数,也称为目标函数。

我们优化任务的目标是找到优化水泥强度的每个输入的值。

差异进化

让我们从 Scipy 中的优化模块导入差分进化类:

from scipy.optimize import differential_evolution

接下来,让我们定义我们的目标函数。它将获取与模型输入相对应的输入数组,并返回每个集合的预测值:

def obj_fun(X):
    X = [X]
    results = model_full.predict(X)
    obj_fun.counter += 1
    print(obj_fun.counter)
    return -results

我们还将打印每一步的函数调用次数。这将有助于比较优化器之间的运行时性能。理想情况下,我们希望优化器能够最快地找到最大值,这通常意味着更少的函数调用。

接下来,我们需要为每个输入定义上限和下限。让我们使用每个输入数据中可用的最大值和最小值:

boundaries = [(df['cement'].min(), df['cement'].max()), (df['slag'].min(), df['slag'].max()), (df['flyash'].min(), df['flyash'].max()), 
                (df['water'].min(), df['water'].max()), (df['superplasticizer'].min(), df['superplasticizer'].max()),
       (df['coarseaggregate'].min(), df['coarseaggregate'].max()), (df['fineaggregate'].min(), df['fineaggregate'].max()), (df['age'].min(), df['age'].max())]

接下来,让我们将目标函数和边界传递给差分进化优化器:

if __name__ == '__main__':
    opt_results = differential_evolution(obj_fun, boundaries)

然后,我们可以打印最佳输入和输出值:

 print('cement:', opt_results.x[0])
    print('slag:', opt_results.x[1])
    print('flyash:', opt_results.x[2])
    print('water:', opt_results.x[3])
    print('superplasticizer:', opt_results.x[4])
    print('coarseaggregate:', opt_results.x[5])
    print('fineaggregate:', opt_results.x[6])
    print('age:', opt_results.x[7])

    print("Max Strength: ", -opt_results.fun)

作者图片

我们看到,在 5500 次函数调用之后,差分进化优化器找到了一个最佳值 79。这种性能相当差,因为它需要大量的函数调用,这使得算法运行非常慢。此外,所需的函数调用次数不能由研究人员指定,实际上可能在运行之间有所不同。这为优化创造了不可预测的运行时间。

MAXLIPO 优化器

Maxlipo 是一个更好的选择,因为它能够用更少的函数调用找到接*最优的值。此外,用户可以指定函数调用,这解决了运行时不可预测的问题。

所以,让我们建立我们的 maxlipo 优化器。我们将从 Python 中的 dlib 库中导入该方法:

import dlib

接下来,让我们定义函数调用的上限和下限以及数量:

import dliblbounds = [df['cement'].min(), df['slag'].min(), df['flyash'].min(), df['water'].min(), df['superplasticizer'].min(), df['coarseaggregate'].min(), 
           df['fineaggregate'].min(), df['age'].min()]
ubounds = [df['cement'].max(), df['slag'].max(), df['flyash'].max(), df['water'].max(), df['superplasticizer'].max(), df['coarseaggregate'].max(), 
           df['fineaggregate'].max(), df['age'].max()]
max_fun_calls = 1000

由于目标函数需要采取稍微不同的形式,让我们重新定义它:

def maxlip_obj_fun(X1, X2, X3, X4, X5, X6, X7, X8):
    X = [[X1, X2, X3, X4, X5, X6, X7, X8]]
    results = model_full.predict(X)
    return results

接下来,我们将边界、目标函数和函数调用的最大数量传递给优化器:

sol, obj_val = dlib.find_max_global(maxlip_obj_fun, lbounds, ubounds, max_fun_calls)

现在,我们可以打印我们的 Maxlipo 优化器的结果:

print("MAXLIPO Results: ")
print('cement:', sol[0])
print('slag:', sol[1])
print('flyash:', sol[2])
print('water:', sol[3])
print('superplasticizer:', sol[4])
print('coarseaggregate:', sol[5])
print('fineaggregate:', sol[6])
print('age:', sol[7])print("Max Strength: ", obj_val)

作者图片

我们看到该算法发现 1000 次函数调用的最大强度为 77。Maxlipo 算法找到了一个接*差分进化找到的最优值,但只有五分之一的函数调用。因此,Maxlipo 算法比差分进化快五倍。

这篇文章中的代码可以在 GitHub 上找到。

结论

优化是一种非常重要的计算方法,在各行各业都有广泛的应用。鉴于许多机器学*模型的复杂性质,在数值优化中做出的许多理想主义假设并不适用。这就是为什么不对函数形式做出强假设的优化方法对机器学*很重要。

可用于元启发式优化的 Python 库非常适合机器学*模型。当考虑优化器时,研究人员需要了解优化器在运行时和函数调用方面的效率,因为它会显著影响生成好结果所需的时间。忽略这些因素会导致公司因浪费计算资源而损失数千美元。出于这个原因,数据科学家需要对可用的元启发式优化方法有一个相当好的理解和熟悉。

如果你有兴趣学* python 编程的基础知识、Pandas 的数据操作以及 python 中的机器学*,请查看Python for Data Science and Machine Learning:Python 编程、Pandas 和 sci kit-初学者学*教程 。我希望你觉得这篇文章有用/有趣。

本帖原载于 内置博客 。原片可以在这里找到https://builtin.com/data-science/metaheuristic-optimization-python

引文:

[1].叶一成,“使用人工神经网络对高性能混凝土的强度进行建模”,水泥与混凝土研究,第 28 卷,第 12 期,第 1797–1808 页(1998 年)。

ML 实验跟踪快速指南——带权重和偏差

原文:https://towardsdatascience.com/a-guide-to-ml-experiment-tracking-with-weights-biases-93a3a2544413

通过度量和日志以及一个示例项目演练,轻松学*跟踪您的所有 ML 实验

照片由吴镇男·麦金尼Unsplash 上拍摄

对于数据科学从业者来说,反复试验的艰苦过程并不陌生。在您的日常工作或附带项目中,您会遇到各种用例,这些用例需要您能够跟踪 ML 管道的分支和实验,其数量级超过了通常的软件项目。

您的目标是为您的解决方案产生可重复的结果并实现更高的准确性(或您需要的任何其他指标),如果不牢牢把握您的管道及其每一个组件,您就无法做到这一点。

通过帮助您跟踪、可视化和试验您的训练数据、代码以及超参数的工具,这种事情变得更加容易,这些工具使您能够在详细的级别上监控您的模型的性能。

让我给你介绍一个这方面的绝妙工具——权重和偏见

在本教程中,我将帮助您了解基础知识,并让您熟悉深度学*项目的训练运行的设置和实验跟踪。

所以,让我们开始吧!

项目设置

在这个使用权重和偏差的演练中,我们将在 Keras 中对时尚 MNIST 数据(与 Keras 捆绑在一起)探索卷积神经网络(我们将它称为:W&B 或**wandb**)。

通常,第一步是在虚拟环境中安装所需的库:

pip install tensorflow wandb # or if you use pipenv like myselfpipenv shell
pipenv install tensorflow wandb

最后,让我们为代码创建两个 python 文件:

  1. **train.py:** 存储模型架构和
  2. **main.py:** 以超参数为自变量进行训练。

带有 wandb 的模型

我们将要构建的 Keras 模型将根据数据进行多类分类,分为 10 个不同的类。

第一步是初始化项目的**wandb**:

# initialize wandb logging to your projectwandb.init(project=args.project_name)

注意:现在不要担心**args**变量,我们稍后会谈到它。😃

然后,我们希望确保我们的模型可以访问我们传递给它的任何参数(超参数):

# log all experimental args to wandbwandb.config.update(args)

现在,就像在一个简单的 Keras 工作流中一样,让我们接收数据,对其进行规范化,并对其进行整形:

# load and prepare the data(X_train, y_train), (X_test, y_test) = fashion_mnist.load_data()labels=["T-shirt/top","Trouser","Pullover","Dress","Coat", "Sandal","Shirt","Sneaker","Bag","Ankle boot"] # normalize the dataX_train = X_train.astype('float32')X_train /= 255.X_test = X_test.astype('float32')X_test /= 255.# reshape the dataX_train = X_train.reshape(X_train.shape[0], img_width, img_height, 1)X_test = X_test.reshape(X_test.shape[0], img_width, img_height, 1)

现在,我们想一次性将数据和标签编码成分类值。这可以简单地通过以下方式实现:

# one hot encode outputsy_train = np_utils.to_categorical(y_train)y_test = np_utils.to_categorical(y_test)num_classes = y_test.shape[1] # = 10, as there are 10 classes in fashion mnist dataset

现在,我们需要建立我们的模型。为了这个教程,我们先来看一个简单的 CNN。

# build modelmodel = Sequential()model.add(Conv2D(args.L1_conv_size, (5, 5), activation='relu', input_shape=(img_width, img_height,1)))model.add(MaxPooling2D(pool_size=(2, 2)))model.add(Conv2D(args.L2_conv_size, (5, 5), activation='relu'))model.add(MaxPooling2D(pool_size=(2, 2)))model.add(Dropout(args.dropout_mask))model.add(Flatten())model.add(Dense(args.hidden_size, activation='relu'))model.add(Dense(num_classes, activation='softmax'))

看到了吗?不要太花哨。

额外:如果你在上面的代码中看到一些你并不真正理解的东西,那也没关系。我也为一个真实世界的项目写了另一个在 Keras 做 CNN 的教程。想学就从这里学吧。😃

现在,我们准备好声明我们的优化器了。对于这种情况,我们使用**Adam**

adam = Adam(lr = args.learning_rate)

现在,我们想使用 Keras 的**ImageDataGenerator**模块,在我们的训练过程中接收验证图像,并输出我们的验证精度和损失:

val_generator = ImageDataGenerator()

最后,我们可以用两行代码来编译和拟合我们的模型:

model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy'])model.fit(X_train, y_train, validation_data = (X_test, y_test), epochs = args.epochs, callbacks = [WandbCallback(data_type = "image", labels = labels, generator = val_generator.flow(X_test, y_test, batch_size=32))])

Extra :你也可以用另一个简单的语句保存你的模型:

model.save(f"{args.model_name}.h5")

让我们把所有这些都打包到我们的**train.py**模块中的一个函数中,好吗?

这里是在**train.py**的完整代码,以及导入。

train . py 的代码 —作者提供

注意到结尾的小**WandbCallback**了吗?它的行为就像 Keras 中的一个典型回调,将有关训练时期的每个细节传递给 wandb 接口,以便跟踪它们。

好吧,看起来不错。现在,我们可以继续构建**main.py**来使用不同的超参数作为参数运行我们的模型。这是我们所有的"**args**"对象发挥作用的地方。

让我们从定义一个参数解析器对象开始:

import argparse # build in Python module for just the thing!parser = argparse.ArgumentParser()

让我们为我们的模型运行提供一个项目名称,好吗?

parser.add_argument("-p","--project_name",type=str,default= "fashion_mnist")

既然您已经看到了如何使用解析器添加/读取额外的参数,那么很容易看到我们如何以类似的方式指定所有其他的参数(没有双关的意思)。

例如,像这样阅读**learning rate**参数:

parser.add_argument("-lr","--learning_rate",type=float,default=LEARNING_RATE,help="learning rate")

诸如此类。整个代码将如下所示:

来自作者的完整 main.py 代码

既然我们已经完成了所有的代码,让我们继续运行所有的代码吧!

执行和跟踪实验

难的部分结束了,容易和好玩的部分从这里开始。

当您第一次尝试运行该脚本时,您将得到以下结果:

作者图片—wandb 终端登录

在这里,我建议您选择(2),为了做到这一点,我建议您在浏览器中导航到 W&B 页面来执行步骤(1)。

一旦成功登录,您就可以用上面的命令执行**main.py**。它会像这样继续下去:

作者图片— 纪元执行

现在,当您的培训正在进行时,转到您打开 W&B 仪表板的浏览器选项卡。我们欢迎你来到这样一个不断更新的屏幕:

作者图片— r 在 W & B 界面中培训期间实时更新指标

您还可以访问实时更新的 CPU/内存利用率:

作者图片— 系统标签

当运行结束时,您将在终端中看到以下屏幕:

作者图片— 培训完成后的终端

注意我们是如何运行 main.py 脚本而不传递任何参数的?这表明我们仍然使用我们在上面的代码中定义的缺省值。

让我们使用通过的两个 参数进行另一次训练运行:

python main.py --learning_rate 0.015 --batch_size 64

这些将覆盖我们提供的默认设置。现在,您的 W & B 仪表盘将显示您刚刚完成的两次训练跑步的对比:

作者图片— 两次训练的总结

您现在有了一个比较表,其中包含了您已经执行的所有运行中的每一次运行中使用的指标和相关特性,如下所示:

作者图片— 所有运行对比表

随着您的团队的成长,您的模型变得越来越复杂,同时还被部署到生产中,记录所发生的一切变得至关重要。

这看起来是一个很好的起点。

几句临别赠言…

感谢您的阅读。

我希望这篇简短的教程是一个很好的方法,可以为一个简单但有用的项目选择这个非常方便的工具。我建议您继续尝试使用“**Sweeps**”权重和偏差功能来运行任何超参数调整管道,如本项目中的网格搜索。这可能是一个很好的学*途径,让你更加熟悉这个平台和它的易用性。

下次见,关注我,不要错过我的下一篇文章

我还建议成为一名中等会员,以便不要错过我每周发表的任何数据科学文章。在此加入👇

https://ipom.medium.com/membership/

我的另外几篇文章你可能会感兴趣:

</31-datasets-for-your-next-data-science-project-6ef9a6f8cac6> </26-github-repositories-to-inspire-your-next-data-science-project-3023c24f4c3c>

生存分析模型选择指南

原文:https://towardsdatascience.com/a-guide-to-model-selection-for-survival-analysis-2500b211c733

如何检查和评估事件时间数据的模型

m.Unsplash 上拍照

为感兴趣的事件时间数据选择合适的模型是生存分析中的重要步骤。

不幸的是,由于生存分析可用的模型数量庞大,很容易被所有可用的信息淹没。

虽然仔细研究每一种建模方法可能很有诱惑力,但让自己沉浸在统计术语中只会妨碍找到最佳模型的努力。

在这里,我们探讨了生存分析中的一些流行模型,然后讨论了在确定感兴趣的事件时间数据的最佳模型时应该考虑的因素。

关键术语

在介绍任何模型之前,熟悉一些在生存分析中感兴趣的指标是很重要的。

  1. 生存功能

生存函数由 S(t)表示,其中:

生存功能(由作者创建)

它显示了受试者存活(即未经历该事件)超过时间 t 的概率。这是一个非递增函数。

2。危险功能

风险函数,也称为风险率,用 h(t)表示,其中

危险函数(由作者创建)

它显示了事件在时间 t 发生的瞬时概率,假设该事件尚未发生。危险函数可以从生存函数中导出,反之亦然。

3。累积危险函数

累积风险函数是一个非递减函数,它显示在时间 t 发生的事件的总累积风险。在数学术语中,它是风险函数下的面积。

4。危险比率

危险比是两组之间危险率的比率。这是对协变量如何影响研究中受试者生存期的量化测量。

选择正确的模型

有许多模型可以用于生存分析。

但是,每种型号在以下方面都是独一无二的:

  • 他们做出的假设
  • 他们提供的信息

因此,选择正确的模型可以归结为确定一个模型,该模型做出适合事件发生时间数据的假设,并使用户能够通过后续分析获得所需的信息。

生存分析中的流行模型

在可用于分析事件发生时间数据的众多模型中,有 4 个模型最为突出:Kaplan Meier 模型、指数模型、Weibull 模型和 Cox 比例风险模型。

在下面的演示中,将使用 Python 的生命线模块探索每种建模方法。这些模型将用于检查内置数据集中的事件发生时间数据。

下面是数据集的预览:

代码输出(由作者创建)

week栏显示存活持续时间,而arrest栏显示事件(即停搏)是否已经发生。

1 -卡普兰迈耶模型

Kaplan-Meier 模型可以说是生存分析中最著名的模型。

它被归类为非参数模型,这意味着它不假设数据的分布。它只利用提供的信息生成一个生存函数。

Kaplan Meier 模型使用以下公式计算生存函数:

生存功能(由作者创建)

这是根据事件发生时间数据构建的卡普兰·迈耶曲线:

代码输出(由作者创建)

不同组的生存函数可以用对数秩检验进行比较,这是一种非参数假设检验。

作为一个例子,我们可以使用logrank_test方法比较有和没有经济援助的受试者的生存功能,以查看经济援助是否影响生存持续时间。

代码输出(由作者创建)

卡普兰迈耶模型的好处是直观,易于解释。由于它很少做潜在的假设,这个模型经常被用作生存分析的基线。

不幸的是,由于模型的最小复杂性,很难从中得出有意义的见解。它不能用于比较不同组之间的风险,也不能用于计算风险比等指标。

双指数模型

指数模型是生存分析中另一个流行的模型。与 Kaplan Meier 模型不同,它是一个参数模型,这意味着它假设数据符合特定的分布。

指数模型的生存函数由以下公式导出:

生存功能(由作者创建)

指数模型的风险率由以下公式得出:

危险函数(由作者创建)

指数模型假设风险率为常数。换句话说,在整个观察期内,相关事件发生的风险保持不变。

下面是根据指数模型绘制的生存曲线:

为了确定危险度量,可以计算该分布中的λ值:

代码输出(由作者创建)

用户还可以直接获得具有生命线程序包中提供的属性的风险度量,该程序包在数据框中报告风险率和累积风险函数:

代码输出(由作者创建)

基于输出,危险率保持不变,这符合指数模型的性质。

总的来说,指数模型提供了生存函数和风险函数的大量信息。此外,它还可以用来比较不同群体的风险率。

但是,它强烈假设风险率在任何给定时间都是恒定的,这可能不适合感兴趣的事件发生时间数据。

3 -威布尔模型

另一个可以考虑的参数模型是威布尔模型。

威布尔模型中的存活率由以下公式确定:

生存功能(由作者创建)

威布尔模型中的危险率由以下公式确定:

危险率(由作者创建)

威布尔模型中的数据分布由两个参数决定:λ和ρ。

λ参数表示 63.2%的受试者经历该事件所需的时间。

ρ参数表示危险率是增加、减少还是保持不变。如果ρ大于 1,则危险率不断增加。如果ρ小于 1,则危险率不断降低。

换句话说,威布尔模型假设危险率的变化是线性的。危险率可以总是增加,总是减少,或者保持不变。但是,危险率不能波动。

下面是根据威布尔模型绘制的生存曲线:

代码输出(由作者创建)

生命线程序包可用于获取λ和ρ参数:

代码输出(由作者创建)

由于ρ值大于 1,所以该模型中的危险率总是增加的。

我们可以通过推导风险率和累积风险函数来证实这一点。

与指数模型相似,威布尔模型能够计算生存分析中的许多相关指标。

然而,其结果是基于一个强有力的假设,即风险率随时间呈线性变化,这可能不适合所讨论的事件时间数据。

4 - Cox 比例风险模型

虽然指数模型和威布尔模型可以评估协变量,但它们只能单独检查每个协变量。

如果目标是进行生存分析,一次检查多个变量的事件发生时间数据,Cox 比例风险模型(也称为 Cox 模型)可能是更好的选择。

Cox 模型实现了生存回归,这是一种针对生存期回归协变量的技术,以深入了解协变量如何影响生存期。

该模型被归类为半参数,因为它结合了参数和非参数元素。

它基于以下危险率公式:

危险率公式(由作者创建)

与风险率遵循固定模式的参数模型相反,Cox 模型允许风险率波动。

然而,该模型依赖于比例风险假设。它假设各组之间的危险比保持不变。换句话说,无论受试者的风险率在观察期间如何变化,一组相对于另一组的风险率将始终保持不变。

生命线模块允许用户可视化基线生存曲线,该曲线说明了当所有协变量都设置为中值时的生存函数。

代码输出(由作者创建)

此外,Cox 模型可以用生存回归来量化协变量和受试者生存持续时间之间的关系强度。生命线模块中的summary方法显示了回归的结果。

输出提供了很多信息,但是最深刻的见解可以从exp(coef)列和p列(p 值)中获得。

p 值表示哪些协变量对生存期有显著影响。基于这些结果,finageprio协变量是决定生存期的统计显著预测因子,因为它们的系数的 p 值很小。

此外,每个协变量的风险比相当于 e 的协变量系数(eᶜᵒᵉᶠ)的幂,这已经在exp(coef)栏中提供。

例如,代表财政援助的fin协变量的 eᶜᵒᵉᶠ值是 0.68。

从数学上讲,这可以解释如下:

鳍的危险比率(由作者创建)

这意味着获得经济援助会使风险率降低 0.68 倍(即降低 32%)。

当然,只有当 Cox 模型的比例风险假设符合事件发生时间数据时,它才适用。为了证明模型的使用,可以使用check_assumptions方法来测试这个假设。

如何挑选合适的型号

当然,除了这四个模型之外,还有许多其他模型值得考虑,例如对数逻辑模型和加速故障时间模型(AFT)。因此,您可能会发现自己有太多的型号可供选择。

为了简化对最佳模型的搜索,请考虑以下因素:

  1. 目标

你应该决定的第一件事是你想从你的生存分析中获得什么信息。有了明确的目标,就更容易找到理想的模式。

2。领域知识

可能有多个模型可用于导出所讨论的事件时间数据的期望指标。在这种情况下,利用主题的领域知识可以帮助过滤掉不合适的模型。

例如,如果你正在进行一项生存分析来研究机器故障,你应该考虑到机器容易磨损的事实。由于磨损,机器故障的风险会随着时间的推移而增加。在这种情况下,假设风险率始终不变的指数模型是不合适的,应该不予考虑。

3。模型性能

如果您发现有多个模型有助于实现研究的目标,并且符合主题的领域知识,您可以通过使用评估指标来衡量模型的性能,从而确定理想的模型。

一个合适的度量标准是赤池信息标准(AIC),它是对模型预测误差的估计。较低的 AIC 分数对应于较好的模型。

例如,如果我们在指数模型和威布尔模型之间进行选择来分析事件发生时间数据,我们可以通过计算这两个模型的 AIC 分数来确定更优的模型。

代码输出(由作者创建)

基于 AIC 度量,威布尔模型更适合于分析事件时间数据。

结论

UnsplashPrateek Katyal 拍摄的照片

总而言之,每一个模型在它所做的假设和提供的信息方面都是独一无二的。

如果您了解感兴趣的事件时间数据的性质,并且知道您希望从生存分析中获得什么,那么找到理想的模型应该是一项相对简单的任务。

如果您熟悉生存分析的方法,但想知道为什么它能在时间-事件数据上胜过其他类型的分析,请查看下面的文章:

我祝你在数据科学的努力中好运!

Python 理解指南

原文:https://towardsdatascience.com/a-guide-to-python-comprehensions-4d16af68c97e

PYTHON 编程

学*列表理解(listcomps)、集合理解(setcomps)、字典理解(dictcomps)甚至生成器表达式的复杂性

祖扎娜·鲁特凯Unsplash 上的照片

理解可能是 Python 中最流行的语法糖。它们是如此强大,当我最终理解它们时,它们让我大吃一惊,从那以后我就一直喜欢使用它们。

是的,你说得对。我第一次理解理解,而不是看见它们。这是因为,乍一看,它们似乎不那么可读。许多初学 Python 的人发现它们既不清晰也不容易理解。有些人可能更喜欢for循环;有些人可能更喜欢 [map()](https://medium.com/towards-data-science/does-python-still-need-the-map-function-96787ea1fb05) 功能;还有一些人可能更喜欢其他有用的东西——只是不喜欢理解。

如果你也是其中一员,我希望这篇文章能让你相信理解在不同的情况下可以派上用场,有时比 Python 提供的任何其他工具都更具可读性。如果你不在其中,我希望这篇文章能帮助你更多地了解理解是如何工作的;这可以帮助你理解和创造简单和高级的理解。此外,我们将讨论理解的一个重要方面:当理解越过太难的界限时,在这种情况下你应该放弃使用它。有时候,为了做到这一点,您必须能够说服自己放弃某个特定的理解,这个理解是如此高级,以至于使用它可能会显示您是一个有经验的 Python 程序员。

这是因为在某些情况下,不同的工具可以做得更好。我们将深入挖掘理解的实用性,我希望你不仅能从这篇文章中学到东西,还能喜欢阅读它。我会试着写下你需要知道的关于他们的一切,但这并不意味着我会写下关于他们的一切。但是一旦你理解了他们的要点并想学*更多,你就有足够的知识去做。

因此,本文旨在解释什么是 Python 理解,以及如何和何时使用它们。它也提供了理解和使用它们的一些重要的复杂性。

我还会介绍一个新名词,Python 的不解。虽然基于 Python 的通用代码库充满了理解和不理解,但你应该争取合理数量的前者,而不是后者。

理解入门

关于理解,我想让你理解的第一件事是单词理解的意思。虽然我在学* Python 理解之前已经知道这个词的意思很多年了,但是我没能把这个意思放到 Python 的上下文中。剑桥词典理解一词给出了如下定义:

完全理解和熟悉情况、事实的能力…

现在,这是我想让你记住的事情——这正是 Python 理解所做的事情:它们帮助理解代码正在做的动作。所以,你可以用另一种方式做这个动作,但这个动作是通过理解来完成的,它有助于理解正在发生的事情。这对他们的作者和读者都有帮助。

尽管如此,要注意 不理解 这个词,是理解的反义词。虽然 Python 不提供不解,但当我们过度理解并使其不可读时,它们就会变成不解。下图介绍了 Python 不解这个术语,就是一个 Python 的领悟变成了一个无法理解的东西;也就是说,与理解存在的目的相反的东西:帮助理解代码正在做的动作。相反,不理解使得理解代码要做什么变得困难。

一句 Python 不解,意思是“不要试图理解我!”。图片作者。

我们将讨论如何让我们的理解变得可理解,而不是变得不可理解;如何把它们变成 Python 的不解;因此,反过来,如何使你的代码 Pythonic 化,简单易懂。

我将理解视为语法糖,使我能够使用更简洁的代码而不是不同的编码工具,如for循环,来创建列表、字典、集合或生成器表达式。如你所见,你可以使用理解来创建四种不同类型的对象。在继续之前,让我们分析一个简单的例子,类似于您可能在许多其他资源中看到的例子。

在第一个例子中,我将向您展示如何使用最简单的理解类型来创建一个列表、一个集合、一个生成器表达式和一个字典。稍后,我们将主要使用列表或字典,这取决于我们将要讨论的内容。生成器表达式虽然使用相同的语法-糖语法创建,但提供了完全不同的行为,所以我将把它们留到另一篇文章中,专门讨论它们以及它们与列表理解的比较。

列表理解(又名列表组件)

假设在 Python 中没有理解,我们想做以下事情。我们有一个数值列表,比如说x,我们想要创建一个包含平方值x的列表。让我们开始吧:

>>> x = [1, 2, 3]
>>> x_squared = []
>>> for x_i in x:
...     x_squared.append(x_i**2)
>>> x_squared
[1, 4, 9]

现在让我们回到现实:Python 确实提供了列表理解。我们可以用更短、更容易理解的代码达到同样的效果:

>>> x = [1, 2, 3]
>>> x_squared = [x_i**2 for x_i in x]
>>> x_squared
[1, 4, 9]

这里的列表理解为[x_i**2 for x_i in x]。可以这样读:对x中的每个值计算x_i**2,并以列表形式返回新值。

为了比较两种方法的长度,让我来定义理解率:

n_for / n_comprehension

其中n_for代表for循环中的字符数,而n_comprehension代表相应理解中的字符数。为了公平起见,我将只使用一个字符的名称。该比率可以用百分比表示。

理解率显示理解代码与for循环相比有多短。警告:这只代表了故事的一个方面:理解的简洁。虽然对于某些示例来说,这种简洁是一件好事,但在其他一些示例中却不是,因为代码可能会变得太难理解。我们无论如何都会使用这个比率,这样你就可以知道与for循环相比,理解的时间有多短。此外,有时另一个编码工具可以比for循环做得更好,例如[map()](/does-python-still-need-the-map-function-96787ea1fb05)函数

在这个简单的例子中,最初的for循环需要 26 个字符(不含空格);列表理解,15 个字符。为了说明这一点,我计算了以下文本中的字符:

for loop:
y=[]foriinx:y.append(i**2)

list comprehension:
y=[i**2foriinx]

在这个例子中,理解率是 173%。还不错!

集合理解(又名集合组合)

>>> x = [1, 2, 3]
>>> x_squared = set()
>>> for x_i in x:
...     x_squared.add(x_i**2)
>>> x_squared
{1, 4, 9}

下面是相应的集合理解:

>>> x = [1, 2, 3]
>>> x_squared = {x_i**2 for x_i in i}
>>> x_squared
{1, 4, 9}

理解率为 186%。

字典理解(又名字典理解,字典组合)

>>> x = [1, 2, 3]
>>> x_squared = {}
>>> for x_i in x:
...     x_squared[x_i] = x_i**2
>>> x_squared
{1: 1, 2: 4, 3: 9}

一句名言理解成了现在:

>>> x = [1, 2, 3]
>>> x_squared = {x_i: x_i**2 for x_i in x}
>>> x_squared
{1: 1, 2: 4, 3: 9}

它的理解率比 listcomps 和 setcomps 低,为 124%。

生成器表达式

你可能想知道,术语生成器表达式与理解有什么关系?应该是类似发电机领悟的东西吧?

有效的观点。我想…我想可能是。对我来说,这很有意义。但我们就是这么叫的:生成器表达式。它们在这里是因为生成器表达式是使用与其他理解相同的语法糖创建的,尽管它们的行为非常不同。我不会讨论这种行为上的差异,因为这个话题太重要了,不能藏在一篇关于理解的普通文章里。因此,我在这里只展示生成器表达式,并保证以后会写更多关于它们的内容。

要创建一个生成器表达式,只需将 listcomp 代码放在方括号([])内,并用括号(())括起来:

>>> x = [1, 2, 3]
>>> x_squared = (x_i**2 for x_i in x)

这里,x_squared是一个基于列表创建的生成器表达式。很容易看出,生成器表达式的理解率与相应的列表理解率相同。

扩展理解

上面的理解是最简单的,因为它们包含了对 iterable 的每个元素的操作。我说的操作是指理解的这一部分:x_i**2;不管我们还在对x_i做什么——请看下图,它解释了什么是操作。我们可以扩展这一部分,但不仅仅是这一部分;有很多扩展 Python 理解的可能性,这就是这个工具的强大之处。

列表理解的结构。图片作者。

下面的列表显示了我们如何扩展最简单的理解。我们可以使用

  • 操作中的一个或多个功能
  • 使用if语句对原始数据进行一个或多个过滤
  • 操作输出的一个或多个过滤器,使用if语句
  • 在操作中使用条件表达式,以对原始数据使用一个或多个过滤器;或者对操作的输出使用一个或多个过滤器
  • 使用高级循环
  • 以上各项的组合

这就是事情变得复杂的地方,我们的任务是确保我们的理解不会变成不理解。在讨论原因之前,我们先来看一下上述场景的例子。分析每一个例子,如果可能的话,在你的 Python 解释器中运行它,特别是当你对 Python 理解还是新手的时候。

上面的分类旨在帮助你理解理解是如何工作的。这不是正式的,老实说,你甚至不需要记住它。我用它来告诉你理解是多么的不同和强大。所以,分析例子,理解它们,如果你认为它们能帮助你,试着记住它们。

在操作中使用函数

>>> def square(x):
...     return x**2
>>> x = [1, 2, 3]
>>> x_squared_list = [square(x_i) for x_i in x]
>>> x_square_list
[1, 4, 9]
>>> x_squared_dict = {x_i: square(x_i) for x_i in x}
{1: 1, 2: 4, 3: 9}

例如,将[square(x_i) for x_i in x]理解为:为x中的每个值计算square(x_i),并将结果作为列表返回。

上面,我们使用了一个函数。我们可以使用更多:

>>> def multiply(x, m):
...     return x*m
>>> def square(x):
...     return x**2
>>> x = [1, 2, 3]
>>> x_squared_list = [multiply(square(x_i), 3) for x_i in x]
>>> x_squared_dict = {x_i: multiply(square(x_i), 3) for x_i in x}
>>> x_square_list
[3, 12, 27]
>>> x_squared_dict = {x_i: square(x_i) for x_i in x}
{1: 3, 2: 12, 3: 27}

从现在开始,我将只列出理解。我希望在这一点上你知道不同类型的理解是如何工作的,所以没有必要一遍又一遍地重复它们,那样会弄乱代码。

过滤原始数据

>>> x = [1, 2, "a", 3, 5]
>>> integers = [x_i for x_i in x if isinstance(x_i, int)]
>>> integers
[1, 2, 3, 5]

我们从x开始创建这个列表,只取整数(所以,当x_i的类型是int,也就是if isinstance(x_i, int))。

你看出这个版本和上一个版本有多相似了吗?是因为两者都使用了if语句过滤原始数据;他们只是以稍微不同的方式做它。以前,我们将其添加到操作中;这里,我们在循环之后添加了它。

这个版本和以前的版本过滤原始数据,与我们将在下一点所做的相反,即过滤操作的结果。换句话说,在这里您可以通过首先过滤数据并对过滤的数据应用列表理解来达到同样的目的。在下面的版本中,您将首先使用 list comprehension,然后过滤其值。

过滤操作的输出

>>> x = [1, 2, 3, 4, 5, 6]
>>> x_squared = [x_i**2 for x_i in x if x_i**2 % 2 == 0]

这里,我们只保留那些偶数的结果,拒绝奇数的结果。

注意这段代码并不完美:我们两次运行相同的操作,第一次在操作中,然后在条件表达式中(以if开始)。从 Python 3.8 开始,我们可以使用 walrus 操作符来改进这段代码:

*>>> x = [1, 2, 3, 4, 5, 6]
>>> x_squared = [y for x_i in x if (y := x_i**2) % 2 == 0]*

如您所见,walrus 操作符使我们能够在这个列表理解中节省一半的计算。这就是为什么,如果你想使用比最简单的更高级的理解,你应该和海象算子成为朋友。

为了好玩和学*,我们可以使用 timeit 来测试这两种理解,看看 walrus 操作符是否真的帮助我们提高了 listcomp 的性能。对于基准测试,我使用了下面的性能部分中的代码,代码片段如下:

*setup = """x = list(range(1000))"""
code_comprehension = """[x_i**2 for x_i in x if x_i**2 % 2 == 0]"""
code_alternative = """[y for x_i in x if (y := x_i**2) % 2 == 0]"""*

这是我在我的机器上得到的(32 GB 内存,Windows 10,WSL 1):

*Time for the comprehension : 26.3394
Time for the alternative   : 31.1244
comprehension-to-alternative ratio: 0.846*

显然,使用 walrus 操作符绝对是值得的——尽管性能增益没有达到人们预期和希望的 50%。一旦你知道这个操作符和这个版本的理解是如何工作的,你就可以使代码不仅性能更好,而且更容易理解。

这个理解比前几个稍微难读一点,虽然意思也一样简单:为x中的每个值计算x_i**2,拒绝输出(x_i**2)的所有偶数值;以列表形式返回结果。

正如你所看到的,你需要理解错综复杂的理解来阅读这一篇,但是一旦它们对你不构成问题,这样的理解就变得容易阅读了。然而,正如你稍后会看到的,有些理解一点也不容易读懂…

在运算中使用条件表达式过滤数据

*>>> x = [1, 2, "a", 3, 5]
>>> res = [x_i**2 if isinstance(x_i, int) else x_i for x_i in x]
>>> res
[1, 4, 'a', 9, 25]*

这个列表理解应该这样理解:对于x中的每一个值,使用以下算法:当它是整数时,求平方;否则不要碰它;将结果收集到列表中。

这个版本非常类似于数据过滤;事实上,它是一种特定类型的数据过滤,是作为操作内部(而不是外部)的条件表达式(x if condition else y)来实现的。老实说,你可以使用上面介绍的数据过滤很容易地重写这个列表理解。为了避免混淆,我不会在这里展示它。但是你可以认为这是一个很好的练*:在操作之外,也就是在理解循环之后,使用数据过滤重写这个理解。

**注释:参见下一小节中的注释块,了解什么时候可以使用条件表达式,什么时候最好避免使用。

在运算中使用条件表达式过滤结果

上面的 listcomp 过滤了数据;我们还可以使用条件表达式来过滤操作的结果,如下所示:

*>>> x = [1, 2, 70, 3, 5]
>>> res = [y if (y := x_i**2) < 10 else 10 for x_i in x]
>>> res
[1, 4, 10, 9, 10]*

为了避免两次使用相同的计算,我们再次使用了 walrus 运算符,如下所示:

*>>> res = [x_i**2 if x_i**2 < 10 else 10 for x_i in x]*

虽然这个版本不是不正确的(特别是对于 Python 3.7 和更早的版本),但它不是高性能的。

由于在操作码中添加了条件表达式,所以当条件较短时,它会很有用。否则会弄乱操作码。在这种情况下,最好使用常规过滤,在理解循环之后出现if条件。

使用高级循环

你不必像我们到目前为止所做的那样,只使用简单的循环。例如,我们可以在理解循环中使用enumerate(),就像在for循环中一样:

*>>> texts = ["whateveR! ", "\n\tZulugula RULES!\n", ]
>>> d = {i: txt for i, txt in enumerate(texts)}
>>> d
{1: "whateveR! ", 2: "\n\tZulugula RULES!\n"}*

现在,让我们遍历一下d字典的键值对:

*>>> [(k, v.lower().strip()) for k, v in d.items()]
[(1, 'whatever!'), (2, 'zulugula rules!')]*

基本上,你可以使用任何可以在for循环中工作的方法。

上述场景的组合

上述情景是基本的,通常你不需要考虑这样的理解是否太难;他们不是。然而,情况可能会变得更加困难,尤其是当它包含不止一个正义的操作或过滤时;换句话说,当它结合了上述场景(包括在一次理解中使用同一个场景两次甚至更多次)。

如果你听说过 Python 的理解会变得太复杂,那你一定没听错。这就是为什么你可以经常读到你不应该过度使用理解;这也是事实。这就是我提出术语不理解的原因。你应该绝对避免在你的 Python 代码中使用不理解。

让我们考虑几个例子,这样你就可以自己看到理解会变得困难。虽然不是所有的例子都太复杂——但有些肯定会。

考虑这个 listcomp,它结合了过滤数据和过滤输出:

*>>> x = range(50)
>>> [y for x_i in x if x_i % 4!= 0 if (y := x_i**2) % 2 == 0]
[4, 36, 100, 196, 324, 484, 676, 900, 1156, 1444, 1764, 2116]*

看看我们在这里做了什么:计算所有能被 4 整除的x值的x_i**2,并且只取那些能被 2 整除的结果输出值。

这里我们有两个if条件。在这种情况下,我们可以通过使用and连接两个条件来简化理解:

*>>> [y for x_i in x if x_i % 4!= 0 and (y := x_i**2) % 2 == 0]
[4, 36, 100, 196, 324, 484, 676, 900, 1156, 1444, 1764, 2116]*

老实说,对我来说,两个if块中的两个条件似乎比一个if块中的两个条件用and操作符连接起来更容易理解。如果你不同意,那么如果你认为and有助于增加可读性,请在评论中分享。这个在某种程度上是一个主观的问题,所以我不期望你们所有人都同意我。**

不管你对使用and有什么想法,我们可以通过将它分成几行来提高这种理解的可读性,每一行代表一个块,该块表示意味着某些事情和/或做某些事情:

*>>> [y for x_i in x
...  if x_i % 4 != 0
...  and (y := x_i**2) % 2 == 0]
[4, 36, 100, 196, 324, 484, 676, 900, 1156, 1444, 1764, 2116]*

或者

*>>> [
...     y for x_i in x
...     if x_i % 4 != 0
...     and (y := x_i**2) % 2 == 0
... ]
[4, 36, 100, 196, 324, 484, 676, 900, 1156, 1444, 1764, 2116]*

以至

*>>> [
...     y
...     for x_i in x
...     if x_i % 4 != 0
...     and (y := x_i**2) % 2 == 0
... ]
[4, 36, 100, 196, 324, 484, 676, 900, 1156, 1444, 1764, 2116]*

当我们在第一行应用一个或多个函数时,最后一个版本更有用,所以这一行比一个名字要长,就像这里。

正如我提到的,两个if块对我来说更具可读性:

*>>> [
...     y for x_i in x
...     if x_i % 4 != 0
...     if (y := x_i**2) % 2 == 0
... ]
[4, 36, 100, 196, 324, 484, 676, 900, 1156, 1444, 1764, 2116]*

这个版本中我特别喜欢的是左边if积木的视觉对称性。你看到了吗?如果没有,返回到前面的代码块,并注意这两部分的区别:

*...     if x_i % 4 != 0
...     if (y := x_i**2) % 2 == 0*

这一部分:

*...     if x_i % 4 != 0
...     and (y := x_i**2) % 2 == 0*

虽然这些方面不太重要,但至少对我来说是有帮助的。我的意思是,当我在做一个更长的理解时,我会把它们考虑进去——我想建议你也把这些方面考虑进去。首先,信不信由你,这给了我乐趣。第二,当你确实注意到你理解的这些小方面时,你会觉得你非常了解它们;你会觉得他们已经(或者还没有,当他们没有准备好的时候)准备好了,完成了,完整了。

这种理解并没有那么难,是吗?所以,让我们把情况变得更复杂一点,从而理解列表:

*>>> [
...     y for x_i in x
...     if x_i % 4 != 0 and x_i % 5 != 0
...     if (y := square(multiply(x_i, 3))) % 2 == 0
... ]
[36, 324, 1764, 2916, 4356, 6084, 10404, 12996, 15876, 19044]*

这个更复杂,但是我认为从这个角度来说,即使是这个理解也是非常可读的。然而,随着越来越多的条件被加入,它会变得越来越复杂——最终,在某一点上,越过了太复杂的界限。

备选方案和示例

正如我已经写的,有时候一个for循环比理解更具可读性。然而,还有更多选择,比如map()filter()功能。你可以在本文的中阅读更多关于map()的内容,在这篇文章中,我解释了为什么了解这个函数的工作原理是有好处的,以及它何时可能是比理解更好的解决方案。

这些函数甚至比相应的理解更快,所以每当性能很重要时,你可能希望检查基于map()filter()的代码是否比你使用的理解更好。

在简单的场景中,理解通常更好;在复杂的情况下,情况不一定如此。最好用例子来说明这一点。因此,下面我展示了几个理解的例子(包括生成器表达式)和相应的替代解决方案。

**例 1。map()函数代替了简单的生成器表达式。

*>>> x = range(100)
>>> gen = (square(x_i) for x_i in x)
>>> other = map(square, x)*

在这种情况下,这两个版本对我来说似乎同样可读,但理解看起来更熟悉。map()函数是一个抽象:你需要知道它是如何工作的,哪个参数必须先去(callable ),哪个参数必须后去(iterable)。

不像map(),在我看来,简单的理解,就像上面这个,看起来并不抽象。我可以直接阅读它们,从左到右写:我正在将函数square()应用到x_i,而x_ix的后续元素;我将结果存储在一个generator对象中。

听起来很容易,但是我知道要阅读这样的理解(是的,我确实是这样读的!),你需要知道这个特定的 Python 语法糖。这就是为什么对于高级(甚至中级)Python 程序员来说,这个生成器表达式(以及其他类似的简单理解)看起来很简单,但是对于初级 Python 开发人员来说,它们很少看起来简单。所以,理解也可以是抽象的。然而,我们很快就会看到,这种语法比它的直接替代方法要清晰得多。

当您在map()中使用lambda 函数时,map()函数的使用变得不那么清晰。比较:

*>>> x = range(100)
>>> gen = (x_i**2 for x_i in x)
>>> other = map(lambda x_i: x_**2, x)*

虽然lambda在 Python 中确实有它们的位置并且非常有用,但是在某些情况下它们会降低可读性——这就是这样的情况。在这里,比起map()版本,我更喜欢生成器表达式。

例 2 :使用filter()函数代替带有if块的生成器表达式进行数据过滤。

*>>> x = range(100)
>>> gen = (x_i for x_i in x if x_i % 5 == 0)
>>> other = filter(lambda x_i: x_i % 5 == 0, x)*

以上所有关于map()的注释都适用于filter()功能。和map()一样,可以带(像这里)也可以不带lambda使用。这里,我还是更喜欢发电机的说法。

例 3 :结合map()filter()代替使用函数处理元素和if块过滤数据的生成器表达式。

*>>> x = range(100)
>>> gen = (square(x_i) for x_i in x if x_i % 5 == 0)
>>> other = map(square, filter(lambda x_i: x_i % 5 == 0, x))*

您可以分两步完成同样的操作:

*>>> x_filtered = filter(lambda x_i: x_i % 5 == 0, x)
>>> other = map(square, x_filtered)*

这是结合map()filter()功能来处理和过滤数据的情况之一。但这也是我需要一些时间来理解这段代码在做什么的情况之一,同时能够几乎立即理解相应的理解(这里是一个生成器表达式)。在这里,我肯定会选择生成器表达式版本。

例 4:map()带包装器功能代替字典理解。

字典理解使得创建字典非常简单。考虑这个例子:

*>>> def square(x):
...     return x**2
>>> y = {x: square(x) for x in range(5)}
>>> y
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}*

这看起来很简单,不是吗?现在尝试使用map()达到同样的效果。

问题是你需要创建键值对,所以,如果我们想用map(),函数返回输出是不够的;它也需要返回密钥。一个好的解决方案是在square()周围使用一个包装函数:

*>>> def wrapper_square(x):
...     return x, square(x)
>>> y = dict(map(wrapper_square, range(5)))
>>> y
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}*

你同意上面的字典理解比这个基于 T6 的版本可读性更好,更容易写,更容易读吗?这里,for循环比map()版本更容易理解:

*>>> y = {}
>>> for x in range(5):
...     y[x] = square(x)
>>> y
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}*

例 5 :一个for循环,用于过滤数据和过滤输出。

让我们回到我们实现了一个相当复杂的 listcomp 的例子:

*>>> [
...     y for x_i in x
...     if x_i % 4 != 0 and x_i % 5 != 0
...     if (y := square(multiply(x_i, 3))) % 2 == 0
... ]
[36, 324, 1764, 2916, 4356, 6084, 10404, 12996, 15876, 19044]*

相应的for循环如下所示:

*>>> output = []
>>> for x_i in x:
...     y = square(multiply(x_i, 3))
...     if y % 2 == 0 and x_i % 4 != 0 and x_i % 5 != 0:
...         output.append(y)
>>> output
[36, 324, 1764, 2916, 4356, 6084, 10404, 12996, 15876, 19044]*

实际上,没那么糟。我认为这是一种边缘情况,有些人仍然会选择 listcomp 版本,而其他人会认为它不可读,而选择for循环。对我来说,listcomp 仍然很好,但我很确定对一些人来说,它已经越过了太难的界限。

例 6 :拆分 listcomp。

让我们用和上面完全一样的例子。让我们试着做一些我不太喜欢的事情;也就是说,让我们将这个复杂的理解分成几个部分,希望这样可以简化代码:

*>>> y_1 = [x_i for x_i in x]
>>> y_2 = [
...     y_1_i
...     for y_1_i in y_1
...     if y_1_i % 4 != 0 and y_1_i% 5 != 0
... ]
>>> y = [
...     y_final
...     for y_2_i in y_2
...     if (y_final := square(multiply(y_2_i, 3))) % 2 == 0
... ]
>>> y
[36, 324, 1764, 2916, 4356, 6084, 10404, 12996, 15876, 19044]*

尽管进行了拆分,但在我看来,这个版本甚至比之前的版本可读性更差。它不仅长得多;对我来说,它的可读性也更差。我不会选择这个,但是我想给你看一下,以防你想知道这样的方法是否可行。这一次,不会了。

例 7 :使用函数简化运算和过滤。

有时,您可以通过将一些要完成的工作导出到外部函数来简化理解。在我们的例子中,我们可以通过操作和过滤器来实现:

*>>> def filter_data(x_i):
...     return x_i % 4 != 0 and x_i % 5 != 0
>>> def filter_output(y_i):
...     return y_i % 2 == 0
>>> def pipeline(x_i):
...     return square(multiply(x_i, 3))
>>> def get_generator(x):
...     return (
...         y for x_i in x
...         if filter_output(y := pipeline(x_i))
...         and filter_data(x_i)
..      )
>>> list(get_generator(x))
[36, 324, 1764, 2916, 4356, 6084, 10404, 12996, 15876, 19044]*

我认为这使得理解更容易阅读。然而,我们可以用for循环做同样的事情:

*>>> output = []
>>> for x_i in x:
...     y = pipeline(x_i)
...     if filter_output(y) and filter_data(x_i):
...         output.append(y)
>>> output
[36, 324, 1764, 2916, 4356, 6084, 10404, 12996, 15876, 19044]*

但是这个for循环比下面对应的生成器好吗(从上面的片段复制过来的)?

*>>> (
...     y for x_i in x
...     if filter_output(y := pipeline(x_i))
...     and filter_data(x_i)
... )*

我会选择 listcomp。它更简洁,看起来可读性更强,至少对我来说是这样。

命名

这是一个提到命名的好时机。再看一遍以上理解中使用的函数名:filter_datafilter_outputpipeline。这些都是清楚的名字,这些名字告诉他们该负责什么;这样,他们应该帮助我们理解在理解中发生了什么。

也许有些人会说它们不是函数的好选择,因为理解应该很短,而更短的函数名将有助于我们写出更简洁的理解。他们只有一点是对的:理解会更简洁。但是写简短的理解并不意味着写好的因为可读的理解。将上面的理解与下面的进行比较,在下面的理解中,我使用了更短但同时意义更小的名称:

*>>> def fd(x_i):
...     return x_i % 4 != 0 and x_i % 5 != 0
>>> def fo(y_i):
...     return y_i % 2 == 0
>>> def pi(x_i):
...     return square(multiply(x_i, 3))
>>> def g_g(x):
...     return (y for x_i in x if fo(y := pi(x_i)) and fd(x_i))
>>> list(get_generator(x))
[36, 324, 1764, 2916, 4356, 6084, 10404, 12996, 15876, 19044]*

肯定更短——也肯定更糟。

注意,虽然这样理解的作者会知道fd代表filter_datapi代表pipeline,但是其他人会知道吗?一个月后作者还会记得吗?还是一年?我不这么认为。因此,这是一个糟糕的命名,它没有函数所负责的真正含义,并且它的秘密代码(fo代表filter_output)将很快被遗忘。

如果你决定在理解中使用外部函数,一定要记住使用好的名字——好的含义简短而有意义的。这同样适用于理解中使用的所有名称。然而,在这里,我们应该使用尽可能短的名字,而不是更长。有一个不成文的规则,当一个变量有一个狭窄的范围,它不需要一个长名字。虽然这条规则并不总是合理的,但在理解中它往往是合理的。例如,考虑这些列表组合:

*>>> y = [square(x_i) for x_i in x if x_i % 2 == 0]
>>> y = [square(xi) for xi in x if xi % 2 == 0]
>>> y = [square(p) for p in x if p% 2 == 0]
>>> y = [square(element_of_x) for element_of_x in x if element_of_x % 2 == 0]*

前两个对我来说几乎一样好,尽管我会选择第一个。这是因为x_i看起来像带下标ixxi看起来不是那样。所以,我准备用额外的_字符来连接xi,来表达这个额外的意思。这样,x_i在数学上看起来有点像x_i,意味着x_i属于x。虽然xi版本看起来非常相似,但它错过了数学方程的这种良好的相似性。因此我选择了前者。

我不喜欢第三个版本。为什么使用p作为循环变量的名称?p是什么意思?如果从我们实现的算法的角度来看,这意味着什么,那很好。但除此之外,就不好了。基本上,使用有意义的名字是好的——但是记住在理解中,你应该使用有意义的简称。**

在理解中,你应该使用有意义的简短名字。

这也是我不喜欢第四版的原因。虽然名称element_of_x确实具有正确的含义,但该名称不必要太长。不要用长名字,因为短名字同样能提供信息。这里的情况是:x_ielement_of_x短得多,至少和element_of_x一样信息量大,如果不是更多的话——这要归功于x_i与数学方程式的共鸣。

还有一件事。当使用循环变量时,使用_作为其名称。例如:

*>>> template = [(0, 0) for _ in range(10)]*

这里不需要使用i或任何我们想要的东西。

太复杂还是还没?

随着时间和经验的积累,你会发现有时候很难决定一个特定的理解是否仍然可以理解,或者是否已经达到了太难的界限。如果你不知所措,选择for循环会更安全,因为几乎每个人都能理解它——虽然不是每个人都能理解复杂的列表组件。

然而,请记住,要尽可能使理解变得简单。例如,我们通过将一些或所有的计算转移到命名良好的外部函数来实现(在上面的例子中,这些函数是pipeline()filter_data()filter_output())。但是,不要过度使用这种方法,因为它比纯粹的理解更罗嗦。更重要的是,定义只在代码中使用一次的函数并不是一个好主意。

关键是,任何时候你写了一个看起来复杂的理解,你应该分析它,并决定它是否足够容易理解。如果没有,用不同的方法替换它。有时基于函数组合的生成器管道可能是一个很好的解决方案,正如这里提出的。

决定哪个理解太复杂并不总是容易的。操作的数量不一定是一个好的指标,因为有时长的理解可能比短的理解更容易理解,这取决于它的目标和使用的命名。

还有一点需要记住的是,一个难理解的意思是对一般开发者来说很难,对你来说并不难。所以,如果你写了一篇非常复杂的理解文章,而且你理解起来没有问题,这并不意味着你应该去做。这可能是你理解它的原因,因为你花了三个小时写了这几行代码!因此,记住要让你的理解——就此而言,还有代码——对其他人来说也是可理解的。

变量作用域

变量作用域在理解中的工作方式使它们不同于for循环——不同之处在于:理解中使用的循环变量在理解的外部作用域中不可见,也不会覆盖外部作用域中的变量

用一个例子来解释会更容易些:

*>>> x = [i**2 for i in range(10)]
>>> x
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> i
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'i' is not defined*

你看到发生了什么吗?虽然我们在列表理解中使用了i作为循环变量,但是它只在这个理解的范围内有效。在这个范围之外它是不可见的——一旦理解完成了列表的创建,它就被删除了。

**理解中使用的循环变量在理解的外部作用域中不可见,也不会覆盖外部作用域中的变量

这还不是全部。如下面的代码片段所示,即使在外部作用域中使用了同名的变量,也可以在理解中使用名称。所以,会同时出现两个不同的同名对象!但是这些名称只在它们的作用域中可见。

*>>> i = "this is i"
>>> x = [i**2 for i in range(10)]
>>> x
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> i
'this is i'*

当然,如果循环变量有相同的名字,你不能在理解中使用外作用域变量。换句话说,您无法从使用内部作用域i变量进行循环(或任何其他操作)的列表理解内部访问外部作用域i

**警告:记住这条规则不适用于使用walrus 运算符赋值的变量:

*>>> i = "this is i"
>>> x = [i for j in range(10) if (i := j**2) < 10]
>>> x
[0, 1, 4, 9]
>>> i
81*

如您所见,使用 walrus 操作符将赋值变量(这里是i)放在理解的外部范围内。

再警告一次。不要过度使用这个特性,因为生成的代码会变得难以阅读,如下所示:

*>>> x = range(10)
>>> x = [x**2 for x in range(10)]
>>> x
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]*

这太过分了。

这种处理变量范围的方法使得使用理解变得安全,因为你不必担心理解的循环变量会覆盖一个重要的变量。如上所述,这是而不是循环如何工作:

*>>> i = "this is i"
>>> for i in range(3):
...     print(i)
...
0
1
2
>>> i
2*

如你所见,如果你在一个for循环中使用相同的名字,在外部作用域中以相同方式命名的变量将被循环变量的当前值覆盖;最终用它的最后一个值。

表演

许多开发人员在决定使用语法糖时考虑的一个方面是性能。有人说没那么重要;也有人说是。

我的意见介于两者之间。请不要嘲笑我,但是我认为性能很重要,当…当性能很重要的时候。当它不存在时,不需要考虑它。

当…当性能很重要时,性能很重要。

即使这听起来有点滑稽,如果不是愚蠢的,这是完全有道理的。如果您的应用程序是否快速并不重要,那么您为什么要担心性能呢?在这种情况下,最好使用其他因素来选择编码风格,比如简洁和可读性。然而,当性能很重要时,尤其是当性能很重要时(例如,在仪表板中),您可以牺牲可读性来优化代码。

另一件重要的事情是开发时间。如果这很重要,那么也许你不应该花整整两天的时间来优化代码,这样可以节省两秒钟的应用程序执行时间。

好了,现在我们知道了什么时候考虑性能,让我们讨论性能理解是什么。一般来说,性能很大程度上取决于数据和理解本身,所以如果性能对特定的理解很重要,你应该对这种理解进行基准测试(如果可能的话)。

在这里,我将向您展示如何使用内置的[timeit](https://docs.python.org/3/library/timeit.html)模块来执行这样的基准测试。你可以从这篇介绍性文章中了解更多。您可以使用下面的片段作为模板,来进行您自己的基准测试。然而,在不久的将来,我将写另一篇文章,专门讨论这类基准测试。它们将被设计成能让我们对理解的表现得出深刻的结论。

*# benchmark_comprehension.py
import timeit

def benchmark(code_comprehension: str,
              code_alternative: str,
              setup: str,
              n: int,
              rep: int) -> None:
    t_comprehension = timeit.repeat(
        code_comprehension,
        setup=setup,
        number=n,
        repeat=rep
    )
    t_alternative = timeit.repeat(
        code_alternative,
        setup=setup,
        number=n,
        repeat=rep
   )
    print(
        "Time for the comprehension :"
        f" {round(min(t_comprehension), 4)}"
        "\nTime for the  alternative : "
        f"{round(min(t_alternative), 4)}"
        "\ncomprehension-to-alternative ratio: "
        f"{round(min(t_comprehension)/min(t_alternative), 3)}"
    )

# timeit settings
n = 100
rep = 7

# code
setup = """pass"""
code_comprehension = """
for _ in [x**2 for x in range(1_000_000)]:
    pass
"""
code_alternative = """
for _ in map(lambda x: x**2, range(1_000_000)):
    pass
"""

if __name__ == "__main__":
    benchmark(code_comprehension, code_alternative, setup, n, rep)*

为了运行这段代码,使用下面的 shell 命令(在 Windows 和 Linux 中都有效):

*$ python benchmark_comprehension.py*

这将运行基准并将结果打印到控制台。例如,这个在我的机器上提供了以下输出:

*Time for the comprehension : 20.3807
Time for the alternative   : 19.9166
comprehension-to-alternative ratio: 1.023*

您可以更改代码片段的以下部分:

  • 设置代码→改变setup;这是在运行基准代码之前运行的代码。**
  • 代码→改变code_comprehensioncode_alternative;注意代码是用三重引号写的,所以你可以把它拆分成更多的行;但是,您也可以使用一行程序。
  • 传递到timeit.repeat()的重复次数→改变rep
  • 运行代码的次数,传递到timeit.repeat() →改变n

(如果您不确定numberrepeat参数在timeit.repeat()中是如何工作的,您将在这篇文章中找到这一点以及更多内容。)

我使代码尽可能简单,这就是为什么我没有使用命令行参数;只是一个简单的基准测试应用程序。当然,你可以随心所欲地改进它。

我们应该根据什么来衡量理解?

这可能是我们在进行此类基准测试之前需要问的最重要的问题。明显但不令人满意的答案是,反对最佳对应方法;它不能令人满意,因为它没有真正回答问题。让我们考虑一下。

  • 生成器表达式

很容易决定我们应该对哪些生成器表达式进行基准测试:map()filter()和/或它们的组合,或者反对任何其他返回生成器的方法。这两个函数分别返回mapfilter对象,它们是生成器,就像生成器表达式一样。

  • 列举理解

一个显而易见的比较方法是for循环。我们需要记住,在简单的场景中,列表理解将比相应的for循环更具可读性;然而,在复杂的场景中,通常情况相反。

另一种方法是使用map()和/或filter(),随后使用list()。然而,在这种情况下,请注意这不是一个自然的比较,因为当您需要一个列表时,您使用的是列表理解,而不是生成器理解。

  • 词典释义

像上面一样,我们有两个最自然的版本:for循环和map()和/或filter()函数。然而,这次使用这两个函数有点棘手,因为我们需要使用键值对,所以函数也必须返回它们。我已经在上面展示了如何使用包装函数来实现这一点。然而,事实是,在这方面,字典理解在这里更容易使用。

  • 集合理解

对于集合,情况几乎和列表一样,所以使用上面的建议。

一些基准

如前所述,我们甚至不会试图在这里分析理解的表现,因为对于它们是否比它们的替代品更有表现的问题,没有一个简单的答案。答案取决于上下文。然而,我想在使用理解的这个方面给你们一些启发——至少向你们展示一些基础。

在下面的例子中,我将提供

  • 分别作为numberrepeat传递给timeit.repeat()的参数nrep
  • 上面提供的基准测试代码片段中的setupcode_comprehensioncode_alternative;和
  • 输出,如前面提供的示例中的格式。

基准 1

*n = 1_000_000
rep = 7

# code
length = 10
setup = """pass"""
code_comprehension = f"""y = [x**2 for x in range({length})]"""
code_alternative = f"""
y = []
for x_i in range({length}):
    y.append(x_i**2)
"""*
*Time for the comprehension : 1.6318
Time for the  alternative : 1.7296
comprehension-to-alternative ratio: 0.943*

length = 100(对于100元素列表也是如此)和n = 100_000的结果:

*Time for the comprehension : 1.4767
Time for the  alternative : 1.6281
comprehension-to-alternative ratio: 0.907*

现在对于length = 1000(【T4 元素】列表)和n = 10_000:

*Time for the comprehension : 1.5612
Time for the  alternative : 1.907
comprehension-to-alternative ratio: 0.819*

最后,对于length = 10_000(10_000 元素列表)和n = 1000:

*Time for the comprehension : 1.5692
Time for the  alternative : 1.9641
comprehension-to-alternative ratio: 0.799*

在输出中,要查看的最重要的元素是比率,因为它是无单位的,而前两行中的时间不是,这取决于number(在我们的例子中是n)。如您所见,构造的列表的长度有所不同:列表越长,for循环相对越慢。

基准 2

我们将定义一个函数length(x: Any) -> int,它

  • x为空可迭代或None时返回0
  • 返回可以确定的对象的长度;和
  • 否则返回1

因此,例如,一个数字的长度为1。我们将使用这个函数创建一个字典

  • 键是来自 iterable 的值;和
  • 如上所述,值是对象长度的元组;以及 iterable x中该元素的计数。
*setup = """
def length(x):
    if not x:
        return 0
    try:
        return len(x)
    except:
        return 1

x = [1, 1, (1, 2, 3), 2, "x", "x", 1, 2, 1.1,
     "x", 66, "y", 34, 34, "44", 690.222, "bubugugu", "44"]
"""
code_comprehension = """
y = {el: (length(el), x.count(el)) for el in set(x)}
"""
code_alternative = """
y = {}
for el in set(x):
    y[el] = (length(el), x.count(el))
"""*
*Time for the comprehension : 0.5727
Time for the  alternative : 0.5736
comprehension-to-alternative ratio: 0.998*

当我们制作由 100 个x列表组成的x时,我们得到了comprehension-to-alternative ratio: 1.009。正如我们所看到的,在这种情况下,列表的长度并不重要:两种方法都具有相同的性能——尽管 dict comprehension 要短得多,也很优雅,但是我认为您需要了解 Python comprehensions 才能理解它们。

你需要了解 Python 的理解才能欣赏它们。

需要的时候使用理解

这可能看起来很奇怪,但是即使你不需要结果对象,使用理解也是很有诱惑力的。这是一个例子:

*>>> def get_int_and_float(x):
...     [print(i) for i in x]
...     return [i for i in x if isinstance(i, (int, float))]
>>> y = get_int_and_float([1, 4, "snake", 5.56, "water"])
1
4
snake
5.56
water
>>> y
[1, 4, 5.56]*

注意这个函数的作用:它接受一个 iterable 并过滤数据,通过删除那些不属于intfloat类型的数据。我们在return行中看到的列表理解的这种用法很好。尽管如此,我们还可以看到函数中使用的另一个列表理解:[print(i) for i in x]。这个列表只理解打印所有的条目,结果列表既不被存储也不被使用。那么,它是为了什么而创造的呢?我们需要它吗?当然不是。我们应该使用一个for循环来代替:

*>>> def get_int_and_float(x):
...     for i in x:
...         print(i)
...     return [i for i in x if isinstance(i, (int, float))]
>>> y = get_int_and_float([1, 4, "snake", 5.56, "water"])
1
4
snake
5.56
water
>>> y
[1, 4, 5.56]*

在这个例子中,for循环是一种自然的方法。

这是一个非常简单的例子,可能看起来有点太简单了。那么,让我们考虑另一个例子。想象一个从文件中读取文本,以某种方式处理文本,并将输出(处理后的文本)写入另一个文件的函数:

*import pathlib

def process_text_from(input_path: pathlib.Path
                      output_path: pathlib.Path) -> None:
    # text is read from input_path, as string;
    # it is then processed somehow;
    # the resulting processed_text object (also string)
    # is written to an output file (output_path)

def make_output_path(path: pathlib.Path):
    return path.parent / path.name.replace(".", "_out.")*

重要提示:注意process_text_from()函数不返回任何东西。

接下来,我们需要实现一个函数,它为路径的 iterable 的每个元素运行process_text_from()input_paths:

*from typing import List

def process_texts(input_paths: List[pathlib.Path]) -> None:
    [process_text_from(p, make_output_path(p)) for p in input_paths]*

同样,这是而不是一个全功能的列表理解:我们创建一个既不存储也不使用的列表。如果你看到这样的东西,这是一个暗示,你不应该使用理解。想另一种方法。

这里,再一次,一个for循环似乎是最好的选择:

*def process_texts(input_paths: List[pathlib.Path]) -> None:
    for p in input_paths:
        process_text_from(p, make_output_path(p))*

结论

Python 简单易读,或者至少这是我们被告知的——这也是大多数 python 爱好者对它的看法。简单性和可读性至少部分源于语言提供的语法优势。也许这种句法糖最重要的元素是理解。

我把理解和优雅联系在一起。它们使我们能够编写简洁易读的代码——以及优雅的代码。有时代码很复杂,但通常很简单——至少对那些了解 Python 的人来说是这样。这就是为什么 Python 初学者理解理解有问题,从而欣赏它们;这就是为什么他们一有机会就开始学*。

我把理解和优雅联系在一起。它们使我们能够编写简洁易读的代码——以及优雅的代码。

这是个好主意,因为理解在 Python 代码中随处可见。我无法想象一个不写理解的中级 Python 开发人员,更不用说高级 Python 开发人员了。它们对 Python 来说是如此自然,以至于没有理解就没有 Python。至少我不能——也不想——想象没有它们的 Python。

本文旨在介绍 Python 的理解:列表、字典和集合理解;和生成器表达式。最后一种类型与其他类型完全不同,所以这可能是它的名字如此不同的原因。生成器表达式的语法与其他类型的语法相似,但它们生成生成器。因此,他们应该有自己的文章,我很快就要写了。

这里有一个总结,以及几个关于理解的附加想法:

  1. 使用理解来帮助用户理解代码负责什么。
  2. 当你需要创建一个列表时,使用列表理解,而不是不同类型的理解。同理,当你需要创建一个特定类型的对象时,使用相应的 list comprehension: dictcomp 用于字典,setcomp 用于集合,generator expression 用于生成器。
  3. 有时候,使用一个特别复杂的理解可能很有诱惑力。一些人屈服于这种诱惑,希望通过这种方式,他们可以证明他们可以编写复杂和高级的 Python 代码。不惜任何代价抵制这样的诱惑。而不是炫耀,写清楚代码;当理解不清楚时,无论如何都要抵制住把它保留在代码中的诱惑。永远不要把你的理解变成不理解,就像永远不要放弃可读性一样。
  4. 当你看到理解变得比可读性更复杂时,是时候考虑简化代码了——要么通过简化理解代码,要么通过使用替代解决方案。也许更好的名字会有用?或者把理解拆分成几行?或者,如果你已经这样做了,也许你可以尝试改变这种分裂?如果没有效果,请尝试不同的解决方案。有时一个for循环会工作得最好;有时候是别的东西。
  5. 如果你选择使用理解力,即使你知道它太难了,要知道大多数高级程序员会认为你想炫耀。我知道这有多诱人!我陷入这种诱惑的次数比我愿意承认的要多。如果你意识到这个问题,并努力克服它,随着时间和经验的积累,你会发现它更容易抵挡诱惑。
  6. 在某些情况下,你可以用一个生成器管道来代替一个过于复杂的理解;在这种情况下,你会显示出对 Python 编程相当深刻的理解,不像前一种情况。
  7. 记住理解范围是如何工作的。为你的目的使用它——但是不要过度使用它。
  8. 在理解的时候,注意它的每一个细节。这包括你是否把它分成更多的行;要拆分的行数,以及如何拆分;是用if还是and;理解是否看起来视觉上很好;等等。考虑你理解中的每一个微小的细节可以帮助你更好地理解它们。
  9. 命名很重要,有几个原因:理解是简洁的,因此是微妙的,一个错误可能会弄糟整个理解;它们通常承担很多责任,比如应用一个或多个函数,过滤数据和/或输出,循环遍历一个 iterable 他们需要使用至少一个局部范围的变量(也就是说,一个被限制在理解范围内的变量)。因此,在理解内部和外部如何命名对象是有区别的。使用下面的建议来命名理解中的变量:使用简短且有意义的名字
  10. 当你不需要结果对象时,不要使用理解。

永远不要把你的理解变成不理解,就像永远不要放弃可读性一样。

资源

*

脚注

在 R,情况稍微差一点。内置的Map()函数(以 R 为基数)将一个函数作为第一个参数,将一个向量作为第二个参数:

> Map(function(x) x^2, 1:3)
[[1]]
[1] 1

[[2]]
[1] 4

[[3]]
[1] 9

然而,purrr::map()函数更受欢迎,尤其是在dplyr用户中,它采用相同的参数,但顺序相反:

> purrr::map(1:3, function(x) x^2)
[[1]]
[1] 1

[[2]]
[1] 4

[[3]]
[1] 9

这是因为当您使用 pip 操作符%>%时,您使用了一个管道向量:

> library(dplyr)
> 1:3 %>% purrr::map(function(x) x^2)

理论上,你可以用相反的顺序来做这件事,使用Map():

> (function(x) x^2) %>% Map(1:3)
[[1]]
[1] 1

[[2]]
[1] 4

[[3]]
[1] 9

但在我看来这不自然。

如果你懂围棋,你可能已经注意到了两者的相似之处,因为这就是作用域在围棋中的工作方式。

https://medium.com/@nyggus/membership *

posted @ 2024-10-18 09:36  绝不原创的飞龙  阅读(122)  评论(0)    收藏  举报