TowardsDataScience-博客中文翻译-2021-六十九-
TowardsDataScience 博客中文翻译 2021(六十九)
论文摘要-部署机器学习的挑战:案例研究调查
本文通过讨论我们可能面临的挑战,为我们部署 ML 模型做更好的准备
概观
生产 ML 很难。如果我们能更好地理解部署 ML 的挑战,我们就能更好地准备下一个项目。这就是为什么我喜欢阅读 Paleyes、Urma 和 Lawrence 的《部署机器学习的挑战:案例研究调查》(2021 年 1 月 18 日,arXiv)。它调查了过去 5 年中与 ML 部署流程相关的论文和文章。为了对 ML 开发过程中的挑战进行分组,作者将 ML 工作流分为 4 个高级步骤,从数据管理到模型部署。之后有一节是关于交叉问题的。
在这篇文章中,我将分享这篇论文的主要亮点,并用参考资料的摘要补充这些要点。我们将讨论:
- 模型重用
- 研究人员和工程师之间的有效合作
- 离群点检测有助于更好的预测
- 处理概念漂移
- 最终用户信任
- 敌对攻击
这篇文章不是这篇论文的完整总结,而是我发现的最新颖的话题。
模型部署
模型重用
在软件工程中代码重用可以减少需要维护的代码,鼓励模块化和可扩展性。重用的思想可以应用于 ML 模型。我们以 Pinterest 为例来看看。Pinterest 使用图像嵌入来支持视觉搜索。他们使用了三个独立的嵌入,专门用于三个不同的任务。因此,不同嵌入的维护、部署和版本控制很快变得势不可挡。作为解决方案,他们在多任务学习框架下统一了嵌入。结果是维护负担减轻,性能提高。
引用一下:
[解决方案]利用来自每个应用程序的所有训练数据组合中的相关信息来生成统一嵌入,该嵌入优于之前为每个产品部署的所有专门嵌入。

来源:在 Pinterest 学习视觉搜索的统一嵌入。他们的多任务度量学习网络的架构。
关于这个图:蓝盒子里的所有东西都是一个巨大的神经网络。被圈起来的部分(被我圈起来的)产生了普遍嵌入。嵌入和下游任务特定网络被联合优化。
ML 实践者的一个问题是:有没有通用的建模组件可以跨 ML 应用程序重用?也许您不能使用反向传播来优化一个通用的神经网络层,但是您可以使用共享组件来实现模型可解释性、异常值检测等等。
研究人员和工程师之间的有效合作
Paleyes 提到,尽管 ML 研究人员和工程师之间似乎有明确的角色分工,但孤立的研究和开发可能会有问题。将数据科学研究“越过墙”扔给工程团队通常被认为是反模式的。研究人员应该与工程师一起工作来拥有产品代码,因为它提高了迭代速度和产品质量。这与 Stitchfix 如何构建他们的数据团队以及这篇关于为什么的数据科学家应该更加端到端的文章是一致的。
离群点检测有助于更好的预测
模型对非分布输入的概括能力很差,导致可信度很高的错误输出。在生产中,检测可能导致不可信预测的异常值是很重要的。
例如,假设您维护一个预测客户是否会流失的模型,客户关系团队使用该模型。该团队投入大量精力来挽救预测流失风险较高的大客户,但大多忽略了“健康”的客户。不正确的预测会带来不小的代价——花费在不需要关注的账户上的努力(用在其他地方会更好),或者被忽视的不健康账户的流失。在某种程度上,你的模型得到了一个异常值,一个与训练数据有很大不同的特征的账户。与其让模型自信地做出预测,不如显示一条消息,说明该帐户是异常值,需要数据科学和客户关系团队进行进一步调查。罪魁祸首实际上可能是上游数据问题。
离群值检测本身可能很难,因为通常缺乏标记离群值。Paleyes 引用了 Seldon Technologies 的一篇论文,其中提到探测器可以预先训练或在线更新。对于前者,检测器作为一个单独的模型部署,并成为另一个模型来发展和维护。后者可以部署为有状态的应用程序。
处理概念漂移
当世界改变时,你的世界模型可能会变得过时和不准确。这在 ML 中被称为概念漂移,定义为联合分布 P(X,y) 中的一个移位。通常 X 是训练数据, y 是目标变量。无法从概念漂移中学习的真实世界类比是因为不适应变化的市场而破产的公司,以及试图与 Z 世代联系的老一代人(这不会起作用)。
在 ML 中,当数据收集方法改变时,概念漂移也可能出现——世界没有改变,但从模型的角度看,它似乎发生了变化。也许上游的数据处理代码发生了变化,或者升级了一个物联网传感器。
Paleyes 引用了一篇探索漂移对 AutoML 算法影响的论文。参考论文的作者使用了真实世界和带有概念漂移的合成数据集。他们定义了不同的适应策略,使模型适应检测到的漂移,并比较了 AutoML 系统、适应策略和数据集的每种组合的结果。

来源:自动机器学习对进化数据的适应策略。概念漂移的适应策略。
在上图中,前 4 个策略定义了检测到漂移时要做的事情。例如,“检测和增量”对 AutoML 模型进行增量训练,训练基于最新的数据批次。最后一个策略是没有再训练的基线。对于每种策略的更深入的描述,我将遵从本文。下面是来自 IMDB 的真实世界数据的每个适应策略的模型准确性结果。

来源:自动机器学习对进化数据的适应策略。使用 H2O 的 AutoML,在 IMDB 数据集上为每种适应策略建立模型的准确性。
图中,没有再培训的基准线是黄线。在 magenta 中,检测和增量策略似乎在这个数据集上工作得最好,但有时比定期重启策略差。
本文的结论是,最佳的适应策略取决于 AutoML 系统和数据集。基本上,没有免费的午餐,你必须找出最能解决你问题的方法。
跨领域挑战
最终用户信任
作为一名数据科学家,您可能对自己的模型非常有信心,但最终用户可能会对模型预测保持警惕。与最终用户建立信任的重点主要是围绕模型的可解释性,但 Paleyes 认为可解释性只是难题的一部分。你还需要与最终用户建立强有力的沟通渠道,透明地开发系统,并设计一个迎合你的观众的用户界面。
例如,引用的论文的作者描述了他们为亚特兰大消防救援部门(AFRD)设计的 ML 系统。该项目有两个主要目标:
- 确定符合消防检查条件的商业物业。AFRD 没有准确的财产清单,因为数据分散在不同的组织中。
- 开发一个模型来预测已识别属性的火灾风险。
很明显,作者与 AFRD 密切合作,并考虑到他们的需求。一个例子是他们如何将预测的风险概率转化为低、中、高风险类别:
风险分类旨在为 AFRD 分配一个可管理的中等风险(N = 402)和高风险(N = 69)的财产,以确定其优先级。
作者还和 AFRD 一起设计了一个用户界面来帮助做决定。他们了解到,除了火灾风险评分,AFRD 还需要该市房产和行政分区的具体信息。最终结果是一个能够过滤和覆盖信息的界面,其中风险分值是更大产品的一部分。

来源:火鸟:预测火灾风险和优先消防检查。物业、火灾和检查的互动地图。
敌对攻击
一种对抗性攻击是数据中毒,攻击者试图破坏训练数据,导致他们可以利用的模型预测。一篇被引用的论文探讨了数据中毒对线性回归模型(如 OLS 和拉索)的影响。一个评估数据集涉及医疗保健患者数据。该模型的目标是正确预测特定药物的剂量。8%的数据中毒率导致一半患者的剂量不正确!网络安全和医疗保健等高风险领域的 ML 从业者需要格外小心,以防范数据中毒攻击。
另一个讨论过的对抗性攻击是模型窃取,如下图所示。

你的模型端点实质上变成了对手的土耳其机器人。对手可以窃取你的知识产权,而且比你花在训练你的神奇模型上的力气还少。调查报告指出,在某些情况下,重建一个相似的模型真的不需要那么多查询。如果您计划通过 API 公开您的模型,这是需要注意的事情。
结论
总之,我们讨论了:
- 模型重用
- 研究人员和工程师之间的有效合作
- 离群点检测有助于更好的预测
- 处理概念漂移
- 最终用户信任
- 敌对攻击
我希望这些话题能更好地为你的下一个 ML 项目做准备。我知道我会更多地考虑模型重用和概念漂移。有相关论文推荐的请伸手!
论文参考
- Andrei Paleyes,Raoul-Gabriel Urma,Neil D. Lawrence:“部署机器学习的挑战:案例研究调查”,2020 年; arXiv:2011.09926 。
- Andrew Zhai,,Eric Tzeng,Dong Huk Park,Charles Rosenberg:“在 Pinterest 学习视觉搜索的统一嵌入”,2019 年; arXiv:1908.01707 。
- Janis Klaise,Arnaud Van Looveren,Clive Cox,Giovanni Vacanti,Alexandru Coca:《生产中模型的监测和可解释性》,2020 年; arXiv:2007.06299 。
- 比格·切利克,华金·范肖伦:“自动机器学习对不断发展的数据的适应策略”,2020 年;arXiv:2006.06480 。
- Madaio,m .,Chen s-t .,Haimson,O. L .,Zhang,w .,Cheng,x .,Hinds-Aldrich,m .,Chau,D. H .,& Dilkina,B. (2016 年 8 月 13 日)。火鸟。第 22 届 ACM SIGKDD 知识发现和数据挖掘国际会议论文集。KDD 16:第 22 届 ACM SIGKDD 知识发现和数据挖掘国际会议。https://doi.org/10.1145/2939672.2939682
- 马修·贾格尔斯基、阿丽娜·奥普雷亚、巴蒂斯塔·比吉奥、刘畅、克里斯蒂娜·妮塔-罗塔鲁、李博:《操纵机器学习:回归学习的中毒攻击与对策》,2018 年; arXiv:1804.00308 。
- Sanjay Kariyappa,Moinuddin K Qureshi:《用自适应误报防御模型窃取攻击》,2019; arXiv:1911.07100 。
包含数据集的论文

PaperswithCode 和 arXiv 已经合作,为人工智能研究提供数据集使用跟踪
跟踪人工智能文献中使用的数据集不是一件容易的事情。然而,这种工具可能非常有用,可以让研究人员快速找到相关论文,共享数据集的使用,以及获得用于感兴趣的特定问题的数据的总体概述。
鉴于这个问题, PaperswithCode 刚刚宣布与 arXiv 合作支持直接在 arXiv 上链接数据集。
在本帖中,我们将快速浏览 arXiv 上直接支持数据集链接对人工智能研究的益处。
人工智能研究取得进展
根据 Robert Stojnic 的说法,他写了一个关于这个公告的快速帖子,你可以在这里查看,
https://medium.com/paperswithcode/datasets-on-arxiv-1a5a8f7bd104
目标是:
通过使研究更容易被发现、复制和推广来加速科学进步
现在,让我们分解这一陈述,以理解在 arXiv 上直接支持到数据集的链接是如何导致有意义的进展的。
便于发现
能够索引在人工智能研究中使用的数据集使你能够将结果透明化,并有助于对人工智能中不同背景下数据集的使用进行广泛的概述。
促进研究的扩展和可重复性
如果其他研究人员能够轻松获得用于完成该发现的数据,人工智能研究人员关于机器学习组件的重要发现——无论是新的损失函数、训练程序还是其他任何东西——都将产生潜在影响。
通过促进对数据集的访问和数据集的使用,这种合作关系为人工智能研究提供了一个更具可重复性的环境。
对于对进一步扩展科学发现感兴趣的人来说,快速访问与感兴趣的主题相关的数据集可能意味着 更快的迭代以获得有意义的结果。
结论
再现性是良好研究的关键组成部分,为了让人工智能社区向前发展,开发强大而健壮的系统,拥有一个促进交叉使用数据集的透明渠道是很重要的,arXiv 中引入这一功能可能意味着朝着这一方向迈出了积极的一步。
如果你喜欢这篇文章,请在 Twitter 、 LinkedIn 上联系我,并在 Medium 上关注我。谢谢,下次再见!😃
Paperspace 推出新的云 IDE
他们通过定制的 Jupyter 笔记本用户界面加入了 Colab 和 Deepnote

黑暗模式下的新 IDE
注意:这个版本似乎是一个正在进行的工作。详情请查看底部与 Colab 和 Deepnote 的对比。
2020 年 2 月 11 日, Paperspace 推出了他们重新设计的基于云的笔记本产品,配备了新的 IDE 和文件浏览器。Paperspace 从 2018 年开始通过其渐变产品提供一键 Jupyter 笔记本。改变的是,笔记本的默认视图现在是他们自己的自定义 IDE 用户界面,类似于 Google Colab 和 Deepnote 提供的界面。
在此更新之前,您使用预安装软件包列表和机器类型(从免费到$$$$)选择了您的基础环境。然后你用 Jupyter 笔记本或者 Jupyter 实验室启动了一个浏览器标签。现在,您仍然可以选择使用 vanilla Jupyter 笔记本/实验室用户界面,但您也可以使用自定义笔记本用户界面,Paperspace 声称该界面速度更快,响应更快。
让我们来看看最近更新中的新功能。
新 IDE
自 2018 年以来,渐变笔记本使用标准的 Jupyter 笔记本或 Jupyter Lab UI。这通常意味着启动环境容器需要几分钟时间,然后必须在浏览器中加载 UI。一旦全部加载完毕,速度非常快,但最初的加载时间总是阻碍我的笔记本电脑快速运行。
新的 IDE 允许您在只读模式下浏览文件和打开笔记本,而无需连接计算机实例来解决这个问题。这意味着您可以快速开始查看您的笔记本。

黑暗模式下的新用户界面。
当然,如果你不喜欢黑暗模式,你也可以选择光明模式。

当您准备好时,您可以选择您的机器类型并启动您的实例。然后,您将能够编辑和执行您的笔记本。宣称的显著更快的设置似乎没有发生在我身上。我通常用 5-10 分钟的时间来准备好我的实例。希望这些是发射故障,速度会提高。
一旦实例准备就绪,现在就有了很好的硬件指示器,让您知道 CPU 和 RAM 的使用情况。

不幸的是,在测试中,并不是所有的环境都与这个新的 IDE 兼容。我测试的一些环境给了我一个错误,笔记本还不支持渐变,但我仍然可以选择在 classic Jupyter 中打开。

即将推出…存储改进?
在 Paperspace 博客公告的底部,他们调侃了即将推出的新存储功能。他们可能正在研究一种方法来解决数据科学家今天面临的最重要的问题之一,即文件和数据管理。数据集可能在 S3 桶或类似的对象存储中,而笔记本可能在本地硬盘驱动器、Google Drive 或其他云提供商中。这产生了许多基本的耗时的管理工作,对任何人来说都不好玩。
与 Deepnote 和 Colab 的比较
Google Colab 提供了一个免费的笔记本环境,可以与 Google Drive 一起工作,并提供免费的 GPU 访问。Deepnote 更进一步,在笔记本中提供了类似 Google Doc 的功能,多个合作者可以在同一个文档中实时编码。Deepnote 目前不提供 GPU 访问。
目前,更新的渐变笔记本没有 Colab 或 Deepnote 提供的功能多。键盘快捷键在活动笔记本中似乎不起作用。看起来你实际上不能在新的 IDE 中创建笔记本,只能运行或编辑现有的。
Paperspace 一推出,我就在它的一键笔记本上使用了它的渐变功能。出于几个原因,从那以后,它一直是我首选的云笔记本供应商。这是因为它们提供了许多机器类型(从免费到$$$$)并允许您使用本机 Jupyter 接口。当在本地编码时,我使用 Jupyter Lab,拥有相同的云选项意味着我不必记住两套键盘快捷键,两种 UI 哲学,或者两种不同的概念什么是笔记本。
像整个数据科学工具空间一样,Jupyter 笔记本的实现似乎正在破裂。也许这是一个好的发展,我们将会看到一些创新的工具化方法。但是现在对我来说,这些解决方案都不会取代我首选的 Jupyter Lab UI。
你可以在 https://gradient.paperspace.com/的查看新的渐变笔记本用户界面。
感谢阅读。你可以在这里找到更多关于我的信息。考虑订阅以便在我发布时收到通知。如果你想直接支持我的文章,你可以使用我的推荐链接注册成为一个媒体会员。
数据科学中的悖论
深入了解与数据科学及其统计基础相关的一些主要悖论

介绍
悖论是一类现象,当我们从已知为真的前提出发,推导出某种逻辑上不合理的结果时,就会出现这种现象。由于机器学习模型从数据中创造知识,这使得它们容易受到训练和测试之间可能存在的认知悖论的影响。
在本文中,我将带您了解一些与数据科学相关的主要悖论,以及如何识别它们:
- 辛普森悖论
- 准确性悖论
- 可学性-哥德尔悖论
- 意外后果定律
辛普森悖论
数据科学中最常见的悖论形式之一是辛普森悖论。
作为一个例子,让我们考虑一个思维实验:我们进行了一项研究,以发现每天进行体育锻炼是否有助于降低胆固醇水平(以毫克/分升为单位),我们现在开始检查所获得的结果。首先,我们根据个人年龄(60 岁以下/60 岁以上)将我们的人口样本分为两大类,然后我们绘制他们的胆固醇水平与受试者每天锻炼小时数的关系图。通过检查图 1 中前两个图的结果,我们可以推断,每天锻炼更多的时间可以导致我们胆固醇水平的整体下降。然后,通过检查通过线性回归推断的最佳拟合线的总体趋势以及在两种情况下得分的相当强的负个人相关性,也可以加强这一假设。
在这一点上,我们得出的结果使我们放心,然后我们可以尝试重复同样的分析,这次考虑整个人口样本(图 1 中最右边的图)。在这种情况下,我们面临着一个完全矛盾的场景和一个正相关,这意味着更多的锻炼会导致胆固醇水平上升。

图 1:胆固醇与每日运动时间(图片由作者提供)。
这种类型的场景通常被称为辛普森悖论,每当我们有某种形式的相关性时就会发生,当在子组中考虑时,这种相关性指向一个方向,而如果被视为整个组的一部分,则指向相反的方向。为了揭示这种机制背后的原因,我们需要尝试超越所提供的数据,思考我们的数据最初是如何产生这种结果的(例如,什么未知的缺失变量可能会阻止我们看到全貌?).
在这个简单的场景中,我们缺失的部分可能是任何潜在的有影响的变量,例如:个体的共病、饮食和年龄。然后,我们决定进一步研究胆固醇水平如何随着年龄的增长而变化(图 2)。重复图 1 中的分析,我们可以清楚地看到胆固醇水平与个人年龄的正相关关系。

图 2:胆固醇与年龄(图片由作者提供)。
从这些结果中,我们可以推断,胆固醇水平更有可能随着年龄的增长和缺乏锻炼而增加(这三个变量之间存在因果关系)。因此,为了尝试量化锻炼对降低胆固醇水平的益处并克服辛普森悖论,我们应该确保在对受试者的年龄设定固定值(控制变量)的情况下进行实验。
在上个世纪,辛普森悖论出现在许多统计研究中,例如:加州大学伯克利分校的性别偏见、肾结石治疗和死刑中的种族差异1。
关于机器学习中因果关系和因果推理的更多信息,可以在我之前的文章机器学习中的因果推理和回答 AI 中的因果问题中找到。
准确性悖论
在我们的日常生活中,人们普遍认为,深入研究一个主题或了解一个话题,必然会对所研究的现象有更深的理解。虽然,这并不总是正确的,因为过度专业化有时会导致我们低估对大局的重要性。
当创建比必要的更复杂的机器学习模型时,也会发生同样的模式。在这种情况下,我们的模型最终会过于关注单个数据点,而没有关注数据的整体趋势(过度拟合)。在这种情况下,我们的模型将能够在训练数据(它记住了每个小细节)上表现良好,而不能推广到新的看不见的数据。
这个问题可以总结在图 3 中。下图考虑了一个回归问题的例子(在意大利,房价如何随着房间数量的增加而变化)。

图 3:欠拟合与过拟合(图片由作者提供)。
创建一个复杂的模型(过度拟合),在这种情况下,我们将能够很好地估计意大利的房价。但是,我们的复杂模型很可能比线性模型表现更差(拟合不足),例如,当试图预测另一个国家的房价时,它没有被训练过(如西班牙)。
因此,这给我们留下了一个准确性悖论。我们训练我们的机器学习模型,以随着时间的推移提高它们的准确性,尽管这可能会导致生产中的准确性降低。这个问题,可以用查尔斯·古德哈特的名言来完美地概括:
"当一个度量成为目标时,它就不再是一个好的度量."
—查尔斯·古德哈特
克服这个问题的最常见的方法之一是在训练期间添加正则化(一种模型复杂性损失)。这样,我们减少了与不太重要的特征相关联的权重,并将我们的注意力集中在为我们提供更多信息的特征上。
在分类问题中,当处理不平衡的训练数据集(一些类出现的频率比其他类高得多)时,最有可能出现准确性悖论。在这种情况下,计算模型的精度和召回率可以成为更有价值的度量。
可学性-哥德尔悖论
库尔特·哥德尔是上世纪最著名的数学家之一。毫无疑问,其中最有趣的理论之一是两个哥德尔不完全性定理。
根据这些定理,现在的数学有一些内在的局限性,不允许它确定地陈述一个陈述是否真实。数据科学的整个领域与数学思维紧密相连,因此这导致了一个悖论(可学习性-哥德尔悖论)。
根据哥德尔理论是对还是错,这将证明从人口样本进行外推是可能的或不可能的。机器学习模型严重依赖于使用样本数据集进行训练,如果这被证明是不够的,那么将使它们不可能正确地执行任何形式的推断/预测。事实上,在整个人口数据上训练机器学习模型在计算上是不可行的,并且在大多数情况下,首先收集整个人口的数据几乎是不可能的。
关于这个悖论及其在机器学习中的应用的其他信息可以在 Shai Ben-David 等人的研究出版物“可学习性是不可判定的”中找到。艾尔。[2]
意外后果定律
随着系统和模型变得越来越复杂,对于我们人类来说,理解一个系统如何工作并做出决策可能会变得越来越困难。这个概念总结在意外后果法则中。
根据这一概念,随着系统复杂性的增长,不期望的行为或“错误”可能会不由自主地发生。当创建大型软件项目或创建复杂的机器学习模型时,这往往是正确的。
近年来,随着大数据制度的出现,人们对创建技术的兴趣越来越大,以使模型更具可解释性和无偏见,从而符合政府机构的法规(如 GDPR)。
最终,这让我们面对本文中考虑的最后一个悖论。日复一日,我们致力于创造尽可能好的模型来实现一个结果,尽管如果过于复杂而难以理解,这可能永远不会在生产中部署(例如 GDPR 第 5 条,右为解释)。
结论
在本文中,我介绍了一些与数据科学相关的主要悖论。虽然,许多其他常见的悖论可能对数据科学和人工智能有潜在的影响。一些例子有3:
- 伯克森悖论
- 布莱斯悖论
- 莫拉维克悖论
- 生日悖论
如果你有任何其他的矛盾,你想加入到这个列表中,请留言告诉我!
我希望你喜欢这篇文章,谢谢你的阅读!
联系人
如果你想了解我最新的文章和项目,请通过媒体关注我,并订阅我的邮件列表。以下是我的一些联系人详细信息:
文献学
1辛普森悖论:如何用相同的数据证明相反的论点威尔·科尔森,走向数据科学。访问日期:https://shorturl.at/jkyT92020 年 8 月。
[2]自然,可学性是不可决定的。Shai Ben-David、Pavel Hrube 等人。艾尔。访问:https://www.nature.com/articles/s42256-018-0002-3,2020 年 9 月。
3数据科学家应该知道的五个悖论,MC.AI .访问:https://MC . ai/Five-paradomes-that-Data-Scientists-Should-Know/,2020 年 9 月。
Python 中的并行批处理

我的个人工作空间(用佳能 Rp 给自己拍照)
使用 joblib 进行批处理,并使用 tqdm 显示进度
Joblib 是一个很好的并行化工具,但有时分批处理工作负载比默认的迭代方式更好。在本文中,我将展示:
- 使用 joblib 和 tqdm 进行并行化的标准方式
- 为什么以及何时不起作用
- 使用批处理进行并行化
- 让进步再次发挥作用
所有代码都可以在 Github 上获得。如果有任何问题,请随时联系我。
代码也可作为 Pypi 包 : pip install tqdm_batch

使用 Python、joblib 和 tqdm 批处理工作负载。
1)使用 joblib 进行并行化的直接方法
2021 年,我们购买的几乎每个 CPU 都有多个内核。我目前的笔记本电脑(Dell XPS)配有带 6 个内核和超线程技术的英特尔 i7,总共有 12 个内核供您使用。甚至现在的移动电话也有多个 CPU,并具有巨大的计算能力。这些 CPU 架构中的内核可以是相同的,即每个内核具有相同的处理能力,或者具有定制的特征。后者的一个例子就是 ARM 的大。将高性能内核与低能耗内核相结合的小型架构。
当处理大型数据集时,我们都遇到过这样的情况:我们不能使用所有的内核并加快处理速度吗?当然,答案是肯定的,但是多重处理一点也不微不足道。
我们都经历过这种情况:难道我们不能利用所有的内核并加快处理速度吗?
Python 内置了多处理和多线程,带有多处理和线程模块。这是您开始使用多处理和多线程所需要的全部内容,但它需要一些样板代码。这可能令人望而生畏,尤其是从数据科学的角度来看,我们有这个串行处理循环,只是想并行运行它。
为了简化我们的多核处理负担,我们可以利用奇妙的库,如 joblib 或 pathos 。在 joblib 的网站上,作者说这些循环是“令人尴尬的平行”,他们肯定是对的,因为它非常容易上手。让我们创建一个示例:
这只是一个伪函数,近似π,但阶数为 6,计算时间约为 200 毫秒。还有另外两个变量。第一个变量是一行,它是要处理的单个项目。这可以是方法需要打开和加载的文件名,也可以是我们需要规范化的字符串。在这个虚拟函数中,我们忽略它。有效载荷将用于显示限制,现在也可以忽略。让我们为这个函数获取一些运行时数据:

图 1:运行时间串行运行和使用 joblib 时。
串行运行,即处理每一行的 for 循环,只需不到 100 秒。在这个例子中,我们可以清楚地看到 joblib 的优点。我们不需要做任何改变就可以并行处理,使用 8 个内核时,结果几乎快了 5 倍。这通常很有效,但是我们必须注意它的局限性。
2)局限性以及为什么它不总是有效
为了理解这些限制,我们需要解释一下多处理和多线程之间的区别,以及这与 Python 的全局解释器锁(GIL)有什么关系。当试图利用多核时,您可能听说过 GIL。关于 GIL 到底是什么有很多很棒的文章,但简而言之,它是一把锁,可以防止多个进程同时访问 Python 解释器。这防止了一些奇怪的问题,比如一个进程删除了一个变量,而另一个进程想同时读取它。GIL 的一个大缺点是,如果你有一个 Python 繁重的任务(大量的 Python 代码)并试图并行运行它,每个任务都必须等待解释器。他们是 GIL 有限公司。
多处理是并行运行任务的一种方式,它使用自己的 Python 解释器创建一个独立的流程。多处理任务没有 GIL 问题。但是,因为我们正在运行一个独立的进程,所以任务不能访问与主程序相同的内存,所有需要的数据都需要发送(序列化)到这个进程。序列化本身是使用 Pythons 内置 Pickle(或 Dill )完成的,它为所有需要的对象创建一个冻结的内存副本。有些对象无法序列化,您将会得到一个错误。例如,对进程 1 的 UI 的引用不能共享给进程 2(这也没有意义)。这种序列化,即把对象的状态冻结成字节发送给进程,是开销(额外的内存和 IO)的原因。例如,当每个进程需要一个字典用于某些翻译时,多处理库将为每个进程复制这个字典。如果这个字典很大,这种开销比串行处理花费更多的时间是很常见的。
这种开销比串行处理花费更多的时间并不罕见。
另一种并行运行任务的方法是多线程。最大的好处是它与主程序共享内存,并且不需要序列化,因此开销更少。但代价是你必须和 GIL 合作。如果任务使用许多编译好的库,比如不需要 GIL 的 Numpy ,这不是问题。

图 2:多处理有开销,多线程 GIL。
默认情况下,joblib 使用多线程,但是可以使用prefer='threads'参数将其设置为多线程。您可以尝试这个设置,如果任务不是 Python 繁重的,您可能会受益。一般来说,默认的多处理是一个非常明智的默认。
当多处理任务需要大量数据进行处理时,例如,一个大型模型进行一些分类,这个模型需要序列化到每个任务,从而产生大量开销。最初的 joblib 示例为 items 中的每一行启动一个新的进程,这意味着它必须为每次迭代序列化所有内容,这与理想情况相差甚远:

图 3:过多的开销导致 4 倍的串行运行时间
开销如此之大,以至于运行时间是逐个执行的四倍。这是否意味着我们无法并行处理这类工作负载?当然不是!通过使用批处理方法:首先将工作量分成大小相等的批,让每个进程处理这些批。
3)使用批处理进行并行化
我们已经看到,对于多处理,由于每个任务的大数据要求的串行化,开销可能是巨大的。解决方案很简单:减少序列化的数量。我们将创建一个额外的包装器函数来处理流程内部的批处理,而不是对每一项进行序列化。这导致每个进程只序列化一次数据。

图 4:减少开销回到我们的常规节省
这为我们带来了与串行方法相似的节省,而不需要大量数据。%%time神奇的功能也向我们展示了一些有趣的事情:我们在这个过程中只花了 1.33 秒的时间。另外的 23 是在%%time不能访问的其他进程中花费的。
你可能已经注意到的另一件事是进度条不再工作了。Tqdm 耦合到批处理,将所有批处理发送出去,并且必须等待进程完成。我们看不到目前为止在项目级别上已经处理了多少。由于我们无法访问内存,所以显示单个(或多个)进度条并不容易。
4)让我们修复进度条
在多重处理时创建进度条不是一件容易的事情。当使用串行方法时,我们可以将 tqdm 保持在主进度(集体进度)中。它会看到传递给流程的每个项目。通过序列化连接进度条是不可能的,因为它需要进程写入主程序内存的一部分。根据定义,这是不允许的,这也是为什么当您尝试将 progress_bar 对象添加到函数参数时会出现序列化错误的原因。
为了解决这个问题,我们可以利用一个队列,一种列表,它使得进程之间的相互通信成为可能。为了使它更简单,让我们创建一个完整的包装器,这样我们就可以提供一个项目列表和一个函数,在我们的包装器将工作划分给所有的进程。

图 5:在多个 CPU 上分配工作并显示进度条
使用这种方法,我们创建了一个插入式替换,您只需编写一个处理单行的方法。会将该函数封装到一个批处理处理器中,该处理器还会更新一个进度条。函数的所有参数都可以在传递给处理函数时使用实参进行添加,包括所需的大型数据和模型。结果类似于最初的运行,开销很小。tqdm_batch被添加到 pypi 中,所以你可以自己做一个pip install tqdm_batch来试试。
工人的数量也不是你所期望的,因为他们不是线性增长的。在某一点上,增加更多的工人不会带来任何好处。我还注意到,我的超线程内核也没有带来任何性能提升。以下是所需计算时间与工作人员数量的概述:

图 6:超过 6 件作品不会增加任何利益
围捕
我希望您对多处理有所了解,并对它的工作原理和局限性有所了解。您可以使用 batch_process 作为 joblib 和 tqdm 的替代工具,在不修改原始代码的情况下批量运行您的处理。
肯定有一些可以改进的地方。首先,我在寻找一个不存在的 joblib 扩展。回想起来,我本可以完全放弃对 joblib 的依赖,全部用普通 Python 来创建。另外,进度条不需要在一个单独的线程中。也许,我会在未来的版本中改变这一点(或者我接受你的拉取请求(-😃。
这让我想到了这篇文章的结尾。如果您有任何意见,请告诉我!在 LinkedIn 上随意联系。
Raspberry Pi 4B+物联网板上的并行计算变得更加简单

来自 Unsplash 的 Arthur V. Ratz 的照片
在 Raspberry Pi 物联网板上构建和运行使用 Khronos CL/SYCL-model 规范实现的 C++17 并行代码。
我们的目标…
该项目提供了有用的指南、技巧和教程,用于在 C++17/2x0 中构建现代并行代码,使用 CL/SYCL 编程模型实现,并在基于创新的 ARM Cortex-A72、四核、64 位 RISC-V CPU 的下一代 Raspberry Pi 4B 物联网板上运行。
读者观众将了解如何设置 Raspberry 4B 物联网板,开箱即用,并将其用于并行计算,使用 Khronos CL/triSYCL 和 Aksel Alpay 的 hipSYCL 项目的开源发行版交付 C++17 中的并行代码,安装和配置 GNU 的编译器集合(GCC)和 LLVM/Clang-9 . x . x Arm/aarch 64-tool chains,以构建并行代码的可执行文件并在 Raspbian Buster 10.6 OS 中运行。
Raspberry PI 4B+物联网板概述
下一代创新的 Raspberry Pi 4B+物联网主板基于强大的 ARM 多核对称 64 位 RISC-V CPU,提供了无与伦比的性能,从而实现了并行计算本身的终极生产力。使用最新的 Raspberry Pi 板可以大幅提高边缘计算过程的实际性能,例如在将数据交付到数据中心进行处理之前,实时收集和预处理数据。并行运行这些流程显著提高了基于云的解决方案的效率,为数十亿个客户端请求提供服务,或者提供数据分析和其他推断。
在我们讨论如何在 C++17 中构建和运行并行代码之前,让我们先花点时间简要了解一下下一代 Raspberry Pi 4B+板及其技术规格,该并行代码是使用 CL/SYCL 异构编程模型规范为采用 Arm/Aarch64 架构的 Raspberry Pi 板设计的:

Raspberry Pi 4B+物联网板
Raspberry Pi 4B+物联网板基于创新的 Broadcom BCM2711B0 (SoC)芯片制造,配备最新的 ARM 四核 Cortex-A72 @ 1.5GHz 64 位 RISC-V CPU,提供终极性能和可扩展性,同时利用它进行边缘并行计算。
Raspberry Pi 以“可靠”和“快速”的微型纳米计算机而闻名,专为数据挖掘和并行计算而设计。ARM 的多核对称 64 位 RISC-V CPU 的主要新硬件架构特性,如 DSP、SIMD、VFPv4 和硬件虚拟化支持,可以显著提高边缘大规模处理数据的物联网集群的性能、加速和可扩展性。
具体来说,最新的 Raspberry Pi 4B+板最重要的优势之一是薄型 LPDDR4 存储器,可选择 2、4 或 8 GiB RAM 容量,工作频率为 3200Mhz,提供典型的大内存事务带宽,总体上对并行计算的性能产生积极影响。对于数据挖掘和并行计算,强烈建议使用安装了 4gb 或更高内存的主板。此外,BCM2711B0 SoC 芯片还捆绑了各种集成设备和外设,如 Broadcom VideoCore VI @ 500Mhz GPUs、PCI-Ex 千兆以太网适配器等。
为了在 C++17 中构建和运行特定的并行现代代码,使用 CL/SYCL 异构编程模型实现,我们真正需要的第一个是 Raspberry Pi 4B+物联网板,该板安装了最新的 Raspbian Buster 10.6 操作系统,并针对首次使用进行了配置。
以下是硬件和软件要求的简要清单:
硬件:
- 树莓 Pi 4 型号 B0,4GB 物联网板;
- Micro-SD 卡 16GB,用于 Raspbian 操作系统和数据存储;
- DC 电源:通过 USB Type-C 连接器的 5.0V/2–3A(最小 3A —用于数据挖掘和并行计算);
软件:
- Raspbian Buster 10.6.0 完整 OS;
- 拉斯比安成像仪 1.4;
- MobaXterm 20.3 build 4396 或任何其他 SSH 客户端;
既然我们已经有了一个 Raspberry Pi 4B+物联网板,现在,我们可以开箱即用地打开它并进行设置。
设置 Raspberry Pi 4B 物联网板
在开始之前,我们必须从官方的 Raspbian 库下载 Raspbian Buster 10.6.0 完整操作系统映像的最新版本。要将 Raspbian 操作系统映像安装到 SD 卡,我们还需要下载并使用适用于各种平台的 Raspbian Imager 1.4 应用程序,如 Windows、Linux 或 macOS:
此外,我们还必须下载并安装 MobaXterm 应用程序,以便通过 SSH 或 FTP 协议远程建立与 Raspberry Pi 板的连接:
由于 Raspbian Buster 操作系统和 Imager 应用程序已成功下载并安装,我们将使用 Imager 应用程序执行以下操作:
1.默认情况下,擦除 SD 卡,将其格式化为 FAT32 文件系统;
2.提取预装的 Raspbian Buster OS 镜像(*。img)到 SD 卡;
由于上述步骤已经完成,从读卡器中取出 SD 卡,并将其插入 Raspberry Pi 板的 SD 卡插槽中。然后,连接微型 HDMI 和以太网电缆。最后,插入 DC 电源电缆的连接器,并打开电路板。最后,系统将启动安装在 SD 卡上的 Raspbian Buster 操作系统,提示执行几个安装后步骤来配置它以供首次使用。
由于主板已通电,请确保已完成以下所有安装后步骤:
1.打开 bash 控制台并设置“root”密码:
pi@raspberrypi4:~ $ sudo passwd root
2.以“root”权限登录 Raspbian bash 控制台:
pi@raspberrypi4:~ $ sudo -s
3.使用以下命令升级 Raspbian 的 Linux 基本系统和固件:
root@raspberrypi4:~# sudo apt update
root@raspberrypi4:~# sudo apt full-upgrade
root@raspberrypi4:~# sudo rpi-update
4.首次重新启动系统:
root@raspberrypi4:~# sudo shutdown -r now
5.再次安装最新的 Raspbian 引导程序并重启系统:
root@raspberrypi4:~# sudo rpi-eeprom-update -d -a
root@raspberrypi4:~# sudo shutdown -r now
6.启动“raspi-config”安装工具:
root@raspberrypi4:~# sudo raspi-config
7.使用“raspi-config”工具完成以下步骤:
*更新“raspi-config”工具:

*启动时禁用 Raspbian 的桌面 GUI:
系统选项 > > 开机/自动登录 > > 控制台自动登录:

*扩展 SD 卡上的根'/'分区大小:

最后,在执行 Raspbian 安装后配置后,重新引导系统。重启后,系统会提示您登录。使用之前设置的“root”用户名和密码,以 root 权限登录 bash 控制台。
因为您已经成功登录,所以在 bash-console 中使用下面的命令安装 APT-repositories 中的一些包:
root@raspberrypi4:~# sudo apt install -y net-tools openssh-server
需要这两个软件包来配置 Raspberry Pi 的网络接口或 OpenSSH-server,以便使用 MobaXterm 通过 SSH 协议远程连接到电路板。
通过修改/etc/network/interfaces 配置板的网络接口“eth0 ”,例如:
auto eth0
iface eth0 inet static
address 192.168.87.100
netmask 255.255.255.0
broadcast 192.168.87.255
gateway 192.168.87.254
nameserver 192.168.87.254
在网络接口旁边,通过取消注释 /etc/ssh/sshd_config 中的这些行来执行 OpenSSH-server 的基本配置:
PermitRootLogin yes
StrictModes noPasswordAuthentication yes
PermitEmptyPasswords yes
这将允许“root”登录,通过 SSH 协议进入 bash 控制台,而无需输入密码。
最后,尝试通过网络连接板,使用 MobaXterm 应用程序并打开到 IP 地址为 192.168.87.100 的主机的远程 SSH 会话。您还必须能够使用之前设置的凭证成功登录到 Raspbian 的 bash 控制台:

用 CL/SYCL-Model 在 C++17 中开发并行代码
2020 年,Khronos 集团、英特尔公司和其他供应商宣布推出革命性的新型异构并行计算平台(XPU),提供了将“繁重”数据处理工作负载的执行卸载到广泛的硬件加速(例如,GPGPU 或 FPGAs)目标的能力,而不仅仅是主机 CPU。从概念上讲,使用 XPU 平台的并行代码开发完全基于 Khronos CL/SYCL 编程模型规范 OpenCL 2.0 库的抽象层。
下面是一个很小的例子,展示了使用 CL/SYCL-model 抽象层实现的 C++17 中的代码:
#include <CL/sycl.hpp>using namespace cl::sycl;constexpr std::uint32*_t N = 1000;**cl::sycl::queue q{};**q.submit([&](cl::sycl::handler &cgh) {*
*cgh.parallel_*for<class Kernel>(cl::sycl::range<1>{N}, \
[=](cl::sycl::id<1> idx) {
// Do some work in parallel
});
});q.wait();
如上所示,C++17 中的代码片段完全基于 CL/SYCL 编程模型交付。它用默认参数初始化列表实例化一个 cl::sycl::queue{}对象,用于将 sycl 内核提交给默认使用的主机 CPU 加速目标来执行。接下来,它调用 cl::sycl::submit(…)方法,使用 cl::sycl::handler{}对象的单个参数来访问提供基本内核功能的方法,这些方法基于各种并行算法,包括 cl::sycl::handler::parallel _ for(…)方法。
下面的方法用于实现从正在运行的内核中产生的紧密并行循环。这个循环的每次迭代都由它自己的线程并行执行。cl::sycl::handler::parallel _ for(…)接受 cl::sycl::range <> {}对象的两个主要参数和一个在每次循环迭代中调用的特定 lambda 函数。cl::sycl::range <> {}对象基本上为每个特定维度定义了几个正在执行的并行循环迭代,以防在处理多维数据时多个嵌套循环被折叠。
在上面的代码中,cl::sycl::range <1> (N)对象用于调度一维并行循环的 N 次迭代。parallel_for(…)方法的 lambda 函数接受另一个 cl::sycl::id <> {}对象的单个参数。和 cl::sycl::range <> {}一样,这个对象实现了一个向量容器,其中的每个元素分别是并行循环的每个维度和每个迭代的索引值。作为参数传递给 lambda 函数范围内的代码,下面的对象用于检索特定的索引值。lambda 函数的主体包含一段并行处理数据的代码。
在将特定内核提交到队列并生成以供执行后,以下代码调用不带参数的 cl::sycl::wait()方法来设置屏障同步,以确保它不会执行任何代码,直到正在生成的内核完成其并行工作。
CL/SYCL 异构编程模型效率高,应用广泛。
然而,英特尔公司和 CodePlay Software Inc .很快就否决了除 x86_64 之外的硬件架构对 CL/SYCL 的支持。这使得不可能使用特定的 CL/SYCL 库,针对 Arm/Aarch64 和其他架构来交付并行 C++代码。
目前,有几个 CL/SYCL 开源库项目,由许多开发人员和爱好者开发,提供对更多硬件架构的支持,而不仅仅是 x86_64。
自 2016 年以来,Khronos Group,Inc .发布了他们的 tri cycl 库开源项目的修订版(https://github . com/tri cycl/tri cycl)、 ,建议将其用作测试平台,同时评估最新的 CL/SYCL 编程模型层规范,并向 Khronos-和 ISO-委员会发送反馈。然而,下面的库发行版并不“稳定”,只能用于演示目的,而不能用于在生产中构建 CL/SYCL 代码。此外,Khronos triSYCL 库发行版完全支持 x86_64 开发机器上的跨平台编译,使用 GNU 的 Arm/Aarch64 跨平台工具链,而不是在 Raspberry Pi 上使用 LLVM/Clang 编译器“本机”构建代码。
2019 年,海德堡大学(德国)的 Aksel Alpay 实现了最新的 CL/SYCL 编程模型层规范库,针对各种硬件架构,包括 Raspberry Pi 的 Arm/Aarch64 架构,并向 GitHub(https://github.com/illuhad/hipSYCL)贡献了 hipSYCL 开源库发行版中最“稳定”的版本。
此外,在这个故事中,我们将讨论安装和配置 GNU 的跨平台 GCC/G++-10.x.x 和“本机”Arm/Aarch64 的 LLVM/Clang-9.x.x 工具链,并使用 triSYCL 和 hipSYCL 库发行版,在 C++17 中交付一个现代的并行代码,基于正在讨论的库的使用。
在 Debian/Ubuntu 开发机器(x86_64)和 Raspberry Pi 物联网板上构建 CL/SYCL 代码
在 C++17 中,基本上有两种构建 CL/SYCL 代码的方法,如上所述,通过使用 GNU 的 GCC/G++-10.x.x 跨平台工具链和基于 x86_64 Debian/Ubuntu 的开发机器,或者在安装了 LLVM/Clang-9.x.x 用于 Arm/Aarch64 硬件架构的 Raspberry Pi 物联网板上“本地”构建。
使用第一种方法允许在 C++17/2x0 中构建代码源,在 Raspberry Pi 上运行之前,使用 Khronos triSYCL 库和 GNU 的跨平台 Arm/aarch 64-tool chain Debian/Ubuntu-based x86 _ 64 开发机器来实现。
要部署 x86_64 开发机器,需要安装最新的 Debian Buster 10.6.0 或 Ubuntu 20.04 LTS:
为了能够在运行 Microsoft Windows 10 的主机上使用开发机器,可以使用选择的任何现有(例如 Oracle VirtualBox 或 VMware Workstation)虚拟化环境,用于该目的:
要开始部署开发机器,我们必须做的就是设置一个特定的虚拟化环境,创建一个虚拟机,并启动 Debian 或 Ubuntu 安装。
既然已经创建了虚拟机,并且 Debian/Ubuntu 已经成功安装,我们可以继续几个步骤,安装和配置 GNU 的 GCC/G++-10.x.x 跨平台编译器、开发工具和 Khronos triSYCL 库,这是针对 Raspberry Pi 的 Arm/Aarch64 架构构建代码所必需的。
在安装和配置 GCC/G++编译器工具链和运行时库之前,请确保已经完成了以下先决步骤:
- 升级 Debian/Ubuntu 的 Linux 基础系统:
root@uarmhf64-dev:~# sudo apt update
root@uarmhf64-dev:~# sudo apt upgrade -y
root@uarmhf64-dev:~# sudo apt full-upgrade -y
要确保 x86_64 主机开发机器上运行的 Debian/Ubuntu 安装是最新的,并且安装了最新的内核和软件包,需要完成这个步骤。
- 从 APT-repository 安装“net-tools”和 OpenSSH-server 软件包:
root@uarmhf64-dev:~# sudo apt install -y net-tools openssh-server
安装“net-tools”和“openssh-server”是为了能够配置开发机器的网络接口,并通过 ssh 和 FTP 协议远程连接到正在运行的开发机器。
由于系统已经升级,所有必需的软件包都已安装,我们可以安装和配置特定的编译器和工具链。
安装和配置 GNU 的 GCC/G++-10.x.x
1.为 x86_64 平台安装 GNU 编译器集合(GCC)的工具链:
root@uarmhf64-dev:~# sudo apt install -y build-essential
2.安装 GNU 的跨平台 Arm64/Armhf 工具链:
root@uarmhf64-dev:~# sudo apt install -y crossbuild-essential-arm64
root@uarmhf64-dev:~# sudo apt install -y crossbuild-essential-armhf
需要为 Arm64/Armhf 硬件架构安装跨平台工具链,以便在 x86_64 开发机器上构建使用 triSYCL 库的 C++17 并行代码。
3.安装 GNU 的 GCC/G++、OpenMP 5.0、Boost、Range-v3、POSIX 线程、C/C++标准运行时库,需要:
root@uarmhf64-dev:~# sudo apt install -y g++-10 libomp-dev libomp5 libboost-all-dev librange-v3-dev libc++-dev libc++1 libc++abi-dev libc++abi1 libpthread-stubs0-dev libpthread-workqueue-dev
4.安装 GNU 的 GCC/G++-10.x.x .跨平台编译器,用于构建针对 Arm64/Armhf 架构的代码:
root@uarmhf64-dev:~# sudo apt install -y gcc-10-arm-linux-gnueabi g++-10-arm-linux-gnueabi gcc-10-arm-linux-gnueabihf g++-10-arm-linux-gnueabihf
5.选择默认情况下使用的 GCC/G++-10.x.x“本机”x86_64 编译器,更新备选方案:
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 2
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 2
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc 3
sudo update-alternatives --set cc /usr/bin/gcc
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++ 3
sudo update-alternatives --set c++ /usr/bin/g++
6.选择默认情况下使用的 GCC/G++-10.x.x 跨平台 Arm/Aarch64 编译器,更新备选方案:
sudo update-alternatives --install /usr/bin/arm-linux-gnueabihf-gcc arm-linux-gnueabihf-gcc /usr/bin/arm-linux-gnueabihf-gcc-9 1
sudo update-alternatives --install /usr/bin/arm-linux-gnueabihf-gcc arm-linux-gnueabihf-gcc /usr/bin/arm-linux-gnueabihf-gcc-10 2
sudo update-alternatives --install /usr/bin/arm-linux-gnueabihf-g++ arm-linux-gnueabihf-g++ /usr/bin/arm-linux-gnueabihf-g++-9 1
sudo update-alternatives --install /usr/bin/arm-linux-gnueabihf-g++ arm-linux-gnueabihf-g++ /usr/bin/arm-linux-gnueabihf-g++-10 2
sudo update-alternatives --install /usr/bin/arm-linux-gnueabihf-cc arm-linux-gnueabihf-cc /usr/bin/arm-linux-gnueabihf-gcc 3
sudo update-alternatives --set arm-linux-gnueabihf-cc /usr/bin/arm-linux-gnueabihf-gcc
sudo update-alternatives --install /usr/bin/arm-linux-gnueabihf-c++ arm-linux-gnueabihf-c++ /usr/bin/arm-linux-gnueabihf-g++ 3
sudo update-alternatives --set arm-linux-gnueabihf-c++ /usr/bin/arm-linux-gnueabihf-g++
7.最后,检查是否安装了 GNU 的“本地”和跨平台工具链的正确版本:
root@uarmhf64-dev:~# gcc --version && g++ --version
root@uarmhf64-dev:~# arm-linux-gnueabihf-gcc --version
root@uarmhf64-dev:~# arm-linux-gnueabihf-g++ --version
8.导航到/opt 目录,从 GitHub 存储库中克隆 Khronos triSYCL 库发行版:
root@uarmhf64-dev:~# cd /opt
root@uarmhf64-dev:~# git clone --recurse-submodules [https://github.com/triSYCL/triSYCL](https://github.com/triSYCL/triSYCL)
以下命令将创建/opt/tri cycl 子目录,其中包含 tri cycl 库分发源。
9.使用“rsync”命令,将 triSYCL 库的 C++头文件从/opt/triSYCL/include 目录复制到开发计算机上的默认位置/usr/include/c++/10/:
root@uarmhf64-dev:~# cd /opt/triSYCL
root@uarmhf64-dev:~# sudo rsync -r ./ include/ /usr/include/c++/10/
10.设置使用 triSYCL 库和 GNU 跨平台工具链所需的环境变量,以前安装了:
export CPLUS_INCLUDE_PATH=/usr/include/c++/10
env CPLUS_INCLUDE_PATH=/usr/include/c++/10
sudo echo "export CPLUS_INCLUDE_PATH=/usr/include/c++/10" >> /root/.bashrc
11.通过删除/opt/triSYCL 子目录来执行简单的清理:
root@uarmhf64-dev:~# rm -rf /opt/triSYCL
12.使用“本机”x86_64 GNU 的 GCC/G++编译器构建“hello.cpp”代码示例:
root@uarmhf64-dev:~# g++ -std=c++17 -o hello hello.cpp -lpthread -lstdc++
在 C++17/2x0 中构建使用 Khronos triSYCL 库的特定代码需要 POSIX 线程和 C++标准库的运行时链接。
13.使用 GNU 的跨平台 GCC/G++编译器构建“hello.cpp”代码示例:
root@uarmhf64-dev:~# arm-linux-gnueabihf-g++ -std=c++17 -o hello_rpi4b hello.cpp -lpthread -lstdc++
既然我们已经成功地为 Arm/Aarch64 架构生成了代码可执行文件,那么就使用 MobaXterm 应用程序,通过 FTP 或 SSH 协议,从开发机器上下载可执行文件。之后,使用另一个 SSH 会话将“hello_rpi4b”可执行文件上传到 Raspberry Pi 板。
要运行“hello_rpi4b”可执行文件,请在 Raspbian 的 bash 控制台中使用以下命令,例如:
root@uarmhf64-dev:~# chmod +rwx hello_rpi4b
root@uarmhf64-dev:~# ./hello_rpi4b > output.txt && cat output.txt
这将创建输出并将其附加到‘output . txt’文件,将其内容打印到 bash 控制台:
Hello from triSYCL on Rasberry Pi 4B+!!!
Hello from triSYCL on Rasberry Pi 4B+!!!
Hello from triSYCL on Rasberry Pi 4B+!!!
Hello from triSYCL on Rasberry Pi 4B+!!!
Hello from triSYCL on Rasberry Pi 4B+!!!
注意:通常,第一种方法不需要从源代码构建 Khronos triSYCL 库发行版,除非您计划将 tri cycl 用于其他 HPC 库,如 OpenCL、OpenMP 或 TBB。有关使用 triSYCL 以及其他库的更多信息,请参考以下指南和文档https://github . com/triSYCL/triSYCL/blob/master/doc/cmake . rst。
使用 Aksel Alpay 的 hipSYCL 开源库发行版和 LLVM/Clang-9.x.x. "native "编译器工具链,以 Arm/Aarch64 架构为目标,是第二种允许在 C++17/2x0 中构建 CL/SYCL 代码的方法,以便在 Raspberry Pi 板上运行。只有在 LLVM/Clang-9.x.x 工具链和 hipSYCL 库发行版都安装在 Raspberry Pi 板上,而不是 x86_64 开发机器本身上的情况下,特定代码的本地构建才是可能的。
此外,我们将讨论在 Raspberry Pi 板上安装和配置 LLVM/Clang-9.x.x 编译器工具链以及从源代码构建 Aksel Alpay 的 hipSYCL 库所需了解的一切。
安装和配置 LLVM/Clang-9.x.x
在使用 Aksel Alpay 的 hipSYCL 库项目发行版之前,必须正确安装和配置特定的 LLVM/Clang-9.x.x 编译器和 Arm/Aarch64 工具链。为此,请确保您已经完成了下列步骤:
1.更新 Raspbian 的 APT-repositories 并安装以下必备软件包:
root@raspberrypi4:~# sudo apt update
root@raspberrypi4:~# sudo apt install -y bison flex python python3 snap snapd git wget
上面的命令将安装一个替代的“snap”包管理器,这是安装 cmake >= 3.18.0 实用程序的正确版本所必需的,以及使用“cmake”实用程序从头开始构建 hipSYCL 开源项目所需的“python”、“python3”发行版和“bison”、“flex”实用程序。
2.使用“快照”软件包管理器安装“cmake”> = 3 . 18 . 0 实用程序和 LLVM/Clang 守护程序:
root@raspberrypi4:~# sudo snap install cmake --classic
root@raspberrypi4:~# sudo snap install clangd --classic
安装“cmake”实用程序后,让我们使用下面的命令检查它是否工作,以及是否从“snap”存储库中安装了正确的版本:
root@raspberrypi4:~# sudo cmake --version
运行此命令后,您必须看到以下输出:
cmake version 3.18.4CMake suite maintained and supported by Kitware (kitware.com/cmake).
3.为 LLVM/Clang 工具链安装最新的 Boost、POSIX-Threads 和 C/C++标准运行时库:
root@raspberrypi4:~# sudo apt install -y libc++-dev libc++1 libc++abi-dev libc++abi1 libpthread-stubs0-dev libpthread-workqueue-devroot@raspberrypi4:~# sudo apt install -y clang-format clang-tidy clang-tools clang libc++-dev libc++1 libc++abi-dev libc++abi1 libclang-dev libclang1 liblldb-dev libllvm-ocaml-dev libomp-dev libomp5 lld lldb llvm-dev llvm-runtime llvm python-clang libboost-all-dev
4.下载并添加 LLVM/Clang 的 APT-repositories 安全密钥:
root@raspberrypi4:~# wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
5.将 LLVM/Clang 的存储库 URL 附加到 APT 的源中。
root@raspberrypi4:~# echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster main" >> /etc/apt/sources.list.d/raspi.listroot@raspberrypi4:~# echo "deb-src http://apt.llvm.org/buster/ llvm-toolchain-buster main" >> /etc/apt/sources.list.d/raspi.list
要从特定的 APT 资源库安装 LLVM/Clang-9.x.x .编译器和特定的工具链,必须完成前面的两个步骤 4 和 5。
6.删除 LLVM/Clang 先前版本的现有符号链接,安装:
root@raspberrypi4:~# cd /usr/bin && rm -f clang clang++
7.再次更新 APT 库,并安装 LLVM/Clang 的编译器、调试器和链接器:
root@raspberrypi4:~# sudo apt update
root@raspberrypi4:~# sudo apt install -y clang-9 lldb-9 lld-9
8.创建“clang-9”和“clang++-9”编译器的相应符号链接,安装:
root@raspberrypi4:~# cd /usr/bin && ln -s clang-9 clang
root@raspberrypi4:~# cd /usr/bin && ln -s clang++-9 clang++
9.最后,您必须能够在 bash 控制台中使用“clang”和“clang++”命令:
root@raspberrypi4:~# clang --version && clang++ --version
这里,让我们检查使用上面的命令安装的 LLVM/Clang 的版本。
使用这些命令后,您必须看到以下输出:
clang version 9.0.1-6+rpi1~bpo10+1
Target: armv6k-unknown-linux-gnueabihf
Thread model: posix
InstalledDir: /usr/bin
clang version 9.0.1-6+rpi1~bpo10+1
Target: armv6k-unknown-linux-gnueabihf
Thread model: posix
InstalledDir: /usr/bin
下载并构建 hipSYCL 库发行版
另一个重要的步骤是下载和构建开源 hipSYCL 库 staging distribution,从它的源代码,贡献给 GitHub。
这通常通过完成以下步骤来完成:
1.下载 hipSYCL 项目的发行版,从 GitHub 克隆它:
root@raspberrypi4:~# git clone https://github.com/llvm/llvm-project llvm-project
root@raspberrypi4:~# git clone --recurse-submodules [https://github.com/illuhad/hipSYCL](https://github.com/illuhad/hipSYCL)
Aksel Alpay 的 hipSYCL 项目的发行版依赖于另一个项目 LLVM/Clang 的开源项目。这就是为什么我们通常需要克隆两个发行版来“从头”构建 hipSYCL 库运行时。
2.通过使用“export”和“env”命令,以及将下面的特定行附加到。bashrc 配置文件脚本:
export LLVM_INSTALL_PREFIX=/usr
export LLVM_DIR=~/llvm-project/llvm
export CLANG_EXECUTABLE_PATH=/usr/bin/clang++
export CLANG_INCLUDE_PATH=$LLVM_INSTALL_PREFIX/include/clang/9.0.1/includeecho "export LLVM_INSTALL_PREFIX=/usr" >> /root/.bashrc
echo "export LLVM_DIR=~/llvm-project/llvm" >> /root/.bashrc
echo "export CLANG_EXECUTABLE_PATH=/usr/bin/clang++" >> /root/.bashrc
echo "export CLANG_INCLUDE_PATH=$LLVM_INSTALL_PREFIX/include/clang/9.0.1/include" >> /root/.bashrcenv LLVM_INSTALL_PREFIX=/usr
env LLVM_DIR=~/llvm-project/llvm
env CLANG_EXECUTABLE_PATH=/usr/bin/clang++
env CLANG_INCLUDE_PATH=$LLVM_INSTALL_PREFIX/include/clang/9.0.1/include
3.在 hipSYCL 项目的主目录下创建并切换到~/hipSYCL/build 子目录:
root@raspberrypi4:~# mkdir ~/hipSYCL/build && cd ~/hipSYCL/build
4.使用“cmake”实用程序配置 hipSYCL 项目的源代码:
root@raspberrypi4:~# cmake -DCMAKE_INSTALL_PREFIX=/opt/hipSYCL ..
5.使用 GNU 的“make”命令构建并安装 hipSYCL 运行时库:
root@raspberrypi4:~# make -j $(nproc) && make install -j $(nproc)
6.将 libhipSYCL-rt.iso 运行时库复制到 Raspbian 的默认库位置:
root@raspberrypi4:~# cp /opt/hipSYCL/lib/libhipSYCL-rt.so /usr/lib/libhipSYCL-rt.so
7.设置使用 hipSYCL 运行时库和 LLVM/Clang 编译器构建源代码所需的环境变量:
export PATH=$PATH:/opt/hipSYCL/bin
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/opt/hipSYCL/include
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/opt/hipSYCL/include
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/hipSYCL/libecho "export PATH=$PATH:/opt/hipSYCL/bin" >> /root/.bashrc
echo "export C_INCLUDE_PATH=$C_INCLUDE_PATH:/opt/hipSYCL/include" >> /root/.bashrc
echo "export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/opt/hipSYCL/include" >> /root/.bashrc
echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/hipSYCL/lib" >> /root/.bashrcenv PATH=$PATH:/opt/hipSYCL/bin
env C_INCLUDE_PATH=$C_INCLUDE_PATH:/opt/hipSYCL/include
env CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/opt/hipSYCL/include
env LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/hipSYCL/lib
在 Raspberry Pi 4B+上运行 C++17 中的并行 CL/SYCL 代码
由于我们最终已经完成了 LLVM/Clang 和 hipSYCL 库的安装和配置,所以强烈建议构建并运行“matmul_hipsycl”示例的可执行文件,确保一切正常运行:
以下是从源代码构建以下示例的最常见步骤:
rm -rf ~/sources
mkdir ~/sources && cd ~/sources
cp ~/matmul_hipsycl.tar.gz ~/sources/matmul_hipsycl.tar.gz
tar -xvf matmul_hipsycl.tar.gz
rm -f matmul_hipsycl.tar.gz
上面的一组命令将创建~/source 子目录,并从 matmul_hipsycl.tar.gz 归档文件中提取 sample 的源代码。
要构建示例的可执行文件,请使用 GNU 的“make”命令:
root@raspberrypi4:~# make all
这将调用“clang++”命令来构建可执行文件:
syclcc-clang -O3 -std=c++17 -o matrix_mul_rpi4 src/matrix_mul_rpi4b.cpp -lstdc++
该命令将使用最高级别的代码优化(例如 O3)来编译特定的 C++17 代码,并将其与 C++标准库运行时相链接。
注: 随着库运行时,hipSYCL 项目的建立,还提供了同样的‘syc LCC’和‘syc LCC-clang’工具,用于构建并行代码,在 C++17 中,使用 hipSYCL 库实现。这些工具的使用与“clang”和“clang++”命令的常规用法略有不同。但是,仍然可以使用“syclcc”和“syclcc-clang ”,指定与原始“clang”和“clang++”命令相同的编译器和链接器选项。
使用这些工具执行编译后,使用下面列出的命令向编译器生成的“matrix_mul_rpi4”文件授予执行权限:
root@raspberrypi4:~# chmod +rwx matrix_mul_rpi4
在 bash 控制台中运行可执行文件:
root@raspberrypi4:~# ./matrix_mul_rpi4
运行之后,执行将会以以下输出结束:
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *Multiplication C = A x B:Matrix C:323 445 243 343 363 316 495 382 463 374
322 329 328 388 378 395 392 432 470 326
398 357 337 366 386 407 478 457 520 374
543 531 382 470 555 520 602 534 639 505
294 388 277 314 278 330 430 319 396 372
447 445 433 485 524 505 604 535 628 509
445 468 349 432 511 391 552 449 534 470
434 454 339 417 502 455 533 498 588 444
470 340 416 364 401 396 485 417 496 464
431 421 325 325 272 331 420 385 419 468 Execution time: 5 ms
或者,我们可以通过安装和使用以下实用程序来评估正在执行的并行代码的性能:
root@raspberrypi4:~# sudo apt install -y top htop
使用已安装的“htop”实用程序,可以在运行并行代码可执行文件时直观显示 CPU 和系统内存的利用率:

LLVM/Clang-9.x.x 编译器和 Aksel Alpay 的 hipSYCL 安装和配置脚本
用于安装和配置 LLVM/Clang-9.x.x Arm/Aarch64 编译器和 Aksel Alpay 的 hipSYCL 库开源发行版的自动化 bash 脚本
#!/bin/sh
sudo apt update
sudo apt install -y bison flex python python3 snap snapd git wget
sudo snap install cmake --classic
sudo snap install clangd --classic
sudo apt install -y libc++-dev libc++1 libc++abi-dev libc++abi1 libpthread-stubs0-dev libpthread-workqueue-dev
sudo apt install -y clang-format clang-tidy clang-tools clang libc++-dev libc++1 libc++abi-dev libc++abi1 libclang-dev libclang1 liblldb-dev libllvm-ocaml-dev libomp-dev libomp5 lld lldb llvm-dev llvm-runtime llvm python-clang libboost-all-dev
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster main" >> /etc/apt/sources.list.d/raspi.list
echo "deb-src http://apt.llvm.org/buster/ llvm-toolchain-buster main" >> /etc/apt/sources.list.d/raspi.list
cd ~
sudo apt update
sudo apt install -y clang-9 lldb-9 lld-9
cd /usr/bin && rm -f clang clang++
cd /usr/bin && ln -s clang-9 clang
cd /usr/bin && ln -s clang++-9 clang++
cd ~
git clone https://github.com/llvm/llvm-project llvm-project
git clone --recurse-submodules https://github.com/illuhad/hipSYCL
export LLVM_INSTALL_PREFIX=/usr
export LLVM_DIR=~/llvm-project/llvm
export CLANG_EXECUTABLE_PATH=/usr/bin/clang++
export CLANG_INCLUDE_PATH=$LLVM_INSTALL_PREFIX/include/clang/9.0.1/include
echo "export LLVM_INSTALL_PREFIX=/usr" >> /root/.bashrc
echo "export LLVM_DIR=~/llvm-project/llvm" >> /root/.bashrc
echo "export CLANG_EXECUTABLE_PATH=/usr/bin/clang++" >> /root/.bashrc
echo "export CLANG_INCLUDE_PATH=$LLVM_INSTALL_PREFIX/include/clang/9.0.1/include" >> /root/.bashrc
env LLVM_INSTALL_PREFIX=/usr
env LLVM_DIR=~/llvm-project/llvm
env CLANG_EXECUTABLE_PATH=/usr/bin/clang++
env CLANG_INCLUDE_PATH=$LLVM_INSTALL_PREFIX/include/clang/9.0.1/include
mkdir ~/hipSYCL/build && cd ~/hipSYCL/build
cmake -DCMAKE_INSTALL_PREFIX=/opt/hipSYCL ..
make -j $(nproc) && make install -j $(nproc)
cp /opt/hipSYCL/lib/libhipSYCL-rt.so /usr/lib/libhipSYCL-rt.so
export PATH=$PATH:/opt/hipSYCL/bin
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/opt/hipSYCL/include
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/opt/hipSYCL/include
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/hipSYCL/lib
echo "export PATH=$PATH:/opt/hipSYCL/bin" >> /root/.bashrc
echo "export C_INCLUDE_PATH=$C_INCLUDE_PATH:/opt/hipSYCL/include" >> /root/.bashrc
echo "export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/opt/hipSYCL/include" >> /root/.bashrc
echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/hipSYCL/lib" >> /root/.bashrc
env PATH=$PATH:/opt/hipSYCL/bin
env C_INCLUDE_PATH=$C_INCLUDE_PATH:/opt/hipSYCL/include
env CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/opt/hipSYCL/include
env LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/hipSYCL/lib
echo "\n*** CONGRATULATIONS!!! YOU'RE ALL SET! ***\n\n"
echo "*** BUILDING MATMUL PROJECT FOR RASPI4B+ ***\n\n"
rm -rf ~/sources
mkdir ~/sources && cd ~/sources
cp ~/matmul_hipsycl.tar.gz ~/sources/matmul_hipsycl.tar.gz
tar -xvf matmul_hipsycl.tar.gz
rm -f matmul_hipsycl.tar.gz
make all
echo "\n*** RUNNING PARALLEL MATMUL (CL/HIPSYCL) EXECUTABLE ON RASPI4B+ ***\n"
./matrix_mul_rpi4 > output.txt
cat output.txt
rm -f output.txt && cd ~
echo "\n*** SUCCESS!!! ***\n\n"
总之…
微 FPGAs,以及具有计算能力的口袋大小的 GPGPUs,通过 GPIO 或 USB 接口从外部连接到物联网板,是物联网并行计算的下一个巨大进步。使用微型 FPGAs 和 GPGPUs 提供了并行执行更复杂和“繁重”计算的机会,大大提高了实际性能,同时实时处理大量大数据。
显然,物联网并行计算的另一个重要方面是继续开发特定的库和框架,提供 CL/SYCL 模型层规范,从而提供异构计算平台(XPU)支持。目前,这些库的最新版本仅提供对将并行代码执行卸载到主机 CPU 加速目标的支持,因为其他加速硬件,如用于纳米计算机的小型 GPGPUs 和 FPGAs,目前还没有由其供应商设计和制造。
事实上,使用 Raspberry Pi 和其他特定物联网板进行并行计算是软件开发人员和硬件技术人员的一个特别兴趣点,在与物联网并行运行时对现有计算流程进行性能评估。
总之,利用基于物联网的并行计算通常有利于旨在实时收集和大规模处理大数据的基于云的解决方案的整体性能。因此,它会积极影响机器学习(ML)和数据分析本身的质量。
平行坐标用 Plotly 绘制
实践教程
规范化还是标准化?

图片来自 Unspalsh 的 Abbas Therani
数据可视化是讲故事的工具。数据科学家通常需要找到隐藏在具有多个数值变量的多维数据集中的模式或趋势。平行图或平行坐标图 (PCPs)是数据可视化图表,适用于同时比较多个数值变量,这些变量具有不同的刻度和/或不同的测量单位。
在 PCP 中,每个数值变量都有自己的轴。除此之外,所有的轴都画成垂直,平行,等间距。数据集的记录被表示为跨每个轴连接的线段。这些线段形成一条折线,该折线由不同数值变量在其相应轴上的取值产生。
下图显示了一个带有四个数值变量和每个变量六条记录的 PCP。所以我们有四个垂直的、平行的、等间距的轴和它们相应的标签。有六条由四个连接点组成的多段线,每个轴上有一个。根据第五个变量采用的数值,每条折线都有其特定的颜色。

图 1:平行坐标图的示意图。这个图形是作者用 Plotly 绘制的。
根据折线之间的平行度分析不同数值变量之间的模式、趋势或聚类:连接平行轴的线的一定平行度意味着相应变量之间的正关系;连接平行轴的线之间的交叉( X 形)暗示它们之间的负关系;没有确定顺序的交叉意味着不存在特定的关系。
由于变量 4 与变量 1 和变量 3 之间的数量级不同,任何试图找出图 1 所示变量之间的模式或关系的尝试都会产生误导:在绘制该图之前,我们必须进行数据标准化任务。标准化或缩放技术将原始数据转换成新的比例,允许我们比较最初不同幅度的值。
两种经典的缩放技术被称为规范化和标准化。规格化在 PCP 中最小和最大值之间缩放每个轴。在此过程中,最小值被设置为 0,最大值被设置为 1,中间的所有其他值也相应地进行转换。标准化使用平均值和标准偏差将原始数值转换为通用刻度。
用 PCP 讲故事通常很难沟通,即使对于技术和科学观众来说也是如此,因为没有办法避免典型的折线混乱。重排和刷的手法可以增强剧情的可读性。
重新排序基于相邻变量之间的关系、聚类或模式比不相邻变量之间的关系、聚类或模式更容易可视化的想法。重新排序基本上包括改变垂直轴的顺序,直到显示中出现某种模式或关系。
刷通过高亮显示所选线条而淡化其他线条,最大限度地减少杂乱并揭示关系。发散色标适用于那些没有内置刷牙方法的可视化工具。
平行坐标用 Plotly 绘制
我们使用五氯苯酚分析了 Kaggle 竞赛的数据,这些数据与评估某些化学特性如何影响葡萄牙" Vinho Verde "葡萄酒红色变种的质量有关[ Cortez 等人,2009 年 ]。葡萄酒的感官质量会受到下列化学或物理特性的影响:酒精、残糖、密度、pH 值、固定酸度、挥发性酸度、柠檬酸、氯化物、游离二氧化硫、总二氧化硫和硫酸盐。为数据集中的每种葡萄酒提供了介于 0(差)和 10(优)之间的质量分数。
首先,我们分别导入 Plotly Express 为 px、和库 Numpy & Pandas 为 np 和 pd 。我们包含了一个关闭特定警告的功能。
import plotly.express as px
import numpy as np
import pandas as pd
pd.set_option('mode.chained_assignment', None)
然后我们使用 read_csv 函数将逗号分隔值(csv)文件读入相应的数据帧( df) 。我们使用 describe() 方法生成描述性统计数据(平均值,标准差。戴夫。、最小值、最大值等。)总结了数据集的集中趋势、形状和分散性。然后,我们使用 isnull() 来检测缺失值。
df = pd.read_csv(path +'winequality-red.csv', sep=';',
header = 0,
names=['fixed acidity', 'volatile acidity',
'citric acidity','residual sugar',
'chlorides','free sulfur dioxide',
'total sulfur dioxide','density', 'pH',
'sulphates', 'alcohol', 'quality'])first10 = df.head(10)
stat_basic = df.describe()
count_null = df.isnull().sum()
下面的屏幕截图显示了数据集的前十条记录:

我们决定使用 Z 分数来消除异常值。请记住,Z 得分是数据集的每个数据点偏离平均值的标准偏差数。我们使用来自 PythonScyPy开源科学库的 stats.zscore(df) 计算 Z 分数。经过几次测试后,我们选择值 6 作为异常值过滤阈值。
*from scipy import statszT = np.abs(stats.zscore(df))df2 = df[(zT < 6).all(axis =1)]*
我们决定将数据集分为高品质和低品质葡萄酒,以减少行数,最大限度地减少混乱,并提高模式检测:
*df_highq = df2[df2['quality'] > 6]df_lowq = df2[df2['quality'] <= 6]*
数据集的屏幕截图清楚地显示了在绘制 PCP 之前需要一个缩放任务。首先,我们使用 scikit-learn 对象 MinMaxScaler 对数据集进行规范化。记住最小最大缩放器从原始值重新缩放数据,所以所有的新值都在 0-1 的范围内。
*from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()df_highqN = df_highqdf_highqN[['fixed acidity', 'volatile acidity', 'citric acidity',
'residual sugar', 'chlorides','free sulfur dioxide',
'total sulfur dioxide','density',
'pH','sulphates','alcohol']]
=scaler.fit_transform(df_highqN[[
'fixed acidity', 'volatile acidity',
'citric acidity','residual sugar',
'chlorides','free sulfur dioxide',
'total sulfur dioxide','density', 'pH',
'sulphates', 'alcohol']])*
对于本文中的平行坐标图,Plotly Express 函数为*px.parallel_coordinates*,对应的参数为:*data_frame*;``*color=***data _ frame中一列的名称,其值用来表示线条的颜色; *dimensions=*在 data_frame 中列的名称,其值用于画线;*color_continuous_scale=* 选择内置色标;*color_continuous_midpoint=* 使用发散色标时设置合适的中点值; *title=* 设置图名。**
**fig0 = px.parallel_coordinates(df_highqN, color="quality",
dimensions=['fixed acidity', 'volatile acidity',
'citric acidity', 'residual sugar',
'total sulfur dioxide','density', 'pH',
'sulphates', 'alcohol'
],
color_continuous_scale=px.colors.diverging.Tealrose,
color_continuous_midpoint=7.5,
title = 'PCP High Quality Wines Without Reordering')fig0.show()**

图 2:没有重新排序的高品质葡萄酒的平行坐标图。作者用 Plotly 开发的图。
真的很乱。没有办法获得任何有用的信息,所以我们决定尝试不同的重新排序方案,直到得到图 3。
**fig1 = px.parallel_coordinates(df_highqN, color="quality",
dimensions=['residual sugar', 'chlorides',
'sulphates', 'volatile acidity', 'density',
'pH', 'citric acidity', 'alcohol'
],
color_continuous_scale=px.colors.diverging.Tealrose,
color_continuous_midpoint=7.5,
title = 'PCP High Quality Wines After Reordering')fig1.show()**

图 3:重新排序的高品质葡萄酒的平行坐标图。作者用 Plotly 开发的图。
该图显示,高品质的葡萄酒具有低水平的残留糖、氯化物和硫酸盐,以及高水平的酒精和柠檬酸。
让我们看看低品质葡萄酒会发生什么:
**df_lowq2 = df_lowqdf_lowq2[['fixed acidity', 'volatile acidity', 'citric acidity',
'residual sugar', 'chlorides','free sulfur dioxide',
'total sulfur dioxide','density', 'pH',
'sulphates', 'alcohol']] =
scaler.fit_transform(df_lowq2[[
'fixed acidity', 'volatile acidity', 'citric acidity',
'residual sugar', 'chlorides','free sulfur dioxide',
'total sulfur dioxide','density', 'pH',
'sulphates', 'alcohol']])fig2 = px.parallel_coordinates(df_lowq2, color="quality",
dimensions=['residual sugar', 'chlorides',
'sulphates', 'volatile acidity',
'alcohol', 'pH','density',
'citric acidity'
],
color_continuous_scale=px.colors.diverging.RdYlBu,
color_continuous_midpoint=4,
title = 'PCP Low Quality Wines After Reordering')fig2.show()**

图 4:重新排序的低品质葡萄酒的平行坐标图。作者用 Plotly 开发的图。
似乎低质量的葡萄酒含有高水平的氯化物、硫酸盐和挥发性酸度。
最后,我们想知道标准化技术的扩展是否改善了故事的讲述。
**from sklearn.preprocessing import StandardScaler
scalerST = StandardScaler()df_highqS = df_highqdf_highqS[['fixed acidity', 'volatile acidity', 'citric acidity',
'residual sugar', 'chlorides','free sulfur dioxide',
'total sulfur dioxide','density', 'pH',
'sulphates', 'alcohol']] =
scalerST.fit_transform(df_highqS[[
'fixed acidity', 'volatile acidity', 'citric acidity',
'residual sugar', 'chlorides','free sulfur dioxide',
'total sulfur dioxide','density', 'pH',
'sulphates', 'alcohol']])fig3 = px.parallel_coordinates(df_highqS, color="quality",
dimensions=['residual sugar', 'chlorides',
'sulphates', 'fixed acidity', 'density',
'volatile acidity','pH',
'citric acidity'
],
color_continuous_scale=px.colors.diverging.Tealrose,
color_continuous_midpoint=7.5,
title= 'PCP High Quality Wines After Standardization')fig3.show()**

图 5:标准化高品质葡萄酒的平行坐标图。作者用 Plotly 开发的图。
可以看出,该显示等同于图 3 的显示,只是难以显示不对应的化学和物理性质的负值。
****总之,平行坐标图是非常复杂的图形,需要几个预处理步骤。它们仅推荐用于具有大量数值变量的数据集。始终使用标准化作为缩放技术。它们的复杂性不允许保证足够的故事。
如果你对这篇文章感兴趣,请阅读我以前的(【https://medium.com/@dar.wtz】T2):
分歧棒线,为什么&如何,用分歧讲故事
**
斜率图表,为什么和如何,用斜率讲故事
**
Python 中的平行句挖掘
使用语言无关的 BERT 句子嵌入模型的双文本挖掘

照片由乔尼·卡斯帕里在 Unsplash 拍摄
今天的主题是关于在两个单语文件(无序的句子)上进行双文本挖掘,以找到平行的句子。在本教程中,您将学习使用一个名为 LaBSE 的语言无关的 BERT 句子嵌入模型。
从 Tensorflow 中心,它表明 LaBSE 模型
…经过训练和优化,专门为相互翻译的双语句子对生成相似的表示。因此,它可以用于在更大的语料库中挖掘句子的翻译。
例如,给定以下文本:
That is a happy person
当使用来自 LaBSE 模型的嵌入时,您将获得以下句子相似度:
That is a happy dog
0.741That is a very happy person
0.897Today is a sunny day
0.450
事实上,您也可以在翻译文本上识别句子相似性。让我们重复使用前面的句子
That is a happy person
并与下面的翻译文本进行比较。
Questo è un cane felice
0.624Questa è una persona molto felice
0.757Oggi è una giornata di sole
0.422Questa è una persona felice
0.830
它表明
Questa è una persona felice
与原始句子最相似,因此可能是其翻译文本。
因此,您可以使用这个概念来识别在两个不同的语料库上共享高相似度值的翻译对。假设一个英语语料库包含以下句子
This is an example sentences.
Good morning, Peter.
Hello World!
你得到了另一个意大利语语料库,如下所示:
Salve, mondo!
L'esame è dietro l'angolo
Buongiorno, Peter.
通过利用 LaBSE 模型,您将能够执行 bitext 挖掘,以便获得以下正确的平行句子:
Hello World! Salve, mondo!
Good morning, Peter. Buongiorno, Peter.
设置
强烈建议您在继续安装之前创建一个新的虚拟环境。
在那之前,你需要在你的系统中安装 FAISS 。有一个库为 cpu 和 gpu 安装提供 python 轮子。根据您的使用情况,按如下方式安装它:
# CPU
pip install faiss-cpu# GPU
pip install faiss-gpu
然后,运行下面的命令来安装sentence-transformer,这是一个用于多语言句子、段落和图像嵌入的 BERT 模型。
pip install sentence-transformers
这个 Python 包不仅限于挖掘平行句。它可用于各种应用,例如:
semantic search:给定一个句子,在大集合中寻找语义相似的句子。image search:将图像和文本映射到同一个向量空间。这允许给定用户查询的图像搜索。- 给定一篇长文档,找出 k 个句子,这些句子对内容进行了简明扼要的总结。
clustering:根据句子的相似性将句子分组。cross-encoder:两个句子同时出现在变压器网络中,得到一个分数(0…1),表示相似性或标签。
官方存储库包含一些示例脚本,您可以根据您的用例直接使用:
[bitext_mining_utils.py](https://github.com/UKPLab/sentence-transformers/blob/master/examples/applications/parallel-sentence-mining/bitext_mining_utils.py):计算分数、执行 knn 聚类和打开文件的实用程序脚本。[bitext_mining.py](https://github.com/UKPLab/sentence-transformers/blob/master/examples/applications/parallel-sentence-mining/bitext_mining.py):读入两个文本文件(每行一句),输出平行句为 gzip 文件(parallel-sentences-out.tsv.gz)。[bucc2018.py](https://github.com/UKPLab/sentence-transformers/blob/master/examples/applications/parallel-sentence-mining/bucc2018.py):BUCC 2018 共享任务关于寻找平行句的示例脚本。
概念
让我们深入探讨 bitext 挖掘的底层过程。流程如下:
- 使用 LaBSE 将所有句子编码成各自的编码,LaBSE 是一种语言无关的 BERT 句子嵌入模型,支持多达 109 种语言。
- 下一步是找到所有句子的 k 个最近邻句子(基于两个方向)。使用近似最近邻(ANN)算法,k 应介于 4 和 16 之间。
- 之后,根据 Artetxe 和 Schwenk(第 4.3 节)的论文中的公式,对所有可能的句子组合打分。分数可以高于 1。
- 然后对分数进行排序。高分说明最有可能是骈文。大约 1.2-1.3 的阈值表示高质量的翻译。
虽然在大多数运行中转化率较低,但是该方法适合于从开源的大单语语料库中获取附加的平行句。例如,您可以从维基百科 EN-IT 中提取文本,为您的 EN-IT 机器翻译模型获取额外的数据集。
bucc2018
对于bucc2018.py,数据集必须采用以下语法(由制表符分隔的 id 和文本):
id text
例如(每个数据点一行):
en-000000005 The Bank of England’s balance sheet is also at 20% of GDP.en-000000007 The most important of these was the creation of regional federations of savings banks.en-000000008 Alongside CECA the central or main clearing bank for Spanish savings banks developed.en-000000010 By 1970 the CECA had attained considerable credibility as a savings bank association.
只需从 BUCC2018 任务中获取数据集,并修改以下几行:
source_file = "bucc2018/de-en/de-en.training.de" target_file = "bucc2018/de-en/de-en.training.en" labels_file = "bucc2018/de-en/de-en.training.gold"
该脚本将使用source_file和target_file执行 bitext 挖掘。然后,它将根据labels_file评估结果。
按如下方式运行它:
python bucc2018.py
在初始运行期间,它将从 HuggingFace 下载模型及其依赖项。如果您使用基于 Linux 的系统,它将存储在以下位置:
`~/.cache/huggingface/transformers`
接下来,它将对 PCA 的训练嵌入进行编码,以减少维数,从而减少内存消耗,代价是性能略有下降。此外,嵌入将被保存在本地,以便以后可以直接从光盘加载。
您应该在终端上得到以下输出:
Shape Source: (32593, 128)
Shape Target: (40354, 128)
Perform approx. kNN search
Done: 5.04 sec
Perform approx. kNN search
Done: 5.03 sec
17039
Best Threshold: 1.22599776371784
Recall: 0.9691714836223507
Precision: 0.9853085210577864
F1: 0.9771733851384167
一旦这个过程完成,就会生成一个名为result.txt的文件:
1.7022742387767527 de-000012693 en-000020744
1.7002160776448074 de-000012458 en-000038209
1.6910422179786977 de-000019004 en-000000538
1.6634726798079071 de-000017671 en-000033396
1.6572470385575508 de-000028406 en-000014338
1.6525712862491655 de-000022783 en-000027704
每一行由以下项目组成:
- 得分
- 源句子的 id
- 目标句子的 id
分数表示翻译对的质量。越高越好,并按降序排列。高质量的翻译对通常在 1.3 以上。
比特矿业公司
另一方面,bitext_mining.py接受不带 id 的普通每行格式的句子
The Bank of England’s balance sheet is also at 20% of GDP.The most important of these was the creation of regional federations of savings banks.Alongside CECA the central or main clearing bank for Spanish savings banks developed.By 1970 the CECA had attained considerable credibility as a savings bank association.
在以下几行修改数据集的路径:
source_file = "data/so.txt.xz"
target_file = "data/yi.txt.xz"
这个脚本还过滤掉长度不在min_sent_len和max_sent_len字符之间的句子。根据您自己的用例修改代码中的以下变量:
min_sent_len = 10
max_sent_len = 200
在您的终端上运行以下命令:
python bitext_mining.py
您应该会在控制台上看到以下输出:
Read source file
Read target file
Shape Source: (6149, 128)
Shape Target: (5579, 128)
Perform approx. kNN search
Done: 0.84 sec
Perform approx. kNN search
Done: 0.68 sec
2222
它将在同一个目录中生成一个名为parallel-sentences-out.tsv.gz的新 zip 文件。这个脚本将输出句子而不是句子 id 作为输出:
score source_sentence target_sentence
如果您遇到类似Segmentation fault (core dumped)的错误,请仔细检查数据集。你需要有足够多的句子才能让它发挥作用。上述结果在 6149 个源句子和 5579 个目标句子上进行了测试。
结论
让我们回顾一下你今天所学的内容。
本文首先简要介绍了 LaBSE 模型,以及如何使用它来计算不同语言的两个文本之间的相似性得分。
然后,它进入安装过程。此外,它还解释了示例脚本和 bitext 挖掘背后的关键概念。
稍后,它将逐步介绍如何运行两个示例脚本,即bucc2018.py和bitext_mining.py。
感谢你阅读这篇文章。祝你有美好的一天!
参考
平行集和冲积图
为什么和如何

美国地质勘探局在 Unsplash 上拍摄的照片
大约在公元前 300 年,在托勒密一世统治时期,发生了第一个影响宇宙数学和几何的“我的生活和工作:欧几里德。它的主要成就是在一个公理系统中汇编了所有以前的几何知识,这在数学史上是第一次,在一篇名为元素的论文中。
欧几里德发展的系统把假设分成两类:1。-未经论证就被接受的原则、假设;2.-定理,基于之前的假设而被接受的假设。反过来,他将原则细分为公理和公设。后者是关于几何对象的陈述,为了进行演示,应该将其视为真实的。
《几何原本》是几何推理的主要来源,直到《非欧几何》的出现,也是继《圣经》之后西方世界的第二本畅销书。作为西方科学的基础基石,它包括其著名的第五公设或纬线公设,为二维几何建立了以下内容:
如果一条直线落在两条直线上,使同一侧的内角小于两个直角,那么这两条直线,如果无限长,就在内角小于两个直角的那一侧相交。1.
虽然该公设并没有特别提到平行线,但它引入了平行的概念。其中一些想法在两个数据可视化图中出现:平行集合图和冲积图。

图 1:欧几里得的图像;资料来源:FamousScientists.org[2]
原因:两个图表都允许可视化多维分类数据集,以便执行此类数据的探索和分析任务。对于连续数据分析来说,这两个图都有其等效物:平行集图 (PSD)类似于 Sankey 图(SD)(https://towardsdatascience . com/Sankey-diagrams-1c 2e 1 f 469 c 85),而冲积图 (AD)对应于平行坐标图(PCP)(https://towardsdatascience . com/Parallel-Coordinates-plots-6 fcfa 066 dcb 3)。
说到类别,记住它们指的是方法、姓氏、城市、公司、品牌、日期、年份等定性元素。如果一组变量的数据或观察值可以被分配到不重叠的类别中,那么这组变量称为分类变量。类别可以是名词或序数:方法、公司和品牌是名词;否则,有序分类变量允许对类别进行排序[低、中、中高、高]或[稀疏、充足、丰富、过多]。分类数据也可以间隔显示。
由于公司和政府不断收集关于产品、客户、调查、人口普查和其他分类变量的数据,因此需要一些技术来分析构成这些数据集的多个类别之间的关系。从这个意义上说,平行集合图的主要功能是可视化两个或更多分类变量的比例。当消息的性质包括相同数据集的比较、分布或流动时,也使用它。冲积图的最佳实现是当它被用来显示一个系统随着时间的变化。它还用于分发和比较任务。
如何做:如维基百科所述,“冲积图是一种流程图,最初是用来表示网络结构随时间的变化。鉴于其视觉外观和对流动的强调,冲积图以冲积扇命名,冲积扇是由流水沉积的土壤自然形成的【3】。然而,数据可视化中使用的 ADs 是由带组成的,这些带代表以固定间隔绘制的节点块之间的流动。每个块的垂直大小与所表示的数据的频率成比例,并且条带的宽度与它们所表示的值的大小成比例。DA 显示以规则的间隔在不同阶段之间流动的彩色条带。当它们从一个阶段移动到下一个阶段时,丝带通常会改变颜色,以显示时间的流逝或分类变量的变化。
在技术文献中被引用最多的 DA 例子是 Michal Bojanowski,他是 R 语言的冲积包的创造者。图 1 显示了根据与 1912 年泰坦尼克号沉没相关的数据创建的图表。

图 2:冲积图,来源:Michal Bojanowski [4]
该图从左至右,从等级类别(乘客和船员)开始,首先按性别类别,然后按年龄类别,将他们分开,直到到达远洋客轮悲剧的幸存者或不幸存者的条件。很明显,阶级、性别、年龄和存活率是分类变量。
该图展示了以规则间隔绘制的节点块,其高度与所表示的数据的频率成比例。您还可以看到带状区的宽度与它们所代表的值的大小成正比。作为一个明确的时代精神,图表显示,大多数乘坐头等舱旅行的女性幸存下来,而大多数溺水对应的是乘坐三等舱旅行的船员和男性。
总之,冲积图显示了一组事件是如何跨类别维度分配的。
在 PSD,又名平行类别图中,每个类别变量都有自己的轴;此外,所有轴都是平行、垂直且等距的。每个轴由一列矩形表示,其中每个矩形的相对高度表示不同子类别或属性的值的相对频率。PSD 显示平行四边形,其宽度随着其通过不同阶段而变化。平行四边形的宽度与它们所代表的观察数量成正比。在第一阶段,颜色分布在主类别的子类别中,并且该颜色在所有剩余阶段都保持不变。
下图显示了一个有三个分类变量的 PSD,每个变量都有相应的子类别。利用*ParCats* 功能,使用*Plotly graphic library*绘制图形。以前你必须使用指令*import plotly.graph_objects as go*导入类graph.objects。您必须为参数*dimensions*提供值,以指示带有相应 *label*和*values*数据的轴或类别的数量,或者为属性 *counts* 提供值,以指示子类别或属性出现的频率。

图 3:作者用 Plotly [5]制作的平行集图。
可以在 PSD 中用表示连续变量的数据。为此,有必要首先将相应的轴划分为箱,然后将它们转换为分类变量。与桑基图不同,PSD 没有方向性,它们不显示方向性箭头,但是类别(轴)的排列可以(也应该)改变,以改善它们之间的比较和分布的交互分析(图 3)。虽然它们没有方向性,但它们可以水平或垂直放置,这取决于讲故事的方式。
总之,我们使用平行集图来显示高维分类数据或混合分类/连续数据。PSD 使用平行四边形来显示分类变量之间的比例或分布。
总结一下:平行集合图和冲积图允许探索和分析多维分类数据。它们是复杂的图表,不容易解释,但是随着多维度中与类别相关的数据数量的增加,它们变得越来越重要。
如果你发现了这篇感兴趣的文章,请阅读我之前的(https://medium.com/@dar.wtz):
分歧棒线,为什么&如何,用分歧讲故事
斜率图表,为什么和如何,用斜率讲故事
参考文献
[2]:“欧几里得。”著名的科学家。famousscientists.org。2018 年 6 月 24 日。网络。2021 年 1 月 11 日<www.famousscientists.org>。</www.famousscientists.org>
[4]:https://cran . r-project . org/web/packages/冲积/vignettes/冲积. html
https://plotly.com/python/parallel-categories-diagram/
Python 中的并行 web 请求
并行执行 web 请求可以显著提高性能。提议的 Python 实现使用队列和线程来创建一个简单的方法,节省了大量时间。

我最近发表了几篇文章,使用 Open Trip Planner 作为公共交通分析的来源。行程路线是通过其 REST API 从 OTP 获得的。OTP 在本地机器上运行,但是仍然需要花费大量时间来发出所有必需的请求。文章中显示的实现是顺序的。为了简单起见,我张贴了这个顺序实现,但在其他情况下,我使用并行实现。本文展示了执行大量 web 请求的并行实现。
虽然我有一些经验,但我发现教程很难掌握。这篇文章包含了我学到的经验,可以用来执行并行 web 请求。
该函数的核心是一个包含所有已执行请求的队列。在这种情况下,请求由其 url 指定:
python 包队列实现了一个有多个生产者和消费者的队列。这意味着可以从多个源( Queue.put() )填充队列,并且多个工作线程( Threads) 可以从队列中获得一个项目。默认实现是先进先出(FIFO)队列,这意味着添加的第一个元素是提取的第一个元素。为此,默认行为是好的。
第二个组成部分是工人。worker 从队列中获取一个元素,执行所需的逻辑,并对它可以从队列中获取的所有元素重复此操作。
当构造一个 worker 时,需要一个对队列的引用(第 2 行)。工作者扩展 Python 类线程。它从线程继承了 start() 方法。调用该方法时,会创建一个新线程,并在该线程中调用 run() 方法。第 7 行到第 15 行实现了 run 方法。第 9 行从队列中检索一个元素(这个调用也从队列中删除了这个元素)。当队列中没有元素时,它会一直阻塞,直到添加了新元素。因为我们将 URL 字符串添加到队列中,所以可以创建(第 12 行)并执行(第 13 行)一个请求。请求的结果被添加到这个工作者的结果列表中(第 14 行)。最后,第 15 行通知队列任务已经成功执行。
因为我们希望线程在所有调用完成后结束,所以我们必须实现一个停止机制。一种方法是调用带有超时值的 Queue.get() 方法。当队列中没有对象时,当超时超过时,抛出一个空异常。我个人不喜欢这个解决方案。异常是针对异常情况的,而不是针对预期的功能。所以这段代码在队列中使用了一个停止值,在这个例子中是一个空字符串。当检索到空字符串时, while 循环结束,从而结束 run- 方法。当 run 方法结束时,线程也自动结束。
将所有这些放在一起:
创建一个将执行并行 web 请求的方法。这些参数是 URL 列表和要创建的工作线程数。创建队列后(第 21–24 行),创建了一组工作线程。每个工作线程都连接到队列并启动。当从队列中检索到一个空字符串时,线程将被停止,因此对于每个 worker,一个空字符串将被添加到队列中(第 33–34 行)。因为我们有一个 FIFO 队列,这些将最后从队列中取出。通过加入 worker,在所有 worker 都结束时执行下一个代码。
所有工人都将结果存储在他们自己的内存空间中。所有这些结果必须在返回给调用者之前组合起来(第 40–42 行)。有了这个方法,用一行代码(第 46 行)就可以并行执行多个 web 调用。通过组合几个循环可以减少代码的大小,但是为了可读性,它们是分开的。
现在,是时候回答它在多大程度上提高了性能这个问题了。因此实现了一个小实验。通过向 OTP 的本地实例发送一千个请求,就可以确定工作人员数量的影响。本地服务器实例用于减少网络流量和互联网速度的影响。为了防止某种缓存,所有请求都是不同的。通过对 1000 个请求进行计时,我们可以确定一小时内可以进行的调用总数(吞吐量)。
生产量作为工人数量的函数进行计算和绘图:

吞吐量(作者图片)
只有一个工作人员时,性能相当于没有并行性的实现。由于线程的额外开销,它会稍微差一些,但与其他测量相比,这并不显著。
在没有线程的情况下,吞吐量是每小时 11000 个调用。通过添加螺纹,该值提高到 56.000。达到这个最佳值后,添加更多的线程并不能提高性能,甚至还会有所下降。该测试是在具有 8 个内核的 CPU 上执行的,因此找到 8 个内核时的最佳性能是意料之中的。OTP 是多线程实现的,8 个内核可以并行处理 8 个请求。根据系统上运行的其他作业,最佳数量等于或略少于内核数量。
在我们的 OTP 示例中,我们可以将吞吐量提高 5 倍。随着我的其他文章中制作 OTP 图的请求数量的增加,运行时间从 6 小时减少到略低于 1 小时。这种性能提升可以通过实现这种相对简单的多线程方法来实现。我希望它能为你节省一些宝贵的时间!
我希望你喜欢这篇文章。要获得使用 OTP 的灵感,请查看我的一些其他文章:
如果你喜欢这个故事,请点击关注按钮!
免责声明:本文包含的观点和看法仅归作者所有。
用 Python 中的 Dask 并行 XGBoost
扩展到非常大的数据集的机器学习

图片由 Billy Hunh 通过 Unsplash.com 提供
TL;博士;医生
开箱即用,XGBoost 无法在大于你电脑内存的数据集上进行训练;Python 会抛出一个MemoryError。本教程将向您展示如何通过利用 Dask 的分布式XGBoost来超越您的本地机器限制,只需对您现有的代码进行微小的更改。
具体来说,您将学习如何:
- 使用 Dask 在小型数据集上本地训练分布式 XGBoost 模型,
- 使用 Dask 和 Coiled 将您的分布式 XGBoost 模型扩展到云,以在大于内存的数据集上进行训练,
- 借助 Dask 核心团队的专业技巧,加快训练速度。
这是我们将使用的代码,如果你想直接进入的话。
关于代码的问题? 加入盘绕群落的松弛通道

作者创建的图像
用xgb.dask.train()进行平行模型训练
默认情况下,XGBoost 按顺序训练模型。这对于基本项目来说没什么问题,但是随着数据集和/或 ML 模型的大小增加,您可能需要考虑使用 Dask 在分布式模式下运行 XGBoost,以加快计算速度并减轻本地机器的负担。
XGBoost 自带原生 Dask 集成,使得并行训练多个模型成为可能。使用分布式 Dask 后端运行 XGBoost 只需要对常规的 XGBoost 代码进行两处修改:
- 用
dtrain = xgb.dask.DaskDMatrix(X_train, y_train)代替dtrain = xgb.DMatrix(X_train, y_train)T26 - 用
xgb.dask.train(client, params, dtrain, ...)替换xgb.train(params, dtrain, ...)
让我们用一个实际的数据集来看看这一点!
我们将使用存储在公共亚马逊 S3 桶中的合成 100GB 数据集。你需要一个免费的 Coiled Cloud 帐户来自己运行这个笔记本中的整个示例。
免责声明:我在 Coiled 工作,是一名数据科学传播者。 Coiled 由Dask的最初作者 Matthew Rocklin 创立,是一个面向分布式计算的开源 Python 库。
具有本地 Dask 集群的分布式 XGBoost
好了,让我们开始这个分布式派对吧!
首先,实例化一个本地版本的 Dask 分布式调度器,它将并行地编排训练你的模型。
from dask.distributed import Client, LocalCluster # local dask cluster
cluster = LocalCluster(n_workers=4)
client = Client(cluster)
client
由于这是合成数据,我们不会对数据做任何预处理。关于包含预处理的真实世界数据的例子,请看这本笔记本,它在 ARCOS 数据集的 20GB 子集上训练 XGBoost。当您完成预处理后,您可以使用 dask-ml 库创建您的训练和测试分割。
在本节中,我们将使用 data_local ,它是整个数据集的子集,只包含前 50 个分区。
from dask_ml.model_selection import train_test_split # Create the train-test split
X, y = data_local.iloc[:, :-1], data_local["target"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, shuffle=True, random_state=2
)
现在您已经准备好开始训练您的 XGBoost 模型了!
让我们在这个例子中使用默认参数。
import xgboost as xgb
# Create the XGBoost DMatrices
dtrain = xgb.dask.DaskDMatrix(client, X_train, y_train)
dtest = xgb.dask.DaskDMatrix(client, X_test, y_test) # train the model
output = xgb.dask.train(
client,
params,
dtrain,
num_boost_round=4,
evals=[(dtrain, 'train')]
)
然后,您可以使用训练好的模型以及测试分割来进行预测。
# make predictions
y_pred = xgb.dask.predict(client, output, dtest)
如您所见,与常规的 XGBoost 代码相比,没有什么变化。我们只是用了xgb.dask.DaskDMatrix代替了xgb.DMatrix,用xgb.dask.train()代替了xgb.train()。
使用 Coiled 将 XGBoost 扩展到云
现在让我们扩展这个工作流,通过将 XGBoost 扩展到云来处理整个 100GB 的数据集。
你需要对我们上面写的代码进行 2 修改:
- 将 Dask 连接到云中的盘绕式集群,而不是我们的本地 CPU 核心,
- 使用整个 100GB 数据集,而不是本地子集。
我们将首先在云中启动一个 Dask 集群,它可以在整个数据集上运行您的管道。要运行本节中的代码,您需要一个 Coiled Cloud 帐户。用你的 Github 证书登录盘绕云来创建一个。
注意,如果您使用盘绕空闲层,您必须将 n_workers 参数减少到 25,因为该层最多允许 100 个并发内核。
import coiled cluster = coiled.Cluster(
name="xgboost",
software="coiled-examples/xgboost",
n_workers=50,
worker_memory='16Gib',
shutdown_on_close=False,
)
最后,让 Dask 在您的盘绕式集群上运行计算。
# connect Dask to your Coiled cluster
from dask.distributed import Client
client = Client(cluster)
对于这一部分,请确保使用包含 2750 个分区的整个 dataframe 数据,而不是我们上面使用的仅包含前 10 个分区的data_local子集。
# download data from S3
data = dd.read_parquet(
"s3://coiled-datasets/synthetic-data/synth-reg-104GB.parquet/",
compression="lz4",
storage_options={"anon": True, 'use_ssl': True},
) data

现在,您可以从上面重新运行所有相同的代码。所有的计算都将在云中的集群上运行,而不是在本地机器上。这意味着您将拥有数量级以上的计算能力!
%%time
# train the model
output = xgb.dask.train(
client,
params,
dtrain,
num_boost_round=5,
evals=[(dtrain, 'train')]
)
CPU times: user 17.5 s, sys: 3.43 s, total: 20.9 s Wall time: 3min 24s
正如您在 Coiled Cloud 仪表盘中看到的,在云中培训 100GB 数据的 XGBoost 花费了 0.83 美元。

训练完成后,我们可以保存模型并关闭集群,释放资源。如果我们出于某种原因忘记这样做,Coiled 会在 20 分钟不活动后自动关闭集群,以帮助避免不必要的成本。
# Shut down the cluster
client.cluster.close()
加速 XGBoost 训练的专业技巧
最后,我们从 Dask 核心团队收集了一些专业技巧,帮助您加快 XGBoost 训练:
- 提升性能的第一件事是增加集群中 Dask workers 的数量。这将加速你的计算。
- 将列重新转换为占用内存较少的数据类型。例如,尽可能将
float64转换成int16。这将减少数据帧的内存负载,并加快训练速度。 - 使用 Dask Dashboard 来发现瓶颈,并找出提高代码性能的机会。观看 Dask 的原作者 Matt Rocklin 解释如何充分利用 Dask Dashboard 这里。
- 注意非托管内存。阅读 Dask 核心贡献者 Guido Imperiale 的博客,了解如何解决 Dask workers 中的非托管内存问题。
https://coiled.io/blog/common-dask-mistakes/
Python 摘要中的并行 XGBoost
让我们回顾一下我们在这篇文章中讨论的内容:
- 开箱即用,XGBoost 无法在大于内存的数据集上进行训练。
- 将 XGboost 连接到本地 Dask 集群允许您利用机器中的多个内核。
- 如果这还不够,您可以将 Dask 连接到 Coiled,并在需要时突发到云。
- 您可以通过检查 Dask 仪表板来调整您的分布式 XGBoost 性能。
让我知道你是否最终把你的数据集换到了这个笔记本上,并亲自驾驶 XGBoost + Dask。
在 LinkedIn 上关注我,了解定期的数据科学和机器学习更新和黑客攻击。
原载于 2021 年 12 月 20 日https://coiled . io。
熊猫数据框架与 Spark 数据框架:当并行计算变得重要时
通过性能比较分析和动画 3D 线框绘图的引导示例
注:本帖代码可在 这里 找到

Python 以其来自开源社区的大量库和资源而闻名。作为一名数据分析师/工程师/科学家,人们可能熟悉一些流行的软件包,如【Numpy】熊猫Scikit-learnKeras和 TensorFlow 。这些模块共同帮助我们从数据中提取价值,推动分析领域的发展。随着数据不断变得更大和更复杂,另一个需要考虑的因素是专门处理大数据的框架,如 Apache Spark 。在本文中,我将展示分布式/集群计算的能力,并对熊猫数据框架和 Spark 数据框架进行比较。我的希望是为选择正确的实现提供更多的信心。
熊猫数据框
熊猫因其简单易用而广受欢迎。它利用数据框架以表格格式呈现数据,就像带有行和列的电子表格。重要的是,它有非常直观的方法来执行常见的分析任务和相对平坦的学习曲线。它将所有数据加载到一台机器上的内存中(一个节点)以便快速执行。虽然熊猫数据框架已经被证明在处理数据方面非常强大,但它也有其局限性。随着数据呈指数级增长,复杂的数据处理变得非常昂贵,并导致性能下降。这些操作需要并行化和分布式计算,而 Pandas DataFrame 并不支持。
介绍集群/分布式计算和 Spark 数据框架
Apache Spark 是一个开源的集群计算框架。借助集群计算,数据处理由多个节点并行分配和执行。这被认为是 MapReduce 框架,因为分工通常可以用功能编程中的 map 、 shuffle 和 reduce 操作的集合来表征。Spark 的集群计算实现是独一无二的,因为进程 1)在内存中执行和 2)建立一个查询计划,直到必要时才执行(称为延迟执行)。尽管 Spark 的集群计算框架具有广泛的实用性,但出于本文的目的,我们只研究 Spark 数据框架。与熊猫中发现的相似,Spark 数据框架具有直观的 API,使其易于实现。

火花过程(图片由作者提供)
熊猫数据框与火花数据框
当比较 Pandas 数据帧和 Spark 数据帧之间的计算速度时,很明显 Pandas 数据帧对于相对较小的数据表现稍好。考虑到大小是影响性能的主要因素,我对两者进行了对比测试(脚本在 GitHub )。我发现随着数据量的增加,特别是超过了的 100 万行和的 1000 列,Spark 数据帧的表现会超过 Pandas 数据帧。下面是一个动画 3D 线框图来说明单变量(平均值)和双变量(相关性)计算的比较。请注意,这不包括与设置 Spark 数据帧相关的高开销。另外,比较是在相对简单的操作上进行的。实际上,使用更复杂的操作,使用 Pandas 数据帧比使用 Spark 数据帧更容易执行。

用于相关性和平均值计算的动画 3D 线框图(如下演示)
鉴于 Pandas 数据帧的单节点特性与 Spark 数据帧的分布式特性,这一结果并不令人惊讶。也就是说,由于熊猫数据帧是在单个服务器上执行的,所以对于非常大的数据集,内存中的计算速度和能力会受到影响。当然,这取决于硬件的能力。
哪个是正确的数据帧?
考虑到现代硬件规格和 Pandas 如何优化计算,一个典型的探索性项目不太可能保证 Spark 的实施。然而,有些情况下 Spark 有明显的意义。

Pandas 数据帧与 Spark 数据帧的特征
- Spark 对于需要高度分布式、持久化和流水线化处理的应用非常有用。开始一个项目,用有限的样本探索熊猫,并在它成熟时迁移到 Spark,这可能是有意义的。这在今天的市场趋势预测、个性化客户体验和天气预报引擎的开发中得到了应用。
- Spark 对于自然语言处理和计算机视觉应用非常有用,这些应用通常需要对又宽又长的数据进行大量计算。
- Spark 有一个机器学习库, MLlib ,旨在提供高级 API 来创建机器学习管道。由于模型调整的迭代性质,机器学习管道理想地在 Spark 的框架上运行。
摘要
对比分析表明,流行的 Pandas DataFrame 非常能够执行快速计算。尽管 Spark DataFrame 的实现已经变得更加容易,因为其语法旨在匹配其 Pandas 对应物,但它并不总是适合于特别分析。对于已经建立的数据处理,它变得非常强大,特别是当数据预计很大的时候。
奖金
以下是我如何创建动画 3D 线框图的步骤。概括地说,它可以分为以下几个步骤:
- 获取一些数据—我使用有限的数据点来推断步骤 2–4 中的其他模拟
- 根据数据拟合回归模型
- 创建所需 X,Y 平面的网格
- 使用回归模型计算网格的 Z 坐标
- 生成 3D 线框出图
- 通过旋转 X,Y 平面的轴重复步骤 5,并将每个图存储为单独的 PNG 文件
- 使用顺序 PNG 文件生成 GIF
我模拟并收集了 26 个不同数据维度的试验,如下所示(只显示了 5 个)。

数据框和 3D 散点图
上面的要点是用来为线框出图生成额外的点,因为散点图并没有为我正在寻找的可视化做一个完整的工作。我使用 for 循环遍历 x,y 轴的不同旋转,生成 GIF 的不同帧。
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3Dax=plt.axes(projection='3d')
X, Y, pandas_Z, spark_Z, _=get_plane(df, 2, 20)
ax.plot_wireframe(X, Y, pandas_Z)
ax.plot_wireframe(X, Y, spark_Z)# rotate x, y plane with the azimuth parameter and save figure at every 10 degrees
# intentionally generate more frames between 180 and 270 degrees to create a slow down effect in GIFfor ii in range(0,360,1):
if ii>=180 and ii<=270 and ii%3==0:
ax.view_init(elev=10., azim=ii)
plt.savefig("movie/movie%d.png" % ii)
elif (ii<=180 or ii>=270) and ii%10==0:
ax.view_init(elev=10., azim=ii)
plt.savefig("movie/movie%d.png" % ii)
并行化您的 python 代码以节省数据处理时间
为处理数据时的长时间等待而烦恼?这个博客是给你的!

埃里克·韦伯在 Unsplash 上的照片
您是否曾经遇到过在处理数据时必须等待很长时间的情况?老实说,我经常遇到这种事。因此,为了减少一点痛苦,我确保使用所有人类可用的计算资源来最小化这种等待。
我们都试图通过使用时间复杂度最小的算法来优化代码的运行时。但是让我告诉你我所说的“所有计算资源”是什么意思通过这篇博客,我们将确保通过并行化我们的代码来使用我们机器中所有可用的处理器。
由吉菲
平行化申请
你会问,并行化有什么用?假设您有多个独立的任务,这些任务由一个“ for 循环执行在这里,并行化是超级英雄,它使每个独立的任务能够由不同的处理器处理,从而减少总时间。
例如,假设我在一个文件夹中保存了 1000 张图像,对于每张图像,我必须执行以下操作。
- 将图像转换为灰度
- 将灰度图像调整到给定的大小
- 将修改后的图像保存在文件夹中
现在,对每个图像执行此过程是相互独立的,即处理一个图像不会影响文件夹中的任何其他图像。因此多重处理可以帮助我们减少总时间。我们的总时间将减少一个因子,该因子等于我们并行使用的处理器的数量。这是使用并行化节省时间的众多例子之一。
回顾一下并行化的优势:
- 减少总时间
- 提高效率
- 最大限度减少可用资源的浪费
- 在等待代码运行时,在 YouTube 上观看更少的视频😛
并行化的类型
并行化可以通过两种方式实现:
- 多线程—使用进程/工作线程的多个线程。
- 多重处理——使用多个处理器(我们在上面的例子中讨论过的那个)。
多线程对于 I/O(输入/输出)绑定的应用程序非常有用。比如我要下载上传多个文件的时候,多线程会更有帮助。
多重处理对于 CPU 受限的应用程序很有用。您可以将上面的例子视为多处理的用例之一。
履行
例子已经够多了;让我们更深入地理解并行化是如何实现的。
让我们首先举一个简单的例子来说明多重处理是如何实现的。为了进行并行化,我们必须使用一个多处理库。在下面的例子中,我们将并行化计算平方的代码。
**from** **multiprocessing** **import** Pool**import os
def** f(x):
**return** x*xworkers = os.cpu_count()**if** __name__ == '__main__':
**with** Pool(workers) **as** p:
print(p.map(f, [1, 2, 3]))
output: [1, 4, 9]
在上面的例子中,我们使用了“os.cpu_count()”来计算机器中可用的处理器数量。因此,如果处理器的数量为三个或更多,那么将并行计算所有三个数字的平方。如果处理器的数量是 2,那么将首先计算两个数字的平方,然后计算剩余数字的平方。让我们来看看我前面提到的图像处理示例的实现。
from multiprocessing import Pool
import cv2
import ospath = "./images"
save_path = "./images_save"
os.makedirs(save_path, exist_ok=True)def image_process(image_path):
img = cv2.imread(os.path.join(path, image_path))
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
resized = cv2.resize(gray, (224, 224), interpolation = cv2.INTER_AREA)
cv2.imwrite(os.path.join(save_path, image_path), resized)
returndef main():
list_image = os.listdir(path)
workers = os.cpu_count()
# number of processors used will be equal to workers
with Pool(workers) as p:
p.map(image_process, list_image)if __name__ == '__main__':
main()
为了让您了解效率的提高,我在 4 核 CPU 上运行了上面的图像处理示例。使用并行化,完成任务需要 13 秒,而串行(没有并行化)需要 49 秒。想象一下,当您处理数百万张图像时,时间会减少多少。
现在,让我们看一个多线程的例子。对于 I/O 任务,如查询数据库或加载网页,CPU 只是在查询后等待答案。因此,多处理,即使用多个处理器,将会浪费资源,因为这些查询将会锁定所有的处理器。然而,多线程可以帮助我们减少为多个查询获取答案的时间,而不会浪费计算资源。为了实现多线程,我们将使用 concurrent.futures 库。
**import** **concurrent.futures**
**import** **urllib.request**
URLS = ['http://www.foxnews.com/',
'http://www.cnn.com/',
'http://europe.wsj.com/',
'http://www.bbc.co.uk/',
'http://some-made-up-domain.com/']
*# Retrieve a single page and report the URL and contents*
**def** load_url(url, timeout):
**with** urllib.request.urlopen(url, timeout=timeout) **as** conn:
**return** conn.read()
*# Use a with statement to ensure threads are cleaned up promptly*
**with** concurrent.futures.ThreadPoolExecutor(4) **as** executor:
future_to_url = {executor.submit(load_url, url, 60): url **for** url **in** URLS}
**for** future **in** concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
**try**:
data = future.result()
**except** Exception **as** exc:
print('*%r* generated an exception: *%s*' % (url, exc))
**else**:
print('*%r* page is *%d* bytes' % (url, len(data)))
我希望上面的例子能帮助你更好地理解多重处理和多线程。
结论
因此,我们了解到并行化可以通过两种方式实现。多线程对于 I/O 绑定的进程很有用,多处理对于 CPU 绑定的进程很有用。我希望这篇文章能让您对并行化有一个很好的了解。如果你觉得有帮助,请在这个博客上发表评论,让我们知道。
关注我们的媒体,阅读更多此类内容。
成为 介质会员 解锁并阅读介质上的许多其他故事。
并行化 Python 代码
本文回顾了并行化 Python 代码的一些常见选项,包括基于进程的并行、专用库、ipython 并行和 Ray。
Python 非常适合训练机器学习模型、执行数值模拟和快速开发概念验证解决方案等任务,而无需设置开发工具和安装几个依赖项。在执行这些任务时,您还希望尽可能多地使用底层硬件来快速获得结果。并行化 Python 代码可以实现这一点。然而,使用标准的 CPython 实现意味着您不能完全使用底层硬件,因为全局解释器锁(GIL)阻止了从多个线程同时运行字节码。
本文回顾了并行化 Python 代码的一些常见选项,包括:
- 基于流程的并行性
- 专业图书馆
- IPython 并行
- 雷
对于每种技术,本文列出了一些优点和缺点,并展示了一个代码示例来帮助您理解它的用法。
如何并行化 Python 代码
有几种常见的并行化 Python 代码的方法。您可以启动多个应用程序实例或一个脚本来并行执行作业。当您不需要在并行作业之间交换数据时,这种方法非常有用。否则,在进程间共享数据会显著降低聚合数据时的性能。
在同一个进程中启动多个线程可以让您更有效地在作业之间共享数据。在这种情况下,基于线程的并行化可以将一些工作卸载到后台。然而,标准 CPython 实现的全局解释器锁(GIL)阻止了字节码在多个线程中同时运行。
下面的示例函数模拟复杂的计算(旨在模拟激活函数)
iterations_count = round(1e7)
**def** **complex_operation**(input_index):
print("Complex operation. Input index: {:2d}".format(input_index))
[math.exp(i) * math.sinh(i) **for** i **in** [1] * iterations_count]
complex_operation执行几次以更好地估计处理时间。它将长时间运行的操作分成一批较小的操作。它通过将输入值分成几个子集,然后并行处理来自这些子集的输入来实现这一点。
下面是运行complex_operation几次(输入范围为 10)并使用 timebudget 包测量执行时间的代码:
@timebudget
**def** **run_complex_operations**(operation, input):
**for** i **in** input:
operation(i)
input = range(10)
run_complex_operations(complex_operation, input)
在执行这个脚本之后,您将得到类似于下面的输出:

如您所见,在本教程中使用的笔记本电脑上执行这段代码花费了大约 39 秒。让我们看看如何改善这个结果。
基于进程的并行性
第一种方法是使用基于流程的并行性。使用这种方法,可以同时(并发地)启动几个进程。这样,他们可以同时执行计算。
从 Python 3 开始,多重处理包被预装,并为我们提供了一个启动并发进程的方便语法。它提供了 Pool 对象,该对象自动将输入划分为子集,并将它们分布在许多进程中。
**import** math
**import** numpy **as** np
**from** timebudget **import** timebudget
**from** multiprocessing **import** Pool
iterations_count = round(1e7)
**def** **complex_operation**(input_index):
print("Complex operation. Input index: {:2d}\n".format(input_index))
[math.exp(i) * math.sinh(i) **for** i **in** [1] * iterations_count]
@timebudget
**def** **run_complex_operations**(operation, input, pool):
pool.map(operation, input)
processes_count = 10
**if** __name__ == '__main__':
processes_pool = Pool(processes_count)
run_complex_operations(complex_operation, range(10), processes_pool)
每个进程同时执行复杂的操作。因此,该代码理论上可以将总执行时间减少 10 倍。然而,下面代码的输出只显示了大约四倍的改进(上一节是 39 秒,本节是 9.4 秒)。

有几个原因可以解释为什么没有十倍的提高。首先,可以同时运行的进程的最大数量取决于系统中 CPU 的数量。您可以通过使用os.cpu_count()方法找出您的系统有多少个 CPU。
import os
print('Number of CPUs in the system: {}'.format(os.cpu_count()))

本教程中使用的机器有八个 CPU
改进不多的另一个原因是本教程中的计算量相对较小。最后,值得注意的是,当并行化计算时通常会有一些开销,因为想要通信的进程必须利用进程间通信机制。这意味着对于非常小的任务,并行计算通常比串行计算慢(普通 Python)。如果你有兴趣了解更多关于多重处理的知识,Selva Prabhakaran 有一个优秀的博客启发了本教程的这一部分。如果你想了解更多关于并行/分布式计算的权衡,看看这篇教程。

专业图书馆
像 NumPy 这样的专门库的许多计算不受 GIL 的影响,可以使用线程和其他技术并行工作。教程的这一部分介绍了结合 NumPy 和多处理的好处
为了展示简单实现和基于 NumPy 的实现之间的差异,需要实现一个额外的函数:
**def** **complex_operation_numpy**(input_index):
print("Complex operation (numpy). Input index: {:2d}".format(input_index))
data = np.ones(iterations_count)
np.exp(data) * np.sinh(data)
代码现在使用 NumPy exp和sinh函数对输入序列执行计算。然后,代码使用进程池执行complex_operation和complex_operation_numpy十次,以比较它们的性能:
processes_count = 10
input = range(10)
**if** __name__ == '__main__':
processes_pool = Pool(processes_count)
print(‘Without NumPy’)
run_complex_operations(complex_operation, input, processes_pool)
print(‘NumPy’)
run_complex_operations(complex_operation_numpy, input, processes_pool)
下面的输出显示了这个脚本在有和没有 NumPy 的情况下的性能。

NumPy 提供了性能的快速提升。在这里,NumPy 将计算时间减少到原来的 10%(859 毫秒对 9.515 秒)。它更快的一个原因是因为 NumPy 中的大多数处理都是矢量化的。通过矢量化,底层代码被有效地“并行化”,因为操作可以一次计算多个数组元素,而不是一次遍历一个数组元素。如果你有兴趣了解更多这方面的知识,杰克·范德普拉斯在这里做了一个关于这个主题的精彩演讲。

IPython 并行
IPython shell 支持跨多个 IPython 实例的交互式并行和分布式计算。IPython Parallel(几乎)是和 IPython 一起开发的。当 IPython 更名为 Jupyter 时,他们将 IPython Parallel 拆分成自己的包。 IPython Parallel 有许多优点,但最大的优点可能是它支持交互式地开发、执行和监控并行应用程序。当使用 IPython Parallel 进行并行计算时,通常从 ipcluster 命令开始。
ipcluster start -n 10
最后一个参数控制要启动的引擎(节点)的数量。在安装 ipyparallel Python 包后,上面的命令变为可用。下面是一个输出示例:

下一步是提供应该连接到 ipcluster 并启动并行作业的 Python 代码。幸运的是,IPython 为此提供了一个方便的 API。下面的代码看起来像是基于池对象的基于进程的并行性:
**import** math
**import** numpy **as** np
**from** timebudget **import** timebudget
**import** ipyparallel **as** ipp
iterations_count = round(1e7)
**def** **complex_operation**(input_index):
print("Complex operation. Input index: {:2d}".format(input_index))
[math.exp(i) * math.sinh(i) **for** i **in** [1] * iterations_count]
**def** **complex_operation_numpy**(input_index):
print("Complex operation (numpy). Input index: {:2d}".format(input_index))
data = np.ones(iterations_count)
np.exp(data) * np.sinh(data)
@timebudget
**def** **run_complex_operations**(operation, input, pool):
pool.map(operation, input)
client_ids = ipp.Client()
pool = client_ids[:]
input = range(10)
print('Without NumPy')
run_complex_operations(complex_operation, input, pool)
print('NumPy')
run_complex_operations(complex_operation_numpy, input, pool)
在终端的新选项卡中执行的上述代码产生如下所示的输出:

IPython Parallel 使用和不使用 NumPy 的执行时间分别为 13.88 ms 和 9.98 ms。请注意,标准输出中不包含日志,但是可以使用其他命令来访问它们。

光线
和 IPython 并行一样, Ray 可以用于并行和分布式计算。Ray 是一个快速、简单的分布式执行框架,使您可以轻松扩展应用程序并利用最先进的机器学习库。使用 Ray,您可以将按顺序运行的 Python 代码转换成分布式应用程序,只需对代码进行最少的修改。

虽然本教程简要介绍了 Ray 如何简化普通 Python 代码的并行化,但需要注意的是,Ray 及其生态系统也简化了现有库的并行化,如 scikit-learn 、 XGBoost 、 LightGBM 、 PyTorch 等等。图片由迈克尔·加拉尼克
要使用射线,需要使用ray.init()来启动所有相关的射线过程。默认情况下,Ray 为每个 CPU 内核创建一个工作进程。如果您想在一个集群上运行 Ray,您需要传入一个类似 Ray . init(address = ' insertAddressHere ')的集群地址。
ray.init()
下一步是创建一个光线任务。这可以通过用@ray.remote装饰器装饰一个普通的 Python 函数来实现。这将创建一个任务,可以跨笔记本电脑的 CPU 内核(或 Ray cluster)进行调度。下面是之前创建的complex_operation_numpy的一个例子:
@ray.remote
**def** **complex_operation_numpy**(input_index):
print("Complex operation (numpy). Input index: {:2d}".format(input_index))
data = np.ones(iterations_count)
np.exp(data) * np.sinh(data)
最后一步,在 ray 运行时中执行这些函数,如下所示:
@timebudget
**def** **run_complex_operations**(operation, input):
ray.get([operation.remote(i) **for** i **in** input])
在执行这个脚本之后,您将得到类似于下面的输出:

使用和不使用 NumPy 时,Ray 的执行时间分别为 3.382 秒和 419.98 毫秒。重要的是要记住,当执行长时间运行的任务时,Ray 的性能优势会更加明显,如下图所示。

当运行更大的任务时,雷有更明显的优势(图片来源)
如果你想了解 Ray 的语法,这里有一个介绍性的教程。

替代 Python 实现
最后一个考虑是,您可以使用其他 Python 实现来应用多线程。例子包括 IronPython。NET 和 Jython for Java。在这种情况下,您可以使用底层框架的低级线程支持。如果您已经有了使用的多处理功能的经验,这种方法会很有用。NET 或者 Java。
结论
本文通过代码示例回顾了并行化 Python 的常用方法,并强调了它们的优缺点。我们使用简单数字数据的基准进行测试。重要的是要记住,并行化的代码通常会带来一些开销,并行化的好处在较大的任务中会更加明显,而不是本教程中的短时间计算。
请记住,并行化对于其他应用程序来说可能更加强大。尤其是在处理典型的基于人工智能的任务时,在这些任务中,您必须对您的模型进行重复的微调。在这种情况下, Ray 提供了最好的支持,因为它拥有丰富的生态系统、自动伸缩、容错和使用远程机器的能力。
最初发表于https://www.anyscale.com。
机器学习中的参数计数
思想和理论
公共数据集与机器学习中参数计数的演化分析
简而言之:我们收集了 1952 年至 2021 年间 n=139 个机器学习系统的开发日期和可训练参数计数的信息。据我们所知,这是同类中最大的公共数据集。你可以在这里访问我们的数据集,生成交互式可视化的代码可以在这里获得。

1954 年至 2021 年间流行的新机器学习系统的模型大小。包括 n=139 个数据点。点击此处查看此图的放大版和互动版。

2000-2021 年流行的新机器学习系统的模型大小。包括 n=114 个数据点。点击此处查看此图的放大版和互动版。
我们选择将重点放在参数计数上,因为之前的工作表明它是模型性能的一个重要变量1,因为它有助于作为模型复杂性的代理,还因为它是通常容易获得或容易从模型架构描述中估计的信息。
我们希望我们的工作将帮助人工智能研究人员和预测者理解模型随着时间的推移变得越来越复杂的一种方式,并为他们对该领域未来如何发展的预测奠定基础。特别是,我们希望这将帮助我们区分机器学习的进步有多少是由于算法的改进而不是模型复杂性的增加。
从我们有偏见和嘈杂的数据集中很难得出确定的结论。然而,我们的工作似乎对两个假设提供了微弱的支持:
- 在 2011-2012 年,模型规模增长的趋势在任何领域都没有中断。这表明深度学习革命不是由于算法的改进,而是机器学习方法的改进趋势赶上了其他方法的性能。
- 相比之下,似乎在 2016 年至 2018 年之间,语言模型的模型复杂性出现了不连续性。规模回报肯定增加了,并将增长轨迹从大约 1.5 年的倍增时间转变为 4 到 8 个月的倍增时间。
这篇文章的结构如下。我们首先描述我们的数据集。我们指出了数据集的一些弱点。我们扩展这些和其他见解。我们提出一些公开的问题。我们最后讨论一些后续步骤,并邀请合作。
数据集的特征
- 该数据集涵盖了从 1952 年到 2020 年的系统,尽管我们包括了更多关于近期系统的信息(从 2010 年开始)。
- 我们包括的系统包含许多类型,包括神经网络、统计模型、支持向量机、贝叶斯网络和其他更奇特的架构。然而,我们主要包括神经网络类型的系统。
- 这些系统来自许多领域,被训练来解决许多任务。然而,我们主要关注的是经过训练的解决视觉、语言和游戏任务的系统。
- 我们依靠显著性的主观标准来决定包含哪些系统。我们的决定是由引用计数(超过 1000 次引用的论文)、外部验证(获得某种年度论文奖或类似奖项的论文)和历史重要性(被其他工作引用为开创性的论文)决定的。这篇文章的参考资料包括一些概述,我们用它们作为整理数据集的起点[2–26]。
- 几个模型有不同比例的版本。每当我们在他们的原始出版物中遇到这种情况,我们都会记录下论文中出现的主要版本或最大的版本。有时,当我们觉得有必要时,我们会录制多个版本,例如,当多个不同的版本被训练来解决不同的任务时。
警告
- 重要的是要考虑到模型大小几乎不是理解 ML 系统进展的最重要的参数。ML 系统中其他更重要的非算法进展指标包括训练计算和训练数据集大小1。
- 模型大小作为模型复杂性的度量标准,在不同的领域甚至不同的体系结构中是很难比较的。例如,混合专家模型可以实现更多的参数计数,但在训练每个参数时投入的计算要少得多。
- 我们对系统的选择在许多重要方面都有偏差。我们偏向于学术出版物(因为商业系统的信息更难获得)。我们包括了更多关于最新系统的信息。我们倾向于包括关于参数计数容易获得的论文的信息,特别是被开发来测试模型可以有多大的限制的更大的模型。我们偏爱用英语发表的论文。我们主要关注视觉、语言和游戏任务方面的系统,而在语音识别、推荐系统或自动驾驶方面的论文相对较少。最后,我们偏向于我们个人认为有趣或令人印象深刻的系统。
- 重新收集信息是一项耗时的工作,需要我们通读数百篇技术论文来收集参数计数。我们很可能犯了一些错误。
洞察力
- 不出所料,模型尺寸有上升趋势。这种趋势似乎是指数级的,而且最近在语言模型方面似乎加快了步伐。进度斜率的眼球估计表明,从 2000 年到 2016 年至 2018 年,所有领域的倍增率在 18 至 24 个月之间,从 2016 年至 2018 年,语言领域的倍增率在 3 至 5 个月之间。
- 就可训练参数而言,最大的模型可以在语言和推荐系统领域中找到。我们发现的最大模型是来自脸书的 12 万亿参数深度学习推荐系统。我们没有足够的关于推荐系统的数据来确定推荐系统在可训练参数方面是否在历史上很大。
- 语言模型在历史上比其他领域更大。这是因为统计模型的参数化与词汇量成比例(如 2005 年的 Hiero 机器翻译系统)以及单词嵌入也与词汇量成比例(如 2013 年的 Word2Vec)。
- 可以说,深度学习在它进入语言处理之前就已经开始在计算机视觉中扩散了(大约在 2011 年至 2013 年),然而第二种方法的参数数量远远超过了第一种方法。特别是,在 2016 年至 2018 年期间,语言模型规模的增长趋势明显加快了速度,达到了 4 至 8 个月的翻倍时间。
- 游戏领域的架构在可训练参数方面很小,低于视觉架构,但显然以类似的节奏增长。我们天真地以为会是另外一种情况,因为玩游戏似乎更复杂。然而,事后看来,决定模型规模的是规模回报率;在更复杂的领域中,我们应该预期更低的有效模型大小,因为模型在其他方面更受限制。
- 在我们研究的所有领域中,模型规模的增长趋势在 2011 年至 2012 年向深度学习时代的过渡中一直相对稳定(尽管鉴于数据量很难肯定)。这表明,深度学习革命与其说是一种范式变革,不如说是现有趋势的自然延续,最终超越了其他非机器学习方法。
开放式问题
- 为什么视觉系统和语言系统的可训练参数的大小和增长趋势会有差异?一些假设是,语言架构随着规模而更好地扩展,视觉模型在训练数据上更加瓶颈,视觉模型需要每个参数更多的计算,或者语言处理 ML 社区在大规模模型的实验中领先(例如,因为他们可以访问更多的计算和资源)。
- 是什么导致了 2018 年以后语言模型规模的爆发式增长?随着人们意识到更大模型的优势,这是一种纯粹的社会现象吗?这是因为发现了可以更好地扩展规模、计算和数据的架构(例如变压器)吗?)还是完全由别的原因造成的?
- 深度学习前和深度学习后的机器学习的标度律实际上有显著差异吗?到目前为止,模型大小似乎表明,其他指标呢?
- 我们如何更准确地估计每个领域和时期的增长率?目前的增长率会持续多久?
后续步骤
- 我们有兴趣与其他研究人员合作,使这个数据集更具代表性,并纠正任何错误。作为奖励,我们将为发现的每个错误或系统添加支付 5 美元(所有提交中总计高达 600 美元;如果您想捐款以提高支付上限,请联系我们)。您可以通过 gmail dot com 将您的投稿发送到 jaimesevillamolina,最好是电子表格格式。
- 我们感兴趣的是包括关于系统的其他信息,尤其是计算和训练数据集的大小。
- 我们希望包括更多其他领域的信息,特别是推荐系统。
- 我们想更努力地寻找系统综述和其他已经策划好的人工智能系统数据集。
承认
本文由 Jaime Sevilla、Pablo Villalobos 和 Juan Felipe Cerón 撰写。Jaime 的工作得到了 NL4XAI Horizon 2020 计划的玛丽·居里资助。
我们感谢 Girish Sastry 在项目开始时为我们提供建议,感谢西班牙有效利他主义社区为孵化这样的项目创造了空间,感谢 Haydn Belfield、Pablo Moreno 和 Ehud Reiter 进行讨论并提交系统。
文献学
- 卡普兰等人,“神经语言模型的标度律”,08361。
- 1.6 强化学习的历史。(未注明)。于 2021 年 6 月 19 日从http://incompleteideas.net/book/first/ebook/node12.html检索
- 人工智能与计算。(未注明)。检索于 2021 年 6 月 19 日,来自https://openai.com/blog/ai-and-compute/
- AI 和效率。(2020 年 5 月 5 日)。OpenAI。https://openai.com/blog/ai-and-efficiency/
- 人工智能进度测量。(2017 年 6 月 12 日)。电子前沿基金会。https://www.eff.org/ai/metrics
- 宣布 2020 年 ACL 计时赛奖项(ToT) | ACL 会员门户。(未注明)。检索于 2021 年 6 月 19 日,来自https://www . ACL web . org/portal/content/announcement-2020-ACL-test-time-awards-tot #:~:text = Each % 20 year % 2C % 20 the % 20 ACL % 20 test,papers % 20 from % 2010% 20 years % 20 earliever。&text = 202020 年%20 月% 20 日公布的% 20 名获奖者。
- 本德、E. M .、格布鲁、t .、麦克米兰-梅杰、a .、和什米切尔(2021)。论随机鹦鹉的危险:语言模型会不会太大?🦜.2021 年美国计算机学会公平、问责和透明会议记录,610–623。https://doi.org/10.1145/3442188.3445922
- 最佳论文奖——ACL Wiki。(未注明)。检索于 2021 年 6 月 19 日,来自https://aclweb.org/aclwiki/Best_paper_awards
- bnlearn —贝叶斯网络知识库。(未注明)。检索于 2021 年 6 月 19 日,来自https://www.bnlearn.com/bnrepository/
- Brian Christian 谈校准问题。(未注明)。八万小时。检索于 2021 年 6 月 19 日,来自https://80000 hours . org/podcast/episodes/Brian-Christian-the-alignment-problem/
- 计算机视觉奖——计算机视觉基金会。(未注明)。于 2021 年 6 月 19 日从https://www.thecvf.com/?page_id=413检索
- DARPA 大挑战。(2021).在维基百科里。https://en.wikipedia.org/w/index.php?title = DARPA _ Grand _ Challenge&oldid = 1021627196
- 卡里姆河(2020 年 11 月 28 日)。图解:10 个 CNN 架构。中等。https://towards data science . com/illustrated-10-CNN-architectures-95d 78 ace 614d
- 穆罕默德·s . m .(2020 年)。检查自然语言处理文献的引文。计算语言学协会第 58 届年会论文集,5199–5209。https://doi.org/10.18653/v1/2020.acl-main.464
- Mudigere,d .、Hao,y .、Huang,j .、Sridharan,s .、Liu,x .、Ozdal,m .、Nie,j .、Park,j .、Luo,l .、Yang,J. A .、Gao,l .、Ivchenko,d .、Basant,a .、Hu,y .、Yang,j .、Ardestani,E. K .、Wang,x .、Komuravelli,r .、… Rao,V. (2021 年)。大规模深度学习推荐模型的高性能、分布式训练。第 2104.05158 号。http://arxiv.org/abs/2104.05158
- 尼尔森,N. (1974)。人工智能。 IFIP 国会。https://doi.org/10.7551/mitpress/11723.003.0006
- 波西,L. (2020 年 4 月 28 日)。人工智能研究的历史。中等。https://towards data science . com/history-of-ai-research-90 a6 cc 8 ADC 9 c
- Raschka,S. (2019 年)。神经网络和深度学习历史的简要总结。深度学习,29。
- 桑,v .,出道,l .,肖蒙德,j .,,沃尔夫,T. (2020)。蒸馏伯特,伯特的蒸馏版本:更小,更快,更便宜,更轻。ArXiv:1910.01108【Cs】。http://arxiv.org/abs/1910.01108
- 北卡罗莱纳州汤普森、k .格林沃尔德、k .李和 G. F .曼索(2020 年)。深度学习的计算极限。 ArXiv:2007.05558 [Cs,Stat] 。http://arxiv.org/abs/2007.05558
- 比达尔河(未标明)。计算机视觉:历史、深度网络的兴起和未来前景。60.
- 王,学士(2021)。Kingoflolz/mesh-transformer-jax【Jupyter 笔记本】。https://github.com/kingoflolz/mesh-transformer-jax(原著 2021 年出版)
- 谁发明了反向传播?(未标明)。检索于 2021 年 6 月 19 日,来自https://people . idsia . ch//~ juergen/who-invented-back propagation . html
- 谢,梁美婷,何,乐,2020。嘈杂学生的自我训练提高了图像网络分类。 ArXiv:1911.04252 [Cs,Stat] 。http://arxiv.org/abs/1911.04252
- Young,t .,Hazarika,d .,茯苓,s .,和 Cambria,E. (2018 年)。基于深度学习的自然语言处理的最新趋势。ArXiv:1708.02709【Cs】。http://arxiv.org/abs/1708.02709
- 张,熊,丁,苏,林,张,洪(2018)。用加减双门递归网络简化神经机器翻译。ArXiv:1810.12546【Cs】。http://arxiv.org/abs/1810.12546
- Zoph,b .,& Le,Q. V. (2016 年)。具有强化学习的神经架构搜索。https://arxiv.org/abs/1611.01578v2
R 降价的灵活报告
教程| R | R 降价参数
使用参数进行快速重现性分析

我们都参加过这样的会议,有人会问,“你能在不同的时间段再运行一次那个分析吗”,或者,“你能为 B 组而不是 A 组运行它吗”,或者,“你能把那个分析发给我吗,这样我就可以摆弄它了。”
当我们试图满足这些要求时,经常会遇到一些问题。改变一个分析的来源可能是困难的,无论它是来自一个。csv 到另一个或从测试数据库到生产数据库。如果要导入的连接或文件是在多个地方定义的,那么很容易遗漏其中一个并产生无效的输出。当将分析从 A 组切换到 B 组或改变分析数据的时间范围时,我们会遇到同样的问题。可能是多个过滤器都需要更换。
给出一个分析也是很麻烦的。当处理一些特别的分析时,通常会在代码中留下数据库密码,同时处理分析的细节。给出 R Markdown 文件或将它放在共享网络驱动器上会引起安全问题。虽然 dotenv 是一个选项,但它需要更多的设置才能正常工作。
幸运的是,R Markdown 有一个名为 parameters 的特性,可以解决所有这些问题。
硬编码参数
R Markdown 文档中的参数使用起来非常简单。在 yaml 头(markdown 文档顶部的部分)中,您只需为您的参数添加几个新行来硬编码它们。一旦它们被编码,它们将在params对象中可用,以用于剩余的分析。通过引用 yaml 头中的参数,需要对它们进行的任何更新只需要一次更改。
对于这个例子,我们将查看 gapminder 数据集。这是一个可从gapminder包中获得的数据集。它有一个由 Gapminder 基金会收集的指标子集。查看汉斯·罗斯林(Gapminder 创始人之一)的 TED 演讲,或者 Gapminder 网站了解更多信息。
# Setup
# install.packages("gapminder")
library(gapminder)# The data frame
gapminder
gapminder数据集包含 6 个变量:国家、洲、年、lifeExp(预期寿命)、pop(人口)和 gdpPercap(gRossdomesticp人均产品)。
代码块中的参数
我们将从过滤 2002 年的数据框架开始。使用tidyverse,代码看起来像这样:
library(tidyverse)gapminder %>%
filter(year == **2002**) %>%
head()
我们可能希望在不同的年份运行此报告,使之成为创建参数的良好候选。在我们的 yaml 头中,我们将添加另一个名为params的项目,在它下面,前面有两个空格,我们将创建一个year参数。当运行 R Markdown 时,将创建一个params对象,并且命名的参数可供我们使用。
---
title: "Hardcoded Parameters"
author: "RealDrewData"
date: "7/6/2021"
output: html_document
**params:
year: 2002**
---
现在我们可以回到我们的过滤代码。我们将用刚刚创建的 year 参数替换 2002。
library(tidyverse)gapminder %>%
filter(year == **params$year**) %>%
head()
我们还可以在代码中的其他地方使用 year 参数,比如一个绘图中的标签。让我们创建一个散点图,x 轴为人均 GDP,y 轴为预期寿命,人口为点的大小,大陆为颜色。在我们的标签中,我们可以使用paste()函数和我们的params$year来构建一个用参数更新的图的标题。
gapminder %>%
filter(year == **params$year**) %>%
ggplot(aes(x = gdpPercap, y = lifeExp, size = pop, color = continent)) +
geom_point() +
labs(title = paste("Life Expectancy and GDP for", **params$year**), x = "GPD Per Capita", y = "Life Expectancy")
降价部分中的参数
减价区是我们有纯文本的地方。我们可以内联运行代码来返回值,并让它们显示在输出文本中。例如,我们可能有一个部分标题,上面写着:
## Gapminder Analysis for 2002
当我们改变参数时,这不会更新。为了纠正这一点,我们可以使用内联代码从params对象中获取年份。r是说我们将使用 R 代码。在编织文档时,这将与前面的代码完全相同,假设year参数仍然设置为 2002。
## Gapminder Analysis for **'r params$year'**
参数的其他常见用途
参数不一定是数字,也可以是布尔值。一个常见的例子是创建开关来修改减价编织时的行为。一个例子是是否在最终输出中打印代码。当向经理或执行官展示某些东西时,您可能不想看到代码,但是当与团队一起评审时,代码可能更重要。
为此,我们将在 year 下创建另一个参数:show_code。
---
title: "Hardcoded Parameters"
author: "RealDrewData"
date: "6/19/2021"
output: html_document
params:
year: 2002
**show_code: FALSE**
---
通常在设置中定义大块编织选项。在我们的例子中,我们希望将echo选项设置为等于show_code。因为我们将这个设置为FALSE,这将阻止我们的代码出现在最终输出中。
knitr::opts_chunk$set(**echo = params$show_code**)
动态参数
Markdown 中的硬编码参数显然增加了一些主要的好处。如果不熟悉 Markdown 的人想要修改报告,或者您想要快速重新运行不同的时间段,该怎么办?动态参数在这里会很有帮助。
对于gapminder数据框,可用数据中存在一些细微差别。数据框中每 5 年一次,从 1952 年开始,到 2007 年结束。对于不知道数据的人来说,他们可以很容易地将参数更改为 1953 年,当没有数据显示时,他们会非常困惑。要改变这一点,我们可以通过修改 yaml 头中的year参数来添加自定义提示。
我们将在 year 下添加另一个子级别,使用我们想要的设置。在本例中,我们希望标签名为 year,默认值为 2002,输入为 slider。我们还可以指定最小值和最大值,以及每个值之间有多少步。
---
title: "Dynamic Parameters"
author: "RealDrewData"
date: "6/19/2021"
output: html_document
params:
**year:
label: "Year"
value: 2002
input: slider
min: 1952
max: 2007
step: 5
sep: ""**
show_code: FALSE
要显示提示,请单击针织按钮旁边的箭头。点击带参数的针织。

作者图片
这将弹出一个窗口,带有一个标有年份的滑块。这个滑块将有我们上面设置的限制。将滑块滑动到 1977 年并单击 knit 与更改 yaml 标题中的 year 值是一样的。额外的好处是,我们知道输入将是正确的类型,我们可以限制可以选择的选项,以确保数据出现在最终输出上。
您还会注意到为show_code参数创建了一个复选框输入。这是布尔参数的默认值。

作者图片
其他输入类型
同样,我们不仅限于数字和布尔类型。如果我调用了一个数据库,其中的代码需要用户名和密码,我们也可以为它们创建参数。
用户名可以是文本字段。该输入由text输入表示。value是默认值,在本例中为空。placeholder是文本框空白时显示的文字。对于密码输入,我们可以使用文本输入的password变体。这将使输入的文本变得模糊,就像你登录一个网站一样。
---
title: "Dynamic Parameters"
author: "RealDrewData"
date: "6/19/2021"
output: html_document
params:
year:
label: "Year"
value: 2002
input: slider
min: 1952
max: 2007
step: 5
sep: ""
show_code: FALSE
username:
label: "username"
value: ""
input: text
placeholder: "username"
password:
label: "PWD"
value: ""
input: password
placeholder: "password"
---
当我们单击“带参数的编织”时,我们现在会看到一个文本框输入和一个密码输入。这绝不是最佳实践,但它为您提供了一个使用动态参数的示例。

作者图片
您可以使用许多其他输入选项来设置参数。更全面的列表可以在闪亮小工具图库中找到。
结论
在 R Markdown 中,参数有多种用途。它们允许您在文档中进行影响整个文档的单个更改。它们使得为不同的时间段运行相同的报告或者为不同的受众运行相同的报告变得容易。他们还可以帮助与他人共享分析,但是对于这个特殊的用例可能有更好的选择。
- R Markdown:权威指南,作者:谢一辉,J. J. Allaire,Garrett Grolemund。由 RStudio 团队成员撰写,涵盖了你想知道的关于 R Markdown 的一切。此处 可在线免费下载 ,但您也可以 购买硬拷贝
- 闪亮小部件图库:所有闪亮小部件的交互示例,可用于动态设置 R Markdown 文档中的参数。
- 汉斯·罗斯林 TED 演讲:汉斯·罗斯林是 Gapminder 基金会的创始人之一。这个 TED 演讲给出了一些例子中使用的数据的背景。它总是让我兴奋起来,并准备做一些数据分析!
https://realdrewdata.medium.com/membership
机器学习的参数和非参数置信区间估计,3 行代码。
学习如何像专家一样报告结果。
[Edit 11/01/2021]添加了更好的定义,并根据 AlexMurphy 的有益建议调整了第 1 节中的示例。
在报告机器学习模型的结果时,置信区间是提供有用信息的一个很好的工具。它们是评估模型可靠性的一种简单直接的方法。
在这篇文章中,我将简要介绍置信区间,并介绍confidence _ interval _ estimator _ ML,这是一个工具,它可以让您用几行代码来估计机器学习模型的参数和非参数置信区间。
以下是这篇文章的结构:
- 举例介绍置信区间。
- 关于置信区间的隐藏假设。
- 什么是参数置信区间。
- 什么是非参数置信区间?
- 如何使用 Confidence _ interval _ estimator _ ML 计算参数和非参数置信区间?

1.举例介绍置信区间。
意译维基百科,置信区间表示未知参数 p 的似是而非的值的范围,相关的置信度表示真实的 p 包含在该范围内的置信程度。
在机器学习的背景下,通常相对于模型的性能,特别是相对于分类模型的准确性来估计置信区间。
给定一个旨在测量参数 p (在我们的情况下是模型的精度)的实验,具有相关置信度 C% 的置信区间 I 可以解释如下:如果实验重复 N 次,在 C 中,结果 p 的%将在区间I内
尽管这样做很有诱惑力,但从技术上讲,置信度不能被解释为概率,这将在下一节讨论。

95%置信区间的解释。演员表:维基百科。
以在 EMNLP2020 上发表的 KERMIT 论文为例,作者声明他们的模型在 Yelp Polarity 数据集上的准确率为 85.91( 0.03) %,置信度为 95%。
根据这个结果,通过在 Yelp 极性数据集上从头开始重新运行模型训练和评估 100 次(并且每次都改变随机种子),我们可以预计 95 倍的准确性将在范围[85.88,85.94]内。置信度很高(95%)且区间相对较窄的事实表明,在给定的条件下,KERMIT 模型相当稳健!
2.关于置信区间的隐藏假设。
在计算统计数据时,总会有一些隐藏的假设,但这些假设对结果有很大的影响。在置信区间的情况下,特别是在机器学习的背景下,在我看来,有两个主要的假设可能是至关重要的:数据的正态性和置信度的解释。
关于数据的正态性,假设数据样本来自正态分布,因此方差与均值无关。这种假设是有问题的,因为数据的分布通常是未知的。
现在,假设您想要估计数据集 D 上模型 M 的精确度的置信区间,您不仅需要知道 D 的分布,还需要知道该分布是高斯分布。幸运的是,中心极限定理派上了用场,它证明了如果样本量足够大,数据分布趋于正态分布。这里大的定义取决于几个因素,但是 30 通常被认为是用于模型评估的数据样本大小的一个很好的阈值。一般来说,正如数据科学中经常出现的情况,数据越多越好。
关于置信度的解释,通常隐含地假设置信度代表真值 p 落入置信区间 I 的概率。这实际上是错误的,因为置信度指的是估计过程的可靠性,正如在这个维基百科章节中广泛讨论的。无论如何,从实践的角度来看,置信区间可以被理解为对总体参数的似是而非的值的估计。
3.什么是参数置信区间
参数估计是一种以封闭形式估计置信区间的方法,甚至可以从模型的单个结果中进行估计。通过参数估计计算置信区间的公式如下:

其中 A 是模型的精确度, N 表示计算 A 的样本大小,因子是由置信度和样本大小决定的常数。因子的目的是确定间隔有多宽。一般来说,置信度越高,区间越宽。该常数的来源取决于样本大小,稍后将予以说明。
值 R 代表区间的范围,因此区间将由I =【A-R,B+R】表示。
如第 2 点所述,参数估计假设数据样本来自正态分布。即使你不知道数据的分布,如果样本量大于 30,由于中心极限定理,这不是问题。在这种情况下,因子参数将是与期望置信度相关联的 z 值 。
如果您的样本量小于 30,那么通常的做法是假设数据来自自由度为 sample_size-1 的 t-student 分布。在不涉及太多细节的情况下,这个假设导致了更宽的置信区间,因为小样本量提供了额外的不确定性。在这种情况下,因子参数将是与具有 N-1 个自由度和指定置信度的t-学生分布相关联的 t 分数 。
4.什么是非参数置信区间
非参数置信区间估计是一种根据经验估计置信区间的方法。这种方法有几个其他的名字:Bootstrap 置信区间估计,Monte Carlo 置信区间估计,或简单的经验置信区间估计,但它们都表明同样的事情。
为了估计模型 M 在数据集 D 上的准确性的非参数置信区间,该模型在 D 的样本上被训练和测试多次。这是执行估算的算法:
- 数据集 D 被采样 N 次以获得 N 个数据集 Di 。这种取样通常在更换时进行。
- 每个样品 Di 被分成两个独立的组 Di_train 和 Di_test。
- 对于每个 Di ,生成模型的一个实例 Mi 。通过在 Di_train 上进行训练和在 Di_test 上进行测试,在相应的数据样本上对每个实例 Mi 进行评估。让我们用 A 来表示测试精度列表。
- 测试精度列表 A 已排序。
- 让我们用 C 来表示期望的置信水平;如果我们希望 c.i .有 95%的置信度, C 将被设置为 95。让我们用α来表示值 100- C 。如果期望的置信水平是 C =95,alpha= 100–95 = 5。
- 置信区间的下限由 A 的 alpha /2- th 百分位给出,而置信区间的上限由C+(alpha/2)-th百分位给出。
- 我们可以说,数据集 D 上的模型 M 的置信区间为下界,上界,置信程度为 C %。
直观地说,我们希望使用 lower_bound 和 upper_bound,以便它们覆盖 A 的 C %的元素,如下图所示。

Bootstrap 置信区间图解。图片由作者提供。
这种计算置信区间的方式挑战了我们在本文第二点中讨论的两个假设。这就是为什么当非参数地计算置信区间时,拥有一个大的数据集和使用大量的迭代来执行估计是至关重要的。
5.如何使用 confidence _ interval _ estimator _ ML 计算参数和非参数置信区间?
至此,您应该对置信区间的参数和非参数估计的工作原理有了一个清晰的概念,所以我将介绍confidence _ interval _ estimator _ ML,这是一个 Python 工具,它将使估计过程非常简单明了。
通过命令行中的 pip 很容易安装该工具:
> pip install confidence_interval_estimator_ML
现在,执行估算非常简单:对于参数化的情况,您只需导入库并运行一行代码,如下例所示:
在给出的示例中,使用的样本大小大于 30,这就是函数指定假设数据是从正态分布中采样的原因。
非参数情况稍微复杂一些,因为您可能需要为要执行的估计指定一些参数。无论如何,这是在两行代码内完成的:
第一行执行第 4 点提出的算法的前三个步骤(它针对指定的迭代次数训练和验证模型的实例),而第二行执行剩余的四个点(计算置信区间的界限)。
第一个函数 get_accuracy_samples 采用几个输入参数:
- get_classifier_instance 是一个通过调用返回模型新实例的函数。返回的模型必须提供拟合和预测方法。每次迭代都会创建一个新的模型实例。
- model_params_dict 是一个可选参数,允许您指定要传递给模型构造函数的参数。
- X 和 y 分别代表模型的输入和目标。
- sample_ratio 表示从 (X,y)开始每次迭代抽取的样本大小。举例来说,如果 (X,y) 有 1000 个数据点,并且*sample _ ratio*被设置为 0.70,则在每次迭代时,将从 (X,y)采样 700 个数据点的数据集。
- train_ratio 指定在每次迭代中用于模型训练和测试的采样数据集的比率。按照前一点的示例,如果 (X,y) 具有 1000 个数据点,并且 sample_ratio=0.70,则在每次迭代中,将对 700 个数据点的数据集进行采样。如果 train_ratio=0.80,560 个数据点将用于训练模型,而剩余的 140 个将用于评估(计算测试精度)。
下面的代码提供了一个更详细的模块用法示例。
这些方法提供了其他参数和用例,为了简洁起见,这里没有讨论这些参数和用例。如果你想探索 confidence _ interval _ estimator _ ML 模块提供的所有可能性,或者你只是需要更多的细节来执行你的估计,你可以查看这个 Google Colab 笔记本,或者只是查看提供代码和文档的 GitHub repo 。
参考资料和进一步阅读:
- https://OCW . MIT . edu/courses/mathematics/18-05-introduction-to-probability and-statistics-spring-2014/readings/MIT 18 _ 05s 14 _ reading 24 . pdf
- https://SPH web . bumc . bu . edu/otlt/MPH-Modules/ph 717-quant core/ph 717-module 6-random error/ph 717-module 6-random error 11 . html
- https://machine learning mastery . com/report-classifier-performance-confidence-intervals/
- https://machine learning mastery . com/confidence-intervals-for-machine-learning/#:~:text = Much % 20 of % 20 machine % 20 learning % 20 涉及% 20 不确定性% 20of % 20an %估计值。&text = That % 20a % 20 置信度% 20 区间%20is,估计% 20a % 20 总体% 20 参数。
- http://www2 . stat . duke . edu/~ ar 182/RR/examples-gallery/bootstrapconfidenceintervals . html
如果你喜欢这篇文章,请随时联系 Linkedin !
参数贝叶斯推理:数字抽样技术的实现与证明
接受/拒绝采样& MCMC Metropolis-Hastings 全计算模拟采样

1:介绍和动机
在参数贝叶斯推理中,我们的目标是恢复感兴趣的参数的后验分布。通过“恢复”分布,这或者是指恢复后验分布的封闭解析形式(PDF、CDF、MGF 等),或者是一种从后验分布中根据经验抽取样本的方法,之后可以根据经验进行数值构建。

作者图片
从上面我们可以看到,后验概率密度函数与概率密度函数乘以先验概率密度函数成正比:

作者图片
在此基础上,让我们再次看看后验概率密度函数的完整方程:

作者图片
挑战在于,即使我们有了可能性和先验的封闭形式表示,也很难整合分母。
在上面的术语中,分母中的积分是参数贝叶斯推理中的真正挑战。问题是,我们如何绕过这个积分问题,并且仍然恢复我们的后验分布?有分析方法和数值方法,我们可以避开这个积分问题;这些方法正是这篇文章将要讨论的内容,特别强调数值方法部分,以及探索这些技术的完整的计算模拟。
这篇文章的目录如下:

作者图片
说到这里,让我们开始吧!
2:分析方法:共轭先验
解决积分问题的第一个策略是利用共轭先验作为θ上先验分布的选择。
假设我们感兴趣的是在给定观察数据的情况下恢复感兴趣的随机参数θ的后验分布。给定θ的性质和从中指定观察数据的分布的性质(其指示似然函数),这个特定的θ可以具有“共轭先验”。共轭先验是一个特殊的分布族,通过它,如果先验被指定为属于这个参数族,那么在数学上保证后验将属于这个相同的分布族。

作者图片
通过这种方法,我们避免了在贝叶斯定理中直接积分分母,因为我们已经知道后验概率所属的分布族。
下面,我们提供证明,共轭先验为:

作者图片
二项分布随机变量的共轭先验:

作者图片
上述声明的证明:

作者图片
泊松分布随机变量的共轭先验:

作者图片
上述声明的证明:

作者图片
3:数值抽样方法
在的第 2 节中,我们介绍了共轭先验,这是一种分析策略,可以避开贝叶斯推理问题的贝叶斯定理分母中难以处理的积分。在个人计算和强大的科学计算软件开发之前,共轭先验在很长一段时间内是规避这个“棘手的积分”问题的唯一策略。
但是,假设我们现在有计算机来利用数值方法,也许有一种数值技术我可以用来恢复后验分布(而不是分析技术)?答案是肯定的,这也是我们在这一部分要解决的问题。
让我们通过贝叶斯定理再次看看我们对后验分布的定义:

作者图片
注意分母中的积分:

作者图片
这一项是观察数据 x 的边缘分布。换句话说,这个“分布”只是一个经验值,即一个标准化常数。这个归一化常数的目的是什么?好吧,回想一下后验需要一个有效的概率密度函数(PDF)。为了成为一个有效的 PDF,当在整个支持面上积分时,后验概率必须等于 1。因此,观察数据 X 的边缘分布充当归一化常数,其确保乘以先验的可能性被缩放为有效的 PDF。

作者图片
在第 2 节的中,我们讨论了如何通过利用共轭先验来解析恢复后验概率。我们现在要讨论数值方法。
假设我们没有后验概率的封闭解,但是我们有能力从中提取样本。假设我们从后面随机抽取一个样本,放在一边。然后另一个随机样本,另一个,另一个,把它们都放在一边。我们重复这个过程很多次,直到我们从后验样本中抽取了足够大的一组 m 样本。即使我们没有后验概率的封闭形式的解析解,如果我们把样本简单地绘制成直方图会怎么样呢?结果将是后验概率密度函数的经验重建!这实际上是“恢复”后验分布的一种形式。
关键还在于认识到,在收集样本后,为了根据经验构建后验概率直方图,我们需要通过经验归一化常数来调整数据。这个归一化常数将是 1/m ,其中 m 再次是我们抽取的样本数。因此,假设我们已经通过我们的经验归一化常数 1/m 归一化了我们的样本,我们不需要从后验本身抽取样本!相反,我们只需要从与后验相乘成比例的东西中抽取样本,即后验的核 k(theta) 。这种情况下的核 k(theta) 是来自贝叶斯定理的分子。
同样,后验概率的核只相当于贝叶斯定理中的分子。因此,我们有一个通用的技术,允许我们完全忽略分母和讨厌的积分!
3.1: 接受/拒绝抽样

作者图片

作者图片
让我们证明上述公式是有效的:

作者图片
3.2:马尔可夫链蒙特卡罗(MCMC): Metropolis-Hastings 抽样

作者图片

作者图片

作者图片

作者图片
4:计算模拟
python 为接受/拒绝采样和 MCMC Metropolis-Hastings 提供了计算模拟。
我们从导入所需的库开始:
指定函数来模拟我们的数据集。具体地说,是由三种高斯分布混合而成的后验分布。
以及用于接受/拒绝采样和 MCMC Metropolis-Hastings 采样的函数。
现在,我们返回模拟数据集的“真实”后验分布:
接下来,我们进行接受/拒绝采样,从数值上恢复后验分布:
最后,我们进行 MCMC Metropolis-Hastings 采样,从数值上恢复后验分布:
从上面的结果可以看出,接受/拒绝抽样和 MCMC Metropolis-Hastings 在恢复“真实的”后验分布方面表现良好。
以上计算模拟的完整代码/笔记本,请看下面的 github 链接 。
5:总结和结论
希望以上有见地。正如我在以前的一些文章中提到的,我认为没有足够的人花时间去做这些类型的练习。对我来说,这种基于理论的洞察力让我在实践中更容易使用方法。我个人的目标是鼓励该领域的其他人采取类似的方法。以后我会继续写类似的作品。请 订阅并关注我在 和 LinkedIn 上的更新!
参数检验——t 检验
假设检验
t 测试的一站式商店——从理论到 python 实现

在我之前的文章中,我们讨论了假设检验的内容、方法和原因,并简要介绍了统计检验以及它们在帮助我们确定统计显著性方面的作用。在这篇文章和接下来的几篇文章中,我们将深入探讨统计测试——不同类型的测试、测试本身以及哪种测试应该用于哪种情况。
如前所述,统计测试是统计方法,帮助我们拒绝或不拒绝我们的零假设。它们基于概率分布,可以是单尾或双尾的,取决于我们选择的假设。
统计检验还有其他不同的方式,其中之一是基于他们对数据遵循的概率分布的假设。
- 参数测试是那些统计测试,其中假设数据近似遵循正态分布,以及其他假设(示例包括 z 测试、t 测试、ANOVA)。重要提示—假设总体数据遵循正态分布,而不是您正在处理的样本数据。
- 非参数检验是那些没有对数据的分布做任何假设的统计检验,因此也被称为无分布检验(例子包括卡方检验、曼-惠特尼 U 检验)。非参数检验基于不同数据点的等级。
每一个参数测试都有一个等价的非参数测试,这意味着对于你遇到的每一种类型的问题,在这两个类别中都会有一个测试来帮助你。

作者图片
然而,选择哪一套测试适合手头的问题并不是非黑即白的。如果您的数据不符合正态分布,非参数检验不一定是正确的选择。该决定取决于其他因素,如样本大小、您所拥有的数据类型、什么样的集中趋势度量最能代表数据等。如果样本量足够大,某些参数测试在非正态数据上表现很好,例如,如果样本量大于 20,并且数据不是正态的,那么单样本 t 检验仍然会对您有益。但是,如果中位数更好地代表了您的数据,那么您最好使用非参数检验。
在本文中,我们将关注参数测试——尤其是 t-测试。
参数检验是指假设样本数据来自一个服从概率分布(正态分布)的总体,并带有一组固定的参数。
常见的参数检验侧重于分析和比较数据的均值或方差。
平均值是描述数据集中趋势的最常用度量,但是它也受到异常值的严重影响。因此,分析您的数据并确定平均值是否是表示数据的最佳方式非常重要。如果是,那么参数测试是正确的方法!如果不是,并且中位数更好地代表了您的数据,那么非参数检验可能是更好的选择。
如上所述,参数测试有几个数据需要满足的假设:
- 正态性 —样本数据来自近似服从正态分布的总体
- 方差齐性 —样本数据来自具有相同方差的总体
- 独立性 —样本数据由独立的观察值组成,并且是随机抽样的
- 异常值 —样本数据不包含任何极端异常值
自由度。
在我们进入不同的统计测试之前,有一个重要的概念需要讨论——自由度。
自由度本质上是在测量统计参数时一组数据中可能变化的独立值的数量。
假设你喜欢每周六出门,你刚买了四套新衣服。你想每个周末都穿一套新衣服。在第一个星期六,四套衣服都没穿过,所以你可以挑任何一套。下周六你可以从三个中挑选,第三个周六你可以从两个中挑选。然而,在这个月的最后一个周六,你只剩下一套衣服,不管你想不想穿,你都必须穿,而在其他周六,你可以选择。
所以基本上,你有 4–1 = 3 个周六的时间自由选择服装——你的服装可以变化。
这就是自由度背后的思想。
关于数值和平均值,数值的总和必须等于样本量乘以平均值,即总和= n *平均值,其中 n 是样本量。因此,如果样本大小为 20,平均值为 40,则样本中所有观察值的总和必须为 800。前 19 个值可以是任何值,但是第 20 个值必须确保所有值的总和为 800,因此它没有变化的自由。因此自由度是 19。
自由度的公式是样本大小——您测量的参数数量。
比较意味着。
如果你想比较两组的平均值,那么可以选择的正确测试是 z 测试和 t 测试。
单样本(单样本 z 检验或单样本 t 检验):一组为样本,二组为总体。所以你基本上是在比较一个样本和总体的标准值。我们基本上是想看看样本是否来自总体,即它的行为是否与总体不同。
这方面的一个例子是我们在上一篇文章中讨论的——已知去看牙医的患者的平均年龄是 18 岁,但我们假设它可能比这个年龄更大。样本必须从总体中随机选取,并且观察值必须相互独立。
双样本(双样本 z 检验和双样本 t 检验):两组都是单独的样本。在单样本检验的情况下,两个样本都必须从总体中随机选取,并且观测值必须相互独立。
当涉及两个变量时,使用双样本检验。例如,比较两性在购物网站上的平均花费。一个样本是女性顾客,第二个样本是男性顾客。因为要比较平均值,所以测试中涉及的变量之一必须是数字变量(在购物网站上花费的钱是数字变量)。
重要提示:不要把单样本和双样本与单尾和双尾混淆!前者与被比较的样本数量有关,后者与你的替代假设是否具有方向性有关。可以进行单样本双尾检验。
但是,我们如何在 z 测试和 t 测试之间做出选择呢?通过观察样本大小和人口方差。
- 如果总体方差已知且样本量较大(大于或等于 30),我们选择 z 检验
- 如果总体方差已知且样本量较小(小于 30),我们可以进行 z 检验或 t 检验
- 如果总体方差未知且样本量较小,我们选择 t 检验
- 如果总体方差未知并且样本量很大,我们选择 t 检验

作者图片
t 检验。
如上所述,t-检验与 z-检验非常相似,除非它适用于较小的样本,并且不需要知道总体方差。
t 检验基于 t 分布,t 分布是一个像正态分布一样的钟形曲线,但是尾部更重。
随着样本量的增加,自由度也增加,t 分布变得类似于正态分布。它在平均值附近变得不那么偏斜和紧密(较轻的尾部)。为什么?我们一会儿就知道了。
有三种类型的 t 检验。上面已经介绍了两个——单样本和双样本。这两种都属于‘不成对 t 检验’的范畴,所以第三种 t 检验是‘成对 t 检验’。
成对和不成对的概念与样本有关。样品是一样的还是两个不同的样品?我们是在两个不同的组中监控一个变量还是在同一个组中?如果样本相同,则 t 检验应该配对,否则不配对。
例如,假设您想测试某种药物是否会提高女性的孕酮水平。
如果您拥有的数据是一组女性服药前的孕酮水平和同一组女性服药后的孕酮水平,那么您将进行配对 t 检验,因为样本是相同的。
如果你的数据是两组不同年龄组的妇女服药后的黄体酮水平,那么你将进行双样本不成对 t 检验,因为有两个不同的样本。
每个统计检验都有一个检验统计量,它帮助我们计算 p 值,然后决定是否拒绝零假设。在 t 检验的情况下,检验统计量称为 t 统计量。计算 t 统计量的公式根据您正在执行的 t 检验而有所不同,所以让我们仔细看看它们。
下面所有例子中用到的代码和数据都可以在 这里 找到。
单样本 t 检验。
印度女性的平均身高被记录为 158.5cm。今天印度女性的平均身高是否大于 158.5cm?
为了验证这个假设,我询问了 25 名女性的身高。
我的假设是—

- 显著性水平为 0.05。
- 样本平均值为 162 厘米,样本标准偏差为 2.4 厘米
- 由于样本大小为 25,自由度将为 24(25–1)。
- 因为我将样本平均值与总体平均值(标准值)进行比较,所以这将是一个单样本测试。
- 由于我的假设有一个方向——样本平均高度大于总体平均高度——这将是一个单尾检验。
计算 t 统计量的公式为:

作者图片
所以我们的 t 统计量是

作者图片
接下来,我们需要在 t 统计值的表格中查找 t 分布的临界值,其中α为 0.05,自由度为 24 。我们场景的临界值是 1.711。我们的 t 统计量大于临界值,所以可以拒绝零假设,得出印度女性平均身高大于 158.5 cm 的结论!
虽然最好在假设检验中计算 p 值以拒绝或不拒绝零假设,但计算 t 统计的 p 值的公式有点棘手。在手动执行假设检验时,您可以使用 t 分布表值,或者只使用临界值来拒绝或不拒绝零假设。否则,使用计算器或 python 函数将有助于您获得 p 值。让我们看看如何!
我们首先将 csv 读入数据帧:
import pandas as pd
data = pd.read_csv("one-sample-t.csv")
data.head()

作者图片
我们有两栏——年龄和身高。对于这个单样本 t 检验,我们只需要身高,因为我们要将这个样本的平均身高与总体平均身高(158.5 厘米)进行比较
让我们检查高度栏的平均值和标准偏差:
data.height.mean()
>> 162.053526834564data.height.std()
>> 2.4128517286126048
t 检验的假设表明样本数据必须来自正态分布。我们可以通过使用概率图(也称为 QQ 图—分位数-分位数图)来检查身高列是否呈正态分布。简而言之,概率图是一种检查数据集是否遵循特定分布的图形方法。它实际上是两个数据集的图,一个是要检查其分布的数据,另一个是来自该分布本身的数据。在我们的例子中,一组数据将是高度列,分布将是正态分布。
import pylab
stats.probplot(data.height, dist="norm", plot=pylab)
pylab.show()

作者图片
红线代表正态分布,蓝点代表身高栏的数据。上图证实了身高列来自/遵循正态分布,因为身高数据点遵循正态分布线的路径。
现在,我们将使用 scipy 的 stats 方法执行单样本 t 检验。我们需要向它传递我们的数据和人口意味着:
stats.ttest_1samp(data.height,popmean=158.5)
>> Ttest_1sampResult(statistic=7.363748862859639, pvalue=1.32483697812078e-07)
p 值小的离谱!所以我们可以拒绝零假设。
双样本 t 检验。
印度女性的年龄和身高有关系吗?
为了验证这一假设,我询问了 50 名女性的年龄——25 名女性年龄在 27 至 30 岁之间(A 组),25 名女性年龄在 37 至 40 岁之间(B 组)。
- 我的假设是—

- 显著性水平为 0.05。
- A 组的样本均值和标准差分别为 162 厘米和 2.4 厘米。
- B 组的样本均值和标准差分别为 158.6 厘米和 3.4 厘米。
- 因为我在比较两个样本的平均值,所以这将是一个双样本测试。
- 由于我的假设是无方向性的,这将是一个双尾检验。
前面提到过,参数检验假设方差同质,即两个样本的方差应该相同。在这里提到的例子中,方差肯定是不一样的——A 组的标准差是 2.4 厘米,而 b 组的标准差是 3.4 厘米。这是否意味着我们不能执行双样本 t 检验?不,不是的!令人欣慰的是,t 检验有一个变体,允许不同的方差,它被称为韦尔奇的 t 检验。
当两个样本的方差相等时,用于计算 t 统计量的分母称为混合方差。如果两组的样本量不同,则公式为:

作者图片
如果两组的样本量相等,则公式如下:

作者图片
它找出两组的共同方差,用于 t 统计公式。t 统计量的公式为:

作者图片
然而,当两个样本的方差不相等时,分母比较两个方差,计算 t 统计量的公式为:

作者图片
此外,自由度的计算在两种测试之间也不同。如果当前示例中两组的方差相等,自由度将为 48(25+25–2;我们减去 2,因为我们测量两个参数——每个样本的平均值)。
在 Welch t-test 的情况下,自由度是分数,总是小于学生 t-test 的自由度,坦白地说,计算起来有点复杂。
由于我们的方差不相等,我们将执行韦尔奇的 t 检验。
所以我们的 t 统计量是:

作者图片
让我们也用 python 来做这件事。
import pandas as pd
df_a = pd.read_csv("one-sample-t.csv") # group A
df_b = pd.read_csv("two-sample-t.csv") # group B
A 组与我们用于单样本 t 检验的 csv 相同,因此我们已经知道它的均值和标准差。让我们对 b 组进行同样的检查。
df_b.height.mean()
>> 158.60704061997612df_b.height.std()
>> 3.42443022417948
现在我们进行 t 检验!要执行 Welch 的 t-test,我们只需将 equal_var 参数作为 False 传递。默认情况下,这是真的,所以如果我们进行学生的 t 检验,我们根本不需要通过它。
stats.ttest_ind(df_a.height, df_b.height, equal_var=False)
>> Ttest_indResult(statistic=4.113633648976651, pvalue=0.00017195968508873518)
p 值远小于 0.05,因此我们可以拒绝零假设。
配对 t 检验。
营养饮料 xyz 会增加女性身高吗?
为了验证这一假设,我测量了 25 名女性在开始喝营养饮料前和结束后的身高。
- 我的假设是-

- 显著性水平为 0.05。
- 女性饮酒前的样本均值和标准差分别为 162 厘米和 2.4 厘米。
- 女性饮酒后的样本均值和标准差分别为 167 厘米和 3.4 厘米。
- 由于样本大小为 25,自由度将为 24(25–1)。
- 因为我比较的是同一样本的平均值,但中间有干预,所以这将是一个配对 t 检验。
- 因为我的假设是有方向性的,所以这将是一个单尾检验。
有趣的事实—配对 t 检验计算两组数据(相同样本,之前和之后)中配对观察值之间的差异,然后使用平均差异和平均标准偏差执行单样本 t 检验。
让我们直接用 python 实现它:
将 csv 读入数据帧。
import pandas as pd
data = pd.read_csv("paired-t.csv")
data.head()
使用 describe()方法检查 before 和 after 列的平均值和标准差。
data.describe()

作者图片
使用 scipy stat 的 ttest_rel 方法执行配对 t 检验!我们在替代参数中传递“更大”,因为我们的替代假设是营养饮料后的平均身高将大于饮料前的身高。
stats.ttest_rel(data.height_before, data.height_after, alternative='greater')
>> Ttest_relResult(statistic=-1.9094338173992416, pvalue=0.9658844528005113)
p 值大于 0.05,因此我们不能拒绝零假设。
现在我们已经看到了所有类型的 t 检验及其计算 t 统计量的公式,我们可以理解为什么随着样本量的增加,t 分布变得类似于正态分布。所有不同的 t 检验都涉及样本的标准差/方差。这只是对总体方差的估计,因为它是未知的。由于 t-检验中的假设是样本数据来自服从正态分布的总体,因此随着样本大小的增加以及自由度的增加,方差的这种估计实际上是正确的(即,是总体的方差)的可能性更大。此外,样本量越大,就越接近总体。由于总体是正态分布的,所以具有更高自由度的更大样本量的 t 分布也类似于正态分布是有意义的。
机器学习中的参数方法与非参数方法
讨论机器学习中参数方法和非参数方法的区别

Alex Padurariu 在 Unsplash 上拍摄的照片
介绍
在我之前的一篇文章中,我在统计学习的背景下讨论了预测和推断之间的差异。尽管它们在最终目标上有很大不同,但在这两种方法中,我们都需要估计一个未知函数 f 。
换句话说,我们需要学习一个将输入(即自变量 X 的集合)映射到输出(即目标变量 Y)的函数,如下所示。
Y = f(X) + ε
为了估计未知函数,我们需要在数据上拟合一个模型(训练数据更精确些)。我们试图估计的函数的形式通常是未知的,因此我们可能不得不应用不同的模型以达到目的,或者对函数 f 的形式做出一些假设。一般来说,该过程可以是参数化或非参数化。
在今天的文章中,我们将讨论机器学习环境中的参数和非参数方法。此外,我们将探讨它们的主要区别以及它们的主要优点和缺点。
参数方法
在参数方法中,我们通常对函数f的形式做出假设。例如,你可以假设未知函数 f 是线性的。换句话说,我们假设函数的形式为
f(X) = β₀ + β₁ X₁ + … + βₚ Xₚ
其中 f (X)为待估计的未知函数, β 为待学习的系数, p 为自变量的个数, X 为相应的输入。
现在,我们已经对要估计的函数的形式做出了假设,并选择了与该假设一致的模型,我们需要一个学习过程,该过程最终将帮助我们训练模型并估计系数。
总之,机器学习中的参数方法通常采用基于模型的方法,其中我们对要估计的函数的形式做出假设,然后我们基于该假设选择合适的模型,以便估计参数集。
参数方法最大的缺点是我们做出的 T2 假设并不总是正确的。例如,你可能假设函数的形式是线性的,而它不是。因此,这些方法涉及不太灵活的算法,通常用于不太复杂的问题。
然而,参数方法往往很快,而且与非参数方法相比需要的数据也少得多(下一节将详细介绍)。此外,由于参数方法往往不太灵活,不适合不太复杂的问题,它们更容易解释。
机器学习中的参数方法的一些例子包括线性判别分析、朴素贝叶斯和感知器。
非参数方法
另一方面,非参数方法指的是一组算法,这些算法不会对要估计的函数形式做出任何潜在假设。并且由于没有进行假设,这种方法能够估计可能是任何形式的未知函数 f 。
非参数方法往往更准确,因为它们寻求最佳拟合数据点。然而,这是以需要非常大量的观测值为代价的,这些观测值是精确估计未知函数 f 所需要的。此外,在训练模型时,这些方法往往效率较低。此外,非参数方法有时可能会引入过度拟合。由于这些算法往往更加灵活,它们有时可能会以一种无法很好地推广到新的、看不见的数据点的方式来学习错误和噪声。
另一方面,非参数方法非常灵活,可以带来更好的模型性能,因为没有对基础函数做任何假设。
机器学习中非参数方法的一些例子包括支持向量机和 K-最近邻。
最后的想法
在今天的文章中,我们讨论了机器学习环境中的参数和非参数方法。此外,我们还探讨了它们的优缺点。
参数方法指的是一组算法,这些算法往往不太灵活、不太准确,但更容易解释,而非参数方法往往更灵活(因此适用于更复杂的问题),也更准确,但不太容易解释。
尽管参数方法不太灵活,有时不太准确,但它们在许多用例中仍然有用,因为在较简单的问题中使用相当灵活的非参数方法可能会导致过度拟合。
成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。
你可能也会喜欢
Python 中的父母和孩子

照片由 Unsplash 上的 vivek kumar 拍摄
继承:扩展类以产生新的类
您可能知道,在面向对象的编程语言中创建一个全功能的类是非常耗时的,因为真实的类执行许多复杂的任务。
在 Python 中,可以从现有的类(父类)中获取想要的特性来创建新的类(子类)。这个 Python 特性被称为继承。
通过继承,您可以
- 获取父类的特征,
- 改变你不需要的功能,
- 给你的子类增加新的特性。(派生类或子类)
因为您正在使用一个预先使用过的、经过测试的类,所以您不必在新类中投入太多的精力。子类继承了其父类的属性和功能。
如果我们有几个相似的类,我们可以在一个类中定义它们的公共功能,并定义这个父类的子类,在那里实现特定的功能。
三名数据专业人员

作者图片
我们有 3 名数据专家。他们都懂数学和统计学,SQL,还有 Python,R,Java 等一类的编程语言。
class Data_Scientist:
def __init__(self, name, SQL):
self.name = name
self.SQL = SQL
def knows_maths_stats(self):
return True
def knows_programming(self):
return True
class Data_Analyst:
def __init__(self, name, SQL):
self.name = name
self.SQL = SQL
def knows_maths_stats(self):
return True
def knows_programming(self):
return True
class Data_Engineer:
def __init__(self, name):
self.name = name
self.SQL = SQL
def knows_maths_stats(self):
return True
def knows_programming(self):
return True
我们可以定义一个父类“Data_Professional”和 Data_Professional 类的 3 个子类:Data_Analyst、Data_Scientist 和 Data_Engineer,而不是反复编写同一个类。
父类
我们继承的类称为父类、超类或基类。
class Data_Professional:
def __init__(self, name, SQL):
self.name = name
self.SQL = SQL
def knows_maths_stats(self):
return True
def knows_programming(self):
return True
子类别
通过继承父类的功能创建的新类称为子类、派生类或子类。子类的 init()函数覆盖了父类的 init()函数。
从子类访问父类变量
Mary Smith 是一名数据科学家,精通数学和统计学、SQL 和 Python。
1。定义子类
class Data_Scientist(Data_Professional):
def __init__(self, name, SQL):
super().__init__(name, SQL)
2。初始化 dt_sci 对象:
dt_sci = Data_Scientist(“Mary Smith”, 9)
3。打印 dt_sci 的特征:
print(dt_sci.name,
dt_sci.SQL,
dt_sci.knows_maths_stats(),
dt_sci.knows_programming())Mary Smith 9 True True
空类具有其父类的属性
class Data_Engineer(Data_Professional):
passdt_en = Data_Engineer(“Hyun-mi Tokko”, 9)print(dt_en.name,
dt_en.SQL,
dt_en.knows_maths_stats(),
dt_en.knows_programming())Hyun-mi Tokko 9 True True
覆盖类
class Data_Analyst(Data_Professional):
def __init__(self, name, SQL):
super().__init__(name, SQL)
def knows_programming(self):
return Falsedt_an = Data_Analyst(“Brinda Kumar”, 7)print(dt_an.name,
dt_an.SQL,
dt_an.knows_maths_stats(),
dt_an.knows_programming())Brinda Kumar 7 True False
检查子类和实例
issubclass()方法:
内置函数 issubclass(class1,class2) 询问一个类是否是另一个类的子类。
print(issubclass(Data_Analyst, Data_Professional))Trueprint(issubclass(Data_Professional, Data_Analyst))False
isinstance()方法
内置函数 isinstance(object,class) 询问对象是否是类的实例。
print(isinstance(dt_an, Data_Analyst))Trueprint(isinstance(dt_en, Data_Analyst))False
继承的类型
有几种类型的继承。这些是单一继承、多重继承、多级继承、层次继承和混合继承。其中的前四个如下图所示。混合遗传是这四种类型遗传的混合。上面的例子是一个混合继承的例子。

作者图片
继承的优势
继承提供了很多优势。通过使用父类和子类,
- 我们可以编写更多可重用的、灵活的代码,这些代码很容易被新开发人员扩展,
- 我们可以编写更有组织、结构化、可读性更强的代码,帮助我们更容易地调试和识别错误,
- 我们可以减少代码的重复和重复,我们可以把所有的公共属性和功能放到父类中,由子类访问它们。
结论
继承是面向对象编程(OOP)中最重要的概念之一。它允许我们基于一个现有的类或多个类创建新的类。我们将在接下来的博客中讨论多态、封装和抽象。
联系我
如果你想了解我的最新文章,请在媒体上关注我。你可以在 LinkedIn 上联系我,在seymatas@gmail.com 给我发邮件!
非常感谢您的任何建议和意见!
用 Python 解析健身跟踪器数据
看看一些最常见的文件格式和有用的 Python 库

由 Cameron Venti 在 Unsplash 拍摄的照片
介绍
现在,使用健身跟踪应用程序和设备(如 Garmin、FitBit、Strava 等)来跟踪您的锻炼非常普遍,尤其是跑步和骑自行车等有氧运动。有很多手机和网络应用程序可以让你以越来越复杂的方式查看和分析你的活动。其中一些应用是由设备制造商提供的(如 Garmin Connect ),而另一些则是独立于设备和制造商的,如 Strava 。
但也许你不想依赖你的设备制造商或第三方提供商来存储和访问你的数据。(例如,Garmin 的服务器在 2020 年 7 月宕机数日,导致 Garmin 用户完全无法访问 Garmin Connect。)或者,你可能想用那些应用程序无法做到的方式来回顾和分析你的活动。
幸运的是,有了一些基本的编程技巧,自己解析和分析这些数据并不困难,唯一的限制就是你的想象力。在本文中,我们将讨论使用 Python 获取活动数据并解析它的基础知识。我们将研究存储和导出活动数据的最常见格式,并探索一些用于解析活动数据的有用 Python 库。还会有一些示例脚本来解析数据文件并使用数据构建[pandas](https://pandas.pydata.org/)数据帧(否则我们不会在这里讨论 pandas,阅读本文不需要熟悉 pandas——一些 Python 和 XML 的基础知识会有所帮助)。示例脚本可以在以下 GitHub repo 中找到(运行所需的最低 Python 版本是 3.6,依赖项可以在 Pipfile 中找到):
https://github.com/bunburya/fitness_tracker_data_parsing
当我们说“活动数据”时,我们主要指的是 GPS 和时间数据,它们描述了一些基于运动的活动,如跑步、步行或骑自行车,以及您的设备可能提供的补充数据,如海拔、心率和节奏数据。
本文中描述的方法主要基于我对我的 Garmin vívoactive 3 手表(为了方便起见,我称之为 VA3)或我的旧款 Garmin Forerunner 30 手表(我称之为 FR30)记录的活动的研究,我主要用它们来记录跑步和步行。每个设备记录和导出数据的方式会有所不同,因此您的里程可能会有所不同,但我们将讨论的文件格式和库应该有助于与各种流行的跟踪设备和应用程序配合使用。
除非明确声明,否则我没有参与本文中讨论或链接的任何服务、软件或文章的开发。
如何获取数据
从应用程序导出
许多更受欢迎的应用程序都为您提供了将活动导出为常见文件格式的选项。例如,Strava 和 Garmin Connect 都允许您将活动导出为 GPX 和 TCX 格式,并下载原始源文件(可能是 FIT 文件)。Strava 的指令是这里的和 Garmin Connect 的指令是这里的。当然,你需要有一个账号,并且已经将你的活动上传到相关 app。
如果你想分析很多活动,你可能想批量下载文件,而不是一个接一个地下载。并非所有应用程序都提供批量活动导出功能。然而,Strava、Garmin 和其他遵守一般数据保护条例 (GDPR)的公司应该给你一个选项,下载他们掌握的所有关于你的个人数据,这将包括你的活动数据(以及其他数据)。看这里的Strava 的和这里的Garmin 的。您还可以找到第三方脚本,以更方便的方式批量下载您的活动,例如 Garmin Connect 的Python 脚本(任何此类脚本都可能会要求您提供用户名和密码,以便下载您的活动,因此请小心,只使用您信任的软件)。
对于 Strava 和 Garmin Connect 以外的应用程序,请咨询其常见问题或技术支持以了解其导出选项。
直接从设备
有些设备可以通过 USB 接口连接到您的电脑上,这样您就可以直接访问这些设备上的活动文件。这是否可能以及在哪里可以找到活动文件将取决于您的设备,因此如果有疑问,请查看设备制造商提供的常见问题解答或技术支持。例如,FR30 和 VA3 都有一个名为 GARMIN 的目录,其中包含一个名为 ACTIVITY 的目录。该目录包含作为 FIT 文件的活动数据。
如何解析数据
用 gpxpy 解析 GPX 文件
GPS 交换格式 (GPX)是一种开放的基于 XML 的格式,通常用于存储基于 GPS 的数据。在本文将要讨论的三种格式中,GPX 可能是最容易使用的。这是一种简单且记录良好的格式,并且有几种有用的工具和库可用于处理 GPX 数据。(另一方面,TCX 和 FIT 文件可以包含比 GPX 文件更多的活动信息。)我们将使用 Python 的[gpxpy](https://github.com/tkrajina/gpxpy)库来处理 GPX 文件。
但是首先,让我们看看 GPX 文件是什么样子的。举例来说,这里有一个截屏显示(前几行)并排的两个 GPX 文件,一个从 Strava 下载,另一个从 Garmin Connect 下载(但都是使用 my FR30 中相同的底层数据生成的)。

两个 GPX 档案:左边是斯特拉瓦;右边是 Garmin
在每个文件中,您可以看到根元素是一个gpx元素,有几个属性描述了 GPX 文件的创建者和其中使用的 XML 名称空间。在gpx元素中有一个metadata元素,包含关于文件本身的元数据,还有一个trk元素表示“轨迹”,即“描述路径的有序点列表”。这大致对应于我们通常认为的单一活动(跑步、骑自行车、散步等)。
trk元素包含一些关于活动的元数据,比如它的名称和它的活动类型(稍后将详细介绍),以及一个或多个trkseg元素,每个元素代表一个“轨道片段”,它是“一个按顺序逻辑连接的轨道点列表”。换句话说,一个trkseg应该包含连续的 GPS 数据。如果你的活动只是打开你的 GPS,跑 10 公里,然后在完成后关闭你的 GPS,那么整个活动通常是一个单独的轨道段。然而,无论出于何种原因,如果您在活动中关闭并再次打开了 GPS(或者丢失了 GPS 功能,然后又恢复了),则trk可能由多个trkseg元素组成。(至少,理论上是这样,根据文档;当我在跑步过程中暂停并重新启动我的 VA3 时,它似乎仍然将整个跑步过程表示为一个单独的赛道段。)
每个trkseg元素应该包含一个或多个(可能很多个)trkpt或“跟踪点”元素,每个元素代表由您的 GPS 设备检测到的单个(地理)点。这些点通常相隔几秒钟。
至少,trkpt必须包含纬度和经度数据(作为元素的属性lat和lon),并且可以可选地包含时间和海拔(ele)数据,作为子元素(由健康跟踪器生成的数据很可能至少包含时间)。一个trkpt也可以包含一个extensions元素,它可以包含附加信息。在上面的例子中,扩展元素(Garmin 的TrackPointExtension(TPE)格式)用于存储 FR30 提供的心率和节奏数据。
我想指出上面显示的两个 GPX 文件之间的三个主要区别。首先,trk元素的【the Garmin 文件将其描述为“running ”,而 Strava 文件只是将其描述为“9”。没有标准化的方法来表示轨道的类型。Garmin 使用诸如“跑步”、“散步”、“徒步旅行”等词语。,而 Strava 使用数字代码,例如“4”代表徒步旅行,“9”代表跑步,“10”代表步行,等等。我找不到 Strava 数字代码到活动类型的全面映射。如果您想要查找特定活动类型的代码,您可以在 Strava 上编辑现有活动的活动类型(单击活动页面左侧的铅笔图标),然后将其导出到 GPX,以检查type元素中的值。
其次,报告的轨迹点高程不同,这似乎令人惊讶,因为它们基于相同的基础数据。一些健身追踪器(似乎包括 FR30)要么不记录海拔数据,要么根据 GPS 信号进行非常不准确的记录。在这些情况下,Strava 和 Garmin 等应用程序使用自己的内部高程数据库和算法来生成自己的高程数据或调整设备记录的数据,以便给出更真实的读数(请参见此处的了解 Strava 的更多信息)。每个应用程序生成或调整高程数据的方法会略有不同,您将在此见证这种差异。
最后,您会注意到 Garmin 文件报告的纬度和经度数据要精确得多,有时会给出大约 30 个小数位的值,而 Strava 文件会给出 7 个小数位的值。Garmin 文件似乎反映了 FR30 报告的原始数据的精确度,而 Strava 似乎对数据进行了四舍五入。需要注意的是精度不同于精度。报告纬度和经度到小数点后 30 位意味着真正的微观精度水平,而你的健身追踪器中的 GPS 可能最多精确到几米。因此,你的健身追踪器报告的所有额外精度并不是特别有用。但是,它可能会对活动的总记录距离(通过累加所有点之间的距离计算)产生微小但明显的影响,因此总距离可能会因数据的来源而略有不同。
所以,我们来看看gpxpy库。首先,确保它已安装:
pip install gpxpy
现在让我们在与 GPX 文件相同的目录中启动一个 Python 解释器(在本文的其余部分,我将数据用于一个与我们上面看到的不同的活动)。解析文件非常简单:
>>> import gpxpy
>>> with open('activity_strava.gpx') as f:
... gpx = gpxpy.parse(f)
...
>>> gpx
GPX(tracks=[GPXTrack(name='Morning Walk', segments=[GPXTrackSegment(points=[...])])])
你可以看到,在 GPX 文件对象上调用gpxpy.parse会给你一个GPX对象。这是一种反映 GPX 文件本身结构的数据结构。其中,它包含了一个GPXTrack对象的列表,每个对象代表一个轨迹。每个GPXTrack对象包含一些关于音轨的元数据和一个片段列表。
>>> len(gpx.tracks)
1
>>> track = gpx.tracks[0]
>>> track
GPXTrack(name='Morning Walk', segments=[GPXTrackSegment(points=[...])])
>>> track.type
'10'
>>> track.name
'Morning Walk'
>>> track.segments
[GPXTrackSegment(points=[...])]
每个GPXTrackSegment依次包含一个GPXTrackPoint对象列表,每个对象反映一个单独的跟踪点。
>>> segment = track.segments[0]
>>> len(segment.points)
2433
>>> random_point = segment.points[44]
>>> random_point
GPXTrackPoint(40.642868, 14.593911, elevation=147.2, time=datetime.datetime(2020, 10, 13, 7, 44, 13, tzinfo=SimpleTZ("Z")))
>>> random_point.latitude
40.642868
>>> random_point.longitude
14.593911
>>> random_point.elevation
147.2
>>> random_point.time
datetime.datetime(2020, 10, 13, 7, 44, 13, tzinfo=SimpleTZ("Z"))
以扩展名形式存储在 GPX 文件中的信息也可以被访问。在这种情况下,相关的 XML 元素(即 GPX 文件中的extensions元素的子元素)存储在一个列表中。
>>> random_point.extensions
[<Element {[http://www.garmin.com/xmlschemas/TrackPointExtension/v1}TrackPointExtension](http://www.garmin.com/xmlschemas/TrackPointExtension/v1}TrackPointExtension) at 0x7f32bcc93540>]
>>> tpe = random_point.extensions[0]
>>> for child in tpe:
... print(child.tag, child.text)
...
{[http://www.garmin.com/xmlschemas/TrackPointExtension/v1}hr](http://www.garmin.com/xmlschemas/TrackPointExtension/v1}hr) 134
{[http://www.garmin.com/xmlschemas/TrackPointExtension/v1}cad](http://www.garmin.com/xmlschemas/TrackPointExtension/v1}cad) 43
除了保存从底层 GPX 文件解析的数据之外,GPXTrack和GPXTrackSegment对象还有一些有用的方法来计算我们可能想知道的基于数据的东西。例如,您可以计算轨道或片段的总长度:
>>> segment.length_2d() # ignoring elevation
8104.369313043303
>>> segment.length_3d() # including elevation
8256.807195641411
或者关于移动时间或速度(米/秒)的数据:
>>> segment.get_moving_data()
MovingData(moving_time=7829.0, stopped_time=971.0, moving_distance=8096.192269756624, stopped_distance=160.6149258847903, max_speed=1.7427574692488983)
>>> segment.get_speed(44) # The number of the point at which you want to measure speed
1.157300752926421
有各种其他方法可用于计算其他度量,以及调整或修改数据的方法,例如通过添加或删除点、分割段、平滑值等。您可以通过调用相关对象上的help来探索这些。
最后,这里的是一个 Python 脚本,用于解析 GPX 文件并将一些关键数据放入熊猫数据帧。在我们的 GPX 文件中调用这个脚本:
python3 parse_gpx.py activity_strava.gpx
…将输出如下内容:

带有跟踪点数据的熊猫数据帧。
用 lxml 解析 TCX 文件
培训中心 XML (TCX)格式是存储活动数据的另一种常见格式,由 Garmin 创建。要理解 GPX 和 TCX 之间的区别,最简单的方法是把这两个文件放在一起看:

GPX 的文件在左边;TCX 的文件在右边。
您可能会注意到的第一件事是,TCX 文件中的数据点被分组为“圈数”,每个Lap元素都有一些相关的有用数据,如一圈所用的总时间、消耗的卡路里、平均和最大心率等。什么构成“lap”取决于设备是如何配置的;在这种情况下,这项活动被分成 1000 米的“圈”或“分”跑。
您可能已经注意到的另一件事是,TCX 文件中的第一个Trackpoint元素包含高度、距离、心率和速度数据,但不包含纬度或经度数据。这种情况偶尔会发生,反映了设备记录的原始(拟合)数据的结构。我只能猜测发生这种情况是因为该数据(不依赖于 GPS)被单独报告给纬度和经度数据。因为 GPX 文件中的一个trkpt元素必须包含纬度和经度,所以 GPX 文件不可能单独记录海拔高度(etc);它必须与一些纬度和经度数据相关联。因此,从 Garmin Connect 下载的 GPX 文件似乎只是忽略了那些没有纬度和经度数据的数据点,而从 Strava 下载的 GPX 文件似乎包含了它们,并使用下一个点的数据“填充”了缺少的纬度和经度数据。
除了以上几点,TCX 文件的结构与 GPX 文件的结构没有太大的不同。根元素是一个TrainingCenterDatabase元素,它包含一个Activities元素。该元素有一个或多个Activity元素,每个元素描述一个活动。除了一些元数据,Activity元素还包含许多Lap元素。每个Lap元素包含一些关于相关圈(或分段)的元数据,以及一个包含许多Trackpoint元素的Track元素,每个元素代表由设备报告的一个数据点,其中可能(也可能不)包含纬度和经度、高度、心率、步调、距离和速度数据。
我不知道有什么现成的 Python 库可以处理 TCX 文件,但是考虑到它只是一种 XML 文件,您可以使用lxml或 Python 的标准xml库来解析它。这里的是一个 Python 脚本,它使用lxml库来解析一个 TCX 文件,并将一些关键数据放入一个 pandas DataFrame,类似于上面链接到 GPX 文件的那个。注意,我们还使用python-dateutil库来轻松解析 ISO 8601 格式的时间戳。利用 TCX 文件中包含的额外信息,我们创建了一个包含单圈信息的额外数据帧。如下调用该脚本(确保您已经安装了lxml和python-dateutil):
python3 parse_tcx.py activity_strava.tcx
…会给你这样的东西:

熊猫数据帧与圈和跟踪点数据。
用 fitdecode 解析 FIT 文件
与基于 XML 的 GPX 和 TCX 格式不同,灵活且可互操作的数据传输 (FIT)协议是由 Garmin 创建的二进制格式。[fitdecode](https://pypi.org/project/fitdecode/)是一个解析 FIT 文件的 Python 库。这个库的文档在这里。它可以这样安装:
pip install fitdecode
fitdecode库允许你创建一个读取 FIT 文件的FitReader对象。然后,您可以遍历FitReader来依次访问 FIT 文件中的每个“帧”或数据块。根据底层数据记录的类型,每个帧由一个FitHeader、FitDefinitionMessage、FitDataMessage或FitCRC对象表示。FitDataMessage是我们感兴趣的对象,因为它是包含实际数据的对象。但是并不是每一个FitDataMessage都是相关的;其中许多可能只包含有关设备状态的数据或有关文件本身的元数据。出于目前的目的,我们正在寻找一个FitDataMessage,其中name属性为lap或record:
with fitdecode.FitReader('activity_garmin.fit') as fit_file:
for frame in fit_file:
if isinstance(frame, fitdecode.records.FitDataMessage):
if frame.name == 'lap':
# This frame contains data about a lap.
elif frame.name == 'record':
# This frame contains data about a "track point".
您可以检查给定帧的数据字段,如下所示:
for field in frame.fields:
# field is a FieldData object
print(field.name)
并且可以使用FitDataMessage对象的has_field、get_field和get_value方法来访问相关数据。
# Assuming the frame is a "record"
if frame.has_field('position_lat') and frame.has_field('position_long'):
print('latitude:', frame.get_value('position_lat'))
print('longitude:', frame.get_value('position_long'))
# Or you can provide a "fallback" argument to give you a default
# value if the field is not present:
print('non_existent_field:', frame.get_value('non_existent_field', fallback='field not present'))
上面的代码(如果在上下文中调用,其中frame是消息类型record的FitDataMessage对象,并且具有纬度和经度数据)将产生类似如下的输出:
latitude: 484805747
longitude: 174290634
non_existent_field: field not present
现在,你会注意到latitude和longitude被存储为整数。根据这篇 StackOverflow 帖子,将这些整数转换成度数的方法是用它们除以(2**32)/360:
>>> 484805747 / ((2**32)/360)
40.63594828359783
>>> 174290634 / ((2**32)/360)
14.608872178941965
以下是 my VA3 生成的 FIT 文件中一些更有用的字段:
- 对于
lap帧:start_time、start_position_lat、start_position_long、total_elapsed_time、total_distance、total_calories、avg_speed、max_speed、total_ascent、total_descent、avg_heart_rate、max_heart_rate、avg_cadence、max_cadence、avg_power、max_power - 对于
record帧:timestamp、position_lat、position_long、distance、altitude、enhanced_altitude、speed、enhanced_speed、heart_rate、cadence
还有其他的,不同的设备可能报告不同的数据,所以值得探索你自己的文件,看看你能找到什么。此外,并非每个字段都会始终存在,例如,正如我们在上一节中看到的,有时纬度和经度数据可能不会被报告。因此,使用has_field方法或者为get_value提供一个后备参数是一个很好的实践。
这里的是一个基本的脚本,它解析一个 FIT 文件并生成带有圈和轨迹点信息的 pandas 数据帧,类似于我在上一节中为 TCX 文件链接的脚本。

熊猫数据帧与圈和跟踪点数据。
接下来呢?
既然你已经知道了使用 Python 获取和解析你的健身追踪器数据的基本知识,那就看你想用它做什么了。你可能想做的事情之一是以某种方式可视化数据,使用[matplotlib](https://matplotlib.org/)、[seaborn](https://seaborn.pydata.org/)、[plotly](https://plotly.com/)或其他一些数据可视化库。
下面是一些您可能会感兴趣的文章和库:
- 本文部分讨论了我们在本文中讨论过的内容,但继续讨论了绘制和转换 GPS 数据的基础知识,并对如何计算两点之间的距离进行了有益的讨论。
- 本文讨论如何使用叶子可视化 GPS 数据。
- 如果您的设备没有报告海拔数据,请查看与
gpxpy出自同一作者的[srtm.py](https://github.com/tkrajina/srtm.py),这将让您使用美国宇航局的航天飞机雷达地形任务数据来查找海拔数据。
感谢阅读,希望这篇文章对你有所帮助!
用 Pandas 解析固定宽度的文本文件
实践教程
如何轻松地将固定宽度的文本文件读入 Pandas 数据帧

什么是固定宽度文本文件?
固定宽度文件类似于 csv 文件,但不是使用分隔符,每个字段都有固定数量的字符。这将创建所有数据整齐排列的文件,当在文本编辑器中打开时,其外观类似于电子表格。如果您在文本编辑器中查看原始数据文件,这很方便,但是当您需要以编程方式处理数据时,这就不太理想了。
固定宽度文件有一些常见的怪癖要记住:
- 当值不消耗字段的总字符数时,使用填充字符来增加该字段的总字符数。
- 只要在整个文件中保持一致,任何字符都可以用作填充字符。空白是一种常见的填充字符。
- 字段中的值可以左对齐或右对齐,并且文件中所有字段的对齐方式必须一致。
此处提供了固定宽度文件的详细描述。
注意:固定宽度文件中的所有字段不需要有相同的字符数。例如:在一个有三个字段的文件中,第一个字段可以是 6 个字符,第二个是 20 个字符,最后一个是 9 个字符。
如何识别固定宽度的文本文件?
最初检查时,当空白用作填充字符时,固定宽度的文件看起来像制表符分隔的文件。如果你试图读取一个固定宽度的 csv 或 tsv 格式的文件并得到错误的结果,试着在文本编辑器中打开它。如果数据排列整齐,这可能是一个固定宽度的文件。许多文本编辑器还会给出光标位置的字符数,这使得在字符数中发现模式变得更加容易。
如果您的文件太大,不容易在文本编辑器中打开,有多种方法可以在命令行上将它的一部分采样到一个单独的较小的文件中。Unix/Linux 系统上的一个简单方法是head命令。下面的例子使用head和-n 50来读取large_file.txt的前 50 行,然后将它们复制到一个名为first_50_rows.txt的新文件中。
head -n 50 large_file.txt > first_50_rows.txt
让我们使用一个真实的示例文件
UniProtKB 数据库
UniProt Knowledgebase (UniProtKB)是一个可免费访问的综合性数据库,包含了 CC-BY (4.0)许可下可用的蛋白质序列和注释数据。UniProtKB 的 Swiss-Prot 分支已经人工注释和审查了各种生物体的蛋白质信息。UniProt 数据的完整数据集可从 ftp.uniprot.org下载。人类蛋白质的数据包含在一组固定宽度的文本文件中: humchr01.txt - humchr22.txt、humchrx.txt和humchry.txt。
对于这个例子,我们不需要所有的 24 个文件,所以这里是指向集合中第一个文件的链接:
在用熊猫阅读文件之前检查它
在文本编辑器中快速浏览一下该文件,可以看到一个我们不需要导入 6 个数据字段的大标题。

humchr01.txt 开头的片段
固定宽度文件似乎不像许多其他数据文件格式那样常见,乍看起来它们可能像制表符分隔的文件。在尝试使用 Pandas 阅读文件之前,在一个好的文本编辑器中对文本文件进行视觉检查可以大大减少挫折感,并有助于突出格式模式。
使用 pandas.read_fwf()和默认参数
注意:这个例子的所有代码都是为 Python3.6 和 Pandas1.2.0 编写的
[pandas.read_fwf()](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_fwf.html)的文档列出了 5 个参数:
filepath_or_buffer、colspecs、widths、infer_nrows和**kwds
两个pandas.read_fwf()参数colspecs和infer_nrows具有默认值,用于根据初始行的抽样推断列。
让我们利用pandas.read_fwf()的默认设置来获得整洁的数据帧。我们将把colspecs参数保留为它的默认值‘infer ’,这又利用了infer_nrows参数的默认值(100)。这两个默认值试图在数据的前 100 行(在任何跳过的行之后)中找到一个模式,并使用该模式将数据分成列。
基本文件清理
在我们的示例文件中,表格信息之前有几行文件头。我们需要在读取文件时跳过它们。
没有一个参数适合在读取文件时跳过行。那么我们该怎么做呢?我们利用**kwds参数。
便利的是,pandas.read_fwf()使用与[pandas.read_table()](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_table.html)相同的TextFileReader上下文管理器。这与**kwds参数相结合,允许我们将pandas.read_table()的参数与pandas.read_fwf()一起使用。所以我们可以使用skiprows参数跳过示例文件中的前 35 行。类似地,我们可以使用skipfooter参数跳过示例文件的最后 5 行,其中包含不属于表格数据的页脚。
pandas.read_fwf('humchr01.txt', skiprows=35, skipfooter=5)
上面的尝试让数据帧有点混乱😔:

注意:因为我们对colspecs和infer_nrows使用默认值,所以我们不必声明它们。
这里的部分问题是,默认的colspecs参数试图根据前 100 行推断列宽,但是表格数据之前的行(文件中的第 36 行,显示在上面的列名中)实际上并不遵循数据表中的字符计数模式,所以推断的列宽变得混乱。
如果我们将skiprows设置为 36 而不是 35,我们会将第一行数据推入列名,这也会破坏推断的列宽。如果没有额外的清理工作,就没有胜利可言。让我们用names参数解决列名问题,看看是否有帮助。
注意:使用names参数意味着我们没有将文件中的一行分配给列名,因此我们作为用户必须确保考虑到这样一个事实,即skiprows必须从第一个数据行开始。所以在下一个例子中skiprows被设置为 36,但是在之前的例子中,当我们没有使用names参数时,它是 35。
pandas.read_fwf('humchr01.txt', skiprows=36, skipfooter=5, names=['gene_name', 'chromosomal_position', 'uniprot', 'entry_name', 'mtm_code', 'description'])

好多了,但还是有点乱。Pandas 推断列拆分是正确的,但是将前两个字段推到了索引中。让我们通过设置index_col=False来解决索引问题。
pandas.read_fwf('humchr01.txt', skiprows=36, skipfooter=5, index_col=False, names=['gene_name', 'chromosomal_position', 'uniprot', 'entry_name', 'mtm_code', 'description'])

看起来不错!列拆分正确,列名有意义,数据帧中的第一行数据与示例文件中的第一行数据相匹配。
我们依靠两个pandas.read_fwf()特定参数的默认设置来获得整洁的数据帧。colspecs参数保留其默认值“infer ”,它反过来利用infer_nrows参数的默认值,并在数据的前 100 行(跳过的行之后)中找到一个模式,并使用该模式将数据分成列。默认参数在这个示例文件中运行良好,但是我们也可以指定colspecs参数,而不是让 pandas 推断列。
使用 colspecs 手动设置字段宽度
就像上面的例子一样,我们需要从一些基本的清理开始。我们将在文件中放置页眉和页脚,并像前面一样设置列名。
下一步是用每个字段的间隔建立一个元组列表。下面的列表符合示例文件。
colspecs = [(0, 14), (14, 30), (30, 41), (41, 53), (53, 60), (60, -1)]
注意最后一个元组:(60,-1)。我们可以用-1 来表示最后一个索引值。或者,我们可以使用None而不是-1 来表示最后一个索引值。
注意:当使用colspecs时,元组不必是排除性的!如果需要,可以将最终列设置为重叠的元组。例如,如果您想要复制第一个字段:colspecs = [(0, 14), (0, 14), ...
pandas.read_fwf('humchr01.txt', skiprows=36, skipfooter=5, colspecs=colspecs, names=['gene_name', 'chromosomal_position', 'uniprot', 'entry_name', 'mtm_code', 'description'])

我们又一次获得了一个整洁的数据框架。这一次,我们使用colspecs参数明确声明了我们的字段开始和结束位置,而不是让 pandas 推断字段。
结论
用 Pandas 阅读固定宽度的文本文件既简单又容易。pandas.read_fwf()的默认参数在大多数情况下都有效,定制选项有据可查。熊猫图书馆有许多功能可以让读取各种文件类型,而pandas.read_fwf()是一个需要记住的更有用的熊猫工具。
使用 Python 解析 QIF 文件以检索金融数据
Quiffen 包的基本概述以及它如此有用的原因

如果你曾经试图完成一个围绕个人财务的数据科学项目,你可能熟悉银行导出的交易数据 CSV 文件的乐趣。
简而言之,它们非常糟糕。大多数银行会允许你将银行对账单导出为 CSV 格式,但很多银行不会告诉你这些 CSV 文件的结构有多糟糕。不同的银行对金额、参考等使用不同的标题名称,对每个银行进行硬编码几乎是不可能的。
这就是 QIF 档案的用处。QIF(Quicken Interchange Format)是由 Intuit 开发的一种文件格式,最初用于 QuickBooks 产品。然而,这种格式现在已经很老了(甚至比 OFX 还老)。
尽管如此,大多数银行和其他金融机构(股票经纪人等。)将在导出数据时提供 QIF 作为选项。尽管年代久远,QIF 文件格式仍然有用,因为:
- 它是标准化的—不再需要为不同的银行硬编码不同的 CSV 模式
- 它被广泛使用
- 它是用纯文本编写的,所以很容易解析
Quiffen 是一个 Python 包,用于解析 QIF 文件,并以更有用的格式导出数据,如字典或熊猫数据帧。Quiffen 可以通过运行以下命令从命令行安装:
pip install quiffen
从那里,您就可以开始分析您的数据了!
解析 QIF 文件
以下是如何解析 QIF 文件并将其中的事务导出到数据帧的代码示例:
您可以选择不同的数据来导出,例如类别(由树表示)或存储在Qif对象中的帐户。您还可以将数据从导出到带有标题的 CSV 文件中,每次都与to_csv()方法保持一致。
创建您自己的 QIF 文件
Quiffen 还支持 QIF 结构的创建和导出!你可以创建自己的Qif对象,添加账户、交易、类别、类等等。下面显示了一个例子,但是完整的 API 参考可以在 Quiffen 文档中找到:
您可以在 GitHub 上查看 Quiffen 包的完整源代码。任何反馈或(建设性的!)批评不胜感激!
https://github.com/isaacharrisholt/quiffen
我强烈建议用你自己的交易数据来试试 Quiffen,如果你也想看看这个让我的金融屁股进入状态的程序,请也这样做!
此外,如果您有任何问题,请随时在 GitHub 上发送给我,或在 LinkedIn 上与我联系!我喜欢写我的项目,所以你可能也想在 Medium 上关注我。
这一条就这么多了!我希望你喜欢它,任何反馈都非常感谢!
优化数据科学工作流程的指南
实践教程,生产力
第 1 部分:用这些提高工作效率的技巧来强化你的工作流程。从 Jupyter 笔记本到 Python 文件和模块。

作者图片
本指南由两部分组成。我建议要么喝着咖啡跟着它走,要么一大块一大块地反复阅读,因为我会以的令人麻木的细节来回顾工作流程中的四个步骤,这样你就可以拿起它,定制它,让它成为你自己的。如果您对某一部分感兴趣,可以随意点击它向前跳(仅适用于桌面)。为此,我将从头到尾给出一个目录。
为了简洁起见,在这篇文章中我们将只讨论前两步,
第一步: Jupyter 笔记本第二步: Python 文件&模块
建立工作流程是一件可怕的事情。没有一种解决方案适合所有人。从制表符 vs 空格开始,开发人员对他们的设置很挑剔,并对其他人的想法有强烈的意见。在这篇文章中,我回顾了如何优化我的工作流程。即使这不符合你的需要,你也可以把它的一点一滴收集起来,用你自己独特的方式组合起来。在一天结束的时候,开发人员的流动和生产力很重要,你应该努力去实现它。
VS 代码将用于设置工作流。尽管本文是针对 VS 代码的,但是您也可以将相同的思想应用到其他 IDE 中。
两个工作流程
典型的数据科学工作流程始于在 Jupyter 笔记本上开发代码和构建代码原型,然后将其转移到 python 文件和模块中。
大团队
如果你在一个大团队中工作,你的工作到此结束,你把你的代码交给开发人员,他们负责应用程序的归档和部署。
数据科学家: 笔记本→ Python 文件/模块 开发运营: Python 文件/模块→ Dockerize →部署
中小型团队/副业项目
但是如果你在一个中小型团队中工作,或者在你的副业项目中工作,你需要将你的应用程序 dockerize 并自己部署它。
数据科学家: 笔记本→ Python 文件/模块→ Dockerize →部署
无论你属于哪个阵营,你都可以从这个指南中获得一些技巧,并将其融入到你自己的工作流程中。
上下文开关程序
上下文切换是工作流的祸根。每当你切换环境/工具时,你会失去你的心流,需要时间来适应。尽可能避免切换上下文。在过去的几年里,我一直将 VS 代码作为 python 开发的日常驱动程序,并且卓有成效。
你在一个工具上花的时间越多,你就越擅长它。VS Code 拥有强大的社区、多语言支持、各种扩展和来自 MSFT 的支持。你最好向它过渡,宜早不宜迟。
初始设置
如果你想跟着做,确保你已经安装了 VS 代码。我将使用pyenv和poetry来设置项目。如果您需要参考资料,请查看我关于如何设置 python 环境的文章,
>mkdir dream_ds_project
>cd dream_ds_project# Setting python version
dream_ds_project>pyenv local 3.8.2# Setting up poetry
dream_ds_project>poetry init
典型的数据科学代码编写工作流程
打开 jupyter 笔记本->编写大量工作 python 语句→用函数包装→将相关函数分组到类抽象中→python 文件中的相关类和函数→模块中的相关 python 文件

作者图片
目录:
Python 互动Jupyter 快捷方式自动重新加载Pylance林挺格式化
L et 不要自欺欺人了。这是大部分时间花在开发和构建代码原型上的地方。Jupyter 笔记本是一个很好的原型制作工具。除了快速运行一段代码和共享结果,笔记本在开发工作流程中效率不高。通常,开发从 jupyter 笔记本开始,然后复制到 python 文件中。
如果我们能两全其美呢?在开发过程中,使用笔记本特性(如快速运行)制作 python 代码的原型,然后使用 IDE 特性(如林挺、格式化、代码导航、版本控制和自动完成等)?
如果我们能做到这一点,我们就可以轻松地从第一步进入第二步。通常,jupyter 笔记本将用于原型制作,然后当您开始创建文件和模块时,代码将被转移到 IDE。VS 代码提供了这两种特性,你不需要切换工具。
Python 交互式
VS Code 有一个不太为人知的特性叫做 Python 交互式笔记本。这允许您打开一个 python 文件,并使用# %%在文件上您想要的任何地方创建一个笔记本单元格。这样做的好处是,您仍然可以处理.py文件,同时获得 IDE 的所有好处。
让我们从安装包开始,
dream_ds_project>poetry shell# Install ipykernel as dev dependency
dream_ds_project>poetry add ipykernel --dev# Install prod dependency
dream_ds_project>poetry add fastpi uvicorn[standard]
当您在这种模式下运行代码时,会弹出一个新的 jupyter 笔记本窗口并为您运行代码。您的输出、图表和内容与您的代码是分开的。

Python 与 IDE 功能交互,如林挺、自动完成、文档参考以及 Jupyter 笔记本快速运行功能。(图片由作者提供)
Jupyter 快捷方式
您可以将交互式笔记本的行为更改为类似于 Jupyter 笔记本的行为。作为一个笔记本用户,我已经习惯了使用键盘快捷键来运行一个单元,运行和前进。通过使用keybindings.json文件中的以下设置,您可以在 VSCode 中做完全相同的事情。
# For running the current cell
{
"key": "shift+enter",
"command": "jupyter.execSelectionInteractive",
"when": "editorTextFocus && editorLangId == 'python'"
},
# For running the cell and create a new cell below
{
"key": "ctrl+enter",
"command": "jupyter.runcurrentcelladvance",
"when": "editorTextFocus && editorLangId == 'python'"
}

Shift+Enter 运行当前单元格,Ctrl+Enter 运行当前单元格并前进。(图片由作者提供)
自动重新加载
一旦完成了函数的编写,就可以将块移动到不同的 python 文件中。唯一的问题是,如果你回去更新你的功能,jupyter 笔记本需要重新加载,以适应新的变化。Autoreload是一款出色的笔记本扩展,使笔记本无需重新加载即可自动获取这些更改。通过将这两行添加到您的settings.json文件中,您可以获得完全相同的特性。
# Jupyter notebook startup command
"jupyter.runStartupCommands": [
"%load_ext autoreload\n%autoreload 2"
],
通过此设置,自动重新加载会自动添加到您的所有交互式笔记本中。

string_split 函数更新会立即反映出来,无需重新加载内核或重新导入。(图片由作者提供)
挂架
Pylance 是 VSCode 中最好的 python 语言服务器。在 VSCode 中,扩展提供语言支持,pylance 是支持 VSCode 中 python 特性的扩展。
它具有以下特征但不限于:
1.使用 pyright 的静态类型检查(类似于 mypy)
2。代码自动完成,参数建议,文档字符串
3。语义高亮和自动导入。


1: Pylance 自动导入 string_split 函数。 2: Pylance 使用类型提示自动生成文档字符串(图片由作者提供)
林挺
林挺对于动态编程语言来说是必不可少的。它有助于您尽早发现错误并维护代码标准。我使用 flake8、Mypy 和 pylance 的组合来验证每一个文件保存,保持我的代码没有 bug 并遵守代码标准。
- Flake8 Flake8 是一款风格引导工具,具有出色的插件支持。您可以通过在根目录中使用一个
setup.cfg来实施您自己的标准和插件。你可以把规则添加到忽略部分,但是在你添加到这个列表时要严格,因为你不想打开大门。您还可以设置最大线路长度。
可读性:代码可读性对于长期项目来说至关重要,并且是高度主观的。当在团队中工作时,你可以使用 flake8 插件系统来设置你的复杂性阈值,以便每个人都遵守它们。你可以安装 radon ,当你超过你设置的循环复杂度时,它会让你知道。类似地,你可以安装一个表达式复杂度插件来检查表达式复杂度是否超过限制。
[flake8]
*ignore* = E203, W503, E251, E501, E402, F601
*max-line-length* = 88
# Keep circular complexity in check
*radon-max-cc* = 10
# Keep expression complexity in check
*max-expression-complexity*=3
例如,对于下面的代码,当最大表达式复杂度超过 3 时,您将得到一个林挺警告。这将确保在编写代码时,可读性也受到密切监控。
open_file = *True* valid_file = *False* override_validitiy = *True* dummy_flag = *True**if* open_file *and* (valid_file or override_validitiy)*and* dummy_flag:
*print*(*True*)dream_ds_project>flake8 --max-expression-complexity=3 test.py
test.py: ECE001 Expression is too complex (4.0 > 3)
- Mypy 是 python 开发者工具箱中的一个优秀工具。它可以帮助你在错误发生之前就抓住它。一旦你开始在你的论点中加入类型提示,它就会工作得特别好,随着在整个数据科学生态系统中得到越来越多的支持,你不能要求更多。


1: Mypy 使用来自 python 标准库的类型提示来正确地猜测 os.getenv 可能返回 null,从而主动捕捉 bug。 2: Mypy 使用用户定义的类型提示来正确通知 split_string 函数接受字符串输入。(图片由作者提供)
使用 pipx 在你的系统中安装 flake8 和 mypy 是一个好主意。这样,您可以跨项目重用它们,并且只需安装一次。您可以使用 user settings.json文件中的以下设置指向 pipx 安装位置,
{
“python.linting.flake8Enabled”: *true*,
“python.linting.flake8Path”: “C:*\\*Users*\\*username*\\*.local*\\*pipx*\\*venvs*\\*flake8*\\*Scripts*\\*flake8.exe”,
“python.linting.mypyEnabled”: *true*,
“python.linting.mypyPath”: “C:*\\*Users*\\*username*\\*.local*\\*pipx*\\*venvs*\\*mypy*\\*Scripts*\\*mypy.exe”
}
通过 mypy、flake8 和 pylance 启用林挺,您甚至可以在原型开发期间安全地编写代码和捕获 bug。
格式化
- 黑色格式化有助于在团队工作时维护代码格式化标准。你有没有让队友就 PR 的代码格式进行辩论?Black 是一个 python 工具,它使用一组预定义的规则自动格式化代码。这是一个固执己见的自动格式化库。黑色在开发过程中特别有用,因为它可以用一种易于阅读的格式分解复杂的语句。重新格式化是确定性的,因此具有相同设置的用户可以获得完全相同的格式化,而不管格式化在什么操作系统、IDE 或平台上运行。您可以使用 pipx 在您的机器上设置黑色。
黑色格式化前,
**def** very_important_function(template: str, *variables, file: os.PathLike, engine: str, header: bool = **True**, debug: bool = **False**):
*"""Applies `variables` to the `template` and writes to `file`."""*
**with** open(file, 'w') **as** f:
...
黑色格式化后,
**def** very_important_function(
template: str,
*variables,
file: os.PathLike,
engine: str,
header: bool = **True**,
debug: bool = **False**,
):
*"""Applies `variables` to the `template` and writes to `file`."""*
**with** open(file, "w") **as** f:
...
- I-sort黑色不会格式化你的导入。用户在没有任何顺序的情况下随意地将东西导入到项目中。i-sort(导入排序)通过在导入中提供层次结构,为这种混乱提供了一些秩序。它格式化导入的方式是 python 标准库导入,然后是第三方库导入,最后是用户定义的库导入。在每个类别中,导入进一步以升序排序。这有助于在有大量进口时快速识别进口。
在 i-sort 之前,
from my_lib import Object
import os
from my_lib import Object3
from my_lib import Object2
import sys
from third_party import lib15, lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8, lib9, lib10, lib11, lib12, lib13, lib14
import sys
from __future__ import absolute_import
from third_party import lib3
print("Hey")
print("yo")
在 i-sort 之后,
from __future__ import absolute_import# Python Standard library
import os
import sys # Third party library
from third_party import (lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8,lib9, lib10, lib11, lib12, lib13, lib14, lib15) # User defined libarary/modules
from my_lib import Object, Object2, Object3
print("Hey")
print("yo")
在您的setup.cfg文件中使用该设置来配置 i-sort 以处理黑色。默认情况下,88 个字符的行配置为黑色。确保 flake8 和 i-sort 也配置为完全相同的设置。
[flake8]
*max-line-length* = 88
[isort]
*line_length* = 88

作者图片
目录:
这是非常关键的一步,因为这是设计诸如函数、类和模块等抽象概念的地方。在这个步骤中,数据科学家可以从 python 开发人员的工作流程中学到很多东西。
Dotenv
假设您有以下文件结构,
dream_ds_project
--dev # Jupyter notebook folder
--notebook1.py
--notebook2.py
--src # Source code folder
--module1.py
--module2.py
--.env # Environment variable file
--setup.cfg # Configuration file for python tools
你首先在 dev/notebook1.py 中构建一个函数test_prototype。然后你可以将这个函数移动到 src/module1.py 中。现在当你必须导入这个函数时,在根文件夹中的.env文件中设置PYTHONPATH,就像这样。
# Set pythonpath to a relative path. In this case it sets where .env # file is present as the root path
PYTHONPATH=.
现在您可以将 dev/notebook1.py 中的 test_prototype 导入为
from src.module1 import test_prototype
.env是一个特殊的文件。您可以使用它来存储敏感信息,如密码、密钥等。这不应该是 git 提交的一部分,应该保密。保留两份.env文件,一份用于生产,一份用于开发。
生产。env 文件可能是这样的,
MONGODB_USER=prod_user
MONGODB_PWD=prod_pwd
MONGODB_SERVER=prod.server.com
反之发展。env 文件可能类似于,
MONGODB_USER=dev_user
MONGODB_PWD=dev_pwd
MONGODB_SERVER=dev.server.com
您可以使用 python-dotenv 库将这些变量加载到您的环境中。在 python 代码中,可以像这样访问这些变量,
from dotenv import load_dotenv
import os# Call the function to read and load the .env file into local env
load_dotenv()print(os.getenv("MONGODB_SERVER"))
>>prod.server.com # For prod .env file
>>dev.server.com # For dev .env file
这有助于保持 prod 和 dev 的通用代码,只需替换。基于代码运行环境的 env 文件。
预提交
预提交有助于验证您的 git 提交。它有助于维护一个清晰的 git 提交历史,并提供了一种在每次提交之前进行用户定义的验证的机制。它有一个强大的生态系统,并且有一个你能想到的大多数常见提交验证的插件。
内置钩子:
我最喜欢的一些内置预提交钩子是,
- detect-AWS-credentials&detect-private-key确保提交中不包含意外的敏感信息。
- 检查添加的大文件以确保提交不包括超过 1MB 的文件大小,这可以使用
maxkb参数来控制。我发现这非常有用,因为代码文件很少大于 1MB,这可以防止在数据科学工作流中意外提交大数据文件。 - check-ast 确保代码在语法上是有效的 python 代码。
安装预提交,poetry add pre-commit — dev 创建一个.pre-commit-config.yaml文件,
repos:
- repo: [https://github.com/pre-commit/pre-commit-hooks](https://github.com/pre-commit/pre-commit-hooks)
rev: v3.2.0
hooks:
- id: detect-aws-credentials
- id: detect-private-key
- id: check-added-large-files
args: ['--maxkb=1000']
- id: check-ast
插件:
在内置钩子之上,预提交也提供了对插件的支持。我最喜欢的一些插件是,
- 黑色确保所有提交文件的格式遵循黑色惯例。
- Mypy 验证静态类型检查没有错误。
- Flake8 确保遵守编码标准。
- pytest 在提交之前确保所有的测试都通过。这对于小型项目特别有用,因为您没有 CI/CD 设置,测试可以在本地完成。
- repo: [https://github.com/psf/black](https://github.com/psf/black)
rev: 20.8b1
hooks:
- id: black
args: ['--check']
- repo: [https://github.com/pycqa/isort](https://github.com/pycqa/isort)
rev: '5.6.3'
hooks:
- id: isort
args: ['--profile', 'black', '--check-only']
- repo: [https://github.com/pre-commit/mirrors-mypy](https://github.com/pre-commit/mirrors-mypy)
rev: v0.800
hooks:
- id: mypy
- repo: [https://gitlab.com/pycqa/flake8](https://gitlab.com/pycqa/flake8)
rev: '3.8.3'
hooks:
- id: flake8
args: ['--config=setup.cfg']
- repo: local
hooks:
- id: pytest-check
name: pytest-check
entry: pytest
language: system
pass_filenames: *false* always_run: *true*
预提交仅读取文件并验证提交,它从不对文件执行格式化或任何写入操作。如果出现验证错误,它会取消提交,您可以在再次提交之前返回并修复错误。
由于提交大型数据文件而导致的预提交失败示例
dream_ds_project> git commit -m "precommit example - failure"
Detect AWS Credentials...................................................Passed
Detect Private Key.......................................................Passed
Check for added large files..............................................**Failed**
- hook id: check-added-large-files
- exit code: 1**all_data.json (18317 KB) exceeds 1000 KB.**Check python ast.........................(no files to check)Skipped
black....................................(no files to check)Skipped
mypy.....................................(no files to check)Skipped
flake8...................................(no files to check)Skipped
预提交成功示例,
dream_ds_project> git commit -m “precommit example — success”
Detect AWS Credentials……………………………………………Passed
Detect Private Key……………………………………………….Passed
Check for added large files……………………………………….Passed
Check python ast…………………………………………………Passed
black…………………………………………………………..Passed
mypy……………………………………………………………Passed
flake8………………………………………………………….Passed
[master] precommit example — success
7 files changed, 54 insertions(+), 33 deletions(-)
触摸打字
触摸打字是一个基本的生产力技巧,适用于任何计算机任务。如果你花大量的时间在电脑前,这是至关重要的。只要每天练习几分钟,你就能达到显著的打字速度。
触摸输入对程序员来说至关重要,因为重点更多的是输入特殊字符,当你思考逻辑时,你不想把注意力集中在键盘上。Keybr 是一个非常棒的培训触摸打字的网站。


1: 我的平均触摸打字速度从大约 64 wpm 逐渐增加到大约 90wpm,有些测试的峰值为 110 wpm, 2: 这是我在 mercurial python 代码库上进行的典型打字测试的结果,速度为 42 wpm,比触摸打字慢得多。(图片由作者提供)
除了触摸输入,编程还涉及到许多特殊字符,其中一些字符是语言特有的。出于这个原因,有一些网站可以让你练习并提高你的代码编写速度。如果你想练习从 Python 到 PHP 的任何编码语言,Typing.io 真的很棒。代码编写速度将比一般的触摸打字慢,因为它涉及到许多在英语散文中通常不使用的特殊字符。
精力
VIM 是程序员所有生产力工具之母。VIM 是一种学习曲线很陡的工具,但是一旦你掌握了它,你就离不开它了。VIM 使用户能够利用键盘快速浏览他们的代码库,编辑代码。如果你知道触摸输入并练习你的代码输入技巧,这将会更好。
只要看到 VIM 的可能性,就会激励你去学习它。下面,我提供了一系列 gif 来解释 VIM 如何简化您的工作流程。



1。无鼠标导航的快速跳转,后面是一个单词的重命名。 2。使用点运算符重复上一个动作。 3。使用 VIM 宏记录一系列 VIM 动作,并在多行上执行。(图片由作者提供)
VIM 也鼓励你不要使用箭头,因为使用它们将需要你重新定位你的手。VIM 鼓励保持在 f 和 j 的顶部,因此不要浪费时间移动和重新校准你的手。出于这个原因,我将所有 IDE 箭头的用法都配置为 VIM 的 j 和 k。
VIM 启发的键盘快捷键,不使用箭头导航。将此添加到您的keybindings.json,
# Move up and down jupyter notebook without arrows
{
"key": "ctrl+j",
"command": "jupyter.gotoNextCellInFile",
"when": "editorTextFocus && editorLangId == 'python'"
},
{
"key": "ctrl+k",
"command": "jupyter.gotoPrevCellInFile",
"when": "editorTextFocus && editorLangId == 'python'"
}# Workbench quick select without arrows
{
"key": "ctrl+j",
"command": "workbench.action.quickOpenSelectNext",
"when": "vim.active && inQuickOpen"
},
{
"key": "ctrl+k",
"command": "workbench.action.quickOpenSelectPrevious",
"when": "vim.active && inQuickOpen"
},# Loop through autosuggestions without arrows
{
"key": "ctrl+j",
"command": "selectNextSuggestion",
"when": "suggestWidgetVisible && vim.active"
},
{
"key": "ctrl+k",
"command": "selectPrevSuggestion",
"when": "suggestWidgetVisible && vim.active"
},# Loop through search window without arrows
{
"key": "ctrl+j",
"command": "search.action.focusNextSearchResult",
"when": "vim.active && searchViewletVisible"
},
{
"key": "ctrl+k",
"command": "search.action.focusPreviousSearchResult",
"when": "vim.active && searchViewletVisible"
},# Loop through multiple terminals with j and k
{
"key": "ctrl+shift+k",
"command": "workbench.action.terminal.focusNext"
},
{
"key": "ctrl+shift+j",
"command": "workbench.action.terminal.focusPrevious"
},
VS 代码扩展
因为我们在笔记本上也使用了.py文件,所以所有常规的 python 扩展仍然可以在我们的笔记本开发过程中使用。下面列出了在这个阶段使用 VsCode 进行开发时要注意的一些扩展。
- 路径智能感知 帮助自动完成路径。当你想读入一个 csv 文件来启动你的 jupyter 笔记本时,这是非常有用的。

csv 文件的智能感知自动完成路径。作者图片
- Sourcery 是一个漂亮的工具,它为重写代码提供建议,并为函数提供可读性度量。


1。 Sourcery 重写代码以遵循 python 最佳实践。 2。源代码库显示了代码重写前后的可读性指标。(图片由作者提供)
- Github copilot,一个聪明的 AI 插件,自动完成你的代码和功能。




1。 Copilot 正在与第三方库进行模式匹配。 2。使用来自函数和参数命名的上下文的函数自动完成 3。使用当前范围前后的上下文自动完成。 4。使用类型提示进行自动完成。(图片由作者提供)
如果您想在工作流程中有效地使用 GitHub copilot,请查看我的文章,
如果你喜欢本文中 GIF 格式的代码,可以看看我写的关于如何自己设置它的文章,
https://python.plainenglish.io/make-your-python-code-look-pretty-c65dc2ff2bfa
第二部分
请关注这篇文章第二部分的更新。
如果你喜欢这篇文章,并且想要更多这样的内容,请留下掌声,如果你有任何问题/建议来改进这个指南,请留下评论。
第 1 部分——构建深度 Q 网络来玩 grid world——deep mind 的深度 Q 网络
在本文中,让我们构建一个类似于 DeepMind 的 Atari 代理的深度 Q 网络来玩 Gridworld 问题。我们将从零开始构建与 DeepMind 几乎相同的系统,以详细了解深度 Q 网络。我们将了解香草深 Q 网络的缺点,并想出聪明的方法来克服它们。
在我们之前的文章中,我们已经看到了如何构建一个简单的深度强化学习代理来帮助我们增加数字营销活动的收入。
2013 年,当 DeepMind 建立了一个代理来玩 Atari 游戏并能够击败当时的世界冠军时,深度强化学习被掀起。DeepMind 发表了一篇题为“用深度强化学习玩雅达利”的论文,概述了他们对一种旧算法的新方法,这种方法使他们有足够的性能以创纪录的水平玩七场雅达利 2600 游戏中的六场。他们使用的算法只分析了游戏中与人类相似的像素数据。他们使用的旧算法叫做 Q-learning。DeepMind 对旧算法进行了重大修改,以解决强化学习算法当时遇到的一些问题。
在这篇文章中,我们将理解和建立一个香草深度 Q 学习模型,并讨论它的缺点。稍后我们还将看到 DeepMind 用来克服 vanilla 算法局限性的技术。
什么是 Q-learning?
在我们上一篇文章中,当我们建立一个神经网络来优化广告投放问题时,我们实现了一种 Q 学习算法。Q-learning 的主要思想是,你的算法预测一个状态-动作对的值,然后你将这个预测与稍后某个时间观察到的累积奖励进行比较,并更新你的算法的参数,这样下次它就会做出更好的预测。Q-learning 更新规则如下所述

图 1 : Q 学习更新规则
上面的公式告诉我们,在时间 t 更新的 Q 值是当前的 Q 值和未来的预期 Q 值之和,假设我们从当前状态最优地玩。贴现因子(从 0 到 0.99)告诉我们未来的回报对我们采取当前行动有多重要
什么是 GridWorld?
图 2 中描述的 Gridworld 游戏展示了 Gridworld 的简单版本。代理(A)必须沿最短路径导航到目标方块(+),并避免掉入坑中(–)

图 2: GridWorld 游戏
GridWorld 的状态是一个张量,表示网格上所有对象的位置。
我们的目标是训练一个神经网络从头开始玩 Gridworld。代理将有权访问该板的外观。有四种可能的动作,即向上、向下、向左和向右。基于代理采取的行动,将会收到奖励。每次移动获得-1 的奖励。任何导致掉落坑内或墙壁的行为将获得-10 的奖励。成功达成目标可获得+10 的正奖励。
下面是代理在玩 GridWorld 时采取的一系列步骤
- 游戏的状态
我们在某个状态开始博弈,我们称之为 S( t) 。这个状态包含了我们所拥有的关于这个游戏的所有信息。对于我们的 Gridworld 示例,游戏状态表示为一个 4 × 4 × 4 张量。如下图 3 所示,它是 4 X 4 网格的 4 个部分

图 3 : 4X4X4 状态表示
每个网格片代表棋盘上单个对象的位置,包含一个 1,所有其他元素为 0。1 的位置表示该切片对象的位置。由于我们的神经网络代理将使用状态作为输入向量,因此我们的输入层将有 64 个神经元
2.获得期望值预测
在传统的 Q-学习算法中,状态 S( t )和坦诚动作被馈送到网络,并且它预测单个值的期望值。在我们的例子中,我们有 4 个可能的行动,网络应该预测期望值 4 次,每个行动作为一个输入。这只会增加网络的开销和处理时间。因此,DeepMind 有了一个想法,让网络预测所有行为的概率分布。随着这种变化,网络只需进行一次预测。但是这次输出向量将包含所有可能动作的概率分布。在我们的例子中,网络的输出是一个长度为 4 的向量。对应于最大概率的动作在任何特定状态下都可以被认为是最佳的。下图解释了这一变化

图 4:最初的 Q 函数接受一个状态-动作对,并返回该状态-动作对的值——一个单一的数字。DeepMind 使用了一个修改的向量值 Q 函数,该函数接受一个状态并返回一个状态-动作值向量,对于给定的输入状态,每个可能的动作都有一个向量值。向量值 Q 函数更有效,因为您只需对所有动作计算一次该函数。
3.选择最佳行动
一旦我们从网络中得到每个状态的概率分布的预测,我们需要选择一个要采取的行动。为此我们可以使用ε贪婪策略。对于ε-贪婪策略,我们选择ε的值,对于该概率,我们选择忽略预测概率的随机动作(探索)。对于概率 1-ε,我们将选择与最高预测 Q 值相关联的动作。

图 5:在ε-贪婪动作选择方法中,我们将ε参数设置为某个值,例如 0.1,并且利用该概率,我们将随机选择动作(完全忽略预测的 Q 值),或者利用概率 1-ε= 0.9,我们将选择与最高预测的 Q 值相关联的动作。另一个有用的技术是从高ε值开始,比如 1,然后在训练迭代中慢慢递减。
一旦行动被选择为 A(t),我们将在一个新的状态 S(t+1)中结束,观察到的回报为 R(t+1)。我们在 S(t)得到的期望值的预测就是预测的 Q(S(t),A(t))。我们现在想更新我们的网络,让它知道采取了建议的行动后收到的实际奖励
4.在(t+1)获得奖励
现在,我们在 s(t+1)运行网络,得到我们称为 Q(S(t+1),a)的期望值,并计算出我们需要采取的行动。明确地说,这是一个单一的值,它反映了给定我们的新状态和所有可能的动作的最高预测值 Q 。
5.更新网络
我们将使用一些损失函数(如均方误差)执行一次迭代训练,以最小化网络预测值与目标预测值之间的差异

对于第一次阅读的读者来说,上面的步骤可能会令人困惑(甚至当我第一次浏览这些概念时也感到困惑)。让我们开始编码,我相信混乱会被清除
我们来编码吧!!!
创建 GridWorld 游戏
您将在这个 GIT 存储库中找到两个名为 GridWorld.py 和 GridBoard.py 的文件。请下载它们并将它们保存在构建 RL 代理的同一个文件夹中。这些包含一些运行游戏实例的类。您可以通过下面的代码创建一个游戏实例
from Gridworld import Gridworld
game = Gridworld(size=4, mode='static')
有三种方法可以初始化电路板。第一种是静态初始化它,如上所示,这样棋盘上的对象在相同的预定位置被初始化。第二,你可以设置 mode='player ',这样只有玩家在棋盘上的随机位置被初始化。最后,你可以使用 mode='random '初始化它,这样所有的对象都是随机放置的(这对于算法来说更难学习)。我们最终会使用所有三个选项。在这个 GIT 链接中可以找到更多关于移动和检查奖励的操作
建立一个神经网络作为 Q 函数
让我们建立一个神经网络作为 Q 函数。我们已经讨论了神经网络的输入和输出向量大小。我们的输入将是 4X4X4 向量(状态向量),输出将是 4 元素向量,这是所有可能动作(上、下、左、右)的概率分布。

图 6:具有用作 Q 函数的两个隐藏层的神经网络
Pytorch 中神经网络的代码如下所示
import numpy as np
import torch
from Gridworld import Gridworld
import random
from matplotlib import pylab as plt
l1 = 64
l2 = 150
l3 = 100
l4 = 4
model = torch.nn.Sequential(
torch.nn.Linear(l1, l2),
torch.nn.ReLU(),
torch.nn.Linear(l2, l3),
torch.nn.ReLU(),
torch.nn.Linear(l3,l4)
)
loss_fn = torch.nn.MSELoss()
learning_rate = 1e-3
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
gamma = 0.9
epsilon = 1.0
一旦神经网络准备就绪,我们需要创建一个训练循环来训练我们的 RL 代理,并使它按照图 1 中给出的 Q 学习公式进行学习。为此,我们遵循以下步骤
- 为循环创建一个来表示周期数
- 初始化 GridWorld 的状态
- 创建一个 while 循环来监控游戏的情节
- 向前运行 Q 网络以获得当前状态的评估 Q 值
- 用贪婪的ε方法来选择行动
- 采取上一步中决定的行动,以达到一个新的状态 s ’,回报为 r(t+1)
- 在状态 s '运行 Q 网络向前,并收集最高的 Q 值,我们称之为 maxQ
- 我们训练网络的目标值是r(t+1)+γ** maxQA(S(t+1)),其中γ(γ)是一个介于 0 和 1 之间的参数。如果在采取动作后游戏结束,没有合法的 s(t +1),那么γ** maxQA(S(t+1))无效,我们可以将其设置为 0。目标正好变成 r(t +1)。
- 假设我们有四个输出,我们只想更新(即训练)与我们刚刚采取的行动相关联的输出,我们的目标输出向量与第一次运行的输出向量相同,只是我们将与我们的行动相关联的一个输出更改为我们使用 Q-learning 公式计算的结果。
- 在这一步训练模型,并重复步骤 2 到 9
上述步骤的代码如下所述
epochs = 1000
losses = [] 1
for i in range(epochs): 2
game = Gridworld(size=4, mode='static') 3
state_ = game.board.render_np().reshape(1,64) \
+ np.random.rand(1,64)/10.0 4
state1 = torch.from_numpy(state_).float() 5
status = 1 6
while(status == 1): 7
qval = model(state1) 8
qval_ = qval.data.numpy()
if (random.random() < epsilon): 9
action_ = np.random.randint(0,4)
else:
action_ = np.argmax(qval_)
action = action_set[action_] 10
game.makeMove(action) 11
state2_ = game.board.render_np().reshape(1,64) +
np.random.rand(1,64)/10.0
state2 = torch.from_numpy(state2_).float() 12
reward = game.reward()
with torch.no_grad():
newQ = model(state2.reshape(1,64))
maxQ = torch.max(newQ) 13
if reward == -1: 14
Y = reward + (gamma * maxQ)
else:
Y = reward
Y = torch.Tensor([Y]).detach()
X = qval.squeeze()[action_] 15
loss = loss_fn(X, Y)
optimizer.zero_grad()
loss.backward()
losses.append(loss.item())
optimizer.step()
state1 = state2
if reward != -1: 16
status = 0
if epsilon > 0.1: 17
epsilon -= (1/epochs)
1 创建一个列表来存储损失值,这样我们可以在以后绘制趋势图
2 主训练循环
3 对于每个时期,我们开始一个新游戏。
4 在我们创建游戏后,我们提取状态信息,并添加少量噪声。这样做是为了避免 ReLU 激活时发生的“死神经元”
5 将 numpy 数组转换为 PyTorch 张量,然后转换为 PyTorch 变量
6 使用状态变量来跟踪游戏是否仍在进行中
7 当游戏仍在进行时, 玩到结束,然后开始一个新的时期
8 向前运行 Q 网络以获得所有动作的预测 Q 值
9 使用ε-贪婪方法选择一个动作
10 将数字动作翻译成我们的 Gridworld 游戏期望的动作角色之一
11 在使用ε-贪婪方法选择一个动作之后,在移动之后采取动作
12, 获得游戏的新状态
13 找到根据新状态预测的最大 Q 值
14 计算目标 Q 值
15 创建 qval 数组的副本,然后更新与所采取的动作相对应的一个元素
16 如果奖励为–1,则游戏尚未赢或输,并且仍在进行中
17 在每个时期递减ε值
我们也行动起来。GridWorld 希望动作是字母形式的,比如 u 代表 up 等等。该字典将数字映射到相应的动作,如下所示
action_set = {
0: 'u',
1: 'd',
2: 'l',
3: 'r',
}
对上述网络进行 1000 个纪元的训练会给我们带来惊人的结果。一旦训练完毕,我们可以看到损失如下减少

图 7:静态环境下训练后各时期的损失减少
测试模型的代码如下所示。测试函数与 train 相同,只是我们没有将损失计算反向传播到模型中。我们只是向前运行模型以获得预测,并使用具有最大值的动作。
def test_model(model, mode='static', display=True):
i = 0
test_game = Gridworld(mode=mode)
state_ = test_game.board.render_np().reshape(1,64) + np.random.rand(1,64)/10.0
state = torch.from_numpy(state_).float()
if display:
print("Initial State:")
print(test_game.display())
status = 1
while(status == 1): 1
qval = model(state)
qval_ = qval.data.numpy()
action_ = np.argmax(qval_) 2
action = action_set[action_]
if display:
print('Move #: %s; Taking action: %s' % (i, action))
test_game.makeMove(action)
state_ = test_game.board.render_np().reshape(1,64) + np.random.rand(1,64)/10.0
state = torch.from_numpy(state_).float()
if display:
print(test_game.display())
reward = test_game.reward()
if reward != -1:
if reward > 0:
status = 2
if display:
print("Game won! Reward: %s" % (reward,))
else:
status = 0
if display:
print("Game LOST. Reward: %s" % (reward,))
i += 1
if (i > 15):
if display:
print("Game lost; too many moves.")
break
win = True if status == 2 else False
return win
用静态模型运行上面的测试函数将会得到类似这样的结果
test_model(model, 'static')
Initial State:
[['+' '-' ' ' 'P']
[' ' 'W' ' ' ' ']
[' ' ' ' ' ' ' ']
[' ' ' ' ' ' ' ']]
Move #: 0; Taking action: d
[['+' '-' ' ' ' ']
[' ' 'W' ' ' 'P']
[' ' ' ' ' ' ' ']
[' ' ' ' ' ' ' ']]
Move #: 1; Taking action: d
[['+' '-' ' ' ' ']
[' ' 'W' ' ' ' ']
[' ' ' ' ' ' 'P']
[' ' ' ' ' ' ' ']]
Move #: 2; Taking action: l
[['+' '-' ' ' ' ']
[' ' 'W' ' ' ' ']
[' ' ' ' 'P' ' ']
[' ' ' ' ' ' ' ']]
Move #: 3; Taking action: l
[['+' '-' ' ' ' ']
[' ' 'W' ' ' ' ']
[' ' 'P' ' ' ' ']
[' ' ' ' ' ' ' ']]
Move #: 4; Taking action: l
[['+' '-' ' ' ' ']
[' ' 'W' ' ' ' ']
['P' ' ' ' ' ' ']
[' ' ' ' ' ' ' ']]
Move #: 5; Taking action: u
[['+' '-' ' ' ' ']
['P' 'W' ' ' ' ']
[' ' ' ' ' ' ' ']
[' ' ' ' ' ' ' ']]
Move #: 6; Taking action: u
[['+' '-' ' ' ' ']
[' ' 'W' ' ' ' ']
[' ' ' ' ' ' ' ']
[' ' ' ' ' ' ' ']]
Reward: 10
我们可以看到,在静态下,该模型表现非常好。但是在随机状态下性能不是很好
>>> testModel(model, 'random')
Initial State:
[[' ' '+' ' ' 'P']
[' ' 'W' ' ' ' ']
[' ' ' ' ' ' ' ']
[' ' ' ' '-' ' ']]
Move #: 0; Taking action: d
[[' ' '+' ' ' ' ']
[' ' 'W' ' ' 'P']
[' ' ' ' ' ' ' ']
[' ' ' ' '-' ' ']]
Move #: 1; Taking action: d
[[' ' '+' ' ' ' ']
[' ' 'W' ' ' ' ']
[' ' ' ' ' ' 'P']
[' ' ' ' '-' ' ']]
Move #: 2; Taking action: l
[[' ' '+' ' ' ' ']
[' ' 'W' ' ' ' ']
[' ' ' ' 'P' ' ']
[' ' ' ' '-' ' ']]
Move #: 3; Taking action: l
[[' ' '+' ' ' ' ']
[' ' 'W' ' ' ' ']
[' ' 'P' ' ' ' ']
[' ' ' ' '-' ' ']]
Move #: 4; Taking action: l
[[' ' '+' ' ' ' ']
[' ' 'W' ' ' ' ']
['P' ' ' ' ' ' ']
[' ' ' ' '-' ' ']]
Move #: 5; Taking action: u
[[' ' '+' ' ' ' ']
['P' 'W' ' ' ' ']
[' ' ' ' ' ' ' ']
[' ' ' ' '-' ' ']]
Move #: 6; Taking action: u
[['P' '+' ' ' ' ']
[' ' 'W' ' ' ' ']
[' ' ' ' ' ' ' ']
[' ' ' ' '-' ' ']]
Move #: 7; Taking action: d
[[' ' '+' ' ' ' ']
['P' 'W' ' ' ' ']
[' ' ' ' ' ' ' ']
[' ' ' ' '-' ' ']]
# we omitted the last several moves to save space
Game lost; too many moves.
我们看到,在随机模式下,代理人无法赢得游戏。在随机模式下训练模型也不太成功,导致损失图如下

图 8:在随机环境模式下训练模型的损失图
哪里出了问题?
我们能够在一个静态环境中训练我们的模型,每次模型在相同的位置看到物体、玩家和目标。但是当代理接受更复杂的初始化训练时,每次创建新的一集时,环境都会被随机初始化,它无法学习。这是 DeepMind 也面临的问题,并称之为“灾难性遗忘”。他们想出了一个非常简单但非常棒的方法来克服这个问题,这个方法被称为“经验重演”。
让我们在本文的下一部分这里详细了解问题和解决方案。
从 GIT 链接获取本文使用的完整代码
到现在!!
- 我们从理解什么是 Q 学习和用于更新 Q 学习的公式开始这篇文章
- 后来我们看到了 GridWorld 游戏,并定义了它的状态、动作和奖励。
- 然后我们想出了一个强化学习的方法来赢得比赛
- 我们学习了如何导入 GridWorld 环境和环境的各种模式
- 设计并建立了一个神经网络作为 Q 函数。
- 我们对 RL 代理进行了训练和测试,在解决静态网格世界问题上取得了很好的效果。但我们未能解决随机网格世界。
- 我们知道问题是什么,并承诺在我们的下一篇文章中解决它
点击这里查看文章的第二部分:
BERT 需要干净的数据吗?第 1 部分:数据清理。

一场可能会在推特上传播的灾难。照片由 sippakorn yamkasikorn 在 Unsplash 拍摄。
以 84%的准确率检测灾难,并测试 NLP 方法,以找到为 BERT 清理数据的最佳方式。
如果我把碧昂斯的 Met Gala 礼服描述为“飓风”,那么飓风碧昂斯会成为一件东西吗?
对清理数据不感兴趣,只想了解不同级别的数据清理,以及这对 BERT 有何影响?跳到 第二部分 。
介绍
在本文中,我们使用 Kaggle 上的灾难推文竞赛数据集来学习典型的数据科学&机器学习实践,更具体地说是关于自然语言处理(NLP)。通过应用不同的文本清理方法,然后运行变压器的双向编码器表示(BERT)来预测常规推文中的灾难,我们可以比较模型,并查看文本清理对准确性的影响。最终结果是竞争中的前 50 名提交率约为 84%。
我希望你能通过阅读这篇文章了解以下内容:
- 通过执行以下操作探索典型的数据科学和机器学习实践:为机器学习导入、探索、清理和准备数据。
- 应用特定的 NLP 数据准备技术,例如通过创建元特征的特征工程和使用符号化的文本清理。
- 应用自然语言处理的最新语言模型 BERT,并找出 BERT 模型的最佳输入。
目标
这个项目的主要目标是区分指示世界灾难的推文和那些包含灾难词汇但关于灾难以外的其他事情的推文。这样做,我们可以理解输入文本影响 BERT 的方式;具体来说,如果文本或多或少被清理会有所不同!
这是一个很难解决的问题,因为很多“灾难词汇”经常被用来描述日常生活。例如,有人可能将鞋子描述为“火”,这可能会混淆我们的模型,导致我们没有注意到世界各地正在发生的实际火灾!

所以,事不宜迟,让我们开始研究解决这个令人兴奋的问题的方法吧!
方法
总而言之,这个项目被分成四个笔记本。第一本包含必要的数据准备,随后的笔记本(2,3,& 4)都是获得我们预测的不同方法。
笔记本 1(资料准备):
- 导入库
- 导入数据
- 数据探索
- 数据准备
- 计算元特征
笔记本 2(元特写 CNN):
- 导入准备好的数据
- 标准化
- 卷积神经网络
- 模型评估和提交
笔记本 3(大扫除伯特):
- 导入准备好的数据
- 带有正则表达式的大量干净文本
- 词汇化
- 标记化
- 伯特建模
- 模型评估和提交
笔记本 4(轻清洁伯特):
- 导入准备好的数据
- 带有正则表达式的简洁文本
- 标记化
- 伯特建模
- 模型评估和提交
注意:要查看该项目的完整代码,请在这里查看 GitHub 库。
数据准备
导入数据
为了导入我们的数据,我们在下载数据并将其放入正确的目录后编写以下内容:
raw_test_data = pd.read_csv("../data/raw/test.csv")
raw_train_data = pd.read_csv("../data/raw/train.csv") # check your output by just running the following:
raw_train_data
数据探索
为了探索我们的数据,我们使用熊猫图谱。这是一个有用的库,可以快速获得我们刚刚导入的数据的大量描述性统计数据。
profile = ProfileReport(raw_train_data, title="Pandas Profiling Report")profile.to_notebook_iframe()
这应该会在你的 Jupyter 笔记本上加载一个很好的小部件。它会给你提供一些很棒的描述性统计数据,比如这个!

熊猫图谱的输出。图片作者。
具体来说,通过使用 Pandas Profiling,我们可以检查数据集的一些基本特征:
- 训练数据集中目标变量的类别分布。这是一个 4342 (0),3271 (1)的分裂。这种接近相等的分离对于训练我们的模型是可以的。
- 缺失数据。我们看到 location 和 keyword 列包含缺失的数据。这将在下面处理。
- 基数。我们的位置价值非常独特。这也在下面讨论和处理。
从这里,我们可以继续我们的数据准备和处理这些问题,我们已经强调了!
数据准备
location和keyword包含空值,如 pandas profiling 报告所示。
我们通过首先更深入地检查空值的数量来处理这个问题。
foo = [(raw_train_data[['keyword', 'location']].isnull().sum().values, raw_test_data[['keyword', 'location']].isnull().sum().values)]out = np.concatenate(foo).ravel()
null_counts = pd.DataFrame({
"column": ['keyword', 'location', 'keyword', 'location'],
"label": ['train', 'train', 'test', 'test'],
"count": out
})sns.catplot(x="column", y="count", data=null_counts, hue="label", kind="bar", height=8.27, aspect=11.7/8.27)
plt.title('Number of Null Values')
plt.show()

上面的代码输出的图形。这告诉我们在训练和测试数据集中跨关键字和位置列的 null 值的数量。图片作者。
Twitter 上的位置是由用户填充的,因此过于随意。唯一值太多,输入没有标准化。我们可以去掉这个特性。
# drop location data
clean_train_data = raw_train_data.drop(columns="location")
clean_test_data = raw_test_data.drop(columns="location")
另一方面,关键字作为一种识别灾难的方式是很有趣的。这是因为有些关键词确实只在特定的上下文中使用。
我们的关键词长什么样?我们可以为我们的训练和测试数据集输出单词云来检验这一点。

用于训练数据的词云。图片作者。

测试数据的词云。图片作者。
我们看到在治疗和控制中的关键词之间有很好的重叠。关键字只是查看数据的一种方式,如果我们只检查关键字,就没有足够的上下文来生成准确的预测。同样,因为我们正在实现一个 BERT 模型,它完全是关于一个单词在句子中的上下文,我们不想做任何事情,比如在相关 Tweet 的末尾添加关键字,以增加该单词的权重。
在我们的模型中利用关键词的一个聪明的方法是将关键词转换成情感分数。这样,我们就有了一个作为元特征的价值,而不会改变推文中包含的重要信息。我们通过使用 NLTK 库的内置的、预先训练好的情感分析器 VADER 来做到这一点。
# drop nan keyword rows in train dataset
clean_train_data = clean_train_data.dropna(subset=['keyword']).reset_index(drop=True)# we fill none into the Nan Values of the test dataset, to give 0 sentiment
clean_test_data['keyword'] = clean_test_data['keyword'].fillna("None")# collect keywords into arrays
train_keywords = clean_train_data['keyword']
test_keywords = clean_test_data['keyword']# use sentiment analyser
sia = SentimentIntensityAnalyzer()
train_keyword_sia = [sia.polarity_scores(i)['compound'] for i in train_keywords]
test_keyword_sia = [sia.polarity_scores(i)['compound'] for i in test_keywords]# update keyword column
clean_train_data['keyword'] = train_keyword_sia
clean_test_data['keyword'] = test_keyword_sia
最后,我们检查重复数据和相应的重复标签。
# check for duplicates using groupby
df_nondupes = clean_train_data.groupby(['text']).nunique().sort_values(by='target', ascending=False)# find duplicates with target > 1 as a way of flagging if they are duplicate
df_dupes = df_nondupes[df_nondupes['target'] > 1]
df_dupes.rename(columns={'id':'# of duplicates', 'target':'sum of target var'})
我们看到有一些重复。当训练我们的模型时,我们不希望有任何重复,因为它会使我们的输出产生偏差。如果它们也被贴上不同的标签,情况会特别糟糕。从输出中,我们可以看到它们是。这对于具有相同特征但不同标签的模型来说是额外的混淆。

副本表。“重复的数量”和“目标变量的总和”之间的差异表示标记不同的重复。图片作者。
如果我们遍历这些副本,我们可以手动分别标记它们,这样我们就保留了数据。这是必要的,因为它们中的一些被贴错标签并且是重复的。例如,第一行有三个副本,但其中两个的目标标签为 1,另一个的目标标签为 0。如上所述,通过# of duplicates与sum of target var的差异,可以在整个表格中看到这一点。
# take index which is the texts themselves
dupe_text_list = df_dupes.index dupe_text_list = list(dupe_text_list)# manually make label list to iterate
right_labels = [0,0,0,1,0,0,1,0,1,1,1,0,1,1,1,0,0,0]# drop duplicates except for one
clean_train_data = clean_train_data.drop_duplicates(subset=['text'], keep='last').reset_index(drop=True)# relabel duplicate rows
for i in range(len(dupe_text_list)):
clean_train_data.loc[clean_train_data['text'] == dupe_text_list[i], 'target'] = right_labels[i]
计算元特征
既然我们的数据已经清理和准备好了,我们就要开始有趣的事情了!
我们需要更多地了解我们的数据,并将其分离成更多的功能。通过思考我们可以产生的不同变量,这些变量可能有助于我们区分灾难和非灾难,我们可以在更多的特征上训练我们的模型。这将为我们的模型提供更多的可见性。考虑表示灾难的推文的最佳方式是,它们可能来自更高质量的来源,性质更严重。因此,遵循更严格的语法规则,充分报告情况,并分享链接。以下元特征,以及从关键词栏计算出的我们的情感得分,将代表我们正在寻找的推文类型(从这里中精选)。
- 标签数(#)(假设(H):标签由普通用户而不是新闻机构使用)
num_mentions-提及次数(@) (H:普通用户可以使用更多标签,而不是新闻机构)num_words-字数统计(H:推特上适当报道的字数比普通用户推特上的多)num_stop_words-停用词的数量(H:通过新闻机构的适当语法使用更多停用词)num_urls-网址数量(H:报道灾难的新闻机构共享的网址)avg_word_length-单词的平均字符数(H:新闻机构使用的不缩写的较长单词)num_chars-字符数(H:在新闻机构的推文中使用更多字符来报道完整的故事)num_punctuation-标点符号计数(H:在符合正确语法的新闻机构推文中有更多标点符号)
我们使用这些代码来构建这些特性。
### num_hashtags
clean_train_data['num_hashtags'] = clean_train_data['text'].apply(lambda x: len([c for c in str(x) if c == '#'])) clean_test_data['num_hashtags'] = clean_test_data['text'].apply(lambda x: len([c for c in str(x) if c == '#'])) ### num_mentions
clean_train_data['num_mentions'] = clean_train_data['text'].apply(lambda x: len([c for c in str(x) if c == '@'])) clean_test_data['num_mentions'] = clean_test_data['text'].apply(lambda x: len([c for c in str(x) if c == '@'])) ### num_words
clean_train_data['num_words'] = clean_train_data['text'].apply(lambda x: len(str(x).split())) clean_test_data['num_words'] = clean_test_data['text'].apply(lambda x: len(str(x).split())) ### num_stop_words
clean_train_data['num_stop_words'] = clean_train_data['text'].apply(lambda x: len([w for w in str(x).lower().split() if w in STOPWORDS])) clean_test_data['num_stop_words'] = clean_test_data['text'].apply(lambda x: len([w for w in str(x).lower().split() if w in STOPWORDS])) ### num_urls
clean_train_data['num_urls'] = clean_train_data['text'].apply(lambda x: len([w for w in str(x).lower().split() if 'http' in w or 'https' in w])) clean_test_data['num_urls'] = clean_test_data['text'].apply(lambda x: len([w for w in str(x).lower().split() if 'http' in w or 'https' in w])) ### avg_word_length
clean_train_data['avg_word_length'] = clean_train_data['text'].apply(lambda x: np.mean([len(w) for w in str(x).split()])) clean_test_data['avg_word_length'] = clean_test_data['text'].apply(lambda x: np.mean([len(w) for w in str(x).split()])) ### num_chars
clean_train_data['num_chars'] = clean_train_data['text'].apply(lambda x: len(str(x))) clean_test_data['num_chars'] = clean_test_data['text'].apply(lambda x: len(str(x))) ### num_punctuation
clean_train_data['num_punctuation'] = clean_train_data['text'].apply(lambda x: len([c for c in str(x) if c in string.punctuation])) clean_test_data['num_punctuation'] = clean_test_data['text'].apply(lambda x: len([c for c in str(x) if c in string.punctuation]))
请记住,您可以编写 clean_train_data 并运行单元来查看我们当前的熊猫数据框架是什么样子的!
此时,我们已经从数据集中清除了空变量,计算了我们的元特征,并去除了错误标签和重复项。
我们应该保存我们的数据集。我们用泡菜来做这个!
# we save these as pickles
clean_train_data.to_pickle(“../data/pickles/clean_train_data.pkl”)
clean_test_data.to_pickle(“../data/pickles/clean_test_data.pkl”)
我们现在可以执行以下操作:
- 稍微清理一下文本数据,不要删除停用词或 Tweets 的其他上下文片段,然后运行 BERT。
- 彻底清理文本数据,删除停用词和其他可能混淆模型的功能,然后运行 BERT。
- 从文本数据中分离出元特征,并尝试运行 CNN。
从那里,我们可以适当地比较我们的模型的准确性。因为 BERT 是一种语言模型,它从两个方向利用句子的结构将每个输出元素连接到每个输入元素,并根据这种连接动态调整权重(这一过程称为注意力),所以我的假设是较轻的预处理会做得更好。这是因为停用词和句子的其他语法特征可能有助于模型的注意力。换句话说,伯特避免给单词赋予独立于上下文的固定含义。相反,单词是由它们周围的单词定义的。
后续步骤
我们在哪里生成预测并了解更多关于 BERT、机器学习以及我们的数据清理过程如何影响结果的信息,请参见媒体上的第 2 部分或我个人网站上的第 2 部分。
如果你喜欢这篇文章,请在推特上关注我!我每天都在建造。
感谢阅读!
文献学
A.Pai,什么是标记化?,(2020),分析维迪亚
G.Evitan, NLP 与灾难推文:EDA,清洁和 BERT ,(2019),Kaggle 笔记本
G.Giacaglia,变形金刚如何工作,(2019),走向数据科学
I A. Khalid,用 Python 清理文本数据,(2020),走向数据科学
J.Devlin 和 M-W. Chang,谷歌人工智能博客:开源 BERT ,(2018),谷歌博客
J.朱,班底模型对比,(未注明),斯坦福大学
Kaggle 团队,自然语言处理与灾难推文 (2021),Kaggle 竞赛
页(page 的缩写)普拉卡什,伯特·托肯泽的解释性指南,(2021),分析维迪亚
南蒂勒,使用预训练手套向量的基础知识,(2019),分析 Vidhya
维基百科, F 分数,(未注明),维基百科
第二部分——构建深度 Q 网络玩 grid world——灾难性遗忘和经验重演
在这篇文章中,我们来讨论一下普通 Q 学习模型中的问题:灾难性遗忘。我们将使用经验回放来解决这个问题,看看我们在玩 GridWorld 时所做的改进
欢迎来到深度 Q-网络教程的第二部分。这是第一部的延续。如果你还没有阅读第 1 部分,我强烈建议你通读一遍,因为这篇文章中的许多代码和解释将直接关系到那些已经在第 1 部分中解释过的。
至今 第一部 !!
- 我们从理解什么是 Q 学习和用于更新 Q 学习的公式开始
- 后来我们看到了 GridWorld 游戏,并定义了它的状态、动作和奖励。
- 然后我们想出了一个强化学习的方法来赢得比赛
- 我们学习了如何导入 GridWorld 环境和环境的各种模式
- 设计并建立了一个神经网络作为 Q 函数。
- 我们对 RL 代理进行了训练和测试,在解决静态网格世界问题上取得了很好的效果。但我们未能解决随机网格世界。
- 我们了解问题所在,并承诺在本文中解决问题
什么问题??
我们能够在静态环境中训练我们的模型,每次模型在相同的位置看到物体、玩家和目标。但是当代理接受更复杂的初始化训练时,每次创建新的一集时,环境都会被随机初始化,它无法学习。
上述问题的名称是灾难性遗忘。这是一个与基于梯度下降的训练相关的重要问题,当我们在游戏中的每个动作后反向移动时,我们称之为在线训练。
灾难性遗忘的想法是,当两个游戏状态非常相似,但却导致非常不同的结果时,Q 函数将变得“混乱”,无法知道该做什么。在下面的例子中,灾难性的遗忘发生了,因为 Q 函数从游戏 1 中得知向右移动会得到+1 的奖励,但是在游戏 2 中,看起来非常相似,向右移动后它会得到-1 的奖励。结果,该算法忘记了它先前学习的关于游戏 1 的内容,导致基本上根本没有显著的学习。

图 1:紧密相关的状态导致灾难性遗忘
灾难性遗忘的原因是我们在游戏的每一步棋后都在更新权重。在监督学习领域,我们通常没有这个问题,因为我们进行随机批量学习,直到我们迭代了训练数据的一些随机子集并计算了批量的总和或平均梯度,我们才更新我们的权重。这在目标上是平均的,并且稳定了学习。
我们能在 DQN 做吗?
是的,这就是所谓的体验回放。体验回放让我们在在线学习模式中批量更新。

图 2:体验回放
上图显示了经验重放的总体概况,这是一种缓解在线训练算法主要问题的方法:灾难性遗忘。其思想是通过存储过去的经验,然后使用这些经验的随机子集来更新 Q 网络,而不是仅使用单个最近的经验,来采用小型批处理。
体验回放涉及的步骤有
- 在状态 s ,采取动作 a ,观察新状态 s(t +1),奖励 r(t +1)。
- 将此作为一个元组( s , a , s(t +1), r(t +1))存储在一个列表中。
- 继续在这个列表中存储每个经历,直到你把列表填充到一个特定的长度(这个由你来定义)。
- 一旦体验回放内存被填满,随机选择一个子集(同样,您需要定义子集大小)。
- 遍历这个子集并计算每个子集的值更新;将这些存储在一个目标数组中(如 Y ,并将每个内存的状态 s 存储在 X 中。
- 使用 X 和 Y 作为小批量进行批量训练。对于数组已满的后续时期,只需覆盖经验重放内存数组中的旧值。
体验回放的实现如下图所示
from collections import deque
epochs = 5000
losses = []
mem_size = 1000 ***1***
batch_size = 200 ***2***
replay = deque(maxlen=mem_size) ***3***
max_moves = 50 ***4***
h = 0
for i in range(epochs):
game = Gridworld(size=4, mode='random')
state1_ = game.board.render_np().reshape(1,64) + np.random.rand(1,64)/100.0
state1 = torch.from_numpy(state1_).float()
status = 1
mov = 0
while(status == 1):
mov += 1
qval = model(state1) ***5***
qval_ = qval.data.numpy()
if (random.random() < epsilon): ***6***
action_ = np.random.randint(0,4)
else:
action_ = np.argmax(qval_)
action = action_set[action_]
game.makeMove(action)
state2_ = game.board.render_np().reshape(1,64) + np.random.rand(1,64)/100.0
state2 = torch.from_numpy(state2_).float()
reward = game.reward()
done = True if reward > 0 else False
exp = (state1, action_, reward, state2, done) ***7***
replay.append(exp) ***8***
state1 = state2
if len(replay) > batch_size: ***9***
minibatch = random.sample(replay, batch_size) ***10***
state1_batch = torch.cat([s1 for (s1,a,r,s2,d) in minibatch]) ***11***
action_batch = torch.Tensor([a for (s1,a,r,s2,d) in minibatch])
reward_batch = torch.Tensor([r for (s1,a,r,s2,d) in minibatch])
state2_batch = torch.cat([s2 for (s1,a,r,s2,d) in minibatch])
done_batch = torch.Tensor([d for (s1,a,r,s2,d) in minibatch])
Q1 = model(state1_batch) ***12***
with torch.no_grad():
Q2 = model(state2_batch) ***13***
Y = reward_batch + gamma * ((1 - done_batch) * torch.max(Q2,dim=1)[0]) ***14***
X = \
Q1.gather(dim=1,index=action_batch.long().unsqueeze(dim=1)).squeeze()
loss = loss_fn(X, Y.detach())
optimizer.zero_grad()
loss.backward()
losses.append(loss.item())
optimizer.step()
if reward != -1 or mov > max_moves: ***15***
status = 0
mov = 0
losses = np.array(losses)
- 1 设置体验重放存储器的总容量
- 2 设置小批量
- 3 将内存重放创建为一个队列列表
- 4 设定游戏结束前的最大移动次数
- 5 使用ε贪婪策略选择一个动作
- 6 根据输入状态计算 Q 值,以选择一个动作
- 7 以元组的形式创建状态、奖励、动作和下一个状态的体验
- 8 将体验添加到体验回放列表中
- 9 如果重放列表至少与小批量一样长,则开始小批量训练
- 10 随机抽取重放列表的一个子集
- 11 将每个体验的组成部分分离成单独的小批量张量
- 12 重新计算小批量状态的 Q 值以获得梯度
- 13 计算小批量下一状态的 Q 值,但不计算梯度
- 14 计算我们希望 DQN 学习的目标 Q 值
- 15 如果游戏结束,重置状态和 mov 号
Y = reward_batch+gamma (1—done_batch) torch . max(Q2,dim=1)[0])中的 done _ batch 是一个布尔变量,它在游戏结束(一集结束)时将 reward _ batch 的权限设置为零
在随机模式下训练模型 5000 个纪元并运行游戏 1000 次后,我们能够赢得 90%的游戏,损失锁定如下

图 3:带有经验回复的损失图
从上面的数字我们可以看到,在训练中损失明显减少,而且我们有 90%的机会赢得比赛。我们必须明白,在游戏的某些状态下,赢是不可能的。所以 90%的胜率确实不错。
但是,我们仍然看到,在以后的时代,损失增加,趋势是非常不稳定的。这在深度强化学习(DRL)问题中很常见。我们称之为学习不稳定性。是的,有一个解决方案。
使用目标网络将解决学习不稳定的问题。我们将在第 3 部分中看到如何实现目标网络
这篇文章的代码可以在这个 GIT 链接中获得
至今!!
- 我们学习了什么是灾难性遗忘,以及它如何影响 DQN 特工
- 我们通过实施经验回复解决了灾难性遗忘
- DRL 学习不稳定。我们将在第三部分中看到如何实现目标网络来消除学习不稳定性
点击这里查看本文的第 1 部分:
篮球分析,第 2 部分:投篮质量
是的,3 比长 2 好,但这不是全部。

来自维基共享
所以我们已经到了你期待已久的时刻。在这篇文章中,我将解释为什么没有球员应该投中距离跳投。
我当然不会真的那么做。体育运动中很少有一成不变的事情。但是对于那些怀疑分析的人来说,我们将要讨论一个重要的争论点。到此结束时,对于为什么中距离投篮如此失宠,你应该有一些很好的答案了。但是我们的分析也将提出至少和它回答的一样多的问题。
数据怎么说?
让我们用最简单的方式来看这个问题。如果我们从 2020-2021 年 NBA 常规赛中取出所有投篮命中率,这些来自不同位置的投篮如何相互叠加?

突出显示使用 Tableau |作者图像创建的表格
在优化拍摄位置方面,有一个明显的赢家。离篮筐很近的投篮很好。但令人惊讶的是,当我们离开篮子时,效率下降的速度有多快。中距离跳投和篮下投篮(本质上是上篮和扣篮)的效率有着巨大的差异。
虽然三分球比中距离跳投稍微难一点(他们的 FG%更低),但他们的效率更高。这也不是一个小差异。三分球平均每投 0.16 分(PPS)比平均中距离跳投更有效率。大多数 NBA 球队的节奏大约是每 48 分钟 100 次控球。所以在一场比赛中,我们期望一支只投中 3 分的球队比一支只投中 16 分的球队得分高。这是一个不容忽视的巨大差异。
有什么例外吗?
下一步我们可能要检验这个问题,看看是否有一些玩家逆势而为。在统计学中,我们喜欢把事物聚合在一起,然后取平均值。但是我们可以受益于更加精确。毕竟,有一些球员,我们认为是中端专家。所以让我们看看这些球员在中距离比在 3 距离时是否更有效率。

作者用 Tableau | Image 创建的饼图
我想看看在 2021 赛季,有多少球员在中距离投篮方面比三分球更有效率。我的样本只包括了至少有 100 个总进球数和 20 次中距离投篮和 3 分投篮的球员。在这 274 名球员中,只有 21 人的中距离投篮效率更高。这还不到所有玩家的 8%。其他球员在三分球范围内比在中距离范围内更有效率。是的,这包括像克里斯·保罗和科怀·伦纳德这样的优秀中距离射手。

作者用 Tableau | Image 创建的文本表格
因此,尽管对我们来说区别对待每个玩家很重要,但忽视仅基于少数玩家的压倒性证据并不是一个好主意。
这一切意味着什么
所有这些分析的最终目标是为决策提供信息。那么我们能从这些数据中得到什么呢?显而易见的答案是减少中距离跳投,增加上篮和三分球。但事情没那么简单。在一场篮球比赛中有两个队,另一个队想阻止你执行你的比赛。这意味着给定剧目的“最佳镜头”可能会根据上下文而变化。对方球队的防守方案是什么?谁在球场上?比赛还剩多少时间?比分是多少?这些都是玩家在决定拍摄哪些镜头或不拍摄时的相关问题。
看待镜头选择问题的一种方式是认识到效率并不存在于真空中。乔·哈里斯以场均 6.4 次出手的 47%的三分球率领先联盟。所以有人可能会说:“网队应该让他每次在球场上投三分球。”虽然这很有意思,但每个篮球迷都能直观地理解为什么这不会成功。乔·哈里斯如此高效的原因之一是他倾向于投好球,而不去管那些糟糕的球。除非球队在整体策略上做出一些重大改变,否则乔·哈里斯大幅增加投篮次数的唯一方法就是投更多糟糕的球。这反过来会降低他的整体效率。
所有这些都是为了说明我们从这个简单的分析中只能得到这么多。我们不能确切地告诉 NBA 的进攻应该如何运行,但我们可以根据我们看到的数据提出一些一般性的建议。
不要投长时间有争议的两分球
如果你已经读到这里,这一个应该是有意义的。长二型是最差的投篮类型,最差的长二型是面对防守者的投篮。除了深夜和比赛后期的情况,这些镜头最好保持原样(除非你的名字是凯文·杜兰特,在这种情况下,每个镜头都是开放的)。
外线球员应该切断长传,投 2 分球
三分球革命的很大一部分是强调间隔的概念。球队希望把优秀的射手放在场上,当他们无球时,让他们站在三分线外,而不是在三分线内。为什么?第一个原因基本上是我们整个分析的内容。我们宁愿争取三分,而不是两分。
但是还有一个原因。一般来说,你希望玩家尽可能分散。如果球员都挤在三分线内,那就更容易让对方防守。这使得持球者和低位球员更难获得空位投篮。但这也让射手们更难获得空位和保持空位。在 18 英尺的跳跃者身上比在 22 英尺的跳跃者身上要容易得多。
教练应该强调导致 3 分和上篮的方案
这个很明显。让你打好球的战术。
以下是我们根据这些数据无法得出的结论:
- 球员永远不应该投篮。
- 球员不应该练习投篮。
- 投篮不好的应该多投三分球。
- 即使在寒冷的时候,球队也应该继续从深处投篮。
其中一些问题可能值得研究,但它们不是我们已经完成的分析的结果。
三分球对改变 NBA 比赛起了很大作用。他们也帮助进攻提高了很多。很明显,3 和效率之间没有完美的关系。然而,三分尝试的急剧增加与 NBA 球队进攻效率的急剧增加相一致。

使用 Tableau |作者提供的图像创建的折线图
玩家跟踪数据无疑会给团队更多更好的工具来更深入地研究这个话题。此外,Thinking Basketball 最近推出了一个很棒的视频,讨论了某些明星如何以及为什么仍然可以充分利用中距离投篮。
如果你想看看用于这种分析的 Python 代码,可以看看这个 Jupyter 笔记本。所有数据来自 Basketball-Reference.com。
感谢阅读!
第 3 部分——构建深度 Q 网络来玩 grid world——学习不稳定性和目标网络
在本文中,让我们了解什么是学习不稳定性,这是深度强化学习代理的一个常见问题。我们将通过实现目标网络来解决这个问题
欢迎来到深度 Q-网络教程的第三部分。这是第一部分和第二部分的延续。如果您还没有阅读这些,我强烈建议您阅读它们,因为本文中的许多代码和解释将与其中已经解释的直接相关。
到目前为止的第一部分!!
- 我们从理解什么是 Q 学习和用于更新 Q 学习的公式开始
- 后来我们看到了 GridWorld 游戏,并定义了它的状态、动作和奖励。
- 然后我们想出了一个强化学习的方法来赢得比赛
- 我们学习了如何导入 GridWorld 环境和环境的各种模式
- 设计并建立了一个神经网络作为 Q 函数。
- 我们对 RL 代理进行了训练和测试,在解决静态网格世界问题上取得了很好的效果。但我们未能解决随机网格世界。
第二部!!
- 我们学习了什么是灾难性遗忘,以及它如何影响 DQN 特工
- 我们通过实施经验回复解决了灾难性遗忘
- 我们看到 DRL 学习不稳定。
在这篇文章中,我们将学习如何实现目标网络,以摆脱学习不稳定性
什么是学习不稳定??
当 Q-network 的参数在每次移动后都被更新时,网络中存在不稳定的机会,因为奖励非常少(只有在赢或输时才会给出显著的奖励)。由于每一步都没有显著的回报,算法开始变得不稳定。
例如,在任何状态下,移动“向上”将赢得游戏,因此获得+10 作为奖励。我们的算法认为动作“向上”对于当前状态是好的,并更新其参数以预测该动作的高 Q 值。但是在下一场比赛中,网络预测高 Q 值为“向上”,这可能导致获得-10 奖励。现在我们的算法认为这个动作是坏的,并更新它的参数。然后一些游戏后来上升可以导致获胜。这将导致混乱,并且预测的 Q 值将永远不会满足于合理的稳定值。这与我们在上一篇文章中讨论的灾难性遗忘非常相似。
设备一个重复的 Q-网络称为目标网络!!
DeepMind 设计的解决方案是将 q 网络复制成两份,每份都有自己的模型参数:“常规”q 网络和一份名为目标网络(象征性地表示为 Q^-network,读作“q 帽子”)。在开始时,在任何训练之前,目标网络与 Q 网络是相同的,但是它自己的参数在如何更新方面落后于常规的 Q 网络。

图 1:目标网络的 Q 学习
上图显示了对目标网络的 Q-learning 的一般概述。这是普通 Q 学习算法的一个相当简单的扩展,除了你有第二个 Q 网络叫做目标网络,它的预测 Q 值被用来反向传播和训练主 Q 网络。目标网络的参数不被训练,但是它们周期性地与 Q 网络的参数同步。想法是使用目标网络的 Q 值来训练 Q 网络将提高训练的稳定性。
使用目标网络的步骤如下
- 用参数(权重) θ(Q) 初始化 Q 网络(读作“θQ”)。
- 将目标网络初始化为 Q 网络的副本,但具有单独的参数 θ(T) (读作“θT”),并设置 θ(T) = θ(Q) 。
- 使用ε贪婪方法选择具有 Q 网络的 Q 值的动作 a
- 观察采取行动 a 后状态 s(t+1)的回报 r(t+1)
- 如果剧集刚刚结束(即游戏是赢是输),目标网络的 Q 值将被设置为 r(t +1),否则将被设置为r(t+1)+γmaxQθr(S(t+1))
- 通过 Q 网络反向传播目标网络的 Q 值。这里我们不使用 Q 网络的 Q 值,因为这会导致学习不稳定
- 每 C 次迭代,用 Q-网络权重设置目标网络权重
让我们看看使用 PyTorch 实现目标网络
import copy
model = torch.nn.Sequential(
torch.nn.Linear(l1, l2),
torch.nn.ReLU(),
torch.nn.Linear(l2, l3),
torch.nn.ReLU(),
torch.nn.Linear(l3,l4)
)
model2 = model2 = copy.deepcopy(model) ***1***
model2.load_state_dict(model.state_dict()) ***2***
sync_freq = 50 ***3***
loss_fn = torch.nn.MSELoss()
learning_rate = 1e-3
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
- 1 通过制作原始 Q 网络模型的相同副本来创建第二个模型
- 2 复制原模型的参数
- 3 同步频率参数;每 50 步我们将把模型的参数复制到模型 2 中
让我们现在建立一个 DQN 与经验重放和目标网络
from collections import deque
epochs = 5000
losses = []
mem_size = 1000
batch_size = 200
replay = deque(maxlen=mem_size)
max_moves = 50
h = 0
sync_freq = 500 ***1***
j=0
for i in range(epochs):
game = Gridworld(size=4, mode='random')
state1_ = game.board.render_np().reshape(1,64) + np.random.rand(1,64)/100.0
state1 = torch.from_numpy(state1_).float()
status = 1
mov = 0
while(status == 1):
j+=1
mov += 1
qval = model(state1)
qval_ = qval.data.numpy()
if (random.random() < epsilon):
action_ = np.random.randint(0,4)
else:
action_ = np.argmax(qval_)
action = action_set[action_]
game.makeMove(action)
state2_ = game.board.render_np().reshape(1,64) + np.random.rand(1,64)/100.0
state2 = torch.from_numpy(state2_).float()
reward = game.reward()
done = True if reward > 0 else False
exp = (state1, action_, reward, state2, done)
replay.append(exp)
state1 = state2
if len(replay) > batch_size:
minibatch = random.sample(replay, batch_size)
state1_batch = torch.cat([s1 for (s1,a,r,s2,d) in minibatch])
action_batch = torch.Tensor([a for (s1,a,r,s2,d) in minibatch])
reward_batch = torch.Tensor([r for (s1,a,r,s2,d) in minibatch])
state2_batch = torch.cat([s2 for (s1,a,r,s2,d) in minibatch])
done_batch = torch.Tensor([d for (s1,a,r,s2,d) in minibatch])
Q1 = model(state1_batch)
with torch.no_grad():
Q2 = model2(state2_batch) ***2***
Y = reward_batch + gamma * ((1-done_batch) * \
torch.max(Q2,dim=1)[0])
X = Q1.gather(dim=1,index=action_batch.long() \
.unsqueeze(dim=1)).squeeze()
loss = loss_fn(X, Y.detach())
print(i, loss.item())
clear_output(wait=True)
optimizer.zero_grad()
loss.backward()
losses.append(loss.item())
optimizer.step()
if j % sync_freq == 0: ***3***
model2.load_state_dict(model.state_dict())
if reward != -1 or mov > max_moves:
status = 0
mov = 0
losses = np.array(losses)
- 1 设置目标模型参数与主 DQN 同步的更新频率
- 2 使用目标网络获得下一状态的最大 Q 值
- 3 将主模型参数复制到目标网络
下面是目标网络的 DQN 的损失图

图 2:目标网络的损耗图
我们可以看到亏损有一个更稳定的下降趋势。试验超参数,例如经验重放缓冲区大小、批量大小、目标网络更新频率和学习速率。性能对这些超参数非常敏感。
当在 1000 个游戏上进行实验时,我们在准确率上比仅仅使用经验回放提高了 3%。现在准确率在 93%左右
这个项目的完整代码可以在这个 GIT 链接中找到
点击这里查看本文的第 1 部分:
点击这里查看本文的第 2 部分:
用 Spotify 构建歌曲推荐系统

大卫·普帕扎在 Unsplash 上的照片
你有没有想过 Spotify 上的“推荐(基于播放列表中的内容)”部分是如何工作的?本文解决了部分谜团,并在此过程中构建了一个推荐。让我们来分解一下流媒体服务中的“推荐算法”。
语境
这是关于使用 Spotify 播放列表数据集建立自动播放列表延续(APC)算法的四篇文章系列中的一篇文章。在这里看到我队友之前和之后的文章。
- 第一部分 : 用 Python 从 Spotify 的 API 中提取歌曲数据
- 第二部分 : EDA 和聚类
- 第三部分:用 Spotify 搭建歌曲推荐系统
- 第四部分 : 用 Flask 部署 Spotify 推荐模型
现在我们已经学习了如何导入数据并在数据上构建聚类模型。我们现在来看看如何使用推荐系统来预测播放列表中的下一首歌曲。
介绍
当 YouTubers 在讨论最新的策略以获得更多的浏览量时,或者当你或你的朋友在网飞上比较“推荐给你的”列表时,你可能听说过“推荐系统(RS)”这个术语。简而言之,推荐系统 基于你自己的观看历史或者你和朋友作为一个集体观看历史,推荐人们可能喜欢的东西 。从一个非数据科学从业者的角度来看,这就是你需要了解的关于 RS 的全部内容…哦,但是你是一个数据科学从业者。如果是这样的话,我们需要更深入地挖掘推荐系统背后的思想和代码。
在我们深入研究 RS 和代码之前,让我们后退一步,澄清一些问题:
- 什么是推荐系统? 正如我之前提到的,推荐算法的目标是基于用户的数据或基于整个用户数据库推荐或预测用户可能喜欢的项目。我们稍后会详细讨论如何做到这一点以及不同类型的推荐人,但现在,这里有一个概念性的管道来展示推荐一首歌曲的过程。

一般推荐系统管道。(图片由作者提供)
- 为什么要使用歌曲预测任务的推荐?另一个问题是为什么在歌曲预测任务中使用推荐系统。正如您在第二部分中读到的,可以使用基于聚类的算法来预测歌曲,但是,它缺乏向系统添加其他功能的灵活性,例如分类预测器。
换句话说,基于聚类的算法是一种推荐系统。然而,与下面介绍的另外两种类型的 RS 相比,基于聚类的算法缺乏灵活性。事实上,基于内容的过滤和协作过滤都可以将聚类结果包含到模型中,从而创建一个混合 RS。**
现在,让我简单介绍一下业界最常见的两个推荐系统。
推荐系统

不同类型的推荐系统。(图片由作者提供)
根据应用过滤技术的对象,可以将粗糙集分为两类:基于内容的过滤和协同过滤。哇,这里有很多大词。让我解释一下。 过滤指的是“选择” — 我们选择的是彼此相似而非相异的特征。例如,从下面的话中:
你的单词包:猫、牛、鸟、桥
哪个单词是奇数?直观地,弹出“桥字样。怎么来了?你可能会认为所有其他的动物都是一种动物。你也可以认为所有其他人都活着,所有其他人都有眼睛,等等。重点是:有一组独特的特征将桥与袋中的所有其他单词分开。
现在,我们有了一个新词,狗,我们想知道什么词更接近包里的狗。很自然地,我们会说,“ 狗 更类似于 猫 , 牛 ,以及 鸟 ,相比之下 桥 ”如果有人问你原因,你会回答狗是一种动物,活着,有眼睛等等。
等等。这看起来很熟悉。是的,当你想要区分桥和袋子里的其他单词,或者准确地说,是相同的特征集时,你正在使用相同的过程。通过找到包中物品的相似之处,我们从本质上“过滤”了包中的桥。
瞧。您现在知道如何进行基于内容的过滤了!基于内容的过滤使用每个项目的特征来寻找相似性项目。通过对每个项目的相似程度进行评分,我们可以根据它与数据集中所有其他项目的相似程度来推荐一个项目。
在 Spotify 播放列表的上下文中,我们使用功能(响度、速度等。)来查找整个播放列表的平均分数。那么,我们推荐一首与播放列表评分相近,但不在播放列表中的歌曲。
基于内容的过滤:推荐与数据集中其他歌曲相似的歌曲。
“但是,等等。你说的是另一种过滤。除了特征之外,还有什么不同的方法来选择和过滤包中的物品?”
好问题!根本就没有!另一种过滤不是过滤包里的物品,而是过滤包本身!迷茫?让我用同样的类比来解释:
现在,你的朋友带着另一袋单词进来,上面写着:
你朋友的话包:公路,隧道,钢铁,山羊。
使用我们之前的方法,我们可以看到,根据项目特征,新单词" dog "与你朋友的包相比,更类似于你的包,因为你包中的大多数单词是一种动物、有眼睛等。相比之下,你朋友的包里大部分都是没有生命的东西。
然而,有另一种方法来解决这个问题。如果你有一群朋友,他们有自己的词汇包,如下:

单词矩阵。(图片由作者提供)
如果单词在他们的包里,则值为“是”,否则为“否”。现在,我用红色表示“是”,蓝色表示“否”,并根据黑线将单词分成两部分。

带注释的单词矩阵。(图片由作者提供)
我们看到黑线上面的字是动物,下面的字是无生命的物体。
现在,我不确定单词 dog 是否应该放在你或你朋友的包里,因为你们两个的包里都没有这个单词。为此,我进行了以下调查:
- 我观察了你的朋友 A、B 和 C,看看他们的书包里都有什么单词。
- 令人惊讶的是,我发现你和朋友 A 有 3 个不匹配,而你的朋友和朋友 c 只有 1 个不匹配。
- 我检查了他们的包里有没有狗字样。
- 朋友 A 有狗,朋友 C 没有狗。
- 我把狗放在你的包里,而不是你朋友的。
我们看到,我们“ 过滤了 ”的数据是基于你和你朋友的共同点,或者换句话说, 协作地 。您现在已经执行了协同过滤(CF) !
因为你和你的朋友是包的主人,我们在你包里的物品中寻找相似之处,这种类型的协作过滤被称为用户-用户 CF ,重点放在用户之间持有的物品上。
在歌曲预测的上下文中,我们将查看每个播放列表中的歌曲的相似性,并且如果歌曲与另一个播放列表的相似性高并且该歌曲不在另一个播放列表中,则推荐一个播放列表中的歌曲。
协同过滤(CF):根据数据集中播放列表中歌曲的重叠部分推荐歌曲。
还有其他方法来进行协同过滤,包括项目-项目 CF 和更广泛的基于模型的伞状 CFCF,但是我们不会在本文中详细讨论。
请参考 Abhijit Roy 的 这篇文章 。
履行

该项目的推荐系统管道。(图片由作者提供)
在这一节中,由于这个项目的限制,我将主要实现基于内容的过滤。
查看上面带注释的推荐系统管道,我们将首先根据从第一部分中清理的数据来查看 Spotify 数据的特征。然后,我们将进行特征工程,将数据处理成可以输入基于内容的过滤算法以及所使用的相似性度量的形式。最后,我们将很快讨论一些与模型相关的阈值和指标。
预处理
在提取任何特征之前,在导入提取的原始数据之后需要两个预处理步骤。
- 数据选择
数据选择程序包括两项任务。第一个是丢弃重复的歌曲。由于导入的数据最初是 Spotify 播放列表数据,因此删除多个播放列表之间存在的重复歌曲至关重要。该过程包括收集艺术家姓名和曲目标题,这样我们就不会意外删除同名但出自不同艺术家之手的歌曲。
这可以简单地通过 熊猫 数据帧操作来实现:
由于原始来源的数据丰富,我们希望在稍后的功能工程和推荐中选择与其他相关的功能(更多信息请参见完整笔记本)。这也是用 熊猫 进行的:
- 列表拼接
在选择了有用的数据后,由于 dataframe 的导入格式,我们需要将genres列转换回列表。

列表串联。图片作者。
这是通过使用split()功能完成的:
特征
下一步是对各种数据进行特征工程。我们可以根据数据的来源将数据中的变量分为三种类型,即元数据、音频数据和文本数据。
- 元数据
元数据指的是与歌曲相关的属性,而不是歌曲本身(例如,流行度和流派)。在这个项目中,我用两种方式处理元数据。
对于流派数据,我使用了 one-hot encoding ,这是一种将分类数据转换成机器可读特征的常用技术。这是通过将每个类别转换成一个列来实现的,这样每个类别可以表示为真或假。

这可以使用 熊猫 包来执行:
然而,Spotify 中的流派分布并不均衡,一些流派更加流行,而另一些流派则更加模糊。此外,一个艺术家或曲目可以与多个流派相关联。因此,我们需要权衡每一个流派的重要性,以防止过分看重特定流派而低估其他流派。
因此, TF-IDF 度量被引入并应用于类型数据。TF-IDF,也称为词频-逆文档频率,是一种对一组文档中的词进行量化的工具。TF-IDF 的目标是显示一个词在文档和语料库中的重要性。计算 TF-IDF 的一般公式为:

- 词频(TF) :一个词在每个文档中出现的次数除以文档中的总字数。
- 逆文档频率(IDF) :文档频率的对数值。文档频率是出现一个术语的文档总数。
这样做的目的是找到不仅在每个文档中重要,而且在整个语料库中也很重要的单词。取对数值是为了降低大 N 的影响,大 N 会导致与 TF 相比非常大的 IDF。TF 关注的是一个单词在文档中的重要性,而 IDF 关注的是一个单词在文档中的重要性。
在这个项目中,文档类似于歌曲。因此,我们正在计算每首歌曲中最突出的流派及其在歌曲中的流行程度,以确定流派的权重。这比简单的一键编码好得多,因为没有权重来确定每个流派的重要性和广泛程度,导致不常见流派的权重过大。

TF-IDF 公式。图片由泰德·梅提供。
为了实现这一点,我们使用了来自 scikit learn 的 TfidfVectorizer() 函数。
对于流行度数据,我把它当作一个连续变量,只有把归一化到 0 到 1 之间。这个想法是流行的歌曲很可能被喜欢流行歌曲的人听到,而不太流行的歌曲很可能被有相同品味的人听到。
这是通过 scikit learn 中的minmax scaler()函数完成的:
- 音频
音频数据是指使用 Spotify API 提取的歌曲的音频特征。例如,响度、速度、可跳舞性、能量、语速、声学、乐器性、活跃度、效价和持续时间。在这个项目中,我对这些数据进行的唯一操作是根据每个变量的最大值和最小值进行标准化。
此外,还对其他几个音频特征进行了一键编码,比如音轨的键。这种方法的限制类似于一键编码——我们不知道人们是否平等地看待不同的键。通过假设每个键的权重相等,不太可能从数学上获得数据的最佳表示。因此,可能需要调整超参数来改善预测。
- 正文
我最终使用的唯一文本特征是曲目名称。我进行了情感分析,找到了曲目名称的极性和主观性。
- 主观性 (0,1):文中包含的个人观点和事实信息的多少。
- 极性 (-1,1):表示否定的强烈或明确的情感程度。
情感分析的目标是从音轨中提取额外的特征。通过这样做,我们可以通过文本信息提取情感数据和其他音频特征。例如,如果播放列表的歌曲标题的总体情绪是积极的,那么这可以用来推荐积极的歌曲。然而,由于标题的长度较短,这两个指标无法产生最佳结果。因此,这两个指标的权重被评为低。
为了进行情感分析,使用了 TextBlob .情操 :
现在,我们将每种特征工程方法结合成一个功能,并将数据输出到一个大的特征数据框架中:
过程
在提取了每首歌曲的所有数据之后,我们现在讨论执行基于内容的过滤算法的过程。该算法分两步实现。每当有人输入新的播放列表查询时,都需要这两个步骤:
- 播放列表摘要

总结流程图。图片由 Madhav Thaker 和修改作者提供。
在这一步中,我们希望将播放列表中的所有歌曲汇总到一个向量中,该向量可以与数据集中的所有其他歌曲进行比较,以找到它们的相似性。
首先,我们需要导入一个 播放列表数据帧 。数据帧中唯一需要的是轨道 id 。有了曲目 id 数据,我们可以首先区分播放列表中的歌曲和不在播放列表中的歌曲。从播放列表中排除歌曲很重要,因为我们不想推荐现有的歌曲。
然后,我们根据上一节中生成的数据集找到这些歌曲的特征。因此,重要的是我们的数据集包括尽可能多的歌曲,以降低在此步骤中 与播放列表 中的歌曲不匹配的可能性。
最后,我们将播放列表中每首歌曲的所有特征值加在一起作为摘要向量。
换句话说,这个向量描述了整个播放列表,就好像它是一首歌一样。
- 相似性和推荐
在检索播放列表概括向量和非播放列表歌曲之后,我们可以找到数据库中的每首歌曲和播放列表之间的相似性。选择的相似性度量是余弦相似性。
余弦相似度是一个衡量向量之间相似性的数学值。想象我们的歌曲向量仅仅是二维的,视觉表示看起来类似于下图。

余弦相似性度量插图。图片由 DeepAI 提供。
一旦这两个向量大体上指向相同的方向,那么它们是相似的。这也是为什么我没有找到歌曲的意思,只是简单地把它们加起来。在我们的情况下,歌曲向量是超多维的,所以我们不能用图表很好地说明它。然而,数学直觉仍然是一样的。
形式上,数学公式可以表示为:

在我们的代码中,我们使用了来自scikit learn的cosine_similarity()函数来测量每首歌曲和摘要播放列表向量之间的相似性。
这样做的一个很大的优点是整个算法的时间复杂度等于一个矩阵乘法,因为我们正在执行每个行向量(歌曲)和概括的播放列表特征的列向量之间的余弦相似性度量。
结果
这种模式最大的一个问题是,几乎没有衡量推荐好坏的指标。因为在这个项目中,第二部分采用了基于聚类的方法。我们决定比较这两个建议,看看是否有任何共识。
比较


妈妈播放列表前 20 首歌(左)和两种推荐算法的推荐结果(右)。图片由作者提供。
为了比较不同推荐系统的结果,我们找到了这个名为“妈妈的播放列表”的播放列表,并将数据输入到我们不同的引擎中。(左上图显示了妈妈播放列表中的前 20 首歌曲)
从上图中,我们看到结果非常不同。这里,这两种方法是基于内容的过滤方法和使用 K-最近邻的聚类方法。关于后者的更多信息,请阅读本系列的第二部分。我们看到,在所有的列表中,唯一的相似之处是碧昂斯出现在其中的两个列表中——然而,歌曲并不匹配。结果的差异可能表明两种可能性。最有可能的原因是这两款机型都表现不佳。这可能是因为数据集大小、缺少超参数调整或模型约束。但是,也有一种可能,其中一个型号的性能最好,而另一个型号的性能跟不上。不管是哪种情况,这都显示了没有合适的度量来训练模型的问题,从而导致缺乏可以帮助改进模型的衡量成功的技术。
最后,这也显示了大型科技公司在推荐系统领域的优势。在开源环境中,如果不部署和接收用户的反馈,就很难衡量系统的成功。在歌曲推荐方面,这可以是将推荐的歌曲添加到他们的播放列表中的用户数量。通过查看指标,我们可以执行 A/B 测试,以查看哪个模型或参数执行得最好,并相应地更新模型。然而,理解推荐系统会给你在这个领域取得成功打下坚实的基础。希望在读完这篇文章后,你会更加清楚地了解这个声名狼藉但却必不可少的“推荐算法”的黑箱。
摘要
在本文中,我们首先学习了推荐系统的基础知识,包括基于内容的过滤和协同过滤这两种常用方法。然后,我们从零开始使用 Spotify 播放列表数据构建了一个基于内容的过滤推荐系统。这包括特征预处理、特征生成和推荐建模的过程。我们还讨论了每个部分的一些约束条件,以表明该方法可以进一步优化,以获得更好的结果。最后,我们将该推荐与其他歌曲推荐系统进行了比较,特别是聚类方法,并看到了在开源环境中衡量推荐系统成功的困难。
在构建模型之后,如果不知道如何部署它,就不能对它做任何事情!如果你想了解这个模型的部署过程,请阅读我的队友在系列中关于模型部署的部分。
参考
- Github 上的全笔记本
- 完整的项目 Github 库
- 谷歌开发者,基于内容的过滤 (2021)
- A.罗伊,推荐系统介绍- 1:基于内容的过滤和协作过滤 (2020),迈向数据科学
- 米(meter 的缩写))Thaker, Spotify 推荐系统 (2020),在 Github 上
- W.Scott, TF-IDF 在真实世界数据集上用 python 从头开始。 (2019),走向数据科学
- 页(page 的缩写)Shah,使用 TextBlob (2020)进行情感分析,迈向数据科学
- Spotipy, spotipy 文档(未注明)
词性标注快速指南
利用 NLP 的这一重要组成部分来执行客户意见分析

亚历山大·佩莱斯在 Unsplash 上拍摄的照片
什么是词类?
词性是描述单词语法功能的一种方式。在自然语言处理(NLP)中,词性是语言模型和解释文本的基本构件。虽然在 NLP 的高级功能中使用了 POS 标签,但是理解它们本身是很重要的,并且可以在文本分析中利用它们达到有用的目的。
英语中有八种(有时是九种)不同的词类被普遍定义为。布特学院介绍以下定义:
名词:名词是一个人、地方、事物或想法的名称。
代词:代词是用来代替名词的词。
动词:动词表示动作或存在。
形容词:形容词修饰或描述名词或代词。副词:副词修饰或描述一个动词、一个形容词或另一个副词。
介词:介词是放在名词或代词前的词,构成短语,修饰句子中的另一个词。
连词:连词连接单词、短语或从句。
感叹词:感叹词是用来表达情感的词。
限定词或冠词:表示确定(the)或不确定(A,an)的语法标记。这些并不总是被认为是词性,但通常包含在词性标注库中。
词性标注的基础
让我们从一些简单的例子开始,这些例子使用了三个常见的 Python 库:NLTK、TextBlob 和 Spacy。我们将为每一个做绝对的基础,并比较结果。
从导入所有需要的库开始。
import nltk
from textblob import TextBlob
from textblob.taggers import PatternTagger
import spacy
在我们的例子中,我们将使用两个带有一个常用词的句子, book ,这个词可以是名词也可以是动词,来测试词性标注在上下文中的表现。
- 请帮我订一张去加州的机票
- 我读了一本非常好的书
NLTK
先说 Python 中 NLP 最常见的库;自然语言工具包或 NLTK 。
tokenized_sent = nltk.sent_tokenize("Please book my flight to California")
[nltk.pos_tag(nltk.word_tokenize(word)) for word in tokenized_sent]
[[('Please', 'NNP'),
('book', 'NN'),
('my', 'PRP$'),
('flight', 'NN'),
('to', 'TO'),
('California', 'NNP')]]
tokenized_sent = nltk.sent_tokenize("I read a very good book")
[nltk.pos_tag(nltk.word_tokenize(word)) for word in tokenized_sent]
[[('I', 'PRP'),
('read', 'VBP'),
('a', 'DT'),
('very', 'RB'),
('good', 'JJ'),
('book', 'NN')]]
我们在这里注意到的是,在大多数情况下, NLTK 正确地识别了上下文中的单词;然而,在我们的第一句话中,有几个错误,如 Please 被标记为专有名词(NNP) 和 book 被标记为名词(NN) ,而它应该是一个动词(VB) 。
注意:关于标签含义的列表,请参见 Penn Treebank 项目。
文本 Blob
让我们试试另一个名为 TextBlob 的库,它提供了一个简单的 API 来执行标准的自然语言处理(NLP)任务。这是 NLP 库的一个非常好的 Pythonic 实现,简化了一些常见的 NLP 任务。 TextBlob 做的大部分事情是包装 NLTK 和其他流行的 NLP 库,使它们更容易使用。
blob = TextBlob("Please book my flight to California", pos_tagger=PatternTagger())
blob.tags
[('Please', 'VB'),
('book', 'NN'),
('my', 'PRP$'),
('flight', 'NN'),
('to', 'TO'),
('California', 'NNP')]
blob = TextBlob("I read a very good book", pos_tagger=PatternTagger())
blob.tags
[('I', 'PRP'),
('read', 'VB'),
('a', 'DT'),
('very', 'RB'),
('good', 'JJ'),
('book', 'NN')]
注意在 Blob 的初始化中使用了PatternTagger。默认情况下使用 NLTK 的 tagger,产生与上面相同的结果。这允许我们尝试不同的 POS Tagger 并检查其性能。我们可以看到 TextBlob 这次正确地将 Please 识别为动词但在第一句中仍然遗漏了 Book 为动词。
空间
Spacy 是这三款中最现代、最先进的。它对于大量的 NLP 任务来说非常健壮,如果需要更多的能力,还可以进行定制。这是我目前最喜欢的 NLP 库,让我们用我们的句子来看看吧。
doc = nlp("Please book my flight to California")
for token in doc:
print(token.text, token.pos_)
Please INTJ
book VERB
my PRON
flight NOUN
to ADP
California PROPN
doc = nlp("I read a very good book")
for token in doc:
print(token.text, token.pos_)
I PRON
read VERB
a DET
very ADV
good ADJ
book NOUN
我们在这里看到 Spacy 正确地标记了我们所有的单词,它将 Please 识别为一个感叹词而不是一个动词,后者更准确,并且在第一句中还将 Book 识别为一个动词。
这些库各有利弊。我相信你应该从 NLTK 开始理解它是如何工作的,特别是因为它对不同的语料库有如此强大的支持。当你想要简单的处理几个 NLP 任务时,TextBlob 是很棒的,当你想要一个最健壮的 NLP 库时,text blobSpacy是很棒的。
看看这个伟大的自然语言系列 NLTK 和 Python来自PythonProgramming.net。
用词类分析客户之声
NLP 执行的最常见的任务之一是分析来自各种来源的客户反馈,并确定客户对您的产品或服务的评价。这种类型的分析称为客户分析之声或 VOC 。
有许多方法可以进行 VOC 分析。从情感分析到话题建模,你可以使用的一种方法是词性标注来缩小客户在谈论什么以及他们如何谈论你的产品和服务。
用于分析的文本可以来自调查回复、支持票、脸书评论、推文、聊天对话、电子邮件、电话记录和在线评论。假设您有一个客户评论集。你可能想确定的事情之一是人们正在谈论的所有产品。您可能在数据库中有一个完美的产品分类,但是如果您没有达到您需要的粒度级别呢?对于这个例子,我们将使用 Kaggle 上女装电商服装评论的数据集。
导入数据集后,我们可以为所有单词及其 POS 标签创建一个新的数据帧。下面的函数获取每个评论并确定每个单词的 POS 标签;一个重要的区别,因为我们得到了句子中每个单词的上下文,正如我们在上面看到的,这对于关联哪个 POS 标签有很大的区别。
def pos_tag(text):
df = pd.DataFrame(columns = ['WORD', 'POS'])
doc = nlp(text)
for token in doc:
df = df.append({'WORD': token.text, 'POS': token.pos_}, ignore_index=True)
return df
接下来,我们可以对评论的子集运行该函数。由于我们使用的是单个单词,数量可能是数百万,我们很可能不需要整个数据集。
# Take a random sample of reviews
df2 = df.sample(10000, random_state=42).copy()
# Create an empty dataframe to store the results
df_pos = pd.DataFrame(columns = ['WORD', 'POS'])
# Iterate through the reviews and append each POS tag to the dataframe
df_pos = pos_tag(df2['Review Text'].to_string())
df_pos.shape
(144498, 2)
接下来,我们可以对每个 POS 标签进行分组和计数,以查看使用频率最高的标签。
df_top_pos = df_pos.groupby('POS')['POS'].count().\
reset_index(name='count').sort_values(['count'],ascending=False).head(15)

作者图片
太好了!我们有很多标签和单词。然而,这些标签本身并不能告诉我们太多,只能让我们看到不同标签的分布情况。然而,现在我们可以使用我们的标签来提取可能代表产品的词,而不是那些代表我们评论中其他词的词。为此,我们可以只过滤名词。
df_nn = df_pos[df_pos['POS'] == 'NOUN'].copy()
df_nn.groupby('WORD')['WORD'].count().reset_index(name='count').\
sort_values(['count'], ascending=False).head(10)
WORD COUNT
667 dress 1779
2062 top 1176
1764 shirt 463
1971 sweater 437
453 color 383
1807 size 312
765 fabric 287
1922 store 274
1822 skirt 256
1416 pants 246
看那个!我们有这个评论子集中使用的前 15 个词,其中大多数看起来像产品类别。如果我们现在查看相同评论子集的顶级形容词会怎么样?
df_adj = df_pos[df_pos['POS'] == 'ADJ'].copy()
df_adj.groupby('WORD')['WORD'].count().reset_index(name='count').\
sort_values(['count'], ascending=False).head(15)
WORD COUNT
400 great 481
144 beautiful 405
248 cute 398
784 soft 321
218 comfortable 272
632 perfect 243
585 nice 196
776 small 176
41 Great 149
666 pretty 146
394 gorgeous 144
511 little 142
611 other 142
523 lovely 125
349 flattering 122
忠太🎉!描述顾客如何谈论我们产品的最常用词。我们有很多积极的词汇,但也有一些词汇可能值得研究。像小号和小号这样的东西潜在地说明了尺码问题,有些衣服不太合身。产品经理可以利用这些信息,更深入地研究提到这个词的评论。
如上所述,还有其他可能更好的方法来分析文本,比如情感分析和主题建模。然而,这是一种在实际用例中应用 POS 标记的有趣方式,甚至可以与其他 NLP 工具结合使用,以帮助您最大限度地利用客户的反馈。
这个分析的所有代码都可以在 GitHub 上获得。
如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。一个月 5 美元,让你可以无限制地访问成千上万篇文章。如果你使用我的链接注册,我会赚一小笔佣金,不需要你额外付费。
参考
偏相关——概念和意义
两个变量之间的相关性可靠吗?

来自像素的图像
通常,多元线性回归是关于量化一个因变量和几个自变量之间的线性关系。通过这个模型,我们可以发现一个变量如何受其他变量的影响,例如,收入如何受教育程度、地区、年龄、性别等的影响。然而,我们需要问,模型显示的关系是可靠的,还是乱真的?
作为一个介绍性的例子,为了说明检验偏相关的重要性,我们尝试建立一个收入的多元线性回归模型。数据集内置于 R 中,来源于国家纵向研究。
在构建多元模型之前,首先检查相关性通常是一个好的做法,这将在下一节中简要概述。我们使用相关矩阵来可视化相关性

图 0.1 相关矩阵。(图片由作者提供)
从图 0.1 我们可以看出,收入与身高、体重、教育程度和 afdp(武装部队资格测试百分位分数)正相关。负面受婚姻(婚姻状态用数字表示如下:单身:1,已婚:2,分居:3,离婚:4,丧偶:5),性别(男性用 1 表示,女性用 2。这种负相关意味着女性挣得少一点)。数据在某种程度上证实了我们已经知道的,有一件事很可疑,那就是高个子挣得更多。其实这个相关性是乱真的,后面我们会用偏相关来验证和解释为什么会这样。
快速捕捉相关性
相关性描述了两个变量之间的关系,即其中一个变量的增长(减少)会导致另一个变量的增长(减少)。

图 1.1 相关性示意图。(图片由作者提供)
有几种方法可以衡量两个变量之间的相关性。在本文中,我们使用皮尔逊相关系数( PCC ),也称为 Person's r ,因为它也在稍后的偏相关公式中使用。另一个参数,Spearman 的 rho,经常在 R 中实现,事实上,也使用人的 R,只是在等级系数之上。人的休息公式由下式给出

人的 r
其中,σₓ是 x 的标准差,σᵧ是 y 的标准差,cov(X,y)表示协方差,其定义为两个随机变量 x 和 y 与其均值的偏差乘积的期望值,即

协方差
相关性 通过将协方差 除以两个变量的标准偏差来归一化协方差。与协方差不同,相关性的范围在-1 和 1 之间。当它等于-1 或 1 时,意味着两个变量之间的关系给定正好是分别具有正斜率或负斜率的线性函数。
偏相关—去除混杂因素
偏相关是与相关性密切相关的概念。它表明,当我们发现两个变量之间的相关性时,这并不一定意味着它们之间有因果关系。偏相关以一个或几个其他变量为条件,量化两个变量之间的相关性。这意味着,当两个变量之间存在相关性时,这种相关性可能部分地由第三个变量解释,即混杂变量(或控制变量),这是虚假相关性的常见原因。去掉这部分后,剩下的,就是这两个变量之间的偏相关。
几何是理解本中提到的偏相关的直观途径之一。将变量表示为向量,下图显示了混杂因素如何影响其他变量。在我们的例子中,混杂因素是性别,我们可以看到它与收入和身高都呈负相关。

图 2.1 混杂因素的影响。(图片由作者提供)
在只有一个混杂因素的情况下(称为一阶偏相关),剔除 Z 因素后,随机变量 X 和 Y 之间的偏相关公式为

一阶偏相关公式
如果有多个控制变量,比如说一组 n 个控制变量 Z = {Z₁,Z₂,..,Zₙ},那么等式 2.1 中的 z 应该替换为 Z ,其中表示一个集合。X 和 Y 之间的偏相关的正式定义是
X 与 Z 和 Y 与 Z 线性回归的残差之间的相关性。
在这篇文章中,我们将坚持一阶偏相关。现在我们有了一个不同的工具,我们可以重温我们的介绍示例,并研究变量之间的部分相关性,如图 2.3 所示。这次我们可以看到身高,体重,婚姻状况被划掉了,对比图 0.1 中的相关矩阵。

图 2.3 偏相关矩阵。(图片由作者提供)
如何计算偏相关?三种方法在维基百科上有描述:1。线性回归,2。递归公式,3。矩阵求逆。第一种方法,线性回归,是基于偏回归的定义,当我们计算偏相关时,它让我们了解我们在计算什么。假设我们要计算 X 和 Y 之间的偏相关,去掉 Z 的影响。思路是我们先计算 X 关于 Z 的线性回归,得到残差。为什么残?是因为这是X 中的部分,Z 没有解释。我们对 Y 做类似的事情。然后我们计算这两个残差之间的相关性。下图显示了“解释”的含义,它实际上是线性回归中定义的一个术语

图 2.2 线性回归模型中的 SSE、SST 和 SSR(图片由作者提供)
形式推导。有一个很重要的事实值得强调:(偏)相关与线性回归密切相关。
我们可以用下面几行代码,尝试用回归来计算身高和收入的偏相关。
m.i <- lm(income ~ sex, data = dataH)
m.s<- lm(height ~ sex, data = dataH)
v.i <- summary(m.i)
v.s <- summary(m.s)
cor(v.i$residuals, v.s$residuals, method="pearson")
使用线性回归的结果是 0.094。但是pcor的输出告诉我们,身高和收入的偏相关是 0.017。为什么会这样呢?这是因为pcor在的影响下计算了每对变量之间的偏相关,所有其他变量都被移除(使用摩尔-彭罗斯广义矩阵求逆)。如果我们只取数据的子集(只有列income、height和sex),输出将是相同的 0.094。
分析介绍示例的完整代码
除了帮助我们识别多元线性回归中两个因素之间的真正相关性,偏相关在更复杂的模型中也非常有用。自回归-移动平均(AMRA)模型可以作为这样的例子,其中偏相关,更准确地说是偏自相关,在超参数选择中起着重要作用。
摘要
题目以一个介绍性的例子开始,这个例子让我们看到,有时候相关性不一定要表示因果关系。然后我们介绍这种现象的一个常见原因——混杂因素,以及解决这一问题的方法——检验偏相关。还介绍了计算偏相关的方法,并给出了用线性回归计算 R 中偏相关的实现。
资源
1Everitt,B. S .,& Skrondal,a .剑桥统计词典(2010 年)。
[2]Kenett,D. Y .,黄,x .,Vodenska,I .,Havlin,s .,& Stanley,H. E. 偏相关分析:金融市场应用 (2015)。量化金融, 15 (4),569–578。
偏最小二乘法
实践教程
深入探讨偏最小二乘回归和偏最小二乘判别分析,并提供 R 和 Python 中的完整示例

偏最小二乘法——应用于橄榄油和肉类的例子——由 Calum Lewis 在 Unsplash 上拍摄
偏最小二乘法
在本文中,您将发现关于偏最小二乘您需要知道的一切。顾名思义,偏最小二乘法与普通最小二乘法相关:拟合线性回归的标准数学方法。
抗多重共线性的偏最小二乘
线性回归的目标是模拟一个因变量(目标变量)和多个自变量(解释变量)之间的依赖关系。普通的最小二乘法非常适合这种情况,只要你满足线性回归的假设。
在某些领域中,您的模型中可能会有许多独立变量,其中许多变量与其他独立变量相关。如果发生这种情况,如果你使用 OLS,你会遇到麻烦:你会有多重共线性,因此违反了线性回归的假设。
偏最小二乘法是这种情况的解决方案:它允许您减少相关变量的维度,并对这些变量的潜在共享信息进行建模(在因变量和自变量中)。
多元结果的偏最小二乘法
偏最小二乘法的第二大优势是它是一种可以模拟多个结果变量的方法。许多统计和机器学习模型不能直接处理多个结果变量。
通常可以为这些模型找到解决方案。例如,为每个变量建立一个模型。然而,特别是对于分析用例,将所有东西都放在一个模型中是非常重要的,因为一个多变量模型的解释将不同于许多单变量模型的解释。
偏最小二乘法与其他模型
您已经看到了使用偏最小二乘法的两个主要原因:有多个因变量和许多相关(独立)变量。
还有其他方法可以解决这些问题。在深入了解偏最小二乘法的细节之前,让我们先来看几个相互竞争的方法。
偏最小二乘法与多元多元回归
多元多元回归是多元回归的多元对应物:它对多个自变量进行建模,以解释多个因变量。
虽然多元多重回归在许多情况下工作良好,但它不能处理多重共线性。如果数据集有许多相关的预测变量,则需要进行偏最小二乘回归。
偏最小二乘法与主成分回归
主成分回归是一种回归方法,提出了一种替代解决方案,具有许多相关的独立变量。在将独立变量输入普通最小二乘模型之前,PCR 对独立变量应用主成分分析。
PCR 可以分两步完成,先进行 PCA,然后进行线性回归,但也有同时完成这两步的实现。
偏最小二乘法和主成分回归的区别在于,主成分回归在降维的同时更注重方差。另一方面,偏最小二乘侧重于协方差,同时降低维数。
在偏最小二乘法中,自变量的已识别分量被定义为与因变量的已识别分量相关。在主成分回归中,在不考虑因变量的情况下创建成分。
当目标是找到因变量和自变量之间的依赖关系时,偏最小二乘法在这里具有优势。
偏最小二乘法与典型相关分析
典型相关分析是一种专注于研究两个数据集之间相关性的统计方法。这是通过在两个数据集上减少维度并找到具有最大相关量的成分对来获得的。
偏最小二乘法和典型相关分析之间的思想是相当可比的。这两种方法的主要区别在于,偏最小二乘法侧重于协方差,而典型相关分析侧重于相关性。
PLS 模型
现在我们已经看到了使用偏最小二乘法的一般原因,让我们来更详细地了解一下。
在偏最小二乘法中,有子类别,关于 PLS 的文献充满了令人困惑的术语和类别。在开始一个示例的实现之前,让我们试着澄清一些你在学习 PLS 时会遇到的术语,并给出一个不同 PLS 模型的列表。
偏最小二乘回归
绝对最常见的偏最小二乘模型是偏最小二乘回归,或 PLS 回归。偏最小二乘回归是 PLS 模型家族中其他模型的基础。由于它是一个回归模型,当你的因变量是数字时,它适用。
偏最小二乘判别分析
当您的因变量是分类变量时,可以使用偏最小二乘判别分析,或 PLS-DA。判别分析是一种分类算法,PLS-DA 增加了降维部分。
PLS1 与 PLS2
在一些文献和软件实现中,对 PLS1 和 PLS2 进行了区分。在这种情况下,PLS1 指的是仅具有一个因变量的偏最小二乘模型,而 PLS2 指的是具有多个因变量的模型。
SIMPLS vs NIPALS
SIMPLS 和 NIPALS 是做 PLS 的两种方法。SIMPLS 是作为早期版本 NIPALS 的更快和“更简单”的替代方案而发明的。当执行 PLS 时,这可能并不重要,因为两种方法的结果会非常接近。然而,如果你可以选择的话,最好还是选择更现代的简约风格。
KernelPLS
如前所述,偏最小二乘法是普通最小二乘法(线性回归)的一种变体。正因为如此,偏最小二乘法不能应用于非线性问题。核 PLS 解决了这个问题,使得偏最小二乘可用于非线性问题。核 PLS 拟合高维空间中输入和输出变量之间的关系,使得输入数据集可以被认为是线性的。
OPLS
OPLS 是潜在结构正交投影的缩写,是偏最小二乘法的一种改进。OPLS 承诺更容易解读。PLS 仅将变异性分为系统变异性和噪声变异性,而 OPLS 则更进一步,将系统变异性分为预测变异性和正交变异性。
对 OPLS 也有一些批评,因为众所周知这两种方法都获得了相同的预测性能(如果不能产生更好的性能,我们可以认为这不是“真正的”改进),其次,传统 PLS 更快。
对于本文的其余部分,我们将坚持使用传统的 PLS,因为它执行起来更简单,性能也一样好。
SPLS
SPLS 是稀疏偏最小二乘的缩写,是偏最小二乘模型的一种变体。如前所述,PLS 的目标是进行降维:将大量相关变量减少到更少的分量中。
SPLS 的目标不仅仅是降维。此外,它还适用于变量选择。SPLS 使用著名的套索惩罚在 X 和 Y 数据集中进行变量选择,目的是获得更容易解释的成分。
左旋偏最小二乘法
L-PLS 是 PLS 的一个提议变体,可以应用于一些特定的用例。该方法由 Martens 等人在 2005 年提出:
描述了一种新的方法,用于根据关于矩阵 X 中给出的 Y 中的行和矩阵 Z 中给出的 Y 中的列的附加信息来提取和可视化数据矩阵 Y 中的结构。X(I × K)和 Z(J × L)不共享矩阵尺寸维度,但通过 Y(I × J)相连。
正如他们在文章中所描述的,L-PLS 背后的思想是对同一个回归问题使用三个数据集,以便在行和列上有额外的数据。他们将这种情况描述为具有 L 形式的数据,这由 L-PLS 中的 L 表示。
这种方法非常有创意,当然也有它的使用案例。不幸的是,在流行的统计库中缺乏实现,而且它的应用非常特殊,这使得它在实践中还没有被广泛使用。
偏最小二乘回归示例
在本例中,我们将使用肉类数据集。应该数据集发布者的要求,以下是数据附带的官方声明:
*Fat, water and protein content of meat samples*
*"These data are recorded on a Tecator Infratec Food and Feed Analyzer working in the wavelength range 850 - 1050 nm by the Near Infrared Transmission (NIT) principle. Each sample contains finely chopped pure meat with different moisture, fat and protein contents.*
*If results from these data are used in a publication we want you to mention the instrument and company name (Tecator) in the publication. In addition, please send a preprint of your article to Karin Thente, Tecator AB, Box 70, S-263 21 Hoganas, Sweden*
*The data are available in the public domain with no responsibility from the original data source. The data can be redistributed as long as this permission note is attached."*
*"For each meat sample the data consists of a 100 channel spectrum of*
*absorbances and the contents of moisture (water), fat and protein. The absorbance is -log10 of the transmittance measured by the spectrometer. The three contents, measured in percent, are determined by analytic chemistry." Included here are the traning, monitoring and test sets.*
肉类数据的目标是对肉类进行 100 次自动近红外测量,并使用它们来预测肉类的水分、脂肪和蛋白质含量。这个例子有多种理由使用偏最小二乘法:
- 它有许多可以相互关联的独立变量。至少它们是非常不可解释的,这是对它们进行降维的好例子。
- 有多个因变量,它们之间也可能有一些相关性。
这种模型的目标是能够直接从化学计量测量中推断出肉类的脂肪、水分和蛋白质含量。因此,基本上理想的目标是让您能够避免耗时的测量工作,只需将您的肉放入扫描仪,即可获得估计的脂肪、水分和蛋白质含量。
与大多数偏最小二乘法的化学计量学用例一样,该用例主要关注于获得最佳性能。对模型的分析和解释是次要的。稍后我们将看到第二个例子,其中解释是核心,而不是预测性能。
在为预测性能建立模型时,用不同的模型进行模型基准测试总是很重要的。你可以参考这个机器学习入门指南来了解如何进行这样的基准测试设置,或者查看这篇关于网格搜索的文章:一个调整模型的好方法。
偏最小二乘回归 R
首先,让我们导入 r 中的数据。这些数据存在于modeldata库中,因此您可以使用下面的代码来导入数据:
偏最小二乘法-导入数据
数据如下所示:100 列具有化学计量学测量值,三列提供水、脂肪和蛋白质含量的测量值:

偏最小二乘法—肉类数据
如前所述,我们将尝试建立一个 PLS 回归模型,该模型可以从 100 次测量中预测水、脂肪和蛋白质含量。为了做到这一点,我们希望确定获得样本外的预测性能,而不是解释样本内的最大差异。
为此,我们将数据分成三个数据集:
- 用于训练模型的训练数据
- 用于调整模型的验证数据
- 对样本外误差进行最终估计的测试数据
通常,最好对数据进行随机分割。但是,为了使示例更容易理解,您可以对数据进行非随机分割,如下所示:
偏最小二乘法-分割数据
下一步是拟合一个完整的模型。也就是说,具有最大数量组件的模型:
偏最小二乘法-拟合模型
现在我们有了最复杂的模型,我们将使用它来确定应该使用多少个组件来获得最佳预测性能:这是一个重要的决定。
我们首先要看的是每个分量和每个因变量的预测均方根误差图。在这个 R 函数中,使用了内置的留一交叉验证函数:
您将获得以下三个图,以显示预测误差(与预测性能相反)在组件数量非常少的模型中最高,然后逐渐降低,当模型组件过多时,它们再次具有高误差。
最佳值应该在 15 到 20 之间。

偏最小二乘法—组件数量
为了更好地理解当组件更多或更少时会发生什么,我们可以查看下图。它显示了每个变量的回归系数(每个变量对应一个波长)。
偏最小二乘法—回归系数
该图清楚地表明,我们使用的组件越多,模式就变得越难:

偏最小二乘法—系数图
相比之下,包含 15 个组件的模式要复杂得多:
偏最小二乘法—拟合 15 组分模型
从下图中可以看出这种复杂性,因为系数要极端得多(有些低得多,有些高得多):

偏最小二乘法-15 组分模型的回归系数
现在,这些图表可以很好地获得最佳组件数量的估计值。然而,获得一个固定的数字作为输出要实际得多。网格搜索是一个工具,允许您搜索数值网格以获得最佳预测性能。它通常用于调整 ML 模型的超参数,我们将在这里使用它来调整ncomp超参数:
偏最小二乘法——网格搜索最佳 ncomp
这就为一个 19 的best_ncomp获得了一个 0.9483937 的best_r2。根据网格搜索,这意味着具有 19 个成分的 PLS 回归模型是预测肉类水分、脂肪和蛋白质含量的最佳模型。
作为最后的检查,让我们使用下面的代码来验证我们是否也在测试数据集上获得了好的分数:
偏最小二乘法-检查测试数据的 R2
最佳模型获得预测平均 R2 分数 0.9726439。我们现在已经成功调整了 PLS 回归模型,用于预测水、脂肪和蛋白质含量。如果这种误差幅度对于肉类测量所需的精度来说是可以接受的,我们可以用自动化的化学计量学测量来代替手工测量。然后,我们可以使用 PLS 回归模型将这些自动测量值转换为水、脂肪和蛋白质含量的估计值。
偏最小二乘回归 Python
对于那些喜欢 Python 的人来说,在继续讨论分类数据示例之前,让我们也用 Python 实现一下。
在 Python 中,您可以从我的 S3 存储桶中导入肉类数据,我在那里存储了来自 r 的数据的副本。
偏最小二乘法-导入数据
Python 中的数据如下所示:

偏最小二乘法 Python 中的数据
要获得与 R 中相同的训练、验证和测试数据的非随机分割,可以使用下面的代码。如果你不介意结果略有不同,你也可以使用 scikitlearn 的train_test_split函数进行随机抽样。
偏最小二乘法-在训练、验证和测试中分割数据
现在,完整的模型可以估计如下:
偏最小二乘法-估计模型
就像我们在 R 中做的一样,您需要调整组件的数量。预测误差作为部件数量的函数的曲线图可以如下创建:
偏最小二乘法—绘制预测误差与 ncomp 的函数关系
它将为您提供以下图表,这些图表显示了 15 到 20 个组件的最佳数量。

偏最小二乘法-绘制组件数量
现在,可视化不同数量组件的结果差异会很好。由于模型中有 100 个系数,数字数据很难查看。下图显示了图中的系数:
偏最小二乘法—系数图
对于三个因变量(水、脂肪、蛋白质)和成分数量(1 =蓝色,2 =橙色,3 =绿色),该图从左到右如下所示。

您可以看到,随着更多组件的加入,模型变得更加复杂,就像我们看到的 R 代码一样。
现在,让我们继续进行网格搜索,寻找元件数量n_comp的最佳值。我们为n_comp的每个可能值计算验证数据的 R2 分数,如下所示:
我们可以获得的最佳 R2 是 R2 分数为 0.9431952353094432 ,ncomp 值为 15。
作为对我们模型的最后验证,让我们验证我们是否在测试数据集上获得了可比较的分数:
得到的 R2 分数为 0.95628 ,甚至略高于验证误差。我们可以确信模型不会过度拟合,并且我们已经找到了合适数量的组件来制作一个高性能的模型。
如果这种误差对于肉类测试来说是可接受的,我们就可以放心地用自动化化学计量学测量结合 PLS 回归来代替手动测量水分、脂肪和蛋白质。然后,PLS 回归将化学计量学测量值转化为水、脂肪和蛋白质含量的估计值。
偏最小二乘判别分析实例
对于第二个例子,我们将做一个解释性模型:一个专注于理解和解释组件而不是获得预测性能的模型。
该数据是橄榄油的数据集。目的是看看我们能否根据化学测量和感官测量来预测原产国。这个模型将让我们了解如何根据化学和感官测量来区分不同国家的橄榄油。
因变量(原产国)是分类的,这使得它成为判别分析的一个很好的例子,因为这是分类模型家族中的一种方法。
偏最小二乘鉴别分析
在 R 中,只要导入 pls 库,就可以获得橄榄油数据集。您可以这样做:
偏最小二乘法-橄榄油数据
数据如下所示。它包含两个矩阵:具有 5 个化学测量变量的化学矩阵和具有 6 个感官测量变量的感官矩阵:

偏最小二乘法-橄榄油数据
当然,这种数据格式对于我们的用例来说并不理想,所以我们需要将两个矩阵的数据帧做成仅仅一个矩阵。您可以这样做:
偏最小二乘法-根据橄榄油数据制作一个矩阵
生成的矩阵如下所示。如您所见,它自动包含列名:

偏最小二乘法—正确的橄榄油数据格式
国家是行名的第一个字母(G 代表希腊,I 代表意大利,S 代表西班牙)。这里有一个简单的方法来创建 Y 数据作为因子。因子是 r 中的分类数据变量。
偏最小二乘法-创建国家变量
现在,我们来看看模型。我们将使用插入符号库来拟合模型。Caret 是一个很棒的库,包含了很多机器学习模型和很多模型开发工具。如果你对 caret 感兴趣,你可以看看这篇文章,它比较了 R 的 caret 和 Python 的 scikit-learn。
您可以使用 caret 中的 PLSDA 函数来拟合模型,如下所示:
下一步是获得双标图,这样我们可以解释组件的含义,同时分析个体的位置。
为了解释双绘图,你需要看方向。为了理解这意味着什么,试着从图的中间画一条假想的线到每个标签。假想线之间的角度使两个物体接近。与中间的距离决定了重量的强弱。
提示:你可以快速向下滚动到 Python biplot 来更好地理解假想线的概念!

偏最小二乘法—双标图
解释偏最小二乘双图
当将偏最小二乘法用于解释性用例时,可以从双标图中得出很多结论。为了使事情简单,我们有时会考虑两个以上的维度,但是从第三维度开始,事情会很快变得复杂。这里我们坚持二维。
- 解读第一维度
我们通常问自己的第一个问题是关于第一维的含义和解释。我们可以通过观察与第一维度密切相关的变量(图上的红色标签)来定义这一点。为了看到这些,我们可以把一个变量放在最左边(比较 1 的低分)和一个变量放在最右边(比较 1 的高分)。
在我们的例子中,第一个组件从左边的绿色到右边的黄色。显然,绿色和黄色的分界在橄榄油中很重要!
为了证实这一点,我们现在来看看个人是否有趋势(图中黑色标签)。我们在左边看到很多希腊油画,而在右边我们看到很多西班牙油画。这意味着黄色和绿色橄榄油的区别让我们能够区分希腊和西班牙的橄榄油!
就变量而言,一个有趣的见解是,橄榄油中从黄色到绿色似乎有一个非常明显的颜色梯度,而棕色不是由第一维度表示的。
2。解读二次元
现在,让我们看看我们能从二次元中学到什么。首先,我们来找一些有代表性的变量。要做到这一点,我们需要找到在维度 2 上得分非常高或非常低的变量。
我们可以看到,dimension 2 从底部的棕色到顶部的光滑透明。显然,橄榄油有一个重要的梯度,一端是棕色的油,另一端是光滑透明的油。
为了获得关于国家的知识,让我们看看个体是如何沿着维度 2 的轴分布的。这种分离没有维度 1 中的明显。然而,当我们仔细观察时,我们可以清楚地看到意大利橄榄油通常比其他油更棕色。 非意大利橄榄油一般也比其他油更有光泽和透明度。
就变量而言,一个有趣的见解是,从棕色到另一种颜色没有渐变,而是从一侧的棕色到另一侧的光泽和透明。当然,在现实中,橄榄油专家会合作进行这样的研究来帮助解释这些发现。
3。主要组件中变量类型的解释
我们在这里看到的是被模型定义为最重要的维度。我们可以注意到,最重要的成分大多存在于感觉成分中。这意味着感官特征似乎可以很好地检测橄榄油的来源国。这也是一门很有意思的学问!
偏最小二乘判别分析 Python
橄榄油数据集内置在 R PLS 库中。我在我的 S3 桶上放了一个副本,让它也可以很容易地用 Python 导入。(如果您想在其他地方分发此数据集,请查看更高一级数据集的通知)。
您可以使用以下代码将数据导入 Python:
偏最小二乘法 Python 中的橄榄油数据
数据是这样的。我添加了国家作为变量,而在原始的 R 数据集中却不是这样。

偏最小二乘法-橄榄油数据
Python 中的 PLS 判别分析实际上是通过对转换成哑元的分类变量进行 PLS 回归来完成的。虚拟变量将分类变量转换为具有 1 和 0 值的每个类别的变量:如果行属于该类别,则为 1,否则为 0。
进行虚拟编码是因为 0 和 1 在许多机器学习模型中更容易使用,并且一组虚拟变量包含与原始变量完全相同的信息。
在现代机器学习的行话中,创建虚拟人也被称为一热编码。
您可以使用熊猫创建假人,如下所示:
偏最小二乘法-创建虚拟数据
数据现在将包含三个国家变量:每个国家一个。如果该行属于该国家,则值为 1,否则为 0:

偏最小二乘法-虚拟编码国家
下一步是将数据分割成 X 数据框和 Y 数据框,因为这是 Python 中 PLS 模型所要求的:
偏最小二乘法-在 X 和 Y 方向分割数据
现在我们来看看模型。我们将使用 scikitlearn 包中的 PLSRegression。我们可以直接用两个分量来拟合它,以给出与我们在 R 示例中所做的相同的解释。
偏最小二乘法-拟合偏最小二乘法模型
Python 中多元统计的难点往往是图的创建。在下面的代码块中,创建了一个双绘图。它是逐步编码的:
- 首先我们获得分数。分数代表每种橄榄油在每个维度上的得分。分数将允许我们绘制双标图中的个体。
- 我们需要将分数标准化,以使它们与加载值在同一个图上。
- 然后我们得到载荷。载荷包含每个部件上每个变量的权重。它们将允许我们在双图上绘制变量。
- 然后,我们循环遍历每个个体和每个变量,并为它们绘制一个箭头和一个标签。维度 1 得分或载荷将成为图上的 x 坐标,维度 2 得分或载荷将成为图上的 y 坐标。
代码如下所示:
Python 中的偏最小二乘法-双标图
生成的双绘图如下所示:

Python 中的偏最小二乘法-双标图
结论
双标图的 R 解释将给出与 Python 双标图相同的发现。快速回顾一下调查结果:
- 维度 1 (x 轴)的一边是绿色,另一边是黄色。因此区分黄色和绿色的油是很重要的。
- 在绿色的一边,有很多希腊的油画,在黄色的一边,有很多西班牙的油画。绿色/黄色分离允许区分希腊和西班牙橄榄油。
- 尺寸 2 (y 轴 _ 一侧为棕色,另一侧为光滑透明。很明显,棕色的油和光滑透明的油是有区别的,但两者不能同时存在。
- 意大利的油画倾向于棕色,而西班牙和希腊的油画倾向于光泽和透明。
关键要点
在本文中,您首先看到了偏最小二乘法的(许多)变体的概述。此外,您已经看到了使用偏最小二乘法的两种方法的深入解释和实现:
- 偏最小二乘法作为机器学习算法,用于肉类示例中的预测性能
- 橄榄油示例中解释的偏最小二乘法
您还看到了如何对不同类型的因变量使用偏最小二乘法:
- 肉类用例中数字因变量的偏最小二乘回归
- 橄榄油用例中分类因变量的偏最小二乘法判别分析
通过在两个例子中使用 R 和 Python 实现,您现在有了在自己的用例中应用偏最小二乘法所需的资源!
现在,感谢您的阅读,请继续关注更多数学、统计和数据科学内容!
基于 Webots 和 ROS2 的粒子滤波定位
思想和理论
使用蒙特卡罗方法(粒子滤波定位)帮助机器人在地图中定位。
概观
移动机器人必须解决的主要问题之一是知道它在环境中的位置。确定机器人相对于其环境的位置和方向(机器人姿态)的过程称为定位。
如果不知道它在环境中的位置,机器人能完成的任务就非常有限。因此这是它要解决的最重要的问题之一。

环境中的 e-puck(图片由作者提供)
我们很容易知道机器人在上图中的位置,因为我们可以看到一切。但是,机器人本身却不是这样。下图显示了机器人对周围环境的了解。

地图中的机器人(图片由作者提供)
它所知道的是地图看起来是什么样子,这不是很准确——有缺失的信息,如墙上的洞等。传感器的读数,有时还有它近似的初始姿态。
如果你把自己放入机器人中,你可以看到,用你所拥有的所有信息和能力来知道你在哪里并不是一件容易的事情。从你现在所处的位置来看,你可能无法百分百确定你在给定地图上的位置。
在本文中,我们将研究解决定位问题最广泛使用的方法,蒙特卡罗定位或通常称为粒子滤波定位。
我们将浏览粒子过滤器本地化的构建模块,并查看我在 Webots 模拟器和 ROS2 上实现的演示。源代码在我的 Github 上,你可以参考。
由于这个主题的复杂性,不可能在这一篇文章中写完所有的内容。我假设你熟悉 ROS2、Webots 模拟器、Python、统计学和概率论等。但是不要担心,如果您对所有这些东西不满意,您仍然可以浏览代码并尝试运行模拟。
贝叶斯过滤器
机器人学中许多概率方法的基础是 贝叶斯滤波器 或也被称为 递归贝叶斯估计 。在概率机器人学中,机器人姿态等状态由其概率密度函数或 pdf 表示。
另一个概念叫做 信念 ,也就是说它是机器人的内部知识,是从它的角度而不是我们的角度。
贝叶斯滤波器是一种使用数学过程模型和测量值递归估计状态的 pdf 的通用方法。

贝叶斯过滤器(作者图片)
该算法根据先前的置信分布 bel(t-1) 、控制/过程数据和测量数据 z 来计算置信分布 bel ,并且它递归地进行。
该算法有两个步骤:
- 预测步骤,我们根据之前的状态和控制数据计算我们的预测
- 测量更新,我们根据我们的预测和测量数据更新我们的信念分布
目前可能还不清楚这些步骤试图做什么。本质上,过滤器帮助我们计算一些我们不能直接测量的东西。为此,我们需要两样东西:
- 一些我们可以控制的事情
- 不准确/有噪音的测量
对于我们的机器人,我们可以控制速度(线性和角度),我们可以使用距离传感器测量到最近物体的距离。因此,通过省略细节,我们的步骤是:
- 根据机器人先前的姿态和速度信息,预测机器人已经移动到哪里
- 根据传感器的测量结果调整我们的预测

预测与现实(作者图片)
如上图所示,我们的预测是不准确的,它可能是由不同的事情引起的。白色机器人是处于 t-1 或先前姿势的机器人,蓝色机器人是基于我们预测的当前姿势,红色机器人是真实姿势。星星是机器人环境中的地标。
第二步很重要,因为我们使用我们的测量来调整我们的估计,使之更加准确。因为,如果机器人在蓝色位置,我们应该期望从传感器得到一些读数。当读数不同时,我们相应地调整我们的估计。这是贝叶斯过滤器的基本和直观的概念。

根据测量数据调整估计值(图片由作者提供)
贝叶斯滤波器是一个通用的概念,现实世界的应用包括滤波器如卡尔曼滤波器及其变种如 EKF、UKF 等。,以及粒子过滤器,这就是我们在这篇文章中用于我们的定位。
粒子滤波定位的构建模块
从上面讨论的两个步骤中,我们可以看到滤波器中有几个不同的组件。对于我们的应用程序,粒子过滤器定位,我们需要以下组件:
- 用于预测更新的运动模型
- 用于度量更新的度量模型
- 用于整体递归更新的粒子滤波器
我们可以使用许多方法来实现每个组件,在本文中,我们将重点关注:
- 里程计运动模型
- 可能性字段
- 粒子过滤器
里程计运动模型
大多数机器人都配备了车轮编码器来感知车轮旋转了多远。利用这些信息,机器人可以预测它在移动一段时间后的姿态。
然而,就像从其他传感器获得的测量值一样,里程计是错误的。它会打滑和漂移。
里程计运动模型使用相对运动信息,该信息是先前和当前姿态之间的增量。

里程计运动模型(图片由作者提供)
如果我们知道三角洲,它包括:
- 旋转 1
- 翻译
- 旋转 2

增量计算(图片由作者提供)
我们可以通过在建模错误里程计数据的过程中添加一些不确定性来预测我们当前的姿态。我们将噪声建模为零均值高斯,误差参数为α1至α4。

零均值高斯(图片由作者提供)
下面噪声函数的输入是方差。

运动模型噪声(图片由作者提供)
最后,我们可以计算我们预测的状态。

预测状态(图片由作者提供)
你可以在 motion_model.py 中查看 python 中里程计运动模型的实现。
可能性字段
如 概率机器人 所述,测量模型描述了传感器测量在物理世界中生成的形成过程。形式上,它被定义为条件概率分布:

测量模型(图片由作者提供)
在我们的例子中,我们对距离传感器进行建模。有不同的方法来模拟距离传感器,例如:
- 梁模型
- 可能性字段
- 地图匹配
- 等等。
在本文中,我们主要关注似然场模型。该模型并不是一个真正的逆传感器模型,因为其关键思想是将传感器读数的端点投影到地图的坐标框架中。
当我们知道终点在地图上的位置时,我们就可以计算出它们是真实的可能性有多大。当没有障碍时,信息没有意义,因此被丢弃。
使用这种模型的好处是,与更好地描述传感器测量在物理世界中如何产生的波束模型相比,它更平滑。
为了计算地图上的端点,我们使用以下等式:

坐标框架中的端点(图片由作者提供)
算法相当简单,我们只需要计算每个端点到 最近物体 的距离。获得距离后,我们可以使用均值为零的高斯函数和可配置的标准偏差来模拟噪声,从而计算出概率。
对于真实的机器人,我们可能希望模拟不同类型的噪声,例如:
- 测量噪声
- 传感器故障
- 无法解释的随机测量
但是对于本文,我们只对测量噪声建模,因为在机器人的环境中没有动态对象。
要了解它在 Python 中是如何实现的,请参见 sensor_model.py 。我还在同一个 python 模块中实现了 Beam 模型。然而,正如《概率机器人学》一书中所述,它显示出缺乏平滑度,当前状态下的一个小姿势误差会产生很大的影响。很难获得大概率。
粒子过滤器
最后也是最重要的组件是粒子过滤器。如上所述,它是从贝叶斯过滤器得到的。但与高斯滤波器不同,它是非参数的,这意味着我们不使用均值和方差等参数,它通过有限数量的样本来逼近后验概率。
关键思想是使用从后验概率 bel(xt) 中抽取的随机样本。因为它是非参数的,所以它可以表示更广泛的分布,例如,不限于高斯分布,并且它可以模拟非线性系统。这些样本被称为粒子,因此得名粒子滤波器。

基本粒子滤波算法(图片由作者提供)
就像贝叶斯过滤器一样,我们有两个步骤:
- 预测,使用样本运动模型
- 测量更新,在给定机器人的姿态和地图的情况下,或者在称为权重的粒子滤波器中,获得传感器读数正确的概率
直观地说,粒子过滤器通过使用一组粒子/样本来估计真实姿态。区域越密集,越有可能接近真实姿势。参见下面的例子,红色小箭头代表粒子。

红色小箭头显示粒子(图片由作者提供)
我们不会在粒子滤波上深入探讨,因为这是一个非常复杂的话题。然而,由于以下挑战,上述简单算法在实践中并不十分有效:
- 简并问题,其中一个粒子的权重将非常接近于1,而其余的非常接近于0,这个问题可以通过重采样步骤来解决。**
- 样本贫化,其中在对粒子进行重采样之后,它们中的大多数是相同的或者在相同的位置,这可以通过添加人工噪声*的粗糙化来解决,或者在重采样步骤之后,或者将运动模型参数调整为噪声更大。*
- 偏离度,姿态偏离真实姿态太远的情况,可以通过检查未标准化的权重(概率)来检测,如果它们都非常接近零的话。一种恢复方法是通过使用例如均匀分布来重新初始化粒子。
- 计算复杂度,粒子滤波器需要大量样本才能正常工作。我们使用的粒子越多,计算复杂度就越高。为了克服这个问题,有不同的方法,如自适应*方法,其中粒子的数量在运行期间动态变化。如果粒子集中在同一位置,则可以减少样本/粒子的数量,以提高计算复杂度。*
要查看 Python 中实现的细节,请查看 monte_carlo_localizer.py 。它实现了重采样和自适应方法。
使用 Webots 和 ROS2 进行仿真
要构建软件包,您可以克隆存储库并按照自述文件中的步骤进行操作。
在这个模拟中,我们在 Webots 模拟器上使用了一个 e-puck 机器人,它配备了一个远程 ToF 传感器和八个短程红外传感器以及地图。

环境地图(图片由作者提供)
机器人的初始姿态是已知的,但不精确,所以我们将使用高斯分布来初始化我们的粒子。我们粒子的初始数量是 1000。
非自适应粒子滤波器
在这种方法中,我们使用非自适应粒子过滤器,这意味着粒子的数量在整个运行过程中保持不变。在我们的例子中,它是 1000 个粒子。
正如我们在下面的视频中看到的,初始姿势是错误的,因此过滤器试图调整它。
非自适应粒子滤波器定位(视频由作者提供)
红色大箭头是里程计姿势,红色小箭头代表粒子。
我们命令机器人在开始时旋转 360 度,让粒子过滤器调整初始姿态。我们可以看到误差相当大,初始姿态向右移动。
我们还可以看到,粒子过滤器在调整姿态方面表现非常好。这不是很准确,但考虑到大多数时间传感器仅从 t of 传感器读数中提供一个点,并且地图也有噪声(有一些缺失的占用信息),这是可以接受的。

机器人路径—非适应性(图片由作者提供)
蓝线是粒子过滤器路径,红线是里程计路径。我们可以看到,在某些时候,粒子滤波器会调整机器人的姿态。
自适应粒子滤波器
如前所述,自适应粒子滤波器在运行过程中动态改变粒子数量,以降低计算复杂度。
这是因为如前一段视频所示,大部分时间粒子都集中在一个位置,因此我们实际上可以减少样本数量。看下面这个视频。
自适应粒子滤波器(视频由作者提供)
我们可以看到,在它开始旋转后,粒子的数量显著减少。从下面的路径中,我们可以看到,与上面的非自适应版本相比,精度没有显著影响。请注意,机器人是随机移动的,因此我们不希望它在所有运行中都遵循相同的路径。

机器人路径—自适应(图片由作者提供)
摘要和参考文献
我们已经看到粒子滤波定位由三个主要部分组成:
- 运动模型
- 测量模型
- 粒子过滤器
粒子滤波器有一些我们必须解决的挑战,例如增加重采样步骤,向运动模型添加人工噪声,以及使用自适应方法来降低计算复杂度。
希望这篇文章和下面提供的源代码对更多人了解粒子滤波本地化有用。
源代码
您可以从以下网址获得完整的源代码:
*https://github.com/debbynirwan/mcl
要了解更多,我建议您尝试在 Webots 模拟器和 ROS2 上运行它。
参考
如果你想了解更多细节,你可以阅读《概率机器人学》一书:
https://mitpress.mit.edu/books/probabilistic-robotics
还有下面这张纸:
https://www.mdpi.com/1424-8220/21/2/438 *
通往数据科学的道路
数据科学的重要技术和教育方面

原始图像来源:【09d94fd.png(974×330)】(insightextractor.com)
数据科学被预测为 21 世纪最有希望的 T4 工作。因此,它应该会吸引许多试图改变职业的专业人士。对于这些人来说,提前知道作为数据科学家的关键要求是什么,以及什么可以帮助你成为成功的数据科学家,这可能很重要。在本文中,我们将解决其中的一些问题。更具体地说,我们将试图理解数据科学家的主要技术和教育特征,以及他们与其他类型的开发人员的区别。此外,我们将尝试通过这些功能如何影响数据科学家和专业人士的薪酬水平来衡量它们的重要性。
为了回答这些问题,我们将使用来自堆栈溢出调查的数据。这是世界范围内最大的调查之一,主要涉及编码人员。这已经是该地区的一项既定的传统举措,已经运行了近十年。在 2020 年,这项调查涉及近 65,000 名受访者。
本文使用的所有数据和代码都可以在我的 GitHub 资源库中找到。
前景和机遇
尽管被认为是一个有前途的职业,但从 2017 年到 2020 年,我们在开发人员样本中的数据科学家比例下降了 1.87 个百分点。即使差别不是很大,但也有统计学意义。有人可能会说,我们的样本不足以代表数据科学家的数量。但即便如此,如果数据科学真的是一个有前途的工作,我们预计会看到我们的估计数上升。

数据科学家相对频率的演变
如果阻止数据科学人数上升的原因是专业人员短缺,那么数据科学仍有可能是一个有前途的工作。从这个意义上说,我们仍然有越来越大的兴趣从雇主那里雇佣更多的数据科学家。不幸的是,由于数据科学家群体的失业率更高,这也无法得到证实。

比较数据科学家和其他开发人员之间失业率的演变
数据科学家的特点是什么?
为了更好地了解数据科学家专业人士,我们将仅使用 2020 年调查的数据。仅选择那些有效地以专业方式编写代码的受访者(初始样本的 77.54%),我们得出其中只有 7.83%是数据科学家。大多数受访者认为自己是更传统类型的开发者:全栈(56.22%)、后端(56.21%)和前端(37.19%)。由于回答者可以为他们的答案选择一个以上的选项,所有选项相对频率的总和大于 1。

显影剂类型分布
比较数据科学家和其他开发人员的教育水平,我们可以发现数据科学家通常比其他人有更高的教育水平。超过一半的其他开发人员止步于学士学位(50.96%),而 41.11%的数据科学家更进一步,获得了硕士学位,14.68%的人甚至更进一步,获得了博士学位。

按本科专业分列的受访者分布
大多数数据科学家和其他开发人员都宣称他们的本科专业是计算机科学、计算机工程或软件工程。尽管这两个群体都是最受欢迎的专业,但其他开发人员的差距为 13.46 个百分点。数据科学家群体中这一较低的比例由数学或统计学(12.35%对 2.81%)、自然科学(10.73%对 3.77%)和其他工程学科(11.93%对 9.01%)中较高的相对频率来补偿。

按本科专业分列的受访者分布
这里的关键点是,数据科学名称中的科学部分很重要。数据科学家的方法就像一种科学方法,我们首先有我们的初始假设,然后我们将做我们的研究来验证我们的假设。数据科学家通常研究更科学的领域,并进一步深入他们的学术结构。这些方面对于发展作为数据科学家工作所必需的数学和研究技能可能很重要。
通过比较两组开发人员在 2020 年使用最多的 10 项技术,我们发现了语言和其他技术的一些显著差异。

Top-10 在 2020 年与其他开发人员和数据科学家的技术合作
虽然 Python 是最受数据科学家开发人员欢迎的语言,但它在其他开发人员中仅获得第五名。另一个重要的区别是关于 R 编程语言。虽然它在数据科学家分布中排名第八,但对于其他开发者来说,它甚至没有进入前十。
杂项是更具体的技术层,也是可以观察到大多数差异的地方。首先,看起来分布更集中在数据科学家群体,反映了技术使用的某种一致性和专业化。在十大杂项技术中,我们可以确定两种重要的技术:机器学习技术(TensorFlow、Keras 和 Torch/Pytorch)和大数据技术(Apache Spark 和 Hadoop)。这两种类型的技术都没有进入其他开发者的前 10 名,强调了数据科学工作的特殊性。
技术和教育特征如何反映在薪酬中
为了了解上述技术和教育特征与薪酬水平之间的关系,我们将使用多元线性回归模型(普通最小二乘法— OLS)。通过将所有这些特征的模型与薪酬水平的对数进行拟合,我们可以测量特定特征的存在如何影响薪酬水平的百分比。
用来预测补偿的一些特征被认为是不重要的(它们的系数在统计意义上等于零)。我们在分析中忽略了这些特征,我们将只关注那些对解释补偿差异有重要意义的特征。

技术和教育特征对数据科学家薪酬水平决定的影响
从上面的有序条形图中,我们可以看出,拥有较高的教育水平对专业人士的薪酬贡献最大。根据我们的估计,拥有博士学位预计会使专业人士的薪酬增加 71.37%,而拥有专业学位和硕士学位则分别增加 52.60%和 32.31%。令人惊讶的是,宣布本科专业为更技术性的领域与薪酬呈负相关。申报信息系统、信息技术或系统管理专业的人少挣 47.62%,申报计算机科学、计算机工程或软件工程专业毕业的人少挣 47.02%。
从十种最受数据科学家欢迎的语言来看,只有 HTML/CSS 和 Bash/Shell/PowerShell 具有统计显著性,命令行语言的积极影响是增加了 25.35%的补偿,HTML/CSS 减少了 15.87%。其他不太流行的语言对薪酬产生了积极的影响,Scala 提高了 26.18%,Perl 提高了 27.10%,Objective-C 提高了 33.06%。尽管我们没有发现 Python 与补偿正相关,但这并不意味着这种语言对数据科学家来说不重要。可能的情况是,用 Python 编程正成为数据科学家工作的一项要求。因此,它无助于职业分化,因此不会提高预期的薪酬。
对于这些平台,我们确定了三种对确定补偿水平很重要的部署技术。Heroku 是唯一有负面影响的(22%)。Docker (13.00%)和 AWS (24.17%)都与薪酬有正相关关系。另一个令人惊讶的结果是,大多数流行的杂项技术对薪酬并不重要。只有 Keras 是重要的,但不是以积极的方式,在 2020 年使用该技术导致薪酬水平降低了 20.30%。
结论
我们无法确认数据科学仍然是一个有前途的职业。可能是因为我们有一个有偏见的样本,不能正确地代表数据科学家在开发人员中的比例。或者,与我们最初的假设相反,数据科学不再那么有前途,数据科学家的数量已经激增。
我们可以确定的是,与其他类型的开发人员相比,数据科学家有一些显著的不同。Python 是他们的主要语言,被大多数专业人士使用。还有其他针对 te 职业的技术,比如 TensorFlow,Torch/Pytorch,Spark。但即使这些工具很受欢迎,它们对于获得高于平均水平的薪酬并不重要。情况可能是这样的,它们就像是职业的一个要求,而不是为开发者提供任何竞争优势。
获得更高的教育水平才是真正重要的。大多数数据科学家都有硕士或博士学位。此外,拥有更高的教育水平对于获得高于平均水平的薪酬是最重要的。尽管这不是必要条件,但更多的学习可能会为数据科学专业的成功铺平道路。
美国患者电子病历的采用
NCI 提示调查数据分析
项目定义
项目概述
本研究的目的是利用美国国家癌症研究所健康信息国家趋势调查(HINTS) 的公开数据,试图了解哪些患者人口统计、健康和互联网/电子设备相关因素推动了美国电子病历(EMRs)的访问和使用。
问题陈述
2009 年的《经济和临床健康卫生信息技术(HITECH)法案》为美国医疗保健提供商(hcp)采用和促进患者使用电子病历制定了要求和激励措施1。然而,患者对电子病历的使用率似乎很低(< 50%),包括在慢性疾病状态下[2,3]。
控制患者使用 EMR 的一个因素是它们由 HCP 或其他来源(例如健康保险提供商)提供。了解哪些因素推动了电子病历的可用性,将有助于提高医护人员和健康计划管理者对患者获取电子病历的可能障碍的认识。它还将阐明 EMR 是否被适当地提供给那些最需要它们的人(例如,那些患有多种慢性疾病或不健康生活方式的人),或者那些可能不太需要它们,但更有可能使用它们的人(例如,更年轻、更健康、受过更好教育的患者,他们可能不需要密切监控他们的健康)。
了解与实际使用电子病历相关的因素也将表明最需要访问或轻松携带其病历的患者(同样,患有多种慢性疾病或健康状况较差的患者,他们可能需要看多个 hcp 或可能需要更频繁地去医院或医生处就诊)是否在实际使用电子病历。
对于这两种结果,可用性和采用是否随着时间的推移而变化,以及在新冠肺炎疫情开始后是否有明显的变化将被评估为模型的一部分。
解决这个问题的策略将是应用多元逻辑回归模型来预测这两个结果。预期的结果是,这些模型将为每个预测变量产生易于解释的系数,允许计算优势比以确定每个变量的影响强度。这些系数也可以用来预测每个结果的可能性,从而确定哪些患者更有可能或更不可能接受和使用电子病历。
最后,将确定 EMR 使用和访问的预测概率≥ 80%和≤ 20%之间患病率差异最大的变量。
韵律学
最优模型将是最大化精确度和召回率的模型。这将倾向于最大限度地减少假阳性和假阴性,同时避免使用准确性的陷阱,这可能会在结果类别不平衡的情况下产生误导。
这些指标定义如下:
精度=真阳性/(真阳性+假阳性)
召回=真 _ 正/(真 _ 正+假 _ 负)
本项目中使用的自动递归特征消除( RFECV )的 scikit-learn 实现采用了这些的组合,F1 分数:
F1_Score = 2 精度召回/(精度+召回)
在二元变量的情况下,比值比描述了变量存在与不存在时结果的比值。对于连续变量,它是预测值中单位变化的结果的概率。在逻辑回归中,系数为β的预测值的优势比是:
Odds_Ratio = exp(beta)
分析
为了进行这项分析,我们需要有关电子病历可用性和使用的数据,以及有关患者特征的数据,如人口统计、健康状况、病史和互联网/电子设备(e-device)访问,这些数据可能会影响电子病历对患者的可用性及其使用。
这一领域的大多数公开数据集都是由美国政府管理的。本研究使用的资料来源于健康信息国家趋势调查(提示)。这是由国家癌症研究所进行的年度调查,目的是:
(收集)关于美国公众对癌症和健康相关信息的知识、态度和使用的全国代表性数据。提示数据用于监测快速发展的健康传播和健康信息技术领域的变化,并在不同人群中创建更有效的健康传播策略。
数据探索和可视化
完整的探索性分析和从中导出的数据集可以在 Jupyter 笔记本“DSND _ 最终 _ 探索. ipynb”的 Github 存储库中找到。
方法
许多提示数据是癌症特异性的;然而,有几个人口统计变量(例如,年龄、种族、性别、地理区域、收入、地理区域)。还有几个领域与互联网和电子设备的使用有关,也与健康状况和获得医疗保健资源有关。还有一些特定于电子病历的可用性和使用的字段。该调查旨在具有全国代表性。
截至 2020 年的多年数据均可获得。每年的调查由一个周期数命名,在一月和四月之间进行。2020 年调查(周期 4)与新冠肺炎疫情的《世卫组织宣言》( 2020 年 3 月 11 日)部分重叠,并且有一个字段表示是否在此日期之前或之后收到回复。
为了评估电子病历的采用是否随着时间的推移而发展,以及在大流行后的时间段(公认是有限的)是否有可察觉的变化,还检索了前两年的数据(周期 2 和周期 3)。
数据集包含 438 到 731 列(来自调查问题)和 3504 到 5438 行(每一行都是唯一的调查回答)。第 3 周期包含更多的回答,因为它包括一个实验,其中允许更多的回答者选择在网上完成调查,而不是在纸上。正如 HINTS 的“网络试点结果报告”(可在 Github 知识库中获得)中所讨论的,那些被随机分配到网络调查中的人与那些在纸上完成调查的人在几个人口统计数据(性别、年龄、健康状况、教育)上有很大不同。因此,第 3 周期的网络响应数据被删除。剩下 4573 个回答。
在以电子形式进行汇编之前,调查回答已经由 HINTS 工作人员进行了预先筛选,并且只有那些至少完成 50%的回答才被包括在内。除了每个允许回答的代码之外,每个字段还可以包含描述数据丢失原因的代码(例如,不适当的回答、错误回答的问题、省略的回答)。这些代码是:
- -1:缺少“有效的”。不应填写该字段,因为前面的字段已标记了一个条目,使该字段不适用于此回应者。
- -2:填写不当。根据以前的回答,该字段应该为空,但回答者给出了答案。
- -4:难以辨认或不符合要求。回答无法阅读,或者远远超出问题的预期范围(例如,身高 11 英尺,年龄> 105 岁)。
- -5:选择的答案多于问题的合适答案。
- -6:对缺失“过滤器”问题的跟进中缺失值。应该触发回答者回答该问题的替代问题没有得到回答,该问题也没有得到回答。
- -9:失踪/未查明。这个问题应该得到回答,但却没有。
如下所述,这些丢失数据代码是在特定字段的基础上处理的。
虽然大多数调查问题在各个周期中都很常见,但并非所有问题都是如此。提示数据集包括描述每个调查问题并列出可能的回答及其频率的代码本。使用代码簿,对可用变量的相关性和可用数据量进行了预先筛选。此外,与获取和使用电子病历的关联无法区分为因果相关的变量被删除。然后对相关变量进行协调,并保留所有三个周期共有的变量(或可以重新配置以匹配各周期的变量)。代码簿和方法报告可以在 Github 存储库中获得。
结果
初步筛选使用提示码书确定了 59 个变量被认为是相关的,并在所有三年的数据。这些变量的列表可以在“提示-变量. ods”文件的“数据”文件夹中的中找到。合并后的数据集包含 11942 条记录。
合并的数据被分成 70%/30%的训练集和测试集。然后对训练集进行探索性可视化和单变量统计分析,以识别多变量机器学习模型的潜在特征。使用 Kruskal-Wallis 检验对连续变量进行单变量分析,使用卡方检验对分类变量的 n 乘 m 列联表进行单变量分析。双侧 p 值<为 0.05 的变量被考虑纳入多变量模型。
单变量可视化
由于变量数量巨大,因此显示了代表性的单变量图。其余部分可在笔记本“DSND _ 最终 _ 探索. ipynb”中查看。
代表性人口统计预测值的分布:年龄、性别、种族、教育:

图一。年龄分布

图二。性别分布

图 3。种族分布

图 4。教育分布
年龄呈相对正态分布,高峰在 50-64 岁。大多数受访者认为自己是白人和女性,最常见的教育水平是大学学历。
结果变量:

图 5。电子病历访问结果变量的分布

图 6。使用结果变量电子病历分布
HCP 或保险公司向大多数受访者提供了电子病历访问权限。然而,大多数人在过去的 12 个月里没有使用过电子病历。在有过的人中,最常见的频率是 1-2 次。
一个变量“phq4”根据这一分析进行了修改。此变量代表 PHQ-4 心理压力评分。范围从 0 到 12。它在这个尺度上的分布如图 7 所示。

图 7。基于所有可能值的 PHQ-4 评分分布
大部分类别都比较稀疏。此外,通常根据范围进行评分,如下所示[4]:
- 0-2 分:无痛苦
- 3-5 分:轻度痛苦
- 6-8 分:中度痛苦
- 9-12 分:极度痛苦
这一变量被重新配置为代表上述范围的四个类别。修改后的变量如图 8 所示。这仍然是稀疏的,但稍微少一些,更医学相关。

图 8。PHQ-4 类别的分布
多变量可视化和单变量统计
结果变量中缺少条目
在检查潜在预测因素和结果变量之间的个别关系之前,需要确定处理结果变量中省略响应(缺失数据代码-9)的策略。初步猜测是,没有反应可能表明不感兴趣或不参与。
对于 EMR 访问结果,回答“不知道”的患者和跳过该问题的患者的特征可能相似。如果是这样,丢失的条目可以与“不知道”的条目合并。如果它们不相似,缺少的条目需要作为一个单独的类别保留,或者被删除。
为了评估相似性,对回答“不知道”和没有回答的人的关键人口统计学特征进行了比较。
分析的人口统计学变量有:
- “阶层”(人口普查地区的少数民族地位)
- “highspanli”(英语说得不太熟练的人普遍存在)
- “使用互联网”(互联网使用)
- “健康保险”(任何形式的保险)
- “自我性别”(性别)
- “年龄组”
- “educa”(教育级团体)
- “raceethn5”(种族/民族群体)
- “hhinc”(家庭收入组)
卡方列联表分析用于评估每个人口统计学变量和结果变量之间的关系。零假设是选择“不知道”(代码 3)作为结果变量的受访者与未回答(代码 9)的受访者具有相同的人口统计学特征。另一个假设是,这些群体之间的人口特征是不同的。对于 p 值< 0.05 的测试,无效假设将被拒绝。
分析结果揭示了 p 值< 0.05 for all variables except “stratum” (p = 0.09) and “highspanli” (p = 0.80). This indicates a lack of demographic similarity between the “don’t know” and omitted-answer respondents. Based on these criteria, the two responses can’t be combined. The differences appear to be almost completely driven by a higher frequency of omitted answers (code -9) to the demographic questions for those who also omitted an answer to the EMR access ( “offeredaccesseither”) outcome question. This could indicate a general lack of interest in the survey, or general concerns about providing information. Because these surveys are likely to have a large number of fields with missing data, requiring further assumptions to handle, the missing (-9) entries for this variable were dropped.
For the EMR use variable, categories relate to frequency of use and there isn’t a “don’t know” category. A missing answer could indicate that the respondent doesn’t use an EMR, doesn’t remember whether they have used it, or doesn’t want to respond. Thus, it would be difficult to determine which category to merge the missing-answer category with. Also, since the categories denote increasing frequency, keeping the missing code as a separate category creates a disruption in that order. For these reasons, and since the missing data are relatively infrequent, surveys with missing responses to this question will also be dropped.
Dropping these entries reduces the dataset to 11578 records.
连续预测因子与结果的单变量关系
有三个连续的变量:身体质量指数(身体质量指数),每周平均锻炼时间,每周平均饮酒量。使用盒须图进行图形探索。由于这些变量显示出单变量分布的偏斜(参见“DSND 最终探索”的完整结果),与结果变量的关系用非参数 Kruskal-Wallis 检验进行了检验。
图 9 和图 10 显示了每个连续变量对每个结果变量的盒须图。

图 9。连续变量与电子病历访问变量的关系

图 10。连续变量和 EMR 使用变量之间的关系
如果排除明显不一致的数据,所有关系在 p < 0.05 level; in fact, all except BMI vs. EMR use ( p = 0.043) had p-values < 0.001. However, as the plots show, all three predictor variables have many outliers.
These fields 处都是显著的,因为根据方法论,这些数据将被标记为代码-4。
例如,身体质量指数的值涵盖了一个极端的范围,但在生理上是可能的。平均运动量的数值也并非不合理(即不超过一周的分钟数)。平均饮酒量的数值也很大,但并非不可能,例如 120 杯/周就是 17 杯/天。
因此,有可能极端只是代表极端的行为或生理极端。由于这个原因,离群值没有被丢弃。相反,通过将这些变量转化为范畴,它们的影响被削弱了。
在此之前,缺失的条目(所有负代码)被删除,因为它们不能被放入分位数。这样做留下了 9803 个条目,主要是因为 940 个省略了对“每周平均饮酒量”问题的回答。
身体质量指数被分为四分位数,分界点如下:
第一个四分位数:21.6 千克/平方米
第二个四分位数:25.6 千克/平方米
第三个四分位数:29.4 千克/平方米
第四个四分位数:37.8 千克/平方米
另外两个变量有大量的零值条目。分位数不太适合这些情况,因为箱的边缘是不唯一的。对于这些变量,使用生理临界值代替。
对于“weeklyminutesmoderateexercise”(分钟/周的适度锻炼),疾病控制中心(CDC)建议成人每周至少 150 分钟[5]。基于此建议的类别有:
- 0 分钟/周(最常观察到的值)
-
0 到< 50% recommended (0 — <75 mins)
- ≥ 50% to < 100% recommended (75 — < 150 mins)
- ≥ 100 to < 150% recommended (150– 224 mins)
- ≥ 150% recommended (≥ 225 mins)
For “avgdrinksperweek” (average number of weekly alcoholic drinks), CDC recommendations [6] were again used. Those recommendations define heavy drinking as ≥ 8 drinks/week for women and ≥ 15 drinks/week for men [6]. For respondents who didn’t specify a gender, the mean value of ≥ 11.5 drinks/week was used. Cutoffs similar to those used for exercise were chosen:
- 0 drinks/week (the most frequent category)
-
0 到< 50% heavy drinking (M: 1–7; F 1–3; not specified: 1–5 drinks)
- ≥ 50% to < 100% heavy drinking (M: 8–14; F: 4–7; not specified: 6–11 drinks)
- ≥ 100 to < 150% heavy drinking (M: 15–22; F: 8–12; not specified: 12–17 drinks)
- ≥ 150% heavy drinking (M: ≥ 23; F: ≥ 13; not specified: ≥ 18 drinks)
分类预测因子与结果的单变量关系
用条形图研究了图形关系。使用卡方列联表方法进行统计分析。
最初的分析包括-9“省略回答”代码。
对于这两个结果变量,唯一具有非显著关系(卡方 p 值> 0.05)的预测因子是“eciguse”,表示主动电子烟消费。此变量已被删除。包括“eciguse”在内的代表性地块如下所示。整套资料可在“DSND _ 最终 _ 探索. ipynb”笔记本中找到。

图 11。年龄和结果变量之间的关系。两种结果的卡方 p 值< 0.001。

图 12。性别和结果变量之间的关系。两种结果的卡方 p 值< 0.001。

图 13。种族和结果变量之间的关系。两种结果的卡方 p 值< 0.001。

图 14。教育和结果变量之间的关系。两种结果的卡方 p 值< 0.001。

图 15。电子烟使用和结果变量之间的关系。EMR 访问和 EMR 使用的卡方 p 值分别为 0.214 和 0.882。
一般来说,除了性别、家庭收入和种族之外,9 代码很少见。对于这些变量,-9 条目被保留为单独的类别,因为它们可能表示不认同给定的选择(对于种族和性别),或者不愿意透露个人信息。
对于其余变量,通过检查其相对于其他响应类别的频率,并在删除该代码后重新运行分析,对该代码的影响进行了评估。这表明某些类别的显著 p 值可能是由于-9 标志的存在。在某些领域,这是因为其他反应很少。在其他情况下,这是由于-9 场中的小频率导致与预期值的相对较大的差异。为了避免这些未回答的问题影响最终的多变量模型,包含它们的行被删除。
删除缺失数据代码后,数据集缩减到 7818 个条目(训练集中 5490 个,测试集中 2328 个)。在该训练集中,以下预测因素不再与结果有显著关系(卡方检验 p > 0.05):
EMR 访问结果:
- “健康保险”(tricare 或其他军事保险)
- “退伍军人健康保险”
- “healthins_ihs”(仅限印度健康服务福利)
- “healthins_other”(上文未具体说明的其他保险)
- “medconditions_highbp”(曾被诊断患有高血压)
- “medconditions_heartcondition”(曾被诊断患有心脏病)
- “medconditions_lungdisease”(曾被诊断患有肺部疾病)
电子病历使用结果:
- “健康保险”(tricare 或其他军事保险)
- “退伍军人健康保险”
- “healthins_ihs”(仅限印度健康服务福利)
- “medconditions_lungdisease”(曾被诊断患有肺部疾病)
最后四个预测因子,对两个结果都不重要,被删除了。
此外,有几个与互联网接入方式和位置相关的变量,其中受访者可以做出多种选择(例如,通过手机、wifi、宽带、拨号接入;在家里、工作场所、公共场所使用互联网)。健康保险的类型也有多种选择。为了避免冗余和使用多个相关变量过度拟合的可能性,对这些变量进行了更仔细的检查,并进行了一些合并。
就互联网接入方式而言,存在大量重叠,一些受访者甚至报告同时使用宽带和拨号上网。所有的问题都是“是/否”,所以没有什么可以表明那些选择了多个选项的人最常使用哪种模式。鉴于此,只保留了宽带类别(以及基本互联网接入与无接入变量)。那些选择这个选项的人承认他们拥有基于家庭的、相对高速的互联网接入,并且对电脑足够了解,知道这就是他们所拥有的。因此,这一类别可能会区分更多和更少的互联网知识的受访者。
对于互联网接入地点,类别之间再次出现严重重叠,即使是那些受访者报告日常使用的地点。然而,较少的主要使用公共场所的受访者有其他机会访问互联网。这可能会限制他们使用电子病历的能力。因此,创建了一个访问位置类别“whruseinet_pubvother ”,与“从不”或“不适用”相比,表示“每天”或“有时”使用公共互联网,其他类别则被删除。
最后,对于保险,由于与结果的关系不显著,已经排除了几种可能的反应(见上文)。此外,删除了“healthins_other ”,因为它与电子病历的使用没有显著关系,并且与选择“是”的受访者相比,遗漏的回答更多。这种不平衡可能使其成为无效的预测器。
保险类别有一些重叠,因为一个病人可能有一级和二级保险。相对较少的受访者只有医疗补助,这被认为是穷人或残疾人的“安全网”保险。然而,将那些享受公共资助保险(医疗保险或医疗补助)的人与那些享受私人保险的人进行比较是很有趣的。这些和其余的保险类别被合并为一个变量“healthins _ pubpriv ”,表示没有医疗保险/医疗补助的私人/雇主提供的保险,没有私人或雇主发放的保险的医疗保险/医疗补助,没有或其他。
在这些操作后,数据集包含 41 个预测变量(38 个与 EMR 访问有关;41 到 EMR 使用)。该数据集用于开发多元逻辑回归模型。
方法学
数据预处理
数据被读入熊猫数据框。提示数据文件有 SAS、SPSS 和 STATA 格式。Pandas 的 SAS 导入不允许限制要读入的列集,而第 2 周期的 SPSS 文件似乎已损坏,无法加载。出于这些原因,使用了 STATA 文件。
59 个变量中有 55 个是分类变量。它们被从浮点转换成整数。
如上所述,第 3 周期包括一个实验,其中一部分受访者可以在网上完成调查。如前所述,这些回复被删除,因为这些回复的人口统计数据与纸质回复的人口统计数据存在显著差异。
根据这些数据集中的字段,创建了两个结果变量:
- EMR 可用性(变量名“offeredaccesseither”):HCP 或保险公司是否向患者提供了 EMR 访问权限(编码为“是”、“否”或“不知道”)。这是周期 2 的单个变量,但必须通过合并两个变量来创建(“offeredaccesshcp 2”):HCP 是否向受访者提供了访问权限?;以及“offeredaccessinsurer2”:保险公司是否向被调查者提供了访问权限?)用于第 3 和第 4 周期。
- EMR 使用(变量名“accessonlinerecord”):在过去 12 个月中,患者访问其 EMR 的频率(分为从“无”到“≥ 10”的 5 个类别)。这个变量在所有三个数据集中以相同的形式存在。
两个变量都是多类的,有一些稀疏的响应类别。初步分析是在它们保持原样的情况下进行的,如果多类分类较差,则计划将它们二值化。
此外,增加了一个名为“调查周期”的变量,以说明时间和大流行后时期的影响。这用 2 和 3 编码,代表那些周期,4 代表疫情之前的周期 4,5 代表疫情之后的周期 4。
然后合并三个周期的数据集,并评估缺失数据的频率。代码-1 和-2 在此分析中被忽略,因为它们代表回答者不应该回答的问题。大多数变量缺失≤ 2%的数据,只有“avgdrinksperweek”大于 10%,为 12.1%。基于此,没有变量因过度遗漏而被删除。
缺失数据通常通过删除来处理。例外情况如上所述。
履行
该项目是用 Python 实现的。在 Jupyter 笔记本“DSND _ 最终 _ 探索. ipynb”中进行数据处理、清理和初步分析。
使用这些测试的 SciPy 实现来执行 Kruskal-Wallis 和卡方列联表分析。
使用 scikit-learn 的逻辑回归分类器创建多变量逻辑回归模型。平衡类权重用于解释结果反应类别之间的不平衡。分类变量是一次性编码的,每个模型都有一个参考类别。每个模型都适合训练数据集,并在测试集上进行评估。
首先尝试了使用每个结果变量的所有可能反应的多类模型。如果由于类别不平衡,这些被证明是不充分的,则响应被二进制化,并且模型被重新拟合。在笔记本“DSND _ 最终 _ 分析. ipynb”中进行机器学习分析。
用 scikit-learn 的递归特征消除算法( RFE 和 RFECV )实现特征选择和减少。使用他们的网格搜索实现( GridSearchCV )进行模型调整。
最后,使用 scikit-learn 的分类度量实现来计算精确度、召回率和混淆矩阵。
精炼
从初步筛选中识别的所有特征开始,使用递归特征消除(RFE)完成特征选择和减少。首先,使用交叉验证 RFE(rfe cv)进行自动选择,通过 F1 评分进行模型评估。
使用交叉验证的网格搜索方法尝试了模型调整。网格搜索中使用的参数是逻辑回归正则化参数 C ,平衡与无类别加权,以及 RFECV 中使用的特征缩减步长。随后,通过使用具有一系列规定数量特征的手动 RFE 来寻找更简洁的模型。
通过比较训练集和测试集之间的精度和召回率来评估过度拟合,检查测试集性能的大幅下降。每个结果的最佳模型是最节省的模型,它最大限度地提高了精确度和召回率。
中间和最终模型解决方案将在下面讨论。
结果
下面概述的用于机器学习模型开发的完整过程和代码包含在笔记本“DSND _ 最终 _ 分析. ipynb”中。
模型评估和验证
EMR 访问模型
对 38 个分类变量进行一次性编码,并为每个变量提供一个参考类别,得到了 106 个潜在的预测特征。对所有三个结果(“是”、“否”和“不知道”)拟合具有自动 RFECV 修剪的多类逻辑回归模型产生了 64 个特征,在测试集上具有相对较差的精度(0.622)和召回率(0.555)。训练集参数相似(0.666 和 0.598),表明没有明显的过度拟合。
该模型使用网格搜索对以下参数进行了调整:
- 逻辑回归分类权重:平衡,无
- 逻辑回归正则化参数 C : 0.01,0.1,1,10,100
- 每次 RFECV 迭代中删除的特性数:1、3、5
调整产生了一个具有 81 个特征的模型,并且在精度(0.630)和召回率(0.567)上有最小的改进。最佳参数是 C = 0.01,平衡的类权重,以及每次 RFECV 迭代移除一个特征。再次,过度拟合并不明显(训练集精度 0.667,回忆 0.602)。
为了潜在地提高适合度,稀疏的“不知道”类别与“不”类别合并,创建了具有更好的类别平衡的二元结果。具有默认参数和 RFECV 修剪的二元模型具有 52 个特征,并且提高了精确度(0.702)和召回率(0.696)。训练集值为 0.728 和 0.719,因此不怀疑过度拟合。
使用上述相同参数空间的网格搜索调整产生了具有基本相同精度(0.706)和召回率(0.700)的 93 特征模型。训练集值是相似的(0.724 和 0.714)。最佳参数是 C = 0.01,平衡的类权重,以及每次 RFECV 迭代消除一个特征。
由于特征的增加和拟合的最小改进,网格搜索调整的模型被丢弃,并且初始模型被作为使用 RFE 手动特征减少的起点。从这个 52 个特征的模型中,拟合了 RFE 选择的具有 5 至 50 个参数(以 5 个参数为增量)的简化模型。图 16 显示了手动 RFE 调谐的结果。在 30 个特征上获得了最佳的准确率(0.705)和召回率(0.699),该模型被选为最终模型。同样,训练和测试集精度(0.723)和召回率(0.715)是相似的,表明没有明显的过度拟合。

图 16。作为 EMR 访问的逻辑回归模型中特征数量的函数的精度和召回率(作为二元结果)。
最终模型中包含的功能如下所示。
与获得 EMR 访问权限的可能性较高相关的特征:
人口统计&时间统计:
- “educa_4”:大学或高等教育(相对于所有其他级别)
- “selfgender_2”:女(对男还是没答案)
- “调查 _ 周期 _ 3”:2019 年(对比 2018 年、2020 年疫情会议前后)
- “调查 _ 周期 _4”:疫情之前的 2020 年(对比疫情之后的 2018 年、2019 年、2020 年)
- “调查 _ 周期 _5”:大流行后的 2020 年(与疫情前的 2018 年、2019 年、2020 年相比)
- “Age grpb _ 4”:65-74 岁(相对于所有其他年龄层;最高≥ 75)
健康相关:
- “固定提供者”:有固定的 HCP(相对于没有)
- “健康保险”:有某种形式的健康保险(相对于没有)
- “everhadcancer”:曾被诊断患有癌症(相对于从未)
- “qualitycare_1”:将 HCP 的护理质量评定为“优秀”(与“不去”、“非常好”、“好”、“一般”、“差”相对比)
- “qualitycare_2”:将 HCP 的护理质量评定为“非常好”(与“不要去”、“优秀”、“良好”、“一般”、“差”相对)
- “freqgoprovider_2”:每年见 HCP 两次(相对于 0、1、3、4、5–9 和≥ 10)
- “freqgoprovider_3”:每年查看 HCP 3 次(相对于 0、1、2、4、5–9 和≥ 10)
- “freqgoprovider_4”:每年查看 HCP 4 次(相对于 0、1、2、3、5–9 和≥ 10)
- “freqgoprovider_5”:每年查看 HCP 5–9 次(相对于 0、1、2、3、4 和≥ 10)
- “freqgoprovider_6”:每年查看 HCP ≥ 10 次(相对于 0、1、2、3、4 和 5–9)
电子设备&互联网相关:
- “使用互联网”:使用互联网浏览网页/收发电子邮件(与不使用互联网相比)
- “electronic_selfhealthinfo”:在过去 12 个月中使用过电子方式搜索与健康相关的信息(从未使用过)
- “whruseinet_pubvother_1”:在公共场所(如图书馆)“经常”或“有时”使用互联网(与从不或不使用互联网相对)
- “whruseinet_pubvother_2”:不要在公共场所(如图书馆)使用互联网(相对于经常/有时或不使用互联网)
- “tablethealthwellnessapps _ 1”:在平板电脑上安装健康/保健应用程序(相对于没有或不拥有平板电脑)
- “tablet_discussionshcp_1”:使用平板电脑作为与 hcp 讨论的辅助工具(相对于没有或不拥有平板电脑)
与获得 EMR 访问权限的可能性较低相关的特征:
人口统计&时间统计:
- “highspanli”:语言孤立(高流行率,不太精通英语的人)
- “raceethn5_4”:非西班牙裔亚洲人(相对于所有其他种族群体)
- “censdiv_6”:东南中心人口普查处(肯塔基州、田纳西州、密西西比州、阿拉巴马州;与所有其他部门相比)
- “hhinc_1”:最低类别的家庭收入(< $20k/yr; vs. all higher categories & not reported)
- “maritalstatus_6” : Single (vs. all other categories)
与健康相关:
- “healthins _ pubpriv _ 2”:公共保险(医疗保险/医疗补助),无雇主提供的保险(相对于私人/雇主提供或其他/无)
- “avg drinks _ cat _ 5”:≥150%的饮酒量 CDC 分类为重度饮酒(M ≥ 23,F≥13;其他:≥18;这是最高的类别;与所有较低类别相比)
- " ownabilitytakecarehealth_5 ":"一点也不"对自己照顾健康的能力有信心(相对于完全、非常、有点或有点信心)
图 17 显示了每个变量的优势,用优势比来衡量。在图中,1.0 处的分割线划分了与 EMR 访问相关的特征(绿色;比值比> 1.0)和那些与 EMR 使用无关的(红色;比值比< 1.0)。

图 17。与 EMR 访问相关的特征的优势比(作为二元变量)。
拥有任何类型的保险与获得 EMR 访问权限的关联最强,其次是护理评级为“优秀”。女性、较高的教育程度和年龄也很重要。唯一有显著影响的慢性疾病是癌症史,尽管更频繁地访问 HCP 与获得访问的可能性更高相关。使用互联网,以及出于健康相关目的使用 it 和电子设备也是预测因素。最后,2019 年至 2020 年(相对于 2018 年)的调查周期与 EMR 访问的增加相关,疫情会议前的 2020 年权重最高,其次是疫情会议后的 2020 年,然后是 2019 年,这表明存在时间效应,尽管可能不是线性的。
相比之下,对自己照顾自己健康的能力“完全没有信心”与没有获得电子病历联系最紧密,其次是非西班牙裔亚裔种族身份。处于最低收入阶层、单身、仅享有 Medicare 和/或 Medicaid,以及居住在东南中心人口普查区或语言隔离区,也与访问权限减少相关。没有慢性疾病出现,但大量饮酒也预示着减少访问。
图 18 描述了 10 个特征,它们在提供 EMR 访问的预测概率≥ 80%和≤ 20%之间具有最大的差异。红色柱表示在被提供 EMR 访问的预测概率≤ 20%的患者中更普遍的特征,而绿色柱表示在预测概率≥ 80%的患者中更普遍的特征。

图 18。被提供 EMR 访问的概率≤ 20%和≥ 80%的患者之间患病率差异最大的特征。
前 10 个变量中在低概率群体中更普遍的唯一变量是最低家庭收入层($20,000/年)。
在高概率组中,患者更可能是女性,并且至少具有大学学位。从医学上讲,他们更有可能定期接受 HCP 检查,并对 HCP 的医疗质量给予最高评级(优秀)。其余变量与互联网接入和使用有关:他们更有可能使用互联网,但不太可能通过公共接入(如图书馆)这样做。他们更可能使用互联网和平板电脑等设备来查找健康信息,监控自己的健康状况,并与他们的 HCP 进行讨论。
电子病历使用模型
用一键编码扩展 41 个分类变量,并省去一个参考类别,得到 109 个潜在特征,用于预测在过去 12 个月内使用电子病历的可能性。类别包括“无”(包括无权访问 EMR 的人)、1-2 次、3-5 次、6-9 次和≥ 10 次。
再次,多类逻辑回归模型被用来预测所有五种可能的结果。该结果的初始 RFECV +逻辑回归模型有 82 个特征和相当的精确度(0.607),但召回率很低(0.471)。训练集值是相似的(0.627 和 0.512)。
再次执行网格搜索调优。因为这个结果的类明显不平衡,所以没有尝试类权重=无。网格是:
- 逻辑回归正则化参数 C : 1x10-5,1x10-4,1x10^-3,0.01,0.1,1,10,100
- 每次 RFECV 迭代中删除的特性数:1、3、5
这种网格搜索给出了 99 个参数的模型,具有降低的精度(0.551)和略微改进但仍然差的召回率(0.517)。最佳参数是 C = 1x10^-5,每次 RFECV 迭代删除一个特征。过度拟合并不明显:训练集精度为 0.554,召回率为 0.521。
与 EMR 访问结果一样,罪魁祸首被认为是导致不平衡预测的不太频繁的类别。因此,一个比较“没有”和“任何”使用电子病历的二元结果变量产生了。
这个结果的 RFECV +逻辑回归模型显示使用 62 个特征大大提高了精确度(0.741)和召回率(0.724)。训练集值(0.752 和 0.740)没有表明过度拟合。
使用与上面相同的参数网格的网格搜索调优将特征空间减少到 54,但是具有较低的精度(0.709)和召回率(0.689)。最佳参数与多级模型相同。训练集性能(精度 0.714,召回 0.703)没有表明过度拟合。
基于这些结果,62 特征模型被用作手动 RFE 调整的起点。创建了包含 5 至 60 个特征(同样以 5 个特征为增量)的模型,并比较了它们的得分。
图 19 显示了手动 RFE 调谐的结果。用 45 个和 50 个特征获得了最佳精度(0.742)和召回率(0.724);具有 45 个特征的模型被选为最节俭的。如前所述,对于训练集,观察到精度(0.749)和召回率(0.736)的最小差异,降低了过度拟合的可能性。

图 19。作为电子病历使用的逻辑回归模型中特征数量的函数的精确度和召回率(作为二元结果)。
下面列出了为此型号选择的功能。
使用电子病历可能性较高的特征:
人口统计&时间统计:
- “educa_2”:高中教育(相对于所有其他级别;最低/参考是
- “educa_3”:一些大学教育(相对于所有其他级别)
- “educa_4”:大学或高等教育(相对于所有其他级别)
- “selfgender_2”:女(对男还是没答案)
- “censdiv_9”:太平洋人口普查司(CA,OR,WA,AK,HI;与所有其他部门相比)
- “调查 _ 周期 _ 3”:2019 年(对比 2018 年、2020 年疫情会议前后)
- “调查 _ 周期 _4”:疫情之前的 2020 年(对比疫情之后的 2018 年、2019 年、2020 年)
- “调查 _ 周期 _5”:大流行后的 2020 年(与疫情前的 2018 年、2019 年、2020 年相比)
健康相关:
- “固定提供者”:有固定的 HCP(相对于没有)
- “健康保险”:有某种形式的健康保险(相对于没有)
- “medconditions_diabetes”:曾被诊断患有糖尿病(相对于从未)
- “everhadcancer”:曾被诊断患有癌症(相对于从未)
- “qualitycare_1”:将 HCP 的护理质量评定为“优秀”(与“不去”、“非常好”、“好”、“一般”、“差”相对比)
- “qualitycare_2”:将 HCP 的护理质量评定为“非常好”(与“不要去”、“优秀”、“良好”、“一般”、“差”相对)
- “qualitycare_3”:将 HCP 的护理质量评定为“好”(与“不要去”、“优秀”、“非常好”、“一般”、“差”相对比)
- “qualitycare_4”:对 HCP 的护理质量进行“一般”评级(与“不要去”、“优秀”、“非常好”、“好”、“差”相对)
- “qualitycare_5”:将 HCP 的护理质量评定为“差”(与“不要去”、“优秀”、“非常好”、“良好”、“一般”相比)
- “freqgoprovider_3”:每年查看 HCP 3 次(相对于 0、1、2、4、5–9 和≥ 10)
- “freqgoprovider_4”:每年查看 HCP 4 次(相对于 0、1、2、3、5–9 和≥ 10)
- “freqgoprovider_5”:每年查看 HCP 5–9 次(相对于 0、1、2、3、4 和≥ 10)
- “freqgoprovider_6”:每年查看 HCP ≥ 10 次(相对于 0、1、2、3、4 和 5–9)
- “smokestat_2”:以前吸烟者(与现在相比,从不吸烟)
- “smokestat_3”:从不吸烟(对比现在和以前)
电子设备&互联网相关:
- “使用互联网”:使用互联网浏览网页/收发电子邮件(与不使用互联网相比)
- “electronic_selfhealthinfo”:在过去 12 个月中使用过电子方式搜索与健康相关的信息(从未使用过)
- “intrsn_visitedsocnet”:使用互联网访问社交网络(相对于没有或不浏览)
- “whruseinet_pubvother_1”:在公共场所(如图书馆)“经常”或“有时”使用互联网(与从不或不使用互联网相对)
- “whruseinet_pubvother_2”:不要在公共场所(如图书馆)使用互联网(相对于经常/有时或不使用互联网)
- “tablethealthwellnessapps _ 1”:在平板电脑上安装健康/保健应用程序(相对于没有或不拥有平板电脑)
- “tablet_discussionshcp_1”:使用平板电脑作为与 hcp 讨论的辅助工具(相对于没有或不拥有平板电脑)
- “havedevice_cat_5”:拥有多个电子设备(手机、普通电话、平板电脑;与没有或其中之一相比)
- “internet_broadbnd_1”:通过宽带连接访问互联网(相对于不要或没有互联网)
EMR 使用可能性较低的特征:
人口统计&时间统计:
- “highspanli”:语言孤立(高流行率,不太精通英语的人)
- “raceethn5_3”:西班牙裔(相对于所有其他种族群体)
- “censdiv_2”:中大西洋人口普查局(新泽西州、纽约州、宾夕法尼亚州;与所有其他部门相比)
- “censdiv_6”:东、南、中人口普查局(肯塔基州、田纳西州、密西西比州、阿拉巴马州;与所有其他部门相比)
- “censdiv_8”:山区人口普查司(亚利桑那州、CO、ID、NM、MT、UT、NV、WY;与所有其他部门相比)
- “nchsurcode2013_4”:大都市:小型地铁城市与农村分类(6 个分类中的第 4 个最小分类;与所有其他分类相比)
- " nchsurcode2013_5 ":非大都市:小城市与农村分类(6 个分类中的第 5 个最小分类;与所有其他分类相比)
- “hhinc_1”:最低类别的家庭收入(< $20k/yr; vs. all higher categories & not reported)
- “hhinc_2” : Household income in second-lowest category ($20–34.99k/yr; vs. all other categories & not reported)
- “maritalstatus_5” : Separated (vs. all other categories)
健康相关:
- “phq4_cat_4”:基于 PHQ-4 评分的严重心理困扰(相对于无、轻度或中度)
- “avg drinks _ cat _ 4”:≥100%至< 150% of number drinks CDC classifies as heavy drinking (M: 15–22, F: 8–12; missing: 12–17; this is second-highest category; vs. other categories)
- “ownabilitytakecarehealth_5” : “Not at all” confident in own ability to take care of health (vs. completely, very, somewhat, or a little confident)
Odds ratios for each variable are shown in Figure 20, where again green bars (odds ratio > 1.0)与 EMR 使用相关,而红色(比值比< 1.0)则不相关。

图 20。EMR 使用相关特征的优势比(作为二元变量)。
在这里,将护理评为“优秀”与使用过电子病历联系最紧密,其次是获得了大学学位或更高学位。HCP 保健评级类别与电子病历的使用有不同程度的关联(相对于默认的无评级类别)。投保和女性,定期 HCP 和更高的 HCP 访问频率都再次出现,癌症史也是如此。另一种慢性疾病,糖尿病,也是一个预测因素,同时目前不吸烟也包括在内。调查周期的顺序与 EMR 访问的顺序相同。受教育程度较低也影响较小(与没有高中文凭相比)。电子设备和互联网相关因素与电子病历访问相关因素类似。最后,居住在太平洋人口普查局预示着使用电子病历的可能性更高。
同样类似于 EMR 访问的模型,居住在东南中心人口普查区和语言隔离区与没有使用过 EMR 最相关。家庭收入低,自我照顾能力差,饮酒量大。EMR 访问模型中不存在的其他人口普查分区(山区和大西洋中部)预测使用的可能性较低,居住在更多农村地区、被隔离以及 PHQ-4 评分与严重心理困扰一致的情况也是如此。
图 21 显示了使用 EMR 的预测概率≥ 80%和≤ 20%之间存在最大差异的 10 个特征。红色柱再次表明预测概率≤ 20%的人群患病率较高,绿色柱表明使用 EMR 的预测概率≥ 80%。

图 21。使用 EMR 的概率≤ 20%和≥ 80%的患者之间患病率差异最大的特征。
所有前 10 个特征在高概率群体中更普遍。与 EMR 访问模式不同,不存在性别差异。同样,这些患者更有可能至少拥有大学学位。从医学上讲,他们更有可能患有常规 HCP。同样,其余变量与互联网接入和使用有关:他们更有可能使用互联网和宽带互联网接入。他们不太可能通过公共资源(如图书馆)访问互联网。他们更有可能使用互联网访问社交网站。他们倾向于使用多种便携式电子设备,在网上查找健康信息,使用桌面应用程序监控他们的健康状况,并使用平板电脑与他们的 HCP 进行讨论。
正当理由;辩解
机器学习模型能够使用公开的美国政府数据集的数据来识别与 EMR 访问相关的特征。
选择逻辑回归而不是其他机器学习模型,因为该研究的目标是阐明每个预测变量的影响。逻辑回归为每个变量提供了易于解释的系数和优势比,以及易于部署的最终模型,例如在电子表格中。
两种结果的初始多类模型显示出较差的精确度和召回率,通过参数网格搜索的调整没有改善。这是由于类别不平衡,一些结果类别很少有响应,导致这些类别不太适合。因此,多重结果被合并为“是”/“否”二元变量,从而减少了类别不平衡。
将两种结果二分法极大地提高了精确度和召回率。虽然这导致结果中粒度的一些损失(例如,无法区分更多使用 EMR 和更少使用 EMR 的人的特征),但是多类模型的不良拟合使得它们在进行预测时用处更小。
网格搜索调整并没有提高二元模型的拟合度。在逻辑回归中,正则化参数 C 是唯一可调整的模型变量,默认值 1.0 提供了比通过网格搜索获得的较低值更好或更好的拟合。这可能是因为调查响应的数量(训练集中有 5490 个,测试集中有 2328 个)远远大于特征的数量(106 & 109 个),因此过度拟合不是主要问题。所有模型的训练集和测试集之间在精度和召回率方面的最小观察差异证实了不存在过度拟合。
此外,平衡的类权重被用于初始模型,并且比没有类权重表现得更好。这可能表明平衡权重有助于补偿任何结果类别的不平衡。
最后,调整了 RFECV 删除的默认特性数量。每次迭代删除一个特性比删除三个或五个要好,这表明模型比大的变化更能容忍小的变化。
由于大量的预测变量(106 个用于 EMR 访问,109 个用于 EMR 使用),递归特征消除被用于修剪特征空间。使用 F1 分数的自动特征修剪能够将二进制模型的特征数量减少到 52 和 62 个特征,以供 EMR 访问和使用。然而,通过从这些模型开始并使用手动 RFE,特征空间可以分别减少到 30 和 45 个特征,而不会损失精度和召回率。这些更简约的模型被选为最终模型,用于确定与电子病历访问和使用最相关的功能。
更有可能由 HCP 或保险公司提供电子病历的调查受访者往往是受过良好教育的女性,并且更多地使用电子资源获取健康信息。这些特征可能表明向那些最有可能使用 EMR 的人提供 EMR 访问的一些偏向。应该进一步研究这种可能性,以提高获得电子病历的平等性。
他们也倾向于更频繁地拜访他们的医护专业人员,并且年龄较大,这可能表明他们有更复杂的医疗需求。就慢性疾病而言,只有患过癌症的人预测获得治疗的可能性更高。是否有必要更积极地为患有其他慢性疾病的患者提供治疗,应进一步探讨。
最后,有一个时间效应,随着调查周期的增加,访问增加。然而,大流行后的 2020 年变量的影响小于疫情 2020 年前的变量,因此在可获得数据的大流行后时期(诚然有限), EMR 的可用性没有明显提高。
相反,那些不太可能获得 EMR 访问权限的人处于最低的教育和年收入层,更可能生活在东南中心人口普查区和语言隔离区,倾向于单身,更可能参加公共医疗保险,酗酒,对自己管理医疗保健的能力信心非常低。这些变量中的大多数表明,患者通常不太可能获得资源,包括医疗保健。这些患者可能与他们的医护人员互动较少,或者可能被他们的医护人员预判为 EMR 访问的不良候选人。语言隔离表明他们可能英语说得不好,无法与医护人员很好地沟通。这些患者可能会从教育计划中受益,最好是用他们的母语,宣传电子病历的价值并提供使用说明。医护专业人员也可能不知道关于这些患者的无意识偏见,并可能受益于激励措施,以扩大 EMR 对未得到充分服务的患者的访问。
同样,那些更有可能使用电子病历的人也往往是女性,受教育程度更高,更经常访问他们的医疗保健中心,更经常使用电子设备和健康相关信息的消费者。就慢性疾病而言,糖尿病和癌症病史都预示着 EMR 使用的增加。
时间效应与 EMR 访问相同,随着每个调查周期的使用增加,但大流行后时期的影响不如疫情 2020 年前。
同样,看起来更多的电子和健康知识患者更倾向于使用电子病历。相应地,那些有较高 HCP 访问频率和慢性疾病的人也更有可能使用电子病历。这些患者可能有复杂的病史和多个医护专业人员,如果他们的信息以电子形式更便于携带,并且能够访问他们的数据以便与医护专业人员或家庭成员讨论,他们将从中受益。
与那些不太可能获得电子病历的人类似,那些被预测不太可能使用电子病历的人往往属于收入最低的类别,居住在东南中心人口普查区和语言孤立的地区,对他们管理自己护理的能力评价很差,并且往往是酗酒者。这些相似性可能是由于那些未被提供 EMR 访问的人也不会使用 EMR。
此外,这些患者往往来自更多的农村地区,处于第二低收入阶层,被认为是西班牙裔,并符合 PHQ-4 标准的严重心理困扰。这些指标再次确定了一个高风险患者群体,他们可能较少获得资源,特别是医疗保健。严重的心理压力和对健康相关能力的低信心可能表明那些需要更多支持/援助来管理其健康事务的人。如上所述,这些患者可能受益于适当的语言,有针对性的宣传和教育,以强调电子病历的可用性和好处。
结论
反射
在这项分析中,来自国家癌症研究所提示调查的三年数据被用于分析与访问和使用电子病历相关的患者属性。
经过初步筛选和分析后,为这两个结果变量建立了多变量逻辑回归模型。由于两个变量中的多个结果类别的稀疏性,多类别模型显示出较差的预测性能,如通过精确度和召回率测量的。将结果二分法导致了更好的类别平衡,提高了精确度和召回率。
预测 EMR 访问的最终模型有 30 个特征,并在测试集上产生了 0.705 和 0.699 的精确度和召回率。
与 EMR 访问的预测可能性增加相关的人口统计学特征是女性、高等教育和中等年龄(65-74 岁)。还有年份(调查周期)和新冠肺炎疫情的影响。与进入可能性降低相关的人口统计学特征是单身、低收入、非西班牙裔亚裔、语言孤立和居住在东南中心人口普查区。
与预测的 EMR 访问增加相关的健康相关特征是:定期进行 HCP,其护理评级更高,每年看两次或更多次 HCP,有健康保险,有癌症史。这一类别中与获得可能性较小相关的特征是单独的公共保险(Medicare 和/或 Medicaid)、酗酒以及对个人管理健康事务的能力缺乏信心。
预测接入可能性增加的电子设备和互联网相关因素是互联网使用、出于健康相关目的使用电子设备和网络的增加、访问社交网站以及公共互联网接入的使用程度。在这一类别中,没有变量预测减少访问。
每个特征的强度通过它的优势比来检验。与增加可及性最密切相关的特征是拥有健康保险和对个人护理的高度评价。与获得机会的可能性降低最密切相关的是对自己管理健康事务的能力缺乏信心,以及非西班牙裔亚裔种族。
最后,评估了那些被提供 EMR 访问的预测可能性≥ 80%和≤ 20%之间患病率差异最大的特征。在前十名中,低收入在低可能性组中更普遍,而女性性别、较高教育水平、定期 HCP、较高 HCP 评级以及与互联网/设备使用增加和用于健康相关目的相关的几个变量在高可能性组中更普遍。
预测电子病历使用的最终模型有 45 个特征,精度为 0.742,召回率为 0.724。
与 EMR 使用的预测可能性增加相关的人口统计学特征为女性、任何教育程度≥高中以及居住在太平洋地区(加州、俄勒冈州、华盛顿州、阿拉斯加州、HI)人口普查部门。再次有一个年度(调查周期)和新冠肺炎疫情的影响。与使用可能性降低相关的人口统计学特征包括分居、低收入、西班牙裔、语言孤立、居住在大西洋中部、东南中部或山区人口普查区,以及居住在更偏远的农村地区。
与预测的 EMR 使用增加相关的健康相关特征是定期进行 HCP 检查,每年检查 HCP ≥ 3 次,任何护理评级(除了无/不要访问 HCP),有健康保险,有糖尿病或癌症史,以及目前不吸烟。这一类别中与使用可能性较小相关的特征是酗酒、严重的心理困扰(PHQ-4 评分最高)以及对自己管理健康事务的能力缺乏信心。
电子设备和互联网相关因素预测使用电子病历的可能性增加,这些因素包括互联网使用、宽带接入、拥有多个电子设备、出于健康相关目的使用电子设备和网络的增加以及公共互联网接入的使用程度。如前所述,这一类别中没有变量预测减少使用。
检查优势比,一个“优秀”的 HCP 护理等级和≥大学学位与增加使用电子病历的可能性最强相关。那些与访问可能性下降最密切相关的人居住在东南中心人口普查处和语言隔离区。
最后,评估了使用 EMR 的预测可能性≥ 80%和≤ 20%的人群之间的患病率差异。前十名在那些预测更可能使用电子病历的人群中更常见。除了教育程度≥大学学位和定期 HCP 外,所有这些都与出于一般和健康相关目的的互联网和电子设备接入/使用增加有关。
我发现这个项目的一个有趣的方面是,这样一个庞大的、功能丰富的数据集是公开可用的,并且这些数据是每年收集一次的。在开始为这个项目寻找合适的数据集之前,我从未听说过提示调查。对于未来的项目,我一定会探索像这样的其他政府管理的数据源。我也很好奇这些数据是否用于卫生政策决策。提示页面声明收集数据的目的是:
调查研究人员正在使用这些数据来了解 18 岁及以上的成年人如何使用不同的沟通渠道,包括互联网,来为他们自己和他们所爱的人获取重要的健康信息。项目规划者正在使用这些数据来克服跨人群使用健康信息的障碍,并获得他们需要的数据来创建更有效的沟通策略。最后,社会科学家正在使用这些数据来完善他们在信息时代的健康传播理论,并为减轻整个人口的癌症负担提供新的更好的建议。
然而,他们没有引用基于调查结果的具体举措或政策。看到这方面的更多细节会很有意思。提到的最后一次用户会议发生在 2014 年。如果这些数据仅仅是为了拥有它们而被收集,而不是用于进一步提高医疗保健可及性的目标,那将是不幸的。
我发现这个项目最具挑战性的是(我想,通用数据科学家的抱怨)争论数据和有效地修剪特征。即使有可用的代码簿,也很难确定一些相关字段之间的关系。令我惊讶的是,如此多的特征与结果有显著的单变量关系,这在很大程度上否定了我试图在创建多变量模型之前删除变量的目标。我的结论是,这些是使用一个不是自己收集的数据集的困难,并且它不是为所提问题而专门构建的。
改进
限制
该数据集仅限于从提示中可获得的字段,这不是专门为 EMR 使用调查而设计的。
虽然提示调查旨在代表美国人口,但本研究的感兴趣领域有相当数量的缺失条目,这些条目的删除可能会改变样本的组成,使其代表性降低。
虽然有关于患者几种慢性疾病史的数据,但没有关于患者整体医学状况复杂性或其共病负担的数据。这些因素可能会增加多次 HCP 就诊的需求以及对多个不同 hcp 的就诊需求,从而增加对可用的便携式电子病历的需求。
同样,调查也没有阐明一个病人看多少种不同的 hcp。它也没有关于紧急护理、急诊或住院医疗访问的数据。对其中许多信息的需求可能表明患者需要更密切的随访,他们可能访问多个医疗保健来源,并且他们可能从访问他们的许多医疗保健记录中受益。
最后,由于调查在每年 1 月至 4 月间进行,2020 年大流行后的数据仅涵盖有限的时间段。
改进
进一步的研究可以深入评估医护人员在教育患者和传播电子病历信息时所面临的障碍。医护人员的办公室是否缺乏资源来讨论和教育患者有关电子病历,从而可能产生一种偏见,倾向于主要向需要较少帮助的“潜在采纳者”提供电子病历?同样,对于管理电子病历中面向患者的部分及其可能增加的 HCP 工作人员的负担,会缺乏兴趣吗?
额外研究的另一个领域可能包括检查具有 EMR 访问权限的患者不使用它们的原因。与此相关的问题是提示的一部分,但额外的分析超出了本项目的范围。也许一些患者在讨论复杂的健康问题时更喜欢直接的面对面互动,或者也许他们觉得 EMR 中的数据在他们访问的上下文之外没有用。对于以电子方式访问此类敏感数据,还可能存在隐私问题。
此外,评估患者和 HCP 对各种电子病历利弊的看法,尤其是就其可用性而言,将是有益的。从患者的角度来看,了解 EMR 数据是否有任何对患者友好的解释将是有用的,因为没有医学背景的人很难理解原始诊断测试或实验室结果。
尽管互操作性是 HITECH Act 互操作性倡议1的一个非常具体的目标,但研究各种 EMR 系统的实际互操作性也是值得的,尤其是从最终用户的角度来看。这对于有多个提供商的患者来说尤其重要,他们可能使用不同的电子病历平台。
最后,与疫情 2020 年之前相比,在大流行后时期,获得或使用电子病历没有明显增加。然而,所研究的大流行后时期相对较短,进一步的随访可能会更好地阐明这种独特的情况是否影响 EMR 的获取和使用。
参考
- 疾病控制和预防中心,国家癌症登记计划:电子健康记录的有意义的使用。https://www.cdc.gov/cancer/npcr/meaningful_use.htm。2021 年 8 月 5 日接入。
- 拉法塔·JE,米勒·CA,谢雷斯·达,戴尔·K,拉特里夫·SM,施赖伯·m。患者对电子患者门户网站的采用和特征访问。Am J Manag Care2018;24(11):e352-e357。
- Jhamb M,Cavanaugh KL,Bian A,Chen G,Ikizler TA,Unruh ML,Abdel-Kader K.《电子健康记录患者门户网站在肾病诊所的使用差异》.Clin J Am Soc Nephrol2015;10(11):2013–22.
- 社会科学测量工具数据库:患者健康问卷-4 (PHQ-4)。https://www . midss . org/content/patient-health-question-4-phq-4。2021 年 8 月 7 日接入。
- 疾病控制和预防中心:体力活动:成年人需要多少体力活动?https://www . CDC . gov/physical activity/basics/adults/index . htm。2021 年 8 月 10 日接入。
- 疾病控制和预防中心:酒精和公共健康:酒精使用和你的健康。https://www.cdc.gov/alcohol/fact-sheets/alcohol-use.htm。2021 年 8 月 10 日接入。
Patsy:用任意 Python 代码构建强大的特性
一行代码中的数据转换
动机
有时,您可能想要试验各种功能的组合来创建一个好的模型。然而,这通常需要额外的工作来将特征转换成可以被 scikit-learn 的模型使用的数组。

作者图片
如果可以使用任意 Python 代码快速创建要素,那不是很好吗?这时候 Patsy 就派上用场了。
Patsy 是什么?
Patsy 是一个 Python 库,允许使用任意 Python 代码进行数据转换。
对于 Patsy,您可以使用人类可读的语法,如life_expectancy ~ income_group + year + region(预期寿命取决于收入群体、年份和地区)。
要安装 Patsy,请键入:
pip install patsy
入门指南
为了测试这个库,我们将使用来自 Kaggle 的不同国家出生时预期寿命数据集。为了便于我们观察输出,我们将只取原始数据的一个子集。

我们的任务是预测一个地区在特定年份的预期寿命。
由于收入群体、地区和年份可能在决定预期寿命方面起很大作用,我们将使用 Patsy 将这 3 个特征转换成一个矩阵。
在上面的代码中,
outcome是一个代表life_expectancy的DesignMatrix对象,T3 是我们要预测的变量
>>> outcomeDesignMatrix with shape (140, 1)
life_expectancy
60.73805
59.74837
68.94266
46.45793
54.71871
[135 rows omitted]
Terms:
'life_expectancy' (column 0)
(to view full data, use np.asarray(this_obj))
predictors是一个DesignMatrix对象,代表income_group、region和year的组合
>>> predictorsDesignMatrix with shape (140, 11)
Columns:
['Intercept',
'income_group[T.Low income]',
'income_group[T.Lower middle income]',
'income_group[T.Upper middle income]',
'region[T.Europe & Central Asia]',
'region[T.Latin America & Caribbean]',
'region[T.Middle East & North Africa]',
'region[T.North America]',
'region[T.South Asia]',
'region[T.Sub-Saharan Africa]',
'year']
Terms:
'Intercept' (column 0)
'income_group' (columns 1:4)
'region' (columns 4:10)
'year' (column 10)
(to view full data, use np.asarray(this_obj))
酷!似乎我们得到了一个形状的特征矩阵(140, 11)。140表示有 140 个例子,11表示有 11 个特征。这 11 个特征是:
- Intercept(第 0 列)—1 的数组
income_group(第 1 至第 4 栏)region(第 4 至 10 栏)year(第 10 列)
为什么income_group和regions特性有额外的列?这是因为 Patsy 使用一键编码对这些分类特征进行编码,因此它们可以被机器学习模型解释。
这意味着,分类列:
income_group
1\. low_income
2\. upper_middle_income
3\. lower_middle_income
4\. low_income
5\. upper_middle_income
…将被转换为 0 和 1 的矩阵:
low_income lower_middle_income upper_middle_income
1\. 1 0 0
2\. 0 0 1
3\. 0 1 0
4\. 1 0 0
5\. 0 0 1
好吧,我知道用 Patsy 创建功能有多简单了。但是我如何使用这些功能呢?
Patsy 创建的功能可以很容易地被 scikit-learn 等流行的机器学习库使用。换句话说,你可以把**DesignMatrix** 对象当作一个 NumPy 数组。
这意味着您可以使用train_test_split将DesignMatrix对象分为训练集和测试集:
…并将训练集和测试集提供给机器学习模型。
我们将使用 Yellowbrick 的预测误差图来比较实际目标和我们的模型生成的预测值。
要安装 Yellowbrick,请键入:
pip install yellowbrick

作者图片
在上面的图中,
- y 是代表真实值的轴
- ŷ是表示预测值的轴
- 黑色虚线是由当前模型创建的拟合线
- 如果预测值与实际值完全匹配,灰色虚线表示拟合线的位置
线性模型很好地跟踪了观察到的目标。R 分是 0.911。让我们创建一个函数,这样我们就可以观察给定方程的拟合线。
如果我们仅使用income_group列预测目标值,会发生什么情况?

作者图片
R 分相当低。我们可以通过组合income_group列和region列来看输出是否更好:

作者图片
输出看起来也不是很好。让我们试着把income_group和year结合起来:

作者图片
啊哈!结合income_group和year功能可以产生非常好的结果。这可能是因为收入群体和年份强烈影响一个国家一年的预期寿命。
内置函数
有时,我们可能希望对某个特性进行额外的处理,比如缩放或标准化。幸运的是,Patsy 提供了内置函数,使我们可以轻松地做到这一点。
为了演示这些函数,让我们切换到 Bikeshare 数据集并仅从数据中提取 2k 个样本。

注意列season、year、month、hour、holiday、workingday和weather是分类变量。然而,由于它们的数据类型是整数,Patsy 认为它们是数字变量。
DesignMatrix with shape (2000, 4)
Intercept year hour temp
1 0 4 0.58
1 1 0 0.46
1 0 0 0.44
1 1 7 0.64
1 1 3 0.44
[1995 rows omitted]
Terms:
'Intercept' (column 0)
'year' (column 1)
'hour' (column 2)
'temp' (column 3)
(to view full data, use np.asarray(this_obj))
指定分类变量
为了解决上面的问题,我们可以使用C(column)将一些列指定为分类列:
DesignMatrix with shape (2000, 26)
Columns:
['Intercept',
'C(year)[T.1]',
'C(hour)[T.1]',
'C(hour)[T.2]',
....
'C(hour)[T.22]',
'C(hour)[T.23]',
'temp']
Terms:
'Intercept' (column 0)
'C(year)' (column 1)
'C(hour)' (columns 2:25)
'temp' (column 25)
(to view full data, use np.asarray(this_obj))
这些特征现在得到了更好的表现。
使标准化
我们还可以通过使用standardize方法来标准化特定特征(减去平均值并除以标准偏差):
该功能将转换humidity的初始分布:

作者图片
…对于该发行版:

作者图片
你可以在这里找到 Patsy 提供的其他内置方法。
自定义功能
除了内置函数之外,您还可以编写自己的函数,例如将一列乘以二:
temp功能的前 5 行:
temp
0.58
0.46
0.44
0.64
0.44
multiply_by_two(temp)特征的前 5 行:
multiply_by_two(temp)
1.16
0.92
0.88
1.28
0.88
经营者
除了+操作符,您可以使用其他操作符以其他方式组合特征。
**-** 操作员
该运算符用于删除右边的术语。例如,使用month + temp — 1将删除截距:
DesignMatrix with shape (2000, 2)
month temp
9 0.58
10 0.46
5 0.44
6 0.64
1 0.44
[1995 rows omitted]
Terms:
'month' (column 0)
'temp' (column 1)
(to view full data, use np.asarray(this_obj))
**:** 操作员
该运算符计算左侧和右侧每一项之间的相互作用。例如,使用riders ~ month:temp会将month列中的值除以temp列中的值。
DesignMatrix with shape (2000, 2)
Intercept month:temp
1 5.22
1 4.60
1 2.20
1 3.84
1 0.44
[1995 rows omitted]
Terms:
'Intercept' (column 0)
'month:temp' (column 1)
如果每边有多个项,左边的每个项将被右边的每个项除。举个例子,
(C(year) + C(hour) + C(workingday)) : temp
扩展为:
C(year):temp + C(hour):temp + C(workingday):temp
让我们画出上面的等式,看看输出是什么样子的:

作者图片
我们得到了 0.709 的 R 值。这是一个相当不错的分数!
***** 操作员
a:b包括a和b之间的互动,但不包括a和b本身的特性。如果你想既包括变量又包括变量之间的相互作用,使用a * b。
举个例子,
month * temp
是以下内容的简写:
month + temp + month:temp
****** 操作员
这个操作符用自身计算一组术语的*n 次。
举个例子,
(temp + humidity + windspeed)**3
扩展为:
(temp + humidity + windspeed) * (temp + humidity + windspeed) * (temp + humidity + windspeed)
让我们画出方程式riders ~ (C(year) + C(hour) + C(workingday) + temp) ** 3,看看它看起来是什么样子:

作者图片
哇!我们得到了 0.812 的 R,这是一个非常好的分数。这是否意味着拥有更多的功能会产生更好的输出?让我们试着把3换成6,看看会发生什么。

作者图片
不完全是。拥有更多的特性会导致过度拟合,所以增加更多的特性并不一定更好。
你可以在这里找到所有的操作符及其用法。
结论
恭喜你!您刚刚学习了如何使用 Patsy 用任意 Python 代码构建特性。我希望这个工具能让您更容易地试验不同的特性组合,并理解它们对输出的影响。
随意发挥,并在这里叉源代码:
https://github.com/khuyentran1401/Data-science/blob/master/statistics/patsy_example.ipynb
我喜欢写一些基本的数据科学概念,并尝试不同的算法和数据科学工具。你可以在 LinkedIn 和 Twitter 上与我联系。
如果你想查看我写的所有文章的代码,请点击这里。在 Medium 上关注我,了解我的最新数据科学文章,例如:
Python 3.10 中的模式匹配
类固醇的转换声明

Teo Duldulao 在 Unsplash 上拍照
Python 3.10 实现了 switch 语句——算是吧。C 或 Java 等其他语言中的 switch 语句对变量进行简单的值匹配,并根据该值执行代码。
它可以作为一个简单的 switch 语句使用,但它的功能远不止于此。
这对于 C 来说可能已经足够好了,但是这是 Python,Python 3.10 实现了一个更加强大和灵活的结构,叫做结构模式匹配。它可用作简单的 switch 语句,但功能远不止于此。
让我们以简单的开关为例。下面是对单个值进行切换的代码片段。我们通过用值 1、2、3 和 4 在一个循环中运行它来测试它。
for thing in [1,2,3,4]:
match thing:
case 1:
print("thing is 1")
case 2:
print("thing is 2")
case 3:
print("thing is 3")
case _:
print("thing is not 1, 2 or 3")
首先要注意的是语法的整洁。它以关键字match开始,后跟一个变量名。然后是一个以case开头的案例列表,后面是匹配的值。注意冒号和缩进的使用。
这与其他语言中的 switch/case 语句并不相似,但与 C 不同,例如,在执行了特定 case 的代码后,控制跳转到match语句的末尾。
如果不匹配,则执行默认情况下的代码,由-表示。
这就是结果。
thing is 1
thing is 2
thing is 3
thing is not 1, 2 or 3
没什么好惊讶的。
switch 语句是模式匹配的一个简单例子,但是 Python 做了进一步的发展。看一下这段代码:
for thing in [[1,2],[9,10],[1,2,3],[1],[0,0,0,0,0]]:
match thing:
case [x]:
print(f"single value: {x}")
case [x,y]:
print(f"two values: {x} and {y}")
case [x,y,z]:
print(f"three values: {x}, {y} and {z}")
case _:
print("too many values")
这又是一个循环中的match语句,但这次循环将遍历的值列表是列表本身——第一个是[1,2],然后是[9,10] ,依此类推。
case 语句试图匹配这些列表。第一种情况匹配单个元素的列表,第二种情况匹配两个元素的列表,第三种情况匹配三个元素的列表。最后一种情况是默认的。
但它的作用不止于此。它还绑定与case语句中的标识符匹配的值。例如,第一个列表是[1,2],它匹配第二个案例[x,y]。因此,在执行的代码中,标识符 x 和 y 分别取值 1 和 2。很好,嗯!
所以,结果是这样的:
two values: 1 and 2
two values: 9 and 10
three values: 1, 2 and 3
single value: 1
too many values
从第一个简单的程序中我们知道我们可以匹配值,从上面的程序中我们可以匹配更一般的模式。那么我们能匹配包含值的模式吗?当然啦!
for thing in [[1,2],[9,10],[3,4],[1,2,3],[1],[0,0,0,0,0]]:
match thing:
case [x]:
print(f"single value: {x}")
case [1,y]:
print(f"two values: 1 and {y}")
case [x,10]:
print(f"two values: {x} and 10")
case [x,y]:
print(f"two values: {x} and {y}")
case [x,y,z]:
print(f"three values: {x}, {y} and {z}")
case _:
print("too many values")
例如,您可以看到,在第二种情况下,我们匹配一个包含两个元素的列表,其中第一个元素的值为 1。
结果如下:
two values: 1 and 2
two values: 9 and 10
two values: 3 and 4
three values: 1, 2 and 3
single value: 1
too many values
但是还有更多。
这是一个匹配任意数量元素的列表的程序。
for thing in [[1,2,3,4],['a','b','c'],"this won't be matched"]:
match thing:
case [*y]:
for i in y:
print(i)
case _:
print("unknown")
在第一种情况下,标识符是带星号的,这意味着整个列表可以绑定到它,正如我们看到的,它可以在一个循环中迭代。
1
2
3
4
a
b
c
unknown
还有更多的内容,但是希望这篇文章已经让您体验了 Python 3.10 中的结构模式匹配。
更新:我在这里 写过更高级的模式匹配 。
在Python.org网站上有关于结构模式匹配的完整描述,包括一个教程,在 PEP 636 中。教程非常好,我鼓励你去读它。
除了结构模式匹配,Python 3.10 还有很多新的发展。几篇文章解释了它们,例如,参见James BriggsPython 3.10 中的新特性。
但是,请注意,3.10 仍然只是一个测试版,虽然我上面展示的例子运行得非常好,但是在正式发布之前,你不应该愤怒地使用 Python 3.10。
数据仓库中外部数据集成的模式抽象
简化数据仓库中的第三方数据集成

照片由 Unsplash 上的 Imthaz Ahamed 拍摄
我们的大部分数据是在组织的内部系统中有机生成的,但我们从第三方系统获取(并发送到第三方系统)的数据中有很大一部分是出于分析或运营目的。这些需求可以是财务、物流、营销或任何其他业务部门的需求。我将在下面提供一些营销方面的例子,因为我最近一直在创建和标准化许多第三方营销数据资产。
- 电子邮件/推送营销:我们在数据-api 交互方面的一个重要参与方是 Braze,它是我们的电子邮件和推送营销供应商。我们使用 Braze SDK 收集不同的就餐者行为指标,并将这些指标与其他内部和外部数据结合起来,向我们的就餐者发送深入的个性化通信/促销信息。因此,这是一种反馈循环,使用我们的数据和第三方 API 不断完善。对于 Braze,我们使用异步调用、直接 API 调用和事件数据收集,供应商将事件实时传输到 S3 存储桶。
- 搜索引擎营销(SEM):我们与几个搜索引擎合作,我们的产品在这些搜索引擎中排名更高,并在某些关键词搜索中显示为付费广告。我们收集广告效果和其他第三方数据用于 SEM 分析。我们为此使用的一些 API 是——广告活动表现(苹果、必应、谷歌、雅虎...)、广告分析(谷歌、脸书、Snapchat...)等。
- 搜索引擎优化(SEO):我们需要尽一切努力来提高我们的产品在相关搜索中的知名度,以最好地利用我们的营销资金。我们的一些 API 交互是——Ahrefs(反向链接/入站链接检查),关键字性能报告(谷歌广告词,谷歌广告词 PPA,关键字建设…)等。
- 应用商店优化(ASO):我们收集评级、评论、移动属性、竞争对手数据等。以优化我们的应用安装转化率和应用评级。一些例子是——AppFollow(ASO 数据)、Appsflyer(移动归属数据收集)等。
- 联盟营销:我们从联盟营销市场收集联盟营销数据,如按钮、影响半径、朋友购买等。
- 潜在客户:我们从为我们带来潜在客户的不同合作伙伴那里收集数据,如果这些潜在客户转化为客户,他们就会得到报酬。
- 其他:一系列其他第三方系统,我们使用 SFTP/S3 或事件流获取或发送数据。
问题:
我们在 EMR 中使用 Python & Spark,并将结果数据集存储在 S3 的 Hive 中。在此期间,来自不同团队的许多数据科学家和工程师致力于这些第三方集成。我们面临的问题是—
- 缺乏抽象:主要问题是不同的数据收集模式没有被识别和抽象。这导致了更长的开发周期、代码重复、无法从最佳实践中获益以及其他问题,这些问题本可以通过使用抽象或制造工厂来隐藏。
- 性能:许多开发人员没有利用并行处理或分布式处理,而是使用单线程 python 进程。在这些情况下,性能受到严重影响。
- 可伸缩性:单线程 python 进程是不可伸缩的。在某些情况下,使用了分布式处理,但以不可扩展的方式,例如使用 pandas_df 或将所有数据收集到主节点。
- 重试机制:对 API 的调用可能由于多种原因而失败。可以实现盲重试,但这可能会适得其反。因为 API 可以阻止客户端(由 API 键标识)配额限制耗尽或过多尝试,从而导致相同的错误代码。在这些情况下,我们应该使用“自适应重试”机制。
- 日志:当前进程没有存储 API 响应细节的机制。因此,即使作业完成了,也不能保证所有数据都提取出来了。可重用的日志记录机制用原因代码量化失败,并有助于稍后重试失败的调用。
在 ETL 管道过程中,我将保留“T”部分,因为这是每个过程特有的,但是我们可以识别和抽象数据收集中的主要模式,即提取(E)和加载(L)。
数据收集模式(E)
我们确定了五种不同的第三方数据收集模式—
- API —同步拉取:这里我们调用一个 SOAP 或者 REST API 端点,以 JSON 为例传递一个有效载荷。API 处理该请求,完成后返回一个带有结构化数据格式(JSON、CSV、XML 等)的响应。)该数据由被调用者保存,并且该过程继续到下一个请求。
- API —异步拉取:有时 API 接受请求,但不通过同步方式提供请求的数据,而是返回确认并在后台准备请求的数据。因此,被调用方进程不会等待,而是转到下一个任务。在这里,在有效负载中,我们可以选择传递一个“回调 url ”, API 可以点击该 URL 来表示数据已准备好并保存到某个事先商定的云存储位置。
- API 客户端库:如果可用,可以使用供应商特定的库。这些可用于直接在 JSON、CSV 等中提取固定报告。格式,并作为底层原始 API 端点的更高层次的抽象。
- 事件流:在某些情况下,第三方供应商将事件数据流式传输到云存储,可以批量接收或使用流式传输技术(如 spark streaming)。
- SFTP/云存储:数据按照特定的时间表或根据请求上传到 SFTP 服务器或云存储位置,需要被拉取并摄取到数据仓库。
- 网页抓取:我们不怎么做这个。
既然我们已经最终确定了模式,那么是时候看看这些模式中的每一个有哪些是可重用的部分,以及我们如何标准化它们了。
API 同步拉取
这里的主要组件是 API 端点和有效负载。但是由于每个 API 可以有不同的存储和服务数据的结构,集合可以是嵌套的和复杂的。例如,如果我正在收集上个月所有活动的时间序列数据,首先我必须点击活动/列表?端点收集 campaign _ ids,然后将这些 id 传递给campaign/details?收集相应的详细数据以使用特定过滤器或获取字段,我可以将这些字段作为最终有效负载传递给 campaigns/data_series?端点。在每个响应中,数据可以按页或有限数量的行提供,下一个 URL 标记表示更多数据。所有这些页面或数据块都需要导航,直到我们全部使用完。因此,这可以深入运行许多级别,或者可以有其他需要考虑的 API 特定实现。但是这里仍然有很多标准化的空间-
a.重试 —使用“重试”模块很容易创建一个包装器,其中每个 API 调用都使用这个模块,并在失败之前重试多次。这太普通和盲目了,相反我们可以做一个自适应的重试模块,我们可以根据从 API 收到的响应定制“重试”行为。例如,对于脸书分析 API,如果响应的 status_code 是 400,这意味着“超过速率限制”,并且将有相应的文本建议在下一次重试之前等待的分钟数,线程应该在重试之前休眠该时间。类似地,响应代码 500 表示“请求的数据太多,请减少数据大小”,这里我们可以减少“页面限制”并重试。从这些例子中可以看出,盲目重试实际上是有害的,因为它不会成功返回任何数据,相反,调用将被计入 API 速率限制,等待时间将被进一步提前。这个“自适应重试”模块可以是基于规则的,它将基于 API 和错误代码来管理重试的行为,其细节需要从相应的 API 文档中获得。
b.批处理—API 通常接受“批处理”有效载荷,即 API 将接受一批有效载荷并返回一批响应。批量发送 50 个请求比一个接一个地发送 50 个请求要有效得多。关于使用和限制,需要参考相应的 API 文档,但是不管怎样,这种批处理功能是可以抽象的。
c.日志 —在来自 API 的响应被解析和处理之前,它可以通过一个日志包装器,该包装器将响应记录在一个持久存储中。这样,我们可以量化错误的原因,并在此基础上,如果需要,稍后重试。
d.结构化解析 —来自大多数业务 API 的响应是结构化的,并且有良好的文档记录。应该使用定义良好的模式来解析结构化数据,例如创建 spark 模式并使用该模式解析响应 json。在解析时,我们可以省略一些数据元素,甚至进行转换。这种方式易于管理,可以轻松处理复杂的数据类型,并且比将响应视为文本并使用普通 python 从文本中收集数据元素要高效得多。应该在代码库中创建一个模式存储库,用于解析不同的 API 响应。
API 异步拉取
对于异步拉取,可以简化的一个特别的事情是“回调 url”以及如何在数据管道中触发下一步。通常,我们希望将 AWS API 网关 url 作为“回调 url”与我们的有效负载一起传递,当 API 异步完成处理请求时,它可以点击该 url,让我们知道请求的任务已完成,数据在预先安排的位置(例如 S3 路径)可用。我们可以将 lambda 函数与 API 网关集成在一起,并可以触发 EMR 步骤函数或任何其他自定义流程来处理管道中的后续步骤。但是我们可以把它抽象成一个单独的过程,这个过程可以被任何异步提取 API 数据的作业重用。只创建一个 API 网关,格式为-https://xxxxxxx . execute-API . us-east-1 . Amazon AWS . com/{ environment }/{ unique identifier }。从提取代码,同时使用它作为回调 url,我们传递一个环境(开发,生产等。)和唯一标识符。与 API 网关集成的 lambda 函数要么在 S3 中创建一个唯一的空“信号”文件,以表示下一步的启动准备就绪(可以从被调用方作业轮询),要么采取规则集中定义的其他动作。
一旦提取了数据,在摄取期间也可以使用日志记录和结构化解析。
API 客户端库
API 客户端库提供了与底层 API 交互的编程访问。在大多数情况下,这减少了代码量,使代码更健壮,并简化了身份验证和授权的设置。如果可行,这是指导 API 调用的首选方法。安装和使用指南需要参考 API 客户端文档。由于这些客户端库已经在 raw API 之上提供了一个更高层次的抽象层,除了在其他情况下已经遵循的,这里没有太多具体的改进范围。
事件流
有时,第三方供应商将事件流式传输到云存储,我们以批处理模式或使用流式传输技术(如 spark streaming)接收这些事件。在批处理模式下,我们可以检查最后处理的文件,在下次运行时,我们可以从那里处理文件。在流的情况下,我们可以创建一个 streamingFactory,用于检查点(元数据和/或数据)、流的离散化、将较小的文件合并为较大的文件以提高配置单元性能、分区刷新、错误处理等。可以抽象出来。基本上这两个过程都是高度可配置的,因为主要的移动部分只有表名、模式、数据位置等。
SFTP/云存储
云存储或 SFTP 是一种流行而安全的数据传输方式,许多小供应商更喜欢在无法利用 API、事件流或其他技术的地方使用。但这显然不是第三方数据集成的首选方式,因为它没有数据契约(不像 API、事件流或 API 库)或无缝集成。然而,如果使用云存储(例如 S3),那么当数据到达时,lambda 函数可以触发接收作业,对于 SFTP,我们必须不断轮询和检查数据到达。
以下是不同数据收集流程的高级图示。

作者图片
数据存储(L)
在提取和处理之后,我们需要存储处理过的数据,使其易于使用。在我们的生态系统中,我们使用带有 S3 的 Hive 作为外部持久存储。我们还在 hive 上的 EMR 上使用 Presto 来最小化 SQL 读取延迟。由于 S3 文件系统是不可变的,所以在写入已处理的数据时,我们可以选择覆盖所有现有的数据,向其追加数据,或者将数据写入一个全新的路径并在那里重新打印表。在这里,我们可以处理不同的用例,如下所示—
a. Insert :这是所有方法中最简单的,可以将处理过的数据附加到未分区表的现有路径上,或者在那里创建并写入新的分区。
b.更新/删除:由于 S3 文件系统是不可变的,最终数据集作为一个整体,需要在数据处理过程中进行计算,并像 Insert 一样写回 S3。
c.保留/清除:我们可以通过放置一个 _history hive 表元数据来保留表以前的状态,并通过 run_date 或 run_id 对其进行分区,以便于时间序列/趋势分析。通过传递一定数量的保留天数或运行 id,可以自动清除 S3 中较旧分区的相应数据。
以上所有常见的任务都可以被抽象出来,成为可重用的流程。我不会深入技术细节,因为这里的目标是识别和讨论可以抽象的共性。
最终想法
接收各种不同格式的第三方数据是数据工程师/科学家日常生活的一部分。理解不同的模式并将它们抽象为可重用的工具和过程有很多好处,例如缩短开发生命周期、更干净的代码库、易于维护、性能和可伸缩性提高等等。在许多情况下,我们会将数据或消息发送回外部系统,在那里可以遵循类似的流程。我们不断发送用户资料、客户旅程、订购行为等。使用各种内部系统和流程的 API 将事件发送到我们的 CRM 和电子邮件/推送营销外部系统。我们正在进行 POC,以建立一个中间消息传递系统,使该流程更加可靠和健壮。
通过强化学习实现自动化内容创建和 A/B 测试
情人眼里出西施,但它的创造将很快掌握在算法的手中

Pixabay 许可 : 免费用于商业用途
无需署名
附带视频:https://youtu.be/dmxrN7uUUhs
这篇文章介绍了自动内容创建的新兴领域,它具有播放器生成的强化学习信号。在所展示的虚幻引擎示例中,用户在与游戏交互时展示了他们对游戏组件的偏好,而软件则根据这些展示的偏好自动调整或创建组件。在极端情况下,这种技术可以用来根据用户潜意识的反馈完全重新设计一个软件的脚本或目标。
虽然没有水晶球,但这种方法似乎代表了游戏开发的下一个前沿,或许更普遍地代表了艺术、娱乐和用户界面。在这种情况下,没有一个内容是固定的,而是随着用户与它的交互而不断调整。虽然这听起来像科幻小说,但通过网飞和潘多拉推荐算法,我们已经熟悉了这个概念。这里介绍的工具和方法只是扩展了这一点,允许人工智能连续和主动地操作内容,而不仅仅是在播放列表中的歌曲或视频之间进行选择。在深入研究使用虚幻引擎的真实游戏示例之前,让我们先了解一下这是如何使用玩家生成的强化学习信号来完成的概念。
对于那些不熟悉的人来说,深度强化学习代表了一种根据在试错的基础上与环境交互时收到的奖励来调整人工智能行为的方法。这种方法目前在 AlphaZero 等国际象棋机器人中表现最佳,甚至正在揭开蛋白质折叠的神秘面纱,以解锁新的超级药物。我和其他人认为,强化学习是实现人工智能的关键,我在《超越:强化学习——承诺与危险》一书中详细探讨了这个话题。在下图中,我们可以看到强化学习设置中使用的模式。

作者提供的图片
现在,让我们来看看如何使用强化学习来创建基于用户偏好的独特场景组件。考虑一个思维实验,其中用户在游戏中遇到三个入口,必须选择一个以进入下一关。每个入口都是特定设计或颜色的形状。然而,玩家不知道的是,这三个门户也代表了一个游戏——在这个游戏中,人工智能试图猜测用户喜欢哪种形状,并在未来向他们提供更多这种类型的门户。起初,人工智能没有猜测用户偏好的信息,然而,当用户在这些随机生成的入口中进行选择时,人工智能现在已经接收到关于用户欣赏特定场景组件的设计和构成的反馈信号。下次向用户提供门户选择时,它可以利用过去与用户的交互来更好地猜测他们喜欢门户中的哪些组件,并相应地创建一个。随着这一过程的重复,人工智能将最终专注于该用户的独特偏好,并能够生成与这些期望模式一致的门户。
然而,这种设置有一个缺陷。如果人工智能被允许控制所有三个入口的设计组件,它可能会学习创建两个看起来很糟糕的入口,以便引导玩家进入相对不那么糟糕的入口形状。它不是为了吸引而优化,而是为了相对厌恶而优化。每当处理强化学习算法时,在构建奖励信号时必须非常小心,以免导致意想不到的后果。在我们的例子中,为了避免这种情况,两个门户是随机生成的,尽管人工智能可以“观察”它们是什么,但它只能根据用户的选择来调整单个门户。
为了验证这种方法在梳理用户偏好和相应地调整内容方面的实际效果,我们将进一步简化这个示例,其中门户只能采用 4 种形状——三角形、圆形、正方形或圆柱形。想象一下,在人工智能不知道的情况下,我们的玩家对三角形有一个秘密的偏好。因此,我们将假设当他们在门户中挑选时,他们会表现出选择三角形门户的明显偏好。随着时间的推移,我们希望我们的 AI 学习这种偏好,并在选择向用户显示什么时显示更多基于三角形的门户。请记住,这可以是任何特征,或者是我们选择的特征的组合,因为这些都可以被随机化以创建任意大的解决方案空间,AI 可用的独特模式的数量是无限的。但是回到我们的简单例子,当用户选择三角形的入口时,我们将会看到 AI 选择显示三角形入口和其他形状的频率。一旦我们验证了人工智能已经学习了这个简单的偏好,我们就可以将其扩展到更复杂的例子,在这些例子中,人工智能正在探索关于用户偏好的更大的解决方案空间,甚至可能选择游戏中的角色看起来会是什么样子,或者给用户提供什么样的任务。
其他应用包括优化人工智能助手,如 Google Now 或 Alexa,其中助手可能会根据用户反馈的不同措施来改变或调整他们的语气和反应类型。这也延伸到伴侣机器人和任何其他数字存在。让我们使用免费的 MindMaker 机器学习插件在 Unreal Engine 中浏览基于门户的示例,看看在自己选择的领域中开始实施这些类型的调整是多么容易。

作者的屏幕截图
我们从默认的 MindMaker Starter 内容图开始,它提供了许多基本的深度强化学习功能和算法,我们将在本示例的剩余部分中使用这些功能和算法。接下来,我们创建三个新的场景对象,一个球体是默认的 MindMakerActorBP 蓝图,两个球体是具有基本形状(如正方形)的蓝图类。回想一下,我们只希望一个入口由人工智能控制,其他两个将被随机分配形状。我们只是给他们我们自己选择的任何形状。为了让我们的物体看起来更有“传送门”的感觉,我们给它们添加了蓝色的发射色。发射的颜色,它的大小和强度也是我们可以用人工智能控制的因素。见上面截图。
接下来,我们要将门户动作添加到这些对象中,这样当玩家接触到它们时,它们的位置会重置,并出现新一轮的门户对象。回想一下,每次我们遍历一个入口时,我们都希望其中两个入口被随机分配一个形状。这是通过随机门户蓝图类的以下蓝图来完成的。

作者的屏幕截图
接下来,我们需要构建 AIs 反馈系统,以便在游戏的每一轮中,它可以看到随机入口被分配了什么形状,并赋予它选择自己的入口形状的能力。我们还将修改 MindMaker 奖励功能,这样如果用户选择了人工智能选择的形状,它就会获得奖励。这将允许人工智能随着时间的推移学习用户的偏好。人工智能的观察空间将是一个由 4 个值组成的数组(3 个入口各一个,一个表示玩家选择的入口)。每个门户占位符可以取 0 到 3 之间的整数值,表示门户的形状。同样,这可能是任意复杂的。参见下面的屏幕截图。AI 的动作空间是 0-3 之间的离散值,表示它为自己的入口形状选择的形状。最后,奖励函数是一个整数,当玩家决定通过哪个入口时,每当 AI 选择一个形状,奖励函数就增加 100。

作者的屏幕截图
不要过多地谈论深度强化学习算法如何工作,简单地说,奖励、观察和行动都被馈送到 MindMaker 中包含的选定深度强化学习算法,如 A2C、DQN 等。然后,该算法将根据过去的行动和观察结果,决定为门户选择什么形状。有许多方法可以通过选择神经网络的深度等来微调这些算法。有关 MindMaker 深度强化学习算法和功能的详细信息,请参考创建定制的深度强化学习环境 t。
接下来,我们需要在基本的 MindMaker 蓝图中添加一个延迟节点,以便游戏在将动作、观察和奖励数据传递给算法之前等待,直到玩家选择了一个入口。所有这些都是由用户操作生成的。

作者的屏幕截图
为了测试它,我们将尝试 50 集训练,之后人工智能将从训练切换到实际尝试猜测玩家更喜欢什么形状。在此之前,它将主要进行随机猜测,并观察其猜测的结果。如果它已经正确地学习了,根据我们作为玩家选择的形状,它将相应地调整其中一个入口,因此将有更多的那个形状呈现给用户。就这么简单——随着时间的推移,我们希望看到人工智能选择更多的三角形入口,如果这是玩家一直表现出偏好的形状。
这正是所发生的事情,在大约 50 次试验中,当出现三角形时,玩家始终选择三角形,AI 将几乎停止显示它所控制的门户的任何其他形状。至关重要的是,可以调整强化学习算法的探索利用权衡,以便它永远不会完全停止显示其他形状,从而允许玩家的偏好随着时间的推移而演变,如果他们厌倦了看到如此多的三角形,并希望开始看到更多的其他形状。虽然这似乎是没有区别的区别,因为我们中很少有人会对门户的形状有强烈的偏好,但该技术可以扩展到包括任何与用户操作相关的偏好。从这个角度来理解,它是一个非常强大和广泛的工具。
我们还没有讨论资产的另一个主要用例,那就是 A/B 测试。在某种程度上,A/B 测试可以被视为自动内容生成的一个子集,其中不是实际对游戏组件进行调整,而是简单地收集关于用户偏好和权衡备选方案的数据。例如,通过跟踪用户选择不同门户形状的频率,可以了解用户的偏好。在这种情况下,AI 的任务只是改变门户形状,记录用户选择一个门户形状的频率。这可以以某种友好的格式显示,或者以另一种方式使用。使用 MindMaker 深度强化学习资产进行 A/B 测试的好处是,我们可以随着时间的推移战略性地改变选择。这一点很重要,因为人们的偏好在许多情况下很难辨别或者是非固定的。通过延长探索阶段和调整算法的超参数,我们可以系统地改变呈现给用户的选择,以允许这些偏好的改变。在上面给出的例子中,用户可能开始偏好三角形,但是随着人工智能逐渐向他们显示更多的三角形,他们可能决定他们真的更喜欢圆柱。
我们可以通过深度强化学习来操纵和跟踪这些变化,因为人工智能不需要承诺任何一种策略或一组选项,而是根据用户的动作不断改变它们。人工智能学习用户偏好的速率是通过探索/开发权衡和学习速率来控制的。与其他方法相比,这种方法可以实现更加细致入微的数据收集。
总之,深度强化学习为内容生成者,无论是游戏程序员、UI 设计师,甚至是数字艺术家,提供了一种新的、强大的方法来探索和创建用户生成的内容。像 MindMaker 这样的插件降低了那些希望采用这些方法的人的门槛,并有助于开创游戏开发、娱乐和用户界面的新时代。
如果您有任何问题或疑问,请随时留在这里或亲自联系我。在接下来的文章中,我将探索相关的主题,比如奖励工程的黑暗艺术在强化学习中的应用。
主成分分析:超越降维
了解如何使用 PCA 算法来寻找一起变化的变量

照片由 Unsplash 上的 Pritesh Sudra 拍摄
主成分分析
主成分分析简称 PCA,是一种基于协方差计算的数学变换。
许多初学数据的科学家第一次接触到算法,知道它有利于降维,这意味着当我们有一个包含许多变量的宽数据集时,我们可以使用 PCA 将我们的数据转换成我们想要的尽可能多的分量,从而在预测之前减少它。
这是真的,实际上是一个很好的技巧。但是在这篇文章中,我想告诉你 PCA 的另一个好的用法:验证特征是如何一起变化的。
协方差用于计算两个变量的移动。它表示变量之间线性关系的方向。
知道了什么是协方差以及它的作用,我们就可以知道变量是一起运动,相反还是彼此独立。
PCA 做什么
明白事情比这个解释复杂多了,不过还是简单点说吧。
假设我们有一个三维数据集。嗯,PCA 将获得您的数据集,并查看这三个维度中的哪一个可以绘制最长的线,这意味着逐点查看,我可以获得每个维度的最大差异是什么。一旦计算完成,它将画出这条线,称之为主成分 1。
第一个主成分捕获了数据的最大变化。第二台 PC 捕捉第二多的变化。
之后,它会转到下一个维度,画另一条线,这条线必须垂直于 PC1,并保持可能的最大方差。最后,将在第三维度上完成,始终遵循垂直于先前 PCs 的规则,并尽可能保持最大的方差。这样,如果我们有 n 个维度,那么这个过程将被执行 n 次。
我发现解释这一点的一个好方法是想象一个瓶子。想象它充满了来自你的数据集中的点。当然,最大的变化将是从顶部到底部。这就是 PC1。然后,PC2 需要垂直,所以它留给我们从一边到另一边的箭头。

图 1:“瓶子”数据集。计算机将如何“看到”您的数据。图片由作者提供。
在那之后,我们可以继续画很多其他的线来显示我们的数据是如何分布到所有可能的边上的。这些向量以数学方式“绘制”数据集,因此计算机可以理解它。
运行 PCA
让我们编写一点代码并运行 PCA。
import pandas as pd
import random as rd
import numpy as np
from sklearn.decomposition import PCA
from sklearn import preprocessing
import matplotlib.pyplot as plt# Create a dataset
observations = ['obs' + str(i) for i in range(1,101)]
sA = ['sampleA' + str(i) for i in range(1,6)]
sB = ['sampleB' + str(i) for i in range(1,6)]data = pd.DataFrame(columns=[*sA, *sB], index=observations)for observation in data.index:
data.loc[observation, 'sampleA1':'sampleA5'] = np.random.poisson(lam=rd.randrange(10,1000), size=5)
data.loc[observation, 'sampleB1':'sampleb5'] = np.random.poisson(lam=rd.randrange(10,1000), size=5)

表 1:样本数据。图片由作者提供。
现在我们应该缩放数据。如果一些变量的方差很大,一些很小,PCA 会为 PC1 画一条最长的线,这样会扭曲你的数字,使其他的 PCs 变得很小。是的,PCA 会受到异常值的影响。

PC1 方差中的主要异常值。图片由作者提供。
因此,将变量标准化将使这种影响最小化。另一方面,如果你的变量的具体范围很重要(因为你希望你的 PCA 在那个范围内),也许你不想标准化,但要意识到这个问题。
接下来,当您运行 PCA 时,您将看到矩阵的行是显示组件编号的行。在我们的例子中,我们希望看到样本是如何一起变化的,所以我将转置样本,使其成行,缩放并运行 PCA。
# Transpose and Scale
scaled_data = preprocessing.scale(data.T)# PCA instance
pca = PCA()#fit (learn the parameters of the data)
pca.fit(scaled_data)# transform (apply PCA)
pca_data = pca.transform(scaled_data)# Creating the Scree Plot to check PCs variance explanation
per_var = np.round(pca.explained_variance_ratio_*100, 1)
labels = ['PC' + str(x) for x in range(1, len(per_var)+1)]plt.figure(figsize=(12,6))
plt.bar(x=labels, height=per_var)
plt.ylabel('Variance explained')
plt.show()

Scree Plot:每台电脑解释了多少差异。图片由作者提供。
特征向量,特征值
运行 PCA 后,您将收到一串数字,如表 2 所示。这些数字是每个主成分的特征向量——换句话说,这些数字“创造”了计算机用来理解你的数据的箭头。
特征值代表由变量pca.explained_variance_解释的方差的量
# Loadings Table
pca_df = pd.DataFrame(pca_data, index=[*sA, *sB], columns=labels)

表 2:特征向量。图片由作者提供。
如果你想降低数据集的维数,这很简单。使用参数n_components即可。
# PCA instance with 3 dimensions
pca = PCA(n_components=3)#fit (learn the parameters of the data)
pca.fit(scaled_data)# transform (apply PCA)
pca_data = pca.transform(scaled_data)
变量关系
现在我们到了这篇文章的最后一部分。让我们超越降维。让我们学习如何理解变量是否一起浮动。
- 看每个主成分列,注意有正负符号。
- 正样本与 PC 的方向相同,而负号表示样本的变化方向相反。
- 数字意味着力量。越高,PC 中样本的方差越大。
- 查看每台 PC 解释差异的百分比— PC1 = 92%。因此,我们可以只查看 PC1 来了解样本之间的关系,因为 PC1 中解释了几乎所有的方差。
- 我们可以看到,A 样本朝一个方向(+)前进,而 B 样本朝另一个方向(-)前进。
让我们画一张图来更好地说明这个想法。
# PC1 x PC2 scatter plotplt.figure(figsize=(12,6))
plt.scatter(pca_df.PC1, pca_df.PC2)
plt.title('PCA graph')
plt.xlabel(f'PC1 - {per_var[0]}%')
plt.ylabel(f'PC2 - {per_var[1]}%')for sample in pca_df.index:
plt.annotate(sample, (pca_df.PC1.loc[sample], pca_df.PC2.loc[sample]) )plt.show()

令人惊讶的是样品 A 和 B 是如何排列在一起的。图片由作者提供。
在你走之前
在这篇文章中,我们学习了 PCA 算法的另一个很好的用途,那就是了解哪些变量是相关的,哪些变量“浮动”在一起,哪些变量可以成为特征选择的有趣工具或者可以与聚类相结合。
观看 StatQuest 的这个视频,你可以学到更多东西,这是我找到的关于 PCA 的最好的视频,也是我经常回来咨询的视频。
另一个考虑因素:
- PCA 基于协方差分析,受离群值影响。
- 它可以用来降低维数。
- 可用于检查数据集中的要素是如何相关的。
- 如果 Scree 图(显示百分比的图)显示 PC1 的方差较低,如 20%,您可能需要更多的 PCs 来合计至少 80%的解释方差,以执行您的分析。比如下面的例子。
# Multiply (weigh) by the explained proportion
pca_df2 = pca_df.mul(pca.explained_variance_ratio_)# Sum of the components
pca_df2 = pd.DataFrame({'fabricante':pca_df2.index, 'tt_component': pca_df2.sum(axis=1)}).sort_values(by='tt_component')pca_df2.plot.scatter(x='tt_component', y='fabricante', figsize=(8,8))

用于差异分析的总件数。图片由作者提供。
代码在 GitHub,在这里。
如果你对这些内容感兴趣,请关注我的博客。
https://medium.com/gustavorsantos
PCA、LDA 和 SVD:通过用于交通 POI 分类的特征缩减进行模型调整
比较要素缩减方法以调整将 POI 记录分类为机场、火车站或公交车站的模型

布雷特·乔丹在 Unsplash 上的照片
注意 :本文是关于使用安全图模式数据进行分类的系列文章中的第二篇。第一篇文章使用几个多类分类器(如高斯朴素贝叶斯分类器、决策树分类器、K-最近邻分类器和支持向量机分类器)来分析数据的初始分类,如公交车站、机场和火车站。第 1 部分可在此链接找到::POI 分类第 1 部分
本文将通过使用各种特征约简算法来调整我们在本系列第一部分中使用的模型。这篇文章将作为本项目笔记本中代码片段的解释。该项目利用来自安全图的数据。SafeGraph 是一家数据提供商,为数千家企业和类别提供 POI 数据。它向学术界免费提供数据。对于这个项目,我选择使用 SafeGraph 模式数据,以便将记录分类为各种 POI。模式数据的模式可以在这里找到:模式信息。如果你想看笔记本并自己运行代码,在这里看笔记本看
什么是特征约简?
真实世界的数据非常复杂,由许多要素组成,尤其是用于机器学习的数据。随着要素数量的增加,可视化数据变得越来越困难。此外,大量特征的存在导致更长的计算时间。因此,为了更好地可视化数据并更快地执行计算,我们必须通过组合变量来移除不需要的特征或降低特征的维度。
在我们进入特征缩减概念的第一步之前,我们必须首先加载我们将用于这个项目的数据:加载数据的过程可以在笔记本中找到,并且已经在系列的第一部分中详细解释。就我们的目的而言,所需要的只是对所采取的步骤和结果数据框架的简要概述:
- 删除不必要的列- ['parent_safegraph_place_id ',' placekey ',' safegraph_place_id ',' parent_placekey ',' parent_placekey ',' safegraph_brand_ids ',' brands ',' poi_cbg']
- 创建地面实况列,将每个记录建立为机场、汽车站、机场或未知
- 删除未知记录以清除无法识别的记录
- 使用 pyspark 水平分解 JSON 字符串的列
- 阵列的水平分解列
- 使用 Sklearn LabelEncoder 包转换类列
作为这些转换的结果,输出的数据如下所示,并具有以下各列:

Raw_visit_counts: 在日期范围内,我们的小组中对此兴趣点的访问次数。
Raw_visitor_counts: 在日期范围内从我们的面板访问该兴趣点的独立访问者的数量。
Distance_from_home: 访客(我们已确定其住所的访客)离家的中间距离,以米为单位。
中值 _ 停留:中值最小停留时间,以分钟为单位。
分时段停留(分解为< 5,5–10,11-20,21-60,61-120,121–240):键是分钟范围,值是在该持续时间内的访问次数
Popularity_by_day(分解到周一至周日):一周中的某一天到日期范围内每天(当地时间)的访问次数的映射
Popularity_by_hour(分解为 Popularity _ 1-Popularity _ 24):将一天中的某个小时映射到当地时间日期范围内每小时的访问量。数组中的第一个元素对应于从午夜到凌晨 1 点的时间
Device_type(分解为 ios 和 Android): 使用 Android 和 IOS 的 POI 访客人数。仅显示至少包含 2 个设备的设备类型,包含少于 5 个设备的任何类别都报告为 4

既然已经获得了数据,我们可以使用特征约简来查看整体精度和各个类的精度是如何变化的。以下是高斯朴素贝叶斯模型、决策树模型和 K-最近邻模型在使用原始数据集进行训练和测试时的热图和精度,以供参考:
高斯朴素贝叶斯:


决策树:


K-最近邻分类器



使用主成分分析的特征减少
PCA 是一种降维方法,它采用具有大量特征的数据集,并将它们减少到几个基本特征。PCA 通过执行以下步骤来查找给定数据集中的基础要素:
1.计算特征矩阵的协方差
2.计算协方差矩阵的特征向量和特征值
3.按特征值降序排列特征向量
4.选择第 k 个特征向量。这个 k 值将成为 k 的新尺寸
5.将原始数据转换成 k 尺寸
这篇文章深入探讨了 PCA 背后的数学原理
sklearn 的 PCA 包为我们执行这个过程。
在下面的代码片段中,我们将初始数据集的 75 个要素缩减为 8 个要素。
from sklearn.decomposition import PCApca = PCA(n_components=8)Principal_components=pca.fit_transform(transportation_df.drop([‘location_name’, ‘Class’], axis=1))pca_df = pd.DataFrame(data = Principal_components, columns = [‘PC 1’, ‘PC 2’, ‘PC 3’, ‘PC 4’, ‘PC 5’, ‘PC 6’, ‘PC 7’, ‘PC 8’])pca_df.head(3)

此代码片段用于显示特征缩减算法适合的最佳特征数量。scree 图显示理想值是n = 2,但是,我们将继续使用n = 8特征进行剩余的分析,因为它实际上为下面的分类器提供了比n = 2特征稍好的准确性
PC_values = np.arange(pca.n_components_) + 1plt.plot(PC_values, pca.explained_variance_ratio_, ‘ro-’, linewidth=2)plt.title(‘Scree Plot’)plt.xlabel(‘Principal Component’)plt.ylabel(‘Proportion of Variance Explained’)plt.show()

将类列添加回 PCA 数据帧
PCA_df = pd.concat([pca_df, transportation_df[‘Class’]], axis = 1)PCA_df.head()

将 PC1 和 PC2 绘制成不同类别的散点图
PC_0 = PCA_df.where(PCA_df[‘Class’] == 0).dropna()[[‘PC 1’,’PC 2']]PC_1 = PCA_df.where(PCA_df[‘Class’] == 1).dropna()[[‘PC 1’,’PC 2']]PC_2 = PCA_df.where(PCA_df[‘Class’] == 2).dropna()[[‘PC 1’,’PC 2']]plt.scatter(PC_0[‘PC 1’],PC_0[‘PC 2’])plt.scatter(PC_1[‘PC 1’],PC_1[‘PC 2’])plt.scatter(PC_2[‘PC 1’],PC_2[‘PC 2’])

使用 PCA 的分类算法
以下片段将展示如何使用高斯朴素贝叶斯、决策树和 K-最近邻分类器来简化特征。
将数据分为训练数据和测试数据
x_cols = []for item in list(PCA_df.columns):if(item != 'Class'):x_cols.append(item)X = PCA_df[x_cols]y = PCA_df['Class']X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 0)
高斯朴素贝叶斯分类器:
gnb = GaussianNB().fit(X_train, y_train)gnb_predictions = gnb.predict(X_test)

将特征减少到 n = 8 个特征似乎有助于高斯朴素贝叶斯模型的整体模型准确性。预测率略好于原始数据集的准确率~26%。该模型将实际机场预测为预测列车,PCA 数据帧的速率略高于初始数据帧(. 770 vs .802)。该模型很好地正确预测了火车站,但表现略差于初始数据(. 940 vs. .968)。该模型在正确预测公交车站方面稍差一些(0.009 比 0.00),但这种准确性的降低会导致模型错误地预测所有公交车站记录。该模型预测机场的精度是原始数据集的两倍(. 59 vs .120)。我们可以从上面的热图中看到,大多数记录都被归类为火车站。
决策树分类器:
dtree_model = DecisionTreeClassifier(max_depth = 3).fit(X_train, y_train)dtree_predictions = dtree_model.predict(X_test)

决策树模型使用 PCA 数据帧的总体性能比使用原始数据帧的性能稍差(75%比 73.93%)。该模型在正确预测机场方面表现非常好,事实上,比原始数据在这个特定模型中表现得更好(. 958 vs .912)。该模型在预测公交车站方面表现不佳,这与该模型对原始数据集的预测方式类似。该模型预测火车站的正确率低于原始数据集的正确率(. 387 vs. .553)。该模型倾向于将大多数记录预测为机场。
K-最近邻:
knn = KNeighborsClassifier(n_neighbors = 22).fit(X_train, y_train)

K-最近邻算法具有良好的总体精度,并且与使用原始数据集的算法一样好。该模型以类似于原始数据集的速率(0.931 比 0.937)准确预测机场记录。该模型准确地预测了公共汽车记录和火车记录,预测率与原始数据的预测率相同(分别为 0.027 比 0.027,0.180 比 0.180)。在应用 PCA 算法之前,大部分数据被分类,因此,对于这个特定的模型,使用 PCA 来降低特征的维度似乎不是最佳选择。
从这三个模型及其结果中,我们可以看到 PCA 数据框架并没有提高决策树和 K-最近邻等模型的准确性。唯一看到增长的模型是高斯朴素贝叶斯模型。这可能是由于模型没有考虑特征之间的相关性,这个问题用主成分解决了,因为主成分考虑了原始特征之间的相关性。因此,通过主分量的这种调节导致了稍好的精确度。

Anthony Intraversato 在 Unsplash 上拍摄的照片
使用 LDA 的特征约简
线性判别分析是另一种降维算法。下面是对 LDA 算法的深入研究: LDA 文章
LDA 通过以下步骤工作:
1)计算不同要素的平均值之间的距离-这称为要素间方差
2)计算每个要素的平均值和样本之间的距离-这称为要素内方差
3)构造低维空间以最大化特征间方差和最小化特征内方差
sk learnlinear discriminant analysis 软件包非常方便地为我们执行这一分析
from sklearn.discriminant_analysis import LinearDiscriminantAnalysislda = LinearDiscriminantAnalysis(n_components=2, solver=’svd’)X_lda = lda.fit_transform(transportation_df.drop([‘location_name’, ‘Class’], axis=1), transportation_df[‘Class’])lda_df = pd.DataFrame(data = X_lda, columns = [‘PC 1’, ‘PC 2’])lda_df.head()

将类列添加到 LDA 数据帧:
lda_df = pd.concat([lda_df, transportation_df[‘Class’]], axis = 1)lda_df.head()
下面的代码片段将数据集中的不同类绘制成 PC 1 和 PC 2 的函数。从图中可以看出,LDA 分析得出的降维结果与 PCA 分析得出的降维结果有很大不同。让我们看看这个特殊的降维算法的有效性
PC_0 = lda_df.where(lda_df[‘Class’] == 0).dropna()[[‘PC 1’,’PC 2']]PC_1 = lda_df.where(lda_df[‘Class’] == 1).dropna()[[‘PC 1’,’PC 2']]PC_2 = lda_df.where(lda_df[‘Class’] == 2).dropna()[[‘PC 1’,’PC 2']]plt.scatter(PC_0[‘PC 1’],PC_0[‘PC 2’])plt.scatter(PC_1[‘PC 1’],PC_1[‘PC 2’])plt.scatter(PC_2[‘PC 1’],PC_2[‘PC 2’])

使用 LDA 的分类算法
以下片段将展示如何使用高斯朴素贝叶斯、决策树和 K-最近邻分类器来简化特征。
将数据分为训练数据和测试数据
x_cols = []for item in list(lda_df.columns):if(item != 'Class'):x_cols.append(item)X = lda_df[x_cols]y = lda_df['Class']X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 0)
高斯朴素贝叶斯分类器:
gnb = GaussianNB().fit(X_train, y_train)gnb_predictions = gnb.predict(X_test)

尽管高斯朴素贝叶斯模型背后的耻辱是效率低下,因为它无法将数据集的要素视为相互依赖,而是将它们和独立且不相关的要素(因此是模型的朴素本质)考虑在内,但该模型在降维后的数据上似乎表现得非常好。该模型在预测真实的机场记录(. 973)方面表现非常好,并且在预测真实的公交车站记录(. 064 vs. .009)方面做得更好。该模型在正确分类真实列车记录方面表现更差(. 968 vs. .035)。与模型使用原始数据的性能不同,模型现在预测大多数值为机场。这个模型预测的原因可以归结为数据的不平衡。由于数据主要由机场记录组成,该模型可能倾向于预测更多的机场记录
决策树模型:
dtree_model = DecisionTreeClassifier(max_depth = 3).fit(X_train, y_train)dtree_predictions = dtree_model.predict(X_test)

使用 LDA 数据帧的决策树模型的性能比使用原始数据帧的模型稍差(分别为 0.703 和 0.75)。该模型在准确预测真实的机场记录方面稍差(0.91 比 0.9)。该模型不能用任何一个数据帧预测任何真实的公交车站记录,并且在正确分类真实的火车站记录时表现明显更差(0.292 对 0.553)。该模型显示了将大多数记录预测为机场的趋势,这可能归因于数据的不平衡性质。
K 近邻:
knn = KNeighborsClassifier(n_neighbors = 22).fit(X_train, y_train)

K-最近邻分类器使用降维的数据帧比原始数据帧执行得更好(. 703 对. 673)。与原始数据帧(. 927 vs. .937)相比,该模型在预测具有 LDA 数据帧的真实机场记录方面稍差,但是它在正确预测正确的汽车站记录方面做得同样好。该模型在预测真实火车站记录时表现稍差(0.173 比 0.180)。虽然使用 LDA 数据帧的模型的性能与原始数据帧相同或稍差,但准确性提高了,因为与原始数据帧的情况相比,错误预测记录的比率在 LDA 数据帧中分布得更好。
从对 LDA 数据帧进行的分析中,我们开始看到一种模式。随着特征约简的出现,不围绕数据的互连性质的分类算法的整体准确性会降低。这可以从决策树算法中看出,该算法以一种简单明了的方式工作,其核心思想是根据特征的变化选择一个特定的类。当特征的数量从 75 个特征减少到 2 个特征时,模型的精度下降。但是,特征约简有助于完全基于概率的模型或无监督模型(如高斯朴素贝叶斯模型和 K-最近邻算法)的准确性。让我们看看这种趋势是否会延续到最后一种特征约简方法中。

由 Marek Piwnicki 在 Unsplash 拍摄的照片
使用奇异值分解减少特征
奇异值分解是另一种降维算法。下面是对 SVD 算法的深入研究: SVD 文章
SVD 算法的工作原理是将特征矩阵分解为三个矩阵,这三个矩阵通过以下公式组合在一起:
A = U𝚺V^T
其中 A 是维数为(m×n)的特征矩阵, U 是维数为(m×m)的正交矩阵, V 是维数为(n×n)的正交矩阵, 𝚺 是大小为(m×n)的非负矩形对角矩阵
sklearn 包中的TruncatedSVD函数很好地执行了这种转换
from sklearn.decomposition import TruncatedSVDsvd = TruncatedSVD(n_components=2, algorithm=’randomized’,random_state=0)X_svd = svd.fit_transform(transportation_df.drop([‘location_name’, ‘Class’], axis=1))svd_df = pd.DataFrame(data = X_svd, columns = [‘PC 1’, ‘PC 2’])svd_df.head(3)

将类列添加到 SVD 数据帧中
svd_df = pd.concat([svd_df, transportation_df[‘Class’]], axis = 1)svd_df.head(3)

绘制 PC1 和 PC2 的各种类的函数图。下图似乎类似于通过 PCA 分析生成的图表。这显示了两种算法的输出之间的相似性。让我们看看分类算法在准确性上是否与 PCA 分析相似
PC_0 = svd_df.where(svd_df[‘Class’] == 0).dropna()[[‘PC 1’,’PC 2']]PC_1 = svd_df.where(svd_df[‘Class’] == 1).dropna()[[‘PC 1’,’PC 2']]PC_2 = svd_df.where(svd_df[‘Class’] == 2).dropna()[[‘PC 1’,’PC 2']]plt.scatter(PC_0[‘PC 1’],PC_0[‘PC 2’])plt.scatter(PC_1[‘PC 1’],PC_1[‘PC 2’])plt.scatter(PC_2[‘PC 1’],PC_2[‘PC 2’])

使用奇异值分解的分类算法
以下片段将展示如何使用高斯朴素贝叶斯、决策树和 K-最近邻分类器来简化特征。
将数据分为训练数据和测试数据
x_cols = []for item in list(svd_df.columns):if(item != ‘Class’):x_cols.append(item)X = svd_df[x_cols]y = svd_df[‘Class’]X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 0)
高斯朴素贝叶斯分类器:
gnb = GaussianNB().fit(X_train, y_train)gnb_predictions = gnb.predict(X_test)

高斯朴素贝叶斯的热图显示,SVD 数据帧的总体精度略好于原始数据帧的总体精度(. 265 vs. .319)。总的来说,该模型在用数据帧(. 140 对. 059)和(0 对. 009)预测实际机场记录和汽车站记录方面表现不佳。在这两种情况下,大多数记录都被分类为火车站,并且在这两种情况下,模型都非常好地预测了实际的火车站记录(. 968 vs. .968)。与所有使用高斯朴素贝叶斯模型的情况一样,无论是否使用特征约简,该模型都倾向于将大多数记录预测为火车站。这也是因为模型的性质,以及它如何将每个功能视为单独的功能,而不是相互关联的功能。
决策树分类器:
dtree_model = DecisionTreeClassifier(max_depth = 3).fit(X_train, y_train)dtree_predictions = dtree_model.predict(X_test)

使用 SVD 数据帧的决策树模型比使用原始数据帧的模型稍差(67.8 比 75)。该模型预测真实机场记录的速度比使用原始数据时的模型(0.91 比 0.9)稍差。该模型在缺乏对真实公交车站记录的预测方面没有改变,但在预测真实列车记录方面比原始数据集差得多(. 533 vs. .292)。该模型显示了将大多数记录分类为机场的趋势,因为数据中存在不平衡,并且大多数记录具有机场的初始地面真实性
K-最近邻分类器
knn = KNeighborsClassifier(n_neighbors = 22).fit(X_train, y_train)

使用 SVD 数据帧的 K-最近邻模型比使用原始数据的模型表现稍差(分别为 0.67 和 0.679)。像原始模型一样,SVD 数据框架模型在预测实际机场记录方面做得很好(. 927 vs. .93)。实际公共汽车和火车站记录的预测精度也类似于原始数据的精度(0.027 对. 027)和(. 180 对. 173)。与以前的模型一样,数据集中的不平衡和大量的机场记录导致该模型将大多数记录预测为机场。
上述分析证实了我们以前的理论。基于概率决策和无监督模型(如高斯朴素贝叶斯和 K-最近邻模型)的算法在处理特征减少的数据时比需要大量特征以获得更好性能的模型(如决策树模型)表现得更好。
结论:
从我们对三种不同形式的特征约简进行的分析以及这些特征约简的数据帧在三种不同的多类分类器上的应用中,我们看到一种特定形式的特征约简比其他两种效果好得多,这就是 LDA 变换。LDA 算法有助于提高所使用的大多数分类器的准确性,事实上,使高斯朴素贝叶斯模型的准确性提高了近 30%。因此,该模型在减少安全图数据的特征和提供最佳结果方面是最有效的。
本笔记本向我们介绍了基于安全图模式访问数据使用特征缩减和 POI 类别分类进行模型调整的方法。我们的算法在对机场、火车站和公交车站进行分类时取得了一些成功,所有这些都基于停留时间、游客离家的距离、一天中每个小时的受欢迎程度以及一周中每一天的受欢迎程度。
该系列的下一个笔记本(链接即将推出!)将深入研究使用深度学习分类器来执行相同的分析,并看看神经网络是否能提高预测精度
提问?
我邀请你在 SafeGraph 社区的 #safegraphdata 频道问他们,这是一个面向数据爱好者的免费 Slack 社区。获得支持、共享您的工作或与 GIS 社区中的其他人联系。通过 SafeGraph 社区,学者们可以免费访问美国、英国和加拿大 700 多万家企业的数据。
高光谱数据的主成分分析
对初学者友好的高光谱数据主成分分析教程

美国地质勘探局在 Unsplash 上拍摄的照片
介绍
高光谱数据扩展了图像分类的能力。高光谱数据不仅区分不同的土地覆盖类型,而且还提供每种土地覆盖的详细特征,如矿物、土壤、人造结构(建筑物、道路等)。)和植被类型。
在处理超光谱数据时,一个缺点是要处理的波段太多。除此之外,存储如此大量的数据也是一个挑战。数据量大,时间复杂度也增加。
因此,减少数据量或仅选择相关波段变得至关重要。应该记住,分类质量不应该随着波段数量的减少而降低。选择相关波段的方法之一是通过应用主成分分析对高光谱数据进行预处理。
应用主成分分析
PCA 是基于高光谱图像的相邻波段高度相关并且传达相同信息的事实。
PCA 有许多方法,如 Hotelling 变换或 Karhunen-Loeve 变换,但它们都具有相同的高光谱波段协方差矩阵特征值分解的数学原理。
让我们仔细看看 PCA 的工作原理…
步骤 1:导入库
第二步:准备数据
您可以从以下链接下载数据。
https://github.com/GatorSense/MUUFLGulfport
它包含 MUUFL Gulfport 超光谱和激光雷达数据收集文件。但是我们只处理 HSI 数据。
输出形状: (325,220,64)
步骤 3:可视化 RGB 数据

RGB 图像
步骤 4:重塑超光谱数据
输出形状: (71500,64)
步骤 5:计算协方差矩阵;特征值和特征向量
PCA 基于被称为待分析的超光谱波段的协方差矩阵的特征值分解的数学原理。
第六步:按降序排列特征值,并获得相应的特征向量
按降序排列后,信息最多的特征值和特征向量将位于列表的顶部。
步骤 7:提取前 3 个特征向量
步骤 8:特征向量的 3D 投影

步骤 9:打印特征向量的投影矩阵
步骤 10:绘制特征值

在这幅图中,我们可以看到顶部的特征值包含了最多的信息。
结论
主成分分析是一种非常有用的高光谱分类技术。从上面的图中我们可以看到,前 3 个特征值包含了最多的信息。之后,剩下的大多是噪音。PCA 大大减少了分类的计算时间,也减少了要处理的数据量。PCA 预处理给出了相当可接受和准确的分类结果。
除了 PCA 之外,还有其他可选的预处理方法,如典型成分分析。它将来自不同视图的数据映射到一个具有最大相关性的公共空间。
感谢阅读!我希望这篇文章对你有用。如果你有任何问题,请随意提问。
参考
- 【https://github.com/GatorSense/MUUFLGulfport 号
- https://engineering . purdue . edu/~ jshan/publications/2002/SaLIS _ 2002 _ hyperimagespca . pdf
- https://www . asprs . org/WP-content/uploads/pers/1999 journal/aug/1999 _ aug _ 909-914 . pdf
- https://en . Wikipedia . org/wiki/Karhunen % E2 % 80% 93Lo % C3 % A8ve _ theory
PCA:主成分分析——如何用更少的维度获得优越的结果?
机器学习
降维的最佳技术之一

主成分分析。图片由作者提供。
介绍
主成分分析(PCA)是数据科学家常用的技术,用于提高模型训练的效率,并在较低的维度上可视化数据。
在本文中,我解释了 PCA 是如何工作的,并给出了一个如何在 Python 中执行这种分析的例子。
内容
- PCA 属于机器学习技术的范畴
- PCA 工作原理的直观解释
- 对真实数据执行 PCA 的 Python 示例
- 结论
主成分分析(PCA)属于机器学习技术的哪一类?
虽然 PCA 通常被称为降维技术,但它实际上是一种数据转换。
然而,主成分分析可以很容易地使用得到的主成分来减少维数,因为它将它们从“最有用”(捕获大量数据方差)到“最不有用”(捕获非常少的数据方差)进行排序。
因此,把它放在 ML 的无监督学习分支中的算法的维数减少组中是没有害处的。
下图是互动,所以请点击不同类别放大并展示更多👇。
机器学习算法分类。由作者创建的交互式图表。
如果你喜欢数据科学和机器学习 ,请 订阅 每当我发布一个新的故事,你都会收到一封电子邮件。
主成分分析(PCA)是如何工作的?
准备数据
简单来说,主成分分析帮助我们找到新的维度轴(主成分),可以更好地捕捉数据的方差。
例如,您可能有两个属性-房屋大小(平方英尺)和房屋中的房间数量。毫不奇怪,这两个属性高度相关,因为更大的房子往往有更多的房间。
让我们通过在 2D 散点图上绘制这两个属性来形象化我们虚构的示例:

房子大小和房间数量的关系。图片来自作者。
我们可以看到,房子大小和房间数量之间是正相关的。此外,与房间数量相比,房子的大小似乎有更大的差异。
但是,在我们做出任何过早的结论之前,让我们暂停一分钟,看一下 x 和 y 的标度,我们可以看到,这两个属性使用了两种不同的标度;因此,上面的图片并不真正具有代表性。
为了更客观的解读,我们先把数据标准化。
请注意,标准化是一种数据转换技术,它对数据进行重新缩放,使每个属性的平均值为 0,标准差为 1。这种转换可以用下面的公式来描述:

标准化。图片由作者提供。
这是同样的数据经过标准化后的样子:

房子大小和房间数量之间的关系(标准化数据)。图片由作者提供。
我们可以看到数据现在以原点为中心,因为两个标准化属性的平均值都是 0。同时,现在更容易直观地比较这两个属性的分布(方差)。正如之前推测的那样,房子的大小确实比房间的数量有更大的差异。
寻找主成分— PC1
我将有意避免进入寻找主成分背后的数学,因为我的目标是给你一个关于 PCA 如何工作的直观/视觉理解。
如果你确实喜欢一点代数,尤其是矩阵乘法,你可以看看这个来自王子晨关于 PCA 和 SVD 使用 NumPy 的解释。
既然我们已经将数据标准化并以原点为中心,我们可以寻找“最佳拟合”线。这条线必须穿过原点,我们可以通过最小化数据点到它们在这条线上的投影的距离来找到它。看起来是这样的:

数据的“最佳”线,也是 PC1 的坐标轴。图片由作者提供。
有趣的是,这条“最佳拟合”线恰好也是主成分 1 (PC1)的轴。这是因为最小化与线的距离也使同一条线上的数据点投影的分布最大化。
换句话说,我们已经找到了一个新的轴,它可以捕捉到该维度中数据的最大变化量。
如果你决定单独研究这个过程的数学,你会遇到像特征向量和特征值这样的术语。在某种意义上,它们描述了这条“最佳”线,并通过对协方差矩阵进行特征分解或通过奇异值分解分析(简称 SVD)在数学上找到。
寻找主成分— PC2 和其他
记住,主成分分析不仅仅是降维。因此,我们可以在数据中找到和属性(维度)一样多的主成分。说了那个,还是找 PC2 吧。
由于我们只有两个属性,在本例中,一旦我们知道了 PC1,就很容易找到 PC2。它只是一条穿过原点与 PC1 正交(90 度角)的直线。
这是图表:

主成分 2 (PC2)。图片由作者提供。
请注意,如果我们有三个属性,那么 PC2 将是穿过原点的直线,与 PC1 正交,并且使数据点到它们在 PC2 直线上的投影的距离最小。那么 PC3 就是穿过原点并与 PC1 和 PC2 都正交的直线。
总结主要成分
如果我们要列出每个主成分,PC1 将是捕获数据方差的最高比例的维度,PC2 是捕获 PC1 不能捕获的剩余方差的最高比例的维度。类似地,PC3 将是捕获 PC1 和 PC2 不能捕获的剩余方差的最高比例的维度,等等。
PCA 的目标是以某种方式转换数据,使我们能够捕捉每个后续维度中的最大方差。
现在我们已经在二维示例中找到了 PC1 和 PC2,我们可以旋转图形,将 PC1 和 PC2 作为我们的新轴:

PC1 — PC2 图。图片由作者提供。
此外,如果我们愿意,我们可以通过将点投影到 PC1 并去掉 PC2 来进行降维。

将数据点投影到 PC1 上。图片由作者提供。
最后,无论出于何种原因,如果我们不喜欢 PC1,我们可以通过将点投影到 PC2 上来降低维数,从而摆脱 PC1。然而,如果我们这样做,我们将丢失大量的数据方差。

将数据点投影到 PC1 和 PC2 上。图片由作者提供。
使用真实数据在 Python 中进行主成分分析
现在让我们动手对真实数据进行 PCA 分析。
设置
我们将使用以下数据和库:
- 来自 Kaggle 的澳大利亚天气数据
- scikit-learn 的标准定标器用于标准化我们的数据,而 PCA 用于执行主成分分析
- 熊猫进行数据操作
- 用于可视化的 Matplotlib 和 Seaborn
让我们导入所有的库:
然后,我们从 Kaggle 下载并获取澳大利亚的天气数据。同时,我们还衍生出一些新的特性。
下面是数据的一个片段:

Kaggle 的澳大利亚天气数据片段。图片由作者提供。
数据相关性
在进行 PCA 分析之前,让我们通过查看相关图来更好地理解我们的数据。

气象数据的相关矩阵。图片由作者提供。
正如我们所见,我们有大量高度相关的变量。例如,在上午 9 点和下午 3 点,MaxTemp 与湿度高度负相关。同时,风速与上午 9 点和下午 3 点的风速高度正相关。
许多变量之间的强相关性表明,主成分分析将帮助我们在潜在的更小数量的维度内捕获大量的方差。
数据标准化
现在让我们对数据进行标准化,得到一组特征,每个特征的平均值为 0,标准差为 1。
执行 PCA
最后,让我们用算法进行主成分分析。
为我们提供了以下结果:

PCA 结果。图片由作者提供。
解释的方差比告诉我们有多少方差被每个后续的主成分捕获。让我们把它画在条形图上,这样我们可以更容易地检查它。

PCA 解释的方差图。图片来自作者。
该图使我们能够很容易地看到,大部分方差仅被前几个 PC 捕获:
- PC1 单独获得了 31%的方差
- 前 2 台电脑获得了 50%的差异
- 前 6 台电脑占据了 80%的差异
- 前 11 台电脑占据了 95%的差异
- 前 13 台电脑占据了 99%的差异
因此,如果您正在建立一个预测模型,您肯定可以在很少或没有损失其性能的情况下使训练更加有效。你可以通过减少维数,只选择几个主要成分来达到这个目的。
请注意,您可以通过简单地选取 X_trans 数组的一个子集来选择顶部的几个组件,或者通过将 n_components =6 或任何您想要保留的维数来重新运行 PCA 分析。
结论
我们已经了解了主成分分析的工作原理,以及如何将其应用到我们的数据中。我真诚地希望您能够使用这些新知识和我提供的代码来构建更好、更高效的模型。
在你离开之前,最后一个有趣的想法要记住。尽管减少维度数量会损失一些数据方差,但这通常会导致更好的模型性能。
虽然这听起来可能违反直觉,但值得记住的是,一些预测算法受到维度诅咒的困扰,这意味着过多的维度会使数据变得稀疏,更难建模。因此,减少维数有助于识别属性之间的联系,从而提高性能。
如果你觉得这篇文章很有用,或者有任何问题或建议,请随时联系我们。
干杯!👏
索尔·多比拉斯
如果你已经花光了这个月的学习预算,下次请记得我。 我的个性化链接加入媒介是:
https://solclover.com/membership
一些相关的文章你可能会感兴趣:
窥视黑盒内部
一个简单的方法来洞察你的 CNN 模型的局限性

Marc-Olivier Jodoin 在 Unsplash 上拍摄的照片
现代算法解决我们当前数据驱动的问题的方式有些神奇。尽管来自神经科学背景,对我们的感觉系统有很强的理解,但当我编写模拟它们的算法时,我感到谦卑。
然而,给我印象最深的一件事是,两者都可以被描述为黑盒,这个术语通常是为深度学习模型保留的。例如,现在当你读这篇文章时,你真正看到的是什么?
单词…句子…段落?不会吧!
你没有看到任何的东西。相反,以不同波长、角度和强度发射的光进入你的视网膜,产生一连串事件,转换成电化学信号。这个信号被发送到你大脑深处的一个小区域,在那里它被整合,然后被投射到你大脑后部的一个大区域……以某种方式让你将信息解释为一篇文章中的文本。
如果你真的想让自己大吃一惊,考虑一下在浅色背景下阅读黑色文字的后果。如果我们的视觉系统被光激活,我们真正阅读的东西更像是文本的“影子”(因为黑色=没有光,因此没有激活我们的视网膜细胞)!
今天,我们将找到窥视黑盒内部的方法,以帮助从卷积神经网络(CNN)的结果中获得洞察力。在之前的文章中,我提供了从头开始构建自己的 CNN 的逐步说明。我用迁移学习将这个模型与其他几个模型进行了比较。有兴趣可以看看我这个项目的 Github 回购!
最近,有人问我是否知道我的模型在哪里出错…这是一个多么好的问题!我掉进了兔子洞,只考虑损失最小化和准确性最大化。当我开发出一个性能良好的模型时,我真的很高兴。与此同时,我不知道这个模型在哪里或者为什么会失败。
这篇文章的目的是试图找出我的模型哪里出错了。为此,我将使用下面的代码生成一个混淆矩阵(注意:这个例子为我从头构建的表现最好的 CNN 提供了一个混淆矩阵,其验证准确率为 76%)。
数据集的刷新
对于那些没有看过这篇文章的人来说,这只是一个快速入门,这是一个针对糖尿病视网膜病变(DR)的多分类任务。诊断的分布极不均衡。在原始数据集中,有 1805 个对照,370 个轻度(1 级),999 个中度(2 级),193 个重度(3 级),295 个增生性(4 级)。
为了解释混淆矩阵,我们的目标是最大化从左上到右下的对角线上的值。我们可以在图 1 中看到,在对照组中,n=361 被正确分类,而 n=8 被分类为 1,n=3 被分类为 2。这还不错。
但是看看其他的类,准确率没有什么值得大书特书的!

图 1 —混淆矩阵,展示了该数据集的真实标签与预测标签。回想一下,0 是对照组,1-4 代表糖尿病视网膜病变严重程度的增加。作者图片
更进一步,我们可以使用下面的代码来绘制顶级错误的图像,以及它们的真实值和预测值。
绘制前 10 个错误的图像向我们显示,它们都被预测为控制图像。

图 2-10 大错误图片。作者图片
通过绘制误差图,我们获得了什么信息?
我认为公平地说,这里提出的模型受到不成比例的群体规模的严重影响。在总共 733 幅验证图像中,650 幅被预测为对照或第 2 类,这与它们在数据集中的过度表现一致。
如何改进模型
由于不平衡的数据集显著影响了模型,有一些方法可以纠正这个问题。查看这篇文章了解更多详情。
- 焦点损失:定制的损失函数将惩罚容易分类的类别,而不是为每个类别分配相等的权重。这一结果将所有阶层置于一个更加公平的竞争环境中,这可能会改善代表性不足的阶层的分类。
- 过采样/欠采样:通过选择过代表组的随机比例(欠采样)或创建欠代表组的副本(过采样),我们可以综合平衡组大小。
我将尝试这些不同的选项,并让您知道它们如何改变模型的准确性。感谢阅读!
人工智能安全的神经网络研究
播客
丹尼尔·菲兰谈可解释性,人工智能安全,以及如何找到重要的问题来解决
编者按:这一集是我们关于数据科学和机器学习新兴问题的播客系列的一部分,由 Jeremie Harris 主持。除了主持播客,Jeremie 还帮助运营一家名为sharpes minds的数据科学导师初创公司。
许多人工智能研究人员认为,随着人工智能能力的增加,很难设计出继续保持安全的人工智能系统。我们已经在播客上看到,人工智能对齐领域已经出现来解决这个问题,但一个相关的努力也正针对安全问题的一个单独的维度:人工智能的可解释性。
我们解释人工智能系统如何处理信息和做出决策的能力将可能成为未来确保人工智能可靠性的重要因素。本期播客的嘉宾将他的研究集中在这个主题上。丹尼尔·菲兰是伯克利的人工智能安全研究员,在那里他受到人工智能先驱斯图尔特·拉塞尔的指导。丹尼尔还运营 AXRP,这是一个致力于人工智能技术对齐研究的播客。
以下是我最喜欢的一些外卖食品:
- 像有效利他主义社区的许多人一样,丹尼尔经常在重要事件上下赌注,以测试他的世界模型。他发现这是一个有用的练习,原因有几个:在游戏中有金融皮肤可以迫使你更客观地思考你认为是真实的或可能的。第二,当你输掉一场赌博时,不得不付钱给某人,这可以作为一个有用的提醒,让你重新评估自己的心智模式:交出钱的痛苦是一个很好的“可教的时刻触发器”。在政治等话题上,赌博尤其有助于避免陷入盲目的意识形态陷阱,在这些话题上,谈论(和无支持的预测)是廉价的:通过在游戏中加入皮肤,你被迫评估你可能会保护或认为理所当然的信念。
- 我们讨论了人工智能风险怀疑论者通常提出的两个论点。第一个想法是,普遍比人更聪明的人工智能可能是不可能的。丹尼尔认为这不太可能,因为我们知道物质的特定排列可以提高人类的智力,人类之间的差异表明人类大脑本身可以轻微调整以大大提高认知能力。出于这个原因和其他原因,当谈到智力时,人类代表了物理可能的顶点似乎是相当不可信的。
- 我们讨论的人工智能怀疑论的第二个论点是一个更常见的论点:随着人工智能变得越来越聪明,它们自然会变得更有道德,因此也更安全。丹尼尔不同意这种观点,并引用了正交命题,即智力和道德不一定相关。我们探索了几个支持正交命题的例子,包括世界上许多最糟糕的独裁者客观上非常聪明的观点。最终,丹尼尔认为,没有理由想象极端水平的智力不能被部署为不道德或不受欢迎的目标服务。
- 丹尼尔的研究涉及在特定任务上训练神经网络(例如,MNIST 数据集上的手写数字分类),并在训练过程后检查它们,以查看网络是否形成有趣的结构。丹所说的“有趣的结构”指的是通过大权重紧密相连的神经元簇,以及通过小得多的权重与网络其余部分松散相连的神经元簇。值得注意的是,事实证明神经网络确实形成了这些结构,这可能为它们的操作和“思考”方式提供了线索。丹尼尔希望,随着时间的推移,这些结构将允许我们解释并最终预测神经网络的行为,并减少事故和人工智能对抗性发展的机会。
你可以在这里的 Twitter 上关注丹尼尔,或者在这里的 Twitter 上关注我。

章节:
- 0:00 介绍
- 1:30 有效利他主义
- 5:05 带来统计数据
- 10:55 与其他论点互动
- 17:45 人类类比的风险
- 23:30 更新心智模型
- 31:00 人工智能安全研究中的警告
- 35:47 清晰度和安全性之间的关系
- 45:20 我们对现实的假设
- 59:55 人工智能社区内部的紧张关系
- 1:04:40 总结
笔和纸贝叶斯
如何通过优雅的结果享受更简单的计算
这个故事假设你对贝叶斯框架有一个基本的了解——相对于经典的频率主义者框架。但差不多就是这样了:)我们将看到模型的学习步骤不需要大量的数字,因为一些幸运的结果可以大大加快速度。每当资源紧张时,例如在减少的硬件或在线学习环境中,这种改进就开启了这种东西的采用。

“以前,当我看着一张白纸时,我的脑海里充满了各种想法。现在我所看到的是一张白纸“保罗 Erdős |照片由亚伦负担在 Unsplash
离开被打败的贝叶斯路径
贝叶斯学习的主要思想是迭代地应用贝叶斯定理来寻找以可能的最佳方式描述某些数据行为的分布。该描述是关于一些产生新的数据点并预测接下来哪种数据点的机器,告知这种机器在两种成分上看起来如何。第一个是从一些或多或少有根据的对你的数据看起来如何的猜测开始。第二个是我们不断观察的数据,随着时间的推移,这完善了我们最初的假设。
首先,值得一提的是贝叶斯定理本身:

一些非常基本的符号(并为格式道歉,见结束语):
- x ᵢ是我们看到的第 i 个数据点,来自我们试图了解的随机变量 X̅
- θ̅ 是模型参数的向量,所以分布 D 中的参数x̅~ d(θ̅)——我们的目标是为它发现合理的值;如果 X̅~ N(μ,σ ) 那么 θ̅=(μ,σ )
- α̅ 是模型 H 统治 θ̅ 的参数向量:由于我们将 θ̅ 视为随机向量,它遵循一个分布,其参数由 α̅ 表示,因此θ̅~h(α̅超参数
注意:我们将在下面的例子中使用 α̅ ,但这是巧合。

学习一些东西——每当你看到新的东西时,就刷新你已经学到的东西。|照片由凯莉·西克玛在 Unsplash 上拍摄
该机制依赖于几个项目的定义:
- 先验分布:这是初始假设 H ,定义为关于数据分布参数的分布,所以p(θ̅|α̅);它的参数是模型的超参数
- 后验分布:这是根据进来的新数据点对初始假设的更新,所以 p(θ̅|x ₁ ,x ₂ ,…,xᵢ,α̅);它的超参数是我们用来初始化先验分布的原始超参数的函数,当然还有数据点
- 抽样分布:这就是你的实际数据分布 D 的样子,它被称为似然性;正是 p(x ᵢ |θ̅)
- 边际可能性:抽样分布,在超参数上被边缘化,这就是所谓的证据,p(xᵢ|α̅);我们在问自己,先验的一个特定实例如何与数据相兼容
- 后验预测分布:这是学习引擎,因为它定义了任何可能性假设下的事件概率——不仅仅是当前的假设,这是最新的后验超参数集——它是 p(x ᵢ |x ₁ ,x ₂ ,…,xᵢ₋₁,α̅);这是与基于简单最大似然法的频率主义方法的主要概念差异。
您可以从当前可能性或后验预测性中对一些数据点进行采样,但有一个根本的区别:前者仅说明截至目前更新参数的特定假设,而后者说明一个点仍可能来自任何其他可能性的事实——即使随着更多数据点的出现出现一个选项,我们也不知道真相。
注意后验分布和后验预测分布之间的区别,如上所述,它们是而不是的同义词。特别是:
- 后验是随着新数据点的到来而对先验的重复更新;它的形式是 p(θ̅|x ᵢ ) ,它说了一些关于可能的模型参数 θ̅ 视一个点 x ᵢ的条件
- 后验预测对实际点 x ᵢ说了些什么,将所有可能的模型假设 θ̅ 一起考虑(边缘化)
后验分布是关于数据分布的参数,这就是为什么我们说在贝叶斯框架中,我们不试图准确预测下一个点会是什么——提出一个最佳候选是基于频率最大似然的方法——而是告知它应该看起来如何,因此它的分布。
我们的定义。和 5。上图:你可能认为这是我们可以使用的主要方法工具,当我们看到越来越多的要点时,它会变得更加锋利。记住这些定义并不重要,但当然要理解它们的含义。我们将在后面看到后验预测的作用!
一般来说,更新后验概率在计算上具有挑战性,因为你基本上必须求解积分。对于先验和可能性的任意选择,你可能需要一些库的帮助,比如 PyMC3 。但是……对于许多已知的组合来说,后验概率的封闭形式确实存在:这是一个很大的优势,因为它为在线学习的采用打开了大门——当你只希望能跟上数据流时,你就不会被昂贵的数字所困。
一个阐明性的例子
这种神奇的配对被称为共轭先验,理解任何一张表上的几个例子都是确保你理解正在发生什么的一个极好的方法。共轭先验到底是什么?可能性函数(见上文)是我们对产生数据的过程的假设:如果我们正在研究一个硬币投掷,这个过程将是伯努利过程——代表特定硬币投掷的每个变量都是一个指标,0 代表正面 1 代表反面。伯努利分布的参数是人头的概率 p 。先验分布以概率分布的形式,设定了这个 p 应该是什么样子的初始假设,希望是有根据的假设。每次抛硬币,随着新的数据观测值的到来,这个先验被更新,得到的分布被称为后验分布。如果新更新的后验概率来自与先验概率相同的族,那么我们说先验分布是所选过程似然分布的共轭先验。一个分布族实际上可能是一个纯粹形式上的,而且正在消失的概念。它是一组给定的分布,可以用有限数量的参数一次性全部描述出来。例如,正态分布可以被视为一个族分布,特别是一个指数分布;如果通过正确设置参数,可以从同一个解析形式获得两个分布,那么它们属于同一个族。
例如,假设我们认为我们的数据点来自一个帕累托分布,那么这将是我们的可能性。这种厚尾分布是由一个标度 xm 和一个形状 k 定义的,标度告诉我们最小值是多少,k 越靠近轴越高(注意:维基百科使用XM——不幸的是不能用 Unicode 呈现——标度是α,形状是α,我们用 k 代替)。对其样本不能为负的任何事件进行建模是有用的,并且我们预计大值并非不可能(非正式地,比我们用半正态所能做的更好)。

帕累托咖啡:80%水,20%咖啡因。|照片由 Austin Distel 在 Unsplash 上拍摄
让我们确定尺度;在大多数应用中,这是一件合理的事情:假设我们对保险公司索赔的预期成本建模感兴趣,最小成本可以作为被拒绝索赔的处理和办公成本(保险公司不支付客户,但即使说不也有一些成本)。通常我们只在固定了一个参数子集后才研究一个共轭;总的来说,有可能找到一种形式来学习所有这些知识,我们将简要地谈到这一点。假设我们将比例固定为某个值 xm=xm* 。请注意,对于我们示例中的所有计算,我们将舍入到两位小数(这将在各处引入一些小的舍入误差)。
所以帕累托是我们的可能性,比例是固定的;我们最初对这个形状的猜测是什么?形状值的分布应该是怎样的呢?从表中我们看到伽马是一个;这个分布有两个参数,α和β,我们学习模型的超参数。对于我们的帕累托来说,一个好的选择是 k=2.1 ,其中 2 作为方差未定义的病理值,因此 2+0.1 保持足够接近。将 gamma 集中在 2.1 上:我们知道 gamma 的平均值是 μ=α/(α+β) 所以我们设置: α/(α+β)=2.1 集中在它周围,并固定 α=2 (我们在这里没有好的猜测,所以我们选择一个好的 α )所以我们得到: β = -1.04 。所以结果是 Gamma(2,-1.04) :很好,我们已经决定了我们的先验是什么样子的!在该表中,您还可以看到我们如何在读取更多 xᵢ数据点时更新这两个超参数,其中 n 是迄今为止的观察次数。更新后的α和β的后验概率是什么样的?通过共轭的美丽,在看到她从 Pareto 得到的数据后更新 Gamma 仍然会产生一个 Gamma,带有更新的参数。请注意后验预测是如何从表格中消失的。这意味着我们可以继续从可能性过程中生成数据,但是我们还没有真正的方法。)来说明特定数据点接下来出现的概率,而不局限于关于可能性的特定假设。
链接参数
对于具有多个参数的可能性,如上例所示,默认设置是固定所有参数,除了要学习的参数,该参数成为模型参数。这是针对单参数共轭的。没有什么能阻止我们尝试一下子了解所有的参数。从这个意义上说,对一个单一参数的推理是大多数例子和应用的情况,但它不是一个要求或任何东西。你可以在假设的基础上思考假设。
相当详细的讨论可以在这里找到。以高斯共轭为例,其中先验和似然都是正态的。我们可以对平均值进行第一次猜测: θ ~ N(μ₀,τ₀) 并将其插入实际的后验概率 X̅ ~N(θ,σ ₀) 因此从三个超参数开始,两个获得平均值(一个平均值和一个方差,因为我们有一个正态先验),一个获得插入该平均值后的点(一个方差,也在正态先验下)。参见此处的算出示例(第。3).注意:精度是方差的倒数, τ=1/σ 。

没有什么能阻止你根据自己的假设做出假设——逻辑被解放了!|照片由郭佳欣·阿维蒂斯扬在 Unsplash 上拍摄
显示数据
我们现在将把帕累托例子带到生活中,显示分析似然更新和数字预测后验概率,后者没有一个安全的封闭形式。

这将使你的想法成为一个伟大的想法。|图片由 Franki Chamaki 在 Unsplash 上拍摄
共轭案
您可能希望打开示例笔记本。
对于帕累托的例子,在维基百科页面上我们发现了一个非常有趣的(并且无人认领,至少在撰写本文的时候)参考:Steam 上的用户玩不同游戏的时间量是帕累托分布的。根据著名的原则,这基本上意味着普通用户将花费 80%的时间玩他或她拥有的 20%的游戏——有些游戏玩得很多,但大多数不是。这里的基本上是有意义的,因为只有对于一个非常具体的尺度和形状选择,我们才能得到帕累托分布的经典 80–20 行为——我们不知道这是否会成为我们数据集的情况。我们想研究一个游戏被玩 x 小时的概率。
事实证明(这真的是一个巧合),在 Kaggle 上我们可以找到完美的数据集(已经上传,连同其原始许可证,在回购):数百名 Steam 用户的用户行为。

蒸汽上有吃豆人吗?如果是的话,我很确定它的游戏时间是集合中的异常值。|照片由 Kirill Sharkovski 在 Unsplash 上拍摄
从数据中我们可以看出,游戏时间的最小值是 0.1 小时(如果你在五分钟后就厌倦了,那确实是一个非常糟糕的游戏);这将成为我们的固定比例, xm=0.1 。在现实生活中,你必须只是猜测,因为你事先没有所有的数据,所以你当然不能取最小值!或者你必须依赖于你所建模的事物的法则或硬性限制。追逐形状参数 k 将会精心制作我们的帕累托图。我们对伽玛整形 k 的最初猜测是什么?我们可以根据我们一般预期的平均游戏时间,从一个有根据的猜测开始;从网站上这个数据集的元数据来看,不清楚它指的是哪个时期,也不清楚它是否被更新过;我们可以从绝对的角度来选择,从 2003 年(Steam 启动)到 2017 年(数据集创建)。我们说 7 个小时好吗?作为补充:如果我们的猜测不准确,这真的是个问题吗?如果我们有足够的数据,并检查了一些无聊的条件,它不是,因为:任何偏见来自一个最初的坏选择在先验将被冲走,因为我们阅读更多的数据,并调整后验;这要感谢这个定理,它告诉我们先验变得不相关,渐近地。人们甚至可以谈论贝叶斯模型的收敛速度,但那是另一个故事了。我们可能没有足够的数据,但规律性检查是有的。那么我们如何得到想要的形状呢?首先,我们必须问一下 k > 1 否则 μ=∞ 这在我们的例子中是不正确的:一般的游戏倾向于玩一个有限的小时,也有一个小的小时,我们选择的是 7 。插入 xm=0。进入均值对于这种情况,我们求解出 k 的帕累托均值,即:

帕累托分布平均值,k 和 xm 如上。你能找出这个错别字吗?
这给了我们 k = 1.01 T21。首先,我们期望我们的数据过程是一个帕累托(0.1,1.01) 。

用 essycode 创建;没有重新调整。
我们从数据集中获取的每个数据点都是某个用户和某个游戏的注册游戏时间;由于我们对全局行为感兴趣,我们丢弃了用户和游戏信息,只保留小时记录。
现在的问题是:对于我们的先验,什么是好的价值?我们的帕累托参数是 xm=0.1 和 k=1.01 。对于伽马,我们设置 α=1 ,因为它是最小和最好的形状(对于 α ≥ 1 ,分布被很好地定义并且也有一个模式);观察到我们的目标帕累托形状是 k=1.01 ,我们将通过选择 β=0.99 观察到 μ=α/β 来将伽马集中在 k 周围。同样,我们的帕累托标度 xm 是固定的,并且作为未知模型参数的形状被假设为表现为伽马(1,0.99) (这里用标度 θ 表示,它是我们的速率 β 的倒数)。从这个分布中取样,以帕累托分布的形式给出了我们的可能性的一个实例。
现在,是获取数据的时候了。该集合按用户 id 排序,它只是用户的一个小样本。我们只对娱乐活动感兴趣。因为我们想有一种从未知和帕累托假设分布中随机抽样的感觉,我们将稍微打乱行。
这是前几次迭代的伽马后验均值的演变,因此随着超参数的更新,图为 μ=α/β :

伽马后验均值图;你可以看到它在大约 n=50 次迭代后稳定下来。
你可以看到它在 0.25 附近非常快地稳定下来,实现了我们的帕累托似然的形状参数 k 的期望值。总的来说,这可以被视为一个好的迹象。为了更进一步的信心,我们可以对不同的范围伽马选择进行一点试验,看看收敛速度是否保持恒定——或者至少不会爆炸。关于收敛,一些结果确实如我们上面提到的那样成立,但是这个讨论暂时带了一个太先进的标志,并成为一些其他博客实验的好材料:)
以下是我们的后验概率和可能性的最终结果:


所以我们最终得到的是一个规模 xm=0.1 形状 k=0.25 的帕累托分布。这毕竟不是很好:如果规模小于 1 ,均值和方差都趋向于无穷大,这是获得任何分布感觉的两个重要特征。如果这是一个保险风险模型,我们真的应该退出,因为这将意味着:预期的索赔成本是巨大的!
在我们的例子中,我们确实看到一个合法的帕累托出现了,尽管这个帕累托信息量不大,看起来也不美观。有可能随着更多的数据,结果转移到更好的模型,即形状大于 1 的模型,因此主力矩是有限的。但由于我们很快就在这个数据集上收敛了,我们真的希望看到更多来自分布中间的数据,而不是来自肥胖尾部的数据——如果事情真的过于普遍,我们就说不出任何有趣的东西,肥胖尾部是一条平坦的线,我们可以在任何地方着陆。
我们从中可以得出什么结论?当然,我想到的问题是:我们的假设是合法的吗?总的来说,无论何时,只要得到的分布看起来不太好,我们就应该保持谨慎,抛开理论不谈,因为一个看起来不太好的分布,例如,一个缺少一些时刻的分布,是不适合工作的,并且它也没有什么信息价值。
如果我们认为帕累托就是我们的过程,那么它会是一个数据吗?例如:
- 没有足够的来自整个 Steam 群体的数据:这个假设在这么少的样本下可能不成立——我们可能需要更多的数据
- 数据采样不良:如果该数据来自带有固有偏差的一些采样,或者通常代表不同的子群体(例如顶级玩家、单一土地等)。)我们可能不得不确定这不是事实,或者寻找一个更适合于这片土地的假说
为了强调我们的选择是好的还是不好的,我们也可以看看贝叶斯范围之外,考虑更经典的拟合优度测试。
为了这个博客的目的,我们坚信帕累托仍然是一个很好的描述。总的来说,对于根据数字和经验判断过于古怪的假设,要保持警惕;)
让我们看看如何保持帕累托假设,并在数字案例中尝试,我们得到了完全相同的数字。
数值案例
请看示例笔记本这里。

链条的另一个特写。这次是马尔可夫链。|图片由 JJ 英在 Unsplash 上拍摄
由于我们没有预测后验概率的分析形式,使用共轭方法,我们可以发现可能性应该是什么样子,我们也可以从这种最佳可能性的特定实现中取样,但是……我们不能看一眼某个匿名数据点会是什么样子,匿名是指源自我们新发现的特定可能性或任何其他类似的可能性——所以形状仍然来自更新的 Gamma。
为了得到一些帮助,我们将求助于数字抽样。详述这种方法如何工作超出了我们的讨论范围。我们将使用著名的 PyMC3 库。
我们模型的定义非常简单:https://gist.github.com/ad1a76de0cd45db8a1e580cb6033b1af
我们从贝叶斯构建模块中提取不同种类的样本,最后一个当然是我们遗漏的:【https://gist.github.com/8f48b5e26ed1bb6bd1d83e643e0424e1
绘制先验我们看到了一些我们已经知道的东西:帕累托图的形状围绕着 k=0.25 :
很高兴看到我们得到了相同的结果!
为后验预测提供若干样本:https://gist.github.com/7159b872941b272307ea4f272e59bebb
你可能已经注意到了数字采样比纸笔循环花费更多的时间。
重述
我们已经看了一个在真实数据上进行贝叶斯学习的例子,借此机会看看数字方法旁边的分析方法。你对我们的模型满意吗?如果没有,为什么?😃
最后,关于笔和纸的事情:在这个 supa-dupa 机器学习的世界里,只进行加法和乘法来得到结果,真的感觉就是这样——但是让计算机做数学绝对是一个更好的主意。
第 t+1 时间见!

再见了。| Jonathan Kemper 在 Unsplash 上拍摄的照片
找到 Markdown 版本 此处 及其 HTML 渲染 此处 。作为结束语,很遗憾 Medium 不支持 LaTeX 公式,也不允许像 KaTeX_Math 这样的自定义字体。的。md 用 这种 这样的扩展名可以正常渲染。

让我来看看这个乳胶配方……|照片由克莱班克斯在 Unsplash 上拍摄
作者:里卡多·温切利
作者要感谢他在荷兰的雇主毕马威对这篇博客故事发展的支持,以及同事尤因·斯密茨、雷德默·伯滕斯和安布尔·普拉茨曼对其内容的审阅。
用新的 ASGL Python 模块惩罚回归
随身携带的新 Python 模块

伊利亚·巴甫洛夫在 Unsplash 上的照片
有几个用于回归的 Python 模块,每个模块都有其特殊性和局限性。现有 Python 模块的使用很大程度上取决于用户想要执行的回归类型及其目标。如果回归是简单的,并且变量是连续的,在许多情况下 NumPy 库有特定的方法来处理这个问题。另一方面,如果您对更复杂的定量和定性变量回归问题感兴趣,则 Scikit-learn 模块可根据具体情况和问题为用户提供多种选择。
最近,一个新的 Python 模块已经向公众开放,其目的是改善当前 Python 模块在惩罚回归方面的一些限制,并提高操作性能。模块的名称是 asgl(自适应稀疏组套索) 。在这篇文章中,我讨论了这个模块是关于什么的,它应该做什么,以及你是否应该在你的回归问题中使用它。
当前的事态
许多软件为用户提供了根据具体问题以不同尺度进行回归的可能性。人们可以用 Python、Mathematica、R 和 Matlab 进行回归,这里仅举几个例子。
回归可以分为几类,其中最重要的一类是惩罚回归。当预测值的数量( p )远大于观察数据的数量( n )时,这种类型的回归通常在高维数据中非常重要。
惩罚过程包括向最小平方成本函数添加正则化器C(x);D) 。LASSO 是最重要的正则化方法之一,它给最小二乘代价函数增加了一个正则化惩罚。LASSO 的目标是提供线性模型系数的稀疏估计。套索的其他重要品种包括群套索和稀疏群套索。
稀疏组套索是套索和套索组的概括,其目标是生成稀疏组之间和之内的回归解。
上述所有基于套索的方法的共同问题是,它们都使用恒定的惩罚率 𝛌 ,这有可能严重影响变量选择的质量和准确性。为了解决这个问题,一些研究者提出了所谓的自适应群套索(asgl)方法来计算成本函数C(x;d)。读者可以在这篇文章中找到这种方法的数学描述。该方法的目标是在高维度和低维度数据集中提供非常好的误差估计。
正如我上面提到的,可以做不同的层次(套索等。)使用 Scikit-learn 和 Statsmodel 进行回归。然而,由于 asgl 误差估计器是一个相对较新的概念,这个估计器不包括在上面提到的 Python 模块中。asgl Python 模块将标准套索和组套索扩展到适用于线性和分位数回归的情况,我将在下面进行描述。
线性和分位数回归的 asgl
我假设读者知道什么是线性回归,以及它是如何正式完成的。然而,读者可能不太熟悉分位数回归。这种类型的回归是由 Koenker 和 Basset 在 1978 年提出的,它适用于存在异方差和异常值的情况。该方法的目标是提供自变量条件分位数作为协变量函数的估计值。
当仅在线性和分位数回归的情况下可以使用自适应方法时,asgl 误差估计器提供了一个框架。正如我上面提到的,在线性和分位数回归的情况下,Statsmodel 和 Scikit-learn 中不存在自适应方法。例如,Scikit-learn Python 模块根本没有给用户提供执行分位数回归的可能性,更不用说自适应情况了。
在 asgl 方法中,通过使用惩罚自适应方法找到的参数向量𝜷的解是由给出的:

其中 R( 𝜷 ) 是仅用于线性和分位数回归的风险函数。在线性回归的情况下,该函数由下式给出:

另一方面,在分位数回归的情况下,它由下式给出:

其中函数𝜌_𝜏是所谓的损失检查函数。在上面的参数向量方程中, 𝛌 是控制惩罚权重的惩罚率, K 是组的数量,𝜷^l 是从第 l 到第组的𝜷分量的向量,在组套索中, p_l 是每个 l 到第组的大小,𝛼是控制套索和组套索之间的平衡的参数。波浪号矢量 v 和 w 是 asgl 模型中定义的权重矢量。更多细节可以在原研文章中找到。
asgl 方法带来的关键概念,即包含在 v 和 w、 中的关键概念是,重要的变量必须具有较小的权重,因此处罚较轻,而不太重要的变量必须具有较大的权重,并且处罚较重。这种方法通过提高准确性和变量选择为用户提供了更多的灵活性。
如何用 Python 实现 asgl?
与其他 Python 包一样,使用 asgl 模块/包非常简单。可以使用以下命令完成安装:
pip install asgl
aslg 模块基于其他 python 模块,如 NumPy(1.15 版或更高版本)、Scikit-learn(0 . 23 . 1 版或更高版本)、cvx py(1 . 1 . 0 版或更高版本)。该模块还需要 Python 版本 3.5 或更高版本。
另一种可能是使用 GitHub 并提取以下存储库:
git clone https://github.com/alvaromc317/asgl.git
然后,在存储库提取之后,您必须运行以下代码来执行 setup.py 文件:
cd asgl
Python setup.py
你能用 asgl 做什么?
asgl 模块使用四个主要的类对象:ASGL 类、重量类、CV 类和 TVT 类。有了这些类,就可以使用上面描述的自适应方法来解决实际的线性和分位数回归问题。
ASGL 类是最重要的一个,它可以用来执行套索、群组套索、稀疏套索和自适应稀疏套索。asgl 类的缺省参数是(更多细节请参见 ASGL 模块作者撰写的这篇 arXiv 文章):
model = asgl.ASGL(model, penalization, intercept=True, tol=1e-5,lambda1=1, alpha=0.5, tau=0.5, lasso_weights=None,gl_weights=None, parallel=False, num_cores=None, solver=None,max_iters=500)
ASGL 类有三个主要方法,分别是:拟合、预测、和检索参数值。****配合功能的调用方法是:
fit(x, y, group_index)
其中 x 是预测向量的 2D NumPy 数组, y 是 1D 独立变量向量,group_index 是长度等于问题中存在的变量数量的 1D NumPy 数组。
预测函数的调用方法是:
predict(x_new)
其中 x_new 是一个 2D NumPy 数组,其列数等于原始矩阵中的列数。为了进行预测,用户必须运行以下命令:
*predictions = model.predict(x_new)*
因此,我们可以看到,这些模块的使用方式与其他 Python 模块(如 Statsmodel 或 Scikit-learn)相当标准。
另一个重要的方法是retrieve _ parameter _ values,它的作用是给出用 asgl 方法求解最小二乘回归得到的模型参数。要运行它,需要调用方法:
*retrieve_parameters_value(param_index)*
其中,param_index 是不大于 model.coef_ list 长度的整数。要显示参数的解,需要运行:
*N
model.retrieve_parameters_value(param_index = N)*
其中 N 是整数。
正如我上面讨论的,asgl 包有另外三个主要的类,每个类都有自己的重要性,可以根据用户的需要来使用。我在这里不讨论这些其他的类,读者可以参考 asgl 包存储库来获得更多的信息。
结论
上面我简要讨论了新的 asgl Python 包及其主要用途和特性。该软件包为用户提供了使用自适应方法进行线性和分位数回归的可能性。
你应该使用 asgl 模块吗?这个问题的简短回答是:是的,你应该试一试。但是,我建议您首先了解 asgl 方法背后的理论,看看是否可以将其应用于您的数据科学和机器学习问题。正如作者所述,这个新模块让用户有可能首次在 Python 中执行分位数回归,并使用 asgl 包提供的自适应方法来改进变量选择和预测。
如果你喜欢我的文章,请与你可能对这个话题感兴趣的朋友分享,并在你的研究中引用/参考我的文章。不要忘记订阅将来会发布的其他相关主题。
钟摆:可能是最好的 Python 日期时间库

图片由来自 Pixabay 的 Manfred Antranias Zimmer 拍摄
Python datetime 模块的绝佳替代产品
datetime 模块是 Python 中最重要的内置模块之一。它非常灵活和强大,为实际编程问题提供了许多现成的解决方案。例如,时间增量是我最喜欢的工具之一。
但是,我不得不说,datetime 模块也有一些限制。例如,当我们处理时区时,它通常会揭示短缺。有时,我们不得不引入一些第三方库作为补充。此外,datetime 模块中的某些方面不是很直观,或者在其他编程语言中不常用。
在本文中,我将介绍一个名为 Pendulum 的第三方库,它将解决内置日期时间模块的所有问题。
1.日期时间的插入替换

使用一些第三方库比如pytz来解决一些 Python datetime 不擅长的问题并不少见。但是,我们仍然需要导入 datetime 模块,并将其作为基本模块使用,因为我们需要使用它来实例化 datetime 对象。
让我告诉你为什么钟摆是一个下降的替代品。首先,我们需要使用 pip 安装它。
pip install pendulum
库的名字有点长,所以我建议用别名导入。
import pendulum as pdl
虽然pd是一个较短的缩写,但我会把它留给熊猫。不想制造任何混乱。
让我们用 Pendulum 创建一个 datetime 对象,看看它的对象类型。
from datetime import datetimedt = pdl.datetime(2021, 11, 6)
isinstance(dt, datetime)

没有魔法。只是因为 Pendulum 继承了 Python 的 datetime 对象。因此,我们不需要担心使用 datetime 模块中的一些原始特性。从字面上看,钟摆日期时间对象是 Python 日期时间对象。
2.时区

钟摆图书馆最令人印象深刻的特征是时区。这也是内置 datetime 模块的关键问题之一。在 Python 3.9 之前,如果我们想使用 IANA 时区,我们必须涉及pytz。
有了钟摆库,我们可以轻松地创建一个带有时区的 datetime 对象。
dt_melbourne = pdl.datetime(2021, 11, 6, tz='Australia/Melbourne')
dt_brisbane = pdl.datetime(2021, 11, 6, tz='Australia/Queensland')
print(dt_melbourne)
print(dt_brisbane)

在上面的例子中,我们同时创建了两个对象。然而,时区是不同的。钟摆也让我们可以很容易地比较时间。
dt_melbourne.diff(dt_brisbane).in_hours()

多简单啊!比较两个不同时区的 datetime 对象并获得准确的结果!
如果我们需要定义多个 datetime 对象并希望重用 timezone 字符串,我们可以创建一个 timezone 对象并将其传递给 datetime 构造函数。
my_timezone = pdl.timezone('Australia/Melbourne')
dt_melbourne = pdl.datetime(2021, 11, 6, tz=my_timezone)print(dt_melbourne)
print(dt_melbourne.timezone.name)

一个更酷的功能是将时间返回到不同的时区。例如,墨尔本现在是午夜,那么布里斯班现在是几点?

3.日期时间解析

解析日期时间可能是编程中最常见的用例。Python 日期时间模块做得很好。然而,与大多数其他编程语言相比,Python 使用不同的格式%Y%m%d。
Pendulum 允许我们使用如下的通用格式代码。
pdl.from_format('2021-11-06 22:00:00', 'YYYY-MM-DD HH:mm:ss')

此外,它完全支持 RFC 3339 和 ISO 8601 格式,以及其他一些常见的格式。这意味着我们不必指定格式代码来将字符串解析成日期时间。
pdl.parse('2021-11-01 22:00:00')

Pendulum 还集成了许多常见的日期时间扩展,如 dateutil 。如果我们希望库依赖于 dateutil 解析器,我们可以传递标志strict=False。
pdl.parse('21-11-06', strict=False)

除此之外,钟摆还支持更多的动态格式。例如,只有数字的日期时间。
pdl.parse('20211106')

这个非常有趣,指定了年份,星期号和星期几,钟摆给你正确的日期时间。
pdl.parse('2021-W44-6')

如果我们特别想拥有一个日期对象或者时间对象,只需要指定exact=True,这比 Python 的 datetime 模块简单多了。
pdl.parse('20211106', exact=True)
pdl.parse('22:00:00', exact=True)

4.字符串格式

在将字符串解析成 datetime 对象之后,下一件重要的事情就是将 datetime 输出成带格式的字符串。
首先,让我们有一个日期时间对象。由于 Pendulum 继承了 Python datetime,所以我们可以使用所有的方法,比如now()。
dt = pdl.now()

然后,让我从 Pendulum 中挑选几个“to string”方法的例子,看看用现成的格式输出 datetime 是多么容易。
dt.to_date_string() # with date only
dt.to_time_string() # with time only
dt.to_formatted_date_string() # month_abbr date, year
dt.to_day_datetime_string() # day, month_abbr date, year hh:mm am/pm
dt.to_iso8601_string() # to ISO 9601 standard
dt.to_atom_string() # to Atom format
dt.to_cookie_string() # to cookie style format

当然,我们可以使用格式代码定制输出字符串,格式代码更直观。
dt.format('DD MMMM, YYYY dddd HH:mm:ss A')

另一个很酷的东西是,我们可以很容易地将一些不相关的字符串添加到格式字符串中,并让它们从格式中逸出。
dt.format('[Hi! Today is] DD MMMM, YYYY dddd HH:mm:ss A')

5.人类可读性

图片来自 Pixabay 的 fancycrave1
在内置的 Python datetime 模块中,timedelta 工具非常好地完成了比较工作。然而,在比较两个 datetime 对象时,Pendulum 甚至可以通过提供一些更人性化的输出来改进它。
例如,diff_for_humans()方法将日期时间对象与当前时间进行比较,并返回一个非常人性化的输出。
dt1 = pdl.datetime(2021, 1, 1)
dt1.diff_for_humans()dt2 = pdl.datetime(2021, 11, 7, 1)
dt2.diff_for_humans()

6.查找相对日期时间

图片来自 Pixabay 的 Andreas Lischka
内置 Python datetime 可以改进的一个方面是根据给定的日期时间查找相对日期时间。例如,当我们想找到当月的最后一天时,我们必须使用 datetutil 模块中的relativedelta。
from dateutil.relativedelta import relativedeltadatetime.datetime(2013, 2, 21) + relativedelta(day=31)
此外,代码可读性不强,因为我们使用了day=31作为参数,尽管它在一个月少于 31 天的时候也可以使用。
而在钟摆中,这再简单不过了。
pdl.now().start_of('day') # find the start time of the day
pdl.now().start_of('month')
pdl.now().end_of('day')
pdl.now().end_of('month')

内置 datetime 模块的另一个不便之处是查找一周中的某一天。例如,如果我们想找到下周一的日期,这可能是最简单的方法。
from datetime import datetime, timedeltadatetime.now() + timedelta(days=(0-datetime.now().weekday()+7)%7)
它完成了工作,但是可读性很差。开发人员需要花一些时间来理解这行代码的逻辑。
用钟摆,就是这么简单。
pdl.now().next(pdl.MONDAY)

我们甚至不去考虑应该用 0 还是 1 来表示星期一,因为钟摆使用了枚举法。
同样,我们可以用previous()方法找到前一个星期二,如下所示。同样,我们可以通过设置参数keep_time=True来保留时间部分。
pdl.now().previous(pdl.TUESDAY)
pdl.now().previous(pdl.TUESDAY, keep_time=True)

7.一些额外的便利

这个库中藏着更多的“糖”。再举几个例子,比如得到昨天或者明天。
print(pdl.yesterday())
print(pdl.today())
print(pdl.tomorrow())

对于不同的文化和语言,输出带有区域设置的日期时间也非常容易。
pdl.now().format('dddd DD MMMM YYYY', locale='zh')

再举一个例子。如果一个人出生于 1988 年 1 月 1 日,这个人的年龄是多少?
pdl.datetime(1988, 1, 1).age

在官方文档中可以找到更多有趣的东西。
摘要

在本文中,我介绍了第三方 Python 库 Pendulum。它是 Python 内置 datetime 模块的替代产品。通过使用这个库,datetime 模块可以解决的许多问题,例如查找相对日期,现在都可以很容易地解决。更重要的是,Pendulum 提供了整洁干净的 API 来提高我们代码的可读性,这些解决方案更加直观。
https://medium.com/@qiuyujx/membership
如果你觉得我的文章有帮助,请考虑加入 Medium 会员来支持我和成千上万的其他作者!(点击上面的链接)
用 Python 实现感知器算法
从零开始的机器学习:第 6 部分

作者图片
在这篇文章中,我们将看看感知器算法,它是用于二进制分类的最基本单层神经网络。首先,我们将查看单位阶跃函数并查看感知器算法如何分类,然后查看感知器更新规则。
最后,我们将为我们的数据绘制决策边界。我们将使用只有两个特征的数据,由于感知器是一个二元分类器,因此将有两个类。我们将使用 Python NumPy 实现所有代码,使用 Matplotlib 可视化/绘图。
介绍
感知器算法的灵感来自大脑中的基本处理单元,称为神经元,以及它们如何处理信号。它是由弗兰克·罗森布拉特利用麦卡洛克-皮茨神经元和赫布的发现发明的。感知器研究论文。
感知器算法并没有在实践中广泛使用。我们研究它主要是因为历史原因,也因为它是最基本和最简单的单层神经网络。

神经元;来源维基百科
感知器
让我们用下面的数据作为激励的例子来理解感知器算法。
**from sklearn import datasets****X, y = datasets.make_blobs(n_samples=150,n_features=2,
centers=2,cluster_std=1.05,
random_state=2)**#Plotting**fig = plt.figure(figsize=(10,8))****plt.plot(X[:, 0][y == 0], X[:, 1][y == 0], 'r^')
plt.plot(X[:, 0][y == 1], X[:, 1][y == 1], 'bs')
plt.xlabel("feature 1")
plt.ylabel("feature 2")
plt.title('Random Classification Data with 2 classes')**

作者图片
有两个类,红色和绿色,我们想通过在它们之间画一条直线来区分它们。或者,更正式地说,我们想要学习一组参数theta来找到一个最佳超平面(我们的数据的直线),它将两个类分开。
对于 线性回归 我们的假设(y_hat)是theta.X。然后,对于 Logistic 回归 中的二元分类,我们需要输出 0 到 1 之间的概率,所以我们将假设修改为——sigmoid(theta.X)。我们对输入要素和参数的点积应用了 sigmoid 函数,因为我们需要将输出压缩在 0 和 1 之间。
对于感知器算法,我们对theta.X应用不同的函数,即单位阶跃函数,其定义为—

来源:吴恩达课程
在哪里,

来源:吴恩达课程
与输出 0 和 1 之间概率的逻辑回归不同,感知器输出的值恰好是 0 或 1。
该函数表示,如果输出(theta.X)大于或等于零,则模型将分类为 1 (例如红色),如果输出小于零,则模型将分类为 0 (例如绿色)。感知算法就是这样分类的。
让我们用图形来看单位阶跃函数—

单位阶跃函数;来源
我们可以看到对于 z≥0 , g(z) = 1 和对于 z < 0 , g(z) = 0 。
让我们编写步骤函数。
**def step_func(z):
return 1.0 if (z > 0) else 0.0**
作为神经网络的感知器

一个感知器;作者图片
我们可以通过上图直观的了解感知器。对于每个训练示例,我们首先取输入特征和参数的点积,theta。然后,我们应用单位阶跃函数进行预测(y_hat)。
如果预测是错误的,或者换句话说,模型对这个例子进行了错误分类,我们对参数θ进行更新。当预测正确时(或者与真实/目标值y相同),我们不更新。
我们来看看更新规则是什么。
感知器更新规则
感知更新规则非常类似于梯度下降更新规则。以下是更新规则—

来源:吴恩达课程
请注意,即使感知器算法可能看起来类似于逻辑回归,但它实际上是一种非常不同的算法,因为很难赋予感知器的预测有意义的概率解释,或导出感知器作为最大似然估计算法。
(出自吴恩达教程)
我们来编码吧。见评论(#)。
**def perceptron(X, y, lr, epochs):**
# X --> Inputs.
# y --> labels/target.
# lr --> learning rate.
# epochs --> Number of iterations.
# m-> number of training examples
# n-> number of features
**m, n = X.shape**
# Initializing parapeters(theta) to zeros.
# +1 in n+1 for the bias term.
**theta = np.zeros((n+1,1))**
# Empty list to store how many examples were
# misclassified at every iteration.
**n_miss_list = []**
# Training.
**for epoch in range(epochs):**
# variable to store #misclassified.
**n_miss = 0**
# looping for every example.
**for idx, x_i in enumerate(X):**
# Insering 1 for bias, X0 = 1.
**x_i = np.insert(x_i, 0, 1).reshape(-1,1)**
# Calculating prediction/hypothesis.
**y_hat = step_func(np.dot(x_i.T, theta))**
# Updating if the example is misclassified.
**if (np.squeeze(y_hat) - y[idx]) != 0:
theta += lr*((y[idx] - y_hat)*x_i)**
# Incrementing by 1.
** n_miss += 1**
# Appending number of misclassified examples
# at every iteration.
**n_miss_list.append(n_miss)**
** return theta, n_miss_list**
绘制决策边界
我们知道这个模型预测了—
y=1 时
y=0 时
所以,**theta.X = 0**将是我们的决策边界。
以下用于绘制决策边界的代码仅在
X中只有两个特征时有效。
见注释(#)。
**def plot_decision_boundary(X, theta):**
# X --> Inputs
# theta --> parameters
# The Line is y=mx+c
# So, Equate mx+c = theta0.X0 + theta1.X1 + theta2.X2
# Solving we find m and c
** x1 = [min(X[:,0]), max(X[:,0])]
m = -theta[1]/theta[2]
c = -theta[0]/theta[2]
x2 = m*x1 + c**
# Plotting
**fig = plt.figure(figsize=(10,8))
plt.plot(X[:, 0][y==0], X[:, 1][y==0], "r^")
plt.plot(X[:, 0][y==1], X[:, 1][y==1], "bs")
plt.xlabel("feature 1")
plt.ylabel("feature 2")
plt.title(’Perceptron Algorithm’)** **plt.plot(x1, x2, 'y-')**
训练和绘图
**theta, miss_l = perceptron(X, y, 0.5, 100)****plot_decision_boundary(X, theta)**

作者图片
我们可以从上面的决策边界图中看到,我们能够完美地分离绿色和蓝色类。也就是说,我们得到了 100%的准确率。
感知器算法的局限性
- 它只是一个线性分类器,不能分离不可线性分离的数据。
- 该算法仅用于二元分类问题。
感谢阅读。对于问题、评论、顾虑,请在回复部分进行讨论。更多的 ML 从零开始即将推出。
看看从零开始学习的机器系列—
- 第 1 部分:Python 中从零开始的线性回归
- 第二部分:Python 中的局部加权线性回归
- 第三部分: 使用 Python 的正规方程:线性回归的封闭解
- 第四部分:Python 中的多项式回归从零开始
- 第五部分:Python 中的逻辑回归从零开始
深度图像恢复的感知损失
从均方误差到 GANs——什么是好的感知损失函数?

图片由作者提供。
神经网络的第一次应用让我忍不住惊呼“哇!”是关于风格转变的开创性论文。这项工作使用卷积神经网络(CNN)将风格从一幅图像转移到另一幅图像。这是第一次,我们可以在普通的图片上添加很酷的艺术滤镜——将任何照片变成 Vang Gough 的画,或者添加莫奈的笔触!
我们现在已经发展到把一幅图像的特征转移到另一幅图像的更复杂的问题。例如,人类肖像的情感/风格转移,或者从一个视频到另一个视频的运动转移。现实主义的水平和质量所取得的成果也有天空火箭!然而,在这份第一风格的转印纸中提出的一个元素以这样或那样的形式持续存在——知觉丧失。
感知损失是损失函数中的一个术语,它鼓励自然的和在感知上令人愉快的结果。
在这篇文章中,我将谈论不同种类的感知损失。然而,由于它们的应用在复杂程度上有所不同,因此它们的使用也有细微差别,我将集中讨论它们可以应用的最简单的问题之一——图像恢复。这里,目标是从失真的对应物中恢复高质量的图像,该图像可能已经被噪声、欠采样、模糊、压缩等破坏。
本文部分基于我们最近的工作,该工作探索了对于图像恢复任务,如单幅图像超分辨率、去噪和 JPEG 伪像消除,什么是好的损失函数的问题。作为这项工作的结果,我们还提出了一个新的损失,专门为图像恢复定制。点击查看代码!
下面,我先说说正在解决的问题。然后,我讨论了各种感知损失函数,并比较他们的表现。
图像恢复
如果图像占用很少的空间,却保持高质量,这不是很好吗?这是研究压缩算法的工程师和研究人员向自己提出的问题。然而,这并不是一项简单的任务——压缩算法经常会引入伪像。



从左至右:地面真相,JPEG 压缩图像,重建消除人工制品。来自 BSD 数据集的图像。
用旧诺基亚手机拍摄的图像分辨率如何?我们能否将它们超分辨率提高 16 倍,从而在现代高分辨率显示器上获得愉悦的观看体验?



从左至右:原始图像、降采样图像和 4x 超分辨率图像。来自 BSD 数据集的图像。
在弱光条件下拍摄的图像怎么样?通过相机管道的图像引入了噪声。我们能把它从照片上去掉吗?



从左至右:原始图像、噪声图像和清洁图像。图片来自 BSD 数据集。
这些问题的答案是肯定的!深度学习方法取得了特别的成功。卷积神经网络(CNN)专门针对图像,特别是我在以前的文章中详细谈到的图像,经常被用于这项任务。
为什么我们需要一个感知损失函数?
影响图像恢复方法性能的因素之一是定义优化目标的损失函数。
在图像恢复的情况下,目标是将受损图像恢复到视觉上与原始的未失真图像相匹配。因此,我们需要设计符合这一目标的损失。
在解决这个问题时,我们承认开发完美恢复目标图像的方法可能是不可能的,因为重建问题本质上是不适定的,即对于任何失真的图像,都可能有多个感觉上令人满意的似乎合理的解决方案。
感知损失
下面,我将损失函数分类为手工制作的损失,这些损失依赖于现有的度量;特征方面的损失,其中使用深度学习模型提取图像统计;和分布损失,其中损失将解决方案推向自然图像的流形。
手工制作的损失
直觉上,感知损失应该随着感知质量的增加而减少。因此,设计良好的感知损失函数的最初尝试着眼于提取简单的图像统计,并将它们用作损失函数中的分量。
然而,并不是所有的统计数据都是好的。例如,考虑一个标准损失项——L2。优化图像恢复方法以最小化平均每像素平方差会导致图像模糊。
问题在于当损失接近零时的形状。误差越接近零,梯度越小,这意味着与地面真实值的小偏差(这对锐度很重要)不会受到太多的惩罚。
这是一个众所周知的问题很长一段时间与 L1 被用来作为一个更好的替代图像恢复。L1 具有恒定的梯度,这意味着随着损耗接近零,梯度不会减小,从而产生看起来更清晰的图像。

用 L2 和 L1 损失训练超分辨率方法(EDSR)的结果。来自 BSD 数据集的图像。
赵等人。艾尔。使用 L2、L1、SSIM 和 MS-SSIM(后两者是客观的图像质量度量)作为损失函数,研究了图像超分辨率、去噪和去马赛克算法产生的图像的视觉质量。由 L1 损失和 MS-SSIM 损失的组合训练的算法产生的图像达到了由客观质量度量测量的最佳质量。紧随这一结果的是单独使用的 L1 损失。
后期作品比较了几种图像重建方法中用作损失函数的图像质量指标。他们发现,许多流行的图像质量评估指标不具有可以保证良好重建结果的属性。
缺点 尽管 L1 能产生更清晰的图像,但它不会根据像素对感知质量的重要性来衡量像素。人类视觉系统被调整为聚焦于特定的、显著的区域,例如面部。因此,良好的感知损失也应该考虑到这一点。
特征损失
一类新的损失函数,最近获得了显著的普及,采用神经网络作为特征提取器。最常见的是,损失被计算为经过训练的图像分类网络(例如 VGG 网络)的隐藏层的激活之间的 L2 距离。
由于隐藏层中的池化,实现损失函数的网络通常不是双射的,这意味着对函数的不同输入可能导致相同的潜在表示。因此,基于特征的损失通常与正则化项(如 L2 或 L1 范数)结合使用,并且需要仔细调整每个损失分量的权重。
最早的工作使用从 VGG 网络提取的参考和测试图像的特征之间的 L2 范数作为损失函数来训练风格转移和超分辨率算法。在这里,VGG 网络在 ImageNet 数据集上被训练。

图片由约翰逊
使用 L2 范数进行特征比较有些武断。后来的工作已经开发了替代方案来比较提取的表征。因此,这是一种专门针对风格转换的语境损失。
缺点 基于特征的损失有多个缺点——它们计算量大,需要正则化和超参数调整,涉及在不相关的任务上训练的大型网络,因此图像恢复任务的训练过程非常占用存储器。
分布损耗(GAN)
由于许多图像恢复算法本质上是不适定的,例如,由超分辨率或去噪算法产生的图像可能具有可接受的感知质量,而不是精确地匹配地面实况,所以图像重建算法可以被优化以产生在自然图像流形上的图像,该图像受到与地面实况分布的相似性的约束。为了确保第一个要求得到满足,许多作品都依赖于生成对抗网络(GAN)的。
在这样的设置中,图像生成算法具有几个损失项:被训练来区分生成图像和自然图像的鉴别器,以及一个或几个限制生成器网络产生接近地面真实的图像的损失项。
典型的约束条件是 L1 规范,使训练正规化,如叶和伊索拉所示,可以产生相当好的效果。 Dosovitsky 和 Ledig 使用基于特征的 VGG 损失。其他一些作品使用了手工制作和特征损失相结合的方法。 Tej 和 Jo 引入了基于鉴别器特征损失的正则化。
缺点 GANs 确保生成的图像位于自然图像流形上,但当单独使用时,可能会导致图像与输入有很大不同,需要多个损失项和仔细的微调。此外,由于其优化问题的不稳定性,gan 的训练也具有挑战性。
我们的工作
在我们的工作中,我们观察到单个自然图像足以训练一个轻量级的特征提取器,该特征提取器在单个图像的超分辨率、去噪和 JPEG 伪影去除方面优于最先进的损失函数。
我们提出了一种新的多尺度鉴别特征(MDF)损失,包括一系列鉴别器,训练以惩罚由生成器引入的错误。

多尺度鉴别器。图片由作者提供。
我们的损失函数的基础基于以下命题:
命题 1: 用作损耗特征提取器的网络应被训练成对发电机的恢复误差敏感。这使得特征空间更适合于在特定任务的训练期间惩罚失真。
命题 2: 学习自然图像流形,这是通常归因于鉴别器的任务,是更困难的任务,并且与特征方式损失函数不太相关。
为了验证这两个命题,我们设计了一个新的特征损失。特征空间包括这组鉴别器网络的中间激活。鉴别器网络被训练为单图像 GAN ,其从种子图像中移除特定任务的失真(阶段 1)。我们用 y 表示种子图像,以将其与用于学习恢复任务的用 x 表示的训练图像区分开。所提出的损失函数以多尺度方式训练,使得它对多尺度下的相关失真敏感。种子图像可以具有与训练图像不同的大小,可以描绘不同类型的场景,或者可以是合成图像。用于特定任务生成器的生成图像和测试图像的训练鉴别器的中间特征之间的 L2 范数被用作损失。
比较性能
视觉内容的最终消费者是人类观察者。因此,如果有一组观察者来判断针对各种损失函数优化的图像重建算法所产生的图像质量,那就太好了。
在我们的工作中,我们在亚马逊 Mechanical Turk 众包平台上进行感知实验。为了测试的最佳灵敏度,我们使用了完全设计成对比较方案。
在我们的实验中,我们比较了四种图像恢复应用的损失函数:使用 SR-ResNet 的单幅图像超分辨率,使用 EDSR 的单幅图像超分辨率,去噪和 JPEG 伪像去除。
对于每个应用程序,我们都进行了两两比较实验,汇总了收集到的比较结果,并使用这种方法对结果进行了显著差异(JND)(瑟斯顿)衡量。缩放的结果用 JND 单位表示质量差异。一个 JND 单位意味着 75%的人会(从一对方法中)选择一种方法。缩放的结果显示了我们的方法相对于其他损失函数的持续改进。

来自亚马逊 MTurk 的主观实验,以 JND 为单位(越高越好)。误差线表示 95%的置信区间。LPIPS 和 VGG 损失与 L2 损失一起使用。图片由作者提供。
为了获得更深入的了解,下面我们将结果可视化为感知失真权衡,其在 x 轴上示出了失真(PSNR ),在 y 轴上示出了 JND 质量值(反向标度)。所有应用的结果清楚地表明,MDF 损耗导致最低的失真和最高的感知质量。

测试损失的感知失真权衡。轴已经颠倒,因此最低的失真显示在左边,最高的感知质量显示在底部。图片由作者提供。
上图还显示,传统的客观图像质量评估指标,如 PSNR 或 NIQE,在预测图像的感知质量时可能不可靠。他们也没有抓住感知差异的实际意义;我们不知道 0.5 dB 的改善是否会被普通观察者所接受。
定性结果






使用 EDSR 获得 4 倍单幅图像超分辨率的结果。从左上顺时针方向:原件、L2、L1、VGG+L1、MDF、MS-SSIM+1。来自 BSD 数据集的图像。






使用 SR-ResNet 获得 4 倍单幅图像超分辨率的结果。从左上顺时针方向:原件、L2、L1、VGG+L1、MDF、MS-SSIM+1。来自 BSD 数据集的图像。
想了解更多关于 JPEG 伪像去除和去噪的结果,请查看我们的项目页面!
摘要
设计有效的感知损失函数是具有挑战性的。尤其困难的是从人类观察者那里获得反馈,以判断图像生成方法产生的结果的质量——这既昂贵又耗时。
然而,关于损失函数的多个发现已经被提出:好的感知损失不需要预测感知的图像质量,并且好的质量度量可能不会产生好的损失函数;应该小心选择用于比较特征方面损失的深度表示的度量;特定任务损失往往比一般损失表现更好。
更多细节请看我们最近的作品,关于图像恢复的损失函数,记住我们这里也有代码!
如果你喜欢这篇文章,请与朋友分享!要阅读更多关于机器学习和图像处理的主题,请点击订阅!
喜欢作者?保持联系!
我错过了什么吗?不要犹豫,直接在 LinkedIn 或 Twitter 上给我留言、评论或发消息吧!
EBM 的性能和可解释性
模型可解释性
可解释的助推机器是如何收获这两者的

从那时起,人们不得不在有效模型和可解释模型之间做出选择。一方面,像逻辑回归这样的简单模型是可以解释的,但是在表现上有所滞后。另一方面,像提升树或神经网络这样的复杂模型达到了令人难以置信的准确性,但却难以理解。
自 2012 年以来,来自微软的研究人员研究并实现了一种打破规则的算法:可解释的助推机器 (EBM)。EBM 是唯一没有这种性能与可解释性比率曲线的算法。

有了 EBM,当可解释性是一个需求时,你不必牺牲性能,你可以用顶级性能免费得到解释。循证医学的一个重要方面是它是自然可解释的。我们不谈论用像 Lime、Shap 或 GramCam 这样的方法来解释复杂的模型:EBM 本质上是可以解释的。人们可以直接看到模型如何使用每个特性。例如,参见一个变量的全局解释图:

蓝线是模型如何使用特性,而不是估计。这些图表对于理解模型的内部工作是非常宝贵的。它们对调试也有很大的帮助:如果模型在某个地方过度拟合了一个特性,您可以立即看到它(您看到这里的过度拟合了吗?).这些都是用石灰或 Shap 很难发现的问题。有人会说这是数据探索阶段的目标。不幸的是,在这一阶段很容易遗漏一些东西,所以依赖模型本身是很好的。
那么 EBM 是如何工作的呢?这是不是好得难以置信?
我们将会看到,由于几种技术的巧妙结合,这是可能的。
广义可加模型
先说让 EBM 可解释的基础。EBM 是一种广义加法模型 (GAM),由 Trevor Hastie 和 Robert Tibshirani 形式化。这个模型家族的完整描述可以在他们的书统计学习要素的第 9 章中找到。GAM 是满足以下公式的任何模型:

这些 f 函数被命名为形状函数。 g 功能就是链接功能。您可能注意到这与线性回归非常相似:

你是对的!线性回归也是博弈,其中:
- 形状函数是(与权重的)乘法。
- 链接功能就是身份。
类似地,逻辑回归也是一个 GAM,其中链接函数是 logit 。
gam 的数学定义定义了为什么它们是可解释的:画出形状函数,看看模型如何使用每个特征。就这么简单。上一节的图就是:一个形状函数的图。
比 GAM 更好:GA M
所以游戏可以通过设计来解释。然而,它们有一个明显的缺点:它们单独使用每个特性,忽略了它们之间的任何联系。即使是一个单独的决策树也可以结合特征进行预测。请注意,根据我的经验,这种简单的方法可以提供非常好的性能。
EBM 为 GAMs 带来了第一个改进,它还在附加项上使用了成对的特性:

这样的模型叫做 GA M: 有相互作用的广义模型(不对,定义没有错误。为一个算法找一个名字并不总是容易的)。
关键是,使用两个特征的形状函数仍然是可以解释的:它们不是以直线、曲线或台阶的形式呈现,而是以热图的形式显示。当然,它们更难解释,但它们确实如此。另一方面,这允许组合几个特征并提高性能。
在我们继续讨论 EBM 对 GAMs 的改进之前,让我们看看如何进行预测。
循证医学推理
EBM 的另一个优点是它的推理在计算方面是轻的。这是生产环境中预测延迟必须尽可能小的一点。
下图显示了预测的所有步骤:

计算密集型部分是箱柜查找和加法!
第一步包括连续变量的离散化和分类变量的映射。连续变量被分割成箱。在宁滨步骤中,特征被映射到索引值。每个指数都与一个分数相关联。对每个特征都这样做。最终,预测是所有分数加上截距/偏差的总和。多类分类,每类一分。根据任务,可能会使用后处理(链接功能):
- 对于回归,总和直接是预测值。
- 对于分类,得分最高的类是预测类。
- 对于类别概率,对每个类别的分数应用软最大值
让我们通过一个例子来遵循这些步骤。下图突出显示了使用两个特征的模型对数据集第一行的预测:

这些步骤都是 GAM 所期望的。然而,最初的宁滨和计分部分更有趣:它们是形状函数,但独立于任何算法或公式。它们可以代表任何形状的函数:样条、决策树,甚至神经网络。
让我们看看这种离散化是从哪里来的。
拟合形状函数
EBM 培训部分结合使用提升树和套袋。一个好的定义可能是袋装浅树。该算法的核心使用提升树,如下图所示:

浅树以助推的方式训练。这些是小树(默认情况下最多有 3 片叶子)。此外,提升过程是特定的:每个树只针对一个特征进行训练。在每一轮提升过程中,针对每个特征一个接一个地训练树。它确保:
- 模型是可加的。
- 每个形状函数只使用一个特征。
这是算法的基础,但其他技术可以进一步提高性能:
- Bagging,在这个基础估计值之上。
- 可选装袋,用于每个增压步骤。默认情况下它是禁用的,因为它大大增加了训练时间。
- 成对互动。
根据任务的不同,第三种技术可以极大地提高性能:一旦用单个特征训练了一个模型,就进行第二次训练(使用相同的训练程序),但使用成对的特征。配对选择使用专用算法,避免尝试所有可能的组合(当有许多特征时,这是不可行的)。
最后,经过所有这些步骤,我们有一个树合奏。这些树被离散化,只需用输入要素的所有可能值运行它们。这很容易,因为所有特征都是离散化的。因此,预测值的最大数量是每个要素的条柱数量。最后,这数千棵树被简化为宁滨和每个特征的得分向量。
所以这些向量是成千上万只存在几分钟的树的结果。一旦我们建造了它们,我们就不再需要它们了!
结论
人们不得不在准确性和可解释性之间做出选择的时代已经过去了。EBMs 可以像 boosted 树一样有效,同时又像逻辑回归一样容易解释。
EBM 属于广义可加模型家族。他们使用以允许简单推断和解释的方式编码的增强树。
EBM 是微软 InterpretML 项目的一部分。我鼓励任何从事表格数据工作的数据科学家尝试一下。我们在几个任务上取得了与 XGBoost 相当的性能,同时清楚地了解了模型如何使用每个特性。如果你在一个领域工作,其中可解释性是强制性的,或者运行时性能是关键,你应该尝试一下。
原载于 2021 年 4 月 13 日【https://blog.oakbits.com】。
性能比较:CatBoost 与 XGBoost 以及 CatBoost 与 LightGBM
助推技术
机器学习中的助推算法——第七部分

Arnaud Mesureur 在 Unsplash 上拍摄的照片
到目前为止,我们已经讨论了 5 种不同的 boosting 算法: AdaBoost , 梯度 Boosting , XGBoost ,light GBM和 CatBoost 。
其中, XGBoost ,light GBM和CatBoost是更重要的算法,因为它们产生更准确的结果,执行时间更快。****
在第 5 部分中,我们已经比较了 XGBoost 和 LightGBM 的性能和执行时间。在那里,我们发现:
LightGBM 有时会优于 XGBoost,但并不总是如此。但是,LightGBM 比 XGBoost 快 7 倍左右!
是时候对 CatBoost 和 XGBoost 以及 CatBoost 和 LightGBM 进行一些性能比较了。在本内容的最后,我还将提到一些指导原则,帮助您为您的任务选择正确的升压算法。
CatBoost 与 XGBoost
这里我们考虑 2 个因素: 性能 和 执行时间 。我们在加州房价数据集上构建 CatBoost 和 XGBoost 回归模型。
CatBoost 与 XGBoost

(图片由作者提供)
XGBoost 的表现略胜于 CatBoost。但是,CatBoost 比 XGBoost 快 3.5 倍左右!
CatBoost 与 LightGBM
这里,我们也考虑同样的两个因素。这一次,我们在加州房价数据集上构建 CatBoost 和 LightGBM 回归模型。
CatBoost 与 LightGBM

(图片由作者提供)
LightGBM 略胜 CatBoost,比 CatBoost 快 2 倍左右!
结论
当我们考虑执行时间时,LightGBM 轻而易举地胜出!比 XGBoost 快 7 倍,比 CatBoost 快 2 倍!
当我们考虑性能的时候,XGBoost 比另外两个稍微好一点。然而,选择正确的升压技术取决于许多因素。这里有一些指导方针,可以帮助您为您的任务选择正确的升压算法。
选择正确增压技术的指南
- 除了并行化训练过程之外,任何提升技术都比决策树和随机森林好得多。
- 您可以从基本的增强技术开始,如 AdaBoost 或 渐变增强 ,然后您可以转向增强技术,如 XGBoost 。
- light GBM和 CatBoost 是 XGBoost 的绝佳替代品。
- 如果你有更大的数据集,可以考虑使用 LightGBM 或者CatBoost。LightGBM 是最好的选择。
- 如果你的数据集有分类特征,可以考虑使用light GBM或者CatBoost。两者都可以处理尚未编码的分类特征。CatBoost 是处理分类特征的最佳选择。****
- XGBoost比其他 boosting 技术有更多的泛化能力。就性能而言,这很好。
- light GBM执行时间最快。****
- 当我们同时考虑 性能 和 执行时间 时,light GBM是最佳选择。****
boosting 技术的一个主要缺点是,由于 boosting 算法是基于树的算法,因此很容易发生过拟合。我们已经讨论了一些解决过度拟合问题的技术:
解决文章系列过多的问题(作者截图)******
可以用来解决 boosting 算法中过拟合问题的最佳技术之一是 提前停止 。这是我们在本文中讨论过的一种正规化。我还将发表一篇单独的文章,描述我们如何使用早期停止,尤其是在 boosting 算法中。
今天的帖子到此结束。我的读者可以通过下面的链接注册成为会员,以获得我写的每个故事的全部信息,我将收到你的一部分会员费。
****https://rukshanpramoditha.medium.com/membership
非常感谢你一直以来的支持!下一个故事再见。祝大家学习愉快!
特别要感谢 Unsplash 网站上的 Arnaud Mesureur 、、T3,他为我提供了这篇文章的精美封面图片。
鲁克山·普拉莫迪塔
2021–11–03****
Apache Spark 中的性能:基准测试 9 种不同的技术
Spark 3.1 中不同数组处理方法的比较

照片由 Kolleen Gladden 在 Unsplash 上拍摄
在 Apache Spark 中,相同的转换可以通过不同的方式实现是很常见的。这也是 Spark 不断发展的结果,因为新版本中提供了新的技术和功能。
在本文中,我们将比较导致相同结果的 9 种不同技术,但是它们中的每一种都有非常不同的性能。我们将在一个与数组处理相关的特殊例子中展示它,但是结果可以推广到其他情况。
Spark 中的基准测试
当测量 Spark 中不同转换的性能时,需要小心避免一些陷阱。首先,我们需要通过调用一个操作来运行 Spark 作业来实现转换(要理解 Spark 中转换和操作的区别,请参见我最近的文章)。您能想到的一个动作是函数 count() 。然而,使用计数时,有一个问题,即要评估结果数据帧代表多少行,Spark 并不总是需要执行所有的转换。Spark 优化器可以以这样的方式简化查询计划,即您需要测量的实际转换将被跳过,因为在找出最终计数时根本不需要它。所以为了确保所有的转换都被执行,最好调用 write 并将结果保存在某个地方。但是保存输出还有另一个缺点,因为执行时间会受到写入过程的影响。
在 Spark 3.0 中,由于 Spark 3.0 中的一个新特性 noop write format,基准测试简化了,进行性能基准测试变得更加方便。我们可以简单地将其指定为 write 格式,它将具体化查询并执行所有转换,但不会将结果写入任何地方。
(
final_df
.write
.mode("overwrite")
**.format("noop")**
.save()
)
我们将要对查询进行基准测试的用例与下面的例子相关。想象一个数据集,其中每行都有一个单词数组。我们想添加一个新的列,它也是数组类型,这个新数组的值对应于原始数组中单词的长度,如下图所示:

作者图片
技术 I——高阶函数
从概念上来说,我们在这里要做的转换是,我们要从一个数组创建另一个数组,并且我们要对数据集中的每一行都这样做。在函数式编程语言中,通常有一个在数组(或另一个集合)上调用的 map 函数,它将另一个函数作为参数,然后这个函数被应用到数组的每个元素上,如下图所示

作者图片
从 Spark 2.4 开始,Spark SQL 中也支持这个概念,这个映射函数被称为 变换 (注意,除了变换之外,Spark 中还有其他可用的 Hof,比如 过滤器 、 存在 、等)。这种支持最初只出现在 SQL API 中,所以如果你想在 DataFrames DSL(在 2.4 中)中使用它,你必须在 expr 中调用它(或者 selectExpr )。然而,从 Spark 3.0 开始,Hof 也在 Scala API 中得到支持,并且从 Spark 3.1.1 开始,Python API 中也增加了这种支持。使用 HOFs,可以如下实现转换:
final_df = (
df
.withColumn("tag_lengths",expr("TRANSFORM(tags, x -> length(x))"))
)
在 Scala API 中——从 Spark 3.0 开始:
val final_df = df
.withColumn("tag_lengths", transform($"tags", x => length(x)))
在 Python API 中—从 Spark 3.1.1 开始
final_df = (
df
.withColumn("tag_lengths", transform("tags", lambda x: length(x)))
)
技术二— Python UDF(香草)
由于没有直接的本地函数来执行我们想要的转换,人们可能会尝试为它实现一个用户定义的函数(UDF ),事实上在 Spark 2.4 支持 HOFs 之前,UDF 是解决 Spark 中数组问题的一种非常常见的技术。在 PySpark 中有两种主要类型的 UDF,第一种是普通的 UDFs 我们在这里称之为香草 UDF,第二种是熊猫 UDF,我们将分别测量它们的性能。使用普通 UDF 的转换可以写成如下形式:
@udf("array<int>")
def pythonUDF(tags):
return [len(tag) for tag in tags]final_df = df.withColumn("tag_lengths", pythonUDF("tags"))
技巧三——蟒蛇 UDF(熊猫)
因为 Spark 不能将 Python 代码从 UDF 翻译成 JVM 指令,所以 Python UDF 必须在 Python worker 上执行,而不像 Spark 作业的其余部分在 JVM 中执行。为了做到这一点,Spark 必须将数据从 JVM 传输到 Python worker。使用普通的 Python UDF,这种传输是通过将数据转换为腌制字节并将其发送到 Python 来实现的,这不是很高效,并且具有相当大的内存占用。这在 Spark 2.3 中得到改进,将 Spark 与 Apache Arrow 集成在一起,并支持所谓的熊猫 UDF。其思想是,如果 UDF 是使用熊猫 API 实现的,并且该函数注册为熊猫 UDF,则数据将使用 Arrow 格式传输,这是一种内存中的数据格式,专门设计用于在不同系统之间高效地交换数据。此外,熊猫 UDF 可以在 Python 端利用矢量化执行,这将非常有效,特别是如果 UDF 将进行一些数值计算并将使用 NumPy 或 SciPy 支持的矢量化操作。使用熊猫 UDF,我们的数组转换可以实现如下:
@pandas_udf("array<int>")
def pandasUDF(tags):
return tags.map(lambda x: [len(tag) for tag in x])final_df = df.withColumn("tag_lengths", pandasUDF("tags"))
技术四——斯卡拉 UDF
使用这种技术,我们将测试如果我们使用 Scala 而不是 Python,性能会有怎样的变化。如果您只使用 DataFrame API 支持的原生转换,而不使用任何 UDF,那么无论您使用 Python 还是 Scala,性能都是一样的,因为原生转换在幕后创建了一个查询计划,无论使用什么语言,该计划都是一样的(参见我的另一篇文章以了解关于查询计划的更多信息)。计划的执行和 Spark 作业的执行将在 JVM 内部发生。然而,如果您使用 UDF,情况就不同了,因为在这种情况下,UDF 内部的转换不能转换为查询计划,并且正如我们上面所描述的,在 Python 的情况下,执行将在 Python 工作器上进行,这与传输数据的开销有关。如果 UDF 是在 Scala 中实现的,我们可以避免将数据传输到 Python,执行将留在 JVM 中。然而,将数据从内部数据格式转换为 java 对象仍然会有一些开销。对于原生转换,Spark 使用内部数据格式,所有原生转换在运行时都会生成非常高效的 Java 代码。对于 UDF,Spark 不知道如何生成代码,必须将数据转换为 Java 对象,然后在其上执行 UDF,之后,将数据转换回内部格式。Scala UDF 的代码可以写成如下形式:
def tag_lengths(tags: Seq[String]): Seq[Int] = {
tags.map(tag => tag.length)
}val scalaUDF = udf(tag_lengths(_:Seq[String]): Seq[Int])val final_df = df.withColumn("tag_lengths", scalaUDF($"tags"))
技术五——从 Python 调用 Scala UDF
上面我们描述了使用 Scala 和 Python 作为 Spark 语言的区别——如果我们只使用本机转换,对性能没有影响,但是,如果我们使用 UDF,性能会受到影响。对于 Python 开发人员来说,如果性能很关键,但又需要使用 UDF,这可能会很不幸。有趣的是,有一种混合的方法,在这种方法中,我们可以用 Python 编写 Spark 应用程序,而在 Scala 中,我们只实现 UDF 来保持 JVM 内部的执行。
// in Scala:
// this is inside src/main/scala/vrbad/sandbox/CustomFunctions.scalapackage vrbad.sandboximport org.apache.spark.sql.SparkSessionobject CustomFunctions { def sizeElements(elements: Seq[String]): Seq[Int] = {
elements.map(el => el.length)
} def registerUDF(_spark: SparkSession) = {
val f = sizeElements(_)
_spark.udf.register("sizeElements", f)
}
} # in Python:java_instance = spark._jvm.vrbad.sandbox.CustomFunctions
java_instance.registerUDF(spark._jsparkSession)final_df = (
df.withColumn("tag_lengths", expr("sizeElements(tags)"))
)
Scala 文件使用 sbt 编译成 jar 文件,上传到集群,可以在 PySpark 应用程序中使用,如上面的代码所示。
技术六——分解阵列
分解数组允许分别访问数组的每个元素。 explode 函数将简单地为数组中的每个元素生成一个新行,因此 DataFrame 在它所代表的行数方面变得更大。我们可以计算每个元素的长度,然后,我们可以将这些结果分组到数组中,从而将数据帧收缩回其原始大小:
from pyspark.sql.functions import explode, length, collect_listfinal_df = (
df.withColumn("tag", explode("tags"))
.withColumn("tag_size", length("tag"))
.groupBy("id")
.agg(
collect_list("tag_size").alias("tag_lengths")
)
)
必须理解这种技术有五个重要的注意事项,其中一个与性能直接相关,两个与结果的正确性相关,一个与最终输出的形状相关,一个与空处理相关:
- groupBy 是一种需要特定分区的转换,即数据必须按分组键重新分区。如果不满足这一要求,Spark 将不得不对数据进行洗牌,这将对性能产生直接影响,因为洗牌的成本通常很高。
- collect_list 函数是不确定的,所以最终数组中的元素可能以不同的顺序结束,就像原始数组中的元素一样。
- 该方法假设在调用 explode 之前,分组键包含唯一值,因为如果有重复,它们将在 groupBy 之后被删除,因此在这种情况下,我们将得到比开始时更少的行——重复将被分组在一起。
- 在 groupBy 之后的输出数据帧将只包含分组关键字和聚合结果,所有其他原始字段都将丢失,因此如果您在最终输出中需要它们,您需要将结果与原始数据帧连接起来,这当然会带来额外的速度减慢。
- 如果数组为空,那么 explode 函数将不会生成任何行,所以在这里,在分组之后,我们可以得到比开始时更少的行。这可以用函数 explode_outer 来控制,即使数组为空,也会生成一行(空值)。
技术七—用分桶源分解阵列
如果数据有正确的划分,上一点提到的混乱就可以避免(参见我的另一篇文章,我在那里详细描述了这一点)。如果数据源是按分组键分桶的,就可以实现这一点。在这里,我们将测试它如何提高性能,查询与技术 VI 中的相同,但是源将按照分组关键字— id 进行分类。
技术八 Python 中的 RDD API
RDD API 是一个低级 API,尤其是在 Spark 2.0 之前使用。从 Spark 2.x 开始,建议避免使用它,而使用 DataFrames 或 SQL。Spark 内部仍然使用 RDD,在执行期间,查询计划总是被编译成 RDD DAG(有向无环图)。关键是 Spark 以一种非常高效的方式做到了这一点,首先它优化了查询计划,然后它还生成了高效的 Java 代码来操作内部数据表示——一种所谓的钨格式。然而,如果您自己编写 RDD 代码,不会发生任何优化,数据将被表示为 Java 对象,与钨相比,其内存消耗要大得多。
为了完整起见,我们将在这里测试 RDD 方法,在 Python 中,代码可能是这样的
from pyspark.sql.types import *r = (
df.rdd
.map(lambda row: (row[0], [len(tag) for tag in row[1]]))
)data_schema = StructType(
[
StructField("id", StringType()),
StructField("tag_lengths", ArrayType(LongType()))
]
)final_df = spark.createDataFrame(r, data_schema)
技巧九 Scala 中的 RDD API
在这个基准测试中测试的最后一项技术与前一项相同——RDD 方法,但是现在在 Scala 中:
val final_df = df.rdd
.map(row =>
(row(0).asInstanceOf[String],
row(1).asInstanceOf[Seq[String]].map(tag => tag.length))
).toDF("id", "tag_lengths")
基准详细信息
所有查询都使用 Spark 3.1.1(撰写本文时的最新版本——2021 年 3 月)在运行时为 8.0 的 Databricks 平台上执行。所使用的集群有 3 个工作线程 m5d.2xlarge(总共 24 个内核),输入数据集以 Apache Parquet 文件格式存储在 S3 (AWS 分布式对象存储)上,它有 1 047 385 835 行(略超过 1 0 亿行)。
基准测试结果
在下图中,您可以看到所有 9 种技术的执行时间:

作者图片
最快的是技术 I——使用高阶函数的本地方法,耗时 66 秒。稍微慢一点的是技术 V 和 IV——从 Python 应用程序调用的 Scala UDF(89 秒)和从 Scala 应用程序调用的 Scala UDF(94 秒)。接下来是用 Scala 中的 RDD API 编写的查询,令人惊讶的是它只用了 104 秒。使用 explode 和 groupBy 的方法要慢得多,如果我们使用分桶源并避免混洗,它运行 244 秒,另一方面,如果我们不使用分桶,执行速度会慢一些,需要 302 秒。非常类似的是熊猫 UDF——299 秒。普通的 Python UDF 用了 386 秒,最后最慢的是 Python 中使用的 RDD API(1020 秒)。让我们来看看一些亮点:
- HOFs 的本地方法是最有效的——这并不奇怪,它可以利用所有的内部特性,如 Spark optimizer、代码生成或内部钨数据格式。
- Scala UDF 比 Python UDF 快得多——我们预料到了这一点,因为 Scala UDF 在 JVM 内部执行,避免了 JVM 和 Python 工作器之间的数据传输。
- 从 Python 调用的 Scala UDF 比从 Scala 调用的 Scala UDF 略快。在这里,我假设这两种技术在性能方面是等价的,并且我确实看不出为什么从 PySpark 应用程序调用时会更快,但是,差异非常小,只有 5 秒钟,所以也可能是在集群上发生了一些不相关的事情。
- 技术 VI 和 VII ( 爆发 + 分组通过无桶和有桶)之间的差别不是很大。这意味着在第一种情况下,洗牌并不是一个大的瓶颈。这是因为混洗数据不是很大,从 SparkUI 中,我可以看到混洗写只有 26 GB。就行而言,数据集很大(超过 10 亿),但它只有两列( id 和标记)被混洗,并且在具有 24 个核心的集群上,这样的混洗不会带来很大的开销。
- 熊猫 UDF 比香草 UDF 跑得快,但没有人们想象的那么快。实际上,熊猫 UDF 的表现可以接近斯卡拉 UDF 的表现。但是这些情况通常是你的 UDF 在做一些数值计算,并且是用 SciPy 或 NumPy 编写的,所以熊猫可以在幕后利用 BLAS 或 LAPACK 库的矢量化执行。这不是我们的情况——我们不做任何数值计算,但我们仍然获得了比香草 UDF 更快的执行速度,因为熊猫 UDF 使用 Apache Arrow 格式进行数据交换。
- 在我们的例子中,Scala 中的 RDD API 非常快。在 DataFrame API 的例子中,Spark 使用了一个优化器,它不会用于 rdd。但是在这里,我们实际上只对阵列进行了一次转换,因此优化器不会带来太多好处。RDD API 不会利用生成的代码和钨数据格式,其性能可以与 Scala UDF 相媲美。
- Python 中的 RDD API 确实非常慢,我们可以看到所有其他技术都快得多。
结论
在本文中,我们针对 Apache Spark 处理阵列中的一个特定用例测试了 9 种技术的性能。我们已经看到,SQL 中的 Spark 2.4、Scala API 中的 3.0 以及 Python API 中的 3.1.1 都支持高阶函数,从而实现了最佳性能。我们还比较了用户定义函数的不同方法,如 vanilla Python UDF、Pandas UDF、Scala UDF,以及一种混合方法,其中我们从 PySpark 应用程序中调用 Scala UDF。我们还看到了它与分解数组的比较,并指出了与这种方法相关的一些注意事项。最后,我们还在测试中包含了遗留的 RDD API。
二元分类器的性能度量(用简单的话来说)
对于初学者和所有喜欢猫和不喜欢深奥解释的人

作者所有图片
分类错误
假设我们有一个简单的二进制分类器,它接受装有薛定谔猫的盒子😺作为输入,如果猫是活的,我们期望分类器返回标签 1(阳性),如果不是,返回标签 0(阴性),但是错误时有发生。
为什么?
原因可能有所不同,例如,输入数据质量差,或者选择了错误的要素。例如,如果它是一只熟睡的猫,所以它看起来很死,分类器可以很容易地为它返回 0。
下图显示了 4 种可能的情况,其中两种分类器给出了正确的预测,而另外两种则是错误的:

因此,如果我们的零假设是所有的猫都活着(属于第 1 类),那么可能的错误是:
假阳性(I 型错误)
- 无效假设被错误地拒绝了
- 死猫被归类为活的
假阴性(II 型错误)
- 零假设被错误地接受了
- 活猫被归类为死猫
混淆矩阵
让我们用数字来完成上面的 pic:有多少样本被正确标记,有多少次分类器是错误的。这就是我们如何得到二元分类的混淆矩阵。
下面是一个使用 Python scikit-learn 构建的矩阵示例:
from sklearn.metrics import confusion_matrix
import pandas as pdn = confusion_matrix(test_labels, predictions)
plot_confusion_matrix(n, classes = ['Dead cat', 'Alive cat'],
title = 'Confusion Matrix');

在这种情况下,我们有:
- 真阴性= 616(死猫标记为死亡)
- 假阴性= 59(标记为死亡的活猫)
- 真阳性= 71(标记为活的活猫)
- 假阳性= 89(死猫标记为活的)
我们可以从 numpy 使用 ravel()获得的所有值:
tn, fp, fn, tp = confusion_matrix(test_labels, predictions).ravel()
所以,现在我们知道模型做了多少次正确和错误的预测,下一步是评估分类器——检查它做出的预测是否足够好。让我们看看我们有什么衡量标准。
准确度分数
首先;我们将尝试使用准确度分数进行评估;将正确预测的数量除以总预测数量;得到正确预测的样本百分比;

不合适的时候?
当数据不平衡,且活猫数量明显多于死猫数量时(反之亦然)。让我们弄清楚为什么。
想象一个非常悲伤的情况:在 100 个盒子中,我们有 95 个装着死猫,只有 5 个装着活猫。

我们想在它们加入其他 95 只之前尽快找到所有活着的猫,但我们的分类器已经失去了所有希望,变得悲观:现在它总是说盒子里的猫已经死了(即使它没有死)。
那么就会对 95 次(TN = 95),错 5 次(FN = 5)。其他值为:TP = FP = 0。让我们计算分类器的准确度:

看起来分类器有很高的准确率 — 95%,但是却什么也做不了。
原因是大量“猜测”的死猫对准确度分数和抑制误差有很大贡献。
因此,为了获得较高的准确率,分类器总是能够给出一个主要类别的标签。但这不符合问题的解决逻辑:高精度并没有帮助我们找到一个装有活猫的盒子。
看起来我们需要从计算所有类的公共度量转移到每个类的单独性能度量。
真阳性率,真阴性率
- 为了计算真阳性率(又名回忆、灵敏度和命中率),我们将猫活着的正确预测数与装有活着的猫的箱子总数进行比较:

- 真阴性率(又名特异性或选择性):这里我们比较有死猫的箱子数和猫已经死亡的正确预测数:

让我们计算悲观分类器和不平衡数据(95 只活猫和 5 只死猫)的度量:

好吧,这一次模型没能欺骗我们:虽然 TNR 是完美的(1),但 TPR 是 0。
这清楚地说明了这个模型的本质:它很擅长寻找死猫,但很难找到活猫。
所有这些都在平衡精度度量中考虑到了,该度量是精度的模拟,但可用于不平衡的类别。
平衡精度
它被计算为灵敏度和特异性的平均值,即每个类别的比例校正值的平均值:

当所有类别都是平衡的,因此每个类别中有相同数量的样本时,TP + FN ≈ TN + FP 和二进制分类器的“常规”准确度大约等于平衡准确度。
在我们不平衡的猫的例子中(95 只死了,5 只活了):

这里我们得到了 50%,而准确性得分是 95%。而且,直到我们的模型猜出至少一只活着的猫,它也不会得到高于 50%的 BA 分数——这就是生活(以及平衡的准确度公式)。
问题解决了?
看起来已经完成了——我们可以一直使用平衡精度,因为它不受职业不平衡的影响。但这里还有一个例子。
假设现在我们的分类器说盒子里有 10 只活着的猫,但是……没有一只真的活着。
这就是我们所拥有的:
- 没有发现 5 只存活的猫(TP = 0),它们都被标记为死亡(FN = 5)
- 在 95 只死猫中,85 只被正确分类(TN = 85),10 只被误认为是活的(FP = 10)
平衡精度从 0.5 下降到 0.45:

一方面,这是有道理的,因为现在有更多的错误(该模型以前明确无误地识别了死猫)。另一方面,当我们试图寻找活着的猫时,两个模型都同样无用:他们都没有找到一只活着的猫,但 metrics 说第一个模型更好。那么我们应该相信什么呢?
活着的猫比死了的更重要
关键是,平衡准确度指标只给出了我们正确分类的存活猫数量的一半。另一半是我们正确标记了多少只死猫,而对于我们的任务来说,这个信息是没有用的。
我们使用分类器的任务在这里扮演了一个重要的角色。在上面描述的例子中,检测最大数量的活猫符合我们的最佳利益,但模型是否能识别死猫并没有太大关系(无论如何,试图拯救它们已经太晚了)。但如果有一天找到死猫变得和找到活猫一样重要,我们可以放心地使用平衡精度。
但目前,我们仍然专注于检测活着的猫,并继续寻找可以帮助我们评估我们模型的指标。
回忆和精确:从两个角度看
让我们假设我们重新训练了我们的模型,它最终预测出 5 只猫中有 5 只还活着!但是我们仍然有一些错误——5 只死猫也被标记为活的。
换句话说:FP = 5,TP = 5,TN = 90,FN = 0。
那么什么是模型的回忆(真阳性率)?

1%或 100% —太棒了!
但是那些被称为活着但已经死了的猫呢?我们都知道分类器错的次数和它对的次数一样多。显然,应该有一个指标来反映它。
精度又名阳性预测值
有这样一个度量——精度:

它显示了我们的模型将“活着的猫”类与所有其他类区分开来的能力,但是,不幸的是,它没有给出我们是否已经找到所有活着的猫的想法。
相比之下, Recall metric 显示了我们是否找到了所有活着的猫,但它不能说明我们在整个搜索过程中打开装有死猫的箱子的次数。
好吧,让我们计算最后一次实验的精度,然后解释结果:

所以:
- Recall = 1 意味着无论总共有多少只活着的猫,我们都会找到它们。
- 精度= 0.5 意味着打开一个标签为“活着”的盒子,在一半的情况下,我们会在那里找到一只死猫。
找到平衡
(我要提醒一下,我们还有 95 名☠️死亡,5 名活着😸猫)
分开来看,精确度和召回率没有什么意义:
- 一个总是把所有猫都归类为活的(乐观 ) 的模型,召回率很完美(100%),但是精度非常低(5%)。
- 一个认为只有那些绝对确定的猫还活着的模型(悲观),具有 100%的精确度。但是如果有很多猫睡得像狗一样,召回率会很低。
我们需要平衡这两个指标,以获得完整的图片和两个问题的答案:
1。有多少“活着”的预测是正确的(精度)
2。活猫的哪一部分被正确分类(回忆)
这就是 F1 指标派上用场的地方。
f1-分数
F1 是精确度和召回率的调和平均值,所以如果其中一个非常低,你就不可能得到高的 F1。

让我们回忆一下我们的模型是如何训练的,并计算所有 3 种情况下的 F1 分数:
- 起初,模型将所有猫标记为死亡:
TP = FP = 0,TN = 95,FN = 5 - 然后它将 10 只猫标记为活着,但其中没有一只活着的猫:
TP = 0,FP = 10,FN = 5,TN = 85 - 最后,分类器正确预测了 5 只活着的猫中的 5 只,但也错误地将 5 只死猫标记为活着:
TP = 5,FP = 5,FN = 0,TN = 90
您可以看到,随着每次重新训练,负面预测的“质量”下降,而正面预测的“质量”上升,因此我们希望 F1 能够反映这种变化:

嗯,有道理。案例 1 和案例 2 对我们来说基本上是一样的——尽管模型的行为不同,但我们没有发现任何活着的猫。但是第三次,分类器正确地标记了所有活着的猫——F1 增加了,同时我们拯救猫的希望也增加了😸。
问题是,与平衡精度不同,F1 没有考虑真正的负面因素——如果我们扩展公式,很容易看出这一点:

这意味着数据集中有多少只死猫并不重要,我们成功找到了多少只也不重要。我们专注于检测活着的人,因为他们更重要。
让我们将其与平衡精度行为进行比较:

因此,如果我们更关心拯救活着的猫(检测阳性),度量 F1 更适合。
F1 只关心模型说是阳性的样本,以及实际上是阳性的样本,根本不关心数据集中有多少阴性样本或者有多少被正确分类。
这就是为什么在评估旨在发现异常的模型时,这种度量非常受欢迎。
相反,如果阴性样本几乎和阳性样本一样重要,我们应该更喜欢平衡的准确性。
注意:
对于多类分类,如果一个类包含的样本比其他类多得多,则其度量将抑制所有其他类。然后,您可以形成自己的列联矩阵,并为每个类别计算自己的 F1 值,得到的 F1 应为所有类别的简单(即未加权)算术平均值。
摘要
准确(性)
- 正确预测的比例
- 取决于类之间的平衡,不适用于不平衡的数据集
- 对于平衡数据集,等于平衡精度
平衡精度
- 灵敏度和特异性的平均值
- 不受类别不平衡的影响,可应用于不平衡的数据集
- 对于平衡数据集,等于精度
精确度(阳性预测值)
- 所有正面预测中真正正面预测的比例
- 显示分类器是否能够将一个类别与所有其他类别区分开来
- 不受类别不平衡的影响,可应用于不平衡的数据集
- 应与召回相平衡
召回(真阳性率、灵敏度、命中率)
- 数据集中所有阳性样本的真阳性预测的比例
- 显示分类器是否能够检测到给定的类
- 不受类别不平衡的影响,可应用于不平衡的数据集
- 应与精度保持平衡
子一代
- 精度的调和平均值和召回
- 没有考虑到真正的负面因素
- 不受类别不平衡的影响,可应用于不平衡的数据集

上述所有指标均可在 scikit-learn 库中找到:
from sklearn.metrics import accuracy_score
from sklearn.metrics import balanced_accuracy_score
from sklearn.metrics import recall_score
from sklearn.metrics import precision_score
from sklearn.metrics import f1_score
附言
我在这里跳过了一些指标,如果您有不平衡的数据集,也可以使用这些指标,如 Precision-Recall curve,或 Kappa score,或 AUC ROС,它们也不受类不平衡的影响……或者不是?
让我们在下一部分弄清楚:)
**所有图片由作者*提供
数据科学中的性能指标被广泛误解和误用
小窍门 ,实用见解
在评估分类器的性能时,您应该做些什么来避免误用。

您认为准确率达到 99%的分类器是好是坏?很大一部分人会认为正确答案是“好”。太震撼了!。正确答案是两者都不是。
让它深入人心。
我参加过许多会议,会上人们认为如果相关的性能指标高于特定值,那么模型一定是好的。作为一名数据科学家,你应该更清楚。
让我解释一下。任何数据科学家都不应该在没有上下文 的情况下, 记住任何性能指标的任何阈值,以表明它是好是坏。数据科学是应用科学。它非常依赖于上下文。我将用两个广泛使用的性能指标来说明这一点:精确度和接收机工作特性曲线下的面积(ROC)。这两种度量被广泛用于评估分类器的性能。
当你看完这篇文章,你会清楚地明白为什么开题的正确答案是都不是。
ROC 的使用和误用
使用
我想你已经明白 ROC 是做什么的了。我在这里简单介绍一下。不过,如果你是新手或者有点生疏,我有更详细的介绍。
ROC 曲线是评估分类器性能的最广泛使用的性能度量之一。
简而言之,ROC 在 y 轴上绘制灵敏度,在 x 轴上绘制特异性。敏感度为我们提供了正确识别的阳性类别比例的估计值。特异性为我们提供了被正确识别的阴性病例比例的量度。根据阈值的选择,在灵敏度和特异性之间存在权衡。
我们可以通过从一个极端到另一个极端改变阈值来考虑所有可能的敏感性和特异性对,而不是决定使用什么阈值。ROC plot 就是这么做的。它让我们绘制出灵敏度和特异性的所有可能的配对值,并创建一条“曲线”。
AUC(曲线下面积)是一个可以总结整体表现而不是查看每个阈值的权衡的单一指标。ROC 非常适用于比较分类器,然后选择 AUC 较高的分类器进行进一步考虑(见下图)。

ROC 为例。曲线下的面积是选择更好模型的方便指标。随机分类器的 AUC 为 0.5(图片由作者提供)
误用
然而,这就是问题的开始。我们现在不仅使用 ROC 来选择更好的模型,而且更多的时候,我们还将它作为我们数据科学故事的最后一部分。ROC 指标不应作为数据科学项目的最终结果。
这是因为 ROC 代表在所有可能的阈值下运行的模型。然而,在实际操作中,我们只能运行具有单一阈值的模型来进行分类。
使用 ROC 作为分析的最后一部分,类似于说我们不知道哪个阈值可以起作用,所以让我们考虑所有的可能性,绘制结果,然后用 AUC 总结它,就这样吧。
听起来很懒,不是吗?
这种方法对于选择一个模型而不是另一个模型来说是足够的,但是不足以确定所提出的模型是否能够解决手头的问题。这需要进一步的思考和分析,而数据科学家没有受过这方面的正式培训(在典型的课程中你找不到)。
你看,人们通常被教导说,AUC 超过 0.5 意味着你做得比机会好。虽然这是真的,但这并没有使模型变得有用。
如果一个模型比现状更好,它将是有用的。现状并不总是随机的分类器。
在没有现有解决方案的情况下,0.68 的 AUC 可能是有用的。这方面的一个实际例子是我们之前为慢性疾病患者开发的早期预警系统(慢性阻塞性肺病,任何有兴趣的人都可以进一步了解)。该系统旨在帮助患者在家自己管理病情。因为这个特殊案例中的现状没有做任何事情,所以 AUC 为 0.68 的模型是有用的。
然而,如果现有解决方案提供更好的灵敏度和特异性,则 AUC 为 0.80 的模型(提供早期预警以帮助医生更好地管理医院中的患者)是无用的。
底线是什么?没有任何上下文,你无法弄清楚一个给定 AUC 的模型是好是坏。AUC 为 0.80 的模型可能是无用的,而 AUC 为 0.6 的模型可能是好的,这取决于上下文。
你可能会和各种各样的人一起工作,从领域专家到软件开发人员,从市场营销到销售。ROC 是一个很棒的工具,但是对于初学者来说并不简单。作为一名数据科学家,您首先应该清楚在给定的上下文中什么是好的 AUC。
如果一次会议倒退到固定在一个具体的 AUC 值上,你最好准备好证据来解释假设一个具体的 AUC 值是黄金标准是一派胡言。
准确性的使用和误用
对于每个人来说,准确性可能是最直观的性能指标。准确度为 99%的分类器意味着,对于每 100 个输出,分类器正确 99 次。准确度通常以百分比报告。我们倾向于相信任何超过 50%的精确度都是好的,当然,任何超过 90%的精确度都是惊人的。同样,这是一个需要纠正的谬误。
罕见病病例
让我们想象一个简单的假设场景。有一场疾病爆发,感染了很多人。然而,它只在 1%的感染者中致命,而对其余 99%的人来说,对健康没有任何影响。
我们的任务是建立一个分类器,它可以识别出 1%的人,在这些人中感染是致命的,这样他们就可以服用保护性的药丸。由于副作用,我们不能给每个人吃药。
假设我们有 10,000 人的训练数据,其中 100 人不幸死亡。一个完全无用的分类器可能是说没有人会死于感染。 这样一个无用的分类器仍然会有 99%的准确率。
这是你对开题的回答。在上面的场景中,准确率为 99%的分类器毫无用处。除非提供上下文,否则我们无法判断一个 99%准确率的分类器是好是坏。
这种罕见的疾病在现实世界中并不少见。有几个数据科学应用的类别不平衡,有时甚至严重不平衡。
如果班级如此不平衡,你该怎么办?
您需要采用关注稀有类的替代性度量标准,而不是使用准确性作为度量标准。在这种情况下,精确/召回曲线是合适的。Precision 测量从所有被预测为阳性的病例中识别出的实际阳性病例的比例(阳性预测值)。召回衡量被正确识别的阳性病例的比例(与敏感度相同)。一个结合了精确度和召回率的总结指标是 F1 分数(精确度和召回率的加权平均值)。
人们还可以采用其他几种方法,精确/召回只是众多方法中的一种。
最后的想法
这篇文章并不打算为你提供一个所有性能指标的详尽列表(并详述它们的使用和误用)。然而,这篇文章展示了两个最广为人知的例子来说明它们是如何被误解和误用的。
我们数据科学家有责任在给定的环境中选择正确的性能指标,然后详细说明结果意味着什么,以及它们与任何现有解决方案相比如何。
使用一个或两个可以上下文化的度量标准要比给出一个没有韵律或理由的性能度量标准清单好得多。
我们会遇到不一定理解数据细微差别的人,我们可能会出于错误的原因挑选一个指标,但…我们应该这样做吗?
“怯懦会问这样的问题,‘安全吗?’权宜之计提出了这样一个问题,“这是策略吗?”虚荣问这个问题,“它受欢迎吗?”但是,良心会问这样一个问题,“这是对的吗?”总有一天,一个人必须采取一种既不安全,也不政治,也不受欢迎的立场,但他必须这样做,因为他的良心告诉他这是正确的”马丁·路德·金
https://ahmarshah.medium.com/membership
机器学习中的性能度量—第 2 部分:回归
为正确的任务使用正确的绩效指标

来源:尼古拉斯·霍伊泽
在这个由三部分组成的系列文章的上一篇中,我讲述了每个数据科学家在从事分类工作时应该知道的最常见的性能指标。
你可以在这里查看本系列的前一部分。
在第二部分中,我将介绍最适用于回归任务的性能度量。这些是最常见的工具,能够有效地评估一个模型是否实际上性能良好,可以投入生产,或者它仍然需要一些微调。
回归
在深入了解性能指标之前,最好强调一些关键的介绍性概念。
错误
误差是一个非常直观的度量,因为它是一个广为人知的概念,所以不需要什么正式的定义。就机器学习性能而言,关键是要定义,当我们谈论误差时,我们特指实际目标值和预测值之间的差异,即“增量”。
刷新我们对上一篇文章的记忆:
在评估分类模型的性能时,两个概念是关键,即实际结果(通常称为' y' )和预测结果(通常称为' ŷ' )。
例如,可以训练一个模型来预测一个人是否会患某种疾病。在这种情况下,它使用样本进行训练,例如一个人的数据,包含预测信息,如年龄、性别等。每个人都被贴上了一个标签,表明疾病是否会发展。在这种情况下,标签可以是疾病是否会发生( y=1 )或不会发生( y=0 )。
机器学习模型旨在确保每次向其提供样本时,预测的结果都与真实的结果相对应。模型的预测值与真实值越一致,模型的性能就越高。这里提到了许多不同的评估模型性能的方法,但是一般来说,模型会犯错误,降低性能。
实际结果“y”和预测结果'ŷ'之间的差异越大,模型就越“偏离”现象的准确表现;值越接近,系统的性能越好。
均方误差(MSE) /均方偏差(MSD)
均方误差衡量误差平方的平均值。它基本上计算估计值和实际值之间的差异,平方这些结果,然后计算它们的平均值。
因为误差是平方的,所以 MSE 只能假设非负值。由于与大多数过程相关的固有随机性和噪声,MSE 通常为正且不为零。

像方差一样,MSE 的度量单位与被估计量的平方相同。
与方差类似,均方误差的一个主要缺点是对异常值不稳健。如果一个样本的“y”和相关误差远大于其他样本,则误差的平方会更大。这与 MSE 计算误差平均值的事实相结合,使得 MSE 容易出现异常值。
均方根误差(RMSE) /均方根偏差(RMSD)
与均方误差类似,RMSE 计算所有样本的均方误差的平均值,但此外,还计算结果的平方根,实际上就是 MSE 的平方根。
通过这样做,RMSE 提供了一个与目标变量相同单位的误差度量。例如,如果我们的目标 y 是明年以美元计算的销售额,RMSE 会给出以美元计算的误差,而 MSE 会以美元的平方计算,这就不太容易解释了。

平均绝对误差
平均绝对误差不取误差的平方。相反,它只是计算误差的绝对值,然后取这些值的平均值。
MAE 采用绝对值,因为我们对估计值和实际目标值的差异方向不感兴趣(估计值>实际值,反之亦然),而是对绝对距离感兴趣。这也避免了在计算 MAE 时相互抵消的误差。

与 MSE 不同,MAE 对较大误差的惩罚并不比较小误差多,因为 MAE 的公式并不将平方应用于误差。
另一个优点是 MAE 不平方单位,类似于 RMSE,使结果更容易解释。
平均绝对百分比误差(MAPE)
平均绝对百分比误差以百分比形式衡量实际值和预测值之间的误差。它通过类似于 MAE 的方法计算它,但是也用它除以实际值,将结果表示为百分比。

通过将误差表示为百分比,我们可以更好地了解我们的预测相对而言有多差。例如,如果我们要预测下一年的支出,50 美元的平均误差可能是一个相对好或坏的近似值。
例如,如果 50 美元的误差是相对于 100 万美元的实际支出而言的,我们可以有把握地说这个预测相当不错。相反,如果错误是在 60 美元的成本预测上,那么它将与实际值相差甚远。
相对而言,100 万美元预测的 50 美元误差是 0.005%的误差。如果这个误差是在 60 美元的预测上产生的,这将意味着误差是预测值的 83%(基本上导致 10 美元到 110 美元的范围,几乎达到实际值的两倍)。
在这种情况下,使用 MAPE 可以更准确地表示相对于绝对值的误差。
R 的平方(R ) /决定系数
R 的平方(R)代表由自变量 X 解释的因变量 y 的方差的比例。r 解释了一个变量的方差在多大程度上解释了第二个变量的方差。因此,如果模型的 R 是 0.75,那么大约 75%的观察到的变化可以用模型的特征来解释。
r 的计算方法是用 1 减去残差平方和除以总平方和。

r 将所选模型的拟合度与作为基线的水平线的拟合度进行比较。如果选择的模型比水平线拟合得更差,则𝑅为负。因为𝑅公式,即使涉及到“平方”,它也可以有负值,而不违反任何数学规则。只有当模型不遵循数据的趋势,并且比水平线拟合得更差时,𝑅才是负的。
R 的缺点之一是模型中添加的功能越多,R 增加得越多。即使添加到模型中的特征本身没有预测性,也会发生这种情况。
调整后的 R 平方(R)
为此,引入了调整后的 R。它考虑了预测模型中使用的功能。这样,添加到模型中的预测特征越多,调整后的 R 就越高。然而,添加到模型中的“无用”特征越多,调整后的 R 值就越低,这与 R 的情况不同。
因此,调整后的 R 总是小于或等于 R 值。

其中 n 是数据点的数量,而 k 是模型中特征的数量。
使用哪些指标?
总的来说,报告误差度量(如 RMSE)和 R 度量通常很重要。这是因为 R 表示模型中的特征 X 和目标变量 y 之间的关系。相反,误差度量表示数据点相对于回归拟合的分散程度。例如,报告调整后的 R 和 RMSE,可以更好地将模型与其他基准进行比较。
摘要
在浏览了前一篇文章中的分类性能指标之后,我们检查了回归指标。
我们首先强调了错误的含义,然后特别关注每个数据科学家都应该知道的最常见的指标。
误差度量
- 均方误差(mean square error)
- 均方根误差
- 平均绝对误差
- multidimensional assessment of philosophy of education 教育哲学的多维评价
R 指标
- 稀有
- 调整后 R
查看关于性能指标的其他文章,例如:
- Neptune AI 的"机器学习中的性能指标"
想看更多这样的文章,关注我的Twitter,LinkedIn或者我的 网站 。
机器学习中的性能度量—第 3 部分:聚类
为正确的任务使用正确的绩效指标

来源:弗洛里安·施梅兹
在本系列的前两部分中,我们探讨了用于评估机器学习模型的主要性能指标类型。这些涵盖了两个主要类型的 ML 任务,分类和回归。虽然这种类型的任务构成了大多数常见的应用程序,但是还存在另一个关键类别:集群。
要阅读本系列的前两部分,请访问以下链接:
[## 机器学习中的性能度量第 1 部分:分类
towardsdatascience.com](/performance-metrics-in-machine-learning-part-1-classification-6c6b8d8a8c92)
虽然分类和回归任务形成了所谓的监督学习,但聚类形成了大多数非监督学习任务。这两个宏观领域的区别在于使用的数据类型。在监督学习中,样本用分类标签(分类)或数值(回归)来标记,而在非监督学习中,样本不被标记,这使得执行和评估成为相对复杂的任务。
正确测量聚类算法的性能是关键。这一点尤其正确,因为经常需要对集群进行手动和定性检查,以确定结果是否有意义。
在本系列的第三部分中,我们将讨论用于评估聚类算法性能的主要度量标准,严格地说是一组度量标准。
使聚集
剪影分数
轮廓分数和轮廓图用于测量聚类之间的分离距离。它显示了聚类中的每个点与相邻聚类中的点的接近程度。这种度量的范围是[-1,1],是直观检查类内相似性和类间差异的重要工具。
使用每个样本的平均聚类内距离(i)和平均最近聚类距离(n)来计算轮廓得分。样本的轮廓系数是(n - i) / max(i, n)。
n是每个样本和该样本不属于的最近聚类之间的距离,而i是每个聚类内的平均距离。
典型的轮廓图在 y 轴上表示聚类标签,而在 x 轴上表示实际的轮廓分数。轮廓的大小/厚度也与该聚类内的样本数量成比例。

轮廓图示例。在 y 轴上,每个值代表一个聚类,而 x 轴代表轮廓系数/得分。
轮廓系数越高(越接近+1),聚类样本离相邻聚类样本越远。值为 0 表示样本位于或非常接近两个相邻聚类之间的判定边界。相反,负值表示这些样本可能被分配到了错误的聚类。平均剪影系数,我们可以得到一个全局剪影分数,它可以用来描述整个群体的表现与一个单一的值。
让我们通过观察每种配置的性能来了解轮廓图如何帮助我们找到最佳的集群数量:
使用“K=2”,意味着两个聚类来分离群体,我们实现了 0.70 的平均轮廓分数。

将聚类数增加到三个,平均轮廓分数会下降一点。




随着集群数量的增加,也会发生同样的情况。还可以注意到,轮廓的厚度随着聚类数量的增加而不断减小,因为每个聚类中的样本较少。
总体而言,平均轮廓得分为:
For n_clusters = 2 The average silhouette_score is : 0.70
For n_clusters = 3 The average silhouette_score is : 0.59
For n_clusters = 4 The average silhouette_score is : 0.65
For n_clusters = 5 The average silhouette_score is : 0.56
For n_clusters = 6 The average silhouette_score is : 0.45
在计算了 K=6 的每个可能配置的轮廓分数之后,我们可以看到,根据该度量,最佳的聚类数目是两个,并且聚类数目越多,性能变得越差。
要在 Python 中计算剪影分数,只需使用 Sklearn 和 do:
sklearn.metrics.**silhouette_score**(*X*, *labels*, ***, *metric='euclidean'*, *sample_size=None*, *random_state=None*, ***kwds*)
该函数将以下内容作为输入:
- X :样本之间成对距离的数组,或者特征数组,如果参数“预计算”设置为 False。
- 标签:一组标签,代表每个样本被分配到的标签。
兰德指数
另一个常用的指标是兰德指数。它通过考虑所有样本对并对在预测和真实聚类中分配到相同或不同聚类中的样本对进行计数来计算两个聚类之间的相似性度量。
兰德指数的公式是:

RI 的范围从 0 到 1,完全匹配。
Rand Index 的唯一缺点是,它假设我们可以找到地面真实聚类标签,并使用它们来比较我们模型的性能,因此对于纯无监督学习任务,它远不如剪影分数有用。
要计算兰德指数:
sklearn.metrics.**rand_score**(*labels_true*, *labels_pred*)
调整后的兰德指数
随机调整的兰德指数。
Rand 索引通过考虑所有样本对并对在预测聚类和真实聚类中分配到相同或不同聚类中的样本对进行计数来计算两个聚类之间的相似性度量。
然后,使用以下方案将原始 RI 分数“根据机会调整”为 ARI 分数:

类似于 RI,调整后的 Rand 指数的范围是从 0 到 1,0 相当于随机标记,1 相当于聚类是相同的。
与 RI 类似,要计算 ARI:
sklearn.metrics.**adjusted_mutual_info_score**(*labels_true*, *labels_pred*, ***, *average_method='arithmetic'*)
交互信息
互信息是另一种常用于评估聚类算法性能的度量。它是对相同数据的两个标签之间的相似性的度量。其中|Ui|是聚类 Ui 中样本的数量,并且|Vj|是聚类 Vj 中样本的数量,聚类 U 和 V 之间的互信息被给出为:

与 Rand 指数类似,这一指标的一个主要缺点是需要事先知道分布的基本事实标签。这在现实生活中几乎是不可能的。
使用 Sklearn:
sklearn.metrics.**mutual_info_score**(*labels_true*, *labels_pred*, ***, *contingency=None*)
卡林斯基-哈拉巴斯指数
卡林斯基-哈拉巴斯指数也被称为方差比标准。
分数被定义为组内离差和组间离差之间的比率。C-H 指数是评估聚类算法性能的一种很好的方法,因为它不需要关于基本事实标签的信息。
指数越高,性能越好。
公式是:

其中 tr(Bk)是组间离差矩阵的迹线,tr(Wk)是组内离差矩阵的迹线,定义如下:


用 Python 来计算:
sklearn.metrics.**calinski_harabasz_score**(*X*, *labels*)
戴维斯-波尔丁指数
Davies-Bouldin 指数被定义为每个聚类与其最相似的聚类的平均相似性度量。相似性是类内距离与类间距离的比率。这样,距离更远且分散程度更低的聚类将导致更好的分数。
最低分数为零,与大多数性能指标不同,值越低,聚类性能越好。
类似于剪影评分,D-B 指数不需要地面实况标签的先验知识,但是在公式方面比剪影评分具有更简单的实现。
sklearn.metrics.**davies_bouldin_score**(*X*, *labels*)
摘要
总之,在本系列的第三部分中,我们分析了集群性能指标,特别关注:
- 剪影分数
- 兰德指数
- 调整后的兰德指数
- 交互信息
- 卡林斯基-哈拉巴斯指数
- 戴维斯-波尔丁指数
要阅读更多类似的文章,请关注我的 Twitter ,LinkedIn或我的 网站 。
性能测试 Google BigQuery 地理空间——技术深度探索
行业笔记
介绍
数据库性能是一件棘手的事情。当比较不同的技术并尝试选择一种时,有很多因素在起作用。成本、基础设施管理、生态系统、用户体验以及许多其他决策因素都在其中发挥作用。性能变得困难,因为每种技术都有许多不同的旋钮,可以用来调整特定任务的性能。对于地理空间工作负载,我们想尝试找到一些我们可以用来进行性能基准测试的东西,我们还想看看我们是否可以用最少的性能调优工作来做到这一点,并看看效果如何。这篇文章详细介绍了关于如何设置环境的技术信息,我们用来生成测试数据的查询,以及我们用来测试性能的查询。如果你想看统计摘要和动机描述,请参考我在关于这个话题的主帖。
作为参考,我们使用的基准测试基于“地理空间大数据基准测试技术”白皮书,我们将完全在 BigQuery 内部执行数据集生成和运行基准查询。作为参考,我们只处理数据集 1、2 和 3,并且只执行简单、复杂和连接查询。白皮书中的详细信息和结果在第 44 页到第 56 页的“技术比较”部分。我们将跳过字符串处理部分,因为有许多教程演示了 BigQuery 在这方面的能力。
此外,还要感谢谷歌的 Bhavi Patel 帮助调试和优化了一些查询。
预订和空位
如果你计划在你的谷歌云项目中运行这个,请注意这将会产生费用。了解 BigQuery 是如何收费的,以及您可以使用哪些选项,这一点很重要。BigQuery 使用一种称为插槽的机制进行分析。BigQuery 的文档很好地概述了插槽如何工作,以及它如何影响计算性能。对于数据分析,大多数项目通常默认采用按需定价,即每扫描 1tb 数据只收取费用。BigQuery 还允许您通过年度、月度和每分钟级别的预留来预留固定数量的槽位(每分钟预留通常被称为“灵活槽位”)。值得检查您的项目,以了解您当前使用的计费机制(您可能能够在 BigQuery 控制台的容量管理部分查看预订分配),但值得与您的 Google Cloud 管理员核实,以了解您的组织是否有您当前可能被分配的现有预订。出于测试目的,本文中的所有查询都是使用 2000 个 Flex 插槽执行的。
数据生成
作为测试的一部分,我们希望测试 BigQuery 在大规模数据生成方面的能力。使用这篇文章,我们发现我们可以利用 faker.js 中的biqquery JavaScript UDF来生成假数据。这符合我们的需要,因为 faker.js 似乎也可以生成经度和纬度坐标。所有的查询大部分都是即插即用的,但是一定要先阅读关于使用 faker.js 和 BigQuery 的教程,并相应地用你的文件的位置替换云存储桶 URI。
本教程使用了一个名为geotest的数据集,所以如果您想要复制和粘贴查询,那么一定要在您的项目中创建它。如果没有,请确保相应地更改数据集名称。
数据集 1
以下是针对数据集 1 描述的结构:
- 位置:随机点,有效纬度长,例如“54.22312.234234”
- Short_text_field: 单个随机单词+可选数字,如“Dog456”、“3Cat”、“Cow”
- Long_text_field_1: 多个不同的随机单词(10-200 个单词)和标点符号,例如“狗猫鱼牛马猪…#”
- Long_text_field_2: Multiple, varying random words (10–200 words) and punctuation in a random character set (e.g. “狗; 猫”)
- Security_Tag: 从“高”、“中”&“低”中随机挑选
- 数值 _ 字段 _1: 随机整数,如“45”
- 数值 _ 字段 _2: 随机浮点数,如“4.45646”
- 时间戳:最近 10 年的随机时间,如“2007–04–05t 12:00:01”
我们偏离的一列是long_test_field_2,因为我们跳过了字符串处理测试,所以它被确定为非关键的随机字符集,所以所有文本都是 us-EN。
第一个测试是为 100 亿行创建种子表。
DECLARE desired_rows INT64 DEFAULT 10000000000;CALL geotest.generate_rows(‘geotest.row_seq_10bn’, desired_rows);ASSERT (SELECT MAX(row_num) FROM geotest.row_seq_10bn) = desired_rows
AS ‘max row value does not match desired rows’;
ASSERT (SELECT COUNT(*) FROM geotest.row_seq_10bn) = desired_rows
AS ‘row count does not match desired rows’;
ASSERT NOT EXISTS(
SELECT row_num,count(*) as count
FROM geotest.row_seq_10bn
GROUP BY row_num
HAVING count > 1)
AS ‘row_num must not be reused’;
这花了我们 5 分 04 秒的时间。
接下来,我们将运行以下查询来生成数据集。
CREATE TEMP FUNCTION entity(seed INT64)
RETURNS STRUCT<longitude NUMERIC, latitude NUMERIC, short_text_field STRING, long_text_field1 STRING, long_text_field2 STRING, security_tag STRING, numerical_field_1 NUMERIC, numerical_field_2 FLOAT64, timestamp TIMESTAMP>
LANGUAGE js
AS """
var f = getFaker()
f.seed(Number(seed))
var t = {};t.longitude = f.address.longitude();
t.latitude = f.address.latitude();
t.short_text_field = f.random.word() + f.random.alphaNumeric();
t.long_text_field1 = f.lorem.sentences();
t.long_text_field2 = f.lorem.sentences();
var security_tag_temp = f.random.number() % 3;
if (security_tag_temp = 0) {
t.security_tag = "low"
}
else if (security_tag_temp = 1) {
t.security_tag = "medium";
}
else {
t.security_tag = "high";
}
t.numerical_field_1 = f.random.number();
t.numerical_field_2 = f.random.number() * 1.3627;
t.timestamp = f.date.past();return t;
"""
OPTIONS (
library=["gs://path/to/your/faker.js"]
);CREATE OR REPLACE TABLE geotest.dataset1_10bn AS (
SELECT row_num, entity(row_num).*
FROM geotest.row_seq_10bn
where row_num <= 10000000000
);CREATE OR REPLACE TABLE
geotest.dataset1_10bn AS
SELECT
ST_GeogPoint(longitude,
latitude) AS location,
*
FROM
geotest.dataset1_10bn;
这花了我们 1 小时 40 分钟跑完。
这从创建一个临时函数开始,该函数概述了我们想要的随机生成的数据模式。然后创建一个新表,该表使用该结构创建 100 亿行,并将每一行的编号作为种子值。最后,我们用地理点类型替换纬度和经度列。
数据集 2
以下是数据集 2 所需的结构:
- 位置:随机点,有效纬度长,如“54.22313 12.234234”
- Short_text_field :单个随机单词+可选数字,如“Dog456”、“3Cat”、“Cow”
因为数据集 2 需要一千万行,所以我们想要生成一个新的种子表。
DECLARE desired_rows INT64 DEFAULT 10000000;CALL geotest.generate_rows('geotest.row_seq_10m', desired_rows);ASSERT (SELECT MAX(row_num) FROM geotest.row_seq_10m) = desired_rows
AS 'max row value does not match desired rows';
ASSERT (SELECT COUNT(*) FROM geotest.row_seq_10m) = desired_rows
AS 'row count does not match desired rows';
ASSERT NOT EXISTS(
SELECT row_num,
count(*) as count
FROM geotest.row_seq_10m
GROUP BY row_num
HAVING count > 1)
AS 'row_num must not be reused';
这花了我们 35 分钟跑完。与数据集 1 类似,我们可以用同样的方式使用 faker.js 来生成我们需要的数据。
CREATE TEMP FUNCTION entity(seed INT64)
RETURNS STRUCT<longitude NUMERIC, latitude NUMERIC, short_text_field STRING>
LANGUAGE js
AS """
var f = getFaker()
f.seed(Number(seed))
var t = {};t.longitude = f.address.longitude();
t.latitude = f.address.latitude();
t.short_text_field = f.random.word() + f.random.alphaNumeric();
return t;
"""
OPTIONS (
library=["gs://path/to/your/faker.js"]
);CREATE OR REPLACE TABLE geotest.dataset2_10m AS (
SELECT row_num, entity(row_num).*
FROM geotest.row_seq_10m
where row_num <= 10000000
);CREATE OR REPLACE TABLE
geotest.dataset2_10m AS
SELECT
ST_GeogPoint(longitude,
latitude) AS location,
*
FROM
geotest.dataset2_10m;
这花了我们 3m34s 跑。
数据集 3
以下是数据集 3 的结构:
- 位置:随机生成的椭圆,随机长轴为 0.1–10 公里,随机短轴为 0.1–2 公里,方向随机。
- 时间戳:最近 10 年的随机时间,如“2007–04–05t 12:00:01”
这样做的总体思路是生成一个由 16 个随机地理点组成的数组,然后围绕它创建一个凸包来创建我们的多边形。当我们创建某些点的乘数时,还有一些辅助函数来修剪坐标,并希望确保它不会超出地球坐标边界的界限。
CREATE TEMP FUNCTION entity(seed INT64)
RETURNS STRUCT<longitude NUMERIC, latitude NUMERIC, timestamp TIMESTAMP>
LANGUAGE js
AS """
var f = getFaker()
f.seed(Number(seed))
var t = {};t.longitude = f.address.longitude();
t.latitude = f.address.latitude();
t.timestamp = f.date.past();return t;
"""
OPTIONS (
library=["gs://bq-suk/bq_fake.js"]
);CREATE TEMP FUNCTION
trim_latitude(latitude FLOAT64) AS ( GREATEST(-90, LEAST(90, latitude)) );CREATE TEMP FUNCTION
trim_longitude(longitude FLOAT64) AS( GREATEST(-180, LEAST(180, longitude)) );CREATE TEMP FUNCTION
gen_ellipse(longitude FLOAT64,
latitude FLOAT64) AS ( (
SELECT
ST_CONVEXHULL(ST_UNION_AGG(ST_GEOGPOINT(trim_longitude(longitude + RAND() * 0.05),
trim_latitude(latitude + RAND() * 0.05))))
FROM UNNEST(GENERATE_ARRAY(1,16)) ));CREATE OR REPLACE TABLE geotest.dataset3_10bn AS (
SELECT row_num,gen_ellipse(longitude,latitude) as location,timestamp FROM
(SELECT row_num, entity(row_num).*
FROM geotest.row_seq_10bn
where row_num <= 10000000000)
);
这花了我们 1 小时 17 分钟来处理。
数据查询
我们现在将深入研究每个查询,分解我们如何解释它,与 BigQuery 兼容的查询,以及它所用的时间。
数据集 1-查询 1
白皮书中的第一条是:
select * from dataset1 where dataset1.geo is within bbox1
根据该论文,“数据集 1 查询 1 涉及返回英国大小的边界框 1 内的所有点。”
很好,那我们就这么做吧。让我们转到这里获取英国的 GeoJSON 文件,并将该文件下载到您的本地机器上。这些是从国家统计局整理出来的文件,已经为我们转换成 GeoJSON(大多数源文件是 Esri Shapefiles)。我们用于英国边界的具体文件是这里是。然后,我们将使用解析和加载 GeoJSON 文件的指南来处理和加载我们的文件。Python 代码应该可以正常工作,只需确保更改输入文件名以匹配我们下载的内容。
还要注意,Python 脚本可能会给出不正确的数据类型推断。在这里运行它猜测大多数列是 FLOAT64,而它们应该是STRING。只需在运行指南中的bq load命令时进行更改。

作者图片
将一个简单的select *放入 BigQuery Geoviz 告诉我们我们在正确的轨道上。

作者图片
这产生了一个MULTIPOLYGON对象,我们可以用它来进行比较。现在,让我们创建一个创建英国边界的工作表。
CREATE OR REPLACE TABLE geotest.geo_uk_boundaries AS (
SELECT ST_UNION(ARRAY_AGG(geometry)) AS uk_outline_geom
FROM geotest.ref_geographies
)
然后,我们可以使用它来创建我们的查询:
SELECT *
FROM geotest.dataset1_10bn
WHERE ST_CONTAINS((SELECT uk_outline_geom FROM geotest.geo_uk_boundaries), location)
让我们首先尝试使用按需计费来运行它。请注意,因为我们预计生成的数据集会很大,所以使用了目标表。运行时间为 42.7 秒,结果为 4,885,605 行。看起来我们所有的点都在右边界。

作者图片
数据集 1-查询 2
根据白皮书,这里是第二个查询:
select * from dataset1 where dataset1.geo is within bbox2
在这种情况下,bbox2就是伦敦的海德公园。为此,OpenStreetMaps 数据集非常有用,因为它已经包含了大多数地标及其地理信息。
SELECT
geometry
FROM
`bigquery-public-data.geo_openstreetmap.planet_features`
WHERE
osm_way_id = 372975520
看起来这是我们想要的正确边界。让我们将这一点写入我们的WHERE条款。
SELECT *
FROM geotest.dataset1_10bn
WHERE ST_CONTAINS(
(SELECT geometry
FROM `bigquery-public-data.geo_openstreetmap.planet_features`
WHERE osm_way_id = 372975520),
location)
这个查询用了 29 秒完成,产生了 45 行。对输出进行快速地图检查显示,它们都在海德公园内。

作者图片
顺便说一句,如果你想在 OSM 寻找物品或地点,只需进入 OSM 网页,搜索你要找的东西,它会在结果中给你 OSM 身份证号码。

作者图片
数据集 1-查询 3
下面是第三个查询:
select * from Dataset 1 where Dataset 1.timestamp < time1 and Dataset 1.timestamp > time2
为了找到一个过滤时间,我们来看看这个数据集的最小和最大时间戳是多少:
SELECT max(timestamp) as max, min(timestamp) as min
FROM geotest.dataset1_10bn
我们发现它在2021–09–08 23:49:22.194 UTC和2020–09–08 23:49:23.201 UTC之间。因此,我们只需过滤该窗口前后大约四个月的时间。
SELECT *
FROM geotest.dataset1_10bn
WHERE timestamp > TIMESTAMP(DATE "2021-1-8")
AND timestamp < TIMESTAMP(DATE "2021-05-08")
这个跑了 2 分 05 秒。
数据集 1-查询 4
以下是基准查询:
select * from Dataset 1 where Dataset 1.geo is within bbox1 and Dataset 1.timestamp < time1 and Dataset 1.timestamp > time2
让我们使用查询 3 中的相同时间窗口,并向其添加边界框。
SELECT *
FROM geotest.dataset1_10bn
WHERE ST_CONTAINS((SELECT uk_outline_geom FROM geotest.geo_uk_boundaries), location)
AND timestamp > TIMESTAMP(DATE "2021-1-8")
AND timestamp < TIMESTAMP(DATE "2021-05-08")
这个用了 1 分 05 秒。
数据集 1-查询 5
以下是基准查询:
select * from Dataset 1 where Dataset 1.geo is within bbox2 and Dataset 1.timestamp < time1 and Dataset 1.timestamp > time2
为此,我们也将采用与查询 4 中相同的方法。
SELECT *
FROM geotest.dataset1_10bn
WHERE ST_CONTAINS(
(SELECT geometry
FROM `bigquery-public-data.geo_openstreetmap.planet_features`
WHERE osm_way_id = 372975520
), location)
AND timestamp > TIMESTAMP(DATE "2021-1-8")
AND timestamp < TIMESTAMP(DATE "2021-05-08")
这运行了 25 秒,生成了 25 行。

作者图片
数据集 3-查询 1
根据基准测试,下面是我们需要运行的一般查询:
select * from dataset3 where dataset3.geo intersects bbox1
因此,从数据集 1 —查询 1,我们创建了英国边界对象,即bbox1。因此,让我们重新使用它,并查询数据集 3,看看有什么交集。
SELECT a.*
FROM geotest.dataset3_10bn a
JOIN geotest.geo_uk_boundaries b
ON ST_INTERSECTS(location, b.uk_outline_geom)
这在 35 秒内成功运行。
数据集 3-查询 2
以下是基准查询:
select * from dataset3 where dataset3.geo intersects bbox2
当我们在 BigQuery 中运行时,我们使用以下代码:
SELECT *
FROM geotest.dataset1_10bn
WHERE ST_CONTAINS(
(SELECT geometry
FROM `bigquery-public-data.geo_openstreetmap.planet_features`
WHERE osm_way_id = 372975520),
location)
这个查询运行了 18 秒。
数据集 3-查询 3
在这里,我们将使用查询 1,但是也向它添加我们先前的时间边界窗口。以下是基准查询:
select * from dataset3 where dataset3.geo intersects bbox1 and dataset3.timestamp < time1 and dataset3.timestamp > time2
当我们在 BigQuery 中运行时,查询如下:
SELECT a.*
FROM geotest.dataset3_10bn a
JOIN geotest.geo_uk_boundaries b
ON ST_INTERSECTS(location, b.uk_outline_geom)
WHERE timestamp > TIMESTAMP(DATE "2021-1-8")
AND timestamp < TIMESTAMP(DATE "2021-05-08")
这花了 46 秒来运行。
数据集 3-查询 4
这也将为我们的海德公园数据集 3 查询添加时间边界。以下是基准查询:
select * from dataset3 where dataset3.geo intersects bbox2 and dataset3.timestamp < time1 and dataset3.timestamp > time2
在 BigQuery 中,它是这样的:
WITH hyde_park AS (
SELECT geometry
FROM `bigquery-public-data.geo_openstreetmap.planet_features`
WHERE osm_way_id = 372975520
)
SELECT a.*
FROM geotest.dataset3_10bn a
JOIN hyde_park
ON ST_INTERSECTS(location, geometry)
WHERE timestamp > TIMESTAMP(DATE "2021-1-8")
AND timestamp < TIMESTAMP(DATE "2021-05-08")
这个跑了 16 秒。
数据集 1-查询 9
这里,我们在数据集内搜索特定点附近的所有点。以下是基准查询:
select * from dataset1 where dataset1.geo is within 10km of point1, order by distance to point1
由于我住在纽约市,我们将选择帝国大厦的坐标作为我们的搜索点。下面是我们将在 BigQuery 中运行的查询:
SELECT *,
ST_DISTANCE(ST_GEOGPOINT(-73.98567458036479,40.748600441048104), location) distance
FROM geotest.dataset1_10bn
WHERE ST_DWITHIN(ST_GEOGPOINT(-73.98567458036479,40.748600441048104), location, 10000)
ORDER BY distance DESC
最后运行了 4 秒钟,生成了 5,116 行。
数据集 1-查询 10
这次我们只是改变排序字段。以下是基准查询:
select * from dataset 1 where dataset1.geo is within 10km of point1, order by timestamp
下面是 BigQuery 中的查询:
SELECT *
FROM geotest.dataset1_10bn
WHERE ST_DWITHIN(ST_GEOGPOINT(-73.98567458036479,40.748600441048104), location, 10000)
ORDER BY timestamp DESC
这也用了 4 秒钟,生成了 5,116 行。它与前一个非常相似,因为它只是切换了排序列。时差可以忽略不计。
数据集 3-查询 5
在这里,我们应用相同的子句搜索距离该点(在我们的例子中是帝国大厦)10 公里以内的所有地理位置,除了它与在数据集 3 中创建的多边形。以下是基准查询:
select * from dataset3 where dataset3.geo is within 10km of point1, order by closest distance to point1
当我们在 BigQuery 中编写这个代码时,它看起来像这样:
SELECT *,
ST_DISTANCE(ST_GEOGPOINT(-73.98567458036479,40.748600441048104), location) distance
FROM geotest.dataset3_10bn
WHERE ST_DWITHIN(ST_GEOGPOINT(-73.98567458036479,40.748600441048104), location, 10000)
ORDER BY distance DESC
这最终运行了 14 秒,并返回了 7,690 行数据。
数据集 3-查询 6
现在,我们正在做同样的事情,除了切换排序列。基准查询是:
select * from dataset3 where dataset3.geo is within 10km of point1, order by timestamp
当我们对 BigQuery 进行翻译时,我们得到:
SELECT *
FROM geotest.dataset3_10bn
WHERE ST_DWITHIN(ST_GEOGPOINT(-73.98567458036479,40.748600441048104), location, 10000)
ORDER BY timestamp DESC
这为我们提供了同样的 7,690 行数据,运行时间为 15 秒。
连接查询
对于最后一对查询,它要求我们将数据集 1 和 3 与数据集 2 连接起来,查找两个地理位置相距 10 公里以内的所有内容。两个基准查询是:
select * from dataset1 where dataset1.geo is within 10km of any point in dataset2.geo
和
select * from dataset3 where dataset3.geo is within 10km of any point in dataset2.geo
这是最后两个要测试的查询。
连接查询 1
为了做到这一点,我们基本上只想得到数据集 1 中的每个点,并找到数据集 2 中 10 公里内的所有点。由于它不断地将十亿行数据集中的每个对象与一千万个对象进行比较,这在计算上会变得很困难。然而,这个查询非常简单。
SELECT a.*
FROM geotest.dataset1_10bn a
JOIN geotest.dataset2_10m b
ON ST_DWITHIN(a.location, b.location, 10000)
运行了几个小时后,它仍在运行,不清楚什么时候会结束。它也进行了相当多的重新划分。

作者图片
在大查询可视化工具中查看,它看起来非常粗糙..

作者图片
所以一定有更好的方法来运行它。而且有!我们希望这篇博客文章能指导我们如何将查询分成更易管理的部分。这篇博文很好地解释了它是如何工作的,所以我们不会在这里讨论,但这里是我们运行的查询,以便让它与我们的查询一起工作。对于第一个数据集,生成新分区表的查询是:
CREATE OR REPLACE TABLE `geotest.dataset1_10bn_irp`
PARTITION BY RANGE_BUCKET(range_id, GENERATE_ARRAY(0, 3999, 1))
AS (
SELECT *,
ABS(MOD(FARM_FINGERPRINT(CAST(row_num as STRING)), 4000)) range_id
FROM `geotest.dataset1_10bn`
)
这只花了 9m41s 跑。从这里,我们将我们试图运行的查询,重构为这种新的分解方式,这就是我们运行的结果:
DECLARE max_partition int64 DEFAULT 3999;
DECLARE increment int64 DEFAULT 200;
DECLARE start int64 DEFAULT 0;
CREATE OR REPLACE TABLE geotest.query_join_1_output
(
location_a GEOGRAPHY,
location_b GEOGRAPHY,
distance FLOAT64,
row_num INT64,
short_text_field STRING,
long_text_field1 STRING,
long_text_field2 STRING,
security_tag STRING,
numerical_field_1 NUMERIC,
numerical_field_2 FLOAT64,
timestamp TIMESTAMP
);
WHILE start <= max_partition DOINSERT INTO geotest.query_join_1_output
SELECT
a.location as location_a,
b.location as location_b,
ST_DISTANCE(a.location, b.location) as distance,
a.row_num,
a.short_text_field,
a.long_text_field1,
a.long_text_field2,
a.security_tag,
a.numerical_field_1,
a.numerical_field_2,
a.timestamp
FROM geotest.dataset1_10bn a
JOIN geotest.dataset2_10m b
ON
ST_DWITHIN(a.location, b.location, 10000)
WHERE
a.row_num between start and start + increment - 1;
SET start = start + increment;
END WHILE;
这稍微复杂一点,但另一方面,这最终在 2m22s 中运行!

作者图片
这比以前快得多,是分解大型查询的一种非常聪明的方式!
连接查询 2
我们将使用这个查询尝试一些类似的东西。没有分区的原始查询如下所示:
SELECT
a.location AS location_a,
b.location AS location_b,
ST_DISTANCE(a.location,b.location) AS distance,
a.timestamp
FROM geotest.dataset3_10bn_irp a
JOIN geotest.dataset2_10bn b
ON ST_DWITHIN(a.location,b.location,10000)
它遇到了与前一个查询相同的性能问题,作业必须在三个小时后停止。因此,让我们继续用分区重新创建表,与前一个表的创建方式完全一样:
CREATE OR REPLACE TABLE `geotest.dataset3_10bn_irp`
PARTITION BY RANGE_BUCKET(range_id, GENERATE_ARRAY(0, 3999, 1))
AS (
SELECT *,
ABS(MOD(FARM_FINGERPRINT(CAST(row_num as STRING)), 4000)) range_id
FROM `geotest.dataset3_10bn`
)
这需要 8m38s 来创造。然后,我们使用分区表构造查询来计算结果。
DECLARE max_partition int64 DEFAULT 3999;
DECLARE increment int64 DEFAULT 200;
DECLARE start int64 DEFAULT 0;CREATE OR REPLACE TABLE geotest.query_join_2_output
(
location_a GEOGRAPHY,
location_b GEOGRAPHY,
distance FLOAT64,
timestamp TIMESTAMP
);WHILE start <= max_partition DOINSERT INTO geotest.query_join_2_output
SELECT
a.location as location_a,
b.location as location_b,
ST_DISTANCE(a.location, b.location) as distance,
a.timestamp
FROM geotest.dataset3_10bn_irp a
JOIN geotest.dataset2_10m b
ON
ST_DWITHIN(a.location, b.location, 10000)
WHERE
a.row_num between start and start + increment - 1;
SET start = start + increment;
END WHILE;
这只花了 3m18s 运行!

作者图片
那是一个包裹
希望这能让你更好地理解我们是如何生成数据的,以及我们是如何进行测试的。我们做了一些工作来提高连接查询的性能,但是在大多数情况下,我们几乎不需要任何优化工作就可以运行测试。通过组合 BigQuery 的各种功能,并利用平台是完全托管的这一事实,我们能够以最小的努力做一些我认为非常有趣的事情。
一如既往,如果您对代码有任何疑问、反馈或问题,请在评论中告诉我,如果您决定在其他数据技术上运行这些测试,我也很乐意听到这些测试的结果!查询愉快!
用一段代码执行计算机视觉和自然语言处理任务
使用 PaddleHub 加载图像和文本相关数据的预训练深度学习模型

随着深度学习建模的出现,执行 NLP 和深度学习任务变得相对容易。有多个 Python 库可用于执行计算机视觉任务,如图像分割、图像检测等,NLP 相关问题也是如此。
从头开始创建模型可能会花费大量时间和精力,因为我们需要预处理数据并相应地训练模型,并且使用文本数据或图像数据训练模型通常会消耗大量时间。我们可以使用预先训练好的模型进行图像处理,如 VGG16、ImageNet 等,而不是从头开始创建模型。
使用预训练的模型需要一定的编程经验,尤其是在深度学习领域,因为我们需要创建自己的层或编写代码来使用预训练的模型层。如果我们可以直接使用这些预先训练好的模型进行预测,那不是很好吗?因此,即使是非程序员也可以使用这些模型并生成预测。
PaddleHub 是一个开源的 Python 库,在使用预先训练好的模型时,它具有多种功能。它提供了预训练模型的直接使用,这些模型不仅仅限于图像或文本,还可用于音频、视频和工业应用。它包含大约 300 多个预先训练好的模型,可以很容易地免费下载。它还支持多种平台,如 Linux、Windows、macOS 等。
PaddleHub 包含的一些主要预训练模型有:
a.文本识别
b.图像编辑和图像生成
c.人脸检测、对象检测和关键点检测
d.图像分割和图像分类
等等。
在本文中,我们将使用 PaddleHub 预训练模型进行人脸检测、关键点检测和 NLP 相关操作。
让我们开始吧…
安装所需的库
我们将从使用 pip 安装 PaddleHub 开始。下面给出的命令可以做到这一点。
!pip install --upgrade paddlepaddle
!pip install --upgrade paddlehub
导入所需的库
在这一步中,我们将导入所需的库和函数来加载预先训练好的模型,并相应地使用它们。
import paddlehub as hub
使用预先训练的模型
现在我们将开始使用不同类型的预训练模型进行预测。
- 人脸检测
我们将使用人脸检测器模型进行人脸检测。您可以使用任何图像来生成预测。你只需要改变你使用的图像的路径。
module = hub.Module(name="ultra_light_fast_generic_face_detector_1mb_640")res = module.face_detection(
paths = ["/content/IMG-20191210-WA0024.jpg"],
visualization=True,
output_dir='face_detection_output')

面部检测(来源:作者)
2.关键点检测
关键点检测可用于突出关键部分,例如,面部的关键部分是鼻子、耳朵等。
module = hub.Module(name="openpose_body_estimation")res = module.predict(
img="/content/IMG-20191210-WA0024.jpg",
visualization=True,
save_path='keypoint_output')

关键点(来源:作者)
3.词法分析
词法分析或记号赋予器用于将字符序列转换成记号序列。
lac = hub.Module(name="lac")test_text = ["This article is in collaboration with Piyush Ingale."]
print(lac.lexical_analysis(texts = test_text))

词汇分析(来源:作者)
4。情绪分析
在这种情况下,我们试图找出文本数据是积极的,消极的,还是中性的。
senta = hub.Module(name="senta_bilstm")test_text = ["Everyone I met in life taught me something"]
print(senta.sentiment_classify(texts = test_text))

情感分析(来源:作者)
如果我们尝试制作上面讨论的所有这些模型,将会花费很多时间,但在这里,您看到了我们如何使用所有这些预训练的模型,并仅使用一行代码生成预测。这些是我们在这里探索的 Paddlehub 的一些模型。
继续使用不同的数据进行尝试,并使用本文中讨论的所有模型进行预测。如果您发现任何困难,请在回复部分告诉我。
本文是与皮尤什·英加尔合作完成的。
在你走之前
感谢 的阅读!如果你想与我取得联系,请随时通过 hmix13@gmail.com 联系我或我的 LinkedIn 个人资料 。可以查看我的Github*简介针对不同的数据科学项目和包教程。还有,随意探索* 我的简介 ,阅读我写过的与数据科学相关的不同文章。
通过记录链接和监督学习执行重复数据删除
实践教程
用机器学习方法识别重复记录

由 Valentino Funghi 在 Unsplash 上拍摄的照片
介绍
大多数数据都是由人工记录的,而且通常没有经过审核,没有同步,仅仅是因为出现了诸如打字错误之类的错误。请想一想,您是否曾经填写过两次相同的表格,但地址略有不同?例如,您提交了如下图所示的表单:

填写样本名称和地址详细信息(图片由作者提供)
请注意,这些细节实际上是指具有相同“地址”的同一个人“简”。许多组织都在处理这样的数据,它们清楚地显示是重复的,并且表示相同的实体,但是单词并不完全相等。因此,python 函数“drop_duplicates”将无法将这些记录识别为重复记录,因为单词并不完全匹配。所以解决这些乱七八糟的数据的办法就是执行带记录链接的重复数据删除。记录链接通过比较不同来源的记录来确定记录是否匹配并代表同一个实体(个人/公司/企业)。
在本文中,我们将探索使用记录链接和结合监督学习来分类重复和非重复记录。以下是我们将在本文中涉及的主题:
[**Table of Contents:**](http://xx) **
- What is Record Linkage?
- Understand our Data Set
- Applying Record Linkage Process
(a) Preprocessing
(b) Indexing
(c) Comparison & Similarity
(d) Supervised Learning (Classification)
- Conclusion**
什么是记录联动?
记录链接是指识别和链接与同一实体(人员、业务、产品等)相关的记录的方法。)在一个或多个数据源中。它搜索可能的重复记录并将它们链接在一起作为单个记录处理,这也使得避免数据冗余成为可能。
当唯一标识符变量存在于数据集中时,例如(标识号、散列码等),链接相同实体的过程将是简单的。但是,在有些情况下,数据集中不存在唯一的标识符,因此我们需要识别出被复制的变量的良好候选对象,并将它们配对(例如:州、姓氏、出生日期、电话号码)——在执行步骤:索引时,我们会对此有更多的了解。
我们将使用 Python 记录链接工具包库,它提供了执行记录链接和重复数据删除所需的工具和函数。记录链接工具包的安装和导入如下:
了解我们的数据集
对于本教程,我们将使用由 Febrl 项目(来源:可自由扩展的生物医学记录链接)生成的 Python 记录链接工具包下可用的公共数据集。有四组数据可用,但我们将使用第二组数据——feb rl 2。让我们从子模块 recordlinkage.datasets. 导入数据集

加载数据集— FEBRL 2(图片由作者提供)
数据集以“数据帧”的格式返回,我们可以看到这个数据集总共有 5000 条记录。根据 Febrl 的数据集来源,该表中有 4000 条原始记录和 1000 条重复记录。
让我们使用以下内容来更好地理解表中的数据类型:

数据类型信息(作者图片)
根据上面的结果,我们的数据集中的列值具有相同的数据类型——“object”(也称为“String”),并且都具有非空条件。
接下来,我们还应该使用以下函数对数据集的统计汇总进行基本检查:

统计摘要(图片由作者提供)
从统计汇总结果中,我们可以很快看到姓氏 given_name 的唯一计数不是 5000,这表明同一个人在数据集中可能有多个记录,这些记录具有不同的地址/街道号/州等。
我们数据集中的重复记录示例如下所示:

数据集的样本重复记录(作者图片)
请注意,从这一对被称为重复记录的样本记录来看,差异在于“姓氏”、“地址 _2”和“郊区”,只有几个字符的差异。我们的目标是识别并突出显示像这个样本这样的重复记录。
现在,我们已经对数据集有了基本的了解,让我们了解并应用记录链接过程来对数据集进行重复数据删除并正确分类。
应用记录链接流程

记录链接过程(图片由作者提供)
(a)预处理
这一步很重要,因为将数据标准化为相同的格式将增加识别重复项的机会。根据数据中的值,预处理步骤可能包括:
- 小写/大写
这是文本预处理最简单也是最重要的一步,就是将你的文本数据集标准化为全部“小写”或“大写”。在下面的例子中,我们将数据集中的文本转换为大写。

预处理:大写(图片由作者提供)
- 停用词移除
停用词是常见的词,被删除是为了给文本中更重要的信息提供更大的重要性。例如在一个完整的句子中,停用词有、【the】、【a】、等。对于公司名称,停用词可以是、【公司】、【公司】、【有限公司】、等。对于人名,停用词可以是、【先生】、【夫人】、【女士】、【先生】等。对于地址,停用词可以是、【街】、【地点】、【路】、等。
对于我们的数据集,没有要从名称中删除的停用词,但是我们可以从地址字段“address_1”中删除停用词。在下面的例子中,我们删除了数据集中常见的停用词。

预处理:停用词移除(图片由作者提供)
- 邮政编码清理
邮政编码清理是通过删除可能包含的符号来完成的,如“-”、“+”或空格。(通常,这种清理是在电话号码上完成的,但由于我们的数据集中没有电话号码,我们将对邮政编码应用类似的逻辑)

邮政编码清理示例(图片由作者提供)
下面的例子显示了对“邮政编码”的清理,其中只保留数字值。

预处理:邮政编码清理(图片由作者提供)
- 删除无关符号
特殊符号无助于识别文本中的相似之处,应该清除。以下示例显示了为删除地址字段中不相关的符号而进行的清理。

预处理:去除无关符号(图片由作者提供)
(b)步进
既然我们的数据集已经过预处理并被认为是干净的数据集,我们将需要创建记录对(也称为候选链接)。创建记录对并计算相似性,以确定记录对是否被认为是匹配/重复的。 Python 记录链接工具包提供了索引模块来创建记录配对,从而简化了流程。
有几种索引技术可用于记录链接,例如:
- 完整索引
基于数据集中记录对的所有可能组合创建完整索引。使用完全索引在数据量上有风险,因为记录的数量会以二次方的方式增加。例如,基于我们的 5000 条记录的数据集,使用完整索引总共创建了 12497500 对。

索引方法:完整(图片由作者提供)
- 阻塞
分块索引是完整索引的一个很好的替代方案,因为记录对是基于同一个块(具有公共值)生成的。通过基于特定列进行分块,可以大大减少记录对的数量。例如,通过阻塞“State”列,只有来自同一州的记录对相互链接,总共创建了 2768103 对记录,这也是与完整索引相比较少的记录。

索引方法:块(图片由作者提供)
但是,请注意,拥有较少的记录对并不总是最好的方法,因为如果存在重复的记录,但“State”的值存在拼写错误,则可能会错过实际的匹配。
- 排序邻域
按排序邻域的索引是另一种产生具有邻近值的对的替代方法,例如,以下记录被配对在一起,因为在“姓氏”列中有相似性— Laundon 和 Lanyon 。

索引方法:排序邻域样本对(图片由作者提供)
使用排序邻域索引总共创建了 75034 对,与完整索引和块索引相比,这也是较少的记录。(这也取决于所选列的值内容)

索引方法:排序邻域(图片由作者提供)
在本教程中,我们将结合两种方法对数据集进行索引,即“分块”索引和“排序邻域”索引。
为什么我选择使用多种指数方法?
- 使用“全索引”将为我们提供所有可能匹配的记录对,但将导致记录总数的巨大增长。
- 因此,通过“分块”或“排序邻域”使用索引能够解决记录总数大幅增长的问题。
- 然而,仅仅通过使用“分块”或“排序邻域”方法的索引,就有可能错过实际的匹配。那么,为什么不通过结合这两种方法来减少遗漏实际匹配记录的可能性,并且仍然拥有比全索引更少的记录呢!
下面的命令是追加由“分块”和“排序邻域”创建的记录对。

附加两种索引方法(图片由作者提供)
比较和相似性
现在,我们已经生成了记录对,我们希望对记录对执行比较,以创建一个比较向量来计算两个记录对之间的相似性得分。下图显示了根据“给定姓名”列上的索引对计算和比较的相似性得分。例如,“rec-712-dup-0”和“rec-2778-org”的记录对在给定名称上具有 0.46667 的低相似性得分。

样本比较向量(图片由作者提供)
可以用许多不同的方法进行比较,以计算字符串、数值或日期中的相似性值。在我们计算字符串值的相似性得分的场景中,我们可以使用以下算法:
- 贾罗温克勒
- 莱文斯坦
- 最长公共子串(LCS)
- 雅克卡德
让我们继续计算数据集中不同列的相似性得分。

比较矢量输出(图片由作者提供)
请注意,本例中使用的相似性函数是“Jarowinkler”或“Levenshtein”。Jarowinler 相似性得分是通过给予字符串的开头更多的重要性来计算的,因此该算法用于计算诸如姓名、地址、州等特征的相似性得分。Levenshtein 相似性得分是根据字符的顺序计算的,并提供更高的重要性,因此该算法用于计算街道号、邮政编码等特征的相似性得分。(还有很多其他不同的相似度函数也可以探索,比如“余弦”、“dameray_levenshtein”等)。现在我们已经创建了相似性特征,我们可以进入下一步,即构建监督学习模型。
监督学习(分类)
在本节中,我们将训练一个模型,根据提供的数据集对重复项和非重复项进行分类。但是在我们可以训练模型之前,我们需要在我们的数据集中有一个“标签”列(目标变量),以便模型知道哪些是重复的,哪些不是。
加载数据时,指定“return_links = True”将返回已知的重复记录对。
df, links = load_febrl2(return_links=True)

返回“真实重复”记录对(按作者排序的图像)
我们还可以为真正的重复对计算和创建比较向量,以全面了解它们的相似性得分有多高,并将这组配对转换为下一步的数据帧格式。
duplicate_pairs_vectors = compare.compute(links,df)

“真实重复”记录对的比较矢量输出(图片由作者提供)
从矢量输出中,我们可以通过观察和注意到,对于大多数特征来说,重复对往往具有高相似性得分,从而给出粗略的估计。
下面的步骤是在我们的数据集上创建列“Label”的一些 ETL 过程,由此如果在数据集“duplicate_pairs”中找到配对,则标签为“1”否则为“0”(Duplicate = 1,Not Duplicate = 0)

添加了“标签”列的数据集
在标记数据集之后,注意有 1901 对重复项和 2824073 对重复项,这也表明许多配对被索引但却是唯一的。
有了一组标记的数据,我们可以开始训练一个监督学习模型,将记录分类为“重复”或“不重复”。在这个例子中,我将训练一个 XGBoost 模型来执行分类。下面是用于导入模型库和将数据集拆分为训练集和测试集的命令。

测试集分组(图片由作者提供)
通过查看测试集分布,我们有 760 对模型副本来测试和预测。接下来,我们可以训练 XGBoost 模型,并将训练好的模型应用于测试集,以将记录分类为“重复”或“不重复”
让我们来查看模型归类为“重复项”(predict = 1)的配对记录的输出

模型将记录分类为“重复”(按作者分类)
接下来,我们将仔细挑选前两对,并查看实际记录,以确定有什么不同。

样本记录—归类为“重复”(按作者分类的图像)
从示例记录中,请注意,对于第一次配对,可以在两个地址字段上看到差异。第二次配对的其他地方—可以在街道号码和地址字段中看到差异。看起来模型能够对数据集中不同值的记录进行分类。
恭喜你。我们已经建立了一个模型来识别数据集中的重复项。
结论
在本文中,我们学习了如何结合使用记录链接和监督学习来执行重复数据删除。由此,在能够执行比较以计算相似性得分和用于模型训练之前,记录需要被索引成对。但是,请注意,这是为了理解执行重复数据消除的过程而进行的练习,数据集值很简单。真实世界的数据通常比我们的例子更加混乱和复杂。
感谢您阅读我的文章,如果您喜欢并愿意支持我:
参考和链接:
[1]https://github.com/dedupeio/dedupe-examples.git
[2]https://github . com/vinta software/de duplication-slides/blob/master/slides . ipynb
[3]https://recordlinkage.readthedocs.io/en/latest/
在多索引 Pandas 数据帧上执行分组
探索 set_index()和 groupby()函数

Maksym Kaharlytskyi 在 Unsplash 上的照片
在我的上一篇文章——“使用多索引 Pandas DataFrames ”中,我谈到了如何将单索引数据帧转换成多索引数据帧,以及使用它的各种技术。
在本文中,我将继续讨论多索引数据帧,但这一次我将更多地关注于使用 set_index() 和 groupby() 函数,并探索它们的相似之处和不同之处。
加载数据帧
在这篇文章中,我将使用两个 CSV 文件。第一个是 temperature.csv ,包含以下内容:
Country,City,1/1/2020,1/2/2020,1/3/2020,1/4/2020,1/5/2020
Singapore,Singapore,28,29,28,31,33
England,London,3,4,1,4,7
England,Birmingham,5,7,12,3,6
Japan,Tokyo,6,8,14,6,4
Japan,Tahara,3,4,7,9,5
第二个是湿度. csv ,内容如下:
Country,City,1/1/2020,1/2/2020,1/3/2020,1/4/2020,1/5/2020
Singapore,Singapore,0.71,0.69,0.80,0.72,0.73
England,London,0.81,0.79,0.68,0.82,0.83
England,Birmingham,0.88,0.9,0.92,0.79,0.79
Japan,Tokyo,0.93,0.9,0.91,0.89,0.9
Japan,Tahara,1,0.92,0.99,0.93,0.95
让我们加载两个 CSV 文件并检查它们的结构:
import pandas as pd
df_temp = pd.read_csv("temperature.csv")
df_temp

df_humd = pd.read_csv("humidity.csv")
df_humd

如您所见,这两个文件包含了三个国家不同城市的一系列温度和湿度数据。日期以列的形式排列,这使得操作起来并不容易。因此,更好的办法是使用 melt() 函数来取消它们的透视:
df_temp_melted = \
**df_temp.melt**(id_vars=['Country','City'], # columns to keep
var_name='Date', # name of column to add
value_name='Temp') # name of new column to
# store the temp for each datedf_humd_melted = **df_humd.melt**(id_vars=['Country','City'],
var_name='Date',
value_name='Humidity')
取消透视的 df_temp_melted 数据帧现在看起来像这样:

取消透视的 df_humd_melted 数据帧现在看起来像这样:

因为除了温度和湿度列之外,这两个数据帧是相同的,所以使用 concat()函数将它们合并成一个数据帧是有意义的:
df_melted = pd.concat(
[df_temp_melted, df_humd_melted['Humidity']],axis=1)
df_melted
最后,您有一个包含温度和湿度值的单指数数据框架:

使用 set_index()函数转换为多索引数据帧
让我们尝试使用 set_index() 函数,使用 Country 列为 df_melted 设置一个新的索引:
df_melted.set_index(**['Country']**)
您将看到以下结果。这是一个单索引数据帧:

用国家和城市列作为索引怎么样?当然,您只需要将两列作为一个列表传入:
df_melted.set_index(**['Country','City']**)
因此,现在您有了一个多索引数据框架,以国家和城市作为索引:

当然我们也可以有三列作为索引:
df_melted.set_index(**['Country','City','Date']**)
这是结果:

如果你看上面的输出,你可能会对输出有点失望。您可能希望输出如下所示:

不幸的是, set_index() 函数的输出会因数据在数据帧中的排序方式而异。为了获得上面想要的输出,在使用 set_index() 函数设置索引之前,您需要首先对数据帧进行排序:
**# sort the dataframe first ...
df_melted = df_melted.sort_values(by=['Country','City','Date'])****# ... then set the three columns as index**
df_melted.set_index(['Country','City','Date'])
您现在应该会看到如上所示的输出。
使用 groupby()获取多索引数据帧
获得上一节结果的一个更简单的方法是使用 groupby() 函数。默认情况下, groupby() 函数根据分组所依据的列对数据帧进行排序。使用 groupby(),您的代码将如下所示:
df_melted.**groupby(['Country','City','Date'],
as_index=True).agg(lambda x:x)**
在上面的声明中:
- 首先根据国家、城市和日期对行进行排序
- 然后将指定的列用作索引(通过将 as_index 参数设置为 True (默认值))
- 然后对 groupby() 函数的结果调用 agg() 函数;然后,数字列的每个值(温度和湿度)作为一个序列传递给 lambda 函数
如果 as_index 参数设置为 False ,结果将是单索引数据帧。
您可以通过检查传递给 aggregate ( agg() )函数的值来验证最后一点:
**def check(x):
print(x)
return x**df_melted.groupby(['Country','City','Date'],
as_index=True).agg(**check**)
上面的代码片段将打印出以下输出。温度列中的每个值作为一个序列传入,后面是湿度列中的每个值:
2 5
Name: Temp, dtype: int64
7 7
Name: Temp, dtype: int64
12 12
Name: Temp, dtype: int64
17 3
Name: Temp, dtype: int64
22 6
Name: Temp, dtype: int64
...0 0.71
Name: Humidity, dtype: float64
5 0.69
Name: Humidity, dtype: float64
10 0.8
Name: Humidity, dtype: float64
15 0.72
Name: Humidity, dtype: float64
20 0.73
Name: Humidity, dtype: float64
因为我们的 dataframe 对于每个国家、城市和日期都有一个惟一的值,所以传递给 lambda 函数( x )的每个值都是一个包含一个元素的 Series 对象。因此,我们可以简单地返回 lambda 函数中的序列:
df_melted.groupby(['Country','City','Date'],
as_index=True).agg(**lambda x:x**)
多索引数据帧现在如下所示:

如果在 groupby() 函数中设置 sort=False ,会得到不同的结果。试试看,你就会明白我的意思。
提示:结果将类似于前面使用 set_index() 函数得到的结果。
让我们尝试只使用两列来分组— 国家和城市:
df_melted.groupby(**['Country','City']**, as_index=True).agg(
**lambda x:x**)
以上语句在运行时会产生一个错误:
*ValueError: Must produce aggregated value*
这是因为现在传递给 lambda 函数( x )的序列有不止一行(每个唯一的国家和城市行有不止一行日期、温度和湿度)。并且聚合函数期望您返回一个值(或一个具有一个值的序列),而不是一个具有多个值的序列。要解决这个问题,您需要提供一个函数,比如对序列调用 mean() :
df_melted.groupby(['Country','City'], as_index=True).agg(
**lambda x:x.mean()**)
上述语句现在将产生以下输出:

您还可以向 agg() 函数传递一个字典,指示应用于每一列的特定函数:
df_melted.groupby(['Country','City'], as_index=True).agg( **{
'Temp':['mean','min','max'],
'Humidity':['mean','min','max'],
}**
)
上述语句生成以下输出:

对多索引数据帧执行 groupby
在多索引数据帧上调用 groupby() 函数时,也可以指定索引来执行 groupby。让我们基于您之前看到的 df_melted 数据帧生成一个新的数据帧 df2 :
df2 = df_melted.groupby(['Country','City','Date'],
as_index=True).agg(lambda x:x)
df2
下面是 df2 的样子:

假设您想要每个国家的平均温度和湿度。为此,您可以在 dataframe 上执行一个 groupby() 并指定索引级别:
df2.groupby(level=0).mean()
这里,级别=0 表示国家指数。或者,您也可以直接指定索引的名称:
df2.groupby(level='**Country**').mean()
两个语句都会产生以下结果:

每个城市的平均温度和湿度怎么样?简单,只需指定城市为关卡:
df2.groupby(level='**City**').mean()

最后,每个国家和城市的平均温度和湿度如何?供应列表中的国家和城市:
df2.groupby(level=[**'Country','City'**]).mean()

当然你也可以使用 agg() 函数来指定应用于每一列的特定函数
结论
在本文中,我们看到了 set_index() 和 groupby() 函数,它们都允许您创建多索引数据帧。虽然表面上看起来很相似,但是由 set_index() 创建的输出依赖于数据帧中行的顺序,并要求您对行执行一些预先排序/重新排列。另一方面, groupby() 函数根据默认指定的列对行进行排序,并允许您使用一个或多个索引对多索引数据帧执行“groupby”
set_index() 函数仅适用于单索引数据帧,而 groupby() 既适用于单索引数据帧,也适用于多索引数据帧。
https://weimenglee.medium.com/membership
执行统计估计
试图用最简单的形式解释统计学中的估计过程。

(图片由作者提供)
众所周知,统计学是一门研究收集数据、总结和可视化数据、识别模式、差异、局限性和不一致性以及从样本中推断人口信息的学科。
这种从样本中推断总体信息的过程被称为估计。因为不可能从人口中的每一个成员那里收集信息,所以我们从样本中收集信息,并以我们的方式估计人口的信息。在这个过程中,我们使用一种叫做“估计器”的东西来进行估计。
当对整个人口计算一个值时,它被称为参数,而人口子集(也称为样本)的相应术语被称为统计量。
什么是评估者?
u '(读作 mu)是指总体的平均值或总体的真实平均值。但在大多数情况下,我们不知道这个值,所以我们试图确定一个称为 xbar 的统计量,它是样本的平均值。然后 Xbar 成为我们的估计器。
使用 xbar 来估计 u 似乎是合理的,但是让我们假设我承担这个任务来确定我所在城市所有人的平均身高。我真的不可能去问这个城市的每一个人他们的身高。因此,我决定选择一个较小的样本,即我所在大楼中的人,来估计人们的平均身高。由于我的心不在焉,我最终做了一个不正确的输入,将其中一个值的小数放在了错误的位置(8.0 变成了 80.0)。
我的样本看起来是这样的英寸:[4,5.3,5.2,5.5,5.8,6.0,6.1,80.0]。该样品的平均“xbar”为 14.73 英寸。人真的平均那么高吗?在这种情况下,“Xbar”似乎不是估计“u”的最佳选择。
因此,我决定使用另一种叫做中位数的统计方法。中位数不过是中间值。这个样本的中值是 5.65,这看起来更合理,更接近“u”的实际值。
我的估计量是有偏的还是无偏的?
方差是一种常用的估计量,用于确定数据的分布,通常由以下公式给出:

但是,当我们转向更大的样本(或总体)时,这个方差公式往往会低估方差的值。换句话说,是有倾向性的。偏差只不过是期望值和实际值之间的差异或“xbar-u”。当这个偏差等于 0 时,我们说估计量是无偏的。上述公式产生非零偏差。这方面的证据可以在这里找到:https://en.wikipedia.org/wiki/Bias_of_an_estimator
因此,我们使用下面的公式来计算样本方差,用 1/(n-1)代替 1/n

这个公式得出的偏差等于零。它也没有低估人口方差。直觉上,由于分母现在是一个较小的数字,方差的值较大,因此对于一个较大的样本(或总体),我们自然期望一个较大的方差。
样本均值总是总体均值的无偏估计量。这是因为均值的期望值等于总体的实际值或真实均值。一些样本的均值可能大于总体均值,而一些样本的均值可能低于总体均值。然而,当这个过程重复多次,并且在这些迭代中计算估计值的平均值时,这些采样实验的平均值将最终等于总体平均值。
我们如何确定最佳估计量?
这实际上取决于我们是在试图最小化错误还是最大化获得正确答案的机会。
如果我们试图将误差最小化,我们使用一种叫做均方误差或均方根误差的东西。在真实的实验中,计算估计量的过程在许多不同的样本上重复多次。在没有异常值的情况下,样本均值“xbar”使均方误差(MSE)最小化:

其中“m”是迭代次数。RMSE 不过是 MSE 的平方根。
但是最小化 MSE/RMSE 总是最好的选择吗?想象一下掷骰子的情况。掷骰子的平均值是(1+2+3+4+5+6)/6 = 3.5。我们永远不可能掷出 3.5 的骰子。再一次,想象我们掷出一个 6 面骰子 3 次,我们被要求估计掷骰子的总数。如果我们使用 MSE 方法,并试图确定使 MSE 最小的值,我们将得出结论,总和的期望值将是 3*3.5 = 10.5。
但是掷骰子的总和永远不会是小数。在这种情况下,我们应该选择一个最有可能得到正确答案的估计量,也称为最大似然估计量。对于掷骰子的情况,如果我们说 3 次掷骰子的和是 3、10 或 12,我们得到正确答案的机会就会增加。
估计量的分布是什么?
回到我寻找我所在城市的人们平均身高的好奇心。我在我的大楼里挑选了一个人的样本(显然我犯了一个错误,最终我改正了这个错误),然后计算了一个统计数据——样本的平均值。那里的样本量是 8。我对我的分析不完全满意,所以我决定对更多相同大小的样本重复这个过程。我决定将这个样本集扩展到我朋友的 4 栋楼。然后每个样本产生一个统计数据(不可救药的我仍然坚持样本均值,尽管它有缺陷并且无法处理异常值)。
以下是我的样品:
Sample1:[4,5.3,5.2,5.5,5.8,6.0,6.1,8.0] Mean1: 5.74 Std dev: 1.13
Sample2:[4.8,6.1,6.3,5.0,5.5,5.9,5.8,5.6] Mean2: 5.62 Std dev: 0.51
Sample3:[6.1,6.2,6.3,6.0,5.11,5.10,6.0,6.1]Mean3: 5.86 Std dev: 0.48
Sample4:[5.1,5.2,5.4,5.9,5.5,5.10,5.9,5.5] Mean4: 5.45 Std dev: 0.32
Sample5:[2.5,4.11,5.7,5.3,5.8,5.9,5.2,5.1] Mean5: 4.95 Std dev: 1.14
现在我对平均高度有了一些基本的概念,我决定对大约 100 个样本进行模拟,并计算每个样本的平均值。我使用均值为 5.5、标准差为 0.5、样本大小为 8 的正态分布来生成样本均值。
import numpy as np
import random
means = [5.74,5.62,5.86,5.45,4.95]
for i in range(100):
x = np.random.normal(5.5, 0.5, 8)
xbar = np.mean(x)
means.append(xbar)
这种统计分布,即样本均值,被称为抽样分布。可视化样品的分布意味着:
import seaborn as sns
sns.displot(means,color='green')

图 1:样本平均值的分布(图片由作者提供)
想象一下,如果我的分析只基于样本 3:[6.1,6.2,6.3,6.0,5.11,5.10,6.0,6.1]。从样本的外观来看,我似乎选择了最高的人。这种由于随机选择或噪声造成的估计值的变化,其中一个样本可能没有完全反映真实总体,被称为抽样误差。
我们对评估有多少信心?
我们观察样本均值、样本中值和样本标准差来估计相应的总体值。我们使用一个单独的值来表示我们试图估算的值。这叫做点估计。当我们使用一系列值来估计相应的总体值时,它被称为区间估计。
我们的抽样分布以一个标准误差和置信区间为特征。
平均而言,标准误差衡量的是我们期望估计值偏离真实值的程度,这与标准偏差不同,标准偏差是样本本身的变化。我们在图 1 中看到的是样本平均值的抽样分布的标准偏差。反过来,这就是我们在上一节中看到的 RMSE,即“xbar-u”所有值的平均值的平方根。在上例中,标准误差为 0.17 英寸:
u = 5.5
error = [(mean-u)**2 for mean in means]
rmse = math.sqrt(np.mean(error))
当‘n’足够大,n>30 时,标准误差将等于σ/n 的根,其中σ是真正的标准偏差。

请看图 1,直观地将高度的样本平均值视为一系列值。置信区间是一个区间估计值,它提供了包含在抽样分布的给定部分中的一系列值。如图 2 所示,90%置信区间将是第 5 和第 95 百分位之间的值的范围,即大约(5.2,5.7)英寸。更简单地说,这意味着 90%的人口参数(u)将在 5.2-5.7 英寸的范围内。这并不意味着我们的总体参数(u)有 90%的概率位于 5.2-5.7 英寸的范围内。
import matplotlib.pyplot as plt
from matplotlib import mlab
fig, ax = plt.subplots(figsize=(8, 4))
ax.hist(means, density=1, cumulative=True, label='CDF', color='green')
plt.axhline(y=0.05, color='b', linestyle='-')
plt.axhline(y=0.95, color='b', linestyle='-')
plt.show()

图 2:样本均值的累积分布函数图(作者图片)
置信区间和标准误差是抽样分布的特征,但是它们没有考虑由于抽样偏差和/或测量不精确而产生的误差或噪声。
回到我痴迷于寻找我所在城市的人们的平均身高。我没有实际测量他们的身高,而是决定给他们发电子邮件让他们填写调查问卷。这种方法会有一定的局限性。他们中的一些人可能没有电子邮件,或者他们没有办法回复我的电子邮件,或者有些人可能有时间或可能没有时间填写调查,因为他们的日程安排很忙或不太忙。然后我会对某一类人群进行抽样调查。这被称为采样偏差。收入、可用资源、回应意愿(自我选择:一些回应者拒绝回应)等外部因素可能会间接影响我的评估。
在回复电子邮件调查时,回答者可能会低估或高估或向上或向下舍入他们的身高,这可能会导致测量误差。
结论
进行估计时,重要的是要记住置信区间和标准误差是有用的,但不是抽样误差的唯一指标,并且估计很可能受到其他未考虑的误差的影响,如抽样偏差和测量不准确性。
参考资料:
这一汇编深受艾伦·B·唐尼的 Thinkstats 书的影响。
分三步进行不确定性分析:实践指南
这篇文章将带你通过一个完整的不确定性分析案例研究,使用拉丁超立方体抽样,蒙特卡罗模拟和假设的结果图。

paweczerwi ski 在 Unsplash 上的照片
做出可靠的基于模型的预测并不总是一件容易的事情。
在一般形式下,我们有一个模型 f (。)加上一些模型参数 θ 。这个模型应该模拟一些现实生活中的过程。那么,给定输入 x ,我们就可以用模型来预测,这就引出了 y 。

更多的时候,模型参数是 θ 不确定的,即我们无法确定它的确切值。当我们从有限的、有噪声的训练数据中校准 θ 时,就会发生这种情况。
不确定 θ 的结果是对应的预测 y 也不确定。由于有效的决策在很大程度上依赖于对 y 的可靠预测,因此分析师必须报告模型预测的不确定性。
为此,我们需要做一个远期不确定性量化分析。这种类型的分析旨在量化给定输入数据不确定性的模型预测变化。它被称为正向,因为不确定性信息通过模型从输入流向输出。
在本文中,我们将通过一个完整的案例研究来了解如何在实践中进行前瞻性不确定性量化分析。下面是我们将要做的事情的概述:
- 案例研究 →我们考虑疾病在人群中传播的建模,其中我们将感染和恢复率视为不确定参数,并且我们调查最高感染病例数及其发生时间的预测变化。
- 方法 →该分析将通过蒙特卡洛模拟进行。蒙特卡洛方法直观易懂,易于实施,在不确定性量化方面非常流行。
- 工作流程 →我们分三步进行不确定性分析:随机样本生成、不确定性传播和不确定性可视化。
在接下来的几节中,我们将深入探讨每个步骤的技术细节。

图 1 远期不确定性量化分析概述。(图片由作者提供)
你可以在这里找到同伴 Jupyter 笔记本 ,这里可以转载所有呈现的分析和结果。
目录
1。问题陈述
∘ 1.1 背景
∘ 1.2 不确定性分析
2。蒙特卡洛模拟
3。准备s
∘3.1 包
∘ 3.2 模拟 SIR 模型
4。随机样本生成
∘ 4.1 拉丁超立方抽样
∘ 4.2 样本变换
∘ 4.3 整个抽样过程
5 .蒙特卡洛模拟
6。不确定性可视化
7。假想结局剧情
8。外卖
关于作者
1.问题陈述
1.1 背景
在本案例研究中,我们使用 SIR 模型对疾病在人群中的传播进行建模。在其基本形式中,SIR 模型将𝑁的总人口分为三个不同的区间,这三个区间随着时间的变化而变化:
- SS(t),易感但尚未感染疾病的个体数量;
- I ( t ),感染个体数;
- T4,从疾病中恢复并对疾病免疫的个体数量。
SIR 模型用下列常微分方程组描述了 S ( t )、 I ( t )和 R ( t )种群的时间演化:

其中 β 表示感染率, γ 表示恢复率。
1.2 不确定性分析
我们将𝛽和𝛾视为两个不确定的模型参数。当遇到新疾病爆发时(例如新冠肺炎),这些模型参数通常是未知的。
在实践中,关于感染和康复病人数量变化的官方记录被用来估计𝛽和𝛾.不幸的是,这些记录可能不准确,尤其是在疫情爆发的早期阶段。因此,𝛽和𝛾从“嘈杂”的数据估计将是不确定的。推导参数估计不确定性属于 系统辨识 的范畴,通常发生在正向 UQ 分析之前。
由于我们当前的研究侧重于远期不确定性量化分析,我们将简单假设𝛽−𝛾估计已经完成,并且以下二元正态分布表征了它们的不确定性:

这里,(0.22,0.1)表示(𝛽,𝛾)的平均值,(2e-4,1e-4)表示它们的方差值,4e-5 表示它们的协方差值。
运行 SIR 模型还需要除𝛽和𝛾:之外的其他参数
- I (0),最初感染个体的数量;
- R (0),初始恢复/免疫个体数;
- N ,人口规模。
我们简单地假设它们是当前研究中的常数,包括𝐼0=8、𝑅0=0 和𝑁=1000.
2.蒙特 卡罗模拟
蒙特卡罗模拟是一种简单而强大的统计方法。它使我们能够在甚至不知道分布形式的情况下从目标输出分布中生成代表性样本,这是通过简单地模拟各种输入情景下的模型输出来实现的。稍后,我们可以基于累积的样本检索输出分布。
实施蒙特卡罗模拟非常简单:
- 从𝛽和𝛾的概率分布(即二元正态)中抽取大量随机样本;
- 对于每个样本,将其𝛽和𝛾值插入 SIR 模型,并运行 SIR 模型以预测感兴趣的输出,即最高感染病例数及其发生时间;
- 基于预测的集合,我们可以估计两个输出的联合/边际概率分布。
为了更好地理解蒙特卡罗模拟如何帮助量化模型预测的不确定性,请看我在这里的帖子:
3.准备
在我们着手进行不确定性分析之前,让我们做一些必要的准备。
3.1 包装
除了基本的数据分析和可视化包之外,我们需要导入一些额外的包来促进目标不确定性分析:
[scipy.integrate](https://docs.scipy.org/doc/scipy/reference/tutorial/integrate.html)(第 6 行):我们用这个包来求解 SIR 模型的常微分方程组。[pyDOE](https://pythonhosted.org/pyDOE/)(第 9 行):DOE 代表“实验设计”我们使用这个包来生成𝛽和𝛾.的随机样本具体来说,我们将使用拉丁超立方体采样来实现这个目标。要安装pyDOE,请使用pip install pyDOE。[celluloid](https://github.com/jwkvam/celluloid)(第 19 行):我们使用这个包来创建动画,以增强不确定性的可视化。要安装celluloid,请使用pip install celluloid。
3.2 模拟 SIR 模型
为了保持条理,在运行不确定性分析之前定义一个函数来模拟 SIR 模型是有益的。
SIR_model函数取感染率𝛽、恢复率𝛾、时间点网格(以天为单位) t 计算疫情演变、人口规模 N 以及分别为 I0 和r0**。
通过[scipy.integrate.odeint](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.odeint.html)函数(第 14 行)求解常微分方程组,得到在指定时间网格 t 计算的 S ( t )、 I ( t )、 R ( t )值。
为了直观地理解 SIR 模型的预测结果,我们可以在给定𝛽和𝛾(即𝛽=0.22 和𝛾=0.1.)平均值的情况下运行上述 SIR 模型
S(t)I(t)R(t)的演变如下图所示。我们可以看到,随着越来越多的人康复,易感和感染人群显著下降。

图 2 预测的流行病演变。(图片由作者提供)
通过对时间序列 I ( t )进行后处理,我们可以获得我们感兴趣的输出:感染人数在疫情爆发后 40 天达到峰值,总共达到 190 名感染者。
现在我们一切都准备好了。是时候做一些不确定性分析了!在接下来的三个部分中,我们将详细介绍这三个步骤。
4.随机样本生成
在这一步中,我们将使用拉丁超立方体采样方法生成 β 和 γ 的代表样本。这些生成的样本将用于随后的蒙特卡罗模拟。
4.1 拉丁超立方体采样
拉丁超立方体采样(LHS)是一种先进的采样方法,旨在生成“空间填充”样本。与朴素随机采样方法(如[numpy.random.rand](https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.random.rand.html))相比,拉丁超立方体样本可以在很大程度上避免参数空间中的聚类和间隙(见下图),从而均匀地填充整个参数空间。这一特性在蒙特卡罗模拟中是所希望的,因为它提高了估计精度。

图 3 拉丁超立方体采样可以生成“空间填充”样本。(图片由作者提供)
在当前的研究中,我们采用来自pyDOE包的 LHS 实现来生成样本。pyDOE旨在帮助科学家、工程师、统计学家等。,构建合适的实验设计。此处阅读更多。
4.2 样本转换
pyDOE.lhs创建的样本在[0,1]内均匀分布。因此,我们需要执行样本转换,将获得的样本转换为之前指定的二元正态分布。
4.3 整个取样过程
我们分三步完成整个取样过程:
第一步:从均匀分布(0,1)中生成样本。
对于函数pyDOE.lhs,它接受三个参数:
- n:参数个数(整数);
- samples:要生成的样本数(整数);
- 标准:决定
lhs如何采样点(一个字符串)。
因此,在这种情况下,我指示pyDOE.lhs使用 maxmin 标准为 2 个参数生成 1000 个样本,最大化样本点之间的最小距离。关于标准、中的其他可用选项,请点击此处。
生成的 2D 数组uni_samples有 1000 行 2 列,其中每列保存从均匀分布 U (0,1)中抽取的 1000 个随机实现。
第二步:把均匀分布变成标准正态分布。
对于uni_samples的每一列,我们将把从均匀分布(0,1)中抽取的那些值映射到标准正态分布 N (0,1)中的那些值。**
为了实现这个目标,我们应用了 逆变换采样 技术。这一转换过程如下图所示,其中蓝色曲线是标准正态随机变量的累积分布函数。

图 4 逆变换采样技术。(图片由作者提供)
****第三步:将标准正态分布转化为目标二元正态分布。
一个𝑁-dimensional 多元正态分布𝑌∾n(μ,σ)和一个𝑁-dimensional 独立标准正态分布z∾n(0,I的关系

其中 L 是将乔列斯基分解应用于协方差矩阵 𝚺 得到的下三角矩阵,即,

在上面的变换中,我们假设 𝑌 和 𝑍 都是随机变量的行向量。
基于上述定理,我们首先将 Cholesky 分解([np.linalg.cholesky](https://numpy.org/doc/stable/reference/generated/numpy.linalg.cholesky.html))应用于我们的目标正态分布的协方差矩阵,以得到 𝐿 。然后,我们使用上述等式将从上一步获得的标准正态分布样本转化为遵循我们的目标二元正态分布的样本。
我们可以看到转化后的样本。

图 5 从目标二元正态分布抽取的样本。(图片由作者提供)
从边际分布中,我们可以看到𝛽和𝛾确实遵循正态分布,分别以 0.22 和 0.1 为中心。此外,我们可以从散点图中看到𝛽和𝛾样本正相关。这与我们的目标协方差矩阵的非对角项为正(准确地说是 4𝑒−5)的事实相匹配。
5.蒙特卡洛模拟
现在我们准备执行蒙特卡罗模拟。这一步很简单:我们只需要创建一个循环来预测𝛽和𝛾.的每个样本的 S 、 I 和 R 的演变
6.不确定性可视化
现在我们准备好可视化获得的结果。为了呈现输出的不确定性,我们首先想到的是使用直方图和散点图。Seaborn软件包提供[jointplot](https://seaborn.pydata.org/generated/seaborn.jointplot.html)功能,可以同时显示变量的联合分布和单个变量的边际分布。

图 6 直方图和散点图中显示的输出不确定性。(图片由作者提供)
从上图中,我们可以直观地理解在给定不确定输入参数的情况下,输出是如何变化的。我们可能会注意到以下几点:
- 感染病例的最高数量及其发生日期似乎呈正态分布;
- 最有可能的情况是,感染病例数攀升至最高 180-200 例,这发生在疫情爆发后 38-40 天;
- 我们的两个产出是负相关的。这意味着,如果感染病例的数量在早期达到最高,这个数字也往往会很高,反之亦然。
基于获得的结果,我们可能想问更多的问题,如:
- 什么样的𝛽−𝛾病毒组合导致最高感染病例数超过某个阈值?
- 哪个参数(𝛽或𝛾)对两个输出的变化贡献更大?
- 我们应该如何减少输出预测的不确定性?
为了回答这些问题,我们需要进一步进行全局灵敏度分析和稳健设计分析。与前向不确定性量化一起,它们构成了计算科学和工程中不确定性管理的主干。如果你想了解更多,请看看我在这里的帖子:****
**
7.假设结果图
除了静态可视化之外,我们还可以通过使用一种称为假设结果图的数据可视化技术,使我们的结果呈现更加生动和直观。
假设结果图特别擅长于向更广泛的受众传达分析的不确定性,例如利益相关者、领域专家等。,不一定有很强的统计学背景。基本上,这种方法的工作原理是通过创建动画来循环多个不同的情节,每个情节模拟从结果分布中提取的一个可能的场景。更多详情,请查看我的帖子:
下面,我们将使用celluloid包来创建动画。要查看如何使用celluloid,制作动画的详细教程,请点击此处:
该动画有两个目的:首先,它模拟了易感、感染和康复病例在各种𝛽−𝛾组合下的演变。此外,它还在散点图中捕获了感染病例的最高数量及其发生日期。
图 7 可能的流行病演变动画。(图片由作者提供)
你有它!
8.外卖食品
我们刚刚完成了一个关于进行远期不确定性量化分析的完整案例研究。恭喜你!
以下是我们所取得成就的总结:
- 随机样本生成,其中我们使用拉丁超立方体采样来生成不确定输入参数的代表性样本;
- 不确定性传播,我们使用蒙特卡罗模拟将不确定性从输入传播到输出;
- 不确定性可视化,我们以静态形式(直方图和散点图)和动态形式(假设结果图)可视化输出不确定性。
使用简单蒙特卡罗模拟的一个缺点是相关的高计算成本,在各种输入样本下多次运行模型会导致。对于我们目前的情况,SIR 模型可以相当快地运行。不幸的是,工业应用通常不是这样。
通常,高保真度的物理模拟被用来进行预测,一次模拟运行可能需要几天甚至几周的时间。在这些情况下,运行成百上千次昂贵的物理模拟模型是不可能的。
一种解决方法是首先训练快速评估的替代模型来近似物理模拟。随后的蒙特卡罗模拟可以应用于训练的替代模型,以实现加速的正向不确定性量化分析。要了解更多,请查看我的帖子:
关于作者
我是一名博士研究员,致力于航空航天应用的不确定性量化和可靠性分析。统计学和数据科学是我日常工作的核心。我喜欢分享我在迷人的统计世界中学到的东西。查看我以前的帖子以了解更多信息,并在 中 和Linkedin上与我联系。**
R 中的置换假设检验
探索一种强大的模拟技术,用 R 从头开始实现

埃里克·普劳泽特在 Unsplash 上拍摄的照片
介绍
为了比较实验结果,我们经常使用学生的 t 检验。它假设数据是从总体中随机选取的,以大样本(> 30)到达,或者正态分布,组间方差相等。
如果我们不符合这些假设,我们可能会使用其中一个模拟测试。在本文中,我们将介绍排列测试。
排列测试不是假设基础分布,而是构建它的分布,打破组之间的关联。通常我们感兴趣的是组间均值或中位数的差异,而零假设是没有差异。我们可能会问这样一个问题:从所有可能的排列中,我们的数据看起来会有多极端?所有可能的排列将代表一个理论分布。在实践中,不需要执行所有排列来构建理论分布,而是运行合理数量的模拟来从该分布中抽取样本。通常有 10k 或者 100k 的模拟。
例子
假设我们有一个由 12 家零售店组成的连锁店,我们想测试一种新的销售技巧。例如,我们可以指定或选择 5 家商店,在那里进行尝试。然后将一段时间后的平均销售额与其余 7 家商店的销售额进行比较。
数据不是随机收集的,想必不是正态分布的;而样本本身只有不到 30 个观测值。
在我们的数据集中,我们有两列:treatment——一个二元变量,表示商店是否实施了新方法;结果—记录期末销售额的数字变量。
从箱线图的表示中,我们可以看到各组差异很大,方差大致相等。

结果的直方图似乎不是正态分布的;然而,夏皮罗-维尔克正态性检验得出不显著的 p 值= 0.3654

为了找出组间均值的差异,我们使用内置命令并将其保存到变量 original:
original <- diff(tapply(outcome, treatment, mean))
相差~37.8 分。现在我们问这样一个问题:如果我们将数据混洗一万次,我们多久会观察到这种或更极端的差异?
我们运行了一个没有替换的 10k 轴的模拟图,并记录了每个排列的平均值的差异。然后,我们执行双边测试,计算绝对值超过原始差值绝对值的记录数。
该函数返回模拟分布和 p 值。
如果我们绘制分布图,我们可以观察到我们的原始差异不是特别极端,精确的 p 值为 0.1822818

在许多研究项目中,这将表明各组之间没有统计上的显著差异:然而,在这个商业案例中,我会选择有一些证据表明销售方法影响结果的选项。
与其他测试的比较
如果我们运行 Welch t 检验,我们将得到一个类似的 p 值 0.1815。
夏皮罗-维尔克正态性检验表明,数据可能是从正态分布中得到的,但这不是一般情况。

r 也有几个库来运行排列测试。其中一个是 library(coin),它执行一个独立性测试。它还返回类似的 p 值 0.1858。

结论
排列测试是测量实验效果的有力工具。它易于实现,并且不像其他测试那样依赖于许多假设。直到计算机上的模拟成为常规实施,它才得到广泛的流行。
它与另一个模拟测试密切相关:bootstrapping 假设测试,其中样本是通过替换抽取的。
感谢您的阅读!
在 LinkedIn 上连接
作为机器学习工程师的个人知识管理
我捕获、存储和检索信息的系统

来源:弗雷迪婚姻
介绍
2019 年大学毕业后,我在当地一家初创公司开始工作,担任机器学习工程师。在工作中,我经常发现自己做了大量的阅读和学习。我接触到的信息可以是书籍、Youtube 视频、在线文章、git repos,甚至是 Stack Overflow 的问答。这给了我一些如何在这个领域提高自己的见解。其中一个洞见是使用个人知识管理(PKM)系统来帮助我管理我在做研究和做项目时遇到的所有信息。因此,在这篇文章中,我想和你分享我是如何设置这个系统的。更具体地说,我将带您经历三个阶段,一条信息在我的 PKM 系统中传递,以及我用来实现每个阶段目标的工具。
虽然这篇文章重点介绍了我作为机器学习工程师的经历,但我相信其他专业人士或学者可以从这篇文章中获得一些有用的信息。
我的个人知识管理系统

P.A.R.A 方法实质上是将你的整个数字生活分成项目、领域、资源和档案。你可以在这里阅读更多关于 T2 的方法。
就我个人而言,我使用蒂亚戈·福特介绍的 P.A.R.A 方法来管理我数字生活的方方面面。因此,我的 PKM 系统存在于一个用这种方法创建的更大的系统中。在我的 PKM 系统中,一条信息要经过三个阶段。以下部分将详细解释每个阶段。
第一阶段:快速捕捉
工具: 勾选
在我的 PKM 系统中,一条信息通过的第一阶段是快速捕获阶段。这是当我迅速记下这条信息到一个中心位置。这是很重要的,因为当我在做 ML 项目或做 ML 相关主题的研究时,我会遇到许多可能感兴趣但目前没有时间浏览的论文、书籍、代码库等。为了实现这个目标,我使用 TickTick。即使我使用 TickTick,你选择的任何其他应用程序都可以使用。也就是说,我使用 TickTick 的原因有三点。首先,它可以在我的设备上运行的所有操作系统中使用,包括 iOS 和 macOS。其次,您可以将文件附加到 todo 项目,这在我想将 pdf 添加到快速捕获收件箱时非常有用。第三,总体来说,我喜欢这个应用的用户界面/UX。在 TickTick 中,我利用收件箱部分作为我遇到的任何信息的目的地。每当我遇到我感兴趣的论文、网页、git repo,我都会把它们放到 TickTick 的收件箱部分,稍后再浏览。
第二阶段:整洁的存储
工具: 观念 ,Google Drive, 口径

我的图书馆数据库。
我的 PKM 系统的第二阶段是整洁的存储。在这个阶段,我开始整理我的 TickTick 收件箱部分的所有信息。如果一个信息没有用,我会把它从收件箱里删除。然而,如果我发现它是有用的,那么这个信息就有资格作为一个重要的资源来存储。就像 P.A.R.A 方法中的资源一样,我的 PKM 系统中的资源与一个项目相关,而项目与我想要维持的生活领域相关。我可能在资源中包含的其他元数据有:引用、添加日期、阅读日期、我做的笔记、作者和类型/主题。为了设置系统的这一部分,我使用了 idea、Calibre 和 Google Drive。在概念中,我创建了一个名为 Library 的数据库来存储资源及其元数据,以及它们与项目和领域的关系。如果资源是一个文件(PDF、epub、mobi 等),我首先将文件添加到我的 Calibre library 中,该文件将同步并更新到我的 Google Drive 帐户中。将我的 Calibre 库与 Google Drive 同步的原因是因为我可以对文件进行更改(亮点、注释等)。)所做的更改将在我的所有设备上同步。在文件被同步后,我检索到该文件的 Google Drive 链接,并将它和其他元数据一起存储在我的图书馆数据库中。
阶段 3:有意义的检索
工具:观念
当我想在以后检索资源时,系统的最后阶段就发生了。这个搜索/检索过程完全发生在观念中。因为我用有意义的元数据存储资源,所以我可以通过使用这些元数据来检索它。这是强大的。这让我能够利用我在研究和项目中接触到的所有资源。例如,如果我想找到我读过的与“对象检测”相关的所有资源,那么我可以使用流派/主题元数据来检索它。此外,由于每个资源还包括我在浏览时做的笔记,我将会想起最初让我保留该资源的重要知识。此外,由于我也写中型文章,我发现很容易为每个资源准备好引用,因为一些资源可以作为参考出现在许多不同的文章中。这三个用例只是冰山一角。一旦你开始用这种方式实现你自己的 PKM 系统,你会发现你可以用这种设置做更多的事情。
结论
最后,本文通过解释一条信息在我的 PKM 系统中经历的三个阶段来分享这个系统的细节。我希望它能激励你创建自己的 PKM 系统,并给你一些如何开始这一旅程的想法。
喜欢这篇文章并想表达您的支持?关注我或者给我买咖啡
音乐中的个性检测
实践教程
音乐是我们用来表达自己的许多方式之一。从沮丧时听电台司令,到想去加州旅馆时听老鹰乐队。

作者图片
因此,我和我的一个好朋友,也是我数据科学和工程硕士的同事蒂亚戈·费尔南德斯开始思考如何将音乐与人格特质联系起来。
测量个性
为了开始我们的研究,我们首先要决定如何测量人格。因此,在阅读了心理学领域的一些论文后,我们遇到了多种模型,目的是根据其特质来分析人格:奥尔波特的特质理论,卡特勒的十六因素模型,艾森克的巨人三,和迈尔斯布里格斯类型指标,然而,大五模型是当今人格心理学领域最广泛的模型。
大五模型
它包括在一个连续的范围内(从 0%到 100%)测量五种性格特征。更多的维度已经被尝试添加到这个模型中,但是,与具有更多维度的其他版本相比,当前的五个维度更加可靠和稳定。

如果你已经熟悉五大模型,你可以跳过下面的列表
五种性格特征如下:
- 外向:这种性格特征有两个众所周知的极端,外向和内向。这和与他人的交流互动有关。在这项测试中得分低的人更可能是沉默寡言、沉默寡言、深思熟虑的人。
- 宜人性:这一特质反映在社会和谐中,因此与友好、合作、能够原谅他人相关……在这一特质上得分很低的人往往不值得信任,反之亦然。
- 责任心:这种特质与想要把工作或任务做好、做仔细的品质有关。在这一性格特征上得分高的人往往更有责任感,更好地制定计划,因此与学习和工作中的良好表现相关联。另一方面,那些分数低的人往往更灵活,可塑性更强(极端情况下会疏忽大意)。
- 神经质:这种特质与低耐受性、抑郁、不安全感、愤怒、尴尬等(即情绪不稳定)有关。那些高度神经质的人更容易焦虑、悲伤和自卑。分数低的人更有勇气和自信。
- 开放体验:这是对冒险、不同寻常的想法、想象力和好奇心的欣赏。与得分低的人相比,在这一性格特征上得分高的人往往更愿意尝试新事物。
音乐
我们的主要目标是为音乐收藏(又名播放列表)描绘出个性特征。所以我们决定使用 Spotify 播放列表,并将它们的特征映射到创建它们的人的个性特征上。
为了实现这一点,我们使用了spot ipywitch,它是用于 Spotify Web API 的轻量级 Python 库。有了 Spotify,你可以完全访问 Spotify 平台提供的所有音乐数据。所以我们用它来从播放列表中提取音乐数据(重要的是要提到我们只能访问公共播放列表)。
有了这个 API,我们决定从播放列表中的歌曲中提取出以下特征:
- 持续时间
- 声学
- 可跳舞性
- 活力
- 工具性
- 活性
- 音量
- 语音
- 效价
- 拍子
如果你想更好地理解这些功能的作用和代表,请查看音频功能主题部分的 Spotify 开发者 API 文档。
收集数据
为了收集数据,我们决定创建一个表单,参与者在其中填写他们的 Spotify 标识符和一个 链接到五大测试 ,然后是 5 个字段(每个字段代表一种人格特质),供用户填写在同一测试中获得的结果。
值得一提的是,由于我们从头开始构建数据库,它并不完全平衡,因为我们不能为了拥有一个同质的数据库而排除用户。
我们总共从 85 个不同的用户(大多数是大学生)那里获得了大约 1000 个播放列表,因此我们能够将每个播放列表与创建它的用户的 5 种个性特征对应起来。
对于每个播放列表,我们计算了每个功能的歌曲的平均值。
数据分析
在数据收集的开始,为了获得我们正在处理的数据的一些敏感性,我们做了一个探索性研究,目的是从每个用户的播放列表中找到一些模式。
以下两个图是我们对数据进行的第一次分析。

随机选择的参与者的所有播放列表的特征的雷达图(重叠)

大五人格特征的柱状图
由于我们收集的数据非常混乱和随机,我们决定用 10 个值的 10 个区间来描述数据(0 是聚合 1 到 10 的值的最小区间,9 是聚合 91 到 100 的值的最大区间)。
通过这种方式,我们用这些原始数据和标准化数据分析了播放列表的特征在不同的个性特征方面的表现。
我们用这些线图的目的,在下面,是为了看看我们是否能在离散的数据中识别出一些模式(关于个性特征),因为连续的(我们不会在这里展示)只是一个巨大的混乱。我们对所有的五大性格特征都做了这个,但是为了说明,我们将只展示开放的那些。

支线剧情中与开放性相关的原始特征

图中重叠的归一化要素
因此,用肉眼,我们可以看到性格特征与播放列表特征的相关性,并更详细地分析相关性最高的特征。下一步是计算性格特征和播放列表特征之间的相关性。

性状和特征之间的相关性
出于好奇,我们决定也独立分析个性特征和播放列表特征之间的相关性。

性状间的相关性

特征之间的相关性
从上面图中的相关矩阵中,我们还可以看到一些相关值较高的特征,如能量和声音。由此,我们可以得出结论,具有更多能量的播放列表往往具有更少的声音在下一个图中更详细。

相关能量和声学
特征选择
对于功能的选择,我们没有添加任何更多的功能,因为这项工作的重点依赖于播放列表的特性。然而,我们使用 ReliefF 的目的是为我们的目标选择一个特征。
利用 ReliefF 算法,我们对与 5 个性特征相关的特征进行了分类,能够了解哪些特征对每个特征来说更重要或更不重要。

救济的结果
从表中分析,一般来说,我们可以看到最重要的特征是我们预期的化合价和能量,而不太重要的特征是舞蹈性和持续时间。
机器学习
这里我们将讨论各种分类和回归 模型,目的是预测多重人格特质。因此,我们决定独立对待每一种人格特质,这不仅是因为所讨论数据的随机性(我们认为这只会使分类更加复杂),还因为在关于人格检测主题的文献中,绝大多数也独立分析了各种特质。
然后,我们使用由 z 分数标准化的播放列表的特征作为输入,并将每个离散的个性特征作为目标。
我们使用 960 个播放列表中的 25%进行测试,其余 75%用于训练模型。
对于排名,我们使用 F1 分数来分析哪些模型最适合不同的性格特征。
说到分类器,我们使用以下内容:
- 随机森林分类器
- 决策树分类器
- 额外树分类器
- KNeighbours 分类器
- 支持向量分类器
- 朴素贝叶斯伯努利分类器
- 多层感知器分类器,具有 3 个隐藏层,每层 10 个神经元。
对于回归变量,我们使用以下公式:
- 线性回归
- 支持向量回归
应该注意的是,所有的模型都在 Python 的 Scikit-Learn 库中。
我们还决定测试模型的不同数量的特性,从 2 个最重要的特性(根据 ReliefF)到所有 10 个特性。

f1-不同算法中不同数量特征的得分
由此,我们得出结论,大多数算法在所有特征都为的情况下具有更好的性能,即使有很多 6 和 8 个特征的最佳分数。****
结果
在这一节中,我们将展示针对每一种性格特征测试的所有模型的结果。

宜人性得分

责任心得分

外向性得分

神经质得分

开放性得分
召回率和准确率最高的人格特质是开放性和宜人性,最高分 44.9% 和 41.5% 和平均分分别为 28.8% 和 33.2% 。从所做的测试中,我们能够得出结论,最好的模型是随机森林分类器(尽管近邻分类器也获得了非常好的结果),而最差的模型是线性回归,主要是因为它不是为处理这种数据而设计的。

预测的结果
发现的问题
由于我们是基于自己创建的在线调查从零开始建立数据库的,因此我们无法控制用户反馈的类型。这导致了一个不平衡的数据库。
我们不能使用像 SMOTE 这样的数据平衡算法,因为不仅数据库平衡不佳,而且我们的目标中也有缺失值。事实上,我们没有一个适当平衡的个性特征,这将使我们无法对整个数据领域进行适当的分析,这也将影响机器学习算法。
结论
总之,我们认为工作远未完成,但进展顺利。这项研究让我们在性格检测和音乐领域学到了很多东西。虽然我们在这里没有得到任何惊人的结果,但我们相信,如果我们有更多的数据和一个更平衡的数据库,我们将能够超过 50%的 F1 分数。
虽然在我们阅读的文章中,外向性的特质在大多数情况下是获得最佳结果的特质,但在我们的工作中,它是得分最差的特质(这可能与我们不平衡的数据库有关)。
感谢阅读:)
透视与仿射变换
图像保持线平行吗?
你有没有注意到,当你拍摄一个矩形平面物体的图像时,角很少是 90 度?

图 1:透视投影扭曲的矩形。在 Unsplash 上 Mat the Feeney拍摄的照片
这种现象是透视投影的一个特征,3D 场景中的点(例如,矩形的角)通过一个针丨孔丨被投影到一个平面(相机图像传感器)上。靠近摄像机的线段看起来比距离摄像机较远的相同长度的线段长。直角可能变成锐角或钝角,平行线可能看起来会向消失点会聚。
在自动检查平面物体的情况下,透视变形可能是一个问题。因为我们想要检查对象特征是否具有正确的形状和大小,所以我们想要“反转”透视变形。如果我们能做到这一点,我们就能得到一幅图像,使物体看起来好像照相机的轴垂直于物体的表面。

图 2:左:从两个不同的角度看一本书的图像。右图:透视变形得到补偿后的对应图像。图片由作者提供。
图 2 显示了反转透视扭曲的效果(代码此处为)。在这种情况下,书的角被映射到一个正方形的任意点上,用红色圆圈标记。右侧的图像更适合自动检测,因为可以轻松定位特征并与模板进行比较。
翘曲,对,但是哪一个?
我们希望扭曲受透视失真影响的图像,使其恢复感兴趣平面的特征:角度失真最小,3D 中的平行线看起来平行。
OpenCV 提供了两个函数来做到这一点: warpAffine() 和 warpPerspective() 。它们具有相同的签名,除了 warpAffine()需要 2×3 的变换矩阵,而 warpPerspective()需要 3×3 的变换矩阵。这些矩阵分别由函数 getAffineTransform() 和getperspective transform()计算。前者期望三对匹配坐标,后者期望四对匹配坐标。直观地说,得到三对比得到四对更容易,所以问题出现了:在什么情况下我们可以摆脱三对坐标,使用仿射变换而不是透视变换?
为了回答这个问题,让我们考虑图 3 中的两幅图像。

图 3:左图:棋盘格图像,相机在大约 1 米处。右图:棋盘格图像,相机在大约 7 米处。图片由作者提供。
虽然棋盘在两次图像捕捉之间没有移动,但图案看起来不同。在右侧图像中,相机距离物体约 7 米,线平行度比左侧图像保持得更好。如果我们计算仿射和透视变换,然后相应地扭曲这些图像(代码这里是),我们将获得如图 4 和图 5 所示的图像。

图 4:图 3 图像的仿射变换。棋盘上的点 A、B 和 C 被映射到相应的用红圈标记的任意点。图片由作者提供。

图 5:图 3 中图像的透视变换。棋盘上的点 A、B、C 和 D 被映射到相应的用红色圆圈标记的任意点上。图片由作者提供。
图 4 显示了使用点 A、B 和 c 的仿射变换的效果。右侧图像(在约 7m 处拍摄)得到了相当好的补偿,尽管并不完美(见图 6)。左侧图像仍然严重失真,因为点 D(在图像中甚至不可见)没有投影到靠近其对应的红色圆圈的任何地方。

图 6:图 4 的细节。D 点(蓝色圆盘)被投影得很近,但不完全在红色圆圈的中心。图片由作者提供。
图 5 显示了透视变换的效果。因为四个共面点 A、B、C 和 D 用于计算变换矩阵,所以透视变形在两种情况下都得到了很好的补偿,并且图像看起来相同。
我们应该使用的标准是保持线平行度。仿射变换保持线平行。如果要检查的物体在 3D 世界中具有平行线,并且图像中的对应线是平行的(例如图 3 的情况,右侧),仿射变换就足够了。相反,如果 3D 世界中的平行线在图像中发散(例如图 3 左侧的情况),仿射变换是不够的:我们需要透视变换。
透视变换的推导
将平面场景与像素坐标联系起来的透视变换来自于通过针丨孔丨的 3D 到 2D 投影的一般情况:

…其中 f 是焦距,(lx,ly)是像素的物理尺寸,(cx,cy)是相机的光学中心,以像素为单位。
等式(1)明确示出了针丨孔丨相机模型如何将 3D 点(X,Y,Z)投影到图像坐标(u,v ),直到缩放因子λ。从右到左阅读,它会说:
通过旋转和平移将世界坐标中的点转换为相机坐标中的坐标,然后将这些坐标投影到传感器平面中。
投影矩阵 P 具有 3×4 的形状。
与透视变换的联系来自于场景(点(X,Y,Z)所在的地方)是平面的假设。因此,Z = αX + βY + γ:

将等式(2)中的 P 项与α、β和γ混合,给出了新的 3×3 未知矩阵 M,透视变换矩阵:

为了求解 m 的项,我们必须首先在方程(3)中代入λ=m₂₀ X + m₂₁ Y +m₂₂,这给了我们两个含有九个未知数的齐次线性方程:

我们可以用物平面中的坐标(X,Y)和它们的像素值(u,v)之间的四个对应关系来求解透视变换矩阵 M 的九个元素(达到一个比例因子)。
我们有九个未知数。既然每封信都为我们提供了两个含有九个未知数的线性方程,为什么四封信(以及八个线性方程)就足够了呢?
因为我们处理的是齐次线性方程组,即方程(4)的右边是一个零向量。如果我们添加第九个独立的线性方程,我们将会陷入平凡解 m₀₀ = m₀₁ = … = m₂₂ = 0。我们通过求解四对方程(4)获得的解可以通过任意非零因子来缩放。我们通常用这个自由度来设定 m₂₂=1.
仿射变换
如前所述,仿射变换保持线平行。这意味着一个平行四边形总是映射到另一个平行四边形。当我们计算具有来自两个平行四边形的四个对应的透视变换矩阵时(例如图 5 的情况,右侧),我们观察到 m₂₀和 m₂₁项非常接近于零。你可以用这个脚本来验证这个观察结果,这个脚本比较了普通四边形和平行四边形之间的透视变换矩阵。

图 7:映射两个平行四边形的角的透视变换矩阵的第三行约为[0 0 1]。图片由作者提供。
如果你对经验观察不满意,这里有一个更有说服力的论点:
平行四边形到另一个平行四边形的映射可以分解为平移、旋转、剪切、轴缩放、剪切、旋转和平移(加上可选的反射)。这些变换中的每一个都可以由第三行是[0 0 1]的矩阵来表示,因此它们的乘积的第三行也是[0 0 1]。
仿射变换的特殊情况的等式(3)可以写成:

…我们使用透视变换矩阵的自由度来任意设置 a₂₂=1,通过这样做,我们摆脱了(烦人的)λ乘数。我们可以重新排列这些项,以展平未知向量中的仿射变换项:

通过(6),每封信为我们提供了两个 6 个未知数的线性方程。这就是为什么三个对应足以定义一个仿射变换矩阵。
结论
我们从一组对应关系中解决了在平面场景中用像素坐标映射坐标的问题。哪种类型的变换,透视变换还是仿射变换,这个问题占据了本文的中心部分。
我必须承认,直到最近我才注意到这个问题。我面临的情况是,除了三个对应点之外,转换没有将对象特征映射到它们的理论位置。在深入挖掘透视变换矩阵的起源后,我对仿射变换矩阵的特殊情况有了更好的理解。我希望现在已经很清楚,我们必须注意保持平行,以便决定转变的类型。
感谢您的宝贵时间!
1在真正的相机中,镜头取代了针丨孔丨。镜头允许更多的光到达传感器,代价是减少了景深。
[2]图书参考:从 A 到 Z,动物和字母的惊人冒险,索菲·罗森恩·鲍彻,马克索&毕索,2021
使用 Python 进行 Pfam 数据库过滤
通过字符串搜索选择隐马尔可夫模型(HMM)

取自 Pfam 上 Metallophos (PF00149)的 HMM 标志图截图。
在生物信息学中,隐马尔可夫模型(hmm)用于识别在功能方面可能进化相似的序列。隐马尔可夫模型试图在给定观察到的 Y(氨基酸序列)的情况下或者对于贝叶斯纯粹主义者:P(X | Y)的情况下指定 X(在这个例子中是函数)。在上面的徽标中,其他具有感兴趣功能的基因(金属磷)可能在第 8 位含有 D(天冬氨酸),在第 10 位含有 H(组氨酸)。如果这两个氨基酸存在于一个未标记的基因中,那么称这个新基因的功能为金属蛋白的信心就会增加。XKCD(不出所料)有一个很棒的漫画解释了条件(Y)的观测如何影响 X(太阳爆炸)的预测。

图像归功于 XKCD 。
隐马尔可夫模型在生物信息学中有着广泛的应用。一个被充分利用的存储蛋白质功能域 hmm 的仓库是 Pfam 数据库。Pfam 数据库由 Erik Sonhammer、Sean Eddy 和 Richard Durbin 于 1995 年创建,用于帮助基因组注释。Pfam 34.0 于 2021 年 3 月 24 日发布,包含 19179 个蛋白质家族。蛋白质家族是共享相似序列或功能的一组蛋白质。Pfam 提供了一个 FTP 站点来下载整个数据库。Pfam 网站在允许用户搜索关键字和感兴趣的 Pfam 方面做得很好,但是,直到现在,在查询之后,下载感兴趣的家族是一个耗时的手动过程。
从数据库中过滤 Pfams】

照片由法国人 Daphné Be 在 Unsplash 上拍摄
步骤 1:下载 Pfam-A.hmm.gz 数据库
Pfam-A.hmm 数据库可以从 FTP 站点访问和下载。解压后需要 1.5GB 多一点的内存,所以要准备好。
步骤 2:选择您的家庭或查询选项
本文中的示例是查找包含单词“metallophos”的域。下载数据集中的 Pfams 描述不会像基于网络的搜索那样全面,因为域仅包含家族名称和简短的功能描述文本。
步骤 3:进行过滤
from re import search, IGNORECASE
re 模块是 Python 标准库的一部分。在数据库中,Pfams 由“//”分隔。我可以将所有内容读入内存,但是,您可以通过下面的 awk 命令分割 Pfam-A.hmm 文件,并搜索每个文件:
**注意:将 n=10000 改为较小的数据库文件
awk -v n=10000 'BEGIN{RS=ORS="//\n"; FS=OFS="\n"; c=0} {$1=$1; if(NR%n==1){close(f); f="file_" ++c ".txt"}; print >f}' Pfam-A.hmm
为了将 Pfam 记录分割成一个列表,我们用两个正斜杠("//")分割数据库文件。我们还删除了最后一条记录,它对应于一个空条目。
a_file = open("Path_to_your/Downloads/Pfam-A.hmm", 'r')file_contents = a_file.read().split('//')[0:-1]
现在是字符串搜索魔术:
## Our query string
substring="metallophos"## Loop through our database list
for file in file_contents:
## Check if substring is in record
if search(substring, file, IGNORECASE):
if file[:1] == '\n':
file = file[1:]
## Write matches to new file
with open("metallophos.hmm", "a") as myfile:
myfile.write(file+"//\n")
就是这样!我们对子串 metallophos 的搜索返回了 6 个 Pfam 家族,我们可以用它们来搜索我们感兴趣的基因组以识别相似的功能域。我叫科迪·格利克曼,可以在 LinkedIn 上找到我。一定要看看我的其他一些文章!
1 Sonnhammer,Erik LL,Sean R. Eddy 和 Richard Durbin。" Pfam:基于种子比对的蛋白质结构域家族综合数据库."蛋白质:结构、功能和生物信息学28.3(1997):405–420。
医药品牌销售预测
建立预测的特定行业视角

Powerpoint 创建的图表,图片由作者提供
我记得当我开始在 ARIMA 预测毒品数量时,问了我的同事/前辈一个问题,团队的其他成员使用随机森林模型预测未来三个月的毒品数量。问题是,“预测和预报之间有什么区别吗?”我过去得到的回答是“是的”,但我从未满足过。在他们的讨论中,我意识到他们之间有一种看法,认为两者非常不同。但随着时间的推移,当我从事一个为期 6 个月的预测项目时,我意识到有一条细线,预测只是预测建模最常见的应用之一。那么,到底什么是预测?以及它如何适应预测分析领域?
什么是预测?
对一个普通人来说,预测是基于现在和过去的情景对未知的未来(主要关键字)的最佳判断(简单地说,预测),而预测只是基于未知的数据对结果进行估计。所以,综上所述,预测是带有时间信息的预测的子集,即时间序列数据。很多人都在致力于准确预测,但这是机器学习领域最具挑战性的任务之一,因为未来存在不确定性。许多人尝试过,但都失败了,因为他们要么高估了实际数字,要么取得了远远超出预期的成就。因此,这是一个持续的改进过程,将会看到市场上出现许多新方法。
预测的目的是什么?
为了更深入地研究制药预测,第一个重要的问题是任何制药公司预测的目的是什么?它可以有多种用途- ,如收入规划、生产规划、资源分配、项目优先排序、合作决策、补偿计划、游说工作等等。这使得预测更具挑战性,因为它需要迎合不同的利益相关者。预测利用的另一个复杂性是它是单向使用(在一些其他函数中输入)还是双向使用(用于量化预测中其他市场事件的影响/贡献,称为“预测函数”)。因此,了解预测对象选择最佳方法的需求至关重要。
图片:其他职能领域的预测链接:

Powerpoint 智能艺术,作者图片
未来有什么?
到项目结束时,我明白预测的下一个最重要的方面是记录风险承担者在推动预测时考虑的输入场景和假设,因为这些用于指导资源分配决策。预测者可能无法收集其他职能团队参与的影响品牌的所有领域。请注意,这是一个持续的过程,会根据不断变化的资源分配不时调整预测。
预测不是数字!
另一方面,需要强调的是,预测一般不是一个数字。但在行业中,提供一系列预测是很常见的,包括未来不确定性的变化。这被称为概率预测。
构建您自己的第一个预测模型
现在,作为一名预测者,我将直接进入下一部分,即如何创建预测。
A.定义对预测的需求(在上一节已经讨论过)
B.评估预测的最终受众,即侧重于业务/流程还是侧重于技术?
C.历史品牌销售和活动动态的市场研究(新的/市场内)
D.选择适当的方法并在模型中包含相关的市场资源。
E.最终确定最佳未来假设并分析预测结果
F.呈现给最终用户,即所有利益相关者。
G.在预测模型中添加反馈循环,以便将来进行调整。
图片:预测的过程:

Powerpoint 智能艺术,作者图片
B.为什么需要评估目标受众?
在我项目的每个阶段,我都觉得,虽然最初客户希望利用技术含量高的高级预测分析,如基于树的模型进行预测,但他们后来意识到,由于复杂的机器学习模型带有“黑盒”的光环,它可能不够透明,无法在组织内传播给更广泛的受众。此外,人们认识到,最终用户很难对这种模型进行简单的修改,以频繁调整预测。因此,我们在我们的机器学习模型上建立了一个模拟器,允许客户根据不同的场景调整预测。
所以,我认为真正的责任在于预报员提供满足观众需求的预测方法。
C.无论是新产品还是成熟产品,最重要的是了解品牌在市场中的定位。如果是新产品,由于缺乏数据,预测建模可能不可行,市场研究将是首选(尽管可以考虑类似的旧类比)。在我的案例中,该药物是一个成熟的重磅炸弹,这意味着在市场上有足够的历史数据(5 年)来继续进行。因此,我花了大量时间了解过去每月的时间序列销售,以发现趋势,周期,偏差,逐月增长。
现在,下一个最重要的问题是如何选择“正确的”预测方法?
1.在我们项目的最初讨论中,最初的对话是关于预测的时间范围。这对于预先了解缩小最佳方法和建模结构是非常重要的。因为我必须做一年的模型预测,所以我选择累计月度水平预测以提高准确性。
2.下一个问题是预测水平;对我来说,这是国家和地区&付款人层面(我们称之为次国家)的预测,因为这些推动了客户的业务发展。
结论
到目前为止,我们已经介绍了什么是预测、预测的需求/目的、预测面临的一般挑战,然后从预测者的角度从头开始构建预测。下一篇文章将涵盖最关键的部分,即模型的开发和最终短期预测的呈现。
我将在下一篇文章中继续介绍详细的模型代码/流程图。
注:一些参考资料/图片摘自阿瑟·G·库克的书《制药业预测》
Pharos:如何用 4 行代码可视化高级计算机视觉数据集
这个免费的 Python 库可以为您节省大量时间。但是有什么问题吗?

Bermix 工作室在 Unsplash 拍摄的照片
近年来,深度学习已经取得了很大进展。从业者现在已经远远超出了简单的图像分类任务。在图像和视频中检测甚至分割感兴趣的对象变得越来越容易。计算机视觉已经走过了漫长的道路,但有些东西多年来没有改变。可视化就是其中之一。
Pharos 是一个用于可视化高级计算机视觉数据集的免费库——想想物体检测。它围绕您的数据集构建了一个 Flask web 应用程序,从而使探索变得毫不费力。
通过使用 Pharos,您可以轻松地探索数据集是如何标记的,并确定是否存在任何数据质量问题。该网络应用程序将向您展示现代标签软件所能提供的一切,而无需过多的选项。
听起来不错?让我们先解决问题。
法罗斯适合你吗?
大多数数据科学社区从 Kaggle 获取数据集。组织拥有自己处理的内部数据集,大部分不向公众开放。默认情况下,Pharos 只能可视化来自非常狭窄的数据源的数据集— Graviti Open Datasets 。这是更高级的计算机视觉问题的一种替代方案。
您可以使用 Pharos 来可视化任何图像数据集,但是有一个问题—您必须手动编写数据加载器。您可以在项目之间复制/粘贴 95%的代码,只需修改路径,因此不会花费您太多时间。
这篇文章将向您展示如何可视化 VOC2012 检测数据集,这是一个对象检测数据集,包含来自“flickr”网站的人、动物、车辆和室内物品,因此使用它意味着您同意他们的许可和条款。它还将向您展示如何从零开始为 Kaggle 的猫对狗数据集创建一个数据加载器,该数据加载器在 CC0: Public domain 下获得许可。
您将在下一节看到如何使用 Pharos。
Pharos —开始
让我们从创建一个新的虚拟环境开始。我给我的取名为pharos_env。下面是如何用 Anaconda 创建它:
conda create --name pharos_env python=3.8
conda activate pharos_env
下一步是安装所需的库。你只需要pharos,但是我也会安装 JupyterLab。欢迎您使用任何其他 IDE。安装 Pharos 将会处理所有相关的库,比如底层的 TensorBay。
conda install -c conda-forge jupyter jupyterlab
pip install pharos
就是这样!接下来您可以启动 JupyterLab:
jupyter lab
你必须使用VOC2012Detection数据加载器来加载 Pharos 创建应用程序所需的一切。数据加载器需要一个参数,即数据集的路径:
from tensorbay.opendataset import VOC2012Detection
dataset = VOC2012Detection('VOCdevkit/VOC2012')
数据集已成功加载!现在,您可以使用以下两行将其可视化:
from pharos import visualize
visualize(dataset)
执行上述代码行后,您将看到以下内容,表明 Flask 应用程序已成功启动:

图 1 — Pharos 应用程序已在本地主机上启动(图片由作者提供)
现在,应用程序已经在本地主机上运行了。可以去127.0.0.1:5000/探索一下:

图 2 — Pharos 数据集可视化应用程序(图片由作者提供)
如您所见,Pharos 允许您从数据集中选择任何图像,并探索带注释的对象。可以很容易地看出注释是否正确,数据集是否组织正确。
在自定义数据集上使用 Pharos
现在是从零开始编写数据加载器的繁琐部分。您将为 Kaggle 的狗对猫数据集编写一个,但该代码应该适用于任何图像分类数据集。
如果您想学习如何为对象检测和分割数据集编写数据加载器,请告诉我。
开始之前,请确保您的数据集已按以下方式格式化:
dataset/
test_set/
cat.0.jpg
...
dog.0.jpg
...
training_set/
cat.0.jpg
...
dog.0.jpg
...
猫和狗的图像应该在同一个文件夹中。您不应该为每个图像类都有一个单独的目录。
先做最重要的事——库导入。你需要os、glob和tensorbay的几个类:
import os
import glob
from tensorbay.dataset import Data, Dataset
from tensorbay.label import Classification
下一步是创建一个catalog.json文件。它将包含分类类别的名称。在根目录(而不是数据集目录)中创建文件,并粘贴以下内容:
{
"CLASSIFICATION": {
"categories": [{ "name": "cat" }, { "name": "dog" }]
}
}
下一步是编写一个函数,在给定数据集路径的情况下创建一个tensorbay.dataset.Dataset对象。该函数的工作是创建一个Dataset对象,并存储训练集和测试集的图像路径。该函数使用 TensorBay 的Data和Classification类进行适当的转换并分配类标签。
代码如下:
快到了!现在可以调用DogsVsCats()函数并传递数据集路径:
dataset = DogsVsCats('/Users/dradecic/Desktop/PharosData/dataset/')
下面是dataset变量的内容:

图片 3-tensor bay 数据集对象内容(图片由作者提供)
剩下唯一要做的就是可视化数据集。您可以使用以下代码来实现这一点:
from pharos import visualize
visualize(dataset)
将打开另一个烧瓶应用程序。它看起来是这样的:

图 4 —自定义数据集上的 Pharos 数据可视化应用程序(图片由作者提供)
现在你已经知道了——如何使用 Pharos 可视化定制数据集。
问题仍然是,你应该使用 Pharos 吗?让我们在下一部分回答这个问题。
判决
默认情况下,Pharos 并不是非常有用。最大的缺点是它受限于开放数据集,无法可视化任何来自其他来源的数据。此外,每个数据集在tensorbay.opendataset中都有自己的类。这对于最终用户来说是非常乏味的,我希望在未来的版本中看到改进。
您可以通过编写自己的数据加载器来减轻上述缺点。当然,这需要一些编码,但是最终的 web 应用程序是值得的。也许不是为了简单的图像分类,但肯定是为了更高级的对象检测数据集。
你怎么看?您在可视化对象检测和对象分割数据集方面有什么经验?在评论区告诉我。
喜欢这篇文章吗?成为 中等会员 继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@radecicdario/membership
保持联系
阶段性政策梯度(PPG)第 1 部分
对 PPG 背后理论的直观探究
这是我将讨论 PPG 算法背后的理论的两部分系列的第 1 部分。接下来请务必查看 PyTorch 中的 第 2 部分 的实现。

先决条件
如果你有近似政策优化(PPO)的工作知识和行动者批评方法背后的一般数学知识,这会有所帮助。否则,我会在整篇文章中提供一系列参考资料链接来帮助您:)
联合演员评论家网络的困境
行动者批评框架依赖于同时优化两个不同的目标:政策和价值函数。简而言之,通常由 π (a|s)表示的策略表示代理将从给定的观察到的环境状态中采样的行为的贝叶斯分布,并且价值函数描述了处于特定环境状态的预期(折扣)回报。神经网络非常擅长逼近函数!但是一个问题出现了;我们应该用一个神经网络同时逼近这两个函数吗?
让我们考虑一下这样做的好处:
- 根据经验,由每个目标训练的特征可以用来更好地优化另一个:)
- 使用一个网络比两个网络使用的内存更少:)
然而,也有一些缺点:
- 当使用共享参数时,总是存在一个目标的优化将干扰另一个目标的优化的风险:(
- 两个目标都需要相同水平的样本重复使用。一般来说,价值函数的目标是偏离策略的,因此它具有更高的采样效率:(
该算法
PPG 背后的想法是将两个目标的训练解耦,同时仍然允许将学到的价值函数特征注入到策略网络中。为此,如图 1 所示,使用了不相交的策略和值网络:

图 1:分离的策略和价值网络1
两个θ代表各自网络的网络参数(权重和偏差)。此外,使用了两个不同的培训阶段,即政策阶段和辅助阶段。
政策阶段
策略阶段的目标是优化策略网络本身的训练。
在这一阶段,代理接受与最近策略优化(PPO)相同目标的培训。具体而言,使用以下目标训练策略网络:

PPO 政策目标函数1
其中:

更新前当前策略和旧策略之间的比率1
而 A 代表优势函数。这背后的想法是剪辑更新,以便新策略不会在任何单个更新中偏离旧策略太多;导致更稳定的训练。通常,熵项被添加到这个函数中以鼓励探索(因为均匀分布具有最大熵)。有关这方面的更多详细信息,请参见[2]。
此处,值网络也用预测值和目标值之间的均方差进行更新:

价值网络目标函数1
价值目标和优势函数都是用广义优势估计(GAE)计算的;在所有可能的滑跑长度上 TD 估计值的指数平均值。有关这方面的更多详细信息,请参见3和[4]。
辅助阶段
辅助阶段的目标是优化价值网络的训练,同时也将一些价值特征注入策略中,以帮助其未来的训练。
价值网络的更新就像在策略阶段一样,具有相同的目标函数。
请注意图 1 中策略网络的辅助值标题。这是将学习到的价值特征注入策略网络的关键!现在,我们可以使用这个头部在任意辅助目标函数上训练策略网络。例如,在这种情况下,我们希望添加有用的价值特征,因此我们可以让这个头部预测输入环境状态的值,就像价值网络一样。这样做时,使用相同的价值损失目标函数,除了预测值取自辅助头而不是价值网络:

辅助阶段策略网络的价值目标函数1
然而,仍然存在优化辅助目标函数将干扰政策目标的风险。我们不想在辅助阶段破坏政策阶段取得的进展!为了防止这种情况,我们可以简单地在辅助阶段之前的策略和当前预测的策略之间添加 Kullback-Leibler (KL)散度项:

辅助目标函数1
对于那些不熟悉 KL 散度的人来说,它是从信息论中推导出来的,本质上描述了当两个输入分布相同时,全局最小值为 0 的两个分布如何不同。β系数控制当前策略与前一个策略的接近程度。如果设置得更大,则 KL 散度项的梯度值将更大,并且总的来说,梯度下降将推动策略更趋向于优化该目标而不是辅助目标,反之亦然。
将一切联系在一起
图 2 显示了如何结合策略和辅助阶段来创建 PPG 算法,图 3 显示了本文中使用的默认超参数。

图 2: PPG 算法1

图 3: PPG 默认超参数1
策略阶段每运行 N 次,我们就运行一次辅助阶段。由于该算法的主要目标是优化策略,所以该策略频率参数可以设置得相对较高,因为辅助阶段不直接优化策略。
为了保存对另一个超参数的调整,值时期的数量被设置为与策略时期的数量相同。这可以设置得更高,但没有必要,因为价值网络主要针对辅助阶段。
策略阶段是基于策略的,因此它不会从样本重用中受益。结果,策略时期的数量保持较低,并且在每次策略更新之后收集新的一批经验。
价值函数与策略无关,因此辅助阶段受益于相对于策略阶段的高样本重用,因为其目标是优化价值网络。因此,辅助时期的数量高于策略时期的数量。
前面讲过β系数如何平衡 KL 散度和辅助值目标函数。在本文中,该术语默认设置为 1。但是,这也取决于 KL 散度是如何计算的。不幸的是,KL 散度对于所有的分布都没有封闭的解。因此,有时会使用近似值:

反向 KL 散度的近似形式
这个推导可以在我的文章 这里 中找到。这通常小于完全 KL 散度,因此β系数应根据使用的形式适当改变。
总结想法
PPG 为联合网络 PPO 算法提出的许多问题提供了一个简单而强大的解决方案。让我们回顾一下优势:
- 更高的采样效率:)
- 向策略注入更多受控值:)
然而,与任何事物一样,也有缺点:
- 更多需要考虑的超参数:(
- 两个网络的内存需求增加:(
对于第二个缺点,可以使用单个网络,并在策略阶段简单地分离最后一个共享图层的值函数梯度。这与两个网络解决方案的性能基本相同,因此使点静音。
一件令人兴奋的事情是,还有哪些辅助目标可以用来进一步提高性能!
我希望这篇文章对您有所启发,请务必查看 PyTorch 中的 第 2 部分 。

如果您觉得这篇文章有用,请考虑:
参考
1卡尔·科布、雅各布·希尔顿、奥列格·克里莫夫、约翰·舒尔曼https://arxiv.org/abs/2009.04416提出的阶段性政策梯度
[2]约翰·舒尔曼、菲利普·沃尔斯基、普拉富拉·德里瓦尔、亚历克·拉德福德、奥列格·克里莫夫https://arxiv.org/abs/1707.06347v1的《近似政策优化算法》
3约翰·舒尔曼、菲利普·莫里茨、谢尔盖·莱文、迈克尔·乔丹、彼得·阿贝耳https://arxiv.org/abs/1506.02438使用广义优势估计的高维连续控制
[4] A (Long)窥视强化学习作者 Lilian Wenghttps://Lilian Weng . github . io/lil-log/2018/02/19/A-Long-Peek-into-Reinforcement-Learning . html # combining-TD-and-MC-Learning
[5]约翰·舒尔曼http://joschu.net/blog/kl-approx.html的《逼近 KL 散度》
阶段性政策梯度(PPG)第二部分
PyTorch 中的实现
如果你只是想要一个代码的链接, 这里就是 。尽情享受吧!

照片由 Florian Olivo 在 Unsplash 上拍摄
在本系列的第一部分中,我们探究了 PPG 背后的理论。这一次,我们将采用一种更实用的方法,在 LunarLander-v2 环境上测试之前,使用 PyTorch 实现该算法!!
提醒一下,算法本身如下图 1 所示:

图 1: PPG 算法1
把密码给我!
这篇文章的目的是作为一个演示,如果你想要更多的功能,请留意这个链接,我在这里将算法添加到稳定基线 3 Contrib 开源库!
缓冲
我们需要两个缓冲器。一个用于训练策略阶段并跟踪代理在每个时间步经历的展开,另一个用于训练辅助阶段,它需要存储在每个时间步遍历的值目标和状态。代码 1 中定义了必要的存储器参数。注意,AuxMemory 包含一个 old_values 条目,尽管图 1 中的算法没有指定这一点。这样做是为了允许更新函数中的值裁剪,以便进行更稳定的训练。
代码 1:记忆类型
在代码 2 中,我们使用了集合库中的 deque 数据结构,所以我们不需要担心自己直接实现复杂的缓冲区!我们将把 AuxMemory 元组添加到 aux_memories 缓冲区,把 Memory 元组添加到 memories 缓冲区。
代码 2:缓冲区
网络
接下来,让我们定义我们将使用的神经网络。为了保持简单,我们坚持使用多层感知器(MLPs ),但也可以随意摆弄其他更有趣的网络!记住 PPG 的要点是有两个不相交的网络,所以我们定义了两个网络类,一个用于演员,一个用于评论家。
代码 3 中显示了参与者。注意,我们定义了两个头;actor_head 将输出用于策略分布的预测策略参数,而 value_head 将输出预测辅助值。
代码 3:演员神经网络
代码 4 中显示了批评家。这甚至比演员没有额外的头,只是一个香草 MLP 网络更简单!
代码 4:评论家神经网络
培养
有两个培训阶段,让我们从代码 5 所示的策略阶段开始。
代码 5:策略阶段
clipped_value_loss 如代码 6 所示,可用于促进更稳定的训练:
代码 6:限幅值损失函数
代码 7 说明了辅助阶段。希望现在我们在辅助存储器中存储旧值预测的原因更加明显,因为这里的值损失使用代码 6 中的 clipped_value_loss。
代码 7:辅助阶段
主循环
现在,我们只需要把所有东西放在一起。代码 8 显示了主循环。关键线路是从 105 到 112 ,它们规定了培训时间表。
代码 8:主 PPG 环路
需要注意的是,这是为具有明确分布策略的代理而设计的,所以连续代理在这里不兼容!
结果
我们使用 OpenAI gym 的 LunarLander-v2 环境来测试我们的代理,在下面的 7500 场比赛后检查结果:
特工经过 7500 场训练!
很明显,代理在开始时出了一点问题,但能够自我纠正,再多一点培训,它可能会做得更好!
如果您觉得这篇文章有用,请考虑:
参考
1卡尔·科布、雅各布·希尔顿、奥列格·克里莫夫、约翰·舒尔曼https://arxiv.org/abs/2009.04416提出的阶段性政策梯度
菲克(𝜙k)——熟悉最新的相关系数

实践教程
这在分类变量、序数变量和区间变量之间也是一致的!
最近我在用pandas-profiling做 EDA,一些东西激起了我的兴趣。在相关性选项卡中,我看到了许多我从大学开始就知道的指标——皮尔逊的 r、斯皮尔曼的ρ等等。然而,在这些人中我看到了一些新的东西——菲克( 𝜙 k)。我以前从未听说过这个指标,所以我决定深入研究一下。

作者图片
幸运的是,由pandas-profiling生成的报告还有一个选项,可以显示更多关于指标的细节。提供了以下关于 Phik 的信息:
Phik ( 𝜙 k)是一个新的实用相关系数,它在分类变量、顺序变量和区间变量之间始终有效,捕捉非线性相关性,并在双变量正态输入分布的情况下恢复为皮尔逊相关系数。
我必须说,这听起来真的很有用!尤其是在处理包含数值和分类特征的数据集时。在本文中,您可以找到新的相关性度量的快速总结,以及如何在实践中与phik库一起使用它。
𝜙简介
在许多领域(不仅仅是数据科学),皮尔逊相关系数是衡量两个变量之间相关性的标准方法。然而,它也有一些缺点:
- 它只适用于连续变量,
- 它只考虑了变量之间的线性关系,
- 它对异常值很敏感。
这就是𝜙k 发挥作用的地方,它对当前的定位方法进行了一些改进。Phik 的主要优势是:
- 它是基于对皮尔森的χ2 (卡方)偶然性检验(contingency test)的几个改进——一种对两个(或多个)变量之间独立性的假设检验,
- 它在分类、顺序和区间(连续)变量之间工作一致,
- ti 捕获非线性依赖关系,
- 在输入的二元正态分布的情况下,它回复到皮尔逊相关系数,
- 该算法包含一种针对统计波动的内置降噪技术。
与 𝜙 k 最相似的指标是克莱默的 𝜙 ,这是两个分类变量的相关系数,也是基于皮尔逊的χ2 检验统计。值得注意的是,尽管它是用于分类变量的度量,但它也可用于序数和分箱区间变量。然而,该系数的值高度依赖于每个变量所选择的宁滨,因此可能难以解释和比较。对于𝜙k 来说,情况并非如此。此外,克莱姆的𝜙对异常值很敏感,尤其是对于较小的样本量。
在下图中,您可以看到呈现所选三个相关性指标的一些比较。我们可以看到 𝜙 k 在检测其他系数遗漏的非线性模式方面做得很好。如您所见, 𝜙 k 的值介于 0 和 1 之间,因此没有指示关系的方向。

自然,新方法也有一些缺点:
- 𝜙 k 的计算在计算上是昂贵的(由于引擎盖下的一些积分的计算),
- 没有封闭的公式,
- 没有方向的指示,
- 处理纯数字变量时,其他相关系数会更精确,尤其是对于小样本。
在这篇文章中,我不想深入讨论如何实际计算𝜙k 的细节,主要原因是这个过程比仅仅解释一个公式更复杂,就像皮尔逊的 r 一样。这就是为什么我更喜欢关注前面的实用部分,并让所有感兴趣的人参考的源文件(它以一种友好且易于理解的方式写成)。
Python 实践示例
有一段时间,我想知道什么是测试新相关系数的好数据集。在浏览一些视频游戏新闻时,灵感不期而至——包含所有神奇宝贝的数据集将非常适合分析,因为它结合了分类和数字特征。数据集中没有顺序特征,但这对于演示如何使用phik来说不是问题。你可以在这里找到数据。

和往常一样,第一步是加载库。
然后,我们加载并准备数据。我们只保留相关的列(战斗统计、世代、类型和指示神奇宝贝是否是传奇的布尔标志),因为许多其他列都与进化和其他形式有关。

作者图片
现在我们准备好使用 𝜙 k 系数来探索数据。
𝜙 k 相关矩阵
获得包含成对的 𝜙 k 系数的相关矩阵与使用phik_matrix方法一样简单。默认情况下,库会从用于计算相关系数的数据中删除 nan。此外,为了提高可读性,我们将结果四舍五入到两位小数。
phik_overview = df.phik_matrix()phik_overview.round(2)

作者图片
当我们不提供包含区间列的列表作为参数时,将根据有根据的猜测来选择列。在这种情况下,is_legendary和 related 不是 interval 列,所以我们将创建一个适当的列表并将其作为参数传递。
此外,我们可以手动指定希望用于区间变量的条块。我们不这样做,所以箱子将被自动确定。
为了使表格的分析更容易,我们可以使用plot_correlation_matrix函数将结果绘制成热图。

作者图片
我们可以看到defense和hp,或者hp和attack等变量之间存在一定的相关性。更重要的是,我们可以看到special attack和generation之间没有相关性。
相关性的重要性
在评估相关性时,我们不仅要看系数,还要看它们的统计显著性。因为最终,一个大的相关性可能在统计上是不显著的,反之亦然。

作者图片
上面的热图显示了重要性矩阵。色标指示显著性水平,并且在+/- 5 个标准偏差处饱和。我们上面提到的战斗统计的相关系数的相对高的值在统计上是显著的,而special attack对generation的相关性则不是。
有关如何计算统计显著性以及对“标准”p 值计算进行何种修正的更多详细信息,请参考原始论文。
全局相关性
全局相关系数是表示数据集中一个变量与所有其他变量的总相关性的有用度量。这给了我们一个指示,一个变量可以如何使用其他变量建模。

作者图片
所有变量都有很高的全局相关性度量值,最高的值是defense。我们之前已经看到过defense和其他一些战斗数据有很强的相关性,因此这里的分数最高。
异常显著性
虽然两个连续变量之间的皮尔逊相关性很容易解释,但对于混合类型的两个变量之间的 𝜙 k 来说,情况并非如此,尤其是当它涉及分类变量时。这就是为什么作者提供了额外的功能来查看异常值——来自两个变量的列联表的超出和超出预期频率的赤字。
我们首先来看看连续特征和分类特征。对于那个例子,我们选择了secondary_type和defense。
运行代码会生成以下热图:

作者图片
我们可以从上面的图中得出一些结论——岩石和钢神奇宝贝(作为第二种类型)具有明显更高的防御,而毒药/仙女/飞人则相反。
然后,我们对两个分类变量——主要类型和次要类型——进行类似的分析。

作者图片
分析这张表表明,正常飞行和草毒的神奇宝贝比预期的要多得多,而正常毒和龙虫则少得多。从我对神奇宝贝的了解来看,确实是这样,从这张表得出的结论代表了游戏中神奇宝贝的实际类型。
相关性报告
上面,我们已经看到了四种不同的东西,我们可以通过phik图书馆进行调查。还有一个方便的函数,允许我们用一行代码生成上述所有内容。
该函数通过成对评估所有相关性、其显著性和异常值显著性来生成报告。
注意:对于较大的数据集,图的数量很容易激增。这就是为什么我们可以调整相关性和显著性阈值,只绘制相关变量。
外卖食品
- 𝜙 k 是一个新的相关系数,特别适合处理混合型变量。
- 使用该系数,我们可以找到具有(不)预期相关性的变量对,并评估它们的统计显著性。我们还可以解释每对变量之间的依赖关系。
您可以在我的 GitHub 上找到本文使用的代码。此外,欢迎任何建设性的反馈。你可以在推特或评论中联系我。
如果您喜欢这篇文章,您可能还会对以下内容感兴趣:
</9-useful-pandas-methods-you-probably-have-not-heard-about-28ff6c0bceee>
参考
- Baak、r . Koopman、Snoek、h .和 Klous,S. (2020 年)。具有皮尔逊特征的分类变量、顺序变量和区间变量之间的新相关系数。计算统计&数据分析, 152 ,107043。—https://arxiv.org/abs/1811.11440
- https://stats . stack exchange . com/questions/497742/making-sense-of-the-phi-k-correlation-coefficient
- https://github.com/KaveIO/PhiK
什么是心灵?人工智能爱好者的哲学
思想和理论
人工智能概论系列的第 3 部分

抽象符合应用。资料来源:瓦伦丁·b·克雷默通过 Unsplash 。
“心理生物学在关注自然世界的科学和关注人类经验意义的人文科学之间架起了桥梁。”
艾瑞克·坎德尔
欢迎来到这个探索人工智能的新系列的第三部分(AGI)。如果你错过了第 1 部分或第 2 部分,请检查它们;第一部分介绍了什么是 AGI,第二部分是对人工智能认知科学的简要概述。本周我们将介绍思维哲学中的重要概念,我认为每个计算机科学家、AI/ML 研究者或 AGI 爱好者都应该知道这些概念。
具体来说,我们在介绍和讨论这些问题:
- 什么是头脑?
- 头脑从何而来?
- 我们如何测试计算机/人工智能体的思维存在?
- 机器思维能够真正理解语义吗?
心智的概念——它们的本质,它们的实现,它们的应用等等。——对 AGI 的研究人员,甚至任何对人工智能稍感兴趣的人都有很大的兴趣;可以说,这是人工智能/AGI 研究人员的全部工作:创造人工思维(有些只是比其他人更狭隘)。本周,我将尝试阐明上述关于计算机和思想的问题,并向你展示,即使是关于人工智能、思维和 AGI 的最看似无害的问题,也绝不是…
在这个系列中,每周/每隔一周会有一个新的帖子出现(我希望如此),如果有问题、评论或澄清,可以随时给(mferg@mit.edu 发电子邮件。尽情享受吧!
免责声明:毫无疑问,有些人更有资格对我将涉及的问题和主题进行深入的讨论,从情感到神经形态计算。对于这些人来说,这个系列只是对这些主题的介绍,因此,如果为了简洁而省略或浓缩了一些内容,请见谅。我真的只是想要这个系列,以及激发它的课程,作为一个相关 AGI 主题的调查,这些主题通常在学习人工智能或人工智能时不会被教授。还要注意:这个系列与麻省理工学院或其品牌没有任何关系——我只是喜欢写一些我感兴趣的东西,麻省理工学院并不正式认可这里陈述的观点。
第一部分:什么是心灵?
认知科学来拯救…
“我无法想象无限,但我不接受有限。我希望这种作为我生活背景的冒险永无止境地继续下去。”
-西蒙娜·德·波伏娃,拉维耶斯
F 首先,有点跑题:心灵哲学非常密集(我曾经花了整整一个月辩论摇滚是不是意识?!在哲学课程中…),我们将仅仅触及表面。我打算让这篇文章变得通俗易懂,同时涵盖我认为一个 AGI 研究者或对 AGI 感兴趣的人应该知道的必要内容。焦点(就像在这个系列中一样)是广度,而不是深度:这是对 AGI 感兴趣的所有相关领域的调查(或 AI/ML/RL)。
回到头脑。上周,我们学习了认知科学是对大脑的研究,发生在你头脑中的高级过程,如注意力、意识、感知、语言、记忆等。我们还研究了与 AGI/人工智能/人工智能相关的认知科学的一些细节——比如记忆类型和边缘系统,我还试图准确地解释为什么人工智能的人可能需要知道这些。但是问题仍然存在:什么是头脑?
进入心智的标准模型(SMOM):试图固化 心智为了被认为是心智 必须做的事情。SMOM 论文的作者之一,保罗·罗森布鲁姆博士(2020 年夏天,我有幸在南加州大学的研究项目/小组西格玛认知架构的创建者)在阐述 SMOM 的论文中说1:
头脑是一个可以思考的功能实体,因此支持智能行为。像许多其他动物一样,人类也有头脑。在这样的自然系统中,思想是通过大脑实现的,大脑是一种特殊的物理设备。然而,人工智能的一个关键基础假设是,思维是一种特殊类型的计算实体,即认知系统,可以通过各种物理设备来实现(这是一个最近被重新定义为基底独立性的概念[Bostrom 2003]),无论是自然大脑,传统的通用计算机,还是其他足够功能形式的硬件或软件。
因此,思维是一种抽象概念,或者说是一种“软件”,根据上文所述,它独立于实现它的硬件。也就是说,心灵可以在除了大脑之外的其他媒介中实现(这在心灵哲学中经常被称为“多重可实现性”)。AGI 的好消息!
《SMOM》于 2017 年出版,在很大程度上已经起飞,因为它是一种准确编纂“软件包”必须做什么才能真正被称为大脑的方式。它是通过观察三种不同的认知架构(我们将在几周内介绍认知架构和它们的含义)的共同点而聚合而成的。它可以被认为是大脑必须做的事情的高级地图或蓝图。从本质上说,根据论文所述,成为一个头脑所需要的是1:
- 工作记忆的一个主要模块
- 程序性长期记忆的一个模块
- 陈述性记忆的一个模块
- 感知界面
- 电机接口
- 工作记忆是连接 PLTM、DLTM 和 P/M 接口的中间人。
希望你能明白为什么我上周谈到了相关的认知科学:)

大脑是唯一可以支撑心智的硬件吗?来源:Robina Weermeijer viaUnsplash。
因此,我们实际上可能有一个关于什么是头脑的非常坚实的起点——根据这篇论文,如果它满足上述标准,那么它就是一个头脑,QED。并非所有的思维类型都是平等创造的,一些比另一些更强大或更有特色——例如,人类的思维与海龟的思维有很大不同——但 SMOM 试图为类人类的思维编纂最基本的 。我认为这是一个很好的起点,所以这就是我在本系列剩余部分提到“思维”、“类人思维”或“人造思维”时的意思。
好了,今天我们要解决的问题清单上的第一个问题看起来有一个简单的答案:如果符合 SMOM 标准,大脑就是类似人类的大脑。我们有了一个良好的开端;做好准备,因为哲学思考才刚刚开始…
第二部分:头脑从何而来?
灵魂?大脑?物质的普遍特征?
"除了我们自己的思想,没有什么是我们绝对能控制的."
勒内·笛卡尔
Q 关于精神状态的快速插曲:精神状态是只有思考、感受的生物才能拥有的过程或条件【2】。一些精神状态包括:疼痛,发痒,看到红色,听到音乐,记得你的初吻,情绪,以及在寒冷的夜晚第一次泡热水澡。
好的,回到思想的来源:一个的精神状态和身体有什么关系?就像,他们是分开的,还是有一个灵魂在控制你?如果有,它是如何与物质世界相互作用的?如果这听起来令人困惑,那是因为它是!这就是所谓的 身心问题 (或者特别提到意识时的 难题)——精神状态/体验如何与物质基础/状态相关联?* 什么是红色的?冰淇淋的味道是什么?它从哪里来?我们要看看两个主要的思想流派来回答这个问题:***
- 二元论: 在 17 世纪,有影响力的哲学家笛卡尔(发音为 day-cart :我在第一堂哲学课上把他称为 dess-cart-ees ,这并不好玩:/)阐明了心灵是一个非物质和非身体的实体:一个灵魂或精神物质[2]。所以,根据他的说法,是什么让你成为你,也就是你的思想,实际上是你的灵魂,讨论结束,句号。这就是所谓的“二元论”,或者“笛卡尔二元论”: 你的心灵(你)是什么不是由任何物理的“东西”——它是完全不同的东西[2]。然而,如果你采用这种观点,主要未回答的问题是: 非物质实体(灵魂)如何与物质世界和物质 互动?笛卡尔在他的时代认为答案是松果体,这相对来说很快就被揭穿了。因此,这通常被大多数现代神经科学家/哲学家拒绝,而支持选项二。
- 唯物主义(或一元论/物理主义):我总而言之,你是你的大脑 。你所经历的,相信的等等…都是大脑中生化反应的产物。没有什么神秘的东西在起作用:宇宙都是物质的,大脑也不例外。大多数神经科学家和心灵哲学家可能持这种观点。如果你归因于此,那么主要的未回答的问题是: 精神状态是如何从物理状态中产生的? 什么是主观体验?这可以说是认知科学/心灵哲学中最重要的问题。**

你是你的灵魂吗?还是你的大脑?资料来源:Mim。通过 Unsplash 获得。
这场辩论是所有哲学中最大的一次;它激发了各种各样的争论,因为(传统上)二元论观点与宗教密切相关。然而,现有的立场并不像这种二分法那样清晰——这两者有不同之处,也有不同的组合,所以如果你不归属于其中任何一个,还有其他的:偶然现象论、泛灵论、综合信息理论等等。我只是想介绍这一领域的两个主要理论。神经科学家兼作家保罗·努内斯在他的巨著《意识的新科学》的前几章中展示了更多的阴影,所以如果你想更深入地研究,就去看看吧。
你认为是哪一个?当这是弗吉尼亚大学的一门真正的课程时,这个问题(以及随后出现的其他问题)激发了双方学生的一些最激烈的辩论,所以想一想,看看你会得出什么结论!
第三部分:我们如何测试计算机/人工智能体中的意识存在?
前任玛奇纳和她的可能会给我们一点线索…**
“我们只能看到前面不远的地方,但我们可以看到那里有许多需要做的事情。”
艾伦·图灵
在 1950 年,人工智能创始人艾伦·图灵提出了一个测试来确定一台计算机是否是 智能 。基本的想法是,你有一台计算机和一个人在终端窗口与另一个人(法官)交流,法官必须通过问任何她想问的问题来确定哪个是人,哪个是计算机。
来自斯坦福哲学百科【3】:
计算机程序必须能够经受住某人的询问,该人知道对话中的另外两个参与者之一是机器。
来自图灵本人3:
“……让他们把模仿游戏玩得如此之好,以至于一个普通的审讯者在五分钟的审讯后做出正确辨认的概率不超过 70%……”
这个想法很简单:如果一个人可以根据对话来区分人脑和电脑,那么这个非人类就是不聪明的。非常重要的一点(但也是最容易被忽视的一点)是 图灵测试只测试智力 ,这是图灵的意图和提议。它确实 而不是 测试意识、知觉或任何类似的东西——仅仅是智力。然而,我提出的理由是,它可以被推断为思想的存在,因为智力通常是思想存在的代理。

Alexa 或 Google Home 能通过图灵测试吗?来源:Fitore F via Unsplash 。
然而,图灵测试也有其批评之处:
- 图灵测试是沙文主义的:它只认可能够与我们保持对话的事物的智能。[2]
- 图灵测试的要求还不够高:我们已经有轶事证据表明相当不聪明的程序也能通过。3
- 图灵测试本身并不决定某样东西是否有智能——它只是决定了它是否能与人类交流 T21。(沟通=智能??) 3
- 存在(理论上)可以通过强力方法通过测试的程序,从而使失败无效。问题是,运行这个程序需要比宇宙中可用的资源更多的资源——这是个问题吗?理论与实践:智力需要时间维度吗?
我把艾伦·图灵列为我最想与之共进晚餐的五个头脑之一,我认为当他去世时,世界和艾本身都严重倒退了。我个人也认为他走在了时代的前面,但是图灵测试需要更新或扩展以反映现代 NLP 系统能力的变化,比如 GPT-3;但是就像头脑的标准模型一样,这是解决一个棘手问题的绝佳起点。
第四部分:人工大脑能够真正理解语义吗?
“懂”是什么意思?
"哲学敏感性的众多标志之一是对大多数理智的人认为不值得烦恼的问题的痴迷。"
——约翰·塞尔《新世纪的哲学》
S 陷阱,这一个是激烈的,并且可能是自笛卡尔时代以来心灵哲学有史以来最有力和最重要的论证。
然而,在这个令人费解的东西之前,还有一个小插曲,这次是关于语法和语义之间的区别(你们大多数人可能都知道这一点,但这很重要,所以需要复习一下)。语法与表达的属性有关,如词语选择、语言和实际的符号。相反,语义学关注的是表达式之间的关系(意为)【2】。显然是两码事,但是你能从操纵符号中得到意义吗——语义能从语法中产生吗?这就是哲学家约翰·塞尔在他著名的“中国房间论证”中试图解决的问题,他是这样说的:
想象一下,一个母语为英语、不懂中文的人被锁在一个堆满中文符号盒子的房间里。他还有一本书,详细介绍了操纵中文符号产生其他中文符号的每一条规则。一个在房间外面的中国人给这个人一些中国符号(输入),另一个也在房间外面的中国人在另一边等待回应(输出)。通过遵循账本上的规则,这个人能够 100%正确地输出中文短语。该程序使该男子能够通过理解中文的图灵测试,但他自己并不理解中文。
因此,主要的含义是,既然房间里的人不能通过执行正确的理解程序(账本)来理解中文,那么一台机器永远也不会理解它正在操作的位[2]。这是一个微妙的,但经常被错误引用的结论,所以下面是 CRA 没有展示的:
- 不表明机器不能思考。—大脑是一台机器(从广义上说,是一台生物机器),大脑可以思考。
- 不表明电脑不会思考。如果你把计算机定义为任何能够执行计算的东西,那么所有的人都是计算机,他们会思考。这确实表明,图灵定义为形式符号操作的计算并不是思维的组成部分[2][5]。
- 并没有表明只有大脑才能思考,只是说如果我们想让机器思考,我们需要模仿大脑的随意属性,以达到目的:也就是说,改变语法是行不通的。[2].
CRA 是一个 doozy,但希望如果你有点迷路,这 TL;博士版将帮助你理解结论的重要性:
- 实现的程序是纯形式/语法的。想象一下 1 和 0 移动得非常快。
- 头脑有精神和语义的内容。思想有意义。
- 句法本身对于语义来说是不够的,因为它不能保证语义理解将来自句法操作。[2]
- 结论:实现的程序无法让头脑有意义。

谷歌翻译懂中文吗?资料来源:timothée GID enne viaun splash。
更广泛的,也可能是塞尔所说的最重要的一点是,s 强 AI 是错误的【4】【5】。我们没有讨论强人工智能和弱人工智能之间的差异(许多人经常使用这种差异来区分狭义人工智能和一般人工智能,但它们 与 不同, 不能与 互换使用),但是,简单地说,强人工智能就是这样的人工智能,其中的或语义、直接来自于语法重排——没有其他需要:如果你以正确的方式重排符号,那么你将获得意义。塞尔与 CRA 对此进行了反驳,并得出结论,强人工智能并非如此[5]。
明确为 AI/AGI/ML 研究人员:基于此, 你的 AI 永远不会理解它所操纵的比特(如果它是基于传统的图灵机计算模型)。 除了对它所操作的 1 和 0 的表层“理解”之外,它永远不会有更深的东西。 比如“狗”对一个人工智能来说,就没有对我们一样的意义;对于人工智能来说,“狗”只是以某种方式洗牌。
这是一个非常致命的论点,它让许多人为了反驳它而一头撞在墙上。有几个著名的反驳,如果你感兴趣,它们主要是系统回复和机器人回复,这两个都是塞尔提供的反驳。据我所知,现在还没有一个反驳论点,人们可以同意有效地反驳 CRA 本身。
这是不是意味着我们对 AGI 的探索结束了?不一定——我们将很快研究潜在的变通办法,所以请继续关注;所有的希望都没有失去!下周我们将看一个情感和感觉的模型,并试图解决人工智能是否能感受事物或体验情感的问题。
TL;速度三角形定位法(dead reckoning)
帽子很多。一个头脑可以用头脑的标准模型来近似。二元论和唯物主义都提供了关于心灵从何而来的观点。图灵测试是衡量一台机器是否有智能的一种方式,也有其缺陷。最后,中文房间论点指出,图灵机计算机永远不会实现意义(语义)。

CPU 如何才能让理解?来源:Brian Kostiuk viaUnsplash。
其他需要考虑的问题:
- 故地重游:你认为心是由脑而起,还是有其他东西缺失?
- 如果一台计算机通过了图灵测试,是否意味着它是智能的?
- 对于那些想成为 AGI 的人来说,《中国人的房间》是一个“终极目标”吗?
关于作者
Mike Ferguson 是麻省理工学院 DiCarlo 实验室的计算研究开发人员。他将致力于大脑评分,这是一种测量人工神经网络有多像大脑的工具。他将于 2021 年春天从弗吉尼亚大学毕业,获得计算机科学和应用数学学士学位,以及认知科学和哲学学士学位。他是《一周挑战》一书的参与者,在两年内阅读了超过 138 本关于人工智能、哲学以及对人类意味着什么的书籍。他和他的伯恩山犬“博伊·温斯顿”以及收养的边境牧羊犬“影子”住在弗吉尼亚州的夏洛茨维尔。
参考资料:
- 莱尔德,约翰 e,基督教 Lebiere,和保罗 s 罗森布鲁姆。《心智的标准模型:走向人工智能、认知科学、神经科学和机器人学的通用计算框架》Ai 杂志 38.4(2017):13–26。
- 罗伯特·威尔逊和弗兰克·凯尔。麻省理工学院认知科学百科全书。麻省理工学院出版社,1999 年。
- *【https://plato.stanford.edu/entries/turing-test/ *
- http://www . faculty . umb . edu/Gary _ zabel/Courses/Bodies,%20Souls,% 20 and % 20 robots/Texts/UPHS _ 中国 _ 房间. html
- https://plato.stanford.edu/entries/chinese-room/
基于集成模型的网络钓鱼分类。
行业笔记
从探索到部署
在这篇文章中,我们将讨论我们的 ML 团队的方法和工作流程,并通过一个案例研究来大规模部署一个真实的机器学习模型。Ironscales 是一家网络安全初创公司,旨在保护邮箱免受钓鱼攻击。我们的产品使用机器学习实时检测网络钓鱼攻击,并可以自动从最终用户的邮箱中删除电子邮件。
为此,我们构建了一个机器学习分类器,可以计算电子邮件的钓鱼概率。模型输入包括特定电子邮件的特征和属性,期望的输出是“网络钓鱼”或“非网络钓鱼”。端到端开发不像训练数据和保存到二进制文件那么简单。数据收集、验证和部署经常超出 ML 教程和文章的范围。通过撰写端到端的过程,我们希望本文可以提供独特的见解,这些见解可以应用于机器学习和威胁检测领域的其他领域。
ML 发展有几个不同的阶段:
- 数据探索
- 培养
- 确认
- 服务于大规模生产
- 监视
开发是一个迭代的过程,模型通常要经历几轮训练、验证、监控和再培训。
数据探索
数据探索是端到端机器学习工作流的第一步。通常,我们会分发电子邮件,在 Pandas 中打开,为争论和可视化编写脚本,然后保存以供进一步探索或培训。
选择数据集
在 Ironscales,我们处理数十亿封电子邮件。处理这种规模的数据是一项艰巨的任务,并且面临着多项挑战。首先,像 Pandas 或 Excel 这样的工具不可能处理、分类和加载如此多的数据。可以使用分布式技术,如 Dask 或 Spark,但这需要更多的复杂性,并且在许多情况下,不适合这种类型的探索性分析。此外,在电子邮件的正常分发中,存在对网络钓鱼的极端偏见。也就是说,绝大多数邮件都不是钓鱼邮件,随机查看一堆邮件更费时间。
在这种情况下,我们可以通过查看至少包含一个异常的电子邮件来减少数据集的大小。异常是意外的或偏离预期值的电子邮件属性。尽管我们不会收集和跟踪所有异常情况,并且一些网络钓鱼电子邮件可能仍未被检测到并从我们的数据集中排除,但将我们的搜索范围缩小到异常电子邮件将减少数据集的不平衡和大小,使其更易于管理。异常本身不足以确定电子邮件是否是网络钓鱼,但我们现在有了进一步探索的起点。
数据分析
由于我们已经有了一个可以轻松访问异常电子邮件的存储系统,这项任务的第一步是从存储中提取有趣的电子邮件分布,并在 Pandas 中进行探索。假设我们有一个表格,其中有多行电子邮件和多列属性。
片段 1。加载电子邮件和查看数据帧
表 1。显示器输出(df)
从这里,我们可以开始查看不同的子样本,并尝试理解数据中的模式。在对此数据进行分析时,我们发现特定发件人正在发送大量网络钓鱼电子邮件。同样明显的是,这些发送者中有许多具有共同的属性。为了进行调查,我们希望对表进行透视,并根据发件人值聚集属性。
片段 2。枢纽数据框架
通过旋转表,我们现在有了 sender 作为主索引和列,这些列聚合了样本中包含的该发件人的所有电子邮件的相应属性。
表二。旋转数据框架
此时,可以在 3D 特征空间中显示这一群网络钓鱼发送者。
片段 3。图表 1 的代码

图表 1。3D 特征空间。
在此图中,蓝点代表合法发件人,黄色和绿色代表网络钓鱼发件人。图表 1 清楚地显示,中间是一个集中的网络钓鱼发件人集群,周围是蓝色的合法发件人。这不是一个很容易用规则或基于启发式的业务逻辑来确定的精确关系,但它是用机器学习进行训练的一个很好的候选对象。
在这种电子邮件分布中,我们可以观察到 3 种类型的电子邮件发件人:
- 通知
- 垃圾邮件
- 网络钓鱼

图一。数据集中的电子邮件类型。图片由作者提供。
该模型的目标是专门识别网络钓鱼发件人,并防止他们的电子邮件到达最终用户的收件箱。通知和垃圾邮件都应该被忽略。
片段 4。保存透视表
现在我们将数据保存到 CSV,并打开一个新的笔记本进行训练。
分类任务
我们提取了各种特征,并试图适应数据。由于数据本质上是表格形式的,随机森林是一个很好的预测候选模型。
片段 5。培训模式
结果看起来相当不错。

图表 2。精确回忆曲线
表二。分类报告
注意,对于这个模型,我们关心的是精确度而不是回忆。换句话说,假阴性(将网络钓鱼分类为合法)对于该模型是可接受的,但是假阳性(将合法电子邮件分类为网络钓鱼)是不可接受的。这是因为该模型的范围是检测特定类型的网络钓鱼。还有其他模型检测其他类型的网络钓鱼,因此只要我们不意外地从客户的邮箱中删除一封电子邮件,我们就可以通过引入检测其他网络钓鱼电子邮件的其他分类器来改进系统。可以通过添加更多的模型来提高召回率,但是精度只能达到所有模型的加权平均精度。
改进功能
通过实验,可以添加新的功能并改进模型的分类指标。RandomForestClassifier 有一个根据重要性对要素进行排序的 API。
片段 6。打印特征重要性表
表 3。特征重要性
片段 7。地块特征重要性

图表 3。功能重要性。
交叉验证
既然模型已经被训练和优化,下一步就是交叉验证。为此,我们可以使用校准的 ClassifierCV。这个分类器的优点是可以校准 predict_proba 方法,该方法会使随机森林模型的发生偏斜。
代码片段 8。校准模型
静默部署
好了,现在我们有了一个模型,并准备将其部署到生产中。为了了解它在实时数据上的表现,我们需要将它集成到我们的电子邮件检查流程中,并进行静默预测。让我们在高层次上讨论一下检查流程。
清单 1。检验流程。(Medium 不支持嵌套列表。)

图 3。检验流程。图片由作者提供。
为了服务于模型,我们需要聚集每个发送者的异常。幸运的是,我们有一个异常表,在创建了一个关于发送者和日期的新索引后,它可以被快速读取。
我们的初始部署有 4 个步骤:
- 查询发送方为的行的异常表
- 聚合和展平从查询结果中提取的特征,并格式化以进行预测
[1, 3, .43 , .87,] - 运行预测
- 如果预测网络钓鱼概率高于阈值,则在静默网络钓鱼表中写入新条目
片段 9。推理功能
值得注意的是,模型预测聚合的时间窗口应该与训练数据的时间相匹配。例如,如果从异常表中提取 30 天的数据用于训练,则 30 天应用于预测。另一方面,例如,如果使用 90 天的窗口大小,模型输入值可能会比训练集大 3 倍,这可能会在推断时导致严重的问题。
最后,在将模型部署到生产系统之前,最佳实践是编写单元测试并验证代码质量。即使是一个静默的模型也有可能在检查流程中导致异常,因此通常更倾向于编写一些基本的测试。首先,最简单的方法是将预先生成的值传递给预测函数,并检查在逻辑和类型处理中是否有容易注意到的异常,或者值错误是否正确。第二,至少一个集成测试,其中数据被插入到测试数据库中,然后为特定的电子邮件调用整个检查流程。第三,测试和基准测试模型的分类性能是很重要的。如果分类器需要大量资源或者从磁盘读取需要时间,它可能需要不同的体系结构。
无声预测的分析
部署模型后,我们等待了几天,并查看了静默预测表,以确定模型的准确性。
查看预测后,我们注意到该模型将大量垃圾邮件归类为网络钓鱼。这是因为垃圾邮件和网络钓鱼的发件人活动通常是相同的;即,同一发件人同时发送垃圾邮件和网络钓鱼电子邮件,唯一的区别是电子邮件的内容。经过一些计算后,发现精确度只有 80%左右。不幸的是,这个模型还不够成熟,无法部署到主动模式中。

图 4。垃圾邮件和网络钓鱼。图片由作者提供。
用于集成推理的训练 NLP 模型
为了进一步完善这一预测,我们需要添加另一个模型来训练使用电子邮件正文来区分垃圾邮件和钓鱼电子邮件,即使它们来自同一发件人。我们将为第二个分类任务创建一个简单的语言模型。对于训练数据集,我们使用静默预测电子邮件。其思想是,在随机森林模型对电子邮件进行预测并确定发件人是网络钓鱼之后,将会有对电子邮件文本的第二语言模型预测来识别垃圾语言并取消第一预测。
代码片段 10。NLP 模型
现在我们有了一个健壮的集合模型。
片段 11。集成推理
如果电子邮件是垃圾邮件,该函数返回的网络钓鱼概率为零。此外,保存原始预测对于将来的分析、重新训练和监控也很有用。由于发件人的属性会随着随后的每封电子邮件而改变,因此也会返回发件人的统计信息。
主动部署
经过几天的静默评估,新发送者异常集合模型的测量精度得分在 99%以上。下一个阶段涉及添加到在主动检测流中运行的模型列表中。如果这些模型中的任何一个单独预测电子邮件是高于特定阈值的网络钓鱼,则该电子邮件被升级。由于模型已经在静默模式下运行了几个星期,所以只需要对代码进行少量的修改。
监控
在我们将一个模型部署到产品中之后,随着时间的推移监控它的有效性是很重要的。机器学习模型不会永远存在,它们需要维护和再培训。最终,它们被弃用并被删除。随着时间的推移,输入数据可能会发生变化。也许垃圾邮件制造者会使用新的文本模板。网络钓鱼者可以使用新的发送模式来躲避检测。对检查流程其他部分的更改可能会改变保存的异常电子邮件的分布,并且输入数据会有漂移。
虽然我们目前缺乏强有力的监测系统,但这是一个值得关注的主要领域。一项任务是记录一段时间内的预测和概率,并使用 Datadog 等监控工具进行显示,分析师可以轻松地实时查看这些数据。另一个任务是监控输入值的漂移。
后续步骤

图 5。完全分离的电子邮件类型。图片由作者提供。
最后,钓鱼邮件在生产系统中以高精度升级!我们的模型已经部署,客户也很满意。该模型的开发总共花费了几个月的时间,从提取异常电子邮件进行探索,到从最终用户邮箱中删除第一封电子邮件。这段时间学到了很多,还有很多可以改进的地方。
这个模型使用了一种新的分类方法。虽然以前的大多数方法都试图根据内容和元数据对电子邮件进行分类,但这是 Ironscales 的第一个模型,仅根据发送模式就可以将电子邮件分类为网络钓鱼。如果某个发件人的一封电子邮件被归类为网络钓鱼,那么该发件人最近发送的所有电子邮件不都是可疑的吗?发件人分类通过收集新类别的关系和活动数据,为未来的探索开辟了一个令人兴奋的新领域。
另一个有趣的发现是垃圾邮件对网络钓鱼分类的影响。我们通过构建模型的观察表明,对什么是不是的网络钓鱼进行分类对于识别什么是是是有用的。理解和分类垃圾邮件是提高网络钓鱼检测准确性的另一个方面。
结论
在本文中,我们讨论了与机器学习模型的开发、部署和监控相关的几个主题:
1.熊猫的数据探索
2.用 RandomForestClassifier 对表格数据训练模型
3.按比例部署模型
4.用 SGDClassifier 训练模型文本数据
5.监控生产代码
首先,为了进行数据探索,我们选择了一个数据集,检查了一个熊猫数据框架,并创建了一些图表和可视化。然后,我们使用 RandomForestClassifier 在表格数据上训练模型。我们在生产系统中验证、校准并部署了我们的模型。在意识到分类分数需要一些改进之后,我们添加了一个基于文本的模型并进行重新部署。最后,我们的模型正在积极地进行预测,我们正在监控回归的性能。
祝贺你——你坚持到了最后!如果你对建立机器学习模型和分布式系统感兴趣,以帮助保护公司免受高级电子邮件攻击,Ironscales 正在招聘。
美照菲尼莎:在家里构建人工智能
实践教程
一台二手游戏电脑、一门大型开放在线课程和 23 行代码是如何帮我省下了数周的苦差事

我家幻灯片收藏的一小部分。图片作者。
照片,到处都是照片…
作为一个 70 年代的孩子,我的大部分早期记忆都停留在老式的摄影媒体上,比如幻灯片和底片。虽然这些幻灯片可能比我活得长,但它们很难索引和分享,更难以任何有意义的方式备份。
2010 年代初,我决定着手一个扫描项目,将我家的摄影史数字化,既为了将来保存照片,也为了更容易与家人分享。
虽然我在项目中使用的扫描仪会自动应用曝光校正和除尘,但这个过程留给我 16 000 张需要手动旋转的图像。
在用手旋转了几百张图像后,我几乎厌倦了,我决定简单地将扫描存档,等待未来:
- 我有时间手动旋转它们,或者
- 技术已经发展到一个程度,计算机可以为我做这件事。
快进到 2020 年,两种未来同时到来。2020 年末,当第二次澳大利亚电晕病毒封锁降临墨尔本时,我突然有了一些室内空闲时间,并决定参加一个在线人工智能编程课程。程序员实用深度学习是一门免费的大规模开放在线课程,在短短 8 天内教你如何使用一种叫做深度学习的特定类型的机器学习。
想出一个解决方案
深度学习
深度学习通过使用计算机代码模拟神经网络来模拟我们大脑的工作方式。作为一种有监督的机器学习,深度学习要求你首先用一些已经手动标记的样本数据来训练系统,然后你可以要求它识别新数据中的类似特征。
在我的例子中,我想使用一些手动旋转的图像来教会神经网络识别图像是向左、向右还是上下旋转,并将这些信息馈送给开源图像处理实用程序 imagemagick 以正确旋转它们。
旋转与重命名
虽然我最初的设计需要一个完全自动化的程序,可以简单地指向包含一些需要旋转的图像的目录,但事实是我的图像在。jpg 格式意味着我需要一种稍微不同的方法。
不幸的是,修改一个. jpg 图像有点像给一盘老式磁带配音,因为每次你打开-修改-保存文件时,一些图像细节都会丢失。由于我还想在稍后的过程中用不同的 ML 魔术裁剪图像,在这一步中实际旋转它们意味着我将打开-修改-保存每张图像两次。因此,我认为更好的方法是简单地识别每个图像在这一阶段指向的方向,然后以某种方式将该信息嵌入到文件中,而不需要再次打开并保存它。
与修改其内容不同,简单地重命名. jpg 文件对其图像质量没有任何影响。因此,在文件名中嵌入旋转信息提供了一种很好的方式,可以将该信息传送到工作流程的其余部分,而不会降低图像质量。重命名而不是旋转文件还有一个好处,就是将生成的程序从一个单一用途的图像旋转器转换为一个通用的基于内容的文件重命名器,该重命名器还可以被重用以用于其他用途。
用户交互设计
对于模型训练步骤,程序被提供到包含几个子文件夹的文件夹的路径,每个子文件夹包含一组图像,这些图像被选择或处理以共享一些共同的特征。
每个子文件夹中的文件然后被馈送到神经网络训练算法,该算法试图提取图像共有的特征。完成训练阶段后,系统保存一个模型文件,该文件包含经过训练的神经网络以及它已经学习的类别列表。
训练后将神经网络保存到磁盘有两个好处:
- 它允许在计算机关闭的情况下重复运行重命名工具,而不必重复训练,并且
- 它允许在云服务上运行训练步骤,如 Google Colab 如果你在家里无法使用支持 GPU 的机器。

在自动进料器可靠装载之前,一些纸板载玻片需要重新装框。图片作者。
对于文件重命名步骤,向程序提供包含要重命名的图像的文件夹的路径。该程序从磁盘加载模型文件,然后将输入文件夹中的每个图像呈现给神经网络。神经网络计算的结果称为推理,是特定图像属于每个先前训练的类别的概率列表。
在我的图像旋转模型中,给定的图像可能有 83%的概率是垂直的,7%的概率是向左旋转的,6%的概率是向右旋转的,4%的概率是颠倒的。如果任何类别的得分超过 50%,程序会自动重命名文件,在文件名前加上最可能的类别名称。
准备我的数据
为了产生我的训练数据,我最初从扫描图像档案中取出 200 张图像,并用手正确地旋转它们。我试图确保这些至少包括一些风景和一些人的图像,以便模型能够学会如何识别这两者。
然后,我将这个文件夹复制了三次,并对每个文件夹中的同一组图像进行了整体旋转,或者向左旋转,或者向右旋转,或者上下颠倒。
.fastai
└── data
└── familyphotos_small
├── 000
├── 090
├── 180
└── 270
这给我留下了四个文件夹,我以所附图像的旋转角度命名,文件夹“000”保存直立的图像,文件夹“090”保存向右旋转的同一组图像,文件夹“180”保存颠倒的图像,文件夹“270”保存向左旋转的图像。

FastAi 生成了显示类别名称的我的训练数据的可视化。图片作者。
虽然我碰巧对我的文件夹使用了“000”、“090”、“180”和“270”的名称,但这些只是文本字符串。我可以很容易地将文件夹命名为“左”、“右”、“垂直”和“颠倒”,或者 A、B、C 和 d。程序代码只是将文件夹名称作为文本字符串读取,以标记类别。
有趣的是,该程序对图像文件之间的实际差异也不敏感。我可以很容易地制作包含“海滩”、“雪”和“运动”等类别的图像文件夹,神经网络将学会识别这些图像。我的程序所基于的代码库是 FastAi 代码库中宠物品种示例的一部分,它甚至可以学会辨别 40 种不同品种的猫和狗之间的差异!
编写代码
如今,神经网络非常容易整合到你自己编写的程序中。有许多相互竞争的深度学习框架可以为您完成繁重的工作,并且经过优化,可以利用 NVIDIA GPUs 中的矩阵乘法硬件。
两个最受欢迎的深度学习框架,张量流(最初由谷歌开发)和 Pytorch (最初由脸书开发),提供了一组基本的机器学习子程序,用于处理神经网络训练和推理。
为了使这些框架更容易使用,几个附加的开源项目提供了包装基础框架的函数库。其中的一些例子是张量流的 Keras 和 Pytorch 的 FastAi 。
由于我跟随的程序员实用深度学习课程使用了 FastAi 中包装的 Pytorch,所以我在我的文件重命名项目中使用了这个组合。
许多云提供商,如 AWS 、 Google Colab 和 Lambda Labs 提供预装 Pytorch 库的虚拟机。如果你不熟悉 Linux,或者你的计算机中没有基于 NVIDIA 的 GPU,这些虚拟机提供了一种快速且经济高效的解决方案,来在云中训练你的深度学习模型。
就我而言,我用的是一台 Ubuntu 电脑,配有 NVIDIA 1080ti 显卡,这是我花 650 美元从一位升级了电脑的游戏玩家那里买的二手电脑。FastAi 文档解释了如何通过几个简单的步骤安装 Pytorch 和 FastAi 库。
设置培训数据
程序的第一部分设置环境并加载将用于训练模型的图像列表。
FastAi 使用数据块的概念来描述在哪里可以找到组成数据集的文件,如何标记和组织它们,以及如何将它们呈现给学习算法。
对于大多数任务,你不会希望一个经过训练的神经网络对它要识别的对象的旋转过于敏感。想象一下,你想训练一个神经网络来识别圣诞树。如果在训练过程中,你只向它展示竖直方向的圣诞树,就像展示圣诞树摆放在客厅的照片一样,网络只会学习识别那个方向的圣诞树。如果你随后要求网络识别一张侧躺着的树的图像,比如一张在运输过程中绑在车顶上的树的图片,网络很可能无法完全识别这棵树。
确保神经网络学习识别对象而不管它们的方向的方法之一是多次显示训练集中的图像,同时每次随机旋转它们。这样,即使你只有直立的圣诞树的图片,你的网络也会显示一些向左倾斜的树的图片,一些向右倾斜的树的图片,甚至一些完全颠倒的树的图片。
这项技术非常有效,FastAi 库将通过一个名为项目转换的选项,自动将图像旋转添加到您的数据块配置中。然而,对于这个项目来说,自动将轮换添加到训练数据中会适得其反。由于我们特别希望网络能够学习识别方向,而不考虑图片中的对象,因此我们需要在数据块定义的训练过程中关闭自动旋转。
如果仔细观察上面的数据块代码,您会看到行item_tfms=**RandomResizedCrop**(224,min_scale=0.5)。这段代码告诉 datablock 构造函数只通过随机调整大小和裁剪来增加数据。这个选项用来代替更常用的item_tfms=**aug_transforms**(224,min_scale=0.5),它也可以增加图像旋转增强。
如果你使用这个程序来完成除了识别图像旋转以外的任何任务,使用aug_transforms选项几乎总是会产生更好的结果。关于aug_transforms选项的更多信息可以在 FastAi 文档中找到。
训练模型
接下来是训练神经网络的代码。FastAi 库包括一个lr_find功能,可以帮助你在开始实际训练过程之前找到合适的学习速度。
学习率被称为超参数,因为它控制学习算法行为的一个方面,它告诉算法在训练期间增加或减少网络权重时使用什么步长。
如果你的程序采取的步骤太小,你的模型将需要几天才能取得几分钟就能取得的进展。如果你的程序采取的步骤太大,你的模型会反复超调,永远找不到最优解。
将计算出的最佳学习率复制到步骤 9,并要求模型在数据集上运行 25 次。这些重复中的每一次都被称为 epochs,在我的 GPU 上花费了大约 2 分钟,总训练时间大约为 45 分钟。
如果计算机没有兼容的 GPU,可以指示同一个库使用 CPU,代价是等待时间更长。当我试图在我的计算机的 CPU 上运行相同的代码时,每个时期需要大约 40 分钟而不是 2 分钟,对于相同的培训工作来说,总共需要 16 个小时。
对于只需要做一次的事情,额外的半天几乎不是问题,但是能够在 45 分钟而不是 16 小时内运行整个过程无疑会使代码调试更容易。
在 25 个时期之后,该模型获得了 2.7%的错误率,这意味着它在 97.3%的时间里正确地猜测了测试集中图像的方向。
当错误率达到稳定水平时,查看网络仍然误解的图像通常是一个好主意,因为这可以指示在哪里寻找问题,或者网络已经简单地了解了它可以做什么,现在只是在努力处理甚至人类都难以处理的例子。
在 FastAi 中,可以通过调用函数interp.plot_top_losses生成最常被误解的图像的概述。查看输出可以发现,错误仅限于那些即使人类也难以正确判断的图像:

FastAi 生成了测试集中最常误判的图片的可视化。图片作者。
例如,第一张图片显示了一个 3 岁的我站在我爸爸的脚上保持平衡,而他正仰面朝天拍照:即使现在看着它,也不容易猜出照片的旋转方向…
使用模型
模型经过训练并保存到磁盘后,我们可以继续使用它进行推理。本节从重复环境设置开始,因为它假设推理步骤将在不同的日期发生,或者在不同于训练步骤的系统上发生。
从磁盘加载神经网络后,可以通过打印learn_inf.dls.vocab变量快速检查模型训练识别的类别。
在这种情况下,加载的模型报告它被训练来识别称为“000”、“090”、“180”和“270”的类别。因为这与原始图像旋转训练数据的目录名相匹配,所以我知道我有正确的模型,并且我知道它将使用哪些标签作为我的图像文件的前缀。
然后向程序提供包含要分类的文件的目录名,并逐个处理它们。例如,文件 SN_600_26.jpg 被呈现给网络,网络预测有 92%的可能性图像是颠倒的(即,它类似于在名为“180”的训练目录中的图像)。
************************************
Old Filename: /media/streicher/2TB_ExFAT/Streicher_Negatives/SN_600_26.jpg
Predicted Class: 180
Predicted Confidence: 0.9214386343955994
New Filename: /media/streicher/2TB_ExFAT/Streicher_Negatives/180_SN_600_26.jpg
************************************
当预测置信度高于 50%时,程序继续并将文件重命名为 180_SN_600_26.jpg。如果程序在其预测中得分低于 50%的置信度,则它会将文件重命名为 UNK_SN_600_26.jpg,以表示旋转未知。
总之,我的程序的全部功能代码只有 23 行。23 行来组装一个数据集,训练一个神经网络,将它保存到磁盘,并使用它来执行一项需要人类几周才能完成的任务。
结论
像 Keras 和 FastAi 这样的库使深度学习的力量在易于使用的函数中可用,即使是新手程序员也可以用它来解决复杂的任务。
这些库提供的易用性和低实现成本意味着,自动化原本是手动的任务正在迅速向自动化倾斜。
GPU 技术的不断进步意味着,廉价的二手游戏电脑可以很容易地重新用于运行最新人工智能软件的强大数据科学工作站。
免费、自定进度的大规模开放在线课程,如 FastAi 的程序员深度学习和 Coursera 的深度学习提供了自学机器学习基础知识以及如何将其应用于现实世界任务的机会。
这个项目的完整源代码可以在我位于 https://github.com/streicherlouw/DeepRename的 GitHub 仓库中找到

对于那些对扫描硬件感兴趣的人,我用 Reflecta DigitDia 5000 拍摄幻灯片,用 Reflecta RPS 7200 拍摄底片,用 Cannon 8800f 拍摄照片。我选择这些扫描仪是因为它们能够扫描彩色和红外线。红外扫描允许他们检测胶片介质中的灰尘和划痕,并自动润色输出。图片作者。
物理和深度学习,天体为什么是球形的?
探索物理学和人工智能的共同点。

来源:维基百科 |太阳系
你知道为什么地球形状像一个球体吗?
所以你可以踢它,它就会滚走!从你早上醒来的那一刻起,你总是和人们在一起,你的头脑被世俗的想法所困扰。所以在一天中的某个时候,坐几分钟,进入你内心的洞穴,闭上眼睛,像一个球一样把世界踢开。- Sri Sri Ravishankar
抛开这个空灵的答案,我们来看看深度学习有没有这个问题的答案。几乎所有的深度学习和人工智能都是关于解决复杂的优化问题,其中要优化的目标是多个参数的函数,有时这些参数的范围是数百万或数十亿。 GPT 三号拥有 1750 亿个参数!不要担心,物理问题只需要几百个(实际上不是很深!)
你一定想知道物理学和深度学习之间有什么联系,我也是,然后我意识到所有的自然法则都可以重新表述为优化问题的解决方案。我开始了一系列的帖子,在这些帖子中,我将着手解决一个物理问题,并使用 Pytorch 惊人的自动微分和优化能力来解决它。
现在回到我们最初的问题,为什么行星和恒星是球形的?如果我们将它们视为静态系统,忽略它们围绕轴的旋转和它们在时空中的路径,物理定律(最小作用原理)告诉我们,这样一个静态系统会处于最低势能状态。对于像地球这样的天体,势能就是自身重力势能。是重力将地球维系在一起,是其组成材料的刚性阻止了它自身的坍塌。
但是为什么重力一定要把地球塑造成一个球体,而不是立方体或者甜甜圈呢?看起来球形的势能最小。让我们尝试一个实验,我拿一些粒子,随机扔进 2D 空间,然后优化重力势能。我给这些粒子增加了一些刚性,以防止这些粒子相互碰撞。通过在势能上增加一项来增加刚性,该势能与粒子之间重叠的平方成比例(类似于弹簧的势能)。
在这种情况下,损失函数是每对粒子之间的重力势能和由于相互接触的粒子的弹性而产生的势能之和。
下面是一些动画,展示了这些实验的结果,在这些实验中,我选取了 1000 个不同大小和不同初始位置的粒子,并优化了它们的坐标,以使损失达到最低值。总共有 2000 个参数需要优化。所有这些都只是 matplotlib 中的散点图,点在散点图中相互碰撞!!




(图片由作者提供)优化算法以不同的初始位置运行 1)围绕中心的正态分布 2)以正态分布分散在矩形内的点 3)均匀分布的是正方形区域 4)高的垂直柱。
我们应该得到的最终形状是一个圆形,从上面的实验中得到的形状趋向于圆形,但不完全是圆形,因为这需要更精细的粒子和更多的迭代。这些形状目前停留在局部极小值,它们可以通过轻微的扰动接近一个完美的圆。但是仅用几行 python (Pytorch)代码,结果仍然令人惊叹!你可以在这本 colab 笔记本里找到这些台词。我希望你现在知道为什么行星是圆的了!注意下一个帖子。
阅读更多博客点击这里。
选择一种可解释的技术
模型可解释性
一个ML 可解释性技术分类法来理解你的模型。

ML 模型可解释性(有时被称为模型可解释性或 ML 模型透明性)是 AI 质量的基本支柱。在不了解机器学习模型如何以及为什么做出决策,以及这些决策是否合理的情况下,不可能信任机器学习模型。在将 ML 模型部署到野外之前,对其进行仔细研究是绝对必要的,在野外,一个理解不充分的模型不仅无法实现其目标,还会导致负面的商业或社会影响,或者遇到监管问题。可解释性也是其他值得信赖的 ML 支柱的重要支柱,如公平性和稳定性。
然而“可解释性”通常是一个宽泛的、有时令人困惑的概念。最简单地说,机器学习解释是模型功能的一组视图,帮助您理解机器学习模型预测的结果。
我如何挑选最好的可解释技术?
这个简单的描述背后隐藏着相当大的复杂性。提供模型解释的方法有很多:逻辑回归系数、LIME、Shapley 值技术(QII、SHAP)和综合梯度解释。你如何知道你是否得到了一个好的模型解释?你如何比较不同的解释方法来为你的模型选择最好的方法?为了清晰起见,我们引入了解释方法的分类,以理解和描述无数种解释技术是如何结合在一起的。
我们的 ML 可解释性分类法有四个维度:
- 解释范围。解释的范围是什么,我们试图解释什么输出?
- 输入。我们的解释方法使用了什么输入?
- 访问。解释方法有什么模型和数据访问?
- 阶段。我们将我们的解释应用到模型的哪个阶段?
让我们将这些问题逐一分解,并理解它们的含义。

图片作者。解释方法的分类。
解释范围:我们试图解释什么输出?
让我们用一个具体的例子来说明这种细微差别。假设一位数据科学家训练了一个机器学习模型,来预测给定个体面临疾病风险的概率。该模型在其决策中使用各种医疗数据,数据科学家希望向可能使用该算法的医生证明该模型的预测。
全球还是本地?
数据科学家可能会列出驱动所有个人模型预测的前五个重要特征,如一个人的血压和他们是否有该疾病的家族史。这是一个全局解释的例子,因为数据科学家正在通过跨许多数据点的预测的整体驱动因素来解释模型。数据科学家也可以用当地的解释来补充这一点,这将代替解释单个个体/数据点预测的驱动因素——例如,为什么约翰被明确预测有患这种疾病的风险?解释的范围可以从局部到全局。介于两者之间,数据科学家可以解释模型对一部分人口进行预测的驱动因素,例如,只对女性进行预测的驱动因素..
输出类型
我们要解释的不仅仅是有多少个输出,还有它们的类型。让我们考虑一个决定某人是否值得贷款的模型。我们是否在解释一个模型分配给每个数据点的概率分数(一个人获得贷款的概率)?或者当数据科学家在原始概率分数上阈值时创建的分类决策(是否提供贷款)?正在解释的模型输出的微小变化会对解释本身产生巨大的影响。
作为一个简单的例子来说明被解释的输出的微小变化是如何对解释本身产生重大影响的,让我们以美国总统选举为例。每个州可以被视为一个“特征”,它分配一个分数(选举团选票),然后该分数被汇总以确定选举结果。如果你要提出一个问题来解释选举的原始分数,“为什么乔·拜登获得了 306 张选举人票?”像德克萨斯州、加利福尼亚州和纽约州这样的大州将是他精确得分的主要贡献者。相比之下,要解释“乔·拜登为什么赢得总统大选?”相反,我们会转向较小的摇摆州,这些州是决定候选人的关键。
解释输入:我们的解释是从哪些输入中计算出来的?
可解释性方法必须根据模型的某些部分总结它们的结果。通常,这些被分析的组件是组成特征、中间特征或训练数据。
- 输入特征。最常见的是,模型的组成特征正在被解释。一种方法将试图通过解释每个特征如何影响整体模型决策来解释模型的局部或全局属性。在上面的医疗保健示例中,数据科学家可能会指出“血压”和“疾病家族史”是影响模型决策的特定输入特征。
- 中级特征。现在让我们转向一个新的例子:假设一名研究人员在图像数据上训练了一个卷积神经网络,以预测图像中是否出现了一只狗。研究人员可能首先针对特定输入图像的每个像素来解释模型,但这可能很难对数千张图片进行推理。然而,研究人员知道神经网络的特定中间层负责识别图像中指示狗存在的模式,并希望检查这些过滤器的正确性。因此,她代之以生成关于网络中间层的解释。这些类型的内部解释对于具有顺序固有结构的模型来说是常见的,例如神经网络的中间层或决策树中的分支结构。
- 训练数据特征。但是关于模型内部计算的解释仍然使用模型的中间“特征”。如果我们的解释方法试图通过追溯到训练数据本身来证明模型行为的合理性呢?其他解释技术试图通过量化对学习模型行为的某个方面贡献最大的数据点来做到这一点。
解释访问:我们的解释对模型了解多少信息?
解释方法对模型本身的访问量是我们分类法的另一个定义维度。“有限访问”并不一定比“完全访问”更好,反之亦然——它们都有特定的好处。
- 受限访问。许多解释技术经常假设对模型的输入和输出的访问是有限的,而不知道模型架构本身。与模型无关的技术,如莱姆、 SHAP 和 QII 检查输入对模型输出的影响,而不检查模型内部。这对于在同一数据集上训练的多个模型之间比较解释非常有用。当数据科学家将逻辑回归模型转换为更复杂的模型(如一组树)时,模型行为会如何变化?
- 中间通路。针对特定模型类的解释技术已经变得越来越流行——例如,树模型的 Shapley 值的有效近似,利用模型的结构来提高性能。
- 完全访问。在极端情况下,特定于模型的技术可能需要对模型对象的完全访问。尽管它们不能跨多个模型类进行转换,但是它们可以利用特定模型类型的结构来提供深入的见解,或者提供相对于模型不可知方法的性能改进。特定于模型的技术的流行示例包括基于梯度的策略,如为神经网络设计的集成梯度、平滑梯度和梯度凸轮。
解释阶段:什么时候开始解释?
我们的解释分类法的最后一部分与解释在模型开发的哪个阶段被应用有关——在训练期间,还是之后?解释方法可以自然地遵循模型的结构(例如在决策树的情况下),或者追溯应用以理解更不透明的模型(例如神经网络)。到目前为止,文献中一个常见的争论是,是用自我或内在可解释的模型还是算法可解释的模型来构建。
培训期间可以解释
典型的自我解释(也称为“白盒”)模型是简单的线性或逻辑回归;有了这些,可以从线性模型的系数中直观地了解要素的重要性,并且模型得出结论的确切方式也很清楚,因为线性模型将其预测创建为输入要素的加权和。
决策树、决策集、基于规则的分类器和记分卡也被认为是白盒模型,因为它们迫使模型通过明确定义的规则来创建预测。最近,也有一些研究将可解释性约束的味道直接添加到模型训练本身,例如基于贝叶斯规则的推理模型、通过梯度惩罚稀疏化神经网络,或者甚至通过将人类反馈添加到优化程序。
虽然内在可解释的模型确实简化了模型的“解释”,但现实是,将自己局限于线性或基于规则的模型意味着数据科学家将严重限制他们可以通过机器学习实现的模型或用例的类型。
培训后可解释
在训练完成后应用于模型的可解释技术可以解释上面提到的“自解释”模型,同时也解释“算法上可解释”的模型。
算法上可解释的模型是那些在训练中不受限制以符合一组规则的模型。这些规则是在训练过程中推导出来的,对于数据科学家或外部观察者来说不一定是显而易见的。由于这些原因,它们通常被称为“黑箱”模型。为了解释算法上可解释的模型,可解释性技术必须在模型完成训练后应用。出于这个原因,这些技术通常被称为“事后可解释性”,它们可以用于任何类型的机器学习模型,包括白盒和黑盒。
缩小:使用这种分类法的常见解释技术
我们已经剖析了解释分类法的每一个维度,但是让我们以一些流行的可解释性技术的例子为基础,总结在下表中。

图片作者。这种分类下流行的可解释技术。
解释逻辑回归模型的系数
逻辑回归模型是捕捉输入和输出之间的线性关系的简单模型。因此,很容易理解和解释模型的决策过程——这都是通过逻辑回归的系数获得的。这些系数各自的大小可以给出一个特征在多大程度上驱动模型预测的内在感觉。
因此,解释逻辑回归模型的系数是一种自我解释的技术,因为模型本身固有地被限制为模拟输入和输出之间的简单关系。解释的范围是全局的-系数大小不能用来直观地了解一个特定的数据点,而是预测本身的聚合驱动因素。这也是一种特定于模型的技术,因为它需要访问模型的学习权重。
石灰
里贝罗等人。艾尔的“我为什么要相信你?”论文介绍了局部可解释的模型不可知解释,或 LIME,这是一种众所周知的技术。在高层次上,LIME 致力于通过干扰数据点的特征来解释单个输入的模型决策,并查看这如何改变模型预测。
正如您从名称中所看到的,LIME 是一种局部可解释技术,只适合孤立地解释单个数据点。它也是模型不可知的,因为它假设只访问模型输入和输出。它根本不约束模型训练,而是在模型训练后解释模型的决策,使其成为一种算法上可解释的技术。最后,LIME 通常用于计算关于模型输入要素的解释。
基于 Shapley 值的解释
基于 Shapley 值的解释技术如 Lundberg 等人。艾尔的 SHAP 和的达塔等人。艾尔的 QII 论文越来越受欢迎。我们将在随后的文章中更深入地讨论这些技术,但两者都使用 Shapley 值的概念(一个来自联盟博弈论的术语)来精确地确定模型的输出得分或每个输入的分类决策。
基于 Shapley 值的技术,如 LIME,是算法上可解释的和模型不可知的方法。它们假设不能访问模型内部,并且可以应用于任何模型类型。核心算法本身可以应用于任何输入,但最常用于解释模型的组成特征。然而,与 LIME 不同,基于 Shapley 值的解释可用于确定各种模型输出(概率、回归和分类结果等)的局部和全局模型推理。
让我们智能地解释我们的机器学习模型
正如我们所见,有各种各样的方法来解释机器学习模型。由于不同的技术适用于不同的模型类型或情况,这种分类法使用解释范围、输入、访问和阶段等维度,有助于数据科学家确定最适合他们的模型的技术。
这种分类法有助于描述和比较每种独特的 ML 场景的解释技术的适用性。当数据科学家为他们的模型选择最合适的技术时,他们将自己放在了准确描述模型结果的最佳位置,并增加了利益相关者和用户对模型的信任,从而最有可能获得现实世界使用和广泛、长期使用的批准。当选择了一个不合适的方法时,数据科学家可能会处于相反的位置——争先恐后地解释模型为什么会这样,解释最终变得不可靠,导致信心螺旋下降。我们希望这有助于做出关于如何评估模型可信度的明智决策。
PicoAPI:微服务的 FastAPI?
FastAPI 是一个令人惊叹的库,但是当我们在微服务架构中使用它时,我们可以改善生活

图片由李泽同在 unsplash 上拍摄
微服务范式本质上包括一组小的离散的迷你应用程序,它们作为一个整体的更大的应用程序一起工作。这种架构使较小的团队能够支持应用程序的较小部分,并清楚地定义了应用程序不同部分之间的契约。微服务在这种设置中最常见的通信方式是通过 HTTP/RESTful API。将应用程序构建为微服务时,通常会使用像 Consul 这样的服务发现和配置应用程序。
我很少遇到一个图书馆会想,“哇,这真漂亮”。最后一次是 python 的谜一样的请求库。FastAPI 的类型和其他现代 python 特性使它成为开发人员的绝对梦想,并使从概念化到生产模型的周转时间变得更快。它不仅支持快速开发,而且使用请求和响应方案意味着您的 API 从长远来看可以快速、轻松地调试和维护。

Patrick Coffey 的图片(使用 draw.io)
我经常选择一种更简单的方法,而不是全力以赴地制作《领事》的副本。这包括配置我的一个微服务作为主服务,其他微服务在启动时向主服务注册。这意味着我的堆栈只需要知道主微服务的地址,从微服务都可以向主微服务发送一个 PUT 请求,注册它们自己,一组定义它们提供的东西的标签,以及一个健康检查 URL,主微服务可以按计划获取该 URL 以测试该微服务是否仍然可达。
以上是我用来表示微服务向主服务器注册的模式。这里需要注意的是我用来描述微服务的标签字段。一个使用我们大多数数据科学家都熟悉的东西的例子;我们有一个网络爬虫模块。它可能有标签:网络爬虫,examplehost.com,肮脏。这将向主服务器指示该微服务提供 web 爬行,它被配置为从“examplehost.com”中抓取数据,并且该微服务的输出是脏的(需要进一步清理 NLP 任务)。这些标签显然需要提前定义,但是我发现这个松散的字符串列表到目前为止非常灵活。
FastAPI(严格来说是 starlette)提供了一些内部事件的挂钩。其中一些是 启动 和 关闭 事件。这些事件允许我们添加应用程序逻辑,这将导致 API 将其微服务定义放入主微服务,从而有效地注册它。一旦接收到这些 PUT 请求中的一个,主节点将把服务定义放入其服务目录中,并且如果定义了 once,则设置所描述的健康检查。
PicoAPI 可以使用 python 中的一个简单线程来处理健康检查信息。HTTP 响应代码范围在 200–299 之间的任何响应都将导致健康状态。所有其他状态代码将导致不健康状态。微服务返回 300 代码可能有一些合理的原因,这个代码可能需要更改。但是,我在使用这个架构的过程中,还没有遇到过这些情况。只要主微服务保持对实例化对象的引用,它就可以简单地检查health check . health变量,以查看上次检查时该健康服务是否正在运行。这里唯一需要实现的另一部分逻辑是每个从服务器上的健康检查端点,当微服务正确运行时,它会返回 200 代码。

Patrick Coffey 的图片(使用 draw.io)
主控可能需要哪些额外的端点?因为主节点已经承担了处理服务注册的责任,所以它需要一个端点来允许其他从节点检查哪些服务已经注册。在这方面,它充当注册服务的地址簿。

Patrick Coffey 的图片(使用 draw.io)
我还选择不仅返回注册的服务,还返回它们的 OpenAPI 服务定义。这允许从模块发现关于其他注册从模块的额外上下文。虽然我个人并不需要在我的微服务之间进行这种程度的自省,但是实现起来很容易,所以我把它包括进来了。
这篇文章中的代码已经被打包成一个 python 库,并发布在 PyPI 上,供任何想使用它的人使用。该库仍处于起步阶段,但我正在一些个人项目中成功地使用它。请在 GitHub repo 上提交任何问题、建议和公关,以便我可以妥善处理它们。
您可以在 GitHub 上找到它:
https://github.com/schlerp/picoapi
在 PyPI 上:
https://pypi.org/project/picoapi/
图像完美
与你毛茸茸的朋友一起捕捉最美好的瞬间!

给动物拍照并不总是容易的……(照片来自 Reddit 猫和狗)
曾经为拍摄动物照片而挣扎过吗?

作者 GIF
或者你有没有发现自己给宠物拍了一堆照片…后来翻遍每张照片,手动选择不好的删除?
嗯……这款名为Picture pur fect的应用是为了让你轻松拍摄猫咪照片而开发的!只需上传一段你的猫的短视频(特别是当它们在玩耍或走动时),算法就会自动为你选择最佳帧。
这是该应用的一个简短演示:
如果你对它的工作原理感兴趣,请继续阅读!或者你可以跳到最后看完整的演示。
我将讨论四个部分:

数据
用于构建算法的数据由 120 个猫咪视频组成,这些视频要么来自互联网(多个 Youtube 猫咪频道),要么是我自己拍摄的。每个视频的时长为 10-30 秒。这些导致超过 2000 个帧,每个帧被标记为好的或坏的。

数据收集和预处理。(图片由作者提供)
正如你在上面的例子中看到的,一个好的帧将包含一只猫,它的脸清晰地显示出来。如果猫的脸被遮挡或模糊,该帧将被标记为坏的。但是你可以想象,除了这些基本标准之外,还有一定程度的主观性,这取决于谁在给这些框架贴标签(在这种情况下是我)。最终的问题是,我们如何教会机器算法学习我们如何思考?为此,我们需要特征来定量地描述每一帧。(如果我们能够收集足够多的标记帧,我们就可以使用深度学习方法,而不用担心特征。不幸的是,对于这个项目,我没有足够的时间或合适的来源来收集大量的数据。
特征工程
特征工程当然是这个项目的主要焦点,因为它在模型训练中起着重要的作用。有三类技术:
- 猫脸检测
- 模糊检测
- 猫面部特征检测
猫脸检测

用 Haar 级联分类器进行猫脸检测(猫脸图像经Creative Fabrica授权给作者)
我使用 Haar cascade classifier(这篇博文简要解释了 cascade classifier 如何工作)作为快速过滤器来筛选给定视频中的所有帧,并只保留那些带有 cats 的帧,用于剩余的(更复杂和耗时的)特征工程步骤。
使用这个检测器,您可以获得猫脸中心的坐标以及猫脸周围边界框的高度和宽度。
从包围盒的位置和大小可以进一步发现猫是否是帧的主体(人脸中心到帧中心的距离和人脸占整个帧的大小比例):

推断猫是否是画面的主体:人脸中心到画面中心的距离,猫脸占整个画面的大小比例。
模糊检测
我们还可以使用一些基本的图像处理技术对每一帧进行模糊检测。有许多不同的方法,但一般来说,它们都与图像中的边缘有多清晰有关,即边缘检测。我选择拉普拉斯和 Canny 滤波器。
拉普拉斯方法计算图像的二阶导数,它测量一阶导数变化的速率。然后,我们可以看到相邻像素值的变化是来自不连续的边缘还是来自连续的级数。
Canny 边缘检测稍微复杂一些。它有多个阶段,包括高斯滤波器降噪。得到的图像是二进制的(大部分是黑色的,但是检测到的边缘用白线描绘)。
下面的例子显示了一个清晰的猫脸帧和另一个模糊的猫脸帧。您可以看到应用拉普拉斯算子和 Canny 算子后帧的外观:

原始帧和应用拉普拉斯算子后的 Canny 滤波器。(来自 YouTube 频道的猫君的厨房)
然后,我们可以计算经过处理的帧的方差,并确定原始帧的清晰度或模糊度。可以对整个帧进行计算,也可以只对猫脸进行计算。猫脸锐度占整个画面锐度的比例也应该算一个特征。
猫咪五官检测

使用定制训练的 YOLOv3 (darknet)进行面部特征检测
前两类与框架的宏观层次更相关。微观层面,我们要识别猫的耳朵、眼睛、鼻子等面部特征。这可以通过基于神经网络架构(darknet 的 YOLOv3)的定制训练对象检测器来完成。与面部检测的情况类似,将返回每个特征周围的边界框的中心坐标和高度、宽度。这其实给了我们很多信息!
这是一个猫从完全清醒到打瞌睡的面部特征检测的例子:

猫面部特征检测。(来自 Youtube 频道的猫 KiSH-Log )
如果只是看眼睛,很明显从猫眼的高宽比可以推断出眼睛的形状(猫的眼睛是不是睁着的,猫是不是困了)。

从高度和宽度的比例推断猫眼的形状。(图片由作者提供)
这是另一个猫洗澡的例子:

猫在洗澡。(猫来自 Youtube 频道基底龙
从猫的两只眼睛和鼻子的相对位置,我们可以推算出猫的面部角度:猫是直视镜头,还是侧视。

使用眼睛、鼻子的位置来计算猫脸相对于摄像机的角度。(图片由作者提供)
模型
在所有准备工作(数据收集、标记、特征工程)完成后,我们就可以开始训练模型了!在测试的几个不同分类器中,Random forest 在 ROC AUC 方面的性能最好(83.3%)。进一步研究特性的重要性,不难发现顶级特性与清晰度有关。

模型训练:给定数据信息,训练随机森林分类器。重要的特征显示在右边。(图片由作者提供)
Web 应用程序
您已经在开始部分看到了 web 应用程序演示!以下是 Picture Purrfect 选择的几帧:

由 Picture Purrfect 选择的雪纺(平纹)和切达(橙色)框架。(作者照片)
希望你喜欢!
完整展示
了解有关项目的更多信息:
https://github.com/katiehuang1221/onl_ds5_project_5
了解有关该应用程序的更多信息:
https://github.com/katiehuang1221/PicturePurrfect_app
其他视频:
https://www.youtube.com/channel/UCaZHIPzCIG9Y9xWYDpMvH5w
Plotly 的饼图和圆环图
为什么和如何

在 Unsplash 上由 Toa Heftiba 拍摄的照片
饼状图&备选方案
原因:饼图( PCs )用于显示互斥且不重叠类别的整体的部分。概念上的想法不是在类别之间进行比较,而是显示每个类别相对于总数的百分比或比例。这种类型的图表不应用于相关性、偏差或分布分析。
如何 : PCs 是被分成片状扇区的圆形图表。全圆代表整体 (100%),切片代表部分。每个部分都必须正确标记,最好包括对应于扇区百分比的数值。切片(段、扇区、楔形)不能重叠(互斥)。它们的总和必须始终为 100%。

图 1:作者用 Plotly Express 做的饼状图。
PCs 用两个可视属性对相应的数值进行编码:1 .- 每个切片沿周长的长度;2.- 每个切片的面积。
讲故事:电脑广泛应用于金融、商业、新闻和教育领域,用于显示各种类别的百分比或比例,包括预算、销售、失业率、人口细分、学术表现或调查答案。它们通过每个切片的面积和/或沿饼图周长的每个切片的长度来表达整体-部分关系的信息。
它们作为一种数据可视化技术的使用引起了很大的争议:一方面,这些可视化属性不容易解码;另一方面,观众非常熟悉饼状图的简单美学。无可争议的是,当切片过多时,它们很难阅读。
一些警告
总是检查整体是 100%,整体对观众有清楚的意义,并且类别是互斥的。例如,这些要求并不总是在调查结果中得到验证。
总是包括注释和百分比来描述内容。
当类别之间的差异很小时,或用于趋势或模式时,PCs 不应用于精确比较。
尽量不要用超过六七片。它们不仅很难读,而且有太多的传说。
如果你有许多难以阅读的小区域,试着把它们合并成一个单独的区域,用一个适合新情况的图例和一个中性的颜色。
避免所有 3D 效果。尽管它们在美学上令人愉悦,但它们会引入失真,并严重妨碍对信息的解读。
备选 1: 圆环图(圆环图)
它们完全等同于饼图(它们显示相互排斥且不重叠的类别的整体的部分),唯一的区别是图表中心有一个空白空间(一个洞),在那里显示某种附加信息以增强故事讲述。
现在不可能对面积进行比较,但扇形的数值仅通过沿圆周的弧长进行编码。
当中心的信息允许获得空间,从而减少图表产生的总空间时,使用它是合理的。例如,对应于每个扇区的图例可以指示在圆环的孔中。当然,这种空间上的收益必须超过视觉属性减少所带来的新的困难。
备选方案 2: 段分离
可以通过从原始饼图中分离出一个或几个片段来加强信息。你可以将整个饼图分开,也可以根据叙述突出显示单个切片。请注意,这种视觉分离会在图表中产生失真,可能会使信息难以理解。
饼状图&备选方案与策划案
位于加拿大蒙特利尔的技术计算公司 Plotly 于 2019 年推出了 Plotly Express (PE),这是一个全新的高级 Python 可视化库。PE 包括用一个函数调用来绘制标准图表的函数。
Plotly 还提供了一个名为 plotly.graph_objects 的模块,其中包含了 Python 类的层次结构。 Figure ,一个主类,有一个数据属性,允许以一种相对快速和简单的方式创建和显示图形。
我们的数据指的是 2017 年阿根廷共和国从事经济活动的人口。我们将根据劳动力市场的压力类型,用饼状图显示四组从事经济活动人口的百分比。这四个组被命名为:1。——“已就业”;2.——“失业”;3.-"未充分就业的求职者";4.-"就业不足的非求职者"。
对于本文中的饼状图,Plotly Express 函数是 px.pie ,对应的参数是: data_frame ,第一个参数,对应饼状图的扇区;数值设置与扇区相关的数值;名称设置扇区的标签;颜色给扇区分配色调。
这是图 1(上图)中饼图的代码:
import plotly.express as pxlabels = ['employed', 'unemployed',
'underemployed job seekers',
'underemployed non-job seekers']values = [72.8, 7.2, 14.7, 5.3]fig1 = px.pie(labels, values = values, names = labels)fig1.show()
我们认为改变其他人的默认颜色会很方便,这将改善信息。为此,我们使用参数 color_discrete_map 并获得图号 2。
fig2 = px.pie(labels, values = values,
names = labels, color = labels,
color_discrete_map = {'employed':'blue',
'unemployed': 'red',
'underemployed job seekers':'lightblue',
'underemployed non-job seekers':'orange'
})fig2.show()

图 2:带有选定颜色的饼图。由作者用 Plotly Express 制作。
接下来,我们添加一个标题和一个胡佛工具提示(图 3):
fig3 = px.pie(labels, values = values,
names = labels, color = labels,
color_discrete_map = {'employed':'blue',
'unemployed': 'red',
'underemployed job seekers':'lightblue',
'underemployed non-job seekers':'orange'
})fig3.update_traces(title = 'Population Economically Active',
title_font = dict(size=25,family='Verdana',
color='darkred'),
hoverinfo='label+percent',
textinfo='percent', textfont_size=20)fig3.show()

图 3:作者用 Plotly Express 做的饼状图。
让我们来看看圆环图是如何传达信息的。我们包括了孔参数,用于设置切割出饼图的半径分数。
fig4 = px.pie(labels, values = values, hole = 0.4,
names = labels, color = labels,
title = 'Population Economically Active',
color_discrete_map = {'employed':'blue',
'unemployed': 'red',
'underemployed job seekers':'lightblue',
'underemployed non-job seekers':'orange'
})fig4.update_traces(
title_font = dict(size=25,family='Verdana',
color='darkred'),
hoverinfo='label+percent',
textinfo='percent', textfont_size=20)fig4.show()

图 4:作者用 Plotly Express 做的圆环图。
我们感兴趣的是有单独段的图表是什么样子,但是有一个缺点: Plotly Express 没有为该任务提供参数。因此,我们不得不求助于 plotly.graph_objects。
对于本文中的以下圆环图, plotly.graph_objects 函数为 data = go。Pie() 和对应的参数有:值;标签;洞;标记 _ 颜色。
import plotly.graph_objects as gocolors = ['blue','red','lightblue','orange']fig5 = go.Figure(data = go.Pie(values = values,
labels = labels, hole = 0.4,
title = 'Population Economically Active',
marker_colors = colors
))fig5.update_traces(
title_font=dict(size=25,family='Verdana',
color='darkred'),
hoverinfo='label+percent',
textinfo='percent',
textfont_size=20,
)fig5.show()

图 5:作者用 plotly.graph_objects 做的圆环图。
可以看出,我们有一个问题:标题出现了漏洞。为了解决它,我们在孔中添加了一个注释。
fig6 = go.Figure(data = go.Pie(values = values,
labels = labels, hole = 0.4,
marker_colors = colors ))fig6.update_traces(hoverinfo='label+percent',
textinfo='percent', textfont_size=20)fig6.update_layout(
title_text = 'Population Economically Active',
title_font = dict(size=25,family='Verdana',
color='darkred'))fig6.add_annotation(x= 0.5, y = 0.5,
text = 'Year 2017',
font = dict(size=20,family='Verdana',
color='black'),
showarrow = False)fig6.show()

图 6:带有注释的环形图。由作者用 plotly.graph_objects 制作。
让我们看看是否通过从原始图表中分离一段的来加强信息。我们为拉设置值,这是一个定义从中心拉出哪些切片以及拉出到什么程度的列表。
fig7 = go.Figure(data = go.Pie(values = values,
labels = labels, hole = 0.4,
pull = [0,0.25,0,0],
marker_colors = colors ))fig7.update_traces(hoverinfo='label+percent',
textinfo='percent', textfont_size=20)fig7.update_layout(
title_text = 'Population Economically Active',
title_font = dict(size=25,family='Verdana',
color='darkred'))fig7.add_annotation(x= 0.5, y = 0.5,
text = 'Year 2017',
font = dict(size=20,family='Verdana',
color='black'),
showarrow = False)fig7.show()

图 7:拉出一段的圆环图。由作者用 plotly.graph_objects 制作。
我们让您来决定哪个图表(图 4、图 6 或图 7)提供了最好的故事讲述。
如果你发现了这篇感兴趣的文章,请阅读我之前的(https://medium.com/@dar.wtz):
棒棒糖和哑铃图,有曲线、平均值或中值?
斜率图表,为什么和如何,用斜率讲故事
在 Python 中使用管道操作以获得更好的可读性和更快的编码
一个方便的 Python 包,可以节省大量的编码时间,并提高 shell 风格管道操作的可读性

使用管道操作在单个语句中创建多个数据操作的链。照片由来自 Pexels 的 Joey Kyber 拍摄
Python 已经是一种优雅的编程语言。但不代表没有进步的空间。
Pipe 是一个漂亮的包,它将 Python 处理数据的能力提升到了一个新的水平。它采用类似 SQL 的声明性方法来操作集合中的元素。它可以过滤、转换、排序、删除重复项、执行分组操作等等,而不需要编写大量代码。
在这篇小文章中,让我们讨论如何用管道简化我们的 python 代码。最重要的是,我们将构建定制的可重用管道操作,以便在我们的项目中重用。
让我们从
- 一个励志的例子;
- 一些有用的开箱即用管道操作,以及;
- 构建我们自己的管道运营。
如果您想知道如何设置它,您可以很容易地用 PyPI 安装管道。这是你需要做的。
pip install pipe
开始在 Python 中使用管道。
这里有一个使用管道的例子。假设我们有一个数字列表,我们想,
- 删除所有重复项;
- 仅过滤奇数;
- 平方列表中的每个元素,以及;
- 按升序对值进行排序;
这是我们在普通 Python 中通常会做的事情。
一些使用基本 Python 的数据争论——由作者编写的代码片段。
[1, 49, 169, 361, 441, 729]
上面的代码非常易读。但是这里有一个更好的使用管道的方法。
Pipe 如何提高 Python 中代码的可读性——作者的代码片段。
[1, 49, 169, 361, 441, 729]
两种代码产生相同的结果。然而第二个比第一个更直观。显然,它的代码行也更少了。
这就是 Pipe 帮助我们简化代码的方式。我们可以对集合进行链式操作,而无需编写单独的代码行。
但是在 Pipe 中有更酷的操作,就像我们在上面的例子中使用的那样。此外,如果我们需要非常独特的东西,我们可以创建一个。我们先来探讨一些预置的操作。
最有用的管道操作
我们已经看到了几个管道在工作。但是还有更多。在这一节中,让我们讨论一些其他有用的现成数据操作。
这些并不是管道安装的完整操作列表。要获得详细的清单,请参考 GitHub 上的管道库。
分组依据
我相信这是对数据科学家最有用的管道。我们更喜欢在熊猫身上做,我仍然喜欢用它。但是将一个列表转换成一个数据集有时感觉有点矫枉过正。在所有这些情况下,我都可以随时使用这种按管道分组的操作。
使用 group by pipe 操作—作者为的代码片段。
上面的代码将我们的数据集分组为奇数和偶数。它创建一个二元组列表。每个元组都有在 lambda 函数和分组对象中指定的名称。因此,上面的代码产生了下面的组。
[
('Even', <itertools._grouper object at 0x7fdc05ed0310>),
('Odd', <itertools._grouper object at 0x7fdc05f88700>),
]
我们现在可以为我们创建的每个组分别执行操作。这里有一个例子,它从每组中取出元素,然后将它们平方。
将变换应用到组——由作者编写的代码片段。
[
{'Even': [16, 4, 36, 64, 100, 400, 324]},
{'Odd': [1, 729, 49, 169, 361, 441, 49, 729]},
]
链条和导线
这两个操作使得展开一个嵌套列表变得很容易。该链一步一步地执行,递归地遍历,直到列表不再扩展。
下面是链条的工作原理。
使用链式管道操作在 Python 中展开嵌套列表—代码片段由作者编写。
[1, 2, 3, 4, 5, 6, 7, [8, 9]]
正如我们所看到的,chain 展开了列表的最外层。8 和 9 保留在嵌套列表中,因为它们已经向下嵌套了一层。
以下是使用 traverse 的结果。
使用遍历管道操作递归展开多层嵌套列表——作者的代码片段。
[1, 2, 3, 4, 5, 6, 7, 8, 9]
特拉弗斯展开了它能展开的一切。
我主要用列表理解来展开列表。但是阅读和理解代码中发生的事情变得越来越困难。此外,当我们不知道有多少嵌套层时,很难像上面例子中的遍历操作那样递归扩展。
我老派的展开嵌套列表的方式——作者作者的代码片段。
Take_while 和 Skip_while
这两个操作类似于我们前面使用的“where”操作。关键的区别在于,如果满足某些条件,take_while 和 skip_while 会停止查看集合中的其他元素。而另一方面,计算列表中的每个元素。
下面是 take_while 和 where 在过滤小于 5 的值的简单任务中的工作方式。
使用 take_while 管道操作在满足特定条件时停止执行—代码片段由作者编写。
上述代码的结果如下:
take_while: [5, 3]
where: [3, 4, 3]
请注意,take_while 操作跳过了最终的“3 ”,而“where”进程包含了它。
Skip_while 的工作方式很像 take_while,只是它只在满足某些条件时包含元素。
满足特定条件时使用 skip_while 管道开始执行——作者作者的代码片段。
[5, 3]
正如我前面提到的,这些并不是您可以使用管道库做的事情的完整列表。更多的内置函数和例子请查看资源库。
创建新的管道操作
创建新的管道操作相对容易。我们所需要的就是用 Pipe 类来注释一个函数。
在下面的例子中,我们将一个常规的 Python 函数转换成一个管道操作。它接受一个整数作为输入,并返回它的平方值。
用 Python 创建新的管道操作——作者作者的代码片段。
因为我们已经用@Pipe类注释了这个函数,所以它变成了一个管道操作。在第 9 行,我们用它来计算一个数字的平方。
管道操作也可以接受额外的参数。第一个参数总是链中上一个操作的输出。我们可以有任何额外的参数,并在链中使用它们时指定它们。
额外的参数甚至可以是一个函数。
在下面的示例中,我们创建了一个采用附加参数的管道操作。附加参数是一个函数。我们的管道操作是使用函数转换列表中的每个元素。
使用额外参数创建管道操作——作者的代码片段。
最后的想法
看到 Python 如何进一步改进,令人印象深刻。
作为一名实践数据科学家,我发现 Pipe 在许多日常任务中非常有用。我们也可以用熊猫来完成这些任务。然而,管道在提高代码可读性方面表现出色。即使是编程新手也能理解这种转变。
这里需要注意的是,我还没有在大型项目中使用过 Pipe。我还没有探索它在大规模数据集和数据管道上的表现。但我确实相信这个包将在离线分析中发挥重要作用。
感谢阅读,朋友!跟我打招呼上LinkedInTwitter,以及* 中 。*
还不是中等会员?请使用此链接 成为会员 因为,在不为你额外付费的情况下,我为你引荐赚取一小笔佣金。
管道:具有超参数调整的自动机器学习!
创建自己的可重用代码库的第一步。
厌倦了为每一次深入研究数据科学而重新编写相同的旧代码吗?你来对地方了!
首先是数据,然后是无尽的 ETL 过程,然后是建模,最后是推理。但是,将这一切自动化,以便您可以即插即用任何数据,只需进行最小的修改,这难道不是很酷吗?管道使我们能够做到这一点!

什么是机器学习管道?
就像我们在现实世界中的对等物一样,ML 管道是将一个进程与另一个进程连接起来的载体,因此只有一个输入和一个输出的连接点。现代应用程序过度依赖于连接 ML 服务和现有 DevOps 流程的管道架构!简而言之,它帮助你把你的项目超级物化!
- 数据准备,包括导入、验证和清理、管理和转换、规范化和暂存
- 培训配置,包括参数化参数、文件路径和日志/报告配置
- 高效、重复地培训和验证。效率可能来自指定特定的数据子集;不同的硬件计算资源、分布式处理和进度监控。
- 部署,包括版本控制、扩展、供应和访问控制
如果你想了解更多,这里有一个关于 ML 管道的好的开端。
在这篇博客中,我将介绍 Scikit 的基础知识——学习管道。我将编写定制的 Transformer 函数,负责数据清理、特征工程和模型训练。(我知道你来这里是为了寻找定制变压器解决方案,而不是 onehotencoder 插入管道步骤:D
数据和描述
我正在从 Kaggle 获取“恒星类型分类”数据集。这个数据集混合了分类变量和连续变量。我们来做一个多类分类( saucy )!

数据帧头
大部分特性都是不言自明的!你可以在这里阅读光谱类。
我们的目标变量是“类型”,它是——红矮星(0),褐矮星(1),白矮星(2),主序(3),超级巨星(4),超巨星(5)。
同样,每个类有 40 个观察值这使得它成为一个完美平衡的问题。
解释性数据分析

我们最喜欢的散点图与花式过滤器!
我使用了这个项目的自动化 EDA 工具 D-tale 。我喜欢它,因为它的简单和超快的速度。
但是,你不是来找埃达的,对吧?让我们继续前进!
数据预处理
以下是我从分析中得出的结论。
- 数据集中没有丢失的值(不过我还是会处理它)
- 好家伙,有离群值。所以,在评论中加入你最好的离群点管理策略。我已经通过第 20 和第 80 百分位范围对异常值的四分位范围进行了修整。见高斯分布此处。
- 特征向右或向左倾斜。因此,我不能简单地应用线性方法。
- 缩放——空间非常巨大,需要缩放。因为我想让我的模型看到异常值,但保留它们的方差-偏差权衡,所以我将对我的数据应用 RobustScalar。
特征工程
我已经进行了三次测试,以发掘我的脚踏实地的管道中的一个功能的天堂般的重要性。
- 这是我的小抄!

- Pearson’s R for continuous-continuous cases - Correlation Ratio for categorical-continuous cases - Cramer’s V or Theil’s U for categorical-categorical cases
2.方差分析 (ANOVA):是个美女!它显示了与目标相关的重要性。
Temperature: 3.323401956092008e-11
RelativeLuminosity: 1.641155523850019e-33
RelativeRadius: 1.6272694239287043e-31
ApparentMagnitude: 6.33087509199811e-128
Color: 1.047429715544e-06
SpectralClass: 0.44868186785826514
如你所见,色彩的 P 值非常高!
3.卡方检验:用于传统的分类分析。
Color 8.716079e-26SpectralClass 1.167568e-37
好吧,休息两分钟!回顾刚刚讲述的概念。我知道你是专业的,但只是一个小小的心理回顾!
我们来说说变形金刚吧!
不是迈克尔湾的,Scikit-learn 变压器形成了管道的一个组成部分,让你即插即用不同的组件。首先,在这里导入这两个基类:
from sklearn.base import BaseEstimator, TransformerMixin
我们刚刚导入的这些类就像是自定义类的粘合剂。基本估算器 为管道提供所有 sklearn 估算器都需要的 get_params 和 set_params 方法。transformer mixin给出了 fit_transform 方法。
为了防止任何数据泄漏和漂移,最好在开始时分割我们的训练和测试数据集。有几个优点,即:
- 预处理:您正在填充缺失值并初步移除异常值;你的模型将如何对看不见的真实世界数据进行归纳?因此,首先分裂!
- 特征缩放:-我们使用标量。在训练数据集上拟合(),让这些计算出的参数转换训练和测试数据。LoL 什么?是的,它有助于检测生产中的数据漂移。
X_train, X_test, y_train, y_test = train_test_split(df.drop(columns='Type'), df['Type'],test_size=0.30, random_state=1,stratify= df['Type'])
我使用分层分割,因为我希望训练和测试数据集能够代表所有的目标标签。(训练数据集可能永远不会包含某些类别)
数字特征转换器
我们总是可以使用标量或估算器的默认类实现。但是,如果您的用例要求对一个已经成为问题的特性进行自定义处理,该怎么办呢?它可以打破或使系统!
您需要填充两个方法:
- 拟合—这里更喜欢计算步骤,如测量平均值、中值和方差。
- transform——此处首选应用转换。
用这个的好处是你会有绝对的控制权。此外,您的计算实例不必两次计算相同的统计数据。
注意 :注意你在 fit 和 transform 方法中实现的功能。实现的顺序可能会引入偏差(就像我提到的缩放)。
分类特征的转换器
现在,我们已经处理了我们的数字特征。我们可以继续进行分类特征。我会把我的分类特征变得愚蠢。您可以包含 KninsDiscretiser 或任何其他定制函数来适应您的用例。'
厉害!快到了
现在,我们需要将这两个类缝合在一起。Scikit-learn 提供两种功能:
FeatureUnion:连接多个 transformer 对象的结果。这个估计器将 transformer 对象列表并行应用于输入数据,然后连接结果。这对于将几个特征提取机制组合到单个变换器中是有用的。
ColumnTransformer:将转换器应用于数组或 pandas 数据框架的列。该估计器允许输入的不同列或列子集被分别变换,并且由每个变换器生成的特征将被连接以形成单个特征空间。这对于异构数据或列数据很有用,可以将几个特征提取机制或转换组合到单个转换器中。
在我们的用例中,我创建了一个基本的转换器,以便分类和数字特征被分别处理。因此,我将使用 ColumnTransformer。
击鼓吧。
显然,我们首先需要一个特性列表。🙌
numerical_columns = ['Temperature', 'RelativeLuminosity', 'RelativeRadius', 'ApparentMagnitude']categorical_columns = ['SpectralClass']
牛逼。完成了!
让我们使用基于内核的模型来测试它。因为我的用例是多类分类,所以我将 KNearestNeighbors 作为我的基本模型。
我也使用了 K 倍交叉验证。但是,在我心里,我知道它会过拟合,因为训练数据的大小是 110。但是,这不是我们现在关心的问题。过度拟合将很快通过超参数调整和模型迭代来解决。
模特培训
一旦您转换并运行了处理,您就可以将它插入到培训管道中。您可以包含各种奇特的步骤,包括 GridSearchCV、交叉验证,并在一个链中创建一组模型。
result = cross_val_score(model ,X_train, y_train, cv = KFold(n_splits=3), error_score=-1)
print("Avg accuracy: {}".format(max(result)))Avg accuracy: 0.9107142857142857
嘿!成功了,耶,你成功了。我确信这些步骤足以启动你的管道之路。毕竟我们都是马里奥和路易吉!(管道—游戏—请理解这个笑话!)
终于!
任务完成!今天,我们在 scikit 中创建了基本框架——了解任何数据科学的深层知识。我们使用定制的转换元素执行数据清理、规范化和数据转换。
接下来,我们使用 ColumnTransfomer 将管道的各个通道(分类通道和数字通道)连接起来,ColumnTransfomer 将两个数据子集粘合在一起。最后,我们用我们的管道训练了一个基本模型。
我希望这篇博客能澄清一些关于定制变压器的基础知识。它们是任何现代 ML 应用程序的支柱,如果你能把它们包含在你的工作流程中,那将是最好的。
你可以在 Github 上的 这里获得这个完整的笔记本 。
使用 HyperOpts 自动调整多个 SKlearn 模型的 Hyper 参数的后续步骤可在这里找到。
谢谢你留下来!你可以随时通过 LinkedIn 联系我,获得一些想法!
如何使用 Kedro 来编排您的 MLOps 管道
从这里开始您的动手实践之旅

米赞·拉提夫在 Unsplash 上的照片
介绍
MLOps 是一套很难在家学习的概念。如果没有生产环境,很难模拟真实世界中发生的事情。我一直在研究在家开发 MLOps 的方法,并选择了一些实现这些概念并能正常工作的工具。这篇文章涵盖了由麦肯锡公司的 QuantumBlack 开发的工具 Kedro。
官方文件将其描述为…
一个开源 Python 框架,用于创建可复制、可维护和模块化的数据科学代码。它从软件工程最佳实践中借用概念,并将它们应用于机器学习代码;应用的概念包括模块化、关注点分离和版本控制。
在本文中,我们将看看 Kedro 如何帮助我们构建被称为管道的可重复、自动化代码。
目录
- 拿笔记本
- 创建一个 Kedro 项目
- 将 Python 包添加到 Requirements.txt
- 配置数据目录
- 创建管道
- 创建节点
- 建造管道
- 参数字典
- 完成 Nodes.py 和 Pipeline.py 及参数字典
- 完成数据目录
- 注册管道
- 运行项目
- 结论
去拿笔记本
我已经准备了一个笔记本,上面有很好的重构代码,准备好变成一个 Kedro 项目。你可以使用你自己的,但是你可能需要做一些额外的争论和代码重构。
为了平稳地从笔记本过渡到管道,您需要确保您已经重构了代码,以将任何转换编写为函数。这些功能将成为我们 Kedro 管道中的节点。
要了解如何重构你的代码,请点击这里查看我的文章:
如果你只是想要笔记本,不用多说:
https://github.com/AdamShafi92/mlops-at-home/blob/main/1-refactoring_code/3 Refactored.ipynb
包含完整 Kedro 项目的最终回购位于以下位置:
https://github.com/AdamShafi92/mlops-at-home/tree/main/2-pipelines/hpmlops
创建一个 Kedro 项目
我们现在准备开始进入 MLOps。
在计算机上的某个位置创建一个新文件夹。导航到该文件夹并打开一个终端或 bash 窗口。
从创建一个全新的环境开始,我推荐使用 Conda 来管理您的环境。 Kedro 建议每个项目有一个环境。
conda create -n house_prices_mlops python=3.8 anaconda
确保您激活了环境
conda activate house_prices_mlops
然后使用以下命令安装 Kedro
conda install -c conda-forge kedro
现在初始化一个新的 kedro 项目。您将被要求为项目的不同部分提供 3 个名称,您可以将所有这些都称为 house_prices_mlops 。
kedro new
最后,如果需要,您可以初始化 git repo。这次我没有这样做。
在一个以您的项目名命名的文件夹中,您应该得到下面的文件夹结构,我们称之为项目名。这看起来确实很复杂,但是我们现在可以忽略很多东西。实际上只需要很少的配置就可以开始了。
**├──** **conf**
├── base
├── catalog.yml
├── logging.yml
├── parameters.yml
├── local
├── credentials.yml
**├──** **data**
├── 01_raw
├── 02_intermediate
├── 03_primary
├── 04_feature
├── 05_model_input
├── 06_models
├── 07_model_output
├── 08_reporting
**├──** **docs**
├── source
├── conf.py
├── index.rst
**├──** **logs**
├── journals
**├──** **notebooks** **├──** **src**
├── project_name
├── __pycache__
├── pipelines
├── __init__.py
├── __init__.py
├── __main__.py
├── cli.py
├── hooks.py
├── pipeline_registry.py
├── tests
├── pipelines
├── __init__.py
├── __init__.py
├── test_run.py
├── requirements.in
├── requirements.txt
├── setup.py
├── pyproject.toml
├── README.md
├── setup.cfg
如果您已经下载了我的笔记本,您可以将其添加到笔记本文件夹中,。/house _ prices _ mlops/notebooks
将 Python 包添加到 Requirements.txt
对于导入到笔记本中的每个包,您需要在 requirements.txt 文件中有一个条目。这是一个包含项目使用的所有 Python 包的文件。这些是作为 Kedro 初始化步骤的一部分安装的。
如果您想要一种从 Jupyter 笔记本中提取需求的简单方法,请看这里:
创建文件后,将内容复制到 Kedro 的等效文件中,该文件位于:
├── conf
├── data
├── docs
├── logs
├── notebooks**├──** **src**
**├── requirements.txt**
注意我们使用的是 。txt 文件,而不是
我完成的 requirements.txt 文件位于主 repo 中,这里。
配置数据目录
数据目录是一个。Kedro 用来加载和保存数据的 yml 文件。这意味着我们可以使用数据集名称而不是文件路径来访问数据。
你读入或保存的每一个数据集都需要在数据目录中注册。
目录位于以下位置:
***├──** **conf
** **├── base****├── catalog.yml**
├── logging.yml
├── parameters.yml
├── data
├── docs
├── logs
├── notebooks
├── src*
首先,将原始数据文件复制到下面的目录中。
*├── conf
**├──** **data
** **├── 01_raw** ├── docs
├── logs
├── notebooks
├── src*
然后,在 catalog.yml 文件中注册您的原始数据。我的数据集被命名为 house_prices ,因此在 catalog.yml 文件中需要的行是:
***house_prices:**
**type**: pandas.CSVDataSet
**filepath**: data/01_raw/train.csv*
我们现在可以在 Kedro 的其他区域引用这个数据集。
此时,您可以保存并关闭 catalog.yml 文件。
创建新管道
管道是一系列被称为节点的转换。要创建一个,我们需要 2 个 Python 文件:
- 一个节点文件,这是用于单独转换的 Python 函数
- 一个管道文件,指定了 python 函数的顺序和参数。
我们需要在这里创建一些文件和文件夹来开始。每个管道都应该在自己的文件夹中有自己的管道和节点文件。通常为不同的阶段创建不同的管道,例如 EDA、数据处理、模型训练。让我们创建一个数据处理管道。
创建文件夹和空 Python 文件以匹配下面的结构,斜体中的项目是需要创建的项目。
*├── conf
├── data
├── docs
├── logs
├── notebooks**├──** **src**
**├── project_name
****├── pipelines**
├── __init__.py
*├── data_processing
* *├── __init.py__
* *├── nodes.py
* *├── pipeline.py**
首先打开你新创建的 init。py 文件并添加下面一行代码。然后保存并关闭它。
*from .pipeline import create_pipeline*
创建节点
一个节点是 3 项的 Kedro 术语,一个 Python 函数、输入位置和参数以及一个输出位置。如果你用的是我上面的笔记本,这些步骤已经变成了简单的函数,我们可以很容易地把它们变成节点。
要创建 nodes.py 文件,
- 打开您在上一步中创建的文件
- 将所有 Python 包导入复制到文件的顶部。
- 将笔记本中的每个函数复制到 nodes.py 文件中。
这里有一个笔记本功能的例子。
*import pandas as pddef remove_outliers(train):
train = train.copy()
train = train.drop(
train[(train[‘GrLivArea’]>4000) &
(train[‘SalePrice’]<30000)].index)
return train*
注:我们可能想要改变 GrLivArea 等参数,在本教程中,我们将使用参数字典*来完成。这是一个中央字典,包含了我们函数的所有值。我们可以在进行实验时,用这个从一个位置改变这些。*
在更新参数字典之前,我们将这个函数添加到一个管道中。
建设管道
管道只是将节点缝合在一起,并定义每个函数的关键字参数。它还定义了每个函数的输出应该保存在哪里。你不必在每一步之间都存钱,但我觉得这样更容易。让我们看一个例子。
*node(
func=remove_outliers,
inputs=[“house_prices”, “parameters”],
outputs=”house_prices_no_outliers”,
name=”outliers_node”,)*
- func: 要使用的函数名,来自 nodes.py 文件。
- **输入:在这个例子中,列表包含数据源(使用 Kedro 名称,在数据目录中定义)和 Kedro parameter.yaml 文件,该文件由 Kedro 命名为 Parameters。
- **输出:另一个 Kedro 数据源,在数据目录中定义
- **名称:节点的名称
因为我们已经指定了一个输出名称,所以这个名称也需要在数据目录中,以便 Kedro 能够理解。因为我们将为每个节点创建一个数据集,所以稍后我们将批量更新它。
每个节点都应该位于管道中,如下所示,每个节点都包含上面括号中格式的处理代码。
*def create_pipeline(**kwargs):
return Pipeline(
[
node(),
node(),
node(),])*
参数字典
参数字典为我们管理管道中的任何参数提供了一种简洁的方式,例如函数关键字参数、模型超参数或加载的文件路径。
字典是一个. yml 文件,位于:
***├──** **conf
** **├── base** ├── catalog.yml
├── logging.yml
**├── parameters.yml**
├── data
├── docs
├── logs
├── notebooks
├── src*
为了利用这一点,我们需要三样东西
- 字典中包含我们需要的信息的键值对。
- 在节点和管道文件中指定的参数字典。
- 字典需要在每个被使用的函数中被调用。
比方说,我们想在不同的层次上尝试丢弃异常值。
在参数字典中,我们需要给值一个名称。我通过将 GrLivArea 和 SalePrice 变量放入异常值中来嵌套它。
*outliers:
GrLivArea: 4000
SalePrice: 300000*
在 Python 中,你可以用标准的方式访问这个字典,例如
*parameters['outliers']['GrLivArea']>>> 4000*
我们现在可以更新我们的函数来包含参数字典。
- 包含参数字典作为关键字参数。
- 在函数的相关部分调用它。
*def remove_outliers(train, parameters):
train = train.copy()
train = train.drop(
train[(train[‘GrLivArea’]>parameters[‘outliers’][‘GrLivArea’]) &
(train[‘SalePrice’]<parameters[‘outliers’][‘SalePrice’])].index)
return train*
目前,“参数”只是一个关键词。我们在 pipeline.py 文件中提供了字典本身。parameter.yaml 文件在 Kedro 中有自己的名称,所以我们只需在管道中包含字符串“parameters”。
*node(
func=remove_outliers,
inputs=[“house_prices”, “parameters”],
outputs=”house_prices_no_outliers”,
name=”outliers_node”,*
完成 Nodes.py 和 Pipeline.py 以及参数字典
现在,您应该已经用一个函数更新了这些文件,删除了异常值。原来的笔记本还有很多我们用来处理数据的功能,这些也要注册。
Nodes.py
将每个函数添加到节点文件中,并确保包含所有相关的导入。
我们在 parameters 字典中注册我们想要改变的任何内容,并确保 parameters 是函数中的一个关键字参数。
完成的节点文件在这里。
管道. py
我们现在可以注册管道中的每个节点。这相当简单——我们只需添加每个节点的名称和输入/输出,就像我们之前做的那样。
参数字典
确保你添加的任何东西是一个参数也在字典里,我完成的文件在这里。
完成数据目录
您会注意到,在节点中,我们使用字符串来定义每个函数的输入和输出。这些可能是。csv 文件(或其他兼容的文件类型)。我们必须告诉 Kedro 每个文件是什么,并给它一个名称。这些都在一个位置指定,即数据目录。我们已经添加了一个文件,但是让我们更详细地看看它。
数据目录是一个. yml 文件,位于:
***├──** **conf
** **├── base** **├── catalog.yml**
├── logging.yml
├── parameters.yml
├── data
├── docs
├── logs
├── notebooks
├── src*
定义一个数据源至少需要三样东西,名称、数据类型和数据的文件路径。我们之前添加了 or house_prices 数据集,它应该看起来像这样。
*house_prices:
type: pandas.CSVDataSet
filepath: data/01_raw/train.csv*
然而,在我们的第一个节点中,我们还定义了一个输出数据集,“house_prices_no_outliers”。Kedro 也需要知道在哪里保存它,它是以同样的方式定义的。
*house_prices_no_outliers:
type: pandas.CSVDataSet
filepath: data/02_intermediate/house_prices_no_outliers.csv*
我们将在每个处理步骤后保存 csv 文件,所以现在让我们定义其余的。
*y_train:
type: pandas.CSVDataSet
filepath: data/05_model_input/y_train.csvhouse_prices_drop:
type: pandas.CSVDataSet
filepath: data/02_intermediate/house_prices_drop.csvhouse_prices_no_na:
type: pandas.CSVDataSet
filepath: data/02_intermediate/house_prices_no_na.csvhouse_prices_clean:
type: pandas.CSVDataSet
filepath: data/02_intermediate/house_prices_clean.csv*
注册管道
我们现在已经定义了所有的数据源、参数,创建了节点,并将它们连接在一个管道中。
我们现在可以将这些放在管道注册表中。
这是一个。py 文件存储在:**
*├── conf
├── data
├── docs
├── logs
├── notebooks**├──** **src**
**├── project_name
****├── pipeline_registry.py***
在这个文件中,我们简单地将 pipeline.py 文件映射到一个名称。如果有任何其他管道,它们也应该在这里定义,但我们目前只有一个。
运行项目
我们现在拥有了运行项目和处理数据所需的一切!
要实际运行管道,只需导航到根目录(包含 conf、data、docs 等文件夹的目录)并在命令行中输入以下内容:
*kedro run*
凯卓现在将管理你所有的管道。由于软件包贬值或没有 git repo,您很可能会得到一些错误消息。目前来看,这些都可以忽略。您应该在命令行中看到以下内容
*INFO — Completed 5 out of 5 tasks
INFO — Pipeline execution completed successfully*
理解你的步骤的一个简便方法是产生一个观想。要做到这一点,您需要 pip 安装 kedro-viz。你可以在这里了解更多。
*pip install kedro-viz*
只需在命令行中运行以下命令,即可获得以下交互式可视化效果。下面的截图中有我没有包括的滤镜和设置,但是你的应该看起来很像。

显示我们 Kedro 管道中每一步的流程图。图片作者。
结论
我们现在已经建立了一种自动化管道的方法。与简单的 Jupyter 笔记本相比,Kedro 提供了一种更强大、标准化的数据处理方式。代码是高度可重用的。然而,完整的好处可能还不清楚,因此我们需要开始集成其他工具来自动化预处理、测试和模型跟踪。
下图显示了我个人在家开发 MLOps 的计划。您可以看到,我们已经开发了一个框架,并开始管理管道。事情还没有完全自动化,我们还有很长的路要走,但这是一个好的开始!

在家开发 MLOps。图片作者。
了解更多信息
* [## TabNet:梯度推进的终结?
towardsdatascience.com](/tabnet-e1b979907694)*
Pipenv 与 Conda(针对数据科学家)
截至 2021 年 1 月,pipenv 和 conda 基于各种“数据科学”标准的比较
介绍
Python 有许多工具可用于向开发人员分发代码,并且不遵循“应该有一种——最好只有一种——显而易见的方法来做这件事”。例如,管理无处不在的 scipy 堆栈的 scipy.org 的推荐 Conda+Anaconda,而 python 打包权威的 PyPA 推荐 pipenv+PyPI。这可能会让数据科学家有点左右为难。本文使用以下一组标准对截至 2021 年 1 月的 pipenv 和 conda 进行了比较,其中一些标准与数据科学家更相关:
本文并不推荐一种工具而不推荐另一种,而是应该帮助读者根据他们的需求做出决定。本文假设读者已经熟悉 python 打包生态系统、pipenv 和 conda。对于那些不太熟悉的人,我还在文章末尾列出了一个 有用资源的列表 。
包装可用性
包装是否有合适的格式?
正如 Anaconda 所说,“Anaconda 资源库中有超过 1500 个包,包括最流行的数据科学、机器学习和人工智能框架。这些,以及 Anaconda cloud 上来自 channeling 的数千个额外的软件包,包括 conda-forge 和 bioconda ,都可以使用 conda 安装。尽管有这么多的软件包,但与 PyPI 上的 150,000 个软件包相比还是很少。另一方面,并不是 PyPI 中的所有包都可以作为 wheels 使用,这对于通常需要 C/C++/Fortran 代码的数据科学库来说尤其成问题。虽然在 conda 环境中使用 pip 安装 PyPI 包是可能的,但这要求所有子依赖项本身也是 pip 包,这可能会引起麻烦,所以不推荐使用。与 PyPI 相比,Anaconda 主通道中可用的包之间通常会有延迟。例如,熊猫的延迟似乎是几个星期。
我想检查 pipenv+PyPI 和 conda+Anaconda 是否可以提供数据科学家的基本工具集:pandas、scikit-learn、sqlalchemy、jupyter、matplotlib 和 networkx。我用的是 python3.8,因为 3.9 是最近才出的。
$ pipenv install pandas scikit-learn sqlalchemy jupyter matplotlib networkx --python 3.8$ conda create --name env_ds scikit-learn sqlalchemy jupyter matplotlib networkx python=3.8
两个环境都在大约 3 分钟内成功创建。请注意,我使用的是 Ubuntu WSL1,不同的平台在创建环境方面可能不太成功。
依赖性解析
解析直接和间接依赖关系
康达
为了测试这个标准,我使用了依赖 numpy 的 pandas。我首先尝试使用 conda 安装 numpy1.15.3 和 pandas,这样环境就直接依赖 pandas 和 numpy,间接依赖 numpy:
$ conda create --name env_a numpy==1.15.3 pandas python=3.7
Conda 成功地创建了一个环境,并安装了 pandas1.0.5,这是支持 numpy1.15.3 的最后一个 pandas 版本。
如果现有环境的软件包版本需要升级或降级:
$ conda create --name env_b pandas python=3.7
$ conda activate env_b
$ conda install numpy==1.15.3
Conda 会在更新环境前询问您:
以下软件包将被降级:
numpy 1 . 19 . 2-py 37h 54 aff 64 _ 0→1 . 15 . 3-py 37h 99 e 49 EC _ 0
numpy-base 1 . 19 . 2-py 37 HFA 32 c 7d _ 0→1 . 15 . 3-py 37 H2 f 8d 375 _ 0
熊猫 1 . 2 . 0-py 37 ha 9443 f 7 _ 0→1 . 0 . 5-py 37h 0573 a6f _ 0继续吗?
注意,建议同时指定所有包,以帮助 Conda 解决依赖性。
Pipenv
然后,我尝试用 pipenv 安装相同的包:
$ pipenv install numpy==1.15.3 pandas --python 3.7
Pipenv 使用 numpy1.19.1 创建了一个环境,它不符合我的规范。Pipenv 确定存在冲突,无法创建 Pipfile.lock 并打印以下有用的消息:
✘锁定失败!
解析后的依赖关系中存在不兼容的版本:
numpy = = 1 . 15 . 3(from-r/tmp/pipenvzq 7 o 52 yj requirements/pipenv-5 BF 3v 15 e-constraints . txt(第 3 行))
numpy>= 1 . 16 . 5(from pandas = = 1 . 2 . 0->-r/tmp/pipenvzq 7 o 52 yj requirements/pipenv-5 BF 3v 15 e-1
Pipenv 还有 graph 和 graph-reverse 命令,可以打印依赖关系图,并允许用户跟踪包如何相互依赖,帮助解决冲突。
$ pipenv graph
pandas = = 1 . 2 . 0
-numpy[必选:> =1.16.5,已安装:1 . 19 . 5】
-python-dateutil[必选:> =2.7.3,已安装:2 . 8 . 1】
—six[必选:> =1.5,已安装:1 . 15 . 0】
-pytz[必选:> =2017.3,已安装:2020.5
注意,pip 依赖解析器正在经历变化。我使用了最新版本(20.3.1 ),但结果可能会因 pip 版本而异。
Python 版本
管理不同的 python 版本
康达
Conda 将 python 发行版视为一个包,并自动安装您直接指定的任何 python 版本。此外,在创建新环境时,conda 将确定最佳 python 版本(如果没有指定)。例如:
$ conda create —-name env_a pandas
用 python3.8.5 和 pandas1.1.5 创建一个环境,但是
$ conda create —-name env_c pandas==0.25.0
使用 python3.7.9 创建环境,python 3 . 7 . 9 是支持 pandas0.25.0 的最新 python 版本。
如果需要升级/降级现有环境的 python 版本,安装将会失败:
$ conda create —-name env_d python==3.8
$ conda activate env_d
$ conda install pandas==0.25.0
但是错误消息非常有用:
UnsatisfiableError:发现以下规范
与您环境中现有的 python 安装不兼容:
规范:
-pandas = = 0 . 25 . 0->python[version = '>= 3.6,< 3.7.0a0| > =3.7,< 3.8.0a0']
Pipenv
Pipenv 本身并不安装不同的 python 版本。它将使用系统 python(通常存储在/usr/lib 中)或基础 python(如果安装了 miniconda,通常存储在~/miniconda3/bin 中)来创建新环境。但是,如果安装了 pyenv,pipenv 可以使用 pyenv 安装其他 python 版本。您可以使用 pyenv 预安装 python 版本,或者如果本地没有 python 版本,pipenv 会要求您安装该版本:
https://towardsdatascience . com/python-environment-101-1 d68 BDA 3094d
不幸的是,pipenv+pyenv 不能解析最好的 python 版本,即使从头开始创建环境也是如此。例如:
$ pipenv install pandas
使用 python3.8.5 和 pandas1.2.0 创建环境。尝试安装 pandas0.25.0,其中默认 pyenv python 版本为 3.8 暂停:
$ pipenv install pandas==0.25.0
请注意,暂停可能是由于 pandas0.25.0 的要求是如何配置的。pip 依靠 python_requires 属性来确定 python 版本是否合适,这是最近添加的。在不满足 python_requires 属性的情况下,尝试安装更新的包通常会失败,并显示“找不到发行版”错误。请注意,如果没有指定,pipenv 还会尝试安装软件包的最新版本,而不管 python 版本如何。例如,在 python3.5 环境中尝试熊猫:
$ pipenv install pandas --python 3.5
将失败,并显示以下错误消息:
[pipenv . exceptions . install ERROR]:错误:找不到满足要求的版本 pandas==1.1.5
[pipenv . exceptions . install ERROR]:错误:找不到 pandas = = 1 . 1 . 5 的匹配分发
该信息并不十分有用,已作为一个问题向 pip 提出。
依赖性说明
确保可升级的可重复构建
Pipenv 使用两个文件来指定依赖关系:Pipfile 用于直接依赖关系,Pipfile.lock 用于直接和间接依赖关系。使用 Pipfile.lock 创建一个环境可以确保安装完全相同的包,包括包的散列。如果需要,使用 Pipfile 创建环境可以让 it 灵活地升级间接依赖关系。Pipenv 希望 Pipfiles 在未来能够取代 requirements.txt(参见https://github.com/pypa/pipfile)。
Conda 使用一个 environment.yaml 文件来指定直接和间接依赖关系。用户在更新他们的环境时必须使用试错法。有一个 conda-lock 库复制了 Pipfile.lock 功能,但是 Anaconda 目前不支持它。
磁盘空间
环境会占用多少空间?分享有帮助吗?
数据科学家使用的 Python 环境往往很大,尤其是 conda 环境。例如,包含 jupyter 和 pandas 的 conda 环境占用 1.7GB,而同等的 pipenv 环境占用 208MB。虽然与大多数开发环境无关,但这在生产中可能变得更加重要,例如当使用容器时:
https://towards data science . com/how-to-shrink-numpy-scipy-pandas-and-matplotlib-for-your-data-product-4 EC 8d 7 e 86 ee 4
由于规模庞大,数据科学家经常在多个探索项目中使用 conda 环境,甚至在同一个解决方案的多个生产项目中使用:
https://stack overflow . com/questions/55892572/keeping-The-same-shared-virtualenvs-when-switching-from-pyenv-virtualenv-to-pip
conda 环境可以从任何位置创建、激活和使用。
pipenv 环境被绑定到一个项目存储库。创建之后,Pipenv 将 pip 文件保存到存储库的根目录。已安装的软件包会保存到~/。本地/共享/。virtualenvs /默认情况下,其中 pipenv 通过创建一个新目录并将路径的散列附加到名称(即my_project-a3de50)来确保为每个 repo 创建一个环境。用户必须 cd 到项目存储库的根目录才能激活环境,但是即使您离开该目录,shell 也将保持激活状态。通过将 pip 文件存储在一个单独的目录中,可以跨多个项目共享一个环境。然后,用户必须记住 cd 到存储库来激活和更新环境。
安全性
安装软件包有多安全?
Anaconda 主频道https://anaconda.org/anaconda/由 Anaconda 员工维护,包裹在上传前要经过严格的安全检查。在使用 PyPI 的 pipenv 的情况下,任何人都可以上传任何包,并且在过去已经发现了恶意的包(参见https://www . zdnet . com/article/twelve-malicious-python-libraries-found-and-removed-from-PyPI/)。conda-forge 也是如此,尽管他们正在开发一个流程,在工件上传到存储库之前对其进行验证。
变通办法包括:
- 使用 x 射线https://jfrog.com/xray/等工具进行安全检查
- 仅安装至少一个月前的软件包,以便有足够的时间发现和解决问题
寿命
conda/pipenv 会留下来吗?有多成熟?谁支持?
Pipenv 最初是由流行的请求库的创建者在 2017 年推出的。Pipenv 在 2018 年 11 月至 2020 年 5 月期间没有发布任何新代码,这引起了人们对其未来的一些担忧:
https://medium . com/telnyx-engineering/rip-Pipenv-tryed-too-did-hard-do-what-you-need-with-pip-tools-d 500 EDC 161d 4
https://Chris warrick . com/blog/2018/07/17/17/Pipenv-promises-a-a
Conda/Anaconda 是由 scipy 背后管理 scipy 堆栈的同一个团队在 2012 年创建的。Conda 是一个开源工具,但是 anaconda 存储库是由一个盈利组织 Anaconda Inc .托管的。虽然这意味着 conda/anaconda 不太可能很快消失,但这引起了人们对 Anaconda Inc .可能开始向用户收费的担忧。他们最近改变了他们的条件条款向重度或商业用户收费,包括镜像 anaconda 库。请注意,新的条件条款不适用于康达-福吉渠道。
定制
定制包管理器带来哪些优势?
Conda/Anaconda 是由 python 科学社区创建的,用于解决特定于他们社区的问题,例如非 python 依赖关系:
http://technical discovery . blogspot . com/2013/12/why-I-promote-conda . html
这赋予了它灵活性和动力来创建面向数据科学家的产品。
Conda 可以分发非 Python 构建需求,比如gcc,这极大地简化了在其分发的预编译二进制文件之上构建其他包的过程。康达也可以安装 R 包。Anaconda 开发了一些最流行的数字/科学 Python 库的 MKL 驱动的二进制版本。这些已经被证明能够显著提高性能。虽然 MKL 优化不再生产,Anaconda 仍然可以开发只与 conda 环境兼容的工具。
包装
代码是如何打包的?
conda 和 pipenv 都依赖额外的工具来打包代码。根据代码是否包含非 python 代码和目标平台,两者都依赖于下面的“配方”。
Conda-build 用于创建 Conda 包:【https://docs.conda.io/projects/conda-build/en/latest/T21
PyPA 建议使用 setuptools 构建可以使用 pipenv 安装的轮子。下面是一个很棒的概述:
https://realpython.com/python-wheels/
注意,随着 pyproject.toml 文件和 pep 518:
https://grassfedcode . medium . com/pep-517-and-518-in-plain-English-47208 ca 8 b 7 a 6的引入,python 打包预计未来会有很大变化
多方面的
还有其他需要考虑的因素吗?
- 在安装软件包之前,Conda 解析并打印将要安装的软件包,让用户有机会在经历漫长的安装过程之前继续或重新考虑
- 更改项目目录的名称/路径会破坏 pipenv 环境,并自动创建一个新环境(参见
https://github.com/pypa/pipenv/issues/796) - Conda 不会自动创建/更新 environment.yaml 文件,不像 pipenv 会更新 Pipfile。因此,如果您忘记更新您的 environment.yaml 文件,您的环境和 environment.yaml 文件可能会变得不同步
有用的资源
python 打包生态系统回顾 https://packaging.python.org/overview/
https://towardsdatascience . com/packaging-in-python-tools-and-formats-743 EAD 5 f 39 ee
https://realpython.com/pipenv-guide/
T22 导游
数据科学家 conda/Anaconda 指南 (Whist 面向 Windows 该理论适用于任何操作系统)
https://real python . com/python-Windows-machine-learning-setup/
康达与 pip 的比较
https://jakevdp . github . io/blog/2016/08/25/康达-神话-误解/
https://www.anaconda.com/blog/understanding-conda-and-pip
确保可重现的构建,并且仍然能够快速更改您的依赖关系
https://python speed . com/articles/conda-dependency-management/
打包你的 Python 代码的选项
https://pythonspeed.com/articles/distributing-software/
BigQuery 中的透视和脚本
使用 Google BigQuery 中的脚本构建您自己的 pivot 函数
自从写了这篇文章,谷歌已经发布了官方的枢纽和取消枢纽功能。然而,作为一个使用脚本的例子,你可能仍然会觉得这篇文章很有趣。]
分析数据时的一个常见需求是需要获取存储在单独行中的信息,并将其翻转,以便按列显示。这通常被称为旋转。在电子表格中,这真的很简单:你只需从一个菜单中选择透视表,选择你想看到的字段和一些其他选项,工作就完成了。当您使用数据库时,事情就不那么简单了。
这篇文章通过一个例子提供了使用 Google 的 BigQuery 中的脚本特性来透视数据的分步指南。
源数据
我们将使用 Google 托管的公共数据集中的一个例子。表格" big query-public-data . covid 19 _ open _ data _ eu . COVID 19 _ open _ data "包含自 2020 年初以来按日期分列的 COVID 病例数和死亡数的统计数据。该表包括各国和各国内部各地区每天的大量统计数据,包括死亡、住院和确诊病例的数字。我们的目标是制作一个时间序列图表,比较累计死亡总数最高的 5 个国家的每日死亡人数。
以下查询查找这前 5 名,然后选择这些国家的每日计数。
请注意,基础表包含国家/地区和城市的行。aggregation_level = 0确保我们只包含包含每个国家整体数据的行。
该查询生成了一个如下所示的表。

我们希望透视这些结果,以生成一个每个日期一行,每个国家一列的表格。
概括地说,在 SQL 中这样做的方法如下:
- 按日期和国家名称分组,以确保每对国家和日期最多有一行,并聚合(如 SUM,AVG)要显示的值。
- 对于我们想要透视的字段(即国家名称),创建一组
CASE WHEN语句,将给定国家的值映射到一个新列。因此,对于巴西,我们会有类似于CASE WHEN country_name='Brazil' THEN total_deceased END AS brazil。 - 使用 MAX 或 SUM 对这些语句进行聚合,同时按日期进行分组,以便为每个日期创建一行。
这种方法总是可行的,但是它有两个潜在的严重缺陷。首先,如果您的 pivot 字段包含许多不同的值,那么您将需要写出大量的 SQL 代码。这既费时又产生难以阅读和维护的代码。
更基本的是,为了编写 SQL 代码,您需要提前知道 pivot 字段的所有不同值。在我们的示例中,死亡人数最多的国家的集合可能会随着时间的推移而改变,我们希望避免每次运行分析时都必须手动更新查询代码。同样,我们可能希望查看排名前 10 或 20 位的国家,这样做无需重写查询的大部分内容。
创建动态 SQL 的脚本
BigQuery 中问题的解决方案是脚本,它允许我们动态生成 SQL 查询的文本。我们可以使用一个循环来创建上面第 2 步中的重复 CASE WHEN 语句,这使我们可以灵活地选择 pivot 生成的列的数量和命名。
结合存储过程和用户定义函数,我们拥有一套非常强大的工具来创建可重用的函数,以改善我们在 BigQuery 中处理和分析数据的方式。下面的例子只解决了这个特定的透视问题,但是它相对容易概括来创建一个可重用的透视函数。
我们将根据以下大纲构建我们的脚本:
- 获取前 5 个国家名称并存储在一个数组中;
- 使用循环生成这 5 个国家的 CASE WHEN 语句;
- 将这些语句添加到模板 SQL 查询中并运行。
获取前 5 个国家名称
我们首先运行一个简单的查询来获取排名前 5 的国家。然而,我们将在脚本中完成这项工作,并将查询结果存储在变量countries中。变量需要在脚本的开头声明,因此我们最终得到如下结果:
请注意,该查询的“处理位置”需要设置为 EU。这会产生以下结果。

生成 CASE WHEN 语句
我们脚本的下一部分为每个国家名创建了一个 CASE WHEN 语句。这个循环很简单,但是我们需要小心转义引号并生成有效的字段名。BigQuery 字段名中唯一有效的字符是下划线、a-z、A-Z 和数字 0-9,但不能作为第一个字符。
以下脚本定义了几个可能导致非法字段名称的名称测试示例,以检查正则表达式是否正常工作。
该脚本生成以下字符串(为了便于阅读,我添加了换行符):
SUM(CASE WHEN countries_name='U. S. A.' THEN new_deceased ELSE 0 END) AS U__S__A_,
SUM(CASE WHEN countries_name='bad & name' THEN new_deceased ELSE 0 END) AS bad___name,
SUM(CASE WHEN countries_name='123 something else' THEN new_deceased ELSE 0 END) AS piv_123_something_else,
我们已经创建了有效的字段名称,并且字符串被正确地用单引号括起来。请注意,这不处理任何边缘情况—例如,如果 pivot 字段本身包含一个引号字符,我们的代码将不起作用。
将动态生成的文本添加到模板 SQL
现在,我们可以合并前面两个步骤的结果,并将结果字符串插入到从相关源表中选择数据的查询中:
这给了我们想要的结果——每个日期一行,前 5 个国家一列。然后,很容易将这一小组结果导入到 Google Sheets 来绘制时间序列。

最后的想法
虽然上面的代码是针对这个例子的,但是它很容易推广到其他数据。如果你以前没有接触过脚本,希望这篇文章也可以作为对脚本的介绍。
脚本是 BigQuery 的一个非常强大的扩展。在我作为数据科学家的工作中,这意味着更多的管道处理可以完全在 BigQuery 中完成——而不是在将数据返回到 BQ 表之前必须将数据提取到 Python 中。例如,我最近一直在使用上面讨论的技术将存储在数组中的单词嵌入转换成列,以便在 BigQuery ML 分类模型中使用它们作为特性。也许在另一篇文章中会有更多的介绍。
BigQuery 中的透视
将行转换为列
有时,您可能想要重新格式化表格结果,以便为每个唯一值设置单独的列。这被称为数据透视表——通常,它只是 BI 工具支持的一个显示功能。但是,在 SQL 中创建数据透视表有时会很有帮助。下面是如何在 Google BigQuery 中使用 PIVOT 操作符来创建数据透视表。

图片由皮克斯拜的 Gerd Altmann 提供
Pivot 是做什么的?
透视将行更改为列。例如,假设我们有一个航班和航班延误表,如下所示:

我们希望输出获得每个机场-航空公司对的平均出发延迟,其中航空公司代码是列:

我们会做一个旋转。请注意,如果我们只是想要平均出发延误,并且接受不同行上的平均值,我们可以简单地做:
SELECT
airline,
departure_airport,
AVG(departure_delay)
FROM `bigquery-samples.airline_ontime_data.flights`
GROUP BY 1, 2
并获得:

事实是我们需要它像 2D 桌子一样摆放,需要一个支点。
枢轴语法
BigQuery 中的 Pivot 操作符需要您指定三件事:
- 起输入作用的 from_item 。航班表中的三列(航空公司、出发机场、出发延误)是我们的 from_item。
- 聚合因为输出表的每个单元格都由多个值组成。这里,这是出发延迟的 AVG
- pivot_column ,其值构成输出表中的列的列。不幸的是,这需要是一个常量——您不能在其中放入查询。
让我们看几个例子。
航班表透视
我们想要机场和航空公司的起飞延迟。透视查询是:
SELECT * FROM
(
-- #1 from_item
SELECT
airline,
departure_airport,
departure_delay
FROM `bigquery-samples.airline_ontime_data.flights`
)
PIVOT
(
-- #2 aggregate
AVG(departure_delay) AS avgdelay
-- #3 pivot_column
FOR airline in ('AA', 'KH', 'DL', '9E')
)
请注意这三个部分,将出发延误转换成一个由出发机场和航空公司代码组织的表。因为 pivot_column 需要是一个常量,所以我们必须显式地列出它们。
结果:

使用前缀 avgdelay 的原因是我们将聚合称为 avgdelay。
堆栈溢出枢纽
让我们按年份创建一个带有给定标签的问题数量的枢纽
SELECT
tag,
EXTRACT(YEAR from creation_date) AS year,
COUNT(*) AS n
FROM
`bigquery-public-data.stackoverflow.posts_questions`,
UNNEST(SPLIT(tags, '|')) AS tag
WHERE tags IS NOT null
GROUP BY 1, 2

其中行是标签,列是年份:
SELECT * FROM
(
-- #1 from_item
SELECT
tag,
EXTRACT(YEAR from creation_date) AS year
FROM
`bigquery-public-data.stackoverflow.posts_questions`,
UNNEST(SPLIT(tags, '|')) AS tag
WHERE tags IS NOT null
)
PIVOT
(
-- #2 aggregate
COUNT(*) AS n
-- #3 pivot_column
FOR year in (2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020)
)

动态生成透视列值
如果不能事先列出所有可能的值或透视列,该怎么办?一种解决方案是使用脚本。在这里,我创建了一个名为 airlines 的变量,它以正确的格式打印出字符串(尝试一下):
DECLARE airlines STRING;
SET airlines = (
SELECT
CONCAT('("', STRING_AGG(DISTINCT airline, '", "'), '")'),
FROM `bigquery-samples.airline_ontime_data.flights`
);
然后,我可以使用 EXECUTE IMMEDIATE 运行实际的查询:
**EXECUTE IMMEDIATE format**("""
SELECT * FROM
(
SELECT
airline,
departure_airport,
departure_delay
FROM `bigquery-samples.airline_ontime_data.flights`
)
PIVOT
(
AVG(departure_delay) AS avgdelay
FOR airline in **%s**
)
ORDER BY departure_airport ASC
""", **airlines**);
这导致:

尽情享受吧!
Python 中的数据透视表既快速又简单

照片由 Cookie 在 Unsplash 上的 Pom 拍摄
如何使用 Pandas 库在 Python 中创建和可视化数据透视表。
几年前,我看到有人在推特上说自己是 excel 专家,然后他们的老板让他们做一个数据透视表。根据推文,那个人立即惊慌失措,离开了他们的工作。这条推文很有趣,我可以理解,因为起初,它们看起来很混乱,尤其是在 excel 中。但是不用担心,数据透视表很棒,在 Python 中,它们又快又简单。数据透视表是数据科学中一个方便的工具。任何开始数据科学之旅的人都应该熟悉它们。让我们快速地看一下这个过程,在这个过程结束时,我们将消除你对数据透视表的恐惧。
什么是数据透视表?
数据透视表是一种重新排列或“透视”数据以汇总某些信息的技术。
例如,考虑一个产品销售数据集。其中一列可能是“年龄类别”,如年轻、中年和老年。如果您想查看每个年龄类别的平均销售额,数据透视表将是查看该分析的一个很好的工具。它将为您提供一个新的表格,显示该列中每个类别的平均销售额。
让我们来看一个真实的场景,在这个场景中数据透视表很有用。我们可以用它来分析数据,甚至得出一些结论。
为了说明这个工作流程,让我们采取以下步骤。
- 提出一个问题或假设
- 查找数据
- 使用熊猫创建一个数据透视表
- 用条形图形象化我们的发现
- 根据我们最初的问题或假设得出结论
我们试图回答的问题
假设一群愤怒的父母认为电子游戏又一次太暴力了。这一次,他们说视频游戏开发商制作了太多带有卡通和虚构风格暴力的游戏。《ESRB》认为这些游戏适合儿童,这意味着开发者可以向更广泛的受众出售他们的游戏。对这个群体来说,大多数儿童游戏都有这种卡通暴力风格。让我们看看是否能找到一些数据,看看他们的说法是否有任何有效性。
“谁能为孩子们想想!”—海伦·洛夫乔伊
在我们开始提出问题或假设之前,我们首先需要了解视频游戏的收视率。ESRB 有一个评级系统,我们需要先熟悉它,然后才能继续前进。这些评分在他们的网站上有详细描述,但我也在下表中总结了这些评分。

我们这群愤怒的父母在他们的指控中相当含糊,但让我们对他们的主张采取一些自由。会预测他们认为会被定义为“多数”的游戏的百分比,所以我们可以做一个假设。
由于 EC 的分级明确表示“不含父母认为不合适的内容”,我们可以提出第一个假设。如果任何“EC”游戏都有这种暴力风格,那么分级系统一定有问题。我们可以用下面的假设来表达这一点。
超过 0%被评为“EC”的电子游戏含有某种形式的卡通暴力。
如果这个假设是真的,那么父母是正确的,评级系统有问题。
如果我们的假设是错误的,那么父母是不正确的,评级系统是如何设计的。
我们还需要一个评级为“E”的游戏的假设。《e》是为 6 岁以上的儿童设计的,但它可能包含卡通暴力。假设超过 50%是多数,并使用以下假设。
超过 50%被评为“E”级的电子游戏都有某种形式的卡通暴力。
如果我们的假设是真的,那么父母是正确的,暴力现在正被偷偷带入面向儿童的游戏中。如果我们的假设是错误的,那么我们就可以在晚上睡得很好,因为我们知道分级为“E”的视频游戏中没有太多的暴力。
数据
Kaggle 有一个名为的完美数据集,被“ESRB”评为视频游戏。从描述中,我们可以看到该数据集包含 1895 个游戏,具有 34 个 ESRP 评级描述符和 ESRB 给定评级。每个描述符都以二进制值的形式列出,其中 1 表示描述符是否存在,0 表示描述符不存在。数据集还有几列,但我们只关心评级描述符。让我们下载该数据集,并将其导入 Jupyter 笔记本。使用 Jupyter Notebook 将允许我们导入我们需要的 Python 库,并给我们一个显示结果的好方法。
用熊猫做一张数据透视表
Pandas 库是 Python 中任何类型的数据操作和分析的主要工具。如果你没有安装,请参考这个链接。
首先,我们需要导入 pandas,然后我们可以将 Kaggle 数据集转换成一个带有 pandas.read_csv 的数据帧。
import pandas as pdratings = pd.read_csv("Video_games_esrb_rating.csv")
现在,我们需要决定哪些评级描述符将被视为卡通或动画。我们用以下几个描述词吧:动画 _ 热血,漫画 _ 暴力,轻度 _ 漫画 _ 暴力,轻度 _ 幻想 _ 暴力。我们将把这些列名存储在一个列表中。
cartoon_cols = ["animated_blood", "cartoon_violence", "mild_cartoon_violence", "mild_fantasy_violence"]
我们的 DataFrame 有一个名为 pivot_table 的方法,它将为我们构建数据透视表。在这个例子中,我们将使用两个参数。第一个参数是指数,这将是评级。把索引想象成我们分组所依据的值。第二个参数是值,这将是我们之前制作的列表。还有一个很重要的参数, aggfunc。该参数将决定如何汇总我们的信息。因为这些列都是布尔值,所以查找平均值的缺省值是完美的。这些列的平均值将给出每个描述符中包含 1 的游戏的百分比。
pivot = ratings.pivot_table(index="esrb_rating", values=cartoon_cols)
pivot

厉害!让我们分析一下这个输出告诉我们什么。
- 我们只在 4 个不同的评级中看到这些内容描述符:E(每个人),ET(E+10),T(青少年),M(成熟)。这告诉我们,EC(早期儿童)游戏不包含任何这种卡通风格的暴力。
- 在被评为“E”级的游戏中,没有一款含有血腥或卡通暴力。大约 5%的人有轻度卡通暴力,大约 18.5%的人有轻度幻想暴力。
- 2%的 ET('E+10 ')游戏有动画血,5%有漫画暴力,4%有轻度漫画暴力,约 3%有轻度幻想暴力。
- 成熟游戏在这些类别中很少有暴力,青少年游戏有一些这种类型的暴力,但比“E+10”级游戏少。
用条形图可视化数据透视表
数据透视表在几秒钟内给了我们一些快速信息。如果直观地展示,人们往往更容易理解。我们可以用熊猫和数据透视表做一个条形图。
由于本演练是基于使用 Jupyter 笔记本,我们需要第一行来查看条形图。我们还使用 numpy.arange 作为快捷方式,在 y 轴上制作 10 个刻度,从 0 开始,以 0.1 的增量递增。我们创建的数据透视表实际上是一个数据框架,允许我们在其上调用 plot.bar 方法。如果我们不指定 x 轴上的值,则使用索引。在这种情况下,这是完美的,因为它将使用我们的“ESRB”评级。y 轴将显示每个描述符生成的值。
%matplotlib inline
import numpy as np
pivot.plot.bar(ylim=(0,1),yticks=np.arange(0,1,.1))

卡通暴力不同分级的条形图
相当酷!现在我们可以将数据透视表中的信息可视化。
我们的结论
我们立刻看到我们的第一个假设是错误的。没有一个卡通/动画暴力描述符出现在“EC”评级中。这太棒了!3 岁以下的孩子还是安全的。
第二个假设有点复杂。问题源于视频游戏可以有多个描述符。对于这个例子,因为我们正在尝试看看是否超过 50%的游戏有卡通暴力,我们可以说没有,因为. 052885 + .0185096 是. 237981。这个数字远远小于 0.50,所以我们可以放心地得出这个结论。然而,如果我们的假设说超过 20%的游戏有这种类型的暴力,我们需要看看有多少游戏既有轻度卡通暴力又有轻度幻想暴力。这样,我们可以在进行评估时适当地考虑重叠部分。
这一分析有些令人吃惊。在评级为“E”的游戏中,18.5%有他们所谓的轻度幻想暴力。与其他评级相比,这似乎真的很高。也许父母发现了什么。这可能是需要进一步探索的。
最后的想法
这太简单了!多亏了熊猫,我们能够快速创建一个数据透视表并将其可视化。数据透视表帮助我们看到数据中的某些模式,这些模式可以帮助回答问题、分析数据,甚至帮助我们为机器学习模型设计新功能。
有熊猫的数据透视表
Python 中的 Excel 收藏夹

卢卡斯·布拉塞克在 Unsplash 上的照片
几乎每个处理过任何类型数据的人都可能使用过微软 Excel 的数据透视表功能。这是一个快速、用户友好的工具,允许用户计算、汇总和总结数据集,从而进一步分析模式和趋势。Excel 提供了一个直观的 GUI,使分析师只需点击、拖放数据,就可以轻松应用他们选择的任何聚合函数。这是一个非常棒的工具,可以用来帮助构建商业演示的 Excel 可视化。
Python 的 Pandas 库——专门研究表格数据,类似于 Excel——也有一个 。pivot_table() 作用于同一概念的函数。这是一个强大的方法,带有许多可定制的参数,应该在每个分析师的 Python 工具箱中。理解方法背后的语法需要一些时间,但是一旦你熟悉了,它比 Excel 快得多,也更有效率。
让我们深入一个 NBA 统计数据集,看看熊猫的 。pivot_table() 功能作品!
**#importing required libraries** import pandas as pd
import numpy as np**#reading in the data & selecting needed columns** df = pd.read_csv('../Data/NBA_Player_seasonal.csv', index_col = 0)
df = df[['Player', 'Season', 'Age', 'Tm','WS', 'G','MP','3P','TRB','AST','STL','BLK', 'TOV', 'PF','PTS']]**#displaying first 5 rows of dataframe**
df.head()

熊猫数据框
上面,我们可以看到我们的数据集的预览。这是自 1947 年联盟成立以来每个 NBA 和 ABA 球员的赛季总数。它是按照最高的" WS "预先排序和排名的——获胜份额,一个计算玩家为他们的团队贡献的估计获胜次数的指标。假设我们希望看到这些信息,但是这些信息是由团队汇总的。我们可以这样做:
**#creating a pivot table where team is the index**
df.pivot_table(index = 'Tm')

默认情况下, pivot_table() 引入所有数字列,并使用其平均值来聚合数据。假设我们只关心这些列中的几列,而不是平均值,我们想要看到总数。
**#pivot table with selected columns & summed**
df.pivot_table(df[['PTS', 'AST', 'TRB']], #selected columns
index = 'Tm', #indexed by team
aggfunc = np.sum) #aggregated by sum

现在,我们可以只查看我们感兴趣的统计数据,并查看总数而不是平均值。您可以为 aggfunc 参数传入一个库,允许用户选择应该对每一列执行哪种类型的聚合。
更进一步, pivot_table() 允许用户拥有多个索引来进一步分析数据。下面是同一个数据集的一个例子,但是经过多年的团队统计。
**#multi-index pivot table**
df.pivot_table(df[['PTS', 'AST', 'TRB', 'STL','BLK', 'TOV']],
index = ['Season', 'Tm'],
aggfunc = np.mean).head()

在上面的数据透视表中,我们汇总了缺失的数据,甚至一些基本的统计数据直到 1947 年第一个季度之后才被记录下来。假设我们想在分析中用零填充它们。我们可以通过简单地添加数据透视表参数来实现这一点。
**#filling in NaN values w/ 0's**
df.pivot_table(df[['PTS', 'AST', 'TRB', 'STL','BLK', 'TOV']],
index = ['Season', 'Tm'],
aggfunc = np.mean,
fill_value = 0).head()

该功能还使用户能够用更少的代码快速可视化他们的数据,而不是直接从原始数据集编码。作为一个例子,让我们通过一个数据透视表线图来绘制联盟多年来的季节性平均水平。
**#plotting pivot table**
df.pivot_table(df[['PTS', 'AST', 'TRB', 'STL','BLK', 'TOV']],
index = ['Season',],
aggfunc = np.mean,
fill_value = 0).plot()

这不是最漂亮的视觉效果,但是它很快并且提供了一些重要的信息。你可以很快识别出 1999 年的停摆赛季,与通常的 82 场比赛相比,这个赛季只打了 50 场比赛。
这些是几个 pivot_table() 参数,您可以使用它们来定制/重塑您的数据集。还有其他参数,如页边距,它将列和行的总和相加。另一个是列,它提供了一种额外的方式来分割你的数据集。最后,每个数据透视表输出一个新的数据框架,这允许您执行任何标准的数据框架功能/方法,例如根据特定标准进行过滤,例如,如果我们只想查看波士顿凯尔特人队的数据。
希望这个快速教程对理解熊猫的透视表功能有多强大有帮助!如有任何问题或留下任何反馈,请随时联系我们。
参考
- https://pandas . pydata . org/docs/reference/API/pandas . pivot _ table . html
- https://support . Microsoft . com/en-us/office/create-a-pivot table-to-analyze-worksheet-data-a9a 84538-bfe 9-40 a9-a8e 9-f 99134456576
旋转熊猫数据框架
两行代码节省了我几个小时的工作

介绍
作为一名数据分析师或开发人员,最有趣的时刻之一是遇到你大三时写的代码。这个时刻最近发生了,当一个利益相关者要求更新我第一次加入时提供的摘录时。要求是分析现有工作人员每个日历年的临时工作时间。令人尴尬的是,我天真的方法是在主 select 语句中使用子查询创建列来分隔年份。虽然这种方法有效,但是查询的整体性能非常糟糕。
更好的方法
我最初的方法是合计 1 月 1 日到 12 月 31 日之间的临时工作时间,然后以年份命名该列。以这种方式构建查询会产生技术债务,因为每年我都需要创建一个新列来捕获数据。
输入熊猫;Pandas 是用 Python 编写的强大开源库。它提供了易于使用的现成功能来接收和分析关系数据。对于那些刚刚接触数据科学或者还没有接触过 Python Pandas 的人,我们建议首先从 Pandas 系列& DataFrame Explained 或 Python Pandas 迭代 DataFrame 开始。这两篇文章都将为您提供安装说明和今天文章的背景知识。
透视数据
Pandas 提供了使用其内置的.pivot()函数来解决上述问题的能力。我们在下面为您提供了熊猫技术文档的一个片段。
上面的 Python 片段显示了 Pandas 内置 pivot 函数的语法。
参数index允许您从现有的数据帧中传递列名,为结果数据帧创建一个新的索引。使用columns参数传递列,这将决定最终数据帧中的列。您可以使用values参数来指定哪些值将填充新创建的 DataFrame 列。
结果
我们在下面包含了一个 Python 片段,以创建一个类似于我们最初请求的数据的数据帧。执行下面的代码片段后,您将创建一个包含虚拟数据的四乘五的数据帧。如果您在运行以下脚本时遇到问题,请参考 Pandas 系列& DataFrame 解释来仔细检查您的安装。
上面的 Python 片段将创建一个包含虚拟数据的 DataFrame。

上面的截图显示了执行上面的 Python 代码片段的结果。
现在你已经创建了数据帧,我们将在year和hours列上调用 Pandas .pivot()函数。我们在下面提供了一个片段,您可以使用它来实现这一点。
上面的 Python 片段将基于年和小时列透视数据帧。

上面的屏幕截图显示了旋转原始数据框架的结果。
从上面的截图可以看出熊猫.pivot()功能的强大。.pivot()函数获取列的唯一值,并将它们作为单独的列扩展到新的数据框架中。
摘要
Pandas .pivot()函数是一个非常强大的工具,当您需要根据列值重塑数据框架时,可以使用它。这种方法具有时效性,不需要在久而久之增加新年栏目。
感谢您花时间阅读我们的故事,我们希望您发现它有价值!
将 ML 应用引向成功
3 精益验证,以确保你在正确的道路上

是什么让人工智能应用如此特别
AI 和一般开发的主要区别之一是所需的评估范围。通过了解做什么和如何做,验证需求并保持简单,你几乎是安全的。但在人工智能应用上,你可以拥有最好的研究、顶级开发人员和精英产品团队,但仍会面临失败。原因是更高的不确定性在许多方面上升。早在 2015 年,斯卡利等人通过描述他们的隐含 技术债务 ,讨论了 ML 应用的独特性;虽然大多数人认为 ML 代码是他们的主要成分,但事实是这只是人工智能应用程序所需开发的一个小摩擦。使用人工智能成功三角的重要性、可行性和可用性我们可以格式化一组验证,以确保在我们成功的道路上避免任何隐藏的警告。

ML 系统中 ML 代码的一小部分,来自 Sculley 等人的论文
使用成功三角评估不确定性
人工智能成功三角形通过突出关键因素来帮助绘制可能的风险;第一个也是最直接的一个是重要性(确保开发具有高价值并且对业务很重要),其次是可行性(确保目标可以实现),最后是可用性(确保它可以被很好地利用)。一个直观的例子是试图猜测彩票号码——预测下一个号码非常重要(赢得一等奖),很容易利用(只需提交这些号码),但不太可行(除非你是魔术师)。预测之前的数字是可行的(只需在网上搜索),但极难利用(除非你能回到过去)。评估在应用程序生命周期的 3 个主要阶段完成;在之前(构思)在期间(开发)在之后(面向客户)。每一个都有不同的风险因素,但总的来说都可以用相同的成功三角来描绘。成功的人工智能应用程序必须准备好快速旋转;无论是因为数据稀缺、业务改变了想法,还是算法似乎没有收敛。我们应该通过在正确的时间问正确的问题来不断确保我们在正确的道路上。下面是验证内容、时间和方式的详细信息。

人工智能成功三角
之前——应用构思阶段
初始阶段也是最容易关注的阶段(没有花费精力),因此它是大多数验证应该完成的地方。假设我们的任务是预测客户流失的可能性。重要性(要节省的钱)是很明显的。对许多公司来说,这足以让他们开始研究这个话题。对我们来说,看着成功三角,我们应该再问两个问题——如果客户有流失的危险,你将如何防止?(可用性)由于客户流失预测应用大多需要整体数据视图,因此“客户数据的可访问性如何?如何才能做到这一点?”(可行性)。可用性评估可能会揭示这样一个事实,即使我们设法开发了应用程序,也不确定它能够实现我们的计划。可行性评估可能揭示出隐含的更高成本。考虑这些因素可能会说服我们转向。即使没有,它也将使我们能够提前修复它(在某些情况下,确保应用程序更具可操作性就足够了)。因为这是最便宜的支点,所以在决定继续下一阶段——开始开发应用程序——之前,我们应该花很多时间来确保验证已经设置好。
在应用开发阶段
一旦我们开始开发应用程序,枢轴价格随着时间的推移而增加(更多的工作被浪费)。因此,我们应该预先确定关键的内部里程碑和确认点。看看成功三角,重要性和可用性已经在构思阶段得到验证,因此风险较小。可行性是我们应该在数据和技术可行性上花费大部分验证时间的地方。由于多种因素,数据可能不可行。例如,我们可能会发现一个重要的历史数据片段没有被收集,标签不可用,或者推断所需的数据仅在稍后阶段可用。还有很多技术问题会影响 app 的可行性。例如,我们可能会发现我们所依赖的论文在现实生活中不起作用,它需要的资源太多,或者我们包含了一个在 edge 设备上不受支持的 python 库。只要提前发现这些问题,很多问题都很容易解决。这就是为什么设置内部确认点如此重要。此外,在许多情况下,一旦有了一个最小的 MVP,向前跳并重新验证我们的可用性假设是有意义的——它将如何被服务,它是否适合我们拥有的软件生态系统,等等。问这些问题可能需要回到上一阶段,以更好地定义我们试图解决的需求。请记住,在早期阶段发现应用程序与 edge 的使用不兼容,与在大型发布活动中发现它是两码事。在素描室失败比在舞台上失败要容易得多。

发展阶段三角形
之后——生产阶段的应用程序
一旦我们开始向客户部署我们的应用程序,其重要性和可行性就不那么麻烦了(当我们设法开发它时),可用性成为监控的主要障碍。通用 pivot 相关性较小(重新开发 app 会超级贵)。更相关的是 UX/用户界面支点;如何更好地吸引客户,提升应用价值等。可用性可以分为两种主要的度量方式——直接和间接。回到流失预测的例子,直接可用性测量主要的价值主张(流失客户的数量应该会减少),间接可用性测量次要的动机(如一般使用和利用率)。重要的是,要避免在糟糕的直接评估反馈后得出快速结论的诱惑(被搅动的客户数量是相同的- >失败)。在许多情况下,成功需要一些用户指导,因此对间接测量的深入分析也是至关重要的。让我们假设我们决定添加一个反馈部分,以便更好地衡量我们的流失预测应用程序。问题是我们选择的措辞。在询问警报是否有用时得到负面反馈可能是因为错误,但也可能是因为太迟了(这是完全不同的反馈)。一个简单的解决方案是在反馈表单上添加一个自由文本区域。我们不仅应该监控和收集反馈,还应该以一种能让我们从中获得可操作的见解的方式来做。确保我们做出的决定是基于真实的数据。

生产阶段三角形
最后——保持简单
这听起来像是一句咒语,但要记住的最重要的任务是保持简单。看着花哨的架构、庞大的数据集和闪亮的新库,很容易匆忙投入开发,但后来发现这是一个错误。在评估上多花一分钟可以避免以后的转变,从而节省大量的时间。
皮克斯:一个数据故事
从 pixarfilms R 包中探索关于 Pixar 电影的数据

米凯尔·弗里沃尔德在 Unsplash 上拍摄的照片
皮克斯和美好的回忆
你终于 18 岁了。你正准备去上大学。你甚至可能离开父母的家,搬到另一个城市。也许你会带一些让你想起童年记忆的物件。或者谁知道,也许你是这个新的独立存在的父亲或母亲。你为你的儿子或女儿将要走的路而心痛。也许你正在玩具总动员 3 中实现自己。你是安迪或者你是安迪的父母。
无论你是安迪的父母之一还是安迪本人,你都有可能在最近几年看过皮克斯的大部分电影。你一定被海底总动员的绝望所苦恼,从汽车中吸取了一些教训,被瓦力的末日般的未来所惊吓,等等。
在这篇课文中,任务是让你不感到孤独。意识到电影屏幕上讲述的冒险是共同的情感,深受公众和评论家的喜爱。让我们用皮克斯最具代表性的系列电影《玩具总动员》的颜色,通过数据和图形来看看这一点。带上你的氦气球,让我们一起踏上这段旅程。
预算,票房…和评论家
我不是这个故事中的安迪。我是他的父亲。我儿子和我以前看过皮克斯的所有电影。我记得的第一个是车。我们看了几十遍了。也许是因为不同的原因。就我而言,我过去喜欢看装饰艺术和 20 世纪 50 年代和 60 年代的精神,这在电影中很容易识别。
如果我们的首选是汽车,毫无疑问这不是一个共同的观点。在下面的第一张图中,排名显示了其他选择。

评论家评分。作者图片
如上图所示,我最喜欢的汽车在列表的底部。但是,没关系。也许影评人没看懂这部电影。
《玩具总动员》 (1995)是皮克斯的第一部电影。上图是被Roten tomatos评价最好,被 Metacritic 列入前三。平均分排名第二的是皮克斯的第二部电影《一只虫子的生活》(1998)。因为似乎第一部电影有时比最近的电影评价更好,也许看电影的时间线是个好主意,但现在衡量与它们相关的预算和票房。

关于票房和预算信息的时间线。作者图片
正如你在上面看到的,当我们比较与金钱相关的变量时,《玩具总动员》告诉我们一个与其他电影不同的故事。它的预算很少,票房也远远达不到皮克斯最重要的大片所达到的良好表现。也许与评论家的观点相比,公众对这部电影有不同的看法。没那么快。我不是专家,但也许这种现象可以用 25 年前制作一部动画所隐含的不同成本来解释,甚至整个时期的通货膨胀也会影响最终结果。我邀请你来做这项研究。
当看时间线时,令人瞩目的是《超人特工队 2》、《玩具总动员 3》和《玩具总动员 4》的表现。这些电影是皮克斯的主要利润来源。不幸的是,对于灵魂就不能这么说了。我还没有看过,但我的大多数朋友都告诉我这是一部很棒的电影。疫情也许对这些结果有很大的影响。
当我们谈论预算和票房时,出现的一个问题是:当我们看到这些变量的散点图时,我们能找出任何关系吗?这个问题用下图来回答。

预算 x 票房。作者图片
我们的图表中显示了四象限法则。我们看到一些电影两个变量的值都很小。这些是玩具总动员、汽车和玩具总动员 2* 的案例。我们还可以在像《超人总动员》和《海底总动员》这样的电影中看到小预算和高票房。就皮克斯的表现而言,也有失败类型的代表:灵魂,汽车总动员 3 和好恐龙。这些是大预算小票房的电影。最后,我们可以注意到以超人总动员 2、玩具总动员 4、玩具总动员 3 和寻找多莉为代表的伟大大片。在这些情况下,这就是我们从常识中所期待的:巨额预算与巨额收入相关。*
另一个值得尝试的关系是预算和评论家评分的平均值之间的关系。和我一起分析。

预算 x 评论家等级。作者图片
在上图中,我们注意到不存在评级低的低预算象限。要分析的第一个象限是与具有高评论家评级的低预算电影相关联的象限。在这种情况下,《玩具总动员》是目前为止最好的代表。当我们将其与之前分析的图表进行比较时,这一发现完全改变了这部电影的画面。另一方面,我们看到一些预算大、成绩差的电影。对车和车 3* 的备注。最后,我们很容易注意到人们对常识的期望高度集中:高平均分的大预算。这里的亮点是玩具总动员 3 和玩具总动员 4 。*
请注意,我们在上图中看到的票房冠军《超人前传 2》属于常识类。然而,事实证明,它的平均成绩与这部电影所获得的观众成功并不相符。这引发了另一个问题:观众的成功与评论家的成功有关吗?给你的气球充上氦气,让我们在下一张图停下来继续旅程。

x 级票房。作者图片
在我们最后的分析中,似乎很清楚的是,批评的评价并不总是与公众的评价相一致,这里所代表的是电影所取得的票房。我们看到有魂和玩具总动员 2* 等批评成功的案例,但收入较低。玩具总动员在这个象限,但是正如我们之前指出的,很难根据电影的收入来评价它是大众的相对失败,因为这是二十多年来的数据。另一方面,你会发现其他大多数电影都有巧合,尤其是在最佳评论电影和票房表现的象限中。对于这些案例,玩具总动员 3、玩具总动员 4、海底总动员似乎是最好的例子。*
继续游
所以我们已经到了课文的结尾。我们已经看到,皮克斯讲述的故事是世界各地的儿童、青少年、年轻人和他们的父母所共有的一系列记忆的一部分。甚至是那些在报纸上写自己观点的讨厌的人(嘿,这是个笑话)。
对于我们这些注意到孩子生命中不断变化的阶段的父母来说,他们现在将会用其他的可能性和经历来面对这个世界,记住《海底总动员》的海报是值得的。
这个世界将充满鲨鱼、水母,还有 Nemos、Dorys、鲸鱼和海龟。在一天结束时,最重要的似乎是那句名言:继续游泳。
信用
因为没有别的办法,我们必须留下电影的演职员表。这里的选项是使用数据可视化资源。我在下面展示了在电影结尾出现最多的 23 位专业人士。
非常感谢斯坦顿、多科特、拉塞特和他们一伙。愿更多的动画留在我们的情感记忆中。继续游。

最终学分。图片作者。
我也要感谢埃里克·梁,皮克斯电影公司包的作者。对于像这样的数据探索来说,这无疑是一个简单而伟大的想法。
代码和数据
本要点中有代码和数据。
你可以在推特上找到我。
PixelCNN 的盲点
思想和理论
PixelCNN 的局限性以及如何修复它!
由沃尔特·雨果·洛佩兹·皮纳亚、佩德罗·f·达·科斯塔和杰西卡·达弗伦编剧
大家好!今天,我们将继续关于自回归模型的系列,我们将重点关注 PixelCNNs 的最大限制之一(即盲点)以及如何改进以解决它。
总结
- 自回归模型— PixelCNN
- 多通道建模数据
- PixelCNN 的盲点及其修复方法—门控 PixelCNN
- 使用门控像素 CNN 的条件生成
- 带裁剪卷积的门控像素 CNN
- 提高性能— PixelCNN++
- 缩短采样时间—快速 PixelCNN++
- 使用注意机制——pixels nail
- 生成多样化的高保真图像——VQ-VAE 2
对于每个主题,代码都可以在这个库中找到。
介绍
在前两篇文章中,我们介绍了生成模型,PixelCNN 背后的概念,并研究了彩色 pixel CNN 的工作原理。回想一下,PixelCNNs 是一种生成模型,它学习像素的概率分布,这意味着未来像素的强度将由先前的像素决定。在这个 blogpost 系列中,我们实现了两个 PixelCNNs,并注意到性能并不出色。在之前的帖子中,我们提到了改善模型性能的方法之一是修复盲点问题。因此,在本帖中,我们将介绍盲点的概念,讨论像素 CNN 是如何受到影响的,并提出一种解决方案——门控像素 CNN 。我们开始吧!
盲点
正如你从以前的帖子中回忆的那样,PixelCNN 学习图像中所有像素的条件分布,并使用这些信息进行预测。还记得 PixelCNNs 将从左到右和从上到下学习像素的分布。因此,为了确保“未来”像素(即预测像素右侧或下方的像素)不能用于给定像素的预测,通常使用掩码(图 1A)。如图 1A 所示,掩码将当前预测像素“之后”的像素清零,这对应于掩码中心的像素。然而,由于这种选择,不是所有的“过去的”像素将被用于计算新点,并且信息的丢失将导致盲点的产生。
为了理解盲点问题,让我们看看图 1B。在图 1B 中,暗粉色的点( m )是我们想要预测的像素,因为它位于过滤器的中心。因为我们用的是 3x3 的蒙版(1A。),像素 m 依赖于 l,g,h,i. 另一方面,那些像素依赖于之前的像素。例如,像素 g 依赖于 f,a,b,c,而像素 i 依赖于 h,c,d,e. 从图 1B 中,我们还可以看到,尽管出现在像素 m 之前,像素 j 在计算 m 的预测时从未被考虑。类似地,如果我们想要对 q、 j、n、o 进行预测,则从不考虑(图 1C。).并非所有先前的像素都会影响预测的事实被称为盲点问题。

图 1: A .举例说明了一个 3×3 掩码的例子,该掩码可用于确保只有“过去的”像素用于计算预测。只有绿色的像素将用于计算蒙版中心的像素。b .如何在 5x 5 图像上使用掩模来预测像素 m 的示例。只有像素 l、g、h、I 将用于计算暗粉色(m)的像素。但是,我们需要记住,l,g,h,I 也依赖于之前的像素(a,b,c,d,e,f,k;显示为淡紫色)。从 B 中的第三张图可以看出,j(橙色)将不会被使用,从而形成一个盲点。c .如果我们将遮罩的中心下移,现在预测像素 q,我们可以看到橙色的像素永远不会被考虑在内(图片由作者提供)。
我们将首先从 PixelCNNs 的实现以及盲点如何影响结果开始。下面的代码片段显示了使用 Tensorflow 2.0 框架从 PixelCNN 实现遮罩。
查看原始 PixelCNN 的感受野(在图 2 中用黄色标记),我们可以看到盲点以及它如何在不同层上传播。在这篇博文的第二部分,我们将描述 PixelCNN 的下一个版本,门控 PixelCNN,它引入了一种新的机制来避免盲点的产生。

图 PixelCNN 上盲点的演变(图片由作者提供)。
门控像素 CNN
在前两篇博文中,我们介绍了 PixelCNN 然而,这种模型性能较低,并且存在我们上面介绍的盲点问题。
为了解决这些问题, van den Oord 等人(2016) 引入了门控 PixelCNN。门控像素 CNN 在两个主要方面不同于像素 CNN:
- 它解决了盲点问题
- 它使用门控卷积图层提高了模型的性能
1.门控像素 CNN 如何解决盲点问题
这个新模型通过将卷积分成两部分:垂直和水平叠加,解决了盲点问题。让我们看看垂直和水平堆栈是如何工作的。
纵横书库

图 3:垂直堆栈(绿色)和水平堆栈(蓝色-作者提供的图片)。
在垂直堆栈中,目标是处理当前行之前所有行的上下文信息。用于确保使用所有先前的信息并且保持因果关系(即,当前预测的像素不应该知道它右边的信息)的技巧是将掩模的中心分别向上移动一行到被预测的像素。如图 3 所示,虽然垂直掩码的中心是浅绿色像素( m ,但是由垂直堆栈收集的信息将不会用于预测它,而是用于预测它下面的行中的像素( r )。
然而,单独使用垂直堆栈会在黑色预测像素( m )的左侧产生盲点。为了避免这种情况,由垂直堆栈收集的信息与来自水平堆栈的信息相结合( p-q 在图 3 中用蓝色表示),水平堆栈预测预测像素左侧的所有像素( m )。水平和垂直堆栈之间的组合解决了两个问题:(1)不使用预测像素右边的信息,(2)因为我们作为一个块来考虑,所以我们不再有盲点。
在 van den Oord et al. (2016) 中,实现了垂直叠加,使得每个卷积的感受野具有 2×3 格式。我们通过使用一个 3×3 卷积来实现这一点,最后一行被屏蔽掉。在水平堆栈中,卷积层将预测值与来自被分析像素的当前行的数据相关联。这可以使用 1×3 卷积来实现,其中我们屏蔽未来像素以保证自回归模型的因果条件。与 PixelCNN 类似,我们实现了一个 A 型遮罩(用于第一层)和一个 B 型遮罩(用于后续层)。
我们修改了以前的掩蔽卷积层,以便能够实现这些新配置。下面的代码片段显示了使用 Tensorflow 2.0 框架实现的掩码。
通过在网络上添加这两个堆栈的特征图,我们获得了一个具有一致感受野的自回归模型,并且不会产生盲点(图 4)。

图 4:门控 PixelCNN 感受野概述。我们注意到,使用垂直和水平堆栈的组合,我们能够避免在 PixelCNNs 的初始版本中观察到的盲点问题(图 3 —作者提供的图片)。
2.门控激活单元(或门控块)
从普通像素 CNN 到门控 CNN 的第二个主要改进是引入了门控块和乘法单元(以 LSTM 门的形式)。因此,不像原始的 pixelCNN 那样在屏蔽卷积之间使用校正线性单元(ReLUs );门控像素 CNN 使用门控激活单元来模拟特征之间更复杂的相互作用。这种门控激活单元使用 sigmoid(作为遗忘门)和 tanh(作为实际激活)。在的原始论文中,作者提出这可能是 PixelRNN(使用 LSTM 的)优于 PixelCNN 的一个原因,因为他们能够通过递归的方式更好地捕捉过去的像素——他们可以记住过去的信息。因此,门控 PixelCNN 使用了以下内容:

σ是 sigmoid 非线性度, k 是层数,⊙是逐元素乘积,∵是卷积算子, W 是来自前一层的权重。让我们更详细地看一下 PixelCNN 中的一个层。
门控像素 CNN 中的单层块
堆栈和门是门控 PixelCNN 的基本模块(图 5)。但是它们是如何连接的,信息将如何处理?我们将把它分成 4 个处理步骤,我们将在下面的会议中讨论。

图 5:门控 PixelCNN 架构概述(图片来自原始论文)。颜色代表不同的操作(即,绿色:卷积;红色:元素间的乘法和加法;蓝色:与权重卷积
1。计算垂直叠加特征图

(图片改编自原纸)
作为第一步,来自垂直叠加的输入由我们的 3×3 卷积层和垂直掩模处理。然后,得到的特征图通过门控激活单元,并被输入到下一个块的垂直堆栈中。
2。将垂直地图送入水平堆栈

(图片改编自原图)
对于我们的自回归模型,需要结合纵向和横向堆栈的信息。为此,在每个块中,垂直堆栈也被用作水平层的输入之一。由于垂直堆栈的每个卷积步长的中心对应于被分析的像素,所以我们不能仅仅添加垂直信息。这将打破自回归模型的因果关系条件,因为它将允许未来像素的信息用于预测水平堆栈中的值。这是图 8A 中第二幅图的情况,其中黑色像素右侧(或未来)的像素用于预测它。因此,在将垂直信息输入水平堆栈之前,我们使用填充和裁剪将其下移(图 8B)。).通过对图像进行零填充并裁剪图像的底部,我们可以确保垂直和水平堆栈之间的因果关系得以保持。我们将在以后的文章中深入探讨关于裁剪如何工作的更多细节,所以如果细节不完全清楚,请不要担心。

图 8:如何确保像素之间的因果关系得以保留(图片由作者提供)
3。计算水平特征图

(图片改编自原文)
在这一步中,我们处理水平卷积层的特征图。事实上,第一步包括对从垂直到水平卷积层输出的特征图求和。该组合的输出具有理想的接收格式,其考虑了所有先前像素的信息。最后,特征图通过门控激活单元。
4。计算水平堆栈上的剩余连接T3

(图片改编自原创论文)
在这最后一步中,如果块不是网络的第一个,剩余连接将组合前一步的输出(通过 1x1 卷积处理),然后馈入下一个块的水平堆栈。如果是网络的第一块,那么就没有剩余连接,跳过这一步。
使用 Tensorflow 2。我们实现了上述方案,如下所示:
总之,使用门控块,我们解决了感受野上的盲点并改善了模型性能。
体系结构
在 Oord et al. 2016 中,PixelCNN 使用了以下架构:第一层是带有 7x7 滤波器的掩蔽卷积(A 型)。然后,使用 15 个残差块。每个模块使用 3×3 层卷积层和标准 1×1 卷积层的组合来处理数据。在每个卷积层之间,有一个非线性 ReLU。最后,剩余块还包括剩余连接。
在下一篇文章中,我们将看看如何进一步改善门控像素 CNN 的性能。我们还将介绍有条件的 PixelCNN,敬请期待!
结果
我们训练了 PixelCNN 和门控 PixelCNN,并比较了下面的结果。

图 11:PixelCNN 和门控 pixel CNN 的比较(图片由作者提供)
当比较 PixelCNN 和门控 PixelCNN 的 MNIST 预测(图 11)时,我们没有观察到 MNIST 数据集有很大的改善。一些以前被正确预测的数字现在被错误地预测了。但是,这并不意味着不应考虑 PixelCNNs。在下一篇博文中,我们将讨论门控 PixelCNNs 和 PixelCNN++以及它们将如何提高模型的性能。敬请关注!
参考
https://openreview.net/pdf?id=Hyvw0L9el
https://www . slide share . net/suga 93/conditional-image-generation-with-pixel CNN-decoders
https://sergeiturukin.com/2017/02/24/gated-pixelcnn.html
像素并非生而平等
思想和理论
利用低秩结构来寻找图像中最重要的像素。

在 32 256 个像素中,只有不到 3%的像素(此处为红色)与从扩展的耶鲁 B 数据库中重建人脸相关。图片由作者提供。
图像空间极其广阔。想一想。如果考虑一个简单的 8 乘 8 像素的网格,其颜色使用单个位(即黑色或白色)进行编码,则可以构建 18 446 744 073 709 551 616 个图像。如果你现在使用 8 位对灰度进行编码,或者考虑百万像素的图像,这个数字就超出了天文数字。比已知宇宙中的原子数量还要多!图像空间是如此之大,以至于你能想到的任何图像都存在于其中。这包括你此刻盯着屏幕的画面,或者你(未来)的孩子骑着自行车的画面。还有一个你打扮成拿破仑的形象,骑着恐龙和维京人战斗,而背景是一群熊在杂耍。这是多么广阔的图像空间。图像空间是被模仿的维度诅咒。然而,我们经常在这个巨大的数学空间中执行极其复杂的运算。这怎么可能呢?利用数据中的低秩结构就是这样一种可能性。
工程和工业中使用的数据集往往是高度结构化和标准化的。例如,用于制成品异常检测的图像必须遵循特定的规范。不同角度的高分辨率图像是在物体坐在离相机特定距离处并处于预定的闪电条件下拍摄的。然而,有用的信息存在于一个比纯粹的图像尺寸更小的维度空间中。数据集的这种高度标准化的性质通常意味着某种形式的底层低秩结构,这种结构可以为我们所用。在本帖中,我们将看到如何使用简单的线性代数技术来识别标准化图像中最相关的像素,并仅使用这些有限的测量值来重建人脸图像。这是受到华盛顿大学合作者最近一系列文章的启发。
扩展耶鲁人脸数据库 B
扩展的耶鲁人脸数据库 B 是一个经典的数据集,用来说明图像处理算法。它包含 28 个人类对象在 9 种姿势和 64 种照明条件下的 16 128 幅图像。为了简单起见,我们将使用它的裁剪版本。图像的总数约为 2000 张,对于这篇介绍性文章来说,这是一个更容易处理的数据集。这些面的随机子集如下所示。

来自裁剪的扩展耶鲁人脸数据集 b 的随机图像。
在这篇文章的其余部分,我们将尝试确定哪个像素子集对图像重建最重要。
稀疏传感器放置
我们试图解决的问题是稀疏传感器放置,即您应该在哪里放置有限数量的传感器(或者在这种情况下测量哪个像素)以最佳地估计您的系统状态(或者重建您的图像)。数学上,我们有以下等式

其中 x ∊ ℝⁿ是我们想要测量的高维对象(在我们的例子中是图像的矢量化版本,n = 192 × 168 ),而 C 是我们想要设计的 m× n 未知测量矩阵。最后,矢量 y ∊ ℝᵐ是我们想要从中推断出 x 的有限测量值。如果没有关于数据的潜在结构的额外假设,这是一个大规模欠定问题(即未知数比方程多),允许有无限多的解。关于数据的不同假设产生了这个问题的解决方案。一个这样的例子,尽管不是本文的主题,是[压缩传感](https://en.wikipedia.org/wiki/Compressed_sensing#:~:text=Compressed sensing (also known as,solutions to underdetermined linear systems.),其中假设 x 在适当的基础上表达是稀疏的。这里,我们将改为假设 x 可以在低秩基础上表示,即

用ψ表示 n × r 低秩基(用 r ≪ n), a ∊ ℝʳ表示 x 在这个特殊基中的表示, η 是 x 不在ψ跨度内的(希望是小的)分量。
将这种扩展引入我们的测量方程,得到

其中 ε 为 η 引起的测量噪声。这可以改写为

其中𝚯=cψ是我们的低维未知测量矩阵。该系统的解决方案由下式给出

因此,我们的目标是设计矩阵 C ,使得𝚯 和尽可能处于良好状态,以避免测量噪声爆炸。
最简单的形式是,这是一个很难处理的组合问题,因为必须测试传感器的每一种可能的组合。已经提出了不同的算法来将这个问题简化成一个更简单的问题。讨论它们的数学细节超出了这篇文章的范围。可以说,如果将自己限制为逐点测量(即图像情况下的单像素测量),可以很容易地从具有列旋转的 QR 分解中获得准最佳解决方案

这里 P 是一个矩阵,迭代地选择ψᵀ的列,使得 R 的对角元素不减少(在数量上)。如果通过提取与每个枢轴 pᵢ相关联的ψ的行来构造低维测量矩阵𝚯,这个简单的过程确保𝚯的行列式在每次迭代中最大化。因此,这个测量矩阵是条件良好的。更多详情,感兴趣的读者可参考原创研究文章。
扩展耶鲁人脸数据集 B 上的插图

平均脸来自扩展的耶鲁人脸数据库。
现在让我们在数据集上演示这种方法。随机选择 75%的图像作为训练集的一部分,剩余的 25%形成测试集。每个图像被转换成一个矢量,并堆叠在数据矩阵 X 中(即 X 的每一列对应一个不同的图像)。在分析之前,减去平均面(显示在左边)。
降维和特征脸
我们的图像是高度标准化的,因此意味着潜在的低等级结构的存在。解开这种结构的标准方法是数据矩阵的奇异值分解 X

在统计界,奇异值分解等价于主成分分析(直到一个常数标度)。它旨在提供数据矩阵 X 的近似低秩分解。
在人脸图像的特定上下文中, U 的列被称为特征脸。主要的如下图所示。

前 36 个特征面(从左到右和从上到下)。图片由作者提供。
这些特征脸捕捉人脸的各种特征和不同的照明条件。在剩下的部分,我们要考虑的每张图像都将被转换成这些特征脸的线性组合。但是我们需要多少这样的特征脸呢?

核范数是所考虑的特征面基秩的函数。图片由作者提供。
左边的图显示了作为其秩的函数的奇异值 X (近似的核范数)的累积分布。仅使用几百个本征面就可以获得合理的近似。此后我们将考虑其中的 500 个。因此,在这种特定基础上表示图像的低维向量 a 将是 500 维的,远小于图像中像素数量所暗示的 32 256 维。
下图使用适当的线性组合,将测试集中的一些图像与其近似值进行了比较。

顶行:测试集中的面。底行:使用前 500 个特征脸(加上平均脸)重建的相应图像。图片由作者提供。
可以观察到极好的一致性,尽管丢失了次要的细节。然而,必须考虑原始图像的所有像素,并将其投影到特征面的跨度上。假设我们的基础是 500 维的,那么相同的任务实际上最多需要 500 个像素,这还不到原始图像大小的 2%。现在让我们找出最相关的!
稀疏传感器放置
给定我们的低秩基础的最有信息量的像素可以从其具有列旋转的 QR 分解来计算。这个标准的数值过程在所有主要的科学编程语言中都有实现。下面的两段代码说明了如何使用 Python 和 Julia 来计算它。
传感器放置算法的 Python 实现。告诉过你这非常简单。
传感器放置算法的 Julia 实现。就像在 Python 中一样简单。

叠加在平均面上的 500 个最相关像素的位置。图片由作者提供。
枢轴的线性指数可以容易地转换成图像中相应像素的坐标。这些位置显示在左侧,叠加在平均面上。该算法识别的最具信息性的区域是眼睛、鼻子和嘴。从生物学的角度来看,这些区域对应于我们试图识别某人时目光倾向于聚焦的地方,这是一个令人惊讶的结果,因为我们的算法和大脑如何工作之间不存在明显的联系(至少就我所知)。
从少量像素测量值重建图像
识别出最相关的像素后,低维测量矩阵

可以简单地通过提取ψ矩阵中的相应行来构建,产生以下线性方程组

可以使用两种不同的策略从这些稀疏测量值 y 中推断出低维表示 a 。在没有额外信息的情况下,最好的办法就是简单地解这个方程组,即

或者,数据矩阵 X 的 SVD 为我们提供的不仅仅是本征脸。它还提供了关于特征面基中每个坐标和 ᵢ的分布的信息。利用这些额外的知识,可以获得未知人脸的估计表示 a 作为以下约束最小化问题的解决方案

其中不等式约束确保估计的 aᵢ在训练集中观察到的两个标准偏差内( m 是训练样本的数量)。下面是这两种方法的 Julia 代码。使用 Python,可以使用 SciPy LP 解算器或 cvxopt 编写类似的代码。
下面显示了由两种方法为来自测试集的图像的随机子集提供的重建的比较。

顶行:来自测试集的地面真实图像。中间一行:通过低维测量矩阵的直接反演进行重建。底部一行:使用约束最小二乘公式进行重建。在所有情况下,仅使用 500 像素的测量值(即少于图像中总像素的 3%)来重建图像。图片由作者提供。
虽然直接对低维测量矩阵𝚯求逆提供了合理的重建,但是利用关于训练数据的低秩结构的额外信息比特以计算成本的微小增加为代价产生了好得多的重建。请记住,这些重建只依赖于原始图像中不到 3%的总像素的测量,当你想到这一点时,这是非常了不起的。尽管图像空间很大,但利用我们数据下面的低秩结构使得这些令人难以置信的性能成为可能!最后,由于需要非常少的像素测量,该技术可以容易地与预训练的分类器(例如,逻辑回归或 SVC)结合,以在低存储设备(例如,Raspberry Pi 或 Arduino)上操作简单的人脸识别软件。这是一个有趣的项目,我现在正在做。
结论
在过去的十年里,深度学习吸引了大量的注意力,并使仅仅基于线性代数的技术黯然失色。然而,线性代数仍然有很多优点,特别是对于工程和工业中流行的高度结构化和标准化的数据集,或者对于在计算资源有限的低存储设备上部署模型。在这里,我们一直在利用数据的低秩结构,仅通过几个像素测量值来重建图像。然而,这种技术可以用在与科学和工程更相关的环境中。马诺哈尔等人。使用它来研究在全球范围内何处放置一组有限的传感器来估计海面温度。他们还在流体动力学中使用它来确定在气流中放置传感器的位置,以推断速度场是什么,这在空气动力学和闭环反馈控制中非常重要。
因为它依赖于相当简单的数值线性代数技术,这种稀疏传感器放置方法也可以很容易地扩展到处理这个问题的变化。例如, Clark et al. 已经考虑到空间的某些区域可能更难探测(即在那里部署传感器更昂贵),或者您可能可以使用不同传感器的组合(例如压力、温度和速度传感器),每个传感器都有其感测精度和运行成本。它还被扩展到受控系统,在这些系统中,人们必须决定使用哪种类型的致动器,以及将它们放置在哪里,以便对系统具有最大的权威。我不怀疑许多其他实际工程问题可以在这个特别简单的框架内重新表述。你的问题呢?
我是一个有使命的线性代数狂热者。这篇文章是即将到来的系列文章的第一篇,旨在恢复线性代数的荣誉徽章,特别强调科学和工程的数据驱动技术。敬请期待更多精彩!
PS:如果你想了解更多,建议你看看 Brunton & Kutz 的新书, 数据驱动的科学与工程——机器学习、动力系统与控制 ,你可以从他们的网站(【http://databookuw.com/】)免费下载。你也可以看看他们的 YouTube 频道(这里这里和这里)。
想看更多这方面的内容?查看我其他关于 低秩结构和数据驱动建模 的文章或者干脆我的机器学习基础知识 !
古今 DNA 中的瘟疫
思想与理论,生命科学的数理统计与机器学习
如何在任何随机样本中找到鼠疫杆菌病原体

彼得·勃鲁盖尔《死亡的胜利》,马德里普拉多博物馆,图片来源
这是我的专栏 生命科学的数理统计和机器学习 中的第二十三篇文章,在这里我用通俗易懂的语言讨论了一些计算生物学中常见的神秘分析技术。应用于考古材料的 DNA 测序技术极大地丰富了我们关于人类过去的知识。例如,分析历史墓葬中人类遗骸的 DNA 可以提供关于古代大流行的大量信息,如由 鼠疫 细菌引起的 瘟疫 。然而,检测古代病原体的普通方法往往缺乏特异性,可能导致错误发现。在本文中,我将展示如何将序列比对与常见病原微生物检测标准(如覆盖深度)一起应用于现代宏基因组样本,从而轻松发现不应该存在的鼠疫杆菌。我还将讨论为什么在解释古代病原体发现时需要谨慎,以及如何使用更好的指标,如覆盖面可以更可靠地进行病原体检测。
在垃圾堆里搜寻
DNA 测序技术的出现有益于许多研究领域,包括个性化医疗、群体遗传学和古代 DNA (aDNA) 。从历史上看,aDNA 研究围绕着人类进化展开,其中主要人类 aDNA 被提取、测序和分析。然而,最近已经证明,对来自内源微生物群落的 aDNA 进行分析,可以带来关于古代大流行、生活方式和过去人口迁移的极其有价值的信息,这在以前常常被视为副产品。

早在 1998 年,可能就有第一篇发表在 PNAS 日报上的科学文章证明了在一个 16 世纪的古代人的人类牙髓中存在鼠疫耶尔森氏菌病原体(导致 鼠疫 )。

Drancourt 等人,美国国家科学院院刊,1998 年 10 月 13 日;95(21):12637–40.doi: 10.1073/pnas
随着下一代测序(NGS) 技术的发展,从数百个古代样本中筛选微生物群落成为可能。这导致了许多惊人的发现。例如,有证据表明鼠疫耶尔森菌在 青铜时代 就已经存在,而不仅仅是之前认为的中世纪。

[拉斯姆森等人,细胞。2015 年 10 月 22 日;163(3):571–82.doi: 10.1016/j.cell.2015.10.009,PMID: 26496604](http://Rasmussen S, Allentoft ME, Nielsen K, Orlando L, Sikora M, Sjögren KG, Pedersen AG, Schubert M, Van Dam A, Kapel CM, Nielsen HB, Brunak S, Avetisyan P, Epimakhov A, Khalyapin MV, Gnuni A, Kriiska A, Lasak I, Metspalu M, Moiseyev V, Gromov A, Pokutta D, Saag L, Varul L, Yepiskoposyan L, Sicheritz-Pontén T, Foley RA, Lahr MM, Nielsen R, Kristiansen K, Willerslev E. Early divergent strains of Yersinia pestis in Eurasia 5,000 years ago. Cell. 2015 Oct 22;163(3):571-82. doi: 10.1016/j.cell.2015.10.009. Epub 2015 Oct 22. PMID: 26496604; PMCID: PMC4644222.)
从那以后,的许多研究报道了在人类和动物的古代样本中发现微生物病原体。虽然在这里我并不质疑这些发现,因为它们中的大多数都提供了彻底的验证,但我仍然想表达一句谨慎的话。下面,我将展示在任何随机的宏基因组样本中错误地“检测”鼠疫杆菌是多么容易,因为病原体的存在毫无意义。
覆盖深度可能会产生误导
通常,在参加科学 aDNA 研讨会和会议时,我会听到以下在古代样本中发现病原体的典型报告方式:
检测到鼠疫耶尔森菌病原体,绘制了约 5000 个读数,1.2 倍基因组覆盖范围
这里,“读出”是指通过测序技术读出其核苷酸序列的小 DNA 片段。然后将“读数”与所讨论的生物的 参考基因组进行比对。在上面的例子中,大约 5000 个小 DNA“读数”与鼠疫耶尔森菌参考基因组进行了比对。这是相当多的,这似乎是一个很好的证据,证明鼠疫杆菌确实存在于样本中。所谓“1.2X 基因组覆盖”,我们通常是指鼠疫耶尔森菌参考基因组中的任何随机位置平均被大约 1.2 个“读数”覆盖。数字越大,更具体地说称为覆盖深度,测序和比对的质量越好,样品中存在鼠疫耶尔森菌的可能性越大。****
然而,当处理宏基因组数据的分析时,即当对多种微生物进行同时比对时,覆盖深度可能是一个误导性指标。更具体地说,用来自多种生物的序列工作,很难确定与特定微生物生物的参考基因组比对的序列真的来自该生物,而不是来自另一种生物。aDNA的降解和损伤,以及与参考数据库的进化分歧,不完整和被污染的数据库,现代污染,跨生物体的 DNA 保守和水平基因转移使得对特定生物体(包括鼠疫耶尔森氏菌等病原体)的序列确定变得复杂。

具有相同覆盖深度的两种比对方案:在一个区域 a)中比对的读数,以及在参考基因组 b)中均匀分布的读数,作者图像
在上图中,我展示了短(蓝色)序列与参考(黑色)基因组比对的两种情况。假设序列的长度为 L,为简单起见,参考基因组的长度为 4*L。当使用例如Integrative Genomics Viewer(IGV)工具可视化比对时,人们经常会遇到所有读数/序列都在一个区域中进行比对的情况。这是一个不幸的场景,表明序列和排列存在问题。另一种情况是,当读数在参考基因组中均匀分布时,这是我们想要观察的,因为这是成功作图的证据。然而,如上图所示,计算覆盖深度可能会导致这两种截然不同的制图场景产生相同的值。覆盖深度度量的一个更好的替代方法是覆盖广度(覆盖均匀度),它简单地计算被至少一个序列/读数覆盖的参考基因组的部分。
在随机样本中检测鼠疫耶尔森菌
为了证明人们如何能够错误地检测出鼠疫耶尔森氏菌(耶尔森氏菌,T2 鼠疫耶尔森氏菌)病原体,让我们使用来自布罗德研究所(Broad Institute)的糖尿病免疫数据库(三个国家队列),从一名现代婴儿身上随机抽取粪便样本。

随机宏基因组样本取自布罗德研究所的糖尿病免疫项目,图片来源
现在,让我们使用 Bowtie2 mapper 将样本中的序列与鼠疫耶尔森氏菌的参考基因组进行比对。然而,首先我们必须为鼠疫耶尔森菌的参考基因组建立一个 Bowtie2 指数。
在这里,我们惊奇地发现 19 987 阅读序列可以映射到鼠疫耶尔森菌参考基因组。这是一个很大的数字,但没有什么意义,因为这是一个随机的现代婴儿 T21 样本,很难相信耶尔森氏鼠疫杆菌会出现在那里。请注意,通过说“19 987 个映射读数”,我们使用覆盖深度作为病原体检测的标准。上述分析中一个明显的缺陷是,我们只提供了鼠疫耶尔森菌病原体参考基因组用于绘图,而这是一个应该包含大量人类 DNA 的人类粪便样本。因此,许多与鼠疫耶尔森氏菌参考基因组比对的读数可能实际上来自人类,它们可能已经定位到 保守/直向同源 区域。为了解决这个问题,让我们将一个人类参考基因组添加到鼠疫杆菌参考基因组中,并再次执行作图。
现在,我们可以看到“只有” 8 148 个读数与鼠疫耶尔森氏菌参考基因组对齐,而它们被给予了与人类 hg38 参考基因组对齐的选择。随着所用数据库大小的增加(2 个基因组而不是 1 个),分析变得更加稳健,因为在这里我们不强制读数仅与鼠疫耶尔森菌比对。然而,我们仍然发现许多明显的假阳性序列与鼠疫耶尔森氏菌的序列比对。那些可能是微生物序列,在分类学上更接近鼠疫杆菌而不是真核生物。由于除了鼠疫耶尔森氏菌 之外没有提供 (包括在数据库中)微生物参考基因组,许多实际上来自非耶尔森氏菌微生物**的序列被迫与鼠疫耶尔森氏菌进行比对。为了解决这个问题,我们需要下载所有可用的细菌参考基因组,随机选取其中的 10、100、1000 和 10 000 个,与耶尔森氏鼠疫杆菌 +人类参考基因组合并,并对大小不断增长的数据库进行比对。每次,我们都要检查唯一映射到鼠疫耶尔森氏菌的读数数量,同时记住这些读数可能与其他微生物有更高的亲和力。****

随着更多的基因组被添加到数据库中,绘制鼠疫耶尔森氏菌图谱的读数数量减少,图片由作者提供
我们可以看到,随着 数据库大小的增长 ,鼠疫耶尔森氏菌与唯一比对**的读数在减少,当我们将 10 000 个随机细菌纳入数据库时,读数会变得与 6 一样多。因此,我们得出结论,有限大小的数据库可能导致微生物的假阳性检测,因为我们得到越来越多的与鼠疫耶尔森菌相关的读数,这显然是一个假阳性的发现,我们提供的用于竞争性图谱的基因组越少。此外,富含病原微生物的数据库可能导致在古代和现代宏基因组样本中错误发现病原体。**
诀窍在哪里?
那么,为什么我们似乎在现代婴儿样本中检测到了不应该存在的鼠疫耶尔森菌呢?显然是因为我们简单地统计了映射到鼠疫耶尔森菌的读数,从而使用覆盖深度作为检测标准。这些读数很可能来自另一种 同源 微生物,但由于其参考基因组不包括在数据库中,因此无法将其归属于该微生物。我们能过滤掉这些假阳性的发现吗?是的,我们可以!通过使用比覆盖深度/映射读取数更好的标准。如果我们使用覆盖的广度,即读数在参考基因组中的分布有多均匀,我们可以很容易地将鼠疫耶尔森氏菌作为假阳性结果过滤掉。

整合基因组浏览器(IGV)与鼠疫耶尔森氏菌参考比对的读数可视化,图片由作者提供
当使用整合基因组浏览器(IGV)工具可视化鼠疫耶尔森菌参考基因组比对时,假阳性变得明显,如上图所示。这些读数并不是沿基因组均匀分布的,而是与几个(最有可能是保守的)区域对齐。因此,在未来,一种更可靠、更令人信服的报告鼠疫耶尔森氏菌 在古代样本中真实存在的方法如下(注意 85%的覆盖面):
检测到鼠疫耶尔森菌病原体,绘制了约 5000 个读数,1.2 倍基因组覆盖率,覆盖了 85%的参考基因组
摘要
在这篇文章中,我们了解到从古代样本的微生物群落中测序 DNA 可以提供关于在古代人类和动物中流行的疾病的惊人信息。然而,可靠的检测古代微生物病原体还是很有挑战性的。由于忘记应用多层次的验证,人们很容易以错误的发现告终,因此需要对每个古代样本进行定制和仔细的 分析以准确地筛选古代微生物病原体的存在。
像往常一样,让我在下面的评论中知道生命科学和计算生物学的哪些分析方法对你来说似乎特别神秘,我将在这个专栏中尝试解决它们。在我的 github 上查看帖子使用的文件。在MediumNikolay Oskolkov 关注我,在Twitter@ NikolayOskolkov 关注我,并通过 Linkedin 联系我。在下一篇文章中,我们将讨论如何在 UMAP 空间聚集,敬请关注。
机器人星球
人工智能如何接管附近的星球

图片来自皮克斯拜的艾努尔·扎基罗夫
你可能会惊讶地发现,在离我们很近的地方,有一个由机器人统治的星球。那里没有人类居住。人口仍然相对较少,只有五个机器人在上面,但它正在稳步增长。这颗行星正是我们的姐妹行星火星。

图片由布里吉特根据你的照片制作定制作品,非常感谢来自 Pixabay
事实上,美国国家航空航天局(NASA)自 1971 年以来共向这颗红色星球发送了 7 辆火星车,但其中两辆从未完成旅程。在其他 5 个中,Sojourner 在着陆后三个月失去了通信(1997 年),Spirit 工作了六年(直到 2010 年),但它的轮子陷入了沙子,而 Opportunity 由于一场严重的沙尘暴进入了冬眠(2018 年)。后两个,好奇号(2012)和恒心号(2021),还在运营中。
毫无疑问,毅力漫游者是最先进的,它使用了复杂的人工智能(AI ),尽管它的处理器不如你的手机强大!选择 90s 处理器的原因是因为火星的大气层对有害辐射和带电粒子的保护远不如地球大气层。使用更老、更简单的芯片可以降低出错的几率。然而,这并不能阻止毅力做一些相当不可思议的事情。
首先,它成功地从地球飞行了 3 亿英里。着陆是最关键的部分,以至于他们称之为“恐怖的 7 分钟”。着陆是危险的,因为着陆点附近有悬崖和沙丘;然而,人工智能地形相对导航(TRN)来救了我们。它的机载相机拍了几张下降的照片;人工智能识别出飞船的位置,并把它导向一个安全的着陆点。“TRN”号成功地在离约定着陆点不到 40 米的地方着陆。这样的人工智能是至关重要的,因为在这 7 分钟内,航天器是靠自己的,从地球发出的任何纠正命令都不会及时到达它那里(因为它可能需要 40 分钟)。
人工智能也将有助于坚持其科学使命。自主探索采集增强科学系统(AEGIS)是一个智能定位软件,可以自主发现奇怪的岩石。然后,科学家可以向半径 7 米内的任何位置发射强大的激光,蒸发部分表面,并使用 ChemCam 或 SuperCam 分析其元素组成。
最新的火星漫游车也是一种自动驾驶车辆。人工智能可以在拍照、进行实验和避开障碍的同时,连续驾驶漫游车在地球上行驶。这种新型火星车的另一个令人兴奋的特点是可以在火星表面钻孔,收集土壤,并将样本放入试管中。火星登月舱(MAV)将在 2030 年左右收集这些管子,并将它们发射回地球。
毅力还拥有一架名为“独创性”的小型无人驾驶直升机。它可以在地面以上 5 米的地方飞行 90 秒,最远飞行 50 米。虽然它的飞行将被精心编写,但它可以使用自主控制,从而允许人工智能驾驶它。

欧洲南方天文台/M. Kornmesser
即使“机器人星球”不是外星人的接触,我们也可能在 2017 年遇到一些真正的外星人。当时,一颗名为 Oumuamua(或 scout)的流氓彗星被探测到接近地球。到目前为止没有什么奇怪的,但在 2018 年 6 月 27 日,天文学家注意到了一个与他们的计算不匹配的运动。起初,他们认为当物体靠近太阳时,气体从物体中蒸发,但科学家后来否定了这一理论。多年来有各种各样的理论,但似乎都站不住脚。
即使“机器人星球”不是外星人的接触,我们也可能在 2017 年遇到一些真正的外星人。当时,一颗名为 Oumuamua(或 scout)的流氓彗星被探测到接近地球。到目前为止没有什么奇怪的,但在 2018 年 6 月 27 日,天文学家注意到了一个与他们的计算不匹配的运动。起初,他们认为当物体靠近太阳时,气体从物体中蒸发,但科学家后来否定了这一理论。多年来有各种各样的理论,但似乎都站不住脚。
2018 年,哈佛大学天文系主任发表论文声称,经过仔细的数学分析;Oumuamua 可能是人工制造的轻型帆船。这种帆利用照射在大镜子上的阳光推动物体穿越太空。人类在 2010 年向金星发射 IKAROS 飞船时已经使用了轻型帆船。
Oumuamua 是否是外星飞船仍有争议,需要更多的数据才能有任何确定性。在那之前,让我们继续探索我们的太阳系,一次探索一颗行星。可以肯定的是,为了实现更雄心勃勃的太空任务,我们需要人工智能来导航我们的航天器穿越太空。自动驾驶汽车将分析外星世界,并将信息传回地球。利用技术作为我们延伸的臂膀,我们可以设法探索前人未曾涉足的领域。
如果你喜欢这篇文章并想联系我,请联系我🐦 碎碎念 ,🔗LinkedIn,📷insta gram或者😊https://www.facebook.com/alexieidingli/。
* https://medium.com/dataseries/from-paris-to-london-in-a-handful-of-minutes-7657d978152
阿列克谢·丁力教授 是马耳他大学的 AI 教授。二十多年来,他一直在人工智能领域进行研究和工作,协助不同的公司实施人工智能解决方案。他的工作被国际专家评为世界级,并赢得了几个当地和国际奖项(如欧洲航天局、世界知识产权组织和联合国等)。他已经出版了几本同行评审的出版物,并成为马耳他的一员。由马耳他政府成立的人工智能特别工作组,旨在使马耳他成为世界上人工智能水平最高的国家之一。*
利用先进的深度学习和反应技术进行植物病害检测
高级深度学习| FLASK | REACTJS
用于可视化和诊断植物疾病的最新架构!
大量的论文和文章可用于使用深度学习来检测叶子或植物疾病。卷积神经网络通过提供有助于准确检测植物疾病的模型,彻底改变了农业领域。但是检测疾病是不够的,人们还应该了解叶子或植物图像中的疾病症状。为了通过目标检测定位工厂中受影响的区域,使用了单触发多盒检测器(SSD)、更快的基于区域的卷积神经网络(更快的 RCNN)和基于区域的完全卷积网络(R-FCN)。此外,对于实时对象检测,采用了只看一次(YOLO)算法。所有这些算法都可以用于植物疾病的诊断和受影响区域的可视化,但是它们缺乏透明度,并且在实践中充当黑箱。
在开始我最新研究的文章之前,我想分享一个好消息:

在这篇文章中,一个可解释的国家最先进的方法被采用,超越了所有以前提出的方法。最近在线发表在 Scopus 索引期刊《农业信息处理》上的一篇影响因子为 6.41 的研究论文被用作本文的基础。
https://www.sciencedirect.com/science/article/pii/S2214317321000482
本文提出了一种高级深度学习架构 ResTS,Residual Teacher/Student 的缩写。它包含两个分类器,即 ResTeacher 和 ResStudent 以及一个解码器。rest teacher+Decoder 用作自动编码器,从输入图像中移除非鉴别特征,而 ResStudent 分类器有助于改善这种架构的重建损失。ResTS 输出一个热图,只突出显示叶部疾病和疾病类别的区别特征。它是在 PlantVillage 数据集(分段版本)上训练的。这种提出的结构优于先前发现的用于植物疾病的诊断和可视化的架构,最高 F1 分数为 0.991。figure 1–4 描述了休止符如何操纵输入图像。以下部分解释了这些图像是如何由模型构建的,以及如何通过几个重要的公式进行后处理的。



图一。输入图像 2。解码器用 matplotlib(范围 0..①浮舟③。解码器通过 OpenCV 重建的图像(范围 0..255 浮球) 4。 来自 matplotlib 的热图由(cmap = ' red ')
本文将构建一个 React 应用程序作为我们的前端,并在后端运行来自研究论文的高级深度学习架构- ResTS 。作为回应,系统将如图 5 所示进行机动。我们将创建整个系统并运行它,最后,看看是否所有的部分都适合在一起。
要访问已训练的 ResTS 模型,请参见下一节末尾的说明。

图 5。系统的一般流程
首先,我们将设计 Flask 服务器来适应预先训练好的 ResTS 架构。服务器将合并一个路由来处理来自应用程序的输入图像,并将返回一个新的图像,该图像仅包含显著特征以及疾病名称和概率。其次,我们将开发一个简单的 React 应用程序,可以上传和显示图像。我们不会深入研究 ResTS 架构的细节。请参考原始论文,以了解该架构如何操作来诊断植物疾病。
要访问预训练的 ResTS 模型,
1。在 LinkedIn 上的以下帖子中对您的请求提出适当的理由,我将分享个人链接,以各种格式访问预先培训的已保存模型:
2.按照这里的指示引用论文:https://doi.org/10.1016/j.inpa.2021.06.001
3.只有当你的请求是可信的,并且论文被恰当地引用和注明时,你才会被授予访问权。
1.创建包含 ResTS(剩余教师/学生)模型的服务器
架构代码需要与服务器放在同一个文件中。然而,这是可以改变的,但由于一些错误,我决定把整个架构的代码放在同一个文件中,并加载权重。此外,只运行一个文件而不是管理多个文件也很好。
源代码访问:你可以从上面提到的文章中找到这篇文章的全部源代码和 ResTS 架构的链接。
首先,我们将所有必需的库导入到我们的 Flask 服务器文件中。
import os
from flask import Flask, request
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
import pandas as pd
import gevent.pywsgi
from flask_cors import CORS
from datetime import datetime
from tensorflow.python.keras.backend import set_session
from keras import backend as K
import tensorflow.compat.v1 as tf
import keras_preprocessing
from keras.applications.xception import preprocess_input as xception_preprocess_input
from keras_preprocessing import image
import json
from PIL import Image
from hashlib import sha256
###########################
from tensorflow.keras.layers import Input, Dense, Conv2D, Activation, MaxPool2D
from tensorflow.keras.layers import *
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.optimizers import Adam, SGD
import glob
import argparse
from keras import __version__
from keras.applications.xception import preprocess_input as xception_preprocess_input
from keras.preprocessing.image import ImageDataGenerator
from keras.optimizers import SGD
from keras import optimizers
from keras import callbacks
from keras.callbacks import ModelCheckpoint, LearningRateScheduler
from keras import backend as keras
from keras.regularizers import l2,l1
import pandas as pd
这将需要巨大的处理能力。下一步是用一些其他重要的变量和使用 tf.session 的会话来定义‘app’变量。运行服务器时需要 tf.disable_v2_behavior() 和 tf.get_default_graph() 来避免任何与图形相关的错误。
input_shape = (224,224,3)
nbr_of_classes=38
tf.disable_v2_behavior()
graph = tf.get_default_graph()app = Flask(__name__)
CORS(app)
sess = tf.Session()
set_session(sess)
注意:如果在服务器启动阶段仍然出现错误,很有可能是由于 TensorFlow 的版本错误。在这种情况下,尝试搜索 Stackoverflow。
定义会话后,将实现架构代码。ResTS 使用标准 Xception 架构作为 ResTeacher 和 ResStudent,解码器以与 Xception 架构完全相反的方式生成,以再现图像。深入讨论 ResTS 架构超出了本文的范围。要正确理解下面的代码,请参考原文。
**#ResTS ARCHITECTURE****#RESTEACHER** base_model1 = tf.keras.applications.Xception(include_top=False, weights='imagenet',input_shape = input_shape)
x1_0 = base_model1.output
x1_0 = Flatten(name='Flatten1')(x1_0)
dense1 = Dense(256, name='fc1',activation='relu')(x1_0)
x = classif_out_encoder1 = Dense(38, name='out1', activation = 'softmax')(dense1) # Latent Representation / Bottleneck#Get Xception's tensors for skip connection.
...**#DECODER**
dense2 = Dense(256, activation='relu')(x)x = Add(name='first_merge')([dense1, dense2])
x = Dense(7*7*2048)(x)
reshape1 = Reshape((7, 7, 2048))(x)#BLOCK 1
...#BLOCK 2
...#BLOCK 3-10
...#BLOCK 11
...#BLOCK 12
...#BLOCK 13
...#BLOCK 14
...x = Conv2D(2, 3, activation = 'relu', padding = 'same',)(x)
mask = x = Conv2D(3, 1, activation = 'sigmoid',name='Mask')(x)**#RESSTUDENT**
base_model2 = tf.keras.applications.Xception(include_top=False, weights='imagenet',input_shape = (224,224,3))
x2_0 = base_model2(mask)
x2_0 = Flatten(name='Flatten2')(x2_0)
x2_1 = Dense(256, name='fc2',activation='relu')(x2_0)
classif_out_encoder2 = Dense(nbr_of_classes, name='out2',activation='softmax')(x2_1)#Create ResTS Model and Load Pre-trained weights
ResTS = Model(base_model1.input, [classif_out_encoder1, classif_out_encoder2])
ResTS.load_weights('tf/RTS')
ResTS 架构的设计方式是,在自动编码器的帮助下,从树叶的输入图像中筛选出噪声区域。遮罩层是解码器的输出,用于可视化叶子图像的重要区域。ResTeacher 的输出 classif_out_encoder1 在 softmax 函数的帮助下,协助对疾病类别进行分类。
#For visualization impetuslayer_name ='Mask'
NewInput = ResTS.get_layer(layer_name).output
visualization = K.function([ResTS.input], [NewInput])
在上面的代码中, visualization 是一个函数,它帮助获得只包含叶子图像的重要特征的图像。接下来,我们将定义一些重要的函数,用于对从可视化函数接收到的图像的特征进行可视化。
def reduce_channels_sequare(heatmap):
channel1 = heatmap[:,:,0]
channel2 = heatmap[:,:,1]
channel3 = heatmap[:,:,2]
new_heatmap = np.sqrt(((channel1-0.149)*(channel1-0.149))+((channel2-0.1529)*(channel2-0.1529))+((channel3-0.3412)*(channel3-0.3412)))
return new_heatmap
需要reduce _ channels _ se quare函数将重建的 RGB 图像转换为单通道图像。它间接找到重建图像中主色之间的距离(在此功能中称为热图)。
def postprocess_vis(heatmap1,threshould = 0.9):
heatmap = heatmap1.copy()
heatmap = (heatmap - heatmap.min())/(heatmap.max() - heatmap.min())
heatmap = reduce_channels_sequare(heatmap)
heatmap = (heatmap - heatmap.min())/(heatmap.max() - heatmap.min())
heatmap[heatmap>threshould] = 1
**heatmap = heatmap*255**
return heatmap
postprocess_vis 函数执行基本的二进制阈值处理,将值大于 0.9 的像素设置为 1。热图(重建图像)然后乘以 255,这样我们就得到范围[0,255]内的值。
注意:我们现在只处理单通道图像,而不是 RGB 通道。有必要在减少通道之前和之后标准化热图中的值,以便使用 OpenCV 获得如图 6 所示的最终热图。
注意,如果使用 OpenCV:如果我们没有乘以 255,我们将得到图 6 中的大部分黑色图像,因为像素值在范围[0,1]内。因为 OpenCV 考虑范围[0,255]并相应地输出图像。
注意,如果使用 Matplotlib (cmap= 'Reds '):如果我们没有乘以 255,那么使用 cmap= 'Reds' ,我们将得到与图 4 相同的输出。因为 matplotlib 在显示 RGB 图像时只要求特定的范围。
def visualize_image(img_name):
image_size = (224,224)
original_image = image.load_img(img_name, target_size=image_size)
img = image.img_to_array(original_image)
img = np.expand_dims(img, axis=0)
img = xception_preprocess_input(img)
global sess
global graph
with graph.as_default():
set_session(sess)
**vis = visualization([img])[0][0]**
disease = ResTS.predict(img)[0]
probab = max(disease[0])
disease = np.argmax(disease)
heatmap = postprocess_vis(vis)
img = plt.imshow(heatmap, cmap='Reds')
plt.axis('off')
plt.savefig(img_name, bbox_inches='tight')
return disease, probab
这里, vis 变量由范围[0,1]内的浮点值组成。因此,如果您想对 vis 变量运行 plt.imshow() 方法,它将给出如图 2 所示的重建图像输出。如果将 vis 变量乘以 255,浮点值将出现在【0,255】范围内,这是 plt.imshow() 方法不支持的,因为它要求“RGB”图像的浮点值在[0,1]范围内,整数值在[0,255]范围内。现在,获得如图 3 所示的输出。,只需将 vis 乘以 255,使用 OpenCV 保存即可,如下所述。
#cv2.imwrite('vis.jpg',vis)
Matplotlib 的cmap = ' red '给出了如图 4 所示的红色热图。 visualize_image 函数用热图覆盖输入图像(具有相同的文件名)。
注意:如果我们不使用 plt.imshow(heatmap,cmap = ' Reds '),而是使用 cv2.imwrite(heatmap),我们将得到如下输出图像。原因是我们已经在后处理 _vis 函数中生成了一个“单通道”热图,并且应用了二进制阈值。OpenCV 将根据像素值写入图像(255 像素值= '白色'区域,0 像素值= '黑色'区域,其他值为'浅灰色'区域)。

图 6。带 OpenCV 的热图(不带 cmap = ' Reds ')
visualize_image 功能是这个系统的主干。它处理疾病的预测以及疾病症状可视化的生成。首先,通过从“/detect”路径传递的名称读取输入图像,并通过来自标准异常架构的默认异常 _ 预处理 _ 输入函数进行预处理。可视化函数被调用,得到解码器的输出,即重建图像。。在模型上调用 predict() 方法来获得 ResTeacher 的输出,即疾病的类别。它返回预测的疾病名称及其概率(表明模型对预测的置信度)。它保存具有相同文件名的热图,即覆盖原始文件。
创建路线'/detect '
/detect' route 从 React 应用程序获取图像,生成突出显示疾病鉴别特征的热图,并将其与疾病类别一起返回给应用程序。它还返回预测的概率。图 7。决定了这条路线的流向。

图 7。“/detect”路线的流程
下面是这条路线的代码改编。它以与图 7 所示完全相同的方式工作。因此,下面的代码是不言自明的。
@app.route('/detect', methods=['POST'])
def change():
image_size = (224,224)
img_data = request.get_json()['image']
img_name = str(int(datetime.timestamp(datetime.now()))) + str(np.random.randint(1000000000))
img_name = sha256(img_name.encode()).hexdigest()[0:12]
img_data = np.array(list(img_data.values())).reshape([224, 224, 3])
im = Image.fromarray((img_data).astype(np.uint8))
im.save(img_name+'.jpg')
disease, probab = visualize_image(img_name+'.jpg')
img = cv2.imread(img_name+'.jpg')
img = cv2.resize(img, image_size) / 255.0
img = img.tolist()
os.remove(img_name+'.jpg')
return json.dumps({"image": img, "disease":int(disease), "probab":str(probab)})
1.1 要访问预训练的 ResTS 模型,
1。在 LinkedIn 上的以下帖子中对您的请求提出适当的理由,我将分享个人链接,以访问各种格式的预培训保存模型:
2.按此处说明引用论文:【https://doi.org/10.1016/j.inpa.2021.06.001
3.只有当你的请求是可信的,并且论文被恰当地引用和注明时,你才会被授予访问权。
2.创建简单的 ReactJS 应用程序
让我们直接进入 React 中的编码部分(App.js)文件。
首先,我们将导入一些库并定义全局变量。
import logo from './logo.svg';
import './App.css';
import React from 'react';
import * as tf from '@tensorflow/tfjs';
import cat from './cat.jpg';
import {CLASSES} from './imagenet_classes';
const axios = require('axios');const IMAGE_SIZE = 224;
let mobilenet;
let demoStatusElement;
let status;
let mobilenet2;
imagenet_classes 是一个包含所有类的名称和字典中相应数字的文件。不要介意变量名!这段代码经历了多次尝试,以获得手头任务的完美应用程序。接下来,我们将从“App”类开始。类中的第一个方法是构造函数方法。
constructor(props){
super(props);
this.state = {
load:false,
status: "F1 score of the model is: 0.9908 ",
probab: ""
};
this.mobilenetDemo = this.mobilenetDemo.bind(this);
this.predict = this.predict.bind(this);
this.showResults = this.showResults.bind(this);
this.filechangehandler = this.filechangehandler.bind(this);
}
加载状态是针对加载图像之前的动画。状态是应用程序中的默认声明。概率每次图像通过时,状态都会发生变化,包含其预测的准确性。下面几节将讨论这四种方法。
现在,我们将为特定任务定义所有这 4 种方法。正如我之前提到的变量名,忽略它们。ResTS 架构使用异常架构,即使有些变量说 mobilenet 。
async mobilenetDemo(){
const catElement = document.getElementById('cat');
if (catElement.complete && catElement.naturalHeight !== 0) {
this.predict(catElement);
catElement.style.display = '';
} else {
catElement.onload = () => {
this.predict(catElement);
catElement.style.display = '';
}
}};
mobilenetDemo() async 方法通过调用 prediction() 方法,加载 app 第一次渲染时的第一张图片及其预测。 prediction() 方法以图像元素为输入,调用 Flask 服务器进行相关预测。服务器返回 3 个参数——疾病、概率和热图。
async predict(imgElement) {
let img = tf.browser.fromPixels(imgElement).toFloat().reshape([1, 224, 224, 3]);
//img = tf.reverse(img, -1);
this.setState({
load:true
});
const image = await axios.post('http://localhost:5000/detect', {'image': img.dataSync()});
this.setState({
load:false
});
// // Show the classes in the DOM.
this.showResults(imgElement, image.data['disease'], image.data['probab'], tf.tensor3d([image.data['image']].flat(), [224, 224, 3]));
}
最后,调用 showResults() 方法在 app 中显示结果。 showResults() 方法从 prediction() 方法中取 4 个值作为参数。该方法执行一些基本的 HTML 操作,将结果从服务器描绘到应用程序中。
async showResults(imgElement, diseaseClass, probab, tensor) {
const predictionContainer = document.createElement('div');
predictionContainer.className = 'pred-container'; const imgContainer = document.createElement('div');
imgContainer.appendChild(imgElement);
predictionContainer.appendChild(imgContainer); const probsContainer = document.createElement('div');
const predictedCanvas = document.createElement('canvas');
probsContainer.appendChild(predictedCanvas); predictedCanvas.width = tensor.shape[0];
predictedCanvas.height = tensor.shape[1];
tensor = tf.reverse(tensor, -1);
await tf.browser.toPixels(tensor, predictedCanvas);
console.log(probab);
this.setState({
probab: "The last prediction was " + parseFloat(probab)*100 + " % accurate!"
});
const predictedDisease = document.createElement('p');
predictedDisease.innerHTML = 'Disease: ';
const i = document.createElement('i');
i.innerHTML = CLASSES[diseaseClass];
predictedDisease.appendChild(i);
//probsContainer.appendChild(predictedCanvas);
//probsContainer.appendChild(predictedDisease); predictionContainer.appendChild(probsContainer);
predictionContainer.appendChild(predictedDisease);
const predictionsElement = document.getElementById('predictions');
predictionsElement.insertBefore(
predictionContainer, predictionsElement.firstChild);
}
每当通过上传按钮上传图像时,就会触发 filechangehandler() 方法。
filechangehandler(evt){
let files = evt.target.files;
for (let i = 0, f; f = files[i]; i++) {
// Only process image files (skip non image files)
if (!f.type.match('image.*')) {
continue;
}
let reader = new FileReader();
reader.onload = e => {
// Fill the image & call predict.
let img = document.createElement('img');
img.src = e.target.result;
img.width = IMAGE_SIZE;
img.height = IMAGE_SIZE;
img.onload = () => this.predict(img);
};// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
}
最后,我们要调用 mobilenetDemo() 方法来加载第一幅图像及其预测。对于此任务,我们将使用 componentDidMount() 生命周期方法。
componentDidMount(){
this.mobilenetDemo();
}render(){
return (
<div className="tfjs-example-container">
<section className='title-area'>
<h1>ResTS for Plant Disease Diagnosis</h1>
</section><section>
<p className='section-head'>Description</p>
<p>
This WebApp uses the ResTS model which will be made available soon for public use.It is not trained to recognize images that DO NOT have BLACK BACKGROUNDS. For best performance, upload images of leaf\/Plant with black background. You can see the disease categories it has been trained to recognize in <a
href="https://github.com/spMohanty/PlantVillage-Dataset/tree/master/raw/segmented">this folder</a>.
</p>
</section><section>
<p className='section-head'>Status</p>
{this.state.load?<div id="status">{this.state.status}</div>:<div id="status">{this.state.status}<br></br>{this.state.probab}</div>}
</section><section>
<p className='section-head'>Model Output</p><div id="file-container">
Upload an image: <input type="file" id="files" name="files[]" onChange={this.filechangehandler} multiple />
</div>
{this.state.load?<div className="lds-roller"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>:''}<div id="predictions"></div><img id="cat" src={cat}/>
</section>
</div>);
}
render() 方法包含不言自明的 HTML 元素。
3.集成先进的深度学习和 ReactJS 用于植物病害诊断
下面的 Gif 描述了与 flask 服务器连接的 web 应用程序的工作方式,该服务器包括预训练的 ResTS 模型。

Gif 1。反应应用程序
从这里在 LinkedIn 上与我联系。
参考
- D.Shah,V. Trivedi,V. Sheth,A. Shah,U. Chauhan,ResTS:植物病害检测的剩余深度可解释体系结构,农业信息处理(2021),doi:https://doi.org/10.1016/j.inpa.2021.06.001
NBA 中的球员、位置和概率
使用监督机器学习构建 NBA 位置分类器

科比·布莱恩特在对阵萨克拉门托国王队的比赛中罚球。拉米罗·皮亚诺罗萨在 Unsplash 上的照片
让我们以一个重要的问题开始这篇博客:为什么你需要使用机器学习来根据位置对 NBA 球员进行分类?一个普通的篮球迷可能已经看了足够多的篮球比赛,以确定哪个球员打哪个位置。即使是一个对这项运动一无所知的人,也可以在 YouTube 上找到一些历史上最伟大球员的视频,以了解每个位置的作用。那么,机器学习算法可能为我们提供哪些额外的信息来证明它的用途呢?
嗯,当谈到分类的任务时,机器学习算法使用预测的概率来决定某样东西是否属于某个组。在 NBA 位置的情况下,机器学习算法将预测球员成为控球后卫、得分后卫、小前锋、大前锋或中锋的概率。该算法将为我们提供任何给定玩家的每个位置的概率。正是这些概率可能被证明对前台或 NBA 分析师之间的讨论是有用的。随着每一代 NBA 天才的出现,我们看到球员之间在技能组合方面有了更多的融合。中锋不再是严格意义上的靠近篮筐的低位球员,他们只是来到三分线前做掩护。现代中锋游离到三分线外投中三分球,有些人拥有有效快攻的必备技能。通过给一个球员分配位置概率,我们可以开始理解一个球员的多面性。如果我们知道一个队中每个球员的位置概率,那就可以用来分析你自己队或对手的阵容。
让我们也考虑这些位置概率的另一个用途。每年一个体育记者和广播员小组投票选出各种奖项,如最有价值球员、年度最佳防守球员和最佳 NBA 球队。根据 hoopsrumors.com 的一篇文章,如果球员符合以下标准,他们就有资格获得一份超高的合同(球队工资帽的 30%):
- 在前一个赛季或前三个赛季中的两个赛季组建一支全明星队
- 在前一个赛季或前三个赛季中的两个赛季被评为年度最佳防守球员
- 在前三个赛季中至少有一个赛季被评为最有价值球员
鉴于这些媒体成员的投票会影响球员未来的收入,我们在评估和比较球员时拥有公平的衡量标准是至关重要的。NBA 位置分类器算法的输入将是描述球员的表现和对比赛的影响的一些记录的统计数据组。该算法将接受这些输入,执行某种数学运算,并为我们提供位置概率。假设我们对输入做出了正确的假设,并对模型进行了适当的训练,我们可以生成如下内容:

勒布朗·詹姆斯在 2015-2016 赛季的位置概率。(图片由作者提供)
如果在两个潜在的 MVP 候选人之间做出决定,一个奖项的投票者可能会使用如上所示的工具,并决定选择位置多样性更大的球员。
数据收集
为了创建一个能产生上述概率的模型,我们首先要收集每个玩家的数据。从 basketball-reference.com 收集了每 100 点拥有统计和高级统计。玩家跟踪数据是通过 Python 的nba_stats_tracking库从 stats.nba.com 收集的。这个库的文档可以在这里找到。只使用了 2015-16 到 2018-19 赛季的球员数据。我选择不使用 2019-20 球员数据,因为新冠肺炎导致的突然暂停和常规赛比赛总数的不一致。NBA 网站上的球员跟踪数据只能追溯到 2013-2014 赛季。正如我们很快会看到的,NBA 在投篮选择方面经历了一点演变。在 NBA 的所有球队中,尝试和投中三分球的次数都有显著增加。通过使用明显属于 3 分时代的 4 个赛季的数据,我的目标是防止模型由于每年的统计差异而对球员做出不正确的假设。只使用了常规赛的数据。不同的来源被组合成一个单一的熊猫数据帧,使用球员的名字和赛季。每个球员都被视为单个观察值(即 2015–16 勒布朗詹姆斯不同于 2017–18 勒布朗詹姆斯)。我选择将每个玩家作为一个单独的观察对象,有三个原因:
- 为了确保有足够的数据来训练和测试模型
- 名册很少年年相同。一个新球员的引进,无论是选秀、交易还是自由球员,都可能导致教练不得不调整阵容,球员自己也可能为了球队的成功而改变他们的打法。
- 就像其他人一样,NBA 球员每年都在变老。不管是因为年龄还是伤病,时间确实会影响球员的比赛。一个受了重伤的爆发力控球后卫在恢复后可能会更依赖跳投,更适合担任得分后卫,而让另一名球员负责进攻。
数据探索
为了建立我的最终模型,我最终为每个玩家使用了 51 个变量。很明显,生成和分析这些统计数据需要花费大量的时间,所以我在这篇博客中只列出了几个。我会在下面留下我的 Github repo 的链接,在那里你可以看到更多的分析和可视化。
我简单提到过,3 分投篮已经成为当今 NBA 越来越重要的技能。让我们按位置来看一下 4 个赛季的 3 分投篮:

3 分投篮命中率和尝试次数(图片由作者提供)
在过去的 4 个赛季中,每个位置每 100 次控球中的 3 分都在增加,我们也可以看到大前锋和中锋的比例随着时间的推移有所提高。虽然大前锋和中锋的三分球命中率不如外线位置,但这是一个证明球员天赋水平不断提高的指标

2 分投篮命中率和尝试次数(图片由作者提供)
我们看到,随着 3 分尝试次数的增加,2 分尝试次数必然会减少。就百分比而言,所有职位的总体趋势都是积极的。2 分率的逐年增加可能是因为在篮筐附近的 2 分投篮比中距离投篮更频繁。这里是一篇很好的文章,看看 NBA 这些年来投篮选择是如何变化的。
让我们也来看看球员跟踪指标,如控球时间。

按职位划分的平均占有时间(按作者划分的图像)
这里需要注意的是,谁持球最多似乎有一个明确的等级顺序。我们可以看到位置之间的明确划分的统计数据只会有助于我们的分类模型更好地执行。类似于控球时间,当我们看每 100 次控球 3 分的尝试次数或每 100 次控球 2 分的百分比时,我们看到一些区别。
型号选择
在我们讨论我在这个项目中使用的最终模型之前,我将首先讨论一些我尝试过的其他模型,并讨论它们不适合的原因。
逻辑回归
为什么不适合:逻辑回归要求变量相互独立。
如前所述,我为每个玩家准备了 51 个输入变量来确定他们的位置。例如,我选择包括接球投篮的 3 分尝试,以及整体的 3 分尝试。随着接球投篮三分球尝试次数的增加,三分球尝试次数也会增加。我想说的是,当试图预测他们的位置时,接球投篮三分球的数量是一个重要的指标。与中锋相比,我们可能期望得分后卫有更大的比例去做接球投篮 3 分。
k 个最近邻居
为什么它不是一个很好的适合:准确性分数充其量是平庸。
从 3 个邻居开始,模型精度仅为 64.5%。对更多的邻居重复这个过程只会导致精确度的增加。11 个邻居导致了 68.8%的模型准确度。虽然我们可以继续增加邻居的数量,希望精确度不断提高,但这可能会有风险。
为什么有风险?让我们用两个 NBA 球员来回顾一下 K 近邻算法是如何工作的
想象一下,我们试图仅仅根据场均助攻数和场均篮板数来对球员进行分类。如果我们要对每个球员绘图(x 轴是助攻,y 轴是篮板),那么助攻和篮板数高的球员会聚集在一起,而助攻和篮板数低的球员会聚集在一起。勒布朗·詹姆斯职业生涯场均 7.4 次助攻(APG)和 7.4 个篮板(RPG),拉塞尔·维斯特布鲁克场均 8.3 次助攻和 7.1 个篮板,贾森·基德场均 8.7 个 APG 和 6.3 个 RPG。拉塞尔·维斯特布鲁克和贾森·基德是控卫,而勒布朗·詹姆斯(至少在他职业生涯的大部分时间里)是小前锋。在这种情况下,最近邻算法会选择将勒布朗归类为控球后卫。显然,我们可以让勒布朗成为一个控球后卫(或者更好的是一个前锋),但这是一个稍微不同的讨论。
随机森林
为什么不适合:过度适合的严重问题。
使用随机森林和 XGBoost,我能够分别获得 78.2%和 83.8%的训练准确率。遗憾的是,模型在测试集上的准确率分别只有 68.6%和 69.5%。显然,精确度的差异使得随机森林算法对于该问题不可行。当我们思考随机森林如何工作时,我们可以发展出一些直觉,为什么这个算法可能不适用于这个数据。本质上,随机森林只是一组决策树,每个决策树都在建立一个阈值,将我们的数据分成不同的组。如果随机森林中的一个决策树建立了一个阈值,所有平均得分至少为 5 PPG,5 APG 和 5 RPG 的球员都是小前锋,我们会很快发现这个决策树通常是错误的。毫无疑问,所有位置都有很多球员达到或超过了这些平均水平。如果我们有一个随机森林,上面描述的决策树应用于一个看不见的测试集,那些阈值可能不适用。
那么,什么模型起作用了呢?
该数据的最佳表现模型是支持向量机。我能够获得 74%的交叉验证训练准确率和 73%的测试准确率。这两个准确性分数非常接近,我们可以得出结论,我们有一个通用的模型,可以根据位置对 NBA 球员进行分类。作为参考,以下是使用GridSearchCV找到的该模型的最佳参数:
{'svc__C': 1,
'svc_gamma': 'scale',
'svc_kernel': 'linear',
'svc_proability: True}
我们知道在 NBA 有很多球员有足够的天赋去打多个位置。这意味着我们可以忍受模型中的一些错误分类。为了更好地理解模型的表现,让我们来讨论一些数字并看看混淆矩阵(阅读这篇文章来理解如何解释多类混淆矩阵)。

位置预测的混淆矩阵(图片由作者提供)
看上面的混淆矩阵,24%的大前锋被归为中锋,22%的小前锋被归为得分后卫,20%的得分后卫被归为小前锋。考虑到这些位置可能会根据玩家(和团队)的不同而互换,我们需要查看特定的玩家错误分类,以了解这些错误分类是否可以接受。
上面最糟糕的错误分类可能是一个中锋被预测为小前锋,两个得分后卫被预测为大前锋。我们也可以看看被错误归类为小前锋的 14 名大前锋。在今天的 NBA,如果这些大前锋中的一些人拥有必要的投篮能力和控球技术来打小前锋的位置,这并不奇怪。
快速闪回 2016 年 NBA 总决赛
为了看到一个玩家的位置概率,我用一个叫做 .position_breakdown() 的方法创建了一个 Player 类,让我们可以很容易地看到模型是如何看到每个玩家的。方便的是,【basketball-reference.com】也有类似的功能,可以估算一个球员在某个位置上花费的时间百分比。这些估计可以为模型预测的位置概率提供基准。
我们将实例化一个玩家对象,并如下调用 .position_breakdown() 方法来生成之前看到的饼状图:
LeBron = Player('LeBron', 'James', '15-16')LeBron.position_breakdown()
正如我在介绍中提到的,我们可以使用位置概率来评估不同的阵容,并希望了解这些阵容在与对手对抗时有什么优势或劣势。
首先,2015-2016 赛季克利夫兰骑士队:





2015-2016 赛季骑士首发阵容的位置概率(图片由作者提供)
2015–2016 金州勇士队:





2015-2016 赛季勇士首发阵容的位置概率(图片由作者提供)
除了安德鲁·博古特,我们有理由认为 2016 年 NBA 总决赛的首发球员能够胜任多个位置。当与比赛电影和球探报告搭配时,克里夫兰教练组可能会利用这些位置概率,通过可能迫使他转向一个更小更灵活的球员并利用这种不匹配来在进攻上利用安德鲁·博古特。教练组也可能选择让特里斯坦·汤普森换一名边锋,让凯文·乐福打中锋,勒布朗打大前锋。凯文·乐福三分球的能力可能会让博古特离开内线,让骑士队在勒布朗有更清晰的篮下路线的地方执行比赛。
通过进一步的模型调整,我们还可以查看这些位置概率以及加减统计数据,以便我们可以评估不同阵容的生产。
思考的食粮:年复一年的变化
这里有更多我认为感兴趣的玩家位置分析!篮球是一项团队运动,你在球场上的角色/位置会受到球场上其他人的影响。
以下是 2015-16 赛季俄克拉荷马雷霆队的凯文·杜兰特队与 2016-17 赛季金州勇士队的凯文·杜兰特队的比赛情况:


凯文·杜兰特位置概率的逐年比较(图片由作者提供)
这是拉塞尔·维斯特布鲁克在同一时期的位置概率:


拉塞尔·维斯特布鲁克位置概率的逐年比较(图片由作者提供)。回想一下,在 2016-17 赛季,拉塞尔·维斯特布鲁克自 1962 年奥斯卡·罗伯斯顿完成这一壮举以来,首次获得了场均三双。威斯布鲁克也是当年的 NBA MVP。
下一步措施和未来考虑事项
现在我们有了一个可以预测位置概率的基线模型,我们当然希望改进它!改进该模型的一种可能方法是使用主成分分析来尝试并减少进行预测所需的特征数量。上述支持向量机使用了 51 个不同的变量,通过消除那些不重要的变量,我们可能能够提高模型的准确性。在向非技术利益相关者(如教练、球探或前台)解释模型性能和各种统计数据的重要性时,变量越少越有帮助。
一个未来的 NBA 位置分类器也可能在评估大学选秀前景或未签约的自由球员时有用。与他们的大学球队相比,某些大学球员可能更适合 NBA 球队的不同位置。不幸的是,其他国家的大学或篮球联盟并不容易获得球员跟踪数据。我认为为 WNBA 和女子大学篮球创造同样的模式也是有益的,但是在女子篮球中,对球员跟踪系统的采用落后于。
建立一个 NBA 位置分类器也提出了另一个重要的问题。有了一个 73%准确的模型,传统的 5 个位置是否仍然足以描述不同的比赛风格?我们看到,在 5 个位置中的 3 个位置上,至少有 25%的球员被错误分类。我认为,这些球员中的一些根本没有被错误分类,而是他们拥有足够多才多艺的技能,不适合五个传统位置中的一个。你可能听说过像组合后卫、3&D、伸展 4 或前锋这样的术语。虽然这些不是新词,而且有证据表明这些类型的球员可以追溯到 NBA 篮球的早期时代,但它们已经成为 NBA 词典中更频繁使用的术语。幸运的是,已经有研究和分析试图更好地描述 NBA 名单上球员的不同角色(如果你对以上任何内容感兴趣,一定要阅读这篇文章)。
如果你做到了这一步,非常感谢你的阅读!这有点长,但我想写一篇对篮球迷和数据科学家都有深刻见解的博客。一如既往,欢迎任何反馈。你可以在我的 Github 中找到我的整个项目。
如果你想讨论更多关于数据科学或篮球分析的问题,请随时联系 LinkedIn 或 Twitter !
公平密码解密
用于解密使用 Playfair 密码编码的字符串的 c 代码
如果你还没有读过这篇关于 Playfair 加密的文章,先去看看吧!它简要介绍了 Playfair 密码以及大部分代码背后的逻辑。
内容

由 iMattSmart 在 Unsplash 上拍摄的照片
公平密码
假设关键字是' Charles ',解密过程如下。绘制一个 5x5 的矩阵,在每个单元格中填充字母,从关键字开始,后面是字母表中的字母。I/J 填充在同一个单元格中。所有重复的字母都被去掉了,给了我们这个矩阵-

作者图片-关键字'查尔斯'的公平矩阵
给定一个密文句子,它被分割成多个二元体。对于密文' gddogdrqprsdhmembv ',其二进制数为-
gd do gd rq pr sd hm em bv
解密规则
- 矩阵同一行中的两个密文字母分别被左边的字母替换,该行的最后一个元素循环跟随第一个元素。
sd 将由 eb 代替
gi 将被替换为 ng
2.落在矩阵的同一列中的两个密文字母被上面的字母替换,该列的底部元素循环地跟随顶部。
我的将被 dt 取代
yr 将由 ty 代替
3.否则,一对密文中的每一个字母都将被另一个密文字母所在的行和列所替换。
动力局会被我取代
do 将被 et 取代
遵循这些规则,明文变成了'在桥上见我 ' (x 在最后被忽略,因为它是一个填充字母)。
实施情况
首先,我们导入所需的库,并定义一个足够大的大小来分配要解密的密文。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 100
现在,我们编写一个函数,使用 Playfair 密码对密文进行解密。字符串被转换为小写字母,并生成 5x5 键正方形。为此,首先使用一个 26 字符的 hashmap 来存储密钥字符串中每个字母的计数。使用这种方法,矩阵中的每个单元格首先用关键字字符串字母填充,并且通过减少 hashmap 中的计数只填充一次。然后填充剩余的字母表。
然后,在有向图中搜索密文中的每个字符,并找到其位置。根据字符对中字符的相对位置,遵循上述详细规则,执行解密,并返回解密后的字符对。一旦两个数字连在一起,就可以通过看字母而忽略填充字母如 x 等来造句。
加密和解密实现之间的主要区别在于极限情况;当字母在第一行或第一列时。在加密的情况下,最后一行或最后一列中的字母必须由第一行中的字母替换,这种代码由模数为 5 的运算处理。然而,对于解密,必须为这些极限情况编写单独的 if 子句。
// Function to decrypt using the Playfair Cipher
void PlayfairDeCrypt(char str[], char keystr[])
{
char ps, ks, keyT[5][5];
// Key
ks = strlen(keystr);
// ciphertext
ps = strlen(str);
// Convert all the characters of a string to lowercase
// Can also use the library function toLower here, but a function was written for better understanding of ascii values.
void toLowerCase(char plain[], int ps)
{
int i;
for (i = 0; i < ps; i++) {
if (plain[i] > 64 && plain[i] < 91)
plain[i] += 32;
}
}
// generates the 5x5 key square
void generateKeyTable(char keystr[], int ks,
char keyT[5][5])
{
int i, j, k, flag = 0, *dicty;
// a 26 character hashmap to store count of the alphabet
dicty = (int*)calloc(26, sizeof(int));
for (i = 0; i < ks; i++) {
if (keystr[i] != 'j')
dicty[keystr[i] - 97] = 2;
}
dicty['j' - 97] = 1;
i = 0;
j = 0;
for (k = 0; k < ks; k++) {
if (dicty[keystr[k] - 97] == 2) {
dicty[keystr[k] - 97] -= 1;
keyT[i][j] = keystr[k];
j++;
if (j == 5) {
i++;
j = 0;
}
}
}
for (k = 0; k < 26; k++) {
if (dicty[k] == 0) {
keyT[i][j] = (char)(k + 97);
j++;
if (j == 5) {
i++;
j = 0;
}
}
}
}
// Search for the characters of a digraph in the key square and return their position
void search(char keyT[5][5], char a, char b, int arr[])
{
int i, j;
if (a == 'j')
a = 'i';
else if (b == 'j')
b = 'i';
for (i = 0; i < 5; i++) {
for (j = 0; j < 5; j++) {
if (keyT[i][j] == a) {
arr[0] = i;
arr[1] = j;
}
else if (keyT[i][j] == b) {
arr[2] = i;
arr[3] = j;
}
}
}
}
// Function to decrypt
void decrypt(char str[], char keyT[5][5], int ps)
{
int i, a[4];
for (i = 0; i < ps; i += 2) {
search(keyT, str[i], str[i + 1], a);
if (a[0] == a[2]) {
if(a[1]==0){
str[i] = keyT[a[0]][4];
str[i + 1] = keyT[a[0]][(a[3]-1)%5];
}
else if(a[3]==0){
str[i] = keyT[a[0]][(a[1] - 1)%5];
str[i + 1] = keyT[a[0]][4];
}
else{
str[i] = keyT[a[0]][(a[1] - 1)%5];
str[i + 1] = keyT[a[0]][(a[3]-1)%5];
}
}
else if (a[1] == a[3]) {
if(a[0]==0){
str[i] = keyT[4][a[1]];
str[i + 1] = keyT[(a[2]-1)%5[a[1]];
}
else if(a[2]==0){
str[i] = keyT[(a[0] - 1)%5][a[1]];
str[i + 1] = keyT[4][a[1]];
}
else{
str[i] = keyT[(a[0] - 1)%5][a[1]];
str[i + 1] = keyT[(a[2]-1)%5][a[1]];
}
}
else {
str[i] = keyT[a[0]][a[3]];
str[i + 1] = keyT[a[2]][a[1]];
}
}
}
ks = removeSpaces(keystr, ks);
toLowerCase(str, ps);
ps = removeSpaces(str, ps);
generateKeyTable(keystr, ks, keyT);
decrypt(str, keyT, ps);
//plain text printed in lower case letters
printf("Plain text: %s\n", str);
}
驱动程序代码只接受输入的密钥字符串和输入的密文,并调用 PlayfairDeCrypt 函数输出解密后的字符串。
// Driver code
int main()
{
char str[SIZE], keystr[SIZE];
//Key used - to be enered in lower case letters
printf("Enter the key: ");
scanf("%[^\n]s", &keystr);
printf("Key text: %s\n", keystr);
printf("Enter the ciphertext: ");
scanf("\n");
scanf("%[^\n]s", &str);
printf("Cipher text: %s\n", str);
//Calling the PlayfairDeCrypt function
PlayfairDeCrypt(str, keystr);
return 0;
}
一些密文的输出
使用密钥字符串' diskjockey '对上述代码进行编译、运行并对一些密文进行测试。
- RBIABDIGTPSZ
- REBSLUMNGYXYNBLFCR
- QTBPCPSCDZLXYBQTDMYIKDTKUFGEQDSIYEITBQGYGDGAKW



图片由作者-输出的一些密文使用 C 代码的公平游戏解密
本文中使用和解释的全部代码可以在这里找到。
进一步阅读
- 项目 Playfair 密码编译码的实现
- 艾琳·鲍德温——一篇关于公平竞赛密码的论文
- 帕尔,拉马尼,艾扬格,苏尼塔。公平游戏密码的一个变种
公平密码加密
使用 Playfair 密码加密字符串的 c 代码
内容

法比奥于 Unsplash 发表
介绍
密码学是秘密写作的科学或艺术。加密技术的基本目标是让两个人能够在不安全的信道上进行通信,使对方无法理解对方在说什么。目前使用的加密技术主要有两种
- 对称密钥加密-加密和解密使用相同的密钥
- 非对称密钥加密-一个密钥用于加密,另一个用于解密
还有许多其他类型的密码,如单表和多表、流和分组等。这篇文章着眼于 Playfair 密码及其使用 C 函数的应用。
公平密码
由查尔斯·惠斯通发明的公平密码(Playfair cipher)是一种多字母替代密码,这意味着一个字母可以在其加密中由不同的字母表示,这取决于给双方使用的关键字。例如,让我们假设关键字是'查尔斯'。绘制一个 5x5 的矩阵,在每个单元格中填充字母,从关键字开始,后面是字母表中的字母。I/J 填充在同一个单元格中。所有重复的字母都被去掉了,给了我们这个矩阵-

作者图片-关键字'查尔斯'的公平矩阵
给定一个明文句子,它被分割成两个字母组,去掉所有空格,并在字母数为奇数的情况下用字母 x 填充。重复的纯文本字母用填充字母(如 x)分隔。给定句子“在桥上见我”,两个数字将是-
在 eb ri dg ex 见我
加密规则
- 矩阵同一行中的两个明文字母分别被右边的字母替换,该行的第一个元素循环跟随最后一个元素。
eb 将由 sd 代替
ng 将由 gi/gj 代替
2.落在矩阵的同一列中的两个明文字母被下面的字母替换,该列的顶部元素循环跟随底部元素。
dt 将被我的
ty 将由 yr 代替
3.否则,一对中的每个明文字母都将被位于其自己的行中的字母和被另一个明文字母占据的列所替换。
我会被 gd 取代
et 将被 do 取代
遵循这些规则,密文变成 'gd do gd rq pr sd hm em bv' 。
这种密码比简单的替换更安全,但是通过对字母对进行统计频率计数,仍然容易受到纯密文攻击,因为每对字母总是以相同的方式加密。此外,短关键字使得 Playfair 密码更容易破解。
实施情况
首先,我们导入所需的库,并为要加密的字符串分配定义一个足够大的大小。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 100
现在我们编写一个函数来使用 Playfair 密码加密明文。字符串被转换为大写,所有空格都被删除。明文被填充为偶数长度,字母对变得不相同。生成 5x5 的关键正方形。为此,首先使用一个 26 字符的 hashmap 来存储密钥字符串中每个字母的计数。使用这种方法,矩阵中的每个单元格首先用关键字字符串字母填充,并且通过减少 hashmap 中的计数只填充一次。然后填充剩余的字母表。
然后在有向图中搜索明文中的每个字符,并找到它的位置。根据字符对中字符的相对位置,遵循上述详细规则,执行加密,并返回加密的字符对。
// Function to encrypt using the Playfair Cipher
void PlayfairCrypt(char str[], char keystr[])
{
char keyT[5][5], ks, ps;
// Key
ks = strlen(keystr);
// Plaintext
ps = strlen(str);
// Function to convert the string to uppercase
// Can also use the library function toUpper here, but a function was written for better understanding of ascii values.
void toUpperCase(char encrypt[], int ps)
{
int i;
for (i = 0; i < ps; i++) {
if (encrypt[i] > 96 && encrypt[i] < 123)
encrypt[i] -= 32;
}
}
// Function to remove all spaces in a string
int removeSpaces(char* plain, int ps)
{
int i, count = 0;
for (i = 0; i < ps; i++)
if (plain[i] != ' ')
plain[count++] = plain[i];
plain[count] = '\0';
return count;
}
// Function to generate the 5x5 key square
void generateKeyTable(char keystr[], int ks, char keyT[5][5])
{
int i, j, k, flag = 0, *dicty;
// a 26 character hashmap to store count of the alphabet
dicty = (int*)calloc(26, sizeof(int));
for (i = 0; i < ks; i++) {
if (keystr[i] != 'j')
dicty[keystr[i] - 97] = 2;
}
dicty['j' - 97] = 1;
i = 0;
j = 0;
for (k = 0; k < ks; k++) {
if (dicty[keystr[k] - 97] == 2) {
dicty[keystr[k] - 97] -= 1;
keyT[i][j] = keystr[k];
j++;
if (j == 5) {
i++;
j = 0;
}
}
}
for (k = 0; k < 26; k++) {
if (dicty[k] == 0) {
keyT[i][j] = (char)(k + 97);
j++;
if (j == 5) {
i++;
j = 0;
}
}
}
}
// Function to search for the characters of a digraph in the key square and return their position
void search(char keyT[5][5], char a, char b, int arr[])
{
int i, j;
if (a == 'j')
a = 'i';
else if (b == 'j')
b = 'i';
for (i = 0; i < 5; i++) {
for (j = 0; j < 5; j++) {
if (keyT[i][j] == a) {
arr[0] = i;
arr[1] = j;
}
else if (keyT[i][j] == b) {
arr[2] = i;
arr[3] = j;
}
}
}
}
// Function to make the plain text length even, and make pairs unidentical.
int prepare(char str[], int ptrs)
{
int i, j, subs_s = ptrs;
for (i = 0; i < subs_s; i += 2) {
if(str[i]==str[i+1]){
for(j=subs_s; j>i+1; j--){
str[j]=str[j-1];
}
str[i+1]='x';
subs_s+=1;
}
}
str[subs_s]='\0';
if (subs_s % 2 != 0) {
str[subs_s++] = 'z';
str[subs_s] = '\0';
}
return subs_s;
}
// Function for performing the encryption
void encrypt(char str[], char keyT[5][5], int ps)
{
int i, a[4];
for(i=0; i<ps; i+=2){
search(keyT, str[i], str[i + 1], a);
if (a[0] == a[2]) {
str[i] = keyT[a[0]][(a[1] + 1)%5];
str[i + 1] = keyT[a[0]][(a[3] + 1)%5];
}
else if (a[1] == a[3]) {
str[i] = keyT[(a[0] + 1)%5][a[1]];
str[i + 1] = keyT[(a[2] + 1)%5][a[1]];
}
else {
str[i] = keyT[a[0]][a[3]];
str[i + 1] = keyT[a[2]][a[1]];
}
}
}
ks = removeSpaces(keystr, ks);
ps = removeSpaces(str, ps);
ps = prepare(str, ps);
generateKeyTable(keystr, ks, keyT);
encrypt(str, keyT, ps);
toUpperCase(str, ps);
//cipher text - printed in upper case letters
printf("Cipher text: %s\n", str);
}
驱动程序代码只接受输入密钥字符串和输入明文,并调用输出加密字符串的 PlayfairCrypt 函数。
// Driver code
int main()
{
char str[SIZE], keystr[SIZE];
//Key used - to be entered in lower case letters
printf("Enter the key: ");
scanf("%[^\n]s", &keystr);
printf("Key text: %s\n", keystr);
// Plaintext to be encrypted - entered in lower case letters
printf("Enter the plaintext: ");
scanf("\n");
scanf("%[^\n]s", &str);
printf("Plain text: %s\n", str);
//Calling the PlayfairCrypt function
PlayfairCrypt(str, keystr);
return 0;
}
一些纯文本的输出
使用关键字字符串' diskjockey '对上述代码进行编译、运行和测试。
- 这批货将于中午到达
- 周五之前保持低调
- 永远走后门
- 电话被窃听了




作者的图片——使用 Playfair 加密的 C 代码输出一些纯文本
本文中使用和解释的全部代码可以在这里找到。
进一步阅读
- 项目-实现 Playfair 密码的编码和解码
- 艾琳·鲍德温——一篇关于公平游戏密码的论文
- 帕尔,拉马尼,艾扬格,苏尼塔。公平游戏密码工作方式的变化
- Playfair 密码解密
和一个广义的人工智能下棋
将 MuZero 和感知者 IO 结合起来创建一个通用的 AI

作者图片
大家好,今天我们将改进我们在我的构建国际象棋引擎第二部分中创建的国际象棋人工智能,将其转换为一个更通用的系统。这节课又是技术性的,所以请耐心听我说。我试图提供方程式和图表来帮助使事情变得简单一点。
对于这个新的人工智能,我们将通过使用其强大的搜索方法建立在穆泽罗的基础上。MuZero 是由 DeepMind 在一份白皮书中提出的,作为对 AlphaZero 的改进。在这份白皮书中,MuZero 在多个游戏中实现了超人的性能,而没有精确的模拟器使其成为一种独特的算法。
MuZero 的前身 AlphaZero 实现了超人的性能,使得两种算法之间的性能不相上下。然而,AlphaZero 在其规划算法中使用了外部模拟器,这使得它不如 MuZero 通用。对这些外部模拟器的依赖限制了 AlphaZero 使用预先制作的模拟器来完善知识游戏。然而,MuZero 已经取消了这些外部模拟器,允许它执行多种任务。移除外部模拟器可能会让你认为 MuZero 使用了某种形式的无模型强化学习(RL)。然而,这是不真实的,MuZero 使用一个学习模型来表示其手头任务的动态。
我们的人工智能将在某些方面与论文不同,因为我们将不会使用相同的模型架构,而是将使用基于感知者 IO 的架构。感知者 IO 是一个通用模型,可以处理各种数据类型。这种普遍性对我们很有吸引力,因为它与穆泽罗的一般性质很好地匹配。
搜索
MuZero 是直接从 AlphaZero 建立起来的,所以你会发现他们有很多相似的意识形态。这些类似的思想包括用深度学习模型指导蒙特卡罗树搜索 (MCTS)算法的想法。然而,如上所述,MuZero 并不使用外部模拟器来预测其 MCTS,而是使用学习动力学模型。这个动力学模型是独特的,因为它不试图预测所选动作的精确表示,而只是试图预测隐藏状态表示。这种表示简化了学习过程,允许动态模型学习用于精确规划的规则。在这个新的搜索算法中,我们将改变旧的置信上限(UCB)公式。

旧 UCB 方程式|作者图片
我们将用本文中找到的多项式上置信树(pUCT)方程代替我们的旧 UCB 方程。

pUCT 方程式|作者图片
当比较这两个等式时,你会发现它们非常相似,都试图平衡 MCTS 问题中所面临的勘探和开采困境。新方程在我们的旧 UCB 方程中增加了一个额外的超参数(c2)。有了这个新的等式,c2 与父节点访问成比例地衰减。以这种方式缩小 c2 鼓励搜索算法对于频繁访问的父节点变得更少利用。我们的新 pUCT 公式的另一个不同之处是我们对 Q 值进行了归一化。将我们的 Q 值归一化会迫使它变成一个百分比。将我们的 Q 值作为百分比是有益的,因为它允许等式的探索部分具有更大的影响。

标准化 Q 方程|作者图片
我们采用的另一个技巧是在训练期间在根节点将狄利克雷噪声添加到我们的策略值中。添加这种噪音增强了探索,允许人工智能在训练时思考更多的想法。这种噪声将导致性能差的节点被访问。然而,在进一步的搜索中,性能差的节点将被 pUCT 方程修剪掉。pUCT 等式修剪了性能差的节点,因为我们仅将噪声应用于等式的探索侧,这使得高价值移动的利用能够继续。

狄利克雷噪声方程|图片由作者提供
有了新的人工智能,我们也调整了节点反向传播过程中的更新值公式。父节点的值在 gamma 折扣的帮助下得到更新。

伽玛折扣公式|作者图片
更新节点时,gamma 折扣和预测值之和将被归一化。像这样更新节点值通过创建自然衰减实现了更好的近似,这将导致 AI 更喜欢接近当前节点的更高值。

更新价值方程式|作者图片
经过这些改变,我们的新 MCTS 算法看起来像这样。

穆泽罗·MCTS |作者图片
在我们的游戏树上运行 MCTS 后,最好的行动是访问最多的节点。为了以概率分布的形式获得最佳动作,我们根据每个动作的访问次数对其进行归一化。据信,与单独使用我们的预测神经网络(NN)相比,像这样使用 MCTS 允许开发增强的策略。在训练过程中,我们根据当前游戏移动计数安排一个衰减到我们的温度变量,以增加每个游戏早期的探索。当我们的温度变量达到零时,我们选择最常访问的动作,允许 AI 利用它的 MCTS 知识。当评估 AI 时,我们将温度变量设置为 1,因此它没有影响。

最佳动作方程式|作者图片
模型架构
正如引言所述,我们不会使用 MuZero 论文中的模型。相反,我们将使用一种感知者 IO 架构,这是 DeepMind 发布的一种新模型,是他们的 Perciver 模型的前身。
感知者模型是一种基于注意力的方法,试图解决普通变形金刚的一些缺点。香草变形金刚最近在很多领域都非常成功。然而,由于变压器中存在的二次缩放问题,大输入空间的实现并不成功。像变形金刚这样的感知者非常依赖注意力机制。因此,你可能会认为这个二次缩放问题仍然存在。然而,感知者使用多头交叉注意力和多头自我注意力的组合。这种组合通过将高维数据投影到较低的维度来克服这个问题。

注意力等式|作者图片
自我关注使用相同大小的 Q 值、V 值和 K 值,迫使所有计算保持相同的大小。

自我关注|作者图片
相反,交叉注意力使用不同大小的 Q 值,这有效地将计算缩放到与 Q 值相同的大小。

交叉关注|作者图片
既然我们理解了交叉注意和自我注意的区别,我将解释感知者如何将两者结合起来。

感知建筑|图片来自 https://arxiv.org/abs/2103.03206
感知者接收单个输入数组,在上面表示为字节数组。而被指定为潜在阵列的第二个变量是一组学习的权重。潜在数组作为 Q 值传递给交叉注意块,交叉注意块成功地缩小了输入(假设潜在数组小于输入)。像这样缩小输入阵列是感知者减轻二次缩放问题的方式。潜在变换器块然后对交叉关注块的结果执行自关注。潜在变换器块可以使用自我关注,而不会负面影响处理时间,因为输入大小的减小使得二次缩放影响较小。潜在转换器的输出被递归地传递给另一个潜在转换器,次数不限。在潜在变换之后,可以选择对结果潜在层和输入数据进行交叉关注。据信,这一步骤允许模型关注输入数据的其他部分,而这些部分可能是之前的交叉注意所遗漏的。这种信念来自于这样一个事实,即你的潜在阵列现在是不同的,因为它刚刚被先前通过这个模型块所转换。该交叉注意块的输出然后将进入另一个潜在变换器的递归。这种将潜在变形者的结果传递给另一个交叉注意模块的过程可以发生很多次。交叉关注块和潜在变换块之间的所有权重都可以选择在它们之间共享。这意味着我们可以将数据传递到同一个交叉注意模块/潜在转换模块,或者使用具有自己的一组权重的独立网络。

感知者 IO 建筑|图片来自https://arxiv.org/abs/2107.14795
既然我们理解了感知者的架构,那么感知者 IO 的架构就不会那么难了。Perciveir IO 为感知者添加了两个交叉注意力模块,以将输入和输出数据缩放到其所需的形状和大小。这个技巧是有益的,因为它允许在各种情况下使用这个模型,因为我们的数据的形状和大小不再具有影响力。
我们的模型
既然我们理解了在感知者 IO 架构中实现的基本思想,我可以解释它是如何适应这个人工智能的。
我们的人工智能通过拥有多个模型来使用与 MuZero 相同的基本思想。然而,我们并不坚持本文中提出的三模型格式,而是使用两个模型。
第一个模型是交叉注意模型,用于表示游戏的当前隐藏状态,与 MuZero 中的表示模型具有相同的目的。这个模型可以被认为是我们的感知者 IO 架构的输入模型,因为它将输入投射到我们的第二个模型可以处理的标准维度。这个模型接收了与我们在构建国际象棋引擎第二部分中的 AI 相同的编码游戏状态。

隐藏状态方程|作者图片
第二个模型稍微复杂一些,因为它是一个多任务模型。这个多任务模型处理 MuZero 中的预测功能和动态功能将执行的所有任务(价值、政策、奖励、下一状态)。由于任务之间的关系,我们在这里使用多任务模型而不是单个模型。这种关系使得多任务模型变得有益,因为它导致了每个任务之间共享特性的概念。这个模型由两部分组成,一个主干和独立的专用头。

多任务架构|作者图片
骨干通过感知者网络学习广义的游戏知识,并充当该网络的自动编码器层。这一层获得一个隐藏状态表示和一个动作令牌作为输入。

主干方程|作者图片
价值头是一个用来预测游戏结果的交叉注意力模型。这一层是我们感知 IO 架构的输出之一。这一层将主干作为输入,并将其投射到一个表示结果的值(-1:1)。

价值函数方程|作者图片
策略头是一个交叉注意力模型,用于预测采取每个动作的概率分布。这一层是我们感知者 IO 架构的另一个输出。这一层使用主干作为输入,并将其投影到一个向量,该向量表示采取每个动作的概率分布。

政策函数方程|作者图片
状态头是一个交叉注意模型,用于预测执行所需动作的隐藏状态表示。这一层是我们感知 IO 架构的另一个输出。这一层使用主干作为输入,并将其投射到执行所需动作的隐藏状态表示中。

下一个状态函数方程|作者图片
奖励头是一个交叉注意力模型,用于预测执行所需动作的即时奖励。这一层是我们感知 IO 架构的输出之一。这一层将主干作为输入,并将其投射到一个代表即时奖励的值(对于像国际象棋这样的棋盘游戏为零)。

奖励函数方程|作者图片
培养
类似于 MuZero 和我们之前的 AI,我们的模型完全通过自我游戏来学习。通过我们的训练,我们通过拥有一个活跃的模型和一个新的模型来增加一个进化的方面。新模型通过自我发挥来训练自己,而主动模型是我们目前表现最好的模型。为了将潜在的收益递减最小化,这两个模型以循环赛的方式进行比赛。在这里,循环赛的获胜者将成为新的活动模型。

培训流程|作者图片
在训练时,我们将这两个模型分成五个独立的部分(价值、策略、下一状态、奖励、主干、隐藏状态)。我们以这种方式将两个模型分开,以单独微调第二个模型的每个头部。单独微调每个头部允许模型更有效地学习每个任务,因为不会受到其他头部梯度的干扰。在训练时,我们使用 Adam 优化器来微调所有五个部分。
为了训练我们的第二个模型的价值头,我们使用在每个自我游戏结束时发现的最终结果作为我们的目标价值。当训练该头部时,均方差(MSE)被用于我们的损失函数。我们在这里使用均方误差损失函数,因为我们这一层的输出是一个回归。

价值水头损失函数|作者图片
为了训练我们的第二个模型的策略头,我们使用在自我游戏期间发现的记录的 MCTS 策略作为我们的目标值。在训练这个头部时,二进制交叉熵(BCE)被用于我们的损失函数。我们在这里使用二元交叉熵损失函数,因为这个头部具有多变量分类输出。

保单水头损失函数|作者图片
为了训练我们第二个模型的下一个国家元首,我们从论文有效零中吸取了一些想法。高效零使用自我监督学习来提高训练 MuZero 的动力学模型的速度。

自我监督学习|图片来自 https://arxiv.org/abs/2111.00210
这里我们实现了同样的想法,将已知的下一个状态传递给隐藏状态模型。然后,我们将结果用作损失函数的均方误差计算中的目标值。我们在这里使用均方误差损失函数,因为这一层的输出是多变量回归。

下一个国家元首损失函数|作者图片
为了训练我们的第二个模型的奖励头,我们使用在自我游戏中发现的记录奖励作为我们的目标值。当训练该头部时,均方差(MSE)被用于我们的损失函数。我们在这里使用均方误差损失函数,因为我们这一层的输出是一个回归。

奖励人头损失功能|图片作者
为了训练我们的第二个模型的主干,我们使用单个头损失的总和作为总损失。在这里,我们将所有头部的损失相加,以了解所有任务的游戏表示。

主干损失函数|图片由作者提供
为了训练我们的隐藏状态模型,我们将第二个模型的损失相加,将下一个状态的损失排除在总损失之外。我们已经排除了下一状态损失,因为隐藏状态模型的输出用于下一状态损失计算。

隐藏状态损失函数|作者图片
谢谢
现在你知道了,我们已经成功地将我们的国际象棋人工智能升级到一个更通用的系统。这个 AI 将在没有任何知识的情况下开始学习国际象棋的游戏,包括规则。它玩的训练游戏越多,表现就越好。你可以在我的 GitHub 上查看完整版本的代码。你也可以在这里看到我之前的 AI 。
感谢阅读。如果你喜欢这样,可以考虑订阅我的账户,以便在我最近发帖时得到通知。
参考
- 【https://arxiv.org/abs/1911.08265
- https://deepmind.com/
- https://arxiv.org/abs/1712.01815
- https://arxiv.org/abs/2107.14795
- https://arxiv.org/abs/2103.03206
- https://en.wikipedia.org/wiki/Dirichlet_distribution
- https://arxiv.org/abs/1706.03762
- https://arxiv.org/abs/2111.00210
https://medium.com/mlearning-ai/mlearning-ai-submission-suggestions-b51e2b130bfb
在 Python 中玩禀赋效应
如何在 35 行代码中融合经济学和心理学

艾米·巴夏礼在 Unsplash 上拍摄的照片。
行为经济理论的支柱之一是禀赋效应。在本文中,我们将简要介绍它是如何工作的,为什么会工作,然后我们将编写一些代码行,以便用 Python 对其建模。
方案
想象一下一个房间里有 44 个人。随机选择其中的 22 个,给他们一个咖啡杯。之后,假设你创造了一个市场,任何人都可以交易咖啡杯。拥有咖啡杯的人会申报他们出售咖啡杯所需的最低金额(询问)。反之亦然,每个非所有者陈述他们愿意为咖啡杯支付的最高价格(出价)。一旦收集了所有的底价,我们就通过对比买卖双方来决定交易的数量。如果最高出价大于或等于最低要价,我们至少有一笔交易。如果第二高的出价大于或等于第二低的要价,我们至少有两笔交易,以此类推,直到出价低于要价(或者我们用完了底价)。所有的销售都以相同的价格进行,由接受的最高要价决定。

市场运作的例子。做了三笔交易,销售价格是 4。图片作者。
我们预计这个市场会有多少交易?根据传统的经济理论,我们可以用科斯定理【1】来回答。本质上,它认为在一个没有交易成本的市场中,资源将流向最大化其价值的用途。为了将这个定理应用到我们的场景中,让我们假设根据人们对咖啡杯的感知价值对他们进行分类,并将咖啡杯随机分配给一半人。

一个假设的感知价值量表。图片作者。
为了达到平衡,所有在天平下半部的咖啡杯必须流向最重视它们的人。因为我们随机分配了咖啡杯,所以我们希望一半的杯子放在下半部分。因此,我们可以预计交换的数量是现有咖啡杯的一半。

交易需要达到平衡。图片作者。
上面解释的设置正是 Kahneman 等人进行的实验[2]。1990 年。令人惊讶的是,他们发现真实世界场景中的平均交易次数为 2.25 次,远低于科斯定理预测的 11 次交易。这是为什么呢?
禀赋效应
上述现象可以用禀赋效应来解释。
禀赋效应(endowment effect)表明,人们更有可能保留他们拥有的物品,而不是在他们不拥有时获得同样的物品。
心理学对这种行为有明确的解释。前景理论【3】描述了个体如何不对称地评估他们的损失和收益的观点。这一理论与一种被称为损失厌恶的认知偏见密切相关,即人类倾向于避免损失,而不是获得同等的收益。
看待前景理论的一个有趣方式是通过上面显示的价值函数。它将一个人感知的效用与收益和损失联系起来。当一个人放弃某样东西时,他/她得到的痛苦远远大于他/她得到同样东西时得到的快乐。

前景理论价值函数。图片作者。
下图是实验情况。在收到咖啡杯之前,哈利对这件物品的评价低于其他人的上半部分。因此,他是转移的候选人,以防他收到杯子。但在收到杯子后,对该物体的感知有用性增加了,以至于他现在处于价值尺度的上半部分。因此,哈利不会卖掉他的杯子。同样的现象也发生在所有收到咖啡杯的人身上。
正因如此,价值尺度永远是不平衡的。上半部分将包含更多带杯子的人。它解释了为什么需要更少的交易来达到均衡。

送咖啡杯前的价值尺度。图片作者。

送完咖啡杯后的价值尺度。只需要一次交易(K → B)就可以达到均衡。图片作者。
用 Python 建模禀赋效应
最后,让我们写一些代码。首先我们声明一个名为 Sim 的类。在构造函数中,我们创建了一个名为 prior_values 的长度为 m 的列表来表示每个人对杯子的先验感知值。我们用随机值初始化它。之后,我们用 1 和 0 随机初始化一个名为 mugs 的列表,其中 1 表示这个人有一个咖啡杯,否则为 0。
run() 方法计算每个人对杯子的感知效用。它利用先验值和前景理论的价值函数。最后,我们计算遵循上述规则模拟市场的交易数量。
为了简单起见,我们用下面的分段线性函数来近似前景理论的价值函数。

这是代码。
接下来我们可能要做的是找到一个能够解释 Kahneman 等人[2]实验结果的价值函数。我们的价值函数只包含一个自由参数,所以挑战在于找到 α 的最佳值。这里 Optuna 前来救援。
首先,我们定义一个要优化的目标()函数。我们使用均方误差(MSE),这是一个众所周知的损失函数。由于模拟是随机的,我们用蒙特卡罗方法进行。给定 Optuna 、建议的值 α ,我们运行模拟 N 次,并计算所有执行的二次误差(随机种子仅出于再现性目的而固定)。
该过程被重复 1000 次。在所有试验结束时,我们打印出找到的 α 的最佳值。注意,默认情况下,Optuna 使用树形结构的 Parzen Estimator 算法建议参数,但是其他算法也是可用的。
>>> {'alpha': 8.378816205287283}
该模型的一个有趣的性质是,当(且仅当) k = m/2 时,价值函数的形状与先验值的分布无关。如果你想深化这一点,我建议你看一下 GitHub 上的完整例子。
这篇文章的灵感来源于诺贝尔奖获得者理查德·h·泰勒(Richard h . Thaler)**写的惊人之作《 行为不端:行为经济学 》一书。
1 R. H .科斯,社会成本问题 (1960),《法律与经济学杂志》
[2] D .卡尼曼、J. L .克内奇和 R. H .泰勒,禀赋效应的实验检验和科斯定理 (1990),《政治经济学杂志》
3 D. Kahneman 和 A. Tversky,前景理论:风险下的决策分析 (1979),计量经济学
请不要害怕人工智能

照片:连浩曲,Unsplash
反对围绕使用人工智能散布恐惧的论点
一天早上,你醒来发现床头柜上有一袋彩球。在这个袋子里有两种颜色的球:黑色和白色。袋子里的黑球相对于白球要多得多。从袋子里抽出一个球,相对于选择一个白球来说,选择一个黑球的概率要大得多。这个经典的概率问题可以作为一个简单的类比扩展到今天围绕人工智能的问题:黑球代表了当前可以用人工智能解决的行星级问题或当前因使用人工智能而变得复杂的问题;白球代表了人工智能驱动的技术对人类存在威胁的未来可能性。
在最近一集的《走向数据科学》播客中,柳文欢·埃齐奥尼提出了一个令人信服的论点,即关注当前围绕人工智能的问题,而不是不断发展的技术带来的哲学上的、未来的生存威胁。例如,Etzioni 的建议是,应该研究当前由大规模失业和监视状态造成的有形危机,这一建议相当有说服力。我强烈建议听一听。
哲学家和社会评论家伊凡·伊里奇会同意。1989 年,他假设“信息革命”在其早期“为工业发展的疲惫逻辑注入了新的生命”。这鼓励了人们的期望,即通过工具,人类可以摆脱自身条件的限制。”Illich 进一步指出,可持续增长的概念并没有缓解人们对发展意识形态的担忧,而是导致了一种危险的错觉,这相当于在我们的环境正在耗尽自然资源时的一种痛苦管理。
我们现在有工具来检查整个地球的物体,包括通过人工智能发展的影响。
有了相互联系的行星性和行星规模的计算,我们现在有了检查整个地球物体的工具,包括通过人工智能开发的这种影响。托拜厄斯·里斯在接受诺埃马采访时雄辩地指出,“行星性本身是由我们已经建立的技术构成的。这不是“揭示”,从某种意义上来说,有些东西总是在那里,等待展示自己;更重要的是,随着智能变得技术化,随着智能变得人工化,超越了生物有机体的狭窄范围,我们现在有了分布式智能系统,产生了一种称为行星或整个地球系统的知识对象。这个知识对象以前没有也不可能存在;这取决于技术。”以前,这个星球上散布着不可思议的巨大森林和湖泊,而现在,金属杆和电线矗立在贫瘠的田野中。技术进步和整个地球通过数据和嵌入式智能系统的互联性,使得围绕当前人类经历的地面真相得以揭示。
如果真的像本杰明·布拉顿所说的那样,“这个星球并不是像马丁·海德格尔所说的那样突然出现在‘世界图景’中,而是一个特定物种的栖息地,这个物种能够构建一个外部图像,最终呈现出这个物种及其世界出现的星球条件。它一直就在那里——但我们只是刚刚能够看到它,”人类现在不仅有工具来理解行星系统作为一个整体,而且还可以理解驱动这些系统的机制,并在其中制定变革以补救今天出现的最严重的危机,从大规模失业到威胁生命的气候变化。我们应该从 Etzioni 那里得到启示,专注于部署人工智能,而不是担心技术带来的假设存在的威胁,直到任何这样的担忧成为现实。虽然你不太可能很快醒来发现床边有一袋魔法球,但你能够围绕人工智能在现实世界中的应用运用批判性思维来解决当前的行星问题。
Danielle 是一名多学科统计学家和社会科学家,目前正在联合创办一家初创公司,其使命是正确量化所有垂直领域商品生产的碳强度,整合 E、S 和 G(环境、社会、治理)的所有方面。
使用 Keras Functional API 绘制张量流模型
在 TensorFlow 中可视化神经网络结构的快速指南
为什么要看这篇文章?
本文将向您展示如何使用 Google Colab 通过 Tensorflow 的 eager execution 和 Keras Functional API 实现一个卷积神经网络模型。然后我们将以图形形式绘制我们的模型:这种可视化允许我们有效地观察我们模型的结构,这是一个基本方面,尤其是在处理复杂模型时。

詹姆斯·哈里森在 Unsplash 上拍摄的照片
我们将得到如下图所示的图表:

模型结构—按作者分类的图像
Keras 功能 API
Keras Functional API 允许您比传统的顺序配置更灵活有效地创建模型,后者通常用于生成简单的代理模型。functional API 允许您创建具有非线性拓扑结构、共享层、多输入和多输出的模型……本质上,我们正在创建一个层图。有了这个 API,我们可以创建有向无环图,但是如果再加上自定义层和模型,以及自定义损失函数和优化器,它就可以创建功能强大且完全可定制的神经网络模型。
代码
我们创建一个顺序卷积网络模型,仅用作示例。我们使用 Keras Functional API 中的一些有用工具:
- 输入:用于实例化一个 Keras 张量。这种类似张量的物体只需要知道网络的输入和输出就可以建立一个 Keras 模型。我们指定输入形状(即全高清 RGB 图像:三维张量)和层的名称
- Conv2D : 2D 卷积层(如图像上的空间卷积)。我们指定过滤器的数量和大小以及激活函数。
- MaxPooling2D:2D 空间数据的最大池操作。我们只需要指定池的大小
- 展平:展平输入
- 密集:规则密集连接 NN 层
model.summary() 显示了模型属性的有用摘要。然而,图形可视化可能成为理解和保存模型图结构的另一个强有力的工具。

模型结构—按作者分类的图像
有趣的是注意到模型中数据流的显示,以及每一层的名称和与各种卷积和池层的应用相关的大小变化。让我们用均匀分布生成的一些数据来测试这个模型,均匀分布的形状与我们的模型输入一致,批量大小等于 1。我们还会使用指定的名称将图像自动保存为“my _ model . png”。
tf.random.uniform 对象返回随机值,具有均匀分布,具有指定的形状和类型。有趣的是,由于问题中的数据是浮点数,取值范围自动设置为【0,1】。如果需要,可以将最小和最大采样范围作为参数传递给该调用。
结论
本文就到这里,希望对你有用。你可以在这里找到源代码,在 GitHub 和 YouTube 上可以找到完整的资源库。
下次见,
马可
3 分钟机器学习
3 分钟机器学习是一系列与人工智能、深度学习和数据科学相关的教程、视频和文章。你可以在 YouTube 上找到完整的视频集。GitHub 库包含了文章和视频中显示的所有 Google Colab 笔记本。希望这些内容对你有用,或者只是你感兴趣。欢迎任何反馈。
查看其他剧集:
使用 Geopandas 绘制带有 shapefiles 的 choropleth 地图
Choropleth 图是有用且强大的可视化工具,它通过根据值进行着色或图案化的区域或地区来呈现数据。这有点像热图,但在地理形状上有所不同。
我经常使用它,因为它对用户来说很清楚区域在哪里,也方便他们快速比较区域之间的数据。如果您正在创建一个交互式仪表板,choropleth maps 将帮助读者按区域进行切片和切块,并使仪表板更加生动,而不仅限于图表或表格。

杰克·斯台普顿在 Unsplash 上拍摄的照片
我以前在 Power BI 中使用 choropleth 地图的经验是,大量的形状都是默认的,没有自定义自己地图的灵活性。例如,在处理犯罪数据时,我希望按警区而不是城市或地区来显示数据,这是 Power BI shape map 无法实现的。使用 Python 中的 Geopandas 的好处是,只需几行代码就可以轻松实现。
先决条件:
- Python 包:
import pandas as pd
import matplotlib.pyplot as plt #**if using matplotlib**
import plotly.express as px #**if using plotly** import geopandas as gpd
- 形状文件:
新西兰警区的文件是从 Koordinates 下载的。
为可视化准备数据
**#set up the file path and read the shapefile data**fp = "nz-police-district-boundaries.shx"
map_df = gpd.read_file(fp)
map_df.to_crs(pyproj.CRS.from_epsg(4326), inplace=True)**#read the csv data** df = pd.read_csv('District.csv')
您可能想要检查并查看 shapefile 的外观:
**#see what the map looks like** map_df.plot(figsize=(20, 10))

图 1:地图和表格
您可能注意到,' District.csv '和' map_df '之间有些不匹配,例如 csv 文件中的' 县 Manukau '和地图文件中的' 县/Manukau' 。因此,我们需要在创建地图之前整理这些内容:
**#rename one of the columns**
map_df = map_df.rename({'DISTRICT_N': 'District'}, axis = 'columns')**#drop a column**
map_df = map_df.drop(columns = 'DISTRICT_I')**#replace the values in the dataframe**
map_df = map_df.replace(['Counties/Manukau','Northland'],['Counties Manukau','Northen'])

图 2:表格 _ 修订版
创建 choropleth 图之前的最后一步是合并两个数据集。这个过程类似于 SQL:
df_merged = map_df.merge(df, **#map_df merge to df**
left_on=[‘District’],
right_on=[‘District’])
然后我们得到一个新的数据帧:

图 3:合并的表格
用 Matplotlib 可视化:
fig, ax = plt.subplots(1, figsize=(10,6))
df_merged.plot(column='Sum', cmap='Blues', linewidth=1, ax=ax, edgecolor='0.9', legend = True)
ax.axis('off')
用阴谋想象:
fig = px.choropleth(merged, geojson=merged.geometry,
locations=merged.index, color="Sum",
height=500,
color_continuous_scale="Viridis")
fig.update_geos(fitbounds="locations", visible=True)fig.update_layout(
title_text='Map'
)fig.update(layout = dict(title=dict(x=0.5)))
fig.update_layout(
margin={"r":0,"t":30,"l":10,"b":10},
coloraxis_colorbar={
'title':'Sum'})fig.show()

图 Matplotlib & Plotly 的 Choropleth 图
总而言之:
如今,Choropleth 地图在仪表板中使用得非常频繁。要创建地图,第一件事是获取您想要在个性化地图中应用的 shapefile 或 geo json 文件,然后相应地合并您的数据。
玩一玩吧🤪!有任何问题,请随时在这里或通过我的 LinkedIn 给我留言。
Plotly Dash:从开发到部署
一本关于 Plotly Dash 的简单易懂的实践教程。在这个过程中学习一点 HTML 、 CSS 、 Python Decorators ,以及 Docker 。

Plotly Dash 是一个惊人的框架,可以完全使用 Python 来构建交互式仪表盘。由于仪表板类似于一个网站,肯定也涉及到一点 HTML & CSS,但请放心,它是如此的基础,甚至我在创建我的第一个仪表板时就学会了。
在本教程中,我们将构建一个 COVID19 仪表板,显示印度 COVID19 病例、康复和死亡的增长/下降情况。使用这个链接可以访问仪表板,使用这个 GitHub 库可以访问底层代码。

截图来自 Aarogya Setu(COVID 更新部分)
下面是一张 GIF 图片,展示了我用 Plotly Dash 制作的仪表盘。我从 Aarogya Setu 应用程序中获得了灵感,因为我不认为我有设计头脑。此外,我觉得这款应用在阐述数据见解方面做得很好。

使用 Plotly Dash 构建的 COVID19 仪表板
开发和设置每个人都可以看到的仪表板的整个过程的步骤大致如下
- 找出数据源💽
- 创建静态仪表板📊
- 让它互动💹
- 集装箱化一切📦
- 部署🖥️项目
现在,我将详细阐述每一点。
我建议总是为你所有的 Python 项目创建一个单独的环境。这使它变得很容易,因为你不会面临任何依赖问题,而且它对部署也很有帮助。Anaconda 是我的首选工具。
1.找出数据源
这是一项任务,可以像使用从 Kaggle 或任何其他数据平台下载的 CSV 文件一样简单,也可以像从网站上抓取大型数据集一样困难。
我在仪表板上使用的数据集来自这个 API 。有多种 API,您可以根据需要使用它们。唯一需要记住的是,尽管所有的历史数据都存在,但是当天的数据每天都在刷新。因此,这是你在设计数据源时需要注意的一件事:新数据应该每天添加。
既然数据源已经弄清楚了,就需要一种存储数据的方法。它可以是任何东西,如 CSV,SQL 表,e.t.c。由于数据不多,为了保持简单,我使用 CSV 文件作为我的数据库,添加和更新记录的逻辑可以在这里找到。
2.创建静态仪表板
对于构建静态网页,你肯定需要了解 HTML 和 CSS,但对于 Plotly Dash 来说,情况并非如此。Dash 是一个 web 应用程序框架,它在 HTML、CSS 和 javascript 之上添加了纯 Python 抽象。说到这里,可以通过 W3 schools 学习 HTML 和 CSS 的基础知识。超级基础!(至少对于创建像我这样的仪表板来说是这样的😛)
注意:对于要显示的 CSS 更改,请确保将您的 CSS 文件保存在文件夹路径
./assets/typography.css(此处)。”表示主项目文件夹)。命名必须是这样的。此外,您可以使用 HTML 标记、id 或类来直接引用 app.py 文件中的组件。
Dash 为我们提供了许多现成的功能,但我不会一一介绍。以下是我用过的
- Dash HTML 组件:您不用编写 HTML 或使用 HTML 模板引擎,而是使用 Python 结构和
dash-html-components库来构建您的布局。它提供了所有的 HTML 标签。 - Dash 核心组件 : Dash 配备了用于交互式用户界面的增压组件。由 Dash 团队编写和维护的一组核心组件可以在
dash-core-components库中获得。 - Dash DAQ : Dash DAQ 包括一套强大的控制装置,使数据采集和控制更容易集成到您的 Dash 应用程序中。
基本的应用程序布局可以很容易地在教程中找到。下面,我将尝试解释我的仪表板的每一个组成部分,以及我是如何想出它的。
整个布局代码
2.1 顶部截面

仪表板的顶部
本部分包含标题以及我的一些广告(LOL ),随后显示了迄今为止的总指标以及它们与前一天相比的跳跃。这个部分肯定会每天发生变化,但是我们仍然可以创建它的静态版本。在上面的layout.py文件中,lines 13–39组成了这个部分。
注意:为了让图像显示在你的仪表板上,它们应该存储在路径
./assets/中。请注意“资产”文件夹的拼写。子文件夹可以命名为任何名称。
如果你仔细看,HTML 标签<h1>COVID19 India Dashboard</h1>变成了html.h1(‘COVID19 India Dashboard’)。可以使用参数发送 HTML 标签属性,以破折号 HTML 组件。
另一个例子<img src=’/assets/images/kaggle.png’ class=’img’>相当于html.Img(src=’/assets/images/kaggle.png’,className=’img’)
函数(如下)显示了用于从 pandas dataframe 创建表格(使用 dash HTML 组件)的函数。
用于生成顶部表格的代码
2.2 中间部分

仪表板的中间部分
这是仪表板的核心,包含下拉菜单、选项卡(活动、已确认、已恢复和已死亡)、图表(线+条)和拨动开关。第 40–64 行组成了上面所示的layout.py文件的中间部分。
**dropdown**很容易添加,这里的有很多例子供你参考。它是仪表板核心部件的一部分。- 使用本文档中的添加
**tabs**。它是仪表板核心部件的一部分。 **graph**也是仪表板核心部件的一部分,可以使用这个来添加。dcc.graph()应该包含一个 plotly 图形对象。可使用 plotly express 或 graph objects 创建 plotly 图形对象。plotly express 是非常容易使用的 API,而 graph objects 有点不平凡,但提供了很多定制。**ToggleSwitch**是DAQ的一部分,可以参照此链接添加。
2.3 底部截面

仪表板的底部
第 65–70 行构成了这一部分,它包含标题和一个显示州级细分的表格。该表将是动态的(因为状态的位置可以根据总的情况而改变),所以我创建了一个函数来创建一个表,该表可以使用来自 pandas dataframe 的 dash-html-components 来显示。该函数几乎类似于 2.1 节中使用的函数。它的名字是代码中的 generate_table()。
3.让它互动
通常,需要 JavaScript 来使网站具有交互性,但是不要担心,使用 plotly dash 创建仪表板时我们不需要知道它。你只需要懂 Python!
Dash 提供了 回调函数 ,这些函数不过是 Python 装饰器,只要输入组件的属性发生变化,Dash 就会自动调用这些函数。参考这个 Youtube 视频了解更多关于装修工的信息。
如果你看过我的仪表盘或者顶部的 GIF 图,你会发现所有的交互都在图中
- 根据下拉列表中选择的状态进行更改。默认情况下,它显示整个印度的数据。
- 基于所选选项卡的更改:有效、已确认、已恢复或已死亡。
- 悬停时,在左上角显示值和日期。
- 基于拨动开关的变化:累积(折线图)或每日(条形图)。
所有的改变只发生在同一个图中,所以我们只需要一个回调函数。下面我试着用简单的话解释一下。

回调函数的解释。注意输入的顺序以及输入的 id、参数和值。
state-dropdown是下拉列表的“id ”,我们将它的“value”参数的值作为第一个输入。默认值为印度。india-graph是图形本身的“id ”,但是这里我们将它的“hoverData”参数的值作为第二个输入。默认值为 Null 。(因为打开应用程序后,我们不会停留在图表上。)my-toggle-switch是 ToggleSwitch 的“id ”,我们将它的“值”参数的值作为第三个输入。默认值是真我假设是累计。tabs是选项卡组件的“id ”,我们将它的“value”参数的值作为第 4 个输入。默认值为激活。
回调的输出是一个图形,它的“id”是india-graph,输出被发送到图形的图参数。(如果您在理解过程中发现任何问题,请参考 plotly graph objects 文档。)
因此,一旦您打开应用程序,您将看到印度的累积(3)活动(4) COVID19 案例(1),没有任何 hoverinfo(2),根据回调函数参数。括号中的数字显示了发送给回调函数的输入顺序。
你在回调函数中看到的输入 & 输出都是破折号依赖。如果这些是多个,那么它们需要作为一个列表发送(在上面的例子中是 4 个输入)。请记住,您需要将它们发送给装饰器/回调函数,下面您可以用任何名称定义一个函数,并为它们指定您喜欢的任何参数名称。
函数update_tab_graph接受回调中定义的 4 个输入:(可以将这些参数看作是回调输入的映射)
- 状态=输入 1 中的值。
- hdata =输入 2 中的 hoverData。
- r_value =输入 3 中的值。
- col =输入 4 中的值。
输出是由函数返回的图形对象。
现在,如果有人改变了 4 个输入中的任何一个,回调将被触发,图形将被更新。如上图所示为德里的一个例子。
4.集装箱化一切
Docker 是一个创建容器的神奇工具,而且非常容易使用。最好的部分是容器是不可知的。大多数容器都是基于 Linux 的,但是可以在 Windows/Mac OS 上运行,没有任何问题,这要感谢一些令人敬畏的技术,这些技术超出了本文的范围。
Docker 本身是巨大的,不能在本文中肯定地解释。所以,我将试着用我自己简单的话来解释它。
假设您创建了一个 Python 项目。现在你想和你的朋友分享。一个简单的方法是共享整个项目文件夹。然后,您的朋友将创建一个新环境,并在 requirements.txt 文件中安装依赖项,然后就可以运行项目了。这种方法对于经验丰富的开发人员来说非常容易,但是对于刚刚开始或者甚至不太了解安装过程的人来说,肯定会面临很多问题。这就是码头工人来救援的地方。你的朋友只需要安装 Docker(Docker 桌面首选),然后他/她就可以导入你的 Docker 镜像,然后运行它就可以看到你的项目了。基本上,如果你的 docker 容器运行在你的本地机器上,它肯定会运行任何其他的机器,而不管操作系统是什么。
为了消除困惑,你只需要知道这三件事就可以开始使用 Docker 了:
- Dockerfile :这个文件包含了创建 Docker 图像的所有指令。
- 图像:包含 docker 文件的整个包/构建以及所有必需的项目文件(您可以通过在中提及它们来忽略一些文件)。dockerignore 文件)和所有安装的依赖项。
- 容器:按照 Dockerfile 文件中的步骤/层顺序运行的图像实例。
我在某处读到过一个惊人的类比:容器是一个 cookie,图像是用来创建 cookie 的模型,Dockerfile 是创建该模型的指令。
下面是我在项目中使用的 docker 文件。我会试着逐一解释所有的步骤/层次。
FROM python:3.8-slimWORKDIR /app COPY . . RUN pip install -r requirements.txt EXPOSE 8050 CMD [“python”, “app.py”]
FROM python:3.8-slim导入 python 镜像(基本上是 Linux OS 上安装的 python3.8)。我已经导入了苗条的版本,因为我想保持我的形象轻巧。WORKDIR /app在我们的 python 映像中创建一个名为“app”的目录。COPY . .将项目文件夹中的所有文件和文件夹复制到 app 目录中。中提到的文件/文件夹。忽略 dockerignore 文件。- 运行命令行,在 requirements.txt 文件中安装所有的依赖项。该文件是在上一步中复制的。
EXPOSE 8050暴露 docker 端的端口 8050。将机器的端口映射到这个端口时需要小心。CMD [“python”, “app.py”]管理你的项目。它完全类似于在本地机器上运行项目时使用的命令行(只是在空格处将文本分成一个列表)。
一旦 docker 文件完成,在同一个文件夹中执行下面的代码来构建你的 docker 映像。
docker build -t covid-dashboard-india .
最后,要运行您的容器,请执行下面的代码。注意我做的端口映射。这是使用-p 标记完成的,分号的左边是本地机器的端口,右边是容器的端口。
docker run -p 8050:8050 covid-dashboard-india
在端口 8050 上导航到您的本地主机,查看仪表板并使用它。
5.部署项目
如果您对一切都满意,那么最后一步就是选择一个可以部署 docker 映像的注册表。有太多的注册中心,比如 Heroku,AWS ECR,e.t.c .最好的事情是 Heroku 是免费的,我已经在我的这个项目中使用了它。
只需按照链接中提到的所有步骤来部署您的映像。这很容易理解。
哒哒!您刚刚开发并部署了一个仪表板👏
我希望这篇文章对你有用。如有任何疑问,请随时联系我:)
这就是我如何纯粹用 Python 创建令人眼花缭乱的仪表盘。
Plotly dash 应用程序是用 python 构建生产级仪表板的最快方式。

我不需要说服你为什么我们需要一个交互式仪表板。但大多数人不知道的是,他们不必购买 Tableau 或 PowerBI 的昂贵授权。你也不必注册 JavaScript 课程。
Dash 应用程序允许你纯粹用 Python 来构建交互式仪表盘。有趣的是,它可以达到流行的 BI 平台所不能达到的高度。此外,您可以在您的服务器和条款上托管它。
为什么是 Dash?为什么不是 Tableau,Power BI,或者一些 JavaScript 库?
像 Tableau 和 PowerBI 这样的 BI 平台做得非常出色。它甚至允许非技术管理人员自己进行数据探索。我对他们没有抱怨。
它们是对只读数据集执行分析的优秀工具。但是在大型数据科学项目中,你必须执行复杂的操作。例如,您必须触发一个后端功能,并开始模型再训练。
在这种情况下,我的最佳解决方案是从头开始构建一个 web 应用程序。 JavaScript 数据可视化库比如 HighCharts 就是很好的工具。它们对几乎每一个可能的用户操作都有回调。我用它们将数据发送回服务器,并更好地控制它。
但这不是在公园散步。我的数据科学团队擅长 Python 和 R,但不擅长 JavaScript。N ot 在 web 框架上,比如 Django 上。这还不够。要构建现代网络应用,你需要前端网络框架,比如 React 。
随着我们的进步,我们意识到残酷的事实。我们堆栈中的每项新技术都会成倍增加难度。
我们很幸运找到了达什。
如果你正在寻找一个轻量级的替代品,请查看Streamlit。如果您需要一个灵活、完整的 Python 仪表盘解决方案,请继续阅读。
现在,这就是我如何用 Python 创建令人眼花缭乱的仪表盘。
用 Python 构建您的第一个仪表板(不到 1 分钟。)
是的,在 Dash 中构建仪表板就是这么简单。用下面的命令安装 Pandas 和 dash,然后启动计时器。
pip install dash pandas
在您的项目目录中,使用以下内容创建一个名为 app.py 的文件。
最简单的 Plotly Dash 应用程序。
表演时间到了。让我们使用以下命令运行仪表板:
python app.py
您将看到它在端口 8050 启动一个服务器。如果您在浏览器上访问 http://127.0.0.1:8050 ,您会看到如下所示的仪表板:

仅在 Python 中创建的仪表板。—作者
如果您已经使用 JavaScript 库创建了一个类似的应用程序,您会体会到其中的不同。Dash 通过消除大量样板代码节省了大量时间。即使是流行的 BI 工具也需要做大量的前期工作才能达到这一步。
太棒了。这对于激发你构建仪表板来说是相当重要的。但你可能已经意识到它还不耀眼。在接下来的部分中,我们将讨论如何
- 我们可以改进布局;
- 向小部件添加交互和回调,以及;
- 进一步设计应用程序。
有了这个,你就可以创建你需要的炫目的仪表盘了。浏览 Plottly Dash 图库获取更多此类仪表盘的灵感。
向布局添加更多小部件。
Dash 遵循类似 HTML 的元素层次结构。您可以将任何 Dash 的 HTML 组件或核心组件附加到应用程序的布局属性中。布局属性是 Dash 应用程序元素层次结构的根。
核心组件是一组预配置的小部件,如下拉菜单和滑块。
Dash 的 HTML 组件几乎涵盖了所有可用的 HTML 元素。要创建标题,可以使用html.H1和html.P创建段落。children 属性允许您将一个 HTML 组件嵌套在另一个组件中。
在 Plotly Dash 应用程序中使用组件。—作者作者
在上面的代码中,我们包含了三个核心组件——两个下拉菜单和一个滑块。这些控制器元素允许我们过滤图表数据,并在下一部分创建交互性。
添加与组件回调的交互性。
Dash 的核心组件有回调来控制对用户动作的响应。这个特性是 Dash 应用程序胜过流行的 BI 平台的显著原因。
您可以使用这个回调来控制图表的重新呈现,或者触发大量的分析。查看我关于执行大规模计算的文章,连同芹菜一起使用 Dash 应用程序。
在这篇文章中,我们使用回调来过滤图表数据。
在 Plotly Dash 应用程序中使用回调。—作者作者
回调函数用@app.callback 装饰器进行了注释。这个装饰器的第一个参数是元素树中的输出组件。我们需要指定该元素的 id 和需要更改的属性。该属性将更改为回调函数的返回值。
那么修饰的将接受任意数量的输入参数。每一个都将被绑定到一个核心组件,就像我们附加输出组件一样。我们可以指定元素的 id 和发出更改值的属性。通常,这将是“价值”
装饰器中的每个输入应该在回调函数的定义中有各自的参数。
最后,我们将图形组件移到了回调函数中。每次我们运行回调函数时,它都会创建一个新的 figure 实例并更新 UI。
仪表板的样式。
您可以使用 Dash 应用程序中提供的内嵌样式选项。但是用很少的 CSS,你可以得到惊人的结果。
在 dash 中,您可以用三种不同的方式来设计元素的样式。
内嵌样式
每个 Dash 组件都接受一个样式参数。您可以传递一个字典并设计任何元素的样式。这是设计 Dash 应用程序最便捷的方式。
Dash 应用程序组件的内联样式。—作者作者
本地样式表。
或者,您可以将类名属性传递给任何 Dash 组件,并使用单独的 CSS 文件对其进行样式化。您应该将这个 CSS 文件放在项目目录的资产文件夹中。Dash 会自动选择它,并将其样式应用到组件中。
Plotly Dash 应用程序中的本地样式表。—作者
外部样式表。
您也可以使用互联网上的样式表。例如,dash 有这个预配置的样式表,附带了方便的实用程序类。您可以指定样式表,并在元素中使用它的类名,使元素更加美观。
Plotly Dash 应用程序中的外部样式表。—作者
这里是应用程序的完整的源代码。我们使用了本地样式表,并以支持样式的方式组织 HTML。您可以在这个 GitHub 资源库中找到完整的代码和本地样式表。
用 Python 创建的仪表板的完整源代码。作者作者
当您使用上述内容更新代码时,您的 web 应用程序会刷新。它可能看起来像下图——你的第一个令人眼花缭乱的仪表板版本。

用 Python 创建的基本样式仪表板的截屏。—作者。
最后的想法
对于 Python 开发者来说,Dash 应用是一个不可思议的工具。由于大多数数据科学团队并不专门研究 JavaScript,所以用 Dash 构建仪表板节省了他们大量的时间。
我们可以使用 Tableau、PowerBI 和类似的 BI 平台进行数据探索和可视化。但是 Dash 应用程序比他们更出色,因为它们与后端代码紧密集成。
在本文中,我们探索了 Dash 应用程序的表面。我相信这会给你一个开始,让你不用担心可怕的技术堆栈,就能构建出色的仪表板。
作为下一步,我强烈建议探索 Dash 的文档页面和他们的示例库。
谢谢你的阅读,朋友。看来你和我有许多共同的兴趣。也一定要看看我的个人博客。
还不是中等会员?请使用此链接 成为会员 因为,不需要你额外付费,我为你引荐赚取一小笔佣金。
分层数据可视化的 Plotly:树形图和更多
一步一步的教程,使用 Plotly 和 Treemaps 可视化美国大辞职

图片来源: Pixabay
介绍
2021 年 12 月 8 日,美国劳工统计局发布了其最新的劳动力流动数据。从 6 月到 10 月,超过 2000 万美国人辞去了工作。10 月份的初步数据显示,当月有超过 400 万美国人辞职,比去年同期高出近 25%。

劳动统计局:经济新闻发布
劳动力流动数据还可以细分到不同的行业,在更精细的层次上提供丰富而深刻的信息。但是,请注意,这些数据是以表格格式呈现的,具有按行业划分的层次结构。这使得发现模式和快速识别需要注意的类别或子类别变得非常困难。
这是数据可视化发挥作用的典型场景!我们如何可视化这种类型的分层数据,以便它以易读的方式提供见解,同时有效地利用空间?
在本教程中,我们将使用 Plotly 创建一个树形图来可视化不同行业 10 月份的辞职率。树形图使用嵌套矩形显示分层数据,并使用每个矩形的大小和颜色来表示我们想要显示的不同指标。它是分层数据可视化的完美候选。
作为奖励,在教程的最后,我们还将构建一个 sunburst 图表和 icicle 图表,它们与 treemaps 非常相似,并且共享相同的输入数据格式。我们将要创建的树形图如下所示:

作者图片
Plotly Express vs. Plotly Go
Plotly Python 库是一个交互式的开源图形库,涵盖了广泛的图表类型和数据可视化用例。它有一个名为 Plotly Express 的包装器,是 Plotly 的高级接口。
Plotly Express 可以轻松快捷地使用简单的语法创建最常见的图表,但在涉及更高级的图表类型或自定义时,它缺乏功能和灵活性。
与 Plotly Express 相反,Plotly Go(图形对象)是一个较低级别的图形包,通常需要更多的编码,但更具可定制性和灵活性。在本教程中,我们将使用 Plotly Go 创建如上所示的树形图。您还可以将代码保存为模板,以便在其他用例中创建类似的图表。
下载并准备数据
你可以从劳动统计局经济新闻发布页面获得本教程的数据。该数据显示了 2021 年 6 月至 10 月经季节性调整的各行业的对等水平(数字以千计)和比率。我将数据保存在一个 Excel 文件中,工作表名为“By_Industry ”,并直接在 Excel 中重命名列名,使它们更直观。
我们先把数据读入 python。由于我们只对 10 月份的辞职率感兴趣,我们将只在数据框中保留“行业”、“2021 年 10 月辞职”和“2021 年 10 月辞职率”列。请注意,数据框中的“行业”列没有反映出原始表中显示的等级结构-我们需要稍后将这一缺失的信息添加到数据框中,以便绘制树形图。

作者图片
用 go 创建一个树形图。树形图
要使用 Plotly Graph_Objects 制作树形图,我们至少需要“告诉”Plotly 以下内容:
- 每个矩形的标签
- 嵌套矩形的层次结构
- 每个矩形的大小和颜色
在我们的数据框中,“行业”列显示了每个矩形的标签。“Oct_2021_Quits”列可用于表示每个矩形的大小,显示每个行业部门的辞职总数。“Oct_2021_Quit_Rates”列可用于表示每个矩形的颜色,颜色越深,该部门的周转率越高。
数据框中唯一缺少的信息是标签/矩形的等级。我们需要创建一个新列(我们可以将其命名为“parent”)来明确定义标签的层次结构。这是通过以下代码实现的:

作者图片
现在我们准备绘制树状图了!在下面的代码中,我们将首先创建一个 graph_objects 图形,然后使用 go 向它添加 treemap 跟踪。图(去。Treemap())。围棋之内。Treemap(),我们通过values和parents属性来定义层次结构。每个矩形的大小/面积由values属性定义,颜色由marker属性定义。我们也可以通过用 HTML 代码编辑hovertemplate属性来自定义工具提示。

作者图片
微调树形图
我们刚刚创建的树形图看起来不错!使用 Plotly 创建一个只有几行代码的树形图似乎非常简单方便!请注意,在上面的树形图中,一些行业被压缩成非常小的矩形,由于矩形的大小,标签难以辨认。让我们微调树形图来解决这些问题。
在我们的可视化中,由于我们主要对不同行业的辞职率感兴趣,而不是辞职的数量,我们可以从前面的代码中删除value属性。由于没有给value属性分配一个指标,一个类别的面积现在被平均分配给其父类别中的其他子类别。
我们也可以通过使用uniformtext布局参数来强制所有的文本标签具有相同的字体大小。minsize属性设置所需的字体大小。mode属性决定了不适合所需字体大小的标签会发生什么。你可以选择hide或者show溢出。

作者图片
现在标签看起来更清楚了,尽管标签溢出了矩形的边界还有一个小问题。不幸的是,Plotly 不能自动换行,所以我们需要手动修复它。
在下面的代码中,我们使用 HTML 代码通过
元素将长格式文本分成多行。HTML 元素
在文本中产生一个换行符,并且
之后的文本从文本块的下一行开始重新开始。我们可以将这个换行的文本列命名为“id2”。
现在让我们通过将‘id2’赋给labels属性来完成我们的绘图。我们还将添加一个图表标题,指定标题的字体大小,并将其放在树形图上方的中心位置。

树形图(图片由作者提供)
奖励:旭日图和冰柱图
在可视化分层数据时,还有一些其他图表类型与 treemaps 非常相似,比如 sunburst 图表和 icicle 图表。这两种图表类型的输入数据格式与树形图相同:层次结构由labels和parents属性定义。
要绘制旭日图和冰柱图,代码中唯一需要修改的就是替换 go。带 go 的树形图。旭日()或 go。冰柱()。下面的代码用于创建旭日图。旭日图直观地显示了从根到叶径向向外延伸的分层数据。

旭日图(图片由作者提供)
冰柱图使用矩形扇区从根到叶沿四个方向之一显示分层数据:上、下、左或右。冰柱图有一个带有两个参数orientation和flip的tiling属性。您可以结合使用这两个参数来创建四个方向中的每一个:水平、垂直、向左或向右。

冰柱图(图片由作者提供)
与条形图、折线图或饼图等其他图表类型相比,树状图、旭日图和冰柱图是不太常见的可视化形式。然而,当涉及到可视化分层数据时,它们绝对是完成工作的完美选择,既漂亮又高效!感谢您的阅读,我希望您发现这篇文章有助于提高您的绘图和数据可视化技能!
参考和数据来源:
- Plotly 官方文档页面:https://plotly.com/python/treemaps/
- 数据来源:劳动统计局发布的经济新闻(https://www.bls.gov/news.release/jolts.t04.htm)。这是一个没有许可的开放数据集。
你可以通过这个推荐链接注册 Medium 会员(每月 5 美元)来获得我的作品和 Medium 的其他内容。通过这个链接注册,我将收到你的一部分会员费,不需要你额外付费。谢谢大家!
plotly VS matplotlib:Python 中数据可视化最好的库是哪个?

在这个简短的教程中,我将向您展示如何使用plotly和matplotlib库快速构建一个基本的折线图。参考plotly,我将使用图形对象,它们是用于表示图形部分的plotly类。关于图形对象的详细信息,可以参考官方 plotly 文档。
你可以从我的 Github 库下载本教程的代码。
输入数据
首先,我将数据导入一个pandas数据框架。我使用read_csv()功能。该数据集包含在意大利住宿的游客数量,按城镇或总数划分。
import pandas as pddf = pd.read_csv('presenza_negli_alberghi.csv')
df

作者图片
数据过滤
如果你急着画图可以跳过这一节:)。
因为我想绘制存在的总数,所以我必须应用一些过滤器。我将Sigla列转换成一个字符串,以便执行一些比较,然后我只维护列Sigla中值的长度大于 2 的行。
import numpy as np
df['Sigla'] = df['Sigla'].astype('str')
regions = df['Sigla'].apply(lambda x: x if len(x) > 2 else np.nan).dropna().unique()
现在我过滤数据,以便只维护数据的总数。
df_it = df[(df['Territorio'] == 'Italia') & (df['Esercizio'] == 'totale esercizi ricettivi') & (df['Indicatori'] == 'presenze')]
df_it

作者图片
Matplotlib
现在我利用matplotlib库来绘制基本线条。我可以通过figure()功能设置图形大小。这个函数必须在调用其他函数之前被调用。我调用了plot()函数,通过传递x和y作为前两个参数,以及线color参数(橙色)、linewidth (2)和alpha参数的值(0.7)来设置绘图透明度。我还通过style.use()函数定义了要使用的样式。然后我指定剧情标题(功能title()和xlabel()和ylabel())。最后,我调用show()函数来绘制图表。
import matplotlib.pyplot as plt
plt.figure(figsize=(15,7))
plt.plot(df_it['Periodo'], df_it['Dato'], marker='', color='orange', linewidth=2, alpha=0.7)
plt.style.use('seaborn')
plt.title("Number of presences in the Italian Accommodations", loc='right', fontsize=12, fontweight=0)
plt.xlabel("Year")
plt.ylabel("Number of Presences")plt.show()

作者图片
Plotly
通过plotly库可以绘制相同的图形。在导入plotly.graph_objects之后,我定义了一个Figure(),我们向其传递两个参数:data和mode。模式包含线条的类型(在我的例子中是lines+markers),而data包含一个Scatter()对象。在Scatter()对象中,我必须指定x和y以及line选项(颜色、宽度)。然后我调用update_layout()函数来定义绘图选项,比如标题以及 y 和 x 标签。最后,我通过show()函数绘制图表。
import plotly.graph_objects as go
import numpy as npfig = go.Figure(data=go.Scatter(x=df_it['Periodo'], y=df_it['Dato'],line=dict(color='orange', width=2),mode='lines+markers'))
fig.update_layout(
title={
'text': "Number of presences in the Italian Accommodations",
'y':0.9,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'},
xaxis_title="Year",
yaxis_title="Number of presences"
)
fig.show()

作者图片
讨论和总结
这两个被分析的库产生了相似的结果。然而,它们的方法大不相同,因为matplotlib为要执行的每个操作定义了一个函数,例如title()、xlabel()和ylabel(),而plotly定义了一个布局,它接收一个字典作为输入。哪个最好?没有最好的图书馆。看你的敏感程度了。我个人更喜欢matplotlib但是你可以随意使用。
事实上,Python 中还存在其他用于数据可视化的库,比如 Vega Lite 。如果你想了解这个图书馆,请继续关注…
如果你想了解我的研究和其他活动的最新情况,你可以在 Twitter 、 Youtube 和 Github 上关注我。
绘制和分析表格数据
使用 Tablexplore 替换电子表格

来源:作者
数据可视化是识别任何隐藏模式、数据点之间的关系等的最佳方式之一。这些数据通常不会以表格形式显示。不同的可视化有助于分析数据并生成有用的见解。
我们可以使用多个 Python 库来可视化和分析数据。分析 MS Excel、Google Spreadsheets、Tableau 等中数据的最常用工具。现在,这些库和工具的问题是,它们中的大多数要么是付费的,要么我们需要知道很多编码才能使它们工作。
Tablexplore 是一个基于 Python 的开源工具,可用于绘制和分析任何表格数据集。它有各种各样的功能,使它易于使用和高效。
在本文中,我们将探索 Tablexplore 并使用它创建一些可视化效果。
让我们开始吧…
安装 Tablexplore
为了安装 tablexplore for windows,我们可以从下面给出的链接直接下载 windows installer。
https://dmnfarrell.github.io/tablexplore/
安装后,我们可以直接启动它,开始使用它。

主页(来源:作者)
现在,让我们加载一个数据集,开始创建不同的图,只需点击鼠标,不需要任何代码就可以分析数据。

散点图(来源:作者)
我们只需选择两列或更多列,然后单击图表按钮,我们可以从右侧菜单中选择图表类型。让我们再创建一个图表。

方框图(来源:作者)
这个工具是高度可定制的,这意味着我们可以使用颜色方案、图表大小等。还有许多其他选项,如合并、换位、聚合等。这使得它比市场上的任何其他工具都更有用。
尝试使用不同的数据集,创建不同的可视化效果,并在回复部分告诉我您的意见。
本文是与 Piyush Ingale 合作完成的。
在你走之前
感谢 的阅读!如果你想与我取得联系,请随时通过 hmix13@gmail.com 联系我或我的 LinkedIn 个人资料 。可以查看我的Github*简介针对不同的数据科学项目和包教程。还有,随意探索* 我的简介 ,阅读我写过的与数据科学相关的不同文章。






浙公网安备 33010602011771号