TowardsDataScience-博客中文翻译-2022-二十-
TowardsDataScience 博客中文翻译 2022(二十)
Elasticsearch Beats 研讨会#4
原文:https://towardsdatascience.com/elasticsearch-beats-workshop-4-609bf10ce3e7
Metricbeat 与 Filebeat

欢迎来到 Beats 工作坊的第四部分。这里可以找到第一家作坊。像往常一样,为了使每篇文章尽可能简洁,我将代码简化为片段。如果你想看完整的代码,请查阅我的 GitHub 页面。如果谷歌把你带到这里,你可能还会检查系列的其他部分。
今天,我们来看看可能是最流行的节拍:Metricbeat 和 Filebeat。两者都是 Beats 家族的成员,都是开源和社区项目,都是用 Go 编写的。
这次研讨会的技术性将不如其他研讨会。技术方面的东西,比如安装 Metric-和 Filebeat,将完全放在 GitHub repo 中。我们将仔细看看这些节拍的预期目的,以及它们如何最好地用于你的项目。
这是游戏计划:
- 我将在一小段中描述每个节拍
- 我将描述测试场景和两个 Beats 需要达到的目标
- 我们将看看在每一拍中需要多少努力才能达到目标
- 我们将看看不同的节拍分配了多少系统资源
- 我们将经历每一拍的正面和负面
- 在结论中,我将描述,什么时候顶用哪个节拍
话虽如此,我们还是开始吧!
关于度量的一些话
此时,我需要澄清 Metricbeat 中的度量是什么。指标是从 Metricbeat 到 Elasticsearch 以固定间隔报告的值。这些值可以是各种类型的,从长整型到布尔型,甚至是字符串。重要的是要知道 Metricbeat 不是由一个事件触发的,而是由一个定义的间隔触发的—例如,每 5 秒。即使在创建指标时没有发生任何事情,Metricbeat 也会发送一份报告。在这种情况下,度量将是一个空字符串、零、假或任何定义为“什么都没有发生”的值。如果发生了一些事情,例如,CPU 1 以 75%的负载运行,那么 CPU 1 的指标可能是 0.75。
Metricbeat 的简要描述
Metricbeat 自带许多模块,适用于最常用的应用。Nginx、Mysql、MongoDB、Prometheus 和许多其他软件在基本安装中都有一个模块,可以轻松配置。然而:如果您的应用程序或您的度量标准不是基本安装的一部分,您需要额外的努力。最好的情况是,在 Metricbeat GitHub repo 中找到所需的模块,然后设置开发环境,并用新模块编译 Metricbeat。如何做到这一点在我的第二次研讨会中有所描述。但是在大多数情况下,你可能需要编写你的模块和矩阵集。为此,你至少需要对围棋有一个基本的了解,你甚至可能会雇佣一名围棋开发者,或者让一家专业公司为你开发这个模块。
关于日志的几句话
此时,我需要澄清一下 Filebeat 的日志是什么:它是一个由事件提供的文件。事件可以是应用程序崩溃、用户登录或其他足以写入记录的重要事件。当然,守护程序可以定期发送应用程序的统计数据(这将与指标的定义相匹配),并每 5 秒钟将测量结果记录到日志文件中。但是如果这个守护进程停止了,并且没有向日志文件发送任何数据,那么对于 Filebeat 来说,没有事件发生,因此不会向 Elasticsearch 发送任何数据。或者简而言之,Filebeat 并不关心记录是以间隔存储还是完全任意存储:只要有新记录出现,它就会拾取它们。
日志记录通常带有时间戳。虽然没有时间戳的日志条目几乎没有意义,但是即使没有时间戳,Filebeat 也会发送拾取的记录。每条记录都有一个自动添加的字段@timestamp,它表示 Filebeat 选择记录时的时间戳。
Filebeat 的简要描述
Filebeat,顾名思义,是一个跟踪日志文件并将新记录发送到 Elasticsearch 集群或 Logstash 的 beat。如果 Filebeat 停止了——可能是因为升级——那么只要 Filebeat 再次运行,它就会从最后一个位置继续跟踪日志文件。内存机制保证了随时重启的能力,而不会丢失数据或发送副本。这使得 Filebeat 成为跟踪和分析日志的一个非常健壮的工具。
Filebeat 8.1.0 自带 71 个模块。它附带了用于本地存储日志(如 Log、Syslog、filestream 和 journald)的模块,但也支持网络协议和端点(如 TCP、UDP、Kafka、MQTT 和 Google Workspace)。
Filebeat 具有非常灵活的给定模块。它可以配置为任何类型的输入,基于文件或通过网络地址。添加新字段可以通过在配置文件中定义它们来完成。但它不止于此:Filebeat 配备了许多处理器。在将数据发送到 Elasticsearch 之前,可以使用它们来转换数据。Logstash 中的许多处理器都在 Filebeat 中实现,减少了 Logstash 或 Elasticsearch 集群上所需的处理。
最后但同样重要的是:如果需要,Filebeat 还配有手刹。它可以防止您的摄取管道过载。背压敏感协议与 Elasticsearch 或 Logstash 对话,并在需要时减慢 Filebeat。
测试场景
在我们的简单场景中,我们监控文件 filebeat.log 的修改时间。目标是每 10 秒发送一个包含 filebeat.log 的最新修改日期的报告。该报告如下所示:

一个示例文档——按作者排序的图像
使用 Metricbeat 向 Elasticsearch 发送指标
作为 Metricbeat 模块,我将使用我们在 workshop 3 中创建的模块。它有一点额外的计算,但没有什么会有任何不同。
使用 Filebeat 向 Elasticsearch 发送指标
由于 Filebeat 从日志中读取事件,因此该任务需要两个步骤:一个简单的 shell 脚本——称为 Filebeat _ logger . sh——每隔 10 秒从 filebeat.log 中读取修改时间,并将其写入自身。以及跟踪日志文件并将事件发送给 Elasticsearch 的 Filebeat 实例。
Filebeat 的简单配置
我将使用 Filebeat 的一个非常基本的配置:
filebeat.inputs:
- type: log
enabled: true
paths:
- /logs/filebeat.log
exclude_lines: ["^\"\""]
processors:
- decode_csv_fields:
fields:
message: decoded.csv
separator: ","
- extract_array:
field: decoded.csv
mappings:
timestamp: 0
file_name: 1
mod_time: 2
- drop_fields:
fields: ["decoded"]
性能比较
我们来看看 Filebeat 的内存和 CPU 消耗:

超过 10 分钟的 Filebeat CPU 和实际内存使用情况——图片由作者提供
Filebeat 使用 120MB 内存。CPU 仅在非常短的峰值中使用。10%的峰值占大多数,也有 3 个峰值达到 20% —在 10 分钟内。

filebeat_logger.sh 超过 10 分钟的 CPU 和实际内存使用情况——图片由作者提供
记录器需要大约 3MB 的内存,并且这里那里有一些奇怪的尖峰。我不清楚为什么,但我也没有进一步研究它。sh 是一个 Bash 脚本,出于性能原因,我不建议使用 Bash 来编写守护进程。对于这个例子,这无关紧要,因为这只是一个测试。
Metricbeat 只需要 60MB 内存,是 Filebeat 所需内存的一半。有趣的是,它和 Filebeat 有相同的 CPU 峰值。可以说,Metricbeat 消耗的 CPU 和 Filebeat 一样多。

超过 10 分钟的 CPU 和实际内存使用度量——图片由作者提供
Filebeat 的优缺点
Filebeat 的巨大优势是它的灵活性:几乎没有限制。如果对磁盘的 I/O 是一个问题,Filebeat 可以从网络地址读取。有了处理器,Filebeat 提供了一个非常强大的工具来转换数据,并在摄取之前丰富它们。通过对背压敏感的协议,Filebeat 实现了一种安全机制,可以防止集群过载。而且 Filebeat 不会丢失一条日志记录,即使 Filebeat 暂时没有运行。
最后但同样重要的一点是:如果出现问题,您可以在本地保存日志文件。这可能是一种祝福,也可能是一种诅咒。如果您的 Elasticsearch 集群无法访问,您可以使用 grep 和其他工具来分析您的日志。另一方面,您需要管理您的日志。
然而:Filebeat 是事件驱动的。如果 logger 守护进程停止了,没有新的数据进来,Filebeat 不会在意。记录器守护进程将增加 CPU 和内存消耗。如果资源很少或者 logger 守护进程触发了大量的请求,那么您可能希望用 Go、C++或者至少 Perl 或 Python 编写守护进程,而不是 Bash。
Metricbeat 的优缺点
Metricbeat 的优势似乎很明显:它只需要 Filebeat 一半的内存。Metricbeat 将每 10 秒向 Elasticsearch 发送一份报告,即使什么也没发生。有了 Go 中的知识,就可以创建内存占用非常少并且几乎有无限可能的托运人。
缺点是 Metricbeat 模块和 metricsets 高度专门化,具有特定的字段和数据类型。您可能能够创建某种通用模块,但是灵活性不是 Metricbeat 的优势,并且在某一点上,您将被迫为其他类型的指标创建另一个模块或 metricset 这将带来一个价格标签,它肯定比使用将数据发送到 Filebeat 或 Logstash 的自行编写的守护程序进行测量要高。
结论
只要您对 Metricbeat 8.1.2 提供的 73 个模块满意,您就拥有了一个监视最流行的应用程序的好工具。到目前为止,我遇到的模块配置起来非常简单。通常,每个模块都有 Kibana 仪表板。
如果您的物联网设备内存和存储有限,应该运行数量严格受限的进程,并且指标是预先定义的,那么定制指标集可能是正确的选择。
另一方面,Filebeat 与 logger 守护程序相结合,为您提供了很大的灵活性,并且能够非常容易地扩展您的监控框架。Filebeat 仍然是一个事件驱动的工具,但是您的记录器确保事件基于记录的指标。这在不断出现新的监控需求的动态环境中非常有用。
所以最后,这真的取决于,你的重点在哪里。灵活性与性能。在一个交付时间至关重要的世界里,当谈到标准安装之外的指标时,我在大多数情况下会选择 Filebeat。
最初发布于https://cdax . ch。
弹性搜索:关键概念介绍
原文:https://towardsdatascience.com/elasticsearch-introduction-to-key-concepts-928816046440
开始使用 Elasticsearch for NLP 的 5 个基本步骤
作者pawemielniczuk和 丹尼尔·波佩克 。

这篇文章背后的野心
在我们在 NeuroSYS 工作期间,我们处理了自然语言处理中的各种问题,包括信息检索。我们主要关注基于变形金刚的深度学习模型。然而, Elasticsearch 经常为我们提供一个很好的基线。我们一直在广泛使用这个搜索引擎;因此,我们希望与您分享我们的发现。
但是如果你能直接找到 Elasticsearch 文档,为什么还要读这个呢?不要误解我们,文档是我们每天依赖的极好的信息来源。然而,正如文档所做的那样,它们需要是彻底的,并且包括关于该工具已经提供了什么的每一点信息。
相反,我们将更加关注 NLP 和elastic search的实际方面。我们还决定将这篇文章分成两部分:
- 介绍部分
—解释主要概念,
—指出我们认为最重要的东西,
—识别可能导致错误或不当使用的不明显的东西。 - 实验部分
—提供现成的代码,
—提出一些优化技巧,
—展示不同策略使用的结果。
即使你对后者更感兴趣,我们仍然强烈建议你阅读介绍。
在下面的五个步骤中,我们揭示了我们发现对开始尝试搜索结果质量改进最重要的是什么。
注:本文使用的 Elasticsearch 服务器是开源的,可以在 Elasticsearch Github 上获得。在第二篇文章中,我们将提供 GitHub 链接到我们的 dockerized 环境,以便于使用。但是,如果您不希望自己设置一切,云中有现成的设置,包括安全性、监控或复制等功能。所有定价选项均可在这里获得。
第一步:理解什么是 Elasticsearch,什么是搜索引擎
Elasticsearch 是一个搜索引擎,数百万人使用它来迅速找到查询结果。弹性有许多用途;然而,我们将主要关注自然语言处理中对我们最重要的方面——所谓的全文搜索的功能。
注意:本文主要关注 Elasticsearch 的第七个版本,在撰写本文时,一个更新的版本 8 已经发布了,它提供了一些额外的特性。
数据库与弹性搜索
但是等等,一个常用的数据库不就是为了快速存储和搜索信息而设计的吗?我们真的需要 Elastic 或其他搜索引擎吗?是也不是。数据库非常适合快速和频繁的插入、更新或删除,不像数据仓库或弹性搜索。

数据存储互不相同。作者图片
对,没错,说到没完没了的插入,Elasticsearch 并不是一个好的选择。通常建议将弹性视为"一旦建立,就不要再修改。“这主要是由于倒排索引的工作方式——它们是为搜索而优化的,而不是为修改而优化的。
此外,数据库和 Elastic 在搜索用例上有所不同。让我们用一个例子来更好地说明;想象一下,你有一个图书馆,藏书很多。每本书可以有许多与之相关的属性,例如,标题、文本、作者、ISBN(唯一图书标识符)等。这些都必须存储在某个地方,很可能是某种数据库中。

标准数据库中的图书表示示例。作者图片
当试图在查询中查找给定作者的特定书籍时,这种搜索可能会很快。如果在这个字段上创建一个数据库索引,可能会更快。然后,它以排序的方式保存在磁盘上,这大大加快了查找过程。
但是,如果您想要查找包含某个文本片段的所有书籍,该怎么办呢?在数据库中,我们可能会把 SQL 看做语句,可能还带有一些通配符 % 。
很快,进一步的问题出现了:
- 如果您想根据文本与您查询的内容的紧密程度对行进行排序,该怎么办呢?
- 如果您有多个字段,例如标题和文本,您希望将它们包含在您的搜索中,该怎么办?
- 如果您不想搜索整个短语,但是将查询分成单独的单词,并且只接受包含其中一些单词的匹配结果,该怎么办?
- 如果您想拒绝语言中经常出现的单词并只考虑查询的相关部分,该怎么办?
您可能会看到,当使用类似 SQL 的查询和标准数据库时,处理更复杂的搜索是多么困难。这就是搜索引擎的确切用例。
简而言之,如果你想通过 ISBN、书名、或作者进行搜索,那就去用数据库吧。然而,如果你打算基于长文本中的段落来搜索文档,同时关注单词的相关性,搜索引擎,尤其是 Elasticsearch,将是更好的选择。
Elasticsearch 设法通过多种不同的查询类型来处理匹配的查询和文档文本,我们将进一步展开讨论。然而,它最重要的特性是一个倒排索引,它是基于来自经过标记化和预处理的原始文本的术语创建的。

Elasticsearch 中的图书表示示例。作者图片
倒排索引可以被认为是一本字典:我们查找某个单词并得到匹配的描述。所以,这里它基本上是从单个单词到整个文档的映射。
给定一本书的前一个例子,我们将通过从一本书的内容中提取关键字或最能描述它的关键字来创建一个反向索引,并将它们映射为一个集合/向量,从现在开始它将代表这本书。
所以通常情况下,当查询时,我们必须遍历数据库的每一行并检查一些条件。相反,我们可以将查询分解成一个标记化的表示(一个标记向量),并且只将这个向量与数据库中已经存储的标记向量进行比较。由于这一点,我们还可以轻松地实现一个评分机制来衡量所有对象与该查询的相关程度。
作为旁注,还值得补充的是,每个 Elasticsearch 集群包含许多索引,这些索引又包含许多碎片,也称为 Apache Lucene 索引。在实践中,它一次使用几个这样的碎片来划分数据子集,以加快查询速度。

弹性搜索的组成部分。作者图片
第二步:了解何时不使用 Elasticsearch
Elasticsearch 是一个很棒的工具;然而,就像许多工具一样,如果使用不当,可能会导致与实际解决的问题一样多的问题。我们希望你从这篇文章中领会到的是, Elasticsearch 不是一个数据库,而是一个搜索引擎,并且应该被如此对待。意思是,不要把它当成你唯一拥有的数据存储。原因有很多,但我们认为最重要的是:
- 搜索引擎应该只关心他们实际用于搜索的数据。
- 使用搜索引擎时,应该避免频繁更新和插入。
Re 1)
不要用你不打算用来搜索的东西污染搜索引擎。我们知道数据库如何增长,模式如何随着时间变化。新数据的加入导致更复杂的结构形成。Elasticsearch 用了也不会好;因此,建立一个单独的数据库,将一些额外的信息链接到您的搜索结果可能是一个好主意。除此之外,额外的数据也可能影响搜索结果,你会在 BM25 一节中发现。
Re 2)
创建和修改倒排索引的成本很高。elastic search 中的新条目强制改变倒排索引。Elastic 的创建者也考虑到了这一点,并不是每次更新时都重建整个索引(例如每秒 10 次),而是创建一个单独的小 Lucene 索引(Elastic 建立在较低级别的机制上)。然后将它与主索引合并(重新索引操作)。默认情况下,该过程每秒发生次,但也需要一些时间来完成重新索引。处理更多的副本和分片会花费更多的时间。

搜索结果如何与附加数据链接。作者图片
任何额外的数据都会导致该过程花费更长的时间。由于这个原因,你应该只在你的索引中保存重要的搜索数据。此外,不要期望数据立即可用,因为 Elastic 不符合 ACID 标准,因为它更像是一个主要关注 BASE 属性的 NoSQL 数据存储库。
第三步:理解评分机制
Okapi BM25
存储在索引中的术语影响评分机制。 BM25 是 Elasticsearch 中默认的评分/关联算法,是 TF-IDF 的继承者。我们不会在这里过多地钻研数学,因为这会占用整篇文章的篇幅。然而,我们将挑选出最重要的部分,并试图让你对它的工作原理有一个基本的了解。

BM25 配方。图片来源 Elasticsearch 文档
这个等式一开始可能有点混乱,但是当单独看每个组件时,它变得非常直观。
- 第一个功能是 IDF(qi) —如果你对 IDF(逆文档频率)比较熟悉,这个功能你可能很熟悉。 qi 代表来自查询的每个术语。它本质上做的是通过计算它们总共出现的次数来惩罚在所有文档中更频繁出现的术语。我们宁愿只考虑查询中最具描述性的词,而丢弃其他的词。
例如:

只选择文档表示的描述性词语。作者图片
如果我们把这个句子符号化,我们会期望像 Elasticsearch,search,engine,query这样的词比 更有价值,因为后者对这个句子的本质贡献较少。**
- 另一个相关因素是函数 f( qi ,D) 或术语 qi 在文档 D 中的出现频率,对其进行评分。直觉上,特定文档中查询词的频率越高,该文档就越相关。
- 最后但并非最不重要的是field len/avgFieldLen比率。它计算给定文档的长度与存储的所有文档的平均长度的比较。因为它被放在分母中,我们可以观察到分数会随着文档长度的增加而减少,反之亦然。所以,如果你经历的短结果比长结果多,那只是因为这个因素促进了短文本。
第四步:理解文本预处理的机制
分析器
当考虑优化时,你需要提出的第一个问题可能是:如何在你的倒排索引中预处理和表示文本。Elasticsearch 中有很多现成的概念,取自自然语言处理。它们被封装在所谓的分析器中,分析器将连续的文本转换成单独的术语,这些术语被编入索引。在“外行人的术语”中,分析器既是一个记号赋予器,它把文本分成记号(术语),又是一个过滤器集合,它做额外的处理。
我们可以使用 Elastic 提供的内置分析器或定义自己的分析器。为了创建一个自定义的,我们应该确定我们想要使用哪个标记器,并提供一组过滤器。
我们可以将三种可能的分析器类型应用于给定的字段,这些类型根据它们处理文本的方式和时间而有所不同:
- 索引分析器 —用于文档索引阶段,
- 搜索分析器 —用于在搜索过程中映射查询术语,以便可以将它们与字段中的索引术语进行比较。注意:如果我们没有明确定义搜索分析器,默认情况下,将使用该字段的索引分析器
- 搜索引用分析器 —用于全短语的严格搜索
通常,应用不同于索引分析器的搜索分析器是没有意义的。此外,如果您想自己测试它们,可以通过内置 API 或直接从库中选择语言来轻松完成。
内置分析仪应能涵盖步进期间最常用的操作。如果需要,你可以使用专门为特定语言设计的分析器,叫做语言分析器。
过滤
不管名字如何,过滤器不仅执行标记选择,还负责大量常见的 NLP 预处理任务。它们还可用于多种操作,例如:
- 词干,
- 停用字词过滤,
- 下/上壳体,
- 在字符或单词上创建 n 元语法。
但是,它们不能执行词汇化。下面,我们列出了一些最常见的。然而,如果你对可用过滤器的完整列表感兴趣,你可以在这里找到它。
- 带状疱疹 —创建 n 个单词,
- n-gram —创建 n-gram 字符,
- 停用词 —删除停用词,
- 词干分析器(波特/洛文斯)——根据波特/洛文斯算法执行词干分析,
- remove_duplicate —删除重复的令牌。
标记化者
他们的目标是根据选定的策略将文本划分为标记,例如:
- standard_tokenizer —删除标点符号并根据单词边界断开文本,
- letter_tokenizer —在每个非字母字符上断开文本,
- whitespace_tokenizer —在任何空白上断开文本,
- pattern_tokenizer —在指定的分隔符上断开文本,如分号或逗号。
在下面的图表中,我们展示了一些示例性的分析器及其对句子“汤姆·汉克斯是个好演员,因为他热爱表演。”

不同的分析器应用于同一个句子。作者图片
每个记号赋予器都有不同的操作方式,所以选择最适合您的数据的一个。然而,标准分析器通常很适合许多情况。
步骤 5:理解不同类型的查询
问题
Elasticsearch 支持各种不同的查询类型。我们可以做出的基本区分是我们是否关心相关性分数。考虑到这一点,我们有两个上下文可供选择:
- 查询上下文— 计算分数,文档是否匹配查询,以及匹配程度,
- 过滤上下文— 不计算得分,它只识别是否匹配查询的文档。
因此,使用一个查询上下文来判断文档与查询的匹配程度和一个过滤上下文来过滤掉不匹配的文档,这些文档在计算分数时不会被考虑。
布尔查询
尽管我们已经声明我们将主要关注文本查询,但至少理解 Bool 查询的基础是很重要的,因为匹配查询归结于它们。最重要的方面是我们决定使用的运算符。当创建查询时,我们通常喜欢使用逻辑表达式,如 AND、OR、NOR 。它们在 Elasticsearch DSL(特定领域语言)中分别作为 必须、应该、和必须 _ 不、** 可用。使用它们,我们可以很容易地描述所需的逻辑关系。
全文查询
这些是我们最感兴趣的,因为它们非常适合包含应用了分析器的文本的字段。值得注意的是,当在搜索期间查询每个字段时,查询文本也将由用于索引字段的同一分析器处理。
有几种类型的 FTS 查询:
- 区间 —使用匹配术语的规则,并允许它们的排序。该查询擅长的是邻近搜索。我们可以定义一个区间(从查询名称开始),在这个区间中我们可以查找一些术语。这很有用,尤其是当我们知道搜索的术语不一定会同时出现,但可能会以预定的距离出现。或者相反,我们希望它们靠得很近。
- 匹配——FTS 的首选,子类型有:
- match_phrase —设计用于“精确短语”和单词邻近匹配,
- multi_match —允许以首选方式查询多个字段的匹配类型。
- combined_fields —此类型允许查询多个字段,并将其视为组合字段。例如:当查询名字和姓氏时,我们可能想要配对。
- query_string —较低级别的查询语法。它允许使用 AND、or、NOT 等操作符创建复杂的查询,以及多个字段查询或多个附加操作符,如通配符操作符。
- simple_query_string —它是 query_string 的高级包装器,对最终用户更加友好。
我们现在将重点更详细地解释基于匹配的查询,因为我们发现它们足够通用,可以做我们需要的任何事情,同时编写和修改速度也相当快。
匹配查询 —这是全文搜索的标准,其中每个查询都以与其匹配的字段相同的方式进行分析。我们发现以下参数是最重要的参数:
- 模糊性— 当搜索一些短语时,用户可能会打错字。模糊性使人能够通过同时搜索相似的单词来快速处理这种拼写错误。定义了每个单词可接受的错误率,解释为 Levenstein 编辑距离。模糊度是一个可选参数,可以取值为 0、1、2 或 AUTO。我们建议将参数设为 AUTO ,因为它会根据单词的长度自动调整每个单词的错误数量。对于 2、3–5 和超过 5 个字符的单词长度,误差距离分别为 0、1 和 2。如果你决定在一个领域中使用同义词,模糊性就不能再用了。
- 操作符 —如上所述,基于分析的搜索文本构建布尔查询。此参数定义将使用哪个运算符 AND 或 or,默认为 or。例如,or 运算符的文本“超级比特币挖矿”构造为“超级或比特币或挖矿”,而对于 AND,则构造为“超级和比特币及挖矿”
- Minimum_should_match —这定义了布尔查询中有多少项应该被匹配以使文档被接受。它非常通用,因为它接受整数、百分比,甚至它们的组合。
匹配短语查询 —它是匹配查询的变体,其中所有术语都必须出现在查询字段中,以相同的顺序,彼此相邻。当使用删除停用词的分析器时,可以稍微修改一下序列。
匹配前缀查询 —它将查询中的最后一个术语转换为前缀术语,前缀术语充当后跟通配符的术语。此查询有两种类型:
- 匹配布尔前缀 —从术语中构建布尔查询,
- 匹配短语前缀 —术语被视为短语;它们需要按照特定的顺序排列。

匹配短语前缀查询示例。作者图片
当使用 匹配短语前缀 查询时,比特币挖矿 c 将与两个文档“比特币挖矿中心”以及“比特币挖矿集群”匹配,因为前两个单词构成了短语,而最后一个单词被认为是前缀。
组合字段查询 —允许在多个字段中进行搜索,如同它们被组合成一个字段。清晰是组合字段查询的一个巨大优势,因为当创建这种类型的查询时,它被转换为布尔查询,并使用选择的逻辑运算符。然而,对于组合字段查询有一个重要的假设;所有被查询的字段需要相同的分析器。
这个查询的缺点是增加了搜索时间,因为它必须动态地组合字段。这就是为什么在索引文档时使用 copy_to 可能更明智。


左边:在索引期间使用 copy_to 时执行查询。右边:当 Elastic 需要动态组合字段时执行查询。作者图片
Copy_to 允许创建单独的字段,将其他字段的数据组合起来。这意味着在搜索过程中没有额外的开销。
多匹配查询 —它不同于组合字段,因为它允许查询应用了不同分析器甚至不同类型的多个字段。最重要的参数是查询的类型:
- best_fields —默认值,它计算每个指定字段的分数。当我们希望答案只出现在一个给定的字段中,而不是出现在多个字段中时,这很有用。
- most_fields — 不同字段中可以找到相同文本时的最佳选择。在这些字段中可能会使用不同的分析器,其中一个可以使用词干和同义词,而第二个可以使用 n 元语法,最后一个是原始文本。相关性分数合并所有字段的分数,然后除以每个字段中的匹配数。
注意:best_fields 和 most_fields 被视为以字段为中心,这意味着查询中的匹配是按字段而不是按术语应用的。例如,使用操作符 AND 查询 "Search Engine" 意味着所有术语必须出现在一个字段中,这可能不是我们的意图。
- cross_fields —被认为是以术语为中心的,当我们期望在多个字段中找到答案到时,这是一个很好的选择。例如,当查询名字和姓氏时,我们会期望在不同的字段中找到它们。相比于最和最佳字段,其中术语必须在相同字段中找到,这里,所有术语必须在至少一个字段中。关于 cross_fields 更酷的一点是,它可以用同一个分析器将字段分组,而不是按组计算分数。更多细节可以在官方文档中找到。****
助推
我们还想强调的是查询可以被提升。我们每天都广泛地使用这个特性。

查询字段提升示例。作者图片
该查询会将字段标题的分数乘以 2 倍,将作者的分数乘以4 倍,而描述的分数将保持不变。Boost 可以是整数,也可以是浮点数;但是,它必须大于或等于 1.0 。
结论
总而言之,我们提出了五个我们认为对开始使用 Elastic 至关重要的步骤。我们已经讨论了什么是 Elasticsearch,什么不是,以及如何使用和不使用它。我们还描述了评分机制和各种类型的查询和分析器。
我们相信,本文中收集的知识对于开始优化您的搜索结果至关重要。本文旨在介绍一些关键概念,但也是下一篇文章的基础,在下一篇文章中,我们将为您提供值得尝试的示例,并将共享代码。
我们希望这篇博文能让你对不同搜索机制的工作原理有所了解。我们希望你已经学到了一些新的或有用的东西,有一天你可能会发现这些东西对你的项目有用。
第 2 部分—即将推出。
Elasticsearch Python 研讨会#1
原文:https://towardsdatascience.com/elasticsearch-python-workshop-1-741fa8b9aaf1
基础知识

戴维·克洛德在 Unsplash 上的照片
欢迎来到 Elasticsearch Python 研讨会的第一部分。本系列将从 Python 程序员的角度来关注这个问题,我希望我能对这个小型生态系统有所了解,这个生态系统是用 Python 客户端和工具从 Elasticsearch 集群中获取额外数据而构建起来的。特别是当你是一个数据科学家时,这个系列可能会节省你一些时间。像所有的研讨会一样:为了保持文章简洁,我把代码剪成了片段。但是你可以从 GitHub repo 为这个工作坊下载完整的代码。话虽如此,让我们直入主题吧。
我在 Elasticsearch 迁移 6.22 到 7.8.0 (抱歉,这篇文章只有德语版)中使用了 python 库 Python Elasticsearch 客户端用于 reindex 任务。为了节省您的时间,我们从安装和一些基本的配置设置开始。这可能会更方便,因为 Elasticsearch 8.0.0 增强了安全性——在没有任何安全性的情况下连接到本地主机是默认不启用的。但是首先,让我们安装 Elasticsearch 客户端。下面的例子是使用 Ubuntu 18.04 LTS。
安装库
使用 pip3 安装最新的 Elasticsearch 客户端:
sudo apt update && sudo apt upgrade
sudo apt install python3-pip
sudo python3 -m pip install 'elasticsearch>=7.0.0,<8.0.0'
sudo python3 -m pip install elasticsearch_dsl
创建您的第一个连接
让我们从简单开始,假设您的集群是 pre-Elasticsearch=8.0.0,并且您没有实现安全性。如果没有将节点暴露给网络,请连接到“localhost”而不是主机名。
es = Elasticsearch(["[http://srvelk:9200](http://srvelk:9200)"])
es.cat.nodes()
就是这样。没有安全措施很简单,对吧?好了,让我们看看在启用安全性时需要做些什么。
创建启用安全性的连接
为了通过安全集群建立连接,我们需要再安装一个 pip 模块(可能已经安装了,只要确保您已经安装了):
sudo python3 -m pip install urllib3
您可能还没有证书,但您至少需要一个用户名/密码。您可以像这样设置连接:
from ssl import create_default_context
from elasticsearch import Elasticsearches = Elasticsearch(["[https://username:password@srvelk:9200](https://username:password@srvelk:9200)"], verify_certs=False)
es.cat.nodes()
这将是可行的,但会产生一些令人不快的警告。我们最好使用证书,所以如果您没有证书,让我们创建一个 pem-certificate。
生成 pem 证书
如果您使用默认设置安装了 Elasticsearch 8,您可能不知道集群的 SSL-Keystore 密码。但是温和地说,Elastic 提供了直接从密钥库中读取密码的工具,但是您需要 root 权限:
/usr/share/elasticsearch/bin/elasticsearch-keystore \
show xpack.security.http.ssl.keystore.secure_password
这是将证书添加到密钥库中所需的“导入密码”。我们现在使用 OpenSSL 创建证书 python_es_client.pem:
openssl pkcs12 -in /etc/elasticsearch/certs/http.p12 \
-cacerts -out /etc/elasticsearch/certs/python_es_client.pem
出现提示时,输入密码。选择您选择的 PEM 密码。之后,将 pem 文件复制到 python 脚本可以访问的位置。将您的证书存储在/tmp 中并不是一个好的选择,这只是出于演示的目的。
chmod 666 /etc/elasticsearch/certs/python_es_client.pem
cp /etc/elasticsearch/certs/python_es_client.pem /tmp
让我们用 Python 创建另一个连接:
from ssl import create_default_context
from elasticsearch import Elasticsearchcontext = create_default_context(cafile='/tmp/python_es_client.pem')
es = Elasticsearch(["[https://username:password@srvelk:9200](https://username:password@srvelk:9200)"], ssl_context=context)
es.cat.nodes()
最好不要在脚本中包含您的用户名和密码。我们需要的是一个 API 密钥,并在没有凭据的情况下连接到集群。
使用 API 密钥连接
我们需要首先创建一个角色。转到堆栈管理->角色并创建一个角色。该角色必须至少包含“管理 api 密钥”权限

作者图片
现在向用户添加或创建一个角色。转到堆栈管理->用户并添加角色:

作者图片
现在生成 API 密钥。转到堆栈管理-> API 密钥,并为需要通过 API 密钥访问的用户创建 API 密钥。请在创建后将 API 密钥保存在安全的地方:

作者图片
让我们用 Python 创建另一个连接:
from ssl import create_default_context
from elasticsearch import Elasticsearchapi_key='aC1wNUYzOEJCWV...RSjJMaEhvbDMyWElvZw=='
context = create_default_context(cafile='/tmp/python_es_client.pem')
es = Elasticsearch(["[https://srvelk:9200](https://srvelk:9200)"], ssl_context=context, api_key=api_key)
es.cat.nodes()
这个看起来更好🙂
结论
如果你成功了:祝贺你!现在,您应该能够创建与 Python Elasticsearch 客户端的连接了。如有问题,请查阅官方文档或给我留言。你也可以和我联系或者关注链接。
如果谷歌把你带到这里,你可能还会检查这个系列的其他部分
原发布于https://cdax . ch。
Elasticsearch Python 研讨会#2
原文:https://towardsdatascience.com/elasticsearch-python-workshop-2-37447f8d2845
API、类以及如何调用它们

戴维·克洛德在 Unsplash 上的照片
欢迎来到 Elasticsearch Python 研讨会的第二部分。本系列将从 Python 程序员的角度来关注这个问题,我希望我能对这个小型生态系统有所了解,这个生态系统是用 Python 客户端和工具从 Elasticsearch 集群中获取额外数据而构建起来的。特别是当你是一个数据科学家时,这个系列可能会节省你一些时间。像所有的研讨会一样:为了保持文章简洁,我把代码剪成了片段。但是你可以从 GitHub repo 下载这个工作坊的完整代码。话虽如此,我们还是开始吧。
让我们创建一个简单的连接。如果您使用的是 Elasticsearch 版本 8.0.0 及以上,请检查首次研讨会以创建安全连接。
from elasticsearch import Elasticsearch
es = Elasticsearch(["[https://username:password@srvelk:9200](https://username:password@srvelk:9200)"], verify_certs=False)
弹性搜索类
因为我们的连接是用 Elasticsearch 类创建的,所以最基本的 API 调用可以作为方法直接从这个类中获得。这些方法直接列在模块弹性搜索下:

作者图片
创建索引和文档
让我们用“create”方法创建一个索引和一个文档。来自官方文件:

弹性搜索图片
这个调用看起来像这样:
response = es.create(index='testidx', id=1, document='{"item": "content"}')
让我们看看 API 调用的响应:
print(json.dumps(response, indent=4))
{
"_index": "testidx",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
我们可以测试状态:
if response['result'] == 'created':
print("document created")
得到一份文件
让我们使用 get 方法来读取文档:
response = es.get(index='testidx', id=1)
print(json.dumps(response, indent=4))
{
"_index": "testidx",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"item": "content"
}
}
这与你在 curl 或 Kibana 中得到的结果是一样的:

作者图片
搜索文档
搜索 API 的调用如下所示:
es.search(index="testidx", body={
"query": {
"match_all": {}
}
})
使用 CatClient 类(和任何其他类)
当你看一看官方文档时,你会发现这个图书馆有丰富的课程。然而,对于初学者来说,如何调用它们可能会有点困惑。
例如,CatClient(或弹性术语中的“_ cat”API)的调用是:

作者图片

您可以导入整个 elasticsearch 库,并使用现有的“es”对象(您已经建立的连接)作为参数。然后调用方法,在本例中是 node()方法:
>>> elasticsearch.client.cat.CatClient(es).nodes()
'192.168.1.68 44 85 1 0.07 0.04 0.01 cdfhilmrstw * srvelk8\n'
看起来很奇怪,对吧?还有一个更好的方法:导入类“elasticsearch。Elasticsearch”包括 elasticsearch-library 的所有类作为对象。让我们看看在该类的 dict 属性中发现了什么:
print(es.__dict__['cat'])
<elasticsearch.client.cat.CatClient object at 0x7ff874e24160>
如果用参数“cat”调用 Elasticsearch 类,基本上就是调用 CatClient 类。这样,您可以用相当短的语法调用 CatClient 类的 nodes 方法。
>>> es.cat.nodes()
'192.168.1.68 44 85 1 0.49 0.22 0.08 cdfhilmrstw * srvelk\n'
代表弹性搜索 API 的弹性搜索属性表
以下是作为对象存储在 dict 中的属性(左)和类(右)的汇编:

作者图片
结论
如果你成功了,恭喜你!现在,您应该能够使用 Python Elasticsearch 客户端的所有类,因此也能够使用最常用的 Elasticsearch APIs。如有问题,请查阅官方文档或给我留言。你也可以和我联系或者关注链接。
如果谷歌把你带到这里,你可能也会检查系列的其他部分。下一部分再见!
原发布于https://cdax . ch。
弹性研究研讨会#6 —编写脚本第 4 部分
原文:https://towardsdatascience.com/elasticsearch-workshop-6-scripting-part-4-53ba577eaff4
正则表达式和模式匹配

欢迎来到研讨会的第 6 部分。像往常一样,为了使每篇文章尽可能紧凑,我将把查询简化为片段。如果你想看完整的代码,请查阅我的 GitHub 页面。如果谷歌把你带到这里,你可能还会检查系列的开头,或者整个系列的。
在这个研讨会中,我将向您展示一些正则表达式的实例以及使用它们的不同方法。官方文档中有几个例子,但是我认为还有更多的内容,我们应该深入研究。如果你想了解模式标志(比如不区分大小写的匹配),在官方文档中有介绍。一如既往:让我们直接开始吧!
准备
在我们继续之前,您需要确保您的集群可以运行正则表达式。如果以下查询返回 limited 或 false,您将无法正常运行 workshop:
GET _cluster/settings?include_defaults&filter_path=defaults.script.painless.regex
您可以通过在集群中的每个节点上添加以下参数 elasticsearch.yml 来启用 regex。群集需要重新启动:
script.painless.regex.enabled: true
数据
像往常一样,我们将使用一个文档作为一个虚构的小软件公司的数据:
PUT companies/_doc/1
{ "ticker_symbol" : "ESTC",
"market_cap" : "8B",
"share_price" : 85.41}
“market_cap”字段读起来很舒服,但不适合计算。使用正则表达式,我们将把这个字段转换成 long 数据类型的值。
正则表达式
无痛中的正则表达式实现与 Java 中的相同。如果你想知道如何指定字符类或者如何定义"/ /"里面的量词,请ĥave 看一下甲骨文文档。
以下示例匹配“Market_cap”字段的“8B ”:
if ( doc['market_cap.keyword'].value =~ /B$/){...}
“=”匹配子字符串,称为查找操作符。当“==”需要匹配整个字符串时,它被称为匹配操作符。如果我们想使用匹配操作符,正则表达式应该是:
if ( doc['market_cap.keyword'].value ==~ /^8B$/){...}
Java 匹配器类
Java Matcher 类提供了模式匹配所需的一切。虽然寻找模式很有效,但我发现对匹配进行分组却不容易。我在所有 7.x.x 和现在的 8.0.0 Elasticsearch 版本中都看到了这个 bug。我可以演示一下——让我们定义一个模式,将字符串“8B”分成两组:第一组是“8”,第二组是“B”。我们首先定义模式:
Pattern p = /([0-9]+)([A-Za-z]+)$/;
现在我们从模式对象(p)调用 matcher 类,想知道模式是否匹配:
def result = p.matcher(market_cap_string).matches();

作者图片
很好,我们已经确认匹配了。我现在尝试 group()方法,因为使用该方法我可以将第一个组存储在一个变量中。让我们看看会发生什么:
def result = p.matcher(market_cap_string).group(1);

作者图片
你可能认为我做错了什么——我也是。我花了几个小时研究这个问题,我甚至向 Elasticsearch 报告了这个问题。到目前为止没有回应——如果你知道这里出了什么问题,请随时通过 LinkedIn 或更新帖子给我发 pm。提前感谢!
但是还是有解决办法的。让我首先向您展示来自同一个类的 replaceAll()方法。为了实现这一点,我们需要匹配子串并修改我们的模式,然后我们不替换“B ”,或者换句话说,我们删除“B ”:
Pattern p = /([A-Za-z]+)$/;
def result = p.matcher(market_cap_string).replaceAll('');

作者图片
现在,我们还可以使用前面的模式,将匹配整个字符串的两个组替换为第一个组,这也从“8B”中删除了“B”:
Pattern p = /([0-9]+)([A-Za-z]+)$/;
def market_cap = p.matcher(market_cap_string).replaceAll('$1');
匹配器类示例
在我的 GitHub 页面上有很多 matcher 类的例子。有管道、存储脚本、脚本化字段等等的例子。卡住了请看看。
Java 字符串包含()方法
还有另一种方法,适用于所有的上下文:使用 contains()方法,这是每个 String 对象都有的。让我们来看看:
if (market_cap_string.contains("B")){
mc_long_as_string = market_cap_string.replace('B', '');
可以看到, String 对象有更多的方法。replace()方法现在将“8B”转换为“8”。它仍然是一个字符串。因此,让我们从 Integer 类调用静态方法 parseInt(),并将“8”转换为 long 数据类型:
if (market_cap_string.contains("B")){
mc_long_as_string = market_cap_string.replace('B', '');
mc_long = (long) Integer.parseInt(mc_long_as_string);
market_cap = mc_long * 1000000000
}

作者图片
现在,我缺少的是一些位置匹配。使用 Java Pattern 类,我能够定义匹配的“B”应该在哪里:在字符串的末尾。使用 contains()时,字符串可以在任何地方,但是 string 类为我提供了一个在行尾进行匹配的简便方法:endsWith()。当然,还有一个方法 startsWith()。但是回到这个例子:
if (market_cap_string.endsWith("B")){...}
搜寻并剖析模式
还有一件事:寻找并剖析模式。如果您熟悉 Logstash,这可能会让您感兴趣。因为只有 grok 使用正则表达式模式(dissection 不使用正则表达式,但是速度更快),所以我将跳过例子中的 dissection,把重点放在 Grok 上。
然而:不幸的是只有运行时映射实现了 grok 和剖析插件。因此,它不像字符串方法或模式/匹配器类那样通用。
让我们看看如何分割字符串“8B”并将“8”和“B”存储在两个变量中:
String mc_long_as_string =
grok('%{NUMBER:markcap}').extract
(doc['market_cap.keyword'].value).markcap;
String factor_as_string =
grok('(?<fact>[A-Z])').extract
(doc['market_cap.keyword'].value).fact;
Grok 建立在语法(模式的名称)和语义(标识符的名称)的基础上,在本例中是 NUMBER ,因为我们想要从字符串中提取数字,在本例中是“ markcap ”。它从文档(或字符串)中提取模式,并将其保存在对象 markcap 中。Grok 和 dissect 将是 Logstash workshop 中的一个主题。
我对未来的希望是,Elasticsearch 在其他环境中也能实现 grok 和剖析模式。但是现在,我们仅限于运行时映射。
结论
如果你成功了:祝贺你!现在,您应该能够用正则表达式、字符串方法或 grok 模式匹配模式了。
如有疑问,请留言、联系或关注我的 LinkedIn 。
原发布于https://cdax . ch。
基于样条聚类的肘部检测
原文:https://towardsdatascience.com/elbow-detection-for-clustering-using-splines-3b26894f9229
用样条函数优化聚类数

要定义智力,我们通常指的是在不同知识或学科之间编织联系的能力。接下来的问题是,大脑将自己从具体事物中抽象出来,以便后退一步,突出连接这些不同元素的共性。
这种能力在人工智能中当然是受追捧的,而这也恰恰是我们这里要讨论的聚类方法的目的。
在机器学习和人工智能提供的方法中,聚类方法是最有趣的方法之一。这些方法属于无监督方法的类别,因此不会遭受偏见或预设,因为它们不寻求学习已知的规则,而是识别未知的链接。因此,它们的吸引力在于它们能够理解那些数量和/或基数超过人类处理能力的数据。
监督和非监督学习
在人工智能领域,有两大类方法:有监督的方法和无监督的方法。
它们通过提交给机器学习的问题的形式来区分。
在监督学习的情况下,我们有一个数据集,其中每个元素,通常是包含几列的一行数据,都是合格的。例如,这些行中的每一行都可以包含蛋白质的物理和化学特征,并通过其空间构型来限定。学习过程然后将识别哪些物理或化学特征允许推断 3D 配置。因此,我们对我们试图预测的性质有上游知识。
相比之下,在无监督学习的情况下,我们不知道集合中每个元素的特定属性。训练的目的是在没有任何先入之见的情况下,自动对相似的元素进行分组。让我们再次以蛋白质分析为例:在处理的最后,具有相似性的蛋白质被分组在一起,而没有提供任何先验知识。
监督方法非常有效,但是需要对数据进行上游鉴定,因此提出了两个问题,这两个问题与标记阶段的人工干预有关:
- 标签类别的选择是人类的决定,因此会导致偏差的引入。
- 人类参与标记阶段对人工智能的标记提出了质疑,因为标记的部分智能工作实际上是手工完成的。我们接着讲人工人工智能 。
聚类的应用
聚类方法有许多用途。当面对高维数据时,不管它们的基数是多少,它们都特别有趣。换句话说,这些方法非常适用于每个元素都有许多属性的数据,不管收集的数据中有多少个元素。
因此,它们经常用于医疗应用,其中与患者相关的样本很少,但具有高度的特征:生命常数、血液学、药理学数据…
仍然是在健康领域,它们是在分析由几十个颅内或颅外传感器记录的脑电图(EEG)信号中发现的。基于这些数据,它们提供了自动检测与不同大脑功能相对应的记录段落的可能性:活跃期、睡眠期、肌肉活动、眼球运动、癫痫发作…
在零售领域,它们的使用允许将具有相似销售行为的产品分组在一起,从而允许开发利用它们的相似性的更精确的销售预测模型。以稍微不同的方式使用,它们也允许捕捉联合销售或自相残杀的影响。
计算过程
聚类方法有很多种,根据以下情况而有所不同:
- 它们沿着非线性边界聚集的能力;
- 它们在计算时间方面的效率;
- 它们在内存消耗方面的效率;
- 他们处理大量数据的能力;
- 他们处理大量维度的能力;
下面的截图摘自著名的 Scikit-learn 库,的文档,展示了这种多样性:

Scikit-learn 库实现集群的例子。开源内容。
尽管种类繁多,但每种方法都遵循下面详述的主要步骤。
特征计算
任何聚类的基本前提是计算属性的最大值。如果我们处理时间数据,它可以是信号的频率、傅立叶级数或小波分解的系数、能量、振幅…
如果我们处理文本数据,就有可能使用给定语料库中单词的频率、它们的长度、它们以向量形式使用的编码,如 Word2Vec…
在此阶段,有必要尽可能地发挥创造力,并使训练集中包含的特征数量最大化,以便为算法提供最大量的材料。许多特征过于有限,或者更糟,受到人类干预的限制,将会破坏集群的所有好处:在人类察觉不到的地方形成一个结构。
粉碎
矛盾的是,一旦我们的集合被尽可能多的特征所充实,聚类方法通常会经历一个减少维数的阶段。也就是说,它们将限制特征的数量,要么通过消除它们,要么通过重新组合它们。
理查德·贝尔曼在 1961 年引入的一个概念从数学上证明了这种减少,这个概念被命名为维度的瘟疫。潜在的观察结果是,工作空间的维度越高,信息密度越低,提取相关统计信息就变得越困难。
您可能会问,既然我们会在之后立即减少功能,为什么要在前一步中增加功能的数量呢?兴趣在于还原方法的客观性,它不像人类那样没有偏见或预设。
在此阶段使用的方法本质上是对一个或多个特征所携带的能量的分析。对此最常用的方法无疑是主成分分析(PCA)。然后排除携带少量能量的特征的组合。
分组
上面的图 1 很好地说明了我们在集群阶段要做的事情。在这个图中,我们面对的是 2D 数据的小例子。
对于肉眼来说,在前四行中可以很好地看到相关的分组。
该图中所示的不同方法本质上使用了几何方法。从预定数量的 n 个聚类开始,他们随机创建 n 个点,这些点在包含点集的空间中有规律地分布。这些 n 点就是我们星团的初始重心。
然后以迭代的方式,在更新聚类中心的同时合并最近的点。
当新的聚类中心没有从旧的聚类中心显著移动时,迭代停止。
这两种方法的区别在于初始化第一个重心的方式,以及可能应用于点的坐标变化。重心的初始化可以例如通过考虑点密度的分布来改进。
特定方法的选择通常由聚类的几何形状、数据集的大小和计算时间决定。
最佳聚类数
正如我们刚刚看到的,有几种方法可以用来识别具有高基数和/或维度的数据中的一致组。
这些方法绝大多数都不能确定最佳的聚类数。为了理解这一点,让我们回到大脑活动分析的案例,尤其是在睡眠期间。
有四个主要阶段:
- N1:清醒-睡眠转换
- N2:浅睡眠
- N3:深度睡眠
- 快速眼动睡眠
如果我们对错误数量的聚类应用我们的聚类方法,聚类将是不相关的。如果簇太少,不同的相将会混淆。如果簇太多,相同的相位会出现在不同的簇中。
这是聚类方法的一个非常严格的限制。幸运的是,存在克服这些问题的方法。我们将提出一个结果良好的方案。
用样条曲线确定最佳聚类数
评估聚类的相关性
首先,需要一个标准来评估一个聚类的有效性。通常使用的标准是解释方差,它表示有多少方差是由聚类解释的。当它为零时,聚类不考虑任何逻辑,而当它为 1 时,它指示数据结构已被完美捕获。
聚类的问题是,我们添加的聚类越多,我们解释的差异就越多,因为如果我们将练习推向极端,我们得到的聚类和点一样多,差异就可以得到充分的解释。然后,我们面临一个过度拟合的情况。这些指标似乎表明分裂很好地解释了数据,但实际上,它只对训练数据有效,不会很好地概括。
为了避免过度拟合,我们通常使用所谓的“肘方法”,即在绘制解释的方差与聚类数的曲线中寻找弯曲。
这样生成的曲线与图 2 中绘制的曲线非常相似。

图 2:解释方差作为集群数量的函数。作者的情节。
从该曲线可以清楚地看出,最佳值大约是 10 个集群。在此之下,聚类很难解释这种差异。除此之外,曲线的斜率明显降低,表明过度学习。
我们将尝试自动确定这个弯曲的位置。
微分几何和弯头
我们在这篇文章中提出的方法是基于微分几何的概念,正如它的名字所示,微分几何是数学的一个分支,将微分学应用于几何。
它方便地提供了一系列工具来表征曲线或曲面。在我们的例子中,我们将使用曲线曲率的计算。
你会注意到我们的曲线的肘部对应于曲率最显著的区域。
曲线的曲率
曲线某一点的曲率定义为该点最大切圆半径的倒数。
对于一条直线,这个半径可以无限大,它永远不会穿过曲线。因此曲率为零。
对于像我们这样的曲线,尤其是在肘部,最大圆的半径仍然很小。半径的倒数,曲率,是很重要的。
图 3 显示了这种曲率:

图 3:曲线两点处的曲率及其相关圆。
然而,实际上,我们并不是通过在一个点上找到最大的切圆来计算曲率的。有一种更直接的方法来获得这个值:通过使用曲线的二阶导数,这在数学上被证明可以给出曲线的曲率。这是我们上面提到的微分几何的贡献之一。
然而,这种方法并不直接适用于我们的数据。我们处理离散数据,每个聚类数都有一个值。
直接计算离散曲线上的二阶导数是可能的,但是会受到数值不稳定性的影响,这通常会导致对曲率的不良估计,从而导致对最佳聚类数的不良估计。
样条函数
为了绕过这一限制,我们将回到我们有一个连续曲线的情况,用一个数学表达式,我们可以得到。
本文选择的可能性是由样条函数提供的。
我们将使用三次样条,这是一个分段定义的曲线,每一段都是 3 阶多项式。由于是三阶,我们可以推导它们两次。
首先,输入数据是致命的噪声,我们将使用 Savitzky-Golay 型滤波器对其进行降噪。该滤波器根据滑动窗口的原理工作,并且通过由阶为 k 的多项式提供的插值来替换该窗口中的值,调整到窗口的点。
对我们示例的数据实施,放大噪声部分,我们得到图 4。

图 4:放大平滑的数据和它们的样条逼近。
我们的曲线平滑得很好,消除了由于离散化造成的假象。
要确定弯曲的位置,没有什么比这更容易的了,因为我们已经对曲线的曲率有了准确的估计。取横坐标上二阶导数,即曲率最大的地方。
图 5 显示我们确实得到了预期的结果,因为对于 10 个集群的数量确实发现了峰值曲率。

图 5:对于等于 10 的集群数,峰值曲率达到很好。
结论
我们以提出确定最佳聚类数的方法为借口,回到聚类的众多应用中来。我们还简要回顾了这些方法的构建方式,以及为给定用例选择最合适的方法时要考虑的标准。
所提出的用于确定最佳尺寸的方法是非常通用的,并且可以容易地应用于其他设置,例如特征选择。
聚类仍然是人工智能应用的核心工具,特别是因为它有能力理解人类可能忽略的地方。如果使用得当,它是一个非常强大的工具,适用于许多情况。
在 K-means 聚类中不要再用肘法了,取而代之,用这个!
了解如何在 K-均值聚类中查找聚类数

图片来源:Unsplash
K-means 聚类是数据科学领域使用最多的聚类算法之一。为了成功实现 K-means 算法,我们需要确定使用 K-means 创建的聚类的数量。
在这篇博客中,我们将探讨——对于你的 K-means 聚类算法来说,找到聚类数(或 K)的最实用的方法是什么。
而肘法不是答案!
以下是我们将在本博客中涉及的主题:
- 什么是 K 均值聚类?
- 什么是肘法及其弊端?
- 如何求 K-means 中‘K’的值?
- Python 实现
- 结论
我们开始吧!
什么是 K-means 聚类?
K-means 聚类是一种基于距离的无监督聚类算法,其中彼此接近的数据点被分组到给定数量的聚类/组中。
以下是 K-means 算法遵循的步骤:
- 初始化‘K’,即要创建的集群数量。
- 随机分配 K 个质心点。
- 将每个数据点分配到其最近的质心以创建 K 个聚类。
- 使用新创建的聚类重新计算质心。
- 重复步骤 3 和 4 ,直到质心固定。
什么是肘法及其弊端?
肘方法是在 K 均值聚类中寻找最佳“K”的图形表示。其工作原理是寻找 WCSS(类内平方和),即类中各点与类质心之间的平方距离之和。
肘形图显示了对应于不同 K 值(在 x 轴上)的 WCSS 值(在 y 轴上)。当我们在图中看到一个弯头形状时,我们选择创建弯头的 K 值。我们可以把这个点叫做肘点。超过拐点后,增加“K”值不会导致 WCSS 显著降低。
肘部曲线预计是这样的😊

预期肘曲线(图片由作者提供)
什么样子!😒

实际肘部曲线(图片由作者提供)
因此,在大多数真实世界的数据集中,使用肘方法识别正确的“K”并不十分清楚。
那么,我们如何在 K-means 中找到“K”呢?
在肘法不显示肘点的情况下,侧影评分是一个非常有用的求 K 数的方法。
剪影得分的值范围从-1 到 1 。以下是剪影配乐解读。
- 1: 点被完美地分配在一个簇中,簇很容易区分。
- 0: 簇是重叠。
- -1: 点被错误分配到簇中。

两个聚类的轮廓分数
剪影得分= (b-a)/max(a,b)
其中,a=平均类内距离,即类内每个点之间的平均距离。
b=平均簇间距离,即所有簇之间的平均距离 。
Python 实现
让我们使用虹膜数据集来比较肘部曲线和****轮廓得分。
使用以下代码可以创建弯头曲线:
#install yellowbrick to vizualize the Elbow curve
!pip install yellowbrick
from sklearn import datasets
from sklearn.cluster import KMeans
from yellowbrick.cluster import KElbowVisualizer
# Load the IRIS dataset
iris = datasets.load_iris()
X = iris.data
y = iris.target
# Instantiate the clustering model and visualizer
km = KMeans(random_state=42)
visualizer = KElbowVisualizer(km, k=(2,10))
visualizer.fit(X) # Fit the data to the visualizer
visualizer.show() # Finalize and render the figure

弯头图在 K=4 处找到弯头点
上图在 K=4 处选择了一个弯头点,但是 K=3 看起来也是一个合理的弯头点。所以,不清楚肘点**应该是什么。让我们使用轮廓图验证 K 的值(使用以下代码)。**
from sklearn import datasets
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from yellowbrick.cluster import SilhouetteVisualizer
# Load the IRIS dataset
iris = datasets.load_iris()
X = iris.data
y = iris.target
fig, ax = plt.subplots(3, 2, figsize=(15,8))
for i in [2, 3, 4, 5]:
'''
Create KMeans instances for different number of clusters
'''
km = KMeans(n_clusters=i, init='k-means++', n_init=10, max_iter=100, random_state=42)
q, mod = divmod(i, 2)
'''
Create SilhouetteVisualizer instance with KMeans instance
Fit the visualizer
'''
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-1][mod])
visualizer.fit(X)

K = 2 到 5 的轮廓图(图片由作者提供)
对于 K = 2,轮廓分数是最大的(0.68),但是这不足以选择最佳的 K。
应检查以下条件****以使用剪影图选择正确的‘K’:
- 对于特定的 K,所有的聚类都应该具有大于数据集的平均分数的轮廓分数(由红色虚线表示)。x 轴代表轮廓分数。K = 4 和 5 的聚类被消除,因为它们不符合这个条件。
- ****星团的大小不应该有大的波动。聚类的宽度代表数据点的数量。对于 K = 2,蓝色簇的宽度几乎是绿色簇的两倍。对于 K = 3,这个蓝色簇被分解成 2 个子簇,从而形成大小一致的簇。
因此,轮廓图方法给出了 K = 3 作为最佳值。
对于 Iris 数据集上的最终聚类,我们应该选择 K = 3。
import plotly.graph_objects as go #for 3D plot
## K-means using k = 3
kmeans = KMeans(n_clusters=3)
kmeans.fit(X)
y_kmeans = kmeans.predict(X)
## 3D plot
Scene = dict(xaxis = dict(title = 'sepal_length -->'),yaxis = dict(title = 'sepal_width--->'),zaxis = dict(title = 'petal_length-->'))
labels = kmeans.labels_
trace = go.Scatter3d(x=X[:, 0], y=X[:, 1], z=X[:, 2], mode='markers',marker=dict(color = labels, size= 10, line=dict(color= 'black',width = 10)))
layout = go.Layout(margin=dict(l=0,r=0),scene = Scene,height = 800,width = 800)
data = [trace]
fig = go.Figure(data = data, layout = layout)
fig.show()

聚类的三维图(图片由作者提供)
我还通过索引/检查输入特征在集群内的分布来验证输出集群。****
结论
肘曲线和剪影图都是为 K-均值聚类寻找最佳 K 的非常有用的技术。在现实世界的数据集中,你会发现很多肘曲线不足以找到正确的“K”的情况。在这种情况下,您应该使用轮廓图来计算数据集的最佳聚类数。
我建议您结合使用这两种技术来计算 K 均值聚类的最佳 K 值。**
谢谢大家!
你可以在收件箱里看到我所有的帖子。 做到这里 ! 如果你喜欢体验媒介的自己,可以考虑通过 报名会员来支持我和其他成千上万的作家。它每个月只需要 5 美元,它极大地支持了我们,作家,你可以在媒体上看到所有精彩的故事。
你可能喜欢的故事!
**https://medium.com/codex/feature-engineering-techniques-every-data-scientist-should-know-e40dc656c71f https://medium.com/codex/24-powerful-must-know-pandas-functions-for-every-data-analysis-a1a9990d47c8 https://medium.com/codex/know-everything-about-bias-and-variance-7c7b9f9ee0ed **
sklearn 管道中使用 NLTK 的优雅文本预处理
使用组件架构快速启动您的 NLP 代码

Max 陈在 Unsplash 上的照片
典型的 NLP 预测管道从摄取文本数据开始。来自不同来源的文本数据具有不同的特征,在对它们应用任何模型之前,需要进行一定量的预处理。
在本文中,我们将首先回顾预处理的原因,并讨论不同类型的预处理。然后,我们将通过 python 代码了解各种文本清理和预处理技术。出于教学目的,本文中提供的所有代码片段都按其对应的类别进行了分组。由于库的独特性,预处理步骤中存在固有的顺序依赖性,所以请不要忘记参考本文中关于建议执行顺序的部分,以避免大量的痛苦和错误。
虽然代码片段可以在 Jupyter 笔记本中执行和测试,但通过使用统一和定义良好的 API 将它们重构为 python 类(或模块),以便在类似生产的 sklearn 管道中轻松使用和重用,可以实现它们的全部好处。本着这种精神,本文以一个 sklearn 转换器作为结尾,它包含了本文中概述的所有文本预处理技术和一个管道调用示例。
为什么要预处理?
NLP 中的所有技术,从简单的单词包到花哨的 BERT,都需要一个共同的东西来表示文本——单词向量。而 BERT 及其同类产品不需要文本预处理,尤其是在使用预训练模型时(例如,它们依赖于单词块算法或其变体,从而消除了对词干提取和词条化等的需要。),更简单的 NLP 模型极大地受益于文本预处理。
从单词向量的角度来看,每个单词都只是一个数字向量。因此,足球这个词不同于橄榄球。所以踢,被踢和踢是完全不同的。在非常大的语料库上的训练理论上可以为足球/橄榄球以及踢/被踢/踢产生类似的向量表示。但是我们可以马上大大缩短词汇,因为我们知道从我们的角度来看,这些不仅仅是相似的单词,而是相同的单词。类似地,助动词(in,was 是 as 等。)连接词(and since)几乎出现在每个句子中,从 NLP 的角度来看,它对句子没有任何意义。砍掉这些将减少句子/文档的向量维数。
文本预处理的类型
下图列出了一长串(但不是全部)非正式的文本处理技术。本文只实现了带虚线边框的绿框中的那些。其中一些不需要介绍,而其他的如实体规范化、依赖解析都是高级的,它们本身就包含某些词法/ML 算法。

图一。文本预处理技术(图片由作者提供)
下表 1 总结了受这些预处理影响的变化。由特定处理引起的更改在“之前”和“之后”列中突出显示。“之后”列中没有突出显示意味着“之前”中突出显示的文本由于预处理而被移除。

表 1。每种文本预处理的影响
亲眼目睹这一切
好了,理论到此为止。让我们写一些代码让它做一些有用的事情。与其一次进行一类预处理,不如按顺序进行某些操作。例如,删除 html 标签(如果有的话)作为第一个预处理步骤,然后是与小写结合的词汇化,然后是其他清理
- 所需库
在 Python 3.5+上,除了 numpy 和 pandas 之外,还应该安装以下模块。
pip install nltk
pip install beautifulsoup4
pip install contractions
pip install Unidecode
pip install textblob
pip install pyspellchecker
2。用熊猫加载数据帧
import pandas as pddf = pd.read_csv(“…..”)
text_col = df[“tweets”] #tweets is text column to pre-process
3。下壳体
text_col = text_col.apply(lambda x: x.lower())
4。扩张收缩
text_col = text_col.apply(
lambda x: " ".join([contractions.fix(expanded_word) for expanded_word in x.split()]))
5。噪声消除
5.1 删除 html 标签
from bs4 import BeautifulSoup
text_col = text_col.apply(
lambda x: BeautifulSoup(x, 'html.parser').get_text())
5.2 删除号码
text_col = text_col.apply(lambda x: re.sub(r'\d+', '', x))
5.3 用空格替换点
有时文本包含 IP,这样的句点应该替换为空格。在这种情况下,请使用下面的代码。然而,如果需要将句子拆分成标记,那么首先使用带有 punkt 的 nltk 标记化。它理解句号的出现并不总是意味着句子的结束(如 Mr. Mrs. e.g .),因此会标记化。
text_col = text_col.apply(lambda x: re.sub("[.]", " ", x))
5.4 删除标点符号
# '!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~' 32 punctuations in python string module
text_col = text_col.apply(lambda x: re.sub('[%s]' % re.escape(string.punctuation), '' , x))
5.5 去掉双空格
text_col = text_col.apply(lambda x: re.sub(' +', ' ', x))
6。替换音调符号(重音字符)
音调符号被替换为最接近的字符。如果找不到兼容的,默认情况下会在它的位置放一个空格。或者,如果指定了 errors="preserve ",则字符将保持原样
from unidecode import unidecode
text_col = text_col.apply(lambda x: unidecode(x, errors="preserve"))
7。错别字纠正
它有助于在应用许多关键步骤之前纠正拼写,如停用词删除、词条化等。为此,我们将使用 textblob 和 pyspellchecker。使用这些就像 123 一样简单
from textblob import TextBlob
text_col = text_col.apply(lambda x: str(TextBlob(x).correct()))
8。移除停用词
我们将使用 NLTK 来移除停用字词。NLTK 停用词必须通过下载功能下载(与 pip 安装无关)。该下载功能在随后的时间内被忽略。这些文件在 Linux 上通常下载到/use/local/share/nltk_data 或/ <user_home>/local/nltk_data 的适当子文件夹中。在 windows 上一般下载到 C:\ Users <user_id>\ AppData \ Roaming \ nltk _ data。下载后,它们也会被解压缩</user_id></user_home>
import nltk
nltk.download("stopwords")
sw_nltk = stopwords.words('english')# stopwords customaization: Add custom stopwords
new_stopwords = ['cowboy']
sw_nltk.extend(new_stopwords)# stopwords customaization: Remove already existing stopwords
sw_nltk.remove('not')text_col = text_col.apply(
lambda x: " ".join([ word for word in x.split() if word not in sw_nltk]) )
您可以看到停用字词资源必须下载一次。每个资源都有一个名称,并存储在子文件夹 base nltk_data 中。例如,特定于语言的停用词作为单独的文件存储在 corpora 子文件夹下。POS tagger 资源存储在 taggers 子文件夹下,依此类推。本质上,每个资源都有一个资源名称和一个用于存储的子文件夹。我们只能用这个代码片段第一次自动下载
def download_if_non_existent(res_path, res_name):
try:
nltk.data.find(res_path)
except LookupError:
print(f'resource {res_name} not found in {res_path}")
print("Downloading now ...')
nltk.download(res_name)download_if_non_existent('corpora/stopwords', 'stopwords')
download_if_non_existent('taggers/averaged_perceptron_tagger', 'averaged_perceptron_tagger')
download_if_non_existent('corpora/wordnet', 'wordnet')
当第一次查找某个资源时,它的缺失会触发一个错误,这个错误会被及时捕获并采取适当的措施。
9。引理化
词干化和词汇化都将 word 转换为其基本形式。词干是一种快速的基于规则的技术,有时会不准确地截断(词干不足和词干过多)。你可能没有注意到 NLTK 提供了 PorterStemmer 和稍微改进的雪球斯特梅尔。
词汇化是基于词典的技术,比词干法更精确,但稍慢。我们将使用 NLTK 的 WordnetLemmatizer。为此,我们将下载 wordnet 资源。
import nltk
nltk.download("wordnet")
from nltk.stem import WordNetLemmatizerlemmatizer = WordNetLemmatizer()
text_col = text_col.apply(lambda x: lemmatizer.lemmatize(x))
上面的代码是有效的。但是有一个小问题。WordnetLemmatizer 假设每个单词都是名词。然而有时词性会改变引理。如果不考虑的话,我们会得到有趣的结果
lemmatizer.lemmatize("leaves") # outputs 'leaf'
“leaves”这个词在词性=名词时变成 leaf,在词性=动词时变成 leave。因此,我们必须为 Lemmatizer 指定词性。下面是我们的做法
from nltk.corpus import wordnet
lemmatizer.lemmatize("leaves", wordnet.VERB) # outputs 'leave'
太好了!但是我们如何获得一个单词在句子中的词性呢?幸运的是,NLTK 在 pos_tag 函数中内置了这个功能
nltk.pos_tag(["leaves"]) # outputs [('leaves', 'NNS')]
NLTK 已经将 POS 输出为上面的名词。在 NLTK 行话中,NNS 代表复数名词。然而,NLTK 足够聪明,可以识别出这个单词在正确的上下文中作为句子的一部分出现时是动词,如下所示。
sentence = "He leaves for England"
pos_list_of_tuples = nltk.pos_tag(nltk.word_tokenize(sentence))
pos_list_of_tuples
上面的代码输出适当的位置,如下所示。
[('He', 'PRP'), ('leaves', 'VBZ'), ('for', 'IN'), ('England', 'NNP')]
现在,我们可以制定一个策略来使 lemmatize。我们首先使用 NLTK pos_tag()函数来识别词性。然后,我们将 POS 作为参数显式提供给 WordnetLemmatizer.lemmatize()函数。听起来不错。但是有一个小问题。
NLTK pos 标签由 2-3 个字母组成。例如,在 NLTK 中,NN、NNS、NNP 分别代表单数、复数和专有名词,而在 WordNet 中,所有这些名词都用一个由变量 nltk.corpus.wordnet.NOUN 指定的总括词性“n”来表示
我们通过创建一个查找字典来解决这个问题。一个查看来自 NLTK 的细粒度 POS 并映射到 Wordnet 的。实际上,查看 NLTK POS 的第一个字符就足以确定 Wordnet 的等价字符。我们的字典查出来的是这样一些东西:
pos_tag_dict = {"J": wordnet.ADJ,
"N": wordnet.NOUN,
"V": wordnet.VERB,
"R": wordnet.ADV}
现在让我们使用查找字典进行词汇化
sentence = "He leaves for England"
pos_list_of_tuples = nltk.pos_tag(nltk.word_tokenize(sentence))new_sentence_words = []
for word_idx, word in enumerate(nltk.word_tokenize(sentence)):
nltk_word_pos = pos_list_of_tuples[word_idx][1]
wordnet_word_pos = tag_dict.get(nltk_word_pos[0].upper(), None)
if wordnet_word_pos is not None:
new_word = lemmatizer.lemmatize(word, wordnet_word_pos)
else:
new_word = lemmatizer.lemmatize(word)
new_sentence_words.append(new_word)new_sentence = " ".join(new_sentence_words)
print(new_sentence)
您会注意到我们的 pos 标签查找字典非常简单。我们根本不提供到许多 POS 标签的映射。在这种情况下,如果键不存在,我们可能会得到一个 KeyError。我们在 dictionary 上使用 get 方法,如果缺少 pos 键,则返回默认值(None)。然后,我们将相应的 wordnet pos 传递给 lemmatize 函数。最终输出将是:
He leave for England tomorrow
那太好了。让我们试试另一个句子,比如“蝙蝠在夜间飞行”。惊喜惊喜!我们得到“蝙蝠正在夜间飞行”。显然,当单词以大写字母开头时,lemmatizer 不会删除复数。如果我们将其小写为“bats”,那么我们将得到“bat”作为 lemmatize 过程的输出。有时需要这种功能。因为我们的例子很简单,所以在词汇化之前,让我们把每个句子都小写。
关于引理化的一个注记
词汇化不是一个强制性的过程。我们可以使用这些经验法则来决定我们是否需要术语化
- TF-IDF、Word2Vec 等简单的单词矢量化技术受益于词汇化。
- 主题建模受益于术语化
- 情感分析有时会受到词条泛化的影响,当然也会因为删除某些停用词而受到影响
- 经验表明,对句子进行词汇化会降低 BERT 等中预先训练的大型语言模型的准确性。
建议的执行顺序
预处理没有固定的顺序。但是对于许多简单的场景,这里有一个建议的执行顺序。这必须在语料库中的每个文档上完成。回想一下,一个语料库由许多文档组成。反过来,每个文档是一个或多个句子的集合。例如,df['tweets']是熊猫数据帧中的一列。每一行 df['tweets']本身可以有很多句子。
- 删除 HTML 标签
- 替换音调符号
- 扩大收缩
- 删除号码
- 错别字纠正
- 复合词汇化过程
- 停用词移除
前&后步骤的复合符号化过程
对每个文档中的每个句子执行以下步骤:
- 删除除句点以外的特殊字符
- 小写字母盘
- 把…按屈折变化形式进行归类
- 删除每个文档中的句点和双空格
类中的完整代码
既然我们已经完整地介绍了所有部分,让我们把它们放在一起,为文本预处理创建一个单独的完整的类。
首先是进口
import string
import re
import contractions
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.corpus import wordnet
from nltk.stem import WordNetLemmatizer
from bs4 import BeautifulSoup
from textblob import TextBlob
from unidecode import unidecode
接下来,是对文本进行词汇化的独立函数
def lemmatize_pos_tagged_text(text, lemmatizer, post_tag_dict):
sentences = nltk.sent_tokenize(text)
new_sentences = []
for sentence in sentences:
sentence = sentence.lower()
new_sentence_words = [] #one pos_tuple for sentence
pos_tuples = nltk.pos_tag(nltk.word_tokenize(sentence))
for word_idx, word in enumerate(nltk.word_tokenize(sentence)):
nltk_word_pos = pos_tuples[word_idx][1]
wordnet_word_pos = pos_tag_dict.get(
nltk_word_pos[0].upper(), None)
if wordnet_word_pos is not None:
new_word = lemmatizer.lemmatize(word, wordnet_word_pos)
else:
new_word = lemmatizer.lemmatize(word)
new_sentence_words.append(new_word)
new_sentence = " ".join(new_sentence_words)
new_sentences.append(new_sentence)
return " ".join(new_sentences)
最后是一个做所有文本预处理的类
def download_if_non_existent(res_path, res_name):
try:
nltk.data.find(res_path)
except LookupError:
print(f'resource {res_path} not found. Downloading now...')
nltk.download(res_name)class NltkPreprocessingSteps:
def __init__(self, X):
self.X = X
download_if_non_existent('corpora/stopwords', 'stopwords')
download_if_non_existent('tokenizers/punkt', 'punkt')
download_if_non_existent('taggers/averaged_perceptron_tagger',
'averaged_perceptron_tagger')
download_if_non_existent('corpora/wordnet', 'wordnet')
download_if_non_existent('corpora/omw-1.4', 'omw-1.4')
self.sw_nltk = stopwords.words('english')
new_stopwords = ['<*>']
self.sw_nltk.extend(new_stopwords)
self.sw_nltk.remove('not')
self.pos_tag_dict = {"J": wordnet.ADJ,
"N": wordnet.NOUN,
"V": wordnet.VERB,
"R": wordnet.ADV}
# '!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~' 32 punctuations in python
# we dont want to replace . first time around
self.remove_punctuations = string.punctuation.replace('.','')
def remove_html_tags(self):
self.X = self.X.apply(
lambda x: BeautifulSoup(x, 'html.parser').get_text())
return self
def replace_diacritics(self):
self.X = self.X.apply(
lambda x: unidecode(x, errors="preserve"))
return self
def to_lower(self):
self.X = np.apply_along_axis(lambda x: x.lower(), self.X)
return self
def expand_contractions(self):
self.X = self.X.apply(
lambda x: " ".join([contractions.fix(expanded_word)
for expanded_word in x.split()]))
return self
def remove_numbers(self):
self.X = self.X.apply(lambda x: re.sub(r'\d+', '', x))
return self
def replace_dots_with_spaces(self):
self.X = self.X.apply(lambda x: re.sub("[.]", " ", x))
return self
def remove_punctuations_except_periods(self):
self.X = self.X.apply(
lambda x: re.sub('[%s]' %
re.escape(self.remove_punctuations), '' , x))
return self
def remove_all_punctuations(self):
self.X = self.X.apply(lambda x: re.sub('[%s]' %
re.escape(string.punctuation), '' , x))
return self
def remove_double_spaces(self):
self.X = self.X.apply(lambda x: re.sub(' +', ' ', x))
return self
def fix_typos(self):
self.X = self.X.apply(lambda x: str(TextBlob(x).correct()))
return self
def remove_stopwords(self):
# remove stop words from token list in each column
self.X = self.X.apply(
lambda x: " ".join([ word for word in x.split()
if word not in self.sw_nltk]) )
return self
def lemmatize(self):
lemmatizer = WordNetLemmatizer()
self.X = self.X.apply(lambda x: lemmatize_pos_tagged_text(
x, lemmatizer, self.post_tag_dict))
return self
def get_processed_text(self):
return self.X
上面的类只不过是以前编写的函数的集合。注意,每个方法都返回对 self 的引用。这是为了以流畅的方式实现方法的无缝链接。通过查看下面的用法,您将会很欣赏它:
txt_preproc = NltkPreprocessingSteps(df['tweets'])
processed_text = \
txt_preproc \
.remove_html_tags()\
.replace_diacritics()\
.expand_contractions()\
.remove_numbers()\
.fix_typos()\
.remove_punctuations_except_periods()\
.lemmatize()\
.remove_double_spaces()\
.remove_all_punctuations()\
.remove_stopwords()\
.get_processed_text()
将代码转换到 sklearn 转换器
现在,我们已经看到了最常用的文本预处理步骤的代码片段,是时候将它们放入 sklearn 转换器中了。
sklearn 转换器旨在执行数据转换,无论是插补、操作还是其他处理,可选地(并且最好)作为复合 ML 管道框架的一部分,具有其熟悉的 fit()、transform()和 predict()生命周期范例,这是一种非常适合我们的文本预处理和精度生命周期的结构。但是在我们到达那里之前,一个快速的 sklearn 生命周期初级读本是合适的。开始了。
世界上最短的 sklearn 生命周期入门
在 sklearn 行话中,管道是一组连续的执行步骤。这些步骤可以属于两个类别之一——转换、ML 预测。管道可以是纯转换管道或预测管道。
一个纯转换管道只有为顺序执行设置的转换器。预测管道可以在其末端包含可选的转换器和强制的单个预测器。

图二。两种类型的管道(图片由作者提供)
一个转换有三个主要操作——拟合、转换和拟合 _ 转换。预测器拟合方法也有三个类似的操作——拟合、预测和拟合 _ 预测。在这两种情况下,适合的方法就是学习发生的地方。学习是根据类属性来捕获的,这些类属性可以分别由变换器和预测器用于变换和预测。

图 3。转换器和预测器操作(图片由作者提供)
典型的监督学习场景包括两个步骤——训练和推理。在训练阶段,用户在管道上调用 fit()。在推断阶段,用户在管道上调用 predict()。
sklearn 在训练和推理阶段的两个最重要的翻译:
1.管道上的 fit()调用被转换为管道中所有转换器组件上的顺序 fit()和 transform()调用,以及最终预测器组件(如果存在)上的单个 fit()。
2.predict()调用被转换为管道中所有转换器上的顺序 transform()调用(当转换器存在时),最后是管道中最后一个组件上的单个 predict(),如果调用预测,该组件必须是预测器。
呜哇!恭喜你不费吹灰之力走了这么远。我们已经准备好向成熟的文本预处理转换器迈出最后一步。它来了。
使 NLTK 预处理步骤适应 sklearn 转换器 API
这最后一节将是小菜一碟,因为在前面关于 sklearn 生命周期的章节中有足够的关于 sklearn 生命周期的背景知识。我们现在要做的就是将 NLTK 预处理步骤包装成 TransformerMixin 的 sklearn 的子类。
from sklearn.base import BaseEstimator, TransformerMixin
class NltkTextPreprocessor(TransformerMixin, BaseEstimator):
def __init__(self):
pass
def fit(self, X):
return self
def transform(self, X):
txt_preproc = NltkPreprocessingSteps(X.copy())
processed_text = \
txt_preproc \
.remove_html_tags()\
.replace_diacritics()\
.expand_contractions()\
.remove_numbers()\
.fix_typos()\
.remove_punctuations_except_periods()\
.lemmatize()\
.remove_double_spaces()\
.remove_all_punctuations()\
.remove_stopwords()\
.get_processed_text()
return processed_text
现在,您可以用熟悉的标准方式在任何 sklearn 管道中定制 NLTK 预处理转换器。下面是它在纯转换管道和预测管道中的两个用法示例
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.naive_bayes import BernoulliNBX = pd.read_csv("....")X_train, X_test, y_train, y_test = train_test_split(X['tweets'], y, random_state=0)pure_transformation_pipeline = Pipeline(steps=[
('text_preproc', NltkTextPreprocessor()),
('tfidf', TfidfTransformer())])
pure_transformation_pipeline.fit(X_train)# Call fit_transform if we only wanted to get transformed data
tfidf_data = pure_transformation_pipeline.fit_transform(X_train)prediction_pipeline = Pipeline(steps=[
('text_preproc', NltkTextPreprocessor()),
('tfidf', TfidfTransformer()),
('bernoulli', BernoulliNB())])
prediction_pipeline.fit(X_train)y_pred = prediction_pipeline.predict(X_test)
就是这样,伙计们!我们的 sklearn 集成已经完成。现在我们可以获得管道的全部好处——交叉验证、防止数据泄露、代码重用等等。
在本文中,我们开始以通常的方式处理基于 NLTK 的文本预处理——通过编写通常在 Jupyter Notebook 单元中执行的小代码片段,将它们重构为函数,过渡到可重用的类,并使我们的代码适应行业标准的可重用 API 和组件。
为什么我们要做这一切?原因其实很简单;由数据科学家编写的代码终究是代码,并且在维护、修订生命周期中伴随着价格标签(拥有成本)。引用马丁·福勒的话——“任何人都可以编写计算机能够理解的代码。优秀的程序员编写人类能够理解的代码”。具有标准使用模式的好代码使数据科学家(人类)容易理解和维护 ML 管道,从而降低拥有成本。有了适用于我们代码的标准 API 适配器,即使没有 ML 背景的软件工程师也可以轻松地使用它,推动了我们刚刚编写的基于 NLTK 的文本预处理代码在公司范围内的采用,更不用说 ROI 了。欢迎来到架构优雅的文本预处理世界。
在您的数字团队中消除 8 种 LSS 浪费(大木)
原文:https://towardsdatascience.com/eliminating-the-8-lss-wastes-muda-in-your-digital-teams-c0132bfcf55c

杰森·古德曼在 Unsplash 上拍摄的照片
精益六适马实践是制造业的行业标准,但当应用于软件开发和生产的数字团队时,同样的原则也是容易转移和转化的
精益六适马和相关的全面质量管理系统已经存在了几十年,从 W. Edwards Deming 开始有多个前身,并成功应用于个别领域的特定质量系统,如技术领域的敏捷项目管理。
虽然在敏捷中对可度量性和功能性工作技术的明确强调是显而易见的,但是在 LSS 认识到的一些浪费可能不那么明显,但是在我们的开发团队和生产环境中仍然值得给予极大的关注。在本文中,我们将详细介绍 LSS 的八种浪费,也称为大木,很容易用首字母缩写“TIMWOODS”来回忆,以及它们如何以数字形式应用到我们的工作中。
T —运输
这种浪费最初描述了人员、机械和其他资源在工厂和其他制造环境中的移动。如果有可能让操作彼此相邻,为什么要让传送带在整个工厂的单元操作之间传送部件呢?毕竟,它降低了风险,使用更少的电力,并节省时间。
这个概念也很容易转移到数据的移动。例如,如果您有一个万亿字节客户数据的数据库,并且正在搜索一个数据集来模拟一个城镇的习惯,那么选择所有内容的 SQL 查询将比特定的、可管理的、简化的带键查询使用更多的时间、精力和计算资源。
-- Do not do (may take hours to run):
Select *
From CustomerData-- Instead do (will run much faster):
Select CustomerID, CustomerAddress, CustomerTown, LastOrderDate
From CustomerData
Where CustomerTown = 'Nashville'
And LastOrderDate >= date('2022-01-01')
一、库存
传统上,库存管理问题可能处于囤积的边缘,尽管在一些行业中有必要建立“以防万一”储备,但在大多数情况下,当及时库存管理是理想的时,可能会在其他行业中导致危机。大多数人会记得在疫情开始时,超市空空如也的货架,特别是放有卫生纸的货架,当时 COVID 甚至没有证明卫生纸需求如此急剧增长的合理性。在整个疫情,类似的库存管理问题导致了硅片、制药设备和其他我们今天可能认为理所当然的材料和零件的短缺。不动产和存储空间是有限的,就像超市货架一样。
数字空间也是有限的,即使有时在微事件中维护数据的成本看起来很小。但是,在大规模应用中,数据和存储限制可能会产生重大影响。2000 年问题在整个银行业和技术领域引起了严重的恐慌(尽管没有造成巨大的影响),类似的数据维度问题可能随时都会发生。整数溢出问题,如阿丽亚娜 5 号火箭,一个看似简单的算术错误就能造成数亿美元的损失。另一方面,浪费库存,比如允许的虚假社交媒体账户存在于 Meta 和 Twitter 拥有的平台上,可能会让单个公司在维护费和存储虚假照片和帖子方面花费数百万美元。我们的团队需要在微观层面解决适当的数字库存管理问题,以便事件不会产生宏观影响。
m-运动
运动中的浪费在日常生活中司空见惯,更不用说在制造空间或我们的数字团队中了。在架子顶上放一个工具,需要一个脚凳才能够到,这是一种浪费,因为需要额外的步骤。在制造业,也许需要增加更多价值的设备是在房间的另一边,而不是在你的工作站附近。在技术上,类似的运动中的浪费也适用于数据。过于复杂的用户界面或数据输入会浪费时间。甚至像“确认”或人工检查这样的小事也可能是多余的。在理想的程序环境中,只能输入正确的数据。一个例子是在查找电话号码的程序中允许按字母顺序敲击键盘。这种浪费给每个相关人员带来了糟糕的体验。
W —等待
可以说,清单上最具侵略性的浪费是等待——回想一下,就在这个星期,我们有多少次排队,在车流中,等着别人给我们拿我们需要的东西,等等。制造业中的等待对于产品的价值来说是一种不必要的支出,而在技术领域中的等待会导致糟糕的开发人员流动、糟糕的后端时间安排和糟糕的用户体验。最终,许多数字等待的情况可以通过改变容量来避免。也许是服务器的问题,要么是 A)服务器不够多,要么是 B)服务器不够快。保持我们代码库的整洁和高效有助于减少这个问题,以及构建尽可能使用现代并行化技术和矢量化的功能性代码。我们希望我们的软件能够平稳运行,并且不超过硬件的容量。
O —生产过剩
物理层面上的生产过剩正是我们所期望的——生产了太多超出我们需求的产品。从数字上看,生产过剩可能与服务过多有关。是的,服务太多。过多的服务是不必要的开支,因为现有的服务没有得到充分利用。也许这是使用台式计算机来执行廉价微控制器的任务,或者让多个专用服务器执行无服务器架构可以执行的相同工作。最终,这是一场关于成本和需要完成的工作量的对话。我们的代码也可能由于使用了不必要的不适当大小的变量而被过度生产,并且在我们的数据结构中是低效的,导致在太多的数字库存上的类似成本。
O —过度加工
过度加工的制成品为顾客提供了超出必要的价值。一些很好的例子是使用稀有昂贵的金属作为零件,而便宜的金属就足够了,给汽车增加更多的油漆层,而不是让人们注意到,设计电视或其他屏幕的帧率甚至比我们的眼睛更高。如果没有人会注意到“附加值”,那它怎么会增加更多的价值呢?
类似地,在技术上,我们的代码可能被过度处理,敏捷在对抗范围蔓延方面做得很好,通过结构化和度量的 sprints 节奏。基于用户反馈和需求,管理聚焦于“最小可行产品”和良好定义的基本特性的软件项目是对抗数字化生产过剩的最佳方式。为什么要给产品添加一个特性,花费开发人员的时间、精力,甚至为一些没有要求或不需要的东西付费呢?虽然有些功能确实会给用户带来惊喜,增强用户体验,但有些用户可能永远不会注意到。数字化生产过剩是对团队时间和精力的浪费,这些时间和精力应该在其他地方得到更好的利用。
D —缺陷
不同的行业,甚至同一行业的不同产品,对缺陷的衡量是不一样的。打印纸的缺陷,除非几乎被物理损坏,否则大多数消费者不会注意到。外观上的细微变化可能无法察觉。然而,在实验室环境中使用的石蕊试纸的缺陷可能会完全破坏实验,这反过来会影响他们的声誉。典型地,精益六适马的六适马部分获得缺陷减少的目标,Sigma 是统计集合的方差。“三个西格玛”覆盖 93.32%的人口,四个覆盖 99.35%,五个覆盖 99.98%,六个达到 99.9997%。在缺陷的二进制分类中,使用六西格玛流程,99.9997%的生产单元应该是无缺陷的,或者每一百万个生产单元中大约有三个缺陷。这是一个很难达到的目标,但是六西格玛流程是非常可靠和一致的,并且肯定会赢得客户的信任。
软件也会有缺陷。想想一个应用程序在智能手机上崩溃了多少次,或者一个游戏或程序中的“错误”阻止了你在任务中前进多少次。在最不重要的情况下,这些错误可能会产生不良后果,甚至是幽默的后果。然而,在最严重的情况下,软件缺陷可能会危及生命。很多情况下,即使是一个不受控制和无法预测的小错误也会造成数百万的损失并威胁到人们的生活。最终,每个软件团队都会在预测错误和按时完成项目之间做出权衡——然而目标是明确的。最好的代码可能只有 3.4/1,000,000 次运行失败。
S —技能(第八种浪费)
最新确定的浪费基于员工的技能。乍一看,这种浪费似乎是没有合适技能的风险,然而更大的风险是没有认识到员工是公司最大的资产。这种浪费,或者说不启用、认可和支持员工,是最大的浪费。尤其是在科技领域。
随着每一个新的毕业年,一个班级和一代兴奋的新员工带着新的技能和背景进入劳动力市场,渴望获得经验。微观管理以及缺乏创造力和对员工的信任会毁掉一家公司。鼓励新员工的成长和所有权将建立忠诚度——认可和奖励新开发的技能和经验将对团队的成功产生多米诺骨牌效应。
上面列出的八种浪费同样适用于物理工业制造领域以及软件开发和数据科学领域。应用对这些浪费的基本认识将减少团队的风险并提高他们的效率。让我知道你的想法,如果你同意将 LSS 的概念应用到你的技术和敏捷团队中会有助于更大的成功!
欢迎通过 LinkedIn 联系我,提出反馈、问题或看看我们如何合作!点击这里查看我关于数据分析和可视化的其他文章!
在 Web 上嵌入交互式 Python 绘图
原文:https://towardsdatascience.com/embedding-interactive-python-plots-on-the-web-84ceab57e417
关于如何使用 Plotly Chart Studio 和 Datapane 在 web 上共享 Python 绘图的指南

杰森·库德里特在 Unsplash 上拍摄的照片
介绍
数据科学管道中最重要的步骤之一是数据可视化。事实上,由于数据可视化,数据科学家能够快速收集关于他们可用的数据和任何可能的异常的见解。
传统上,数据可视化包括创建静态图像和汇总统计数据,然后使用 Python 和 Matplotlib 等库将其导出为图像(如 PNG、JPG、SVG)。虽然,由于 Plotly、Bokeh 或 Altair 等库的发展,现在可以创建更多的交互式可视化甚至在线仪表盘,可以与同事和股东共享。
一旦完成一个项目,通常有两种不同的方法可以用来与任何感兴趣的人分享您的可视化和结果:
- 创建某种形式的数据驱动演示。
- 创建一个项目摘要内部网页,其中嵌入了所创建的任何交互式绘图。
在本文中,我们现在将探索第二种方法,提出两种可能的解决方案: Plotly Chart Studio 和 Datapane 。本文使用的所有代码都可以在我的 GitHub 账户上公开获得,网址是这个链接。
Plotly 图表工作室
Plotly 是一个基于 plotly.js 构建的开源 Python 库,使用 Plotly,你可以使用 Python、R、Julia 和 Javascript 等编程语言创建交互式图形和仪表盘。
为了创建易于共享的图表,Plotly 提供了一个图表工作室服务。使用此服务,您可以像往常一样通过编程创建一个图,然后将其添加到您的在线集合中,或者您可以通过在线用户界面上传您的数据,然后从那里创建您的图表(完全不需要编码)。使用 Plotly Chart Studio 创建的一些绘图示例可在链接获得。
对于这个例子,我们将使用 Python 和免费提供的 Kaggle 学生考试成绩数据集 [1]的编程选项。如果您也有兴趣探索用户界面来创建您的图表,Plotly 在此链接提供了一个指南教程列表。
为了开始使用 Plotly Chart Studio,我们需要首先创建一个帐户,然后使用以下命令安装 Python 库:
pip install chart_studio
在这一点上,我们需要保存我们的帐户 API 密钥,以便能够将使用 Python 创建的图表共享到我们的在线帐户中。这可以通过登录你的账户,进入设置,然后 API 键找到。我们现在准备开始我们的数据探索性分析。
数据可视化
首先,我们需要导入所有必要的依赖项,并检查头部的学生在考试数据集中的表现(图 1)。

图 1:学生在考试数据集中的表现(图片由作者提供)。
使用下面的代码片段,我们可以创建一个 3D 散点图,并将其保存到我们的在线集合中,命名为 3d_embed_ex (记得添加您之前从您的帐户设置中复制的 Plotly Chart Studio 用户名和 API 密钥)。
进入你的 Plotly Chart Studio 账户和你的我的文件部分,你应该能够看到你刚刚用 Python 创建的图表。点击 Share 然后点击 Embed 标签,你就能看到 iframe 和 HTML 代码,你可以用它们在网络上的任何地方分享你的互动情节(如下图 2 所示)。
图 2: 3D 散点图 Plotly Chart Studio Embed(图片由作者提供)。
数据面板
Datapane 是一个 Python 库,旨在通过 web 嵌入、自动生成的 HTML 文件等更容易地共享任何形式的数据分析。使用 Datapane 的主要优势之一是它支持许多数据可视化库,例如:【matplotlib】【plotly】bokeh**pandas等等。此外,使用 Datapane,不仅可以共享单个图形,还可以共享图形集合和 Latex/Markdown 文本。
为了开始使用 Datapane,我们首先需要创建一个免费帐户并以与 Plotly Chart Studio 相同的方式获取我们的 API 密钥。然后可以使用以下命令安装 Python 库:
pip install datapane
数据可视化
为了演示如何使用 Datapane,将再次使用学生在考试数据集中的表现(如图 1 所示)。然后使用下面的代码片段,可以创建一个 Datapane 嵌入(记得添加您之前从您的帐户设置中复制的 Datapane API 密钥)。此外,使用 保存 选项,还可以创建您的报告的 HTML 文件版本,然后您可以与任何您想要的人共享,而不必在网络上公开您的报告。
为了分享你新创建的报告,你可以进入你的数据面板账户,点击我的报告,选择你感兴趣的报告,点击分享(图 3)。
图 3:嵌入 3D 散点图数据面板(图片由作者提供)。
通过添加更多的页面和可视化,可以创建更复杂的报告(图 4)。在下面的示例中,创建了一个两页报表:一页显示数据集的前五行,另一页显示三维散点图。
图 4: 2 页数据面板嵌入(图片由作者提供)。
结论
在本文中,我们探讨了 Plotly Chart Studio 和 Datapane 作为在 web 上嵌入交互式图表的两种可能的选择。创建交互式图表不仅可以用于准备和探索数据,还可以用于创建关于机器学习模型的可视化和界面。用于这类任务的两个最常见的库是 Streamlit 和 Gradio。关于这个话题的更多信息,可以在我的“机器学习可视化”文章中找到。
联系人
如果你想了解我最新的文章和项目,请通过媒体关注我,并订阅我的邮件列表。以下是我的一些联系人详细信息:
文献学
[1]“学生在考试中的表现”(SATWIK VERMA,LicenseCC0:Public Domain)。访问:https://www . ka ggle . com/datasets/satwikverma/students-performance-in-examings
拥抱用于机器学习的 Kubernetes:设置[1/3]
原文:https://towardsdatascience.com/embracing-kubernetes-for-machine-learning-setup-1-3-51c02686edfb
通过 Minikube 为本地开发设置 Kubernetes
害怕它,逃避它,库伯内特还是来了。熟悉最流行的容器编排工具在任何现代机器学习环境(或任何软件开发环境)中都是不可避免的。

图片来源: Pixabay
众所周知,Kubernetes 是一种大多数开发人员都厌恶的工具,并且很难理解和利用。不管它值不值得,它真的会让人感到无法抗拒——最大限度地发挥 Kubernetes 潜力的体验。世界上能够真正称得上“驯服库伯内特的人”的开发者并不多。然而,人们不需要了解 Kubernetes 或任何工具的每一件事情,就可以有效地使用它。掌握任何工具都是需要时间,练习和坚持的。这篇文章(3 部分系列的一部分)从在 Kubernetes 集群上部署机器学习模型的角度对 Kubernetes 的世界进行了实际介绍。
NB :虽然重点可能是从模型部署的角度来看,但是这篇文章中探索的概念是如此的通用,以至于没有理由为什么这个逻辑不能应用于任何容器化应用程序的部署。本系列的先决条件是对通过 Docker 实现 app 容器化有一些基本的了解。这篇关于应用容器化的早期文章推荐给很少或没有 Docker 经验的读者。
以下部分的攻击计划是:
- 概述一下为什么需要 Kubernetes
- 安装 Minikube 作为我们的本地 Kubernetes 集群
- 部署一个基本应用程序来测试新集群
Kubernetes (K8s)到底有什么模糊之处?
Kubernetes 是 Google 的开源产品,用于容器化应用的自动化部署和扩展。在容器中运输应用程序是目前软件部署的黄金标准,Kubernetes 提供了一种非常有趣的方式,以可伸缩和健壮的方式自动化容器。
名字 Kubernetes 源自希腊语,意为舵手或领航员。 K8s 是对“ K ”和“ s ”之间的八个字母进行计数得到的缩写。
在当前的容器部署时代,持续监控容器以便采取必要的措施来避免停机是至关重要的。例如,一个容器一旦死亡,就需要立即启动新的容器。有人可能会说,一种幼稚的方法是编写某种形式的自动化脚本来完成这类操作。然而,这些应用程序中的大多数都有如此多的移动微型部件,这样的方法只会导致大量的麻烦,最终会失败。
Kubernetes 通过消除对编排的需求,为自动管理和运行分布式应用程序或系统提供了一个更健壮、更有弹性的框架。Kubernetes 提供了一些令人兴奋的功能,如;
- 自动自愈
- 负载平衡
- 秘密管理
- 自动化推出和回滚
- 存储编排
- 自动装箱
让我们深入 Kubernetes 的一些高级组件,了解可伸缩性的概念是从哪里来的?
库伯内特斯的基本成分
最核心的是,使用 Kubernetes 的部署会导致 Kubernetes 集群的创建。但是在这个合成的集群中有什么呢?
这个集群由运行容器化应用程序的 节点 (一组工作机)组成。每个 Kubernetes 集群至少有一个节点。这些节点主要托管pod,它们本质上是一组运行的容器。这些节点和单元由 控制平面 管理,该控制平面负责以这样的方式管理节点和单元,即实现并保持用户定义的期望状态。实际上,人们可以将 Kubernetes 视为一个系统,其中用户提供一些所需的配置,控制平面调度各种节点,容器 pod 在这些节点上旋转以运行各种工作负载,从而以自动化的方式实现和维护预期的状态。这里的卖点是,用户可以拥有无限数量的运行各种应用程序的节点,所有这些应用程序都由群集自动管理。某个应用程序的实例在某个节点上崩溃了?没问题!另一个会自动启动以取代死去的那个。这个神奇的工具有这么多,但我希望你现在明白了。现在,让我们进入有趣的部分,在本地设置一个!
通过 Minikube 在本地设置 Kubernetes
Minikube 是一个工具,它能够以一种简单的方式在本地创建 Kubernetes 集群。顾名思义,Minikube 只是一个轻量级版本,其中部署的集群是一台个人计算机上的单个节点。
本系列假设读者将在类似 UNIX 的系统上安装 Minikube,如 Linux 发行版或 Apple Macs。只能访问 Windows 系统的读者应该使用 WSL 2 来跟进。本系列是在一个 Linux 发行版上完成的,使用 VSCode 作为 IDE。所有的包都很容易从【AUR】(ARCH 用户库)安装。
安装必备工具
- 安装Docker CLI(Mac 用户可以使用 自制 )
- 安装 kubectl
- 安装 minikube (Mac 用户可以使用 自制 )
- 安装 kubectx
- **加成:为 Kubernetes 安装值得注意的 VS 代码扩展( Kubernetes ,Kubernetes)。
配置 Minikube 集群
成功安装 Minikube 后,就可以配置集群了。让我们首先为这个 Minikube 集群将要驻留的虚拟机定义一些内存和 CPU 分配。这些分配是任意的,因此根据与您的可用资源相匹配的任何所需配置进行分配:
***minikube config set cpus** **6**
**minikube config set memory 12g
minikube delete --all***
由于我们选择 docker 作为我们集群的驱动程序,因为我们将构建一些 docker 容器,所以我们可以用以下内容启动我们的 Minikube 集群:
***minikube start --driver=docker***

设置 minikube 集群配置并启动它(来源:作者)
日志显示一切正常,Minikube 集群已经启动并运行。现在是检查先前安装的kubectx和kubens是否能与新集群交互的好时机。对于名称空间来说,默认选项现在很好,因为我们还没有配置任何名称空间。我们只有一个集群,因此切换集群上下文仅限于创建的 Minikube 集群。

通过 kubens 选择名称空间(来源:作者)
Minikube 还部署了一个仪表板,用于可视化地概述所部署的集群。用户可以使用以下命令签出该仪表板:
***minikube dashboard***

Kubernetes 仪表板(来源:作者)
启用 Minikube 加载项
一切正常后,现在让我们启用一些附加组件来改善我们的用户 Kubernetes 的本地开发体验。该命令提供了许多附加组件:
***minikube addons list***

可用加载项(来源:作者)
大多数用例并不需要每个插件,所以让我们启用并配置一些我认为非常有用和实用的插件。我鼓励读者深入探究它们,并启用他们喜欢的内容。启用 Minikube 附加组件也很简单;
***minikube addons enable <NAME OF ADDON>***
- 日志查看器 :日志对于监控部署至关重要。有一个漂亮的插件,允许用户在一个网页上显示集群中的所有实时日志(部署在
http:**<YOUR_MINIKUBE_IP>**:32000)和许多其他日志相关的实用程序。
*❯ **minikube addons enable logviewer**▪ Using image ivans3/minikube-log-viewer:latest*
- metrics-server : 这个附加组件提供了度量仪表板,有助于可视化已部署应用程序的资源消耗。它还可以很好地与 Kubernetes 仪表板集成。
*❯ **minikube addons enable metrics-server **
▪ Using image k8s.gcr.io/metrics-server/metrics-server:v0.4.2
🌟 The 'metrics-server' addon is enabled*
- 入口 : 在某种形式的入口控制器上部署应用是很常见的。这个插件也支持入口控制器。它只是部署一个 Nginx 入口控制器,通过一个
Nodeport服务将分配给集群的 IP 指向这个控制器。
*❯ **minikube addons enable ingress**
▪ Using image k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1
▪ Using image k8s.gcr.io/ingress-nginx/controller:v1.1.1
▪ Using image k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1
🔎 Verifying ingress addon...
🌟 The 'ingress' addon is enabled*
- ingress-dns😗*Nginx Ingress 控制器现在将流量定向到新集群的 IP,让我们启用此插件,因为 Ingress 在 DNS 上工作,它将解析为分配给 Minikube 的 IP,并在 Kubernetes 集群中充当 DNS 服务。如果没有这个附加组件,任何针对每个服务的DNS 调用都将失败,除非显式添加到主机上的
/etc/hosts文件中。对于不喜欢篡改主机文件的读者来说,通过遵循官方指南,可以很容易地设置任何想要的 DNS 配置。
*❯ **minikube addons enable ingress-dns **
▪ Using image gcr.io/k8s-minikube/minikube-ingress-dns:0.0.2
🌟 The 'ingress-dns' addon is enabled*
- registry-creds:在主流云提供商的私有容器注册表中托管应用程序是相当常见的。该插件允许用户配置本地 Minikube 集群,以访问来自这些云提供商的私有注册表中的映像。目前,AWS、GCP、Docker、Azure 的容器注册中心都得到支持,这对于大多数用例来说已经足够了。使用以下命令配置任何选择注册表(本系列将使用 Docker 注册表):
*❯ **minikube addons configure registry-creds**Do you want to enable AWS Elastic Container Registry? [y/n]: nDo you want to enable Google Container Registry? [y/n]: nDo you want to enable Docker Registry? [y/n]: y
-- Enter docker registry server url: [https://hub.docker.com/](https://hub.docker.com/)
-- Enter docker registry username: **<YOUR USERNAME>**
-- Enter docker registry password: **<YOUR PASSWORD>**Do you want to enable Azure Container Registry? [y/n]: n
✅ registry-creds was successfully configured*
设置正确的注册表后,启用附加组件本身:
*❯ **minikube addons enable registry-creds**
▪ Using image upmcenterprises/registry-creds:1.10
🌟 The 'registry-creds' addon is enabled*
- metallb: 云提供商为负载平衡器服务(来自外界的网络访问)分配 IP,但是我们的私有和本地集群呢?这就是这个漂亮的附加组件的用武之地。它将负责为集群中各种定义的服务分配 IP。让我们通过这个命令来启用它;
*❯ **minikube addons enable metallb**
▪ Using image metallb/speaker:v0.9.6
▪ Using image metallb/controller:v0.9.6
🌟 The 'metallb' addon is enabled*
即使启用了 metallb,它也无法找到可以分配给服务的 IP 范围。我们需要给它这个范围的 IP。首先,检查群集的 IP,通过以下方式了解一个合适的范围:
*❯ **echo $(minikube ip)**
XXX.XXX.XX.2*
有了这个 IP 地址,我决定从XXX.xxx.xx.5到xxx.xxx.xx.55给出一个 50 的范围,作为负载平衡器服务应该分配到的范围。该范围是任意的,因此指定任何所需的范围。**
*❯ **minikube addons configure metallb **
-- Enter Load Balancer Start IP: XXX.XXX.XX.5
-- Enter Load Balancer End IP: XXX.XXX.XX.55
▪ Using image metallb/controller:v0.9.6
▪ Using image metallb/speaker:v0.9.6
✅ metallb was successfully configured*
砍!我知道设置这些东西可能是乏味和机械的,但我们现在已经完成了设置我们新的闪亮 Minikube 集群的所有螺栓和螺母。最后看一下附加组件列表,现在应该会显示已经成功启用的附加组件。是时候开始在这个新集群上进行一些部署了!

Minikube 插件启用的最终状态(来源:作者)
在新集群上测试延迟
我们经历的这些麻烦没有白费。让我们对一些基本应用程序进行一些快速部署,以检查我们的新系统是否如预期那样工作。
***kubectl create deployment balanced --image=k8s.gcr.io/echoserver:1.4**
**kubectl expose deployment balanced --type=LoadBalancer --port=8080***

使用 kubectl 在默认名称空间中创建名为“balanced”的部署(来源:作者)
根据日志,echoserver 演示应用程序被部署(部署名为" balanced" )并在 8080 上作为"负载平衡器"类型的服务公开。要获得分配的 IP,只需检查此部署的服务
***kubectl get services balanced***

正在检查为公开的平衡负载平衡器服务分配的外部 IP(来源:作者)
从给定的信息中,请注意EXTERNAL-IP部分是为此部署分配的 IP。与公开部署的端口相结合,给出了与部署的应用程序进行外部交互的完整 URL。让我们通过发出 get 请求来查询这个应用程序。我使用了curl,但是任何请求工具甚至浏览器都可以正常工作。记住**EXTERNAL-IP:PORT**发送外部 URL!
*❯ **curl <YOUR-ASSIGNED-EXTERNAL-IP>:8080**
CLIENT VALUES:
client_address=172.17.0.1
command=GET
real path=/
query=nil
request_version=1.1
request_uri=[http://](http://192.168.49.2:8080/)**<YOUR-ASSIGNED-EXTERNAL-IP>**[:8080/](http://192.168.49.2:8080/)SERVER VALUES:
server_version=nginx: 1.10.0 - lua: 10001HEADERS RECEIVED:
accept=*/*
host=**<YOUR-ASSIGNED-EXTERNAL-IP>**:8080
user-agent=curl/7.83.1
BODY:
-no body in request-%*
部署的应用程序似乎正在完成它的工作,因为它只是将发送到该服务器的所有内容回显给用户。
此外,可以在浏览器中从所有名称空间或所选名称空间的实时网页访问先前启用的日志附加组件。
*[http://](http://192.168.49.2:32000/)**<YOUR-****MINIKUBE****-IP>**[:32000/](http://192.168.49.2:32000/)*

从日志查看器插件检查日志(来源:作者)
另一个与 Minikube 配合得非常好的实用程序是[K9s](https://k9scli.io/)。它只是终端中用于管理集群的一个交互式仪表板。只需通过命令k9s启动它,并尝试在default名称空间中杀死(*ctrl+d*)我们名为balanced-xxxxxx的部署,看看会发生什么。

来自 K9s 的交互 UI(来源:作者)
让我们清理为这个初始演示创建的部署和服务。
*❯ **kubectl delete services balanced **
service "balanced" deleted
❯ **kubectl delete deployment balanced **
deployment.apps "balanced" deleted*
正如您可能已经注意到的那样,仅仅使用这些单一的kubectl命令,复杂应用程序的部署将会非常繁琐。标准的做法是将东西整齐地打包到yaml文件中,以保证健壮性和紧凑性。在本系列的下一篇文章中,我们将讨论类似真实世界中使用yaml文件的部署。
最后,重新启动计算机不会删除群集或启用的附加组件及其配置。此外,群集上的所有部署也将重新启动。

重启现有的日志(来源:作者)
我们将在随后的帖子中讨论适当的部署,所以如果现在一切都有点模糊,不要太担心。很多信息都被扔给你了。只是玩玩这个部署,回顾一下最初的概念。Kubernetes 的世界是巨大的!幸运的是,你现在有了 Minikube,可以按照自己的速度探索它。在本系列的下一篇文章中,我们将通过在新集群上部署机器学习模型来更深入地探索 Kubernetes 的世界。在此之前,请继续阅读关于您可能刚刚读到的一些概念的大量文档,并尝试一些基本的部署!
经验累积分布函数:数据科学家需要的唯一绘图工具
看完这篇文章,你就再也不会用直方图了
现代数据科学家在绘图技术的海洋中遨游。从 matplotlib 到 ggplot2 从 plotly 到 d3.js 从直方图,到箱线图,到散点图,到 seaborn 接下来想出的任何疯狂的图表;数据科学家很难理解这一切!

图片由作者提供,修改自公共领域矢量图片
但是不用担心了。经验累积分布函数(ecdf)将带你去你需要去的任何地方;它们是你真正需要的探索性数据分析伴侣;穿过所有噪音的那个;它将永远在那里可靠地照亮你尚未理解的数据的黑暗的统计世界。
什么是经验累积分布函数?
我现在已经在数据科学领域工作了 4+年,我可以告诉你:我很少看到经验累积分布函数(ecdf),除了在我自己的屏幕上。所以,如果你不熟悉,让我们花点时间解释一下。来自维基百科(转述):
经验累积分布函数(eCDF)是与样本的经验测量相关的分布函数。这个累积分布函数是一个阶跃函数,在 n 个数据点中的每一个上跳 1/n。它在被测变量的任何指定值处的值是被测变量的观测值中小于或等于指定值的部分。

来自维基百科 : “绿色曲线,渐近接近 0 和 1 的高度而没有达到它们,是 标准正态分布 的真实累积分布函数。灰色散列标记代表从该分布中抽取的特定 样本 中的观察值,蓝色阶跃函数的水平阶跃形成该样本的经验分布函数
在进一步讨论 ecdf 以及它们与直方图的关系之前,我们必须首先讨论直方图和 ecdf 旨在逼近的函数:pdf 和 CDFs。
pdf 和 CDF
当概率密度函数(PDF) 测量从某个支持X上的某个概率函数P中随机抽取x的P(x=X)时,累积分布函数(CDF) 测量同一支持X上的同一概率函数P的P(x<=X)。

对于台下的微积分爱好者来说,CDF 就是 PDF 的积分。
直方图和 ECDFs
上面的那些曲线是理论上的,通过绘制正态分布的方程来显示。
然而,当我们对现实世界的数据进行探索性的数据分析时,并没有一个显式的等式生成我们的数据;相反,它是由我们想要理解、利用甚至影响的复杂过程产生的。
为了开始这一理解过程,我们通常绘制数据的经验分布图;这通常通过绘制直方图来完成。正如直方图提供了对潜在 PDF 的估计,ECDF 提供了对潜在 CDF 的估计。
为了演示直方图和 ECDF 之间的区别,我使用它们来可视化来自正态分布的相同的 10000 个绘图:
# Import.
import numpy as np
import matplotlib.pyplot as plt
# Create data.
mean = 0
std = 1
gaussian_draws = np.random.normal(mean, std, size=n_draws)
# Plot histogram.
plt.hist(gaussian_draws, bins=25, density=True)
plt.show()
# Plot ECDF.
x = np.sort(gaussian_draws)
y = np.linspace(0, 1, len(gaussian_draws))
plt.plot(x, y)
plt.show()

直方图在直观的可理解性方面可能很好,但它们的好处仅此而已。学会热爱 ECDFs 是值得的,我可以用 5 个理由说服你。
ECDFs 渲染直方图过时的 5 个原因
1.关键的四分位值变得非常明显
我说的“关键四分位值”是什么意思?我的意思是:
- 数据的第 0 百分位值(即最小值);
- 数据的第 25 个百分位数;
- 数据的第 50 百分位值(即中值);
- 数据的第 75 百分位值;
- 数据的第 100 个百分位数(即最大值)。
这些信息很容易编入箱线图和 ECDFs 中,但在直方图中却很模糊。

这就是了。我们可以看到:
- 从箱线图来看,第 25 百分位约为
-0.6,中位数约为0,第 75 百分位约为0.6; - 从 ECDF 来看,第 25 百分位约为
-0.6,中位数约为0,第 75 百分位约为0.6; - 从直方图来看…嗯,我们真的看不到太多。我们可以猜测中位数大概在
0左右,但也仅此而已;而且,由于数据的对称性,我们只能猜测中位数。
没错,伙计们!关于直方图的所有讨论,我忘了提到:方框图显然是多余的!虽然它确实能做直方图做不到的事情,但它只是 ECDF 的影子。
2.ECDFs 在低数据量下保持相对高的完整性
在上一节中,我们考虑了来自正态分布的10,000数据。当我们减少数据量时,事情会变得怎样?让我们来看看:

直方图很好地估计了10000绘图的潜在 PDF,甚至是1000绘图的潜在 PDF 但是在100抽签时,情况开始变得有点可疑。事实上,如果我不知道这些数据是如何产生的,我就不能很有把握地说这是一个正态分布!然而,即使在100提款时,ecdf 仍然更加牢固地附着在基础 CDF 上。
你现在可能会问自己,“但是本,我们为什么不增加箱子的尺寸呢?这难道不会使直方图变得平滑,然后它看起来又像我们的潜在正态分布了吗?”是的,它会,但它回避了问题:我们如何决定使用多少箱?
3.对于直方图,您永远不知道要使用多少个箱
让我们放纵一下:如果我们减少容器的数量,上面的直方图会是什么样子?

虽然 7 柱直方图仍然不是我们所知道的令人信服的正态分布,但它比 25 柱直方图更令人信服。但是事情是这样的:不管直方图看起来有多好如果你得到了正确的数量,ECDF 根本不需要这个猜谜游戏!

作者图片
在这种意义上,“箱数”或“箱大小”成为一种超参数。探索性数据分析是尽可能快速有效地了解数据的所有丰富性的时间;现在不是调整超参数的时候。
4.宁滨偏见的危险,或者“你是我的局外人吗?”
假设您仍然想使用直方图。因此,您只需减小容器大小,以适应您的数据量,直到它看起来完美。

图片由作者提供,修改自公共领域矢量图片
如果这是你的方法,那么请注意:如果你的数据有异常值,那么你的直方图可能在骗你。
在下面的图表中,我采用了与上面相同的高斯采样数据,但是手动添加了一个异常值。让我们看看异常值在直方图中的样子,这取决于我们使用的箱数:

根据箱的数量,异常值的感知性质和数据的潜在分布会完全改变:
- 当我们使用
25箱时,我们在X = 10看到一个异常值。但是,垃圾箱显得有点嘈杂,所以我们减少到7垃圾箱。 - 当我们使用
7箱时,假设的 PDF 是平滑的,但是现在离群值在X = 9处。发生了什么事?异常值在箱子中的确切位置?甚至是离群值吗? - 当我们减少到
3条时,异常值扭曲了整个分布,以至于它看起来不再像异常值了!这也变得令人生疑:这可能不是正态分布吗?是指数型还是对数正态型?
通过直方图,我们可以看到,随着箱数的减少,异常值越来越接近分布的主体。然而,如果我们查看相应的 ECDFs,就不会有任何歧义:到X = 2.5时,我们已经看到了 99%的数据;然后,CDF 描绘出一条长的、几乎水平的线,得到值为X = 10的奇异异常值。瞧啊。
5.比较经验分布在数学和视觉上都变得更加简单
…但是不要从我这里拿走它;以安德雷·柯尔莫哥洛夫和尼古拉·斯米尔诺夫为例,他们是科尔莫戈罗夫-斯米尔诺夫检验(K-S 检验)的创始人,这是比较两个经验分布最常用的检验之一。来自维基百科:
在统计学中,Kolmogorov-Smirnov 检验(K-S 检验或 KS 检验)是连续一维概率分布相等的非参数检验,可用于比较样本与参考概率分布(单样本 K-S 检验),或比较两个样本(双样本 K-S 检验)…
…Kolmogorov-Smirnov 统计量化了样本的[ECDF]和参考分布的[CDF]之间的距离,或者两个样本的[ecdf]之间的距离…
…直观地说,[K-S]统计取所有 x 值的两个分布函数之间的最大绝对差值,[在下图中用黑色箭头表示]…

图片来自维基百科
ECDFs 不仅对视觉比较有用;它们对于统计比较也很有用。然而,由于需要通过宁滨进行聚合,直方图是严格的可视化工具。
但是,即使作为可视化比较工具,ECDFs 的性能也和直方图一样好,甚至更好。
举个例子,从 Kaggle 上托管的 2016 年里约奥运会所有运动员的数据集中获取关于人们身高和性别的数据。具体来说,让我们看看男性和女性的身高是如何比较的,使用 Seaborn 的四种不同方法来比较两个直方图:
# Import and load.
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
athlete_df = pd.read_csv("athletes.csv")
# Create figure.
fig, axs = plt.subplots(1, 4, figsize=(20, 5))
# Plot a separate distribution for each sex.
for i, hist_type in enumerate(["layer", "dodge", "stack", "fill"]):
sns.histplot(athlete_df, x="height", hue="sex", multiple=hist_type,
shrink=.75, ax=axs[i], bins=25, stat="probability")
# Display.
plt.show()

哪一个看起来最好:并排显示条,重叠条,堆叠条,或者令人作呕的“填充”方法一直在右边?使用 ECDFs,有一个明确的方法:

这很简单。
结论
当我们将所有这些都包括在内时,ECDF 会告诉您关于数据集分布的所有信息:
- 关键四分位值;
- 分布形状;
- 任何异常值的精确位置;
- 数据如何与其他数据或理论分布进行比较;
…它表现出无与伦比的清晰度和美感。
有了所有这些,ECDF 就是一面镜子,通过它数据集可以向您揭示其所有秘密,不会被任何宁滨混淆。
当然,ECDFs 和直方图仍然有许多兄弟和 sistograms:还有其他绘图工具,其中许多服务于非常不同的功能。仅举一个例子,散点图主要用于初步了解两个联合数据列之间的相关性,这是直方图和 ECDFs 无法做到的。也就是说,如果您的目标是全面了解一列数据的所有特性,或者比较两列数据的经验分布,那么 ECDF 根本不适合。
仍然计划使用直方图吗?在评论里让我知道为什么;)
PS:创建这个帖子时使用的所有代码都可以在这里找到。
PPS:所有图片,除非特别注明,均为作者所有。
喜欢你读的书吗?随时联系我:
LinkedIn|Twitter|benfeifke.com|insta gram|YouTube
在 我的网站 查看这篇博文!
如何使用你的数据的经验分布
原文:https://towardsdatascience.com/empirical-distribution-6af5142eed09
通过从数据的经验分布中提取的随机变量来改进模拟建模
根据机器学习和统计学,我们主要建立模型和理论,这些模型和理论假设数据的基本分布。这可能并不总是合适的,但是如何利用数据的经验分布来解决分析问题呢?
动机
概率分布是统计、机器学习、的核心元素,在模拟中随机数生成的过程中也起着非常关键的作用。虽然现代统计库和软件完全能够从期望的分布中抽取随机数,如正态分布、指数分布或均匀分布(仅举几个例子),但当基础数据不符合任何这些分布时,这项任务就变得更加复杂。为了预测核心信息,逆变换定理在这一切中扮演了巨大的角色。
本文要回答的主要问题是:“如何从任何经验分布中生成随机变量(RV)?
为了激发你的兴趣,让我给你提供一个实际的问题:假设你经营自己的餐馆,你想模拟顾客到达的数量。正如你在《统计学 101》一书中所述,为了模拟这样的问题,你可以从泊松分布(比率为λ)中提取到达次数,而到达间隔时间则通过指数分布(1/λ)来建模。然而,在实践中,你观察到这几乎从来都不是真的——不管出于什么原因,你的客人只是以一种你所研究的任何分布都无法捕捉的方式来来去去,留给你的是到达数据的经验分布——或者换句话说,你通过查看手头的数据直接推断出的分布。
为了模拟真实的游客到达,您希望直接从给定的底层分布中对随机事件进行采样,但是如何做到这一点呢?

我们将在这篇文章中讨论什么
本文探讨了任意经验分布的作用以及逆变换定理的作用,逆变换定理允许我们从给定的数据分布中生成随机变量:
- 经验数据分布
- 逆变换定理
- 从经验分布中抽取随机变量(证明为什么所有这些真的有效)
- 结论
经验数据分布
经验示例数据是通过具有不同参数(均值、方差)和不同抽取样本数量的两个正态分布生成的。这给我们留下了一个双峰正态分布。现在,假设对于我们的分析问题,我们感兴趣的是通过从 N(0,1)分布中抽取来初始化权重矩阵。这可能很简单,但导致的问题是我们不会考虑沉重的右尾巴。因此,我们将从右尾提取很少的值,但是在平均值 0 附近的值要多得多。
换句话说,我们会取消对经验分布右侧的低估,并比我们应该看到的更少看到 4 左右的值。

数据的直方图和密度
上面的直方图被分成 20 个区间。每个箱代表一个值范围,对于每个范围,我们计算落入其中的值的数量。
变量“bin”说明了每个值存储桶的边界,因此,为了找到直方图中第一个条形的高度,我们需要对落入范围从-3.310 到-2.863 的存储桶中的所有值进行计数。在经验数据中,有 12 个值可以分配到该范围。自然地,给定用于生成数据的基本分布,大多数出现在 0 和 4 均值附近。
bins =
array([-3.310 , -2.863, -2.417, -1.971, -1.524, -1.078, -0.632, -0.186, 0.261, 0.707, 1.153, 1.599, 2.046, 2.492, 2.938, 3.384, 3.831, 4.277, 4.723, 5.169, 5.616])
# bold: 0.707/1.153
# The bold printed numbers are used for explanation later on
变量“counts”简单地计算落入每个相应仓中的事件的数量。
counts =
[ 12, 52, 172, 374, 801, 1237, 1657, 1788, 1540, 1117, 697,
343, 145, 73, 193, 535, 666, 430, 148, 20]
# value related to bin [0.707, 1.153]: 1117
概率密度函数
使用从直方图获得的值,我们可以直接推断出经验数据的概率密度函数 ( PDF )。为此,我们只需将每个计数除以值的总数:
pdf =
array([0.001, 0.004, 0.014, 0.031, 0.067, 0.103, 0.138, 0.149, 0.128, 0.093, 0.058, 0.029, 0.012, 0.006, 0.016, 0.045, 0.056, 0.036, 0.012, 0.002])
# p for value 1117: 0.093
如果你观察 PDF 值,你会发现它们先增加,然后减少,再稍微增加(大约是 x=4 的值),然后再减少到接近初始值。根据我们的直觉,这与我们之前看到的直方图/密度图非常吻合。
当然,不需要手动进行拆分和计数;Numpy 的 histogram() 函数就是这样做的。
values, bins = np.histogram(data, bins=20)
累积分布函数
到目前为止,我们已经能够给一个给定的输入值分配一个概率。例如,如果我们定义我们的示例 X=0.8,我们可以将它分配给边界为 0.707 到 1.153 的 bin。有 1117 个值落入此区间,假设总共有 12000 个观察值,在该区间中看到值的概率约为 1117 / 12000 = 0.093 或 9.3% 。
"bounds: [0.707, 1.153]" : {"counts" : 1117, "probability" : 0.093}
累积分布函数(CDF)基于从 PDF 获得的知识。参考上面的例子,CDF 表示 X ≤0.8 的概率。这意味着我们不仅要查看函数在 X=0.8 的精确值,还要查看到该点为止的所有值。
为了用简单的代码片段来表达这一点,我们可以直接从 PDF 获取 CDF:
np.cumsum( counts / np.sum(counts) ) # counts-> no of values per bin
为了查看从已知分布计算 PDF 值的上下文中的一个示例,我可以参考下面的帖子(它使用超几何分布作为基础分布,并将其放在 A/B 测试的上下文中):
基本情况:正态分布
我们先来看看标准正态分布。如您所见,CDF 中的值范围是从 0 到 1——从 PDF 的左侧开始移动,将所有值添加到右侧。考虑到 CDF 总是随着我们从左向右移动而累加值,我们可以确信 CDF 是严格单调递增的。

标准正态分布的直方图和 CDF(模拟 10,000 个随机变量)
为什么我们关心经验分布的 CDF?
这个有限的范围对于进一步的考虑非常有价值,因为它允许我们在我们的经验 CDF 中的这个值上画出范围[0,1]中的任何随机均匀数点,并且将其“反向”映射到它在经验 PDF** 中各自的 X 值——这个过程通常被称为逆变换定理(ITT)。**
逆变换定理
正如刚才所描述的,ITT 允许我们使用 CDF 来将聚合概率映射到其各自的 X 值(PDF 的)。这意味着我们可以选择任何随机的统一值,在 CDF 的垂直线上找到它的点,并在水平轴上找到 X 的对应值——这个映射值对应于我们的 PDF 中感兴趣的变量。
Uniform(0,1) -> CDF (y-axis) -> PDF (x-axis) # done
在下面的图表中,我们对两个不同的均匀随机变量进行了采样,并试图找到我们的基本分布的各自的 X 值。

红线对应 U=0.8 ,而金线对应 U=0.15 (将 y 轴视为 U 的范围)。我们现在来看看 CDF 函数的截取。通过观察,我们已经可以假设红线的 X 值大约是 0.9,而金线的 X 值似乎是-1 左右。
为了检查这一观察结果,我们可以将百分比值插入百分点函数(ppf) 。这将为我们插入的总概率提供 X 的精确值。正如我们可以看到的,随机采样数据及其观察到的映射与标准正常 CDF 的精确值非常一致:
norm.ppf(0.8) = **0.8416212**, norm.ppf(0.15) = **-1.0364334**
除了我们刚刚研究的,我们的主要兴趣是如何从经验分布中抽取随机数。为了回答这个问题,我们可以利用与 ITT 完全相同的想法。在前面的步骤中,我已经导出了数据的经验 PDF。这些 PDF 值的累积和将产生经验 CDF ,如下所示:

显然,经验 CDF 偏离了标准正态分布过程中所讨论的内容。然而,应用 ITT 来获得 X 值的想法也是适用的。
在不使用外部包/库的情况下,可以查看 CDF 并识别累积概率小于或等于给定均匀随机变量(U)的索引:
idx=np.argmin(mycdf <= 0.5) # provides the first index where "True"
pdf[idx] # desired bin which is ≈ X
使用该索引,我们可以找到相应的感兴趣的容器,并将值 U 映射到该容器。这当然不是很准确,实际上需要在面元内进行插值,以使分布足够连续。
找到对应于我们的统一 U 的 X 的更好和更准确的方法是使用 statmodels 的 experimental _ distribution。 ECDF() 为您提供了 x 和 y 属性,使索引变得非常简单。
from statsmodels.distributions.empirical_distribution import ECDF
ecdf = ECDF( data )
# ecdf.x, ecdf.y
参考我们之前用于标准正常 CDF 的例子,我们寻找对应于 U=0.8 的 X 值。

U=0.8 ≈ X=1.73
与 U=0.8 导致 X 约为 0.84 的标准正态分布的结果相反,经验分布指定了一个大得多的值 X 约为 1.73。这是有意义的,因为我们知道经验分布有一个很大的右尾,或者更确切地说是在 X=4 的值周围添加了一个“附加正态分布。
从经验分布中提取随机变量
为了结束这个话题,我想说明 ITT 的概念确实提供了一致和正确的结果。这个绘制 U 并找到其正确 bin 位置的迭代过程称为“自举。”
为此,我将采用经验 CDF 并抽取 5000 个独立的随机均匀分布(U)。对于每个 U,我将找到 CDF ≤ U 为真的第一个索引,并且递增该索引位置的 bin 计数。最终,绝对出现次数将根据总值的数量进行归一化,从而获得 PDF。
回想一下,对于这个总结步骤,我们只有经验 CDF,我们使用 bootstrapping 来导出 PDF,它应该看起来与我们开始时的经验 PDF 相似。
pdf = np.zeros_like(bins)
for step in np.arange(1000):
u = np.random.uniform()
p = np.argmin(mycdf <= u) # p is the index
# Increment the counter at bin position of index
pdf[p-1]+=1
# Calculating the PDF
pdf = pdf / np.sum(pdf)
通过使用经验 CDF 和 1000 个随机抽取的均匀随机变量,我们能够近似我们在这篇文章的第一步中创建的经验 PDF。这个总结性实验表明,应用 ITT 是从任意经验分布中生成随机变量的一种直接方式。

结论
虽然统计和机器学习课程主要关注众所周知的概率分布的推导和应用,但使用经验分布似乎超出了范围。
然而,在现实世界中,尤其是在模拟领域,我们可能会观察到无法映射到标准、指数、均匀或其他分布的数据分布。对于这些情况,从观察到的经验数据分布中简单地提取随机变量是非常方便的。
如果你觉得这篇文章有趣,我会很感激“关注”🫀,直到那时:
照顾好自己,如果可以的话,也照顾好别人。
—借用史蒂芬·都伯纳
以上所有图片均由作者创作。这篇文章的灵感来自于佐治亚理工学院教授戴夫·戈德曼杰出的 IYSE 6644“模拟”课程。
标题图片由 Ruben Gutierrez 在 Unsplash 上提供:

实现 DBT 的持续集成
原文:https://towardsdatascience.com/enabling-continuous-integration-for-dbt-f77a5b8ce994
使用 Github Actions 和 Docker 自动化测试、宏、种子文件、完整 dbt 加载等等

马库斯·温克勒在 Unsplash 上的照片
作为一名数据工程师,我认为工作要求的一句俏皮话是“能够解决问题”。我喜欢我工作的这一部分。对我来说,处理一个任务或问题并为它建立一个解决方案是非常值得的。
虽然我喜欢在工作中解决问题和开发解决方案,但我不特别是喜欢重复的任务。当我发现自己一遍又一遍地做同样的事情时,我经常问自己“这里缺少了什么?我能做些什么来简化或自动化这一过程?”
这个问题是最近用 dbt (数据构建工具)提出来的。我使用 dbt 来构建数据仓库和模型,供业务分析师和执行报告在下游使用。dbt 是一个非常棒的工具,它有很多我不会在本文中介绍的好处。尽管对于 dbt 的所有优点来说,我目前并不喜欢为生产准备 dbt 代码的过程。
我们有一个标准的 dbt 命令链,希望在打开 PR 进行审查之前运行。该链通常如下所示,可能需要大约 20-30 分钟来运行,具体取决于我们拥有的模型和测试的数量:
dbt deps
dbt seed
dbt snapshot
dbt run
dbt run-operation {{ macro_name }}
dbt test
即使错过这些命令中的一个,也可能导致生产管道中的故障,从而导致下游消费者的大规模中断。
虽然我知道开发和测试干净的代码有多重要,但我不想等着输入每一个命令并希望它们成功。我更愿意提交我的提交,打开一个 PR,让一个自动化的工作流为我处理这个过程,同时我开始一个新的任务。然后我可以稍后回来检查运行的结果。
为了实现这一点,我构建了一个自动化的 CI 工作流,它利用了一个 Docker 容器,该容器可以启动、安装 dbt 及其依赖项,并通过 GitHub 操作运行我的存储库中可用的模型和测试。
要求
- 用模型和数据源配置的现有 dbt 项目(我使用的是雪花)
- GitHub 回购
- Dockerfile 文件
码头集装箱
我将python:3.9-bullseye作为 Docker 容器的基础图像。从那里,我将 dbt 目录复制到容器中,安装所需的 dbt 包,并添加构建参数来处理 dbt 的环境雪花凭证*。
在文件的后半部分,我将目标环境切换到test并执行 PR 批准所需的必要 dbt 命令。
传入构建参数,然后将这些值设置为 DBT 的容器环境变量,这似乎有些重复,但是我无法找到一种方法来使用 docker 构建命令设置环境变量,就像您可以使用构建参数一样。如果你对此有更好的想法,那么我们来聊聊吧!
工作流文件
接下来,应该构建 docker 容器,并在打开 pull 请求和添加其他提交时运行列出的 dbt 命令。然而,我只希望这个动作在/dbt目录中发生变化时执行。
为了执行这个工作流文件,我建议对您的 dbt 数据源凭证使用类似的存储库机密。这个文件应该放在项目根目录下的.github/workflows目录中。
有了这两个文件,您的项目应该可以开始了!每当打开包含对/dbt目录所做更改的 PR 时,就会触发一个工作流来启动 Docker 容器并运行 Docker 文件中列出的指定 dbt 命令。
这是为数据仓库构建可靠的 CI(持续集成)管道的第一步。CI 的好处可以包括使开发更容易、更快、风险更小。在将变更推向生产之前,对其进行彻底的测试,这可以建立信任,并获得涉众和开发团队的支持。我强烈建议在您的数据仓库生命周期中实现和自动化 CI 流程(如果还没有实现的话)。
如果你对这篇文章的内容有其他问题,请联系我,如果我的其他一些帖子引起了你的注意,请查看一下!
*注意:我只是在一个私有的 org 存储库中构建带有 GitHub 动作的 Docker 容器。我能够管理该存储库的适当机密,并使用这些机密来存储该配置项的凭据。我不建议将你的容器推送到注册中心,因为在存储库机密和凭证方面存在潜在的安全隐患。
用 pip-compile-multi 结束 Python 依赖地狱
原文:https://towardsdatascience.com/end-python-dependency-hell-with-pip-compile-multi-56eea0c55ffe
保持项目的可复制性和复杂的 Python 依赖关系的组织性

约翰·巴克利普在 Unsplash 上的照片
大多数重要的 Python 项目都有复杂的依赖关系管理需求,普通的开源解决方案不足以解决这些需求。一些工具试图解决整个打包体验,而另一些工具旨在解决一两个狭窄的子问题。尽管有无数的解决方案,开发人员仍然面临着相同的依赖关系管理挑战:
- 新用户和贡献者如何轻松正确地安装依赖项?
- 我如何知道我的依赖项都是兼容的?
- 我如何使构建具有确定性和可重复性?
- 我如何确保我的部署工件使用一致和兼容的依赖关系?
- 我如何避免依赖性膨胀?
这篇文章将主要使用[pip-compile-multi](https://pip-compile-multi.readthedocs.io/en/latest/)来回答这些问题,这是一个开源命令行工具,它扩展了流行的[pip-tools](https://pip-tools.readthedocs.io/en/latest/)的功能,以满足具有复杂依赖关系的项目的需求。
锁定文件的问题是
部分解决方案是维护一个依赖关系锁文件,像[poetry](https://python-poetry.org/docs/cli/#lock)和[pip-tools](https://pip-tools.readthedocs.io/en/latest/#example-usage-for-pip-compile)这样的工具可以实现这一点。我们可以把 lockfile 想象成一个“依赖接口”:一个告诉项目需要什么外部依赖才能正常工作的抽象概念。为你的整个项目拥有一个单一的整体锁文件的问题是,作为一个接口,它不是良好分离的:为了确保兼容性、确定性和可再现性,代码的每个消费者(用户、开发人员、打包系统、构建工件、部署目标)将需要安装锁文件枚举的每个单一依赖项——无论他们实际上是否使用它。例如,如果您曾经努力将您的林挺和测试库从您的产品构建中分离出来,您就会遇到这个问题。
由此导致的依赖性膨胀可能是一个真正的问题。除了不必要的膨胀的构建时间和包/工件大小之外,它还增加了您的项目或应用程序中安全漏洞的表面积。

我在一个使用安全的项目中发现的漏洞。
更好的解决方案
理想情况下,我们可以将依赖接口重组为多个更窄的接口——多个锁文件:
- 按功能分组相关性
- 可以互相组合
- 可以独立消费
- 是相互兼容的
如果我们能做到这一点,事情就会变得简单:
- 了解在哪里使用哪些依赖项
- 包装变型(例如定义 pip 附加
- 多阶段构建(例如 Docker 多阶段
幸运的是,[pip-compile-multi](https://pip-compile-multi.readthedocs.io/en/latest/)做到了以上几点!这是一个轻量级的、pip可安装的 CLI,构建在优秀的[pip-tools](https://pip-tools.readthedocs.io/en/latest/)项目之上。您只需将您的requirements.txt文件分割成一个或多个 pip 需求文件(通常带有后缀.in)。每个文件可能包含一个或-r / --requirement选项,这些选项将文件链接成一个有向无环图(DAG)。依赖关系的 DAG 表示是pip-compile-multi的核心。
例子
比方说你的requirements.txt长这样:
# requirements.txt
flake8
mypy
numpy
pandas
torch>1.12
第一步是将这些依赖项分成功能组。我们将一组写到main.in,另一组写到dev.in。我们现在应该删除我们的requirements.txt。我们的两个新的.in文件可能看起来像这样,形成一个简单的双节点依赖 DAG:

一个简单的双节点依赖 DAG。主项目依赖项放在“main.in”中,我们的代码链接和相关的开发工具放在“dev.in”中。这使我们的依赖项保持逻辑分组。
每个节点是一个定义依赖组的.in文件。每个有向边代表一个组对另一个组的需求。每个节点用一个或多个-r / --requirement选项定义自己的入边。
一旦我们定义了这个依赖 DAG,运行pip-compile-multi将生成一个等价的 lockfile DAG。该工具将为 DAG 中的每个.in输出一个.txt pip 需求文件。

pip-compile-multi 编译的锁文件 DAG。我已经删除了这些锁文件中自动生成的行内注释,但实际上您永远不需要手动编辑它们。
默认情况下,生成的锁文件将创建在与.in文件相同的目录中,并镜像它们的名称。
自动解决跨文件冲突
将pip-compile-multi与其他 lockfiles 工具如pip-tools区分开来的杀手级特性是自动解决跨文件冲突,使用--autoresolve标志可以轻松实现。在自动解决模式下,pip-compile-multi将首先预解决所有依赖关系,然后使用该解决方案来约束每个节点的单个解决方案。这通过防止临时依赖关系中的任何冲突来确保每个锁文件保持相互兼容。为了使用自动解析,您的 DAG 必须正好有一个源节点(请注意,pip-compile-multi 文档反转了 DAG 边的方向,因此当我说 source 时,它们将指 sink 节点,反之亦然)。
锁定文件验证
另一个有用的命令是[pip-compile-multi verify](https://pip-compile-multi.readthedocs.io/en/latest/features.html#check-that-pip-compile-multi-was-run-after-changes-in-in-file),它检查您的锁文件是否与您的.in文件中指定的相匹配。这是一个简单而有价值的检查,您可以轻松地将其合并到您的 CICD 管道中,以防止错误的依赖关系更新。它甚至可以作为预提交钩子使用!
充分利用 pip-compile-multi 的技巧
适当地组织依赖关系
如果你把你的依赖关系分组的很差,你就是在为失败做准备。尝试基于代码中依赖项的预期功能来定义组:不要将flake8(一个代码 linter)与torch(一个深度学习框架)放在一个组中。
具有单个源节点和单个接收器节点
我发现,当您可以将最普遍的依赖项组织成一个所有其他节点都需要(一个汇聚节点)的单一“核心”依赖项集,并将所有开发依赖项组织成一个节点需要所有其他节点(直接或间接)都需要(一个源)的节点时,效果最好。这种模式使您的 DAG 相对简单,并确保您可以使用 pip-compile-multi 强大的自动解析功能。
启用画中画缓存
设置[--use-cache](https://pip-compile-multi.readthedocs.io/en/latest/features.html#use-cache)标志可以显著提高pip-compile-multi的速度,因为它支持在对pip-compile的底层调用中进行缓存。
真实世界的例子
为了让事情更清楚,我们来看一个机器学习领域的例子。
一个典型的机器学习系统将至少有两个组件:一个训练工作负载,它在一些数据上创建一个模型,以及一个推理服务器,它为模型预测提供服务。
这两个组件都有一些共同的依赖项,比如用于数据处理和建模的库。我们可以在一个名为main.in的文本文件中列出这些,它只是一个 pip 需求文件:
# requirements/main.in
pandas
torch>1.12
训练组件可能对分布式通信、实验跟踪和度量计算有一些特殊的依赖性。我们会把这些放在training.in:
# requirements/training.in
-r main.in
horovod
mlflow==1.29
torchmetrics
注意我们添加了-r标志,它告诉 pip-compile-multitraining.in需要来自main.in的依赖项。
推理组件将有一些服务和监控的专有依赖项,我们将其添加到inference.in:
# requirements/inference.in
-r main.in
prometheus
torchserve
最后,整个代码库共享相同的开发工具链。这些开发工具,比如 linters、单元测试模块,甚至pip-compile-multi本身都在dev.in中:
# requirements/dev.in
-r inference.in
-r training.in
flake8
pip-compile-multi
pytest
再次注意,指示dev.in的-r标志依赖于training.in和inference.in。我们不需要一个-r main.in,因为training.in和inference.in已经有了。
总的来说,我们的依赖 DAG 如下所示:

一个四节点依赖 DAG。
假设我们的.in文件位于名为requirements/的目录中,我们可以使用以下命令来解析我们的 DAG 并生成锁文件:
pip-compile-multi --autoresolve --use-cache --directory=requirements
该命令成功后,您会在requirements/中看到四个新文件:main.txt、training.txt、inference.txt和dev.txt。这些是我们的锁档。我们可以像使用有效的requirements.txt文件一样使用它们。也许我们可以用它们来构建高效的 Docker 多级图像目标:
或者,我们可能是安装环境的新项目贡献者。我们可以简单地运行pip install -r requirements/dev.txt(或者更好:pip-sync requirements/dev.txt)在“开发”模式下安装项目,包括所有的开发依赖项。
结论
用于管理 Python 依赖关系的工具选项数量惊人。很少有工具能够很好地支持按功能划分依赖关系,我认为这已经成为一个常见的项目需求。虽然pip-compile-multi不是灵丹妙药,但是它支持优雅的依赖分离,并且将它添加到您的项目中非常简单!
除非另有说明,所有图片均为作者所有。
基于端到端注意力的最小张量流代码机器翻译模型
这篇博文介绍了仅使用高级 Tensorflow API 的基于注意力的机器翻译的训练和推理。这是一个简化的端到端教程,旨在减少代码行。
关于这个话题有很多在线教程。但大多数都是包含大量样板代码的 Tensorflow 官方教程的变种。虽然理解它是如何使用低级 API 实现的是有帮助的,但是容易获得和简化的实现通常是受欢迎的——初学者可以使用它来快速形成对主题的直觉;从业者可以很容易地借用自包含的代码片段。这篇博文提供了一个简化的基于注意力的机器翻译代码的端到端实现,仅使用高级 Tensorflow Keras 层实现。
翻译任务
我们将执行的翻译任务与 Tensorflow 官方教程中的任务相同。我们要把西班牙语翻译成英语。我们将重用与 Tensorflow 官方教程中相同的数据集,它是西班牙语和英语对的列表,存储在一个文本文档中,每对一行。我们下载数据集并解压。注意,我们还需要用于文本处理的tensorflow_text包。
$ curl [http://storage.googleapis.com/download.tensorflow.org/data/spa-eng.zip](http://storage.googleapis.com/download.tensorflow.org/data/spa-eng.zip) –output spa-eng.zip
$ unzip spa-eng.zip
$ pip install –user tensorflow-text
预处理
文本预处理是任何自然语言文章中不可或缺的一步。如果您有兴趣了解各种文本预处理技术的影响,请随意查看这篇博客文章了解更多细节。我们利用TextLineDataset API 直接构建数据集,避免了 Tensorflow 官方教程中的大量样板代码。
数据加载
现在数据集已经可以使用了,我们使用TextVectorization层来索引文本中的记号。这部分和 Tensorflow 官方教程几乎一模一样。目标是为西班牙语和英语构建各自的词汇集,这样我们就可以将标记映射到索引,稍后将使用索引进行嵌入。
文本预处理
模型架构
这篇博文的核心是:模型。在进入模型代码之前,让我们看看模型架构的高级视图。该模型是典型的序列对序列模型。西班牙语输入被送入双向递归神经网络,我们称之为双向编码器。双向编码器的最终状态s成为解码器的初始状态。双向编码器h的输出由注意层aij加权,并与英文输入组合。我们使用教师强制机制来训练解码器,即解码器的英语输入来自上一步的预期(而不是实际)解码器输出。众所周知,这种机制可以加快训练速度,因为各个步骤可以通过正确的输入独立学习,而不是依赖于来自其先前步骤的潜在虚假输入。这也使得实现更加容易。解码器输出最终的英语。请参见下图进行概述。

模型架构
注意,解码器的英语输入具有[START]标记,解码器的英语输出具有[END]标记。一个用于启动文本生成,另一个用于终止文本生成。英语输入/输出内容也移动一步。双向编码器的西班牙语输入具有[START]和[END]标记。这仅仅是因为我们为两种语言重用了相同的standardize函数。理论上,西班牙语输入不需要这两个标记。
在推理时间,我们不再有预期的英语作为输入。因此,我们必须使用上一步中解码器的输出,一步一步地生成输出序列。
型号代码
模型代码实现为 Tensorflow Keras 模型的子类。请参见接口的以下代码片段。
模型界面
在初始化过程中,我们为模型构建了必要的层,包括西班牙语和英语的TextVectorization层、西班牙语和英语的Embedding层、西班牙语编码器的Bidirectional层、英语解码器的LSTM层、编码器和解码器之间的Attention层,以及将解码器的输出映射到英语词汇索引的最终Dense层。参见下面初始化函数的代码片段。
模型初始化
几个值得注意的点:Bidirectional层的内部LSTM有其他层的一半单元,因为Bidirectional层将连接向前和向后输出。Bidirectional编码器和LSTM解码器都设置为返回序列和状态,因为它们将在训练/推断过程中使用。两个Embedding层都将mask_zero设置为True,因为由于句子长度可变,我们将依靠蒙版来忽略 0 填充。
模型调用调用这些层并返回输出。请参见以下调用函数的代码片段。
模型调用
同样,有几点值得注意:Bidirectional层单独返回状态。所以我们需要把它们连接起来。解码器的英语输入是没有终止符[END]的英语句子。来自解码器的预期英语输出是没有起始标记[START]的英语句子。解码器的输出形状为(batch, Te-1, rnn_output_dim)。eng_vocab_size单元的最终输出Dense层足够智能以产生形状(batch, Te-1, eng_vocab_size)的输出。最初由嵌入层创建的掩码通过所有层传递,有时是隐式的(在递归神经网络中),有时是显式的(在注意层和最终函数返回中)。
训练代码
训练代码遍历成批的句子对,收集模型的输出,计算损失,并应用梯度下降。有关培训部分,请参见下面的代码片段。最重要的一点是,在损失计算中,我们需要屏蔽掉不相关的元素——因为它们只是可变长度句子的填充。
模特培训
经过 50 个时期后,我们可以看到模型损耗接近于零。这意味着我们的模型有能力过度拟合数据,这是模型能力的一个很好的指标。这里可以应用许多正则化技术来保证模型的泛化能力。但是这超出了这篇博文的范围。

培训损失
推理代码
推理代码与模型调用代码非常相似。关键的区别在于,我们需要在推理过程中一步一步地生成英语输出,而不是一次生成全部,因为我们需要上一步的解码器输出。有关推理代码,请参见以下代码片段。注意,推理代码还返回注意力矩阵,我们可以用它来可视化西班牙语输入和英语输出之间的模型注意力。
模型推理
最后,让我们为随机选取的西班牙语输入“Hoy tengo la tarde libre, así que pienso ir al parque, sentarme bajo un árbol y leer un libro.”调用translate推理函数。输出是“i have the afternoon off today , so i plan to go to the park , sit under a tree and read a book .”,它与数据集中的英文输出完全相同,只是由于我们的文本预处理,去掉了字母大小写和标点符号空间。不奇怪。正如我们上面讨论的,模型是过度拟合的。让我们也使用一个类似于 Tensorflow 官方教程中的 helper 函数的plot_attention函数来可视化注意力。
注意力绘图

注意力图
端到端 BigQuery 机器学习
原文:https://towardsdatascience.com/end-to-end-bigquery-machine-learning-e7e6e2e83b34
使用 Google Cloud BigQuery 参加 Kaggle 竞赛
我将向您展示如何仅使用 BigQuery 和 Kaggle API 来预测泰坦尼克号灾难的幸存者。自从我遇到 BigQuery,我就成了它的粉丝。它很容易使用,超级快,超级便宜——如果你用得好的话…
它不仅可以进行 Pb 级的复杂分析,还可以运行一些机器学习模型,所有这些都来自 SQL。我不建议你对所有的工作都这样做,但对于某些任务, BigQuery 是机器学习的理想选择,因为它的语法简单,成本低,而且因为它是一个完全托管的服务,你不需要担心它在哪里运行或者如何运行。
我们将执行以下操作:
- 使用 CLI 工具从 Kaggle 下载数据集
- 将其作为 BigQuery 表上传
- 将数据分为训练和测试
- 训练逻辑回归模型
- 向 Kaggle 提交预测
- 在竞争中获得第一名——可能不会😅
要获得所有媒体文章的完整信息,包括我的文章,请考虑在此订阅。
准备好

杰里米·贝赞格在 Unsplash 上的照片
既然我们将使用 BigQuery,为什么需要安装任何东西呢?你所需要的只是一个浏览器和一个谷歌账户——哦,还有鼠标和键盘,你仍然需要做一些打字工作!
第一步是登录 Google Colab 。这是谷歌的一项免费服务,你可以在云端运行一些笔记本。我们将使用它从 Kaggle 获取数据——是的,您需要一个帐户——并调用 Google Cloud APIs 与 BigQuery 进行交互。
接下来,您需要 Kaggle API 密钥。查看文档的Authentication部分,这里是关于如何获得带有密钥的 json 文件。我们会将这些上传到 collab:
在 Colab 中运行这段代码后,您会看到一个弹出按钮,要求上传文件。确保选择从 Kaggle 下载的kaggle.json文件。
嘿,很快,你可以使用 Kaggle 命令行界面:

作者截图
我们还需要向 Google Cloud 认证我们的 Google Colab 会话。如果我们想使用 BigQuery,这是必要的。幸运的是,已经预装了一个超级简单的助手库:
请按照屏幕上的指示操作
如果我们能够调用一些 BigQuery 神奇的函数并引用正确的项目 id,那也是很有帮助的:
❗️如果你不确定如何在 BigQuery 中创建数据集,请查看这篇文章。
安装完成!✅
获取一些数据
现在我们在云端有了一台电脑,接入了 Google Cloud,还有 Kaggle 的 API,但是我们在 BigQuery 里还是没有数据。我们如何将 Kaggle 的数据导入 BigQuery?当然是通过调用 Kaggle API。下面几行代码将下载titanic数据集,并将其作为 csv 文件保存在我们的 Colab 机器上。有关数据集的更多信息,请查看 Kaggle 上的细节。我们还可以使用 Pandas 的to_gbq()将文件作为表格上传到 BigQuery。
我们可以使用%%bigquery ipython 魔术直接从 Colab 查询 BigQuery。

作者截图
不要忘记也保存测试数据,这是我们将要做的预测:
分割数据集

照片由乔·科恩拍摄——工作室在 Unsplash 上干燥 2.6
任何像样的 ML 书籍或课程都会告诉你应该将数据分成训练和测试数据集(你也可以为超参数调整做一个评估集)。用 Python 做到这一点很容易。你抓取 sklearn,然后做一个train_test_split(),它甚至保持两个数据集的类标签分布相同。为了在 BigQuery 中做到这一点,我们需要一些技巧,但不要太复杂。
首先,我们需要创建一个可重复的随机排序。我已经写了关于这个,但是主要结论是FARM_FINGERPRINT是你的朋友。如果你需要一个不同的种子,在把它们传递给函数之前,先把你的字符串加盐,也就是说,给你散列的每个 id 添加一个字符串。这里有一个例子:

作者截图
正如您所看到的,对于相同的输入,我们得到相同的散列,并且我们可以通过添加不同的 salt 字符串来得到新的散列。这对我们的乘客点餐来说再好不过了。
❗️如果您需要复习 BigQuery 中的采样,请查看这篇文章:
接下来,我们需要挑选 80%的幸存乘客和 80%的死亡乘客进入我们的训练集。其他人都将在测试集中结束。我们可以通过使用PERCENTILE_CONT分析功能来实现。这个将我们的散列整数转换成百分位数,所以如果这个百分位数小于 0.80,那么就意味着你是前 80%乘客中的一员。
以下是完整的问题,包括两个步骤——如果有不清楚的地方,请在评论中提问:

作者截图
感觉检查:110 / (110 + 439) ≈ 0.2 ✅
接下来,我们将新数据集保存到两个分区中,一个用于训练,一个用于测试:
请注意,我们可以为 BigQuery magic 使用
--destination_table参数,但是我们不能指定分区范围。或者,我们可以使用 BigQuery Python SDK 将结果保存为表格。
partition by子句是一个巧妙的技巧,它将我们的训练集从测试集中分离出来并节省资金。基本上,如果你通过过滤isTrain来查询这个表,那么 BigQuery 会知道不同分区的确切位置,并且你只需要为你选择的分区付费。您可以通过跳转到 BigQuery UI 并查看查询大小估计来确认这一点:

注意右上角不断变化的 KiBs 作者截屏
💰对于较大的数据集,这个技巧可以让你在较小的数据集上进行训练,而不需要两个不同的表,从而为你节省很多钱。
逻辑回归

训练时间到了!—karst en wine geart在 Unsplash 上的照片
现在是时候训练一些模型了。我们将从训练一个逻辑回归模型开始,我将尽力解释所有的参数。由于这仍然是 BQ,我们需要一些 SQL 来创建一个新的模型对象:
下面是一步步发生的事情:
- 我们的模型将被称为
ds.titanic_baseline。请注意,这看起来与任何其他表名完全一样 OPTIONS会告诉 BQ 创建什么模型,标签是什么等。SELECT将告诉 BQ 从哪个数据集训练模型。我们基本上是从准备好的表中选择所有的东西,减去passengerid,并且我们也将isTrain转换为布尔值,因为我们将在该列上分割我们的训练集和测试集。
在OPTIONS里面发生了很多事情:
MODEL_TYPE告诉 BQ 使用什么型号系列。这可以是逻辑回归、线性回归、k 均值聚类、时间序列模型、提升树甚至 AutoML。此处阅读更多内容。INPUT_LABEL_COLS是包含我们标签的列的列表。在我们的例子中,这告诉我们谁幸存,谁没有。DATA_SPLIT_METHOD和DATA_SPLIT_COLS齐头并进。我们想自己拆分数据,为此我们使用了isTrain列。EARLY_STOP如果测试集(isTrain = 0)误差没有减少,告诉 BQ 停止训练。L2_REG是我们模型的 L2 正则化参数。这有助于我们不过度拟合数据。随意尝试不同的值,找到最适合自己的模型。
训练完模型后,您可以进入 BigQuery UI 并检查模型训练的情况:

BigQuery UI 模型训练—作者截屏
一定要四处看看,因为那里有相当多的信息!
臣服于 Kaggle
我们有了一个模型和一套测试设备,现在是打分的时候了,享受我们 Kaggle 提交的荣耀。
为了给我们的测试集打分,我们需要ML.PREDICT方法。这将为数据集的每一行提供一个结构数组。是的,我知道结构数组,那是什么鬼东西,对吧?
这是为多类模型设计的,所以数组中每个标签有一个元素,然后标签作为结构的第一部分,预测概率作为第二部分。为了解开所有这些,我们需要在数组列上使用CROSS JOIN和UNNEST:
请务必阅读上面的评论,因为它们解释了正在发生的事情

如果一切顺利,我们应该会在最后得到一堆预测的“问题”——作者截图
Kaggle 希望我们提交一个包含两列的 csv 文件——一列用于乘客 id,一列用于预测类别(1 或 0)。因此,我们将选择一个阈值,我使用 0.4,并将 1 分配给所有乘客,分配给高于该阈值的所有乘客,将 0 分配给其他所有人。我们可以使用 Python 的 BigQuery 客户端 SDK 来完成这项工作:
您还可以使用 %%bigquery 魔法来保存结果
剩下的就是把这个数据帧保存为 csv 格式,然后上传到 Kaggle:

作者截图
以上给了我一个扎实的 0.75 作为比赛中的分数。这不是很好,但这是一个好的开始!
越来越花哨

加布里埃拉·克莱尔·马里诺在 Unsplash 上拍摄的照片
本教程到此结束,但是我邀请您尝试Google Cloud big query 的更多特性。例如:
- 如果我们把 L2 正则化参数改成别的什么呢?
- 我们能不能用
TRANSFORM从这些栏目中创造出更多的特色,也许年龄应该分时段,或者票价应该有所不同?(文档此处) - 如果我们用助推树 :
MODEL_TYPE='BOOSTED_TREE_CLASSIFIER’会怎么样?
对于其中的一些代码,请查看用于本文的笔记本。
感谢您花时间阅读本文!我主要写的是数据科学工具和 Julia,所以如果你喜欢这篇文章,请随意订阅!
使用机器学习构建信用记分卡的端到端指南
现实世界问题的开源工具系列

图片由 ibrahimboran 在 Unsplash 上拍摄
我正在开辟一个名为的新系列,为现实世界的问题提供开源工具。
在这个系列中,我将首先回顾一个开源工具,然后展示如何将它应用于现实世界的问题。在这个过程中,我会展示所有的编码,列出所有你需要知道的术语、定理、算法。
潜在受众:
- 想要在简历中加入一个完整的项目,并试图获得更多面试机会的学生
- 数据科学家/ML 工程师希望构建记分卡并投入生产
在这篇博客中,你将了解到:
- 通过 Python 使用机器学习构建记分卡
- 技能组合:逻辑回归,梯度推进,证据权重,信息值(IV) ,宁滨,卡方宁滨
构建信用记分卡是一个非常典型的行业级问题,比如
- 评估交易或客户的信誉以执行进一步的行动,例如发行信用卡或为信用卡公司的高信用客户提供余额转账优惠,
- 在电子商务平台给予高价值客户促销或额外权利,
- 提供良好的客户细分以接触到营销公司中的合适的人。
你需要建立一个系统给客户打分,还得跟非技术人员交代清楚,因为出了问题(虚惊一场)你就知道怎么跟经理/客户/业务方交代了。
简介:
这个博客的代码和数据可以在这里找到:
开源工具: Toad
Toad 是一个用于构建记分卡的生产即用库;它提供 EDA、特征工程和记分卡生成。其关键功能简化了最关键、最耗时的流程,如功能选择和精细宁滨。
数据集:信用卡客户数据集的默认值
描述可以在 卡格尔 上找到。原始数据集可以在 UCI 找到。
有 25 个变量:
- ID :客户 ID
- 23 个特征包括:限额余额、性别、学历、婚姻、年龄、还款、账单金额、上一次还款金额
- 标签:默认付款(1 =是,0 =否)
假设你是一家信用卡公司的数据科学家,为了促进信用卡的使用,你的经理告诉你为现有客户建立一个记分卡系统,向他们发送一些余额转移优惠。比方说 6 个月,年利率为 0%,费用为 3%。
你有这些客户的历史数据。你不想给那些经常违约的人寄钱,因为他们可能会拿了钱就跑了。6 个月后,贵公司将无法收回这些转账余额,这将成为贵公司的损失。你可能还需要雇佣一个讨债公司来讨回这些债务。这可不好。
所以一切都取决于你的记分卡系统!
我们开始吧。
1.数据预处理

由 gtomassetti 在 Unsplash 上拍摄的图像
在现实生活中,您可能需要使用类似 SQL 的查询从您公司的数据库中获取原始数据;这可能会花你很多时间。
在这篇博客中,我们将只使用 CSV 文件。
安装和导入软件包
import pkg_resources
import pip
installedPackages = {pkg.key for pkg in pkg_resources.working_set}
required = { 'pandas','numpy', 'matplotlib', 'seaborn','toad','pickle','sklearn'}
missing = required - installedPackages
if missing:
!pip install pandas
!pip install numpy
!pip install matplotlib
!pip install seaborn
!pip install toad
!pip install pickle
!pip install sklearn
import pandas as pd
from sklearn.metrics import roc_auc_score,roc_curve,auc,precision_recall_curve
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import GradientBoostingClassifier
import numpy as np
import glob
import math
import seaborn as sns
import matplotlib.pyplot as plt
import toad
import pickle
加载数据并检查默认标签速率
# use pandas to load the csv file
data = pd.read_csv('UCI_Credit_Card.csv')
# check the size of the data
data.shape
# check few lines
data.head()
#use the world 'label'
data['label']=data['default.payment.next.month']
data=data.drop(columns=['default.payment.next.month'])
#check the fraud proportion of the data
target_info(data['label'])
# set an exclude list for the scorecard package Toad
exclude_list = ['ID','label']
22%的欺诈率(违约人群),这是历史数据中相当高的违约率。

ai4 金融教育版权所有
训练和测试分割
在实际项目中,您可能希望按照日期/时间分割训练和测试,随机分割并不好,因为这会破坏时间序列,并可能导致过度拟合。
我们将使用用户 ID 作为进行拆分的时间。

ai4 金融教育版权所有
# use the ID column to split the train-test data
data.ID.describe()
train = data_split(data,start = 0, end=22500,date_col='ID')
test = data_split(data,start = 22500, end=172792,date_col='ID')

ai4 金融教育版权所有
2.特征过滤
首先,我们需要执行特征过滤,以去除具有低信息价值和高相关性的特征。
###feature filtering by missing value, IV & corrrelation:
##If the missing value rate is greater than the threshold, delete the feature
##If the correlation coefficient is greater than the threshold, delete the feature
##If the IV is smaller than the threshold, delete the features
train_selected, drop_lst= toad.selection.select(frame = train,
target=train['label'],
empty = 0.7,
iv = 0.02, corr = 1,
return_drop=True,
exclude=exclude_list)
print("keep:",train_selected.shape[1],
"drop empty:",len(drop_lst['empty']),
"drop iv:",len(drop_lst['iv']),
"drop corr:",len(drop_lst['corr']))

ai4 金融教育版权所有
因此,我们将总共保留 23 个特征,并删除 2 个特征[‘性’,‘婚姻’],因为它们具有低 IV 。
- 证据权重(WOE) —描述预测变量和二元目标变量之间的关系
- 信息值(IV) —基于 WOE 衡量关系的强度。行业水平是丢弃 IV 低于 0.02 的特性

# output the iv table to a dataframe
def output_iv_importance(train_selected,label_col):
feat_import_iv = toad.quality(train_selected,label_col,iv_only=True)
feat_import_iv=feat_import_iv['iv']
feat_import_iv = feat_import_iv.reset_index()
feat_import_iv.columns = ['name','iv']
return feat_import_iv
df_iv=output_iv_importance(train_selected,'label')
df_iv.head(30)
这是所有特性的 IV 排名。我们可以看到 PAY_0 的 IV 最高,这是有意义的,因为这个特征表示最近的还款状态。与支付状态和金额相比,教育和年龄的 IV 较低。

ai4 金融教育版权所有
3.特色宁滨
特征宁滨是将连续变量或数值变量转化为分类特征。
特色宁滨的优势:
- 它简化了逻辑回归模型,而降低了模型过度拟合的风险
- Logistic 回归是一个广义线性模型,表达能力有限;特征宁滨可以将非线性引入到模型中,可以提高模型的表达能力,有助于更好的模型拟合
- 离散化特征对异常数据非常鲁棒:比如年龄> 30 时 a 特征的值为 1,否则为 0。如果特征没有被离散化,异常数据点“300 岁”将影响模型拟合
- 它可以将空数据视为一个单独的类
功能宁滨的步骤:
****第一步。初始化:c = toad.transform.Combiner()
****第二步。训练宁滨:
c.fit(dataframe,
y = 'target',
method = 'chi',
min_samples = 0.05,
n_bins = None,
empty_separate = False)
- y :目标列
- ****方法:宁滨方法,支持 chi (卡方宁滨) dt (决策树宁滨)k 均值,分位数,步长(等步长宁滨)
- min_samples :每箱包含最少数量的样品,可以是号或**比例**
- ****n _ bin:bin 的数量;如果不可能划分这么多箱子,则将划分最大数量的箱子。
- 空 _ 分开:是否分开空箱
第三步。检查宁滨节点 : c.export()
第四步。手动调节宁滨 : c.load(dict)
第五步。应用宁滨结果:c . transform(data frame,labels=False)
- ****标签:是否将宁滨结果转换成盒子标签。如果假,输出 0,1,2 …(离散变量按比例排序),如果真输出 (-inf,0】,(0,10),【10,INF】。
import time
start = time.time()
combiner = toad.transform.Combiner()
# use the filtered features for training
# Use the stable chi-square binning,
# specifying that each bin has at least 5% data to ensure stability
# empty values will be automatically assigned to the best bin
combiner.fit(X=train_selected,
y=train_selected['label'],
method='chi',
min_samples = 0.05,
exclude=exclude_list)
end = time.time()
print((end-start)/60)
#output binning
bins = combiner.export()
宁滨结果:

ai4 金融教育版权所有
#apply binning
train_selected_bin = combiner.transform(train_selected)
test_bin = combiner.transform(test[train_selected_bin.columns])
#Fine tune bins
from toad.plot import bin_plot,badrate_plot
bin_plot(train_selected_bin,x='PAY_AMT1',target='label')
bin_plot(test_bin,x='PAY_AMT1',target='label')

ai4 金融教育版权所有
在该图中,柱状图代表相应 bin 中数据的比例;红线代表违约客户比例。****
我们需要确保宁滨具有单调性,这意味着线与没有突然跳跃或下降的趋势相同。
这个剧情看起来还行,如果有突然的跳跃或者下降,我们需要用 c.set_rules(dict) 结合宁滨。
比如说。
#setting rules
rule = {'PAY_AMT1':[['0', 'nan'],['1'], ['2'], ['3']]}
#Adjust binning
c.set_rules(rule)
4.转换为 WOE 并计算 PSI
WOE 转换在宁滨完成后执行。
步骤如下:
- 使用上面调整的组合器 c** 转换数据**
- 初始化 woe 转换t:t = toad . transform . woe transformer()
- ****训练t:t . fit _ transform训练并输出车列的 woe 转换数据
- ****目标:目标列数据(非列名)
- ****排除:不需要进行 WOE 变换的列。注意:所有列都将被转换,包括没有被宁滨的列,不需要被 WOE 转换的列将通过 exclude 删除,尤其是目标列。
- ****转换测试/OOT 数据:transfer . Transform
##transform to WOE
t=toad.transform.WOETransformer()
#transform training set
train_woe = t.fit_transform(X=train_selected_bin,
y=train_selected_bin['label'],
exclude=exclude_list)
#transform testing set
test_woe = t.transform(test_bin)
final_data_woe = pd.concat([train_woe,test_woe])

ai4 金融教育版权所有
计算 PSI
PSI(种群稳定性指数)反映了分布的稳定性。我们经常用它来筛选特征和评估模型稳定性。行业水平是丢弃 PSI 大于 0.2 的特性****

#get the feature name
features_list = [feat for feat in train_woe.columns if feat not in exclude_list]
#calculate PSI using toad
psi_df = toad.metrics.PSI(train_woe[features_list], test_woe[features_list]).sort_values(0)
#put into a dataframe
psi_df = psi_df.reset_index()
psi_df = psi_df.rename(columns = {'index' : 'feature',0:'psi'})
# features less than 0.25
psi005 = list(psi_df[psi_df.psi<0.25].feature)
# features geater than 0.25
psi_remove = list(psi_df[psi_df.psi>=0.25].feature)
# keep exclude list
for i in exclude_list:
if i in psi005:
pass
else:
psi005.append(i)
# remove features that are geater than 0.25
train_selected_woe_psi = train_woe[psi005]
off_woe_psi = test_woe[psi005]
# output our final data table
final_data_woe = pd.concat([train_selected_woe_psi,off_woe_psi])

ai4 金融教育版权所有
5.最终输出 IV
这一步是在 WOE 变换之后输出 IV,它与原始特征的 IV 有一点不同。
# output the IV
features_use = [feat for feat in final_data_woe.columns if feat not in exclude_list]
len(features_use)
df_iv=output_iv_importance(final_data_woe[features_use+['label']],'label')

ai4 金融教育版权所有
想法是获得具有最高 IV和最低 PSI** 的特征。**
6.模型调整

逻辑回归
信用记分卡建模过程中最常用的算法是逻辑回归。原因如下:
- ****简单线性关系:变量之间的关系是线性关系
- ****良好的可解释性:输入变量对目标变量的影响很容易得到
- ****给出概率而不是判别类:客户的特征信息(如婚姻、年龄、历史信用表现等。)可以整合转换成一个概率值,为预测客户好坏提供一个直观的依据。即价值越大,客户未来违约的概率越小。
- ****易于部署:测试、部署、监控、调优等。,都比较简单
def check_train_test_auc(x_train,y_train,x_test,y_test):
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(random_state=42,C= 0.1, penalty='l2', solver='newton-cg')
lr = LogisticRegression(class_weight='balanced')
lr.fit(x_train, y_train)
pred_train = lr.predict_proba(x_train)[:,1]
from toad.metrics import KS, AUC
print('train KS',KS(pred_train, y_train))
print('train AUC',AUC(pred_train, y_train))
pred_OOT =lr.predict_proba(x_test)[:,1]
print('Test KS',KS(pred_OOT, y_test))
print('Test AUC',AUC(pred_OOT, y_test))
from sklearn.metrics import confusion_matrix, accuracy_score, roc_auc_score, plot_roc_curve, classification_report
fig, ax = plt.subplots(figsize=(12, 8))
plot_roc_curve(lr, x_test, y_test, color='blue', ax=ax)
#train & test
check_train_test_auc(x_train = train_woe[features_use],y_train=train_woe['label'],
x_test =test_woe[features_use] ,y_test = test_woe['label'])

ai4 金融教育版权所有
我们可以看到,在训练 AUC 和测试 AUC 或训练 KS 和测试 KS 之间没有太大的差异。这意味着我们的模型不会过度拟合。
训练 GradientBoostingClassifier 并检查要素重要性表
查看 GBDT 模型的性能是否优于 LR,并将特征重要性表与 IV 进行比较。
def get_evaluation_scores(label, predictions):
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
from sklearn.metrics import balanced_accuracy_score
tp, fn, fp, tn = confusion_matrix(label,predictions,labels=[1,0]).reshape(-1)
print('True Positive:',tp)
print('True Negative:',tn)
print('False Positive:',fp)
print('False Negative:',fn)
accuracy = (tp+tn)/(tp+fn+fp+tn)
print('accuracy: ',accuracy)
recall = tp/(tp+fn)
print('(recall): ',recall)
precision = tp/(tp+fp)
print('(precision): ',precision)
#f1 score = 2*(P*R)/(P+R)
f1 = 2*precision*recall/(precision+recall)
print('F1 score: ',f1)
print(classification_report(label, predictions))
print('balanced_accuracy_score: ',balanced_accuracy_score(label,predictions))
return precision, recall
def evaluate_result(df_train,df_test,features_name):
from sklearn.ensemble import AdaBoostClassifier, GradientBoostingClassifier, RandomForestClassifier, ExtraTreesClassifier
import seaborn as sns
import matplotlib.pyplot as plt
start = time.time()
x_train = df_train[features_name]
y_train = df_train['label']
x_test = df_test[features_name]
y_test = df_test['label']
model = GradientBoostingClassifier(n_estimators=250,random_state=0)
model.fit(x_train,y_train)
predictions = model.predict(x_test)
get_evaluation_scores(label = y_test, predictions=predictions)
feat_importances = pd.Series(model.feature_importances_, index=features_name)
feat_importances=pd.DataFrame(feat_importances).reset_index()
feat_importances.columns=['feature_name','feature_importance']
feat_importances=feat_importances.sort_values(['feature_importance'],ascending=False)
import matplotlib.pyplot as plt
plt.figure(figsize=(15,15))
sns_plot1=sns.barplot(feat_importances.feature_importance,feat_importances.feature_name,estimator=sum)
plt.title("Features Importance",size=18)
plt.ylabel('', size = 15)
plt.tick_params(labelsize=18)
return feat_importances,model,x_train,y_train,x_test,y_test
fet_importance_GBDT_reason,model,x_train,y_train,x_test,y_test = evaluate_result(df_train=train_woe,
df_test=test_woe,
features_name=features_use)

AI4Finance-Foundation 版权所有

ai4 金融教育版权所有
从功能重要性表中我们可以看出,GBDT 非常重视 PAY_0 功能(64%)。
def plot_roc_pre_recall_curve(labels, probs):
from sklearn.metrics import precision_recall_curve
# Get ROC curve FPR and TPR from true labels vs score values
fpr, tpr, _ = roc_curve(labels, probs)
# Calculate ROC Area Under the Curve (AUC) from FPR and TPR data points
roc_auc = auc(fpr, tpr)
# Calculate precision and recall from true labels vs score values
precision, recall, _ = precision_recall_curve(labels, probs)
plt.figure(figsize=(8, 3))
plt.subplot(1,2,1)
lw = 2
plt.plot(fpr, tpr, color='darkorange', lw=lw, label='ROC curve (area = %0.4f)' % roc_auc)
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend(loc="lower right")
plt.grid(True)
plt.subplot(1,2,2)
plt.step(recall, precision, color='orange', where='post')
# plt.fill_between(recall, precision, step='post', alpha=0.5, color='orange')
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.ylim([0.0, 1.05])
plt.xlim([0.0, 1.0])
plt.title('Precision Recall Curve')
plt.grid(True)
left = 0.125 # the left side of the subplots of the figure
right = 0.9 # the right side of the subplots of the figure
bottom = 0.1 # the bottom of the subplots of the figure
top = 0.9 # the top of the subplots of the figure
wspace = 0.5 # the amount of width reserved for blank space between subplots
hspace = 0.2 # the amount of height reserved for white space between subplots
plt.subplots_adjust(left, bottom, right, top, wspace, hspace)
plt.show()
probs = model.predict_proba(x_test)[:,1]
sns.set(font_scale = 1)
plot_roc_pre_recall_curve(y_test, probs)

ai4 金融教育版权所有
ROC 和精确回忆曲线看起来不错。
7.模型生产
让我们为逻辑回归训练我们的生产模型
#prepare train & test data
x_train = train_woe[features_use]
y_train=train_woe['label']
x_test =test_woe[features_use]
y_test = test_woe['label']
#Train LR
#lr = LogisticRegression(random_state=42,C= 0.1, penalty='l2', solver='newton-cg')
lr = LogisticRegression(class_weight = 'balanced')
lr.fit(x_train, y_train)
#check AUC
probs = lr.predict_proba(x_test)[:,1]
sns.set(font_scale = 1)
plot_roc_pre_recall_curve(y_test, probs)

ai4 金融教育版权所有
LR 的 AUC:0.7835
GBDT 的 AUC:0.7892
这些模型之间没有太大的区别。所以用 LR 建立记分卡是可以的。
8.记分卡调整
以下参数是记分卡优化中最重要的参数。
- base_score = 1000, base_odds = 35, pdo = 80,比率 = 2
实际意思是,当基础赔率为 35 时,基准分为 1000** ,当比基准高一倍时,基准分下降 80 分。**
# scorecard tuning
card = toad.ScoreCard(
combiner = combiner,
transer = t,
class_weight = 'balanced',
C=0.1,
base_score = 1000,
base_odds = 35 ,
pdo = 80,
rate = 2
)
card.fit(train_woe[features_use], train_woe['label'])
#inference on test data
test['CreditScore'] = card.predict(test)
test['CreditScore'].describe()
#output the scorecard
final_card_score=card.export()
len(final_card_score)
#transform the scorecard into dataframe and save to csv
keys = list(card.export().keys())
score_card_df = pd.DataFrame()
for n in keys:
temp = pd.DataFrame.from_dict(final_card_score[n], orient='index')
temp = temp.reset_index()
temp.columns= ['binning','score']
temp['variable'] = n
temp = temp[['variable','binning','score']]
score_card_df=score_card_df.append(temp)
score_card_df.head(30)

ai4 金融教育版权所有
这是我们的记分卡。一旦我们得到这个表,我们就可以把这个 CSV 文件扔给开发者,让他们开发一个服务来给每个客户打分。
但是我们还需要做更多的事情;对于一个典型的记分卡,我们需要有一个分数范围,每个范围代表一个信任级别。
这样,业务方人员更容易针对不同级别的客户采取行动。****
例如,我们可以这样设置信用等级:

ai4 金融教育版权所有
9.分布分析

我们需要绘制违约客户与优质客户的分数分布,以便划分信用等级(0 级到 8 级)。
plt.figure(figsize=(12,10))
import random
import numpy
from matplotlib import pyplot as plt
w = 40
n = math.ceil((data['CreditScore'].max() - data['CreditScore'].min())/w)
#bins = numpy.linspace(-10, 10, 100)
plt.hist(data[data.label==1].CreditScore, alpha=0.5, label='Black',bins = n)
plt.hist(data[data.label==0].CreditScore, alpha=0.5, label='White',bins = n)
plt.legend(loc='upper left')
plt.title('Credit Score Distribution: Test Set',size=15)
plt.show()

ai4 金融教育版权所有
黑色表示默认客户,白色表示好客户。
一个好的模型会清晰的分出黑/白分布。
最理想的分布是一个微笑的形状****
- 信用分数高的好客户->最右边
- 信用评分低的默认客户->最左边。
****我们当前的模型不能很好地分离这些分布。对我来说,我会回去探索更多的特性来增加的预测能力。但是我们总是可以先建立一个基线模型并在此基础上进行改进。
10.阈值调谐
我们需要针对不同的信用级别执行阈值调整,这是损失&覆盖范围之间的权衡。****
比方说,你的老板可以承受一些损失,但你必须为下个月覆盖 70%的好客户。
换句话说,你的目标是找到一个 ≤10%亏损和≥70%覆盖率的阈值。
def get_credit_level(
test,
target_score ='order_score',
out_col = 'order_level',
left_bound = -100,
level_0 = 100,
level_1 = 200,
level_2 = 250,
level_3 = 300,
level_4 = 350,
level_5 = 400,
level_6 = 450,
level_7 = 500,
level_8 = 800):
level = []
for i in range(len(test)):
if (test[target_score][i]>left_bound) & (test[target_score][i]<=level_0):
level.append(0)
elif (test[target_score][i]>level_0) & (test[target_score][i]<=level_1):
level.append(1)
elif (test[target_score][i]>level_1) & (test[target_score][i]<=level_2):
level.append(2)
elif (test[target_score][i]>level_2) & (test[target_score][i]<=level_3):
level.append(3)
elif (test[target_score][i]>level_3) & (test[target_score][i]<=level_4):
level.append(4)
elif (test[target_score][i]>level_4) & (test[target_score][i]<=level_5):
level.append(5)
elif (test[target_score][i]>level_5) & (test[target_score][i]<=level_6):
level.append(6)
elif (test[target_score][i]>level_6) & (test[target_score][i]<=level_7):
level.append(7)
elif (test[target_score][i]>level_7 )& (test[target_score][i]<=level_8):
level.append(8)
test[out_col] = level
return test
def plot_bts_level_loss(test, target_col):
bts_level_df = test[target_col].value_counts()
bts_level_df=pd.DataFrame(bts_level_df)
df_label_level= test[test.label==1].groupby(target_col)['label'].count()/ test.groupby(target_col)['label'].count()
df_label_level = pd.DataFrame(df_label_level)
bts_level_df.sort_index().plot.bar(title='')
df_label_level.plot()
test = get_credit_level(test,
target_score ='CreditScore',
out_col = 'CreditScore_level',
left_bound = -1000,
level_0 = 250,
level_1 = 300,
level_2 = 400,
level_3 = 500,
level_4 = 580,
level_5 = 630,
level_6 = 690,
level_7 = 730,
level_8 = 1000
)
plot_bts_level_loss(test,target_col='CreditScore_level')
def get_loss_coverage(test,target_level):
#level 5-Leve 8 Loss (percentage of default people)
L5_loss = test[test[target_level]>=5 ].label.value_counts()/len(test[test[target_level]>=5 ])
#level 5- level 8 Coverage (percentage of good people)
L5_coverage=test[test[target_level]>=5 ].label.value_counts()[0]/test[test.label==0].shape[0]
print("Level 5-Level 8: Loss is ",L5_loss[1], "; Coverage is ",L5_coverage)
#level 6-level 8 Loss
L6_loss=test[test[target_level]>=6 ].label.value_counts()/len(test[test[target_level]>=6 ])
#level 6-level 8 Coverage
L6_coverage=test[test[target_level]>=6].label.value_counts()[0]/test[test.label==0].shape[0]
print("Level 6-Level 8: Loss is ",L6_loss[1], "; Coverage is ",L6_coverage)

ai4 金融教育版权所有
该图显示了每个信用等级和该等级下信用违约率的分布。

ai4 金融教育版权所有

ai4 金融教育版权所有
通过检查每一层,你会有一个损失和保险表。例如,如果您向所有人 7501 ( L0-L8 )发送余额转移报价,您将遭受 21%的损失率(1548/7501) 。
要达到目标(损耗≤10%,覆盖率≥70%),需要挑选损耗 10.1%(347+120+34)/(2573+1570+796),覆盖率 75%(2226+1450+762)/5953。
基本上,下个月(假设测试集是下个月的数据)业务端的人会给6 级或以上的客户发送余额转移报价,总共 4939 个客户。****
11.手动测试我们的记分卡

下个月,我们能否向客户发送包含以下信息的余额转移报价?

ai4 金融教育版权所有
让我们手动检查记分卡。

ai4 金融教育版权所有
toad 的推断:

ai4 金融教育版权所有
通过查看我们的信用等级表:

ai4 金融教育版权所有
我们可以看到这个客户是8 级客户,信用完美。所以我们可以给他/她一个余额转移报价。
结论:
这篇博客介绍了基于开源 ML 工具 Toad 构建信用记分卡的端到端过程。
本博客讨论了以下 ML 流行语:
信息值(IV)、证据权重(WOE)、群体稳定性指数(PSI)、AUC(ROC 曲线下面积)、KS (Kolmogorov-Smirnov)、Logistic 回归(LR)、GBDT(梯度推进决策树)
对于未来的工作,作为一名数据科学家,您可以尝试使用其他 ML 模型来构建记分卡,如深度神经网络,以进一步提高准确性并降低假阳性率,从而提高客户满意度。
谢了。
来自《走向数据科学》编辑的注释: 虽然我们允许独立作者根据我们的 规则和指导方针 发表文章,但我们不认可每个作者的贡献。你不应该在没有寻求专业建议的情况下依赖一个作者的作品。详见我们的 读者术语 。
五分钟内完成端到端 Pytorch 模型
原文:https://towardsdatascience.com/end-to-end-pytorch-model-in-five-minutes-a72da7bd4ebb
标准化数据加载器、模型、培训和验证——因此您不必

如果您想在五分钟内编写一个 Pytorch 模型,需要经历四个步骤:
- 导入和预处理(数据集)数据,并对其进行批处理(数据加载器)
- 使用
nn.Module建立模型 - 编写一个训练循环并运行它
- 在验证集上验证
因为 MNIST 已经被做死了,我们将介绍如何导入火炬视觉数据集,并在五分钟内编写一些代码。由于这个原因,它不会很漂亮,但它会工作。
下载和导入数据
因为 MNIST 已经被做死了,我们将搜索标准的火炬视觉数据集,看看是否有其他我们想要尝试和预测的东西。让我们来看看 Kuzushiji-MNIST,它是 MNIST 数据集的平假名(日语)替代物,由 70,000 幅图像组成。你可以在论文 日本古典文学的深度学习 中了解创建数据集背后的动机。
首先,我们找到每个通道的均值和标准差。这背后的原因是,我们显然希望归一化我们的训练数据,但 Pytorch 变换需要预先给定归一化均值和标准差。因此,我们将使用数据集来查找这些值,然后重新导入它,并传递带有预定义值的规范化转换。
注意,kmnist是一个数据集,所以循环遍历它会在每个实例上给我们一个图像和一个标签。因此,如果我们循环遍历数据集中的每一幅图像,并沿着额外的第四维堆叠它们,我们将得到所有图像的张量。
我们现在计算每个通道的平均值。请注意,调用imgs.view(1,-1)会将所有张量挤入第二维,导致我们有一个尾随的第一维。因此,我们取第二维像素值的平均值(因此为dim=1)。我们也对标准差做同样的事情。
我们现在可以重新导入我们的数据,使用一个Normalize变换以及一个将数组转换为张量的变换。请注意,Normalize转换将像素值的平均值和标准偏差作为参数。
现在我们有了数据集,我们需要将这些数据输入到DataLoader中进行批量处理。如果你在 CPU 上,一定要设置小一点的批量,设置num_workers=1(这是 GPU 的事情,不用太在意)。
我们可以查看数据集中的一些样本。我不打算在这里遍历代码,它应该是不言自明的。

图片作者。
构建模型
这不是一个关于如何从理论上构建深度学习模型的教程。因此,我们将在这里展示这个模型,并且只评论三件事。首先,您需要将您的模型实例化为nn.Module的实例,方法是将其传递给该类。第二,你需要通过通常的 Python 方法初始化超类(教程这里)。最后,你需要一个模型初始化,在这里我们定义所有的模型层,然后是一个正向方法,告诉模型如何接受输入并通过这些层传递。仅此而已。
在这个阶段,通过从数据加载器中给模型一个例子来调试模型总是很重要的。然后,我们将这个图像传递给模型,并检查它是否输出了正确大小的东西。
完美。我们构建了一个模型,该模型采用 K-MNIST 图像,并输出 10 个类别,代表从 0 到 9 的每个可能数字的 10 种不同概率。
编写和运行训练循环
像往常一样,我们的训练步骤是老一套。向前传球。计算损失。重置梯度(Pytorch 专业)。反向传播计算相对于损失的梯度。用这些梯度更新我们的权重。仅此而已(记得将模型设置为train模式)。
然后,我们实例化我们的模型,设置 Adam 优化器,并使用交叉熵损失(因为这是一个多类分类问题)。如果你的问题需要你改变这些,优化器和损失函数分别在torch.optim和torch.nn中。
然后将这些参数传递给训练循环,让它运行。
验证模型
我们希望事情尽可能简单,所以我们要让我们的验证循环的结构反映训练循环。迭代验证数据加载器中的图像和标签。向前传递,通过找到输出张量中值最高的索引来获得预测(记住,我们输出 10 个概率的向量)。记住使用.data.squeeze()来获得实际的标量本身。最后,通过对预测等于标签的所有时间求和(使用np.sum()和.item()来避开梯度),除以标签的总数,打印出准确度。
验证集准确率为 95%。对于五分钟的编码来说还不错。
结论和进一步措施
您可以为自己做几件事来使您的模型变得更好:
- 在模型训练时打印验证集指标:显然,很高兴看到训练损失随着每个时期而减少。但是在我们训练后验证它之前,我们真的不知道模型的表现如何。如果您打印验证准确性,您将对模型的成功有更好的了解。
- 实施提前停止:一旦验证准确度在一定数量的时期内未能提高(称为耐心),则返回到最佳执行时期并使用这些权重。
- 看看其他指标:另一个真正强大的指标是曲线下面积(AUC),通过对所有类使用加权平均精确召回率,可以从二进制到多类分类采用该指标(有关这方面的介绍,请参见此处的)。
- 实现剩余网络架构:自从引入 CNN 以来,计算机视觉已经走过了漫长的道路。您可以尝试其他架构来提高性能。你甚至可能想试一试计算机视觉转换器。
仅此而已。希望这篇文章能够消除大多数 Pytorch 入门教程中包含的许多不必要的排场。我发现,一旦我知道了深度学习的理论基础,我真的希望有一种资源来帮助我自己构建这些模型的纯端到端过程。我希望这能为你提供这样的资源。
使用实时非侵入式负载监控的能量管理
如何在边缘使用机器学习构建可持续清洁能源的关键组成部分

罗伯特·林德在 Unsplash 上的照片
介绍
非侵入式负载监测(NILM)的目标是从总电源信号中恢复单个设备的能耗,总电源信号是建筑物或房屋总耗电量的一种度量。NILM 也被称为能量分解,这两个术语将在本文中互换使用。
R.Gopinath 等人在论文中很好地总结了 NILM 背后的理性:使用非侵入式负载监控技术的能源管理——最新技术和未来研究方向:
近年来,发展智能可持续城市已成为城市规划者和决策者负责任地利用资源、保护环境和改善社会福祉的首要重点。能源管理是智能可持续城市发展方案的一个组成部分,该方案涉及有意识和有效地利用现有能源资源,以实现能源系统的可持续性和自力更生。建筑行业是使用能源较多的关键行业之一。因此,人们正在努力有效地监控和管理住宅和商业建筑中的能源消耗。近年来,非侵入式负荷监测(NILM)技术已成为一种流行的新兴方法,用于使用单个电能表来监测建筑物中电器/电力设施的事件(开/关)和能耗。关于电器级别的能量消耗的信息将帮助消费者了解他们的电器使用行为,并采取必要的步骤来减少能量消耗。
通过使用在公开可用的数据集上训练的深度学习模型,现在可以通过利用商用硬件和开源软件以非常经济高效的方式实现 NILM。在电网的边缘,即建筑物层,部署训练模型导致额外的成本节约,因为它消除了对总是连接的互联网云服务的需要,这在规模上可能是昂贵的,并且实时运行算法提供了低延迟操作。
这篇文章将向你展示 NILM 是如何工作的,带你完成我在家里实现一个原型系统的步骤。
体系结构
下面的前两个图表从较高的层面说明了 NILM 的概念和流程步骤。最后一张图展示了我的原型系统,它基于这些概念,并使用基于 Arduino 和 Raspberry Pi 的计算来实现。

一般 NILM 概念(R. Gopinath 等人)

NILM 过程步骤(R. Gopinath 等人)

NILM 原型系统框图
NILM 算法选择和模型训练
算法选择
能量分解是一个非常欠定的单通道盲源分离(BSS)问题,这使得很难获得准确的预测。您需要从单个观察中提取多个来源。过去的方法包括因子隐马尔可夫模型(FHMM)和具有一些 success⁶.的各种基于事件的方法
您还可以通过使用神经网络的序列到序列(seq2seq)学习来解决单通道 BSS 问题,并且可以使用卷积和递归神经网络将其应用于 NILM 问题。Seq2seq 学习涉及训练一个深度网络,以在输入时间序列(如 NILM 的总电力读数)和输出序列(如单台设备的估计能耗)之间进行映射。滑动输入窗口通常用于训练网络,该网络产生相应的输出窗口。此方法为输出中的每个设备生成多个预测,因此预测的平均值用于最终结果。有些预测会比其他预测更准确,尤其是那些接近输入滑动窗口中点的预测。平均会降低预测的整体准确度。
对于单通道 BSS⁴.,seq2seq 学习的一些缺点可以通过序列到点学习(seq2point)来减轻在这种方法中还使用滑动输入信号窗口,但是网络被训练为仅在窗口的中点预测输出信号,这使得网络上的预测问题更容易,从而导致更准确的结果。
我为我的原型系统选择了 seq2point 学习方法,我的实现受到了 Michele DIncecco、Stefano Squartini 和 Mingjun Zhong⁵.描述的工作的启发和指导
数据集
有许多专门为解决 NILM 问题而设计的大规模公开可用数据集,这些数据集是在不同国家的家庭建筑中捕获的。下面的 table⁷展示了几种最广泛使用的。

NILM 数据集(奥利弗·帕森和 al.⁷)
数据集通常包括数百万个有功功率、无功功率、电流和电压样本,但采样频率不同,需要在使用前对数据进行预处理。大多数 NILM 算法仅利用有功或视在功率数据。能源分解研究通常考虑五种电器,它们是水壶、微波炉、冰箱、洗碗机和洗衣机。这些是我根据 Michele DIncecco 等人的工作为原型考虑的设备。 ⁵,我主要关注 REFIT⁸数据 et,但最终将包括 UK-DALE 和 REDD。
模特培训
我使用 TensorFlow 2 和 Keras APIs 来训练和测试模型。与这部分相关的所有代码都可以在该项目的 GitHub, NILM ⁹.的机器学习部分找到
用于器具的 seq2point 学习模型在 z 归一化改装数据上被单独训练。我使用以下四个指标来评估模型的性能。你可以在这里查看计算这些指标的代码。
- 平均绝对误差(MAE ),用于评估每个时间点的预测值与实际值之间的绝对差值,并计算平均值。
- 归一化信号总误差(SAE),表示总能量的相对误差。
- 每日能量(EpD ),用于测量一天中使用的预测能量的绝对误差,当家庭用户对一段时间内消耗的总能量感兴趣时非常有用。
- 标准化分解误差(NDE ),用于测量预测值和设备实际值之间的平方差的标准化误差。
我使用 Keras Functional API 创建了 seq2point 学习模型,如下面的 Python 函数所示。您可以在该项目的 GitHub 中找到替代的模型架构,但是这个目前给出了最好的结果。
seq2point 学习模型
集合表观功耗信号的 599 个样本的滑动窗口被用作 seq2point 模型的输入,并且电器的相应窗口的中点被用作目标。您可以在下面的代码片段中看到示例和目标是如何生成的。
窗口生成器
你可以在下面看到我用来训练模型的代码片段和完整的程序这里。为了训练,我使用了 Keras Adam 优化器,为了减少过度拟合,我使用了提前停止和 InverseTimeDecay 。训练代码可以配置为从头开始训练 seq2point 模型,或者给定一个拟合的模型,用量化感知训练(QAT)对其进行修剪或微调,这两种方法都可以提高推理性能,尤其是在边缘硬件上。
主要培训代码
训练和优化程序的超参数总结如下。
- 输入窗口大小:599 个样本
- 批量:1000 个样品。
- 从零开始学习率:0.001,具有逆时间衰减。
- QAT 和剪枝学习率:0.0001。
- 优化器:beta_1=0.9,beta_2=0.999,ε= 1e-08。
- 从无到有的早期停止标准:6 个时代。
训练程序监控训练和验证数据的均方误差(MSE)损失和验证数据的平均绝对误差(MAE ),并及早停止以减少过度拟合。数据集包含大量具有重复模式的样本(数以百万计);对于一些器具来说,仅在一个时期之后就出现过度适配的情况并不罕见。为了缓解这种情况,我使用了训练数据的子集,通常在 500 万到 1000 万个样本之间。
微波设备的典型从头训练运行的 MSE 和 MAE 结果如下所示。您可以看到,最小验证损失发生在第 10 个时期左右,此时训练程序保存为最终训练模型,并在第 16 个时期后提前停止。

微波训练和验证损失

微波验证
按照上述指标得出的典型模型性能结果可在附录中找到。我达到或超过了 Michele DIncecco 等人在工作中报告的结果。 ⁵
模型量化
我使用 TensorFlow Lite 将模型的权重和激活函数从 Float32 量化到 INT8,以提高边缘硬件上的推理性能,包括 Raspberry Pi 和 Google Edge TPU。量化后,您可能会观察到性能略有下降,但这对于大多数用例来说是可以接受的。典型结果如下所示,请注意,浮点模型在 INT8 转换之前既没有使用 QAT 进行微调,也没有进行修剪。
### Fridge Float32 vs INT8 Model Performance ###Float32 tflite Model (Running on x86 w/XNNPACK):
MAE 17.10(Watts)
NDE 0.373
SAE 0.283
Inference Rate 701.0 HzINT8 tflite Model (running on Raspberry Pi 4):
MAE: 12.75 (Watts)
NDE 0.461
SAE 0.120
Inference Rate 163.5 Hz
与此部分相关的所有代码都可以在该项目的 GitHub、 NILM ⁹的机器学习部分找到,特别是参见下面的模块convert _ keras _ to _ TF lite . py及其代码片段。
Keras 到 tflite 转换函数
NILM 原型系统组件
我在家里建造了一个 NILM 原型,在真实世界条件下测试能量分解算法,并了解它们可以改进的地方。原型由以下主要子系统组成。你可以在附录中看到原型的照片。
模拟信号调理
我在家里的一个子面板上使用了两个夹式电流互感器来感测流经每个分压相位的电流,并在断路器面板附近的插座上插入了一个电压互感器,以提供其中一个相位的电压。这些信号由该子系统进行电平转换、放大和低通滤波,然后传递给执行总指标计算的 Arduino MEGA 2560 内部的模数转换器。您可以在附录中看到模拟信号调理子系统的原理图。
聚合指标计算
我使用 Arduino MEGA 2560 来托管信号处理算法,该算法从模拟信号调理子系统获取电压信号,并实时产生总均方根电压、均方根电流、有功功率和视在功率指标。目前,在下游处理中仅使用有功功率。我利用了⁰的 emonLibCM 来实现这些信号处理算法。emonLibCM 在后台持续运行,对 Arduino 的模拟输入通道进行数字化处理,计算这些指标,然后通知 Arduino 草图测量可用,应由下游处理读取和处理。草图配置为每八秒更新一次指标,每次更新后执行模拟前端的自动增益控制,以优化信噪比。
你可以在下面和项目的 GitHub 中看到 Arduino 草图, NILM ⁹
Arduino 草图
分项能耗计算
实际的能源分解计算在一个 Raspberry Pi 4 上进行,它通过 USB 连接到 Arduino 以获取聚合指标。计算包括运行 tflite 设备推理模型,按照上述步骤进行训练和量化,以及预处理和后处理步骤。推理模型从集合实际功率输入信号的 599 个样本大小的窗口中输出每个设备的预测能量。这些预测存储在本地 CSV 文件中,可用于下游报告和分析。
下图显示了我家的典型分类能源预测结果,使用的是在另一台机器上根据上述数据集训练的 tflite 模型。

Float32 和 tflite 设备型号预测

放大冰箱和微波炉
没有对本地数据进行微调。水平轴是由八秒样本数(采样间隔是八秒)测量的时间,在本例中,它跨越了大约八天半。纵轴是以瓦特为单位的能耗。顶部曲线是总(两相)市电视在功率。以下轨迹是每个设备的预测能量。在此期间,参考设备组中只有一台冰箱和一台微波炉在使用,尽管同一子面板还提供了其他设备,如计算机和加热器。
可以看到,Float32 和 tflite 版本的算法都检测冰箱和微波炉,但两者之间存在能量偏移,这可能表明需要更好的校准。我用功率表计算了冰箱和微波炉的均方根功耗;它与 Float32 预测的误差在+/- 15%以内。
与此部分相关的所有代码都可以在该项目的 GitHub、 NILM ⁹的机器学习部分找到,特别是参见模块 infer.py 和下面来自它的执行实时预测的代码片段。
infer.py 主要
亚马逊 Alexa
我计划使用亚马逊 Alexa 作为设备能源数据的用户界面,但这项工作尚未开始。
结论
通过使用大型公开可用数据集来训练 seq2point 学习模型,执行相当准确的能量分解是非常可行的,而无需用本地数据微调模型。这些模型大小适中,精度损失很小,可以量化以在商用边缘硬件(如 Raspberry Pi 4)上有效运行。需要做更多的工作来进一步提高模型的准确性,并使用更多的设备类型进行测试。该项目表明,可持续和可扩展的电网的一个关键组成部分是大众消费市场所能达到的。
参考
- 可持续的城市和社会 62 (2020) 102411 |使用非侵入式负荷监测技术的能源管理 R. Gopinath、Mukesh Kumar、C. Prakash Chandra Joshua 和 Kota Srinivas 的最新技术和未来研究方向。
- 维基| 信号分离。
- arXiv:1507.06594 | 神经 NILM:应用于能量分解的深度神经网络作者:Jack Kelly 和 William Knottenbelt。
- arXiv:1612.09106 | 用于非侵入式负荷监控的神经网络序列点到点学习作者:、钟明军、王宗佐、Nigel Goddard 和 Charles Sutton。
- arXiv:1902.08835 |Michele DIncecco、Stefano Squartini 和 Mingjun Zhong 的《非侵入式负载监控的迁移学习》。
- 可扩展能源转换的人工智能技术第 109–131 页|基于机器学习的非侵入式负荷监控方法综述。
- 在第三届 IEEE 信号与信息处理全球会议上召开的第一届智能建筑信号处理应用国际研讨会| Dataport 和 NILMTK:专为非侵入式负载监控设计的建筑数据集作者:Oliver Parson、Grant Fisher、April Hersey、Nipun Batra、Jack Kelly、Amarjeet Singh、William Knottenbelt 和 Alex Rogers。
- 第八届国际家用电器和照明能效会议论文集| 个性化实时能源反馈数据管理平台作者:大卫·穆雷、廖婧、莉娜·斯坦科维奇、弗拉基米尔·斯坦科维奇、理查德·豪克斯韦尔·鲍德温、查理·威尔森、迈克尔·科尔曼、Tom Kane 和史蒂文·弗斯。本项目中使用的改装数据集是在知识共享署名 4.0 国际公共许可证下许可的。
- GitHub | NILM 作者林多·圣·安吉尔
- Trystan Lea,Glyn Hudson,Brian Orpin 和 Ivan 克拉韦茨。
附录
原型照片
下面是我的原型系统的早期版本的照片。

早期原型
模拟信号调理原理图
模拟信号调理电路原理图如下所示。

模拟信号调理原理图
模型训练结果
根据上述指标评估的典型模型性能如下。
### Dishwasher ###2022–06–05 12:48:09,322 [INFO ] Appliance target is: dishwasher
2022–06–05 12:48:09,322 [INFO ] File for test: dishwasher_test_H20.csv
2022–06–05 12:48:09,322 [INFO ] Loading from: ./dataset_management/refit/dishwasher/dishwasher_test_H20.csv
2022–06–05 12:48:10,010 [INFO ] There are 5.169M test samples.
2022–06–05 12:48:10,015 [INFO ] Loading saved model from ./models/dishwasher/checkpoints.
2022–06–05 12:48:35,045 [INFO ] aggregate_mean: 522
2022–06–05 12:48:35,046 [INFO ] aggregate_std: 814
2022–06–05 12:48:35,046 [INFO ] appliance_mean: 700
2022–06–05 12:48:35,046 [INFO ] appliance_std: 1000
2022–06–05 12:48:35,121 [INFO ] true positives=5168007.0
2022–06–05 12:48:35,121 [INFO ] false negatives=0.0
2022–06–05 12:48:35,121 [INFO ] recall=1.0
2022–06–05 12:48:35,173 [INFO ] true positives=5168007.0
2022–06–05 12:48:35,173 [INFO ] false positives=0.0
2022–06–05 12:48:35,173 [INFO ] precision=1.0
2022–06–05 12:48:35,173 [INFO ] F1:1.0
2022–06–05 12:48:35,184 [INFO ] NDE:0.45971032977104187
2022–06–05 12:48:35,657 [INFO ]
MAE: 10.477645874023438
-std: 104.59112548828125
-min: 0.0
-max: 3588.0
-q1: 0.0
-median: 0.0
-q2: 0.78582763671875
2022–06–05 12:48:35,675 [INFO ] SAE: 0.1882956475019455
2022–06–05 12:48:35,691 [INFO ] Energy per Day: 145.72999548734632### Microwave ###2022-06-05 18:14:05,471 [INFO ] Appliance target is: microwave
2022-06-05 18:14:05,471 [INFO ] File for test: microwave_test_H4.csv
2022-06-05 18:14:05,471 [INFO ] Loading from: ./dataset_management/refit/microwave/microwave_test_H4.csv
2022-06-05 18:14:06,476 [INFO ] There are 6.761M test samples.
2022-06-05 18:14:06,482 [INFO ] Loading saved model from ./models/microwave/checkpoints.
2022-06-05 18:14:38,385 [INFO ] aggregate_mean: 522
2022-06-05 18:14:38,385 [INFO ] aggregate_std: 814
2022-06-05 18:14:38,385 [INFO ] appliance_mean: 500
2022-06-05 18:14:38,385 [INFO ] appliance_std: 800
2022-06-05 18:14:38,478 [INFO ] true positives=6759913.0
2022-06-05 18:14:38,478 [INFO ] false negatives=0.0
2022-06-05 18:14:38,478 [INFO ] recall=1.0
2022-06-05 18:14:38,549 [INFO ] true positives=6759913.0
2022-06-05 18:14:38,549 [INFO ] false positives=0.0
2022-06-05 18:14:38,549 [INFO ] precision=1.0
2022-06-05 18:14:38,549 [INFO ] F1:1.0
2022-06-05 18:14:38,568 [INFO ] NDE:0.6228251457214355
2022-06-05 18:14:39,469 [INFO ]
MAE: 7.6666789054870605
-std: 73.37799835205078
-min: 0.0
-max: 3591.474365234375
-q1: 0.757080078125
-median: 1.178070068359375
-q2: 1.459686279296875
2022-06-05 18:14:39,493 [INFO ] SAE: 0.2528369128704071
2022-06-05 18:14:39,512 [INFO ] Energy per Day: 99.00535584592438### Fridge ###2022-06-06 05:14:39,830 [INFO ] Appliance target is: fridge
2022-06-06 05:14:39,830 [INFO ] File for test: fridge_test_H15.csv
2022-06-06 05:14:39,830 [INFO ] Loading from: ./dataset_management/refit/fridge/fridge_test_H15.csv
2022-06-06 05:14:40,671 [INFO ] There are 6.226M test samples.
2022-06-06 05:14:40,677 [INFO ] Loading saved model from ./models/fridge/checkpoints.
2022-06-06 05:15:11,539 [INFO ] aggregate_mean: 522
2022-06-06 05:15:11,539 [INFO ] aggregate_std: 814
2022-06-06 05:15:11,539 [INFO ] appliance_mean: 200
2022-06-06 05:15:11,539 [INFO ] appliance_std: 400
2022-06-06 05:15:11,649 [INFO ] true positives=6225098.0
2022-06-06 05:15:11,649 [INFO ] false negatives=0.0
2022-06-06 05:15:11,649 [INFO ] recall=1.0
2022-06-06 05:15:11,713 [INFO ] true positives=6225098.0
2022-06-06 05:15:11,713 [INFO ] false positives=0.0
2022-06-06 05:15:11,713 [INFO ] precision=1.0
2022-06-06 05:15:11,713 [INFO ] F1:1.0
2022-06-06 05:15:11,728 [INFO ] NDE:0.390367716550827
2022-06-06 05:15:12,732 [INFO ]
MAE: 18.173030853271484
-std: 22.19791030883789
-min: 0.0
-max: 2045.119873046875
-q1: 5.2667236328125
-median: 12.299388885498047
-q2: 24.688186645507812
2022-06-06 05:15:12,754 [INFO ] SAE: 0.3662513792514801
2022-06-06 05:15:12,774 [INFO ] Energy per Day: 219.63657335193759### Washing Machine ###2022-06-05 05:49:17,614 [INFO ] Appliance target is: washingmachine
2022-06-05 05:49:17,614 [INFO ] File for test: washingmachine_test_H8.csv
2022-06-05 05:49:17,614 [INFO ] Loading from: ./dataset_management/refit/washingmachine/washingmachine_test_H8.csv
2022-06-05 05:49:18,762 [INFO ] There are 6.118M test samples.
2022-06-05 05:49:18,767 [INFO ] Loading saved model from ./models/washingmachine/checkpoints.
2022-06-05 05:49:47,965 [INFO ] aggregate_mean: 522
2022-06-05 05:49:47,965 [INFO ] aggregate_std: 814
2022-06-05 05:49:47,965 [INFO ] appliance_mean: 400
2022-06-05 05:49:47,965 [INFO ] appliance_std: 700
2022-06-05 05:49:48,054 [INFO ] true positives=6117871.0
2022-06-05 05:49:48,055 [INFO ] false negatives=0.0
2022-06-05 05:49:48,055 [INFO ] recall=1.0
2022-06-05 05:49:48,115 [INFO ] true positives=6117871.0
2022-06-05 05:49:48,115 [INFO ] false positives=0.0
2022-06-05 05:49:48,115 [INFO ] precision=1.0
2022-06-05 05:49:48,115 [INFO ] F1:1.0
2022-06-05 05:49:48,128 [INFO ] NDE:0.4052383601665497
2022-06-05 05:49:48,835 [INFO ]
MAE: 20.846961975097656
-std: 155.17930603027344
-min: 0.0
-max: 3972.0
-q1: 3.0517578125e-05
-median: 0.25152587890625
-q2: 1.6888427734375
2022-06-05 05:49:48,856 [INFO ] SAE: 0.3226347267627716
2022-06-05 05:49:48,876 [INFO ] Energy per Day: 346.94591079354274### Kettle ###2022-05-25 15:19:15,366 [INFO ] Appliance target is: kettle
2022-05-25 15:19:15,366 [INFO ] File for test: kettle_test_H2.csv
2022-05-25 15:19:15,366 [INFO ] Loading from: ./dataset_management/refit/kettle/kettle_test_H2.csv
2022-05-25 15:19:16,109 [INFO ] There are 5.734M test samples.
2022-05-25 15:19:16,115 [INFO ] Loading saved model from ./models/kettle/checkpoints.
2022-05-25 15:19:44,459 [INFO ] aggregate_mean: 522
2022-05-25 15:19:44,459 [INFO ] aggregate_std: 814
2022-05-25 15:19:44,459 [INFO ] appliance_mean: 700
2022-05-25 15:19:44,459 [INFO ] appliance_std: 1000
2022-05-25 15:19:44,540 [INFO ] true positives=5732928.0
2022-05-25 15:19:44,540 [INFO ] false negatives=0.0
2022-05-25 15:19:44,540 [INFO ] recall=1.0
2022-05-25 15:19:44,597 [INFO ] true positives=5732928.0
2022-05-25 15:19:44,597 [INFO ] false positives=0.0
2022-05-25 15:19:44,597 [INFO ] precision=1.0
2022-05-25 15:19:44,597 [INFO ] F1:1.0
2022-05-25 15:19:44,610 [INFO ] NDE:0.2578023374080658
2022-05-25 15:19:45,204 [INFO ]
MAE: 18.681659698486328
-std: 125.82673645019531
-min: 0.0
-max: 3734.59228515625
-q1: 0.0
-median: 0.20867919921875
-q2: 1.63885498046875
2022-05-25 15:19:45,224 [INFO ] SAE: 0.2403179109096527
2022-05-25 15:19:45,243 [INFO ] Energy per Day: 155.25137268110353
工程最佳实践应用于您的分析工作流程
如何利用 Github、版本控制和数据质量测试为您带来优势

无论您的职业生涯是从数据工程师、数据科学家还是数据分析师开始的,每个角色的最佳实践都可以应用到您的分析工作流程中。作为一名前数据工程师,我学习了 Github 的最佳实践,比如创建新的分支、审查拉请求以及对我的代码进行版本控制。
但是,我不会说谎,当你从一个公司角色到一个刚刚开始使用数据堆栈的小公司时,有更重要的优先事项。关注速度和直接影响,而不是编写干净代码的最佳实践,可能会很容易。
这绝对是一种微妙的平衡。你想立刻对业务产生尽可能大的影响。但是,您也希望从一开始就制定高标准。当你没有以高标准开始时,当你决定是时候优先考虑质量时,你会给自己留下一个更大的烂摊子。
那么,我们可以从传统的软件工程角色中获得哪些应用于分析的最佳实践呢?
Github 分支
在我的数据工程角色中,从 main 或 master 创建您自己的分支,并在其上编写您的更改是一个最佳实践。这确保了不正确的代码不会被推送到我们的主分支,破坏代码库。这起到了检查的作用,最大限度地减少了出错的可能性。
为了让代码进入主分支,它必须经过我团队中另外两个人的审查和批准。我们稍后会深入讨论这个问题,但是每个团队都应该这样做。如果你的团队中有不止一个人在使用 Github,这是必要的。
当然,如果你试图快速测试你的代码并将其推向生产,它会减慢速度。但是,从长远来看,它利大于弊。创建分支时,可以为您正在处理的每个票据或每个数据模型创建一个新分支。当您将所有代码更改放在一个分支上时,可能会变得草率。
例如,假设您正在开发一个客户获取数据模型。您将创建一个名为customer_acquisition的 Github 分支,并在该分支上编写您的模型。然后,当您的任务完成时,您可以将它合并到main中,供其他人查看。
如果您在处理customer_acquisition模型的同时也对revenue模型进行了更改,那么您将创建一个名为revenue的main的新分支来编写您的更改。这样,您对不同模型的更改就不会都写在同一个分支上。
这样做可以很容易地跟踪跨数据模型的变更,并确保所有内容都正确地合并到主代码库中。
Github 拉请求和代码审查
当您将分支代码与主代码库合并时,您需要创建一个拉请求。Github 上的 pull 请求将突出显示主分支代码和您的分支代码之间的所有更改。突出显示这些更改是一种很好的方式,可以确保您已经做出了想要做出的更改。
很多时候,当我这样做的时候,我会发现一些小的错别字,这些错别字是我在编辑数据模型的时候无意犯的。这是捕捉这些的好方法!
为了提高代码质量,您应该在合并代码之前,由团队中的某个人强制执行代码审查。
如何添加分支保护
为此,您需要导航到“分支保护”设置,并选中“合并前要求提取请求审查”。然后,您可以选择所需的审阅者人数。如果你在一个小团队中工作,最好只选择一个。
你总是想让别人关注你的代码。两对眼睛总比一对好!你的队友可能会抓住你没有抓住的东西。如果没有,你可以感觉很好,知道你的代码不会破坏生产中的东西。
作为团队中唯一的分析工程师,我经常在本地计算机上保存大量代码。显然,这不是一个很好的实践。您总是希望您的团队能够找到代码的最新版本,与他们在您的数据仓库中看到的相匹配的版本。强制分支和代码审查也迫使你将最新的代码推送到 Github。如果你做了更改,它需要被其他人看到才能被批准。透明永远是最好的!
版本控制
添加版本是数据模型和数据管道的最佳实践。它们帮助您识别所做的更改,并确定最后的成功或失败的原因。
使用 dbt 进行转换
如果你是一名分析工程师,很可能你也是 dbt 的用户(和爱好者)。dbt 使得对我们的数据模型进行版本控制变得容易。每个 yaml 文件中都有一个包含版本的选项。每次对这些文件进行重大更新时,也应该更新版本。

作者图片
跟踪数据模型的版本将允许在出错的情况下更容易地调试。您可以轻松地恢复到模型的上一个成功版本,而不是让生产中断,直到您发现问题。
如果您使用 dbt 的文档特性,版本控制会清楚地显示在一个易于阅读的 UI 中,供您使用。
管弦乐编曲
出于同样的原因,对您的 dbt 模型进行版本控制很重要,选择包含版本控制的编排工具也很重要。同样,在出现问题的情况下,您希望确保可以恢复到以前的版本,这样生产不会长时间停止。
就个人而言,我使用perfect来编排我的数据模型。它们为每个项目中的每个流程提供自动版本控制。这使得跟踪我在开发和生产环境中部署的版本变得容易。我还在每个流程提供的自述文件中记录了每个版本部署的模型。

作者图片
您可以在最右边看到为我的每个管道列出的版本号。这有助于我跟踪每个环境中部署了多少个不同版本的管道。
测试
测试是软件工程世界中的另一个重要实践,但是在分析中却被忽略了。但是测试应该和每一种开发类型的一起进行。每当代码发生变化并有可能影响最终用户(无论是业务团队还是实际客户)的产品时,都需要进行测试!
当我作为一名数据工程师工作时,我们总是有至少四个不同的测试环境,代码在最终进入生产之前必须部署到这些环境中。我们有多个开发环境、试运行和预生产。虽然我不认为这对分析是必要的,但我们确实需要至少一个开发环境来测试代码更改。
开发和生产管道
我们的数据通过数据管道从 A 点移动到 B 点。我们的管道运行我们所有的建模代码。因此,有必要对它们进行测试。每当您需要将一个新模型部署到生产环境中时,都需要首先使用测试管道进行部署。当对任何数据模型进行更改,或者创建一个新的数据模型时,总是有可能出现上游和下游问题。您将能够首先在您的开发环境中捕获这些,而不是在您将更改部署到生产环境中时。
我有过几次需要在基本模型中更改列名的情况。每当您对您的基础模型进行更改时,有可能会有下游模型引用这些列。如果不跟踪列血统,很难在下游做出所有需要的更改。很多时候,模型会因为我忘记更改被引用的列名而无法运行。首先部署到开发有助于捕捉这些小错误,这样它们就不会破坏生产流程。
数据质量检查
测试的另一个关键部分是对数据管道进行数据质量检查。这些测试应该在您的管道中运行,沿途检查数据的某些方面。
dbt 测试
如果已经在使用 dbt 编写数据模型,dbt 测试是一个很好的方法。您可以将它们直接添加到您的 dbt 项目中,让它们检查 null 值、惟一值、可接受值以及列之间的关系。
关于 dbt 测试最好的部分是您可以为您的源数据和模型运行它们。我推荐两个都设置!通过这种方式,您可以捕捉任何上游数据质量问题,也可以捕捉由模型代码引起的问题。
re_data
我还喜欢使用一个名为 re_data 的 dbt 包,它可以检测你的数据中的异常。我特别喜欢用这个包来测试新鲜度和行数。该软件包被设置为像 dbt 模型一样运行,它将向您选择的松弛通道直接发送警报。
它通过计算您正在监控的指标的平均值和标准偏差来工作。它在识别其他类型的测试可能检测不到的奇怪问题方面做得很好。要了解如何设置这个 dbt 包,请查看这篇文章。
结论
我怎么强调都不为过。最佳实践应该从一开始就设定。没有它们的时间越长,你的数据和代码就会变得越乱。从长远来看,你只会为你自己和你的团队创造更多的工作。这听起来可能并不有趣和迷人,但是从一开始就记录你的团队的最佳实践将会使你的团队走得更远。
随着分析变得越来越复杂,我们有很多东西要向软件工程团队学习。随着工程和分析的相互交织,我们制定的标准也是如此。我们不能再像以前那样做事了。我们需要拥抱 Github、版本控制和测试。它们在工程中如此常见是有原因的——它们降低了风险!
要了解分析工程领域的更多最佳实践,订阅我的时事通讯。
用 BERT 变换器和名词短语提取关键短语
利用名词短语预处理增强基于 BERT 的关键词抽取

这篇文章基于我们的论文 “模式排序:利用预训练的语言模型和词性进行无监督的关键短语提取(2022)”。你可以在那里或者在我们的 PatternRank 博客文章 中阅读关于我们方法的更多细节。
要快速浏览文本内容,提取能简明反映其语义上下文的关键词会很有帮助。虽然常用的术语是关键字,但我们通常实际上想要关键短语来实现这个目的。
关键词或关键短语都应该描述文章的本质。两者的区别在于,关键词是单个单词,而关键短语是由几个单词组成的。例如“小狗”vs“小狗服从训练”。——艾里斯·盖伦
关键短语比简单的关键字提供了更准确的描述,因此通常是首选。幸运的是,许多开源解决方案允许我们从文本中自动提取关键短语。最近非常流行的解决方案之一是 KeyBERT 。这是一个易于使用的 Python 包,通过 BERT 语言模型提取关键短语。简单地说,KeyBERT 首先创建文档文本的 BERT 嵌入。然后,创建具有预定义长度的单词 n 元文法的 BERT 关键短语嵌入。最后,计算文档和关键短语嵌入之间的余弦相似度,以提取最佳描述整个文档的关键短语。关于 KeyBERT 更详细的介绍可以在这里找到。
为什么需要增强 KeyBERT 结果
尽管 KeyBERT 能够自己提取好的关键短语,但实际上仍然存在两个问题。这是由 KeyBERT 在嵌入步骤之前从文档中提取关键短语的方式造成的。用户需要预定义一个单词 n 元语法范围来指定提取的关键短语的长度。然后,KeyBERT 从文档中提取定义长度的简单单词 n-grams,并将它们用作嵌入创建和相似性计算的候选关键短语。
单词 n 元语法范围让用户决定应该从给定文本中提取的连续单词序列的长度。假设我们定义了一个
word n-gram range = (1,3)。然后,我们将选择从文本中提取一元词(只有一个单词)、二元词(两个连续单词的组合)和三元词(三个连续单词的组合)。将单词 n-gram range 应用于"an apple a day keeps the doctor away"将导致["an", "apple", "a","day", "keeps", "the", "doctor", "away", "an apple", "apple a", "a day", "day keeps", "keeps the", "the doctor", "doctor away", "an apple", "apple a day", "a day keeps", "day keeps the", "keeps the doctor", "the doctor away"]。- 德维什·帕尔马
然而,用户通常不知道最佳的 n-gram 范围,因此必须花费一些时间进行试验,直到他们找到合适的 n-gram 范围。此外,这意味着根本不考虑语法句子结构。这导致这样的效果,即使在找到一个好的 n 元语法范围之后,返回的关键短语有时仍然在语法上不太正确或者稍微跑调。继续上面的例子,如果 KeyBERT 从候选关键短语集合中识别出最重要的关键短语、或、。
如何用关键短语向量增强 KeyBERT 结果
为了解决上面提到的问题,我们可以将keyphrasvectors包与 KeyBERT 一起使用。KeyphraseVectorizers 包从一组文本文档中提取具有词性模式的关键短语,并将它们转换成文档-关键短语矩阵。文档关键短语矩阵是描述关键短语在文档集合中出现的频率的数学矩阵。
KeyphraseVectorizer 包是如何工作的?
首先,文档文本用空间词性标签标注。其次,从词性标签与预定义的正则表达式模式匹配的文档文本中提取关键短语。默认情况下,矢量器提取包含零个或多个形容词的关键短语,后跟一个或多个使用英语空间词性标签的名词。最后,矢量器计算文档关键短语矩阵。除了矩阵之外,这个软件包还可以为我们提供通过词性提取的关键短语。
举例:
我们可以用下面的命令来安装 KeyphraseVectorizers 包:pip install keyphrase-vectorizers。
{'binary': False, 'dtype': <class 'numpy.int64'>, 'lowercase': True, 'max_df': None, 'min_df': None, 'pos_pattern': '<J.*>*<N.*>+', 'spacy_pipeline': 'en_core_web_sm', 'stop_words': None, 'workers': 1}
默认情况下,矢量器针对英语进行初始化。这意味着,指定了一个英语spacy_pipeline,没有删除任何stop_words,并且pos_pattern提取具有零个或多个形容词的关键字,后跟一个或多个使用英语 spaCy 词性标签的名词。
[[0 0 0 0 1 3 2 1 1 0 1 1 3 1 0 0 0 0 1 0 1 1 1 0 1 0 2 0 1 1 1 0 1 1 0 0 0 1 1 3 3 0 1 3 3]
[1 1 5 1 0 0 0 0 0 1 0 0 0 0 1 1 1 1 0 1 0 0 0 2 0 1 0 1 0 0 0 2 0 0 1 1 1 0 0 0 0 5 0 0 0]]
['users' 'main topics' 'learning algorithm' 'overlap' 'documents' 'output' 'keywords' 'precise summary' 'new examples' 'training data' 'input' 'document content' 'training examples' 'unseen instances' 'optimal scenario' 'document' 'task' 'supervised learning algorithm' 'example' 'interest' 'function' 'example input' 'various applications' 'unseen situations' 'phrases' 'indication' 'inductive bias' 'supervisory signal' 'document relevance' 'information retrieval' 'set' 'input object' 'groups' 'output value' 'list' 'learning' 'output pairs' 'pair' 'class labels' 'supervised learning' 'machine' 'information retrieval environment' 'algorithm' 'vector' 'way']
矢量器的输出显示,与简单的 n 元语法不同,提取的单词语法正确,有意义。这是矢量器提取名词短语和扩展名词短语的结果。
名词短语是围绕一个名词构建的简单短语。它包含一个限定词和一个名词。例如:一棵树,一些糖果,城堡。一个扩展名词短语通过添加一个或多个形容词来为名词添加更多细节。形容词是描述名词的词。例如:一棵巨大的树,一些五彩缤纷的糖果,那些巨大的、皇家的城堡。英国广播公司
KeyBERT 如何使用 KeyphraseVectorizers?
关键短语矢量器可以与 KeyBERT 一起使用,以提取与文档最相似的语法正确的关键短语。因此,矢量器首先从文本文档中提取候选关键短语,随后由 KeyBERT 基于它们的文档相似性对其进行排序。然后,前 n 个最相似的关键短语可以被认为是文档关键词。
除了 KeyBERT 之外,使用 KeyphraseVectorizers 的优点是,它允许用户获得语法正确的关键短语,而不是简单的预定义长度的 n 元语法。
关键短语分类器首先提取由零个或多个形容词组成的候选关键短语,然后在预处理步骤中提取一个或多个名词,而不是简单的 n 元语法。 TextRank 、 SingleRank 和embe beed已经成功使用这种名词短语方法进行关键短语提取。提取的候选关键短语随后被传递给 KeyBERT 用于嵌入生成和相似性计算。为了使用这两个包来提取关键短语,我们需要传递给 KeyBERT 一个带有vectorizer参数的关键短语矢量器。由于关键短语的长度现在取决于词性标签,因此不再需要定义 n 元语法长度。
示例:
KeyBERT 可以通过pip install keybert安装。
不是决定合适的 n 元语法范围,例如(1,2)…
[[('labeled training', 0.6013),
('examples supervised', 0.6112),
('signal supervised', 0.6152),
('supervised', 0.6676),
('supervised learning', 0.6779)],
[('keywords assigned', 0.6354),
('keywords used', 0.6373),
('list keywords', 0.6375),
('keywords quickly', 0.6376),
('keywords defined', 0.6997)]]
我们现在可以让关键短语向量器决定合适的关键短语,而没有最大或最小 n 元语法范围的限制。我们只需将一个关键短语矢量器作为参数传递给 KeyBERT:
[[('learning', 0.4813),
('training data', 0.5271),
('learning algorithm', 0.5632),
('supervised learning', 0.6779),
('supervised learning algorithm', 0.6992)],
[('document content', 0.3988),
('information retrieval environment', 0.5166),
('information retrieval', 0.5792),
('keywords', 0.6046),
('document relevance', 0.633)]]
这使我们能够确保我们不会因为将 n 元语法范围定义得太短而删除重要的单词。例如,我们可能找不到带有keyphrase_ngram_range=(1,2)的关键词“监督学习算法”。此外,我们避免获得稍微跑调的关键短语,如、、、【信号监控】、或、【快速关键词】、。
提取英语以外的语言中的关键短语:
此外,我们还可以将这种方法应用于其他语言,如德语。这只需要对键相器和键齿的一些参数进行修改。
对于关键相分离器,spacy_pipeline和stop_words参数需要修改为spacy_pipeline=’de_core_new_sm’和stop_words=’german’。因为德语 spaCy 词性标签与英语不同,pos_pattern参数也需要修改。regex 模式<ADJ.*>*<N.*>+提取包含零个或多个形容词的关键字,后跟一个或多个使用德语 spaCy 词性标签的名词。
对于 KeyBERT,需要通过pip install flair安装 Flair 包,并且必须选择德国 BERT 型号。
[[('schwester cornelia', 0.2491),
('neigung', 0.2996),
('angesehenen bürgerlichen familie', 0.3131),
('ausbildung', 0.3651),
('straßburg', 0.4022)],
[('tochter', 0.0821),
('friedrich schiller', 0.0912),
('ehefrau elisabetha dorothea schiller', 0.0919),
('neckar johann kaspar schiller', 0.092),
('wundarztes', 0.1334)]]
摘要
keyphrasvectors是最近发布的一个包,除了 KeyBERT 之外,它还可以用来从文本文档中提取增强的关键短语。这种方法消除了对用户定义的单词 n 元语法范围的需要,并提取语法正确的关键短语。此外,该方法可以应用于许多不同的语言。这两个开源包都易于使用,只需几行代码就可以精确提取关键短语。
也非常感谢 Maarten Grootendorst ,他在我编写keyphrasevectors包时给了我输入和灵感。
来源
https://github.com/TimSchopf/KeyphraseVectorizers https://github.com/MaartenGr/KeyBERT https://arxiv.org/abs/2210.05245
米哈尔恰和塔劳(2004 年)。 TextRank:将 or-
der 带入文本。2004 年自然语言处理经验方法会议论文集-
ing,第 404–411 页,西班牙巴塞罗那。计算语言学协会。
万,谢,肖(2008)。 CollabRank:实现一种
协作方法来提取单个文档的关键短语
。《第 22 届国际计算语言学会议(T4)论文集》(2008 年出版),第 969-976 页,英国曼彻斯特。科林 2008 组委会。
本纳尼-斯米尔,k .,穆萨特,c .,霍斯曼,a .,贝里斯维尔,
M .,贾吉,M. (2018)。使用句子嵌入的简单无监督
关键短语提取。第 22 届计算机自然语言学习会议论文集,第 221-229 页,
比利时布鲁塞尔。计算语言学协会。
提高微型神经网络的训练性能
注意训练大型和小型神经网络的区别

克雷格·麦克戈尼格尔在 Unsplash 上的照片
训练深度神经网络(NN)是困难的,有时甚至对于资深从业者来说也是棘手的。为了在给定特定数据集的情况下达到模型的最高潜在性能,我们需要考虑许多方面:超参数调整、正则化、验证度量、归一化等等。然而,我们应该考虑的问题并不是对所有不结盟国家都适用的。这取决于模型的大小。
由于大模型容易过拟合,使用各种正则化方法消除过拟合是训练大模型时最重要的课题之一。然而,对于微型模型来说,情况并非如此,因为微型模型容易出现拟合不足的情况。如果在训练微小模型时使用正则化方法,性能可能会变差。
为什么会这样?模型容量和正则化有什么关系?这篇文章将讨论它们。
模型拟合应考虑模型容量
深度学习模型通常很大,可扩展,这意味着模型规模越大,性能越好,尤其是对变形金刚而言。然而,有时我们需要在资源受限的 AI 芯片上部署微小的模型。工业上既需要大型号,也需要小型号。
这就是问题所在。大多数书籍,教程,博客等。关于如何训练一个模特实际上是关于如何训练大模特。这将留下一种错觉,即所有规模的模型都可以以类似的方式训练,以获得良好的性能。
事实上,如果你用训练大模型的方式训练一个小模型,你可能会错过要点。
大模型往往会过度拟合
由于大模型有如此多的参数,它们容易学习数据集的所有噪声,这被称为过拟合。所有取自真实物理世界的数据集都包含大量噪声,在训练过程中消除这些噪声对于模型泛化至关重要。

欠拟合与过拟合(图片由作者提供)
过度拟合也意味着该模型不是抗噪声的。这就是为什么该模型在训练数据集上表现良好,但在验证数据集上表现不佳,因为包含在验证数据集中的噪声没有被该模型学习到。正则化是使模型对噪声鲁棒所必需的,因此该模型也可以在验证数据集上表现良好,即使它包含与训练数据集不同的噪声。
为什么正则化对小模型不起作用
正则化通过向数据集或模型添加噪声来工作。数据扩充等正则化方法会向数据集添加噪声,而丢弃等方法会向模型添加噪声。因为标签是固定的,所以模型可以被训练来推断具有这种噪声的相同标签,因此是抗噪声的。
但是,如果模型很小,容易出现欠拟合怎么办?如上图,容量低的微小模型都无法很好的学习数据集,更不用说数据集中包含的噪音了。在这种情况下,我们应该扩大模型容量,使其先过拟合,然后再正则化。
增强模型,而不是数据集
如何放大微小的模型?模型尺寸根本放大不了怎么办?
幸运的是,麻省理工学院最近在 ICLR2022 上发表的一篇论文提出了一个名为 NetAug 的解决方案。

作者提出了一种简单的方法,在训练过程中扩大 CNN 模型的宽度(通道),在推理过程中保持原有的结构。如上图,结果很惊人。它还表明,正则化对小模型有负面影响。

NetAug 的损耗函数是原始和扩充架构损耗的组合。由于每一层的增强的可能性,例如如何扩展信道,不是唯一的,所以我们可以有多个增强的架构。为了计算这些损失,将向前和向后通过的总次数相乘。然而,训练时间开销不会增加太多,因为模型很小,训练时间主要由数据加载和通信决定。
参考
https://dushuchen.medium.com/membership
使用以下提示丰富您的 GitHub 个人资料
原文:https://towardsdatascience.com/enrich-your-github-profile-with-these-tips-272fa1eafe05
开源代码库
在 GitHub 上建立自己独特档案的灵感创意
你知道我们可以通过使用一个特殊的库来定制我们的 GitHub 配置文件吗?我最近对我的 GitHub 个人资料进行了个性化处理,使其更加个性化,信息量更大。在这篇文章中,我分享了一些我一路走来学到的技巧。我希望这些建议能让你更容易创建自己独特的个人资料。

我的新 GitHub 个人资料部分|作者图片
📍 0.创建个人资料自述文件
让我们先了解一下配置文件定制的基本原理。我使用这个库、一个以我的 GitHub 用户名命名的特殊库,来定制我的 GitHub 。然后,我在存储库中的 README.md 中包含了我想在我的个人资料上显示的内容。在这之后,我的 GitHub 个人资料被漂亮地定制了。
现在,让我们为您创建一个特殊的存储库并初始化一个 README.md 文件。如果您对 GitHub 感到满意,请继续使用您喜欢的方法,并跳到下一节。否则,您可以在登录 GitHub 后按照以下步骤操作:
- 点击位于️and 右上角的+,选择新建库,创建一个新的公共* GitHub 库。*

2.以你的 GitHub 用户名命名这个库。输入存储库名称后,您将会看到一个关于✨特殊✨存储库的提示,如下图所示。

我已经有了一个特殊的存储库,因此警告消息是粉红色的。
3.特殊存储库必须是公开的,才能显示在您的 GitHub 个人资料上。所以对资源库做一个简要的描述(例如“GitHub profile”,这一步是可选的),选择 Public】,打勾“添加一个自述文件”,点击“创建资源库”:

很好,您刚刚创建了您的特殊存储库并在其中初始化了一个 README.md 文件。现在,有趣的部分开始了!我们将看看几种丰富 GitHub 概要文件的方法。将显示示例提示和示例代码来说明一个想法。通过选择和调整您最喜欢的文件,并将其包含在 README.md 文件中,您可以根据自己的喜好设计您的个人资料!
👽 1.“关于我”部分
在 GitHub 个人资料中包含简短的自我介绍是很常见的。如果您不太确定是否开始,这里有一个示例提示:
### Hi there 👋
* 👂 My name is ...
* 👩 Pronouns: ...
* 🔭 I’m currently working on ...
* 🌱 I’m currently learning ...
* 🤝 I’m looking to collaborate on ...
* 🤔 I’m looking for help with ...
* 💬 Ask me about ...
* 📫 How to reach me: ...
* ❤️ I love ...
* ⚡ Fun fact: ...

👽 1.1.附加资源:
◼️各种表情符号如下:github markdown 表情符号标记的完整列表
🎨 2.形象
🎨 2.1.嵌入图像
添加图像,包括 GIF:动画图像,可以丰富您的个人资料。我们将使用以下两种方法中的一种来嵌入.jpg、.png、.gif或.svg文件:
◼️降价方法: 带有<img>标签的◼️ HTML 方法:<img src="<url>"/> HTML 方法允许进一步定制渲染图像,例如调整大小和对齐。此外,HTML 方法与其他 HTML 标签协调工作(我们将在第 6 节看到一个这样的例子)。
我们可以使用原始文件链接嵌入存储在存储库中的图像。嵌入此类图像的语法如下所示:
<!-- Markdown approach -->
<username>/<repository>/<branch>/<file_path>)<!-- HTML approach -->
<img src="[https://raw.githubusercontent.com/](https://raw.githubusercontent.com/)<username>/<repository>/<branch>/<file_path>"/>
或者,我们可以嵌入存储在网络其他地方的图像:
<!-- Markdown approach -->
)<!-- HTML approach -->
<img src="[https://images.unsplash.com/photo-1511914265872-c40672604a80?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1074&q=80](https://images.unsplash.com/photo-1511914265872-c40672604a80?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1074&q=80)"/>

使用外部 url 的一个潜在风险是链接将来可能会断开。
已经学会了如何嵌入图像,让我们来学习一些方法来嵌入图像。
🎨 2.2.寻找/设计图像
上面的海龟图片取自 Unsplash ,这是一个寻找高质量免费可用的图片的绝佳平台。如果你想找一张照片放在你的个人资料里,平台上有很多漂亮的照片。
另一个选择是设计你自己的横幅。可以使用 Adobe Photoshop、PowerPoint 和 Keynote 等常见工具制作横幅。至于我,我喜欢用 Canva ,一个用户友好的直观设计工具。使用其中一个免费模板,我在几分钟内制作了以下横幅:

如果你不确定从哪里开始,我建议从免费的模板开始,在‘脸书封面’,【YouTube 频道艺术’,和‘LinkedIn 横幅’下。此外,https://excalidraw.com/**或draw . io都是免费的有用探索工具。****
GIF 让我们表达的不仅仅是静态图像。你可以从 GIPHY 或 Tenor 等平台上找到一个 GIF 来补充你的个人资料。或者,你可以制作你独特的 GIF,这就是我为我的个人资料所做的。
🎨 2.3.免费资源整理列表
◼️ 设计工具: PowerPoint、Keynote、 Canva 、 Excalidraw 或draw . io◼️高质量图片:unsplash◼️gif:giphy或男高音**
📈 3.GitHub 自述文件统计
如果你在 GitHub 上很活跃,显示你的 GitHub 活动摘要可以很好地增加你的个人资料。我们将考虑几个选项。
📈 3.1.GitHub 统计
通过在示例 url 中将zluvsand替换为您的用户名,摘要将适应您的 GitHub 活动。
**<img src="https://github-readme-stats.vercel.app/api?username=zluvsand&show_icons=true"/>**

📈 3.2.最常用的语言
该统计基于您的公共存储库:
**<img src="https://github-readme-stats.vercel.app/api/top-langs?username=zluvsand"/>**

通过将&layout=compact添加到 url,我们可以使它更紧凑:
**<img src="https://github-readme-stats.vercel.app/api/top-langs?username=zluvsand&layout=compact"/>**

📈 3.3.总贡献和条纹
这个用来追踪条纹:
**<img src="https://github-readme-streak-stats.herokuapp.com/?user=zluvsand"/>**

📈 3.4.GitHub 知识库
如果您想在个人资料中突出显示一两个存储库。这个例子基于我的名为github_profile的知识库:
**<img src="https://github-readme-stats.vercel.app/api/pin/?username=zluvsand&repo=github_profile"/>**

📈 3.5.主题
这些摘要可以用主题中的一个进一步设计。要应用你最喜欢的主题,我们只需要在 url 的末尾添加&theme=<theme_name>。以下是如何在 GitHub stats 的例子中应用黑暗主题:
**<img src="https://github-readme-stats.vercel.app/api?username=zluvsand&show_icons=true&theme=dark"/>**

📈 3.6.额外资源
要了解更多关于 GitHub README stats 的信息,请查看以下内容:
◼️github readme stats
◼️github readme streak stats
◼️令人惊叹的 GitHub Stats Demo
☎️ 4.“与我联系”部分
如果你想和你的访客保持联系,在你的 GitHub 个人资料中添加你的社交媒体和其他平台上的个人资料的链接会很有用。让我们看 3 个例子。
☎️ 4.1.示例 1: Shields.io 徽章
在本例中,我们将使用 Shields.io 徽章并链接徽章。我们将使用这个 Markdown 语法结构:[](<hyperlink>)。
**[](https://medium.com/@zluvsand)[](https://www.linkedin.com/in/zluvsand/)[](https://open.spotify.com/playlist/7KmIUNWrK8wEHfQcQfFrQ1?si=0e2d44043b5a40a4)**

这些徽章非常灵活,可以根据您的喜好定制。让我们学习一些关于格式化这些徽章之类的基本东西:
◼ ️Let's 从一个简单的中等徽章开始,?之前的部分:[https://img.shields.io/badge/Medium-12100E](https://img.shields.io/badge/Medium-12100E)。

◼️我们给格式加上第一个参数:?<argument>=<value>。我们将通过添加?style=for-the-badge : [https://img.shields.io/badge/Medium-12100E?style=for-the-badge](https://img.shields.io/badge/Medium-12100E?style=for-the-badge)来改变徽章的样式。

◼️我们可以在后面加上&<argument>=<value>。您可能还记得,我们在第 3 节中应用主题时已经这样做了。我们通过添加&logo=medium : [https://img.shields.io/badge/Medium-12100E?style=for-the-badge&logo=medium](https://img.shields.io/badge/Medium-12100E?style=for-the-badge&logo=medium)来添加中号 logo 吧。

如果你想了解更多关于这些徽章的信息, Shields.io 也有例子。或者,您可以从[这里](http://Markdown Badges)或这里找到预先格式化的徽章。
☎️ 4.2.示例 2:图标
在本例中,我们将展示不带任何品牌名称的徽标。这一次,我们将使用 HTML 语法:<a href="<hyperlink>"><img src="<[i](https://cdn4.iconfinder.com/data/icons/social-media-rounded-corners/512/Medium_rounded_cr-306.png)mage_url>"/></a>。
**<a href="[https://medium.com/@zluvsand](https://medium.com/@zluvsand)">
<img height="50" src="[https://cdn4.iconfinder.com/data/icons/social-media-rounded-corners/512/Medium_rounded_cr-306.png](https://cdn4.iconfinder.com/data/icons/social-media-rounded-corners/512/Medium_rounded_cr-306.png)"/>
</a>
<a href="[https://www.linkedin.com/in/zluvsand/](https://www.linkedin.com/in/zluvsand/)">
<img height="50" src="[https://cdn2.iconfinder.com/data/icons/social-icon-3/512/social_style_3_in-306.png](https://cdn2.iconfinder.com/data/icons/social-icon-3/512/social_style_3_in-306.png)"/>
</a>
<a href="[https://open.spotify.com/playlist/7KmIUNWrK8wEHfQcQfFrQ1?si=0e2d44043b5a40a4](https://open.spotify.com/playlist/7KmIUNWrK8wEHfQcQfFrQ1?si=0e2d44043b5a40a4)">
<img height="50" src="[https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/315_Spotify_logo-128.png](https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/315_Spotify_logo-128.png)"/>
</a>**

在<img>标签中,我们用height参数定制了图标的大小。
这些徽标图片来自全球最大的图标和插图市场 Iconfinder 。
☎️ 4.3.示例 3:图标和表格
在最后一个例子中,我们将使用 HTML 语法创建一个表格,使徽标看起来更有条理。
**<table>
<tbody>
<tr>
<td><a href="[https://medium.com/@zluvsand](https://medium.com/@zluvsand)">
<img height="50" src="[https://www.vectorlogo.zone/logos/medium/medium-ar21.svg](https://www.vectorlogo.zone/logos/medium/medium-ar21.svg)" />
</a></td>
<td><a href="[https://www.linkedin.com/in/zluvsand/](https://www.linkedin.com/in/zluvsand/)">
<img height="50" src="[https://www.vectorlogo.zone/logos/linkedin/linkedin-ar21.svg](https://www.vectorlogo.zone/logos/linkedin/linkedin-ar21.svg)" />
</a></td>
<td><a href="[https://open.spotify.com/playlist/7KmIUNWrK8wEHfQcQfFrQ1?si=0e2d44043b5a40a4](https://open.spotify.com/playlist/7KmIUNWrK8wEHfQcQfFrQ1?si=0e2d44043b5a40a4)">
<img height="50" src="[https://www.vectorlogo.zone/logos/spotify/spotify-ar21.svg](https://www.vectorlogo.zone/logos/spotify/spotify-ar21.svg)"/>
</a></td>
</tr>
</tbody>
</table>**

这些标志取自矢量标志专区,一个以.svg格式展示华丽标志的网站。
在这些示例中,徽章和徽标直接来自网站。下载徽章和徽标的图像并保存在存储库中是一种替代方法。
☎️ 4.4.免费资源整理列表
◼️ 徽章: Shields.io , Markdown 徽章或 150+徽章为 GitHub
◼️ 图标: Iconfinder , VectorLogoZone 或简单图标
◼️5.4。免费资源整理列表
🛠️ 5.语言和工具
本节与第 4 节非常相似。因此,第 4 节和第 5 节中的资源和设计可以互换使用。我们将再次看 3 个例子。
🛠️ 5.1.示例 1
Devicon 为编程语言提供许多图标,设计&开发工具。在本例中,我们将使用 Devicon 的图标:
**<img height=50 src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/python/python-original.svg"/><img height=50 src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/java/java-original.svg"/><img height=50 src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/html5/html5-original.svg" /><img height=50 src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/css3/css3-original.svg" /><img height=50 src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg" /><img height=50 src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/git/git-plain.svg"/><img height=50 src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/github/github-original.svg"/><img height=50 src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/canva/canva-original.svg"/>**

对于给定的图标,可以使用 Devicon 网站左侧窗格上的< img > element" 在下找到 url。这里的语法更简单,因为我们不需要链接图片。
🛠️ 5.2.示例 2
我们现在将使用来自矢量徽标区的徽标,并将使用<code>标签格式化图标,以呈现略有不同的外观:
**<code><img width="10%" src="[https://www.vectorlogo.zone/logos/python/python-ar21.svg](https://www.vectorlogo.zone/logos/python/python-ar21.svg)"></code>
<code><img width="10%" src="[https://www.vectorlogo.zone/logos/java/java-ar21.svg](https://www.vectorlogo.zone/logos/java/java-ar21.svg)"></code>
<code><img width="10%" src="[https://www.vectorlogo.zone/logos/w3_html5/w3_html5-ar21.svg](https://www.vectorlogo.zone/logos/w3_html5/w3_html5-ar21.svg)"></code>
<code><img width="10%" src="[https://www.vectorlogo.zone/logos/w3_css/w3_css-ar21.svg](https://www.vectorlogo.zone/logos/w3_css/w3_css-ar21.svg)"></code>
<br />
<code><img width="10%" src="[https://www.vectorlogo.zone/logos/reactjs/reactjs-ar21.svg](https://www.vectorlogo.zone/logos/reactjs/reactjs-ar21.svg)"></code>
<code><img width="10%" src="[https://www.vectorlogo.zone/logos/git-scm/git-scm-ar21.svg](https://www.vectorlogo.zone/logos/git-scm/git-scm-ar21.svg)"></code>
<code><img width="10%" src="[https://www.vectorlogo.zone/logos/github/github-ar21.svg](https://www.vectorlogo.zone/logos/github/github-ar21.svg)"></code>
<code><img width="10%" src="[https://www.vectorlogo.zone/logos/canva/canva-ar21.svg](https://www.vectorlogo.zone/logos/canva/canva-ar21.svg)"></code>**

这给徽标增加了一个松散的结构。
🛠️ 5.3.示例 3
我们将使用一张 2x4 的桌子来组织我们之前使用的 Devicon 徽标:
**<table width="320px">
<tbody>
<tr valign="top">
<td width="80px" align="center">
<span><strong>Python</strong></span><br>
<img height="32px" src="[https://cdn.jsdelivr.net/gh/devicons/devicon/icons/python/python-original.svg](https://cdn.jsdelivr.net/gh/devicons/devicon/icons/python/python-original.svg)">
</td>
<td width="80px" align="center">
<span><strong>Java</strong></span><br>
<img height="32" src="[https://cdn.jsdelivr.net/gh/devicons/devicon/icons/java/java-original.svg](https://cdn.jsdelivr.net/gh/devicons/devicon/icons/java/java-original.svg)">
</td>
<td width="80px" align="center">
<span><strong>HTML</strong></span><br>
<img height="32" src="[https://cdn.jsdelivr.net/gh/devicons/devicon/icons/html5/html5-original.svg](https://cdn.jsdelivr.net/gh/devicons/devicon/icons/html5/html5-original.svg)">
</td>
<td width="80px" align="center">
<span><strong>CSS</strong></span><br>
<img height="32px" src="[https://cdn.jsdelivr.net/gh/devicons/devicon/icons/css3/css3-original.svg](https://cdn.jsdelivr.net/gh/devicons/devicon/icons/css3/css3-original.svg)">
</td>
</tr>
<tr valign="top">
<td width="80px" align="center">
<span><strong>React</strong></span><br>
<img height="32px" src="[https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg](https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg)">
</td>
<td width="80px" align="center">
<span><strong>git</strong></span><br>
<img height="32px" src="[https://cdn.jsdelivr.net/gh/devicons/devicon/icons/git/git-plain.svg](https://cdn.jsdelivr.net/gh/devicons/devicon/icons/git/git-plain.svg)">
</td>
<td width="80px" align="center">
<span><strong>GitHub</strong></span><br>
<img height="32px" src="[https://cdn.jsdelivr.net/gh/devicons/devicon/icons/github/github-original.svg](https://cdn.jsdelivr.net/gh/devicons/devicon/icons/github/github-original.svg)">
<td width="80px" align="center">
<span><strong>Canva</strong></span><br>
<img height="32px" src="[https://cdn.jsdelivr.net/gh/devicons/devicon/icons/canva/canva-original.svg](https://cdn.jsdelivr.net/gh/devicons/devicon/icons/canva/canva-original.svg)">
</td>
</tr>
</tbody>
</table>**

语法增长了不少。我们使用width参数来使列宽等于和align参数来居中对齐内容。
🛠️ 5.4.免费资源整理列表
◼️ 图标: 图标
◼️节☎️️ 4.4。免费资源整理列表
📁 6.可折叠部分
如果你有很多东西要放在你的个人资料中,但是你不想让你的访问者不知所措,你会发现可折叠的部分很有帮助。我们可以使用<details> HTML 标签创建可折叠的部分:
**<details>
<summary><b>✨About Me</b></summary><br/>
Sample text
</details><details>
<summary><b>🛠️ Languages & Tools</b></summary><br/>
Sample text
</details>**

如果您想在折叠部分嵌入一个图像,您需要使用 HTML 方法:
**<details>
<summary><b>🎁 Open me (Markdown approach) </b></summary>
)
</details>
<details>
<summary><b>🎁 Open me (HTML approach) </b></summary>
<img src="[https://media.giphy.com/media/H4uE6w9G1uK4M/giphy.gif](https://media.giphy.com/media/H4uE6w9G1uK4M/giphy.gif)"/>
</details>**

❄️ 7.主观因素
在设计 GitHub profile 的时候,你可能想加入一些你的个人风格。我将分享我所做的作为一个例子,希望这能给你一些想法,并帮助你找到你的个人风格。我是这样做的:
◼️尝试用我最喜欢的一些颜色为整个档案使用相同的调色板:黑色和粉红色
◼️找到了我的两个兴趣的很好的交集:喜剧和编程。我找到了这个神奇的笑话 API ,它随机生成编程笑话,只有一行代码:
****

◼ ️Put 在一点点努力变成我的旗帜。
那么你呢?你可以做些什么来让你的个人资料更独特?
📚 8.额外资源
瞧,就是这样。希望这些提示能给你一些启发,让你开始创建自己的 GitHub 档案!
🔗如果你在设计你的个人资料方面有更多的灵感,这里有一些整理了 awesome 个人资料的仓库:
◼️ ️️ Awesome GitHub 个人资料自述文件-awesome github 个人资料收藏库
◼️️️github 个人资料收藏库
◼️ ️️ Awesome GitHub 个人资料自述文件模板收藏库
通过点击 README.md 然后点击 Raw ,你可以看到底层代码。
🔗如果您想学习更高级的主题,比如动态更新内容,这里有一些资源可以探索:
◼️ ️️ 复杂的可嵌入指标
◼️ ️️ 了解 GitHub 动作
🔗如果您想尝试用户友好的界面来自动生成 GitHub Profile 的自定义自述脚本,那么 GitHub Profile 自述文件生成器可能会让您感兴趣。
最后,我总结了我们今天在 GitHub 库中学到的代码片段。如果你在设计你的 GitHub 个人资料时使用了一些技巧,请不要犹豫,留下你的个人资料链接的评论——我很乐意看到!

罗伯特·卡茨基在 Unsplash 上拍摄的照片
您想要访问更多这样的内容吗?媒体会员可以无限制地访问媒体上的任何文章。如果你使用 我的推荐链接 ,成为会员,你的一部分会费会直接去支持我。
谢谢你看我的帖子。如果你感兴趣,这里有我的一些帖子的链接:
◼️️ 用这些技巧充实你的 Jupyter 笔记本
◼️ 用这些技巧整理你的 Jupyter 笔记本
◼️ 有用的 IPython 魔法命令
◼️python 虚拟数据科学环境简介
◼️git 数据科学简介
◼️python 中的简单数据可视化,你会发现有用的
◼️ 6 个简单技巧,让情节更漂亮,更定制
再见🏃💨
集成平均—通过投票提高机器学习性能
整体平均利用多个模型预测来开发稳健的性能

帕克·约翰逊在 Unsplash 上拍摄的照片
人多力量大。令人惊讶的是,在机器学习领域也可以做出同样的观察。整体平均是设计多个不同模型的技术,即线性回归或梯度增强树,并允许它们对最终预测形成意见。
该技术可以通过以下三个步骤轻松应用:
- 开发多个预测模型,每个模型都能够做出自己的预测
- 使用同一组训练数据训练每个预测模型
- 通过使用每一个模型预测结果,并平均它们的值
这看起来似乎很容易实现,与单一的独立预测模型相比,它真的会产生更好的结果吗?事实上,一组模型经常比任何一个单独的模型显示出更好的结果。为什么这样
为什么系综平均有效?
偏差和方差
为了理解它的工作原理,我们首先来看看机器学习模型的两个最重要的属性:偏差和方差。

作者的代码输出图
偏差本质上是模型使用目标函数将给定输入映射到输出的假设。例如,简单的普通最小二乘(OLS)回归是一种具有高偏差的模型。具有高偏差的模型允许它自己容易地学习输入和输出之间的关系。然而,它们总是对目标函数做更多的假设,因此灵活性较低。所以偏高的机型往往会出现欠配的问题(左图)。
另一方面,模型的方差测量模型在不同训练数据上的表现。决策树不对目标函数做任何假设。因此,它非常灵活,并且通常具有低偏差。然而,由于灵活性,它倾向于学习最初用于训练模型的所有噪声。因此,这些模型引入了另一个问题— 高方差。这在另一方面经常导致过拟合的问题(右图)。

乔恩·泰森在 Unsplash 上的照片
偏差-方差困境因此存在于机器学习的世界中,因为试图同时最小化偏差和方差存在冲突。越来越复杂的模型通常具有低偏差和高方差,而相对简单的模型通常具有高偏差和低方差。因此,困境描述了模型学习输入和输出之间的最佳关系,同时在原始训练集之外进行概括的难度。
投票机概述
一群模型就像投票机一样工作。每个合奏成员通过使用他们的预测进行投票来平等地做出贡献。因此,当每个模型被集合时,它引入了多样性的概念。这种多样性将减少方差,并提高超越训练数据的概括能力。
让我们假设下图中的八杯咖啡分别代表不同的预测模型。每一种都有不同的口味(有些更甜,有些微苦)。由于我们事先不了解顾客,所以我们无法确定我们用来招待顾客(预测结果)的咖啡是否合适。我们的主要目标是想出一种新的咖啡,味道好,更适合大多数人的口味。

我们在这里可以做的是,通过混合八杯咖啡,我们得到了一种新的咖啡混合物,它保留了每种咖啡的焦糖、巧克力和苦味。与例如纯浓咖啡相比,这种咖啡在理论上应该能够服务于更广泛的顾客。这是因为最初浓缩咖啡的苦味可能不适合一些顾客,但这种混合已经抵消了。
多样性的定义?
然而,多样性的确切定义在任何地方都没有定义。然而,经验表明,在大多数情况下,集合的性能优于单个模型。由于数据科学是一个需要经验证据的领域,能够在大多数时间工作实际上比你想象的更有说服力。这意味着它能够解决偏差-方差困境,通过使用模型集合减少方差,而不增加偏差。
从金融角度来看这个问题,这个概念与投资组合多样化非常相似。如果投资组合中的每只股票都是独立的或者相关性很低。非系统风险(每个模型的差异或限制)可以通过多样化来减轻,而每个单独组件的回报不会改变。
Python 演示
在本文中,我将演示集成平均在回归问题和分类问题上的实现。

分类
首先,我们从 Scikit Learn 导入一些必要的模块。
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.tree import DecisionTreeClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import VotingClassifier
from sklearn.neighbors import KNeighborsClassifier
我们使用来自 Scitkit Learn 的 make_classification()来模拟具有 50 个特征和 10000 个样本的二元分类问题。
模拟数据被进一步分成训练集和测试集。
X, y = make_classification(n_samples=10000, n_features=50, n_redundant= 15, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30, random_state=43)
现在,我们训练几个独立的模型来预测分类问题。在本文中,我们将使用决策树分类器,KNN 和朴素贝叶斯。
models = [('Decision Tree', DecisionTreeClassifier()),
('KNN', KNeighborsClassifier()),
('Naive Bayes', GaussianNB())]for name, model in models:
model.fit(X_train, y_train)
prediction = model.predict(X_test)
score = accuracy_score(y_test, prediction)
print('{} Model Accuracy: {}'.format(name,score))

作者输出的代码
在拟合模型之后,我们可以观察到模型本身已经做得很好了。让我们看看使用 Scitkit Learn 的 VotingClassifier 实现集成技术后的结果。
ensemble = VotingClassifier(estimators=models)
ensemble.fit(X_train, y_train)prediction = ensemble.predict(X_test)
score = accuracy_score(y_test, prediction)
print('Ensemble Model Accuracy: {}'.format(score))

作者输出的代码
VotingClassifier 通过使用多数规则投票选择预测的类别标签来实现三个模型的集成。例如,如果有两个模型预测实例为 A 类,而只有一个模型预测为 B 类,则预测的最终结果将是 A 类。
从这个实验中,我们可以看到,在我们实现集成平均之后,预测性能有了显著的提高。
回归

该技术也可以应用于回归问题。类似于我们在分类问题中介绍的,我们首先从 Scitkit Learn 导入一些必要的模块。
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.datasets import make_regression
from sklearn.svm import SVR
from sklearn.tree import DecisionTreeRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.ensemble import VotingRegressor
我们这次用 make_regression()模拟一个 10 个特征,10000 个样本的回归问题。
模拟数据再次被进一步分成训练集和测试集。
X, y = make_regression(n_samples=10000, n_features=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30, random_state=43)
我们训练支持向量回归机、决策树回归机、KNN 来预测回归问题。
models = [('Support Vector', SVR()),
('Decision Tree', DecisionTreeRegressor()),
('KNN', KNeighborsRegressor())]
score = []for name, model in models:
model.fit(X_train, y_train)
prediction = model.predict(X_test)
score = mean_squared_error(y_test, prediction, squared = False)
scores.append(score)
print('{} Model RMSE: {}'.format(name,score))

作者输出的代码
像往常一样,我们现在使用 Scitkit Learn 的 VotingRegressor 实现集成技术。
ensemble = VotingRegressor(estimators=models)
ensemble.fit(X_train, y_train)prediction = ensemble.predict(X_test)
score = mean_squared_error(y_test, prediction, squared = False)
print('Ensemble Model RMSE: {}'.format(score))

作者输出的代码
VotingRegressor 的工作方式与 VotingClassifier 非常相似,只是它通过平均各个预测来实现集成,从而形成最终预测。
额外的实用技巧
在 VotingRegressor 和 VotingClassifier 中,我们可以利用参数“权重”来改变集成结果。“权重”参数指定了每个模型在最终结果中的能力。例如,在这个回归问题中,我们可以看到 KNN 模型在预测结果方面比其同行相对更强。在这种情况下,我们可以为其对最终预测的贡献分配更强的权重。为此,我们只需将权重列表传递给 VotingRegressor 函数。

事实上,我们可以根据模型相对于其他模型的排名来分配它们的权重。通过这样做,我们让那些预测更好的人在最终结果的投票中有更大的发言权。
scores = [-x for x in scores]
ranking = 1 + np.argsort(scores)
print(ranking)

作者输出的代码
使用模型中的 RMSE,我们实现了 np.argsort 来根据它们的等级对它们进行排序。由于 np.argsort 从一个较小的值开始排序,所以我们在排序之前先对分数取反。这样,RMSE 较低的模型将具有较高的权重。例如,KNN 模型的权重为 3 分(满分为 6 分)。
让我们再训练一次,看看我们的结果是否会更好。
ensemble = VotingRegressor(estimators=models, weights = ranking)
ensemble.fit(X_train, y_train)prediction = ensemble.predict(X_test)
score = mean_squared_error(y_test, prediction, squared = False)
print('Ensemble Model RMSE: {}'.format(score))

作者输出的代码
不幸的是,与正常平均相比,我们的集合模型这次具有更高的 RMSE。您可能会问,我们如何知道设置哪个权重,以便为每个模型获得最合适的权重?
幸运的是,我们可以使用堆叠回归器,它将从预测器中学习最佳线性组合,这能够以提高的预测精度再次达到最终输出。我将在我的下一篇文章中详细阐述更多关于 堆叠回归变量 。
外卖
整体平均是一项伟大的技术,可以通过引入多样性以减少方差来提高模型性能。请注意,尽管这种技术经常奏效,但它并不像药丸那样奏效。有时,您可能会看到模型的集合比单个模型工作得更差。
然而,鉴于它易于实现,它绝对值得被包含在您的机器学习管道中。
再次感谢您在百忙之中抽出更多的时间来阅读这篇文章。

用于机器学习的集成特征选择
原文:https://towardsdatascience.com/ensemble-feature-selection-for-machine-learning-c0df77b970f9
通过组合单个特征选择方法来选择最佳特征

这篇文章的大部分内容来自我的论文:
“环境数据特征选择方法的评估”,有兴趣的人可以在这里找到。
在我之前的文章中,我介绍了 12 种单独的特征选择方法。本文是特性选择的下一步。
如果你在这里,我想你已经熟悉了在分类算法上建立良好的集成,它提供了比使用单一算法更好的结果和鲁棒性。相同的原理可以应用于特征选择算法。
集成特征选择
集成特征选择背后的思想是结合多种不同的特征选择方法,考虑它们的优势,并创建最优的最佳子集。
总的来说,它构成了更好的特征空间,降低了选择不稳定子集的风险。
此外,单个方法可能产生被认为是局部最优的子集,而集成可能提供更稳定的结果。
集成方法
集合方法是基于投票系统的。
为了使用它们,我们首先必须运行至少几个单独的特征选择方法,并获得它们为每个特征给出的排名。
1.博尔达伯爵
Borda Count 是最著名的投票系统之一。它相当简单,根据特征选择方法的各个等级的总和给每个特征打分。
然后从特征总数中减去这个分数。因此最强的特征将具有最大等级 N ,其中 N 是特征。
计算 Borda 计数的简单公式是:

其中 j = 1,2,…,N 为特征选择方法, rj(f) 为特征按照 j 方法的等级。
博尔达计数可以有许多名称,如线性聚集,平均平均排名和排名聚集。
2.倒数排名
倒数排名基于根据以下等式对特征 f 的最终排名 r(f) 的计算:

它相当于调和平均秩,也称为逆秩位置。
3.瞬时径流
这是一种用于选举的投票方法,是 1850 年单一可转移投票的特例。
对于 ML,特征选择方法是投票者,特征是候选者。在选举中,获胜的候选人获得第一优先选择的多数选票,即超过 50%的选票。如果没有这样的候选人,得票最少的候选人将被除名,下一个优先候选人将取而代之。
重复这一过程,直到找到获胜者。为了创建特征(候选)的排名,每次发现获胜者时,它被放置在排名列表上,并且该方法对剩余的特征再次运行。
4.孔多塞
这种方法从偏好等级列表中选择获胜者,属于配对比较的范畴。
在一对一的战斗中“赢得”所有其他特征选择方法的特征被称为孔多塞赢家。为了应用孔多塞方法,有必要创建一个尺寸为 n × n 的表格 T ,其中 n 是特征的数量。
该表被置零,并且特征比较被初始化。如果特征 fi 优于特征 fj (意味着它的等级更高),那么将‘1’添加到单元 ti,j 。表 T 完成后,对于每个元素,两个特征之间的胜者确定如下:
如果 ti,j > n/2 ,那么 feature fi 获胜,否则 feature fj 获胜,而他们之间可以有平局。
每个特征的最终得分从配对产生的所有单独得分的总和中扣除,因此最终排名可以被导出。
在平局的情况下,他们的排名将是随机的。
5.库姆斯
这是排名榜的另一种投票系统。它类似于即时决胜,因为它每轮删除一个功能,并将投票重新分配给剩余的功能,直到其中一个获得多数票。
每种特征选择方法都按优先顺序排列特征。对最后位置持有最多投票的特征被记录在最终合并列表的最后位置。
对剩余的特征进行新一轮,并且该过程继续直到完成。
6.巴克林
这是另一个系统,它基于拥有大多数选票的获胜者的选举。
然而,目前的制度在选举方式上有所不同,在这种情况下,没有候选人成功地从第一轮选举中收集到获得多数票所需的票数,需要增加下一轮选举的票数。
重复该过程,直到至少一个特征获得多数票。
当找到这个特征时,它被添加到最终的合并列表中。然后,Bucklin 再次运行,直到所有特征都在最终列表中排序。
集成特征选择是否优越?
我在一个分类任务的 8 个数据集中比较了 12 种单独的特征选择方法和上述的 6 种集成方法(可以在论文中看到)。
结果如下表所示:

结果按 Friedman rank 排序,它只是计算每个方法在数据集中的平均排名。
根据该表,最好的方法是倒数排名,与第二种方法(SHAP)相差近 1.4 位。
除了 Coombs 之外,所有集成方法都优于无特征选择基线,并且实现了高性能。
总之,集成特征选择方法,尤其是倒数排序值得关注,因为它们可以提供高性能。
然而,如果寻找一个强大的个人方法,SHAP 是一个使用。
不确定接下来要读什么?这里有两个选择:
保持联络
关注我在媒体上的更多内容。
我们在 LinkedIn 上连线吧。检查我的 GitHub。
sklearn 中的集成学习
原文:https://towardsdatascience.com/ensemble-learning-in-sklearn-587f21246e8d
了解如何在 sklearn 中与 pipelines 和 GridSearchCV 一起执行集成学习

在我之前的文章中,我谈到了在 sklearn 中使用管道:
在那篇文章中,您学习了如何在 sklearn 中使用管道来简化您的机器学习工作流。您还了解了如何使用GridSearchCV()和管道来为您的数据集寻找最佳估计器。
如果能够利用多个机器学习模型(估计器)对数据集进行单独预测,而不是仅仅依赖于一个估计器,这将非常有用。然后,可以将这些结果结合起来进行最终预测:

作者图片
这就是所谓的合奏学习。总的来说,集成学习模型的性能通常比使用单个估计器要好。
因此,在本文中,我将向您展示如何在 sklearn 中使用集成学习来为您的数据做出更好的预测。对于代码的前几个部分,我将使用我在上一篇文章中创建的管道。因此,如果您不熟悉管道,请务必在继续之前先阅读这篇文章。
加载数据
对于本文,我们将使用流行的泰坦尼克号数据集,我们将从 ka ggle(【https://www.kaggle.com/c/titanic/data?select=train.csv】)下载:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_splitdf = pd.read_csv('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)
数据来源 :本文数据来源于https://www.kaggle.com/c/titanic/data.
定义管道
接下来的两节将定义转换数据集中的列的管道。
定义变压器
我们将首先为不同的列定义不同的转换器:
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.utils.validation import check_is_fitted
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder**# custom transformer to select specific columns**
class FeatureSelector(BaseEstimator, TransformerMixin):
def __init__(self, feature_names):
self._feature_names = feature_names
def fit(self, X, y = None):
return self def transform(self, X, y = None):
return X[self._feature_names]**# 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'))
])
如果您不熟悉自定义变压器,请参考本文:
转换列
接下来我们将使用ColumnTransformer类来转换所有需要的列:
from sklearn.compose import ColumnTransformerfeatures_preprocessor = ColumnTransformer(
transformers=[
('numeric', numeric_transformer, ['Age','Fare']),
('categorical1', categorical_transformer1, ['Sex',
'Embarked']),
('categorical2', categorical_transformer2, ['Pclass'])
], remainder='passthrough')
使用 GridSearchCV 为每个分类器寻找最佳估计量
现在我们已经定义了转换列的管道,是时候为我们的预测尝试不同的算法了。让我们一步一步来。
首先,导入所需的模块:
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn import svmimport warnings
warnings.filterwarnings('ignore')
然后,创建一个列表来存储要使用的算法(分类器)列表:
**# the list of classifiers to use**
# use random_state for reproducibility
classifiers = [
LogisticRegression(random_state=0),
KNeighborsClassifier(),
RandomForestClassifier(random_state=0)
]
为了再现性,我将第一个和最后一个分类器的 random_state 设置为 0。
对于本例,我们将使用:
- 逻辑回归
- k-最近邻(KNN)
- 随机森林
接下来,我们为每个分类器指定我们想要调整的各种超参数:
**# parameter grids for the various classifiers**
logregress_parameters = {
'**classifier__**penalty' : ['l1','l2'],
'**classifier__**C' : np.logspace(-3,3,7),
'**classifier__**solver' : ['newton-cg', 'lbfgs', 'liblinear'],
}knn_parameters = {
'**classifier__**n_neighbors': np.arange(1, 25, 2)
}randomforest_parameters = {
'**classifier__**n_estimators': [50, 100, 200, 300]
}
注意每个超参数的前缀
classifier__。这个前缀与您稍后将在管道中使用的分类器的名称相关联。
然后,将所有这些超参数存储在一个列表中:
**# stores all the parameters in a list**
parameters = [
logregress_parameters,
knn_parameters,
randomforest_parameters
]
创建一个名为estimators的列表,它将存储所有调优的估计器。该列表将包含以下格式的元组集合:( name_of_classifier , tuned_estimator
estimators = []
最后,创建一个循环来调整每个分类器。对于每个优化的分类器,打印出优化的超参数及其精度。此外,将分类器和调优的估计器的名称添加到estimators列表中:
**# iterate through each classifier and use GridSearchCV**
for i, classifier in enumerate(classifiers): **# create a Pipeline object**
pipe = Pipeline(steps=[
('preprocessor', features_preprocessor),
('classifier', classifier)
]) clf = GridSearchCV(pipe, # model
param_grid = parameters[i], # hyperparameters
scoring='accuracy', # metric for scoring
cv=10) # number of folds clf.fit(X, y)
print("Tuned Hyperparameters :", clf.best_params_)
print("Accuracy :", clf.best_score_) **# add the clf to the estimators list**
estimators.append((classifier.__class__.__name__, clf))
整个代码块如下所示:
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn import svmimport warnings
warnings.filterwarnings('ignore')**# the list of classifiers to use**
# use random_state for reproducibility
classifiers = [
LogisticRegression(random_state=0),
KNeighborsClassifier(),
RandomForestClassifier(random_state=0)
]**# parameter grids for the various classifiers**
logregress_parameters = {
'classifier__penalty' : ['l1','l2'],
'classifier__C' : np.logspace(-3,3,7),
'classifier__solver' : ['newton-cg', 'lbfgs', 'liblinear'],
}knn_parameters = {
'classifier__n_neighbors': np.arange(1, 25, 2)
}randomforest_parameters = {
'classifier__n_estimators': [50, 100, 200, 300]
}**# stores all the parameters in a list**
parameters = [
logregress_parameters,
knn_parameters,
randomforest_parameters
]# estimators is a list of tuple ->
# [(name_of_classifier, tuned_estimator)]
estimators = []**# iterate through each classifier and use GridSearchCV**
for i, classifier in enumerate(classifiers): **# create a Pipeline object**
pipe = Pipeline(steps=[
('preprocessor', features_preprocessor),
('classifier', classifier)
]) clf = GridSearchCV(pipe, # model
param_grid = parameters[i], # hyperparameters
scoring='accuracy', # metric for scoring
cv=10) # number of folds clf.fit(X, y)
print("Tuned Hyperparameters :", clf.best_params_)
print("Accuracy :", clf.best_score_) **# add the clf to the estimators list**
estimators.append((classifier.__class__.__name__, clf))
运行这段代码后,您将看到以下输出:
Tuned Hyperparameters : {'classifier__C': 0.1, 'classifier__penalty': 'l2', 'classifier__solver': 'liblinear'}
Accuracy : 0.7934956304619226
Tuned Hyperparameters : {'classifier__n_neighbors': 5}
Accuracy : 0.8204619225967539
Tuned Hyperparameters : {'classifier__n_estimators': 50}
Accuracy : 0.8148564294631709
如您所见,性能最好的估计器是邻居为 5 的 KNN,精度为 0.82。
集成学习
现在您已经用GridSearchCV()调好了三个分类器的超参数,现在您可以使用VotingClassifier类将它们放在一起用于集成学习。使用这个类,您可以传入您的调优估计器,并选择如何使用它们进行预测:
from sklearn.ensemble import VotingClassifier**ensemble = VotingClassifier(estimators, voting='hard')**
voting参数有两个可能的值:
hard—也称为 多数表决 ,这意味着每个估计器将继续做出自己的预测。在预测结束时,由大多数估计器预测的标签将是最终预测的标签:

参数投票= '硬'(图片由作者提供)
ensemble = VotingClassifier(estimators, **voting='hard'**) # default is
# 'hard'
如果出现平局,预测的类标签将按升序排序,并将选择顶部的标签。
soft—也称为 加权平均概率投票 ,这意味着每个估计器将生成每个类别的概率。
ensemble = VotingClassifier(estimators, **voting='soft'**)
要使用
*soft*投票,您的所有评估人员必须支持*predict_proba()*方法。
在该过程结束时,对每个类的所有概率进行求和,并且具有最高求和概率的类将是最终的预测标签:

参数投票= '软'(图片由作者提供)
您还可以为每个评估者分配权重:
ensemble = VotingClassifier(estimators,
voting='soft',
**weights=[1,1,1]**) # n-estimators
当您为每个估计值分配权重时,它们将应用于每个类的计算概率。然后计算每个类别的加权平均值,具有最高加权平均值的类别是最终预测的标签:

将权重应用于集合模型(图片由作者提供)
权重也可以应用于
hard投票。
现在,您可以用您的训练数据来调整整体:
ensemble.fit(X_train, y_train)
然后用你的测试集来评分:
ensemble.score(X_test, y_test)# 0.8507462686567164
正如你所观察到的,集合模型的精度高于每个单独的估计量。
最后,您可以使用自己的数据进行预测:
# test data for 2 passengers
test_data = {
'Pclass' : [2,1],
'Sex' : ['male','female'],
'Age' : [35,15],
'Fare' : [90,20],
'Embarked' : ['S','Q']
}ensemble.predict(pd.DataFrame.from_dict(test_data))
# array([0, 1])
在上面的输出中,第一个乘客没有幸存(0),而第二个乘客幸存(1)。
摘要
这篇短文解释了集成学习是如何工作的。特别是,我将它与 pipelines 和GridSearchCV()结合起来,使你的机器学习工作流程正式化。如果你在你的机器学习项目中使用集成学习,请在评论中告诉我。
https://weimenglee.medium.com/membership
机器学习中的集成
原文:https://towardsdatascience.com/ensembles-in-machine-learning-9128215629d1
包含 Python 示例和代码的教程

拉里莎·比尔塔在 Unsplash 上拍摄的照片
TL;速度三角形定位法(dead reckoning)
集成方法是机器学习(ML)中公认的算法基础。就像在现实生活中一样,在 ML 中,一个专家委员会通常会比一个人表现得更好,只要在组成委员会时给予适当的关注。自从最早的 ML 研究以来,随着随机森林和梯度推进成为当今分类中的前沿方法,已经开发了各种集成策略。本教程中的主要建议如下:
- 如果目标是最大化精度,梯度推进可能被认为是 ML [16]中领先的监督学习方法。
- 随机森林还可以实现非常好的准确性,并具有通过特征重要性机制提供数据洞察力的额外优势。
- 理解堆叠是如何工作的是值得的,因为它在汽车中有一个新兴的角色。
介绍
从最大似然研究的早期开始,人们就认识到分类器的集合比单个模型更准确。在 ML 中,集成实际上是集合单个分类器预测的委员会。它们有效的原因与专家委员会在人类决策中工作的原因非常相似,它们可以利用不同的专业知识,平均效应可以减少误差。这篇文章提供了一个关于在 ML 中使用的主要集成方法的教程,链接到 Python 笔记本和数据集来展示这些方法。目的是帮助从业者开始学习 ML 课程,并提供一个关于课程在什么时候和为什么有效的见解。
对 ML 系综的研究可以追溯到 20 世纪 90 年代[25,1,6]。从那时起,已经有了很多的发展,集合思想仍然是 ML 应用中的前沿。例如,随机森林[2]和梯度推进[7]被认为是当今 ML 从业者可用的最强大的方法。
图 1 给出了一般的集合概念。所有集成都由一组基本分类器组成,也称为成员或估计器。当提出一个查询时,它们将各自做出一个预测,并且这些预测将被组合以产生一个集合预测。不同的集成策略在如何精确地训练基本分类器以及如何实现预测的组合上有所不同。出于本教程的目的,我们将集合方法分为四类:
- Bagging:Bootstrap aggregation(Bagging)指的是通过对数据的随机 Bootstrap 重采样进行训练来实现估计量多样性的集成。这些估计器的输出的集合是通过平均或多数表决来实现的。在这个范畴下,我们也考虑基于随机子空间而不是随机子集的系综。随机森林也包括在此类别中。

图 1 :集成是一组产生预测的分类器(估计器),这些预测被组合起来产生一个集合预测。不同的集成架构通过如何训练分类器和如何执行聚合来区分。图片作者。
- Boosting :当 bagging 集成成员被有效地独立训练时,利用 Boosting,估计器被串行训练,一个新成员的训练被整体性能影响至今。估计器的性能也决定了它们在汇总过程中的作用。梯度推进寻求优化新估计器的训练与聚合过程。
- 异构集成:既然有太多的分类器方法可用,为什么不使用各种模型来实现集成的多样性呢?虽然这似乎是一件显而易见的事情,但异构集成在研究文献中并没有得到太多的关注,在实践中也没有得到太多的使用。这个主题将在下面的中介绍。
- 堆叠:(又名堆叠一般化)堆叠将聚集过程本身视为一个学习过程。元模型被训练来学习估计器的输出和目标之间的关系。用于此目的的数据不应用于训练估算者,因此叠加会带来一些数据管理挑战。
本文包括这四种总体架构的每一个部分。在下一节中,我们将提供一些可以追溯到 18 世纪的历史背景。然后,我们提出了一个框架来分类预测误差,因为这提供了一些重要的见解,何时和为什么集成可以提高准确性。在开始深入讨论主要的合奏类别之前,我们提供一个关于合奏如何工作的高级总结。文章最后提出了一些建议和结论。
历史关联
从某种意义上说,合奏体现了“两个脑袋比一个脑袋好”的理念,这一点已经为人所知。事实上,孔多塞侯爵在 1785 年提出的孔多塞陪审团定理[3]声称,委员会的决策将优于个人的决策。孔多塞陪审团定理指出,对于一个由 n 个投票人组成的委员会,其中每个投票人都有正确的概率 p ,并且大多数投票人正确的概率是 M ,那么:
- p>0.5M>p
- p>0.5M→1.0asn→∞
也就是说,如果一个人有超过 50%的机会是正确的,那么一群这样的人就更有可能是正确的。如果系综中存在多样性,第一种说法通常是正确的。毕竟,如果所有成员都以同样的方式投票,那么这个群体就不会比个人更好。因此,选民群体中必须有多样性——也就是说,他们的决定之间必须有一些分歧。
第二种说法是,规模较大的委员会很可能做出正确的决定,这种说法更有问题。只有当集合中的多样性继续增长时,大多数投票者正确的概率才会随着集合的增长而增加[15]。最终,新成员将拥有与现有成员共线的投票模式,也就是说,他们将以同样的方式投票。一般来说,集合的多样性会趋于平稳,在 10-50 个成员之间的某个规模时,集合的准确性也会趋于平稳。
误差成分
集成的好处通常是根据集成如何有助于减少误差来描述的。在这种情况下,误差有三个组成部分:
- 贝叶斯误差:(又名不可约误差)如果一个模型无法访问影响结果的所有特征,那么该模型不可能 100%准确。设想一个预测房价的模型,它没有关于房产位置的信息。通常,缺失的信息不会如此明显,但关键是模型无法将误差降至零。这个剩余误差就是贝叶斯误差,有时也称为不可约误差。因此,贝叶斯误差代表了性能的极限,并不是所有的模型都能达到。
- 偏差:如果模型没有达到贝叶斯误差,附加误差有两个分量,其中一个是偏差。在回归分析中,如果预测分布的平均值(趋势)高于或低于应有值,则偏差可能很明显(见图 2 左栏)。在分类中,某个特定类别可能在预测中被低估或高估。

图 2 :误差的偏差和方差分量的图示。这是一个 2D 回归任务,目标在点(0,0)。目标显示为红色,对该目标的预测显示为蓝色。图片作者。
- 方差:在回归中,一个模型可以有很低的偏差,但仍然有很高的误差(图 2 右上角的图像)。预测的平均值是正确的,但是预测变化很大。在分类中,模型不会偏向单一结果,但错误会表现为多个方向的错误分类。
图 2 右上角的“低偏差高方差”示例显示了集合与 James Surowiecki [21]在书中提出的“群体智慧”理念之间的联系。这本书以高尔顿 1907 年在《自然》杂志上发表的“猜猜这头牛的重量”为例[8]。在这个例子中,787 人参加了一场在县集市上猜一头牛的重量的比赛。高尔顿分析了这些估计值,结果表明,虽然它们差异很大,但估计值的中位数在正确权重的 1%以内。同样,在图 2 中,我们可以看到集合如何通过对许多估计值求平均值来减少误差的方差分量。这是对系综的主要主张,即系综可以减少误差的方差分量。稍后我们将会看到,提升系综在某些情况下也可以减少误差的偏置分量。
为什么合奏会起作用?
在检查具体的合奏策略的细节之前,有必要看看合奏所依赖的一般原则。前面我们从孔多塞陪审团定理和随后的政治学著作中看到,有两个重要的考虑因素:
- 合理范围内,合奏成员越多越好。随着更多成员的加入,集合的准确性将增加,直到最终新成员不再带来新信息。
- 多样性:为了使集合的精度优于个体,集合成员中必须存在多样性。

图 3 :系综大小和多样性对精度的影响。在左边,随着更多估计量(集合成员)的增加,精确度增加。在右边,对于 10 个估计量的集合,可以通过使用较小的数据子集来增加多样性,从而减少训练集重叠。随着估计量越来越不相似,精确度也越来越高。作者图表。
这些因素的影响在第一个系综-预备笔记本中有说明。这些结果如图 3 所示。这些结果是使用来自 scikit-learn 的 bagging ensemble 实现在来自 UCI 知识库的葡萄酒数据集上生成的。使用交叉验证获得准确度估计值。集合大小的影响显示在左边的图 3 中。利用两个估计器(成员),集合具有 0.87 的准确度。这在 7 个估计量的情况下增加到 0.91 以上,但是随着更多估计量的增加没有实质性的增加。这种先增加后稳定的模式是典型的,尽管对于其他数据集来说,在添加更多的估计量之前可能不会达到稳定状态。
说明多样性的影响并不简单,因为多样性不太容易量化。在右边的图 3 中,我们用来管理多样性的策略是控制用来训练估计量的数据集中的重叠。集合大小总是 10,并且第一个集合成员使用没有替换的采样数据的 95%来训练。所以这些估值器彼此非常相似。然后使用 90%的数据训练下一个集合,依此类推。因此,通过用越来越少的可用数据训练估值器,允许它们不那么相似,多样性就增加了。推测起来,当使用较少的数据时,这些估计器变得不太准确,但是总体准确性增加,因为个体估计器中的任何准确性损失都被多样性的增加所抵消。最终好处会逐渐消失,因为在不犯错的情况下增加多样性变得越来越困难。
量化多样性
如果多样性是决定集合有效性的一个如此重要的因素,能够量化它将是很好的。Krogh 和 Vedelsby [12]已经证明了回归系综中误差和模糊性(多样性)之间的以下非常重要的关系:

其中 E 是输入分布上的总体误差,是总体分量的平均泛化误差,ā是输入分布上的平均总体模糊度。是标准平方误差估计(L2 损失),而ā是单个模糊度 ā(x) 的集合(平均值),单个输入 x 上的 N 个估计器的集合的模糊度:****

其中求和项是来自每个估计器 k 的估计和平均估计之间的差的平方。因此,模糊度实际上是来自集合成员的预测的方差。
不幸的是,对于分类器来说,不存在这样简洁的分析。Kuncheva 和 Whitaker [14]分析了分类器集合的 10 种不同的多样性度量。他们发现,他们都在量化多样性方面做了合理的工作,但在进行了相当广泛的评估后,他们得出结论,没有单一的赢家。在 Kuncheva 和 Whitaker 的分析中,他们区分了成对和非成对测量。为了使用成对度量来评估集合多样性,对所有的 N 估计量对计算度量,并取平均值。他们的分析考虑了四个成对的和六个非成对的测量。
为了深入了解多样性度量的工作原理,我们在这里提供了两个例子,每种类型一个。也许最直接的成对测量是简单的不一致测量23。该度量简单地计算两个分类器不一致的测试集的比例,该度量在范围[0,1]内。两个分类器 h ᵤ和 h ᵥ的简单不一致度量是:

其中 m 是数据集中实例的数量,求和过程计算两个分类器之间的差异。即 Diff(a,b) = 0 ,如果 a=b ,否则 Diff(a,b) = 1 。总体总体分集将是这些度量的N×(n1)的平均值。在本文的一些评估中使用了这种简单的不一致度量,例如参见图 5 和图 11。**
相比之下,非成对测量一次考虑所有系综成员。在单个测试样本的水平上,我们想要测量该样本的一组 N 预测的多样性。这个集合的熵是一个显而易见的选择。那么对于有 C 个类别的 m 个样本的测试集,熵是[4,14]:

其中, P 项是样本 i 预测中类别 k 的频率——预测中的离散度或随机性越大,差异越大。
装袋(和变体)
bagging 系综的想法是由 Breiman 在 1996 年提出的[1]。Bagging 通过 bootstrap 聚合工作,因此得名。分类器集合中的多样性(图 1)是通过 bootstrap 采样实现的,然后通过简单多数投票聚合预测。Bootstrap 抽样是指替换抽样。关于 bagging 的 Python 笔记本开头的一个简单例子说明了 bootstrap 采样的特征。我们创建一个包含 1,000 个唯一样本的列表,然后从中创建一个大小为 1,000 的子样本。如果用替换法进行抽样,我们发现大约 63%的样本被选中,有些被多次选中。这意味着每个“袋子”中有 37%的样本未被选中,这些是袋外(OOB)样本。我们以后会看到,这些证明是非常有用的。
为了使用 bagging 实现如图 1 所示的集成,用来自训练数据的引导样本训练集成成员。通常,这些引导样本与完整训练集的大小相同。在正确的情况下,正如我们将看到的,这种采样策略将产生足够的多样性,以产生一个有效的集合。聚集步骤是简单地对全体成员进行多数投票,其中成员具有相等的票数。

图 4:bagging 对稳定和不稳定分类器的效果。(a)装袋对 k-NN(一种稳定的分类器)影响较小。(b)对于五个不同的分类器,bagging 仅提高了两个不稳定模型(树和神经网络)的准确性。作者图表。
在图 4 中,我们看到 bagging 是如何在葡萄酒数据集上工作的。这些结果来自装袋笔记本中的评估。在图 4(a)中,我们看到随着更多估计器(即模型或分类器)的加入,神经网络集成的集成精度提高。然而,用 k -NN 系综没有实现类似的改进。
原因很好理解,bagging 只对不稳定的分类器有效[1,20]。在这种情况下,不稳定分类器是指模型输入(在这种情况下是训练数据)的微小变化会产生明显不同的模型。利用 bagging,这种不稳定性是一个优势,因为它产生了集合工作所需的多样性。在图 4(b)中,我们展示了用五种不同基础模型构建的 bagging 系综的结果。已知决策树和神经网络是不稳定的,而逻辑回归、 k -NN 和朴素贝叶斯是稳定的。果不其然,装袋对不稳定的模型有好处,但对其他模型没有好处。
我们可以使用简单的不同意度量来进一步探讨这一点。图 5 显示了神经网络和各有五个成员的 k -NN 集成的简单不一致测量的彩色图。神经网络集成的情况相当健康,估计量之间存在显著的成对不一致。与 k -NN 集合的情况非常不同,一些估计器仅在百分之几的样本上不同。所以 k -NN 面对训练数据的显著变化是稳定的。这意味着用 k -NN 装袋不起作用。然而,还有其他可能的策略,其中最流行的是随机子空间。

图 5 :这些彩色地图显示了两个五人合奏的简单不一致测量。神经网络集成差异很大,而 k-NN 集成差异很小。在这些彩色地图中,颜色越浅越好,即差异越大。作者图表。
随机子空间
在图 6 所示的例子中,打包需要选择原始数据集中的行的不同子集。相反,随机子空间[11,10]将选择如图所示的列的不同子集。我们在图 4 中看到,当基本分类器稳定时,装袋没有任何好处。图 5 中的彩图表明,这是因为不同的 bootstrap 训练集导致估计器的输出变化很小。**
图 6 显示了确保多样性的随机子空间策略。不是对行进行二次抽样,而是对列进行二次抽样。如果所有的特征代表完整的特征空间,不同的估计器在随机子空间上被训练。这可能不会立即显而易见,但这通常将是实现多样性的更具决定性的机制。不同的评估者将反映对数据的不同观点。一些估计者将无法获得最具预测性的特征,并将需要依赖预测性较低的特征。研究表明,随机子空间策略确实能提高稳定估计量[10,11,13]。

图 6 :基于随机子空间的集成——使用特征的随机子集来训练不同的集成成员。作者图表。
随机子空间结合 k -NN 对葡萄酒数据集的影响如图 7 所示。图 7 中再次示出了来自图 4(b)的原始神经网络和 k -NN 结果,但是添加了基于随机子空间的集成结果。鉴于套袋对 k -NN 无效,随机次间隔确实产生一些改善。图 4 和图 7 中所示的多个模型组合的结果表明,97%至 98%的交叉验证准确度可能是该数据集可实现的最佳准确度,即贝叶斯误差约为 2%。

图 7 :组合 k-NN 中随机子空间的影响。虽然装袋对 k-NN 没有改善,但随机子空间却有。这两种策略对神经网络同样有效。作者配图。
在 scikit 中打包-了解
在同一类别中考虑打包和随机子空间的一个理由是,它们是在 scikit-learn 中的单个集成框架中实现的。bagging classifier框架有以下参数:****
- base_estimator :组件分类器类型。
- n 个估计者:估计者的数量。
- max_samples :用于训练每个估计器的样本数量(或比例)。
- bootstrap :表示采样是否为 bootstrap 的二元特征。
- max_features :每个估计器中使用的特征数量(或比例)。
这些参数的正确组合将指示集合是使用 bagging 还是随机子空间。如果 max_samples 和 max_features 设置为 1,并且 bootstrap 设置为 True ,我们就可以装袋了。例如,如果 max_samples 设置为 1, max_features 设置为 0.5,并且 bootstrap 设置为 False ,则我们有随机子空间。我们也可以把装袋和随机子空间结合起来;这就是随机森林的情况。
随机森林
随机森林代表基于特征和样本选择的集合中的最先进的状态。顾名思义,基础估计器是一个决策树。随机森林使用自举和随机间距来确保多样性[2]。树的数量倾向于相对较大,因为作为集合构建过程的副作用,通常会生成对概括准确度和特征重要性的估计。
对于由一组特征 F 描述的 m 实例的数据集 D ,策略是构建大量的树,通常是 100 到 1000 棵树。然后,对于每棵树:
- 如同在 bagging 中,对于每个系综成员,训练集 D 用替换进行子采样,以产生大小为 m 的训练集。
- 其中 F 是描述数据的特征集合, q ≪|F| 被选为特征选择过程中要使用的特征数量。在构建树的每个阶段(即节点),随机选择 q 个特征作为在该节点进行分裂的候选。
假设我们正在处理一个有很多超参数的模型,那么检查一下 scikit-learn 中的默认参数是很有趣的。默认的树数是 100。默认引导集大小为 m 。树修剪参数被设置为没有修剪,即树是浓密的。默认情况下 q = √|F| 。在关于随机森林的原始论文中,Breiman 强调了除预测准确性之外的许多好处[2]。人们普遍认为,OOB 概化精度估计和可变重要性分数是随机森林方法的显著优点。
****OOB 泛化估计:随机森林的一个突出优点是,无需执行交叉验证或保留测试数据,就有可能获得非常好的泛化精度估计。事实上,这种 OOB 策略可以被视为一种隐含的交叉验证。流程如下:
*****1\. Identify all samples that are OOB in some trees
2\. For each OOB sample:
2.1\. Find the trees not trained using that sample
2.2\. Generate predictions using those trees for that sample
2.3\. Determine the majority prediction and compare with the true
value
3\. Compile the OOB error on all the OOB samples*****
这种 OOB 策略对于获得泛化精度的精确估计确实有效。图 8(a)简单说明了这一点。在这个评估中,我们保留了三分之一的葡萄酒数据集进行测试,并根据训练数据训练随机森林。对于具有 10 到 100 个估计量的随机森林,重复这个过程。每个步骤重复 50 次,以确保可靠的结果。该代码可在 Ensembles-RandomF 笔记本中找到。评估表明,一旦集合具有 60 个估计量,概括估计就与来自重复保持测试的估计一致。值得记住的是 scikit-learn 中默认的随机森林大小是 100。

图 8 :随机森林的两个好处是:( a)泛化精度的 OOB 估计,( b)特征重要性的估计。作者图表。
****特征重要性:随机森林特征重要性机制的基本原理是给变量添加噪声,看看会发生什么。实际细节与已经描述过的 OOB 战略密切相关。对于每个特征,我们依次置换 OOB 样本中该特征的值,并通过这些样本为 OOB 的树重新运行这些样本。置换值的策略是混洗特定特征的值,即该特征的列中的值被混洗。比较置换前后的广义误差估计。如果一个变量是重要的,这种洗牌将产生重大影响。
葡萄酒数据集的可变重要性分数如图 8 (b)所示。例如,我们看到 Ash 特征具有低特征重要性分数,因为它对错误的影响最小。相比之下,脯氨酸似乎是一个非常重要的特征。这被证明是评估变量/特征重要性的非常有效的机制,因为特征是在上下文中评估的。
助推
增强指的是集成可以将弱学习者“增强”成任意精确的强学习者的方式。在 PAC 学习理论中,【19】弱学习者是指仅比随机猜测稍好的学习者。给定足够的训练数据,强学习者可以实现任意低的错误率。在 boosting 中,弱学习者通常是一个决策树桩,一个只有一个决策节点的决策树,如图 9(c)所示。
而对于 bagging,集合成员被独立训练,对于 boosting,估计器被串行训练,估计器的性能 k 影响估计器的训练 (k+ 1) 。关键的创新是关注错误分类的例子,以便在训练下一个估计器时对它们进行上采样。这种对误差所在位置的关注使得升压有可能降低误差的偏置分量以及方差。虽然这种助推想法是训练合奏的一般原则,但首次流行的具体实现是由 Freund 和 Schapire [6]引入的 AdaBoost 。总体的 AdaBoost 算法从一个由 m 个例子组成的数据集中训练出一组 N 个估计器,如下所示:
*****1\. For estimator 0 assign an equal weight of *1/m* to all training
examples: *D0(i) = 1/m*.
2\. FOR each *k* of the *N* estimators to be trained:
2.1\. Randomly sample *l* examples from the full training set with
replacement, based on the current weights.
2.2\. Train estimator *hₖ* on this sample.
2.3\. Identify examples misclassified by this estimator, calculate
the error ****ε****ₖ*.
2.4\. Calculate the weight α*ₖ* for this estimator based on ***ε****ₖ*:******

******2.5\. Increase weights for misclassified examples, decrease
weights for other examples.******

******3\. Output final model based on all *N* estimators
(e.g. a majority voting model)******
这取决于如何实现采样(步骤 2.1。可能有必要在步骤 2.5 中对更新的权重进行归一化,以确保保持适当的分布,即权重总和为 1。
为了说明增强操作的细节,我们提供了一个简单运动员选择数据集的例子,该数据集只有两个特征。这个例子的代码可以在集成提升笔记本中找到。这两个特征是速度和敏捷性,类别标签为选择/未选择** —参见图 9(a)。******

图 9 :一个简单的带有两个估值器的 boosting 例子。(a)训练数据,两个决策树桩的决策面以绿色显示。被 Est 0 错误分类的两个样本以橙色突出显示。(b)两个估计量的样本权重。两个决策树桩(估算者)。图片作者。
该图显示了两个估计器,它们是决策树桩。 Est 0 以阈值 5 对速度进行分区。这错误地分类了 13 个训练示例中的两个, x15 和X11;这些在图 9(a)中用橙色突出显示。在图 9(b)中,我们看到这对 Est 1 的权重的影响。错误分类的例子的权重明显高于正确分类的例子。如果集合被设置为只有两个成员,那么图 9(c)中的两个决策树桩将是估计值,并且它们的权重将使用步骤 2.5 中的等式来确定。******
梯度推进
近年来,梯度推进已经成为 ML [16]中最强大的预测算法。梯度推进的思想是当在推进中增加估计量时,使用梯度下降来优化新估计量的参数。我们可以使用下面的等式非常概括地描述升压:

其中 ŷᵢ 是对真实结果的估计 yᵢ , αₖ 和 pₖ 是估计器 k 的权重和参数。这对于 yᵢ 为数值的回归有效。如果我们将总和的符号作为预测标签,这在分类标签为[-1,+1]的二元分类场景中也是有效的。在这些术语中,boosting 是一个附加模型,其中新的估计器被训练来补偿早期估计器的问题。

当添加新的估计量时,目标是消除真实值和现有总体估计之间的差异(误差)。

因此,添加新估计量的目的是使其适合这个“残差”【yᵢ−fₖ(xᵢ】。例如,当处理回归中的均方根误差(RMSE)时,该损失函数为:**

在训练数据上,总体损失是:

如果这个损失函数是可微的,例如 RMSE,那么我们可以使用梯度下降来更新 αₖ 和 pₖ 参数。这种梯度下降训练的细节取决于损失函数和基本估计量的性质(如逻辑回归、决策树、判定树)[7,18]。
作为标准 scikit-learn 分布的一部分,提供了一种梯度增强实现。在这里,我们针对 scikit-learn 中实现的主要集成方法,对该实现的性能进行了非正式评估。我们比较了 bagging、随机子空间、随机森林和标准 adaboost。在这种情况下,我们不使用葡萄酒数据集,因为其准确率约为 98%,没有太多的“上升空间”可供改进。相反,我们使用酒店评论数据集,目的是预测用户是否会认为评论是有用的。用于评估的数据集和代码可在 GitHub 上获得。

图 10 :五种集成方法在酒店评论数据集上的比较。所有方法的基本估计器都是决策树。在这个单一数据集上,adaboost 表现最好,而随机子空间集成表现最差。作者配图。
结果如图 10 所示。使用重复的 10 重交叉验证(10 次重复)对所有集合进行评分。所有的系综都有 100 个估计器,并且使用默认参数,即没有试图调整参数。没有太多的型号可供选择。随机子空间在 70%时表现最差。Adaboost 在梯度增强和随机森林方面做得最好,紧随其后,误差在 1%以内。所有这些模型都有相当大的参数调整空间,因此如果模型被调整,整体排名可能会发生变化。
异类系综
当 ML 的学生第一次接触集成思想时,通常假设集成是基本分类器模型的异类集合,例如朴素贝叶斯、 k -NN、决策树等。假设集合成员需要多样化,有什么比使用不同的模型类型更好的方法来实现多样化呢?事实上,如果数据存在多样性,一些模型可能在一些数据样本上成功,而其他模型则完全失败。虽然有一些关于异类合奏的研究,但这绝不是一个主流话题。异构集成的最大成功是在专业领域,如恶意软件检测[17]或数据流上的 ML[24]。
异类合奏缺乏突出性的一个解释是,有更容易的方法来实现多样性。我们在这里提供了一个简单的演示。在图 11 中,我们在酒店评论数据集上将七个分类器的异构集成与相同大小的决策树的 bagging 集成进行了比较。因为我们的目标是展示多样性/不一致的作用,所以我们呈现来自单个拒绝评估的结果。七种估计器类型的性能如图 11(a)所示。最准确的是逻辑回归和支持向量分类器(SVC ),最不准确的是决策树。然而,图 11(c)中的热图显示决策树具有最佳的成对不一致,因此它可能非常有用。异源集合是中等有效的,因为集合精度(红条)比分量估计的平均值好。然而,集成并不比逻辑回归或 SVC 更好,因此模型选择策略可能比集成更有效。

图 11 :异质系综和相同大小的 bagging 系综的比较。bagging 集成在测试集上获得了更高的精度,可能是因为估计量中有更多的多样性。在色彩图中,越亮越好。作者图表。
相比之下,bagging 集合图 11(b & d)非常有效,集合优于分量估计量。两张彩色地图显示了系综成员之间的成对分歧,这让我们对正在发生的事情有了一些了解。bagging 系综较浅的颜色表明系综成员之间有较好的多样性。在异质集成中,逻辑回归、SVC 和二次判别分析(QDA)都是很好的分类器,但它们被证明是非常相似的。此外,利用 bagging,添加新的系综成员是简单的。对于异构策略,我们已经“穷途末路”,如果我们想添加新的评估者,我们需要想出一些新的东西。
允许异类集合中有更多集合成员的一个策略是放宽异类的标准。通过在模型上使用不同的变体可以实现多样性,例如在隐藏层中具有不同数量单元的神经网络[9]。通过使用不同的超参数集来改变模型,异类集合中的估计量的数量可以显著增加。这个想法将在下一节堆栈的上下文中进一步探讨。
因此,尽管异构集成可能看起来是实现多样性的显而易见的方法,但是模型的多样性确实引入了额外的软件工程复杂性,并且有更简单的方法来用单一的模型类型实现相同级别的多样性。我们将在后面看到,如果目标是使整个 ML 管道自动化,而不需要在模型选择上投入太多精力,那么异构集成可能在 AutoML 运动中发挥作用。
堆垛
在从 bagging 到 boosting 再到 gradient boosting 的过程中,更加关注集合的聚集阶段(图 1)。通过将该步骤本身视为受监督的学习任务,即训练最终估计器来优化聚合过程,叠加可以得出可能被认为是符合逻辑的结论(见图 12)。虽然这似乎是一个显而易见的策略,但仍有一些重要的实现问题需要考虑。

图 12 :分类模式下的叠加系综。样本被传递给基本估计量,得到的预测被传递给最终估计量。原始样本可能会或可能不会被传递到最终估计器。图片作者。
训练数据的管理需要仔细考虑。重要的是,用于训练最终估计量(执行聚合的估计量)的数据不用于训练基本估计量。
- 最终估计量应该只使用基本估计量的输出,还是也应该访问原始输入特征?这通常称为直通,如图 12 所示。
- 如果基本估计量可以产生一个概率(例如朴素贝叶斯)作为清晰类别标签的替代,是否应该将其用作最终估计量的输入?
- 为什么停在一级堆叠?增加更多的层有什么好处吗?
我们在图 13 所示的评估中评估了叠加的优点。该评估的代码可在系综堆叠笔记本中找到。该评估使用重复 20 次的 10 重交叉验证。两个绿色条显示了基于前面介绍的七个异类集合成员的集合的性能。通过叠加,使用逻辑回归作为最终估计量进行聚合,精确度略有提高。这些结果基于传递给最终估计器的无通过和清晰的类别标签。当我们允许通过并使用“概率”输出时,没有改善。

图 13 :基本非均匀系综和叠加当量的精度比较。还示出了基于 SVC 变体的堆叠系综的性能。作者配图。
因此,堆叠确实提高了异构集成的性能,但是不同模型类型的可用性限制了集成的大小。我们已经提到,在单个模型类型中使用不同的超参数可能会克服这个问题。图 13 中右边的红色条显示了这种策略的潜力。考虑的单个模型是 SVC,并且通过随机采样超参数将变化引入集合成员。考虑的选择如下:
- 内核:RBF、线性或聚合之一。****
- C:0.05,0.1,0.2 中的一个,
- :0.1,0.5 之一,
这里 C 是 SVC 的正则化参数,γ是选择 rbf 和 poly 核时的核参数。图中的蓝条显示了从叠加系综中随机选择的单个 SVC 模型的性能。显然,随机选择超参数有时会导致模型不佳。然而,具有 7 和 20 个估计量的堆叠集成确实产生了良好的性能。因此,这种超参数策略允许我们用许多基本估计量生成叠加系综。有趣的是,尺寸为 20 的堆叠系综并不比尺寸为 7 的更好。这可能是由于在最终估计量中有过度拟合的趋势。
在基于超参数选择设置这些叠加系综时,需要大量的手动调整,因为一些超参数组合产生非常差的估计值。因此,我们的经验是,堆叠比装袋、运输或随机森林更难做对。可能正是由于这个原因,叠加比其他系综选择受到的关注要少。尽管如此,堆栈似乎确实在 AutoML 中发挥了作用,所以本节以对该主题的简短介绍结束。
AutoML
AutoML 指的是自动机器学习——一项旨在帮助有限的 ML 经验的开发者建立有效的 ML 模型的运动。目标是自动化 ML 管路的所有方面,包括数据准备、模型选择和模型调整[22]。在这种背景下,亚马逊的研究人员提出了堆叠和异构集成作为自动化模型选择的解决方案[5]。他们称为 AutoGluon 的框架试图通过使用堆栈来学习最佳整体架构,从而避免模型和超参数选择的任务。因此,虽然可能没有很多 ML 从业者明确地使用堆栈,但它完全有可能在 AutoML 的幕后被广泛使用。
建议和结论
本教程的目标是提供一个实用的 ML 系综教程。我们已经介绍了主要的集成架构,并讨论了集成如何有效地提高单个分类器的精度。在附录中,我们提供了 Python 代码的链接,这些代码将允许在这里涉及的所有系综架构上进行实验。我们的主要建议和意见可总结如下:
- 如果目标是最大化精度,梯度推进可能被认为是 ML [16]中领先的监督学习方法。
- 随机森林还可以实现非常好的准确性,并具有通过特征重要性机制提供数据洞察力的额外优势。
- 理解堆栈是如何工作的是值得的,因为它在 AutoML 中有一个新兴的角色。
附录:Python 代码
与本教程相关的 GitHub 资源库(【https://github.com/PadraigC/EnsemblesTutorial】T2)包含以下 Python 笔记:
- 系综-预备:展示系综规模和多样性对准确性影响的代码。
- 集成-打包:打包和随机子空间集成的代码。
- Ensembles-RandomF :使用随机森林生成特征重要性分数和泛化精度的 OOB 估计。
- Ensembles-Boosting :一个简单的 AdaBoost 例子来说明内部工作原理。
- 集成-GBoost :与其他集成方法相比的梯度增强。
- 异类集成:一种异类集成,具有 7 个估计量,符合 bagging 集成。
- 系综叠加:异质体与一些叠加方案的比较。
参考
- 长度布雷曼,装袋预测器 (1996),机器学习,24(2):123–140。
- 长度 Breiman,随机森林 (2001),机器学习,45(1):5–32。
- 孔多塞侯爵,巴黎皇家学院《运用分析多种意见作出决定的可能性研究》(1785 年)。
- 页(page 的缩写)Cunningham 和 J. Carney,基于特征选择的分类集成中的多样性与质量 (2000),欧洲机器学习会议,第 109–116 页。斯普林格。
- 名词(noun 的缩写)Erickson,J. Mueller,A. Shirkov,H. Zhang,P. Larroy,M. Li,A. Smola,Autogluon-tabular:Robust and accurate automl for structured data(2020),arXiv 预印本 arXiv:2003.06505 .
- Y.Freund,R. Schapire,用一种新的推进算法进行的实验 (1996),ICML'96,第 148-156 页。
- J.弗里德曼,贪婪函数逼近:梯度推进机 (2001),《统计年鉴》,第 1189-1232 页。
- F.高尔顿,《人民之声》(1907 年),《自然》,第 75 卷(1949 年):第 450-451 页。
- 长度汉森和 p .萨拉蒙,神经网络集成 (1990),IEEE 模式分析和机器智能汇刊,12(10):993–1001。
- T.Kam Ho,随机子空间中的最近邻 (1998),模式识别(SPR)和结构与句法模式识别(SSPR)统计技术联合 IAPR 国际研讨会,第 640–648 页。斯普林格。
- T.Kam Ho,构造决策森林的随机子空间方法 (1998),IEEE 模式分析与机器智能汇刊,20(8):832–844。
- A.Krogh 和 J. Vedelsby,神经网络集成、交叉验证和主动学习 (1995),神经信息处理系统进展 7,7:231。
- 长度 Kuncheva,J. Rodríguez,C. Plumpton,D. Linden 和 S. Johnston,【fRMI 分类的随机子空间集成 (2010),IEEE 医学成像汇刊,29(2):531–542。
- 长度 Kuncheva 和 C. Whitaker,分类器集成中多样性的度量及其与集成准确性的关系 (2003),机器学习,51(2):181–207。
- K.拉达,孔多塞陪审团定理,言论自由,和相关投票 (1992),美国政治科学杂志,第 617-634 页。
- A.Mangal,N. Kumar,利用大数据提升博世生产线性能:A Kaggle 挑战 (2016),2016 年 IEEE 大数据国际会议(大数据),第 2029–2035 页。IEEE。
- E.Menahem,A. Shabtai,L. Rokach,Y. Elovici,通过应用多诱导剂集成提高恶意软件检测 (2009),计算统计学&数据分析,53(4):1483–1494。
- A.纳特金,a .诺尔,梯度推进机器,教程 (2013),神经机器人前沿,7:21。
- R.Schapire,弱可学习性的强度 (1990),机器学习,5(2):197–227。
- 米(meter 的缩写))Skurichina,R. Duin, Bagging,boosting 和线性分类器的随机子空间方法 (2002),模式分析&应用,5(2):121–135。
- J.索罗维基,《群体的智慧》(2005),主播。
- C.Thornton,F. Hutter,H. Hoos,K. Leyton-Brown, Auto-WEKA:分类算法的组合选择和超参数优化,(2013),第 19 届 ACM SIGKDD 知识发现和数据挖掘国际会议论文集,第 847-855 页。
- A.Tsymbal,M. Pechenizkiy,P. Cunningham。集成特征选择搜索策略的多样性 (2005),信息融合,6(1):83–98。
- J.van Rijn,G. Holmes,B. Pfahringer,J. Vanschoren,在线性能估计框架:数据流的异构集成学习,(2018),机器学习,107(1):149–176。
- D.沃伯特。堆叠推广 (1992),神经网络,5(2):241–259。
确保你的机器学习算法做出准确的预测
scikit-learn 的 cross_val_score 如何简化这一关键流程

图片来源:pexels 上的 RONDAE productions
我们之前已经讨论过如何确保你的机器学习算法得到良好的训练,并在对给定数据集进行预测时表现良好。这是这个过程中非常重要的一部分:如果一个机器学习算法与数据集不匹配,那么预测很可能是垃圾,这意味着你的算法不会完成你需要它做的事情。
正如上一篇文章中所讨论的,验证模型的过程通常由以下步骤组成:
- 将数据集分成训练和测试数据集,
- 使用训练数据来训练算法,
- 使用算法预测测试数据的输出,以及
- 将预测值与实际测试数据进行比较。
这种方法有两个问题。一是这确保了模型对于特定的训练和测试数据集工作良好。这里的问题是,该模型在不同的训练/测试数据分割下可能不会工作得很好。第二个问题是,这不是解决问题的最简单的方法。
幸运的是,scikit-learn 包含一个交叉验证算法,这使得事情变得非常简单。
什么是交叉验证?
交叉验证本质上是一个多次执行上述步骤的过程。当它将数据分成测试和训练数据集时,它使用不同的随机化种子,这会返回不同的数据集以在该过程中使用。这样,每次执行训练和测试时,它都使用不同的数据集,并创建不同的分数。如果您使用交叉验证来多次执行该过程,您会比使用单个训练/测试实现更好地了解算法的执行情况。
为了让它更好,scikit-learn 提供了一个非常简单的算法,我们可以使用。该算法简称为 cross_val_score,它对用户指定的模型执行用户指定次数的验证。在执行验证时,它使用用户请求的评分方法计算分数。完成后,它返回每次运行的分数。
让我们看看如何执行交叉验证。
导入数据集
首先,我们需要导入一个数据集来执行验证。这类项目目前最受欢迎的数据集是加州住房数据集,它显示了加州房屋的价格和每个房屋的一些特征。由于这个数据集在 scikit-learn 中可用,我们可以很容易地导入和研究它。
from sklearn import datasetsca = datasets.fetch_california_housing()X = ca.datay = ca.targetcol_names = ca.feature_namesdescription = ca.DESCRprint(col_names)print(description)
运行该代码将提供一些输出,让您对该文件中包含的数据有所了解。具体来说,它告诉我们:
- 在任何分析中都有 20,640 个数据点可以使用,
- 数据集中有八个预测数字特征和一个目标值,以及
- 这八个特征包括诸如街区中值家庭收入、街区中每所房子的平均房间数量以及街区的纬度/纬度坐标的项目。
由于我们将特征存储在 X 变量中,将目标存储在 y 变量中,我们现在可以开发一个模型,并使用数据集进行验证。
执行交叉验证
在执行交叉验证之前,我们必须首先导入 scikit-learn 的交叉验证工具,并创建一个在交叉验证过程中使用的模型。我们可以这样做,使用随机森林回归(默认超参数。更多内容请见下文!)对于这个例子,使用下面的代码:
from sklearn.model_selection import cross_val_scorefrom sklearn.ensemble import RandomForestRegressormodel = RandomForestRegressor()
现在我们可以将模型和数据传递给 cross_val_score 来生成分数。cross_val_score 的一般语法(使用我认为最重要的输入。您可以在 scikit-learn 的文档中看到完整的输入列表,如下所示:
scores = cross_val_score(regression algorithm, X data, y data, scoring metric, number of iterations)
为了运行交叉验证,我们只需要用实际输入替换我上面使用的描述性术语。具体来说,我们需要更换如下物品:
- 回归算法需要用过程中使用的机器学习模型来代替。在这个例子中,我们在变量模型中保存了一个 RandomForestRegressor 实例,这样我们就可以在那里使用模型。
- X 数据被替换为交叉验证过程中使用的 X 数据。由于我们之前已经在 X 中保存了加州住房数据集的特征,所以我们可以使用它。
- 除了目标数据之外,y 数据与 X 数据相同。类似地,我们可以使用来自加州住房数据集中的 y 变量。
- 评分标准指定您希望 scikit-learn 在评估预测器性能时使用的误差计算方法。您可以在 sklearn.metrics.SCORERS.keys()找到可用评分指标的完整列表。我最喜欢的指标是均方根误差 (RMSE)。不幸的是,这在 scikit-learn 中不可用。幸运的是,我们可以使用 scikit-learn 的负均方误差( neg_mean_squared_error )度量,并由此计算 RMSE。
- 最后,我们需要说明我们想要执行验证过程的次数。次数越多,结果越好,但计算时间越长。你总是需要根据情况做出决定。这个模型极其精确有多关键?你有多少时间进行计算?对于这个例子,我们将使用 10 次迭代。记住:只要你使用不止一次迭代,交叉验证将会比单次训练/测试更好。
将所有这些放在一起,我们得到以下代码:
scores = cross_val_score(model, X, y, scoring = ‘neg_mean_squared_error’, cv = 10)
当我们运行这段代码时,我们将收到一个数组,其中包含 10 个验证过程中每一个验证过程的负均方误差值。由于我更喜欢使用 RMSE,我们可以将负均方误差值转换为 RMSE 值。我们还可以通过对结果进行后处理来计算平均 RMSE 和 RMSE 的平方根。所有这些步骤都可以用下面的代码来执行:
import numpy as npscores = np.sqrt(-scores)mean = scores.mean()stdev = scores.std()print(scores)print(mean)print(stdev)
这最后三行打印了 10 次运行的 RMSE、10 次得分的平均值和 10 次得分的标准偏差。这让我们看到模型平均表现如何,以及运行之间有多少可变性。我们得到的输出是:
[0.86971303 0.60083121 0.68912278 0.50477132 0.66335802 0.621953990.53314316 0.78384348 0.82483679 0.51839043]0.66099641905933050.12350211658614779
这意味着具有默认参数的 RandomForestRegressor 预测加利福尼亚州特定街区的房屋平均值,其平均 RMSE 为 66,100 美元,RMSE 的标准偏差为 12,350 美元。表现最好的案例显示平均 RMSE 为 51839 美元,表现最差的案例显示平均 RMSE 为 86971 美元。
这提供了一些在比较模型时使用的非常有价值的指标。例如,我们可以使用 ExtraTreesRegressor 或 MultiLayerPerceptronRegressor 尝试相同的过程,看看哪个模型表现最好。或者我们可以改变模型超参数,看看哪个参数集表现最好。最后,我们还可以对数据进行预处理,使其更易于机器学习算法的管理,并看看这是否能提高性能。
就是这样!现在你知道如何执行交叉验证,并评估不同机器学习算法的性能。
用 TigerGraph 进行实体解析?加入 Zingg 的组合!
原文:https://towardsdatascience.com/entity-resolution-with-tigergraph-add-zingg-to-the-mix-95009471ca02
基于训练数据学习匹配什么(缩放)和如何匹配(相似性)的开源工具。
随着组织收集更多的数据并利用更多的内部和云工具,关于客户、供应商、合作伙伴和其他实体的可信信息变得越来越分散。如果没有核心业务实体的统一视图,分析就会受到影响。如果我们的客户数据是孤立的,我们就无法量化客户终身价值、每个季度增加的新客户、每个客户的平均重复订单以及其他关键的客户参与度指标。这导致对我们的业务和我们的客户缺乏了解,从而影响品牌价值。客户希望我们向他们提供个性化的产品和建议,但是如果我们的基础数据没有整合,我们就无法取悦客户,也无法发现交叉销售和追加销售的机会。
这种孤立数据的问题并不仅限于收入目的的客户信息。合规部门需要供应商的明确实体定义,以确保他们不与制裁名单上的人或组织打交道。反洗钱和 KYC 活动始于建立单一的真相来源。
解析没有唯一标识符且属于同一实体的多条记录被称为实体解析。从表面上看,实体解析似乎是一个容易解决的问题——从人的角度来看,识别有变化的记录是很直观的,所以计算机程序肯定能做得更好!也许我们在不同的系统和记录中有一些共同的标识符,我们可以利用它们来统一数据?不幸的是,即使有了像电子邮件这样的可信标识符,人们还是会使用工作、个人、学校和其他 id,这并不能完全解决问题。
由于我们的系统捕获和记录信息的方式不同,我们现实世界的大部分数据看起来都是这样的。

作者图片
正如我们在上面的样本记录中看到的,我们没有任何单个属性在整个实体中完全匹配。这个例子只包含 3 条记录和 4 个属性,想象一下在 Python 或 SQL 这样的编程语言中精心设计相似性规则来匹配这些记录!
具有内置链接模式的图形数据库是消除记录歧义和解析实体的理想选择。TigerGraph 是一个领先的图形数据库,是一个强大的实体解析工具。正如在 TigerGraph 博客文章中概述的,我们可以通过定义五种类型的顶点来构建上述三个记录的图表模式,一种用于实际的客户,其余的用于四个属性——姓名、地址、电话和城市。通过将属性表示为顶点,将实体-has 关系表示为边,我们可以将上述三个记录转换为下图。

作者图片
然后,我们可以定义类似余弦或 Jaccard 的相似性度量来链接姓名、电话号码、城市和地址。匹配两个顶点后,我们可以在它们之间添加一条边来连接它们。然后,我们可以使用连接的组件将相似的实体组合在一起。
由于关系的传递性,这种方法比在关系数据库中运行查询要好得多。然而,在定义属性之间的相似性标准、将多个属性组合在一起以及将它们全部链接起来以推导实体关系方面,它仍然留给我们大量的工作。属性的成对相似性在计算上是禁止的,即使在利用分布式图之后,我们也面临着可伸缩性的挑战。如果我们能够以一种更简单的方式解析实体,然后将图形加载到 TigerGraph 中,用于我们的 KYC、AML 和客户 360°场景,会怎么样?
解决上述挑战的一种方法是使用专门的开源实体解析框架,如 Zingg 。这将我们从复杂的实体解析中解放出来,让我们有更多的时间进行推理。(充分披露:我是 Zingg 的作者)
使用 Zingg 解析实体的典型工作流如下所示。
1.我们构建一个配置 json ,指定我们的输入和输出数据位置,并定义我们想要配置哪些字段进行匹配。
2.我们通过 Zingg 的互动学习者来训练它。这挑选出我们标记为可接受的匹配或不匹配的代表性样本对。我们可以通过贴标机标记多达 30-40 个匹配,Zingg 将自动计算出每个属性的相似性阈值和权重。
3.我们通过 Zingg 运行我们的数据,并获得解析的实体。我们可以将步骤 2 中创建的模型与更新的增量数据集一起重用。
当加载到 TigerGraph 时,Zingg 的输出如下所示:

作者图片
匹配的顶点由具有概率分数的边连接。我们现在可以引入我们的事务数据来进一步分析 TigerGraph 中已解析的实体图。
由于以下原因,这种方法优于单独使用图形的方法:
- 我们不必担心属性级别的相似性度量。
- 我们不必找出将不同属性的属性相似性组合成实体相似性的最佳方式。
- 像 Zingg 这样的专门实体解析框架有内置的阻塞机制 s,它只对选择的记录计算成对相似性。这大大减少了计算时间,并帮助我们将实体分辨率扩展到更大的数据集。
- 我们不必手动担心链接-分组方面,因为它由幕后的实体解析框架负责。
因此,实体解析的所有艰苦工作都可以卸载。
通过 Zingg 解析实体并在 Tigergraph 中利用它们,我们结合了两个世界的精华——通过 Zingg 的简单和可扩展的实体解析以及在 TigerGraph 中对图形的进一步分析。这让我们有更多的时间来处理我们的核心业务需求,因为这些实体现在可以用于我们的分析和运营需求。
Python 中的环境变量
原文:https://towardsdatascience.com/environment-variables-in-python-a37cab6d2530
在本文中,我们将探索如何在 Python 中使用环境变量

目录
- 介绍
- 如何使用 Python 获取环境变量
- 如何使用 Python 设置环境变量
- 结论
介绍
环境变量是存储在程序之外(在操作系统级别)的变量,它们会影响程序的运行方式,并有助于存储敏感信息。
例如,你的程序可能正在使用一些有密钥的 API。这个密钥应该作为环境变量存储在您正在运行的代码之外。
但我们为什么要这么做?
使用环境变量有两个主要优点:
- 提高敏感数据的安全性——当多个开发人员在一个代码库上工作时,您可能希望将您的密钥放在代码库之外,以提高安全性。或者,如果您在 GitHub 上共享某个程序的源代码,您希望将您的 API 密匙保密,不要让任何下载代码的人都可以访问。
- 自动化 —将秘密存储在环境变量中有助于避免手动更新代码库,例如,当您在其他人的机器上运行相同的代码时。因此,您可以使用环境变量自动更新源代码,而不是根据运行代码的不同“用户”来更改源代码。
接下来,让我们探索如何在 Python 中使用环境变量!
在本教程中,我们将使用 os 模块,该模块提供了一种使用操作系统相关功能的可移植方式,因此不需要额外安装。
如何使用 Python 获取环境变量
在 Python 中,环境变量是通过 os 模块实现的,并且可以使用 os.environ 进行检索,它提供了表示流程环境的键值对的映射。
要使用 Python 获取所有环境变量,只需运行:
#Import the required dependency
import os
#Get all environment variables
env_vars = os.environ
#Print environment variables
for key, value in dict(env_vars).items():
print(f'{key} : {value}')
您应该可以打印出环境变量的完整映射:
TERM_PROGRAM : vscode
SHELL : /bin/bash
TERM_PROGRAM_VERSION : 1.72.2
ORIGINAL_XDG_CURRENT_DESKTOP : undefined
USER : datas
PATH : /Library/Frameworks/Python.framework/Versions/3.7
__CFBundleIdentifier : com.microsoft.VSCode
LANG : en_US.UTF-8
HOME : /Users/datas
COLORTERM : truecolor
_ : /usr/local/bin/python3
__PYVENV_LAUNCHER__ : /usr/local/bin/python3
为了获得特定环境变量的值,我们将使用 os.environ.get() 。它将检索指定的环境变量的值。
例如,我想检索用户环境变量:
#Import the required dependency
import os
#Get specific environment variable
user_var = os.environ.get('USER')
#Print environment variables
print(user_var)
您应该可以打印出系统上设置的特定用户变量。
我的情况是:
datas
如果你要查找一个没有匹配关键字的环境变量,代码应该简单地返回“None”。
让我们尝试查找一些不存在的环境变量,比如“TEST_PASSWORD”:
#Import the required dependency
import os
#Get specific environment variable
pwd_var = os.environ.get('TEST_PASSWORD')
#Print environment variables
print(pwd_var)
您应该得到:
None
如何使用 Python 设置环境变量
在 Python 中,环境变量是通过 os 模块实现的,并且可以使用 os.environ 进行设置,这与将键值对插入字典是一样的。
环境变量 key 和值都必须是字符串格式。
在上一节中,我们尝试检索一个不存在的环境变量“TEST_PASSWORD”,代码返回“None”。
现在让我们用一个自定义值设置这个环境变量,并将其打印出来:
#Import the required dependency
import os
#Set specific environment variable
os.environ['TEST_PASSWORD'] = '12345'
#Get specific environment variable
pwd_var = os.environ.get('TEST_PASSWORD')
#Print environment variables
print(pwd_var)
您应该得到:
12345
注意:在运行代码时,对环境变量(在我们的例子中是添加的环境变量)的更改只对这一个会话有效。它不会影响系统环境变量或其他会话中的环境变量。
结论
在本文中,我们将探索如何在 Python 中使用环境变量。
我们重点讨论了如何使用 Python 获取现有的环境变量,以及如何在 Python 中为会话设置环境变量。
如果你有任何问题或对编辑有任何建议,请随时在下面留下评论,并查看我的更多 Python 编程教程。
原载于 2022 年 12 月 20 日 https://pyshark.com**。
在 Python 中与环境变量交互
原文:https://towardsdatascience.com/environment-variables-python-aecb9bf01b85
使用 Python 访问、导出和取消设置环境变量

照片由 Clarisse Meyer 在 Unsplash 上拍摄
介绍
某些应用程序可能不得不使用已经在程序本身之外初始化的变量,而是在源代码应该被执行的环境中。
环境变量被指定为执行流程(如 Python 应用程序)的环境的一部分。它由一个名称/值对组成,可以在任何给定时间被访问、覆盖和取消设置。这种变量通常直接在命令行界面上定义,或者用 bash 脚本定义(例如,在操作系统启动时)。然而,甚至软件程序本身也可以与它们进行交互。
在今天的简短教程中,我们将展示如何以编程方式访问覆盖和取消设置现有的环境变量,以及如何导出新的环境变量。最后,我们还将演示几种检查环境变量是否存在的方法。
访问环境变量
首先,我们将演示如何以编程方式访问已经作为执行 Python 应用程序的环境的一部分导出的环境变量。
假设系统管理员已经用值dev初始化了一个名为ENV的环境变量:
$ export ENV=dev
我们可以通过命令行回显该值来验证环境变量是否已经初始化:
$ echo ENV
dev
现在,如果我们想用 Python 编程访问环境变量,我们需要使用**os.environ**映射对象:
一个映射对象,其中键和值是表示流程环境的字符串。例如,
environ['HOME']是你的主目录的路径名(在某些平台上),相当于 c 中的getenv("HOME")这个映射是在第一次导入
[os](https://docs.python.org/3/library/os.html#module-os)模块时捕获的,通常是在 Python 启动时作为处理site.py的一部分。除了通过直接修改os.environ所做的更改外,在此时间之后对环境所做的更改不会反映在os.environ中。
你可以使用环境变量名作为os.environ对象的键来推断值:
import os**env_var = os.environ['ENV']**
print(f'Currently working in {env_var} environment'.
上面表达式的问题是,如果环境变量ENV不在环境中,它将会以KeyError失败。因此,最好使用os.environ.get()来访问它。如果不存在,它会简单地返回None而不是抛出一个KeyError。
import os**env_var =** **os.environ.get('****ENV****')**
print(f'Currently working in {env_var} environment'.
接下来,如果环境变量尚未初始化,您甚至可能需要设置一个默认值:
import os**env_var =** **os.environ.get('****ENV****', 'DEFAULT_VALUE')**
print(f'Currently working in {env_var} environment'.
显然,另一种选择是捕捉KeyError,但是我认为这对于这种操作来说可能是一种过度的破坏:
import ostry:
env_var = os.environ['ENV']
except KeyError:
# Do something
...
导出或覆盖环境变量
如果您想要导出或者甚至覆盖一个现有的环境变量,那么您可以在os.environ对象上使用一个简单的赋值:
import osos.environ['ENV'] = 'dev'
现在使用try-except符号可能更有意义:
import ostry:
env_var = os.environ['ENV']
except KeyError:
os.environ['ENV'] = 'dev'
检查环境变量是否存在
我们已经部分介绍了这一部分,但是我还将演示一些检查环境变量是否存在的其他方法。
第一种方法是简单地使用变量名作为关键字访问os.environ对象,并捕获表示环境变量不存在的KeyError:
import ostry:
env_var = os.environ['ENV']
print('ENV environment variable exists')
except KeyError:
print('ENV environment variable does not exist')
另一种方法是简单地检查环境变量是否是os.environ对象的成员:
import os env_var_exists = 'ENV' in os.environ
第三种方法是检查os.environ.get()方法是否返回None(没有指定默认值):
import osenv_var = os.environ.get('ENV')if not env_var:
print('ENV environment variable does not exist')
最后,您甚至可以使用has_key()方法来检查环境变量名是否作为关键字包含在os.environ映射对象中:
import osenv_var_exists = os.environ.has_key('ENV')
取消设置环境变量
现在为了取消设置环境变量,您可以调用del操作:
del os.environ['ENV']
同样,如果ENV没有初始化,上面的表达式将失败,因此它从os.environ映射对象中丢失。
为了避免这种情况,您可以使用 if 语句
import osif 'ENV' in os.environ:
del os.environ['ENV']
或者,一种更优雅的方式是从映射对象中调用pop环境变量,如下所示。
os.environ.pop('ENV', None)
最后的想法
在今天的文章中,我们讨论了环境变量的重要性,以及它们如何与正在运行的 Python 应用程序进行交互。更具体地说,我们展示了如何用 Python 编程访问、取消设置甚至导出环境变量。
同样重要的是要提到,环境变量通常不会只被您自己的 Python 应用程序使用,因此您要确保以一种不会使环境处于可能中断其他应用程序甚至更糟的状态的方式与它们交互,从而错误地影响其他进程的执行流程。
成为会员 阅读媒介上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。
https://gmyrianthous.medium.com/membership
相关文章你可能也喜欢
</16-must-know-bash-commands-for-data-scientists-d8263e990e0e> https://betterprogramming.pub/kafka-cli-commands-1a135a4ae1bd
从熊猫到 PySpark 的平稳过渡
原文:https://towardsdatascience.com/equivalents-between-pandas-and-pyspark-c8b5ba57dc1d
熊猫大战 PySpark 101

熊猫是每个数据科学家的首选图书馆。对于每个希望操作数据和执行一些数据分析的人来说,这是必不可少的。
然而,尽管它的实用性和广泛的功能,我们很快就开始看到它在处理大型数据集时的局限性。在这种情况下,过渡到 PySpark 变得至关重要,因为它提供了在多台机器上运行操作的可能性,不像 Pandas。在本文中,我们将提供 PySpark 中 pandas 方法的等效方法,以及现成的代码片段,以便于 PySpark 新手完成任务😉
PySpark 提供了在多台机器上运行操作的可能性,不像 Pandas
**Table Of Contents**
· [DataFrame creation](#8396)
· [Specifying columns types](#dfc7)
· [Reading and writing files](#7382)
· [Filtering](#710b)
∘ [Specific columns](#50d3)
∘ [Specific lines](#f04d)
∘ [Using a condition](#e50f)
· [Add a column](#901e)
· [Concatenate dataframes](#6990)
∘ [Two Dataframes](#0b25)
∘ [Multiple Dataframes](#da7a)
· [Computing specified statistics](#a849)
· [Aggregations](#8b6c)
· [Apply a transformation over a column](#27da)
入门指南
在深入研究对等物之前,我们首先需要为后面做准备。不言而喻,第一步是导入所需的库:
import pandas as pd
import pyspark.sql.functions as F
PySpark 功能的入口点是 SparkSession 类。通过 SparkSession 实例,您可以创建数据帧、应用各种转换、读写文件等。要定义 SparkSession,您可以使用以下内容:
from pyspark.sql import SparkSession
spark = SparkSession\
.builder\
.appName('SparkByExamples.com')\
.getOrCreate()
现在一切都准备好了,让我们直接进入熊猫大战 PySpark 的部分吧!
数据帧创建
首先,让我们定义一个我们将使用的数据样本:
columns = ["employee","department","state","salary","age"]
data = [("Alain","Sales","Paris",60000,34),
("Ahmed","Sales","Lyon",80000,45),
("Ines","Sales","Nice",55000,30),
("Fatima","Finance","Paris",90000,28),
("Marie","Finance","Nantes",100000,40)]
要创建一只熊猫 DataFrame,我们可以使用下面的:
df = pd.DataFrame(data=data, columns=columns)
# Show a few lines
df.head(2)
PySpark
df = spark.createDataFrame(data).toDF(*columns)
# Show a few lines
df.limit(2).show()
指定列类型
熊猫
types_dict = {
"employee": pd.Series([r[0] for r in data], dtype='str'),
"department": pd.Series([r[1] for r in data], dtype='str'),
"state": pd.Series([r[2] for r in data], dtype='str'),
"salary": pd.Series([r[3] for r in data], dtype='int'),
"age": pd.Series([r[4] for r in data], dtype='int')
}
df = pd.DataFrame(types_dict)
您可以通过执行以下代码行来检查您的类型:
df.dtypes
PySpark
from pyspark.sql.types import StructType,StructField, StringType, IntegerType
schema = StructType([ \
StructField("employee",StringType(),True), \
StructField("department",StringType(),True), \
StructField("state",StringType(),True), \
StructField("salary", IntegerType(), True), \
StructField("age", IntegerType(), True) \
])
df = spark.createDataFrame(data=data,schema=schema)
您可以通过执行以下命令来检查数据帧的模式:
df.dtypes
# OR
df.printSchema()
读取和写入文件
熊猫和 PySpark 的阅读和写作是如此的相似。语法如下:熊猫
df = pd.read_csv(path, sep=';', header=True)
df.to_csv(path, ';', index=False)
PySpark
df = spark.read.csv(path, sep=';')
df.coalesce(n).write.mode('overwrite').csv(path, sep=';')
注 1💡:您可以指定要对其进行分区的列:
df.partitionBy("department","state").write.mode('overwrite').csv(path, sep=';')
注 2💡:您可以通过在上面的所有代码行中更改 CSV by parquet 来读写不同的格式,比如 parquet 格式
过滤
特定列
选择熊猫中的某些列是这样完成的:熊猫
columns_subset = ['employee', 'salary']
df[columns_subset].head()
df.loc[:, columns_subset].head()
而在 PySpark 中,我们需要对列列表使用 select 方法: PySpark
columns_subset = ['employee', 'salary']
df.select(columns_subset).show(5)
特定线路
要选择一系列线条,您可以在 Pandas 中使用iloc方法:
熊猫
# Take a sample ( first 2 lines )
df.iloc[:2].head()
在 Spark 中,不可能获得任何范围的行号。然而,可以像这样选择前 n 行:
PySpark
df.take(2).head()
# Or
df.limit(2).head()
注💡:请记住 spark,数据可能分布在不同的计算节点上,并且“第一”行可能会因运行而异,因为没有底层顺序
使用条件
可以根据特定条件过滤数据。Pandas 中的语法如下:
熊猫
# First method
flt = (df['salary'] >= 90_000) & (df['state'] == 'Paris')
filtered_df = df[flt]
# Second Method: Using query which is generally faster
filtered_df = df.query('(salary >= 90_000) and (state == "Paris")')
# Or
target_state = "Paris"
filtered_df = df.query('(salary >= 90_000) and (state == @target_state)')
在 Spark 中,通过使用filter方法或执行 SQL 查询可以得到相同的结果。语法如下:
PySpark
# First method
filtered_df = df.filter((F.col('salary') >= 90_000) & (F.col('state') == 'Paris'))
# Second Method:
df.createOrReplaceTempView("people")
filtered_df = spark.sql("""
SELECT * FROM people
WHERE (salary >= 90000) and (state == "Paris")
""")
# OR
filtered_df = df.filter(F.expr('(salary >= 90000) and (state == "Paris")'))
添加列
在 Pandas 中,有几种方法可以添加列:
熊猫
seniority = [3, 5, 2, 4, 10]
# Method 1
df['seniority'] = seniority
# Method 2
df.insert(2, "seniority", seniority, True)
在 PySpark 中有一个叫做withColumn的特殊方法,可以用来添加一个列:
PySpark
from itertools import chainseniority= {
'Alain': 3,
'Ahmed': 5,
'Ines': 2,
'Fatima': 4,
'Marie': 10,
}mapping = create_map([lit(x) for x in chain(*seniority.items())])df.withColumn('seniority', mapping.getItem(F.col("employee")))
连接数据帧
两个数据帧
熊猫
df_to_add = pd.DataFrame(data=[("Robert","Advertisement","Paris",55000,27)], columns=columns)
df = pd.concat([df, df_to_add], ignore_index = True)
PySpark
df_to_add = spark.createDataFrame([("Robert","Advertisement","Paris",55000,27)]).toDF(*columns)
df = df.union(df_to_add)
多个数据帧
熊猫
dfs = [df, df1, df2,...,dfn]
df = pd.concat(dfs, ignore_index = True)
PySpark 的方法unionAll只连接了两个数据帧。解决这一限制的方法是根据需要多次迭代连接。为了获得更简洁优雅的语法,我们将避免循环,并使用 reduce 方法来应用unionAll:
PySpark
from functools import reduce
from pyspark.sql import DataFrame
def unionAll(*dfs):
return reduce(DataFrame.unionAll, dfs)
dfs = [df, df1, df2,...,dfn]
df = unionAll(*dfs)
计算指定的统计数据
在某些情况下,我们需要通过一些统计 KPI 来执行一些数据分析。Pandas 和 PySpark 都提供了非常容易地获得数据帧中每一列的以下信息的可能性:
- 列元素的计数
- 列元素的平均值
- 性传播疾病
- 最小值
- 三个百分点:25%、50%和 75%
- 最大值
您可以通过执行以下行来计算这些值:
熊猫和 PySpark
df.summary()
#OR
df.describe() # the method describe doesn't return the percentiles
聚集
为了执行一些聚合,语法几乎是 Pandas 和 PySpark: Pandas
df.groupby('department').agg({'employee': 'count', 'salary':'max', 'age':'mean'})
PySpark
df.groupBy('department').agg({'employee': 'count', 'salary':'max', 'age':'mean'})
然而,熊猫和 PySpark 的结果需要一些调整才能相似。1.在 pandas 中,分组依据的列成为索引:

要将它作为一个列取回,我们需要应用 reset_index方法:熊猫
df.groupby('department').agg({'employee': 'count', 'salary':'max', 'age':'mean'}).reset_index()

- 在 PySpark 中,列名在结果数据帧中被修改,提到了执行的聚合:

如果您希望避免这种情况,您需要像这样使用别名方法:
df.groupBy('department').agg(F.count('employee').alias('employee'), F.max('salary').alias('salary'), F.mean('age').alias('age'))

对列应用变换
要对列应用某种转换,PySpark 中不再提供 apply 方法。相反,我们可以使用一个名为udf(或者用户定义的函数)的方法来封装 python 函数。
例如,如果工资低于 60000 英镑,我们需要增加 15%的工资,如果超过 60000 英镑,我们需要增加 5%的工资。
pandas 中的语法如下:
df['new_salary'] = df['salary'].apply(lambda x: x*1.15 if x<= 60000 else x*1.05)
PySpark 中的对等用法如下:
from pyspark.sql.types import FloatType
df.withColumn('new_salary', F.udf(lambda x: x*1.15 if x<= 60000 else x*1.05, FloatType())('salary'))
⚠️注意到udf方法需要明确指定数据类型(在我们的例子中是 FloatType)
最后的想法
总之,很明显,Pandas 和 PySpark 的语法有很多相似之处。这将极大地促进从一个到另一个的过渡。
使用 PySpark 在处理大型数据集时会给你带来很大的优势,因为它允许并行计算。但是,如果您正在处理的数据集很小,那么恢复到唯一的熊猫会很快变得更有效。
因为这篇文章是关于从 pandas 到 PySpark 的平稳过渡,所以有必要提到一个 Pandas 的等价 API,叫做考拉,它工作在 Apache Spark 上,因此填补了两者之间的空白。
谢谢你坚持到现在。注意安全,下一个故事再见😊!
如果您有兴趣了解关于 scikit-learn 的更多信息,请查看以下文章:
</4-scikit-learn-tools-every-data-scientist-should-use-4ee942958d9e> </5-hyperparameter-optimization-methods-you-should-use-521e47d7feb0>
SQL 中的错误处理—第 13 部分了解 SQL Server Management Studio
实现这些简单的错误处理步骤来加强你的脚本

迈克尔·泽兹奇在 Unsplash 上的照片
在最后几集里…
你好。欢迎来到 SQL 和 SQL Server Studio 系列教程的第 13 期。我的目标很简单:让你熟悉和适应这个工具和语言。“这有什么关系?”我知道你在问。事实证明,好奇心和副业往往是被新项目选中甚至获得新工作的关键。事实上,您已经使用了一个重要的工具,比如 SQL Server Studio,并且编写了一些 SQL 查询,这将会给您一个清晰的开端。
- 在第 1 集中,我将向您展示如何设置我们的环境和本地服务器 — 第 1 部分逐步设置 SQL Server Studio
- 在第 2 集中,我们将讲述如何创建数据库、表、和关于命名约定的重要注释— 第 2 部分数据库、表&命名约定
- 在第三集中,我们介绍了 CRUD 操作和主键和外键 — 第三部分 CRUD 操作,主键&外键
- 在第 4 集中,我们讨论了模式&的主要规范化步骤 — 第 4 部分模式&规范化
- 在第 5 集中,我们覆盖了存储过程&调度,这是一个真正的野兽。我怎么强调这将如何促进和自动化你的日常(数据)生活都不为过。第五部分存储过程&调度
- 在第 6 集中,我们介绍了用于 ETL 的 SSIS 包,并回顾了如何在两个数据库之间以及一个数据库和 Excel 之间导入和导出数据。第六部 SSIS 套餐简介
- 在第 7 集中,我们将 SQL Studio 连接到 PowerBI 并构建我们的第一个视觉效果。第 7 部分连接到 PowerBI &第一视觉效果。
- 在第 8 集中,我们利用 SQL 视图的强大功能,这是一个简单的轻量级工具,用于组合和检索复杂的表。你不会相信没有他们你怎么活下去。第 8 部分利用观点
- 在第 9 集中,我们利用临时表,在存储过程中临时存储和检索数据。找出中间结果并以表格形式缓存数据以备后用是非常有用的。在第九部分临时表格中了解更多信息
- 第 10 集探讨了如何使用存储过程和调度任务将 SQL 数据导出为 CSV 文件并通过电子邮件发送。第 10 部分通过电子邮件以 CSV 格式导出 SQL 查询数据
- 第 11 集跳入云端,在 Azure Cloud 中设置服务器和数据库。
- 第 12 集回顾了对相似数据行进行排序的简便方法,并通过窗口排序功能仅选择一行。
别忘了回来😉。
期待什么?
数据处理并不总是胆小的人的任务。因为它可以有许多形状和形式,所以记录本身的值有时会取意外的值。或者流程中的某个步骤可能会失败。每当它是存储过程的一部分时,我们可能希望忽略错误并继续前进到下一步。
为此,我们将调用 TRY…CATCH 语句。这类似于其他编程语言中的异常处理。这可能会解决一些错误,甚至可能会触发一个警报到您的邮箱来通知您。事不宜迟…
开始—表创建
我们假设您已经设置好了 SQL Server Studio 环境。如果你需要任何帮助,请查看上面提到的第一集。
让我们首先创建一个新表‘employee compensation’:
CREATE TABLE EmployeeCompensation(Name nvarchar(max), Salary int)
现在让我们用一些数据填充它:
INSERT INTO [dbo].[EmployeeCompensation]VALUES ('Max', 1000)
让我们运行一个快速选择来验证我们是好的:
SELECT * FROM [dbo].[EmployeeCompensation]
这应该会返回:

测试错误
除以 0 会导致错误,因为这是未定义的。对于我们的例子来说,这是一个完美的罪魁祸首:
自己尝试一下:
INSERT INTO [dbo].[EmployeeCompensation]VALUES ('Carl', 1/0)

可怜的卡尔拿不到任何薪水。这也终止了我们的声明。让我们用 TRY…CATCH 来覆盖我们的查询。
错误处理—语法
TRY…CATCH 语法很简单,语法如下:
**BEGIN TRY** <SQL query>**END TRY** **BEGIN CATCH** <SQL query>**END CATCH**
在我们的示例中,这可以转化为:
**BEGIN TRY** INSERT INTO [dbo].[EmployeeCompensation] VALUES ('Carl', 1/0)**END TRY****BEGIN CATCH** **BEGIN** EXEC msdb.dbo.sp_send_dbmail @profile_name= '<Your DB Email Account>', @recipients= '<youremail@email.com>', @subject = 'Error Process ABC - insertion error', @body = 'Hi, there was an error on Process ABC.', @body_format = 'HTML' **END****END CATCH**
脚本的逐步纲要:
- 我们首先尝试将值插入到 EmployeeCompensation 表中。
- 因为整数是 1/0,这将引发一个错误。
- 在我们的例子中,我们将它嵌套在一个 TRY 语句中。这会导致 SQL 脚本触发 CATCH 部分,在这种情况下,它会向选定的邮箱发送一封警告电子邮件。电子邮件可以定制我们选择的标题和正文。
当我们运行该脚本时,我们没有抛出错误,而是被告知有一封电子邮件已经排队并正在发往我们的邮箱:

获得关于错误的更多信息
这个错误已经被避免了…但是在这个设置下,在真实的场景中,我们不知道到底发生了什么。
要是我们能对错误本身有所了解就好了。这可以帮助我们复制它,并调试或处理我们的脚本对此类错误场景的更好响应。
谢天谢地,有一个特殊的功能可以利用。它的名字不会让很多人感到惊讶:ERROR_MESSAGE()
它所做的是返回错误信息,通常是纯文本的,易于阅读。让我们测试一下,我们可以在 CATCH 块中添加下面一行:
PRINT(ERROR_MESSAGE()
我们可以将它嵌入到我们的脚本中:
**BEGIN TRY** INSERT INTO [dbo].[EmployeeCompensation] VALUES ('Carl', 1/0)**END TRY****BEGIN CATCH** PRINT(ERROR_MESSAGE()) **BEGIN** EXEC msdb.dbo.sp_send_dbmail @profile_name= '<Your DB Email Account>', @recipients= '<youremail@email.com>', @subject = 'Error Process ABC - insertion error', @body = 'Hi, there was an error on Process ABC.', @body_format = 'HTML' **END****END CATCH**
输出:

- 未调整任何数据
- 我们获得了一个可读的错误信息
- 发送了一封电子邮件
最后的调整
让我们在电子邮件正文中发送错误消息,而不是在输出窗口中打印错误消息。这将提供个性化的信息,也留下了到底哪里出错的痕迹。
这可以通过在我们的脚本中添加以下内容来实现:
DECLARE @body nvarchar(max)SET @body = 'This is the error ' + ERROR_MESSAGE()
同时,我们可以删除 PRINT(ERROR_MESSAGE())语句,因为这里不再需要它了。我们脚本的最后一部分应该是这样的:
**BEGIN TRY**INSERT INTO[dbo].[EmployeeCompensation]VALUES ('Carl', 1/0)**END TRY****BEGIN CATCH
BEGIN** DECLARE @body nvarchar(max) SET @body = 'This is the error ' + ERROR_MESSAGE()
EXEC msdb.dbo.sp_send_dbmail @profile_name= '<Your DB Email Account>', @recipients= '<youremail@email.com>', @subject = 'Error Process ABC - insertion error', @body = @body, @body_format = 'HTML'**END****END CATCH**
一封警告邮件应该已经在路上了,里面有关于错误的详细信息。
最后的话
我们现在理解了一个新的 SQL 语句,TRY…CATCH。通过处理错误,这有助于使许多流程更加健壮。然后,它还可以帮助我们修复一些不可预见的事件。警报通知将确保我们了解问题,并获得一些关于拒绝内容及其原因的有价值的见解。
我希望你觉得这篇文章有用,让我知道你的想法,或者如果有我应该涵盖的主题。与此同时,请随时订阅并关注我。下次见!
快乐编码🎉!
感谢阅读!喜欢这个故事吗? 加入媒介 可完整访问我的所有故事。或者通过 我的 ko-fi 页面 用提示支持我
你应该知道的基本的可解释的人工智能 Python 框架
实践中应用可解释人工智能的 9 大 Python 框架

图片来源: Unsplash
可解释的人工智能 是确保 AI 和 ML 解决方案透明、可信、负责和符合道德的最有效实践,以便有效地解决所有关于算法透明度、风险缓解和后备计划的监管要求。AI 和 ML 可解释性技术为这些算法在其解决方案生命周期的每个阶段如何运行提供了必要的可见性,允许最终用户理解为什么和查询如何与 AI 和 ML 模型的结果相关。在本文中,我们将介绍成为应用 XAI 专家需要学习的九大 XAI python 框架。
什么是可解释的人工智能(XAI)?
【可解释的人工智能(XAI) 是解释复杂“黑箱”人工智能模型的功能的方法,这些模型证明了这些模型产生的预测背后的原因。
如果你想要一个 45 分钟的简短视频来简要介绍 XAI,那么你可以观看我在 2021 年 APAC 举行的人工智能加速器节上发表的关于 XAI 的演讲:
可解释的人工智能:使 ML 和 DL 模型更易解释(作者谈)
你也可以浏览我的书 应用机器学习可解释技术 并看看代码库来获得这些 XAI 方法的实践经验。在本文中,我将参考我的书 应用机器学习可解释技术 中讨论的一些 XAI 框架。
如果你想要这本书的详细反馈,下面的视频可能对你有用:
现在,让我提供我对 9 大 XAI python 框架的建议:
1。本地可解释的模型不可知的解释 ( 石灰)
LIME 是一种新颖的、模型不可知的局部解释技术,用于通过学习预测周围的局部模型来解释黑盒模型。LIME 提供了对模型的直观的全局理解,这对于非专家用户也是有帮助的。这项技术最早是在研究论文“我为什么要相信你?”解释任何分类器的预测里贝罗等人。(https://arxiv . org/ABS/1602.04938)。该算法通过使用近似的局部可解释模型,以忠实的方式很好地解释了任何分类器或回归器。它为任何黑箱模型提供了建立信任的全局视角;因此,它允许您在人类可解释的表示上识别可解释的模型,这在局部上忠实于算法。因此,它主要通过学习可解释数据表示、在保真度-可解释度权衡中保持平衡和搜索局部探索来发挥作用。
GitHub:https://github.com/marcotcr/lime
安装 : `pip 安装石灰'
2.沙普利添加剂解说 ( SHAP )
2017 年,Scott Lundberg 和 Su-In Lee 首先从他们的论文中介绍了 SHAP 框架,一种解释模型预测的统一方法(https://arxiv.org/abs/1705.07874)。这个框架背后的基本思想是基于合作博弈理论中的 Shapley 值的概念。SHAP 算法考虑附加特征重要性来解释底层模型特征的集体贡献。数学上,Shapley 值被定义为在特征空间中所有可能的值范围内单个特征值的平均边际贡献。但是对沙普利值的数学理解是相当复杂的,但是在罗伊德·S·沙普利的研究论文《n 人游戏的数值》中有很好的解释对博弈论的贡献 2.28 (1953) 。
GitHub:【https://github.com/slundberg/shap】T42
安装 : `pip 安装形状'

图片来源: GitHub
3。用概念激活向量(TCAV)进行测试
TCAV 是来自谷歌人工智能的模型可解释性框架,它在实践中实现了基于概念的解释方法的思想。该算法依赖于概念激活向量(CAV) ,它使用人类友好的概念提供对 ML 模型内部状态的解释。从更专业的角度来说,TCAV 使用方向导数来量化对人类友好的高级概念对模型预测的重要性。例如,在描述发型时,TCAV 可以使用诸如卷发、直发或发色等概念。这些用户定义的概念不是算法在训练过程中使用的数据集的输入要素。
GitHub:https://github.com/tensorflow/tcav
安装 : `pip 安装 tcav '

TCAV 帮助我们通过神经网络解决了用户定义的图像分类概念重要性的关键问题
4。用于探索和解释的模型不可知语言(DALEX)
DALEX ( 探索和解释的模型不可知语言)是极少数被广泛使用的 XAI 框架之一,它试图解决可解释性的大部分维度。DALEX 是模型不可知的,可以提供一些关于底层数据集的元数据,为解释提供一些上下文。这个框架为您提供了对模型性能和模型公平性的洞察,并且它还提供了全局和局部模型的可解释性。
DALEX 框架的开发人员希望遵守以下要求列表,他们定义这些要求是为了解释复杂的黑盒算法:
- 预测的理由:DALEX 的开发者认为,ML 模型用户应该能够理解最终预测的变量或特征属性。
- 预测的推测:假设假设情景或了解数据集的特定特征对模型结果的敏感性是 DALEX 开发人员考虑的其他因素。
- 预测的验证:对于一个模型的每一个预测结果,用户应该能够验证证实该模型特定预测的证据的强度。
GitHub:https://github.com/ModelOriented/DALEX
安装 : `pip 安装 dalex -U '

DALEX 可解释性金字塔(来源: GitHub DALEX 项目
5.解释器仪表板
该框架允许定制仪表板,但我认为默认版本包括了模型可解释性的所有支持方面。生成的基于 web 应用程序的仪表板可以直接从实时仪表板导出为静态网页。否则,仪表板可以通过自动化的持续集成 ( CI )/ 持续部署 ( CD )部署过程以编程方式部署为 web app。我建议你浏览一下这个框架的官方文档(https://explainerdashboard.readthedocs.io/en/latest/)。
GitHub:https://github.com/oegedijk/explainerdashboard
安装 : `pip 安装说明仪表板'

6.解释性语言
interpret ml(https://interpret.ml/)是微软的 XAI 工具包。它旨在为 ML 模型的调试、结果解释和监管审计提供对 ML 模型的全面理解。有了这个 Python 模块,我们可以训练可解释的玻璃盒子模型或者解释黑盒模型。
微软研究院开发了另一种叫做可解释 Boosting Machine(EBM)的算法,将 Boosting、bagging、自动交互检测等现代 ML 技术引入到广义可加模型 ( GAMs )等经典算法中。研究人员还发现,EBM 作为随机森林和梯度增强树是准确的,但与这种黑盒模型不同,EBM 是可解释的和透明的。因此,EBM 是内置于 InterpretML 框架中的玻璃盒子模型。
GitHub:https://github.com/interpretml/interpret
安装 : `pip 安装解释'

7。不在场证明
Alibi 是另一个开源的 Python 库,旨在机器学习模型检验和解释。该库的重点是为分类和回归模型提供黑盒、白盒、局部和全局解释方法的高质量实现。
GitHub:【https://github.com/SeldonIO/alibi】T4
安装 : `pip 安装 alibi '

来源: ALIBI GitHub 项目
8.不同的反事实解释(DiCE)
多样的反事实解释 ( 骰子)是另一个流行的 XAI 框架,特别是对于反事实解释。有趣的是,DiCE 也是微软研究院的关键 XAI 框架之一,但是它还没有与 InterpretML 模块集成(我想知道为什么!).我发现反事实解释的整个想法非常接近于给出可操作建议的理想的人性化解释。这篇来自微软的博客讨论了 DiCE 框架背后的动机和想法:https://www . Microsoft . com/en-us/research/blog/open-source-library-provide-explain-for-machine-learning-through-diversity-counter factuals/。与 ALIBI CFE 相比,我发现 DiCE 能够以最小的超参数调整产生更合适的 CFE。这就是为什么我觉得提到骰子很重要,因为它主要是为基于示例的解释而设计的。
GitHub:https://github.com/interpretml/DiCE
安装 : `pip 安装骰子-ml '

来源: DiCE GitHub 项目
9.像我五岁一样解释(ELI5)
ELI5 ,或 Explain Like I'm Five ,是一个用于调试、检查和解释 ML 分类器的 Python XAI 库。它是最初开发的 XAI 框架之一,以最简化的格式解释黑盒模型。它支持广泛的 ML 建模框架,如 scikit-learn 兼容模型、Keras 等。它还集成了 LIME 解释器,可以处理表格数据集以及文本和图像等非结构化数据。https://eli5.readthedocs.io/en/latest/的提供了库文档,https://github.com/eli5-org/eli5的提供了 GitHub 项目。
GitHub:https://github.com/TeamHG-Memex/eli5
安装 : `pip 安装 eli5 '

来源: ELI5 GitHub 项目
摘要
本文中介绍的框架是我在模型可解释性方面的首选库。然而,我不建议盲目地应用这些框架,因为你需要对问题和目标受众有一个透彻的理解,以便对人工智能模型进行适当的解释。我推荐阅读这本书:应用机器学习可解释技术并探索 GitHub 库以获得实际操作的代码示例。****
作者关于 TDS 的其他 XAI 相关文章:
- 对基于文本数据训练的模型的可解释机器学习:将 SHAP 与变压器模型相结合
- EUCA——一个有效的 XAI 框架,让人工智能更贴近终端用户
- 理解可解释人工智能中使用的 SHAP 和沙普利值的工作原理
- 如何用石灰解释图像分类器
参考
- 应用机器学习解释技术
- GitHub repo 自《应用机器学习可解释技术》——https://GitHub . com/packt publishing/Applied-Machine-Learning-explability-Techniques/****
机器学习中的 R&D 基本指南:模型构建
ML 研究每一步的基本经验

丹·迪莫克、https://unsplash.com/photos/3mt71MKGjQ0的照片
2021 年,Michael A. Lones 发表了一篇名为“如何避免机器学习陷阱:学术研究人员指南”的每一位数据科学家的必备论文,内容是关于机器学习中的常见错误和挑战,以及处理它们的最佳方法。在他的工作中,他给出了许多关于如何做适当的研究以及如何不做的宝贵建议。
在这篇文章中,我以一种精炼的方式分享了我关于这篇论文的基调以及我的评论和结论。更详细的解释,请查看 Lones 的研究。
我用相同的章节标题,这样任何读者都可以把我的笔记和原文联系起来。
本文涵盖:
- 在你开始建立模型之前
- 如何可靠地建立模型
下一篇文章涵盖:
- 如何稳健地评估模型
- 如何公平地比较模型
- 如何报告您的结果

机器学习管道。作者图。
在你开始建立模型之前
本节讨论在构建模型之前应该做什么和不应该做什么。
花时间思考项目的目标。
这似乎是老生常谈的建议,但它可能是研究中最关键的部分。没有一个具体的目标,研究人员会在短时间内迷失方向。在整个研究过程中,他们会面临诸如' X 没有按预期工作,我应该尝试 Y 还是将项目向 Z 发展?要回答这类问题,他们需要清楚地了解研究的目的。否则,不可避免地会用不必要的实验或没有重要性的概念来填充项目。
一定要花时间去理解你的数据
确保数据集来自可靠的来源。如果它在一篇论文中被描述,检查它是否在一个有声望的地方被发表。阅读作者的笔记,了解任何提到的偏见或缺点。
如果数据集被多次使用,并不意味着它的质量很好。如果数据集质量很差,你的模型也将是垃圾。这就是所谓的垃圾进垃圾出原理。
总是从做探索性数据分析开始。非常了解你的数据,并在工作开始时发现任何弱点。
不要看你所有的数据
将数据分成训练集和测试集。不要在探索性数据分析阶段看你的测试数据。不仅模型会有偏差,人也会有偏差。任何关于测试数据的知识都可能促使你使用一种策略,这种策略可能会对最终产品造成隐藏的偏差。
确保你有足够的数据
在选择机器学习模型时,了解自己的能力很重要。如果您没有足够的数据,您可能需要使用不太复杂的 ML 模型。
检查信噪比(一个统计术语)可能有助于决定数据是否足够。如果信号很弱,你需要更多的数据。您还可以用您的模型进行试验,看看数据是否足够。
如果你发现你需要更多的数据,而你无法收集更多,你可以使用数据扩充技术。或者,您可以使用交叉验证来更有效地使用您的数据。
与领域专家交流
与领域专家交谈可能会节省你很多时间。它们可以帮助你理解解决哪些问题是有用的,以及领域的基本需求是什么。
他们还能帮你找到最合适的观众。如果你正在从事特定领域的 ML 研究,找到合适的会议或期刊来发送你的工作可以让你接触到那些将从你的研究中受益的人。
做调查文献
学术进步是一个典型的迭代过程。你需要了解文献来推进这个迭代。
不要气馁,去寻找与你相似的研究。很可能,在这些研究中仍然有大量的问题需要调查。它们也可以作为你工作的理由。
了解文献可以节省你在已经完成的调查上花费的资源。使用这些资源来增强它们,而不是重复它们。
在这个阶段,我总是从检查最近 5 年发表在著名的特定领域的会议和期刊上的论文开始,然后我继续进行谷歌学术搜索,并涵盖更早的研究。调查论文是进入一个领域的好方法,你也可以使用你阅读的论文的参考文献来加强你的搜索。我也喜欢查paperswithcode.com看看有没有我可以快速重现的研究。
查看 S. Keshav 的“如何阅读论文”,了解更多关于正确阅读论文的信息。
如何可靠地建立模型
借助 Scikit-Learn、Pytorch、Keras 等新颖的 ML 框架。,很容易插上很多 ML 的型号,看看哪个效果更好。然而,在没有任何推理的情况下选择 ML 模型会产生无法解释的麻烦。任何模型决策都需要用逻辑解释来证明。以有组织的方式进行模型构建,并在考虑数据和其他环境变量的同时选择模型。
用一种没有正当理由的蛮力方法挑选模型是我以前经历过的常见错误。这种方法使得向你的同事解释你所做的决定变得更加困难。由于你不知道当前结果的潜在原因,提高你的结果也变得更加困难。我还想提一下,检查所有输入以决定 ML 模型是一种特别有教育意义的方式,可以促进对机器学习和数据科学的理解。
不要让测试数据泄露到培训过程中
来自测试集的任何信息都不能泄漏到训练阶段,因为它会损害模型的通用性和可靠性。
当使用来自整个数据集的信息来获得变量的平均值和范围以执行变量缩放时,会发生泄漏的例子。这应该使用训练数据集来完成。
另一个例子是在分割数据之前进行特征选择,这是 ML 研究中的常见错误。
最好在研究开始时对一个测试数据集进行分区,并在评估最后一个模型之前忘记它。在某些情况下,您甚至可以创建两个不同的测试集来进一步防止有偏见的评估。
尝试一系列不同的型号
总的来说,没有一个 ML 模型可以统治所有的,你应该尝试不同的 ML 模型来找到适合你的情况。
挑模特的时候要虚心。不要把自己局限在你有经验的模型上。
你也应该熟悉文献,知道什么模型被使用,为什么被使用,什么是当前的 SOTA 方法。
结论
作为一名数据科学家,Michael A. Lones 的论文极大地促进了我的自我提升。在这篇文章中,我的目标是与其他人分享这一贡献,并从更广阔的视角展示 ML R&D 的生命周期。
在这一点上,您可以查看的原始研究进行详细分析,或者您可以查看我的下一篇文章,该文章涵盖了从到的洛内斯论文的第二部分。
致敬
埃姆雷·托尔加·阿扬
参考
《如何避免机器学习陷阱:学术研究者指南》arXiv 预印本 arXiv:2108.02497 (2021)。
柯沙夫,斯里尼瓦桑。“如何阅读一篇论文。”ACM SIGCOMM 计算机通信评论 37.3(2007):83–84。
机器学习中的 R&D 基本指南:模型评估
ML 研究每一步的基本经验

照片由亚尼克·普尔弗,【https://unsplash.com/photos/hopX_jpVtRM】T2 拍摄
2021 年,Michael A. Lones 发表了一篇名为“如何避免机器学习陷阱:学术研究人员指南”的每一位数据科学家的必备论文,内容是关于机器学习中的常见错误和挑战,以及处理它们的最佳方法。在他的工作中,他给出了许多关于如何做适当的研究以及如何不做的宝贵建议。
在这篇文章中,我将继续用我的第二篇也是最后一篇关于这篇论文的文章来探索这个非凡的作品。我以一种精炼的方式分享了我关于这篇论文的基调以及我的评论和结论。
更详细的解释,请查看 Lones 的研究。你也可以看看我的第一篇文章,它涵盖了 Lones 工作的前两部分。
我用相同的章节标题,这样任何读者都可以把我的笔记和原文联系起来。
上一篇文章包括:
- 在你开始建立模型之前
- 如何可靠地建立模型
本文涵盖:
- 如何稳健地评估模型
- 如何公平地比较模型
- 如何报告您的结果

机器学习管道。作者图。
如何稳健地评估模型
为了对你的工作做出贡献,你需要公平透明地评估你的模型并报告你的结果。任何有意或无意的不公平都可能误导其他研究者的研究,阻碍文献的迭代进展。检查你的工作两次,以确保你没有偏见。
务必使用合适的测试集
总是使用测试集来测量 ML 模型的通用性。您甚至可以根据问题使用多个测试集。确保您的测试集没有与训练集重叠,并且它很好地代表了总体。
务必使用验证集
创建验证集,并将其用于超参数优化、模型比较、设置提前停止等。换句话说,用它来确保你训练出好的模型。但是不要使用验证集评估结果作为最终结果。这应该使用测试数据集来完成。
多次评估一个模型
许多 ML 模型是不稳定的,在不同的运行中会产生不同的结果。进行多次评估,并保存每次评估的结果。交叉验证是一种流行的方法。如果数据类很小,您可以进行分层,以确保每个数据文件夹都代表总体。
稍后,您可以报告结果的平均值和标准偏差。您还可以在以后使用这些结果进行统计测试来比较模型。
请务必保存一些数据来评估您的最终模型实例
为测试集保留一些数据,仅在评估最终模型时使用。在其他情况下使用测试集的结果可能会促使您做出有偏见的决定。
换句话说,不要让测试集成为训练过程的隐含部分。把它分开来评估你的最终产品。
不要对不平衡的数据集使用准确性
这个问题的一个经典例子是病人与健康人的分类任务。想象一个数据集,其中只有 0.1%的人口生病,而你正试图训练一个 ML 模型来预测一个人是否生病。如果你的模型预测所有样本都是健康的,你会得到 99.9%的准确率,但是你的模型并没有像预期的那样解决问题。因此,如果数据集不平衡,准确性就不是一个可靠的指标。在这种情况下,最好使用科恩的卡帕系数或马修斯相关系数。
我还发现,比较精确度、召回率和 f1 分数的宏观和微观值,以了解 ML 模型如何对不平衡的数据做出反应,这很有用。
如何公平地比较模型
我们多次讨论了公平模型评估,但是 Lones 创建了一个完整的独立部分。我认为这是有意表明公正的评价很重要。如前所述,不公平的评估可能会在工作中误导你的同事,并耗费金钱和时间。这是一种损害文献迭代过程的行为,任何研究者都应该关心它。
因此,请确保在相同的环境中评估不同的模型,探索多个视角,并小心正确使用统计测试。
不要以为数字越大就意味着型号越好
Lones 提到了许多论文中使用的一个常见状态:
“在以前的研究中,报道的准确率高达 94%。我们的模型达到了 95%,因此更好。”
更高的值并不总是意味着更好的模型。训练验证测试分区可能不同,它们可能用于完全不同的数据集,或者它们的超参数可能不同。这些因素可能会造成不公平的比较,研究人员必须对此保持谨慎。
在比较模型时,一定要使用统计测试
有两类测试用于比较单个 ML 模型。
第一种类型是比较单个模型实例,就像两个经过训练的决策树。对于这种类型的比较,麦克内马的测试是一个公平的选择。
第二种类型是比较一般的模型选择,比如决策树和神经网络。这种类型的比较需要多次评估。如果数据的分布是正态分布,学生的 T 检验在这种类型的比较中是有用的。如果不是正态分布,曼-惠特尼的 U 检验是更好的选择。
请纠正多重比较
如果你以 95%的置信度报告统计测试结果,就意味着 20 次中有 1 次会给你一个假阳性。如果您运行多个测试,很可能其中一个实际上是不正确的。这被称为多重性效应,是数据科学中更广泛问题的一个例子,称为数据挖掘或 p-hacking。Bonferroni 校正法是处理这一问题的好方法。
不要总是相信来自社区基准的结果
像 ML 模型一样,人们也会有偏见。如果每个使用数据的人只使用测试集一次,那么总的来说,测试集被社区使用了很多次。人们可能会根据这些可能导致偏见的基准来形成他们的决策。因此,请小心这些基准报告,并谨慎您的决定。
请考虑模型的组合
考虑集成学习方法总是好的。正确组合多个模型可以显著提高性能,应该由研究人员进行实验。
如何报告您的结果
一定要透明
永远试着对你所做的和你所发现的保持透明,因为这将使其他人更容易在你的工作基础上更进一步。
如果可以的话,分享你的代码和数据集。这样做也鼓励你更加小心,工作更加干净。这既有利于你,也有利于其他研究人员。
以多种方式报告绩效
如果可能的话,用多个数据集报告你的结果。使用多个指标评估您的模型,以增加您工作的透明度。您可以使用的一些指标有精确度、召回率、灵敏度、特异性和 F 值。您还可以报告 AUC、ROC 和 PR 曲线。在报告这些指标时,确保您清楚地标记了数字和表格,以防止混淆。
不要超越数据进行归纳
重要的是不要给出无效的结论,因为这会把其他研究人员引入歧途。例如,您的模型在您的测试数据上的性能可能与其他一些数据集不同。不要将你的讨论概括到你没有试验过的案例。
在报告统计意义时一定要小心
统计测试并不完美。例如,95%的置信度意味着 20 次测试中有 1 次产生错误的结果。一些统计学家甚至说,最好报告 p 值,让读者来解释。
另一件要考虑的事情是两个模型之间的差异实际上是否重要。Cohen 的 d 统计或 Kolmogoroc-Smirnov 方法对于这种类型的比较是优选的。
一定要看看你的模型
研究的目的不是比任何人做得更好,而是创造有益于学术发展迭代过程的知识。试着解释你的模特表现背后的潜在原因。查看可解释的人工智能(XAI)技术,展示可能用于进一步研究的知识。
结论
作为一名数据科学家,Michael A. Lones 的论文极大地促进了我的自我提升。在这篇文章中,我的目标是与其他人分享这一贡献,并从更广阔的视角展示 ML R&D 的生命周期。
任何想进一步了解的人都应该查阅一下 Lones 的论文。他还为他讨论的主题提供了大量参考资料。
我感谢 Lones 的论文,也感谢抽出时间阅读本文的读者。
致敬
埃姆雷·托尔加·阿扬
参考
《如何避免机器学习陷阱:学术研究者指南》 arXiv 预印本 arXiv:2108.02497 (2021)。
开始数据科学项目之前要问的基本问题
如何确保你的项目有一个良好的开端

在 Unsplash 上由Towfiqu barb huya拍摄的照片
在每个项目开始之前,问一些问题来帮助你了解接下来几周甚至几个月你将会做什么是很重要的。在项目开始时,询问诸如我们要完成什么、我们为什么要完成、最终用户将如何受益等问题是非常重要的,因为它们对于推动成功的结果和使您要解决的问题变得清晰非常重要。
在开始数据科学项目之前,您应该问以下问题:
- 谁是客户,客户在哪个业务领域?
了解客户所在的业务领域、他们的运营方式、对他们来说什么是重要的、哪些关键变量用于定义该领域的成功,将使您能够构建一个直接影响对客户来说什么是重要的解决方案。
2。我们试图解决什么业务问题?
《面向预测数据分析的机器学习基础》一书完美地描述了这一点:
组织不存在进行预测性数据分析。组织的存在是为了做一些事情,比如赚更多的钱,获得新的客户,销售更多的产品,或者减少欺诈造成的损失。不幸的是,我们可以建立的预测分析模型不做这些事情。分析从业者构建的模型只是根据从历史数据集中提取的模式进行预测。这些预测并不能解决商业问题;相反,它们提供的见解可以帮助组织做出更好的决策来解决他们的业务问题。
因此,在任何数据分析项目中,一个关键步骤是了解组织想要解决的业务问题,并基于此确定预测分析模型可以提供何种洞察力来帮助组织解决该问题。这定义了分析解决方案,分析从业者将着手使用机器学习[1]来构建该解决方案。
如果您公司的目标是降低客户流失率,一个可能的解决方案是建立一个预测模型,确定哪些客户在不久的将来最有可能流失。
3。客户将如何消费它?
了解您的客户将如何使用您的模型输出,将允许您创建针对他们的工作。例如,您是在构建服务于内部用户并影响公司战略的模型,还是在构建面向客户的模型。
4。这个项目的经济影响是什么?
给一个项目设定一个金额是最难做到的事情之一。但是,了解您的数据产品将如何为客户增加收入或降低成本,可以让您获得领导地位,并在整个项目中为您提供支持。
5。我们的数据科学功能将推动什么类型的决策?
什么样的模式能让他们做以前做不到的事情。
6。我们将使用什么标准来衡量这个项目的成功,我们将如何衡量它?
心中有一个具体的目标将确保你的项目有一个最终的结果,你不会无限期地工作。量化指标值的哪些改进对客户场景有用(例如,减少 20%的劳动力成本)。度量必须是智能 ( S 特定, M 可测量, A 可实现, R 相关,以及 T 时间受限)。例如:在这个为期 3 个月的项目结束时,实现 20%的客户流失预测准确率,以便我们可以提供促销活动来减少流失[2]。
想象一下,数据科学家(DS)和产品经理(PM)就在应用程序中引入新的 ML 功能展开对话,该应用程序旨在提供更好的仓库运营可见性。假设产品经理非常了解仓库空间,并且头脑中已经有了一个特性。
我认为客户 ABC 正面临一些问题。你能帮助我理解问题是什么吗?
PM: 确定。ABC 一直在努力实现每日订单目标。
什么是每日订单目标?
仓库通常在一天开始时设定订单目标,试图在一天结束前发货。例如,在一天开始时,仓库中的操作员将设定一些订单目标,比如 45000 个订单,他们需要在一天结束前出门并发货。
DS: 明白了!为什么达到这个每日目标对他们很重要?
PM: 好问题。没有达到当天的订单目标意味着没有按时交付给客户,这可能会导致额外的支持成本、声誉受损以及客户流失。为了让客户的生活更轻松,我建议在应用程序中发布一个 ML 功能,帮助我们的客户更好地了解他们今天是否有望根据他们当前的表现达到订单目标。
DS: 明白了。还有你为什么觉得这个功能对他们有用?这将推动什么类型的决策?
PM: 好问题。最重要的一个用例是,它将允许仓库中的操作员在早期相应地分配劳动力。例如,如果我们当天的预计发货订单低于他们的每日目标,他们可以增加工人数量,以加快货物运输速度。因此,这有助于他们更有效地开展日常业务。
DS: 客户将如何消费它?

作者图片
PM: 让我分享一下我的屏幕,向你们展示一下。用户将能够在我们的应用程序中看到这个功能。我是这样设想的:蓝色实线显示了到目前为止他们已经发货的订单。绿色虚线是从我们的模型中生成的预测。红色实线是他们今天的目标。
DS: 啊,这是一个很好的视觉效果——让我把事情看得很清楚。所以这将是一个实时功能,我们每小时更新一次当天生成的预测?
下午:是的。没错。
DS: 另一个问题:他们目前使用什么,该指标的基线(当前)值是多少?
PM: 他们目前不使用任何东西,这就是为什么这个特性会给他们的运营带来很多清晰性。
这个项目的经济影响是什么?成功的标准是什么?
PM: 很棒的问题。好吧,如果我们的预测在这两个月结束时有小于 30%的平均绝对误差,我们可以称这个项目的第一次迭代完成了。至于经济影响,我的粗略估计是,该功能还将允许他们优化资源规划和分配决策,这将帮助他们减少对劳动力的依赖,并将成本降低 30%。我将不得不做更多的调查和处理一些数字,以得到确切的金额。
DS: 啊,看起来这个功能会为我们的客户提高很多部门的效率。让我检查数据,然后获取所有这些信息,并创建一个关于我将如何着手做这个项目的粗略计划,并与您和团队分享以获得反馈。
下午:太棒了!谢了。
Lak Ananth 在他的书预见失败中指出“每个企业都是从问题是什么、解决方案是什么以及为什么它是一个引人注目的企业这一部分开始的”。同样,一个数据科学项目必须从一个假设开始,即我们试图解决什么样的客户问题,我们为什么试图解决它,以及它的影响是什么。
参考文献
[1] Kelleher,J. D .,Namee,M. B .,& D'Arcy,A. 预测数据分析的机器学习基础:算法、工作实例和案例研究 (2015)。麻省理工学院出版社。
[2] Microsoft, Azure-TDSP-ProjectTemplate , (2021)
设计熊猫数据框的基本技巧
原文:https://towardsdatascience.com/essential-techniques-to-style-pandas-dataframes-8796c74c9ff3
如何有效地用表格(包括备忘单)交流数据

造型熊猫(作者图片)
在数据分析的最后,你需要决定如何传达你的发现。当你需要你的听众查找单个的精确值并将它们与其他值进行比较时,表格比图表更适合于交流数据。然而,表格包含了大量的信息,你的听众通过阅读来处理这些信息,这使得你的听众很难马上理解你的信息。表格的随意设计,如太多的颜色、粗体边框或太多的信息,会额外分散你的观众的注意力。但是,有目的地使用格式和样式可以将观众的注意力引导到表格中最重要的数字上。
有目的地使用格式和样式可以引导你的观众注意到表格中最重要的数字。
pandas 库中的数据帧非常适合在 Python 中将数据可视化为表格。此外,pandas 库提供了通过style属性格式化和样式化数据帧的方法。因此,本文讨论了对 pandas 数据帧进行格式化和样式化的基本技术,以便有效地传递数据。
如果您想尝试本文中描述的技术,您可以从我的相关 Kaggle 笔记本下载或派生本文的代码,包括示例数据集。
在本教程中,我们将使用以下小型虚构数据集:

虚构样本数据集的无样式熊猫数据框架(图片作者来自 Kaggle
全局显示选项
在开始为单个数据帧定制可视化之前,您可以使用.set_option()方法[1]调整 pandas 的全局显示行为。您可以处理的两个常见任务是:
- 显示数据帧的所有列
- 调整 DataFrame 列的宽度。
当你的数据帧有太多的列时,pandas 不会显示所有的列,而是隐藏中间的列。要强制 pandas显示所有列,您可以设置:
pd.set_option("display.max_columns", None)
当您处理长文本时,pandas 会截断列中的文本。要通过增加列宽来强制 pandas显示整列内容,您可以设置:
pd.set_option("display.max_colwidth", None)
一般提示
DataFrame 的style属性返回一个类Styler的对象。该类包含各种格式和样式方法。以下提示适用于Styler对象的所有方法。
多种风格
通过将多个方法链接在一起,可以组合多种样式。
例如df.style.set_caption(...).format(...).bar(...).set_properties(...)
列方式与行方式的样式
默认情况下,样式是按列应用的(axis = 0)。
df.style.highlight_max() # default is axis = 0

用“轴= 0”突出显示列方向的最大值(图片由作者从 Kaggle 获得)
如果您想按行应用样式,请在属性中使用axis = 1。
df.style.highlight_max(axis = 1))

用“轴= 1”高亮显示行方向的最大值(图片由作者从 Kaggle 获得)
仅设计子集的样式
默认情况下,样式方法应用于所有列。
df.style.background_gradient()

应用于所有列的背景渐变(图片由作者从 Kaggle 获得)
如果您想将样式仅应用于一列或一组选定的列,使用subset参数。
df.style.background_gradient(subset = ["A", "D"]))

应用于 A 列和 D 列的背景渐变(图片由作者从 Kaggle 获得)
格式化
在我们开始任何特定的着色之前,让我们先来看看一些基本的格式化技术,让你的数据帧看起来更加完美。
标题
向表格添加标题对于向受众提供一些背景信息至关重要。您可以使用.set_caption()方法向数据帧添加标题。
df.style.set_caption("Caption Text")

数据框上方添加的标题(图片由作者从 Kaggle 获得)
重命名列
如果列名是变量名或缩写,您的读者可能不清楚他们正在查看什么数据。为列提供直观的列名,可以支持您的受众对数据的理解。
重命名列有两个选项:
- 一次重命名所有列
- 仅重命名列的子集
如果您稍后需要使用数据帧,创建数据帧的副本仅用于可视化目的可能是有意义的。
# Create a copy of the DataFrame for visualization purposes
df_viz = df.copy()
您可以通过更改columns属性来一次重命名所有列:
# Rename all columns
df_viz.columns = ["New Column Name A",
"New Column Name B",
"New Column Name C",
"New Column Name D"]

重命名 DataFrame 的所有列(图片作者来自 Kaggle )
或者您可以使用 .rename()方法和一个字典只重命名列的子集。
# Rename selection of columns
df_viz.rename(columns = {"A" : "New Column Name A",
"B" : "New Column Name B"},
inplace = True)

仅重命名列的子集(图片由来自 Kaggle 的作者提供)
隐藏索引
索引是否添加了任何有价值的信息?如果没有,你可以用.hide_index()的方法隐藏索引。
df.style.hide_index()

隐藏索引(图片作者来自 Kaggle
格式化列
添加千位分隔符或截断浮点数到更少的小数位数可以增加数据帧的可读性。为此,Styler对象可以区分显示值和实际值。
通过使用.format()方法,您可以根据格式规范字符串[3]操作显示值。您甚至可以在数字之前或之后添加一个单位作为格式的一部分。
df.style.format({"A" : "{:,.0f}",
"B" : "{:d} $",
"C" : "{:.3f}",
"D" : "{:.2f}"})

使用千位分隔符和自定义浮点数格式化的列(图片来自作者来自 Kaggle
然而,为了不引起注意,我建议在列名中将单位放在方括号中,例如“薪金[$]”。
样式属性
有时,你想做的可能只是通过调整背景和字体颜色来让突出显示数据框中的一列。为此,您可以使用.set_properties()方法调整 DataFrame 的相关 CSS 属性,如颜色、字体、边框等。
df.style.set_properties(subset = ["C"],
**{"background-color": "lightblue",
"color" : "white",
"border" : "0.5px solid white"})

突出显示的栏(图片由作者从 Kaggle 获得)
内置样式
Style类有一些用于常见样式任务的内置方法。
突出
突出显示单个单元格是一种简单的方法,可以将观众的注意力引导到您想要展示的内容上。您可能想要突出显示的常见值有最小值、最大值和空值。对于这些情况,您可以使用各自的内置方法。
您可以使用参数color调整高亮颜色,以进行最小和最大高亮显示,使用参数nullcolor调整零高亮显示。
df.style.highlight_null(null_color = "yellow")

突出显示的空值(图片来自作者来自 Kaggle
如果您想要突出显示最小值和最大值,可以通过将两个函数链接在一起来实现。
df.style.highlight_min(color = "red")\
.highlight_max(color = "green")

突出显示的最小值和最大值(图片由作者从 Kaggle 获得)
梯度
添加渐变样式可以帮助读者理解表格、单列或单行中的数值之间的关系。例如,渐变可以指示一个值是大还是小,是正还是负,甚至是好还是坏。
还有两种技术可以向数据帧添加渐变:
- 您可以将渐变样式应用于文本或
- 您可以将渐变样式应用于背景[2]。
使用cmap参数、vmin和vmax可以设置渐变的属性。cmap设置使用的颜色图,vmin和vmax设置相关的开始和结束值。
您可以使用.text_gradient()方法对文本应用渐变:
df.style.text_gradient(subset = ["D"],
cmap = "RdYlGn",
vmin = -1,
vmax = 1)

渐变样式应用于 D 列的文本(图片由作者从 Kaggle 获得)
或者您可以使用.background_gradient()方法将渐变应用于背景:
df.style.background_gradient(subset = ["D"],
cmap = "RdYlGn",
vmin = -1,
vmax = 1)

应用于 D 列背景的渐变样式(图片来自 Kaggle )
酒吧
另一种可视化列或行中的关系和顺序的方法是在单元格的背景中绘制线条[2]。
同样,在数据帧中使用条形有两个基本技巧:
- 直接的应用是使用标准的单色条。
- 或者你也可以从中点创建双色条形图。
对于标准条形图,简单使用.bar()方法如下:
df.style.bar(subset = ["A"],
color = "lightblue",
vmin = 0)

添加到 A 列背景的条(图片由作者从 Kaggle 获得)
要创建双色条形图,将校准设置为mid并定义上下限值的颜色。在使用这种方法时,我建议结合一些边框来增加可读性。
df.style.bar(subset = ["D"],
align = "mid",
color = ["salmon", "lightgreen"])\
.set_properties(**{'border': '0.5px solid black'})

添加到 D 列背景的正值和负值条(图片由作者从 Kaggle 获得)
自定义样式
如果内置的样式方法不足以满足您的需求,您可以编写自己的样式函数,并将其应用于 DataFrame。您可以使用.applymap()方法应用元素样式,或者使用.apply()方法[2]应用列或行样式。
一个常见的例子是用红色显示数据帧的负值,如下所示:
def custom_styling(val):
color = "red" if val < 0 else "black"
return f"color: {color}"df.style.applymap(custom_styling)

用红色突出显示负值(图片由来自 Kaggle 的作者提供)
导出到 Excel
如果您需要 Excel 格式的样式化数据帧,您可以将其导出,包括样式化和格式化到。xlsx 文件[3]。为此,您需要安装 openpyxl 包。
pip install openpyxl
要导出数据帧,您可以像往常一样对数据帧应用样式和格式,然后使用.to_excel()方法。
df.style.background_gradient(subset = ["D"],
cmap = "RdYlGn",
vmin = -1,
vmax = 1)\
.to_excel("styled.xlsx", engine = "openpyxl")
结论
pandas 数据框架的style属性使您能够格式化和设计数据框架,以有效地传达您的数据分析的见解。本文讨论了设置 pandas 数据帧样式的基本技术,包括如何设置全局显示选项、格式化和定制样式,甚至如何将数据帧导出为 Excel 格式。在 pandas 文档中有更多的样式和格式选项。
为了进行进一步的实验,您可以从我的相关 Kaggle 笔记本中下载或派生本文的代码,包括示例数据集。
以下是我在备忘单中总结的所有提示:

熊猫数据框架的基本格式和样式技术备忘单(图片由作者提供)。
喜欢这个故事吗?
要阅读更多来自我和其他作家的故事,请在 Medium 上注册。报名时可以用我的 推荐链接 支持我。我将收取佣金,不需要你额外付费。
https://medium.com/@iamleonie/membership
在 LinkedIn 和 上找我 Kaggle !
参考
[1]“熊猫 1.4.2 文档”,“选项和设置。”pandas.pydata.org。https://pandas.pydata.org/docs/user_guide/options.html(2022 年 6 月 13 日访问)
[2]《熊猫 1.4.2 文档》,《样式》pandas.pydata.org。https://pandas.pydata.org/docs/reference/style.html(2022 年 6 月 16 日访问)
[3]“熊猫 1.4.2 文档化”,“表格可视化。”pandas.pydata.org。https://pandas.pydata.org/docs/user_guide/style.html(2022 年 6 月 16 日访问)
[4]“Python”,“string——常见的字符串操作。”python.org。https://docs.python.org/3/library/string.html#formatspec(2022 年 6 月 13 日访问)
Python 中 Firestore 的使用要点
原文:https://towardsdatascience.com/essentials-for-working-with-firestore-in-python-372f859851f7
学习用 Python 管理 Firebase 应用程序数据

Firestore 由 Firebase 和 Google Cloud 提供,是一个流行的 NoSQL 移动和网络应用云数据库。像 MongoDB 一样,Firestores 将数据存储在包含映射到值的字段的文档中。文档被组织成与关系数据库中的表相对应的集合。
为了使用 Python 管理 Firestore 数据,我们需要使用 Firebase Admin SDK,它是一组库,允许您从特权环境中与 Firebase 进行交互。在这篇文章中,我们将通过一些简单的例子介绍如何使用 Admin SDK 管理 Firestore 中的数据,这些例子涵盖了常见的 CRUD 操作。
创建一个 Firebase 项目
在 Python 中使用 Firestore 之前,我们需要有一个活动的 Firebase 项目。如果还没有,你可能想先看看这篇文章,以便快速上手 Firebase。
安装 Firebase Admin SDK
为了在 Python 中使用 Firestore,我们需要首先安装 Firebase Admin SDK ,它可以安装在您的虚拟环境中。您可以选择自己喜欢的工具来创建/管理虚拟环境。这里使用 Conda 是因为我们可以在虚拟环境中安装特定版本的 Python,如果您的系统的 Python 版本很旧,而您不愿意或无法升级它,这将非常方便。
# You need to specify a channel if you need to install the latest version of Python.
$ conda create --name firebase python=3.11 -c conda-forge
$ conda activate firebase
$ pip install --upgrade firebase-admin ipython
安装 iPython 是为了更方便的交互运行 Python 代码。
在 GCP 初始化 Firebase Admin SDK
如果您的 Python 代码运行在 Google Cloud 环境中,如 Compute Engine、App Engine、Cloud functions 等,您可以在没有参数的情况下初始化 Firebase,因为凭证查找是自动完成的:
import firebase_admin
from firebase_admin import firestore
app = firebase_admin.initialize_app()
firestore_client = firestore.client()
在非 GCP 环境中初始化 Firebase Admin SDK
如果您的 Python 代码在非 GCP 环境中运行,您将需要使用您的 Firebase 服务帐户的私钥文件来验证 Firebase。创建 Firebase 项目时,会自动创建此服务帐户。
要为您的服务帐户生成私钥文件,请转到 Firebase 控制台,并遵循以下说明:

作者图片
一旦生成了私钥文件,就可以用它来验证 Firebase。您可以将它与应用程序默认凭证(ADC)一起使用,这意味着将环境变量GOOGLE_APPLICATION_CREDENTIALS设置为包含您的服务帐户私钥的 JSON 文件的路径。这样,应用程序默认凭据(ADC)就能够隐式地确定 Firebase 凭据。这种方式更安全,在适用的情况下推荐使用。
$ export GOOGLE_APPLICATION_CREDENTIALS="/home/lynn/Downloads/service-account-file.json"
然后,您可以按如下方式初始化 Firebase SDK:
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
# Use the application default credentials.
cred = credentials.ApplicationDefault()
firebase_admin.initialize_app(cred)
firestore_client = firestore.client()
然而,如果您有多个 Firebase 项目或者您的 Firebase 项目不属于您的默认 Google Cloud 项目,那么设置GOOGLE_APPLICATION_CREDENTIALS环境变量是不适用的。在这些情况下,我们需要直接使用私钥文件进行身份验证:
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
# Use the private key file of the service account directly.
cred = credentials.Certificate("/home/lynn/Downloads/service-account-file.json")
app = firebase_admin.initialize_app(cred)
firestore_client = firestore.client()
您可以用上面演示的三种方法中的任何一种来初始化 Firebase Admin SDK。如果您在本地使用笔记本电脑工作,那么第三款最有可能适合您。
既然 Firebase Admin SDK 已经过验证和初始化,我们就可以开始用它创建集合和文档了。我们将用简单的代码片段介绍常见的 C reate、 R ead、 U pdate 和Ddelete(CRUD)操作。
创建文档
与 MongoDB 类似,Cloud Firestore 是无模式的,具有动态映射。第一次向文档添加数据时,它会隐式创建集合和文档。因此,我们不需要显式地创建集合或文档并指定映射(即字段的类型定义)。我们可以直接创建一个文档并将数据分配给它:
doc_ref = firestore_client.collection("laptops").document("1")
doc_ref.set(
{
"name": "HP EliteBook Model 1",
"brand": "HP",
}
)
请注意,文档 id(“1”)必须是唯一的,并且必须是一个字符串。
引用是指向 Firestore 数据库中目标集合或文档的位置的对象。创建对目标集合或文档的引用时,它不需要存在。一旦创建了引用,我们就可以添加数据。Firestore 中的所有 CRUD 操作都是通过引用实现的,我们将在后面看到。
运行该代码片段后,将创建集合和文档,这可以在 Firebase 控制台中查看:

作者图片
因为 Firestore 中的文档是无模式的,这意味着文档没有预定义的字段,所以我们可以在每个文档中有不同的字段。现在让我们添加一个新的笔记本文档,其中包含一些额外的字段:
doc_ref = firestore_client.collection("laptops").document("2")
doc_ref.set(
{
"name": "Lenovo IdeaPad Model 2",
"brand": "Lenovo",
"tags": ["Popular", "Latest"],
"order": {"price": 9405.0, "quantity": 2},
}
)
对于第二个文档,添加了两个新字段,即数组字段和映射字段,映射字段是嵌套对象(或者 Python 中的字典)。这是它们在 Firebase 控制台中的显示方式:

作者图片
在上面的例子中,我们指定了文档 id,这是唯一的字符串。但是,如果文档没有包含唯一值的字段,我们可以省略文档 ID,让 Firestore 使用add()方法为我们分配一个自动生成的 ID:
coll_ref = firestore_client.collection("laptops")
create_time, doc_ref = coll_ref.add(
{
"name": "Apple macbook air",
"brand": "Apple",
}
)
print(f"{doc_ref.id} is created at {create_time}")
# CnidNv3f6ZQD9K7MnLyy is created at 2022-11-13 09:55:23.989902+00:00

作者图片
创建带有子集合的文档
子集合是与特定文档相关联的集合。在本例中,我们将创建一个包含笔记本电脑属性的子集合。
laptop_ref = firestore_client.collection("laptops").document("4")
laptop_ref.set(
{
"name": "Apple Macbook Pro",
"brand": "Apple",
}
)
# Specify the subcollection for a laptop document.
attr_coll = laptop_ref.collection("attributes")
# Add documents to the subcollection.
attr_ref = attr_coll.document("storage")
attr_ref.set({"name": "Storage", "value": "1", "unit": "TB"})
# We don't need to create the doc ref beforehand if the metadata is not needed.
attr_coll.document("ram").set({"name": "ram", "value": "16", "unit": "GB"})
请注意子集合在 Firebase 控制台中的显示方式:

作者图片
使用子集合有许多限制。一个主要的,对我来说似乎是错误的是,当父文档被删除时,子集合没有被删除。然而,这可能不是使用子集合的好例子。一个更好的例子是官方文档中给出的聊天室示例,其中每个子集合中的消息都是独立且等价的实体,当父文档被删除时,保留这些消息更有意义。
在这个简单的示例中,attributes 子集合可以由一组映射替换:
laptop_ref = firestore_client.collection("laptops").document("5")
laptop_ref.set(
{
"name": "Apple Macbook Pro",
"brand": "Apple",
"attributes": [
{"name": "Storage", "value": "1", "unit": "TB"},
{"name": "ram", "value": "16", "unit": "GB"},
],
}
)
Firestore 就像前端开发人员的傻瓜相机,如果你需要嵌套文档的更多高级功能,你可能想尝试更多专用的服务器端数据库,如 MongoDB 或 Elasticsearch 。
阅读文档
现在我们已经插入了一些文档,我们可以尝试用不同的方式阅读它们。
首先,让我们通过 ID 读取单个文档。
doc_ref = firestore_client.collection('laptops').document("1")
# We can read the id directly:
print(f"The document id is {doc_ref.id}")
# The document id is 1
# We need to use .get() to get a snapshot of the document:
doc = doc_ref.get()
print(f"The document is {doc.to_dict()}")
# The document is {'brand': 'HP', 'name': 'HP EliteBook Model 1'}
注意,我们需要调用文档引用的.get()方法来获取文档数据的快照。
现在,让我们阅读集合中的所有文档:
coll_ref = firestore_client.collection('laptops')
# Using coll_ref.stream() is more efficient than coll_ref.get()
docs = coll_ref.stream()
for doc in docs:
print(f'{doc.id} => {doc.to_dict()}')
注意,coll_ref.stream()返回的是DocumentSnapshot的生成器,而coll_ref.get()返回的是它们的列表。因此,coll_ref.stream()效率更高,大多数情况下应该首选。
以下是代码片段的结果:
1 => {'brand': 'HP', 'name': 'HP EliteBook Model 1'}
2 => {'tags': ['Popular', 'Latest'], 'order': {'quantity': 2, 'price': 9405.0}, 'brand': 'Lenovo', 'name': 'Lenovo IdeaPad Model 2'}
4 => {'brand': 'Apple', 'name': 'Apple Macbook Pro'}
5 => {'attributes': [{'value': '1', 'unit': 'TB', 'name': 'Storage'}, {'value': '16', 'unit': 'GB', 'name': 'ram'}], 'brand': 'Apple', 'name': 'Apple Macbook Pro'}
CnidNv3f6ZQD9K7MnLyy => {'brand': 'Apple', 'name': 'Apple macbook air'}
注意,缺省情况下不读取 document 4 的子集合,但是会读取 document 5 的映射数组。实际上,子集合中的文档需要像顶级文档一样被显式读取。让我们读一下文档 4 的attributes子集合中的属性文档:
attr_coll_ref = (
firestore_client.collection("laptops")
.document("4")
.collection("attributes")
)
for attr_doc in attr_coll_ref.stream():
print(f"{attr_doc.id} => {attr_doc.to_dict()}")
这次可以成功读取属性文档:
ram => {'value': '16', 'unit': 'GB', 'name': 'ram'}
storage => {'value': '1', 'unit': 'TB', 'name': 'Storage'}
使用过滤查询阅读文档
在上述读取操作中,在没有过滤条件的情况下读取文档。在实践中,执行简单和复合查询来获取我们需要的数据是很常见的。
首先,让我们尝试获得所有品牌为 Apple 的笔记本电脑:
# Create a reference to the laptops collection.
coll_ref = firestore_client.collection("laptops")
# Create a query against the collection reference.
query_ref = coll_ref.where("brand", "==", "Apple")
# Print the documents returned from the query:
for doc in query_ref.stream():
print(f"{doc.id} => {doc.to_dict()}")
这是从这段代码中返回的内容:
4 => {'brand': 'Apple', 'name': 'Apple Macbook Pro'}
5 => {'attributes': [{'value': '1', 'unit': 'TB', 'name': 'Storage'}, {'value': '16', 'unit': 'GB', 'name': 'ram'}], 'brand': 'Apple', 'name': 'Apple Macbook Pro'}
CnidNv3f6ZQD9K7MnLyy => {'brand': 'Apple', 'name': 'Apple macbook air'}
注意,我们需要首先创建对集合的引用,然后基于它生成查询。
集合引用的where()方法用于过滤,它有三个参数,即要过滤的字段、比较运算符和值。常见查询操作符的列表可以在这里找到。
有两个运算符很容易混淆,分别是in和array-contains,我们用两个简单的例子来检查一下。
in操作符返回给定字段匹配任何指定值的文档。例如,此查询查找品牌为“HP”或“Lenovo”的笔记本电脑:
query_ref = coll_ref.where("brand", "in", ["HP", "Lenovo"])
另一方面,array-contains操作符返回给定数组字段包含指定值作为成员的文档。以下查询查找带有“Popular”标签的笔记本电脑:
query_ref = coll_ref.where("tags", "array_contains", "Popular")
查询子集合并添加索引
因为我们在一个笔记本文档中有子集合,所以让我们看看如何通过子集合进行过滤,以及结果会是什么样子。
由于每个笔记本文档都可以有自己的attributes子集合,我们需要通过一个集合组进行查询,这个集合组就是具有相同 ID 的所有集合。
collection_group方法用于按采集组过滤。让我们查找名称为“Storage”、单位为“TB”、值为“1”的属性:
query_ref = (
firestore_client.collection_group("attributes")
.where("name", "==", "Storage")
.where("unit", "==", "TB")
.where("value", "==", "1")
)
for doc in query_ref.stream():
print(f"{doc.id} => {doc.to_dict()}")
正如我们看到的,where()方法可以通过多个字段链接到过滤器。当上面的代码运行时,会出现一个错误,说明没有可用的索引。
FailedPrecondition: 400 The query requires an index. You can create it here: https://console.firebase.google.com.....
默认情况下,Firestore 会自动为每个字段创建单个字段索引,从而支持按单个字段进行筛选。然而,当我们按多个字段过滤时,就需要一个复合索引。
单击控制台中给出的链接,您将被定向到为上述查询创建相应的复合索引的页面:

作者图片
单击“创建索引”创建综合索引。这需要一些时间来完成。完成后,状态将变为“已启用”:

作者图片
当您再次运行上面的子集合查询时,您将成功地获得结果:
storage => {'value': '1', 'unit': 'TB', 'name': 'Storage'}
注意,它只返回attributes子集合中的文档,不返回父文档。
Firestore 的查询有很多限制,不适合复杂查询,尤其是嵌套字段和全文搜索。对于更高级的搜索,应该考虑使用 MongoDB 和 Elasticsearch 。然而,对于前端使用,上面提供的基本查询在大多数情况下应该足够了。
更新文档
当你到达这里的时候祝贺你!我们已经讨论了写作和阅读中最复杂的部分。更新和删除文档的其余部分要简单得多。
让我们首先更新特定字段的值。例如,让我们将 Apple MacBook air 的产品名称更新为全部大写:
doc_ref = firestore_client.collection("laptops").document(
"CnidNv3f6ZQD9K7MnLyy"
)
doc_ref.update({"name": "Apple MacBook Air"})
这些更改应该是即时的,因为可以在 Firebase 控制台中找到。
然后让我们看看如何更新一个嵌套字段。让我们将 ID 为“2”的文档的数量更改为 5:
doc_ref = firestore_client.collection("laptops").document("2")
doc_ref.update({"order.quantity": 5})
请注意,嵌套字段是用点符号指定的。
最后,让我们更新tags字段,它是一个数组字段。让我们添加“库存”标签,并删除“最新”标签:
doc_ref = firestore_client.collection('laptops').document('2')
# Add a new array element.
doc_ref.update({'tags': firestore.ArrayUnion(["In Stock"])})
# Remove an existing array element.
doc_ref.update({'tags': firestore.ArrayRemove(["Latest"])})
请注意,要添加或移除的数组元素被指定为数组本身。当添加或删除一个元素时,看起来可能很奇怪,但是当处理多个元素时,就变得更自然了。
删除文档
最后,我们来看看如何删除文档。嗯,其实很简单。我们只需要在文档引用上调用delete()方法:
doc_ref = firestore_client.collection("laptops").document(
"CnidNv3f6ZQD9K7MnLyy"
)
doc_ref.delete()
正如我们多次提到的,删除文档不会删除其子集合。让我们在实践中看看:
doc_ref = firestore_client.collection('laptops').document('4')
doc_ref.delete()
当我们在 Firebase 控制台中检查文档“4”时,我们可以看到数据被删除了,但是属性子集合仍然存在。文档 ID 为斜体灰色,表示文档已被删除。

作者图片
至于收藏本身,我们不能用库直接删除它。但是,我们可以在 Firebase 控制台中完成。要删除带有库的集合,我们需要首先删除所有文档。并且当所有文档都被删除时,该集合将被自动删除。
在这篇文章中,我们首先介绍了如何设置 Firebase Admin SDK 来使用 Python 中的 Firestore。使用服务帐户的私钥文件在大多数情况下适用于本地开发。当客户端库被认证和初始化后,我们可以在 Python 中执行各种 CRUD 操作。我们已经介绍了如何处理基本字段、数组字段、嵌套字段以及子集合。通过本教程,您将非常有信心用 Python 管理您的移动或 web 应用程序使用的数据。
相关文章
建立数据网格生态系统
原文:https://towardsdatascience.com/establishing-the-data-mesh-ecosystem-172d9512f43d
因优步和 AirBnB 而出名的平台生态系统正在改变行业。如何应用平台生态系统方法来转变您的企业数据网格?

照片由 Aaron Burden 在 Unsplash
平台生态系统正在改变行业
2016 年发表在《哈佛商业评论》上的一篇著名的文章解释了一种新的“平台生态系统”方法将如何改变行业。作者的关键见解是,旧式的“管道”公司——那些创造产品并出售给客户的公司——将被“平台”所取代。这些所谓的平台将使“消费者”和“生产者”的社区能够安全可靠地找到彼此,进行互动和交易。通过将消费者和生产者聚集在一起,他们预测增长将呈爆炸式增长:随着更多的消费者使用该平台,它吸引了更多的生产者,随着更多的生产者加入进来并提供新的和创新的产品,它吸引了更多的消费者,如此这般,随着网络效应虚拟圈的形成。
历史证明,他们是对的。在交通方面,优步将需要乘车的“消费者”与提供乘车服务的“生产者”匹配起来。在住宿方面,AirBnB 为想要住宿的“消费者”和想要租赁他们住处的“生产者”牵线搭桥。还有交通住宿,还有很多其他行业,从来都不是这样!
那么,数据网格专业人员为什么要关心呢?简而言之,因为平台生态系统方法改变了数据网格的实施方式。这篇文章论证了数据网格原则——域所有权、数据即产品、自助式基础设施和联合治理——强烈鼓励在企业内部采用“平台生态系统”方法。事实上,我认为将平台生态系统方法与数据网格相结合意义深远,它将改变企业创建和执行数据策略的方式。
本文假设您对数据网格有很高的理解。如果你需要一些关于数据网格的背景信息,这里有一些链接,可以链接到一些关于数据网格原则、架构和模式的文章。
平台让生产者和消费者互动和交易
什么是平台?
首先,平台是关于创建生态系统(“平台生态系统”)。根据具有里程碑意义的 HBR 文章,平台生态系统中有几个参与者:平台的所有者控制他们的知识产权,并提供标准、协议和治理;生产者创造他们的产品;消费者使用这些产品;并且,提供者创建将生产者和消费者联系在一起的基础设施。
但有趣的是,将生产者和消费者联系在一起的能力创造了网络效应。随着越来越多的消费者加入一个平台,越来越多的生产者创造新的和创新的应用程序,随着更多的应用程序,更多的消费者加入,等等。一个由网络效应产生的良性循环开始了!
或许举几个例子就能说明这一点。再来看看优步,知名的拼车公司。在优步,提供游乐设施的人是生产者。想要搭车的人是消费者。优步是该平台的所有者和提供商,它创建和管理基础设施和应用程序,使消费者和生产者能够找到彼此,并安全地互动和交易以获得乘车服务。正如陈楚翔在他的新书《冷启动问题》(The Cold Start Problem中描述他与优步的旅程一样,一旦优步在某个特定城市获得了足够数量的司机(生产商)和想要搭车的人(消费者),网络效应就开始发挥作用,优步在很短的时间内主导了拼车。
现在,让我们来看看著名的住宿租赁公司 AirBnB。在 AirBnB,想提供住宿出租的人是制作人。想要租赁住宿的人是消费者。AirBnB 是该平台的所有者和提供商,并创建和管理基础设施和应用程序,使消费者和生产者能够找到彼此,安全地互动和交易,并获得一个位置。与优步类似,陈楚翔在他的书中也描述了 AirBnB 的增长,并指出一旦他们有足够的出租单元,客户和生产者就会涌向 AirBnB,网络效应再次创造了一个行业巨头。

图 1,平台生态系统
数据产品是天然的平台
那么,这如何应用于数据网格呢?嗯,我想在几个方面。
首先,像平台一样,数据产品也有所有者。数据产品所有者负责建立允许数据生产者和数据消费者安全可靠地进行交互和交易的机制。在 AirBnB 或优步等平台提供基础设施和应用的情况下,数据产品所有者提供基础设施以及允许数据生产者和数据消费者互动的机制(使用 API 访问数据和数据产品目录来查找数据和消费)。
其次,数据产品——数据网格的核心概念——体现了消费者和生产者的概念。每个数据产品都拥有并生产数据,每个数据产品都有消费数据的群体。
第三,随着每个生产者创建新数据(或更新现有数据),数据产品的价值增加。随着更多的数据或更好的数据,消费者从数据产品中获得更多的价值。
最后,价值越大,需求越大,这推动了额外的资金(通常通过新项目),从而推动生产者创造更多新的更好的数据。和网络效应的良性循环有机会站稳脚跟。

图 2,数据产品生态系统
最后也是最重要的一点。将平台生态系统方法与传统的 It 共享服务相混淆可能是很自然的。所以,我强调:平台生态系统不是共享服务!
平台生态系统将生产者和消费者聚集在一起,因此专注于资源协调,而共享服务试图将稀缺资源聚集在一起,并充当资源控制的机制。平台生态系统专注于优化生产者和消费者群体之间的互动,而共享服务专注于优化内部流程。平台生态系统关注灵活性,而共享服务关注成本效率。
数据网格创造自然生态系统
优步或 AirBnB 等平台创建了一个社区。社区中参与者的互动创造了一个生态系统。当一个生态系统达到临界质量时,网络效应就会产生,价值就会呈指数增长。
从这个意义上说,数据网格是一个自然的生态系统。企业数据网格培育了许多社区,每个社区都由许多数据产品和它们自己的社区组成。因此,当单个数据产品开始与其他数据产品共享数据时,虽然每个数据产品社区都在创造价值,但创造的价值甚至更多。随着数据生产者和消费者的不断互动,数据网格(及其数据产品)的奇妙网络效应按照一种可预测的模式增长(如下图 3 所示):
- 数据生产者创造数据。
- 数据消费者用户数据,但随着更多或更好的数据产生,数据消费者的数量也在增长。
- 随着消费者数量和数据需求的增加,网络效应开始显现,这推动了资金的增加(例如,通过新资助的项目),这反过来又创造了新的数据生产者,等等。
- 随着网络效应的出现,企业数据的价值呈指数级增长(回到第 1 步,清洗并重复)。

图 3,企业数据网格生态系统
平台生态系统方法忠实于核心数据网格原则:
- 数据是产品:数据产品为数据产品服务提供清晰的边界,平台为平台服务提供边界。每一个都有一个由生产者和消费者组成的生态系统,他们共同互动消费并创造价值。
- 自助服务:平台生态系统方法通过激励数据产品所有者让数据生产者和消费者更容易消费数据来促进自助服务。例如,在优步和 AirBnB 的案例中,他们(为生产者和消费者)创建了应用程序,使人们可以更容易地找到彼此,进行互动和交易。数据产品目录(及其企业数据产品目录表兄弟)和 API 通过使查找数据和消费数据变得容易来培养自助服务能力。
- 分散的域所有权:数据产品边界为所有权和服务提供提供了清晰的描述和责任。类似地,平台生态系统方法对所有者和提供者有明确的界限和责任。
- 联合治理:在优步或 AirBnB 等外部平台生态系统中,治理是完全联合的,因为每个企业都必须担保自己的财务状况,并遵守政府制定的法律法规。与平台生态系统方法一样,数据产品决策由数据产品所有者做出,他们有权在遵守企业规则和流程(例如,技术标准等)的同时,做出符合其数据生产者和消费者最大利益的决策。
数据网格和平台为企业数据战略创造了新规则
平台生态系统方法的引入提供了一种建立企业数据网格的新方法。但是这种方法具有基本的技术、治理和组织含义,需要重新思考企业数据战略的规则:
- 共享服务 IT 成为联合平台:如今,IT 共享服务制定技术决策并负责数据管理。在一个平台生态系统中,所有者(在我们的例子中是数据产品所有者)负责选择和实现他们自己的平台功能,同时在有意义的情况下根据他们的判断采用企业标准。
- 敏捷性取代成本优化:如今,IT(尤其是共享服务模式)针对成本进行优化。相比之下,平台生态系统方法重视敏捷性,这是通过让数据产品所有者(最了解其数据价值的人)能够根据其数据生产者和消费者的需求确定资金和活动的优先顺序来实现的。
- 集中化 vs 康威定律:康威定律表明(解释)系统和数据遵循组织结构。今天,IT 通常是在集中式服务中组织的(与康威定律相反)。相反,平台和生态系统方法允许数据产品由那些最接近和最了解数据的人拥有,这些人通常是业务部门。
- 用户价值对生态系统价值:今天,用户试图最大化他们的数据仓库的价值,不幸的是,超出这个范围是一项痛苦而昂贵的工作(参见前面的观点)。另一方面,平台生态系统方法寻求在生产者和消费者消费和交换数据时最大化他们的生态系统的价值。这种方法将生产者和消费者确立为一等公民。生产者和消费者与单个数据产品的互动,然后扩展到企业数据网格中的许多数据产品,创造了一个蓬勃发展的生态系统,如果运作良好,随着网络效应的形成,这将创造指数增长和价值实现的良性循环。
总结想法
显然,数据网格为我们如何管理企业数据并从中实现价值提供了新的机会。但是,在 tooday 的传统集中式 IT 共享服务中,组织、技术和治理方面的影响给实现数据网格带来了巨大的挑战。
这些挑战可以通过平台生态系统方法来解决,该方法实施数据网格“所有权”原则,为数据产品所有者提供明确的决策责任,使用“平台”从头开始实施联合治理,允许所有者以最佳服务于其数据生产者和消费者的方式建立自助服务能力,随着网络效应在企业内部扎根,通过数据生产和消费的良性循环,以指数方式放大数据的价值(并创建新的融资机制)。
因此,当您着手实施企业数据战略时,请记住平台生态系统方法提供了一种实用、可行且自然的方式来解决数据网格的基本组织、技术和治理挑战。
除非另有说明,本文中的所有图片均由 Eric Broda(本文作者)创作。图像中使用的所有图标都是普通的 PowerPoint 图标,不受版权保护。
使用随机数估计圆周率
原文:https://towardsdatascience.com/estimate-pi-using-random-numbers-8b13a7e8c791
通过和 Julia 一起计算π来回答这个面试问题
好吧,所以我真的应该在圆周率日(3 月 14 日)发表这篇文章,但我认为这不会发生,所以无论如何在这里。让我们在 Julia 中使用随机数来估计圆周率——当然😉。

雨果·艾特肯在 Unsplash 上拍摄的照片
这篇文章的灵感来自霍马的视频。有兴趣的话去看看他的频道。他是个怪人,但他是个好人。😃
视频由霍马理工
要获得所有媒体文章的完整信息——包括我的——可以考虑在这里订阅。
这个想法
这个任务有点奇怪,但是一旦你深入研究就会明白:你会得到一个随机数发生器,你的任务是估计圆周率。

照片由 Unsplash 上的 Anastase Maragos 拍摄
想象一下向飞镖靶投掷飞镖。如果你知道你每次都会击中棋盘,但飞镖的位置是均匀分布的,那么飞镖落在棋盘上半部的可能性有多大?直觉上是 50%。为什么?因为上半部分的面积是下半部分的面积。
我们将利用这一事实,开始在(-1,-1)和(1,1)的正方形内一次绘制 2 个随机数(也称为 2D 点或向量),如下所示:

作者使用 Plots.jl 制作的图
方便的是,这个正方形适合半径= 1 的单位圆。所以如果向量的长度大于 1,那么我们的点在圆外,如果小于 1,那么它一定在单位圆内。

单位圆正好适合,有 2 个例子点——作者做的
然后,我们多次随机抽取点,并计算我们从圆内和圆外抽取点的次数。该比率应该接近圆和正方形的面积比:

圆形和正方形代表形状的面积。
也就是说,我们将圆内的点数除以正方形内的点数,再乘以 4,得到圆周率。
让我们用 Julia 来编码吧!🤓
随机抽取一个点

克里斯·贾维斯在 Unsplash 上拍摄的照片
在我们开始画点之前,让我们导入几个库来简化我们的生活。
这些库是:
- Plots.jl 用于稍后惊人且超级简单的绘图。
- 用于设置随机数种子的内置随机模块。
- Distributions.jl 便于获取均匀随机分布。
- 内置的 Printf,这样我们以后可以更容易地将浮点数格式化为字符串。
现在我们已经有了所有的工具,让我们创建一个函数:
如果你对朱莉娅不熟悉,我会尽最大努力打开所有这些东西。
rand(distribution, 2)从第 3 行定义的均匀分布中绘制一个长度为2 的向量,即在-1 和 1 之间。v .^ 2使用广播运算符来计算 v 的所有元素的平方,所以你得到(x,y)。sum(v .^ 2) <= 1根据矢量的长度,逐个元素检查该点是否在单位圆内。x + y ≤ 1 则(x,y)在圆内。- 最后,我们只需计算圆内点与我们采集的样本数量的比率,乘以 4,我们就有了我们的估计值。🎉
让我们尝试一下——本着可再现性的精神,我将在调用该函数之前设置随机数种子,因此您应该会得到相同的确切结果。
julia> Random.seed!(42)
MersenneTwister(42)julia> estimate = pi_estimator(10_000_000)
3.1422016julia> abs(π - estimate)
0.0006089464102068121
这看起来很接近我。😃
Julia 有一个内置的 pi 常量,您可以通过键入
\pi并按 tab 键来访问它。这将自动完成 Unicode pi 字符:π。
搞点剧情!

这是我在绘图时的感受——照片由森居提·昆杜在 Unsplash 上拍摄
现在我们有了一个估计器,它给了我们一个相当不错的结果,如果我们能够可视化我们如何得到这个估计值,那将会非常酷。
让我们首先修改我们的函数,这样我们不仅返回最终估计,而且返回所有绘制的向量v和中间估计。我们可以将它们收集到两个数组中,分别命名为samples和estimates。
无论是在函数内部还是外部,都要使用合理的变量名称。你未来的自己会感谢你的!
调用此函数后,您可以将最后返回的元组解包到估计值和样本的两个数组中:
有点忘乎所以,根据我们的估计称之为plot:

当我们采样更多的点时,我们的估计。—由作者创建。
我已经整理好了,并制作了另一个函数来绘制绘制的点。不要忘记所有的代码都存在于 GitHub 上。
如果你仔细看了上面的内容,你会注意到我给绘图函数添加了一个i参数。这是为了让我可以在i迭代后绘制图形。为什么?嗯,因为我喜欢 gifs
不,不是这种 Gif——来自 GIPHY.com 的 Gif
这些类型的 gif:

这整篇文章在 1 gif——作者制作的 Gif
是的,我将向您展示如何使用Plots.jl包中的一个简单宏来实现这一点。
这是结局,但我这里有更多的朱莉娅内容:
摘要🎉
希望这篇文章能和霍马的视频一样有趣——可能不会😆。
至少,现在您知道如何:
- 用面积和随机数估计圆周率。
- 从朱莉娅的均匀分布中得出结论。
- 绘制一些图表。
- 将这些图链接成一个 gif。
拜托了。——GIPHY.com 上的 Gif
估计多因素模型的权重
原文:https://towardsdatascience.com/estimate-weights-for-multifactor-model-e061174ca79
利用神经网络的思想来估计具有多个因素的系统的权重

网从 Unsplash
假设您有一个包含许多特征的客户数据集,并且您正试图构建一个加权系统来预测某些客户行为(比如购买或不购买)。如何确定那些个体的权重值?

当特征尺寸非常小时,我们可以用肉眼读取、分析和确定权重。对于任何超过 3 个权重的系统,我们将需要一个系统化的解决方案来确定权重值,自动。
在本文中,我将介绍一个解决方案(用 Python 代码)来实现它。一种思想借用了神经网络。
一个重量系统
为了尽可能简单地演示这个想法,让我从一个重量系统开始。

是的,这是一个简单的线性系统。如果w = 2, b = 3。该函数的工作方式如下:
f(1) = 2*1 + 3 = 5
f(2) = 2*2 + 3 = 7
f(3) = 2*3 + 3 = 9
f(4) = 2*4 + 4 = 12
...
现在,假设我们对 w 和 b 一无所知。但只知道 f(x)及其结果:
f(1) = 5
f(2) = 7
f(3) = 9
f(4) = 12
并利用上面的成对数字估算出 w 和 b 。
步骤 1 ,初始化参数和测试数据。
import numpy as np
# model = y = 2*x + 3
X = np.array([1,2,3,4],dtype=np.float32)
Y = [(2*x + 3) for x in X]
# w and b to be predicted
**w,b = 0.0,0.0**
第二步,类似于神经网络模型,定义正向、损失和梯度函数。
*def* forward(*x*):
return w**x*+b*def* loss(*y*,*y_pred*):
return ((*y_pred*-*y*)**2).mean()*def* gradient(*x*,*y*,*y_pred*):
return np.dot(*x*,(*y_pred*-*y*))print(*f*'print the prediction before training: f(5)={forward(5)}')
步骤 3 ,用 4 对数据进行训练,并用 f(5)检验结果
learning_rate,n_inters = (0.01,10000)
for epoch in range(n_inters):
y_pred = forward(X)
l = loss(Y,y_pred)
dw = gradient(X,Y,y_pred)
w = w - learning_rate*dw
b = b + learning_rate*l
print(*f*'epoch {epoch+1}: w= {w*:.3f*},b= {b*:.3f*} loss = {l*:.8f*}, dw = {dw*:.3f*}')
print(*f*'print the prediction after training: f(5)={forward(5)}')
这里是完整的代码,我强烈建议您自己运行代码并查看结果:
经过一万轮的训练。我们“几乎”拥有正确的 w 和 b 。

w = 2.019b= 2.942
我们得到w = 2.019; b= 2.942 。非常接近w = 2和b = 3。看起来不错,嗯?毕竟只有 8 个数字用来预测权重和偏倚。
三重制
现在,让我们将模型扩展到估计三重系统中的重量。

假设, w1 = 2 , w2 = 3 , w3 = 4 , bias = 5 。填入上面的函数。我们将拥有:
f(1,2,3) = 25
f(2,3,4) = 24
f(3,4,5) = 43
...
闭上眼睛,忘记重量和偏见。假设我只知道输入和输出,用 Python 代码实现权重估计逻辑。
第 1 步,初始化数据。因为 bias 实际上是一个变量乘以 1。所以,我们可以认为 bias 为 w4 ,但总会乘以 1。
import numpy as npX = np.array(
[[1,2,3,1]
,[2,3,4,1]
,[3,4,5,1]
,[4,3,2,1]
,[12,13,14,1]
,[7,8,9,1]
,[5,2,10,1]]
,*dtype*=np.float32
)
# w1= 2, w2= 3, w3= 4, b= 5
W_r = np.array([2,3,4,5],*dtype*=np.float32)# r->real
W = np.array([0,0,0,0],*dtype*=np.float32) # for estimationY = np.array([x@W_r for x in X],*dtype*=np.float32)
在代码中,X 是一个 4 列 2d 数组。最后一列是偏差。
第二步,定义模型。几乎与单权重系统模型相同,但不需要单独处理偏差。
*def* forward(*X*):
return np.dot(*X*,W)*def* loss(*Y*,*Y_pred*):
return ((*Y_pred*-*Y*)**2)*def* gradient(*X*,*Y*,*Y_pred*):
return *X**(*Y_pred*-*Y*)
第三步,训练,看预估权重和偏差。
learning_rate,num_epoch = 0.001,3000for epoch in range(num_epoch):
for i,x in enumerate(X):
y_pred = forward(x)
l = loss(y_pred,Y[i])
dw = gradient(x,Y[i],y_pred)
W = (W - dw*learning_rate)
print(*f*'epoch:{epoch} | loss:{l*:.8f*}')
print(W)
以下是完整的代码:
经过 3000 轮训练后,模型返回如下结果:

非常接近[2,3,4,5]!
将模型应用于 30 个权重的数据集
是时候研究一些真实的数据了。这次让我根据输入和输出数据来预测 30 个权重。

我将要使用的数据集是来自 scikit-learn 的著名的乳腺癌数据。如果你决定试一试代码,不需要手动下载数据。如果您安装了 scikit-learn 软件包,数据将自动下载。
导入包和加载数据:
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import numpy as np# prepare data
bc = datasets.load_breast_cancer()
X,y = bc.data,bc.target
X_train, X_test, y_train, y_test = train_test_split(X, y, *test_size*=0.5, *random_state*=1234)
将数据集转换为标准格式:
# transform to the standard dataset
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)
y_train = np.where(y_train==0,-1,y_train) # replace 0 with -1
y_test = np.where(y_test==0,-1,y_test) # replace 0 with -1
fit() 得到平均值和标准差。transform()将原始数据转换为正态分布风格的数据。mean = 0,stdev =1。为什么?因为原始数据可能会有极大的偏差,有些介于-0.1 到 0.1 之间,而另一些特征则从 0 到 10。fit_transform()在一个函数调用中执行拟合和转换。如果你不熟悉 fit 和 transform 操作,我发现这个 stackexchange 线程最好地解释了转换操作。
我还将 y 数组中的 0 转换为-1,以便于后面的数值处理。
接下来,定义模型,训练并验证结果,下面是完整的代码:
结果是:

该模型适用于训练数据集,在测试数据集上的准确率为 97.7% 和 97.2% 。结果即使不是最好的,也应该足够好了。(参见附录部分中 scikit-learn 的其他模型的结果。).
顺便说一下,10,000 轮训练大约需要 10-20 秒。
核心思想
很多年前,我的第一个 C 语言程序是一个价格猜谜游戏。我的程序会随机生成一个价格#。在终端,我输入我的价格猜测。如果我的输入高于真实价格,程序会提醒我输入过高,反之亦然。
经过多轮尝试,我可以大致估算出价格范围。最后,我输入正确的数字,程序会告诉我“成功”和试验次数。
在这种情况下,我是价格学习者:我根据程序的反馈上下移动我的猜测数字。
在上面的三个权重估计的例子中,我在价格猜测游戏中的想法是一样的。
在一个权重和一个偏置系统中,当输出高于真实结果时,模型将稍微增加/减少权重。因为权重w是倍数x,所以我根据输出微分和输入 x 按比例移动了#。
dw = np.dot(***x***,(*y_pred*-*y*))
w = w - learning_rate*dw
不仅仅是另外两个模型,现在连神经网络深度学习模型都在用同样的思路,没有任何魔力。简单明了。
包裹
ML 是而不是学习一个框架并按照官方教程调用函数。那有点无聊。真正神奇的是下划线的思想和逻辑。
一套逻辑如何根据已知信息准确预测未知?
我们生活在一个可以接触到强大的 CPU 和 GPU 的时代,真是太幸运了。如果我们生活在 100 年前,没有人能做这种预测,因为人类几乎不可能在训练数据上迭代这么多次。但是我们可以让 CPU 或者 GPU 来做这个重复性的工作。而且真的管用。
ML 模型有时不是纯粹的数学,而是数学和蛮力的结合。
当我在思考神经网络模型时,我想到了这个权重系统预测。如果 NN 对图像如此有效,而神经网络深度学习模型的核心是找到一种微调(上下微调)权重和偏差的方法,那么无论定义了多少个隐藏层,核心思想都是一样的。
为什么不应用同样的想法来建立一个适用于其他数据集的模型呢?这很有效。
如果你有任何问题,请留下评论,我会尽力回答,如果你发现了错误,不要犹豫,把它们标记出来。感谢阅读。
参考链接
- https://data science . stack exchange . com/questions/12321/what-the-difference-of-fit-and-fit-transform-in-scikit-learn-models/12346 # 12346
- https://sci kit-learn . org/stable/modules/generated/sk learn . datasets . load _ breast _ cancer . html
- https://github.com/xhinker/nn_sample_code
附录—与 Scikit-Learn 中的其他模型进行比较
在相同的训练和测试数据集上应用来自 scikit-learn 的其他模型,并测量准确度分数。
决策树:90.5%
代码:
from sklearn.tree import DecisionTreeClassifier as dtc
tree = dtc(*random_state*=1)
tree.fit(X_train,y_train)
print(*f*"decision tree Accuracy on test set:{tree.score(X_test,y_test)}")
结果:
decision tree Accuracy on test set:0.9052631578947369
最近邻法:94.7%
代码:
from sklearn.neighbors import KNeighborsClassifier as knt
k_model = knt(*n_neighbors*=3)
k_model.fit(X_train,y_train)
print(*f*"K-nearest Accuracy on test set:{k_model.score(X_test,y_test)}")
结果:
K-nearest Accuracy on test set:0.9473684210526315
逻辑回归分析:96.1%
代码:
from sklearn.linear_model import LogisticRegression as LR
lr_model = LR().fit(X_train,y_train)
print(*f*'Logistic Regression Accuracy on test set:{lr_model.score(X_test,y_test)}')
结果:
Logistic Regression Accuracy on test set:0.9614035087719298
贝叶斯估计:91.2%
代码:
from sklearn.naive_bayes import GaussianNB as bayes
b_model = bayes().fit(X_train,y_train)
print(*f*'Bayes Estimation on test set:{b_model.score(X_test,y_test)}')
结果:
Bayes Estimation on test set:0.9122807017543859
请随意运行它,如果有更好的结果,请告诉我。
使用多边形估计π
原文:https://towardsdatascience.com/estimating-π-using-polygons-d1a53a53f408
利用朱莉娅玩各种形状和馅饼
π日(3 月 14 日)发什么帖子想了很多。我已经写了一篇关于用蒙特卡罗方法估计π的文章这里,所以我不能再做了。然后我想起在高中的时候,我们被展示了一种不同的估算π的方法:通过使用 n 边正多边形。所以我拿出我的冥王星笔记本,抓起 Plots.jl 为这篇文章编造一些东西。

向这些年来我最棒的数学老师们欢呼吧!—伊莫·威格曼在 Unsplash 拍摄的照片
这整篇文章作为冥王星笔记本放在 GitHub 上。
这几乎是一个圆
为了解释这个想法,想象画一个单位圆。在这个圆内,画一个等边三角形。现在试着画一个等边的正方形或五边形。你画的边越多,形状就越接近圆形。

看到这几乎是一个圆了吗?—作者截图
圆的周长为 2π(因为它是单位圆)——这是π的可能定义之一。如果我们想象画一个边越来越多的正多边形,我们会期望多边形的周长越来越接近 2π。现在,让我们算出边的长度,然后乘以边的数量。是时候让布鲁托出来了!
如果你需要对 Pluto.jl 的介绍,我写了一篇关于为什么我认为它很棒的文章。
编写一些函数
我承认这是我们开始作弊的地方…😬为了计算多边形的边长,我们需要使用正弦定律,这意味着我们使用 sin 函数,但是为了计算 sin 的值,我想我们需要知道π。如果我在这里说了什么愚蠢的话,请在评论里枪毙我。不管怎样,我们仍然可以玩得很开心,所以让我们写一些函数:
请注意,我们可以在这里使用度数而不是弧度
这里的技巧是把每个多边形想象成 n 个等腰三角形在圆心处连接在一起。两条相等的边具有单位长度,因为它们从圆心指向圆上的顶点。我们感兴趣的是第三方。以弧度表示的内角是 2π除以边数(记住一个完整的圆是 2π)。而且因为三角形的角之和是π,所以我们知道如何计算另外 2 个角(上面的β)。
但是我们如何知道这是否有效呢?嗯,我能想到的一个方法是检查它是否是正方形。根据毕达哥拉斯定理,适合单位圆的正方形的边长是 2 的平方根。所以正方形的周长必须是这个的 4 倍:
# this should be 4 times √2, using Pythagoras
circumference_polygon(4) == 2^0.5 * 4
这给了我们true,从而让我们确信我们不是完全的白痴。单元测试万岁!快速提示:在 Julia 中测试这个和using Test一样简单,然后:

在 Julia 进行测试——作者截图
要估算π,我们只需将这个数除以 2——注意,圆的周长是 2π,而不是π:

对于一个 100k 边的多边形,我们非常接近圆周率——作者截图

吉勒·Á·阿尔瓦雷斯在 Unsplash 上拍摄的照片
如果我想偷懒,发表一篇 2 分钟的文章,这将是它。但是 Plots.jl 和 Pluto.jl 只是乞求被使用。这里还有更多好玩的东西。
多快才算快?
我们可以问的一个问题是:“我们的估计多快接近π?”。要回答这个问题,我们可以使用 Plots.jl 来绘制一些漂亮的线条:
如果你以前从未见过朱莉娅,上面有几件事正在发生:
:给你一个数字范围,使得x有点像向量。.将我们的标量函数传播给x的所有值,给出π的 98 个估计值。plot非常棒,知道如何处理我们的x和y坐标对。hline!在π处添加一条水平线,因为该函数以!结束,所以它修改当前图形。- 我写π和π̂是通过键入
\pi和\pi+\hat来自动完成 Unicode 字符,让它看起来好像我懂数学。
专业提示:如果你想让别人相信你懂数学,在你的代码中使用 Unicode 字符和希腊符号。
我们光荣的阴谋在这里:

非常接近——作者策划的情节
多近才算近?
Gif 来自 GIPHY
我们可以问的另一个问题是:“这些多边形实际上看起来像什么?”。他们真的越来越接近一个圈子了吗?
嗯,我很高兴你问了,因为我们有与冥王星互动的滑块。所以我们来玩玩吧。
首先,我们将绘制我们的多边形。为此,我们可以使用绘图的Shape构造函数,并用我们选择的颜色填充它:

这给了我们一个无聊的正方形——作者的情节
我们如何知道顶点应该去哪里?我的方法是将第一个点任意设置为(0,1)。那么所有其他的点都将是这个向量旋转 2π/n 的旋转,其中 n 是我们的多边形的边数。让我们再用数学来旋转这些点:
让我们通过顺时针旋转我们的初始点 90 度来检查我们的函数是否工作(我们期望它落在(1.0,0.0)):

万岁!—作者截图
现在,让我们得到一个 n 边正多边形的所有点:
让我们通过在 Pluto 中添加一个滑块来实现这一点:
最后用我们的估计画出结果:

作者制作的 Gif
感谢阅读!
嗯,这很有趣。我希望你在阅读这篇文章的时候过得愉快,并且学到了一些关于几何(不太可能)或者朱莉娅(可能性稍微大一点)的新东西。如果你喜欢读这篇文章,考虑在 Medium 上关注我,我在这里发布关于数据科学、Julia 和有趣的东西。
https://blog.devgenius.io/make-a-command-line-game-with-julia-a408057adcfe
估计流数据中特定于事件的计数
原文:https://towardsdatascience.com/estimating-event-specific-counts-in-streaming-data-c237f51ead4f
简单而引人注目的 CountMin 草图

想象一串输入符号 a , b , a , c , a ,…。我们想知道某个符号到达了多少次。
这个问题有很多用途。计算向谷歌进行某项查询、在 YouTube 上观看某个视频或从 iTunes 购买某首歌曲的次数。和许多其他人。
我们可以使用散列表来解决这个问题,散列表的键是目前为止在流中看到的不同符号,值是它们的频率。然后可以从这个散列表中查找任何符号的计数。
当有数十亿个不同的符号时,这种方法会消耗大量内存。
如果我们愿意牺牲一些计数的准确性,我们可以使用一个非常紧凑的数据结构来解决这个问题。此外,数据结构将以流式方式更新。
好吧,我们开始吧。首先,目录详细地揭示了我们将涉及的内容。
**Single Hash Function** Randomizing the Hash Function
Example 3
**Multiple Hash Functions** Example
Estimated Symbol’s Count
Example
Very High-Dimensional Universes
Sparse Representations
Why Use Multiple Hash Functions?
Choosing The Hash Functions
Running Time
Mergability
Design Choices And Tradeoffs: A Thought Experiment
**Very High-Dimensional Use Cases**
Text Documents As Symbols
Configuring The CountMinSketch On Text Documents
Count Estimation Example
The Expected Effects Of Our Configuration Choices
**Summary
Further Reading**
单哈希函数
让我们从散列表方法开始,“进化我们的方式”到 CountMinSketch。为了便于说明,我们将使用一个运行场景,其中的符号是 32 位二进制向量。
hashmap 方法使用计数器的映射 C 。最初,地图是空的。就好像宇宙中所有的符号都是 0 计数。当一个新的符号 v 到达流中时,我们将 C [ v ]加 1。我们查任意符号 v 在任意时刻的频率为 C [ v ]。
这种方法的内存需求是流中不同符号的数量,在最坏的情况下是 2。如果流已经有许多不同的到达,这可能是一个问题。比如几十亿。即使到目前为止这个流只接收到少量的不同符号,我们也不能保证 C 的大小最终不会爆炸。
现在让我们开始概括,试图防止 C 爆炸。
让 h ( v )表示我们应用于符号 v 的特定散列函数。在上一段的描述中,我们把每一次出现的 C [ v ]都换成C[h(v)】。hashmap 方法对应于使用哈希函数 h ( v )等于 v 。
这就是事情开始变得有趣的地方。现在让我们使用一个散列函数,它的范围比宇宙的大小小得多。例如, h ( v )返回 v 的前 16 位。 h ( v )的范围在 65K 左右。所以 C 永远不需要超过 65K 的计数器,远远少于 2。
我们必须付出代价。我们是。我们现在可以有碰撞。假设两个符号 u 和 v 具有相同的 hash 值,即 h ( u )等于 h ( v )。 u 和 v 的计数混合到同一个计数器C[h(u)】。也就是说,虽然我们无法辨别 u 或 v 的真实计数,但我们可以肯定地说C[h(u)]是两者的上限。例如,如果[ h ( u )]为 0,我们可以确定地知道,无论是 u 还是 v 都没有到达流中。
总结
To better bound the number of counters, we use a hash function whose range is **constricted**. While this potentially sacrifices accuracy, our estimated counts remain **upper bounds**.
随机化散列函数
哈希函数“提取前 16 位”偏向于前 16 位。一个简单的随机化方法使其无偏。
首先在初始化阶段,产生一个随机的 32 位二进制数,其中正好有 16 位为 1 值。(随机性在于这些 1 值的位置。)记录这个数字,并在计算任何符号的哈希值时将其用作掩码。
让我们在下面的例子中说明这一点。
例子
在这里,我们将宇宙限制为 8 位数字。请注意,哈希值是一个 4 位数。
mask 0 **1** **1** 0 0 **1** 0 **1**
value 1 **1** **1** 0 0 **0** 0 **1**
hash **1** **1** **0** **1**
非常高维度的宇宙
在我们运行的例子中,二进制向量是 32 位长。有些情况下,位向量要长得多,有数百万或数十亿位。我们将在后面的章节中讨论它们。CountMinSketch 在那里特别有吸引力。
稀疏表示
至此,我们已经隐含地假设 C 是由所选散列函数的范围索引的数组。在许多用例中,我们想要使用的散列函数可能有很大的范围。也就是说, m 非常大,可能几十亿。
即使在低维宇宙中,选择的哈希函数也可能有非常大的范围。我们之前见过一个例子, h ( v ) = v 其中 m 在 32 位二进制向量的论域上是 2。显然,对于一个更高维度的宇宙,这变得更加可能。
在这种情况下, C 的一种自然表示就是散列表。 C 的键是目前为止在流中观察到的 h ( v )的值。C[h(v)]照例是 h ( v ) 的数。
当在流中观察到的 h ( v )的不同值的数量远小于 m 时,这种表示非常紧凑。实际情况往往如此。
多个哈希函数
我们将使用多个散列函数 h 1、…、 hn ,都在相同的范围 1、2、…、 m 上。我们将把我们的计数器映射归纳为一个计数器矩阵。行索引散列函数。这些列索引散列值,都在范围 1、2、…、 m 内。 C [ i , hi ( v )]将存储对符号 v 应用 i th 哈希函数的计数。
当符号 v 到达流中时,从 1 到 n ,每增加一个IC[I][hi(v)】就增加 1。如下图所示。
示例
为了方便起见,我们将使用一个比我们运行的宇宙基数更高的宇宙。
我们的符号将是以 4 为基数的 3 位数。即v=v1v2v3 其中每个 vi 为 0、1、2 或 3。我们将使用三个哈希函数 h 1( v )、 h 2( v )和 h 3( v )。一位数一个。 hi ( v )将等于 vi,vv的第 i 位的值。例如, h 2(1 3 2)等于 3,如粗体所示。
C 是一个 3×4 矩阵,因为我们有三个散列函数,每个散列函数都有四个相同的值。
假设符号 011 和 232 分别到达流中四次和七次。最后,不管它们到达的顺序如何, C 是
**0 1 2 3**
**1** 4 0 7 0
**2** 0 4 0 7
**3** 0 4 7 0
C 的行和列,用粗体显示,分别索引数字位置和数字值。
我们来解释一下C【2】【1】。它至少是 4,因为 0 1 1 在流中出现了四次,并且它的第二个数字是 1。It 不大于 4,因为没有冲突,因为在流中出现的唯一的另一个符号 232 在第二位置具有不同的值。
估计符号数
回想一下,对于任何符号 v ,我们想要估计它到目前为止到达流中的次数。
当使用单个散列函数时,符号的估计计数只是简单的C[h(v)】。当我们使用多个哈希函数时, v 的出现影响 n 计数器 C [ i ,hi(v)】forI范围从 1 到 n 。我们如何从 n 次计数得到一个估计值呢?
回想一下,在上一节中我们注意到,对于任何固定的哈希函数 h ,C[h(v)]是 v 的真实计数的上限。在 n 哈希函数的设置中,这意味着对于范围从 1 到 n 的 i ,每个C**I, hi ( v )】都是 v 的真实计数的一个上界。因此,这些上限中最小的一个作为这个真实计数的最佳估计。也就是说,我们有
CountUB(v): The minimum of C[i][hi(v)] for i from 1 to n
例子
现在让我们来计算宇宙中几个数字的估计数。一个将被证明是精确的计数,另一个是明显的高估。
考虑 232。CountUB(232)是下面粗体显示的三个单元格C【1】【2】、C【2】【3】和C【3】【2】中的最小计数。
**0 1 2 3**
**1** 4 0 **7** 0
**2** 0 4 0 **7**
**3** 0 4 **7** 0
首先,注意这些单元中的每一个都是无冲突的。它只记录流中 232 的出现次数。011 是该流接收到的唯一的另一个符号,它不影响任何这些单元。这是因为两个符号{2,3}和{0,1}中的值集是不相交的。因此,这三个单元格中的最小值 7 正好是 232 的真实计数。
接下来考虑 212。CountUB(212)是下面以粗体显示的三个单元格中的最小计数。
**0 1 2 3**
**1** 4 0 **7** 0
**2** 0 **4** 0 **7**
**3** 0 4 **7** 0
CountUB(212)是 4,而 212 的真实计数是零。这是因为所有三个单元都有相对于 212 的冲突。这是因为2T2 1T4 2 在第一和第三位置与 2 3 2 碰撞,在第二位置与 0 1 1 碰撞。
为什么要使用多个哈希函数?
与其使用多个散列函数,为什么不适当地扩大单个散列函数的范围呢?考虑我们运行的 universe 示例中的散列函数“提取前 16 位”。它使用大约 65K 的计数器。假设我们添加了第二个哈希函数“提取接下来的 16 位”。现在这两个加起来用了~130K 计数器。我们可以使用单个散列函数“提取第一个 17 位”并获得相同数量的计数器。
使用多个散列函数的直觉围绕着在估计符号计数中使用最小运算。例如,为了精确估计符号的计数,最小操作中涉及的仅仅一个单元是无冲突的就足够了。
使用多个散列函数的好处,尤其是使用最小操作的好处,将在后面关于非常高维用例的章节中更清楚地展现出来。
选择哈希函数
哈希函数应该是独立的。这个想法是为了减少碰撞。也就是多元化。
作为相反类型的极端例子,考虑当所有散列函数都相同时。使用多个并没有给我们带来任何好处。在额外的内存和计算需求中,我们确实承担了使用多个的所有成本。
这是一个具体的例子,在我们的 32 位独立数的宇宙中,有 4 个合理的散列函数可供选择。 h 1、 h 2、 h 3、 h 4 将分别提取第一、第二、第三和第四个 8 位的块。
运行时间
假设散列函数可以在恒定时间内被评估,在计数器矩阵 C 中寻找和增加适当的计数器的运行时间与散列函数的数量 n 成比例。计算任何符号的估计计数也在与 n 成比例的时间内运行,因为它涉及查找 n 个单元并计算它们的最小值。
可制造性
这是一个概念,将两个流分别创建的草图合并,会产生与从两个流合并的单个流中获得的草图相同的草图。
可合并性在分布式设置中是非常有用的操作。它允许以分布式的方式从不同的流中构建草图。然后根据需要进行合并,以估计全局计数,即,好像来自所有流的合并。
只要所有草图使用相同的散列函数,CountMinSketch 就是可合并的。
这可以表示如下:
C(S1) + C(S2) = C(S1 + S2)
这里 C ( S )表示由流 S 构建的草图,LHS 中的“+”表示草图合并操作,RHS 中的“+”表示流合并操作。
合并两个 CountMinSketches 很简单:只需按组件将它们的矩阵相加。这在下面描述。
C[i,j] = C1[i,j] + C2[i,j] for all i, j
C 是 C 1 和 C 2 的合并。
设计选择和权衡:一个思维实验
散列函数的数量及其范围的大小如何影响各种权衡?
下面的思维实验将帮助我们思考这些问题。
我们的宇宙将是 32 位数字。我们将考察 n = 1,2,4,8,…,32 的各种配置。
配置 n 会使用 n 哈希函数 h 1( n ),…, h 2( n )。 hi ( n )将从一个 32 位数字中提取 32/ n 位的第 i 块。
配置 n =1 使用单一哈希函数h(v)=v。在另一个极端,配置 n =32 使用 32 个哈希函数hi(v)=VI,其中 i 范围从 1 到 n 。
n =1 配置提供完全精确的计数。然而,在最坏的情况下,它可能需要 2 个计数器。在另一个极端, n =32,我们只需要 32*2=64 个计数器。显然,估计的符号计数可能相当不准确。
很明显,随着 n 的增加,内存限制会成倍增加。这是因为 C 中的行数随着 n 线性增加,而列数呈指数减少。
精确度如何下降尚不清楚。
这两个极端之间的某个地方可能是用户认为可以接受的特定折衷的最佳点。内存需求与准确性的对比。到达数据流的数据的性质也明显影响这种权衡。
非常高维的用例
文本文件作为符号
假设我们的“符号”是到达流中的文本文档。例如,到达 Twitter 流的推文。比方说,对于每个文档,我们想要估计它在流中被观察到的次数。
按照惯例,让我们在向量空间模型中用一个合适的向量来表示一个文档。向量的维数是词典中术语的数量,通常以百万计。第 i 个成分表示词典中的第 i 个术语。
我们可以将文档向量的第 i 个分量的值设置为文档中第 I 个词典项的频率。或者该频率的二进制版本。或者更高级的变体,其利用术语的 IDF 来调整该频率。
好了,现在我们已经将文档编码为相同固定维度的向量。我们现在试图估计在水流中观察到的不同矢量的数量。
请注意,不同的向量计数不一定是不同的文档计数。这是因为多个文档可能具有相同的矢量编码。
这可能是一件好事。通过适当地选择文档的矢量编码,我们可以忽略我们可能不关心的细微差别。
在文本文档上配置 count minsketch
在这种设置中,什么是合理的哈希函数?这里有一个。所谓的比特采样哈希函数,hi(v) = vi,我们在本帖前面遇到过。它只是提取 v 的第 I 个分量。在我们的设置中,这对应于词典中的特定术语。
值 vi 取决于我们的矢量编码。我们可以将它作为术语在文档中的出现指示符(布尔型),或者它的频率。或者更复杂的东西。我们的选择隐含地定义了我们正在计数的内容。例如,当 v 为布尔向量编码时,哈希函数h_data(v)根据 v 编码的文档中是否出现 data 一词,返回 true 或 false 。
在本节的其余部分,我们将仅限于布尔型的位采样散列函数。
下一个问题:我们应该使用多个哈希函数吗?如果我们能够负担得起使用词典中有多少术语就有多少哈希函数(可能有数百万),是的,那就好了。如果没有,我们可能首先选择一个我们能负担得起的词典的随机子集,并使用与之对应的散列函数。
计数估计示例
对于特定的 v ,CountUB( v )会是什么?让我们用一个玩具的例子来说明。这个例子将引发一场有趣的讨论。
在这个例子中,为了便于阅读,我们将“估计文档的频率”简称为“估计文档的矢量编码的频率”。
假设我们的词典正好由五个词组成:数据、数据、数据、挖掘数据计算机。比方说我们要估计文档{ 数据, of }在流中出现的频率。
首先,让我们确定在这个估计中我们需要的特定计数器。分别是C**数据【T17,1】,C【T21,1】,*C【T25,0】,C**采矿【T29,0】,C计算机【T33,0】。前两个分别计算流中包含单词 data 和 mining 的文档的数量。接下来的三个分别计算流中不包含单词、、的和计算机的文档的数量。接下来,我们取其中的最小值,并将其作为估计值返回。***
现在进行非正式讨论。这将有助于想象词典是维基百科中所有不同的文章。并且该流的文档是从该语料库中提取的。
我们期望 C [ 数据【T47,1】比C[【T51,1】的小得多,因为的比数据更常见。我们预计 C [ the 【T61,0】比 C[ mining 【T63,0】或 C [ computer 【T67,0】要小得多,因为大多数维基百科文章中都有单词 the 。对于采矿或电脑*来说并非如此。*****
因此,我们预计我们的文档在流中的估计频率将是 C [ 数据【T77,1】和 C [ the ,0]中的最小值。换句话说,是“包含单词数据的维基百科文章数量”和“不包含单词数据的维基百科文章数量”中较小的一个。
我们的配置选择的预期效果
很明显,随着我们增加哈希函数的数量,即增加我们跟踪的存在和不存在计数的词典项的数量,估计精度应该会提高。我们跟踪的词典单词数量越多,我们就越能期望它们在特定文档中的存在和不存在计数的最小值接近真实计数。
总结
这篇文章讨论了在一个可能无界的流中估计某个符号出现次数的问题。它专注于 CountMinSketch,这是一种带有伴随算法的数据结构,在流设置中特别有吸引力。它只对数据进行一次操作,并且可以根据我们的需要配置使用更少或更多的内存。(后者是为了提高精度或用于非常高维的用例)。
延伸阅读
用开源数据估算太阳能电池板输出
原文:https://towardsdatascience.com/estimating-solar-panel-output-with-open-source-data-bbca6ea1f523
使用 Python 和 QGIS 估算太阳能电池板输出的综合指南

图片由来自 Unsplash 的 Jack Price-Burns 提供。
为了计算一个屋顶能产生多少太阳能,我们需要以下信息:
- 屋顶坡度
- 屋顶方位角(朝向太阳)
- 一个屋顶可以容纳的太阳能电池板数量
- 阴影系数(被阴影阻挡的光的百分比)
- 太阳辐照度(屋顶接收的总阳光量)
问题是,确定前四个变量需要实地考察,这使得实施安装太阳能电池板的计划或政策更加困难。相反,我们可以使用公开可用的数据*来推导这些变量,以确定哪些区域具有高太阳能电池板输出的潜力。然后,我们可以使用不同的计算方法来估计每个屋顶的太阳能电池板年产量。
*数据可用性的广度取决于感兴趣的区域。
这篇文章是我在 DSSGxUK 为西米德兰兹郡联合权力机构和纯蛙跳应用的方法论的一个抽象的、概括的版本。参见 GitHub 库获取完整代码和西米德兰兹(英国)特定教程。
验证数据
我们能在多大程度上信任这种方法论?
我们需要验证数据来验证(1)导出的输入和(2)最终的太阳能电池板输出。困难在于获得它。
你可以从谷歌街景的房屋图像中取样,以确定屋顶的坡度和方位角,以及屋顶可以容纳的太阳能电池板的数量(尽管这个过程会很繁琐),或者如果你幸运的话,可能会有一个收集的数据库。虽然说实话,我自己还没找到。
太阳能电池板输出的真正验证数据需要从安装的太阳能电池板中读取仪表读数,或者通过在随机设置的屋顶上安装仪表来收集数据,以测量屋顶遮阳的效果。
否则,你将不得不依赖于来自组织的评估,例如英国的微型发电认证计划服务(MCS) 来进行比较。从本质上说,你只是在比较不同形式的评估,而没有任何实际数据。然而,这些仍然可以作为很好的大概估计,所以你知道你的估计至少在正确的数量级内。
采购数据
您将需要四份数据:
- 数字表面模型(DSM) :地球表面的高程信息,包括人造结构(如:建筑物、桥梁)和自然特征(如:树木、草地)。
- 数字地形模型(DTM) :裸露的所有自然和人造结构的地球表面的高度信息。它仍然包括像河流和丘陵的地形特征。
- 建筑足迹:每个建筑用多边形表示的建筑位置信息。
- 太阳辐照度:一个地区接收到的太阳能的总量。
这些数据的可用性取决于您正在查看的区域。一些政府和开源项目收集并公开发布这些数据。有些人躲在付费墙后面。在本文中,我将分享我发现的提供全球数据和英国特定数据的来源(因为我的项目主要集中在西米德兰兹)。这些来源并不详尽,所以如果你找到其他人,请留下评论!
现在,我将解释所有这些数据是什么。
来自激光雷达数据的 DSM 和 DTM
如果你熟悉回声定位(鲸鱼和海豚使用)的工作原理,激光雷达的工作原理有些类似。在固定高度飞行的飞机会发出激光脉冲(高频光)。激光在表面上反射,激光返回所需的时间提供了距离(阅读此处了解更多详情)。
我见过的英国数据的最高分辨率是 25 厘米(仅针对该国很小的一部分),而 1 米和 2 米的分辨率几乎涵盖了一切。分辨率意味着每个像素代表 25 厘米的空间,这使得图像更清晰。

伯明翰部分地区的 DSM 和 DTM 模型。亮斑表示较高的高程,与图像成比例。【图片来自作者】。
激光雷达只是测量海拔的一种方法。研究人员利用这些数据得出 DSM 和 DTM。您也可以使用卫星成像或 3D 模型,但这些通常很难找到开源的,并且在相同的粒度水平下需要更多的处理能力。
开放街道地图中的建筑物覆盖区
建筑物覆盖区有助于识别您在 DSM 和 DTM 中看到的形状,并以 shapefiles 的形式出现。Shapefiles 是一种存储地理属性信息(例如:坐标、坐标参考系统、标签)的矢量化文件。
虽然有些 shapefiles 比其他 shapefiles 更容易访问,但您也可以转向开放数据源,如 Open Street Map 来下载数据。他们也有一个 API,查看参考资料来学习如何使用它们。
查看你感兴趣地区的政府网站!他们将拥有建筑足迹数据,因为他们将其用于城市规划,这是一个数据是否公开的问题。例如,“法令测量”仅在许可建筑物覆盖区数据时发布建筑物的质心。

亮绿色代表住宅类型的建筑多边形。存储在 shapefile 中的地理信息允许面与激光雷达数据完美对齐。[图片由作者提供]
太阳辐照度数据
太阳辐照度是太阳从以下来源获得的能量:
- 直接法向辐照度:一个表面垂直接收的太阳光线量
- 漫射水平照射:没有垂直到达太阳能电池组件的太阳光线
当太阳能电池板在晴朗的天空下,倾斜等于纬度并面向南方时,获得最大的太阳辐照度。你可以在这里得到更详细的解释。
欧盟 JRC PVGIS 在线工具是太阳辐照度数据的可靠来源。其他气象机构也会有这些数据,但是欧盟 PVGIS 联合研究中心很好地整合了我将在下面介绍的一种估算方法。
屋顶分割:坡度和方位角
与其将屋顶视为一个单独的单元,不如将其视为一组具有各自方位角(坡向)和坡度的部分,这样更准确。

图片来自作者。
屋顶分割结果甚至在论文中也是可疑的。屋顶部分看起来不像您自己绘制的部分(图 4)。作者仅使用了 20 个屋顶的测试集,分别获得了 75%和 70%的房屋的坡度和坡向在 10 度以内的精度。这些结果还有许多需要改进的地方,屋顶分割不佳表明了作者对未来工作的一些建议。

QGIS 中确定的屋顶部分(作者左侧)和谷歌地图上同一区域的实际屋顶视图。
估计阴影
阴影通常是由其他较高的建筑物或植被造成的。有了 DSM 层,我们可以对投射到屋顶上的阴影量有一个相当精确的概念(至少精确到一年前)。没有 DSM,我们仍然可以根据建筑高度信息创建伪 DSM 图层。
伪 DSM
如果没有 DSM,只要有建筑高度值和周围建筑的覆盖区,您仍然可以计算着色。要创建伪 DSM,请使用建筑物高度作为像素值栅格化矢量图层,并将所有其他像素保留为“0”。
因为我们的数据只包含住宅建筑的高度,而不是所有建筑的高度,所以不准确。它也没有考虑植被的高度。
UMEP:太阳辐射:阴影生成器
UMEP(城市多尺度通用预测器)是一个开源的气候服务工具,作为 QGIS 上的插件提供。阴影生成器从 DSM 创建一天和一年的不同时间产生的阴影模型。然而,这是一个计算量很大的过程。对于比例,需要 1 小时 15 分钟来计算 500 像素乘 500 像素 DSM 在两天内以两小时为间隔的平均明暗度。
估计太阳能电池板输出
最终的太阳能电池板输出需要所有这些信息和关于用于说明不同系统的效率和容量的太阳能电池模块类型的细节。
pvlib 是一个 Python 库,用于模拟光伏能源系统的性能。对于不同的模块,您可以使用许多选项。该模型还基于太阳能电池板模块的斜度、方位和高度中的坐标和因素从 PVGIS 获取气象数据。
如果屋顶坡度和坡向不可用,可以计算坡度(0 -60,每 10)和坡向(0 -359,每 10)的几种可接受组合的太阳能电池板输出。然后,我们将平均输出作为房子的太阳能光伏输出。
输出是潜在的太阳能光伏输出,单位为千瓦时/年/平方米。
我将我的估计与 MCS 的公式和最终计算进行了比较,发现我的结果在同一数量级内。然而,如果没有真实的数据,就没有办法确切地知道每个估计有多准确。

图片来自作者。

图片来自作者。
这有多靠谱?
我们能相信这些结果吗?是数据科学中最重要的问题之一。您的结果取决于它们的可靠性!
激光雷达数据相当精确(取决于您的数据源),对于建筑物,1m 的分辨率对于我们正在寻找的粒度级别来说已经足够好了。但是如果您将这种方法扩展到更小的结构,您将需要小心行事。您还应该仔细检查激光雷达数据的最后更新时间,以便它与您的建筑数据保持一致。
构建足迹数据取决于您的来源。谁收集的数据?开放街道地图是由人们填充的开源数据和日志的集合。而政府来源可能有更严格的数据收集方法,您应该检查其可靠性。一些问题要问数据:
- 数据最后一次更新是什么时候?
- 谁收集的数据?是否涉及多方,如果是,他们如何确保数据收集的标准化?
- 是否进行了任何检查以确保数据的可靠性?
- 哪些房屋可能被排除在数据收集之外?哪些房子比其他房子更容易出错?有些房子是如何被概括的?
在 QGIS 上运行的计算反映了数据的可靠性。如果激光雷达数据与建筑物覆盖区数据不一致,则可能存在异常值。例如,在收集激光雷达数据后可能已经建造了一座建筑物。
也许我对太阳能输出估计的警告让你感到悲观。如果我不能验证它,那还有什么意义?这值得做吗?
我的希望是,这种方法将为建立适当的验证数据铺平道路,这些数据是推动未来太阳能电池板评估方法发展所必需的!所有地方议会和关注气候的组织都需要这样的数据来帮助增加我们的可再生能源使用份额,也许这将向你表明这是值得投资的。
资源
QGIS
- QGIS 教程计算建筑高度
- 通过地理信息学对屋顶类型进行分类的指南
- 通过 3DBuildings 在 QGIS 上创建建筑物高度的 3D 视图。(2020 年 12 月 9 日)
- 如果你在栅格化 shapefile 时遇到困难,看看这个视频来帮你调试。
- 论文比较了使用激光雷达数据检测光伏屋顶阴影的不同方法
- 太阳能 GIS 模拟的光伏模拟不确定性中建筑物/地形阴影损失的不确定性较高。查看他们用于太阳能光伏计算的假设。
建筑足迹
- 使用这个向导为 override API 创建查询
- Python 的天桥 API 教程
- 开源城市 GML 3D 语义建筑模型作者 Joe T. Santhanavanich
激光雷达
- 英国开源激光雷达数据(邮件索要批量文件;估计需要的时间)
- GISGeography 的激光雷达完全指南:光探测和测距。(2022 年 5 月 28 日)
- 如何:从激光雷达数据中提取建筑物高度并制作 3D 建筑物由 nadnerb 制作。(2015 年 9 月 18 日)
在缺乏基本事实的情况下估计最大似然模型的性能

阿菲夫·库苏马在 Unsplash 上拍摄的照片
这可能吗?你应该如何接近它?
将机器和深度学习模型部署到生产中,对于每个从事该项目的数据科学家来说,绝对是一个非常令人满意的时刻。然而,这绝不是项目的最后一步。随着时间的推移,我们仍然需要监控模型及其性能,进行维护,并可能对其进行改进。
作为监控的一部分,我们应该特别注意我们的模型的无声故障——当我们的模型的性能随着时间的推移而恶化,而这并没有被注意到。换句话说,模型仍然输出它的预测,不管它是一个类别标签还是一些概率分数。然而,其性能不再与我们最初将其投入生产时所期望的相似。使检测这种故障尤其具有挑战性的是,事实真相信息可能缺失或延迟。在这种情况下,我们将不得不等待发现该模型已经表现不佳很长一段时间了。
在本文中,我们首先讨论模型静默失败的潜在原因,并提到检测它们的可能方法。然后,我们研究一种方法,这种方法可以帮助我们估计最大似然模型的性能,即使我们没有基本的真实信息。
为什么模型在生产中会失败?
在这一部分中,我们提供了模型无声失败的潜在原因的例子。
数据漂移
我们的模型在生产中可能失败的第一个原因是数据漂移,即模型输入的分布随时间的变化。下图说明了这个问题。

作者图片
数据漂移在与人类行为相关的表格数据中尤其明显。例如,当疫情启动时,电子商务平台中代表客户行为的特性可能已经发生了显著变化。
有许多可能的方法来检测数据漂移。检测单变量数据漂移的一种更简单的方法是使用统计检验(例如 Kolmogorov-Smirnov 检验)来比较给定变量(特征)在两个样本中的分布。我们可以对我们的每个特性重复这个过程,但是,这样做我们假设这些特性是独立的。在大多数情况下,这是一个非常强烈且不切实际的假设。
概念漂移
概念漂移可以理解为模型输入和目标之间映射的变化。这意味着用于进行预测的决策边界在评估期之间发生了变化。我们可以在下图中看到一个例子。

作者图片
如何检测概念漂移?我们可以尝试以下方法:
- 查看两个样本中的输入和标签(基础事实)之间的相关性。
- 尝试识别训练数据和更近的样本(上个月、上个星期、上一天等)之间的模型参数变化。).
各种因素的结合
我们已经讨论了模型无声故障的两个潜在来源。实际上,原因可能更复杂,或者只是两者的结合。
一种情况可能是数据漂移实际上导致了概念漂移。在另一个场景中,数据漂移和一些未观察到的变量的组合可能看起来像概念漂移。换句话说,看起来输入和输出之间的映射在变化,但实际上变化的是未观察到的变量如何表现以及它们如何影响目标。
预测没有基础事实的 ML 模型的性能
现在,我们将解释如何评估模型的性能,即使我们没有基本事实(目标)。
动机
在我们更深入地评估性能之前,我们应该回答这个问题,它为什么重要。凭直觉,我们可以将模型投入生产,计算预测值,然后简单地将它们与实际值进行比较。瞧啊!
你可能已经猜到了,现实生活并没有那么简单。例如,与 Kaggle 竞赛相比,我们并不总是有计算模型性能的基础事实。
首先,让我们来看看理想的场景。在这本书里,我们在做出预测后几乎立刻就能得到事实真相。我们可以观察到这种情况的一个行业例子是数字广告。一些模型预测客户最有可能点击哪个广告,然后我们几乎立刻就知道客户是否点击了它。不幸的是,并不是所有的场景都这么简单。
第一个棘手的情况是被拖延了地真相。在金融行业中,这种情况非常常见,银行或其他机构使用 ML 模型来预测哪些客户可能会拖欠贷款。自然,他们不知道某个客户是否违约,直到他们的贷款全部还清。或者,我们可以为电子商务业务建立一个客户流失预测模型,其中客户流失定义为 6 个月内没有交易。这意味着我们必须再次等待一段固定的时间,直到我们知道一个给定的预测是否正确。
在第二种情况下,可能根本就没有地面真相或者很难获得。我们可能不得不雇佣人类验证者来评估某个预测是否成真。首先想到的例子是在线 KYC(了解你的客户)过程。在被授权访问一些服务之前,我们需要提供一些关于我们的数据,并对我们的 ID 和面部进行拍照,以便系统可以检查它是否匹配以及 ID 是否是伪造的。
如果一张图片被标记,它可能会被人工代理进一步调查。然后,我们将有地面真相。对于所有图片看起来合法并且没有发现不匹配的观察结果,人类注释者可能会查看其中的一小部分来仔细检查模型的性能。然而,这很少涵盖模型实际评估的所有观察结果。
下图总结了考虑的场景。

作者图片
概率校准入门
假设我们有一个二元分类器。它们中的大多数返回两个输出—预测的类和分数,分数表明模型对其预测的信心。分数越接近极值(通常分别为 0 和 1),模型的预测就越有信心。到目前为止,这应该不是什么特别新的东西。
潜在的新部分是将分数解释为概率。在分数与实际概率相符的情况下,我们可以用它来计算出错的概率。因此,当一个校准良好的模型以 0.8 分(可以解释为不出错的概率)做出 X 个预测时,我们可以假设大约 80%的此类预测确实是正确的。换句话说,校准良好的分类器是那些predict_proba方法的输出可以直接解释为模型置信度的分类器。
鉴于大多数模型侧重于性能而不是概率估计,它们的分数很少得到很好的校准。为了研究一个模型是否有很好的校准概率,我们可以使用校准曲线(又名可靠性图)。使用这样的图,我们可以看到阳性标记的真实频率是否与其预测的概率相匹配。在图中,预测的概率被分组到统一的桶中。这些轴可以解释如下:
- x- 轴表示每个桶的平均预测概率,
- y- 轴表示每个桶中阳性的比例,即类别为阳性的样本的比例。
对角线代表完全校准的分数。在下图中,我们可以看到默认情况下,逻辑回归分类器返回校准良好的预测。这是因为它直接优化了日志丢失。不幸的是,这不是其他分类器的情况。

来源:https://scikit-learn.org/stable/modules/calibration.html
底部的直方图通过显示每个预测概率箱中的样本数量提供了进一步的见解。
概率校准是一个非常有趣的话题,我将在另一篇文章中讨论。就目前而言,这个快速入门应该足以理解为什么我们需要良好校准的概率来估计缺乏基础事实的 ML 模型的性能。
怎么做
基于信心的性能估计 (CBPE)是一种算法,允许我们在缺乏地面真相的情况下估计模型的性能。它是由 NannyML 开发的,这是一个用于部署后数据科学的开源 Python 库。
为了估计模型的性能,该算法利用模型对其预测(概率估计)和预期错误率的信心。为了直观地了解这种方法在实践中是如何工作的,我们将它应用于最简单的分类评估指标——准确性。使用完全相同的原理,我们可以估计其他分数,如精确度、召回率、F1 分数、F-beta 分数、ROC AUC 等。
在进入算法的本质细节之前,我们需要提到这种方法的两个基本假设:
- 我们有一个分类器产生精确的概率,
- 没有概念漂移。
因此,只要我们只预期数据漂移,算法就应该提供模型性能的可靠估计。
逐步估算精度
众所周知,精确度的定义如下:

作者图片
为了简化,我们实际上只需要真阳性、真阴性和样本大小 n (上式中的分母)。但是我们展示了如何计算所有元素,以防我们对不同的性能指标感兴趣。
首先,对于 1 到 n 的范围内的每个预测 i ,我们使用预测的概率计算混淆矩阵的元素。假设概率是相对于正类来表示的,请考虑以下两种情况:
- 概率为 90%的观察结果—这意味着 90%的模型是正确的( TP ),10%是假阳性( FP )。两个负面指标的概率都是 0%。
- 一个概率为 20%的观察——这表明出现真阴性( TN )的几率为 80%,出现假阴性( FN )的几率为 20%。两个正面指标的概率都是 0%。
使用这个模式,我们可以为样本中的每个观察值构建混淆矩阵的四个元素。
然后,我们将概率相加,以获得整个集合的混淆矩阵的聚合元素。为了计算精度,我们需要以下两个:

作者图片
使用这些值,我们可以计算出估计的精度:

作者图片
就是这样,仅使用一个校准良好的分类器的输出,我们就可以评估模型的性能(在这种情况下,它的准确性),而无需了解基础事实。
我们可以按照完全相同的步骤获得其他性能指标。ROC AUC 得分稍微复杂一些,因为它带来了为所有考虑的阈值重新计算上述步骤的额外复杂性。
正如你所看到的,算法非常简单,但是底层思想真正“落地”需要一点时间。对我来说,花费最多时间的是概率的总和。在一个正常的混淆矩阵中,我们简单地观察表示属于一个特定类别的观察数量的整数。
在这种方法中,我们有一个预期概率的总和,这导致了预期的真阳性、假阴性等的数量。这个期望值不是一个整数,但这没关系,因为在计算预期的性能评估指标时,这不是一个问题。
实践中的方法
为了加强我们对算法的理解,让我们用一个简化的例子来完成它的步骤。首先,我们导入库并创建一个玩具数据集。
我们可以把它看作是我们校准良好的分类器的输出:类别预测和相应的概率。使用典型的方法,我们假设超过 50%的概率导致肯定类别的预测。请记住,在这个练习中,我们没有地面真相!

作者图片
使用分类器的输出,我们为每个观察值计算混淆矩阵的元素。

作者图片
接下来,我们需要合计各个概率,以得出整个样本的合计值:
pred_df[["TP", "TN", "FP", "FN"]].sum()
它返回:
TP 2.35
TN 1.40
FP 0.65
FN 0.60
最后,我们可以计算估计的精度(没有实际的标签)为(2.35 + 1.4) / 5 = 0.75。
我们也可以使用[nannyml](https://github.com/NannyML/nannyml)库,而不是手动计算估计的性能指标,这使得过程更加简单。使用[nannyml](https://github.com/NannyML/nannyml)的另一个好处是,它会自动检测分类器是否校准良好,并在必要时为我们进行校准。
首先,让我们假设我们的数据被分成两个分区:
- 参考分区 —用于建立模型的基准性能。例如,我们可以使用模型已经投入生产并提供令人满意的性能时的数据。
- 分析分区 —该分区通常包含最新的生产数据,我们根据这些观察结果来评估模型的性能(当我们仍然不知道实际情况时)。需要满足的一个条件是,它需要在引用分区结束的点之后开始。
使用nannyml,我们可以很容易地计算出参考分区的模型的观察性能和分析分区的估计性能。
下面,我们可以看到一个输出的例子。曲线的前半部分对应于参考期。然后,接下来的点实际上是模型的估计性能,以及置信区间(使用参考周期计算)。有趣的是,该库还使用参考周期来计算警报阈值——当估计的性能超过这些阈值时,这是一个信号,表明模型的性能发生了显著变化(变好或变坏)。

来源:https://github.com/NannyML/nannyml
外卖食品
- 数据漂移和概念漂移会导致我们的模型在生产中无声的失败。
- 使用基于置信度的性能估计,我们可以估计模型的性能,即使我们无法访问(可靠的)地面真相。
- 该算法有两个假设:没有概念漂移和分类器产生良好校准的概率。
一如既往,我们非常欢迎任何建设性的反馈。你可以在推特上或者评论里联系我。
喜欢这篇文章?成为一个媒介成员,通过无限制的阅读继续学习。如果您使用 这个链接 成为会员,您将支持我,无需额外费用。提前感谢,再见!
您可能还会对以下内容感兴趣:
参考
- 尼古列斯库-米齐尔和卡鲁阿纳(2005 年 8 月)。用监督学习预测好的概率。在第 22 届机器学习国际会议论文集(第 625–632 页)。—https://www . cs . Cornell . edu/~ alexn/papers/calibration . icml 05 . CRC . rev 3 . pdf
- Naeini,Mahdi Pakdaman,Gregory Cooper 和 Milos Hauskrecht:“使用贝叶斯宁滨获得校准良好的概率”2015 年第二十九届 AAAI 人工智能大会。—https://www . aaai . org/OCS/index . PHP/AAAI/aaai 15/paper/download/9667/9958
- https://scikit-learn.org/stable/modules/calibration.html
- https://github.com/NannyML/nannyml
- https://docs . nannyml . com/main/deep _ dive/performance _ estimation . html
道德和会话助理
原文:https://towardsdatascience.com/ethics-and-conversational-assistants-68adcec20ca
意见
近年来,信任和对伦理规则的考虑已经成为人工智能领域的一个重要组成部分。早期的滥用导致立法者在事后监管该行业。本文讨论的道德问题的会话助理考虑这些新的规定。

2018 年 9 月 28 日,加州州长批准第 6 章命令,机器人必须明确表示它们只是软件。
2020 年 9 月 7 日,CNIL ( 国家信息和自由委员会 — 法国组织,负责确保保护计算机文件和处理或纸张(公共和私人)中包含的个人数据)发布了一份关于语音助手的白皮书,并提出了道德等问题。
同年 10 月 20 日,欧洲议会通过了一项关于人工智能、机器人和相关技术伦理方面框架的决议。
作为该决议和其他工作的延续,同一个议会和欧洲委员会于 2021 年 4 月 21 日提交了一份关于建立人工智能统一规则和修订欧盟某些立法法案的法规提案。
2021 年 9 月 15 日,cn pen(“commitéNational Pilote d ' ethique du numérique”是一个法国委员会,成立于 2019 年,旨在全面解决数字世界的道德问题)通过了一项关于对话代理的道德风险的意见,并制定了十三项设计建议。
这些最近的出版物告诉了我们什么?
伦理学
伦理学是哲学的一个分支,研究道德的基础和社会中个人的行为。伦理学研究道德,研究什么是对或错,什么是正义或不正义。
即使对话助手是简单的计算机程序,最近的技术进步和聊天机器人的广泛使用也对它们在社会中的作用以及它们对人类的影响提出了质疑。
出版物
加利福尼亚州是美国第一个对“机器人”(“非人类”执行动作或发布消息)的道德规范进行立法的州。州长认为掩盖机器人的人工身份,不清楚地表明它是简单的软件,而不是提供建议或信息的人是非法的。对这种虚拟性的提及必须“清晰、显眼、设计合理”[1]。
尽管这项法律是 2016 年美国大选期间“假新闻”传播的间接后果,并且具有明显的限制性(拥有超过一千万访问者的平台,煽动购买,在选举期间影响投票),但它现在已经扎根于这个领域,并可以在事实上应用于对话助理。这条定律给我们上了第一课:我们不应该欺骗公众,把一个严格的人性层面归因于一个会话助手。
2020 年 9 月 7 日,CNIL 发布了一份 88 页的白皮书,探讨语音助手面临的伦理、技术和法律挑战[2]。这份报告指出,语音辅助设备通过改变我们使用数字工具的方式,提出了一个重大的范式转变。报告指出,一方面,这些终端的设计带来了不透明性,没有屏幕,因此无法可视化过去的活动。另一方面,这些设备只提供一个答案,这就带来了选择的可靠性和中立性的问题。对于这个问题,“谁是最好的水管工?”,系统可以很好地给出支付最多的人的地址,以首先被定位,而无需机器人这样说。
除了隐私和数据保护的问题(比伦理部分更详细),拟人化的问题引起了争论,设计师有时会推动助手的拟人化以增加承诺。该报告提到了“宜家综合症”,即当用户预见到个性化助理的可能性时,会导致用户对物品产生更强烈的依恋,从而赋予物品更大的价值,因为他为物品的构造做出了贡献[3]。即使参数化仅仅在于选择助手的声音,该附件也会出现。通过这个简单的动作,机器人变成了一个私人助理,一个它帮助建造的存在。
关于国家笔会对话代理人的第 3 号意见在伦理领域走得更远[4]。第一,它占用了不欺骗用户的原则。该意见然后建议在选择代理人性别时选择公平原则。令人惊讶的是,该意见建议能够个性化助理,从而为用户诱发“宜家综合症”。
…聊天机器人不应该被用户认为是一个负责任的人,即使是通过投影。总的来说,这不是一个让拟人化自由发展的问题,也不是一个想不惜一切代价消除它的问题,而是在具体情况下确定其限度的问题。
该报告还处理了对助手的侮辱,指出他们没有什么不道德的,因为他们只是计算机程序。同时考虑到这种行为对说出这些话的人来说是有辱人格的。最后,CNPEN 建议通过允许对侮辱做出反应来处理这些行为,但建议不要寻求升级。另一方面,他们建议不要在学习语料库中包括这样的句子,这与检测的概念奇怪地矛盾…
对于“助理可以故意撒谎吗?”答案是“没有”(迄今为止)。助手没有道德判断,没有邪恶的意图,因此责任在于制造商。我要补充的是,训练机器人的人也有责任。
今天,助手的作用是显而易见的,特别是对弱势群体(=“未成年人或由于年龄、疾病、虚弱、身体或精神缺陷或怀孕状态而无法保护自己的人”——《法国刑法典》第 434-3 条)。这种有用性与这些系统的弹性、对所说内容的判断缺失以及它们在任何时候的可用性密切相关。尽管如此,从设计上来说,还是有必要防止责任的混淆,以及例如助手和医务人员之间可能存在的责任混淆。最后要注意的一点是用户和他的机器人之间可能的依赖。
“死穴”
2017 年,微软申请了一项专利,解释了他们如何计划将个人的个性数据复制到对话机器人中[5]。该系统使用个人数据(例如,图像、语音数据、社交媒体帖子、电子邮件、书面信件等。)来创建或修改帮助训练机器人的“索引”。
最令人不安的用途是这种系统允许实现所谓的“死机器人”的能力,也就是说,模拟一个已故的个人的形象及其对话能力。正如国家预防和控制网络的报告所指出的,死者存在的这种延长"首先质疑方法本身"。他们最后指出,首先,对这些助手的存在进行社会反思是可取的。
欧洲伦理框架
人工智能、机器人和相关技术的伦理方面的框架以及欧洲议会和理事会制定人工智能协调规则(人工智能立法)和修订某些联盟立法法案的提案[6]汇集了一系列涵盖人工智能广阔领域的文章。它对会话助手(聊天机器人)的应用赋予我们以下义务:
- 人工监督必须始终完整。
- 告知与人工智能系统交互的责任。请注意,该提案规定"这一义务不适用于法律授权检测、预防、调查和起诉刑事犯罪的人工智能系统,除非这些系统可供公众报告刑事犯罪。
- 能够暂时禁用某些功能,只保留最安全的功能。
- 反偏见和反歧视的方法。
- 在使用情绪识别系统的情况下,该文本建议用户有义务了解系统的功能。
综合
总之,助理创作者需要记住的道德设计原则如下:
- 不要通过表明它是一个计算机程序来欺骗用户。有义务从互动一开始就明确,并定期重复这种状态。
- 从算法的设计阶段或对话的创建阶段就考虑伦理问题,以避免偏见或歧视。
- 描述用户助手的用途及其目标。告知对过去的对话进行的操作,特别是如果拟人原则被应用于改善未来的对话。展示系统在语言处理方面的能力和局限性。如果适用,解释如何管理情绪以及系统将如何做出相应的反应。
- 将对话保留必要的时间来处理它们,并且能够解释、后验以及(如果必要的话)机器人在对话期间的行为。
- 让机器人的个性适应用户的文化。
因为使用语言作为交流的媒介,所以在称呼会话助手时排除任何形式的拟人化都是空想。因此,设计者必须通过实施这些设计规则来限制这些缺点,从而降低欺骗和依赖的风险,并对这些系统给予信任。
参考
[1] 参议院第 1001 号法案——第 6 章。bots—https://leginfo . legislation . ca . gov/faces/billtextclient . XHTML?bill_id=201720180SB1001
[2][法语]Livre blanc sur les assistants vocaux de la CNIL—探讨语音助手的伦理、技术和法律问题—https://www . cnil . fr/sites/default/files/atoms/files/cnil _ Livre-blanc-assistants-vocaux . pdf
[3][法语] 合成之声:人机交互的大众传播。对话 avec le monde——https://www . cairn . info/revue-communication-et-langages 1-2017-3-page-63 . htm
[4][法语] Avis n 3 代理商 conversationnels enjeux d ' ethique—https://www . ccne-ethique . fr/sites/default/files/2022–02/Avis % 20n % C2 % B03 % 20 代理商% 20 conversationnels % 20 enjeux % 20d % 27% C3 % a9thique . pdf
[5] 创建一个特定人的对话聊天机器人——https://patents.google.com/patent/US10853717B2/en?oq=us10853717b2
[6] 关于欧洲议会和理事会制定人工智能统一规则(人工智能法案)并修正某些欧盟法案的法规提案—https://eur-lex.europa.eu/resource.html?uri = cellar:e 0649735-a372-11eb-9585-01aa 75 ed 71 a 1.0001 . 02/DOC _ 1&format = PDF
感谢您的阅读!如果你想了解更多关于聊天机器人、自然语言处理和人工智能的信息,请关注我的媒体、领英、和推特。
与会话助手相关的其他文章
https://ai.plainenglish.io/is-your-chatbot-accessible-6b89a5e300f1 </14-criteria-for-well-choosing-a-chatbots-solution-2e788aace3b8> https://ai.plainenglish.io/conversational-ai-for-builders-the-4-levels-of-complexity-scale-4eb482a862d9
临床数据科学背景下的 ETL
原文:https://towardsdatascience.com/etl-in-the-context-of-clinical-data-science-9236e399c88a
医疗保健中的 ETL 概述,这是临床数据科学中数据生命周期的关键部分
这是我的临床数据科学系列的第二篇文章。在第一篇文章中,我提供了对临床数据科学广阔领域的全面概述,这为这一版的构建奠定了基础。如果您不熟悉临床数据科学领域,或者您只是想快速复习关键思想,您可能想先睹为快地阅读一下介绍性文章。话虽如此,文章其余部分组织如下;
- ETL 概述
- 临床数据科学/医疗保健领域的 ETL
- ETL 生命周期
- ETL 工具
- 临床数据仓库
- 医疗保健中 ETL 面临的挑战。
ETL 概述
提取、转换和加载(ETL)是一个三阶段的过程,涉及从一个或多个源获取原始数据,并将其移动到一个称为暂存区的中间临时存储区;转换提取的数据以实施数据有效性标准和与目标系统的一致性;并将数据加载到目标数据库,通常是数据仓库或储存库。
ETL 的提取阶段处理数据的导出和验证,转换阶段涉及清理和操作数据,以确保数据适合目标,最后一个阶段是加载阶段,涉及将提取和清理的数据集成到最终目的地。这一流程是构建数据分析工作流的基础。通过使用一组技术和业务标准,ETL 过程确保数据是干净的,并且被适当地组织以满足业务智能的需要。

典型的 ETL 过程(图片由作者提供)
典型的 ETL 过程采用循序渐进的方法,从理解源系统中包含的数据的结构和语义开始。我们试图获取的数据源可能是数据存储平台、遗留系统、移动设备、移动应用程序、网页、现有数据库等。在建立技术和业务需求之后,需要理解满足这些需求的正确的字段/属性以及它们存储的格式。其中许多格式,包括关系表单、XML、JSON、平面文件等。,可用于源系统中的数据。
为了准备将提取的数据集成到数据仓库中,应用了一系列规则来检查、清理和组织数据,以确保只加载“合适”的数据。在这个阶段,应用了许多转换类型。例如,不是源系统中的每个字段都可以在目标系统中使用;转换过程将考虑选择可以在源和目标之间同步的精确字段。一个很好的例子是,我们在源系统中有一个名为“date _ of _ birth”的字段,它是一个日期值,由个人的出生年月日组成。然而,在目标系统中,这些字段被分解为、【出生年份】、【出生月份】、、【出生日期】、字段。在我们的转换管道中,我们需要创建一个规则,将源系统中的单个字段分解成目标系统中相应的三个字段,以确保它们是一致的。

将源数据中的字段映射到目标系统中的字段(图片由作者提供)
在提取和转换之后,数据就可以加载到目标系统中,用于查询或进一步的分析处理。如前所述,目标系统最典型的例子是数据仓库,它只是作为从各种来源编译的数据的存储库。稍后将详细介绍。
临床数据科学/医疗保健领域的 ETL
要回答与健康相关的问题,需要对医疗保健行业中产生的数据的复杂性以及源系统中的数据在目标数据库中的组织方式有一个透彻的了解。在医疗保健行业中,ETL 对于从一个源(通常是 EHR)导出数据,并将其转换为与目标数据库结构兼容的形式是必需的,数据将存储在目标数据库中,供以后使用或以现成的格式提供。正如在第一个系列中提到的,EHR 提供关于人们健康状况的信息,是医疗保健行业的重要信息源。来自 EHRs 的数据给从业者和研究人员提供了改善患者结果和健康相关决策的机会。
医疗保健中的 ETL 可以简单到将来自临床环境中几个部门的数据进行合并以改善决策,也可以复杂到将来自大量 EHR 系统的数据整合到一个通用数据模型(CDM)中,例如观察医疗结果合作伙伴关系(OMOP)、集成生物学和床边的信息学(i2b2)、Mini-Sentinel (MS)和以患者为中心的结果研究网络(PCORNet),这些模型通常被研究网络用于知识共享和研究。
为了填充目标数据库以进行提取,并提供源数据和目标数据元素之间的映射,医疗保健领域 ETL 的提取阶段需要由具有领域专业知识的个人定义源数据(如 EHR 或索赔数据)中的合适字段。
正如 Toan 等人所描述的,在确定了要映射到目标数据库的正确数据元素之后,工程师/数据库程序员定义数据转换的规则/技术以及将数据加载到协调模式中的模式映射。为了符合目标模式格式和代码,以便可以将它们放入目标数据库,转换是一个复杂的数据“清理”(例如,重复数据删除、冲突解决)、标准化(例如,本地术语映射)的过程。这个阶段需要使用结构化查询语言(SQL)等语言进行手工数据库编程。这些过程经常重复,直到更改的数据被认为是全面和准确的。
在临床科学的环境中,集成来自不同来源的数据是一项艰巨的任务,需要在 ETL 过程中进行多次迭代。这些迭代过程通常有自己的困难,这可能是由不准确的映射、冗长的查询时间和数据质量问题引起的。不正确的映射通常源于源数据和目标系统之间的兼容性冲突,在这种情况下,源数据库通常具有不同的数据表示、词汇、数据元素术语和数据粒度级别。
ETL 生命周期
在粒度级别上,ETL 过程包括若干迭代,从 ETL 规范、数据提取、数据验证、ETL 规则创建、查询生成(使用 SQL)、测试和调试以及数据质量报告开始。
- ETL 规范是为开发 ETL 脚本收集必要信息的文档。
- 简单地说,数据验证是确保作为 ETL 过程的一部分传输的数据在目标生产实时系统中是一致的、正确的和完整的,以满足业务需求的过程。
- 规则创建和查询生成包括创建数据提取规则和使用(最常见的是使用 SQL)实现规则。
- 测试和调试确保数据在整个数据管道中是准确、可靠和一致的,包括数据仓库和迁移阶段。通过测量整个 ETL 过程的有效性,我们可以发现任何瓶颈,并确保该过程准备好随着数据量的增加而扩展。
- 数据质量报告给出了在 ETL 过程中发现的任何质量缺陷,这是确保数据完整性所必需的。为了更准确地反映其维度和影响,数据质量被认为是多维的。每个维度都有一组度量标准,可以对其进行评估和测量。
在临床数据科学中,数据质量问题可能发生在可访问性、有效性、新鲜度、相关性、完整性、一致性、可靠性和完整性方面。临床数据质量是一个关键问题,因为它影响研究的决策和可靠性。
ETL 工具
ETL 工具是为简化 ETL 过程而设计的技术解决方案。如果使用得当,ETL 技术可以提供一致的数据获取、共享和存储方法,从而简化数据管理技术并提高数据质量。商业企业生产并支持一些现成的 ETL 工具。它们提供了各种各样的功能,包括用于开发 ETL 管道的图形用户界面(GUI ),对关系和非关系数据库的支持,以及丰富的文档。它们在设计上相当稳健和成熟。例如,Hevo、SAS 数据管理、Fivetran、Oracle data integrator 等。
由于高昂的价格和使用企业构建的 ETL 工具所需的培训水平,其他替代方案包括使用开源软件,如 Talend Open Studio、Pentaho Data Integration、Singer 或 Hadoop。然而,开源工具可能无法满足组织的特定需求。此外,由于开源 ETL 技术经常得不到盈利企业的支持,它们的维护、文档、可用性和有用性会有所不同。

ETL 工具类别和示例(作者列表)
正如 Toan 等人在他们的论文中指出的,一个带有 GUI 的数据集成解决方案,可以简化 ETL 过程,减少与 ETL 设计过程相关的手工工作量。但是,基于 GUI 的技术通常缺乏处理复杂转换操作所需的灵活性,例如用于执行重复数据删除或增量数据加载的独特协议。此外,用基于 GUI 的工具评估转换问题可能是一个挑战,因为它们经常缺乏执行转换的底层查询命令的透明性。
如果一个机构高度重视对灵活性的控制,如果他们有必要的开发资源,他们可能会设计一个内部解决方案。这种方法的主要优点是能够创建特定于组织的优先级和流程的解决方案。流行的编程语言如 SQL、Python 和 Java 都可以用于此。这种策略的主要缺点是测试、维护和更新定制 ETL 工具所需的内部资源。
临床数据仓库(CDW)
数据仓库是为报告和研究而安排的旧数据的集合。它通过汇集和链接来自不同来源的数据使数据访问变得更容易,从而使它们更容易访问。跨许多学科的利益相关者进行决策的最重要的工具之一是数据仓库(DW)。数据仓库中的数据以多维形式组合和表示,便于快速简单的显示和分析。
根据维基百科,一个临床数据仓库 (CDW)或临床数据仓库 (CDR)是一个实时数据库,它整合了来自各种临床来源的数据,以呈现单个患者的统一视图。它经过优化,允许临床医生检索单个患者的数据,而不是识别具有共同特征的患者群体,或者方便特定临床科室的管理。CDR 中常见的典型数据类型包括:临床实验室测试结果、患者人口统计、药房信息、放射学报告和图像、病理报告、住院、出院和转院日期、ICD-9 代码、出院总结和进度记录。
CDW 可以作为记录、实施、规划和促进临床研究的基础。此外,CDW 改进了临床决策,同时简化了数据分析和处理。在传统的 ETL 项目中,提取和转换的数据被加载到数据仓库(DW)中;然而,在临床数据科学中,数据被加载到临床数据仓库(CDW)中。导入 CDW 的数据与临床决策和研究的数据一样好,因此彻底执行 ETL 流程的所有部分至关重要。
医疗保健领域 ETL 面临的挑战
人们认识到,数据协调操作是一项耗费大量资源的艰巨任务。因此,过去已经做了很多工作来解决医疗保健中与 ETL 相关的挑战。
Toan 等人认为,ETL 过程的典型技术挑战包括源数据和目标数据之间的兼容性、源数据质量以及 ETL 过程的可伸缩性。
虽然许多 EHR 系统在设计上很灵活,允许包括医生和护士在内的保健人员以非编码方式输入信息,但这导致必须从各种来源集成的数据缺乏统一性,这就是兼容性成为问题的原因。由于系统之间的字段、词汇和术语冲突,源系统中可能存在不一致。如果目标数据系统无法有效集成源数据,兼容性问题可能会导致信息丢失。
由于健康数据通常具有高容量,并且源系统中存在持续的更新和操作变化,因此设计和维护一个能够适应不断增长的数据大小和工作负载的工具,同时保持合理的响应时间变得非常具有挑战性,因此存在可伸缩性问题。
从源提取的数据可能来自一个系统,该系统中的数据组织与目标系统中使用的设计完全不同。因此,确保通过 ETL 过程获得的数据的准确性是一个挑战。为了向最终用户提供尽可能清晰、全面和正确的数据,必须消除数据质量问题,这些问题可能从文本特征中的简单拼写错误到值差异、违反数据库约束以及矛盾或缺失信息。
解决这些问题的第一步是承认它们的存在,并意识到在创建 ETL 解决方案时,您可能会遇到许多这样的问题。通过集中和提取满足 CDW 利益攸关方要求的数据,并以某种格式存储,可以确保数据质量。
正如 Fred Nunes 在他的博客文章中所说,目前缺乏高质量的数据管道是医疗保健部门广泛采用可用尖端方法的最大障碍。这种障碍不是该行业固有的,也不是与其从业者或患者有关
结论
在本系列文章中,我们先回顾了 ETL 的基础知识,然后展示了它在医疗保健中的应用。然后,我们检查了 ETL 工具以及将提取的数据集成到存储库中作为流程的最终输出。我指出,临床数据科学和医疗保健中的 ETL 对数据完整性和质量非常敏感,因为低质量的数据可能会对组织的决策过程或使用数据的研究结果产生不利影响。临床数据结构的复杂性和医疗操作的多样性要求在将数据加载到 CDW 存储之前实施复杂的 ETL。
参考文献
医疗行业的数据管道(daredata.engineering)
提取-转换-加载技术综述。(researchgate.net)
【2022 年 13 款最佳 ETL 工具(hubspot.com)
ETL 测试数据仓库测试教程(完全指南)(softwaretestinghelp.com)
EUCA——一个有效的 XAI 框架,让人工智能更贴近终端用户
在这篇文章中,我们将讨论如何使用 EUCA,一个可解释的人工智能框架来增加终端用户对人工智能的采用

来源: Pixabay
人工智能(AI) 正被积极用于解决许多领域的商业问题。但是人工智能算法通常被认为是复杂的 T4 或者神奇的黑匣子,可以准确预测未来。尽管人工智能在许多应用领域取得了突破性的进展,但由于人工智能不够透明,最终用户对关键应用领域采用人工智能犹豫不决。因此,解释复杂人工智能模型工作的能力是为关键决策过程提供人工智能解决方案的必要条件。缺乏透明度,在最终用户和人工智能解决方案之间造成了隔阂。所以, 【可解释 AI (XAI) 方法被认为是在 AI 和终端用户之间架起桥梁的工具。
什么是 XAI?
XAI 是一系列用于解释“黑箱”人工智能模型的工作原理并证明这些人工智能模型产生预测背后的原因的方法。
由于未能产生准确的预测,人工智能模型已被仔细审查,尤其是在最近的时间。有偏见和不公平的模型是人工智能生产者和消费者的主要担忧。因此,建立了许多监管准则来揭开人工智能算法的复杂性和工作原理。因此,需要可解释性,这可以通过各种 XAI 技术来实现。
如果你对 XAI 概念不太熟悉,我强烈推荐你观看过去在 2021 年 APAC人工智能加速器节上发表的关于 XAI 的演讲:
可解释的人工智能:使 ML 和 DL 模型更易解释(作者谈)
****更新如果你喜欢这篇文章,并希望更多地支持我为社区所做的贡献,请看看我的书“ 【应用机器学习可解释技术 ”,这是 GitHub 资源库,其中包含许多关于书中各个章节的实践教程:https://GitHub . com/packt publishing/Applied-Machine-Learning-explability-Techniques。如果你喜欢 GitHub 资源库中提供的教程,请在资源库中做 fork 和 star,以示你对这个项目的支持!这本书现在接受预购。请订购本书的 实体本 或 电子本 以示支持。
通常对于机器学习(ML)模型,XAI 通常被称为可解释的 ML 或可解释的 ML 。

来源—https://github.com/weinajin/end-user-xai
简而言之,XAI 掌握着让人工智能更接近终端用户的关键。对于所有的工业用例及业务问题,XAI 现在是一个基本的必需品,而不仅仅是一个附加物。让我们讨论一下 XAI 框架的最新发展水平。
实践中使用的当前流行的 XAI 框架有哪些?
自成立以来,XAI 在学术和工业领域都取得了重大进展。以下是实践中实施 XAI 方法的一些最常用的框架:
所有这些框架都设计得非常好,在解决可解释性的不同方面和维度方面非常有用。由于 XAI 领域正在快速发展,未来将会有更多的框架出现。但是很难概括所有的可解释性问题,因此很难用一个统一的框架来解决模型可解释性的所有方面。
尽管这些框架各有各的优点,但几乎所有的框架都是为技术专家开发的,如数据科学家、ML 工程师、ML 架构师和数据产品所有者。因此,这些 XAI 框架提供的可解释性不容易被任何非技术终端用户理解。让我们在下一节中对此进行更多的讨论。
向非技术终端用户解释 AI 真的很容易吗?
大多数 XAI 框架试图根据 ML 模型使用的特性的相关性来提供可解释性。其中一些使用复杂的可视化,如部分依赖图、汇总分布图等,对于任何非技术用户来说都不容易理解。非技术用户更喜欢对人友好的预测,这些预测是可行的,并且与他们对该领域的先验知识一致。所以,向非技术终端用户解释人工智能并不容易。
人工智能和人工智能消费者之间存在差距的另一个原因是,大多数人工智能应用程序都是在孤岛中开发的,最终用户只是在部署过程之后才被介绍到解决方案中。因此,为了弥合这一差距,建议遵循以最终用户为中心的人工智能(耐力)方法,在这种方法中,消费者从设计过程开始就参与进来,人工智能解决方案的开发以用户为中心。
基于类似的思想,以最终用户为中心的可解释人工智能框架(T15)金等人在他们的研究工作中提出了以最终用户为中心的可解释人工智能框架,。
让我们在下一节讨论更多关于 EUCA 的内容。
EUCA:以终端用户为中心的可解释人工智能框架
EUCA 框架是作为一个原型工具开发的,为非专业用户设计 XAI。GitHub 官方项目可以从这里获得:https://github.com/weinajin/end-user-xai。EUCA 可以帮助建立一个低保真度的 XAI 原型。它可用于快速构建“试错”原型,并通过获取最终消费者的反馈进行迭代改进。
谁能使用 EUCA?
EUCA 框架可以主要由 UX 研究人员、设计师、人机交互研究人员、人工智能开发人员和研究人员使用,以用户为中心为最终用户设计或构建 XAI 系统。

来源— Pixabay
这个框架是关于什么的?
该框架提供了以下主要组件:
- 12 种最终用户友好的解释表格
- 相关的设计示例/模板
- 相应的实现算法,以及
- 根据我们的用户研究结果确定的属性(它们的优点、缺点、用户界面/UX 设计含义以及适用的解释需求)。
- 建议的原型法
- 最终用户的不同解释需要分析(例如校准信任、检测偏差、解决与 AI 的分歧)
查看设计模板的说明表格页面。

来源—https://github.com/weinajin/end-user-xai
EUCA 框架中的 12 种解释形式可以用下图来概括:

EUCA 的 12 种解释形式(来源—https://github.com/weinajin/end-user-xai)
正如 GitHub 库中所提供的,每种形式都包含不同类型的解释方法,如下所示:
基于特征的解释
举例说明
基于规则的解释
补充信息
接下来,让我们介绍使用 EUCA 进行 XAI 原型制作的步骤。
将 EUCA 用于 XAI 原型制作
XAI 原型制作的过程可以概括为三个主要步骤,如下图所示:

使用 https://github.com/weinajin/end-user-xai 的 EUCA 制作原型的步骤(来源— )
步骤如下—
- 使用 EUCA 提供的原型卡片模板使用基于卡片的原型。
- 设计低保真度原型,并根据最终用户的反馈反复改进原型。
- 开发功能原型,在实践中实施 XAI 技术。
引用
金,,等.《:一个面向以终端用户为中心的可解释人工智能的实用原型框架》arXiv 预印本 arXiv:2102.02437 (2021)。
摘要
EUCA 框架为 HCI/AI 从业者和研究人员提供了一个实用的原型工具包,帮助他们理解最终用户构建可解释的 AI/ML 系统的目标和需求。但是,在这一点上,它只能被认为是一个良好的开端。提供的模板为快速设计和开发 XAI 系统原型提供了很好的指导。
****更新如果你喜欢这篇文章,并希望更多地支持我为社区所做的贡献,请看看我的书“ 【应用机器学习可解释技术 ”,这是 GitHub 资源库,其中包含许多关于书中各个章节的实践教程:https://GitHub . com/packt publishing/Applied-Machine-Learning-explability-Techniques。如果你喜欢 GitHub 资源库中提供的教程,请在资源库中做 fork 和 star,以示你对这个项目的支持!这本书现在接受预购。请订购本书的 实体本 或 电子本 以示支持。
如果你想联系我分享任何反馈,请随时过来,在 LinkedIn 上说hi——https://www.linkedin.com/in/aditya-bhattacharya-b59155b6/,我们也可以通过我网站上提到的其他方式联系:https://aditya-bhattacharya.net/contact-me/。我的其他介质物品也可以从这里轻松获取:https://adib0073.medium.com/。快乐学习:)
如何在 NumPy 中计算欧氏距离
原文:https://towardsdatascience.com/euclidean-distance-numpy-1b2784e966fc
展示如何在 NumPy 数组中计算欧几里得距离

马库斯·斯皮斯克在 Unsplash 上拍摄的照片
介绍
两点之间的欧几里得距离对应于两点之间线段的长度。假设我们有两点 A (x₁,y₁)和 B (x₂,y₂),这两点之间的欧几里得距离如下图所示。

两点之间的欧几里德距离—来源:作者
下面给出了用于计算两点之间欧几里德距离的数学公式。
d =√((x₂-x₁)+(y₂-y₁)
在今天的简短教程中,我们将探讨在使用 NumPy 数组时计算欧氏距离的几种不同方法。更具体地说,我们将展示如何使用
linalg.nrom()方法scipy套餐- 以及
sqrt()和einsum()方法的组合
首先,让我们创建一个示例 NumPy 数组,我们将在下面的部分中引用它来演示计算欧几里德距离的几种不同方法。
import numpy as npa = np.array((1, 2, 3))
b = np.array((4, 5, 6))print(a)
***array([1, 2, 3])***print(b)
***array([4, 5, 6])***
使用 linalg.norm()计算欧几里德距离
在计算欧几里得距离时,我们的第一个选项是[numpy.linalg.norm()](https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html)函数,它用于返回八个不同矩阵范数中的一个。
欧几里德距离实际上是 l2 范数,默认情况下,numpy.linalg.norm()函数计算第二个范数(参见参数ord)。
因此,为了计算欧几里得距离,我们可以简单地将两个 NumPy 数组的差传递给这个函数:
euclidean_distance = np.linalg.norm(a - b)print(euclidean_distance)
***5.196152422706632***
使用 SciPy 计算欧几里得距离
Scipy 包提供了一个模块,该模块具有许多计算各种类型的距离度量的函数,包括欧几里德距离。更具体地说,scipy.spatial.distance.euclidean函数可以计算两个一维数组之间的欧几里德距离。
from scipy.spatial.distance import euclideaneuclidean_distance = euclidean(a, b)print(euclidean_distance)
***5.196152422706632***
编写我们自己的函数
最后,另一个选择(我认为是一个非常明显的选择)是简单地编写我们自己的函数,它能够计算两个输入数组之间的欧几里德距离。
现在让我们重温一下我们在文章开头已经讨论过的欧几里德距离的数学定义。
d =√((x₂-x₁)+(y₂-y₁)
下面共享的函数将完全做到这一点:
def compute_euclidean(x, y):
return np.sqrt(np.sum((x-y)**2))
最后,让我们确保结果与前面提到的两种方法相同:
euclidean_distance = compute_euclidean_distance(a, b)print(euclidean_distance)
***5.196152422706632***
最后的想法
在今天的文章中,我们讨论了欧几里德距离以及在使用 NumPy 数组和 Python 时如何计算它。更具体地说,我们展示了如何使用三种不同的方法来计算它;linalg.nrom()方法,sqrt()和einsum()方法的组合,并使用scipy包。
成为会员 阅读媒介上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。
https://gmyrianthous.medium.com/membership
相关文章你可能也喜欢
欧洲竞争最激烈的足球联赛——数据科学家的回答
实践教程,只是为了好玩的数据科学项目
欧洲竞争最激烈的足球联赛——数据科学家的回答
用数据科学回答由来已久的足球争论

托马斯·塞勒在 Unsplash 上的照片
序文
足球是世界上最受欢迎的运动。2018 年国际足联世界杯是第二大最受关注的体育赛事(仅次于奥运会),拥有约 30 亿观众。在欧洲,我们热爱足球,它经常是许多激烈辩论的中心。欧洲国内联赛之间存在巨大的竞争,每个人都认为他们的国内联赛更胜一筹。
足球迷们的老问题是,“竞争最激烈的联赛是什么?”
在英国,我们愿意相信我们的国内联赛是精英联赛,我们经常瞧不起其他欧洲联赛,称它们为“农民联赛”。
我想用数据科学一劳永逸地结束这场争论。那么欧洲竞争最激烈的联赛是什么呢?
数据科学家对古老争论的回应
我有必要为这次调查设定一个合适的背景。我会看过去的 11 个足球赛季,从 2010/2011 到 2020/2021。我的分析将试图回答“在过去的 11 个赛季中,竞争最激烈的欧洲联赛是什么?”。
我意识到我漏掉了一些联赛,我没有无限的时间来做这些项目,所以如果你愿意,请随意扩展我的方法。
联赛:西甲(西班牙)、英超(英国)、法甲(法国)、德甲(德国)、荷甲(荷兰)和意甲(意大利)
欧洲足球联赛数据
我手动整理了六个欧洲联赛 11 个赛季的实际历史联赛成绩。我的 GitHub repo 上有 CSV 格式的编译数据。
注 :我使用了 维基百科 作为数据源来编制所有的联赛成绩。
该数据将用于生成每个欧洲联赛的积分分布。我将在下一节解释我为什么这样做。
注意:积分分布是每个联赛所有赛季累积的所有积分的总和。
我们所说的竞争性是什么意思?
首先,我认为我们必须定义什么是竞争力。在我参与的大多数辩论中,球迷们通常会谈论各种各样以前赢得过联赛冠军的球队来衡量竞争力。我不认为这是最好的指标,它过于关注赢家,而忽略了其他球队。我一直认为,我们需要一种更全面的方法来衡量竞争力。

欧洲足球联赛冠军(数据来源维基百科,图片由作者生成)
什么是更好的竞争衡量标准?
我提出了另一种衡量竞争程度的方法,一种只有数据科学家才会想到的方法。
前提很简单。我希望你们跟着我做一个思维实验,从这个问题开始:
一个完全竞争的联盟实际上会是什么样子?
我假设一个联盟,其中每个队都有 50%的机会赢或输,代表完全竞争。换句话说,每场足球比赛都是真正的“五五开”。
我们可以用这个“对半”联盟作为基准来衡量其他联盟的竞争力。这里的逻辑很简单,一个联赛越接近这个“完全竞争的联赛”,就越有竞争力。
注意:我们的“完全竞争联盟”没有任何平局。
我们将通过测量他们的分数分布和我们的五五联盟的分数分布之间的“距离”来比较这些联盟。分数分布最接近“完全竞争”联盟的联盟获胜。
我相信我建议的方法比大多数球迷的方法更好,因为我们考虑的是联盟中的所有球队,而不仅仅是冠军。
足球联盟是如何运作的
我简单解释一下足球联赛是怎么运作的。联盟中的每支球队都要打两次,一次主场,一次客场。赢了给三分,平了给一分,输了给 0 分。联盟通常从 8 月到 5 月进行一个赛季,一旦赛季结束,积分就会被记录下来,获胜者是总积分最高的球队。
模拟一个完全竞争的联赛
因此,我们已经收集了六个欧洲联赛 11 个赛季的数据,但是我们如何才能得到我们的完全竞争联赛的数据呢?没错,没有哪一个联赛的每场比赛都是对半分的,所以看起来我们好像被困住了,对吗?
好消息是,我们不需要对半联盟的实际数据,因为我们可以很容易地模拟一个。我用 Python 写了一个脚本来做这件事。模拟遵循以下简单步骤:
每个队互相打两次。
有 50%的胜算。
如果这个队赢了,给 3 分,否则给 0 分。
以上重复 11 季。
结果是每支球队在 11 个赛季的总积分,我们可以很容易地将它绘制成积分分布图。这将代表我们的“完全竞争联盟”。
注意:我运行了两个独立的模拟,一个是 20 支球队的联赛,另一个是 18 支球队的联赛。德甲和荷甲都有 18 支球队,所以比赛少,其他联赛都有 20 支。
如果你对我如何用 python 构建模拟不感兴趣,直接跳到文章的下一部分。
模拟五十比五十联赛(天真的方法假设跨赛季和比赛的结果独立)
我们如何测量点分布之间的距离?
我们将使用 Kolmogorov-Smirnov (KS)统计量,以快速而肮脏的方式完成这项工作。
Kolmogorov–Smirnov 统计量量化了样本的经验分布函数和参考分布的累积分布函数之间的距离,或者两个样本的经验分布函数之间的距离。 来源于维基百科
首先,我们应该陈述两个样本的 KS 检验的假设:
H0 :两个样本都来自同一分布的人群
H1 :样本来自不同人群
用简单的英语来说,这是什么意思?好吧,让我们想想我们要达到的目标。我们想衡量我们每个联赛的积分分布与我们完全竞争的联赛之间的差异。由于我们只收集了 11 个赛季的联赛数据,所以我们只有一个结果的样本(注:所有联赛的运行时间都超过了 11 个赛季)。通过 KS 测试,我们可以揭穿一个联盟完全竞争的说法,但我们只是没有足够大的样本量来实现这一点。拒绝我们的零假设(H0)将表明一个联盟不是完全竞争的。
我们将假设统计显著性为 5%。如果我们看到一个统计上显著的结果,那么我们将提出 Kolmogorov-Smirnov 统计量越大,积分分布之间的差异就越大,因此联赛的竞争力就越低。
迷茫?
好的,让我们先来看看这些分布,看看我们是否能发现任何差异。对于那些数学头脑不太好的人来说,这只是 11 个赛季积分的直观表示。黑色分布是我们完全竞争的联赛,彩色图是我们的欧洲联赛。竞争力可以被视为黑色和彩色分布之间的差异。差距越大,联赛的竞争力越弱。我们只是用 KS 统计来证实我们在这些图中看到的。

11 个赛季的足球联赛积分分布——黑色模拟五十比五十联赛(图片由作者提供)
好极了,告诉我竞争最激烈的联赛是什么
为了正确地做到这一点,我们需要快速重温我们的统计测试。我们的 p 值都低于 5%的显著性点,我很高兴地说,我们可以拒绝零假设。用外行的话来说,这仅仅意味着我们可以说我们的欧洲联赛不是完全竞争的。这应该是显而易见的,因为我们规定球队不能在我们的模拟联赛中抽签,但是我们知道他们可以,并且在现实生活中有数据。为了让你(在某种程度上)信服,下面是一次模拟运行的结果。

一次模拟运行的 Kolmogorov-Smirnov 统计和 pvalues(图片由作者提供)
现在让我们看看 KS 统计量的大小。我们知道它测量点分布之间的距离(粗略地说)。因此,我们可以用这个作为一个代理指标,哪个足球联赛是最具竞争力的。
但是我们还没有完成……
如果您能回想一下本文的开头,您会记得我们模拟了一个“完全竞争的联盟”,其中每个团队都有 50%的胜算。因为这是一个基于概率的模拟,每次我们运行它,联盟的积分构成都会略有不同,我们计算的 KS 统计量也会有所不同。我们通过运行 1000 次以上的迭代来解决这个问题怎么样?
下面是我用来做这件事的 python 脚本:
多次迭代运行模拟
这是结果图:

所有联赛的 KS 分布:越靠右,竞争力越弱(图片由作者提供)
解释很简单,KS 分布越靠右,联盟的竞争力越弱。
所以,这里有两个杰出的联盟。竞争最激烈的欧洲联赛(我们测试的)是:
🚀德甲(德)
…对于最没有竞争力的人来说:
🚜荷兰文
不幸的是,我们不能说任何关于英超、西甲、法甲的结论,KS 的统计数据有很多重叠。虽然意甲看起来是竞争最激烈的联赛的亚军。
结论:德甲最有竞争力
我在德国的读者会很高兴地知道,他们的国内足球联赛在竞争力方面名列前茅。我的发现明确地反映在这个国家足球的成功上。但了解足球迷的人,大多数不会同意。
使用 KS 统计实际上是一种测量分布差异的粗略方法,因为它只考虑了最大距离。作为这一研究的延伸,探索推土机的距离(EMD) 将是有趣的。EMD 估计“总”差异。
以下是关于这一主题的其他分析结果:
《看台报道》的⭐️An 文章认为西甲是最有竞争力的
⭐️A 关于他们如何踢球的统计不太严谨的文章暗示这是意甲
⭐️A 多维分析表明这是英超联赛
如果你想自己玩玩这个笔记,我在这里提供了它:足球分析
⭐️ 我喜欢通过分享我在野外的数据科学经验来帮助人们。如果你还不是会员,可以考虑订阅 Medium,从我这里获得更多有用的内容。
https://johnadeojo.medium.com/membership
用 Kolmogorov-Smirnov (KS)检验评估分类模型
使用 KS 检验评估类分布之间的分离

图片由拥有的摄影在 Unsplash 上拍摄
在大多数二元分类问题中,我们使用 ROC 曲线和 ROC AUC 分数作为模型如何区分两个不同类别的预测的度量。我在另一篇文章中解释了这种机制,但是直觉很简单:如果模型给否定类较低的概率分数,给肯定类较高的分数,我们可以说这是一个好模型。
现在这里有一个问题:我们也可以使用 KS-2samp 测试来做这件事!
科尔莫戈罗夫-斯米尔诺夫试验
两个样本的 KS 统计量仅仅是它们的两个 CDF 之间的最高距离,因此如果我们测量正类分布和负类分布之间的距离,我们可以有另一个度量来评估分类器。
这种方法有一个好处:ROC AUC 得分从 0.5 到 1.0,而 KS 统计值从 0.0 到 1.0。对于业务团队来说,理解 0.5 是 ROC AUC 的烂分,而 0.75“仅仅”是中等分是不太直观的。还有一篇预印论文【1】声称 KS 计算起来更简单。
如果你想更好地理解 KS 测试是如何工作的,可以看看我关于这个主题的文章:
实验
我们开始工作吧。
我的 github 上有所有的代码,所以我只讲述最重要的部分。
https://github.com/vinyluis/Articles/tree/main/Kolmogorov-Smirnov
作为一个例子,我们可以构建三个数据集,它们在类之间具有不同的分离级别(参见代码以了解它们是如何构建的)。

三个样本的散点图。图片作者。
在“好的”数据集上,类没有重叠,它们之间有一个明显的间隙。在“中等”上,有足够的重叠来混淆分类器。“坏”数据集上的重叠如此强烈,以至于这些类几乎是不可分的。
我为每个数据集训练了一个默认的朴素贝叶斯分类器。通过绘制直方图,我们可以看到每一类的预测分布。在 x 轴上,我们有一个被分类为“阳性”的观察值的概率,在 y 轴上,我们有直方图的每个箱中的观察值的计数:

类别分离直方图。图片作者。
“好”的例子(左)有一个完美的分离,正如所料。“中等”的一个(中心)有一点重叠,但大多数例子可以正确分类。然而,分类器不能分离“坏的”例子(右)。
我们现在可以评估每种情况的 KS 和 ROC AUC:
输出是:
Good classifier:
KS: 1.0000 (p-value: 7.400e-300)
ROC AUC: 1.0000Medium classifier:
KS: 0.6780 (p-value: 1.173e-109)
ROC AUC: 0.9080Bad classifier:
KS: 0.1260 (p-value: 7.045e-04)
ROC AUC: 0.5770
好的(或者我应该说完美的)分类器在这两个指标上都获得了满分。
中等的 ROC AUC 为 0.908,听起来几乎完美,但 KS 分数为 0.678,这更好地反映了类不是“几乎完美”可分的事实。
最后,糟糕的分类器得到了 0.57 的 AUC 分数,这是糟糕的(对于我们这些知道 0.5 =最坏情况的数据爱好者来说),但听起来没有 KS 分数 0.126 那么糟糕。
我们还可以检查每个案例的 CDF:

好、中和坏分类器的 CDF。图片作者。
正如所料,坏分类器在类 0 和类 1 的 CDF 之间有一个狭窄的距离,因为它们几乎是相同的。中等分类器在类 CDF 之间具有更大的间隙,因此 KS 统计量也更大。最后,“完美的”分类器在它们的 CDF 上没有重叠,所以距离是最大的,KS = 1。
数据不平衡的影响
以及数据失衡如何影响 KS 评分?
为了测试这一点,我们可以基于“中等”数据集生成三个数据集:
- 原,其中正类有 100%的原例(500)
- 一个数据集,其中正类有 50%的原始示例(250)
- 一个数据集,其中正类只有 10%的原始示例(50)
在所有这三种情况下,负类在所有 500 个例子中都不会改变。训练分类器后,我们可以看到它们的直方图,如前所述:

不平衡数据的类分离直方图。图片作者。
负类基本相同,正类只是规模变化。
我们可以使用相同的函数来计算 KS 和 ROC AUC 得分:
print("Balanced data:")
ks_100, auc_100 = evaluate_ks_and_roc_auc(y_100, y_proba_100)print("Positive class with 50% of the data:")
ks_50, auc_50 = evaluate_ks_and_roc_auc(y_50, y_proba_50)print("Positive class with 10% of the data:")
ks_10, auc_10 = evaluate_ks_and_roc_auc(y_10, y_proba_10)
输出是:
Balanced data:
KS: 0.6780 (p-value: 1.173e-109)
ROC AUC: 0.9080Positive class with 50% of the data:
KS: 0.6880 (p-value: 3.087e-79)
ROC AUC: 0.9104Positive class with 10% of the data:
KS: 0.6280 (p-value: 1.068e-17)
ROC AUC: 0.8837
即使在最坏的情况下,积极的类有 90%的例子,在这种情况下,KS 分数只比原来的少 7.37%。ROC 和 KS 对数据不平衡都具有鲁棒性。
多类分类评估
正如 ROC 曲线和 ROC AUC 的情况一样,我们无法在不将其转化为二元分类问题的情况下计算多类问题的 KS。我们可以通过使用“OvO”和“OvR”策略来做到这一点。
您可以在我的 GitHub 存储库中找到本文的代码片段,但是您也可以使用我关于多类 ROC 曲线和 ROC AUC 的文章作为参考:
结论
KS 和 ROC AUC 技术将以不同的方式评估相同的指标。即使 ROC AUC 是分类分离的最广泛的度量,了解两者总是有用的。
当我开始在一个使用 KS 的地方工作时,我才明白为什么我需要使用它。这更多的是一个偏好的问题,真的,所以坚持做让你舒服的事情。
如果你喜欢这个帖子…
支持我一杯咖啡!
给我买杯咖啡!
看看这个很棒的帖子
参考
[1] Adeodato,P. J. L .,Melo,S. M. 关于 Kolmogorov-Smirnov 和 ROC 曲线度量在二元分类中的等价性。
[2] Scipy Api 参考。 scipy.stats.ks_2samp。
[3] Scipy Api 参考。 scipy.stats.ks_1samp。
[4] Scipy Api 参考。 scipy.stats.kstwo 。
[5] Trevisan,V. 解读 ROC 曲线和 ROC AUC 进行分类评价。
利用 Elo 算法评价足球运球技术
使用 StatsBomb 开放数据集,用 Python 中的 Elo 算法对球员的运球技巧进行评分。

托拜厄斯·弗莱克特在 Unsplash 拍摄的照片
运球
这可能是游戏中最激动人心和最引人注目的一步。当战术失败时,它可以是一个强大的游戏规则改变者,或者当所有的传球路线都被覆盖时,在对手的防守中制造一个重大的裂缝。成功可以唤起观众的敬畏之声,而失败可能导致危险的反击。
尽管有它的光环,我们,足球分析社区,还没有想出如何正确评估运球。通常归结为成功运球的次数或其成功率。既不考虑舞台,也不考虑对手——他可能只是一个疲惫的前锋,或者全能的维吉尔·范·迪克。哪种情况下运球者胜算更大?
在我的帖子测量和可视化足球运动员的技能中,我展示了一种测量球员持球技能的标准化稳健方法——T4 提升指数。使用这种方法,我们可以评估足球中的任何有球动作。所需要的只是足够大的行动样本、概率以及它们的实际结果。然而,所有的动作类型都适合这种方法吗?
例如,投篮和传球等动作会受到角度、距离和球员在球场上的位置等空间因素的高度影响。因此,这种动作的成功概率可以用足够数量的事件和/或跟踪数据来建模。
不过运球就比较 不一样。的确,有些球场位置可能比其他位置更具挑战性,但这种一对一的决斗主要取决于球员本身——进攻者的运球技巧、防守者的一对一能力。
接受这个事实让我放弃了之前描述的基于机器学习的概率方法。相反,我寻找一种更加基于个人的方法。当我不知道我到底在找什么的时候,我会写下我知道的一切:运球(通常)是两个人的战斗,高度依赖双方的技术和配合。这也是一个运气、信心和动力的问题,也就是所谓的流动 [ 1 ],表明它可能会随着时间的推移而演变。
有了这些线索,我寻找具有相同属性的运动,两个立即出现在我的脑海中:网球和象棋。事实证明,国际象棋比赛是用 Elo 分数(读作“ay-luh”)来汇总的——那么,为什么不把这种非常有用的算法也用于运球决斗呢?

国际象棋游戏由 Elo 分数聚合,假设每个游戏中每个玩家的国际象棋表现是正态分布的随机变量。JESHOOTS.COM 在 Unsplash 上拍照。
考虑到 Elo 候选人,下一步是阅读更多关于它的内容。它适合这个问题吗?我可以用它来量化运球吗?
数据集
与之前的帖子一样,这项工作的数据基于 Statsbomb 开放数据集。在数据集的 Github 存储库上可以找到更详细的文档。
Elo 分数
Elo 系统最初是作为一种改进的国际象棋评级系统发明的,以其匈牙利创造者 Arpad Elo 的名字命名。美国国际象棋联合会(USCF)使用 Elo 分数对棋手进行分类。他们将分数≥ 2400 的玩家评为高级大师,将分数在 2200–2399 之间的玩家评为国家级大师,等等。
自其发明以来,Elo 评分已经涵盖了许多行业。它现在包括体育组织,如足球、美式足球、篮球、棒球、围棋、乒乓球;桌游如 拼字游戏外交;电竞游戏如 反恐精英:全球攻势 、英雄联盟、 FIFA (EA-Sports)、 Pokemon Go 中的围棋对战联盟;有趣的是,即使是约会应用,如 Tinder ,也使用 Elo 的变体来评估和匹配他们的用户。
那么,是什么原因导致 Elo 评分被如此大程度的采用呢?首先,Elo 分数假设随机个人游戏。对于国际象棋来说,意味着每场比赛中每个棋手的象棋表现是一个正态分布的随机变量。分布的平均值随时间缓慢变化。
此外,玩家的评级不依赖于一系列的行为,而只能从赢、平、输中推断出来。在 Elo 上下文中,一场比赛或一场游戏可以是任何产生赢家和输家的过程。
Elo 算法给我们的一个巨大好处是用决斗训练数据训练它的能力,并使用分数作为未来决斗的预测器。这可以为新的比赛策略打开大门。
评估运球的 Elo 分数
Elo 分数已经在足球比赛中使用。2009 年的一项研究发现,Elo 系统对足球比赛的预测能力最高,测试了八种不同的方法[ 2 ]。然而,这些用例与足球俱乐部有关,与球员无关。
以运球为例,我们可以把一次运球尝试定义为一场 决斗 ,进攻者和防守者相互较量。如果运球成功,或者进攻球员犯规,进攻方获胜。因此,他被认为在那场决斗中比他们的防守队员表现得更好。相反,如果进攻方输了,防守方被认为表现得更好。
为了减轻噪音和游戏的随机性质,我们将集合它们,而不是使用每个运球事件作为决斗。这种聚合可以应用于比赛、比赛阶段,甚至整个赛季。例如,如果利奥·梅西和维吉尔·范·迪克在一场比赛中有 5 次一对一的情况,我们可以将它们用作不同的决斗或者将它们聚合成一个平均结果。相应地,假设胜者全拿,梅西以 3 比 2 获胜将被转换为梅西的一场胜利。
然而,还需要另一个关键的调整。运球和一对一防守能力是完全不同的技能。不出所料,国际足联给梅西的运球得分打了 95 分,防守得分只有 34 分(站立铲球 35 分)。显然,我们必须将球员的进攻和防守表现以及得分分开。
为此,我们将使用最后一招:每个玩家将被代表两次:一次作为进攻者,和再次作为防守者。除了区分进攻和防守动作之外,这也能让我们找到在这两种技能上都很出色的球员。
调优 Elo 系统
我们可以调整一些超参数和细微之处,以更好地适应不同使用情形和不同数据规模的 Elo 得分。
调整更新机制
更新过程是任何 Elo 算法实现的核心。每个组织都采取了不同的路线来处理评级中固有的不确定性,特别是新评级,并处理评级膨胀/紧缩的问题。
不同比赛不同权重
足球运动员可以参加多种类型的比赛。比如里奥梅西,他在欧冠、英甲、美洲杯等赛场上踢球。由于每个锦标赛的难度不同,它对玩家评分的贡献应该相应地计入。
国际足联改编的 Elo 评分有 8 个权重,世界杯淘汰赛阶段的权重是一些友谊赛的 12 倍。表 1 显示了这些权重,它们涉及系统使用的 k 因子(参见下面的 Python 实现)。

表 1:来源:维基百科
对不同的评分等级使用不同的权重
美国国际象棋联合会(USCF)以前根据三个范围交错排列 k 因子(参见 Python 实现):
- 2100 以下玩家: k 系数32 用
- 介于 2100 和 2400 之间的玩家:使用了 24 个 k 因子中的
- 2400 以上玩家: k 因子16 用。
这种渐进主义减少了那些 k 系数低的人评级膨胀或紧缩的可能性,因为他们快速改变排名的能力有限。
处理评级通货膨胀和通货紧缩(长期)
评级膨胀意味着分数随时间递减;相反,“通货紧缩”表明平均水平在增长。就像最近几年同样质量的转会费飙升一样,当出现通货膨胀时,2500 的现代评级意味着低于 2500 的历史评级。在通缩期间,评级会随着时间推移而上升。这给比较不同时代的球员带来了挑战。
因此,人们可以采取一些行动来缩放评级,例如使用评级下限——所有玩家的最小值,保证玩家永远不会低于某个限制;或者对高评级用户使用较低的 k 因子,如表 1 所述。
处理数据偏差
StatsBomb 开放数据集本质上是二十年来数千个匹配的非常部分的样本的拼贴。这些比赛起源于许多比赛,各大洲,包括男女双方。更重要的是,它不是随机收集的(参见文档)。因此,数据非常稀疏,有偏见,并且往往集中在顶级球队和联赛,而不是足球运动员的真实分布。
为了减轻这种偏见,我使用了两场比赛,一次一场,在 StatsBomb 数据集中完全覆盖:2020 年欧洲杯和足总女超联赛(2018/2019–2020/2021 赛季)。图 1 显示了 2020 年欧洲杯上进攻者和防守者的决斗次数:

图 1:2020 欧洲杯期间球员参与运球赛事(决斗)的分布。每个球员代表两次:作为进攻者(蓝色)和防守者(红色)。这种分布是偏斜的,因为一些球员比其他人更倾向于运球/防守,这反映了一种模式而不是偏见。图片作者。
案例研究#1:探索 2020 年欧洲杯的 Elo 信号
在这项研究中,我们将使用 StatsBomb 公开数据集对 Elo 分数进行初步评估,作为 2020 年欧锦赛的评级系统,我们将把对决定义为运球尝试。在运球成功或防守犯规的情况下,进攻方获胜。在其他任何情况下,防守者都是胜利者。
决斗将被聚集到一个比赛级别:对于每一对玩家,(玩家 1,玩家 2),在比赛中获胜次数最多的玩家获胜。在平局的情况下,进攻者全拿走。
Elo 赛季被定义为比赛中的一个阶段。实际上,它引发了一个到目前为止已经达到的分数的季节性缩放。通过这样做,我们可以在比赛的后期进行运球,在某种意义上补偿固定的 k 因子的使用。固定的 k 系数在如此短暂的比赛中是有意义的,但在整个赛季的实施中用处不大。**
2020 年欧锦赛最佳运球者将是那些被评估为进攻者时 Elo 得分最高的人。同样,最好的一对一防守者是那些在被评估为防守者时 Elo 得分最高的人。
至于 Elo 参数,我用了以下常量:Elo 均值= 1500,宽度= 400, k 因子 = 32,赛季定义=欧洲杯 2020 比赛阶段,比例因子= 1.1。
Python 实现
- 定义决斗
决斗是基于四种类型的事件来决定的:“运球”、“运球过去”、“犯规”和“犯规获胜”。为了丰富我们非常小的数据集,我们将认为防守犯规是成功的运球尝试。当更大的数据集可用时,我们可能希望我们的定义更加严格。
*# CASE 1: A successful dribble, the 'Dribbled Past' event holds the loser (the defender) and the winner is the attacker.# CAS2 2: Unsuccessful dribble.
# Identified with 'dribble_outcome_name' == 'Incomplete'. The loser is the attacker; the winner is the player to be on the ball in the next event.# CASE 3: Foul
# Foul is a win for the attacker (subsequent event can be foul won or shot/pass from free kick
# Foul should not be in defence (not 'foul_won_defensive')
# If free kick - next touch is not made necessarily by the dribbler*
2。准备决斗
为了确保一致性,决斗被定义为玩家名字的排序元组。当左边的玩家获胜时,决斗结果为零,当右边的玩家获胜时,决斗结果设置为一。决斗 _ 数据将所有决斗存储在一个数据帧中。每个玩家代表两次:“DEF”和“ATK”。决斗的例子:
*duel (‘daniel wass:ATK’, ‘glen kamara:DEF’)
result 0
Winner daniel wass:ATK
Loser glen kamara:DEF
t_elo Group Stage
aggregation match_id
match_id 3788742
num_events 1*
3。Elo 参数
*mean_elo = 1500
elo_width = 400
k_factor = 32
end_of_season_scale_factor = 1.1*
4。设置
设置和参数初始化。作者代码。
5。Elo 三大核心功能
第一个核心函数在每场比赛后更新 Elo 分数;第二种方法是在给定两个 Elo 分数的基础上计算预期结果;最后一个是在每个阶段/赛季结束后计算分数,在我们的情况下,这发生在比赛的每个阶段结束时:小组赛、16 强赛、四分之一决赛、半决赛和决赛。
Elo 的三个核心功能—更新分数、计算预期结果和季末缩放。作者代码,大量基于维基百科 Elo 页面。
6。Elo 循环
这是代码的主要部分,是我们迭代季节和决斗的地方。
Elo 代码的主要部分——Elo 循环。作者根据 KASPER P. LAURITZEN 的《Kaggle 指南》编写的代码。
结果
这个数据集是完整的,但就时间而言很短。因此,分数非常接近平均值(低方差)。然而,即使持续时间如此之短,该算法也能够找到相干信号。
谁是最好的运球者?
如图 2 所示,丹尼尔·奥尔莫是 2020 年欧洲杯最佳运球者。他的“标准”数据是 37 次成功运球 26 次(成功率 70%)。亚军是拉希姆·斯特林(37/51 次成功运球——72.5%成功率),阿尔瓦罗·莫拉塔最后一个登上领奖台(18/24 次,成功运球——75%成功率)。与直觉相反,根据 Elo 排名,在前三个职位上,成功率实际上是下降的。但是我们必须记住,虽然 Elo 分数受获胜次数的影响,但它主要是关于你与谁的对决。
丹尼尔·奥尔茂击败过各种伟大的后卫,比如卡米尔·格利克、维克托·林德洛夫、莱昂纳多·博努奇、吉奥吉奥·基耶利尼(第二好后卫)、曼努埃尔·阿坎吉;以及一些非常优秀的中场球员,如马尔切洛·布罗佐维奇、丹尼斯·扎卡里亚、马尔科·维拉蒂、若日尼奥、曼努埃尔·洛卡特利和尼科罗·巴雷拉。
另一方面,斯特林赢得了大部分对中场球员。自然,中场球员比最后防线的球员能承担更多的风险。相应地,他们的 Elo 得分通常较低(记住,我们认为防守犯规是输)。因此,尽管斯特林战胜了吉奥吉奥·基耶利尼(第二最佳防守球员)和莱昂纳多·博努奇,但他的大部分荣誉都是在对阵若日尼奥、马尔科·维拉蒂、布罗佐维奇、莱昂·格雷茨卡、托尼·克罗斯和托马斯·德莱尼等低级别但技术高超的球员时获得的。
图 2:2020 年欧洲杯期间 Elo 运球得分排名前 20 的运球运动员。图片作者。
这是对我们方法的一个很好的健全性检查。然而,它不能算作模型评估,因为我们不测量任何东西。例如,阿尔瓦罗·莫拉塔的高排名可能是我对成功运球的软定义的结果,包括被判犯规的情况。
与基线比较
出于某种角度,我使用了相同的决斗定义,并提取了两个排名选项作为基线:运球胜率和总胜场数。表 2 显示了每个指标排名前 20 的玩家。
表 2:2020 欧锦赛期间运球胜率和总胜率排名前 20 的球员。
虽然胜率嘈杂且表现不佳,但胜率基线与 Elo 选择重叠得很好,甚至还捕捉到了其他优秀的运球者,如 Kylian Mbappe 和保罗·博格巴。老实说,我不是一个能够真正判断哪个排名更好的领域专家。现在,我们可以简单地承认一个信号的存在,并将这个健全性检查标记为完成,至少对于这个硬币的攻击面来说,它是运球。
最佳一对一防守队员
为了完成这个比喻,每个硬币都有两面;每个进攻者面前都有一个防守者,他的目标是对抗那些努力。从表面上看,基尔·沃克在 2020 年欧洲杯一对一的防守中表现最佳。排名第二的是传奇人物吉奥吉奥·基耶利尼。接下来是约尔迪·阿尔巴,乔丹·亨德森,以及 2021 年的官方金童,来自巴萨的佩德里。图 3 显示了 2020 年欧洲杯一对一比赛中,Elo 排名前 20 位的防守球员。
图 3:2020 欧洲杯期间 Elo 运球得分排名前 20 的一对一防守球员。
擅长两项任务的玩家
基尔·沃克(第 10 位最佳运球者,最佳防守者)和佩德里(第 7 位最佳运球者,第 5 位最佳防守者)是一些球员可以在两边都表现出色的第一个迹象。两个分数的简单平均是产生整体一对一能力模拟分数的一种方法(表 3)。当然,人们可以使用其他类型的手段,如调和平均值,来惩罚技能之一中的低值。
表 3:2020 年欧洲杯期间 Elo 运球得分前 20 名。总得分最高的比赛被定义为一对一比赛中进攻和防守的平均成绩。
案例研究 1#总结
显然,结果看起来是直观和合理的。最重要的是,与更传统的方法相比,Elo 可以捕捉不同的玩家。然而不同难道不就代表更好吗?是的,结果是有意义的,但是为什么它们比其他方法甚至基线更有意义?
评估结果是棘手的。理想情况下,我会测试 Elo 排名与预测运球结果的基线。不幸的是,这需要一个长期一致的数据集,以及更强的基线。
让我们看看案例研究#2 能为讨论增添什么。
案例研究#2:评估 Elo 预测——足总女超联赛
在这里,我们使用了 2019-2021 赛季之间发生的 327 场足总女超比赛,来自 StatsBomb 公开数据集。这一次,我们不关注排名结果,我们将考察用 Elo 分数预测运球对决未来结果的能力。
实验规范
数据集包含(根据之前陈述的决斗定义)17,012 次决斗,或者在应用匹配级别聚合时包含 13,409 次决斗。按时间排序,我把前 85%的数据作为训练集,剩下的 15%作为测试集。图 4 展示了足总女超数据集上进攻者和防守者决斗的次数。

图 4:2019–2021 赛季足总女超联赛期间球员参与运球事件(决斗)的分布。每个球员代表两次:作为进攻者(蓝色)和防守者(红色)。图片作者。
由于决斗的双方同等重要,我选择了标准的精度度量作为我的目标函数。考虑到游戏的高度随机性,在这样一个简单的实验中达到 80%左右将是非常惊人的。显然,任何低于 0.5 的结果都比随机差,因此是无用的。由于 Elo 得分为每场决斗产生一个概率(参见上面 Elo 的三个核心功能 ),所以也可以容纳损失(图 5)。
为了解决冷启动问题,我们将使用 Elo 平均值的缺省值来为看不见的玩家提供预测。
对于这个实验,我尝试了多种设置:动态/固定 k 因子,没有/有匹配级别聚合,以及多个赛季比例因子。Elo 赛季被定义为一个完整的足球赛季。在用作验证集的 15%的训练集上执行参数调整。
结果
使用动态 k 因子,将决斗聚集到一个比赛级别,并使用 1.02 的赛季比例因子,可以实现最佳性能。该配置在测试集上实现了 70.5%的准确度。
- 基线数量获胜准确率= 63.6%
- 每场比赛的基准胜率(取代了艰难的“胜率”基准)准确度= 66.5%
前五名运球手
***Name Elo score**
Lia Wälti 1695.29
Kim Little 1673.56
Christie Murray 1673.46
Samantha June Mewis 1672.15
Jackie Groenen 1668.71*
前五名一对一防守队员
***Name Elo score**
Esther Morgan 1574.17
So-Yun Ji 1568.19
Lois Joel 1554.72
Lia Wälti 1551.93
Jennifer Patricia Beattie 1545.78*
完整的 Elo 分数结果可在此处获得。

图 5:测试集上 Elo 算法预测的日志损失分布。图片作者。
考虑到我们使用的数据集和比赛的性质,这些表现相当显著,每 10 次运球中有 7 次被正确预测,比随机好 20.5%!
不可否认,Elo在预测能力上超过了目前的基线,但当经过多个联赛和长时间的训练后,将真正闪耀。在所展示的案例研究中,所有玩家只参加一场比赛,也就是说,难度相同。在这种情况下,Elo 算法失去了一些魔力。然而,在跨国比赛中,如欧洲冠军联赛或国际足联世界杯,它可以协同其所有球员得分,更重要的是比赛的困难,变成一个连贯的预测。
摘要
在这项工作中,我们使用了著名的 Elo 算法对足球运动员的运球技术进行评分和排名。这种方法的一个副产品是一对一防守能力的补充得分。在我们的第一个概念验证中,我们对 2020 年欧洲杯数据应用了该算法,对结果进行了手动评估。丹尼尔·奥尔茂被认为是锦标赛中最好的运球者。
有了明确的信号,我们开始对 Elo 算法预测未知决斗事件的预测能力进行定性(适当)评估。我们再次应用了 Elo,这一次是在更大的足总女超数据上。
在训练集上,Elo 的表现超过了三个基本基准——随机、 num_wins、和 wins per match ,预测决斗结果的准确率达到了 70.5%。
对未来的思考
这些初步结果可能表明,有了更大、更一致的数据集,我们将能够做得更好。此外,我们将能够丰富我们的分析,分析一段时间内的趋势,或者寻找被低估的球员。
由于 Elo 的预测性,另一个可能的研究方向是将其作为一种工具来优化阵容,球员的位置,并创建更智能的比赛。也就是说,找到最佳定位和战术,为我们的球员创造最佳环境,在一对一的情况下击败对手。
在足球决策的更广泛背景下,人们可以将 Elo 概率与预期传球(xP)和预期进球(xG)相结合,以创建数据驱动的决策助手。最后,这种方法可以用来评估前锋的射门和门将的一对一技术。而且在足球的范围之外,Elo 分数也可以为篮球运球工作。
参考
[1]中村,珍妮和米哈里·契克森米哈。“流量的概念。”心流和积极心理学的基础。施普林格,多德雷赫特,2014。239–263.
[2] Lasek、Jan、Zoltán Szlávik 和 Sandjai Bhulai。"足球协会排名系统的预测能力."国际应用模式识别杂志1.1(2013):27–46。
[3]铃木、古屋和大森和信。"国际足联/可口可乐世界排名在预测世界杯决赛结果中的有效性."足球科学5(2008):18–25。
通过分析我的 Fitbit 数据档案评估我的健康状况

在日常生活中利用数据分析
数据分析是一个专业领域。作为一名数据分析师,我的日常活动是强调信息技术的需求和重要性,挖掘信息以揭示我所工作的实体的洞察力。我太习惯于为了工作和商业利益而深挖数据,却没有意识到它也可以用来改善我自己的生活。
在过去几年中,健身追踪器已经成为一种新兴趋势。我在去年开始使用它,它对跟踪我的日常健身很有帮助,当我超过每日步数目标或看到锻炼后燃烧的卡路里数量时,会给我一些成就感。但也仅此而已。
日复一日,Fitbit 收集了如此多的数据,而我并没有深入研究。好的,我们看到一个 70 的睡眠分数;或者燃烧了 138 卡路里,但这意味着什么呢?就其本身而言,这些数据可能没有多大意义,但一旦我们将它们结合起来,并将其可视化,一些模式可能会出现,并向我们展示我们的日常行为和个人健康状况。
这篇文章是一篇个人探索性数据分析日志,旨在揭示我的日常行为和健康水平,并(希望)想出一些改善它们的方法。
数据处理和分析中用到的完整代码可以在 这个 Github 资源库 中找到。
获取 Fitbit 数据存档
我使用 Fitbit 作为我的健身追踪器,幸运的是,导出 Fitbit 收集的数据进行进一步处理非常容易。此支持页面编译了直接检索您最近的 Fitbit 数据或请求您的数据的完整存档的步骤;所有这些都来自你的 fitbit.com 仪表盘。
可以导出多种数据类型,从您的活动、社交和睡眠,甚至到女士的月经数据。

可以导出的 Fitbit 数据类型(从 Fitbit 支持页面检索)
我检索了我的完整存档,根据上面的数据类型,它位于多个文件夹中。每个文件夹都包含记录数据的 JSON 文件。

Fitbit 数据的样本 JSON 文件(图片由作者提供)
数据处理
由于数据大多以 JSON 格式出现,因此不同数据类别的结构略有不同。例如,有一个“心率”文件,显示每分钟捕获的心率;而“心率范围内的时间”显示每分钟的心率范围/类别。
另一方面,睡眠数据包含许多具有不同深度的字段。有一些字段直接归因于睡眠(即,开始时间、每个睡眠类别的持续时间等)以及作为比较的每个睡眠类别的三十天平均值。
这需要多次解析,具体取决于要研究的数据本身。在这种情况下,我关注的是身体活动数据、日常睡眠数据和锻炼活动数据。
我使用“glob”库进行递归数据导入。这是一个将多个 JSON 文件中的心率数据转换成 dataframe 的示例代码。
import globpath = r'Downloads/MyFitbitData/OliviaOlivia/Physical Activity/' # use your path
all_files = glob.glob(path + "/heart_rate-*.json")li = []for filename in all_files:
with open(filename) as data_file:
data = json.load(data_file)
df = pd.json_normalize(data)
li.append(df)df_heart_rate_all = pd.DataFrame()
df_heart_rate_all = pd.concat(li, axis=0, ignore_index=True)
数据处理和分析中使用的完整代码可以在这个 Github 库中找到。
数据探索:通过分析我的 Fitbit 数据,我了解了关于我的健康的 4 件事
1.正常的下限心率,这很好
Fitbit 每隔几秒钟提取一次我们的心率(bpm)值及其置信度。我按日期、日期和时间对数据进行分组,以找出我在一天中某个时间的总体心率。


心率数据的可视化(图片由作者提供)
心率在每分钟 55-125 次之间变化。在晚上我睡觉的时候,它保持在 55-75 BPM 的较低水平。与哈佛医学院共享的 60–100 BPM 的标准相比,这略低。这是一件好事,也有人说,处于该范围低端的静息心率可能会对心脏病发作提供一些保护。
这可能很大程度上是由我的锻炼习惯造成的。我经常在早上做一些家庭运动,这表现在我每天早上 7-9 点大约 100-125 次/分的高心率上。虽然进行一些有氧运动很好,但建议仍然将心率保持在目标和最大限度内。根据 CDC 的数据,最大心率是 220 减去你的年龄,适度体力活动的目标心率是最大心率的 64–76%。得知我的运动心率刚好在目标心率范围内,我感到非常欣慰。
2.更多的跑步可以燃烧更多的卡路里
这项运动的衡量标准之一是燃烧的卡路里数。根据 Healthhub SG 的说法,卡路里是你进行日常活动所需的能量,但消耗过多而不燃烧它们可能会导致体重增加和健康问题。
作为一个生活在新加坡的典型的一日三餐的人,我每天消耗大约 1200-1600 千卡。而我是一个一天大部分时间坐着的上班族,我一天需要 1425 千卡来源。我需要燃烧通过体育活动获得的额外的 100-200 千卡热量。
在这里,我将每分钟运动消耗的卡路里按活动类型进行可视化。

每分钟燃烧的卡路里的可视化(图片由作者提供)
看起来各有不同,但最低的是~ 1 kcal/分钟的瑜伽,其次是~ 3.9 kcal/分钟的步行,~ 4.6 kcal/分钟的健身,最终是~ 6 kcal/分钟的跑步。
也就是说,一个可以燃烧我过多卡路里消耗的组合是步行 20 分钟和锻炼 15 分钟。或者运行 25 分钟。现在我可以估计我的练习的最短持续时间:)
一些额外的可视化——总行走步数和行走活动消耗的卡路里之间的高度相关性。

总步数与消耗的卡路里的可视化对比(图片由作者提供)
3.请多睡会儿
关于健康的另一个重要信息是关于你的睡眠。根据睡眠基金会的说法,充足的良好睡眠可以降低某些疾病的风险,如心脏病、二型糖尿病、中风等。它也有助于防止疲劳,让你感到精力充沛,做有成效的工作。
我通常每天睡 6-7 个小时。这比成人每天 7-9 小时的推荐睡眠时间略低。我的 Fitbit 睡眠总得分(T8)也不太令人满意,只有 70-75 分,该得分来自睡眠持续时间、质量(深度睡眠)和恢复/躁动。当然,这是需要改进的地方。


总体睡眠持续时间和得分(图片由作者提供)
深度睡眠是睡眠旅程的重要组成部分。根据睡眠基金会的说法,这是身体真正放松的时候,生长激素被释放出来,并致力于建立和修复肌肉、骨骼和组织,以及免疫系统的功能。对于成年人来说,建议将我们总睡眠的 13–23%用于深度睡眠。

深度睡眠的百分比和持续时间(图片由作者提供)
对我来说一件好事是我的深度睡眠百分比已经在推荐的阈值内。因此,我只需要努力获得更长的睡眠时间。也许是想早点睡觉?
4.燃烧更多的卡路里并不能保证一夜好眠
虽然来自霍普金斯医学院的研究显示锻炼对睡眠质量有积极影响,但我的个人数据显示结果喜忧参半。我起草了每日步数和燃烧的卡路里与各种可用睡眠评分之间的相关性,结果发现它们之间没有显著的正相关性。
由于这是个人数据,我可能缺乏医学专业知识来评估它,可能有一些其他因素影响他们。也许是因为我的数据方差很小,因为我和我的日常习惯很一致?

运动与睡眠评分的相关性(图片由作者提供)
我应该做些什么来改善我的健康状况?
与一般数据分析一样,它是为了揭示见解,并最终根据这些见解采取行动,以实现预期目标。从这个数据探索中,我发现我的心跳率和消耗的卡路里已经达到了一个理想的推荐阈值,这太棒了。然而,我的睡眠评分仍然相当欠缺,可以改进,尤其是从持续时间的角度来看。这也有望增加深度睡眠的持续时间。
一些保持健康甚至提高健康的方法:
- 保持日常锻炼燃烧过量 150–200 千卡/天。也许 20 分钟的步行和 15 分钟的锻炼?或者跑 25 分钟?
- 增加睡眠时间。我知道我是一个早起的人(我早上有很多约会),所以唯一的办法就是早点开始睡觉。我想应该是晚上 11 点下班的时候了。
发现关于我自己的一些事实和见解是很有趣的,也有一些行动点来改善它。如果你有兴趣探索你的 Fitbit 数据,你可以参考我的 Python 笔记本,它包含了这个 Github 库中的全部数据分析。
利用众包按需评估搜索相关性
原文:https://towardsdatascience.com/evaluating-search-relevance-on-demand-with-crowdsourcing-4203b7058101
众包搜索相关性评估的 5 个内幕提示

作者照片
简介
当今最重要的电子商务任务之一是搜索相关性评估。您的在线市场依赖于搜索算法来改善客户体验,但评估搜索数据具有挑战性。
在这篇文章中,你将学到一些关于如何为搜索相关性项目获得一致准确的人工来源标签的内部技巧。
什么是搜索相关性评估?
搜索相关性评估将特定的搜索结果与用户的搜索查询进行比较,以查看它们是否匹配。在电子商务中,它用于改善搜索页面结果,选择相关广告,建议客户购买的配件等。

作者照片
标记的相关性数据集用于验证预训练的 ML 算法的结果,并为训练新版本的模型提供数据。
此外,它们可以用于在大量受众中测试新功能或不同的搜索方法,而无需部署到产品中或以任何其他方式影响实际市场。
为搜索相关性评估写扎实的说明
通常,标记项目的第一步是为评估相关性的人编写说明。确保你的说明易于阅读,并且没有任何相关背景或经验的人也能容易理解。阐明他们应该如何处理所有可能的类别和边缘情况。理想情况下,为每种情况提供例子。
另一个好的经验法则是“指令越短越好”——这在大多数情况下都很有效。
我们的实验还表明,搜索相关性类别在没有分级排序的情况下效果更好。我们建议不要按比例划分类别,例如(1–5),其中 1 表示不相关,5 表示完全匹配,而是使用独立的、无序的备选方案。良好类别的例子有完全匹配、可能替换、附件、和不相关 t。

作者截图
分解查询以提高准确性
下一个问题是做标记的人应该如何判断搜索查询。在某种程度上,如果某物是'可能的替代物'或'附件',这可能是主观的。
因此,最好将查询分解成更小的部分,如‘产品类别’,【产品特性】,或‘关键产品特性’。您可以在下面的图表中看到这个想法,其中有两个查询示例:“女式 M 码夏装”和“ PC 赛车视频游戏”。

作者截图
一旦您分解了您的查询,您就可以像这样定义规则:
如果类别不同且不是附件,则不相关
如果类别不同,但它是一个配件,那么它就是配件
如果次要特征不同,则可能更换
如果关键特征不同,则不相关
像这样清晰的陈述将有助于表演者根据你的指示决定他们应该为该项目选择哪个类别。
提供一个练习项目
一旦你清楚地定义了所有类的规则,你需要把这些信息传达给标签员。不幸的是,规则有时会变得有点复杂——尤其是搜索查询——我们确实说过指令应该简单明了。想象一下,在说明中你有这样一点:
如果类别不同,但它是一个附件,那么它就是附件
但是你说的从犯是什么意思清楚吗?你也许应该教育贴标机什么是你认为的附属品,但这将需要一个很长的解释。
有时,创建一个与主要说明分开的教育项目更好,在那里贴标签的人可以看到类别的详细解释。我们称之为“教育项目”,只有完成它的人才能接触到实际的任务。每当规则发生变化时,对表演者进行再教育也很重要。

作者截图
优化重叠以获得可靠的结果
当我们使用众包时,我们通常会将相同的任务交给几个人,以便更加确信我们得到的结果是正确的。

作者截图
那么有多少人应该完成同样的任务呢?完成任务的人越多,我们获得的信心就越多,但我们花的钱也就越多。解决问题的方法是使用动态重叠。如果任务复杂性差异很大,这一点尤其有效。
在动态重叠中,我们可以从让两个人解决同一个任务开始。如果他们的答案一致,我们可以假设答案是正确的。如果没有,我们应该增加重叠以收集更多的答案。
使用控制任务检查质量
控制任务,也称为蜜罐,帮助您确保贴标质量。这些是你知道答案的任务,并且以一种贴标签者无法区分的方式混合在普通任务中。您可以根据每位执行者对控制任务的回答来计算他们的得分。控制任务对于质量控制至关重要,因为它们可以帮助你过滤掉表现不佳的人。我们已经注意到控制任务的一些常见陷阱——尽量避免下表中列出的那些。
- 如果你的控制任务太复杂,可能会导致低绩效,因为没有多少人能够解决这些问题。
- 如果你的控制任务太少,你可能会很快用完。
- 如果控制任务没有正确的类别分布来反映您的数据集,您可能会计算出不正确的技能值。
- 如果您使用旧的控制任务,您可能会面临以下风险:所包含的链接已经过期,或者执行者已经看到控制任务,并且他们知道这些任务的答案。
总结
在本文中,我们研究了如何使用众包来评估搜索相关性查询。我希望你已经发现它很有用,并且能够在评估你自己的搜索相关性引擎时应用其中的一些技巧。
如果你对更多细节感兴趣,请观看由 Dmitrii 主持的这个演示。另外,如果你想了解更多关于数据标注管道的信息,请加入这个数据驱动的社区。
PS:我正在 Medium 和aboutdatablog.com上写文章,深入浅出地解释基本的数据科学概念。你可以订阅我的 邮件列表 在我每次写新文章的时候得到通知。如果你还不是中等会员,你可以在这里加入https://medium.com/@konkiewicz.m/membership。
下面还有一些你可能喜欢的帖子
* *
评估 Kruve EQ 意式浓缩咖啡杯
原文:https://towardsdatascience.com/evaluating-the-kruve-eq-cup-for-espresso-c0c28b1549f1
咖啡数据科学
一个数据驱动的,主观的口味评论
当 Kruve 第一次推出 EQ 咖啡杯时,我拒绝了。虽然杯子能改善口感的想法听起来很合理,但我认为这个成本不值得。后来我中了一套,我错了。

所有图片由作者提供
当我为我的书开始我的 Kickstarter 时,我结束了和 Kruve 的很多交谈。我一直在用他们的筛滤机进行断奏咖啡的拍摄。我在这方面已经有偏见了,因为我很感激他们的筛选员为我做的一切。他们很友好,给我的 Kickerstarter 打了折扣,还送了我一套他们的 EQ 咖啡杯。他们问我是否可以试一试。这是一个简单的实验,将它与我以前的海盗咖啡杯进行比较。

克鲁夫情商杯和我的海盗杯
最初的想法
我拉了一些配对的镜头,下面是我没有数据的初步想法。
赞成的意见
- 更好的品尝体验,因为宽口。
- 由于有鳍,我不用搅我的镜头。
- 拿杯子比用小把手更容易。
- 由于这种设计,我喝咖啡的时间也更长了。
骗局
- 杯子对于我现在的机器(Kim Express)来说几乎太大了。我无法在不丧失拍摄视频能力的情况下获得秤。
- 它不像我现在的杯子那样在侧面有标记,我用它来估计我的产量。
- 因为双层设计,它不允许浓缩咖啡冷却。我让我的镜头在大约 4 到 5 分钟内冷却到 47 摄氏度,但使用 Kruve,需要更长的时间。
- 这个杯子比我现在旅行用的杯子大了一点。
设备/技术
浓缩咖啡机:金快线
咖啡研磨机:小生零
咖啡:家庭烘焙咖啡,中杯(第一口+ 1 分钟)
预输注:长,约 25 秒
输液:压力脉动
过滤篮 : 20g VST
其他设备: Atago TDS 计、 Acaia Pyxis 秤、 Kruve 筛
绩效指标
我使用两个指标来评估技术之间的差异:最终得分和咖啡萃取。
最终得分 是记分卡上 7 个指标(辛辣、浓郁、糖浆、甜味、酸味、苦味和回味)的平均值。当然,这些分数是主观的,但它们符合我的口味,帮助我提高了我的拍摄水平。分数有一些变化。我的目标是保持每个指标的一致性,但有时粒度很难确定。
总溶解固体(TDS)是用折射仪测量的,这个数字结合弹丸的输出重量和咖啡的输入重量用来确定提取到杯中的咖啡的百分比,称为提取率(EY)** 。**
强度半径(IR) 定义为 TDS vs EY 控制图上原点的半径,所以 IR = sqrt( TDS + EY)。这一指标有助于标准化产量或酿造比的击球性能。
表演
在设计这个实验时,我本可以取出一个镜头,然后把它分在两个杯子里。然而,我的浓缩咖啡很浓,我担心这样做会扭曲体验。相反,我使用相同的拍摄准备和每对参数。
我拍了 10 双,趋势很快就明朗了。我总是先用 Kruve EQ 杯拍照,因为我无法将天平放在下面。然后我拉的下一个镜头,我用了海盗杯的秤,得到了类似的输出产量。
EQ 杯提升了我的味觉体验。TDS/EY/IR 没有变化,但我绘制它们是为了表明我拍摄的照片主要因为杯子而味道不同。或者说,因为杯中的内容物相同,所以味觉体验不同。
****
我分解了主要成分的平均口味分数。最大的变化是丰富和甜蜜的组成部分。0.5 的差异几乎不明显。

所以我把这些分数放到散点图上,富和甜肯定有明显的影响。然而,对于酸和苦,我没有注意到一个影响。

这种效果可能是因为它的作用类似于一个酒杯。我看了一对 t 检验,我一般喜欢样本多。然而,我非常喜欢这个奖杯,我想继续进行其他实验。该味道具有明显的味道差异。

我做了一些配对测试,发现 Kruve EQ 的味觉体验比我现在的杯子要好。不知道其他杯子会怎么样。然而,你可以在家里尝试这种体验,而不用买杯子,看看你感觉如何。如果你有一个大酒杯,试着把你的酒倒入杯中,旋转,然后从那里喝。你应该知道你是否也喜欢它,以及是否值得为改善浓缩咖啡的体验而花钱。
如果你愿意,可以在推特、 YouTube 和 Instagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以关注我在中和订阅。
我的进一步阅读:
仔细看看 T-test 的性能
原文:https://towardsdatascience.com/evaluating-the-performance-of-the-t-test-1c2a4895020c
一项模拟研究,调查不同情况下 t 检验的 I 型误差和功效。样本大小和方差如何影响双样本 t 检验的性能。

照片由 gerald 在 Pixabay 上拍摄
我们如何评估统计测试的表现?答案是蒙特卡洛模拟。模拟研究是一种计算机实验,涉及使用伪随机采样从概率分布中生成数据。模拟的关键优势在于我们可以评估统计方法的行为,因为我们可以控制测试的假设和“真相”。对于真实世界的数据,总体的参数是未知的。一旦我们指定了要在数据生成中使用的假设和参数,模拟就相当简单了。在本文中,我将引导您通过一个小的模拟研究,使用 R 来调查两个版本的 t-test 的性能。
本文使用的代码可以在这个 Github repo 中找到。
评估 T 检验的性能
双样本独立 t 检验是一种常见的统计检验,用于查看两组的均值是否不同。例如,它可以用来测试药物在治疗组和安慰剂组之间的效果是否不同。t 检验和其他统计方法一样,都有假设。如果假设被违反会发生什么?“常规”t 检验(也称为学生 t 检验)的假设之一是,被比较的两组的方差相等。有一种被称为韦尔奇 t-检验的适应性 t-检验,如果方差不能被假定为相等,它可能更可靠。
我们的模拟研究将调查两个独立样本的方差和样本量如何影响两个 t 检验的性能。我们的研究针对零假设,我们将查看类型 1 误差和功效来评估性能。这两个概念将在本文后面更详细地解释。
模拟要回答的问题
我们将着眼于小样本和大样本的属性,以及相等和不相等方差的影响。更准确地说,我们将研究以下场景:
- 方差相等时,组样本大小如何影响I 型误差
- 当方差不相等时,组样本大小如何影响I 型误差
- 当方差分别相等和不相等时,均值差异如何影响功率
首先,让我们回顾一下 t 检验的假设和公式。
t 检验的假设
- 数据是连续的。
- 数据服从正态分布。
- 这两个样本是独立的。
- 这两个样本是从各自总体中随机抽取的简单样本。
- 两个样本的方差相等。学生的 t 检验假设方差相等,而韦尔奇 t 检验不假设方差相等。
除了关于方差的最后一个假设外,学生的 t 检验和韦尔奇检验的所有假设都是相同的。在我们的模拟中,我们将生成满足所有前四个假设的数据。
“学生”t 检验
t 统计量的计算方法如下

其中混合方差是

有自由度的

韦尔奇 t 检验
韦尔奇检验不使用混合方差,而是直接使用每个样本的方差。t 统计量的计算方法如下

有自由度的

I 型误差和功率
我们调查的目标是零假设,我们将着眼于评估测试的性能测量是I 型误差和功效。两种版本的 t 检验评估均值相同的零假设,而不是均值不同的替代假设。

为了评估 t 检验在不同情况下的表现,我们将查看当空值为真和空值为假时,空值假设的拒绝率。也就是说,小于公称尺寸α的 p 值百分比= 0.05。这将分别给出 I 型误差和功率。下面的矩阵给出了假设零假设为真/假的情况下,统计测试的可能结果的概述。

I 型和 II 型误差矩阵(图片由作者提供)
大小或I 型误差是拒绝一个假备选项的真零假设的概率(假阳性)。换句话说,它是在实际均值没有差异的情况下错误地检测到均值差异的概率。我们可以通过生成具有相同平均值的两个独立样本的数据来估计测试的 I 型误差。通过计算拒绝率,我们估计测试的 I 型误差。如果大小等于或小于 alpha,则测试的显著性水平为 alpha。通常,显著性水平和大小是相同的,α的常见值被设置为 0.05。如果它们相同,这意味着该测试具有 95%的显著性水平。通常使用 0.05 的大小级别,这意味着我们预计,平均而言,由于随机性,大约有 5%的测试会出现假阳性。
功效是拒绝假零假设(真正)的概率。它给出了检测真实均值差异的概率。与 I 类误差一样,它可以通过产生两个独立样本来估计。在这种情况下,我们将使用不同的均值生成样本,运行 t 检验并计算拒绝率。理想情况下,我们希望测试的功效尽可能高,但至少高于 80%。如果功效是 80%,这意味着检测到平均值的真实差异的概率是 80%。
运行模拟的代码
我们希望创建一个函数来运行模拟,并允许我们通过指定其参数来指定我们想要研究的场景。
下面的函数采用形式参数 seed(用于再现性),S 表示迭代次数,对于两个样本,我们可以指定样本大小、平均值和标准偏差。该函数使用给定的参数值从正态分布生成数据,并执行学生 t 检验和韦尔奇检验。存储每个数据集和每个测试的 p 值,计算并返回无效假设的拒绝比例。比例就是大小或力量(取决于手段是作为相等还是不同传递的)。
场景 1(第一类误差):等方差,小样本和大样本
一旦我们有了一个模拟不同场景的函数,我们就可以运行我们的第一个模拟,通过指定两组的平均值相等来评估类型 I 错误(假阳性)。这种模拟不违反任何一种 t 检验的假设。模拟中的值指定如下。

R 中运行模拟的代码:
该表显示了结果,我们可以看到,当方差相等时,学生的 t 检验在小样本和大样本的名义水平上表现良好。韦尔奇检验在大样本上表现良好,但在小样本上表现不佳。

作者图片
场景 2(第一类错误):不等方差,小样本和大样本
在第二个模拟中,我们观察方差不相等时的第一类错误(假阳性)。这违反了学生 t 检验的假设,因此我们期望看到一些与 0.05 的目标水平不同的 alpha 值。韦尔奇检验并不假设方差相等,所以让我们看看这个检验是否比学生的 t 检验表现得更好。我们将模拟中的值设置如下。

R 中运行模拟的代码:
正如我们所料,当方差不相等时,学生 t 检验的 alpha 值开始与名义水平不同。对于最大样本量 200,它在目标上只有一个 alpha 值。它不同于其他样本大小的目标,越小的样本差异越大。除了最小样本量 3 之外,所有样本量的 alpha 值在目标值为 5%时,Welch 检验表现良好。

作者图片
侧记两组样本量不同时的情况。我还模拟了场景 1 和场景 2,但是将样本大小改为不相等。当方差相等时,样本量相等时和样本量不相等时,学生的 t 检验具有相同的结果。我们看到学生的 t 检验对不等方差很敏感,增加不等样本量会使检验表现更差。它甚至对大样本量> 200 表现不佳。Welch 检验对不等方差不敏感,在增加不等样本量时仍然稳健。然而,不管方差/样本量相等/不相等,威尔士检验在小样本量上表现不佳。
情景 3 和 4(幂):相等和不相等的差异
在第三和第四种情况下,我们比较第二个样本组中不同平均值的两个 t 检验的功效(真阳性),保持所有其他参数相等。在这些模拟中,我们将看到检测实际均值差异的概率是多少。在第一次运行中,我们指定两组的方差相等,在第二次模拟中,我们指定方差不相等。模拟的值如下。

模拟评估功率的 R 代码:
下图显示了两个 t 检验的功效,亮红色和蓝色显示方差相等时 t 检验的功效,暗红色和蓝色显示方差不相等时的功效。我们可以看到,学生的 t 检验和韦尔奇检验具有几乎相同的功效,两者方差相等时,方差不相等时。虽然,当方差不相等时,两个测试的功效都较低。

作者图片
这意味着,如果我们有两个方差不相等的样本,选择常规 t 检验或韦尔奇检验不会影响检验的功效。然而,由于我们已经看到,如果方差不相等,常规 t 检验的类型 1 误差高于韦尔奇检验,因此韦尔奇检验是优选的。但是应该记住,不相等的方差可能会导致较低的功率。(功效还取决于其他变量,如 alpha 级别、样本大小和效果大小。)
结论
模拟研究对于评估统计方法非常有效。在本文中,我们做了一个模拟来评估双样本 t 检验的 I 型误差和功效。模拟显示,假设两组的方差相等的学生 t 检验,在方差相等时,对于小样本量和大样本量都非常稳健。当不满足方差相等的假设时,t 检验表现不佳。
韦尔奇检验并不假设方差相等,我们可以从模拟中看出,当方差和样本大小不相等时,它比 t 检验更稳健。当方差和样本量相等时,它对小样本表现不佳。它对大样本量表现良好。
两个版本的测试给出了几乎相同的权力。从模拟中观察到的一个有趣现象是,均值差异越小,功效越低,特别是当样本大小仅为 20 时方差不相等。
如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。每月 5 美元,你可以无限制地阅读媒体上的故事。如果你注册使用我的链接,我会赚一小笔佣金。
https://medium.com/@andreagustafsen/membership
用提升、增益和十分位数分析评估模型的潜在回报
使用这三个工具来理解你的机器学习模型的有用性

图片由 Sung Jin Cho 在 Unsplash 上拍摄
在我们构建模型时,我们习惯于使用最多样化的指标来评估它们,如回归的 RMSE、R 和残差正态性,或二元分类的 BCE、F1 和 ROC AUC。
但另一个有用的技巧是测量模型的能力,以一种有用的方式对预测进行排序,为产生最大回报的情况提供更高的概率。
我们可以使用三种简单的技术进行这种评估:十分位数分析、累积收益和提升曲线。
本文中使用的所有示例都可以在我的 github 资源库中找到:
https://github.com/vinyluis/Articles/tree/main/Decile Gain Lift
十分位数分析
十分位数分析是一个有用的工具,可以用来理解我们样本中的前十分位数与其他十分位数相比表现如何。
作为一个例子,我们可以使用十分位数分析来查看我们排名前 10%的产品产生了多少利润。为此,我们首先从利润最高到利润最低对产品进行排序,然后将它们分成 10 组,每组包含 10%的产品:
# Divides into deciles
product_profit = product_profit.sort_values("profit_percent", ascending = False)
product_profit["decile"] = pd.qcut(product_profit["profit_percent"], q = 10, labels = list(range(10, 0, -1)))# Plots the deciles
sns.countplot(x = "decile", data = product_profit, order = range(1, 11), palette = 'Blues_r')

每十分位数内的产品数。作者图片
要查看每个十分位数的利润贡献,我们可以使用一个groupby聚合来合计十分位数内每个产品的利润贡献:
# Sums the profit contribution of the products
grp_product_profit = product_profit[["profit_percent", "decile"]].groupby("decile").sum("profit_percent").reset_index()# Plots the decile x profit graph
sns.barplot(x = "decile", y = "profit_percent", data = grp_product_profit, order = range(1, 11), palette = 'Blues_r')

每个产品十分位数带来的利润。图片作者。
我们很容易看到,在这个场景中,20%的产品贡献了超过 80%的利润。
当然,这是一个杜撰的例子。根据你分析的主题和变量,你可能会发现不同的比例,但想法是一样的。
累积增益曲线
另一种观察部分公众对企业或模型结果的影响的方法是使用累积收益曲线。
在前面的例子中,我们看到前 10%的产品带来了超过 50%的利润,如果我们考虑前 20%的产品,总利润将超过 80%。等效增益曲线如下:
现在让我们把这个概念带到机器学习中。
假设您有一个二元分类模型,该模型试图预测客户是否会购买产品(1)或(0)。
我们预计,该模型会将潜在买家归类为概率值较高的买家,而其他人的概率较低。在下图中,我们比较了三种情况下的增益曲线:差的分类器、中等的分类器和好的分类器。

累积增益比较。图片作者。
用于训练模型的数据是平衡的,50%的示例具有真实类别 1,50%具有真实类别 0。
好的分类器做得非常好,它在 50%的客户群中恢复了 100%的收益。这意味着它可以很好地将买家分类为“1”,而将其他买家分类为“0”,按照概率排序,所有买家都被分配到前 5 个十分位。再看看这个情节,因为现实生活中你再也看不到了。
这是因为现实生活中的场景对于分类器学习来说不是那么明显,所以“中等”情况更类似于我们将得到的情况。在这种情况下,分类器仍然可以在 50%的公众中正确地找到 80%的买家,这对于现实生活中的应用程序来说是一个好结果。
累积收益曲线有助于衡量我们在接触人们方面所付出的努力,以及我们可能期望从这一行动中获得的回报,因此被称为“收益”。有了这个分类器,如果我们试图将产品卖给前 50%的公众,我们将能够找到大约 80%的真正买家。
然而,糟糕的分类器在寻找正确的人方面表现不佳。这几乎和随机选择它们一样好。顺便说一下,随机选取的“增益曲线”是灰色的虚对角线。
绘制增益曲线
为了计算给定比例样本的增益,我们需要计算该子集内的阳性数,并除以整个样本内的阳性总数。

因此,如果我们想找到前 25%案例的增益,我们需要计算前 25%案例中有多少个阳性案例,然后除以样本中阳性案例的总数。
给定样本的真实类别和预测概率,下面的函数绘制增益曲线。
升力曲线
我们也可以用另一种方法来分析:升力曲线。
为了计算给定比例的样本的提升,我们需要找到这个子集内的阳性比率,并将其除以整个样本内的阳性比率。

因此,如果我们想要找到前 25%案例的提升,我们需要找到前 25%案例中阳性案例的比率,然后除以样本中阳性案例的总比率。和以前一样,数据也需要排序。
下面的函数绘制了任何给定样品的升力:
通过使用增益示例的相同分类器,我们可以得到以下提升图:

电梯对比。图片作者。
由于数据是平衡的,全样本的总阳性率为 0.5。由于仅包含正值的任何子集的正比率都将为 1,因此该数据集可能的最高提升值为 1 / 0.5 = 2。
这就是我们在好的分类器上看到的:高达前 50%的样本将具有提升值 2,这是最大可能值。显然,随着我们添加更多超过 50%的负面案例,提升值将下降,直到子集与样本匹配,提升值将为 1。
另一种解释是,这个部分的阳性病例将是整个数据集的两倍,这表明分类器为阳性人群提供了更高的概率。
中度场景再次更类似于现实生活:具有更高概率的子集将具有更高的提升值,并且当我们将人添加到该子集时,提升值将对应于作为子集一部分的更多负面例子而下降。
“1”上的恒定线表示随机选择。这个想法很简单:如果我们随机选择样本的任何一个子集,这个子集的阳性率应该等于整个样本的阳性率。
这就是为什么坏场景是混乱的,几乎在任何地方都接近 1。它不能正确地对人进行排序,因此值会上下波动,当有很多数据点时,这种行为会稳定在 1 附近。
结论
作为数据科学家,我们需要的不仅仅是理解我们的模型。我们需要了解它们可能对业务产生的影响。
我今天展示的工具并不是解决问题的神奇工具,但它们仍然可以帮助我们做出更好的决策。
如果你喜欢这个帖子…
支持我一杯咖啡!
给我买杯咖啡!
看看这个很棒的帖子
评估您的 NLP 系统
原文:https://towardsdatascience.com/evaluating-your-nlp-systems-2ba0d3f09f04
如何为您的 NLP 场景建立一套度量和评估

由 iMattSmart 在 Unspash 拍摄的照片
所有的组织都希望围绕实验和度量标准来构建,但是这并不像听起来那么容易,因为每个度量标准只呈现了现实的某个视图。通常需要收集正确的指标来充分描述您的数据。度量标准不仅是您所做工作的可测量的结果,而且它们实际上是您业务的杠杆。这是因为一旦您选择了一个指标,您就为它进行了优化。您对要优化的度量标准的选择对项目成功的影响通常比您为其进行优化的能力更大。
这篇文章将讨论机器学习生命周期中使用的一些常见指标和评估。我将描述的所有这些指标就像工具箱中的工具。机器学习从业者有责任选择正确的工具,并构建一套适用于关键用例的指标。
评估您的业务效用
每一个好的企业都会有一套你的 ML 应该影响的产品指标。仅仅有很好的模型质量指标是远远不够的,因为机器学习应该显示出对你的业务和客户的可测量的改进。拥有一个框架来将您的机器学习基础设施与您的客户联系起来是关键。我在我的另一篇博客文章中对此进行了更详细的讨论。不管你的模型看起来有多棒,它们必须为你的客户提供好处。
评估您的数据
在 NLP 的世界中,评估数据的质量通常是一项严格但重要的工作。在这一阶段,数据科学家会逐渐熟悉构建模型所需的知识。本节的其余部分将介绍一些计算技术,您可以尝试这些技术来获得关于数据质量的更多信息。
基本统计测量
测量和清除数据中噪声的最简单方法是返回到基本统计数据来评估数据集。去除停用词,理解语料库中的热门词、二元词和三元词可能会有所帮助。如果应用词汇化或词干化,可能会对数据中出现的常见单词和短语有更深入的了解。
主题建模
使用 LDA 或 LSI 等技术,从文本中提取主题是可能的。将这些技术与词云结合起来可能会让你对主题有更深入的了解。然而,选择主题粒度的超参数相当棘手,主题本身的可解释性也很混乱。然而,在正确的情况下,主题建模可能是一个有用的工具。
使聚集
与主题建模类似,有时可以使用聚类方法。如果您使用 USE 或 GloVe 嵌入您的语料库,您可以尝试构建语义集群。一些流行的聚类选项是 k-means 和 hdbscan。对于 k-means,您可能需要对您的数据有一些先验知识,以便对聚类数(k)做出有根据的猜测。Hdbscan 不要求您设置大量的集群,但是集群本身可能需要进行调整,如果引入更多的数据,集群可能不会一般化。在某些情况下,聚类可以帮助您了解数据中的噪声,甚至可以识别几个类。
人工数据评估
评估文本数据质量不是一个简单或自动的过程。很多时候,它需要通读数千行,并使用直觉来猜测质量。将上述方法的一些结果与定性分析相结合通常是衡量数据质量的最佳方式。
评估您的模型
这些是大多数数据科学家熟悉的指标,通常也是你在机器学习课上学到的内容。一大堆其他补充指标可以与其中的每一个配对,还有像模型校准这样的完整主题,它们可以为这一讨论添加细微差别。我不会在这篇博文中讨论这些话题,但是我会在这一部分的最后提到一个应该更多使用的度量框架。
准确(性)
准确性是最简单的衡量标准。它只是告诉我们,在我们的系统做出的所有预测中,有多少是正确的。如果你仅仅基于准确性来解释你的模型的有效性,会有很多陷阱,包括不平衡的数据集或高灵敏度的用例。精确度可以是一个很好的开始指标,但是它通常应该与其他测量技术相结合,以对您的模型进行适当的评估。在现实世界中,很少发现只有准确性才是足够的。
罗马纪元
AUC 代表曲线下面积。AUC 所指的曲线被称为 ROC(接收操作特性)曲线。ROC 曲线测量假阳性率对真阳性率。要真正理解 ROC 和 AUC 的本质,你可以参考我的老同事关于这个的精彩的博文。通常 roc 和 AUC 是在二元分类器的上下文中解释的,但是许多 NLP 场景通常有两个以上的意图。为了使 AUC 适应多类场景,你可以使用一对一或一对一技术。OvR 和 OvO 本质上将你的多类分解成许多不同的二元分类器。
精确度、召回率和 F1
许多 NLP 系统依赖于意图分类。这就是模型的工作是预测特定文本的意图。在分类器中,精确度、召回率和 F1 分数是衡量这些意图预测质量的最常见方法。Precision 告诉你所有你预测为正(TP + FP)的项目,有多少实际上是正的。回忆告诉你所有实际的阳性标记项目(TP + FN),你预测有多少是阳性的。F1 是精确度和召回率的调和平均值。
在多类场景中,您通常可以获得每个类的精度、召回率和 F1。sklearn有一个分类报告,可以在一行代码中计算所有这些指标。如果你的多类场景有太多意图,可读性可能会有限制。F1 是不平衡数据集问题的一个很好的度量,可以帮助对抗一些精度的限制。
混淆矩阵
混淆矩阵没有单点度量,您可以使用它来评估您的模型在看不见的数据上的表现。但是,它们提供了一种定性评估模型预测能力的好方法。通常,在聊天机器人中构建基于意图的模型时,您可能会遇到这些意图的定性问题。话语有多相似?意向 X 是意向 Y 的子集吗?混淆矩阵可以帮助您相对快速地分析和诊断数据问题。我经常把它作为我的每级精度/召回/F1 报告的伴侣。混淆矩阵的一个缺点是,如果你有太多的意图,可解释性就会受到影响。混淆矩阵还会导致定性分析,因此评估存在一定的偏差风险。
BLEU 评分
如果你正在处理语言翻译的情况,“双语评估替角”分数是一个很好理解的评估翻译质量的方法。分数由机器生成的翻译与专业人工翻译的接近程度决定。当然,如果你没有专业的人工翻译,你可以找到其他方法来获得高质量的翻译,以非常好地近似真实情况。
清单失败率
有时候,点度量是不够的。从论文 行为准确性:用清单 进行 NLP 模型的行为测试来看,清单是一种完全不同的、伟大的评估你的 NLP 模型的方式。本文深入探讨了对 NLP 系统中可能出现的常见行为进行单元测试的想法。
Checklist 本身是一个框架,在这个框架中,您可以用一种可量化、可测量的方式实际测试不同维度的 NLP 模型。它允许您测试您的模型对拼写错误、随机标点错误、命名实体识别问题以及现实文本中出现的其他常见错误的响应情况。本质上,使用像nlpaug这样的包或checklist包本身,您可以实际模拟您想要识别和测量数据中这些不同维度的失败率的测试类型。实施纸质清单的缺点是您必须自己生成文本,这可能是一个昂贵的过程。检查表也简单地描述了问题,但是解决检查表发现的问题可能是复杂的。
结论
这篇文章涵盖了一些流行的方法来衡量和评估你的 ML 生命周期的不同部分。这不是一个详尽的列表,因为有许多子领域和主题都有一组测量值。然而,我在这里列出的评估工具应该是构建一个整体的度量套件来探索 NLP 系统的不同维度的良好开端。
评估指标:离开你的舒适区,尝试 MCC 和 Brier 评分
Matthews 相关系数和 Brier 评分简介

Eran Menashri 在 Unsplash 上拍摄的照片
数据科学 是一个跨学科的领域,它使用科学的方法、流程、算法和系统从嘈杂、结构化和非结构化的数据中提取知识和见解,并将来自数据的知识和可操作的见解应用于广泛的应用领域。
机器学习 取而代之的是对计算机算法的研究,这些算法可以通过经验和数据的使用来自动改进。它被视为人工智能的一部分。
世界各地的数据科学家应用机器学习来建立能够预测未来事件的模型,将人/物体聚类到相似的组中,并识别意外的异常。
每个热情的数据科学家都知道,机器学习最令人兴奋的部分是选择能够解决感兴趣的问题(有监督或无监督)的最酷的算法。然而,机器学习中的一个关键部分是模型评估,有时被危险地低估了。
尽管如此,我知道大多数数据科学家知道如何正确评估一个模型,他们总是倾向于使用相同的(众所周知的)评估指标。本文的目的是给大家介绍两个不常用的分类评价指标:马修斯相关系数 (MCC)和布赖尔评分 (BS)。此外,我还将尝试强调将它们纳入数据科学管道的原因。
模型评估
模型评估是评估机器学习模型执行特定任务(如预测某种疾病的存在与否)的能力的过程。每当你在建立一个机器学习模型的时候,在某些时候,你必须对它进行评估,以确保得到好的结果。
从技术上讲,模型评估包括将您的数据集分为训练集和测试集,使用训练集训练您的模型(即估计模型参数),然后使用测试集应用一些评估指标。
当然,模型度量在无监督和有监督学习的情况下是不同的,对于有监督学习,在回归和分类问题的情况下也是不同的。然而,想法是一样的,非常简单:
不管你将使用什么类型的评估标准,当预测的结果尽可能接近真实的观察结果时,模型将表现良好。
由于本文的目标是向您介绍分类问题中使用的 MCC 和 Brier Score ,在下一节中,我将总结这一领域中一些最常用的评估指标。然后,我会向你解释为什么你真的应该尝试这两种方法来代替传统的方法。
通用评估指标
比方说,我们在训练集上训练了我们的 awesome 分类模型,我们希望评估它在给定新观察值(测试集)的情况下表现如何。在一个典型的二进制问题中,对于测试集的每个元素,你都有一个标签来说明该元素是正还是负(通常是 1 或 0)。你的机器学习模型为测试集中的每个元素提供了一个预测,表示它是正还是负。它会将每个元素分配到以下类别之一:真阴性(TN) ,真阳性(TP) ,假阳性(FP) ,假阴性(FN) 。理想情况下,模型应该最大化 TP 和 TN 的数量。在这种情况下,您的模型能够正确地将测试集中为阳性的元素分类为阳性,而不会产生假阴性,并且将测试集中实际为阴性的样本预测为阴性,而不会产生假阳性。
作为数据科学家,您知道要证明您的模型表现如何,您可以利用通用评估指标来综合您的模型在 TP、TN、FP、FN 方面的性能。当需要决定必须选择哪些指标时,大多数人会选择:
准确率:是正确预测(TP + TN)占被检验案例总数的比例。

精度范围在区间[0;+1],有+1 个最佳值和 0 个最差值。
精度:是模型做出的正面预测总数(TP + FP)中正确正面预测(TP)的比例。

区间[0;+1],有+1 个最佳值和 0 个最差值。
召回:是正确的正面预测(TP)占测试集(TP + FN)中正面样本总数的比例。

召回区间[0;+1],有+1 个最佳值和 0 个最差值。
F1 评分:是精度和召回率的调和平均值。

F1 的范围在区间[0;+1],有+1 个最佳值和 0 个最差值。
这四个指标通常一起使用,以获取模型正确预测实例的能力。 准确性 是最简单的选择,当数据集很平衡时(正面和负面的比例倾向于 50:50 或 60:40 的比例),获得关于模型性能的第一印象很有用。 精度 当我们想要非常确定我们的预测时,是评估度量的有效选择。 回忆 当我们想要捕捉尽可能多的正面时,是一个有效的选择。 F1 评分 试图将精度和召回率合成为一个单一的度量,常用于不平衡问题。
当然,这些并不是唯一的衡量标准。其中最著名的还有:
- 平衡精度:一种可用于不平衡数据集的精度。
- 加权 F1 评分:经典 F1 评分的演变,克服了 F1 评分的主要问题:对准确率和召回率给予同等权重。
- 对数损失:通常用于衡量模型的置信度,因为它是基于预测的概率。
而且,数据科学家倾向于使用大量的 ROC 曲线及其相应的度量 AUC。 ROC 曲线是显示分类模型在所有分类阈值下的性能的图形。 AUC 代表“ROC 曲线下的面积”,它测量整个 ROC 曲线下的整个二维面积(想想积分)。然而,给出 ROC 和 AUC 的完整介绍超出了本文的范围。
离开你的舒适区,尝试这些评估指标
做了这个初步的,但必要的介绍,让我们深入到本文的主要目标,即,介绍两个很少使用的评价指标: Matthews 相关系数和 Brier 评分。
Matthews Correlation Coefficient:它是 Pearson Correlation Coefficient 的一种特殊情况,测量真实类别与预测标签的相关性。

它的范围在区间[1,+1]内,其中–1 表示完全错误分类,+1 表示完全分类,而 MCC=0 基本上表示随机猜测。我真正邀请您在您的数据科学项目中考虑这一评估指标的主要原因是,只有当预测在所有混淆矩阵类别(TP、FP、TN、FN)中获得良好结果时,MCC 才会产生高分,与数据集中的正元素大小和负元素大小成比例。这个结果已经被 Davide Chicco 和 Giuseppe Jurman 在他们的论文中证明了。
此外,让我们考虑一个由 100 个元素组成的测试集,其中 90%的实例是阳性的(不平衡数据集)。现在,考虑这两种情况:
场景 A →你不知道不平衡类的概念,你建立了一个奇特的模型,它意外地预测所有元素为正。你得出了以下值:TP=90,FP=10,TN=0,FN=0。使用这些数字,准确度和精确度将等于 90%,F1 分数将为 94.74%,召回率甚至将等于 1,这些都是非常好的结果。假设 TN 和 FN 的数目等于 0,MCC 的分母也将是 0,因此其分数将是未定义的。这将是一个明确的信号,表明您的模型正朝着错误的方向发展,这个信号没有被其他指标捕捉到。
场景 B →你决定建立另一个模型,因为第一个模型总是预测积极的实例。现在,模型也预测负元素,你面临这样的情况:TP=80,FP=10,TN=2,FN=8。在这种情况下,准确率=82%,精度=88.88%,F1=89.89%,召回率=90.9%,MCC=0.08。即使在这种情况下,MCC 也是唯一能够捕获模型无法正确预测实例的指标。
要点 1:将 MCC 纳入您的评估流程!它将帮助您捕捉模型正确预测实例的真实能力。
Brier Score: 这是一个评估指标,用于确定预测概率得分的准确性。BS 是应用于预测概率的均方误差。

这里, N 是实例总数, fᵢ 是预测的概率, oᵢ 是事件在实例 i. 的实际结果
Brier 得分总是取 0(最佳值)和 1(最差值)之间的值,因为这是预测概率(必须在 0 和 1 之间)和实际结果(只能取 0 和 1 的值)之间的最大可能差异。Brier Score 类似于 Log Loss,因为它使用概率来衡量模型的可信度。然而,我个人认为 Brier 评分比 Log Loss 更能提供信息,因为它的公式更容易解释,其值的范围在 0 到 1 之间,而 Log Loss 没有上限。
作为一名数据科学家,我在医疗保健和制药行业工作,部署准确、可解释并且自信的模型至关重要。Brier Score 是帮助我了解模型是否自信的指标。如果我们考虑两个模型,它们给出相同的精度值,甚至更好,相同的 MCC 值,我应该最依赖哪一个?一个可能的解决方案是使用 Brier Score 来查看两个模型的置信度。让我们假设在你的测试集中你只有 3 个实例(请不要这样做),2 个正的和 1 个负的[1,1,0]。第一个模型预测关于正类的以下概率[0.6,0.55,0.4],而第二个模型预测[0.8,0.7,0.1]。第一个模型的 BS 等于 0.34,而第二个模型的 BS 等于 0.14。即使这两个模型具有相同的准确性或 MCC 值,通过评估 Brier 评分,我们可以得出结论,第二个模型应该是首选的,因为它更有信心。简而言之,如果你知道自己在做什么,Brier Score 会奖励你(即给真实结果一个高概率),惩罚你不知道自己在做什么(即给错误结果一个高概率)。
方法 2:在你的评估过程中包括 Brier 评分!它会帮助你了解模特有多自信。
最后但同样重要的是, scikit-learn 将 MCC 和 Brier 评分作为评估指标,而 PyCaret 仅支持 MCC,尚不支持 Brier 评分。
结论
Matthews 相关系数和 Brier 分数是数据科学家并不总是考虑的两个重要指标。MCC 可用作众所周知的指标(准确度、F1 分数、精确度和召回率)的替代,因为(a)它在不平衡数据的情况下更可靠,以及(b)只有当预测在所有混淆矩阵类别(TP、FP、TN、FN)中获得良好结果时,它才给出高分。相反,如果您想衡量模型对您的预测有多有信心,BS 应该是一个选项。由于 BS 的取值范围和公式,它比更著名的测井曲线损失更容易解释。
我真的希望你喜欢这篇文章,这是我的第一篇。让我们乐观地说,这是一个长系列的第一个。欢迎发表任何评论,甚至在 LinkedIn 上加我。另外,如果你想看我的下一篇文章,别忘了关注我。
用于发现风力涡轮机邻居的聚类方法的评估
数据科学技术在可再生能源数据分析中的应用

艾比·阿纳迪在 Unsplash 上拍摄的照片
简介
在本文中,我们将采用流行的聚类算法来发现风力发电场的涡轮机“邻居”。这里的术语“邻居”表示一组基于地理位置、风速、功率输出或其他环境和机械变量具有相似特征的涡轮机。
在题为“运行中的风力涡轮机的基于聚类的数据预处理”的前一篇文章中,我们详细介绍了在风力涡轮机数据预处理期间输入缺失值的基于聚类的方法。我们在该分析中仅使用了 KMeans 算法,这里的重点是评估用于此目的的其他可用算法。
在选择对涡轮机进行分组的“最佳”模型时,我们特别寻找能够创建最佳数量的聚类的模型,这些聚类的统计数据能够对缺失的风速值做出最佳预测。
我们使用均方根误差(RMSE)和平均绝对误差(MAE)来评估缺失的风速预测。此外,我们将使用平均绝对百分比误差(MAPE)来评估非零缺失值。
应当注意的是,如果在期望的时间戳没有集群中的涡轮机的可用数据,或者如果一些集群由太少的涡轮机组成,则基于集群的方法可能不会填充所有缺失的值。因此,我们还将报告由算法填充的缺失值的百分比。
有效地将风电场中的涡轮机分组可以减少数据分析所需的人力和计算工作量。
数据
我们在 Kaggle 上使用公开可用的龙源风力涡轮机数据,必要时使用引文。数据在此公开发布。这是 134 台涡轮机 245 天的运行数据集,分辨率为 10 分钟。还提供了涡轮机位置数据。数据集的标题如下所示:

作者图片

作者图片
数据准备
我们首先执行数据清理和过滤,以提取代表涡轮机正常运行条件的数据点。原始和过滤后的运行数据如下所示:

作者图片
此外,我们从清理后的数据中创建一个测试集来评估算法。测试数据包含 19940 行,占清理数据的 1.05%。
训练数据被进一步转换成类似数据透视表的结构,其中涡轮机 ID 作为行索引,时间戳作为列索引,风速作为值。

作者图片
为了结束数据准备步骤,在将数据传递到聚类算法之前,我们使用 Scikit-learn 中的标准定标器模块对转换后的数据进行定标。
集群建模
我们使用 KMeans、凝聚聚类(AGC)、高斯混合模型(GMM)和相似性传播(AP)算法基于风速创建涡轮机聚类,其中给定时间戳的聚类平均值用于预测(填充)该时间戳的缺失值。
此外,使用轮廓度量来选择重要的超参数,例如簇的数量和阻尼因子(在 AP 的情况下)。轮廓度量用于评估由聚类算法创建的聚类的质量,因此可以用于选择超参数。
这是最流行的聚类方法。它通过最小化类内距离平方和同时最大化类间距离平方和来创建类。它需要选择簇的数量作为超参数。

作者图片
从上图可以看出,该模型的最佳聚类数是 6。接下来,我们使用这个超参数来拟合模型。
这是一种层次聚类算法,使用链接距离递归合并一对样本数据聚类,使得同一聚类中的数据点更相似,而不同聚类中的数据点不相似。在 k 均值的情况下,需要选择最佳的聚类数。

作者图片
对于这个模型,最佳的聚类数是 10 ,我们使用这个超参数来拟合这个模型。
这是一个概率模型,它从训练数据中学习有限数量的高斯分布的混合,并采用期望最大化 (EM)算法来拟合高斯模型。在这种情况下,我们使用 KMeans 来初始化模型组件,并使用剪影度量来选择最佳的聚类数。

作者图片
GMM 算法的最佳聚类数是 10。
该算法通过在数据点对之间交换消息来创建聚类,以确定每个点对另一个点的吸引力,并且该过程迭代执行,直到实现收敛。尽管阻尼因子和偏好是该算法的关键超参数,但我们仅调整阻尼因子。

作者图片
最佳阻尼因子是 0.6 ,这导致了 13 簇的产生。
结果
使用每个算法的最佳超参数,我们将被认为是风速邻居的涡轮机分组。结果如下所示:

作者图片
在所研究的所有模型中,结果显示沿 X 轴两边的涡轮机比那些在地理上更接近但在其他涡轮机之间的涡轮机更可能是邻居。类似地,基于风速,靠近 X 轴中间的涡轮机更可能是邻居。
然而,这些模型在如何划分中间排涡轮机方面有所不同,这直接影响了所得集群的代表性,从而影响了集群准确预测成员涡轮机缺失值的能力。
与公园中间的涡轮机相比,边缘的涡轮机可能会遇到相似的最小风速障碍,因此获得的结果非常直观。
模型评估
基于测试数据,我们评估了每个聚类模型在根据聚类平均值预测给定时间戳的缺失风速值方面的性能。
此外,集群中的涡轮机数量越少,该集群填补因数据不足而导致的缺失值的能力就越低。因此,使用大量分类的分类模型可能会填充较少数量的缺失值。下表显示了结果汇总:
接下来,我们可视化模型的性能。

作者图片
相似性传播模型在 MAE 和 MAPE 方面给出了最好的性能,而 KMeans 模型在 RMSE 方面给出了最小的误差。一般来说,所有考虑的算法的性能是可比的,并且可以被组合以填充更多的缺失值。
由于误差的初始平方,与其他度量相比,RMSE 度量对异常值更敏感,并且对大偏差应用更高的惩罚。然而,MAE 和 MAPE 是模型性能的更直观的度量。
下面显示的预测缺失值示例显示了模型和地面真实情况之间非常好的一致性。

作者图片
现在,我们将插补误差可视化。

作者图片
所有算法的插补误差都表现良好,并且围绕零误差位置对称。
Jupyter 实验室笔记本包含了本文中使用的所有 Python 代码,可以在这里找到。
结论
我们探索了四种不同的聚类算法来创建涡轮机组。这些涡轮机组如果被优化创建,可以提供关于成员涡轮机的有用信息,这些信息可以用于在数据预处理期间填充缺失值。
Kmeans 模型非常直观,可以填充大部分缺失的值。然而,基于 MAE 和 MAPE,诸如相似性传播方法的其他方法给出了更好的性能。因此,可以将这些算法的结果结合起来,以获得更大的好处。
我希望你喜欢阅读这篇文章,直到下次。干杯!
什么更有趣?你可以通过下面我的推荐链接订阅 Medium 来获得更多我和其他作者的启发性文章,这也支持我的写作。
https://aolaoye.medium.com/membership
不要忘了查看在可再生能源领域应用最新数据科学原理的其他故事。
参考文献
周军,陆,徐,肖,苏,吕军军,马,窦,(2022)。SD wpf:2022 年 KDD 杯空间动态风力预测挑战数据集。 arXiv 。https://doi.org/10.48550/arXiv.2208.04360
深度翻译质量评估——即使是谷歌也没有做对
原文:https://towardsdatascience.com/even-google-does-not-get-it-right-a9685f7f63c9
如何正确进行深度翻译质量评估——从衡量标准到常见注意事项

作者图片
十年前,学习一门新语言是一件艰难的事情。逐字翻译文本需要很长时间——你首先必须在字典中查找单词开头的字母,然后搜索单词本身,然后选择最符合句子意思的翻译。
今天,学习一门新语言变得容易多了。我们有太多的辅助工具供我们使用。显而易见的“去”资源是谷歌翻译。然而,即使是 google-translate,使用深度的、训练有素的神经网络也不是完美的。在互联网上有成千上万的例子,当它得到的翻译非常错误。纵观这些例子,我们似乎离完美的机器翻译(MT)工具还有很长的路要走。那么为什么设计一个如此具有挑战性呢?
翻译是一个人工智能不完整的问题——为了训练一个模型,以人类水平的准确性将翻译从一种语言映射到另一种语言,它需要纳入大量的社会,文化和历史信息,这意味着模型应该开发一种普遍的智能。
例如简单的问候“你好吗?”(法语 ) 根据上下文可以翻译成、【你好】、【瓦格万】、【你好】、或、。考虑另一个例子——语言随时间的变化。如果给我们一本 19 世纪的小说,我们会期望模型使用适当的词汇——选择兴奋而不是炒作。
开发能够产生适当的、人类质量的翻译的机器翻译模型的更大挑战,可以通过增加模型的复杂性、在更大的数据集上训练或引入语言偏见来解决。但是,我们手上有多种模型,我们如何选择一个实际上运行良好的模型呢?我们将使用 MT 质量评估模型。很长一段时间以来,我们对 MT 模型的自动评估都是错误的,直到最近这些问题才开始自我纠正。
本文总结了机器翻译模型的自动评估指标。我将首先讨论传统的基于字符串的指标(那些实际上迫使我们犯错的指标),质量评估的深度模型,最后讨论如何确保我们使用正确的参考来比较机器翻译模型。
机器翻译质量评估
评价翻译模型效果如何,最直接的方法就是请有经验的译者来评判。然而,这是非常昂贵的——你要花多长时间彻底检查一份两页的文件?要对翻译质量做出可靠的评估,两页纸是不够的。
对于那些感兴趣的人,我在关于成对比较作为质量评估和数据集合并用于评级和排名分数的协议的文章中谈到了主观(基于人的)评估的挑战。
自动化质量评估模型可以显著简化模型评估过程,其目的是评估参考翻译和输出翻译之间的重叠。
为了获得可靠的分数,我们需要一个与人类判断密切相关的好的度量标准,以及一组高质量的参考翻译,该度量标准将根据这些翻译进行基准测试。因此,让我们看看如何才能把两者都做好。
韵律学
通常,基于字符串的模型和基于深度学习的模型是有区别的。它们各有利弊。
字符串度量
衡量标准最简单的形式是参考译文和目标译文中出现的单词数。有数百万个理由说明这个指标不是一个好主意——想想同义词!
BLEU 是一种改进的精度指标,适用于 NLP 目的。其中精度是匹配参考的单词数除以候选句子中的单词总数。
机器翻译经常过度生成“合理的”单词,导致不太可能但高精度的结果(例如生成一个只包含冠词“the”的句子)。为了减轻这个问题,解决方案是清楚的:在匹配的候选单词被识别之后,参考单词应该被认为是耗尽的。因此,BLEU 修改了单字精度。
为了计算它,我们取一个单词在任何单一参考译文中出现的最大次数;将每个候选词的总计数除以其最大引用计数,将这些被剔除的计数相加,然后除以候选词的总(未被剔除)数。
例如一个候选翻译:“The The The The The The The”,带参考 1: “猫在沙发上”**参考 2: “沙发上有只猫。”冠词“the”在参考文献 1 中出现两次,在参考文献 2 中出现一次,我们取最大值——两次,候选词中“the”出现的总次数为七次。因此简单的 BLEU 分数是 2/7。
完整的 BLEU 分数结合了句子长度和各种 n-gram(一系列连续的 n 项)长度:

其中,BP 是简短惩罚,括号中的项是修改后的N-克精度、 pn 的几何平均值,使用N-克长度达到 N 和正权重 wn 之和为 1。
胭脂 是对 BLEU 的一种修改,然而在 Bleu 测量精度:多少单词(和/或 n-grams)在机器生成的摘要中出现在人类参考摘要中。胭脂措施召回:机器生成摘要中出现人类参考摘要中的单词(和/或 n-grams)的多少。
ChrF 是字符 n-gram F-score(精确度和召回率的调和平均值)。与基于字符串的度量类似,它测量翻译和引用之间的重叠,但是与其他度量不同,它对短字符序列(n-grams)而不是单词进行操作。
这种基于字符的形式有几个优点,首先,它降低了对句子符号化的敏感性;第二,它对拼写错误的单词给予部分奖励。
CHRF emtric 的完整形式是:

- CHRP:假设中的 n-grams 在引用中有对应项的百分比;
- CHRR:在参照中出现在假设中的字符 n-grams 的百分比。
- β是一个控制权衡的参数,通常设置为 1。
预培训指标
基于字符串的度量对于简单的 MT 模型来说已经足够了。现代机器翻译的神经方法产生了高得多的翻译质量,这通常偏离了语言之间单调的词汇转移。因此,需要更复杂的机制来评估翻译质量。
深度学习模型在这里有所帮助。典型地,这些或者是端到端的,或者是比较嵌入空间中的句子标记,而不是作为基于字符串的度量直接计数。
https://arxiv.org/pdf/1904.09675.pdf

用于计算 BertScore 的管道。图片由张天翼提供。
BertScore 使用由预训练的 Bert 模型产生的上下文嵌入,并使用余弦相似度计算匹配,可选地使用逆文档频率分数加权。
BLEURT与 BertScore 类似,BLEURT 使用 Bert 作为主干,然而与它不同的是,BLEURT 是一个端到端的模型,它不使用手工制作的相似性度量,而是直接回归到分数。由于如此大的模型需要大量的数据来训练,而来自人类评分者的机器翻译数据很少,BLEURT rests 使用合成数据集和 BertScore 作为真实分数的代理来进行预训练。在微调阶段,使用真实数据集和翻译者排名来训练模型。

彗星估计模型。图片由 Ricardo Rei 拍摄。
与 BertScore 和 BLEURT 不同,COMET 在评估过程中也包括源语言的输入。该模型允许在没有模型输出(假设)的参考翻译的情况下进行预测。该模型被称为 COMET-src。


棱镜框架。图片由布莱恩·汤普森
Prism 是一个句子的、序列到序列的解释器,用于强制解码机器翻译输出,并根据相应的人类参考对其进行评分。通过用特定的系统输出“查询”模型,我们可以使用模型分数来测量系统输出解释人类参考翻译的程度。该模型不基于任何人类质量判断进行训练,这在许多领域和/或语言对中是不可用的。
Prism 接受多语种平行示例的培训,例如“Ciao amico”翻译成法语是“Salut l'ami”。在评估时,该模型在零触发模式下使用,以根据其相应的人类参考对 MT 系统输出进行评分。例如,以人类参考“Hello world”为条件的 MT 系统输出“Hi world”被发现具有令牌概率[0.3,0.6]。
Prism 考虑了两种结合模型中记号级概率的方法—序列级对数概率(G)和平均记号级对数概率(H):

并且有和没有参考评估的最终棱镜得分由下式给出:

度量比较


车型对比。图片由汤姆·科米提供。
微软最近的一篇论文(To Ship or Not Ship:机器翻译自动度量的广泛评估)对数百种语言对的度量进行了比较。
基于他们的发现,作者建议使用以下自动度量的最佳实践:
- 使用预先训练的指标作为主要自动指标;有彗星推荐。对不支持的语言使用基于字符串的度量标准,并将其作为辅助度量标准,例如 ChrF。不要用 BLEU,它不如其他指标,已经被过度使用了。
- 运行成对显著性检验,以减少随机抽样变异造成的度量误判。
- 在公共测试集上发布您的系统输出,以允许比较和重新计算不同的指标分数。
参考数据
为了构建训练数据集,有经验的翻译人员将一种语言的文本映射到另一种语言的文本。为了确保翻译的真实性和准确性,翻译人员通常不能从框中复制文本,以防止使用自动翻译软件。孤立的句子评估是有效的,但不能惩罚上下文不匹配,因此通常会向译者展示更大上下文中的句子。通常,多个译者会收到相同的文本,并选择最相关的翻译。
除了所有这些机制,还有一个更强大的机制阻止翻译的准确性——人类的懒惰。
谷歌最近的一篇论文( BLEU 可能有罪,但参考文献并非无辜)显示,用于评估机器翻译模型的常用参考文献的质量远非完美。主要问题是翻译腔映射——带有源语言假象的句子,例如特定的词序和词汇选择。也许这并不奇怪,但是像 BLEU 这样对字符串值进行操作的度量标准会鼓励模型学习映射到翻译腔语言。
很容易看出,如果我们奖励翻译腔映射的一致性,劣质模型将如何被奖励和选择用于开发。
从谷歌的论文中可以看出,标准参考翻译可能会对改善机器翻译的机制产生偏见。先前的研究可能错误地丢弃了基于这些偏见的技术。为了纠正这个问题,他们建议重新措辞参考——这有助于改善自动评估指标的机器翻译模型选择,现在将惩罚多样化和相关的映射。
摘要
随着时间的推移,语言的变化和多种可能的翻译使机器翻译成为一个难以解决的问题。随着机器翻译模型的发展,质量评估模型也应该发展-正如我们所见,BLEU 足以进行简单的翻译,但是,它无法捕捉当前深度学习模型所实现的丰富的语言结构。
因为即使对专业口译员和笔译员来说,获得正确的翻译也是非常具有挑战性的,所以我们在使用参考翻译对模型进行基准测试时需要格外小心。然而,有专门的机制来解决这个问题。
在本文中,我介绍了以下基于字符串的指标:,胭脂,ChrF;以及基于深度学习的度量:慧星、贝特斯科尔、布莱恩特、棱镜、 ESIM 。****
如果你喜欢这篇文章,请与朋友分享!要阅读更多关于机器学习、数据科学、计算机视觉和图像处理的内容,请点击订阅!
我错过了什么吗?不要犹豫,直接给我留言、评论或发消息吧!
平均分割增加了 A/B 测试的能力
原文:https://towardsdatascience.com/even-split-increases-power-of-a-b-tests-7cc2b8cb182a
您的实验在平均分割的情况下更快达到统计显著性

班农·莫里西在 Unsplash 拍摄的照片
当我将更多流量分配给处理桶时,实验是否会更快完成?我应该通过分配更少的用户到治疗桶来最小化风险吗?这些是数据科学家问的常见问题。这篇博文分享了我们关于设置实验样本比率以更快达到统计显著性的研究。
你的实验有一个更高的平均分配能力
一般的建议是以平均分配的方式运行实验——50%的用户在对照组,50%的用户在治疗组。因为平均分割导致最佳的方差减少,并因此导致最高的统计功效。换句话说,当实验变量(例如,对照、处理)大小相等时,实验更快达到统计显著性。
不同采样率之间的功率比较
我们针对四种不同的样本比例绘制了关于总样本量(对照样本量+治疗样本量)的功效函数:5%对照用户和 95%治疗用户,15%对照用户和 85%治疗用户,25%对照用户和 75%治疗用户,甚至分裂(50%对照用户和 50%治疗用户)。对于每个分割设置,我们计算不同总样本量的 t 检验的功效。我们用 t 检验来说明,因为它是 A/B 检验中最广泛使用的假设检验。
图中的每条曲线描绘了功率如何随着总样本量的增加而增加。我们通过改变采样比率生成了四条曲线。所有其他配置都是相同的——总样本量、两组平均值之间的差异、标准偏差和所需的假阳性率。
图 1 说明了在平均分配的情况下, t-test 的功率最快达到所需的统计功率 (80%的功率由绿色钻石引爆)。换句话说,为了达到期望的功效,事件分裂与备选分裂相比需要最小的样本量。

图一。具有均匀分割的 t 检验以最小的样本量达到期望的功效
重现剧情的代码。
由于最佳方差减少,平均分割具有最高功效
以 t 检验为例,其中检验统计量的分子是桶 1(如处理)的样本均值和桶 2(如对照)的样本均值之差,这是对效应大小δ的估计——两个总体均值之差。在分母中,s 和 n 分别表示标准差和样本量。

给定相同的要素影响(δ),较小的分母会产生较大的正 t 统计量或较小的负 t 统计量,从而产生较小的 p 值。t 统计量的分母对应于δ的标准误差的估计值。假设第一桶(s₁)的标准偏差与第二桶(s₂)的标准偏差相似,这在实践中通常是正确的,我们如何将总用户(n₁+n₂)分配到两个实验组(对照组和治疗组)会显著影响分母的值。
在假设方差相等的情况下,t 统计简化为

其中,在分母中,s 表示控制或处理桶的标准偏差,c 是用户总数,p 是分配给处理桶的用户总数的比例。用户分配仅通过 p 影响 t 统计量,当 p 等于 0.5 时,p(1-p)最大。
例如,当 1%和 99%的用户分别位于桶 1 和桶 2 中时,t 统计的分母比均匀分割时的分母大五倍以上。因此,相同的实验在平均流量分配的情况下会更快达到统计显著性。
不平等分配的神话被揭穿
误区 1 :我需要保持低治疗桶(例如 5%)。因为我不确定该功能的影响,也不想让太多用户接触一个有风险的功能。
假设你担心新特性的负面影响。在这种情况下,更好的做法是设置一个较低的暴露率(符合实验条件的用户总数的百分比),同时仍然平均分配流量(50%治疗,50%控制)。一旦你从早期实验读数中确认特征风险,增加暴露率。当分配给一小群用户一个糟糕的体验时,要花更多的时间来完成实验。这些用户更有可能因为长期暴露在负面体验中而流失。
误区二:我需要快速完成实验,所以我必须保持治疗桶在一个高百分比(比如 95%)。
为了测试治疗是否与对照组有统计学差异,我们需要测量对照组和治疗组的目标度量的方差。图 1 表明,如果我们分配给控制或处理桶的流量太少,将需要更长的时间才能达到统计显著性。
何时运行分配不均的 A/B 测试
根据业务需求,有不平等分配的 A/B 测试。一个典型的例子是,我们需要尽快推出一个特性,而不管这个特性对业务度量的影响。这些特性通常是战略赌注、政策变化,或者那些无疑会让我们的用户受益的特性,即使可能会影响财务指标。在这些情况下,A/B 测试更像是研究特性影响的维持实验,而不是决策工具。
结论
Wish 的实验团队做出了大量努力,将我们的实验平台提升到一个新的水平。虽然严谨的平台奠定了基础,但良好的实验设计对于实现 A/B 测试的力量至关重要。均匀分割是一种简单而有效的改进,可以提高实验的统计功效。
感谢
我们非常感谢 Pai Liu 的支持以及 Pavel Kochetkov、Lance Deng、和 Delia Mitchell 的反馈。
数据处理流水线中事件驱动的可伸缩性
原文:https://towardsdatascience.com/event-driven-scalability-in-data-processing-pipeline-aa4417d7950a
基于要处理的数据量,基础架构和成本方面的架构主干可自动扩展

爱德华·豪厄尔在 Unsplash 上拍摄的照片
构建数据处理管道是最常见的问题陈述之一,为此,您可能已经编写了一些小脚本,或者基于数据的数量和频率构建了一个成熟的可伸缩系统。在本文中,我们将讨论事件驱动的可伸缩性的思想,它是成本优化的主干,只需要最少的开发和操作。
为什么要建立一个事件驱动的、可伸缩的数据处理管道?
当与初创公司合作或建立一个团队项目或个人项目时,需要一个数据处理管道,总会有成本的限制。有两种方法可以解决这个问题:
- 在项目开始时编写一个脚本或构建一个小规模的系统,当需求增加时重新构建一个大规模的系统。
- 构建一个事件驱动的可扩展系统,这意味着在不使用时,零成本,在使用时,成本将与使用量成比例,即使有很高的规模要求,也将很容易处理。
问题陈述示例:
- 用于训练深度学习模型的数据创建和预处理管道。
- 从输入的原始数据中提取元数据。
- 抓取数据并提取元数据。等等。
我们将使用一个简单的例子来构建电子商务产品的元数据提取系统。然后,元数据用于提供由 Streamoid 提供的电子商务解决方案。问题陈述是客户将提供他们的数据。为了让内部服务工作,需要提取元数据。为了便于理解,内部系统需要标准数据才能运行,而从客户端接收的数据是原始的,可能不符合标准。因此,这需要一个数据处理管道来将原始数据转换为内部系统能够很好理解的标准元数据。
该元数据将被组织中的各种系统使用,以构建若干产品,例如(类似和 outfitter 推荐),提供市场等效数据(Amazon、Myntra、Tatacliq 等)。),训练多个分类器,理解趋势和行为。
让我们看一个电子商务产品元数据提取的例子:

电子商务产品的元数据提取(图片由作者提供)
看起来简单?是的,任务很简单:
- 对产品的标题和描述进行文本分类。
- 对图像进行图像分类并输出文本分类。
- 还有更多的步骤,如改变背景,丰富,元数据转换到其他市场,如亚马逊等。但是为了方便起见,让我们假设只有 2 个步骤文本和图像分类。
当我们将这个数据处理管道与一个业务用例相结合时,挑战就来了:
- 基于优先级的处理。企业客户端优先级最高,然后是使用免费层的客户端,之后是用于内部评估、演示和培训目的的数据。
- 成本优化,有些日子要加工的产品数量会高达 500,000,而有些日子会少于 1,000。可扩展系统的成本应该与给定日期处理的产品数量成正比。
- 有限的资源使用量。深度学习分类器需要大量计算,并且不能无限扩展。因此,根据优先级优化和充分利用这些资源。
在我们继续之前,让我们看一下架构主干的 GIF,我们将在本文中讨论它。这是数据处理管道中的两个过程(文本和图像分类)。
下图显示了两次迭代,一次是在处理 10 个产品时,另一次是在处理 100,000 个产品时。

数据处理系统的事件驱动缩放(图片由作者提供)
在该架构中,我们使用了:
- MongoDB 作为数据库(所有服务使用的生产数据库)
- RabbitMQ 作为消息代理
- 谷歌 Kubernetes 与 KEDA 的 RabbitMQ 消费者规模
- 面向繁重计算分类器和自动缩放的 Google Cloud 运行
该图可分为 4 个主要部分。让我们详细讨论每个组件。

作者图片
MongoDB

MongoDB(作者图片)
使用非 SQL 共享数据库来处理大量读写操作的可伸缩性。正如您所看到的,这里的数据库就像一个插件,如果需要对单独或多个数据库进行读写操作,可以很容易地插入 Push 和 Pull 服务,这些服务也部署在 Cloud Run 上。
RabbitMQ

RabbitMQ(作者图片)
开源消息代理。还有其他消息代理,比如 Kafka,或者云绑定的消息代理,比如 pub-sub 等等。但是选择 rabbitmq 的原因是:
- 从设计上支持优先级队列,这是该流水线的重要需求之一。对于其他消息代理,优先级可以通过分桶来支持,这意味着为具有不同优先级的同一流程维护 3 个队列,优先级需求将由使用者来处理。
- 这是一个基于推送的系统,这意味着与消费者没有竞争条件。它以循环方式根据优先级将消息分配给所有连接的使用者,并在将消息弹出队列之前等待,直到收到成功处理消息的确认。
- 广播或通知在这里不是一个要求,卡夫卡或 pub-sub 会是更好的选择。
也可以使用其他消息代理。消息代理的选择取决于用例。
用 KEDA 搜索 Kubernetes】

GKE(自动驾驶仪)和 KEDA(图片由作者提供)
这里用的是 Google Kubernetes 集群。这里,我们使用了一个自动驾驶集群,这意味着不需要为集群分配节点来进行自动伸缩。资源扩展由集群本身管理,它将创建和删除新的 pod。
我们使用了 KEDA (Kubernetes 事件驱动伸缩)。这么做的原因是 rabbitmq。如果需要在内存/CPU 使用率或 HTTP 点击量上进行伸缩,那么伸缩是很容易的。但是这里需要根据队列中的消息数量进行伸缩。此外,rabbitmq 不会发送调用,它会向已经连接的消费者发送消息。为了应对这一挑战,我们使用了 KEDA,它将跟踪队列中的消息数量,并相应地在谷歌自动驾驶集群中产生新的 pod(rabbit MQ 消费者)。
谷歌云运行

谷歌云运行(图片由作者提供)
这里使用云运行来处理所有繁重的计算,如分类器。这可以缩小到 0,并根据 HTTP 调用进行缩放。在任何管道中,主要成本来自于租用服务器进行繁重的计算处理,以及在没有使用服务器的情况下保持服务器运行。有了 Cloud Run,您可以将繁重的计算服务部署为容器化的解决方案,但只需在使用时付费。部署后,不需要进行任何操作。在这里,我们可以使用 Amazon 弹性容器服务(ECS ),它可以与云运行相媲美,或者如果有 GPU 需求,我们可以在 GPU 节点上构建我们的服务。基本上,这取决于服务计算要求。我们根据需求使用所有三个 GCR、ECS 和 GPU 节点。
我还想提一下,Cloud Run 可能是所有云扩展解决方案中最好的云解决方案。主要原因是它具有所有的功能,并且对开发人员友好,只需部署两行代码。
此外,这种设计的局限性在于扩展繁重的计算资源。
为什么要建立这种架构?
- 它是一个事件驱动的系统,因此当有一个事件发生时,就会产生伸缩和成本,该事件是要处理的新数据的可用性、数据的更新或请求重新处理数据。
- 所有组件就像一个插件。我们可以改变数据库,或者使用多个数据库。如果需求要求,我们可以更改消息代理。我们可以改变谷歌云运行或使用多种缩放服务。该设计消耗的只是一个用于繁重计算服务的 HTTP URL。
- 在管道中添加一个新流程非常容易。添加新流程时,无需在系统的任何其他部分花费时间。所需的工作是添加消费者,因为消费者可能是特定于流程的。
- 相同的管道/系统可以被其他服务用于一组完全不同的任务。这意味着,如果我们设置一次这个管道,它可以被广泛用于不同的数据处理需求。
- 一旦部署,实际上需要零操作和最小开发。
这个架构代表了事件驱动伸缩的主干和思想。实际的系统要比这复杂得多。
要理解上面提到的技术:
- MongoDB
- rabbit MQ:Google 云平台上的集群部署
- 拉比泰姆 vs 卡夫卡
- 谷歌 云运行
- 亚马逊 弹性容器服务 与 Fargate
- 云运行 v/s 弹性容器服务
- 谷歌 自动驾驶集群
- KEDA:Kubernetes 事件驱动自动缩放
- 自动缩放 与 KEDA 用于 RabbitMQ 上谷歌自动驾驶集群
选择技术取决于解决方案。技术上稍加改变的相同架构可以满足不同类型的需求。所以最好是分析所有的技术,然后选择一个。
决策点示例:
- 使用哪个云提供商,或者我们应该在本地部署?
- 使用哪个数据库?
- 使用哪个队列/消息代理?等等。
对于技术方面的决策点,我总是参考 CNCF 风景来查看所有可用的选项,并根据我的要求进行分析。
让我知道你对此的想法。
因果推理的事件研究:该做什么和不该做什么
原文:https://towardsdatascience.com/event-studies-for-causal-inference-the-dos-and-donts-863f29ca7b65
避免事件研究常见陷阱的指南

里卡多·戈麦斯·安吉尔在 Unsplash 上的照片
事件研究是因果推理的有用工具。它们被用于准实验场景。在这些情况下,治疗不是随机分配的。因此,与随机实验(即 A/B 测试)相比,人们不能依靠简单的组间均值比较来做出因果推断。在这种情况下,事件研究非常有用。
事件研究也经常被用于观察治疗组和非治疗组之间是否存在任何治疗前差异,作为预测试平行趋势的一种方法,这是一种被称为差异中的差异(DiD)的流行因果推断方法的关键假设。
然而,最近的文献显示了事件研究中的各种缺陷。如果被忽略,当使用事件研究进行因果推断或作为平行趋势的预测试时,这些陷阱会产生重大后果。
在本文中,我将讨论这些陷阱以及如何避免它们的建议。我将把重点放在面板数据的应用上,在面板数据中,我观察单位随时间的变化。我将用一个玩具例子来说明陷阱和建议。你可以在这里找到用于模拟和分析数据的完整代码。在本文中,我将代码的使用限制在最关键的部分,以避免混乱。
一个例证
事件研究通常用于调查某个事件(如某个国家的新法规)的影响。此类事件的最近一个例子是由于疫情而实施的封锁。在停工事件中,许多企业受到影响,因为人们开始花更多的时间在家里。例如,音乐流媒体平台可能想知道人们的音乐消费模式是否因锁定而发生了变化,以便他们能够应对这些变化,更好地服务于他们的客户。
为该平台工作的研究人员可以调查锁定后音乐消费的数量是否发生了变化。研究人员可以使用从未实施封锁或后来实施封锁的国家作为控制组。在这种情况下,事件研究是合适的。对于本文,假设实施封锁的国家/地区会一直持续到我们的观察期结束,并且封锁的实施是二元的(即,忽略封锁的严格程度会有所不同)。
事件研究规范
我将重点介绍以下形式的事件研究:

事件研究规范,图片由作者提供(公式修改自:brant ly Callaway 和 Pedro H.C. Sant'Anna 使用 did 包在 DiD 设置中进行预测试)。
Y ᵢₜ是利益的产物。αᵢ是单位固定效应,它控制时间常数单位特性。γₜ是时间固定效应,它控制时间趋势或季节性。 l 是相对于治疗的时间,它表示在给定时间 t 从治疗开始已经过了多少周期。例如, l = -1 表示治疗前一个周期, l = 2 表示治疗后两个周期。D ˡᵢₜ 是相对时间段 l 在时间 t 对于单元 i. 基本上,我们包括治疗的超前和滞后。ϵᵢₜ是随机误差。
感兴趣系数β ₗ 表示给定相对时间段内的平均治疗效果 l 。在观察期内,有 T 个周期,因此,周期范围从 0 到 T-1。这些单位在不同时期接受治疗。同时接受治疗的每组单位组成一个治疗群组。这种类型的事件研究是一种差异中的差异(DiD)设计,其中单位在不同的时间点接受治疗(Borusyak 等人,2021 年)
例证续:
根据我们的说明性示例,我模拟了一个面板数据集。在此数据集中,有 10,000 个客户(或单位)和 5 个期间(从期间 0 到 4)。我分别对这些单位和时期的单位固定效应和时间固定效应进行随机抽样。总的来说,我们有 50,000 次(10,000 个单位 x 5 个周期)客户周期级别的观察。感兴趣的结果是以小时计量的音乐消费。
我随机将客户分配到 3 个不同的国家。其中一个国家在第二阶段实施了封锁,另一个国家在第三阶段实施了封锁,还有一个国家从未实施封锁。因此,来自这些不同国家的顾客在不同的时间受到不同的对待。为了便于理解,我将根据客户接受治疗的时间,按照他们的治疗群组来指代客户:群组第 2 期和群组第 3 期分别针对在第 2 期和第 3 期接受治疗的客户。其中一个群组从未接受治疗,因此,为了便于编码,我将其称为群组周期 99 。
在模拟中,在这些客户被随机分配到这些群组之一后,我创建了治疗虚拟变量treat,如果cohort_period >= period等于 1,否则等于 0。treat表示一个单位在给定的时间段内是否被处理。接下来,我创建一个在每个治疗期间增长的动态治疗效果(例如,治疗发生期间的 1 小时和之后期间的 2 小时)。治疗前的治疗效果为零。
我将感兴趣的结果 hrs_listened计算为我随机选择的常数(80)、单位和时间固定效应、治疗效应以及每个单位和周期的误差(随机噪声)的总和。通过构建,处理(锁定)对音乐消费有着越来越大的积极影响。
为了避免混乱,我跳过了一些代码的设置和模拟部分,但是你可以在这里找到完整的代码。
在下图中,我展示了数据的快照。unit指的是客户,cohort_period指的是某个单位被处理的时候。hrs_listened是因变量,它以小时为单位衡量给定客户在给定时间段内的音乐消费。
rm(list = ls())
library(data.table)
library(fastDummies)
library(tidyverse)
library(ggthemes)
library(fixest)
library(kableExtra)
data <- make_data(...)
kable(head(data[, ..select_cols]), 'simple')

模拟数据快照,图片由作者提供。
在下图中,我展示了按群组和时间段划分的平均音乐收听趋势。我还记录了这些国家首次实施封锁的时间。你可以看到,与未治疗组的客户相比,早期和晚期治疗国家的锁定似乎有积极的影响。
# Graph average music listening by cohort and period
avg_dv_period <- data[, .(mean_hrs_listened = mean(hrs_listened)), by = c('cohort_period','period')]
ggplot(avg_dv_period, aes(fill=factor(cohort_period), y=mean_hrs_listened, x=period)) +
geom_bar(position="dodge", stat="identity") + coord_cartesian(ylim=c(79,85))+
labs(x = "Period", y = "Hours", title = 'Average music listening (hours)',
caption = 'Cohort 2 is the early treated, cohort 3 is the late treated and cohort 99 is the never treated group.') +
theme(legend.position = 'bottom',
axis.title = element_text(size = 14),
axis.text = element_text(size = 12)) + scale_fill_manual(values=cbPalette) +
geom_vline(xintercept = 1.5, color = '#999999', lty = 5)+
geom_vline(xintercept = 2.5, color = '#E69F00', lty = 5) +
geom_text(label = 'Cohort period 2 is treated',aes(1.4,83), color = '#999999', angle = 90)+
geom_text(label = 'Cohort period 3 is treated',aes(2.4,83), color = '#E69F00', angle = 90) +
guides(fill=guide_legend(title="Treatment cohort period"))

按群组和时间段划分的平均音乐收听率,按作者划分的图像。
由于这个数据集是模拟的,我知道每个队列和每个时期锁定的真实治疗效果。在下图中,我展示了封锁的真实治疗效果。
在治疗后的第一阶段(相对阶段 1),两个组的听力都增加了 1 小时。在相对于治疗的第二阶段,两个组的治疗效果都是 2 小时。对于相对周期 3,我们看到处理效果是 3 小时。
这里需要注意的一点是,在相对时间段内,治疗效果在各群组中是同质的(例如,在相对时间段 1 中为 1 小时;相对周期 2)中的 2 小时。稍后,我们将看到如果不是这样会发生什么。
# Graph the true treatment effects
avg_treat_period <- data[treat == 1, .(mean_treat_effect = mean(tau_cum)), by = c('cohort_period','period')]
ggplot(avg_treat_period, aes(fill=factor(cohort_period), y=mean_treat_effect, x=period)) +
geom_bar(position="dodge", stat="identity") +
labs(x = "Period", y = "Hours", title = 'True treatment effect (hrs)',
caption = 'Cohort 2 is the early treated, cohort 3 is the late treated and cohort 99 is the never treated group.') +
theme(legend.position = 'bottom',
axis.title = element_text(size = 14),
axis.text = element_text(size = 12)) + scale_fill_manual(values=cbPalette) +
guides(fill=guide_legend(title="Treatment cohort period"))

真实治疗效果大小,图片由作者提供。
现在,我们通过回归相关时期假人的hrs_listened进行事件研究。相对周期是period和cohort_period之差。负相对周期表示治疗前的周期,正相对周期表示治疗后的周期。对于所有的事件研究回归,我们使用单位固定效应(αᵢ)和期间固定效应(γₜ)。
在下表中,我报告了这次事件研究的结果。不出所料,在治疗前没有检测到任何影响。治疗后效应被精确和正确地估计为 1、2 和 3 小时。所以到目前为止一切正常!让我们看看事情不太顺利的情况…
# Create relative time dummies to use in the regression
data <- data %>%
# make relative year indicator
mutate(rel_period = ifelse(cohort_period == 99,99,period - cohort_period))
summary(data$rel_period)
data <- data %>%
dummy_cols(select_columns = "rel_period")
rel_per_dummies <- colnames(data)[grepl('rel_period_', colnames(data))]
# Change name w/ minuses to handle them more easily
rel_per_dummies_new<-gsub('-','min', rel_per_dummies)
setnames(data, rel_per_dummies, rel_per_dummies_new)
# Event study
covs <- setdiff(rel_per_dummies_new, c('rel_period_99','rel_period_min1'))
covs_collapse <- paste0(covs, collapse='+')
formula <- as.formula(paste0('hrs_listened ~ ',covs_collapse))
model <- feols(formula,
data = data, panel.id = "unit",
fixef = c("unit", "period"))
summary(model)

模拟数据的事件研究结果,图片由作者提供。
到目前为止,一切都很好,但在使用事件研究方法时,为了避免潜在的陷阱,需要注意以下四件事:
1。无预期假设
文献中许多事件研究的应用强加了一个无预期假设。无预期假设意味着处理单元在处理之前不会改变它们对处理的预期行为。当无预期假设成立时,可以使用事件发生前的时期作为参考时期,并将其他时期与该时期进行比较。
然而,在某些情况下,无预期假设可能不成立,例如,当在实施处理之前向小组宣布处理时,单元可以通过调整它们的行为来响应该宣布。在这种情况下,需要仔细选择参考周期,以避免偏差。如果您知道受试者何时开始预期治疗并改变他们的行为,您可以将该时期作为治疗的实际开始,并将之前的时期作为参考时期(Borusyak 等人,2021)。
例如,如果您怀疑受试者在 l = -1(治疗前一个周期)中改变他们的行为是因为他们预期治疗,您可以使用 l = -2(治疗前两个周期)作为您的参考周期。你可以通过从等式中去掉 D ˡᵢₜ 其中 l = -2 来实现,而不是去掉 l = -2 的哑元。这样,您可以将 l = -2 周期用作参考周期。要检查您对在 l = -1 中改变其行为的单位的预感是否属实,您可以检查 l = -1 中估计的处理效果是否具有统计显著性。
例证续:
回到我们的说明性示例,通常在实施封锁之前宣布封锁,这可能会影响单元的预处理行为。例如,一旦宣布了封锁,但还没有强制实施,人们可能已经开始在家工作了。
因此,人们甚至可以在实际实施封锁之前改变他们的音乐收听行为。如果在实际实施前 1 个周期宣布锁定,则可以通过从规范中删除相对周期-1 的虚拟周期,使用相对周期= -2 作为参考周期。
根据这个例子,我复制并修改了原始数据,以引入一些预期效果。我介绍了在相对周期-1 中所有单元收听时间增加 0.5 小时。我称这个新的数据集为预期数据集data_anticip。
下一张图显示了相对时间段内的平均音乐收听时间。很容易注意到,与相对周期-2 和-3 相比,收听时间在相对周期-1 中已经开始加快。忽略收听时间的这一显著变化会产生误导性的结果。
# Summarize the hours listened over relative period (excluding the untreated cohort)
avg_dep_anticip <- data_anticip[rel_period != 99, .(mean_hrs_listened = mean(hrs_listened)), (rel_period)]
setorder(avg_dep_anticip, 'rel_period')
rel_periods <- sort(unique(avg_dep_anticip$rel_period))
ggplot(avg_dep_anticip, aes(y=mean_hrs_listened, x=rel_period)) +
geom_bar(position="dodge", stat="identity", fill = 'deepskyblue') + coord_cartesian(ylim=c(79,85))+
labs(x = "Relative period", y = "Hours", title = 'Average music listening over relative time period',
caption = 'Only for the treated units') +
theme(legend.position = 'bottom',
legend.title = element_blank(),
axis.title = element_text(size = 14),
axis.text = element_text(size = 12)) + scale_x_continuous(breaks = min(rel_periods):max(rel_periods))

相对时间段内的平均音乐收听量,图片由作者提供。
现在,让我们做一个事件研究,就像我们之前做的那样,通过回归相对时间段假人的收听时间。请记住,我唯一改变的是相对周期-1 中的效应,其余数据与之前完全相同。
您可以在下表中看到,即使在这些时期没有真正的治疗效果,治疗前的效果也是负面的和显著的。原因是我们使用相对周期-1 作为参考周期,这打乱了所有的效果估计。我们需要做的是用一个没有预期的时期作为参考时期。
formula <- as.formula(paste0('hrs_listened ~ ',covs_collapse))
model <- feols(formula,
data = data_anticip, panel.id = "unit",
fixef = c("unit", "period"))
summary(model)

忽略预期时的事件研究结果,图片由作者提供。
在下表中,我报告了新回归的事件研究结果,其中我使用相对期间-2 作为参考期间。现在,我们有了正确的估计!在相对周期-3 中没有检测到影响,尽管在相对周期-1 中正确地检测到了影响。此外,现在可以正确估计治疗后期间的效应大小。
# Use release period -2 as the reference period instead
covs_anticip <- setdiff(c(covs,'rel_period_min1'),'rel_period_min2')
covs_anticip_collapse <- paste0(covs_anticip,collapse = '+')
formula <- as.formula(paste0('hrs_listened ~ ',covs_anticip_collapse))
model <- feols(formula,
data = data_anticip, panel.id = "unit",
fixef = c("unit", "period"))
summary(model)

当预期不被忽略时的事件研究结果。
2。跨群组同质治疗效果的假设
在前面所示的等式中,治疗效果只能随相对时间段而变化。这里隐含的假设是,这些治疗效果在各治疗组中是同质的。然而,如果这种隐含的假设是错误的,那么估计的治疗效果可能会与实际的治疗效果显著不同,从而导致偏差(Borusyak 等人,2021 年)。一个示例情况可能是,与后治疗组相比,前治疗组从治疗中获益更多。这意味着不同队列的治疗效果不同。
解决这个问题的最简单的解决方案是允许异构性。考虑到队列间治疗效果的异质性,可以估计相对时间和队列特异性治疗效果,如下文所述。在以下说明中, c 代表治疗群组。此处,除了将使用β ₗ,c 的估计器对每个相对时间&治疗队列组合的治疗效果进行估计之外,一切都与之前的规范相同。dᵢᶜt14】代表给定单元 i 的治疗群组假人。

允许群组水平异质性的事件研究规范,作者图像。
例证续:
在锁定的例子中,由于不同的原因(例如,可能在其中一个国家,人们更有可能遵守新的法规),锁定的效果在不同的被处理国家是不同的。因此,应该估计国家和相对特定时间的治疗效果,而不是仅仅估计相对特定时间的治疗效果。
在最初的模拟数据集中,我在不同时期的治疗效果中引入了队列异质性,并将这个新数据集称为data_hetero。如下图所示,在所有治疗周期中,群组周期 2 的治疗效果是群组周期 3 的 1.5 倍。

队列异质性的真实治疗效果,图片由作者提供。
现在,正如我们之前所做的,让我们为data_hetero运行一个事件研究。下表报告了本次事件研究的结果。尽管在治疗前阶段没有治疗或预期效果,但事件研究检测到了具有统计显著性的效果!这是因为我们没有考虑跨队列的异质性。
# Event study
formula <- as.formula(paste0('hrs_listened ~ ',covs_collapse))
model <- feols(formula,
data = data_hetero, panel.id = "unit",
fixef = c("unit", "period"))
summary(model)

忽略队列异质性时的事件研究结果,图片由作者提供。
让我们通过对队列特定的相对时期的假人进行几个小时的监听来说明队列间治疗效果的异质性。在下表中,我报告了这次事件研究的结果。在该表中,报告了每个队列和相对时期的治疗效果估计值。通过允许每个队列的治疗效果不同,我们解释了异质性,因此,我们有正确的估计!没有检测到预处理应有的效果。
# Create dummies for the cohort-period
data <- data_hetero %>%
dummy_cols(select_columns = "cohort_period")
cohort_dummies <- c('cohort_period_2','cohort_period_3')
# Create interactions between relative period and cohort dummies
interact <- as.data.table(expand_grid(cohort_dummies, covs))
interact[, interaction := paste0(cohort_dummies,':',covs)]
interact_covs <- interact$interaction
interact_covs_collapse <- paste0(interact_covs,collapse = '+')
# Run the event study
formula <- as.formula(paste0('hrs_listened ~ ',interact_covs_collapse))
model <- feols(formula,
data = data_hetero, panel.id = "unit",
fixef = c("unit", "period"))
summary(model)

事件研究结果说明了队列异质性,图片由作者提供。
3。在完全动态规范中,在没有从未治疗组的情况下识别不足
在完全动态事件研究规范中,其中包括治疗的所有超前和滞后(通常只有相对时间-1 被删除以避免完全多重共线性),在没有非治疗组的情况下治疗效果系数未被识别。其原因是动态因果效应无法从单位效应和时间效应的组合中区分出来(Borusyak 等人,2021 年)。实际解决方案是放下另一个预处理假人(即另一个铅处理假人)以避免识别不足的问题。
例证续:
想象一下,我们没有任何未经治疗的国家的数据。因此,我们的样本中只有经过治疗的国家。我们仍然可以利用治疗时间的变化进行事件研究。然而,在这种情况下,我们不得不使用不止一个而是至少两个参考周期来避免识别不足。可以通过从规格中删除治疗前的周期和最负相对周期模型来做到这一点。
在模拟数据集中,我删除了来自未处理队列的观察结果,并将这个新数据集称为data_under_id。现在,我们只处理了样本中的队列。其余部分与原始模拟数据集相同。因此,我们必须使用至少两个参考期,通过丢弃任何治疗前相对期模型的模型。我选择排除相对周期-1 和-3 的虚拟值。我在下面报告这次事件研究的结果。正如你现在看到的,我在模型中只估计了一个相对周期。估计没错,太好了!

没有未处理组时的估计结果,图片由作者提供。
4。使用事件研究作为平行趋势假设的预测试
使用事件研究作为平行趋势假设(PTA)的预测试是一种常见策略,平行趋势假设是差异中的差异(DiD)方法的重要假设。PTA 指出,在没有处理的情况下,处理和未处理的单位在感兴趣的结果方面将遵循平行的趋势。事件研究用于观察在治疗发生之前,治疗组的行为是否与未治疗组不同。据认为,如果在治疗组和未治疗组之间没有检测到统计学上的显著差异,PTA 可能保持不变。
然而,Roth (2022)表明这种方法可能有问题。一个问题是这些类型的预测试具有较低的统计功效。这使得发现不同的趋势变得更加困难。另一个问题是,如果你有很高的统计能力,你可能会发现不同的治疗前效应(前趋势),即使它们不是那么关键。
Roth (2022)推荐了几个解决这个问题的方法:
- 不要仅仅依赖于预测试系数的统计显著性,要考虑预测试的统计功效。如果功率较低,事件研究将不会提供关于预投标存在的大量信息。如果你有很高的统计能力,预测试的结果可能仍然是误导性的,因为你可能会发现一个不那么重要的有统计学意义的预测趋势。
- 考虑完全避免预测试的方法,例如,在给定背景下使用经济知识选择正确的 PTA,如有条件的 PTA。另一种方法是,如果您认为治疗组和未治疗组遵循不同的趋势,并且不具有可比性,则使用后期治疗组作为对照组。请看卡拉威&圣安娜的 2021 年论文,了解放松家长教师协会的潜在方法。
例证续:
回到最初的例子,我们有三个国家,假设我们想进行 DiD 分析,我们想找到支持 PTA 在这种情况下成立的证据。这意味着,如果接受治疗的国家不接受治疗,音乐消费将与未接受治疗的国家的音乐消费平行移动。
我们考虑使用一个均匀的研究作为预测试 PTA 的方法,因为没有直接测试 PTA 的方法。首先,我们需要考虑测试的统计能力。Roth (2021)为此提供了一些工具。虽然这超出了本文的范围,但我可以说,在这个模拟数据集中,我们有相对较高的统计能力。因为随机噪声很低,我们有一个相对较大的样本量,没有太多的系数要估计。尽管如此,运行场景分析来查看人们可以正确检测到多大程度的预处理效果还是有好处的。
其次,不管治疗前估计值的统计显著性状态如何,都要考虑特定的背景。我是否期望接受治疗的国家和未接受治疗的国家遵循同样的趋势?在我的模拟数据中,我很确定这一点,因为我确定了数据的样子。然而,在现实世界中,这不太可能无条件成立。因此,我会考虑使用有条件的优惠贸易协定,根据各种协变量对优惠贸易协定进行调整,使各国之间更具可比性。
结论
事件研究是强有力的工具。然而,人们应该意识到他们潜在的陷阱。在本文中,我探索了最常遇到的陷阱,并提供了关于如何使用模拟数据集解决这些问题的建议。我讨论了与无预期假设、队列间治疗效果的异质性、缺乏未治疗队列时的识别不足以及使用事件研究作为 PTA 的预测试相关的问题。
参考
[1] Borusyak,k .,Jaravel,x .,& Spiess,J. 重温事件研究设计:稳健有效的评估。(2021). arXiv 预印本 arXiv:2108.12419 。
[2]j .罗斯谨慎地进行预测试:平行趋势测试后的事件研究估计。(2022).美国经济评论:洞见, 4 (3),305–22。
[3] Callaway,b .,& Sant'Anna,P. H. 多个时间段的差异中的差异。(2021).计量经济学杂志, 225 (2),200–230。
其他相关论文
Sun,l .,& Abraham,S. 在具有异质治疗效应的事件研究中估计动态治疗效应。(2021).计量经济学杂志, 225 (2),175–199。
Wooldridge,J. M. 双向固定效应、双向蒙德拉克回归和差异中的差异估计。(2021).可在 SSRN 3906345 获得。
感谢您的阅读!
如果你喜欢这篇文章并想看更多我的文章,可以考虑 关注我 。
免责声明 :我写作是为了学习,所以你可能会发现文章或代码中的错误。如果你这样做,请让我知道。
想知道你提交的黑客马拉松是如何被审核的吗?在这里学习吧!
来自一家一级咨询公司黑客马拉松的关键评分维度

杰佛森·桑多斯在 Unsplash 上拍摄的照片
几年前,我在一家一级咨询公司给黑客马拉松提交的内容打分。这是一次很好的学习经历。我经常想知道这是怎么做到的。分数仅仅是由模型表现驱动的吗?还是编码标准和创造力的作用最大?
我很高兴强调准确性不是唯一的评分因素。
黑客马拉松通常针对学生或入门级的潜在新员工。这是挑战自我和拓展人脉的好方法。黑客马拉松是展示你的技能和发展数据科学家的最佳机会之一。
这篇文章的目的是通过一个例子来提高这个话题的透明度。这是一家一级咨询公司在之前的黑客马拉松中使用的评估矩阵。

作者图片
该矩阵显示了编码最佳实践的一般准则。无论需要什么样的环境和规模,都可以使用这些指南。通过应用以下标准来确保最终项目的生产质量:
- 编程风格
- 证明文件
- 技术
- 结构
代码评估是黑客马拉松的众多评估领域之一。模型的准确性和表达技巧也被考虑在内。为了确保全面评估,与会者介绍了他们的结果。这些领域的评分将不会在这篇文章中强调。
现在,让我们深入矩阵中的五个维度。对于每个维度,参与者的得分范围为 0-5。5 分反映了该维度的完全完成。
编码风格

克里斯·里德在 Unsplash 上拍摄的照片
首先要做的事。编码风格和简洁非常重要。这是一个项目最大的组成部分,应该永远是重中之重。如果使用 python,编码标准应遵循 PEP8 。现在,让我们看看主要的评估领域:
- 尽可能避免循环,以确保代码的透明性。执行一般任务时,请始终使用相关的库
- 避免冗余以确保清晰易懂的编码风格。冗余是指不再需要的代码,如临时变量和检查。这些也可以是没有存储在函数中的代码的副本。提交前总是删除或优化
- 如果异常存在,通过施加
try语句来正确处理异常。捕获try子句中的主要操作和except子句中的异常。 - 使用配置文件存储参数和路径。这确保了干净的代码,并确保了代码的简单移交。优化工作流程的绝佳方式
文档
如果有良好的文档记录,代码是最好的共享和重用方式。如果没有文档,不一致或不正确使用代码的可能性会迅速增加。代码文档标准至少应包括:
- 表单的所有公共模块、函数、类和方法的文档字符串(示例来自 PEP8 Style guide ):
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
- 屏蔽评论如果用笔记本引导读者。用 # 初始化块注释
此外,如果使用 GitHub 进行版本控制和协作,请包括以下内容:
- 自述文件向读者介绍该项目。包括解释存储库中的设置、工作流和文件夹结构的部分
技术

照片由 Richy Great 在 Unsplash 上拍摄
在这次黑客马拉松中,参与者被要求将他们的提交内容上传到 GitHub。鉴于此,这一类别的分级与平台的使用密切相关。GitHub 是数据科学家首选的版本控制和协作平台。
参与者根据以下 GitHub 最佳实践参数进行评分:
- 没有数据文件推送到 GitHub 。数据可能包含不应向公众公开的敏感信息。如果您需要存储您的数据,请使用云服务,如亚马逊 S3
- 开发新代码时使用临时分支。当对新代码满意时,通过拉请求推送到主分支。这确保了主代码在任何时候都充分发挥作用
- 推。如果使用笔记本,将 md (markdown)文件保存到 GitHub 。这确保了分级员和其他观众易于阅读的设置。Jupytext 是一个很好的保护这一点的库
结构
保持严格的透明工作流程结构。尤其是在大型团队中从事大型项目时。让我们看看评估的子维度:
- 使用相关库进行分析。大多数简单的计算和通用分析指标都存在于定义明确的库中。总是努力应用可用的库来保持简单的工作流。
- 在一个干净的文件夹结构中组织代码以确保存储库中的透明性。最佳实践包括:
1。将代码存储在专用的 src/ 文件夹中。
2。将库需求存储在 requirements.txt 文件
3 中。自述文件中的目录树中的概要文件夹结构 - 使用函数和类来组织代码。这确保了具有有限循环和冗余的干净编码结构。
奖励积分
数据科学家需要的一项关键技能是创造力。这就是为什么在黑客马拉松中奖励积分应该优先考虑的原因。
在这里,是你大放异彩、展示非凡技能和创造力的时候了。我强调了展示这一点的两种方式,但还有更多方式。
引入外部数据
开源或公共数据是免费的,任何人都可以在线获得。如果经过优化选择,包含外部数据可以导致模型的重大改进。在网上搜索,看看是否有公共数据可以帮助你提高模型的准确性。
在下面找到一些关于公共数据源的好资源:
介绍最先进的技术
数据科学领域在不断发展。展示任何最先进的技术,给评估者留下深刻印象。有很多方法可以做到这一点,最相关的方法取决于你要解决的问题。找到下面的几个例子:
- 集装箱化使用码头
- 云服务,如亚马逊网络服务(AWS)
评分和反馈

在这里突出显示的黑客马拉松中,每个维度都按照 0-5 的等级进行评分。通过将每个维度的得分相加,得出最终得分,最高得分为 25 分。
为了认可黑客马拉松的表现,每位参与者都收到了反馈。参与者收到的反馈强调了优秀的绩效和发展领域。
总结想法
这篇文章的目标是推动如何审查黑客马拉松提交的透明度。了解所有的评估标准可以在未来的比赛中提高表现。
评估指标围绕五个子维度展开:
- 编程风格
- 证明文件
- 技术
- 结构
- 奖励积分
让我们在职业生活的各个方面都努力做到透明!公开期望和评估标准将使其更容易执行。
你有什么想法?明年你会被鼓励加入吗?欢迎在评论区留下你的想法。
感谢阅读!
每一滴都很重要
原文:https://towardsdatascience.com/every-drop-counts-e37201071f67
在缺水的世界使用智能灌溉

美丽胜于丑陋(Python 之禅,照片由 Linus Mimietz 拍摄)
如今,节约使用水这一重要资源变得越来越重要。受到塞思·西格尔的书《要有水》和德国干旱期延长的启发,我开始为我的花园灌溉寻找一个更可持续的解决方案。由于我是 Python 编程的朋友,我的解决方案不能由简单的雨量计组成,而是必须包括智能套接字、OpenWeather API 和几行 Python 代码。开玩笑,与简单的雨量计相比,OpenWeather 提供了许多其他有用的天气功能,可以与智能插座结合使用。为了简单起见,本文中我只使用下雨的概率。但是你也可以,例如,增加对太阳辐射的评估,并相应地调整遮阳系统。由于 OpenWeather,可能性是多方面的。
背景:
当我在度暑假时,我的花园由一个计时器浇水。设置如下:计时器每天晚上为水泵供电 15 分钟。该泵从水箱中抽水并通过软管浇花。不管下雨与否,水泵每天都在运转。这一程序现在将被一种更聪明的方法所取代。未来 24 小时内是否会下雨有待核实。并且只有在预测不下雨的情况下,泵才被供电 15 分钟,以便向花园供水。
如果你正在寻找更深入的技术解决方案,我可以强烈推荐 Chris 的 Github 资源库。虽然 Chris 住在挪威,这个国家不一定以缺水闻名,但他开发了一个令人印象深刻的 Python 应用程序,该程序结合了天气预报来控制花园灌溉以节约用水。

https://github.com/chrisb2/water-system(图片来自克里斯的 Github 库)
解决方案:
与克里斯的工程方法相比,我将采用一种更普通的方法:我将灌溉系统(水泵)连接到一个智能插头上。在这种情况下,智能意味着插头连接到互联网。只有在预测第二天不会下雨的时候,那个插头才会被打开。
我的观点不是宣传一个“美国的一切卖家”,而是为你提供一个平衡的解决方案,不需要工程技术就能可靠地工作。这就是为什么我决定将 Alexa 的智能插头与一个名为“虚拟智能家居”的应用程序一起使用。该应用程序可以非常轻松地为 Alexa 的智能插件例程设置 URL 触发器:
https://www.virtualsmarthome.xyz/url_routine_trigger/
首先,在网站“virtualsmarthome.xyz”上,我们需要创建一个新的触发器。我已经输入了“GardenHose”作为名称:

您可以输入您喜欢的任何名称(图片由作者提供)
现在只需点击“保存”按钮,就大功告成了:


我们将在后面的代码示例中使用 URL 地址(图片由作者提供)
现在,在你的 Alexa 应用程序中,选择你的智能插头,并创建一个每当 URL 被激活时就会打开电源的例程:

在我的例子中,智能插头被称为“erste Steckdose”
点击“创建一个例程”,并告知每次“按下 GardenHose”(这意味着有一个对该 URL 的调用):

选择“开机”这样的动作作为对 URL 调用的响应(图片由作者提供)
…插头的电源必须打开:

(图片由作者提供)
现在重复上述步骤进行第二次触发。这将用于关闭智能插头:

花园软管关闭是第二个会关闭电源的触发器(图片由作者提供)
唯一不同的是,对于这个触发器“花园软管关闭”,智能插头将被关闭:

(图片由作者提供)
…当该 URL 被点击时:

(图片由作者提供)
现在编程部分开始了。由于我们将调用 OpenWeather 的 API,我们首先需要创建一个免费帐户(如果还没有完成的话)。转到 OpenWeather,创建一个帐户并确认您的邮件。
https://home.openweathermap.org/api_keys
之后,激活您的 api 密钥可能需要几分钟,所以请耐心等待。要检查您的 API 是否正常工作,请尝试代码的开头:
from pprint import pprint
import requests, json
r = requests.get('https://api.openweathermap.org/data/2.5/forecast?q=Hamburg,de&cnt=9&APPID=YourApiKey')
pprint(r.json())
如果您的 API 键工作正常,那么您的输出应该类似于以下内容:

(图片由作者提供)
恭喜,我们现在已经收到汉堡未来 24 小时(cnt=9)的天气预报(q =汉堡)。因为上面的 Json 格式有点难以阅读,所以我们使用 json_normalize 将其转换为 pandas 数据帧:
df = pd.json_normalize(r.json())

Df 输出比 Json 干净,但是列“list”仍然需要一些准备(图片由作者提供)
相关的天气预报存储在“列表”栏中。

“pop”代表降水概率(图片由作者提供)
正是“pop”场告诉我们降水的概率。参数值在 0 和 1 之间变化,其中 0 等于 0%,1 等于 100%。每次“pop”之间的时间间隔是三个小时,这就是为什么我们最终在接下来的 24 小时内总共有 9 次“pop”(第一次 pop 是调用 API 的当前时间)。
因为我们主要对未来 24 小时的降雨概率感兴趣,所以我们将其提取到 9 列中。
df['list'] = df['list'].astype(str)
df = df[['list']].join(df['list'].str.extractall(r"(?<='pop': )([^'pop']+)(?=,)").unstack().droplevel(0, axis=1).rename(lambda x: 'RainProbability_' + str(x+1), axis=1))

列“RainProbability_1”表示有 20%的可能性下雨,而三个小时后下雨的可能性只有 6%等(图片由作者提供)
对于我们的例子,让我们估计,如果下雨的概率超过 80%,就会下雨。否则我们预计不太可能下雨:
import numpy as np
conditions = [(df['RainProbability_1'] > '0.8') |
(df['RainProbability_2'] > '0.8') |
(df['RainProbability_3'] > '0.8') |
(df['RainProbability_4'] > '0.8') |
(df['RainProbability_5'] > '0.8') |
(df['RainProbability_6'] > '0.8') |
(df['RainProbability_7'] > '0.8') |
(df['RainProbability_8'] > '0.8') |
(df['RainProbability_9'] > '0.8')]
choices = ['RainLikely']
df['RainProbability'] = np.select(conditions, choices, default='RainUnlikely')

未来 24 小时内的“降雨量”都不超过 80%,因此不太可能下雨(图片由作者提供)
我们将把这个输出与我们的智能插头结合起来。只有在未来 24 小时内不太可能下雨的情况下,智能插头才会打开(从而启动水泵给花园浇水)。浇水 15 分钟后,水泵应该关闭。另一方面,当天气预报预测有雨时,插头应该保持关闭。
import webbrowser
import time
b = df['RainProbability'].iloc[0]
new = 2 # open in a new tab, if possible
if b =='RainUnlikely':
url = "https://www.virtualsmarthome.xyz/url_routine_trigger/activate.php?trigger=YourTriggerToTurnSmartPlugOn&response=html"
webbrowser.open(url, new=new)
time.sleep(900)
url="https://www.virtualsmarthome.xyz/url_routine_trigger/activate.php?trigger=YourTriggerToTurnSmartPlugOff&response=html"
webbrowser.open(url, new=new)
else:
print ('No need to water since there will be rain within the next 24h')

当预计不会下雨时,花园软管的触发器将被激活(图片由作者提供)

15 分钟后插头将再次关闭(图片由作者提供)
下面是完整的代码:
概要:
恭喜你,你已经建立了一个智能灌溉系统。这是迈向更可持续的世界的又一简单步骤。当然,我们的智能插头只使用可持续的电力来源(原子能不算,对吗,法国?😃).
非常感谢您的阅读!希望这篇文章对你有帮助。请随时在 LinkedIn 、 Twitter 或工作室与我联系。
https://jesko-rehberg.medium.com/membership
还要特别感谢 Katherine Prairie 的宝贵反馈!
各种定标器及其在数据科学中的应用
原文:https://towardsdatascience.com/every-scaler-and-its-application-in-data-science-1115bec62660
古老的连续定标器及其在机器学习中的应用

安托万·道特里在 Unsplash 上拍摄的照片
介绍
你的模型中最重要的因素永远是你的数据。提供给模型的数据将最终决定模型的性能,因此特性是创建一个真正性能良好的模型不可或缺的一部分。有几种方法可以通过调整数据的不同方面来提高模型的有效性。有两种不同类型的特征,其中这些种类的改变通常是有益的,它们是分类特征和连续特征。对于分类特征,我们使用编码器。对于连续特征,我们使用定标器。虽然在某些方面,它们服务于不同的目的,但最终作为预处理技术,它们都有助于提高验证的准确性。我过去已经讨论过编码器,所以今天我们将采取相反的轨迹。如果您想了解更多关于编码器的信息,我也有一篇文章,您可以在这里阅读:
对于数据科学家来说,定标器是一个非常重要的工具。这些缩放器用于数据,以便使其更容易被机器学习算法解释。这种类型的数学可以帮助我们更果断地从数据中归纳和得出结论。可能最著名的连续定标器在统计和机器学习中都有非常突出的应用,这就是正常或标准定标器。
标准定标器通过确定我们的数据与总体平均值的标准差来标准化我们的数据。为了证明这一点,我们将观察它的公式,并解释这究竟如何提高我们量化连续特征的能力。我们还将触及其他连续定标器的基础,它们对机器学习和科学都有用,但不那么有用。
标准定标器
正如我之前提到的,标准定标器很可能是最著名的定标器。更方便的是,这个定标器也是正态分布。正态分布是推断统计和高斯统计的完美介绍和基本原则,因此作为科学家,我们强调这一尺度的重要性和用法是非常重要的。如果你想了解更多关于正态分布的知识,我有一篇文章可以很好地解释它,你可以在这里阅读:
让我们简单回顾一下什么是正态分布。该分布的概率密度函数(PDF)表示为:
x̄- / σ
即样本减去总体均值,除以总体标准差。检查数学,我们发现我们的样本和平均值之间的差异,然后看看有多少标准差可以符合它。这就是为什么正态分布是均值标准差的可视化。假设大部分人口位于平均值附近的中心(这就是为什么它是平均值),这就是为什么我们看到抛物线形状通常与分布和数据相关联。我们的标准差给出了我们的数据如何分布的参数,平均值提供了该参数应用于每个样本的平均数。
这项技术已经被证明在机器学习中非常有效。事实上,像这样利用连续数据的大多数管道很可能包括这种形式的标准化。同样,如果你正在做一个项目,这可能是这类工作的最佳工具。下面是 Julia 中一个标准缩放器的简单编写:
function standard_scale(x::Vector{<:Number})
σ = std(x)
μ = mean(x)
[i = (i-μ) / σ for i in x]::Vector{<:Number}
end
单位长度定标器
鲜为人知的定标器是单位长度定标器。抱歉,这里有另一个链接,但我为那些想了解更多信息的人写了另一个关于单位长度缩放器和机器学习的资源:
虽然典型的重新标度器、任意重新标度器和均值规格化器需要向标准化倒退,但单位长度标度器在机器学习世界中独树一帜。这通过将每个元素除以向量的欧几里德长度来实现。
下面是 Julia 中的一个缩放器示例:
function unitl_scale(data::AbstractVector)
zeroes = [i = 0 for i in data]
euclidlen = euclidian(zeroes, data)
[i = i / euclidlen for i in data]
end
均值归一化
均值归一化是另一个有一些有用应用的定标器。然而,一般来说,有更好的选择,这是一个缩放器,我在我的经验中没有看到太多的使用。也就是说,在某些时候,这种标准化可能会派上用场,并且是比一般标准缩放器更合适的选择。
function mean_norm(array::Array{<:Number})
avg = mean(array)
b = minimum(array)
a = maximum(array)
[i = (i-avg) / (a-b) for i in array]
end
通过正态分布进行归一化涉及确定平均值的标准偏差数,而平均值归一化涉及通过从最大值中减去数据中的最小值来形成标度。与标准缩放器相比,此公式中所替换的只是用a — b替换了标准偏差。
改比例
重新调整是更简单的选择之一,在数据科学中并不常见。这并不是说这个定标器没有用途,而是说它在数据科学中的作用肯定非常小。如果这个定标器有一些不可测量的用途,那肯定会很有趣,但目前这肯定不是我使用的定标器。不管怎样,看到这些缩放器背后的数学是很酷的,所以我至少想写一个。下面是一个用 julia 写的重标度的例子:
function rescaler(x::Array{<:Any})
min = minumum(x)
max = maximum(x)
[i = (i-min) / (max - min) for i in x]
end
这可能是最不可能应用于数据科学的定标器。话虽如此,你永远不知道;这当然仍然是一个有趣的公式。幸运的是,它们在数学上也相当简单,只需要最小和最大值来缩放数据。
结束语
缩放数据是统计学原理的基础构件,对于任何想要熟悉数据科学的人来说,这绝对是一件非常重要的事情。很容易陷入这样一个陷阱,即对每个应用程序简单地使用标准定标器,而从不探索其他可用的工具。这当然是明智的,因为标准定标器通常是这项工作的最佳工具。然而,我认为在这方面做一些探索是很有意义的。
掌握这些不同的定标器肯定可以在许多不同的情况下为巨大的预测准确度提供实质性的好处。此外,对不同的定标器有一个多样化的知识和坚定的理解,可以方便地理解当它们出现问题时发生了什么。我希望这篇文章有助于提供这方面的一些背景,感谢您的阅读!
Python 中每一个令人敬畏的内置函数
原文:https://towardsdatascience.com/every-single-awesome-built-in-function-in-python-f36a23842954
看看这种语言已经提供的一些最好的 Python 方法,以及如何使用它

介绍
ython 以其 C 解释的和类似英语的语法荣耀,已经在机器学习领域运行了几年。令人惊讶的是,一种可以用世界上最有影响力和最强大的编程语言之一轻松编写的高级解释脚本语言在机器学习方面表现出色。就语言和科学计算而言,Python 的成功是当之无愧的,也是不言自明的。
虽然 Python 是一种相对容易掌握的语言,读起来像英语,语法简单,但它可能很难掌握。Python 编程语言在某些领域有很大的深度,不断接触新的模块和工具以了解更多他们碰巧正在学习的东西是很棒的——这就是计算的乐趣。外面有这么多东西,没有一个人不能探索新的东西。总是有更多的东西要学,所以热爱学习的人应该考虑数据科学或某种软件工程的职业生涯,这很有趣,而且很多人没有意识到这是非常有创造性的。
有了这种艺术,总有办法提高你的技术。为了说明这一点,我想介绍一个场景。:在我们所有的开发者故事和数据科学旅程中,有一段时间我们遇到了另一个比我们优秀得多的程序员。最棒的是我们有一个极好的机会去学习,不好的是当我们觉得我们不知道我们在做什么,因为
他/她/他们只是把我的 20 行互相嵌套的循环变成了一行方法调用。
好吧,不管怎样,也许这只是我早期和我的一个老师经历过的一个场景——但是情况表明,在你的武器库中拥有一堆方法和这些方法的小技巧是很重要的。对于 Python 来说尤其如此,我将这种语言描述为在对象旁边有一个非常有条理的生态系统。我最近写了一篇关于我最喜欢的 Python 装饰者的文章。这让我开始思考,我最喜欢的东西是什么…只是 Python..?我认为它的包装让它变得很棒。我首先想到的是标准库中令人惊奇的选择方法,这些方法总是很有用。如果你想阅读我关于装饰者的文章,这里有一个链接:
</10-of-my-favorite-python-decorators-9f05c72d9e33>
不断接触新的做事方法是很棒的,今天我们将回顾一些我一直以来最喜欢的方法,
你可能没有注意到他们。
我们将讨论 Python 编程语言中的所有内置函数。这不包括用 Python 打包的标准库中的那些。这些功能的伟大之处在于它们总是在那里帮助你。另外,对于那些想自己尝试这段代码的人来说,这里是这篇文章的笔记本:
№1: abs()
abs()方法是一种内置于 Python 核心的方法。我记得在我早期的编程经历中,当我还是一个小孩子的时候,看到各种语言中一直在使用 abs(),但不知道它是什么意思。因此,假设我们中的许多人都使用过这种方法,或者至少以前在某人的代码中见过它,这可能是明智的。这个方法到底是做什么的,这个方法的一些常规用法是什么?
abs()中的 abs()是 absolute 的简称。该函数返回两个数值之间的绝对距离。这些术语中的距离是数字上远离零的索引,例如,你离零有多少个数字,你们之间有什么。假设列表中有两个项目:
list = [5, -32]
如果我们想计算 5 是否在-32 的 3 以内,您首先需要将-32 转换为它的正等效值,然后将这两个值相减。要做到这一点,我们需要找出这个数字是否是负数,如果是,那么我们将不得不看看我们的数字是小于它还是大于它,等等。最后变成了一堆条件句。然而,有了 abs,
abs(5 - 32)27
那就简单多了。
№2:任何()
我们今天要看的第二个方法是 any()方法。any()方法可以用于许多不同的事情,通常用于列表,尽管它意味着接受任何 iterable。any 方法只是简单地检查列表的所有索引是真还是假。如果任何元素为真,它将返回 true。如果所有的元素都是假的,它将返回假。如果没有元素,它将返回 false。
any([True, False, True]) any([False, False])False any([])False
让我们把这纳入一个使用概念,我们将首先考虑以下功能:
def check_list(lst : list):
if any(lst):
print("there is now a value")
这个函数检查一个列表,看看里面是否有任何东西。这是一个更干净的选择,如果有的话,只调用(list)而不是
if len(lst) > 0:
print("There is now a value")
但是,在这种情况下,any()方法有一些重要的问题需要考虑,
性能。
第二个例子可能没有那么漂亮,但是由于 any 方法的迭代性质,它可能会更快。也就是说,这个模块有很多很酷的应用程序,所以我肯定会在库中保留一个标签,以防这种用例在您的工作中发生。
ascii()
ascii 方法提供了 Python 字符的可打印版本。也就是说,扩展的 ascii 是一种全球计算标准,例如,要让打印机正确地读取整数,而不是作为字符读取,它们需要加上 48 才能正确地驻留在 ascii 表上。基本上,这个函数可以用来将标准的 Python 字符转换成 ascii 标准。
ascii("hello")
"'hello'"
bin()
bin 方法只是将一个值(由于某种原因不能是 char)返回到一个字节的数据中。
bin(50)
'0b110010'
如果您正在从 Python 中控制汇编代码,这可能是有用的,但是该事件的可能性在统计上可能并不显著。很有可能这在 Python 中完全没用。
可调用()
如果对象是可调用的,callable()方法返回 true,否则返回 false。我认为这个函数的真正用途最初来自于产生“_ 不可调用!”错误。然而,它可能对一些在你自己的软件中相似的应用程序有用,所以这是一个值得注意的功能。
callable(5)
False
编译()
compile()方法只是编译一个添加的参数 python 文件。我们可以编译一串 Python。然后将这个编译后的 Python 放入一个可以执行的对象中。
code = 'sum([5, 10, 15])'
codeObeject = compile(code, 'sumstring', 'exec')exec(codeObeject)
对象的类型是我们的变量名:
type(codeObject)
code
枚举()
enumerate 方法将接受一个 iterable,并返回对应于每个索引的索引位置的键。每当我们需要循环计数,或者需要我们处理的每个值的索引时,这通常在 for 循环中使用。
for index, val in enumerate([5, 10, 15]):
print(index, "\n", val)0
5
1
10
2
15
在这里,我只是打印了每个值,每个值之间有一个\n,它开始一个新行。非常简单,但是很容易看出这种方法的应用领域。也就是说,enumerate 有很多不同的用例,它肯定是 Python 的一个功能。也就是说,我强烈建议选择这个来提高自己的 Python 技能。这可能是这个列表中为数不多的方法之一,实际上我可以自信地说我每天都在使用它,所以有了这种用处,就很容易明白为什么我推荐使用它了。
静态方法()
staticmethod 函数实际上是一个非常酷的函数,可以用来从 Python 中移除一些范例。我们可以调用面向对象类的函数来创建一个公共的全局版本。实际上,这意味着我们可以有条不紊地上课。
class Adder:def add_numbers(num1, num2):
return num1 + num2Adder.add_numbers = staticmethod(Adder.add_numbers)tot = Adder.add_numbers(5, 7)
print(tot)
最棒的是,我们不再需要用我们的类创建一个新的类型,我们可以直接调用这个方法。当你有一个私有方法时,这是很方便的,但是你更希望它是全局的,特别是当你不是写这样一个类的人的时候。
过滤器()
filter()方法与几乎所有其他编程语言中的 filter()方法非常相似。该方法可用于根据“掩码”过滤 iterable 掩码只是位数组的高级名称。位数组只是一个位的数组,意味着一个带索引的位的集合。一个位只是一个真/假值,通常是 1 或 0。换句话说,一个位数组只是一个布尔数组,或者一个二进制数组,例如…
[0, 1, 0, 1, 1, 0, 1, 0]
为了使用 filter()方法进行过滤,我们只需创建一个过滤函数——或表达式。我们也可以像上面一样用一个位数组来实现,但是我将使用一个函数,因为这在 Python 约定中似乎更典型:
def filter_func(x):
if x < 5:
return(False)
else:
return(True)
现在让我们制作一个数组来使用:
d = [1, 3, 5, 7, 9, 11]
最后,我们可以通过 filter()方法传递它。然而,这个函数的输出可能不是您所期望的:
z = filter(filter_func, d)<filter at 0x7fb7851834f0>
我们得到的不是过滤后的数组的返回,而是一个过滤器对象。filter 对象是预过滤元素的迭代器。为了演示如何使用一个简单的 for 循环,我将把它们拿出来:
vals = [w for w in z]
print(vals)[5, 7, 9, 11]
格式()
Python 中的一个对数据科学应用程序非常有用的函数是 format()方法。这个方法可以用一个简单的 char 将任何值转换成给定的格式。例如,我们可以使用以下语法将值. 5 转换成百分比:
x = .5
fifty_percent = format(x, '%')
我对这个函数的唯一看法,更具体地说,它在数据科学中的应用,是这个新格式化值的数据类型是而不是。我觉得这多少破坏了这个功能的价值。例如,如果有一个百分比类型,我们的值随后被转换为百分比类型,这也很好,但是看看我们得到的类型:
print(type(fifty_percent))
print(fifty_percent)<class 'str'>
50.000000%
虽然有点不足,但是,在很多情况下,这个函数都可以派上用场——而且在基本 Python 中,它肯定是值得了解的。
getattr()
getattr()是一个相当简单的方法,但是它可以用于许多不同的事情。一般来说,它并不完全用于标准的 Python 操作,但是,在某些情况下,它肯定会派上用场。考虑我们以前的加法器类,但是现在有了更多的数据:
class Adder:
numbers_added = 0
def add_numbers(num1, num2):
self.numbers_added += 1
return num1 + num2
我们现在可以使用 getattr()方法获得 numbers_added 属性:
ouradder = Adder()added = getattr(ouradder, "numbers_added")print(added)0
我认为这可以派上用场的地方是第三个位置参数,默认。该参数允许设置默认值。举一个更奇怪的例子,假设我们已经编写了一个完整的游戏,这两个类是我们的元素:
class Player:
hp = 1class Bush:
def __init__(self, size):
self.size = size
玩家职业有属性 hp,但是灌木没有生命值,所以它没有。现在让我们假设我们有一个方法来检查游戏中所有对象的健康状况,就像这样:
def check_hp(x):
print(x.hp)
如果我们让一个玩家通过这个新方法,一切都很好:
check_hp(Player())1
然而,由于 Bush 类型没有属性 hp,一旦我们在 Bush 上尝试,我们将得到一个恰当命名的属性错误:
check_hp(Bush(5))AttributeError: 'Bush' object has no attribute 'hp'
我们可以通过使用 getattr()方法减轻这种情况,并提供一个默认值零。
def check_hp(x):
hp = getattr(x, "hp", 0)
print(hp)check_hp(Bush(5))0
现在,一个完整的玩家和灌木丛列表可以毫无问题地调用这个方法,而且它会忽略那些没有这个属性的类。
全局()
globals()方法只是返回当前全局范围内所有全局定义的别名的字典。
x = globals()
print(x){'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, '__loader__': None, '__spec__': None, '__builtin__': <module 'builtins' (built-in)>, '__builtins__': <module 'buil...............
这在某些情况下非常有用,尤其是在跟踪给定全局范围的状态非常重要的情况下。
哈希()
正如所料,hash()方法返回给定 Python 对象的散列值。这有许多应用,并被广泛用于根据本质上并不完全唯一的 UUID 来跟踪代码中的对象。除了唯一性之外,唯一的区别是 UUID 的 hash()版本也是…
数据。精彩,漂亮,数据。
也就是说,UUID 的值通常是任意的并且完全唯一,而散列的值遵循特定的散列算法(散列函数)。)这通常应用于密码术中,因为散列经常被用作存储或传输信息的私有方式。把它想象成一把锁,哈希是数据上的锁,哈希函数是打开锁的钥匙。
iter()
iter()方法非常有用。它可以用来从任何类型的 iterable 中快速创建一个迭代器,如果一个特定的类型在其定义中没有绑定 iter default 函数,这将非常方便。
mylist = iter(["spaghetti", "onions", "celery"])
此外,它还可以和其他方法一起使用,比如下面的例子:
下一个()
next()方法可用于返回迭代器的下一个值。这可以用在任何类型的迭代器上。它可以用来构建你自己的全局类型的循环,这非常棒。在迭代时,您可以有效地定义全局值,而不必受限于 for 循环的边界。
mylist = iter(["spaghetti", "onions", "celery"])
x = next(mylist)
print(x)
x = next(mylist)
print(x)
x = next(mylist)
print(x)spaghetti
onions
celery
反转()
这个方法真的很简单,但是很有用。这个函数的结果在 Python 语言中随处可见。如果你熟悉 reverse 或 rev 是你正在使用的方法中的一个关键词参数,那就太惊讶了!一直以来,您都在使用依赖于 Python 基础上的这个方法的函数调用!
mylist = reversed(["spaghetti", "onions", "celery"])
x = next(mylist)
print(x)
x = next(mylist)
print(x)
x = next(mylist)
print(x)celery
onions
spaghetti
切片()
slice()方法也是处理集合的一个非常棒和有用的方法。该方法可用于选择给定 iterable 的特定索引。我们可以使用 start 参数选择单个,或者使用 start 和 stop 参数选择一个范围:
a = [5, 10, 15]
x = slice(1, 2)
print(a[x])
[5, 10]
我们还可以使用 step 参数更进一步。这个参数将从开始到结束取那个间隔,并且只返回那些值。在这里,我使用精确的方法得到字母表中的每三个字母:
# Every third letter
a = ("a", "b", "c", "d", "e", "f", "g", "h")
x = slice(1, len(a), 3)
print(a[x])
结论
Python 编程语言是一种因其高级本质而广受尊崇的语言,这肯定是有道理的。这些方法中有很多展示了高级 Python 如何获得一些开箱即用的相当激进的能力。所有这些都很容易使用,这是一个优势。在这种情况下,知道就已经成功了一半!感谢您阅读我的文章,我很高兴能够为您的 Python 技能做出贡献!
每次统计测试都要检查特征相关性
原文:https://towardsdatascience.com/every-statistical-test-to-check-feature-dependence-773a21cd6722
不同数据类型和假设的相关性和假设检验
本文涵盖了检测特征对之间相关性的统计测试,不管它们的数据类型如何,以及基于数据的属性使用哪种测试。

您经常会发现自己在探索性数据分析过程中使用统计测试。在监督设置中,可以查看特征和目标变量之间是否存在相关性,从而决定数据集是否可用于预测目标结果。在无人监督的情况下,您可能会使用统计测试来查看是否存在相互依赖的特征。这种关系可能指向冗余特征,您可能需要将其删除。您使用的测试取决于要素类型和数据集属性。
特征可以有不同的类型,即定性(名义/顺序)和定量(连续/离散)。因此,对于每对变量,我们必须使用适当的统计检验。此外,每个统计测试都假定数据集具有某些行为(例如,正态分布、单调关系等)。因此,还必须根据这些假设中的哪一个来选择统计检验。
让我们考虑虹膜数据集并设计一些特征来解释您需要了解的不同类型的统计测试
正在加载虹膜数据集。请注意,我们还创建了一个序号变量“花瓣 _len_cats ”,以显示如何处理序号数据。
定量&定量
对于变量对,其中两者都是定量的,我们使用统计测试,给我们一个称为相关性的度量。相关性被定义为两个随机变量之间的关联。在统计学中,它通常指一对变量线性相关的程度。
注意:在谈论相关性时必须提到的一个强制性警告是“相关性并不意味着因果关系”。查看这篇文章了解更多相关信息。
让我们看看可以使用的不同相关性测试。
皮尔逊相关检验
这也被称为皮尔逊积矩相关(PPMC ),用于检测变量对之间的线性关系。它返回一个介于-1 和+1 之间的值。A -1 意味着有很强的负相关性,即一个变量增加,另一个变量减少。+1 表示有很强的正相关性,即一个变量增加,另一个变量也增加。0 表示没有相关性(这也称为零相关性)。
在以下情况下,您应该使用皮尔逊相关性检验
- 两个变量都是定量的
- 两个变量都是正态分布
- 数据没有异常值
- 变量之间的关系被假定为线性的
我们可以使用 scipy 的 stats 库来计算相关性。
使用 scipy stats 计算 pearson 相关性
注意:该软件包还提供了 p 值(查看为什么 p 值很重要这里是),它表示不相关的数据集能够产生相同相关性的概率。p 值用于确保相关数是可靠的。
斯皮尔曼相关检验
这也称为 Spearman 秩相关检验或 Spearman 秩相关检验,用于检测变量之间是否存在单调关系。它还返回一个介于-1 和+1 之间的值,其中-1 表示负单调关系,+1 表示正单调关系。
在以下情况下,您应该使用 spearman 相关性检验
- 变量是数量的或顺序的
- 变量不符合正态假设
- 变量具有单调关系
注:斯皮尔曼相关或斯皮尔曼等级相关之所以如此称呼,是因为它被设计用于顺序变量。然而,由于它的非参数性质,它也用于没有正态分布的定量变量
我们可以再次使用 scipy 的 stats 包来计算 spearman 相关性。
使用 scipy stats 计算 spearman 相关性
肯德尔-陶相关检验
这也称为肯德尔秩相关检验,用于检测单调关系的存在。
你应该使用肯德尔-陶
- 变量是顺序的或数量的
- 假设变量具有单调关系
- 数据的样本量很小(因数据集而异,但粗略的经验法则是小于 500)
我们可以再次使用 scipy 的 stats 包来计算 kendall-tau 相关性。
使用 scipy stats 计算 kendall-tau 相关性
定量和定性
对于成对的变量,其中一个是定量的,一个是定性的,我们可以使用下面的统计测试
方差分析测试
方差分析(ANOVA),也称为单向 ANOVA,用于比较两个以上组的平均值(本质上,我们按定性变量分组,取定量变量的平均值)。它被设置为假设检验,以确定两个或多个组的平均值是否相等(零假设),或者是否至少一个组的平均值与其他组的平均值不同(交替假设)。既然是假设检验,我们就用 p 值来拒绝(如果 p 值<0.05) or accept the null-hypothesis.
Accepting the null hypothesis means that features do not have a dependence between them, while rejecting it means features do have a dependence between them.
Note: When you have only 2-groups, you can also use a t 检验)。然而,根据经验,t 检验和 ANOVA 的 p 值通常是一致的。
在以下情况下,您应该使用方差分析
- 因变量是定量的,自变量是定性的
- 残差是正态分布的(残差是单个值和一组所有值的平均值之间的差)
- 组间方差相等(称为“同方差”)
- 一个群体中的各个价值观彼此之间,或者在他们自己内部,并不存在依赖关系。
我们可以使用 scipy 的统计软件包来计算 ANOVA 测试 p 值。
使用 scipy stats 计算 ANOVA p 值。
克鲁斯卡尔-沃利斯试验
这也被称为 Kruskal-Wallis H 检验或 Kruskal-Wallis ANOVA,因为它是单向 ANOVA 的非参数替代方法。它再次被用作假设检验来比较多于 2 组的平均值(类似于 ANOVA ),但是关于数据集的假设是不同的。
在下列情况下,您应该应用克鲁斯卡尔-沃利斯检验
- 因变量应该是数量型或序数型的
- 自变量应该至少有两组
- 一个群体中的各个价值观彼此之间,或者在他们自己内部,并不存在依赖关系。
因此,假设的数量本质上比方差分析的数量少(即不需要正态性或同方差)
注意:当您只有 2 组时,您也可以使用曼-惠特尼 U 检验。根据经验,克鲁斯卡尔-沃利斯或曼恩-怀特尼的 p 值通常与接近
我们可以使用 scipy 的 stats 包来计算 Kruskal-Wallis 检验 p 值。
使用 scipy stats 计算 Kruskal p 值。
定性和定性
对于两个都是定性变量的变量对,我们使用卡方检验。
卡方检验
它也被称为独立性卡方检验,也是一种假设检验。我们创建一个定性变量的交叉表。(见下图)然后,如果变量之间没有关系,我们测试表中观察到的频率和预期频率之间是否有任何差异。零假设假设观察到的频率和预期的频率之间没有差异。因此,如果我们接受零假设(例如 p 值> 0.05),那么我们说变量不相互依赖。

使用 iris 数据集中的“class”和“petal_len_cats”列的交叉列表数据
在下列情况下,您应该使用卡方检验
- 两个变量都是定性的
- 所有的观察都是独立的
- 我们观察到,当数据由单独的类别值对分组时,在创建的每个组中至少有 5 个频率。
我们使用 scipy 的 stats 包来计算卡方检验的 p 值。
使用 scipy stats 计算卡方 p 值。
结论
在这篇文章中,我们介绍了检查特性依赖性所需的所有统计测试。
- 皮尔逊相关检验
- 斯皮尔曼相关检验
- 肯德尔-陶相关检验
- 方差分析检验
- 克鲁斯卡尔-沃利斯 H 检验
- 卡方检验
注:需要注意的是,即使原始特征在上述测试中没有显示出相互之间或与目标变量之间的相关性,但在特征工程中(如宁滨是一个定量变量或压缩一个定性变量),相关性可能会出现。因此,我们不应该简单地应用测试,而应该根据需要检查、理解和转换数据,以识别依赖性/关系。
参考
https://www.scribbr.com/statistics/correlation-coefficient/#spearmans-rho http://www.biostathandbook.com/spearman.html#:~:text=When to use it,tends to increase or decrease https://www.reneshbedre.com/blog/anova.html https://www.reneshbedre.com/blog/kruskal-wallis-test.html https://en.wikipedia.org/wiki/Chi-squared_test https://www.statology.org/chi-square-test-assumptions/
大家对 AI 写作工具的看法都是错的
原文:https://towardsdatascience.com/everyone-is-wrong-about-ai-writing-tools-582439953cf3
嗯,也许不是每个人,但我没有写这个标题…

Alina Constantin /更好的人工智能图像/手工人工智能/ CC-BY 4.0
我是一个人工智能作家。因此,我对这个话题有一个独特的双重视角:我的“我知道人工智能”思维和我的“我写作”思维有强烈的冲突意见。
我的 AI 脑子里不断重复着“放心吧,AI 是哑巴;它不能推理,也不能理解。”但是我的写作思维说,“嘿,我以前在区分 GPT-3 和人类方面有问题。”
当然,我的那些身份关心对他们每个人都重要的事情。正是在他们观点的交汇点上,我意识到双方都是对的——部分是对的。
一方面,即使是最复杂的人工智能写作工具——尽管令人印象深刻——也不过是精通的表象。这是一个海市蜃楼,源于我们的轻信和我们对他们完美咒语背后的现实的有限了解。
另一方面,对我们来说不幸的是,外表可能已经足够了:只要这感觉像是我写的,而且幻觉经得起你的仔细检查,那么你就满意了。
这是我写的还是人工智能写的,对你来说都不重要。
是吗?
人类语言的秘密
正如艾米丽·m·本德和亚历山大·柯勒教授在 2020 年关于人工智能语言模型的论文中雄辩地指出的那样,我们人类写作是为了交流一些东西。
总有一个意图——话语背后的目的。
然而,光靠文字无法传达这种意图。读者,无论我想在你身上引起什么样的效果,都藏在我的心里。一旦这些字母离开我的指尖永远不变地留在这一页上,它们就变成了一个空的外壳。
除非另一个头脑——你的头脑——偶然发现这些符号并赋予它们新的含义。可能和我的差不多。也许不是。
作为一个读者,你的任务是用你赋予它们的意义,结合我们共同的语言系统(英语)和你的世界知识,对我想要灌输到这些单词中的信息进行逆向工程。
钥匙来了。
就在那一刻——当你的意思压倒了我的意思,因为你重建了最初的意图,交流发生了——突然变得至关重要的是,这些话来自我的思维头脑,而不是来自人工智能随机鹦鹉。
为什么?因为如果不是一个人写的,你会毫无意义地寻找不存在的东西:
内在没有意义。也没有被收回的意图。
本文选自The algorithm Bridge,这是一份旨在弥合算法与人之间鸿沟的教育通讯。它将帮助你理解人工智能对你生活的影响,并开发工具来更好地导航未来。
https://thealgorithmicbridge.substack.com/subscribe
AI 书写工具达不到的地方
我看过 GPT-3 写的高质量散文:

信用:OpenAI
以及引人入胜的哲学论文:
然而,尽管它不可否认地掌握了语言的形式,但还是有人工智能永远达不到的地方——不管它有多好(在当前的范式下)。
在这些地方,意图比语言更重要。
如果你了解我,你在这里不是仅仅因为你想读一些东西或者得到一些未定义的值。你在这里是因为你想阅读我必须写的东西,并提取我能够提供的价值。
我作为作家对你来说有一些内在的重要性,因为你想通过我的文字,获得我隐藏在内心的交流意图。你想偷窥我的想法。
这些话只不过是达到那个目的的手段。
直截了当地说:如果这些话是 GPT-3 而不是我写的,这将扼杀写这些话的初衷。读这篇文章不会给你同样的价值,因为方法(文字)不会带你到你的目的(找回我的意图)。
让我用这句话来说明为什么(这句话来自一位老师要求学生不要用 GPT-3 写文章):
“作为一名教师,我可以说这是我害怕的事情。如果我知道我的学生提交人工智能论文,我会退出。给一个人工智能写的东西打分是对我生命的一种令人沮丧的浪费。”
读者看重的是与作者之间值得信赖的关系,而不是表面上值得信赖的关系。
但是我承认——这要看情况。
读者对意图和文字的相对重视取决于许多因素:他们是在读一本书还是一则广告?他们了解作者还是只关心内容提供了什么?他们关心被辩护的论点和论题,还是只关心花些时间阅读?
读书可以是被动消费。在这种情况下,文字最有价值。
但是当阅读成为与作者永恒的主动对话时,文字就不重要了——有价值的是潜在的意图。
无论是 GPT-3 还是任何其他类似的人工智能语言模型——无论它多么擅长连贯地表达符号——都无法提供这种能力。
但是人工智能背后有一个人类!
你可能会说提示可以有效地保留人类的意图。
最终,人工智能不会自己想出单词,而是人类以自然语言输入的形式通过聪明的推动来引导它完成可能的完成。
然而,由于我上面列出的论点,提示不能包含人工智能背后的人的意图。而且,即使他们做到了,也没有人工智能能够捕捉到最初的目的。
人工智能系统不仅缺乏故意写的能力。它们也缺乏从人类书写的文本中检索意图的能力。原因?他们无法获得意义,而意义通过将后者置于现实世界的实体中来调解意图和话语之间的关系。
底线是,每当一个人工智能出现在通信链中,就有一个有缺陷的环节。
一旦作者(提示者)接受人工智能的输出是有效的,他们就放弃了他们最初的意图——如果有的话。
对于作者来说,这可能是本文中最有价值的见解:在使用人工智能写作工具时,你可能会用人工智能决定输出的任何东西来取代你明智、有趣或有用的交流意图。
一旦你开始说,“那就够好了”,你在成品中的存在就开始减少。
最后的想法
在这篇文章中,我强调了人类和人工智能写作工具之间的比较是多么的根本错误。
这种分析是理解谁处于风险中以及哪些任务可能最终被自动化的关键。例如,规模比风格更重要,对读者的影响比作者的意图更重要的任务。
在这种情况下,风险最高。如果你在读莎士比亚的故事,你会在意是他而不是一个模仿他声音的人工智能。如果一家营销机构想为下一次活动想出一个巧妙的广告,你和他们都不会在意。
作为一名时事通讯作者,我可能处于安全-不安全光谱的上半部分。但是,广告文案、广告营销人员、普通内容创作者、自由撰稿人,甚至代笔人——以及其他人——可能会面临更加严峻的未来。
最后,请记住这一点:
AI 能写出看似人为的文字吗?是的。读者能从 AI 写的作品中获得价值吗?是的。AI 写作工具能增强写作者的能力吗?是的。AI 写作工具能否冲击对人类作家的需求?很遗憾,是的。
但是 AI 能写出如何或者为什么人类会写吗?不,现在不会,以后也不会。
作者和读者都要记住的东西(也适用于其他生成性人工智能,如文本到图像模型)。
订阅 算法桥 。弥合算法和人之间的鸿沟。关于与你生活相关的人工智能的时事通讯。
您也可以直接支持我在 Medium 上的工作,并通过使用我的推荐链接 这里 成为会员来获得无限制的访问权限! 😃
Python 中链表数据结构的一切:中级指南
在本文中,我们将重点介绍 Python 链表数据结构的完整过程。

目录
- 什么是链表
- 如何创建一个链表
- 如何遍历一个链表
- 如何在链表的末尾插入一个节点
- 如何在链表的开头插入一个节点
- 如何在链表的给定节点后插入一个节点
- 如何在链表中搜索节点
- 如何从链表中删除一个节点
- 结论
什么是链表
链表是由节点组成的数据结构,其中每个节点包含两个字段:
- 价值
- 指向下一个节点的指针(引用链接)
它是使用指针构成序列的节点的集合,其中链表的第一个节点称为头节点,而最后一个节点有一个指向 NULL 的引用链接。

作者图片
如何在 Python 中创建链表
我们已经知道链表是由节点组成的序列。
创建链表的第一步是创建一个节点结构(作为它自己的类)。
初始化时,一个节点应该接受一些数据作为值,并且将指向下一个节点的默认指针设置为 None,当我们添加更多的节点形成一个序列时,这个值将会改变。
为了测试代码,让我们创建一个值等于 5 的节点,并打印出该值和指向下一个节点的指针:
您应该得到:
5
None
正如我们所料,存储的值是 5 ,到下一个节点的引用链接是 None 。
让我们继续创建几个单独的节点:
在这一点上,我们有三个没有相互连接的节点:第一个节点、第二个节点、第三个节点。
下一步是为一个链表创建一个结构(作为它自己的类)。
初始化时,为了启动一个空链表,链表应该只将头节点设置为 NULL:
要开始向链表添加节点,我们希望我们创建的 first_node 成为链表的头节点:
这将创建一个有一个节点的链表(这将是头节点)。
回想一下,每个节点都有一个设置为 NULL 的引用链接。为了创建节点序列,我们将设置每个引用链接指向下一个节点:
现在我们已经成功地创建了一个包含三个节点的链表,看起来应该是这样的:

作者图片
在这一节中,我们一直在向链表添加节点,而不是手动添加,而不是以编程方式添加。在接下来的部分中,我们将探索如何在链表的末尾、开头和特定位置插入节点。
如何在 Python 中遍历链表
在上一节中,我们创建了链表 llist ,现在我们要遍历链表并打印出每个节点的值。
回想一下,链表的初始构造由下式给出:
单向链表只能向前遍历。
逻辑相当简单:我们将从头节点开始,打印它的当前值,然后移动到下一个节点(假设引用链接不为空)。
只要节点中有数据,我们就会继续移动到下一个节点。

作者图片
在本节中,我们将实现一个方法 print_llist() ,用于遍历一个链表,并为 LinkedList() 类打印每个节点的值。
它将被添加为 LinkedList() 类的方法:
让我们测试创建与上一节相同的列表,并打印出每个节点的值:
您应该得到:
5
7
15
如何在 Python 中的链表末尾插入一个节点
在前面的一节中,我们展示了一个如何用 Python 创建链表的例子。
我们使用的方法是手动的,而不是编程的,因为我们必须手动将节点链接在一起,并添加到下一个节点的引用链接。
在本节中,我们将实现一个方法 insert_at_end() ,用于为 LinkedList() 类在链表的末尾插入节点(基本上是追加新节点)。
让我们重用我们在上一节中构建的链表:
为了在 Python 中的链表末尾添加节点,我们应该遵循类似于在 Python 中遍历链表的逻辑。
我们从头节点开始:
- 如果头节点存在,那么我们移动到下一个节点
- 如果头节点为空,那么我们要添加的节点就成为头节点
假设头节点存在,我们移动到下一个节点(假设引用链接不为空)。
只要引用链接指向另一个有值的节点,并且它不是最后一个节点(引用链接为空),我们将继续移动到下一个节点。
一旦我们到达最后一个节点,我们将在它后面插入新的节点,并将它的引用链接设为空。

作者图片
在本节中,我们将实现一个方法 insert_at_end() ,用于在 LinkedList() 类的链表末尾插入节点。
它将被添加为 LinkedList() 类的方法:
现在让我们试着创建与本教程的第二部分相同的列表,看看我们是否得到相同的结果:
您应该得到:
5
7
15
这和我们手工创建链表的时候一模一样。

作者图片
使用 insert_at_end() 方法,我们可以在 Python 中的链表末尾添加更多的节点。
如何在 Python 中的链表开头插入一个节点
在上一节中,我们实现了一个在链表末尾插入节点的方法。
但是如果我们需要在链表的开头添加一个节点呢?这就是我们将在本节中讨论的内容!
从逻辑和代码的角度来看,在链表的开头插入一个节点要简单得多。
链表的第一个节点是一个头节点,如果我们需要在它之前插入一个节点,我们应该简单地拥有一个节点,该节点有一个到头节点的引用链接(当然,如果链表中没有节点,那么新节点就成为头节点)。

作者图片
在本节中,我们将实现一个方法insert _ at _ begin(),用于在 LinkedList() 类的链表的开头插入节点。
它将被添加为 LinkedList() 类的方法:
现在让我们创建与本教程前面章节相同的列表,并在链表的开头添加一个值为“9”的节点:
您应该得到:
9
5
7
15

作者图片
使用 insert_at_beginning() 方法,我们可以在 Python 中的链表的开头添加更多的节点。
如何在 Python 中在链表的给定节点后插入节点
到目前为止,在本教程中,我们讨论了如何在 Python 中的链表的开头和结尾添加元素。
在这一节中,我们将探索如何在 Python 中的链表的给定节点后添加元素。
在给定节点后插入节点时,第一步是在链表中找到前一个节点。
为了找到前一个节点,我们将从它的头节点开始遍历链表,一直到找到前一个节点。
一旦我们找到了的前一个节点,我们将把一个新节点的引用链接从 NULL 改为前一个节点的引用链接,然后我们将把前一个节点的引用链接改为新节点。

作者图片
在本节中,我们将实现一个方法 insert_after_node() ,用于在 LinkedList() 类的链表的给定节点之后插入节点。
它将被添加为 LinkedList() 类的方法:
现在,让我们创建与本教程的上一节中相同的列表,并在链表中值为“7”的节点后添加值为“10”的节点:
您应该得到:
9
5
7
10
15

作者图片
使用 insert_after_node() 方法,我们可以在 Python 中的链表的给定节点后添加一个节点。
如何在 Python 中搜索链表中的节点
我们已经知道如何创建一个链表,假设我们用 Python 创建了一个链表。
现在我们想在这个链表中搜索一个节点,检查它是否存在。
逻辑相当简单:我们将从头节点开始,并开始移动到下一个节点(一个节点接一个节点,并且假设引用链接不为空)。
我们会将每个节点的值与我们正在搜索的值进行比较,一旦找到节点就打印“找到节点”,或者如果我们遍历了链表没有找到我们正在搜索的值,就打印“没有找到节点”。

作者图片
在本节中,我们将实现一个方法 search_node() ,用于在链表中为 LinkedList() 类搜索一个节点。
它将被添加为 LinkedList() 类的方法:
现在,让我们创建与本教程的前一节中相同的列表,并搜索值为“15”的节点:
您应该得到:
Node found

作者图片
使用 search_node() 方法,我们可以在 Python 中搜索链表中的一个节点。
如何在 Python 中删除链表中的节点
我们已经知道如何在 Python 中搜索链表中的节点。
现在我们想从 Python 的链表中删除一个节点。
可以想象,这个过程将类似于遍历一个链表并在链表中搜索一个给定的节点。

作者图片
逻辑相当简单:我们将从头节点开始,检查它是否是我们想要删除的节点:
- 如果是,那么我们将把头节点重新指向下一个节点。
- 如果没有,那么我们将使用它作为当前节点,并检查我们是否想要删除下一个节点(该过程一个节点接一个节点地继续,直到我们从链表中找到要删除的节点)。
在本节中,我们将实现一个方法 delete_node() ,用于从 LinkedList() 类的链表中删除一个节点(该方法假设您试图删除一个存在于链表中的节点)。
它将被添加为 LinkedList() 类的方法:
现在让我们创建与本教程的前一节中相同的列表,并删除值为“5”的节点:
您应该得到:
9
7
10
15

作者图片
使用 delete_node() 方法,我们可以从 Python 中的链表中删除一个节点。
结论
这篇文章是对 Python中的链表数据结构及其功能的介绍性演练,学习这些功能很重要,因为它们在编程和机器学习的许多领域中使用。
如果你有任何问题或者对编辑有任何建议,请随时在下面留下评论,并查看我的更多数据结构文章。
原载于 2022 年 4 月 11 日https://pyshark.com。
关于 Python 数值数据类型的一切:初学者指南
在本文中,我们将探索 Python 数字数据类型。

Kai Gradert 在 Unsplash 上拍摄的照片
目录
- 介绍
- 整数
- 浮点数
- 复数
- 数值运算
- 例子
- 结论
介绍
在 Python 中,有三种截然不同的数字类型:整数、浮点数和复数。数字是由数字文字创建的,或者是内置函数和运算符的结果。
学习每种编程语言中的数据类型对于理解代码和程序是必不可少的。
数值数据类型广泛用于 Python 中构建的许多数学、统计、数据科学和机器学习解决方案中。
Python int(整数)数值类型
pythonint(integer)numeric 类型表示一个整数,可以是正数,也可以是负数,长度不限。
在 Python 中, int() 构造函数可以用来创建一个整数。
考虑下面的例子:
a = 1
b = -2
c = int(5)
d = int('6')
print(type(a))
print(type(b))
print(type(c))
print(type(d))
print(a, b, c, d)
输出:
class 'int'
class 'int'
class 'int'
class 'int'
1 -2 5 6
Python float(浮点数)数值类型
Python float (浮点数)numeric 类型表示包含一个或多个小数的数字,它可以是正数,也可以是负数(包括无穷大)。
在 Python 中, float() 构造函数可以用来创建浮点数。
考虑下面的例子:
a = 1.0
b = -1.10
c = float('5.5')
d = float('-inf')
print(type(a))
print(type(b))
print(type(c))
print(type(d))
print(a, b, c, d)
输出:
class 'float'
class 'float'
class 'float'
class 'float'
1.0 -1.10 5.5 -inf
Python complex(复数)数值类型
Python 复数(复数)数值类型表示一个包含一个实部和一个虚部的复数,由两个实数构造而成。
在 Python 中, complex() 构造函数可以用来创建一个复数。
考虑下面的例子:
a = 1+3j
b = -2+5j
c = complex(3,-7)
d = complex('1')
print(type(a))
print(type(b))
print(type(c))
print(type(d))
print(a, b, c, d)
输出:
class 'complex'
class 'complex'
class 'complex'
class 'complex'
(1+3j) (-2+5j) (3-7j) (1+0j)
Python 中的数值运算
所有数值类型都支持以下操作:

作者图片
例子
了解数字数据类型及其属性对于运算来说至关重要
加法运算
执行整数加法将导致整数类型输出:
a = 1
b = 2
c = a+b
print(c)
#Output: 3
执行整数和浮点的加法将导致浮点类型的输出:
a = 1
b = 2.0
c = a+b
print(c)
#Output: 3.0
减法运算
与加法运算类似,从整数中减去整数将产生整数类型的输出:
a = 5
b = 3
c = a-b
print(c)
#Output: 2
从整数中减去浮点数将产生浮点数类型的输出:
a = 5
b = 3.0
c = a-b
print(c)
#Output: 2.0
您也可以减去负数,这将导致加法运算:
a = 3
b = -6
#Operation: 3 - (-6)
c = a-b
print(c)
#Output: 9
乘法运算
执行整数乘法将产生整数类型的输出:
a = 5
b = 2
c = a*b
print(c)
#Output: 10
执行整数乘以浮点运算将产生浮点型输出:
a = 5
b = 2.0
c = a*b
print(c)
#Output: 10.0
除法运算
执行整数除法将导致浮点型输出:
a = 9
b = 3
c = a/b
print(c)
#Output: 3.0
执行整数除以浮点运算将产生浮点型输出:
a = 9
b = 3.0
c = a/b
print(c)
#Output: 3.0
结论
在本文中,我们探索了 Python 数字数据类型,包括整数、浮点数和复数。
作为学习 Python 的下一步,请考虑阅读以下文章中关于 Python 数据结构的内容:
原载于 2022 年 12 月 12 日【https://pyshark.com】。
关于 Python 元组数据结构的一切:初学者指南
在本文中,我们将重点介绍 Python 元组数据结构的完整过程

托尔比约恩·赫尔格森在 Unsplash 上拍摄的照片
目录
- 什么是 Python 元组
- 如何创建 Python 元组
- 如何从 Python 元组中访问元素
- 如何对 Python 元组切片
- 结论
什么是 Python 元组
Python tuple 是一种序列数据类型,它允许我们在一个逗号分隔的数据实例中组合几个项目。
元组可以包含相同或不同数据类型的元素,并且它是不可变的,这意味着我们不能在 Python 中改变元组对象在创建后的大小和内容(除非将其转换为不同的数据结构并创建新的元组)。
如何创建 Python 元组
用 Python 创建一个元组非常简单。你需要把所有用逗号分隔的条目放入括号中。
请记住,您可以将不同的数据类型放在一起,例如整数、浮点、布尔甚至其他元组。
这里有几个例子:
具有字符串元素的元组
Output:('Apple', 'Banana', 'Orange', 'Pineapple')
具有整数元素的元组
Output:(1, 2, 3, 4)
具有混合类型元素的元组
Output:('Apple', 1, True, 2.4)
具有嵌套元素的元组
在上面的例子中,元组中的每一项只包含一个值。
如果我们希望每个项目存储两个值呢?Python 元组也允许我们这样做!
例如,您想创建一个产品及其价格的元组。您的数据如下:一个苹果的价格是 1 美元,一个香蕉的价格是 0.70 美元。
为了给元组中的每个项目添加两个或更多的值,我们需要将它们放在括号中,并用逗号分隔。
看起来像是在一个元组中有一个元组(嵌套的):
Output:(('Apple', 1), ('Banana', 0.7))
如何从 Python 元组中访问元素
Python 元组的一个重要且非常有用的属性是它是一个索引序列,这意味着对于一个有 n 个元素的元组,第一个元素的索引= 0,第二个元素的索引= 1,一直到 n -1。
使用索引访问元组中的元素
索引也可以反过来,这意味着第一个元素的索引= — n ,第二个元素的索引= — n +1,一直到-1。
为了便于展示,请看下面的视觉效果:

作者图片
我们可以看到元组中的' Apple '元素有两个索引:0 和-4。
让我们用 Python 重新创建这个元组:
现在,我们想打印出元组中的第一个元素。
从前面我们知道,它有两个索引:0 和-4,所以我们可以两个都尝试,看看输出是否相同。
Output:Apple
Apple
在元组中查找元素
假设我们有以下包含字符串元素的元组:
Output:('Apple', 'Banana', 'Orange', 'Pineapple')
并且您想要查找“香蕉”元素的索引。
这可以通过使用简单地找到。index() 方法将某个值作为参数,并在 Python 元组中找到它的索引:
Output:1
我们很快发现“Banana”元素位于元组的索引 1 处。
如何对 Python 元组切片
在前一节中,我们展示了如何使用精确索引从 Python tuple 中访问元素。
在这一节中,我们将展示当您想要访问某个范围的元素时,例如,前两个或后两个元素,该怎么做。
回想一下,为了使用索引从元组中检索元素,我们将它放在方括号 [] 中。
切片使用相同的方法,但是我们不是传递单个索引值,而是传递一个范围。
Python 中的一个范围使用以下语法传递 [ 从 : 到。
不指定 from 和 to 的切片
如果从到到你不放任何索引到,默认情况下 Python 会取整个元组。
下面两行代码产生了相同的输出:
Output:('Apple', 'Banana', 'Orange', 'Pineapple')
('Apple', 'Banana', 'Orange', 'Pineapple')
通过指定 from 进行切片
您可以通过从中指定来分割一个元组,它将从您指定的索引中获取元素,直到元组结束。
例如,您希望打印元组中的最后两个元素。请记住,您可以同时使用索引和反向索引。
Output:('Orange', 'Pineapple')
('Orange', 'Pineapple')
指定为的切片
您可以通过将指定为来分割一个元组,它将从元组的开头开始获取元素,直到您指定的索引处的元素-1。
例如,您希望打印元组的前两个元素。请记住,您可以同时使用索引和反向索引。
以下代码将产生所需的输出:
Output:('Apple', 'Banana')
注意这里,我们将指定为 index = 2,重要的是要记住 index = 3 实际上是第三个元素的位置。Python 中元组切片的工作方式是遍历元素,直到指定的到索引(在我们的例子中是 2 ),并包括该索引之前的所有元素,但不包括到索引下的元素。
结论
本文是对 Python 元组数据结构及其功能的介绍性演练,学习这些数据结构及其功能非常重要,因为它们在编程和机器学习的许多领域中都有使用。
如果你有任何问题或对一些编辑有建议,请随时在下面留下评论,并查看更多我的数据结构文章。
原载于 2022 年 3 月 19 日【https://pyshark.com】。**
Python 中关于栈数据结构的一切
原文:https://towardsdatascience.com/everything-about-stack-data-structure-in-python-e0e6e6ec2ff3
在本文中,我们将重点介绍 Python 堆栈数据结构的完整过程。

伊瓦·拉乔维奇在 Unsplash 上的照片
目录
- 什么是堆栈
- 如何在 Python 中创建堆栈
- 如何在 Python 中检查堆栈是否为空
- 如何在 Python 中向堆栈添加元素
- 如何在 Python 中从堆栈中移除元素
- 结论
什么是堆栈
堆栈是一种类似数组的数据结构,它使用后进先出(LIFO)方法存储项目。
你可以想象一下储物盒,每个盒子放在另一个盒子上面,就像这样:

作者图片
正如你所看到的,盒子是按照以下顺序添加的:1 -> 2 -> 3,其中盒子 3 是最后添加的。
现在,如果我们想要得到盒子 1,我们将需要首先除去盒子 3,然后盒子 2,然后得到盒子 1,使它成为一个相反的顺序:3 -> 2 -> 1。
上面的例子显示了后进先出原则背后的方法论:最后添加的项目将首先被删除。
如何在 Python 中创建堆栈
在 Python 中创建堆栈有多种方式:
在本教程中,我们将使用最简单的方法,并使用列表数据结构在 Python 中创建堆栈。
创建一个空栈与使用方括号 [] 在 Python 中创建一个列表是一样的。
让我们从创建一个新类并将其初始化为一个空堆栈开始:
然后,我们将创建一个空堆栈并打印输入输出:
您应该得到:
[]
如何在 Python 中检查堆栈是否为空
在处理堆栈时,我们经常需要知道它们是否为空,这是一些操作的要求。
例如,当从栈中移除元素时,我们需要确保有元素要移除(因为我们不能从空栈中移除元素)。
因为我们的栈数据结构的实现是基于 Python 列表的,所以我们可以简单地检查栈的长度来确定它是否为空。
在本节中,我们将实现一个方法 check_empty() ,用于检查 Stack() 类的堆栈中是否有元素。
它将被添加为 Stack() 类的方法:
我们可以用一个空栈来测试它:
您应该得到:
True
如何在 Python 中向堆栈添加元素
一旦我们有了一个栈,我们就可以开始向它添加元素。
这个堆栈操作被称为将推入堆栈。
因为我们的栈是作为一个列表创建的,所以我们可以使用。添加元素的Python 列表的 append() 方法。
在这一节中,我们将实现一个方法 push() ,用于向 Stack() 类的堆栈中添加元素。
它将被添加为 Stack() 类的方法:
让我们创建与示例部分相同的堆栈,并打印出堆栈的元素:
您应该得到:
[1, 2, 3]

作者图片
如何在 Python 中从堆栈中移除元素
一旦我们有了一个包含元素的堆栈,我们可能想要从堆栈中移除一些元素。
回想一下,堆栈使用 LIFO(后进先出)方法,这意味着我们只能在每次迭代中删除堆栈顶部的元素。
这个堆栈操作被称为从堆栈中弹出。
因为我们的栈是作为一个列表创建的,所以我们可以使用。用于移除元素的 Python 列表的 pop() 方法。
在本节中,我们将实现一个方法 pop() ,用于从 Stack() 类的堆栈中移除元素。
它将被添加为 Stack() 类的方法:
让我们测试创建与前面章节中的相同的堆栈,然后弹出一个元素并打印出堆栈的元素:
您应该得到:
[1, 2]

作者图片
结论
这篇文章是一篇介绍性的演练,介绍了 Python 中的堆栈数据结构及其功能,学习这些功能很重要,因为它们在编程和机器学习的许多领域中使用。
如果你有任何问题或者对编辑有任何建议,请在下面留下你的评论,并查看更多我的数据结构文章。
原载于 2022 年 4 月 15 日 https://pyshark.com**的 。
关于支持向量分类的一切——以上及以上
原文:https://towardsdatascience.com/everything-about-svm-classification-above-and-beyond-cc665bfd993e

图片来自修补洞见
支持向量分类综述
当涉及到识别和解决你所在领域的具体问题时,机器学习提供了很多可能性。试图掌握机器学习有时既复杂又难以理解。大多数初学者开始学习回归是因为它的简单和容易,然而这并没有解决我们的目的!当涉及到为不同的应用使用不同类型的算法时,人们可以做的不仅仅是回归。
分类是各种监督学习算法的一个应用领域。除了不自然的趋势和势头,这些分类器通常具有相似的性能。然而,当涉及到数据的复杂性及其范围时,支持向量机可能是制定更好决策的好选择。
在继续这篇文章之前,我建议你快速通读一下我以前的文章——随机森林回归快速而肮脏的指南和释放支持向量回归的真正力量来研究集成学习和支持向量机的概念。如果你觉得这篇文章内容丰富,请考虑为这篇文章鼓掌,让更多的人看到它,并关注我获取更多#MachineLearningRecipes。
支持机罩下的向量机
机器学习中发生的一切都有直接或间接的数学直觉与之相关联。类似地,使用支持向量机,大海中有大量的数学。有各种各样的概念,例如向量的长度和方向、向量点积以及与算法相关的线性可分性。

图片来自 ResearchGate
虽然这是研究的一个重要部分,但它主要由研究人员进行研究,试图优化和/或扩展算法。在这一节中,我们将只研究定义超平面和分类器背后的数学直觉。二维可线性分离的数据可以用一条线分开。直线的函数由 y=ax+b 给出,用 x1 重命名 x,用 x2 重命名 y,得到 ax1 x2+b = 0。
如果我们定义 x = (x1,x2)和 w = (a,1),我们得到:w⋅x+b=0.这是超平面的方程。一旦定义了超平面,就可以用它来进行预测。假设函数 h .超平面之上或之上的点将被分类为 class +1,超平面之下的点将被分类为 class -1。
什么是支持向量机?
支持向量机或 SVM 具有监督学习算法,可用于回归和分类任务。由于它的健壮性,它通常被用来解决分类任务。在该算法中,数据点首先在 n 维空间中表示。然后,该算法使用统计方法来寻找分隔数据中存在的各种类的最佳线。

图片来自 ResearchGate
如果数据点被绘制在二维图中,那么决策边界被称为直线。然而,如果有两个以上的维度,这些维度被称为超平面。虽然可能有多个超平面来分隔类别,但 SVM 选择了类别之间距离最大的超平面。
超平面和最近的数据点(称为支持向量)之间的距离被称为余量。保证金可以是软保证金,也可以是硬保证金。当数据可以分成两个不同的集合,并且我们不希望有任何错误分类时,我们在交叉期间使用我们的训练集权重来训练具有硬边界的 SVM。
然而,当我们需要我们的分类器有更多的通用性时,或者当数据不能清楚地分成两个不同的组时,我们应该选择一个软余量,以便允许一些非零的误分类。支持向量机通常用于复杂的小型健壮数据集。

图片来自 Pixabay
没有核的支持向量机可能具有与逻辑回归算法相似的性能,因此可以互换使用。与考虑所有数据点的逻辑回归算法不同,支持向量分类器仅考虑最接近超平面的数据点,即支持向量。
SVM 分类图书馆
Scikit-learn 包含许多有用的库,可以在一组数据上实现 SVM 算法。我们有几个库可以帮助我们顺利实现支持向量机。这主要是因为这些库提供了我们需要的所有必要的函数,这样我们就可以轻松地应用 SVM 了。
首先,有一个 LinearSVC()分类器。顾名思义,这个分类器只使用线性核。在 LinearSVC()分类器中,我们不传递内核的值,因为它仅用于线性分类目的。Scikit-Learn 提供了另外两个分类器— SVC()和 NuSVC(),它们用于分类目的。

图片来自 SkLearn
这些分类器大多相似,只是在参数上有一些差异。NuSVC()类似于 SVC(),但是使用一个参数来控制支持向量的数量。既然你已经掌握了什么是支持向量分类的关键,我们将试着构建我们自己的支持向量分类器。构建这个回归模型的代码和其他资源可以在这里找到。
步骤 1:导入库并获取数据集:
第一步,导入我们将用于实现本例中的 SVM 分类器的库。没有必要只在一个地方导入所有的库。Python 给了我们在任何地方导入库的灵活性。首先,我们将导入 Pandas、Numpy、Matplotlib 和 Seaborn 库。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
import os
for dirname, _, filenames **in** os.walk('/kaggle/input'):
for filename **in** filenames:
print(os.path.join(dirname, filename))

图片来自 SkLearn
在这个例子中,使用了预测脉冲星恒星的数据集。该数据集包含 16,259 个由 RFI/噪声引起的虚假示例和 1,639 个真实脉冲星示例。每行首先列出变量,类标签是最后一项。使用的类别标签是 0(负)和 1(正)。
步骤 2:探索性数据分析和可视化:
成功加载数据后,我们的下一步是浏览数据以获得关于数据的见解。数据集中有 9 个变量。8 是连续变量,1 是离散变量。离散变量是 target_class 变量。它也是目标变量。
df.columns = ['IP Mean', 'IP Sd', 'IP Kurtosis', 'IP Skewness',
'DM-SNR Mean', 'DM-SNR Sd', 'DM-SNR Kurtosis', 'DM-SNR Skewness', 'target_class']
df.column
df['target_class'].value_counts()/np.float(len(df))
df.info()plt.figure(figsize=(24,20))
plt.subplot(4, 2, 1)
fig = df.boxplot(column='IP Mean')
fig.set_title('')
fig.set_ylabel('IP Mean')
重命名列、删除前导空格以及处理丢失的值也是这一步的一部分。清理完数据后,将数据集可视化,以了解趋势。search 是一个优秀的库,可以用来可视化数据。
步骤 3:特征工程和拟合模型:
特征工程是利用领域知识通过数据挖掘技术从原始数据中提取特征的过程。对于这个模型,我选择了只有数值的列。

图片来自 Pixabay
为了处理分类值,应用了标签编码技术。默认的超参数意味着 C=1.0,内核=rbf,gamma=auto 以及其他参数。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
选择所需参数后,下一步是从 sklearn 库中导入 train_test_split,该库用于将数据集拆分为训练和测试数据。之后,从 sklearn.svm 导入 SVR,并在训练数据集上拟合模型。
第四步:准确度、精确度和混淆矩阵:
需要检查分类器是否过拟合和欠拟合。训练集准确度分数是 0.9783,而测试集准确度是 0.9830。这两个值相当。所以,不存在过度拟合的问题。精度可以定义为正确预测的阳性结果占所有预测阳性结果的百分比。
它可以被给定为真阳性(TP)与真阳性和假阳性之和(TP + FP)的比值。混淆矩阵是一种总结分类算法性能的工具。混淆矩阵将给出分类模型性能和模型产生的错误类型的清晰图像。

图片来自 ResearchGate
它给出了按类别细分的正确和错误预测的摘要。这个概要以表格的形式表示。分类报告是评估分类模型性能的另一种方式。它显示模型的精确度、召回率、f1 和支持分数
svc=SVC()
svc.fit(X_train,y_train)y_pred=svc.predict(X_test)
print('Model accuracy score with default hyperparameters: **{0:0.4f}**'. format(accuracy_score(y_test, y_pred))
另一个直观测量分类模型性能的工具是 ROC 曲线。ROC 曲线代表受试者工作特性曲线。ROC 曲线是显示分类模型在各种分类阈值水平的性能的图。
SVM 分类器的优点:
支持向量分类具有下面提到的某些优点:
- 当阶级之间有明确的界限时,SVM 相对来说运作良好。
- SVM 在高维空间中更有效,并且相对内存效率高
- SVM 在维数大于样本数的情况下是有效的。

图片来自 SkLearn
SVM 分类器的缺点:
SVM 在处理分类时面临的一些缺点如下所述:
- SVM 算法不适合大数据集。
- 当数据集具有更多噪声时,即目标类别重叠时,SVM 执行得不是很好。如果每个数据点的特征数量超过了训练数据样本的数量,则 SVM 将表现不佳。
- 由于支持向量分类器通过将数据点放在分类超平面的上方和下方来工作,因此没有对分类的概率解释。
至此,我们已经到了这篇文章的结尾。我希望这篇文章能帮助你了解 SVC 算法背后的思想。如果你有任何问题,或者如果你认为我犯了任何错误,请联系我!通过 LinkedIn 与我联系,并查看我的其他机器学习食谱:
你需要知道的关于艾伯特,罗伯塔和迪沃伯特的一切
回顾不同 BERT 变形金刚的异同,以及如何从拥抱脸变形金刚库中使用它们

内特·雷菲尔德在 Unsplash 上拍摄的照片
在这篇文章中,我将解释你需要知道的关于 Albert、Roberta 和 Distilbert 的一切。如果你不能从名字上看出来,这些模型都是原始最先进的变压器 BERT 的修改版本。这三个型号,连同伯特,是目前最受欢迎的变形金刚。我将回顾这些模型与 BERT 的不同(和相似)之处,对于每个模型,我将包含代码片段,演示如何使用拥抱面部变形库中的每个模型。注意,这篇文章写于 2022 年 7 月,所以拥抱脸的早期/未来版本可能不起作用。还要注意的是,本文假设您对 BERT transformer 有所了解,所以在阅读本文之前要先了解这一点。然而,我将在本文中快速回顾 BERT,作为一个简短的概述。
伯特
BERT——或来自 Transformers 的双向编码器表示——是第一个建立在原始编码器-解码器转换器基础上的转换器,它使用掩蔽语言建模和下一句预测任务的自我监督训练来学习/产生单词的上下文表示。BERT 的核心架构由 12 个编码器模块堆叠而成(来自原始编码器-解码器转换纸)。为了对其他任务进行微调,例如(但不限于)问题回答、摘要和序列分类,BERT 在堆叠编码器的顶部添加了额外的线性层。这些额外的层用于根据 BERT 正在解决的任务生成特定的输出。然而,重要的是要记住,BERT 的原始的、核心的、不可改变的部分是来自堆叠的双向编码器的输出。这些模块使得 BERT 如此强大:通过定制/添加任何特定的层组合,您几乎可以配置 BERT 来解决任何任务。在本文中,我将向您展示这样配置 BERT 的代码。你可以从拥抱脸变形库这里找到如何使用 BERT 的代码。
为了方便起见,下面是如何使用 BERT 完成任何通用任务的代码片段:
from transformers import BertModel
class Bert_Model(nn.Module):
def __init__(self, class):
super(Bert_Model, self).__init__()
self.bert = BertModel.from_pretrained('bert-base-uncased')
self.out = nn.Linear(self.bert.config.hidden_size, classes)
def forward(self, input):
_, output = self.bert(**input)
out = self.out(output)
return out
上面的代码可以用来构建一个通用的 Pytorch BERT 模型,该模型可以在任何其他未指定的任务上进行微调。正如你所看到的,我没有下载一个已经为特定任务设计的特定的 BERT 模型,比如 BERTForQuestionAnswering 或 BERTForMaksedLM,而是下载了一个未经训练的 BERT 模型,它没有附带任何“头”。相反,我在上面添加了我自己的线性层,然后可以配置为其他任务,这些任务没有列在 HuggingFace transformer 库已经完成的任务中,你可以在这里找到。虽然上面的代码不一定是您想要的,但是您可以浏览 hugging face 提供的模型列表并使用它们的 API。例如,下面是如何为 BERT 建立一个屏蔽语言模型。
from transformers import BertTokenizer, BertForMaskedLM
from torch.nn import functional as F
import torch
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertForMaskedLM.from_pretrained('bert-base-uncased', return_dict = True)
text = "The capital of France, " + tokenizer.mask_token + ", contains the Eiffel Tower."
input = tokenizer.encode_plus(text, return_tensors = "pt")
mask_index = torch.where(input["input_ids"][0] == tokenizer.mask_token_id)
output = model(**input)
logits = output.logits
softmax = F.softmax(logits, dim = -1)
mask_word = softmax[0, mask_index, :]
top_10 = torch.topk(mask_word, 10, dim = 1)[1][0]
for token in top_10:
word = tokenizer.decode([token])
new_sentence = text.replace(tokenizer.mask_token, word)
print(new_sentence)
屏蔽语言建模本质上是一个“填空任务”,其中模型屏蔽一个标记,并训练自己使用屏蔽标记周围的上下文来准确预测屏蔽标记是什么。在上面的示例中,代码使用 BERT 列出屏蔽令牌的前 10 个候选令牌。你可以在这里阅读发生的事情的更详细的描述。该代码片段的输出是:
The capital of France, paris, contains the Eiffel Tower.
The capital of France, lyon, contains the Eiffel Tower.
The capital of France, lille, contains the Eiffel Tower.
The capital of France, toulouse, contains the Eiffel Tower.
The capital of France, marseille, contains the Eiffel Tower.
The capital of France, orleans, contains the Eiffel Tower.
The capital of France, strasbourg, contains the Eiffel Tower.
The capital of France, nice, contains the Eiffel Tower.
The capital of France, cannes, contains the Eiffel Tower.
The capital of France, versailles, contains the Eiffel Tower.
罗伯塔
罗伯塔是伯特的一个简单但非常受欢迎的替代者/继承者。它主要通过仔细和智能地优化 BERT 的训练超参数来改进 BERT。几个简单明了的变化一起增强了 Roberta 的性能,使它在 BERT 设计解决的几乎所有任务上都优于 BERT。值得注意的一个有趣事实是,在 Roberta 出版的时候,另一个流行的新变形金刚, XLNet ,也在一篇研究论文中发表/介绍。然而,与 XLNet 不同的是,XLNet 引入的变化比 Roberta 引入的变化更难实现,这只会增加 Roberta 在 AI/NLP 社区中的受欢迎程度。
正如我之前提到的,罗伯塔实际上使用了与伯特相同的架构。然而,与 BERT 不同的是,在预训练期间,它只通过掩蔽语言建模进行预训练(BERT 也通过下一句预测进行预训练)。下面是 Roberta 用来获得更好性能的一些超参数变化。
- 更长的训练时间和更大的训练数据(从 16GB 到 160GB 增加 10 倍)
- 从 256 到 8000 的更大批量和从 30k 到 50k 的更大词汇量
- 使用更长的序列作为输入,但是 Roberta 仍然像 BERT 一样有 512 个标记的最大标记限制
- 动态掩蔽允许每次将序列输入模型时掩蔽模式不同,这与使用相同掩蔽模式的 BERT 相反。
知道如何使用拥抱脸变形库中的 BERT 确实有助于理解 Roberta(以及本文中描述的所有模型)是如何编码的。你可以从拥抱脸变形库这里学习如何使用 BERT。按照那篇文章中的代码,使用拥抱脸中的 Roberta 非常简单。
from transformers import RobertaModel
import torch
import torch.nn as nnclass RoBERTa_Model(nn.Module):
def __init__(self, classes):
super(RoBERTa_Model, self).__init__()
self.roberta = RobertaModel.from_pretrained('roberta-base')
self.out = nn.Linear(self.roberta.config.hidden_size, classes)
self.sigmoid = nn.Sigmoid()
def forward(self, input, attention_mask):
_, output = self.roberta(input, attention_mask = attention_mask)
out = self.sigmoid(self.out(output))
return out
上面的代码展示了如何构建一个通用的 Roberta Pytorch 模型。如果您将其与基于 BERT 的模型的代码进行比较,您确实可以看到我们实际上只是用 Roberta 替换了 BERT!这确实有道理——毕竟,罗伯塔更像伯特,但受过更好的训练。但是你很快就会发现,阿尔伯特和迪翁伯特也是如此。因为这些模型都是 BERT 的修改版本,所以拥抱脸代码的工作方式是,使用任何模型时,您只需从上面获取 BERT 代码,然后用 Roberta 替换所有 BERT 术语(即,改为导入 Roberta 模型,使用正确的模型 id“Roberta-base”,并导入正确的 Roberta tokenizer)。因此,如果你想用 Roberta 做屏蔽语言建模、抽取式问题回答或其他任何事情,你可以使用上面的 BERT 代码或这里的并直接用 Roberta、Distilbert 或 Albert(你想用哪个就用哪个)替换 BERT 术语。
蒸馏啤酒
Distilbert 的目标是通过减小 bert 的大小和提高 BERT 的速度来优化训练,同时尽可能地保持最佳性能。具体来说,Distilbert 比最初的 BERT-base 模型小 40%,比它快 60%,并且保留了 97%的功能。
Distilbert 是如何做到这一点的?它使用与 BERT 大致相同的通用架构,但只有 6 个编码器模块(回想一下,BERT base 有 12 个)。这些编码器块也通过仅从每 2 个预训练的 BERT 编码器块中取出 1 个来初始化。此外,BERT 的令牌类型嵌入和池功能也从 Distilbert 中移除。
与 bert 不同,Distilbert 仅使用掩蔽语言建模进行预训练(回想一下,BERT 是使用 MLM 和下一句预测进行训练的)。使用三重损失/三重损失函数训练 Distilbert:
- 伯特使用的相同的语言模型损失
- 蒸馏损失衡量蒸馏器和 bert 之间输出的相似性。
- 余弦距离损失衡量蒸馏伯特和伯特的隐藏状态有多相似。
这些损失函数的组合模拟了 Distilbert 和 bert 之间的学生-教师学习关系。Distilbert 还使用了几个与 Roberta 相同的超参数,比如更大的批量,动态屏蔽,以及我之前提到的,没有对下一句预测进行预训练。从上面看 Roberta(和 BERT)的代码,使用拥抱脸的 Distilbert 非常容易。
from transformers import DistilBertModel
import torch
import torch.nn as nnclass DistilBERT_Model(nn.Module):
def __init__(self, classes):
super(DistilBERT_Model, self).__init__()
self.distilbert = DistilBertModel.from_pretrained('distilbert
base-uncased')
self.out = nn.Linear(self.distilbert.config.hidden_size, classes)
self.sigmoid = nn.Sigmoid()
def forward(self, input, attention_mask):
_, output = self.distilbert(input, attention_mask
= attention_mask)
out = self.sigmoid(self.out(output))
return out
艾伯特
Albert 与 Distilbert 大约在同一时间出版/推出,也有一些与论文中介绍的相同的动机。就像 Distilbert 一样,Albert 减少了 bert 的模型大小(参数减少了 18 倍),训练速度也提高了 1.7 倍。然而,与 Distilbert 不同的是,Albert 在性能上没有折衷(Distilbert 在性能上确实有轻微的折衷)。这来自于 Distilbert 和 Albert 实验构造方式的核心差异。Distilbert 的训练方式是将 bert 作为其训练/蒸馏过程的老师。另一方面,艾伯特和伯特一样是从零开始训练的。更好的是,Albert 优于所有以前的模型,包括 bert、Roberta、Distilbert 和 XLNet。
Albert 能够通过这些参数缩减技术获得较小模型架构的结果:
- 因式分解的嵌入参数化:为了确保隐藏层的大小和嵌入维度是不同的,Alberta 将嵌入矩阵解构为 2 块。这允许它实质上增加隐藏层的大小,而不真正修改实际的嵌入尺寸。在分解嵌入矩阵之后,在嵌入阶段完成之后,Alberta 将线性层/全连接层添加到嵌入矩阵上,并且这映射/确保嵌入维度的维度是相同正确的。你可以在这里阅读更多关于这个的内容。
- 跨层参数共享:回想一下,BERT 和 Alberta 各有 12 个编码器模块。在阿尔伯塔省,这些编码器块共享所有参数。这将参数大小减少了 12 倍,并且还增加了模型的正则化(正则化是在校准用于防止过拟合/欠拟合的 ML 模型时的一种技术)
- Alberta 删除了辍学层:辍学层是一种技术,其中随机选择的神经元在训练过程中被忽略。这意味着他们不再被训练,基本上暂时无用。
from transformers import AlbertModel
import torch
import torch.nn as nnclass ALBERT_Model(nn.Module):
def __init__(self, classes):
super(ALBERT_Model, self).__init__()
self.albert = AlbertModel.from_pretrained('albert-base-v2')
self.out = nn.Linear(self.albert.config.hidden_size, classes)
self.sigmoid = nn.Sigmoid()
def forward(self, input, attention_mask):
_, output = self.albert(input, attention_mask = attention_mask)
out = self.sigmoid(self.out(output))
return out
其他类似变压器
虽然 Albert、Roberta 和 Distilbert 可能是最受欢迎的三种变形金刚(bert 的所有修改/版本/改进),但其他几种受欢迎的变形金刚也实现了类似的一流性能。这些包括但不限于 XLNet、BART 和 Mobile-BERT。 XLNet 是一个自回归语言模型,建立在 Transformer-XL 模型的基础上,使用置换语言建模来实现与 Roberta 类似的最先进的结果。Mobile-BERT 类似于 DistilBERT:它主要是为速度和效率而设计的。与 BERT-base 相比,它的体积小 4.3 倍,速度快 5.5 倍,但性能相当/相似。BART 是另一个预训练的模型,在 NLU(自然语言理解)任务上取得了与 Roberta 相似的性能。除此之外,BART 还可以在 NLG(自然语言生成)任务上表现出色,如抽象摘要,这就是它的独特之处。你可以在这里阅读更多关于他们的信息。
我希望您觉得这些内容很容易理解。如果你认为我需要进一步阐述或澄清什么,请在下面留言。
参考
预训练语言模型回顾:从 BERT、RoBERTa 到 ELECTRA、DeBERTa、BigBird 等等:https://tungmphung . com/a-review-of-pre-trained-language-models-from-BERT-RoBERTa-to-ELECTRA-DeBERTa-big bird-and-more/# distill BERT
伯特:用于语言理解的深度双向转换器的预训练:https://arxiv.org/abs/1810.04805
ALBERT:一个用于语言表征自我监督学习的 Lite BERT:https://ai . Google blog . com/2019/12/ALBERT-Lite-BERT-for-Self-Supervised . html
蒸馏伯特,伯特的蒸馏版本:更小、更快、更便宜、更轻:https://arxiv.org/abs/1910.01108
抱抱脸变形金刚库:https://huggingface.co/docs/transformers/index
关于偏差、过度拟合和欠拟合,你需要知道的一切
偏见的详细描述以及它是如何融入机器学习模型的

介绍
数据科学领域的一个关键特征是将人工智能和统计学应用于现实世界的数据。数据科学的这一部分的伟大之处在于,它非常强大,几乎可以应用于任何可以成功争论数据的地方。实际上,编写一个机械地适应特征的算法是相当困难的,因此一个非机械的、基于损失的算法可以允许人们使用统计数据来做出决策。
虽然在许多应用程序中使用机器学习可能是有益的,但在每种情况下使用机器学习也有一些缺点。首先是性能,因为通常神经网络的性能不会超过简单的条件和传统算法。虽然性能是模型对话中的一个重要话题,但我想把今天的焦点更多地转向在不同的环境中使用机器学习的不同缺点,
机器学习很难。
请允许我详细说明,预测建模是而不是难。我认为,大多数(如果不是所有的话)有编程经验的人可能会查看 Python 中基本网络的一些源代码,并快速掌握代码中发生的事情。然而,困难的是预测建模之前的一切。总的来说,我认为预测建模确实包括处理数据,但是我想说的是,完美地处理数据以处理您的模型比仅仅将模型拟合到所述数据要困难得多。今天,我想回顾建模和模型偏差的基础知识,所有这些如何影响你的预测,以及一些可以用来缓解这些问题的潜在途径。
Part №1:什么是偏见?
在我们继续讨论偏见之前,我们也许应该谈一谈偏见本身究竟是什么。在常规的人类定义中,偏见意味着对某一特定类别或观点存在某种程度的偏见。例如,一个飞机制造商可能有偏见地说飞机是最好的运输方式。这个定义如何应用于数据?
那么,在机器学习环境中,什么是偏见呢?所有的机器学习模型都有偏差,偏差并不一定需要低或高。偏差只是模型基于特征得出结论的倾向。偏见的重要之处在于,随着模型信息过载,它会不断增长。基本上,每当解算器和权重组合达到峰值时,就会发生这种情况。这意味着模型以最高效率预测我们提供给它的数据。第二部分非常重要,在我们讨论完“完美契合”之后,我们会明白为什么这是正确的。
第二部分:“完美契合”
T he perfect fit 只是我想出来的一个玩笑名字,用来描述一个在偏差上完全平衡的模型。在大多数情况下,很难得到完全完美的输入数据,因此很可能大多数模型可以在数据上稍作调整,以创建更完美的拟合。也就是说,找到至少一个可以接受的合适人选并不容易。现在,请允许我以三个月前的精神,十月,给你讲一个恐怖的故事。
“偏见的平衡”
偏差的平衡是一个必须用他们的模型来玩的游戏,以确保模型有效地预测。有两种情况可能对与偏差相关的模型的性能完全有害:
- 过度拟合——参数估计值几乎没有偏差。
- 欠拟合-参数估计值中有太多的偏差。
过度拟合
过度拟合通常意味着您的模型在评估您的训练数据时基本上已经超出了偏差。换句话说,这些数据中包含了如此多的特征和观察结果,以至于模型在所有事物之间画出了太多的联系。总的来说,您的模型有太多的数据,或者经过劣质预处理的数据。
欠拟合
适配不足与适配过度正好相反。数据太多,偏差变为零,而偏差却增加到天文数字。发生这种情况是因为模型只有几个例子可以使用。总而言之,模型没有足够的数据。判断模型是否欠拟合的一个关键方法是检查偏差和方差。如果偏差较高,但方差较低,则很可能您正在处理一个急需更多数据的模型。
第 3 部分:减轻您的模型
我想说的最后一件事是如何修复一个表现不佳的模型。我有一整篇文章讨论如何通过处理输入数据来改进模型,你可能会在这里读到,因为在这篇文章中,我只打算介绍一些技术来帮助平衡偏差。如果你对这样一篇文章感兴趣,你可以在这里查阅:
过度拟合
现在让我们转到一些技术上来,这些技术可以用来帮助你的模型停止过度拟合。首先必须使用一个验证集。验证集通常要小得多,尽管更少的观察看起来可能是一件坏事,但最终模型将从这种变化中受益。另一个防止过度拟合的好方法是使用分解。如果您不熟悉分解及其用途,我有一整篇关于最流行的分解类型——奇异值分解的文章,其中我编写了一个 SVD 方法,您可以在这里深入研究:
[## 深入奇异值分解
towardsdatascience.com](/deep-in-singular-value-decomposition-98cfd9532241)
无论如何,让一个模型停止过度拟合的最好方法可能是最小化你的特征。有很多很好的方法可以做到这一点,其中很多都在减轻您的模型文章中,但是最明显的方法就是去掉这些特性。或许,做一些基本假设,进行假设检验,然后花点时间探索你的特征,这样你就能确定哪些特征相关,哪些不相关。另一个好主意是设计一些特性,在这篇文章中,我也用 Python 演示了这些特性的代码绑定。
欠拟合
你的模型不合适的原因可能归结为两个简单的事情。第一种情况是你没有足够的数据。这通常意味着数据中有很高的方差,这使得诊断真正的拟合不足非常困难。如果是这种情况,除了简单地添加更多数据之外,您没有太多的方法来修复一个没有数据的模型。没有办法绕过它,你需要数据来拥有机器学习模型。
在其他情况下,您的模型可能不适合,或者可能看起来不适合,因为数据有很大的变化。减轻这种情况的一个好方法是对连续值做基本处理。评估方差,可能基于 Z 分数对数据进行归一化,等等。对于分类值,您可能获得的最佳结果是查看一个集合,也许是集合计数,这样您就可以看到哪个集合出现的次数最多,并且您将知道该特征的每个类别。然后应用正确的蒙版,使你的特征具有更重要的价值。
结论:所有数据都是不同的
既然我们知道了什么是偏差,以及它与过度拟合和欠拟合的关系,我们就有可能看到平衡偏差对于构建有效模型是多么重要。然而,关于这些技巧,要记住的一件关键事情是,所有的数据都是不同的。作为一名数据科学家,部分乐趣在于我们可以处理各种不同的数据。然而,这也提出了一个挑战,因为所有的数据都是不同的,需要解决问题的技巧,有时还需要巧妙的技巧,才能让您的数据完全符合您的需要。
在这条数据科学的道路上,你可能会遇到许多不同类型的数据,拥有一个平衡的模型的绝对关键是仔细探索和选择你的特性。你不会在一个不牢固的基础上建造一个房子,那么你为什么要在一个不牢固的基础上建造一个模型呢?这当然是要考虑的事情,我希望这一信息能引起共鸣,因为我发现通过一个我赞同的过程节省了很多时间。如果你想用我的数据了解更多我的过程,我有一整篇文章,其中我反复讨论了每个步骤,你可以在这里阅读:
是的,我喜欢我写了这么多文章,现在我可以提供我的旧文章作为阐述。
在开始学习数据科学时,偏见是一件需要理解的重要事情,一般来说,数据处理团队也是如此。我希望这篇文章足够吸引人,向我的读者揭示避免过度适应和适应不足的秘密。感谢您阅读我的故事,并祝您在数据科学探险中好运!
关于 Julia 中的索引,您需要知道的一切
原文:https://towardsdatascience.com/everything-you-need-to-know-about-indexing-in-julia-b0a695de45b7
关于 Julia 中索引的所有细节的概述

(图片由 Pixabay 上的 DavidZydd 提供)
介绍
在编程领域,尤其是数据科学领域,使用由更小元素组成的数据结构是很常见的。正如人们所料,这些集合需要以某种方式从集合外部进行交互——为此,程序员使用索引。索引是一个至关重要的编程主题,它在特定语言中的深度总是很大的学问。很多时候,当谈到对编程语言的熟悉和了解时,归结为使用一段时间后对调用的了解。
幸运的是,在本文中,我将介绍一些非常抽象的方法。也就是说,包含类型的类型定义对于什么类型可以通过该函数传递是非常模糊的。在朱莉娅,最大的错误是
方法错误
我们也可以用索引来解决这些问题。有一种使用索引的核心方法是 Julia 独有的,而且以我个人的经验来看非常简单。与其他生态系统相比,这种方法似乎非常适合。此外,如果您想查看包含本文代码的笔记本,您可以从 Github 仓库获得它:
https://github.com/emmettgb/Emmetts-DS-NoteBooks/blob/master/Julia/Julia Indexing.ipynb
索引
Julia 内部的每种类型都可以被索引,信不信由你。即使对于不是集合的类型也是如此,我们将演示这一点。然而,让我们先来看看更简单的东西,比如基本的数组索引。我们可以使用一个整数作为数组中的一维参考点进行索引。如果数组是有序的,这尤其有用。
array = [1, 2, 3, 4, 5]
array[1]1
现在要澄清的是,我们没有得到一个值,因为在这个例子中,我们通过索引传递了一个值,我们得到一个值,因为这是数组中的第一个值。例如,如果我们把 1 改成 5:
array = [5, 2, 3, 4, 5]
array[1]5
我们还可以通过范围进行索引:
array[1:5]5-element Vector{Int64}:
1
2
3
4
5
如果这个集合是一个字典,我们也可以通过键(不管是什么类型)进行索引。)如果你想更多地了解 Julia 的各种数据结构,你可以在这里阅读或观看:
对于要提供的类型的完整列表,我们实际上可以对一个方法打上问号。索引调用的方法是来自 base 的 getindex()方法。让我们在上面调用文档浏览器:
?(getindex)

我们也可以设置索引。这将调用 setindex!()方法,它有一个解释点,因为它会改变我们发送给它的类型。让我们继续导入 getindex()和 setindex!()直接从基础开始,以便我们可以扩展它们:
import Base: getindex, setindex!
现在,只需使用典型的 Julia 分派方法,我们就可以设置任何结构来拥有我们想要的任何类型的索引。
mutable struct NotACollection
x::Int64
end
我们构造的类型根本不是这样一个集合,相反,它只是在里面有一个整数。它实际上是一个非常无用的构造函数。不管怎样,我们现在要把它分派到索引任何东西都会返回 x 的地方,只有一个索引,我不知道你想让我说什么。
getindex(notcol::NotACollection, y::Int64) = notcol.xnotacol = NotACollection(5)
现在,如果我们调用我们的类型的索引,它肯定不是一个集合,我们得到的数字,并没有大惊小怪:
notacol[1]
5
如果您在没有首先扩展 getindex()的情况下这样做,这段代码将返回一个 MethodError。现在,为了好玩,我们将对 setindex 做同样的事情!():
setindex!(notcol::NotACollection, y::Int64, val::Int64) = notcol.x = ynotacol[1] = 10println(notacol[x])
索引方法
现在让我们看看一些有用的方法,它们可以用来处理基本的数据结构和改变它们的索引。我认为这些是 Julia 的基本知识,所以我真的很高兴与你分享这些令人敬畏的基本方法。这些是我所知道的随时会派上用场的一些工具。
大小()
第一种方法是 size()。这个方法相当于 Python 中的 shape()。这将给出给定数组或矩阵的维数。虽然我们还没有深入多维数组,但是 size()对于处理多维数组来说将会非常方便。也就是说,保持这种方法及其使用方便,当然可以适用于一维或多维应用程序。
nums = [54, 33, 22, 13]
size(nums)(4,)
返回类型是一个二元元组,我们可以对其进行索引:
println(typeof(size(nums)))Tuple{Int64}println(size(nums)[1])4
用力!()
用力!()方法是 Julia 语言中最有用的方法之一。这个方法的伟大之处在于它被定义在类型层次结构的顶端。这意味着可以将哪些类型传递给 push!()通常非常抽象。这种抽象意味着可以提供许多不同的类型。然而,这确实带来了一些可能需要注意的细微差别。朱莉娅的伟大之处还在于我们可以改变语言。
用力!()方法用于将新元素移动到结构中。它可以处理字典、数组和各种其他数据类型——几乎是你能想到的任何数据类型。推动的伟大之处在于!()除非你尝试,否则你永远不知道它是否会起作用,如果它不起作用,你可以随时写你自己的推!()来照顾它。让我们在我们的阵列上尝试一下:
push!(nums, 5)5-element Vector{Int64}:
54
33
22
13
5
实际上,我写了一整篇文章来深入讨论这个问题!()方法,所以如果您碰巧对阅读这种方法及其各种细微差别感兴趣,您可以在这里阅读:
追加!()
追加!()方法很类似于推送!()方法。喜欢推!()方法,追加!()方法将元素添加到给定的数组中。然而,一个关键的区别是附加!()会尽可能避免变异命令,推!()不会。追加!()方法对于它应该使用的类型来说也更加明确和具体。通常,它用于简单数据类型的数组。让我们看一下我们阵列的一个示例:
append!(nums, 5)
6-element Vector{Int64}:
54
33
22
13
5
5
注意,因为我们的数组是 Vector{Int64}类型的,所以我们只能通过这个函数推送整数。我们也可以推送可以转换成整数的数字,但是在大多数情况下,我们需要在提供它们之前进行类型转换。
append!(nums, 5.5)InexactError: Int64(5.5)
铸造那种类型产生它确实工作:
append!(nums, 5.5)
但是,如果该结构将包含多种类型,更好的方法可能是将整个结构强制转换为包含{Any}:
nums = Array{Any}(nums)
append!(nums, 5.5)
滤镜!()
Julia 中的 filter 方法是另一个非常常用的方法。这一点在使用统计数据时尤其有用。想想熊猫数据框面具,它实际上成了我最喜欢的熊猫特写的一部分:
</20-great-pandas-tricks-for-data-science-3a6daed71da0>
但是,过滤!()在 Julia 中是等价的。也哇,一年多前。
我和过滤器有些小问题!(),这并不是说我不喜欢这种方法——我只是觉得使用起来比其他界面更烦人。Julia 倾向于在它所做的许多事情上出类拔萃,并使事情对程序员来说变得超级容易——但这是一个例子,我认为学会这一点比学会另一种语言更难,那就是 Python。幸运的是,我希望进行的修改的语法非常容易完成,布尔索引现在可以在我目前正在开发的包 OddFrames.jl 中找到,您也可以在这里查看 Github 页面:
https://github.com/ChifiSource/OddFrames.jl
filter((x) -> x == 5, nums)
对于这个方法,我们使用一个叫做匿名函数的东西作为它的第一个参数,为了更全面地了解这个概念,您可以在这里阅读一篇深入研究它们的文章:
在本文的稍后部分,我们还将更多地涉及它们,但是现在我们将简单地观察我们的过滤值:
filter((x) -> x == 5, nums)2-element Vector{Any}:
5
5
收集()
像 collect 这样的方法通常很难解释,但是 collect 可以用来快速地从某种生成器或进程中获取值。本质上任何能够返回 iterable 的东西。如果我们有范围
r = 5:10
我们想得到一个从 5 到 10 的数组,我们可以简单地调用 collect 来得到结果
collect(r)6-element Vector{Int64}:
5
6
7
8
9
10
findall()
芬多。()方法是一种我可以非常肯定地说非常有用的方法。有很多这样的呼吁,所以用 ole 问号’是一个很好的例子:
?(findall)
实际上,我在 OddFrames.jl 中多次使用这种方法。对于我将要演示的示例代码,我们将查看 OddFrames.jl 的 index_iter.jl 文件,您可以通过单击该文本来查看。
function getindex(od::AbstractOddFrame, col::Symbol)
pos = findall(x->x==col, od.labels)[1]
return(od.columns[pos])
end
getindex(od::AbstractOddFrame, col::String) = od[Symbol(col)]
getindex(od::AbstractOddFrame, axis::Int64) = od.columns[axis]
function getindex(od::AbstractOddFrame, mask::BitArray)
pos = findall(x->x==0, mask)
od.drop(pos)
end
getindex(z::UnitRange) = [od.labels[i] for i in z]
这段代码是几个 getindex()调度绑定。您可能还记得前面的 getindex()。OddFrames.jl 处理数组中的一维数组,这些数组可以相互堆叠,但不能。findall()方法在这里使用了两次。第一种情况是用符号调用索引时:
od[:column]
在这个例子中,findall()方法向我们返回等同于这样一个东西的任何实例的索引。出于某种原因,我见过的许多 Julia 代码在操作符周围没有使用空格,因此我采用了它——尽管我认为这不合适,所以让我把它写得更容易阅读一些:
pos = findall(x -> x == col, od.labels)[1]
我们所做的就是试图找到一个与该值相等的标签,我们首先使用:
x -> x
这告诉 Julia 我们想要创建一个匿名函数。现在这个 x 是一个变量,我们已经定义了一个函数,它将返回一个 BitArray。BitArray 只是一种保存布尔真/假值的数组,通常为 1 和 0。1 和 0 都只占用一位,因此这被称为双数组。也许我可以简单的解释一下,说它是一个二进制数组。无论如何,我们的数组是直接从这里返回的:
x == col
然后,我们遍历 od.labels,看看是否有匹配的值。由于 OddFrame 中的标签不能相同,我们总是知道如果在 OddFrame 中没有找到它,那么它要么是一个值,要么没有值。给定这些信息,我在最后调用第一个索引,这将把我们的值的类型从数组{Int64,1}变成 Int64。然后我们简单地在返回中调用该列的索引:
function getindex(od::AbstractOddFrame, col::Symbol)
pos = findall(x->x==col, od.labels)[1]
return(od.columns[pos])
end
向前跳一点,我们看到条件掩码也使用了同样的方法:
function getindex(od::AbstractOddFrame, mask::BitArray)
pos = findall(x->x==0, mask)
od.drop(pos)
end
唯一的区别是,这次 findall()用于收集需要删除的位置,然后在 od.drop()方法中使用这个列表中的下一个方法来删除那个值。
对于我们的笔记本示例,我们将获得等于 5 的每个值:
z = findall(x -> x == 5, nums)
array = [nums[x] for x in z]
删除 at!()
我想看的最后一个方法是 deleteat!()方法。如前所述,我们将在 OddFrames.jl 中查看这个示例。我们还将在笔记本中做另一个示例。OddFrames 示例位于 OddFrames 存储库中的 member_func.jl 文件中,再次单击此文本。删除 at!()方法就是用来做这件事的,在特定的索引处删除。
我知道有人在某处不断读到,当你吃东西的时候,要知道你并不孤单。也许我们只是饿了。
该方法简单易用,只需提供一个集合和一个索引:
function _drop(row::Int64, columns::Array)
[deleteat!(col, row) for col in columns]
end
在这种情况下,这是通过迭代循环完成的,并对提供的向量进行变异。虽然这样做有回报,但并不意味着要使用它——也许我应该在此之后返回,以确保这一点得到理解。对于我们的笔记本电脑示例,我们将删除整个阵列:
while length(nums) > 1
deleteat!(nums, 1)
endprintln(nums)Any[5.5]
安息吧。
这么多方法
当然,处理这类类型的方法有很多,所以我不可能一一描述,但是除了像 length()和 sum()等常规方法之外,我还发现了一些非常有价值的方法。除了 size(),这些都是特定于 Julia 的,所以我认为新的 Julia 用户可能会发现这些有参考价值。甚至还有横向和纵向的串联,各种矩阵运算等等。
结论
Julia 语言有一种非常好的处理迭代的方式。每种语言都有我欣赏的方法,但与使用 Python 类的默认方法或在 r 中完成相同的任务相比,我真的很喜欢这种方法。每当我使用其他语言时,我发现自己经常错过这些功能,这告诉我它们可能是很棒的功能。
感谢您的阅读,我很高兴能与您分享这些知识。这些是 Julia 用户第一次开始时可能不知道的一些事情,所以把这些方法带到你面前并提供更多关于在 Julia 中使用 iterable items 的信息真是太棒了。
关于熊猫排名你需要知道的一切
原文:https://towardsdatascience.com/everything-you-need-to-know-about-ranking-with-pandas-aa2ab5921c01
实用指南。

约书亚·戈尔德在 Unsplash 上拍摄的照片
- 前 10 个最高分
- 基于点击数的前 10 项观察
- 每个类别中最便宜的 3 件商品
- 每月的前 3 次测量
- 销售量最高和最低的日子
以上所有内容都可以通过给数据点(即行)分配一个等级来找到。幸运的是,Pandas 为我们提供了一个执行这些任务的单一函数:rank 函数。
虽然分配等级似乎是一个简单的任务,但在某些情况下,你需要稍微超出默认设置来得到你真正需要的。
在本文中,我们将通过几个例子来演示您需要了解的关于 rank 函数的所有内容以及如何正确使用它。
让我们从导入 Pandas 和创建一个示例数据框架开始。
import pandas as pddf = pd.DataFrame({
"name": ["John","Jane","Emily","Lisa","Matt","Jenny","Adam"],
"current": [92,94,87,82,90,78,84],
"overall": [184,173,184,201,208,182,185],
"group":["A","B","C","A","A","C","B"]
})df

df(作者图片)
我们创建了一个 7 行 4 列的数据帧。让我们从默认设置开始,根据整个列为行分配一个等级。
df["rank_default"] = df["overall"].rank()df

df(作者图片)
等级默认列包含等级值。关于默认设置,有两点很重要:
- 顺序是升序的,因此最低的值被指定为第一个等级。在我们的例子中,Jane 的总分最低,因此排名第一。
- 在相等的情况下,通过取平均值来确定排名。例如,John 和 Emily 的总分都是倒数第三。因为两个人得分相同,所以他们被分配的等级为 3.5,这是 3 和 4 的平均值。下一个最低分是 185,排名第 5。
这些是默认设置。让我们改变顺序,按降序排列,这样分数最高的人排在第一位。
df["rank_default_desc"] = df["overall"].rank(ascending=False)
df = df.sort_values(by="rank_default_desc", ignore_index=True)df

df(作者图片)
我们还使用 sort_values 函数根据“rank_default_desc”列对行进行了排序。这样更容易跟踪和比较这些值。
Matt 的总得分最高,因此按降序排列时,他排名第一。John 和 Emily 现在是第 4 和第 5 个人,所以他们被指定为 4.5 级,因为我们仍然在平等的情况下使用平均方法。
不同的排名方法
在相等的情况下,rank 函数有 5 个不同的选项。这个选项是用 method 参数选择的,默认值是“average ”,正如我们在前面的例子中看到的。
其他选项包括“最小”、“最大”、“第一”和“密集”。让我们首先将最小值和最大值与平均值进行比较。
# create DataFrame
df = pd.DataFrame({
"name": ["John","Jane","Emily","Lisa","Matt","Jenny","Adam"],
"current": [92,94,87,82,90,78,84],
"overall": [184,173,184,201,208,182,185],
"group":["A","B","C","A","A","C","B"]
})# create rank columns
df["rank_default"] = df["overall"].rank(ascending=False)
df["rank_min"] = df["overall"].rank(method="min", ascending=False)
df["rank_max"] = df["overall"].rank(method="max", ascending=False)# sort rows
df = df.sort_values(by="rank_default", ignore_index=True)df

df(作者图片)
当两行或更多行具有相同的值时,可以观察到差异。约翰和艾米丽是第四和第五个人。
- method = "average "给出的平均值为 4.5
- method = "min "给出的最低值是 4。
- method = "max "给出的最高值为 5。
您可能已经注意到,在创建“rank_default”列时,我们没有编写方法参数。因为它是默认值,所以我们不需要指定它,但是如果您按如下方式编写它,它也是有效的:
df["rank_default"] = df["overall"].rank(method="average", ascending=False)
方法参数的另外两个选项是“第一”和“密集”。让我们将它们添加到当前的数据框架中,然后解释它们的作用。
df["rank_first"] = df["overall"].rank(method="first", ascending=False)df["rank_dense"] = df["overall"].rank(method="dense", ascending=False)df

df(作者图片)
- “第一”方法根据等级出现的顺序来分配等级。在相等的情况下,数据帧中的第一个观察值取下一个值,第二个观察值取下一个值,依此类推。在我们的例子中,John 和 Emily 是第 4 和第 5 个人,所以 John 的等级是 4,Emily 的等级是 5。
- “dense”方法类似于“min ”,但它不会在具有相同值的行和下一行之间留下任何间隙。John 和 Emily 的等级为 4,因为他们使用“min ”,但是下一个人的等级不同。Jenny 在“最小”方法中排名第 6,但在“密集”方法中排名第 5。
基于多列的排名
在某些情况下,我们基于多个列创建一个等级。例如,在我们的 DataFrame 中,我们可能希望同时使用 total 和 current 列中的值来分配等级。
我们可以通过从这两列创建一个元组并使用它进行排序来做到这一点。
我们可以通过对每行应用元组函数来创建元组,如下所示:
df[["overall","current"]].apply(tuple, axis=1)**# Output**
0 (208, 90)
1 (201, 82)
2 (185, 84)
3 (184, 92)
4 (184, 87)
5 (182, 78)
6 (173, 94)
dtype: object
我们用它来排名吧。
# create DataFrame
df = pd.DataFrame({
"name": ["John","Jane","Emily","Lisa","Matt","Jenny","Adam"],
"current": [92,94,87,82,90,78,84],
"overall": [184,173,184,201,208,182,185],
"group":["A","B","C","A","A","C","B"]
})# create rank column
df["rank_overall_current"] = df[["overall","current"]].apply(
tuple, axis=1
).rank(ascending=False)# sort values
df = df.sort_values(by="rank_overall_current", ignore_index=True)df

df(作者图片)
等级是基于总体和等级的组合创建的。因为总列中的值最先出现,所以首先检查它们。如果总值相等,则考虑当前值。
例如,John 和 Emily 具有相同的总价值,但 John 的当前价值更高,因此他的排名在 Emily 之前。
组内排名
我们可能还需要为不同的组分配不同的等级。我创建了 group 列来演示这个案例。
我们只是需要“groupby”函数的一些帮助,该函数用于根据给定列中的不同值对行进行分组。
如果我们按组列对数据帧中的行进行分组,我们最终会将列中的不同值分为不同的组,即 A、B 和 c。然后,我们选择用于排名的列,然后应用 rank 函数。
# create DataFrame
df = pd.DataFrame({
"name": ["John","Jane","Emily","Lisa","Matt","Jenny","Adam"],
"current": [92,94,87,82,90,78,84],
"overall": [184,173,184,201,208,182,185],
"group":["A","B","C","A","A","C","B"]
})# create rank column
df["group_rank"] = df.groupby("group")["overall"].rank(
ascending=False)# sort values
df = df.sort_values(by=["group","group_rank"], ignore_index=True)df

df(作者图片)
我们现在在每个组中都有一个单独的等级。我们可以改变秩函数的方法。它将像我们迄今为止所做的例子一样工作。唯一不同的是,每一组都将单独完成。
我们已经做了几个例子来演示 rank 函数是如何使用的以及它的参数是什么意思。
你可以成为 媒介会员 解锁我的全部写作权限,外加其余媒介。如果你已经是了,别忘了订阅https://sonery.medium.com/subscribe如果你想在我发表新文章时收到电子邮件。
*https://sonery.medium.com/membership
感谢您的阅读。如果您有任何反馈,请告诉我。*
关于二分搜索法算法你需要知道的一切
在 8 分钟内掌握二分搜索法算法

图片由作者提供。
你如何在英语词典中查找一个单词?我知道你不会这样做:从第一页开始,仔细阅读每个单词,直到找到你要找的那个——当然,除非你的单词是“土豚”。但是,如果你要找的词是“动物园”,这种方法将需要很长时间。
你如何在英语词典中查找一个单词?
更快的方法是在中间打开它,然后决定是在词典的前半部分还是后半部分继续搜索。
这种方法是二分搜索法算法的松散描述,该算法在元素的排序列表中查找元素的位置。它被称为二分搜索法(来自拉丁语bīnī:“two-by-two,pair”),因为它在每次迭代时将数组分成两半,以缩小搜索空间。
让我们定义一下前一句话中的行话。“算法”是一种解决问题的方法,就像我们在例子中用来查找单词的方法一样。“元素”是我们正在查找的单词,“元素排序列表”是字典。它被“排序”是因为字典中的单词是按字母顺序排列的。
本文讨论了二分搜索法算法是如何在直观的层面上工作的。然后我们将看看它在 Python 和 C++中的实现以及它们的内置函数。最后,我们将讨论它与线性搜索算法相比的性能。
算法
这一节将让你对二分搜索法算法有一个更好的直觉。首先,我们将查看问题陈述,然后了解算法本身,最后,通过一个示例来浏览算法。
问题陈述
在 Leetcode 这个练习编码面试问题的平台上,二分搜索法问题表述如下[3]:
给定一个由 n 个元素组成的有序(升序)整数数组
nums和一个target值,编写一个函数来搜索nums中的target。如果目标存在,返回其索引;否则,返回-1。
- 输入:排序数组(
nums)和一个目标值(target) - 输出:
target值的索引
二分搜索法算法
二分搜索法算法的工作原理如下:
- 将搜索空间设置为等于排序后的数组
- 取搜索空间的中间元素,并将其与目标值进行比较。
-如果目标等于中间元素,则您已经找到目标值。返回中间元素的索引并终止函数。
-如果目标小于中间元素,则通过丢弃中间元素右侧的所有元素将搜索空间减半,并继续在其左侧搜索,因为数组按升序排序。重复此步骤,直到找到目标。
-如果目标大于中间元素,则通过丢弃中间元素左侧的所有元素将搜索空间减半,并继续在其右侧搜索,因为数组是按升序排序的。重复此步骤,直到找到目标。 - 如果数组中没有匹配,返回-1
示例演示
让我们通过一个例子来完成二分搜索法算法。在下图中,你可以看到一个排序后的数组nums = [3,7,8,9,14,16,17,22,24],有 n = 9 个元素。我们要找到target = 8 的位置。

二分搜索法算法问题陈述(图片由作者受迈克·巴斯【7】启发而来)
迭代 1:

二分搜索法算法迭代 1(图片由作者从迈克·巴斯【7】获得灵感)
我们通过称为low和high的开始和结束索引来定义搜索空间。我们通过将low分配给数组中第一个元素的索引(0)并将high分配给数组中最后一个元素的索引(8)来设置搜索空间。
我们用公式(low+【T8)//2 得到数组mid中间元素的索引。该操作执行一个 floor 函数来实现所需的中间元素:mid = (low + high) // 2 = (0 + 8) / 2 = 4
中间元素的值是nums[mid] = nums[4] = 14,因此大于target = 8。因此,中间元素右侧的所有元素都可以被丢弃。我们通过将high更新为(mid — 1) = 4 — 1 = 3将搜索空间减半。
迭代 2:

二分搜索法算法迭代 2(图片由作者从迈克·巴斯【7】获得灵感)
现在,我们重复步骤 2。
数组中间元素的索引现在是mid = (low + high) // 2 = (0 + 3) / 2 = 1。中间元素的值是nums[mid] = nums[1] = 7,因此小于target = 8。因此,中间元素左侧的所有元素都可以被丢弃。我们通过将low更新为(mid + 1) = 1 + 1 = 2,将搜索空间减半。
迭代 3:

二分搜索法算法迭代 3(图片由作者从迈克·巴斯【7】获得灵感)
同样,我们重复步骤 2。
数组中间元素的索引现在是mid = (low + high) // 2 = (1 + 3) / 2 = 2。
中间元素的值是nums[mid] = nums[2] = 8,因此等于target = 8。我们返回mid = 2作为目标的位置并终止函数。
履行
在这一节中,您将看到二分搜索法算法在 Python 和 C++中最基本的实现。我们还将看看 Python 和 C++ 中内置的二分搜索法函数。
二分搜索法算法有不同的实现方式[4]。但是,本文将只讨论初等迭代实现,这也是最常见的实现。
计算机编程语言
Python 实现如下所示:
用 Python 实现的二分搜索法算法
您可以使用 Python [5]中的bisect模块中的bisect_left()函数,而不是编写自己的二分搜索法函数。
from bisect import bisect_lefti = bisect_left(target, nums)
C++
C++实现如下所示:
二分搜索法算法的 C++实现
在 C++中,标准模板库(STL)提供了函数lower_bound(),可以按照下面的例子[2]使用。还有函数binary_search(),它返回一个布尔值,不管target是否存在于排序后的数组中,但不返回它的位置[1]。
#include <algorithm>i = std::lower_bound(nums.begin(), nums.end(), target);
讨论
二分搜索法算法的时间和空间复杂度是:
- 时间复杂度与 O(log n) 成对数关系【6】。如果 n 是输入数组的长度,二分搜索法算法最坏情况下的时间复杂度是 O(log n ),因为它是通过在每次迭代中将搜索空间减半来执行的。例如,如果我们想要在长度为 8 的数组中找到一个元素,在最坏的情况下将需要 log₂(8 = 3 次迭代。
- 空间复杂度与 O(1) 不变。因为该算法需要用于三个索引、
mid、low和high的空间,但是每次迭代不需要额外的空间。
与线性搜索算法相比,二分搜索法算法的主要优势在于其速度。因为线性搜索算法的概念是遍历数组直到找到目标元素——就像从英文字典的第一页开始查找特定的单词一样——线性搜索算法的时间复杂度与 O(n) 成线性关系。
例如,如果我们想要从前面长度为 8 的示例中找到数组中的元素,在最坏的情况下将需要 n = 8 次迭代。二分搜索法算法只需要三次迭代。
然而,二分搜索法算法的主要缺点是它需要一个有序数组在每次迭代中丢弃一半的搜索空间。尽管可以在运行二分搜索法算法之前对数组进行排序,但排序算法会增加总的时间复杂度。一般来说,排序的时间复杂度为 O(n log n),比线性搜索算法的线性时间复杂度更差。
下面,您可以看到二分搜索法算法、线性搜索算法和二分搜索法算法的时间复杂性,以及作为预处理步骤的附加排序:

大 O 总结(图片由作者启发[8])
结论
开发算法的最佳方法是将问题分解成你已经知道如何解决的算法,比如搜索和排序。这就是为什么理解二分搜索法算法可以帮助你编写更好的算法——无论你是软件工程师、数据科学家还是其他任何开发算法的人。
理解二分搜索法算法可以帮助你写出更好的算法——无论你是软件工程师、数据科学家还是其他人
这篇文章解释了二分搜索法算法是如何工作的。该算法在排序列表中查找元素。因为搜索空间是排序的,所以该算法在每次迭代后丢弃一半的搜索空间。因此,我们将搜索空间减半,直到找到目标元素。您可以在下面看到算法的直观总结。

如何在数组中二分搜索法为数字 8(图片由作者受迈克·巴斯的启发而创作)
在排序列表上,二分搜索法算法比线性搜索算法更有效。它具有对数时间复杂度和常数空间复杂度。
喜欢这个故事吗?
如果你想把我的新故事直接发到你的收件箱, 订阅 !
成为媒介会员,阅读更多其他作家和我的故事。报名时可以用我的 推荐链接 支持我。我将收取佣金,不需要你额外付费。
https://medium.com/@iamleonie/membership
参考
[1]“c++参考”,“std::binary_search。”cppreference.com。https://en.cppreference.com/w/cpp/algorithm/binary_search(2022 年 7 月 2 日访问)
[2]《c++参考》,《std::lower_bound》cppreference.com。https://en.cppreference.com/w/cpp/algorithm/lower_bound(2022 年 7 月 2 日访问)
[3]李特码,“704。二分搜索法。”leetcode.com。https://leetcode.com/problems/binary-search/(2022 年 7 月 2 日访问)
[4]李特码,“了解二分搜索法”。leetcode.com。https://leetcode.com/explore/learn/card/binary-search/(2022 年 7 月 2 日访问)
[5]“Python”,“二等分—数组二等分算法。”python.org。https://docs.python.org/3/library/string.html#formatspec(2022 年 7 月 2 日访问)
[6] S. Selkow,G. T. Heineman,G. Pollice,《算法概述》( 2008 年), O'Reilly Media。
[7] M .巴斯,“在斯威夫特与二分搜索法分而治之”,mikebuss.com。https://mikebuss.com/2016/04/21/binary-search/(2022 年 7 月 2 日访问)
[8]“大 O 小抄”,“了解你的复杂性!”,bigocheatsheet.com。https://www.bigocheatsheet.com/(2022 年 7 月 2 日访问)
关于 Python 中的类型继承,你需要知道的一切
Python 中类型继承及其用法概述

介绍
在 Python 中,类型化是我们经常考虑的事情。虽然 Python 的强类型可能不会反映科学计算的其他语言,但它们仍然有用户对这种语言的需求和期望。也就是说,Python 是目前最受欢迎的科学计算语言,具有最迷人的生态系统,所以 Python 可能确实有一个完美的类型系统来处理这种事情。
不管怎样,关于 Python 的类型,我们可以肯定的一点是,它们非常具有声明性和动态性。也就是说,由于类型化是在执行时完成的,而不是在编译时完成的,所以用 Python 进行类型化肯定会有一些细微差别。今天,我将演示 Python 中的类型,并解释如何将所有这些结合在一起形成一个普通的脚本语言会话——以及我们可以对我们的类型进行什么类型的转换、断言和观察。
对象介绍
在 Python 中,类型通常不被称为类型,它们被称为对象。令人担忧的是,当听到单词 types 时,有多少只有 Python 程序员会感到担心,但这只是一个术语,它在功能上与更广泛的计算领域中的类等效物相同。也就是说,我将在本文中不断地提到“类型”和“类型系统”,所以要知道 Python 的翻译就是“对象”
Python 中的对象就像其他编程语言中的常规数据结构。我们为其创建一个构造函数。Python 中唯一显著的区别是在定义构造函数本身的时候。我们有一些典型的、面向对象的“类”语法。然而,有些事情是不同的;首先,有一些神奇的东西叫做 decorators,它们既可以促进元编程,也可以对 Python 类型的能力做一些很酷的事情。我们可以通过使用 type()函数获得任何 Python 对象类型的名称。一个重要的注意事项是 type()不区分子类型,因此如果您的类继承了另一个类的属性,它仍将被视为其父类型。这种子类型、面向对象的方法中使用了一个主要思想,称为 Liskov 替换原则。这个原则规定子类型应该能够代替它们的父类型。当您考虑到父类只是属性的抽象,为了一致性,这些属性应该理想地应用于它下面的类型时,这是很有意义的。
下面是一个基本子类和常规类的例子。
class Person():
def show(self):
print("This is class")
class Em(Person):
def anotherfunction(self):
print("This is subclass of Person")
Person 类是 Em 的父类。我们用括号表示,然后提供父类。然后,Em 类继承了 Person 的所有代码,这意味着我们不需要重写所有代码来创建更多这样的子类。如果我们要从构造函数 Em 初始化一个新的对象,那么它将有 show()函数可用。
在 Python 中 type 实际上是什么意思?
类型之类的东西在每种语言中都有特定的含义。在 Python 中,程序员与其他语言中的类型更加分离。尽管如此,Python 也是动态和强类型的。动态类型化仅仅意味着定义可以在运行时改变它们的类型,这可能是数据科学等领域所希望的。然而,强类型可能是你需要多加注意的事情。强类型仅仅意味着如果我们不直接要求,我们定义的名字的类型不会改变。
除了确定给定类的方法和属性之外,在 Python 中输入实际上没什么用。与其他语言相比,Python 中的类型和函数是非常分离的。这既是一件好事,也是一件坏事,因为这可能意味着类型更容易被错误地作为参数提供。从这个意义上来说,好的文档是必不可少的,所以这是使用 Python 时需要注意的一件重要事情。除了类型及其内容的实际构造之外,类型在与代码的其余部分进行交互时应用最少。当然,某些方法意味着不同的事情,但问题是 Python 不会告诉你什么时候你提供了错误的类型参数。为此,您确实需要依赖文档,这当然不是我的偏好。如果你想了解更多这方面的内容,我有一整篇文章专门讨论为什么你应该用 Python 输入你的参数,以及这实际上能做什么。
结论
这就是类型的核心思想以及它们在 Python 中的工作方式。它在很多方面与 C++非常相似,作为一个 C++的超级粉丝,我当然可以支持它。继承是一件非常有价值的事情,不仅对于方法,对于属性也是如此。我不认为 Python 有最好的类型系统,但是每种类型系统使用起来都有很多优点和缺点。使用 Python 的子类型、基于 Simula 的类型和继承系统的优点是,您可以非常容易地继承类属性。然而,缺点是您不能控制哪些属性被继承。然而,回到利斯科夫原理,这很有意义。非常感谢您的阅读,我希望这是对 Python 中类型的全面概述。
关于 Julia 中的类型,你需要知道的一切
原文:https://towardsdatascience.com/everything-you-need-to-know-about-types-in-julia-84f64c0f86f3
对 Julia 中类型系统的全面概述,以及一些你需要了解的关键内容。

介绍
在我看来,作为一种动态类型语言,在 Julia 中仍然需要考虑很多类型。这实际上是我比其他语言更喜欢的东西,其他语言有时会试图隐藏类型并隐式地为你改变类型。JavaScript 中的这类东西把我逼到了疯狂的边缘,所以像 Julia 的(令人惊奇的)类型系统是非常令人耳目一新的。来自许多其他语言,我觉得 Julia 的类型系统非常健壮,可能是我用过的最好的一个。
然而,对于任何动态类型化的类型系统,类型如何被持有以及如何被改变和操作的特定设计必然会有一些奇怪和细微的差别。这是在 Julia 被 JIT 编译的基础上,而且…哦,是的…
使用多重分派作为编程范例。
今天,我将与你们分享我所知道的关于类型的一切,从数据结构、构造函数和方法与类型的接口,到学习更多关于类型的知识,甚至构建复杂的构造函数。
№1:一切都是一种类型。
关于 Julia,我们应该了解的第一件事是,一切都是一种类型。这是 Julia 语法方法论的中心,一切都需要是一个类型。也就是说,既然一切都是类型,我们可以通过其他调用来分派类型,等等。就像那样,我们的类型可以通过它们的方法组连接起来。这是茱莉亚背后的核心思想,而且很奇妙。例如,函数的类型如下:
Function
一切事物都有一部分是这样被持有的,因为语言大部分是自己写的。此外,这些类型的大部分功能都可以通过导入基方法然后将它们绑定到值来扩展。这使得你在 Julia 中写的任何东西都更加可扩展。
№2:类型 of()
如果我们不知道某个东西的类型,我们可以使用 type of()方法来解决这个问题。对于您可能希望偶尔运行的条件来说,这也很方便。这个函数也方便地提供了许多探索特性,因为用 typeof 调用第一个索引非常容易。然而如今,我觉得我更喜欢 od.dtype()的 OddFrames.jl 方法。
№3: varinfo()
varinfo()方法是一个非常酷和简单的方法,每当你启动它时,Julia 就会导出它。换句话说,你可以在任何地方使用它!它显示了我们的全球环境中的值及其各自的内存使用情况的减价表:
varinfo()

第四名:派遣
如果你对 Julia 感兴趣,但是没有听说过 multiple dispatch,或者它在 Julia 中是什么样的——准备在下一集享受这个奇妙的范例吧。我们基本上可以将方法名定义为它们的别名和参数类型!这是一个自 ML 语言以来就存在的概念,但至少对于大型编程语言设计来说,有点不为人知。我有一整篇文章详细介绍了为什么多重调度在 Julia 中如此之好,实际上有趣的是,它使用了与今天相同的项目中的一些旧代码!
Julia 以一种巨大的方式出现在 dispatch 中,由于多重调度,这个列表中的许多元素都是可能的。这是一个关键特性,有太多的理由认为它很棒。但是,首先让我向您展示一个使用多重分派来扩展 length()函数的基本用法,例如,它相当于 Python 中的 len()。
function _drop!(column::Symbol, labels::Array{Symbol}, columns::Array,
types::Array)
pos = findall(x->x==column, labels)[1]
deleteat!(labels, pos)
deleteat!(types, pos)
deleteat!(columns, pos)
end
function _drop!(mask::BitArray, labels::Vector{Symbol},
columns::AbstractArray, types::AbstractArray)
pos = findall(x->x==0, mask)
_drop(pos, column, labels, types)
end
function _drop!(row::Int64, columns::Array, labels::Vector{Symbol},
types::Array{Type})
[deleteat!(col, row) for col in columns]
end
这个例子函数,_drop!()来自我最近开发的一个叫做 OddFrames.jl 的包。顺便说一下,它已经非常成熟了,并且即将有一个稳定的版本,所以如果你想看看,这里有链接!:
https://github.com/ChifiSource/OddFrames.jl/tree/main
反正你也看到了,我有 _ 滴!()使用值绑定掩码
№5:子打字
关于在 Julia 中输入,你应该知道的下一件事是子类型。什么是子类型,它们与调度有什么关系?通过使用抽象层,子类型被用来在广泛的功能范围内扩展类型的方法。我们本质上是在定义一个新的方法范围,这些方法是私有的,用于将我们的类型传递给其他类型。在这个抽象层中,我们有很多孩子,用“:子类型”操作符表示。看看这个:
abstract type AbstractOddFrame end
abstract type AbstractMutableOddFrame <: AbstractOddFrame end
我们定义了这两个抽象层,Julia 将始终使用它拥有的最低层。如果调度是直接针对那个类型的,它将跳到下一个可用的抽象类型,直到它用完抽象;然后你得到一个 MethodError。这意味着有了这两个子类型:
OddFrame <: AbstractMutableOddFrame
和
AbstractOddFrame <: ImmutableOddFrame
从技术上讲,我可以在复制函数中调用::AbstractOddFrame 来调度 ImmutableOddFrame:
function copy(od::OddFrame)
values = copy(Array{Pair}(od))
return(OddFrame(values))
endfunction copy(od::ImmutableOddFrame)
values = copy(Array{Pair}(od))
return(ImmutableOddFrame(values))
end
重要的是,我们要知道一个不可变的或可变的 OddFrame 是否存在。我们可以让这里的急件更抽象一些
copy(od::ImmutableOddFrame)
copy(od::OddFrame)
到…里面
copy(od::AbstractOddFrame)
copy(od::AbstractMutableOddFrame)
这是因为 AbstractMutableOddFrame 是抽象 OddFrames 的子类型。但是请记住,方法是在最接近的抽象类型中被读取的。这是需要记住的关键。
№6:匿名打字
您可以做的另一件有趣的事情是对一组随机数据类型有效地调用 new()。这些数据类型可以是任何东西,函数、整数、数组、矩阵、构造类型,我指的是任何东西。同样,我们可以把它构造成一个回报,就像这样:
x(w) = 5 + wy = 5
z = () -> (x;y)z.x(5)10z.y5
№7:动态构造函数类型
有时,我们可能希望将一个类型提供给构造函数,以便在某种别名下进行分派。在我们不知道将得到什么类型的回报的用例中,我们可能希望这样。我们可以使用以下语法将提供给内部构造函数的类型更改为在外部构造函数中提供。
mutable struct example{unknownT}
这种语法基本上意味着“这里的类型”,因为同样的原因,我们可以有一个数组{Pair},同时仍然为数组使用相同的内部构造函数返回,例如。这完全进入了我的下一个类型特性,所以让我们通过关注函数和结构中的 uknownT()调用来更详细地了解动态构造函数类型。
№8:内部构造函数
# Outer Constructor
mutable struct example{unknownT}
data::Dict
idontknow::unknownT# Inner Constructor
function example(data::Dict)
idontknow = 5
unknownT = typeof(idontknow)
new{unknownT}(data, idontknow)endend
使用内部构造函数也是 dispatch 的一个特性。内部构造函数很棒,因为在某种程度上它们充当了类型的初始化函数,但它们也可以像数据类对象一样使用,或者可以是一个成熟的类,我们将在下一个例子中看到。
№9:面向对象编程
只需将构造函数属性设置为 Function 类型,我们就可以轻松地改变 Julia 的范式,开始面向对象编程。您甚至可以使用成员变量,根本不需要使用 self。看看我的 OddFrames.jl 项目中的这三个 OddFrame 构造函数,看看你是否能注意到它们是如何组合在一起的。
mutable struct OddFrame <: AbstractMutableOddFrame
labels::Array{Symbol}
columns::Array{Any}
types::Array
head::Function
dtype::Function
not::Function
only::Function
drop!::Function
dropna!::Function
dtype!::Function
merge!::Function
only!::Function
#==
Constructors
==#
function OddFrame(labels::Vector{Symbol}, columns::Any,
types::Vector{DataType})
head, dtype, not, only = member_immutables(labels, columns,
types)
drop!, dropna!, dtype!, merge!, only! = member_mutables(labels,
columns, types)
return(new(labels, columns, types, head, dtype, not, only, drop!,
dropna!, dtype!, merge!, only!))
end
function OddFrame(p::Pair ...)
labels, columns = ikeys(p), ivalues(p)
length_check(columns)
name_check(labels)
types = [typeof(x[1]) for x in columns]
return(OddFrame(labels, columns, types))
end
这里实际上有几个调度电话在一起工作。因为唯一真正根据输入改变的是,我为所有的调用创建了一个分派存根,以创建我的方法并返回类型,这样代码就不会重复。同样,这也是 OddFrame 的另一个构造函数。实际的 OddFrame 类型包含几个函数,其行为很像一个类。
№10:获取字段()
另一个很酷的东西是 getfield()模块,您可以利用它来更强制性地处理 Julia 中的类型。我们可以使用它通过符号(提供给外部构造函数的字段)获得数据值。这是一个非常有趣的电话,是一个很好的记住,以防事情变得有点疯狂。在朱莉娅的作品中,你可以看到一些非常疯狂的东西。记住这是一个好主意,把这样的事情记在心里,这样你就可以通过探索回报来实验和学习。
№11:将方法扩展到类型
我想谈的最后一件事是展示多重调度作为一种范例是多么有效..是的,因为这可能暗示;这一个也确实因为多重派遣才存在。在下面的示例中,我们从 base 扩展了 getindex()方法,以便在 OddFrame 上设置索引:
import Base: getindex
function getindex(od::AbstractOddFrame, col::Symbol)
pos = findall(x->x==col, od.labels)[1]
return(od.columns[pos])
end
结论
Julia 的范式方法可能有点奇怪
“如果多重派遣能做到,我们就让你去做”
背后的方法论。也就是说,对我来说,这当然不是一件坏事。多重分派是如此不可思议的方便,以至于可以出于许多不同的原因来制作伟大的软件。我们对 Julia 的能力没有任何限制,因为它有如此多的通用应用程序——作为一个范例,多重调度是非常通用的。它使事情更容易扩展,允许我们构建复杂的构造函数,并允许我们基于层次结构对不同类型使用相同的方法调用——这非常棒!非常感谢你阅读我的文章,它对我来说意味着整个世界。我希望这澄清了一些关于用 Julia 语言打字的误解或秘密。一如既往,祝你度过愉快的一天!
构建惊人的二进制分类器所需的一切
利用机器学习将产品评论自动分类为正面或负面

安妮·斯普拉特在 Unsplash 上的照片
机器学习中的分类是什么?
有两种最简单形式的监督机器学习方法。首先,你可能会遇到一个回归问题,你试图预测一个连续变量,比如温度或股票价格。第二个是分类问题,您希望预测一个分类变量,如通过/失败或垃圾邮件/火腿。此外,我们可能会遇到二元分类问题,我们将在此仅讨论两个结果,以及多类分类两个以上的结果。
准备数据
我们想采取几个步骤来为机器学习准备数据。对于文本,我们希望清除它移除不需要的字符或数字,我们希望移除停用词,或出现频率太高的词,我们还应该词干或对词进行去词法分析,这将把诸如运行和运行这样的词带到它们的根形式【T28 运行。我们可能想要创建新列或功能来帮助您的机器学习分类过程。
对于这个例子,我们将使用 Kaggle 上女装电子商务服装评论的数据集,该数据集可以在 CC0:公共领域下供您使用。
像往常一样,让我们加载执行该分析所需的所有库:
# Gereral Imports
import numpy as np
import pandas as pd
import re
import string
from timeit import timeit
# Machine Learning Imports
from sklearn.model_selection import train_test_split
from sklearn.metrics import ConfusionMatrixDisplay
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn import metrics
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import MinMaxScaler
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
# Model Persistence Imports
from joblib import dump, load
# Test Processing Imports
import nltk
from nltk.stem import PorterStemmer
# Plotting Imports
import matplotlib.pyplot as plt
%matplotlib inline
特征工程
该数据集包含评论、评级、部门名称和撰写评论的人的年龄。评级系统是经典的1–5 星系统。在清理之前,让我们导入数据并创建一些我们将在模型中使用的关键列,也称为特征工程。我们将使用read_csv函数读取数据并创建一个名为df的数据帧。
- 由于我们正在执行二进制分类,我们的目标变量需要是
1或0。在五星评论系统中,我们可以将4和5评论设为正级,然后将剩余的1、2和3评论设为负级。 - 这组特殊的评论既有标题又有评论文本字段。我们可以将这两列合并成一个名为 Text 的新列,以简化我们的处理。
- 作为模型中的另一个特性,我们可以创建一个新的列来表示评论文本的总长度。
注意 : 这里我没有展示的一个步骤是 EDA 或者探索性数据分析。我建议你总是在建立模型之前这样做。你可以在我的帖子 探索性数据分析 中找到我的过程。
# Import the data
df = pd.read_csv("ClothingReviews.csv")
# add a column for positive or negative based on the 5 star review
df['Target'] = df['Rating'].apply(lambda c: 0 if c < 4 else 1)
# Combine the title and text into a single column
df['Text'] = df['Title'] + ' ' + df['Review Text']
# Create a new column that is the length of the text field
df['text_len'] = df.apply(lambda row: len(row['Text']), axis = 1)
清理文本
接下来,我们需要清理文本。我已经创建了一个适用于几乎所有 NLP 清理情况的函数。我们先来看看正文前的正文:
' Love this dress! it\'s sooo pretty. i happened to find it in a store,
and i\'m glad i did bc i never would have ordered it online bc it\'s
petite. i bought a petite and am 5\'8". i love the length on me-
hits just a little below the knee. would definitely be a true
midi on someone who is truly petite.'
接下来是一个用于处理字符串的函数:
# Cleaning Function
def process_string(text):
final_string = ""
# Convert the text to lowercase
text = text.lower()
# Remove punctuation
translator = str.maketrans('', '', string.punctuation)
text = text.translate(translator)
# Remove stop words and useless words
text = text.split()
useless_words = nltk.corpus.stopwords.words("english")
text_filtered = [word for word in text if not word in useless_words]
# Remove numbers
text_filtered = [re.sub('\w*\d\w*', '', w) for w in text_filtered]
# Stem the text with NLTK PorterStemmer
stemmer = PorterStemmer()
text_stemmed = [stemmer.stem(y) for y in text_filtered]
# Join the words back into a string
final_string = ' '.join(text_stemmed)
return final_string
我们使用 Pandas apply方法在我们的数据帧上运行这个。应用这个函数后,让我们来看看结果字符串。
df['Text_Processed'] = df['Text'].apply(lambda x: process_string(x))
'love dress sooo pretti happen find store im glad bc never would
order onlin bc petit bought petit love length hit littl knee would
definit true midi someon truli petit'
我们可以看到字符串非常干净,没有数字、标点符号、停用词,单词被词干化成它们最简单的形式。我们已经差不多准备好开始建造我们的模型了!
不平衡数据测试
我们必须了解我们的数据集是否在目标中的两个类之间不平衡。我们可以用熊猫的一行代码很快做到这一点。不平衡数据是指一个类比另一个类有更多的观察值。当我们训练我们的模型时,结果将是模型将偏向具有最多观察值的类。
df['Target'].value_counts()
1 17433
0 5193
Name: Target, dtype: int64
计数让我们知道数据不平衡。正面类别1的观察数量是负面类别0的三倍。我们必须确保在我们的模型中恰当地处理不平衡。在本文中,我将介绍几种不同的方法。
管道建设
流水线是正确构建机器学习模型的必备条件。它们帮助您组织转换数据和重复使用模型所需的所有步骤。您可以单独执行这些步骤,但是在将您的模型应用到新数据时,如果您不这样做,就不容易。稍后,我将向您展示这在新数据上的实际工作方式,但现在让我们先构建管道。
我们有一个创建管道的函数。管道被包装在一个函数中,以便在我们评估我们的选项时利用多个模型。
该功能中的第一个是列变压器。列转换器允许我们以任何适合我们模型的方式预处理数据。有许多不同的方法来转换你的数据,我不会在这里一一介绍,但是让我解释一下我们使用的两种方法。
- tfidf 矢量器:TF-IDF 矢量器将文本转换成数值。关于这方面的详细描述,请查看我在 BoW 和 TF-IDF 上的帖子。在这里,我们正在转换我们的
Text_Processed列。 - 最小最大缩放器:将所有数值转换成一个介于
0和1之间的范围。大多数 ML 算法不处理具有大范围值的数据;扩展数据始终是一种最佳做法。你可以在 Scikit-Learn 的文档上阅读更多相关内容。这里我们正在扩展我们的text_len列。
第二个是管道本身的创建。管道列出了我们想要对数据运行的步骤。我们这里有一个相当简单的管道,但是您可以添加任意多的步骤。我们有以下内容。
- 准备:这是从上面看的柱形变压器。它将矢量化我们的文本,并缩放我们的文本长度。
- clf :这是我们选择分类器实例的地方。你可以看到这被传入我们的函数,我们把它传入函数,用相同的数据测试不同的分类器。
注意 : 分类器的实例就是类本身。比如 *LogisticRegression()* 就是 *LogisticRegression* 的一个实例。
def create_pipe(clf):
column_trans = ColumnTransformer(
[('Text', TfidfVectorizer(), 'Text_Processed'),
('Text Length', MinMaxScaler(), ['text_len'])],
remainder='drop')
pipeline = Pipeline([('prep',column_trans),
('clf', clf)])
return pipeline
通过交叉验证选择模型
当构建机器学习模型时,最佳实践是执行模型选择。模型选择允许您对数据测试不同的算法,并确定哪种算法的性能最好。首先,我们将把数据集分成X和y数据集。X代表我们模型的所有特征,y将代表目标变量。目标是我们试图预测的变量。
X = df[['Text_Processed', 'text_len']]
y = df['Target']
现在是交叉验证两个分类器的时候了。交叉验证是将数据划分为 n 个不同部分的过程,然后您使用这些部分来验证您的模型。交叉验证很重要,因为在某些情况下,它从训练集中学习的观察值可能不代表测试集中的观察值。因此,您可以通过运行不同的数据片来避免这种情况。在 Scikit-Learn 的文档上阅读更多相关信息。
重要 : 下面有一点我要特别注意的是RepeatedStratifiedKFold进行交叉验证。分层的交叉验证器将确保在不平衡的数据的情况下,分区在分割中保持相对的类频率。
最后,我们将使用一个支持class_weight参数的分类器来处理不平衡数据。Scikit-Learn 的一些模型支持这一点,只需将值设置为balanced,我们就可以解决不平衡的数据。其他方法包括 SMOTE (合成生成少数类观察值以平衡数据,但这是一个很好的起点。你可以从我的另一篇文章中读到更多关于处理不平衡数据的信息。
models = {'LogReg' : LogisticRegression(random_state=42,
class_weight='balanced',
max_iter=500),
'RandomForest' : RandomForestClassifier(
class_weight='balanced',
random_state=42)}
for name, model, in models.items():
clf = model
pipeline = create_pipe(clf)
cv = RepeatedStratifiedKFold(n_splits=10,
n_repeats=3,
random_state=1)
%time scores = cross_val_score(pipeline, X, y,
scoring='f1_weighted', cv=cv,
n_jobs=-1, error_score='raise')
print(name, ': Mean f1 Weighted: %.3f and StdDev: (%.3f)' % \
(np.mean(scores), np.std(scores)))
CPU times: user 23.2 s, sys: 10.7 s, total: 33.9 s
Wall time: 15.5 s
LogReg : Mean f1 Weighted: 0.878 and StdDev: (0.005)
CPU times: user 3min, sys: 2.35 s, total: 3min 2s
Wall time: 3min 2s
RandomForest : Mean f1 Weighted: 0.824 and StdDev: (0.008)
在上面的函数中,你可以看到评分是用f1_weighted完成的。选择正确的指标是一个完整的讨论,理解它至关重要。我写过如何选择正确的评估指标。
下面简单解释一下我为什么选择这个指标。首先,我们有不平衡的数据,而且我们从未想要使用accuracy作为我们的衡量标准。不平衡数据的准确性会给你一种虚假的成功感,但准确性会使更多的观察偏向这个类。还有precision和recall可以帮助你最小化误报(精确度)或者最小化漏报(召回)。根据您想要优化的结果,您可以选择其中之一。
由于我们不喜欢预测正面评价和负面评价,在这种情况下,我选择了F1分数。F1顾名思义就是将精确度和回忆的调和平均值结合成一个单一的度量。然而,也有一种方法可以告诉这个指标用weighted标志对不平衡的数据进行评分。正如 Sciki-learn 文档所述:
计算每个标签的指标,并根据支持度(每个标签的真实实例数)计算其平均值。这改变了“宏”以解决标签不平衡;它会导致精确度和召回率之间的 F 值。
基于这些结果,我们可以看到LogisticRegression分类器表现稍好,速度更快, 15 秒对 3 分钟。因此,我们可以继续前进,用它来训练我们的模型。
同样,请阅读我对模型评估的完整解释,让您更好地了解使用哪一个以及何时使用。
模型训练和验证
我们终于到了!是时候训练我们的最终模型并验证它了。我们将把我们的数据分成训练和测试分区,因为我们在上面执行交叉验证时没有这样做。你永远不会想用你所有的数据来训练你的模型,而是留一部分出来测试。每当模型在训练期间看到所有数据,它就会知道这些数据并导致过拟合。过度拟合是指模型过于擅长预测它所看到的数据,而不能推广到新数据。
# Make training and test sets
X_train, X_test, y_train, y_test = train_test_split(X,
y,
test_size=0.33,
random_state=53)
接下来,一个快速函数将适合我们的模型,并用一个classification_report和一个混淆矩阵 (CM)对其进行评估。我认为与 CM 一起运行分类报告是至关重要的,它将向您展示您的每个关键评估指标,并告诉您模型的表现如何。CM 是可视化结果的好方法。
def fit_and_print(pipeline, name):
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
print(metrics.classification_report(y_test, y_pred, digits=3))
ConfusionMatrixDisplay.from_predictions(y_test,
y_pred,
cmap=plt.cm.YlOrBr)
plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('predicted label')
plt.tight_layout()
plt.savefig('classification_1.png', dpi=300)
clf = LogisticRegression(random_state=42,
class_weight='balanced',
max_iter=500)
pipeline = create_pipe(clf)
fit_and_print(pipeline, 'Logistic Regression')
precision recall f1-score support
0 0.681 0.855 0.758 1715
1 0.953 0.881 0.915 5752
accuracy 0.875 7467
macro avg 0.817 0.868 0.837 7467
weighted avg 0.891 0.875 0.879 7467

作者图片
结果出来了!分类报告显示了我们需要的一切。因为我们说过不一定要针对正类或负类进行优化,所以我们将使用f1-score列。我们可以在0.758看到0班,在0.915看到1班。每当您有不平衡的数据时,您可以期望较大的类执行得更好,但是您可以使用上面的一些步骤来提高模型的性能。
92% 的时候,该模型会正确地将评论分类为正面类,而 76% 的时候会正确地将评论分类为负面类。仅仅通过查看用户提交的评论文本,就能给人留下深刻的印象!
坚持模型
我们可以很容易地将我们的模型持久化以备后用。因为我们使用了一个管道来构建模型,所以我们执行了所有必要的步骤来预处理我们的数据并运行模型。持久化模型使得在生产服务器上运行它或者稍后加载它变得容易,并且不需要再次训练它。保存到磁盘后,这个模型在磁盘上还不到500KB简直不可思议!
# Save the model to disk
dump(pipeline, 'binary.joblib')
# Load the model from disk when you're ready to continue
pipeline = load('binary.joblib')
测试新数据
现在来演示真正重要的部分。你的模型对以前从未见过的新观测的预测有多好?这个过程在生产中会有很大的不同,但是我们可以通过创建一些新的评论来模拟它。我还创建了一个函数,它将字符串列表转换成带有干净文本和text_len列的格式正确的数据帧。
def create_test_data(x):
x = process_string(x)
length = len(x)
d = {'Text_Processed' : x,
'text_len' : length}
df = pd.DataFrame(d, index=[0])
return df
# Manually Generated Reviews
revs = ['This dress is gorgeous and I love it and would gladly recommend it to all of my friends.',
'This skirt has really horrible quality and I hate it!',
'A super cute top with the perfect fit.',
'The most gorgeous pair of jeans I have seen.',
'This item is too little and tight.']
# Print the predictions on new data
print('Returns 1 for Positive reviews and 0 for Negative reviews:','\n')
for rev in revs:
c_res = pipeline.predict(create_test_data(rev))
print(rev, '=', c_res)
Returns 1 for Positive reviews and 0 for Negative reviews:
This dress is gorgeous and I love it and would gladly recommend it to all of my friends. = [1]
This skirt has really horrible quality and I hate it! = [0]
A super cute top with the perfect fit. = [1]
The most gorgeous pair of jeans I have seen. = [1]
This item is too little and tight. = [0]
结论
你有它!构建二元分类器的端到端示例。用几个步骤构建一个可能很简单,但是这篇文章概述了你应该用来正确地构建你的分类器的优选步骤。我们从清理数据和一些轻量级的特征工程、管道建筑、模型选择、模型训练和评估开始,最后持久化模型。最后,我们还在新数据上进行了测试。这个工作流程应该适用于几乎任何二元或多类分类问题。享受和快乐的模型建设!
这里我还遗漏了另外两个步骤,稍后我会讲到。特征选择和超参数调整。根据数据的复杂性,您可能希望深入研究特征选择,并充分发挥模型的性能,请查看超参数调整。
这篇文章的所有代码都可以在 GitHub 上找到
如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。一个月 5 美元,让你可以无限制地访问成千上万篇文章。如果你使用我的链接注册,我会赚一小笔佣金,不需要你额外付费。
在稀疏回报环境中进化神经网络
原文:https://towardsdatascience.com/evolving-a-neural-network-in-a-sparse-reward-environment-db1f596d5dc6
用遗传算法求解月球着陆器连续环境下的稀疏回报

温斯顿·陈在 Unsplash 上的照片
遗传算法是一种受生物进化启发的解决优化问题的强大方法。它们由一个连续的过程组成,通过随机突变和交叉创建解决方案,并选择最佳解决方案(根据要最大化的数量)进行繁殖。选择最佳解决方案的一个关键要素是 适应度函数 ,该函数将任何解决方案关联到一个名为的解决方案的 适应度 。适应度应该对应于优化问题中要最大化的目标。每个解都由一组称为 基因 的参数来参数化,这些参数构成解的 染色体 。每一步所有当前解的集合称为 群体 。一个遗传算法由若干后续步骤组成,称为其中:
- 评估每个解决方案的适合度;
- 从由选择函数指定的种群中选择下一代的双亲,偏好具有最高适应度的解;****
- 按照 交叉函数 的规定,每对父母通过混合他们染色体中的基因产生新的解;
- 根据 变异函数对新溶液的基因进行变异;
- 从群体中移除低适应性解决方案。
随着世代数量的增加,群体将充满更高适应性的个体,因为这些个体有更高的概率将其基因传播给下一代。此外,由于交叉,使解决方案在获得更高适应度方面具有优势的突变将组合并累积到后代中。
遗传算法是非常通用的,只要有可能写出一个全局最大值解决任务的适应度函数,就可以应用于各种优化问题。
一个非常有趣的应用是使用遗传算法来优化神经网络的权重,这与通常的梯度下降方法相反。尽管对于许多任务,遗传算法可能收敛得更慢(因为我们放弃了一条有价值的信息,即损失函数的梯度),但它们可以克服基于梯度的方法带来的一些限制。例如,遗传算法不会遭受通常的爆炸/消失梯度问题。遗传算法可能是基于梯度的方法的有趣替代的另一种情况是当梯度几乎总是零或无信息时。
遗传算法可能有用的一个用例是解决带有稀疏回报的顺序决策问题。顺序决策问题对应于需要一系列行动才能解决的任务。作为一个例子,让我们考虑逃离迷宫的问题:我们需要在每个十字路口选择我们想要遵循的方向,而遵循的方向取决于迷宫中的当前位置。当许多决策步骤没有外部奖励时,奖励稀疏就会发生,例如,当奖励只在任务结束时提供。在我们的例子中,如果只在成功逃离迷宫后给予奖励,而在中间步骤没有奖励,这种情况就会发生。这种问题对简单的强化学习算法提出了挑战,因为许多步骤不会提供信息梯度。
由于我很好奇遗传算法在稀疏奖励环境中的表现,我用它们在 OpenAI gym 的月球着陆器连续环境中进化了一个神经网络。在香草版本中,这种环境有一种奖励,它不是稀疏的。为了模拟一个稀疏的奖励环境,我使用了在整个剧集的每个时间点获得的奖励总和作为适应度函数。这样,对于遗传算法来说,在每个时间步提供奖励的原始环境和在最后一步立即给予奖励的环境没有区别。
我在这个 Colab 笔记本中提供了完整的代码,它使用了我在这个 GitHub 库中实现的遗传算法包。让我们一起来看看各个部分。
环境
正如预期的那样,我已经使用了 OpenAI gym 的月球着陆器连续环境。环境期望我们的代理提供一个由二维向量组成的动作,向量的值介于-1 和 1 之间。第一个条目控制主发动机:它从 0 (-1)到 100% (+1)改变功率。但是,主机不能在低于 50%的功率下工作。第二个条目控制侧边引擎:在区间[-1,-0,5]启动左侧引擎,在区间[-0.5,0.5]关闭侧边引擎,[0.5,1]启动右侧引擎。
环境为代理提供一个由包含以下信息的 8 维向量组成的观察状态:
- 着陆器的 x 坐标;
- 着陆器的 y 坐标;
- 着陆器的水平速度;
- 着陆器的垂直速度;
- 着陆器的角度;
- 着陆器绕其中心的角速度;
- 一个布尔值,指示左腿是否接触地面;
- 表示右腿接触地面的布尔值。
目标着陆位置由一个平滑的水平线段组成,总是位于坐标原点。每集开始时,着陆器以随机速度向随机方向启动。这种随机性使得问题对于遗传算法来说更加困难,因为有时由于初始条件的随机性,好的解决方案可能接收到较差的总回报。由于总回报是我们用来将解决方案传播给下一代的指标,一些好的解决方案可能仅仅因为运气不好而无法将它们的基因传递下去。我预计这将减缓算法的收敛,但它将使它对不同的初始条件更鲁棒。
遗传算法
我现在将详细解释我进行小实验的遗传算法的各个部分。
****变异函数:我用了一个变异概率为 0.1 的高斯变异。这意味着新创建的子解的每个基因有 0.1 的概率发生突变。突变包括从平均值为 0、标准偏差为 0.1 的高斯分布中取样的随机值的加法。
****交叉函数:我用的是 0.5 概率的单点交叉。这意味着,通过交配 2 个亲本解而获得的新解有 0.5 的概率通过单点交叉从两个亲本获得,并且有 0.5 的概率等于亲本 1。单点交叉选择从 0 到染色体长度的随机索引 i、,并创建由父代 1 的染色体的基因直到位置 i 和父代 2 的染色体的基因从位置 i 到末端组成的新染色体。
****适应度函数:分配给每个解的适应度就是一集获得的总奖励。再次注意,这没有利用环境提供的详细的逐步奖励。
****选择函数:在每一代结束时,根据亲本的适合度,通过从波尔兹曼分布中取样解决方案来选择亲本。这意味着解决方案被选为父解决方案的概率等于

图片作者。
其中 fᵢ 是所考虑的解决方案的适合度,分母中的和超过群体中的所有解决方案。玻尔兹曼选择的温度 T 调节解的选择压力:在高温下,解具有相似的被选择的概率,而在低温下,具有更高适应度的解具有显著更高的被选择的概率。我从温度 T=1 开始,随着世代数 n 的增加,温度逐渐降低

图片作者。
一直到最小值 0.1,之后我保持不变。
****精英主义:为了避免由于变异和交叉的随机效应而失去一些好解的风险,将前 10%的解不加任何改变地传递给下一代。
我选择了一个规模为 50 的群体,从随机初始化的神经网络权重开始。
神经网络
现在让我们来看看神经网络,其权重将构成群体中解的基因。我选择了一个简单的结构,由一个前馈神经网络组成,它有两个隐藏层,每个隐藏层有 256 个特征。每一层都通过一个双曲正切激活函数。这将把输出限制在[-1,1]之间。最后一层的输出直接作为动作。有趣的是,在梯度下降的常规优化情况下,对中间层使用 tanh 激活会导致梯度饱和,可能不是一个好的选择。相反,在我们的例子中,我们不希望出现这样的问题,因为我们没有渐变,而且 tanh 激活看起来确实工作得很好。实际上,激活的饱和部分可能对我们的设置有益,因为它们将鼓励我们群体中解决方案的多样性,因为不同的权重将具有相似的激活。
结果
让我们最终可视化我的小实验的结果吧!
作为免责声明,我没有太多的时间来广泛地优化算法或神经网络架构的参数,所以如果结果可以很容易地得到改善,我不会感到惊讶。此外,进化在 180 个时期终止。这个值完全是任意的,由于时间限制,我在这里停止了它。数据似乎表明性能仍在提高,因此让算法运行更多代可以产生更好的结果。
下面是每一代的最佳适应度图

图片作者。
以及每一代群体中所有解的平均适合度(阴影区域代表区域[均值-标准差,均值+标准差])

图片作者。
正如我们所看到的,不仅每一代的最佳解在 100 代之后急剧上升,而且群体中所有解的平均适应度随着时间稳步增加。因此,作为一个整体,人类正在进化,越来越好地解决这个问题!
让我们通过查看不同代中具有最高适应性的解决方案所生成的片段来可视化进化过程。
第 50 代

在第 50 代使用最高适应性解决方案生成 5 集。图片作者。
第 100 代

在第 100 代使用最高适应性解决方案生成 5 集。图片作者。
第 150 代

在第 150 代使用最高适应性解决方案生成 5 集。图片作者。
第 180 代

在第 180 代使用最高适应性解决方案生成 5 集。图片作者。
我们看到最近几代人的技能有了令人印象深刻的提高!
虽然在 50 代之后,神经网络除了自由降落之外什么也没有学到,但是我们开始看到第 100 代着陆器的方向稳定,并试图软着陆。到第 150 代时,最佳解决方案能够避免崩溃,在最后一代(180)时,我们观察到完美的目标着陆!
一个重要的问题是在探索的解决方案中找到一个能更好地完成任务的解决方案。事实上,每个解决方案的适用性是在单集结束时评估的,并且由于环境的随机性,不同集的相同解决方案的总回报具有很大的可变性。因此,如果我们要选择与最高记录的适应性相对应的解决方案,我们可能会选择一个平均表现不佳但运气不错的解决方案(或者是因为环境配置特别简单,或者是因为它仅适用于少数情况)。这个问题的一个解决方案可能是不仅仅使用单个情节的奖励,而是使用 N 个这样的情节的平均奖励作为适应度,这也有助于使进化过程更加稳定。这个。然而,由于 N 或群体中的解的数量变得非常大,这将是计算上昂贵的(因为适应度评估是算法中最慢的部分)。或者,由于整个群体的平均水平随着世代数的增加而提高,因此将注意力限制在上一代的解决方案上是有意义的。平均而言,它们应该比前几代的解决方案更好。尽管如此,计算群体中所有解决方案的 100 集的平均回报还是非常慢。因此,我对上一代中适应性最高的解决方案进行了评估。我注意到,如前所述,不能保证这是有史以来探索过的最好的解决方案(按平均回报计算),甚至不能保证是上一代的解决方案。然而,这个解决方案已经在 100 集以上获得了令人印象深刻的 238.84 的平均奖励!
结论
我对我的小实验结果非常满意。在仅仅 180 代之后,并且在对参数和神经网络架构进行最小到没有优化的情况下,遗传算法能够找到可靠地解决月球着陆器连续任务的解决方案。该算法成功了,尽管事实上它没有被传递关于环境的细粒度奖励的详细信息,而只是在每集结束时模拟稀疏奖励环境的累积奖励。我相信,总的来说,对于只有少量奖励的任务,遗传算法可以成为通常的强化学习方法的一种有竞争力的替代方法。
我希望你喜欢这篇文章,我很高兴听到你的想法!
进化的长生不老药密码:用长生不老药 AST 进行遗传编程
用 Elixir 的宏系统&抽象语法树进行遗传编程

遗传编程是一种强大的技术,它使用进化和自然选择作为搜索技术来创建能够解决问题的算法。
在这篇文章中,我描述了 Elixir 的内置宏系统和抽象语法树(AST)表示如何使实现可以解决现实世界问题的基本遗传编程系统变得简单。
如果你已经了解了基因编程,并且只是想自己尝试一下代码,那么前往 GitHub 查看 QuoteGP 。
进化解决方案——什么是遗传编程?
遗传编程是一种人工智能技术,在这种技术中,我们试图进化出一种解决特定问题的算法。我们从几个随机解决方案开始,然后使用进化技术来引导我们在许多代中找到越来越好的解决方案。如果一切按预期进行,我们会找到一个完全适合我们问题的解决方案。
适者生存
遗传编程的工作方式是从几个(也许是几千个)随机的代码片段开始。我们根据问题集评估每个程序,以评估一个适应值——一个告诉我们程序在解决问题方面有多好的数字。当我们开始使用随机算法时,它们几乎总是非常糟糕(即,具有非常低的适应值)。但有些,只是偶然,比其他人略胜一筹。因此,我们使用**选择过程来选择最好的,并对它们进行轻微的进化,以产生新一代的解决方案。
新一代的解决方案可能仍然不好,但是比我们开始使用的解决方案稍微好一点。如果我们一遍又一遍地重复这个过程,我们最终会得到非常好的解决方案——如果我们坚持足够长的时间,我们可能最终会得到一个完美的解决方案。
遗传算子
那么,我们如何从现有的解决方案中产生新的解决方案呢?我们使用各种各样的遗传算子来实现。在我们的 QuoteGP 系统中,我们使用了几个不同的遗传算子来产生新一代的解决方案:
- 遗传交叉:从上一代中取出两个解决方案,并将它们混合(结合两者的一部分)以产生一个新的候选解决方案
- 变异:从上一代中取出一个解决方案,随机改变它以产生一个新的解决方案
- 复制:取一个解决方案,然后“按原样”复制
- 随机:生成一个完全随机的新解
除了“随机”操作符之外,所有这些操作符在选择对哪个程序进行操作时,都使用基于适合度的选择过程。这意味着在执行交叉或变异时,我们更喜欢操作“更好”的解决方案。
在更广泛的遗传编程领域中,在选择过程和遗传算子的选择上有无限的变化,但是我们在 QuoteGP 中保持它的简单并使用上面描述的方案。
如何表示可进化的程序
为了建立和测试这个系统,我们将考虑一类被称为符号回归、的问题,在这些问题中,我们试图找到一个数学公式来最好地表示一些训练数据。
构建 GP 系统最重要的考虑之一是程序表示:可执行基因组看起来像什么?
我们可能会尝试一种常用的真实世界编程语言,如 C、Python 或 Ruby。尽管人类程序员最熟悉这些语言,但他们并不特别擅长进化:在所有可表示的文本字符串的搜索空间中,几乎没有一个字符串是语法上有效的程序!
在设计一个 GP 系统(或者实际上,任何一种进化算法)时,表示是关键:我们想要一种表示,其中所有可表示的状态都是有效的程序,并且对于诸如变异和交叉之类的遗传操作符是健壮的。这在很大程度上排除了我们习惯使用的文本编程语言。

GP 系统中使用的基于树的程序表示。图片来自geneticprogramming.com。
考虑到这一点,一种常见的 GP 方法是基于树的遗传编程,其中我们将程序表示为表达式树。给定一组允许的节点和终端值,我们可以确保任何树都是有效的程序,并且诸如变异和交叉的遗传算子也将生成有效的程序。
还有一些其他的表示类型,如线性 GP,其特点是类似于机器字节码或基于堆栈的 GP 的线性指令系列,其中类型化堆栈用于存储中间值,但对于这个项目,我们将专注于基于树的表示。
通常,用 C、Java 或 Python 这样的语言构建一个基于树的 GP,需要构建某种新颖的表示和解释器。但是有些语言,比如 Elixir,有一个语法树和解释器作为一级概念内置。在这种情况下,我们可以使用语言本身进行基因编程。
同形语言与遗传编程
一些编程语言是同形,意味着代码是数据,数据是代码。在这些语言中,例如 Lisp,遗传编程系统可以非常简单地实现,因为程序表示是数据表示:进化的程序可以在语言中本地执行。
尽管 Elixir 语言并不完全是同形异义的,但它确实提供了对内置抽象语法树(AST)的本地访问和操作,而抽象语法树具有这些同形异义的特性。虽然 Elixir 代码通常不是以普通数据的形式编写的,但它很容易转换成普通数据表示形式。**
*# Turn an Elixir expression into an AST:
iex(1)> quote(do: 1 * 3)
{:*, [context: Elixir, import: Kernel], [1, 3]}# Turn an AST into an Elixir expression:
iex(2)> Macro.to_string({:*, [context: Elixir, import: Kernel], [1, 3]})
"1 * 3"*
这意味着我们可以使用 Elixir ASTs 作为本地遗传编程表示来进化 Elixir 代码。我们可以生成随机的灵丹妙药(受我们指定的保持它们有效的约束),评估它们的适合度,然后对它们执行一般操作以产生新的。如果我们找到一个解决方案或其他我们感兴趣的程序,我们可以把它转换回仙丹来读取或执行它。
样本问题—符号回归
一个符号回归问题是一个曲线拟合问题,在这个问题中,我们试图确定一个数学表达式,它将为一系列给定的输入产生一系列输出值。为了测试我们的系统&演示它是如何工作的,我们将挑选一些玩具符号回归问题,这些问题系统应该能够轻松解决,但是仍然需要一些搜索&进化。
假设给了我们一组数据,代表对某个过程的观察,我们试图找出一个公式来生成这样的数据。例如,这里有一个输入和输出值的列表:
*[
[10, 138],
[11, 162],
[12, 188],
[13, 216],
[14, 246],
[15, 278],
...
]*
我们希望我们的系统接受这些数据,并进化出产生这些数据的程序表达式。在这种情况下,我们要找的表达式是x*x + 3x + 8。我们知道这一点,因为我们自己编造了数据,但我们想看看我们的基因编程系统是否能自己解决这个问题。
注:尽管我们之前讨论过 适应值 值越高越好,但在许多实现中(包括本例),我们使用 误差值 来表示我们离实际解有多远。这些代表相同的东西,只是我们试图进化更小的误差值,而不是更高的适应值。这只是实现细节,对系统如何工作没有影响。
让我们看看 QuoteGP 系统的典型输出是什么样的:
*=== Generation 0 best fitness: 252.0 - program: (10 - 1 + 7 * -1 + input) * (input + (3 - -8 / -4))
=== Generation 1 best fitness: 252.0 - program: (10 - 1 + 7 * -1 + input) * (input + (3 - -8 / -4))
=== Generation 2 best fitness: 1008 - program: (input + 4) * (input + -1)
=== Generation 3 best fitness: 203 - program: (7 + -3 - input * -1) * input
=== Generation 4 best fitness: 22.75 - program: input * (input + (3 - -8 / -4 / -4))
=== Generation 5 best fitness: 22.75 - program: input * (input + (3 - -8 / -4 / -4))
=== Generation 6 best fitness: 8.75 - program: -5 - -6 + input * input + (input - input * (-3 - 8 - 9)) / (1 - 1 - -6)
=== Generation 7 best fitness: 2.5968626643538126 - program: -7 + (input + 4) * (input + -1) + (-5 - input * (-3 - 8 - 9)) / input
=== Generation 8 best fitness: 4.861111111111127 - program: input + input * input + (6 * (10 + 7) + 4 - input - input * (-8 - 8 - 9)) / (7 - 1 - -6)
=== Generation 9 best fitness: 1.6155871338926322e-27 - program: input * (input + (3 - -8 / input))
=== Generation 10 best fitness: 1.6155871338926322e-27 - program: input * (input + (3 - -8 / input))
=== Generation 11 best fitness: 0 - program: input + (4 - -4) + input * input + (input + input)*
在这个输出中,我们看到了遗传编程系统经过许多代的结果。在每一步,我们看到最佳的适应度(或误差值)和最佳的解决方案。我们从误差值 252 开始,告诉我们最佳起始解离“正确”解有多远。
我们还看到了产生最佳输出的程序。请注意,尽管这些表达式看起来简单而普通,但它们是完全有效的灵丹妙药,只要我们设置了预期的绑定(在本例中为value)。
在 12 代的过程中(当然,从 0 开始),这些误差值稳步下降,直到误差值为 0——完全匹配!我们看到了输出,一个有效的长生不老药程序:
*input + (4 - -4) + input * input + (input + input)# which simplifies to...
input + 8 + input*input + input + input
input*input + 3*input + 8*
你自己试试
在这一点上,你可能想自己尝试一下这个系统。你可以通过在 GitHub 上试用 QuoteGP,并查看samples目录中的例子。
当你玩它的时候,你可能会注意到一些事情——这些都是使用遗传编程(以及更普遍的人工智能搜索技术)的真实世界挑战。
适应性有时会上升而不是下降
因为通用运算符是随机应用的,所以有时“最佳”候选项不会延续到下一代。围绕选拔过程有大量的研究,但没有正确的答案。虽然总是倾向于将最好的程序一代一代地复制下来似乎很直观,但这是一个悬而未决的问题——一些研究表明,随着时间的推移,这种方法实际上会产生更差的结果。(注意:当我现在研究这个声明时,我看到了更多支持这种方法的最新证据——也许我应该把它添加到 QuoteGP 中!)
有时问题没有得到完全解决
是的,这是常有的事。
基因编程实际上只是一种特殊的搜索技术,并不能保证一定会找到解决方案。事实上,大多数个体真实世界的基因编程运行不会找到一个问题的完美解决方案,因为你是在大海捞针。需要多次尝试才能找到正确的解决方案,这种情况并不少见。
此外,在这些示例问题中,我们使用 0 的停止标准,换句话说,一个完全完美的解决方案。这在现实世界中并不典型——对于许多问题来说,通常并不是完全完美的解决方案。相反,找到具有微小误差值的东西是一个有效且有用的现实世界解决方案。
节目越来越长了!
“膨胀”是基因编程中的另一个常见问题,也有一个研究机构致力于解决这个问题。简而言之,膨胀是程序长度随着时间的推移而增加,但由于“死”或残留代码而没有增加适应性。在这个简单的系统中,我们没有采用任何膨胀控制技术,所以这并不奇怪。
结论和未来工作
那么,考虑到可进化代码与 Elixir 的完美契合,它是遗传编程的最佳系统和语言吗?老实说:不。大多数 GP 系统不使用正常的人类编程语言,因为没有必要。他们倾向于使用一些其他的内部表示,只有在需要的时候才被翻译成人类可读的表达式。它们甚至可能包含语言特征,这将使它们对人类编程来说可怕,但对进化却非常有用。
此外,大多数 GP 系统远比这个简单的例子复杂。更多阅读请见系统列表。我个人很喜欢(并且已经开发过)PushGP 系统,但是还有很多其他的系统。
然而,使用 Elixir 进行遗传编程的好处在于,它非常容易实现、检查和理解,这使它成为修补遗传编程、理解其工作原理和尝试新想法的优秀教学工具。
如果你想自己尝试代码,请前往 GitHub 。
Jonathan在大型创业公司&小型企业中拥有超过 20 年的工程领导经验。如果你喜欢这篇文章,请考虑加入 Medium 来支持* 乔纳森和其他成千上万的作者 。*
使用 XlsxWriter 生成用户友好报表的 Excel 自动化工具
通过编写任何 excel 用户都可以阅读的显式 excel 公式,使用 python 自动创建 Excel 报表

问题陈述 使用 python 和 pandas 编写的 excel 自动化脚本可能面临的主要问题是没有编程技能的用户的体验。
事实上,您的工具可以被看作是一个黑盒,它从 excel 文件中获取数据,在后端进行处理,然后导出到另一个 excel 文件中。
一些用户不能信任报告,如果他们不能访问和修改用于填充结果的公式。
因此,用 python 替换 excel 会影响用户对解决方案的接受度。
目标 在本文中,我提出了一种替代方案,用 python 库 xlswriter 来克服这个问题。
💌新文章直接免费放入你的收件箱:时事通讯
如何用 Python 构建 Excel 自动化工具?
情况
你是一家时尚零售公司的数据分析师,负责销售报告。
报告由管理商店销售点的系统生成。
在这些报告中,您有
- 单件销售数量
- 共有 50 个项目的项目代码
- 覆盖全年的日期和星期
工作
出于报告目的,您需要处理这些报告,并将数据集与将用于分析销售趋势的附加功能进行映射。
这些附加特性与物料代码相关联
- 项目系列:皮具、配饰、成衣或其他
- 范围:布尔值,通知项目是否在分析范围内
目标
由于您每个月需要执行这项任务超过 100 次,因此您正在寻找一个自动化流程的解决方案。
解决方法
使用 Python 熊猫的初步解决方案
最初,您使用 Python 构建了一个解决方案,该解决方案使用 pandas 实现了自动化处理。
使用 pandas_read excel,从几个 excel 文件导入数据,由您的脚本处理并导出到另一个 excel 文件。

初始解决方案—(图片由作者提供)
此脚本已转换为可执行文件(。这样您的同事就可以在没有您支持的情况下使用它了
- 设计并测试您的 python 脚本
- 使用 pyinstaller 将 python 脚本导出到可执行文件中
- 分享你的。exe 文件(有详细说明)与你的同事
有关如何构建该解决方案的更多信息,
https://www.samirsaci.com/build-excel-automation-tools-with-python/
用户接受度的问题
一些用户抱怨说他们无法访问用于处理数据的公式。
你好 Samir,为什么我们看不到最终报告中的公式?你确定你选对了列吗?
这个问题引起了您的同事的一些担忧,他们质疑该工具的准确性,因为他们无法检查它。
使用 xlsxwriter 的新解决方案
想法是使用 python 库 xlsxwriter 来执行 Excel 文件中的计算。
您可以在 excel 单元格中编写公式,输出文件的用户可以阅读(和修改)这些公式。

xlsxwriter 生成的公式示例—(图片由作者提供)
因此,您保留了 python 的自动化功能,同时为只熟悉 Excel 的用户提供了更多的可见性。
履行
如果你看一下 xlsxwriter 的文档,你会发现几种在 excel 单元格上创建公式的方法。
您可以使用 Pip 安装这个库
pip install xlsxwriter
您可以编写应用于单个单元格的公式,
并将你的公式应用于一个数组,

最终结果—(图片由作者提供)
然后,您可以为我们的简单处理任务构建一个解决方案

最终结果—(图片由作者提供)
另外三列是使用 Excel 公式生成的,每个单元格都可以读取这些公式。
结论
关注我的 medium,了解更多与供应链数据科学相关的见解。
我们满足了最终用户对可见性的需求,同时保持了 python 的自动化功能。
低处理速度
但是,由于库的结构和在 excel 文件中创建公式所需的计算能力,您将会损失处理速度。
有限的功能
除了 excel 公式的限制,您还需要处理 xlsxwriter 的有限功能。
例如,您不能用它来构建数据透视表。
对于高级计算和处理,您需要教育您的用户,并使用另一种方式带来透明度,以获得他们的信任。
关于我
让我们连接上 Linkedin 和 Twitter ,我是一名供应链工程师,正在使用数据分析来改善物流运营和降低成本。
如果你对数据分析和供应链感兴趣,可以看看我的网站
Excel 用户,以下是如何让您的数据分析和报告更上一层楼
这里有 3 个比 Excel、VBA 和 Power Query 更好的工具

图片来自 Shutterstock,授权给 Frank Andrade
如果你是一个高级 Excel 用户,你可能已经使用 VBA 创建宏和自动化重复的任务,或使用电源查询清理和转换数据。
它们是很好的工具,但是当涉及到创建自定义自动化和快速处理大量数据时,Microsoft Excel 是不够的。
这就是你需要像 Python 这样的编程语言的时候。
Python 有数百个库,使得数据操作、分析和报告自动化成为可能。Python 不仅能帮助你完成 Excel 任务,还能帮助你完成更复杂的任务。
在本文中,我将向 Excel 用户展示从头开始使用 Python 的最佳方式,并向您介绍 3 个 Python 库,它们可以帮助您将数据分析和 Excel 报表提升到一个新的水平。
1.学习 Python 和 Pandas,以便更好地进行数据分析
Python 是一种流行的语言,有很多应用。其中一个应用是数据分析。我们可以用 Python 做数据分析,使用一个叫做 Pandas 的库。
Pandas 库被称为“类固醇上的 Excel ”,因为你可以在 Microsoft Excel 中完成大多数任务,但要借助 Python 的强大功能。这意味着我们可以清理和争论大型数据集,并生成数据透视表和图表,而不会出现性能问题。不同之处在于,在 Pandas 中,我们使用数据框架,而在 Excel 中,我们使用工作表。
以下是 Python/Pandas 相对于 Excel 的一些优势:
- Excel 可以处理一百万行。Python 可以处理数百万行数据
- Python 可以处理复杂的计算,但这可能会导致 Excel 中的工作簿崩溃
- Excel 中的自动化仅限于 VBA 和 Power Query,而 Python 中的可能性是无限的,这要归功于它的数百个免费库
如果你是一个从未写过一行代码的 Excel 用户,过渡到 Python 的想法可能听起来很可怕,但是有免费的教程可以教你从零开始学习 Python,同时考虑到你在 Excel 中的知识。
这是 freeCodeCamp 频道为 Excel 用户提供的免费 Python 课程。
在本课程中,您将在模块 1 中学习基本的 Python 知识,如数据类型、变量和字典,然后在模块 2 中学习如何使用 Pandas 库处理数据。一旦您了解 Pandas,您将在模块 3 中学习如何创建数据透视表和数据可视化。
本课程使用 Jupyter Notebooks 教授,这是一种广泛用于 Python 数据分析的文本编辑器。
2.米托:如果你能编辑 Excel 文件,你现在就能编写 Python 代码了
如果你想拥有 Python 的强大功能和 Microsoft Excel 的简单性,你应该试试米托图书馆。
米托是 Python 中的一个电子表格。这个库帮助你使用 Pandas 数据框架,就像它是一个 Excel 工作簿一样。这意味着我们不再需要编写 Python 代码来操作数据帧,而是可以使用 Python 通过几次点击来进行数据分析。
为了让事情变得更好,米托会在你对米托的电子表格进行每一次修改后为你生成 Python 代码。
假设您想读取一个 CSV 文件,然后制作一个箱线图。对于 Pandas,您需要使用.read_csv方法读取一个 CSV 文件,然后使用.iplot(kind='box')方法创建一个箱线图。
然而,有了米托,你不用写代码就可以完成所有这些,但是,首先,你需要安装它,让 pip 运行下面的命令(在 Jupyter Notebook 和 JupyterLab 上,你需要在一个单元中运行命令)。
python -m pip install mitoinstaller
python -m mitoinstaller install
现在我们可以用下面的代码创建一个 mitosheet。这就是你需要写的所有代码!
**import** mitosheet
mitosheet.sheet()
如果您看到如下所示的表单,那么一切都设置成功了。现在,我们只需点击几下鼠标就可以完成很多任务。对于这个例子,我将使用 Google Drive 上的“StudentsPerformance_id.csv”文件。
我们可以用米托导入这个 CSV 文件。你只需要点击“导入”按钮,选择你之前下载的 CSV 文件。

作者图片
现在,我们可以通过点击“图形”按钮并在“图表类型”选项中选择“方框”来创建一个方框图(和其他图形),如下图 gif 所示。

作者图片
太好了!要获取用于图形的代码,请单击“复制图形代码”按钮。关于这个库的更多信息,请查看米托的 Github 和文档。
3.使用 Python 和 Openpyxl 实现 Excel 自动化
除了数据分析,Python 还经常用于自动化任务。有了 Python,你可以自动化网络,发送电子邮件、文件、文件夹,应有尽有!
说到用 Python 自动化 Excel,前途无量!
您可以使用 Python、Pandas 库和 OS 模块来完成简单的自动化操作,如连接多个 Excel 文件、更改多个文件的名称/扩展名,甚至修改文件内部的数据(比如您想要添加/删除一些字符)。
在下面的指南中,我分享了用 Python 做这件事的代码。
</5-common-excel-tasks-simplified-with-python-feff966e73a4>
但这还不是全部!您可以使用一个名为 openpyxl 的库来实现更复杂的自动化,比如生成 Excel 报表。这个库允许我们加载工作簿并操作它们,就像我们使用 Microsoft Excel 一样(但是使用了 Python 的强大功能)。
使用 Python,我们可以像双击文件一样简单地创建 Excel 报表。
不相信我?假设您需要创建一个数据透视表,然后向工作表添加公式、图表和标题/副标题。你可以使用 Python 来完成所有这些。
首先,您可以用 Pandas 库创建数据透视表,然后用 openpyxl 添加公式、图表和文本。如果您想更进一步,您可以使用一个名为 pyinstaller 的库,它可以将 Python 脚本转换成可执行文件,这样每当您双击可执行文件时,报告就会自动生成。
如果这听起来好得令人难以置信,请查看我的分步指南来学习如何用 Python、openpyxl 和 pandas 自动化 Excel 报表。
自动化你的生活! 加入我的 10k+人电子邮件列表,获取我的免费自动化小抄。
如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。每月 5 美元,让您可以无限制地访问数以千计的 Python 指南和数据科学文章。如果你使用我的链接注册,我会赚一小笔佣金,不需要你额外付费。
https://frank-andrade.medium.com/membership
C++中的异常处理
原文:https://towardsdatascience.com/exception-handling-in-c-eb2a2f55a2d9
C++中的错误处理介绍

在计算机编程中,异常处理是处理程序中错误的过程,这样它就能在异常情况下运行。此外,它清楚地向用户解释了程序失败的原因。在许多情况下,软件程序会得到坏数据或其他影响代码成功运行的异常情况。
因此,异常处理是软件开发的关键,因为可靠地处理错误可以防止软件程序表现出非预期的行为。理想情况下,很好地实现错误处理可以保证代码在任何条件下都能成功运行。
意外行为的例子包括程序运行缓慢或在接收到错误输入时失败。在错误输入的情况下,考虑一个函数,该函数将一个实数值(float)作为用户输入,执行计算,并返回结果值。如果该函数期望浮点值,但却收到字符串,则该函数将引发错误并失败。错误异常处理可以用来处理这样的情况。如果用户输入一个错误的值,比如字符串,程序会返回一个有意义的消息,而不是失败或提供一个不清楚的系统错误信息。
在 C++ 中,异常处理使用了表达式 Try、Throw 和 Catch。Try 表达式标识可能有错误异常的代码块。它可能包含逻辑,例如将两个数字相除或在数字列表上迭代。Throw 表达式处理异常。例如,当您的代码试图将两个数相除,但分母为零时,您可以抛出一条错误消息,如“不能被零除”最后,Catch 表达式处理错误。这些表达式可以处理 C++中的各种异常,包括数组和向量的长度错误。
在这里,我们将通过一些简单的例子来说明如何在 C++中实现异常处理。首先,我们将考虑一个运行时错误的例子。我们将定义一个以身高和体重为输入并计算体重指数的函数。身体质量指数的公式是体重除以身高的平方。如果零值作为参数传递给函数,代码将尝试除以零。我们将使用 try、catch 和 throw 来处理运行时错误。最后,我们将通过一个额外的长度错误异常处理的例子。这两个例子应该为刚刚开始使用 C++进行异常处理的开发人员提供了一个很好的起点。
运行时异常处理
为了演示运行时错误的异常处理,我们将构建一个简单的身体质量指数计算器应用程序。该应用程序将用户的姓名,体重和身高作为输入,并显示计算出的身体质量指数。首先,让我们看一下如何在没有任何异常处理的情况下构建应用程序。
首先,让我们写一个脚本,我们称之为 bmi.cpp。我们将包括
#include <iostream>
#include <string>using namespace std;
编译和执行时,程序会询问用户的名字。为此,我们需要为 name 定义一个字符串变量,为 weight 和 height 分别定义两个浮点变量:
// Main() function: where the execution of program beginsint main(){string name;
float weight;
float height;}
接下来,让我们添加询问用户名的逻辑:
// Main() function: where the execution of program beginsint main(){string name;
float weight;
float height;cout << "Please Enter your Name \n";
cin >> name;}
为了编译我们的代码,我们在终端中运行以下命令:
g++ bmi.cpp
我们使用以下命令执行:
./a.out
我们得到以下输出:

作者图片
现在,让我们编写一个逻辑,在用户输入姓名时问候用户并询问他们的体重:
// Main() function: where the execution of program beginsint main(){string name;
float weight;
float height;cout << "Please Enter your Name \n";
cin >> name;
cout << "Hello "<< name << ", please enter your weight in Kg \n";}
如果我们编译并执行,我们会得到以下结果:

作者图片
然后,我们可以添加权重逻辑作为用户输入:
int main(){string name;
float weight;
float height;cout << "Please Enter your Name \n";
cin >> name;
cout << "Hello "<< name << ", please enter your weight in Kg \n";
cin >> weight;}
将提示用户输入重量值。现在,让我们包含逻辑,以便在提供以 kg 为单位的体重时,我们要求用户提供以米为单位的身高:
int main(){string name;
float weight;
float height;cout << "Please Enter your Name \n";
cin >> name;
cout << "Hello "<< name << ", please enter your weight in Kg \n";
cin >> weight;
cout << "Thank you "<< name << ", now please enter your height in meters \n";
cin >> height;}

作者图片
现在我们可以用这些数据来计算身体质量指数。身体质量指数的公式是体重/身高 2。我输入我的名字 Sadrach,我的体重 90 公斤,我的身高 1.92 米:
int main(){string name;
float weight;
float height;cout << "Please Enter your Name \n";
cin >> name;
cout << "Hello "<< name << ", please enter your weight in Kg \n";
cin >> weight;
cout << "Thank you "<< name << ", now please enter your height in meters \n";
cin >> height;
bmi = weight/(height*height);
cout << "Your BMI is: "<< bmi <<"\n";}
身高 1.92 米,体重 90 公斤,我们可以计算出身体质量指数为 24.4:

作者图片
现在,让我们尝试为高度传递一个零值:

作者图片
我们看到,在输入零的高度,我们有一个“inf”的身体质量指数计算,“INF”意味着无限。这对于用户来说显然不是一个有用的值,可能只会引起混乱。我们应该尝试捕捉这个错误并向用户显示一条消息,告诉用户他们提供了一个无效的高度值,而不是显示 infinity。我们可以通过捕获运行时错误来实现这一点:
首先,在脚本的顶部,我们需要导入' stdexcept '来使用 runtime_error:
#include<stdexcept>
接下来,我们可以在函数中重写身体质量指数计算的逻辑:
float BMI_calculator(float weight, float height){if (height == 0){throw runtime_error("You attempted to calculate BMI with an invalid value of zero for height \n");}return weight/(height*height);}
然后我们可以修改我们的主函数来尝试计算身体质量指数:
try{bmi = BMI_calculator(weight, height);
cout << "Your BMI is: "<< bmi <<"\n";}
并在运行时错误发生时捕获它。如果出现这种情况,我们将显示文本“警告:您试图用无效的零值来计算身体质量指数的高度”
catch (runtime_error&e){cout<< "Warning: "<< e.what();}
然后,我们编译并执行代码,将零作为高度的输入。我们得到以下显示消息:

作者图片
我们看到这条消息比原来的显示消息“您的身体质量指数是:inf”更具描述性和有用
长度错误异常处理
另一个常见的异常是向量的长度错误异常。为了演示这种错误的发生,我们将定义一个函数来计算退休金账户的每月缴款总额。首先,让我们定义一个名为 retirement_contributions.cpp 的 C++脚本。
让我们添加我们的主函数以及必要的
#include <iostream>
#include <stdexcept> // std::out_of_range
#include <vector>using namespace std;int main(){}
让我们添加逻辑,以便用户可以指定他们想要计算总退休金的月数:
#include <iostream>
#include <stdexcept> // std::out_of_range
#include <vector>using namespace std;int main(){int months;
cout << "Please enter the number of months \n";
cin >> months;}
接下来,让我们定义一个向量,它是月数的大小。向量将包含美元金额贡献,因此它将是一个浮动向量:
int main(){int months;cout << "Please enter the number of months \n";
cin >> months;
std::vector<float> contributions(months);}
让我们初始化一个名为 current month 的变量,它将是我们用来迭代数组的索引:
int current_month = 1;
然后,让我们将数组的第一个元素定义为我们的初始贡献:
contributions[1] = initial_contrbution;
在 while 循环中,当当前月份少于总月份数时,我们将后续贡献增加 2 %:
while (current_month <= months){contributions[current_month + 1] =1.02*contributions[current_month];}
我们还可以显示每个月的贡献,同时将我们的指数递增 1:
while (current_month <= months){contributions[current_month + 1] =1.02*contributions[current_month];cout << "Month "<< current_month << "contribution is: "<< contributions[current_month]<< endl;current_month++;}
所以,完整的脚本如下:
int main(){int months;int current_month = 1;cout << "Please enter the number of months \n";
cin >> months;std::vector<float> contributions(months);
float initial_contrbution = 100;
contributions[1] = initial_contrbution;while (current_month <= months){contributions[current_month + 1] =1.02*contributions[current_month];cout << "Month "<< current_month << "contribution is: "<< contributions[current_month]<< endl;current_month++;}}
如果我们使用五个月的输入值编译并执行这个脚本,我们将得到以下输出:

作者图片
从这里,我们可以计算出贡献的总和。让我们初始化一个浮点变量,我们将使用它来存储总和,并在 while 循环中计算总和:
int main(){int months;
int current_month = 1;cout << "Please enter the number of months \n";
cin >> months;std::vector<float> contributions(months);
float initial_contrbution = 100;
float sum_contributions = 0;
contributions[1] = initial_contrbution;while (current_month <= months){contributions[current_month + 1] =1.02*contributions[current_month];cout << "Month "<< current_month << "contribution is: "<< contributions[current_month]<< endl;sum_contributions += contributions[current_month];current_month++;}cout<<"Sum of contributions for "<< months << "months is: "<<sum_contributions << endl;}

作者图片
现在让我们考虑一个特例。假设用户不小心输入了一个负值,比如负五,持续了几个月。我们得到以下 std::length_error:

作者图片
我们看到我们的代码失败了,并显示了一条与我们的向量 std::length_error 的长度相关的神秘消息。这是因为我们可以定义一个长度为负五的向量。我们可以使用异常处理来解决这样的情况:
try{std::vector<float> contributions(months); //float contributions[months];float initial_contrbution = 100;
float sum_contributions = 0;
contributions[1] = initial_contrbution;while (current_month <= months){contributions[current_month + 1] =1.02*contributions[current_month];cout << "Month "<< current_month << "contribution is: "<< contributions[current_month]<< endl;sum_contributions += contributions[current_month];current_month++;}cout<<"Sum of contributions for "<< months << "months is: "<<sum_contributions << endl;catch (const std::length_error& le) {std::cerr << "Length of "<< le.what() << "can’t be negative \n";}
所以,完整的脚本如下:
int main(){int months;
int current_month = 1;cout << "Please enter the number of months \n";
cin >> months;try{std::vector<float> contributions(months); //float contributions[months];float initial_contrbution = 100;
float sum_contributions = 0;
contributions[1] = initial_contrbution;while (current_month <= months){contributions[current_month + 1] =1.02*contributions[current_month];
cout << "Month "<< current_month << "contribution is: "<< contributions[current_month]<< endl;
sum_contributions += contributions[current_month];
current_month++;}cout<<"Sum of contributions for "<< months << "months is: "<<sum_contributions << endl;catch (const std::length_error& le) {std::cerr << "Length of "<< le.what() << "can’t be negative \n";}}}
现在,如果我们连续几个月编译、执行并传递一个负值,我们会适当地处理错误:

作者图片
我们看到,通过适当地处理长度错误,我们能够向用户显示一条描述性的消息。
这篇博客中使用的脚本可以在 GitHub 上获得。
结论
异常处理是软件编程中非常重要的一部分。它允许开发人员处理代码的意外行为、异常输入、意外运行时等等。
这通常是防止代码无法成功运行的有效措施。此外,异常情况通常会导致难以理解的系统生成的错误。不透明的、系统生成的错误消息会让用户、开发人员和工程师感到沮丧。如果用户无意中提供了一个错误的输入值,最好采取措施来处理这些类型的值,并向用户提供一条有意义的消息,说明错误发生的原因。理解异常处理对于软件开发人员、软件工程师甚至机器学习工程师和数据科学家来说都很重要。
如果你有兴趣学习 python 编程的基础知识、Pandas 的数据操作以及 python 中的机器学习,请查看Python for Data Science and Machine Learning:Python 编程、Pandas 和 sci kit-初学者学习教程 。我希望你觉得这篇文章有用/有趣。
本帖原载于 内置博客 。原片可以在这里找到https://builtin.com/software-engineering-perspectives/how-to-write-clean-exception-handling-code-c%2B%2B。
Python 中多处理池类的方法中的异常处理
使用 map、imap 和 imap_unordered 方法

由 Marek Piwnicki 在 Unsplash 拍摄的照片
介绍
处理大数据时,通常需要并行计算。在 python 中,标准的 多处理 模块通常用于需要大量计算资源的任务。在 DS 中,我们必须不断地解决容易并行化的问题。示例可以是引导、多重预测(多个示例的模型预测)、数据预处理等。
在本文中,我想谈谈在 python 中使用多重处理[Pool](https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing.pool)类时需要考虑的一些有趣而重要的事情:
- Pool 类的方法中的异常处理
- python 中悬挂函数的处理
- 进程使用的内存限制(仅适用于 Unix 系统)
我将在 OS Ubuntu 20.04 上使用 3.9 版本的 Python。
所以让我们开始吧!
Pool 类的方法中的异常处理
在我的实践中,我经常不得不使用多重处理模块。把电脑的所有能力都用上,把处理器的汁液都挤出来,感觉很不错吧?让我们想象一下,你写了非常复杂的代码,你的计算量非常大,以至于你决定在晚上运行它们,希望醒来后能看到你工作的精彩结果。所以,这就是我们美丽的函数(假设我们忘记了不可能除以一个零,有谁没发生呢?)
早上你会看到什么?我想你会非常沮丧,因为很明显,你会看到下面的追溯:
multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
File "/usr/lib/python3.9/multiprocessing/pool.py", line 125, in worker
result = (True, func(*args, **kwds))
File "/usr/lib/python3.9/multiprocessing/pool.py", line 48, in mapstar
return list(map(*args))
File "/home/PycharmProjects/myproject/main.py", line 9, in my_awesome_foo
1 / 0
ZeroDivisionError: division by zero
"""The above exception was the direct cause of the following exception:Traceback (most recent call last):
File "/home/PycharmProjects/myproject/main.py", line 19, in <module>
result = p.map(my_awesome_foo, tasks)
File "/usr/lib/python3.9/multiprocessing/pool.py", line 364, in map
return self._map_async(func, iterable, mapstar, chunksize).get()
File "/usr/lib/python3.9/multiprocessing/pool.py", line 771, in get
raise self._value
ZeroDivisionError: division by zero
有人会说,这不奇怪,这应该发生,而且绝对正确。但是让我们稍微修改一下我们的代码,试着更详细地了解当一个进程中发生异常时,池内部发生了什么。我们将在我们的功能中添加打印消息的功能,该过程已经开始,我将完成这项工作。使用多重处理模块的函数[current_procces().name](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.current_process)可以获得进程名称。
Process ForkPoolWorker-1 started working on task 0
Process ForkPoolWorker-2 started working on task 1
Process ForkPoolWorker-4 started working on task 3
Process ForkPoolWorker-3 started working on task 2
Process ForkPoolWorker-1 started working on task 4
Process ForkPoolWorker-2 ended working on task 1
Process ForkPoolWorker-2 started working on task 5
Process ForkPoolWorker-4 ended working on task 3
Process ForkPoolWorker-4 started working on task 6
Process ForkPoolWorker-2 ended working on task 5
Process ForkPoolWorker-2 started working on task 7
Process ForkPoolWorker-1 ended working on task 4
Process ForkPoolWorker-1 started working on task 8
Process ForkPoolWorker-4 ended working on task 6
Process ForkPoolWorker-4 started working on task 9
Process ForkPoolWorker-3 ended working on task 2
Process ForkPoolWorker-1 ended working on task 8
Process ForkPoolWorker-4 ended working on task 9
Process ForkPoolWorker-2 ended working on task 7
multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
File "/usr/lib/python3.9/multiprocessing/pool.py", line 125, in worker
result = (True, func(*args, **kwds))
File "/usr/lib/python3.9/multiprocessing/pool.py", line 48, in mapstar
return list(map(*args))
File "/home/PycharmProjects/myproject/main.py", line 9, in my_awesome_foo
1 / 0
ZeroDivisionError: division by zero
"""The above exception was the direct cause of the following exception:Traceback (most recent call last):
File "/home/PycharmProjects/myproject/main.py", line 19, in <module>
result = p.map(my_awesome_foo, tasks)
File "/usr/lib/python3.9/multiprocessing/pool.py", line 364, in map
return self._map_async(func, iterable, mapstar, chunksize).get()
File "/usr/lib/python3.9/multiprocessing/pool.py", line 771, in get
raise self._value
ZeroDivisionError: division by zeroProcess finished with exit code 1
嘭!
所以我们的函数在第一次迭代时捕捉到了一个异常,但是我们看到了什么呢?我们看到所有的过程都在我们看到出错之前开始并成功完成了它们的工作。事实上,这意味着你的程序真的会通宵工作,但是最后,它仍然以一个错误结束,你不会得到任何结果。很遗憾,不是吗?
这个例子清楚地显示了在使用 Pool 类的 map 方法时处理异常是多么重要。那么imap和imap_unordered方法呢?这里我们看到更多可预测的行为:
Process ForkPoolWorker-1 started working on task 0
Process ForkPoolWorker-3 started working on task 2
Process ForkPoolWorker-2 started working on task 1
Process ForkPoolWorker-4 started working on task 3
Process ForkPoolWorker-1 started working on task 4
multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
File "/usr/lib/python3.9/multiprocessing/pool.py", line 125, in worker
result = (True, func(*args, **kwds))
File "/home/PycharmProjects/myproject/main.py", line 8, in my_awesome_foo
1 / 0
ZeroDivisionError: division by zero
"""The above exception was the direct cause of the following exception:Traceback (most recent call last):
File "/home/PycharmProjects/myproject/main.py", line 21, in <module>
result = list(p.imap(my_awesome_foo, tasks))
File "/usr/lib/python3.9/multiprocessing/pool.py", line 870, in next
raise value
ZeroDivisionError: division by zeroProcess finished with exit code 1
不幸的是,正确处理map方法中出现的异常超出了本文的范围。有像 pebble 这样的库可以让你这么做。
下面是imap方法的一个异常处理选项的例子(也适用于imap_unordered)
Process ForkPoolWorker-1 started working on task 0
Process ForkPoolWorker-2 started working on task 1
Process ForkPoolWorker-4 started working on task 3
Process ForkPoolWorker-3 started working on task 2
Process ForkPoolWorker-1 started working on task 4
Process ForkPoolWorker-4 ended working on task 3
Process ForkPoolWorker-4 started working on task 5
Process ForkPoolWorker-2 ended working on task 1
Process ForkPoolWorker-3 ended working on task 2
Process ForkPoolWorker-2 started working on task 6
Process ForkPoolWorker-3 started working on task 7
Process ForkPoolWorker-1 ended working on task 4
Process ForkPoolWorker-1 started working on task 8
Process ForkPoolWorker-4 ended working on task 5
Process ForkPoolWorker-4 started working on task 9
Process ForkPoolWorker-2 ended working on task 6
Process ForkPoolWorker-3 ended working on task 7
Process ForkPoolWorker-1 ended working on task 8
Process ForkPoolWorker-4 ended working on task 9
time took: 3.0
[ZeroDivisionError('division by zero'), 1, 2, 3, 4, 5, 6, 7, 8, 9]Process finished with exit code 0
然后,您可以打印出完整的回溯,看看哪里出错了:
Traceback (most recent call last):
File "/usr/lib/python3.9/multiprocessing/pool.py", line 125, in worker
result = (True, func(*args, **kwds))
File "/home/PycharmProjects/myproject/main.py", line 9, in my_awesome_foo
1 / 0
ZeroDivisionError: division by zero
"""The above exception was the direct cause of the following exception:Traceback (most recent call last):
File "/home/PycharmProjects/myproject/main.py", line 23, in <module>
result.append(next(iterator))
File "/usr/lib/python3.9/multiprocessing/pool.py", line 870, in next
raise value
ZeroDivisionError: division by zero
因此,我们成功地捕获了异常,我们的池完成了它的工作,并给了我们结果。此外,我们可以打印整个异常堆栈,并查看代码中发生错误的地方。
Python 中悬挂函数的处理
让我们改变我们美丽的功能:
对于 n=0 ,我们的函数休眠 5 秒,对于所有其他 n ,休眠 1 秒。现在想象一下,举例来说,不是 5 秒,而是 5 小时。或者更糟,对于一些输入数据,你的函数陷入了一个无限循环。我们不想永远等下去,不是吗?那么在这种情况下该怎么办呢?下面是针对imap方法的 python 文档摘录:
同样,如果 chunksize 为
1,那么[imap()](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.Pool.imap)方法返回的迭代器的next()方法有一个可选的超时参数:如果在超时秒内不能返回结果,*next(timeout)*将引发[multiprocessing.TimeoutError](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.TimeoutError)。
因此,让我们尝试使用文档中描述的带有超时参数的迭代器next()方法。在前一章中,我们学习了如何处理错误,理论上,我们应该正确处理 TimeoutError :
这次我们应该看什么?
Process ForkPoolWorker-1 started working on task 0
Process ForkPoolWorker-2 started working on task 1
Process ForkPoolWorker-3 started working on task 2
Process ForkPoolWorker-4 started working on task 3
Process ForkPoolWorker-2 ended working on task 1
Process ForkPoolWorker-3 ended working on task 2
Process ForkPoolWorker-4 ended working on task 3
Process ForkPoolWorker-2 started working on task 4
Process ForkPoolWorker-3 started working on task 5
Process ForkPoolWorker-4 started working on task 6
Process ForkPoolWorker-2 ended working on task 4
Process ForkPoolWorker-3 ended working on task 5
Process ForkPoolWorker-2 started working on task 7Process ForkPoolWorker-4 ended working on task 6
Process ForkPoolWorker-3 started working on task 8
Process ForkPoolWorker-4 started working on task 9
Process ForkPoolWorker-2 ended working on task 7
Process ForkPoolWorker-4 ended working on task 9
Process ForkPoolWorker-3 ended working on task 8
Process ForkPoolWorker-1 ended working on task 0
time took: 6.0
[TimeoutError(), TimeoutError(), TimeoutError(), TimeoutError(), 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]Process finished with exit code 0
双啵!
我们捕获了超时错误异常 4 次并处理了它,而函数在 n=0 时仍然工作。也就是说,ForkPoolWorker-1进程本身并没有停止等待 5 秒,每隔 1.5 秒就会出现一个异常,我们拦截了这个异常。然后ForkPoolWorker-1进程成功完成其工作并返回值 0 。这根本不是我们想要的,是吗?
这种情况下我们该怎么办?超时过期后如何强行终止进程?
让我们考虑一下如何中断函数的执行。可能很多,我知道这甚至可以从键盘上使用键盘快捷键 Ctr+C 来完成。如何在 python 中强制中断呢?我们需要向我们的进程发送一个中断信号。让我们看看os模块的kill功能的文档:
[os.**kill**](https://docs.python.org/3/library/os.html#os.kill)(pid,sig)向进程 pid 发送信号 sig 。主机平台上可用的特定信号的常数在
[signal](https://docs.python.org/3/library/signal.html#module-signal)模块中定义。
查阅 信号 模块的文档,可以看到[SIGINT](https://docs.python.org/3/library/signal.html#signal.SIGINT)负责从键盘中断(默认动作是抬起[KeyboardInterrupt](https://docs.python.org/3/library/exceptions.html#KeyboardInterrupt)
注意:这种方法只适用于 Unix 系统。稍后我将描述如何在窗口中完成这项工作。
类
[threading.**Timer**](https://docs.python.org/3/library/threading.html#threading.Timer)(区间,函数,args=None,kwargs=None)创建一个计时器,在经过间隔秒后,该计时器将运行函数,其参数为 args 和关键词参数 kwargs 。如果参数为
None(默认),那么将使用空列表。如果 kwargs 为None(默认值),那么将使用空字典。
很好,现在需要的是,我们将创建一个函数来模拟来自键盘的中断,并且我们将在一个等于超时的计时器上运行这个函数。如果它没有来,我们将简单地取消计时器。让我们以装饰器的形式实现我们的想法:
让我们看看它是如何为我们的功能工作的:
Process MainProcess started working on task 0
function my_awesome_foo took longer than 1.5 s.
time took: 1.5Process finished with exit code 0
一切都如我们所愿!对于基于 Windows 的系统,可以用[_thread.interrupt_main()](https://docs.python.org/3/library/_thread.html#thread.interrupt_main)代替os.kill()。我在 Windows 11 上进行了测试,一切正常。让我们看看我们的修饰函数如何与 Pool 类的imap方法一起工作:
Process ForkPoolWorker-2 started working on task 1
Process ForkPoolWorker-1 started working on task 0
Process ForkPoolWorker-4 started working on task 3
Process ForkPoolWorker-3 started working on task 2
Process ForkPoolWorker-2 ended working on task 1
Process ForkPoolWorker-4 ended working on task 3
Process ForkPoolWorker-3 ended working on task 2
Process ForkPoolWorker-4 started working on task 4
Process ForkPoolWorker-2 started working on task 6
Process ForkPoolWorker-3 started working on task 5
Process ForkPoolWorker-1 started working on task 7
Process ForkPoolWorker-3 ended working on task 5
Process ForkPoolWorker-4 ended working on task 4
Process ForkPoolWorker-2 ended working on task 6
Process ForkPoolWorker-4 started working on task 9
Process ForkPoolWorker-3 started working on task 8
Process ForkPoolWorker-1 ended working on task 7
Process ForkPoolWorker-4 ended working on task 9
Process ForkPoolWorker-3 ended working on task 8time took: 3.0['function my_awesome_foo took longer than 1.5 s.', 1, 2, 3, 4, 5, 6, 7, 8, 9]Process finished with exit code 0
这就是我们想要的!
进程使用的内存限制(仅适用于 Unix 系统)
现在让我们设想一种情况,您想要限制一个进程可以使用的内存。这可以在 Unix 系统上使用 资源 模块轻松完成。
Process ForkPoolWorker-1 started working on task 0
Process ForkPoolWorker-2 started working on task 1
Process ForkPoolWorker-3 started working on task 2
Process ForkPoolWorker-4 started working on task 3
Process ForkPoolWorker-1 started working on task 4
Process ForkPoolWorker-4 ended working on task 3
Process ForkPoolWorker-2 ended working on task 1
Process ForkPoolWorker-3 ended working on task 2
Process ForkPoolWorker-2 started working on task 5
Process ForkPoolWorker-3 started working on task 6
Process ForkPoolWorker-4 started working on task 7
Process ForkPoolWorker-1 ended working on task 4
Process ForkPoolWorker-1 started working on task 8
Process ForkPoolWorker-2 ended working on task 5
Process ForkPoolWorker-3 ended working on task 6
Process ForkPoolWorker-4 ended working on task 7
Process ForkPoolWorker-2 started working on task 9
Process ForkPoolWorker-1 ended working on task 8
Process ForkPoolWorker-2 ended working on task 9time took: 3.0[MemoryError(), 1, 2, 3, 4, 5, 6, 7, 8, 9]Process finished with exit code 0
嗯,就像蛋糕上的樱桃一样,让我们把所有的例子收集到一个例子中,看看我们在这里讨论的所有事情是如何通过使用 parallelbar 库的一个命令来完成的:

作者图片
结果是:
time took: 8.2
[MemoryError(), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, TimeoutError('function my_awesome_foo took longer than 1.5 s.'), 21, 22, 23, 24, 25, 26, 27, 28, 29]Process finished with exit code 0
因此,多亏了进度条,我们能够估计到执行结束还剩下多少时间,他还向我们展示了被拦截的错误。
您可以在我的文章中了解更多关于 parallelbar 的信息:
或者您可以查看[文档](http://or you can check the documentation)
结论
- 在本文中,我们以多重处理模块的
Pool类为例,简要回顾了 python 中的多重处理。 - 我们已经看到了如何使用
imap函数在进程池中处理异常。 - 我们实现了一个装饰器,允许你在指定的超时后中断函数的执行
- 我们以限制使用的内存为例,研究了如何限制进程池中某个进程使用的资源
- 我们看了一个使用 parallelbar 库实现异常处理和限制进程使用资源的小例子
我希望这篇文章对你有用!
Python 中的异常处理
原文:https://towardsdatascience.com/exception-handling-in-python-8cc8f69f16ad
了解如何使用 Python Try Except

穆斯塔法·梅拉吉在 Unsplash 上的照片
Python Try Except 是一种在 Python 程序中处理所谓异常的方法,这样应用程序就不会崩溃。借助 Python Try Except,可以捕捉和处理异常。
语法错误和异常有什么区别?
我们可能都不得不痛苦地学习,一旦出现错误或异常, Python 就会终止一个正在运行的程序。这里必须做一个重要的区分。代码可能由于语法错误而停止,即代码只是被错误地编写并且不能被解释,或者由于异常而停止,即语法正确的代码组件在执行期间引起问题。
例如,错误地设置括号可能会导致语法错误:
另一方面,异常是在应用程序执行期间发生的错误,即使代码在语法上是正确的。例如,当试图将一个字符串和一个数字相加时,就会发生这种情况。代码本身写得很正确,但是这个操作的实现是不可能的:
Python 中的异常类型有哪些?
在 Python 中,有许多不同类型的异常可能在执行代码时发生。下面的列表描述了最常见的异常类型,但不是全部:
- AssertionError :当命令“assert”没有被正确使用或产生错误时,该异常发生。
- ImportError :如果导入模块有问题,会出现导入错误。例如,如果模块(如 Pandas )尚未安装,要从模块加载不正确或不存在的功能,或者指定名称的模块根本不存在,就会发生这种情况。
- IndexError :当使用 Python 有索引的数据对象时,比如 Python 元组或者 Python 列表,如果使用了在对象中找不到的索引,就会发生 IndexError。
- KeyError :类似于 IndexError,在使用 Python 字典时,如果在字典对象中找不到某个键,就会发生 KeyError。
- 内存错误:当机器内存不足以继续运行 Python 程序时,会出现内存错误。
- unboundlocalrerror:使用局部变量时,一旦引用了尚未定义的变量,即尚未赋值的变量,就会发生 unboundlocalrerror。
Python 除了工作如何尝试?
Python 的 Try Except 功能通过定义一个精确描述如何处理异常的例程,使得有针对性地处理可能的异常成为可能。如果出现异常的概率非常高,或者要不惜一切代价防止程序中断,这就特别有用。
为此使用了两个块:Try 和 Except。Try 块用于添加在执行过程中可能导致异常的代码。如果这个代码块运行没有问题,下面的 Except 块将被跳过,代码将在后面执行。但是,如果 Try 块中有异常,Except 块中的代码会自动执行。
在这种情况下,总是执行 a 和 b 的加法,除非为两个没有编号的变量传递值。那么实际上会有一个 ValueError,但这是由我们的 Python Try Except 循环捕获的。文本“a、b 或两者都不是数字”,而不是值 Error。请用不同的值重试。
然而,在 Python 的 Try Except 循环中,您不必指定要响应的特定异常,也可以将其定义为对任何异常执行 Except 块。此外,可以使用“finally”定义一个例程,以防程序没有出现异常。
在哪些应用中使用 Python Try 有意义,除了?
在数据科学中,在许多应用程序中使用 Python Try Except 循环来避免过早终止程序是有意义的:
- 准备大型数据集:如果你想为机器学习准备大型数据集,用作训练数据集,准备工作通常需要几个小时。根据数据集的质量,并非所有的数据类型都与给定的匹配。同时,你要避免程序中途停止而你没有注意到。为此,您可以使用 Python Try Except 循环简单地跳过数据质量不正确的单个记录,这将导致异常。
- 软件的日志记录:这里也需要 Python 的 Try Except 循环,因为生产应用程序应该继续工作。使用 Except 块,可以将错误输出到日志中,然后进行评估。在新版本中修复错误后,可以部署新状态,并确保应用程序的停机时间尽可能低。
这是你应该带走的东西
- Python Try Except 是一种处理 Python 程序中所谓异常的方法,这样应用程序就不会崩溃。
- Try 块包含了可能导致异常的行。另一方面,Except 块定义了在发生错误时应该执行的代码。如果没有异常发生,Python Try Except 循环之后的代码将被简单地执行。
- 此外,您可以使用“finally”命令来定义如果 Try 块中没有错误发生时应该发生什么。
如果你喜欢我的作品,请在这里订阅https://medium.com/subscribe/@niklas_lang或者查看我的网站* 数据大本营 !另外,媒体允许你每月免费阅读 3 篇文章 。如果你想让无限制地访问我的文章和数以千计的精彩文章,请不要犹豫,通过点击我的推荐链接:https://medium.com/@niklas_lang/membership每月花$5获得会员资格*
**</5-basic-commands-for-working-with-python-lists-3088e57bace6> </6-fundamental-questions-when-working-with-a-pandas-series-1d142b5fba4e> </6-pandas-dataframe-tasks-anyone-learning-python-should-know-1aadce307d26> **
令人兴奋的与 Python 硬件交互的方式
原文:https://towardsdatascience.com/exciting-ways-to-interact-with-hardware-from-python-2afb9278fe07
命令式 Python!与您的硬件交互

克里斯蒂安·威迪格在 Unsplash 上拍摄的照片
介绍
D 独立编程语言是当今软件不可或缺的一部分。声明式编程是与命令式编程相对的两种基本编程范式之一。声明式编程的目标是掩盖计算机的硬件,而仅仅关注代码本身的逻辑。另一方面,命令式编程通常涉及程序员和计算机硬件之间的密切关系。这些当然都是范式的简化,但通常这是两者之间的自由裁量权。重要的是要记住这是一个范围,一种语言可以混合命令式和声明式特性,就像一种语言可以混合函数式和面向对象的特性一样。声明式语言的目标是考虑可读性和简单性,而不是速度,这正是大多数声明式语言所提供的。声明式编程语言不仅容易学习,而且对于编程概念的原型开发和教学也非常有用。上一代程序员最典型的声明式编程语言可能是 BASIC 或类似的语言,但今天这个名称落在了 Python 上。
Python 当然提供了我们可能从任何其他声明式编程语言中期待的所有特性。Python 有令人难以置信的可读语法,常常感觉像是要消化的英语。这种语言也不需要很长时间就能学会,尤其是与 c 语言等其他语言相比。如今,许多计算机科学课程都是从 Python 课程开始,然后才转向另一种编程语言。最后,Python 也在许多原型开发环境中使用。由于它们的可读性和缺乏硬件控制,声明性语言使得像原型制作或学习这样的任务变得非常容易。一个程序员甚至不需要首先知道计算机有什么硬件就可以很好地编写 Python。
然而,除了声明性之外,Python 语言当然还有很多巨大的优势。这种语言当然是恰当的,并且已经被它今天在从系统管理到数据科学的一切事物中的普遍使用所证明是值得尊敬的。特别是,由于 Python 与命令式语言的紧密集成,这种语言在机器学习中得到了很好的应用。此外,PYthon 的生态系统绝对庞大,在 Python 包索引中拥有超过 200,000 个独特的注册包。
虽然使用声明式编程语言有很多优点,特别是对于像机器学习这样的复杂任务,但是使用这种语言也有很多明显的缺点。第一个也是最明显的是速度。声明式编程语言的固有特性不一定是速度慢,但是大多数时候,有人决定开发一种声明式语言,这种语言最终会被解释。此外,更简单的语法也与声明性语言相关联,结果是语言在大多数情况下运行得有点慢。即使语言本身并不慢,命令式语言中的低级硬件交互也很重要,因为它允许您在大多数情况下尽可能高效。然而,对于声明性语言来说,情况并非如此,因为通常会有一些解释器或编译器试图找出表述您的工作的最佳方式。由于这是声明式语言的一个缺点,它也成为 Python 的一个缺点。
毫无疑问,Python 是一种声明式编程语言,正如我提到的,早期的语言通常是几种其他编程范式的混合,因此 Python 是一种通常可能是声明式的语言,但仍然具有一些命令式的特性。尽管功能相对有限,但您可能最终会惊讶于所有 Python 所能做的事情!
压型
许多声明式编程语言的第一个也是最明显的命令式特性是剖析,Python 也不例外。在标准库中,有各种不同的工具可以用来分析您的机器。从最基本和最不可能的命令开始,我们可以使用timeit或time模块来计时解释。对于我的例子,我将使用time:
>>> import time
>>> start = time.time()
>>> print("% s seconds" % (time.time() - start))
0.4931051731109619 seconds
所以计时功能当然有用,但并不是必须的。我们可能会查看处理器运行这段 Python 代码所花费的时间,但这仍然不能给我们提供太多关于硬件的信息。在这方面一个类似的技术是使用标准库中的cProfile,它提供了比time更多的信息。为此,我们使用cProfile.run函数,以字符串形式提供我们的 Python:
>>> cProfile.run("5 * 1")
3 function calls in 0.000 secondsOrdered by: standard namencalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
我非常喜欢使用的另一个选项是line_profiler。这个选项有助于获得更多关于这些时间的信息,这些信息没有包含在cProfile中。然而,在硬件方面,性能评测器可能是pylikwid,它提供有关处理器的信息:
--------------------------------------------------------------------------------CPU name: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60 GHz x 8CPU type: Intel Core Haswell processorCPU clock: 2.60 GHz--------------------------------------------------------------------------------================================================================================Group 1 L3: Region MY_REGION================================================================================+-------------------+----------+| Region Info | Core 0 |+-------------------+----------+| RDTSC Runtime [s] | 0.091028 || call count | 1 |+-------------------+----------++-----------------------+---------+--------------+| Event | Counter | Core 0 |+-----------------------+---------+--------------+| INSTR_RETIRED_ANY | FIXC0 | 9.262083e+08 || CPU_CLK_UNHALTED_CORE | FIXC1 | 3.255393e+08 || CPU_CLK_UNHALTED_REF | FIXC2 | 2.846262e+08 || L2_LINES_IN_ALL | PMC0 | 1.219118e+06 || L2_TRANS_L2_WB | PMC1 | 9.183680e+05 |+-----------------------+---------+--------------++-------------------------------+--------------+| Metric | Core 0 |+-------------------------------+--------------+| Runtime (RDTSC) [s] | 0.09102752 || Runtime unhalted [s] | 9.596737e-02 || Clock [MHz] | 3.879792e+03 || CPI | 3.514753e-01 || L3 load bandwidth [MBytes/s] | 8.571425e+02 || L3 load data volume [GBytes] | 0.078023552 || L3 evict bandwidth [MBytes/s] | 6.456899e+02 || L3 evict data volume [GBytes] | 0.058775552 || L3 bandwidth [MBytes/s] | 1.502832e+03 || L3 data volume [GBytes] | 0.136799104 |+-------------------------------+--------------+
最后,当然还有memory_profile,可以用来检查你的记忆状态。这可能是这里最重要的配置文件,这就是为什么我把最好的留到了最后!要分析给定函数的内存使用情况,只需将 profile decorator 添加到您想要分析的内容中,然后使用memory_profiler运行该文件:
python -m memory_profiler myfile.py
记忆
好吧,剖析很酷,但这并不是绝对必要的特性。我们可能在查看我们的硬件,但我们没有与我们的硬件互动。对于我们的第一个命令性特性,我们将看看关键词del。这是 Python 为我们提供的管理内存的少数工具之一。这个关键字将从 Python 的内存堆中删除一个给定的变量。用法非常简单,只需将关键字放在变量名之前:
>>> x = 5
>>> **del** x
>>> print(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
这似乎是一个非常基本的特性,但是许多现代声明式语言并不包含这样的东西。甚至我最喜欢的语言 Julia 也没有这样的功能。取而代之的是,把事情置之不理,让 Julia 用垃圾收集来随便处理剩下的事情,这是司空见惯的。不用说,这是一个很棒的特性。肯定有一些时候,我们可能希望通过删除不同的对象来节省内存,幸运的是 Python 包含了一种开箱即用的方法del!
还应该注意的是,有了所有这些例子,C 语言还可以做更多的事情。如果你用 C 语言编写 Python 库,管理硬件要容易得多,这就是为什么 Pandas 和 NumPy 有如此出色的性能!
处理器
对于处理器来说,我们在硬件方面做不了多少语言本身做不了的事情,但是,我确实想借此机会提出 C 语言方面的另一点。每当您运行一个没有依赖关系的典型 Python 脚本时,该过程将总是在一个线程上运行。然而,当用 C 编写 Python 模块时,可以访问多个线程并从模块中获得更大的性能。再一次,这种语言和解释它的语言之间的简单对话,以及 C Python 模块到轮子中的进一步打包,使得我们日常使用的许多 Python 包足够快,可以用于那些应用程序。
国家政治保卫局。参见 OGPU
虽然 Python 最初在设计时从未考虑过数值计算或并行计算,但由于该语言在机器学习中的突出使用,我们现在有了大量不同的 GPU 包。我个人最喜欢的是 Numba。Numba 非常棒,因为它不仅提供了 GPU 加速,还提供了 JIT 编译——它本质上是最快的 Python 解释器,无需将 Python 转换成 c。更好的是,它使用一个简单的装饰器来完成这一点。我正好有一篇关于 Numba 的文章(写于 2020 年的黑暗时期),所以如果你想读更多关于 Numba 的文章,这里有一个链接:
另一个很好的选择是 Dask 模块。Dask 在很多方面与 JIT 相似,但是侧重点有所不同。Dask 不仅有助于解决并行计算的速度问题,还有助于在机器集群之间分配工作负载。此外,它有自己的数组对象和接口,最后甚至有这些数组的惰性/代数形式。
惰性/代数意味着数组不保存在内存中,而是由一个函数来表示。数据的变化会立即保存和计算,但索引可以按需完成,因为函数只是针对该范围内的数字运行。
结论
当谈到命令性特性时,Python 是一种肯定不会获奖的语言。然而,与许多其他类似的声明性语言相比,Python 确实在这方面用相当少的资源完成了很多工作。使用像 Python 这样的语言肯定有缺点,不与硬件有密切的关系肯定会是其中之一。也就是说,这肯定不会阻止任何人在需要这种硬件集成时用 Python 做原型并将操作转移到 C。幸运的是,即使您不想重新使用 ole GCC 并编写一个 Python 模块,仍然有很多方法可以减轻 Python 的声明性并提高您的性能!
强化学习的精彩世界
原文:https://towardsdatascience.com/exciting-world-of-reinforcement-learning-6dcb9a54cb21
消费者企业的案例

Preethi Viswanathan 在 Unsplash 上拍摄的照片
自从我对强化学习领域及其在行业中的众多应用感到好奇和着迷以来,我对该领域的兴奋与日俱增。在这里,我想分享一些我对强化学习(RL)在消费者业务中的潜在应用的了解。但是,在我深入细节之前,先为初涉这一主题的 ML 从业者快速介绍一下 RL。
RL 是机器学习的一个分支,涉及智能代理的培训,它可以通过在环境中反复试验来学习执行目标,在培训结束时,我们有一个代理可以在现实生活中独立执行目标。现在,如果你熟悉其他类型的 ML——监督和非监督学习技术——这听起来可能非常类似于监督学习方法。但是两者之间的最大区别(以及其他区别)是 RL 不需要提供任何明确的标签,这与监督学习技术不同。要了解更多细节和背景,你可以阅读一些关于 RL 的博客/文章。(TDS/medium 上也有几个。)你还可以查阅一些由 Deepmind 、 OpenAI 所做的开创性工作,以了解这些年来的成就,也可以阅读理查德·萨顿&安德鲁·g·巴尔托所著的《强化学习——简介》一书,以了解强化学习领域是如何产生的。
在众多超级令人兴奋的 RL 应用中,我的搜索集中在消费者业务的个性化用例上。虽然我的用例集中在媒体和出版行业,但这可以很容易地扩展到其他行业,如电子零售商、旅游/酒店等。最后,我们将看看可以帮助完成这些用例的 RL 解决方案的大致轮廓。
a)新闻快递个性化——任何媒体和出版公司的主要流量来源之一是通过新闻快递。我们经常会遇到这样的情况:不管我们想什么时候阅读,我们最喜欢的日报/周报和杂志的简讯都会在同一时间到达我们的手中。换句话说,简讯在一周的同一时间/同一天发给所有用户并不罕见。现在,在当前的数字化时代,情况未必如此。理想的解决方案是在用户很可能打开它的时候发送它。RL 可用于在每个用户的最佳时间发送电子邮件,为读者带来个性化体验。

马库斯·温克勒在 Unsplash 上的照片
b) NL 容量识别——其次,营销人员经常遇到的另一个问题是识别发送给订户的 NL 的最佳数量。“每个用户发多少封邮件算多?”众所周知,NL 偏好因用户而异,并且不总是相同的。然而,我们习惯于始终向所有订户发送相同数量的电子邮件。我承认,没有简单的方法可以为每个用户动态地确定这个神奇的数字。但是,通过应用 RL 技术,这是一个可以解决的问题。
c)个性化盒子订阅——盒子订阅是这样设计的订阅产品,即一期订阅由某类产品组成。美容盒的月订阅将包含美容产品的随机分类,例如面部、皮肤、头发等产品。下个月的问题可能是一个完全不同的产品分类。请注意,在这种模式下,订阅者没有选择他/她想要的产品的权利,从用户那里得到的唯一反馈是更新订阅的用户。这个问题的主要挑战是确定正确的产品组合,以最大限度地保留我们的用户。
将这个问题公式化为 RL 问题,我们可以为每个订阅问题确定最个性化的最佳分类,同时最大化订户的保留。

d)动态付费墙计量——在数字媒体和出版行业,出版商需要做出的一个关键决策是,在通过允许用户免费阅读文章提供广告来获取收入和通过阻止数字付费墙免费访问(在一定数量的免费文章后)诱导用户订阅来获取收入之间进行权衡。付费墙的号召可以是订阅,也可以是让读者注册以便继续阅读。通常情况下,付费墙的收费标准是所有用户每月 2/4/6 篇免费文章。

安妮·斯普拉特在 Unsplash 上的照片
但是这种实现并不是最佳的解决方案,因为一个品牌的忠实读者会继续阅读更多的文章,通过更多的广告收入做出贡献,并且将该用户的读者群减少到每月只有 4 篇文章,这是过早地从该用户中减少了潜在的更多广告收入。理想情况下,我们可以为这样的用户带来 5 月后,每月 6-7 篇文章的付费墙。另一方面,不太可能回来阅读第二篇文章的参与度较低的用户不需要有 4 篇文章的限制,因为该用户不太可能通过广告产生任何收入,所以我们甚至可以在该用户第二次访问时为其设置付费墙,并推动该用户订阅。
RL 可以学习每个用户的阅读模式,并推荐一个最佳的付费墙限制,以最大化每个用户的收入潜力,而不是为每个用户设置这样的手动规则。不仅如此,它的学习会随着时间的推移适应每个用户不断变化的阅读行为,并自动调整付费墙的限制,以最大限度地提高企业的收入潜力。
现在我们已经看了用例,让我先睹为快,看看其中一个用例的 RL 解决方案设计。我们将使用一种叫做 DQN(深度 Q 网络)的 RL 算法来解决这个问题,这是深度学习和 Q 学习原理的结合。我认为大多数 ML 从业者都熟悉深度学习。Q-learning 是一类称为表格解决方案的 RL 解决方案的算法,旨在学习每个状态的 q 值。(一个州的 Q 值是代理人未来可能去的所有州的累积(贴现)奖励)。对于具有有限状态空间的问题,例如冰封湖问题,这是一个很好的解决方案。然而,对于更大的状态空间,这种解决方案变得笨拙,我们需要采用一种近似的方法来估计状态值,这类解决方案被称为“近似方法”。DQN 算法是近似方法中最流行的算法。
在 DQN,深度学习网络作为一个函数近似值,估计给定状态/(动作)的值。所有用例的解决方案设计、算法和设置都是相同的,但是 MDP(马尔可夫决策过程)的配置(状态空间、奖励和要采取的行动)会因每个用例而异。
用例 a)的 MDP 配置如下。
状态—过去半个月的 NL 打开/点击模式。
行动—一天中的 1-24 小时。这可以进一步减少到 12 个动作值,每个动作代表可以发送电子邮件的 2 小时时间段。
奖励——本地点击+2,本地打开+1,否则为 0
我还分享了一篇媒体博客文章的链接(作者是 Zynga ML 工程团队的迈赫迪·本·阿耶德和帕特里克·哈利娜),解释了他们如何解决定制应用程序通知的问题。这是验证这些用例的 DQN 解决方案/方法的非常有用的参考来源和动机。
我希望您发现上面的应用程序 RL 的业务用例是有见地和有用的。在这篇文章之后,我想介绍一些广告/商业业务的使用案例和一些学习资源,我发现这些资源对于在我的团队中建立 RL 能力很有用。
使用 OKRs 执行数据策略
原文:https://towardsdatascience.com/executing-a-data-strategy-with-okrs-acbdbbf126a7
编写数据策略是一回事,实现它是另一回事。以下是目标和关键结果的帮助。

安娜斯塔西娅·彼得罗娃在 Unsplash 上的照片
okr 是公司的舵手
目标和关键结果(通常简称为 okr)是将战略意图转化为组织可衡量结果的框架。
这是英特尔的安迪·格罗夫发明的,源于他之前的管理科学,已经存在了一段时间,并在谷歌、Intuit 和 MyFitnessPal 等公司得到了有效的应用。我在过去工作过的三家公司都用过 OKRs。如果你搜索一下,你会发现有大量关于 OKRs 的文章。如果你想更深入,那么看看 whatmatters.com 的作品,或许读读约翰·杜尔的书。
除非你知道基本知识,否则这篇文章没有多大意义,所以这里有一个关于 OKRs 的盆栽概述。
目标是公司或部门希望在给定的时间范围内(通常是一年)实现的一系列目标。
okr 建立在整个组织中。他们可能从高管概述一些更高层次的战略目标开始,这些目标由公司各部门和团队进行解释和采纳。重要的是,okr 不能机械地向下级联。相反,因为 okr 是公开共享的,所以可以自下而上甚至“中间向外”地构建它们。这避免了不灵活的过度调整计划和相应的跨多层相互关联的 okr 跟踪进展的努力。它还允许团队引入本地创新,同时仍然与公司的整体使命保持一致。
举一个营销领域的例子;首席执行官将制定获取客户的目标,首席营销官将制定营销活动的目标,并以关键结果(成果)的形式表达客户获取情况,表明这些目标已经实现。这些 okr 将指导营销部门定义一套衡量指标来显示 okr 的进展。一旦定义了度量标准,就可以在营销团队中开发和计划一系列营销活动。然后,团队可以开始工作,与更大的公司目标保持一致。
如上所述,这并不是说 okr 是自上而下的。相关 okr 之间需要足够的一致性和可追溯性,以确保公司朝着正确的方向前进。每个团队、部门、分部或任何组织单位都有责任定义他们的目标,他们如何知道他们什么时候达到了目标,以及他们需要做什么来达到目标。
由于 okr 在整个公司都是透明的,它们可以帮助填补战略规划的空白。数据部门可以看到市场营销试图实现什么,以及他们的预期结果是什么。营销数据团队可以计划并查看他们可以帮助完成哪些活动。重要的是,营销数据团队可以增加改进目标,这可能使他们成为更好的团队,并提供更好的服务。
当他们做得好的时候,OKRs 可以把团队的精力集中在真正重要的任务上,使团队朝着公司期望的方向前进。
使用 OKR 方法的重点是执行。它们应该是关于真正需要完成的事情。这就是为什么它们在与可靠的数据战略合作时非常出色。
公司战略是地图和指南针
成功的公司执行他们的战略。非常成功的公司无情地做到了这一点。
在过去的十年中,数据对于公司整体战略的重要性不断提升。企业将数据战略作为其更广泛的业务战略的一部分并与之并行发展,这种情况已经变得很常见。总的来说,这伴随着一个新的高管职位的任命,即首席数据官。
像 OKRs 一样,有很多关于为什么公司应该建立数据战略的好资源。大多数数据战略的重点是将公司转型为数据驱动型公司。也就是说,数据被视为有价值的资产,可以转化为规划业务方向的信息。
这是强大的东西。那么,为什么许多公司无法执行他们的数据战略呢?
你不用找很远就能找到证据,告诉我们大多数公司未能成为数据驱动型公司。TechCrunch [1]引用了 Randy Bean 和 Thomas H. Davenport 在《哈佛商业评论》上发表的一篇关于 NewVantage Partners 调查的文章,该文章发现,69%的受访公司报告称,尽管在数据技术和人工智能计划方面进行了大量投资,但他们未能创建一个数据驱动的组织[2]。随着 Bean 和 Davenport 的文章引用 2017 年以来的下降趋势,越来越多的公司认为自己越来越受观点而不是数据的驱动,消息变得更糟。
通常情况下,在战略和行动中表达的意图之间存在着执行差距,这在工厂车间就很明显。OKRs 可以填补这一空白。
我们已经在我现在工作的公司开始这样做了。数据策略的上一次迭代概括为 4 个大主题。适当的度量(如 KPI)与主题相关联。其意图是值得称赞的,并在绩效目标中有所体现,但该方法缺乏正式的执行框架。今年,我们使用 OKRs 来提供这个框架。
全面的数据策略应包含以下要素:
:
假设你生产产品的总体策略实际上符合客户的实际需求(否则你很快就会破产!),那么数据策略需要解决构建这些产品的功能的 okr,即:数据团队需要为产品、营销、客户服务、移动、网站工程等提供支持。各队去见他们的 okr。
数据平台技术与架构:
建立一个强大的数据存储平台、数据馈送、可视化和建模工具以及支持连接基础设施的计划。
分析学
能够应用模型并对您拥有的数据进行深度分析。
:
在必要时使数据可用,对其进行编目,使其可被发现并被很好地理解,以鼓励公司中的员工有效地使用它。
人 :
雇佣和留住顶尖人才,培养现有员工,培养技术卓越和协作的文化。
合规/治理 :
遵守数据收集和使用方面的法规要求和公司政策。建立高效透明的流程,确保数据团队在开发解决方案时遵守法规和政策。
数据质量与管理:
为流经公司的数据设定可信的标准和机制。
安全:
与企业保护数据和系统安全的更广泛方法保持同步。
数据素养与文化 :
将模型和分析的输出插入公司的决策结构。如何获得数据成果并加以操作,将其转化为业务行动。将数据提升为公司的头等大事。
将数据策略表示为 OKRs
一旦概述了这些数据战略要素,就可以构建一个 OKR 来实现每一个要素。不过,战略元素与 okr 之间并不一定是一一对应的。更常见的情况是,OKR 可能涵盖战略的多个方面。确切的 okr 将根据贵公司的具体情况而有所不同。这里有一些简单的例子来演示这种技术。
公司战略: 与客户需求接轨
O: 获取更多手机 app 客户
KR: 移动应用下载量增长 75%
数据战略:与职能团队合作,实现公司目标
数据 OKR:所有导致应用程序商店购买的客户旅程必须有指标收集和分析,以衡量进展或退出。
公司战略: 人
O: 致力于培养我们的员工以减少流失和技能泄露
KR: 每个季度员工流失率保持在 10%以下
数据策略:让我们的团队保持技术熟练、参与和最新
数据 OKR:我们数据团队中 75%的员工在一年内成功完成了 3 门在线技术课程
公司战略: 对产品功能开发做出更明智的决策
O: 数据对产品开发起着关键的输入作用
KR: 使用生命周期价值(LTV)计算作为产品所有者的输入,他们正在开发产品功能以吸引更高价值的客户
数据策略:提高决策的数据素养
数据 OKR:LTV 计算的输出与产品团队 scrums 中超过 200 个特性开发故事点相关联
…冲洗并重复,以便数据战略与公司战略保持一致,并且 okr 可以执行。
开发 okr 并找到它们之间的一致性并不是一件容易的事情。它总是包括与你的战略家同事讨论、分歧、哄骗和测试想法。虽然这看起来很难,但副作用是它培养了跨公司协作和共同使命的精神。
执行路径上的下一步
一旦概述了 okr,并通过了感觉检查以确认它们完全覆盖了数据策略的元素,就该进入更详细的执行阶段了。
这涉及到;
- 开发用于衡量每个 OKR 进度的指标和收集方法。
- 与负责交付的数据团队交流并微调 okr。
- 然后,数据团队将确定实现关键结果所需的任务和活动。这如何发生取决于你的公司如何建立产品和服务。例如,在敏捷商店中,这将导致故事开发和冲刺计划。
结论
拥有全面的数据战略对于现代企业的发展至关重要。将战略变成现实可能会很棘手。这就是 OKRs 可以提供帮助的地方。在本文中,我展示了如何在数据策略和 OKRs 之间建立联系,以构建一个与公司整体发展方向一致的交付框架。
[1]罗恩·米勒,大公司变得不够快,TechCrunch (2019)
[2]兰迪·比恩和托马斯·h·达文波特,公司在成为数据驱动的努力中失败了 (2019),《哈佛商业评论》
Python 中的执行时间
原文:https://towardsdatascience.com/execution-times-in-python-ed45ecc1bb4d
用正确的方法测量 Python 代码的执行时间

图片作者。
测量代码执行时间是一项至关重要的工作,无论是数据科学项目中的算法选择还是软件开发中的速度优化。不管是哪种情况,在 Python 中测量时间有其微妙之处,我们最好把它做好。
在这个故事中,我们将通过许多方法来测量 Python 中的时间。我们的目标是,计算代码块的执行时间。
您可能会和我一样感到困惑,因为没有多少 pythonic 式的方法来测量执行时间。希望在读完这个故事后,你能实现更有效的方法来计时你的代码。
我们将在整个故事中使用这两个函数来测试我们的计时器。第一个函数“do_complex_stuff”休眠 1 秒钟(实际上是休眠 Python 线程),以模拟一些长时间的计算。第二个函数,“do_simple_stuff”不休眠。
为什么你应该避免时间。时间
测量时间时,首先要避免使用 time.time 。典型的例子是这样的:
做复杂的事情…
1.0006861686706543
您应该避免它的两个主要原因是:
- 即使系统时间分辨率为 1 秒,时间也会以浮点形式返回,表示自 epoch 以来的秒数。在某些系统中,这种行为会导致舍入误差。
- 它不像时间模块中的其他时钟一样精确。一些系统会产生奇怪的行为,特别是对于小时间的测量。
使用纳秒来避免舍入误差
你可以使用 time.time_ns 来避免舍入误差, time_ns 给出从纪元开始的整数纳秒。我们可以这样做:
做复杂的事情…
1002516000
它不是最精确的钟,但比时间好。
关于纳秒的一个简单说明。从 Python 3.7 开始,时间模块中很多带后缀“_ns”的函数都返回整数纳秒。它们的原始浮点(秒)版本可以使用不带“_ns”后缀的同名函数来调用。例时间。时间和 time.time_ns 。
使用 perf_counter 获得精度
如果精度是你想要的,使用 time.perf_counter 代替,或者更确切地说是它的纳秒版本 time.perf_counter_ns。这是目前最精确的时钟。时钟的绝对值是没有意义的;只有时间的减法才有意义。
做复杂的事情…
1005195401
这是计时执行的首选方式。
使用 process_time 表示 CPU 时间
在 Python 中执行代码时,CPU 不会将其所有时间让给执行进程。在操作系统级别,还有其他进程也在争夺这些时间。在 Python 中,我们可以通过使用 time.sleep 显式地告诉 CPU 让进程线程休眠。因此,允许操作系统级别的其他进程运行。
有时候我们想测量进程的 CPU 时间,不考虑线程睡眠(其他进程做其他事情)。在这种情况下,我们应该使用 time.process_time_ns 。与 perf_counter 一样,只有两次相减才有意义。
这方面的一个例子是:
做复杂的事情…
3872000
现在让我们也为“do_simple_stuff”函数计时。与“do_complex_stuff”函数的唯一区别是它休眠 1 秒钟:
做简单的事情…
3128000
我们在这里看到的是睡眠时间没有考虑在内。这正是我们想要的行为。
有时候,这种方式对时间编码是有帮助的,特别是当有很多 I/O 绑定的任务并且线程休眠不受我们控制的时候。
对长流程使用单调时钟
当对较长的流程进行计时时,有一个不容忽视的时间问题。如果系统时间在进程的生命周期中改变了会发生什么?系统时间的改变比你想象的更常见;它们可能是由于 NTP 同步服务或夏令时而发生的。
在这种情况下,我们需要一个即使系统时间改变也不会改变的时钟。输入 time.monotonic_ns 该时钟适用于计时较长的流程:
做复杂的事情…
1004428370
禁用垃圾收集器以获得准确的计时
Python 的一个特性是自动垃圾收集。不涉及太多细节,这是一个通过自动化内存管理(即,为未使用的对象释放内存)使您的生活变得更加轻松的过程。
垃圾收集会影响执行时间,因为它可能发生在代码块计时期间。这需要的时间是不可忽略的:
68679577
因此,最好在测量时间时暂时禁用它:
做简单的事情…
486133
timeit 模块
我对 timeit 模块持观望态度,但我喜欢 Jupyter 笔记本 magic command 的界面:
做复杂的事情…
做复杂的事情…
做复杂的事情…
做复杂的事情…
做复杂的事情…
做复杂的事情…
做复杂的事情…
做复杂的事情…
每次循环 1 秒 1.85 毫秒(平均标准时间戴夫。7 次运行,每次 1 个循环)
我不喜欢 Python 界面。但是,时间编码很方便。
代码的单次执行:
做复杂的事情…
做复杂的事情…
做复杂的事情…
做复杂的事情…
做复杂的事情…
5.012320117995841
或者重复测量。
做复杂的事情…
做复杂的事情…
做复杂的事情…
做复杂的事情…
做复杂的事情…
做复杂的事情…
[2.0122088000002805, 2.0041232610001316, 2.0006091740001466]
需要注意的是,timeit 官方文档建议使用多次测量中的最小时间,因为这可能比平均值更有代表性。
在 timeit 接口中,我们可以使用函数计时,也可以使用字符串计时。使用一根弦对我来说并不像是 pythonic。不管有多方便,我都不能不皱眉头。
做事…
做事…
做事…
做事…
做事…
做事…
做事…
做事…
做事…
做事…
0.000834739999845624
构建自定义计时器类
让我们通过使用我们现在所知道的关于垃圾收集器、我们可以使用的不同时钟以及对更 Pythonic 化的定时器的需求来创建一个定制的定时器类。
我们想从这个计时器得到的是:
- 关闭垃圾收集的可能性
- 选择我们想要的时钟类型
- 除非特别转换为秒,否则使用纳秒时间
这个类完成这项工作:
使用它非常简单,启动、停止并获得纳秒或秒的时间;就是这样。
要实例化该类,我们需要指定 timer_type,即我们将使用性能时钟、进程时钟还是长期运行时钟(单调);我们还可以确定是否禁用垃圾收集。
做复杂的事情…
1001954642, 1.001954642
这是一个进步,但对我来说还不是很 pythonic 化。我们可以做得比显式启动和停止计时器更好。
计时上下文管理器
进入上下文管理器,这是 Python 的顶级语法糖特性之一。当我们阅读带有关键字“with”的代码时,有一种优雅的感觉。对于那些不熟悉上下文管理器的人来说,简单的上下文管理器并不难创建,尤其是在使用标准模块 contextlib 时。
所以我们可以创建一个上下文管理器,它使用我们的定时器类,并负责启动和停止它,就像这样:
然后我们可以用它来计时一段代码:
做复杂的事情…
1.005408027
这里的计时器变量是上一节中的计时器类的一个实例,因此它具有相同的属性。
最后的话
到目前为止,您已经了解了如何为代码计时的基本知识。希望从现在开始,你会避免大多数常见的错误。您还了解了如何定制定时器,并将其封装在一个干净的 pythonic 上下文管理器中。
关于 timeit 模块,我不怎么用它,除了它的 Jupyter 魔法命令界面。然而,它是一个方便的模块,尤其是重复和自动量程功能。
我希望这个故事对你有用。如果你想知道更多类似的故事,请订阅。
https://medium.com/subscribe/@diego-barba
喜欢这个故事吗?通过我下面的推荐链接成为一个媒体成员来支持我的写作。无限制地访问我的故事和许多其他内容。
https://medium.com/@diego-barba/membership
使用这些模型扩展您的时间序列库
原文:https://towardsdatascience.com/expand-your-time-series-arsenal-with-these-models-10c807d37558
整理、装袋、堆叠等等

作者图片
时间序列数据通常有三个组成部分:
- 季节性
- 趋势
- 残留的;剩余的
预测这些组成部分,你就可以预测几乎任何时间序列。听起来很简单,对吧?
不完全是。围绕指定模型以使其能够正确考虑这些元素的最佳方法存在许多模糊之处,并且在过去几年中已经发布了许多研究来寻找这样做的最佳方法,其中最先进的模型(如递归和其他神经网络模型)占据了中心位置。此外,许多系列还有其他应该考虑的影响,如假期和结构性中断。
总而言之,预测任何给定的序列通常不像使用线性回归那么容易。非线性估计和集成方法可以与线性方法相结合,找到任何序列的最佳方法。在本文中,我概述了 Scitkit-learn 库中这些模型的一个示例,以及如何利用它们来最大化准确性。这些方法应用于每日访客数据集中,这些数据在 Kaggle 或 RegressIt 上有超过 2000 次的观察。特别感谢教授 Bob Nau 提供!
这篇博文的结构相当重复,每个应用的模型都有相同的图表和评估指标。如果您已经熟悉 Scikit-learn 模型和 API,如 MLR、XGBoost 等,您可以跳到打包和堆叠部分来概述一些可能略有不同的内容。你可以在 GitHub 上找到完整的笔记本。
准备模型
所有模型都使用 scalecast 包运行,该包包含结果,并将 Scikit-learn 和其他模型包装在时序数据周围。默认情况下,它的所有预测都使用动态多步预测,与一步预测的平均值相比,它在整个预测范围内返回更合理的准确性/误差指标。
pip install scalecast
如果你觉得这个包有趣,就在 GitHub 上给它一颗星。
我们将使用 60 天的预测范围,并使用 60 天的设置来验证每个模型并调整其超参数。所有模型都将在 20%的原始数据上进行测试:
f=Forecaster(y=data['First.Time.Visits'],current_dates=data['Date'])
f.generate_future_dates(60)
f.set_test_length(.2)
f.set_validation_length(60)
从 EDA(此处未显示)来看,似乎在过去 4 周内存在自相关,并且前 7 个因变量滞后可能是显著的。
f.add_ar_terms(7) # 7 auto-regressive terms
f.add_AR_terms((4,7)) # 4 seasonal terms spaced 7 apart
对于这个数据集,必须考虑几个季节性因素,包括每日、每周、每月和每季度的波动。
f.add_seasonal_regressors(
'month',
'quarter',
'week',
'dayofyear',
raw=False,
sincos=True
) # fourier transformation
f.add_seasonal_regressors(
'dayofweek',
'is_leap_year',
'week',
raw=False,
dummy=True,
drop_first=True
) # dummy vars
最后,我们可以通过添加年份变量来模拟序列的趋势:
f.add_seasonal_regressors('year')
对于所有这些模型,您通常希望为它们提供稳定的时间序列数据。我们可以用扩展的 Dickey-Fuller 检验来确认该数据是稳定的:

作者图片
最低贷款利率(minimumlendingrate)
我们将从简单开始,尝试应用多元线性回归(MLR)。该模型快速、简单,并且没有要调整的超参数。它经常获得很高的精确度,即使使用更先进的方法也很难被击败。
它假设模型中的所有组件可以以线性方式组合,以预测最终输出:

作者图片
其中 j 是添加的回归量的数量(在我们的例子中,是 AR、季节和趋势分量),α是相应的系数。在我们的代码中,调用这个函数类似于:
f.set_estimator('mlr')
f.manual_forecast()

作者图片

作者图片
值得注意的是,时间序列的线性方法的一个更常见的应用是 ARIMA ,它也使用序列的误差作为回归变量。MLR 假设序列的误差是不相关的,这在时间序列中是虚假的。也就是说,在我们的分析中,MLR 获得了 13%的测试集平均绝对百分比误差和 76%的 R2。让我们看看是否可以通过增加复杂性来解决这个问题。
套索
接下来回顾的三个模型,Lasso、Ridge 和 ElasticNet,都使用 MLR 的相同基础函数进行预测,但是估计系数的方式不同。有 L1 和 L2 正则化参数,用于减少系数的大小,从而减少过拟合,并可以导致更好的样本外预测。在我们的案例中,这可能是一个很好的尝试技术,因为 MLR 的样本内 R2 得分为 95%,明显大于样本外 R2 的 76%,表明过度拟合。
使用 lasso 可以估计一个参数,即 L1 惩罚参数或 alpha 的大小。我们可以通过对验证数据集的 100 个 alpha 值进行网格搜索来做到这一点。看起来是这样的:
f.add_sklearn_estimator(Lasso,'lasso')
f.set_estimator('lasso')
lasso_grid = {'alpha':np.linspace(0,2,100)}
f.ingest_grid(lasso_grid)
f.tune()
f.auto_forecast()
Lasso(以及 Ridge 和 ElasticNet)使用缩放输入很重要,这样惩罚参数就能平衡所有系数。默认情况下,Scalecast 使用最小最大缩放器。

作者图片

作者图片
选择的最佳 alpha 值为 0.081。该模型既没有提高 MLR 模型的样本外精度,也没有减少过拟合。我们可以用山脊模型再试一次。
山脉
山脊类似于套索,除了它使用 L2 惩罚。这里的区别在于,L1 罚函数可以将某些系数降低到零,而里奇罚函数只能将系数降低到接近零。通常,两种模型产生相似的结果。我们可以使用为套索模型创建的相同网格来调整山脊模型。
f.set_estimator('ridge')
f.ingest_grid(lasso_grid)
f.tune()
f.auto_forecast()

作者图片

作者图片
为脊模型选择的最佳α是 0.384,其结果与套索模型相似,如果不比套索模型差一点的话。我们还有一个可以将正则化应用于 MLR 的模型:ElasticNet。
弹性网
Scikit-learn 提供的 ElasticNet 模型将使用线性模型预测输出,但现在它将混合 L1 和 L2 惩罚。现在要调整的关键参数是:
- L1/L2 罚款比率(
l1_ratio) - 惩罚值(
alpha)
Scalecast 为 ElasticNet 提供了一个很好的默认验证网格,所以我们不必创建一个。
f.set_estimator('elasticnet')
f.tune()
f.auto_forecast()

作者图片

作者图片
ElasticNet 的最佳参数是 1 的 l1_ratio,这使得它相当于套索模型,以及 0.3 的 alpha。它的表现与套索和脊模型一样好,并且没有减少过度拟合。
随机森林
在用尽了几种线性方法来估计这个数列之后,我们可以转向非线性方法。其中最流行的是随机森林,一种基于树的集成方法。它通过用原始数据集的自举样本(替换采样)聚集几个树估计器来起作用。每个样本可以利用原始数据集的不同行和列,最终结果是应用于每个样本的特定数量的基础决策树估计器的平均值。

作者图片
对使用随机森林进行时间序列预测的一些批评不时出现,例如估计值不能大于最大观测值或小于最小观测值。有时,这个问题可以通过确保只将平稳数据提供给估计器来解决。但总的来说,这个模型并不以其时间序列的威力而闻名。让我们看看它在这个例子中的表现。我们可以指定自己的验证网格。
rf_grid **=** {
'max_depth':[2,3,4,5],
'n_estimators':[100,200,500],
'max_features':['auto','sqrt','log2'],
'max_samples':[.75,.9,1],
}
然后运行预测。
f**.**set_estimator('rf')
f**.**ingest_grid(rf_grid)
f**.**tune()
f**.**auto_forecast()

作者图片
从现在开始,我必须粘贴两个表来进行模型基准测试,否则就无法阅读了。底部表格中的行与顶部表格中列出的型号相对应。

作者图片

作者图片
不幸的是,我们在随机森林没什么运气。它的表现明显不如之前评估的那些。然而,一旦我们用 BaggingRegressor 模型概述预测,这里介绍的自举抽样的概念将是完整的。
XGBoost
XGBoost 是一个很难简单解释的模型,所以如果读者感兴趣的话,我将在这里的文章(针对初学者)和这里的文章(针对深入研究)中进行解释。基本思想是,类似于随机森林,通过一系列决策树进行估计,最终结果是每个基础估计的一种加权平均值。与随机森林不同,树是按顺序构建的,其中每个后续的树都对之前的树的残差进行建模,希望尽可能地最小化最终残差。采样不是随机的,而是基于先前树的弱点。通过这种方式,通过提升样本来获得结果,而随机森林使用引导聚合,其中每个树和样本都相互独立。这是对引擎盖下真正发生的事情的过度简化,所以如果这种解释不能让你满意,请阅读链接的文章。在这种模型中有许多超参数需要调整,我们可以构建这样一个网格:
xgboost_grid = {
'n_estimators':[150,200,250],
'scale_pos_weight':[5,10],
'learning_rate':[0.1,0.2],
'gamma':[0,3,5],
'subsample':[0.8,0.9]
}
评估模型:
f.set_estimator('xgboost')
f.ingest_grid(xgboost_grid)
f.tune()
f.auto_forecast()

作者图片

作者图片

作者图片
XGBoost 表现优于 MLR,测试集 MAPE 为 12%。然而,它似乎更加过度,样本内 R2 得分接近 100%。
到目前为止,希望您已经了解了这些模型是如何构建和评估的。在提供的笔记本中,您还可以看到 LightGBM (微软的 boosted tree 模型,类似于 XGBoost)、随机梯度下降和 K-最近邻的示例。为了简洁起见,我将跳到打包和堆叠模型来结束这篇文章。
制袋材料
Scikit-learn 的 BaggingRegressor 使用了本文随机森林部分介绍的相同的采样概念,但不是每个底层估计器都是决策树,我们可以使用任何我们喜欢的模型。在这种情况下,我指定了 10 个多层感知器神经网络模型,每层 100 个单元,以及 LBFGS 解算器。允许每个数据子集使用原始数据集大小的 90%来对观察值进行采样,还可以随机使用原始数据集的 50%的特征。在代码中,它看起来像这样:
f**.**add_sklearn_estimator(BaggingRegressor,'bagging')
f**.**set_estimator('bagging')
f**.**manual_forecast(
base_estimator **=** MLPRegressor(
hidden_layer_sizes**=**(100,100,100),
solver**=**'lbfgs'
),
max_samples **=** 0.9,
max_features **=** 0.5,
)

作者图片

作者图片

作者图片
这个模型迄今为止表现最好,测试集 MAPE 为 11%,测试集 R2 得分为 79%。从 XGBoost 和 MLR 这两个指标来看,这比第二好的模型要好得多。
堆垛
最后一个模型是 StackingRegressor。它创建了一个新的估计器,该估计器使用来自其他指定模型的预测来创建最终预测。它使用传递给final_estimator 参数的估计量进行最终预测。

作者图片
在我们的示例中,我们堆叠了四个模型,这些模型评估了该数据集上的最佳样本外数据:
- k-最近邻(KNN)
- XGBoost
- LightGBM
- 随机梯度下降
最后一个估计量是我们在上一节定义的 bagging 估计量。

作者图片
使用 BaggingRegressor 作为最终估计器的优点是,即使 KNN 和 XGBoost 模型高度过拟合,MLP 模型也应该相应地在概念上人为地将它们的预测加权,因为这个模型只使用任何给定 MLP 模型的一半特征输入来训练,它也应该学会信任来自两个没有过拟合的模型的预测:LightGBM 和 SGD。因此,该模型被评估为:

作者图片

作者图片

作者图片
增加模型的复杂性并不总是会改善结果,但这似乎是正在发生的事情。我们的堆叠模型明显优于其他模型,测试集 MAPE 为 10%,测试集 R2 得分为 83%。它还具有与评估的 MLR 相当的样本内指标。
回溯测试
为了进一步验证我们的模型,我们可以对它们进行回溯测试。这是一个迭代地评估它们在最后 n 个预测范围内的准确性的过程,以查看如果该模型被实施、仅在每个预测范围之前的观测值上被训练,实际上会实现什么结果。默认情况下,scalecast 选择 10 个预测时段,其长度由对象中生成的未来日期数决定(在我们的示例中为 60)。下面是它在代码中的样子:
f**.**backtest('stacking')
f**.**export_backtest_metrics('stacking')

作者图片
这告诉我们,平均而言,使用 60 天的预测长度,我们的模型将获得 7%的 MAPE 分数和 76%的 R2 分数。这是一个很好的方法来验证模型不仅仅是幸运地得到了特定的测试集,而且实际上可以推广到看不见的数据。
结论
这篇文章介绍了几种用于预测的建模概念,包括线性方法(MLR、Lasso、Ridge)、树集合(Random Forest、XGBoost、LightGBM)、装袋和堆叠。通过提高应用于它的非线性方法的复杂性,并使用巧妙的采样方法,我们在一个有大约 400 个观察值的测试集上获得了 10%的测试集 MAPE 分数。我希望这个例子能够激发你自己的一些想法,我很感谢你的参与!如果你觉得这个概述有帮助,在 GitHub 上给 scalecast 包打个星吧。
https://github.com/mikekeith52/scalecast
不同公共数据集上 SOTA 半监督学习 NLP 算法的经验
在 NLP 中,我们如何处理少量的训练数据?半监督学习——拯救我们!

来源:此处见。
半监督学习是机器学习领域中一个活跃的研究领域。它通常用于通过利用大量未标记的数据(即输入或特征可用但基础事实或实际输出值未知的观察值)来提高监督学习问题的概化能力(即基于提供的输入和基础事实或每个观察值的实际输出值来训练模型)。
实验设置
我们有以下两个不同的数据集(从分类的角度来看,以复杂性递增的顺序排列):
IMDB 评论数据:由斯坦福主持,这是一个电影评论的情感(二元-正面和负面)分类数据集。更多详情请参考此处。
20Newsgroup 数据集:这是一个多类分类数据集,其中每个观察都是一篇新闻文章,并标有一个新闻主题(政治、体育、宗教等)。这些数据也可以通过开源库 scikit-learn 获得。关于这个数据集的更多细节可以在这里阅读。
我们已经进行了实验,以观察这些算法在低、中和高数据量下对未标记数据的表现。我们已经了解了 sklearn 的 SelfTrainingClassifier,它作为一个包装器函数来支持任何基于 sklearn 的算法的自我训练。我们已经看到了简单逻辑回归模型和 ANN(人工神经网络)模型的自我训练前后结果的比较。
在本文的第部分-1 中,对该领域中使用的一些初始方法进行了彻底的讨论——随后是更复杂的第部分-2 部分,您可以从中了解如何在实践中应用这些方法。
在这一部分,我们将介绍一些最新的半监督学习(SSL)方法,以及它们在我们的实验中的表现。我们先来看看一个先进的 NLP 模型——比如 LSTM/CNN(是的,你没看错!!CNN 可以是一个很好的 NLP 模型。下一节将详细介绍)或像 BERT 这样的先进(SOTA)模型在半监督学习(SSL)环境中执行。然后,我们继续讨论这一领域的最新方法——UDA 和 mixt,它们采用了一些新颖的思想来解决 SSL 问题。最后,我们对这些算法进行了详细的比较,并提出了进一步的研究方向。
让我们开始吧!
LSTM
我们已经看到 LSTMs 从一开始就扰乱了 NLP 空间。因此,我们认为使用 LSTM 将是下一个合乎逻辑的步骤。这篇文章很好地提供了 LSTMs 和 rnn 的简史和用法。
由于 LSTM 是一个序列模型,tf-idf 嵌入帮助不大。因此,我们决定使用手套嵌入,它可以作为顺序输入提供给模型。我们使用了一个相当简单的架构来创建 LSTM 模型。它由三个 LSTM 层组成,每个层后面都有一个下降层。最后,我们有一个密集层,后面是输出层。
以下代码显示了该架构的外观:
请注意,本系列中的所有代码都可以在这个 github 页面上找到。
官方[sklearn.semi_supervised](https://scikit-learn.org/stable/modules/classes.html#module-sklearn.semi_supervised)。SelfTrainingClassifier 不适用于模型中包含功能模块的 TensorFlow 组件。在这种情况下,我们将使用 scikeras.wrappers 中的 KerasClassifier,它的工作方式与 SelfTrainingClassifier 完全相同,并且支持 TensorFlow 模型的功能模块。
我们在这一点上没有包括 LSTM 的结果,因为即使是相对较浅的架构,运行它也需要相当长的时间,甚至性能也无法与 CNN 模型相提并论——这是我们将在下一节中看到的。
美国有线新闻网;卷积神经网络
现在,像 CNN 这样主要用于计算机视觉的模型在我们的 NLP 实验中做什么呢?人们已经尝试使用这种方法,并取得了巨大的成功。参见这篇论文,了解关于此类工作的更多细节。
回到我们的问题,我们认为尝试 CNN 是值得的,原因有二:
- 与 CNN 相比,LSTMs 执行起来相对较慢。
- 在许多使用案例中,CNN 的性能几乎与 LSTMs 一样好,甚至更好。
我们使用相同的手套嵌入和训练我们的 CNN。参见这篇综合文章,了解如何对文本数据应用 CNN 的更多细节。
这里有一段代码可以帮助你理解我们的 CNN 架构:
我们使用了我们在 LSTM 实验中使用的来自 scikeras.wrappers 的相同的 KerasClassifier。
我们已经看到 CNN 的表现至少和 LSTM 一样好,有时甚至更好!运行时间也大大减少了。
结果如下:

CNN 关于 IMDB 数据

CNN 新闻组数据
伯特
如果没有试用 BERT,这些天的任何 NLP 模型构建练习都是不完整的。BERT 是一个预训练的语言模型,由谷歌在 2018 年推出,从那时起,它扰乱了 NLP 空间。它使用一组具有编码器-解码器架构的转换器,并使用一种称为掩蔽语言模型(MLM) 的新技术在大量数据上进行训练。如果这些听起来不熟悉,你可以通过这篇文章来更深入地理解 BERT 背后的思想和机制。
在 BERT 之后,出现了许多预训练模型的变体,它们具有不同的架构,并使用不同的技术进行训练。总而言之,像 BERT 这样的预训练模型携带了大量关于它们被训练的语料库的信息和知识。这些模型唯一的缺点是它们需要大量的 GPU 形式的计算能力。
我们不需要为 BERT 单独使用任何单词嵌入,因为它产生的 BERT 嵌入在上下文嵌入的意义上优于 GloVe、单词到 vec 和其他嵌入..即根据出现的上下文,同一个单词可以有不同的嵌入。例如:
- 苹果是我最喜欢的水果。
- 我买了一部 苹果 iPhone。
上面两个句子中的单词“apple”在不同的上下文中会有不同的 BERT 嵌入。
由于 BERT 是一个已经预先训练好的模型,我们不再从头开始训练它。但是我们会微调它。微调 BERT 是一种受监督的方法,包括传递一些带标签的训练示例,之后,它将准备好为我们的测试数据给出标签。
[出于本文的目的,我们在 BERT 的上下文中交替使用训练和微调这两个词。]
出于训练/微调的目的,我们使用了一个非常用户友好的 BERT API,名为 sklearn-bert 。这是一个无附加条件的 API,我们可以像使用其他 sklearn 模型一样使用它。这就是我们安装 sklearn-bert 的方法。
下面的代码片段显示了如何训练 sklearn-bert:
由于我们的计算限制,我们将序列长度和 batch_size 分别限制为 128 和 16。我们使用了最大序列长度为 512 的“bert-base-uncased”模型。我们可以只做一个模型。fit()然后瞧!我们已经完成了微调。我们可以做一个 model.predict()来获得预测的标签。
这种 API 的优点是,我们不需要手动对 BERT 进行任何类型的标记化或最后一层添加——如果我们使用 PyTorch 或 TensorFlow 训练 BERT,就必须这样做。
现在,我们如何在这个 BERT 模型上做 SSL 呢?因为这是一个 sklearn 模型,所以我们认为可以使用以前使用过的来自 scikeras.wrappers 的相同的 KerasClassifier。但不幸的是,这并没有奏效。我们认为这是因为 BertClassifier 没有实现我们的 KerasClassifier 需要的所有 sklearn 方法。因此,我们必须自己实现 SSL 逻辑——这相当简单。查看下面的代码片段,了解我们的 BERT SSL 实现:
让我们快速回顾一下我们是如何实现 SSL 算法的。
X_u 表示未标记的数据集。迭代次数— num_iter 最多是 X_u 和 kbest 值的比值。
我们做一个 model.fit(),然后使用模型进行预测。然后,我们选择概率最大的预测作为最终的模型输出。
然后,我们基于 n_to_select 对其进行划分,这是 kbest 值和 X_u. 大小中较小的一个,它与已经存在的 new_X 数据集连接在一起。最后一步,不管 kbest 值是多少,我们都将剩下的内容(在if not _ selected _ update = = ' yes ':子句中完成)添加到数据中。
下面是我们使用 SSL 和 BERT 获得的结果:

IMDB 数据上的 BERT

新闻组数据上的 BERT
由于内存和计算资源的限制,我们无法使用 BERT 来处理新闻组数据集中更多的标记数据。
无监督数据增强(UDA)
到目前为止,我们所做的一切只涉及改变模型和尝试不同的架构,以提高性能。但是现在,我们从根本上改变了处理这个问题的方式。
在 UDA 中,我们使用数据扩充和一致性训练来进一步提高现有性能,而不是相同的 SSL。我们现在将简要讨论 UDA 是如何工作的,特别是在 NLP 的环境中。
无监督的数据增强试图通过使用一组数据增强来有效地增加标记数据的数量,如反向翻译、单词替换等。
反向翻译
反向翻译把一个英语句子翻译成不同的语言——比如说,俄语、德语、汉语等等,然后再把它们翻译回英语,这样我们就会得到一个重新措辞的句子,意思相同,因此标签也相同。这些回译可以通过使用任何机器翻译模型作为服务来完成。
Sentence in English — S, Rephrased sentence — S”.S’ = EnglishToGerman(S);S” = GermanToEnglish(S’).
我们可以控制回译的质量,得到不同种类的重组句子,它们的结构和措辞略有不同,但意思相似。在下图中,我们可以看到一个句子的三种不同的回译。

一个反向翻译的例子。【图片来源
单词替换
单词替换是另一种数据扩充方法。我们通过从整个词汇表中取样单词来替换具有低 TF-IDF 分数的句子中的单词。这可能有助于生成多样且有效的示例。
UDA 使用标记数据和扩充的未标记数据进行训练。我们可以使用任何通用的 NLP 模型来执行 UDA,只需稍微改变损失函数。下图显示了损失函数是如何调整的:

UDA [ 图像源概述
损失函数有两个部分:有标记数据的监督交叉熵损失,这是传统的损失函数,以及无标记数据的 无监督一致性损失 。
直观地说,无监督的一致性损失做了一件非常简单的事情:它迫使所有增强的例子具有相同的标签。这似乎是合理的,因为我们知道,我们所做的扩充给了我们具有相同含义的重组句子,因此,相同的标签。
我们有一个稍微更新的 UDA 版本——叫做 mixt,这就是我们已经实现的。现在就来看看吧。
混合文本
mixt最初做的正是我们在 UDA 中讨论的事情——接受标记数据和未标记数据,进行扩充,并预测未标记数据的标签。但是在应用交叉熵和一致性损失之前,它执行了另一个称为 TMix 的增强。
TMix
TMix 提供了一种扩充文本数据的通用方法,因此可以独立地应用于任何下游任务。TMix 在文本隐藏空间做— 插值。已经表明,从两个隐藏向量的插值中解码生成具有两个原始句子的混合含义的新句子。
对于具有 L 层的编码器,我们选择在第 m 层混合隐藏表示,其中 m ∈ [0,L]。简单来说,TMix 取两个带有标签 y 和 y* 的文本样本 *x 和 x ,将它们在层 m 的隐藏状态表示 h 和 h` 混合到h"中,然后继续向前传递以预测混合标签y"。混合隐藏状态(带有参数λ)的情况如下:
实验表明,一些特定的 BERT 层{3,4,6,7,9,12}最具代表性。基于此,mixt 的作者选择了一些随机子集,并且{7,9,12}给出了最佳结果,因此,这就是我们所使用的。
运行 MixText 要求我们按照他们的 GitHub 页面上的说明进行操作。我们必须稍微修改 code/read_data.py 文件中的代码,以便读取我们的 IMDB/新闻组数据格式。
结果如下:

IMDB 数据上的 mixt 与 BERT
最终比较
我们可以看到,BERT 和 CNN 在 SSL 设置中表现得相当好。由于没有明确的赢家,我们可以根据手头的任务来测试和选择它们。
在我们固定的序列长度下,我们无法观察到 mixt 和 BERT+SSL 之间的任何显著差异。作者提到的结果可能是因为在更好的计算资源的帮助下,使用了更长的序列长度和更有效和多样的扩增。
包括本文在内的 3 部分系列是由 Pilani-IN 和热情的 NLP 数据科学家 Sreepada Abhinivesh 共同完成的,Naveen Rathani 是一位拥有 BITS 硕士学位的应用机器学习专家和数据科学爱好者。
10 分钟内使用 MLflow 进行实验跟踪
原文:https://towardsdatascience.com/experiment-tracking-with-mlflow-in-10-minutes-f7c2128b8f2c
轻松管理机器学习生命周期——用 Python 示例解释

艾萨克·梅海根在 Unsplash 上拍摄的照片
Databricks MLflow 是一个管理机器学习生命周期的开源平台。通俗地说,它可以跟踪和存储数据、参数和指标,以便以后检索或在 web 界面上很好地显示。这在实验或 A/B 测试中非常有用,因为跟踪超参数和评估指标对于透明度和再现性非常重要。MLflow 还可以实现集中的模型治理,并鼓励协作,因为它是一个集中的模型库。
本文将涉及 MLflow 中使用的组件和术语,如何设置、跟踪和查询 MLflow 的 Python 示例,以及如何启动 MLflow web 界面。
更新 :本文是系列文章的一部分。查看其他“10 分钟内”话题 此处 !
目录
什么是 MLflow
MLflow 是一个管理机器学习(ML)生命周期的平台,包括 ETL、特征工程、培训、评分和监控模型。MLflow 可以集成到 ML 生命周期的任何阶段,这取决于用户想要跟踪的内容。
MLflow 有 4 个组件,它们可以独立使用。
- MLflow Tracking :使用代码或 web 界面记录和查询实验(代码、数据、配置、超参数、评估指标、结果)
- MLflow 项目:代码打包格式,可在任何平台上重复运行,结合 Git 进行源代码控制,结合环境进行依赖跟踪
- MLflow 模型:支持多样化批量和实时评分的模型封装格式
- MLflow 模型注册中心:支持和管理 ML 生命周期的集中式模型存储、一组 API 和 web 界面
物流跟踪的组件
MLflow 跟踪可能是数据科学家最常用的工具,本文将重点介绍使用 MLflow 跟踪的实验跟踪。可以跟踪几个项目,
- 参数:输入参数的键值对,即随机森林模型的深度
- 指标:评估指标,即 RMSE、ROC-AUC
- 工件:任意格式的任意输出文件,包括图像、酸洗模型、数据文件
- Source :原始代码,如果链接到 GitHub,包括提交散列
给一个预览,上面所有的项目都可以用一行代码来跟踪!在后面的章节中,我们将更深入地探讨如何将下面的代码集成到代码库中,以及如何使用代码和 UI 来查询被跟踪的项目。
# Track parameters
mlflow.log_param("max_depth", 20)
# Track metrics
mlflow.log_metric("rmse", 0.5)
# Track artifact
mlflow.log_artifact("/path/graph.png", "myGraph")
# Track model (depends on model type)
mlflow.sklearn.log_model(model, "myModel")
mlflow.keras.log_model(model, "myModel")
mlflow.pytorch.log_model(model, "myModel")
安装 MLflow
可以使用以下命令将 MLflow 安装为 Python 包,
$ pip install mlflow
现在你可以去追踪一些东西了!
将 MLflow 与 Python 结合使用
为了与现有的代码库集成,有两个更重要的术语很重要——实验和运行。实验用于查询和比较不同的运行,通常是项目级的设置。另一方面,run 只是指一次运行,多次运行可以标记到同一个实验中。
请注意,有一个实验 ID 和实验名称、运行 ID 和运行名称不能混淆的概念!
用相同的 运行名 初始化 MLflow 被认为是不同的运行,跟踪的项目将分开存储,而用相同的 运行 id 初始化 MLflow 将一起存储跟踪的项目。如果您在同一运行的代码库的多个部分中使用 MLflow,建议检索运行 id 并使用运行 id 在后面的部分中初始化 MLflow。
使用 MLflow 进行跟踪
正如引言中提到的,MLflow 可以在任何阶段集成到 ML 生命周期中。让我们看一个例子,其中使用简单的 Iris 数据集在数据科学工作流中跟踪参数、指标和模型。
从上面的代码中,我们在第 14 行定义了EXPERIMENT_NAME,在第 15 行检索了EXPERIMENT_ID。实际的跟踪从第 25 行开始,其中记录了不同运行的参数、度量和模型。通过这样做,MLflow 在您的代码库中创建了一个名为mlruns的文件夹,可以使用代码或 MLflow web 界面查询该文件夹。
使用 MLflow 查询
可以使用代码或 MLflow 交互式 web 界面访问跟踪的项目。使用代码访问提供了一种更自动化的方法来检索最佳模型或最高度量分数,因为它可以立即集成到代码中,绕过 web 界面。
为了查询被跟踪的项目,通常的做法是我们检索与实验的单次运行相关的数据,并通过运行 id 获得最佳运行信息。使用最佳运行信息,我们可以查询被跟踪的项目,即检索参数,下载模型工件。下面是查询 MLflow 项目的代码片段,上接上一节的数据科学工作流示例。
启动 Web 界面
可以在http://localhost:5000访问 MLflow web 界面,并使用以下命令启动。
$ mlflow ui

图 MLflow web 界面的主页—作者图片
交互式网络界面允许您在侧面板上查看实验,并在一页上以表格形式显示与实验相关的所有运行信息。

图 2:查看一个运行信息—按作者排序的图像
单击其中一个运行,除了跟踪的项目之外,您还可以查看该运行的更多元数据,例如运行时间、运行状态和生命周期阶段。如果您不想对其进行编码,也可以在这里手动添加或更改描述或标签!在工件下,这次运行生成了一些文件,如MLmodel、conda.yaml和requirements.txt。这些文件现在并不重要,除非您计划使用 MLflow Projects 组件,例如,它在一次运行中将所有项目打包在一起以便发送部署。

图 MLflow 上的搜索结果—按作者排序的图片
如果需要,您还可以使用搜索栏来查询满足特定参数和/或指标标准的运行!如果你想知道你最后一次使用某个参数是在什么时候,而没有经历所有的运行,这是非常有用的。
希望您已经了解了 Databricks MLflow 的基础知识,以及如何通过 Python 和 MLflow web 界面设置和使用 MLflow。在此介绍之后,您应该能够更好地跟踪和理解 MLflow 文档、论坛或其他文章。
感谢您的阅读!如果你喜欢这篇文章,请随意分享。
相关链接
MLflow 文档:https://www.mlflow.org/docs/latest/index.html
MLflow 教程:https://docs . databricks . com/applications/ml flow/quick-start-python . html
MLflow 官方 GitHub:https://github.com/mlflow/mlflow
投资回报实验
原文:https://towardsdatascience.com/experiments-on-returns-on-investment-34a1953c5f16
因果数据科学
比率度量推断的 delta 方法介绍。

封面图片,由作者使用nightcafe生成
当我们进行一项实验时,我们通常不仅对一项治疗(新产品、新功能、新界面……)对收入的影响感兴趣,还对其成本效益感兴趣。换句话说,投资是否值得?常见的例子包括计算资源投资、广告回报、点击率和其他比率指标。
当我们调查因果关系时,金标准是随机对照试验,也就是 AB 测试。将治疗随机分配给人群的一个子集(用户、患者、顾客等),我们确保平均而言,结果的差异可以归因于治疗。然而,当感兴趣的目标是成本效益时,AB 测试提出了一些额外的问题,因为我们不仅对一种治疗效果感兴趣,而且对两种治疗效果的比率感兴趣,即投资的结果超过其成本。
在本帖中,我们将看到当感兴趣的对象是投资回报率(ROI) 时,如何分析随机实验。我们将探索衡量投资是否有回报的替代指标。我们还将介绍一个非常强大的复杂度量推断工具:delta 方法。虽然代数可能很密集,但结果很简单:我们可以使用简单的线性回归来计算比率估计量的置信区间。
投资云计算
为了更好地说明这些概念,我们将在整篇文章中使用一个玩具示例:假设我们是一个在线市场,我们想要投资云计算,我们想要通过切换到一个更高层的服务器来提高我们内部搜索引擎的计算能力。这个想法是,更快的搜索将改善用户体验,潜在地导致更高的销售额。所以,问题是:投资值不值这个成本?感兴趣的对象是投资回报率。

作者使用nightcafe生成的图像
与通常的 AB 测试或随机实验不同,我们对单一的因果影响不感兴趣,而是对两个指标的比率感兴趣:对收入的影响和对成本的影响。我们仍将使用随机对照试验或 AB 测试来估算投资回报率:我们将用户随机分配到治疗组或对照组。接受治疗的用户将受益于更快的云机器,而控制用户将使用旧的较慢的机器。随机化确保我们可以通过比较治疗组和对照组中的用户来估计新机器对成本或收入的影响:平均值的差异是平均治疗效果的无偏估计值。然而,事情因为它们的比例而变得更加复杂。
我从[src.dgp](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/dgp.py)导入数据生成过程dgp_cloud()。关于以前的文章,我生成了一个新的 DGP 父类,它处理随机化和数据生成,而它的子类包含特定的用例。我也从[src.utils](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/utils.py)导入绘图函数和库。为了不仅包括代码,还包括数据和表格,我使用了 Deepnote ,一个类似 Jupyter 的基于网络的协作笔记本环境。
该数据包含一个月内一组 10.000 个用户的总数cost和revenue的信息。我们也有关于治疗的信息:搜索引擎是运行在旧的还是new machines上。正如商业指标经常发生的那样,成本和收入的分布都非常不均衡(T21)。此外,大多数人不购买任何东西,因此产生零收入,即使他们仍然使用该平台,产生正成本。

成本和收入分布,按作者分类的图像
我们可以通过回归治疗指标的结果来计算cost和revenue的均值差估计值。
平均每个用户增加了 0.5152 美元。营收呢?
每个用户的平均revenue也增加了 1.0664 美元。那么,这笔投资盈利了吗?
要回答这个问题,我们首先必须决定使用哪个指标作为我们的结果指标。在比率度量的情况下,这不是微不足道的。
平均回报还是平均回报?
这是一个非常诱人的解决问题的方法:的确,我们有两个变量,所以我们可以计算它们的比率,然后像往常一样,使用一个单变量:个人水平回报来分析一切。

个人回报,作者图片
如果我们使用这个单一的度量来分析实验,会发生什么?
估计效果为负且显著,-0.7392!新机器似乎不是一项好的投资,回报率下降了 74%。
这个结果似乎与我们先前的估计相矛盾。我们之前已经看到,收入的平均增长超过了成本(1.0664 美元对 0.5152 美元)。为什么会这样呢?问题是我们给重度用户和轻度用户的权重是一样的。让我们用一个简单的有两个用户的例子。第一个(蓝色)是轻度用户,以前花费 1 美元并返回 10 美元,而现在花费 4 美元并返回 20 美元。另一个用户(violet)是一个重度用户,以前花费 10 美元并返回 100 美元,现在花费 20 美元并返回 220 美元。

高使用率和低使用率用户示例,按作者排序
平均回报率为-3 倍:平均每个用户的回报率下降了 300%。然而,每个用户的总回报是 1000%:13 美元的成本增加产生了 130 美元的收入!结果大相径庭,完全由两个用户的权重决定:重度用户的影响在相对方面较低,但在绝对方面较高,而轻度用户则相反。因此,平均相对效果主要由轻度用户驱动,而相对平均效果主要由重度用户驱动。
哪个指标与我们的设置更相关?当谈到投资回报时,我们通常感兴趣的是了解我们所花的钱是否得到了回报。所以总收益比平均收益更有意思。
从现在开始,感兴趣的对象将是投资回报率(ROI) ,由预期收入增长超过预期成本增长给出,我们将用希腊字母 rho, ρ 表示。

投资回报,作者图片
我们可以估计ROI 为之前两次估计的比率:治疗组和对照组之间的平均收入差异,与治疗组和对照组之间的平均成本差异(差异用希腊字母 delta 表示)。

投资回报的估算者,图片由作者提供
请注意与前面的公式有一个微妙但至关重要的差异:我们用经验期望运算符𝔼ₙ替换了期望值 𝔼,也称为样本平均值。符号上的差异很小,但概念上的差异是巨大的。第一个,𝔼,是一个理论上的概念,而第二个,𝔼ₙ,是经验上的:它是一个依赖于实际数据的数字。我个人喜欢这个符号,因为它强调了两个概念之间的紧密联系(第二个概念是第一个概念的经验对应),同时也清楚地表明第二个概念取决于样本大小 n 。
估计值为 2.0698:在新机器上每多花 1 美元,收入就多 2.0698 美元。听起来很棒!
但是我们应该在多大程度上相信这个数字呢?是和一个显著不同,还是只是被噪音驱动?
推理
为了回答这个问题,我们想计算一个 置信区间 来进行我们的估计。我们如何计算比率度量的置信区间?第一步是计算估计量的标准差。一种始终可用的方法是 bootstrap :通过多次替换对数据进行重新采样,并使用样本上估计值的分布来计算估计值的标准偏差。
让我们在自己的案例中尝试一下。我计算了 10.000 个引导样本的标准偏差,使用带有选项frac=1的函数pd.DataFrame().sample()获得相同大小的数据集,并使用选项replace=True进行替换采样。
标准差的 bootstrap 估计等于 0.979。有多好?
由于我们完全控制数据生成过程,我们可以模拟估计量的“真实”分布。我们对 10.000 次模拟进行此操作,然后计算估计量的标准偏差。
使用“真实”数据生成过程的估计量的估计方差略高,但非常相似,约为 1.055。
bootstrap 的问题是它的计算量非常大,因为它需要重复估计过程数千次。我们现在要探索另一个极其强大的替代方案,它只需要一个估计步骤,即 增量法 。delta 方法通常允许我们对随机变量的函数进行推断,因此它的应用比比率更广泛。
⚠️ 警告:下一部分将会是代数密集型的。如果你愿意,你可以跳过它,直接进入最后一节。
德尔塔法
什么是 delta 法?简而言之,对于随机变量函数来说,这是一个非常强大的渐近推断方法,它利用了泰勒展开式。简而言之,德尔塔法需要四个要素
我将假设所有四个概念的一些基本知识。假设我们有一组满足中心极限定理(CLT) 要求的随机变量的实现 X₁、…、Xₙ:独立性,期望值同分布 μ ,有限方差 σ 。在这些条件下,CLT 告诉我们,样本平均𝔼ₙ[X]在分布上收敛于正态分布,或者更准确地说

中心极限定理,作者图片
等式是什么意思?它表示“归一化样本平均值,按系数√n 缩放,在分布上收敛于标准正态分布,即对于足够大的样本,它近似为高斯分布。
现在,假设我们对样本平均值 f 的函数感兴趣(𝔼ₙ[X]).注意,这不同于函数𝔼ₙ[ f (X)】的样本平均值。delta 方法告诉我们样本平均值的函数在分布中收敛到什么。

Delta 方法,图片由作者提供
其中f’(μ)是函数 f 的导数,在 μ 处计算。
这个公式背后的直觉是什么?我们在方差的表达式里面有一个新的项,平方一阶导数f’(μ)(≠二阶导数)。如果函数的导数很低,方差就会减小,因为不同的输入会转化为相似的输出。相反,如果函数的导数很高,则分布的方差会放大,因为不同的输入会转化为更多不同的输出。

德尔塔法背后的直觉,作者图片
这个结果直接来自泰勒近似的 f (𝔼ₙ[X]):

一阶泰勒展开,作者图片
重要的是,渐近地,最后一项消失,线性近似完全成立!
这与比率估算器有什么联系?为了理解这一点,我们需要更多的数学知识,从一维转换到二维。在我们的例子中,我们有两个随机变量的二元函数,δR和δC,它返回它们的比率。在多元函数 f 的情况下,估计量的渐近方差由下式给出

ROI 估计值的渐近方差,图片由作者提供
其中,∇表示函数的梯度,即方向导数的向量,σₙ是 x 的经验方差-协方差矩阵,在我们的例子中,它们对应于

ROI 估计值的梯度,图片由作者提供
和

经验方差-协方差矩阵,图片由作者提供
,其中下标 n 表示经验对应物,至于期望值。
将前面三个方程与一点矩阵代数结合起来,我们得到了投资回报估计量的渐近方差公式。

ROI 估计值的渐近方差,图片由作者提供
由于估计量由ρ̂=𝔼ₙ[δr/𝔼ₙ[δc给出,我们可以将渐近方差改写为

ROI 估计值的渐近方差,图片由作者提供
最后一个表达式非常有趣,因为它表明我们可以将估计量的渐近方差重写为新辅助变量的均值差异估计量的方差。事实上,我们可以将上面的表达式重写为

ROI 估计值的渐近方差,图片由作者提供
这个表达式非常有用,因为它给了我们直觉,允许我们通过线性回归来估计估计量的标准偏差。
线性回归推断
你跳过前一部分了吗?没问题!
经过一些代数运算后,我们得出结论,估计 ROI 估计量 ρ̂ 的方差等价于估计辅助变量r̃的均值差异估计量的方差,定义为

辅助变量,作者图片
这个表达式起初可能看起来晦涩难懂,但它非常有用。事实上,它给了我们(1)对估计量方差的直观解释和(2)估计方差的实用方法。
先解读!上面的表达应该怎么读?我们可以将经验估计量的方差估计为均值差异估计量的方差,对于新变量 R̃ ,我们可以很容易地从数据中计算出来。我们只需要获得收入 R ,减去成本 C 乘以估计的投资回报率 ρ̂ 并按预期成本差|𝔼ₙ[δc】将其缩减。我们可以将这个变量解释为基准收入,即不受投资影响的收入。它与预期成本差异成比例的事实告诉我们,它的方差在总投资中是递减的:我们花得越多,我们就能越精确地估计这笔支出的回报。
现在,让我们通过四个步骤来估计 ROI 估计量的方差。
- 我们需要估计投资回报率 ρ̂ 。
2.术语|𝔼ₙ[δ是治疗组和对照组之间平均费用的绝对差异。
3.我们现在拥有了生成辅助变量 R̃ 的所有要素。
4.治疗-对照差异的方差δr̃可以通过线性回归直接计算,如均值差异估计量的随机对照试验(见 Angrist 和 Pischke,2008)。
ROI 的估计标准误差为 0.917,非常接近 bootstrap 估计值 0.979 和模拟值 1.055。然而,关于引导,delta 方法允许我们在一个单一的步骤中计算它,使它明显地更快(在我的本地机器上大约 1000 倍)。
注意,这个估计的标准差意味着 95% 的置信区间为 2.0698 +- 1.96 × 0.917,等于[-0.2735,3.8671]。这似乎是个好消息,因为置信区间不覆盖零。然而,请注意,在这种情况下,一个更有趣的零假设是 ROI 等于 1:我们收支平衡。大于 1 的值意味着盈利,而小于 1 的值意味着亏损。在我们的案例中,我们不能拒绝新机器投资无利可图的无效假设。
结论
在本文中,我们探讨了一个非常常见的因果推断问题:评估投资回报率。无论是新硬件的物理投资、虚拟成本还是广告支出,我们都有兴趣了解这种增量成本是否有回报。额外的复杂性来自于这样一个事实,我们正在研究的不是一个,而是两个相互交织的因果量。
我们首先探索和比较不同的结果指标,以评估投资是否有回报。然后,我们引入了一个非常强大的方法来对复杂的随机变量进行推理:T2 德尔塔法。在比率的特定情况下,delta 方法为估计量的渐近方差提供了一种非常有见地和实用的函数形式,可以通过简单的线性回归进行估计。
参考
[1] A. Deng,U. Knoblich,J. Lu,《在度量分析中应用 Delta 方法:具有新颖想法的实用指南》 (2018)。
[2] R. Budylin,A. Drutsa,I. Katsev,V. Tsoy,高效在线受控实验的比率度量的一致变换 (2018)。 ACM 。
[3] J. Angrist,J. Pischke,大多无害的计量经济学:一个经验主义者的伴侣 (2009)。普林斯顿大学出版社。
相关文章
密码
你可以在这里找到 Jupyter 的原始笔记本:
**https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/delta.ipynb **
感谢您的阅读!
我真的很感激!🤗如果你喜欢这个帖子并且想看更多,可以考虑 关注我 。我定期发布与因果推理和数据分析相关的主题。我尽量让我的帖子简单而精确,总是提供代码、例子和模拟。
还有,一个小小的 免责声明 :我写作是为了学习所以错误是家常便饭,尽管我尽力了。当你发现他们的时候,请告诉我。也很欣赏新话题的建议!
实验、窥视和最佳停止
原文:https://towardsdatascience.com/experiments-peeking-and-optimal-stopping-954506cec665
因果数据科学
如何用序贯概率比检验进行较小样本量的有效实验

封面,作者图片
在第二次世界大战前的十年里,战争物资的工业生产大量增加,因此有必要确保产品,特别是弹药的可靠性。战争物资的测试不仅昂贵而且破坏性因为,例如,子弹需要发射才能被测试。
因此,美国政府面临着如下的困境:在宣布一批子弹可靠之前,一个人应该从一批子弹中射出多少颗子弹?显然,如果我们要发射所有的子弹,我们就会知道一个板条箱中有效子弹的确切数量。然而,没有子弹了。
由于这些统计问题越来越重要,1939 年,一群杰出的统计学家和经济学家在哥伦比亚大学的 统计研究小组(SGR) 联合起来。这个小组包括,除了其他人之外,w·艾伦·沃利斯、雅各布·沃尔福威茨和亚伯拉罕·瓦尔德。根据 Wallis 自己的说法,SGR 小组是由“从数量和质量两方面考虑,肯定是有史以来最非凡的统计学家组成的小组”。
他们的工作非常重要,被列为机密,沃利斯报道说:
据说,当沃尔德进行顺序分析时,他的文件被拿走并被列为机密。作为一名“敌国人”,他没有安全许可,因此,据说,他不被允许知道他的结果。 [ 沃利斯(1980) ]
事实上,该小组在美国陆军的压力下工作,以交付可随时部署在战场上的快速实用解决方案。例如,沃利斯报告说
在 1944 年 12 月的 突出部战役 中,几名高级军官从战场飞到华盛顿,花了一天的时间讨论对地面部队进行空中轰炸的近炸引信的最佳设置,然后飞回战场,将来自其他人的建议付诸实施 米尔顿·弗里德曼 ,他早期对引信的研究使他对引信的实际性能有了广泛而准确的了解沃利斯(1980)
来自 SGR 经验的最突出的结果无疑是顺序概率比测试。沃利斯和弗里德曼首先意识到
使用一种测试可能是值得的,如果取 N 个样本,这种测试可能不如传统测试有效,但当顺序使用时,它提供了一个很好的提前终止的机会,从而大大抵消了这一缺点沃利斯(1980)
这两位经济学家向统计学家雅各布·沃尔福威茨提出了这个想法
“对于像米尔顿和我这样对数学如此无知的人,竟然冒险干涉最强大的统计学等神圣的概念,这似乎有些令人反感。毫无疑问,这种反感因我们称新测试为‘超级色狼’而加强,理由是它们比‘最强大’的测试更强大。” [ 沃利斯(1980) ]
最终,这两位经济学家设法引起了沃尔福威茨和沃尔德的注意,开始正式研究这个想法。这些结果一直是最高机密,直到战争结束,沃尔德发表了他的《统计假设的序列测试》一文。
在这篇文章中,在简单介绍了假设检验之后,我们将探索序列概率比检验,并在 Python 中实现它。
假设检验
当我们设计一个 A/B 测试,或者更一般地说,一个实验时,标准步骤如下
- 定义一个零假设 H₀ ,通常是实验对感兴趣的指标没有影响
- 定义一个显著性水平 α ,通常等于 0.05 ,代表当其为真时拒绝零假设的最大概率
- 定义一个替代假设 H₁ ,通常是我们想要检测的最小效应大小
- 定义一个功率水平 1-β ,通常等于 0.8 (即 β=0.2 ),代表当备选项 H₁ 为真时,拒绝零假设 H₀的最小概率
- 选择一个测试统计量,其分布在两种假设下都是已知的,通常是感兴趣的度量的样本平均值
- 在给定所有测试参数的情况下,计算最小样本大小,以达到期望的功率水平 1-β
然后,我们运行测试,根据测试统计的实际值,我们决定是否拒绝零假设。特别是,如果 p 值,即在零假设下观察到的统计量等于或大于样本统计量的概率,低于显著性水平 α ,我们拒绝零假设。
请记住,拒绝零假设并不意味着接受替代假设。
偷看
假设在实验进行到一半时,我们要偷看数据并注意到,对于检验统计量的中间值,我们会拒绝零假设。我们应该停止实验吗?如果我们做了,会发生什么?
答案是我们不应该停止实验。如果我们这样做,测试将不会达到预期的显著性水平,或者,换句话说,我们的置信区间将会有错误覆盖。
示例
让我们用一个例子来看看我的意思。假设我们的数据生成过程是一个均值 μ 未知,方差σ= 1:X∾N(μ,1) 的标准正态分布。
让我们挑选以下(任意的)假设来测试:

测试假设,作者图片
每次观察 n 后,我们计算 z 检验统计量

z 检验统计,按作者分类的图像
其中 X̅ₙ 是来自大小为 n 的样本 X₁、X₂、…、Xₙ、的样本均值, σ 是总体的标准差, μ₀ 是在零假设下的总体均值。分母中的项是样本均值的方差。在零均值的零假设下,检验统计量分布为具有零均值和单位方差的标准正态分布, N(0,1) 。
让我们用 Python 编写测试代码。我从实用程序中导入了一些代码,让情节更漂亮。
import numpy as np
from src.utils import *
zstat = lambda x: np.mean(x) * np.sqrt(len(x))
zstat.__name__ = 'z-statistic'
假设我们想要一个显著性水平 α=0.05 和功效 1-β=0.8 的测试。我们需要多大的样本量?
我们需要一个样本大小
- 当 H₀ 为真时,拒绝零假设 H₀ 的概率最多为 α=0.05
- 当 H₀ 为假(即 H₁ 为真)时,不拒绝零假设 H₀ 的概率,最多为 β=0.2
即我们需要找到一个临界值 c 使得

临界值方程,作者图片
其中 zₚ 是在 p 处的 CDF 倒数(或百分点函数),而 μᵢ 是不同假设下的平均值。
如果我们不知道未知平均值 μ 的符号,我们必须运行一个双边测试。这意味着,在分布的每一侧,类型 1 错误的最大概率必须是 α/2 = 0.025 ,这意味着 z ₀.₉₇₅.
将这两个表达式结合在一起,我们可以求解出所需的最小样本量。

最小样本量公式,作者图片
以便

最小样本量方程求解,图片由作者提供
我们至少需要 785 次观察。
我们可以通过图形化绘制带有临界值的两个分布来获得更好的直觉。我写了一个函数[plot_test](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/figures.py)来画一个标准的假设检验设置。
from src.figures import plot_test
plot_test(mu0=0, mu1=0.1, alpha=0.05, n=n)

假设检验,作者图片
临界值是这样的,给定两种假设下的分布,红色的拒绝面积等于 α 。样本大小 n 缩小了两个分布的方差,因此绿色区域等于 β 。
现在让我们模拟一个实验,在这个实验中,我们绘制一个有序的观察序列,在每次观察之后,我们计算检验统计的值。
让我们看看样品是什么样的。
df = experiment(zstat)
df.head()

数据快照,图片由作者提供
现在,随着我们在采样过程中积累观察值,我们可以绘制检验统计量的时间趋势图。我还用横线标记了在 α=0.05 的测试中拒绝零假设的值。
plot_experiment(df, ybounds=[-1.96, 1.96])

使用顺序抽样的测试统计,按作者分类的图像
在这种情况下,测试不会越过临界值。所以偷看是没有效果的。我们不会过早地停止实验。
如果我们多次重复这个实验,会发生什么?由于数据是在零假设下生成的, H₀: μ=0 ,我们预计只在 α=5% 的时候拒绝它。
让我们模拟数据生成过程 K=100 次。
我们绘制了样本的 z 统计分布图。
simulate_experiments(zstat, ybounds=[-1.96, 1.96], early_stop=False);Bounds crossed: 3 (67% upper, 33% lower)
Average experiment duration: 785

超过 100 个模拟的连续抽样的测试统计,图片由作者提供
在上图中,我突出显示了我们拒绝零假设而不偷看的实验,即在采样过程结束时给出 z 检验统计量的值。只有在 3 个实验中,最终值位于临界值之外,因此我们拒绝零假设。这意味着 3%的拒绝率非常接近预期拒绝率 α=0.05 (在零假设下)。
相反,如果我们不耐烦并且在收集了第一个 100 个观察值之后,一旦我们看到 z 统计量越过边界,我们就停止会怎么样?
stops_zstat_h0 = simulate_experiments(zstsat, xmin=99, ybounds=[-1.96, 1.96], early_stop=True, lw=2);
plt.vlines(100, ymin=plt.ylim()[0], ymax=plt.ylim()[1], color='k', lw=1, ls='--');Bounds crossed: 25 (48% upper, 52% lower)
Average experiment duration: 523

测试统计超过 100 个模拟,图片由作者提供
在上图中,我突出显示了从第 100 次观察开始 z 统计值跨越其中一个边界的实验。这发生在 100 的 25 模拟中,这意味着的拒绝率为 25% ,这与 α=0.05 的预期拒绝率(在零假设下)相差甚远。偷看会扭曲测试的显著性水平。
一个潜在的解决方案是序贯概率比检验。但是,为了理解它,我们首先需要介绍一下似然比检验。
似然比检测
似然比检验是一种试图评估观察数据由两个竞争统计模型之一生成的概率(或似然性)的检验。
为了执行假设检验的似然比检验,我们需要在两种假设下完全指定数据生成过程。例如,以下假设就是这种情况:

测试假设,作者图片
在这种情况下,我们说统计检验是完全指定的。如果替代假设是 H₁: μ ≠ 0 ,那么数据生成过程将不会在替代假设下指定。
当一个统计检验被完全指定时,我们可以将似然比计算为两个假设下似然函数的比值。

似然比,按作者分类的图像
似然比检验提供了如下决策规则:
- 如果λ>c,则拒绝h₀;
- 如果λ<c,不要拒绝h₀;
- 如果λ= c,以概率 q 拒绝
选择值 c 和 q 以获得指定的显著性水平 α 。
ney man–Pearson 引理 指出,在这种情况下,这种似然比检验是所有水平 α 检验中最有效的。
特例:正态分布均值检验
让我们回到我们的示例,其中数据来自具有未知均值 μ 和已知方差 σ 的正态分布,我们希望执行以下测试

测试假设,作者图片
具有未知均值 μ 和已知方差 σ 的正态分布的可能性为

可能性,按作者分类的图像
因此,两种假设下的似然比是

可能性比率,按作者分类的图像
我们现在已经具备了进入这篇博文的最后阶段的所有要素:顺序概率比测试。
序贯概率比检验
给定一对完全指定的假设,比如说 H₀ 和 H₁ ,序贯概率比检验的第一步是计算对数似然比检验log(λᵢ,随着新数据的到来:用 S₀=0 ,那么,对于 i=1,2,…,n 。

公式,作者图片
我们现在可以使用 Sᵢ 来生成一个简单的阈值方案:
- Sᵢ≥ b :接受 H₁
- Sᵢ≤答:接受 H₀
- a < Sᵢ < b :继续监控(临界不等式)
其中阈值 a 和b(-∞<a<0<b<∞)应取决于期望的I 型和 II 型误差、 α 和 β 。我们应该如何挑选 a 和 b ?
Wald (1945) 表明,选择以下边界进行测试时,第 1 类和第 2 类错误的预期概率分别不大于 α 和 β 。

最佳 alpha 和 beta,作者图片
由于数据生成过程的离散性,这些方程是近似的。
Wald 和 Wolfowitz (1948) 已经证明,具有这些边界的检验是最有效的序贯概率比检验,即具有相同功效 1-β 和显著性 α 的所有 SPR 检验至少需要相同数量的观察值。
特例:测试无效效果
让我们回到我们的例子,数据来自正态分布,具有未知的均值 μ 和已知的方差 σ 以及假设 H₀: μ=0 和 H₁: μ=0.1 。
我们已经看到大小为 n 的样本的似然比是

似然比,按作者分类的图像
因此,对数似然(更容易计算)为

对数似然比,作者图片
模拟
我们现在准备执行一些模拟。首先,让我们对刚刚计算的对数似然比检验统计量进行编码。
log_lr = lambda x: (np.sum((x)**2) - np.sum((x-0.1)**2) ) / 2
log_lr.__name__ = 'log likelihood-ratio'
我们重复开始时做的相同实验,只有一点不同:我们将计算对数似然比作为一个统计量。数据生成过程有 μ=0 ,如在零假设下。
df = experiment(log_lr)
df.head()

数据快照,图片由作者提供
给定显著性水平 α=0.05 和幂 1-β=0.8 ,现在让我们计算最佳界限。
alpha = 0.05
beta = 0.2a = np.log( beta / (1-alpha) )
b = np.log( (1-beta) / alpha )
print(f'Optimal bounds : [{a:.3f}, {b:.3f}]')Optimal bounds : [-1.558, 2.773]
由于显著性和(一减)功效不同,零假设的界限比替代假设的界限更接近。这意味着,在中间效应为 μ=0.05 的情况下,我们将更有可能接受零假设 H₀: μ=0 而不是备选方案 H₁: μ=0.1 。
我们可以绘制在零假设 H₀: μ=0 下抽取的样本的似然比分布。
plot_experiment(df, ybounds=[a,b])

序贯抽样的对数似然比,图片由作者提供
在这种特殊情况下,测试在我们的抽样框架内是不确定的。为了做出决定,我们需要收集更多的数据。
plot_experiment(experiment(log_lr, n=789), ybounds=[a,b]);

序贯抽样的对数似然比,图片由作者提供
需要 789 次观察才能得出结论,而之前的样本量是 785。该测试程序可能需要一个比前一个更大的样本量。平均来说是真的吗?
如果我们重复实验 K=100 次会发生什么?
simulate_experiments(log_lr, ybounds=[a, b], early_stop=True, lw=1.5);Bounds crossed: 96 (4% upper, 96% lower)
Average experiment duration: 264

对数似然比,超过 100 次模拟的顺序采样,图片由作者提供
我们对 100 个模拟中的 96 个做出了决定,其中 96%是正确的决定。所以我们的拒绝率非常接近预期的 α=0.05 (在零假设下)。
但是,对于 4 的实验,测试是没有定论的。如果我们在每个实验中取样直到得出结论,会发生什么?
simulate_experiments(log_lr, ybounds=[a,b], early_stop=True, lw=1.5, n=1900);Bounds crossed: 100 (4% upper, 96% lower)
Average experiment duration: 275

对数似然比,超过 100 次模拟的顺序采样,图片由作者提供
从图中我们可以看出,在一个特别不走运的实验中,我们需要收集 1900 次观测结果才能得出结论。然而,尽管有这个异常值,平均实验持续时间是惊人的 275 个 T21 样本,几乎是 785 个样本的三分之一!
如果另一个假设 H₁: μ= 0.1 是真的,会发生什么?
simulate_experiments(log_lr, ybounds=[a,b], early_stop=True, mu=0.1, lw=1, n=2100);Bounds crossed: 100 (84% upper, 16% lower)
Average experiment duration: 443

对数似然比,超过 100 次模拟的顺序采样,图片由作者提供
在这种情况下,我们只在 84% 的模拟中做出了正确的决策,这与 80% (替代假设下)的期望值,即实验的功效 1-β 非常接近。
此外,同样在这种情况下,我们需要一个小得多的样本量:平均来说,只有 443 个观察值。
结论
在这篇文章中,我们已经看到了在随机实验中偷看的危险。过早停止测试可能是危险的,因为它会扭曲推断,使置信区间的覆盖范围发生偏差。
这是否意味着我们总是需要用预先指定的样本量进行测试?不要!存在允许最佳停止的程序。这些程序是为了一个特定的目的而诞生的:在不牺牲准确性的情况下,尽可能地减少样本量。第一个也是最著名的是序贯概率比检验,Wallis 将其定义为“过去三分之一世纪中最强大和最具开创性的统计思想”(1980 年)。
SPRT 不仅是 80 年前测试弹药箱的强大工具,而且今天仍被用于非常实际的目的(例如,它是如何在网飞或 T2【优步】T3 使用的)。
参考
[1] A .沃尔德,(1945),《数理统计年鉴》。
[2] A .沃尔德和 J .沃尔福威茨,序贯概率比检验的最优特征 (1948 年),《数理统计年鉴》。
[3] W. A .沃利斯,统计研究小组,1942–1945(1980),美国统计协会杂志。
密码
你可以在这里找到 Jupyter 的原版笔记本。
https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/optimal_stopping.ipynb
感谢您的阅读!
真的很感谢!🤗如果你喜欢这个帖子并且想看更多,可以考虑 关注我 。我每周发布一次与因果推断和数据分析相关的主题。我尽量让我的帖子简单而精确,总是提供代码、例子和模拟。
还有,一个小小的 免责声明 :我写作是为了学习所以错误是家常便饭,尽管我尽了最大努力。当你发现他们的时候,请告诉我。也很欣赏新话题的建议!
使用 SHAP 图书馆解释机器学习模型
原文:https://towardsdatascience.com/explain-machine-learning-models-using-shap-library-e05a1583c34f
Python 的 Shapley 附加解释可以帮助您轻松解释模型如何预测结果

萨姆·莫格达姆·卡姆西在 Unsplash 上的照片
介绍
复杂的机器学习模型经常被称为“黑盒”。这里有一个很好的概念解释。
在科学、计算和工程中,黑匣子是一种设备、系统或物体,它产生有用的信息,但不透露任何关于其内部工作的信息。对其结论的解释仍然模糊不清,或者说是“黑的”。( Investopedia )
因此,简单地说,就是当你创建一个 ML 模型,它工作得很好,但是如果有人问你是如何得到那个答案的,你不知道如何解释。“它就这样发生了”。
然而,作为数据科学家,我们与企业和高管一起工作,他们会经常要求解释,以确保他们做出正确的决定。或者,至少,他们想知道模型在做什么,它是如何得出结论的,什么有助于理解结果。
在这篇文章中,我们将学习如何使用 Python 中的 SHAP 模块来使“黑盒”模型更易于解释。这不仅有利于展示数据科学领域的领域,也有利于向客户推销您的模型,方法是演示数据如何进入以及如何出来、转换为预测。
沙普利值
沙普利价值观以劳埃德·沙普利命名,他因此获得了 2012 年诺贝尔奖。这个概念是基于博弈论,提出了一个解决方案,以确定每个球员的重要性,整体合作,游戏。
将这个概念转用到机器学习上,我们可以说 Shapley 值将告诉我们每个数据点对模型决策的重要性。
打个比方,我们来想象一个银行账户,最后的余额是怎么确定的。
有些钱进来(工资、股票收益),有些钱出去(账单、食物、交通、房租)。每一笔交易都会增加或减少最终余额,对吗?有些收入会很高,有些不会很高。同样,有些费用会很大,有些会很小…
月底,余额将是所有收支的总和。所以,这些数字中的每一个都像 Shapley 值,它们更容易告诉我们每个交易在一个月中对余额的贡献是积极的还是消极的。
例如,工资支票有很大的积极影响,而租金是最大的下降影响。

每个值对最终结果都有积极或消极的影响。图片由作者提供。
如何使用 SHAP 模块
好了,现在我们知道了什么是 Shapley 值,我们如何实际使用它们呢?这是我们将在序列中看到的。
我们将安装并导入名为shap的 Python 模块。
# Install
pip install shap#import
import shap
我们应该为这个练习导入一些其他的需求。
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
接下来,让我们创建一个用于训练目的的分类数据集,并将其分为训练集和测试集。
# Create dataset
X, y = make_classification(n_samples=1000, n_features=5, n_informative=3,n_redundant=2, n_repeated=0, n_classes=2,scale=10, shuffle=True, random_state=12)# Train Test Split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=12)
好的。我们有自己的一套。现在让我们训练一个随机森林分类器。
# Model
rf = RandomForestClassifier().fit(X_train, y_train)
接下来,我们可以创建一个X_test的副本,并添加一些列名,以帮助 SHAP 的可解释性。我们可以假设数据集中的随机数是对给定产品的测试分数。要预测的标签是二进制的(0 或 1),这可能意味着 0=失败,1 =通过。
X_test_df = pd.DataFrame(X_test, columns=['Test1', 'Test2', 'Test3', 'Test4', 'Test5'])

测试数据集。图片由作者提供。
解释者和 SHAP 价值观
下一步是为基于树的模型创建一个解释器。为此,我们将输入训练好的模型。在序列中,我们用最近创建的带有特性名称的测试集来计算shap_values()。
# Create Tree Explainer object that can calculate shap values
explainer = shap.explainers.Tree(rf)# Calculate shap values
shap_values = explainer.shap_values(X_test_df)
现在让我们来看看这个数据集的 Shapley 值。注意代码中有切片符号[1],因为.shap_values()函数返回一个张量,每个类有一个数组。片[0]中的一个将类 0 的预测视为参考,片[1]将 1 的预测视为参考。
# SHAP values for predictions with (1 = passed) as reference
pd.DataFrame(shap_values[1])

数据集的 Shapley 值。图片由作者提供。
好吧,酷,但这是什么意思?这些数字中的每一个都意味着该数据点对预测结果 1 的贡献有多大。因此,例如,第一行的变量 0 贡献更多,增加了它成为 1 的机会,而变量 3 具有最大的负面影响,减少了它被分类为 1 的机会。
力图
我们可以在这个很酷的力图中直观地看到贡献。
# Check Single Prediction# Choose observation
observation = X_test_df.loc[[0]]# Calculate Shapley values
shap_values2 = explainer.shap_values(observation)# Initiate Java script for plotting
shap.initjs()# Plot
shap.force_plot(explainer.expected_value[1], shap_values2[1], observation)

第一个条目的预测组成。图片由作者提供。
让我们一点一点地理解这个图形。
- SHAP 从一个基础值开始,即目标变量的平均值。
y.mean()为 0.505。因此,如果类 0 的基值是这样,则类 1 的基值将是 1–0.505,大约是 0.495。 - 由此,算法计算出是什么增加或减少了类为 0 或 1 的机会。粉色使机会增加,蓝色使机会减少。
- 测试 4 和 5 将分类推回到 0 类,而测试 1、2 和 3 将分类推回到 1 类。
- 我们看到,在每个贡献之后,最终结果是 0.48,低于 0.5,因此它使该观察的预测为 0 类(在我们的示例中,失败)。
决策图
与力图类似的一个选项是决策图,它显示了算法决策的相同步骤,但以不同的可视化方式显示。
# Choose observation
observation = X_test_df.loc[[195]]# Calculate Shapley values
shap_values2 = explainer.shap_values(observation)# Decision Plot
shap.decision_plot(explainer.expected_value[1], shap_values2[1], observation)

来自 SHAP 的决策图。图片由作者提供。
力图(特征重要性)
为了有一个总体的观点,非常类似于随机森林中的特性重要性图,我们可以绘制一个摘要条形图。下面是代码,后面是生成的图像。
# Summary bar plot
shap.summary_plot(shap_values[1], x_test_df, plot_type='bar')

汇总条形图。图片由作者提供。
变量 Test1 对预测最重要,其次是测试号 3、4、5 和 2。
数据点汇总图
另一个有趣的情节是摘要散点图。它显示了每个数据点如何影响分类的摘要。代码很简单:只需调用summary_plot()并用 SHAP 值和要绘制的数据来填充它。
# Summary scatterplot
shap.summary_plot(shap_values[1], x_test_df)
这个结果起初看起来很可怕,但是很容易解释。首先,观察颜色是数据点的值。所以,值越高,越红,值越低,越蓝。
第二个注意点,X 轴是 SHAP 值出现的地方。向右为正值意味着对分类为 1 的影响更大,因为我们使用的是带切片的数组shap_values[1]。Y 轴显示变量名。

汇总散点图。图片由作者提供。
所以,解释一下:
- Test1 值越高,对分类的负面影响就越大,越倾向于 0 类。
- 对于测试 3 ,该值越高,对 1 级分类的影响越大。
- Test4 和 Test2 也显示了与 SHAP 负面影响更相关的较高值,这意味着它们将分类推至 0 级。
- 测试 5 看起来更加混合,但是较低的值影响分类为 1。
依赖图
最后,我们来看依赖情节。这种可视化可以帮助我们在算法对观察结果进行分类时确定哪些变量关系更密切。
下面是创建它的代码。使用shap.dependence_plot()并输入特征索引号、SHAP 值和绘图数据集。
# Dependence plot
shap.dependence_plot(0, shap_values[1], x_test_df)
结果是这样的。

来自 SHAP 模块的依赖图。图片由作者提供。
它带来的有趣之处是:
- 该图显示了预测时关系更密切的两个变量。在我们的例子中,测试 1 与测试 4 有很大关系。
- X 上的值是数据点。y 为同一变量带来 SHAP 值。
- 颜色取决于第二个变量 Test4 。
- 这里,它表明对于高值的测试 1 和测试 4 ,在分类中的影响是负面的(SHAP 值低)。因此,在测试 1 上具有高值的观察值,在测试 4 上具有高值的观察值更有可能被分类为 0 级。
- 请参见下图,这两个变量都是分类值的主要减损因素。

Test1 和 Test4 的高值对 SHAP 有负面影响,将分类推到 0 类。图片由作者提供。
在你走之前
我一直在研究这个包,我知道还有很多东西要学。我强烈建议你阅读下面这些来自data man博士的参考资料,它们对我了解这个令人敬畏的图书馆和创建这个帖子帮助很大。
促使我这样做的是来自 Aayush Agrawal 的一篇很棒的文章,我也附上这篇文章作为关于微软研究院interpret模块的参考。他们也将这个概念包装在一个很好的 Python 模块中。我鼓励你去看 ML 可解释性的“艺术”部分,那真是太棒了!
概述:
- SHAP 值计算每个数据点对给定分类的影响。
- SHAP 是一种加法方法,因此重要性的总和将决定最终结果
- 探索图并解释每个观察的分类。
如果你喜欢这个内容,关注我的博客或者考虑使用这个推荐链接加入 Medium(部分资源归作者所有)。
**http://gustavorsantos.medium.com/
上 Linkedin 找我。
参考
https://medium.com/dataman-in-ai/explain-your-model-with-the-shap-values-bc36aac4de3d https://www.kaggle.com/code/vikumsw/explaining-random-forest-model-with-shapely-values https://shap.readthedocs.io/en/latest/api.html#explainers **
关于 K 均值聚类的 7 个最常见问题
原文:https://towardsdatascience.com/explain-ml-in-a-simple-way-k-means-clustering-e925d019743b
最常用的无监督聚类算法

作者图片
背景
我们每天大约产生 2.5 万亿字节的数据。这些数据采用字符、数字、文本、图像、声音等形式。不足为奇的是,这些数据中的大部分都没有标签,有用的见解被淹没在数据的大山中。聚类是一种无监督学习方法,广泛用于寻找具有相似特征的数据点组(称为聚类),而不需要现有的标记数据。
借助聚类方法,我们可以将原始数据转化为可操作的见解。例如,我们可以对共享相同主题的消息进行聚类,对属于同一对象的图像进行分组,将具有相似行为的客户归类到同一组中。在本文中,我们将讨论最常用的聚类方法: K-Means 聚类。
#1:什么是 K 均值?
用通俗的语言来说,K-Means 的目的就是把具有相似特征的数据点放在同一个聚类中(即内部内聚),把具有不同特征的数据点分离到不同的聚类中(即外部分离)。

作者图片
从技术上讲,我们需要数学公式来量化内部内聚和外部分离。
- 群内方差(也称为平方误差函数或内平方和(SSW)或平方和误差(SSE))用于量化内部凝聚力。它被定义为平均点(称为质心)和簇中每个点之间的平方距离之和。值越小,聚类越好。

作者图片
- 组间方差(又名,( SSB)之间的平方和)用于量化外部分离。它被定义为全局平均点和每个质心之间的平方距离之和。值越大,聚类越好。

作者图片
在实践中,我们只需要最小化组内方差,因为最小化 SSW(组内平方和)必然会最大化 SSB(组间平方和)
我们用一个简单的例子来证明。在下面的例子中,我们希望根据得分值创建聚类。如果我们简单地将前三个观察值归入第一组,后三个观察值归入第二组。第一组的平均分数是 25 分,第二组是 16 分。我们知道,不管集群是如何创建的,全局平均值(20.5)总是保持不变。所以 SST (每个点与全局平均点之间距离的平方和)也将一直保持不变。数学上,证明 SST = SSW + SSB 并不难。因此,找到最小化 SSW 的簇将间接最大化 SSB 。

作者图片
# 2:K-Means 聚类是如何工作的?
步骤 1:通过随机选取 K 个起始点来初始化聚类质心
步骤 2:将每个数据点分配到最近的质心。K-Means 聚类常用的距离计算是欧几里德距离,这是一个度量两个数据点之间距离的尺度值。

作者图片
步骤 3:更新聚类质心。质心计算为聚类中数据点的平均值。更新的质心可能是也可能不是实际的数据点。如果他们是,那将是一个巧合。

作者图片
步骤 4:重复步骤 2-3(将每个数据点分配给新的质心并更新聚类质心),直到满足其中一个停止条件。
- 更新后的质心与前一次迭代中的质心保持一致(这是一种理想的情况,但在实践中,这可能太耗时了)
- 上证指数至少没有提高 x %
- 达到了最大迭代次数(明智地选择最大迭代次数,否则,我们会得到较差的聚类。)
#3:如何对 K-Means 的原始数据进行预处理?
K-Means 使用基于距离的测量(例如欧几里德距离)来计算每个数据点与使用所有特征的值的质心的相似程度。这些特征通常采用不可比单位的值(例如,以美元表示的货币、以千克表示的重量、以华氏度表示的温度)。为了产生公平的结果,建议将原始数据标准化。我们可以转换原始数据,使的平均值为 0,标准偏差为 1,这样所有特征的权重都相等。
#4: 如何挑选 K-Means 中的 K 值?
如果我们事先知道 K 值或者从领域专家那里得到建议,那将是理想的。如果没有,我们将需要依靠替代方法。虽然没有关于如何选择 K-Means 聚类的 K 值的最终方法,但是有一些流行的方法可以用来估计最佳的聚类数。
肘法:用 SSE(又名,集群惯性)来评价拆分的好坏。然后,我们为范围从 2 到 N 的 K 值创建一个 SSE 肘图(您可以为您的研究设置 N 的值)。随着 K 的增加,对应的 SSE 会减少。我们将观察 K 和 SSE 之间的权衡(我们希望 SSE 低,同时将 K 保持在合理的值)。当我们看到上证综指开始变平并形成肘形时,我们通常会选择 K 的最佳值。

剪影分析:用剪影系数来评价分割的好坏。轮廓系数计算如下

作者图片
S(i)是给定数据点的轮廓系数。a(i)是该给定数据点和同一聚类中所有其他数据点之间的平均距离。b(i)是该给定数据点和来自最近聚类的所有数据点之间的平均距离。S(i)的范围可以从-1 到 1。
- 如果 S(i) = 1,则意味着该数据点靠近同一聚类内的点,而远离相邻聚类。
- 如果 S(i) = 0,则意味着该数据点接近其聚类的边界。
- 如果 S(i) = -1,则意味着该数据点被分配到错误的簇。
最终轮廓系数计算为所有数据点的平均轮廓系数。然后,我们计算范围从 2 到 n 的 K 值的轮廓系数。轮廓系数越高,聚类可能越好。
戴维斯-波尔丁指数:用戴维斯-波尔丁指数来评价分割的好坏。戴维斯-波尔丁指数计算如下

作者图片
D(i,j)是给定的一对簇(例如,簇 I 和 j)的 Davies-Bouldin 指数。d(i)和 d(j)分别是聚类 I 和聚类 j 中每个点与其质心之间的平均距离。d(i,j)是簇 I 和 j 的质心之间的距离。
对于一个给定的簇,我们将计算它自己和所有其他簇之间的 Davies-Bouldin 指数。然后我们取这个集群的最大戴维斯-波尔丁指数。最后,我们将最终的戴维斯-波尔丁指数计算为这些最大值的平均值。然后我们计算 K 值范围从 2 到 n 的 Davies-Bouldin 指数Davies-Bouldin 指数越小,这些聚类越远,聚类越好。
#5:如何挑选 K 均值的起点?
即使我们选择了最佳的 K 值,K-Means 方法也不一定能产生最佳的聚类。由于 K-Means 算法很可能陷入局部最优,并且永远不会收敛到全局最优,因此得到的聚类可能会基于不同的起点而变化。因此,强烈建议使用不同的随机起点集运行 K-Means,并根据上面提到的三种评估方法选择最佳结果。
有一种先进的初始化方法,如 K-Means++和,这使得它能够克服陷入局部最优的问题,并提高聚类的质量。直觉很简单。我们将挑选彼此远离的初始质心,这样更有可能从不同的簇中挑选点。 K-Means++可以通过以下步骤实现。
- 步骤 1:我们随机选取一个数据点作为第一个质心。
- 步骤 2:我们用最近的质心计算剩余点之间的距离。
- 步骤 3:我们选取下一个质心,使得选取给定点作为质心的概率与这个给定点和它最近的所选质心之间的距离成比例。换句话说,一个点离选择的质心越远,它就越有可能被选为下一个质心。
- 重复步骤 2–3,直到拾取 K 个质心。
#6:如何用 Python 实现 K-Means 聚类?
在下面的例子中,我将用 Python 在 Iris 数据集上实现 K-Means 聚类。虹膜数据集包括 4 个特征变量(例如,“萼片长度”、“萼片宽度”、“花瓣长度”、“花瓣宽度”)和 1 个描述鸢尾花种类的变量(例如,Setosa、Versicolor、Virginica)。
from sklearn.preprocessing import StandardScaler
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import seaborn as sns
from sklearn.cluster import KMeans
from sklearn.metrics import davies_bouldin_score, silhouette_score, silhouette_samples
import numpy as npdf = pd.read_csv("[https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data](https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data)", names = ['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth', 'Species'])
# standardize the data to have a mean of zero and a standard deviation of one
df.iloc[:,:4] = StandardScaler().fit_transform(df.iloc[:,:4])
# Exploratory Data Analysis
sns.pairplot(df, diag_kind= 'kde')
sns.pairplot(df, hue="Species", diag_kind= 'kde')
在图 1 中,我们可以看到数据点的分离。我们希望在应用 K-means 聚类之后,能够得到尽可能接近图 2 的结果。

图 1:没有标签的原始数据(图片由作者提供)

图 2:带有正确标签的原始数据(图片由作者提供)
我们将使用 Python 中“sklearn”库中的“KMeans”算法。“n_clusters”表示要形成的簇的数量。“max_iter”表示单次运行中执行的最大迭代次数。“n_init”表示 K-Means 将在不同的起始点集合上运行的次数。init = "random|k-means++ "将指示是使用随机初始化方法还是 k-means++初始化。“random_state”用于确保结果是可重复的。
要计算 SSE,我们可以使用"。惯性 _ "来自 K-Means 输出。“戴维斯-波尔丁分数”和“侧影分数”分别用于计算戴维斯-波尔丁指数和侧影分数。
x = df.iloc[:,:4]
sse, db, slc = {}, {}, {}
for k in range(2, 10):
kmeans = KMeans(n_clusters = k, max_iter = 1000, n_init = 10, init = 'k-means++', random_state=123).fit(x)
clusters = kmeans.labels_
sse[k] = kmeans.inertia_
db[k] = davies_bouldin_score(x, clusters)
slc[k] = silhouette_score(x, clusters)
肘方法:在图 3 中,我们有一个 SSE 与集群数量的关系图。该图表明,弯头的 K 值约为 3-5。K=5 后,上证指数开始缓慢下降。
plt.figure(figsize=(8, 6))
plt.plot(list(sse.keys()), list(sse.values()), marker='o')
plt.xlabel('Number of Clusters', fontsize=24)
plt.ylabel('SSE', fontsize=24)
plt.show()

图 3(作者图片)
侧影分析:在图 4 中,我们有一个侧影指数与聚类数的关系图。该图表明高轮廓指数出现在较低的 K 值(例如,2,3)。
plt.figure(figsize=(8, 6))
plt.plot(list(slc.keys()), list(slc.values()), marker='o')
plt.xlabel('Number of Clusters', fontsize=24)
plt.ylabel('Silhouette Score', fontsize=24)
plt.show()

图 4(作者图片)
戴维斯-波尔丁指数:在图 5 中,我们绘制了戴维斯-波尔丁指数与集群数量的关系图。该图还表明,低戴维斯-波尔丁指数出现在较低的 K 值(例如 2,3)。
plt.figure(figsize=(8, 6))
plt.plot(list(db.keys()), list(db.values()), marker='o')
plt.xlabel('Number of Clusters', fontsize=24)
plt.ylabel('Davies-Bouldin Values', fontsize=24)
plt.show()

图 5(作者图片)
侧影图:我们可以创建另一个信息图来确定 K 的最佳值,这就是侧影图。它为不同聚类中的所有点绘制轮廓系数。该图包括每个集群的一个刀形。宽度代表每个点的轮廓系数。高度表示给定聚类中的点数。我们可以用下面的标准来选择最佳 K 值。
- 平均轮廓指数高。
- 聚类大致平衡,即聚类具有大致相同的点数。
- 大多数点的轮廓系数高于平均轮廓指数。
在图 6 中,K=2,3 具有相对较高的轮廓指数。但是 K=3 具有更平衡的集群。所以 3 更有可能是最优 K 值。
def make_Silhouette_plot(X, n_clusters):
plt.xlim([-0.1, 1])
plt.ylim([0, len(X) + (n_clusters + 1) * 10])clusterer = KMeans(n_clusters=n_clusters, max_iter = 1000, n_init = 10, init = 'k-means++', random_state=10)
cluster_labels = clusterer.fit_predict(X)
silhouette_avg = silhouette_score(X, cluster_labels)
print(
"For n_clusters =", n_clusters,
"The average silhouette_score is :", silhouette_avg,
)# Compute the silhouette scores for each sample
sample_silhouette_values = silhouette_samples(X, cluster_labels)y_lower = 10
for i in range(n_clusters):
ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]
ith_cluster_silhouette_values.sort()
size_cluster_i = ith_cluster_silhouette_values.shape[0]
y_upper = y_lower + size_cluster_i
color = cm.nipy_spectral(float(i) / n_clusters)
plt.fill_betweenx(
np.arange(y_lower, y_upper),
0,
ith_cluster_silhouette_values,
facecolor=color,
edgecolor=color,
alpha=0.7,
)plt.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
y_lower = y_upper + 10plt.title(f"The Silhouette Plot for n_cluster = {n_clusters}", fontsize=26)
plt.xlabel("The silhouette coefficient values", fontsize=24)
plt.ylabel("Cluster label", fontsize=24)
plt.axvline(x=silhouette_avg, color="red", linestyle="--")
plt.yticks([])
plt.xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
range_n_clusters = [2, 3, 4, 5]
for n_clusters in range_n_clusters:
make_Silhouette_plot(x, n_clusters)
plt.savefig('Silhouette_plot_{}.png'.format(n_clusters))
plt.close()

图 6(作者图片)
基于所有不同的度量,3 似乎是 K 均值聚类的最佳 K 值。最后,让我们使用 K=3 来产生 K 均值输出。在图 7 中,考虑到 K-Means 没有使用任何预先标记的训练数据,预测的聚类看起来相当准确。
kmeans = KMeans(n_clusters = 3, max_iter = 1000, n_init = 10, init = 'k-means++', random_state=123).fit(x)
clusters = kmeans.labels_
x['K-Means Predicted'] = clusters
sns.pairplot(x, hue="K-Means Predicted", diag_kind= 'kde')

图 7(作者图片)
# 7:K 均值的优缺点是什么?
K-Means 是最常用的聚类算法,因为它非常容易实现和解释。只有一个超参数(K 值)需要优化。它是一个有效的工具,可以应用于几乎所有不同的数据类型。
然而,K-Means 有一些明显的缺点。它假设
- 不同的簇有不同的质心,它们彼此相距很远。
- 如果点(A)比点(B)离给定质心更远,则点(A)比点(B)更不可能属于该给定聚类
在图 8 的第一个例子中,内圈应该属于一个集群,外圈应该属于另一个集群。但是 K-Means 不能正确地聚类这些点,因为它不能克服质心重叠的问题。
在第二个例子中,两个半圆应该属于两个不同的聚类,K-Means 再次未能识别出明显的模式。

图 8(作者图片)
现实生活中的数据几乎总是复杂的,因为它们由噪声和异常组成。虽然 K-Means 聚类是一个强大的工具,但我们也应该意识到它的局限性。
感谢您的阅读!!!
如果你喜欢这篇文章,并且想请我喝杯咖啡,请点击这里。
您可以注册一个 会员 来解锁我的文章的全部访问权限,并且可以无限制地访问介质上的所有内容。如果你想在我发表新文章时收到电子邮件通知,请订阅。
正确解释 SQL 连接
原文:https://towardsdatascience.com/explain-sql-joins-the-right-way-f6ea784b568b

(作者供图)
本文从需要向人们介绍数据库中连接概念的人的角度出发,例如LEFT JOIN。这可以是高级分析师角色、数据科学家或实际的教师和导师。这在实践层面上结合了数据和教育科学。
在讲授 SQL 中的连接时,您可能会使用或看到维恩图——只需图片搜索连接 SQL ,它们就随处可见。但是维恩图是错误的 心智模型【1】!心理模型是一个有用的概念,它将学习描述为外部事件的内部建模——有点像机器学习,但结果集成在一个完整的模型网络中,帮助人类预测和生存其社会和物理环境[7]。
使用维恩来解释连接,大脑将它们建模为集合运算(像UNION),并将对它们的结果做出错误的预测。因此,结果不像预期的那样,导致沮丧和与SELECT DISTINCT的可疑的解决办法。
我想展示一下
- 维恩图适用于集合运算,如并集等。—不适用于连接
- 连接是组合运算,所以我们需要这样教它们。即
A CROSS JOIN B是来自 A 的所有元素与来自 B 的所有元素的组合 - 所有的连接都是某种(过滤的)交叉连接——所以我们可以从交叉连接开始介绍连接,并从那里继续深入。
垂直运算:集合代数与并集
但是首先,为什么要比较 Join 和 Union 呢?因为维恩图是 SQL 中UNION等集合运算的正确心智模型!它们甚至以相似的方式命名:
A UNION DISTINCT B= A ⋃ B :“碗运算符”将集合 a 和 b 组合成一个具有不同元素的集合(或“碗”)。

集合联合(图片由作者提供)

在表格中设置联合(作者图片)
a⋂b:“cookie cutter”操作符只保留两个集合中的元素。

设置交叉点(图片由作者提供)

在表格中设置交叉点(图片由作者提供)
A EXCEPT B = A \ B :用“铲运算符”从 A 中减去所有 B 元素,得到一个精简的集合 A(如果它们共享任何元素的话)。

设置减法(图片由作者提供)

在表格中设置减法(图片由作者提供)
由于表不是真正的集合,我们也有像 UNION ALL 这样的构造,其中记录/元素允许出现不止一次。
SQL 中的一个集合是一个表,其中元素是行,所以我们要把想成垂直。
但是JOIN被解释为好像列是元素——因为我们join列。Joins是**和给一个元素添加属性。他们不会添加新元素,但是会将新属性关联到现有的集合——这将创建一个全新的集合,其中包含从原始表中创建的新元素。**
还是那句话:不是加法,是联想。新元素是通过关联新属性(列)产生的,而不是通过添加另一个集合中的元素(行)产生的。
这比集合运算更接近矩阵乘法。
" joins 的心智模型是集合运算",维恩图很容易将它们可视化:例如,如果任一表中的键不是惟一的,您可以使用LEFT JOIN创建新行。这是主键/外键思想中的标准情况:外键不应该是惟一的。
从右表的角度来看,在具有主键的left join中,元素的数量减少了左表中缺少主键的数量,因为我们只连接左表中存在的东西。所以右边的表丢失了元素。**

当 A 左连接 B 时,A 从关联中获得行,但是 B 丢失了不能关联的行(图片由作者提供)
但是从左边表的角度来看,元素的数量增加了外键倍数的数量,因为我们必须连接右边表中的所有信息,而不仅仅是一个键的第一次出现。因此左边的表根据新组合的数量增加元素。
维恩图没有传达这些想法,这很糟糕,因为它非常重要——它改变了结果表的形状,从而改变了我们对它们进行的任何聚合。
如果您查看了一些初级同事的 SQL,您可能会看到有人试图在奇怪的地方使用 DISTINCT 来修复连接问题,以消除最终输出表中不需要的重复项。但是这可能会改变含义,从而改变对聚合和分析的解释。对转换数据的错误解释会让公司损失惨重。
那么对于连接,什么是更好的心理模型呢?
备选方案:带 JOIN 的组合学**
从技术上讲,连接是创建新集合的元组操作(元组组合学 [2】)。维恩图给人的印象是集合大小保持不变,而实际上joins并没有给你对集合的控制,而是通过创建一个具有所需组合的新集合来控制记录的组合。
我们需要根据组合学,而不是集合代数来思考。
交叉连接
在交叉连接中,我们将一个表中的每一行与另一个表中的每一行结合起来。这个概念非常容易理解,因为它将一切与一切结合起来——非常直观。结果表大小是各个表大小的乘积。
让我们在更高级的连接中使用这种直觉…
条件连接
当使用一个需要匹配的键时,我们可以把那个键想象成定义了一个子域 ,我们在其中交叉连接。
以一个简单的键为例:在表 A 和表 B 中找到带有某个值 x_1 的键的每个实例——交叉连接它们!对键的下一个值 x_2 重复,依此类推…
这种连接只告诉我们如何处理不匹配的元素:
INNER:丢弃任何一方导致NULL的东西——但是在ON定义的子域内进行交叉连接。LEFT:只在左侧表格中找到的子域内进行交叉连接。RIGHT:仅在右侧表格中找到的子域内进行交叉连接。FULL OUTER:在任一表格中找到的子域内进行交叉连接。有点像INNER,但保留NULLs。
例子
让我们看一些代码。用匹配和非匹配键建立一些简单的表,并用子域中新的交叉连接思想解释它。**
你现在可以使用db-fiddle.com或者类似的东西来尝试(复制/粘贴)。
*/* Schema SQL, */
/* prepare tables and add some data */
CREATE TABLE A (
id INT,
val VARCHAR
);
CREATE TABLE B (
id INT,
val VARCHAR
);
INSERT INTO A (id, val) VALUES (1, 'xxx');
INSERT INTO A (id, val) VALUES (2, 'def');
INSERT INTO A (id, val) VALUES (4, 'ghi');INSERT INTO B (id, val) VALUES (1, 'xxx');
INSERT INTO B (id, val) VALUES (1, 'zzz');
INSERT INTO B (id, val) VALUES (2, 'aaa');
INSERT INTO B (id, val) VALUES (3, 'bbb');*
现在是一些简短的查询:
*SELECT * FROM A; /* Query 1: table A */
SELECT * FROM B; /* Query 2: table B *//* Query #3: cross join without subdomains */
SELECT * FROM A **CROSS JOIN** B;/* Query #4: A left join B
* = cross join within subdomain id found in A:
* There's no id 3 because it doesn't exist in A!
*
* We have two id=1 because they're both in B
* and two id=2 because they're both in A.
* Sub-domain cross-join in action!
*/
SELECT * FROM A **LEFT JOIN** B ON A.id=B.id;/* Query #5: cross join within subdomain id found in B: no id 4! */
SELECT * FROM A **RIGHT JOIN** B ON A.id=B.id;/* Query #6: cross join within subdomain id found in A or B: all ids! */
SELECT * FROM A **FULL OUTER JOIN** B ON A.id=B.id;/* Query #7: cross join within subdomain id found in A or B:
* All ids that have a matching partner in the other table!
* So don't keep NULL rows!
*/
SELECT * FROM A **INNER JOIN** B ON A.id=B.id;*
特别是inner join表明我们正在进行子域交叉连接。将其与INTERSECT进行比较,后者是一个真实的设置操作:
*SELECT * FROM A **INTERSECT** SELECT * FROM B;*
我们只得到在两个集合/表格中匹配的一个集合元素,而由于组合学的原因,inner join返回 doubles。
关键场景
在理想的主键/外键场景中,我们是安全的:我们只向表中加入唯一的信息,并且大小不会改变。但是这个用例是有欺骗性的——它只显示了查询引擎实际在做什么的边缘情况。如果查询引擎是围绕这个用例构建的,那么当右边的键不是惟一的或者左边没有主键时,左连接应该会给我们一个警告。但是他们通常不关心——而且 CTE 和子查询没有任何主键,也不可能实现。
如果您从集合的角度考虑,并期望 A 中的行数+B 中的一定数量的行(匹配 id ),但是您的连接组合学突然创建了一个奇怪的意外行数和 id 重复数,您无法解释,这很快就会令人沮丧。
结论
我们需要教授正确的心智模型,这样让人们从电子表格世界进入数据库世界(变得越来越有用)就更容易了。
当解释连接时,用组合学的术语来说,避免维恩图,不惜一切代价使用代数术语。按以下顺序引入联接:
- 交叉连接,因为我们还没有键,而且“把所有的东西和所有的东西结合起来”是很容易理解的。
- 内部连接,因为它类似于交叉连接,但是通过我们连接的键引入了子域。不匹配的案例会被直接删除。
- 左/右/外连接,因为它增加了对不匹配情况的控制,我们希望保留前面介绍的概念。
总的来说:为了避免指令错误,确保你用类似于动作映射 [3]的东西开始课程规划。对于评估,使用学习文件夹,或者——如果你被迫给人打分——使用并分享事先制定的带有建设性一致性的标准【4】。对于更复杂的课程,使用 4CID [5]。为了发展实际的学习经验,使用来自爱立信【6】和德阿纳【7】的见解。
参考
[1] N. Staggers,A. F. Norcio,心智模型:人机交互研究的概念(1993),Int。人机研究
[2]连接和类连接运算符。维基百科(2022)。URL:https://en . Wikipedia . org/wiki/Relational _ algebra # Joins _ and _ join-like _ operators
[3] C .摩尔,训练真的是答案吗?问流程图。(2013),网址:https://blog . cathy-Moore . com/2013/05/is-training-true-the-answer-ask-the-flow trade/
[4] J. Biggs,Constructive Alignment(无日期),网址:https://www . John Biggs . com . au/academic/Constructive-Alignment/
[5] J.J.G .范·梅林波尔,《四要素教学设计模式:其主要设计原则概述》(2019 年)网址:https://www.4cid.org/publications/
[6] K. A .埃里克森、M. J .普列图拉和 E. T .科克里,《专家的形成》。(2007 年)网址:https://hbr.org/2007/07/the-making-of-an-expert
[7]s·德阿纳《我们如何学习》。教育和大脑的新科学。(2021)爱尔兰:企鹅图书有限公司



浙公网安备 33010602011771号