决策深度强化学习下象棋
用机器学习创造国际象棋冠军
作者图片
下图是人工智能算法在 Chess.com 引擎上应用的将死棋,该引擎模拟了 2650(这是相当高的)特级大师——丹尼尔·纳罗迪茨基“丫蛋”。
在本文中,我将介绍为这种深度强化学习算法开发的概念,以及它如何赢得这场激烈的机器大战!
作者图片:查尔斯·谢赛 x·丫蛋——将军
“呜…你打败了丫蛋。希望你自我感觉良好:)”——丫蛋发动机
国际象棋是一种非常古老的战略棋类游戏,它的传统已经将它转变为一项运动、一门艺术,最终成为一门科学。
由于其复杂性,人类多年来一直试图创建模型来破解国际象棋,并一劳永逸地找到一种变得不可战胜的方法。今天,随着计算的巨大进步,我们有了可以提前几个步骤计算并取得优异结果的模型,如谷歌的 alpha-zero 模型。
我对国际象棋充满热情,没有谷歌的巨大资源,现在是我展示对国际象棋知之甚少但对算法知之甚多的时候了,我们可以创造一个国际象棋冠军,并尝试挑战该领域一些最强的算法。
决策深度强化学习
决策深度强化学习是一个概念,它使用深度强化学习通过先前定义的策略来优化决策。主要的想法是,从一组不同的好策略中,算法可以为面临的每种情况选择最佳策略。
深度强化学习模型观察环境中的每个状态,并使用神经网络来选择特定的动作。然后在被改变的环境中采取这个动作,然后代理对新状态进行新的观察并准备下一个动作。每个行为都会给代理人带来一个奖励,代理人的目标总是选择一个行为,使每个状态下的奖励最大化。
作者图片:CharlesChessAI 建筑
随着国际象棋游戏、深度 Q 学习、LSTM 和引擎的混合,CharlesChessAI 诞生了!
模拟环境——Python Chess 和 Gym-AI
第一步是创建一个模拟环境,以便模型可以观察状态并采取行动,基本上是一个下棋的操场。为此,我使用了 Python 中的几个库:chess、gym 和 gym chess。这些库允许我复制和观察棋盘上的所有移动,模拟一场比赛,除了获得分析分数和状态和移动的良好定义,还有检查,将死,可能的移动等等。
Charles chesai——智慧
CharlesChessAI 的智能基于 3 个预定义的策略:
- id:0——人类长期记忆:使用人类玩的 20,000 个游戏的序列,仅过滤在将死中完成的游戏。这个选项着眼于长远,因为它总是以算法的将死而告终。
- id:1-人类短期记忆:LSTM 的下一个单词预测器先前在大型游戏基础上训练,考虑 10 个移动的序列。该模型使用国际象棋运动作为字符串,并将游戏中的运动解释为句子中的单词。国际象棋表现得像玩家之间的对话。这种选择着眼于短期,因为它基于最后 10 个步骤,只产生下一个步骤。
- id: 2 —引擎:引擎 Stockfish 的使用。Stockfish 是一个免费的开源国际象棋引擎,它分析游戏并提前计算一些走法以选择最佳走法。
夏尔·谢赛十世·丫蛋
下面是一段完整的视频,展示了 CharlesChessAI 在对阵丫蛋的比赛中的所有动作,这场比赛持续了大约 60 个动作 10 分钟。
查尔斯·谢赛 x·丫蛋——作者视频
在与 Danny 的比赛中,该模型存储了他的所有决定,我们可以在下图中分析这些决定,并了解比赛中的行为和决策。
作者图片:CharlesChessAI Choices
在游戏的开始,直到第 25 步,我们看到这个模型在策略之间做了很好的平衡切换,但是在 LSTM 身上使用了很多短期记忆策略。在游戏的中间,直到第 45 步,我们看到模型比开始时更多地使用引擎,更多地与长期人类记忆交替,这保证了良好的移动将游戏带到有利的结局。在游戏的最后,我们看到这个模型使用了大量的人类记忆选项来将死。
仍在开发中的代码在下面我的 GitHub 库中:
https://github.com/octavio-santiago/CharlesChess_Reinforcement_Learning
结论
决策模型使算法为每种游戏情况选择最佳策略,多样化策略的组合为一个引擎带来了来自伟大玩家的额外创造力,这是人和机器之间的一种优秀组合。这种算法也是非常动态和自适应的,因为根据玩更多游戏和面对新情况的模型,它学习如何针对每种情况优化决策。
以同样的方式,这种类型的模型应用于国际象棋,它可以应用于其他不同的决策环境。
我留给你们另一个国际象棋游戏,它是在 CharlesChessAI(白棋)和一个名为 Chess Lv100 的 Windows 应用程序引擎之间进行的,难度最大。现在这个模型在与引擎和人类的比赛中都是不败的,我会与他们进行更多的比赛,并在同一频道上发布。“塔之舞”是这场比赛的好名字,你可以更好地跟随视频。
查尔斯想要赢!
我希望这是一本好书!我将提供更多的信息和 CharlesChess 匹配,并将在 LinkedIn 上联系:https://www.linkedin.com/in/octavio-b-santiago/
黑掉惠普调优来执行自动型号选择
瓦伦丁·彼得科夫在 Unsplash 上的照片
超参数调整的任务是选择与目标相关的最佳模型配置。我们在之前的帖子中已经看到,尽管这看起来是一项具有挑战性的任务,但是当使用基于模型的方法时,编码并不困难。
但是等等!如果惠普调优可以为给定的 ML 任务选择最佳参数,我们是否可以重用它来选择最佳模型?也就是说,是否有可能创建一种方法,像使用其他参数一样使用模型类型作为参数,并让超级参数优化为我们选择正确的参数?
这难道不是解决 ML 问题的巨大加速吗?答案不仅是肯定的是的!而且,实现起来也不是很复杂。
我们将在本文中详细探讨这一点。
基于型号的惠普调整快速提醒
超参数调整(HPT,有时也称为 HPO 超参数优化)的目标是找到最大化或最小化给定目标的配置。有各种方法来执行超参数调整:蛮力,随机搜索,贝叶斯搜索,或基于模型的方法。
我以前提倡使用基于模型的方法,这在我遇到的一些问题上证明是非常有效的。你可以在这里找到更多关于 SMAC 的细节,这是一个高效的 HPO 图书馆:
您可能也有兴趣更好地理解这些方法是如何工作的,并构建自己的超参数优化库。
这里有一个有趣的方法,利用你对 XGBoost、CatBoost 或 RandomForest 等标准模型的了解:
[## 用 XGBoost 调优 XGBoost:编写自己的 Hyper Parameters 优化引擎
towardsdatascience.com](/tuning-xgboost-with-xgboost-writing-your-own-hyper-parameters-optimization-engine-a593498b5fba)
除了令人兴奋之外(使用 ML 模型来调整 ML 模型),基于模型的超参数优化提供了一个优于其他解决方案的非常有趣的优势:它支持分类参数。这意味着我们可以将模型类型编码为分类参数。
也就是说,让我们看看如何破解标准的超参数调整来执行模型选择和加速模型构建。
超级模型
正如介绍中所提到的,这次黑客攻击背后的想法是这样的:我们能否像其他任何一个参数一样,将 model_type (即 XGBoost,Prophet,S/ARIMA……)视为一个参数,并让 Hyper Parameter Tuning 方法为我们完成这项工作?
令人高兴的是,如上所述,使用基于模型的方法的超参数优化支持分类参数。毕竟,他们的底层模型通常是一个增强的(或者不是)决策树。
这个属性的一个直接结果是,我们可以创建一个 超级模型 ,它将由模型类型以及其他参数来定义。 model_type 将对用于训练的底层模型进行编码。在 python 中,这给出了:
一个超级模型,它将底层模型类型作为一个参数。作者代码。
超模型的实现遵循 scikit-learn 模型接口,即它提供了 fit、predict、set_params 和 get_params 方法。为了简单起见,我们只支持两个模型:XGBoost 和 RandomForest,但是再增加一个只会多几行。例如,您应该尝试一下 SVR。
您可能已经注意到,我们使用白名单来只保留那些适用于给定模型的参数。这不是支持参数因模型而异这一事实的最佳方式。正确的做法是使用条件配置。ConfigSpace python 库支持这一点,但 scikit 并不支持这一点。
使用我们的新类进行训练和预测是立竿见影的。假设我们想使用 XGBoost 作为底层模型。这给出了:
用我们的超模。作者代码。
我们现在要做的就是在 HPT/HPO 步骤中使用这个模型,并让它选择最佳候选模型类型。
寻找最佳模型
有了主参数是模型类型的超级模型,我们可以使用标准的超级参数调整方法来识别最佳模型。
需要记住的重要一点是,绝对没有“最佳模式”。当我写“最佳模型”时,我指的是给定分数的最佳模型。在本文中,我们将使用平均绝对误差作为得分。
尽管在我之前的两篇关于这个主题的文章中,我一直在使用(并建议)SMAC 或一个定制的超参数优化实现来执行 HP 调优,但在本文中,我们将尝试另一种方法。不要错过尝试新事物的机会:)
这一次,我们将使用 BayesSearchCV 来探索配置空间。贝叶斯搜索的基本原理是使用高斯过程建立一个代理模型,估计模型得分。
每个新的训练更新代理模型的后验知识。然后为这个代理提供随机挑选的配置,给出最佳分数的配置被保留用于训练。
因为它使用高斯过程模型来学习超参数和候选模型的分数之间的关系,所以它可以被认为是基于模型的方法。
将所有这些放在一起会产生以下代码行:
使用我们的超级模型来确定波士顿数据集的最佳模式。作者的代码。
配置空间主要使用参数的均匀分布来定义。这意味着在给定范围内选择一个值的概率在任何地方都是相同的。例如, max_features 、 n_estimators 或 max_depth 就是这种情况。相反,跨越多个数量级的 gamma 和 learning_rate 使用对数均匀分布选取。
运行这段代码将向您展示 XGBoost 似乎是这个数据集的最佳选择。
检查
像我一样,你可能不相信一个算法的结果,在执行一些检查之前。
幸运的是,许多其他数据科学家已经研究了波士顿数据集挑战。更具体地说,在 Kaggle 这里由 Shreayan Chaudhary 研究过,他得出了与我们的算法相同的结论。这是好消息。
然而,我们再谨慎也不为过。让我们执行另一个简单的检查,以确保如果我们对 HP Tuning exploration 进行更多迭代,并且只针对随机森林进行优化,RandomForest 不会优于 XGBoost:
这次调优单一模型:RandomForestRegressor。作者代码。
我们简单地重用了我们的超模型,但是这一次我们强制将探索集中在一个模型上:RandomForestRegressor。我们也允许更多的迭代:随机森林 50 次,而以前两个模型都是 50 次。
结论是一样的。就平均绝对误差而言,XGboost 精度保持得更好:随机森林为 2.68,而 XGBoost 为 2.57。
我们也可能是“幸运的”,XGBoost 优于 RandomForestRegressor 的事实可能完全是随机的,并且与用于初始化贝叶斯搜索的初始种子相关: random_state=0。
使用各种 random_states 多次运行代码应该会让你相信我们并不幸运,在这种情况下,XGBoost 是关于我们选择的分数的最佳选项:平均绝对误差。
我们的自动模型选择丢弃坏选项的速度有多快?
另一个要考虑的非常有趣的方面是我们的模型选择丢弃跛脚鸭的速度。为了说明这一点,我们将向超模型支持的模型列表中添加另一个模型:LinearRegression。
直觉上,这是最糟糕的选择。让我们看看我们的自动模型选择花了多少时间来探索这种可能性。首先,我们将它添加到超模:
向超级模型添加 LinearRegression。作者代码
我们将使用一个肮脏的黑客来测量探索一个给定配置所花费的时间。我们只在第 42 行打印它(信不信由你),并对它执行一些 greps,得到以下统计数据:
- 线性回归研究了 3 次。
- 随机森林 17 次。
- XGBoost 30 次。
值得注意的是,在仅仅 50 次迭代中,我们的模型选择已经学会关注最有前途的模型:XGBoost,并且在仅仅 3 次迭代中就放弃了线性回归。
如果我们使用 ConfigSpace 的条件配置,而不是使用允许参数的白名单,我们可以更快地排除线性回归。这就人为增加了不必要的审判。
结论
扩展模型选择的超参数调整。用几行代码演示它非常容易。
我们一直使用贝叶斯搜索来探索配置空间,但我们也可以使用任何其他有效的超参数调整方法。
另一个值得尝试的想法是使用超参数优化方法进行特征选择。很有可能它也能有效工作。
带有 GCP Dataproc 的 Hadoop
Hadoop、其服务和架构简介
理查德·雅各布斯在 Unsplash 上的照片
今天,大数据分析是世界上发展最快的领域之一,因为人们可以从中获得大量好处。随着其巨大的增长和大量的好处,也带来了自己的一系列问题。存储大数据的一个主要问题是,您需要一个大空间来存储数千万亿字节的数据,这是您无法通过个人计算机实现的。即使你设法存储了大数据的一部分,也需要数年时间来处理它。作为对此的解决方案,Hadoop 是由 Apache 软件基金会开发的。
介绍
先说:Hadoop 是什么?
Hadoop 是一个开源的框架,为存储和处理大数据而设计。
因此,Hadoop 提供了两个主要功能,存储大数据和处理大数据。我们使用 HDFS (Hadoop 分布式文件系统)存储大数据,使用 MapReduce 处理大数据。在本文的其余部分,我们将更多地讨论 HDFS。
在谈论 HDFS 之前,让我们先来看看 DFS。在分布式文件系统(DFS)中,您将数据分成小块,分别存储在几台机器上。
HDFS 是专门设计的分布式文件系统,用于在商用硬件集群中存储大型数据集。
注:商品硬件是廉价硬件。比如你日常使用的笔记本电脑就是商品硬件。
一般来说,你把文件和目录存储在电脑的硬盘上。一个 HD 分为磁道,然后是扇区,最后是块。通常,硬盘中一个这样的块的大小是 4 KB。
注意:块是操作系统可以指向的一组扇区。
大小为 500 GB 的硬盘中的一组块(图片由作者提供)
如果我想在硬盘上存储一个大小为 2 KB 的文件,它会存储在一个块中,但是会有剩余的 2 KB 空间。HD 无法将剩余空间再次用于其他文件。因此,该空间将被浪费。
现在,在这个硬盘之上,我们将安装带有 HDFS 的 Hadoop。
在 Hadoop 2.x 中,HDFS 的块大小默认为 128 MB(在 Hadoop 1.x 中为 64 MB)
如果我想在 HDFS 存储一个大小为 300 MB 的文件( example.txt ),它将跨三个块存储,如下所示。在块 3 中,将只使用 44 MB。它将有 84 MB 的可用空间,剩余的空间将被释放出来供其他文件使用。
Hadoop 中 300 MB 大小文件的数据分布(作者图片)
因此,Hadoop 比 HD 更有效地管理数据存储。
注意:这里我取了一个 300 MB 大小的文件,仅仅是为了解释这个概念。通常,Hadoop 处理的是太字节大小的非常大的文件!
HDFS 服务
HDFS 有两个主要的服务,即 NameNode 和 Datanode 。
NameNode :运行在高端主机上的主守护进程。
DataNode :运行在商用硬件上的从守护进程。
注意:我们为什么对 NameNode 使用高端机器是因为所有的元数据都存储在 NameNode 上。如果 NameNode 出现故障,我们将丢失有关文件每个部分存储位置的所有信息,这最终可能导致无法访问整个 Hadoop 集群。因此,即使数据节点处于活动状态,也不会再使用群集,因为我们将无法访问存储在那里的数据。为了解决这个问题,最常见的做法是使用第二个 NameNode 作为备份。
NameNode
- 主守护进程
- 维护和管理数据节点
- 记录元数据(例如,存储块的位置、文件的大小、权限、层次结构等。)
- 从所有数据节点接收心跳和数据块报告
注意:Heartbeat 告诉 NameNode 这个 DataNode 还活着。
数据节点
- 从属守护进程
- 存储实际数据
- 为客户端发出的读写请求提供服务
HDFS 的街区复制
为了实现容错,Hadoop 在不同的数据节点上存储数据块的副本。默认情况下,复制因子为 3。也就是说,它将跨数据节点保留集群中任何数据块的三份拷贝。
让我们以之前的 300 MB 文件为例。
HDFS 数据块复制的高级表示(图片由作者提供)
Hadoop 如何决定将创建的块的副本存储在哪里?
它使用机架感知算法。
当客户端请求在 Hadoop 集群中进行读/写操作时,为了最大限度地减少流量,NameNode 会选择离它更近的 DataNode。这称为机架感知。
支持容错的块及其副本在机架中的分布(作者图片)
不应在原始拷贝所在的同一机架中创建数据块的副本。这里,不应在机架 1 中创建数据块 1 的副本。它们可以在机架 1 以外的任何其他机架中创建。如果我将数据块 1 的副本存储在机架 1 中,并且如果机架 1 出现故障,那么我将丢失数据块 1 中的数据。
注意:机架是 30 或 40 个节点的集合,这些节点在物理上紧密地存储在一起,并且都连接到同一个网络交换机。机架中任意两个节点之间的网络带宽大于不同机架上两个节点之间的带宽。
为什么将数据块 1 的两个副本存储在同一个机架(机架 2)上?
这有两个原因。首先,两个机架(机架 1 和机架 2)同时出现故障的可能性最小。其次,将数据文件从一个机架中的一个数据节点移动到同一机架中的一个数据节点所需的网络带宽比将数据文件从另一个机架中的一个数据节点移动要少得多。当不需要额外带宽时,消耗额外带宽是没有意义的。
写 HDFS 的建筑
再回到我们之前的例子,假设我们有一个客户想要存储一个名为 example.txt 的文件,大小为 300 MB。由于他在本地机器上没有足够的空间,他想把它放到 Hadoop 集群中,但是客户端不知道哪些数据节点有空闲空间来存储他的数据。所以客户端首先联系 NameNode。客户机向 NameNode 发送一个请求,说他想把 example.txt 文件放到集群中。
HDFS 书写体系结构的高级视觉表示(作者图片)
现在,NameNode 查看了 example.txt 的文件大小,计算了需要多少个块,以及如何将该文件分割成多个 128 MB 的块。因此,300 MB 的文件将被分成 3 个块,每个块分别容纳 128 MB、128 MB 和 44 MB。我们把文件的每一个拆分叫做 a.txt 、 b.txt 和 c.txt 。之后,NameNode 会快速检查哪些 DataNode 有空闲空间,然后向客户端返回一个响应,说请将 300 MB 的文件存储在 DataNode 1、3 和 5 中。
现在,客户端首先直接访问 DataNode1,在那里存储 a.txt 文件。默认情况下,集群会再保留两个 a.txt 的备份文件。一旦存储了 a.txt ,DataNode1 就将该文件的副本发送到另一个有一些空闲空间的 DataNode,比如 DataNode2。类似地,DataNode2 将该文件的副本提供给另一个 DataNode,比如 DataNode4。一旦 DataNode4 将该文件存储在他那里,他就向 DataNode2 发送一个确认,表示您发送的文件已经存储在我的本地 DataNode 中。同样,DataNode2 向 DataNode1 发出确认,表示您发送的文件已经存储在我的 DataNode2 和 DataNode4 中。现在,DataNode1 将向客户端返回一个确认,表示发送的文件已经存储在 DataNode1、2 和 4 中。
数据节点和客户端之间的相互通信(作者图片)
但是 NameNode 如何知道 a.txt 文件的确切存储位置呢?所有的 datanode 每隔一小段时间就向 NameNode 提供块报告,告诉 NameNode 在各自的 datanode 中有多少块被占用。
注:A 块报告 包含服务器托管的每个块副本的块 ID、代戳和长度。
通过这些块报告,NameNode 相应地更新元数据中的信息。同样的, b.txt 文件, c.txt 文件也会存储在集群中。
当一个 DataNode 关闭时会发生什么?
所有的 DataNode 不时地给 NameNode 一个心跳,这有助于 NameNode 判断 DataNode 是否还活着。如果任何一个 DataNode 没有给出正确的心跳,那么 NameNode 就认为这个 DataNode 死了。假设 DataNode1 死亡。然后 NameNode 会将它从元数据中的 a.txt 文件中删除,并将该文件分配给另一个有空闲空间的 DataNode。假设它被发送到 DataNode7。然后 DataNode7 向 NameNode 发回 block 报告,NameNode 会为 a.txt 更新元数据。
Heartbeat 如何在 Hadoop 集群中工作(作者图片)
阅读 HDFS 的建筑
假设客户想要读取他之前存储的 example.txt 文件。客户端首先联系 NameNode,说他想读取 example.txt 文件。NameNode 将查看它拥有的关于所述文件的元数据,选择每个存储的分割的最接近的副本给客户端,并且将那些数据节点的相关 IP 地址发送回客户端。然后,客户端将直接访问存储数据块的数据节点并读取数据。一旦客户端获得所有需要的文件块,它将组合这些块以形成文件, example.txt 。
注意:当服务于客户端的读取请求时,HDFS 选择离客户端最近的副本。这减少了读取延迟和网络带宽消耗。
HDFS 里德建筑的高级视觉表现(图片由作者提供)
在开始动手操作之前,让我简要地告诉您关于 Dataproc 的情况。
Dataproc 是用于运行 Hadoop & Spark 作业的托管服务(它现在支持 30 多种开源工具和框架)。可用于大数据处理和机器学习。
下面的实践是关于使用 GCP Dataproc 创建一个云集群并在其上运行 Hadoop 任务。
亲自动手
我将使用谷歌云平台和 Ubuntu 18.04.1 来实现这一点。
首先,您需要设置一个 Hadoop 集群。
- 选择或创建 Google 云平台项目
- 您需要创建一个云 Bigtable 实例。首先,启用云 Bigtable 和云 Bigtable 管理 API
- 现在通过 GCloud shell 创建一个云 Bigtable 实例
gcloud bigtable instances create INSTANCE_ID \
--cluster=CLUSTER_ID \
--cluster-zone=CLUSTER_ZONE \
--display-name=DISPLAY_NAME \
[--cluster-num-nodes=CLUSTER_NUM_NODES] \
[--cluster-storage-type=CLUSTER_STORAGE_TYPE] \
[--instance-type=INSTANCE_TYPE]
通过 GCloud shell 创建 Bigtable 实例
注意:确保使用有云 Bigtable 的集群区域。
- 启用云 Bigtable、云 Bigtable Admin、云 Dataproc 和云存储 JSON APIs。
- 通过运行
gcloud components install gsutil
安装gsutil
工具 - 安装 Apache Maven ,它将用于运行一个示例 Hadoop 作业。
sudo apt-get install maven
- 克隆 GitHub 存储库Google Cloud platform/Cloud-bigtable-examples,其中包含一个使用云 Bigtable 的 Hadoop 作业的示例。
git clone [https://github.com/GoogleCloudPlatform/cloud-bigtable-examples.git](https://github.com/GoogleCloudPlatform/cloud-bigtable-examples.git)
- 现在创建一个云存储桶。Cloud Dataproc 使用一个云存储桶来存储临时文件。
gsutil mb -p [PROJECT_ID] gs://[BUCKET_NAME]
创建云存储桶
注意:云存储桶名称在所有桶中必须是全局唯一的。请确保为此使用唯一的名称。如果您得到一个 *ServiceException: 409 Bucket hadoop-bucket already exists,*
,这意味着给定的 bucket 名称已经被使用。
- 您可以通过运行
gsutil ls
来检查项目中创建的存储桶
检查创建的 Gcloud 存储桶
Gcloud 存储桶
- 创建一个包含三个工作节点的云 Dataproc 集群。
转到导航菜单,在“大数据”组类别下,您可以找到“Dataproc”标签。单击它并选择“集群”。单击“创建集群”按钮。为您的集群取一个合适的名称,将 Worker nodes 改为 3。点击页面底部的“高级选项”,找到“云存储暂存桶部分”,点击“浏览”并选择您之前制作的桶。如果全部完成,单击“创建集群”并等待几分钟,直到集群创建完毕。
成功创建集群后,它将显示如下。
创建一个 Dataproc 集群
- 您可以进入创建的集群,然后单击“VM Instances”选项卡。在那里,您可以找到为集群创建的节点列表。
集群中的主&工作者节点
- 转到目录
java/dataproc-wordcount
- 用 Maven 构建项目
mvn clean package -Dbigtable.projectID=[PROJECT_ID] \
-Dbigtable.instanceID=[BIGTABLE_INSTANCE_ID]
用 Maven 建造
- 项目完全构建完成后,您会看到一条“构建成功”的消息。
- 现在开始 Hadoop 作业
./cluster.sh start [DATAPROC_CLUSTER_NAME]
gcloud dataproc jobs submit pig --cluster test-hadoop --execute ‘fs -ls /’
Hadoop 作业执行的详细信息
Hadoop 作业的输出
您可以通过以下方式删除云 Dataproc 集群
gcloud dataproc clusters delete [DATAPROC_CLUSTER_NAME]
资源
[1] GCP Dataproc 文档
[2] GCP 大表例题
干杯!🙂
MLOps 的 Hagakure:合理规模的 ML 的四大支柱
在启动阶段有效地进行 ML
没有太多操作的 MLOps 第 3 集
“不要用弓箭杀死蚊子.”
孔子
在本系列的上一集中,我们探讨了我们所谓的“合理规模”(RS),给出了一个有点松散的定义,旨在把握我们认为许多公司目前在 ML 方面所处的状况。
鉴于 RS 公司必须面对的许多重大挑战,本文将阐述一个基于四个主要哲学支柱的框架,同时将这些原则应用到现实世界的问题中。
在提出我们在不断变化的现代传销环境中对传销从业者的实践和精神指导之前——我们对合理规模的传销的haga kure———让我们简单回顾一下 RS 公司面临和解决的主要挑战。**
当在真实的公司中做出实际的选择时,我们在上一篇文章中描述的约束有无数的分支。这部分是由于制约因素的异质性。RS 公司必须始终考虑工程资源、数据量、数据质量、团队规模、用例以及此类用例的货币影响等一系列问题。一个特定组织内部的确切图景可能取决于许多因素,我们应该期望它对该组织来说是有些独特的,这使得很难提出一个单一的成功秘诀。
令人眼花缭乱的 MLOps 供应商并没有让事情变得更简单(见这里和这里)。虽然一些公司倾向于可以在整个 ML 堆栈中采用的端到端解决方案,如 Databricks ,但该行业最近见证了一场快速而无情的功能整合运动。换句话说,越来越多的公司(目前主要是初创公司)正在为 ML 周期的所有步骤开发更成熟的解决方案,从数据仓库到模型部署。现在,这种前景的发展总体上有助于提供一套连贯的解决方案,这些解决方案可以相互集成,但事实是,这个领域的大多数参与者都是独立行动的,这给 ML 从业者留下了一个艰巨的任务,即找到正确的方法来选择他们的工具,并以连贯的方式将它们组合在一起。
因此,再一次,就像每一次对智慧的追求一样,我们的任务是制定一个原则性的框架,而不是指向一个具体的解决方案。由于我们全心全意地赞同精神指导只有在实践中有用才是好的这一观点,我们希望确保这样一个框架能够解决遥感公司的现实问题。
规模合理的 ML 四大支柱
杰里米·珀金斯在 Unsplash 上拍摄的照片
一 Data is superior to modeling.
二 Log then transform.
三 PaaS & FaaS is preferable to IaaS.
四 Vertical cuts deeper than distributed.
一 DATA IS SUPERIOR TO MODELING
RS 公司更大的边际收益总是在于拥有干净和可访问的数据:好的数据比建模和模型架构的相对改进更重要。
总的来说,行业似乎进入了一个新的阶段,模型功能变得越来越商品化,可能是因为我们在建模方面变得越来越好,开箱即用的模型变得足够引人注目。这一点很重要,因为在内部构建非常有竞争力的模型已经成为 RS 公司的一项更大的投资,而 ROI 可能没有太大意义。
在这种情况下,从战略角度来看,专有数据流至关重要,尤其是对于遥感公司。举个例子,由 Chris Re 和最近由吴恩达倡导的以数据为中心的人工智能框架,它专注于数据集优化以提供一个坚实的基础,其中数据收集受到限制,训练样本天生稀缺,迭代不可能超快(你可能记得我们的第二集,这是 RS 的关键原则)。
这一原则的第一个结果是,数据摄取是您的 MLOps 周期的一等公民,获得清晰的数据应该是最终目标。
简单地说,数据摄取必须包括通过标准获取数据。你可以选择一个已经存在的特定领域的协议(例如 Google Analytics 用于浏览事件),或者如果你的数据对你的业务来说有些独特,你可以提出自己的标准。
然而底线是相同的:两个描述完全相同事物的事件绝不会有不同的结构。例如,假设我们正在构建一个推荐系统,我们正在从不同的电子商务网站收集添加到购物车的操作:在任何情况下,来自网站 A* 的添加到购物车事件都不应该与来自网站 B 的添加到购物车事件不同。对于这条规则的每一个例外,迟早都要付出代价。*
与标准化这一点密切相关,我们可以补充一点关于严格验证和拒绝。一般来说,即使事件不符合约定的标准格式,也不应该被拒绝。系统应该标记所有格式错误的事件,将它们发送到不同的路径,并通知警报系统。虽然形式不良的事件不会出现在我们的表中是非常重要的,但知道什么时候出错也是非常重要的。
二 Log then transform
数据接收和处理之间的明确分离产生了可靠且可重复的数据管道。数据仓库应该包含系统在任何给定时间的每个状态的不可变的原始记录。
因此,数据管道应该总是从原始事件构建,并在流和处理之间实现明显的分离。前者的作用是保证任何给定系统状态的真实快照,后者是为最终目的准备数据的引擎。
关键的区别在于,流的输出是不可变的,而处理的输出总是可以撤消和修改的。如果您从小就认为数据库是关于写入和转换数据的,那么这里的想法有些违反直觉,但是它的核心非常简单:模型总是可以固定的,而数据却不能。
这个原则的北极星是你的系统是否允许可重复性。主要的问题是:你能在数据转换、查询或模型中改变一些东西,然后重放自时间开始以来你摄取的所有数据,而没有任何大问题(时间除外)吗?如果答案是肯定的,那么您成功地将这一原则应用到了您的数据基础架构中。如果答案是否定的,你真的应该试着把它变成肯定的,在不可能的情况下,尽可能地接近它。
在我们系列的下一集,我们将更详细地讨论数据摄取以及流和处理之间的分离,我们还将分享基于雪花 + dbt 的开源实现。
三 PaaS & FaaS is preferable to IaaS
专注是 RS 的精髓。不要构建和管理 ML 管道的每个组件,而是采用完全托管的服务来运行计算。
RS 公司的主要特点是,它们在 ML 计划的资源方面受到这样或那样的限制。当然,当资源有限时,将资源投入到最重要的业务问题中是一种很好的做法。例如,假设您正在构建一个推荐系统,您可能希望尽可能地专注于提供好的推荐。就这么简单。
当然,倾向于完全托管服务的方法在硬成本方面往往更昂贵,但与此同时,数据科学家可以不必担心停机、复制、自动扩展等问题。所有这些都是必要的,但这并不意味着它们是您的 ML 应用程序的中心,根据我们的经验,让您的组织专注于核心业务问题的好处往往超过低成本的好处(当然,在合理的范围内)。因为由专门的人员维护和扩展基础设施仍然很昂贵,所以最终很容易就会比支付和管理少数新提供商的费用高得多。
此外,建设和维护基础设施最糟糕的一点是,随着时间的推移,实际成本是不可预测的。不仅很容易低估长期所需的全部努力,而且每次你为了构建一个基础设施而创建一个团队时,你都引入了人的因素的典型的不可预测性。
最终需要多少人?他们会在一年后被公司吸收吗?组织内的护城河是因为角色的过度分离还是因为一些团队的唯一目的是保持基础设施的正常运行?
消费账单很少有积极的方面,但有一点是肯定的,那就是它们很容易预测。如果你想更大规模地预测成本,你可以将你的账单乘以任何你认为能抓住你成长道路下一阶段的数字。
四 Vertical cuts deeper than distributed.
RS 公司不需要在每一步都进行分布式计算。高效的垂直设计可以实现很多目标。
这可能是我们做出的最有争议的声明,但是坚持我们的观点,这是有原因的。分布式系统,如 Hadoop 和 Spark ,在大数据革命中发挥了关键作用。仅仅在五年前,Spark 几乎是大规模进行 ML 的唯一选择。世界各地的初创公司都使用 Spark(我们也不例外)进行流传输,SparkSQL 进行数据探索,MLlib 进行功能选择和构建 ML 管道。
然而,它们使用起来很麻烦,很难调试,而且它们还强制许多科学家不熟悉的编程模式,这对新员工的增加时间有非常负面的影响。
我们想表达的观点很简单:如果你是一家 RS 公司,你要处理的数据量不需要无处不在的分布式计算:有了好的垂直设计,就有可能在显著改善开发人员体验的同时完成大部分工作。
总的想法是,您的 ML 管道应该被视为贯穿许多模块化步骤实现的 DAG。现在,虽然其中一些可能需要更多的计算能力,但是重要的是从 ML 开发的过程和经验中抽象出计算部分。
分布式计算应该用于它真正的用途:解决大量复杂的数据问题。对于其他所有事情,更实际的做法是在单独的盒子中运行管道中的步骤,在云基础设施中扩展计算,并且只在中扩展需要的步骤,例如 Metaflow 允许这样做。
在我们的经验中,这种观点的转变对任何 ML 团队的开发人员体验都有巨大的影响,因为它会给数据科学家一种在本地开发的感觉,而不必一直经历处理分布式计算的重重困难和障碍。
爱你的开发者:关于垂直独立性的推论。
最后两点旨在尽可能地从 ML 开发者那里抽象出基础设施。这种哲学有很好的理由存在。
我们希望鼓励 ML 团队的纵向独立性。ML 中的许多工作在很大程度上取决于所解决问题的类型,因此数据科学家需要能够根据数据集、数据类型、算法和安全约束对工具、架构和建模做出合理的独立选择。此外,ML 系统不是针对静态环境部署的,因此数据科学家需要了解数据中的变化、模型中的变化、敌对攻击等等。我们倾向于纵向独立,而不是嵌入到高度条块化的组织中,因为在这种情况下过度专业化会导致高协调成本、低迭代率和适应环境变化的困难。最后,我们痛苦地意识到,在 RS 公司必须进行预算的所有资源中,最稀缺的是优秀的工程师。
我们认为,在吸引和留住 RS 公司的关键人才方面,垂直独立性非常重要。我们经常听到数据科学家仍然将相当大一部分时间投入到低影响任务中,例如数据准备、简单分析、基础设施维护以及更普遍的跳过繁琐流程的束缚。
根据我们的经验,优秀的工程师和数据科学家会对使用最好的工具做前沿工作感到兴奋。糟糕的开发体验以及无法看到他们的工作在生产中的影响是数据科学家感到沮丧的主要原因之一。我们都知道,沮丧的工程师更有可能在 LinkedIn 上回复招聘人员的信息。
纵向独立在创新速度、最小化、精简和自由方面获得回报:RS 组织最有价值的硬币。让您的数据科学家拥有从获取数据到生产测试的整个周期的可能性,让他们尽可能容易地在整个端到端管道的不同阶段来回移动,并赋予他们权力。这样做,他们会开发出具有真正商业价值的可靠应用程序,让你大吃一惊。
无耻的下一个帖子的悬念
这些是我们在 RS 规模生产中 ML 的原则。采用它们使我们能够成功地大规模工作,同时最大限度地减少所有可能的基础架构问题,否则我们将不得不处理这些问题。请记住,我们所做的一切都是受 RS 公司所受约束的激励。最终目标永远是做 DataOps 和 MLOps,而不做太多的 Ops。
在下一篇文章中,我们将更深入地探讨前两个原则,并设计出一个具体的数据接收和数据处理管道示例,它完全可以用无服务器、雪花和 dbt 来重现。
承认
如果没有我们开源贡献者的承诺,这个系列是不可能的:
- Patrick John Chia :局部流量和基线模型;
- Luca Bigon :通用工程和基础优化;
- Andrew Sutcliffe :远程流;
- Leopoldo Garcia Vargas :质量保证和测试。
优雅地处理聊天机器人故障
用 Rasa 构建聊天机器人——第三部分
作者图片
不管你训练 Rasa 聊天机器人有多好,总会有机器人不知道如何处理的情况。我们的目标不是制造一个完美的防故障聊天机器人,因为这甚至是不可能的。事实上,作为人类,我们自己也经常要求澄清。更确切地说,我们的目标是在事情变糟时让系统就位。
比起被误解,人们更喜欢被要求澄清。当然,要求用户重复五次并不理想,这也是为什么要有后备系统的原因。
在本帖中,我们将讨论如何处理你的机器人不太明白用户想说什么的情况。在 Rasa 术语中,这被称为回退。
快速注释
这是我关于 Rasa 系列的下一篇文章。您可以查看以下系列的前几篇文章:
在那里,我们讨论了 Rasa 和聊天机器人的基本组成部分,如果你是 Rasa 新手,通读前两篇文章肯定会有所帮助。
目录
- An example
- The Fallback Classifier
- Simple Fallback
- Single-stage Fallback
- Two-stage Fallback
- Handling Human Handoff
一个例子
我们将看一个没有后备机制的简单例子,然后我们将从那里开始。
让我们继续使用本系列中使用的聊天机器人——它要求用户提供联系信息。我们将通过减少我们的DIETClassifier
的训练次数来模拟回退场景,它试图理解用户在说什么。
缺省情况下,缺省配置文件(运行 rasa init 时获得)有一个FallbackClassifier
组件。我们将在这里做两处更改:
- 我们将删除
FallbackClassifier
组件,看看会发生什么。 - 我们也将减少
DIETClassifier
到2
的训练周期,所以模拟一个难以察觉的短语。
language: enpipeline:
- name: WhitespaceTokenizer
- name: RegexFeaturizer
- name: LexicalSyntacticFeaturizer
- name: CountVectorsFeaturizer
- name: CountVectorsFeaturizer
analyzer: char_wb
min_ngram: 1
max_ngram: 4
- name: DIETClassifier
**epochs: 2 # reduced epochs**
constrain_similarities: true
- name: EntitySynonymMapper
- name: ResponseSelector
epochs: 100
constrain_similarities: true
**# removed the FallbackClassifier from here**policies:
- name: MemoizationPolicy
- name: TEDPolicy
max_history: 5
epochs: 100
constrain_similarities: true
- name: RulePolicy
训练完机器人后,我们将使用
rasa shell --debug
对话大概是这样的:
user: Hi
bot: Sorry, you'll have to provide your information to proceed.
这完全没有意义。仔细查看调试日志,我们看到了以下内容:
Received user message 'hi' with intent '{'id': -4215159201959237708, 'name': 'deny', 'confidence': 0.37452369928359985}' and entities '[]'
机器人预测用户的消息“嗨”是一个deny
意图,而不是greet
意图。机器人的响应可能会让用户感到困惑,这在实际项目中会更加明显,在实际项目中,您通常会有许多意图和大量数据,在某些情况下,这些数据可能会有点相似。
现在,让我们看看当我们只在配置中包含FallbackClassifier
而没有实际设置整个机制时会发生什么。
添加到管道末端:
- name: FallbackClassifier
threshold: 0.5
ambiguity_threshold: 0.1
和重新训练,当用户说“嗨”时,我们看到日志中的变化:
Received user message 'hi' with intent '{'name': 'nlu_fallback', 'confidence': 0.5}' and entities '[]'
它不再预测具有最高置信度的意图,即使它真的很低。现在,FallbackClassifier
用nlu_fallback
覆盖了这个意图。在我们继续回退机制之前,我们将讨论回退分类器。
回退分类器
回退分类器实现两个阈值:
- 民盟的退路
threshold
- 而
ambiguity_threshold
后退分类器阈值-按作者分类的图像
在您的配置文件中,它是在意图分类器之后定义的,如下所示:
language: en
pipeline:
..
..
# intent classifier like DIETClassifier defined here
- name: FallbackClassifier
threshold: 0.7
ambiguity_threshold: 0.1
(NLU 回退)阈值
threshold
键用于指定config.yml
中 NLU 回退的阈值。如果 top intent 的置信度低于这个值,则回退分类器忽略它并继续进行nlu_fallback
。
模糊阈值
该键指定了前两个意图的置信度应该是多少,以继续常规对话流。如果置信度得分的差值比这里指定的值少或,则不管是否满足threshold
条件,回退分类器都使用nlu_fallback
。
简单的退路
简单的退路——作者图片
这种回退是最基本的:当你的机器人预测用户的意图低于某个阈值(nlu_fallback_threshold
)时,我们将简单地将聊天转移到一个人类代理。
现在,将聊天转移到人工代理的实际机制,称为“人工移交”,取决于您的聊天机器人部署的方式和位置。为了让这适用于每个人,我们将创建一个占位符自定义动作,该动作将返回一个transferred_to_human
标志,只是为了让我们知道它正在工作。
我们来实施吧。
添加规则
我们首先需要定义一个rule
,每当nlu_fallback
被预测时,运行action_default_fallback
。将此添加到rules.yml
:
# simple fallback:
- rule: Implementation of the simple-Fallback
steps:
- intent: nlu_fallback
- action: action_default_fallback
履行
接下来,我们将覆盖action_default_fallback
。简单地将这个类添加到actions.py
:
class ActionDefaultFallback(Action):def name(self) -> Text:
return "action_default_fallback"def run(self, dispatcher, tracker, domain):
# output a message saying that the conversation will now be
# continued by a human.
**message = "Sorry! Let me connect you to a human..."
dispatcher.utter_message(text=message)**# pause tracker
# undo last user interaction
return [ConversationPaused(), UserUtteranceReverted()]
我们还将在actions
字段下的域中定义它。
测试
我们现在有了更好的体验。
user: Hi
bot: Sorry, couldn't understand that! Let me connect you to a
human...
请记住,经过适当训练的机器人几乎肯定能够对“嗨”做出反应。在这里,我们只是模拟一个机器人无法理解的用户消息。
如果您查看日志,可以看到 Rasa 正确地应用了我们定义的规则。
**rasa.core.policies.rule_policy -** There is a rule for the next action 'action_default_fallback'.
单阶段回退
单阶段回退机制—图片由作者提供
简单的退路比继续进行几乎肯定会失败的对话要好。
但是我们可以做得更好。如果你的机器人不太确定用户想说什么,我们可以让机器人通过提供建议来澄清用户的意思,而不是仅仅执行移交。
当然,即使在这之后,如果机器人仍然没有得到用户想要说的话,我们可以进行切换。
这将更加有用,因为
- 它减轻了人类的负担
- 它让用户参与的时间更长,因为如果目前没有人可用,他们很可能没有耐心等待。
默认情况下,当请求澄清时,机器人会以最高的可信度暗示意图。
The utter_ask_rephrase response —图片作者使用 carbon.now.sh
您可能会注意到这里有两件事:
- 仅建议最高意图,而不是提供,比如说,前 3 个预测
- 使用 NLU 定义的意向名称。对用户来说不是一个很好的体验,特别是对于那些有更多隐晦措辞的意图。
我们可以通过覆盖一个叫做action_default_ask_affirmation
的方法来解决这两个问题。
让我们实现这一点。
添加相关规则
这比前一个案子要复杂一点。我们将在这里定义两条规则。
nlu_fallback →向用户提供建议
一旦机器人无法理解用户,它将提供用户可能想要表达的意思的相关建议。这里,我们将执行被覆盖的action_default_ask_affirmation
自定义动作,稍后我们将实现它。
- rule: Single stage fallback | ask user to choose what they meant
steps:
- intent: nlu_fallback
- action: action_default_ask_affirmation
用户不喜欢建议→人工交接
第二个规则是当用户不想继续任何提供的建议并点击一个类似“这些都不要”的选项时(我们也将实现这个选项)。它被映射到默认的out_of_scope
意图。
当这种情况发生时,我们希望将用户直接连接到人,所以我们将调用action_default_fallback
。
- rule: Single stage fallback | call default fallback if user is not ok
steps:
- action: action_default_ask_affirmation
- intent: out_of_scope
- action: action_default_fallback
覆盖默认确认自定义操作
我们将做两件事来解决上面提到的问题:
- 在意图和易读的措辞之间建立一个映射。类似于:
supply_contact_info --> "Supply Contact Information"
- 向用户提供前三个意向预测作为建议,而不仅仅是前一个,这样用户更有可能找到他们想要的。
class ActionDefaultAskAffirmation(Action):def name(self):
return "action_default_ask_affirmation"async def run(self, dispatcher, tracker, domain):
**# select the top three intents from the tracker**
**# ignore the first one -- nlu fallback**
predicted_intents = tracker.latest_message["intent_ranking"][1:4]**# A prompt asking the user to select an option**
message = "Sorry! What do you want to do?"**# a mapping between intents and user friendly wordings
** intent_mappings = {
"supply_contact_info": "Supply Contact Information",
"affirm": "Agree",
"deny": "Disagree",
"goodbye": "End conversation"
}**# show the top three intents as buttons to the user**
buttons = [
{
"title": intent_mappings[intent['name']],
"payload": "/{}".format(intent['name'])
}
for intent in predicted_intents
]**# add a "none of these button", if the user doesn't
# agree when any suggestion**
buttons.append({
"title": "None of These",
"payload": "/out_of_scope"
})dispatcher.utter_message(text=message, buttons=buttons)return []
注意对于某些意图,您可能不希望它们作为建议出现。“你想问候我吗?”听起来不错。如果这样的意图是前三个中的一个,可以添加一个简单的检查来确保用户不会把它看作是一个建议。
记得将action_default_ask_affirmation
作为注册动作添加到您的域中。
测试
我们将尝试与之前相同的对话流程。
测试单级回退机制—图片由作者使用 carbon.now.sh 提供
这比以前好多了。现在让我们尝试第三种策略。
两阶段回退
两阶段回退是最近才引入的。它让用户两次阐明自己(通过向他们建议可能的行动),然后开始最终的后退。
两阶段回退机制—作者图片
虽然看起来很复杂,但是 Rasa 已经实现了,所以设置起来要容易得多,特别是因为我们已经用前两种方法完成了一半的工作。
添加要求用户重新措辞的响应
Rasa 提供了名为utter_ask_rephrase
的默认响应。当然,还有一个名为action_default_ask_rephrase
的默认自定义动作,您可以覆盖它来实现自定义行为,就像我们之前对action_default_fallback
和action_default_ask_affirmation
所做的那样。
现在,我们将只使用响应。我们将在domain.yml
的responses
字段下定义它。
utter_ask_rephrase:
- text: I'm sorry, I didn't quite understand that. Could you rephrase?
添加两阶段回退规则
我们将用这个替换以前的单阶段回退方法的规则。
# two stage fallback:
- rule: Implementation of the Two-Stage-Fallback
steps:
- intent: nlu_fallback
- action: action_two_stage_fallback
- active_loop: action_two_stage_fallback
没有必要进行其他更改。
测试
流程是这样的:
测试两阶段回退策略—图片由作者使用 carbon.now.sh 提供
当人工交接不成功时你会怎么做?
有时,即使在用户多次澄清后,你的机器人可能不明白他们想说什么。在这种情况下,明智的做法是让用户知道他们现在将连接到一个人,然后将对话转移到一个人可以访问的地方。
发送跟踪器和帮助,因为人类现在已经有了对话的上下文,这意味着用户不必从头开始他们的请求。
即使在这之后,也可能没有人来照顾这个用户。可能用户处于不同的时区,或者所有客户服务代理都被占用。有时,将用户连接到代理的机制可能会中断。在这种情况下,与其让用户等待不知道如何继续,不如通知他们这个问题是有意义的。
除了一条简单的消息,说明人工代理目前不可用,以及一些功能,让用户留下消息或提供他们的联系方式,以便以后可以联系到他们。
示例代码
https://github.com/Polaris000/BlogCode/tree/main/FallbackExample
该系列的其他文章(这是第三篇)
第一部分:用 Rasa 构建聊天机器人
第二部分:槽和实体一样吗?
第四部分:聊天机器人是如何理解的?
结论
不管你的机器人有多好,总会有机器人不理解用户想说什么的情况,尤其是在开发的早期。你可能有很好的数据来训练你的机器人,但它可能不代表真实用户会如何交谈。
在构建聊天机器人时,学习如何优雅地失败是很重要的。毕竟,即使作为人类,由于从语言到糟糕的网络质量等过多的沟通问题,我们也可能经常不得不这样做。
最终,我写这篇文章的目的是让你知道在为你的机器人设计后备策略时可能会发生什么。
希望有帮助!
更新
20.3.2022
添加到系列中其他帖子的链接
处理数据偏差
走向伦理人工智能的旅程
由路易斯·里德在 Unsplash 上拍摄的照片,由作者编辑
如果你也有一个愿景,那就是确保你正在开发的产品遵循“人工智能永远有效”的所有书面规则,那么你肯定会遇到你的数据有偏差的情况。
有偏见的模型、有偏见的数据或有偏见的实现——是数据科学家生活中的典型悲哀。因此,首先我们需要理解并承认偏见的存在,并且可以采取任何形式。
是的,偏差是一个宽泛的术语,它可以出现在数据收集、算法中,甚至出现在 ML 输出解释阶段。
由作者使用 PowerPoint 创建
偏见为什么会伤人?
基于种族、年龄或性别等人类特征的偏见会导致不同的机会,应该予以制止
根据斯坦福大学的人工智能指数报告,人工智能/人工智能组织认为以下风险在行业中普遍存在,并且正在努力减轻这些风险,因为它们对他们的业务和人类普遍有害。
数据偏差可以有多种形式:
- **结构偏差:**数据可能会有纯粹的偏差,因为它是由结构差异决定的。女性代表护士、厨师、教师,这显然是从社会结构中衍生出来的。一家电子商务巨头试图建立一个招聘工具,以获取他们现有员工的细微差别,这不用说,是有偏见的。许多属性,如运动、社交活动、成就等,都是由机器挑选的,这导致了一个偏向男性的工具。
- **数据收集:**数据收集偏差的可能原因可能基于一天中的时间、人群的年龄组、原籍国、阶级阶层等。输入算法的数据应该不断更新,以反映我们生活的世界的真实情况,进而反映我们想要预测的世界的未来状态。
- **数据操作:**更容易删除没有附加标签或缺少值的实例。但重要的是要检查被剔除的观察值是否会导致性别、种族、国籍和相关属性的错误数据。
- **算法偏差:**算法将学习数据模式建议它学习的内容。该算法要么反映了普遍的偏见,要么放大了这些偏见,这是我们最担心的。如果判断偏向于特定人群,那么机器也会从训练数据中学习。算法中的偏差源于数据,这些数据要么不是正确的代表,要么是源于现存的偏见。如果输入数据是不平衡的,那么我们需要确保算法仍然看到足够多的少数类实例,以便对其执行良好。有多种方法可以实现数据重新平衡,主要方法包括创建合成数据,或者分配类权重,以便算法对少数类的每个错误预测施加更高的惩罚。
- **实现偏差:**所有的 ML 模型都建立在训练和测试数据集应该属于相似分布的基本假设之上。根据夏季数据训练的模型可能具有不同的特征分布,因此不适合预测冬季的消费者行为。只有当新数据与过去观察到的数据相似时,该模型才会表现良好。不仅仅是执行,解释也可能有偏差。如果我们在分析算法输出的过程中,试图叠加我们的信念并支持我们的(有偏见的)观点,会怎么样呢?
虽然偏见是我们追求道德人工智能框架时需要改善的因素之一,但减轻它肯定不是小事。
构建“人工智能为善”生态系统的一些重要方面是:
- 数据收集者、开发人员和产品经理通常是在现场工作的人,他们更接近数据。对于组织来说,重要的是提高员工的敏感性,并传播对偏见的可能原因以及如何减轻偏见的认识
- 拥有一位擅长识别偏见来源的专家(人工智能伦理学家)可以帮助企业将他们的愿景与道德框架相一致
- 一个由来自不同团队的人组成的治理团队,如隐私、道德、合规、产品和工程,将有助于提供一个新的视角来识别可能被忽略的偏见。
没有一个规则手册可以立刻阅读和实施,它是一个不断发展的框架。
此外,值得称赞的是,维护一个不偏不倚、公平和可信的人工智能框架的努力不再被视为深奥的东西,而是在全世界范围内获得了正确的关注。
构建机器学习应用程序时处理数据稀缺
启动应用程序的实用技术
图 1 模型生长模拟:从幼苗到健康植物(图片来源: Pixy
数据稀缺是指 a)标记的训练数据数量有限或完全缺乏,或 b)与其他标签相比,某个标签缺乏数据(也称为数据不平衡)。较大的技术公司倾向于访问丰富的数据,尽管他们可能会遇到数据不平衡的问题。较小的技术公司通常会受到标记训练数据可用性有限的困扰。在这篇文章中,我将分享我如何处理可用训练数据量很低的情况的笔记。
当训练机器学习模型时,一般的经验法则是在你的数据集中每个自由度至少有 10 个例子(这篇文章很好地概述了什么是自由度)。随着自由度的增加,对训练合理模型所需的数据量的需求也在增加。
此外,在选择模型类型时,考虑可用的训练数据量也很重要。图 2 比较了传统机器学习模型(示例:逻辑回归)v/s 深度学习方法的离线模型性能度量。当有更多训练数据时,深度学习的表现会好得多。另一方面,当数据量很小时,这种类型的模型过拟合的可能性很高。
图 2 深度学习 v/s 传统 ML 方法相对于数据集规模的性能。
让我们来看一些场景和适合它们的方法。
从启发式开始
图 3 一株幼苗(图片来源: Freepik )
让我们考虑几个数据可用性可能较低的例子:1)一家初创公司正在尝试将机器学习模型添加到其产品中,2)一家大公司的团队创建了一个相当新的产品,并希望应用机器学习来优化某个问题。幼苗的比喻恰当地描述了这种情况(图 3)。在这种情况下,如何构建机器学习模型呢?经历过类似的情况后,我认识到从简单的启发式开始很有效。启发式有几个优点:
- 它们不需要大量的数据,可以通过直觉或领域知识来创建。
- 它们具有高度的可解释性。通常的问题是“为什么一个模型预测某个东西是垃圾邮件?”可以通过查看代码或记录给定查询触发的代码路径来回答。
- 一般来说,它们不会遇到典型的机器学习流水线的问题和复杂性:数据偏斜、代码偏斜、模型陈旧、对特征分布变化的敏感性等。
为了进一步解释这一点,让我们更深入地研究一下这家初创公司的例子——假设这家初创公司的主要产品是一款移动应用程序,它可以根据设备的地理位置来获取相关的本地新闻。在这里建立一个机器学习模型会很难,因为没有带标签的数据。然而,启发式模型仍然是可能的。我们可以考虑几个信号来确定给定新闻文章的排名:1)地理位置匹配后的相关性分数(由底层 IR 系统如 ElasticSearch 返回的分数),2)基于发布时间的文章新近度,以及 3)发布者的预定流行度分数(假设范围是[1,5],其中 5 是最受欢迎的,1 是最不受欢迎的)。这些信号可以用一个线性函数来组合:
w1 * f1 + w2 * f2 + w3 * f3
其中{w1,w2,w3}表示特定信号的强调(也称为权重)的数字概念,而{f1,f2,f3}是上面提到的 3 个信号。检索新闻文章并对其进行排名的逻辑可以用两个阶段来表达:
- 检索给定地理位置的匹配新闻文章(及其相关性分数)。删除相关性分数低于阈值的文章(阈值可以使用轶事示例来选择)
- 对新闻文章的结果集应用线性启发式模型以生成“分数”。根据该分数对文章进行排序,以生成最终结果。
启发式模型中的权重可以被适当地调整,直到新闻馈送的定性分析看起来是最优的。这种方法可以很好地将产品推出市场。随着更多的数据变得可用,直觉/领域知识可以与来自数据的洞察力相结合,以进一步调整启发式模型。一旦你有了足够的用户交互数据,你可以运行逻辑回归来找到正确的权重。接下来的步骤是建立一个模型再培训管道。您的设置越复杂,您就越需要关注数据质量、模型性能质量等。从这一节中要学到的关键点是绝对可以接受用试探法来优化某种产品体验,尤其是当数据量很低的时候。
外部 API
图 4 从农民手中购买水果(图片来源: Pxhere
有些场景需要使用最先进的技术来解决特定产品的问题,而启发式方法不是开始的选择。例如,图像中的对象识别——给定一张图像,识别人、动物、建筑物、车辆等。在这种情况下,有几个 API 提供者: AWS Rekognition , Google Cloud 的 Vision AI 仅举几个例子,它们提供的 API 可以用来检测图像中的对象,执行人脸识别,检测图像中的文本等。用一种可以利用这些 API 的方式来分解你的问题可能是一个起步的好方法。图 4 为这种情况提供了一个类比:你可以从农民那里购买水果,而不是自己种植。这里举个例子:你想构建一个应用程序,识别一个图像中人穿的鞋的具体品牌(让我们把品牌限定为 Adidas、Nike 和 Puma)。设计这个问题的解决方案的一种方法是使用视觉 API 来检测图像中的鞋子以及鞋子上的文本。您可以使用结果信息(鞋上的文本加上检测到的鞋对象)来推断鞋属于特定品牌。与另一种方法相比,这种方法更容易实现,后者需要建立一个模型,在给定输入图像的情况下,直接检测特定品牌的鞋子。考虑到流行的 API 提供商提供的免费层的可用性,成本影响通常很小。
综合数据
图 5 施于土壤以帮助植物生长的肥料(图片来源: Pixabay
解决标签数据缺乏的一个方法是为你的特定问题人工制造合成数据。在机器学习中使用合成数据来捕捉在训练数据集中看不到但理论上可能存在的示例。例如,在构建对象识别应用程序时,用户可能会将相机以某个角度指向某个对象(比如一只鹿),而不是直接指向它。为了正确识别对象,可以旋转原始对象图像,并将其作为具有相同标签的新示例插入到数据集中。这将有助于模型了解在不同角度拍摄的图像对应于同一物体。
合成数据有用的另一种情况是当数据集严重不平衡时,即与其他类相比,某个特定类的代表性过高。这方面的一个例子是垃圾邮件检测问题,其中正面例子的数量比负面例子少(垃圾邮件令人讨厌但很少)。通常,垃圾邮件占整个数据集的 0.1%到 2%。在这种情况下,使用类似于 SMOTE 的技术对于在您的数据集中生成更多稀有标签示例非常有用。但是,这样做也有不好的一面-生成的数据可能不代表真实世界的数据,这可能会导致模型性能出现问题。
迁移学习
图 6 嫁接樱桃树(图片来源:维基百科)
如果您可以使用大量可用的非特定领域数据,并训练一个能够为特定领域任务工作的模型,这不是很神奇吗?这可以通过迁移学习来实现。这项技术本身就值得写一整篇博文,但是我将在这里提供一个简要的概述。
考虑一个例子:你想训练一个分类器,将一条推文分为积极、消极或中性情绪。你只有为数不多的 100 条正面、负面和中性的推文,这些推文是你手动标记的(由你自己或使用外部标记服务,如亚马逊土耳其机械)。你的目标是训练一个与最先进的情感分类模型不相上下的模型。
在这种情况下,你可以利用在 ULMFiT 论文中描述的迁移学习技术(我过去也使用过 ULMFiT)。这个想法是首先学习一个基础语言模型。语言模型本质上是在给定单词输入序列的情况下预测序列中下一个单词的概率的模型。维基百科数据集可以用来训练语言模型(截至 2021 年 1 月,的最新数据集拥有超过 37 亿个单词)。在第一步中,生成的语言模型学习通用领域(维基百科)中的语言表示。第二步是“微调”目标数据集上的第一个模型(使用您的 100 个 tweet 示例)。微调步骤包括与第一步相同的设置(语言建模),不同之处在于模型现在被训练以捕捉目标数据集(tweets)中的特质。这里的直觉是推文中的语言不同于维基百科文章中的语言,因此有必要捕捉这种差异。第三步也是最后一步是训练微调后的模型能够对情绪进行分类,而不是预测下一个单词。一种方法是删除微调模型的最终分类层,代之以用于特定任务的分类层——情绪预测,由用于多类分类问题的 Softmax 层处理。图 7(取自 ULMFiT 论文的图像)显示了训练神经网络的 3 个步骤。更多细节请参考论文【1】。
图 7 乌尔姆菲特的三个阶段(图片来源:乌尔姆菲特论文【1】)
研究表明,迁移学习有助于大幅度提高目标任务的准确性。直觉上,这是有道理的。一个类比是,一个知道如何走路/跑步的人可以比一个试图直接学习如何滑冰/滑雪的人更快地学习如何滑冰/滑雪。
最后,以一个植物类比来结束,图 6 显示了一棵樱桃树被嫁接。嫁接具有类似的优点:a)增强植物对某些疾病的抵抗力 b)通过利用适应的宿主植物等使品种适应不利的土壤或气候条件。
结论
总之,以下是我在处理少量训练数据时遵循的一组通用步骤:
- 尽可能使用启发式方法。例如,由领域专家手动调整权重来优化项目排名是一个良好的开端。
- 如果可能的话,委派。以一种可以利用外部 API 的方式分解问题。
- 试验合成数据,尤其是在数据集严重不平衡的情况下。
- 尝试迁移学习。要知道这一步的时间投入可能很高。
你如何处理数据稀缺的问题?请随意留下你的想法。
参考
- 霍华德,j .,&鲁德,S. (2018)。用于文本分类的通用语言模型微调。 arXiv 预印本 arXiv:1801.06146 。https://arxiv.org/abs/1801.06146v5
在 AWS 中处理地理空间数据
美国宇航局在 Unsplash 拍摄的照片
数据工程
简要介绍如何使用 AWS 存储、处理、分析和可视化地理空间数据
随着数据库、仓库、分析和可视化工具扩展其处理地理空间数据的能力,我们比以往任何时候都更有能力捕捉和使用 GIS 数据。虽然单个产品支持 GIS,但云提供商并没有在早期提供完整的 GIS 解决方案。随着时间的推移,这种情况已经发生了很大变化。像 AWS 这样的云提供商在任何需要的地方都提供支持。对于像 AWS 这样的软件公司来说,我们需要理解他们构建的产品的选择直接受到通常是他们的高收入客户的需求的影响。
现在,许多公司看到了对地理空间数据的需求,并且这些公司能够以合理的低成本获取和存储地理空间数据(因为位置数据无处不在,存储变得越来越便宜),我们已经看到地理空间数据库、分析和可视化工具被更广泛地采用。虽然 AWS 没有一个成熟的地理空间数据解决方案,但它确实在不同的服务中提供了一些很好的功能。在这篇文章中,我们将对此进行一些讨论。
关系数据库
AWS 有两个托管关系数据库服务— RDS 和 Aurora。很明显,您可以使用 EC2 安装自己的关系数据库,但这不是一个托管解决方案,所以我们在这里不做讨论。地理空间数据的高效存储对于我们今后想用这些数据做的任何事情都非常重要。
AWS RDS 只支持它正在运行的任何版本的关系数据库服务,因此对 GIS 的支持取决于数据库的版本。例如,如果您运行 MySQL 的 RDS,那么您将拥有该 MySQL 版本支持的 PostGIS 特性。如果您运行 PostgreSQL,您将需要在 RDS 上运行的 PostgreSQL 上安装 PostGIS 扩展。
在 AWS RDS PostgreSQL 上安装 PostGIS 扩展的示例脚本。
另一方面,使用 Aurora,它是 AWS 为不同的关系数据库编写的一个引擎,您可以在最初版本的关系数据库所支持的通常特性的基础上获得一些额外的特性、改进和优化。例如,阅读Aurora 如何通过使用 Z 顺序曲线优化地理空间索引。
仓库、湖泊和可视化
红移虽然基于红移,但最长时间没有支持地理空间数据,终于在 2019 年推出了对它的支持。目前,在 Redshift 中,我们可以使用 find 和使用原生 GIS 数据类型。这里是 GIS 数据红移支持的函数的完整列表。
https://aws.amazon.com/blogs/aws/using-spatial-data-with-amazon-redshift/
这是针对红移的,但是当你把地理定位数据存储在 S3 时会发生什么呢?你如何质疑这一点?假设您已经以 GeoJSON 格式存储了地理位置数据,那么您应该能够使用 nature GIS 类型在 Athena 中创建一个外部表,并使用 Athena 查询数据。更多细节见下例。
对地理空间数据使用 Lambda & Step 函数的完整数据湖解决方案。
还有一个关于可视化的快速说明——通过 AWS,您可以使用 Quicksight 使用 Athena 和其他地方的数据创建地理/地理空间图表。虽然不如 Looker 和 Tableau 等成熟的可视化工具丰富和强大,但在需要简单的可视化时,Quicksight 确实很有用。
像 SNS,SQS 这样的服务不关心他们收到的是什么样的数据,只要是规定格式的。这意味着如果有一个地理位置数据的生产者,那么它可以被 AWS 中的事件/流服务消费。话虽如此,AWS 物联网事件使处理地理位置数据(一般来说是物联网事件)变得非常容易。
亚马逊定位服务
我不知道这一说法是否适合亚马逊定位服务(ALS),该服务将在不损害数据隐私和安全的情况下,与谷歌地图等公司竞争,因为将有数据永远不会离开 AWS 网络的规定,尽管这可能会产生额外的成本。
https://techcrunch.com/2020/12/16/aws-launches-amazon-location-a-new-mapping-service-for-developers/
我们还没有看到这项服务有多成熟。它也将提供地址转换、细化和规范化吗?几天前,我遇到了一个叫 Placekey 的神奇服务,它试图以极高的准确性解决地址规范化的问题。我们将拭目以待亚马逊是否有锦囊妙计。目前,亚马逊位置服务正在预览中。
结论
随着亚马逊定位服务已经进入预览版,AWS 是否会更加专注于获取更多地理定位数据特定服务,也许是专门的地理定位数据库或可视化引擎?在过去的几年里,AWS 在时间序列、图形、文档数据库市场上占据了一定的份额。更多的地理定位服务可能真的会出现。
从业务驱动的角度处理不平衡的数据分类
Elena Mozhvilo 在 Unsplash 上的照片
我认为数据科学家应该获得的一项关键技能是将实际业务问题转化为数据科学问题的能力。了解所有的机器学习(ML)技术是一件重要的事情;而知道如何将商业洞察力注入 ML 建模则是另一回事。到目前为止,我已经从事了几个不平衡数据集的数据科学项目,我想与您分享一些我从最佳实践中学到的策略。
我注意到许多关于不平衡数据分析的在线资源(博客和教程)主要集中在技术技巧上,包括采样、参数调整和 ML 模型的选择。因此,在这篇博客中,我将更多地从 DS 生产的角度来谈论,并重点关注商业知识将如何帮助建模。我希望你会喜欢阅读它!
1.商业世界中的不平衡数据建模
不平衡的数据集在商业世界中并不少见。通常,在对大目标人群中的低概率事件建模时,会出现这种不平衡。一些例子包括欺诈检测、缺陷产品识别、保险索赔等。大部分不平衡是内在造成的,这是由事件的性质造成的(即像欺诈这样的自然低概率事件)。然而,由于收集数据时的限制,一些数据集可能具有外部不平衡。例如,如果数据收集设备仅限于在白天工作,那么即使事件在白天以恒定的频率发生,在晚上收集的数据观察值也会少得多。
在没有商业知识的情况下,数据科学家通常会开发旨在优化 F1 分数、ROC- AUC、混淆矩阵、精确度和召回率的机器学习模型。然而,在处理业务问题时,实际上,包括成本和/或收入在内的业务背景知识可能会改变游戏规则。考虑到时间、成本等因素和限制,平衡**假阳性(1 型错误)和假阴性(2 型错误)**可能非常复杂。然而,利用业务知识可以帮助处理不平衡的数据建模。
2.处理模型开发过程中的不平衡
通常,我们可以通过以下步骤开发 ML 模型:
作者图片
实际上,业务驱动视角可以应用于所有的步骤。在接下来的部分中,我将介绍如何使用业务知识来创建一个更好的具有不平衡数据的 ML 模型。
2.1 数据准备
在训练模型之前,我们需要很好地理解数据结构。数据操作和特征工程对模型开发至关重要。对于不平衡数据,我们可以考虑潜在分割。这包括以下方面的细分:
1)响应变量
如果响应变量的一个类是多数类,而其他类都是少数类,我们可以考虑合并少数类。例如,当预测一个月内严重龙卷风的数量时,我们可能会看到大多数数据条目有 0 个龙卷风,少数数据条目有 1、2 或 3 个龙卷风。在这种情况下,我们可以通过预测是否会有严重的龙卷风,将响应变量从多类分类变量变为二元变量。
2)独立变量
探究数据总体,找出导致不平衡的原因。例如,如果年轻员工比年长员工更有可能离开公司,我们可以尝试根据年龄对员工进行细分。
除了分段,另一种预处理数据的技术是执行采样方法。你可能已经知道欠采样、过采样、以及其他一些方法,比如 SMOTE(合成少数过采样技术)。在对数据进行抽样时,了解业务知识,如假阴性(FN) 和假阳性(FP) 的成本,可以帮助确定抽样权重。基本上,较高的权重将给予少数阶级,而较低的权重将给予多数阶级。
例如:
- 假设在原始数据中,正负比例为 1:100
- 假设 FP 的成本:10 美元,FN 的成本:100 美元
- 当执行欠采样时,我们可以使用 1:10 的正负权重来表示成本
2.2 模型调整
一些 ML 方法库提供了与权重相关的参数进行调整。例如,在 Python 中,随机森林分类器的一个常用参数是“class_weight”。通过设置’ class_weight='balanced ',模型使用 y 值来自动调整与类频率成反比的权重。或者,我们可以再次使用成本数据,通过惩罚模型中的分类错误来设置权重。与选择采样权重的想法类似,我们希望调整模型,使其符合降低成本和/或增加潜在收入的现实考虑。
2.3 模型评估
对于不平衡数据,在比较模型性能时,使用精确召回(PR)曲线优于 ROC AUC 曲线(参考文献 1):
从上图可以看出,虽然 a 和 b 具有相同的 ROC 曲线,但是 b 的 PR 曲线要比 a 的好很多,说明 a 使用的模型在 a 数据不平衡的情况下并没有很好的表现。
此外,我强烈建议分析混淆矩阵,计算预期损失和/或收入,如果相关业务知识可用的话。同时处理 FP 和 FN 总是困难的,但是分析预期的损失/收益可以将这种权衡情况变成一个优化问题。下面是一个使用商业知识评估模型的二元分类示例:
故障摄像机示例分析
- 问题描述:
项目目标:摄像机的故障检测
二进制响应变量:1 有故障,0 无故障
不平衡数据集: 1% 的摄像机出现故障(响应变量= 1)
- 业务背景:
日产 50k 摄像机,测试产能为 1k 摄像机
每台摄像机的测试费用: $10 ,
检测失败时的损失: $200
- 车型性能:
召回=0.1,因此该模型可以在 1000 个预测中正确检测出 100 个故障摄像机
业务信息如何影响建模?
如上所述,根据现有的业务背景,我们可以通过计算预期损失来回答这个问题,这是我们希望最小化的目标。下表计算了 3 种情况下的预期损失:
作者图片
通过将场景 3 与场景 1(基线)进行比较,可以看出,该模型使用这种 ML 模型可以节省 100k-90k = 10k 。因此,如果我们已经训练了多个 ML 模型,我们可以通过减少这里计算的预期损失来比较它们的性能**。**
从给定的商业信息中,我们还能了解到什么?
值得注意的是测试容量是 1k 相机。有了建模结果,只有预测为阳性的相机才会被测试(这就是使用 ML 模型的目的)。所以测试容量实际上决定了 FP+TP 的上限,也就是模型预测的阳性数。选择< = 1k 预测阳性的型号是有意义的,因为我们被限制测试最多 1k 摄像头。如果模型预测超过 1k 个阳性,我们将没有能力在现实中测试所有预测的相机。
2.4 模型输出
我个人认为交付模型输出是最需要与业务伙伴合作努力的阶段,尤其是当数据科学模型导致一个产品的时候。模型的最终输出应该满足我们想要实现的业务目标。
一般来说,业务背景主要在以下几个方面影响模型输出和后处理:
2.4.1 输出格式
根据模型用户是谁,对输出格式的要求可能会有所不同。就分类而言,交付模型输出的常见格式是类别标签和被分配到某个标签的概率。有时候模型用户只需要知道给出了哪个标签,但是在某些情况下知道概率是必要的,尤其是结果需要排名。
2.4.2 成绩排名
受时间、资源和成本的限制,交付按优先级排序的模型结果更有意义。同样,这也是 DS 和业务合作伙伴应该讨论的地方 1)什么是限制和 2)什么是优先级。
会员制营销实例分析:
- 问题描述:
项目目标:接触有可能购买 VIP 会员资格的客户
二元响应变量:1 潜在客户,0 非潜在客户
不平衡数据集: 10% 的客户愿意购买(响应变量= 1)
- 商业背景:
不同的客户如果加入会员,带来的收益量是不一样的,所以要对模型输出进行排序,使收益最大化。
- 先联系谁?
假设我们有以下 3 个客户的示例模型输出,我们如何根据预测的概率和业务知识对他们进行排序?
作者图片
由于的目标是最大化收益,我们可以根据建模结果计算预期收益。这里我建议一个 EDA 是热图图。在本例中,预期潜在收入的计算方法是模型给出的概率乘以客户购买的收入。下面是基于玩具收入数据创建热图的 python 代码。
```python
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
prob = np.arange(0.1, 1.1, 0.1)
col_prob = ['10%','20%','30%','40%','50%','60%','70%','80%','90%','100%',]
revenue = np.arange(100, 1100, 100)
df = pd.DataFrame(np.outer(prob, revenue), columns=col_prob)
df.index = revenue.copy()
a4_dims = (11.7, 8.27)
fig, ax = plt.subplots(figsize=a4_dims)
ax = sns.heatmap(df.astype('int32'),
cmap='coolwarm',
annot=True,
fmt="d",
annot_kws={'size':12},
cbar_kws={'label':'Potential Revenue'},
square=True)
plt.title('Expected Potential Revenue from New VIP Members')
ax.set(xlabel='Prob. of Purchasing VIP Membership', ylabel='Revenue bought by Customers')

作者图片
从这张热图中,我们可以清楚地看到,虽然客户 A 加入 VIP 俱乐部的概率最高,客户 C 带来的潜在收入最多,但我们想先接触客户 B!
* **进一步的想法**
显然,这里给出的例子是从真实的业务问题中简化而来的。实际上,在绘制热图和排列模型输出时,可能需要更多的考虑。例如,如果接触客户的成本不同,那么这个成本应该是预期收入分析中涉及的另一个因素。
# 3.摘要
用不平衡的数据集建模从来都不是一件容易的事情。但幸运的是,在商业世界中,我们可以注入商业见解,以多种方式帮助模型开发过程。除了你已经尝试过的提高模型性能的所有技术策略之外,我希望这篇博客从不同的角度来看是有用的。感谢阅读!
## 💖喜欢这个故事吗?请随时[订阅 DS 和 ML 粉丝的邮件列表](https://mingjie-zhao.medium.com/subscribe),并且[成为会员](https://medium.com/@mingjie-zhao/membership)!🤩
## 这篇博客最初发表在我的个人网站上。
**参考**
【1】Lever 等人(2016) Nat 方法:分类器性能的图形表示避免了对结果设置精确的阈值,但可能对数据的重要方面不敏感。
# 使用 Synapse SQL 处理 Power BI 中的区域设置字母
> 原文:<https://towardsdatascience.com/handling-locale-letters-in-power-bi-using-synapse-sql-336113b34f76?source=collection_archive---------52----------------------->
## 在 Power BI 中,特殊的字母表字母可能很难处理。还是没有?使用 Synapse 无服务器 SQL 池检查这个简单的解决方案!

[https://unsplash.com/photos/lNpo-WNqBo0](https://unsplash.com/photos/lNpo-WNqBo0)
如果你经常关注我的博客,你已经意识到我更喜欢描述现实生活中的用例,而不是写一些纯理论的东西……不要误解我的意思——当然,在投入真正的乐趣之前,你应该能够理解特定解决方案背后的核心理论和概念基础,但是解决现实世界的问题最终是有价值的。
## 方案
由于我生活在一个除了“常规”字母表之外还有特殊字符的语言区(对于那些不知道的人,我住在奥地利,那里讲德语),我最近在为一个客户创建 Power BI 报告时遇到了一个有趣的问题。
这个“问题”与德语“元音字母”的具体处理有关,这些字母是特殊的字母 ***、ü、o、*** ,它们是德语字母表的“版本”所特有的。事情是这样的,这些字母有时是按“原样”写的,但有时是按发音写的:ae,ue,oe,ss(例如,奥地利汽车驾驶员协会叫做 AMTC,但他们的网站是[https://www . OEA MTC . at](https://www.oeamtc.at)——只是为了说明我的意思)。

[https://unsplash.com/photos/MP88Ac7oOqU](https://unsplash.com/photos/MP88Ac7oOqU)
回到客户最初的请求:他们正在分析奥地利和德国不同城市的特定商店的汽车销售情况。数据被收集到一个 CSV 文件中(我知道,我知道:),然后送到 Power BI 进行进一步分析。
为了清楚起见,这个 CSV 文件只是最终报告中不同数据源之一。
然而,具有挑战性的部分来了:有时,城市名称使用“元音字母”书写,有时使用“发音”逻辑——例如——münich 和 Muenich 杜塞尔多夫和杜塞尔多夫等。
下面是我为本文准备的简化文件中的数据:

作者图片
现在,当我在 Power BI 中导入这个 CSV 文件时,我得到的结果如下:

作者图片
如您所见,Power BI 对数据进行了“原样”解释:这意味着,即使实际上在杜塞尔多夫销售了 83 辆汽车,我们也无法在报告中看到这一数字,因为它是在杜塞尔多夫和杜塞尔多夫之间划分的!同样的情况也适用于名字中包含“元音字母”的其他城市。只有汉堡的结果是正确的,因为这个城市的名称中没有特殊字符。
## 尝试在 Power BI 中寻找解决方案
Power BI Desktop 为您的数据提供了很多设置和调整功能。然而,没有(或者至少我不知道)一个选项来以适当的方式处理这些特殊字符。
在将数据导入 Power BI 桌面之前,我曾尝试将文件来源更改为德语,但发生的情况是元音字母被问号替代:

作者图片
然后,我试图调整当前文件的区域设置,却发现只有数字、日期和时间会受到此设置的影响!

作者图片
我这边有一个重要的声明:我绝不是超级查询专家,也许 PQ 中有一个变通的解决方案来处理这个挑战,但是我在网上找不到任何类似的东西…
因此,看起来我的客户需要清理源端的数据(这当然是所有情况下可能的最佳解决方案,并且应该尽可能地应用),但是现实是残酷的,客户通常希望您代替他们做所有艰苦的工作:))
## Synapse 无服务器 SQL 拯救您!
在为这个问题绞尽脑汁的时候,我决定尝试在 Synapse 无服务器 SQL 池中处理数据。我已经写了这个集成分析平台提供的的[巨大可能性,特别是在与 Power BI](https://datamozart.medium.com/power-bi-synapse-part-1-the-art-of-im-possible-aee1cec5ebfa) 的[协同中,所以让我们看看是否可以利用 Synapse 无服务器 SQL 来解决我们的“变音”问题!](/power-bi-synapse-part-2-what-synapse-brings-to-power-bi-table-4592a03b1b74)
由于它利用常规的 T-SQL 语法从多个不同的源(如 CSV、Parquet 文件或 Cosmos DB)中检索数据,因此无服务器 SQL 带来的一个好处是(几乎)拥有“普通”SQL Server 中的全部功能。
在“普通的”SQL Server 中,可以通过更改数据库排序规则来处理类似的请求,以便能够“识别”德语中的特殊字符。更多关于校勘本身和具体校勘类型可以在[这里](https://docs.microsoft.com/en-us/sql/relational-databases/collations/collation-and-unicode-support?view=sql-server-ver15)找到。
由于我的客户已经在使用 Azure Synapse Analytics 处理各种数据工作负载,并且激活了一个无服务器 SQL 池,因此我决定冒险使用与本地 SQL Server 类似的策略!
但是,首先,我已经从 Synapse Studio 中将我的 CSV 文件上传到 Azure Data Lake Storage Gen2 帐户:

作者图片
如果我从 Synapse Studio 运行以下脚本…
SELECT
TOP 100 *
FROM
OPENROWSET(
BULK ‘https://nikolaiadls.dfs.core.windows.net/nikolaiadlsfilesys/Data/Umlauts.csv’,
FORMAT = ‘CSV’,
PARSER_VERSION=‘2.0’,
HEADER_ROW = TRUE
) AS [result]
我会得到和以前完全一样的结果…

作者图片
因此,让我们更改数据库的排序规则,以便能够识别德语字母:
ALTER DATABASE CURRENT collate German_PhoneBook_100_CI_AI_SC_UTF8;
让我们看看在我运行以下脚本后是否发生了变化:
SELECT
City
,SUM([Sales Qty]) AS TotalSalesQty
FROM
OPENROWSET(
BULK ‘https://nikolaiadls.dfs.core.windows.net/nikolaiadlsfilesys/Data/Umlauts.csv’,
FORMAT = ‘CSV’,
PARSER_VERSION=‘2.0’,
HEADER_ROW = TRUE
)
AS [result]
GROUP BY City
以下是结果…

作者图片
瞧啊。!!这看起来正是我想要实现的!让我们在该数据集上创建一个视图,并在 Power BI Desktop 中使用该视图:

作者图片
现在,当我将两个视图放在一起时——一个基于直接来自 CSV 文件的数据,另一个基于 Synapse 无服务器 SQL 池中“整理”的数据,我得到以下结果:

作者图片
不需要说哪个视觉提供了正确的和预期的结果!
## 结论
正如您所看到的,您不需要使用 Synapse Analytics 和其中的无服务器 SQL 池来管理一些常见的通用任务,例如查询来自各种不同来源的数据(CSV、JSON、Parquet 文件;Cosmos DB 火花表)。
您可以充分利用无服务器 SQL 池的特性来解决一些特殊的挑战,比如我最近在现实生活中遇到的这个问题!
当然,我的场景需要找到德语“元音变音”的特定解决方案,但是如果您正在处理斯堪的纳维亚语、土耳其语或世界上任何包含非标准字母的字母表,您可以执行相同的方法!您只需要为正在处理的各个字符集找到合适的排序规则。
现在,我的问题解决了,客户满意了,所以我可以享受一品脱明希纳啤酒了:)!
感谢阅读!
# 处理缺失数据
> 原文:<https://towardsdatascience.com/handling-missing-data-5be11eddbdd?source=collection_archive---------33----------------------->
## 一个统计学家关于如何(不)做它来保持你的机器学习工作流程的观点。

劳尔·纳杰拉在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
最近,我不禁注意到一些关于流行的机器学习书籍的惊人之处。即使是在解释算法及其应用方面做得很好的最好的标题,也往往会忽略一个重要的方面。在需要统计的严格性来正确做事的情况下,他们经常建议危险的过度简化的解决方案,给像我这样的训练有素的统计学家带来严重的头痛,并对机器学习工作流产生不利影响。
> 即使是最好的机器学习书籍也往往会忽略需要统计严密性才能正确做事的主题,而是提出危险的过于简化的解决方案。
几周前,我写了一篇关于数据测量水平的文章,解释了一种经常被推荐的分类变量编码方式如何与统计理论形成对比,从而导致模型可解释性和易学性的潜在问题。不要犹豫走弯路:
</data-measurement-levels-dfa9a4564176>
还有一个:处理丢失的数据。平视:拜托,不要吝啬——估算,不要落下不完整的观察!

## 不要删除不完整的行
由于大多数机器学习算法不接受 NaN 输入,许多文本建议简单地从数据中删除包含缺失值的行。只要你有大量的数据,也就是说。如果是这样的话,去掉几行就解决问题了,不会有太大影响吧?不对!我敢说,大错特错。
第一,良性原因。不完全完整的行通常也不是完全 NaN。删除它们是摆脱坏 nan 的捷径,但同时也删除了一些非常有效的数据点。一个数据科学家究竟为什么要扔掉有用的数据?
第二,恶性原因。这与所谓的*缺失数据机制有关。*在统计学理论中,它们是遗漏的可能原因。有三种情况:数据可能完全随机丢失(MCAR)、随机丢失(MAR)和非随机丢失(MNAR)。如果你对数据中包含它们的含义以及如何区分它们感到好奇,看看我在 DataCamp 上教的[处理缺失数据的课程](http://datacamp.com/courses/handling-missing-data-with-imputations-in-r)。这里重要的是,只有当数据是 MCAR 时,才能删除不完整的行,这意味着绝对不会影响丢失的数据点。如果数据不是 MCAR(当一个变量中的缺失可以用其他变量来预测时,就会发生这种情况),丢弃不完整的行会给数据带来偏差,这种偏差会转移到基于这些数据构建的任何机器学习模型。
> 如果遗漏的原因不完全是随机的,丢弃不完整的行会给数据带来偏差,这种偏差会转移到基于这些数据建立的任何机器学习模型。
区分 MCAR 和其他机制并不简单。有一些统计测试,人们也需要大量的领域知识。但是为了安全起见,最好完全放弃删除不完整行的做法。

## 不意味着-估算
在顶级机器学习书籍中经常找到的另一个建议是,用这个变量的观察值的平均值来替换每个变量中所有缺失的值。其背后的逻辑是,通过输入平均值,我们不会对模型产生太大影响。拜托,千万别这么做!通过均值估算,我们产生了两个问题:我们减少了均值估算变量的方差,破坏了这些变量与其余数据之间的相关性。
> 通过均值估算,我们产生了两个问题:我们减少了均值估算变量的方差,破坏了这些变量与其余数据之间的相关性。
考虑下面的散点图。它根据健康调查数据显示了人们的身高(厘米)和体重(千克)。这两个变量显然是正相关的,但两者都有一些缺失值。让我们用平均身高(166 cm)来估算所有缺失的身高,用平均体重(67 kg)来估算所有缺失的体重。估算的数据点以橙色显示。

图片来自作者在 DataCamp 教授的 R 课程[用插补处理缺失数据。](http://datacamp.com/courses/handling-missing-data-with-imputations-in-r)
这里发生了什么事?估算值通常是异常值。任何在这些数据上训练的机器学习模型都会被它们忽悠,会产生有偏差的结果。机器学习就是在数据中寻找模式。均值插补通常是在数据中引入人为的错误模式。他们应该如何携手共进?
> 机器学习就是在数据中寻找模式。均值插补通常是在数据中引入人为的错误模式。它们并不齐头并进。
改做什么?这取决于你有多少时间和计算资源。

## 当你有时间的时候做些什么
如果时间和计算资源都很充足,你能做的最好的事情就是运行多重插补。实现这种技术的一种方法是自举。我们来看看是怎么做到的。下图最能说明这一过程:

图片来自作者在 DataCamp 教授的 R 课程中的[用插补处理缺失数据。](http://datacamp.com/courses/handling-missing-data-with-imputations-in-r)
首先,我们通过替换从数据行中取样来创建许多(比如说一千个)数据副本。这叫做自举。然后,我们对每个拷贝应用插补算法。常用的插补算法有 kNN、随机森林或基于迭代模型的插补。这样,我们就有了一千份不同的、完全完整的数据集。接下来,我们对每个副本的原始数据做任何我们想做的事情:训练一个模型,计算一些汇总统计数据,你能想到的。最后,不管我们最后的结果是什么(模型预测,一个描述性统计?),我们对所有自举和估算数据集的值进行平均,以获得结果的分布。
这可能看起来有点让人不知所措,但实际上实现起来相当容易,Python 和 R 中都有包可以做到这一点。查看[我的课程](http://datacamp.com/courses/handling-missing-data-with-imputations-in-r),学习如何自己动手。
在多重插补的诸多优点中,最重要的一个优点是您可以获得结果的概率分布,这允许量化您的模型或分析的不确定性,包括插补的不确定性(毕竟,插补只是一个有根据的猜测,而猜测伴随着不确定性)。我在这里写了更多:
</uncertainty-from-imputation-8dbb34a19612>
多重插补的缺点是速度会很慢。如果没有那么多时间呢?

## 当你没有时间的时候,该做些什么
一个快速但合理的解决方案是所谓的热卡插补。要估算变量中的缺失值,您可以选择一个或多个与所讨论的变量相关的其他变量。然后,通过这些选定的变量对数据集的行进行排序。最后,从上到下循环遍历这些行,用同一个变量中以前的非缺失值替换每个缺失值。
这样,从中借用值的行与粘贴值的行相似。这避免了均值插补的大部分缺点,而且速度也相当快。

感谢阅读!如果您有兴趣了解关于缺失数据、各种插补方法以及如何将插补不确定性纳入建模的更多信息,请查看 DataCamp 上的 [**我的课程**](https://datacamp.com/courses/handling-missing-data-with-imputations-in-r) 。
如果你喜欢这篇文章,为什么不 [**订阅邮件更新**](https://michaloleszak.medium.com/subscribe) 我的新文章呢?并且通过 [**成为媒介会员**](https://michaloleszak.medium.com/membership) ,可以支持我的写作,获得其他作者和我自己的所有故事的无限访问权限。
需要咨询?你可以问我任何事情,也可以在这里 为我预约 1:1 [**。**](http://hiretheauthor.com/michal)
也可以试试 [**我的其他文章**](https://michaloleszak.github.io/blog/) 中的一篇。不能选择?从这些中选择一个:
</monte-carlo-dropout-7fd52f8b6571> </calibrating-classifiers-559abc30711a> </working-with-amazon-s3-buckets-with-boto3-785252ea22e0>
# 机器学习中缺失数据的处理
> 原文:<https://towardsdatascience.com/handling-missing-data-in-machine-learning-d7acac44bef9?source=collection_archive---------24----------------------->
## 构建 ML 模型时如何处理缺失数据

凯利·西克玛在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
在真实的(表格)数据集中,您通常会意识到并非所有的特性都适用于您的所有行。学习处理这种现象将允许你仍然利用你的数据集,甚至当一些数据丢失的时候。
## 缺失数据的类型
有多种原因可能导致数据丢失,例如数据损坏、在特定时间段内缺乏数据可用性或数据收集过程中的偏差(例如,调查中特定类别的受访者将答案留空)。
这里要做的重要区分是你丢失的数据是否是随机发生的*。*
*缺失数据有三种情况:*
* ***完全随意失踪(MCAR)。**对于所有可能的数据点,数据缺失的概率是相等的。*
* ***随机失踪(MAR)。**数据丢失的概率在所有数据点上并不相等,但是*在*已知的数据类别中,丢失数据的概率是相等的。例如,只有在特定国家的答复中,数据缺失的概率可能是相等的。*
* ***不随意遗漏(MNAR)。**数据丢失的概率在所有数据点上并不相等,并且不存在已知的数据分组,其中丢失数据的概率相等。*
## *删除方法*
*处理缺失数据的第一类方法是从数据集中删除数据。这些通常都是简单的方法,旨在删除因损坏太严重而无法使用的数据点或列。*
1. ***删除缺少太多列值的行。**在 MCAR 和火星方案中,这可能在不显著改变整个数据集分布的情况下完成。但是,在 MNAR 场景中,这可能会使您的数据集产生偏差,因为数据移除过程会偏向于导致较高数据丢失率的过程所生成的点。这并不一定是坏事,尤其是在丢失了太多可用数据的情况下,但这是需要注意的。*
2. ***删除缺少太多值的列。**一些特征可能仅仅是低质量的,并且具有过高的空率以至于不能用它们来建立模型。特别是在生产机器学习设置中,删除通常不可用或低质量的功能通常是一个好主意。*
## *插补方法*
*另一类处理缺失数据的方法包括尝试填充或估算缺失数据。这通常包括学习对数据集进行建模,以便推断出缺失的值应该是什么。*
1. *常数/平均值/中位数插补。这些是最简单的插补方法,最多只是计算一些简单的列统计数据。对于每一列,以及在 MAR 方案中的每一组中,您可以用常数值或可用特征值的某一列统计数据(例如,平均值或中值)来替换缺失值。*
2. ***回归插补。**一种更复杂的数据输入方法包括建立一个预测模型,该模型可以推断缺失数据的值。这些方法更强大,能够理解数据的底层结构,以便为缺失数据提供更智能的猜测。因此,例如,如果我们有 5 个特征和 1 个缺失,我们建立一个模型,在给定其他 5 个特征的情况下预测缺失的 1 个。这种方法的一个警告是,它可以人为地加强数据中的关系,并产生“好得难以置信”的估计,这只会加强数据集中的偏差和相关性。因此,这些估算值可能低估了数据集中的方差。*
3. ***多重插补。**该方法是为估算值创建更稳健估计的一种方式,可以利用上述两种方法。在多重插补中,您创建 N 个不同版本的插补数据集,然后将 N 个值汇集在一起,以产生一个最终数据集(通过简单的方法,如平均,或一些更复杂的统计方法)。多重插补的结果比单一插补的结果偏差更小,更能代表数据集的方差。*
# 像专家一样处理“缺失数据”——第 1 部分——删除方法
> 原文:<https://towardsdatascience.com/handling-missing-data-like-a-pro-part-1-deletion-methods-9f451b475429?source=collection_archive---------15----------------------->
## 数据科学。分析。统计学。Python。
## 面向 21 世纪数据科学家的基本和高级技术

艾米丽·莫特在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
正如我们在致力于缺失数据系列的[第一篇文章](/the-three-types-of-missing-data-every-data-professional-should-know-d988e17d6ace)中提到的,关于“缺失”的机制或结构的知识是至关重要的,因为我们的反应将依赖于它们。
虽然处理缺失数据的技术越来越多,但我们在下面讨论一些最基本到最著名的技术。这些技术包括数据删除、常数单和基于模型的插补,等等。
在我们开始讨论它们之前,请注意这些技术的应用需要数据科学家的洞察力。即使我们可以确定遗漏的机制,也需要其他信息,如数据收集和方法学来选择最合适的技术。
因为我们将要讨论的技术范围相当广泛,所以让我们把它们分割开来,使它们更容易理解。对于文章的这一部分,我们将关注:**删除方法**。
# 成人收入数据集
为了加强我们的理解,让我们使用一个数据集,特别是来自 [UCI](https://archive.ics.uci.edu/ml/datasets/adult) 的成人收入数据集。
在我们开始之前,为这个数据集执行 EDA 是很重要的。变量密度的知识将在选择合适的技术时派上用场。[关于这个](/dramatically-improve-your-exploratory-data-analysis-eda-a2fc8c851124)见我的文章。
## 预赛
import random
import numpy as np
import pandas as pd#For data exploration
import missingno as msno #A simple library to view completeness of data
import matplotlib.pyplot as pltfrom numpy import random
%matplotlib inline
## 加载数据集
df = pd.read_csv(‘data/adult.csv’)
df.head()

通过缺失 no 包实现完整性可视化。我们的数据在分类变量中缺少一些值。
## 模拟失踪
让我们模拟一些连续变量的缺失:*年龄*和 *fnlwgt* 。注意,这里的收入是目标变量,是分类变量。
#Random Seed
random.seed(25)#Percentage Missing
percentage_missing_1 = 0.15
percentage_missing_2 = 0.10#Number of Observations
obs = df.shape[0]#Simulation
df[“fnlwgt”] = random.binomial(1,(1-percentage_missing_1), size=obs)*df[‘fnlwgt’]
df[“age”] = random.binomial(1,(1-percentage_missing_2), size=obs)*df[‘age’]
df[“fnlwgt”] = df[“fnlwgt”].replace(0, np.nan)
df[“age”]= df[“age”].replace(0, np.nan)msno.matrix(df)

在我们模拟了一些缺失数据后的完整性可视化。
“最终重量”变量的缺失数据最多。这与我们的缺失数据模拟是一致的。请注意,我们采用的机制仅仅是 MCAR。
“最终重量”变量的缺失数据最多。这与我们的缺失数据模拟是一致的。
既然我们有了丢失数据的数据集,我们现在可以继续检查我们的不同技术如何影响数据集,以及使用这样的数据集的结果。
# 数据删除方法
所有数据科学博客(*甚至一些发表的文章*)中最简单的数据处理方法是数据删除。但是正如我们在引言中提到的,数据删除会降低我们的模型的有效性,特别是如果丢失的数据量很大的话。

缺失数据的数据删除方法总结
## 列表法\完全案例法
从名称本身来看,只要缺少一个值,listwise 或 complete 方法就会删除一个观察值。如果应用不小心,回顾一下这是如何减少我们的观察的:
df1 = df.copy()
df1.dropna(inplace=True)
df1.shape[0]

我们的观察失去了 30%的原始价值。
我们失去了 30%的观测数据,这是一个很大的数字!这是显而易见的,因为整个数据集中使用的 *dropna()* 会丢弃所有的观察值,只要有一列丢失。
这种方法有一些优点,例如简单高效,但请注意,这种方法仅适用于 MCAR 数据集。
**应用列表删除前**
在决定如何处理缺失数据之前,尤其是如果您计划应用列表式删除,您需要首先确定研究的相关变量。
如果不需要该变量,则特定项是否丢失并不重要,并且应该在应用列表式删除之前将其排除在 dataframe 的子集中。
例如:如果我们认为最终权重与我们的研究无关(例如*预测收入等级*),我们可以将其从我们的特征数据框架中排除。
df2 = df.copy()
df2 = df2.loc[:, df2.columns != ‘fnlwgt’]

排除一个变量只会导致总观察值的 17%下降。
通过这一额外的步骤,我们能够从浪费的删除中节省额外的 6,182 个观察值。对于一些研究来说,这一小步可能是你所追求的目标精确度的差异制造者。
列表删除主要用于 MCAR 数据。由于数据是以完全随机的方式丢失的,假设我们没有删除大量的观测数据,那么我们可以假设删除操作几乎没有丢失任何信息。
列表删除在大多数回归和监督学习方法中使用,包括主成分分析。(PCA)
## 成对删除\可用案例方法
与列表删除相比,可用案例方法使用所有可用的观察值。也就是说,如果观察的特征/变量丢失,使用该特征/变量的方法或技术仅丢弃具有丢失信息的变量,而不是整个观察。
例如,如果我们的数据框架中的上述观察值不包含“最终重量”值,则不会为该观察值计算需要最终重量值的测量/指标或参数。其他一切都将继续利用这个观察。
这种方法如此不受重视,以至于大多数人都没有意识到这是相关性分析中使用的方法。要了解这一点,请访问:

请注意,我们使用了带有缺失值的原始数据帧,并且仍然可以计算相关性。
除了相关性分析,成对方法还用于因子分析。对于那些正在计算的人
## **可用项目**
用于复合变量的**创建的方法是可用项方法。这种方法与可用案例方法一样,使用所有可用的信息。**
可用项目方法通过以下方式汇总**相关项目**:
1. 首先应用标准化方法,例如 z 分数。
2. 此后,**变换后的变量不是被相加,而是对每个观察值进行平均。**
因此,现在可以创建一个综合分数。
现在,这被称为删除方法,因为**它没有试图替换丢失的值。**
如果您计划创建综合分数,可以简单地应用此算法。
# 结束语
本文介绍了用于处理缺失数据的第一类技术——删除。
删除的主要优点是简单,而主要缺点是失去了统计能力。但正如我们将在下一篇文章中看到的,另一类技术,即插补,在某些情况下也有缺点,即缺失数据专家宁愿使用删除方法。
最后,我们上面提到的任何技术的应用,需要由研究者的目标、数据收集方法和遗漏的潜在机制所引导的判断。
在下一篇文章中,我们将讨论插补方法。
[像专家一样处理“缺失数据”——第 2 部分:插补方法](/handling-missing-data-like-a-pro-part-2-imputation-methods-eabbf10b9ce4)
[像专家一样处理“缺失数据”——第 3 部分:基于模型的&多重插补方法](/handling-missing-data-like-a-pro-part-3-model-based-multiple-imputation-methods-bdfe85f93087)
# 参考
麦克奈特,P. E. (2007)。*缺失数据:温柔的介绍*。吉尔福德出版社。
# 像专家一样处理“缺失数据”第 2 部分:插补方法
> 原文:<https://towardsdatascience.com/handling-missing-data-like-a-pro-part-2-imputation-methods-eabbf10b9ce4?source=collection_archive---------4----------------------->
## 数据科学。分析。统计学。Python。
## 面向 21 世纪数据科学家的基本和高级技术

乔恩·泰森在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上的照片
正如我们在致力于缺失数据系列的[第一篇文章](/the-three-types-of-missing-data-every-data-professional-should-know-d988e17d6ace)中提到的,关于“缺失”的机制或结构的知识是至关重要的,因为我们的反应将依赖于它们。
在[像专家一样处理“缺失数据”——第 1 部分——删除方法](/handling-missing-data-like-a-pro-part-1-deletion-methods-9f451b475429)中,我们已经讨论了删除方法。
对于文章的这一部分,我们将关注**插补方法**。我们将比较对数据集的影响,以及每种方法的优缺点。
# 加载数据集并模拟缺失
加载成人数据集并模拟这篇文章[中的 MCAR 数据集。](/handling-missing-data-like-a-pro-part-1-deletion-methods-9f451b475429)
# 插补方法
现在我们有了一个数据集来实践我们的插补,让我们开始讨论这些是什么。
以下是我们在研究中可以采用的现代插补方法的总结:

现代缺失值插补的技术类别和方法
当我们将讨论背后的理论和概念时,让我们使用 *Scikit-learn* 来为我们做脏活。
## 常数替换方法
常数插补是处理缺失数据时最常用的单一插补方法。
常数插补方法在替换观察中的缺失数据时插补一个常数值。很简单,这种技术有各种各样的变化,数据科学家也有一些方法让这种技术更有效。
**表示替换**
对于平均值替换,缺失值将替换为要素的算术平均值。
#Import the imputer
from sklearn.impute import SimpleImputer
#Initiate the imputer object
imp = SimpleImputer(missing_values=np.nan, strategy=‘mean’)
#Isolate the columns where we want to import
X = df[[‘age’, ‘fnlwgt’]]#Fit to learn the mean
imp.fit(X)#Impute
imp.transform(X)

向量间的平均插补比较

均值插补保留缺失值数据集的均值。然而,这种方法的一个很大的缺点是降低了方差。

估算数据集的方差显著较低。
均值插补保留了缺失值数据集的均值,正如我们在上面的例子中看到的。然而,只有当我们假设我们的数据是正态分布时,这才是合适的,因为通常假设大多数观察值都在平均值附近。对于少量丢失数据的情况,这也非常有用。
均值插补的主要缺点是,它往往会对某些参数,尤其是方差产生有偏估计。这个事实影响了置信区间的构建,这对于一些研究人员来说是一个严重的问题。
有一种方法可以稍微弥补这一点,这适用于所有的常数替换方法:人们可以为不同的子群估算不同的平均值。例如,对于“年龄”值插补,您可以选择为所有缺少年龄值且属于男性组类的观察值插补男性的平均年龄。
**注意,对于用整数表示的变量,如年龄,您可以在插补后向上或向下取整**。
**最大似然均值替代**
最大似然(ML)法是一项惊人的技术,它具有最大的**恢复真实总体参数**的能力。
ML 方法被高度赞扬和使用,因为它们利用数据集的每一个观察来估计总体参数。**因此,如果您的数据集是 MCAR,它具有最大的收敛概率。**
我们将在“基于模型的”数据扩充文章中详细讨论这一点及其背后的数学原理,但现在,让我们来计算数据集的最大似然均值。
from scipy import stats
from scipy.optimize import minimizedef fnlwgt_ML_mean(params):
mean = params[0]
sd = params[1]# Calculate negative log likelihood
nll = -np.sum(stats.norm.logpdf(df[‘fnlwgt’].dropna(), loc=mean, scale=sd))return nllinitParams = [7, 12]fnlwgt_results = minimize(fnlwgt_ML_mean, initParams, method=‘Nelder-Mead’)
mle_fnlwgt = fnlwgt_results.x[0]
这导致对平均值和标准偏差的以下估计:

看估算;它们是我们完整数据集的近似估计。
如果你比较一下我们所拥有的:

这些估计值可以与 MCAR 数据集的估计值进行比较。这种差异是由于我们过于简化的分布假设(回想一下,年龄变量是右偏的)。
对于较小的数据集,只要我们得到正确的分布假设,那么均值的最大似然估计实际上可能比普通均值估计更好。
得到估计值后,你可以把它作为常数代入估算值。
使用最大似然法的一个特别的缺点是我们需要假设数据的分布。在这方面,预先了解发行版或一些初步的 EDA 可能会有所帮助。此外,与平均值和中值常数替换不同,对每个特征进行单独的 MLE 计算。
**中位数替代**
对于中位数替代,中位数而不是平均值被用作缺失观察值的替代值。
#Median Imputer
imp = SimpleImputer(missing_values=np.nan, strategy=‘median’)
imp.fit(X)
中位数替代虽然可能是偏斜数据集的一个好选择,但会使数据集的均值和方差都有偏差。因此,这将是研究者需要考虑的因素。
**零插补**
对于某些类型的研究,更自然的做法是将缺失变量估算为零(“0”)。零分对于本质上是社会性的变量来说可能是有意义的,比如“兴趣的消退”,或者对于那些在考试中没有出现的人来说,他们自然得到了零分。
当然,只有零是有效值的变量才有可能,所以这对于参与者不是真正的新生儿的年龄变量是不可能的。
**模式插补**
从名称本身来看,模式插补估算特定变量的“最频繁”值,对于正态分布变量来说,这可能是一个不错的方法选择。
## 随机替换方法
与常量值替换方法相反,随机替换方法用随机生成的值替换丢失的数据。
有两种方法可以实现这一点:
1. **使用经验数据**——如果你熟悉 bootstrap 方法,那么你可以把这个看作类似于那个。这意味着用于替换缺失值的观测值来自数据集本身的可用数据。
2. **使用统计分布** —如果我们知道一个变量的分布,我们可以从理论/统计分布中抽取样本(又称为*参数化*)。为此,我们可以用最大似然估计代替参数,因为它们被认为更稳健。
下面我们试着讨论一下*经验*随机替换的一些方法。
**热甲板法**
热卡方法是用从当前数据集中随机选择的值替换缺失值的方法。这与冷盘方法形成对比,在冷盘方法中,您可能有一个单独的数据集可以从中随机提取值。
例如,对于我们的成人数据集,如果一个人忘记报告他/她的年龄,这种方法将从那些已经报告了年龄的数据中选取一个随机值。
random.seed(25)
df4 = df.copy()#For Weight
df4.loc[:,‘fnlwgt’] = [random.choice(df4[‘fnlwgt’].dropna()) if np.isnan(i) else i for i in df4[‘fnlwgt’]]df4.loc[fnlwgt_missing,‘fnlwgt’]

该算法使用从该变量的可用数据中随机选择的方法来估算缺失值。

热卡插补可能导致标准差高于原始值。
热卡插补可能导致标准差高于(或低于)我们的完整数据集,当然,这并不比用于置信区间构建的低估(或高估)值更好。
与均值插补一样,您可以使用亚组进行热卡插补(例如,不是从整个数据集,而是从该数据集的子集,如男性亚组、25-64 岁亚组等,进行随机选择插补。).
**冷甲板方法**
可以从一个单独的数据集中引入一个替换值,该替换值类似于带有缺失值的数据集。
例如,您可能想要研究两个群体,其中的人口是同质的,但您只是碰巧将他们分成两组(例如,星期一组和星期二组)。如果您有星期二组的缺失值,比如说年龄,*,前提是两个组都是同质的并且随机分配*,那么可以使用来自星期一组的随机选择的年龄值来填充缺失的年龄。
可以使用训练数据集的两个子组来实现冷甲板,正如我们对验证所做的那样。注意不要使用测试数据集中的数据,以避免数据泄漏。
## 基于模型的替换方法
基于模型的替换方法用于生成参数估计,条件是我们拥有的给定数据、观察到的变量之间的关系以及基础分布所施加的约束。
因为我们利用了底层的分布,我们称这些方法为“基于模型的”。
基于模型的方法包括马尔可夫链蒙特卡罗(MCMC)、最大似然、期望最大化算法和贝叶斯岭。
也可以使用决策树和额外的树,尽管它们没有包含在原始方法中(那些严重依赖数据分布的方法)。我们将在这里包括这些,因为它们无论如何都是机器学习中的有效模型。
由于这些都是美丽而复杂的技术,我们需要在一篇单独的文章中讨论它们,这样我们才能更深入地欣赏它们。
## 非随机替换:一个条件
**组均值/组中值**
我们已经在常数替换方法一节中讨论了非随机替换。
对于组均值和组中值,我们不是为所有缺失值输入单个值(均值或中值),而是将观察值分成子组,并为这些子组中的缺失值输入均值/中值。
性别分组的例子有男性和女性分组,对于年龄变量(*,正如我们所看到的可能是正偏态的*),我们可以使用定制的年龄组。
例如,如果我们示例中的最终权重值缺失,那么我们可以将子组划分为工作类别,获得相应的平均值/中值,并分别估算子组中缺失的值。
df5 = df.copy()#Accomplish Using Transform Method
df5[“age”] = df5[‘age’].fillna(df.groupby(‘workclass’)[‘age’].transform(‘mean’))df5[“fnlwgt”] = df5[‘fnlwgt’].fillna(df.groupby(‘workclass’)[‘fnlwgt’].transform(‘mean’))

使用“工作类别”变量,您可以获得每个工作类别的平均值,并将平均值“年龄”或“fnlwgt”替换为这些类别的缺失值。
使用 *groupby()* 方法,您可以创建多个分组级别,比如在工作班之后,您可以进一步按教育级别分组。
只要有助于你的研究,你可以在小组讨论中发挥创造力和探索性。
**上次观察结转(LOCF)**
对于一些时间序列数据,丢失数据的主要原因是“损耗”。例如,假设你正在研究一个特定人的减肥计划的效果。如果在最后一次观察之前,您看到了持续的改进,那么第一次遗漏的观察可以假定为与最后一次观察值大致相同。这里的自然减员是因为那个人已经达到了他/她的理想体重。
假设您的行是每年排列的:
#Just assuming the the variable below is a time series data
#df[‘column’].fillna(method=‘ffill’)#Another implementation but combined with groupmeans method
#df[‘age’] = df.groupby([‘workclass’])[‘age’].ffill()
如果您将此方法应用于非时间序列数据集,则此方法被视为“热卡”方法,因为它使用数据集的实际观测值。然而,应小心谨慎,因为这可能并不完全适用于许多情况,因为它已被证明会使参数估计产生偏差并增加 1 类误差。

组均值插补的均值和标准差计算。
**下一次观测向后进行(NOCB)**
在精神上类似于 LOCF,“下一个观察结转(NOCB)”携带随后的值,但不是向前,而是向后。如果你听说过术语“回填”,这基本上就是那个过程。
如果你仔细想想,有很多这样的例子。比方说,你正在研究不同测试对象的工资增长。如果你知道公司在某一年没有给员工加薪(例如,在 T2 的 COVID-疫情法案期间),你可以用当年的工资来填充过去的几年。
与 LOCF 一样,这适用于时间序列数据,但也有同样的缺点。
#Assuming a time-series variable
df[‘variable’].fillna(method=‘backfill’)#Another implementation but combined with groupmeans method
#df[‘age’] = df.groupby([‘workclass’])[‘age’].ffill()
## 非随机替换:多个条件
**平均先前/平均后续观察值**
在某些情况下,我们可以做的不是只依赖于一个先前的或一个向后的观察,而是对几个观察进行平均。
例如,对于涉及股票或证券价格的研究,这无疑是首选。
假设您想要计算三(3)个周期的平均值并将其结转,您应该使用的代码是:
df6[[‘age’, ‘fnlwgt’]]= df6[[‘age’, ‘fnlwgt’]] = df6[[‘age’, ‘fnlwgt’]].fillna(df6[[‘age’, ‘fnlwgt’]].rolling(3,min_periods=0).mean())
相反,如果我们想要回填的三(3)个周期的平均值:
df7 = df.copy()#Rough codes as I can’t find a more elegant solution to thisdf7[[‘age’, ‘fnlwgt’]] = df7[[‘age’, ‘fnlwgt’]].iloc[::-1].rolling(3, min_periods=0).mean().iloc[::-1]
**回归和带误差的回归**
回归和带误差的回归方法通过基于数据集中的其他变量预测变量来填充变量的缺失值。
在某种程度上,您可以将其视为线性回归模型中作为目标变量的缺失值。想想看,当你使用任何监督学习模型时,你是在试图预测或找到一个未被观察到的结果。和缺失数据本身都是无法观察到的结果。
预测值可以使用数据集中的所有其他变量,或者只是其中的一个子集。
我们可以从头开始编写一个代码来完成这项工作,但是让我们简单地使用一个可用的包: ***autoimpute*** 。
在您的终端上运行`pip install autoimpute`之后,我们可以运行下面的代码:
from autoimpute.imputations import SingleImputer, MultipleImputerdf8 = df.copy()# create an instance of the single imputer and impute the data
with autoimpute, you can choose a strategy per category or variable
si_dict = SingleImputer(strategy={“age”:‘least squares’, “fnlwgt”: ‘least squares’})
si_data_full = si_dict.fit_transform(df8[[‘age’, ‘fnlwgt’]])

具有最小二乘回归模型估算值的数据集。

虽然这种技术很奇特,但在参数估计方面,它似乎与其他方法不相上下。当然,数据集可能与实际的机器学习训练不同,这是我们需要自己测试的东西。
在某些情况下,向回归预测中添加误差会允许更大的随机性,这可能会改善模型的参数估计,尤其是方差。不幸的是,这不能通过 autoimpute 来完成,但是如果回归模型是从零开始的,我们可以这样做。
使用相同变量进行插补的一个潜在缺点是,它可能会给参数估计带来一些偏差。这意味着,最好使用一组不包括在您当前正在研究的机器学习模型中的变量来执行回归插补。
**K-最近邻(KNN)**
与我们刚刚讨论的回归和带误差模型的回归类似,KNN 可用于填充数据集中的缺失值。
这背后的直觉是,一个点的值可以由最接近该缺失点的点来近似。
我们可以使用 scikit-learn 中的 KNNImputer 来完成这项工作:
df9 = df.copy()# importing the KNN from fancyimpute library
from sklearn.impute import KNNImputer# calling the KNN class
knn_imputer = KNNImputer(n_neighbors=3)
imputing the missing value with knn imputer
df9[[‘age’, ‘fnlwgt’]] = knn_imputer.fit_transform(df9[[‘age’, ‘fnlwgt’]])

KNN 插补与其他插补方法的比较。

均值和方差估计,包括 KNN 插补
正如我们在上面看到的,KNN 似乎比其他插补方法表现更好的地方是方差的估计。
鼓励尝试不同的邻居数量公式,以获得比上面更好的结果。
# 最后的想法
虽然没有一种方法可以处理缺失数据,但本文揭示了可以用来处理缺失数据的各种技术和方法,以及它们的弱点和专业评论。
这一研究领域正在令人惊讶地、理所当然地发展,并且正在开发新的方法来处理缺失的数据。最终,选择的方法应该牢记研究目标,数据丢失的机制,以及数据集偏差的可能性。排除所有这些因素,一些数据从业者得出结论,对于简单的 MCAR 缺失,删除方法可能是首选。
鼓励数据科学家探索一种或多种方法,甚至组合使用这些方法来实现更好的模型。
虽然我们测试了不同插补方法对参数估计的影响,但最终我们希望看到这些方法如何改善机器学习模型及其预测能力。
在下一篇文章中,让我们看看处理缺失数据的一些最先进的方法:**基于模型和多重插补方法**。
[像专家一样处理“缺失数据”——第 3 部分:基于模型的&多重插补方法](/handling-missing-data-like-a-pro-part-3-model-based-multiple-imputation-methods-bdfe85f93087)
完整的代码可以在我的 [Github 页面上找到。](https://github.com/francisadrianviernes/Data-Preprocessing-and-Feature-Engineering/blob/master/Handling%20Missing%20Data%20Like%20a%C2%A0Pro.ipynb)
# 参考
麦克奈特,P. E. (2007)。*缺失数据:温柔的介绍*。吉尔福德出版社。
# 像专家一样处理“缺失数据”——第 3 部分:基于模型的多重插补方法
> 原文:<https://towardsdatascience.com/handling-missing-data-like-a-pro-part-3-model-based-multiple-imputation-methods-bdfe85f93087?source=collection_archive---------9----------------------->
## **数据科学。分析。PYTHON**
## 面向 21 世纪数据科学家的基本和高级技术

[迈特·沃尔什](https://unsplash.com/@two_tees?utm_source=medium&utm_medium=referral)在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍照
正如我们在专门研究缺失数据系列的第一篇文章[中所提到的,关于“缺失”的机制或结构的知识是至关重要的,因为我们的处理方法将主要依赖于它。](/the-three-types-of-missing-data-every-data-professional-should-know-d988e17d6ace)
在[像专家一样处理“缺失数据”——第 1 部分——删除方法](/handling-missing-data-like-a-pro-part-1-deletion-methods-9f451b475429)中,我们已经讨论了删除方法。
在[像专家一样处理“缺失数据”——第 2 部分:插补方法](/handling-missing-data-like-a-pro-part-2-imputation-methods-eabbf10b9ce4)中,我们讨论了简单的**插补方法**。虽然某些插补方法被认为适用于特定类型的数据,例如 n *正态分布数据、MCAR 缺失等。*,这些方法主要是因为我们的估计和模型有偏差而受到批评。因此,有些人认为删除方法在某些情况下更安全。
幸运的是,新的插补方法类别**解决了简单插补和删除方法**的这些弱点。
这些是基于模型的多重插补方法。
# 加载数据集并模拟缺失
加载成人数据集并模拟本文[中的 MCAR 数据集。](/handling-missing-data-like-a-pro-part-1-deletion-methods-9f451b475429)
# 基于模型的插补方法

本文中讨论的方法的总结
在我们的主要参考文献中,McKnight (2007)对基于模型的方法进行了不同的定义。在这种情况下,使用这些方法不是为了估计缺失数据,而是为了生成参数估计,就像观察到缺失数据一样。因此,它们被更恰当地称为*数据增强方法*。
对我们来说,我们称之为基于模型,因为它们使用机器学习/统计模型来估计缺失数据。事实上,回归估计应该属于这里(来自我们的上一篇文章),但我们已经将下面的方法分开,因为它们被视为复杂得多(因此数据科学家较少使用)。
我们已经开始讨论最大似然均值的产生。让我们再讨论其中的两种,EM 算法和马尔可夫链蒙特卡罗方法。
## 期望值最大化算法
EM 算法是在数据缺失时获得最大似然估计的通用方法(Dempster,Laird & Rubin,1977)。
基本上,EM 算法由两个步骤组成:期望步骤(E)和最大化步骤(M)。这是一个为处理潜在(未观察到的)变量而设计的漂亮算法,因此适用于缺失数据。
要执行此算法:
1. 使用最大似然法估算缺失数据的值。使用每个观察值的**非缺失**变量计算缺失值的最大似然估计。
2. 根据步骤 1 为“模拟的”完整数据集生成参数估计值。
3. 基于从步骤 2 获得的参数估计值(或“更新的”参数估计值)重新估算值。
4. 根据步骤 3 中的估算数据重新估计参数。
当我们的参数估计“不再改变”或不再更新时,迭代过程停止。(*技术术语是,当前值减去更新值的误差小于某个ε。*)
与许多使用迭代的机器学习方法一样,EM 算法产生的估计偏差较小。
要用一个包来估算,首先安装`impyute`到`pip install impyute.`
与我们的线性回归一样,最好在计算最大似然估计值时包括您的研究中没有包括的变量,以免使模型产生偏差。
假设像 KNN 一样,我们希望使用下列项目的观测值来估计缺失数据:'**年龄**'、 **fnlwgt** '、**教育人数**'和'**每周工作时间**'。
代码应该是:
df10 = df.copy()# importing the package
import impyute as impy# imputing the missing value but ensure that the values are in matrix formdf10[[‘age’, ‘fnlwgt’, ‘educational-num’, ‘capital-gain’, ‘capital-loss’,
‘hours-per-week’]] =
impy.em(df10[[‘age’, ‘fnlwgt’, ‘educational-num’, ‘capital-gain’, ‘capital-loss’,
‘hours-per-week’]].values, loops=100)#Simulate New Comparison Container (So we can separate these new categories)
comparison_df = pd.concat([orig_df[[‘age’, ‘fnlwgt’]], X], axis=1)#Rename so We can Compare Across Datasets
comparison_df.columns = [“age_orig”, “fnlwgt_orig”, “age_MCAR”, “fnlwgt_MCAR”]
cols = comparison_df.columns.to_list()comparison_df = pd.concat([comparison_df, df10[[‘age’, ‘fnlwgt’]]], axis=1)
comparison_df.columns = [*cols,‘age_EM.imp’, ‘fnlwgt_EM.imp’]#View the comparison across dataset
comparison_df.loc[fnlwgt_missing,[“fnlwgt_orig”,“fnlwgt_MCAR”,
‘fnlwgt_EM.imp’]]

与 EM 算法插补的比较。
如我们所见,只需几行代码,我们就能执行 EM 插补。那些一直关注这个系列的人会立即看到,这是最接近我们理想想要的标准差参数的方法。
然而,这种特殊的方法假设我们的数据是多元正态的。然而,我们缺少值的特征不能被假定为正态分布。年龄和最终体重通常是正偏的,不会变成负的。
> **因此,盲目应用代码导致年龄和最终体重的插补值为负值,这是不可能的!**

EM 方法估算的负最终权重
因此,在应用上面的代码之前,我们必须找到一种规范化值的方法。
我们可以简单地应用对数变换,并检查我们的算法对这些新变换的变量的效果。
df11 = df.copy()# imputing the missing value but ensure that the values are in matrix formdf11[[‘age’, ‘fnlwgt’, ‘educational-num’, ‘capital-gain’, ‘capital-loss’,
‘hours-per-week’]] =
np.exp(impy.em(np.log(df10[[‘age’, ‘fnlwgt’, ‘educational-num’, ‘capital-gain’, ‘capital-loss’,
‘hours-per-week’]].values), loops=100))

原始数据集和对数变换数据集上 EM 代码的比较。
虽然我们的标准差较低,但与我们讨论过的其他单一插补方法相比,它仍具有更好的估计值。平均估计值也更接近原始值。
## 马尔可夫链蒙特卡罗方法
基于最大似然法的模型的一个局限性是,它们需要数据的分布假设(例如多元正态性)。
越来越流行的马尔可夫链蒙特卡罗(MCMC)程序可以在缺乏这种知识的情况下使用。该过程本质上是贝叶斯过程,最终目标是获得后验分布。
我们需要把这个概念分解成什么是马尔可夫链,蒙特卡洛与它有什么关系,但是我们把这个问题留给另一篇文章,这样这篇文章就简短了。但是像 EM 算法一样,MCMC 增加了观测数据来处理参数的估计。
从`NumPyro`开始,这些也可以采用一个包。由于这种方法使用的代码比其他方法长得多,我们将读者引向 NumPyro 的官方文档:[http://num . pyro . ai/en/latest/tutorials/Bayesian _ attribute . html](http://num.pyro.ai/en/latest/tutorials/bayesian_imputation.html)
## 其他 SCIKIT 估算器:贝叶斯岭、决策树、额外树、K 邻居
对本文中讨论的所有方法进行分类的一种方法是称它们为“多元估算器”。也就是说,它们基于数据集中存在的所有其他变量的值进行估算。
由于大多数读者被认为熟悉机器学习,另一种看待它的方式是使用数据集内的可用数据作为预测器来估算缺失数据的机器学习模型。
因为每个方法的过程都非常相似,所以让我们简单地为上面的四个方法创建一个循环。
from sklearn.experimental import enable_iterative_imputer #MUST IMPORT THIS
from sklearn.impute import IterativeImputerfrom sklearn.linear_model import BayesianRidge
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import ExtraTreesRegressor
from sklearn.neighbors import KNeighborsRegressor#STEP 1 - Choosing Variables and Create a Matrix of Values
df12 = df.copy()[[‘age’, ‘fnlwgt’, ‘educational-num’, ‘capital-gain’, ‘capital-loss’,
‘hours-per-week’]]X = df12.values #Matrix of Values# STEP 2 - INITIALIZE ESTIMATORS
estimators = {
“bayesianridge”:BayesianRidge(),
“DTrees”: DecisionTreeRegressor(max_features=‘sqrt’),
“ETrees”:ExtraTreesRegressor(n_estimators=10),
“KNreg”:KNeighborsRegressor(n_neighbors=15)
}# STEP 3 - RUN IMPUTATIONS AND STORE IMPUTED VALUES
for key, value in estimators.items():
imputer = IterativeImputer(random_state=19, estimator=estimators[key])
imputer.fit(X)
transformed = imputer.transform(X)
#Temporarily Store
temp_df = pd.DataFrame(np.column_stack(list(zip(*transformed))), columns=df12.columns)
#Get updated columns list
cols = comparison_df.columns.to_list()
#Combine for Comparison
comparison_df = pd.concat([comparison_df, temp_df[['age', 'fnlwgt']]], axis=1)
comparison_df.columns = [*cols,f'age_{key}.imp', f'fnlwgt_{key}.imp']

不同 scikit 插补的比较。
请注意,我们可以尝试的估计量并不局限于上面的估计量。这些只是在这里找到的官方文档中讨论的:[https://sci kit-learn . org/stable/auto _ examples/impute/plot _ iterative _ import _ variants _ comparison . html](https://scikit-learn.org/stable/auto_examples/impute/plot_iterative_imputer_variants_comparison.html)
# 多重插补方法
多重插补(MI)是目前处理缺失数据最受欢迎的方法。这些方法提供了无偏的(因此是可推广的)估计,并恢复了总体方差,这对于统计推断是至关重要的。
单一插补方法的主要区别在于,不是为缺失的观察值输入一个值,而是输入几个值(比如 3 到 10)。**其平均值被视为最终估算值。**
MI 不仅仅是一种方法,而是处理多重价值估算的许多方法的术语。这些多个值是从一个迭代过程中得到的,该迭代过程使用了:
1。观察数据和
2。迭代期间生成的样本值。
每组估算值随后被用来替换缺失值以创建一个完整的数据集。因此,如果我们选择估算 3 个值,这些值会产生三个完整的数据集。
这些多个估计值被组合以获得感兴趣的参数的单个最佳估计值。例如,如果我们的方法是多元回归模型,则构建三个回归模型,每个完整数据集一个。得到的模型有它们相应的参数和系数估计值,这些估计值的平均值将是我们的最终值。
在 Python 中实现这一点的一个包是 **MICEFOREST。—通过链式方程(小鼠)**和随机森林(`pip install miceforest`)进行多重插补。
回想一下,在我们之前的例子中,决策树在恢复群体特征方面表现相对较好。因此,该软件包将使用随机森林方法应用多重插补,因此,让我们希望这将产生比我们之前更好的性能。
import miceforest as mfdf13 = df.copy()df13 = df.copy()[[‘age’, ‘fnlwgt’, ‘educational-num’, ‘capital-gain’, ‘capital-loss’,
‘hours-per-week’]]# Create kernel.
kernel = mf.MultipleImputedKernel(
df13,
datasets=4,
save_all_iterations=True,
random_state=1989
)# Run the MICE algorithm for 3 iterations on each of the datasets
kernel.mice(3)#View Last Dataset Imputedkernel.complete_data(2).loc[fnlwgt_missing]

来自 MICEFOREST 的结果
在生成这些多次迭代之后,我们还需要做一件事:**我们需要对它们进行平均**。
mi_results = pd.concat([kernel.complete_data(i).loc[:,[“age”, ‘fnlwgt’]] for i in range(3)]).groupby(level=0).mean()

最终比较,包括 MICE 森林估算
正如我们所看到的,我们的 MI 程序在恢复群体参数方面做得非常好。MI 的一个不言而喻的优点是,我们摆脱了上面讨论的一些方法,特别是 ML 方法所带来的分布假设。
因为 MI 方法产生渐近无偏的估计,所以它们可以被实现用于 MAR 和 MNAR 机制!这对我们数据科学家来说是一个巨大的胜利。
还有很多关于 MICE 方法的讨论,我们可以在这里回顾一下:[https://onlinelibrary.wiley.com/doi/epdf/10.1002/sim.4067](https://onlinelibrary.wiley.com/doi/epdf/10.1002/sim.4067)
# 结束语
虽然我们在这一系列文章中展示了许多备受推崇的现代技术,但我们必须记住以下几点:
1. 这些技术并不全面。新技术不断发展,推进了我们处理缺失数据的方式;
2. 该应用因用例及研究目标而异;在我们的案例中,我们只是通过参数估计来进行研究。
3. 尽管我们在这里讨论的方法很复杂,但是没有比避免缺失数据更好的方法来处理缺失数据。研究者应该对适当的研究设计、收集、储存和提取给予适当的考虑。
完整代码可以在我的 [Github 页面](https://github.com/francisadrianviernes/Data-Preprocessing-and-Feature-Engineering/blob/master/Handling%20Missing%20Data%20Like%20a%C2%A0Pro.ipynb)找到。
# 参考
麦克奈特,P. E. (2007)。*缺失数据:温柔的介绍*。吉尔福德出版社。
<https://github.com/AnotherSamWilson/miceforest#Simple-Example-Of-Multiple-Imputation> <https://onlinelibrary.wiley.com/doi/epdf/10.1002/sim.4067>
# 在烧瓶应用程序中处理 ML 预测
> 原文:<https://towardsdatascience.com/handling-ml-predictions-in-a-flask-app-1ccfeff06326?source=collection_archive---------37----------------------->
## 不要让长时间运行的代码拖慢你的 Flask 应用程序

[HalGatewood.com](https://unsplash.com/@halacious?utm_source=medium&utm_medium=referral)在 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 上拍照
我为 Metis 数据科学训练营做的一个项目涉及创建一个 Flask 应用程序的 MVP,向用户显示电影推荐(想想网飞主屏幕)。
我的建议包括模型预测与 SQL 查询的结合——所有这些都是在请求进来时、响应发出之前完成的。演示日到来时,加载网站主页大约需要 30 秒。
我的 Flask 应用程序的简化版代码
公平地说,这是我在*数据科学*训练营的一个很短的期限内创建的一个 MVP 不是网络开发训练营。尽管如此,30 秒的等待时间并不太好。
毕业后,一旦我有了更多的时间,我就重新审视我的项目,看看我还能改进什么。
这里有两个我可以探索的选项,它们可以大大加快我的页面加载时间,而不必改变我的预测算法。
# 1)加载页面,然后进行预测
不要在返回主页之前进行预测(就像上面的代码一样),而是将预测代码与 Flask 应用程序中的页面响应代码分开。返回没有预测的页面。然后,加载页面后,使用 JavaScript 调用 API。下面是更新后的 Flask 应用程序代码的样子:
更新 Flask 应用程序,其中 ML 预测被移动到单独的路线
下面是 JavaScript 代码的样子:
用于查询 Flask API 的 JavaScript 代码片段
这是对初始代码的一个小改动,但对用户来说却有很大的不同。该页面最初可以加载占位符图像或加载栏,以便用户在等待加载预测时仍然可以与您的站点进行交互。
# 2)把工作交给芹菜
通过在 Flask 响应函数中运行 ML 预测或复杂的 SQL 查询等缓慢的过程,您会使 Flask 服务器陷入困境。这可能不是你关心的问题,取决于你期望得到多少流量。也许这只是一个概念验证,或者一次只有少数人会使用你的服务。在这种情况下,只要使用我们的 API 方法就可以了。否则,您可能需要考虑一个可以水平扩展的解决方案。
进入[Celery](https://docs.celeryproject.org/en/stable)——一个用于创建“分布式任务队列”的 python 库。使用 Celery,您可以创建一个工人池来处理收到的请求,这就像在 python 函数中添加一个装饰器一样简单。
对于我们新的 Celery 工作流,我们将把 API 路径分成两部分:一部分用于安排预测,另一部分用于获取预测结果。
让我们来看看更新后的 Flask 片段:
这是新的芹菜片段:
以及更新后的 JavaScript:
现在我们开始加载页面,然后 JavaScript 将安排模型预测,并继续检查结果,直到它们准备好。
诚然,这增加了我们解决方案的复杂性(您需要添加一个像 Redis 这样的代理,并启动一个单独的芹菜工人进程),但它允许我们通过向我们的池中添加所需数量的芹菜工人来横向扩展我们的应用程序。
要查看完整的示例,请查看 GitHub repo:
<https://github.com/a-poor/flask-celery-ml>
感谢阅读!我很想听听你的反馈。让我知道你使用什么技术来添加 ML 到 Flask 应用程序中。请在下面留下你的评论,或者在 Twitter 或 LinkedIn 上联系我。
# 在 Python 中处理绘图轴脊线
> 原文:<https://towardsdatascience.com/handling-plot-axis-spines-in-python-f143b8554da2?source=collection_archive---------9----------------------->
## matplotlib 和 seaborn 中一些有用的方法和技巧

来自 [Unsplash](https://unsplash.com/photos/UaB91-kVLb4)
轴脊线是限制绘图区域的线。根据不同的情况,我们可能要删除一些(或全部)它们,改变它们的颜色,使它们不那么明显,调整它们的宽度/样式,或者改变它们的位置。在本文中,我们将探索一些处理轴刺的简便方法。
# 移除脊椎
很多情况下,我们只需要去掉棘突。让我们假设我们有下面的情节:
import numpy as np
import matplotlib.pyplot as pltx = np.arange(0, 5, 1)
y = 2 * x
plt.plot(x, y)
plt.show()

作者图片
我们想从轴上去掉顶部的脊椎。为此,我们可以使用*面向对象 API 接口*的`set_visible()`方法(即当我们使用`ax.plot`而不是`plt.plot`时)。语法如下:`ax.spines['top'].set_visible(False)`。要删除几根刺,使用 For 循环是有意义的:
fig, ax = plt.subplots()
ax.plot(x, y)
for spine in [‘top’, ‘right’]:
ax.spines[spine].set_visible(False)
plt.show()

作者图片
更直接的方法是使用 seaborn 实用函数`sns.despine()`。在这种情况下,我们是使用面向对象的 API 还是 pyplot 接口并不重要。如果没有传入参数,默认情况下,顶部和右侧的脊线将被移除:
import seaborn as snsplt.plot(x, y)
sns.despine()
plt.show()

作者图片
可以移除任何剩余的脊线(如`left=True`)或恢复默认移除的脊线(如`right=False`)。
要一次移除所有 4 根刺,我们可以使用`set_visible()`或`sns.despine()`,但有一种更短的方法:使用`plt.box()`方法。
plt.plot(x, y)
plt.box(on=False)
plt.show()

作者图片
# 更改书脊颜色和透明度
`set_color()`和`set_alpha()`方法与面向对象的 API 接口相关,语法类似于`set_visible()`的语法。当我们想要保留脊线但使其不明显时,`set_alpha()`方法很方便:
fig, ax = plt.subplots()
ax.plot(x, y)
ax.spines[‘left’].set_color(‘red’)
ax.spines[‘bottom’].set_alpha(0.2)
plt.show()

作者图片
# 调整书脊宽度/样式
现在让我们试着用方法`set_linewidth()`和`set_linestyle()`改变我们的绘图的宽度和样式,其中后者可以接受以下值:`'solid'`(默认)、`'dashed'`、`'dashdot'`或`'dotted'`。
fig, ax = plt.subplots()
ax.plot(x, y)
ax.spines[‘left’].set_linewidth(3)
ax.spines[‘bottom’].set_linestyle(‘dashed’)
plt.show()

作者图片
看来`set_linestyle()`的方法没有起到预期的效果。我们可以通过改变同一脊柱的宽度来解决这个问题:
fig, ax = plt.subplots()
ax.plot(x, y)
ax.spines[‘bottom’].set_linewidth(4)
ax.spines[‘bottom’].set_linestyle(‘dashed’)
plt.show()

作者图片
现在看起来更好,但破折号彼此太近。为了进一步修复它,我们可以调整轴脊的另一个属性:capstyle,即每个虚线或圆点的结束样式。柱帽可以是凸出的*、*对接的*或圆形的*。为了理解它们之间的区别,让我们来看下面的示意图,该示意图显示了长度相同但顶样式不同的破折号:

作者图片
我们看到*平头*帽型是最不“占用空间”的,而轴棘的默认帽型是*凸出*。让我们应用`set_capstyle()`方法并传入一个新值来调整我们的绘图:
fig, ax = plt.subplots()
ax.plot(x, y)
ax.spines[‘bottom’].set_capstyle(‘butt’)
ax.spines[‘bottom’].set_linewidth(4)
ax.spines[‘bottom’].set_linestyle(‘dashed’)
plt.show()

作者图片
还有一种提供书脊样式的替代方法:向`set_linestyle()`传递一个以下形式的元组: *(offset,onoffseq)* ,其中*偏移量*通常为 0, *onoffseq* 是一个以磅为单位的偶数长度的开/关墨迹元组。这种方法甚至更方便,因为它不需要调整任何其他参数,如宽度或 capstyle。我们可以用它来制作虚线、点线或任何其他图案的脊柱:
fig, ax = plt.subplots()
ax.plot(x, y)
ax.spines[‘top’].set_linestyle((0, (10, 10)))
ax.spines[‘right’].set_linestyle((0, (10, 10, 1, 10)))
plt.show()

作者图片
# 改变位置
我们可以使用`set_position()`方法将任意脊柱放在任意位置,并传入以下形式的元组:*(位置类型,数量)*。可能的职位类型有:
* `'outward'` —在绘图区域之外(或在绘图区域之内,如果以点为单位的数量为负值),
* `'axes'` —在指定的坐标轴坐标上,可以取 0 到 1 的值,
* `'data'` —在指定的数据坐标上。
例如,最后一个选项对于在 0:
x1 = np.arange(-5, 5, 0.1)
y1 = np.sin(x1)
fig, ax = plt.subplots()
plt.plot(x1, y1)
ax.spines[‘left’].set_position((‘data’, 0))
sns.despine()
plt.show()

作者图片
`'outward'`位置类型在`sns.despine()`函数中有一个等价类型,我们可以传入可选的`offset`参数。该参数可以接受一个整数作为所有可见脊线的偏移量,也可以接受一个字典来分别指定每个可见脊线的偏移量。以下两个图是相同的:
fig, ax = plt.subplots()
ax.plot(x, y)
ax.spines[‘left’].set_position((‘outward’, 20))
sns.despine()
plt.show()fig, ax = plt.subplots()
ax.plot(x, y)
sns.despine(offset={‘left’: 20})
plt.show()

作者图片
最后,谈到定位 spines,在某些情况下,还有一种方法是有用的:`set_zorder()`。向它传递 0,我们可以在情节后面“隐藏”一个脊柱。下面我们来对比一下剧情:
fig, ax = plt.subplots()
plt.plot(x1, y1, color=‘red’, linewidth=5)
ax.spines[‘left’].set_position((‘data’, 0))
ax.spines[‘left’].set_linewidth(5)
sns.despine()
plt.show()fig, ax = plt.subplots()
plt.plot(x1, y1, color=‘red’, linewidth=5)
ax.spines[‘left’].set_zorder(0)
ax.spines[‘left’].set_position((‘data’, 0))
ax.spines[‘left’].set_linewidth(5)
sns.despine()
plt.show()

作者图片
# 结论
在本文中,我们探讨了在 matplotlib 和 seaborn 库中处理轴刺的不同方法和技巧,包括删除它们、更改它们的颜色和透明度、调整宽度/样式或更改位置。
感谢阅读!
**你会发现这些文章也很有趣:**
</how-to-fill-plots-with-patterns-in-matplotlib-58ad41ea8cf8> </an-unconventional-yet-convenient-matplotlib-broken-barh-function-and-when-it-is-particularly-88887b76c127> </how-to-fetch-the-exact-values-from-a-boxplot-python-8b8a648fc813>