TowardsDataScience-博客中文翻译-2021-九十四-
TowardsDataScience 博客中文翻译 2021(九十四)
在非英语文本中使用拥抱脸模型

作者图片
如何在非英语文本上使用来自拥抱脸的预先训练的英语语言模型
一个预训练模型是一个保存的机器学习模型,它以前在一个大数据集(例如维基百科中的所有文章)上训练过,以后可以用作一个执行特定任务的“程序”(例如找到文本的情感)。
拥抱脸对于预先训练好的语言处理模型来说是一个很好的资源。也就是说,大多数可用的模型都是针对流行语言(英语、西班牙语、法语等)训练的。).幸运的是,许多较小的语言都有预先训练好的模型可用于翻译任务。在这里,我将通过以下方式演示如何使用可用的模型:
- 将输入文本翻译成英语,
- 使用预先训练的英语模型执行特定任务,
- 将结果翻译回原始语言。
我使用爱沙尼亚语(约 110 万人的母语)作为输入语言,并评估该工作流程在以下任务中的实用性:
- 情感分析。
- 提取问题回答。
- 文本生成。
- 命名实体识别。
- 总结。
翻译
翻译是将文本从一种语言翻译成另一种语言的任务。这将是我们每个示例中的第一个也是最后一个任务。访问拥抱脸部模型的方法之一是通过他们的推理 API ,它能够运行推理(从机器学习模型中询问一些东西),而无需本地安装或下载任何模型。首先,你需要在拥抱脸注册,并在你的个人资料中获得你的 API 令牌。
使用 API 包括:
- 从模型中枢中选择模型并定义端点
ENDPOINT = https://api-inference.huggingface.co/models/<MODEL_ID>. - 用您的个人 API 令牌定义头部。
- 定义查询的输入(强制)和参数(可选)。
- 运行 API 请求。
下面的端到端代码示例说明了这一点。通过从模型库中复制模型名称来定义 API 端点;2.设置一个 API 令牌,在拥抱脸注册账号后可以从你的用户设置中找到;3.定义向 API 发出 POST 请求的函数;4.定义我想翻译的输入文本,并使用这个输入和 5 运行 API 查询。提取结果。 NB:对于下面的翻译,我们只需要重复步骤 4 和 5(定义新的输入并提取结果)。
情感分析
情感分析是将输入文本分类为正面或负面的任务。可能的类别数量取决于特定的预训练模型。例如,一些模型仅使用两个类别(阳性、阴性),而其他模型使用三个(阳性、中性、阴性)或更多。
对于情感分析,我使用变形金刚库,这是继推理 API 之后的另一个简单选项,用于访问预训练的模型。通过笔记本中的!pip install transformers或终端中的pip install transformers安装库。
在给定任务中使用预训练模型的最简单方法是使用pipeline('name-of-the-task')功能。该库为特定任务下载预先训练好的模型,推理在您的本地机器上运行(回想一下,如果您不想下载模型,可以使用推理 API)。让我们看看它是如何进行情感分析的:
- 首先,我们用管道函数和任务名称创建一个分类器。您可以从文档中找到可用的任务。
- 接下来,我们将输入文本从原始语言翻译成英语。
- 最后,我们用一行代码运行情感分析并提取结果。下面的例子很成功——翻译得不错“我们想要最好的,但结果总是这样。”我们正确地将句子的情绪归类为负面。
这是另一个例子,但是这次任务由于错误的翻译而失败了。翻译回复说“支持民主的最佳论据是与普通选民进行五分钟的谈话”,但正确的翻译应该是“反对民主的最佳论据是 T2”。在翻译和使用预先训练的英语模型时,要记住错误翻译的可能性。
抽取式问题回答
抽取式问题回答是基于输入文本回答问题的任务。在下面的例子中,我将给出一个关于我正在工作的单位的一般描述,并尝试回答“这个单位在做什么?”。让我们看一下下面的代码:
- 创建问答管道(第 2 行)。
- 提供原文的上下文,并将上下文翻译成英语。
- 用原文提供问题,并将其翻译成英语。
- 通过管道运行问题和上下文,并提取答案。
- 将答案翻译回原文。
上面的输出显示,翻译并不完美,但它会做的工作。“电子实验室在做什么?”这个问题的答案也有点尖锐但核心意思是正确的。
文本生成
文本生成(也称为因果语言建模)是在给定句子开头的情况下预测下一个单词的任务。换句话说,这就是机器学习模型试图成为作家的任务😄!
在下面的例子中,我们:
- 生成文本生成管道。
- 用爱沙尼亚语定义两个句子开头,然后翻译成英语。
- 使用管道根据开头生成文本,并将生成的单词数限制在 50 个以内。
- 将生成的文本翻译回爱沙尼亚语。
正如你从上面的结果文本中看到的,我们的模型产生了相当多的废话,比如“爱沙尼亚用燃烧天然气的水力发电厂发电”😄。也就是说,Est-to-Eng-to-Est 的工作流程似乎工作得很好,并且这个例子中的翻译质量非常好。
命名实体识别
命名实体识别(NER)的任务是试图从文本中找到人名、地名、组织名。在下面的例子中,我将给出两个爱沙尼亚语句子作为输入,并尝试从中检测所有命名实体。让我们看一下下面的代码:
- 初始化命名实体识别管道,并为我们的模型输出的的类定义合理的名称。在这里,我创建了一个字典,使用类代码作为键,爱沙尼亚语的含义作为值。这些以后会用到。
- 定义输入文本。
- 将输入内容翻译成英语。
- 为 NER 定义函数:我们的函数 a .)用输入运行 ner pipeline,b .)用爱沙尼亚语(在 for 循环中)的合理名称替换所有模糊的类名,c .)将属于同一实体的字符串/标记组合在一起(您可以在不分组的情况下尝试该函数以查看原始输出)。
- 打印所有实体及其所属的类。
上述结果表明,该模型能够正确检测所有命名实体,并将它们划分到相应的类别(组织或个人)。
摘要
摘要是将一篇长的文本或文档概括成一篇较短的文本的任务。在下面的例子中,我使用了 Google 的 T5 模型,该模型是根据混合输入数据(包括来自 CNN 和 Daily Mail 的文档)进行训练的。同样,我们可以遵循已经熟悉的工作流程:
- 用“摘要”任务初始化 Transformers 管道。
- 提供一个输入句子或文档。
- 将输入内容翻译成英语。
- 用预先训练好的英语模式总结课文。您可以提供摘要的最大和最小长度作为参数—这里,我将摘要的长度限制为 30 个令牌。
- 翻译回输入语言。
上面的总结者能够很好地捕捉输入的核心思想,但是输出有一些语法问题。因此,我们可以预期,该模型应该用于有意识的用户快速捕捉长文档的核心含义。
摘要
本文通过将翻译作为工作流的一部分,展示了在非英语语言上使用预先训练的英语语言模型的想法。这个概念在五个不同的任务上进行了测试:
- 在情感分析中,输出的质量很大程度上取决于翻译的质量。在这里,我们试图检测两个爱沙尼亚句子的情感——其中一个例子是成功的,另一个由于翻译错误改变了句子的意思而失败。
- 在回答问题时,我们看到我们的例子在翻译中几乎没有丢失任何东西。
- 文本生成也可以工作,但是很难看到这个任务的任何“真实”用例,因为输出纯粹是虚构的。
- 在命名实体识别中,我们在翻译中没有损失太多,我们的方法非常适用。
- 在文本摘要中,模型应该用于捕获较长文本的核心思想,而不是生成语法正确的文本。
简而言之,translate 的概念->使用预先训练的英语模型-> translate back 是一种有用的方法,可以在较小的非流行语言上完成各种自然语言处理任务。
使用倒排索引进行高效的文档相似度计算
实践教程
一种在某些情况下更有效地处理成对相似性计算的简单方法,使用 python 示例。

图片作者:https://www.pexels.com/photo/clear-light-bulb-355948/
搜索查询背后的魔力
我目前的谷歌搜索显示 Quora 上有超过 300 万个问题,但即使你正在键入一个新问题,它们也能立即向你显示类似的问题。我只需在谷歌的搜索栏中输入这些信息,算法就会根据我的搜索查询立即向我显示最相关的内容。
尽管有许多先进的概念来实现这个魔术,我将向您展示一个古老而简单的方法,用于在某些场景中更有效地处理文档的成对相似性计算。我将把它与暴力方法进行比较,并给出一些 Python 代码示例。
完整代码可以在这里找到: GitHub 链接。
相似性比较的重要性
如果我们可以用向量解释任何信息,那么通过一些相似性比较,我们就可以找到这个域中隐藏的联系。
相似性比较方法被大量用于聚类向量。它用于主题检测、文档聚类、语言翻译或检查抄袭。它还用于在社交媒体网站上推荐 youtube 视频或朋友建议。
还有更多的应用领域。简而言之,如果我们可以用向量解释任何信息,我们就可以利用它进行信息检索。
了解倒排索引
假设您有大量包含许多单词的文档。在这种结构中,根据单词所属的文档来收集单词。换句话说,文档是父,文字是子。倒排索引方法颠倒了这种关系,将单词作为父级,将文档作为子级。我将用一个例子来说明这一点。
假设您有这样的文档:
doc1: "Today is a beautiful, and a sunny day to start my workout."
doc2: "I will not be able to come today to meet with him."
doc3: "Our class meeting starts soon!"
doc4: "My class starts at 6."
在标记化、删除停用词和一些清理之后,文档看起来像这样:
doc1: ["today", "beautiful", "sunny" "day", "start", "workout"]
doc2: ["come", "today", "meet"]
doc3: ["class", "meet", "start", "soon"]
doc4: ["class", "start"]
现在我们可以颠倒文件和文字。
today : [doc1, doc2]
beautiful : [doc1]
sunny : [doc1]
day : [doc1]
start : [doc1, doc3]
workout : [doc1]
come : [doc2]
meet : [doc2, doc3]
class : [doc3, doc4]
soon : [doc3]
现在我们可以看到哪些单词出现在哪些文档中。或者,我们甚至可以演示单词在文档中的位置,但是为了简单起见,我们将使用这个演示。
我们如何从倒排索引结构中获益?
假设您正试图通过这些文档的特征嵌入(可以用 word2vec、doc2vec、BERT 或 TF-IDF 等创建的文档的数字表示)对它们进行聚类。),你会通过这些向量的余弦相似度来做到这一点。在强力方法中,由于有四个文档,您必须进行 6 次不同的相似性检查。
倒排索引结构有助于我们发现具有共同单词的文档。只有当一个文档与其他文档至少有一个共同的单词时,我们才会将它们进行比较。
为了提高效率,我们将按照数字顺序遍历文档。对于文档 1,它出现在单词“今天”和“开始于另一个文档”的集合中。这些集合的组合给出了[文档 1,文档 2,文档 3]。因此,我们将计算(文档 1,文档 2)和(文档 1,文档 3)之间的相似性得分。我们可以接着看第二份文件。
由于我们已经讨论了第一个文档,我们将不再考虑它。因为文档 2 与文档 4 一起出现在“meet”集合中,所以我们将只找到相似性(文档 2,文档 4)。
对于文档 3,由于它存在于文档 4 的“类”集合中,所以我们将只找到相似性(文档 3,文档 4)。
因为我们只剩下一个文档,所以我们不需要做任何计算。在前面的步骤中,我们已经涵盖了所有可能的配对。
因此,我们计算了。
(doc1, doc2) (doc1, doc3) (doc2, doc4) (doc3, doc4)
比较的次数比蛮力方法少两个!这似乎对性能有一点影响。因此,让我们用一个真实的数据集来尝试这种方法。
Python 的真实例子
我们将从这里加载一个文本数据集,它包含不同主题的土耳其文本数据。
数据是这样的:
category text
0 siyaset 3 milyon ile ön seçim vaadi mhp nin 10 olağan...
1 siyaset mesut_yılmaz yüce_divan da ceza alabilirdi pr...
2 siyaset disko lar kaldırılıyor başbakan_yardımcısı ar...
3 siyaset sarıgül anayasa_mahkemesi ne gidiyor mustafa_...
4 siyaset erdoğan idamın bir haklılık sebebi var demek ...
我们会在使用之前清除数据。
现在是时候生成一些特征向量了。目前,我所知道的最好的生成特征向量的方法是使用句子-Bert。你可以点击这里了解更多。但是,这超出了这篇博文的范围。因此,我们将使用简单的 TF-IDF 进行演示。
现在让我们试试暴力和倒排索引方法。
蛮力方法:
倒排指数法:
结果如下:
强力方法:

倒排指数法:

这两种方法之间没有明显的区别,不像承诺的那样。现在是时候讨论倒排索引方法的瓶颈了。
只有当文档足够清晰并且每个文档中的字数很少时,倒排索引方法才是有用的。否则,它可能比强力方法慢,因为生成倒排索引结构需要额外的计算。
这个问题最常见的解决方案是减少每个文档中的数据。有许多不同的特性选择方法,这些方法超出了本文的范围。为了解决这个问题,我们将从每个描述文档的文档中提取 50 个关键字,并用它们来表示文档。
提取关键词
结果如下:


其中“B.F .”代表“蛮力”,“I.I .”代表“倒排索引”。原始数据显示关键字提取前的结果,简化数据显示关键字提取后的结果。
提取关键字后,强力方法的比较次数没有变化,运行时间略有不同。不过倒排索引性能提升明显!这几乎比使用 1000 行数据的暴力方法快 50 秒。如果您正在处理数千行数据,那么运行时将会有显著的不同。
你可以在这里找到完整的代码: GitHub 链接。
最终意见
这篇博客文章展示了一种加速两两相似性比较操作的普通而简单的方法,并用 python 例子展示了它。结果表明,倒排索引方法仅在文档很短或非常清晰的情况下有用。尽管如此,对长数据使用数据简化技术还是有很大的不同。
今天,根据数据和挑战,可能有更好、更合适的方法。然而,倒排索引是一种易于实现和理解的方法。
就是这样。我希望你喜欢它,非常感谢你阅读我的博文。
如果您愿意,可以通过以下链接联系我:
推特:【https://twitter.com/etolga_ayan
领英:https://www.linkedin.com/in/emre-tolga-ayan-7b9a0a149/
GitHub:https://github.com/tolgayan/
干杯,
埃姆雷·托尔加·阿扬
参考
[1] S .耶尔德勒姆,土耳其语文本分类的基准数据,https://www.kaggle.com/savasy/ttc4900
[2] K. Ganesan,教程:用 TF-IDF 和 Python 的 Scikit-Learn 提取关键词,https://ka vita-Ganesan . com/Extracting-Keywords-from-text-tfi df/#。YDNHzegzZnJ
[3]利用倒排索引、草绘和采样的高效文档相似度,https://cosine similarity . org/content/inverted-index-sketching-sampling . html
在数据科学中使用吉拉和用户故事
如何在数据领域成功实施吉拉和敏捷项目管理

威尔弗里德·桑特在 Unsplash 上的照片
无论你是项目经理还是产品负责人,项目管理工具和一些基本的敏捷技术将极大地帮助你管理你的项目或产品。至少,这些工具会给你、管理层和你的团队一个更好的概述。此外,结果可能是更快的实现、更少的查询、更好的时间估计和更大的动力。在下面的文章中,我想为您提供一些可以帮助您改进项目管理和底层用户故事的要点。
管理工具
在这里,正如标题所示,我推荐吉拉——用于管理目的的标准软件。你可以很容易地从免费 SaaS 解决方案【1】开始。您可以在应用程序中以 Scrum 或看板模式运行您的项目。免费版本为您提供了许多优秀的功能,如积压、报告、工作流等。

吉拉·斯普林特——作者图片
如果您有特定的 GDPR 需求,您也可以在本地托管吉拉或现在所谓的数据中心。像 Trello(也是免费的)或开源版本 Wekan(免费,但是你必须建立一个服务器)也存在,但是我更喜欢吉拉。在回答了 toll 问题之后,我想给你六个你在从事数据科学项目时应该注意的主要话题。
Scrum 对看板对瀑布
选择工具后的下一步可能是决定你想使用哪种项目管理方法。当使用 Scrum 时,我会推荐吉拉作为首选工具——因为它提供了你需要的一切。通过使用 Scrum,你将需要设置角色、仪式和工件。此外,Scrum 专注于复杂的软件开发。当在开发新的复杂软件产品或服务的环境中使用时,所描述的项目管理方法可以显示其全部优势。

看板板——图片由 Gerd Altmann 在 Pixabay 上拍摄
另一方面,由于持续改进的核心原则,看板适合作为可控软件过程中的一种方法。看板经常在支持或维护团队中使用,在这些团队中,要解决的任务很复杂,但通常并不复杂(比如软件推出或维护工作)。看板关注过程效率和生产率的提高。除了 Scrum 和看板,BEAM 方法可能对你也有意义——你可以在这里阅读更多关于它的内容。
讨论这些原则,甚至传统的项目管理方法,并决定哪一个最适合你,可能是一个相当困难的过程。在这里,你必须自己做一点研究。这也是有意义的,这样你就理解了理论背景。这个决定也是基于你的组织和人们想要如何一起工作——所以简而言之:你必须自己去发现它。对于我自己,我真的可以推荐吉拉文档[4]。我的观点是,如果你在数据集成、分析、报告等领域工作。由于复杂的任务和不断变化的需求,敏捷工作确实很有意义。
用户故事
在建立了基本的基础设施并决定使用哪种项目管理方法之后,让我们从用户故事开始。用户故事是用日常语言表述的软件需求,并且有意保持简短。为了更深入,我推荐下面的文章: 如何在敏捷软件开发中编写好的用户故事 。

用户故事示例—作者提供的图片
在吉拉,我将用户故事放在描述字段中。通常把其他重要的参考放在那里也是有意义的。如果有必要,你可以使用与故事相关的任务。
积压优化
谈到故事和任务,很好地描述它们非常重要,尤其是在大数据领域。大多数技术细节必须被很好地记录下来,这样来自开发人员的错误和问题就可以保持在较低的水平。在这里,我向 sprint 规划推荐一个反周期的 backlog 精化,在这里产品负责人和团队可以讨论故事以及如何在技术上实现它们。

可思考的敏捷过程——作者图片
然后,可以标记好准备好的优先故事,并将其引入下一个 sprint。当不使用 Scrum 时,类似的团队会议和协作也是有用的。
验收准则
验收标准 (AC)是软件产品被用户、客户或其他系统接受所必须满足的条件。它们对于每个用户故事都是独一无二的,并且从最终用户的角度定义了特性行为[5]。

AC 示例—作者提供的图像
在吉拉,你也可以把它放在描述字段或为它创建一个新的文本字段。在这里,产品负责人投入一些时间以满足业务需求真的很重要——这取决于他在业务部门和开发团队之间进行调解。
故事点与人工日
我真的会推荐以上两种工具中的一种或两种。故事点是一个用来描述用户故事大小的单位。他们代表了开发工作。
传统的软件团队以基于时间的格式创建评估,例如,天、周和月。然而,许多敏捷团队已经转移到故事点。故事点是估计完全实现产品待办事项或其他任务项所需的总工作量的度量单位。团队根据任务的复杂性、工作量以及风险或不确定性来分配故事点。赋值是为了更好地管理不确定性,以便有效地将任务分成更小的部分[1]。
根据我的经验,我喜欢使用人工来完成 ETL/ELT 管道、建立一些数据库和其他相关工作,这些工作可能也很复杂,但是团队已经很熟悉了。对于更复杂的任务,如开发深度学习算法或建立新的基于云的数据湖——通常是你以前从未做过的事情——使用故事点是有意义的。
谈谈你的工作
最后但同样重要的是,我的建议是,总是谈论你和你的团队取得的成就,以及你的故事如何有助于改善业务和流程,或者至少使它们更容易。我的经验告诉我,公司中像 BI、数据科学或工程这样的新团队通常不太受关注。事实上,同事们可能会问他们在公司里到底在做什么。为了给你的团队留下好印象并获得后续订单,项目营销是必不可少的。此外,通过认可,团队通常会更加团结,积极的情绪会传播开来——至少在我看来是这样。
GIF 由 GIPHY
项目营销被理解为一个项目在其环境和超越。项目营销及其效果在实践中常常被低估,过多的精力被放在应对技术需求上。项目的主动“销售”然后被遗忘:结果是项目团队做了很好的工作,但是这没有被任何人注意和欣赏。因此,在争夺稀缺资源或有价值的注意力方面,该项目往往落后于其他项目,并且在未来将不得不应对比正常项目营销更糟糕的情况[7]。
结论
希望这篇文章能给你一些启发,有所入手。第一步永远是建立一个工具集,当然还有作为产品或项目经理与你的团队取得联系,例如与吉拉取得联系。在我看来,所提供的工具和方法是最重要的因素之一,尤其是在数据领域。我真的推荐使用敏捷过程来更快地部署已开发的系统,以便最小化过程中的风险和不期望的开发。你可以在下面的资源和阅读材料中找到更多的灵感和工具。在数据科学领域,敏捷方法很有前途,因为通常不可能预先评估项目是否可以使用可用数据完成。结果在多大程度上是成功的,只有一旦有了结果才能判断。
资料来源和进一步阅读
[1]吉拉,我们的云产品能够更好地协同工作 (2020 年)
[2]https://trello.com特雷罗 (2020)
[3]韦坎,开源看板 (2020)
[4]吉拉,敏捷宣言还是一样东西吗? (2020 年)
[5] altexsoft,验收标准:目的、格式和最佳实践 (2020)
[6] Quickscrum,产品积压细化 (2020)
[7]项目管理手册,项目营销 (2021)
ML 流水线中的 Schemafull 流数据处理
使用带有 AVRO 和模式注册的 Kafka,使容器化的 Python 流数据管道利用模式进行数据验证
介绍
在我以前的一篇关于机器学习管道的文章中,消息队列被认为是 HTTP 客户端-服务器架构的一种替代方式,这是现今服务于 ML 模型的最常见的方式。
提醒一下,在 ML 管道中使用像 Apache Kafka 这样的队列有以下优点:
- 输入数据生产者和数据处理器(Kafka 术语中的消费者)解耦的自然异步处理,即消费数据的 ML 推理/训练服务的失败不会导致 HTTP 超时、数据丢失和数据生产者的失败,而只会导致延迟处理
- 更好地利用 CPU/GPU 相关任务的计算资源,例如 ML 推断/训练:
- 批量推断通常效率更高,因为 ML 模型不需要为每个预测预热和拆除
-如果传入数据不规则到达,队列会平滑负载,而 HTTP 服务器会在负载高峰期间不堪重负,并在安静期间空闲
- 批量推断通常效率更高,因为 ML 模型不需要为每个预测预热和拆除
- 提高吞吐量。然而,值得一提的是,当推理延迟至关重要时,队列并不完美。在这些情况下,轮询间隔非常短的 Kafka 消费者(这消除了以前的一些优势)或 HTTP ML 模型服务器仍然是可行的选择。
建议读者在阅读本文之前先熟悉 Apache Kafka 和 Docker(包括 docker-compose)。Confluent 有一个不错的介绍卡夫卡的页面。 Docker docs 也很棒。
为什么要使用模式?
大多数关于如何使用 Kafka,尤其是 Python 的教程都展示了生成和使用无模式 JSON 字符串的例子。尽管运行这些示例很容易,但它们并不太适合生产使用。我们来看看为什么。
无模式方法的缺点
- 生产者不能强制其发布到主题的数据格式(字段集),这可能导致消费者端的不兼容和运行时错误。当然,它可以在代码中处理,但这会使代码更加复杂,因此容易出错,需要更多的维护。
- 数据格式演化没有约束,因此生产者可以潜在地切换到新的模式,而不需要消费者期望的字段,这可能再次导致运行时错误(有关详细信息,请参见下一节)
- JSON 格式的数据包括每个消息的字段名,这使得它在存储空间方面效率很低
Schemafull 消息解决了这些问题,但是,它的代价是更复杂的基础设施,并且需要对模式演化非常小心。
AVRO 图式及其演变
Kafka 支持 AVRO 、 Protobuf 和 JSON-schema (这仍然存在 JSON 数据格式非二进制且在存储方面不是很高效的缺点)。我们将在文章的代码中使用 AVRO,因为这似乎是卡夫卡最常见的模式格式。
一旦定义,模式通常不能随意更改。提到的一个例子是字段删除或重命名。这个潜在的问题可以通过将字段定义为nullable并使用default值来解决,因此如果字段变得过时,生成器只需停止填充它,同时它仍保留在模式的新更新版本中:
{"name": "value", "type": ["null", "double"], "default": null}
其他模式进化考虑在 AVRO 规范中有描述。
演示设置
这个库有一个 Python AVRO 消费者和生产者的基本例子:
https://github.com/isenilov/python-kafka
让我们回顾一下它所包含的服务,查看docker-compose.yaml的services部分
动物园管理员
Apache ZooKeeper 由 Kafka 用于在一个集中的位置维护配置,并且是 Kafka 集群工作的先决条件。消费者和生产者通常不与它直接交互,所以我们在这里省略了它的详细描述。
图式注册表

用于存储和检索模式的汇合模式注册表( 来源 )
汇合模式注册中心是一个用于存储和检索模式的服务。显然,人们可以使用 Kafka,而不仅仅是拥有模式和应用程序代码,但是模式注册中心确保了所有消费者和生产者的版本化模式的单一真实来源,排除了这些消费者和生产者之间模式偏差的可能性。
注册表既有 RESTful API,也有由 confluent-kafka-client 提供的本地支持,这实际上是在 Python 中使用 kafka 的标准。
可以通过 HTTP 请求注册模式,如下所示:
SCHEMA=$(sed 's/"/\\"/g' < ./Message.avsc)
curl -X POST -H "Content-Type: application/vnd.schemaregistry.v1+json" \
--data "{\"schema\":\"${SCHEMA//$'\n'}\"}" \
http://<registry_host>:<registry_port>/subjects/<name>/versions
它应该返回模式注册的 id(版本):{“id”:1}
使用 Python 代码中的模式可以通过从前面提到的客户端实例化AvroSerializer和AvroDeserializer来完成。
卡夫卡
Apache Kafka 集群本身使用来自汇合的 Docker 映像运行,该映像具有其运行所需的一切。
它有许多配置选项,但我们仅限于基本的工作选项,例如,PLAINTEXT协议不执行任何加密。
init-卡夫卡
这是 Kafka 的一个短暂实例,仅用于创建我们计划在使用kafka-topics CLI 的演示中使用的主题:
kafka-topics --create --if-not-exists --topic Test.Topic --bootstrap-server kafka:29092 --replication-factor 1 --partitions 1
为了简单起见,将replication-factor和partitions选项设置为 1。参考 Kafka 文档了解更多关于这些和更多其他选项的细节。
工人
这是消费和生产应用程序本身。为了简单起见,消费者和生产者都在__main__.py中。代码大部分是不言自明的,所以让我们只看它的一些重要部分。
生产者配置:
"value.serializer": AvroSerializer(schema_str=Message.schema,
schema_registry_client=schema_registry_client, to_dict=todict)
schema_str=Message.schema — producer 需要传递一个模式,这里我们传递包含该模式的生成的 Python 类的属性。模式将在schema_registry_client中上传和注册。
to_dict=todict —这必须是将消息对象转换为 Python 字典的可调用对象。我们使用来自生成代码的helpers.py的todict方法。
消费者配置:
"value.deserializer": AvroDeserializer(schema_str=None, schema_registry_client=schema_registry_client,
from_dict=lambda obj, _: Message(obj))
这里我们不传递模式,所以schema_registry_client将从模式注册中心获取它。
from_dict=lambda obj, _: Message(obj) —将字典转换为消息对象的相反操作。这里我们需要做一点修改,因为反序列化器将 context 作为第二个参数传递给 callable(我们忽略了它),但是生成的类构造函数只接受字典。
"group.id": "test_group"
“group.id”。这允许共享相同消费者组 ID 的多个消费者(读取具有数据处理应用程序的多个容器以进行水平缩放)轮流消费消息,因为他们共享 Kafka 偏移量。同时,如果需要将这些消息用于其他目的,例如,在线测试一个实验性的 ML 模型,可以通过使用新的group.id部署这个实验组来完成,从而避免与主要的产品结构混淆。
运行演示
存储库的README.md有使用docker-compose运行所有服务的指令。执行这些命令应该会在 stdout 中产生类似这样的结果:
worker_1 | Waiting for schema registry to be available
worker_1 | Producing message to topic 'Test.Topic'
worker_1 | Produced message: {'timestamp': 1639304378, 'data': {'count': 10, 'value': 100.0}}
worker_1 |
worker_1 | Flushing records...
worker_1 | Consumed message: {'timestamp': 1639304378, 'data': {'count': 10, 'value': 100.0}}
这意味着生产和消费是成功执行的。
我们打印消费的消息:
if message is not None:
print(f"Consumed message: {message.dict()}")
但是,这是推理代码通常随后将结果预测发布到另一个主题、将它们保存到数据库或在下游 API 调用中使用它们的地方。
监视
一般来说,对 Kafka 消费 ML 服务的监控类似于在NLP 任务的完整机器学习管道文章中所描述的。
然而,推断时间可以(并且可能应该)伴随着消费者滞后,这表明消费者落后于公布的数据多少。如果增长,通常意味着消费者群体必须扩大。当使用基于[librdkafka](https://github.com/edenhill/librdkafka#language-bindings) 的客户端时,如本例中使用的confluent-kafka-python,可以使用librdkafka返回的统计数据来获得消费者滞后,如本期所述。
结论
本文展示了为什么在 Kafka 中使用模式可能是一个好主意,以及如何使用 Python(ML 服务的首选语言)来实现它。一如既往,如果您对内容有任何问题或建议,请随时通过 LinkedIn 联系我。
自然语言处理中使用关键词抽取进行无监督文本分类
自然语言处理的应用
无监督分类任务的混合方法

马库斯·斯皮斯克在 Unsplash 上的照片
文本分类是自然语言处理中的一项常见任务。主要的方法倾向于以一种有意义的方式表示文本——无论是通过 TF-IDF、Word2Vec,还是像 BERT 这样更高级的模型——并将表示上的训练模型作为标记输入。然而,有时要么标记数据是不切实际的,要么没有足够的标记数据来建立有效的多分类模型。相反,我们被迫利用无监督的学习方法来完成分类任务。
在本文中,我将概述我为数据科学面试/职业准备网站 Interview Query 的面试问题数据集构建无监督文本分类器的过程。出于几个原因,这将对他们非常有益。面试查询希望能够为用户提供更多关于他们申请的公司的有见地的信息,以及练习特定问题类型的功能。最重要的是,这将使他们能够通过他们提出的问题类型来“描述”不同的公司。
我们的任务是将一个给定的面试问题归类为与机器学习、统计学、概率、Python、产品管理、SQL、A/B 测试、算法或带回家有关的问题。我认为最实用的方法是首先从语料库中提取尽可能多的相关关键词,然后手动将得到的关键词分配到与我们想要的分类相对应的“箱”中。最后,我将遍历数据集中的每个面试问题,并比较每个箱中关键字的总数,以便对它们进行分类。还考虑了使用潜在狄利克雷分配的可能性,以便生成主题模型和检索与每个主题相关的关键词,而不必手动分配它们,以及 K-均值聚类。鉴于我们的分类范围广泛且不同,这些被证明是困难的,并且不如简单地统计关键词有效。
首先,数据必须被清理和预处理。我使用 SpaCy 对文本进行标记化、词汇化、小写化和删除停用词。
import pandas as pd
import nltk
import spacy
from tqdm import tqdnlp = spacy.load("en_core_web_sm")def create_tokens(dataframe):
tokens = []
for doc in tqdm(nlp.pipe(dataframe.astype('unicode').values), total=dataframe.size):
if doc.is_parsed:
tokens.append([n.lemma_.lower() for n in doc if (not n.is_punct and not n.is_space and not n.is_stop)])
else:
tokens.append("")
return tokensraw = pd.read_csv("topics_raw.csv")
tokens = create_tokens(raw)
接下来的问题是选择一种从文本语料库中提取关键词的方法。因为我的语料库由大量小“文档”组成,每个文档都是不同的面试问题,所以我决定从每个文档中单独提取关键词,而不是组合任何数据,并根据频率从结果列表中排序唯一的关键词。
然后,测试开始了。考虑了各种方法,如 TF-IDF、RAKE,以及一些最近的最新方法,如 SGRank 、 YAKE 和 TextRank 。我也很好奇,尝试了 Amazon understand,一个自动 ML 解决方案,看看它有多强。不幸的是,结果并不令人满意,因为高层次抽象与 NLP 任务粒度的结合仍然被证明是不切实际的。最后,在比较了每种方法产生的关键词后,我发现 SGRank 产生的结果最好(相关关键词数量最高)。
import textacy
import textacy.ke text = " ".join(raw.tolist())
nlp = spacy.load('en_core_web_sm')
nlp.max_length = len(text)keywords = []
for tokenlist in tqdm(question_tokens):
doc = nlp(" ".join(tokenlist))
extract = textacy.ke.sgrank(doc, ngrams=(1), window_size=2, normalize=None, topn = 2, include_pos=['NOUN', 'PROPN'])
for a, b in extract:
keywords.append(a)
最后,我按照频率对唯一的关键词进行了排序,以获得最显著的关键词。
res = sorted(set(keywords), key = lambda x: keywords.count(x), reverse=True)
结果是大约 1900 个单词,然后我手动检查并把最相关的 200 个单词分配到我们的垃圾箱。

最后,有了最终的分类关键词列表,就可以通过统计关键词在每个问题中的出现次数,将每个面试问题分类为 8 种不同类型中的一种。此外,我们可以为不同的公司生成“个性”档案,并在网站上显示。

https://www . interview query . com/interview-experiences/Facebook/data-scientist
总之,我发现对于这个特定的问题,最好是简单地选择一种混合方法来完成无监督分类任务,这种方法既涉及机器学习,也涉及人工工作。
一般来说,在自然语言处理中无监督的上下文中工作,在数据分析和结果的实际应用之间留下了相当大的距离,这迫使人们采用本文中看到的替代方法。在我看来,这是 NLP 的一个明显的缺陷,比其他领域如计算机视觉或生成模型更严重。当然,我预计未来更有洞察力的模型和其他研究的进步将在这方面取得显著的进步。
无论如何,感谢你阅读这篇文章!我希望你学到了一些东西。
使用线性规划来提高你的强化学习算法
大而高维的动作空间往往是强化学习的计算瓶颈。将你的决策问题公式化为一个线性规划可以极大地增加你的算法可以处理的问题的范围。

照片由穆罕默德·图尔古特·柯克戈兹通过派克斯拍摄
请运筹学研究者解决任何问题——无论是优化你的股票投资组合、安排你的送货路线,还是解决你的婚姻问题——他们很可能会脱口而出“线性规划”作为他们的首选解决方案。线性规划是二战后作为美国陆军航空兵自动化规划程序的一种手段而构想的一种数学方法,此后已发展成为一个成熟的领域,广泛应用于运输、制造、金融、医疗保健和许多其他领域。典型的实现处理具有成千上万个决策变量、无数个约束以及大量成本和回报成分的问题。这些问题不仅仅是学术上的想象;真实世界的实际问题就是这样建模和解决的。事实上,线性编程在如此多的环境中工作得如此之好,有时看到它在强化学习社区中受到的关注如此之少令人惊讶。
线性编程导论
对于那些不熟悉这种方法的人来说,一个非常简短的入门可能是合适的。线性规划本质上是遵循固定结构的方程组,包含(I)必须最小化或最大化的目标函数,(ii)决策空间上的一组约束,以及(iii)具有相应域约束的决策变量的定义。顾名思义,整个系统应该是线性的——这个强大的特性允许利用问题结构的凸性,并显著提高求解方法的速度。求解该系统给出最优解。典型的线性规划方法如下所示:

为什么要用这样的公式呢?嗯,没有人比国父乔治·丹齐格本人更能说明线性编程的潜力了[1]:
“考虑一下给 70 个人分配 70 份工作的问题。“活动”包括将第 I 个人分配到第 j 个作业。[……]因此有 2 * 70 或 140 个限制和 70 * 70 或 4900 个活动,有 4900 个相应的 0-1 决策变量。不幸的是,还有 70 种阶乘排列,或者说分配方式。[…]
在这个例子中,70 阶乘是一个非常大的数字。为了了解它有多大,假设我们在 1500 万年前的大爆炸时有一台 IBM 主机。从那时到现在,它能够检查所有可能的解决方案吗?不要![……]即使地球上到处都是纳秒速度的计算机,它们都并行工作,答案仍然是否定的。[……]令人惊奇的是,单纯形法在现代计算机的帮助下可以在瞬间解决这个问题。"
至少可以说,令人印象深刻。这种单纯形法发展于 20 世纪 40 年代末,已经成为许多高级解算器的基础,例如现在使用的 CPLEX 和 Gurobi。结合硬件的不断进步,该领域的进展令人震惊,解决的问题越来越多。请记住,线性规划是一种精确的解决方法:它不会简单地返回一个可行的或“好”的解决方案,而是所有这些数百万、数十亿或数万亿个解决方案中的最佳可能解决方案,很少会在几秒钟内返回!
强化学习中的应用
目前关于线性编程已经说得够多了——我不想给人留下我在推销或试图抬高我的 IBM 股票的印象。现在让我们把注意力转向强化学习。为了方便起见,假设我们试图(近似)求解一个马尔可夫决策模型。当试图这样做时,我们经常遇到所谓的三个维度的诅咒,指的是当问题的规模增长时,状态空间、结果空间和行动空间都爆炸[2]。在著名的贝尔曼方程中,这些计算问题是显而易见的:

结果空间S’指的是从所选择的状态-动作对可以达到的未来状态的集合。由于每个状态都有自己的价值,因此为每个可行的决策遍历所有这些状态是一项繁琐的任务。我们不是评估所有可能的结果,而是选择一个样本,然后继续前进。通过经常这样做,大数定律预测我们应该收敛到潜在随机变量的真实值。对于状态空间 S ,我们通常定义特征(基本上只是解释变量)来提取预测其值的最显著的属性,而不是显式地观察每个状态。因此,我们不需要观察每个州来评估它有多吸引人;我们可以根据特征做出合理的猜测。这些解决方案被广泛嵌入到许多强化学习算法中。
动作空间 X 的维度受到的关注相对较少。部分地,这可以用这样一个事实来解释,即对于许多问题,完全枚举是可能的。一旦我们生成一个动作,我们只需要计算状态-动作对的值来衡量动作的吸引力,因此对于适度的动作空间枚举工作得非常好。在迷宫中你只能向四个方向移动,即使是超级马里奥也没有那那么多锦囊妙计。在棋盘上或在围棋比赛中,事情变得更加复杂,但没有什么是现代计算机不能处理的。除了列举之外,我们当然还有政策梯度定理,它经常被部署在行动者-批评家方法的保护伞下[3]。我们不必列举所有可行的行动,而是可以巧妙地对它们进行采样,并在此过程中调整我们的采样策略。由于没有明确地评估每个动作,我们自然会丢失一些信息,但是只要我们平稳地收敛到好的动作区域,我们通常会认为这是理所当然的。
不幸的是,枚举法和演员批评法并不总是能拯救我们。我们挑一个简单的例子来说明。考虑一个多式联运中心,集装箱可以从这里通过卡车、驳船或火车运输。假设集装箱有 25 个潜在目的地,10 个不同的重量等级,到期日在 1 到 5 之间。然后,我们有 25105=1250 个不同的容器类型,转换成具有 1250 个维度的状态向量,指示当前在中心的每种类型的容器的数量。即使没有正式的证明,也很容易看出可能的排列数量是绝对巨大的——正面处理这些状态和结果空间是不可能的。然而,我们现在感兴趣的是行动空间。每个集装箱可以通过卡车、驳船或火车运输,或者留在中心。如果我们在中心有 n 个集装箱,每个集装箱的这 4 个选项给了我们 4^n 可行的行动;这正是我们不愿意处理的指数行为空间。举例来说:仅仅用 10 个容器,我们就已经面临超过一百万个可行的行动。
这里可以很快排除枚举。我们不想为每一集列举一百万个动作,上帝禁止我们在糟糕的一天遇到 20 个容器。那么是演员兼评论家?我们可以概念化演员网络的各种架构,但它们都面临着同样的挑战。状态向量和动作向量可能很大,但也很稀疏。到目前为止,大多数容器类型在某一天的值都是 0。不幸的是,一个神经网络通过传递一串零是不会学到很多东西的。在高维空间中采样是一项具有挑战性的任务;在这种情况下,大多数演员会在森林里迷路。
集成线性规划和强化学习
尽管面临一个相当干净和直接的问题,但我们无法列举,也无法有效地应用演员-评论家方法。不出所料,这就是线性编程重新登上舞台的地方。还记得这种方法是如何方便地处理大型高维动作问题的吗?真巧。
首先,我们需要将我们的决策问题转换成前面介绍的通用格式。在这种情况下,我们希望以最小化运输成本的方式分配集装箱。知道了我们的模态的成本函数,我们可以全面地将它包装在一个目标函数中。最有可能的是,并不是所有的分配都是允许的:有固定数量的卡车,驳船只能处理一定的重量,显然我们不能分配比我们更多的集装箱…我们的一套约束条件已经成型。决策变量?一组二进制文件似乎可以做到这一点。完成了。自然,并不是每个问题都允许自己如此容易地被这种僵硬的格式所捕获。然而,也不应轻易排除这种可能性。来自许多学科的学者和实践者已经成功地将高度复杂的问题融入到这种格式中。随着整个航空公司的运营被线性程序所控制,你的 RL 问题也有可能被塑造成同样的形状。

二维多面体的线性优化,Ylloh 通过 WikiMedia
好了,我们已经为今天的决策问题建模了,但是明天呢?毕竟,向前看是强化学习如此有趣的原因。为此,我们需要添加一些人为变量。这些变量表示捕获处于给定状态的下游值的特征。在这种情况下,我们通常会手动设计这些功能,使用基于状态和动作的线性表达式来计算它们。
假设——作为我们的功能之一——我们跟踪到期日为 5 的集装箱数量。我们现在有六个,我们考虑运输四个:我们剩下两个(即ϕ_f(s,a)=ϕ_f(6,4)=6–4=2).其他的例子可以是卡车的下一个位置、延期后的剩余现金预算等。所有这些特征都是从状态-动作对计算出来的;学习相应的权重θ_f 以将值赋予特征。
要更深入地了解这种情况下的功能设计,以下关于决策后状态的文章可能会有帮助:
[## 什么是后决策状态?他们想从我们这里得到什么?
towardsdatascience.com](/what-are-post-decision-states-and-what-do-they-want-from-us-9e02105b7f40)
上述等式很容易添加到现有的约束集合中。将人工变量ϕ嵌入到目标函数中,我们只需要将它乘以我们学习到的权重θ,就可以评估我们行动的下游价值[2]。

总而言之,线性规划值得任何处理大量决策的人考虑。当然,还有本文没有讨论的缺点、限制和挑战,但是有了 70 年的历史记录,这种优化方法还远远算不上时尚。与枚举相比,它提供了令人难以置信的升级,同时保持了评估动作的最优性。当你的笔记本电脑的内存不断被无止境的枚举动作数组填满,当你的演员一直带你无处可去,线性编程可能只是你的 RL 算法需要的推动力,以恢复这些巨大的动作空间的秩序。毕竟,正如乔治·丹齐格几十年前发现的那样:线性程序可以管理军队。
参考
[1]与 George B. Dantzig 的访谈:线性规划之父—大学数学杂志(1986)。http://www.phpsimplex.com/en/Dantzig_interview.htm
[2]鲍威尔(2011 年)。近似动态规划,约翰·威利父子公司,第二版。
[3]萨顿和巴尔托(2018 年)。强化学习:导论,麻省理工学院出版社,第二版。
用线性规划调度司机。
使用 Python 中的线性编程库 PuLP 优化工作分配。

埃里克·罗瑟梅尔在 Unsplash 上的照片
介绍
对于大型操作,工作分配可能是一项相当大的日常任务。在这篇文章中,我将重点介绍如何使用 PuLP 来创建一个分配工作的模型。可以使用 Excel 中的 VBA 自动完成这一过程,但是,这是一种“贪婪”的方法,为每个单独的驾驶员而不是为整个分配寻找最佳路径,因此效率不高。
Excel 确实有一个可以解决线性和非线性问题的规划求解器,但它仅限于 200 个决策变量,对于大型运算,这种解决方案是可行的。
什么是纸浆?
PuLP 是 Python 中的一个开源库,可以用来解决这类优化问题。
为了使用纸浆解决这个问题,你需要建立一个线性目标函数,然后模型将尝试最大化或最小化这个总价值。典型地,在运输操作中,你会有路线和司机在一天中间隔地开始,所以我希望该模型尝试最小化开始时间的整体偏差。然而,我还想考虑司机的偏好——使其在路线类型上更有利于匹配他们的偏好。

目标函数

包含路线和驾驶员每种组合的开始时间差异的矩阵。
为了解决这个问题,我们需要为路线和驾驶员的每一个组合创建一个决策变量。
建立纸浆问题并创建二元决策变量。
这将决策变量设置为仅取值 0 或 1 的二进制变量。本质上,如果模型为一个组合返回值 1,这意味着它在最终的解决方案中。
为了避免启动时间差异大于 60 分钟的模型计划驱动程序,我决定使用启发式算法,并最大化以下函数。
根据开始时间的差异,为每个组合设置包含 0 到 1 之间的值的矩阵。
设置目标函数。
我已经为任何开始时间差异大于 60 分钟的组合指定了一个负值(-100)。当我最大化目标函数时,这实质上是一个惩罚,这意味着如果没有可行的路线,模型宁愿让驾驶员不被分配。
为了尝试考虑驾驶员偏好,如果路线类型和驾驶员偏好匹配,则从开始时间的差异返回的分数乘以 2,如果不匹配,则乘以 0.5。这使得具有匹配偏好的路线、驾驶员组合更加有利约 30 分钟,但是,如果模型不能匹配偏好,它仍然会将驾驶员分配到路线,因为该值仍然大于 0。
现在我们需要设置一些约束。
- 一条路线不能在解决方案中出现多次。
- 一个驱动程序不能在解决方案中出现多次。
您可以用与设置目标函数相似的方式创建约束,并将一个不等式分配给模型对象。
在纸浆中设置约束

纸浆中的约束示例。R1 在最终解决方案中只能出现一次。
现在来解决问题。我已经建立了一个有 10 个司机和 10 条路线的基本例子。

解决问题并找到解决方案。
在这个例子中,PuLP 成功地为 9 个司机分配了工作,其中 5 个司机被分配了与他们的偏好相匹配的工作。

司机和剩余路线。
纸浆公司无法为驾驶员 D9 分配工作,因为 R2 剩下的唯一一条路线的开始时间相差超过 60 分钟。
使用线性规划可以自动化分配工作的过程,同时找到最有效的方式来规划您的劳动力并考虑员工的偏好。
利用潜在狄利克雷分布分析定性调查数据
结合自然语言处理、情感分析、神经网络和主题建模,对开放式数据进行即时分析和可视化

调查是市场研究和数据收集中最常用的方法之一。虽然主要产出往往是定量数据,但经常会提出开放式问题,以获得广泛的回应。这些一字不差的回答往往有助于回答数字背后的“为什么”。
然而,分析开放式调查数据是一项艰巨的工作。在一个大的调查数据集中,逐字逐句的回答可能需要几个小时甚至几天的时间。不仅如此,它几乎完全是通过人类编码来完成的。因此,定性的回答经常被忽略,或者只是通过抽出一些逐字的引用来补充叙述。
这让我想到了一个问题——有没有更好的方法来揭示定性调查数据中的洞见?
我混合使用自然语言处理、神经网络、情感分析和主题建模,创建了一个可以接受数据集的模型,并自动返回数据中的关键主题。这只花了我 10 个小时的时间。
TL;DR:在十个小时内,我创建了一个模型,它可以在任何大小的数据集中自动提供关键主题。在我的测试数据集上,有‘just’3000 个响应,下面的整个模型(包括可视化)可以在不到 30 秒的时间内生成。**
如果你想和我一起出去玩,请继续读下去——我保证这是值得的。
我使用了一个包含 3000 份回复的公开数据集——来自德克萨斯州奥斯汀的一个社区调查。在调查结束时,受访者可以选择提供书面评论来回答以下问题:“如果您能与市长分享一件关于奥斯汀市的事情(任何评论、建议等)。),那会是什么呢?”
我认为这将是一个有趣的数据科学挑战,因为可能会有各种各样的答案。
这是我的机器学习管道-
第一步。潜在狄利克雷分配
潜在狄利克雷分配(LDA)是一种流行的自然语言处理(NLP)工具,可以从语料库中自动识别主题。LDA 假设每个主题由一包具有一定概率的单词组成,每个文档由一包具有一定概率的主题组成。LDA 的目标是学习语料库中的单词和主题分布。Gensim 是一个 NLP 包,特别适合 LDA 和其他单词嵌入机器学习算法,所以我用它来实现我的项目。
# Create Dictionary
id2word = corpora.Dictionary(processed_data)# Create Corpus: Term Document Frequency
corpus = [id2word.doc2bow(text) for text in processed_data]# Build LDA model
lda_model = gensim.models.ldamodel.LdaModel(corpus=corpus,
id2word=id2word,
num_topics=4,
random_state=42,
update_every=1,
chunksize=10,
per_word_topics=True)
在对数据进行一些预处理以删除常用词之后,我能够从回答者的反馈中获得主题,主要围绕-
1)生活费用
2)公用事业
3)交通
4)其他问题(归类为专题 0)
从“引擎盖下”看,我们可以看到模型识别的每个主题的最具代表性的句子。

自然,没有一个词云的定性分析是不完整的:)这里我们可以看到每个主题最有代表性的词。

现在变得有点古怪了——我还训练了一个 word2vec 神经网络,并将 LDA 获得的一般主题中的热门单词投影到 word2vec 空间。我们可以使用 t-SNE(t-分布式随机邻居嵌入)算法来可视化 2D 空间中的主题聚类。这让我们可以看到模型是如何将 4 个主题分开的——公用事业问题似乎是最少被提及的。此外,生活费用和公共设施问题之间有一些重叠。
# Getting topic weights
topic_weights = []
for i, row_list in enumerate(lda_model[corpus]):
topic_weights.append([w for i, w in row_list[0]])# Generating an array of optic weights
arr = pd.DataFrame(topic_weights).fillna(0).values# Dominant topic in each document
topic_num = np.argmax(arr, axis=1)# tSNE Dimension Reduction
tsne_model = TSNE(n_components=2, verbose=1, random_state=0, angle=.99, init='pca')
tsne_lda = tsne_model.fit_transform(arr)# Plotting the Topic Clusters with Bokeh
output_notebook()
n_topics = 4
mycolors = np.array([color for name, color in mcolors.TABLEAU_COLORS.items()])
plot = figure(title="t-SNE Clustering of {} LDA Topics".format(n_topics),
plot_width=900, plot_height=700)
plot.scatter(x=tsne_lda[:,0], y=tsne_lda[:,1], color=mycolors[topic_num])
show(plot)

最后,我用 pyLDAVis 创建了这个主题模型的交互式可视化。这可以用来绘制每个主题最突出的单词,以及查看主题的分离程度。
pyLDAvis.enable_notebook()
vis = pyLDAvis.gensim.prepare(lda_model, corpus, dictionary=lda_model.id2word)
vis

虽然这个数据集在分割数据方面稍有欠缺,但我们可以看到,不同的议会选区对主题进行了平均分割。在放弃主题 0(杂项)后,有证据表明生活成本、交通和公用事业之间存在显著的等级关系。在 9/10 地区,公共设施是人们谈论最多的改进问题。如果有人口统计数据来观察公民在突出话题上的差异,那将会很有意思。

每个议会区的不同主题
第二步。情感分析
我还对分析调查开放式评论的观点感兴趣,并将其与我的主题模型相结合。我使用 VADER 库来分配情感分数,并将一个主题的百分比评级定义为当受访者提到该主题时给出积极评论的百分比。该指标用于为主题分配情感分数。
我使用我的 LDA 模型来确定回复中每个句子的主题构成。如果一个句子有 60%或更多是由一个主题主导的,我认为这个句子属于那个特定的主题。然后,我计算了句子的情绪,或正面或负面,最后统计了每个题目中正面句子的总百分比。
这里快速浏览一下整个数据集中的情绪分布。有了这样一个开放式的问题,受访者很容易抱怨。但是考虑到 70%的回复都是正面的,我们可以推断样本倾向于对他们的家乡城市给予建设性的批评。

在这个特定的数据集中,主题之间没有太多的情感差异(如下图所示)。然而,将它应用于商业市场研究环境来观察情绪的差异将会很有趣。

总之,我相信这是逐字分析调查有效和可扩展的方法。
一些警告——由于这是一种“快速而肮脏”的方法,不能指望它完全取代人工分析。此外,LDA 还要求我们选择可以限制的主题数量。根据主题,通过更加定制的 NLP,以及 LDA 模型中更好的超参数调整,还有进一步改进的空间。然而,好处是显而易见的-
机器学习有助于减少人为偏见,节省数小时的分析时间,以便从数据中获取主要主题。这种特殊的方法可以轻松处理大型数据集,并在几秒钟内返回可操作的结果。
如果您想了解更多信息,或者有任何改进建议,请联系我们。整个项目都在我的 GitHub 库上(当你在那里的时候,请随意查看我的其他项目!)
使用机器学习将文本分类成主题
开发一种无监督学习算法来发现主题共性

图片来自 Pixabay
读完一篇新闻文章后——无论主题是美国政治、电影评论还是提高生产率的技巧——你可以转向其他人,让他们大致了解文章的内容,对吗?或者,如果你读一本小说,你可以把它分为科幻小说、文学小说或爱情小说。
人类往往很擅长对文本进行分类。现在,电脑也能做到这一点。
在最近的一个机器学习项目中,我从消费者金融保护局(Consumer Financial Protection Bureau)下载了消费者投诉,并开发了模型,将投诉分为五个产品类别之一。我的表现最好的模型在 86%的情况下都是正确的。(你可以在这个媒体博客中读到这个项目。)
我使用了一种监督学习技术,其中模型使用标记的类别,然后预测未标记的类别。在用于训练我的模型的数据集中,产品类别是由消费者自己选择的,考虑到大多数消费者不是金融专家,他们可能不会 100%完美地完成工作,错误会影响模型的性能。
或者考虑一个不同的数据集,其中的文本没有预先标记。为了训练有监督的自然语言处理(NLP)模型,人类这样做将是乏味的。这些缺点是监督模型所固有的。
作为该项目的后续工作,我想开发一个无监督学习 NLP 模型,看看会出现什么类别。这个模型会忽略预先标记的类别,而是辨别共性,以便将文本分组到自己设计的主题中。
我还想到了一个商业案例。无人监管的模型可能对消费者金融保护局有用,因为它不必依赖消费者对其提交的信息进行分类,而且它可能会将收到的信息处理为新的、不可预见的类别。本质上,无监督的 LDA 模型可以为任何从消费者或客户那里接收信息并帮助分类文本的机构工作。
创建新主题
我已经处理了对监督学习模型的投诉,对文本进行了记号化,删除了停用词,并对单词进行了词条化。(更多细节见前述博客)。对于非监督建模过程,我使用了 Gensim 的潜在狄利克雷分配(LDA)模块。
我让它将 160,000 个投诉分成五个主题,然后使用 pyLDAvis 模块创建一个可视化:

点击 这里 可以看到这些主题可视化的互动页面。
可视化用图表显示了五个主题中最常见的单词:
主题 1
本主题包括一般的金融词汇,如账户、银行、卡、钱、信用,还包括打、电子邮件、电话、信件和收到等词汇。这个话题的一个共同点是与沟通有关。
话题 2
有了征信、举报、举报、查询、局这样的热门词汇,这个话题就是关于征信。
主题 3
付款、贷款、抵押、到期、利息、余额告诉我这个题目关注的是抵押和贷款的相关问题。
主题 4
本话题热门词汇有债务、催收、法律、违规、举证、法律。这个话题似乎与法律事务有关。
主题 5
在这里,一些关键词是信息、消费者、身份、盗窃、侦查、诈骗、公正、受害者。我想说这个话题是关于身份盗窃或者对欺诈活动的调查。
在我进一步讨论这些分组之前,我想指出我可以选择多于或少于五个主题。事实上,Genism 也有衡量模型主题“连贯性”的模块。我用不同数量的主题制作了模型,并将它们与 u-mass 一致性度量进行了比较。这个度量产生一个负数,这个数越接近零,它就越连贯。

正如你所看到的,有五个主题的模型有最高的一致性分数。与此同时,在我将 coherence 作为给我的模型评分的一种方法之后,似乎许多专注于 NLP 的数据科学家并没有在它上面投入太多的精力。LDA 毕竟是一种无人监管的技术,因此有限的测量不一定能取代人类对结果的细微解释。
无监督模型有多大用处?
让我们比较一下监督学习模型的五个产品类别和我的非监督学习模型的五个主题类别。

两对主题重叠,但六个主题不同。这些发现如何具有洞察力或有用性?
我可以想象在消费者金融保护局有一个区域处理通信和消费者服务问题,一个处理法律事务,另一个处理身份盗窃和欺诈。如果是这样,LDA 模型发现属于这些相应主题之一的投诉可以被路由到适当的区域。
总之,一个无人监管的模型确实能够将收到的信息归类到新的、不可预见的类别中。这五个类别是基于我对热门关键词的直觉解读,当然还需要更深入的分析和解释。然而,更重要的一点是,有监督和无监督的 NLP 模型对于任何希望处理大量日常传入邮件的组织都是有用的。
使用机器学习对服务器事件进行分类
如何使用机器学习对服务器事件进行分类的分步介绍
介绍
当我们谈到事件管理时,自动化事件解决是每个公司的必备条件,但要让自动化在正确的事件中发挥作用并提供正确的信息,有必要自动从服务器事件中检索信息,其中大部分内容都基于文本,即事件描述,然后分类(我们称之为“匹配”)哪些已经可用的自动化可能会解决事件。
从不需要深入编程技能的事件中检索信息的常用方法是使用正则表达式,但是如果您已经使用过它,您就会知道它有多糟糕,对事件内容的最小修改就可能导致正则表达式失败。
在这篇文章中,我们描述了我们如何使用机器学习技术来改善这种匹配。

克里斯蒂娜@ wocintechchat.com 在 Unsplash 上的照片
数据准备
用于训练模型的数据基于在过去六个月中由可用自动化之一解决的事件,即来自 9 个不同国家/地区的 180 多个客户的大约 10 万起事件。我们将数据限制为仅 6 个月,因为我们不断部署新的自动化并修复一些正则表达式问题,所以为了防止使用错误/旧的数据,更小的间隔应该足够了。
我们从数据湖中检索的原始数据集有 14 列,但是我们将只关注创建模型所必需的列。
- 自动化 _ 名称:在此事件中执行的自动化的名称(我们分类的目标)
- 组件:事件中受问题影响的组件的高级名称
- 子组件:事件中受问题影响的组件的特定名称
- 摘要:事件的快速描述

数据集中包含的数据示例
在这种特殊情况下,由于组件和子组件是对摘要信息的补充,我们决定连接这三个字段,创建一个“特征字段,这一过程将使接下来的步骤更容易,除此之外,我们将所有内容设置为小写。
在一些国家,我们有不止一种语言的事件,例如英语和西班牙语,为了解决这个问题,我们使用了两个 libs 、 langdetect 来检查事件的语言,我们采用英语作为我们的主要语言,并在必要时使用 googletrans 将事件翻译成英语。这一过程可能需要几个小时,取决于您的数据集大小,我们试图只过滤必要的文本翻译成英语。
为了完成这一步,我们将数据分为特征和类,其中自动化名称是我们想要预测的类,而特征是我们上面处理的结果。
建模
准备好这两个字段后,我们终于可以开始机器学习阶段了。为了使运行我们的模型变得简单,我们创建了一个管道,这个管道有三个主要组件:
from sklearn.pipeline import Pipelinepipeline = Pipeline(steps=[
('bow', CountVectorizer(analyzer=proc_text)),
('tfidf', TfidfTransformer()),
('classifier', model)
])
- CountVectorizer: 这个函数将标记我们的数据,结果将是每个单词计数的稀疏表示
这一阶段的一个重要步骤是删除停用词和标点符号,这两个组件可能会造成我们的情况不必要的复杂性,所以使用 nltk 库和下面附加到 CountVectorizer 的函数,我们把它们去掉。
import string
import nltk
# Necessary to install stopwords, only in the first time
#nltk.download()
from nltk.corpus import stopwords
sw = set(stopwords.words('english'))def proc_text(text):
string_w_no_punc = [char for char in text if char not in string.punctuation]
string_w_no_punc = ‘’.join(string_w_no_punc)
return [word for word in string_w_no_punc.split() if word.lower() not in sw]
- TfidfTransformer: 从 CountVectorizer 返回的稀疏矩阵中,该函数将对其应用 TF-IDF(词频-逆文档频率)表示。TF-IDF 的目标是衡量一个单词在一个文档(TF)和所有文档(IDF)中的影响。
- 模型:流水线的最后一个组件是应用分类器。
管道完成后,一切如常,我们将数据分为训练和测试,并将评估每个分类器的结果。
估价
在这种特殊的情况下,我们需要一个精确和召回的平衡指标,因为在这一点上,它们都不会比另一个带来更多的麻烦。考虑到这一点,我们选择评估 F1 分数和宏观平均值,我们尝试了一些分类器,您可以查看以下结果:

测试分类器的结果
最适合我们的数据的模型是 XGBoost,但是您可以做更多的事情来改善这些数字,例如超参数优化、对少数类进行上采样、以及其他一些方法,但是我会让您自己尝试这些方法。😃
为了在生产中使用它,我们需要有一些保证,即分类器预测自动化的概率高于某个阈值,为此我们使用了来自 sklearn lib 的 predict_proba ,这个函数返回分类器为每个类返回的概率,有了这个和阈值(我们设置为 97%),我们就能够在生产中无忧无虑地实现和监控分类器。这是我们使用的片段:
pred_proba = pipeline.predict_proba(X)
pred = pipeline.predict(X)confidence = []
for item in pred_proba:
confidence.append(item.max())df["predicted_automation"] = pred
df["confidence"] = confidence
df["applicable"] = df_test["confidence"].apply(lambda x: "OK" if x > 0.97 else "NOK")
希望这篇文章能帮助你理解如何使用机器学习来处理文本数据,以及我们如何找到机会并在日常工作中应用它来提高我们客户的满意度。
使用机器学习生成图像字幕
在本文中,我们将通过 Python 使用机器学习来为各种图像生成标题
图像字幕是给图像加上适当标题的过程。作为一个人,这似乎是一个简单的任务,甚至一个五岁的孩子都可以轻松完成,但我们如何编写一个计算机程序,将输入作为图像,并生成标题作为输出?

照片来自 Unsplash
在深度神经网络最近发展之前,这个问题对于业内最聪明的人来说是不可思议的,但在深度神经网络出现之后,如果我们有所需的数据集,这是完全可能的。
例如,网络可以生成与下面的图片 I 相关的以下任何标题,即“”草地上的白狗“ 带褐色斑点的白狗 ”或者甚至“ 草地上的狗和一些粉红色的花 ”。

来自开源 Flikr8k 数据集的图像
数据集
我们选择的数据集是' Flickr 8k '。我们选择了这个数据,因为它很容易访问,并且具有完美的大小,可以在普通的 PC 上训练,也足以公平地训练网络来生成适当的字幕。数据分为三组,主要是包含 6k 图像的训练组、包含 1k 图像的 dev 组和包含 1k 图像的测试组。每个图像包含 5 个标题。其中一个例子如下:

来自开源 Flikr8k 数据集的图像
- 一个穿着粉色连衣裙的孩子正在入口通道爬上一组楼梯。
- 一个女孩走进一栋木制建筑。
- 一个小女孩爬进木制玩具屋。
- 一个小女孩爬楼梯去她的玩具屋。
- 一个穿着粉色连衣裙的小女孩走进了一个小木屋。
数据清理:
任何机器学习程序的第一步也是最重要的一步是清理数据,去掉任何不需要的数据。当我们处理字幕中的文本数据时,我们将执行基本的清理步骤,如将所有字母转换为小写字母,因为对于计算机来说,‘嘿’和‘嘿’是两个完全不同的单词,删除特殊符号和标点符号,如、(、$、%,并删除任何包含数字的单词。*
我们首先为我们的数据集中的所有唯一图片创建一个词汇表,即 8000(图片数量) 5(每张图片的标题)= 40000 个标题。我们发现它等于 8763。但是这些单词中的大多数只出现一两次,我们不希望它们出现在我们的模型中,因为它不会使我们的模型对异常值具有鲁棒性。因此,我们设置了一个阈值,即一个单词在我们的词汇表中最少出现 10 次,这相当于 1652 个独特的单词。*
我们做的另一件事是给每个描述添加两个标记,以指示标题的开始和结束。这两个标记是‘start seq’和‘end seq’,分别代表字幕的开始和结束。
让我们从导入所有需要的库开始:
让我们定义一些助手函数:
让我们逐一解释:
load_doc:获取文件的路径并返回该文件中的内容load_descriptions:获取包含描述的文件内容,并生成一个字典,以图像 id 作为关键字,以描述作为值列表clean_descriptions:通过使所有字母小写,忽略数字和标点符号字符,以及只有一个字符的单词来清除描述save_descriptions:将描述词典作为文本文件保存到内存中loads_set:从文本文件中加载图像的所有唯一标识符load_clean_descriptions:使用上面提取的唯一标识符加载所有清理后的描述
数据预处理:
接下来,我们对图像和字幕进行一些数据预处理。图像基本上是我们的特征向量,即我们对网络的输入。这就是为什么我们需要在将它们传递到神经网络之前,将它们转换为固定大小的向量。为此,我们使用由 Google Research【3】创建的 Inception V3 模型(卷积神经网络)的迁移学习。该模型在' ImageNet' 数据集【4】上进行训练,以对 1000 幅图像执行图像分类,但我们的目标不是执行分类,因此我们移除了最后一个 softmax 层,并为每幅图像提取了 2048 固定矢量,如下图所示:

概念网的体系结构
字幕是我们模型的输出,也就是我们必须预测的东西。但是这种预测不会一下子发生,我们会一个字一个字地预测我们的字幕。为此,我们需要将每个单词编码成一个固定大小的向量(这将在下一节中完成)。为此,我们首先需要创建两个字典,即'单词到索引',它将每个单词映射到一个索引,在我们的例子中是从 1 到 1652,以及'单词索引',它将每个索引映射到它对应的单词。我们要做的最后一件事是计算数据集中具有最大长度的描述的长度,以便我们可以填充所有其他描述,从而保持固定的长度。在我们的例子中,这个长度等于 34。
单词嵌入:
如前所述,我们将把每个单词映射到一个固定大小的向量(即 200),我们将使用一个预先训练的手套模型。最后,我们为词汇表中的所有 1652 个单词创建一个嵌入矩阵,其中包含词汇表中每个单词的固定大小的向量。
让我们仔细分析这段代码:
- 第 1–5 行:将所有训练图像的所有描述提取到一个列表中
- 第 9-18 行:只选择那些在词汇表中出现超过 10 次的单词
- 第 21–30 行:创建一个单词索引和一个单词字典索引。
- 第 33–42 行:将手套嵌入加载到字典中,以单词作为关键字,嵌入向量作为值
- 第 44–52 行:使用上面加载的嵌入为我们的词汇表中的单词创建一个嵌入矩阵
数据准备:
这是这个项目最重要的方面之一。对于图像,我们需要使用前面描述的 Inception V3 模型将它们转换成一个固定大小的向量。
- 第 1–22 行:将训练和测试图像的路径加载到单独的列表中
- 第 25–53 行:遍历训练集和测试集中的每个图像,将它们加载到固定大小,对它们进行预处理,使用 InceptionV3 模型提取特征,最后对它们进行整形。
- 第 56–63 行:将提取的特征保存到磁盘
现在我们不会一下子预测我们的标题,也就是说,我们不会只是给计算机图像,然后让它为图像生成标题。我们要做的是给它图像的特征向量和标题的第一个单词,让它预测第二个单词。然后我们给它前两个字,让它预测第三个字。让我们考虑数据集部分中给出的图像和标题‘一个女孩走进一座木制建筑’*。在这种情况下,在添加标记' startseq' 和 'endseq '之后,下面将是我们在每种情况下的输入(Xi)和输出(Yi)。*

在这之后,我们将使用我们创建的字典' word to index '来改变输入和输出中的每个单词以映射索引。因为我们要进行批处理,所以我们希望所有的序列长度相等,这就是为什么我们要在每个序列后面加上 0,直到它们达到最大长度(如上计算的 34)。可以看到,这是一个巨大的数据量,一次性将其加载到内存中根本不可行,为此,我们将使用一个数据生成器,将其加载到小块中,即只加载需要的数据,而不消耗所有内存。
上面的代码遍历所有的图像和描述,并生成类似于表中的数据项。将让函数从同一行再次运行,因此,让我们批量加载数据
模型架构和培训:
如前所述,我们的模型在每个点都有两个输入,一个是特征图像向量,另一个是部分字幕。我们首先对图像向量应用 0.5 的下降,然后将其与 256 个神经元的层连接。对于部分字幕,我们首先用如上所述预训练的手套中的嵌入矩阵的权重将其连接到嵌入层。然后,我们应用 0.5 的辍学和 LSTM(长短期记忆)。最后,我们将这两者结合起来,并将它们连接到一个由 256 个神经元组成的层,最后连接到一个 softmax 层,该层预测我们词汇表中每个单词的概率。可以使用下图总结高级架构:

问题的定制架构
以下是在训练期间选择的超参数:损失被选择为'分类损失熵',优化器是' Adam '。模型总共训练了 30 个时期,但前 20 个时期的批量和学习率分别为 0.001 和 3,而后 10 个时期的批量和学习率分别为 0.0001 和 6。
让我们稍微解释一下代码:
- 第 1- 11 行:定义模型架构
- 第 13–14 行:将嵌入层的权重设置为上面创建的嵌入矩阵,并设置
trainable=False,这样,该层不再被进一步训练 - 第 16–33 行:用上面提到的超参数在两个不同的区间训练模型
推论:
前 20 个时期和接下来的 10 个时期的训练损失如下所示:

培训损失
为了进行推理,我们编写了一个函数,根据我们的模型预测下一个单词是具有最大概率的单词(即,greedy)

来自开源 Flikr8k 数据集的图像
你能做什么:
因此,总之,我们的模型在没有任何广泛的超参数调整的情况下,在为测试数据集中的图像生成标题方面表现得相当好。我们能想到的一些改进可以被使用
- 更大的数据集
- 进行更多的超参数调整
- 改变模型架构。
如果你尝试了这些方法并得到了更好的结果,请告诉我。该项目的代码可以在这里找到。
如果您觉得以上内容对您有用,请分享并随时支持我-->
使用机器学习来生成实际可行的食谱
用数据科学烹饪

当我尝试了一个星期的水果减肥法时,我吃的食物的照片。使用艺术家 Yury Malkov 的风格,通过神经风格转换生成。作者图片
近年来,生成模型领域在面部生成方面取得了巨大的成功,或者说从图画中创造出有趣的风景。
然而,为一道人类真正喜欢吃的菜制作食谱似乎超出了我们目前的能力。结果往往非常糟糕,实际尝试一下会很有意思,例如:
- https://www.youtube.com/watch?v=LXSjtiahNtY
- https://www . popular mechanics . com/technology/apps/a 19304/computer-generated-recipes-is-gut-bustly-bad/
我开始尝试一些不同的食谱制作方法,烹饪它们,找出它们的缺点,也许还能加以改进。
资料组
对于数据集,我将使用来自 https://www.koket.se/和 https://www.tasteline.com/的食谱。其原因是:
- 我已经使用它们很多年了,我知道这些食谱质量很高,通常能做出美味的食物。
- 食谱有点“国际化”。尽管这些网站是瑞典的,但大多数食谱反映了现代瑞典观众对其他文化食物的渴望。因此,尽管你会发现传统食物,如肉丸,你也会发现汤姆凯盖、肉馅卷饼、scaloppine al limone di vitello、和超过 100 种配方的鹰嘴豆沙。一些更“奇特”的食谱已经被篡改,例如,高良姜有时代替生姜,但并不总是如此。
TextGenRNN
TextGenRNN 是一个使用递归神经网络(RNN)的文本生成 python 库。RNN 是处理顺序数据问题的一种解决方案。由于文本数据是可变长度的(不同数量的单词或字母),我们不能使用普通类型的神经网络,例如,每个字母进入输入层中的一个感知器,因此,我们将一个接一个地输入字母作为序列。我们希望我们的神经网络能够记住输入的所有内容,而不仅仅是序列中最新的元素。为了实现这一点,我们要将每个字母的隐藏状态反馈到感知器中。隐藏状态是 RNN 的内部记忆。

来自维基百科
普通 RNNs 的一个问题是,对于每一步,更远的一步的影响变得越来越小,因为它已经通过神经元馈送了很多次。因此,一个 RNN 人可能在保持一个长句的上下文方面有问题,有时会忘记它是在谈论一个人、一个比萨饼还是天气。减轻这种情况的一种方法是使用一种特殊类型的感知机,称为 LSTM 或 GRU,结果会更好,但不是恒星。
无论如何,这是我得到的:
- 与橄榄油、sambal oelek 混合在一起,放在盘子上
- 黄瓜切丁,切成楔形
- 根据包装上的说明煮意大利面
- 加入洋葱、大蒜、油和熏制三文鱼片的两面,放入烤箱烤 10 分钟左右,或者烤至金黄色
- 将西红柿洗净并切成两半

作者图片
评价:这顿饭还可以。用 sambal oelek 和橄榄油制作简单的调味酱是个不错的主意。我不得不反复阅读许多食谱,然后才能找到我可以真正烹饪的东西。大多数食谱语无伦次,难以理解。
TableGAN
一些最著名的生成模型是生成对抗网络(GANs)。他们通过让两个神经网络竞争来工作。生成器试图生成类似于训练集的数据,鉴别器试图找出哪个是假的,哪个来自训练集。通过这次比赛,他们都变得更好。

作者图片
虽然大多数 GAN 专门用于图像生成, TableGAN 是一个 python 库,它使用 GAN 技术来生成表格数据。在这个例子中,输入数据是期望值为 200、标准差为 100 的随机数。TableGAN 生成了以下代码,我认为看起来相当不错。

作者图片
这似乎是一个很好的配料表。我使用 scikit-learn 的 CountVectorizer 将菜谱转换成向量。结果是:
['茄子','小菠菜','培根','叶菠菜',' bulgur ','蘑菇','辣椒片','辣椒酱','柠檬汁','科涅克','龙','固体土豆','鱼酱','液体人造黄油','新鲜百里香','新鲜香草','希腊酸奶','青椒','黄瓜','日本酱油','日本大米','刺山柑','椰奶','浓缩小牛肉原汤','肉末','酸橙','淡啤酒','牛排','玉米淀粉','油','奶酪','牡蛎片','胡椒(调味','松子','韭菜','迷迭香 '黄油(用来油炸) ','大豆','标准牛奶','黑胡椒','甜辣椒酱','红薯','百里香干','鳕鱼背','核桃','酸奶','西葫芦','鸡蛋','苹果醋']
定论:这款简直成分太多,我就不试了。
GPT-2
创成式预训练变形金刚 2 (GPT-2)就是所谓的变形金刚。他们从训练数据中学习一个单词出现的可能性,这取决于全文中的其他单词,但不同的单词被赋予不同的权重,这一过程被称为注意力。这样,它就可以无限期地保持理论上的语境。使用 GPT-2 的方法是写几个词作为开始,让转换器填写哪个词最有可能跟随,然后查看新字符串,并重复这个过程。GPT-2 在互联网的大部分区域接受训练,见过许多食谱。

作者图片
我用过 https://app.inferkit.com/demo 的,这是一个可以免费试用 GPT 2 的网站。
开头的句子是粗体的。
切洋葱。磨碎孜然和肉豆蔻。炒洋葱。从煎锅中取出洋葱、孜然芹、肉豆蔻、盐和胡椒,在另一个容器中磨碎洋葱。重新加热。加入鹰嘴豆、大蒜、生姜、香茅、鸡汤、酸橙汁、酱油和葡萄酒。把液体煨一下,然后关小火,盖上锅盖。煮大约 30 分钟,偶尔搅拌。一旦液体完全沸腾,加入浓奶油搅拌。再次降低温度,盖上盖子,然后
结论:这似乎是一道美味的炖鹰嘴豆。我最终没有做它。
在一个大碗中混合西红柿、芫荽叶、番茄酱、酸奶油、酸橙汁、盐、黑胡椒、孜然、辣椒粉和辣椒粉。腌制 30 分钟。用中火加热一个 12 英寸的不粘锅。加入熏肉、洋葱和胡椒片。烹饪并搅拌 3 分钟或直到培根变脆。搅拌酸奶油混合物。加入绞碎的牛肉,烹饪,用木勺打碎牛肉,大约 3 到 4 分钟,或者直到牛肉不再呈粉红色。将切碎的生菜、洋葱和西红柿混合;投掷

作者图片
结论:这味道真好。只是可能有点太普通了。
勒苟拉斯切碎洋葱,并把它们与面包屑、一些橄榄油和鸡汤混合在一起。当他们搅拌的时候,阿拉贡把千层面的面团擀开,切成八等份。“我不知道这是不是有史以来最好吃的千层面,”他说,“但至少比昨天好吃。”“你还没吃呢。”阿拉贡站起来,开始把所有东西放在一起。“在这一点上你必须相信我。”“很好。”“不如我的好。不过话说回来,我的就是我的专长。”他们做好了饭
结论:GPT 2 号的一个有趣之处在于,你可以给它喂任何你喜欢的东西,它会连贯地完成任务。我特别喜欢 GPT 知道阿拉贡在品尝食物之前就评论它有多好吃,并且写了勒苟拉斯是如何召唤他的。
我没有做这种难吃的饭。
分析数据集
上面的食谱已经足够好了,但是我不得不重复了好几次才得到它们。不过,我觉得最大的问题是他们的缘分很低。 Serendipity 是推荐系统中常用的一个指标,高分意味着用户感到惊喜。所以,例如,如果我推荐食物,我可以通过推荐牛奶或香蕉来获得高准确率,因为这是人们经常购买的东西。但是这样的推荐并不是很好,因为顾客无论如何都会买的。相反,我们会向客户推荐他们会喜欢的东西,但他们自己可能不会想到。这是很高的意外收获。它可以被视为人工智能生成的食谱的最重要的衡量标准。我们已经有人类在制作食谱,我们希望从人工智能那里得到的是一个令人震惊的不同但仍然美味的食谱,摆脱人类先入为主的什么一起吃味道好的观念。
需要注意的一点是,当一个人制作一份食谱时,他会从不同的配料的味道、气味和外观中汲取经验。所有这些信息对算法来说都是不可用的,算法只能看到不同的成分是如何一起使用的。这是机器学习中的一个常见问题,顺便说一下,模型继承了训练数据的偏差。这排除了一些由于地理意外而不常见的可行成分组合。例如,生姜和奶油放在一起是不常见的,因为生姜生长在世界上有很大一部分人对乳糖不耐受的地方。然而,我看不出他们为什么不能一起工作,因为生姜和椰子汁是常见的。
我决定在配料中添加一些特性并进行调查。我使用的功能有:
- 淀粉 _1
- 淀粉 _2
- 蛋白质 _1
- 蛋白质 _2
- Fat_1
- 脂肪 _2
- 甜甜 _1
- 甜甜 _2
- 酸味 _1
- 酸味 _2
- 鲜味 _1
- 鲜味 _2
- 苦的
- 柔软的
- 中等
- 易碎的
- 红色
- 黄色
- 格林(姓氏);绿色的
- 褐色的
- 怀特(姓氏)
- 香料
1 表示“一些”,2 表示“大部分”。例如,牛奶有 sweet_1,而蜂蜜有 sweet_2。柔软、适中、松脆是指质地。
现在,这有点主观,也容易出错,因为某些成分有不同的特点,取决于它们是如何准备的。这可以通过例如将“土豆”和“土豆泥”作为不同的配料来稍微缓解。
计算频率揭示了一些有趣的事情。

作者图片
计数是这样完成的:每当一个食谱至少有 1 个特征,我们就增加那个特征。我们对所有的食谱都这样做,然后除以食谱的总量。每当一个食谱包含一种我在我的配料清单中没有的配料时,“未命名”标记。这意味着如果我有时间和精力记录更多的成分,这个数字可能会更高。
这似乎表明:
- 除了苦味,大多数菜肴至少有一些所有的味道。
- 许多食谱由许多不同的质地和颜色组成。
- 几乎所有的菜都有某种香料。
我还是不确定关于苦能得出什么结论。它在那里是因为我们喜欢它,还是仅仅因为一种苦味的成分有一些其他令人满意的品质,例如,罗马沙拉,它是苦的,但也非常脆。我最大的猜测是,有时它是偶然出现的,而其他时候它是有意识地添加进来,让菜肴变得更有趣,就像使用香料一样。它在功能上似乎不同于其他四种味道,甜、盐、鲜味和酸味。
我发现将这些与我得到的甜点进行对比很有趣:

作者图片
多点甜少点鲜味,果然不出所料。我怀疑鲜味 1 的流行主要是由于各种各样的鸡蛋和坚果。在本文中,我将重点介绍非甜点食谱。
看看非甜点食谱中最常见的成分:
- 洋葱(黄色、红色、葱等):0.90
- 奶酪:0.55
- 番茄:0.45
- 大蒜:0.31
- 柠檬:0.30
不知道为什么会这样。可能很早就在许多地方种植了洋葱(5500 年前的埃及、5000 年前的印度和中国、4500 年前的苏美尔)。此外,洋葱富含硫,油炸时产生的一些化学化合物有点像肉。奶酪的流行很可能是因为食谱来自西方烹饪网站。
根据特征生成配方
既然我们已经有了根据它们的特点而不是成分来描述的食谱,也许我们可以用它作为训练数据?但是,我们希望生成一个由配料而不是功能描述的食谱。为实现这一目标,我们将:
- 训练一个一级 SVM 根据其特征识别什么是有效的食谱。
- 生成成分的随机组合,计算特征,并让 SVM 确定它是否是一个有效的食谱,如果不是重复的。这意味着在我们找到可以使用的东西之前,我们将不得不经历几百种随机组合。尽管这种生成东西的方法有点间接,但已经足够快了。
no_of_tries = 1000
generated_recipe = ''for x in range(no_of_tries):
sample = df_ingredients.sample(12)
sample_sum = sample.sum()[feature_names]
pred = clf.predict([sample_sum.values])
if pred[0] == 1:
print(sample_sum[feature_names].values)
generated_recipe = sample.sort_values(['Starch_2', 'Protein_2'], ascending=False)
break
generated_recipe['Nudles', 'Chickpeas', 'Shrimps', 'Melon', 'Asparagus',
'Thai Basil', 'Cabbage', 'Sesame seeds', 'Orange','Tamarind',
'Butter', 'Cinnamon']
另一种方法可能是查看上述特性的频率,并得出结论,我们希望每种特性都有一个。因此,代替我们的 SVM,我们硬编码这个规则,像这样:
no_of_tries = 10000
generated_recipe = ''for x in range(no_of_tries):
sample = df_ingredients.sample(16)
sample_sum = sample.sum()[feature_names]
if sample_sum['Starch_2'] == 1 and sample_sum['Protein_2'] == 1 and all([x > 0 for x in sample_sum]):
generated_recipe = sample.sort_values(['Starch_2', 'Protein_2'], ascending=False)
break['Polenta', 'Chicken', 'Pear', 'Star Anise', 'Lime juice', 'Radish', 'Mayonnaise', 'Tomato', 'Mushrooms', 'Artichoke', 'Vanilla', 'Eggs', 'Orange Zest', 'Oyster sauce', 'Sugar', 'Fennel']
由于我们只有配料,我不得不自己决定如何处理它们。这是我想到的:
- 盐水鸡。
- 用茴香和蚝油做玉米粥。
- 烤鸡和玉米粥。
- 煎烤蘑菇和西红柿。
- 用梨、萝卜、朝鲜蓟和酸橙汁做沙拉。
- 用蚝油、糖、酸橙汁、水、橙皮、香草、八角、茴香和蛋黄酱做调味汁。

作者图片
评价:味道不错,更重要的是,有点不寻常(很高的意外收获)。糖、香草、八角和茴香使调味汁尝起来有点像甘草糖,但蚝油使它美味可口,从而避免了它尝起来像甜点。
填充配料
我突然想到,如果一开始就给它一些成分,这个发生器将有助于填补缺失的成分。由于鹰嘴豆泥有数百种配方,我决定看看该算法是否能产生一种新的配方。所以我从面包和鹰嘴豆开始,食谱变成了:
['Bread', 'Chickpeas', 'Stock', 'Dried tomatoes', 'Sunflower oil', 'Coca cola', 'Almonds', 'Dill', 'Pickles', 'Smoked paprika', 'Lemon juice', 'Beer']
我决定用切碎的杏仁作为配菜,泡菜作为配菜。然后我必须决定可口可乐和啤酒中的哪一种应该加入鹰嘴豆泥中,哪一种我应该喝。我决定往鹰嘴豆泥里倒几汤匙可口可乐。

作者图片
结论:味道不错,和通常的鹰嘴豆泥食谱有点不同。
熟读烹饪知识
此时,我认为自己没有足够的背景知识来继续学习,所以我订购了几本书:

作者图片
我还在 YouTube 上找到了一个不错的系列讲座:
读完和听完之后,我准备对这一切意味着什么做出一些有根据的猜测。
洞察力
以下是我自己的想法。我已经尝试过了,但样本量太小,无法得出任何一般性的结论。尽管如此,我还是会把它们呈现在这里,但要有所保留。
美食的要素
- 平衡。只有盐味的东西不好吃。对甜味或其他任何味道也是一样。做一顿好饭需要的是甜、盐、鲜味和酸味之间的平衡。
- 变异。一顿饭需要变化多样,刺激许多不同的感官,因此最好是包含不同的颜色、质地、香味和温度。
- 新奇。反复吃同样的东西会令人厌烦。我们喜欢体验新的感觉组合。我认为这是许多现代餐厅的主要特征之一,这些餐厅提供味道、质地、颜色等不同寻常的食物组合。
- 熟悉度。作为孩子,我们从我们的文化中学习什么是可食用的,什么是不可食用的。这对于我们的生存至关重要,这样每一代人就不必通过尝试来找出哪些浆果是有毒的。这也导致在烹饪时,我们不能偏离前人设定的界限太远。至少不是一蹴而就的,通过循序渐进,有可能学会欣赏与你习惯的食物非常不同的新型食物。
我觉得有趣的是,人类的饮食习惯似乎有一个特定的人类元素。做一顿好饭的主要挑战之一当然是做出一些大脑认为有营养的食物,而不是像草或泥土这样的随机植物。但是,这还不够。它也需要刺激和惊喜我们。它的工作方式似乎与许多其他人类休闲活动的工作方式惊人地相似。例如,有人可能会说上面的列表也可以作为如何制作音乐的指南。
为什么我们喜欢自己喜欢的东西?
纹理:说到人类,我有一个猜测,为什么我们会如此痴迷于纹理。在许多烹饪书中,有整整一章,有时几章,致力于获得正确的肉的质地(我更喜欢用 sous vide 或 reverse sear 处理嫩切肉,用高压锅处理切肉,将胶原蛋白转化为明胶。提前几个小时在盐或盐水中摩擦)。同时,大多数动物乐意吃所有的肉,不管有多硬。我的猜测是,这与我们有一个非常高的喉头(因为讲话)和小下巴肌肉(为我们的大大脑腾出空间)有关。这使得人类特别容易因窒息而死,所以鲜肉不仅仅是味道的问题,也是生存的问题。较软的肉也可能是肉已经被煮熟的标志,因为这使我们更容易吸收营养,所以我们更喜欢这样的肉是有道理的。
味道:我们喜欢甜食,这一点应该不会让任何人感到惊讶,因为糖是高能量的,因此我们的祖先会给它定价。鲜味有点复杂,因为它不是一般的蛋白质,而是一种特殊的氨基酸。我猜谷氨酸盐通常与其他蛋白质一起被发现,我们用它来代表高蛋白食物。酸味更加棘手。这可能表明维生素 C 的含量,也可能是它让我们的祖先更喜欢未成熟的水果,因此让他们在比赛前吃它。盐对于正常的生理机能是必不可少的。
辣椒:现在,这有点神秘,因为吃辣椒会触发痛觉感受器。关于这一点似乎没有共识,但一个很好的理论是,它模拟了一种我们可以控制的危险情况,并且知道它是不危险的,类似于过山车。
香料的作用
在我看来,香料主要是为了让食物变得更有趣,没有哪种香料能和某种配料搭配得更好。配对似乎主要是文化上的。
你应该从建立你的食谱开始,让它包含适量的甜、酸、盐和鲜味。这样你的大脑会把它识别为食物。之后,你可以添加任何你觉得愉快的气味。
如果你喜欢香料的味道,你可以用它搭配任何东西。
- 喜欢肉桂的味道?它不仅仅是甜点,还可以用在炖鸡里,这是印度人的做法。
- 比如松木的味道?在你的冰淇淋里放一些磨碎的杜松子。
- 你觉得川菜的刺痒感有趣吗?在你炖的奶油蘑菇里放一些四川胡椒。
此外,香料大多与气味有关(除了少数像辣椒、黑胡椒、花椒),大脑处理气味的部分和处理记忆的假设之间可能有密切的联系。这意味着香料可以用来唤起某些记忆。普鲁斯特在《寻找逝去的时光》中的一个著名场景中对此进行了精彩的描述,主角吃了一个玛德琳蛋糕,喝了一些莱姆花茶,香味引发了生动的不由自主的记忆。
音响工程师类比
将食物的所有复杂性简化为几个特征,如甜、酸或脆,这似乎很荒谬。某些成分搭配在一起比其他成分更好,以及某些食物如何相互补充和增强,这种想法怎么了?
我认为用声音工程来类比是恰当的。因此,在声音工程的早期,捕捉正确的声音似乎是一项不可完成的任务。声学是一个非常复杂的领域,移动一根柱子或缩小一堵墙,或者改变材料,声波传播和相互干扰的方式就会完全改变。
然而,人们很快认识到,这个问题可以通过理解这样一个事实来简化,即在如此复杂的情况下,人类仍然只有两只耳朵。
同理,人类舌头上只有 5 种味蕾,大约 400 个嗅觉感受器。我们并不像人们常说的那样,在调味汁中加入柠檬来“提亮它”。我们这样做是因为大多数人喜欢特定的 pH 值以及柠檬醛和柠檬烯的气味。也增加了几分甜味。我们可以自由地用任何满足酸、甜和气味标准的东西来代替它。考虑到你喜欢酒石酸、琥珀酸、苹果酸和葡萄酯的味道,香醋可能是一个不错的选择。葡萄酒也是很好的替代品。
说到葡萄,它们和蓝纹奶酪很配,因为它们含有甜味和酸味,但含盐量低,蓝纹奶酪中富含鲜味。牛肉干也含有这些部分,不出所料,和葡萄一起吃味道很好,前提是你喜欢烟熏味。
音乐世界的另一个有趣的类比是对声音进行分类。在过去,人们通常会根据乐器的演奏方式、外观或使用的材料来分类。随着合成器的出现,我们现在可以制造任何声音,而无需参考现实世界中的材料,如木材或黄铜。这不得不导致许多虚拟仪器技术转而根据其功能对仪器进行分类。因此,我们有垫创造背景,领导的旋律,和低音的,嗯,低音。同样的,我倾向于考虑成分,而不是它们是叶、根还是果实,而是它们是松脆的、咸的、红色的,还是含有芳樟醇。
老方法也用于将酱料分类为法国母酱料:贝沙梅尔酱、西班牙酱、番茄酱、维露特酱和荷兰酱。它们是根据烹饪时使用的配料和技术来分类的,而不是根据它们的味道和它们在食物中的作用。
了解富含鲜味成分的作用
我曾经对某些配料的味道感到惊讶,如鱼露、伍斯特郡酱、蚝油等。当我用勺子品尝它们时,我不喜欢它们,那么我为什么要在食物中使用它们呢?甚至像帕尔玛意大利干酪这样的东西也有点神秘,它放在三明治上味道很好,但伯爵酒味道更好,所以为什么不用它来代替呢?
现在我明白了,很多这些味道更“古怪”的配料实际上是古代寻找鲜味的一部分。古罗马人已经有了他们的 garum,据说味道很差,塞内卡写道:
你难道没有意识到,腐败的鱼,那昂贵的血淋淋的块,用它的咸腐来消耗胃吗?
但是,鲜味在自然界中供应不足,我们渴望它,并准备在任何可能的地方获得它。现在,每当我做饭时,我通常会在旁边放一瓶鱼露,鱼腥味在完成的菜肴中并不明显,许多食物用一些鱼露会变得更好。不仅仅是亚洲食物,注意,在肉酱面里放一些会更好吃。
此外,显然有两种类型的化学物质触发鲜味受体,谷氨酸盐和肌苷酸二钠并且两者一起似乎产生强烈的反应,因此串联使用不同类型的鲜味 _2 型成分可能是有益的。
品味与特色
当我开始做这类实验时,有一件事让我感到惊讶,那就是大脑并没有真正把不同的特征看成是独立的,而是根据一些不同的感觉拼凑成一幅统一的画面。因此,举例来说,对一些人来说,在美味的菜肴中加入甜味,就像在棕色酱汁中加入糖一样,听起来可能令人反感。是的,如果你加的太多,你会尝到你不想要的甜味。但是,如果酱汁完全没有甜味,尝起来就好像缺少了什么。向菜肴中添加一些糖不会使它变甜,而是大脑会将其记录为“味道不错”(如果你不想直接添加糖,可以在制作调味汁时使用胡萝卜甚至洋葱)。质地也是如此,有时我根本没有注意到它的质地,只是觉得它很好吃。
比例和酸味
算法并没有给出确切的比例,所以我通常用我内置的化学分析仪来做。也就是说,我品尝它,并根据需要添加配料,通常是一些缺少的特征高度集中的东西。所以如果没有酸味,我会拿一个酸橙,如果没有鲜味,我会用鱼露等。
我想有可能采取更科学的方法,我通过测量我做的酱的 pH 值来尝试酸味。

作者图片
酱汁似乎是 6,相比之下,另一端与水是 7。
将菜肴分类
虽然有许多许多不同类型的菜肴和烹饪方法,但在我看来,其中一些在世界各地都极为常见。
- 汤:将所有材料放入水中,加热至沸腾。让它煨到合适的质地和味道。
Ex:土豆韭菜汤,Tom Kha Gai,Sopa Azteca,Bouillabaisse,蛤蜊杂烩,拉面汤,Pho,酸辣汤,Mulligatawny 汤。 - 炖:作为汤,但去除淀粉成分,单独烹制。
例如:Chili con carne、Dal、Massaman curry、Gumbo、Sega wat、Domoda、麻婆豆腐。 - 自行:自行准备淀粉、蛋白质、蔬菜,配以酱汁食用。肉和土豆,香肠和土豆泥,西班牙小吃,北京烤鸭,黛安牛排。
- 自行混合:自行准备淀粉、蛋白质、蔬菜和酱料,并在上菜前混合。
例如:肉酱面、泰伯香浓、意大利面沙拉、泰伯勒、普克碗。 - 三明治:准备好蛋白质和蔬菜,放在面包上食用。
例如:汉堡、热狗、朗格士、法式长棍面包、烤肉串、玉米煎饼、牛排三明治、Skagen 吐司、smrrebrd。 - 馅饼:在面团上或里面烘焙蛋白质和蔬菜。
例如:肉饼、Pierogi、披萨、饺子、Bö rek、Kiymali pide、Empanadas、Bobotie。
化学反应
烹饪不仅仅是找到合适的原料并把它们组合在一起,它也是一种化学反应。例如:
- 美拉德反应。从 19 世纪开始,就有一个有弹性的神话,说我们把肉烤焦来密封肉汁。这已被证明是错误的,但仍有理由烤焦。原因是美拉德反应,这是氨基酸和还原糖之间的化学反应,产生褐色,酥脆的质地和美好的香味。
- 烫漂。当我们切蔬菜时,我们切断了细胞壁,使不同的化学物质混合在一起。这有时会导致不希望的化学反应。例如,当切罗勒做香蒜酱时,一种化学反应开始了,使鲜绿色变成褐色。阻止这种反应发生的一种方法是通过加热来破坏引起这种反应的化学物质。这可以通过预先热烫罗勒来实现。另一个例子是洋葱,洋葱在切开之前没有太多气味,随后化学物质的混合产生了一些非常强烈的富含硫的化学物质,当它们与水接触时会变成硫酸,使我们的眼睛灼伤。这些反应可以通过在煎锅里加热洋葱来停止。
- 炭化。谁会想到部分燃烧一些植物会让它们味道更好?但这是真的,尤其是对非常有用的甘蓝类蔬菜来说。它是那些被培育了很长时间的物种之一,并被选择采取许多不同的有用的形式。例如:
顶芽选择:甘蓝
侧芽选择:抱子甘蓝
茎选择:大头菜
叶选择:羽衣甘蓝
茎花选择:西兰花
花束选择:花椰菜
Btw,它所属的属还包含芥菜和油菜。
越南牛肉沙拉
我喜欢这道菜,因为很明显所有的配料是如何搭配在一起的。调料是:
- 酸橙
- 棕榈糖(甜)
- 鱼露(鲜味,盐)
- 大蒜、辣椒、生姜(香料)
- 芝麻油(脂肪、香料)
现在所有的口味都考虑到了,真正的沙拉主要是增加质地、颜色和营养。
- 米粉(淀粉,白色,柔软)
- 胡萝卜(淀粉、橙、脆)
- 黄瓜(淀粉,绿色,酥脆)
- 莴苣(苦的、绿色的、脆的)
- 牛肉(蛋白质,中等质地)
当然,其中一些成分还有一些其他的特点,例如,胡萝卜是甜的,有点像香料(事实上,这是波斯人驯化它的原因,用种子和叶子作为香料),肉有鲜味。但是调料里有更多。
为了让这道菜更加有趣,可以加入新鲜的香草:
- 铸造
- 芫荽叶
- 泰国罗勒
意大利面条
这是我做了一辈子的菜,从来没想过改进,直到现在。用我的新知识武装起来,我注意到酸和甜不见了,所以有一天我加了少量的香醋,它确实改善了这道菜。现在,我确信传统上人们会吃酸的配菜来平衡它,但我仍然认为我的加入是一种改进。
集束香料和香草
有许多香料,很难知道用哪一种。为了获得一个概观,我决定将他们分组。我在谷歌上搜索了常见的替代品,并通过绘制 spice 与其替代品之间的关系图对其进行了建模。这是在图形工具 Gephi 中完成的:

作者图片
似乎有几个集群:
- 圣诞串(丁香、肉豆蔻、多香果、肉桂、小豆蔻、姜粉、姜黄)。对我来说,这是姜饼和圣诞节的味道。我猜美国人会把它与感恩节和南瓜派联系在一起。此外,这种味道在印度和印度尼西亚菜肴以及牙买加小吃中也很常见。这个簇的一个特征化学物质是丁香酚。
- 甘草串(八角、八角、茴香、龙蒿)。对我来说,这是甘草、面包、盐和地中海的味道。这一串的特征味道是由于茴香脑或其异构体蒿脑的缘故。
- 麻辣串(辣椒、黑胡椒、花椒、辣椒粉)。这些香料不同于其他香料,因为它们主要是关于口腔中的感觉(辛辣、火辣、刺痒),而不是香味。
- 鲜串(香茅、柠檬皮、漆树)。这些气味如柠檬醛、芳樟醇和柠檬烯,通常与清新联系在一起,这也是它们常见于肥皂和洗涤剂中的原因。
- 果味集群(罗望子、酸橙)。这些是各种醛类的花香和水果味。
- 踢中窦丛(辣根、芥菜、胡芦巴)。这些和辣串差不多,只是痛在鼻子而不是嘴。葫芦巴没有这种品质。
- 地中海草本植物群(牛至、罗勒、月桂、百里香、鼠尾草):闻起来像披萨和地中海草甸。
- 麝香串(孜然、芫荽、香菜、莳萝):闻起来像中东美食。
- 松树丛(刺柏浆果、迷迭香):这是针叶林的气味,多半源于α-蒎烯。
- 牙膏簇(薄荷、欧芹、香菜)。对我来说,这是牙膏和漱口水的清新味道。
- (烟熏味、香草味、巧克力味、藏红花味):这些香料实际上不属于任何其他类别。
顺便说一句,在许多情况下,让我们如此渴望香草和香料的好气味是化学战。看,草药无法逃离它们的敌人(如真菌和昆虫),所以相反,它们产生有毒的化学物质杀死任何入侵者。这些化学物质使得香料闻起来有味道。显然,一生中被害虫侵袭过的草药比那些喷洒过杀虫剂的草药味道更好。至于人类为什么喜欢这些化学物质,有几种理论。一是有毒化学物质有助于我们的免疫系统。
然而,这并不是故事的全部,罗望子和其他水果闻起来很香,因为它们想被吃掉,这样它们里面的种子就会被传播,辣椒是辣的,以阻止哺乳动物吃它们,这样它们的种子反而被尝不到热量的鸟类传播。
关于香料,另一件要注意的事情是,在使用前将它们整体储存并磨碎是一个好主意。这是因为平方立方定律。通过减小香料颗粒的尺寸,体积随着尺寸减小,而面积仅仅随着尺寸减小,因此小颗粒具有大得多的面积-体积比,气味分子将消散得更快。同理,小动物通常很难保暖,而大动物则很难不过热。
利用机器学习识别高价值足球转会目标
xgboost 和 FIFA 20 数据集的冒险

斯文·库契尼奇在 Unsplash 上的照片
如果你想复制本文中描述的分析,你可以在 GitHub 的 这里 找到所有的代码
马后炮是一个善变的东西。
回顾过去,我们很容易想到我们应该预见到它们的到来。当然,我们认为,我们应该知道比特币会成为一个东西,特朗普会在 2016 年获胜,iPod touch 意味着带按钮的手机的终结。
足球转会也是如此。很难不回顾像安迪卡罗尔到利物浦,费尔南多托雷斯到切尔西,阿莱克西斯·桑切斯到曼联,安德烈舍甫琴科到切尔西这样的转会,并认为他们从一开始就注定要失败。
但现实很少如此简单。
我们举个例子。
在 2016 年和 2017 年的夏天,两名 25 岁的边锋以俱乐部创纪录的费用转会到了冠军联赛的顶级球队。两人在他们的老东家都表现出色;两者的估价都在 2000 万到 3000 万英镑之间;两人在国际足联的总体评分都在 80%左右。在前一个赛季,一名球员每两场比赛进一球,另一名每三场进一球。他们甚至都曾在英超前四名的俱乐部度过了一段糟糕的时光,然后去了国外以获得更多的比赛时间。
你觉得怎么样?这些玩家各自的命运应该是显而易见的吗?如果你知道他们的名字,你认为你的答案会不同吗?
其中一个是德国国脚安德烈·舒尔勒。他在 2016 年转会到多特蒙德,转会费约为 3000 万€。对他来说不幸的是,事情并没有在威斯特法伦斯坦迪翁解决。舒尔勒在托马斯·图切尔的第一个赛季打了 15 场比赛,只进了两次球。在接下来的 12 个月里,他的市场价值直线下降,在他大笔转会三年后,他被租借到了莫斯科斯巴达。他在那个赛季结束后退役了。
另一个呢?嗯,另一个是穆罕默德·萨拉赫。
机器学习和偏见问题
虽然这些可能看起来像无关紧要的轶事,但后见之明偏见的影响可能比你想象的更大。如果我们总是回过头来想,在那个时候区分一个犹太人和一个萨拉赫人应该很容易,我们就会低估在未来做同样的事情有多难。下一次我们看到一个 25 岁的边锋在欧洲踢球得分,他看起来是下一个大人物,我们会认为很明显他们是一个伟大的转会目标。我们可能会在不经意间让 Schürrles 比 Salahs 多。
那么,我们能做些什么呢?
我们可以做的一件事是将我们的评估建立在更加客观的数据基础上。如果我们可以设计和验证系统来学习与随后的高绩效可靠相关的模式,并证明它们在现实世界中有效,我们就可以开始做出更好的决策,并消除人类偏见。
当然,机器也不会对偏见免疫——远非如此。过去几年中有太多有偏见的算法的例子,这几乎已经成为数据科学领域的陈词滥调。特别是,机器学习算法经常会无意中放大人类的偏见,尤其是在根据人类判断生成的数据进行训练的情况下。不过这通常是可以避免的:如果你知道偏见来自哪里,你就可以想出如何从你的训练过程中消除它们,从而获得更好的结果。
但是听着,我知道你不需要我告诉你这些。你已经看过了钱球。你在这里是为了了解我们实际上是如何做到的。
所以让我们继续吧。
接下来
在这篇文章的剩余部分,我们将使用一个玩具示例来演示如何使用 R、xgboost和 FIFA 20 数据集来构建一个足球转会推荐系统。我们的目标是训练一种算法来识别在即将到来的赛季中表现出色的球员。此外,我们真正想知道的是:如果我们签下他们,哪些球员可能会在我们支持的俱乐部表现出色。
我们将把这个问题构建成一个 监督机器学习问题 ,其中有一个与未来玩家表现相关的连续目标变量,我们的目标是提前预测。
我们将采取的步骤是:
- 获取数据,设计特征并定义目标变量
- 探索性可视化
- 训练和验证一个简单的 xgboost 模型
- 根据最新数据创建预测并探索结果
(如果您对代码不感兴趣,只想听一些有趣的传输八卦,您可以直接跳到第 4 部分)
步骤 1:加载数据和工程特征
首先,我们将加载所有的数据文件并做一些基本的清理。我们还将从国际足联的数据中提取非常详细的“位置”特征,并将其转化为稍微更广泛的类别(守门员、中后卫、边后卫、中场、攻击型中场、边锋、前锋)。
我们还在这里定义了我们的目标变量。在每个赛季开始时,我们希望预测每个球员在该赛季的表现如何。总的来说,在下一个版本的游戏中,拥有一个好赛季的玩家将会被开发者提高他们的总评分。那些赛季表现不佳的人将会看到他们的总体评分下降。因此,我们的目标变量将是改进栏,代表从一个赛季到下一个赛季总体评分的变化。
特征工程
接下来,我们将设计一些我们认为我们的机器学习算法将从中受益的功能。当然,并不是所有的算法都需要这一步。如果我们走深度学习的路线,那么我们可以在理论上指定一个足够复杂的模型来设计它自己的底层功能,并从中学习。问题是,那种方法需要大量数据——可能比我们这里得到的还要多。记住:越复杂并不总是越好。编码重要领域专业知识的高质量工程特征可以极大地改善大多数机器学习模型,作为足球迷,我们可能有惊人数量的有用知识可以编码到我们的模型中。
比如我们知道足球是团队游戏。这意味着一个球员进步(或失败)的程度不仅取决于他们自己的技能和特点,也取决于他们队友的技能和特点。
这些关系远非简单。一方面,你可能听过专家们谈论一个球员如何从身边有一个更好的球队中受益。如果没有维吉尔·范·迪克和阿利松·贝克尔在他身边,乔·戈麦斯会打得像 19/20 那样好吗?
但另一方面,团队中有太多其他优秀球员——特别是如果他们打同一个位置——从发展的角度来看可能不是那么好,因为这意味着你不太可能获得比赛时间。举例来说,如果加布里埃尔·赫苏斯不是一直充当塞尔吉奥·阿奎罗的副手,他在曼城会有更大的进步。
使用像 xgboost 这样的基于树的模型可以帮助我们捕捉其中的一些非线性。为了让它有所作为,我们将为每个玩家创建一些列来表示:
- 球员在俱乐部的平均综合评分
- 球员在俱乐部的平均潜在得分
- 首发/首选选手的平均综合评分
- 首发进攻者、中场、防守者和守门员的平均综合评分(作为单独的特征)
- 俱乐部中与目标球员踢相同位置的其他球员的平均综合评分
- 同上,但使用潜力而不是总体评分
- 如上,但是是最大等级而不是平均等级
- 玩家在各自职位中的“排位”(按总体评分排序)
注意,我们还对我们的分类变量进行了一次性编码。
第二步。探索性可视化
接下来,我们可以检查数据集的一些有趣的特性。我们发现我们的目标变量有一个稍微偏斜的正态分布,平均值大约为+1:

作者图片
我们可以画出一个赛季中最大的 5 个赢家和输家:

作者图片
该数据集中最大的进步是由当时 16 岁的波兰门将 Drągowski 实现的,他的排名在 2015 年和 2016 年之间惊人地上升了 21 分,从 50 分上升到 71 分。另一方面,最大的输家是可怜的老约娜·托伊维奥。芬兰中后卫的整体评分在 2018/19 赛季期间下降了 8 分,从 71 分降至 63 分。我不知道他做错了什么,但这不可能是好事。
凭直觉,我们可能会认为年轻球员更有可能从一个赛季提高到下一个赛季,而老球员则下降。也可能是这样的情况,在某些职位上的改进往往比其他职位快,甚至两者之间可能存在相互作用。让我们快速浏览一下年龄和职位与进步的关系图,看看是否有什么特别之处。(注意,我们在每个位置随机抽取 500 名球员,以便于绘图)。

作者图片
不出所料,年龄和进步之间存在关联——但平均水平也有很大差异。有很多年轻球员一年比一年差,也有很多稍老的球员变得更好。也许有更多的年轻攻击中场球员在这个数据中有所提高,守门员的关系可能比其他位置稍浅,但除此之外,这种模式在所有组中都相当稳定。
最后,让我们生成一些经典的蜘蛛图来看看那些工程团队级的特性。

作者图片
创建这些图的完整代码如下。
步骤 3:建模和评估
既然我们已经获得了数据并设计了我们的功能,预测建模的实际代码就相当简单了。当然,调整、调整和改进模型需要时间,但是为了这个实验的目的,一些简单的默认参数就可以了。我们所要做的就是将我们的数据分成训练集和测试集,将它们转换成 xgb 数据矩阵,并通过xgb.cv函数运行它们。这将在我们的训练集中进行 k 倍交叉验证,这将为我们提供估计,我们可能期望该模型在样本外的表现如何。顺便说一下,本节假设您对 xgboost 非常熟悉。如果你不是,这里有一篇优美温和的介绍你可以看看。
这里需要注意的一件重要事情是,我们是而不是随机划分我们的训练集和测试集。如果我们这样做了,那么我们将在整个时间段的训练集中包括案例。这意味着我们的一些训练数据会比测试数据来得晚。但是测试集应该代表“看不见的”数据,在你训练完你的模型之前,你在现实世界中是无法访问这些数据的。
因此,我们复制更真实的条件,并按时间分割我们的数据。这意味着我们将总是使用过去的数据来预测未来,不需要时间旅行。
我们还在最后添加了一些代码来计算最佳迭代的一些不同的性能指标,包括 RMSE 和 R 平方。我们可能对它作为基本的改进/未改进分类器的表现感兴趣,因此我们添加了一些分类指标(灵敏度、特异性、阳性预测率和阴性预测率)作为良好的度量。
# A tibble: 1 x 8
model rmse r_square sens spec pos_pred_rate neg_pred_rate
xgboost 2.15 0.339 0.332 0.915 0.651 0.741
我们得到的结果看起来相当合理,因为这只是开箱即用的 xgboost。(注意,在 GitHub repo 中,我们还运行了一个简单的线性模型作为基线,这总是一个好主意,但为了节省空间,这里省略了它)。RMSE 分数或多或少表明,该模型的预测正确率平均在+/- 2 点以内,R 平方表明我们解释了目标变量中约 34%的方差。
对于分类指标,该模型正确地识别了大约三分之一将要改进的玩家,并正确地排除了大约 90%没有改进的玩家。总的来说,如果你用它来选择一组预测在即将到来的赛季会变得更好的球员,大约 65%的人会这样做。
另一个重要的意义检查是查看模型使用哪些变量来进行这些预测。如果它过于依赖一个特性,或者一组我们不认为特别重要的变量,那么这可能是一个奇怪的迹象。我们可以使用下面的变量重要性图来检查这一点(只画出了 20 个最重要的特征)。

作者图片
在预测一个球员未来的进步时,看起来最重要的特征是他们当前的总体评分。这从直觉上讲是有道理的:已经非常优秀的玩家更有可能接近他们游戏的巅峰,并且没有更小的“空间”来提高。
同样有希望的是,该模型正在对年龄以及与球员俱乐部相关的各种特征(我们之前添加的那些额外列)产生影响,包括平均总体评级、平均首发评级、位置排名(“啄食顺序”)等等。
最后,我们来看几个玩家级预测的例子。这是从交叉验证的训练数据中随机选择的预测。
请注意,我们的男孩 Mo Salah 和 André Schürrle 都在那里,该模型正确地将 Mo 标记为良好前景(预计会改善),而 Schürrle 标记为不佳前景(预计会变得更糟)。
因为这个模型看起来很合理(并且因为这只是一个玩具例子),我们将把我们的评估留在那里,并接受它作为我们的最终模型。然后,我们在保留数据上测试它的性能,看看它在样本外的表现如何。
# Predict on test set
preds <- predict(xgb_model, newdata = test_xgb)# Add to data
test <- test %>%
mutate(pred_improvement = preds,
residual = improvement - pred_improvement) %>%
select(1:improvement, pred_improvement, residual, everything())# Metrics
test %>%
filter(season == 19) %>%
mutate(pred = pred_improvement, obs = improvement) %>%
mutate(pred_category = ifelse(pred > 0, "improve", "decline/same"),
obs_category = ifelse(obs > 0, "improve", "decline/same")) %>%
filter(!is.na(pred), !is.na(obs)) %>%
summarise(rmse = RMSE(pred, obs),
r_square = R2(pred, obs),
mae = MAE(pred, obs),
sens = sensitivity(factor(pred_category), reference = factor(obs_category)),
spec = specificity(factor(pred_category), reference = factor(obs_category)),
pos_pred_rate = mean(obs_category[pred_category == "improve"] == "improve"),
neg_pred_rate = mean(obs_category[pred_category != "improve"] != "improve"),
num = n()) %>%
mutate(model = "xgboost") %>%
select(model, everything())## Resultsmodel rmse r_square mae sens spec pos_pred_rate neg_pred_rate
xgboost 1.82 0.119 1.43 0.410 0.784 0.359 0.818
该模型在测试数据上的表现也相当不错——在一些指标上优于交叉验证(RMSE、灵敏度、阴性预测率),而在其他指标上则较差(R 平方、特异性、阳性预测率)。这里没有任何过度拟合的主要证据,所以我们很清楚要真正尝试这个模型,并开始在最新数据中识别转移目标。
第四步:确定转移目标
现在我们有了我们的模型,我们准备对最新的数据进行预测,并确定一些转移目标!
为此,我们将建立一个函数,对于任何给定的俱乐部:
- 计算俱乐部在最近一个赛季的团队级别属性
- 按位置为该俱乐部的球员创建球员评级矩阵。这使得转会前景可以很容易地与俱乐部中相同位置的其他球员进行比较,从而创建一些预测功能
- 采用最新的国际足联数据中的所有球员,并设置他们的属性,以便“看起来”他们在新的俱乐部
- 通过这些新功能预测他们的改进分数
- 将此与原始预测(使用旧俱乐部的特征生成)进行比较
所以本质上我们在这里做的是说:对于数据集中的每个球员,如果他们的俱乐部级别属性改变了,他们的预测会如何改变?
(我们还增加了一个选项来获得最差的转会,即我们应该避免的转会目标,我们认为球员在我们的俱乐部会比留在他们原来的俱乐部做得更差)。
如果我们为利物浦俱乐部运行这个程序,并保留前 100 个目标,我们会得到下面的结果。一些特殊字符的格式有点…不寻常,所以你必须原谅那些。
(注意:当这个帖子被创建时,最新的可用数据是 FIFA 20 数据集——所以这些预测是针对 19/20 赛季的,有点过时了。加入 FIFA 21 的数据将是一个很好的项目扩展!)
这里有一些有趣的事情:
- 所有十大目标都是进攻型中场,包括像凯文·德布劳内(我们可以做梦)保罗·迪巴拉和布鲁诺·费尔南德斯。解释模型的具体选择是我们可能会保存到未来的帖子中的事情——但看起来模型可能会发现尤尔根·克洛普在 CAM 部门相对缺乏可用的选项
- 其中几名球员实际上是利物浦最近几个赛季的转会目标——最引人注目的是纳比尔·费基尔。所以看起来模型至少做出了一些合理的决定!
- De Bruyne 是排在首位的一个特别有趣的内含物。虽然转会不太可能发生,但该模型预测,如果他留在曼城,KDB 的总体评分在不久的将来可能会略有下降——但如果他转会到安菲尔德,他实际上会有所改善。精神食粮。
- 鉴于默西塞德郡目前的防守伤病危机,选择有前途的莱比锡中后卫易卜拉希马·科纳特是一个有趣的选择。事实上,这位 25 岁的球员在最近几个月与转会到利物浦的联系在一起,我们的模型预测,在克洛普的领导下,他的进步速度将比现在的俱乐部快 60%以上。在这个世界上,他排在范迪克、戈麦斯和马蒂普之后,排在第四位——随着更多的一线队出场,他可能会进步得更快。
我们可能会花一整天的时间来研究这些结果——事实上,这种人工检查通常是机器学习项目中的一个重要步骤。
在下面的附录中,你可以看到 19/20 赛季在英超踢球的每个俱乐部的前 50 名转会目标。你同意你的俱乐部的哪些选择?你认为哪个模型出错了?为什么会这样?回答这类问题通常可以帮助您确定要包含的新数据源、要设计的新功能、要编码的更多领域专业知识,这些可能会改进您对未来的预测。
结论
当然,我们在这里采用的方法有很多限制。虽然国际足联的评分系统令人印象深刻的彻底,它自然是不完美的。任何讨论单个玩家评级的留言板都会有很多粉丝抱怨感知的不准确性,而为视频游戏设计的系统永远不会完全捕捉到专业水平所需的所有细微差别。因此,出于类似的原因,我们的“改善”目标有点粗糙。我们也有很多更复杂的方法来模拟这个问题——例如,使用图形卷积网络来更全面地捕捉不同类型的球员在同一个俱乐部中产生的复杂互动效应。
然而,我们在这里展示的只是冰山一角。我们已经探索了一些简单、容易获得的数据,用几行代码构建了一个模型,并且已经获得了直观的结果。甚至这个简单的概念证明也能引发一些有趣的转让讨论。但顶级俱乐部可以获得比这更丰富、更广泛的数据来源。想象一下,如果你有资源将机器学习技术应用于类似于 Wyscout 数据库的东西,你会做些什么。
所以这个故事的寓意是:当谈到机器学习时,从简单的东西开始,在你感兴趣的主题上找到一些容易获得的数据,不要害怕陷入其中并进行实验。你可能会对你的发现感到惊讶。
附录:所有英超转会目标
下表是通过运行 19/20 赛季英超各队的get_best_transfers()函数生成的。对于每支球队,我们都使用一些粗略的启发式方法来筛选更“现实”的转会目标(没有必要只向每个人推荐莱昂内尔·梅西)。
首先,根据国际足联的比赛,我们筛选出那些被记录为市场价值等于或低于目标俱乐部最昂贵球员+ 20%的球员。第二,我们也通过总体评分进行过滤,只显示那些目前在“总体”评分上比首发俱乐部前 11 名中最差球员最多差几分的球员。因此,如果你的球队最差的首发球员总评分为 80,而你最贵的球员价值 5000 万,你会看到当前总评分为 78 或更高的转会目标,当前市值为 6000 万或更少。
在“新俱乐部”栏中搜索你支持的球队,并在探索预测中获得乐趣!
使用机器学习预测信贷损失:为什么当前的方法有缺陷
“所有模型都是错的,但有些是有用的”
乔治·博克斯 1976 年论文中的上述引文在 45 年后的今天仍然适用。)在很多方面。过去十年中,机器学习的进步带来了很多希望(和大量宣传),有助于解决商业中最棘手的挑战:在事情发生之前知道将会发生什么。我们提出,即使所有的模型都是错误的,一些模型比其他模型的错误少。
背景
为了提供机器学习如何解决分类问题的一些基本背景,下面的视觉表示显示了如何通过算法将由具有已知结果的案例组成的数据集(即,数据集中的每一行代表一笔独特的贷款,每一列中有关于该贷款的属性,最后一列代表该贷款是否违约)馈送给训练(即,创建模型:

(图片由作者提供)
一旦模型已经根据已知的历史案例和结果(违约与非违约)进行了训练,就可以输入结果未知的新案例;该模型根据从训练数据集中获得的信息,预测每种新情况的预期结果:

(图片由作者提供)
现在我们有了机器学习如何工作的基本知识。这在理论上听起来很棒,但让我们探索一下为什么像我们上面训练的模型在实践中不适合许多应用,包括大多数信用损失建模。
一个重要的区别
在我们的数据仓库中,我们可能存储代表事务的数据。例如,对于每笔贷款,我们可能有多行数据:如果贷款按月偿还,则新的一行表示每个月的未偿余额。这与我们需要训练模型的数据集的结构形成对比;我们训练数据集中的每一行都应该是一个独特的案例,我们稍后将要求它对其进行预测。如果我们正在构建一个模型来预测香蕉是成熟还是腐烂,我们的训练数据集中的每一行都必须表示关于一个独特香蕉的信息,而不是每天对多余香蕉的观察。
亏空
首先,回想一下,我们的训练数据集由违约或未违约的案例组成。考虑一个贷款组合,我们的数据仓库中有一些历史数据,包含违约或按期全额偿还的贷款(让我们暂时忽略提前还款的想法,我们可以改天再讨论)。然而,我们数据的很大一部分可能代表了仍在账面上的贷款,这些贷款没有违约,但也没有达到贷款期限的末尾(其结果是未知的 T2)。这提出了几个重要的问题:
如果我们不将这些“活跃的”案例包含在我们的训练数据集中,我们是不是遗漏了许多本应该告诉我们的模型的重要信息?毕竟,如果他们还在账面上,他们还没有违约…
此外,我们知道训练数据集中的每一行都必须代表一笔唯一的贷款:
如果我们的训练数据集中的一个案例具有“违约”的已知结果,那么知道该贷款是在 1 个月后还是在 1 年后违约难道不重要吗?
毫无疑问,我们希望我们的模型能够评估这样一位客户,他在 28 年内按时偿还了 30 年期抵押贷款,然后违约,与在最初几个月后破产的客户不同。
考虑一个不同的问题,比如访问者是否会点击我们网页上的广告。在这个例子中,可能没有太多的“寿命”要考虑。他们上了我们的网站,在几秒钟内,要么点击了广告,要么继续前进——仅此而已。我们可以快速创建一个训练数据集,该数据集可以输入到一个算法中,并生成一个模型,如我们之前在背景部分中所述。
相反,客户拖欠贷款不是一个在几秒钟内发生的过程;几乎可以肯定,这是导致违约的几个月或几年内发生的一系列事件的结果。
我们可以把前面提到的两个问题归结为一个一般性问题,这个问题包含了我们想要得到的核心内容:
我们如何将关于案例(贷款)的“生命周期”的更多信息整合到我们的模型中?
让我们来看三种不同的方法来实现这一点:
- 生存分析
- IPCW(逆概率截尾加权)
- 贝叶斯推理
生存分析
生存分析是一种回归方法,旨在预测事件发生的时间。它还允许我们引入“事件”尚未发生的情况。当训练生存模型时,我们的训练数据集中的每一行都是唯一的贷款,并且列被指定如下:
- ‘时间(月)’代表我们观察贷款的时间
- ‘事件’表示事件(默认)是否在观察期间发生;默认值= 1,无默认值= 0
- 【属性 1】【属性 2】**代表贷款的一些其他信息(即自变量);这些变量可以是分类独立变量,如与贷款相关的客户所在地区,或连续独立变量,如观察期间客户的平均债务覆盖率;你可以引入尽可能多的独立变量来帮助解释你的模型中的方差**

(图片由作者提供)
生存模型的输出不同于传统机器学习模型的输出。生存模型输出新贷款案例“存活”(不违约)超过特定月数的概率,而不是输出它对新贷款案例属于哪一类(违约或非违约)的猜测。更具体地说,当输入新病例时,该模型返回在训练数据集中的“时间(月)】列中找到的每个唯一时间值之后存活的概率。
在我们上面的模拟数据集中——忽略属性 1&2——我们的生存模型会告诉我们,一笔新贷款有 67%的概率存活过去 13 个月(因为 2/3 的贷款是在过去 13 个月观察到的),有 33%的概率存活过去 27 个月(因为 3 笔贷款中有 1 笔“存活”过去 27 个月)。当然,更复杂的模型会考虑我们额外的独立变量(如'属性 1 '和'属性 2 ')并改变这些概率。
生存分析是对传统机器学习的改进,因为它允许我们将嵌入在我们尚不知道结果的案例中的所有信息纳入我们的模型。它还允许我们回答一些非常有力的问题,例如,“在我们投资组合的这一部分中,哪些贷款在 12 个月内违约的可能性大于 50%?24 个月怎么样?”。然而,生存模型假设事件(在我们的例子中是违约)将最终在未来的某个时刻发生。这并不完全符合我们的业务问题,因为许多贷款不会出现违约(它们将被全额偿付)。也许有一种方法可以将生存分析中的一些方法论融合到传统的机器学习模型中…
IPCW(截尾加权的逆概率)
在生存分析中,未知结果的病例被称为“已审查”。在 Vock 等人撰写的 2016 年题为“ 使机器学习技术适应审查的时间-事件健康记录数据:使用审查权重的逆概率的通用方法 的论文中,作者讨论了一种将生存方法引入任何机器学习分类模型的方法。
他们提出的方法要求在训练数据集中只使用结果已知的案例,但是使用一种叫做审查加权的逆概率(IPCW) 的技术对这些案例中的每一个单独进行加权。这些权重是基于整个数据集计算的(结果已知的病例和结果未知的病例)。不是推导事件在时间 t 之前发生的概率,而是通过取该推导的逆来计算 IPCW 权重,或者在时间 t 之后结果仍然未知的概率。
使用 IPCW 加权的训练数据集来开发一个模型,使我们能够利用所有最新和最棒的机器学习算法,同时还可以对我们所有关于未知结果贷款的数据进行编码。特别是,它有助于我们的模型理解在生命中不久违约的贷款和违约但几乎还清的贷款之间的差异。
贝叶斯推理
如果您已经做到了这一步,您应该看到我们正在以不同的方式稍微改变估计信用损失的业务问题,以符合适当的建模方法。
我们认为,最后一种适合信用损失建模的方法是贝叶斯推理。对于那些不熟悉的人来说,这些模型要求可能的结果和相关概率的先验分布,然后添加已经收集的数据(下图中的证据),以创建最终的后验分布。它的工作很像我们的大脑工作——我们通常对可能的结果及其可能性有一些预先的信念,我们随着时间的推移收集更多的证据,更新和完善我们的理解。

https://data insights . de/introduction-to-Bayes-inference-with-pystan-part-ii/
在信用损失建模的背景下,贝叶斯推理为我们提供了两个独特的优势:
- 我们通常不需要像训练频繁模型那样多的数据来训练一个统计上健壮的贝叶斯模型;这对于没有大量历史损失数据的组织尤其有用
- 这些模型输出可能结果的分布,而不是单点估计。这使得我们可以得出这样的结论,“我们有 90%的信心,信贷损失将少于 500 万美元,我们有 95%的信心,信贷损失将少于 1000 万美元”。这通常比传统的频率主义模型更有用,后者仅限于得出结论,“我们预测明年的信贷损失总额将达到 600 万美元”
Kwon 在 2012 年的论文 “三篇关于信用风险模型及其贝叶斯估计的论文” 中最好地概述了使用贝叶斯方法对信用损失进行建模。
包扎
尽管使用机器学习来帮助改善信用损失预测的承诺是合理的,但这样做需要确保您正确地设置数据和模型。将你的数据直接插入机器学习算法可能会很快,所以考虑使用生存分析的一些方法——包括 IPCW——或贝叶斯推理,以确保你正在用你拥有的信息建立最好的模型。
如果你想了解更多关于如何改善你的信用损失评估模型,不要犹豫,与我们联系在凯奇布鲁克分析。
使用机器学习预测客户流失
监督机器学习
使用逻辑回归、决策树、随机森林和梯度推进预测客户流失

Gert RDA valasevi it 在 Unsplash 上拍摄的照片
简介
在本文中,我将带您了解机器学习的一个真实的业务用例。通常,企业需要采取主动措施来减少客户流失(流失)。在大数据和机器学习时代,预测客户流失从未如此容易。
我使用四种机器学习方法,并根据性能推荐最佳方法。我用过的四个模型是:逻辑回归、决策树、随机森林分类器和梯度推进机器分类器。
基于两个标准评估模型性能:训练时间和预测能力。AUC 是用于衡量预测能力的指标,因为它提供了一种很好的衡量模型区分流失客户和未流失客户的能力的方法。
数据
模型根据客户数据集进行训练(每个客户都由唯一的客户 id 标识)。特征是与业务相关的客户属性。它们包括以下内容:
性别——注册时指定的客户性别
加入日期——客户加入的日期
联系倾向——客户联系客户服务部的可能性
Country ID——可以连接到 Country 表以获取客户当前居住国家名称的标识符。国家表也可用于分析。
目标(或标签)是客户流失标记,它识别客户停止处理业务的情况。
数据准备
这是机器学习管道的第一步,在这里进行一些初始探索、数据源合并和数据清理。
合并数据:客户属性和国家数据在国家 ID 上合并,带入当前居住国家的名称。
重复:在客户数据的上下文中,重复的观察可能意味着错误的数据管理,因为数据中应该只有一个客户的实例。没有发现重复的观察结果。
初始特征工程:加入日期是一个日期时间特征,已经被拆分为加入月份和加入年份。这种拆分为模型提供了捕捉客户流失的潜在季节性影响的机会。
测试&训练:客户数据被分成测试和训练数据集。具有丢失流失标记的客户被分配到测试集,所有其他客户在训练集中。测试和训练数据集中唯一客户的数量分别为 5,622 和 50,598。
从这一点开始,测试和训练数据被分开处理,以防止两者之间的数据泄漏,这种泄漏会对机器学习的有效性产生不利影响。
缺失数据:大量缺失数据会影响下游建模。对 N/A 或空值进行简单检查,以确保测试或训练中没有。没有意外丢失的值。
探索性数据分析
最初的数据探索揭示了一些有趣的怪癖。
性别:在培训和测试中,有三个独特的性别类别。男,女,U(未知)。未知案例大约占测试和训练集所有案例的 5%。
对于未知的情况有两种可能的解释。1)选择不透露性别的客户。2)数据输入或数据质量问题导致信息丢失。
对于未知的情况,很难说清楚这两种情况之间的区别。因此,我做了一个简化的假设,即未知病例是那些选择不透露性别的人。在此基础上,有理由相信这群人的行为可能与那些选择认同的人不同。这就是我在训练数据中留下未知案例的理由。
未知案例在流失客户和固定客户中的分布比例与男性和女性客户相似。

跨流失标志的性别分布
缺失&未知国家:在训练数据中,共有 12 名客户的当前居住国缺失或未知。这个相对较小的训练集子集可能是由于数据输入或收集错误造成的。客户不明确说明自己的居住国就注册这项服务似乎不太可能。正因为如此,我将从训练数据中省略这些观察值。
注意:如果当前居民所在的国家不断更新,将缺失的数据作为其自己的类别保留下来可能是值得的。
异常加入日期:在培训数据中,一些客户的加入日期为 1901,这表明存在一些数据质量问题。这些观察结果在分析中被忽略了。
非常老的客户:测试数据中的一位客户年龄为 150 岁。这几乎肯定是数据输入错误,因为目前世界上最长寿的人是 117 岁。荒谬的年龄观察已被纠正,将年龄大于 117 岁的观察替换为客户在其居住国的平均年龄。
注:117 岁是写作时最长寿的人的年龄。这可能需要重新考虑。
训练&测试数据:训练和测试的分割基于丢失的流失标记。这是有意义的,因为目标是推断丢失的流失标记。但是,检查测试人群是否是训练人群的代表性样本是明智的。
我们可以通过绘制我们特征的分布图,并在测试和训练中进行比较来实现这一点。通过肉眼观察分布,测试集看起来确实是训练集的子集,这是我们进行有效机器学习所需要的。

加入年度分布

加入月份分布

目前居住分布的国家

性别分布

年龄分布

接触倾向分布
一个热编码
机器学习模型解释数字而不是单词。我们的客户数据具有必须转换的分类变量,以便机器学习模型能够处理它们。这是通过一个热编码完成的。
造型
四个机器学习模型已经按照标准建模管道进行了训练。选择一个模型,并结合网格搜索和 5 重交叉验证仔细调整超参数。

作者图片:ML 培训管道
下面是 Python 中培训管道的样子。这个简单的功能涵盖了结果评估阶段的所有内容。
用于训练和调整 Scikit Learn 机器学习模型的管道
时间和计算的约束限制了我在优化超参数方面的搜索空间。但是,可以通过以下启发式调整来获得最佳预测性能:
逻辑回归:模型已经用 L2 正则化初始化,这稍微降低了模型的复杂性,以防止过度拟合。超参数 C 是正则化参数的倒数,它被调整以找到最佳拟合。[1]
运行逻辑回归管道的 Python 代码
决策树:决策树容易过度拟合,调整最大树深度和最大特征可以避免这种情况。
运行决策树管道的 Python 代码
随机森林:要调整的重要超级参数是树的数量和每次分割时要考虑的特征子集的大小[2]。自举样本用于构建树,以使模型对过拟合具有鲁棒性。
运行随机森林管道的 Python 代码
梯度推进机:树的数量、学习速率和树的深度是需要调整的重要超参数[2]。
运行梯度提升机器管道的 Python 代码
不平衡的阶层:从我们的探索性数据分析中,我们看到流失客户和坚持客户之间的阶层不平衡。所有模型都使用类别加权来解决这个问题。
车型评测
预测能力
使用 AUC 评估预测性能。在预测性能方面,梯度增强优于所有其他模型,平均 AUC 为 0.994。逻辑回归表现不佳,平均 AUC 为 0.7246。
决策树在最佳运行时表现良好。然而,在所有模型中,其最佳运行和整个搜索空间内的方差是最高的,这意味着有些倾向于过度拟合训练数据,这是不容易控制的。
就预测能力而言,随机森林是表现第二好的模型,平均 AUC 为 0.993。然而,它对过度拟合最稳健,交叉验证折叠之间的方差为 0.000287。

跨 5 倍 CV 的最佳超参数空间的模型性能
下面是用于生成所有最佳模型得分表的 Python 代码。
为每个模型生成最佳得分表的 Python 代码
下面是用来生成上面图表的代码。
为每个模型绘制最佳分数的 Python 代码
训练速度
尽管梯度增强在预测能力方面表现最好,但训练速度很慢,仅用了半个多小时。相比之下,随机森林花了大约一半的时间进行训练。逻辑回归和决策树都在不到 10 秒的时间内完成训练。然而,易过拟合和预测性能差使得这些选项不适合商业使用。
最佳模特
随着计算能力的提高,我会推荐梯度推进。然而,随机森林提供了出色的预测性能和对过度拟合的鲁棒性,这对于生产模型是很重要的。
生产模型
最终,这种模型不仅仅会出现在你的笔记本电脑上,还会被客户大规模使用,因此需要进行生产。因此,我建议我们应考虑以下几点:
1) 这将使测试脚本和调试代码(如果需要的话)变得更加容易。
2) 客户数据监控:我们需要能够预测模型性能何时会受到影响。一种简单的方法是监控我们当前的客户群,并评估它是否与过去的客户群有显著差异。
3)模型重新评估&重新培训:我们应该定期根据新数据评估模型的性能。这可能是当我们检测到数据偏差(见第 2 点)或在一个设定的时间框架。如果新数据的模型性能低于我们的阈值,我们将需要重新训练模型。拥有灵活的建模管道将使重新训练模型变得容易。
4) 考虑袋外评分:如果使用随机森林模型,考虑使用袋外验证来加速训练。不是 K-fold 交叉验证,而是在袋外样本上验证随机森林(假设引导样本用于构建树)。
5)
6) 扩展训练数据:通过在数据中包含一些附加特征,可以进一步提高预测性能。其中一些是最常见的走廊和定价、每月交易、转介和平均交易金额。
代码和数据
这个分析背后的完整 python 代码可以在这里运行。数据可在我的流失建模 GitHub repo 中获得。
https://www.linkedin.com/in/john-adeojo/
使用机器学习来预测客户的下一个购买日
机器学习模型来预测客户是否会在一段时间后进行下一次购买。

图片由 Mediamodifier 制作,可以在这里访问。
介绍
如果说零售业的人从新型冠状病毒疫情那里学到了一个重要的教训,那就是要求他们转向通过互联网做生意,即电子商务。电子商务的理念有助于管理人员为公司的发展做出决策。毫无疑问,这些决策中的大多数都会受到数据分析、数据科学和机器学习专家对在线客户购买行为数据的研究结果的影响。
假设一家在线零售店的管理团队带着数据集来找你,你是一名数据科学家,你想知道顾客是否会在最后一次购物后的 90 天内进行下一次购物。您对他们询问的回答将有助于他们确定他们的营销团队在推出下一次促销活动时需要关注哪些客户。
在本文中,作为一名数据科学家,我的目标是构建一个模型,为公司经理提出的问题提供合适的答案。更准确地说,使用给定的数据集,我建立了一个机器学习模型,预测零售店的在线客户是否会在他们最后一次购买的 90 天后进行下一次购买。
值得一提的是巴尔·卡拉曼已经做了一个类似的工作回答了一个不同的问题,而不是我们想要先发制人的这个问题的准确预测。
关于数据集的一些信息
在开始回答感兴趣的问题之前,我将首先介绍一些关于数据集的一般有用信息。
该数据集记录了来自 43 个不同国家的 5942 名在线客户。在零售店的在线客户中,90.8%的人生活在英国。

图 1:以百分比表示的客户国家计数—图片由作者提供
英国有如此庞大的客户群,公司 83%的收入来自英国也就不足为奇了。

图 2:各国收入百分比——图片由作者提供
下面的图 3 给出了在线零售公司每月收入的直观表示。

图 3:2009 年 12 月至 2011 年 12 月的月收入——图片由作者提供。
在这里,我们可以观察到该公司在 2010 年 11 月的收入最高,其次是 2011 年 11 月。此外,8 月份以后每月收入有所上升。
根据本节所做的分析,我们可以向经理们提出一些建议供他们考虑。在该公司努力增加除英国以外的其他国家的客户群的过程中,数据科学家可以向管理团队提出什么建议?在回答这个问题时,我会说…
由于该公司在英国拥有坚实的客户基础,它可以利用这一点推出“双赢促销”。具体来说,对于现有客户购买的任何产品,他/她都有机会通过网络链接邀请英国以外的新客户。如果新客户使用他/她从现有客户那里收到的网络链接从在线商店购买了一些东西,公司将向现有客户和新客户提供一张现金券,双方可以在下次购买时使用该现金券。通过这样做,我们看到公司、现有或以前的客户以及新客户在交易中都获得了一定程度的满意度。
预测顾客的下一次购买
在这一节中,我将重点介绍我为解决感兴趣的问题而部署的方法。也就是说,建立一个机器学习模型,该模型将预测零售店的在线客户是否会在他们最后一次购买的 90 天后进行下一次购买。
主要步骤包括如下:
- 数据角力
- 特征工程
- 建立机器学习模型
- 选择型号
我首先导入必要的 Python 包,下载数据集,然后将其加载到我的 Python 环境中。下面的代码片段总结了这个步骤。
数据争论
然后,我与数据集争论,让它进入良好的状态,以便引入新的X特性。
给定数据集的CustomerID列有243007缺失数据。这代表了所有在线客户的22.77%。此外,Description列有4382缺失数据。我如何处理这些丢失的数据?在与公司领导交谈后,他们建议应该放弃任何缺少CustomerID的项目。
数据帧df_data被分成两个熊猫数据帧。即,
-
第一个子数据框架
ctm_bhvr_dt包含客户在 2009 年 1 月 12 日至 2011 年 8 月 30 日期间的购买情况。从这个数据集中,我得到了所有在线客户的最后购买日期。 -
第二个子数据帧
ctm_next_quarter用于获取客户从 2011 年 01 月 09 日到 2011 年 11 月 30 日的首次购买日期。
接下来,我创建一个 pandas 数据框架,其中包含每个客户的一组特征,以便我们构建预测模型。我首先创建一个数据集,它包含数据帧ctm_bhvr_dt中的不同客户。
然后,我向数据帧ctm_dt添加一个新标签**NextPurchaseDay**。这个新标签将是数据帧中某个客户的最后一次购买日期之间的天数,该客户拥有最频繁购买的商品,并且缺少 CustomerID 下面的过程处理 CustomerID 列中缺少的值。结果是数据框ctm_next_quarter中的ctm_bhvr_dt和他/她的首次购买日期。
下面的图 5 是上面代码片段的输出。它显示了 dataframe 对象的前 5 个条目ctm_dt。
图 5:客户的下一个购买日数据
在下一节中,我将介绍一些功能,并将它们添加到数据框架ctm_dt中,以构建我们的机器学习模型。
特征工程
我在我们的数据框架ctm_dt中引入了一些特性,这些特性根据客户对公司的价值将他们分成不同的组。在执行这个过程中,我使用了 RFM 分割法。RFM 代表
- 最近度:表示顾客购买的时间有多近。
- 频率:顾客购买的频率或次数。
- 货币价值/收入:客户在某一时间点购买时所花费的金额。
利用这三个特征,即新近性、频率和货币价值/收入,我创建了一个 RFM 评分系统来对客户进行分组。从本质上讲,RFM 得分有助于洞察客户在未来可能会做出什么样的购买决定。
在计算出 RFM 分数后,我接着应用无监督机器学习为每个分数识别不同的组(群)并将它们添加到数据框架ct_dt。最后,我将 pandas 数据框架方法get_dummies应用到ctm_dt来处理数据框架中的分类特征。我现在开始编写代码,找出 RFM 分数和聚类的计算方法。
新近度
在了解谁有可能进行当前购买的过程中,我使用最近特征来解决这个问题。考虑到顾客在最后一次购买后离开的时间长度,最近特征在这里就派上用场了。我使用这个特性来了解哪个客户将进行交易。还需要注意的是,最近购买的客户的销售交易远比有一段时间没有购买的客户更有价值。
让我们进入下面的编码。
下面的图 6 给出了在线客户的最近数据的可视化表示。

图 6:客户最近数据的直方图—图片由作者提供。
用于生成上面图 6 的代码可以在 Jupyter 笔记本这里访问。
接下来,我需要为最近值分配一个最近分数。这可以通过应用 K 均值聚类算法来实现。但是,在使用算法之前,我们需要知道聚类的数量。应用 肘法 ,可以确定给定数据所需的聚类数。在我们的例子中,给定最近值作为我们的数据,计算的聚类数是 4。用于计算星团数量的代码可在 Jupyter 笔记本这里获得。
我现在可以使用数据帧ctm_dt中的**Recency**列构建 4 个聚类,并在ctm_dt中创建一个新列**RecencyCluster**,其值是由无监督机器学习算法kmeans预测的聚类值。使用用户定义的 Python 函数order_cluster可访问这里是,我按照**RecencyCluster**中值的降序对数据帧ctm_dt进行排序。下面的代码片段输出了图 7。
图 7:客户最近聚类数据
让我们根据标有**RecencyCluster**的列中的聚类值对数据帧ctm_dt进行分组,并取出每个聚类的**Recency**数据的统计描述
图 RecencyCluster 的最近数据的统计摘要
从上面的图 8 中可以看出,聚类值 3 包含最近的客户,而 0 包含最不活跃的客户。
在接下来的小节中,我将我们在这一小节中讨论的方法应用于频率和收入特性。
频率
如前所述,在特定的时间段内,如果我们考虑客户参与购买交易的次数,频率就会发挥作用。现在,这种频率特征有助于我们了解特定公司或贸易品牌的客户联盟。考虑到这一点,它使公司能够洞察营销策略,以及在什么时间点进行传递,以便特别触及这些客户。
在这里,我进行了与上一小节相似的分析过程(新近度)。
图 9:带有频率的主数据集的前 5 个条目
下面的图 10 展示了购买频率低于 1200 的客户的直方图。

图 10:购买频率低于 1200 的客户直方图—图片由作者提供。
下面的代码片段为每个客户的购买频率分配一个分类值,并按降序对分类值进行排序。
图 11:带有 FrequencyCluster 的主数据集的前五个条目
下面的代码片段根据记录在标签为**FrequencyCluster**的列中的聚类值对数据帧ctm_dt进行分组,并提取每个**FrequencyCluster**值的**Frequency**数据的统计描述。
图 12:针对 FrequencyCluster 的频率数据统计摘要
就像最近的情况一样,具有较高频率聚类值的客户是更好的客户。换句话说,他们经常光顾零售店的产品,而不是那些频率聚类值较低的产品。
货币价值/收入
为了更详细地描述货币价值或收入,它更侧重于客户在任何时间点购买时所花的钱。因此,这有助于确定顾客在购物时可能会放出多少钱。尽管收入的这一特征并不能让人们预测顾客下一次购买的时间,但知道当顾客完成交易时会有多少收入是值得的。
我再次遵循类似的过程来获得每个客户的收入分数,并根据他们的收入分数为每个客户分配聚类值。
图 13:带有收入的主数据集的前五个条目
下图直观地展示了收入低于 10,000 英镑的客户。

图 14:货币价值低于 10000 的客户直方图—图片由作者提供。
下面的代码片段为每个客户的收入分配一个分类值,并按升序对分类值进行排序。
图 RevenueCluster 的收入数据统计摘要
总分
在下面的代码片段中,我向 dataframe ctm_dt添加了一个新列**OverallScore**,其值是最近、频率和收入的聚类值之和。
图 16:根据整体核心价值分组的近期、频率和收入的平均值
上面的评分清楚地告诉我们,总体得分为 8 分的客户是非常优秀的客户,他们为公司带来了很多价值,而那些得分为 3 分的客户被认为是不可靠的,仅仅是徘徊不定。
作为跟进,我根据总体得分将客户分为以下几类:
- 3 到 4:低值
- 5 到 6:中间值
- 7 到 8:高价值
代码片段如下:
图 17:带有 Segment 列的主数据集的前五个条目
然后,我创建数据集ctm_dt的副本,并对其应用方法get_dummies,以便将所有分类列**Segment**转换为指示变量。
为了实现估计客户是否会在下个季度购买的目标,我创建了一个值为 1 或 0 的新列**NextPurchaseDayRange**,定义如下:
- 如果值为 1,则表明客户将在下一季度购买某样东西,即从他或她最后一次购买起 90 天。
- 值 0 表示客户将在自上次购买后的 90 天内购买商品。
我通过计算我们的特征和标签之间的相关性来结束这一部分。我通过对数据帧ctm_class应用corr方法来实现这一点。
图 18:最小和最大相关系数
从上面的图 18 可以看出,**OverallScore**与**RecencyCluster**的正相关性最高,为 0.97,**Segment_Low-Value** 与**Segment_Mid-Value**的负相关性最高,为-0.99。
在下面的图 19 中,我展示了一个很好的系数矩阵的可视化。代码片段如下。

图 19:相关矩阵—图片由作者提供。
构建机器学习模型
在本节中,我将介绍构建机器学习模型所需的必要先决条件。下面的代码片段将数据帧ctm_class分成**X**特征和目标变量**y**。之后,我将**X**和**y**分开,以获得训练和测试数据集,然后测量不同模型的准确度、F₁-score、召回率和精确度。
图 20:所有模型的度量
从上面图 20 的结果中,我们看到LogisticRegression模型在度量准确性和 F₁-score.方面是最好的
让我们看看如何通过找到合适的参数来控制模型的学习过程,从而对现有模型XGB Classifier进行改进,该模型在上面的图 20 中排名第四。这个过程称为超参数调整。然后我用下面的计算来验证改进的XGB Classifier模型是否优于LogisticRegression模型。
图 21: XGB 分类器模型——超参数调整
图 22:改进的 XGB 分类器准确度分数
选择模型
比较上面图 20 中的LogisticsRegression和上面图 22 中的精确XGB classifier的精度,很明显精确XGB classifier 模型比LogisticRegression模型精确0.1。其他指标呢?
图 23:XGB 和 LogisticRegression 分类器的度量分数
从上面图 23 中的输出可以明显看出,对于每个指标、accuracy、F₁-score、recall和precision,改进的XGB classifier模型优于LogisticRegression模型。
在预测客户在最后一次购买 90 天后在网上零售店再次购买的预期时,我们的提交需要准确。因此,我感兴趣的是在进行这种先发制人时尽可能给出最高精确度的模型。因此,最好选择使用改进的XGB classifier型号,而不是LogisticRegression型号。
结论
从数据集中,我强调了一个事实,即以英国为中心的网上商店的强大客户群是公司从英国作为一个地区获得高收入的主要原因。
我还详细演示了如何建立一个机器学习模型来预测零售商店的在线客户是否会在他们最后一次购买的 90 天后进行下一次购买。在我使用的模型中,我必须通过超参数调整过程进一步改进XGB classifier模型,以胜过LogisticRegression模型。XGB classifier模型max_depth和min_child_weight的超参数调整后的初始指标都设置为 3,并没有超过LogisticRegression的指标。所以我不得不进一步试探性地调整这些值,以便让XGB classifier模型的性能优于LogisticRegression模型。
尽管如此,进一步研究如何再次提高模型的准确性和 F₁-score 度量将是有趣的。我建议通过引入“正确的”X特性来改进数据集,从而避免使用超参数调整过程。所以我现在的问题是
在不调整超参数的情况下,将哪些
X-特征引入数据集以达到或提高模型的高精度和 F₁-score 指标是合适的?
本文使用的 Jupyter 笔记本可从这里获得。
参考
[1] 巴尔斯卡拉曼。(2021 年 4 月 28 日访问)预测下一个购买日
使用 MapQuest API 获取地理数据
数据科学技巧
一个友好的关于从街道地址获取邮政编码和其他地理数据的教程。

Jasmin Sessler 在 Unsplash 上拍摄的照片
知道如何处理地理数据是一个数据科学家的必备技能。在这篇文章中,我们将使用 MapQuest Search API 从街道地址中获取邮政编码以及相应的经度和纬度。
场景
2019 年,我和朋友参加了 CivTechSA Datathon。在比赛中,我们希望将数据点可视化并覆盖在圣安东尼奥的地图上。问题是,我们的数据不完整。惊喜!我们有的只是一个街道号码和一个街道名称——没有邮政编码,没有纬度,也没有经度。然后,我们转向互联网寻求帮助。
我们在 MapQuest 上找到了一个很棒的 API,它能给我们所需要的东西。只需少量的 Python 代码,我们就能实现我们的目标。
今天,我们将完成这一过程。
数据
为了跟进,你可以从这里下载数据。只需向下滚动到底部选项卡,转到 2019 年数据目录。寻找 saw(圣安东尼奥水系统),如下所示。

通过点击 Excel 文件的链接下载文件。

或者,你可以点击这个。
MapQuest API 密钥
前往https://developer.mapquest.com/并创建一个账户以获得一个免费的 API 密匙。




截图来自埃德纳林·c·德·迪奥斯

截图来自埃德纳林·c·德·迪奥斯
复制“消费者密钥”并将其保存在安全的地方。我们以后会需要它。
Jupyter 笔记本
现在,让我们打开 Jupyter 笔记本,开始编码吧!
首先,让我们通过几个导入来设置环境。
不要忘记用上面您自己的密钥替换 API_KEY(第 12 行)。
现在。我们用一个简单的df = pd.read_excel()来读取 Excel 文件。

截图来自埃德纳林·c·德·迪奥斯
接下来,我们将合并街道编号和街道名称列。

所有的大写字母刺痛了我的眼睛。让我们做点什么:
df['street_address'] = df.street_address.str.title() .

下面是两个调用 API 并返回地理数据的函数。
我们可以用下面这条线手动调用它。不要忘记用您自己的 API 键替换' ##### '。您可以使用任何想要的地址(用+字符替换空格)。
get_zip('[https://www.mapquestapi.com/geocoding/v1/address?key=####################&inFormat=kvp&outFormat=json&location=](https://www.mapquestapi.com/geocoding/v1/address?key=Zd5jr3WZm1PbGobgPDHzLz9LEFDaco1V&inFormat=kvp&outFormat=json&location=3403+Kildare+Ave&thumbMaps=false&delimiter=%2C')100+ Military+Plaza[&thumbMaps=false&delimiter=%2C'](https://www.mapquestapi.com/geocoding/v1/address?key=Zd5jr3WZm1PbGobgPDHzLz9LEFDaco1V&inFormat=kvp&outFormat=json&location=3403+Kildare+Ave&thumbMaps=false&delimiter=%2C'))
但是我们有很多地址,所以我们将使用一个循环来重复调用 API。
让我们看看结果是什么样的:

最后,让我们创建一个包含街道地址的数据帧——包括邮政编码、纬度和经度。
瞧啊。我们有自己的地理数据。

为了额外加分,让我们将数据导入 Tableau 并获得一个漂亮的视觉效果:

就这样,伙计们!
你可以在这里找到 jupyter 笔记本。
谢谢你过来看我的帖子。希望对你有用:-)
如果你想了解更多关于我从懒鬼到数据科学家的旅程,请查看下面的文章:
如果你正在考虑改变方向,进入数据科学领域,现在就开始考虑重塑品牌:
敬请期待!
运用数学思维寻找完美公寓
面对铺天盖地的选择,我用数学思维来增强我的决策能力,并在一周内找到了完美的公寓。

照片由 Pexels 的 Chait Goli 拍摄
经过几个月紧张的面试,我在一家令人兴奋的公司获得了一个优秀的数据科学职位。2020 年,我回到学校攻读数据科学硕士学位,并准备安顿下来,现在我正在寻找一个令人敬畏的公寓。为了帮助我在最短的时间内找到最好的地方,我为我要看的公寓创建了一个简洁的测量工具,然后计划用它来找到最好的一个。此外,作为一个数据呆子,我做了一点额外的分析只是为了好玩。运用这种方法的很多灵感来自于阅读《思考,快与慢》,并试图在我的思考中避免走捷径。本文中使用的所有数据都来自我在查看每套公寓时进行的一项简短调查。
这篇短文介绍了一种将数据带入决策过程的简单方法,希望这里的想法对你更有用,而不只是在你下次必须做出重大决定时列出利弊清单。它没有描述一种寻找完美地点的复杂方法,但强调了一种为原本可能是情绪主导的决定增加定量思考的方式。它还展示了如何使用数据科学工具包中的两个工具来可视化自己收集的数据,以解决他们自己的个人问题。
如何比较属性?
一开始,我想知道房地产对我来说有哪些重要特征。考虑到这一点,并考虑到我看到的第一批房产后的轻微调整,我决定列出对我来说最重要的九个因素。然后,我通过一项调查,从 1 到 10 对每个特征进行评分。值得注意的是,并不是所有的功能都是平等的。我需要将它们分为“保健”因素和“激励因素”。这种思维方式基于工业心理学中的一种理论,叫做“双因素理论”。要点是,在卫生项目上得分很低可能会对我的日常生活产生更大的负面影响,而在激励因素上得分较高则不会产生明显的影响。我也倾向于从公寓广告中了解公寓的激励因素,所以它们的变化小于卫生因素,而卫生因素只有在我看公寓时才能观察到。我用字母(H)表示健康因素,用字母(M)表示激励因素
- 负担能力(百万)
- 停车(H)
- 安全(H)
- 安静(H)
- 尺寸(米)
- 街区(H)
- 面积(H)
- 美学(M)
- 视图(M)
现在,这里有一些事情需要考虑。很多这些都是主观的衡量,例如,“美学”或“观点”,然而这些东西对我来说非常重要,我需要一些方法来比较这些属性。其次,这些数据中的一部分可能是通过更复杂的手段收集的。例如,“安全”可能包含该区域的犯罪统计数据,以及一些与安全警卫和安全级别(如围栏)相关的分类变量。我不认为这种详细程度对于帮助我做出更好的决策是必要的——将它添加到模型中的 ROI 会非常低。最后,在计算每套公寓的得分时,我将卫生因素的权重定为 2,将激励因素的权重定为 1,因为它们对我认为它们会带来的幸福水平的影响是不平衡的。
使用可靠的旧谷歌表,我把一组简单的加权分数放在一起,取平均值。对我来说有趣的是,我真的被困在 H. Park (G)和 HL (GP)公寓之间,但结果显然倾向于 H. Park。我设法在最后一分钟多看了一次,FH (G)显然是赢家,也是我最终选择的。

每个公寓的每个特征的分数,其中每个特征被从 1 到 10 评级,然后乘以其相关的卫生或激励因素权重;作者图片
变得更专业一点
作为一个书呆子,数据科学家,我决定看看数据中的两件事。首先,我对不同因素之间的相关性感兴趣。这里有一个非常基础的笔记本,如果你想亲自查看或使用代码,你可以去看看。

特征相关矩阵;作者图片
不出所料,负担能力与其他一切都呈负相关;花更多的钱可以买到更好的公寓。最好的公寓楼有最差的停车位。更好的地区也往往更吵,有更小的公寓,也更安全——尽管肯定更贵。停车似乎也与良好的视野负相关。
我还想对数据进行一点主成分分析,看看公寓是否自然地聚集在一起。我没有得到我期望看到的结果,然而有趣的是,通常得分低(平均)的公寓在图表的底部。鉴于我对公寓的记忆,我不认为它们能在这个二维空间中形成有意义的集群。然而,看看非线性降维方法会产生什么是有趣的。

公寓的主要组成部分;作者图片
我通过查看因子权重进一步研究了 PCA 的减少情况。显而易见,安全性和面积高度相关。就公寓的定位而言,图表下半部分的街区更实惠,但在其他功能上的权重较低;考虑到上面的相关图,这并不奇怪。

主成分和因子权重;作者图片
这里值得注意的是,我故意没有缩放数据,尽管我确实把它放在了中间。我没有对数据进行缩放,因为保健和激励变量的缩放差异很大,我不想从数据中删除这种差异。
结论
这项研究最有价值的方面是,它有助于用数字显示我特别感兴趣的公寓之间的差异,并有助于决策过程。对我来说,能够运用一种定量的方法来解决一个主要由直觉驱动的问题也很有趣。有趣的是,一些我在看过之后不会真正考虑的公寓实际上得分并不太差,但仍然不在得分的前一半。我认为可以对流程进行更多的改进,但我认为这是一个迭代过程,我将来肯定会对我做出的决策应用类似的方法。
使用 MATLAB 的深度学习工具箱|第 2 部分
使用乳腺癌成像数据训练深度前馈神经网络

艾莉娜·格鲁布尼亚克在 Unsplash 上的照片
概述
在这个 3 篇文章系列的第一部分中,我们介绍了 MATLAB 的深度学习工具箱 (DLT),用于训练关于乳腺癌恶性肿瘤数据的浅层神经网络分类器。
如果您想回顾第 1 部分,这里有该文章的链接:
在本文中,我们将在 MATLAB 内置的同一个乳腺癌威斯康星州(诊断)数据集上训练一个深度前馈神经网络。

为什么要用 MATLAB 和深度学习工具箱?
MATLAB 不会付钱让我(叫我 MathWorks)去检查他们的工具箱。但是,我认为更多的初学者应该使用 MATLAB 入门深度学习的原因并不是最终构建一个将要大规模部署的 AI 应用。
相反,这篇文章旨在展示训练神经网络所必需的一些实际考虑,而不会陷入每个组件的细节。
通过这种方式,我的希望是在你的人工智能项目中快速起步并边走边学。

弗兰基·查马基在 Unsplash 上拍摄的照片
如果你想知道为什么我认为 MATLAB 是从非编程背景开始学习数据科学的编程语言,你可以阅读我写的这篇文章:
[## 为什么我选择 MATLAB 学习数据科学
towardsdatascience.com](/why-i-chose-matlab-for-learning-data-science-4f5e4650dce9)
乳腺癌数据集的描述
首先,我将简要描述数据集,它是从 699 个活检中获得的。 cancerInputs 变量中的每个特征描述了细胞属性(粘附、大小、形状)。
cancerTargets 变量被编码成 0 和 1,描述细胞是良性的(0)还是恶性的(1)。我们需要确保输出的数据类型是分类变量。
与浅层神经网络函数相比,没有内置的方法可以自动将数据分为训练集、测试集和验证集,MATLAB 也没有自动完成这一任务的函数。
我写了一个模仿 scikit-learn 的train _ test _ split()函数的函数,叫它……嗯,trainttestsplit()。该功能概述如下:
我们将首先使用这个trainttestsplit()函数分割训练和测试数据。
注意特征应横跨行,样本应横跨列。此外,数据类型需要转换为分类类型。最后,对于我们的二元分类任务,我们将只使用其中一个目标标签。
设置网络架构
我们将使用以下神经网络架构训练一个 5 层深度前馈神经网络:

我们将要训练的 5 层神经网络结构的示意图。请注意,输出图层是一个包含布尔值的单一矢量,表示我们预测的两种癌症状态(良性/恶性)。这个塑像是由斯科特·坎皮特设计的。
与我们的浅层神经网络分类器相比,我们需要在该网络架构中指定 4 个组件,如下所述:
- 输入数据层:在输入数据集中调用管道的函数因输入的数据类型(如 2D 数值、张量等)而异。).对于我们的数据集,需要使用 sequenceInputLayer() 函数,需要将要素的数量作为参数。
- 隐层:设计一个全连接的前馈神经网络,我们需要调用 fullyConnectedLayer() 函数,该函数需要将激活节点数作为第一个参数。 WeightsInitializer() 和 BiasInitializer() 参数解释了如何初始化权重和偏差参数。在下面的代码中,我们指定使用正态分布的小随机数来打破对称。
- 激活函数:每个隐藏层之间是激活函数。虽然我们可以使用交叉熵层进行二元分类,但是我们也可以使用 softmaxLayer() 函数,该函数可以推广到多类分类问题。
- 分类层:分类层()将概率解码成它们预测的标签。
我们将这些组件存储到变量层中:
对于 MATLAB 中可用的不同层对象的描述,您可以查看文档。
网络架构的健全性检查和调试
从这里,我们应该停下来,看看我们是否有正确的架构,以及是否有任何潜在的警告或问题会导致培训阶段失败。
我们可以使用analyze network()函数,该函数接受我们刚刚设置的 layers 变量并可视化网络架构。
该函数输出一个表格,显示我们刚刚指定的层的概要以及网络中的参数数量。

analyzeNetwork()函数的汇总表,显示了层类型、每层中的节点数量以及模型正在学习的参数数量。
指定训练和超参数
接下来,我们必须指定模型超参数。 训练选项 () 函数创建一个模型对象,让您将超参数输入神经网络函数。
我们将使用的具体参数如下所示。
用于模型训练的附加超参数选项可在这里找到。
训练 DNN
最后,让我们使用train network()函数来训练神经网络,该函数允许我们训练其他深度神经网络架构。
有关使用此功能训练的其他神经网络的更多信息,您可以阅读文档。
该函数还输出一个图形,显示模型实时训练时的精度和成本曲线。该图如下所示。

trainNetwork()函数输出的训练记录。该图显示了作为时期函数的精确度(顶部)和成本(底部)曲线。这个图形用户界面在模型训练期间弹出。
从该图中可以得出的主要结论是,成本在 200 个历元时收敛到最小值。如果我们增加历元的数量,有证据表明模型开始过度拟合。
用深度神经网络做预测
为了使用深度神经网络模型进行预测,我们可以使用内置的classify()函数,该函数返回给定验证集的目标标签。
针对浅层神经网络的基准模型结果
在第 1 部分中,我们训练了一个浅层神经网络,并根据验证集评估了它的性能。
让我们通过并排绘制混淆矩阵来看看我们更深层次的神经网络是否比浅层次的更好。
在 MATLAB 中生成混淆矩阵的代码如下所示:

将浅层神经网络(准确率:96.7%)与 5 层神经网络(准确率:97.3%)进行基准测试。
5 层神经网络稍微增加了我们的乳腺癌分类任务的准确性。
在这种情况下,我们正在观察深度神经网络的一个普遍趋势:增加神经网络的深度和节点数量往往会提高精度。

超参数调优呢?
MATLAB 中有对深度神经网络进行贝叶斯超参数整定的方法。然而,这个过程更复杂,将在以后的文章中更深入地讨论。
摘要
我们将深度前馈神经网络添加到我们的深度学习工具中,并发现它提高了我们对患者是否患有良性或恶性肿瘤进行分类的能力。
但是,您可能已经注意到,我们一直在使用数值数据对肿瘤成像数据进行分类。如果我们想使用原始图像本身呢?
在下一篇文章中,我们将在图像上训练一个卷积神经网络来预测肿瘤致病性。

克里斯汀·休姆在 Unsplash 上拍摄的照片
我们谈谈吧!
如果你想分享你是如何在你的项目中实现深度神经网络的,或者只是想打个招呼,请在下面的评论中留下你的回复。
此外,请随时通过 LinkedIn联系我——我很乐意与你联系并讨论所有书呆子的事情!
使用微服务构建和扩展数据功能
行业笔记
微服务是我构建灵活有效的数据功能的首选框架。以下是我学到的方法和内容。

来源:Unsplash,照片由米赛尔·莫雷诺拍摄
数据团队是一个专业的业务职能部门,在决策中利用数据和见解。
在我的职业生涯中,到目前为止,我已经建立了两个数据团队。首先是在 Instagram 上建立决策科学功能。这是一个职责广泛的重要角色。我们支持世界各地的业务团队。我们实施了一系列工具,如仪表盘和机器学习程序。我们还支持预算规划,为全球战略提供信息,并提出产品建议。
现在,我回到 fin-tech 初创公司spacex建立数据和分析业务。两家截然不同的公司,两种截然不同的资源。
在这两个例子中,我发现使用微服务架构作为我的框架来编写数据功能计划和扩展团队是成功的。
我想分享一下我是如何在构建数据团队中部署微服务方法的,以及在此过程中的一些收获。
微服务架构快速介绍
如果你已经知道这个东西,请跳过它。
微服务架构是一种应用于服务或应用的架构以及代码编写的方法。
微服务架构背后的基本前提是——将大型复杂的业务问题(如应用程序或服务)分解成较小的、很大程度上独立的组件。
这些组件通过共享功能“松散耦合”在一起,例如表格、定义或公式。但是,在大多数情况下,组件是独立运行的。这意味着一个组件的问题或中断不会影响其他组件,一个小问题也不会影响整个服务。
这与整体架构正好相反,在整体架构中,所有的东西都在一个地方并且相互关联。相反,微服务是独立的,但仍然一起工作来完成相同的任务。例如,在线商店搜索栏可能是一个组件,而“由他人推荐”可能是另一个组件。
关于微服务架构的更多信息,我发现这个来自软件服务公司 Red Hat 的概述很容易理解。
循序渐进的方法
这些是我根据微服务方法创建数据功能计划和策略时遵循的主要步骤。
第一步——了解业务。
我从深入了解业务开始。
我试图理解广泛的商业动态,商业和产品的经济学。
我观察了广泛的用户行为和用户旅程,从认知到获得再到使用再到流失的漏斗(有漏斗吗?)以及更广泛的市场动态(竞争对手、市场变化、人口采用等)。
这些见解是我接下来工作的基础。
第 2 步—概述微服务组。
然后我概述了微服务组。这些组将成为单个组件度量和洞察的集合(参见步骤 3)。我将这些组与业务设置紧密关联。
以宇宙飞船为例。我们有一个线性漏斗。人们了解了宇宙飞船。如果他们感兴趣,他们就签约,他们存一些钱用于投资,并通过存款和经常性投资来增加这些钱。最后,一些人关闭了账户。
所以我的微服务团队很简单:
- 市场洞察
- 收购和增长
- 产品使用和参与
- 搅拌
第三步——定义组件。
接下来,我概述了新数据功能将在每个微服务组中提供的组件指标,以支持业务领导者。
“市场洞察”组中的示例组件包括:行业趋势、品牌(和竞争对手)认知度、品牌考量、意向和品牌情感。
“获取和增长”微服务组的示例组件包括:归因、归因模型、媒体优化、媒体渠道基准(CPM、CPC 等)和漏斗转换百分比。
你会注意到有很多成分。一些复杂的需要专家工作或建模,一些更简单。因此,每个组件都有自己的开发计划和改进清单。
步骤 4——定义共享工具。
快速回顾—此时,我列出了我的微服务组和组件。接下来,我概述了许多组件会使用的共享工具。定义这些共享工具有助于减少工作量,简化 SQL 代码并防止数据管道重复。他们还帮助企业中的每个人分享对某些指标和数据类别的共同理解。
共享数据工具包括标准化定义、标准化公式和标准化类别。
一些示例包括:客户平衡的标准化范围、年龄范围和性别等关键人口统计标签、增长会计范围、软流失与硬流失的定义以及一些产品级别的定义。
第 5 步——盘点和起草。
此时,我已经为我的新数据功能建立了微服务组、组件指标和共享工具/功能。
接下来,我对我的微服务进行了盘点,以查看已经可用的微服务,并为每个组件起草了开发步骤。
具体来说,我在看:
- 我可以重新利用企业中其他地方的资源,
- 每个组件所需的开发和改进水平,
- 眼前的业务优先级(确定交付什么组件和优先级顺序)
例如,在 Spaceship,表格中已经有了“参与”客户的定义。但是客户流失的定义不可用。所以客户流失的定义是我们建立的。
在 Instagram,人口分组和细分工作已经完成,并由中央数据科学团队提供。所以我们不需要建造这些。但是本地化分析不可用,所以这是我们建立的东西。
第六步——敲定计划。
在流程的这一点上,我有了我的微服务组、组件指标、共享工具,以及对业务和开发步骤中已经可用的东西的理解。
我收集了所有这些信息,并最终确定了我的发展计划。
我用两张幻灯片写了这个计划。一张幻灯片显示了微服务组、组件和组件进度。第二张幻灯片显示了 1 年、2 年和 3 年的计划进度,突出了将通过数据团队交付给企业的广泛关注领域和数据功能。
以下是我总结计划的一个例子:

作者提供的图片—仅作为示例

作者提供的图片—仅作为示例
注意——这些幻灯片仅供说明之用,已经过编辑,删除了敏感信息,并经过简化以适应本媒体。
除了上面显示的这些概述幻灯片之外,该计划还得到包含更多详细信息的项目管理表、用于管理“待办事项”列表项目进度和人员/角色规划的 JIRA 委员会的支持。
第七步——回顾、重复、改进、再回顾。
接下来,我制定了一个时间表,每半年回顾一次计划。当业务发生重大变化时,我还会审查该计划。在评估过程中,我重点关注:
- 需要升级的组件,因为工作已经完成,组件已准备好进入下一阶段。
- 本可以更高效的组件或增长过大的组件需要拆分成新的组件。
- 我的数据功能不再需要或不再需要支持的组件或微服务。
我还利用评审时间向业务部门展示更新的计划,并提醒领导层计划和交付时间表。
使用微服务架构构建数据团队的优势
乍一看,可能很容易将微服务的前提过于简单化。“只是把东西分解成小块?就这样?”但是事情远不止如此。
对数据功能采用微服务方法带来了许多好处,我总结如下:
- 结构化、层次化的思维方法——从业务理解到微服务组再到组件指标——让整个功能构建过程感觉可管理、结构化且清晰。
- 事实上,每个组件、每个开发步骤和每个微服务组都与一个业务功能联系在一起,这意味着这些计划得到了整个业务的广泛支持,并被广泛理解。每个业务领导都可以看到他们将获得的具体指标以及如何使用每个指标。
- 在业务中寻找共享的定义和共享的工具(比如公式)确保了数据功能不会重复工作,并且工作与业务中的其他领域保持一致。
- 微服务方法鼓励交付思维——也就是说,它鼓励向业务交付组件指标,逐步向业务领导推出工具和见解,并关注持续的指标改进以支持业务。
- 与此相关的是,它为每个组件和每个微服务组提供了一种勾勒清晰开发计划的方式。从那时起,这就是一个简单地执行计划并推动每个团队改进的案例。
- 微服务范围带来了独立的数据管道、特定于组的代码库、独特的表格和专用的仪表板,从而提高了商业智能的连续性,更快地发现故障和问题,确保表格故障仅影响一小部分组件,并加快了开发速度。
- 最后,我发现微服务方法非常灵活,并成功地将其用于较小规模的项目(如活动测量),直到构建本文中概述的完整数据团队。
考虑到工作的艰巨性,两次建立数据函数的任务既令人畏惧又令人兴奋。然而,我发现使用微服务方法有很多好处。
微服务架构帮助我组织工作,使任务变得非常容易管理。它给了我一个灵活的框架,一个集中我的计划的方法和一个建立有效数据功能的过程。
我鼓励任何管理复杂数据项目或组建数据团队的人将微服务概念视为潜在的辅助手段。
使用 MLFlow 跟踪和版本化机器学习模型
循序渐进的编码实践
当我们调整机器学习模型的参数时,我们可能需要多次训练它,以便选择最佳模型。如果训练次数过多,我们可能会遇到两个问题。
- 我们如何跟踪每个模型的参数和度量?将它们手动写入 excel 文件?那将是乏味且容易出错的。
- 我们如何对每个模型进行版本控制?用不同的名字保存到磁盘上?很难记住哪个模型来自什么参数。
MLFlow 正是我们解决这些问题所需要的。它是一个强大的 MLOps 工具,用于 ML 模型跟踪、版本控制、打包等等。在这篇博客中,我们将关注跟踪和版本控制。关于跟踪,MLFlow 可以跟踪参数、度量和模型。关于版本控制,MLFlow 将模型存储在一个模型注册表中,然后用户可以很容易地选择一个特定的版本。
MLFlow 可以在本地运行或从 docker 容器运行,也可以部署到 kubernetes。它有 Python、Java、R 和 REST 的 API。
在这篇博客中,我们将展示如何使用 mlflow 来跟踪和版本化 mnist 分类模型。我们将首先使用 tensorflow 运行一个 MNIST 示例,然后扩展代码以集成 mlflow。

图片来自Unsplashis AAC Smith
MNIST 的例子
首先创建一个虚拟环境,pip 安装 tensorflow 和 mlflow。
# my version is 2.6.0
pip install tensorflow
# my version is 4.4.0
pip install tensorflow_datasets
# my version is 1.9.1
pip install mlflow
下面是使用 tensorflow 运行 mnist 分类的代码。
张量流中的 MNIST 分类
运行代码,您应该会看到如下所示的日志。它还将模型保存在模型文件夹中。

mnist.py 的日志(图片由作者提供)
物流跟踪
现在,让我们跟踪参数、度量以及工件(模型)。见下面的代码,(也见底部的完整代码)。首先,我们需要命名一次跑步。如果我们愿意,我们甚至可以命名一个实验(更高级别的运行)。然后,我们使用函数log_param、log_metric和log_artifacts来记录参数、度量和工件。
import mlflowwith mlflow.start_run(run_name=run_name):
mlflow.log_param("batch_size", batch_size)
mlflow.log_param("learning_rate", learning_rate)
mlflow.log_param("epochs", epochs)
mlflow.log_metric("train_loss", train_loss)
mlflow.log_metric("train_accuracy", train_acc)
mlflow.log_metric("val_loss", val_loss)
mlflow.log_metric("val_accuracy", val_acc)
mlflow.log_artifacts("./model")
运行 mlflow 代码后,我们可以看到磁盘中有一个名为mlruns 的新文件夹。这是本地存储参数、指标和工件的地方。
然后,我们可以做一些参数调整,例如,改变批量大小和学习速率。每次运行都将被记录到 mlflow 中。
现在,让我们在 mlflow 的 UI 上查看所有运行。在虚拟环境中,键入:
mlflow ui
然后,在 http://localhost:5000/ 浏览。我们可以看到所有运行都记录在那里。在一个页面上可以清楚地看到参数、指标和运行名称。

在 mlflow 追踪上运行(图片由作者提供)
如果我们点击一次跑步,我们可以看到关于这次跑步的更多细节。除了参数和度量,左下方的工件部分显示了我们的工件(模型)。

mlflow 运行的详细信息(图片由作者提供)
每次运行的 url 地址具有以下格式。每个实验中的运行 id 都是唯一的。
http://localhost:5000/#/experiments/<experiment id>/runs/<run id>
MLFlow 模型版本化
MLflow Model Registry 是存储和版本化模型的中心位置。有了它,一个模型就有了从(例如)v1,v2,…到 v10 的迭代版本。对于每个模型和版本,我们可以写一个降价描述(例如详细的参数),这样我们以后就知道这个模型代表了什么。此外,我们可以用Staging、Production或Archived来标记一个版本。
为了建立模型注册,我们需要一个数据库后端来存储模型,参见这里的获取说明。之后,我们可以将 tensorflow 模型上传到 mlflow 注册表。下面是一个代码示例。
import mlflow.tensorflow
from tensorflow.python.saved_model import signature_constantstag=[tf.saved_model.tag_constants.SERVING]
key=signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEYmlflow.tensorflow.log_model(tf_saved_model_dir="./model",
tf_meta_graph_tags=tag,
tf_signature_def_key=key,
artifact_path="model",
registered_model_name="mnist")
总之,MLFlow 是一个强大的 MLOps 工具,用于跟踪和版本化机器学习模型。它支持 python、java、R 等语言的 API。通过其漂亮的用户界面,我们可以清楚地跟踪、存储和比较不同的模型版本。MLFlow 已经成为许多 ML 项目中流行的 MLOps 工具。
下面是 tensorflow 中带有 mlflow 跟踪的 mnist 分类的完整代码。
张量流中 MNIST 分类的完整代码
使用现代艺术博物馆的收藏数据集来可视化谁的故事不见了
数据新闻
艺术如何影响我们对边缘群体的参考点

上面的橙色代表 MoMA 从单身白人男性艺术家那里获得的作品。图片作者。
在最近的一个班级项目中,我使用 MoMA 在 GitHub 上发布的收藏数据集创建了一个互动装置,揭示了被放大的主导叙事。我对 MoMA 在 1930 年至 2019 年间获得艺术品的 15,222 名艺术家的性别和种族进行了细分。我们如何将现代艺术博物馆的藏品分成几个部分来评论主流的叙事?
保持机构的问责制
2019 年 9 月, MoMA 经过紧张的改造后首次对外开放。目标是:扩大博物馆,把“毕加索和莫奈放在更近的、多样化的艺术家旁边。”这已经成为大多数机构和公司发出的一个普遍的前瞻性信息。多元化和包容性的对话是受欢迎的,但我们很少通过行动积极创造实质性的影响。
研究论文“美国主要博物馆艺术家的多样性”详细介绍了艺术博物馆馆长协会(AAMD)如何发现其成员机构中 72%的工作人员认为自己是白人。他们还揭示了领导职位中的性别差距,60%的博物馆工作人员是女性,但只有 43%的管理职位由女性担任。
艺术机构是系统的一个具体例子,这个系统曾经无情地启用与白人主导文化一致的标准。更具体地说,通过欧洲现代主义的镜头来评价艺术品。现在,恐惧正因其排他性和自命不凡的白盒基调而受到批评。MoMA 试图重新思考我们谈论和评价艺术品的方式。我想创造一种体验,来帮助说明主要的故事和完全缺失的故事。
抓取数据集

图片作者。
- 种族:首先,我使用论文美国主要博物馆中艺术家的多样性(Topez,Chad M 等人)中的数据,可视化了 MoMA 中不同种族艺术家的百分比。
- 性别:我使用 MoMA 的收藏数据,提取了从 1930 年到 2019 年每十年获得的每件艺术品中不同性别艺术家的比例。我用颜色来形象化这些百分比。
- 前 10 名女艺术家:从那以后,我把注意力集中在获得最多艺术品的前 10 名女艺术家身上。这是用一个视觉单词云来表示的,其中名字的大小与获得的艺术品数量相关。
通过体验体现数据
视频演练“谁重要?”安装。
第一步:在你认识的名字旁边贴上尽可能多的标签。在每张纸网上散布着在特定的十年里被 MoMA 获得最多艺术品的前 10 位女性艺术家的名字。名字越大,他们的作品就越多。获得的艺术品越多,它们占据的空间就越大。随着时间的推移,来自艺术观众的集体数据输入将揭示艺术家获得的艺术品数量是否影响他们的曝光水平。
第二步:向后拉黑色手柄。纸网形成三维形状。将显示每十年男性、女性、多位艺术家的百分比,以及未找到的性别记录。
第三步:后退一步,查看整个棋盘。钉板的周长用每个种族的百分比进行颜色编码。当你退后一步,绕着棋盘走一圈,主导 3D 条形图的颜色和棋盘的周长揭示了哪个群体主导了博物馆的收藏。

装置的互动性引发了参与者之间的对话。图片作者。
洞察力
- 随着时间的推移,最知名的女艺术家将会涌现出来。我和家人一起完成了贴纸活动。我们在认出的女艺术家旁边贴了贴纸。黑板上的名字越大(获得的艺术品越多),我们就越有可能知道他们的作品。这也促使人们思考遗漏了哪些名字。
- 现代艺术博物馆主要从男性和白人艺术家那里获得作品。自 1930 年至 2019 年,MoMA 从其获得艺术品的大多数艺术家都是男性。近几十年来,女性艺术家略有增加。
- 绕深蓝色边框(代表白人艺术家)花的时间最长。黑人和拉丁裔艺术家的作品在纽约现代艺术博物馆的展示明显较少。绝大多数艺术家是白人。
- 这个装置的互动部分引发了参与者之间的对话。当我向家里的人演示这个装置时,这些活动促使我们反思我们最初的反应。我们互相询问我们是如何知道某个女艺术家的,并讨论了我们对 MoMA 多样性的差距有多惊讶(或不惊讶)。同时反思我们在艺术行业中的角色。
我们的参考点受到缺乏代表性的影响
淡化反亚裔情绪
我们周围的叙事中的代表性水平成为我们如何看待文化和其他边缘化群体的参考点。在 MoMA,可以说是最有影响力的博物馆之一,我们主要看到白人男性艺术家的作品。在我们的家庭、教室和人际关系中,我们周围有哪些观点或经历?
亚特兰大枪击案中八人丧生,其中六人是亚裔女性,这在亚裔美国人社区中造成了巨大的反感。反亚裔仇恨的增加揭示了助长种族主义、仇外心理和厌恶女性攻击的故事有多危险。无论是我们在原始博物馆看到的艺术,还是我们在网上听到的言论,这些都有助于我们与“他人”联系在一起的参考点。
例如,《今日美国》报道称,去年秋天川普被诊断患有新冠肺炎后,反亚裔情绪上升了 85%,ADL 当时发现。从他上任到今天,特朗普一直在困扰人们通过使用极端种族主义术语“中国病毒”来联系亚洲人的参照点。

图片作者。
艺术在日益喧闹的世界中的作用
博物馆为故事提供了空间。对于当代艺术来说,对所谓的“艺术”有一种松散的控制。这种控制欢迎抽象和混乱的演绎表演、装置、绘画、混合媒体、雕塑等等。这通常是对文化和社会问题的一种共同的集体理解,它让我们与某些艺术作品产生共鸣。
从我们屏幕上的内容到与他人的对话,都有越来越分裂和政治狂热的背景噪音。我们受益于参与各行各业艺术家的作品,他们的故事与我们的叙述不同。它为自己提供了一个反思和对话的机会。
创建真正多样化和包容性的参考点
尽管我讲述了我的背景和目前的身份,但总会有一层阴霾围绕着我。由种族主义和我的韩裔美国人血统的减少构成的阴霾。有时如此微妙,有时我告诉自己没什么可担心的。对 AAPI 社区来说,这从来就不是蓝天。
当我反思自己作为一名亚洲女性的背景和身份时,我使用的参考点是什么?我的社区在艺术、媒体、领导职位、坦诚对话等方面的表现,助长了许多基于我的种族对我的概括。通过摒弃这些有害的刻板印象,我们可以减轻边缘化群体的非人化。
露丝·阿莎娃变形钢丝雕塑

4 件露丝·阿莎娃钢丝雕塑。在大卫·兹沃纳展览上看到的。图片由维基百科作者 Njdancer15 提供。这张图片是在知识共享署名-共享 4.0 国际许可下授权的。
随着我对日裔美国艺术家露丝·阿莎娃的了解越来越多,我就越觉得她的作品与我作为一名韩裔美国人的经历相关。
在 1946 年夏天到达黑山学院之前,浅泽出生在加利福尼亚的诺沃克。她的父母是来自日本的移民,感受到了针对日本人的歧视性法律的压力。他们不能拥有土地,成为美国公民,或者梦想成为卡车农民。由于不利的经济形势和对日本人的持续歧视,浅泽在大萧条时期的成长经历和股票市场崩盘导致她的家庭陷入困境。他们被安置在两个拘留营里。
浅泽的生物形态般的线雕塑提醒我,我们的情感,无论多么强大和微弱,都不必像金属线一样僵硬和令人恐惧。相反,它们可以转变成一个通风的,动态的,令人难忘的美丽的线雕塑,展示给每个人。她的钢丝雕塑赋予抽象和杂乱的形状。
我在她的作品中感受到了一种艺术反叛,这种反叛存在于一个歧视和种族主义盛行的体系中。她追求将每一点小小的快乐都投射到自己的影子里,以此来定义自己的存在,这种追求形成了一种反叛。

“靠自己的力量振作起来”的谬论
这个使用 MoMA 的收藏数据集的装置是我思考变化有多慢的一种方式。我们可以庆祝 BIPOC 社区取得的进步,但人们强烈呼吁对被系统抹去的故事进行问责和深度投资。黑人、亚洲人、拉丁人、西班牙人、土著人和非二元艺术家的观点一直存在。我们如何重新思考用来评价和思考艺术品的类别?作为观众,我们的角色是什么?
我想知道当我的祖父在 80 年代从韩国移民到波特兰时,在一家卡车工厂工作时介绍自己是什么感觉。通过证明他知道卡车的零件,他能够证明他是一个高度熟练的工人,尽管他不会说英语。他并不比那里的任何人差——这一点必须不断得到证明。
在一个行为主义的时代,我们必须后退。你的故事很重要。正如米歇尔·奥巴马在《T4》变成 中写道:“如果你不走出去定义自己,你会很快被别人错误地定义。”分配给边缘化群体的空间越多,我们就越能颂扬和尊重他们的文化和精彩故事。
捐:gofundme.com/f/support-aapi-community-fund
幕:stopaapihate.org/actnow
学:anti-asianviolenceresources.carrd.co
看:youtube.com/watch?v=14WUuya94QE
在 Python 中使用 Mongo 数据库
用 PyMongo 介绍 MongoDB

(图片来自https://www.pexels.com/)
MongoDB 是一个基于文档的数据库,具有动态数据模式。它支持的 JavaScript Object Notation (JSON)非常适合使用现代编程语言(如 JavaScript、Python 等)中的对象。这为 SQL 等更传统的关系数据库管理系统(RDBMS)提供了一种替代方案。MongoDB 是 NoSQL 数据库的一个例子。这些数据库通常使用文档集合,而不是 RDBMS 中使用的表。这些数据库支持动态数据库模式,使它们能够响应数据结构的变化。
这篇简短的入门文章给出了一些通过使用 pymongo 库将 MongoDB 与 Python 结合使用的例子。本文最后介绍了在现代软件项目中使用 Mongo 数据库的 mongoengine ,以及如何将它们转换成 dataframe 对象以供进一步分析。假设读者能够轻松地下载和设置 MongoDB,并且有一些使用 Python 的基本经验。
结构
文档数据库的结构不同于以行(记录)和列(字段)存储数据的关系数据库。

显示来自关系数据库的示例股票表的图像(作者提供的图像)
在这种结构中,每一列应该只包含相同类型的数据。例如,我们只希望在库存商品列中看到库存商品数据。任何新数据都需要创建一个新列或新表,然后使用唯一标识符(主键)定义表之间的关系,该唯一标识符在后续表中称为外键。更改此数据的结构,尤其是当它已经包含数据时,会更加复杂,可能需要使用迁移工具。
与此相反,MongoDB 使用键/值对将数据存储为文档集合:

包含数据的一组文档(图片由作者提供)
与我们必须在表中创建新列来存储信息的关系数据库不同,数据可以被嵌入。这意味着我们只需要存储相关的内容,而不是制造冗余。
入门指南
Pymongo 是 mongoDB 的 Python 驱动程序,允许您使用 Python 与 Mongo 数据库进行交互。您首先需要在您的系统上安装 MongoDB。如果你还没有这样做,你可以在这里阅读如何做:https://docs.mongodb.com/manual/installation/
要使用 pymongo,首先需要安装库,例如在 Python 提示符下使用 pip:
pip install pymongo
接下来,我们需要将 pymongo 库导入到 Python 文件或 Jupyter 笔记本中。
import pymongo
然后连接到 Mongo 客户端。这将连接到默认的主机和端口。
client = pymongo.MongoClient(“mongodb://localhost:27017/”)
然后我们可以创建一个数据库来存储一些数据。在本例中,它将为医疗系统存储一些患者的详细信息。
db = client[“med_data”]
接下来,我们可以向数据库添加一个集合。每个数据库可以包含多个集合。这个集合将被称为 patient_data ,我们将使用变量 my_collection 在 Python 中引用这个集合。
my_collection = db["patient_data"]
插入数据
然后,我们可以将一些数据(文档)添加到集合中。假设我们想存储一个病人的一些基本信息。这可能包括他们的姓名、年龄、生物性别和心率。我们还将存储他们的血压,通常用代表收缩压和舒张压的两个数字来显示,通常以毫米汞柱(mmHg)来测量,例如 156/82。在 MongoDB 中,使用 JavaScript 对象符号将字段(数据项)封装在大括号({})中。每个字段由一个键/值对组成。字段名(键)用引号括起来,后跟冒号和相关值。文本(文本数据)值也用引号括起来,数字(数值数据)则不然。值也可以包含其他对象和数组。数组可以存储数据列表和其他键值对,并用方括号([])表示。在这里,我们可以存储收缩压(sys)和舒张压(dia)的键和值以及数据值。
patient_record = {
"Name": "Maureen Skinner",
"Age": 87,
"Sex": "F",
"Blood pressure": [{"sys": 156}, {"dia": 82}],
"Heart rate": 82
}
只需在右括号后添加一个逗号并添加其他对象,就可以添加多个文档。根据需要,不同的对象还可以包含完全不同的数据字段。
一旦我们创建了文档,我们就可以将它添加到集合中。要添加单个文档,我们首先指定要添加的集合,后面跟一个点,然后我们可以使用 insert_one 函数(对于许多文档,我们使用 insert_many )传入文档对象变量:
my_collection.insert_one(patient_record)
为了查看集合的内容,我们可以循环遍历集合中的每一项并打印出来。
for item in my_collection.find():
print(item)
这将输出如下数据:

作者图片
以这种方式查看数据会使阅读变得非常困难,尤其是当您有许多字段和文档要输出时。幸运的是,Python 有一个非常好的打印库来实现这个目的。如果我们修改代码来导入库并使用函数(注意打印中的双“p”):
from pprint import pprintfor item in my_collection.find():
pprint(item)
您可以看到,它以更易于阅读的格式输出数据:

作者图片
注意,MongoDB 会自动添加一个 ObjectId 来惟一地标识每个文档。这是一个 12 字节的十六进制字符串,由时间戳、随机生成的值和递增计数器组成。这些 id 在数据输出时显示。如果需要,您也可以通过为“_id”字段提供您自己的值来覆盖它。
我们可以使用 insert_many 函数一次添加多条记录:
patient_records = [
{
"Name": "Adam Blythe",
"Age": 55,
"Sex": "M",
"Blood pressure": [{"sys": 132}, {"dia": 73}],
"Heart rate": 73
},
{
"Name": "Darren Sanders",
"Age": 34,
"Sex": "M",
"Blood pressure": [{"sys": 120}, {"dia": 70}],
"Heart rate": 67
},
{
"Name": "Sally-Ann Joyce",
"Age": 19,
"Sex": "F",
"Blood pressure": [{"sys": 121}, {"dia": 72}],
"Heart rate": 67
}
]my_collection.insert_many(patient_records)
更新数据
我们可能还想更新以前添加到集合中的数据。同样,我们可以更新单个或多个记录。假设我们不小心为达伦·桑德斯和莎莉·安·乔伊斯添加了相同的心率。达伦的应该是 88 岁。这里,我们可以使用 update_one 函数传递我们想要更新的字段,搜索键/值对“name”和“Darren Sanders ”,然后我们使用$set 选项(前面有一个美元符号)指定键(心率)和新值(88)。这将用新值覆盖初始值。
my_collection.update_one({"Name": "Darren Sanders"}, {"$set":{"Heart rate": 88}})
如你所见,我们可以将多层对象和数组嵌套在一起,从而嵌入数据。另一种选择是在单独的集合中分离出数据并链接到它。我们将着眼于嵌入和链接以及问题,以帮助您确定哪一个是最好的。
嵌入或链接数据
我们可以通过嵌入来嵌套数据。假设我们想要存储某个病人的一些医学测试结果。这可能包括一些血液检查结果和心电图/EKG 图像,用于心脏病发作的一些调查和一些血液检查,包括:
- 肌酸激酶(CK)
- 肌钙蛋白 I (TROP)
- 天冬氨酸氨基转移酶
我们可以从创建一个包含数组的名为“测试结果”的字段开始。
patient_record = {
"Hospital number": "3432543",
"Name": "Karen Baker",
"Age": 45,
"Sex": "F",
"Blood pressure": [{"sys": 126}, {"dia": 72}],
"Heart rate": 78,
"Test results" : []
}
在这个数组中,我们可以存储 ECG 的对象(图像文件的路径)和存储生化结果的另一个数组。
patient_record = {
"Hospital number": "3432543",
"Name": "Karen Baker",
"Age": 45,
"Sex": "F",
"Blood pressure": [{"sys": 126}, {"dia": 72}],
"Heart rate": 78,
"Test results" : [
{
"ECG": "\scans\ECGs\ecg00023.png"
},
{
"BIOCHEM": []
}
]
}
最后,我们可以将血液结果添加为键/值对:
patient_record = {
"Hospital number": "3432543",
"Name": "Karen Baker",
"Age": 45,
"Sex": "F",
"Blood pressure": [{"sys": 126}, {"dia": 72}],
"Heart rate": 78,
"Test results" : [
{
"ECG": "\scans\ECGs\ecg00023.png"
},
{
"BIOCHEM": [{"AST": 37}, {"CK": 180}, {"TROPT": 0.03}]
}
]
}
我们可以把这些写在同一行上,就像我们写血压一样,或者写在不同的行上,以增加可读性。
以这种方式嵌入数据的替代方法是链接到它。链接数据也称为引用。这包括将数据存储在不同的集合中,并通过 id 引用它。决定是否链接或嵌入数据取决于某些考虑因素,例如:
- 您需要多久访问一次嵌入的信息?
- 使用嵌入信息查询数据吗?
- 嵌入的数据会经常改变吗?
- 在没有嵌入数据的其他信息的情况下,您需要访问嵌入数据的频率是多少?
根据这些问题的答案,您可能需要链接到数据。考虑下面的例子。您可能希望存储一些关于给定患者开了什么药的信息。您可以嵌入这些信息,但是如果您还想存储更多关于药物的通用信息,该怎么办呢?在这里,您可以有一个单独的集合,其中包含您可以链接到的信息。
medication_data = [
{
"_id": ObjectId('60a3e4e5f463204490f70900'),
"Drug name": "Omeprazole",
"Type": "Proton pump inhibitor",
"Oral dose": "20mg once daily",
"IV dose": "40mg",
"Net price (GBP)": 4.29
},
{
"_id": ObjectId('60a3e4e5f463204490f70901'),
"Drug name": "Amitriptyline",
"Type": "Tricyclic antidepressant",
"Oral dose": "30–75mg daily",
"IV dose": "N/A",
"Net price (GBP)": 1.32
}
]
我们可以使用 id 和 DBRef 函数来引用另一个集合中的数据。例如:
from bson.dbref import DBRefpatient_records = [
{
"Hospital number": "9956734",
"Name": "Adam Blythe",
"Age": 55,
"Sex": "M",
"Prescribed medications": [
DBRef("medication_data", "60a3e4e5f463204490f70900"),
DBRef("medication_data", "60a3e4e5f463204490f70901")
]
},
{
"Hospital number": "4543673",
"Name": "Darren Sanders",
"Age": 34,
"Sex": "M",
"Prescribed medications": [
DBRef("diagnosis_data", "60a3e4e5f463204490f70901")
]
}
]
查询数据
有几种方法可以查询数据。所有的方法都使用了 find() 函数。可以提供一个查询,后跟您希望在表单中返回的一个或多个字段:
collection.find({ <query> }, { <field(s)> })
要查找单个条目,例如姓名为“Darren Sanders”的患者,我们可以使用查找功能并打印列表中的第一项:
pprint(my_collection.find({"Name": "Darren Sanders"})[0]
我们也可以使用一个循环来输出结果。我们还可以将查询存储在一个单独的变量中,首先将这个变量传递给 find 函数。当查询可能很复杂时,这很有用,因为它有助于提高代码的可读性:
query = {"Name": "Darren Sanders"}doc = my_collection.find(query)
for i in doc:
pprint(i)
最后,如果我们只想要一个结果,我们可以使用 find_one() 函数:
my_collection.find_one({"Name": "Darren Sanders"})
数据库的常见做法是根据特定标准查询数据子集。我们可以使用比较运算符来检索数据子集。例如,我们可以使用大于运算符($gt)来搜索心率大于 70 次/分钟的所有患者姓名。
for heart_rate in my_collection.find({"Heart rate": {"$gt": 70}}, {"Name"}):
pprint(heart_rate)
有许多这样的比较运算符可用,包括:

(图片由作者提供)
使用逻辑运算符可以进一步增强这一功能。例如,我们可以使用心率< 70 beats per minute, and who are aged above 20 years.
result = my_collection.find({
"$and" : [
{
"Heart rate": {"$lte": 70}
},
{
"Age": {"$gt": 20}
}
]
})for pt in result:
pprint(pt)
Logical operators include:

(Image by author)
You might be wondering how we find data that’s contained in arrays. This can be done by using a period (dot). For example you may recall that we stored the patients’ systolic and diastolic blood pressure like so:
"Blood pressure": [{"sys": 126}, {"dia": 72}]
We could query patients with a systolic (sys) blood pressure less than 140 mmHG (mm of mercury) like so:
for normal in my_collection.find({"Blood pressure.sys": {"$lt": 140}}):
pprint(normal)
Note that we reference the key “blood pressure” add a period (dot) and then the key inside the array, for example sys for systolic.
Working with existing data
One of the great things about MongoDB is that it is really straight forward to load JSON files and add them to collections. For example if we had some JSON data stored in a JSON file, we could use the json library to read in this data and add it to a MongoDB collection:
import jsonwith open('data_file.json') as f:
file_data = json.load(f)
my_collection.insert_many(file_data)
You wouldn’t want to output the entire contents of a database with hundreds of thousands of documents. To view the file and see the structure of the data, you may instead output the first n 文档来搜索患者。例如前 10 个文档。这可以通过使用限制()功能来实现。
for item in my_collection.find().limit(10):
pprint(item)
要检查集合中的文档数量,我们可以像这样使用 count_documents 函数:
my_collection.count_documents({})
同样,我们可以在这里添加一个查询来计算满足某些感兴趣的标准的所有文档。
聚合
通常,当处理数据时,我们不仅仅希望使用查询来提取数据的子集,而是希望从现有数据中产生新的信息。这通常包括进行各种计算,比如求某个值的平均值或总和。例如员工的平均工资。
让我们看一个简单的例子,使用一个包含餐馆数据细节的样本数据集(数据可以在这里找到:https://docs . atlas . MongoDB . com/sample-data/available-sample-datasets/)。
下面是一个示例文档:

(图片由作者提供)
你可以看到详细的餐厅地址,它是在哪个区,菜肴的类型,名称,身份证和详细的等级授予相关的分数。假设我们想要计算餐馆的平均分数。为此,我们可以使用聚合函数。
result = my_collection.aggregate(
[
{"$unwind": "$grades"},
{"$match”: {}},
{"$group": {"_id": "$name", "Avg grade": {"$avg": "$grades.score"}}}
]
)
我们将一个数组传递给聚合函数。\(unwind 参数用于解构 grades 数组,以便为每个元素输出一个文档。接下来,我们使用\)match 参数,包括所有内容(通过使用左括号和右括号)。我们可以通过提供附加标准来进一步过滤。接下来,我们使用\(group 参数对要应用计算的数据进行分组。最后,我们创建一个名为“Avg grade”的新关键字,并将\)avg (average)参数应用于引用分数后跟一个点和分数关键字的分数。
产生以下输出(为简洁起见,将其缩短):
{'Avg grade': 15.2, '_id': 'Red Star Restaurant'}
{'Avg grade': 13.0, '_id': 'Weather Up'}
{'Avg grade': 9.4, '_id': 'La Nueva Playitas'}
{'Avg grade': 13.0, '_id': “Marcella’S Pizzeria & Catering”}
{'Avg grade': 9.0, '_id': 'Hot Wok'}
{'Avg grade': 9.333333333333334, '_id': '99 Favor Taste'}
{'Avg grade': 18.0, '_id': 'Flavors Corner'}
{'Avg grade': 10.666666666666666, '_id': 'Corona Restaurant'}
{'Avg grade': 9.0, '_id': 'Mila Cafe'}
{'Avg grade': 8.0, '_id': 'Circle Line Manhattan'}
{'Avg grade': 15.6, '_id': “The Old Time Vincent’S”}
{'Avg grade': 10.833333333333334, '_id': 'Riko'}
{'Avg grade': 10.0, '_id': 'Fresh Tortillas'}
{'Avg grade': 10.333333333333334, '_id': 'Le Village'}
{'Avg grade': 13.2, '_id': 'Ruay Thai Restaurant'}
{'Avg grade': 12.0, '_id': 'Lechonera Don Pancholo'}
{'Avg grade': 11.0, '_id': 'Pepe Rosso Social'}
. . .
还有许多其他参数可用于常见计算,如\(sum、\)min、$max 等。
我们还可以根据需要添加额外的功能。例如,我们可能希望按升序或降序对返回的进行排序。我们可以简单地添加另一行,使用 sort 参数指定根据哪个字段进行排序。1(升序)或-1(降序)。
result = my_collection.aggregate(
[
{"$unwind": "$grades"},
{"$match": {}},
{"$group": {"_id": "$name", "Avg grade": {"$avg": "$grades.score"}}},
{"$sort": {"Avg grade": -1}}
]
)
不使用聚合函数进行排序的另一个选项是使用直接传入字段名的排序函数,例如按名称排序:
for item in my_collection.find().sort("name").limit(10):
pprint(item)
我们可以通过在要排序的字段后添加 1 或-1 来选择升序/降序:
for item in my_collection.find().sort("name", -1).limit(10):
pprint(item)
在软件项目和数据科学中使用 MongoDB
使用 JSON 格式的 MongoDB 的主要优势之一是它提供了与使用类似格式的编程语言的互操作性。这使得在应用程序中处理数据和从数据库中存储/检索数据几乎是无缝的。
将数据库集成到代码中的更好的方法是使用对象关系映射(ORM)之类的方法,或者在 MongoDB 中使用对象文档映射器(ODM)。这是通过将 Python(或其他一些语言)代码翻译成 MongoDB 语句来检索数据的。然后,这些数据被传递回 Python 对象。这样做的好处是确保您只需要使用一种语言(例如 Python)来访问和使用数据库。

(图片由作者提供)
做这件事的一个好库是 mongoengine 。在这里,我们导入库并连接到一个 Mongo 客户端,我们称之为 odm_patients 。
from mongoengine import *
connect('odm_patients')
下面的例子展示了我们如何创建一个 Python 类来建模一些数据,创建该类的几个实例并将其写入数据库。按照前面的例子,我们将创建一个类来存储关于病人的数据。
class Patient(Document):
patient_id = StringField(required=True)
name = StringField()
age = IntField()
sex = StringField(max_length=1)
heart_rate = IntField()
我们可以使用 Python 类创建一个对象来操作数据库。在这里,我们通过指定数据项是什么类型的字段来创建数据项。例如,可以使用 StringField() 函数和带有 IntField() 的整数来创建文本/字符串数据。还可以添加其他参数,例如字符串中的字符数量以及字段是否不能为 null/空。
我们现在可以用 Python 中的标准方式创建这个类的实例。在这里,我们可以创建一对名为 Maxine 和 Hamza 的患者。注意,我们在行尾添加了 save() 函数,将这些数据写入数据库。
maxine_patient = Patient(patient_id = "342453", name = "Maxine Smith", age = 47, sex = "F", heart_rate = 67).save()hamza_patient = Patient(patient_id = "543243", name = "Hamza Khan", age = 22, sex = "M", heart_rate = 73).save()
我们可以使用循环输出这些对象。要访问特定的字段,我们可以使用迭代器,一个点,然后是我们希望输出的字段。例如患者的姓名、id 和年龄。
for patient in Patient.objects:
print(patient.name, patient.patient_id, patient.age)
它产生以下输出:
Maxine Smith 342453 47
Hamza Khan 543243 22
除了将 Mongo 数据库集成到软件项目中,我们还可以将其用于研究和数据科学/分析任务。有一种简单的方法可以将 Mongo 数据库中的数据转换成表格形式,作为 Panda 的 dataframe 对象。首先,我们导入熊猫库。
import pandas as pd
接下来,我们使用标准查询选择所需的数据,例如,我们将检索 Bronx 区面包店的所有名称。接下来,我们将结果转换成列表数据结构。
extracted_data = my_collection.find({},{"borough": "Bronx", "cuisine": "Bakery", "name": 1})
bronx_bakeries = list(extracted_data)
最后,我们使用 from_dict 函数创建一个数据框,将字典转换成表格数据框:
pd.DataFrame.from_dict(bronx_bakeries)
这会产生以下输出:

(图片由作者提供)
总之,MongoDB 是一个强大的可伸缩数据库,当数据模式易于频繁更改时,它非常有用。这有助于轻松与现代软件系统集成,并且在分析 JSON 格式的数据(如一些移动应用程序数据或 Twitter 数据)时,也可以用作数据分析管道的一部分。MongoDB 是最受欢迎的 NoSQL 数据库之一,了解它是什么以及它是如何工作的对于软件工程师和数据科学家来说是必须的。
使用 Neo4j 图形数据库分析 Twitter 数据
收集 Twitter 数据,将其存储在图形数据库中,并使用图形算法进行分析。

图片作者。Neo4j 浏览器生成的图形。推特数据收集于 2021 年 2 月 19 日。地点是泰国。黄色圆圈代表一条推文。靠近中心的红色圆圈代表发布推文的用户。该用户获得最高的中心性分数。蓝色圆圈代表转发。蓝色圆圈周围的红色圆圈代表发布转发的用户。
我们将开发一个应用程序来收集 Twitter 数据,并将它们存储在 Neo4j 图形数据库中。然后,我们将使用一些图形查询、中心性算法和社区检测算法来分析数据。在本文中,我们将讨论:
- Neo4j 图形数据库
- Twitter 数据建模
- 收集 Twitter 数据
- 分析数据
Neo4j 图形数据库
图形数据库是 NoSQL 数据存储技术之一。有一些图形数据库实现。在本文中,我们将使用 Neo4j 图形数据库。以下是它的优点:
- Neo4j 社区版是开源和免费的。
- 易于安装和使用。
- 数据库驱动程序有几种语言版本,包括 Java 和 Python。
如果我们的读者对 Neo4j 图形数据库没有任何经验,我们建议从这里下载 Neo4j 桌面。然后,看一些教程。安装之后,为 Twitter 数据添加一个本地数据库。
Twitter 数据建模
在这一部分,我们将讨论如何在图形数据库中建模 Twitter 对象。一般规则是:
- 一个对象可以是一个节点。对象类型或类将是一个标签。
- 对象关联是一种关系。关联类型将是关系类型。
我们感兴趣的 Twitter 对象将是图形数据库中的节点。这些是标签:
- 自录音再现装置发出的高音
- 转发
- 用户
- 标签
这些对象之间的关系将是图形数据库关系。这些是关系:
- POST 代表用户发布推文。
- RT 表示用户发布了一条转发消息。
- 标签代表用户用标签来标记推文。
- REPLY 代表对另一条推文的回复。
- 引用是指引用另一条推文的推文。
- 提及表示提及用户的推文。
下图显示了节点标签和关系类型。

图片作者。它是由下面的 cypher 命令生成的。
我们已经使用 Neo4j 浏览器通过使用以下命令来生成图片:
Neo4j cypher 命令生成上面的模式图像
我们还需要在一些节点标签上创建约束,以确保我们不会创建重复的对象。每个 Twitter 对象都有一个唯一的 id。以下是命令:
Neo4j cypher 命令在节点标签上创建约束
收集 Twitter 数据
在开始分析之前,我们需要收集数据。我们将构建一个 Java 应用程序来检索推文、转发、用户、标签和相关数据。所有这些对象都是相关的。使用图模型对它们建模是很自然的。然后,我们将它们存储在图形数据库中。
TweetsSavior 类
下面的代码显示了 TweetsSavior 类。
“main”方法从命令行参数中获取参数。变量名是不言自明的。然后,创建 TweetData 对象。实例化流处理程序对象。最后,调用流处理程序对象的 run 方法。
TweetData 接口
这个接口是应用程序和存储 Twitter 对象的数据库之间的抽象层。有了这一层,将来我们可以很容易地用一种类型的数据库替换另一种类型的数据库。比如用 neo4j 数据库替换 SQL 数据库。这个接口有一个重要的方法。saveTweet 将处理并保存 Tweet 和相关对象到底层数据库。
TweetDataFactory 类
我们已经使用简单的工厂类创建了一个实现 TweetData 接口的对象。在这种情况下,工厂将实例化一个 TweetGraphData 类的对象。
TweetGraphData 类
以下代码显示了 TweetGraphData 类。它是 TweetData 接口的一个实现,将数据存储在一个图形数据库中。
saveTweet 方法创建一个可调用的任务,并将其提交给 executor 服务。当许多推文在短时间内到来时,利用 executor 服务是有益的。executor 服务将为我们管理线程和任务队列。
TweetGraphTask 类
这是一个可调用的任务,执行处理和保存 tweet 和相关对象的实际工作。代码依赖于:
- 不管这条微博是不是转发。
- 这条推文是否是对另一条推文的回复。
- 不管它是否引用了另一条推文。
- 这条推文是否有标签,或者是否提到了其他用户。
代码很长,但是很简单。它使用一些 Neo4j Cypher 查询来创建或更新图形节点。代码可以在这里找到。
StreamHandler 类
下面的代码显示了收集 tweet 数据的 StreamHandler 类。
我们有 run 方法作为我们的切入点,并且有主要的逻辑。它执行以下操作:
- 创建一个 Twitter 流对象。
- 当新的 tweet 到达时,它将调用 Twitter 流对象的 onStatus 方法。
- 我们的 onStatus 调用“Tweet data”对象的 saveTweet 方法,并传递 tweet 对象。
- 在 while 循环中,它每 15 分钟获取一次当前的 Twitter 趋势。然后,它使用 Twitter 趋势作为我们希望从 Twitter 流中获得的过滤关键字。
回顾一下这个应用程序,下面的序列图显示了我们的关键类之间的交互。

图片作者。序列图是由 Certiv Analytics 在 eclipse 上的序列插件生成的
我们把地点定在泰国,2021 年 2 月 19 日运行程序,时间是早上 8 点到第二天早上 8 点。我们得到了 159,276 条推文,3,078,281 条转发,11,047 个标签和 403,991 个用户。注意,这些并不是所有的用户,而是当天参与活动的用户。我们的读者须知,那天有一项对政府的不信任动议。Twitter 上的活动可能会比平时多一点。
所以,我们收集了大量的 Twitter 数据。让我们看看我们能用它做什么。
分析数据
我们使用 Cypher 语言查询 Neo4j 图形数据库。就像我们用 SQL 语言查询关系数据库一样。Cypher 只是一种不同的语言,因为图形数据库有不同的体系结构。我们的读者可以在这里查阅 Cypher 语言文档。
我们将从使用一些 Cypher 查询来分析数据开始。然后,我们将对我们的数据应用图中心算法。它应该能够识别网络中心的用户。最后,我们将尝试一种图社区检测算法。它根据用户的交互将用户分成不同的组。
追随者计数
我们想知道的一件重要事情是谁是有影响力的用户。这很简单,因为我们已经有了这样的属性。每个用户节点都有一些属性。“关注者计数”告诉用户有多少关注者。我们使用下面的查询来查找按关注者数量排序的前 10 名用户。下面显示了 Cypher 查询和结果。
让我们稍微讨论一下 Cypher 查询。
- 第一行是匹配子句。这意味着我们需要带有用户标签的节点。
- 第二行是 where 子句。这意味着我们需要具有特定属性的节点。
- 第三行是 where 子句的一部分。这意味着我们只需要关注者少于 3,700,000 的用户,因为关注者更多的用户可能是非本地用户。
- 第四行是 return 子句。它告诉我们需要什么属性作为回报。
- 最后一行告诉数据库按照追随者计数降序排列结果,并只返回特定数量的行。
因此,我们在数据库中找出最有影响力的用户。这些用户拥有数百万粉丝。
接下来我们想知道的是关注者计数的分布。我们认为很少用户有很多追随者。大多数用户只有几个追随者。让我们看看是真是假。因此,我们将绘制追随者计数直方图。我们将使用 JFreechart 库编写一个 Java 代码来绘制图表。Java 源文件如下:
这是结果直方图:

作者图片
在图表中,我们将最大关注人数限制为 400,000。此外,我们需要在频率轴上使用对数标度,以使图形看起来更好。否则,我们将只能看到第一个条形。第一个箱的频率比其余箱的频率大得多。(第一个箱频率是 400,577。第二箱频率是 1,207。)
从图表来看,很明显大多数用户只有几个追随者。很少有人有很多追随者。
接下来,我们将计算追随者计数的百分位数。在我们继续之前,我们需要安装 APOC 库。详情请看这篇文章。
密码查询和结果如下。
以下是百分位值:
- 50%的用户关注者少于 60 人。
- 75%的用户拥有不到 200 名关注者。
- 90%的用户拥有不到 536 个关注者。
- 99%的用户拥有不到 6,222 名关注者。
有趣的是,平均关注人数是 1374 人。然而,90%的人只有不到 536 个追随者。平均值没有意义,因为跟随者计数分布不是正态分布。它严重倾斜。
热门标签
接下来,我们将确定热门标签。我们使用下面的查询来完成。
这个查询比前一个稍微复杂一点。
- 第一行是匹配模式。这里,我们希望匹配标签、推文或转发以及用户。
- 第二行是 where 子句。这意味着模式中的变量 t 要么是 tweet,要么是 retweet。
- 第三条和第四条是退货条款。它告诉数据库返回 hashtag 文本、tweet 和 retweet 计数、用户计数和每个用户的 tweet 计数。
- 在最后一行,我们按照 tweet 计数降序排列结果。然后,取特定数量的顶行。
接下来,我们将制作热门标签的图表。我们将使用 Cypher query 和一些 Java 代码来实现它。这里的代码是。

作者图片
从结果中,我们注意到以下情况:
- 热门标签是政治、流行歌手/偶像或电视节目。
- 我们有每个用户的推文。它显示了用户平均产生多少条推文和转发。我们注意到,对于一些标签,每个用户的 tweets 很高。看起来这些用户做得很好,让他们的标签在 Twitter 上流行起来。
- 从我们的数据来看,每个用户发布超过十条推文的标签是流行歌手/偶像。
按时间推文量
这种分析是为了观察推文量在一天中的变化。高峰期是什么时候?对于每个 Tweet 或 Retweet 节点,都有一个存储创建日期时间的 createdAt 属性。我们需要将其转换为 Cypher 可以理解的类型,然后将其分解为年、月、日和小时。然后,计算每小时的推文和转发次数。
下面是要做的查询任务。
- 第一行是匹配节点。
- 第二行是 WHERE 子句,指定每个节点必须是 tweet 或 retweet。
- 第三行是 WITH 子句。我们调整时区,然后将时间戳转换为日期时间类型。
- 第四行是另一个 WITH 子句。它将日期时间值分解为年、月、日和小时。
- 第 5 行和第 6 行计算 tweet 和 retweet 节点。我们使用 case-when 语句来区分 tweets 和 retweets。
- 第 7 和第 8 个是 WHERE 子句。它只包括特定年份、月份、日期范围和时间的推文或转发。
- 第 9 行是退货条款。它返回年、月、日、小时、tweet 计数和 retweet 计数。
- 最后一行是排序结果。
我们使用查询结果来绘制 tweet 音量。这里是代码。图表如下。

作者图片
以下是我们的观察结果:
- 正如所料,转发量远远高于推文量。
- 转发量在 13:00–23:00 左右达到峰值并趋于平缓。
- 推文音量在 18:00-22:00 左右达到峰值并趋于平缓。
- 午夜后交易量减少,6 点左右又回升。
安装图形数据科学库
我们稍后将使用的中心性和社区检测算法是图形数据科学库的一部分。我们需要将它安装在图形数据库中。详情请看这篇文章。
用户投影
在我们继续之前,我们需要建立一个用户预测。这是一个内存中的图表,显示了一个用户如何连接到另一个用户。图形算法稍后会在上面运行。
我们确定了三种类型的用户连接:
- 用户转发、引用或回复推文。另一个用户发布了这条推文。
- 用户发布转发、引用或回复推文。那条推文提到了另一个用户。
- 一个用户发布了一条提到另一个用户的推文。
以下是创建投影图的命令。
- 第一行是对图形数据科学库中的过程的调用。
- 第二行是图形投影名称。我们稍后会提到它。
- 第 3 行指定了要包含在投影中的节点。我们取所有用户节点。
- 第 4-11 行定义了图表中包含的关系。有三种结合在一起的关系类型。
- 最后一行是选项。
中心
我们将使用中心算法来识别位于网络中心的用户。有一些算法可用。我们决定使用 PageRank 算法。该算法根据传入关系的数量来衡量每个节点的重要性。它还考虑了源节点的重要性。
我们使用下面的命令来运行 PageRank 算法。
- 第一行是对图形数据科学库中算法的调用。它还指定了算法将运行的投影。这是我们之前创建的投影。
- 第二行存储从算法到变量的输出。
- 第三和第四个指定返回值。
- 第 5 个告诉数据库按照 PageRank 分数对结果进行排序,只返回前 10 个用户。
根据中心性结果,以下是我们的观察结果:
- 和热门标签结果一样,最高分属于政治人物、新闻机构和流行偶像。
- 最高 PageRank 分数的用户是 MP。他在当天的不信任案中进行了辩论。
- 名单中十个有七个来自反对党。
- 列表中的用户具有较高的追随者计数。但没那么高。一个拥有不到 10k 的用户仍然进入了我们的十大名单。所以,要看当天的活动。
社区检测
社区检测算法评估节点是如何聚集或划分的。这个库有一些算法可以使用。我们决定使用卢万算法。下面是 Cypher 查询和结果。
- 第一行是对库中 Louvain 算法的调用。投影名称是一个参数。
- 第二行说明了输出、节点 id 和社区 id。
- 第三行使用 WITH 子句计算社区大小并将结果存储在变量中。
- 第四行是 WHERE 子句。我们只想要拥有超过 1000 名用户的社区。
- 第五行是返回子句。我们返回社区 id 和社区大小。注意,社区 id 只是一个数字,没有任何意义。
- 在最后一行,我们按照大小降序排列社区。
因此,我们确定了几十个社区,但我们对每个社区一无所知。我们可以做进一步的分析,看看每个社区都在发什么话题。下面的 Cypher 查询可以做到这一点。
- 第 1-5 行调用 Louvain 算法。它做的事情与前面的查询相同。
- 第 6-9 行是查询的另一部分。它会识别每个社区的标签或被提及的用户。
- 第 10-13 个提取标签或屏幕名称
- 第 14 列指定了我们想要的结果列。我们也只返回每个社区的前 5 个标签或提到的昵称。
- 在最后一行,我们按照大小降序排列社区。
让我们讨论一下结果。
- 最大的社区拥有 230,000+用户。其中三家拥有超过 10,000 名用户。
- 每个社区讨论政治、流行偶像或电视节目。
- 政治是那天的热门话题。一些社区的用户除了偶像标签之外还讨论政治。
图形数据库的利弊
在使用 Neo4j 图表数据库进行 Twitter 分析后,我们发现了这些优点和缺点。
优点:
- Cypher query 比 SQL query 可读性更强、更紧凑,尤其是在有关系的情况下。
- Neo4j 图形数据库有一些图形算法可供使用。
缺点:
- Neo4j 数据库是一个比较新的产品。可用的工具不多。
从性能上来说,已经够快了。凭借 300 多万个节点和 600 多万个关系,我们可以在我们老化的 MacBook Pro 2014 年中期模型上在几秒钟内运行大多数查询。
结论
在本文中,我们讨论开发一个 Java 应用程序来收集 Twitter 数据。然后,存储到 Neo4j 图形数据库中。我们以几种不同的方式分析数据:
- 我们用追随者数量的最高值来识别用户。绘制跟踪计数直方图。
- 我们确定热门标签。
- 我们想象推特的流量在一天中是如何变化的。
- 我们找出位于网络中心的顶级用户。
- 最后,我们根据用户的活动将他们划分到社区中。
这是我们分析数据的几个样本。我们可以做得更多。在图形数据科学库中,我们还没有研究的其他算法是可用的。我们希望我们的读者了解我们可以用图形数据库做什么。
用神经网络求解常微分方程

约翰·莫塞斯·鲍恩在 Unsplash 上拍摄的照片
如何使用神经网络解决常微分方程的快速指南(包括张量流实现)
使用神经网络解决常微分方程的想法首先由 Lagaris 等人提出。其背后的想法基本上是训练神经网络来满足微分方程所需的条件。换句话说,我们需要找到一个导数满足 ODE 条件的函数。本文将介绍这个概念的数学基础,然后我们将使用 TensorFlow 实现它。所有的内容都可以在谷歌协作笔记本这里中找到。
数学基础
假设我们有一个颂歌系统,由下式给出:

因此,我们可以将微分运算理解为具有已知初始条件 u (0)= u 0 的域 t 上的函数。众所周知,神经网络被称为通用逼近器。我们将利用神经网络的这一特性,用它们来逼近给定常微分方程的解:

同样,我们可能同意 NN ( t )的导数会给我们一个类似的等式:

所以,如果 NN ( t )真的接近于真解,那么我们可以说它的导数也接近于真解的导数,即:

因此,我们可以把这个条件转化为我们的损失函数。我们有给定的导数函数 f ( u , t ),我们可以计算每一步的神经网络导数NN'(t)。这激发了下面的损失函数(这是两个值的均方误差):

你可能还记得初始条件,我们仍然需要处理它。最直接的方法是在成本函数中加入一个初始条件项。它看起来像这样:

虽然这可行,但可能不是最好的方法。我们都知道损失函数对神经网络训练的至关重要性,我们也知道这个函数的项数将直接影响我们训练的稳定性。损失函数中的更多项(通常)意味着不稳定的训练。为了避免这种情况,我们可以更有效地将初始条件编码到损失中。让我们定义一个新函数并使用它,而不是直接使用神经网络:

很容易看出, g ( t )将总是满足初始条件,因为 g (0)将导致 tNN ( t )=0,只留下表达式上的初始条件。这样就可以训练 g ( t )来满足 ODE 系统而不是神经网络。然后,它会自动成为导函数的一个解。我们可以将这个新的想法融入我们的损失函数:

Python 实现
我们将使用 TensorFlow 库在 python 中实现所描述的方法。为了更好地理解该方法,我们将使用一个低层次的设计,避免库提供的许多可能的优化。目前,我们的重点是清楚地理解和实现 ODE-solver 神经网络。为此,我们也将选择一首简单的颂歌:

我们可以通过整合解决方案的双方来轻松解决这个问题,导致u+C=x+C,在拟合 C 以满足初始条件后,我们有 u = x +1。然而,让我们尝试使用神经网络来解决它,而不是解析地解决它。

作者图片
对于此示例,我们将创建一个具有两个隐藏层、sigmoid 激活函数和梯度下降优化算法的 MLP 神经网络。也可以使用其他拓扑,这只是一个例子。我们鼓励你尝试这种方法的不同途径。
定义变量
f0 = 1
inf_s = np.sqrt(np.finfo(np.float32).eps)
learning_rate = 0.01
training_steps = 5000
batch_size = 100
display_step = 500# Network Parameters
n_input = 1 # input layer number of neurons
n_hidden_1 = 32 # 1st layer number of neurons
n_hidden_2 = 32 # 2nd layer number of neurons
n_output = 1 # output layer number of neuronsweights = {
'h1': tf.Variable(tf.random.normal([n_input, n_hidden_1])),
'h2': tf.Variable(tf.random.normal([n_hidden_1, n_hidden_2])),
'out': tf.Variable(tf.random.normal([n_hidden_2, n_output]))
}biases = {
'b1': tf.Variable(tf.random.normal([n_hidden_1])),
'b2': tf.Variable(tf.random.normal([n_hidden_2])),
'out': tf.Variable(tf.random.normal([n_output]))
}# Stochastic gradient descent optimizer.
optimizer = tf.optimizers.SGD(learning_rate)
定义模型和损失函数
# Create model
def multilayer_perceptron(x):
x = np.array([[[x]]], dtype='float32')
layer_1 = tf.add(tf.matmul(x, weights['h1']), biases['b1'])
layer_1 = tf.nn.sigmoid(layer_1)
layer_2 = tf.add(tf.matmul(layer_1, weights['h2']), biases['b2'])
layer_2 = tf.nn.sigmoid(layer_2)
output = tf.matmul(layer_2, weights['out']) + biases['out']
return output# Universal Approximator
def g(x):
return x * multilayer_perceptron(x) + f0# Given EDO
def f(x):
return 2*x# Custom loss function to approximate the derivatives
def custom_loss():
summation = []
for x in np.linspace(-1,1,10):
dNN = (g(x+inf_s)-g(x))/inf_s
summation.append((dNN - f(x))**2)
return tf.sqrt(tf.reduce_mean(tf.abs(summation)))
注意,我们在损失函数中没有任何参数。通常,损失函数会将预测值与实际数据进行比较。在我们的例子中,我们不需要数据点。
因为我们知道支配这个模型的 ODE 函数,所以我们可以计算每个点 x 的期望值。您可能还注意到,我们总是使用区间 {-1,1}中的 10 个点来计算损失。对于所有可能的函数来说,这可能还不够,但是考虑到我们例子的简单性,我想我们会做得很好。
神经网络导数由代码中的变量 dNN 表示。我们只是使用差异化定义:

列车功能
def train_step():
with tf.GradientTape() as tape:
loss = custom_loss()
trainable_variables=list(weights.values())+list(biases.values())
gradients = tape.gradient(loss, trainable_variables)
optimizer.apply_gradients(zip(gradients, trainable_variables))# Training the Model:
for i in range(training_steps):
train_step()
if i % display_step == 0:
print("loss: %f " % (custom_loss()))
标绘结果
# True Solution (found analitically)
def true_solution(x):
return x**2 + 1X = np.linspace(0, 1, 100)
result = []
for i in X:
result.append(g(i).numpy()[0][0][0])
S = true_solution(X)plt.plot(X, result)
plt.plot(X, S)
plt.show()
这将给我们带来以下情节:

作者图片
这是一个非常近似的结果,如果我们还记得没有用于训练的数据集的话。如果我们使用更多的搭配点来计算损失函数,或者如果我们保持训练时间长一点,可能会更好。我们邀请读者尝试这些代码并进行这些调整,甚至尝试其他代码。你可以在谷歌协作笔记本这里找到这篇文章的所有内容。玩得开心!谢谢你的阅读。
为数据科学使用新的吉拉工作管理项目
吉拉简单的新功能如何支持数据分析团队

吉拉工作管理是专为营销,人力资源,财务和其他业务团队。因此,您可以根据自己的需求与其他人一起工作和协作。我真正喜欢这个新功能的是简单的设计,以及以前没有的日历、列表和时间线等工具现在也可以使用了。如果你没有账户,你可以在吉拉云中创建一个用于测试的账户,然后和最多十个同事一起试用。
日历
特别是对于 bug 修复或上线约会,日历视图非常方便。每个人都可以很容易地跟踪重要的待办事项并安排它们。

吉拉日历—作者图片
时间表/路线图
类似于吉拉的新一代项目,你也可以按照时间表或路线图来规划任务。我已经发现这个功能非常有用,例如在管理演示中显示未来几天、几周和几个月要做的事情。而且对于自己粗略规划的项目来说,这个功能也是极有帮助的。

为您的任务绘制时间线—按作者排序的图像
形式
使用新的用于构建块的拖放式表单,在几秒钟内为任何项目创建有用的表单[1]。例如,在这里,我将它用于客户仪表板请求。这样,内部和外部数据用户可以提交想法和请求。通过指定表单,您已经可以尝试在这里建立定义良好的用户故事。

仪表板表单—按作者分类的图像
其他有价值的功能
我还发现列表视图很有用。在这里,您可以看到任何和所有任务及其各自的优先级。不幸的是,清单在这里不是内置的,但是我发现了有用的附加软件智能清单,用它你可以,例如,代表验收标准【2】。像报告这样的常用功能,例如评估 sprints,仍然可用。
摘要
在我看来,吉拉新的工作管理功能对于快速轻松地绘制数据分析项目非常有用。特别是,如果我想避免项目管理的开销,而是想在小团队中工作。除了项目之外,这些功能对于产品的进一步开发也非常有用,例如,通过表单,客户可以提交请求和想法。我最喜欢的功能是时间线/路线图视图,我喜欢用它来进行管理演示。
GIPHY 的 GIF 图
资料来源和进一步阅读
[1]吉拉,工作管理 (2021)
[2] Atlassian Marketplace,适用于吉拉的智能清单。企业 (2021)
使用自然语言处理识别药物不良事件
制药和医疗保健公司如何利用 NLP 自动检测非结构化文本中的 ade

莎伦·麦卡琴在 Unsplash 上的照片
这是怎么回事?
药物不良事件 (ADE)定义为患者因接触药物而遭受的伤害。大量关于药物相关安全性问题(如不良反应)的信息发布在医学病例报告中,由于其非结构化的性质,这些信息通常只能由人类读者来研究。
在本教程中,我们将训练一个自然语言处理(NLP)模型来识别给定文本中的 ade。我们将使用来自拥抱面部数据集中枢的 ADE 数据集来教导模型 ADE 相关和非 ADE 相关文本之间的区别。我们将使用拥抱脸和亚马逊 SageMaker 来训练和部署模型,并用我们自己的短语进行测试。
为什么这很重要?
根据疾病预防和健康促进办公室的数据,ade 具有严重的影响:在住院环境中,ade 约占所有医院不良事件的三分之一,每年影响约 200 万次住院,并延长住院时间 1.7 至 4.6 天。在门诊环境中,ade 包括超过 350 万次医生诊室就诊,估计有 100 万次急诊就诊,以及大约 125,000 次住院。
能够使用人工智能模型自动标记非结构化文本数据中的 ADE 相关短语有助于预防 ADE,并带来更安全、更高质量的医疗保健服务、更低的医疗保健成本、更知情和更积极的消费者以及更好的健康结果。
数据
对于本教程,我们将使用 ADE 语料库 V2 数据集。该数据集于 2021 年 5 月发表在拥抱脸网站上,并公开发布。它由 3 个子集组成,(1)ADE 分类语料库,(2)药物 ADE 关系语料库,和(3)药物剂量关系语料库。对于本教程,我们将专注于分类子集,它根据句子是否与 ADE 相关(标签=1)或不相关(标签=0)对句子进行分类。数据集看起来是这样的:

作者图片
行动(或活动、袭击)计划
训练 NLP 模型以识别 ADE 相关短语需要几个步骤:首先,我们需要下载并处理数据集,这导致单独的训练、验证和测试数据集。然后,我们用训练和验证数据集训练该模型。之后,我们将它部署到一个端点,在那里我们可以测试模型。下图说明了步骤的顺序:

作者图片
本教程的所有代码都可以在这个 Github repo 中找到。repo 被分成 4 个笔记本,它们反映了上面概述的步骤:1_data_prep.ipynb(数据处理),2_inspect_data_optional.ipynb(这使我们可以在训练之前查看已处理的数据),3_train.ipynb(模型训练),4_deploy.ipynb(模型部署和测试)。
处理数据
为了处理数据,我们将利用亚马逊 SageMaker 上的拥抱面部处理工作。这允许我们启动一个执行处理脚本的计算实例。工作完成后,计算实例将自动关闭,我们只需支付处理数据的时间。
让我们看看处理脚本的一些关键部分:
处理脚本做一些事情:下载数据,删除重复数据,重组和分割数据,标记数据,并将其保存到 S3。
洗牌和分割数据
有许多方法可以加载数据集、删除重复项并将其分成几个部分。在这个脚本中,我们利用 Pandas 和 Numpy 库来完成这个任务,但是数据集类也有方法来过滤和分割数据集。
将数据集混洗并分成三部分的一种方法是使用 Numpy 的 split()函数。它允许我们在定义的阈值分割数据集。例如,要将一个数据集混洗并拆分成 70%的训练数据、20%的验证数据和 10%的测试数据,我们可以使用这行代码:
将数据符号化
为了使数据集为模型训练做好准备,我们将在将数据集保存到 S3 之前对其进行标记化:
训练模型
在 SageMaker 上训练拥抱脸模型很简单,这要归功于拥抱脸和 AWS 之间的合作关系。Github repo 中的培训笔记本遵循了如何培训 NLP 模型的最佳实践,并使用 SageMaker 培训作业来旋转短暂的计算实例以培训模型。这些计算实例仅用于训练模型,并在训练完成后立即拆除。该模型保存在 S3 上,可以从那里下载并在任何地方托管该模型:

作者图片
部署模型
在本教程中,我们将通过获取我们刚刚训练的模型并像这样将其部署在 S3 上来简化我们的生活(参见 Github repo 中的部署笔记本):
这将创建一个 SageMaker 端点,然后我们可以通过 SDK 与之交互。这使得我们可以很容易地测试和摆弄这个模型。
测试模型
一旦部署了模型,我们就可以用测试数据(或一些编造的句子)来测试它:

作者图片
结论
在本教程中,我们训练了一个 NLP 模型来识别给定短语中的药物不良事件(ade)。我们使用一个带注释的语料库来训练 NLP 模型,并将其部署到 SageMaker 端点,该语料库旨在支持从医学病例报告中提取有关药物相关不良反应的信息。接下来的步骤可能包括使用专用评估脚本进行自动模型评估,并在 Hugging Face 的模型中心发布模型。
引用
本教程使用的数据集是本文中描述的工作的产物:
Gurulingappa 等人,《支持药物副作用信息提取的基准语料库》,JBI,2012 年。
http://www . science direct . com/science/article/pii/s 1532046412000615,DOI:https://doi.org/10.1016/j.jbi.2012.04.008
利用 OpenAI 的 CLIP 搜索外观设计专利
使用自然语言搜索和查找自 2002 年以来进入公共领域的六万多项工业设计

过期的美国外观设计专利样本,来源:美国专利商标局,图片由作者提供
每个人都知道利用专利来保护新发明。除非你是专利律师,否则你可能只知道三种主要类型中的一种,即实用专利。然而,还有另外两种类型,设计和工厂专利。目前,已授权的实用专利超过 1000 万项,设计专利 91 万项,植物专利近 3.2 万项。
如果你猜测植物专利是针对植物新品种的,那你就对了!但是什么是外观设计专利呢?这是美国专利商标局的定义:
专利法规定授予任何人为制造品发明任何新的和非显而易见的装饰性设计的人外观设计专利。外观设计专利只保护物品的外观,而不保护其结构或功能特征。授予外观设计专利的相关程序与授予其他专利的相关程序相同,但有一些不同……”—USPTO[1]
实用专利和外观设计专利的区别之一是期限,即所有者对专利项目拥有垄断权的时间。目前,实用专利的默认期限是自专利申请之日起 20 年。外观设计专利的默认期限是自专利被授予之日起 15 年。一旦期限届满,或者如果所有者提前放弃专利,作品就进入了公共领域。到那时,任何人都可以使用该设计,而无需获得过期专利所有者的许可。在 91 万项外观设计专利中,超过一半属于公共领域,可以免费使用。
概观
在本文中,我将向您展示如何使用 OpenAI 的新剪辑编码器对自 2002 年以来一直处于公共领域的超过 67,000 项设计专利执行基于图像和文本的语义搜索。

作者图表
该系统基于 CLIP,这是 OpenAI 的一个开源模型,可以对文本和图像进行语义搜索。该模型有两个功能,将文本和图像编码成“嵌入”,即代表原始数据要点的数字串。剪辑模型在 4000 万对带有文本标签的图像上进行预训练[2],使得从图像编码的嵌入将类似于从文本标签编码的嵌入。
我编写了一个 Python 程序,从 USPTO 下载五年的每周专利公报,并解析文件以提取设计专利名称和相应的图像。然后,我分别通过剪辑文本和图像编码器运行所有的标题和图像,并将结果保存为两组嵌入。
要执行搜索,首先要输入一个简短的查询。该系统通过剪辑文本编码器运行查询,并比较结果嵌入,以使用外观设计专利的文本和图像嵌入来找到最佳匹配。系统显示从索引数组中获得的顶部结果的相应标题和图像。
例如,如果你搜索“折叠蒲团沙发”,下面是前五个结果:

基于片段的语义搜索结果,来源:USPTO
在下面的部分中,我将讨论设计专利,并更详细地描述语义搜索过程。这里有谷歌实验室,所以你可以自己试试。


乔治·布鲁斯,图片来源:wikimedia.org,和他的双小异食癖手稿,图片来源:乔治·布鲁斯的儿子&公司。这两个图片都属于公共领域。
外观设计专利
正如我上面提到的,外观设计专利保护的是外观,而不是发明的功能方面。第一项外观设计专利于 1842 年 11 月 9 日授予纽约的乔治·布鲁斯,他发明了一种叫做双小十二点活字的新字体。
专利局将每一项设计专利分为 33 类[4],如下所列。(简要说明:乍一看,似乎有 35 个类别,D1-D34 加上 D99 杂项。但奇怪的是,类别编号 D31 和 D33 从列表中消失了。也许 USPTO 对时光机和永动机的花饰有秘密分类?😃)

外观设计专利类别,来源:USPTO
自 20 世纪 60 年代以来,美国专利商标局每年授予的外观设计专利数量呈指数增长。2019 年,有近 35,000 项设计专利获得授权。

图片由作者提供,数据来源: USPTO
USPTO 每周都会发布最新的在线专利公报,列出最近授予的实用、设计和植物专利[4]。专利公报可以在这里批量下载,https://bulkdata.uspto.gov/。这个网站有从 2002 年 7 月到今天的公报。
对于这个项目,我只下载和处理了已经过期的设计专利,所以我抓取了从 2002 年 7 月到 2007 年 2 月的公报,产生了 67,578 个设计专利。
外观设计专利的一个有趣的方面是使用“虚线”来显示所描绘的物体的元素,这些元素不被视为外观设计专利的一部分[5]。
虚线公开应理解为仅用于说明目的,并不构成所要求保护的设计的一部分。不属于要求保护的设计的一部分,但被认为是显示使用该设计的环境所必需的结构,可在图样中用虚线表示—美国专利商标局

情妇玛侬·贝利和马丁·w·f·迪恩,来源:美国专利商标局
在上面的例子中,你可以看到带有点划线图案的虚线不包括在专利设计中,但鞋顶的缝线包括在内。请注意,所有行都包含在提供给剪辑图像编码器的图像中。
外观设计专利的预处理
我编写了一些 Python 代码来下载批量专利公报,解压缩它们,并遍历 HTML 页面来查找设计专利。它们很容易找到,因为外观设计专利以前缀“USD”开头。例如,以下是 HML 文档和相应 PNG 图像的路径:
gazettes/1314-1/OG/html/1314-1/**USD**0534338-20070102.html
gazettes/1314-1/OG/html/1314-1/**USD**0534338-20070102.png
在 HTML 文档中,我获取专利标题并将其保存到文本文件中。我还将图像调整大小并填充为 224x224 像素,这是剪辑图像编码器使用的大小。完整的源代码可以在这里找到。

使用剪辑创建嵌入,图片作者
使用剪辑创建文本和图像嵌入
对文本和图像进行预处理后,创建要搜索的嵌入内容就相当容易了。OpenAI 已经发布了 CLIP 的预训练模型。下载他们的模型后,只需要几行 Python 代码就可以生成嵌入。每次嵌入都是 512 个数字的列表。你可以在这里看到源代码是。

美国专利局,大约 1925 年,来源:美国国会图书馆,公共领域
搜索外观设计专利
现在我们已经有了超过 67,000 项设计专利的文本和图像嵌入,运行文本搜索变得又快又容易。
搜索从输入查询文本开始,例如,“弯曲的水龙头”。然后,系统使用剪辑文本编码器为查询创建文本嵌入。您可以选择三种搜索方法中的一种。
1。文本到文本搜索— 这将查询的嵌入与外观设计专利标题的文本嵌入列表进行比较,并返回最佳结果。

作者图表
2。文本到图像搜索— 将查询嵌入与图像嵌入列表进行比较,并返回最佳结果。

作者图表
3。text to combined text/image search—这将复制查询嵌入,并将组合对与组合的文本和图像嵌入列表进行比较,并返回最佳结果。

作者图表
注意,使用文本和图像嵌入进行语义搜索并不新鲜。例如,您可以阅读这些论文中的最新进展,郭晓晓等人的“基于对话的交互式图像检索”[6]和等人的“通过探索高阶注意力和分心进行视觉语义匹配”[7]。
讨论
在对该系统进行试验后,我发现使用文本和图像的组合嵌入通常会产生最好的结果。外观设计专利的说明文字都很高级,像“男式长靴”。它们通常不包含描述任何关键特征的文本。然而,图像嵌入经常会找到“错误的”对象,而这个对象恰好看起来像被搜索的对象。例如,如果您搜索“体育奖杯”,它可能会显示一个烛台。但是图像嵌入会捕捉设计的一些关键方面,比如上面的“弯曲水龙头”例子。将两种嵌入结合起来进行搜索通常是两全其美的。文本嵌入将搜索限制在查询的范围内,而图像嵌入通过识别关键特征来帮助改进搜索。
请查看下面的附录,查看一些示例搜索结果。
未来的工作
有可能训练一个生成性对抗网络(GAN)来基于来自 USPTO 的图像生成新的设计。这类似于 OpenAI 对 DALL E 系统所做的,它使用一个可变的自动编码器从文本中生成新的图像[8]。
源代码
这个项目的所有源代码都可以在 GitHub 上获得。图像文件和标签可在 Kaggle 上获得。您可以使用这个 Google Colab 来试验代码。这些源在 CC BY-SA 许可下发布。

归属共享相似
感谢
我要感谢詹尼弗·林和奥利弗·斯特里普尔对这个项目的帮助。
参考
[1]美国专利商标局,“关于专利的一般信息”,2015 年 10 月 15 日,https://www.uspto.gov/patents/basics#heading-30
[2] A .拉德福德,J. W .金,c .哈勒西,a .拉梅什,g .戈,s .阿加瓦尔,g .萨斯特里,a .阿斯克尔,p .米什金,j .克拉克等人,《从自然语言监督中学习可转移的视觉模型》,2021 年 1 月 5 日,https://cdn . open ai . com/papers/Learning _ Transferable _ Visual _ Models _ From _ Natural _ Language _ Supervision . pdf
[3] G. Quinn,IP Watchdog,“第一项外观设计专利”,2008 年 11 月 6 日,https://www . IP Watchdog . com/2008/11/06/The-First-Design-Patent-2/
[4]美国专利商标局,“官方公报”,https://www . USPTO . gov/learning-and-resources/Official-Gazette
[5] USPTO,《外观设计专利申请指南》,https://www . USPTO . gov/patents/basics/types-Patent-applications/Design-Patent-Application-Guide # broken
[6] X .郭,h .吴,y .程,s .雷尼,g .特索罗,R. S .费里斯,“基于对话的交互式图像检索”,2018 年 12 月 20 日,
[7] Y .李,d .张,Y. Mu3,“探索高阶注意和注意力分散的视觉-语义匹配”,2020 年 6 月 13 日,https://open access . the CVF . com/content _ CVPR _ 2020/papers/Li _ Visual-Semantic _ Matching _ by _ Exploring _ 高阶注意 _ and _ 注意力分散 _CVPR_2020_paper.pdf
[8] A .拉梅什,m .巴甫洛夫,g .高,S.t .格雷,《达勒:从文本中创造图像》,2021 年 1 月 5 日,https://openai.com/blog/dall-e
附录-搜索结果
鸡尾酒摇壶

组合搜索“不锈钢鸡尾酒调酒器”,作者图片,来源:USPTO
发刷

综合搜索“带刷毛的发刷”,作者图片,来源:USPTO
躺椅

组合搜索“贵妃椅”,作者图片,来源:USPTO
为了无限制地访问 Medium 上的所有文章,成为的会员,每月 5 美元。非会员每月只能看三个锁定的故事。
使用 OrgChartJS 来可视化我的家谱中的 8 代人——160 多个亲戚

莱昂·保罗克霍夫在 Unsplash 上的照片
将编码技能应用到对个人有意义的项目中。涵盖的技术:OrgChartJS、JavaScript/React、MongoDB 和 Heroku。
并不是每天都有人能够将他们的编码技能应用到对个人有意义的项目中。今年 1 月,我就这么做了,我利用我的网络开发技能建立了一个应用程序/网站,直观地展示了我家 160 多人 8 代的历史,一直追溯到 19 世纪晚期!在这篇文章中,我们将讨论我的项目的动机,我如何选择工具,开发过程,先睹为快最终结果,以及我接下来要做什么来推进项目。
那么,为什么要建立我自己的家谱呢?

凯文·杰瑞特在 Unsplash 上拍摄的照片
- 记录我的过去、现在和未来— 作为一个津巴布韦的小男孩,我总是对家族历史着迷,对那些在我之前的人充满好奇。他们面临哪些挑战?是什么让他们微笑或者相爱?了解你的家族史是一种特权,它给我一种不同于我经历过的任何事情的根植感。有人说,如果你不知道你从哪里来,就很难对你要去哪里有一个清晰的愿景!
- 与我的家人分享——这份家族历史应该由最关心它的人——我的家人和亲戚——所有 167 个人分享、构建和维护!这是一个连接点,把它想象成我们自己家庭的社交媒体。在一个全球化的世界里,我们都分散在津巴布韦各地,散居各地,我有一种感觉,我们的后代将感谢我们保持这一信息的跟踪,尽管我们之间的物理距离。
- 当前的替代方案不充分— 我对当前大品牌家族史、家谱、祖先网站和应用程序的研究揭示了一个很大的缺口—这个数据库中没有任何非洲人的记录。我的家人不是乘坐五月花号来美国的,所以他们的记录不会在大型网站上被捕获,任何搜索都是徒劳的。从数据收集的角度来看,目前的市场选择对我来说毫无用处。
- 保护我们的隐私— 这是大事!构建自己的应用程序可以让你控制数据并保证其安全——尤其是因为家庭细节是你可以放在那里的一些最敏感的内容。我不知道所有现有的商业应用程序如何处理我的数据,所以我宁愿避开并实现我自己的解决方案。
- 提升我的技能— 最后,我学到了很多自己从头开始研究和实现初稿的知识。但是最终能够使用和理解像 OrgChartJS 这样的实用程序库向我展示了什么是可能的,以及我如何能够利用它而不用在一个项目上花费数周时间。在故障排除过程中,我也更加熟悉了 javascript 数据结构和其他特性。
为工作选择正确的工具——组织图表
我使用 JavaScript 已经 4 年多了,最近,我学会了使用 React 库。所以我的第一次尝试是从头开始构建一个简单的版本,但它在视觉上并不令人满意,而且要花很多时间来构建我想要的所有功能,如搜索、缩放、树枝和树叶之间的无缝连接。我还尝试了其他具有层次化视觉效果的图表库,但它们都不太符合要求。不是每个项目都需要你从头开始——为了尽可能高效地执行,你需要为工作选择合适的工具。所以我最终选择了 Balkan 的 OrgChartJS 库,它有更好的视觉效果和即插即用的代码。

来自https://balkan.app/?aspxerrorpath=/O的分层视觉样本
项目开发过程
该项目有 4 个主要部分:数据采集、前端、后端和托管/部署。
- 数据收集 —这包括联系我所有的亲戚,为我提供信息以填补空白。我们从家庭的每个分支中选出一个代表,通常是长子,与这个代表协调以获得整个分支的信息会更有效。
- 前端/用户体验 —我使用 JavaScript 和 React 来设置项目(使用可信的 create-react-app 实用程序),然后从https://balkangraph.com/js/latest/OrgChart.js下载 OrgChart.js 文件,将它与我的 main.js 文件一起放在我的 public/scripts 文件夹中。下面是我的文件夹结构是如何设置的:(i) archive —包含旧的代码文件。(ii) 控制器 —包含具有用户登录和注销功能的文件,(iii) 模型 —包含与数据库接口的用户功能,即 CRUD 和用户数据的任何验证和清理,(iv) 公共 —图像、脚本和样式文件夹。脚本文件夹有 orgchart.js 和 main.js 文件,(v) 视图 —包含。主页和树页面的 js 文件。

我的家谱项目的文件夹结构
2。后端/数据管理— 我使用 MongoDB 来处理我的用户名逻辑。我们只需要一套整个家庭的凭证,因为我只期望定期流量。对于家谱数据,我将它们作为常规对象存储在 main.js 文件中。这不是最佳实践,但对这个 MVP 版本来说是可行的。关于设置对象和链接到 OrgChartJS 函数的入门演示,请参见这里:https://balkan.app/OrgChartJS-Demos/。
3。托管/部署— 我使用 Heroku 进行托管/部署。它又快又简单,而且免费!
现在先睹为快最后的结果!
决赛成绩
1.登录页面

带有家庭住宅旋转图像的登录页面
不算太坏,它完成了任务!
2.家谱页面

家谱页面和叶节点弹出窗口—部分,为隐私而编辑
我对结果很满意,所以我制作了一个私人的 YouTube 教程视频,和证书一起发给了我的大家庭。他们都喜欢提供的所有功能,例如搜索、缩放、叶节点弹出等。
后续步骤
我们从概念上了解了如何使用 OrgChartJS、Javascript/React、MongoDB 和在 Heroku 上部署来开发私有家谱。我从这次经历中最大的收获是如何为工作选择正确的工具——作为一名开发人员并不意味着你必须从头开始“开发”一切。只要您理解了基本原理,您就可以继续下去并利用那里的实用程序库。我的家人和亲戚可以享受探索他们的祖先。在未来,我计划增加一些功能,比如(i) 上传照片、(ii) 出生或死亡日期、(iii) 地址、(iv) 简历等。
请分享这篇文章,并在下面写下你对这种方法的看法,或者如果你有任何问题。这对我来说是一次很好的学习经历,我有一个很酷的、有意义的产品可以展示。
编码快乐!
通过神游在 Spark 上使用 Pandera 进行数据验证
本教程将展示如何用赋格将熊猫库引入 Spark 和 Dask。在这个例子中,我们将使用 Spark 上的 Pandera 数据验证库。

数据有效性
数据验证是进行适当的检查,以确保数据符合我们期望的格式和规范。随着数据管道变得越来越互联,无意中破坏其他管道的变化的机会也增加了。验证用于保证上游的更改不会破坏下游数据操作的完整性。常见的数据验证模式包括检查空值或检查数据框形状,以确保转换不会丢失任何记录。其他经常使用的操作是检查列的存在和模式。使用数据验证可以避免数据流程的无声失败,在这种情况下,一切都可以成功运行,但提供的结果不准确。
数据验证可以放在数据管道的开始,以确保任何转换顺利进行,也可以放在最后,以确保在输出提交到数据库之前一切正常。这就是可以使用像潘德拉这样的工具的地方。在这篇文章中,我们将制作一个小熊猫数据框来展示例子。有三列,州、城市和价格。

样本数据—每个位置的价格
潘德拉
Pandera 是一个轻量级的数据验证框架,有很多内置的验证器来验证 DataFrame 模式和值。当验证失败时,它提供信息性错误,并且它对已经编写的代码也是非侵入性的,因为decorator可以与其他函数一起使用来执行验证。下面是我们将如何检查我们的数据,以确保价格在推向生产之前是合理的。
基本的 Pandera 用法
潘德拉代码是直观的。第 3–5 行定义了在列上执行的检查。我们正在检查价格值是否在 5 到 20 之间。第 7–10 行只是一个包装器函数(为了后面的目的),但是我们真正需要的是调用第 9 行的 validate 方法来应用验证。
对火花(或 Dask)的需求
如果数据变得太大,熊猫无法有效处理(超过 15GB),我们该怎么办?为了加快我们的工作,我们需要使用分布式计算框架。这就是火花和达斯克的用武之地。计算操作是在一群机器上执行的,而不是在一台机器上。
在很多情况下,我们已经为熊猫写好了逻辑,但想把它带到 Spark。一个用例是写出一个非常具体的大型验证模式。我们需要在 Spark 中重新创建该功能。不幸的是,Pandera 只适用于熊猫,这意味着我们需要从头开始重新创建。问题是定制的业务逻辑需要 Pandas 和 Spark 的两个实现。
赋格入门
这就把我们带到了赋格。Fugue 是一个抽象层,允许用户将 Python 或 Pandas 代码移植到 Spark 或 Dask。逻辑与执行解耦,用户只需修改一行代码就可以选择自己需要的引擎。我们可以用下面的代码来激发上面熊猫特有的逻辑。注意,第 12 行之前的所有内容都是从前面的 Pandera 代码片段中复制的。
第 15 行中的fugeworkflow类包含一个执行引擎。如果没有传递任何内容,则默认使用熊猫。在这个具体的例子中,我们传递了 SparkExecutionEngine ,它在 Spark 中执行我们所有的逻辑。 price_validation 函数被映射到 Spark 分区。这将加快大型数据集的验证操作。
按分区验证
当前的数据验证框架有一个缺点。根据我们掌握的数据,CA 和 FL 的价格范围相差很大。因为验证是按列应用的,所以我们无法为每个位置指定不同的价格范围。然而,如果我们可以对每组数据应用不同的检查,那将是理想的。这就是我们所说的分区验证。
这个操作对于赋格来说变得非常琐碎。我们可以稍微修改上面的例子来达到这个目的。在下面的代码片段中,第 6 行到第 12 行只是前面验证的两个版本。一个用于 FL,一个用于 CA。我们在第 14 行将它们打包成一个字典。
我们的 price_validation 函数也做了一些调整。首先,我们的函数现在是在假设传入的数据帧只包含一个状态(CA 或 FL)的情况下编写的。我们从第一行的 State 值中提取位置,从字典中找到适当的验证,并应用它。
另一个变化是第 24 行现在在验证之前按照州对数据进行了划分。这基本上意味着 Spark 数据帧被分割成更小的 Pandas 数据帧,并且对每个数据帧分别进行操作。 price_validation 函数为 CA 数据调用一次,为 FL 数据调用一次。这种验证是通过 Spark 执行引擎并行完成的。
结论
在这篇博客文章中,我们简要回顾了什么是数据验证。我们将 Pandera 库视为执行数据验证的一种方式。由于该库只在 Pandas 中可用,我们使用 Fugue 将它带到 Spark,这是一个抽象层,允许用户将 Python 和 Pandas 代码移植到 Spark 和 Dask。使用 Fugue,我们可以在每个数据分区上应用 Python/Pandas 包,这允许我们在这里通过分区执行验证。
神游也可以将其他基于熊猫的库引入 Spark。这个例子只是针对数据验证的。有关更多信息,请查看下面的参考资料。
资源
如果你对使用赋格感兴趣,想给我们反馈,或者有任何问题,我们很乐意在 Slack 上聊天!我们还将为对在数据工作流中应用 Fugue 感兴趣的数据团队举办更详细的研讨会。
赋格
使用“个性测验”和最近邻居进行推荐(英雄联盟)
一个初学者友好的 N 维空间,最近邻,欧几里德距离和更多的演练
这是关于推荐方法的 3 部分系列的最后一部分(在这里找到: 1 & 2 ,尽管我建议在点击离开之前继续阅读),然而我认为这实际上是迄今为止我所写的任何文章中最入门的,因此我建议从这里开始。对于有经验的你来说,我希望仍然有价值——但是你可能想跳过一些介绍性的部分。我会在文章里说清楚这些点在哪里。

最终产品,一个能够推荐一个英雄联盟冠军的性格竞猜!自己试试这里。图片作者。
之前,我写过两种推荐方法:
【用户-用户协同过滤】 : 相似的用户喜欢相似的产品。
【基于内容的模型】 : 如果用户喜欢一个产品,他们也会喜欢类似的产品。
今天,我们来看看标志性的方法:一个性格测试。根据用户对调查问卷的回答,找到与用户最相似的产品。这在“现实生活”中是最不受欢迎的,因为它增加了太多的摩擦,而且相对不可靠。然而,它很简单,并且对推荐引擎的主题提供了很好的介绍,同时允许有更多的时间来详细了解核心概念。此外,他们很有趣。
使用的数据科学技术被称为“最近邻居”,这个领域的许多人会因为我称之为人工智能而排斥我。无论如何,它构成了后来许多应用程序的主干,因此清楚地理解它永远不应该被低估。
根据我的大多数文章,我使用在线游戏英雄联盟(LoL)来应用这些技术。你不需要理解游戏来理解文章,但是如果你想知道更多,这里有一个游戏的介绍。
N 维空间导论
在本文的某个地方你会看到“21 维空间”。这是我失去一些人的地方。对于非数学家来说,有时会有这样的假设,那些提到第四维度以外的任何维度的人可能事实上正在与神交流,预见未来或者是部分蜥蜴类外星人。如果你在这一点上点头同意,最好完整地阅读这一部分。如果你非常熟悉 N 维空间的含义,请直接跳到最近邻的介绍。
让我们从简单的开始。如果我在研究一群人,我可能会从测量他们的体重开始。我创建了一个一维空间,名为“公斤体重”。一个人只能比下一个人更重或更轻。只有一个行进方向。它可以用一条简单的直线来表示。我们越向左走,它们越轻,我们越向右走,它们越重。

一维空间,没有比这更简单的了!图片作者。
现在我们通过增加它们的高度来移动到二维空间。我们可以绘制出那个空间里每个人的体重,以及他们的身高。一切都很容易想象。

身高和体重的 2D 空间。图片作者。
当我们把他们的年薪计算在内时,事情在三维空间变得更加棘手。想象一个立方体,想象你自己在最近的左下角。你沿着宽度到他们的体重,到他们的高度,然后根据他们的工资深入立方体。你甚至可以拿着尺子跳进盒子里,测量两个人之间的距离,看看他们有多相似。

一个 3D 空间,其中点的不透明度表示年收入。图片作者。
然而,当我添加鞋号时会发生什么?我们现在有了四维空间。我们不再能够在脑海中描绘出这将会是什么样子——然而一切都没有改变!那些点仍然出现在一个空间里,只是这个空间在我们这个非常无聊的 3D 世界里无法直观的展现出来。我们甚至还可以测量两点之间的距离,只是我们必须把尺子拿开,用数学方法来计算。
无论我们有 4 维、5 维还是 4500 维,都是如此。没关系!我们不能想象任何超过 3 的东西,但是所有相同的原理和技术都可以以相同的方式应用(数学上),不管我们达到什么样的维度。
最近邻居介绍
不管你是跳过这里,还是读过上面,我希望当我说“N 维空间”时,我们都在同一页上。这意味着我们可以转移到实际的技术本身。
最近邻(NN) 就是确切地说就是罐头上写的。对于空间中的任意一点,找出 N 个最近邻。从字面上看,你只需选择一个点,并找到 N 个最接近该点的点。“N”可以是任何数字,但是标准 Python 实现(sklearn)默认为 5。
希望下面的观想能让这一点变得清晰。蓝色是示例点,红色是 5 个最近的邻居:

2D 空间中最近邻的直观解释。图片作者。
有一点需要明确的是,当我们说“最近”时,我们指的是什么。在上面的 2D 空间中,我们可以在屏幕上按下一把尺子,然而在难以想象的 4 维空间和更高的空间中会发生什么呢?好吧,我们用数学上相当于尺子的:欧几里德距离。

欧几里德距离方程。来源:维基百科。
再次,【vector】这个词是许多人的另一个导火索。因此,让我们明确我们的意思(如果你知道所有这些,到应用与你最近的邻居!).
回到我们的人口例子。假设我们有两个人的体重和身高。
人 1:70 公斤,170 厘米
人二:90kg,185cm。
重量是矢量(70,90),高度是矢量(170,185)。根据上面的公式,如果我们想找到它们之间的欧几里德距离,我们做每个点之间的平方差的和。像这样:
重量平方差:(人 1 重量-人 2 重量)=(90–70)= 20 = 400
身高平方差:(人 1 身高-人 2 身高)=(185–170)= 15 = 225
(体重平方差+身高平方差)的平方根)=平方根(400 + 225) =平方根(625) = 25。
个体一(70kg,170cm)和个体二(90kg,185cm)的欧氏距离为 25。
如果我们改为处理 4 个维度(体重、身高、收入和鞋码),这些值可能是:
人 1:70 公斤,170 厘米,7 万美元,10 号鞋
人 2:90 公斤,185 厘米,95000 美元,12 号鞋
计算是相同的,除了不仅仅是身高和体重的平方差,我们还包括年薪和鞋码。试着自己算出欧几里德距离。答案在文章底部*。
将最近邻应用于个性测验
但这一切如何应用于推荐一个基于性格测验的英雄联盟冠军(可玩角色)?
这是如何做到的:
- 创建一个多维空间,其中的功能有助于描述游戏中的每个冠军(目前有 152 个冠军可供选择)。
- 让神经网络算法适合这个空间。
- 创建一个调查,试图把这个人放在那个空间。
- 使用神经网络算法来确定与该人的调查代表最接近的冠军。
注意: 如果这还没有意义,不要担心,我们将详细介绍它。
第一步。是目前为止最繁琐最耗时的。我们构建了一个脚本,从 Riot API 中获取游戏,并从中提取出所玩游戏的冠军以及在哪条泳道中。我们也抓取一些我们认为有助于描述冠军的特征,比如他们杀了多少人,他们对目标造成了多少伤害,他们阻挡了多少伤害,等等…
我有大约 800 万个游戏的信息,但那是因为我是从其他项目那里得到的。如果你想亲自尝试,我不建议你去那么高的地方。
下面是表格的一个例子,每行代表一名冠军在一场比赛中的表现:

显示冠军在一场比赛中的表现的表格。图片作者。
然后,我们可以使用数据帧上的分组来获得所有游戏中每个通道/冠军组合的平均值。看起来是这样的:

显示冠军在多场比赛中平均表现的表格。图片作者。
最后,我去掉了少于 5000 场比赛的球道/冠军组合。这消除了真正偏离元的选择(<0.25% pick rate) and ensures there’s sufficient data for the averages to converge around their true mean (中心极限定理)。
然后,我们通过按通道过滤将数据帧分成 5 份。你不需要这样做,但我觉得这是一个更干净的过程,我喜欢为每条车道提供个人建议的想法。
我还从原始数据中创建了一些我认为会很有趣的附加特性。例如,我有一个冠军在没有帮助的情况下杀死别人的次数(单杀),我也有他们杀死的总次数。我创造了一个新的特征,那就是单杀占总杀死数的百分比(单杀百分比=单杀/总杀死数)。
下一步是将数据帧转换成矩阵,其中每行代表一个冠军,有 21 个关于他们在游戏中的表现的各种统计数据,平均跨越许多样本。
然后,我们对数据进行归一化处理,并根据矩阵拟合最近邻算法,默认 N = 5。
关于标准化的补充说明
同样,这篇文章真的是为任何水平的读者准备的——所以我不想只写“我们规范化了数据”而不解释和证明自己。因此,如果您对我们如何/为什么这样做感到满意,请跳过这一部分。如果不是,这里有一个简单的解释:
我们正在使用的最近邻方法完全依赖于两点之间的欧几里德距离。我们的问题是每个特征都在不同的尺度上。例如,让我们以一个冠军为例,他平均造成 50,000 次伤害,每场比赛赚 10,000 金币。
然后,我们想测量冠军和一个造成 50,000 次伤害,但只获得 8,000 金币的冠军之间的欧几里德距离。花一点时间考虑一下…
…答案是 2000。因为第一个数字(伤害:50,000)不变,所以所有的距离都来自“黄金”特性,区别在于:
10,000 – 8,000 = 2,000.
然后我们将两个平方差求和:
2,000² + 0² = 2,000²
最后,求平方根:
平方根(2000)= 2000
所以,为了减少 20%的杀戮,欧几里德距离是 2000。
现在,想象一个冠军只造成 40,000 点伤害,但仍然得到 10,000 金币。为了减少 20%的伤害,欧几里德距离现在是 10000!这意味着最近邻算法对伤害属性的依赖是杀死属性的 5 倍。希望这说明了这一点:

观察值 X:伤害,Y:黄金。说明距离对伤害的依赖远远超过黄金。图片作者。
那么,我们该怎么解决呢?我们将数据标准化。或者换句话说,我们确保所有的统计数据都在 0 和 1 之间的相同范围内。为此,我们使用以下方法:

图片作者。
如果 5 个冠军的平均每场杀数是:[3,1,5,6,11]。
最小值(x) = 1,最大值(x) = 11
从列表的开头开始,x=3:
(x-min(x))/(max(x)-min(x))=(3–1)/(11–1)= 2/10 = 0.2
如果我们对列表中的所有数字都这样做,[3,1,5,6,10]=>[0.2,0,0.4,0.5,1]
杀戮现在被限制在 0 和 1 之间。我们对造成的伤害做同样的事情。现在杀戮的变化和伤害的变化一样强大。

除了两个统计数据都被归一化之外,与上面的图相同。现在伤害和黄金的变化同样强大。图片作者。
将问卷转换成数据
在我们开始之前,我跟你说实话。这部分既是科学也是艺术。其实大部分是艺术,带着一大把盐往前走。
所以,我们有了这个 21 维空间(我告诉过你它要来了)。每个点代表一个冠军,他们的位置取决于这 21 个统计数据的组合。我们的下一个目标是创建一个问卷,让我们将用户放入这个空间。
为了做到这一点,我们需要根据调查者的“个性”找出 21 个统计数据中每一个的粗略近似值。换句话说,对于每一个统计数据,都需要有一个相应的问题和一种将答案转化为可用数字的方法。
为此,我选择了“你有多同意以下说法”式的提问。效果好的原因是因为他们的反应是有规模的,我把它设定为:
完全不同意、不同意、中立、同意、完全同意
然后我可以将这些陈述转换成百分位数,然后再转换成数据。一个例子应该有助于说明这一点:
我们数据集中的第一个统计数据是:在没有帮助的情况下完成的冠军猎杀的百分比。我(巧妙地)把它翻译成了下面这句话:
《我是独狼》
为什么?高百分比的冠军是那些在单独行动时获得大部分杀戮的人。那些百分比低的人依靠他们的队友来确保安全捕杀。因此,孤狼。这是科学的艺术部分。
因此,那些回答“完全同意”的人被认为是统计数据中前 20%的人,而那些回答“完全不同意”的人是后 20%的人。说中立的人在 40-60%之间。鉴于我们有所有冠军的统计数据,我们只是计算出他们价值的粗略估计。
为了帮助澄清这一点,让我们想象我只有 10 个冠军,我在看他们平均每场比赛杀死的数量:
每场比赛的死亡数= [1,1,2,3,3,3,4,5,7,8]
如果有人说“完全不同意”一个关于每场比赛杀球的陈述(比如“我喜欢杀很多球”),这将使他们在这个统计中处于倒数 20%。所以,10 个冠军中倒数 20%就是倒数 2 个。对于这个人,我们会给他们每局 1 次的杀戮。为什么?上面列表中最低的 2 个值是 1 和 1。
如果其他人说“完全同意”,他们会排在前 20%。所以每场比赛要么杀 7 个要么杀 8 个。
“同意”怎么样?它在 60-80%之间,所以是 4 或 5。你明白了。
然后,我们继续从 21 个可用的统计数据中创建问题,每次都将它们的答案转换为实际值。问题及其相关统计数据的完整列表可在此处找到:

所有提问的表格,以及相关的统计数据。图片作者。
现在,当有人完成“性格测验”时,我们将每个答案转换为统计数据,归一化这些值(使用我们在原始数据集上使用的相同最小/最大值),然后,我们将他们的性格转换为我们 21 维空间中的位置!
最后一步是使用我们之前拟合的神经网络算法来确定哪些原始冠军最接近此人的回答,并向他们提供前 5 名作为推荐!
最后一点
你们中的一些人可能会问“但在上面的例子中,你说完全同意可能是 7 或 8,你如何选择?”。答案是,我没有。我使用了一个随机数生成器在这个范围内选择一个值。这意味着每次你回答问题时,你都会得到稍微不同的答案,因为你在空间中的点随机在 20%的范围内移动,所以不同的冠军变得更近/更远。一种替代方法是坚持静态答案(即 7 或 8 的中点= 7.5),但是这意味着您在回答中获得的冠军多样性较少。另一种选择是说“在 1 到 1000 的范围内,您对该陈述的赞同程度如何”,但这感觉对用户不太友好。这里没有正确的答案。
如果你想亲自尝试成品,你可以在这里找到它。我很想听听你是否同意这个结果!
这篇文章比我通常写的要详细得多,我花了很多时间来解释更基本的概念,如维度空间和规范化。这是因为我想找个地方介绍一个完全陌生的人进入这个领域(因为我在工作中遇到了很多这样的人)。然而,我希望仍然有一些老兵发现了足够有趣的材料,值得一读。
*我在 NN 简介中设置的问题答案:25000.013
你已经看到文章的结尾了!我叫 Jack J,是一名将人工智能应用于竞技游戏和电子竞技的专业数据科学家。我是iTero 的创始人。GG 和 jung.gg 。你可以在 Twitter 上关注我,加入 iTero Discord 或者给我发邮件到 jack@itero.gg 。下一场见。
最初发表于:https://itero.gg/blog
使用带 RLlib 的 PettingZoo 进行多智能体深度强化学习
免责声明
对于 PettingZoo 和 RLlib 的新版本,本教程不再是最新的。自从这篇文章发表以来,PettingZoo 经历了一些重大的修改,现在是 Farama 基金会的一部分。有关最新文档和教程,请参见 https://pettingzoo.farama.org/的。
通过 RLlib 强化学习库使用 PettingZoo 多代理环境的教程
感谢尤里·普洛特金、罗汉·波特达尔、本·布莱克和卡安·奥兹多格鲁,他们各自创作或编辑了本文的大部分内容。
本教程概述了如何使用 RLlib Python 库和 PettingZoo 环境进行多智能体深度强化学习。关于使用 PettingZoo 环境的更多细节可以在最近的博客文章中找到。在下面的文章中,我们将介绍如何使用两个 PettingZoo 环境来训练和评估 RLlib 策略:
活塞球——不存在非法行为,所有特工同时行动
Leduc Hold'em —非法行动掩蔽,基于回合的行动
宠物动物园和滑雪球
PettingZoo 是为多代理强化学习模拟开发的 Python 库。当前的软件提供了一个标准的 API,在使用其他著名的开源强化学习库的环境中进行训练。你可以把这个库想象成类似于 OpenAI 的 Gym 库,然而,它是为多代理强化学习而定制的。与其他类似,API 的基本用法如下:
from pettingzoo.butterfly import pistonball_v5env = pistonball_v5.env()
env.reset()
for agent in env.agent_iter():
observation, reward, done, info = env.last()
action = policy(observation, agent)
env.step(action)
Pistonball 是一个合作的 PettingZoo 环境,如下图所示:

作者图片
环境的目标是训练活塞协同工作,尽可能快地把球移到左边。
每个活塞都作为一个独立的代理,由一个策略 π 控制,这个策略是用函数逼近技术训练的,比如神经网络(因此是深度强化学习)。每个代理的观察空间是每个活塞上方和侧面的一个窗口(见下文)。

作者图片
我们假设完全可观察,策略 π 返回一个动作,用于将活塞从+4 像素升高或降低到-4 像素(图像尺寸为 84x84 像素)。
随着每个活塞的动作,环境输出两个全局奖励
(δx/xₑ)* 100+τt
其中δx是球的 x 位置的变化, Xₑ 是球的起始位置, τ 是时间惩罚(默认值为 0.1)乘以时间长度 t 。
关于宠物动物园环境的更多细节,请查看以下描述。
代码
让我们更详细地浏览一下代码。
首先,为了运行强化学习环境,我们导入所需的库:
from ray import tunefrom ray.rllib.models import ModelCatalogfrom ray.rllib.models.torch.torch_modelv2 import TorchModelV2from ray.tune.registry import register_envfrom ray.rllib.env.wrappers.pettingzoo_env import ParallelPettingZooEnvfrom pettingzoo.butterfly import pistonball_v5import supersuit as ssimport torchfrom torch import nn
多主体强化学习环境需要分布式训练。为了设置环境,我们使用开源库 Ray 。Ray 是为构建分布式应用程序提供通用 API 而开发的框架。 Tune 是一个构建在 Ray 之上的库,用于分布式强化学习中的可伸缩超参数调优。因此,我们只使用 Tune 在 RLlib 中执行一次训练运行。由于我们将需要使用一个定制模型来训练我们的策略 π ,我们首先在 RLlib 的 ModelCatalog 中注册这个模型。为了创建自定义模型,我们从 RLlib 中继承了 TorchModelV2 类的子类。
为了将 PettingZoo 环境与 Tune 一起使用,我们首先使用 register_env 函数注册环境。parallelpettingzooeenv是一个包装器,用于 PettingZoo 环境,如 Pistonball,以与 RLlib 的多代理 API 接口。SuperSuit 是一个为健身房和宠物动物园环境提供预处理功能的库,我们将在下面看到。
最初,我们在 Pytorch 中创建一个卷积神经网络模型来训练我们的策略 π :
class CNNModelV2(TorchModelV2, nn.Module):def __init__(self, obs_space, act_space, num_outputs, *args, **kwargs):TorchModelV2.__init__(self, obs_space, act_space, num_outputs, *args, **kwargs)nn.Module.__init__(self)self.model = nn.Sequential(nn.Conv2d( 3, 32, [8, 8], stride=(4, 4)),nn.ReLU(),nn.Conv2d( 32, 64, [4, 4], stride=(2, 2)),nn.ReLU(),nn.Conv2d( 64, 64, [3, 3], stride=(1, 1)),nn.ReLU(),nn.Flatten(),(nn.Linear(3136,512)),nn.ReLU(),)self.policy_fn = nn.Linear(512, num_outputs)self.value_fn = nn.Linear(512, 1)def forward(self, input_dict, state, seq_lens):model_out = self.model(input_dict[“obs”].permute(0, 3, 1, 2))self._value_out = self.value_fn(model_out)return self.policy_fn(model_out), statedef value_function(self):return self._value_out.flatten()
你可以在这里找到更多关于如何使用 RLlib 库的定制模型的信息。然后我们需要定义一个函数来创建和返回环境:
def env_creator(args):env = pistonball_v4.parallel_env(n_pistons=20, local_ratio=0, time_penalty=-0.1, continuous=True, random_drop=True, random_rotate=True, ball_mass=0.75, ball_friction=0.3, ball_elasticity=1.5, max_cycles=125)env = ss.color_reduction_v0(env, mode=’B’)env = ss.dtype_v0(env, ‘float32’)env = ss.resize_v0(env, x_size=84, y_size=84)env = ss.frame_stack_v1(env, 3)env = ss.normalize_obs_v0(env, env_min=0, env_max=1)return env
我们使用 PettingZoo 的并行 API 来创建环境。函数的参数控制环境的属性和行为。我们另外使用 SuperSuit 的包装函数进行预处理操作。
第一个函数用于将环境产生的全色观察图像转换为灰度图像,以降低计算复杂度和成本。随后,我们将像素图像数据类型从 uint8 转换为 float32,将其缩减为 84x84 网格表示,并归一化像素强度。最后,为了测量球的方向变化δX(用于计算总奖励),将 3 个连续的观察帧堆叠在一起,以提供一种简单的学习策略。
这些预处理操作的更详细的解释可以在之前的教程中找到。
在定义了模型和环境之后,我们可以使用配置字典中的参数,使用 tune.run()函数运行训练器。你可以在这里详细了解这些超参数。值得注意的是,我们实例化了一个多代理特定配置,其中我们使用字典映射来指定我们的策略:
policy_id 字符串→元组(policy_cls,obs_space,act_space,config)。
policy_mapping_fn 是一个将 agent_ids 映射到 policy_ids 的函数。在我们的特殊情况下,我们使用参数共享来训练活塞,如之前的教程中所述,即所有活塞都由 id 为‘policy _ 0’的同一策略 π 控制。
if __name__ == “__main__”:env_name = “pistonball_v4”register_env(env_name, lambda config: ParallelPettingZooEnv(env_creator(config)))test_env = ParallelPettingZooEnv(env_creator({}))obs_space = test_env.observation_spaceact_space = test_env.action_spaceModelCatalog.register_custom_model(“CNNModelV2”, CNNModelV2)def gen_policy(i):config = {“model”: {“custom_model”: “CNNModelV2”,},“gamma”: 0.99,}return (None, obs_space, act_space, config)policies = {“policy_0”: gen_policy(0)}policy_ids = list(policies.keys())tune.run(“PPO”,name=”PPO”,stop={“timesteps_total”: 5000000},checkpoint_freq=10,local_dir=”~/ray_results/”+env_name,config={# Environment specific“env”: env_name,# General“log_level”: “ERROR”,“framework”: “torch”,“num_gpus”: 1,“num_workers”: 4,“num_envs_per_worker”: 1,“compress_observations”: False,“batch_mode”: ‘truncate_episodes’,# ‘use_critic’: True,‘use_gae’: True,“lambda”: 0.9,“gamma”: .99,# “kl_coeff”: 0.001,# “kl_target”: 1000.,“clip_param”: 0.4,‘grad_clip’: None,“entropy_coeff”: 0.1,‘vf_loss_coeff’: 0.25,“sgd_minibatch_size”: 64,“num_sgd_iter”: 10, # epoc‘rollout_fragment_length’: 512,“train_batch_size”: 512*4,‘lr’: 2e-05,“clip_actions”: True,# Method specific“multiagent”: {“policies”: policies,“policy_mapping_fn”: (lambda agent_id: policy_ids[0]),},},)
现在,我们可以看到我们训练过的策略在环境中自动执行。在训练期间,根据 checkpoint_freq 指定的频率,在每个检查点保存策略。默认情况下,RLlib 将检查点存储在~/ray_results 中。我们首先指定检查点的路径:
checkpoint_path = “foo”
然后,我们可以加载并恢复我们训练过的策略 π ,并在每个时间步渲染环境时使用该策略来选择动作。我们将渲染后的视频保存为 GIF 格式:
with open(params_path, “rb”) as f:config = pickle.load(f)# num_workers not needed since we are not trainingdel config[‘num_workers’]del config[‘num_gpus’]ray.init(num_cpus=8, num_gpus=1)PPOagent = PPOTrainer(env=env_name, config=config)PPOagent.restore(checkpoint_path)reward_sum = 0frame_list = []i = 0env.reset()for agent in env.agent_iter():observation, reward, done, info = env.last()reward_sum += rewardif done:action = Noneelse:action, _, _ = PPOagent.get_policy(“policy_0”).compute_single_action(observation)env.step(action)i += 1if i % (len(env.possible_agents)+1) == 0:frame_list.append(PIL.Image.fromarray(env.render(mode=’rgb_array’)))env.close()print(reward_sum)frame_list[0].save(“out.gif”, save_all=True, append_images=frame_list[1:], duration=3, loop=0)
解决环境的 gif 可以在上面看到。
本教程中详细介绍的整个培训代码可以在这里找到。并且渲染的代码可以在这里找到。对于代码库的任何改进或本文遇到的任何问题,请在 PettingZoo 资源库中创建一个问题。
Leduc 德州扑克
Leduc Hold'em 是 AI 研究中流行的一种扑克变种详细介绍这里和这里;我们将使用双人模式。这种环境是值得注意的,因为它是一个纯粹的回合制游戏,有些动作是非法的(例如,需要动作屏蔽)。
首先,我们需要的初始代码看起来非常相似:
from copy import deepcopyimport osimport rayfrom ray import tunefrom ray.rllib.agents.registry import get_agent_classfrom ray.rllib.env import PettingZooEnvfrom pettingzoo.classic import leduc_holdem_v2from ray.rllib.models import ModelCatalogfrom ray.tune.registry import register_envfrom gym.spaces import Boxfrom ray.rllib.agents.dqn.dqn_torch_model import DQNTorchModelfrom ray.rllib.models.torch.fcnet import FullyConnectedNetwork as TorchFCfrom ray.rllib.utils.framework import try_import_torchfrom ray.rllib.utils.torch_ops import FLOAT_MAXtorch, nn = try_import_torch()
通过参数动作屏蔽使用动作屏蔽初始化合适的策略,如下所示:
class TorchMaskedActions(DQNTorchModel):“””PyTorch version of above ParametricActionsModel.”””def __init__(self,obs_space,action_space,num_outputs,model_config,name,**kw):DQNTorchModel.__init__(self, obs_space, action_space, num_outputs,model_config, name, **kw)obs_len = obs_space.shape[0]-action_space.norig_obs_space = Box(shape=(obs_len,), low=obs_space.low[:obs_len], high=obs_space.high[:obs_len])self.action_embed_model = TorchFC(orig_obs_space, action_space, action_space.n, model_config, name + “_action_embed”)def forward(self, input_dict, state, seq_lens):# Extract the available actions tensor from the observation.action_mask = input_dict[“obs”][“action_mask”]# Compute the predicted action embeddingaction_logits, _ = self.action_embed_model({“obs”: input_dict[“obs”][‘observation’]})# turns probit action mask into logit action maskinf_mask = torch.clamp(torch.log(action_mask), -1e10, FLOAT_MAX)return action_logits + inf_mask, statedef value_function(self):return self.action_embed_model.value_function()
Leduc Hold'em 中的渲染功能类似于 Pistonball,使用以下代码片段:
import rayimport pickle5 as picklefrom ray.tune.registry import register_envfrom ray.rllib.agents.dqn import DQNTrainerfrom pettingzoo.classic import leduc_holdem_v4import supersuit as ssfrom ray.rllib.env.wrappers.pettingzoo_env import PettingZooEnvimport PILfrom ray.rllib.models import ModelCatalogimport numpy as npimport osfrom ray.rllib.agents.registry import get_agent_classfrom copy import deepcopyimport argparsefrom pathlib import Pathfrom rllib_leduc_holdem import TorchMaskedActionsos.environ[“SDL_VIDEODRIVER”] = “dummy”parser = argparse.ArgumentParser(description=’Render pretrained policy loaded from checkpoint’)parser.add_argument(“checkpoint_path”, help=”Path to the checkpoint. This path will likely be something like this: `~/ray_results/pistonball_v4/PPO/PPO_pistonball_v4_660ce_00000_0_2021–06–11_12–30–57/checkpoint_000050/checkpoint-50`”)args = parser.parse_args()checkpoint_path = os.path.expanduser(args.checkpoint_path)params_path = Path(checkpoint_path).parent.parent/”params.pkl”alg_name = “DQN”ModelCatalog.register_custom_model(“pa_model”, TorchMaskedActions)# function that outputs the environment you wish to register.def env_creator():env = leduc_holdem_v4.env()return envnum_cpus = 1config = deepcopy(get_agent_class(alg_name)._default_config)register_env(“leduc_holdem”,lambda config: PettingZooEnv(env_creator()))env = (env_creator())# obs_space = env.observation_space# print(obs_space)# act_space = test_env.action_spacewith open(params_path, “rb”) as f:config = pickle.load(f)# num_workers not needed since we are not trainingdel config[‘num_workers’]del config[‘num_gpus’]ray.init(num_cpus=8, num_gpus=0)DQNAgent = DQNTrainer(env=”leduc_holdem”, config=config)DQNAgent.restore(checkpoint_path)reward_sums = {a:0 for a in env.possible_agents}i = 0env.reset()for agent in env.agent_iter():observation, reward, done, info = env.last()obs = observation[‘observation’]reward_sums[agent] += rewardif done:action = Noneelse:print(DQNAgent.get_policy(agent))policy = DQNAgent.get_policy(agent)batch_obs = {‘obs’:{‘observation’: np.expand_dims(observation[‘observation’], 0),‘action_mask’: np.expand_dims(observation[‘action_mask’],0)}}batched_action, state_out, info = policy.compute_actions_from_input_dict(batch_obs)single_action = batched_action[0]action = single_actionenv.step(action)i += 1env.render()print(“rewards:”)print(reward_sums)
与之前类似,本教程中使用的完整训练代码可以在这里找到,渲染代码可以在这里找到。如果您对这段代码有任何改进,或者对本文有任何问题,请在 PettingZoo 资源库上创建一个问题,我们很乐意提供帮助。
使用 pgfplots 在 LaTeX 中制作经济图表
实践教程
用行业标准排版语言制作光滑、专业图形的通俗易懂的指南

图 0–1:工资弹性和总收入。(图片由作者提供)
1。简介
经济学大量使用图表来说明重要的概念和现象。然而,当在这个领域打一篇论文时,人们可能注意到的第一件事是使用文字处理器制作这些图表的困难。这就是乳胶发挥作用的地方。本指南将解释我们如何使用 pgfplots 包在 LaTeX 中制作精美的经济图表。
1.1。不熟悉乳胶?
有些读者可能不熟悉 LaTeX。它是一种功能强大的排版语言,能够对文本、数学公式、表格以及 word 和 Google Docs 等文字处理器无法处理的许多其他元素进行排版。它的功能通过包来扩展。本指南将重点介绍使用为绘制图形而设计的包——pgfplots——制作图形,PGF plots 本身依赖于为制作图形而设计的包——TikZ。
LaTeX 强大的能力和可定制性带来了良好的学习曲线。不要绝望。本指南是在假设读者熟悉 LaTeX 的基础知识的情况下编写的,但仅此而已。(在第 8 节:资源中,您可以使用一些资源来设置和学习 LaTeX 的基础知识。)如果您可以创建一个文本文档,加载到包中,并在 LaTeX 的显示数学模式下编写一个方程,那么您不需要知道其他任何东西。尽管如果您已经熟悉 pgfplots 和 TikZ,您可能仍然会发现这里使用的一些技术对于经济图表的非常具体的特性非常有用。
1.2.GitHub 项目资源库
此处显示的所有完成的图表都可以在本指南的 GitHub 资源库中找到。你可以在 https://github.com/jackypacky/pgf-econ-graphs访问知识库。在那里,可以找到所有图形的 TeX 源代码和 pdf。
1.3.包装
我们将使用 pgfplots 来绘制经济图表,因此应该调用 pgfplots 包。这也将调用 TikZ 和 xcolor。因为我们希望能够对曲线下的区域进行着色,所以应该调用 fillbetween pgfplots 库。因为我们希望能够指定坐标周围的位置,并在 TikZ 中使用各种箭头,所以也应该调用定位和 arrows.meta TikZ 库。此外,由于我们希望能够使用 LaTeX 的数学模式,我们需要调用 amsmath 包。总的来说,我们在文档的开头有以下代码片段:
出于个人喜好,我们将使用 Times New Roman 12 号字体,行距为 1.25 倍(相当于 1.5 倍行距)。因此,我们将使用此文档序言:
2.概述轴
首先,我们需要画出绘制经济图表的坐标轴。由于 pgfplots 是 TikZ 的依赖项,我们需要用\begin{tikzpicture}和\end{tikzpicture}打开和关闭 TikZ 环境。在这个环境中,为了构建 pgfplots 环境,我们插入了\begin{axis}和\end{axis}。既然我们希望经济图表位于页面的中心,那么所有这些都应该在中心环境中,用\begin{center}和\end{center}包含代码。所以我们有:
对于 axis 环境,参数影响整个图形(即缩放比例、轴刻度、网格线),而命令影响特定的绘图(即函数、线、坐标点)。从语法上来说,需要注意的一点是,参数用逗号分隔,而命令用分号分隔。
2.1.axis 环境
在开始绘制函数之前,我们需要指定我们希望显示笛卡尔平面的哪个区域。为了指定域,我们使用参数xmin = 0和xmax = 10,其中 0 和 10 是域的最小和最大边界。类似地,为了指定范围,我们使用参数ymin = 0和ymax = 10分别指定最小和最大范围界限。到目前为止,我们已经有了这个图形的基本轮廓代码:
运行这段代码会产生 Figure 2–1。

图 2–1:x 轴和 y 轴,带勾和框。(图片由作者提供)
2.2.轴线方向
我们需要做一些修改,使其适合经济图表。图表框架的默认样式是框形,图的四周都是黑线。虽然这对于折线图和散点图(pgfplots 的初衷)很好,但经济图通常有一个 L 形框架。要改变框架样式,我们需要使用参数axis lines = style,样式可以是left、right、center、box或none。figure 2–2 显示了轴线的一些样式。

图 2–2:左、中和右轴线。(图片由作者提供)
显然,我们需要使用axis lines = left作为参数来制作大多数经济图所具有的 L 形框架。此外,轴末端的箭头可以通过在axis lines的末端附加一个星号来删除,所以我们使用axis lines* = left。
2.3.轴刻度、比例和剪裁
经济图表通常没有沿着轴运行的数字。这是因为它们中的大多数都是理论图表,并且不管涉及的数量级如何都适用。为此,我们需要删除沿 x 轴和 y 轴的线条和数字,它们分别称为轴刻度和轴刻度标签。实现这一点的参数分别是 x 轴和 y 轴的xtick = {tick numbers}和ytick = {tick numbers}。在xtick和ytick参数后面的花括号中,我们放置了一个由逗号分隔的数字列表,我们希望这些数字沿着各自的轴运行。由于经济图表在很大程度上保持零在左下方,我们将把它保持在 x 刻度上xtick = {0}。因为我们不希望 y 轴上有轴记号,所以我们写ytick = \empty。空轴刻度数列表是一种特殊情况。因为如果花括号内没有任何内容,pgfplots 将使用默认的轴记号,所以我们需要用\empty指定它是空的。为了制作非空白的轴线——例如,具有特定经验值的图表——第 4 节:经验曲线涵盖了网格线和轴刻度。
考虑到页面上图形的大小,默认的大小太小了,不能在不弄乱可用空间的情况下展示太多的插图。为了稍微增加图形的大小,我们编写了参数\scale = 1.2。当然,如果你有一个更复杂或更重要的插图,可以使用更大的因子来创建更大的图形。另一方面,较小的因子可以用来创建较小的图形。1.2 的比例因子占据了大约三分之一的页面。
我们需要添加的最后一个参数是clip = false。通常情况下,pgfplots 会切断图形外部的所有绘图,这对于自动限制函数非常有用。但是,我们希望能够将标签放在图形之外,所以我们用这个参数禁用了这个特性。相反,我们将手动指示所绘制函数的域和范围。
2.4 轴标签
最后,在图表上绘制任何东西之前,我们必须考虑的最后一个问题是轴标签。将它们放在轴的末端比通常放在底部(对于 x 轴)和左侧(对于 y 轴)更方便,因为我们希望将该空间用于其他东西(如可变标签和尺寸线)。要在 x 轴和 y 轴的末端放置标签,命令采用以下形式:
提醒一下,分号应该在这些行的末尾,因为它们是命令,而不是到目前为止一直使用的参数。(第 3.5 小节:标签更详细地解释了该命令工作的原因。)为了演示,我们将分别使用$x$和$y$的 x 轴和 y 轴标签。将美元符号放在 x 和 y 周围会将它们置于 LaTeX 的数学模式,允许我们编写 LaTeX 已知的数学表达式。
在考虑了所有这些因素后,我们最终得到了图 2–3:

图 2–3:成品 x 轴和 y 轴,毛坯和 L 形。(图片由作者提供)
现在,图表已经准备好在其上绘制图形(例如函数)。产生图 2–3 的代码如下:
3.绘图、颜色和标签(例如无差别地图和预算约束)
有了轴,我们就可以开始在图上画画了。假设我们想用无差异曲线来解释为什么需求是向下倾斜的——这意味着需求量随着价格的上升而下降。更具体地说,我们想在商品 A 和 B 之间绘制一个无差异图,预算约束显示商品 A 的价格增加对应于商品需求量的减少。
3.1 绘图功能
首先,我们想画一条无差异曲线, U₁ ,定义为,

这是通过以下命令完成的:
这是一个很长的命令。让我们把它拆成碎片。domain = min:max的 addplot 参数将绘制的函数限制在由最小 x 值、 min (在本例中为 0)和最大 x 值、 max (在本例中为 10)限定的范围内。类似地,restrict y to domain = min:max将函数限制在介于最小值和最大值之间的范围内(在本例中为 0 和 10)。(将 y 限制在一个“域”中有点用词不当,因为 y 值集的正确数学术语是“范围”。)注意,我们需要定义域和范围的原因,如前所述,是因为我们禁用了 pgfplots 的裁剪特性。addplot 参数samples = n告诉 pgfplots 取一定数量的 x 值, n ,在这些值处计算函数并通过所有计算出的坐标画一条线。实际上,样本数量越多,绘制的曲线越平滑。最后一个 addplot 参数color = colour name不言自明。它将线条着色为颜色名称。颜色将在下一小节中详细介绍。在第 3.2 小节“颜色”中,有一个预定义颜色名称的列表,以及关于如何添加更多预定义颜色集和定义新颜色名称的解释。
转到花括号内的数学表达式,这是我们放置函数表达式10/(x^2)+1的地方。需要注意的是,传统的乘法并置符号(将数字和字母并排放置)会产生错误。相反,星号—“*”—只能表示乘法。否则,传统的计算机符号适用,加法、减法、除法和分组分别用“+”、“-”、“/”和“()”表示。绘制上述命令会产生 Figure 3–1。

图 3–1:绘制好 A 和好 b 之间的无差别曲线。(图片由作者提供)
产生图 3–1 的代码是:
关于图 3–1 及其代码,需要注意的一点是 x 轴标签是 A ,y 轴标签是 B 。这是因为,在这个例子中,我们想表明商品 A 价格的增加会导致该商品需求量的减少。
自然,无差异贴图包含不止一条曲线。因此,让我们通过将第一条曲线向右平移一个单位和向上平移一个单位来绘制多条曲线。在变换符号中,这对应于(x,y)→(x1,y+1)。
在 U₁ 上相继运用这种变换产生了 U₂ 、 U₃ 、 U₄ 、 U₅ ,

绘制所有这五条曲线会产生图 3–2。

图 3–2:绘制好 A 和好 b 之间的差异图。(图片由作者提供)
图 3–2 中的无差异曲线由以下代码片段绘制:
请注意,每个函数的域限制是不同的。 U₁ 被限制为 A ∈ (0,10),而 U₂ 被限制为 A ∈ (1,10), U₃ 被限制为 A ∈ (2,10),以此类推。这是因为每条无差异曲线的方程都有不同的垂直渐近线,对于 U₁ 为 A = 0,对于 U₂ 为 A = 1,对于 U₃ 为 A = 2 等等。因为我们只关心在垂直渐近线右侧的方程的图形,所以我们将效用函数限制在垂直渐近线右侧的区域。figure 3–3 说明了这种域限制。

图 3–3:限制无差异曲线的范围。(图片由作者提供)
现在我们已经绘制了无差异图,我们需要绘制响应商品 A 价格上涨的变化的预算约束。实际上,这意味着两条对角线:一条从正 B 截距到正 A 截距,另一条从 B 截距到减少的正 A 截距。这代表了在商品 B 的最大数量保持不变的情况下,由于价格上涨,商品 A 的最大购买数量发生了变化。此外,因为我们想显示在预算约束收缩后消费者决策的变化,这两个预算约束必须与无差异曲线相切。我们将使用 U₃ 在 A = 4.7 处与原始预算约束相切,使用 U₂ 在 A = 3.3 处与新预算约束相切。原始和新的预算约束分别计算为等式B= 9.16-1.02A和B= 9.16-1.59A(这些是实际切线的近似值)。从逻辑上讲,这些预算约束的方程式可以预先计算或近似计算,可以手动计算,也可以用 WolframAlpha 或 Desmos 等软件计算。(比如用 WolframAlpha,输入tangent at x = 4.7 for y = 10/((x-2)^2)+3就能找到原来的预算约束方程,输出y = 9.14744–1.01611x。)使用我们之前用来绘制无差异曲线的 addplot 命令,我们可以绘制两个预算约束。
最后,为了让预算约束线在无差异图中脱颖而出,我们需要加粗它们。这是通过向 addplot 添加参数thick来实现的。预设的线条粗细有ultra thin、very thin、thin、thick、very thick或ultra thick——或者我们可以用line width = width参数定义自己的粗细,其中width以长度为单位。总的来说,预算约束行的代码片段如下:
将它们标绘在图上会产生图 3–4。

图 3–4:在图表上绘制预算约束。(图片由作者提供)
3.2.旗帜
在前面的小节中,我们使用两种颜色——红色和蓝色——来绘制函数。你可能想知道我们可以使用多少种颜色,或者如何定义新的颜色。记住,我们称之为 xcolor 包是因为它扩展了 LaTeX 处理和使用颜色的有限基础能力。
LaTeX 预定义了 19 种默认颜色:

(我在第 6.2 小节解释了如何制作这些彩色方块,生成红色方块的命令是\fcolorbox{black}{red}{\textcolor{red}{\rule{\fontcharht\fontX}{\fontcharht\fontX}}}。)
然而,如果这些颜色太有限,xcolor 提供了多种选项来定义更多的颜色。例如,dvipsnames 选项定义了另外 68 种颜色:

需要注意的是,这些颜色名称区分大小写。意思是 color = violet 不同于 color = VIOLET,color = Violet 会抛出错误。
为了添加 dvipsnames 调色板,我们使用 dvipsnames 选项调用 xcolor 包,所以,\usepackage[dvipsnames]{xcolor}。如果在 pgfplots 之后调用 usepackage 命令,将会引发错误。这是因为 TikZ 默认调用 xcolor,与这个新的 usepackage 命令冲突。所以要确保在 TikZ 包之前调用 xcolor 包。或者,如果这不起作用,那么我们可以将参数xcolor = {dvipsnames}添加到文档类命令中。对我们来说,我们会用,\documentclass[12pt, xcolor = {dvipsnames}]{article}。
除了 dvipsnames 之外,还有其他调色板,比如定义了 151 种颜色的 svgnames,或者定义了 317 种颜色的 x11names。此外,可使用命令\definecolor{name}{model}{variable 1, variable 2, variable 3}手动定义新颜色,其中模式为 rgb、HTML、cmyk 等。例如,浅棕色可由\definecolor {name}{rgb}{0.95, 0.95, 0.92}定义。
乳胶可以做很多颜色。我们可以给文本着色,使用背景色,并使页面颜色不同于标准的白色(尽管考虑到打印机的压力,这是不明智的)。其中大多数都超出了本指南的范围,因此不在讨论之列。第 8 节:资源中提供了资源,其中包括关于 xcolor 包的文档和指南。
3.3.绘制虚线部分
回到我们正在制作的无差异图和预算约束图,下一步是在上面画虚线。Pgfplots 有一个用于线图的命令,它会用一条穿过坐标的线将所有输入的坐标依次连接起来。原始决策点的坐标(原始预算约束与无差异曲线相切的位置)计算为(4.7,4.37)。因此,虚线将从 A 截距(4.7,0)到决策点(4.7,4.37),再到 B 截距(0,4.37)。执行此操作的命令如下:
正如所见,这条线经过的坐标应该列在花括号内。此外,因为我们希望这条线是虚线,所以我们包含了参数dashed。其他两个参数color = black和thick已经在前面的小节中介绍过了。它们分别将生成的线条涂成黑色,并使其线宽变粗。对(3.3,3.9)处的新决策点重复此过程,得到图 3–5。

图 3–5:在图表上绘制虚线。(图片由作者提供)
3.4.绘制坐标点
方便的是,在图形上施加黑色坐标点非常类似于画一条线。事实上,为了制作散点图,pgfplots 使用了以前用于绘制连接线段的相同命令。然后添加两个参数。第一个是mark = *,在线连接的每个坐标处画一个实心圆。可以使用“*”以外的标记,如“x”、“+”、“|”、“o”(所有这些看起来都像代表它的字符),以及许多其他标记。其次,为了删除这条线,我们编写了参数only marks。此外,为了增加标记的大小,我们添加了参数mark size = 3。总之,我们有以下命令来绘制坐标点:
在图上使用此命令会产生 Figure 3–6。

图 3–6:在图形上绘制坐标点。(图片由作者提供)
3.5.贴标签于
最后,为了完成这个图表,我们需要标注所有相关的部分。具体来说,我们需要在 A 截距和 B 截距、坐标点和函数端点附近放置字母。要标记图形的一部分,该命令采用以下形式:
该命令将在指定的坐标旁边放置一些文本,即标签。此外,标签在坐标附近的位置取决于位置,它可以是坐标的left、right、above或below。例如,要将标签 Qₐ 放在(4.7,0)下面,从原始决策点向下延伸的虚线与 a 轴相交的地方,我们编写以下命令:
我们之前在标注轴时已经使用了节点命令的一种形式。但是,请注意,轴标签的坐标点是不同的,因为它指的是轴的末端。使用标注命令来标注图形的其余部分,例如函数和坐标,我们最终得到图 3–7。

图 3–7:商品 A 的价格上涨对商品 A 和商品 b 之间的预算约束和无差异图的影响。(图片由作者提供)
最后,我们完成了无差异图,解释了需求下降的原因。这是因为预算从 M 转移到m′时q′ₐ少于 Qₐ 。更重要的是,我们用 LaTeX 来说明这一点!figure 3–7 由以下代码生成,结合了本节中涉及的所有元素:
4.经验曲线(例如市场均衡)
在本指南的开始,我假设我们正在处理一个空白图。也就是说,x 轴和 y 轴没有任何刻度,因为它们没有任何关联的值。这些类型的图表对理论模型很有用——适用于相关情况,无论数量级如何。然而,有时我们实际上有经验数据。在这种情况下,我们可能需要网格线,在网格线上绘制诸如函数、坐标、虚线等图形。需要做一些修改来将我们的“空白”图转换为服务经验值。具体来说,我们希望添加网格线,然后解决易读性问题,例如在图表中远离轴标签并在标签后面放置白色背景色以避免混乱。让我们转换一个市场均衡曲线,图 4-1,这样就能反映经验数据。

图 4–1:小工具市场的市场均衡(空白图上)。(图片由作者提供)
Figure 4–1 中的元素已经在前面的章节中解释过了。它由以下代码生成:
4.1.主网格线和次网格线
首先,我们希望轴记号和数字沿着两个轴运行。这在第 2.3 小节:轴刻度、缩放和剪裁中有简要介绍,但我们在这里主要是讨论如何移除它们。提醒一下,控制刻度的轴参数是xtick = {tick numbers}和ytick = {tick numbers}。我们希望它们是符合我们数据的数字,而不是只包含 0 或为空。在这种情况下,他们两个的滴答数集合将是{0,1,2,3,4,5,6, 7,8,9,10}。接下来,我们要在图表上添加网格线。我们仍然在使用轴参数,而不是命令。有用的是,有两种类型的网格线,主要网格线和次要网格线。主网格线从轴记号和数字开始延伸。同时,次要网格线从主要记号之间的较小记号延伸,并且没有与之关联的编号。他们都有不同的风格,因此可以传达更多的审美精度。要显示两个网格,包括轴参数grid = both。类似地,如果我们只需要一个网格或者不需要任何网格,我们将使用major、minor或none的值——尽管默认情况下两个网格都不显示。要设置主刻度之间的副刻度数,使用轴参数minor tick num = number,其中数字是副刻度数。在这种情况下,我们希望在主要刻度之间有一个次要刻度,否则会太混乱,所以次要刻度的数量是 1。此外,我们希望主要网格线是实线,次要网格线是虚线。这样,半增量和全增量在视觉上是明显的。为了将主网格线和次网格线的样式改为实线和虚线,我们分别使用grid style = solid和minor grid style = dotted。更多款式包括dashed、thin、thick。将网格样式定义为实心是多余的,因为这是默认的,但是我选择将其显式化。到目前为止,我们已经添加或修改了以下代码片段中的参数:
此代码片段在 x 轴和 y 轴上以 1 为增量添加从 1 到 10 的轴刻度。此外,它还显示次要网格线和主要网格线,将主要刻度之间的次要刻度数设置为一个刻度,并将主要网格线设置为实线,次要网格线设置为虚线。将这些参数用于空白市场均衡图,结果如图 4–2 所示。

图 4–2:在图形上添加网格线。(图片由作者提供)
啊!太丑了。虽然我们得到了我们正在寻找的轴记号和网格线,但这导致了该图可读性的其他问题。具体来说, Qₑ 和 Pₑ 的标签需要远离轴编号,轴标签应该稍微远离图形,并且 E 、 S 和 D 的标签需要具有白色背景以减少混乱。另外,原点有两个零——一个就够了。
4.2.分隔标签,给标签背景着色
从前一个问题开始,x 和 y 截距标签需要从图表中移走,以便为轴数腾出空间。回想一下标签的形式:
虽然在大多数情况下,位置可以很好地确定标注离坐标的距离,但有时我们希望增加这个距离。在这种情况下,可以用position = distance指定距离。因此,例如, Qₑ 的位置可以更改为below = 10pt。对 Pₑ 标签重复此操作,我们得到以下代码片段:
转到后一个问题。去掉 y 轴标签 0 和移动轴标签都可以通过改变图中显示的域和范围来完成。为了去除 y 轴上的 0,我们将范围的最小值定义为 0.01(任何非常小的数字都可以,尽管这取决于数据的数量级)。所以我们有ymin = 0.01作为轴参数。为了使轴标签远离,域和范围的最大值都应该扩展到 10.5(这也根据数量级而变化)。总之,我们有以下代码片段:
最后,我们想把图上标签的背景涂成白色。否则,我们将被穿过 E 、 S 和 D 的线卡住,这看起来不太好。这也涉及到一个相对简单的调整。我们将参数fill = white添加到标签的节点命令中。已更改标签的代码片段如下:
E 的标签也已使用上一段中解释的定位参数隔开 5 磅。这样做是为了避免用白色背景覆盖供需线。有其他方法可以解决这种不便。例如,因为 pgfplots 按顺序对这些图进行分层(后面的命令覆盖前面的命令),所以只需将标签的节点命令放在供应线和需求线曲线的上方即可。这样,供应和需求曲线的命令将在标签之后执行,并将覆盖白色背景。
综合所有这些调整,我们最终得到了图 4–3。

图 4–3:小工具市场的市场均衡。(图片由作者提供)
因此,我们已经完成了将空白图表调整为带有刻度和网格线的图表,以便它可以容纳经验数据并表示特定值。图 4–3 的代码是:
5.阴影、箭头和图表并排(例如。供应增加,价格接受公司)
通过绘制曲线、直线和坐标点可以做很多事情。但有时我们需要指出特征或指出图上的区域。这可以通过画箭头和用颜色在图上画阴影来实现。让我们用这些工具来解释丰收悖论:为什么农民的收入在大丰收后会下降。丰收对应于市场均衡图中供应向右移动,此时需求相对缺乏价格弹性。这在 Figure 5–1 中进行了描述。

图 5–1:丰收对农产品市场的影响。(图片由作者提供)
figure 5–1 是使用上述章节中已经解释过的工具绘制的。代码如下:
5.1.不透明度和透明度
现在,两条供给曲线都是同样不透明的红色。为了使原始供给曲线和新供给曲线变得明显,我们将使新供给曲线略微透明。我们通过向 addplot 命令添加参数opacity = opacity value来实现这一点,其中不透明度值是绘图不透明的百分比,范围从 0(完全透明)到 1(完全不透明)。使新曲线基本透明似乎表明它是新的。所以我们为它的 addplot 命令写opacity = 0.3。使新的供给曲线更加透明的结果如图 5-2 所示。

图 5–2:使图表上的新供应曲线更加透明。(图片由作者提供)
需要注意的是,xcolor 包——在 3.2 小节:颜色中提到过——提供了一种更简洁但可读性稍差的方法来使颜色透明。当使用一种颜色时,可以在颜色名称后使用一个感叹号,紧接着是不透明度值— colour name!opacity value。这个范围从 0 完全透明到 100 完全不透明。比如red!10多为透明,red!50为半透明,red!90多为不透明。(不过,如果我可以补充的话,那种“基本透明的红色”看起来更像鲑鱼。)

5.2.用颜色在方框中添加阴影
既然我们已经区分了哪条供给曲线是新的(不仅仅是使用标签S′),我们还想指出代表农民总收入损失和总收入增加的区域。这样,我们可以用图表显示总收入的损失远大于总收入的增加。用颜色填充图形区域的命令采用以下形式:
在这个命令中,(A)、(B)、(C)。。。,是一个不定数量的坐标。命令用指定的颜色在由坐标定义的多边形中着色。看图,原来的均衡点是(5.5,7.67),新的均衡点是(7,3.67)。这意味着,对于损失区域,我们需要将坐标(0,7.67)、(0,3.67)、(5.5,3.67)和(5.5,7.67)定义的正方形涂成橙色。类似地,我们将由坐标(5.5,0)、(5.5,3.67)、(7,3.67)和(7,0)定义的增益区域涂成绿色。到目前为止,我们有以下代码片段:
因为我们希望这些彩色区域不引人注目,所以颜色大部分需要透明。这可以用与前面小节“5.1 小节:不透明度和透明度”中所示的绘制方法相同的方法来完成,或者通过 TikZ 的不透明度参数,或者通过 xcolor 的感叹号符号。所以图表中使用的片段是:
不幸的是,我们不能简单地将这些命令放到 axis 环境中。有几个注意事项需要解决,以便这些彩色区域不会干扰和重叠图表上的其他要素。因为 pgfplots 按顺序处理命令,所以后面的图会叠加在前面的图上,所以填充命令应该在每隔一个命令之前,否则彩色区域会覆盖图形的另一部分。此外,由于填充命令不能在轴参数之前,应添加轴参数axis on top。这可确保轴线不会被绘图和其他命令覆盖。彩色区域如图 5–3 所示。

图 5–3:给图上的区域着色。(图片由作者提供)
5.3。直箭头和弯箭头
虽然此图通过阴影区域的相对大小说明了大丰收,但它缺少关于这些阴影区域代表什么的信息。当然,读者可以从上下文中推断出橙色区域代表总收入损失,绿色区域代表总收入增加,但我们也可以使图表对他们来说更具可读性。所以让我们用弯曲的箭头标出阴影部分。此外,由于我们想说明供给曲线从原始曲线到新曲线的移动,我们可以画一个从原始曲线到新曲线的直线箭头。
从后者开始,因为这更容易,绘制箭头的命令采用以下形式:
该命令绘制一个从(A)开始到(B)结束的箭头。B 处的箭头尖端由arrowhead值指定。TikZ 可供选择的箭头有限,所以这就是为什么我们在 1.3 小节:包中加载了arrows.meta TikZ 库。有了这个库,我们可以访问更多的箭头,如Triangle、Circle、Square、Diamond等等,这些箭头都是自描述的。Triangle箭头最适合显示从原始供应曲线到新供应曲线的转变。
使用此命令,我们将从原始供应曲线的右侧到新供应曲线的左侧绘制一个箭头。这对应于箭头为Triangle的从(6.3,8.5)到(8.3,8.5)的箭头。因此,我们有以下代码片段:
虽然这可以完成工作,但是我们应该做一些改变来提高箭头的美观性和可读性。首先,如果箭头是几乎透明的红色,以显示它正在修改供应曲线,那就更好了。方便的是,这就像我们之前做的用颜色填充区域和着色图一样。我们将red, opacity = 0.3作为参数写入箭头的绘制命令。第二,默认情况下,箭头很小。我们想让供应曲线移动箭头稍微大一点。这稍微难一点。首先,我们用花括号将箭头值括起来,表示我们正在使用自定义箭头。于是我们有了-{Triangle}。然后,作为箭头值的参数,我们添加参数length = 4mm, width = 2mm来修改箭头的尺寸。相应地,我们得到了供应转移箭头的最终代码片段:
使用此命令在图表上绘制箭头,结果如图 5–4 所示。

图 5–4:从原始供给曲线到新供给曲线画一条直线。(图片由作者提供)
接下来,我们将标记收益和损失区域。简单地从 x 轴下方放一个垂直箭头到绿色区域会显得过于杂乱。我们将让标签 Q 、Q′和一个箭头共享这个小空间。相反,让我们画一个弯曲的箭头,从轴的右下角指向绿色区域的中心,以逆时针方向弯曲,这样就可以避开 Q 和Q′标签。使用绘图命令绘制弯曲箭头,其形式为:
请注意,除了一处增加之外,这几乎与直线箭头的命令相同。在“到”后面的方括号内,参数指定了从起点坐标到终点坐标的角度,曲线的线应在该角度处绘制。它们以弧度表示,意思是从 0 度向右,到 90 度向上,到 180 度向左,到 270 度向下,再到 360 度向右,以及其间的一切。我事先发现,弯曲箭头的起始坐标是(10,-1.5),终止坐标是(6.5,0.7)。由于箭头应该从起点坐标向上弹出,并从右侧进入终点坐标,因此出角为 90 °,入角为 0°。所以产生指向增益区的弯曲箭头的代码片段是:
对损失区域、从顶部到橙色框的箭头重复此操作,并添加文本标签(在 3.5 小节:标签中讨论过),我们有以下代码片段:
将直箭头和弯箭头组合起来,就产生了 Figure 5–5。

图 5–5:大丰收悖论:为什么农民的收入在大丰收后会减少。(图片由作者提供)
figure 5–5 由以下代码生成:
5.4.并排排列图表
在经济学中,通常将市场均衡图与接受价格的厂商图并列排列。这有助于显示市场的变化是如何决定单个企业的运营价格的。更一般地说,我们将使用这个例子来演示如何将图表并排放置。假设我们想说明市场均衡中的价格下降是如何导致个体农民的总经济利润下降的。这种情况下的取价图如图 5–6 所示。

图 5–6:丰收对个体农民的影响。(图片由作者提供)
上图完全是用前面几节已经介绍过的工具和技术制作的。它是绘图、坐标点、标签、颜色和透明度等的组合。figure 5–6 由以下代码生成:
我们希望市场均衡图(图 5-5)位于面板的左侧,而价格接受公司(图 5-6)位于面板的右侧。令人惊讶的是,这其实很简单。我们所需要做的就是在第一个 axis 环境之后创建另一个 axis 环境,但是仍然在同一个 tikzpicture 环境中。这将把两个图形重叠在一起。然后,我们将参数shift = (axis cs: x-shift, y-shift)添加到第二个轴环境,其中 x 和 y 位移是第二个图形将从其原始位置移开的量。因为我们想将第二个图(价格接受公司)移到第一个图(市场均衡)的右边,所以我们将使用 17 的 x 位移和 0 的 y 位移。所以我们有:
最后,我们需要进行调整以适应页面上的图形。由于图片的宽度大于页边距所允许的范围,我们告诉 TikZ 通过加宽图片框架来忽略页边距。为此,我们在\begin{tikzpicture}前和\end{tikzpicture}后写下\hspace*{-3cm},分别将图片的框架向左和向右加宽 3 厘米。总之,将所有这些放在一起会产生图 5–7。

图 5–7:丰收对农产品市场(L)和个体农民(R)的影响。(图片由作者提供)
产生 Figure 5–7 的代码是:
使用命令和参数执行代码,分别生成图 1 和图 2 的图 5–5 和图 5–6。(同样,图 5–7 的完整代码可以在 GitHub 库https://github.com/jackypacky/pgf-econ-graphs上获得。)
5.5.曲线下区域的阴影
结束这一部分,我们将简要讨论如何对曲线下的区域进行着色,以及如何对两条曲线之间的区域进行着色,因为这在前面的示例中没有涉及。假设我们想用透明的蓝色在商品 A 和商品 B 之间的生产可能性边界下的区域涂上阴影,以说明存在的不一定有效的产出可能性。生产可能性边界如图 5-8 所示。

图 5–8:A 和 B 之间的生产可能性边界(无阴影)。(图片由作者提供)
产生 Figure 5–8 的代码是:
为了在两个函数之间的区域进行着色,我们使用了一个命令,其形式为:
其中parameters是修改 addplot 命令的参数,f和g是函数的名称路径。对于参数,我们将使用blue和opacity = 0.1来创建一个透明的蓝色,类似于我们在第 5.1 小节:不透明度和透明度中所做的。
然而,为了输入名称路径f和g,我们首先需要预先定义名称路径。为了定义曲线的名称路径,我们将参数name path = label添加到 addplot 命令中,其中label是我们想要分配给它的名称。因此,我们将使用 addplot 参数name path = frontier作为绘制生产可能性曲线的命令。因为我们需要另一个函数,在这个函数之间我们可以对这个区域进行着色,并且因为我们想要对曲线下的区域进行着色,我们需要绘制一条由公式 y = 0 定义的新曲线,并将其命名为axis。对于这个新的轴曲线,我们添加 addplot 参数line width = 0pt使其不可见——因为我们不希望它出现在图表上。
总的来说,生产可能性曲线下区域的阴影如图 5-9 所示。

图 5–9:A 和 b 之间的生产可能性边界
figure 5–9 由以下代码生成:
6.轴尺寸线、标签中的换行符和图例(例如消费税)
继续我们关于增强图表可读性的讨论,现在让我们在图表上加上尺寸线和图例。我们可以添加一个图例来显示每个区域所代表的内容,而不是在图表上放置一个标签来指向彩色区域。此外,为了表明两个轴标签之间的差异和含义,可以使用尺寸线。尺寸线是在图表、图形和绘图中用来表示大小、数量和距离(尺寸)的线。在尺寸线的每一端,都有一条垂直线与之相连,就像“T”一样。(尺寸线看起来有点像这样:“|—–|”。)
我们将使用消费税图表来说明尺寸线的用途。假设我们想说明汽油消费税的税收归宿。图 6–1 显示了这一点。

图 6–1:汽油消费税的影响。(图片由作者提供)
产生图 6–1 的代码是:
6.1.在标签中添加尺寸线和换行符
添加尺寸线的方式与绘制箭头的方式相同。回想一下,箭头命令采用以下形式:
我没有告诉你的是箭头可以被附加到箭头的末端和起点。因此,尾部和头部带有箭头的箭头的形状为:
尺寸线使用箭头“|”,这是对箭头外观的一种很好的符号描述。我们有:
Qₑ 和q′的区别在于征收消费税后消耗的气体量减少。为了在图上进行说明,我们将从q′的底部到 Qₑ 绘制一个尺寸指示器。在此之下,我们将添加一个带有文本“消费减少”的标签由于q′处于 Q = 4.14,而 Qₑ 处于 Q = 5.05,我们有:
我们想对消费者事件——在 Pₑ 和p′之间——和生产者事件——在 Pₛ 和 Pₑ 之间重复这一点。然而,简单地复制上面的内容是行不通的,因为标签宽度会太大。相反,我们需要在两个标签中的两个单词之间插入一个换行符。这是通过添加参数align = left来告诉堆叠的文本应该如何对齐(左对齐、右对齐或居中),并通过添加两个反斜杠—“\ \”—在我们希望换行的地方来完成的。因此,消费者和生产者事件的尺寸线和标签代码为:
添加一条从 Pₛ 到p′的尺寸线来表示总税收,并添加到目前为止提到的所有尺寸线和标签,结果如图 6–2 所示。

图 6–2:向图形添加尺寸线。(图片由作者提供)
6.2.添加图例
使用图例,我们可以指定图形上的区域 A 、 B 、 C 和 F 代表什么。添加图例结合了上一小节中使用的几个命令和参数。虽然 pgfplots 有自己内置的图例功能,但使用起来相当笨拙。相反,我们将使用一个文本标签,并将其放在图表的右上角。标签应该允许换行符,并有一个黑色的边框。因此,我们将使用:
回想一下,向节点命令添加参数align允许在标签中使用换行符——使用\\。此外,由于对齐方式设置为左对齐,文本行将向左对齐。这些在前面的小节中已经介绍过了。然而,新的是draw节点参数,它在标签周围画了一个黑色边框。在该命令中,图例的左上角将位于(10.5,10),这对应于图形的右上角,也就是我们希望图例所在的位置。添加描述这些区域代表什么的文本,我们有:
使用上面的节点命令向图表添加一个图例,我们得到 Figure 6–3。

图 6–3:向图表添加图例。(图片由作者提供)
照目前的情况来看,这个传说看起来有点无聊。为了提高图例的美观性,我们可以给字母 A 、 B 、 C 和 F 添加一个彩色背景,以便在视觉上将图例与它们所指向的彩色区域联系起来。如果 A 被涂成非常透明的蓝绿色, B 被涂成非常透明的紫罗兰色,以此类推,效果会很好。
幸运的是,xcolor 包有一个命令可以做到这一点。向文本添加彩色背景的 xcolor 命令采用以下形式:
在该命令中,框架颜色是环绕彩色背景边缘的颜色。背景色是文本后面的颜色。对于图例中的 A 文本,因为我们希望它的背景是非常透明的蓝绿色,所以我们需要的命令是:
请注意,该命令没有以分号结尾,这是因为它不是 TikZ 命令。

虽然 A 看起来不错,但是背景颜色的宽度是由其中文本的宽度决定的。因此,当我们对不同宽度的文本使用多种背景颜色时,遇到了一个障碍。例如, M 的背景颜色比 l 的背景颜色要宽。除了彼此,不一致的宽度是显而易见的。为了解决这个问题,我们将使用 makebox LaTeX 命令(\makebox[width]{text}),宽度为“X”,高度为(\fontcharht\fontX`)。这意味着我们将用来为字母制作彩色背景的命令是:
如果不把它分成几个部分来处理的话,看起来会让人不知所措。

你可能不熟悉重音符“”。在传统的美式键盘上,它位于左上角。它在 LaTeX 中有多种用途,包括引号和给字符添加重音符。(例如,使用`a`给“a”添加重音符会产生“à”。)这样, M 正好和 l 一样宽。不幸的是,控制高度要困难得多,我将在本指南中跳过它。如果我们想要一个没有文字的彩色框,我们可以使用:
运行上面的命令产生一个透明的蓝绿色的盒子。

这是因为\rule{width}{height}产生了一个矩形的墨水滴。顺便说一句,这就是我在本指南中介绍颜色的方式。最后,因为添加图例通常会使图表的宽度大于页边距所允许的宽度,所以我们在\begin{tikzpicture}之前和\end{tikzpicture}之后添加了\hspace*{-3cm}。当我们遇到同样的问题时,我们也在 5.4 小节:并排排列图表中这样做了。
总的来说,我们有以下图例的代码片段:
将此添加到图表中,我们得到图 6–4。

图 6–4:汽油税对消费、生产和总税收的影响。(图片由作者提供)
还有维奥拉。我们添加了尺寸线和图例,进一步传达了图表不同部分的含义。现在,任何人看到这张图表都会明白,消费者承担了大部分税收负担和汽油消费税的盈余损失——以及原因。(假设供给相对价格弹性,需求相对价格弹性。)产生图 6–4 的代码是:
7.结论
有了这个工具箱,你几乎可以创建任何经济图表。学习 pgfplots 和 LaTeX 的努力是值得的。一方面,大多数文字处理器,如 Google Docs(带 Google Sheets)和 word(带 Excel)可以提供经济图表的必需品——轴、线、曲线和标签——PGF plots 具有更大的灵活性,从阴影区域到添加虚线,再到添加尺寸线。一切都很有型。
当然,既然是乳胶,这只是冰山一角。每个人都站在巨人的肩膀上。首先,Donald Knuth 创建了 TeX 来排版文档。然后 Leslie Lamport 创造了 LaTeX 作为 TeX 的标记。然后 Till Tantau 在这个基础上创造了 TikZ 来创造图形。在这一系列事件之后、之前和之间,还有许多其他对我们所站的巨人做出贡献的人。最后,我们偶然发现,我们可以为经济学创造美丽而流畅的图表。
然而,我们总是可以做得更多。例如,我们可以通过定义变量和自动计算交点来自动创建图形。但是,这超出了本指南的范围。
如果我成功地完成了我的工作,你已经部分地扩展了 LaTeX 在制作图表这一领域的著名学习曲线。如果我没有,你就被抛弃了,没有任何关于如何开始你的提升的指导(至少,没有来自我的指导!).让我们期待前者。
8.资源
【LaTeX 入门和学习
- 德州直播:http://www.tug.org/texlive/。这是一个安装 TeX、TeXworks IDE 和一些普遍存在的包(包括 pgfplots 和 xcolor)的门户。
- 背面:【https://www.overleaf.com/】T2。如果安装 LaTeX 是一项太大的任务,那么有许多在线 LaTeX 编辑器足以完成本指南。背面已经安装了 pgfplots 和 xcolor,所以您不需要担心这一点。在定价方面,他们提供了一个免费版本,其中包含了所有的基本要素,只缺少一些专业功能,如 GitHub 集成或实时协作。
- 维基百科上的乳胶:【https://en.wikibooks.org/wiki/LaTeX】T4。本指南解释了 LaTeX 的基本工作原理。从安装 LaTeX 到制作文档,再到更多的技术方面,如盒子,Wikibook 都有解释和附带的代码。
- TeX-LaTeX 栈交换:https://tex.stackexchange.com/。Stack Exchange 是一个论坛,人们可以在这里提问和回答问题。该分论坛专门处理包括 pgfplots 在内的 LaTeX。当然,如果你有一个问题,首先使用搜索功能来检查它是否已经回答过。然后,看规则,问吧。
Pgfplots 和 TikZ 导轨
- Pgfplots 包装指南背面:https://www.overleaf.com/learn/latex/Pgfplots_package。Pgfplots 设计用于制作线形图和散点图。我们必须将它用于经济图表。但是,如果您想将 pgfplots 用于更常规的目的,请阅读下页的指南。
- Kevin Goulding 为经济学家提供的 use package { TikZ:http://static . latex studio . net/WP-content/uploads/2016/06/tikzforeconomists-110619150244-PHP app 01 . pdf。这是另一个用 LaTeX 制作经济图表的指南。然而,它使用 TikZ,并且是在 pgfplots 发布之前编写的。由于 pgfplots 是 TikZ 的依赖项,Kevin Goulding 指南中解释的代码可能适用于使用 pgfplots 制作经济图表。
包安装链接和文档
虽然上面的 LaTeX 编辑解决方案——TeX Live 和 over leaf——已经预装了这些包,但是如果你没有这些包或者想要最新的版本,你可以通过这些 CTAN 链接访问它们。此外,软件包手册可以在 CTAN 上找到,但要注意它们是数百页的技术文档。
- https://ctan.org/pkg/pgfplots CTAN PGF plots 套餐:。这个 pgfplots 包安装链接也是 TikZ 附带的。
- https://ctan.org/pkg/xcolor CTANxcolor 套餐。
9.其他示例
同样,本指南中所有完成的图表 PDFs 和代码——都可以在 GitHub 存储库中找到,包括下面的例子。该知识库可以在 https://github.com/jackypacky/pgf-econ-graphs的访问。

图 9–1:租金管制对租赁市场的影响。(图片由作者提供)

图 9–2:小经济体关税对国内市场(L)和进口市场(R)的影响。(图片由作者提供)

图 9–3:两个消费者 A 和 B 以及两种商品 x 和 y 之间的市场均衡 W′(图片由作者提供)
停止一步一步地构建你的模型。利用管道实现流程自动化!
在 Sci-kit 学习中使用管道

照片由 Rodion Kutsaev 在 Unsplash 上拍摄
为什么是管道?
当我开始在 Sklearn 中构建模型时,我会将每个预处理步骤分解到它的单元或代码块中。这是一个很好的开始方式,因为您可以很容易地将这些步骤分解成可读的块。然而,虽然它更容易阅读,但缺乏可重复性。下一次用新数据表示模型时,在对新数据运行模型之前,您必须运行转换数据的所有步骤,这带来了许多问题。例如,如果在一次热编码过程中创建了新的列,数据的维度可能会发生变化。
答案?管道
管道解剖
首先,像往常一样,导入需要运行这个脚本。
from sklearn.pipeline import Pipeline
from sklearn.compose import make_column_selector as selector
from sklearn.compose import ColumnTransformer
from sklearn. pre-processing import MinMaxScaler
from sklearn. pre-processing import OneHotEncoder
from sklearn.ensemble import RandomForestRegressor
柱式变压器
接下来是一个制作列转换器的函数。我更喜欢通过一个函数来调用它,以使代码更加可重用。
列转换器允许您将任意数量的预处理步骤合并到一个转换器中。在下面的例子中,我们有一个用于数字列的MinMaxScaler和一个用于分类值的OneHotEncoder。您可以在这些步骤中包括来自 Sklearn 的任何转换器。
他们的文档中有一个很好的例子:
混合类型的柱式变压器
除了演示数字列和分类列之外,还展示了列选择器,它允许您根据不同的标准选择列。通过selector(dtype_exclude="object")选择数字列。下面演示了通过名称选择列作为一个简单的 python 列表。您可以将这些精选的样式与您提供的不同变压器相结合。此外,您在下面命名您的变压器,如num和cat; see,以便稍后在您的 fit 模型中识别。
def make_coltrans():
column_trans = ColumnTransformer(transformers=
[('num', MinMaxScaler(), selector(dtype_exclude="object")),
('cat', OneHotEncoder(dtype='int', handle_unknown='ignore'), ['CAT_FIELD_ONE', 'CAT_FIELD_TWO'])],
remainder='drop')
return column_trans
管道
在创建列转换器之后,现在创建管道是一个非常简单的步骤。您所需要做的就是根据您通常采取的步骤的逻辑顺序对管道序列进行排序。这里我们有两个步骤,列转换器和分类器。我们在下面的列转换器中将这些步骤命名为prep和clf。
def create_pipe(clf):
'''Create a pipeline for a given classifier.
The classifier needs to be an instance
of the classifier with all parameters needed specified.'''
# Each pipeline uses the same column transformer.
column_trans = make_coltrans()
pipeline = Pipeline([('prep',column_trans),
('clf', clf)])
return pipeline
创建和拟合模型
最后,我们可以创建分类器的实例,并将其传递给上面创建管道的函数。
# Create the classifier instance and build the pipeline.
clf = RandomForestClassifier(random_state=42, class_weight='balanced')
pipeline = create_pipe(clf)
# Fit the model to the training data
pipeline.fit(X_train, y_train)
摘要
上面演示了设置管道的简单性。第一次这样做时,与独立完成每一步相比,可能会有些困惑。好处是,每当您想要将它应用到一个新模型,或者更好的是,针对您的 fit 模型运行新数据时,所有的数据转换都会自动发生。
如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。一个月 5 美元,让你可以无限制地访问成千上万篇文章。如果你使用我的链接注册,我会赚一小笔佣金,不需要你额外付费。
使用 Plotly Express 创建交互式散点图
使用测井数据创建交互式散点图的示例

图示测井数据的散点图。图片作者。
散点图允许我们从一个数据集中绘制两个变量,并对它们进行比较。从这些图中,我们可以了解这两个变量之间是否存在关系,以及这种关系的强度如何。
在岩石物理散点图中,通常称为交会图。它们通常用作岩石物理解释工作流程的一部分,可用于多种任务,包括:
- 用于粘土或页岩体积计算的粘土和页岩端点识别
- 离群点检测
- 岩性识别
- 碳氢化合物识别
- 岩石分类
- 回归分析
- 更多
在这个简短的教程中,我们将看到如何使用流行的 Python 绘图库 Plotly 生成散点图。
普洛特利图书馆
Plotly 是一个基于网络的工具包,用于生成强大的交互式数据可视化。这是非常有效的,可以用很少的代码行生成图。这是一个受欢迎的库,包含各种各样的图表,包括统计、金融、地图、机器学习等等。
Plotly 库有两种主要用途:
- Plotly Graph Objects,这是一个用于创建图形、轨迹和布局的低级接口
- Plotly Express,它是 Plotly 图形对象的高级包装器。Plotly Express 允许用户键入更简单的语法来生成相同的绘图。
这也是我们在本教程中所要关注的。在以下教程中,我们将了解如何:
- 创建用分类数据着色的 2D 散点图
- 创建用连续数据着色的 2D 散点图
- 将轴设置为对数
我的 YouTube 频道上有本教程的视频版本:
Jupyter Plotly 教程
导入库
在本教程中,我们将使用两个库。Pandas,作为 pd 导入,将用于加载和存储我们的数据,以及 Plotly Express,这是本教程的主要重点,将用于生成交互式可视化。
import plotly.express as px
import pandas as pd
加载&检查数据
我们将在本文中使用的数据集来自 Xeek 和 FORCE(https://xeek.ai/challenges/force-well-logs/overview)举办的岩性预测机器学习竞赛。竞赛的目的是从由 98 口训练井组成的数据集中预测岩性,每口井的测井完整性程度不同。目的是根据测井测量预测岩相。要下载该文件,请导航到上面链接的数据部分。原始数据源可在:https://github . com/bolgebrygg/Force-2020-机器学习-竞赛下载
一旦数据加载完毕,我们可以通过调用df来查看数据帧。正如您在下面看到的,数据集有 18,270 行和 30 列,这使得它很难在单一视图中可视化。因此,pandas 会截断显示的列数。

为了查看所有的列,我们可以调用df.columns来查看所有可用的列:
Index(['WELL', 'DEPTH_MD', 'X_LOC', 'Y_LOC', 'Z_LOC', 'GROUP', 'FORMATION','CALI', 'RSHA', 'RMED', 'RDEP', 'RHOB', 'GR', 'SGR', 'NPHI', 'PEF','DTC', 'SP', 'BS', 'ROP', 'DTS', 'DCAL', 'DRHO', 'MUDWEIGHT', 'RMIC','ROPA', 'RXO', 'FORCE_2020_LITHOFACIES_LITHOLOGY', 'FORCE_2020_LITHOFACIES_CONFIDENCE', 'LITH'],
dtype='object')
现在我们可以看到我们所有的列,如果需要,我们可以很容易地调用它们。
创建简单的 2D 散点图
用 plotly express 创建散点图非常简单。我们调用px.scatter并传入数据帧,以及 x 轴和 y 轴的关键字参数。
px.scatter(df, x='NPHI', y='RHOB')

体积密度(RHOB)与中子孔隙度(NPHI)的简单 2D 散点图。图片由作者提供。
当我们运行上面的代码时,我们得到了密度(RHOB)和中子孔隙度(NPHI)数据的基本散点图。
当处理这种类型的数据时,通常将 y 轴(RHOB)从大约 1.5 克/立方厘米缩放到大约 3 克/立方厘米,并将缩放比例反转,使得最大值在轴的底部,最小值在轴的顶部。
对于 x 轴,数据通常从-0.05 到 0.6,但是,当我们有超过 0.6 的数据点时,我们将最大值设置为 1(代表 100%孔隙度)。
为此,我们需要传入两个参数:range_x和range_y。要反转 y 轴,我们可以先传递最大的数字,然后传递最小的数字,就像这样:range_x=[3, 1.5]。
一旦我们添加了范围参数,我们将得到以下代码:
px.scatter(df_well, x='NPHI', y='RHOB', range_x=[-0.05, 1], range_y=[3, 1])

体积密度(RHOB)与中子孔隙度(NPHI)的简单 2D 散点图。图片由作者提供。
将轴改为对数轴
有些情况下,我们希望以对数刻度显示数据。这可以应用于单个轴或两个轴。
在下面的例子中,我们使用的数据略有不同。该数据是从沿着岩心样本以指定间隔进行的岩心栓测量中获得的。
core_data = pd.read_csv('L05_09_CORE.csv')
core_data

熊猫生成的井芯数据的前五行和后五行。作者图片
现在让我们创建一个简单的散点图,称为 poro-perm 交会图。这种类型的绘图通常用于分析岩心数据内的趋势,并推导岩心测量孔隙度和渗透率之间的关系。然后,可将其应用于测井得出的孔隙度,以预测连续渗透率。
和以前一样,创建散点图就像调用px.scatter一样简单。
px.scatter(core_data, x='CPOR', y='CKH', color='CGD', range_color=[2.64, 2.7])

在线性-线性标度上绘制的岩心孔隙度与岩心渗透率。图片由作者提供。
我们可以看到生成的图看起来不太对。这是因为渗透率(CKH)的范围可以从低至 0.01 mD 到数千 mD。为了更好地理解数据,我们通常以对数标度显示。
为此,我们可以添加一个名为log_y的参数,然后指定我们想要显示数据的对数范围。在这种情况下,我们将设置为 0.01 至 1,000 mD。
px.scatter(core_data, x='CPOR', y='CKH', log_y=[0.01, 1000])

岩心孔隙度与岩心渗透率以对数线性标度绘制。图片由作者提供。
用连续变量添加颜色
为了更深入地了解我们的数据,我们可以通过在颜色参数中设置第三个变量,将它添加到散点图中。在这个例子中,我们将传入 GR(伽马射线)曲线。
px.scatter(df_well, x='NPHI', y='RHOB', range_x=[-0.05, 1], range_y=[3.5, 1], color='GR')

如你所见,颜色有点暗淡。这是因为 GR 曲线的范围从 0 延伸到超过 400 API 的值。通常这种类型的数据在 0 到 150 API 的范围内。为了显示第三个变量的更多细节,我们可以通过设置一个从 0 到 150 的range_color参数来改变颜色范围。
px.scatter(df_well, x='NPHI', y='RHOB', range_x=[-0.05, 1], range_y=[3.5, 1], color='GR', range_color=[0,150])

用伽马射线着色的中子孔隙度与体积密度散点图。图片由作者提供。
用分类变量添加颜色
我们还可以使用分类变量来可视化数据中的趋势。通过将 dataframe 中的GROUP列传递到color参数中,这可以很容易地添加到我们的散点图中。
px.scatter(df_well, x='NPHI', y='RHOB', range_x=[-0.05, 1], range_y=[3.5, 1], color='GROUP')

中子孔隙度与体积密度散点图,按地质分组着色。图片由作者提供。
如果我们只想显示几个组,我们可以在图例中的名称上单击鼠标左键,这将关闭该组。

中子孔隙度与体积密度散点图,由选定的地质组着色。图片由作者提供。
摘要
从上面的例子可以看出,Plotly Express 是一个强大的数据可视化库。它允许你用最少的代码创建非常强大和交互式的情节。颜色形式的额外信息可以增强我们对数据的理解,以及它是如何分布在不同类别中或随着另一个变量而变化的。
感谢阅读!
如果你觉得这篇文章有用,请随时查看我的其他文章,这些文章从不同的角度研究了 Python 和测井数据。你也可以在 GitHub 找到我在这篇文章和其他文章中使用的代码。
如果你想联系我,你可以在LinkedIn或者我的 网站 找到我。
有兴趣了解更多关于 python 和测井数据或岩石物理学的知识吗?跟我上 中 。
如果你喜欢阅读这些教程,并想支持我作为一名作家和创作者,那么请考虑报名成为一名媒体成员。一个月 5 美元,你就可以无限制地阅读数千篇各种主题的文章。如果您使用 我的链接 ,注册,我将为您赚取一小笔佣金,无需额外费用!
https://andymcdonaldgeo.medium.com/membership
在 C #中使用指针
简介

亚历山大·巴甫洛夫·波德瓦尼摄于 Pexels
假设您熟悉编程概念,并熟悉 Python 或 r 等现代高级语言。C 编程语言自 20 世纪 70 年代初就已出现,是一种通用编程语言,用于从网络编程到视频游戏,再到数据科学的各种用途。与 Python 等语言不同,C 是一种编译语言。这意味着它通常需要更长的开发时间,因为每次运行时都需要编译。另一方面,最终编译的程序将比解释语言运行得快得多。一些依赖于性能的 Python 包,如 numpy 和 pandas 有一部分是用 C 语言编写的,C 也可以用来创建速度很重要的数据产品。C 语言的一个特点是它使用指针。这篇简短的文章将通过几个例子介绍 C 语言中的指针。
让我们首先快速浏览一个基本的 C 程序及其结构。
#include <stdio.h>int main(void);int main(void)
{
printf("Hello world\n\r");
return 0;
}
对于不熟悉 C 语言的人来说,上面的程序会在屏幕上显示消息“Hello world”。第一行包括标准的输入和输出头文件,其中包含有用的函数,如用于输出消息的 printf() 。下一行是一个函数原型,告诉编译器将有一个名为 main 的函数,它将返回一个整数值,并且不带任何参数。其次是函数本身。所有的 C 程序都需要一个名为“main”的函数,它是第一个被执行的函数。在函数内部(由左大括号和右大括号{})我们有 main 函数的主体,它使用 printf() 函数显示文本字符串“Hello world ”,后跟换行符“\n”和返回符“\r”。最后返回数字零。这可以用来检查程序是否仍在运行。
什么是指针?
指针是一种变量,它允许我们通过内存中的地址来存储另一个变量在内存中的位置。变量名就像是我们引用这个位置的标签,所以我们可以访问存储在那个位置的值。在 C 语言中可以使用指针,原因有很多,包括:
- 微控制器编程
- 创建动态数据结构(如链表)
- 访问存储在数组中的信息
- 优化程序以运行更快和/或使用更少的内存
如果我们声明一个整数变量,并将其命名为 num ,我们可以给它赋值(例如 10):
int num = 10;
这类似于创建一个“盒子”来存储值,并给这个盒子一个标签来引用里面的值,如下图所示。

作者图片
我们实际上可以使用 C 语言中的&符号(操作符的地址)输出变量存储的地址(内存中的位置)。
printf("Address of num is %p", &num);
这给出了输出:
Address of num is 0x7ffe472b7514
当然,根据数据在实际设备内存中的存储位置,这个输出在不同的机器上会有所不同。指针是为处理这种类型的数据而设计的,可以在 C 语言中通过在变量名前加一个星号(*)来声明,也称为间接或取消引用运算符,因为它是间接工作的。
int *ptr = #
这里我们创建一个指向整数的指针(指针类型必须与它所指向的对象类型相同)并用变量 num 的地址初始化它。下图以图形方式显示了这一点,其中方框代表内存中的位置。我们可以在盒子的前面看到变量名,在盒子的顶部看到存储的内容。你可以看到变量 num 存储在内存中的位置 0x7ffe472b7514,这也是指针变量的值。

作者图片
这允许我们通过指针间接地操作存储在那个位置的值。
我们可以写一个简短的程序来输出变量的值、变量的地址、指针以及指针所指向的值:
#include <stdio.h>int main(void);int main(void)
{
int num = 10;
int *ptr = # printf("Value of num = %d", num);
printf("\n\rAddress = %p", &num);
printf("\n\rPointer ptr = %p", ptr);
printf("\n\rValue pointer is pointing at = %d", *ptr);
return 0;
}
百分比符号是占位符,指示要放在文本字符串中的值的类型。 %d 用于整数, %p 用于指针的值。该代码产生以下输出:
Value of num = 10
Address = 0x7ffe472b7514
Pointer ptr = 0x7ffe472b7514
Value pointer is pointing at = 10
这有什么帮助?
本质上,你可以使用一个指针指向任何相同类型的变量(例如,任何整数、双精度、字符等)。).这可用于改变/更新相同类型的许多不同变量的值。使用指针时,执行时间也更快。更有效地访问存储器。它们还可以用来访问数组和结构的元素,并通过引用而不是通过值将参数传递给函数。它们对于需要访问特定内存位置的低级编程任务也很有用。我们将快速浏览一下其中的一些特性。
指针符号
可以用几种不同的方式声明指针。以下两个例子都是有效的:
int* p = &i;
或者
int *p = &i;
您还可能遇到箭头操作符(->),它与指向结构或联合的指针变量一起使用。
结构是允许几种不同的变量类型一起存储在单个数据结构中的数据结构。在这个例子中,我们创建了一个名为 person 的结构,它包含一个由 100 个字符组成的数组(一个字符串)来表示这个人的名字,一个整数表示他们的年龄,一个浮点数表示他们的薪水。然后,我们创建一个指向这个名为 deptManager (部门经理)的结构的指针,并将其设置为 NULL 。在 main 函数中,我们使用内存分配函数 malloc 为这个结构创建一些内存,然后使用指针的名称,后跟箭头操作符来提供姓名、年龄和薪水的值。我们使用字符串库(string.h)中的字符串复制函数( strcpy )将名称值复制到字符串变量中。最后,我们用 printf 函数输出这些值。
#include <stdio.h>
#include <string.h>int main(void);struct person {
char name[100];
int age;
float salary;
};struct person *deptManager = NULL;int main(void)
{
deptManager = (struct person*)malloc(sizeof(struct person));
strcpy(deptManager->name, "Derek Sanders");
deptManager->age = 47;
deptManager->salary = 22.000; printf("Department manager's name = %s", deptManager->name);
printf("\n\rDepartment manager's age = %d", deptManager->age);
printf("\n\rDepartment manager's salary = %f", deptManager->salary);
return 0;
}
哪些输出:
Department manager's name = Derek Sanders
Department manager's age = 47
Department manager's salary = 22.000
写 deptManager- >年龄与写 (*deptManager)相同。年龄
您还可以通过在变量前添加更多星号来创建指针对指针等:
*p;
**p;
***p;
…

作者图片
指针和数组
我们也可以在数组中使用指针。例如——我可以使用一个字符数组生成一个文本字符串,并将其命名为 myName ,将其值设置为我的名和姓。我可以使用索引(在 C 中从 0 开始)来访问数组的元素。为了得到字母“A”,我可以输出数组的第一个元素我的名字【0】。这种表示法掩盖了指针的使用,所以我们可以使用指针表示法来达到同样的目的:
int main(void)
{
char myName[] = "Alan Davies"; printf("The first letter of my name is %c", myName[0]);
printf("\n\rThe first letter of my name is %c", *(myName));
printf("\n\rThe first letter of my surname is %c", myName[5]);
printf("\n\rThe first letter of my surname is %c", *(myName + 5)); return 0;
}
对于后续元素,我们可以只添加索引,因此对于第 5 个元素“D ”,我们可以添加 5,以此类推。这会产生以下输出:
The first letter of my name is A
The first letter of my name is A
The first letter of my surname is D
The first letter of my surname is D
这也可以应用于更高维的阵列。我们可以编写一个程序来创建一个 8×8 的矩阵,并用随机数填充它。我们可以这样做:
#include <stdio.h>
#include <stdlib.h>int main(void);int main(void)
{
int x, y;
int data[8][8]; for(x=0; x<8; x++)
{
for(y=0; y<8; y++)
{
data[x][y] = rand()%100;
}
}
return 0;
}
这里我们声明一个二维数组来存储矩阵值。然后,我们使用两个 for 循环遍历矩阵中的行和列,使用来自 stdlib 库的 rand() 函数添加一个随机数。这可能会产生类似下面的 if 输出。
49 21 62 27 90 59 63 26
40 26 72 36 11 68 67 29
82 30 62 23 67 35 29 2
22 58 69 67 93 56 11 42
29 73 21 19 84 37 98 24
15 70 13 26 91 80 56 73
62 70 96 81 5 25 84 27
我们可以使用指针用更紧凑的代码实现同样的事情:
#include <stdio.h>
#include <stdlib.h>int main(void);int main(void)
{
int i, data[8][8]; for(i=0; i<64; i++)
*(*data + i) = rand()%100;
return 0;
}
在这个例子中,我们使用指针指向内存中的矩阵位置,并为每个矩阵元素生成一个随机数。这与前一个例子归档了相同的最终目标,但是使用了指针。
使用指针访问多维数组中的数据有几种不同的方式。例如,要获取第一行和第一列中的元素,我们通常会写:
data[0][0]
我们可以写:
*data[0]
或者甚至:
**data
对于零以上的元素,如 data[0][1] ,我们可以编写以下任何一个:
data[0][1]
*(data[0] + 1)
*(*data + 1)
指针和变量范围
指针的另一个有用的方面与变量范围有关。假设您想创建一个简单的函数,将传递给它的变量的值加倍。您可以编写一个简单的函数,将数字作为参数,然后将值加倍并输出。如果在调用 double 函数之前和之后输出变量的值,变量的值将保持不变,因为我们没有更新原始变量的值。
#include <stdio.h>int main(void);
void doubleVariable(int);void doubleVariable(int var)
{
var = var * 2;
printf("\n\rvariable = %d", var);
}int main(void)
{
int num = 10; printf("num = %d", num);
doubleVariable(num);
printf("\n\rnum after doubling = %d", num);
return 0;
}
因此,输出将是:
num = 10
variable = 20
num after doubling = 10
如果我们想更新原始值,我们可以改变程序来使用指针。
#include <stdio.h>int main(void);
void doubleVariable(int*);void doubleVariable(int *var)
{
*var = *var * 2;
printf("\n\rvariable = %d", *var);
}int main(void)
{
int num = 10; printf("num = %d", num);
doubleVariable(&num);
printf("\n\rnum after doubling = %d", num); return 0;
}
它产生:
num = 10
variable = 20
num after doubling = 20
注意,C 知道在乘法运算中使用星号和在基于上下文的指针中使用星号的区别。在这个例子中,我们将变量的地址传递给一个指针参数,它可以更新原始变量。在某些情况下,这可以作为创建全局变量的替代方法,并通过引用而不是通过值来传递变量。
指针和字符串
C #中的字符串表示为字符数组。您可以明确指定在方括号之间使用多少个字符,或者由字符串的长度来定义:
char myStr[20] = "This is my string";
char myStr[] = "This is my string";
我们可以更新这些字符串的值。例如,我们可以在原始字符串上复制一个新字符串:
char myStr[20] = "This is my string";
printf("myStr = %s", myStr);
strcpy(myStr, "New string");
printf("\n\rmyStr = %s", myStr);
它将输出:
myStr = This is my string
myStr = New string
我们也可以将指针与字符串一起使用:
char *newStr = "A new string";
我们可以用同样的方式输出字符串:
printf("\n\rnewStr = %s", newStr);
我们不能像以前那样更新这个字符串。如果你以这种方式声明一个字符串,它被大多数编译器存储在只读存储器中,所以不能被更新。这对于创建不可变的字符串很有用。我们也可以使用内存分配函数为一个字符串创建可以访问的内存。然后我们可以更新字符串。
char *newStr = (char*)malloc(sizeof(char)*4);
newStr = "Alan";
printf("String = %s", newStr);
newStr = "Dave";
printf("\n\rString = %s", newStr);
使用字符指针也适用于单个字符。当与 string 一起使用时,它们实际上指向一个空终止字符串的第一个字符(\0)。
最后,我们可以用它们来节省内存。例如,假设我们有一个水果列表(一个字符串数组)。我们可以说明列表中有多少项(即 5),然后每个项有多少个字符(即 15)。我们也可以像这样输出第一项“苹果”。
char fruitList[5][15] = {
"Apples",
"Pears",
"Oranges",
"Bananas",
"Mangos"
};printf("Fruit[0] is %s", fruitList[0]);
这里的缺点是,我们使用了比我们需要(即 75 字节)更多的字节(每个字符是一个字节)来存储数据,因为我们允许每个单词最多 15 个字符,即使他们不需要全部 15 个字符。我们可以通过使用指针来改进这一点,因此我们只使用我们需要的字节数:
char *fruitList[5] = {
"Apples",
"Pears",
"Oranges",
"Bananas",
"Mangos"
};printf("Fruit[0] is %s", fruitList[0]);
一句警告的话
指针的误用也会导致问题。权力越大,责任越大——您可能会覆盖内存中存储的其他信息,带来灾难性的后果。当您声明一个指针并试图在指向某个地方之前更新它的值时,就会发生这种情况。例如:
int *p;
*p = 10;
应该是这样的:
int i, *p;
p = &i;
*p = 10;
总之
c 语言是一种编译语言,与许多其他现代语言相比,它更接近于计算机的一些低级功能。c 程序编译运行很快。当我们需要为性能(例如实现机器学习算法)或用其他语言(例如 Python)创建数据科学库编写最佳代码时,这可能特别有利。虽然指针最初很难掌握,但它是一个特别强大的工具,可以以多种不同的方式使用,包括优化程序以使用更少的内存和/或运行更快。
使用 PostgreSQL 作为数据仓库
经过一些调整,Postgres 可以成为一个很好的数据仓库。下面是如何配置的。

在旁白我们支持许多数据仓库,包括 Postgres。尽管 Postgres 是为生产系统设计的,但稍加调整,它可以作为数据仓库工作得非常好。
对于那些想切入正题的人,这里有 TL;博士;医生
- 不要使用与您的生产系统相同的服务器
- 升级到 pg 12+(或者在查询中避免通用表表达式
- 轻松完成指标——少即是多
- 考虑对长表进行分区
- 确保你没有被 I/O 束缚
- 批量插入后真空分析仪
- 探索并行查询
- 增加统计抽样
- 在经常查询的表上少用列
- 在规模上考虑一个专用仓库
数据仓库和关系数据库的区别
生产查询
典型的生产数据库查询从潜在的大型数据集中选择少量的行。它们被设计用来快速回答许多这类问题。
想象一个 web 应用程序——成千上万的用户可能同时查询
select * from users where id = 1234
数据库将被调优以快速处理大量这样的请求(在几毫秒内)。
为了支持这一点,包括 Postgres 在内的大多数数据库都是按行存储数据的——这允许从磁盘上高效地装载整行数据。他们经常使用索引来快速查找相对较少的行。
分析查询
分析查询通常相反:
- 一个查询将处理许多行(通常占整个表的很大一部分)
- 查询可能需要几秒到几分钟才能完成
- 一个查询将从一个宽(多列)表中选择少量的列
因此,专用数据仓库(如 Redshift、BigQuery 和 Snowflake)使用面向列的存储,没有索引。
Holistics.io 有一个很好的指南更详细地解释了这一点。
这对 Postgres 意味着什么
Postgres 虽然是面向行的,但也可以轻松处理分析查询。它只需要一些调整和一些测量。虽然 Postgres 是一个很好的选择,但是请记住,像雪花这样基于云的仓库(从长远来看)更容易管理和维护。
将 Postgres 配置为数据仓库

图片由 PostgreSQL 提供
警告:不要将您的生产 Postgres 实例用于数据报告/指标。一些查询是没问题的,但是分析工作负载与典型的生产工作负载有很大不同,它们将对生产系统产生相当大的性能影响。
避免常见的表表达式
公共表表达式(cte)也称为“WITH”查询。它们是避免深度嵌套子查询的好方法。
WITH my_expression AS (
SELECT customer as name FROM my_table
)
SELECT name FROM my_expression
不幸的是,Postgres 的查询规划器(在版本 12 之前)将 CTEs 视为一个黑盒。Postgres 将有效地自己计算 CTE,具体化结果,然后在使用时扫描结果。在许多情况下,这会大大降低查询速度。
在讲述者中,从我们的一些常见查询中删除 3 个 cte 使它们的速度提高了 4 倍。
简单的解决方法是将 cte 重写为子查询(或者升级到 12)。
SELECT name FROM ( SELECT customer as name FROM my_table )
cte 越长,可读性就越差,但对于分析工作负载,这种性能差异是值得的。
谨慎使用索引
索引对于分析工作负载的重要性实际上不如传统的生产查询。其实像红移、雪花这样的专用仓根本没有。
虽然索引对于快速返回少量记录很有用,但是如果查询需要表中的大部分行,它就没有用了。例如,对“讲述人”的一个常见查询是这样的
获取每个客户打开的所有电子邮件,并计算按月分组查看主页的转化率。
不用写出 SQL,很明显这个查询可以覆盖很多行。它必须考虑所有客户、所有打开的电子邮件和所有页面浏览量(其中 page = '/')。
即使我们为这个查询准备了一个索引,Postgres 也不会使用它——在加载许多行时进行表扫描会更快(磁盘上的布局更简单)。
不使用索引的理由
- 对于许多分析查询,Postgres 进行表扫描比索引扫描更快
- 索引增加了表的大小。表格越小,内存就越大。
- 索引增加了每次插入/更新的额外成本
什么时候使用指数
有了索引,有些查询会快得多,值得花时间。对我们来说,我们经常第一次询问客户做了什么。我们为此创建了一个列(activity_occurrence),所以我们构建了一个部分索引。
create index first_occurrence_idx on activity_stream(activity) where activity_occurrence = 1;
分割
对表进行分区是提高表扫描性能的一个很好的方法,而不需要支付索引的存储成本。
从概念上讲,它将一个较大的表分成多个块。理想情况下,大多数查询只需要从一个(或少数几个)中读取,这可以极大地加快速度。
最常见的场景是按时间(range partitioning)分解事物。如果您只查询上个月的数据,那么将一个大表分成几个月的分区可以让所有查询忽略所有旧的行。
在“讲述人”中,我们通常查看所有时间的数据,因此范围没有用。但是,我们有一个非常大的表来存储客户活动(查看页面、提交支持请求等)。我们很少一次查询一两个以上的活动,所以list partitioning非常好用。
好处是双重的:我们的大多数按活动的查询无论如何都要进行全表扫描,所以现在它们扫描的是一个更小的分区,我们不再需要大的活动索引(它主要用于不太频繁的活动)。
对分区的主要警告是,它们需要管理的工作量稍微多一些,并且并不总是能提高性能——创建太多分区或大小极不相等的分区并不总是有帮助。
最小化磁盘和 I/O
因为表扫描更常见(参见上面的索引),所以磁盘 I/O 变得相当重要。按性能影响排序
- 确保 Postgres 有足够的可用内存来缓存最常访问的表——或者使表变小
- 选择固态硬盘而不是硬盘(尽管这取决于成本/数据大小)
- 查看有多少 I/O 可用——如果数据库读取磁盘太多,一些云托管提供商会限制 I/O。
检查长时间运行的查询是否命中磁盘的一个好方法是使用pg_stat_activity表。
SELECT pid, now() - pg_stat_activity.query_start AS duration, usename, query, state, wait_event_type, wait_event FROM pg_stat_activity WHERE state = 'active' and (now() - pg_stat_activity.query_start) > interval '1 minute';
如果查询是从磁盘读取,那么wait_event_type和wait_event列将显示IO和DataFileRead。上面的查询对于查看其他可能阻塞的东西也非常有用,比如锁。
批量插入后抽真空
清空表是保持 Postgres 平稳运行的一个重要方法——它节省空间,当作为vacuum analyze运行时,它将计算统计数据,以确保查询规划器正确地估计一切。
Postgres 默认运行一个自动真空进程来处理这个问题。通常最好不要去管它。
也就是说,vacuum analyze最好在插入或删除大量数据后运行。如果您正在运行一个定期插入数据的任务,那么在您完成插入所有内容后立即运行vacuum analyze是有意义的。这将确保新数据会立即有统计数据,以便进行高效的查询。一旦你运行了它,自动吸尘程序就会知道不要再吸尘了。
看看并行查询
Postgres,当它可以的时候,将并行运行部分查询。这是仓储应用的理想选择。并行查询增加了一点延迟(必须产生工作人员,然后将他们的结果一起返回),但对于分析工作负载来说,这通常无关紧要,因为查询需要几秒钟。
实际上,并行查询大大加快了表或索引扫描的速度,这也是我们的查询需要花费大量时间的地方。
查看它是否按预期运行的最好方法是使用explain。您应该会看到一个Gather后跟一些并行工作(连接、排序、索引扫描、序列扫描等)
-> Gather Merge (cost=2512346.78..2518277.16 rows=40206 width=60) Workers Planned: 2 Workers Launched: 2 ... -> Parallel Seq Scan on activity_stream s_1
工作者是并行执行工作的进程的数量。工人数量由两个设置控制: max_parallel_workers 和max _ parallel _ workers _ per _ gather
show max_parallel_workers; -- total number of workers allowed show max_parallel_workers_per_gather; -- num workers at a time on the query
如果你使用explain(analyze, verbose),你可以看到每个工人花费了多少时间,处理了多少行。如果数字大致相等,那么并行工作可能会有所帮助。
Worker 0: actual time=13093.096..13093.096 rows=8405229 loops=1 Worker 1: actual time=13093.096..13093.096 rows=8315234 loops=1
值得尝试不同的查询并调整max_parallel_workers_per_gather的数量来看看效果。根据经验,Postgres 作为一个仓库比作为一个生产系统更能受益于更多的工人。
增加统计抽样
Postgres 在一个表上收集统计信息,以通知查询规划器。它通过对表进行采样并存储(除其他外)最常见的值来实现这一点。需要的样本越多,查询规划器就越精确。对于分析性工作负载,运行时间较长的查询较少,增加 Postgres 收集的数据量会有所帮助。
这可以在每列的基础上完成
ALTER TABLE table_name ALTER COLUMN column_name set statistics 500;
增加列的统计信息
或者针对整个数据库
ALTER DATABASE mydb SET default_statistics_target = 500;
增加数据库的统计数据
默认值为 100;任何高于 100 到 1000 的值都是好的。请注意,这是应该测量的设置之一。在一些常见的查询上使用EXPLAIN ANALYZE,看看查询规划器错误估计了多少。
使用较少的列
这只是需要注意的一点。Postgres 使用基于行的存储,这意味着行在磁盘上是按顺序排列的。它实际上存储整个第一行(及其所有列),然后存储整个第二行,依此类推。
这意味着当您从一个有很多列的表中选择相对较少的列时,Postgres 将加载大量它不会使用的数据。所有的表数据都是以固定大小(通常为 4KB)的块读取的,所以它不能只是有选择地从磁盘中读取一行的几列。
相比之下,大多数专用数据仓库是列存储,只能读取所需的列。
注意:不要用每个查询都需要连接的多个表替换单个宽表。可能会慢一些(尽管总是要测量)。
这更像是一条经验法则——在所有条件相同的情况下,最好选择更少的列。在实践中,性能提升通常不会很显著。
考虑一个大规模的数据仓库
Postgres 和基于云的数据仓库之间的最后一个主要区别是极端的规模。与 Postgres 不同,它们是作为分布式系统从头开始构建的。这允许他们随着数据大小的增长相对线性地增加更多的处理能力。
当数据库变得太大,应该转移到分布式系统时,我没有一个好的经验法则。但是当您到达那里时,您可能已经具备了处理迁移和理解权衡的专业知识。
在我的非正式测试中,使用 50-100m 行的表,Postgres 表现得非常好——大体上符合红移之类的东西。但是,性能取决于许多因素——磁盘与 ssd、CPU、数据结构、查询类型等,如果不进行一些面对面的测试,真的不可能一概而论。
如果您要将 Postgres 扩展为数十亿行,Citus 是值得考虑的。
原载于 2021 年 5 月 10 日 https://www .叙述者. ai 。
在 COVID 之后使用 Prophet?先看这个。
如何处理 COVID 对时间序列数据的影响?加法 vs 乘法,选哪个?如何处理非日常数据?

弗里达·布莱德森在 Unsplash 上拍摄的照片
Prophet 是基于加法模型预测时间序列数据的程序,其中非线性趋势与年、周、日季节性、加上假日效应相符合。它最适用于具有强烈季节效应的时间序列和几个季节的历史数据。Prophet 对缺失数据和趋势变化具有很强的鲁棒性,通常情况下,能很好地处理异常值,。
通过使用可分解的时间序列模型,Prophet 模型有三个主要组件:
**y(t) = g(t) + s(t) + h(t) + e(t)**g(t): **trend** functions that models *non-periodic* changes;
s(t): **seasonality** functions that models *periodic* changes; i.e. weekly, monthly, yearly seasonality;
h(t): **holdiay** function that represents the effects of potentially irregular scheduled holidays;
e(t): **error** terms that represents changes that are not captured by the model;
如果你是 Prophet 新手,想了解更多的细节,请查看这些令人敬畏的媒体文章(一篇、两篇、三篇)以及原始出版物,它们可以让你快速而深入地开始。假设您已经使用过 Prophet 模型有一段时间了,并且理解了基本原理,那么本文涵盖了在新冠肺炎之后使用 Prophet 建模时间序列时迟早会遇到的 3 个重要的高级主题。
- 如何应对 COVID 的冲击?
- 加法 vs 乘法模型?选哪个?
- 如何处理非日常数据?
1。如何应对 COVID 的冲击?
COVID 的影响已经渗透到方方面面。如果你正在处理时间序列数据,你可能已经注意到 2020 年后一些奇怪的模型表现。这就像是由 COVID 对时间序列数据的趋势和季节性的影响所驱动的。有几种方法可以处理这个问题。
方法 1:将 COIVD 视为“假日”事件
模特没有情感。我们可以做一个简单的“把戏”,让模型将 COVID 视为一个事件或“假日”。这个概念很简单。就像在假日购物季期间,我们预计购物中心的客流量会增加一样,该模型会将 COVID 视为一个特殊的“假日”,估计购物中心的客流量会减少。下面的代码将完成这项工作。
COVID_lockdown = pd.DataFrame({
'holiday': 'covid',
'ds': pd.date_range(start='2020-03-15',
end='2020-05-01',
freq='D'),
'lower_window': 0,
'upper_window': 0,
'prior_scale': 1
})
然而,这种方法很棘手,因为有太多的外部因素(州政策变化)使得 COVID“假日”效应在不同时间和不同场景下不一致。
方法 2:简单地丢弃或替换 2020 COVID 数据(有风险)
这是最简单的方法。只需丢弃 2020 年的数据或在 2019 年数据的基础上插入 2020 年的数据。然而,正如你可以想象的,这种方法是非常不可靠的,尤其是当你有短的历史数据。我们仍然希望包括 2020 年的真实数据,以帮助模型了解季节性。然而,如果您的时间序列数据总体上逐年稳定,并且您认为 COVID 在您的数据中不再起作用,则该方法值得测试。
方法 3:指定“pre_covid”和“post_covid”季节性
COVID 可能会创建一个周季节性模式,该模式在疫情/锁定期间与疫情之后不同。此方法将允许您训练两个独立的季节性模式,这可能对某些时间序列数据有所帮助。然而,在实验过程中,我发现这种方法产生的模型性能最差。因此,请再次考虑使用两种不同的季节来模拟您的数据是否有意义。
#define pre and post covid period
def is_postcovid_seasonality(ds):
date = pd.to_datetime(ds)
return date.date() >= pd.to_datetime('2021-03-15').date()
dfprophet['pre_covid_seasonality']=~dfprophet['ds'].apply(is_postcovid_seasonality)
dfprophet['post_covid_seasonality'] = dfprophet['ds'].apply(is_postcovid_seasonality)#add pre-covid and post-covid seasonality to the model
base_model.add_seasonality(name='pre_covid_seasonality', period=7, fourier_order=3, condition_name='pre_covid_seasonality')
base_model.add_seasonality(name='post_covid_seasonality', period=7, fourier_order=3, condition_name='post_covid_seasonality')
方法 4:添加与 COVID 相关的回归变量,例如 google mobility
在许多业务场景中,这可能是处理 COVID 影响的最佳方法。我们将在此添加流动性数据等外部回归变量,以帮助模型获得 COVID 的影响。由于那些外部回归变量将极大地吸收 COVID 的影响,这将使 COVID 对季节性的影响最小化。以下是一些你可以使用的公开可用指标。消费者信心指数(CCI) ,谷歌流动性, COVID 案例预测
#add a list of external regressors
regressors = ['CCI','google mobility','covid cases']
for regressor in regressors:
base_model = base_model.add_regressor(regressor,
prior_scale=1,
standardize='auto', mode='multiplicative')
需要注意的一点是,其中一些指标可能只是暂时可用的,如谷歌移动数据,它也不提供预测数据。为了利用它,我们可能需要对这些外部变量做自己的预测。尽管如此,包括这些指标将大大提高模型性能。当 COVID 影响消退,我们有了更稳定的时间序列数据时,我们可能只需要重新访问模型。
2。加法 vs 乘法模型?选哪个?
第二个话题也有很多问题和讨论。你可能知道 Prophet 对于季节性和回归量有两种模式,一种是加法模式(默认),另一种是乘法模式。
加性模式下,季节性/回归量逐年不变;而在乘法模式下,季节性/回归量的大小随着趋势而变化(见下图)。

尼古拉·考伦茨的加法与乘法季节性
先知的公式可以写成:
df['yhat'] = df['trend'] * (1 + df['multiplicative_terms']) + df['additive_terms']
由于季节性和回归量都有两种模式,因此有四种组合:
案例 1:附加季节性,附加回归量
【yhat(t)=趋势(t) +季节性(t)+β回归量(t)*
意思是回归量增加一个单位会产生 yhat 的β增加。
案例 2:倍增季节性,倍增回归量
*yhat(t) = trend(t) * (1 +季节性(t) + beta 回归量(t))
意思是回归量增加一个单位会产生 yhat 的 beta *趋势增加。
案例 3:加性季节性,乘性回归
【yhat(t)= trend(t)(1+beta 回归量)+季节性(t)
意为回归量增加一个单位将产生 yhat 的 beta *趋势增加。
案例 4:乘法季节性,加法回归
yhat(t)= trend(t)(1+beta 季节性(t)) +回归量(t)
意思是回归量增加一个单位会产生 yhat 增加一个单位。
一般规则:
因此,在选择加法或乘法模式之前,要问一个问题——回归变量中的一个单位变化是否总是有恒定的影响?或者它将取决于趋势的基础。通常,使用乘法模式更有意义,因为我们可以想象季节性的大小与趋势的大小是一致的。更多信息见本。
例如,夏季气温升高一度,必然会比冬季气温升高一度引发更多的冰淇淋销售。所以在这种情况下,乘法模型更有意义。

然而,由于 COVID… ,规则略有不同
由于 COVID 的影响,2020 年的数字可能非常低,因此如果我们使用乘法季节性,模型将被低基线误导,认为从 2019 年到 2020 年季节性的幅度有很大下降,然后预测 2021 年的季节性幅度甚至小于 2020 年。使用乘法可能会严重低估模型预测。
综上所述, 在疫情期间使用加性季节性更有意义,这样就不会因 2020 年的异常基线而产生偏差,尤其是当 COVID 对你的时间序列数据产生很大影响的时候。 当我们走出疫情的时候,我们可能需要重温一下模型的超参数。
3。如何处理非日常数据?
现在,您可能希望每周或每月汇总一次数据,以抵消 COVID 带来的波动。在 Prophet 中处理非日常数据时有许多注意事项。下面我列出了两个基本的:
警告 1:确保“假期”是在代表周或月的日期。
尽管您有每月或每周的汇总数据,但 Prophet 模型并不知道这一点,它仍然在拟合一个连续的模型,并将每周或每月的数据点视为仅在这些特定日期的单个数据点。因此,那些不属于数据中使用的特定日期的假期将被忽略!!!
例如,如果您有每月数据,并且每个月由该月的第一天表示(01/01/2021、02/01/2021、03/01/2021),那么如果您想要将圣诞节作为假日事件,则需要将圣诞节设置为 2021 年 12 月 1 日,而不是 2021 年 12 月 25 日。为了处理这个潜在的问题,我们只需要将假日移到代表周或月的日期(见下面的代码)。
#Convert date to first day of month
df['First_day_of_the_month'] =
df['date'].to_numpy().astype('datetime64[M]')#Convert date to first day of week (sunday)
df["First_day_of_the_week"] = df['Date'].apply(lambda x: (x - timedelta(days=x.dayofweek + 1)), axis = 1)
警告 2:并非所有的前科都是平等的。
在 Prophet 中,您可以为趋势、季节性、节假日和额外回归变量设置先验。然而,选择正确的先验范围并不是一件容易的事情。
例如,如果数据的变化已经被额外的回归因素捕捉到,那么假日效应可能就是错误的,例如,如果我们使用营销支出作为额外的回归因素来预测需求,我们可能会意外地看到黑色星期五对需求产生负面影响。这是因为营销支出已经解释了需求的大多数变化,这导致了假日的不正确(反直觉)效应。
另一个例子是,如果您增加之前的假期并减少之前的季节性,那么模型会更倾向于使用假期而不是季节性来解释数据的变化。如果您确实想使用季节性,您可以将节假日设置在一个非常小的数字(如 0.00001)之前,将季节性设置在一个非常大的数字(如 0.5)之前。
当然,如果您的目标是短期的准确性,您可以使用网格搜索方法(参见下面的代码块)来优化超参数。但是如果你也关心可解释性,你需要考虑哪一个对特定的变化或改变、季节性、假日或额外的回归变量更有影响,并且相对地增加那个部分的先验。
# Python
import itertools
param_grid = {
'changepoint_prior_scale': [0.001, 0.01, 0.1, 0.5],
'seasonality_prior_scale': [0.01, 0.1, 1.0, 10.0],}# Generate all combinations of parameters
all_params = [dict(zip(param_grid.keys(), v)) for v in itertools.product(*param_grid.values())]
rmses = [] # Store the RMSEs for each params here# Use cross validation to evaluate all parameters
for params in all_params:
m = Prophet(**params).fit(df) # Fit model with given params
df_cv = cross_validation(m, cutoffs=cutoffs, horizon='30 days', parallel="processes")
df_p = performance_metrics(df_cv, rolling_window=1)
rmses.append(df_p['rmse'].values[0])# Find the best parameters
tuning_results = pd.DataFrame(all_params)
tuning_results['rmse'] = rmses
print(tuning_results)
最后的
我希望这篇文章可以帮助您了解如何处理 COVID 影响,如何选择加法和乘法模式,以及如何处理非日常数据。这三个主题变得特别重要,甚至是在新冠肺炎之后使用先知时必须知道的。
我对你如何看待 COVID 在你的模型中的影响也很感兴趣,欢迎在下面发表评论。
保持积极,感谢阅读!
参考
- 泰勒 SJ,勒撒姆 B. 2017。大规模预测。PeerJ 预印本 5:e 3190v 2https://doi.org/10.7287/peerj.preprints.3190v2
使用 Py-Feat 绘制面部表情预测
可视化面部表情数据的实用 python 工具包

介绍 Py-Feat
Py-feat 是一个免费、开源、易于使用的面部表情数据处理工具,它提供了一个工具包,可以轻松地从图像和视频中检测面部表情,预处理和分析面部表情数据,并可视化面部表情数据。您可以通过浏览以下链接了解更多关于 Py-feat 的信息:
https://github.com/cosanlab/py-feat
在本教程中,我们探索如何使用 Py-Feat 在一个单一的人脸图像。
装置
!pip install py-feat
在单幅图像中检测面部表情
首先,加载检测器类。您可以指定要使用的模型。您可以在这里探索 Py-feat 工具包中包含的模型。
**from** **feat** **import** Detector
face_model = "retinaface"
landmark_model = "mobilenet"
au_model = "rf"
emotion_model = "resmasknet"
detector = Detector(face_model = face_model, landmark_model = landmark_model, au_model = au_model, emotion_model = emotion_model)
找到您要处理的文件。在我们的例子中,我们将使用我们的测试图像face.png。
*# Find the file you want to process.*
test_image = os.path.join("face.png")
这是我们的测试图像的样子。
**from** **PIL** **import** Image
**import** **matplotlib.pyplot** **as** **plt**
f, ax = plt.subplots()
im = Image.open(test_image)
ax.imshow(im);

加载的图像-作者提供的图像
现在,我们使用初始化的detector实例通过detect_image()方法进行预测。
image_prediction = detector.detect_image(test_image)
*# Show results*
image_prediction

预测表-按作者分类的图像
1 行× 170 列
输出是一个Fex类实例,它允许您运行Fex的内置方法。
可视化检测结果。
现在,您可以轻松地绘制检测结果。
image_prediction.plot_detections();

探索模型预测-作者图片
减去
你有它!在这个例子中,我们在单个图像上使用了来自机器学习模型的面部表情和情绪预测,并绘制了结果。Py-feat 是计算机视觉和人类行为研究人员解释和分析面部表情数据的有用工具。要亲自尝试,请查看下面的完整代码。
https://colab . research . Google . com/drive/1d 64 vlwbnhrd 9 we 9 tnlz 8 l 1 lnoflzn 5 VD?usp =共享
作者的相关文章—
感谢您的阅读!
使用 Python Flask 和 Ajax 在客户机和服务器之间传递信息
构建 QT 间期计算器

图片来自 Pexels(https://www.pexels.com/)
下面提供了一个例子,说明我们如何在 Python Flask 应用程序中将信息从客户机传递到服务器。首先演示如何将数据从客户端发送到服务器并进行处理,然后我们如何通过在客户端执行计算并使用 Ajax 在同一页面上返回结果来提高效率。为了说明这是如何工作的,这个例子着重于创建一个简单的计算器来计算补偿的 QT 间期,这是医学上使用的 ECG 心电图波形的一部分。这个计算器只是一个例子,不应该用于医疗目的。假设读者对基本的 HTML、CSS、JavaScript 和 Python 有所了解。
QT 间期
QT 间期代表心室激活和恢复的总时间。QT 间期是指从 Q 波开始到 T 波结束的时间,根据心率的不同而不同,女性的 QT 间期也比男性稍长。

Luan Rezende 摄于 Pexels(作者改编以显示 PQRST 波和 QT 间期)
由于间期受心率影响(即心率越高,间期越短),因此存在各种公式来对此进行补偿,包括 Bazett 公式、Fredericia 公式、Hodges 公式和 Framingham 公式[1](补偿后称为 QTc,c)。QT 间期延长会使患者面临心脏性猝死的风险。这可以由某些条件触发,也可以由药物诱发[2]。
对于计算器,我们将使用最流行的公式之一;巴泽特的定义是:

作者图片
蟒蛇皮烧瓶
flask(https://flask.palletsprojects.com/en/2.0.x/)是一个微型网络框架,它允许我们使用 Python 来构建和提供网页。Flask 应用程序要求应用程序以某种方式构建,HTML 文件存储在 templates 文件夹中,其他文件(如 CSS 和 JavaScript)存储在 static 文件夹中。如果需要分离多个 JavaScript/CSS 文件,您也可以在较大的项目中使用子目录。在这个名为 Flask demo 的文件夹中创建的示例中,我们有一个静态文件夹,其中包含我们的样式表( my-style.css )和一个 JavaScript 文件( qtc-calc.js )。templates 文件夹包含这个示例的主 HTML 页面,名为index.html。在这两个文件夹之外,我们有 Python 文件 main.py 中的主 Flask 应用程序。

Flask 项目的文件/文件夹结构(图片由作者提供)
我们将从创建index.html文件开始。我们可以添加以下代码:
<html>
<head>
<title>QT-calculator</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="{{ url_for('static', filename='my-style.css') }}”>
<link href="[https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css](https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css)" rel=”stylesheet” integrity=”sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
</head>
<body>
</body>
</html>
在 HTML 文件的 head 部分,我们导入了 Bootstrap(https://get Bootstrap . com/docs/5.0/getting-started/introduction/),它提供了一个免费的 CSS 框架,使我们能够轻松地制作更专业的 web 内容。这里最大的不同是我们包含本地样式表的方式。通常我们会像这样加载样式表:
<link rel="stylesheet" href="my-style.css">
为了让它与 Flask 一起工作,我们使用 Jinja 模板引擎,使用 url_for 函数指向静态文件夹。你可以在这里找到更多关于金贾的信息:https://jinja.palletsprojects.com/en/3.0.x/
我们现在可以将下面的代码添加到 HTML 文档的 body 部分,为用户添加一些输入字段来输入数据。这包括一些选择男性或女性的无线电选项,以及心率(每分钟心跳数)和 QT 间期(毫秒)的输入字段。
. . .
<body>
<div id="calc-container">
<h1>QTc calculator</h1>
<p>Please enter details to calculate the QTc:</p>
<label>Enter sex:</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="sex" value="m" id="male-option">
<label class="form-check-label" for="male-option">
Male
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="sex" value="f" id="female-option" checked>
<label class="form-check-label" for="female-option">
Female
</label>
</div>
<br>
<label>Enter HR (BPM):</label>
<input type="number" name="hr">
<br><br>
<label>Enter QT interval (msec):</label>
<input type="number" name="qt">
</br><br>
<input type="submit" name="next" value="Next">
</div>
</body>
这里我们使用了一些单选按钮的引导样式。我们向收集用户数据的各种 HTML 元素的 name 属性添加值。这是因为 Flask 利用这个属性来访问表单信息。整个计算器包含在一个名为 calc-container 的 div 中。我们在本地样式表中添加了一些填充(10 像素)来改善外观,方法是引用前面带有散列的 id 属性并添加填充:
#calc-container {
padding: 10px;
}
接下来,我们将创建 Flask 应用程序。首先,我们需要安装烧瓶模块。我们可以在 Python 终端中使用 pip 来做到这一点:
pip install Flask
然后,我们可以在应用程序的主文件夹中创建一个名为 main.py 的 Python 文件,并添加以下代码:
from flask import Flask, render_template, url_for, requestapp = Flask(__name__)[@app](http://twitter.com/app).route('/')
def index():
return render_template('index.html')if __name__ == "__main__":
app.run(debug=True)
我们从 flask 模块导入一些函数,接下来我们创建应用程序并添加一个名为 index 的函数,该函数使用 render_template 函数来渲染存储在 templates 文件夹中的index.html文件。我们可以使用 app.route 装饰器来指定这个页面的主路径。最后,我们在调试模式下运行应用程序。当 main.py 文件运行时,它在默认地址/端口上运行,从而在浏览器中呈现索引页面。
Running on [http://127.0.0.1:5000/](http://127.0.0.1:5000/) (Press CTRL+C to quit)
看起来像这样:

在 Chrome 浏览器中运行的应用程序(图片由作者提供)
现在我们已经设置了前端。我们可以将其连接到后端,以便执行计算。为了向服务器发送数据,我们可以使用一个表单标签。我们需要将希望发送的数据包含在表单标记中,并指定将处理数据的 Python 函数的名称。
这里,我们将计算器代码包装在表单标记中,并使用 url_for 函数将其发送到 main.py 文件中的索引函数,以便使用 POST 请求进行处理。
<form action="{{ url_for('index') }}" method="POST">
<label>Enter sex:</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="sex" value="m" id="male-option">
<label class="form-check-label" for="male-option">
Male
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="sex" value="f" id="female-option" checked>
<label class="form-check-label" for="female-option">
Female
</label>
</div>
<br>
<label>Enter HR (BPM):</label>
<input type="number" name="hr">
<br><br>
<label>Enter QT interval (msec):</label>
<input type="number" name="qt">
</br><br>
<input type="submit" name="next" value="Next">
</form>
超文本传输协议(HTTP)支持几种方法,其中最常用的是 POST 和 GET 方法。然后我们可以修改索引函数来处理这些数据。我们将把 GET 和 POST 方法添加到 app.route 装饰器中。接下来,我们可以检查 POST 方法并使用 request.form 捕获表单数据。这将存储在一个名为的 Python 变量中,格式为。然后,我们将这个表单数据传递给一个新函数来计算 QTc,并将结果返回到 index.html 文件中一个名为 QTc_result 的变量中。
[@app](http://twitter.com/app).route('/', methods=['GET', 'POST'])
def index():
QTc_result = False
if request.method == 'POST':
form = request.form
QTc_result = calculate_qtc(form) return render_template('index.html', QTc_result=QTc_result)
接下来,我们需要创建一个函数来实际执行计算。
def calculate_qtc(form):
sex = request.form['sex']
heart_rate = int(request.form['hr'])
qt_int = int(request.form['qt'])
qt_seconds = qt_int / 1000
rr_interval = (6000 / heart_rate)
QTc = qt_seconds / math.sqrt(rr_interval)
formated_QTc = round((QTc * 1000) * 10, 0) if (formated_QTc > 440 and sex == 'm') or (formated_QTc > 460 and sex == 'f'):
prolonged = True
else:
prolonged = Falsereturn (formated_QTc, prolonged)
我们可以通过使用 HTML 元素的名称属性作为字典键来获取在 HTML 输入字段中输入的数据。我们可以将数字数据转换成整数进行计算。接下来,我们应用 Bazett 公式进行实际计算。我们需要导入 math 模块,以便对公式的平方根部分使用 sqrt 函数。我们分别对照男性和女性的正常值检查结果,并相应地将延长变量设置为真或假。最后,我们返回一个元组中的 QTc 和扩展变量。
最后,我们可以更新 index.html 文件来处理结果。我们可以在结束表单标签下添加下面的代码。
<br><br>
{% if QTc_result %}
<h2>Results</h2>
<p>Compensated QT interval (Bazett formula) = {{QTc_result[0]}} msec.
<br>
{% if QTc_result[1] %}
This is a prolonged QT interval.
{%else%}
This is a normal QT interval.
{% endif %}
{% endif %}
在这里,我们检查是否有任何结果要显示。如果是这样,我们输出 QTc(元组的第一个元素)以及间隔是否延长(元组的第二个元素)。Flask 变量可以用双括号({{ }})显示在 HTML 中。现在,当我们在输入字段中输入数据并单击 next 按钮时,我们会得到以下输出:

计算输出(图片由作者提供)
index.html文件的完整代码如下所示:
<!DOCTYPE html>
<html>
<head>
<title>QT-calculator</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="{{ url_for('static', filename='my-style.css') }}">
<link href="[https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css](https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css)" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
</head>
<body>
<div id="calc-container">
<h1>QTc calculator</h1>
<p>Please enter details to calculate the QTc:</p>
<form action="{{ url_for('index') }}" method="POST">
<label>Enter sex:</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="sex" value="m" id="male-option">
<label class="form-check-label" for="male-option">
Male
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="sex" value="f" id="female-option" checked>
<label class="form-check-label" for="female-option">
Female
</label>
</div>
<br>
<label>Enter HR (BPM):</label>
<input type="number" name="hr">
<br><br>
<label>Enter QT interval (msec):</label>
<input type="number" name="qt">
</br><br>
<input type="submit" name="next" value="Next">
</form>
<br><br>
{% if QTc_result %}
<h2>Results</h2>
<p>Compensated QT interval (Bazett formula) = {{QTc_result[0]}} msec.
<br>
{% if QTc_result[1] %}
This is a prolonged QT interval.
{%else%}
This is a normal QT interval.
{% endif %}
{% endif %}
</div>
</body>
</html>
main.py 文件的完整代码:
import math
from flask import Flask, render_template, url_for, requestapp = Flask(__name__)[@app](http://twitter.com/app).route('/', methods=['GET', 'POST'])
def index():
QTc_result = False
if request.method == 'POST':
form = request.form
QTc_result = calculate_qtc(form) return render_template('index.html', QTc_result=QTc_result)def calculate_qtc(form):
sex = request.form['sex']
heart_rate = int(request.form['hr'])
qt_int = int(request.form['qt'])
qt_seconds = qt_int / 1000
rr_interval = (6000 / heart_rate)
QTc = qt_seconds / math.sqrt(rr_interval)
formated_QTc = round((QTc * 1000) * 10, 0) if (formated_QTc > 440 and sex == 'm') or (formated_QTc > 460 and sex == 'f'):
prolonged = True
else:
prolonged = False return (formated_QTc, prolonged)if __name__ == "__main__":
app.run(debug=True)
使用 Ajax 请求
这是可行的,我们可以成功地将数据从前端发送到后端进行处理。但是,我们应该问自己,是否有必要首先将这些信息发送到后端。毕竟,我们可以用 JavaScript 创建一个函数来执行计算。这比增加进出服务器的流量要有效得多。我们通常考虑向后端发送信息的主要原因是,我们通常希望将这些数据存储在数据库中,或者访问数据库并将特定的结果返回给前端。
我们在这里应用的计算显然是琐碎的,不需要太多的计算能力。然而,如果你正在运行一个强化的机器学习算法,这将是一个非常不同的故事。如果可能的话,最好是在客户端执行计算,并将计算结果返回给服务器以写入数据库。
让我们修改现有代码,用 JavaScript 在客户端执行计算,而不是用 Python 在服务器端执行。我们还可以使用 Ajax 处理用户输入,而不是呈现模板。在客户端执行计算后,我们将把用户输入和结果传递到服务器,存储在数据库中。
Ajax(异步 JavaScript 和 XML)用于在应用程序的后台异步发送/检索数据,而无需重新加载整个页面。当我们希望只更新现有页面的一部分,而不将用户导向新页面或重新加载现有页面时,通常会使用这种方法。当用户填写表单(例如购买产品)时,现有的 form 方法非常有用。他们输入自己的详细信息,这些信息被传递给服务器进行处理,然后被重定向到一个新页面,告诉他们交易成功(或失败)。对于像交互式数据仪表板和计算器这样需要不断更新页面的一部分而不是将用户发送到新页面/重新加载的工具来说,Ajax 是更好的方法。
要修改代码以使用 Ajax,我们需要首先删除 form 标签。然后我们可以添加一个 onclick 事件到 next 按钮,该按钮将启动一个名为 calculateQTc 的 JavaScript 函数。
<label>Enter sex:</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="sex" value="m" id="male-option">
<label class="form-check-label" for="male-option">
Male
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="sex" value="f" id="female-option" checked>
<label class="form-check-label" for="female-option">
Female
</label>
</div>
<br>
<label>Enter HR (BPM):</label>
<input type="number" name="hr" id="hr">
<br><br>
<label>Enter QT interval (msec):</label>
<input type="number" name="qt" id="qt">
</br><br>
<input type="submit" name="next" value="Next" onclick="calculateQTc();">
接下来,我们将添加一个 div 元素来显示结果。这里我们将添加一些带有 id 属性的 span 元素来添加结果,称它们为 qtc 和延长的。
<br><br>
<div id="qtc-results">
<h2>Results</h2>
<p>Compensated QT interval (Bazett formula) = <span id="qtc"></span> msec.
<br>
<p><span id="prolonged"></span></p>
</div>
最后,我们将给包含 div 一个 id,并使它不可见,默认情况下隐藏结果。为此,我们在 CSS 文件中将 div 元素的显示样式设置为 none 。
#qtc-results {
display: none;
}
我们现在可以用 JavaScript 而不是 Python 来实现计算 QTc 的函数,因此它是在客户端执行的。我们可以创建一个名为 qt-calc.js 的 JavaScript 文件,并添加以下代码:
function calculateQTc() {
var prolonged;
var heartRate = parseInt(document.getElementById("hr").value);
var qtInt = parseInt(document.getElementById("qt").value);
var sex = document.getElementsByName("sex");
var qtcResult = document.getElementById("qtc");
var prolongedResult = document.getElementById("prolonged");
var resultsContainer = document.getElementById("qtc-results");
}
这里我们使用 document.getElementById 函数通过元素的 Id 属性(通过在引号中提供 id 值)来访问元素。我们还使用 parseInt 函数将数据转换成整数格式,以便在适当的时候进行计算,这与我们在 Python 中使用的方式非常相似。例外的情况是我们使用document . getelementbyname作为性单选按钮。当函数被调用时,我们希望显示结果,所以接下来我们通过将显示样式设置为块来使结果 div 可见。
resultsContainer.style.display = "block";
接下来,我们使用 for 循环来查找被选中的单选按钮(男性或女性),该按钮将值“m”或“f”存储在名为 selectedSex 的变量中。
for(var i=0; i<sex.length; i++) {
if(sex[i].checked) var selectedSex = sex[i].value;
}
接下来我们可以添加计算本身。这里唯一的主要区别是我们使用 JavaScript 标准数学库来计算平方根和舍入。最后,我们用结果更新 HTML span 元素。
qtSeconds = qtInt / 1000;
rrInterval = (6000 / heartRate);
QTc = qtSeconds / Math.sqrt(rrInterval);
formatedQTc = Math.round((QTc * 1000) * 10, 0);
qtcResult.innerHTML = formatedQTc;
该函数的最后一部分涉及计算 QTc 是否延长,更新 HTML 结果字段,并将延长状态存储在一个名为delivered的变量中。
if((formatedQTc > 440 && selectedSex == 'm') ||
(formatedQTc > 460 && selectedSex == 'f')) {
prolongedResult.innerHTML = "This is a prolonged QT interval";
prolonged = "Prolonged QT";
}
else{
prolongedResult.innerHTML = "This is a normal QT interval";
prolonged = "Normal QT";
}
最后,我们需要将 JavaScript 文件加载到 head 部分的 HTML 文档中。
<script src="{{ url_for('static', filename='qtc-calc.js') }}"></script>
Ajax 允许我们用少量数据异步更新 web 页面的各个部分,作为重载和呈现整个页面的替代方法。
一个简单的方法是使用来自 jQuery JavaScript 库的 ajax 方法。您可以直接下载它并将其作为本地资源包含在您的 web 页面中,或者您可以通过在 HTML 文档的 head 部分包含以下代码行,使用 CDN(内容交付网络)连接到它。
<script src="[https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js](https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js)">
</script>
然后,我们可以修改 JavaScript 代码来实现 jQuery ajax 方法。首先,我们可以将想要传递回服务器的数据存储在一个数组中。这里我们将键/值对作为 JavaScript 对象添加到名为 server_data 的数组中。
var server_data = [
{"QTc": formatedQTc},
{"prolonged": prolonged},
{"HR": heartRate},
{"QT": qtInt},
{"Sex": selectedSex}
];
接下来,我们可以创建 ajax 请求。jQuery 库使用美元($)符号定义一个选择器,然后指定一个动作。这里我们将类型定义为 POST 请求。url 映射到 Python 中应该处理请求的特定函数的 route decorator。发送到服务器的数据应该是字符串(文本数据),所以我们可以使用 JSON.stringify 函数将 JSON (JavaScript 对象表示法)转换为字符串格式,以便传输到服务器。
$.ajax({
type: "POST",
url: "/process_qtc",
data: JSON.stringify(server_data),
contentType: "application/json",
dataType: 'json'
});
然后我们需要更新 Python 文件来处理 ajax 请求。我们需要从 flask 导入 jsonify 。我们还可以删除计算 QT 间期的原始函数,并修改 index 函数,只呈现 index HTML 文件。这里我们使用 request.get_json 函数获取数据,并将其存储在一个名为 qtc_data 的 Python 变量中。现在,我们将把数据输出到控制台,并把一个带有键和值的对象:已处理的和真的返回到前端。
from flask import Flask, render_template, url_for, request, jsonifyapp = Flask(__name__)[@app](http://twitter.com/app).route('/')
def index():
return render_template('index.html')[@app](http://twitter.com/app).route('/process_qtc', methods=['POST', 'GET'])
def process_qt_calculation():
if request.method == "POST":
qtc_data = request.get_json()
print(qtc_data)
results = {'processed': 'true'}
return jsonify(results)if __name__ == "__main__":
app.run(debug=True)
从客户端返回的打印数据可以在控制台中看到。

显示返回输出的控制台屏幕截图(图片由作者提供)
回到 JavaScript 函数,我们可以添加一个 success 函数来处理从 Python 函数返回的数据:
$.ajax({
type: "POST",
url: "/process_qtc",
data: JSON.stringify(server_data),
contentType: "application/json",
dataType: 'json',
success: function(result) {
console.log("Result:");
console.log(result);
}
});
我们可以使用 console.log 函数输出请求成功完成时返回的结果。如果我们在 web 浏览器中查看控制台,我们可以看到预期的输出。

Chrome 控制台显示成功功能输出的屏幕截图(图片由作者提供)
添加数据库
如前所述,我们希望向服务器返回数据的主要原因之一是为了从数据库中存储或检索数据。在最后一部分,我们将向应用程序添加一个 SQLite 数据库,以存储输入的结果并返回保存在数据库中的商品数量。
首先我们需要添加几个模块。我们将使用 SQLAlchemy 模块来管理数据库,使用 os 来设置数据库相对于 Flask 应用程序的路径,使用 datetime 来为数据库中存储的每个项目添加时间戳。最后,我们还需要从 flask 模块导入会话。
import os
from flask import Flask, render_template, url_for, request, jsonify, session
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
我们可以使用 os (操作系统)模块来找到主 Python 文件,并使用它来添加数据库,我们将把这个数据库称为 qtdata.db 到 main.py Python 文件所在的同一个文件夹中。首先,我们将获得当前文件的路径,并将其存储在一个名为 basedir (基本目录)的变量中。
basedir = os.path.abspath(os.path.dirname(__file__))
接下来,我们将数据库文件添加到此位置,并使用应用程序配置设置这些数据库参数:
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'qtdata.db')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
接下来,我们可以将数据库实例绑定到我们的特定应用程序:
db = SQLAlchemy(app)
对象关系映射(ORM)可用于将 Python 类映射到 SQLite 数据库中的表。在这里,我们可以定义与表中的列名相关的变量。我们将调用表 qt_data 并使用 db。列功能定义表中字段(列)的名称和每列相关的数据类型。例如,下面的代码定义了一个名为 QTc 的 Python 变量,该变量引用了表中的一个列名(同名)。使用 db 将数据类型定义为整数。整数。我们可以根据需要定义其他数据类型,比如浮点值、字符值、字符串等等。例如:
QTc = db.Column('QTc', db.Integer)
这里我们定义了类( Store_QTc_data )、表名和列:
class Store_QTc_data(db.Model):
__tablename__ = 'qt_data'
id = db.Column('id', db.Integer, primary_key = True)
timestamp = db.Column('timestamp', db.DateTime)
QTc = db.Column('QTc', db.Integer)
prolonged = db.Column('prolonged', db.String(50))
heart_rate = db.Column('heart rate', db.Integer)
QT = db.Column('QT', db.Integer)
sex = db.Column('sex', db.CHAR)
除了 QT 数据之外,我们还有一个 id 列,它定义了主键(每个记录的惟一标识符)和一个时间戳,时间戳将存储记录写入数据库的日期和时间。最后,我们可以添加一个初始化方法,允许我们为这些字段传递值:
class Store_QTc_data(db.Model):
__tablename__ = 'qt_data'
id = db.Column('id', db.Integer, primary_key = True)
timestamp = db.Column('timestamp', db.DateTime)
QTc = db.Column('QTc', db.Integer)
prolonged = db.Column('prolonged', db.String(50))
heart_rate = db.Column('heart rate', db.Integer)
QT = db.Column('QT', db.Integer)
sex = db.Column('sex', db.CHAR) def __init__(self, QTc, prolonged, heart_rate, QT, sex):
self.QTc = QTc
self.prolonged = prolonged
self.timestamp = datetime.now()
self.heart_rate = heart_rate
self.QT = QT
self.sex = sex
我们现在可以更新索引函数来创建数据库(如果还不存在的话):
[@app](http://twitter.com/app).route('/')
def index():
if not os.path.exists(os.path.join(basedir, 'qtdata.db')):
db.create_all()return render_template('index.html')
接下来,我们可以更新 process_qt_calculation 函数,将数据写入数据库。我们可以使用 db.session.add 函数,使用 ajax 将前端发送的数据传入 Store_QTc_data 类。然后,我们通过 db.session.commit 函数将这些更改写入数据库。
[@app](http://twitter.com/app).route('/process_qtc', methods=['POST', 'GET'])
def process_qt_calculation():
if request.method == "POST":
qtc_data = request.get_json()
db.session.add(Store_QTc_data(qtc_data[0]['QTc'], qtc_data[1]['prolonged'], qtc_data[2]['HR'], qtc_data[3]['QT'], qtc_data[4]['Sex']))
db.session.commit() results = {'processed': 'true'}
return jsonify(results)
如果我们随后向计算器添加一些数据并单击 next,这些数据将被写入数据库。如果我们打开数据库(使用类似 SQLitehttps://sqlitebrowser.org/的 DB 浏览器),您可以看到数据已经成功添加到数据库中。

显示数据成功写入数据库的数据库浏览器屏幕截图(图片由作者提供)
最后,我们将修改结果以返回数据库中的数字项,并将其显示在 HTML 文档中。我们可以使用 count 函数来查找数据库中的记录数,并修改结果变量以将其发送回 ajax 函数。
rows = db.session.query(Store_QTc_data).count()
然后,我们可以显示数据库中的记录数量以及计算结果。我们将添加一个 span 元素来显示记录的数量,作为 id 为 num-rows 的消息的一部分。
<div id="qtc-results">
<h2>Results</h2>
<p>Compensated QT interval (Bazett formula) = <span id="qtc"></span> msec.
<br>
<p><span id="prolonged"></span></p>
<p>Saved data. There are <span id="num-rows"></span> items saved. </p>
</div>
最后,我们将更改 console.log,以便在成功处理请求时更新 span 元素。
$.ajax({
type: "POST",
url: "/process_qtc",
data: JSON.stringify(server_data),
contentType: "application/json",
dataType: 'json',
success: function(result) {
numRows.innerHTML = result.rows;
}
});
输入数据后,最终的计算器看起来是这样的:

显示输入数据和生成结果的图像(作者提供的图像)
Python、HTML 和 JavaScript 文件的最终代码可以在这里看到:
<!DOCTYPE html>
<html>
<head>
<title>QT-calculator</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="{{ url_for('static', filename='my-style.css') }}”>
<link href="[https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css](https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css)" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
<script src="{{ url_for('static', filename='qtc-calc.js') }}"></script>
<script src="[https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js](https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js)"></script>
</head>
<body>
<div id="calc-container">
<h1>QTc calculator</h1>
<p>Please enter details to calculate the QTc:</p>
<label>Enter sex:</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="sex" value="m" id="male-option">
<label class="form-check-label" for="male-option">
Male
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="sex" value="f" id="female-option" checked>
<label class="form-check-label" for="female-option">
Female
</label>
</div>
<br>
<label>Enter HR (BPM):</label>
<input type="number" name="hr" id="hr">
<br><br>
<label>Enter QT interval (msec):</label>
<input type="number" name="qt" id="qt">
</br><br>
<input type="submit" name="next" value="Next" onclick="calculateQTc();">
<br><br>
<div id="qtc-results">
<h2>Results</h2>
<p>Compensated QT interval (Bazett formula) = <span id="qtc"> </span> msec.
<br>
<p><span id="prolonged"></span></p>
<p>Saved data. There are <span id="num-rows"></span> items saved.</p>
</div>
</div>
</body>
</html>
JavaScript 文件:
function calculateQTc() {
var prolonged;
var heartRate = parseInt(document.getElementById("hr").value);
var qtInt = parseInt(document.getElementById("qt").value);
var sex = document.getElementsByName("sex");
var qtcResult = document.getElementById("qtc");
var prolongedResult = document.getElementById("prolonged");
var resultsContainer = document.getElementById("qtc-results");
var numRows = document.getElementById("num-rows"); resultsContainer.style.display = "block"; for(var i=0; i<sex.length; i++) {
if(sex[i].checked) var selectedSex = sex[i].value;
} qtSeconds = qtInt / 1000;
rrInterval = (6000 / heartRate);
QTc = qtSeconds / Math.sqrt(rrInterval);
formatedQTc = Math.round((QTc * 1000) * 10, 0);
qtcResult.innerHTML = formatedQTc; if((formatedQTc > 440 && selectedSex == 'm') ||
(formatedQTc > 460 && selectedSex == 'f')) {
prolongedResult.innerHTML = "This is a prolonged QT interval";
prolonged = "Prolonged QT";
}
else{
prolongedResult.innerHTML = "This is a normal QT interval";
prolonged = "Normal QT";
} var server_data = [
{"QTc": formatedQTc},
{"prolonged": prolonged},
{"HR": heartRate},
{"QT": qtInt},
{"Sex": selectedSex}
]; $.ajax({
type: "POST",
url: "/process_qtc",
data: JSON.stringify(server_data),
contentType: "application/json",
dataType: 'json',
success: function(result) {
numRows.innerHTML = result.rows;
}
});
}
Python 文件:
import os
from flask import Flask, render_template, url_for, request, jsonify, session
from flask_sqlalchemy import SQLAlchemy
from datetime import datetimebasedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'qtdata.db')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = Truedb = SQLAlchemy(app)class Store_QTc_data(db.Model):
__tablename__ = 'qt_data’
id = db.Column('id', db.Integer, primary_key = True)
timestamp = db.Column('timestamp', db.DateTime)
QTc = db.Column('QTc', db.Integer)
prolonged = db.Column('prolonged', db.String(50))
heart_rate = db.Column('heart rate', db.Integer)
QT = db.Column('QT', db.Integer)
sex = db.Column('sex', db.CHAR)def __init__(self, QTc, prolonged, heart_rate, QT, sex):
self.QTc = QTc
self.prolonged = prolonged
self.timestamp = datetime.now()
self.heart_rate = heart_rate
self.QT = QT
self.sex = sex[@app](http://twitter.com/app).route('/')
def index():
if not os.path.exists(os.path.join(basedir, 'qtdata.db')):
db.create_all() return render_template('index.html')[@app](http://twitter.com/app).route('/process_qtc', methods=['POST', 'GET'])
def process_qt_calculation():
if request.method == "POST":
qtc_data = request.get_json()
db.session.add(Store_QTc_data(qtc_data[0]['QTc'], qtc_data[1]['prolonged'], qtc_data[2]['HR'], qtc_data[3]['QT'], qtc_data[4]['Sex']))
db.session.commit()
rows = db.session.query(Store_QTc_data).count() results = {'rows': rows}
return jsonify(results)if __name__ == "__main__":
app.run(debug=True)
总之,我们需要考虑何时将数据返回到服务器进行处理,使用哪种方法,以及何时最好在客户端实现这一点,以充分利用资源。有时我们想返回数据,处理数据,并把用户导向一个新的页面。在其他场合,我们希望在同一页面上来回传递信息。为了实现这一点,我们可以使用 ajax 在同一页面上的客户机和服务器之间快速来回传递对象。随着交互式仪表盘等工具在向用户传递信息方面变得越来越受欢迎,数据科学家应该了解在客户端和服务器之间传递数据的一些不同选项,以及何时应用它们以获得预期结果。
参考
[1] Davies,A,Scott,A (2015)开始读心电图:理论与实践综合指南。伦敦
[2] Alahmadi,A,Davies,A,Royle,J,Jay,C (2021) 一种可解释的算法,用于检测存在尖端扭转型室性心动过速(TdP)风险的药物诱发的 QT 间期延长,与心率和 T 波形态无关。生物学和医学中的计算机。131(1):104281
使用 Python 获取和存储联邦经济数据
从数据获取流程的一些最初步骤开始数据之旅。

关于金钱的数据——还有比这更好的吗?来自 Pexels 的 Karolina Grabowska 摄影
在每个分析师的一生中,都会有这样一个时刻,他们必须获取、处理和存储数据,以便获得分析阶段所需的数据。可悲的是,并不是所有的东西都是干净的,都是从股票数据集中打包出来的。如果你不是一个有经验的数据工程师或不习惯 ETL 过程,这可能是一个挑战,并提出一些你必须解决的概念问题。
我想,既然我经历了这个过程,而且记忆犹新,我会写一篇文章,希望能帮助那些面临挑战的人。我要警告你,这不是高质量的代码。它在我的情况下是有效的,但却被展示出来,毫无保留。我做了很少的清理或优化,所以请记住这一点。
让我们跳进来。
要求
根据它需要什么,我把它保持得非常简单:
— Python(我用的是 3.9,因为它在我的开发工作站上运行)
—熊猫
— psycopg2(用于数据库访问的 Python 库)
— Datapungi_fed(该库允许轻松访问美联储数据库)
— PostgreSQL(我目前使用的是版本 13,但几乎任何最新版本都适用于我们在这里做的事情)
一点也不难。
注意:对于这些数据,您需要向美联储注册以获得 API 密钥。这些在 https://research.stlouisfed.org/docs/api/api_key.html有售
数据
首先说一下我们会收到的数据。
美联储维护着一个庞大的数据库,其中包含各种经济数据集,分布在众多数据库中。几乎所有这些都是时间序列集,根据各种报告标识符以日期和数据的形式出现。
关于使用的简要说明:美联储使数据集可用于个人、非商业用途,因此您可以按照自己选择的长度进行构建和分析。但是,如果你想通读所有的条款和条件,这里有法律常见问题的链接:https://fred.stlouisfed.org/legal/
datapungi_fed 库简化了实际检索过程,并处理了所有繁重的工作。我们简单地通过名字调用一个报告,然后库返回给我们一个系列,我们可以使用 pandas 来操作。
我发现在我的日常使用中,我对 API 的使用超过了我真正喜欢的程度。我想确保 A)我将我的 API 访问限制在可接受的数量。毕竟,我是一个有礼貌的数据收集者。b)我希望数据更接近我的最终目的地,并防止出现连接问题、服务器维护问题和其他连接问题。我还希望能够在必要时进行更多的后端处理,并减轻前端的负担。因为这个原因,我把这个放进了数据库。
我想从美联储获取的另一个数据是发布的每日报告列表。这有点不同,合并了一行,包含 ID、开始和结束日期、报告名称、链接、新闻发布标志和注释。
这第二次拉进了一些数据净化,以使它正确加载,但我们也将解决这个问题。
数据库ˌ资料库
我为数据库创建的简单模式由四个表组成:
- 美联储 _ 报告
- 美联储 _ 报告 _tmp
- 美联储 _ 释放
- 美联储 _ 发布 _tmp
普通表和 _tmp 表本质上都是彼此的副本。它们的作用是在 _tmp 表中对数据库进行初始插入,然后只将新数据复制到主表中,此时 _tmp 表被清空。
表 1 和表 2 的结构是:
CREATE TABLE public.fed_reports (
report_date DATE NOT NULL,
data NUMERIC NOT NULL,
report_name VARCHAR NOT NULL,
hash VARCHAR NOT NULL PRIMARY KEY
);
类似地,表 3 和表 4 的结构是:
CREATE TABLE public.fed_reports (
release_date DATE NOT NULL,
report_name VARCHAR NOT NULL,
report_link VARCHAR NOT NULL,
notes VARCHAR NOT NULL,
hash VARCHAR NOT NULL PRIMARY KEY
);
这为流程中不同点的数据提供了四个容器。现在,我们需要一些方法来确保事情能够正常进行。
前两个函数用于为 _tmp 表的内容分配一个哈希值。执行此操作是因为我只想要新数据。通过对内容进行散列,我为每一行定义了一个惟一的键,这给了我在插入时匹配的内容。当我进入实际的检索代码时,我将进一步讨论这个问题,所以请耐心等待。
CREATE OR REPLACE FUNCTION public.rehash_fed_reports()
RETURNS integer
LANGUAGE 'plpgsql'
VOLATILE
PARALLEL UNSAFE
COST 100AS $BODY$
begin
UPDATE fed_reports_tmp
SET hash = md5(
cast(report_date as VARCHAR) ||
cast(data as VARCHAR) ||
cast(report_name as VARCHAR)
) return 0;
end;
$BODY$;CREATE OR REPLACE FUNCTION public.rehash_fed_releases()
RETURNS integer
LANGUAGE 'plpgsql'
VOLATILE
PARALLEL UNSAFE
COST 100AS $BODY$
begin
UPDATE fed_releases
SET = md5(
cast(release_date as VARCHAR) ||
cast(report_name as VARCHAR) ||
cast(report_link as VARCHAR) ||
cast(notes as VARCHAR)
) return 0;
end;
$BODY$;
这两种功能基本相似。它们对表中的行执行定义的更新,并返回一个整数 0。没有真正的错误检查或任何其他容错,因为如果他们的过程失败,函数将返回一个错误,事务将默认回滚。它使这个应用程序变得简单。
注意,为了使用 md5 函数,我将所有数据转换成文本格式,并将结果连接起来。只要始终使用相同的方法,这就不是问题。
最后,我定义了一个函数来执行数据移动。
CREATE OR REPLACE FUNCTION public.fed_report_update()
RETURNS integer
LANGUAGE 'plpgsql'
VOLATILE
PARALLEL UNSAFE
COST 100AS $BODY$
begin
INSERT INTO fed_releases (
release_date,
report_name,
report_link,
notes,
hash)
SELECT * FROM
fed_releases_tmp
WHERE
hash NOT IN (
SELECT hash FROM fed_releases
);
INSERT INTO
fed_reports (
report_date,
data,
report_name,
hash)
SELECT * FROM
fed_reports_tmp
WHERE
hash NOT IN (
SELECT hash FROM fed_reports);return 0;
end;
$BODY$;
这个函数只使用 INSERT INTO 语句根据子选择的结果执行插入。在本例中,我从 each _tmp 表中选择主表中不存在散列的所有内容。这确保了只添加新数据。
或者,我可以每次只删除主表中的数据,然后重新插入所有数据,而不需要所有的散列步骤,但是在发布报告的情况下,我想要一个每日历史记录,并且我不能再次获得该数据,所以这是必要的。如果我只为一个人做,为什么不使用复制粘贴的魔法,为两个人都做呢?
最后,我唯一没有放在这里的是清除 _tmp 表的函数。我将它们打包到一些其他的进程中,作为更大的应用程序的一部分,但是给定代码示例,您应该能够自己轻松地完成这些工作。
这就是数据库。很简单,对吧?
检索代码
现在我们开始有趣的部分 python 代码。
出于几个原因,这不是一个完整的脚本,因为有些东西在我自己的代码中是不相关的(我太懒了,不会为本文重写一个工作模板),但没有什么重要的东西会被遗漏。但是,如果你被卡住了,就留下你的评论,我会试着给你一个答案——不像一些作者,我会检查并试着回复。
首先,让我们做重要的事情:
import pandas as pd
import datapungi_fed as dpfdata = dpf.data("put your API code here")# Note: please change your database, username & password as per your own values
conn_params_dic = {
"host": "address of database",
"database": "name of database",
"user": "user of database",
"password": "password of database"
}
我们已经做了两件事:1)我们为 API 键设置了一个变量,2)为数据库定义了一个连接字符串。这两者都允许访问我们需要的两个外部资源。
def get_report(report_name):
try:
df = data.series(report_name)
df.rename(columns={report_name: 'data'}, inplace=True)
df['report'] = report_name
df.reset_index(drop=False, inplace=True)
except:
df = pd.DataFrame(columns = ['date', 'data', 'report'])
print(report_name + ' Empty Set')
return df
接下来,我创建了一个快速实用函数,它将一个报告名称作为输入,并使用创建的数据对象来检索报告,该报告以一系列名为 date 和 report_name 的列作为列。因为我将所有的报告存储在一个带有报告名称标识符的大表中,所以我想保持一致,所以我将 report_name 列重命名为“data”。然后,我创建“report”列来保存名称,并删除索引,默认情况下是日期。
这就把所有的东西都放到了正确的格式中。
如果有一个空报告(这种情况时有发生),我只需返回一个带有标题的空数据帧并打印一个警告。
准备工作结束后,让我们先做例外,也就是发布的报告,并把它们处理掉。
released = data.releases()released.drop(columns=['id', 'realtime_end', 'press_release'], inplace = True)released.rename(columns={'realtime_start': 'Date'}, inplace=True)
released.rename(columns={'name': 'Report Name'}, inplace=True)
released.rename(columns={'link': 'Link'}, inplace=True)
released.rename(columns={'notes': 'Notes'}, inplace=True)released['Report Name'] = released['Report Name'].str.replace(r'"', '')
released['Report Name'] = released['Report Name'].str.replace(r',', '')
released['Link'] = released['Link'].str.replace(r'"', '')
released['Link'] = released['Link'].str.replace(r',', '')
released['Notes'] = released['Notes'].str.replace(r'"', '')
released['Notes'] = released['Notes'].str.replace(r',', '')
released['Notes'] = released['Notes'].str.replace(r'\n', '')
released['Notes'] = released['Notes'].str.replace(r'\r', '')
released['Notes'] = released['Notes'].str.replace(r'\\', '')released['hash'] = released.apply(lambda x: hash(tuple(x)), axis=1)
这是一段可怕的代码,但是完成了工作。如果我更有雄心,或者这不仅仅是一个一次性的,我可能会做更多的事情来将其容器化,使其更便于携带,但它就是这样。
在这里,我们获取新的报告发布(目的地为 fed_releases 表)。这存储在“发布”中。接下来,我删除了对我不重要的列,比如 ID、realtime_end 和 press_release。
接下来,我重命名剩余的列。这并不是绝对必要的,而是作为我使用这些数据的一种方法的延续,所以它被留在了这个过程中。
之后,需要清理内容。对这些数据没有太多的关注,所以会有很多错误的格式出现。一些简单的替换就可以解决问题。
因为在使用 COPY 命令将数据复制到数据库之前,首先要将数据移动到 CSV 中,所以大多数问题都与转换有关。我们删除了逗号、引号、反斜杠字符,当然还有换行符。任何这些都会很快破坏你的一天,并导致一些非常无用的错误信息。
最后一步是使用 python 散列库进行散列。
但是,Brad,我们刚刚在上一节中讲述了使用 md5 函数对数据库中的数据进行哈希运算的整个过程,这是怎么回事呢?
简短的回答是,有时候,这个实例中 python 散列函数的结果是不一致的。我发现欺骗给我带来了痛苦,所以这是一次学习的经历。
我把这个留在这里是有原因的。我的表上的哈希值被定义为主键。我可以放弃它,使字段为空,或者我可以做一些其他的工作,但是我意识到在导入后用一个新的散列来更新这些值要简单得多。我选择了最省力的方法。如果你想继续下去,你可能会有更好的结果,但我知道我目前的设置是可靠的。
跳过这一步,我们现在准备进行第一次插入。我倾向于使用我为此定义的一些其他函数,但我也将它们放在这里供您参考:
conn = dbf.connect(conn_params_dic)
conn.autocommit = True
dbf.copy_from_dataFile(conn, released, 'fed_releases_tmp')
dbf 引用的是我拥有的一个单独的模块,它只包含我使用的数据库函数。我有连接函数和复制数据文件函数。以下是这些函数供参考:
def connect(conn_params_dic):
conn = None
try:
conn = psycopg2.connect(**conn_params_dic)
if debug == 1:
print("Connection successful..................")
except OperationalError as err:
show_psycopg2_exception(err)
# set the connection to 'None' in case of error
conn = None
return conndef copy_from_dataFile(conn, df, table):
tmp_df = "/tmp/insert_data.csv"
df.to_csv(tmp_df, header=False, index=False)
f = open(tmp_df, 'r')
cursor = conn.cursor()
try:
cursor.copy_from(f, table, sep=",")
conn.commit()
print("Data inserted successfully....")
cursor.close()
except (Exception, psycopg2.DatabaseError) as error:
os.remove(tmp_df)
# pass exception to function
show_psycopg2_exception(error)
cursor.close()
os.remove(tmp_df)
此外,还使用这两个函数:
def show_psycopg2_exception(err):
# get details about the exception
err_type, err_obj, traceback = sys.exc_info()
line_n = traceback.tb_lineno
print("\npsycopg2 ERROR:", err, "on line number:", line_n)
print("psycopg2 traceback:", traceback, "-- type:", err_type)
print("\nextensions.Diagnostics:", err.diag)
print("pgerror:", err.pgerror)
print("pgcode:", err.pgcode, "\n")def query_return(query):
conn = connect(conn_params_dic)
try:
query_1 = pd.read_sql_query(query, conn)
df = pd.DataFrame(query_1)
except (Exception, psycopg2.Error) as error:
print("Error while fetching data from PostgreSQL", error)
finally:
if conn:
conn.close()
return df
总的来说,这些函数允许建立 psycopg2 连接、通过 copy 命令插入数据、处理异常和进行选择查询。
这些可以合并到您的主获取文件中,或者像我所做的那样,放在一个单独的模块中进行访问。这完全取决于您构建应用程序的内容和方式。
中途总结
哇,我们已经谈了很多了。让我们先喘口气,回顾一下。
我们已经创建了数据库,并设置了保存数据的模式。我们定义了在后端操作数据的函数,并处理所有数据,因此我们有增量更新。
在我们的数据获取过程中,我们已经从美联储建立了到数据库和 API 的连接。我们已经获得了新发布报告的列表,对它们进行了不良字符处理,并运行了将它们插入数据库的函数。
现在,您的 fed_releases_tmp 表应该已经塞满了准备处理的新报告。
让我们进入这个旅程的最后一部分,使用一个报告标识符列表来获取实际的时间序列报告数据,将它们插入到数据库中,然后完成,只留下填充的主表。
检索代码第 2 部分
我们的下一步是定义我们想要的报告列表。确定你感兴趣的是你的事,你需要花一些时间在 https://fred.stlouisfed.org/浏览他们的报告清单,以确定你想要什么。但是,一旦你有了这份清单,你的生活将会更加充实,有最新的经济数据可以戳戳戳,直到你心满意足。
以下是我的清单的一部分:
report_list = [
'WM1NS', # M1 Supply
'WM2NS', # M2 Supply
'ICSA', # Unemployment
'CCSA', # Continued Unemployment
'JTSJOL', # Job Openings: Total Nonfarm
'PAYEMS', # Non-Farm Employment
'RSXFS', # Retail Sales
'TCU', # Capacity Utilization
'UMCSENT', # Consumer Sentiment Index
'BUSINV', # Business Inventories
'INDPRO', # Industrial Production Index
'GACDFSA066MSFRBPHI', # Philidelphia Fed Manufacturing Index
'GACDISA066MSFRBNY', # Empire State Manufacturing Index
'BACTSAMFRBDAL', # Current General Business Activity; Diffusion Index for Texas
'IR', # Import Price Index
'IQ', # Export Price Index
'PPIACO', # Producer Price Index - all
'CPIAUCSL', # Consumer Price Index - all
'CPILFESL', # Consumer Price Index (Core)
'MICH', # University of Michigan: Inflation Expectation
'CSCICP03USM665S', # Consumer Opinion Surveys: Confidence Indicators: Composite Indicators: OECD Indicator for the United States
]
显然,这是所有的地图,但显示了很多共同的经济指标。但是,我们需要处理这个:
# Pull all the reports into a big honking dataframe
all_rep = []
for i in report_list:
df1 = get_report(i)
all_rep.append(df1)df = pd.concat(all_rep)
df['hash'] = df.apply(lambda x: hash(tuple(x)), axis=1)
非常简单,我创建 all_rep 列表作为一个空的占位符。从那里,我读取我的 report_list 列表,并为每个报告调用 get_report 函数,将结果放入 df1。我取结果并将 df1 附加到 all_rep。
我做了最后的连接,然后将结果数据散列到一个名为“hash”的新列中。参见我之前对这个问题的讨论。
现在,我们有了可以输入到函数中并插入的最终系列:
conn = dbf.connect(conn_params_dic)
conn.autocommit = True
dbf.copy_from_dataFile(conn, df, 'fed_reports_tmp')
这与我们对美联储新闻稿所做的一样。
至此,我们应该已经填充了两个 _tmp 表,并准备好进行处理。
这将引导我们进入最后一步:
# Final Cleanuptry:
cursor = conn.cursor()
query = "SELECT rehash_fed_reports_tmp()"
cursor.execute(query)
return_val = cursor.fetchall()
conn.commit() cursor = conn.cursor()
query = "SELECT rehash_fed_releases_tmp()"
cursor.execute(query)
return_val = cursor.fetchall()
conn.commit() cursor = conn.cursor()
query = "SELECT fed_report_update()"
cursor.execute(query)
return_val = cursor.fetchall()
conn.commit() cursor = conn.cursor()
query = "SELECT symbol_cleanup()"
cursor.execute(query)
return_val = cursor.fetchall()
conn.commit() print("Records updated and cleaned up.......")except (Exception, dbf.psycopg2.Error) as error:
print("Error while fetching data from PostgreSQL", error)finally:
# closing database connection.
if conn:
cursor.close()
conn.close()
print("PostgreSQL connection is closed")
在这一步中,我们执行最后的 select 语句来运行我们之前定义的函数。前两个重写了 _tmp 表。第三个函数从 _tmp 表中更新主表。最后一个函数清除 _tmp 表,并为下一次运行做好准备。
最后的过程只是关闭我们的数据库连接,并通过一些友好的消息退出,让我们知道一切正常。
行程安排
我的最后一点是运行这个无人值守。为此,我有一个基本的 cron 条目,它在工作日每 6 小时运行一次该进程:
* */6 * * 1-5 /Library/Frameworks/Python.framework/Versions/3.9/bin/python3 ./data_miners/pull_fed_data.py 2>&1
结论
很难说是写整个过程还是这篇解释过程的文章花了更长的时间。这并不是说这篇文章花了很多时间,而是说一旦你掌握了节奏,获取数据、清理数据并将其放入数据库进行分析就相当容易了。
我鼓励完成这些类型的流程。最好的老师是逆境和排除错误。这在哪里可以改进?您看到了哪些错误?你有更好的解决办法吗?
但是,在数据科学和数据分析的世界里,获取数据只是第一步。以这篇文章为指导,你现在有一大堆热门的相关时间序列数据可以利用。你会用它做什么?
用 python 解决一个复杂的数学问题

图片来源:我
获得矩阵中三个最大菱形和的分步指南
我最近接受了一个 Roblox 编码面试,任务是通过 codesignal 解决一个非常有趣和复杂的编码问题。虽然我不能在结束时间之前完成这个问题,但我被驱使继续这个问题,并在以后结束它。手头的任务是创建一个函数,在一个矩阵中寻找所有可能的菱形(具有特定的半径),对每个菱形中包含的所有数字求和。最后返回具有三个最大菱形和的输出。
在我展示我的函数之前。我想详细说明两个概念:菱形的定义和一个小矩阵函数的例子。
什么是菱形?
菱形是一种类似钻石的形状,它的四条边是相等的。这就像拿两个相同的等边三角形,把它们放在彼此的上面,其中一个倒过来。下图清楚地描绘了菱形的样子。其面积的计算方法是将对角线相乘,然后除以 2。

菱形的图像。图片来源:https://upload . wikimedia . org/Wikipedia/commons/b/b5/rhombus . SVG
矩阵带有菱形或菱形

矩阵的图像
在上图中,我们可以看到一个 4x 5 的矩阵。总共有 6 个菱形,我们将在下面计算它们的总和(请原谅我画的菱形很糟糕)。
菱形 1

菱形半径为 2 的矩阵。
菱形 1 用蓝色菱形突出显示。菱形 1 包含的位数求和如下:99 + 19 + 78 +12 + 12 = 220。
菱形 2

菱形 2 用黄色菱形突出显示。菱形 2 包含的位数求和如下:88 + 78 + 12 + 24 + 66 = 268。
菱形 3

菱形 3 以红色菱形突出显示。菱形 3 包含的位数求和如下:52+ 12+ 24+ 30+ 28= 146。
菱形 4

菱形 4 用绿色菱形突出显示。菱形 4 包含的位数求和如下:78+ 68+ 12+ 66+ 5= 229。
菱形 5

菱形 5 用橙色菱形突出显示。菱形 5 包含的位数求和如下:12+ 12+ 66+ 28+ 90= 208。
菱形 6

菱形 6 用紫色菱形突出显示。菱形 6 包含的位数求和如下:24+ 66+ 28+ 10+ 81= 209。
看看上面六个菱形的总和。最大的总数按降序排列是 268,229,220。下一节将展示这个函数以及它是如何工作的。
密码
上面的代码从一个具有一定半径的矩阵开始。然后,它继续从输入半径偏移 1,以便正确考虑中心点,因此它被考虑用于计算半径。
创建一个 for 循环来遍历矩阵中的每一个点。在 for 循环中,设置了一个条件,以确保矩阵的一部分被视为参与菱形求和,其中心点(当前正在迭代的点)必须以半径为 r 的菱形为界。如果一部分不满足该条件,for 循环将迭代到矩阵中的下一个点。
一旦一个点满足条件,属于菱形的垂直对角线的所有点的指数的垂直阵列被计算,并且属于菱形的水平对角线的所有点的指数的水平阵列被计算。然后,使用这些指数来找出哪些点位于菱形内,并计算与这些指数匹配的所有数字的总和。
在收集所有总和后,该函数将按降序对它们进行排序,并返回三个最大的总和。
测试
下图测试了该函数的有效性。该函数接收标题为 矩阵中使用的相同矩阵,上面有菱形 ,半径为 2。然后返回矩阵中三个最大的菱形和。

测试功能
可以观察到,我们得到了与在什么是菱形中相同的结果。节前。这个函数的伟大之处在于它可以处理更大的矩阵。因此,对于想要计算较大矩阵的三个最大菱形和的人来说,节省了时间,而不必在得出答案之前使用纸笔方法来计算每个单个菱形和。

用更大的矩阵测试函数
在上面随机生成的矩阵中,三个最大的菱形和是 384、362 和 359。
结论
这个问题非常有趣,需要用到两个领域的知识:数学和编码。在我能够用 python 化的方式表达我的函数,之前,我必须首先从数学上考虑它。我开始考虑矩阵的任何部分被归类为菱形所需的所有条件。因此,我能够创建一个对菱形有界点进行分类的数学条件和一个对所有数字求和的数学公式。
之后,在数学上修补这个问题,我能够用 python 来表达它。起初,我在将条件和公式翻译成代码时遇到了问题。然而,在测试和优化函数之后,我能够创建一个有效的函数。
感谢您的阅读。
用于构建和测试该功能的完整代码可以在 这里 找到。
你可以通过这个 链接 获得一个中级会员来支持我的写作。你的一部分会员费每个月会直接给我。
使用 Python 处理 Amazon Dynamo DB
在本教程中,我们将使用 Python 中的 Boto3 模块来处理亚马逊的 NoSQL 数据库 Dynamo DB。本教程还将讨论如何设置 Dynam DB 的本地实例。

你好,我是尼克🎞 on Unsplash
NoSQL 数据库
NoSQL 数据库用于解决 RDMS(关系数据库管理系统)面临的挑战,或者简单地说关系数据库。下面列出了 RDMS 的一些缺点
- 必须预先定义一个模式
- 要存储的数据必须是结构化的
- 很难改变表和关系
另一方面,NoSQL 数据库可以处理非结构化数据,不需要定义模式。
在本教程中,我们将与亚马逊迪纳摩数据库。它是一种键值和文档数据库 NoSQL 数据库。
目录
- 先决条件
- 在本地设置 Dynamo 数据库
- 使用 Python 连接到我们的数据库
- 创建表格
- 插入数据
- 检索数据
- 更新数据
- 删除数据
- 询问
- 结论
- 资源
先决条件
- 对 NoSQL 数据库有基本了解
- 使用 Python 的经验
在本地设置 DynamoDB
第一步
下载并安装 Java SE 。要在您的计算机上运行 DynamoDB,您必须拥有 Java Runtime Environment(JRE)8 . x 版或更高版本。该应用程序不能在早期版本的 JRE 上运行。
第二步
下载并安装 AWS CLI 安装程序。在命令提示符下键入以下命令来验证安装。
aws --version
如果出现错误,您可能需要添加一个路径变量。查看本文了解更多信息
第三步
下载并解压亚马逊迪纳摩数据库
第四步
导航到解压缩 Dynamo DB 的文件夹,并在命令提示符下键入以下命令。
java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb
不要关闭该终端设备您已经完成了对数据库的操作
第五步
配置凭据。在新的命令提示符下键入以下命令
aws configure
第六步
键入以下命令
aws dynamodb list-tables --endpoint-url [http://localhost:8000](http://localhost:8000)
这将返回一个空的表列表,除非您已经有了现有的表。
或者,您也可以将 Amazon Dynamo DB 设置为 web 服务
使用 Python 连接到我们的数据库
开始之前,我们需要设置并激活一个虚拟环境
/* Install virtual environment */
pip install virtualenv/* Create a virtual environment */
python -m virtualenv venv
/* If the above doesn't work, try the following */
python -m venv venv/* Activate the virtual environment */
venv/Scripts/activate
我们将使用 boto3 模块与 Dynamo DB 的本地实例进行交互。
pip install boto3
接下来,我们需要导入库并创建一个数据库对象
import boto3
我们将创建一个类并添加 CRUD 操作作为它的方法。
class dtable:
db = None
tableName = None
table = None
table_created = False def __init__(*self*):
self.db = boto3.resource('dynamodb',
*endpoint_url*="http://localhost:8000")
print("Initialized")
通过创建我们的类的实例来测试您的代码
*if* __name__ == '__main__':
movies = table()
我们将在本文后面使用刚刚创建的类表的实例。
创建表格
在 DynamoDB 中,一个表可以有两种类型的主键:单个分区键或复合主键(分区键+排序键)。
我们将创建一个名为电影的表。电影的年份将是分区键,标题将是排序键。下面是声明密钥模式的格式。将其存储在一个名为 KeySchema 的变量中。
primaryKey=[
{
'AttributeName': 'year',
'KeyType': 'HASH' *# Partition key* },
{
'AttributeName': 'title',
'KeyType': 'RANGE' *# Sort key* }
]
我们还需要声明上述属性的数据类型。
AttributeDataType=[
{
'AttributeName': 'year',
'AttributeType': 'N' #All Number Type },
{
'AttributeName': 'title',
'AttributeType': 'S' #String
},]
我们还需要限制每秒钟对数据库的读写次数
ProvisionedThroughput={
'ReadCapacityUnits': 10,
'WriteCapacityUnits': 10
}
现在已经创建了创建表所需的所有参数。现在我们可以继续使用这些参数来实际创建表。
def createTable(self, tableName , KeySchema, AttributeDefinitions, ProvisionedThroughput):
self.tableName = tableName
table = self.db.create_table(
TableName=tableName,
KeySchema=KeySchema,
AttributeDefinitions=AttributeDefinitions,
ProvisionedThroughput=ProvisionedThroughput
)
self.table = table
print(f'Created Table {self.table}')
上面的函数和我们之前定义的变量将用于创建表
movies.createTable(
tableName="Movie",
KeySchema=primaryKey,
AttributeDefinitions=attributeDataType,
ProvisionedThroughput=provisionedThroughput)
插入数据
要插入的数据的格式如下
{
'year' : 2020,
'title' : 'Some Title',
'info' : {
'key1' : 'value1',
'key2' : 'value2',
}
}
对于每个条目,除了主键(年份和标题)之外,我们可以灵活处理信息中的数据。info 中的数据不需要结构化。
在插入数据之前,我们将创建一个包含几部电影的 JSON 文件。你可以在我的 GitHub repo 里找到 JSON 文件。
def insert_data(self, path):
with open(path) as f:
data = json.load(f)
for item in data:
try:
self.table.put_item(Item = item)
except:
pass
print(f'Inserted Data into {self.tableName}')
获取项目
如果我们知道某项的主键,我们就可以访问数据库中的该项。在我们的例子中,它是+Ttitle 年。我们将尝试访问带有 2020 年和标题“标题 1”的表。
下面是我们的类的方法,它从表中返回项目
def getItem(*self*,*key*):
*try*:
response = self.table.get_item(K*ey* = key)
*return* response['Item']
*except* Exception as e:
print('Item not found')
return None
注意:get_item 函数的 key 参数中的 K 是大写的
这是我们调用函数的方式
print(movies.getItem(*key* = {'year' : 2020 , 'title': 'Title 1'}))
在我们继续讨论更新和删除之前,熟悉几个可以传递给更新和删除函数的表达式参数是有益的。
这两个表达式是更新表达式和条件表达式
下面是一个 UpdateExpression 的示例
UpdateExpression=”set info.rating=:rating, info.Info=:info”
:producer 和 :info 是我们在更新时想要使用的值。它们可以被认为是占位符。
我们还需要传递一个额外的参数expression attribute values来将值传递给这些变量
ExpressionAttributeValues={
':rating': 5.0,
':info': 'Updated Information'
}
在某种程度上,这类似于 Python 中的 format() 函数
你可以在这里找到常见的更新操作(添加、修改、删除)列表
条件表达式类似于 SQL 中的 where 子句。如果评估为真,则执行该命令,否则忽略该命令。
下面是一个例子
ConditionExpression= "info.producer = :producer",
ExpressionAttributeValues={
':producer': 'Kevin Feige'
}
条件表达式也遵循与更新表达式相同的格式
CondtionExpression 可用于条件更新和条件删除。我们将在下面讨论它们。
你可以在这里找到条件表达式的列表
更新
下面是我们班的更新方法
def updateItem(*self*,key, *updateExpression*, *conditionExpression*,*expressionAttributes*):
*try*:
response = self.table.update_item(
Key = key, *UpdateExpression* = updateExpression,
*ConditionExpression* = conditionExpression,
*ExpressionAttributes* = expressionAttributes
)
*except* Exception as e:
print(e)
*return* None
我们将更新凯文·费奇制作的电影。我们将更新信息,添加评分 5,并在流派列表中添加“传奇”流派。
upExp = "SET info.Info = :info , info.rating = :rating, info.Genre = list_append(info.Genre, :genre)"condExp = "info.Producer = :producer"expAttr = {
":info" : "Updated Information",
":rating" : 5,
":genre" : ["Legendary"],
":producer" : "Kevin Feige"
}print("After Update")
movies.updateItem({'year' : 2019 , 'title': 'Title 3'},upExp,condExp,expAttr)
print(movies.getItem(*key* = {'year' : 2019 , 'title': 'Title 3'}))
删除
删除操作类似于更新操作。下面是我们删除一个项目的方法。它接受一个键、条件表达式和表达式属性值。
def deleteItem(*self*, *key*, *conditionExpression*, *expressionAttributes*):
*try*:
response = self.table.delete_item(
*Key* = key,
*ConditionExpression* = conditionExpression,
*ExpressionAttributeValues* = expressionAttributes
)
*except* Exception as e:
print(e)
我们将删除制片人=“ABC”的电影
print("Before Delete")
print(movies.getItem(*key* = {'title':'Title 2' , 'year': 2019}))print("After Delete")
condExp = "info.Producer = :producer"
expAttr = {':producer' : "ABC" }
movies.deleteItem({'title':'Title 2' , 'year': 2019},condExp,expAttr)print(movies.getItem(*key* = {'title':'Title 2' , 'year': 2019}))
询问
我们可以使用创建表时提供的分区键来查询表。在我们的例子中,是年份。查询操作符需要一个分区键,排序键是可选的。
我们将使用 Key 类,你可以在这里阅读更多关于它的内容。
导入密钥类
from boto3.dynamodb.conditions *import* Key
下面是查询的方法
def query(*self*,*projectExpression*,*expressionAttributes*,*keyExpression*):
*try*:
response = self.table.query(
*ProjectionExpression* = projectExpression,
*KeyConditionExpression*= keyExpression,
)
*return* response['Items']
*except* Exception as e:
print(e)
*return* None
参数 ProjectionExpression 是一个字符串,包含我们希望函数返回的列的列表。keyConditionExpression 是使用 Key 类的键条件。在 KeyConditionExpression 中必须有分区键。此外,您还可以传递一个类似于条件表达式的参数 FilterExpression
我们将在 2020 年显示所有以‘M’开头的电影的片名。
print("Movies after 2019 with title starting with M")
projection = "title"
Keycondition = Key('year').eq(2020) & Key('title').begins_with('M')
print(movies.query(projection,expAttr,Keycondition))
结论
我希望这篇文章能够帮助你。如果上面的代码片段有任何错误,请参考我在参考资料部分提到的 Github repo。如果您发现任何错误,请务必通知我:)
快乐学习!
资源
Github 回购
https://github.com/rahulbanerjee26/DynamoDB_Boto3
我最近用 WordPress 创建了一个博客,如果你能看看的话,我会很高兴的😃
在 LinkedIn 上与我联系
https://www.linkedin.com/in/rahulbanerjee2699/
在 Kaggle 上无缝使用 Python 的数据表库
在 Kaggle 上管理大型数据集,无需担心内存不足的错误

作者图片
Datatable 是一个用于操作大型数据帧的 Python 包。它旨在提供大数据支持并实现高性能。这个工具包很像 熊猫 但是更注重速度。支持内存外数据集,多线程数据处理,有灵活的 API。过去,我们写过几篇文章,详细解释了如何使用 datatable 以令人难以置信的速度读取、处理和写入表格数据集:
- Python 的数据表包概述
- 用 Python 的数据表包 加速你的数据分析
这两篇文章在某些参数上比较了 datatable 和 pandas 库的性能。此外,他们还解释了如何使用 datatable 进行数据争论和管理,以及与同一领域的其他库相比,他们的性能如何。
https://h2oai.github.io/db-benchmark/
然而,本文主要关注那些对在 Kaggle 平台上使用 datatable 感兴趣的人。最近,Kaggle 上的许多比赛都有数据集,这些数据集是不可能单独通过熊猫读取的。我们将看到如何使用 datatable 高效地读取这些大型数据集,然后无缝地将它们转换成其他格式。
目前
datatable处于测试阶段,正在积极开发中。
装置
Kaggle Notebooks 是一个云计算环境,支持可重复的协作分析。datatable 包是 Kaggle 的 docker 映像的一部分。这意味着在 Kaggle 上安装这个库不需要额外的工作。您所要做的就是导入库并使用它。
**import** datatable **as** dt
**print**(dt.__version__)
0.11.1
但是,如果您想要下载特定版本的库(或者最新版本的库),您可以通过 pip 安装库来完成。确保笔记本电脑中的互联网设置为打开。

*!pip install datatable==0.11.0*
如果你想在你的系统上本地安装 datatable,遵循官方文档中给出的说明。

来源:https://datatable . readthedocs . io/en/latest/start/install . html # basic-installation
使用
现在让我们看一个例子,使用 datatable 的好处显而易见。我们将在演示中使用的数据集来自最近一次名为 Riiid 答案正确性预测竞赛 的 Kaggle 竞赛。 挑战是通过随着时间的推移对学生知识进行建模来创建“知识追踪”的算法。换句话说,目的是准确预测学生在未来的互动中会如何表现。

https://www.kaggle.com/c/riiid-test-answer-prediction
**train.csv file**由大约一亿百万行组成。数据大小非常适合演示数据表库的功能。

训练数据大小
不幸的是,Pandas 抛出了一个内存不足错误,无法处理如此大规模的数据集。让我们试试 Datatable,并记录读取数据集及其随后转换为 pandas 数据帧所用的时间
1.读取 CSV 格式的数据
数据表中的基本分析单位是一个Frame。这与 pandas DataFrame 或 SQL 表的概念是一样的,即数据排列在具有行和列的二维数组中。
%%time
*# reading the dataset from raw csv file*
train = dt.fread("../input/riiid-test-answer-prediction/train.csv").to_pandas()
print(train.shape)

上面的fread()功能既强大又极快。它可以自动检测和解析大多数文本文件的参数,从。压缩档案或网址,阅读 Excel 文件,等等。让我们检查数据集的前五行。
train.head()

Datatable 读取整个数据集并将其转换为 pandas 不到一分钟。
2.以 jay 格式读取数据
数据集也可以首先以二进制格式保存。jay)然后使用数据表读入数据。 。jay 文件 格式是明确为 datatable 的使用而设计的,但它也可以被其他一些库或程序采用。
*# saving the dataset in .jay (binary format)*
dt.fread("../input/riiid-test-answer-prediction/train.csv").to_jay("train.jay")
现在让我们看看读取 jay 格式文件所花费的时间。
%%time
*# reading the dataset from .jay format*
train = dt.fread("train.jay")
print(train.shape)

中读取整个数据集只需不到一秒钟的时间。杰伦格式。现在让我们把它转换成熊猫,这也相当快。
%%time
train = dt.fread("train.jay").to_pandas()
print(train.shape)

让我们快速浏览一下框架的前几行:
train.head()

这里我们有一个熊猫数据框架,可用于进一步的数据分析。同样,转换所用的时间仅为 27 秒。
结论
在本文中,我们看到了 datatable 包在处理大数据时的表现。由于强调大数据支持,datatable 提供了许多好处,可以缩短在数据集上执行争论任务所需的时间。 Datatable 是一个开源项目,因此它对贡献和合作开放,以改进它,使它变得更好。我们希望您能尝试一下,并在您的项目中使用它。如果你对使用datatable有疑问,使用[py-datatable]标签在堆栈溢出上发帖。
使用 PyTorchVideo 实现高效的视频理解
了解如何轻松可视化和评估 PyTorchVideo 库中的活动分类模型

PyTorchVideo 预言在第五十一可视化(图片由作者提供)
如果你试图找到最好的模型,或者仅仅是与你的任务相关的基线,那么海量的计算机视觉模型可能很难导航。像 TensorFlow Hub 和脸书的 Detectron2 这样的模型动物园让人们很容易接触到受欢迎的模型。此外,像 PyTorch lightning 这样的库使得修改这些模型来满足你的需求变得容易。这对于图像来说很好,但对于视频来说,这是另一回事。视频数据正变得越来越受欢迎,但随之而来的额外复杂性往往使与视频相关的任务处于次要地位。
PyTorchVideo是一个新的库,旨在使视频模型像图像模型一样易于加载、构建和训练。
PyTorchVideo 提供对视频模型动物园、视频数据处理功能和以视频为中心的加速器的访问,以部署所有受 PyTorch 支持的模型,允许无缝集成到现有工作流程中。
PyTorchVideo要完成您的视频工作流程,唯一缺少的是可视化数据集和解释模型结果的方法。这就是第 51 个出现的地方。 FiftyOne 是我在 Voxel51 一直在做的开源工具。它旨在使可视化任何图像或视频数据集变得容易,并探索存储在本地或云中的地面真相和预测标签。51 个数据集和51 个应用程序的灵活表示让您可以快速操作您的数据集并解释您的模型,以找到故障模式、注释错误、可视化复杂标签等等。
这篇博文是最近的 PyTorchVideo 教程的延伸,旨在教你如何将 PyTorchVideo 与fiftone集成起来,从而结束基于视频的 ML 工作流程。具体来说,这篇文章包括:
- 下载动力学数据集的子集
- 加载带有 51 个的视频数据集
- 使用 PyTorchVideo 进行推理
- 可视化和评估PyTorchVideo 模型
跟随 Colab!
你可以在你的浏览器中直接运行这篇博文中的例子。

Google Colab 中这个演练的截图(图片由作者提供)
设置
为了完成这个演练,您需要安装 FiftyOne 、 PyTorchVideo 、 PyTorch 和 TorchVision :
pip install fiftyone pytorch torchvision
虽然 PyTorchVideo 也可以通过pip安装,但本文中的功能要求它通过 GitHub 安装:
git clone https://github.com/facebookresearch/pytorchvideo.git
cd pytorchvideo
pip install -e .
本演练使用了 Kinetics-400 数据集的子集,可通过以下代码片段下载:
视频数据集比图像数据集更难处理的原因之一是,许多流行的视频数据集只能通过 YouTube 获得。因此,你不需要下载一个包含你需要的所有内容的 zip 文件,而是需要运行如下脚本,从 YouTube 下载自数据集管理以来可能不可用的单个视频。
加载和浏览视频数据集
对于图像数据集,有一些基本的选项可用于可视化批量数据,如 pillow 和 OpenCV 。用于可视化视频数据集的选项非常少。 FiftyOne 是一个新的开源库,为图像和视频数据集提供简单而强大的可视化。
如果您的数据集遵循一种通用格式,比如用于检测的 COCO 格式,那么您可以在一行代码中加载它:
即使你的数据集是一个自定义格式,用 FiftyOne 加载你的数据集仍然很简单。例如,如果您使用的是对象检测视频模型,您可以按如下方式加载数据:
在本例中,我们将按照 PyTorchVision 教程运行视频分类模型。通常,视频分类数据集将存储在磁盘上的目录树中,该目录树的子文件夹定义了数据集类别。这种格式可以在一行代码中加载:
如果您自己也在关注,请将鼠标悬停在样本上或点击样本来播放视频:

在五十一应用中观看的动力学视频(图片由作者提供)
我们还需要下载并存储一个默认类名列表,在评估预测时会用到:
wget [https://dl.fbaipublicfiles.com/pyslowfast/dataset/class_names/kinetics_classnames.json](https://dl.fbaipublicfiles.com/pyslowfast/dataset/class_names/kinetics_classnames.json)
运行 PyTorchVideo 模型
在本节中,我们使用 PyTorchVideo 下载并运行视频分类模型,该模型基于我们在上一节中加载的数据,并将结果存储在我们的数据集中。本节代码改编自本 PyTorchVideo 教程。
Torch Hub 是一个预训练 PyTorch 模型的存储库,允许您下载模型并在数据集上运行推理。PyTorchVideo 通过其火炬中心支持的模型动物园提供了数量的视频分类模型,包括 SlowFast、I3D、C2D、R(2+1)D 和 X3D。以下代码片段下载带有 ResNet50 主干的 SlowFast 的慢速分支,并将其加载到 Python 中:
每个模型都有它期望的特定输入结构。标准的工作流程是编写定制脚本,执行必要的加载和转换功能,为每个模型格式化数据。 PyTorchVideo 通过以灵活的方式为您提供这些功能来加速这一过程,这些功能将满足大多数视频处理需求。例如,以下代码构造了转换来对视频帧进行采样、归一化、缩放和裁剪,而无需自己编写任何函数:
由于数据集存储在 fiftone 中,我们可以轻松地遍历这些样本,使用 PyTorchVideo 加载并运行我们的模型,并将预测存储回 fiftone 中,以便进一步可视化和分析:
评估 PyTorchVideo 模型
除了作为数据集监管的开源生态系统,FiftyOne 还旨在通过允许您快速找到并解决模型故障模式来可视化、评估和解释模型。
为此,我们可以从可视化上一节中生成的预测开始:

PyTorchVideo 模型预测在第五十一中可视化(图片由作者提供)
然后,我们可以使用 FiftyOne 来评估具有基本事实的预测,以查看聚合指标和图表,显示诸如混淆矩阵和精确召回曲线之类的东西。这种评估将每个样本的正确性标签(“eval”)添加到数据集,这使得通过正确/不正确的预测进行过滤变得容易,或者更一般地,通过 TP/FP/FN 进行对象检测。评估只需一行代码即可完成:
precision recall f1-score support springboard diving 0.80 0.80 0.80 5
surfing water 1.00 0.60 0.75 5
swimming backstroke 1.00 0.80 0.89 5
swimming breast stroke 0.57 0.80 0.67 5
swimming butterfly stroke 1.00 0.60 0.75 5 micro avg 0.82 0.72 0.77 25
macro avg 0.87 0.72 0.77 25
weighted avg 0.87 0.72 0.77 25
让我们为我们感兴趣的类绘制混淆矩阵:

结果在第五十一个的混淆矩阵中可视化(图片由作者提供)
我们可以将这个图附加到一个会话对象上,使其交互。因此,如果您单击其中一个单元格, FiftyOne App 会话会更新以显示该单元格中的样本。

Jupyter 实验室与 FiftyOne 的互动混淆矩阵(图片由作者提供)
注意:目前只有 Jupyter 笔记本中的绘图是交互式的,但其他环境将很快得到支持!
FiftyOne 还提供了一种新颖的查询语言,通过搜索和过滤任何给定的标签和元数据来创建数据集的视图。这使得浏览数据集和查找与您想到的任何问题相关的样本变得非常容易。例如,我们可以基于跨多个类别的相似置信度快速找到模型对其预测最不确定的样本,并使用来自先前评估的每样本正确性标签(“eval”)来仅查看错误预测的样本:

不确定的 PyTorchVideo 模型预测在51中被可视化(图片由作者提供)
可视化这些样本让我们了解应该添加到训练数据集中的数据类型。为了标记这些以备将来参考,我们可以使用 51 应用程序中的标记功能:

在第五十一个应用中标记样本(图片由作者提供)
这种动手分析的便利性通常会显著提高数据集质量,从而提高模型性能,比仅使用聚合数据集统计数据的任何分析都要快。
视频中的目标检测
虽然大多数大型视频数据集和研究工作都围绕着分类问题,如人类活动识别,但基于视频的 ML 的应用通常涉及对象检测。目前,PyTorchVideo 主要支持视频分类问题,但是,在 FiftyOne 中有视频对象检测功能。
FiftyOne 允许您从 FiftyOne 模型动物园中基于图像的对象检测模型生成预测,或者将来自您自己模型的预测添加到视频数据集中。动物园里有许多模型可供选择。例如,让我们使用 EfficientDet-D0 。我们首先需要安装 TensorFlow 和 AutoML 。我们可以使用 FiftyOne 附带的 eta 包轻松安装 AutoML:
pip install tensorflow==1.14
eta install automl
现在,让我们将该模型应用于视频并可视化结果:

可视化视频对象检测在第五十一(图片由作者提供)
这种可视化需要编写定制脚本来加载原始视频、注释和预测,然后使用像 OpenCV 这样的软件来绘制方框并将可视化导出到磁盘上的新视频中。然后,如果你想改变你正在看的标签,你需要重写你的脚本,并每次重新生成视频。相反,所有这些只花了我们几行代码,并产生了更易于使用和更灵活的数据表示。
摘要
基于视频的机器学习模型越来越受欢迎,但缺乏相同水平的易用代码库,以允许快速开发和评估图像模型。 PyTorchVideo 旨在通过他们的模型动物园、以视频为中心的组件和加速功能,使视频模型更容易实现、训练和评估。另一方面, PyTorchVideo 正在使视频模型的工作变得更容易, FiftyOne 是一个开源库,旨在使管理、评估和改进视频(和图像)数据集变得简单高效。 FiftyOne 和 PyTorchVideo 共同为创建高质量视频数据集和模型节省了大量时间和精力。
关于体素 51
披露:我在 Voxel51 工作,是 五十一 的开发者
Voxel51 总部位于密歇根州安阿伯,由密歇根大学教授杰森·科尔索博士和布莱恩·摩尔博士于 2016 年创立,是一家人工智能软件公司,通过提供开放核心软件构建模块,使计算机视觉和机器学习工程师能够快速设计数据驱动的工作流,从而实现软件 2.0 的民主化。
在 fiftyone.ai 了解更多!
使用回归来理解 AlphaZero 象棋引擎
研究人员已经开始用人类的语言解释 AlphaZero 在想什么。请继续阅读,了解更多信息。

达米亚诺·林戈里在 Unsplash 上拍摄的照片
神经网络的一个关键问题是所谓的“黑箱问题”。神经网络已经在许多领域(语音识别、自动驾驶汽车、游戏、蛋白质折叠等)产生了最先进的结果。)但是我们无法用人类的语言来解释他们所做的决定。让我们举一个具体的例子:象棋引擎。今天最强的两个国际象棋引擎是 Stockfish 和 AlphaZero。
Stockfish 是一个传统的引擎——它用人类创造的概念来评估国际象棋的位置。一个简单的概念是位置上的兵差。如果白棋的棋子比黑棋多,这个概念将为白棋返回正数,为黑棋返回负数。其他的概念更复杂,但是只要有足够的时间,任何人都可以理解所有的 Stockfish 概念(你可以自己查看 Stockfish 代码这里)。从数学上讲,您可以将 Stockfish 概念想象成一个函数,该函数将国际象棋的位置作为输入,并输出一个数字来表示该位置上的概念的评估。
AlphaZero 与 Stockfish 有着本质的不同。根本没有人类创造的概念。AlphaZero 通过一个大型神经网络运行每个国际象棋位置,并在最后吐出它认为最好的棋步。这是一个黑箱:我们不能像看 Stockfish 那样看着一些代码并理解它的思维过程。那么我们是不是注定永远无法理解 AlphaZero 是怎么想的?直到最近,是的。然而,在过去的几年里,有几种方法已经开始破解黑盒。我想强调一个特别简单而有效的方法。这种方法的来源以及结果,见本文的第 4 节。
我们所做的是获取 Stockfish 概念,看看 AlphaZero 神经网络是否包含它们。我们到底该怎么做?对于 AlphaZero 网络中的每一层,我们在该层的激活上运行给定 Stockfish 概念的回归。然后,我们使用该回归的 r 平方来确定该图层是否与 Stockfish 概念相关,r 平方越高,越表明该图层相关。
回归详细信息
上面的描述比较抽象,我们来看一个具体的回归来理清事情。首先,让我们选择一个 Stockfish 概念和一个 AlphaZero 神经网络层。我们将挑选国王安全和第 10 层的概念。这个概念接受一个国际象棋的位置,通过几次试探返回一个代表国王安全程度的数字。接下来,因为我们在做回归,我们需要足够的数据。没问题——我们将从国际象棋数据库中随机抽取几千个位置。然后,我们在每个位置运行 Stockfish king 安全概念。结果是一系列数字——这些是我们回归的 y(因变量)。
为了获得回归的 x(独立变量),我们采用相同的位置样本,并通过 AlphaZero 神经网络逐一运行它们。由于我们选择了第 10 层,所以我们不会通过网络全程运行每个位置;我们停在第 10 层的激活。这些激活是我们的 x。注意,每个 y 是一个标量,每个 x 是一个矢量。
现在,有了数据,我们需要明确我们想要什么样的回归。我们可以做一个简单的线性回归,不需要任何修饰,但是有一个问题。也就是说,AlphaZero 网络中的每一层都很大(总激活数以千计)。因此,正态回归会严重过度拟合,导致人为的高 r 平方值。为了解决这个问题,我们做了一个 L1 正则化回归,取结果的 r 平方作为我们想要的最终值。在这个特定的回归中(第 10 层的国王安全),r 平方为 0.6。因此,我们可以得出结论,第 10 层 AlphaZero 正在计算与 king safety 适度相关的内容。
我们可以对网络中的所有层进行同样的回归,以查看是否存在任何基于层的模式(例如,可能后面的层比前面的层与 king safety 更相关)。我们也可以在网络训练过程中进行这种回归,以深入了解 AlphaZero 如何学习不同的国际象棋概念。作为一名棋手,我对最后一个用例特别感兴趣。人类学习国际象棋从基本概念开始(例如,棋子如何移动,一步棋威胁),然后逐渐进入更高级的概念(国王安全,棋子移动性等)。).AlphaZero 也是如此吗?
在我们继续讨论结果之前,让我们用一些伪代码回顾一下广义回归过程:

广义回归算法。图片作者。
结果
最初论文中的研究人员对 93 种鱼类概念进行了上述回归过程。首先,他们注意到所有概念都出现了一种模式:从训练步骤 16,000 到训练步骤 128,000,所有层的每个概念的 r 平方增加最多。换句话说,AlphaZero 网络中的大部分学习都发生在这个区间。早些时候,我们问了一个问题,AlphaZero 是否会跟随人类,在学习复杂概念之前学习简单概念——答案似乎是否定的; AlphaZero 在相似的时间内学习概念,不管人为指定的难度如何。
研究人员还注意到,一旦网络经过大部分训练(步长> 128,000),大多数简单的概念,如国王是否在检查中或某个棋子是否可以被捕获,都具有在初始层急剧上升的 r 平方,然后在高 r 平方处稳定下来。这表明 AlphaZero 在网络早期处理简单的概念,而 AlphaZero 同意简单的人类象棋概念。当然,当网络还没有被训练时(步骤< 128,000 ),所有层的 r 平方都如预期的那样低。


两个简单概念的回归结果图。左边的概念是游戏方是否可以在一步棋中被将死。正确的概念是玩的一方是否在检查。颜色越浅,r 平方越高。“块”是指神经网络层。经许可从原始纸张拍摄的图像。
对于复杂的概念,情况就不那么清楚了。某些复杂的概念,如 Stockfish 材料不平衡(不仅仅是简单的棋子数差异,涉及国际象棋策略)实际上在后面的训练步骤(步骤> 100,000)中 r 平方递减。这意味着 AlphaZero 不同意鱼群物质不平衡公式的某些内容。随着一些复杂概念的训练进展,这些较低的 r 平方证实了人类国际象棋大师对 AlphaZero 的定性观察:它下棋的方式与人类“不同”。综上所述, AlphaZero 并不认同一些复杂的人类象棋概念。

一个复杂概念的回归结果:鱼群物质不平衡。请注意,在高训练步数时,r 平方实际上是如何降低的。经原始纸张许可拍摄的图像。
当然,这个回归过程并没有告诉我们,例如,AlphaZero 关于物质不平衡的想法与人类有何不同。然而,据我所知,这是第一次,我们找到了一种数学方法来证明存在差异,而在过去,我们所拥有的只是我们的直觉。
在本文中,我们描述了一个回归过程,该过程能够将神经网络中的层与人类概念相关联。这是朝着解决黑箱问题迈出的一步,因为神经网络的内部工作方式对于人类来说是众所周知的难以理解。在未来,我希望看到这种方法应用到国际象棋之外的更多领域。感谢阅读,并请留下任何问题/评论!
更正:Stockfish 的最新版本(从 2020 年 8 月起)现在也有一个神经网络组件。因此它们不再完全是人类可以理解的。
使用 SAP 分析云
从零开始逐个任务地进行数据科学
一个让我兴奋的工具

最近,在观看一场经典的 f1 比赛时,我惊喜地看到了 SAP 分析云的概况,其中米卡·哈基宁非常出色。如果你是一个观看经典老歌的 F1 粉丝,你会知道一定有什么特别的东西吸引你的眼球。事实上,这是我对 SAP 分析云的看法。这是一个让我为 SAP 客户和用户感到兴奋的工具,他们希望将人工智能与机器学习驱动的预测相结合,它还可以进行财务规划和建模。如果你了解行业产品,你会知道通常你需要许多不同的工具来做这样的事情。SAP SAC 在一个地方为您提供这一切,并由 HANA 数据设备提供支持。
那么,为什么在一场经典的情感 F1 比赛中,我会对一款产品感到兴奋呢?继续读下去,让我解释一下!如果你以前没有读过我的作品,你应该知道我喜欢从零开始构建东西,因此对数据库、软件开发和云托管有着更高的认识。数据科学人员可能不会使用 SAC,但我怀疑无代码爱好者会非常兴奋,如果不会被这个领域中的一系列可用产品所破坏的话。
什么是 SAP 分析云?
正如我前面提到的,SAP 分析云(SAC)是一个业务工具捆绑包。 SAP 称之为“在一个解决方案中获得您需要的所有云分析功能,包括商业智能(B.I .)、增强分析、预测分析和企业规划。”。因此,SAC 是一个完整的分析环境,托管在使用 HANA 技术的 SAP 云中。
SAC 是基于云的,你可以注册免费试用并亲自动手。我就是这么做的!我用我的 Mac Mini M1(8g 内存)和谷歌 Chrome 浏览器进行了测试。
什么是 HANA,为什么它很重要?
HANA 是一个由 SAP 设计的内存数据库。SAP 将 HANA 定义为“加速实时数据驱动决策的数据库。”。传统的数据库产品是巨大的发明,但是也有障碍;这些产品包括
- DB2——一系列数据管理产品,包括数据库服务器,由IBM开发。
- MySQL——一个开源 关系数据库管理系统 (RDBMS)
- PostgreSQL——一个强大的开源对象关系数据库系统
- Oracle —一种通常用于运行在线事务处理 (OLTP)、数据仓库 (D.W .)和混合(OLTP & D.W .)数据库工作负载的数据库。
通常,这些产品表现为带有大量物理存储磁盘和核心数据库软件的数据库服务器,所有这些都在孤岛中运行。数据仓库专家在这些系统上工作,他们会针对事务处理(在线事务处理 OLTP)或分析(在线分析处理 OLAP 或数据仓库数据仓库)进行优化,但不会同时针对两者(混合 OLTP 和数据仓库工作负载)。用户体验(响应能力)受以下因素影响:-
- 用作主机的裸机服务器的功率和性能以及内存、存储驱动器速度和 I/O 端口吞吐量都限制了用户性能。
- 服务器的位置和网络带宽的可用性
- 企业的工作负载水平,以及一般不好的用户查询!
因此,性能和用户查询响应时间可能会有所不同,并倾向于强制对工作负载进行批处理或离线处理。如果事情没有高度组织化,那么通过 TCP/IP 和 ODBC 或其他协议连接 Tableau、IBM Cognos、Power Bi、Looker 等产品可能是一种可怕的体验。为每个请求连接到数据服务器的 BI 服务器相当繁重,有大量的处理开销。
HANA 不一样。它是从零开始设计的,旨在消除所有这些问题,并使用存储在大量昂贵的 RAM 中的数据、索引和固态驱动器上的持久性的组合。 HANA 速度极快而解决了传统 SQL 数据库的所有瓶颈和开销。下面的图 1 是 SAP 架构视图。

图 SAP 公开文档中的 HANA 系统。作者从本网站捕捉的图片。
内存数据库技术的概念并不新鲜。我以前的工作总是包括一个 REDIS 数据库用于缓存。我总是发现,使用 REDIS 内存缓存支持 MongoDB 或 Postgres 长期持久性,web 应用程序的性能得到了惊人的提高。
事实上,我第一次使用内存/数据库加速器是在 Netezza 上。 Netezza 是一款“高性能数据仓库设备和高级分析应用,用于包括企业数据仓库、商业智能、预测分析和业务连续性规划”——维基百科。Netezza 改名为 IBM 纯数据,和 HANA 一样,速度很快。将 Tableau、PowerBI、Looker 和其他商业智能工具连接到纯数据提供了超越传统模型的巨大性能,是一个强大的数据仓库解决方案。自然,不利的一面是需要EextractTtransformLoad(ETL)策略,导致了整个行业的 DW/ETL 专家。
分析云
讨论了 HANA 以及我们为什么关注 HANA 之后,分析云就是一个 SAP 托管环境,在集成环境中使用 HANA 技术和 SAP 的分析软件。当我们为 SAC 注册时,我们获得了预配置环境中的租用权,以获得开箱即用的最佳性能。
注册了账户后,我决定去试驾一下。由于我怀疑自己是否能参加一级方程式试驾,我不得不满足于 SAP 的极速体验。
试驾
我想,在我对 F1 经典的情绪状态下,我的思绪飘回到泰坦尼克号和那个现在著名的学习者数据集。图 2 是美国国家海洋和大气管理局拍摄的一张照片,照片中这位经典女士正在休息。"在命运多舛的豪华泰坦尼克号上,一名 17 岁的贵族爱上了一位善良但贫穷的艺术家。"— 来自 IMDB 。那些萦绕心头的主题曲的歌词呢?——我的心将继续……多么激动人心的过山车啊?

泰坦尼克号数据集在很多机器学习课程中使用;的确,我自己也经常用。登录到 SAP SAC 后,我创建了一个新的数据文件夹,如图 3 所示

图 SAC 文件夹“Titantic”作者的图片
接下来,我导入了一个从 Kaggle 中获取的训练和测试文件。如图 4 所示。

图 4:导入的 Titanic 数据文件的作者截图。
单击 train.csv 文件,我们可以看到 SAC 是如何工作的。我们有度量和维度。“度量”是数字,而“维度”是类别和其他参考数据。我添加了图 5 来演示这个视图。你会注意到任何看起来像数字的东西都被认为是一种度量。因此,年龄,羊皮纸,SibSp 和其他可能必须更新为一个维度。

图 5 —我们的训练数据集中的字段。
正如我提到的,泰坦尼克号是一个非常好的使用和研究的数据集。在《走向数据科学》上,我找到了 Niklas Donges 的一篇文章。
Niklas 为每个字段提供了方便的描述,如图 6 所示

图 6 —来自 Niklas Donges 的数据科学表格截图
事实证明,从文件导入到创建故事(仪表板)有一些限制,这使我无法更新数据类型。为了控制数据类型,您需要建立一个模型。使用模型,我能够取得更好的进展。图 7 给出了该模型的视图。

图 7:来自 SAC 的显示模型尺寸的屏幕截图。作者图片
出于我的目的,我对默认值做了很少的修改,但是在实际的模型中,您需要检查每一列并确保它具有正确的数据类型。有了模型,我能够创造一些视觉效果。
画面
图 8 显示了具有网格函数的堆积条形图。我们可以看到一个图表,显示女性和男性,存活率(0 =假,1 =真),按年龄组的分布,以及个体数量。数据显示,大部分妇女和儿童都下了车,只有少数男性乘客幸存。

图 8:作者绘制的谁在泰坦尼克号上的堆积条形图。
图 9 添加了 ticket 类来看看社会阶层是如何公平分配的。

图 9:添加票证类别的附加堆叠网格图。
我想现在大家都知道头等舱和二等舱的乘客做得更好。图 9 显示,一等舱和二等舱的女性乘客大部分都下了车,而三等舱的一些乘客下了车。三班的成年男性似乎比二班或一班的更多。
尽管进行了搜索,我还是无法找到并添加图例标签。标题应该是生存。缺失字段的插补使用基于公式的简单变化函数有点笨拙。我想你可以尝试做卑鄙的诽谤。分组或宁滨分类值似乎是在文件导入过程中由模型中的“公式”完成的。我不得不根据任意截止值手动将年龄维度分为成人、青少年、儿童和退休人员。
机器学习
SAP SAC 的机器学习环境有限。图 10 和 11 显示了分类模型的训练结果。

图 10:分类模型的训练结果。作者图片

图 11:显示分类准确性的混淆矩阵。6%的假阴性/阳性。
我能够应用该模型并获得新的输出。不清楚如何将 ML 模型部署到数据模型以获得连续的推断,但是我没有在这个特性上花太多时间。机器学习选项仅限于刚才金融分析中的选项,如图 12 所示。

图 12:当前可用的机器学习算法。
财务策划
SAP SAC 提供两种类型的数据模型。这些是:-
- 传统 BI 用例的分析模型
- 多维财务规划用例的规划模型,通常使用带有 Excel 插件的 TM1 等产品。
试用许可不允许我在规划模型功能上取得进展,但是基于我所做的概述,它是令人敬畏的。如果你想尝试一下,像我一样获得灵感,我在本文末尾的灵感部分留下了一些提示和链接。
试驾结束了
试驾结束了。该产品是闪亮的和新的,非常诱人。我想,对于大多数大宗购买来说,20 分钟的试驾不会说服你进行大笔金融投资。

扎卡里亚·扎亚恩在 Unsplash 上拍摄的照片
对于任何分析工具,您都需要坐下来规划一个数据模型,实施该模型,然后使用用户定义的用例对您的数据量进行真正的测试。继续进行概念验证,并向自己展示其价值。

灵感
如果您想了解更多关于 SAP 分析云的信息,您可以使用以下资源。
Udemy
https://www.udemy.com/course/sap-analytics-cloud-sac/ https://www.udemy.com/course/sap-analytics-cloud-sac/
SAP 培训
https://training . sap . com/course/sac 01-简介-到-sap-分析-云-教室-026-ie-en/ ?
https://training . SAP . com/training path/Analytics-SAP+分析+云-SAP+分析+云
使用 SHAP 解释——首先理解这些限制
可解释性做对了

我非常享受我的 MBA 学位;我非常喜欢每一个主题,并且能够将每个主题的知识如何帮助我成为一个更好的拥有强大全局观的经理联系起来。然而,有一门课我从来不愿意走进教室,那就是商业伦理。我强烈认为伦理是不可教授的。每个人都有不同程度的信息技术,这是他/她的价值体系的功能。对我来说,也许偷一支笔可以,但偷一辆车就不行了。对其他人来说可能会不同。
最后,普通的定义太好了,不能进一步扩展——“做正确的事情。”
然而,这种“做正确的事情”只对人类有效,因为道德价值是普遍定义的。对于机器来说,价值体系的概念失效了。因此,在我们到达“奇点”这个有争议的阶段之前,为机器定义伦理是人类的责任。
无论是模特的种族偏见、twitter 显著性问题(链接)还是人工智能反基督(链接)——围绕模特偏见和公平问题的例子数不胜数。目前这是人工智能/人工智能发展的最大障碍。
在过去的 5 年里,可解释的人工智能有了巨大的发展。让复杂的模型变得可解释,并试图消除模型中的任何偏见和诱导公平,一直是数据科学家的主要目标之一。
在所有的方法中,SHAP 和莱姆是两个特别的方法,得到了极大的关注。SHAP 以某种方式计算特征的边际贡献,使得总贡献是 100%。另一方面,LIME 关注于局部保真度。即,用局部的、可解释的线性模型来近似任何黑盒机器学习模型以解释每个单独预测的技术。
如果你想了解 SHAP 的运作,这里有一篇关于它的好文章:
在这两者中,虽然 LIME 更快,但是 SHAP 提供了全局和局部的一致性和可解释性,并且在行业中更常用。
在使用 SHAP 之前,应该考虑围绕该方法的各种限制,以便更好地理解和解释该模型。
局限性 1: 相关性而非因果性:重要的是要承认,SHAP 只是“解释”了按照模型结构定义的变量“相关性”。这并不意味着定义的变量也有因果关系。在因果关系缺失的情况下,这可能是因为虚假的相关性,或者是因为模型中省略了变量(一些本可以更好地定义产出的变量从数据集中缺失,而其他变量试图替代这些缺失变量的影响)。重要的是,要对模型中的每个变量的重要性、标志和因果行为进行单独检查。
限制二: 对模型的依赖: SHAP 按设计是“一个特性对模型有多重要”,并不暗示“这个特性在现实中有多重要”。粗略地说,SHAP 显示了变量对输出全球平均值的敏感性——给定模型。
这提出了两个关键限制:
1.注意,由于 SHAP 有助于推断给定模型的特征的重要性,如果模型被不正确地开发/训练,SHAP 推断将存在固有的问题。
2.因为变量的重要性和符号是在全局平均值(称之为基准值)的基础上定义的。基准值本身的不正确可能会导致变量的推断错误——无论是在标牌上还是在特征的重要性上。
限制三:特征重要性和标志:值得注意的是,SHAP 值的推断与模型的“目标”有很强的相关性。例如,如果一个模型是为选择一个好的股票(a 股)而开发的,如果模型的目标是投资组合优化而不是购买/不购买股票,则输出可能具有不同的特征重要性(或标志),尽管这两个模型都旨在增加回报。因此,应始终考虑模型目标来分析 SHAP 输出。
限制 4: 多重共线性问题:如果存在高度多重共线性的变量,则其中一个变量的 SHAP 值会很高,而另一个变量的值为零/非常低。这可能与特性重要性的概念相矛盾。这里的问题不在于 SHAP 如何分配这些值,而是与模型的训练方式有关。如果机器以首先将权重分配给一个变量(比如 x1)的方式被训练,则其他相关变量的贡献(比如 x2)将是最小的。如果从商业角度来看,第二个变量(x2)更直观,这可能看起来是违反直觉的。
正如亚瑟·C·克拉克第三定律所说,“任何足够先进的技术都和魔法没什么区别。”,是像 SHAP 这样的方法论揭示了魔术背后的现实,并为机器定义了道德科学。
SHAP 是提高模型解释力的一个很好的手段。然而,像任何其他方法一样,它有自己的优点和缺点。当务之急是,在使用这种方法时要牢记局限性,并在适当的背景下评估 SHAP 价值观。
如果你遇到了 SHAP 的更多限制,请在评论中分享。
免责声明:本文所表达的观点是作者以个人身份发表的观点,而非其各自雇主的观点。
使用 SimpleElastix 比较膨胀前后的显微镜图像
思想和理论

图 1:NK 细胞扩增前(青色)和扩增后(橙色) ( 图片由作者提供) 的荧光信号。
根据所用光的波长,传统光学显微镜的分辨率限制在大约 200 纳米。较小的结构可以通过使用像 X 射线这样的高能光子来成像。然而,在广泛的生物应用领域中,有必要使用光学显微镜和荧光染料。尽管几种成像技术的发展超过了分辨率极限,如 d STORM ,但这种极限仍然对理解生物的基本过程构成了障碍。随着膨胀显微镜技术的发展,一种新的有前途的方法被开发出来,从不同的角度观察事物。不是增加空间分辨率,而是将样本扩展到合适的大小,揭示进一步的结构细节和生物相互作用。然而,新方法往往会带来新问题:
扩展后成像的结构是否与原始结构相同?
如果出现扭曲,扭曲的程度有多大?
为了理解扩张过程中哪些调整是“自然”的,哪些必须算作扭曲,我们必须看一下自由度(自由度)。

图 2:相似度变换(图片作者)。
由于样本被扩展,可以假设在 x 和 y 方向上的线性放大 s 被包括在该过程中。由于样品在显微镜下的定位,x 和 y 方向的偏移 t 以及角度 θ 的旋转也包括在内。这些参数共同构建了所谓的相似性变换 T ,捕捉完美线性扩展中出现的自由度(图 2)。

等式 1:相似变换。该等式描述了源点 x 的缩放 s、移动 t 和旋转 R,详细说明参见。
使用 elastix(一个用于图像配准的 C++库)优化这种转换,如果出现的失真是刚性的,则产生高相关指数。

图 3: B 样条变换 ( 图片作者) 。
为了检测剩余的各向异性,我们尝试尽可能好地对齐我们的相似性变换的结果,并应用弹性 B 样条变换。B 样条变换引入了具有 N_x 控制点和每个控制点的 2 个自由度的规则网格。这为精确对准提供了高度的灵活性(图 3)。

等式 2: B 样条变换:该等式描述了当前位置 x 向立方插值 B 样条控制点 x_k 的移动。有关详细说明,请参见。
用优化的 B 样条变换来变换网格 X,Y 产生了局部矢量偏移,其指示了在与相似性变换对齐之后剩余的各向异性(图 4)。

图 4:****NK 细胞上的排列过程。
分步指南,用于对齐扩展前和扩展后的映像
对于以下步骤,必须对膨胀前后的相同结构进行成像。一个困难但(在复习过程中)通常是有益的过程。对齐扩展前和扩展后的图像并创建扭曲贴图只需点击几下鼠标并使用 python 环境。
首先,您需要获得一个与您的 python 发行版相匹配的 simpleelastix⁴(elastix 的 python 绑定)版本。一些预建版本可以在这里找到。如果您的版本不是预构建的,而您使用的是 Anaconda,那么最简单的步骤就是用合适的 python 版本创建一个新环境。例如:
conda create -n "myenv" python=3.6.0
将 SimpleElastix 安装到您的虚拟环境中,方法是切换到您刚刚下载并运行的文件夹:
python setup.py install
如果一切顺利,你可以得到一个带有预设变量的工作脚本,并在这里实现绘图。
对于这个脚本(除了 SimpleElastix),你只需要 matplotlib(绘图),numpy(数组操作)(可能都是预装的)和 skimage(图像加载和调整大小)。
conda install scikit-image
剩下唯一要做的就是在 main.py 中定义文件的路径
EXPECTED_EXPANSION = 3.5#replace with your expected expansion factor
root = r"PATH TO YOUR DATA"+"\\"#replace with your root directory
path1 = r"POST EXPANSION.tif"#replace with your post-expansion image
path2 = r"PRE EXPANSION.tif" #replace with your pre-expansion image
save_dir = root + r"\results"+"\\"
save_name = path2.split(".")[0]
关键概念
现在一切都应该启动并运行(如果不是随时与我联系)。在下一节中,我将描述脚本中使用的一些代码概念,以便用 python 创建和绘制扭曲贴图:
为扭曲贴图创建 B 样条变换:
创建扭曲贴图:
结论
本文展示了如何在 python 中使用 SimpleElastix 对齐扩展前和扩展后的图像。
如果你在安装软件时还有问题或困难,请在评论中告诉我。
参考文献:
1。
使用 Sklearn 管道简化您的机器学习过程
了解 Pipeline 类如何简化和自动化您的机器学习工作流

机器学习通常涉及多个步骤——加载数据、可视化数据、拆分数据、预处理数据,然后最终用训练数据训练模型。所有这些步骤都必须按顺序进行,我们通常在 Jupyter Notebook 中按顺序执行所有这些步骤。在你知道它之前,它是一个混乱的地狱,代码片段分散在不同的单元格中。然而,所有这些都可以使用 sklearn 的Pipeline类来简化,该类旨在提供一种自动化机器学习工作流的方法。
在这篇文章中,我将向你解释如何使用 sklearn Pipeline来定义和自动化你的机器学习工作流。
执行机器学习
在我们讨论如何使用 sklearn 的管道来简化机器学习过程之前,我们先来看看在数据集上执行机器学习的典型步骤。这样,您将更好地理解为什么使用管道进行机器学习是有用的。
加载数据
首先要做的是加载数据。对于本文,我们将使用流行的泰坦尼克号数据集,我们将从 ka ggle(https://www.kaggle.com/c/titanic/data?select=train.csv)下载该数据集:
import pandas as pd
import numpy as npdf = pd.read_csv('[train.csv'](https://raw.githubusercontent.com/dsindy/kaggle-titanic/master/data/train.csv'))display(df)
数据来源:本文数据来源于https://www.kaggle.com/c/titanic/data。
正如您从输出(如下)中看到的,总共有 12 列,其中一些对于预测乘客是否会在灾难中幸存没有用:

作者图片
让我们选择要用于机器学习模型的特定列:
# save only those columns that we want to use as features
df = df[['Survived','Pclass','Sex','Age','Fare','Embarked']]
df
为了简单起见,我将不深究为什么选择上面的列。如果您想了解关于特性选择的更多信息,请查看我以前的文章:
Python 中的统计—使用 ANOVA 进行特征选择—https://towardsdatascience . com/Statistics-in-Python-Using-ANOVA-for-Feature-Selection-b4dc 876 ef4f 0
Python 中的统计—使用卡方进行特征选择—https://towards data science . com/Statistics-in-Python-Using-Chi-Square-for-Feature-Selection-d44f 467 ca 745
Python 中的统计—共线性和多重共线性—https://towardsdatascience . com/Statistics-in-Python-共线性和多重共线性-4c 4 DCD 82 B3 f
Python 中的统计—了解方差、协方差和相关性—https://towardsdatascience . com/Statistics-in-Python-Understanding-Variance-协方差-and-Correlation-4729 b 528 db 01
剩余的数据帧现在有六列:

作者图片
让我们检查数据集中是否有 nan:
# check for NaNs
df.isna().sum()# Survived 0
# Pclass 0
# Sex 0
**# Age 177** # Fare 0
**# Embarked 2**
# dtype: int64
如上面以粗体突出显示的那样,年龄和着手列有 nan。
拆分数据
在我们进行任何特性预处理之前,让我们将数据分成训练集和测试集:
from sklearn.model_selection import train_test_split# get all columns except Survived
X = df.iloc[:,1:]# Survived
y = df.iloc[:,0]# perform the splitting now
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size = 0.3,
stratify = y,
random_state = 0)# reset the indices for training and testing sets
X_train = X_train.reset_index(drop=True)
y_train = y_train.reset_index(drop=True)X_test = X_test.reset_index(drop=True)
y_test = y_test.reset_index(drop=True)
请注意,我们在进行任何数据预处理(如编码、替换 nan 等)之前分割了数据集。这里有两种观点——一种观点认为,在分割数据之前,我们应该首先进行数据预处理。另一个学派认为,我们应该在进行任何数据预处理之前拆分数据。一般来说,要防止“数据泄露”,最好先拆分数据,然后独立于测试数据对训练数据进行预处理。使用定型数据对模型进行定型后,您可以对测试数据进行预处理,并将其提供给模型进行评估。
在上面的代码片段中,数据帧被分成 70%的训练集和 30%的测试集。为了确保训练集和测试集具有相同比例的存活值,我们对**y**进行了分层,并为可重复性固定了一个**random_state**参数值。
特征预处理
下一步是预处理训练数据。这里我们需要预处理两种类型的列——数字列和分类列:
对于数字列,我们要做两件事:
- 用一些值替换数字列中的所有 nan
- 标准化数字列值
首先,我们用各列的中值替换年龄和票价列中缺失的值。为此,您可以使用 sklearn 中的**SimpleImputer**类:
from sklearn.impute import SimpleImputer# use the SimpleImputer to replace all NaNs in numeric columns
# with the median
numeric_imputer = SimpleImputer(strategy='median',
missing_values=np.nan)# apply the SimpleImputer on the Age and Fare columns
X_train[['Age','Fare']] = \
numeric_imputer.fit_transform(X_train[['Age','Fare']])display(X_train)
参考我之前关于使用 SimpleImputer 类的文章:
年龄和费用栏中缺失的值现在用各栏的中间值填充:

作者图片
下一步是使用**StandardScaler**类标准化这些值:
from sklearn.preprocessing import StandardScaler# Standardize the Age and Fare columns using the StandardScaler
scaler = StandardScaler()
X_train[['Age','Fare']] = \
scaler.fit_transform(X_train[['Age','Fare']])display(X_train)
年龄和票价列的值现已标准化:

作者图片
对于分类列( Pclass 、 Sex 和oaked),您希望用最频繁出现的值替换缺失值。同样,您可以使用**SimpleImputer**类:
# use the SimpleImputer to replace all NaNs in categorical columns
# with the most frequent ones
categorical_imputer = SimpleImputer(strategy='most_frequent',
missing_values=np.nan)# apply the SimpleImputer on the Pclass, Sex, and Embarked columns
X_train[['Pclass','Sex','Embarked']] = \
categorical_imputer.fit_transform(
X_train[['Pclass','Sex','Embarked']])
X_train
您现在可以确认在X_train数据框中不再有任何 nan:
X_train.isna().sum()# Pclass 0
# Sex 0
# Age 0
# Fare 0
# Embarked 0
# dtype: int64
既然 dataframe 中没有 nan,那么对分类列要做的最后一件事就是执行编码。有两个选项可用:
- 序号编码 —这适用于存在隐式排序的列。例如经济状况、等级、乘客级别等。
- One-hot encoding —这对于值的排序不重要的列很有用。例如性别、发色、肤色等。
在我们的数据集的情况下,**Sex**和**Embarked**的值没有隐式排序,因此它们可以被一次性编码:
from sklearn.preprocessing import OneHotEncoder# one-hot-encode the categorical columns - Sex, and Embarked
enc = OneHotEncoder(handle_unknown='ignore')X_train[['Sex_female','Sex_male',
'Embarked_C','Embarked_Q','Embarked_S']] = \
pd.DataFrame(enc.fit_transform(
X_train[['Sex','Embarked']]).toarray()) display(X_train)
您现在应该已经将性别和列的值进行了 one-hot 编码:

作者图片
现在,您可以继续删除性别和装载的列(接下来,您将使用它们的独热编码列):
# drop the Sex and Embarked and use their one-hot encoded columns
X_train.drop(columns=['Sex','Embarked'], inplace=True)display(X_train)

作者图片
最后,您现在可以使用分类器来训练模型。对于这个例子,我将使用**LogisticRegression**类:
from sklearn.linear_model import LogisticRegression# train using LogisticRegression
logregress = LogisticRegression()
logregress.fit(X_train,y_train)
准备用于评估模型的测试集
训练好模型后,您现在可以使用测试集来评估模型,以查看它的执行情况。还记得你在训练集上做的预处理吗?您现在需要对您的测试集做同样的事情:
**# replace all NaNs and standardize the numerical columns**
X_test[['Age','Fare']] = \
numeric_imputer.transform(X_test[['Age','Fare']])**# standardize the Age and Fare columns**
X_test[['Age','Fare']] = scaler.transform(X_test[['Age','Fare']])**# replace all NaNs in the categorical columns**
X_test[['Pclass','Sex','Embarked']] = \
categorical_imputer.transform(
X_test[['Pclass','Sex','Embarked']])**# one-hot encode the Sex and Embarked columnns**
X_test[['Sex_female','Sex_male',
'Embarked_C','Embarked_Q','Embarked_S']] = \
pd.DataFrame(enc.transform(
X_test[['Sex','Embarked']]).toarray())**# drop the Sex and Embarked columns**
X_test.drop(columns=['Sex','Embarked'], inplace=True)display(X_test)
在对测试集执行预处理之后,它现在看起来像这样:

作者图片
预处理测试集以匹配定型集的列是很重要的。否则,您将无法使用它来评估您的模型。
您现在可以调用**score()**函数来评估模型:
**logregress.score(X_test,y_test)**
# 0.8097014925373134 this is the accuracy score
使用流水线简化机器学习
因此,您刚刚使用上一节中的 Titanic 数据集构建并训练了一个模型。你注意到了什么?以下是值得注意的几点:
- 您必须分别预处理您的训练集和测试集,这涉及到相当多的重复性工作
- 这涉及到许多步骤,必须按照正确的顺序来执行
使用 sklearn Pipeline类,您现在可以为您的机器学习过程创建一个工作流,并强制执行各个步骤的执行顺序。
在接下来的章节中,你将看到如何使用 sklearn Pipeline类来简化之前的机器学习过程。
加载和拆分数据
第一步是加载数据并执行拆分:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_splitdf = pd.read_csv('[https://raw.githubusercontent.com/dsindy/kaggle-titanic/master/data/train.csv'](https://raw.githubusercontent.com/dsindy/kaggle-titanic/master/data/train.csv')) df = df[['Survived','Pclass','Sex','Age','Fare','Embarked']]
X = df.iloc[:,1:]
y = df.iloc[:,0]X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size = 0.3,
stratify = y,
random_state = 0)
为预处理创建管道
让我们使用**Pipeline**类来指定将转换应用到数据的一系列步骤。下面的代码片段创建了三个Pipeline对象:
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder# define the transformer for numeric columns
# for 'Age' and 'Fare'
**numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])**# define the transformer for categorical columns
# for 'Sex' and 'Embarked'
**categorical_transformer1 = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])**# define the transformer for categorical columns
# for 'Pclass'
**categorical_transformer2 = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent'))**
**])**
在上面的代码片段中,我:
- 创建了一个
Pipeline对象(**numeric_transformer**),首先用他们的中间值替换年龄和费用列中的 NaNs。然后,这两列的值被标准化:
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
- 创建了另一个
Pipeline对象(**categorical_features1**)来替换 Sex 和abowed列中的 NaNs,使其具有每列中最频繁出现的值。然后,对这两列的值进行一次性编码:
categorical_transformer1 = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
- 创建另一个
Pipeline对象(**categorical_features2**)来替换 Pclass 列中出现频率最高的值:
categorical_transformer2 = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent'))
])
**Pipeline**类的步骤参数接受一个元组列表。每个元组包含:
- 转换的名称,以及
- 实现
fit或transform方法的转换对象。例如,SimpleImputer、StandardScaler、MinMaxScaler等。最后一个变换对象可以作为估计器(实现fit方法),例如LogisticRegression等。
Pipeline对象中的转换按照元组列表中指定的顺序执行:

作者图片
接下来,您将使用**ColumnTransformer**类将在Pipeline对象中指定的转换应用到 dataframe 中的各个列:
from sklearn.compose import ColumnTransformerfeatures_preprocessor = ColumnTransformer(
transformers=[
('numeric', numeric_transformer, ['Age','Fare']),
('categorical1', categorical_transformer1, ['Sex','Embarked']),
('categorical2', categorical_transformer2, ['Pclass'])
])
在上面的代码片段中,您应用了:
**numeric_transformer**Pipeline对象为年龄和票价列**categorical_transformer1**Pipeline对象到性别和列**categorical_transformer2**Pipeline对象到 Pclass 列
如果有不需要转换的列,您应该将
ColumnTransformer类的remainder参数设置为passthrough以确保这些列被保留。否则,它们将被默认删除)
所有这些步骤都被传入ColumnTransformer类并作为**features_preprocessor**返回。现在你可以使用**features_preprocessor**和你想用来训练你的模型的分类器创建另一个Pipeline对象:
from sklearn.linear_model import LogisticRegressionpipe = Pipeline(steps=[
('preprocessor', features_preprocessor), # preprocess features
('classifier', LogisticRegression()) # apply classifier
])
在上面的代码片段中,我使用了**LogisticRegression**类来训练模型。
回想一下,
**Pipeline**类接受一个包含转换对象列表的元组列表,最后一个类可以实现fit()方法。
最后,您可以使用Pipeline对象训练模型:
# start the training
pipe.fit(X_train, y_train)
运行上述代码,您将看到以下输出:

作者图片
对模型评分
要评估模式,请调用Pipeline对象上的score()方法:
pipe.score(X_test,y_test)
# 0.8097014925373134
输出与前面手动执行机器学习的部分相同。
概括一下,下面是使用Pipeline对象的整个代码块:
import pandas as pd
import numpy as npfrom sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LogisticRegressiondf = pd.read_csv('[https://raw.githubusercontent.com/dsindy/kaggle-titanic/master/data/train.csv'](https://raw.githubusercontent.com/dsindy/kaggle-titanic/master/data/train.csv'))df = df[['Survived','Pclass','Sex','Age','Fare','Embarked']]
X = df.iloc[:,1:]
y = df.iloc[:,0]X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size = 0.3,
stratify = y,
random_state = 0)# define the transformer for numeric columns
# for 'Age' and 'Fare'
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])# define the transformer for categorical columns
# for 'Sex' and 'Embarked'
categorical_transformer1 = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])# define the transformer for categorical columns
# for 'Pclass'
categorical_transformer2 = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent'))
])features_preprocessor = ColumnTransformer(
transformers=[
('numeric', numeric_transformer, ['Age','Fare']),
('categorical1', categorical_transformer1, ['Sex','Embarked']),
('categorical2', categorical_transformer2, ['Pclass'])
])pipe = Pipeline(steps=[
('preprocessor', features_preprocessor), # preprocess features
('classifier', LogisticRegression()) # apply classifier
])# start the training
pipe.fit(X_train, y_train)# evaluate the model
pipe.score(X_test,y_test) # 0.8097014925373134
如果您观察,会发现不需要对测试集执行预处理——Pipeline对象的score()方法会处理好它!此外,工作流现在定义得更加清晰,也更容易理解。
通过 GridSearchCV 使用管道
您还可以使用带有GridSearchCV类的Pipeline对象进行超参数调优。
下面的代码片段显示了如何将Pipeline对象传递给GridSearchCV类,以便您可以找到使用LogisticRegression类训练您的模型的最佳超参数:
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegression
import warningswarnings.filterwarnings('ignore')pipe = Pipeline(steps=[
('preprocessor', features_preprocessor), # preprocess features
('classifier', LogisticRegression()) # apply classifier
])**# parameter grid
parameters = {
'classifier__penalty' : ['l1','l2'],
'classifier__C' : np.logspace(-3,3,7),
'classifier__solver' : ['newton-cg', 'lbfgs', 'liblinear'],
}****clf = GridSearchCV(pipe, # model
param_grid = parameters, # hyperparameters
scoring='accuracy', # metric for scoring
cv=10) # number of folds****clf.fit(X, y) # GridSearchCV will automatically split the data
# into training and testing data**
当将
GridSearchCV与Pipeline对象一起使用时,注意超参数的键必须以转换器的名称为前缀—“**classifier__**”(注意双下划线)。

作者图片
模型定型后,您现在可以评估模型:
**clf.score(X_test, y_test)**
# 0.8134328358208955
现在模型的精度提高了(与之前的 0.8097014925373134 相比)。
您可以打印出最佳超参数和最佳估计值的平均交叉验证分数:
print("Tuned Hyperparameters :", clf.best_params_)
print("Accuracy :",clf.best_score_) # Mean cross-validated score
# of the best_estimator
您应该会看到以下结果:
Tuned Hyperparameters : {'classifier__C': 0.1, 'classifier__penalty': 'l2', 'classifier__solver': 'liblinear'}
Accuracy : 0.7934956304619226
使用管道评估分类器
Pipeline对象的另一个好用途是用它来评估不同的算法来训练你的模型。对于我们的例子,让我们尝试不同的分类器,看看哪一个给出最高的准确性。
让我们导入以下模块:
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, \
AdaBoostClassifier
from sklearn.discriminant_analysis import \
QuadraticDiscriminantAnalysis
from sklearn.gaussian_process import GaussianProcessClassifier
from sklearn.gaussian_process.kernels import RBF
from sklearn.neural_network import MLPClassifier
from sklearn.naive_bayes import GaussianNB
创建我们希望使用的分类器列表,并指定其特定参数:
classifiers = [
LogisticRegression(C=0.1, penalty='l2', solver='liblinear'),
KNeighborsClassifier(3),
KNeighborsClassifier(),
KNeighborsClassifier(7),
SVC(kernel="linear", C=0.025),
SVC(gamma=2, C=1),
GaussianProcessClassifier(1.0 * RBF(1.0)),
DecisionTreeClassifier(max_depth=5),
RandomForestClassifier(max_depth=5, n_estimators=10,
max_features=1),
MLPClassifier(alpha=1, max_iter=1000),
AdaBoostClassifier(),
GaussianNB(),
QuadraticDiscriminantAnalysis(),
]
我们还将使用一个数据帧来存储每个分类器的精度:
# dataframe to store the accuracy of each classifier
df_results = pd.DataFrame(columns=
['Classifier', 'Accuracy'])
要试用各种分类器:
- 遍历列表中的每个分类器,并在每次迭代中创建一个
Pipeline对象。 - 对于每个
Pipeline对象,应用预处理器,然后应用分类器。 - 使用
Pipeline对象训练一个模型,然后对其进行评估。
以下代码片段总结了上述步骤:
# train a model using each classifier
for classifier in classifiers: # create the pipeline to preprocess the features
# and apply the classifier
pipe = Pipeline(steps=[
('preprocessor', features_preprocessor),
('classifier', classifier)]) # train the model
pipe.fit(X_train, y_train)
# append the result to the dataframe
df_results = df_results.append(pd.Series({
'Classifier' : classifier,
'Accuracy' : pipe.score(X_test, y_test)
}),ignore_index = True)
当使用并评估所有分类器时,您可以基于最高准确度对结果数据帧进行排序:
display(df_results.sort_values(by='Accuracy', ascending=False))
结果如下所示:

作者图片
摘要
在本文中,我已经讨论了 sklearn 中Pipeline类的使用。在 sklearn 中使用管道最大的好处就是让你的机器学习工作流程更清晰,更容易理解。此外,您可以使用它来快速评估数据集的各种机器学习算法。我希望您现在对 sklearn 中的管道是如何工作的有了更清楚的了解!
https://weimenglee.medium.com/membership
使用 spaCy 3.0 构建自定义 NER 模型
爆炸使 spaCy 成为 Python 中 NLP 的免费开源库。最近,他们发布了 3.1 版本的更新,这个更新改变了 v2 的很多东西,打破了以前在 Medium 上找到的许多教程。我在 Spacy v3 上唯一能找到的另一篇文章是这篇文章关于用 Spacy 3.0 构建文本分类器。
在该教程的基础上,本文将研究如何在 Spacy v3.1 中构建自定义 NER 模型,使用 Spacy 推荐的命令行界面(CLI)方法,而不是 Spacy v2 中常见的自定义训练循环。因为这篇文章是一篇更实用的文章,我们不会涉及 NER 是什么的基础知识等。你可以在这篇优秀的文章中找到更深入的信息。引用它,NER 本质上是一个“信息提取任务”,我们试图识别实体(如位置、货币价值、组织、人员等)。从给定的文本中。
概观
本质上,在 Spacy v3 中,有一个转变,即在命令行上使用spacy train命令来训练模型管道,而不是在 Python 中创建自己的训练循环。因此,旧的数据格式(json 等。)不再被接受,您必须将数据转换成新的.spacy格式。因此,我们将探讨两个主要问题:
- 将您的数据从旧的 NER 格式更新为新的
.spacy格式 - 使用 CLI 训练您的数据并配置训练
- 加载模型并预测
1.新的.spacy格式
过去,NER 的格式如下:
然而,Spacy v3.1 版不再采用这种格式,必须先在doc中转换,然后在docbin中转换,才能转换成它们的.spacy格式。这是使用下面的代码完成的,改编自他们的示例项目:
这有助于将文件从旧的 Spacy v2 格式转换为全新的 Spacy v3 格式。
2.使用 CLI 训练您的模型
过去,模型的训练是在 Python 内部完成的。然而,Spacy 现在发布了一个用于 CLI 的spacy train命令。他们建议这样做,因为这样可能会更快,并且有助于评估/验证过程。此外,它内置了早期停止逻辑(而在 Spacy v2 中,必须编写一个包装器代码来实现早期停止)。
使用这个新命令的唯一问题是,老实说,它的文档还没有 100%准备好。然而,没有文档的问题是,我有时很难知道命令行上的输出到底意味着什么。然而,他们在他们的 Github 论坛上非常有帮助,我在那里一直得到很好的帮助。
使用 CLI 的第一步是获取配置文件。您可以在这里创建自己的配置文件,Spacy 创建一个小部件,允许您在必要时设置您的配置。

spaCy 小工具的屏幕截图(按作者)
一旦完成配置(在本例中,选择“English”作为语言,选择“ner”作为组件),我们就可以启动 CLI 了。
填写您的配置文件
配置文件没有完全填充。因此,您应该运行的第一个命令是:
这将使用默认值填充您从 Spacy 的小部件下载的 base_config 文件。你可以使用默认设置,并根据你的需要进行调整,但是现在让我们使用默认设置。
训练模型
一旦完成,你就可以训练你的模型了!此时,您手头应该有三个文件:(config.cfg 文件,(2).spacy格式的训练数据和(3)评估数据集。在这种情况下,我没有创建另一个评估数据集,而是简单地使用我的训练数据作为评估数据集(这不是一个好主意,但只适用于本文!).确保这三个文件都在运行 CLI 的文件夹中。在这里,我还将--gpu-id设置为 0,以便选择我的 GPU。
这是您应该大致看到的输出(它将根据您的设置而变化)

列车输出截图(作者)
- E 是历元的数量
#是优化步骤的数量(=处理的批次)- 损失 NER 是模型损失
- ENTS_F、ENTS_P 和 ENTS_R 是 NER 任务的精度、召回率和 fscore
损失 NER 是基于测试集计算的,而 ENTS_F 等。是基于评估数据集计算的。训练完成后,Spacy 将根据您设置配置文件的方式(即配置文件中关于评分权重的部分)保存最佳模型,以及训练的最后一个模型。
3.用模型做预测
一旦通过 CLI 对模型进行了训练,就可以像在 Spacy 2 中一样加载和使用它。在我们的例子中,它看起来像这样:

预测截图(作者)
而且……它起作用了!
感谢阅读这篇文章!如果您有任何问题,并且希望讨论更多关于 Spacy v3 的内容,并了解其他人也在用它做什么,请告诉我。
原载于http://Zach lim 98 . github . io。
利用 Spark + R 分析巴西紧急财政援助数据
理解大数据
下载、加载和分析超过 10 千兆字节的数据库

什么是紧急经济援助?
紧急财政援助是 2020 年 4 月通过第 13.9821 号法律实施的临时财政支持。其目标受众包括 18 岁以上的个人、个体微型企业家、个体 INSS 纳税人、失业者、个体经营者和非正规工人,其月人均收入达到最低工资的一半(522.50 雷亚尔)或家庭月总收入达到三份最低工资(BRL 3.135),这些人在 2018 年没有被 IRPF 征税。(弗雷塔斯等人,2020 年,第 8 页)。
关于日期
可以通过 PNAD 新冠肺炎数据(2020 年 4 月至 11 月)对紧急财政援助进行多项分析,但是,由于其代表样本,PNAD 数据不包括各受益方的独特信息。为了获得每个月每个受益人的数据,必须使用透明度门户网站上每月提供的数据库:http://www . Portal transparencia . gov . br/download-de-dados/auxilio-emergencial。通过门户,您可以选择想要下载数据的年份和月份。

透明度门户网站(https://www.portaltransparencia.gov.br)
一旦数据被下载并解压缩,就需要在 RStudio 中安装 sparklyr 包,csv 将通过它来加载。

作者图片
关于 sparklyr
Sparklyr 是一个包,它提供了 R 和 Apache Spark 之间的接口(简言之,Apache Spark 是一个用于大规模数据处理的统一分析引擎)。换句话说,在 R/RStudio 内部使用 sparklyr 时,我们实际上是在后台运行 Spark 中的函数。使用 sparklyr,可以执行 dplyr 中的几个功能,从 tidy universe 中打包,例如:过滤器、选择、 group_by 等功能。
使用 sparklyr 比使用 data.table 等大型软件包的优势在于:
- 它能够加载非常大的文件,而不需要集群或非常强大的机器来完成。分析可能性的前沿大大增加,现在可以加载和分析大于计算机 RAM 内存的文件。
- 执行速度。加载大文件不会成为一个耗时的过程,并且一旦加载,利用前面提到的 dplyr 函数,数据操作也非常快速和有效。

图片来源:blog.rstudio.com(blog.rstudio.com)
除了 sparklyr 之外,将使用 Apache 基金会的另一个包:arrow。这个包在后台工作,这意味着你不需要专门从它调用任何函数。这里箭头的作用是通过 sparklyr 优化数据加载,通过下面的链接你可以了解更多关于箭头的用处:https://spark.rstudio.com/guides/arrow/.
解释完了,我们现在开始工作吧。
安装包和加载数据
以下代码执行必要软件包的安装和加载:

作者图片
一旦安装并加载了包,就该加载数据了,为此您首先需要启动一个与 Spark 的本地连接。

作者图片
当执行函数“spark _ connection _ is _ open(spark _ conn)时,预期结果为“ TRUE ”,因此我们知道连接成功。
现在是加载数据的时候了:

作者图片
加载时,您需要将一些参数传递给 spark_read_csv 函数,它们是:
- sc:是之前创建的连接( spark_conn )。
- name:将在 Spark 环境中创建的表的名称(我建议插入在该环境中使用的相同名称,在本例中称为“ aux_emerg_202012 ”)。
- 路径:要上传的文件的原始名称及其扩展名(。csv)。
- 字符集:文件的编码。默认情况下,它是 UTF-8,但是葡萄牙语更适合“Latin1”类型。
- 分隔符:csv 分隔符的类型(逗号、分号、…)。
- 内存:该参数用于将表存储在缓存中。但是我们不希望这样,因为这个想法是为了优化加载速度,为此,它必须被通知" FALSE "。
好了,现在我们已经装载好底座并准备好进行各种分析。

作者图片
分析数据
在这第一点上,我们来看看巴西财政援助受益人的分布情况。

作者图片
你可以很快得到一些有趣的统计数据:12 月份有 77,662,032 人受益。这个数字相当于 2020 年巴西人口的 36.7%。当我们观察 15 岁或以上的人口并将其与用户总数进行比较时,这一百分比增加到 46.3%。
如果我们想知道受益人在城市人口中的分布情况,该怎么办?为此,首先需要过滤掉没有市政当局代码信息的行,然后,按市政当局对受益人进行分组和计数:

作者图片
这是上述代码的结果:一个包含所有市政当局及其总受益人的数据框架。

作者图片
为了获得人口中受益者的比例,我们需要 2020 年每个直辖市的人口估计数。我将使用由 UFRN 人口估计和预测实验室准备的人口预测。他们的作品可以在这里找到:【https://demografiaufrn.net/projecao-populacional/】T4。

作者图片
请注意关于总体的重要一点:从样本中移除了 0 到 15 岁的个体。这是因为紧急财政援助是针对 18 岁和 18 岁以上的个人,因为预测是针对 5 年内 5 岁的年龄组,不可能排除所有 18 岁以下的个人。因此,决定去除 15 岁以下的人口。
有了关于受益人和 15 岁以上人口的数据,现在就有可能绘制一张易于可视化的地图。为此,将使用 geobr 软件包,这是一个由 IPEA 团队构建的巴西软件包。通过这个包,可以很快地从国土上获得几个形状文件。

作者图片
这是上面代码的结果:

作者图片
该地图显示了北部/东北部地区有更多的城市,60%以上的人口受益于财政援助。另一方面,南部/东南部地区受益者占其城市人口的百分比最低。
下面的箱线图有助于进一步澄清这个问题:

作者图片
如果我们想知道这在超滤水平上是如何表现的呢?

作者图片
最后,如何知道受益人是否在前几个月已经被考虑过了?这个怎么衡量?解决方法是知道经济资助分期付款的数目:

作者图片
这是结果:

作者图片
12 月受益人中有 53.27%属于第九期财政援助,26.44%属于第八期,相当于该月所有受益人的 79.71%。简而言之,截至年底,该计划覆盖的绝大多数人口都已经在该计划中注册,因为该计划是为了帮助那些受疫情影响最严重的人而设立的。
代码和数据
我的 Github 上有这些代码。你可以看看这些和其他公共数据分析。财政援助数据可在透明门户网站上找到。
你也可以在 LinkedIn 或 Twitter 上找到我。
利用空间信息探测铅管

弗林特的水塔(左)和腐蚀的铅管(右)。图片来自 BlueConduit 。
道路距离和图表如何帮助机器学习模型在弗林特和其他城市找到铅管
blue conduit和* 哈佛的数据科学项目 的合作。 作者: 贾薇拉Max Cembalest凯文·黑尔&达希尔 。 项目顾问: 贾里德·韦伯,艾萨克·斯拉维特,&克里斯·坦纳。***
几个世纪以来,美国的城市都使用一种廉价、有延展性且防漏的材料来建造他们的水管:铅。今天,铅管带来的健康风险是众所周知的。饮用被铅污染的水会阻碍儿童的发育,并导致成人的心脏和肾脏问题。美国环境保护署(EPA)于 1986 年禁止在新建筑中使用铅管。然而,今天,自来水管道(将水从城市管道输送到每个家庭的管道)在全国仍然很普遍。
众所周知,铅管很难识别和更换。识别铅管的唯一可靠方法是把它从地下挖出来。然而,挖掘是昂贵的,所以假阳性(错误地挖掘安全管道——如铜)是相当昂贵的。使问题更加复杂的是,管道材料的城市记录往往不准确和不完整。

左图:管道挖掘人员在弗林特工作。右图:积水和不完整的弗林特市管道记录。图片来自蓝色管道的贾里德·韦伯。
BlueConduit 的成立就是为了解决上述问题。该公司使用机器学习来预测家庭是否有基于其特征(建造年份、批量大小、消防栓类型等)的领先服务线。).该公司由密歇根大学的两位教授创办,他们在弗林特水危机期间构建了铅识别模型。当弗林特市开始使用教授的模型时,他们的铅管命中率(事实上,有铅服务管道的挖掘房屋的百分比)从 15%上升到 81%。

图片来自 BlueConduit 。
随着 BlueConduit 将其工作扩展到美国更多的城市,他们正在积极研究进一步改进其模型的方法。为了帮助这项工作,我们探索了公司感兴趣的新领域:空间建模。目前,BlueConduit 的模型不使用空间信息(如房屋的纬度和经度)来进行预测。然而,由于城市是一条街一条街地建立起来的,BlueConduit 长期以来一直假设,在最近的邻居之间共享信息可以提高他们的铅管预测。我们的项目调查了这个假设。
为了测试纳入空间信息的有效性,我们建立了一个扩散模型,使用家庭位置来调整 BlueConduit 标准模型的预测。当在弗林特的家庭数据集上进行评估时,我们的模型显示出优于 BlueConduit 的标准模型(在命中率和城市节约方面)。在这篇博文中,我们详细介绍了我们的数据集和评估设置、建模过程和结果。然后,我们讨论对未来工作的影响。

这个项目是由 BlueConduit 和 哈佛的数据科学项目 合作完成的。
数据集&评估
我们使用 BlueConduit 关于弗林特铅管的数据集来构建和评估我们的模型。由于弗林特水危机后的大规模挖掘工作,该市现在拥有全国最完整的铅管数据集之一。该数据集包含 26,863 行,每行代表弗林特的一个地块(住宅或商业地产)。每个地块有 74 个描述的特征,例如市场价值、大小、位置(纬度/经度)、建造年份和投票选区。目标是一个二元指示器,指示宗地是否有主服务线。总的来说,该市 38%的家庭有铅服务线。

左:弗林特铅管数据集片段。右图:弗林特地图,包裹中的铅管被确认为红色。作者图片。
在弗林特,BlueConduit 使用了 XGBoost 模型,除了纬度和经度之外,它接受了所有家庭功能的训练。我们称之为“蓝色管道基线”模型。我们试图通过引入空间信息来提高 BlueConduit 基线测试集的性能。
为了评估我们相对于 BlueConduit 基线的绩效,我们使用了 BlueConduit 开发的两个指标:

密歇根州弗林特市 BlueConduit 的基线模型与随机挖掘的命中率曲线的比较。图片作者。
1.命中率曲线:想象你挖了一堆房子,寻找铅管。命中率是指那些被挖掘的房屋中有铅管的百分比。高命中率意味着你很有效率:在你决定挖掘的房屋中,很大一部分含有铅。命中率度量可以扩展到命中率曲线。假设你管理一个挖掘队,任务是挖掘铅管。你会得到一份家庭住址的打印件,以及他们预测的有线索的概率。鉴于资源和时间有限,一种自然的方法是按照预测概率的顺序挖掘房屋——首先是概率较高的房屋,然后是概率较低的房屋。点击率曲线(如上图所示)以图表形式显示了按预测概率(x 轴)排序的挖掘过程中的累积点击率(y 轴)。理想的命中率曲线是尽可能长的时间尽可能高(高命中率)(超过许多被挖的家),这表明你倾向于在非铅家之前挖铅家。上图显示了 BlueConduit 基线的命中率曲线相对于弗林特市随机挖掘的改善。很明显,BlueConduit 基线往往比随机挖掘产生更高的命中率,随机挖掘提供了一个以城市中铅的真实发生率为中心的命中率(38%的家庭)。一旦所有的房屋都被挖掘出来,这两个数字就会收敛到城市的真实含铅量。
2.平均更换成本:BlueConduit 估计挖掘一根(安全的)铜管的原始成本为 3000 美元。挖掘和更换铅管的费用估计为 5000 美元。⁴然而,前者的成本更令人担忧,因为它代表了“浪费”的城市资金(挖掘已经安全的管道)。为了衡量浪费的资金,我们计算平均更换成本:

l 代表挖出的铅管数量,C 代表挖出的铜(或其他安全材料)管数量。假设该城市想要确定更换其第一批 100 根铅管的预期成本。如果一个挖掘算法导致城市在达到 100 根铅管(L = 100)之前挖掘了很多铜管(高 C 值),那么它平均更换每根铅管的成本会相当高。实际上,这一指标总结了该市每一根铅管将需要花费 5000 多美元,这是由于铜屋的浪费性挖掘造成的。
如果我们可以证明我们的模型提高了命中率曲线并降低了平均替换成本,我们就可以证明空间信息可以用于改进 BlueConduit 基线。
我们的空间模型:扩散
在我们的实验中,我们探索的最有前途的空间建模策略是扩散。⁵:让我们通过一个例子来激励扩散,并展示我们如何使用它。

画面 A:弗林特地图,标出弗林特河和 1925 贝克尔街附近。画面 1925 年贝克尔(红色标记)和周围住宅的卫星图像。B 图中显示的每个家庭都有铅管。作者使用谷歌地图创建的图片。
上图中的 A 部分是弗林特的地图。2014 年,该市将其水源转向弗林特河,用深蓝色突出显示。这一决定标志着弗林特水危机的开始,因为弗林特河水中含有化学物质,会腐蚀城市的铅管。靠近弗林特河的是城市西边的一个居民区,用橙色方框标出。这个社区有高密度的铅服务线。一栋特别的房子——贝克街 1925 号——就坐落在这个街区。
面板 B 放大 1925 Becker St .(用红色指示器标记)及其紧邻的区域。因为弗林特的水管都是在水危机后的几年里挖出来的,所以我们知道了地面真相:图中每家每户都有铅管(包括 1925 年的贝克尔)。因此,如果这个邻域在一个测试集中(即尚未挖掘),一个理想的模型将给出通向每个家庭的高概率。下图显示了 BlueConduit 基线模型给予每个家庭的销售线索预测概率。

通过 BlueConduit 基线模型得出的 1925 年贝克尔(31%)和周围家庭(> 90%)的铅概率。回想一下:这张图片中的所有房屋都有铅管。图片作者。
BlueConduit 基线给出了每个家庭领先的高概率(> 90%),除了一个:1925 Becker St (31%)。经过检查,似乎这个家有一个奇特的特征:它的城市记录表明,它有铜(安全)管道。弗林特的管道记录往往不可靠。然而,明确指出铜管的记录通常是准确的。BlueConduit 基线模型锁定了这个特性,并被“愚弄”了。这个模型给了 1925 年的贝克尔(事实上,它有铅管)一个较低的铅概率,将其放在要挖掘的房屋队列中的第 9136 位。如果挖掘资源有限,团队可能永远不会到达这个家。相反,他们可能会在实际上没有铅管的队列中挖掘更高的房屋。我们如何防止这种结果?
正如我们上面提到的,因为城市是一个街区一个街区建立起来的,我们认为近邻应该有相似的领先概率。因为 BlueConduit 基线不使用空间要素,所以它不能在最近的邻居之间直接共享信息。然而,这种类型的信息共享可以通过扩散来实现。
为了建立扩散,我们首先要建模并建立一个家与家之间距离的图表。下图展示了我们的图形构建过程。首先,我们使用开放的街道地图来查找住宅之间的街道距离(曼哈顿距离)。我们选择使用街道距离而不是哈弗森距离(“直线”距离),因为街道编码了家庭之间的一些共享发展和基础设施。住房开发是一个街区一个街区地进行,而不是一个地区一个地区地进行。因此,两栋后院相邻(哈弗线距离较小)的房子可能是由不同的开发商建造的——特别是如果它们没有通过一条共用的道路相连的话。此外,管道通常建在道路下面。因此,街道距离也可以对共享水管进行编码。

我们模拟了家庭之间的道路距离。然后,我们用这些距离来创建一个图表。上图显示了为 1925 年贝克尔街区创建图表的过程。图片作者。
获得道路距离后,我们创建了一个连接住宅的图表。在我们的图中,每个节点是一个家。每条边的长度/重量由它所连接的两个家庭之间的街道距离来定义。根据我们的信念,信息共享应该只发生在同一个紧邻的家庭之间,我们只连接彼此在 0.5 公里以内的家庭。
随着图形的完全构建,我们最终可以进行扩散。扩散过程如下图所示。在扩散中,图中的值在节点之间是平滑的。在我们的例子中,领先概率在连接的家庭之间是平滑的。例如,位于许多低量级概率住宅附近的高概率领先住宅会将其预测强度扩散到其邻居,其领先概率将降低。在 1925 Becker St .(如图所示)的案例中,我们看到一个低概率铅住宅位于许多高概率铅住宅附近。它从邻居那里借用预测力量,领先的概率上升。这样,扩散允许我们的模型“校正”邻居之间的差异。

扩散前后 1925 年贝克尔附近铅的概率。领先的信心与 1925 Becker 共享(“扩散”),将其领先的概率从 31%提高到 56%。图片作者。
由于使用了扩散,1925 年贝克尔街在挖掘队列中前进了 502 位。从这个角度来看,挖掘 500 个家庭至少要花费 150 万美元。一个预算有限的城市可能没有资金来挖掘额外的 500 套住房。因此,1925 年贝克尔街的挖掘顺序代表了更换和未能修复其危险管道的区别。
然而,贝克街 1925 号只是一个家。扩散给整个城市带来了什么结果?我们将在下一节回答这个问题。
火石的结果
在弗林特对所有 BlueConduit 基线预测进行扩散后,lead homes 在挖掘队列中攀升了 327 个位置(平均)。非领先住宅平均下跌 195 个位置。下图显示了铅和非铅家庭中挖掘顺序变化的全部分布。很明显,铅含量高的房屋倾向于按照挖掘顺序上升(正值),含量低的房屋倾向于下降(负值)。

图片作者。
进一步探索,我们发现了家庭间有益信息共享的证据。下图显示了所有家庭中预测领先概率(扩散后)的变化。

图片作者。
我们看到一个似乎适得其反的趋势:扩散倾向于提高非铅家庭的铅概率,降低铅家庭的铅概率。然而,值得注意的是,BlueConduit 基线模型为大多数家庭提供了高度确定和高度准确的铅预测。换句话说,大多数铅家的铅概率接近 1,大多数非铅家的铅概率接近 0。因此,我们期望扩散会稍微平滑这些概率,回归它们的极值更接近平均值。这意味着许多领先家庭(基线概率接近 1)的领先概率略微下移,许多非领先家庭(基线概率接近 0)的领先概率略微上移。
鉴于这种适得其反的趋势,怎么可能领先的房屋在挖掘队列中倾向于上升,而非领先的房屋倾向于下降呢?关键是关注最初不确定的房屋。在下图中,图 A 显示了铅概率的变化,但仅在 BlueConduit 基线给出中等铅概率值(30% — 70%)的家庭中。在这里,我们看到一个生产趋势:扩散增加了铅家园的概率,减少了非铅家园的概率。

作者图片。
图 B 显示了这些相同房屋的随机样本中挖掘顺序的变化。图形的底部表示它们在 BlueConduit 基线挖掘队列中的位置。顶部代表它们在最终扩散挖掘队列中的位置。因为这些房屋最初被赋予了不确定的预测概率,所以它们位于 BlueConduit 基线挖掘顺序的中心。然而,我们看到扩散将领先的房屋拉得更高,而将非领先的房屋拉得更低。
这些结果表明,从某些家庭到不确定的家庭,信息是有效共享的。类似于 1925 年贝克尔街,我们看到证据表明,不确定的领先住宅(具有中等概率值的领先住宅)往往位于具有更高确定性的领先住宅(概率接近 1 的领先住宅)附近。扩散允许这些不确定的家庭从他们的邻居那里借用预测力量,导致更高的领先概率和在挖掘队列中的更高位置。另一方面,不确定的非铅住宅(具有中等概率值的非铅住宅)往往位于更确定的非铅住宅(概率接近于 0 的非铅住宅)附近。扩散允许这些家庭从他们的邻居那里借用预测力量,导致更低的领先概率和挖掘队列中更低的位置。

作者图片。
重要的是,对不确定房屋的积极影响超过了对更确定房屋的潜在不利影响(其极端概率值向平均值回归)。上图中,我们看到了两组命中率曲线。在左侧面板中,我们比较了 BlueConduit XGBoost 基线和 BlueConduit XGBoost 基线的命中率,并将纬度/经度作为预测因素(标记为:“朴素空间模型”)。我们看到,天真地将纬度和经度作为预测因子并没有显著提高命中率。在右边的面板中,我们比较了我们的扩散模型与相同的 BlueConduit XGBoost 基线模型的命中率。在这里,我们看到证据表明,扩散模型在挖掘队列的前半部分和后半部分都具有较高的命中率。
尽管点击率的原始差异看起来很小,但该市储蓄的估计差异相当大。下图显示了这些节约的估计值。

左图:在 BlueConduit 基线模型和扩散模型下,更换每根铅管的平均成本。右图:扩散节省的累积量(相对于 BlueConduit 基线)。这两个图的跨度从 0%到 90%的所有铅管被移除。作者图片。
因为扩散倾向于在挖掘顺序中向前推动铅管(并向下拉动非铅管),所以在城市中找到并替换大多数铅管需要更少的总挖掘量。这转化为货币储蓄。如上所述,扩散明显降低了整个挖掘过程中的平均更换成本。到 90%的铅管被挖掘出来时,扩散总共为该市节省了 30 多万美元(相对于 BlueConduit 基线模型)。这些节约主要是由防止浪费挖掘非铅家。
最终,除了货币价值,最重要的改善是对居民而言。考虑到挖掘人员的时间限制,更高的命中率意味着当工人在城市中行进时,居民将经历更少的铅暴露(平均而言)。
讨论&未来的工作
通过允许具有高度领先不确定性的家庭从他们的邻居那里借用信息,扩散在挖掘队列中提供了更有效的家庭排序。在我们对弗林特数据集的实验中,这为城市节省了大量资金,并最终减少了城市居民接触铅的时间。
更广泛地说,我们的结果表明,一个家的位置可以传达关于它的建筑、发展和材料的信息。经度和纬度可能无法单独提供这些信息。但是,当使用城市基础设施(例如街道距离)进行编码时,位置可用作铅管的关键预测值。特别是,考虑周到的邻居模型可以允许邻居之间有效的信息共享。这可以导致机器学习模型的广泛改进。
值得注意的是,我们只处理了一个城市的数据:弗林特。因此,我们并不确切知道我们的结果是否会推广到其他城市。然而,我们的工作利用了一个趋势,这可能是许多城市共有的:彼此靠近的房屋往往有相似的管道。随着 BlueConduit 将其工作扩展到美国更多地区,我们希望空间信息将在未来的测试中继续提高模型性能。此外,我们希望 BlueConduit 将改进我们的扩散超参数,或者找到比扩散更好的空间模型,以进一步改进我们的结果。
参考文献&脚注
- 环境保护署,“关于饮用水中铅的基本信息”https://www . EPA . gov/ground-water-and-drinking-water/basic-information-about-lead-饮用水。2021 年 12 月 10 日接入。
- **PBS/Nova,“人工智能正在帮助从燧石中取出铅管。”【https://youtu.be/anHwjIASyj4 **
- 从技术上讲,服务线有“公共”和“私人”两个部分,其中公共部分归城市或公用事业所有,私人部分归房主所有。因为两者都会污染水源,所以如果其中任何一个成分是铅,我们就认为这个家庭是危险的。详情请见:https://www . nrdc . org/experts/Erik-d-Olson/how-can-I-find-out-if-I-have-lead-service-line。
- 这些成本和替换指标的平均成本是按照 Webb 等人(2019)的方法计算的。参见 Jared Webb、Jacob Abernathy 和 Eric Schwartz,“走出去:弗林特的数据科学和供水服务线”,彭博数据交换,2019 年。可在:https://storage . Google APIs . com/flint-storage-bucket/d4gx _ 2019% 20(2)获取。pdf
- 我们还尝试使用图形神经网络、高斯过程和堆叠模型。然而,这些策略并没有改善 BlueConduit 基线。我们对这些模型的结果和讨论可以在我们的技术报告中看到。
使用 SQL 的 DateDiff()表示年龄
潜在的 Bug 以及如何避免它

Kortnee Greenfield 通过 Unsplash 拍摄的照片
在某些时候,您可能会被要求提取客户数据,包括客户与公司交易/接触时的年龄。最有可能的情况是,交易时的年龄并不是数据中已经存在的一列,因为它取决于特定事件发生的时间。
简单的解决方案是使用内置函数 DATEDIFF(),在这个函数中,您可以找到两个日期之间的年份差异。让我们来看看使用这个函数的一些结果。在这些结果中,我们将看到今天的日期与个人的生日相比较。

作者照片
啊哦!你刚刚为未成年人提供了酒,现在闪电就要击中生意,把它夷为平地。很难受,我知道。
变通办法
因此,看起来在使用 DATEDIFF()时,数学运算实际上只计算年份值。一年中的月和日似乎没有被考虑在内。让我们把它考虑进去!
首先,我们将获取这个人的生日,并将使用 DATEADD()函数。在这种情况下,我们将根据 DATEDIFF()结果,添加我们期望此人在给定年份中的年数。
最后,我们将使用 CASE 语句。我们可以说,如果当前事件发生在该年个人生日之前或当天,那么使用 DATEDIFF()函数产生的年龄。如果这个人的生日在事件发生时还没有到来,那就从预期年龄中减去一年。这段代码使用了 T-SQL,为了显示生成的每个步骤,这段代码有点长:
结果是什么样的

作者照片
现在,我们看到快乐的绿色小星星会告诉我们年龄变化发生在哪里。我们没有算错年龄!
说到生日
你有没有想过祝同事生日快乐,但要用最难忘的方式?不要再看了。下面的代码,在你改变了这个人的名字值之后,实际上会返回一条很好的生日消息。你可以随时将代码通过电子邮件发送给那个人或任何为你工作的人。
最后的想法
抽查结果至关重要。我总是喜欢用上一篇文章中的问题,“什么会打破这个?”我仍然喜欢你把它变成一场观众欢呼的游戏的想法。
代码是强大的。它加快了我们处理数据的速度。花时间去理解结果和工具是如何工作的。一如既往,继续学习。
使用带状图一次可视化几十个时间序列
带状图对于在很长一段时间内从几十个(甚至几百个)时间序列中获得头部或尾部非常有用。

由 Unsplash 上的 CHUTTERSNAP 拍摄
随着工业 4.0 时代的到来,所有工业部门的工厂和工业资产运营商都希望利用他们多年来收集的大量数据:在许多情况下,这些数据中只有一小部分被使用,而且只是在被动的情况下使用。假设您经营一家婴儿配方奶粉生产厂,质量团队在进行目视检查时开始注意到半成品中的一些黑斑:尽管您的产品可以安全食用(这些黑斑很可能是煮过头的颗粒,当液体干燥成粉末时,它们会溶解在温水或牛奶中),但这可能会导致更高的客户索赔率,为了安全起见,您可能会扔掉整批产品。
流程工程师可能会与您的常驻数据科学家合作,提取设备和流程数据。使用这些来执行根本原因分析将帮助你产生新的知识:例如,他们可能会提出新的设定点来操作你的制造操作,以防止这一事件在未来批次中发生。
然而,通常情况下,此类问题背后的原因远非简单,可能有多个相互关联的原因:此外,可能存在一些您无法像控制制造变量那样容易控制的依赖因素(天气、供应链变化、原材料质量……)。这意味着,当黑点重新出现在成品中时,您必须再次更新最佳工艺参数。您可能会从之前的根本原因分析中领先一步,并且您可能知道要查看哪些参数以及在哪个时间步骤:然后您可以扩展之前的分析,调整您的制造参数,并继续处理另一个问题…
有一天,你决定试着一劳永逸地解决这个黑点问题!多年来,您一直在收集大量数据并将其置于背景中,您知道深度学习方法可能有助于发现生产环境中的微弱信号…
如果你想建立一个高效的深度学习系统,你需要帮助它筛选成千上万的信号和事件的噪音。精确定位数字时间序列中有趣的模式对于向未来的人工智能模型提供高质量的数据集至关重要。在很长一段时间内对许多时间序列的这种有趣的时间模式的检测取决于时间尺度、起点和将所有数据纳入综合可视化的能力,这将指导您的早期探索。
在上一篇文章中,我描述了准备时间序列数据的过程,以便能够对这些信号进行交互式探索:
然而,你首先要做的可能是绘制所有信号的整体地图。有几个原因可能会让你想拥有这样一个视角来开始你的探索:
- 揭示有趣的时态模式和反模式
- 检测感兴趣的横截面
- 确定是否有同时影响许多信号的重大事件
带状图介绍
什么是带状图,我为什么需要它?
如果只有少量的时间序列,通常可以通过单独或同时绘制标准时间图来可视化它们。

时间序列标准图(图片由作者提供)
然而,可视化超过几十个信号所必需的不动产是令人望而却步的。此外,前面的图只显示了 6 个月的数据:可视化更多的数据可能会导致重要的趋势被挤出。
将这样的信号转换成带状图会使信息更加紧凑:

之前信号的相应带状图(图片由作者提供)
在这张图上,我使用了三种颜色:绿色表示信号的低值,橙色表示信号的中值,红色表示信号的高值。这个简单的条形带非常便于显示何时出现低值或高值。
从时间序列中剥离这么多信息有什么意义?
主要原因是,我们不想只关注其中的一个,而是可能关注其中的多个,我们可能还想查看超过 6 个月的数据。让我们来看看这个过程,并以获得的全局可视化来结束…
给我看看!
构建信号的带状图非常简单,但需要您选择一些参数。第一步是离散化你的信号。在下面的代码片段中,我从../data文件夹中加载 70 个信号,并离散化第 51 个:
然后,我可以绘制我的信号,并具体化通过离散化过程获得的仓:

箱可视化(作者图片)
您可以选择想要将信号离散化到的仓的数量(上面代码中的n_bins=3)。这个数字是任意的,取决于您想要在高层次上可视化的内容。我发现三个层次对于可视化我的数据中的关键变化是有用的。如果您的数据发展缓慢,添加更多级别不会损害可视化。如果您有一些尖峰或非常嘈杂的数据,低数量的级别对于保持图有用是很重要的。在上面的例子中,我还使用了分位数:这意味着我的信号分成三类,数据点的数量大致相同。然后,我会将这些仓分配给信号的每个值:

将条柱映射到原始时间序列值(图片由作者提供)
然后,我可以使用这些相同的颜色,将这个信号转换成一个简洁的带状图:

之前信号的相应带状图(图片由作者提供)
在这里,我们在独立于其他信号来观察给定信号的同时进行离散化。不过,量化可能会在全局范围内发生:如果您在许多不同资产的相同位置获取温度值,则在全局考虑来自该特定标签的所有信号的同时,对所有温度值进行离散化是有意义的。绘制相邻的带状图将允许您看到不同的图案跃入您的肉眼,将您的调查导向您工厂中的特定设备。
现在,让我们将时间范围从 6 个月扩大到 3 年,并将所有 70 个信号绘制在一个屏幕上:

三年来 70 个信号的概述(图片由作者提供)
该图像在大约 3 秒内生成,信号的采样率为 1 小时。由于这种可视化,您可以轻松确定要调查的主要事件(2019 年 2 月和 4 月的大红色带)和不时发生的较短事件(例如 2018 年 3 月 22 日)。这是一个非常好的开始!
结论和未来工作
在本文中,我构建了以前的框架,并更新了我的[tsia](https://github.com/michaelhoarau/tsia)包来构建多元时间序列的带状图。在未来,我将通过添加一些概要特性来扩展这个特性,如 R 包[mvtsplot](https://github.com/rdpeng/mvtsplot)(由著名的 Roger Peng 构建!).然后,我将研究使其更有用的方法,通过提供对时间序列分组的自动洞察或基于多变量演变精确定位感兴趣的时间范围(例如,我们能否在上面的最终矩阵图上自动突出显示所有重要的红色范围?).
如果您喜欢这篇文章,我希望您也会对下面的文章感兴趣:
参考
- 彭,R (2008): 一种多变量时间序列数据可视化方法 -统计软件学报。http://doi.org/10.18637/jss.v025.c01
使用子图来表达更多的 gnn
改进 GNNs
消息传递图神经网络的表达能力固有地受到限制,因为它们等价于 Weisfeiler-Lehman 图同构测试。几个并行的近期工作表明,这种限制可以通过对通过从输入图中移除节点或边而获得的子图集合应用 GNN 来克服。在本帖中,我们回顾了这些不同方法的共同主题和细微差别。

本文由莱昂纳多·科塔、法布里齐奥·弗拉斯卡、哈盖·马龙、和·赵合著。另见Weisfeiler-Lehman 试验简介 和 结构编码 。
M 消息传递图神经网络(mpnn)[1]由几个层组成,执行来自相邻节点的信息的逐节点聚合。这种架构的吸引人的特征是它们的局部性(即,每个计算只需要一个节点的直接邻居)和边数的线性复杂度[2]。然而,缺点是它们的表达能力有限:在[3–4]中显示,MPNNs 的表达能力最多与 Weisfeiler-Lehman (WL)图同构测试 [5]一样,这是一种试图通过迭代颜色细化来确定两个图是否结构等价(“同构”)的经典方法[6]。
Weisfeiler-Lehman 测试是一个必要条件,但不是充分条件:事实上,一些同构的图可能是不可区分的。下图显示了一个这样的例子:

weis feiler-Lehman 测试是一个迭代的局部颜色优化过程,试图确定两个图是否同构。它输出颜色直方图,提供同构的必要但非充分条件:如果颜色直方图不同,则图形不是同构的,但测试可能无法区分非同构的图形,如这里显示的图形,产生相同的直方图。
在图论中,最初由 Boris Weisfeiler 和 Andrei Lehman 在 20 世纪 60 年代提出的图同构测试在 20 世纪 70 年代后期被扩展到一个层次结构,该层次结构具有越来越强大的更高维度的 k -WL 测试,这些测试对节点的 k 元组进行操作。这篇文章的作者克里斯多夫·莫利斯和哈盖·马龙——以及他们的合著者——展示了相当于 k -WL 测试[4,9–10]的高维 GNN 架构的不同设计;然而,这种模型失去了标准消息传递 GNN [11]的局部性和线性复杂性的优势。
增加 GNNs 表达能力的另一种方法是使用位置编码,它“预先着色”图的不同节点。位置编码是变压器[12]的一个关键架构特征,可以被视为一种“对称破坏”机制,允许变压器表达排列敏感函数。这是一个必要的属性,例如在语言建模中,这种架构特别成功。然而,对于图形结构的数据来说,这种特性不再是所希望的,而且对排列敏感的位置编码通常缺乏通用性[13]。
L 为了寻找一种排列不变的对称破缺机制,乔治·布瑞萨斯和法布里齐奥·弗拉斯卡(迈克尔的博士生)引入了图子结构网络 (GSN),该网络基于小的预定义子结构(三角形、集团、循环等)的局部计数【14】。适当的结构选择使得这种“结构编码”比基本的魏斯费勒-雷曼测试或更高阶的 k -WL 测试更具表达性[15]。

采用具有三角形计数的结构编码的 gsn 允许在上述示例中的图形之间消除歧义(属于一个三角形的节点用橙色标出;三角形以绿色显示)。
GSN 模型的优点在于,它本质上是标准的 MPNN,增加了额外的节点或边特征(相应结构的计数),因此受益于消息传递方案的局部性和线性复杂性。检测局部子图的较高复杂度是作为预处理阶段引起的,这在一些应用中通常是可以容忍的。虽然在一个有 n 个节点的图中寻找 k 个节点的子结构的最坏情况的代价是𝒪(nᵏ,但是实际的运行时间要乐观得多。
结构编码强调了本文讨论的一类方法背后的一个简单思想,即使用输入图的子图可以增加图形神经网络的表达能力。再次考虑我们反复出现的一对 WL 等价的非同构图的例子:可以扰动这些图(甚至最小程度地,例如,通过删除一条边,如下图所示),使它们通过 WL 测试可区分。

通过 Weisfeiler-Lehman 测试去除边缘(红色虚线),上例中的两幅图变得可以区分。这一观察为几个处理子图集合的表达性 GNN 架构提供了理论基础。
最近几个并行的工作使用这种方法来设计高效的表达图神经网络结构。这些模型,我们统称为“子图 gnn ”,主要区别在于它们的基本原理、产生子图的方式以及聚集来自不同子图的输出的方法。我们在本帖中回顾了其中的一些作品。
ETH Zurich 分布式计算小组引入的Dropout GNNs【16】通过将节点 dropout 应用于图(独立于其他节点以概率 p 移除一个节点)然后运行标准的多层 GNN,从字面上理解这一思想。重复该过程多次,得到不同的子图嵌入;然后,它们以排列不变的方式聚合。这样,每个节点“看到”一个略有不同的邻居,往往解决了导致 Weisfeiler-Lehman 测试失败的歧义。这也可以解释为什么像 DropEdge [17]这样的图的丢失型正则化技术往往工作得很好。

图中节点丢失的例子(丢失的节点用红色标记,不参与消息传递)。与 WL 测试相比,每个节点在多次运行中看到其邻域的一组轻微扰动版本的事实导致了丢失 GNNs 的更大表达能力。
重构 GNNs【18】是本帖作者莱昂纳多·科塔和克里斯多夫·莫利斯与布鲁诺·里贝罗的新作,提出了一种基于图重构猜想的新架构。重构猜想是图论中的一组结果,可追溯到 20 世纪 40 年代 Paul Joseph Kelly 和stanis aw Ulam【19】的工作,假设从所有删除节点的子图的集合中恢复一个图的可能性【20】。虽然对于某些图族可以显示可重构性,但是一般结果仅通过对大小为 n≤ 11 个节点的图进行穷举搜索来验证[21],并且对于更大的 n 是否成立目前是一个公开的问题。

一个图及其去节点子图的集合。子图 1,3,4,6 和 2,5 是同构的。
在十年后发表的后续工作中,Kelly 考虑了从 k 大小的子图[22] 重建的更一般的设置。不幸的是,对于固定大小 k 的重构,我们没有太多可说的。相反,关于 k 的典型结果——重构试图将最小必要子图大小 k 表示为其节点数量 n 的函数;k-对于某些图族,如树和多部图,显示了可重构性[23]。**
k—【18】中提出的重构 GNNs 将标准 MPNN 应用于大小为 k 的每个导出子图,并对所得嵌入求和(这是在深度集【24】中使用的置换不变函数)。由于可能有太多这样的子图[25],必须使用采样(这发生在 Dropout GNNs 的相同程序中)。值得注意的是,1990 年 Béla Bollobás 证明了几乎每个图都可以仅用三个(n-1)大小的子图来重构。这个结果提供了为什么随机子采样子图似乎不会损害 k- 重建 GNNs 经验结果【27】的见解。**
用k=n-1 重构的k-GNN 可以区分标准 GNNs 不能区分的同构图族,如下图所示的循环跳链图:****

循环跳过链接图是 WL 测试(以及标准消息传递 gnn)无法区分的非同构正则图的一个例子,但是可以通过 k-重构 gnn 来区分。
同时,对于足够小的k’s[28],存在可由 GNNs 区分而不能由k-重构 GNNs 区分的图。相反,一个(n--2)-重构 GNN 可以区分强正则图,这是标准 MPNN(n-重构 GNNs)和(n--1)-重构 GNN 都不能区分的。这是一个有点令人失望的结论:虽然通过类比 k -WL 测试,人们可能会期望在 GNNs、(n-1)-重建 GNNs、(n-2)-重建 GNNs 等之间存在表达能力的等级,但并不存在这样的等级。****
【29】中提出的从星星到子图是赵最近与人合作的另一个利用有根子图提高 GNNs 表达能力的作品。看待 WL 测试的一种方式是通过星形模式进行局部颜色细化(根据邻居的颜色和自己的颜色重新标记一个节点,这是有根星形的独特特征)。将星形模式扩展到一般的有根子图,如 egonets [30],产生了 Weisfeiler-Lehman 算法的一种新的基于子图的变体,称为“子图-1-WL”。由于有根子图的数量与图中节点的数量相同,对于常数 k ,该方法与图的大小成线性比例。******

以红色节点为根的星形(左)和 1-ego 子图(右)。
子图-1-WL 严格来说比 WL 更强大,但不亚于 3-WL(在某种意义上,存在 3-WL 不能区分但子图-1-WL 可以区分的非同构图)。当使用过小的有根子图(k-尺寸 k ≤4)时,子图-1-WL 无法区分任意 m≥3 的一族 CFI( m )图[31],这也是 m -WL 所知的无法区分。**

两个非同构强正则图的例子,不能用 3-WL 来区分,但可以用子图-1-WL 来区分(两个允许进行这种区分的子图被突出显示)。
子图 1-WL 算法的神经版本用标准 MPNN 编码每个根子图;然后,聚合得到的节点式表示。与 Dropout GNNs [16]和 Reconstruction GNNs [18]相比,一个重要的区别是使用了交叉子图聚合,它通过利用子图对齐将来自多个子图的相同节点的节点表示汇集在一起(当考虑一组子图的对称组的更精确定义时,我们将看到这种汇集机制可以从第一原理中导出)。此外,通过在训练期间随机丢弃一些子图,同时在测试期间保留所有子图,使用子图丢弃方法来在不牺牲性能的情况下降低可伸缩性开销。**
等变子图聚合网络(ESAN)【32】是一种与子图多重集的对称结构等变的架构,由法布里齐奥·弗拉斯卡、迈克尔·布朗斯坦、哈盖·马龙及其合著者在最近的一篇论文中提出。子图的多重集是高度结构化的对象,其对称性来源于每个组成子图的结构以及作为整体的多重集。等变原则要求改变多重集 m 子图的顺序(置换群σₘ)和子图中节点的顺序(置换群σₙ)产生一个等价的表示,或者换句话说,架构是 w.r.t .等变的乘积群σ=σₘ×σₙ。与[16,18]不同,这种对称群公式考虑了子图对齐的事实,使得相同的置换作用于所有子图的节点(与单独作用于每个子图的单个节点置换相反)。****

多重子图集的对称群是子图置换(绿色)和节点顺序(橙色)的乘积。
ESAN 是“对称对象深层集合”(DSS)架构的一个实例[33]并且使用两个基本图编码器:第一个编码器是一个独立处理每个子图的暹罗网络;第二个通过处理子图的聚集充当信息共享模块。在几个这样的层之后,通过集合学习模块聚集所获得的子图表示,以形成在下游任务中使用的原始图的不变表示。**

ESAN 等变层由一个连体网络(编码器 1,橙色)和一个信息共享模块(编码器 2,黄色)构成。
丢弃 GNNs [16]和重建 GNNs [18]可以被视为仅使用第一编码器的 ESAN 的特殊情况[34]。此外,第二个编码器是[29]中使用的交叉图聚合的一般化,它的添加有利于图分类任务的性能[35]。最后,ESAN 考虑了除了简单的节点删除或 egonets 之外的其他子图选择策略(函数𝜋( G )将输入图 G 映射到其子图集合的可能性。**
这篇论文的很大一部分致力于分析 ESAN 的表达能力——这产生了一种新的 Weisfeiler-Lehman 测试变体,称为“DSS-WL”——具有不同的架构和子图选择政策。结论之一是 ESAN 体系结构可以仅使用 WL 图编码器(标准 MPNN)来区分 3-WL 等价图,并且它可以增强更强体系结构的表达能力。
以前有人认为,为了设计更强大的图形神经网络,超越 Weisfeiler-Lehman 测试的表达能力,人们必须放弃消息传递范式。最近出现的基于子图的 GNN 架构显示了这个问题的廉价解决方案,同时仍然保持在消息传递的职权范围内[36]。
除了表达能力的增加之外,在一些图 ML 任务(例如图分类)中,子图 gnn 与标准 gnn 相比,似乎具有更低的方差风险估计量。这些任务本质上对于(一些)节点删除是不变的[37],本文中回顾的基于子图的技术可以理解为一种数据扩充的形式。
[1] J. Gilmer 等人,量子化学的神经信息传递 (2017)。继续。ICML。
[2]gnns 上下文中的“线性复杂度”通常是指在个节点的数量上缩放为𝒪( n 的算法。虽然最坏情况下的边数可以是𝒪( n ,但是通常假设图是稀疏的,在这种情况下,边数是𝒪( n 。**
[3] K. Xu 等,图神经网络到底有多强大? (2019) ICLR。
[4] C. Morris 等人, Weisfeiler 和 Leman go neural:高阶图神经网络 (2019) AAAI。
[5] B. Weisfeiler,A. Lehman,将一个图简化为标准形式以及其中出现的代数(1968)Nauchno-Technicheskaya informatisia 2(9):12–16。
[6]“颜色”在本文中被理解为节点式离散标签。
[7]其中每个顶点具有相同数量的邻居(即,相等的度数)的正则图是标准 WL 检验不可区分的族的一个例子。要使一个更高维度的 k -WL 失败,需要更多的正则性:这种情况下的一个典型例子是一族k-等正则图。**
[8]拉斯洛·巴拜把 k 的发明归功于 Neil 与鲁道夫·马顿和独立进行的尼尔·伊莫曼和埃里克·兰德的自我测试(后者曾被训练成数学家,但更广为人知的是他在基因组学和生物学方面的工作,他也是拜登政府的现任科学顾问)。参见 L. Babai,拟多项式时间中的图同构 (2015),arXiv:1512.03547 第 27 页。
[9] H. Maron 等人,不变和等变图网络 (2019) ICLR。
[10] H. Maron 等人,可证明强大的图形神经网络 (2019) NeurIPS。参见作者的一篇博文。
[11]Morris 在[4]中介绍的 k -GNN 架构招致𝒪( nᵏ 存储器复杂度。在后续工作中,C. Morris 等人、 Weisfeiler 和 Leman go sparse:Towards scalable higher-order(2020)neur IPS 2020 还设计了一个基于局部邻域聚合的局部版本的k-GNNs[32],考虑到了基础图的稀疏性,并表明该局部版本至少具有与普通的 k -WL 相同的功效。Maron 在[9]中引入的不变图网络(IGN)基于 k 阶张量,并且是kWL 等价的。IGNs 是从 k -WL 的不同变体中派生出来的,与 k -GNNs 相比,在复杂性方面更有优势。特别是,3-WL 等效 IGN“仅”具有二次复杂度。****
[12] A. Vaswani 等人,注意力是你所需要的全部 (2017) NIPS。
[13]关于 GNNs 中位置编码的更详细分析,参见例如 A. Loukas,什么图形神经网络不能学习:深度对宽度(2020)ICLR;ICLR'20,R. Sato 等,随机特征强化图神经网络(2021)SDM;r .阿鲍德等人,具有随机节点初始化的图神经网络的惊人能力 (2020),IJCAI。
[14] G. Bouritsas 等,通过子图同构计数提高图神经网络表达能力(2020)arXiv:2006.09252;参见随附的帖子。
[15]图子结构网络[14]的表现性类不遵循标准的 k -WL 层次结构。论文中的分析表明,GSN 在 WL 时至少具有同样的表达能力(即,能区分 WL 能区分的所有图),此外,它还能区分一些 WL 不能区分的非同构图。对于更高维度的 k -WL 测试,给出了不能被 k -WL 区分但可以被具有右子结构的 GSN 区分的图的例子,然而,GSN 可以区分的图的确切类别是未知的。如果重构猜想成立,那么 GSN 是最大表达的。
[16] P. A. Papp 等, DropGNN:随机丢失增加了图神经网络的表达能力 (2021) arXiv:2111.06283。
[17] Y. Rong 等人, DropEdge:走向节点分类上的深度图卷积网络 (2020)是一个典型的图的丢弃方法,其动机主要是希望设计更深的 GNNs(另见关于深度 GNNs 的帖子)。一般来说,随机子图选择提出了一个潜在的问题,因为网络的输出不是排列不变的。然而,在报告的实验结果中,这似乎不会导致任何显著的性能下降。
[18] L. Cotta 等人,强大图形表示的重构 (2021) NeurIPS。
[19]重构猜想首先出现在 P. J. Kelly 的博士论文《论等距变换》( 1942 年)中,该论文是他在“热核弹之父”stanisaw Ulam 的指导下在威斯康星大学麦迪逊分校完成的。参见 J. A. Bondy,《组合学 166:221–252 中的图形重构者手册(1991)调查》,了解图形重构结果的全面综述。
[20]在重构猜想的上下文中,各个子图被称为“卡片”(每个卡片的大小为n-**1),它们的集合是一副“卡片”。因为卡片组中的子图被交给同构类,所以卡片组是一个多重集(其中每个元素可以出现不止一次)。如果一个图 H 和 G 有相同的牌组(表示为 H ~ G ),则称它们是G的重构。 G 是可重构的,如果它的每一个重构都同构于 G 自身(即 H ~ G 蕴涵 H ≃ G )。重构猜想陈述了如果 G 和 H 是两个至少有三个顶点的有限无向简单图并且 H 是 G 的重构,那么 H ≃ G 。有趣的是,有向图、超图和无限图是不可重构的。****
[21] B. D. McKay,小图是可重构的 (1997)澳大利亚组合学杂志 15:123–126。
[22] P. J .凯利,树的一个同余定理 (1957)太平洋数学杂志。7:961–968 将重构结果扩展到大小为 k 的更小的子图。因此,原始重建结果是一个特例,其中k=n-1。****
[23]v . n dl,从子图的图重构 (2001)离散数学 235(1–3):335–341。
[24] M. Zaheer 等人,深套 (2017),NIPS。
【25】恰恰有 n !/( k !(nT62k)!)在大小为 n 的图中,大小为 k 的子图。**
[26]b . bollo bas,几乎每个图都有重构数 3(1990)《图论杂志》14(1):1–4。
[27]参见[18]的补充。
[28]图中所示为[18]中的命题 2,针对的是 k ≤⌈ n /2⌉.
[29] L .赵等,从星到子图:用局部结构意识提升任何一个 GNN(2021)arXiv:2110.03753。
[30]一个节点的 k -egonet 是其具有诱导连通性的 k -hop 邻域。
[31] CFI 图以 J.-Y. Cai、M. Fürer 和 N. Immerman、的首字母命名,这是图识别变量数量的一个最佳下界(1992)Combinatorica 12(4):389–410。
[32] B. Bevilacqua 等人,等变子图聚合网络 (2021) arXiv:2110.02910。查看主要作者论文演示的视频。
[33]“复合”对象的等变架构,如具有自身内部对称性的对象集(对称对象的深度集,或 DSS),之前由 H. Maron 等人在学习对称元素集时考虑过(2020)ICML。
[34]辍学[16]和重建[18]中使用的暹罗网络可以从一个更大的对称群中导出,该对称群使用了一种不同的群积概念,称为圈积,参见 R. Wang 等人,分层结构的等变映射 (2020) arXiv:2006.03627。
[35]在[32]中示出了信息共享模块的使用,以在 91%的情况下改善基本编码器的性能,相比之下,当仅使用暹罗网络时,改善基本编码器的性能为 75%。
[36]到目前为止,我们的综述并不详尽,其他几个同时进行的工作已经探索了类似的想法,参见例如 M. Zhang 和 P. Li,嵌套图神经网络(2021)arXiv:2110 . 13197;D. Sandfelder 等人,E go-GNNs:利用图神经网络中的自我结构(2021)arXiv:2107.10957;以及 E. H .蒂埃德等人 Autobahn:基于自同构的图形神经网络 (2021) NeurIPS。
[37]这在[18]中显示为k重建 GNNs,这是 ESAN 的一个特例。**
我们非常感谢克里斯蒂安·博德纳尔和乔治·布瑞萨斯对这篇文章的校对和提供的深刻反馈。有关图形神经网络的其他阅读资料,请参见迈克尔在 上的博客,了解数据科学 , 订阅 他的帖子和 YouTube 频道 ,获得 中等会员资格并关注 迈克尔**
使用 5 阶段数据成熟度模型评估组织影响
无论您处于流程的哪个阶段,都可以构建和灵活运用您的业务战略
从简单的商业报告到商业智能是一个巨大的飞跃,但是那些让超越传统商业智能的公司是那些扰乱市场的公司。他们投资于源自其业务运营的现代数据团队,以优化整个组织的洞察力,推动增长和投资回报。所以,如果你落后了,你能做什么?你怎么能在这个水平上竞争?从零开始对机器学习进行重大投资可能对你的公司是正确的,也可能不是正确的——然而,认真审视成熟度曲线是前进的最佳方式。
在这项工作中,有必要了解现代数据团队的定义。过去的数据团队可能只有三个传统参与者,即负责数据仓库功能和容量的数据库管理员、专注于数据建模的数据分析师以及负责构建仪表板和相关自助报告功能的 BI 架构师,而如今的团队成员则更多。通常是一个与企业合作但有自己专业技能的团队,这个团队做从概述业务规则到建模数据再到构建单一事实来源的所有事情。最重要的是,这些人提供专门的分析,这种评估是传统 BI 无法提供的。问一个商业问题,比如“我的收入模式应该是什么?”该小组将测试其假设,以展现出人意料的见解。他们可能会应用机器学习或一些统计和预测技术来确定您的组织没有理想数据的模式或差距。
关键的一点是,与仅提供数据模型的传统 BI 团队不同,今天的数据团队作为企业的核心职能运作,并推荐基于数据的战略。该小组在会议桌上占有一席之地,根据数据提供关于公司应该做什么的明智意见。
并非每家公司都拥有或需要最先进的数据团队
五阶段模型展示了数据团队成熟度和组织影响。许多组织都处于这种模式的早期或晚期阶段,这取决于数据对其业务和业务模式的重要性,以及可以在执行日益高级的分析所需的资源上投入多少。初始阶段包括简单的业务报告;其次是商业智能;第三是特别分析和意想不到的见解。第四阶段整合混合集中式数据团队,第五阶段用机器学习增强分析。

图片来源:Sisense
这些选项的影响与公司在数据和数据分析方面的投资直接相关,更高的性能需要更多的资源。考虑一家专注于业务报告的公司,它被大多数组织视为数据洞察的最低级别。最终,每个小组都使用孤立的数据进行操作,即使数据与公司内的其他团队不一致,也能洞察关键因素。问题伴随着不一致性出现,例如当来自一个域的数据与另一个域的数据冲突时。客服说流失率在上升,而销售团队说流失率在下降。公司各部门、团队或应用程序之间的数据要么不同,要么有不同的解释。可以跟踪部门绩效,但是不同且独立的事实来源代表了组织的真实挣扎。该公司需要进入第二阶段,这一阶段的重点是通过将数据集中到一个地方来实现商业智能,该数据允许以相同的方式从相同的信息中创建一致的报告。
几乎每个组织都有某种形式的 BI,它可以被描述为一个数据模型和一个可视化工具,基于跨多个来源连接的数据支持业务报告。在这种情况下,可以对数据提出问题,跨越各种数据源,以获得反映在包含整个业务的报告中的业务真相。哪些问题会导致负面的客户体验?营销计划和客户保持率之间有什么关系?这些都是从统一的数据分析方法中产生的有价值的业务见解。在这个成熟度级别,数据分析师是等式的一部分,对于在数据之间建立关联至关重要。这是一个远远超出后视洞察力的分析阶段,实现了有助于将数据应用于未来业务运营和战略的预测性报告。
第三个阶段,即席分析和见解,是关于数据专业人员检查组织数据,询问和回答意外问题,并积极寻求优化业务。“我们能否在销售线索出现时对其进行评分,以确定哪些是最重要的?这些应该如何进行优先排序?”这些都是可以随时提问和回答的问题,有助于提高销售和客户健康的评分策略。扩大这项工作的规模是公司进入第四阶段的关键,这一阶段的特点是混合集中式数据团队。
最终,通过单个数据团队路由每个数据请求并不是一个针对规模优化的策略。当集团支持营销、销售、产品、客户成功、财务和其他不同的公司部门时,可能会出现瓶颈。第四阶段引入了一个混合集中式模型,通过建立一个定义方法和实践的核心数据团队来满足需求,并由一个独立的营销、产品、销售等分析团队提供支持。由于每个团队都与集中的团队合作,整个企业可以作为一个数据驱动的实体进行扩展。
第五阶段利用增强分析和机器学习。数据科学家发挥着重要作用,他们发明或利用机器学习、统计技术和预测能力来预测商业行为。他们的新方法与集中式数据团队共享,应用于业务问题,并用于优化大规模运营。“我们的预测是在正轨上还是在变化?业务本身在变化吗?当顾客进入我们的销售漏斗时,我们如何预测他们的行为?什么样的新服务能让我们成为同类最佳?”这些更深入、更高级的问题依靠数据科学家和预测技术来提供价值。需要注意的是,在数据成熟度曲线的这一点上,实现价值的时间可能会更长。更先进的技术需要更大的投资和一些耐心;然而,回报通常比成熟早期要丰厚得多。
大多数组织都处于四个成熟度级别之一
问几个关键问题来决定你公司的定位。您是否使用不同的报告平台来报告不同的业务职能?您是否孤立于不同部门的记录系统中,或者您是否拥有跨各种业务工具的集成报告?第一阶段和第二阶段的区别在于,您的组织数据是否在单个数据仓库中共享,以及是否针对跨多个来源的业务查询进行了结构化。如果公司有集中的报告,并且 ETL 过程提供了良好的数据模型,那么您可能已经达到了第二个成熟阶段。
另一个需要考察的领域是数据广度。您的业务数据中有百分之多少被整合到一个真实的来源中?事实上,对于任何组织来说,将全部 100%的数据放在真实的中心来源中是不寻常的。这甚至可能是一个无法实现的目标,但是你朝着这个目标前进了多少呢?确定有多少业务数据实际上位于中央存储库中,为您的成熟度时间线提供了一个关键的标记。

您的数据分析师可以将模型数据与来自多个来源的原始数据混合在一起吗?这是一个微妙但值得思考的问题。理想情况下,您的数据团队可以获取来自新系统的原始数据,并将其与现有的模型数据(如客户、机会或服务数据)相结合,以回答当前数据模型中可能没有预料到的问题。如果将来自多个来源的数据混合到一个仓库中,该过程发生在数据到达仓库之前还是之后?例如,一种更现代的方法是在入库后混合数据。您的数据是否涵盖了客户旅程的所有相关阶段,或者您是否比销售人员更了解潜在客户?关于产品使用的数据很多,但关于它如何影响客户购买偏好的数据却很少?
在确定数据成熟度的过程中,数据治理和访问也至关重要。您公司中担任不同角色的人员对数据的访问权限是否不同?规模较小的创业公司可能没有设立准入门槛,因为参与其中的每个人都是某种意义上的负责人。但是增长可能需要一些更快的行动。随着组织的成熟,剔除个人身份信息(PII)、工资数据或其他他人根本不需要看到的专有事实可能变得至关重要。一个受管理的、单一的真相来源对这些类型的实体也是至关重要的。通常,早期企业有数据游乐场,任何人都可以做任何事情,无意中造成幻灯片被共享但不一定有谨慎计算支持的情况。相比之下,由数据团队控制的治理源只发布批准的数据或明确标记为根据游戏规则开发的数据。
如果业务单位雇佣并嵌入他们自己的分析师来使用中央数据团队提供的工具,混合集中式模型就发挥作用了。当考虑先进的数据能力时,你的公司有模型管理和生产能力吗?您的产品或分析中是否部署和使用了机器学习系统?你的分析工作流程包括机器学习模型吗?这些不一定是内部构建的,通常可以通过现有的模型和行业工具更容易地利用,假设您的分析工作流有能力和培训将它们纳入分析。
使用数据成熟度模型实现数据驱动的成功
你可能会听到首席执行官拍着桌子说,“我们必须以数据为导向。我希望公司里有机器学习。我不太清楚那是什么,但我知道我想要它。”问题是,从基本报告直接进入增强分析意味着您可能会错过一些关键的基础功能,可能会阻止您从更高级的分析中实现预期的影响。缺少 BI 基础和健康的混合团队,组织可能能够开发先进的见解,但发现自己在整个业务中应用它们时有障碍。没有交流框架和基础设施来共享见解。这是一步步经历成熟度模型的最大原因之一。通过建立这些早期阶段,企业创建了开发新见解所必需的基础设施和经验,然后将它们有效而正确地传达给企业。遵循这条曲线的公司更有利于获得长期竞争优势,建立连接组织,让实体将洞察力转化为真正的商业价值。
在 SQL 中使用计数/分组依据/连接组合
计算 SQL 中条目的实例数

来源:图片来自 Pixabay
在使用 SQL 中的表时,经常会希望计算该表中实例的数量。这可以是产品类别、品牌等。
当使用一个表时,这就足够简单了。但是,当跨多个表工作时,这项任务可能会有些麻烦。
这里,我们将看一个例子,说明如何将 COUNT 和 GROUP BY 函数与一个内部连接结合起来,以计算特定零售商的不同产品类型的数量。
服装店示例
让我们假设一个繁忙的服装店有几个表存储在一个数据库中,该数据库包含在任何给定时间存储在其仓库中的所有商品。这些表包含商品名称、价格、品牌、产品类型等信息。业主难以估计仓库中每件产品的具体数量,即仓库中存放了多少条牛仔裤、多少件 t 恤?
考虑以下两个表:
>>> select * from clothes1 limit 1;item | colour | type
--------------------+----------+----------
Grass Green T-Shirt | Green | T-Shirt>>> select * from clothes2 limit 1;item | price | size
-------------------+---------+--------
Sky Blue Jeans | 79.99 | 31
假设以下场景。 clothes1 代表仓库中曾经出现过的所有衣物。 clothes2 代表仓库中任何一次出现的所有衣物。
假设只有 clothes1 表包含关于类型的信息,这意味着第一个表必须与第二个表连接,以便通过类型识别第一个表中出现的每个项目。
因为我们只想计算当前出现的服装项目的类型,而不是那些存储在第二个表中但没有出现的服装项目的类型,所以我们使用内部连接而不是完全连接来实现这个目的。
使用完全连接将返回 clothes1 中的所有条目,即曾经出现在仓库中的条目。因为我们只对计算当前库存感兴趣,所以我们使用了一个内部连接。
询问
以下是查询:
>>> select t1.type, count(*) from clothes1 as t1 inner join clothes2 as t2 on t1.item=t2.item group by t1.type;
从这个查询中,我们获得了一个类似如下的表:
type | count
-----------+-------
T-Shirt | 2496
Jeans | 3133
Sneakers | 2990
Shirts | 3844
Ties | 1789
Trousers | 2500
(6 rows)
结论
在这个简短的教程中,您已经看到了如何在 SQL 中使用 COUNT/GROUP BY/JOIN 组合来聚合多个表中的条目。
虽然 GROUP BY 查询在只处理一个表时可以简单地实现这一点,但在处理多个表时,情况会变得更加复杂。
非常感谢阅读,任何问题或反馈都非常感谢!您还可以在这里找到原始文章以及有用的 SQL 实践的更多示例。
免责声明:本文是在“原样”的基础上编写的,没有担保。它旨在提供数据科学概念的概述,不应被解释为专业建议。本文中的发现和解释是作者的发现和解释,不被本文中提到的任何第三方认可或隶属于任何第三方。
在 Wolfram 语言中使用 Gurobi 优化器
实践教程
使用 Gurobi 增强您的优化问题解决能力

一个已解决的优化问题——在 Unsplash 上由 S & B Vonlanthen 拍摄
当你进行一次大型旅行时,无论是城市旅游还是背包探险,你都面临着这样一个问题:我的行李里应该放些什么?我是应该多带一件毛衣,因为天可能会冷,还是应该带一件雨衣,因为天气预报似乎要下雨。是应该多带袜子还是多带内衣?我应该带笔记本电脑还是平板电脑就可以了?
当你思考这些问题时,你在解决一个背包问题:给定有限的空间和重量,你在给物品赋值并对它们设置约束。例如,你可能想至少带两件 t 恤,但如果有足够的空间,你可能想带第三件或第四件 t 恤。与此同时,你可能更看重多带几双袜子或多一双鞋。
当你合上行李时,你已经解决了你的最优化问题,实际上就是在你的头脑中做了一大堆数学运算,决定带什么和带多少。
世界各地都出现了更复杂的这类和其他类型的优化问题:用最佳数量的包裹装满一辆卡车,为一辆公共汽车找到最佳路线,在最短的时间内运送最多的人,找出最佳的配料组合,以生产出完整的动物饲料,等等。
当你有不同值和约束的多个选项时,你必须解决一个优化问题。
Wolfram 语言提供了一大组解决优化问题的函数:它可以解决符号和数值优化问题,局部和全局问题,线性和非线性问题,凸和非凸优化问题。每种类型的优化问题都有自己的解决算法,而 Wolfram 语言通过精心设计的顶级界面和内置的自动算法选择,可以轻松使用所有算法。
但是快速解决优化问题是一个复杂的算法挑战:在这个领域没有“可证明最快”的算法。每年都会发现许多新的最先进的算法,将性能边界推得越来越远。为了让 Wolfram Language 访问这些前沿解算器,我们开发了一个通用优化框架,允许您利用任意数量的解算器。
为了使数值问题变得更加简单,Wolfram Research 为来自 Gurobi 的最先进的求解器提供了内置支持,该公司专注于提供世界上最快的优化求解器。他们的旗舰产品 Gurobi Optimizer 比其他求解器快得多,可以解决以下主要优化类型(包括实值和混合整数子类型):线性优化、二次优化、二阶锥优化和二次约束优化。
在这篇文章中,我将温和地介绍使用 Gurobi Optimizer 的可能性,以及如何使用 Wolfram 语言在一个非常高的层次上指定优化问题,它具有内置的排版功能、一流的符号预处理功能以及非常棒的可视化特性。
首先,让我们看看 Gurobi 支持的优化类型及其缩写如何映射到 Wolfram 语言函数中:
- (混合整数)线性优化:线性优化
- (混合整数)二次优化:二次优化
- (混合整数)二阶锥优化:SecondOrderConeOptimization
- (混合整数)二次约束优化:凸优化
除了这些 Wolfram 语言函数之外,还有更高级别的函数,如n 最小化和n 最大化,它们自动识别优化类型并选择合适的求解算法。在这篇文章中,我将坚持使用范围更小的 Wolfram 语言函数。

解决优化问题——Claudio Schwarz 在 Unsplash 上拍摄的照片
基础—线性优化
让我们从一个非常简单的线性优化问题开始,其中成本函数和约束都是具有任意数量决策变量的线性函数:

(图片由作者提供)
在这个有两个决策变量(x 和 y)的简单例子中,我们可以用等高线图来可视化可行区域。此处,橙蓝色阴影表示成本函数的值(无关的绘图细节和选项已通过 Wolfram 笔记本界面的图标化功能省略(…)):

(图片由作者提供)
我们现在使用 LinearOptimization 函数来获得答案,并指定我们感兴趣的结果类型(在本例中是原始最小值和原始极小值)。为了使用古罗比优化器,我们将方法选项设置为“古罗比”:

(图片由作者提供)
通过指定变量的类型,可以很容易地将这个问题转化为混合整数问题:

(图片由作者提供)
我们使用等高线图来放大两个解决方案的位置:

(图片由作者提供)

找到每个集装箱的最佳位置——照片由 Unsplash 上的 CHUTTERSNAP 拍摄
对速度的需求…
对于简单的优化问题,内置解算器的性能与古罗比优化器差不多:你得到相同的结果,两个解算器眨眼之间就返回答案。但是大多数现实世界的优化问题都有大量的变量和约束。为了显示内置求解器(CLP)和 Gurobi 优化之间的性能差异,我们可以创建一个带有 SparseArray 的示例。下面的 SolverTiming 函数接受一个大小参数(“n”)和一个求解方法(“Gurobi”或“CLP”):

(图片由作者提供)
现在,我们可以使用 SolverTiming 函数来创建一个数据集表,以增加每个求解器的问题大小及其计时:

我们可以在列表图中比较性能,显示 Gurobi 比 CLP 求解器快一个数量级以上:

请注意,我在这里使用了一个非常简单的线性优化示例。 Gurobi 优化器在许多领域表现出色,尤其是在混合整数问题上。有关其性能的更多信息,请访问 Gurobi 的基准测试网页。
正在导入优化问题…
除了在一个 Wolfram 语言会话中创建优化问题之外,你还可以导入以 MPS 格式存储的优化问题。例如,这导入了一个标准的线性优化问题,然后使用 Gurobi 优化器来解决它:


以最佳方式管理有限的资源——图片由亚采克·迪拉格在 Unsplash 上拍摄
二次优化
现在让我们来看看几个二次优化问题,你可以用古罗比优化器来解决。二次优化函数可以解决实数和混合整数问题。
Wolfram 语言的一个很好的特性是你可以使用通用的紧凑矩阵和向量符号来描述你的问题。这里,我们随机生成问题参数,并使用向量不等式字符表示:

并使用 Gurobi 优化器获得解决方案:

或者我们可以创建一个混合整数问题。这个例子有 3 个实数决策变量和 5 个整数决策变量:

使用高级 Wolfram 语言函数…
Wolfram Language 的优化框架的另一个很好的特性是能够将更高级别的符号问题公式翻译成一种 Gurobi 优化器可以处理的形式。在这个例子中可以看到这样一个例子,我们使用 Total […]简洁地将向量 x 和 Norm […]的元素之和表示为元素平方和的平方根。

有许多更高级的 Wolfram 语言函数可以用于向量和矩阵,包括所有基本的算术函数和距离函数。
当原语表示适当的约束时,您还可以在像SecondOrderConeOptimization这样的函数中使用像 Disk 这样的基于区域的原语。这里一个圆盘代表一个二阶锥约束 x +y < =1,因此它被转换成一种 Gurobi 优化器可以处理的形式:

这相当于编写以下语句:

其他基于区域的图元包括多边形和多面体。
两全其美…
LinearOptimization 、 QuadraticOptimization 、SecondOrderConeOptimization和 ConvexOptimization 的文档页面包含数百个有趣的应用示例,让您可以非常轻松地开始使用 Wolfram 语言和 Gurobi Optimizer :从库存控制问题、制造问题和运输问题,到解决数独、旅行推销员问题和投资组合优化等数学难题。
对于高技术人员,尤其是那些精通 Wolfram 的人,我们最近也开源了我们的实现,展示了从 Wolfram 语言到 Gurobi 优化器的连接是如何“在幕后”工作的。它被命名为 GurobiLink,位于 Wolfram Research Github 页面。
使用 Wolfram 语言中的 Gurobi Optimizer 为您带来两全其美:领先的优化问题求解器性能与世界上最好的科学和商业应用计算语言。哦,它还能帮你算出下次旅行要带多少衬衫!😉
要了解更多关于古罗比以及如何获得古罗比优化器的信息,请访问:https://www.gurobi.com/
要了解更多关于 Wolfram 研究和 Wolfram 语言,请访问:https://www.wolfram.com/
在机器学习之前使用缺失数据 Python 库识别和可视化缺失数据
使用岩石物理测井测量的例子

蒂姆·莫斯霍尔德在 Unsplash上的照片
数据探索和预处理是任何数据科学或机器学习工作流中的重要步骤。在处理教程或训练数据集时,可能会出现这样的情况,即它们已被设计为易于使用,并允许所讨论的算法成功运行。然而,在现实世界中,数据是杂乱的!它可能有错误的值、不正确的标签,并且它的某些部分可能会丢失。
缺失数据可能是处理真实数据集时最常见的问题之一。数据丢失的原因有很多,包括传感器故障、数据过时、数据管理不当,甚至是人为错误。缺失数据可能是单个值、一个要素中的多个值或整个要素缺失。
重要的是,在进一步的数据分析或机器学习之前,识别并适当处理缺失的数据。许多机器学习算法无法处理缺失数据,需要删除或用新值替换(估算)存在单个缺失值的整行。
根据数据源的不同,缺失值可能以不同的方式表示。最常见的是 NaN(不是数字),但是,其他变体可以包括“NA”、“None”、“999”、“0”、“0”、“-”。如果丢失的数据在数据帧中由 NaN 以外的东西表示,那么应该使用如下所示的np.NaN将其转换为 NaN。
df.replace('', np.NaN)
本文附有以下视频:
失踪的图书馆
Missingno 是一个优秀且简单易用的 Python 库,它提供了一系列可视化功能来理解 pandas 数据帧中缺失数据的存在和分布。这可以是柱状图、矩阵图、热图或树状图的形式。图书馆的原始出版物可以在这里找到。
从这些图中,我们可以确定缺失值出现的位置、缺失的程度以及任何缺失值是否相互关联。通常,丢失的值可能被视为没有提供任何信息,但如果仔细分析,可能有一个潜在的故事。
可以使用 pip 命令安装缺少的库:
pip install missingno
数据集
对于本教程,我们将使用由 Xeek 和 FORCE 2020 举办的机器学习竞赛的公开数据集的子集。竞赛的目的是从现有的标记数据中预测岩性。该数据集由挪威海的 118 口井组成。
该数据包含由测井工具获得的一系列电测量值。测量结果用于表征地下地质和识别合适的油气储层。
本文的数据和笔记本可以在我位于 https://github.com/andymcdgeo/missingno_tutorial的 GitHub 仓库中找到
导入库和加载数据
该过程的第一步是导入库。在本文中,我们将使用 pandas 加载和存储我们的数据,使用 missingno 可视化数据完整性。
import pandas as pd
import missingno as msnodf = pd.read_csv('xeek_train_subset.csv')
熊猫快速分析
在我们使用 missingno 库之前, pandas 库中有几个特性可以让我们初步了解丢失了多少数据。
第一种是使用.describe()方法。这将返回一个表,其中包含有关数据帧的汇总统计信息,如平均值、最大值和最小值。在表格的顶部是一个名为计数的行。在下面的示例中,我们可以看到数据帧中的每个特征都有不同的计数。这提供了并非所有值都存在的初始指示。

我们可以更进一步,使用.info()方法。这将返回数据帧的摘要以及非空值的计数。

从上面的例子中我们可以看到,我们对数据的状态和数据丢失的程度有了一个更简洁的总结。
我们可以使用的另一个快速方法是:
df.isna().sum()
这将返回数据帧中包含多少缺失值的摘要。isna()部分检测数据帧中缺失的值,并为数据帧中的每个元素返回一个布尔值。sum()部分对真值的数量进行求和。
该行返回以下信息。

从这个总结中,我们可以看到许多列,即 WELL、DEPTH_MD、GROUP、GR 和 LITHOFACIES 没有空值。所有其他的都有大量不同程度的缺失值。
使用缺失号识别缺失数据
在 missingno 库中,有四种类型的图形用于显示数据完整性:条形图、矩阵图、热图和树状图。每种方法在识别缺失数据方面都有自己的优势。
让我们依次来看一下其中的每一项。
条形图
条形图提供了一个简单的绘图,其中每个条形代表数据帧中的一列。条形的高度表示该列的完整程度,即有多少非空值。它可以通过调用以下命令来生成:
msno.bar(df)

在图的左侧,y 轴刻度范围从 0.0 到 1.0,其中 1.0 表示 100%的数据完整性。如果柱线小于此值,则表明该列中缺少值。
在图的右侧,标度以指数值度量。右上角代表数据帧内的最大行数。
在图的顶部,有一系列数字表示该列中非空值的总数。
在此示例中,我们可以看到许多列(DTS、DCAL 和 RSHA)有大量缺失值。其他列(如 WELL、DEPTH_MD 和 GR)是完整的,并且具有最大数量的值。
矩阵图
如果您正在处理与深度相关的数据或时间序列数据,矩阵图是一个很好的工具。它为每一列提供颜色填充。当数据存在时,图以灰色(或您选择的颜色)显示,当数据不存在时,图以白色显示。
可通过调用以下命令生成矩阵图:
msno.matrix(df)

缺少显示所有 dataframe 列的数据稀疏性的矩阵图。
如结果图所示,DTS、DCAL 和 RSHA 列显示了大部分缺失数据。这已在条形图中指出,但额外的好处是您可以查看丢失的数据在数据帧中的分布情况。

missingno 迷你图的特写视图。图片由作者提供。
图的右侧是一个迷你图,其范围从左侧的 0 到右侧数据框架中的总列数。上面可以看到特写。当一行的每一列都有值时,该行将位于最右边的位置。随着该行中缺失值开始增加,该行将向左移动。
热图
热图用于识别每个不同列之间的零相关性。换句话说,它可以用来识别每一列之间是否存在空值关系。
接近正 1 的值表示一列中存在空值与另一列中存在空值相关。
接近负 1 的值表示一列中存在空值与另一列中存在空值是反相关的。换句话说,当一列中存在空值时,另一列中存在数据值,反之亦然。
接近 0 的值表示一列中的空值与另一列中的空值之间几乎没有关系。
有很多值显示为
The heatmap can be generated by the following code:
msno.heatmap(df)

missingno heatmap plot illustrating the correlation in nullity between data columns. Image by the author.
Here we can see that the ROP column is slightly negatively correlated with the RHOB, NPHI and PEF columns, and slightly positively correlated with RSHA. If we take a look at DRHO, its absence is highly correlated with missing values in the RHOB, NPHI and PEF columns.
The heatmap approach is more suitable for smaller datasets.
Dendrogram
The dendrogram plot provides a tree-like graph generated through hierarchical clustering and groups together columns that have strong correlations in nullity.
If a number of columns are grouped together at level zero, then the presence of nulls in one of those columns is directly related to the presence or absence of nulls in the others columns. The more separated the columns in the tree, the less likely the null values can be correlated between the columns.
The dendrogram can be generated by:
msno.dendrogram(df)

mssingno dendrogram illustrating the correlation in nullity between the well log measurements.
In the dendrogram plot above, we can see we have two distinct groups. The first is on the right side (DTS, RSHA, and DCAL) which all have a high degree of null values. The second is on the left, with the remainder of the columns which are more complete.
LITHOFACIES, GR, GROUP, WELL, and DEPTH_MD are all grouped together at zero indicating that they are complete.
RDEP, Z_LOC, X_LOC, and Y_LOC are grouped together close to zero. RMED is in the same larger branch suggesting that some of the missing values present within that column can be correlated with these four columns.
Summary
Identifying missing prior to applying machine learning is a key component of the data quality workflow. This can be achieved using the missingno library and a series of visualisations to understand how much missing data is present, where it occurs, and how the occurrence of missing values is related between the different data columns.
感谢阅读!
如果你觉得这篇文章有用,请随时查看我的其他文章,这些文章从不同的角度研究了 Python 和测井数据。你也可以在 GitHub 找到我在这篇文章和其他文章中使用的代码。
如果你想联系我,你可以在 LinkedIn 或者我的 网站 找到我。
有兴趣了解更多关于 python 和测井数据或岩石物理学的知识吗?跟我上 中 。
如果你喜欢这篇文章或任何其他文章,并想表达你的谢意,欢迎你来给我买杯咖啡
参考
比洛古尔(2018)。缺少 no:一个缺少的数据可视化套件。《开源软件杂志》,3 卷 22 期,547 页,https://doi.org/10.21105/joss.00547
博尔曼,彼得,奥桑德,彼得,迪里布,法哈德,曼拉尔,投降,&迪辛顿,彼得。(2020).机器学习竞赛 FORCE 2020 井测井和岩相数据集[数据集]。芝诺多。http://doi.org/10.5281/zenodo.4351156
使用正确的工具可视化数据
数据可视化
Tableau、ggplot2 和 seaborn

图片来源:我
谈到可视化数据,大多数人都有一个简单明了的想法。他们用散点图来显示两个变量之间的关系。箱线图用于比较变量中不同元素的离差。饼图可以用来描绘不同的类作为一个整体对变量的贡献。时间序列图可用于显示某人或某个组织在一段时间内所取得的进展。
除了对使用什么样的图表有一个明确的想法之外,重要的是利用一个软件包来创建图表和开发图表,有多种资源可以用来实现这一点。ggplot2 via R、seaborn via python、Tableau、PowerBI、MS Excel 都是用于构建图表的一些著名平台。
本文将关注在三个包/平台上构建图表的过程:Tableau、seaborn 和 ggplot2。所使用的数据集是广泛使用的 iris 数据集。虹膜数据集有五个变量。其中四个是连续变量:花瓣长度,花瓣宽度,萼片长度和萼片宽度。最后一个是分类变量,叫做物种。它有三个等级:setosa,virginica 和 versicolor。
通过在所有三个平台上构建相同的图表,可以比较图表的质量,并决定在处理数据可视化项目时使用哪一个。生成的两个图表是:
- 比较萼片宽度和萼片长度之间关系的散点图。
- 比较不同物种四个变量平均值的条形图。
iris 数据集在 R-studio 和 Jupyter 笔记本上都是现成的。因此,它很容易被导出用于 Tableau。
(舞台上由人扮的)静态画面
Tableau 是一个让数据可视化尽可能简单的平台。与 python 和 R 相比,它的巨大优势在于它不需要代码来加载数据集或创建图表。由于其拖放功能,它允许用户修改变量来构建图表,从而有效地向用户呈现信息。它还有其他功能,可以用来美化图表,使它们对观众有吸引力。
一个很好的生动的例子。图表是在一分半钟内完成的。
Tableau 的易用能力在上面的视频中可以见证。本·琼斯(Ben Jones)的《与 Tableau 交流数据:设计、开发和交付数据可视化》是一本可以指导初学者如何掌握使用 Tableau 的艺术的书。使用 Tableau 构建的其他图表可以在下面查看。

Tableau 如何呈现散点图

Tableau 如何呈现条形图
ggplot2
ggplot2 是 R-studio 提供的一个非常棒的包。与 Tableau 不同,它要求用户导入一个包来构建图表。虽然它需要一些编码,但是编码的语法非常简单。用 ggplot2 构建一个简单的图表包括两个简单的步骤。
第一步是加载 tidyverse 包。ggplot2 包是 tidyverse 包提供的众多包中的一个。通过加载 tidyverse 包,用户还可以在设计图形时访问其他包的功能。加载 tidyverse 的代码可以在下面看到。
install.packages("tidyverse")
library(tidyverse)
第二步是使用编码语法生成图形。编码语法如下所示。ggplot()调用 ggplot2 包并识别要使用的数据。geom_point()表示带有点的散点图是所需的图形。通过在 geom_point()中使用 aes()命令,可以很容易地绘制出哪些变量应该出现在 x 和 y 轴上,并根据它们的种类对它们进行分组。labs()可用于为图表添加标题,并标记 x 轴和 y 轴。使用 theme_classic()将主题设置为 classic 使得用户可以使用经典主题。
如果用户对绘制不同于下面通过 ggplot2 创建的图表感兴趣,此链接可作为用户的指南。
ggplot(data = df_iris) +
geom_point(aes(x = sepal_width, y = sepal_length, color = species)) +
labs(title = "Sepal length vs Sepal width", x = "Sepal width", y = "Sepal length") +
theme_classic()

ggplot2 如何渲染散点图

ggplot2 如何渲染条形图
海生的
Seaborn 是 python 提供的一个包。它是对 python 提供的另一个数据可视化包 matplotlib 的改进,用于美化图形。Seaborn 的功能就像 ggplot2 一样,它要求用户加载一个包,并使用编码语法来获得所需的绘图。下面是加载 seaborn 包和其他有用的包的代码,这将使设计图表变得容易。
import seaborn as sns; sns.set_theme(style = "dark")
%matplotlib inline
import matplotlib.pyplot as plt
加载包之后,下一步是使用正确的功能来绘制图表。plt.figure()可以用来决定绘图的大小。sns.barplot()接受要放在 x 和 y 轴上的变量以及要使用的数据集。与 ggplot2 一样,在 sns.barplot()函数中对绘图的外观进行了进一步的更改。plt.title()、plt.xlabel()和 plt.ylabel()用于标注地块。
如果用户有兴趣通过 seaborn 绘制不同于下图的图表,这个链接可以作为用户的向导。
plt.figure(figsize = (20,12))
sns.barplot(x = "species", y = "number", data = n_iris2, hue = "feature", palette = "deep")
plt.title("Bar chart of the average values of the features across species", fontsize = 20)
plt.xlabel("Species", fontsize = 12)
plt.ylabel("Average value", fontsize = 12)

seaborn 如何渲染散点图

seaborn 如何渲染条形图
结论
上面讨论的所有三个平台对于设计和构建图形来说都是惊人的。对于对编码不感兴趣的人来说,Tableau 是轻松生成图表的好方法。ggplot2 和 seaborn 是编码平台,为用户提供了一种控制图形外观的开放式方法。当谈到数据可视化时,您的想象力是您唯一的限制。
以下是关于数据可视化的推荐文章列表:
- Tableau:释放视觉分析的力量
- 数据可视化是如何工作的!
- 使用 Matplotlib 构建条形图
- 使用 Matplotlib 创建基本饼图
感谢您的阅读!
使用美国人口普查 API 和 PUMS 进行数据分析
包括 Python 迷你教程的初学者指南。

马库斯·温克勒在 Unsplash 上的照片
这是熨斗学校数据科学训练营的项目周,我们正在收集美国人口普查数据!
有大约 100 万个网站、链接、资源、网络研讨会和关于人口普查数据的原始数据,你可能要花几天时间才能浏览完。你很幸运,我为你花了那么多时间,并且收集了我最喜欢的资源来帮助你开始!我的目标是使这个过程尽可能简单,这样您就可以得到有用的东西:使用一些数据!
作为一名 Python 编程新手和数据科学学生,我害怕使用 API 来创建和清理自己开发的数据集,所以我花了几天时间在互联网上寻找一个不需要使用 API 的数据集。我知道我想关注什么(边缘化和/或代表性不足的群体,谢谢你的提问),但我正在寻找的每个包含变量的数据集对我新生的编码大脑来说都太小、太旧、太稀疏或太复杂(阅读:复杂)。
老实说,我很高兴我不能找到一个完美的,因为它让我克服了恐惧,并自学了 API。剧透:它们实际上并不是一个很难的概念。有一次我浏览了几个教程,觉得自己已经有了一些基础知识…我在观看 2020 年 3 月录制的人口普查网络研讨会时发现了这个神奇的工具。非常感谢美国癌症学会的调查统计学家阿曼达·克里梅克给了我这个金块。她深入地讲述了如何使用人口普查工具和数据,但我为你提取并浓缩了我认为最有用的资源和技巧。
长话短说,美国人口普查局已经推出了一个惊人的工具(在 2021 年出版时仍处于测试阶段,但非常有用),以帮助用户通过 API 请求获得您想要的信息。
在这次人口普查速成班中,我为你计划了 4 件事:
- 一些关于人口普查局的基本常识,
- 如何在踏入代码之前定制自己的数据集,
- 一段快速的 Python 代码让你开始一些分析,并且
- 几个可点击的链接让你在你认为合适的地方加深你的知识
附注:本文引用的所有链接也位于页面底部,以便提供快速方便的参考。
我们来说说人口普查局
美国人口普查局是一个政府机构,进行持续的调查,以收集有关美国人民和经济的数据。我喜欢这个组织的几个方面:
- 他们向公众提供无限制的数据访问(如果需要超过 500 次查询,只需使用一个 API 键
- 数据集惊人的全面和完整(这里没有稀疏矩阵!)
- 任何人和任何人都可以完全免费访问。
你可能会问:“你说的持续是什么意思?我以为人口普查 10 年才进行一次呢?”嗯……算是吧。当我们谈论人口普查局时,我们可以谈论两个调查:美国社区调查和十年一次的人口普查。它们都是关于美国人口的调查,但有一些不同之处:
ACS(美国社区调查)是“一项正在进行的关于社会、经济、住房和人口数据的全国性调查。这项调查的年度样本量为 350 万个地址,调查信息几乎全年每天都在收集。”这个样本量涵盖了大约 1%的人口。
十年一次的人口普查是一项更短、更基本的调查,每 10 年发送给美国的每个人。在 2020 年的人口普查中,只有 9 个问题,而正在进行的 ACS 有近 70 个问题。与收集全年数据的 ACS 相比,十年一次的人口普查收集美国每个人在单一时间点的数据(在普查年的 4 月 1 日进行)。作为参考,2020 年人口普查有大约 60%的美国人口参与。
什么是 PUMS?
人口普查局的内部统计人员向公众提供了大量的汇总数据和有用的表格。不过,我真正想跟大家说的是 PUMS(公用微数据样本)。正如“微观数据”一词所暗示的,在 PUMS 发现的数据是基于个人的答复,而不是带有预定参数的汇总数据。这允许数据爱好者、控制狂和数据科学训练营的学生进行他们自己的分析,以便理解他们自己选择的变量之间的关系。
获取想要的数据!
任何人都可以访问文件传输协议(FTP)网站上的 PUMS 文件,该网站提供 CSV 和 SAS 格式的数据,或者使用 data.census.gov 上的 API 调用。(使用 此链接 访问 FTP 和 data.census.gov,详情如下)
访问 FTP 站点—获取整个 dang 集合
对于检查整个数据集而言,这是一个很好的选择,无需担心参数。使用下面的语法参考进行选择:
- csv: 表示文件类型
- p: 表示人口数据(与表示住房信息的“h”相对)
- 接下来的两个字符表示一个州的代码(例如【tx】表示德克萨斯州),或者查找【US】表示整个美国的数据。

这么容易…太容易了?
使用这些文件的缺点是什么?542 MB 的 zip 文件变成了 2.28 GB 的内存,如果存储空间或计算能力不足,这并不理想。
访问 data.census.gov—选择您的变量
一个三年级学生可以使用这个网站(顺便说一下,这是一件好事)。仔细阅读 2019 年 ACS 中的 510 个变量,或者从下拉菜单中选择一个选项,查看按主题分组的小变量列表(如下图,我在“教育”中搜索变量)。我只是喜欢友好的小蓝绿色框,我可以点击它来选择我的变量,以及细节选项卡下每个变量的下拉菜单。

是不是很美?
在窗口顶部,单击“选择地理位置”以选择一个大的地理区域,或者向下钻取到县级。对于我的分析,我选择将这个选项卡选项留空,因为我想查看整个美国的数据。
附注:如果您正在寻找一个人口少于 65,000 的特定县,那么当选择一个数据集时,您将需要选择“ACS 5 年估计值”,它代表这些较小县 5 年内收集的数据,而不是 1 年的估计值。更多信息点击这里。
在“数据车”中玩滑动和变量,或者直接跳到神奇的“下载”标签(如下图右上方)。今天,我将直接点击“复制 API 获取查询”按钮,将 URL 直接粘贴到我的项目笔记本中。

准备好我承诺的 Python 代码了吗?

现在你已经从 data.census.gov 复制了你的链接,我们可以使用。get() 方法来请求响应。您还需要运行 response.status_code 来检查它是否输出 200(成功)。这表示您的请求已顺利通过,没有任何错误。下面列出了其他响应代码。
可能的响应代码列表及其含义:
- 1xx(信息性):传达传输协议级别的信息
- 2xx(成功):客户请求被成功接受(第一次泵送)
- 3xx(重定向):客户端必须采取一些额外动作来完成它们的请求
- 4xx(客户端错误):将矛头指向客户端(检查你的代码!)
- 5xx(服务器错误):服务器对错误负责
接下来,加载并格式化您的数据,看看吧!

嗯…那很好,但是标题需要重新设置,如果能把变量改成更直观的就更好了。通过使用 df[1:] 并使用 df.columns = [] 重命名列,我们可以确定我们的数据帧从哪一行开始。我们开始吧!

在你走得太远之前,一个好的做法是用 df.info() 检查你的数据框,检查每一列中的数据类型。人口普查数据将作为对象加载,我希望将我的几个变量转换成整数,以便进行统计分析。很快的,让我们创建一个我想更新为整数的变量列表,然后运行一个 For 循环。

非常好。现在,您已经准备好开始处理您的人口普查数据了!我希望这有助于初学者认识到使用 census API 来运行自己的统计分析是多么容易。
资源:
为了让这个过程尽可能的简单,我列出了所有提到的链接和一些额外的收藏夹。
- 在这个页面上,你也可以请求一个 API 密匙,以防你需要 500 个以上的查询
- 加入他们的 Slack 社区——超级友好的人,非常乐于助人。阅读过去的讨论和点击张贴的链接确实帮助我获得了如何使用这个惊人的数据集的感觉。那里的人反应也超级快!虽然可能有其他方式与专家取得联系,但看起来大多数松散的问题和顾虑都会得到相当迅速的回应。
更多关于 ASC 五年数据
PUMS 技术文件——关于 ACS 上的变量列表,参见“PUMS 数据字典”。(选择。txt 或者。pdf 格式,无需下载即可一窥究竟。)
ACS PUMS 手册:了解和使用美国社区调查公共使用微数据样本文件:用户需要了解哪些数据
网上研讨会(2020 年):美国社区调查公众使用微数据样本简介 PUMS 文件
使用华尔街赌注子网站获得对历史股票市场趋势的见解
自然语言理解
使用 BERT、HDBSCAN 和 PEGASUS 从文本中自动生成洞察

简介
当看历史股票市场趋势时,经常需要超越数字。如果你试图理解不寻常的事件,尤其如此。在缺乏内部信息的情况下,分析师通常会使用他们的谷歌搜索引擎,通常依靠头条新闻和媒体报道来了解不寻常的市场运动。
在这篇文章中,我的目标是在一个信息丰富的数据集上应用文本聚类和总结来自动化这个过程。我将关注 1 月 21 日晚些时候 GameStop 股价的上涨,使用华尔街赌注的 subreddit 作为我的数据源。
最终,我的目标是自动化从大量文本中产生洞见的过程。
语境
对于那些不熟悉 GameStop 的人,我将提供一些简单的背景。
2021 年 1 月,GameStop 的股价在短短几天内达到了 347 美元的历史新高。第二天,股价下跌了 44%,第二天又反弹至 325 美元。这一奇怪的事件让这家鲜为人知的公司一跃成为全球新闻媒体的头条。有点像“比特币”,对金融市场不感兴趣的人突然变得非常好奇,有几天 GameStop 成了许多人闲聊的中心。

作者图片
Reddit 被认为是 1 月下旬事件的中心。1 月 29 日的 peek 活动中,明确提到 GameStop 的帖子大约有 3600 条,大约是接下来几天帖子数量的十倍。
数据&数据准备
数据:我使用了从 Reddit 的 WallStreetBets subreddit 收集的数据。subreddit 是一个论坛,会员在这里讨论股票市场交易和投资。数据集在 Kaggle 上公开。
数据准备:为了形成我的分析群体,我隔离了明确提到 GameStop 或 GME 的帖子。
有许多帖子只是图片或其他网站的链接,我从分析人群中删除了这些。我通过删除多余的空白和任何网址来清理文本。
我将我的数据分成两个池;1 月 28 日和 29 日。这与同一时期 GameStop 的股价波动大致相符。
方法— HDBSCAN 和 Google 的 PEGASUS 用于集群和汇总
在高层次上,我通过将相似的帖子聚集在一起,然后对这些集群进行抽象总结来获得洞察力。
令人印象深刻的是,抽象概括可以提取一篇长文本,并生成捕捉主要思想的简短摘要,而不仅仅是复制文本的元素。基本上就是转述。
抽象概括:换句话说,这是什么意思?
在 Reddit 上,用户通过投票给帖子打分,根据他们是否喜欢或同意这个帖子。一个帖子的投票数越高,它在新闻源上的位置就越高。为了减少噪音和提高聚类的性能,我选择了累计占总投票数 90%的帖子。
在大约 55000 个帖子中,只有 13%获得了 90%的选票
我的方法背后的基本原理是试图将大量的帖子减少到最相关的。
HDBSCAN:带有噪声的应用程序的基于层次密度的空间聚类
由 Campello、Moulavi 和 Sander 开发的聚类算法。HDBSCAN 是 DBSCAN 的一个更复杂的表亲,因为它结合了分层建模,使其能够识别不同密度的集群,甚至集群内的集群。
这里有一篇关于hdb scan 机制的优秀文档,供感兴趣的人参考。
谷歌的 PEGASUS:抽象总结的转换器
变压器是一种深度学习模型,旨在处理顺序数据,使其非常适合 NLP 和 NLU 任务。
我使用了预先训练好的 Google PEGASUS 转换器来完成摘要任务。当谈到抽象概括时,这是最先进的。
实现
嵌入
模型和算法不会说英语,而是解释数字。我们在这里处理英语文本,所以第一步是转换我们的文本并创建嵌入。这些是我们文本的高维数字表示。
我使用了 BERT,因为它基于生成 512 维嵌入的单词的上下文提取不同的嵌入。
UMAP
采取这一步骤是为了将我们的 512 维嵌入减少到更低维度的空间。这里可能需要做一些调整,但应该注意的是,降低维度可以提高聚类性能,但代价是信息丢失。
HDBSCAN
执行 HDBSCAN 聚类。
绘图
我们可以通过将它们进一步简化为二维空间并使用标准的 python 绘图库来可视化我们的集群。

作者图片:1 月 28 日 HDBSCAN 星团的 2D 图像

作者图片:1 月 29 日 HDBSCAN 星团的 2D 展示
注—灰点与异常值有关;它们不属于任何集群。
提取话题
我尝试在主题级别实现 TF-IDF,试图直观地理解主题。主题标有一个单词和一个相应的数字,代表该单词在主题中相对于其他单词的重要性。
有关 BERT 和主题建模的更多信息,请阅读此
注意:在这一步中,我还通过聚类来聚合文本
合并主题和正文
创建将提供给转换器的数据集。
跑飞马
对文本聚类进行抽象概括。
把所有的东西放在一起
从头到尾运行所有内容。注意数据应该是一个包含所有你想要分析的文本的列表。
自动生成的见解
下表是 Reddit 自动生成的见解。我已经删除了脏话。
如果主题为-1,HDBSCAN 会将这些帖子视为不属于任何组的离群值。对于 1 月 29 日,我删除了返回无意义结果的摘要。这些要么是空白的,要么是隐藏的 URL,我最初无法从文本中解析出来。
大小是指每个集群内单个帖子的数量。
我不是 GameStop 或金融市场专家,所以我将把摘要的解释留给读者。

作者图片:2021 年 1 月 28 日帖子的见解

作者图片:2021 年 1 月 29 日帖子的见解
最终想法
通过正确的调优、相当大的计算能力和一些耐心,我认为 BERT、HDBSCAN 和 PEGASUS 的组合展示了从大量文本中自动生成有意义摘要的前景。
可以通过增强数据处理、清理和仔细选择数据源来改进结果。调查最小摘要长度和对所生成摘要的理解之间的关系也是令人感兴趣的。
Reddit 的数据相当混乱,但仍然很有见地
在调整 HDBSCAN 以产生更好的集群时,可以进行更多的实验。
最终,对于这样的任务,最重要的是你试图从中获得洞察力的数据源的质量。
俗话说,垃圾进垃圾出。
端到端代码
由于 GPU 的限制,我无法提供完整的端到端代码供您运行。不过,我已经创建了一个 GitHub repo 供您查看!
和往常一样,如果你能想到任何改进我所做的事情的方法,请评论。
https://github.com/john-adeojo/Reddit_WallStreet
关注我在的链接
https://www.linkedin.com/in/john-adeojo/
使用迁移学习进行乳腺癌检测
具有高效微调的多尺度 Inception V3 方法

介绍
每年,美国有超过 230,000 名乳腺癌患者取决于癌症是否转移。转移检测由病理学家检查大量的生物组织来进行。这个过程是劳动密集型的并且容易出错。来源
在这个项目中,我们旨在实现论文 中提出的多尺度转移分类模型,在千兆像素病理图像上检测癌症转移 。
资料组
我们使用camelion-16多千兆像素的幻灯片数据。从主数据集中采样一组 22 个具有显著肿瘤的载玻片。每张载玻片都有一个相应的标记肿瘤区域的遮罩。我们可以访问大约 9 个放大级别。

多倍放大肿瘤图像(。tif 文件)来源
每个多尺度图像以一致的坐标系一个接一个地存储,以允许用户访问任何放大倍数的任何图像片段。
我们利用 OpenSlide C 库及其 Python 绑定来高效地访问千兆像素大小的压缩文件。
数据准备
作为生成标记的训练数据的第一步,我们使用滑动窗口方法在较高的缩放级别上滑动,并创建幻灯片片段的标记图像,稍后我们将使用这些图像进行训练和测试。
我们使用论文中的方法,定义一个中心窗口,如果中心包含至少 1 个标记为肿瘤的像素,我们将图像标记为包含肿瘤细胞。
窗口大小和中心大小分别被选择为 80 和 50。最后,为了使我们的训练数据具有多尺度,我们创建了原始图像片段的类似大小的缩小版本。缩小版本用于向分类模型提供宏观级别的上下文。我们使用多尺度方法生成数据,并使用两种不同的放大倍数来准备数据集。

数据扩充
我们使用了各种数据扩充技术来补充我们的训练数据,使我们的模型更加稳健。
正交旋转和翻转
我们引入了正交扩充来引入旋转不变性,因为载玻片可以在这些方向中的任何方向上被检查
- 随机正交旋转
- 随机水平和垂直翻转
颜色扰动
为了使我们的模型对光照和染料强度有鲁棒性,我们对颜色做了如下改动。
- 最大差值为 64/255 的亮度
- 最大增量为 0.25 的饱和度
- 最大差值为 0.04 的色调
- 与最大增量 0.75 形成对比
方法学
我们利用多塔 Inception V3 模型来利用多尺度图像进行分类。我们只对顶层进行了微调,因为这些层学习更高级的功能,并且可以通过基于我们的数据集对这些层进行微调来大大改善结果。阅读谷歌人工智能的这篇博文了解更多细节
- 使用的架构: Inceptionv3(多尺度)微调图层> 150
- 初始权重:图像净
- 损失使用:二元交叉熵损失
- 放大倍率:2 级和 3 级

多尺度模型结构
结果
我们最后基于一个新的肿瘤切片测试了我们的模型。在对肿瘤切片进行预处理并进行预测后,我们使用模型输出创建了一个热图。

真实肿瘤掩模与预测肿瘤掩模

- AUC : 0.97659
- 阈值 : 0.48429
- 灵敏度 : 0.97478
- 特异性 : 0.95004
- 召回 : 0.97277
- 精度 : 0.22275
- 我们看到所有的肿瘤区域都被正确识别。
- 我们可以产生非常高的召回率(这在医学预后的背景下是很重要的)
- 带有微调的迁移学习在通过计算强度较低的训练产生良好结果方面是有效的
- 该模型对边界的预测似乎不太准确。
未来的改进
- 通过访问更好的 GPU 和高 RAM 机器,使用更高放大倍数的图像。
- 通过计算每个可能的幻灯片方向上的预测来使用预测平均,以提高准确性并引入旋转不变性。
- 使用更好的前景和背景分离技术来提高边界的性能。
如果你有任何问题,你可以通过smarth.g@columbia.edu联系我。在 LinkedIn 和 Twitter 上关注我。
谢谢!
参考
[1] Y .刘, K .加德帕利,m ., G. E .达尔, T .科尔伯格,a ., S .韦努戈帕兰,a ., P. Q .纳尔逊, G
[2]骆驼数据集
使用 Turf.js 通过自定义边界对坐标进行地理编码
这个位置属于❝Which 地区/区域/区域吗?❞
虽然许多国家的医疗保健工作者继续努力接触和跟踪个人的移动,以识别各种“热点”,即感染病例数量特别多的地区,但这一过程通常需要根据地区、地带、城市等对每个识别的位置进行聚类。(取决于实现的边界类型)。

作者图片|新加坡国家的任意示例,感染病例经常出现的位置用地图标记表示
对于我们大多数人来说,当给定任何邮政编码或地址名称时,找到该位置属于哪个地区或区域(例如“位置 A ”)的最本能的方法是搜索更多信息:

作者图片|声称位于“北部地区”和“Z 区”的“位置 A”的任意示例
然而,当您需要每天处理数十、数百甚至数千个唯一地址并根据区域边界标记每个位置时,这就变成了一个完全不同的故事。虽然许多地理空间分析师会使用诸如 ArcGIS 或 QGIS 之类的软件工具,但我个人更喜欢一种更省事的方法,它不需要安装诸如turf . js 库之类的软件。
就短期地理编码而言,我认为这是一种更实际的方法,因为代码片段比桌面软件更容易移植。
为了展示turf . js 库的功能,类似于我以前的一些文章:
- 利用 D3.js v4 构建 Tableau 的网络图|作者李思欣·楚|走向数据科学
- 被低估的 Tableau 组合功能——点、线串&多边形制图|李思欣·楚|走向数据科学
- 通过不到 3 个步骤从现有空间数据生成十六进制地图|作者李思欣·楚|迈向数据科学
我已经将工具部署到与上述教程相同的 Web 应用程序上:

图片作者|注意,过去的 3 个 Tableau 实用工具目前位于 Tableau 数据实用工具的下拉列表中
为了演示,我将使用 2 个输入空间文件(接受 GeoJSON、SHP 和 KML 格式)—
(1)空间边界(https://github . com/孵育-geek-cc/tableau-data-utility/blob/master/public/data/planning areas . zip)

图片作者|在 web 应用程序 Tableau 数据实用程序上,导航至“地理编码器工具”。步骤(1)选择空间文件输入(空间边界文件是 SHP 格式的档案)。边界将被渲染到地图容器中。

图片作者|在 web app 上 Tableau 数据实用程序 |步骤(2)继续选择空间文件输入(空间坐标文件为 GeoJSON 格式)。坐标将被渲染到地图容器中。 注:边界和坐标的唯一编号如图所示。

图片作者|步骤(3)最后,地理编码输出可以导出为 JSON 或 CSV 格式。
为了进行演示,上述内容已导出为 CSV 格式,并呈现在 Tableau 中,如下所示:

按作者分类的图像|左图:在没有底图的情况下渲染 CSV 输出,并根据边界名称对坐标进行颜色编码|右图:渲染底图以将颜色编码的坐标与实际边界进行对比。请注意,坐标根据地图的边界进行了不同的颜色编码,这意味着每个位置都已成功进行了地理编码。
为了说明另一个例子,在下面的演示中使用输入文件(1) US_States.geojson (空间边界)&(2)US _ hospitals . geo JSON(空间坐标):

图片由作者提供|步骤(1)两个空间文件US _ States . geo JSON&US _ hospitals . geo JSON 都上传到 Tableau 数据实用程序 |步骤(2)地理编码坐标导出为单个 CSV 文件

Image by Author |前面步骤导出的 CSV 输出在 Tableau 中呈现。根据底图边界,每个美国医院坐标都已成功进行地理编码。
您可以在 Tableau 数据实用程序随意访问这个地理空间工具,并通过上传您自己的空间文件:D 来试用它
边注: Turf.js 库出人意料地被低估,而且用途惊人。要查看如何将 Turf.js 用于其他用例,也可以随意查看用不到 3 个步骤从现有空间数据生成十六进制地图|作者李思欣·楚|迈向数据科学
感谢阅读,并希望你发现这是有用的!
https://geek-cc.medium.com/membership
使用单词嵌入识别公司名称和股票代码
介绍
项目目标:使用单词嵌入从自然文本中识别公司名称和股票代码。
假设:股票行情和公司名称在类似的自然文本中被使用,比如红迪网帖子或推特。
在这种假设下,单词嵌入应该很适合于识别这些目标单词,因为单词嵌入是根据单词所处的语境来训练的。
计划:
- 在股票市场相关的 Reddit 帖子上训练一个 Word2Vec 模型。
- 创建一个可用于表示目标矢量的矢量(详见下文)。
- 使用代表向量从红迪帖子中识别目标词。
在这篇文章中,我将跳过描述什么是单词嵌入,以及 Word2Vec 算法是如何工作的。我已经就同一项目 写了一篇更详细的论文,可以在这里找到。在本文中,我详细解释了什么是单词嵌入,以及 Word2Vec 算法是如何工作的。我还通过朴素贝叶斯进行详细的情感分析。在这篇文章中,我将以节略的形式展示代码。
数据导入
使用了两种不同的数据源。这两个都是我在卡格尔网站上找到的来自 r/wallstreetbets 子网站的 Reddit 帖子的集合。
- Gabriel Preda,“Reddit WallStreetBets 邮报”Kaggle,2021,https://www.kaggle.com/gpreda/reddit-wallstreetsbets-posts/
- 拉斐尔·丰特斯,“Reddit-r/wallstreet bets”。卡格尔,2021 年。https://www.kaggle.com/unanimad/reddit-rwallstreetbets
在第一步中,我们将导入这些数据并提取每个句子。在其中一个数据集上,我们提取了 Reddit 标题,而另一个数据集上,我们提取了文本的正文,并进行拆分分开句子。注 : Gensim 的 Word2Vec 模型是基于句子进行训练的。
字符串处理
下一步是在我们使用文本训练模型之前对其进行预处理。我们将执行以下操作:
- 小写字母
- 删除标点符号、数字和表情符号
- 清除空白
- 标记化
- 找到大人物
- 查找三元组
现在让我们看看我们处理过的一个句子:

太好了,我们现在大约有 120 万个句子来训练我们的模型。
模特培训
现在我们准备训练或 Word2Vec 模型。Text 8 是一个来自维基百科的数据转储,它非常适合标准化单词向量。我鼓励你比较有和没有这些额外训练数据的模型,看看它有多大帮助。更多关于 text8 的信息可以在这里找到。
现在,我们可以使用 Gensim 的most_similar()功能来查看词汇表中与目标单词(如“gme ”)最相似的单词。

完美,所以我们可以看到,至少前 10 个最相似的词也是公司或股票代号。这给了我们希望,我们的假设是正确的。
创建代表性向量
我们需要一个有代表性的向量来和词汇表中的其他单词进行比较。我们可以使用这个向量和一些相似度阈值来识别帖子中的目标词。
不幸的是, Gensim 没有从其他向量创建平均向量的方法。所以我从 Gensim 的most_similar()函数中提取了一些代码,以获得我正在寻找的功能。我还创建了一个余弦相似度方法,我们将使用它来比较任何两个向量。余弦相似性是一种比较两个非零向量的方法(两个相同的向量的余弦相似性= 1.0)。
太棒了,现在我们可以用它从一些精选的目标单词中创建我们自己的向量。
现在让我们继续使用我们创建的向量,看看我们的模型词汇表中最相似的向量。注:数字是项和代表向量的余弦相似度。


我对这里的结果很满意。如果你跟随你应该探索所有这些 500 强,几乎所有的似乎是股票代号或公司名称。我们还可以看看向量与词汇表中每个单词的相似度的分布。

我在 0.55 处添加了一条垂直线,这是我最终选择的相似度阈值。该行右边的任何单词都将被识别为目标。基于这个数字,看起来阈值可能应该大于 0.55。这一选择是参数分析的结果,并有望随着更可靠的地面实况而增加。以下是更多相关信息。
模型检验
现在我们需要一些方法来测试我们的模型。我随机选择了 1000 个 Reddit 帖子,然后手动提取任何公司名称或股票代码。它看起来是这样的:

现在让我们继续测试它。有几个不同的方法来评分,我选择了一个简单的方法平均一个错过的分数和一个分数。
太好了,让我们开始工作吧。
参数分析
Gensim 的 Word2Vec 模型有几个我有兴趣系统测试的参数。我选择关注 4 个不同的参数:
- 相似性阈值:[0.5,0.55,0.6,0.65]
- n-Gram:[一元,二元,三元]
- 窗口大小:[1,2,3,4,5]
- 向量大小:[100,200,300,400]
注意:以下是关于这些参数的更多信息。
我们可以迭代所有这些参数,并根据我们的实际情况进行测试。
这显然需要很长时间,因为我们最终不得不训练模型 60 次。我只是让它运行了一夜。让我们来看看结果。

现在,我们可以单独考虑这些参数,看看我们是否发现了任何趋势。
相似性阈值
相似性阈值是我们用来确定一个单词是否被提取的阈值:
If cosineSimilarity(word, myVector) >= similarity_threshold:
extract(word)
Else :
continue
经过反复试验,我选择为这些测试考虑 4 个不同的阈值。在比较其他参数时,我还选择了包括这四个阈值中每个阈值的图形,因为这些参数在不同的阈值下可能会有不同的效果。
n 元语法
虽然 N-Gram 不是 word2vec 模型的一个参数,但它是对输入模型的数据进行预处理的一个重要步骤,对我们试图完成的任务有很大的影响。

我们看到每个阈值的趋势并不相同。我选择继续使用 bigrams,因为它既是中间立场,也允许我们将 bigram 公司名称放在一起(例如“home_depot”)。
窗口大小
窗口大小是 Word2vec 模型的一个参数,它指定了我们将考虑作为上下文单词的目标单词左右的单词数(更多细节可以在项目描述中找到)。这对嵌入有很大的影响,特别是对于像 Reddit 帖子这样的结构化程度较低的文本。

向量大小
向量大小是 word2vec 模型的另一个参数。向量大小是我们正在创建的单词嵌入的长度或维度(更多细节可以在项目描述中找到)。选择正确的向量大小确实需要通过测试来完成。我们将考虑 4 个不同的选项。

一旦确定了最佳参数:[n-gram=bigrams,window=1,vector-size=100,threshold=5.5]我们就能够在我们的地面真实数据上获得 89%的总体准确率。我对这个简单的参数分析的结果非常满意。现在,我们可以继续使用该模型来提取公司。
提取公司和股票代码
提取函数非常简单。我们基本上只需要将每个单词与我们的代表向量进行比较。我们之所以将它包装在 try 语句中,是因为我们模型的库不会有每一个可能的术语(word2vec 模型中的 param:min_count=25)。
当用一个threshold = 0.6应用于我们的 Reddit 帖子的原始数据集时,这种方法能够从近 30 万个帖子中提取公司。在相同的阈值下,提取了 800 个独特的公司/报价机。以下是一些提取的公司/证券交易所及其数量的子集。

后续步骤
我想指出的第一件事是,这个项目中的主要因素,代表向量,被掩盖了。这是因为在我做这个项目的时间里,我不认为我能实现一个创建这个向量的系统方法。我认为有很多可以让这个算法更加精确。首先需要做的是创建一个更大的地面真相,最好是从 Twitter 或其他形式的社交媒体中获取。有了一个更强大的基础事实来测试,我们可能会想出一些很酷的方法来学习一个更好的代表性向量。几个想法:
- 简单的方法:获取公司名称/代码的子集,根据实际情况系统地测试不同的组合。
- 更高级的方法:尝试实现某种梯度下降,它可以学习一个向量,这个向量将更好地执行我们现在令人敬畏的地面真理。
我也认为字符串的处理可以做得更多。在这个实现中,我选择删除数字和表情符号。我认为这是在抛出很多丰富的信息,尤其是表情符号。我确信我们的目标词与数字和表情符号都有上下文关系。如果我有任何想法,我一定会更新这篇文章。
包裹
如果您有任何建议、问题或意见,请告诉我,我是一名学生,正在学习所有这些工具。在整个项目中,我还使用朴素贝叶斯来确定不同公司/证券交易所的情绪。您可以在中阅读更多相关内容,完整记录在此处,您也可以在此处查看完整回购。感谢阅读。
使用工作流集筛选和比较银行贷款分类的模型-方法组合
为具有潮汐模型的分类问题筛选一系列模型类型和特征工程步骤
假设您是一家大型银行的数据科学家,您的 CDO 指示您开发一种自动化银行贷款决策的方法。您决定这应该是一个二元分类器,并继续收集数百个数据点。但是你应该从什么样的模型开始,你应该完成什么样的特征工程?为什么不筛选多个组合?
该项目将使用贷款预测数据集来举例说明 Tidymodels 机器学习生态系统中 workflow_sets()的使用。筛选一系列模型和特征工程管道对于避免对特定模型类型的任何内部偏见或“没有免费的午餐”定理非常重要。如果你有兴趣在阅读之前了解更多关于 Tidymodels 生态系统的信息,请查看我的前一篇文章。
贷款预测数据摘自https://Data hack . analyticsvidhya . com/contest/practice-problem-loan-Prediction-iii/。

加载包和数据
library(tidymodels) **#ML meta packages**
library(themis) **#Recipe functions to deal with class imbalances**
library(discrim) **#Naive Bayes Models**
library(tidyposterior) **#Bayesian Performance Comparison**
library(corrr) **#Correlation Viz**
library(readr) **#Read Tabular Data**
library(magrittr) **#Pipe Operators**
library(stringr) **#Work with Strings**
library(forcats) **#Work with Factors**
library(skimr) **#Data Summary**
library(patchwork) **#ggplot grids**
library(GGally) **#Scatterplot Matrices**train <- read_csv("train_ctrUa4K.csv")train %<>% rename(Applicant_Income = ApplicantIncome,
CoApplicant_Income = CoapplicantIncome,
Loan_Amount = LoanAmount)
探索性数据分析
在我们开始筛选模型之前,用一些基本的探索性数据分析来完成我们的尽职调查。
我们使用 skimr 包来产生下面的输出
skim(train)

skimr 的输出(图片由作者提供)
此外,我们可以使用以下函数通过适当的可视化来可视化这些结果。
**#Automated Exploratory Data Analysis**
viz_by_dtype <- function (x,y) {
title <- str_replace_all(y,"_"," ") %>%
str_to_title()
if ("factor" %in% class(x)) {
ggplot(train, aes(x, fill = x)) +
geom_bar() +
theme(legend.position = "none",
axis.text.x = element_text(angle = 45, hjust = 1),
axis.text = element_text(size = 8)) +
theme_minimal() +
scale_fill_viridis_c()+
labs(title = title, y = "", x = "")
}
else if ("numeric" %in% class(x)) {
ggplot(train, aes(x)) +
geom_histogram() +
theme_minimal() +
scale_fill_viridis_c()+
labs(title = title, y = "", x = "")
}
else if ("integer" %in% class(x)) {
ggplot(train, aes(x)) +
geom_histogram() +
theme_minimal() +
scale_fill_viridis_c()+
labs(title = title, y = "", x = "")
}
else if ("character" %in% class(x)) {
ggplot(train, aes(x, fill = x)) +
geom_bar() +
theme_minimal() +
scale_fill_viridis_d()+
theme(legend.position = "none",
axis.text.x = element_text(angle = 45, hjust = 1),
axis.text = element_text(size = 8)) +
labs(title = title, y ="", x= "")
}
}variable_plot <- map2(train, colnames(train), viz_by_dtype) %>%
wrap_plots(ncol = 3,
nrow = 5)

贷款数据集的可视化 EDA(图片由作者提供)
- 性别失衡,男性申请人比例高,有新来港定居人士
- 婚姻状况,几乎 3:2 的已婚申请者对未婚者,有 NAs 在场
- 大多数申请人没有孩子
- 大多数申请人是大学毕业生
- 大多数人不是自营职业者
- 申请人收入向右倾斜
- 共同申请人的收入向右倾斜
- 贷款金额向右倾斜
- 贷款额度期限一般为 360 天
- 大部分申请人都有信用记录,包括 NAs
- 混合物业区域类型
- 大多数申请都获得批准,目标可变贷款状态不平衡,比例为 3:2
双变量数据分析
数量变量
使用 GGally::ggpairs(),我们为所有数字变量生成一个变量矩阵图,并用 Loan_Status 着色。
**#Correlation Matrix Plot**
ggpairs(train %>% select(7:10,13), ggplot2::aes(color = Loan_Status, alpha = 0.3)) +
theme_minimal() +
scale_fill_viridis_d(aesthetics = c("color", "fill"), begin = 0.15, end = 0.85) +
labs(title = "Numeric Bivariate Analysis of Loan Data")

数值预测和贷款状况的双变量分析(图片由作者提供)
定性变量
使用如下生成的汇总数据帧,改变所需的字符变量,迭代生成以下可视化。
**#Generate Summary Variables for Qualitative Variables**
summary_train <-
train %>%
select(where(is.character),
-Loan_ID) %>%
drop_na() %>%
mutate(Loan_Status = if_else(Loan_Status == "Y",1,0)) %>%
pivot_longer(1:6, names_to = "Variables", values_to = "Values") %>%
group_by(Variables, Values) %>%
summarise(mean = mean(Loan_Status),
conf_int = 1.96*sd(Loan_Status)/sqrt(n())) %>%
pivot_wider(names_from = Variables, values_from = Values)summary_train %>% select(Married, mean, conf_int) %>%
drop_na() %>%
ggplot(aes(x=Married, y = mean, color = Married)) +
geom_point() +
geom_errorbar(aes(ymin = mean - conf_int, ymax = mean + conf_int), width = 0.1) +
theme_minimal() +
theme(legend.position = "none",
axis.title.x = element_blank(),
axis.title.y = element_blank()) +
scale_colour_viridis_d(aesthetics = c("color", "fill"), begin = 0.15, end = 0.85) +
labs(title="Married")

95%置信区间下每个分类变量的成功贷款申请比例(图片由作者提供)
完成上述可视化的好处是,它给出了贷款申请成功的差异大小以及不确定性的理解,通过每个分类变量的 95%置信区间可视化。由此我们注意到以下几点:
- 已婚申请人的申请被批准的可能性更大。
- 研究生申请者成功的机会更大,可能是因为收入更稳定/更高。
- 类似地,为自己工作的申请人的成功差异也大得多,可能是因为收入稳定性的差异
- 孩子的数量似乎对贷款状况没有太大影响
- 女性客户在申请成功上有更大的可变性,男性客户则更加离散。我们需要小心这一点,以免在算法中产生根深蒂固的偏见。
- 半城市或郊区的成功率最高。
- 最后,有信用记录的客户和没有信用记录的客户在成功率上有显著差异。
如果我们愿意,我们可以用方差分析/卡方检验进一步探索这些关系。
数据拆分— rsamples
我们将数据按照 Loan_Status 进行了 80:20 的分层。可以通过调用 mc_split 对象上的 training()或 testing()来访问每个拆分表。
**#Split Data for Testing and Training**
set.seed(101)
loan_split <- initial_split(train, prop = 0.8, strata = Loan_Status)
模型开发——防风草。
如上所述,我们希望筛选一系列模型,并锁定几个合适的候选模型。下面我们已经初始化了一系列的标准分类模型。
**#Initialise Seven Models for Screening**
nb_loan <-
naive_Bayes(smoothness = tune(), Laplace = tune()) %>%
set_engine("klaR") %>%
set_mode("classification")logistic_loan <-
logistic_reg(penalty = tune(), mixture = tune()) %>%
set_engine("glmnet") %>%
set_mode("classification")dt_loan <- decision_tree(cost_complexity = tune(), tree_depth = tune(), min_n = tune()) %>%
set_engine("rpart") %>%
set_mode("classification")rf_loan <-
rand_forest(mtry = tune(), trees = tune(), min_n = tune()) %>%
set_engine("ranger") %>%
set_mode("classification")knn_loan <- nearest_neighbor(neighbors = tune(), weight_func = tune(), dist_power = tune()) %>%
set_engine("kknn") %>%
set_mode("classification")svm_loan <-
svm_rbf(cost = tune(), rbf_sigma = tune(), margin = tune()) %>%
set_engine("kernlab") %>%
set_mode("classification")xgboost_loan <- boost_tree(mtry = tune(), trees = tune(), min_n = tune(), tree_depth = tune(), learn_rate = tune(), loss_reduction = tune(), sample_size = tune()) %>%
set_engine("xgboost") %>%
set_mode("classification")
特征工程—配方
以下是一系列配方配置。这两种不同的插补方法如何相互比较,在下面的注释中,我们使用了 impute_mean(代表数字)、impute_mode(代表字符)或使用 impute_bag(使用袋装树的插补),可以对数字和字符变量进行插补。
注意,对于 Credit_History,我们选择不估算这个值,而是将三个结果分配给一个整数值,将未知实例设置为 0。
由于 Loan_Status 不平衡,我们对配方 2 和 4 应用了 SMOTE(合成少数过采样技术)。我们将完成工作流程比较,以了解插补策略之间的准确性差异。
**#Initialise Four Recipes**
recipe_1 <-
recipe(Loan_Status ~., data = training(loan_split)) %>%
step_rm(Loan_ID) %>%
step_mutate(Credit_History = if_else(Credit_History == 1, 1, -1,0)) %>%
step_scale(all_numeric_predictors(), -Credit_History) %>%
step_impute_bag(Gender,
Married,
Dependents,
Self_Employed,
Loan_Amount,
Loan_Amount_Term) %>%
step_dummy(all_nominal_predictors())recipe_2 <-
recipe(Loan_Status ~., data = training(loan_split)) %>%
step_rm(Loan_ID) %>%
step_mutate(Credit_History = if_else(Credit_History == 1, 1, -1,0)) %>%
step_scale(all_numeric_predictors(), -Credit_History) %>%
step_impute_bag(Gender,
Married,
Dependents,
Self_Employed,
Loan_Amount,
Loan_Amount_Term) %>%
step_dummy(all_nominal_predictors()) %>%
step_smote(Loan_Status)
recipe_3 <-
recipe(Loan_Status ~., data = training(loan_split)) %>%
step_rm(Loan_ID) %>%
step_mutate(Credit_History = if_else(Credit_History == 1, 1, -1,0)) %>%
step_scale(all_numeric_predictors(), -Credit_History) %>%
step_impute_mean(all_numeric_predictors()) %>%
step_impute_mode(all_nominal_predictors()) %>%
step_dummy(all_nominal_predictors()) %>%
step_zv(all_predictors())recipe_4 <-
recipe(Loan_Status ~., data = training(loan_split)) %>%
step_rm(Loan_ID) %>%
step_mutate(Credit_History = if_else(Credit_History == 1, 1, -1,0)) %>%
step_scale(all_numeric_predictors(), -Credit_History) %>%
step_impute_mean(all_numeric_predictors()) %>%
step_impute_mode(all_nominal_predictors()) %>%
step_dummy(all_nominal_predictors()) %>%
step_zv(all_predictors()) %>%
step_smote(Loan_Status)
以 recipe_1 为例,我们可以为每个转换准备训练和测试数据集。
**#Prep and Bake Training and Test Datasets**
loan_train <- recipe_1 %>% prep() %>% bake(new_data = NULL)
loan_test <- recipe_1 %>% prep() %>% bake(testing(loan_split))
然后利用这些转换后的数据集,可视化变量相关性。
**#Generate Correlation Visualisation**
loan_train %>% bind_rows(loan_test) %>%
mutate(Loan_Status = if_else(Loan_Status == "Y",1,0)) %>%
correlate() %>%
rearrange() %>%
shave() %>%
rplot(print_cor = T,.order = "alphabet") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 90)) +
scale_color_viridis_c() +
labs(title = "Correlation Plot for Trained Loan Dataset")

已训练贷款数据集的相关图(图片由作者提供)
从上面我们注意到贷款状态和申请人是否有信用历史之间的密切关系。这是有道理的,银行更倾向于贷款给有信用记录的客户,证明客户有能力偿还贷款。
迭代 Parnsip 和配方组合—工作流程集
工作流集合使我们能够筛选配方和 parnsip 模型规格的所有可能组合。如下,我们已经创建了两个命名列表,所有模型和配方。
**#Generate List of Recipes**
recipe_list <-
list(Recipe1 = recipe_1, Recipe2 = recipe_2, Recipe3 = recipe_3, Recipe4 = recipe_4)**#Generate List of Model Types** model_list <-
list(Random_Forest = rf_loan, SVM = svm_loan, Naive_Bayes = nb_loan, Decision_Tree = dt_loan, Boosted_Trees = xgboost_loan, KNN = knn_loan, Logistic_Regression = logistic_loan)
有趣的部分来了,使用 workflow_sets()创建一系列工作流
model_set <- workflow_set(preproc = recipe_list, models = model_list, cross = T)
设置 cross = T 指示 workflow_set 创建 parsnip 模型和配方规格的所有可能组合。
set.seed(2)
train_resamples <- bootstraps(training(loan_split), strata = Loan_Status)doParallel::registerDoParallel(cores = 12)
all_workflows <-
model_set %>% workflow_map(resamples = train_resamples,
verbose = TRUE)
我们已经初始化了一个助推器重采样程序,这在计算上要求更高,但是总体误差更小。通过重采样过程将 model_set 对象传递给 workflow_map,将启动一个所有防风草配方组合的屏幕,显示如下输出。这是一个要求很高的过程,因此我们启动了并行计算来简化这一过程。
使用重采样过程,workflow_map 将使每个工作流符合训练数据,并计算每个重采样的精度,然后取平均值。此外,由于每个配方都指定应该调整超参数,workflow_map 将执行一些调整以提供最佳结果。

workflow_map 的输出(图片由作者提供)
完成后,我们可以调用 all_workflows 对象,收集在此过程中计算的所有指标,并可视化结果。
名词(noun 的缩写)b .对于 workflow_set 对象,有一个可用的 autoplot()函数,但是我们希望能够比较配方和模型性能,因此必须重新设计以下可视化功能。
**#Visualise Performance Comparison of Workflows**
collect_metrics(all_workflows) %>%
separate(wflow_id, into = c("Recipe", "Model_Type"), sep = "_", remove = F, extra = "merge") %>%
filter(.metric == "accuracy") %>%
group_by(wflow_id) %>%
filter(mean == max(mean)) %>%
group_by(model) %>%
select(-.config) %>%
distinct() %>%
ungroup() %>%
mutate(Workflow_Rank = row_number(-mean),
.metric = str_to_upper(.metric)) %>%
ggplot(aes(x=Workflow_Rank, y = mean, shape = Recipe, color = Model_Type)) +
geom_point() +
geom_errorbar(aes(ymin = mean-std_err, ymax = mean+std_err)) +
theme_minimal()+
scale_colour_viridis_d() +
labs(title = "Performance Comparison of Workflow Sets", x = "Workflow Rank", y = "Accuracy", color = "Model Types", shape = "Recipes")

所有工作流的性能比较(图片由作者提供)
从上面我们可以观察到:
- 朴素贝叶斯和 KNN 模型表现最差
- 基于树的方法表现得非常好
- SMOTE 没有提高更复杂模型类型的性能,决策树模型的配方之间没有变化。
虽然我们已经进行了非常一般的初步观察,但考虑到前 8 个工作流的平均准确性略有不同,我们如何辨别哪些是不同的,或者实际上是相同的?
重采样的事后分析—潮汐先验
比较模型的一种方法是检查重采样结果,并询问模型实际上不同吗?
tidyposterior::perf _ mod 函数通过为给定指标生成一组后验分布来比较我们所有的工作流组合,然后可以将这些后验分布相互比较,以实现实际等效性。本质上,我们可以进行工作流之间的比较。
doParallel::registerDoParallel(cores = 12)
set.seed(246)
acc_model_eval <- perf_mod(all_workflows, metric = "accuracy", iter = 5000)

perf_mod 的示例输出(图片由作者提供)
然后将 perf_mod 的结果传递给 tidy()以提取后验分析的基本发现。它们的分布如下图所示。
**#Extract Results from Posterior Analysis and Visualise Distributions** acc_model_eval %>%
tidy() %>%
mutate(model = fct_inorder(model)) %>%
ggplot(aes(x=posterior)) +
geom_histogram(bins = 50) +
theme_minimal() +
facet_wrap(~model, nrow = 7, ncol = 6) +
labs(title = "Comparison of Posterior Distributions of Model Recipe Combinations", x = expression(paste("Posterior for Mean Accuracy")), y = "")

所有工作流的平均准确度的后验分布(按作者分类的图片)
回顾我们的性能比较,我们观察到两个最高等级的模型是提升树和具有 Recipe1 的决策树,这些模型处于基于树的模型的可解释性范围的极端。我们可以通过取它们各自后验分布的差异来寻求进一步的理解。
使用 contrast_models()我们可以进行这种分析。
**#Compare Two Models - Difference in Means**
mod_compare <- contrast_models(acc_model_eval,
list_1 = "Recipe1_Decision_Tree",
list_2 = "Recipe1_Boosted_Trees")a1 <- mod_compare %>%
as_tibble() %>%
ggplot(aes(x=difference)) +
geom_histogram(bins = 50, col = "white", fill = "#73D055FF")+
geom_vline(xintercept = 0, lty = 2) +
theme_minimal()+
scale_fill_viridis_b()+
labs(x= "Posterior for Mean Difference in Accuracy", y="", title = "Posterior Mean Difference Recipe1_Decision_Tree & Recipe3_Boosted_Trees")a2 <- acc_model_eval %>%
tidy() %>% mutate(model = fct_inorder(model)) %>%
filter(model %in% c("Recipe1_Boosted_Trees", "Recipe1_Decision_Tree")) %>%
ggplot(aes(x=posterior)) +
geom_histogram(bins = 50, col = "white", fill = "#73D055FF") +
theme_minimal()+
scale_colour_viridis_b() +
facet_wrap(~model, nrow = 2, ncol = 1) +
labs(title = "Comparison of Posterior Distributions of Model Recipe Combinations", x = expression(paste("Posterior for Mean Accuracy")), y = "")a2/a1

个体平均准确性后验分布及其差异(图片由作者提供)
通过管道将 mod_compare 对象传递给 summary 会生成以下输出
mod_compare %>% summary()
mean
0.001371989 **#Difference in means between posterior distributions**
probability
0.5842 **#Proportion of Posterior of Mean Difference > 0**
如平均差异的后验所证明的,平均准确度的各个工作流程后验分布之间的平均差异较小。均值分布的后验差有 58.4%在 0 以上,所以我们可以推断正差是真实存在的(虽然很轻微)。
我们可以用实际等价的概念做进一步的研究。
summary(mod_compare, size = 0.02)
pract_equiv
0.9975
效应大小是均值后验分布差异两侧的阈值[-0.02,0.02],pract_equiv 测量差异的大小。in 的意思是邮政。分布在这些阈值内。在我们的比较中,Boosted_Trees 在决策树的后验分布中占 99.75%。
我们可以为以下所有工作流完成此练习。
**#Pluck and modify underlying tibble from autoplot()**
autoplot(acc_model_eval, type = "ROPE", size = 0.02) %>%
pluck("data") %>%
mutate(rank = row_number(-pract_equiv)) %>%
arrange(rank) %>%
separate(model, into = c("Recipe", "Model_Type"), sep = "_", remove = F, extra = "merge") %>%
ggplot(aes(x=rank, y= pract_equiv, color = Model_Type, shape = Recipe)) +
geom_point(size = 5) +
theme_minimal() +
scale_colour_viridis_d() +
labs(y= "Practical Equivalance", x = "Workflow Rank", size = "Probability of Practical Equivalence", color = "Model Type", title = "Practical Equivalence of Workflow Sets", subtitle = "Calculated Using an Effect Size of 0.02")

效果大小为 0.02 的工作流的实际等效性(图片由作者提供)
我们已经获取了支持进行 ROPE(实际等价区域)计算的 autoplot 对象的数据,以便我们可以设计和显示更多的功能。这样,我们可以使用 Recipe1_Decision_Tree 作为基准,轻松地比较每个工作流。有几个工作流的性能可能与 Recipe1_Decision_Tree 一样好。
前两个候选树 Boosted_Trees 和 Decision_Tree 位于可解释性光谱的两端。Boosted Trees 在很大程度上是一种黑盒方法,相反决策树更容易理解,在这种情况下提供了最佳结果,因此我们将在使用 Recipe1_Decision_Tree 的基础上完成我们的工作流。
**#Pull Best Performing Hyperparameter Set From workflow_map Object**
best_result <- all_workflows %>%
pull_workflow_set_result("Recipe1_Decision_Tree") %>%
select_best(metric = "accuracy")**#Finalise Workflow Object With Best Parameters**
dt_wf <- all_workflows %>%
pull_workflow("Recipe1_Decision_Tree") %>%
finalize_workflow(best_result)**#Fit Workflow Object to Training Data and Predict Using Test Dataset**
dt_res <-
dt_wf %>%
fit(training(loan_split)) %>%
predict(new_data = testing(loan_split)) %>%
bind_cols(loan_test) %>%
mutate(.pred_class = fct_infreq(.pred_class),
Loan_Status = fct_infreq(Loan_Status))**#Calculate Accuracy of Prediction** accuracy(dt_res, truth = Loan_Status, estimate = .pred_class)

针对测试数据的模型准确性(图片由作者提供)

预测的混淆矩阵(图片由作者提供)
我们的最终模型已经成功地生成了一个非常强的结果,在测试数据集上的准确率为 84.68%。该模型在预测已批贷款的批准情况方面总体表现良好。然而,模型是批准贷款,由银行决定反对。这是一个奇怪的案例,显然,银行提供的贷款越多,收入潜力就越大,风险敞口也就越大。这表明数据集包含一些模型无法完全区分的不一致,并且根据训练集中的观察结果已经被“混淆”。
这为模型的可解释性提供了一个论据,在这个例子中,我们不能产生更多的数据,那么我们如何知道预测的基础呢?
**#Fit and Extract Fit from Workflow Object**
dt_wf_fit <-
dt_wf %>%
fit(training(loan_split))dt_fit <-
dt_wf_fit %>%
pull_workflow_fit()**#Generate Decision Tree Plot Using rpart.plot package**
rpart.plot::rpart.plot(dt_fit$fit)

模型决策的决策树图(图片由作者提供)
由于我们的模型简单到足以可视化和理解,一个树状图就足够了。
我们立即观察到,该决定完全基于信用历史的存在。该模型还错误地向申请人提供未知的信用历史(= 0),并拒绝没有信用历史的申请人(信用历史= -1)。上面提到的不一致是显而易见的,并不是每个有信用记录的人都能获得贷款,但这肯定会有帮助。
结论
你回到你的 CDO,解释你的发现。他们对准确性结果印象深刻,但现在企业必须权衡允许信用历史未知的应用程序违约的风险,以及否则可能产生的潜在收入。
本文试图解释 Tidymodels 生态系统中 workflow_sets 的强大功能。此外,我们已经探索了“没有免费的午餐”定理,即没有一个模型可以说是更好或最好的,因此为什么筛选许多模型是最佳实践。响应良好的模型类型也取决于所执行的特征工程。
感谢您阅读我的第二本出版物。如果你对 Tidymodels 感兴趣,我发布了一个介绍性项目,解释了构建回归模型时的每个核心包。
我要感谢 Julie Slige 和 Max Kuhn 在开发 Tidymodels 生态系统方面所做的出色工作。他们正在写的书可以在 https://www.tmwr.org/看到,这对我的理解很有帮助。
利用 Azure 文件共享在多台机器之间共享数据集,而无需为每台虚拟机下载
还创建了一个健壮的管道,通过使用 Azure Data Factory 将数据从 AWS S3 移动到 Azure 文件共享

洛伦佐·埃雷拉在 Unsplash 上的照片
动机:
机器学习领域一直存在一个问题,当我们有多个虚拟机用于训练时,我们必须下载每个虚拟机中的所有文件。这将占用虚拟机中的大量空间,我们必须为驻留在其中的相同数据集连接大型硬盘驱动器。 Azure 文件共享通过使用行业标准 SMB 协议在多个虚拟机之间共享存储驱动器,克服了这个问题。我还会写如何将数据从 AWS S3 直接移动到 Azure 文件共享中。所以不多说了,让我们开始吧。
缺陷 1:在 Azure Blob 存储上也支持一个 NFS 协议,但它是在预览版中:这意味着你不应该在生产中使用它,但你可以出于测试目的自由使用它。
先决条件:
- 一个 Azure 账号,启用了 Azure 存储账号和 Azure 虚拟机等服务。
- (可选) Azure Data Factory 服务和一个 AWS 账户与 S3 服务。
Azure 文件共享和虚拟机:
Azure 中的一切都必须在名为 Azure Resource Group 的 Azure 逻辑容器中创建。这些资源组帮助我们将所有内容都包含在一个容器中,以便在工作场所中进行进一步的工作。所以,让我们创建一个 Azure 资源组。搜索资源组,然后单击它。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而无需为每个虚拟机 1 下载。2021.JPEG 文件。
单击“新建”按钮后,您将被重定向到新页面。输入基本信息,然后点击“审核+创建”。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而无需为每台虚拟机下载 2。2021.JPEG 文件。
您已经创建了一个资源组。现在是时候创建一个包含 Azure 文件共享和两个 Azure 虚拟机的 Azure 存储帐户了。搜索 Azure 存储帐户。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,无需为每台虚拟机下载 3。2021.JPEG 文件。
点击之后,填写基本细节,点击‘审核+创建’。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而不需要为每个虚拟机 4 下载。2021.JPEG 文件。
对虚拟机也做同样的事情。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而无需为每台虚拟机下载 5。2021.JPEG 文件。
如果您想要创建两个虚拟机并测试它们,您需要重复这个过程两次。创建单个虚拟机也很好。输入完基本信息后,点击“查看+创建”

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,无需为每台虚拟机下载 6。2021.JPEG 文件。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,无需为每台虚拟机下载 7。2021.JPEG 文件。
现在,转到您的存储帐户并选择文件共享。添加这些基本信息,创建一个文件共享,然后点击“创建”。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,无需为每台虚拟机下载 8。2021.JPEG 文件。
出于测试目的,在文件共享中添加一个文件。SMB 协议在端口 445 上工作,因此我们需要在我们的虚拟机上打开该端口。让我们转到您刚刚创建的虚拟机,然后选择“Networking”。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而无需为每台虚拟机下载 9。2021.JPEG 文件。
点击“添加入站端口文件”,在两个虚拟机上添加入站规则 445。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而无需为每个虚拟机 10 下载。2021.JPEG 文件。
如上填写详细信息,然后点击“添加”。现在,打开虚拟机,因为是时候将文件共享装载到虚拟机中了。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而无需为每个虚拟机 11 下载。2021.JPEG 文件。
现在将这些命令输入到您的虚拟机中。
苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个 VM 12 下载。2021.JPEG 文件。
在“az login”一行,您必须对您的虚拟机进行身份验证,才能访问存储帐户。有许多方法可以做到这一点,但' az 登录'是快速和简单的方法。输入命令后,会出现一个提示符

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而无需为每个虚拟机 13 下载。2021.JPEG 文件。
转到您登录到门户的浏览器,粘贴链接,然后输入代码。

苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个 VM 14 下载。2021.JPEG 文件。
完成后,您将通过身份验证,现在可以输入其余的命令了。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而无需为每个虚拟机 15 下载。2021.JPEG 文件。
如你所见,你有你想要的文件。要测试它,请创建一个文件并检查存储帐户。对于我来说,我将在虚拟机上创建一个名为“能力名称. csv”的 CSV 文件。

苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个虚拟机 16 下载。2021.JPEG 文件。
如您所见,文件已创建并显示在这里。我们做到了。我们创建了一个文件共享,并与我们的两个虚拟机共享。
Azure 数据工厂和 AWS S3(可选):
S3 真的很便宜,所以这就是为什么大多数数据都在那里,但你有你的 Azure 上的生产工作负载。Azure Data Factory 是在他们的两个服务之间创建管道来移动数据的完美工具。因此,让我们通过键入“数据工厂”并创建服务来开始吧。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而无需为每个虚拟机 17 下载。2021.JPEG 文件。
添加这些基本信息,然后点击“查看+创建”。

苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个 VM 18 下载。2021.JPEG 文件。
转到新创建的服务,点击“作者和监控”

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而无需为每个虚拟机 19 下载。2021.JPEG 文件。
点击“复制数据”

苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个虚拟机 20 下载。2021.JPEG 文件。
给它一个名字,其余的作为默认。

苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个虚拟机 21 下载。2021.JPEG 文件。
点击“创建新连接”,然后选择“亚马逊 S3”

苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个 VM 22 下载。2021.JPEG 文件。
现在,让我们去 S3。在单独的选项卡上打开 AWS 管理控制台,然后转到 IAM 用户并添加一个用户。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而不需要为每个虚拟机 23 下载。2021.JPEG 文件。
给它一个名字,并允许编程访问。

苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个虚拟机 24 下载。2021.JPEG 文件。
选择“直接附加现有策略”,然后选择“AmazonS3FullAccess”并创建用户。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而无需为每个虚拟机 25 下载。2021.JPEG 文件。
现在,系统会提示您使用您的访问密钥 ID 和秘密访问密钥进入新页面。将这两把钥匙复制到一个安全的地方,因为这是你唯一能看到它们的时间。
现在选择一个存储桶,将文件从 S3 复制到文件共享。我已经有了一个名为“copytofileshare”的存储桶,它包含多个 CSV 文件。我们将把这个桶复制到文件共享中。

苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个虚拟机 26 下载。2021.JPEG 文件。
转到你的 azure 选项卡,然后在空白处添加这些密钥 id 和秘密访问密钥,并点击“测试连接”。您应该可以连接到您的 S3 帐户。

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而不需要为每个虚拟机 27 下载。2021.JPEG 文件。
测试成功后,点击“创建”。现在,在下一个提示中,您选择想要复制的 bucket。

苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个虚拟机 28 下载。2021.JPEG 文件。
现在我们必须创建目的地。选择“Azure 文件存储”,然后点击“继续”

苏拉布什雷斯塔。利用 Azure 文件共享在多台机器之间共享数据集,而无需为每个虚拟机 29 下载。2021.JPEG 文件。
选择您的订阅帐户并测试您的连接。

苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个虚拟机 30 下载。2021.JPEG 文件。
点击 next,保留默认值,您可以看到管道将立即运行。

苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个虚拟机 31 下载。2021.JPEG 文件。
现在,检查您的存储帐户以及您的虚拟机,看这些文件是否已复制。

苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个 VM 32 下载。2021.JPEG 文件。

苏拉布什雷斯塔。利用 Azure 文件共享在多个机器之间共享数据集,而不需要为每个虚拟机下载 33。2021.JPEG 文件。
结论:
我们做到了。我们成功地将数据从 AWS S3 移动到 Azure 文件共享,并使用该文件共享向 Azure 虚拟机提供文件。现在,这些虚拟机可以利用这些 CSV 数据并在其上进行训练,甚至无需从 Azure 文件共享或 AWS S3 下载。
这只是一个从 S3 加载文件到文件共享的例子。数据工厂有许多服务,您可以从中将数据上传到文件共享。然而,选择是无穷无尽的,你想做什么取决于你自己。如果你遇到任何问题或难以遵循这些步骤,请在下面评论这篇文章或在 tsulabh4@gmail.com 给我发消息。你也可以在 Linkedin 和 GitHub 上和我联系。
资源:
[1]挂载文件共享:https://docs . Microsoft . com/en-us/azure/storage/files/storage-how-to-use-files-Linux
[2] Azure 数据工厂:https://docs.microsoft.com/en-us/azure/data-factory/
使用模型生成器和 AutoML 在 Microsoft ML 中创建销售线索决策和销售线索评分模型。网
创建、训练、评估和使用机器学习模型的逐步指南。网

Rodolfo Clix 摄于 Pexels
最近,我写了一篇文章,解释了 ONNX 格式在将 Scikit-learn 线索评分机器学习模型集成到。网络生态系统。我描述了一种将基于 Python 的回归模型部署为 Microsoft Azure 函数的可能方式。这是一个适用于将经过训练的模型集成为 Web API 或控制台应用程序的一部分的过程。我提到的是有机会使用这种方法来弥合不同数据科学和应用程序开发平台之间的技术差异,在这种情况下,目标是。NET 跨平台框架。谈论微软。NET,这是我每天专业使用的技术,在这篇文章中,我想揭示该框架的本机机器学习潜力,更具体地说,是 ML.NET 的。
因为我已经在。NET,我将继续介绍线索决策解决方案的实现,作为使用 KNIME 平台设计和实现的线索决策解决方案的延续。正如文章中提到的,它在概念上遵循相同的方法和监督 ML 思想,不同之处仅在于基于分类的预测策略。除此之外,我还将介绍和利用线索评分的概念,作为所创建模型的预测评估的一部分。
*注意:本文的解决方案设计和源代码经过了简化,以强调核心概念和集成策略。尽管如此,它仍然是一种功能齐全的方法,用于在真实测试或生产部署的原型和应用环境中培训、构建、评估和实施基于预测决策/监督驱动的模型。
**注意:我将利用由自动机器学习或 AutoML 支持的 Model 模型生成器,使用直观且用户友好的图形化 Visual Studio 扩展来设计和构建解决方案。关于构建数据处理和转换管道、定制训练和测试数据分割、应用交叉验证的概念以及模型性能和评估的解释的更详细的技术超出了本文的范围,可以通过 ML.NET API 进行参考。
什么是 ML。网?
ML。NET 代表了一个开放源代码和跨平台的机器学习框架,可以合并和使用。NET 相关的应用程序。它提供了对各种流行用例的支持,能够使用已经存储的应用程序数据在不同的业务领域中构建不同的模型。其核心概念是围绕模型构建器设计的,这是一种工具机制,指定了将输入数据转换为预测所需的步骤。作为补充,它还使用了自动化 ML,这是一个被称为 AutoML 的概念,它包装并简化了接口,提供自动生成的代码项目来描述输入数据和消费模型。除此之外,另一个基础模块ML.NET CLI支持机器学习的集成。总的来说,ML.NET 使用 C#和 F#编程语言支持训练、构建和评估机器学习模型。
环境准备
跟随最新的微软趋势和公告, Visual Studio 2022 和最终(目前为止)版本。NET 6 将于 2021 年 11 月正式发售。传统上,他们发布预览版本供社区使用,以完善主要版本。在这方面,我很高兴使用 Microsoft Visual Studio 社区预览版 (版本 17.0.0,预览版 4.1) 和当前的进行实际演示。NET 6 预览版框架。

作者图片
*注意:使用 ML.NET 模型生成器的先决条件是 Visual Studio 2019 16.10.4 或更高版本/。NET Core 3.1 SDK 或更高版本。
创建解决方案
我将从头开始演示,创建一个新的解决方案“lead generation”,由名为“lead decision”的单一控制台应用程序组成,在这个过程中,选择非常重要。NET 6(预览版)作为目标框架。完成此设置将导致在新的。NET 6 模板。

作者图片

作者图片

作者图片
ML。净延伸
在 Visual Studio 中安装和启用 Model 模型生成器可以使用 Visual Studio 安装程序进行配置,并相应地修改当前版本的安装。一般来说,ML.NET 组件放置在单个组件之下,更准确地说是在中。净截面。

作者图片

作者图片
此外,ML.NET 模型构建器 UI 扩展工具应该安装在主菜单栏上的扩展管理区域。

作者图片
资料组
由于本文将遵循 lead decision predictive ML 模型的设计和实现,因此我将利用我上一篇文章中使用的相同的分析、处理和缩放数据集的优势,使用 KNIME 平台构建模型。在这方面,最初的线索评分原始数据集在 Kaggle 上公开。
ML。NET 模型生成器安装程序
如前所述,模型构建器代表了一个非常用户友好的图形工具扩展,用于管理 Visual Studio 中的机器学习过程。它的主要特点是 mbconfig 配置文件,它管理当前会话并跟踪 ML 模型构建每个阶段的特定变更。提供完整 ML 体验的模型构建器可以按照下面的步骤添加到创建的项目中。

作者图片

作者图片
添加机器学习支持将实际打开模型构建器细节,我将在其中浏览所有步骤,以便构建和评估销售线索决策模型。
场景选择
Model Builder 即将配备大量 不同的内置场景用于机器学习应用。事实上,每个场景都映射到不同的学习方法,这取决于特定的业务领域用例。由于我正在构建销售线索决策预测模型,我将根据分类相关算法选择数据分类场景。

作者图片
培训环境
如图所示,数据分类场景只有本地支持的,这意味着模型训练将在我的本地机器上执行。因此,考虑到目前不支持 Azure 和 GPU 训练模式,这里唯一有效的选择仍然是具有 CPU 能力的本地环境。

作者图片
导入数据
有两种导入数据集的不同选项,使用文件或通过来自 SQL Server 实例的数据源。考虑到我已经从 Jupyter 笔记本中导出了 csv 文件,我将浏览本地系统路径来导入它。****

作者图片
如屏幕截图所示,成功导入的结果是数据集预览,然后是附加的数据配置选项。就此而言,下一步是选择‘已转换’作为标签或目标列,同时打开高级数据选项以便检查和配置其他功能。该配置包括将所有其他列设置为单个数字特征,不包括与模型构建无关的‘column 1’(保存与特定行号相关的数字信息)。

作者图片
注:代表‘未转换’导联*** ,代表‘转换’导联 。在数据处理过程中, 【已转换】列也可以作为分类 对齐,这样分类描述就可以发生,而不是处理整数(转换成字符串)。**
火车模型
下一步是训练配置,我将配置训练模型的时间。事实上,这是一个自动化的过程,模型构建者利用 AutoML 的优势和灵活性来研究和应用许多具有各种参数的不同模型。因此,通过指定更多的训练时间来覆盖 10 秒的默认时间间隔,将揭示探索更多模型的可能性,从而最大化检索更准确的最终模型的机会。据此,我将时间间隔设置为 900 秒(15 分钟)。这里值得一提的是,官方的微软指南根据数据集的大小给出了推荐的时间间隔。

作者图片
训练过程和不同模型的选择在输出窗口中可用,在这里每个模型选择以及迭代精度都被呈现。

作者图片

作者图片
最后,当训练过程成功完成时,模型构建器是一个完整的生成实验结果摘要,其格式如下图所示。

作者图片
此外,我可以在列车区域查看流程的输出,在这里最佳性能模型被呈现。

作者图片
在此特定场景中,LightGbmMulti(lightgbmmultistrainer 类)被评估为提供最佳模型准确性的最佳算法。
评估模型
再往前走,我有机会评估最佳模型精度。此外,值得强调的是,我有与模型交互的可能性,这意味着我可以提供一些迄今为止尚未看到的数据,并立即查看预测。****

作者图片
消费模型
完成评估流程后,我将继续进入消费模型屏幕。如下图所示,在感兴趣的最终应用程序中,有一个代码片段用于显式模型集成和完善。最终应用程序可以是我已经创建的控制台应用程序,但是我还将展示如何将模型集成到生成的 Web API 中(使用添加 Web API 解决方案选项)。****

作者图片
我将在名为leaddositionmodel _ API的解决方案中生成 Web API 应用程序作为单独的项目。

作者图片

作者图片
后续步骤
作为模型构建之旅的总结,还有两个额外的可能性,即“部署您的模型”和“改进模型”部分。事实上,它们目前被实现为一个重定向按钮,用于处理官方文档,在那里可以找到与模型改进和部署相关的更多细节。

作者图片
控制台应用程序触摸
考虑到在模型构建器的消费步骤中集成和消费模型所生成的代码片段,我将将其复制到 Program.cs 文件中。
*注意:即使在 UI 工具关闭后,也可以访问 mbconfig 配置文件。
在注释了预定义的控制台应用程序模板代码并粘贴了从模型构建器生成的代码之后,我只需要引用生成实际模型输入类的 LeadDecision 名称空间。

作者图片
在这种情况下,利用 Visual Studio 内置调试器,我将启动应用程序并检查结果输出对象的类型和内容。

作者图片
因此,可以看出,结果输出表示模型输出对象,包括预测和成功或不成功的线索转换的分数或概率。这明确意味着我们可以使用这种方法来创建和分析机器学习模型,用于预测性线索决策和线索评分系统。

作者图片
在将模型集成到之前生成的 Web API 之前,我想展示并解释一下leaddocisionmodel . MB config结构。

作者图片
****leadecisionmodel . training . cs 文件由生成的机器学习模型组成,包括选择的算法及其配置参数、输入特征集和对预测标签的配置。如下面的屏幕截图所示,一切都被包装在管道转换中,该转换在执行管道和拟合模型的方法中引用。

作者图片
另一方面,leaddositionmodel . cons option . cs文件由描述输入特征的 ModelInput 类、 ModelOutput 类(包含预测输出结果(预测和得分)以及覆盖模型预测功能的预测引擎组成。最后一个是使用 MLContext 类和it transformer 接口生成的,它可以用于更高级的定制 ML 解决方案。

作者图片

作者图片
最后,leadecisionmodel . zip 档案包含创建和训练机器学习模型所需的所有文件。从前面的屏幕截图可以看出,预测引擎构造中引用了 zip 存档的路径,基于监督的预测就是从这里进行的。****

作者图片
Web API 触摸
让我们回忆一下,Web API 项目是使用模型构建器 UI 工具中的添加到解决方案选项自动生成的。因此,它被创建为 LeadGeneration solution 中的一个独立项目,自动合并相同的文件以支持所创建模型的集成。一般来说,用于销售线索决策和销售线索评分的 ML 模型现在是 API 项目的组成部分。考虑到。NET 目标框架,Web API 现在遵循新的轻量级最小设计,由下面截图中的ML.NET 模型生成器生成。****

作者图片
我将启动 API 项目并尝试调用预测端点,因为 MapPost 功能正在定义一个暴露其路由和处理程序的。就此而言,我将继续使用 Postman API 平台来准备端点 url 和 POST 请求主体。
*注意:绑定 url 和端口是 launchSettings.json 配置文件中 iisSettings 配置部分的一部分。

作者图片

作者图片
predict API 调用成功,检索回了潜在客户模型的预测/决策和分数。
最后的话
在这篇文章中,我介绍了一个详细的逐步过程,该过程利用 ML.NET 框架内的模型构建器 AutoML 的优势来构建线索决策和线索评分预测机器学习模型。这个机器学习框架是。NET 生态系统,并且可以非常容易地用于构建与特定业务领域任务和数据相关的不同用例场景。如参考文章中所述,该策略是处理和解释营销和销售数据的最广泛系统方法的一部分,旨在建立更具洞察力和实用性的销售线索挖掘流程。
在中本机工作。NET 生态系统,使用 ML.NET 和自动化模型生成器 UI 工具,对于热衷于将机器学习集成到应用程序中的应用程序开发人员来说,可能是一个优势。此外,ML.NET 为在机器学习和人工智能方面经验丰富的软件开发人员提供了一个完整的设计框架。
我将 ML.NET 与基于 Python 的 Scikit-learn ML 库、R Studio 和 KNIME 平台结合使用,并尝试最大化所有不同可能性和内置算法的全部潜力和灵活性。从我的角度来看,没有一个单一的和最好的环境或库,但一切都取决于具体的业务用例和领域。
— — — — — — — — — — — —
感谢您阅读文章。我相信它在涵盖使用微软 ML.NET 框架构建和评估机器学习模型的所有方面方面是有建设性的和全面的。
目前,我正致力于在生物信息学中利用机器学习算法,更准确地说,是理解微生物组在癌症诊断和治疗中的作用。因此,我将前面提到的所有平台的潜力结合起来,探索数据和知识的全部潜力及其背后的洞察力。
欢迎随时开始讨论,分享你在这方面的想法和经验。
如果您能花时间评论、分享文章并联系我们进行进一步的讨论和潜在的合作,我将不胜感激。
【https://www.linkedin.com】最初发表于https://www.linkedin.com/pulse/utilizing-model-builder-automl-creating-lead-decision-miodrag-cekikj/。****
v 代表数据
数据的六个 v,以及它们对真实使用情形的重要性

WolvesOfTheTwilight 衍生作品:征服者,公共领域,通过维基共享
数据科学、机器学习和所有形式的人工智能都以数据为核心。当涉及到这些学科时,我们经常将大部分注意力集中在公式或代码上,这对那些知识领域的研究人员来说是有意义的。但大多数专业人士和爱好者都是数据科学和机器学习的实践者,而不是研究人员。
对于一个从业者来说,公式、代码、必要的平台大多是借来的。我们可以在 Github 上或者网上一些代码分享空间找到代码。我们可以阅读和研究书籍或博客中的公式。我们必须掌握我们选择的平台的接口机制。对于从业者来说,在这些学科中取得成功的挑战存在于其他地方——存在于数据中。
在当今数据驱动的世界中,数据是最具挑战性的方面。我们的数据经常是不完整的、肮脏的、损坏的,或者在一些罕见的情况下,根本没有收集。为数据科学或人工智能的工作准备数据需要大量的工作,而不是数据科学或 ML 本身。
几十年来,大多数公司都在平庸的数据清理中艰难前行。它没有引起控制预算字符串的执行者的注意,因为坏数据的不良影响在我们草率的决策过程中消失了。从历史上看,唯一值得注意的数据问题是基于简单的数据输入/数据输出测试。
如果您将数据输入到软件中,下次尝试调用该数据时,数据必须是相同的。如果不是,那显然是错的。但是今天,数据比简单的进出测试更加庞大。可以收集并存储大量数据以备后用。或者记录用户行为,然后进行分析。输出只是分析。“什么进什么出”的放松钱包的方法不再涵盖我们数据存储中最有价值的部分。
当一家公司决定将它一直在挖掘的数据推入一个标有 ML 的黑盒时,这个神奇过程的另一端的结果完全取决于输入模型的数据的正确性。数据是人工智能工作和数据科学工作的关键。正确准备我们的数据是成功的关键。
由于数据对我们的工作至关重要,所以让我们考虑一下在评估我们使用的数据时所涉及的因素。这些术语通常被称为六个 v,它们为我们提供了数据的描述,并告诉我们如何选择正确使用数据所需的平台和流程。
根据你问的人的不同,v 的实际数量也不同。我们将坚持使用基本的 6V 版本。
- Volume: 表示数据量。我们可能拥有数百万亿字节的数据,或者正在向月球发射数十亿字节的数据供您使用。当前和预计的数据总量将有助于决定我们在工作中应该考虑的存储解决方案类型。
- 速度:我们需要处理数据以保持其价值的速度。我曾经与那些每月只需要处理一次新数据的公司合作过。其他人每天都需要它,进入系统的新数据就像有人打开了消防水管。每天处理 1000 万个新行需要一个计划。速度将从头到尾决定你的过程:将新数据引入你的系统,处理它,分析它,并报告它。
- 多样性:数据有多种形式和来源。在今天的市场中,数据将来自内部系统、合作伙伴系统、公共系统和大量设备。如此多的来源也意味着数据结构方法的大杂烩。有些将是结构良好和高度结构化。这种数据可能来自内部数据库。其他表单可能完全没有组织结构,比如您需要解析和处理的一段客户反馈。此外,我们的数据结构将生活在这两个极端之间的任何地方。多样性的混合决定了我们的 ETL 过程——提取——转换——加载的复杂性。
- 准确性:准确性与数据的易错性有关。这可能包括偏差、噪声、异常、真实性以及最终的可靠性和可信度。错误是数据工程师存在的祸根。有些数据比其他数据更容易出错。了解我们的数据的错误倾向将为我们的测试方法提供信息。需要进行哪些测试来确保我们模型中使用的数据的准确性?这是理解我们数据真实性的最终结果。
- 价值:如果我们不能从数据中提取价值,那就没有意义。我们能从哪些数据中获得什么价值?这是我最喜欢的 Vs 之一。这个石蕊测试可以用来减少数据集的大小。如果我们的源系统中的数据不能产生价值,我们应该消除它。如果它想把它存储在大型数据库的某个地方,就让他们去吧。但是我们会在我们的数据科学家和机器学习算法使用的仓库中删除这些数据。
- 可变性:您的数据有效期多长?你应该保留它多长时间?它的意义或形状有变化吗?与称量数据的价值类似,确定数据的保存期限也可以减少我们管理的数据集。有时时间会使数据变得无用。其他时候,陈旧的数据会改变我们感兴趣的分析对象。最近的销售在每日或者每小时的水平上都很重要——谷物。但是五年前的销售可能只关系到一个月的谷物。
虽然这六个 v 只是数据管理的开始,但它们是绝对必要的考虑因素。如果我们在数据科学或人工智能的道路上前进时,只有一桶未经称重、计数、分类、筛选和在显微镜下分析的数据,我们就在为自己的失败做准备。
我们可以认为这类似于在快餐连锁店吃一顿饭。想象一下食物被扔在一个内衬纸的泡沫聚苯乙烯容器里。这是一个烂摊子,很难兼顾。现在把它比作在五星级餐厅用餐。这些菜是用精美的餐具一次上一道,以漂亮的颜色和位置呈现在盘子上。
然而,食物类比和真实生活数据之间有一个区别。不管是在泡沫聚苯乙烯还是高档餐厅吃的食物,最终都会完成任务。但是准备不充分的数据不会提供与准备充分的数据相同的结果。所以,请让我们花点时间从头开始评估我们的数据。
杆蓖麻 帮助企业获得分析权!他帮助国际组织和小型企业改善他们的数据分析、数据科学、技术战略和技术领导力。除了咨询,Rod 还喜欢公开演讲、教学和写作。你可以在 rodcastor.com**和通过他的 邮件列表 了解更多关于 Rod 和他的工作。
v 测度:同质的完全聚类

萨姆·穆卡达姆在 Unsplash 上拍摄的照片
有意义的分数
聚类是一种机器学习技术,涉及数据点的分组。给定一组数据点,我们可以使用聚类算法将每个数据样本分类到特定的组(簇)中。
聚类是无监督学习的一种方法,是许多领域使用的统计数据分析的常用技术。数据科学家希望同一聚类中的样本具有相似的属性、特征或行为。例如,可以对癌症样本进行聚类,希望同一组中的样本属于同一癌症亚型[2]。
一旦聚类运行,人们必须定性地评估输出的优度。首先,验证分组是否真的有意义,而且有分数可以作为评估不同模型并选择最佳模型的代理。
有许多不同的分数,每一个都有它的优点和缺点。
https://scikit-learn.org/stable/modules/clustering.html#clustering-evaluation
其中一个度量是所谓的 V-measure(或归一化互信息)得分。这种方法的一个优点是它可以分解成两个容易可视化的指标。
标准化互信息
这个分数可以解释为另外两个测量值的平均值:同质性和完整性。
同种
同质性衡量一个聚类中样本的相似程度。它是用香农熵定义的。

同质性。图片作者。
给定 H(C|K)公式。

很容易理解为什么会这样。如果观察项 H(C|K ),它包含 n𝒸ₖ / nₖ,其代表在聚类kt31】中标记为 c 的样本数量与聚类kt35】中样本总数之间的比率。****
当聚类k中的所有样本都具有相同标号c时,同质性等于 1 。****
注意,在 c 和 k ,上有一个和,哪个是包含特定标签的聚类并不重要,至少有一个就足够了。这在运行输出与标注无关的无监督方法时非常有用。
这是一个同构集群的例子。所有三个聚类只包含一种颜色(特征),换句话说,同一聚类中的所有样本共享它们的标签。

同构集群的示例。图片作者。
无论如何,这种情况不是最佳的,因为橙色样本被分成不同的群。在本例中,特征是不完整的。
完全
尽管完整性度量了聚类算法将多少相似的样本放在一起。

图片作者。
很容易理解为什么会这样。如果观察术语 H(K|C ),它包含 n𝒸ₖ / n𝒸,其代表在聚类k中标记为 c 的样本数量与标记为ct29】的样本总数之间的比率。****
当所有种类为c的样本都被分配到同一个聚类k时,完整性等于 1。****
这是一个完整聚类的例子。每种颜色只存在于一组中。在这种情况下,具有相同属性的所有点被分类在一起。请注意,完整并不意味着同质,事实上在每个集群中有多种颜色。

完整聚类示例。图片作者。
现在我们已经了解了这两个指标,很明显,一个最佳算法将能够获得一个既同质又完全的分区。在这种情况下,具有特定颜色的所有样本被放在一个聚类中,并且该聚类仅包含它们。如果算法是无监督的,那么很明显,利用这个输出,它已经正确地学习了特征。

最佳聚类的示例。图片作者。
否则,最差的聚类可能是既不是同质的也不是完全的。在这种情况下,每个聚类包含多个标签,并且每个标签被分配给多个聚类。在这种情况下,两个测量值都是零。

不良聚类的示例。图片作者。
标准化互信息
最后,为了获得我们的聚类算法的良好性的度量,我们可以考虑在同质性和完备性之间的调和平均值,并且获得 V 度量或归一化互信息(NMI)【1】。

标准化互信息。图片作者。
这个分数是一个介于 0-1 之间的度量值,它实际上量化了聚类分区的好坏。实际上,它要求同时最大化同质性 h 和完备性 c (当 h 和 c 都为 1 时,NMI 为 1)。此外,如果聚类不满足两个条件中的任何一个,NMI 将为零。
这种度量有很多,但是由于它在同质性和完备性两个度量中的分解,它的可解释性是清晰和直观。
sklearn
现在,您已经执行了聚类,并希望估计 NMI。Sci-kit learn 已经实现了,所以估计它真的很容易。
**https://scikit-learn.org/stable/modules/generated/sklearn.metrics.v_measure_score.html
这里有一个简单的例子。从这个例子中还可以清楚地看出,分数不依赖于标签的名称,正如前面所讨论的,对于每个标签排列都是一样的。
**from** **sklearn.metrics.cluster** **import** v_measure_score
>>> v_measure_score([0, 0, 1, 1], [0, 0, 1, 1])
1.0
**>>>** v_measure_score([0, 0, 1, 1], [1, 1, 0, 0])
1.0
参考
- 赫希伯格;V-Measure:一种基于条件熵的外部聚类评估。 (2007 年),《欧洲自然语言文学会议录》。
- 瓦莱,f;奥塞拉,m;Caselle,m .TCGA 乳腺癌和肺癌转录组数据的主题建模分析。(2020)癌症
V2:怎样才能成为一名优秀的数据分析师?
你需要的不仅仅是阅读和视频

照片由 Katrina Wright 通过 Unsplash 拍摄
在最近的一篇文章中,我谈到了如何成为一名伟大的数据分析师,同时更多地关注特质或特征,并加入了一些硬技能/技术。在本文中,我将更多地关注可以帮助你的日常习惯,并讨论它们对你成长为“世界顶级飞行数据分析师,克雷格!”的影响
提醒一下,根据我的目标,这些信息对我自己有用。你必须首先解决你所寻求的角色/日常职责。心中有了目标,找到最佳路径就容易多了。
练习提问
你可能会问自己,“为什么我需要练习提问?”。最疯狂的是,你刚刚问了一个问题!真快。很明显,你在这方面经验丰富。
本该听到这个的豆荚听到了。它会在需要的时候发挥作用。伟大的哲学家伦道夫·杜普利
道格·罗斯(Doug Rose)在 LinkedIn 上有一门我最喜欢的课程,名为“学习数据科学:提出好问题”。我强烈推荐任何使用数据的人使用它,无论是编码的人还是使用生成的报告的人,或者是定期查看可视化的利益相关者。
我最喜欢的一些问题:
- 什么会打破这个?你要担心的不仅仅是编码错误。逻辑错误也存在。
- 这将打破什么?如前一篇文章所述,不要只是为了在某个特定的关键绩效指标上节省几个便士,就建议与一家销量飙升的新分销商断绝关系。
- 还有什么能对此产生影响?如果时间允许,去摘树上更高的水果。
我们都可以提问,但是这些问题是封闭还是鼓励伟大的想法/后续问题呢?争取后者。
做同样的事情,但是用不同的方式
学习编码时,很容易观看各种视频或阅读大量书籍,这是一个很好的开始。你可以很容易地找到在线教程,在那里你甚至可以输入一些代码来对照所提的问题检查结果。寻找那些不是寻找填空方法的项目。
你被期望成为某种类型的魔术师。一个你“鹦鹉学舌”式回答的学习环境只能带你走这么远。努力变得更像《乐高电影》中的埃米特,成为一名建筑大师。当你更有能力以一种不需要逐步说明/内置功能的方式工作时,你就更有能力解决更复杂的情况。
我并不是说不要使用内置函数。我只是想让你知道 MacGyver 用鞋带、泡泡糖和晒干的草屑能做的比那些只知道如何遵循确切指示的人用瑞士军刀能做的更多。
一些有趣的做法是复制内置的 Python 函数。这种想法很疯狂,但是你可以通过多种途径在一个列表中总结出 3 个数字。寻找前 X 个质数也是一件有趣的事情。
努力解决这些问题,然后在获得更多经验后再回来,用不同的方法解决它们。请注意下面关于给你的作品添加一些注释的部分。当你重新考虑这些问题时,这会节省你一些时间。
谈谈你的工作
生活在你自己的思想中是非常容易和普遍的。你知道你在说什么。别人能顺着你的思路吗?试试看。
试着告诉你信任圈里的人你正在做的一个项目,以及你希望证明或否定/发现什么。谈谈你是如何与理解你所用语言的人一起编写代码的。回答问题是否会延长对话/深入挖掘,还是快速回答“试图结束对话,因为我迷路了”类型的问题?
回到问伟大的问题,问一个同事如何调试一些东西。希望你能够用“为什么这段代码不能工作?”来解决这个问题。接近。能够解释你的目标是什么,以及到目前为止你已经采取的步骤可以节省大量的时间。
如果你不能简单地解释它,你就不够了解它。—超级聪明的创意人,阿尔伯特·爱因斯坦

照片由迈克·厄斯金通过 Unsplash
文件
做这个实验:
- 找一个有趣的、有点复杂的小问题来解决。
- 写一堆代码来解决它并保存两个版本:一个有文档,一个没有。
- 在这个问题中计划你接下来的步骤,在大约一个月内解决。
- 等待指定的时间,然后返回并尝试添加/更改该代码,从没有文档的代码开始。
- 踢自己,诅咒世界。
- 松一口气,因为您有一个带文档的版本。
在真实的数据世界中,会发生很多事情。如果你指望记住你在编码过程中所经历的过程,祝你好运。如果你希望记住你过程中的“为什么”,就要准备好面对一系列的麻烦。
学会记录并记住在最终输出中包括日期或谁指导了变更。一旦一些人离开了组织,并且发现了一个 bug,你会惊讶地发现最初的过程中很少有人提供一些见解。
休息一下
没有单一的休息“类型”。你可以散步 15 分钟,让你的大脑稍微清醒一下,或者想一个不同的方法来解决那些难倒你的事情。你也可以花一天或几天时间,从业余项目和学习中解脱出来。双管齐下。
就像你身体里的肌肉一样,你的大脑不可能整天都处于 100%的状态。我的一个朋友在 Coursera 上推荐了一门很棒的课程,名为“学会如何学习”,作者是芭芭拉·奥克利,非常棒。本课程提到休息和避免“把一大堆东西塞进一节课”的方法。
给自己打气!
也许你要去做一个报告,或者只是对最近的一个项目感觉不太好。尽你所能去改变它。
也许你感觉很好,只是想要额外的推动。
我有穆罕默德·阿里的“我要让你看看我有多棒”的演讲,直到能够匹配音调和节奏。
玩得开心
大多数人在唱那首关于生日的歌时,不想要一个感觉像“办公空间”的环境。实际上,我在工作聚会上也用同样单调/沉闷的感觉唱着那首歌!
开些书呆子的玩笑。他们可能非常有创造力和有趣。
最终想法
数据是一个巨大的世界。有大量的读物着眼于提高你的技能。我希望你能够对数据的“其他”部分有一点了解。一如既往,继续学习!
想要找到第一篇关注在数据分析师领域有所帮助的特质的文章吗?
想知道如何在数据职业生涯的早期展示自己的价值吗?
疫苗功效(以新冠肺炎为例)—贝叶斯后验 VE/CI 计算
疫苗效力的贝叶斯统计分析和选定新冠肺炎疫苗的计算概述。
有趣的是,辉瑞/BioNTech 对其新冠肺炎疫苗的主要终点进行了贝叶斯分析。去年已经有一些关于这方面的文章了(参见下面的参考资料部分),所以我不会重复所有的内容。这篇文章的目的是提供(1)用于评估疫苗效力的方法的概述,(2)贝叶斯统计分析的更多细节,包括为了完整性和完全概括报告的结果而对监测时间进行的调整(虽然很小,但在辉瑞/BioNTech 研究中使用了),以及(3)采用这种方法分析其他选择的新冠肺炎疫苗(最初使用 frequentist 方法)。
疫苗效力
我们测量疫苗效力的方法定义如下:

R 可以是风险比(RR,风险比);比率(IRR,发病率比率);或危害(HR,危害比)。由于这些比率,我们认为疫苗效力是一个相对的衡量标准——与未接种组相比,接种组的感染或疾病相对减少了多少。VE 为 90%意味着接种疫苗组的病例比安慰剂组少 90%。我们将使用下标 v 和 p 分别表示接种疫苗组和安慰剂组;但是很明显,这个讨论适用于任何两个治疗组之间的比较——不一定是安慰剂。
带 RR

其中 Nv 和 Np 分别是接种疫苗组和安慰剂组的参与者总数。
内含内部收益率

其中 Tᵥ 和 Tₚ 分别为接种疫苗组和安慰剂组的时间-人年。
带 HR

其中 λᵥ 和 λₚ 分别是接种疫苗组和安慰剂组的风险率。这衡量了感染风险的相对减少。风险比可以通过例如 Cox 回归模型来估计。
选择新冠肺炎疫苗
考虑到上述措施,我们将简要强调选定的新冠肺炎疫苗及其方案特征。我们不会详细讨论纳入/排除标准(请参考实际方案)——以下信息仅用于强调每种疫苗的关键参数。作为参考,请注意世卫组织和 FDA 对 VE 的建议是,中期分析调整 VE 的置信区间的下限应大于 30%,VE 的点估计值至少为 50% [1]。

新冠肺炎疫苗试验选择方案特点。按作者分类的表格。
新冠肺炎确诊病例的构成,包括监测的开始时间,在不同的试验中是不同的。最值得注意的是,

新冠肺炎疫苗试验主要终点定义。按作者分类的表格
贝叶斯统计分析
定义
虽然疫苗效力是我们感兴趣的衡量标准,但我们在统计分析中关注的参数是 θ ,定义为病例率——接种疫苗组中总病例的比例。

从 VE = 1-(cᵥ/tᵥ)(cₚ/tₚ),我们可以推导出 θ 的表达式,以及 ve 关于 θ 的表达式,

其中 r = Tᵥ / Tₚ ,接种组与安慰剂组的人次比。
如果 Tᵥ 和 Tₚ 相等(即两组之间的随访次数和大小相等),我们看到上面的等式简化为:

因此,我们看到 r 是两组间监测时间差异的调整因子。我们还可以看到 θ 与 VE 的关系,

VE 与 θ的关系。图片作者。
当我们的疫苗效力为零时, θ 为 0.5,这意味着接种疫苗组的病例数并不比安慰剂组好(实际上两者是相同的病例数)。
后验计算
在贝叶斯框架中,我们想知道后验 P(VE>0.3| x ,其中 x 是观测数据(即 c ᵥ 和 c ₚ )。然而,实际上我们并没有直接对虚拟企业建模。相反,根据我们上面的定义,我们将基于案例率 θ 进行建模。这部分是因为 θ 是一个比例,范围从 0 到 1,因此我们可以很容易地放置一个基于 beta 分布的先验,它也限制为(0,1)。从我们上面的定义,我们很容易看到,通过知道 θ ,我们也知道 ve,反之亦然。因此,后验 P(VE > 0.3| x )等价于 P(θ<(1–0.3)/(2–0.3)|x),或者 P(θ<0.4117647 |x【T57),假设 1:1 设计,监测次数相等。
记住,根据贝叶斯法则,

数据 x 遵循二项式分布,因为我们正在询问接种组的病例数,总共有 n 个病例,接种组中此类病例的概率为 θ 。我们可以将 θ 的先验定义为遵循贝塔分布。因此,

我们可以扩展后验概率的表达式,基于可能性和先验分布,

但最后一个表达式只是另一个贝塔分布,所以后验可以表示为,

这也是为什么β是二项分布的共轭先验,因为后验也是β分布。我们不会在这里讨论更多的细节,但是后验预测分布是一个β-二项式分布。
我们可以选择什么样的先验?如果我们对检验后验 P(θ<0.4117647 |x)感兴趣,我们可以设置先验贝塔分布,使得平均值就是这个值。这正是辉瑞/BioNTech 所做的——之前使用的是 Beta(0.700102,1),因此平均值为 0.700102/(0.700102+1) ≈ 0.4118。
现在来计算后验 P(θ<0.4117647 |x),这简单来说就是后验 beta 分布的 CDF 高达 0.4117647。在实际的临床试验结果中(与临床设计相反),接种疫苗组和安慰剂组之间的监测时间可能有轻微差异,这是可以解释的。即 P(VE > 0.3| x )等价于 P(θ<(r(1–0.3))/(1+r(1–0.3))|x)。
通过计算第 2.5 和 97.5 百分位的后验概率,可以得到 95%可信区间。下限/上限 θ 可用于导出上限/下限 ve 值(使用 r 进行监视时间调整)。
在进入下一部分之前,我们将使用我们讨论过的先验知识,探索不同的 θ 值对可能性和后验概率的影响。例如,我们将设置总共 150 个病例,并改变接种组中观察到的病例数。
我们看到以下内容,

不同ᵥ.的可能性、先验性和后验性信息最少的先验。图片作者。
我们没有观察到先验对 θ 的估计有很大影响。另一方面,如果我们选择另一个非常不同的先验,例如 Beta(20,0.5),我们会看到我们的后验估计受先验的影响要大得多。

在不同的 c ᵥ.的可能性、先验和后验信息丰富的先验。图片作者。
事实上,所选的先验β值(0.700102,1)是辉瑞/BioNTech 所称的信息最少的先验值,因为它对我们的估计没有大的影响。该先验定义为平均值对应于 VE=0.3,这是 VE 的低期望值,而先验中的 θ 的不确定性仍然非常大。如果我们检查β的 95%可信区间(0.700102,1),则对于 θ ,该范围为(0.0052,0.9645),对应于 VE 的 95%可信区间(-26.16,0.9948)——这基本上涵盖了我们之前认为的 VE 的整个范围。
成功的边界
一旦我们计算出 P(VE > 0.3 |数据),我们仍然需要知道在什么阈值我们认为我们的结果是成功的。如果 VE > 30%的概率只有 50%,显然我们对自己声称的疫苗效力没有信心。
因为要进行中期分析,所以我们需要考虑这一点,以便将总体 I 型误差保持在预定义的水平(例如 2.5%)。从技术上讲,这是一个频率主义的概念,因为我们有一个零假设和相应的测试统计,其中我们拒绝在预定义的阿尔法水平。对于中期研究,传统上在频率主义方法中,我们使用阿尔法支出函数(如 Lan-DeMets O'Brien-Fleming ),该函数描述了我们在每次中期分析中“支出”多少阿尔法,因此总体阿尔法保持在(受保护的)例如 2.5%。实际上,这定义了 alpha 在任何给定的中期分析中接受/拒绝零假设。在贝叶斯试验设计中,可以通过确定后验概率的阈值(即 P(VE>0.3|data))来引入这种频率主义特征,从而将总体 I 型误差控制在期望的水平——因此有点像混合频率主义/贝叶斯策略。
在辉瑞/BioNTech 的研究中,中期分析和主要分析的成功界限分别定义为 P(VE > 0.3 |数据)> 0.995 和> 0.986。界限可以基于模拟和/或敏感性分析得出(尚不清楚辉瑞/BioNTech 研究中使用的确切方法)。从给定的边界,我们也可以推导出设计运行特性。这本身就是一个帖子,我们不会在这里深究。
新冠肺炎 VE 计算
报道的新冠肺炎功效结果如下,

报告结果表(见参考文献)。按作者分类的表格。
而只有辉瑞/BioNTech 试验使用了贝叶斯分析。我们的目标是将相同的贝叶斯方法应用于所有三个试验结果。我们可以将 VE 计算为 1-IRR,并使用β-二项式模型计算 95%可信区间和 P(VE > 30% |数据),以β(0.700102,1)作为先验,并对监测时间进行调整。
我们的计算结果如下:

使用贝叶斯方法估计不同新冠肺炎疫苗的 ve。按作者分类的表格。
毫不奇怪,VE 和后验概率的计算与辉瑞/BioNTech 研究[7]的表 2 中报告的结果完全匹配。由于我们在这里使用了贝叶斯估计,我们预期会观察到 Moderna 和阿斯利康/牛津研究值的细微差异。我们还可以根据辉瑞/BioNTech 研究的实际报告病例,了解潜在的可能性、先验和后验分布。

辉瑞/BioNTech 新冠肺炎研究的贝叶斯分布,分别基于接种疫苗组和安慰剂组的 8 个和 162 个报告病例。图片作者。
值得注意的是,这些试验之间还存在其他差异,例如确定新冠肺炎确诊病例的标准,因此这里的比较仅用于教学目的。此外,新型冠状病毒突变变异体的出现意味着更多最近和/或即将到来的临床试验是在不同病毒遗传变异体群体的背景下进行的(如强生公司在南非的 3 期试验,其中 B.1.351 突变体最为普遍)。这进一步混淆了现实世界中疫苗效力和有效性的比较。
原载于 2021 年 1 月 26 日https://boyangzhao . github . io。
相关员额和资源
-https://Twitter . com/nataliexdean/status/1307067685310730241?s = 20
-http://skranz . github . io/r/2020/11/11/11/covidvaccinebayesian . html
-https://medium . com/swlh/the-smincing-math-power-the-新冠肺炎-vaccine-trials-930 a5 e 97 c9c 9
-https://IBE CAV . net lify . app/post/post
参考文献/脚注
- FDA,预防新冠肺炎病毒疫苗的开发和许可,工业指南。2020 年 6 月。链接
- 一些主要终点也包括安全性,因为一些临床试验是 1/2/3 期试验的组合。方案中进一步描述了确诊 COVID 病例的确切标准。提供的表格仅关注疗效终点的测量。
- 由于所有研究都有中期分析,因此成功标准被定义为总体 I 型错误被保护在预定水平(BNT162b2 和 mRNA-1273 为 2.5%;AZD1222 为 5%)。这里的成功界限仅详细描述了计划的主要分析。一些试验已经通过了中期研究的成功界限。
- 研究方案中计划的成功界限。Baden 等人报告的实际分析基于一项中期研究,其中 p 值的相关成功阈值为 0.0049(在 FDA 关于现代新冠肺炎疫苗的简报文件第 23 页中陈述),这是基于首次中期分析时的 O'Brien Fleming 界限。发现 p 值为<0.0001, surpassing the threshold.
- The trial is a phase 1/2/3 trial with efficacy as primary endpoints in the pivotal phase 2/3 trial. There were two primary endpoints, one with vaccinated group consisting of participants without evidence of infection prior to vaccination; and other of participants with and without evidence of infection prior to vaccination. We will only focus on the former in this post.
- Total time in 1000 person-years. The surveillance time in Pfizer/BioNTech was provided. The surveillance times in Moderna and Astrazeneca studies we have calculated (as cases/incidence rate), given they have provided the incidence rate and the number of cases.
- Polack et al. 2020 N 工程医学 383(27),2603–2615链接
- 巴登等人 2020N Eng J Med链接
- Voysey 等人 2021 柳叶刀9;397(10269),99–111链接
用微积分验证付费搜索的想法
通过抓取关键字规划器的数据,你可以使用微积分计算出付费搜索活动的最大可能利润。
比方说,你正在寻求推出一个新的付费搜索活动。这可能是一个现有品牌的新活动,或者你可能是第一次在付费搜索上推出一个品牌。更好的是,你可能还没有一个品牌,只是想了解搜索市场对某个特定想法的看法。
如果你处于这些情况中的任何一种,有一种方法可以让你在开始之前验证你的策略。这并不明显——我从来没有看到它被写下来——但它可以给你一个相当好的主意,告诉你如何有效地开展你的活动。
为了理解这种方法,我们必须说几句关于关键字规划。
关键词规划器
如果你还不熟悉 Keyword Planner,它是 Google Ads 中的一个免费工具,可以让你获取任何你喜欢的关键词的搜索数据。
一旦你导航到关键字规划,你会想要创建一个计划,其中包含所有的关键字,你打算竞标。
你可以通过输入一个初始的关键词想法(例如跑鞋)或者通过输入一个相关的网站(例如【nike.com/running】T2)来找到这些关键词。无论你走哪条路,你都会看到一个流行关键词的列表。你可以挑选你想添加到你的计划中的关键词,但是你应该只添加你真正想要竞价的关键词。
一旦你建立了你的计划,你会想去关键字规划器的关键字部分。您应该会看到类似这样的内容:

这本质上告诉你的是,基于 1.75 美元的每次点击竞价,你可以通过对这些关键词竞价来达到一定的指标。您可以单击出价来更改它,并查看指标如何响应变化。
现在,这些数据真正有趣的不是数据本身。知道一组输入出价的输出指标是很好的,但这没什么特别的。
有趣的是,当我们试图理解数据背后的方程式时。为了做到这一点,我们将从这个工具中有效地收集一些数据点,并尝试重建这个等式。
我这样做的方法是创建一个带有不同出价级别的 Google 表单,如下所示:

然后,我们将这些出价输入到关键字规划器中,并记下实际的每次点击费用和点击预测。然后我们可以将这些相乘得到成本。
将这些都粘贴进来是一个稍微费力的过程,但是当你完成后,你应该会得到类似下面的东西。

需要注意的是:
- 平凡的是,0.00 美元的出价根本不会给你带来任何点击。
- 出价之间的间隔随着出价的增加而变大。这仅仅是因为随着你出价的增加,点击数的差异会逐渐变小;你的数量不再受出价的限制,而是受总搜索量的限制。因此,我们不需要在更高的出价水平上有同样多的粒度。
现在我们有了这个,我们可以画一个点击数和点击费用的散点图。它应该看起来像这样。

为了理解点击量和点击费之间的关系,我们可以进行回归分析/在散点图中添加一条趋势线。这里我选择使用二阶多项式(即二次方程):

趋势线的方程式显示在图表的顶部。

在我们继续之前,我应该澄清绘制这种趋势线的两个局限性:
- CPC 接近 0 美元时不准确您可以在图表上看到,x 轴截距(直线与 x 轴相交的位置)位于略高于 0 美元的 CPC 处。我们知道在现实中,图表应该通过原点,因为 0.00 美元的点击费会给你带来 0 次点击。因此,我们知道不要相信 CPC 值非常接近 0.00 美元的等式。
- 趋势线没有延伸到超过 2.70 美元的 CPC 上在图表的右侧,我们可以看到当 CPC 接近 3 美元时,趋势线开始下降。我们将忽略图表的这一部分,假设我们的等式仅适用于 0.00 美元(但不要太接近 0.00 美元)到 2.70 美元之间的 CPC。
那么,我们有什么?我们有一个公式,描述了在 0.00 美元到 2.70 美元的范围内,不同的 CPC 可以获得多少点击量。
这为什么有用?好吧,如果我们知道一个特定的点击费我们能得到多少点击量,只要我们有一个转换率的估计,我们也能估计一个特定的点击费我们能得到多少转换率。
如果我们知道我们的利润(即利润)每转换,我们也知道多少利润,我们可以在一个特定的点击费。
我们可以将之前的等式写成:

在这里,我们用之前等式中第三行的点击来代替。请记住,我们的利润(我们每次转换的利润)和我们的 CvR(我们的转换率)被假定为常数。
现在让我们问一个问题,我们能产生的最大利润是多少?
微积分
为了回答这个问题,我们将尝试计算出给我们带来最大利润的 cpc 的值。我们可以通过相对于 cpc 对上述方程求微分,并找到相关的驻点来实现。
这是一个 cpc 的值,在该值下,任何一方的微小变化(即增加或减少 cpc )都不会导致利润发生变化。这使得它成为利润最大的点。
我们可以将前面的利润等式区分如下:

我们将为利润和 CvR 插入一些值,只是为了使事情更简单。假设它们分别是 50 美元和 5%。我们可以简化如下:

现在,为了找到稳定点,即利润最大的点,我们简单地将等式的左侧设置为零。这相当于说我们在寻找上面等式给出的图的峰值。

如果我们仔细检查并求解这个方程,我们发现有两个值的 cpc 满足上述方程:

我们有两个解决方案,即两个 cpc 值,这显然使我们的利润最大化。实际上,我们可以放弃第二个值,因为正如我们前面所说,我们绘制的趋势线只对低于 2.70 美元左右的 cpc 值有效。任何高于这个数字的数据我们都会忽略。
所以,这就是说,我们可以通过达到 1.10 美元的点击费来产生最大可能的利润。看一下上面的出价图,可以看出这相当于 1.80 美元的出价。
计算利润
如果我们想了解这实际上会产生多少利润,我们可以将 1.10 美元的 cpc 值代入前面的利润等式:

如果我们把所有的值加在一起,然后算一算,我们会得到:

请记住,因为我们最初的关键字规划数据是每月的,上面代表了我们可以产生的最大每月利润(每点击 1.10 美元,每次转换 50 美元的利润,5%的 CvR)。
概括起来
本质上,这就是你如何验证付费搜索策略。
概括地说,我们:
- 使用与我们产品相关的搜索词,在关键字规划中建立一个计划。
- 在不同的出价水平范围内提取点击费和点击率数据,并使用这些数据计算出点击费和点击率之间的关系。
- 我们将此纳入利润方程,并对其进行微分,以了解利润如何随每次点击费用而变化。
- 我们将这种差异设置为零,并求解,以找到利润最大化时的 CPC 值。
解释验证曲线—绘制单个超参数的影响
少代码出图!省点时间解读吧!

由 Unsplash 上的 CHUTTERSNAP 拍摄
在机器学习(ML)中,模型验证用于衡量 ML 模型的有效性。一个好的 ML 模型不仅非常适合训练数据,而且可以推广到新的输入数据。
模型超参数在确定 ML 模型的有效性中起着重要的作用。通过网格搜索或随机搜索,我们可以找到 ML 模型的最佳超参数组合。然而,在不进行网格搜索或随机搜索的情况下,找出单个超参数对训练和测试数据的影响有时是有用的。这是你绘制验证曲线的地方。验证曲线是一种图形技术,可用于测量单个超参数的影响。通过查看该曲线,您可以确定模型对于某些范围的超参数值是欠拟合、过拟合还是刚好合适。
先决条件
要理解今天的内容,你应该对交叉验证有很好的了解。如果你还没有,请阅读我写的下面这篇文章的“使用 k-fold 交叉验证评估模型的性能”部分。
k 重交叉验证 通俗地解释
如果您正在寻找一些关于超参数调优的内容,请阅读上述文章的“使用 k-fold 交叉验证进行超参数调优”一节。这是完全可选的,不一定要掌握该部分的知识才能理解今天的内容。
除此之外,有随机森林算法的知识是首选。这是因为,今天,我们建立了一个随机森林模型,并在此基础上绘制验证曲线。看看下面这篇用简单的英语解释随机森林的文章。
介绍够了。让我们进入主题。
获取数据
我们使用“心脏病”数据集。点击此链接即可下载。数据集的前几行是:

“心脏病”数据集的前 5 行(图片由作者提供)
该数据集包含 303 个样本和 13 个特征。最后一列是目标列,包含 0(没有心脏病)和 1(有心脏病)。
该方法
我们在上面的数据上建立一个随机森林分类器。然后,我们特别考虑那个分类器的 max_depth 超参数。 max_depth 值决定集合中每棵树分裂的次数,在此之后,它停止分支。通过将 max_depth 超参数的值范围从 1 到 10,我们绘制了交叉验证的训练和测试分数。然后,我们决定最大深度超参数的最佳值。
让我们编码
在建立模型之后,我只用了 1 行代码(更少的代码)来创建验证曲线。我利用 Yellowbrick 机器学习可视化库。但是,它没有附带 Anaconda 安装程序。您需要手动安装它。打开 Anaconda 提示符,运行下面的命令。
pip install yellowbrick
如果那对你不起作用,用 用户 标签试试下面的。
pip install yellowbrick --user
或者用康达锻造频道试试。
conda install -c conda-forge yellowbrick
或者用 DistrictDataLabs 频道试试。
conda install -c districtdatalabs yellowbrick
现在,请看下面的代码。
等到加载 Python 代码!(作者代码)
输出是:

max_depth 超参数的验证曲线(图片由作者提供)
让我们解释一下
当我们执行 validation_curve() 函数时,很多工作都是在幕后进行的。这个函数的第一个参数应该是 Scikit-learn 估计器(这里是一个随机森林分类器)。第二个和第三个应该是 X (特征矩阵)和 y (目标向量)。 param_name 包含我们想要测量影响的超参数的名称。“n _ jobs =-1”是指我们在做交叉验证程序时,使用计算机处理器的所有内核进行并行计算。 "param_range" 包含可能的参数值的一维 numpy 数组。在我们的例子中,这些值应该是从 1 开始的整数。对于 max_depth 来说,零和负数是不可接受的值。“cv”定义了交叉验证的折叠次数。标准值为 3、5 和 10。评分参数包含模型的评分方法。在分类上,最优选【准确性】****【roc _ AUC】。回归中常用【R2】****【负均方误差】。除此之外,还有许多评估指标。你可以通过访问这个链接找到它们。
当我们执行 validation_curve() 函数时,交叉验证过程在后台发生。正因为如此,我们才输入 X 和 y 。我们不需要把数据集拆分为 X_train 、 y_train 、 X_test 、 y_test 。在交叉验证中,分割是基于我们在 cv 中指定的折叠数在内部完成的。在这里使用交叉验证可以保证模型的准确性分数不会受到随机数据分割过程的太大影响。如果您只是使用 train_test_split() 函数而没有进行 t 交叉验证,那么根据您在 train_test_split() 函数中提供的 random_state ,准确性得分会有很大的不同。在交叉验证中,使用 10 次(cv=10)这样迭代的平均值来计算精确度!
在 k-fold 交叉验证中,我们假设数据集中的所有观察值都以数据没有偏差的方式很好地分布。这就是为什么我们首先使用混洗函数混洗数据集。
注意:使用 ValidationCurve() 类可以实现 validation_curve() 函数的相同功能。在这里,首先创建一个可视化器(一个 ValidationCurve() 类的对象),然后使用公共的。fit(X,y) 范式。这是代码。
等到加载 Python 代码!(作者代码)
让我们来解释验证曲线
现在,我们解释之前绘制的验证曲线。通过查看曲线,我们可以确定对于 max_depth 的某个超参数值范围,模型是欠拟合、过拟合还是刚好合适。注意,在图中,训练集的准确度分数被标记为“训练分数”,测试集的准确度分数被标记为“交叉验证分数”。
- 拟合不足:训练集和测试集的精度分数都很低。这表明该模型过于简单或过于正则化。在 max_depth 值为 1 和 2 时,随机森林模型欠拟合。
- 过拟合:训练准确率分数很高,测试集准确率分数低。该模型非常适合训练数据,但它无法推广到新的输入数据。对于最大深度值为 4,5,…,10,模型高度过度拟合。
- 恰到好处:没有过拟合或欠拟合。在 max_depth 值为 3 时,模型刚刚好。该模型非常适合训练数据,并且也可推广到新的输入数据。这就是我们想要的!
小心:当你使用 MSE 这样的评价指标时,当训练 MSE 很低(不高)而测试集的 MSE 很高(不低)时,就会发生过拟合的情况。这是因为这里我们考虑了一个误差(均方误差)。
小心:这里,你得到了最优的 max_depth 超参数值 3。请记住,这是我们仅考虑 max_depth 超参数时得到的结果。当我们在网格搜索或随机搜索中一次考虑几个超参数时,最佳的 max_depth 超参数值不会是 3。
摘要
验证曲线是一个很好的工具,你应该把它放在你的机器学习工具包里。它可用于绘制单个超参数的影响。它不应该用于调整模型。请改用网格搜索或随机搜索。创建曲线时,应考虑交叉验证方法。根据您选择的评估指标,解释会有所不同。我建议您在创建验证曲线时使用 Yellobrick Python 库。它很容易使用,节省了大量的编码时间。那就用那段时间去解读吧!
感谢阅读!
本教程由 鲁克山·普拉莫迪塔数据科学 365 博客作者设计创作。
在 https://rukshanpramoditha.medium.com阅读我的其他文章
2021–03–13
深度强化学习中基于值的方法
深度强化学习在过去几年中已经成为一个新兴领域。一个好的开始方法是基于值的方法,其中学习状态(或状态-动作)值。在本帖中,我们提供了一个全面的综述,重点是 Q-learning 及其扩展。

不溅
强化学习简介
有三种常见的机器学习方法:1)监督学习,其中学习系统基于标记的示例学习潜在的地图,2)非监督学习,其中学习系统基于未标记的示例建立数据分布的模型,以及 3)强化学习,其中决策系统被训练以做出最佳决策。从设计者的角度来看,各种学习都是由一个损失函数来监督的。监督的来源必须由人来定义。一种方法是利用损失函数。

作者图片
在监督学习中,提供了基础事实标签。但是,在 RL 中,我们通过探索环境来教导代理。我们应该设计一个代理人试图解决任务的世界。这个设计和 RL 有关。正式的 RL 框架定义由[1]给出
一个在环境中表演的特工。在每个时间点,代理观察环境的状态,并决定改变状态的动作。对于每个这样的动作,代理被给予一个奖励信号。代理人的角色是最大化收到的总报酬。

RL 图(图片由作者提供)
那么,它是如何工作的呢?
RL 是一个在一个提供偶然回报的世界中,通过试错来学习解决顺序决策问题的框架。这是根据经验决定在不确定的环境中为实现某些目标而采取的行动顺序的任务。受行为心理学的启发,强化学习(RL)为这个问题提出了一个形式化的框架。人工智能体可以通过与其环境的互动来学习。利用收集的经验,人工智能体可以优化通过累积奖励给出的一些目标。这种方法原则上适用于任何依赖于过去经验的顺序决策问题。环境可能是随机的,代理可能只观察到关于当前状态的部分信息,等等。
为什么要深入?
在过去的几年里,强化学习因其在解决具有挑战性的顺序决策问题上的成功而变得越来越受欢迎。这些成就中的一些是由于 RL 与深度学习技术的结合。例如,深度 RL 代理可以成功地从由数千个像素组成的视觉感知输入中学习(Mnih 等人,2015 / 2013)。
正如莱克斯·弗里德曼所说:
“人工智能中最激动人心的领域之一。它融合了深度神经网络的力量和能力,以行动和理解世界的能力来代表和理解世界”。
它解决了一系列复杂的决策任务,这些任务在以前是机器无法完成的。Deep RL 在医疗保健、机器人、智能电网、金融等领域开辟了许多新的应用。
RL 类型
基于价值的:学习状态或状态行为价值。通过选择状态中的最佳动作来行动。探索是必要的。基于策略的:直接学习将状态映射到行动的随机策略函数。按照抽样政策行事。基于模型:学习世界的模型,然后利用模型进行规划。经常更新和重新规划模型。
数学背景
我们现在关注基于值的方法,它属于“无模型”方法,更具体地说,我们将讨论 DQN 方法,它属于 Q 学习。为此,我们快速回顾一些必要的数学背景。

让我们定义一些数学量:
- 预期收益
RL 代理的目标是找到一个策略,使其优化预期回报(V 值函数):

其中 E 是期望值运算符,gamma 是贴现因子, pi 是一个策略。最优预期收益定义为:

最优 V 值函数是在给定状态 s 时的期望贴现报酬,此后代理遵循策略****pi ***。
2. Q 值
还有更多感兴趣的功能。其中之一是质量价值函数:

与 V 函数类似,最佳 Q 值由下式给出:

最优【Q】-值是在给定状态 s 下,对于给定动作, a, 之后,代理遵循策略**【pi ***。最佳策略可以直接从这个最佳值获得:

3.优势功能
我们可以将最后两个功能联系起来:

它描述了的行动相对于跟随直接政策 pi 时的预期收益“有多好”。
4.贝尔曼方程****
为了学习 Q 值,使用贝尔曼方程。它承诺了一个独特的解决方案 Q :*

其中 B 是行李员:

为了保证最优值:状态-动作对被离散地表示,并且所有动作在所有状态下被重复采样。
q 学习
Q 学习在一个脱离政策的方法中学习在一个状态中采取行动的价值和学习 Q 价值和选择如何在世界中行动。我们定义了状态-动作值函数:在执行 a 和跟随 pi 时的期望收益。用表格的形式表示。根据 Q 学习,代理使用任何策略来估计使未来报酬最大化的 Q 。 Q 直接逼近 Q* ,当代理不断更新每个状态-动作对时。****

对于非深度学习方法,这个 Q 函数只是一个表:

作者图片
在该表中,每个元素是一个奖励值,该值在训练期间被更新,使得在稳态下,它应该达到带有折扣因子的奖励的期望值,该期望值相当于 Q 值。在现实场景中,值迭代是不切实际的;*

图片由谷歌提供
突围游戏中,状态为屏幕像素:图像大小:84x84,连续:4 张图像,灰度:256。因此,Q-表中的行数为:

只是提一下,在宇宙中,有 10⁸原子。这就是为什么我们应该在深度强化学习中解决像突破游戏这样的问题的好理由…
DQN:深度 Q 网络
我们用神经网络来近似 Q 的函数:

神经网络是很好的函数逼近器。DQN 在雅达利运动会上被使用。损失函数有两个 Qs 函数:

****目标:在特定状态下采取行动的预测 Q 值。预测:实际采取行动时得到的值(计算下一步的值,选择总损失最小的一步)。
参数更新:

当更新权重时,也改变了目标。由于神经网络的泛化/外推,在状态-动作空间中建立了大的误差。因此,贝尔曼方程不收敛于 w1。错误可能会随着此更新规则传播(缓慢/不稳定/等等。).
DQN 算法可以在各种 ATARI 游戏的在线设置中获得强大的性能,并直接从像素中学习。限制不稳定性的两种试探法:1 .目标Q-网络的参数仅每 N 次迭代更新一次。这防止了不稳定性快速传播,并最小化发散的风险。2 .可以使用体验重放记忆技巧。

DQN 建筑(MDPI:深度强化学习方法及其在经济学中的应用综述)
DQN 诡计:经验重放和ε贪婪
体验回放
在 DQN,使用 CNN 架构。使用非线性函数的 Q 值近似值不稳定。根据经验重放技巧:所有经验都存储在重放存储器中。训练网络时,使用重放存储器中的随机样本,而不是最近的动作。换句话说:代理收集记忆\存储经验(状态转换、动作和奖励)并为训练创建小批量。
ε贪婪探索
当 Q 函数收敛到 Q 时,它实际上以它找到的第一个有效策略结算。因此,探索是贪婪的。一种有效的探索方式是选择一个概率为“ε”的随机动作,否则(1-ε),选择贪婪的动作(Q 值最高)。经验是由ε-贪婪政策收集的。*
DDQN:双重深度 Q 网络
Q -learning 中的 max 运算符使用相同的值来选择和评估动作。这使得更有可能选择高估的值(在有噪声或不准确的情况下),导致过于乐观的值估计。在 DDQN 中,每个q 都有一个单独的网络,因此有两个神经网络。它有助于减少仍然根据当前权重获得的值来选择政策的偏差。
两个神经网络,分别具有功能:**


现在,损失函数由下式提供:

决斗深度 Q-网络
Q 包含优势(*)*价值除了处于那种状态的价值( V )。 A 早先被定义为在状态 s 中采取动作的优势以及所有其他可能的动作和状态。如果你打算采取的所有行动都“相当好”,我们想知道:它有多好?******

决斗网络代表两个独立的估计器:一个用于状态值函数,一个用于状态相关的动作优势函数。要进一步阅读代码示例,我们可以参考由 Chris Yoon 发布的决斗深度 q 网络。
摘要
我们提出了基于价值的方法中的 Q-learning,并对强化学习和将其置于深度学习环境中的动机进行了一般性介绍。一个数学背景,DQN,DDQN,一些技巧,决斗 DQN 已被探讨。
关于作者
Barak 获得了以色列理工学院的航空工程学士学位(2016 年)、硕士学位(2018 年)以及经济和管理学士学位(2016 年,成绩优异)。他曾在高通工作(2019-2020),在那里他主要研究机器学习和信号处理算法。巴拉克目前正在海法大学攻读博士学位。他的研究兴趣包括传感器融合、导航、机器学习和估计理论。
www.Barakor.com|https://www.linkedin.com/in/barakor/
参考
[1]萨顿,理查德和安德鲁巴尔托。强化学习:简介。麻省理工学院出版社,2018。
[2]通过深度强化学习实现人级控制,Volodymyr Mnih 等,2015。关于自然。
[3] Mosavi,Amirhosein 等,“深度强化学习方法及其在经济学中的应用综述。”数学 8.10 (2020): 1640。
[4]贝尔德,李蒙。"残差算法:函数逼近强化学习."机器学习会议录 1995 。摩根·考夫曼,1995 年。30–37.**
“基于经验”的认证方法在数据科学中的价值
意见
看看数据科学和人工智能领域基于经验的认证/公开徽章,以及它们如何带来价值
作为过去几年一直在讨论、建议、倡导、辅导/指导、实施某些战略来帮助企业为未来做准备的人,这是我感兴趣的话题。我强烈地感觉到,证据不仅有助于那些实践它的人建立信心,也有助于那些接受结果和影响的人建立信心。因此,数据科学和人工智能中基于经验的认证方法非常重要,因为它涉及现实生活场景中经过验证的项目实施经验。
“查看“基于经验”和“基于技能”的认证之间的区别”:数据科学领域更接近于领域/行业、解决问题和讲故事方面,因此,除了技能之外,这方面的经验更为重要,这是一个“显而易见”的问题。数据科学从业者需要理解和欣赏它,当然也要计划投入时间在这些成就上,为他们带来价值和增长潜力。

图片由 Kamal Mishra 提供
如果我们在这里看一下排名前 15 的数据科学认证,我们会注意到这些认证的重要性。与此同时,围绕“基于经验的认证”的问题也很关键。“开放认证数据科学家(Open CDS)”是一种基于经验的认证。我们可以去他们的网站这里了解详情。它也有多个级别,以认可不同级别的数据科学家的旅程。打开 CD 的详细步骤可以在这里找到。围绕专业发展、专业交流、基于经验档案的评估维度的参数是有价值的。
“理解的框架”:让我们探索一下这个框架,它反映了数据科学从业者应该熟悉的各个方面。以下框架列出了真正关键的参数(如果不是详尽的话):

图片由 Kamal Mishra 提供——数据科学从业者维度框架
a)解决问题和批判性思维技能
b)围绕数学和统计学的概念理解和基础知识
c)编程或编码敏锐度
d)专注于机器学习和深度学习
e)理解数据库概念,如 SQL、RDBMS、数据建模等。
f)数据可视化/探索性数据分析技能和讲故事
g)领域/行业知识和对行业相关用例的理解,以及数据科学/ AI / ML 如何帮助解决这些问题
h)方法,对持续实验、改进和运营方面的理解
如果上述参数是关键的,那么一个人应该有布丁的证明,以显示结果,通过解决现实生活中的问题经历这样的场景。因此,当我们在行业的特定环境或场景中执行端到端问题或用例时,经验是非常重要的。
“基于经验的”认证方法的要素:要素可以是各种因素的组合,而不仅仅是显示已经执行的项目经验。基于不同的成熟度和指导原则,期望可能会有所不同。然而,总而言之,下面的一些参数可能是有用的。所有这些都有助于反映真正意义上的“应用数据科学从业者”,因为它不仅仅是技能。
a) 项目体验 —首先当然是项目体验,更重要的是“端到端体验”。这应该包括典型 CRISP-DM 框架的所有生命周期阶段。理想情况下,它应该至少包含两个最佳实践和两个经验教训等。(例如,我们如何在给定数据的情况下以独特的方式更好地进行数据探索,从而实现更好的功能,在预测的情况下更有效地影响目标变量,我们如何在“游戏”的早期与利益相关者进行互动和合作,以有效地引导对话和期望,我们如何在实验中进行概括,以衡量给定环境下的最佳可能解决方案, 这如何有助于回答作为业务目标的一部分而提出的假设和问题,我们如何通过根据当前的经验在未来更好地进行数据清理或数据转换来缩短“价值实现时间”等。 )在起草这份文件时,可能需要展示在作为项目工作的一部分实施解决方案后实现的最终结果、价值和影响。这不仅有助于阐明整体技术解决方案,而且有助于展示你所拥有的“讲故事”艺术!!
b) 对社区的贡献 —贡献的形式可以是可重用组件、资产、对开源社区的贡献、与各个级别的指导工作和构建生态系统相关的贡献、与创新或知识产权创造相关的贡献等等。
c) 思想领导力 —在宣传“下一个关键因素”、未来路线图、博客、“保持现状”场景、如何借助成果、出版物、专利等更好地推动创新方面对数据科学的贡献。

图片由 Kamal Mishra 提供
数据科学职业是一个非常热门的职业,许多人试图冒充数据科学家,但他们不具备这一关键职位所需的适当技能或专业知识。因此,基于经验的认证方法有助于识别真正的数据科学从业者。它帮助公司同时解决几个问题——a)获得他们正在寻找的“正确”候选人来解决他们的计划,b)创建一个具有实际生活经验的从业者库,这些从业者反过来可以在一段时间内帮助他们的团队建立和培养类似的人才。
免责声明 :这里的帖子是来自我的经验、想法和各种来源的阅读的个人观点,不一定代表任何公司的立场、策略或观点。
你的模型火车太慢了吗?
使用梯度下降算法训练神经网络时,探索遇到消失或爆炸梯度问题的各种方法的快速指南

克林特·王茂林在 Unsplash 上拍摄的照片
介绍
对于一个非常深的神经网络,当训练一个模型变得越来越慢时,你肯定遇到过这个问题。这种现象在 DNNs 的反向传播训练(使用梯度下降)期间显著发生,其中,每个参数的梯度误差沿其路径传播到网络的较低层。 为什么?这通常是因为梯度通常会变得越来越小。结果,较低层的权重永远不会改变,训练永远不会收敛到好的解决方案。
这篇文章分类讨论了在训练 DNNs 时减轻消失梯度(或爆炸梯度)问题的方法
有各种方法来克服这个挑战—
- 权重初始化 —选择初始权重,使其在训练反向传播时不随网络层衰减。
- 激活功能 —激活功能的选择可以解决问题。我们将详细研究这方面的选项。
- 权重归一化 —使用归一化技术克服消失/爆炸梯度问题。
让我们详细了解一下所有这些……
重量初始化
我们知道,当在反向传播过程中偏导数出现时,当输入非常高(或太低)时,使用激活函数如 sigmoid 函数将导致接近零导数。在计算导数时,由于链式法则,这导致较低阶层的梯度消失(饱和)。
在他们由 Xavier Glorot 和 Yoshua Bengio 发表的论文中,他们建议,如果我们希望梯度既不饱和也不爆炸,那么我们需要使每一层的输出的方差等于其输入的方差,和梯度在以相反方向流过一个层之前和之后也具有相等的方差。为了达到这个条件——输入层的神经元数量应该等于输出层的神经元数量(即等于 一层的扇入扇出)。这种解决方案不实用。
因此,需要初始化技术来补偿这一基本要求——一个这样的选项是基于 fan-avg=(扇入+扇出)/2 使随机 - 初始化。** 这一理论引出了以下实际选项:——
- Xavier 初始化 或 Glorot 初始化:
带mean 0和variance: *σ2 = 1/fan-avg*的正态分布。
或者,r 和+ r 之间的均匀分布,带有*r = sqrt(3/fan-avg)*
这种重量初始化最适合使用激活功能: tanh,logistic,softmax
- 乐存初始化
将fan-avg替换为fan-in以上数值将变为:
均值为 0 且方差为:σ2 = 1/fan-in
或者 r 和+ r 之间的均匀分布,带有r = sqrt(3/fan-in)
当使用fan-in = fan-out
SELU激活功能时,LeCun 初始化相当于 Glorot 初始化。**
- 何初始化
适用于激活功能: Relu 及其变体
Keras 实现
默认情况下,Keras 使用正态分布的 Glorot 初始化。我们可以显式地将kernel-initializer参数定义为值,例如he_normal或he-uniform:
*keras.layers.Dense(10, activation="relu", kernel_initializer="he_normal")*
激活功能
激活函数的选择是饱和梯度的另一个原因,如前所述,当使用激活函数如 sigmoid 或 tanh 时,问题更加突出。主要原因是梯度趋向于零对于高阶权重,这些函数有时也被称为饱和激活函数。克服这个问题的可能选择是通过使用其他类型的激活函数,这些激活函数对于它们的导数来说是不饱和的,几个选项是:
- ReLU:整流线性单元

ReLU 。来源:维基百科
优势—它不会在x>0(正值)内饱和
缺点—坡度完全为x<0。使这些神经元在训练中失去能力。**
**# Keras implementation
keras.layers.Dense(10, activation="relu")**
- 漏泄继电器

****漏热路。来源:维基百科
优点——它确保x<0神经元不会“死亡”。
**#Keras implementation
keras.layers.LeakyReLU()**
- RReLU:随机泄漏 ReLU
RReLU 是 ReLU 的变体,其中α在训练期间从给定范围中随机选择,并在测试期间固定为平均值。

在范围内。来源
- PReLU:参数化泄漏 ReLU
“α”在训练期间被学习,并且它是将在反向传播期间被修改的参数之一。

PReLU。来源:维基百科
缺点-对于使用小数据集的训练,它很容易过度拟合。
**#Keras implementation
keras.layers.PReLU()**
- ELU:指数线性单位

****ELU。来源:维基百科
从激活方程本身可以明显看出,对于x<0,它减轻了死神经元问题和消失梯度问题,此外,函数的导数在x=0处极其平滑,因此它收敛得相当快。
***#Keras implementation
keras.layers.Dense(10, activation="elu")***
- SELU:比例指数线性单位
对于仅由一堆密集层组成的神经网络,SELU 激活函数通常明显优于其他激活函数。这允许网络 自标准化 更快地收敛,同时解决梯度问题。这对于具有某些前提条件的网络是有效的——网络架构必须是顺序的和密集的和kernel_initializer="lecun_normal"。**
**#Keras implementation
keras.layers.Dense(10, activation=”selu”, kernel_initializer=”lecun_normal”)**
批量标准化
这种技术现在在训练模型处理消失 ( 爆炸)渐变的问题时无处不在。该操作仅在激活功能之后(有时之前)应用。本质上,这个操作:——I .零中心,ii .正常化,三。音阶& iv。移动应用于它的每个输入。总之,该操作将有助于找到给定输入要素集的最佳均值和比例。
**.....
#Keras implementation
keras.layers.Dense(300, activation="relu"),
keras.layers.**BatchNormalization(),**
keras.layers.Dense(100, activation="relu"),
keras.layers.**BatchNormalization()** .....**
BatchNormalization()层的 Keras 实现学习 4-vectors:
1。γ:比例矢量
2。β:偏移(移位)向量
3。μ:平均向量
4。σ:标准偏差向量
批处理标准化向量在训练期间被估计/学习,并在测试期间被原样应用。
这项技术非常成功,以至于在训练大型数据集时,我们甚至可以使用饱和激活函数,如 sigmoid 或 tanh。在某处,它还克服了重量初始化的训练挑战。最后,这是一个很好的正则化选项,类似于 l1 或 l2 正则化子或者与其他退出方法一样好。
结论
在这篇文章中,我粗略地介绍了一些概念,以帮助你缓解在训练神经网络时最突出的消失和爆炸梯度问题。你曾经面临过这些问题吗?你是怎么解决的?一定要让我知道你实践中遇到的解决问题的其他各种技巧。
在 LinkedIn 上与我联系,进一步讨论
深度神经网络中的消失梯度

原因和可能的解决方案
如今,用于图像分析的网络由许多层一层接一层堆叠而成,形成所谓的深层网络。训练这些架构的最大问题之一是消失梯度:梯度假设为零或渐近值,阻止权重被更新。
在本文中,我们将分析这种现象的原因,提出第一种可能的解决方案,并在后续文章中为进一步的技术留下更多细节。在开始之前,将涉及的例子将是使治疗更容易的基础。显然,同样的概念可以应用于更复杂的架构。
注释
- w: 重量
- z: 神经元输入的线性和
- g: 激活功能
- 一个 : 激活功能的输出
- 顶点 :层 l- th
- L: 损失函数
1.什么是消失渐变?

图 1:该图显示了 FCNN VGG-16 网络不同层获得的特征图(来源:图片由我提供)
在深度卷积网络中,输入附近的层从图像中提取空间特征:轮廓的方向,角度的存在,纹理的存在,颜色信息。接近输出层,语义特征将被提取:出现嘴、鼻子等等。图 1 显示了一个例子,其中有一些在 VGG-16 的不同层提取的特征图。一般来说,通过增加网络的深度,每一层将能够学习如何提取更多的一般和复杂特征。
然而,深层网络很难训练。我们可以看到的一个问题是消失渐变。在反向传递过程中,靠近输入的图层的权重保持不变或更新非常缓慢,这与靠近输出的图层的情况相反。

图 2:有三个神经元的简单网络(来源:图片由我提供)
让我们考虑图 2 中神经元的顺序。我们通过计算损失函数相对于它的导数来更新权重 w^(1。为简单起见,我们假设每个神经元的偏置项 b 为零。应用链规则【1】并将激活函数的导数分组,我们得到:

我们来问一下,有什么可以阻止或者减缓梯度?我们分析产品∂ 一/∂z.
情况 1: 假设每次迭代只有第个为空(例如 ∂ a^(2) /∂z^(2)).)在这种情况下,∂L /∂w^(1)将呈现一个空值,阻止 w^(1)的更新,该值将保持不变。事实上,应用德尔塔法则【2】我们得到:

情况 2: 现在假设每一项∂ 和 (i)/∂z(i)在每次迭代中都有渐近零值。这样,∂L /∂w(1)也将渐近于零,w(1)将非常缓慢地更新(我们每次都将接近零的值相加)。应用 delta 规则:**

2.输出范围有限的激活功能

图 3:带共域[0,1]的 sigmoid 函数用蓝色表示。它的导数用红色表示。输入 z 的值表示在横坐标轴上,而相应导数的值表示在纵坐标轴上(来源:图片由 me 提供)
从上一段的例子中,我们了解了激活函数 g 的选择是如何发挥重要作用的。消失梯度的主要原因之一是由于使用了激活功能和有限输出范围:****
- 对于落在有效区域内的输入值 z ,导数∂a*/∂z将仅是非零的***
- 在显著区域,导数呈现相对较小的值。考虑到大量的层和乘以∂a/∂z 的值,我们可以降低渐变速度****
这两点完美地反映了案例 1 和案例 2 的内容。让我们通过将图 3 所示的 sigmoid (σ)视为有限输出范围激活函数来分析它们。考虑到导数,,对于落在区间[-4.4]内的输入值 z ,出现显著区域。对于该区间之外的值,导数将为零(情况 1)。****

图 4:具有 K 个神经元的平面网络(来源:图片由我提供)
现在假设我们有一个 K 层的网络,其中,对于每个神经元, z 在重要区域中取值。计算关于层 K、K-1、K-2 和 1 的梯度,我们得到:

通过乘以红色值,我们注意到随着我们远离输出层 k,乘积∂ a / ∂z 是如何减小的。如果 K 等于 50 层,我们将得到情况 2 :**

3.整流线性单位(ReLU)

图 5:带有 codomain [0,+inf]的 ReLU 函数用蓝色表示。它的导数用红色表示。输入 z 的值表示在横坐标轴上,而相应导数的值表示在纵坐标上(来源:图片由 me 提供)
图 5 所示的 ReLU 激活函数可以通过 max(0,z)来形式化。这个促成了消失渐变的分辨率(至少部分)。观察图 6 中的导数,我们注意到对于 R^+中的 z 的值,导数是单一的,因此允许梯度通过。当输入值为负时出现问题,从而得到情况 1* 。存在整流线性单元的变体,例如 LeakyReLU 和 eLU,其被创建来减轻对于 z < 0 的零导数的问题。***
结论
训练具有大量层的网络并不容易。在这篇文章中,我们探讨了消失梯度问题识别激活函数选择中的一个可能原因。建议的解决方案是使用校正的线性单位,对于正值,它不会阻碍梯度的通过。但是… ReLU 并没有完全解决问题。另一个问题是权重的初始化。如果多层的导数∂z /∂ a 为零或接近零,会发生什么?可以重复刚才讨论的关于激活函数的考虑和例子来回答这个问题。**
如果一方面我们有消失 渐变,另一方面我们必须考虑完全相反的问题,即爆炸渐变:渐变呈现高值,从而阻止优化。**
对深度网络训练的一个重要贡献是引入了跳过连接,我们将在另一篇文章中讨论。****
参考
【1】微分学:链式法则
Python 中的可变范围古怪
Python 编程语言中与变量相关的一些奇怪现象

(src =https://pixabay.com/images/id-2312548/
介绍
Python 是世界上最流行的编程语言。这是有充分理由的,因为这种语言在很多方面都比它的竞争对手有更大的优势。首先,它是可读和可写的,就像编写常规语言一样。许多 Python 代码最终读起来很像英语,所以很容易理解为什么这是初学者友好的,并且通常非常容易理解。其次,该语言具有用户想要的范式特征。这种语言是面向对象的,但是支持大量的泛型编程概念,这使得这种范式对于通用应用程序来说更加可行。最后,该语言的解释器是用 C 编写的,通过 Python.h 头文件与 C 紧密集成,使得 Python 可以在低端相对快速地运行,同时保持前端代码非常简洁易懂。然而,没有一种语言是完美的——行为也不总是如你所愿。Python 是一种充满了一些相当有趣的怪癖的语言。
Python 也是一种动态类型语言。这意味着在程序执行过程中可以改变不同的类型。当涉及到变量、范围和类型时,这一点和 Python 的解释器的结合培养了一些相当有趣的怪癖。今天,我想谈谈 Python 处理变量和作用域的一种奇怪方式。我还擅自将我用来演示这些怪癖的笔记本上传到 Github,所以如果你想亲自尝试这个项目,你可以在这里:
https://github.com/emmettgb/Emmetts-DS-NoteBooks/blob/master/Python3/Python variable oddities.ipynb
№1:范围古怪
范围是编程中嵌套层的一个非常重要的属性。作用域允许我们用相同的别名将应用程序的组件组织成更小的部分,并私下和公开地使用它们。Python 中的第一级作用域是全局作用域。在数据科学中,在这个范围内工作是很常见的,但是当在全局范围内工作并试图进入私有范围时,会出现一些问题。
也就是说,Python 的解释器确实有一些有趣的方式来处理语言中对象和方法的属性。让我们看一个 Python 中有两个函数的例子。第一个函数是调用从未在全局范围或函数范围内定义的变量的函数:
def one():
print(stuff)
第二个打印了一个现在在全局范围内定义的变量:
global global_stuff
global_stuff = "Hello World"
def two():
print(global_stuff)
我在这里使用了全局关键字来声明这个变量的范围。然而,这是不必要的,因为当我们在这个笔记本中工作时,我们已经在全局范围内了。当我们想要从私有范围定义全局变量时,通常使用这个关键字。也就是说,我仍然喜欢使用关键字,因为这是让其他程序员提前了解全局变量的好方法。继续,这是另一个函数,它执行与第一个函数完全相同的算法,但是在调用它之后定义了我们的填充变量:
def three():
print(stuff)
stuff = "Hello World!"
现在我将调用所有这些函数,然后我们将从第二个全局函数开始比较结果:
two()one()three()

- 两个函数,这个函数是我们的全局函数。因为变量 global_stuff 是在它上面的作用域中声明的,所以我们的函数可以访问这个变量并按预期打印出来。
- one 函数——这个函数是我们从未定义过“stuff”的函数,但尝试用 print()方法调用它的别名。这个抛出了一个名字异常,因为名字的东西没有被定义。
- 三个函数——这个函数是我们定义“东西”的函数,但是只有在我们尝试调用它之后。
你注意到发生了什么吗?这里有趣的是 three()和 one()函数中返回的异常之间的差异。这两个函数之间唯一的区别是变量的定义,只有在我们试图打印它之后才会发生。记住,很明显抛出发生在代码运行之前,那么为什么会发生这种情况呢?尤其是在动态类型编程语言的上下文中,这看起来不像是我们在这个场景中可以得到的结果。这里的一个重要区别是,作用域及其变量总是在编译时定义,而不是在运行时定义。然而,它们不是在编译时分配的,而是在运行时分配的,这就是动态部分的来源,因为类型在代码执行时会发生变化。
注意,第一个函数 one()也试图在全局范围内找到我们的“stuff”变量。嵌套层总是会这样做,这是一件重要的跟踪。如果我们的变量没有在函数范围内定义,它将检查全局范围。此外,如果变量没有在循环范围内定义,例如,它将在检查全局范围之前检查函数范围。范围对于包含在其中的私有范围总是公共的。记住这一点,现在让我们看看另一个使用更多全局变量的类似示例:
def one():
print(global_stuff)
第一个函数是前一个例子中第二个函数的模板副本。另一方面,第二个函数执行同样的运算,但是它在函数的私有范围内添加了 global_stuff 的定义:
def two():
print(global_stuff)
global_stuff = "Hello World!"
让我们像以前一样运行这两个函数,然后分析结果:
one()two()

正如所料,我们看到第一个函数为我们提供了与前一个示例中的第二个函数相同的回报。然而,每当我们运行第二个函数时,我们看到这个函数返回一个异常。这个异常与前一个例子中的第三个函数相同,我们试图在一个定义的别名被赋予类型或值之前引用它。为什么会这样?
正如我们前面提到的,Python 决定了什么变量在什么范围内定义,而不是在编译时定义,而不是在运行时定义。作用域也总是在引用它们上面的作用域之前检查自己。也就是说,这个函数试图自己检查 global_stuff 的定义。然后,它找到一个匹配项,但该匹配项尚未定义。结果,我们得到了这个错误!
结论
Python 有时会让人有点困惑,有时理解编译器和该语言的某些属性对编写优秀的软件大有帮助。从这个演示中得到的重要信息是 Python 在编译时定义了所有变量,而不是在运行时。这意味着当你的程序启动时,包含在你的软件的每个范围内的每个变量都已经被解释器定义了。我认为这肯定揭示了试图使用公共范围的一些问题,因为它会导致很多冲突。例如,如果该全局名称在函数中被回收,那么就很难判断实际的错误来自哪里。感谢您的阅读,我希望这是对 Python 编程语言中一个怪异小怪癖的有趣观察!
变量、数据类型和打印语句天哪!
深入编程基础,开始学习 Python!

克里斯蒂安·威迪格在 Unsplash 上拍摄的照片
智者可能会说,1000 个脚本的旅程从一行代码开始。学习编程是一项艰巨的任务,每当我学习新的框架或不同的编程语言时,我自己都是初学者。根据我的经验,我已经能够让许多以前没有或几乎没有经验的人开始他们自己的编程之旅。这是我自己对每个程序员每天都需要牢记的一些基本 Python 概念的看法。我将介绍变量、数据类型和打印语句的概念(天哪!)之前先展示一些例子帮你入门!
变量对日常编程极其重要,就像在你的数学课程中,它允许我们指定一个像“x”或“var”这样的变量来表示一个更复杂的东西。假设我想在代码中多次引用一个很大的数字,例如,光速是 299,792,458 米每秒。如果我想对这个值做不同的事情,比如用其他数字乘或除,我可以在每次想用它的时候把它写出来,或者我可以把这个值赋给一个变量,这样我可以节省时间,让我的代码更可读。这很容易做到,想出一个变量名,用一个等号,让我们做物理学家做的,选择' c '代表光速。
作者的光速数学
对于任何感兴趣的人,这是输出!

现在让我们讨论数据类型。我们的变量代表的东西是一个数据,这个数据可以是各种类型的。数据类型很重要,因为它告诉我们可以对某些数据做什么。例如,两个数字的加法就不同于像名称或标签这样的数据。最基本和最流行的数据类型包括整数、浮点、字符串和列表。
整数是没有小数部分的数字,像-6,-1,0,3,876 都是整数。浮点数是带小数部分的数字,像-6.0,-1.1 0.9238,3.1415,876.000001 都是浮点数。字符串是用单引号或双引号括起来的字符或字符组合,' h '、'再见'、' 42 '、'我爱编程'都是字符串。所有这些类型的数据中最复杂的是列表,它是由逗号分隔并用括号括起来的其他数据的集合。水果名称的 python 列表的一个例子是,['apple ',' orange ',' strawberry" ]。
我们可以用任何类型的数据制作变量!
按作者分类的变量和数据类型
所以现在我们可以创造变量,也可以用其他变量创造新的变量。有时候当我写很多代码的时候,我想看看我在一个变量中存储了什么,而不需要去我最初给它赋值的地方。查看变量的最佳方式是使用可能是最基本的 python 函数,即 print 函数。
我们可以使用 print 语句将东西打印到我们的输出或控制台。这让我们可以从计算中得到结果(就像之前光速中使用的那样),调试有问题的代码部分,并且通常深入我们的代码,看看在某些步骤和部分中发生了什么。要使用打印功能,我们只需键入 print()并将我想要打印的内容放在括号内。
按作者打印声明
我喜欢清理我的打印语句来创建漂亮的输出,在这种情况下,我将一个字符串放在我想作为标签打印出来的变量之前,然后用逗号分隔它。看一看清晰的输出!

按作者打印
这为我们想要看到的数据提供了一个非常清晰和格式良好的表示。为故障诊断代码编写有用的打印语句是我日常工作流程中不可或缺的一部分,这也是您在编写更大的项目之前需要首先学习的事情之一。
非常感谢你阅读我的文章,我希望你有一些工具和新的勇气来学习 Python 甚至另一种编程语言!
你可以在媒体上找到更多我的文章,在那里你可以看到我的最受欢迎的关于量子计算的文章。此外,在 Linkedin 上与我联系,并在我的 GitHub 上查看我的所有代码。
浓缩咖啡的变量
咖啡数据科学
制作饮料的清单
我一直在进行一些探索浓缩咖啡变量的基础实验,在这个过程中,我想我应该列一个清单。我原以为会很简单,但是名单变得很快。我对列表进行了更好的分类,我将把它分成几个类别。这并不是为了吓唬新来的人,而是为了展示这个过程的复杂性,这也是我喜欢浓缩咖啡的原因。
2018 年我的意式浓缩咖啡
几年前,我制定了制作浓缩咖啡的所有要素,当时我认为这很复杂。

所有图片由作者提供
咖啡豆

烘焙咖啡豆的变量是非常巨大的。从青豆到烘焙设备,这些变量中的每一个都会对味道产生很大的影响。


基本硬件

几乎同等重要的是研磨机和咖啡机。两者都有大量的变量来混淆什么是最佳配置。


浓缩咖啡篮/准备

尽管准备冰球看起来很简单,但它是浓缩咖啡中较为敏感的步骤。


这一枪

这些变量需要提前考虑,因为它们更难即时调整,尤其是对新手而言。


咖啡


这种液体黄金是咖啡最浓缩的形式,然而,即使在最终结果中,也有许多次要的事情需要考虑。


数据

对于像我这样的数据科学家,我收集一些拍摄后的数据,这是拍摄的一半乐趣,因为这些变量允许通过所有其他变量改进迭代。


在一天结束时,这些变量中的大多数从一个镜头到下一个镜头都是不变的。有相当多的因素融入到背景中,幸运的是,我们生活在一个社会中,我们可以尽可能多或尽可能少地钻研这些变量,随心所欲地享用美味的浓缩咖啡。
如果你愿意,可以在 Twitter 和 YouTube 上关注我,我会在那里发布不同机器上的浓缩咖啡视频和浓缩咖啡相关的东西。你也可以在 LinkedIn 上找到我。也可以关注我中。
我的进一步阅读:
来自 Oura Ring 和 FitBit 的数据差异——谁是对的?
结合像 Oura 和 Fitbit 这样的个人数据源,我们得出如何处理错误、差异和补充的结论
使用 Prifina 上的图形工具,我们可以查看用户的可穿戴设备数据,比较相同时间间隔内的不同数据点,探索数据本身的相关性和变化。对于不同的可穿戴设备,我们可以在相同的时间间隔(天、周、月)内探索来自同一用户的数据。

图片来自 Drobotdean (Freepik)
除了玩有趣的数据和从中获得潜在见解的娱乐价值之外,我们还发现了相同数据点中的差异,并提出了我们的应用程序应该信任哪些数据点的问题,以及如何处理大量个人设备和传感器之间的总体差异。
设备之间的比较使几个方面变得非常清楚:
- 对于同一时期的相同数据点,不同设备之间存在差异。
- 不同的可穿戴设备各有优缺点。
在本帖中,我们将探讨这些变化,如何处理它们,以及如何为不同类型的应用程序选择不同级别的数据。

图片由普里菲娜提供
Oura 和 Fitbit 设备都可以跟踪卡路里和新陈代谢。用户通过 Fitbit 和 Oura ring 设备测量的同一时期的总卡路里显示在上面的图表中。方差是<10% for 23% of the time period, <15% for 80% of the time period and 7% had a variation of ca. 50%.

Image courtesy of 普里菲娜
在此期间,这两款可穿戴设备还记录了总的睡眠时间。总睡眠与卡路里的变化类型相似,其中 53%的观察数据点在该时间段内的变化小于 10%,83%的总数据点的变化小于 20%。
值得注意的是,虽然两个设备之间有几个(确切地说是 14 个)数据对象是“共享的”(意味着两个设备跟踪相同的数据点,尽管有自己的测量和方法),但上面这两个是差异最小的。
所以,如果我们想要对方差下结论,我们可以假设它在统计学上是显著的。这对于特定的用例或应用是否重要,取决于实际的应用。
不同的设备有不同的优势
用锤子去拧钉子看起来很傻,然而我们假设同样的传感器装备在我们扔给它们的所有任务上。事实并非如此。Oura 戒指主要作为一种睡眠可穿戴设备进行营销,而 Fitbit 主要是一种个人运动可穿戴设备。能够有选择地比较来自更好器件的数据点与所讨论的数据点,可以产生一些有趣的见解,而且能够使用更强的器件最终将为应用提供最佳精度和价值。
抛开营销的立场,我们怎么知道什么是真的?我们当然可以跟着数据走。然而,为了确定哪些设备是最准确的,两个设备是不够的。幸运的是,通过 Prifina 的软件,用户可以将不同的可穿戴设备数据收集到自己的个人云中,应用程序可以利用该框架获得最准确和全面的数据点。
用软件补充硬件
类似于您的变焦视频质量在很大程度上不是由于您的设备摄像头,而是由于根据可用带宽优化质量的软件,我们不仅需要组合数据,还需要利用软件来优化和校正传感器数据本身。
这是我们和 Prifina 一起建造的。我们允许应用程序有选择地、明智地利用数据点,最终为最终用户提供最佳价值。睡眠即服务即业务可以使用来自 Oura 或 Apple Watch 的睡眠数据点,以及来自其他可穿戴设备的运动数据。
想看看你的自己的个人数据图表?
变分自动编码器:介绍和例子
使用变分自动编码器生成看不见的图像
你可能已经知道,经典的自动编码器被广泛用于通过图像重建进行表征学习。然而,有许多其他类型的自动编码器用于各种任务。这篇文章的主题是变分自动编码器(VAE)。如下图所示,VAE 也试图重建输入图像;然而,与传统的自动编码器不同,编码器现在产生两个矢量,解码器使用这两个矢量来重建图像。因此,给定分布,我们可以对随机噪声进行采样并生成逼真的图像。

可变自动编码器。图片作者。
VAE 原则
VAE 的目标是给定一个从预定义的分布生成的随机向量,生成一个逼真的图像。这对于我上次提到的简单的自动编码器是不可能的,因为我们没有指定生成图像的数据的分布。因此,战略如下:
- 编码器获取图像并输出两个向量,其中每个向量代表平均值和标准偏差。
- 我们把均值向量和标准差向量相加,先乘以一个随机的小值作为噪声,得到一个修正向量,这个向量和 is 大小一样。
- 解码器采用修改后的矢量,并尝试重建图像。
- 我们试图优化的损失值是 L2 距离和 KL 散度的组合,KL 散度测量平均值和标准偏差向量的分布分别从 0 和 1 的偏差。
因此,我们鼓励我们的平均向量具有以 0 为中心的分布,而后一个向量应该以 1 为中心(高斯分布)。最后,我们的解码器将能够从均值为 0、标准差为 1 的随机噪声(向量)中生成逼真的图像。
KL 散度
我们使用 KL 散度来计算我们的特征向量与平均值为 0 且标准分布为 1 的值的期望分布有多不同。损失计算如下:

KL 发散。图片作者。
其中σ和μ分别代表标准差和平均值。如图所示,目标是使平均值(μ)尽可能接近 0(通过平方该值)。而等式的其余部分确保标准偏差(σ)接近 1。请注意,我们使用对数来确保标准差不为负。
例子
我将使用的模型如下所示:
正如所见,我们的编码器输出的是方差的对数,而不是标准偏差向量,所以这里要小心。该示例在 MNIST 数字数据集上运行。最后,损失函数如下:
瞧!仅经过 10 个时期的训练,我们的解码器就能够产生非常逼真的随机噪声图像,其均值为 0,标准差为 1(可以使用 torch.randn 函数生成)。

VAE 生成图像。图片作者。
一些遗言
变分自动编码器是一个非常简单而有趣的算法。我希望这对你来说很容易理解,但是不要着急,确保你理解了我们讨论的所有内容。除了 VAE 之外,还有许多类型的自动编码器。通过下面的链接,你可以随意学习其他的自动编码器。谢谢大家!
https://lilianweng.github.io/lil-log/2018/08/12/from-autoencoder-to-beta-vae.html
变分自动编码器和生物信息学
变分自动编码器及其应用简介生物信息学/宏基因组学分析
一般来说,自动编码器旨在学习数据的低维表示。自动编码器的主要优势之一是它们能够学习复杂得多的低维,而 PCA 类分解受到其线性性质的限制。请随意看看我关于自动编码器的文章。

照片由 Hitesh Choudhary 在 Unsplash 上拍摄
像往常一样,我将谈论生物信息学中的一个应用。
可变自动编码器
与传统的自动编码器(AEs)相比,变分自动编码器(VAEs)属于生成模型家族。这是因为 VAEs 学习输入数据的潜在分布。因此,在给定这些分布中的新点的情况下,它们能够重建新的数据点。然而,VAEs 的生成方面并不经常使用,因为 gan 在这方面做得更好。
VAEs 的一个主要重要性是能够学习数据中的不同分布(类似于高斯混合)。这有助于对展示不同基础分布的数据进行聚类。
Python 中的必要导入
为了让您的下一个代码脚本工作,我们将需要以下导入。
import torch
from torch.nn import functional as F
from torch import nn, optim
from torch.utils.data import DataLoader
from torch.utils.data.dataset import TensorDataset
import numpy as np
from tqdm import tqdm, trange
import seaborn as sns
import umap
import matplotlib.pyplot as plt
VAE 的建筑

作者图表
与 AE 类似,我们有一个瓶颈阶段,随后是重建阶段。更正式地说,我们有一个编码器和一个解码器。注意潜在表示VL和下面的sigma和mu变量。从这些分布参数生成重建。我们完整的 VAE 类如下所示。

作者图片
重新参数化技巧
注意,我们有sigma和mu来传递渐变。换句话说,我们将设置 VAE 来学习给定输入数据集的适当的sigma和mu。我们使用重新参数化技巧来实现这一点。也就是;
decoder_input = mu + epsilon * e ^ std
注意,当我们选择使用e^std时,我们正在讨论对数方差。因此出现了代码logvar中的术语。
激活和损失函数
请注意,我们在许多地方使用 RELU 激活。但是,我们在logvar潜变量选择了softplus。这是因为对数方差总是大于零。在最终重建中,我们使用sigmoid,因为我们的输入数据维数范围在 0 和 1 之间。

作者图片
我们的损失函数由两部分组成。重建损失(RL)和 KL 散度(KLD)。我们特别使用 KLD,以确保学习到的分布尽可能接近正态分布(或高斯分布,或我们喜欢的一些分布)。你可以在这里阅读更多。
重建是一个很好的旧的均方误差。根据应用的不同,这可能会有所不同。例如,黑白图像(MNIST)可以使用二进制交叉熵。
最后,我们可以为支持最佳聚类(或宁滨等)的 RL 和 KLD 搜索适当的权重超参数。
使用 PyTorch 运行示例
让我们考虑一下我最近的一篇论文中的宏基因组数据集。使用 SimLoRD 应用程序模拟长读取。

LRBinner 数据集(许可证抄送)
现在,数据集使用以下工具进行了矢量化;
https://github.com/anuradhawick/seq2vec
数据加载器可以设计如下:

作者图片
训练 VAE 的功能;

作者图片
初始化;
data = LOAD DATA
truth = LOAD GROUND TRUTH # for visualizationdevice = "cuda" if torch.cuda.is_available() else "cpu"model = VAE(data.shape[1], 8).to(device)
optimizer = optim.Adam(model.parameters(), lr=1e-2200)
培训;
train_loader = make_data_loader(data, batch_size=1024, drop_last=True, shuffle=True, device=device)epochs = 50train(model, train_loader, epochs, device)
获得潜在表征;
with torch.no_grad():
model.eval()
data = LOAD DATA
data = torch.from_numpy(data).float().to(device)
em, _ = model.encode(data)
可视化;
import randomsidx = random.sample(range(len(data)), 10000) # just use 10000
em_2d = umap.UMAP().fit_transform(em.cpu().numpy()[sidx])plt.figure(figsize=(10,10))
sns.scatterplot(x=em_2d.T[0], y=em_2d.T[1], hue=truth[sidx])
plt.legend(bbox_to_anchor=(1.05, 1))
宁滨宏基因组学
一旦 VAE 被训练,我们就能获得潜在的表象。在这个例子中,我使用 UMAP 将 10000 个读数的样本投射到 2D 进行可视化。它看起来像下面这样。

作者图片
就像它看起来的那样,我们可以到处看到大量的数据点。人们可以很容易地使用像 HDBSCAN 这样的工具从中提取密集的簇。
结论性的评论
最初用 VAE 代表宁滨的想法是由 VAMB 代表宁滨议会提出的。VAMB 通常要求高重叠群计数(> 10000 左右)。这是因为你总是需要更多的数据才能在深度学习中表现得更好。考虑到所有这些挑战和机遇,我们开发了自己的工具 LRBinner 来绑定宏基因组学读数。在那里我们总是有数百万的阅读量。完整的 LRBinner 工具比我在本文中介绍的要复杂得多。但是直觉和想法保持不变。我们也使用了不同的聚类算法。如果你感兴趣,看看下面。
https://github.com/anuradhawick/LRBinner/
在这里找到完整的 Jupyter 笔记本。原 PyTorch 示例代码为此处为。
我希望你喜欢阅读这篇文章。祝您愉快!
变分贝叶斯:变分自动编码器背后的直觉
从一个复杂且通常难以处理的‘真实后验分布’中进行推断的高计算成本一直是贝叶斯框架中的绊脚石。然而(谢天谢地),有一些特定的推理技术,能够用某种……易处理的东西来合理地逼近这个棘手的后验问题。看到我在那里做了什么吗?

最近流行的一种近似推理技术是变分贝叶斯(VB)。相对较低的计算成本和良好的经验近似推动其驱动成功模型背后的直觉,如变分自动编码器等等。在本文中,我试图在变分贝叶斯背后建立一个直觉,作为一个潜在变量模型,通过优化一个叫做 Kullback-Leibler 散度的统计度量,寻找接近“真实后验分布”。
有趣的事实:虽然我们正在用 VB 构建一个后验分布的近似值,但这不是它的主要动机。当试图最大化对数边际似然时,后验近似思想出现了。你会看到的!—西蒙·罗杰斯和马克·吉罗拉米的第一门机器学习课程。
在我们继续之前,让我们举一个简单的(玩具)例子,为潜在变量的概念打下一些基础(并可能理解潜在变量和模型参数之间的区别)。
玩具示例:
生活质量(QoL) 是世卫组织用来描述特定地理区域生活状况的常用指标。它通常被定义为一个人健康、舒适、能够参与或享受生活事件的程度[1]。尽管量化这些抽象的量通常很困难,但基于一般的理解,我们可以提出一些(简单的)指标来影响这种 QoL 测量。例如,该地区的人均国内生产总值,就业率,教育质量,医疗保健,宜居性,幸福指数都是相当容易控制的特征,可能会影响生活质量。(见图)

一个简单的(完全虚构的)潜在变量模型。图片作者。
谈到我们的组合模型,我们可以看到有多种因素(无论是直接观察到的还是其他的)可能会影响我们的兴趣量,即 QoL 测量。由于我们无法单独测量这个抽象值,我们使用了多个替代(可观察)变量来量化它。这样的量被称为潜变量。潜在变量的值可以从可能影响它的可观察变量的测量中推断出来。许多现实场景都是这样,这也是潜变量模型有用的地方。
即使当我们(错误地)假设知道我们可以观察到的量和我们希望观察到的量之间的关系时,也可能有一些未知或隐藏的变量在起作用,但没有被解释。
但是,在使用潜变量模型时,会考虑这些未解释或隐藏的量。否则,这些量可能会在我们的模型中出现错误。关于有意建模错误的一些直觉,请看本文:
https://medium.com/swlh/thinking-generatively-c0bce13dd3e6
变异推理:贝叶斯方法
在消化了我们关于潜在变量概念的简短示例之后,让我们通过考虑具有一些可观察数据 Y、 以及由 θ 定义的模型参数和/或潜在变量的一般建模场景,将我们的变分推理的主要思想置于上下文中。在贝叶斯域中,我们通常将模型参数和潜在变量都作为随机变量来定义,并将其集合在 θ 项中。换句话说,我们把在给定场景中我们不知道的一切都看作是这个 θ 变量的一部分。敬那些关注的人,现在就关注最大化对数边际可能性吧!边缘看起来像这样,

p(Y,θ) 的关节密度可分解为 p(Y|θ)p(θ)。 作者塑造的形象。
其中,我们对参数【θ】的所有可能值的可能性 p(Y|θ) 进行求和,并通过先验的【p(θ)进行加权,我们希望最大化这个量。然而,由于在整个参数空间上潜在的高维积分,我们将不能实现这个表达式的精确评估;这就是为什么我们会利用简森不等式来给我们的 边缘项 的 log (是凸函数)下一个下界。

简森不等式允许我们设置一个下限。图片作者。
取边际可能性的对数,我们得到如下结果:

图片作者。
在应用简森不等式之前,我们先介绍一下 Q(θ), ,它是在【θ, 上的一个任意分布,通过在右侧将其相乘和相除。再看上面我们提到的不等式表达式,我们右边的项可以认为是对 p(Y,θ)/Q(θ) 项 w.r.t Q(θ)的一个期望。现在应用简森不等式,我们可以得到对数边际的一个下界。

我们根据**和* 得到一个下界的后验。 图片作者。*
假设我们已经任意选择了这个分布【Q(θ),这是一个我们可以微调的变量旋钮,这个事实将会是一个非常重要的启示,你将会看到。
现在,让我们计算我们的对数边际似然项和我们刚刚找到的下界【Q】之间的差。

求差: log p(Y) — L(Q)。 图片作者
积分项看起来熟悉吗?让我们清理一下,像这样:

边际和下限之间的差异(我们使用简森不等式发现的)等于负 KL 散度!图片作者。**
积分下的表达式算出(相当精确地)是我们真实的后验概率【θ| Y】和任意选择的 Q(θ)之间的 Kullback-Leibler (KL)散度。 有趣的是,KL 散度是一种常用于量化两个概率分布之间差异的度量,在我们的例子中,后验(我们希望近似)和任意选择的 Q(θ) 是那些两个分布。这个 KL 散度项在我们的变分贝叶斯练习的推导中的出现不仅仅是一个巧合;我们确实在努力寻找一种近似的分布,这种分布最接近我们真实的后验概率,瞧! Q(θ) 是一个变分旋钮,它允许我们调整 L(Q) 项,并且相应地最大化 L(Q) 将允许【Q(θ)到变得更类似于真实的后验概率(θ| Y)**
注 : KL 散度作为一个度量,一般小于或等于零,当两个分布相等时,其最大值即零出现。所以,万一我们找到真后验,那么 KL 散度项将为零,界限 L (Q) 将等于对数边际似然*!*

观想 Q(θ) 的变化,企图近似真实的后验。图片作者。
基本上,我们已经用一个下限代替一个整体分布来近似真实的后验分布,这样更容易优化。从上图可以看出,这个想法是试图使用任意分布 Q(θ)找到真实的后验概率。Q(θ)的这种优化作为后验概率的最佳近似, p(θ|Y) 通常通过类似于期望最大化* (EM)算法的迭代优化过程来实现。 Ravi Charan 关于 EM 算法的文章是一篇很好的读物(非常数学化)😗
*
这就结束了(潜在地)理解驱动最先进模型的强大思想的推理之旅,如变分自动编码器和生成对抗网络。约瑟夫·罗卡的这篇文章对 VAEs 进行了详尽的概念化,非常值得一读。
感谢您的宝贵时间!如果你喜欢这篇文章,请喜欢或分享内容以示支持,让我知道你的兴趣。在媒体上关注我或者通过 LinkedIn 联系我。
下次见!
参考资料:
- 维基百科生活质量文章(https://en.wikipedia.org/wiki/Quality_of_life)https://en.wikipedia.org/wiki/Quality_of_life
- 西蒙·罗杰斯和马克·吉罗拉米。2016.机器学习初级教程,第二版(2nd。由…编辑).查普曼&霍尔/CRC。*
具有正常化流的变分贝叶斯推断:一个简单的例子
思想和理论
使用张量流概率的基本介绍
1.0 简介
变分推理(VI)是一种值得关注的方法论。对于大型或复杂的贝叶斯建模,VI 有可能成为首选方法,尤其是因为在计算上 VI 自然适合机器学习的领域。本文展示了标准化流程——这是简要介绍的——如何极大地提高 VI 的性能,即使对于非常简单的模型也是如此。Tensorflow Probability (TFP)中提供了标准化流,样板代码借用了 TFP 文档。VI 和规范化流程的结合为推理建模创建了一个强大的工具。

图一。具有变换高斯替代后验的线性回归模型 TFP 的变分损失。图片作者。
1.1 为什么要贝叶斯推断?
近年来,贝叶斯建模已经成为许多科学和技术领域的主流。贝叶斯建模的一个定义特征是能力——实际上也是需求——指定关于要估计的模型参数的先验信念。在一些应用中,这种先验知识的整合是选择贝叶斯方法的关键驱动因素,例如,弥补可用数据的不足。在其他应用程序中,采用贝叶斯方法更多的是一个实用的模型设计问题;随着高质量贝叶斯计算库的可用性,例如【JAGS】、斯坦、 R-nimble 、 PyMC3 、 Tensorflow Probability 、 R-INLA ,有可能快速创建几乎任意复杂度的定制概率模型。
贝叶斯模型构建的灵活性带来了额外的风险和责任,例如,模型实际上有意义吗?先验信念的影响是什么?计算估计稳健吗?—诸如此类。对于贝叶斯模型构建艺术的专家观点,我们强烈推荐阅读 James Berger 教授的一些文章。 Stan 用户指南也是范例和良好实践的宝贵来源。
1.2 计算挑战
贝叶斯推理在很大程度上与有效地近似难以解决的积分有关。迄今为止最常见的方法是马尔可夫链蒙特卡罗 (MCMC)抽样,它通过从后验分布生成实现来避免计算积分的需要。有多种不同的取样器可用,这是一个有大量文献的活跃研究领域。BUGS——使用吉布斯抽样的贝叶斯推理——项目始于 1989 年,产生于 1997 年推出的 WinBUGS。通过消除建模者编写自己的采样器的需要,这是使贝叶斯推理和模型构建对非专家来说是可访问的第一个重要步骤。
MCMC 采样的一种替代方法是对难以求解的积分使用解析近似法。这是 R-INLA 采用的方法,它使用集成嵌套拉普拉斯近似。这种方法非常快,R-INLA 在地理空间统计建模中特别受欢迎,尽管这种方法不如 MCMC 普遍适用。
MCMC 的另一种替代方法是https://en.wikipedia.org/wiki/Variational_Bayesian_methods(VI)变分推理,这种方法的应用,使用张量流概率,是本文的重点。一篇优秀的关于 VI 的概括性介绍和评论在这里有stand具体文章这里。变分推理试图找到一个尽可能接近后验分布的(已知)概率分布。使用已知的分布避免了计算难以解决的积分的需要。变分推理通过最大化通常被称为 ELBO 的证据下限来实现这一点,ELBO 与kull back-lei bler-divergence(KL-divergence)密切相关,后者是一种量化两种概率分布彼此相似程度的指标。最大化 ELBO 使真实后验分布和替代分布之间的 KL 偏差最小化。
1.3 机器学习
近年来,变分推理变得越来越流行,并且在标准和张量流概率中都可用。

TPF API 的截图。图片作者。
原因可能是多方面的,但最重要的是因为 VI 自然地适合于机器学习(ML) ,而 MCMC 可能不适合。变分推理需要数值优化,而不是数值积分。机器学习建模通常需要数值优化——最小化一些 ML 模型和数据之间的损失函数。这意味着使用 VI 的贝叶斯建模可以利用最新的专门构建的 ML 框架、软件和硬件,例如跨 CPU 的快速并行化、高性能多 GPU 和其他高性能计算基础设施,如谷歌的 TPUs 。这开启了一个令人兴奋的可能性,即在未来,VI 可能成为大型或复杂贝叶斯模型的首选计算方法,而使用 MCMC 在计算上是不可行的。因此,变分推理是一个值得关注的领域。
1.4 双喷射器和标准化流程
使用 VI 进行贝叶斯推理的一个主要实际挑战是确定后验的候选分布——替代分布。我们需要足够灵活的分布,以紧密匹配真实的后验分布,同时在分析上易于处理,以避免那些难以解决的积分。多元高斯分布,通常经过一些变量的变换,可以说是最常见的选择。例如,Stan 中使用的方法(参见第 3 页,此处为)是首先将模型中的所有潜在变量转换到真实线上,例如,转换约束变量,如方差参数(必须严格为正)。然后为这些变换的变量以及那些已经在真实线上的变量设定多元高斯分布。
使用高斯代理分布的一个已知问题是,即使在必要的变换之后,模型参数的边际后验估计可能是欠分散的。这是因为高斯分布不够灵活,不能完全捕捉真实后验分布的形状。一个实际的例子在的张量流概率教程中给出。当与来自 MCMC 抽样的结果相比时,高斯替代不是真实后验的好估计,后者被假定为金标准。在本文的后面,我们将看到一个类似的分散不足的问题,使用一个不同的数据集和一个更简单的贝叶斯模型。所以这不仅仅是一个复杂模型的问题。
定位高斯代理的替代方法是采用上述张量流教程中使用的方法;使用一个可训练代理分布 ,它是通过使用 归一化流 (NF) ,特别是反向自回归流 (IAF)导出的。从概念上讲,标准化流程通过一系列双射变换将概率分布转换为不同的概率分布。双投影是应用于概率分布的变换,它确保变换产生有效的概率分布。概率密度必须整合为一,因此,如果变换拉伸或压缩参数,则必须相应地调整其密度,以在该密度下保持相同的体积。IAF 方法使用神经网络将双射体变换链接在一起,这些变换具有可训练的参数,因此 IAF 有效地针对后验分布训练代理分布。与高斯替代相比,这可以更好地估计后验参数。
2.0 建模
本文的其余部分是对 Colab/Jupyter 笔记本的概述——完整版本可从 GitHub 这里获得。我们只展示了足够的代码来提供关键步骤的概念。完整的代码有点长,但有相当多的重复,是现有 Tensorflow 概率教程加上对最新 TFP API 的额外调用的混合和匹配。TFP API 是一个移动的目标,因此笔记本使用硬编码版本(Tensorflow=2.6.0rc0,Tensorflow Probability=0.13.0)来避免以后的更改破坏代码,但也使用足够新的版本,因此它们包括。实验 API 函数。带有 GPU 运行时的 Google Colab 工作簿用于所有建模。*
2.1 数据
我们使用来自经典的 WinBUGS Rats 示例的数据,这是一个广泛使用的数据集,用于演示贝叶斯推理中的正常层次模型。数据集包括来自 n=30 只大鼠的观察结果,并且在 36 天的时间内测量每只大鼠的重量 m=5 次。这里我们不使用层次模型(排除先验),而是使用一个简单的线性回归模型,具有固定的效应截距和斜率。对于这些数据来说,这可能不是最好的方法,但选择这种方法是为了给出一个尽可能简单的例子来证明变分推断和标准化流。不难扩展所提供的代码,以将必要的层次结构/随机效应包括到似然函数中。

图二。Rats 数据来自 WinBUGS 示例手册。图片作者。
2.2 模型定义
下面是贝叶斯模型,我们将使用 BUGS 语言中定义的 rats 数据,稍后我们还将使用 JAGS 来拟合该模型(下面的代码与 JAGS 兼容)。
要点 1。bug 中定义的模型 1。
张量流概率(TPF)中定义相同可能性和先验的等效代码如下(要点 2)。这使用了对JointDistributionNamedAutoBatched的调用,这意味着组件是字典。代码可读性很强,但是使用 TFP API 构建一个联合分布可能有点复杂,因为有许多不同的形状参数。使用“自动匹配”调用变体有助于降低复杂性。有关有用的代码片段和 API 文档,请参见官方 TFP 教程。
要点 2。TFP 中定义的模型 1。
我们已经定义了要拟合的模型,接下来需要的是定义模型 1 中参数的后验密度的替代分布。VI 拟合过程将估计该替代中的参数,以便在给定可用数据的情况下给出最佳拟合。
要点三。模型 1 的高斯代理。
上述要点显示了替代密度的定义。模型 1 中的每个参数都在代理中定义。范围受限的参数,例如 tau _ alpha——它是一个方差参数,因此必须严格为正——具有应用于高斯密度的软加变换。这个代理是一组独立的高斯密度(在任何变换之前)。
2.3 模型拟合
我们已经定义了模型和代理,所以剩下的就是进行拟合——最小化负变分证据下限(ELBO)。简而言之,如果我们查看下面的拟合代码片段(要点 4),我们可以看到两个分布target _ model 1 . un normalized _ log _ prob和 surrogate_posterior (其中前者只是要点 2 中定义的 model1 分布,稍加修改使其成为 target_model1,详见 Jupyter 工作簿)。这里执行的优化是试图最小化这两个分布之间的差异(其中差异由 ELBO 度量的负值描述)。
要点 4。优化器代码。
上述要点基本上是不言自明的。将拟合过程包装到一个 tf.function 中极大地改善了计算时间,这也得益于 jit_compile=True 。 tf.function 将代码编译成图形,而不是在 eager 模式下执行(TF v2 中的默认模式)。
2.4 使用高斯替代的虚拟仪器的结果
与任何数值优化一样,跟踪模型拟合过程的进度以评估收敛性是很重要的。下图 3 显示了拟合过程中的损失函数(负 ELBO)。

图三。模型 1 的损失。图片作者。
损失图显示了每次迭代中负 ELBO 的均值和中值估计。其中每一个都基于调用 tfp.vi.fit_surrogate_posterior 时的 4 个数据点(我们选择 sample_size=4)。默认值为 sample_size=1。最小化似乎在大约 30K 次迭代后稳定下来。损失估计是相当嘈杂的,这并不奇怪,因为它是基于蒙特卡罗样本的变分散度估计。增加 sample_size(例如从 4 增加到 20)似乎对损失轨迹中的噪声只有很小的影响。该模型的 ELBO 估计为 ELBO=-663.5,我们希望最大化该值(图中显示为负 ELBO)。
图 4 显示了截距、斜率和标准偏差的边际后验密度估计值(分别为 Gist 2 中的参数αI、βI 和τc)。这些是在拟合过程之后使用从替代密度提取的实现来计算的。后验点估计值和标准差如图 5 所示。

图 4。模型 1 的边际后验概率。图片作者。

图五。模型 1 的后验点估计。图片作者。
3.0 基于双投影器的多元正态替代
上面使用的代理分布是通过使用JointDistributionNamedAutoBatched独立定义每个组件的密度(=模型 1 中的参数)创建的。对于更复杂的替代,例如,允许模型参数之间的协方差的多元正态,则需要使用 TFP 的双投影器功能的更灵活的方法。我们在这里简单地讨论这一点,因为它是通向更复杂的标准化流程方法的自然跳板。
下面的代码片段(要点 5)并不完整——缺少了一些基本的样板文件——但是展示了该方法的总体思想。定义了一个基本分布,这里是一组标准的独立法线。还定义了协方差结构,虽然这里使用的编码为简单起见假设了对角协方差矩阵(因此这与上面使用的代理没有很大的不同),但是这种方法可以使用更复杂的依赖结构。参见这里的一个更复杂的使用双投射器的块结构协方差的例子(这超出了本文的范围)。
最后一步是将一系列双投影变换链接到基本分布,其中一个是协方差矩阵变换,一个是位置参数,一个是确保先验的域匹配(例如,对于一些参数被约束为严格正的)。这种通用方法允许构建多元正态替代分布,该分布被约束到由模型定义的先验支持区域。这种方法当然不限于高斯分布,因为可以使用其他类型的基本分布。
要点 5。用双投影公式定义协方差矩阵。
下图类似于图 3,但这一次使用 bijector 转换构建了代理。该模型的 ELBO 估计为 ELBO=-663.9,在 ELBO 估计值周围的噪声容限给定的情况下,实际上与之前的模型相同(例如,参见图 6 中的右图)。鉴于代孕配方如此相似,这是意料之中的。

图六。使用双射体的多元正态替代模型 1 的损失函数。图片作者。
图 7 中的点估计也与图 5 中的类似,这并不意外,尽管标准差不太相似。

图 7。基于双投影的多元正态替代的点估计。图片作者。
使用规范化流程构建的 4.0 代理
为了建立在 3.0 节的基础上,我们现在使用一个通过规范化流程创建的代理。简而言之,虽然在技术上相当复杂,但是规范化流程允许我们做的是创建定制的概率分布,并针对手边的问题进行训练。要了解更多关于标准化流量的信息,这在密度估计中也有应用,参见参考文献这里。
我们再次使用来自 TFP 的一些样板代码,其中一部分与第 3.0 节中用于基于双对象的代理的代码相同。这里的关键区别在于,一种新的双投影变换——反向自回归流(IAF)——被应用于基本分布(同样是一组高斯分布)。根据 TFP 文档,“反向自回归流(IAF)是使用神经网络捕捉分布成分之间复杂的非线性依赖关系的正常化流”。
下面的代码片段(要点 6)展示了一个用于实现 IAF 以构建代理发行版的代码示例。这段代码大量借鉴了现有的 TFP 教程。感兴趣的主要部分在第 8–12 行,特别是要使用的 IAF 变换的数量,以及在 hidden_units 参数和激活函数的选择中定义的每个变换的参数化(第 12 行)。尝试了不同的 IAF 变换数、隐藏单元数和激活函数值。下面的方法似乎对当前数据集很有效,给出了可靠的损失函数行为(并不总是如此,对这些参数的每个选择进行检查是很重要的)。
要点 6。标准化流程模型定义。
为了根据数据训练 IAF 代理,所需的代码(要点 7)遵循与其他代理相同的形式,唯一的区别是引用新的 IAF 代理密度。
要点 7。优化器代码。
损失函数在性质上类似于前面的替代函数,但是请注意图 8 右侧面板中的不同范围和方差。与图 6 相比——图 6 是直接可比的,因为两者都使用 sample_size=1 来计算损耗,较大的值不稳定——我们可以看到损耗的噪声更大,而且分布略低于图 6 中的分布。

图 8。使用正常化流的代理损失函数。图片作者。
该型号的 ELBO 估计为 ELBO=-662.6 ,如果与之前的损失数字进行比较,那么 ELBO 应该略低似乎是合理的,但差异非常小,实际上可以忽略不计。
5.0 规范化流程在 VI 中表现更好吗?
我们使用了一个非常简单的模型——带截距和斜率的线性回归——并使用 VI 将该模型拟合到数据中,使用和不使用标准化的基于流量的替代密度。确定替代密度表现如何的一个度量是 ELBO 值。在之前的 TFP 教程中,显示了标准化流的替代密度具有更好的(2 倍)ELBO 值。从我们上面的结果来看,这里显然不是这种情况;IAF 代理的 ELBO 稍微好一点,但这很难成为支持更复杂的流量标准化方法的令人信服的证据。
为了更深入地了解标准化流动是否真的有助于解决我们当前的建模问题,我们将来自 VI IAF 模型的密度估计值与来自 JAGS(使用 MCMC)的密度估计值进行了比较。马尔可夫链蒙特卡罗抽样可以说是这里的黄金标准方法,因为它不使用任何替代密度。图 9 显示了从 MCMC (JAGS)获得的结果与使用第 3.0 节中的高斯代理和 IAF 代理从 VI 获得的结果之间的比较。

图九。使用不同方法的边缘后验密度。MCMC——绿色,标准化流——蓝色,变换高斯——粉红色,使用双投影器的约束高斯——红色。图片作者。
从图 9 中可以明显看出,标准化流模型比任何一种高斯替代模型都更接近 MCMC 的结果,实际上,对于截距和斜率参数,IAF 几乎与 MCMC 相同。因此,对于这个几乎是最简单的可能模型和数据的例子,当使用变分推断时,使用标准化流替代给出了实质上更好的参数估计。高斯代理明显遭受分散不足,这类似于在一些 TPF 教程中看到的情况(也使用具有更复杂协方差结构的高斯代理,我们在这里使用简单的对角矩阵)。然而,与 TPF 教程相比,有趣的是,在这个例子中,如果我们只使用了 ELBO 指标,考虑到所涉及的额外复杂性,我们可能会选择不使用标准化流程方法。这表明了检查参数估计的密度图的重要性。
这个简单的例子表明,当使用变分推断时,规范化流可以提供相当大的额外灵活性和可靠性。也就是说,基于 MCMC 的推理仍然可以为当前的许多问题提供最佳的实际解决方案,例如,这里使用的模型可以用 Gist 1 中的代码来拟合,只需增加几行代码来配置采样器,实际采样只需要几秒钟的计算时间。与任何基于 MCMC 的建模一样,需要执行各种诊断检查,但总的来说,对于许多问题,这仍然是一种非常有效的方法。
6.0 结论
本文提供了关于如何在 TPF 中应用变分推理,以及如何使用其规范化流功能来改进建模结果的简要顶级指南。GitHub 这里提供了一个独立的 Colab/Juptyer 笔记本,其中包含生成所示结果所需的所有代码。同一个存储库中还提供了通过 MCMC 适应相同模型所需的 JAGS 代码/文件。
规范化流程的变分推理是一个令人兴奋的领域,值得关注新的方法发展,尤其是涉及真实世界建模挑战的新用例。
关于作者
这是我,我在达能的研发数据科学部门工作,在那里我管理着一个庞大的数据科学家团队,我也很幸运地做了一些建模工作。
变分推理——旧物理学解决新的贝叶斯问题
一种新的分析方法

https://unsplash.com/photos/fBMpqsBYc3A
什么是 VI?
变分推理是一种解决最常见的贝叶斯问题的方法:给定一个观察到的数据,找到控制它生成的概率函数。虽然问题及其解决方案似乎很常见,但当 VI 首次出现时,它被认为是极具创新性的,因为它是针对 Bayes 问题的第一个分析性解决方案,而不是传统的采样方法。在这篇文章中,我将介绍虚拟仪器的早期出现,描述它的数学基础,并展示这个框架的前科学链接。
问题是
我们首先描述 VI 旨在以一致的方式解决的问题。我们正在处理统计数据,因此期待数据样本是合理的。让我们举个例子:
x,x,x..,x ⁰⁰..也就是我们观察到的数据。问题是一般性的,即样本可以是任何类型的数据:图像、语音话语、数字、比特或向量。
假设这个样本是由一个随机的机器生成的,这是非常简单的。作为研究人员,我们希望揭示这台机器的统计方式。在数学上,我们假设我们的观察数据是由控制样本 X 生成的隐藏变量 Z,Z …生成的。因此,我们要找到生成这些 Z 的函数。即找到 P(Z|X)。
下面是贝叶斯公式:

作者
LHS 被称为后验分布。我们希望在观察到的数据上找到这个函数。我们能使用 RHS 吗?
提名者可以写成我们的先验知识和获得的可能性的产物,因此它是已知的。因此,我们的问题集中在分母上。在某些情况下,这是容易处理的。然而,考虑从 K 高斯 GMM 驱动的数据。我们的隐藏变量是高斯参数和它们的权重以及分配的聚类。分母中的概率如下:

作者
很“优雅”不是吗?这种积分通常是难以处理的。人们需要一种方法来克服这一点。然而也有好消息: P(X)独立于 z。我们可以以不同的方式观察贝叶斯公式

作者
棘手的术语不再是障碍。我们仍然需要巧妙地处理 RHS。
直到 1999 年,解决这类问题的常用方法是使用抽样方法,如大都会、T2、黑斯廷斯、T4、吉布斯、HMC。
这些算法是无偏差的,但却患有采样的常规“疾病”:它们被赋予了巨大的方差。
1999 年,著名的迈克尔·乔丹退役了。同年,不太出名的迈克尔·乔丹发表了一篇论文,其中他提出了一种解决贝叶斯问题的新方法。这个想法是开发一个依赖于分析方法的解决方案。通过预先决定分布族(这一步增加了过程的偏差),我们可以构建一个对采样障碍有弹性的解决方案。在 2003 年,随着 Blei 发表了关于主题抽取的论文并将这种方法用于 LDA 过程,这种方法得到了极大的发展。然后他声称最佳的工作方式是“在等待 Gibbs 收敛的同时使用 VI”。
在接下来的几节课中,我将简要描述导致这种方法发展的数学步骤。
寻找最佳函数
为了提供后验分布的解析近似,我们需要两个工具:
- 测量两个分布函数之间距离的度量
- 在这些函数的空间上寻找极值的分析工具。
上面的子句非常明显,我们将使用 KL-Divergence。寻找函数空间的极值需要使用变分法中的工具。该领域处理诸如通过分析表示寻找点之间的最短路径等问题。我相信读者可以猜到这就是“变分推理这个术语的由来。这个领域的一个基本工具是欧拉- 拉格朗日 方程

作者
对于 F 实值函数、 x 自变量和ux 的一个可微函数,我们可以找到使 J 最小的 u 如下:

作者
现在,当我们有了这个工具,我们可以开始逼近后验函数。
因为我们希望逼近 P(Z|X ),所以我们搜索一个函数 Q (Z ),使其与真实后验概率的 KL 散度最小化。显然,当我们说“最小化 Q”时,这意味着我们搜索优化参数 Q ,因此我们必须定义我们将使用哪个分布族。在大多数应用任务中,我们用指数函数来表示 Q,
我们可以将 P(Z|X)和 Q (z)之间的 KL 散度写成如下

作者
其中,LHS 中的第二个术语表示为:

作者
ELBO 代表证据下限:最小化 KL 散度等价于最大化 ELBO,因为 P(X)独立于 z。
读者中的物理学家可能会发现这个公式类似于 亥姆霍兹自由能
解决 ELBO 技术
为了最大化 ELBO,我们需要一些假设和数值工具。
平均场定理
MFT 的想法来自于磁自旋的伊辛模型。这个模型过于严格,超出了本文的范围。然而,总的想法是,我们有一个观察到的序列 r.v X,X …接收值 1 和-1。我们的目标是估计一个依赖于这些观察变量的函数。它由线性和成对乘积项组成。从可以看出:如果我们假设 r.v 不相关,我们可以使用它们的平均值来估计值。
我们可以将这种想法移植到 VI:考虑不相关的对,我们将假设独立性,例如,考虑从高斯分布中抽取的数据,我们的 Z 是对的均值和方差。我们将假设这些参数是独立的,因此 Q 可以写成如下:

作者
将 Q 写成独立函数的乘积的想法简化了 ELBO 优化过程。
数字食谱
求解 ELBO 有两种常用方法:
坐标上升 VI(CAVI)——这个算法在这里和这里有详细描述。这个想法是在每一步 j 中计算 ELBO 的 P 部分,以所有不是 j 的 Z 为条件。(也就是说,每一步我们只更新一个 Z,而其他的 Z 都是已知的)。每一步的解与 P 项的过对数的平均值的指数(ELBO 中的互概率)成正比。
随机 VI — 这个算法使用随机优化(Robbins 和 Monro)来求解 VI。它允许一些在 CAVI 不存在的批处理方式,从而使它更实用,并使用关于 VI 参数的 ELBO 的衍生物。
摘要
我们介绍了虚拟仪器的数学基础。有一些应用已经使用了这种算法,最著名的有:
- GMM
- 主题建模
当然,这是 VAE 发展过程中必不可少的一部分
对动手项目感兴趣的读者可以在 Edward ,在 his implementation 和 Pyro ,以及 PyM C3 中搜索。
关于数字逻辑,已经有一些数字逻辑应用程序用于解决虚拟仪器问题的例子(例如,使用神经网络在数据上寻找虚拟仪器参数),但是,我相信(这也是我写这篇文章的主要动机)贝叶斯推理和数字逻辑的结合可能会提供大量的问题、机会和数学难题。我认为由 DL 训练高阶交换子的不太常见的分布问题非常有趣。
神经网络的变分推理
实践教程
神经网络的贝叶斯近似介绍

变分推理图解。作者图片
贝叶斯分析是量化模型预测的不确定性的好方法。
大多数常规的机器学习建模任务涉及在给定模型及其参数的情况下为数据定义似然函数。目标是在称为最大似然估计(MLE)的过程中最大化关于参数的似然性。最大似然估计是模型参数的点估计,这意味着只进行一次预测。最大似然估计参数用于推断。
在贝叶斯建模中,除了似然函数,我们还必须定义模型参数的先验分布。我们使用贝叶斯规则来寻找后验参数分布。后验分布和模型可用于生成预测的概率分布(如下所示)。这使得我们在使用贝叶斯建模方法时能够量化不确定性。如果我们不再局限于点估计,我们可以选择是否信任基于分布范围的模型预测。

贝叶斯规则和贝叶斯推理。作者图片
这很好,但有什么问题呢?贝叶斯推理通常是一项非常昂贵的工作。计算后验分布通常要么很难,要么在分析上极其困难。此外,即使后验概率有一个封闭的解析形式,对任何合理复杂的模型来说,计算所有参数的积分基本上是不可能的。
有一些方法可以减轻这一困难:
- 最大后验估计(MAP)找到后验分布的峰值,并将其用作模型的点估计(这通常比仅使用可能性要好,但不能为我们的预测提供不确定性的度量)。
- 马尔可夫链蒙特卡罗(MCMC)允许我们从后验样本中抽取样本。然而,它可能会非常慢,特别是对于大型模型和数据集,并且它不能很好地处理高度多峰的后验分布。
- 变分推断(VI)用一个更简单的“行为良好”的分布来近似后验概率。这就是我们在本文剩余部分要探讨的内容。
什么是变异推理(VI)?
变分推理的目的是用一个“行为良好”的分布来逼近后验概率。这意味着积分的计算使得估计越好,近似推断就越准确。让我们来看看一些数学知识,然后我们会看到这如何应用于神经网络。
首先,我们需要定义概率分布之间的距离度量,我们可以使用它来最小化。为此,我们选择了一种称为 Kullback-Liebler (KL)散度的距离度量。我们选择 KL 散度而不是其他距离度量的原因将在一会儿变得清楚,但这基本上是因为它与对数似然性密切相关。
KL 具有以下形式:

KL 定义。作者图片
如果我们用 p(x)项代替后验概率,并做一点重新排列,我们得到…

操纵 KL。作者图片
现在我们可以用下面的方式利用 KL 散度总是正的这个事实…

自由能定义。作者图片
F(D,q)项称为变分自由能或证据下界(ELBo)。重要的是,最大化 ELBo 使近似后验概率和真实后验概率之间的 KL 偏差最小化。最后一行的自由能形式,是对我们最优化最有用的形式。
我们之前讨论过一个细节,我说过我们需要用一个“表现良好”的分布来近似后验概率,但是什么是“表现良好”呢?一种流行的选择是将所有参数的联合后验概率近似为独立分布(通常是高斯分布)的乘积。独立性条件允许使用多种优化方法来最大化 ELBo,包括坐标上升和梯度上升。选择高斯分布有许多原因,包括它们是一个共轭先验以及高斯分布之间的 KL 具有清晰的闭合形式。

高斯之间的 KL。作者图片
这如何应用于神经网络?
那么,我们如何将 VI 和平均场近似法应用于神经网络呢?
从非贝叶斯网络到变分贝叶斯网络的过渡是相当平滑的。通常我们会创建一个带有权重的密集图层,但这些只是点估计,现在我们想将每个权重建模为一个近似的后验分布。假设每个权重都有一个均值为μ、标准差为σ的高斯后验概率。
为了最大化 ELBo,我们需要两个东西,近似后验(q)的平均似然性和 q 与先验之间的 KL。为了计算平均似然性,我们从 q 中抽取蒙特卡罗样本,并通过向前传递一个小批次来估计平均似然性(就像我们通常做的那样)。q 和先验之间的 KL 有一个很好的封闭形式,因为我们选择一切都是高斯的。
那么我们可以用梯度下降法,对吗?不完全是,有一个小的微妙之处,你不能对随机的东西求梯度,这是没有意义的。这是选择高斯函数的另一个原因,您可以通过以下方式参数化高斯函数:

重新参数化技巧。作者图片
现在我们可以对μ和σ求梯度了!
值得注意的是,我们将模型中的参数数量增加了一倍,因为我们现在对每个模型参数的均值和标准差有了单独的权重。这大大增加了模型的复杂性,却没有提高模型的预测能力。
在 PyTorch 看起来像什么?
嗯,看起来是这样的…
对于这个实现,有一些重要的事情需要注意。首先,我们不直接为方差创建权重。相反,我们创建权重使得σ = log(1+exp(w))。我们这样做是为了优化过程中的数值稳定性。第二件事是,我们累计每一层的 KL 损耗,稍后你会看到,我们将损耗传递给下一层。我们可以这样做,因为 KL 项不依赖于数据,它有助于我们保持对总 KL 损失的标签,如果我们只是把它加起来。
现在让我们把它放到一个模型中:
理论上很可爱,但实际可行吗?
很棒的问题!让我们来了解一下!让我们以 MNIST 为例,训练一个模型对手写数字进行分类,看看结果是什么样的。我没有在这里包括训练循环代码,因为这都是相当样板,没有什么花哨的,只是在 MNIST 上训练一个模型大约 5 个时代。
值得注意的一点是,当我们使用该模型进行预测时,我们希望使用来自 q 的样本进行多次预测。这样,我们就释放了贝叶斯网络的真正力量,即预测不确定性的能力。知道您的模型何时对预测有信心可以帮助我们将人包括在循环中,并通过只接受模型有信心的预测来提高我们的整体准确性。

准确性是用于验证的数据百分比的函数。作者图片
为了创建该图,基于预测中的不确定性对所有点进行排序。然后,我们基于预测的不确定性迭代地放弃预测,并评估新模型的准确性。如果我们对所有的数据进行预测,不考虑我们的确定性,我们可以预期验证的准确性约为 93%。通过只标记 5%的数据,我们可以将模型准确性提高 2%以上!一般来说,我们标记以供审查的预测比例越高,模型实现的准确性就越高。我们可以使用这种方法来计算阈值不确定性,该阈值不确定性应该用于决定是否应该标记某个预测以供审查。
我们还可以看看模型不确定的一些样本…

不太可能的例子 3。作者图片
在这种情况下,我们预测 3,但标签是 5…老实说,我不知道你,但我可以看到混乱来自哪里!
我们学到了什么?
我们希望在这篇文章中实现两个主要目标。首先,我们了解什么是变分推理以及它为什么有用,其次,我们知道如何实现和训练利用 VI 的深度神经网络。所以下次当你设计一个用于分类或回归的普通神经网络时,考虑把它变成贝叶斯网络!
MNIST 上带正规流的变分推断
思想和理论
了解规范化流程以及如何在创成式建模中使用它们
介绍
在这篇文章中,我将解释什么是规范化流程,以及如何在变分推理和设计生成模型中使用它们。本文的材料多来自【Rezende and Mohamed,2015】,我相信这是第一篇引入基于流的模型概念的论文(本文标题与论文标题几乎相同)。有许多其他有趣的论文跟进了这篇论文,并使用基于流的模型来解决其他有趣的任务。然而,在这篇文章中,我的重点仅仅是第一篇论文和最基本的概念。
我试图一步一步地解释这些模型是如何工作的,每一步都有 PyTorch 代码片段。模型的代码和可视化都可以在这个 Github repo 中获得。
在开始规范化流程之前,回顾一下什么是变分推理以及规范化流程与它的关系是有帮助的。
什么是变分推理?
假设我们有一组观测值 x ,x ,…,x ⁿ,其中它们是我们不知道的分布 p(x)的独立同分布样本(这些样本不一定在一维空间,可以是多维的)。同样,假设有一组假设变量zt14】,zt16】,…,z ⁿ在后面生成这些数据样本。事实上,为了生成数据样本,首先从分布 p(z) 中抽取一个潜在变量,然后用它来计算我们观察到的数据样本 x 。由于 z 在现实世界中是不可观测的,它们被称为潜在变量。
注:这只是我们自己的世界模型,潜在的变量仅仅存在于我们自己的想象中!最有可能的是,z's 和上述生成数据样本的过程在现实世界中是不存在的。
我们可以用 x 和 z,的联合分布,即 P(x,z) 来数学地指定我们的模型。这是我们模型的一个充分的表示,人们可以从这个联合分布中计算出 P(x) 和 P(z) 。我们通常喜欢用一些参数 θ 来参数化分布,其中大多数时候 θ 是神经网络的参数。因此,我们可以通过将分布写成 P(x,z;θ) 。现在,类似于机器学习中的大多数问题,我们的目标是学习模型的参数。最常见的方法是在我们的模型下最大化观察数据的对数似然性(即 x )。为了实现这一点,我们需要能够计算边际可能性P(x;θ) 为此,我们必须通过对潜在变量的所有可能值求和/积分来边缘化潜在变量。但是大多数时候这是难以想象的昂贵,因为有很多 z !嗯嗯…..如果不可能计算一个简单的可能性,那么我们如何进行最大可能性训练呢?
这个问题有一个可能的答案:
1)引入一个辅助分布,比如q(z | x;ϕ) ,我们可以很容易地计算和工作。这种分布用作难以处理的后验概率 p(z | x;θ) 因而称为近似后验或变分分布。
2)我们不是直接最大化对数似然,而是最大化它的一个称为 ELBO 的下限,它由下面的公式给出:

情商。[1]第 15 页
ELBO 在上式中为 -F(x) 。很容易证明,最大化 ELBO 等价于最大化对数似然(或者最小化负对数似然等价于最小化负 ELBO)。因此,我们的新目标是:
学习模型的参数,即θ,和变分分布的参数,即通过最大化 ELBO 联合学习。
换句话说,变化的参数也使我们能够学习模型的参数。一种优雅的方法是使用可变自动编码器。然而,如果你熟悉 VAEs,你就会知道它们有一个缺点:变分族在大多数情况下过于简单,表达能力不够(通常是多元高斯分布),不能逼近任意复杂的分布。这就是正常化流程帮助我们解决这个问题的原因。
什么是正常化流程?
标准化流是可以从简单分布开始并逼近复杂分布的模型。他们通过用一些函数多次变换初始分布,直到分布变得足够复杂。为了转换分布,我们可以使用可逆函数 f 。从变量变换公式中,我们知道变换变量的 pdf 可以计算如下(等式)。第 5 页,共 1 页):

现在,如果我们顺序地堆叠 k 这样的可逆变换,最后一个变量的密度可以导出如下(等式)。[1]的 6 和 7):

情商。[1]的第 6 和第 7 页
我们将在稍后为生成模型定义的损失中看到最后一个变量的对数概率。
这里需要注意的一点是,这些变换必须高效且易于计算,特别是考虑到密度公式中有一项包含行列式(行列式通常很难计算)。幸运的是,[1]提出了两类变换,它们的行列式很容易计算。它们也足够强大,我们可以从一个简单的发行版开始,仅仅使用这两个转换就可以创建一个非常复杂的发行版。
- 平面流:该变换的公式如下:

该变换采用一个 D 维向量,并在垂直于由权重 W 和偏移 b 指定的超平面的方向上扩展/收缩该向量。平面流转换可以在 PyTorch 中实现,如下所示:
为了更好地理解这一层的表现,我在 2D 空间中用十个不同的|u|值可视化了一个简单流层的输入和输出。以下是可视化的代码:
结果如下图所示,其中蓝点代表初始分布,红点是它们的变换版本。此外,每个点都用实线连接到其变换后的对应点。我们可以很容易地看到,这些线都是平行的,并且垂直于超平面 w.x+b=0 ,它就是直线 x+y=0 。

不同向量的平面流输出的可视化
2.径向流:第二类变换称为径向流,可用以下公式描述:

该变换围绕空间中的单个点扩展或收缩初始分布,并且可以在 PyTorch 中实现如下:
同样,我们可以用不同的值 β 和原点(0,0)作为变换的中心,在 2-D 空间中绘制该变换的可视化。与前面的可视化类似,蓝点表示初始分布,红点表示应用变换后的点(注意,这些线在变换中心的原点相交):

不同β值径向流输出的可视化
我们可以使用这两层的序列,并将简单的初始分布(例如多元高斯分布)转换为复杂的分布(例如多峰分布)。
基于流程的生成模型的实现
为了实现基于规范化流程的生成模型,我们可以使用本文提出的以下架构(图 2,共[1]):

基于流程的生成模型的架构(图 2,共[1])
这个模型由以下三个模块组成,我们将在 PyTorch 中逐一实现它们。
-
编码器:首先有一个编码器,得到观察输入 x ,输出随机变量流中第一个变量的均值(如 μ )和 log-std(如 log(σ) ),即 Z₀ 。值得注意的是,这个编码器类似于变型自动编码器的编码器。
-
Flow-model :编码器后有一堆流层,将第一次分布的样本(即 Z₀ 来自分布 q₀ 的样本)转换为复杂分布 qₖ 的样本 Zₖ 。请注意, Zₖ 是生成数据的主要潜在变量,我们已经使用了所有编码器+流层来推断 Zₖ 。因此,如架构图所示,我们可以将这两个模块合称为推理网络。以下是流模型的实现:
从上面的代码中可以看出,我们在流模型的实现中利用了重新参数化的技巧,其中我们将随机性移动到来自另一个标准高斯分布的样本,例如ε,然后使用 σε+μ* 作为初始样本 Z₀ 。有了这个技巧,我们就能够将梯度传递给编码器网络,并对其进行训练。
此外,我们在 log_det 变量中累积每层计算的对数行列式。该模型输出该变量,该变量将在以后用于计算损失。同样的论点也适用于 Z₀ 和 Zₖ 的对数概率,它们也出现在我们计算的损失中。
3.解码器:最后,解码器取潜变量 Zₖ 和模型 P(x|zₖ) (或者它的非规格化版本,有时叫做 logits)。同样,它类似于变分自动编码器的解码器,并且可以实现为完全连接的网络:
现在,让我们指定这里的θ和ϕ。θ是我们的真实世界模型的参数,它由先验的参数 p(z) 和参数 P(x|zₖ) 组成。我们的先验是标准的多元高斯,没有参数。因此,我们的模型的唯一参数是我们的解码器【p(x|zₖ】的参数。ϕ是变分参数,它由帮助我们从数据 x 中近似真实后验分布的所有参数组成。因此,它包括编码器和流模型的参数。
失败
训练任何机器学习模型的最重要的部分之一是损失函数。如论文所述,我们将优化负 ELBO 作为我们的目标,其通过以下公式(等式)计算。[1]的第 15 页):

第一期望值内的项在 q₀(z₀) 是初始变化样本的对数概率,它是流量模型的输出之一。第二个期望中的项, log p(x,zₖ) ,也可以写成
logp(x|zₖ)+logp(zₖ)。在这个等效表达式中,第一项是解码器的归一化输出(我们将使用 sigmoid 函数归一化解码器的输出),第二项是作为流模型的另一个输出的 zₖ 的对数概率。最后,第三个期望中的项是流模型的 log_det 输出。注意,我们可以通过取小批量的平均值来随机估计期望值。下面是 PyTorch 中最终损失的计算(D 是随机变量的维数):
在二值化的 MNIST 数据集上训练模型
最后,我们可以在二值化的 MNIST 数据集上训练具有上面定义的目标的生成模型。以下是该数据集中的一些示例:

二值化 MNIST 数据集
我们唯一要做的事情是定义一个模型,将数据作为小批量加载,定义一个优化器(为此我们使用 Adam ),并编写一个训练循环:
搞定了。这是 PyTorch 中从 0 到 100 的一个简单的基于流的生成模型的实现!
承认
[1] Rezende 和 Mohamed 2015,用归一化流量进行变分推断 。
https://github.com/tonyduan/normalizing-flows
https://github.com/karpathy/pytorch-normalizing-flows
VarifocalNet (VF-Net):最新的物体探测网络
引入 IoU 感知和变焦损失,提高 SOTA 物体检测分数

在 COCO 女士上的大量实验表明,我们的 VFNet 在不同主干上持续超越强基线∩2.0 AP。我们最好的型号 VFNet-X-1200 和 Res2Net-101-DCN 在 COCO test-dev 上实现了 55.1 的单模型单尺度 AP,这是各种对象检测器中最先进的。代码可在:https://github.com/hyz-xmaster/VarifocalNet.获得
来源: VarifocalNet
几周前,当我在做一个物体探测 Kaggle 比赛时,我偶然发现了 VarifocalNet。我很惊讶地看到它匹配许多 SOTA 对象检测模型,如 YoloV5 和 EfficientDet,在某些情况下甚至超过他们。我亲自检查了这篇论文,我非常喜欢它。它引入了许多我感兴趣的新概念,比如变焦距损耗、IoU 感知分类分数等等。
该论文着重于精确地挑选出所产生的包围盒、优化它们的精度以及过滤它们的挑战,这是对象检测的本质。在一个小区域内存在多个对象的密集对象检测任务中,对象检测模型通常表现不佳。在这些情况下,挑选出正确的边界框变得困难,并且简单的损失函数(例如交集/并集)通常表现不佳(因为框重叠太多,即使它们是正确的)。
VarifocalNet 使用变焦距损失来预测每幅图像的 IoU 感知分类得分。变焦损失是由聚焦损失引起的。如果你对此感兴趣,那就继续读下去吧!
变焦损失介绍:
在我们开始解释变焦距损失之前,我们需要看一下它的前身,焦距损失。焦点损失是经典交叉熵损失的升级。它试图通过向困难的数据点分配更多的权重来优化类别不平衡问题。你可能会想,是什么带来了阶级不平衡的问题。典型的对象检测网络评估每个图像的非常大量的候选框位置,但是这些候选框位置中只有一小部分实际上包含有效对象,这模拟了类别不平衡问题。
焦点损失本质上是在交叉熵中引入了一个“调制因子”,它减少了损失贡献[1]简单的例子,并增加了假阴性/阳性的重要性(这使得网络更加精确)。
这里要注意的一件重要事情是,在典型的对象检测任务中,正样本比负样本少得多(特别是在密集对象检测任务中)。在这里停下来想一想如何利用这个提示来改善聚焦损失可能是值得的。
变焦距损失不对称地对待正例和负例[1],不同于焦距损失平等地对待它们。这意味着正面例子的损失减少量与负面例子的减少量不同。
我们用训练目标 q 来衡量正面例子。如果正面例子具有高 gt IoU,则其对损失的贡献将因此相对较大。这就把训练的重点放在了那些高质量的正面例子上,这些例子对于获得更高的 AP 比那些低质量的例子更重要。
为了平衡正例和负例之间的损耗,我们在负损耗项中增加了一个可调的比例因子α。
来源: VarifocalNet
我总是喜欢强调我所解释的任何新模型的不同之处。我认为损失函数可能是 ML 模型中最重要的事情之一(如果不是最重要的话)。因此,了解新模型中损失函数的变化非常重要。
IACS 和星形盒要素制图表达

在我们解释 VFNet 为每个图像预测的新分数之前,我们必须探索 FCOS+ATSS(具有自适应训练样本选择的全卷积单级)架构,因为这是 VFNet 所基于的。许多对象检测网络是基于锚点的,这意味着预测的框依赖于平铺在图像上的预设锚点。然而,FCOS 试图摆脱锚,提供无锚网络(不需要 IoU 匹配),无提议(使检测只发生在一个阶段),并最终只使用卷积(使它更简单)。
自适应训练样本选择,或 ATSS,是一种根据对象的统计特征自动选择正样本和负样本的方法。它弥补了基于锚和无锚检测器之间的差距。
来源: Arxiv
如果你有兴趣了解更多关于 FCOS 的信息,我建议你看看这篇文章:
我知道我们很快引入了很多概念,但是请耐心听我说。新 SOTA 模型的美妙之处在于,它们几乎总是建立在几种新颖技术的基础上,了解每种技术以及它们是如何组合成一个单一模型的,这是优秀的数据科学家与众不同的地方(当然是在我看来)。这篇文章不足以深入每个概念,所以我将尽力简要地解释它们,如果您需要,只需查看整篇文章中列出的附加资源来增强您的理解。
FCOS 网络预测每个图像的分类分数(除了边界框坐标之外)。VFNet 的作者发现,用预测的边界框和地面真实值之间的 IoU[1](gt _ IoU)替换这个分类分数可以大大提高性能(COCO 上的 74.7 AP vs 56.1 AP)。这主要是因为高质量对象检测的主要秘密是从大量的预测框中选择正确的边界框,而 gt_IoU 在这方面比传统的分类得分更有帮助。
作者还设计了一个更不同的边界框,一个更符合 IACS 分数的星形框。这个星形盒子有 9 个固定的采样点[1],可以更准确地捕捉上下文信息。该星形框还允许在最终预测之前进行更加有效和准确的边界框细化阶段。VFNet 还在最后的边界框细化阶段使用 NMS(非最大抑制)来进一步消除冗余框。
最后要注意的一点是,我喜欢的是,在他们的结果部分,他们有一个名为“单个组件贡献”的部分。本质上,他们把他们引入的每个组件与他们的标准组件互换,并显示结果的差异。这使我们能够看到每个组件对性能改进的贡献有多大。
结论
请随意检查论文的最终实验和结果,显示它如何优于其他 SOTA 模型。我觉得在这里复制和粘贴一个巨大的结果表没有意义,我对网络如何工作更感兴趣。我希望您现在对整个网络的工作原理有了一个更全面的了解。总而言之,VFNet 建立在 3 个主要组件之上。第一个是 FPN(特征提议网络),第二个是主干 CNN,第三个是 VFNet 头(使用上面讨论的变焦损失和 IACS)。如果您对编写 VFNet 比深入理论部分更感兴趣,我建议查看我的关于在自定义数据集上实现 VFNet 的 MMdetection 文章:
谢谢你
感谢您阅读本文:)如果您喜欢这篇文章,请考虑在这里给我买杯咖啡(咖啡有助于我写作):
https://www.buymeacoffee.com/mostafaibrahim
如果你想定期收到关于人工智能和机器学习的最新论文的评论,请在这里添加你的电子邮件并订阅!
https://artisanal-motivator-8249.ck.page/5524b8f934
参考文献:
[1] 变焦镜头
在医疗保健中应用机器学习的各种挑战

比尔·牛津在 Unsplash 上的照片
了解机器学习和数据科学在医疗诊断中使用的各种方式和方法,并了解一些挫折和挑战
机器学习正被用于汽车、制造、零售等多个行业。随着机器学习和深度学习算法的发展,出现了大量有用的预测,如预测股价、房价和贷款违约预测。此外,有不同格式的数据可用于机器学习预测。随着数据的不断增长,机器学习领域有很大的发展空间,未来的预测会越来越好。

freestocks 在 Unsplash 上拍摄的照片
机器学习的一个有趣应用是在医疗保健领域。我们在互联网上看过一些电影,在这些电影中,机器人分别执行医生的工作和做出正确的诊断。像《安德的游戏》这样的电影展示了机器人是如何用于医疗诊断的。在医疗诊断领域,机器学习有许多新的应用。因此,该领域有很大的发展空间和改进空间。
在机器学习和数据科学的帮助下,越来越多复杂的算法正在开发中。机器学习在医疗保健中的一些很酷的应用是预测癌症发生的几率和阿尔茨海默氏症。看看这些应用,我们可以得出一个结论,机器学习仍在增长,未来对它的需求也会增加。现在,有越来越多的复杂算法正在开发中,用于机器学习,以进行稳健的预测。
在放射学中实现了机器学习模型,其中机器正在进行预测,这将确保我们在测试集上也得到了最好的结果。
在医疗保健领域使用机器学习的挑战
尽管机器学习有可能被用于许多行业,尤其是医疗保健行业,但当数据科学家和机器学习工程师试图将这些算法用于医疗诊断时,他们仍然面临一些挑战。了解这些挑战是有益的,以便人们可以收集资源和工具来解决它们,并利用人工智能产生更好的结果。

尝试为医疗保健数据实施机器学习模型可能会面临挑战。处理医疗保健数据时的一个挑战是,对于机器学习模型来说,这些数据可能是因果。因果关系的意思是,当有数据时,如果一个特征导致另一个特征的出现,则可以说该关系具有高度因果关系。在大多数算法的机器学习中,我们假设特征彼此独立,没有一个特征导致另一个特征出现,反之亦然。因此,当特征之间分别存在高因果关系时,该假设将被削弱。
数据科学家和机器学习工程师短缺

Mick Haupt 在 Unsplash 上拍摄的照片
到目前为止,我们已经讨论了数据和机器学习算法,这些是在医疗保健中使用数据科学的限制。然而,如果缺少使用这些算法的人,那么在医疗行业实施人工智能也将是一个重大挑战。由于机构中机器学习和数据科学课程的数量增加,有才华的专业人士进入该领域并取得成功的机会更高。
机器学习中会出现偏差

Alex Padurariu 在 Unsplash 上拍摄的照片
当执行机器学习任务时,会存在偏差,这可能导致机器学习模型分别在未知或测试集上表现不佳。机器学习模型中存在的偏差可能是由于提供给机器学习模型的数据类型造成的。例如,如果提供给模型的数据包含关于特定类别的大量信息和关于少数类别的较少信息,而没有考虑不同的场景,则机器学习模型将对反映包含偏差的训练集的结果的测试集进行预测。结果,模型实现了主导类(多数类)的非常高的准确性,而在少数类上表现不佳。
缺乏质量数据

尽管机器学习算法投入使用的可能性很大,但在医学领域仍需要大量数据来充分利用它。以医学图像的形式呈现的数据在数量上非常少,不能有效地用于测试。此外,存在的数据没有被标记,因此它可以用于机器学习目的。为机器学习标注大量数据确实需要很长时间。
数据标注必须准确无误

JOSHUA COLEMAN 在 Unsplash 上拍摄的照片
数据以医学图像和其他有用信息的形式无处不在。尽管存在大量的数据,但没有注释的例子或预测的输出标签。因为当有输出类标签时,一些最好的机器学习算法在监督下会工作得很好,所以我们需要提供带注释的数据。这将确保带注释的示例将有助于机器学习模型拟合,并确保分别有准确的预测。在医疗数据中,需要对数据进行注释,这是一个耗时的过程。因此,这是在医学中使用机器学习的挑战之一。
需要超参数调谐

有许多复杂的 ML 模型正在开发中,其中一些是随机森林、决策树和神经网络(深度学习)。这些算法的一些挫折是能够调整(改变)超参数,以便它们在测试数据(看不见的数据)上产生非常好的性能。为了让它们更好地工作,这些超参数需要改变和持续监控,以便提高它们的性能。然而,这可能是一项单调乏味的任务,尤其是当有如此多的参数需要调整和监控以获得最佳结果时。因此,设置正确的超参数并改变它们以获得最佳结果也是将机器学习应用于医疗保健的挑战之一。
结论
总而言之,我们已经讨论了机器学习和数据科学如何用于在医疗数据中进行预测。我们还看到,我们拥有的数据越多,模型就越有可能很好地了解基础数据并做出正确的预测。然而,当涉及到将机器学习应用于医疗数据时,我们已经看到了机器学习领域的一些挑战。希望我能够给出一个关于机器学习模型在医疗保健中的使用的好主意。请随意分享你的想法和主意。谢谢!
如果你想进一步了解我的工作,下面是我们可以联系的细节,你也可以查看我的工作。谢了。
GitHub:https://github.com/suhasmaddali
领英:https://www.linkedin.com/in/suhas-maddali/
https://www.facebook.com/suhas.maddali脸书
NumPy 数组上的向量化移动窗口网格操作
无论是摄影、地形学还是其他,都可以通过滑动窗口进行分析

很有可能你今天做了一些使用滑动窗口(也称为移动窗口)的事情,而你甚至不知道。你做过照片编辑吗?许多编辑算法都是基于移动窗口的。你在 GIS 里做地形分析吗?大多数地形栅格度量(坡度、坡向、山体阴影等。)是基于滑动窗口的。任何时候你对格式化为二维数组的数据进行分析时,很有可能会涉及到滑动窗口。
滑动窗口操作非常普遍并且非常有用。它们也很容易用 Python 实现。学习如何实现移动窗口将把你的数据分析和辩论技巧提高到一个新的水平。
什么是滑动窗口?
下面的例子显示了一个 3×3(3x 3)的滑动窗口。红色轮廓的数组元素是目标元素。这是滑动窗口将为其计算新度量的数组位置。例如,在下图中,我们可以计算灰色窗口中 9 个元素的平均值(剧透警报,平均值也是 8),并将其分配给红色轮廓的目标元素。除了平均值,您还可以计算最小值(0)、最大值(16)或许多其他指标。对数组中的每个元素都要这样做。
就是这样。这就是推拉窗的基础。当然,事情可能会变得更加复杂。有限差分方法可用于时间和空间数据。逻辑可以实现。可以使用更大的窗口尺寸或非正方形的窗口。你明白了。但其核心是,移动窗口分析可以简单地总结为相邻元素的平均值。
需要注意的是,必须为边元素提供特殊的容纳,因为它们没有 9 个邻居。因此,许多分析排除了边缘元素。为了简单起见,我们将在本文中排除边缘元素。

示例数组

3 乘 3 推拉窗
创建 NumPy 数组
为了实现一些简单的例子,让我们创建如上所示的数组。首先,导入numpy。
import numpy as np
然后使用arange创建一个 7×7 的数组,其值的范围从 1 到 48。另外,创建另一个填充了无数据值的数组,该数组与初始数组具有相同的形状和数据类型。在这个例子中,我使用-1 作为无数据值。关于填充 NumPy 数组的完整指南,你可以查看我以前关于这个主题的文章。
a = np.arange(49).reshape((7, 7)) b = np.full(a.shape, -1.0)
我们将使用这些数组来开发下面的滑动窗口示例。
带循环的滑动窗口
毫无疑问,你听说过 Python 中的循环很慢,应该尽可能避免。尤其是在处理大型 NumPy 数组时。你所听到的非常正确。尽管如此,我们还是先来看一个使用循环的例子,因为这是一个概念化移动窗口操作中发生的事情的简单方法。在您掌握了循环示例的概念之后,我们将转向更有效的矢量化方法。
要实现移动窗口,只需遍历所有内部数组元素,确定所有相邻元素的值,并在特定的计算中使用这些值。
通过行和列的偏移量很容易识别相邻值。3×3 窗口的偏移量如下所示。

行偏移量

列偏移量
循环中 NumPy 移动窗口的 Python 代码
我们可以用三行代码实现一个移动窗口。此示例计算滑动窗口内的平均值。首先,遍历数组的内部行。第二,遍历数组的内部列。第三,计算滑动窗口内的平均值,并将值赋给输出数组中相应的数组元素。
for i in range(1, a.shape[0]-1):
for j in range(1, a.shape[1]-1):
b[i, j] = (a[i-1, j-1] + a[i-1, j] + a[i-1, j+1] + a[i, j-1] + a[i, j] + a[i, j+1] + a[i+1, j-1] + a[i+1, j] + a[i+1, j+1]) / 9.0
滑动窗口循环结果
您会注意到结果与输入数组具有相同的值,但是外部元素没有分配数据值,因为它们不包含 9 个相邻元素。
[[-1\. -1\. -1\. -1\. -1\. -1\. -1.]
[-1\. 8\. 9\. 10\. 11\. 12\. -1.]
[-1\. 15\. 16\. 17\. 18\. 19\. -1.]
[-1\. 22\. 23\. 24\. 25\. 26\. -1.]
[-1\. 29\. 30\. 31\. 32\. 33\. -1.]
[-1\. 36\. 37\. 38\. 39\. 40\. -1.]
[-1\. -1\. -1\. -1\. -1\. -1\. -1.]]
矢量化滑动窗口
众所周知,Python 中的数组循环通常计算效率很低。通过对通常在循环中完成的操作进行矢量化,可以提高效率。移动窗口矢量化可以通过同时偏移数组的所有内部元素来实现。
下图展示了这一点。每幅图像都配有相应的索引。您会注意到最后一个图像索引了所有的内部元素,以及每个相邻元素的相应图像索引偏移量。



从左到右的偏移索引:[1:-1,:-2],[1:-1,2:],[2:,2:]



从左到右的偏移索引:[2:,-2],[2:1:-1],[:-2,1:-1]



从左到右的偏移索引:[:-2,2:],[:-2,:-2],[1:-1,1:-1]
用于 Numpy 数组上的矢量化移动窗口的 Python 代码
有了上面描述的偏移量,我们现在可以在一行代码中轻松实现滑动窗口。只需将输出数组的所有内部元素设置为您的函数,该函数根据相邻元素计算所需的输出。
b[1:-1, 1:-1] = (a[1:-1, 1:-1] + a[:-2, 1:-1] + a[2:, 1:-1] + a[1:-1, :-2] + a[1:-1, 2:] + a[2:, 2:] + a[:-2, :-2] + a[2:, :-2] + a[:-2, 2:]) / 9.0
矢量化滑动窗口结果
如您所见,这给出了与循环相同的结果。
[[-1\. -1\. -1\. -1\. -1\. -1\. -1.]
[-1\. 8\. 9\. 10\. 11\. 12\. -1.]
[-1\. 15\. 16\. 17\. 18\. 19\. -1.]
[-1\. 22\. 23\. 24\. 25\. 26\. -1.]
[-1\. 29\. 30\. 31\. 32\. 33\. -1.]
[-1\. 36\. 37\. 38\. 39\. 40\. -1.]
[-1\. -1\. -1\. -1\. -1\. -1\. -1.]]
速度比较
上面演示的两种方法产生相同的结果,但是哪一种更有效呢?我计算了从 5 行和 5 列到 100 列数组的每种方法的速度。每种方法对每个阵列测试 100 次。每种方法的平均时间如下图所示。

很明显,矢量化方法更有效。随着数组大小的增加,循环的效率呈指数下降。另外,请注意,包含 10,000 个元素(100 行和 100 列)的数组非常小。例如,来自智能手机相机的全分辨率图像将超过 2000 行和 2000 列。
结论
移动窗口计算在许多数据分析工作流中极为常见。这些计算非常有用,也非常容易实现。然而,用循环实现滑动窗口操作效率极低。矢量化移动窗口实现不仅效率更高,而且使用的代码行也更少。一旦您掌握了实现滑动窗口的矢量化方法,您就可以轻松有效地提高工作流速度。
最初发表于【https://opensourceoptions.com】。
对 nd 数组中的元素对进行矢量化计算
在 Python 中使用 NumPy

作者提供的图片
考虑下面一个非常简单的人为问题。你有一组数字:
import numpy as np
a = np.array([0, 10, -3, 5, 7, 20, -9])
你想计算每对数字之间的平均绝对差值。设 n 为a中元素的个数。那么对数就是n(n-1)/2。因此,一个简单的方法是遍历所有可能的对,计算每对的绝对差,然后求它们的平均值。代码如下:
如果我们在上面的阵列上运行它,我们会得到:
>>> mean_absolute_difference(a)
11.428571428571429
这段代码中两个 for 循环的存在表明效率低下。理想情况下,我们应该通过对算法进行矢量化,尽可能避免数字代码中的 Python for 循环。我们如何做到这一点?
关键的挑战是找出一种不使用任何for 循环的方法来获取a 中的每一对元素。NumPy 能帮我们吗?确实是的。感谢一个漂亮的函数triu _ indexes。
让我们看看它能做什么:
>>> i, j = np.triu_indices(7, k=1)
>>> print(np.row_stack((i, j)))
[[0 0 0 0 0 0 1 1 1 1 1 2 2 2 2 3 3 3 4 4 5]
[1 2 3 4 5 6 2 3 4 5 6 3 4 5 6 4 5 6 5 6 6]]
如果我们仔细观察输出,我们会看到每一列都给出了a中每一对元素的索引。总共有 21 对这样的线对。
现在让我们对平均绝对差函数进行矢量化。
for 循环消失了。我们正好形成 n(n-1)/2 对,所以没有多余的计算。
我在一台 MacBook 上对比了两个版本的性能。
a = np.random.randn(1000)
%timeit mean_absolute_difference(a)
500 ms ± 1.62 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit vectorized_mad(a)
8.78 ms ± 42.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
我们可以看到性能的显著提升。
虽然有可能为这个特定的问题提出一个更智能、更快速的解决方案,但我在这里的目标是说明构建元素对并对每对元素执行一些计算的技术。
向量数组中的对
让我们举一个更具挑战性的例子,计算向量数组之间的成对欧几里德距离:
这与 SciPy 中的[pdist](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.pdist.html)函数针对欧几里德度量执行完全相同的计算。
a = np.random.randn(100, 3)
from scipy.spatial.distance import pdist
assert np.allclose(pdist(a, 'euclidean'), pairwise_distance(a))
SciPy 版本确实更快,因为它是用 C/C++编写的。但是,我们的纯 Python 矢量化版本还不错(特别是对于小数组)。
如果您想要矩阵d 形式的成对距离结果,使得d[i, j]表示i-th和j-th向量之间的距离,该怎么办?这是这样的:
这是一个使用 3 个向量数组的例子,其中每个向量来自 R⁵.
>>> a = np.arange(15).reshape((3,5))
>>> pairwise_distance2(a)
array([[ 0\. , 11.18033989, 22.36067977],
[11.18033989, 0\. , 11.18033989],
[22.36067977, 11.18033989, 0\. ]])
以下是该实施的一些要点:
- 不需要计算
result的对角元素,因为我们知道一个点与其自身的距离为 0。 - 由于欧几里德距离是对称的,我们只需要计算
result的上三角形部分,并在下三角形部分复制相同的部分。 - 一开始计算的索引
i,j稍后被重新使用,以将距离向量放回到result矩阵中。
矩阵阵列中的对
我们还可以继续计算矩阵数组中的矩阵对。这是一个有点复杂的例子,对矩阵对进行了相当复杂的计算。
一个格拉斯曼是一个参数化 n 维向量空间的所有 k 维子空间的空间。这样的子空间也被称为 k 平面。每个平面都可以用一个标准正交基(ONB)来表示。k-平坦的 ONB 只不过是一个(n,k)大小的酉矩阵。平面内直线间的角度概念可以推广到平面间的角度。这些角叫做主角。
事实证明,计算主角度并不难。如果你有两个子空间的 ONBs A 和 b,那么你只需要计算它们的乘积 M = A^H B,并找出它的奇异值。主角度由奇异值的反余弦给出。特别地,两个这样的子空间之间的最小主角度由 m 的最大奇异值的反余弦给出。
现在让我们说,你有一个 n 个子空间的 ONBs 数组,你希望计算每一对子空间之间的最小主角度。由于 SVD 是一个开销很大的操作,所以不希望对超过 n(n-1)/2 对进行 SVD。下面是实现。
快乐矢量化!
使用模拟轨迹数据的车辆类型预测
实践教程
我们能通过轨迹数据区分公共汽车和小汽车吗?

作者图片
这是一个数据科学项目的总结,该项目旨在使用机器学习模型和模拟的时间序列数据来预测车辆是轿车还是公交车。
该项目基于这个存储库中包含的数据。作者采用了由法国索菲亚安蒂波利斯通信系统部门制作的作品,该作品已经开源,并使其易于访问和复制,从而使该项目得以实现。特别感谢他们!这是与他们合作完成的几个附带项目之一,我真诚地鼓励任何阅读这篇文章的人去看看它们。
为了避免文章篇幅过长,大部分代码将不会在这里显示,除非是一些重要的内容(如果您好奇或者不知道一些事情是如何完成的,请继续查看笔记本)。
取得的成果
最佳模型能够在训练过程看不到的数据上获得 F1 分数 0.873 。
关于数据
数据来源于使用 SUMO (城市流动性模拟)软件的模拟。该场景名为 MoST ,由摩纳哥公国的交通模拟组成,覆盖大约 70 公里的大都市区域,时间跨度从早上 4 点到下午 2 点。它包含车辆、行人和自行车的交通数据,如速度、地点坐标、坡度、方向角、车辆类型等。这些数据被聚合成几个以 5 秒为间隔采样的时间序列,形成了我们将在这个项目中操作的主要对象。
更详细的描述和分析可以在本项目的其他笔记本中找到。
因为生成的交通数据非常广泛和庞大,所以我们将只使用其中的一小部分:凌晨 4 点到 6:30 之间的模拟交通。
要求和设置
要复制这项工作,您需要安装资源库中的requirements.txt 文件上列出的所有必要的库以及sktime 包。幸运的是,所有必需的命令都已经在笔记本上了,你只需要运行相应的单元,你就可以开始了。还建议在安装软件包之前创建一个新的虚拟环境。
数据预处理
下一步是下载、选择和预处理我们的数据。最后,你会有一个名为cleaned_data.cs的新文件。
下载数据集(并将其加载到 pandas 数据框架中)后,我们可以大致了解它是如何组织的(请注意,并未显示所有列):

作者图片
查看上面的输出,我们可以了解数据是如何组织的:每一行都包含一个条目,一个特定时间戳上的特定车辆的读数,显示与该时间该车辆对应的特征值。我们将只保留其中的一些功能;vehicle_speed是一个显而易见的例子,但我们假设汽车的方向变化可能比公共汽车小,因为前者往往从城市的一个地方到另一个地方,而后者必须更多地改变方向,以便覆盖特定的区域让人们上下车,所以我们也保留了vehicle_angle。因此,最后,我们将维护vehicle_id、timestep_time(我们的实例标识符)、前面提到的特性和我们的目标标签vehicle_type。
现在让我们来处理目标标签中的一些不一致。根据文档,所有以passenger开头的车型都是汽车的一个子类。所以在我们把既不是轿车也不是公交车的所有品类都掉之后,我们会统一这些标签。
为了使用来自sktime包的时序算法,有必要将我们的数据集转换成一个嵌套的数据集。由于我们不能保存嵌套数据集,我们将把数据帧转换成多索引的数据帧,并保存到磁盘上。通过使用sktime包中的from_multi_index_to_nested方法,多索引数据帧可以变成嵌套的。
完成后,我们准备将数据集保存到磁盘中。
数据处理
因为我们不能保存一个嵌套的数据集,所以我们需要加载预处理过的数据集,并在每次与我们的项目交互时进行必要的转换。在这一点上,如果您在本节之前已经运行了代码,您可以从运行,而无需重复前面的步骤。现在我们来看看数据。

作者图片
我们的数据框架现在是多索引的,如上所述,我们需要它是一个嵌套的数据框架;一个数据集,其中每行包含一个实例(车辆 ID),每列包含一个要素,每个像元是一个完整的时间序列。函数[from_multi_index_to_nested](https://www.sktime.org/en/latest/examples/loading_data.html#Using-multi-indexed-pandas-DataFrames)将把我们的结构转换成一个嵌套框架。因为该函数重置了转换后的数据帧中的索引,所以我们需要保存我们想要的索引(列vehicle_id减少为唯一值),并在调用该函数后将其重新分配给我们的帧。

作者图片
我们现在将根据车辆的速度和方向创建一些新的要素。speed_variation和angle_variation将是分别描述速度和方向如何随时间变化的时间序列。mean_vehicle_speed和std_vehicle_speed都是从原来的vehicle_speed派生的一维特征,mean_speed_variation和mean_angle_variation也是从新创建的speed_variation和angle_variation计算的一维值。我们将看到这些特征如何帮助我们预测目标变量。然后,我们将使用sample方法,通过制作frac=1来随机化数据帧的顺序,指定随机采样过程来保持整个数据集。
我们现在将根据车辆的速度和方向创建一些新的要素。speed_variation和angle_variation将是分别描述速度和方向如何随时间变化的时间序列。mean_vehicle_speed和std_vehicle_speed都是从原来的vehicle_speed派生出来的一维特征,mean_speed_variation和mean_angle_variation也是从新创建的speed_variation和angle_variation计算出来的一维值。我们将看到这些特征如何帮助我们预测目标变量。然后,我们将使用sample方法,通过制作frac=1来随机化数据帧的顺序,指定随机采样过程来保持整个数据集。
接下来,我们需要清除一些由速度和方向的微分产生的空值;我们将删除数据集中所有时间序列的第一个值。之后,为了使用sktime库中的算法,我们所有的时间序列对于给定的特征需要有相同的长度。我们将对每个时间序列的随机间隔进行采样(所有条目对所有特征具有相同的时间戳选择),但不会在移除元素数量少于确定的间隔大小的任何样本之前进行采样。为了保持过程的可重复性,我们将在每次分割数据集中的时间序列时设置一个新的随机种子。
将数据集分为训练数据集和测试数据集后,我们将在训练数据中绘制两个标注上的标量要素分布。请注意,代表时间变化的变量(平均速度变化和平均角度变化)除以 5,因为这是我们的采样周期。

作者图片

作者图片

作者图片

作者图片
根据上面的图表,似乎仅以表格方式查看数据(即不考虑时间方面)并不是正确区分公共汽车和小汽车的好方法。然而,我们将安装一个逻辑分类器作为我们问题的基线分类器,因为它是一个非常简单的开箱即用的分类工具,并且推荐用于低维特征空间的问题。为了做到这一点,我们需要对两个类应用不同的权重,因为我们的数据集严重不平衡(汽车比公共汽车多得多)。
由于准确性不是在不平衡数据集上训练的模型的性能的良好度量,所以 F1 分数将用于评估我们的模型;使用 F1 分数是一种很好的方法来衡量具有非常不平衡类别的分类任务的性能,因为它从少数类别(在我们的情况下是bus)的角度考虑了精度和。为了适应我们的模型并计算 F1 分数,我们将使用来自sklearn的cross_val_predict函数。该函数在训练期间使用交叉验证,并将每个样本分配给一个测试集恰好一次;训练后,当数据集上的每个示例在测试集上时,它返回该示例的预测。此外,我们将使用一个名为compute_results的自定义函数来计算并打印 F1 分数,以及精度和召回分数和混淆矩阵。
重要的是要注意,我们的逻辑回归有特权使用整个数据;它将在我们从每个序列中采样较短时间窗口之前,根据从时间序列中获得的统计数据进行训练,这与我们仅使用这些窗口进行训练的特定时间序列模型相反。
Training time: 3.13 seconds Precision: 0.3022598870056497
Recall: 0.7133333333333334
F1 Score: 0.42460317460317465 Confusion Matrix:
[[ 107 43]
[ 247 1888]
时间序列分类算法
上述结果表明,使用常规机器学习算法对时间序列进行分类会产生较差的结果。我们的实验给了我们一个公平的回忆分数,尽管精确度很低,因此我们的 F1 分数刚刚超过 0.4。现在是时候测试一些特定于时间序列的算法了。我们将测试 3 种不同的算法,每种算法都属于时间序列分类模型的一个特定类别:合成、基于距离和基于 Shapelet。这些算法类别的简要说明可以在本文中查看。
时间序列森林分类器
我们将尝试的第一种算法(属于合成类别)有点类似于我们用逻辑回归所做的:TimeSeriesForestClassifier从所有时间序列的间隔中提取特征(如均值和标准差),然后简单地对这些特征应用传统的随机森林分类器。sktime的这个算法实现只对单变量问题(只有一个特征的问题)有效。为了解决这个问题,我们必须分别为每个时间特征训练一个模型。因为这种方法不处理数据的时间方面,所以期望从这种选择中得到简单的结果是有意义的。
Results for Time Series Forest Classifier trained on vehicle_speed:
Training time: 59.63 seconds Precision: 0.8829787234042553
Recall: 0.5533333333333333
F1 Score: 0.680327868852459 Confusion Matrix:
[[ 83 67]
[ 11 2124]] Results for Time Series Forest Classifier trained on speed_variation:
Training time: 58.70 seconds Precision: 1.0
Recall: 0.2866666666666667
F1 Score: 0.4455958549222798 Confusion Matrix:
[[ 43 107]
[ 0 2135]] Results for Time Series Forest Classifier trained on angle_variation:
Training time: 59.46 seconds Precision: 0.8333333333333334
Recall: 0.03333333333333333
F1 Score: 0.06410256410256411 Confusion Matrix:
[[ 5 145]
[ 1 2134]]
k 近邻时间序列分类器
KNeighborsTimeSeriesClassifier是一种基于距离的时间序列分类方法;它非常类似于用于正常数据的经典 K-Neighbors 分类器,区别在于它使用动态时间弯曲 (DTW)作为两个时间序列之间距离的度量。由于它估计数据集中每对时间序列之间的距离,这是一种相当昂贵的(就计算机能力而言)算法。
Training time: 47.83 seconds Precision: 0.17218543046357615
Recall: 0.17333333333333334
F1 Score: 0.17275747508305647 Confusion Matrix:
[[ 26 124]
[ 125 2010]]
火箭分级机
ROCKET 分类器(随机卷积核变换)是基于 shapelet 的分类器,用于时间序列问题;这意味着它试图从时间序列中提取区间,以帮助区分它们属于哪个标签。ROCKET 算法使用卷积核(类似于卷积神经网络中使用的核),将 shapelets 转换为新的特征,然后将这些特征应用于线性分类器。这种分类解决方案已被证明是非常通用的,胜过神经网络等更复杂的算法。
Training time: 20.03 seconds Precision: 0.9915254237288136
Recall: 0.78
F1 Score: 0.873134328358209 Confusion Matrix:
[[ 117 33]
[ 1 2134]]
下表总结了我们的结果:

作者图片
正如上面的结果可以告诉我们,火箭分类器优于我们的任务的其他算法。我们现在将对它进行微调,看看我们是否可以改进它。
特征选择和微调最佳模型
我们训练我们的模型,假设所有选择的特征都与我们区分公共汽车和小汽车的分类任务相关;然而,这可能不成立,所以下一步是用不同的特定特征集来尝试我们的算法,一次从一个特征开始。
Results for ROCKET Classifier trained on vehicle_speed:
Training time: 14.18 seconds Precision: 0.9918032786885246
Recall: 0.8066666666666666
F1 Score: 0.8897058823529411 Confusion Matrix:
[[ 121 29]
[ 1 2134]] Results for ROCKET Classifier trained on speed_variation:
Training time: 14.29 seconds Precision: 0.991869918699187
Recall: 0.8133333333333334
F1 Score: 0.8937728937728938 Confusion Matrix:
[[ 122 28]
[ 1 2134]] Results for ROCKET Classifier trained on angle_variation:
Training time: 14.28 seconds Precision: 0.864406779661017
Recall: 0.34
F1 Score: 0.48803827751196177 Confusion Matrix:
[[ 51 99]
[ 8 2127]]
每个功能的结果如下所示:

作者图片
有趣的是,vehicle_speed和speed_variation在单独用于训练模型时,都略微提高了我们的分类器的分数,而angle_variation表现不佳。现在一个可能的举措是同时在vehicle_speed和speed_variation上训练我们的模型。然而,由于我们有少量的特征,我们可以对每一对组合进行奢侈的训练,并看到结果。
Results for ROCKET Classifier trained on vehicle_speed and speed_variation:
Training time: 17.85 seconds
Precision: 0.9921259842519685
Recall: 0.84
F1 Score: 0.9097472924187726
Confusion Matrix:
[[ 126 24]
[ 1 2134]]
Results for ROCKET Classifier trained on vehicle_speed and angle_variation:
Training time: 18.89 seconds
Precision: 0.9829059829059829
Recall: 0.7666666666666667
F1 Score: 0.8614232209737828
Confusion Matrix:
[[ 115 35]
[ 2 2133]]
Results for ROCKET Classifier trained on speed_variation and angle_variation:
Training time: 17.98 seconds
Precision: 0.9914529914529915
Recall: 0.7733333333333333
F1 Score: 0.8689138576779025
Confusion Matrix:
[[ 116 34]
[ 1 2134]]
每对特征的结果是:

作者图片
上表向我们展示了vehicle_speed和speed_variation的组合给出了(稍微)更好的结果;因此,我们将把它们作为我们问题的预测特征。现在是时候通过微调来进一步改进我们的模型了。为此,我们将使用sklearns的功能[RandomizedSearchCV](https://sklearn.org/modules/generated/sklearn.model_selection.RandomizedSearchCV.html#sklearn.model_selection.RandomizedSearchCV)进行大范围的训练超参数搜索,使用[GridSearchCV](https://sklearn.org/modules/generated/sklearn.model_selection.GridSearchCV.html#sklearn.model_selection.GridSearchCV)进行小范围的特定搜索。RandomizedSearchCV函数从训练超参数可能性的给定分布中随机取样,以便给出搜索最佳优化的“总体方向”。之后,GridSearchCV looks 从给定的超参数集中尝试每个组合;这一组是围绕从RandomizedSearchCV获得的最佳结果构建的。因为训练一组模型可能需要很长时间,所以我们只对参数num_kernels尝试不同的值,顾名思义,该参数表示模型用于转换数据的核的数量。默认值是 10000,我们将使用 1000 到 100000 之间的一组 100 个步长进行搜索,从中随机抽取样本。
我们还需要使用[make_scorer](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.make_scorer.html#sklearn.metrics.make_scorer)包装 F1 得分函数,因为我们需要指定f1_score少数类,在我们的例子中是bus。
同样值得注意的是,下一步可能需要一段时间,因为我们正在多次训练我们的模型。
Fitting 5 folds for each of 32 candidates, totalling 160 fits
Training time: 2204.24 seconds Best hyperparameters found in wide radomized search:
{'num_kernels': 92900, 'ensemble': False} Best F1 Score:
0.9264127300472378
我们的随机搜索返回的最佳内核数是 92900 ,导致平均交叉验证分数为 0.926 。现在,我们将尝试更精确的搜索,查看 92000 和 93000 之间的所有 100 个区间,看看我们会得到什么。
Fitting 5 folds for each of 10 candidates, totalling 50 fits
Training time: 1336.15 seconds Best hyperparameters found in narrow grid search:
{'ensemble': False, 'num_kernels': 92900} Best F1 Score:
0.9264127300472378
看起来我们的窄范围搜索返回了与宽范围搜索相同的结果:92900 是我们模型的最佳内核数,F1 值为 0.926。因此,我们将保留这个配置,并使用[pickle](https://docs.python.org/3/library/pickle.html)保存我们的模型。
根据看不见的数据对最终模型评分
最后,是时候使用我们的测试数据集对我们的模型进行评分了,我们在工作开始时将测试数据集从训练中分离出来,以便我们的模型可以在看不见的数据上进行验证。我们将加载模型,然后对其进行评分。
Training time: 4.63 seconds Precision: 0.8157894736842105
Recall: 0.9393939393939394
F1 Score: 0.8732394366197183 Confusion Matrix:
[[ 31 2]
[ 7 532]]
结论
在我们的测试数据集上获得的准确率和召回率分别为 0.816 和 0.939 。我们的主要指标 F1 得分达到了 0.873 ,低于培训期间交叉验证观察到的值。不过,这可以被认为是一个很好的结果,火箭分类器在性能和提供良好结果所需的较低参数调整量方面令人印象深刻;即使使用默认的超参数,该模型在我们的第一次尝试中表现良好,这验证了该算法是使用时间序列作为预测器的快速机器学习实现的良好选择。
同样值得一提的是,我们最初关于angle_variation有助于区分汽车和公共汽车的假设被证明是错误的;这是一件好事,我们使用单一特征以及不同的特征组合来训练我们的模型,使我们的分类器更加稳健。
开放式问题
虽然我们现在已经结束了我们的工作,但一些问题仍然存在:
- 除了已使用的算法之外,还有其他算法可以更好地解决我们的问题吗?
angle_variation作为一个预测特征有什么用处吗?- 数据集中还有哪些可用的特征可用于改进我们的分类解决方案?
如果你对这些问题(或其他任何问题)感兴趣,我邀请你去调查它们!你可以随时叉这个回购,甚至不需要请求许可就可以玩得开心,或者你也可以联系我,如果你对这个项目有任何问题,见解或评论。
面向数字营销分析师的供应商模型验证方法
数字营销中的模型风险管理和验证介绍

随着机器学习环境的扩散,现在适合于数据科学家和 ML 从业者视野之外的应用,现代数字营销分析师现在发现自己处于这样的情况下,他们被要求谈论新发现技术的潜在实现。数字营销分析师几乎从未被要求就此事发表完全权威的意见,但面对客户的情况往往会要求围绕预测分析中可以打破的假设基础进行讨论。为了向客户提供价值,数字营销分析师使用他们自己的专业知识、供应商建议和内部团队的经验,为许多组织的促销活动的机器学习实施提供输入。
今天的数字营销分析师不仅需要熟悉基本的机器学习概念,他们还必须意识到在实现客户营销计划的过程中实现模型的责任。这些责任包括全面考虑模型风险及其管理,由金融机构监管办公室定义为:
模型风险— 由模型的设计、开发、实施和/或使用引起的不利财务(如资本、损失、收入)和声誉后果的风险。除其他因素外,它可能源于不适当的规范;不正确的参数估计;有缺陷的假设和/或设想;数学计算误差;不准确、不适当或不完整的数据;不适当、不当或无意的使用;和/或控制不足。
模型风险固有地带有必须减轻风险的假设,为了讨论本文的目的,我们把验证作为数字营销中减轻风险的第一种形式。为此,我们建议遵循货币监理署(美国财政部)规定的模型验证的定义:
模型验证:过程和活动的集合,旨在验证模型按照预期执行,符合它们的设计目标和业务用途。它还确定“潜在的限制和假设,并评估其可能的影响”。"
本文档旨在为数字营销分析师提供一份指南,帮助他们在复杂的供应商模型验证讨论中为客户和其他利益相关者导航。正如许多行业从业者可以证明的那样,供应商通常是数据的持有者,客户围绕这些数据制定营销策略。即使内部数据科学团队有足够的能力来构建定制的客户端解决方案,供应商在模型构建讨论中也有很大的影响力,因为他们是模型构建所基于的数据的所有者。
在本文结束时,读者将对模型验证技术有一个基本的了解,这些技术可以管理客户对主要依赖于专有供应商模型的数字营销计划的期望。我们将首先分析数字营销分析师在验证供应商模型时面临的多种限制,然后为没有 ML 和数据科学广泛背景的读者列出一系列可行的解决方案。本文最后探讨了模块化验证框架,并对 2021 年及以后的模型验证趋势进行了讨论。
主要考虑事项
当开始验证外部模型的工作时,由于不能直接访问生成模型的第三方所采用的逐步过程,会产生一些复杂性。特别是,数字营销分析师应警惕其方法的局限性,并提出以下问题:
- 我有能力联系模型创建者吗?
- a)开发人员仍然在生成模型的组织中吗?
- b)如果对 a)的回答是否定的,谁负责当前模型的维护?
2.我有能力审查构成模型的代码吗?
- a)该准则是否可供公众审查?
- b)代码是开源的吗?
- c)我的组织可以从第三方购买代码吗?
3.任何模型记录中是否有任何缺口?
- a)与模型一起提供的文档是否足够和透明?
4.是否有用于生成模型的数据集的全面记录?
- a)模型考虑了哪些输入和输出?
- b)模型承诺进行哪些转换来得出预测性结论?
从数字营销分析师的角度来看,普通从业者可能不具备所需的技能(与数据科学家具备的技能相同),无法驾驭围绕模型构建和维护的讨论。也就是说,对于上述每个问题,都有一些方法可以探究,最终有助于为后续步骤提供信息。上述问题的潜在解决方案如下:
- 我有能力联系模型创建者吗? —如果可能的话,这里的分析师应该将模型提供给特定的个人。对于由第三方创建的模型,理解模型构建和维护的过程和细节是至关重要的。在数字营销行业的许多情况下,模型通常通过浏览器内平台近乎实时地交付。诸如 Google Search Ads 360、Google Ads 和 Bing Ads 之类的平台在几秒钟内就能提供预测结果,而对其背后的代码或过程却没有任何透明度。分析师可以利用组织支持团队与构建这些模型的工程团队取得联系,以获得预测结果的进一步透明度。
- 我有能力审查构成模型的代码吗? —在这种情况下,分析师可能需要在基本水平上解释供应商模型,或者进行额外的研究以扩展自己的知识。这一步可能需要分析师组织内的数据科学团队的帮助,以理解模型的结构和构造。
- 在任何一个模型记录中有任何缺口吗? —对于来自第三方的定制解决方案,供应商应完整提供模型文档,以确保透明度。这通常很难获得,因为供应商试图保护他们的 ML 解决方案免受竞争对手和其他从业者的攻击。麦肯锡公司最近进行的一项研究得出结论,交付给客户的所有供应商模型中,近 76%的模型文档不完整或质量差。如果没有手动提供的完整文档,分析师可以参考在线文档,这些文档通常可以在支持论坛和供应商网站上的讨论板上找到。这种格式的文档和讨论的一个很好的例子可以在关于预测的 Google 支持部分中找到。⁴:如果没有提供任何文档,就需要联系前面提到的模型开发人员,以深入了解它的创建和维护。
- 是否有用于生成模型的数据集的全面记录? —在这种情况下,分析师必须专注于准确地确定模型的输入和输出。考虑到大多数第三方解决方案需要内部团队的策略指导来完成特定的任务,分析师应该至少熟悉模型所需的输入。如果这是未知的,监督供应商参与模型创建的各个项目经理应该熟悉模型的输入和预期输出。然后,这些输入将作为将模型转换链接到预期输出的主要参数。除了输入和输出之外,监督模型的供应商或项目经理将能够准确地阐明哪些数据集用于训练模型,以及所采用的任何外部数据摄取技术。
如果不全面理解本节中讨论的问题,数字营销分析师很难理解哪些工具、人员和数据集是模型创建和验证的一部分。从某种意义上说,大部分前期工作本质上是管理性的,但是在委托供应商提供模型建议时,为客户提供答案和透明度是必不可少的。这些初始步骤也被证明可以作为分析师和内部团队的内部审计,让客户与供应商取得联系。如果供应商不能以一种彻底的方式提供这些问题的答案,很可能他们的模型不能代表客户正在寻求洞察的现实。
可用的解决方案
在验证为实现营销目标而创建的模型的过程中,协调项目的分析师在处理手头的任务时有一些选择。数字营销分析师可用的部分工具集包括“软”和“硬”解决方案。在下一节中,我们将讨论验证供应商模型的每个解决方案的含义和部署。

“软”形式的验证
我们将验证技术细分为“软”和“硬”类别,因为许多行业从业者无法真正获得进行适当审计所需的模型细节。特别是,供应商解决方案比内部解决方案受到更多的监管,使得 ML 后端很难以透明的方式获得。软形式的验证固有地面临限制,因为它们依赖于定期的模型监控和概念合理性审查、对模型定制文档的严格评估、开发证据以及供应商解决方案对客户营销计划的一般应用。⁵
- 概念合理性:这里的分析师的任务是确定模型是否符合数据集和测量的现实。从概念的角度来看,这源于围绕该模型在理论上是否适合回答客户的倡议而提出的问题。分析师还会质疑模型的设计及其长期和短期的可行性。更具体地说,模型拟合技术(r 平方、均方误差等。)用于标准测试。当不采用更直接的技术并且需要计算强度时,建议分析师对模型进行敏感性 analysis⁶,以验证 ML 后端。对于数字营销分析师来说,还建议将结果与行业基准进行对比,并计算类似时间段内模型与历史现实的偏差。应将模型输出与行业和同行结果进行详细比较,考虑在较长时间框架内测试模型稳定性的多种现实状态。
- 模型文档的评估:这已经在前面的章节中进行了简要的探讨,但是对于执行验证工作的分析师来说,前提仍然是相同的。在一个理想的场景中,文档应该为客户提供完全复制供应商模型设计的能力。这可能是最不可靠的软验证方法,因为在 ML/AI 应用中,正确评估(以及复制)的标准非常低,其中所涉及的维度和变量的数量受开发人员设置的影响。不仅初始设置存在文档无法帮助的限制,而且起始值生成的输出也可能因计算周期的不同而不同。
- 开发证据和应用:分析师可以应用于审计的最后一种软验证方法是,根据供应商规定的预期结果和模型实施产生的实际结果进行逻辑检查。模型是否朝着供应商预测的方向移动了指针?这种方法当然在理论上比在实践中更容易,但是这个前提将概念合理性的审计结合到了一个更简单的层次——我们希望拒绝 Ho。
Hₒ: 供应商模式在部署后不提供增量营销优势。
Hₐ: 供应商模式在部署后确实提供了增量营销优势。
“硬”形式的验证
此处涵盖的验证形式是面向实践的技术,供数字营销分析师在已知关键考虑因素阶段的某些(或全部)方面时日常使用。从模型复制的角度来看,这些技术旨在使分析师更接近供应商生产的模型。
应该注意的是,模型复制并不总是生成与原始模型 1:1 相同的模型——在创建模型的过程中,可能会有一些发现,这些发现会得出与被审核的原始供应商生成的模型相同或更好的结论。使用不同方法、输入和假设复制并得出与原始模型相同结论的模型被称为挑战者模型。如果挑战者模型取代了正在审核的现有模型(称为冠军模型)的性能,那么冠军模型就会受到挑战者模型的质疑,并有可能被取代。⁷
假设验证:这个过程类似于前面讨论的概念合理性的想法,在量化模型本身中做出的假设时进行迭代。在这种情况下,分析师将验证整个模型中概述的假设定义。一旦确定,在对现实的解释中所做的每一个假设都将被量化,或者是数字的(离散的或连续的),或者是分类的(高、中、低等等)。).
例如,供应商建议对搜索引擎中的关键字的广告数据集使用以下形式的线性回归,该数据集具有 3 个变量:关键字的相应点击、平均每次点击成本和相应点进率:
f(x)=β₀+β₁x
交付数据集后,供应商回来提供一个模型,该模型仅根据点击率以 95%的置信区间正确预测用户对网站的点击量:
f(x)=7.21+0.0901x
最小二乘回归(梯度下降):梯度下降是一种优化算法,用于通过在梯度负值定义的最陡下降方向上迭代移动来最小化某个函数。⁸这里被最小化的值是回归线(β1)相对于搜索营销数据集规定的输出的斜率。
正则化:正则化方法通过惩罚具有极大值的特征的系数来工作,从而试图减少误差。它不仅导致错误率的增加,而且降低了模型的复杂性。⁹在我们的例子中,我们使用从初始数据集已知的给定输出值来生成分配给模型递归测试系数的惩罚项。
假设在这种情况下,我们的验证练习试图使用给定的所有变量对我们数据集中的输出进行建模。
f(xᵢ)=7.25+0.0991x₁+9.21x₂
如果我们生成的输出与我们已知的输出相比相差甚远,则正则化练习会惩罚违规系数,并将模型简化为以下形式:
f(xi)=7.25+0.0991x₁̶+̶9̶.̶2̶1̶x̶₂̶
f(xi)=7.25+0.0991x₁
重复该过程,直到模型中的误差减小,并且完全消除了系数的极大值。
平行模型测试:为了通过我们的 challenger vs. champion 框架验证模型,数字营销分析师可能会发现利用供应商文档来尝试全面复制所提供的模型非常有用。这个复制将具有相同的模型结构、相同的模型输入和相同的模型假设。使用这种方法,最终有两种情况需要研究:
- Hₒ: 完全复制,m 供应商的结果≠m 复制
- Hₐ: 完全复制,Mvendor= Mreplicated 的结果
经过多个周期的计算后,每个假设的结果描述了两个独立的结论:
- Hₒ: 完全复制,Mvendor 的结果≠ Mreplicated。当计算相同的数据集时,这些模型无法得出相同的结论——相同实现的常数保持不变,这表明存在计算差异。每个模型的底层代码的处理有明显的不同。
- Hₐ: 完全复制,Mvendor= Mreplicated 的结果。当计算相同的数据集时,这些模型能够得出相同的结论——相同实现的常数保持不变,表明没有计算差异。更重要的是,复制的模型可靠地处理在供应商模型中发现的相同偏差和错误,以获得高百分比的结果。
挑战者模型测试:与前面提到的并行模型测试类似,分析师也应该考虑将挑战者模型与供应商模型进行比较。分析师不是运行通过文档提供的内容的副本,而是使用指南来制作与供应商模型相似的模型,不同之处在于模型配置和假设。挑战者模型的结构、模型输入和模型假设是变化的,而不是导入与供应商模型完全相同的参数。这反过来允许分析员利用相似的模型将结果与供应商提供的模型进行比较,这超出了计算验证的范围。
分析师可能会发现挑战者模型中的预测变量得到了更好的处理,从而可能生成更准确、一致或稳定的模型。此外,数字营销分析师应该与内部数据科学团队协调,以生成 challenger 模型的多个迭代,每个迭代都稍作修改,以确定性能最佳的版本。这些可以单独与供应商提供的冠军模型进行比较,以进行基准测试,并提供挑战者模型优越性的经验证据。在针对特定于供应商的模型进行全面部署之前,challenger 模型本身需要针对测试数据集进行全面验证。

作者图片
此时,分析师可能还会想到,如果挑战者模型的寿命不能在合理的时间内得到证明,那么挑战者模型和供应商模型的合并形式将被视为未来实现的可行混合。当供应商模型在大型历史数据集上运行并证明了其稳定性时,通常会出现这种情况,而挑战者模型尽管在短期内表现出色,但在更长的时间范围内却无法做到这一点。该流程可以是模型更新的一种形式,这一主题超出了本文档的范围,但有助于与客户讨论如何展示该流程的附加值。
框架和研究设计
在讨论了验证问题的一些潜在解决方案之后,分析人员可以设计项目并提供实现的概念框架。这通常是一个多步骤的过程,涉及内部和供应商的合作。高效模型验证的建议方法需要开发人员、业务经理、数据架构师(特别是那些涉及数据摄取和 ETL 的人员)的支持,当然,还需要向客户销售模型的供应商团队的支持。
确定初始模型验证要求
这第一步的目的是澄清为什么模型验证被要求开始行动,以及它如何影响验证活动中涉及的其他组织实体。
模型风险管理策略: 请求是否来自过去与供应商有过不愉快经历的客户?根据聘用合同持有客户的组织是否希望提供附加值?模型的结果乍一看是否令人怀疑,历史数据是否使供应商的预测无效?供应商是否表现出逆境和对供应商模型错误的低容忍度?这些都是分析师应该问的问题,以决定验证活动的范围,以及从业务的角度来看验证发生的范围。
所有权和治理框架: 分析师可能会与组织的 CIO 协调,以确定模型验证的影响。分析师需要确定验证活动如何影响上述风险策略,以及合规性和安全性、信息质量、架构和最终的集成。
法律和法规考虑事项: 由于供应商模型的验证可能涉及访问作为供应商知识产权的技术的请求,分析师需要确保法律和法规预期得到管理,并在整个验证过程中完全符合。
确定验证方法
框架中的这一步可以被认为是规划阶段,在此期间,分析师可以就前面讨论的许多关键考虑事项提供输入。
概念合理性考虑
- 确定将被纳入模型的关键数据集和辅助信息源。
- 确定模型的长期和短期用途。
- 审查模型的设计;它的变量、维度和其他计算参数。
厂商模型的输出评估:
- 确定数据质量是否保留在供应商模型中。
- 对供应商模型的性能进行全面评估。
- 准备因变量和自变量的敏感性分析。
文件审查和冲突解决
- 对供应商提供的文件进行全面评估(哪些可以使用,哪些不可以使用)。
- 已知问题和错误识别(这可能包括模型稳定性、性能和解释不同数据集的能力的预期问题)。
验证练习的实施
这是验证活动的最终执行,其中尝试模型复制并形成挑战者模型。随着供应商模型的性能受到质疑,任何行业和同行基准测试也可能在这个最后阶段出现。在此阶段,模型风险管理人员或控制人员可能会与内部数据科学团队协调,以完成实施。对于生成的所有 challenger 模型,将测试它们自身的有效性,以确保稳定性、完整性和满足规定的置信度计算。
结果解释和更新建议
验证工作完成后,组织现在可以选择继续实施供应商模型,或者根据结果向客户提供新的建议。在供应商模型被认为适合客户营销目标的情况下,分析师可以简单地直接向各自的客户团队提出解决方案。如果供应商模型无效,并且与挑战者模型相比表现不佳,保留客户的组织可以继续使用合并挑战者和供应商模型的整体模型,或者提出一个内部模型解决方案。

作者图片
结论和今后的审议
机器学习在专业应用中的出现也推动了倾向于 ML 的组织将该技术带给大众。在撰写本文时,几乎每个大型金融机构、广告公司、消费品集团和技术巨头都在日常运营中使用某种形式的建模。随着这些实体的供应商提供建模解决方案,他们的工作验证现在是数据科学社区的一个蓬勃发展的子部门。
展望未来,我们已经注意到在 21 世纪 20 年代早期模型验证将如何发展的几个进步。一些最有趣的工作来自美国国防高级研究计划局(DARPA),以其在人工智能和机器人方面的工作而闻名。他们组织中对模型验证的增强围绕着可解释的人工智能(XAI)的概念——即机器学习可以为另一个人工智能或人工智能模型生成的结果提供解释。XAI 旨在通过尝试模型复制来解析另一个人工智能的代码,解释复制的结果,并最终在没有人类干预的情况下自动生成挑战者模型。在流程的每一步,XAI 都会以易读的格式生成文档,以实现流程的完全透明。这一过程目前处于研究和开发阶段,但近 7000 万美元已经资助了该项目,以便在未来十年将该技术带给公众。⁰
展望未来,预计模型验证和计算验证的增长将达到与当今全球许多现代审计公司相同的显著水平。随着企业解决方案催生了 AWS 等自助服务平台,不出多久,外行的小企业主就会开始模拟他们的独资企业的销售。当对所有者在云中生成的模型产生信心的时候,以当今解决方案的一小部分成本迎合他的验证服务可能会广泛传播。基于今天在最大似然验证方面取得的进步,未来可能会有机器简单地验证机器。

弗兰基·查马基在 Unsplash 上拍摄的照片
参考
1“接受存款机构的全面风险管理模式”金融机构监管办公室金融机构监管办公室 2017 年 9 月【www.osfi-bsif.gc.ca/Eng/Docs/e23.pdf.
2“模型风险管理的良好实践”货币监理署,金融机构监管局,2011 年 4 月,www . OCC . gov/news-issues/bulletins/2011/bulletin-2011-12 . html .
3 Crespo,Ignacio,等,“模型风险管理的演变”麦肯锡公司,2017 年 2 月,www . McKinsey . com/business-functions/risk/our-insights/the-evolution-of-model-risk-management。
4 该示例的超链接 URL 是https://support.google.com/searchads/answer/4552409?hl=en
5 Regan,Samantha,等人,“在金融服务中验证机器学习和人工智能模型”埃森哲金融与风险,埃森哲,2017 年,www . Accenture . com/_ ACN media/Accenture/conversion assets/main pages/Documents/Global/Accenture-Emerging-Trends-in-the-Validation-of-ML-and-AI-models . pdf .
6 布朗,贝弗利。"如何用 SAS 市场优化软件进行敏感性分析."SAS 社区图书馆,SAS,2017 年 9 月 11 日,www . Communities . SAS . com/t5/SAS-Communities-Library/How-to-Perform-Sensitivity-Analysis-with-SAS marketing/ta-p/311069。
7 哈利迪,伊莲。"各种模型验证技术的实际考虑.",FMS,2017 年 6 月,www . FMS Inc . org/documents/forum 17/slides/InternalAuditRisk-model validations-Halliday-Zheng-McGuire-tues 400 p . pdf?FB clid = iwar 2 CRB 4 ha 8 gttmhdlh 7 othm 7 kzziqac 3 tbcd _ f 98 kpmxdwcmyn 8 LX 9 utqsm。
8 鲁德,塞巴斯蒂安。"梯度下降优化算法概述.",更粗鲁。木卫一,2016 年 1 月,ruder.io/optimizinggradient-descent/.
9 保罗,萨亚克。" Python 中线性回归的基本原理."DataCamp 社区教程,datacamp.com,2018 年 10 月 31 日,www . data camp . com/Community/Tutorials/essentials-linear-regression-python。
10 Arel,Itamar,等《深度机器学习——人工智能研究的新前沿[研究前沿]》。IEEE 计算智能杂志,pdfs.semanticscholar.org/ea58/ af907495e97c93997119db4a59fab5cd3683.pdf。
维恩图:一个不常见的可视化工具

使用文氏图辅助 EDA 过程的例子
介绍
维恩图是一个通用集合的几个子集的所有可能关系(并、交、差、对称差)的示意图。
至少从 19 世纪 80 年代开始,文氏图(有时称为欧拉-文氏图)就已经作为解决逻辑问题的工具出现了。这个概念是由约翰·维恩在他的符号逻辑第五章“图解表示”中引入的 Leonard Euler(你好,Euler 教授,我们爱你)和 Christian Weise 在他们更早的工作中使用了类似的方法。然而,与一个流行的信念相反(或者不那么流行,不是每个人都生活和呼吸逻辑集合论),欧拉版本的图表与维恩的不一样。欧拉图有时被称为欧拉圈(不,它们并不总是圆形的),它们最初是为了解决亚里士多德三段论而创建的,而维恩用他的来解决数学逻辑问题。
这里有一个数据科学维恩图的简单示例。它有许多版本,但我最喜欢这个:

数据科学维恩图,图片作者
我还会添加“心理学知识”和“艺术技巧”,但让我们保持简单。脚注:没错,数据科学家是独角兽;让我们拥抱它。
那大概就是足够的维基级内容。现在,我们去探索维恩图更实际的一面;它们在数据可视化中的应用。
用例
例 1:
最近,我面临着理解分类问题中的某些特征如何相互关联的问题。数据集中的要素与哥伦比亚工程专业学生的社会经济地位相关联。该项目的最终目标是模拟学生在专业测试中的表现。数据集中的大多数要素都是分类的,包括住房质量、家庭收入和社会经济水平等。其他被探究的类别包括某人是否拥有电脑、手机、淡水,甚至他们是否拥有微波炉。常识让我相信,一个人是否拥有洗衣机,在很大程度上与学生家庭的社会经济水平重叠。同样,获得淡水也与住房质量密切相关。然而,我不能确定我关于特征相互复制的假设是否正确。当然,我可以使用相关矩阵,但我发现色差比尺寸差更难评估。此外,我希望有一种直观的方式来可视化整个记录池中具有特定特征的一部分记录。维恩图似乎是我所需要的一个很好的工具。事情是这样的。
我从安装 matplotlib-venn 包开始。我更喜欢通过康达进行安装:
conda install -c conda-forge matplotlib-venn
或者 pip 安装也可以
!pip install matplotlib-venn
接着是导入:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib_venn as venn
from matplotlib_venn import venn2, venn2_circles, venn3, venn3_circles
最后是神奇的一个
%matplotlib inline
下一步是导入数据集。我已经对它进行了部分预处理;删除了一些有错误的记录,清除了值,并将数据类型从数值更改为对象。最终的数据集可以在我的 github repo ,data_for_venn_clean.csv 中找到
我继续根据 venn2 和 venn3 使用的条件创建集合。完整的代码片段可以在 Jupyter 笔记本或 PDF Venn _ diagram _ examples . ipynb/Venn _ diagram _ examples . PDF 所在的目录中访问。以下是其中一些数据集的示例:
TV_set=set(df_venn.loc[df_venn.TV=='Yes']['COD_S11'])
Internet_set=set(df_venn.loc[df_venn.INTERNET=='Yes']['COD_S11'])
...
SEL1_set=set(df_venn.loc[df_venn.SEL==1]['COD_S11'])
SEL2_set=set(df_venn.loc[df_venn.SEL==2]['COD_S11'])
SEL3_set=set(df_venn.loc[df_venn.SEL==3]['COD_S11'])
SEL4_set=set(df_venn.loc[df_venn.SEL==4]['COD_S11'])
我还构建了几个联合集:
SEL1_and_SEL2_set=SEL_IHE1_set.union(SEL_IHE2_set)
SEL3_and_SEL4_set=SEL_IHE3_set.union(SEL_IHE4_set)
为了节省空间和时间,我会让任何对数据变量的完整描述感兴趣的人参考同一个报告中的 README.md 文件。
可视化、互联网接入与社会经济水平:
plt.figure(figsize=(10, 10))SEL1_and_SEL2_set=SEL_IHE1_set.union(SEL_IHE2_set)
SEL3_and_SEL4_set=SEL_IHE3_set.union(SEL_IHE4_set)ax=plt.gca()sets=[Internet_set, SEL1_and_SEL2_set, SEL3_and_SEL4_set]
labels=('Internet access', 'SocioEconomic level 1 or 2', 'SocioEconomic level 3 or 4')v=venn3(subsets=sets, set_labels = labels, ax=ax, set_colors=("orange", "blue", "red"))v.get_patch_by_id('100').set_alpha(0.3)venn3_circles(subsets=sets,
linestyle="dashed", linewidth=1)plt.annotate('SocioEconomic level 1 or 2\nHas no internet\n36%',
xy=v.get_label_by_id('010').get_position() - np.array([0, 0.05]), xytext=(-130,-130),
ha='center', textcoords='offset points', bbox=dict(boxstyle='round, pad=0.5', fc='gray', alpha=0.1),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.4',color='gray'))plt.annotate('SocioEconomic level 3 or 4\nHas no internet\n9%',
xy=v.get_label_by_id('001').get_position() - np.array([0, -0.05]), xytext=(190,190),
ha='center', textcoords='offset points', bbox=dict(boxstyle='round, pad=0.5', fc='gray', alpha=0.1),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.3',color='gray'))plt.title('Internet access vs SocioEconomic level', fontsize='20')
plt.show()

作者图片
从上图中可以明显看出,来自低社会经济水平家庭的学生无法上网的比例几乎是来自富裕家庭的学生的 3.5 倍。当涉及到计算机的所有权时,同样的模式会重复出现。事实上,互联网接入和计算机所有权本质上是重叠的:

作者图片
这些图表让我得出结论,在社会经济水平和上网/使用电脑之间确实有很强的相关性。此外,我了解到拥有一台电脑与互联网接入的可用性相关。
我用同样的方法形象化了获得淡水和住房质量之间的关系。哥伦比亚使用 strata ( estrato )根据几个标准对社区和区域进行分类。这些数字旨在对房产和其他建筑进行分类,而不一定是居住在那里的人的社会地位,因此收入不在评估范围内。生成的图表得出的结论是,住房质量与获得淡水密切相关。来自较高阶层家庭的学生获得淡水的可能性是来自较低阶层家庭的学生的六倍。

作者图片
上面的可视化帮助我回答了这个问题“我是应该保留数据集中的所有要素来构建模型,还是应该放弃其中的一些?”
例 2
我还发现文氏图有助于理解矢量化之前的 NLP 处理。
我不会在这里展示代码,但任何感兴趣的人都可以在同一个 github repo 中找到它以及 csv 文件,satisratio _ no satire . CSV。这是一个包括讽刺文章(The Onion)和真实新闻(Reuters)的数据集。整套文章被称为文集。它经过预处理,分为两组,讽刺和新闻。下面是一个维恩图,显示了训练数据集中讽刺和新闻单词的集合。

作者图片
结论
这篇文章的目的是展示在建立模型之前,如何使用维恩图来帮助理解两个不同领域的数据——大分类数据集和非结构化 NLP 数据。我真诚地希望这将对您未来的数据分析项目有所帮助。
维恩图——被低估的数据可视化
有时候,简单更好。

一个数据 可视化的主要目标是以一种清晰、引人注目的方式表示一个点。作为数据科学家,我们一直致力于创建最酷、最具创新性的数据可视化类型。以至于实际的信息丢失了。为什么要把事情复杂化?有时,一个简单的信息只需要一个简单的可视化来传达。在这篇博客中,我将分享一个简短的教程,教你如何用 Matplotlib 和任何类型的数据轻松地创建维恩图。
项目背景
在我最近的项目“Twitter 仇恨言论检测”中,我面临着一个重大挑战,即数据的类别不平衡。整个数据集是 24802 条文本推文,其中只有 6%被标记为仇恨言论。最后,这种阶级不平衡对我最终模型的结果产生了影响。你可以在这里查看最终项目的资源库以了解更多细节。
当我为这个项目做演示的时候,我需要一种方法来可视化这个问题。另外,我想找到专有到仇恨言论标签的字数,并且不与简单的攻击性语言重叠。然后我突然想到,维恩图可以完美地展示这个概念。这是最终产品。

在我开始学习教程之前,让我们先回顾一下基础知识。我们在小学都学过这个简单的视觉化。维恩图是由两个或两个以上的圆圈重叠而成的图,用来显示集合之间的逻辑关系。众所周知,集合包含了数据集中的唯一值。因此,这个维恩图显示了来自每个标签的唯一单词,以及那些重叠的单词。
教程
用 Python 创建维恩图非常简单。没有多少人知道这一点,但流行的数据可视化包matplotlib有一个扩展,可以创建可定制的维恩图。你可以点击查看文档和。第一步总是将软件包安装到您的本地机器上。
pip install matplotlib-venn
因为它是基于matplotlib的,所以你也需要导入 matplotlib 的依赖项,比如numpy和scipy。
一旦软件包被安装,你将需要输入你的数据作为一个集合。我不打算深入我的代码的细节,但是我可以在这个笔记本中查看所有的细节。首先,我将每个标签中的 tweets 分开,然后使用 map 函数将标记化的单词变成两个单独的列表。从那以后,我使用列表理解将它们转换成嵌套列表进行查询。
在您的数据采用合适的格式后,我们可以将包导入笔记本电脑本身。
import matplotlib_venn as vennfrom matplotlib_venn import venn2, venn2_circles, venn3, venn3_circlesimport matplotlib.pyplot as plt%matplotlib inline
从这里开始,创建文氏图的代码就像一行代码一样简单。
venn2([set(label_1), set(label_2)])
然而,这个包的美妙之处在于它非常可定制。您可以通过将此代码添加到原始行来添加标题和标签。
venn2([set(label_1), set(label_2)], set_labels = ('Hate Speech', 'Not Hate Speech'))plt.title('Comparison of Unique Words in Each Corpus Label')
最后一步是保存图片并在演示中使用它!
plt.savefig('venn_diagram.png', bbox_inches = "tight", pad_inches=.5)
除了标题和标签,你还可以添加更多的圆、改变圆的颜色、添加轮廓、改变尺寸等等。还有其他博客详细介绍了如何做到这一点,我将在下面链接这些博客。
在探索性数据分析阶段,我们不断地询问关于数据的问题和利用隐藏的洞察力。下一次你发现自己在创建一个复杂的可视化并且在努力传达这些见解,解决方案可能是简化它。我们不应该忽视基本的可视化,如维恩图!
使用 Terraform 的版本控制大查询(也使用 CI/CD)
确保大查询中的所有更改都是负责任的
介绍
当人们在没有通知团队其他成员的情况下开始改变视图时,使用大查询和创建视图的团队工作可能会变得混乱。

该片段取自作者大查询视图
一些团队习惯于在视图顶部使用注释,甚至是电子表格来记录变更。显然,从长远来看,这会导致一个问题,因为人们可能会忘记评论或没有正确地做。
事不宜迟,我如何对我的大查询进行版本控制,以确保我可以跟踪并在需要时回滚更改,并与我的团队一起更好地管理项目。
为了实现这一点,我们将研究两种技术,即 Terraform (基础设施即代码),以及您选择的版本控制系统(Github、Gitlab 等)。我们今天将使用 Github!如果你从未听说过 Terraform,不要太担心,这篇文章也可以算是“GCP terra form 的快速介绍”!
快速跳跃
入门指南
这一部分将用于尚未设置的环境。
入门不多!你只需要安装:
1。地形。他们提供了一个很好的指南,所以只要确保你跟着做,你应该能够在你的终端/命令提示符
2 中调用terraform。饭桶。请为您的操作系统安装 git,这样我们就可以执行基本命令,如git push
TL;步骤的 DR
- 在 GCP 项目上创建服务帐户
- 为服务帐户创建并下载 JSON 密钥
- 设置 Google 云存储(GCS)来存储地形状态
- 授予服务帐户在该存储桶中读取、写入和创建对象的权限
- 设置 Terraform 以连接到 GCS
首次创建服务账户和密钥的详细信息
和
Terraform 将代表我们使用一个叫做服务账户的东西与我们的谷歌云平台(GCP)项目进行互动。

该片段摘自授予 IAM 角色的作者 GCP 项目
继续用谷歌提供的教程创建你的 GCP 项目。当您到达选择 IAM 角色的提示时(截至 2021 年 6 月的教程中的步骤 7),出于本教程的原因,您需要授予 大查询数据编辑器 角色,如上面的代码片段所示。随着您的进步,请随意将权限缩小到仅需要的内容。单击下一步和完成。

该片段摘自作者 GCP 创建新密钥的项目
创建服务帐户后,单击服务帐户,您将看到一个类似于上面代码片段的屏幕。选择键- >添加键- >创建新键。接下来会出现一个弹出窗口,选择 JSON,点击 create。它将下载一个 JSON 文件。保管好它!
创建一个 Google 云存储(GCS)来存储地形状态
Terraform state 基本上记住并告诉 Terraform 你的基础设施现在是什么样子。我建议阅读 Terraform 文档来更好地理解它。我们将使用 GCS 来存储状态,主要有两个原因:
-在 Github 上共享状态通常不是一个好的做法,因为它可能包含敏感信息
-您不需要担心丢失它和重建基础设施
因此,让我们开始按照这里的教程创建一个 GCS bucket。记住您创建的存储桶名称,因为我们稍后将使用它。在该存储桶上,选择 permission 并添加之前创建的具有 Storage Admin 权限的服务帐户。你可以按照这个教程去做。
初始化地形环境
创建一个工作文件夹,在这个文件夹中,我们将创建一个名为providers.tf的文件。我通常使用 Visual Studio 代码来管理我的 Terraform 配置。tf 是一个 Terraform 配置文件,我们所有的 Terraform 内容都将存放在其中。平台提供商基本上是你将要与之合作的平台,无论是微软 Azure、GCP、AWS 还是其他。我将这个文件命名为providers.tf,,因为这是我保存所有提供者配置的地方,你可以随意命名,但是提供者会是一个更好的标准。
将以上内容粘贴到您的providers.tf中。在名为 terraform {…}的第一个“块”中,我们告诉 terraform 将我们的状态存储在 GCS 中。将 YOUR_BUCKET_NAME 更改为您之前创建的 BUCKET 名称。前缀基本上只是桶内的文件夹。
下一个称为 provider 的块表示我们正在使用 Terraform Google Provider 配置,并用您的 YOUR_PROJECT_NAME 对其进行初始化。
现在,Terraform 还不能读取您的 GCS 存储,这就是我们的服务帐户发挥作用的地方。将有一些方法向 Terraform 声明您的服务帐户,但是我们将使用的方法将在稍后在 Github 上设置 CI/CD 时有用。我们将把环境变量设置为我们先前下载的 JSON 文件,所以把那个 JSON 文件也移到您的项目文件夹中。在同一文件夹中,打开终端/命令提示符,并将环境设置为:
Windows 用户:
set GOOGLE_CREDENTIALS=JSON_FILE_NAME.json
Linux/macOS:
export GOOGLE_CREDENTIALS=JSON_FILE_NAME.json
Terraform 上的 Google provider 将自动从环境变量中检测服务帐户密钥。现在您的第一个 Terraform 命令来初始化环境了!
terraform init
如果一切顺利,您应该会在控制台上看到许多绿色文本。GCS 现在应该显示一个名为 state 的文件夹和该文件夹中一个以. tfstate 结尾的文件。
然而,如果你遇到了错误,不要担心,仔细阅读是什么错误。如果是关于 NewClient()失败的问题,请确保您的环境变量被正确地设置到 JSON 文件中。如果是关于拒绝访问,请确保您在 Google IAM 页面上为您的服务帐户提供了适当的访问权限。
创建我们的第一个数据集和视图
太好了!现在我们已经初始化了我们的环境,我们可以开始使用 Terraform 管理大型查询。
就像我们如何用providers.tf来管理我们的提供商一样,我们将创建bigquery.tf来管理我们的大查询。您甚至可以深入到数据集、表和视图的 3 个独立文件,但目前,我们将只创建bigquery.tf。我们将在bigquery/views/vw_aggregrated.sql创建另一个文件夹来存储我们的视图 SQL。
# Some demo content in vw_aggregrated.sqlSELECT
LoadDate, Origin, Destination, COUNT(ID) AS counts
FROM
`terraform-testing-gcp.demoset.DemoTable`
GROUP BY
1, 2, 3
将上面的代码复制粘贴到bigquery.tf中。为了理解发生了什么,第一个资源包含两个参数,第一个是资源类型(Google _ big query _ dataset),第二个是 ID (views) 您可以自己定义。你可以在这里找到谷歌提供商可用的资源。我们正在美国使用 ID 为“views”的“Google _ big query _ dataset”块创建数据集。我已经给了它一个描述和一些标签。标签是完全可选的,你可以删除它们。
接下来,我们创建一个资源“Google _ big query _ table”,ID 为“VW _ aggregated”。对于 dataset_id,它引用我们之前创建的视图 dataset_id。这一次,由于我们正在创建一个视图,我们将不得不打开一个视图块,如教程这里所述。我们将传入的第一个参数是我们想要使用的 SQL。有两种方法可以做到这一点,一种是直接将 SQL 键入 bigquery.tf 本身,例如:query = "SELECT * FROM ... WHERE 1 = 1。然而,我们正在研究可维护性,所以我们已经在bigquery/views/vw_aggregated.sql的一个文件夹中定义了我们的 SQL。
好吧!让我们运行一个命令来标准化我们的 Terraform 代码格式,然后试运行我们的配置。Terraform plan 将基本上“计划”并让我们知道什么资源将被创建/删除/修改等。
terraform fmt
terraform plan
如果你看到x resource to be created,x 是一个数字,你就可以走了!如果您正在获取x resource to be deleted,请检查并验证这是否是有意的。一旦确认这是预期的操作,运行terraform apply应用配置。

Terraform 创建的 author 大查询数据集的片段
如果操作正确,检查您的大查询 UI,您应该看到您在 Terraform 配置中定义的资源已创建!在我们的例子中,创建了一个名为视图和 vw_aggregated 的数据集。
版本控制设置
在我们验证了我们的 Terraform 配置按预期工作后,是时候对配置的变更进行版本控制了。第一步是创建一个 Github 项目,并将其设置为私有 repo,因为您不希望您的基础设施设置公开:)。

作者 Github 秘密创作画面片段
按照 Github 的指南创建一个名为GOOGLE_CREDENTIALS的秘密。这个秘密的值将是我们刚才用来设置环境变量的 JSON 密钥文件的内容。只需用文本编辑器打开 JSON 文件,并将内容复制粘贴到秘密值字段中。它应该类似于上面的代码片段。
我们将保持的“秘密”是服务帐户密钥,以允许 Github 动作在每次我们提交更改时帮助应用我们的 Terraform 配置。
Github secret 在我们的场景中非常棒,原因有两个:
-我们不需要提交我们的 JSON 密钥文件,其他贡献者/成员可以下载和滥用
- Github 操作将应用配置,并且您不需要向贡献者分发具有写权限的服务帐户。这迫使贡献者/成员总是提交他们的工作和变更,以便将其应用到生产中
接下来,我们需要告诉 Github Actions,一旦提交发生,需要做什么。创建一个名为.github/workflows的文件夹,并将以下内容粘贴到新创建的名为terraform.yml的目录中的一个文件中。
在推送至 Github 之前,我们需要排除一些文件或文件夹。在根文件夹上,创建一个名为.gitignore的文件,并粘贴以下内容。这将排除我们之前复制到目录中的 JSON 键,以及任何状态文件(如果有的话)。也可以随意添加自己的文件!
*.json
*.tfstate

作者项目目录的一个片段
对于那些仍然对我们之前创建的许多文件感到困惑的人来说,如果你完全按照这个教程来做(忽略vw_origin_KUL.sql),你的目录应该是这样的。
git 提交,git 推送
黄金时刻来了。以下命令将初始化 git 并将您的配置推送到 Github。理想情况下,如果运行良好,Github Actions 将在您的代码被推送的那一刻开始运行。
terraform fmt
git add .
git remote add origin LINK_TO_YOUR_GIT_REPO
git branch -M main
git commit -m "Initial commit"
git push -u origin main
git add 将在该目录中添加新的和已更改的文件和文件夹,除了那些在.gitignore中定义的。
git remote add originhttps://github.com/…会定义你当前目录应该属于的库。
git branch -M main 将创建一个名为 main
git commit-M " YOUR _ MESSAGE _ HERE "将创建一个“changelog”,一个好的消息将帮助你自己和你团队中的其他人识别出什么被更改了,而无需阅读太多代码
git push-u origin main将把你的代码推送到主分支。

作者 Github 操作工作流页面的片段
现在检查你的 Github 库,你应该在那里看到你的代码。单击 Actions 选项卡,您将看到我们之前在.github/workflows/terraform.yml创建的工作流。

作者 Github 操作工作流详细信息的片段
如果你看到一个绿色或橙色的勾号在跑,你就可以走了!如果出现错误,只需点击进入工作流程并阅读每个步骤的日志,因为错误很容易缩小范围。如你所见,这些步骤是我们在.github/workflows/terraform.yml中定义的。
做出改变并测试我们是否达到了目标
我们几乎完成了整个管道!接下来是测试我们计划实现的目标是否已经实现。让我们对bigquery/views/vw_aggregated.sql做一些改变。我已将我的查询更改为
# Some demo content in vw_aggregrated.sqlSELECT
CAST(LoadDate AS DATE) AS LoadDate,
Origin, Destination, COUNT(ID) AS counts
FROM
`terraform-testing-gcp.demoset.DemoTable`
GROUP BY
1, 2, 3
现在,我们将需要添加任何已更改的内容,创建一个 changelog 消息,然后将其推送到我们的存储库,以便 Github Actions 可以为我们应用它。总是terraform fmt保证代码标准化。
terraform fmt
git add .
git commit -m "Updated vw_aggregated to CAST LoadDate"
git push origin main

作者 Github commit 的一个片段
检查回购,我们可以看到我所做的更改( jonathanlawhh ),如上面的片段所示。

更新后的作者 Github 操作工作流片段
检查“Actions”选项卡,我们可以看到该提交的工作流已成功完成!

更新后的作者大查询视图片段
最后,我们可以看到大查询中的视图已经按照预期进行了更新。
结论
总之,我们首先建立一个 Github 存储库,初始化一个 Terraform 环境,并将 Terraform 配置推送到 Github 存储库。与此同时,我们还准备了一个 CI/CD 配置terraform.yml,以便在我们推出变更时帮助部署我们的配置。至此,可以有把握地说,我们已经实现了使用 Terraform 对大查询进行版本控制的目标,并实现了对我们项目的持续集成和部署。
希望这篇文章足以启动您的实现,并帮助您更多地了解 Terraform 可以实现什么!当然,实现可以根据您的环境进行改进。
如果您有任何问题,请随时联系我,甚至通过
Messenger widget 向我问好:https://jonathanlawhh.com
电子邮件:jon_law98@hotmail.com
版本控制您的数据库第 1 部分:创建迁移和播种
轻松规划、验证和安全地应用对数据库的更改

想象一下,这些盒子里塞满了模式和表格(图片由赖爷·苏比扬托在像素上拍摄)
如果您没有在数据库中处理迁移,那么您就错过了。就像 Git 管理对源代码的更改一样,您可以使用迁移来跟踪对数据库的更改。执行和恢复更改,并将数据库恢复到以前的状态。
设置迁移比您想象的要简单,而且优势巨大。迁移是独立于数据库的,提供一个真实的来源,跟踪变化,甚至可以用一些数据播种您的数据库。当您阅读完本文后,您将能够:
- 创建带有索引和外键的表
- 在开发数据库中轻松规划、验证和安全应用更改,然后将所有更改同步到生产数据库
- 重置开发数据库(全部撤消,再次迁移)
- 在数据库中创建所有指定的表,包括索引和关联
- 为数据库设定种子(插入数据)
- 执行到任何数据库的迁移(例如 PostgreSQL 和 SQL Server)
我将试着用实例展示所有这些特性,这些实例使用您可以重用的真实代码。我将这个过程分为 4 个步骤:设置、创建迁移、执行和撤销,最后是播种。不要因为这篇文章的长度而气馁,你可以使用简单的步骤轻松完成。我们走吧!

我们的计划已经制定,让我们开始吧(照片由斯科特·格雷厄姆在 Unsplash 上拍摄)
步骤 1:设置
在这一步的最后,Sequelize 安装完毕,可以使用了。如果你是一个有经验的程序员,那么跳过“解释”部分。在这一部分的底部,您可以找到所有命令的摘要。
1.1 安装 NPM
安装 NPM 。用npm -v验证
说明:
对于这个项目,你需要安装NPMJavaScript 包管理系统。它类似于 Python 中的 pip,您可以使用它来安装软件包。在此下载 NPM 并遵循安装说明。一旦安装完成,你应该能够打开一个终端(如命令提示符)并运行nmp -v。如果您的是阿瑟版本(如 v14.15.4 ),则节点安装正确。
1.2 设置您的项目
打开终端,导航到您的项目文件夹并执行npm init
解释:
创建一个你想要创建这个项目的文件夹,例如c:/migrationtool。
打开一个终端,导航到这个文件夹:cd c:/migrationtool
调用npm init新建一个项目。NPM 会问你一些关于项目名称、版本、描述和作者姓名的问题。这些都不是必填项,可以跳过,也可以以后填写。完成后,项目文件夹中会出现一个名为“package.json”的文件。在这里,我们所有的项目元数据都将被注册。
1.3 安装软件包
在根文件夹中执行
# installing packages
npm install --save sequelize sequelize-cli
npm install --save pg pg-hstore # for PostgreSQL
npm install --save tedious # for SQL Server
说明:
我们的项目准备好了,让我们安装我们的包吧!首先我们需要 Sequelize 和 Sequelize-cli。这些包允许我们开始进行和执行迁移:npm install --save sequelize sequelize-cli。这是允许我们创建迁移的主包。
为了实际执行这些迁移(例如创建数据库、表或新列), Sequelize 需要了解更多关于数据库的信息。出于演示目的,我们将在项目中使用两种数据库:PostgreSQL 和 SQL Server。为了让 Sequelize 与这些数据库一起工作,我们需要安装一些额外的包:npm install — save pg pgh-store tedious。如果你使用另一个数据库,如 mysql sqllite 或许多其他数据库,你可以在这里选择使用哪个包。你会注意到出现了一个新文件夹;节点 _ 模块。我们所有的软件包都安装在这里。我们的 package.json 文件也扩展了,跟踪我们所有已安装的包。
1.4 初始化序列
在你的根文件夹中执行npx sequelize-cli init 并运行所有步骤
说明:
我们所有的包都安装好了。初始化序列:npx sequelize-cli init。请注意,我们在这里使用的是 NPX,而不是 NPM。NPX 用来执行我们和 NPM 一起安装的包。
命令完成后,您会注意到又出现了三个文件夹:
- config:保存数据库凭证之类的文件
- 模型:保存将数据库表表示为模型的文件
- seeders:在表格中插入和删除数据的文件
1.5 配置序列
Sequelize 已经可以使用了。在我们深入研究之前,我们将让 Sequelize 的工作变得更简单一些
我们将让配置的工作变得更简单一些。根据我的经验,JS 文件比 JSON 更容易使用。转到 config 文件夹,将 config.json 改为 config.js 。然后调整内容从
{
"development": {
到
module.exports: {
"development": {
我们对“我们需要告诉 Sequelize 如何处理新情况”进行了更改。转到项目的根目录(c:/migrationtool/)并创建一个新文件。将这个文件命名为.sequelizerc。请注意,这个文件没有名称,只有扩展名。打开文件并添加下面的内容。这告诉 Sequelize 我们现在使用 config.js 而不是 config.json。
const path = require('path');
module.exports = {
"config": path.resolve('./config', 'config.js'),
"models-path": path.resolve('./models'),
"migrations-path": path.resolve('./migrations'),
"seeders-path": path.resolve('./seeders')
}
乐趣开始前的最后一步:转到 models 文件夹(c://migrationtool/models)并打开 index.js 文件。在第 8 行,将 config.json 替换为 config.js。
摘要
我们准备好出发了!在我们进入下一步之前,检查一下我们使用的命令。
#Creating folder
cd c:/
mkdir migrationtool
cd migrationtool#Setting up project
npm init# installing packages
npm install --save sequelize sequelize-cli
npm install --save pg pg-hstore # for PostgreSQL
npm install --save tedious # for SQL Server# Initialize Sequelize
npx sequelize-cli init

续集到了,我们开始吧。(照片由 Handiwork NYC 在 Unsplash 上拍摄)
步骤 2:创建迁移
在 Sequelize 中,迁移是一个 JavaScript 文件。它的内容描述了在执行和撤销时应该发生什么,例如“创建一个名为‘person data’的模式。让我们创建一个!
2.1 创建我们的首次迁移
在您的根文件夹中执行以下命令。
npx sequelize-cli migration:create -- name create_schemas。这将告诉 sequelize 创建一个新的迁移。执行这个命令将在我们的 migrations-folder 中生成一个文件,名称如下:“20210519183705-create _ schemas . js”。在里面你会发现下面的代码。
我们的第一次迁徙
如您所见,迁移包含两个功能“向上”和“向下”。第一个函数将包含实现我们想要做的事情的所有代码;创建模式。第二个功能将取消“向上”功能;它是它的对立面。让我们完成第一次迁移:
为我们的迁移采取一些行动
这段代码告诉 queryInterface 创建一个名为“app”的模式。
2.2 创建表迁移
让我们加快一点速度;我们将使用npx sequelize-cli migration:create -- name create_country_table创建新的迁移。为新创建的迁移提供以下内容:
这在我们的模式中创建了一个新表
这种迁移有点奇特。首先,它创建了一个事务,在该事务中定义了一个包含几列的表。然后,它将 we 索引添加到表中,然后提交它。事务是为了确保要么一切成功,要么什么都没有。如果创建其中一个索引失败,它将回滚到创建表。down 函数只是删除新创建的表。
还要注意,创建和修改的列都有默认值。

我们的迁移已经确定,让我们构建数据库模型吧!(图jeshoots.com在像素上)
3.执行和撤销我们的迁移
我们的数据库中还没有发生任何事情。我们刚刚生成了一些现在必须执行的指令。我们希望将指令迁移到数据库中。首先,我们将定义我们的数据库连接,然后我们将使用该连接告诉数据库如何执行迁移
3.1 设置数据库连接
编辑 config.js 文件的内容(在 root/config 文件夹中)。我已经创建了两个数据库连接:
module.exports = {
development: {
username: "mike",
password: "my_secret_password",
database: "country_db_dev",
host: "localhost",
port: "5432",
dialect: "postgres"
},
production: {
username: "mike",
password: "my_secret_password",
database: "country_db",
host: "localhost",
port: "1433",
dialect: "mssql"
}
}
解释:
我做了两个连接:开发和生产。这些文件允许我们的迁移工具连接到数据库来执行迁移。
当我们告诉 Sequelize 执行迁移时,它需要知道要连接到哪个数据库。为此,我们将 NODE_ENV 设置为我们的一个数据库连接的名称。我们有“开发”和“生产”。让我们与“发展”联系起来。在您的根文件夹中打开一个终端,执行下面的一行(与您的操作系统匹配):
# On windows
SET NODE_ENV=development
# On OSX
export NODE_ENV=development
#On powershell
$env:NODE_ENV="development"
3.2 执行迁移
既然 Sequelize 知道向何处写入,我们就可以执行迁移了。请注意,在上一步中,我们已经定义了数据库。我们可以执行到任何数据库的迁移,在本文的步骤 1.3 中我们已经为这些数据库下载了包。这样,我们可以在 PostgreSQL 中测试我们的迁移,然后将所有内容迁移到我们的 SQL Server 生产数据库中!
让我们执行:
npx sequelize-cli db:migrate
这段代码将执行所有的迁移,在其中创建我们的模式和一个表,请看:

我们漂亮的表,有列、主键和索引
3.3 撤消迁移
等等,回去!调用下面的代码删除表和模式。npx sequelize-cli db:migrate:undo:all。看看你的数据库里面,都是干净的!您也可以通过在下面的命令
npx sequelize-cli db:migrate:undo:all — to XXXXXXXXXXXXXX-create_country_table.js中指定文件名来撤销直到一个特定的迁移

我们已经成功执行了!(图 byu SpaceX 上 Unsplash
4.播种
为数据库设定种子非常类似于创建迁移。让我们快速浏览一下代码。
4.1 创建种子
第一步:生成种子文件
npx sequelize-cli seed:generate --name seed_country_table
,内容:
这将在我们之前创建的表中插入两条记录。向下删除它们
4.2 执行种子
用npx sequelize-cli db:seed:all执行
4.3 撤销种子
比如用
撤销种子用npx sequelize-cli db:seed:undo `
或者直到一个特定的种子像

很难相信这些小种子会成长为一个漂亮的关系数据库模型
结论
我们今天完成了相当多的工作!我们已经完成了以下工作:
- 安装的节点
- 已安装的序列和所有必要的软件包
- 配置序列
- 创造了一些迁移
- 执行和撤消迁移
- 创造了一些种子
- 已执行和未执行的种子
点击 此处 进入下一部分,我们将关注更高级的内容;我们将开始创建表和策略之间的关联,一旦它所依赖的记录被删除,记录应该如何操作。关注我敬请期待!
—迈克

这是一篇很长的文章,所以这里有一只小猫,如果你还和我在一起!(照片由乔·克利里在 Unsplash 上拍摄)
数据库的版本控制第 2 部分:表关系的迁移和种子
如果你喜欢啤酒、效率或建模高性能数据库,请阅读本文

我们数据库中的所有表格都整齐地链接在一起,并植入了数据(图片由克林特·王茂林在 Unsplash 上提供)
在的上一部分中,我们已经设置了迁移工具,对其进行了配置,创建了一些迁移,甚至在新迁移的表中植入了一些数据。如果你不知道我在说什么,请查看这篇文章。在这一部分中,我们将着重于向表中添加更多的功能。您可以在这里找到我们将在本文中构建的库。当您阅读完这一部分后,您将能够:
- 在事务内迁移
- 创建表之间的关联
- 设置具有类型和约束的列
- 在新创建的数据库中植入数据
- 只需按一下按钮,就能建立一个功能齐全的专业数据库,给别人留下深刻印象
0.目标和准备
我们的目标是为一个名为 BeerSnob 的网站创建一个数据库结构;一个专门为喝优质啤酒的人建立的网站。它允许用户分享关于他们在特定场所喝的啤酒的评论;在提供价格和口味信息的同时,对场地和啤酒进行评级。为了实现我们的目标,我们必须有一个可以存储以下信息的数据库:
- 用户(撰写评论的人)
- 啤酒(名称、类型)
- 国家、城市和比赛场地(出售啤酒的地方)
- 报告:一个用户在某个地方写一些关于啤酒的信息(比如价格、评级和评论)
报告是我们设计中最重要的部分。查看下面的概述,在一个漂亮的数据库模型中捕获这些需求:

BeerSnobs 数据库模型图
如你所见,我们将国家、城市和地点分别放在不同的表格中,这可能有点过分。目前我们并不真的需要它,但如果说这些年来我学到了什么,那就是需求很少保持不变。在未来,我们希望增加可能需要这样设置的功能。以这种方式构建我们的表,我们保证了未来发展的灵活性。
从这个结构来看,报告似乎是最重要的表;它指定了由用户给出的某个地点的啤酒的价格、等级和评论。这是用户在网站上将寻找的东西!
1.设置
我们将在前面的部分中编写的代码的基础上进一步构建。如您所知,我们已经创建了 2 个迁移:
- 创建一个名为“app”的模式
- 在“app”模式中创建一个名为“countries”的表
如果你想继续编码,从这里拉出代码。
2.添加用户和啤酒表
我们将以创建国家表的相同方式创建用户和啤酒。为了防止这篇文章变得太长,我将跳过重复。检查这个库中的代码。
3.为具有外键的表创建到另一个表的迁移
我们已经定义了相当多的表。现在让我们开始将表连接在一起。首先我们将讨论为什么我们应该使用外键,然后我们将创建一个实现外键的迁移。

让我们将这些表格链接在一起(图片由 Travis Saylor 在像素上拍摄)
为什么要使用外键?
外键子表中的列到父表中的主键。在我们的例子中,我们将把城市表中的 CountryId 列连接到国家表中的 Id 列。外键对于保持数据库的整洁和快速非常方便。他们通过防止不正确的输入来做到这一点(不能输入不存在 countryId 的城市)。它还使您能够控制当父表中的引用值被更新或删除时将采取的操作。如果一个国家被删除,外键可以确保属于被删除国家的所有城市也被删除。
创建迁移
现在让我们开始创建一个通过外键将两个表链接在一起的迁移;城市餐桌。首先,我们将使用 npx sequelize-cli migration:create --name create_city生成一个新的迁移。我们将如下定义迁移:
这段代码中有几件有趣的事情:
- 我们用引用创建 CityId。我们将在第 1 部分中定义的 tableModel_countries 表中的 Id 列作为目标。这将创建一个外键。
- 我们设置了一些列默认值(新日期()用于创建和修改)。当你没有传递一个值时,它默认为这里设置的值。
- 我们在 Id 和 Name 列上创建 come 索引
- 我们在一个事务中完成所有这些。例如,如果添加索引失败,它也会回滚到创建表。这样我们保证要么一切都失败,要么一切都成功。点击了解更多交易信息。

我们新做的 PK,FK 和指数
在这次迁移中,我们创建了包含 5 列的城市表,一个主键(蓝色箭头),一个外键(绿色箭头)和两个索引(橙色箭头)。记住,迁移是独立于数据库的;这是非常强大的东西!
4.添加场馆表
这与城市表非常相似。查看库中的代码以了解细节。
5.添加报告表
这是一切汇集的地方;我们必须真正注意这里!我们必须创建一个表,其中的外键指向另外 3 个表。让我们开始吧:

更有 PK,FK 和指数
如您所见,我们已经成功地创建了带有绿色外键、蓝色主键和橙色索引的表。
6.删除/添加列
迁移不仅在设置表时很重要;它们也可以在数据库生产时使用。在 BeerSnob 中,我们希望进行迁移,从“countries”表中删除“Capital”列。这无关紧要。在下面的迁移中,我们定义了如何进行和撤消:
'use strict';
let tableModel = { schema: 'app', tableName: 'countries' };
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn(tableModel, 'Capital')
},
down: async (queryInterface, Sequelize) => {
await queryInterface.addColumn(tableModel, 'Capital', {
allowNull: true, type: Sequelize.STRING });
}
};
在迁移中,您可以看到 queryInterface 是如何删除和添加列的。
7.使用我们的模型→播种一些数据
我们创建了表,设置了外键,完成了索引;我们的数据库模型创建完成了!让我们插入一些数据,这样我们就可以检查是否一切正常。

播种会将一些默认数据放入我们的数据库中,如管理帐户(图片由 Binyamin Mellish 在 Pexels 上提供)
处理用户数据
用户将向我们发送一个包,其中包含一个或多个(或很多,如果他有一个良好的夜晚)报告。每个报告都链接到用户配置文件(用户表)、啤酒表中的特定记录和地点。在网站和数据库之间将有一个 API 来处理数据包。如果用户报告一种尚不存在的啤酒,那么 API 应该首先在 Beers 表中创建一条记录,并在将记录写入报告时使用新创建的啤酒的 Id 列。在这篇文章中,我们将研究 API 是如何做到这一点的。现在我们用种子来模拟它。
插入种子数据
首先,我们将为所有的表插入一条记录。我不会在本文中详细讨论每一个种子,但是你可以在这里的库中查看它们。我将在下面提供一个例子,说明我是如何植入报表的,这是我们最重要的表。首次运行npx sequelize-cli seed:create --name seed_reports。迁移编码如下:
相当简单!唯一困难的是获得正确的 VenueId、BeerId 和 UserID,但这是 API 的工作。在这篇文章中我们不要担心它。
实践中的外键
查看app.reports表;您将看到我们在种子文件中定义的三条记录。想象一下当一个场馆关闭时。我们不能对不存在的场馆进行报道!这就是外键的用武之地。请记住,父表中的记录链接到子表中的相关记录。因为我们已经删除了[onDelete: CASCADE],所以删除 Venues 表中的记录会导致删除 reports 表中的相关记录。通过执行DELETE FROM app.venues WHERE “Id” = 2;来尝试一下,然后再次检查报告表!
结论
在本文中,我们在第一部分的基础上构建。我们对它进行了大量升级,增加了一组完美的链接表,包括索引、主键、默认值和其他约束。我们已经走了很长的路,但更多的升级等待着我们。查看这篇文章,了解我们如何通过构建一个与数据库通信的 API 来允许访问数据库数据,而无需编写一行 SQL!关注我了解更多改进。
—迈克
附注:在 https://github.com/mike-huls/beersnobv2 查看完整的项目
页(page 的缩写)又及:比如我正在做的事?跟我来!
使用 Google Drive 对大型数据集进行版本控制
使可再现的数据集成为可能

MLOps 最近在机器学习社区获得了一些关注。有了这么多的实验,跟踪、管理和协调它们与其他组件已经成为最近讨论的一个重要主题。特别是,当你尝试你的实验时,数据集一直在变化。您可能正在进行缩放、创建新要素、消除要素或只是对整个数据集进行采样。像data_feat1.csv、data_scaling.csv、 data_v2.csv等命名约定。对于最初的几个实验来说是可以的,但是当你扩大实验规模的时候就很痛苦了。
所以,我想我们可以使用 git 来控制数据集的版本。但是 git 不允许存储大于 100 MB 的数据或者对数据进行版本控制。例如,这里我只是将一个非常著名的MNIST数据集用于版本控制。

图片来自作者
git add *
git commit -m "Adding files"
git push -u origin master

从上面的截图可以看出,git 不接受大于 100 MB 的文件直接推送。我遇到了两个可供尝试的选择:
- 使用 Git 大文件存储(建议在输出终端使用)
- 使用 DVC 进行数据集的版本控制。

作者创建的图像
我没有使用 Git LFS(大文件存储),因为当使用版本控制系统如 DVC 来跟踪你的模型、度量等时。把所有东西都放在一个系统下会变得更容易。DVC 提供了几个选项来存储数据集,如亚马逊 S3、Azure Blob 存储,但我最喜欢的是 Google Drive。DVC 设计的系统,整个过程是无缝集成,一旦你开始使用 DVC。以下是说明如何做到这一点的步骤:
步骤 1:初始化存储库:
在您当前拥有数据集的本地目录/位置中初始化 git 和 DVC。转到终端,键入以下内容:
$ git init
$ dvc init
注意:dvc init需要输入准确的位置。git 文件已定位。请确保这一点,否则您的dvc将无法识别 git 文件。这里我们将尝试初始化大约 105 MB 的训练数据。
步骤 2:暂存要提交的文件:
$ dvc add *
$ ls -alh

您将看到创建了一个名为train.csv.dvc的新文件,使用以下代码将新文件提交给 git:
$ git add .gitignore train.csv.dvc
$ git commit -m "Adding training data"

如果您看到这个.dvc文件的内容,您会看到 md5 和 path,这是一个链接到数据的元文件,可用于跟踪数据。

在这里,提交是在本地完成的。然而,这些文件还没有被推送或存储到任何地方(这里是 google drive)。
第三步:将文件推送到谷歌硬盘:
下一步是定义要存储文件的存储位置。您可以使用以下代码来完成该操作:
$ dvc remote add -d storage gdrive://auth_code_folder_location

接下来是 git 提交,使用:
git commit .dvc/config -m "Config remote storage"

您可以使用以下方式将数据推送到存储位置:
dvc push

作者形象
如果您没有安装软件包pydrive2,可能会出现上述错误。
pip install pydrive2

点击链接并验证它是否提供了访问 DVC 的权限,以将文件推送到谷歌存储。一旦您允许,它将提供一个需要输入终端的验证码,如下所示:

一旦你输入代码,你会看到文件被推送到谷歌驱动器。


作者创造的形象
第 4 步:将您的元信息推送到 Github:
现在所有的跟踪信息都在本地 git repo 中进行版本控制。你可以把这些都推给你的 Github 回购计划。您可以在终端中执行以下步骤:
$ git remote add origin <your repo>
$ git push -f origin master
您应该可以在 github 上看到所有的变化:

现在让我们说,如果你想从其他地方/电脑访问数据,你需要安装 DVC,你可以按照下面的步骤在本地下载数据。
首先要检查数据是否存在,您只需输入以下内容:
dvc list <your repo>

作者的形象
但是在下载数据之前,您需要克隆存储库。您可以执行以下操作:
git clone <your repo>
克隆回购后,可以进行git pull。它将提示一个 url,需要像以前一样进行验证。请参见下面的截图:

最后,您会看到文件被下载到您的本地计算机上!瞧。!
感谢您抽出时间阅读本文。我希望你喜欢这个内容,并对你有所帮助。请随意发表评论,提出任何建议/想法。
如您想成为 中型会员 并无限享受阅读文章的乐趣,请注册会员。Medium 将与我分享使用上述链接注册的任何成员的一部分!谢谢。
版本化机器学习实验与跟踪它们
了解如何利用 DVC 提高 ML 重现性

在进行机器学习项目时,通常会运行大量实验来寻找算法、参数和数据预处理步骤的组合,从而为手头的任务产生最佳模型。为了跟踪这些实验数据,由于没有更好的选择,科学家们习惯于将它们记录到 Excel 表格中。然而,由于大部分是手动的,这种方法有其缺点。举几个例子,它容易出错,不方便,速度慢,而且完全脱离实际实验。
幸运的是,在过去的几年中,实验跟踪已经取得了很大的进步,我们已经看到市场上出现了许多工具,可以改善实验跟踪的方式,例如 Weights & Biases、MLflow、Neptune。通常这样的工具提供了一个 API,您可以从代码中调用它来记录实验信息。然后,它被存储在一个数据库中,您可以使用一个仪表板来直观地比较实验。有了它,一旦你改变了你的代码,你再也不用担心忘记写下结果——这是自动为你做的。仪表板有助于可视化和共享。
在跟踪已经完成的工作方面,这是一个很大的改进,但是……在仪表板中发现一个已经产生最佳指标的实验并不能自动转化为准备好部署的模型。很可能你需要先重现最好的实验。然而,您直接观察到的跟踪仪表板和表格与实验本身的联系很弱。因此,您可能仍然需要半手工地追溯您的步骤,以将准确的代码、数据和管道步骤缝合在一起,从而重现该实验。这能自动化吗?
在这篇博文中,我想谈谈版本化实验,而不是跟踪它们,以及这如何在实验跟踪的好处之上带来更容易的可重复性。
为了实现这一点,我将使用 DVC ,这是一个开源工具,在数据版本化的环境中最为人所知(毕竟它就在名字中)。然而,这个工具实际上可以做得更多。例如,您可以使用 DVC 来定义 ML 管道,运行多个实验,并比较指标。您还可以对参与实验的所有活动部件进行版本控制。
实验版本
为了开始对 DVC 进行版本控制实验,您需要从任何 Git repo 中初始化它,如下所示。注意,DVC 希望你的项目有一个合理的结构,你可能需要重新组织你的文件夹。
$ dvc exp init -i
This command will guide you to set up a default stage in dvc.yaml.
See [https://dvc.org/doc/user-guide/project-structure/pipelines-files](https://dvc.org/doc/user-guide/project-structure/pipelines-files).DVC assumes the following workspace structure:
├── data
├── metrics.json
├── models
├── params.yaml
├── plots
└── srcCommand to execute: python src/train.py
Path to a code file/directory [src, n to omit]: src/train.py
Path to a data file/directory [data, n to omit]: data/images/
Path to a model file/directory [models, n to omit]:
Path to a parameters file [params.yaml, n to omit]:
Path to a metrics file [metrics.json, n to omit]:
Path to a plots file/directory [plots, n to omit]: logs.csv
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
default:
cmd: python src/train.py
deps:
- data/images/
- src/train.py
params:
- model
- train
outs:
- models
metrics:
- metrics.json:
cache: false
plots:
- logs.csv:
cache: false
Do you want to add the above contents to dvc.yaml? [y/n]: yCreated default stage in dvc.yaml. To run, use "dvc exp run".
See [https://dvc.org/doc/user-guide/experiment-management/running-experiments](https://dvc.org/doc/user-guide/experiment-management/running-experiments).
您可能还注意到,DVC 假设您将参数和指标存储在文件中,而不是用 API 记录它们。这意味着您需要修改代码,从 YAML 文件中读取参数,并将指标写入 JSON 文件。
最后,在初始化时,DVC 会自动创建一个基本管道,并将其存储在 dvc.yaml 文件中。有了它,您的培训代码、管道、参数和指标现在就存在于可版本化的文件中。
实验即代码方法的好处
干净的代码
当以这种方式设置时,您的代码不再依赖于实验跟踪 API。您不必在代码中插入跟踪 API 调用来将实验信息保存在中央数据库中,而是将其保存在可读文件中。这些在您的 repo 中总是可用的,您的代码保持干净,并且您有更少的依赖性。即使没有 DVC,您也可以使用 Git 读取、保存和版本化您的实验参数和度量,尽管使用普通的 Git 并不是比较 ML 实验的最方便的方式。
$ git diff HEAD~1 -- params.yaml
diff --git a/params.yaml b/params.yaml
index baad571a2..57d098495 100644
--- a/params.yaml
+++ b/params.yaml
@@ -1,5 +1,5 @@
train:
epochs: 10
-model:
- conv_units: 16
+model:
+ conv_units: 128
再现性
实验跟踪数据库并不能捕获重现实验所需的所有信息。经常缺失的一个重要部分是端到端运行实验的管道。让我们看一下已经生成的管道文件“dvc.yaml”。
$ cat dvc.yaml
stages:
default:
cmd: python src/train.py
deps:
- data/images
- src/train.py
params:
- model
- train
outs:
- models
metrics:
- metrics.json:
cache: false
plots:
- logs.csv:
cache: false
这个管道捕获运行实验的命令、参数和其他依赖项、度量、绘图和其他输出。它只有一个“默认”阶段,但是您可以根据需要添加多个阶段。当将实验的所有方面都视为代码时,包括管道,任何人都可以更容易地复制实验。
减少噪音
在仪表板上,你可以看到你所有的实验,我是说所有的实验。在某一点上,你将会有如此多的实验,你将不得不对它们进行分类、标记和过滤以跟上进度。有了实验版本,你在分享什么和如何组织事情上有了更多的灵活性。
例如,您可以在一个新的 Git 分支中尝试一个实验。如果出了问题或者结果不令人振奋,可以选择不推分支。这样您可以减少一些不必要的混乱,否则您会在实验跟踪仪表板中遇到。
同时,如果一个特定的实验看起来很有希望,你可以把它和你的代码一起推送到你的 repo,这样结果就和代码和管道保持同步。结果与相同的人共享,并且已经使用您现有的分支机构名称进行了组织。你可以继续在那个分支上迭代,如果一个实验偏离太多,开始一个新的,或者合并到你的主分支,使它成为你的主要模型。
为什么用 DVC?
即使没有 DVC,您也可以更改代码,从文件中读取参数,向其他文件写入指标,并使用 Git 跟踪更改。然而,DVC 在 Git 上添加了一些特定于 ML 的功能,可以简化实验的比较和再现。
大型数据版本控制
在 Git 中不容易跟踪大型数据和模型,但是通过 DVC,您可以使用自己的存储来跟踪它们,但是它们是 Git 兼容的。初始化时,DVC 开始跟踪“模型”文件夹,让 Git 忽略它,但存储和版本化它,这样你就可以在任何地方备份版本,并在你的实验代码旁边检查它们。
单命令再现性
整理整个实验管道是实现可重复性的第一步,但是仍然需要用户来执行管道。有了 DVC,你只需一个命令就能重现整个实验。不仅如此,它还会检查缓存的输入和输出,并跳过重新计算之前生成的数据,这有时会节省大量时间。
$ dvc exp run
'data/images.dvc' didn't change, skipping
Stage 'default' didn't change, skippingReproduced experiment(s): exp-44136
Experiment results have been applied to your workspace.To promote an experiment to a Git branch run:dvc exp branch <exp> <branch>
更好的分支机构
虽然 Git 分支是一种灵活的组织和管理实验的方式,但是通常有太多的实验不适合任何 Git 分支工作流。DVC 跟踪实验,所以你不需要为每个实验创建提交或分支:
$ dvc exp show ┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┓ ┃**Experiment** ┃ **Created** ┃ **loss** ┃ **acc** ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━┩ │**workspace** │ **-** │ **0.25183** │ **0.9137** │ │**mybranch** │ **Oct 23, 2021** │ **-** │ **-** │ │├──9a4ff1c **[exp-333c9]** │ 10:40 AM │ 0.25183 │ 0.9137 │ │├──138e6ea **[exp-55e90]** │ 10:28 AM │ 0.25784 │ 0.9084 │ │├──51b0324 **[exp-2b728]** │ 10:17 AM │ 0.25829 │ 0.9058 │ └─────────────────────────┴──────────────┴─────────┴────────┘
一旦您决定了这些实验中哪些值得与团队分享,就可以将它们转换成 Git 分支:
$ dvc exp branch exp-333c9 conv-units-64
Git branch 'conv-units-64' has been created from experiment 'exp-333c9'.
To switch to the new branch run:git checkout conv-units-64
这样你就可以避免在回购中产生混乱的分支,并且可以专注于比较那些有前途的实验。
结论
总的来说,实验版本化允许您以这样一种方式来编码您的实验,即您的实验日志总是连接到进入实验的确切数据、代码和管道。你可以控制哪些实验最终与你的同事分享以供比较,这可以防止混乱。
最后,复制一个版本化的实验变得像运行一个命令一样简单,如果一些管道步骤缓存了仍然相关的输出,它甚至可以比最初花费更少的时间。
谢谢你陪我到帖子结束!要了解更多关于管理 DVC 实验的信息,查看文档
振动数据及其分析技巧
振动信号和几种有前途的时频分析技术
什么是振动信号?
振动是一种机械事件,其中围绕一个平衡点发生振荡,携带这些振荡信息的时间序列称为振动信号。这些来自平衡点的振荡需要以高采样率获得。振动信号通常可以用正弦波来表示,如下图所示,正弦波有几个重要的特性,在分析时应该记住(如下所述)。

“作者提供的图像”
- 振幅或峰值: 提供了冲击事件的细节,但它不考虑持续时间,因而也不考虑事件中的能量。
- 峰峰值: 提供波的最大偏移,这在查看位移信息时很有用
- RMS (均方根):最有用,因为它与振动信号的能量含量直接相关,并因此与振动的破坏能力相关,因为它考虑了波形的时间历程。
振动信号大多是非线性、非平稳的,对非平稳信号进行分析是一项具有挑战性的任务。
什么是平稳和非平稳信号?
稳定的信号可以由具有恒定时间周期的正弦波表示,而非稳定的信号将具有具有变化时间周期的正弦波。或者换句话说,如果信号的频率或频谱内容不随时间变化,我们可以称之为平稳信号,因为非平稳信号的频率随时间变化。下图给出了平稳和非平稳信号的一个例子,以及它们的频率分布。

平稳和非平稳信号及其功率谱示例[ref-1]
分析振动信号的技巧有哪些?
经验模式分解
EMD 是一种自适应方法,它将非线性和非平稳信号分解成几个固有模态函数(IMF)和一个残差。EMD 算法基于筛选过程,当残差保持为常数、单调斜率或只有一个极值的函数时,筛选过程结束。

一个“信号”分解成 10 个“IMF”和“残余”。参考文献 2
基于希尔伯特-黄变换和经验模态分解的特定频带分离
经验模态分解(EMD)可以将振动信号 S[n]分解成窄带 IMF。每个 IMF 都满足两个基本条件:
- 数据中极值的计数和零交叉的计数必须相同,或者允许相差最大一个,以及
- 根据它们的定义,从局部最大值和局部最小值获得的包络的平均值在瞬间为零。
将振动信号分解成窄带后,对 IMFs 进行希尔伯特变换,提取瞬时频率。例如,下图显示了振动信号的五个基于频率范围的频带,它们是在瞬时频率的帮助下从 EMD 分解的振动信号中分离出来的,瞬时频率是使用 HT 从每个 IMF 中提取的,HT 为每个样本确定一个频率值。

频率范围内振动信号的五个频段,(I)delta:0–4Hz,(II)theta:4–8Hz,(III)alpha:8–13Hz,(IV)beta:13–30Hz 和(V)gamma:30–60Hz参考文献 3
利用上述方法,可以提取振动信号的多种节律,这对各个方面都有帮助。
对振动信号进行时频分析的几种常用方法如下:
- 短时傅立叶变换(STFT)
- 小波变换(连续/离散小波变换)
- 斯托克韦尔变换
- 维格纳-维尔分布(WVD)
- 平滑伪维格纳-维尔分布
我们将通过一个非常简单的振动信号(包含低频和高频)示例来理解它们的时频表示,如下图所示。

具有两种不同频率的人工振动信号【参考文献 4】。
现在,让我们用各种方法来看看给定信号的时频表示(TFR)。

给定信号在短窗和长窗下的短时傅里叶变换[ref-4]。

给定信号的连续小波变换[ref-4]。

给定信号的 ST[ref-4]。

使用 WVD 对给定信号进行 TFR,我们可以注意到交叉项伪像以高幅度出现【参考文献 4】。

使用 SPWVD 计算给定信号的 TFR,我们在这里看不到交叉项伪像[ref-4]。
这里,变换的目的是可视地呈现有区别的时间和频率特征;因此,只显示了幅度。通过这个博客,人们可以了解如何从各种方法计算的频谱有区别地提出一个振动信号。技术的选择取决于信号特性和/或结果的进一步使用或后续处理。重要的方面是期望的时间和频率分辨率以及伪影的耐受性。例如,如果我们想要检测交叉项伪像,我们应该考虑 WVD,而忽略伪像 SPWVD 是更好的选择。
选择方法的另一个例子是上图(具有短窗口和长窗口的 STFT)包含短窗口和长窗口的频谱图,其中我们分别看到频率的高分辨率和低分辨率。根据应用,我们可以选择窗口大小。然而,随着高分辨率频率的增加,时间分辨率降低。同时,我们不能同时具有高分辨率,但是窗口大小允许我们在时间和频率分辨率之间进行权衡,并且可以根据要求优化窗口大小。
关于本博客中包含的方法,有以下几点,可能有助于根据需求选择合适的方法。
- STFT——易于解读;使用快速傅立叶变换的快速实现:但是有限且固定的分辨率
- WT —分辨率不固定;分辨率取决于频率(多尺度属性);通常,较低频率分量具有更令人满意的频率分辨率和更粗糙的时间分辨率,而较高频率则相反
- ST —倾向于强调更高频率的内容。
- WVD——克服了分辨率有限(计算每个时间步长的频率内容)、强伪像、实施速度慢的问题
下面给出了变换分辨率的示意图,包括用于比较的时域表示和标准傅立叶频谱。

[参考文献 4]
本博客简要介绍了振动信号及其重要特征。在博客的后面,我们将讨论时间序列的平稳和非平稳属性。我们还讨论了几种流行的用于振动信号分析的时频分析技术。在博客的最后,我们将讨论这些技术的优缺点,以及如何选择振动信号分析技术。
参考文献
- 普恩特·吉伦出版社,2016 年。根据驾驶行为预测困倦(利兹大学博士论文)。
- Rai,k .,Bajaj,v .和 Kumar,a .,2015 年。用于病灶和非病灶脑电信号分类的特征提取。在信息科学与应用(第 599–605 页)。斯普林格,柏林,海德堡。
- Bajaj,v .,Rai,k .,Kumar,a .,Sharma,d .,Singh,G.K .,2017 年。基于节律特征的局灶性和非局灶性脑电信号分类。 IET 信号处理, 11 (6),第 743–748 页。
- 学校,s . 2021。傅立叶、Gabor、Morlet 或 Wigner:时频变换的比较。 arXiv 预印本 arXiv:2101.06707 。
云中的视频人工智能:6 个平台和 API
概述了这一新兴领域的一些领先厂商的产品功能。

来源: Pixabay
人工智能(AI)越来越多地被用于管理视频内容。基于深度学习的计算机视觉技术可以帮助识别视频流中的概念和人脸,对视频进行分类,自动添加字幕,并使用超分辨率等技术增强视频和图像。
从零开始开发视频 AI 是一项巨大的投资。今天,你可以利用许多云平台提供的强大的视频 API,利用视频人工智能功能。
在这篇文章中,我将描述几个世界上最先进的视频人工智能平台:
- 自动警报系统
- 微软 Azure
- 谷歌云平台
- IBM 沃森
- Pixop
- 瓦洛萨
1.AWS 上的视频 AI
AWS 为视频提供的核心人工智能服务有:
亚马逊认知
这项服务使用成熟、高度可扩展的深度学习技术,可以轻松地将图像和视频分析添加到您的应用程序中。该服务不需要机器学习专业知识。它可以让您:
- 识别图像和视频中的物体、人物、文本和场景
- 检测不适当的内容
- 执行精确的面部分析和面部检测—适用于用户识别、人口统计和公共安全场景
- 亚马逊 Kinesis 视频流
这项服务可以让您安全地将视频从连接的设备流式传输到 AWS,以进行分析、机器学习(ML)分析和播放。
Kinesis Video Streams 可自动设置和扩展从数百万台设备中捕获视频流数据所需的所有基础设施。它可以永久存储、加密和索引视频数据,以便通过易于使用的 API 进行访问。
Kinesis Video Streams 支持实时和点播视频流,并允许您执行基于人工智能的视频分析视频 Amazon Rekognition,以及 Apache MxNet、TensorFlow 和 OpenCV 等开源框架。
2.微软 Azure 上的视频 AI
微软认知服务是 Azure cloud 上提供的一项服务,包括支持视频图像分析的视觉包。视觉套件提供以下功能:
- 计算机视觉 —可以识别物体、打字和书写的文本、动作(比如走路),可以识别图像的主色。
- 内容仲裁者 —可以检测文本、视频和图像中的不当内容。
- 人脸 API —可以检测人脸并进行分组,还可以识别人脸的年龄、性别、情绪、姿势和面部毛发。
- Emotion API —可以识别和描述面部表情的人脸识别工具。
- 定制视觉服务 —让您用自己的数据构建定制的图像识别模型。
- 视频索引器 —一个可以帮助你在视频中找到人的工具,还可以检测言语的情绪,标记某些关键词。
在 Azure cloud 中,通常使用弹性 blob 存储服务来存储数据。然而,对于要求苛刻的应用程序和实时 AI 处理,有时使用 Azure premium 存储更好。
3.谷歌云平台上的视频 AI
谷歌云平台提供服务和 API,让你对视频流和视频文件进行基于 AI 的操作。
视频智能 API
提供预训练的机器学习模型,可以自动识别存储和流式视频中的大量对象、位置和动作。它开箱即用,在常见用例中提供高性能,并不断更新和重新训练新的对象和概念。
自动视频智能
Google AutoML Video Intelligence 提供了一个图形界面,允许具有最少机器学习经验的用户训练定制模型,以便对视频中的对象进行分类和跟踪。该解决方案适用于需要预训练视频智能 API 未涵盖的标签的项目。
4.IBM Watson 的视频人工智能
IBM Watson Media 是一个用于媒体工作流和视频处理的人工智能平台。其视频丰富产品为视频数据提供计算机视觉解决方案。
您可以通过 IBM Watson Media 流式传输事件、观看、视频营销产品发布和 OTT 流。视频丰富使您能够优化视频质量,执行自动视频搜索并自动创建字幕。教育工作者和媒体公司使用该解决方案来改进视频工作流程和盈利内容。
5.使用 Pixop 的视频人工智能
PIxop Platform 是一个 web 应用程序,可以帮助您在云中存储、转码和处理视频文件,利用机器学习。
Pixop 提供了广泛的功能,包括视频质量分析、基于项目的视频资产管理模块,以及一些支持团队和客户之间协作的功能。
Pixop 平台完全基于云,不需要硬件投资,也不需要安装软件。以下是 Pixop 的几个显著特点:
- Pixop 深度恢复 —通过执行去模糊、消除压缩伪像以及将细节注入降级视频等任务,帮助恢复视频质量。
- Pixop 超分辨率 —这是一个透明的升级器,有助于锐化和提高分辨率,提供比插值更精确的结果。
- Pixop Denoiser —帮助减少数码噪音,改善粒状素材。
- Pixop 解交错扫描仪 —帮助重建交错视频的细节,并将其转换为非交错和渐进的形式。
- Pixop 去抖动器 —帮助稳定和修复因视频转换为数字格式而移位的扫描线。
6.视频人工智能与瓦洛萨
Valossa AI 是一个技术平台,为视频内容提供分析和自动化配置。它提供了以下主要功能:
- 自动预览 —自动生成视频预览,加速内容营销和促销活动。可用于创建视频点播服务,提供在线视频的智能预览。
- 视频识别 API —能够检测和描述视频流中的关键概念。生成场景级别的时间编码元数据,支持视频内容的搜索、检索和组织。
- 人脸分析工具包 —实时识别视频内容中的人脸。分析实时行为和人口统计属性。这通过将人工智能与实时摄像头馈送结合起来,实现了交互式应用。
结论
基于云的视频人工智能平台提供了令人惊叹的功能,即使你的团队不具备数据科学技能,你也可以轻松利用这些功能。通过使用视频人工智能平台,您可以实现以下部分或全部目标:
- 视频流的自动化预处理
- 自动优化视频质量
- 识别视频中的对象、文本和概念
- 自动为视频添加标题、标签和类别
- 创建视频预览和短片
我希望这将有助于您构建下一代视频产品。
边缘视频分析
使用 Raspberry PI 和英特尔 NCS 从 CCTV IP 摄像机中检测人体

尼克·洛格在 Unsplash 上的照片
我最近在家里安装了闭路电视系统。它由 4 台 IP 摄像机组成。我希望他们更聪明一点。不要总是看闭路电视录像,如果任何一个摄像机检测到有人,最好能得到通知。这真是一个很酷的东西。我想,如果任何一台摄像机检测到运动,我能在电报中得到一个警报,那就太好了。虽然我开始只是为了好玩,但我学到了很多。从根本上说,它无需云就能在边缘实现视频分析。我在这里分享我的学习,并渴望向你学习,如果有优化的方法。
目标
我有 4 个不同的 IP 摄像头,可以以 25FPS 的速度播放 720P 的视频。我有一个闲置的树莓派,还有一个英特尔 Movidius 神经计算棒(即第一代 NCS)。我希望 Raspberry PI 和 NCS 执行人体检测,并在摄像头检测到人体时向我发送电报通知。在构建过程中,我认为有几件事非常重要,它们是
1.我应该能够几乎 24/7 运行应用程序,这意味着代码应该是高效的。CPU 利用率必须尽可能低,以便 PI 的温度得到控制。
2.我应该会立刻收到通知。最优先考虑的是,一旦发生,人的检测应该尽快完成。
3.通知应该包含一个图像,以便我可以理解警报的上下文(是谁的人)。
让我提前告诉你,第 1、2 项比我想象的要难多了。
项目开始:
我认为这是一个简单的任务,假设 Raspberry pi 可以从每个摄像机获得视频帧,NCS 可以执行人体检测。然而,这并不容易。
像往常一样,我从普通的 OpenCV 代码开始,在一台摄像机上读取视频 RTSP 流,看看它是如何工作的。
cap = cv2.VideoCapture(“RTSP URL of the IP Camera”)
while(True):
ret, frame = cap.read()
cv2.imshow('frame',frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
然而,很少有事情不像我预期的那样。首先也是最重要的,在实时获取帧时有巨大的延迟。也就是说,即使没有处理(人工检测),也会有大约 5 到 10 秒的延迟,在某些情况下,在运行应用程序的几分钟内,延迟会达到 40 秒。CPU 利用率立即达到 100%。在这一点上,我意识到这比我想象的要复杂得多。所以,我不得不从头开始,看看解决这个问题的最好方法是什么。
在我的分析过程中,我观察到这种解决方案不需要处理来自相机的每一帧。我们绝对不需要分析一秒钟内产生的 25 帧。这是我在设计中使用的主要原则之一,我提出了以下设计。什么参数将帮助我们定义系统可以处理的帧数将在后面讨论。
设计

作者图片
正如你可能注意到的,这个想法是在每 X 秒钟内从每个摄像机捕捉 N 个帧。这并不是使系统有效运行的唯一原因。该解决方案利用了 Raspberry pi 中可用的硬件加速,因此 CPU 使用率非常低。这将在我描述 GStreamer 时详细介绍。帧数和持续时间可变的原因是,它完全基于用于处理帧的硬件的处理能力。据我所知,第一版英特尔 NCS 能够执行的推理数量较少。正如您所想象的,该图清楚地显示了处理引擎中的瓶颈,因为它必须处理所有的帧。由于我计划利用现有的英特尔 NCS,我必须根据 NCS 的能力调整这些参数。
作为一个起点,我决定在每一秒钟内从每一台摄像机中捕捉一帧。这在我看来是有道理的。因为对于一个人来说,在不到一秒的时间内通过摄像机的观察区域实际上是不可能的。然而,处理引擎有可能无法检测到对象,但这是一个单独的主题,我们将在后面讨论。
我意识到,当我使用 OpenCV 时,很难指定我想要捕捉的帧数/秒。因此,探索并发现可能有两种方法来解决这个问题。一个是 GStreamer,另一个是 FFMPG。所以我决定进一步探索第一个选项 GStreamer。
我发现 GStreamer 非常强大,但由于缺乏文档/示例,它非常复杂。所以我开始在 youtube 上探索一些教程,我发现它们非常有用。我在参考资料部分提到了帮助我学习 GStreamer 的链接。我相信这对你也会有帮助。
安装和设置
这个实验的源代码可以在这里找到:
https://github.com/abypaulvarghese56/Edgevisionpi
我的第一步是在 Raspberry PI 中安装 GStreamer。这是相当直接的。
sudo apt-get install gstreamer1.0-tools
下一步是为 raspberry PI 安装英特尔 OpenVino toolkit。这是必要的,因为处理引擎是英特尔 NCS。我必须安装一个支持英特尔 NCS 的 OpenVINO 版本,因为最新版本的 OpenVINO 只支持英特尔 NCS 2。请点击下面的链接,并根据您拥有的 NCS 安装 OpenVINO。
其他库包括:
pip install telepot
pip install viztracer
sudo apt-get install pkg-config libcairo2-dev gcc python3-dev libgirepository1.0-dev
sudo apt-get install python3-gi
pip install gobject PyGObject
下一步是创建 GStreamer 管道。正如我前面提到的,它是非常强大的工具。Gstreamer 通过使用管道来配置,管道是一系列命令,指定从哪里获取视频,如何处理和编码视频,然后将视频发送到哪里。
让我们看看我创建的管道。
pipelinestring = "rtspsrc location={0} name=m_rtspsrc ! rtph264depay ! h264parse ! v4l2h264dec capture-io-mode=4 ! video/x-raw ! v4l2convert output-io-mode=5 capture-io-mode=4 ! video/x-raw, format=(string)BGR, width=(int)640, height=(int)480 ! videorate ! video/x-raw,framerate=1/1 ! appsink name=m_appsink sync=false"
让我们看看每个元素的用途。
RTSP src location = { 0 }-该位置是摄像机流的 RTSP url。
rtph264depay —从 RTP 包中提取 H264 视频。
v4l 2h 264 dec——这个很有意思。这告诉 Raspberry PI 使用 GPU 解码 H264。请记住,有一个替代方案是使用 CPU 来解码。是 avdec_h264。如果我们试图只从一个摄像头解码,使用 avdec_h264 可能会有效。然而,因为我们的目标是从 4 个摄像头捕捉,所以将它卸载到 GPU 是非常必要的。
让我们看看参数,

将视频解码卸载到 GPU 是我们实现最小化 CPU 使用的目标的关键一步。
v4l2convert —这是让系统知道使用 GPU 而不是 CPU 来转换帧。有一个相当于视频转换 CPU。这又是一个 CPU 密集型过程,所以我们应该使用 v4l2convert 来利用 Raspberry PI 提供的硬件加速。我们在这里做一些转换,他们是
将源格式转换为 BGR 格式。帧尺寸缩小到 640x480(从原来的 720P)。v4l2convert 的文档显示,有一个选项可以使用 v4l2convert 本身来控制帧速率。然而,我不能让它工作。请让我知道,如果你们中的任何人让它工作,我会很高兴听到。我不得不使用另一个 videorate 来控制帧速率,因为我无法让 v4l2convert 控制帧速率。
这将帮助我们控制我们想要在应用程序中捕获的帧速率。在我们的例子中,我们希望从摄像机中每秒只捕捉一帧。所以会是 framerate="1/1 "。假设我们只想在两秒钟内捕捉一帧,那么帧速率将是“1/2”。如果我们想在一秒钟内捕捉 5 帧,帧速率将是“5/1”
现在的想法是使用多重处理运行视频捕获应用程序,以利用 Raspberry PI 的不同内核。主程序将为每个摄像机启动一个单独的进程。
其余的事情相当简单。一旦帧被捕获,它就被转换成 OpenCV 格式并被推入队列。目前这种转换是使用 python 代码完成的,请让我知道是否有办法在 GStreamer 管道中处理它。我假设会有,但我没有调查。据我所知,如果我们能够在 GStreamer 管道中处理它,它可以进一步减少 CPU 的使用。因为我们有 4 个不同的摄像机,所以会有 4 个不同的队列。队列使用 TCP 协议,因为如果我们希望这个系统在多个硬件上工作,这将有助于我们将来分配负载。也就是说,我们可以根据我们的需求扩展处理服务器,提高帧速率或捕捉间隔,或者添加更多的摄像头。也许我们会在下一个版本中针对分布式环境进行优化。
在 main_prgall.py(第 27 行)中,您需要输入您的流的 RTSP URL
self.camlink1 = 'rtsp://<username>:<password>@192.168.1.2:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif' #Add your RTSP cam link
并根据您的环境添加其余的流 URL。
您可以从 OpenCV 使用 GStreamer 管道。Opencv 需要在 GStreamer 支持下构建。
处理服务器
这个想法非常简单,从队列(4 个队列)中抓取帧,并将其推送到英特尔 NCS。这里的队列使用发布/订阅模型工作。摄像机是酒馆,流程服务器是 SUB。如果 NCS 在一个帧中检测到一个人,我们将把这个帧移到另一个队列中。我使用的是英特尔型号 zoom 的个人检测零售 013 型号。该模型的详细信息可在此处找到:
您应该使用与您的设备兼容的型号,这一点非常重要。如果您有 NCS2,那么您应该使用支持 NCS 2 的型号。这是一个可以在视频帧中检测出人的模型。它接受高度为 320、宽度为 544 的输入帧
通信引擎
这是一个非常简单的组件。它从队列中获取已处理的数据,并将图像存储到本地目录中。它还与电报机器人连接,并发送附有图像的通知。
Bot 密钥和用户 ID 从源代码中删除。你必须插入你自己的电报机器人密钥和用户 ID
结果

从图像中可以看出,这些帧几乎是实时处理的。在我的测试中,我发现所有的帧都能在不到 2 秒的时间内处理。(在不到 2 秒的时间内检测到运动)。电报机器人发送图像时可能会有轻微的延迟,但这是基于你的互联网连接。然而,我发现它在几乎所有的情况下都可以忽略不计。
CPU 消耗
CPU 消耗大约为 30–35 %,考虑到它每秒钟处理 4 个不同流的视频,这是可以接受的。下图显示了 raspberry pi 报告的 CPU 消耗和温度。
我发现了另一种降低 CPU 占用率的方法。这不在 python 脚本中,而是在 CCTV 本身中。大多数 IP 摄像机支持多个流。即一个主流(具有高质量分辨率和帧速率)和一个分辨率降低的子流(在我的例子中是 640x480)。以下截图显示了我的相机网络界面中的设置

下图显示了连接到子流时的 CPU 利用率和温度

您可能会注意到,整体 cpu 利用率在 16–22%之间,这似乎非常合理。由于繁重的工作是由 GPU 完成的,因此系统能够在不到 2 秒的时间内处理多个流,同时占用较少的 CPU 资源
结论
树莓派是一个很棒的平台。您将能够找到大量的库和支持。相对便宜又高效。英特尔 NCS 1 尽职尽责。很可能英特尔 NCS2 将能够执行更多的推理,因为它被宣传为在性能方面更好。我没有机会在 NCS 2 中测试这个应用程序,因为我没有机会。我真的很想听听你们中是否有人愿意在 NCS2 中测试脚本并报告性能。话虽如此,只是碰巧我的实验用到了 NCS1 和 Raspberry PI。从成本角度来看,如果你从零开始,这可能不是最好的平台。RPI 将花费大约 4K 印度卢比,NCS 花费我大约 8K 印度卢比。很好奇想知道 Nvidia 的 Jetson Nano 2GB 怎么样?它比 NCS2 有更好的性能,不需要像 PI 那样的另一个 SBC。费用在 5000 印度卢比左右。也许我会在下一次更新中提到它。
参考文献
https://github.com/sahilparekh/GStreamer-Python——这是一个很棒的报告,展示了如何在 OpenCV 中获取 RTSP 流。我在我的项目中扩展了这个样本,这归功于各自的所有者。
https://www.youtube.com/watch?v=HDY8pf-b1nA—这是开始学习 GStreamer 的好地方。这真的帮助我了解了 GStreamer。
http://life style transfer . com/how-to-use-gstreamer-app src-in-python/—这是学习高级 Gstreamer 概念的好地方。一组很好的工作示例和很好的解释。
使用 PyTorch 理解视频
了解如何使用 PyTorch 视频和 Lightning Flash 通过 3 个简单的步骤推断自定义视频理解模型

沃尔玛最近开发了一个视频理解系统,可以检测新鲜食品的缺陷和腐败迹象。照片由来自 Pexels 的 Pixabay 拍摄
视频理解,自动化广泛的商业用例,从零售到医疗保健到农业,它使计算机能够识别视频中的行为、对象和活动。
在其最新发布的版本中,Lightning Flash使用脸书 AI Research 的新 PyTorchVideo 由 Lightning 提供动力的库,为视频理解提供支持。
**https://github.com/PyTorchLightning/lightning-flash
Flash 是一个用于快速原型制作、基线和微调可扩展深度学习任务的库。使用 Flash 进行视频理解使您能够根据自己的数据训练、微调和推断 PyTorch 视频模型,而不会被所有细节淹没。
一旦您获得了基线模型,您就可以无缝覆盖默认配置,并体验 PyTorch Lightning 的全部灵活性,从而在您的数据集上获得最先进的结果。
在这篇文章中,你将学习如何通过三个简单的步骤推断一个定制的视频分类模型。
3 个简单步骤中的视频理解
先决条件从 Github Main 安装 Flash

导入 Flash
首先,我们简单地从 flash.video 导入 VideoClassifer 任务。

步骤 2 加载预训练模型
然后我们加载我们想要推断的模型。

Flash 使训练自定义模型变得容易,要了解更多信息,请查看 Lightning 开发人员教程,了解如何通过 5 个简单步骤轻松微调视频理解模型。
https://devblog.pytorchlightning.ai/5-steps-to-training-your-first-video-classifier-in-a-flash-dd11d472fded [## 快速训练第一个视频分类器的 5 个步骤
devblog.pytorchlightning.ai](https://devblog.pytorchlightning.ai/5-steps-to-training-your-first-video-classifier-in-a-flash-dd11d472fded)
根据视频数据进行推断
然后,我们可以通过传递一个有效的路径来推断单个视频或视频目录的模型,如下所示。

把所有的放在一起
这样你就有了根据自己的数据推断视频理解模型所需的所有信息。为了方便起见,所有代码都在这里。

后续步骤
现在你知道了如何用 Flash 的三行代码推断出一个视频理解模型,你可以看看 Flash 使其他 7 个计算机视觉任务变得和视频理解一样简单。
新的任务一直在贡献,所以请留意更新。如果您有任何问题,欢迎在下面评论或通过 Slack 或 Twitter 与我们联系。
关于作者
亚伦(阿里)博恩施泰因 是一名人工智能研究员,对历史充满热情,致力于新技术和计算医学。作为 Grid.ai 的开发者宣传负责人,他与机器学习社区合作,用改变游戏规则的技术解决现实世界的问题,然后将这些技术记录在案,开源,并与世界其他地方共享。**
SQL 中的视图:未充分利用,但非常有用
视图如何帮助跨 SQL 数据库的分析

来源:图片来自 Pixabay
视图是 SQL 中的虚拟表,其功能类似于标准表,但不需要物理存储,即它只存储在内存中,不占用实际存储空间。
例如,经常会有人希望看到表中数据的子集,或者以某种方式组织的数据。
但是,如果只是为了便于理解数据,那么将数据存储在一个表中可能效率很低,因为这意味着额外的存储空间将被用来有效地从另一个表中创建重复的数据。
让我们来看一些例子。
使用 ORDER BY 和 LIMIT 查询创建视图
考虑下面这个名为衣服(所有条目都是作者虚构的)的假设表,它描述了一家商店出售的一系列衣服。
item | price | size
-------------------+---------+--------
Sky Blue Jeans | 79.99 | 31
Fire Red Jeans | 89.99 | 36
Sky Blue Shirt | 59.99 | 38
Grass Green Shirt | 69.99 | 34
...
Peach Purple Hat | 79.99 | 40
Sun Yellow Jeans | 109.99 | 42
Olive Green Hat | 89.99 | 37
想象一下,一个表中有数千个这样的条目。但是,商店的所有者想要创建一个视图,其中 1)只显示商品和价格,2)只显示按价格排列的前三个条目。让我们创建一个视图,并将其命名为 condensedclothes :
create view condensedclothes as select item, price from clothes order by price desc limit 3;
结果输出如下:
>>> select view condensedclothes from database;
item | price
-------------------+---------
Black Sunglasses | 149.99
Blue Sneakers | 129.99
Red Polo Shirt | 129.99
在这个假设的场景中,我们可以看到下面的视图让所有者快速查看哪些商品的零售价格最高。然而,使用视图具有以下优点:1)它允许所有者快速查看相关信息,而不必每次都输入单独的查询;以及 2)视图不使用存储空间来显示,因为它存储在存储器中。
创建具有内部连接的视图
除了上表之外,假设还存在另一个提供所列项目说明的表:
>>> select * from clothes limit 1;
item | price | size
-------------------+---------+--------
Sky Blue Jeans | 79.99 | 31>>> select * from clothes2 limit 1;
item | colour | type
--------------------+----------+----------
Grass Green T-Shirt | Green | T-Shirt
现在,让我们假设所有者希望将两个表连接在一起,并按价格查看前三个销售商品——但这次是从衣服 2 表中添加信息。
>>> create view orderedclothes as select t1.item, t1.price, t2.colour, t2.type from clothes as t1 inner join clothes2 as t2 on t1.item=t2.item order by t1.price desc limit 3;
CREATE VIEW>>> select * from orderedclothes;
item | price | colour | type
--------------------------+---------+---------+-------
Black Sunglasses | 149.99 | Black | Sunglasses
Blue Sneakers | 129.99 | Blue | Sneakers
Red Polo Shirt | 129.99 | Red | Polo Shirt
(3 rows)
正如我们所看到的,选择新创建的视图(在本例中命名为ordered waters),允许我们按降序查看价格最高的三个项目,但是添加了来自第二个表的信息。
同样,视图存储在内存中,没有使用数据库本身的存储。
视图的缺点
正如我们所看到的,从能够快速方便地查看表的重要部分的角度来看,视图非常有用。它们还具有不占用存储空间的优点。
然而,使用视图也有一些缺点——主要是在数据操作方面。
例如,虽然更新视图中包含的数据是可能的,但在许多情况下都不可能做到。这包括在更新视图时使用联接,使用 GROUP BY 子句,以及在使用视图时 DISTINCT 子句不可用。
在这方面,如果需要定期更新视图中的数据,视图并不总是最佳选择。
但是,值得记住的是,视图中的数据本身可以存储为一个单独的表。
例如,假设我们希望获取 condensedclothes 视图,并将数据存储在一个表中(我们称这个表为 condensedtable )。我们可以这样做:
>>> create table condensedtable as select * from condensedclothes;
SELECT 3>>> select * from condensedtable;
item | price
-------------------+---------
Black Sunglasses | 149.99
Blue Sneakers | 129.99
Red Polo Shirt | 129.99
(3 rows)
一旦来自一个视图的数据现在被存储在一个表中,用户就可以更加灵活地利用更大范围的查询,而这在使用视图本身时是不可能的。
结论
本文介绍了 SQL 中的视图以及如何使用它们。特别是,我们看了看:
- 视图的优势和使用它们的原因
- 如何在一系列 SQL 查询中实现视图
- 视图的缺点以及何时以表格格式存储更有意义
同样,使用视图的主要优点之一是,它允许查看表的精简版本,而不必使用存储空间——因为视图存储在内存中。
事实上,视图可以提供两方面的优势,1)在视图中快速分析数据,2)当希望执行更高级的查询时,将数据存储在表中。
非常感谢阅读,任何问题或反馈都非常感谢!您还可以在这里找到原始文章,以及有用的 SQL 实践的更多例子。
参考
- 斯蒂芬斯、琼斯和普勒(2016):24 小时内的 SamsTechYourself
免责声明:本文是在“原样”的基础上编写的,没有任何担保。它旨在提供数据科学概念的概述,不应被解释为专业建议。本文中的发现和解释是作者的发现和解释,不被本文中提到的任何第三方认可或隶属于任何第三方。作者与本文提及的任何第三方无任何关系。
Viola Jones 算法和 Haar 级联分类器
初学者完全解释和数学
Viola Jones 是一种快速检测物体的新方法,具有每秒 15 帧的运行能力。这是第一个实现实时目标检测。

(图片由作者提供)
在本文中,我将讨论“Viola Jones 算法”,它包括以下子主题:
- 维奥拉·琼斯探测器
- Haar like 特征是什么?
- 什么是整体形象?
- 用积分图像计算类哈尔特征
- 升压和 AdaBoost 算法
- 深入研究 AdaBoost 算法数学
- 级联过滤器
- 使用 OpenCV 库实现
维奥拉·琼斯探测器
一种中提琴琼斯检测器,包括以下步骤:
- 计算积分图像
- 计算类哈尔特征
- AdaBoost 学习算法
- 级联过滤器
哈尔有什么样的特征?
哈尔特征是人脸检测的相关特征。它是由阿尔弗雷德·哈尔在 1909 年提出的。它们就像卷积核。有各种类型的 haar 类特征,但最常用的特征是:
- 2 个矩形哈尔特征
- 3 个矩形哈尔特征
- 4 个矩形哈尔特征

(图片作者)灵感(https://docs . opencv . org/3.4/D2/d99/tutorial _ js _ face _ detection . html)
2 矩形特征的值是 2 个矩形区域内的像素之和的差。这些区域具有相同的形状和大小,并且水平和垂直相邻。三矩形特征计算中心矩形的总和。最后,四矩形特征计算矩形对角线对之间的差异。这些不同大小的区域的各种变化在图像中进行卷积,以获得将被输入到 AdaBoost 训练算法的多个滤波器。使用标准技术计算这些特征将需要很长的计算时间。为了缩短这一时间,作者提出了一种称为积分图像的新方法。
什么是积分图?
因为我们必须在所有可能的大小和位置使用 haar-like 特征,这最终导致大约 200k 个特征来计算哪个是真正大的数字。haar 特征的新颖计算的问题在于,我们必须多次计算给定区域的平均值,并且这些操作的时间复杂度是 O(n*n)。我们可以使用积分图像方法来实现 O(1)运行时间。积分图像中的给定像素是左边所有像素和它上面所有像素的总和。

(图片由作者提供)灵感(https://www.mathworks.com/help/images/integral-image.html)

(图片由作者提供)灵感(https://m.blog.naver.com/natalliea/222198638897)
原始图像中所有紫色框的总和等于积分图像中绿色框的总和减去积分图像中的紫色框。
利用积分图像计算类哈尔特征
使用积分图像,我们可以实现哈尔特征的恒定时间评估。
- 边缘特征或 2 个矩形特征仅需要 6 次存储器查找
- 线特征或 3 个矩形特征仅需要 8 次存储器查找。
- 对角线特征或 4 个矩形特征只需要 9 次存储器查找。
2 矩形 = A-2B+C-D+2E-F
3 矩形 = A-B-2C+2D+2E-2F-G+H
4 矩形 = A-2B+C-2D+4E-2F+H-2I+J

计算区域和的技术,用于在恒定时间量内计算 haar 类特征。(图片由作者提供)
升压和 AdaBoost 算法
Boosting 是指任何可以将几个弱学习者组合成一个强学习者的集成方法。大多数 boosting 方法的一般思想是顺序训练预测器,每个预测器都试图纠正其前任。AdaBoost 也称为自适应增强,是最常用的增强技术之一。
AdaBoost :新预测器校正其前任的一种方法是多注意前任欠拟合的训练实例。这导致新的预测者越来越关注疑难病例。这被称为适应性增强。例如,为了构建自适应提升分类器,第一基本分类器(例如决策树或 SVM 分类器)被训练并用于对训练集进行预测。改变和增加误分类预测的相对权重,以便在进行下一个预测时更加重视这些预测。使用更新的权重训练第二分类器,并且再次对训练集进行预测,权重被更新,等等。一旦训练了所有的预测,集成方法使得预测非常类似于增强,除了预测器具有不同的权重,这取决于它们在加权训练集上的整体准确度。这种算法的缺点是它不能并行化,从而增加了所需的时间。因此,在所有特征上成功运行 AdaBoost 之后,我们剩下的是检测所需的最相关的特征。因此,这减少了计算时间,因为我们不必遍历所有的特征,并且效率更高。

(图片由作者提供)受启发(使用 Scikit-Learn、Keras 和 TensorFlow 进行动手机器学习:构建智能系统的概念、工具和技术)
深入研究 AdaBoost 算法
让我们仔细看看 AdaBoost 算法。每个实例权重 w(i)最初被设置为 1/m。训练第一预测器,并在训练集上计算其加权误差率 r1

这里,我们只取错误分类的实例,将这些实例的权重相加,得到加权错误率(图片由作者提供)
然后使用下面给出的公式计算预测器的权重 j。预测器越精确,其权重就越高。如果只是随机猜测,那么它的权重将接近于零。然而,大多数情况下,它是错误的,其权重将是负的。

(图片由作者提供)
接下来,使用下面提供的公式更新实例权重,以提升错误分类的实例

(图片由作者提供)
然后,使用下面提供的公式对所有预测值进行归一化。

(图片由作者提供)
最后,使用更新的权重来训练新的预测器,并且重复整个过程,直到达到用户指定的预测器的期望数量。
在推理过程中,AdaBoost 简单地计算所有预测器的预测值和权重,然后使用预测器权重αj。预测类是获得大多数加权投票的类。
级联过滤器
- 强特征形成二元分类器。:正匹配将被发送到下一个特征。否定匹配被拒绝并退出计算。
- 减少花费在错误窗口上的计算时间。
- 可以调整阈值来调整精度。阈值越低,检测率越高,假阳性越多。

(图片由作者提供)灵感(https://www . research gate . net/figure/Cascade-classifier-illustration-2 _ fig 2 _ 323057610)
简而言之,每个特征都充当级联过滤器中的二元分类器。如果从图像中提取的特征通过分类器,并且它预测图像由该特征组成,则它被传递到下一个分类器,用于下一个特征存在检查,否则它被丢弃,并且检查下一个图像。这因此减少了计算时间,因为我们必须只检查对象不存在的窗口中的一些特征,而不是检查所有特征。这是算法的主要部分,允许它以大约每秒 15 帧的速率处理视频,并实现实时实施。
使用 OpenCV 库实现
参考文献
- 使用简单特征的增强级联的快速对象检测:https://web.iitd.ac.in/~sumeet/viola-cvpr-01.pdf
- 检测人脸(Viola Jones 算法)——电脑爱好者:【https://www.youtube.com/watch?v=uEJ71VlUmMQ】T2
- 使用 Scikit-Learn、Keras 和 TensorFlow 进行机器实践学习:构建智能系统的概念、工具和技术
敬请关注新的研究论文这样的解释!
*请随时联系并给出你的建议:【https://www.linkedin.com/in/mrinal-tyagi-02a1351b1/ *
https://github.com/MrinalTyagi
Python 中的 Violin、Strip、Swarm 和 Raincloud 图是箱线图的更好(有时)替代方案
何时使用它们,如何在 seaborn 库中创建、调整和组合这些类型的可视化

创建箱线图是显示数据集统计摘要的最常用方式。然而,有时,我们可能需要可视化额外的统计信息,并对我们的数据进行更细致的查看。这就是其他类型的图表发挥作用的地方:小提琴图、带状图和虫群图,以及它们的混合图,其中最有趣的是雨云图。在本文中,我们将探索 Python 的 seaborn 库中箱线图的这些替代方案,并找出它们中的每一种在哪些情况下最适用。
对于我们进一步的实验,我们将使用 seaborn 的一个示例数据集— diamonds。让我们下载并快速浏览一下:
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inlinediamonds = sns.load_dataset('diamonds')
print(f'Number of diamonds: {diamonds.shape[0]:,}\n'
f"Diamond cut types: {diamonds['cut'].unique().tolist()}\n"
f"Diamond colors: {sorted(diamonds['color'].unique().tolist())}\n\n"
f'{diamonds.head(3)}\n')**Output:**Number of diamonds: 53,940
Diamond cut types: ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
Diamond colors: ['D', 'E', 'F', 'G', 'H', 'I', 'J']
carat cut color clarity depth table price x y z
0 0.23 Ideal E SI2 61.5 55.0 326 3.95 3.98 2.43
1 0.21 Premium E SI1 59.8 61.0 326 3.89 3.84 2.31
2 0.23 Good E VS1 56.9 65.0 327 4.05 4.07 2.31
数据集相当大。让我们将我们的关注点缩小到超过 2 克拉的理想切割或优质切割的钻石,并且只处理这个较小的数据集。我们只对最好的钻石感兴趣!😀
df = diamonds[((diamonds['cut']=='Ideal')|(diamonds['cut']=='Premium')) & (diamonds['carat']>2)]
print(f'Number of diamonds in "df": {df.shape[0]:,}')**Output:**
Number of diamonds in "df": 1,216
箱线图
现在,我们可以为每个钻石颜色类别的价格范围创建一个箱线图。颜色用大写字母表示,我们可以在这篇维基百科文章中找到更多关于钻石颜色分级的信息。根据外延,我们数据集中的钻石都是无色或接近无色的。
箱线图的主要作用是显示数据集的五位数描述性统计数据:最小值和最大值、中值、第一(Q1)和第三(第三季度)四分位数。此外,它还显示了较高和较低的异常值(如果有),我们还可以选择在图表上添加第六个维度—平均值:
sns.set_style('white')plt.figure(figsize=(12, 7))
sns.boxplot(x='price', y='color', data=df, color='yellow', width=0.6, showmeans=True)# Create a function to customize the axes of all the subsequent graphs in a uniform way.
def add_cosmetics(title='Prices by color for ideal/premium cut diamonds > 2 ct',
xlabel='Price, USD', ylabel='Color'):
plt.title(title, fontsize=28)
plt.xlabel(xlabel, fontsize=20)
plt.ylabel(ylabel, fontsize=20)
plt.xticks(fontsize=15)
plt.yticks(fontsize=15)
sns.despine()add_cosmetics()

作者图片
除了定制图形和轴,我们实际上用 seaborn 编写了一行代码来创建上面的箱线图。我们只调整了图的颜色和宽度,并在每个框上添加了平均值。
上面的方框图清楚地显示了每个颜色类别的价格范围的总体统计数据。此外,从它们的形式来看,存在较低的异常值,平均值几乎在所有情况下都低于中值,我们可以假设每种情况下的价格分布是左偏的,这意味着钻石价格往往相当高。然而,仅仅看这些图,我们无法理解底层数据分布的实际形状和结构。例如,特定颜色类别的分布是单峰的还是多峰的?每个类别包含多少个观察值?不同类别的样本大小有可比性吗?各个观测值在每个分布中的确切位置。
让我们看看创造一个小提琴情节是否有助于我们回答这些问题。
小提琴情节
violin 图类似于 box 图,显示了数据集的相同统计摘要,只是它还显示了底层数据的内核密度图:
plt.figure(figsize=(12, 8))
sns.violinplot(x='price', y='color', data=df, color='yellow', cut=0)
add_cosmetics()

作者图片
我们只调整了cut参数,将其设置为 0。这将每个小提琴限制在实际数据的范围内,而不是向外扩展。
回到我们上面的问题,我们可以说,除了从每把小提琴中间的“迷你箱线图”中获得的每个类别的总体统计数据,我们现在可以看到每个分布的形状。是的,我们关于左偏分布的假设现在被完全证实了。
按类别划分的底层数据的结构呢?我们可以调整inner参数来可视化每把小提琴内部的观察位置和密度:
plt.figure(figsize=(12, 8))
sns.violinplot(x='price', y='color', data=df, color='yellow', cut=0,
inner='stick')
add_cosmetics()

作者图片
现在,我们可以看到每个类别范围内的观察密度。显然,D 和 E 色的钻石比 H 和 I 色的钻石少得多,尽管相应的分布形状看起来非常相似。
然而,调整参数后,我们再也看不到每把小提琴内部的微型盒图了。此外,我们还看不到每个的底层数据点。
带状和群体图
这两种类型的图代表了分类变量的散点图的实现,即它们都精确地显示了分布的内部结构,特别是其样本大小和单个观察值的位置。主要区别在于,在群集图中,数据点不会重叠,而是沿着分类轴进行调整。另一方面,带状图中点重叠的问题可以通过设置调节点透明度的alpha参数得到部分解决。
让我们比较一下这些图:
plt.figure(figsize=(16, 11))plt.subplot(2, 1, 1)
sns.stripplot(x='price', y='color', data=df, color='blue',
alpha=0.3, size=4)
add_cosmetics(xlabel=None)plt.subplot(2, 1, 2)
sns.swarmplot(x='price', y='color', data=df, color='blue', size=4)
add_cosmetics(title=None)plt.tight_layout()

作者图片
带状图和群集图的主要缺点是,它们只能在相对较小的数据集上运行良好。此外,它们不像箱线图和小提琴图那样显示五位数的描述性统计数据。
杂交地块
为了避免丢失有价值的信息并结合不同图表类型的优势,我们可以考虑创建混合图。例如,让我们结合每个类别的 violin 和 swarm 图:
plt.figure(figsize=(15, 8))
sns.violinplot(x='price', y='color', data=df, color='yellow', cut=0)
sns.swarmplot(x='price', y='color', data=df, color='blue')
add_cosmetics()

作者图片
我们现在清楚地看到,小提琴的内部结构在不同的类别中有很大的不同,尽管它们的外部形状相当相似。实际上,对于具有很少数据点的 D 和 E 颜色类别,创建小提琴图实际上没有意义,甚至会导致错误的估计。然而,对于有许多数据点的类别,swarm 和 violin 图的结合有助于理解更大的画面。
值得注意的是,在上图中,我们几乎看不到被点覆盖的迷你盒图(除非我们决定引入alpha参数),所以我们将移除盒子。此外,让我们为群体图添加另一个维度:区分理想和优质钻石切工的数据点:
plt.figure(figsize=(15, 8))
sns.violinplot(x='price', y='color', data=df, color='yellow',
cut=0, inner=None)
sns.swarmplot(x='price', y='color', hue='cut', data=df,
palette=['blue', 'deepskyblue'])plt.legend(frameon=False, fontsize=15, loc='upper left')
add_cosmetics()

作者图片
我们可以观察到,相对“便宜”的钻石大多是溢价切割,而不是更高等级的理想切割。
如果组的数量不超过三个,带状图和群集图有助于区分不同组的单个数据点。出于同样的目的,我们可以尝试另一种方法:根据颜色类别分别为理想和优质切割创建分组的小提琴图。然而,考虑到我们的一些颜色类别已经非常小,将它们分开以创建分组的 violin 图将导致每个部分的样本大小和数据密度的进一步减少,使得这样的图更不具有代表性。因此,在这种情况下,带状和群集图看起来是一个更好的选择。
有一种类型的混合地块值得特别关注,所以让我们更详细地讨论它。
雨云图
雨云图本质上是半小提琴图、箱形图和带状图的组合。从上到下连续放置,这些地块共同提醒雨云,因此命名为混合地块。不幸的是,无论是在 seaborn 中还是在 Python 中,都没有针对这类情节的预定义代码解决方案(至少目前是这样,而且至少是以易于使用和理解的形式)。因此,我们将从零开始创建它,结合并调整可用的工具。代码注释中解释了每个步骤的技术细节:
plt.figure(figsize=(15, 10))# Create violin plots without mini-boxplots inside.
ax = sns.violinplot(x='price', y='color', data=df,
color='mediumslateblue',
cut=0, inner=None)# Clip the lower half of each violin.
for item in ax.collections:
x0, y0, width, height = item.get_paths()[0].get_extents().bounds
item.set_clip_path(plt.Rectangle((x0, y0), width, height/2,
transform=ax.transData))# Create strip plots with partially transparent points of different colors depending on the group.
num_items = len(ax.collections)
sns.stripplot(x='price', y='color', hue='cut', data=df,
palette=['blue', 'deepskyblue'], alpha=0.4, size=7)# Shift each strip plot strictly below the correponding volin.
for item in ax.collections[num_items:]:
item.set_offsets(item.get_offsets() + 0.15)# Create narrow boxplots on top of the corresponding violin and strip plots, with thick lines, the mean values, without the outliers.
sns.boxplot(x='price', y='color', data=df, width=0.25,
showfliers=False, showmeans=True,
meanprops=dict(marker='o', markerfacecolor='darkorange',
markersize=10, zorder=3),
boxprops=dict(facecolor=(0,0,0,0),
linewidth=3, zorder=3),
whiskerprops=dict(linewidth=3),
capprops=dict(linewidth=3),
medianprops=dict(linewidth=3))plt.legend(frameon=False, fontsize=15, loc='upper left')
add_cosmetics()

作者图片
从上面的 raincloud 图中,我们可以提取每个颜色类别的价格范围的完整统计信息:整体五位数统计、平均值、分布形状、样本大小、基础数据的内部结构,包括各个数据点的位置,以及每个类别中两个不同组之间的区别。然后,我们可以比较颜色类别,了解它们之间的关系和大致趋势。
为了创建一个垂直的雨云图,我们必须对上面的代码做一些小的改动。特别是,在创建每种类型的内部图时,我们必须用 y 替换 x,反之亦然,并剪切每把小提琴的右半部分(即,宽度除以 2,高度保持不变)。至于装饰调整,我们必须交换 x 轴和 y 轴标签,并将图例放在左下角:
plt.figure(figsize=(15, 10))# Create violin plots without mini-boxplots inside.
ax = sns.violinplot(y='price', x='color', data=df,
color='mediumslateblue',
cut=0, inner=None)# Clip the right half of each violin.
for item in ax.collections:
x0, y0, width, height = item.get_paths()[0].get_extents().bounds
item.set_clip_path(plt.Rectangle((x0, y0), width/2, height,
transform=ax.transData))# Create strip plots with partially transparent points of different colors depending on the group.
num_items = len(ax.collections)
sns.stripplot(y='price', x='color', hue='cut', data=df,
palette=['blue', 'deepskyblue'], alpha=0.4, size=7)# Shift each strip plot strictly below the correponding volin.
for item in ax.collections[num_items:]:
item.set_offsets(item.get_offsets() + 0.15)# Create narrow boxplots on top of the corresponding violin and strip plots, with thick lines, the mean values, without the outliers.
sns.boxplot(y='price', x='color', data=df, width=0.25,
showfliers=False, showmeans=True,
meanprops=dict(marker='o', markerfacecolor='darkorange',
markersize=10, zorder=3),
boxprops=dict(facecolor=(0,0,0,0),
linewidth=3, zorder=3),
whiskerprops=dict(linewidth=3),
capprops=dict(linewidth=3),
medianprops=dict(linewidth=3))plt.legend(frameon=False, fontsize=15, loc='lower left')
add_cosmetics(xlabel='Color', ylabel='Price, USD')

作者图片
当然,我们也可以很容易地将前面的所有图形垂直化,用 y 替换 x,反之亦然,交换 x 轴和 y 轴标签,并移动图例(如果适用)。
结论
在本文中,我们探索了 Python 的 seaborn 库中箱形图的各种替代方案,即 violin、strip 和 swarm 图,以及它们的混合图,包括作为特例的 raincloud 图。我们讨论了每种类型的可视化的优势和局限性,如何对它们进行调整,以及它们可以揭示什么样的信息。最后,我们考虑了适用于垂直旋转图的修改。
为现实世界的任务选择正确类型的图表并不一定意味着试图显示数据中所有可能的信息。相反,它取决于任务本身和可用的数据。有时,仅仅创建一个箱线图就足够了,而在其他情况下,我们必须更深入地挖掘数据,以获得有意义的见解并发现隐藏的趋势。
感谢阅读!
你会发现这些文章也很有趣:
https://medium.com/geekculture/creating-a-waterfall-chart-in-python-dc7bcddecb45
虚拟血管生成:一套新的计算机血管生成工具

版权所有:Panthermedia.net/Ugreen
思想和理论
在心血管研究领域,在全球或区域循环中发现的许多循环系统疾病始于控制外周(小动脉和毛细血管)血流和细胞功能之间相互作用的功能失调的化学-机械配对。循环的数学模型和计算机模拟及其与细胞功能的耦合为拓展心血管研究的边界提供了强有力的工具。

顺序血管生长(图片由作者提供)。
然而,血流模型和细胞耦合需要在微血管水平实现定义域。这可以通过成像技术来实现,这种技术能够提供动物模型中小血管的高分辨率图像。但是,即使有了这些工具的力量,它们对不同血管区域的适用性还是相当有限,对人类的翻译也是如此。通过开发旨在以自动方式生成血管网络的方法,找到了避免缺乏关于周围床的血管结构的信息的解决方案,该方法由生理标准驱动并符合一组形态测量约束和经验法则。如此生成的血管网络可以在统计学上与解剖学描述一致,因为它们可以再现在血管网络中观察到的主要拓扑特征和功能反应。

分级血管生长:(左)用标准 CCO 算法获得的血管化;(右)使用 DCCO 算法的血管化(图片由作者提供)。

尺度特异性血管生长:(左)用标准 CCO 算法获得的血管形成;(右)使用 DCCO 算法的血管化(图片由作者提供)。
自动生成血管床有两类算法:分形算法和空间填充算法。第一类算法可以容易地遵循主要形态测量(血管半径、长度、纵横比和分叉角)的统计模型,而不考虑血管区域的形状。第二类算法允许在(2D 或 3D)空间中定义的脉管区域内生成脉管网络。这种空间填充算法的一个特殊类别被称为约束构造优化(CCO ),因为血管网络的顺序生成是由成本函数的约束最小化来驱动的。

具有等径血管入口的圆形和球形区域的同时血管化(图片由作者提供)。
我们在具有简单领域的学术场景和涉及多阶段血管生长的现实场景中测试了用于血管网络生成的自适应 CCO (DCCO)算法,从从人体解剖代表性区域中的详细人体模型中获取的现有血管网络开始。
视网膜内血管形成
除了无血管的视网膜中央凹区之外,作为眼睛血管层之一的视网膜内血管层是均匀血管化的。有趣的是,DCCO 血管化在视网膜动脉的插入点附近产生了四个分支,以与体内观察到的相同的方式形成了基于象限的排列。在更远的位置,通过与解剖描述一致的算法来传递均匀的血管模式。这个简单的例子显示了 DCCO 从一小组约束和阶段中复制体内血管模式的能力。**

由两阶段生长过程产生的内部视网膜脉管系统:(第一行)初始化 S0;(第二行)作为初始条件(蓝色)给出的、在 S1 阶段(浅蓝色)和 S2 阶段(红色)生成的生成树鉴别容器;(第三行)血管半径沿生成网络的变化(图片由作者提供)。
大脑皮层血管化
在第二个例子中,我们在原型人脑的左额回中血管化了灰质组织。作为初始条件 S0,使用以左大脑前动脉(l.ACA)为根的动脉树。
生成的脉管树再现了在实验研究中观察到的几个结构特征。灌注脑回的所有主要分支都发育良好,为组织提供大量血流。观察到不同分支的分布在性质上相似,但在数量上有一些差异,这是由每个分支所供应的相应脉管区域的大小造成的。此外,皮质血管在脑回表面呈现均匀的覆盖,其主要的软脑膜血管在此分支为穿入血管,穿入血管向内延伸,几乎与脑回表面垂直。这导致了到达灰质的第一代血管。反过来,这些穿入的血管引起深部血管的局部分支。最终的血管树均匀地灌注灰质体积,正如在 S3 和 S4 生成的血管的分散所证明的。

由四阶段生长过程产生的额上回中的灰质脉管系统:(第一行)初始树 S0,示出了流入和流出约束;(第二行)从左到右,对应于软脑膜网络(S1 和 S2)、穿透小动脉(S3)和深小动脉(S4)的血管;(第三行)上-后部(左)和头-尾轴(右)中间部分的冠状切片,描述了软脑膜(红色)、贯穿(绿色)和深(蓝色)血管穿过组织的结构组织;以及(第四行)描绘血管半径的最终血管树(图片由作者提供)。
胃血管化
作为最后一个案例,我们根据文献中的解剖描述为胃建立了血管化。
得到的血管树与文献中报道的血管结构定性一致,其中浆膜下丛向浆膜层输送均匀的灌注,而来自该浆膜下网络的穿支供应粘膜下层丛。最后,肌肉层的血液由这种浆膜穿支和粘膜下层丛同时提供。由于在所有区域(浆膜下丛血管化粘膜下层、血管化粘膜下层肌层、血管化粘膜下层肌层)存在将血液输送到邻近区域的血管,该器官的复杂结构在所有阶段呈现更复杂的血管半径分布。

由五阶段生长过程产生的胃脉管系统:(第一行)初始树 S0,包括流入和流出约束;(第二和第三排)从左到右和从上到下,对应于浆膜层(S1 和 S2)、穿支肌层(S3)、粘膜层(S4)和肌层(S5)的血管;(底部一行)最终的血管树描绘了胃的前视图和后视图中的血管半径(图片由作者提供)。
我们介绍了 DCCO 作为一种方法,用于生成具有一系列功能和结构特征的血管网络。这项工作描述了实现 DCCO 的算法,以及为实现不同的和不同的解剖现实的脉管系统的表示所必需的阶段建模的基本原理。
此外,进行了进一步的扩展,以将自动血管形成过程与可用数据相结合,例如从医学图像或心血管系统的详细模型中获得的数据。这些特征使我们能够在患者特定的几何结构中使用 DCCO,为探索小尺度血管的作用及其在不同临床场景中的生理特征提供了新的研究机会。
本文由 DProf 的 Maso Talou 博士、Safaei 博士撰写。亨特、布兰科和 https://www.nature.com/articles/s41598-021-85434-9 最初发表于https://www.nature.com/articles/s41598-021-85434-9。
绝对初学者的虚拟环境——什么是虚拟环境,如何创建虚拟环境(+例子)
深入探究 Python 虚拟环境、pip 和避免纠缠依赖

如果你从事许多不同的项目,那么你会认识到依赖——多个项目需要多个版本,多个包。你不能全局安装所有的软件包,你如何保持跟踪?同样,当项目 a 需要 PackageX_version1,而项目 b 需要 PackageX_version2 时会发生什么?当一切都是相互依赖的一团乱麻时,你如何保持理智?
在这篇文章中,我将试图说服使用 venv(虚拟环境)是保持依赖关系与其他项目分离的方法。我们将从定义什么是 venv 开始,它有什么作用,为什么你需要它。然后我们将创建一个并看到它的所有好处。最后,我们将有一些基本的规则来保持我们项目中的依赖关系尽可能的干净。
1.什么是 venv,我为什么需要它?
当您的一个项目需要一个与另一个项目版本不同的包时,会发生什么呢?当你为一个项目更新一个包时会发生什么:它会破坏依赖于那个包的另一个项目中的代码吗?你如何与他人分享你的代码?

让我们不要让我们的项目的依赖性纠缠在一起(图片由 Vika Aleksandrove 在 Unsplash 上提供)
为了了解 venv 是什么,我们将把它分成两部分:和 环境 。
* *
环境
一个你已经熟悉的环境。例如,当你安装 Python3.10 时,你不仅安装了 Python 引擎,还安装了 pip。当您使用 pip 来安装一个包时,它最终会出现在 Python 安装目录下的 Scripts 文件夹中。这是您的环境:Python 3.10 以及所有已安装的包。
如果你像python main.py一样运行你的程序,就会发生这种情况:
- 你的系统通过系统路径变量查找你安装 python 的地方,这样它就把你的命令翻译成类似于
C:\Program Files\Python39\python.exe main.py的东西。 - 接下来,脚本 main.py 被传递给 Python 引擎。例如,如果你在脚本中的某个地方使用了
import requests,它将在 python 的安装目录中查找这个包,在Lib文件夹中(在我的例子中是C:\Program Files\Python39\Lib)。 - 如果你已经安装了这个包,python 可以在这个文件夹中找到它并导入代码,这样你就可以使用它了,否则它会返回一个错误,说你试图导入的包没有安装
如果不使用虚拟环境,Python 的这种全局安装是您唯一的环境。您可以看到,将所有项目的依赖关系捆绑在一个大盒子里是很危险的。是时候将它划分到虚拟环境中了。
* *
虚拟的
我喜欢把 venv 想成是专门为这个项目创造一个全新的、稍微轻松一点的环境。我们将在接下来的部分中看到,当您为项目创建 venv 时,您实际上为这个特定的项目重新安装了 Python、pip 和 dependencies-folder。这段代码不在你的默认 Python 路径中(例如C:\Program Files\Python39),而是可以安装在任何地方,例如在你的项目文件夹中(例如C:\myProject\weatherApp\venv)。
* *
共享和构建环境
一旦你有了一个虚拟环境,你可以告诉它为你创建一个包含所有包的列表。然后,当其他人想要使用你的代码时,他们可以创建自己的 venv,并使用这个列表来安装所有的软件包,同时安装正确的版本。这使得与他人分享你的代码变得非常容易(通过 git、邮件或 u 盘)。

构建我们的新环境(图片由 Rodolfo Quirós 在 Pexels 上拍摄)
2.创建虚拟环境
让我们创建我们的虚拟环境吧!在下面的步骤中,我们将确保可以创建虚拟环境。对于这一部分,如果您对使用终端没有经验或不熟悉,建议阅读本文。我们将使用名为 virtualenv 的 Python 包来创建我们的 venvs。
*
2.1 安装 Python
- 检查您的系统架构;不是 32 位就是 64 位。
- 从Python 网站 下载并安装 Python。确保与您的系统匹配(32 位或 64 位。
- 如果您在终端中执行
python --version后看到您已经安装的版本,那么 Python 安装正确。
2.2 安装 virtualenv
Virtualenv 是一个 Python 包,它允许我们创建 venvs。我们将在我们的机器上全局安装它。
- 使用 pip 安装 virtualenv 包:我们简单地通过调用
pip install virtualenv来安装它 - 如果你能执行
python -m virtualenv -h,说明 Virtualenv 安装正确。
这个命令告诉 python 加载它的一个(-m)模块,即 virtualenv 模块。h 标志要求 virtualenv 显示“帮助”选项。它会显示一些提示,然后你就知道它安装正确。
2.3 创建我们的第一个虚拟环境
既然我们有了创建虚拟环境的软件,我们就可以创建一个了。
- 导航到要创建项目的文件夹。我们称之为根文件夹。我的情况是
cd c:/applications/newpythonproject。 - 告诉 python 使用 venv 创建一个新的虚拟环境
python -m venv c:/applications/newpythonproject/venv`
这将在你的根文件夹中创建一个名为 venv 的新目录,包含 Python 和 pip 的新版本。我们将在这里安装我们的软件包。
3.使用我们的虚拟环境
让我们开始使用我们的虚拟环境。在这一部分,我们将激活它,安装一些软件包,并再次停用它。

我们的食材准备好了;我们做饭吧!(图片由 Ella Olson 在 Pexels 上拍摄)
3.1 激活我们的虚拟环境
当我们现在运行pip install requests时,它仍然会全局安装它们,这不是我们想要的。为了在我们的 venv 中安装软件包,我们必须激活我们的 venv 以便在那里安装它们。在你的根文件夹中,我们执行这个命令:venv\scripts\activate。(注意斜线是\,其他的(/)不起作用。

蓝色:激活前;红色:激活 venv 后
如果你的环境被激活,你会在你的终端上看到(venv)在你的路径之前,如上图所示。你将要安装的每个包都将被安装到你的虚拟环境中。
3.2 使用我们的虚拟环境
例如,现在打电话pip install requests会将它安装到您的 venv 中。你可以在你激活的环境中调用pip list来看到这一点;您将在那里看到请求。另一方面,如果您在另一个终端(没有激活 venv)调用pip list,您将看到一个不同的已安装包列表。
3.3 停用我们的虚拟环境
你可以通过在你的终端执行deactivate来关闭你的 venv。
4.出口和建造 venv
您已经创建了一个应用程序,它可以处理一些依赖项。假设您已经安装了 requests 和 pandas 来从 API 请求数据,在 pandas 中进行一些清理,然后将其保存到一个文件中。现在你想让其他人使用你的程序,但问题是:他们需要安装完全相同的依赖项和正确的版本。让我们给他们一个清单。

很像一本烹饪书,我们为其他用户创建了一些说明(图片来自rod nae productionsonPexels
4.1 在 requirements.txt 中列出您的依赖项
图像你有一个你的程序依赖的包的列表。Pip 可以读取这个列表并安装所有的包。
这正是 requirements.txt 所做的。它使用下面的命令pip freeze > requirements.txt冻结文本文件中pip list的内容。非常简单!唯一需要注意的是,上面的命令会在您当前所在的文件夹中创建 requirements.txt 文件,例如您的根文件夹。你也可以在其他地方或者用不同的名字pip freeze > c:/some/other/path/mylist.txt冻结文件,但是不推荐这样做。
4.2 加载您的依赖项
如果你有 requirements.txt,你可以简单地调用pip install -r requirements.txt让 pip 安装所有列出的包。r 标志代表 read,因此 pip 会将它读取的所有包安装到您指定的文件中。
再次注意,它在您当前所在的文件夹中搜索 requirements.txt。也可以像pip install -r c:/some/other/location/requirements.txt一样从另一个位置加载。
4.3 忽略 git 中的虚拟环境
如果你想把你的项目添加到一个像 Github 这样的版本控制系统中,建议不要包含你的整个虚拟环境。这要占用大量的空间和时间来上传和下载。
建议告诉 git 忽略包含虚拟环境的文件夹。为了做到这一点,你可以简单地在你的根文件夹中创建一个名为.gitignore的文件(注意 gitignore 是扩展名,这个文件没有名字),并给它以下内容venv/。这告诉 git 忽略你的 venv 文件夹。如果你保存 venv 的文件夹是别的名字,显然你必须在这里写下正确的文件夹名。
4.4 使用排除的 venv 从 git 项目安装包
现在,当 Bob 从 Github 获取您的代码并想要使用您的程序时,会发生什么呢?如果你聪明的话,你可以把你的包冻结在 requirements.txt 中。Bob 可以简单地pip install -r requirements.txt并开始使用你的程序!
4.5 虚拟环境,requirements.txt 和 docker
Docker 需要一个构建容器的指令列表,requirements.txt 非常适合。docker 文件可能如下所示:
FROM python:3.9-slim-buster# 1\. Copy application code to a folder in container
COPY . /usr/src/downloadService# 2\. Cd to the folder
WORKDIR /usr/src/downloadService# 3\. Tell pip to install all required packages
RUN pip install -r requirements.txt# 4\. Execute app
EXPOSE 5000
WORKDIR /usr/src/downloadService/src
CMD ["python", "-i", "app.py"]
如果你对 Docker 没有经验,不要担心,只要知道我们可以自动将我们所有的源代码转移到一个容器中,安装所有的包(# 3),然后启动我们的应用程序。
5.最佳实践:防止依赖地狱
依赖地狱是指我们所有的项目都被它们的依赖关系纠缠在一起。这很难避免独自工作,尤其是当你在团队中工作的时候。这一部分试图为如何使用 Python 项目制定一些基本规则。
- 每个 python 项目都有自己的虚拟环境
- 用
pip freeze冻结依赖地狱!每次安装或卸载时更新 requirements.txt - 在根目录下包含一个名为
pythonversion.txt的文件,其中记录了您的 python 版本(python --version)。
- 不总是需要的:在 Docker 容器中构建应用程序,这样你也可以跟踪操作系统
遵循这些规则,对于操作系统、python 及其所有依赖项的类型和版本应该没有问题。
结论
我希望我能阐明虚拟环境和 Python 安装依赖项的方式,以及如何让依赖项在项目间纠缠在一起。
如果你有建议/澄清,请评论,以便我可以改进这篇文章。同时,看看我的其他文章关于各种编程相关的主题,比如:
- Python 为什么慢,如何加速
- Python 中的高级多任务处理:应用线程池和进程池并进行基准测试
- 编写你自己的 C 扩展来加速 Python x100
- 【Cython 入门:如何在 Python 中执行>每秒 17 亿次计算
- 用 FastAPI 用 5 行代码创建一个快速自动归档、可维护且易于使用的 Python API
- 创建并发布自己的 Python 包
- 创建您的定制私有 Python 包,您可以从您的 Git 库 PIP 安装该包
- 面向绝对初学者的虚拟环境——什么是虚拟环境以及如何创建虚拟环境(+示例)
- 通过简单的升级大大提高您的数据库插入速度
编码快乐!
—迈克
页(page 的缩写)学生:比如我正在做的事情?跟我来!
https://mikehuls.medium.com/membership *
为 Python 数据科学项目创建虚拟环境
了解如何在 Mac OS Big Sur 上使用 Pyenv、Virtualenv 和 Pip 为 Python 数据科学项目创建虚拟环境

图片由 www_slon_pics 来自 Pixabay
如果你以前使用过 Python,你可能会遇到一种被称为“依赖地狱”的情况。有些包依赖于其他包才能工作。有时候,另一个包所依赖的包必须是某个版本。稍后,您可能需要使用该包的不同版本,因为还有另一个包依赖于该包是另一个版本。避免依赖地狱和创建专业项目的有效方法是使用虚拟环境。
虚拟环境就像是你的项目的一个小胶囊,在一个地方包含了所有合适的包,合适的版本。这使得您可以轻松地从一个项目切换到另一个项目。它还能让其他人在以后复制它。有不同的虚拟环境包,但我喜欢用 Pyenv 。
在本教程中,我将解释如何结合使用 Pyenv、Virtualenv 和 Pip 来管理 mac OS Big Sur 上的虚拟环境。对于 Linux,这些指令可能也是类似的。如果你有 Windows,你可以在这里了解更多关于如何使用 Pyenv 和 Windows 的信息。
注:这款 tutoria l 有一个 存档版本供卡特琳娜用户使用。主要区别在于,对于 Catalina 用户,基于我的个人偏好,我建议使用 Bash。然而,Zsh 现在是 Big Sur 的默认 shell,使用 Bash 完成这种设置的典型过程在 Big Sur 上是无效的。
一、用自制软件安装 Pyenv
根据他们的口号,家酿是“mac OS 和 Linux 缺失的软件包管理器。”要查看您是否已经有了自制软件,请输入:
% brew update
新手注意: 在跟随这些教程时,不需要输入百分号(%)。它应该已经为你准备好了。当您在教程的代码片段中看到 百分号(%) 或 美元符号($) 时,这通常表示该代码片段应该在命令行中输入(在终端中)。百分号表示终端使用的语言是 Zsh,美元符号表示语言是 Bash。 查看这篇文章,了解更多关于 的差异。

准备好接受命令的终端窗口示例。
如果显示您没有自制软件,我建议您阅读本教程以确保您正确设置完成这些说明。
现在,我们可以用自制软件安装 Pyenv。
% brew install pyenv
我知道这看起来有点太容易了,所以这就是问题所在。有一个特殊的文件,你需要访问,使这实际工作。作为一个初学者,我总是发现这部分很难,所以我把它分成了几个小部分。对于一个更高级的用户来说,这种解释可能有点过于具体,但是为了初学者的缘故,我尽量做得具体一些。
二。创造。zshrc
您需要访问的文件称为。zshrc 。注意点(。)在最前面。当您在文件名前面看到一个点时,这意味着该文件是一个隐藏文件。
当我们启动终端时,我们是从我们的主目录开始的。您的主目录通常被命名为您的用户名。要查看主目录中的文件,您可以键入命令 ls 。然而,如果你想看到隐藏的文件,你需要添加参数 -a 。
输入以下内容:
% ls -a
将出现所有文件的列表,包括隐藏文件。

显示机密文件的 Mac OS 个人目录
仔细看文件。你看到一个叫的文件了吗?zshrc ?如果不行,我们一起努力。选择下面两种方法之一来创建此文件。
选项 1:创建。带文本编辑(或任何文本编辑器)的 zshrc
去创造。zshrc 使用任何文本编辑器应用程序,只需在 Finder 中打开您的主目录。
接下来,输入命令+ Shift +。显示秘密文件。

显示秘密文件的 Mac OS Big Sur 个人文件夹。
如果你看不见。zshrc,打开文本编辑器,将下面几行粘贴到一个新文档中。
eval “$(pyenv init -)”
eval “$(pyenv virtualenv-init -)”
在您的主目录中将文件保存为. zshrc。
选项 2:创建。末端为纳米的 zshrc
MAC 电脑装有一个轻型文本编辑器,你可以在名为 Nano 的终端中访问它。要打开 Nano,只需输入:
% nano .zshrc
该命令表示您希望运行程序 Nano。zshrc 是我们将要创建的文件的名称。如果已经创建了该文件或任何文件,Nano 将打开具有该名称的现有文件,而不是创建新文件。
现在,只需将以下内容复制并粘贴到 Nano 中:
eval “$(pyenv init -)”
eval “$(pyenv virtualenv-init -)”
输入 CTRL + x. 退出 Nano,出现提示时,输入 Y 保存文件。

Mac OS Big Sur 终端中的纳米文本编辑器
三。将 Pyenv 添加到路径
最后,我们可以通过在主目录的终端中运行以下命令,将 Pyenv 添加到我们的路径中
echo ‘export PYENV_ROOT=”$HOME/.pyenv”’ >> ~/.zshrc
echo ‘export PATH=”$PYENV_ROOT/bin:$PATH”’ >> ~/.zshrc
这会将 Pyenv 添加到您的路径中,以便您可以在命令行中使用 Pyenv 命令。
四。使用 Pip 安装 Virtualenv
下一步是安装 Virtualenv 与 Pyenv 一起使用来管理我们的虚拟环境和后续依赖项。
使用以下命令调用 Pip 来安装 Virtualenv。
% pip3 install virtualenv
% pip3 install virtualenvwrapper
% brew install pyenv-virtualenv
四。我们做了什么?
1.更新了家酿并安装了 Pyenv
2。在我们的主文件夹中显示秘密文件。
3。使用 nano 找到和/或创建了. zshrc 文件。
4。将 Pyenv 添加到我们的路径中,这样我们就可以在命令行中使用它。
5。已安装 virtualenv、virtualenvwrapper 和 pyenv-virtualenv。
6。将 pyenv 和 virtualenv 正确配置到我们的 shell 中。
动词 (verb 的缩写)下一步是什么?
在下一篇教程Jupyter 笔记本入门中,我将向您展示如何为您的项目创建一个独特的虚拟环境,并安装一些最流行的数据科学包,如 Jupyter 笔记本和 Pandas。
虚拟环境 Python 中的设置和重要性

(图片由作者提供)
直观的 5 步演练,可作为值得收藏的参考
虚拟环境往往是我发现自己反复返回谷歌的数据科学任务之一,以检查我的语法和日常使用中的类似情况。我希望浏览这个过程及其所有值得注意的元素,作为对自己的提醒,并希望作为一个值得收藏的资源,供其他人方便地参考。
我将在我的 Macbook Pro 上使用 virtualenv for Python。我主要使用 JupyterLab,它将在我以后的一些参考资料中出现。在本文中,我将介绍:
- 虚拟环境的重要性
- 创建虚拟环境(virtualenv)
- 完成设置—激活和停用虚拟环境
- 将虚拟环境连接到 Python
- 使用 Pipfile (pipenv)管理需求/依赖关系
虚拟环境的重要性
随着项目组合的增长,管理包依赖变得更加重要。从一个项目跳到另一个项目意味着不同的库用于不同的目的,并且需要进行版本调整以确保兼容性。所有的软件包都不是以相同的节奏维护的,所以自从最新的熊猫发布以来,它可能不再与你在深夜搜索时发现的单人维护的软件包兼容。
虚拟环境为我们解决了这个问题。我们没有创建一个工作环境,试图不断适应我们正在进行的各种项目,这对多任务者来说是一个很大的麻烦,特别是,我们为每个项目创建了一个独立的工作环境。有点像一系列的容器,用来存放和保持我们的项目工作区分开和独立。这使得我们可以进入一个工作环境,而不用担心任何其他项目会影响我们当前正在进行的代码的稳定性。因此,每次我们在一个项目之间切换来运行我们的代码时,不再需要更新 pandas 版本。相反,我们简单地创建一个环境,该环境具有特定的项目依赖性和版本控制。当我们从中断的地方重新开始各自的项目时,或者当我们需要一个测试平台来确保我们在发布之前有正确的环境设置时,我们可以切换到那个环境。

2.创建虚拟环境(virtualenv)
让我们开始创建第一个虚拟环境。在您的终端中,您需要确保安装了 virtualenv,您可以使用 pip 来安装它。如果需要,可以按如下所示安装 Virtualenv。
pip install virtualenv
现在已经安装了 virtualenv,可以用它来设置我们的第一个虚拟环境。仍然在终端中,使用终端中的“ls”和“cd”命令导航到要创建虚拟环境文件夹的位置。在终端中遇到的关于未找到引用文件的任何错误通常需要检查当前目录,以确保使用了正确的位置。下面的代码将在当前工作目录中创建我们的第一个虚拟环境。
所用的{…}包括括号本身,代表了我用来替代编码者为各自项目选择的名称的代码区域
virtualenv {name_for_your_virtualenv}
示例:virtualenv finance_web_app
现在,工作目录中应该会出现一个以您的虚拟环境命名的文件夹。它应该包含几个其他的嵌套文件夹,比如 python 安装,以及将来在环境中安装的任何库的程序。
3.完成设置—激活和停用虚拟环境
现在,在我们开始我们刚刚为之创建环境的项目之前,我们需要激活该环境,以便从终端安装任何需要的库。我们应该使用用于创建环境的同一工作目录,否则我们将需要为要定位的虚拟环境指定额外的路径详细信息。请注意,“bin”是我们在第 2 步中刚刚创建的{name_for_your_virtualenv}文件夹中的一个文件夹。
source {name_for_your_virtualenv}/bin/activate
示例:source finance _ web _ app/bin/activate
可选:通过检查在终端中运行的以下代码“哪个 python”是否返回包含虚拟环境名称(例如:“finance_web_app”)的路径,来验证虚拟环境是否已激活。
which python
一旦安装了项目所需的库(见第 5 节),我们就用下面的代码关闭虚拟环境。停用后,现在可以激活不同的虚拟环境进行维护或更新。
deactivate
4.将虚拟环境连接到 Python
现在,假设虚拟环境已经设置好了,我们仍然需要将它连接到 python,以确保它被识别,并且可以作为内核在 shell 程序中用于我们的项目。
运行以下代码,将虚拟环境“连接”到 Python,作为创建的每个虚拟环境的内核选项。可以同时连接多个虚拟环境,并在 Python shell 程序中进行切换。
ipython kernel install --user --name={name_for_your_virtualenv}
示例:ipython 内核安装—用户名=财务 _web_app
现在,让我们用期望的虚拟环境(内核)来运行项目。在 Jupyterlab 中,当我开始一个新的笔记本时,我看到下面的选项来选择我希望在其中运行我的项目的环境。Python 3 是默认的内核,所有的库都是在没有虚拟环境的情况下安装的。请注意,我们之前创建的 finance_web_app 虚拟环境现在显示在列表中。

(图片由作者提供)
这些内核选项也可以通过点击页面左下方的一个已经打开的笔记本来显示,这个位置当前显示“Python 3”,这是项目运行的当前内核的名称。

(图片由作者提供)“Python 3”代表了该项目的当前内核选择
在 Jupiter Notebook 中,内核选择位置与上面略有不同,但在创建一个新项目或在当前项目中使用内核下拉菜单时有类似的体验。
成功!此时,创建的虚拟环境已经准备就绪。所以小心世界,因为这个虚拟环境已经准备好发出一个大大的“你好!”
当然,根据项目所需的库,可能需要额外的安装。安装包将在下一节也是最后一节介绍。
稍后删除旧的虚拟环境相当容易,可以删除文件本身,并运行以下代码来防止它在 Python shell 中显示为可用环境。只要确保当前目录与文件的位置匹配即可。
jupyter kernelspec uninstall {name_for_your_virtualenv}
示例:jupyter kernelspec 卸载 finance_web_app
5.使用 Pipfile (pipenv)安装和管理需求/依赖关系
在您的环境中安装软件包有几个不同的选项。
Pip 方法
首先,需要激活相应的虚拟环境。然后可以执行软件包的 pip 安装,此时它应该是 shell 中的一个可导入库(可能需要刷新)。下面是 numpy 最新版本和 pandas 特定版本的安装示例。
pip install numpy
pip install pandas==0.21
如果后来在 shell 中导入该库时找不到它,可能是您的终端正在安装到您的默认 python 位置,而不是您的虚拟环境。
Pipenv 方法
如果使用 pipenv 与 Pipfile 的相关性跟踪功能,请确保目标虚拟环境是其文件夹中的唯一环境。这是因为创建的 Pipfiles 将在同一父文件夹中跟踪任何虚拟环境的已安装包。这可能需要使用“cd”命令将工作目录导航到这个新文件夹/路径。
另一种可以缓解这类问题的方法是使用 pipenv,它是 pip 命令的包装器。Pipenv 将确保在虚拟环境中安装所有软件包。它可以用下面的代码安装,而不需要激活虚拟环境。
pip install pipenv
Pipenv 是可选的设置,通过激活虚拟环境,然后运行以下代码,除了安装包(更多细节见下文)之外,还提供其他功能。这将在与虚拟环境相同的父文件夹中创建 Pipfile 和 Pipfile.lock 文件。
pipenv install
现在我们已经设置好了,下面的代码显示了一个使用 pipenv 为最新版本的 numpy 和特定版本的 pandas 安装程序的例子。
pipenv install numpy
pipenv install pandas==1.2.1
除了 pipenv 方法确保将这些包安装到您的虚拟环境之外,它还内置了帮助记录项目依赖关系的功能。
“requirements.txt”文件作为记录项目所需包的标准方法被广泛使用。这里的挑战是,它通常是手动维护的,这带来了出错或不完整的可能性。pipenv 的好处是一旦运行“pipenv install”命令,就会创建两个 Pipfiles,或者伪“requirements.txt”文件,它们会随着软件包的安装而自动更新(和卸载)。Pipfile 包含一个散列,因此如果 python 文件和 Pipfile 被其他用户共享或从 Github 下载,他们只需运行下面的代码(“pipenv install”),Pipfile 将使用散列位置自动定位,并使用必要的包设置环境。
pipenv install
如果之前使用了“requirements.txt ”,那么其中列出的依赖项也可以很容易地用 pipenv 安装,如下所示。
pipenv install -r requirements.txt
Murtaza Gulamali 的这篇文章很好地详细介绍了 pipenv 的用法,作为额外的参考。
结论
这就结束了我对虚拟环境设置和日常使用的演练。希望除了我自己之外,它还可以帮助其他人,在那些语法模糊的时刻作为一个简单的参考,这对于初学者来说是每天都会发生的,对于有经验的人来说甚至仍然是经常发生的脑雾时刻。
欢迎提出任何意见或建议,我会很高兴有机会与您联系。
关系数据库中的虚拟图
超图/关系数据库绝对有优势

同一型号的三视图。实体关系、图和对象-角色建模。图片作者。
我正在研究专用图形数据库如何处理关系/边上的唯一性约束,我的发现让我很惊讶…
假设我们有一个专用的图形数据库,其中有人节点和车节点,并且有一个模式,其中 人驾驶车 是一种关系类型。
在模式的图形数据库中,如何将人员节点限制为“驾驶”一辆汽车节点?

图表模式。图片作者。
我的研究表明,在一些专用的图形数据库中,当数据库执行约束时,你纯粹是运气不好。在创建新的关系之前,您必须以编程方式检查您选择的 Person 节点和 Car 节点之间是否存在关系……数据库不会自动检查关系类型的基数。
然后我发现了一个例子,有人试图用一个专用的图形数据库做一些事情,我认为这是非常了不起的。对我来说,这是一个试图把一个方钉装进一个圆孔的例子,超图/关系数据库肯定比一个专用的图形数据库有优势。让我们看看…
想象以下图表模式:

属性图架构。图片作者。
该模式用于有健康问题的人在医生诊所的预约。
发布问题的论坛上的人试图确定他们是否应该有用于查询目的的日期和时间的节点,并最终被推荐了一个复杂的模式映射关系(Person)-[ON _ Date]->(Date)->[AT _ Time]->(Time)->[TREATMENT _ FOR]->(Condition),并想知道他们将如何管理这样一个模式中各个节点之间的关系基数。尽管专用图形数据库中的(时间)->[治疗条件]->(条件)关系的益处值得怀疑。
再一次,当涉及到管理关系的唯一性时,共识是…这取决于数据库 用户 而不是专用的图形数据库。基本上这是一个令人费解的组合混乱,有更好的方法来处理管理关系基数的场景。
当您使用超图/关系数据库和对象-角色建模时,所有可能的图在一个模式下都是可能的
让我们用对象角色建模来重新审视这个问题:

对象-角色模型。图片作者。
人、日期、时间和条件之间的关系在约会中是唯一的,约会关系是多对多对多的关系。例如,一个人可能在许多日期和时间有关于条件的约会,但是每个约会都是唯一的。我们已经通过在多个/组合关系上具有唯一性约束解决了关系基数的管理。为了使生活变得更容易…让我们看一下与关系数据库的实体关系图相同的模式:

实体关系图。图片作者。
与我们的对象-角色模型类似,关系模型通过设计来管理关系的基数,其中约会的主键(标记为#)覆盖了 Person_Id、Date、Time 和 Condition_Name 之间关系的唯一性。
在我们的对象-角色模型中,对象类型,人、日期、时间和条件在约会事实类型下连接在一起,如 人在日期时间对条件 进行了咨询。
这种关系的表述被称为对象-角色建模(ORM)中的事实类型读取,ORM 的美妙之处在于可以创建与事实类型关联的对象类型集合的任何组合表现:

同一事实类型的各种事实类型读数。图片作者。
基本上,这导致,如果你有一个图形查询语言在你的超图/关系数据库上使用对象-角色建模主干,那么你可以把任何或所有的人、日期、时间和条件当作图形数据库中的节点。例如,您可以将图形查询表述为*:

FactEngine 中的图形查询。图片作者。
- FactEngine 查询语言。有许多图形查询语言。
或者

FactEngine 中的图形查询。图片作者。
也就是说, 图查询 中的节点决定可能与 属性图模式 不同,当查询您的数据库时,这些有效节点有时由超图(其中对象角色建模被视为超图概念建模语言)处理更好,如果您有超图模式…默认情况下您有其相应的图模式。为什么?因为超图比属性图更具表达性,并且封装了所有信息来派生您的图模式。
同样,专用的图形数据库可能并不总是能够以您想要的方式被查询,在使用正确的工具时,数据库的超图处理为您提供了各种各样的查询选项。
考虑以下查询:

FactEngine 中的图形查询。图片作者。
该查询使用部分现存事实类型 Reading(Person Date at Time……)然后使用Person has AppointmentandAppointment for ConditionLink 事实类型进行非常自然的读取图形查询。Person(_Id),Date 和 Time 在我们的关系视图中是属性,但在查询时成为一个超图中的节点,解决了既有图查询又同时有关系基数约束的问题。
也就是说,关系数据库具有优势,因为它们用唯一索引管理关系基数,而用户不必编写额外的代码来检查关系基数约束是否被破坏。使用正确的工具,您可以在关系数据库上编写图形查询。
在本文中,我们介绍了:
- 您不一定需要专用图形数据库中的节点来使用这些概念节点对数据库进行图形查询。
- 对象角色建模中的对象类型可以被认为是超图数据库中的节点,而不管它们是相应属性图模式中的节点还是属性;和
- 关系数据库中的单个实体可能实际上是节点及其在相应超图模式中的关系的集合,用于查询目的,管理复杂的关系基数约束,以及关系数据库最擅长的……索引关系。
Hypergraph 数据库查询引擎正在开始改变数据库的面貌,而 FactEngine 只是一种新型的查询引擎。事实上,FactEngine 体系结构及其查询语言是数据库不可知的,因此没有提升任何特定的数据库,但第一个版本是基于关系数据库的,并将图形查询转换为结构化查询语言(SQL)。目的是通过比较编写 SQL 或使用标准图形查询语言,使查询数据库变得非常容易。你可以在这里阅读更多关于 FactEngine 架构的信息。
感谢您的阅读。我希望这有助于决定为您的下一个项目选择哪种类型的数据库,以及哪种查询语言会使工作更容易。如果时间允许,我会写更多关于图形/超图数据库、关系数据库和对象角色建模的文章。
— — — — —结束— — — —
数据科学家的虚拟演示技巧
如何有效地沟通你的工作
沟通是数据科学工作中最具挑战性的方面之一。以下是我的笔记…
互联网的想法
有一个古老的基于研究的格言,93%的交流是非语言的。你的交流 55%是肢体语言,38%是语气,7%是口语。

图 1:沟通障碍。图片作者。
视频通话中的肢体语言交流会发生什么?它通常会消失。
你应该试着弥补那 55%的损失,更加注重语气和话语。
一些有用的弥补肢体语言差距的技巧包括夸大声音的变化和改变音量。增加你的面部表情和手势也可以提高你的演讲的接受度。这可能看起来不自然,但是如果你记录下你的演讲并重新观看,你会惊讶于这些变化是多么的正常和有魅力。
另一个有趣的想法是内容、设计和交付框架— src 。

图 2:内容、设计、交付框架。图片作者。
后面两个,设计和交付,分别指的是极简的幻灯片设计和复杂主题的简单措辞。然而,内容部分真的很有趣。
简而言之,这篇文章假设你的听众只会从你的演讲中拿走一句话,所以要让它有价值。要做到这一点,你需要了解他们的技术水平、期望,以及项目的先验知识。如果你为你的听众量身定做你的演讲,你可以让那一句话有价值。
一个非常简单的技巧是整合组织中其他团队的视觉效果。例如,展示一段支持你观点的 UX 研究视频。通过利用以前的工作,您可以节省时间,创建引人注目的演示文稿,并在公司内部建立关系。
我的想法
尽管这些想法很棒,但互联网上的绝大多数信息都是常识。在这一部分,我们将重点关注不太明显的策略。让我们开始吧…
1——了解你的听众的想法
对于超过 10 人的会议,假设其中一人不想参加是安全的。他们和你一样,都是生活忙碌的人。
所以,试着去理解是什么让你的观众兴奋。兴奋是完成事情的动力。它让你的工作可见,从而有影响力。
虽然组织之间的角色差异很大,但大多数数据科学家都有一定的自由来选择和开发自己的项目。如果你的工作成功涉及利益相关者的认同,你必须让他们对项目感到兴奋。句号。
不幸的是,这样做没有明确的捷径,但这里有一些对我有用的方法:
- 尽早、经常地将想法社会化。通过在整个项目过程中获得队友和利益相关者的反馈,你建立了兴奋感并产生了更有价值的最终项目。相信我,努力是值得的。
- 了解你老板的老板关心什么。知道什么会让上层管理兴奋,就知道什么会让下层管理兴奋。安排一次快速的一对一谈话,问一些好的问题。
通过将你的工作与令人兴奋的想法联系起来,你可以极大地增加你的演示的价值。
2 —提前结束会议
不必要的信息有害。你会认为展示你的步骤和假设会对你的听众有益。大多数情况下不是。
通过包含他们理解不必要的信息,你…
- 增加 认知紧张 。这会缩短注意力持续时间,降低信任感,并经常导致你在演示过程中一心多用。
- 下意识地偏向你的听众。每个人都有一种确认偏见,这种偏见会影响他们确认自己的信念。如果你给听众额外的信息,他们更有可能抓住有吸引力的想法而错过关键的结论。
- 浪费时间和金钱。时间就是金钱。试试这个练习——把电话中每个人的时薪加起来。呈现不必要的信息代价很高。
一个简单而有效的方法是尝试提前结束会议。我的一个队友已经受到利益相关者和数据科学家的喜爱,因为他总是提前结束会议。这个规则显然有例外,但这些例外比你想象的要少得多。
如果你的目标是早点结束会议,你会被迫变得有条理、简洁和相关。
3 —大约 80/20 的小费
以上两个部分可能是劳动密集型的,所以这里有一些简单的提示,有希望让你在 20%的时间里得到 80%的结果。
- 向你的沟通风格靠拢。如果你是一个正式的演讲者,就要正式地讲话。如果你讲笑话,就讲笑话。通过利用你自然说话的方式,你会变得更自信、更有亲和力,从而更有效率。
- 以结论开始和结束。以结论开始演讲,可以减轻认知负荷,让听众更深入地思考你的想法。通过在结尾重申结论,你促进了的心理分块,这促进了回忆并帮助他们利用新信息。
- 少说“嗯”和“喜欢”。悲伤地重复说这些话会让你听起来更笨。抱歉。这是一个制作很差但免费的“um/like”探测器的一些基本代码。
- 对于技术概念,就像黑匣子一样对待。大多数听众并不关心这个方法是如何工作的。他们关心它的作用。所以你要做的就是解释输入和输出。
感谢阅读!上面链接的所有资源对我和我的职业生涯都非常有影响。请分享你自己的。
VirtualDataLab:一个用于测量合成序列数据集质量的 Python 库
包括内置的开源数据集、合成数据生成器接口和准确性/隐私指标

Gartner 估计,到 2022 年,40%的 AI/ML 模型将在合成数据上进行训练。[1]
事实上,合成数据越来越受欢迎——我在一家合成数据公司工作,每天都看到这种情况。如果你觉得是时候学习一些合成数据技能了,请继续读下去。我将告诉您一些基本知识,并向您介绍一个很酷的开源工具,您会发现它非常方便。开始吧!
所以,最基本的。用合成数据生成器创建合成数据。合成数据生成器或合成器获取其所谓的输入或目标数据,并返回包含与输入或目标数据相同的模式和统计关系的输出或合成数据。
合成数据生成器的范围从琐碎到复杂。一个简单的例子是返回目标数据的混合样本。这非常准确,但侵犯了隐私,因为它是目标数据的直接拷贝。深度神经网络就是一个复杂的例子。在这种情况下,我们牺牲一些准确性来保护隐私。
随着研究机构不断开发新算法,制作合成数据比以往任何时候都更容易,但我们如何决定哪个生成器是最好的?
选择合成数据生成器的最佳实践
衡量质量的典型方法包括查看汇总统计数据或在下游机器学习任务中使用合成数据。不幸的是,这些方法仅测量合成数据反映目标数据的程度。但是,在合成数据复制大部分原始数据的情况下,精确度太高也会带来严重的后果。合成数据的一个关键好处是保护原始数据中所有个人的隐私。因此,我们希望将生成器保护隐私的能力纳入我们的质量测量中。
在创建合成数据方面不缺乏工具或操作指南,但在如何准确衡量合成数据的效用/隐私方面却很少。一个这样的资源是SD gym——从麻省理工学院的数据到人工智能实验室。然而,我们发现我们想要比图书馆提供的更多的功能,所以我们创建了虚拟数据实验室。
介绍虚拟数据实验室
VDL 是一个 python 框架,用于在准确性和隐私性方面对顺序合成数据生成器进行基准测试。
我们希望它:
- 与熊猫一起工作(Python 的数据帧库)
- 使用任何可能的目标数据源进行生成
- 提供一个通用界面来添加新的合成器
- 创建填充了随机生成的数值类型和分类类型的虚拟数据
- 使用顺序数据
- 拥有直观的隐私指标
- 提供一组真实世界的数据集进行基准测试
我在的团队主要是 AI 经常使用虚拟数据实验室来快速评估对我们合成数据生成器的修改。它使我们能够进行标准化的测试分析,从而节省了我们制作一次性报告的大量时间。按照这些思路,虚拟数据实验室的一个用例可以是为下游机器学习任务测试各种合成数据生成器。不用部署几个昂贵的用几个不同的合成数据集来训练机器学习模型,VDL 可以用作选择最接近原始数据集而不是太接近的数据集的代理。
里面有什么?
在当前版本中,包括三种不同的合成器。三个合成器中有两个是普通的,旨在作为指标的基线。一个合成器是在 PyTorch 中实现的简单可变自动编码器。你可以按照这些指令编写自己的合成器。
接下来,我们继续讨论准确性和隐私指标。要更深入了解,请查看 readme.md
精确度是通过计算各列之间经验分布的差异来衡量的。我们用最大误差或 L1 距离/总和(L1D)来总结误差。最大误差告诉我们在数据集上看到的最大误差是什么,而 L1D 提供了我们看到的误差的总体分数。观察经验分布让我们对合成数据从目标数据中捕捉统计趋势的能力充满信心。NIST 在他们的综合数据挑战赛中也使用了分布测量。
隐私是通过将目标数据分为参考集和维持集来衡量的。
从与维持集大小相同的合成数据中获取样本而不进行替换。使用两个基于最近邻的计算,将两个维持集与参考集进行比较。
到最近记录的距离(DCR)测量维持集中的点和参考集中的点之间的距离。坏的合成数据是当原始目标数据被噪声干扰时。DCR 就是要捕捉这样的场景。

可视化的 NNDR:作者的图像
最近邻距离比(NNDR)测量维持点的第一个邻居和第二个邻居之间的距离。点的 NNDR 范围可以从 0 到 1。接近 1 的值意味着该点可能位于聚类中。接近 0 的值意味着该点可能接近异常值。我们计算目标和维持集之间的每个点对的 NNDR,以获得 NNDR 分布。理想情况下,我们希望合成的 NNDR 分布与目标 NNDR 分布相匹配。通过测试其中的差异,我们可以确保合成数据不会比我们基于维持的预期更接近训练点。
这两个隐私指标旨在为数据集提供隐私保证。我们可以将这两个指标与差分隐私进行比较,差分隐私是一种流行的关于隐私损失量的数学限制。在算法的设计中必须建立不同的私有机制。然而,并不是所有的合成数据生成器都需要有差分隐私来生成高质量的私有数据。通过使用这两个度量,我们获得了在公平竞争环境下比较差分专用合成数据生成器与非差分专用合成数据生成器的能力。
您也不需要编写任何代码来生成合成数据。在 MOSTLY AI,我们发布了我们产品的社区版。合成数据就像将原始数据拖放到浏览器中一样简单。
代码演示
该代码也可以在一个谷歌 Colab 笔记本中找到。
使用 FlatAutoEncoderSynthesizer 生成合成数据
现在你知道了!生成合成数据并将其与原始合成数据进行比较,所有这一切只需不到 5 行代码。
分析结果
解释结果也同样简单。在这里,我们在 VDL 提供的 cdnow 数据集上查看 IdentitySynthesizer 和 FlatAutoEncoderSynthesizer 生成的比较结果。该表是使用 virtualdatalab . benchmark . benchmark 函数创建的。在这个 Google Colab 笔记本中可以找到这个函数在更多合成器+数据集上的例子。

汇总表:按作者分类的图像
所有标有 MAX 或 L1D 的列都是误差的量度。高精度对应于这些列中的低值。我们注意到 IdentitySynthesizer 的值接近于 0。这并不奇怪,因为 IdentitySynthesizer 只是返回目标数据的分割。然而,这也正是它未能通过两项隐私测试的原因。相比之下,FlatAutoEncoderSynthesizer 的误差稍大,但通过了两项测试。这是有意义的,因为 FlatAutoEncoderSynthesizer 正在生成全新的数据,但保持与目标数据的统计关系。
您是否使用特定的合成数据生成器技术的标准将基于哪些指标对您最重要。我们建议,如果您正在为隐私进行优化,那么要警惕任何未通过隐私测试的合成数据生成器方法。
自己试试
要开始使用虚拟数据实验室,请使用 repo 或尝试我们的 Google Colab 笔记本电脑!如果你有兴趣参与回购,请联系我!
致谢
这项研究得到了奥地利联邦气候行动、环境、能源、移动性、创新和技术部的“未来信息和通信技术”资助方案的支持。
[1]高德纳。2018.Maverick研究:使用模拟赋予机器想象力。*
机器学习的虚拟化
理解大数据、行业笔记
了解如何托管机器学习(ML)应用程序,例如培训/测试管道、批处理/流预测作业、以业务为中心的分析应用程序等。,是机器学习工程师必备的技能维度。操作化 ML 模型有许多不同的部署可能性。就在几年前,可再现的 ML 环境还是一个需要解决的挑战性问题。在这种情况下,声明式公共云平台上的虚拟化尤为重要。此外,借助虚拟化,可以更轻松地实现快速资源扩展、在云提供商之间过渡部署环境,甚至将应用从内部迁移到云中。本文探讨了用于托管 ML 应用程序的虚拟化技术。注意,我们没有深入讨论,比如实现细节,这将在后续文章中讨论。
虚拟机 (VMs)和容器是在与底层硬件隔离的虚拟化环境中部署应用程序的最常见方式。这两种选择的主要区别在于隔离的程度。根据应用程序的要求,可能需要在使用虚拟机、容器或两者结合之间做出选择。我们将在下一节对这些方法进行概述。如果您熟悉这些概念,您可以跳到概述为 ML 应用程序使用虚拟化技术的部分。
常见的虚拟化技术

图 1:常见的虚拟化架构:(a)基于虚拟机和(b)基于容器。作者图。
虚拟机
虚拟机是托管在数据中心的物理服务器中的虚拟化服务。它提供了灵活性,允许团队托管应用程序,而不必担心如何获得或管理物理服务器。图 1(a)展示了虚拟机架构的概况。如图所示,在虚拟机内部运行的所有东西都在客户操作系统(OS)上,独立于主机操作系统,即虚拟机管理程序。为了启动虚拟机,管理程序启动一个进程来管理特定虚拟机的虚拟化进程,并且主机系统将其一些硬件资源分配给虚拟机。它在启动时引导一个专用的内核环境和一大组操作系统进程。这使得 VM 的大小比仅包含应用程序的典型容器大得多。
运行专用内核和操作系统有三个主要优势:
- 安全性:没有办法从主机上知道虚拟机内部正在运行什么。
- 可移植性:可以在每一个主要的操作系统上运行管理程序,也可以在一个虚拟机中运行任何类型的操作系统。
- 回滚:在一个给定的实例上创建一个虚拟机快照并在需要时回滚到那个实例是很容易的。这有助于处理数据损坏、安全漏洞和其他问题。
然而,虚拟机技术在托管现代应用程序方面有一个主要限制。由于虚拟机映像可能变得非常庞大,即它们可能具有数十千兆字节的大小,因此在没有很长停机时间的情况下,引导、更新和迁移这样大的映像是很难完成的。
容器
容器是关注完全解耦的应用的虚拟实体,例如,被称为微服务。如图 1(b)所示,容器仅包含必要的库和足以在给定操作系统(例如 Linux)上运行单元应用程序的其他系统依赖项。容器与主机系统中的其他容器共享相同的内核。Docker 是一个众所周知的集装箱化平台,能够广泛采用该技术。
容器技术具有以下优势:
- 开始时间:与虚拟机相比,容器通常只需几秒钟就能启动,而虚拟机完成同样的任务需要几分钟
- 效率:容器通常需要较少的计算资源,即 CPU 时间、RAM 和存储空间。因此,在同一基础设施中可以支持更多的应用程序。
- 许可:用于容器的常用技术和库,如 Docker 、 Kubernetes 等。,都是开源的,可以免费使用。因此,采用这些技术没有许可成本。
- 重用:****容器是基于映像的,它包含容器运行给定应用程序所需的二进制文件和库。例如,使用 Dockerfiles 很容易构建 docker 映像。此外,可以使用容器注册中心共享和重用这些图像,容器注册中心基本上是托管容器图像的存储库。
然而,容器缺乏应用程序在虚拟环境中获得的更紧密的安全性。此外,容器与 OS 版本紧密耦合,这使得它们的可移植性较差。
Kubernetes 服务
Kubernetes (K8S)是一个通过提供以下功能来管理容器化应用程序的平台:
- ****自动化容器打包一组节点(如虚拟机)上的 K8S 可以根据给定的 CPU 和内存要求自动运行容器化任务,以充分利用计算资源。
- 故障鲁棒性: K8S 自动重启、替换和终止无响应的容器化任务,停机时间非常短。
- 灵活的存储协调: K8S 支持安装过多的存储系统,例如本地存储、网络文件系统、来自云提供商的 blob 存储等。,到集装箱化的任务。
- ****受控的部署和回滚:通过定义容器化任务的期望状态,K8S 支持以一种优雅的方式部署,这种方式支持众所周知的部署模式,例如 Canary release 。
- 服务发现和负载平衡: K8S 可以将使用服务名/地址的容器公开给外部服务和集群中部署的其他容器。此外,它将平衡副本集中容器之间的负载。
- 秘密和配置管理: K8S 以这样一种方式存储和管理密码、令牌和 SSH 密钥,使得这些敏感信息可以在不重建映像的情况下被动态更改。
使用 Kubernetes 服务虚拟部署 ML 应用程序

图 2:使用 Kubernetes 服务和容器部署 ML 应用程序
ML 应用程序
为了简化讨论,我们将 ML 应用程序分为两类:ML 管道和应用程序,如图 2 中圆形的彩色方框所示。ML 管道(在图 2 中由浅绿色的方框描述)是用于训练和测试 ML 模型的工作流。ML 应用程序(在图 2 中用绿色、蓝色和橙色的实心方框表示)是使用 ML 模型的分析应用程序。图 2 显示了这样的应用。
为 ML 应用程序使用容器
ML 管道中的任务可以在容器中编排。该容器将基于包含相关库和二进制文件的映像,如 Python 、 PySpark 、 scikit-learn 、 pandas 等。此外,负责数据争论、模型训练、模型评估等的应用程序代码。也可以安装在映像中,或者安装在运行时容器可以访问的文件系统中。我们姑且称这个图像为 ML 代码图像。如图 2 所示,深灰色方框代表这样的图像,由 ML 管道使用。
像 ML 管道的容器一样,ML 应用程序的映像包括安装或挂载在本地文件系统中的库和二进制文件以及应用程序代码。此外,它或者包括在文件系统中本地部署的 ML 模型,或者可通过模型服务系统访问,该系统的访问信息被提供。我们姑且称这个图像为 ML 模型图像。如图 2 所示,浅灰色方框代表 ML 应用程序使用的图像。
图像中使用的库和二进制文件可能(大部分)是通用的。因此,它们都可以基于共同定制的基础图像,或者模型图像基于代码图像。
对于采用越来越多 ML 应用程序的现代组织来说,使用公共云提供商的 ML 平台是很常见的,如 AWS Sagemaker 、 Azure ML Studio 和 Google Vertex AI 。所有这些系统都严重依赖于容器。
部署 ML 应用程序
想象一下 Kubernetes 服务,其中应用程序部署在虚拟机集群中。公共云公司提供这样的服务( Azure Kubernetes 服务、 Amazon Elastic Kubernetes 服务、 Google Kubernetes 引擎),不需要或只需要很少的管理开销。这样的服务将使用某种容器注册中心( Azure 容器注册中心、 Amazon 弹性容器注册中心、 Google 容器注册中心)。这些映像的创建和归档可以由持续集成和部署管道( Azure 管道、 AWS 代码管道、谷歌云构建)来支持。查看这个指南,了解如何使用 Azure stack 实现这样一个管道的推荐方法。
图 2 提供了基于 Kubernetes 的 ML 应用程序部署的高级概述。应用程序包括三个领域:一个代表开发模型的团队,另外两个代表使用模型的团队。开发团队的应用程序由绿色方框表示,可以理解的是,涵盖了管道和应用程序类别。另一个团队,用蓝色和橙色框表示,只托管一系列使用该模型的应用程序。为了安全访问,不同团队的容器映像可能包含在不同的存储库中,这些存储库是用于控制对映像的访问的逻辑抽象。此外,一个图像可以被多个应用程序使用,这通过容器注册变得很容易。
这一思路引发了许多深层问题,包括但不限于:
- 图像创建的实现
- 对图像的访问管理
- 映像的持续集成和部署管道的设计和实现
- 向应用程序展示和回滚图像
笔记
如果你对这些类型的挑战感兴趣,可以考虑从事操作机器学习模型的工程职业,即机器学习工程。如果您不熟悉这些技术,可以考虑在云、虚拟机和容器技术领域进行学习。如果你已经在应对这些挑战,请分享。最后,如果你不同意任何观点,请批评性地评论。
用于股骨骨折分类的视觉转换器
思想和理论
我两年主要博士研究的简要简历
目录
- 介绍
- 背景
- 我们系统的全部管道
- 结果
- 结论
- 参考

1.介绍
自从我开始攻读博士学位以来,我一直与都灵(意大利)CTO(骨科创伤中心)的骨科团队合作,开发一种能够帮助医生进行骨折诊断的算法。我们选择股骨作为起点,因为它是最常见的骨折,其正确分类强烈影响患者的治疗和预后。我们从卷积神经网络(CNN)开始了我们的旅程,最近,在文献中第一次应用了视觉转换器 (ViT)来克服该主题中的艺术状态,并为专家提供基于深度学习的支持工具。
在这篇短文中,我将总结(尽可能以最简单的方式)并演示我们系统的有效性,这在 arXiv 原始论文中有更详细的描述。
2.背景
2.1 问题
肌肉骨骼疾病,尤其是髋部骨折,是全球范围内导致严重、长期残疾的最常见原因之一。由于人口的逐渐老龄化,脆性骨折的患病率和发病率正在增加,并且在未来将继续增加。
大致了解一下,2010 年,全球每年髋部骨折的估计发病率为270 万患者。这是一个非常庞大的诊断数量。
当然,医生在这方面负有很大的责任,他们每天要评估数十张 x 光照片。由于多种原因,他们很难评估 X 射线图像:
- x 光可以隐藏骨骼的某些特性
- 正确分类不同类型的骨折需要长期的经验
- 医生经常不得不在紧急情况下采取行动,可能会受到时间和疲劳的限制
在这种情况下,在医生的工作流程中实施 CAD(计算机辅助诊断)系统可能会对患者的结果产生直接影响。
这个理念是我们工作的核心! 开发快速、直观、准确的股骨骨折分类系统,完全依靠 2D X 线。
为了建立一个监督分类器,第一个必要的步骤是理解你想要识别的特定类。在标准分类问题中,最终目标通常是训练一个至少和普通人一样好的网络(例如,几乎任何人都可以识别狗和猫)。不幸的是,这通常不适用于医学领域,尤其是骨折,这是非常棘手的评估,并且需要在该领域具有丰富经验的“非一般”人。那么,专家是如何对股骨骨折进行分类的呢?
2.2 AO 分类
其中一个答案是股骨近端的 AO/OTA 分类,这是一个等级,由骨折线的位置和结构决定。
它区分了三种主要的裂缝类型,命名为 A 、 B 和 C 。然后,根据骨折的复杂程度,考虑骨折线的数量以及碎片的位移,将每组分成不同水平的亚组。

作者图片
从上图来看,这似乎是一个非常简单的问题。但是我们来看一些真实的样品!

作者图片
两年后,我仍然很难用眼睛区分不同的小组。幸运的是,我们实现的网络比我聪明多了。
2.3 分级 CNN
2019 年,当我和我的研究小组开始这项工作时,我对这个话题完全陌生。所以第一件事是进行文献综述,然后我们在这里发表了。从我们的分析来看,我们想要解决的问题显然还没有解决。大多数现有的方法集中在断裂的和未断裂的骨头之间的二元分类,不幸的是,这对于医生的诊断具有非常低的影响。只有两个研究小组试图将骨骼分为不同的亚骨折,但结果仍然不是最佳的。
我们尝试的第一种方法,在本论文中解释,提出了一种多阶段方法,将裂缝分为 5 类(当时,我们还没有获得 B 亚组的标签,并且 C 裂缝由于样本数量少而被排除在外,现在仍然如此),遵循 AO 分类的层次结构。原始 X 射线通过一种半自动方法进行裁剪,用于建立一个具有 2878 个样本的数据集,分为 A1 、 A2 、 A3 、 B 和未中断类。分级方法由三个阶段的级联组成:第一个网络识别未破损或破损骨骼,第二个网络将第一个网络预测为破损的图像分类为 A 和 B、,第三个网络处理 A 亚组。然后将该方法与三个经典 CNN,即 ResNet50、InceptionV3 和 VGG16 进行比较。

作者图片
这种非常琐碎的方法超过了三个 CNN,但远非最佳。当视觉变形金刚出现时,我们正在努力改善这些结果!
2.4 视觉转换器
最近,一种称为 Transformer 的新范式(以前为自然语言处理(NLP)引入)已经在广泛的语言任务中表现出了示范性的性能。
Transformer 架构基于自我关注机制,该机制学习序列元素之间的关系,并且 1)可以处理完整的序列,从而学习长距离关系 2)可以轻松并行化 3)可以扩展到高复杂性模型和大规模数据集。
自然语言处理领域中变形网络的发现引起了计算机视觉界的极大兴趣。然而,可视数据遵循典型的结构,因此需要新的网络设计和训练方案。
因此,不同的作者提出了他们自己的应用于视觉的变压器模型的实现,但是 SOTA 仅由 视觉变压器 (ViT)实现,其特殊性在于聚焦于图像的小块,这些小块被视为令牌。

来自原始 ViT 纸
关于自我关注和视觉转换的更深入的解释可以在我的第一篇关于媒介的文章中找到。
最后来讨论一下我们是如何利用 ViT 的超能力超越这个题目的艺术状态的。
3.我们系统的全部管道
预处理阶段与前一种方法非常相似,但有两个主要区别:裁剪阶段使用 YOLOv3 网络完全自动化,数据集中的样本现在是 4027 个,并分为 7 个不同的类别(出于同样的原因,仍然不包括 C 裂缝)。CNN 和分级 CNN(在第 1.3 节中讨论)用于比较 ViT 与两个基线的结果。在预训练 ViT 的最后,附加了两个密集层用于分类(关于架构的更多信息,我建议阅读 arXiv 上的论文)。
注意力图也被可视化以证明网络确实聚焦于骨骼的正确区域,并且执行聚类实验以观察 ViT 在特征提取中的能力。

作者图片
4.结果
从下表中可以看出,ViT 的表现超过了基线!要了解更多结果,您可以阅读(猜猜是什么?)arXiv 预印本。

4.1 聚类
我们对结果很满意,但我们也想证明网络确实擅长特征提取。为了证明这一点,我们转向了无监督学习:如果一个网络能够在没有标签的情况下识别不同的类别,那么提取的特征肯定是非常多样的。测试了三种聚类方法,结果如下所示:首先,使用卷积自动编码器(a)对图像的初始数据集进行聚类。在第二种情况下,卷积自动编码器被自动编码器代替,该自动编码器将 1024 个值的向量作为输入,一种情况下从 CNN (b)提取,另一种情况下从 ViT 编码器(c)提取。显然,ViT 是唯一一个能够提取有意义特征的,尽管可以理解它仍然在与亚断裂作斗争。

三个聚类的分布。聚类标签以边栏中显示的颜色显示。作者图片
4.2 可视化
我们还可视化了注意力地图,以突出网络在推理过程中关注的地方。专家对这些图进行了评估,确认了 ViT 定位骨折区域的正确方法。

作者图片
4.3 专家评估
最后,为了证明这种工具实际上可以用于医院的日常工作,我们请 7 名住院医生和 4 名放射科医生评估了 150 张没有网络预测的图像和(两周后)有网络预测的图像,平均改善了 29% !

5.结论
这部作品带来了四个方面的新奇之处:
- 我们介绍了有史以来最大和最丰富的股骨骨折分类标记数据集,4207 张图像分为 7 个不同的类别;
- 我们首次为分类任务应用了视觉转换器(ViT),超越了经典 CNN 和分级 CNN 的两条基线;
- 我们可视化了 ViT 的注意力图,并对变压器编码器的输出进行了聚类,以了解这种架构的潜力;
- 我们进行了最终评估,要求 11 名专家通过在线调查的方式对 150 幅图像进行分类,有无我们系统的帮助。
就是这样!第一次,我们取得了非常好的结果,同时达到了 AO 分类的深层次。
该工具的主要局限性在于,在数据集中,某些类的代表性不足。出于这个原因,我们正在与生成对抗网络 (GANs)合作,以产生新的人工但可靠的样本。你能认出下图中的假货样品吗?

作者图片
都是假的!很神奇,不是吗?
6.参考
如果你喜欢这个故事,你也可以看看我的第一篇关于媒体的文章,在那里我解释了一个最有趣和最新的视觉架构,CoAtNet!
https://medium.com/codex/coatnet-how-to-perfectly-combine-cnns-and-transformer-9632e187ecbf
PyTorch 中的视觉变形金刚
当回旋不再是一种趋势。

图片来自 Unsplash。
卷积神经网络(CNN)已经成为几乎所有用于计算机视觉和图像相关任务的网络的主要骨干,因为与传统的多层感知器(MLPs)相比,它们在 2D 邻域感知和翻译均衡方面具有优势。然而,随着最近在语言处理领域中用变换器代替递归神经网络的转变,人们可能会怀疑变换器在图像领域中的能力。
幸运的是,最近在 ICLR 2021* 发表的一篇论文已经探索了这种能力,并实际上提供了一种新的最先进的架构——视觉变压器——这与基于卷积的模型形成了鲜明对比。
本文深入探讨了变压器的概念,特别是视觉变压器及其与 CNN 的比较,并讨论了如何在 PyTorch 上整合/训练变压器,尽管训练这些架构很困难。
*边注:国际学习表示会议(ICLR)是一个顶级的知名会议,专注于深度学习和表示。
CNN 有什么好处?
为什么 CNN 在计算机视觉领域如此受欢迎?答案在于卷积的固有性质。内核或卷积窗口将附近像素的特征聚集在一起,允许在学习过程中一起考虑附近的特征。此外,当我们在图像中移动核时,图像上任何地方出现的特征都可以被检测到并用于分类——我们称之为平移等价。这些特征允许 CNN 提取特征,而不管特征在图像中的位置,因此在过去几年中鼓励了图像分类任务的显著改进。
但是如果 CNN 做了所有这些,变形金刚做了什么?
NLP 中的变压器

图一。原始变压器中的缩放点积注意机制和多头注意机制。来源:https://arxiv.org/abs/1706.03762。
变形金刚最初是在自然语言处理领域的论文中提出的“注意力是你所需要的全部”。该领域的传统方法(例如,RNNs 和 LSTMs)在计算任何预测时考虑短语内邻近单词的信息。然而,由于当前状态(输入)需要计算所有先前的输入,所以该过程是顺序的,因此相当慢。
变形金刚利用注意力方案来计算最终的预测,在某种意义上,注意力方案实质上是矢量化单词之间的相关性。因为一个单词与其他单词的相关性独立于其他单词的相关性,所以同时计算是可能的,并且因此使得深度网络在这种情况下在计算方面更加合理。通过考虑所有的单词和相关性,结果实际上明显优于传统的递归方法。
此外,transformer 融入了多头注意力,它可以并行多次运行注意力机制,并将分离的向量连接到最终输出中。
转向视觉世界

图二。视觉转换管道。图像被分成小块,并被展平以模仿序列。来源:https://arxiv.org/abs/2010.11929。
随着它给语言处理带来的成功,问题出现了:我们如何将技术从语言转移到图像?纸质视觉转换器提供了最直接的方法。它将图像分成小块,并进一步使用这些小块并将它们转换成嵌入,然后将它们作为等价于语言处理中的嵌入的序列来馈送,以找到彼此之间的关注点。
实验代码
在本节中,我们将探索经过良好预训练的视觉转换器,并在各种数据集上测试其功能。值得注意的是,在原始论文的广泛研究中,只有当预训练数据集达到非常大的规模时,vision transformers 才优于 CNN。因此,如果您的计算资源相当有限,那么最好不要自己训练它。
数据集
为了探索视觉转换器的功能和通用性,我们可能需要在多个数据集上进行测试。幸运的是, Graviti 的开放平台提供了计算机视觉领域众多著名数据集的链接和数据库。人们可以简单地选择他们的服务并直接找到数据集的链接。
如果你只关注推理而不是训练,你甚至可以使用他们目前开发的 API,这将简化数据组织和加载过程。
PyTorch 中的视觉转换器
如前所述,由于学习良好的特征提取需要非常大规模的数据,视觉变压器非常难以训练。幸运的是,现在许多 Github 库都提供预构建和预培训的视觉变形器。我们的教程将基于来自https://github.com/lucidrains/vit-pytorch的视觉转换器。
要导入他们的模型,需要通过 pip 安装,如下所示:
*pip install vit-pytorch*
确保 Pytorch 和 Torchvision 库也得到更新,以便版本相互一致。
然后,您可以使用以下内容初始化视觉转换器:
要进行推断,只需执行以下操作:
如果你真的想进一步训练你的视觉转换能力,你可以参考最近发表在这篇论文中的通过蒸馏进行数据有效训练。这种训练方法比直接训练视觉转换者要有效得多。代码也可以在上面提到的 vit-pytorch 库中获得。
结果

图二。视觉转换器在多个数据集上的结果。资料来源:https://arxiv.org/abs/2010.11929。
如果我们回头参考这篇论文,我们可以看到,当使用超大规模数据集进行预训练时,大型视觉转换器模型可以提供最先进的结果。然而,预训练要求这种模型具有相当大的训练能力来实现高精度。
超越视觉变形金刚
近年来,计算机视觉社区一直致力于改进变形金刚,以适应基于图像的任务,甚至 3D 点云任务的需要。最近的 ICCV 2021 论文如云变形金刚和最佳论文获奖者 Swin 变形金刚都显示了注意力机制的力量是图像任务的新趋势。
结论
原来就是这样!趋势变换及其在计算机视觉中的应用概述。
感谢您坚持到现在🙏! 我会在计算机视觉/深度学习的不同领域发布更多内容,所以 加入并订阅 如果你有兴趣了解更多!
视觉变形器还是卷积神经网络?都是!
探索使用卷积神经网络和视觉转换器构建混合架构的各种方法

作者图片
计算机视觉领域多年来一直由卷积神经网络(CNN)主导。通过使用过滤器,这些网络能够通过创建突出最相关部分的特征图来生成输入图像的简化版本。这些特征然后被多层感知器用来执行期望的分类。

图片由作者使用 CNN 解说者创建
但是最近这个领域已经被视觉变形器(ViT)的架构带来了令人难以置信的革命,它通过自我关注的机制已经被证明在许多任务中获得了优异的结果。
在这篇文章中,视觉变形金刚的一些基本方面被认为是理所当然的,如果你想更深入地了解这个主题,我建议你阅读我以前的架构概述。
虽然变压器已被证明是 CNN 的优秀替代品,但有一个重要的限制因素使其应用相当具有挑战性,即需要大型数据集。事实上,CNN 甚至能够在相当少量的数据存在的情况下学习,这主要归功于归纳偏差的存在[1,8]。这些建议让模型更快地学习,更好地概括。具体而言,CNN 有两个与架构功能直接相关的偏见,即:
- 图像中的相邻像素彼此相关;
- 图像的不同部分必须以相同的方式处理,而不管它们的绝对位置。
然而,变压器架构中不存在这些偏差,因此他们需要更多数据来全面了解问题,但同时,他们能够以更自由的方式做到这一点。因此,可以说,变压器能够学习更多,但需要更多的数据,而卷积神经网络实现了对所处理的任务的较低理解,但也用较小的数据摩尔实现了这一点。

作者图片
但是,难道没有一种方法可以从两种架构中获得最佳效果吗?幸运的是,这两种架构基于两种截然不同的概念,可以以多种不同的方式结合起来,以获得能够利用两者优点的东西!
使用 CNN 作为补丁提取器
第一种可能的方法包括在将补片作为输入传递到视觉转换器之前,改变提取补片的方式。这些小块通常是通过将输入图像分成许多小部分而获得的。

图片由作者基于关于变形金刚、时间形成者和注意力
为了理解如何通过卷积网络从图像到补丁,观察其内部功能就足够了:

作者图片
当大图像作为 CNN 的输入时,通过卷积层,它从三通道 RGB 图像转换为 N 通道图像。同时,它的尺寸急剧减小,图像本身的内容也发生了变化。
如果在卷积过程结束时,N 通道图像被认为是一组 N 个小图像,我们就获得了视觉转换器所必需的补丁。因此,可能的卷积视觉变换器的新架构将如下构成:

图片由作者基于 结合 EfficientNet 和视觉变形金刚进行视频深度打假检测
这种技术已被证明在许多情况下特别有效,也可以使用预先训练的卷积网络(如 EfficientNet)作为补丁提取器来应用。这种方法的一个可能的应用已经被我和 Pisa 的 CNR 的研究人员应用于执行视频 deepfake 检测[2],如果你想了解它的更多信息点击这里。
从自我关注到门控位置自我关注(GPSA)
为了能够利用变压器内的卷积网络,利用了自我关注层可以作为卷积层工作的直觉。我们之前已经指出视觉转换器没有感应偏差。因此,脸书研究人员的目标是修改架构,以引入软卷积电感偏差。必要时,新网络必须能够充当卷积网络。
为了实现这个目标,门控位置自我注意(GPSA) [1]被引入,一种带有额外参数λ的位置自我注意形式。该参数用于平衡该层作为卷积层或经典自关注层的功能。在训练期间,网络将校准该参数,并且如果必要的话,在过程结束时,这些层中的一些将充当卷积层。

图片由作者根据 ConViT 制作:利用软卷积电感偏置改进视觉转换器
除了在发生时用于捕获输入中的本地信息的 GPSA 层之外,还有形成网络的非本地部分的经典自我注意层。这种架构被称为卷积视觉转换器(ConViT)。
CMT:卷积神经网络遇到视觉变压器
另一个最近的提议来自华为的实验室,他们引入了一个比迄今为止看到的更先进的架构,提出了他们所谓的 CMT 块[3]。其中许多模块用于新的架构中,将自我关注机制与卷积机制相结合,还引入了一些性能优化。

图片由作者基于 CMT:卷积神经网络遇见视觉变形金刚
每个 CMT 模块由三个基本部分组成:
- 局部感知单元:用于克服传统位置嵌入带来的限制,以及传统视觉转换器无法捕捉单个补丁内的局部关系和结构化信息。局部感知单元(LPU)通过简单的深度卷积提取局部信息。
- 轻量级多头自我关注:为了减少关注度计算中的计算量,通过该组件,使用 K×K 深度方向与 K 步距的卷积来减少矩阵 K 和 V 的空间大小。这样,通过处理由卷积过程产生的更小的矩阵,减少了自关注计算的数量;
- 反向残差前馈网络:这是每个块的最后一层,用一个扩展层取代了视觉变形器的经典多层感知器,之后是深度卷积和投影层。
因此,所得到的架构能够利用两种网络的优点,并且由于在不同层中引入的各种特性而有效地做到这一点。
结论
结合卷积网络和视觉转换器的想法不仅在许多方面可行,而且非常有效。迄今为止,这些变体在 ImageNet 等关键数据集上取得了优异的结果,就该数据集的准确性而言,CMT 目前是最先进的网络。似乎这还不够,进行的实验表明,这些网络也比完全基于卷积网络的经典方法和基于视觉变换器的方法轻得多,也小得多。
许多人将视觉变压器视为卷积神经网络的继任者,但今天看来,这两种方法的结合具有巨大的威力。
我们可以肯定地说:“ 团结就是力量! 。
参考资料和见解
[1]“达斯科利等人”。" ConViT:利用软卷积电感偏置改进视觉变压器"
[2]“cocco mini 等人”。结合 EfficientNet 和 Vision Transformers 进行视频深度打假检测
[3]《郭等人》。" CMT:卷积神经网络遇上视觉变形金刚"
[4]《大卫·柯考米尼》。"关于变形金刚,时间形成者和注意"
[5]《大卫·柯考米尼》。在迪诺上,无标签自蒸馏
[6]《大卫·柯考米尼》。"注意力是你在《变形金刚》中真正需要的吗?”
[7]《路易·布沙尔》。"变形金刚会取代计算机视觉中的 CNN 吗?”
[8]《维克多·佩雷斯》。计算机视觉中的变形金刚:告别回旋!”
根据我的记忆训练的视觉人工智能
#MERZmory:借助人工智能探索摄影

我的 MERZmory 实验的各种完成(StyleGAN +我的照片)//图片作者
我的父亲是一名职业作家和摄影记者。他记录了苏联和俄罗斯的生活和文化。当我们来到德国时,他没有停下来。直到生命的最后几天,他都在拍摄和记录德国。几年前他去世后,留给我一个巨大的照片档案。
重温他的照片档案,我意识到一件事:
摄影师拍下他或她当时经历的地方、人物和情景。这位摄影师与一位旁观者分享记忆,他将在未来的某个特定时间凝视这些照片。
共享的记忆。
大概父亲的职业成了我的困扰——我开始到处拍照——在家里,在工作中,在去日本的旅途中,我也想与世界分享我的记忆和经历——把它们发布在 Instagram 上。它们不仅仅是报道我的行踪的图片;我试图与世界交流我的想法和观点。
当我开始深入研究创造性人工智能时,对我来说最迷人的事情之一是一台机器对人类文化遗产的重新诠释。人工智能如何重建我们的想象、梦想和欲望?
已经让我大吃一惊了。

图片来自我的文章"BigGAN as a Creative Engine"//图片作者
随着 StyleGAN2 的出现,在海量数据库上训练(例如 FlickrFacesHQ 等。),我们已经进入了视觉创意的新领域。
但仍然有一件事让我很困扰。经常使用所有这些数据集(即 ImageNet 等。).但是我的摄影呢,我的内心世界——如果我在作品上训练人工智能,我会认出来吗?
我做到了。
我用我的记忆训练人工智能。
有多种方法可以在数据集上训练 StyleGAN2。幸运的是,对于我们中的非编码人员来说,有 RunwayML (它值得一篇特别的文章[稍后发布])。
在你的照片数据库上训练 AI 的工作流程很简单。你必须:
- 在一个文件夹中收集至少 500 张你想用来训练的图片 /我自己用了大约 3000 张 Instagram 图片
- 使用 RunwayML 上传它们
- 选择您的数据集
- 选择预先训练的模型和训练步骤

3)选择数据集(截屏来自 RunwayML,图片由作者提供)

4)选择预先训练好的模型(截图来自 RunwayML,图片由作者提供)
…然后运行它。
对于我的实验,我使用了各种设置和预先训练的模型。结果令人兴奋。这是我的发现。
实验一:抽象记忆

(截图来自 RunwayML,图片由作者提供)
首先,我采用了预先训练的 StyleGAN2 模型“Flickr Faces HQ”。即使我(最初)不想生成人脸,这种模型的好处是:它具有目前最好的分辨率(1024x1024),正在对来自 flicker 的大约 70k 高清人脸进行训练(你从“这个人不存在”中知道他们)
对于训练步骤,我选择了 4000 。我想在最初的实验中远离面部图像和特征,从语义上转向 Instagram 数据集的内容。
这里有一个免责声明:事情不是这样的。我的 Instagram 数据集由各种风格和流派的照片组成:风景、人像、实验、微距摄影等。StyleGAN2 无法将这种多样性概括为特定的类别。但这不是我想要的。
我想要的是体验,而不是具体的东西。
我已经得到了。

作者提供的图片
我有非常抽象的视觉效果和一些可识别的特征。取而代之的是“相同能量”倾向的新图像。
有趣的还有图像的词源或考古学(在视觉模型的情况下,我们可以使用这两个术语来表示 寻找机器完成 背后的根源和原件)



左:AI 生成的图片/中+右:来自我的 Instagram 数据集的图片//所有图片由作者提供
或者一些微距摄影:



左:AI 生成的图片/中+右:来自我的 Instagram 数据集的图片//所有图片由作者提供



左:AI 生成的图片/中+右:来自我的 Instagram 数据集的图片//所有图片由作者提供
对于了解他的摄影的作者来说,这样的重新发现是鼓舞人心和令人着迷的。
RunwayML 的另一个伟大之处是矢量/潜在空间浏览器,在这里你可以精确地找到你要找的东西。通过改变“截断”和“采样距离”,您可以通过寻找具有稍微不同特征的特定图像来微调您的视觉搜索。

作者提供的图片
在 4000 步之后,我摄影的紧张和情绪战胜了人工智能预训练的模型。



我第一次实验的几张照片。//图片作者
这是我记忆中的视觉旅程,由人工智能创造:
(图片由我提供,音乐由 AI 模型点唱机创作,图片由作者提供)
因此,回顾我的实验,我回顾了我训练的某个地方600——预先训练的 StyleGAN2 模型的脸仍然出现在特征中。我的 Instagram 照片无法辨认。这样的面孔可以赢得每一场“被诅咒的形象”比赛。

作者提供的图片
但现在我看到了一种新的可能性:如果我在 Instagram 图片上稍微训练一下 StyleGAN2,让原始的 FlickrFaces 出现在开头的某个地方,会怎么样?扭曲,但不是令人毛骨悚然的方式?
我做到了。
实验二:记忆的面孔
我在我的 Instagram 照片上重新训练了 StyleGAN,采取了更少的步骤(大约 500)。
我所看到的出乎意料的好:

是的,的确,许多面孔仍然是你在梦里不喜欢遇到的样子,但许多面孔与 Instagram 数据集功能完美融合。



作者提供的图片
这种消失的个性的风格和主题,人工智能驱动的讲故事,以及人类驱动的解释,这种人机合作为美学和叙事带来了新的视角。
使用潜在空间浏览器,你可以找到最有趣的面孔:

RunwayML,作者图片
这里——动态:
实验三:你记忆中的物体
在面孔让我相信人工智能的创造力之后,我想在抽象艺术(实验 1 )和具体的面孔(实验 2 )之间找到一条中间道路。我选择了另一个数据集,针对不同的对象进行了预训练。
已经走了 500 步之后,我得到了各种各样的主题(即使汽车和其他交通工具应该是数据集中最常见的对象)。

作者提供的图片
这种模式的缺点是完成尺寸(512x512),但优点是图像的多样性。
提示:使用各种人工智能方法,你可以将图像放大到你需要的尺寸。
特别是在这里,你需要潜在的浏览器,以避免多余的汽车图像,寻找中间的珍珠和宝石(不是反对车辆,只是多样性是重要的):

作者提供的图片
这里有一些例子:



作者提供的图片
作为一个短的视觉和音乐装置(音乐是由人工智能模型自动点唱机产生的)
(视觉效果由我制作,音乐由 AI 模型点唱机创作)
结论
当然,在预先训练好的模型上进行训练并不是什么新鲜事。研究人员和艺术家已经用它进行了几十年的试验(即使机器学习的“几十年”意味着 1-2 年,因为发展正在迅速演变)。
但是仍然有太多的东西需要去发现,去重新诠释,去微调。毕竟,这与技术无关;这是关于我们的想象力。(这仍然可以通过技术来增强)。
是关于人机协作的。

作者提供的图片
混淆矩阵的可视化指南
入门
二元和多类混淆矩阵解释

(图片由作者提供)
我们已经建立了一个机器学习模型来分类病人是否感染了某种病毒。一个病人可能处于两种状态:他们要么有病毒,要么没有。

我们把病人描绘成一个圆圈。填了表示他们有病毒。未填充意味着它们没有。(图片由作者提供)
对于任何给定的患者,我们的模型产生 0 到 1 之间的分数。1 分意味着模型预测患者确实携带病毒,0 分意味着预测患者没有携带病毒。

预测分数用正方形表示。填的越多,分数越高。(图片由作者提供)
如果我们选择 100 名患者,其中一部分患者携带病毒,然后我们让模型为每个患者生成一个分数,我们如何评估模型的性能?

(图片由作者提供)
有许多不同的方法可以评估模型的分数与实际值的匹配程度。在这个例子中,我们将看一下混淆矩阵,其中我们使用一个网格来直观地评估结果。
阈值
为了确定这些分数是指对病毒的正面预测还是负面预测,我们需要决定在哪里设定阈值。我们可以通过选择一个值来做到这一点,其中大于该值的所有分数都被认为是正面预测,而小于该值的所有分数都是负面预测。

所有高于阈值(红线)的分数都被认为是病毒阳性。以下都被认为是负面的。(图片由作者提供)
我们放置这个阈值的位置决定了我们的模型对这两个类的偏向程度。低阈值导致偏向正输出,高阈值则相反。选择正确的阈值取决于您的模型的目标是什么以及如何使用它。不过现在我们将简单地选择 0.5 作为阈值。

(图片由作者提供)
比较实际与预测
既然我们已经将阈值应用于每个输出得分,我们可以将我们的输出与实际患者进行比较。

(图片由作者提供)
当我们比较结果时,出现了四个不同的组。

(图片由作者提供)
- 真阳性(TP): 预测类别为病毒阳性,实际类别为阳性。
- 假阳性(FP): 模型预测为阳性,实际类为阴性。
- 假阴性(FN): 预测类为阴性,实际类为阳性。
- 真阴性(TN): 病毒的预测类别为阴性,实际类别也为阴性。
第一个字是布尔型的真或假,当预测值和实际值匹配时为真,否则为假。第二个词指的是类:在这种情况下,它可以是正的,也可以是负的。一个表现良好的模型主要会有真实的正面和真实的负面,我们可以在一个混淆矩阵中看到它们。
混淆矩阵
通过对四个类别中的每一个进行计数,我们可以在一个 2×2 的网格中显示结果。y 轴是实际值(患者及其阳性或阴性标签), x 轴是我们的预测值。这个网格的每个象限指的是四个类别中的一个,因此通过计算一个象限的结果,我们可以将值放在该单元格中。

每个象限代表四种可能性中的一种。(图片由作者提供)
用单元格值占该行中所有单元格的比例来给每个象限着色是很有帮助的。由于真正的正面和真正的负面是沿着从左上到右下的对角线,如果这条对角线被突出显示,我们可以假设模型运行良好。

(图片由作者提供)
如果模型表现不佳,很容易确定哪里出了问题,因为对角线上的一个单元格只会被稍微填充,而该行上的另一个单元格会被突出显示。对于上面的错误模型,假阳性的数量比真阴性的数量高得多,所以我们的模型过度预测了阳性类别(该模型预测患者有病毒,而他们没有)。
我们可以返回模型,并根据这些信息进行更改。也许我们改变我们训练它的方式,或者模型本身的架构。这也可能是将阈值更改为更高值的好时机,这样输出会偏向负类。像这样的二元类混淆矩阵可以帮助我们在建立分类模型时指导决策。
多类混淆矩阵
事业成功后,我们从医学界退休,并决定从事野生动物摄影这一爱好。为了帮助组织我们拍摄的照片,我们建立了一个分类器模型来标记照片中的动物。如何使用混淆矩阵来评估多类问题?

像以前一样,我们仍然需要确定输出分数已经预测了哪个类。我们将选择概率最高的输出,并考虑模型预测的类别,而不是执行阈值处理。

突出显示的单元格是照片的最高预测分数。(图片由作者提供)
混淆矩阵的建立方式和以前类似,只是现在多了两个类。总体思路也是一样的:对于每个预测,我们将结果与我们正在评估的标记数据进行比较。如果我们的模型预测到大象并且图像确实包含一只大象,那就是正确的分类!然后,我们可以将结果添加到相关的单元格中。当我们的模型出错时也是如此,比如当预测的类是猴子但照片实际上包含了狮子。
在将每个预测类与相关联的标签进行比较之后,混淆矩阵开始看起来类似于二元类的情况。此处的对角线是模型精确执行的位置,相对于同一行中的其他单元格,这些单元格将包含较高的值。

(图片由作者提供)
此外,我们仍然可以识别所有真阳性、假阳性、真阴性和假阴性相对于任何类的位置。假设我们对我们的模型在类 fish 上的表现感兴趣:

(图片由作者提供)
- 真阳性(TP): 实际类是鱼时对鱼的一种分类。
- 假阳性(FP): 鱼类的一个分类,但实际上是其他 3 类中的一类。
- 假阴性(FN): 不是鱼的一个分类,但实际上是鱼。
- 真否定(TN): 鱼以外的东西的一个分类,实际的类是鱼以外的东西。(预测类和实际类不必匹配,因为我们只关心 fish 类)。
当分类不正确时,我们可以直观地看到模型错误的确切位置。这里我们可以看到我们的模型将狮子归类为猴子:

(图片由作者提供)
希望这种模型不要部署在任何高风险环境中!
想支持更多这样的内容?使用此链接https://mlee-articles.medium.com/membership注册 Medium 的会员资格,您的一部分会员资格将用于未来内容的开发
折扣给齐柏林飞船上的基因组数据分析带来了火花
下一级 k-mer 分析教程

齐柏林飞艇上的折扣 k-mer 分析。(图片由作者提供。)
在生物信息学中,k-mers 是长度为 k、的序列片段,通常是 20 到 50 之间的一个小数字。K-mer 分析是许多其他分析的重要组成部分,包括宏基因组分类、基因组组装和序列比对。Jupyter 和 Zeppelin 等交互式笔记本正在成为数据科学家的重要工具,提供了相当大的功能和灵活性,我们可以混合和匹配语言、可视化和代码片段,与其他用户合作,以及版本控制更改。然而,直到现在,由于爆炸式的数据大小和高内存和 CPU 要求,k-mer 数据在这种笔记本电脑中一直难以处理。今年,我们发布了 Discount ,(分布式计数)一个在 Apache Spark 上推动 k-mer 分析艺术的工具。Discount 从根本上加快了数据分析和操作,如计数、排序和搜索 k-mers,从泛基因组数据到高度复杂的宏基因组数据集。通过在 Discount 和 Spark 的基础上构建 Zeppelin 笔记本,我们现在可以将这种能力用于交互使用,使笔记本中的 k-mer 分析变得微不足道。

在大肠杆菌短读数据集中计数 k-mers (k=28)。(图片由作者提供。)
在本文中,我将详细介绍如何在带有大肠杆菌数据的 Zeppelin 笔记本中使用 Discount。我们将在独立的机器上安装软件,但数据分析也可以在云中更快地进行,例如在 AWS EMR 上。
设置火花和齐柏林飞艇
首先,我们需要下载 Apache Zeppelin 版本 0.10.0(或更高版本)。有两个下载可用。较小的一个(zeppelin-netinst-0.10.0.tgz)将足以满足我们的目的。
Zeppelin 包含一个内置的 Spark 解释器,但是这里我们需要一个更新的版本。因此,我们还需要从 Spark 下载页面下载一个单独的 Spark 发行版。对于本文,我们使用的是 Spark 3.1。
最后,我们需要下载折扣 k-mer 分析工具。它可以从我们的 GitHub 库的版本页面获得。Discount 2.0.1 zip 包含 Discount jar(我们将把它放在 Spark 类路径中)以及一些必要的数据文件。我们提取这三个下载并从 Zeppelin 目录中启动 Zeppelin:
./bin/zeppelin-daemon.sh start
默认的 Zeppelin 端口(可以在conf目录中更改)是 8080,所以假设一切顺利,我们应该可以在 http://127.0.0.1:8080 上加载 Zeppelin 笔记本界面。
获取大肠杆菌数据
对于我们的例子,我们将从大肠杆菌菌株中寻找下一代测序(NGS)短序列。这是一个相对适中的数据集,可以快速分析。但是,您可以随意对您喜欢的任何数据尝试这些方法。登录号为 ERR022075 的数据集可以从NCBI 测序读取档案下载。由于 Discount 目前只接受 fasta 和 fastq 格式的文件,您可能需要使用 sratools 将下载的文件转换成 fastq 文件。
设置和数据加载
我们将使用的折扣演示笔记本的最新版本可从以下网址获得:https://raw . githubusercontent . com/jtnystrom/Discount/master/notebooks/Discount % 20 demo . zpln。这是一个 JSON 格式的导出笔记本。从 Zeppelin 的起始页,点击导入注释链接将允许您粘贴并导入该 URL。接下来,我们需要配置一些基本设置。
齐柏林笔记本分为段。运行一个段落将执行里面的代码片段并显示结果。

在做任何事情之前,编辑 spark.jars 和 SPARK_HOME,如果您还没有在其他地方配置这个设置的话。段落可以通过按 shift+enter,或者点击右上角的“播放”按钮来运行。(图片由作者提供。)
编辑完“Spark settings”段落后,按 shift+enter 来“运行”该段落,让 Zeppelin 接受这些新设置。(如果您稍后再次更改这些设置,或者遇到问题,您可能需要重新启动 Spark 解释器,这可以从右上角的齿轮菜单中完成。)接下来,我们运行 Imports 段落,使一些折扣类可见。从这一点开始,这本笔记本中的代码大部分是用 Scala 语言编写的,但是如果你以前没有见过 Scala,请不要惊慌——它将非常简单,并且在这本笔记本中看起来很像 Python。
在“数据加载”中,我们将编辑一些与数据加载和缓存相关的设置。首先我们改变discountRoot和input。前者应该指向提取折扣包的位置。后者应该指向输入数据文件,在本例中是 ERR022075.fastq,我们在上面下载了它。

数据加载段落定义了本笔记本中其余段落使用的主要数据集。必须在运行之前配置 discountRoot 和 input。(图片由作者提供。)
我们还将把val lowerBound = Some(2L)改为val lowerBound = None,因为我们希望看到所有的 k-mers,而不仅仅是出现至少两次的 k-mers。(2L 是 Scala 对长整数的语法。)我们还可以在这里做一些小的改动,比如把 k 的值从默认值 28 改变为 28。
这一段是声明和缓存几个数据集,例如val kmers = discount.kmers(input).cache。在本笔记本中,counts、buckets、kmers等数据集在本数据加载段配置一次,然后被下游段重复使用。缓存将使 Spark 在计算一次后存储数据供以后使用,而不是每次都重新计算。这些数据集代表输入数据的三种不同视图:分别是计数的 k-mer、k-mer 桶和未计数的 k-mer。一旦我们运行了这一段,所有后续分析的数据环境就已经配置好了。要在以后进行更改,您可以随时编辑并重新运行此段落。
当我们运行这一段时,Discount 将抽取文件的一小部分。这可能需要几分钟时间。

采样完成后,您将看到类似于(但可能不完全相同)如下的输出。Discount 对 10 mer 频率进行了随机采样,以获得最佳数据分布。(图片由作者提供。)
当数据加载完成时,我们将看到一些输出,这表明我们已经为下一步做好准备。
数据集摘要

此数据集中 k-mers 的统计概述。(图片由作者提供。)
此时,我们已经准备好分析数据。统计摘要段落可能是我们要查看的第一件事,因为它告诉我们数据集中有多少 k-mers,它们的平均丰度(它们被看到的次数),有多少不同的 k-mers,以及其他概述指标。我们第一次运行这一段可能需要几分钟,因为 Spark 将需要生成一次缓存数据集,但后续运行会更快。这些结果显示,k-mers 的总丰度约为 39 亿,平均丰度为 5.9。
K-mer 直方图
一个给定的数据集通常会被排序到某个深度,这意味着有意义的 k-mers 应该在某种程度上均匀地分布在平均值周围。丰度低的 K-mers 被认为是噪音。如果我们简单地按原样运行 k-mer 计数直方图段落,我们将看到看起来像一个空图,但实际上是一个高度倾斜的直方图。

k-mer 直方图。z.show()调用显示 Zeppelin 的表格/交互式图表小部件。该直方图看起来是空的,但实际上在计数= 1 时在左侧有一个显著的峰值。(图片由作者提供。)
在这一段中,读者可能会发现以z.show开头的一行。这就是允许 Zeppelin 显示从 Spark 提取的数据的魔力。z 是一个引用 Zeppelin 的特殊对象,show告诉它以多种方式显示一些数据。默认情况下,我们得到一个显示表格的可视化显示,但也允许我们显示各种类型的图,或者保存一个 JSON/CSV 文件。
通过手动检查表格模式中的数据,我们形成了假设,即 35 是一个有意义的临界值。于是我们把这一段稍微改一下说:z.show(histogram.filter($"value" > 35).limit(800))。这将从直方图中删除计数低于 35 的 k-mers。表达式$"value" > 35在 Spark SQL 中定义了一个条件,告诉它过滤名为value的列。最后,limit(800)在这里告诉 Spark,我们也不关心丰度非常高的 k-mers;我们只希望看到直方图中的前 800 行。

k-mer 丰度截止值为 36 的过滤 k-mer 直方图。在此范围内,k-mer 计数似乎围绕一个平均值均匀分布。(图片由作者提供。)
这张图片与最初的直方图非常不同,似乎证实了数据集的行为符合预期。在这一点上,我们可以执行进一步的过滤,当我们满意时,将选择的子集写入文件。
查询感兴趣的 k-mers
有时,我们可能希望检查特定感兴趣区域的 k-mer 计数。大肠杆菌基因组中的一个编码区是基因精氨酸脱羧酶。编码序列可在欧洲核苷酸档案馆(ENA)找到。如果一切都做对了,我们应该能够在数据中找到这个序列。我可以简单地从编码序列中复制前两行,并将它们插入到主题段落中的 Find k-mers 中(注意将它们连接成一行):
val query = List(“ATGAAAGTATTAATTGTTGAAAGCGAGTTTCTCCATCAAGACACCTGGGTCGGTAACGCCGTTGAGCGTCTGGCAGATGCTTTAAGCCAGCAAAATGTTACCGTGATTAAATCCACCTCC”)
当我们运行该段落时,该序列将被分解成 k-mers,并将找到数据中的任何匹配。我们可以看到,数据中确实存在丰度相对较高的匹配。和以前一样,如果我们愿意,可以将结果保存到文件中。这一段使用了与前面相同的z.show()语句,但是结果现在以表格模式显示。

K-mer 计数精氨酸脱羧酶基因的匹配部分。正如注释掉的代码所示,也可以从提供的 fasta 文件中的序列进行查询。(图片由作者提供。)
更进一步
演示笔记本包含许多其他可以探索的例子,在不同的段落中注释了一些有用的变化。Discount 最新版本的 API 文档作为参考会很有用,有助于理解这些例子是如何工作的,以及看看还有什么是可能的。
不熟悉 Scala 的人可能会对单独的 k-mer counts 段落中的这段代码感到疑惑:z.show(counts.filter(_ > 5).withSequences).``_字符允许我们快速创建一个 lambda 函数,所以我们也可以编写:filter(x => x > 5),它具有相同的含义。

大肠杆菌数据集的堆积 k-mer 丰度直方图,k=28、35 和 45。该图是通过使用 Spark SQL 将不同的 k-mer 直方图表连接在一起而生成的。(图片由作者提供。)
通过多次加载相同的数据,我们还可以比较多个 k 值的结果,如上图所示。这最后一个例子不是演示笔记本的一部分,而是留给读者作为练习。
敬未来
Spark 为云环境带来了强大的抽象和类似 SQL 的查询语言(以及 Python、R、Scala 等通用语言),让我们——大多数时候——可以将一个可能有数百台机器的集群看作一个实体。在写这篇文章的时候,我是在一台机器上运行实验,但是我也可以在云中以同样的方式运行,这样会更快。通过突破性的新 k-mer 分布算法,Discount 首次允许我们以真正实用的方式将 Spark 应用于 k-mer 数据。通过在此基础上添加 Zeppelin 笔记本,我们能够以交互方式探索和询问基因组的各种子集和基因组读取,比传统命令行工具允许的自由和灵活性更大,即使是在非常大的数据集上。随着泛基因组学和宏基因组学增加了对数据分析的需求,由 Discount 等技术支持的交互式笔记本将把我们从笨重的管道中解放出来,并改变生物信息学家的日常工作体验。
机器人视觉伺服
计算机视觉与机器人
使用摄像机传感器的路径规划
介绍
这个项目的目的是使用一个鱼眼镜头和一个带 ROS 的 Turtlubot3 实现一个端到端的视觉伺服项目。视觉伺服意味着,机器人将只使用一个传感器,即摄像头,实现自动驾驶!
- 第一个目标是将机器人从当前位置移动到目标位置。为了指定这些位置和它们的姿态,我们使用两个 Aruco 标记。
- 机器人的第二个任务是避开用红色指定的障碍物,我们用 A-star 路径搜索算法实现了这个任务。
- 最后,当机器人在目标位置时,我们必须“停车”,这意味着目标的位置姿态应该与机器人的姿态相同。
在这个报告之后,我们将分析数学理论背景和我们实现的代码实现。

位置之间的角度,作者的图像
ROS 是什么?
机器人操作系统(ROS)是一个开源中间件,它包含一组用于促进机器人应用程序开发的库、软件和工具。有太多的功能,从传感器驱动器到最先进的算法。作为中间件,它包含软件和硬件的特性,因此,它能够执行各种动作,如硬件抽象和低级控制。到目前为止,不同版本的 ROS 存在一些重要的差异,所以出于兼容性的原因,我们使用 Melodic release。
用于此场景的机器人——turtle bot 3
在这个项目中,使用的移动机器人是一个 Turtlebot3 汉堡。Turtlebot3 是一个紧凑、模块化和可编程的移动机器人。它使用 ROS,并且能够为培训研究和开发创建多个应用程序。

工作流程,按作者分类的图像
第一个目标—从当前位置移动到目标位置
1。摄像机校准
摄像机校准是这个项目不可或缺的一部分。在这一阶段,该项目使用 camera_calibration 包,它允许使用棋盘校准目标轻松校准单目摄像机。这些包使用 OpenCV 库,其中包含摄像机校准方法。

棋盘,作者图片
内在校准
正如我们前面提到的,它使用 camera_calibration 包。该套件可轻松校准单目或立体摄像机。棋盘是为了修复采集图像的 径向畸变 的工具。 径向或桶形失真 可以表示为:

x 失真,图片由作者提供

y 变形,图片作者
以同样的方式,因为成像镜头没有完全平行于成像平面对准,所以出现切向失真。因此,一些图像看起来比预期的要近。切向变形量可表示如下:

x 失真,图片由作者提供

y 变形,图片作者
根据上面的等式,我们可以找到五个参数,称为失真系数

失真系数,图片由作者提供
此外,内在参数允许相机坐标和图像帧中的像素坐标之间的映射。它们包括像局部长度(fx,fy)和光学中心(Cx,Cy)这样的信息。
这些参数可以用摄像机矩阵表示:

相机矩阵,图片由作者提供
2。接收图像
下一步是通过订阅 ROS 主题“/camera/image_raw”接收图像帧,并将其转换为 NumPy 数组。然后我们需要根据我们的需要裁剪图像,通过指定我们需要工作的窗口。然后,我们使用摄像机矩阵和上一步接收到的失真系数对接收到的图像进行去失真处理。
3。检测标记
使用 OpenCV 库,我们可以检测放置在机器人顶部的两个 Aruco 标记,一个标记用于当前位置,一个标记用于目标位置。我们只需调用函数“cv2.detectMarkers ”,从该函数中我们可以接收每个标记的角,然后我们可以继续进行姿态估计。
4。姿态估计
下一步是估计当前和目标姿态,简单地调用姿态估计模块中的函数cv2 . estimateposesinglemarkers。从中我们接收每个标记的两个向量,一个平移向量[x,y,z]和一个旋转向量[x,y,z]。使用 ROS publisher,我们将这些向量发送给机器人控制器,后者负责以机器人应该能够移动的方式翻译这些矩阵。这是通过 ROS publisher 实现的
其中 current_position 是控制器将订阅的 ROS 主题,以获取我们创建的自定义消息Pose _ estimation _ vectors,以便发送这些向量。
该消息的结构如下:
几何 _msgs/Vector3 旋转
几何 _msgs/Vector3 平移
这是两种姿势的图像:

初始姿势,图片作者
5。控制器——将旋转和平移矩阵转换成齐次矩阵
齐次变换由外部参数 R 和 t 编码,并表示从世界坐标系 w 到摄像机坐标系 c 的基的变化。因此,给定点 P 在世界坐标 Pw 中的表示,我们通过下式获得 P 在摄像机坐标系 Pc 中的表示:

积分矩阵,作者图片
这种齐次变换由 3 乘 3 旋转矩阵 R 和 3 乘 1 平移向量 t 组成:

同质矩阵,作者图片
结合射影变换和齐次变换,我们获得了将世界坐标中的 3D 点映射到图像平面和归一化相机坐标中的 2D 点的射影变换:

同质矩阵,作者图片
控制器模块必须通过 OpenCV 函数使用 Rodrigues 变换将旋转矢量转换成旋转矩阵:
rotational_matrix, _ = cv2.Rodrigues(np.array([data.rotational.x, data.rotational.y, data.rotational.z], dtype=np.float32))
然后,我们将旋转矩阵与变换向量水平堆叠,并在末尾添加行[0,0,0,1]以获得齐次矩阵。
6。计算当前位置和目标位置之间的α角和距离ρ
我们从上一步获得的齐次矩阵描述了每个位置相对于摄像机框架的位置。我们需要将它们组合起来,以便从一个位置接收相对于另一个位置的位置。为此,我们将当前齐次矩阵的逆矩阵与目标齐次矩阵相乘,以获得组合的齐次矩阵(t):
t = np.matmul(np.linalg.inv(self.curr_homogeneous_matrix), self.target_homogeneous_matrix)

组合齐次矩阵,作者图片
接下来,我们需要计算机器人应该移动的角度(α)和距离(ρ):
我们从上面的矩阵中得到 dx , dy :
dx = t[0][3]
dy = t[1][3]
然后我们用 dx , dy 计算反正切:
self.alpha = math.atan2(dy, dx)
最后,我们通过应用欧几里德距离得到到目标的距离(rho ):
self.rho = math.sqrt(math.pow(dy, 2) + math.pow(dx, 2))
7。固定角度并将机器人移动到目标
现在,我们通过向 ROS 主题“cmd_vel”发布仅角速度来确定β角,并且当根据目标角度角度角度正确时,我们再次向同一主题发布仅线速度,直到到目标的距离接近于零。我们使用比例控制器,所以给机器人的速度乘以两个常数,一个是角速度,一个是线速度。
第二个目标——避开障碍
1.障碍物检测
障碍检测模块正在使用输入图像,并将其切割成与机器人大小相同的方块。这样,我们就有了一个数组,里面有机器人能完成的所有可能的动作。为了区分障碍物,这意味着如果图像中有一个障碍物——一个红色的盒子——机器人就不能移动到那里。
然后,我们对图像的每个框进行迭代,并将该框转换为 HSV。接下来,如果该框包含红色范围内的任何像素,我们将对图像进行遮罩,并对输出应用按位遮罩。如果盒子包含红色像素,我们假设它是一个障碍。
这一步的输出是一个长度等于盒子数量的单向数组,它包含 0(没有障碍物时)和 1(有障碍物时)。

障碍地图,作者提供的图像
2.使用 A-star 算法寻找最短路径
使用上一步的障碍地图阵列,我们实现了路径规划模块,以获得机器人应该移动的最短路径,从而更快地进入目标。
这是一个基于图的算法,使用启发式方法来获得更好的性能。其核心是 f = g + h,其中:
- f 是总成本
- g 是当前节点和起始节点之间的距离。
- h 是启发式的,即从当前节点到结束节点的估计距离。
阅读下面的文章了解更多信息。
3.估计中点姿态
最短路径包含一些中间点,机器人应该在不与障碍物发生任何碰撞的情况下更快地找到目标位置。
这里,我们面临一个问题,因为我们只有这些中间点的索引。因此,我们决定根据我们的框式框架将这些索引转换成像素。使用它们的角,我们计算它们的姿态,就像我们使用函数cv2.aruco.estimatePoseSingleMarkers计算 aruco 标记的姿态一样。

中点姿势,作者图片
4.绘制最短路径
然后,我们查看障碍地图,并在图像上画出我们在前面步骤中计算的最短路径的每个中点。
这是最终的地图,其中蓝色的零表示机器人有有效的移动,浅蓝色的 X 表示有障碍,橙色的圆圈表示最短的路径,粗体白色的 S 和 G 分别表示路径的起点和目标点。

最终地图,图片由作者提供
5.在每个中间点上移动
每次执行on_receive_image回调时,我们的控制器都会接收当前的姿态向量。如前所述,它计算齐次矩阵,并将其保存为类变量self.current_homogeneous_matrix。
对于每个中间点,控制器接收相同的向量并更新列表target_position_path类变量。当这个列表没有值时,机器人不会移动,因为它不知道去哪里。当接收到一些值时,它计算这个特定中点的齐次矩阵,并将其保存为类变量self.target_homogeneous_matrix。
通过固定角度,然后移动到中点,机器人现在可以在target_position_path列表中指定的每个中点上移动。当机器人接近中点并且距离误差很小时,我们得到列表的下一个元素(中点),并继续移动直到列表为空!
第三个目标——停车
1.计算β欧拉角
在控制器move_robot上,有两个循环用于执行前面的步骤(固定角度,向前移动)。出于停车的目的,我们需要添加另一个循环,该循环仅在机器人位于目标位置时执行,该循环的目的是固定相对于指定目标姿态的最终角度。
我们需要额外代码很简单:
rotational_matrix = np.array([
[t[0][0], t[0][1], t[0][2]],
[t[1][0], t[1][1], t[1][2]],
[t[2][0], t[2][1], t[2][2]],
])
r = R.from_matrix(rotational_matrix)
self.beta = r.as_euler('XYZ', degrees=False)[2]
我们将旋转矩阵转换为欧拉角,我们接收一个向量[x,y,z]并得到第三个值,因为我们只需要 z 角,我们将它保存为类变量 beta。
2.移动机器人以修正角度误差
最后,我们公布所需的角速度,机器人确定β角!
演示
执行 Roscore:
roscore
启动摄像机
roslaunch ueye_cam rgb8.launch
远程监控,了解更多详情点击此处:
ssh ubuntu@192.168.0.200
关于 turtlebot 运行,更多详情请点击此处:
roslaunch turtlebot3_bringup turtlebot3_robot.launch
在远程计算机上启动我们的实现:
roslaunch visual_servoing visual_servoing.launch


浙公网安备 33010602011771号