TowardsDataScience-博客中文翻译-2022-三十一-
TowardsDataScience 博客中文翻译 2022(三十一)
没有数据的图像分类?
原文:https://towardsdatascience.com/image-classification-with-no-data-b44089f8dc28
使用较少数据的机器学习算法的理论综述

在 Unsplash 上由 R. Makhecha 拍摄的照片
你想建立一个没有多少数据的机器学习模型?众所周知,机器学习需要大量数据,而收集和标注数据需要时间,而且成本高昂。这篇文章介绍了一些用更少的数据建立一个有效的图像分类器的方法!
目录
介绍
训练机器学习模型总是需要数据的。庞大的数据集对于构建强大的管道、避免过度拟合以及很好地推广到新图像是必要的。
然而,给定一些假设,您可以极大地减少所需的数据量——不管是否带注释。本文介绍了一些在没有大量数据或标记数据的情况下进行图像分类的方法。我将介绍迁移学习、自监督学习、半监督学习、主动学习、少镜头图像分类、基于文本的零镜头分类器中最重要的几个方面。
请记住,我提出的每种方法都可以用于其他机器学习问题。
第 1 节和第 2 节介绍了用经过充分研究的方法用大约一千个带注释的样本构建分类器的方法。第 3 节和第 4 节通过热门研究主题将数据效率推向极限。
为了简洁起见,我不再谈论数据增强和生成网络。出于同样的原因,我不会深入数学或实现细节。要了解更多信息,我建议你直接阅读参考资料部分的论文。话虽如此,我们还是开始吧!

作者图片
迁移学习
当你从零开始训练一个深度神经网络时,你经常会随机初始化你的权重。这是初始化神经网络的最佳方式吗?答案一般是否定的。
首先,深度学习是关于表现的。在经典的机器学习中,特征需要手工制作。深度学习背后的想法是,你让你的神经网络在训练时自己学习一种特征表示。
在神经网络的每一层之间,都有一个输入数据的表示。你越深入你的神经网络,你的表现就应该越全面。通常,已知分类器神经网络的第一层能够检测颜色和边缘的斑点。中间层将第一层表示作为输入来计算比第一层更复杂的概念。例如,他们可能会发现猫眼或狗耳的存在。最后一层给出了图像来自每个类的概率。

深层特征表现——作者的形象
迁移学习 [ 1 背后的想法是,从另一个分类任务中学到的一些表征可能对你的任务有用。迁移学习是将预先训练好的网络的第一层用于另一项任务,在其上添加新层,并在感兴趣的数据集上微调整个网络。
相比之下,如果你的目标是学会赢得足球比赛,转移学习将包括首先学会打篮球,以适应移动你的身体,锻炼你的耐力,等等。,才开始玩足球游戏。

转移学习管道—图片由作者提供
它将如何影响最终网络的性能?你应该在哪里切断你预先训练的网络?这些问题在[ 1 中有详细阐述。
总结一下最重要的观点:
- 神经网络的第一层非常一般,而最深层是预训练任务中最专业的。因此,你可以预期,如果你的预训练任务接近你的目标任务,那么保持更多的层将更有益。
- 在中间层进行切割通常会导致性能下降。这是由于通过微调在中间层获得了脆弱的平衡。
- 使用预先训练的权重总是比使用随机初始化的权重更好。这是因为,通过先在另一项任务上进行训练,你的模型学会了用其他方法学不到的特性。
- 当重新训练这些预训练的权重时,可以获得更好的性能-最终对它们使用较低的学习速率,或者在几个时期后解冻它们。
利用未标记的数据
与已标记的数据相比,未标记的数据通常更容易访问。不利用这一点就太浪费了!
自我监督学习
自我监督学习 [ 2 ]解决了从未标记数据中学习深度特征的问题。在训练了一个自我监督的模型之后,可以像在迁移学习中一样使用特征提取器,所以你仍然需要一些带注释的数据来微调。
那么,如何从未标记的数据中训练深度特征提取器呢?总之,你需要一个足够难的借口任务,使你能够为你的分类任务学习有趣的特征。
例如,如果你想在不玩真实游戏的情况下赢得足球比赛,你可以尽可能多地训练玩杂耍球。玩杂耍球会提高你的控球技术,这在玩游戏时会派上用场。

自我监督学习管道[ 2
借口任务的一个例子是预测图像的旋转角度。基本上,对于每个图像,你应用一个旋转 z 来得到旋转后的图像 x 。然后你训练一个神经网络从 x 预测z**这个旋转预测任务迫使你的网络深入理解你的数据。事实上,要预测一幅狗的图像的旋转,你的网络首先需要了解图像中有一只狗,并且一只狗应该以特定的方式定向。


托辞任务示例:旋转变换预测—(左)[ 2 ] |(右)作者图片
借口任务可以根据你的具体目标有很大的不同。常用的借口任务包括:
- 变换预测:数据集中的样本被变换修改,您的网络学习预测该变换。
- 屏蔽预测:输入图像的随机正方形被屏蔽,网络必须预测图像的屏蔽部分。
- 实例辨别:学习一种分离所有数据样本的表示法。例如,每个数据点可以被认为是一个类,并且可以在这个任务上训练一个分类器。

自我监督借口任务的示例[ 2
半监督学习
半监督学习 [ 4 ]非常类似于自监督学习,因为它也涉及到拥有一个小的带注释的数据集以及一个大的未标记的数据集。根据你所取的定义,自监督学习可以看作是半监督学习的一个特例。然而,在更严格的定义中,半监督学习在于直接利用未标记的数据作为训练样本,而不是作为学习有意义特征的方法。
最流行的做半监督学习的方式是自学 [ 3 。基本上,您在已标记的数据上训练基线,在未标记的数据上推断以获得伪标签,仅保留具有最高置信度的伪标签,并在已标记和剩余伪标记数据的联合上训练。
换句话说,自我训练在于根据标记数据训练一个模型,然后这个第一个模型成为学生模型的老师。然后,学生模型可以成为老师,等等。至于迭代次数,通常情况下,一两次迭代就足够让你的度量最大化了。
继续与赢得足球比赛进行比较,半监督学习就是你观看足球比赛的录像,教练分析每场比赛。

带自我训练的半监督学习[ 3
半监督学习和自我监督学习比迁移学习更有效。这仅仅是因为半监督或自监督设置中的数据更接近目标任务。尽管如此,在半监督和自我监督之间并没有明显的赢家。
主动学习
上面介绍的所有方法都假设您已经注释了一些数据,并且您不能或不想注释更多的数据。主动学习 [ 5 ]给你一套规则,让你选择标注哪些数据,以最少的标注样本获得最好的结果。
要注释哪些数据?答案是相当自然的:您应该总是为您的模型注释硬样本。这样,您就迫使您的模型学习更精确的特征来识别您的类。

用主动学习进行注解。“先知”是一个人类注释者。
在学习赢得足球比赛时,经典训练集中在你的缺陷上。假设你是前锋,你会希望掌握控球,达到良好的身体爆发力,并有很大的投篮能力。这些都要掌握,而不是直接打游戏,最有效率的方法就是一个一个的训练每个技能,先把重点放在自己最差的技能上。
所以现在,你会问如何确定这些“硬样本”。这就是事情变得复杂的地方。一个简单的基线是随机标记一小部分数据,在此基础上训练一个模型,在此基础上进行推断,并标注那些你最没有信心的数据。但是,因为大型网络通常过于自信,所以通常建议估计不确定性,而不是直接使用神经网络的输出。为了得到不确定性估计,你可以使用高斯过程或者贝叶斯网络作为例子。
少量学习,将数据效率推向极限
少量学习是从很少的样本中学习。典型的数量级是每类 1 到 20 个样本。虽然深度学习通常需要至少数千个样本,但少量学习解决了根本没有数据的问题。这个领域目前是一个热门的研究课题,所以你可能不想马上投入生产。
它是如何工作的?怎么能从这么少的数据中学会分类呢?执行少镜头图像分类的方法有很多种。我将只介绍其中的几个,主要是那些对研究有最重要影响的。
基础设置:公制学习和质子
还记得我说过深度学习都是关于表象的吗?因为你只有几十个样本,你不能训练一个神经网络去理解每一类,并对其中一类图像进行分类。其思想是通过利用预先训练的特征提取器来找到到特定类的距离。可以用上面提到的任何方法来训练特征提取器。
假设每个类别只有一个图像,并且您想对一个新的未标记图像进行分类。给定一个特征提取器,您可以提取每个标记图像以及未标记图像的特征表示。如果提取的特征足够好,则从未标记图像中提取的特征应该与来自同一类别的图像的特征最相似。然后,您可以计算到每个类的距离。您可以使用神经网络或常规距离,如余弦距离或均方误差。那就是所谓的公制学习 [ 6 ][ 7 。

公制学习设置[ 6
如果你每门课都有几张图像,研究论文通常会使用质子设置[ 8 ]。它包括为每个类计算一个原型。基本上,对于每个类,你用预先训练好的特征提取器计算所有的特征,并采取措施得到每个类的原型。那么一切工作就好像你只有一个图像。

原型网络[8]
元学习
与度量学习,元学习 [ 9 ]是一种完全不同的方式来执行少量学习。这里的想法是,首先,训练一个模型,以便在经过几个时期的微调后,它可以获得最佳指标。
换句话说,您希望找到最佳的权重初始化,这样您只需在几个时期内使用少量数据进行微调。为了找到最佳的初始化,使用学习模式。这就是为什么元学习又叫学习学习的原因。
这种学会学习的想法在开始时有点令人不安。足球的元学习将是你在尽可能多的运动中训练,网球、篮球、橄榄球、排球、肌肉等等。正因为如此,你现在可以适应任何不需要训练的运动,包括足球。

快速适应目标任务的元学习
让我们假设你有三个不同的任务,数据很少。 θ* 代表每项任务的理论最佳值。经过几次微调后,你应该从哪里开始训练以获得最佳效果?元学习试图找到最佳的初始化 θ 以在微调后最接近所有任务的最优。
为了避免让你迷路,我就不细说数学了。我建议你阅读这篇论文,以便更好地理解它。
元学习的想法似乎真的很有趣,但在实践中,与其他方法相比,它没有达到良好的性能指标。此外,为了元训练您的模型,您需要几个不同的任务,这在实践中很少出现。
最新技术和局限性
目前排行榜上最好的模特是P>M>F[10。本文结合了许多其他论文,研究了全球管道各部分的影响。特征提取器是一个预先训练好的视觉转换器,使用屏蔽自我监督 方法。然后用质子设置对模型进行元训练。为了执行特定的分类任务,需要对它进行微调。

在 Mini-Imagenet 5 路单镜头上进行代码基准测试的纸张
最好的模型达到 95%的准确率!是不是意味着我们不再需要带标签的数据了?不幸的是,目前的研究集中在不切实际的学术数据集上。基准测试通常是用少量的类(图中的基准测试中有 5 个类)来计算的,并且测试样本是从均匀分布中抽取的。这使得目前的方法在实际应用中不可靠。少镜头图像分类仍然是一个具有挑战性的领域,它肯定会随着时间的推移而改进!
弱监督学习和基于文本的零镜头分类器
本节呈现来自 OpenAI [ 12 ]的片段。这种架构通过将图像和文本结合在一起,在 2021 年彻底改变了深度学习。这里,零拍摄意味着该模型能够对图像进行分类,而无需使用标记类进行专门训练。
CLIP 由两个编码器或特征提取器组成:一个用于图像,另一个用于文本。两个编码器在相同的潜在空间中分别变换图像和文本,使得能够在文本和图像之间用提取的特征的简单余弦距离进行比较。

作者图片
是怎么训练出来的?使用弱监督学习来训练 CLIP。它包含了大量的噪声数据。它利用了你可以从互联网上截取的大量图片说明数据。即使来自互联网的数据极其嘈杂,但事实证明 CLIP 相当准确,具有非常强的零拍能力。
由于其大规模和嘈杂的训练数据集,CLIP 可以在不进行任何微调的情况下对常见图像进行分类,同时比经典分类器更健壮、更准确。OpenAI 在其博客上展示了一份更加准确的分析。
CLIP 有什么缺点吗?!嗯,凡事都有代价…
最大的 ResNet 模型 RN50x64 在 592 个 V100 GPU 上训练了 18 天,而最大的 Vision Transformer 在 256 个 V100 GPU 上训练了 12 天。
不是每个人——实际上,几乎没有人——都有能力训练如此庞大的神经网络。我甚至不会谈论训练数据集的大小。就推理而言,这完全是关于计算资源和准确性之间的权衡。CLIP 有几种较小的版本。
尽管巨大而缓慢,CLIP 可以用来生成高质量的伪标签,然后训练一个较小的网络。此外,当伪标签可用时,人工注释通常会更快,因为注释器只需在几个类之间进行选择,而不是几十或几百个类。
结论
研究太令人兴奋了!我发现在没有太多数据的情况下能够做得这么好简直是疯了。没有创造力,这些方法是不可能的。创造力开启了如此多的可能性!
总结一下这些想法:
- 当你有一个巨大的标注数据集接近你的目标任务时,一定要迁移学习。
- 当你有大量的未标注数据和标注数据的子集时,进行自监督学习或半监督学习。
- 如果您想高效地注释数据,请使用主动学习。
- 少镜头图像分类可以在几乎没有标注数据的情况下对图像进行分类。尽管它仍在研究中,但目前的基准是令人鼓舞的。
- 基于文本的零起点学习者在未来也可能发挥主导作用。它们可以开箱即用,不需要任何数据。
我没有在少量学习中展示直推模型,因为我个人认为这些在实际应用中是不可用的。你可能想看看他们,因为他们曾经统治了代码为的报纸的排行榜。
不要忘记查看正则化、 数据增强、和生成模型。这些可能会更容易解决你的问题。
恭喜你。您已到达这篇文章的结尾。我希望你喜欢读它。你现在可以毫无畏惧地阅读所有的报纸了。不要犹豫评论,伸手!
参考
[1] J. Yosinski,J. Clune,Y. Bengio,et al .深度神经网络中的特征有多大的可转移性? (2014 年),NIPS 2014 年
[2] L. Ericsson,H. Gouk,C. C. Loy 等.自我监督表征学习:介绍、进展与挑战 (2021),IEEE 2022
[3]谢,梁,何,等.噪声学生自我训练改善图像网络分类(2020)2020
[4] Y .奥阿利,c .胡德洛,和 m .塔米,深度半监督学习概述 (2020),arXiv 预印本 arXiv:2006.05278
[5]任平,肖,常,等.深度主动学习综述 (2021),ACM 计算综述 2022
[6] O. Vinyals,C. Blundell,T. Lillicrap 等,一次学习的匹配网络 (2017),NIPS 2016
[7]宋凤英,杨玉英,张立荣等.学会比较:少投学习的关系网络(2018)2018
[8] J. Snell,K. Swersky 和 R. S. Zemel,用于少量学习的原型网络 (2017),NIPS 2017
[9] C. Finn,P. Abbeel 和 S. Levine,用于深度网络快速适应的模型不可知元学习 (2017),ICML 2017
[10]胡世祥,李,施蒂默等,挑战简单流水线的极限:外部数据和微调有所作为 (2022),2022
[11] E. Bennequin,m .塔米,A. Toubhans,等人,少拍图像分类基准离现实太远:用语义任务采样重建得更好 (2022),CVPR 2022
[12] A .拉德福德,J. W .金,c .哈勒西等,从自然语言监督中学习可转移的视觉模型 (2021),PMLR 2021
用 Python 分 4 步提取图像颜色
原文:https://towardsdatascience.com/image-color-extraction-with-python-in-4-steps-8d9370d9216e
创建您自己独特的颜色列表

罗伯特·卡茨基在 Unsplash 上的照片
为什么颜色很重要?
颜色有助于在第一眼就引起人们的注意。它们有助于交流信息,例如,用红色和蓝色表示高温和低温,用绿色和红色表示财务价值。颜色在数据可视化方面也有一些好处。
使用基本色是一个很好的选择,因为它们就像批量生产的衣服一样随时可以使用并且通用。但是,有时候量身定制的颜色更适合讲故事,让作品脱颖而出。
有哪些合适的颜色?
互联网可以是一个很好的来源。很多网站推荐的颜色如“20XX 年的颜色”可以让你的作品看起来很时尚。
另一种颜色来源可以在你手机或相机的照片中找到。你可以给包含你想要的颜色的东西拍照,或者你可能已经有了一张你喜欢的颜色的照片。
这篇文章将指导如何从图像中提取颜色。这种方法也可以用一种有趣的方式来分析你最喜欢的照片,比如你度假时的美景或你喜欢的东西。

本文给出了一个图像颜色提取的结果。图片由作者提供。
总共有 4 个步骤:
- 1.选择一幅图像
- 2.导入库
- 3.创建一个函数
- 4.应用该功能
我们开始吧
1 选择一幅图像
从选择图像开始。比如下面我会用一张海水和椰子树的照片。

海水和椰子树的照片。图片由作者提供。
注:本文所有照片,除封面外,均归本人所有。我用我的设备(相机和手机)拍的。如果您想用本文中的代码测试它们的合理使用,请随意使用它们,无需征得许可。
2。导入库
从 Pillow 导入 Numpy、Panda、Matplotlib 和图像。
接下来,让我们导入 extcolors 和 rgb2hex 库。extcolors 库返回 RGB 值,这些值将被 rgb2hex 库转换成十六进制颜色代码。
如果在导入两个库时出现一些错误,您可以尝试安装一些必备的库来使它们工作。这些库在下面的代码中。删除前面的#符号!pip 并运行以安装它们。请不要删除版本号前的#符号。它们只是音符。
3.创建一个函数
接下来,我将一步一步地解释如何创建颜色提取函数。如果你想直接得到函数,请把分数降到定义一个函数。
调整图像大小
从准备输入图像开始。用现代相机和手机拍摄的照片太大了。有些可以拍摄超过 5000 万像素的照片(一台 4K.) 显示器只能显示大约 830 万像素)。如果我们直接使用巨大的图像,处理可能需要一些时间。
因此,首先要做的是调整大小。下面的代码显示了如何将图片的宽度调整为 900 像素。如果图像不是很大或者你的 CPU 很快,这一步可以省略,或者可以提高输出分辨率数。请注意,调整照片大小后,新照片将保存在您的计算机上,以供下一步阅读。

调整大小后的结果。图片由作者提供。
颜色提取
用 extcolors 库提取颜色。我们必须设置的参数:
- 容差:将颜色分组以限制输出并给出更好的视觉表现。从 0 到 100 分。其中 0 不会将任何颜色组合在一起,100 会将所有颜色组合在一起。
- limit:输出中呈现的提取颜色数量的上限。
在下面的代码中,我将公差值设置为 12,并将颜色代码输出的数量限制为 11 种颜色(limit=12)。该数字可以根据您的需要进行更改。获得的结果将是 RGB 颜色代码及其出现。

使用 rgb2hex 库定义一个将 RGB 代码转换成十六进制颜色代码的函数,并创建一个数据帧。

甜甜圈图
绘制一个环形图来可视化结果。

显示颜色及其在照片中所占百分比的圆环图。图片由作者提供。
调色板
创建一个调色板,并用十六进制颜色代码标记它们。如果提取步骤中的限制数设置为超过 12 种颜色,则可以修改 X 轴和 Y 轴值以适应该结果。

照片中的调色板。图片由作者提供。
最后,让我们结合代码。
瞧啊。!...

海水和椰子树照片的颜色提取结果。作者图片。
定义一个函数
到目前为止,我们已经完成了所有的步骤,让我们将它们结合起来,轻松地应用于其他图像。该函数适用于与 python 文件位于同一文件夹中的图像。
4.应用功能
解释我们必须为 exact_color 函数设置的参数:
exact_color('image name', resized_width, tolerance, zoom)
- 图像位置/名称:图像在计算机上的位置及其名称。
- resized_width:我们想要调整的照片宽度输出。
- 容差:根据 0 到 100 的范围对颜色进行分组以限制输出。
- 缩放:在圆环图的中心调整照片的大小。
例 1

一张切好的橙子和甜瓜的照片。作者图片。
exact_color('example1.jpg', 900, 12, 2.5)

从切片的橙子和甜瓜的照片中提取颜色的结果。作者图片。
例 2

黄色芙蓉花的照片。作者图片。
exact_color('example2.jpg', 900, 8, 2.5)

芙蓉花黄色照片的颜色提取结果。作者图片。
改善结果
如果结果没有您想要的颜色,有两种方法可以帮助获得颜色。
- 增加公差
例如,我将在下面照片的中心精确地画出一朵木槿花的粉红色,这是一种热带花卉。

一张粉红色的芙蓉花和绿叶的照片。图片由作者提供。
首先将公差值设置为 12
exact_color('example.jpg', 900, 12, 2.5)

将公差值设置为 12 后的结果。作者图片。
可以注意到上面的结果中没有粉色。这可能是因为组合的颜色太小。因此,我们将公差的数量增加到 24。
exact_color('example.jpg', 900, 24, 2.5)

将公差值设置为 24 后的结果。作者图片。
结果得到了改善。调色板上有一个粉色代码。我们可以更进一步,将公差数字增加到 36,以获得更多的粉红色代码。以下结果返回 2 种色调的粉红色。
exact_color('example.jpg', 900, 36, 2.5)

将公差值设置为 36 后的结果。作者图片。
2.裁剪输入
另一种改善结果的方法是在分析之前使用一些计算机程序对照片进行裁剪。在运行代码之前,我裁剪了输入图像。即使容差等于 24,提取也会返回 3 种粉红色阴影。
exact_color('example.jpg', 900, 24, 4.5)

裁剪照片的结果。作者图片。
摘要
本文解释了如何从图像中提取颜色。结果可以帮助分析颜色及其数量。也可以通过 Python for loop 命令应用该函数来处理批量图像。如果您有任何问题或建议,请随时留下评论。感谢阅读。
这些是关于数据可视化的其他文章,您可能会感兴趣。
参考
- Extcolors 。皮皮。于 2022 年 5 月 22 日从https://pypi.org/project/extcolors检索
- e .默里(2019 年 3 月 22 日)。颜色在数据可视化中的重要性。福布斯。2022 年 5 月 23 日检索,来自https://www . Forbes . com/sites/EVA Murray/2019/03/22/the-importance-of-color-in-data-visualizations/?sh=fdc72a657ec5
基于 K-均值聚类的颜色分割
原文:https://towardsdatascience.com/image-color-segmentation-by-k-means-clustering-algorithm-5792e563f26e
使用轮廓和 K-means 聚类,根据颜色识别和量化图像中的对象的详细指南。

简介
颜色分割是计算机视觉中使用的一种技术,用于根据颜色来识别和区分图像中的不同对象或区域。聚类算法可以自动将相似的颜色分组在一起,而无需为每种颜色指定阈值。当处理具有大范围颜色的图像时,或者当事先不知道精确的阈值时,这可能很有用。
在本教程中,我们将探讨如何使用 K-means 聚类算法来执行颜色分割,并计算每种颜色的对象数量。我们将使用“泡泡射手”游戏中的一幅图像作为例子,通过它们的轮廓找到并过滤泡泡对象,并应用 K-means 算法将具有相似颜色的泡泡分组在一起。这将允许我们计数和提取具有相似颜色的气泡的掩模,用于进一步的下游应用。我们将使用OpenCV和scikit-learn库进行图像分割和颜色聚类。
用阈值法提取二值掩模
第一步是从背景中提取所有的气泡。为此,我们将首先使用cv2.cvtColor()函数将图像转换为灰度,然后使用cv2.threshold()将其转换为二值图像,其中像素为 0 或 255。阈值设置为 60,因此所有低于 60 的像素设置为 0,其他像素设置为 255。由于一些气泡在二值图像上有轻微的重叠,我们使用cv2.erode()函数来分离它们。腐蚀是一种形态学操作,可以缩小图像中对象的大小。它可以用来消除小的白噪声,也可以用来分离连接的对象。
image = cv2.imread(r'bubbles.jpeg', cv2.IMREAD_UNCHANGED)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_ , mask = cv2.threshold(gray, 60, 255, cv2.THRESH_BINARY)
mask = cv2.erode(mask, np.ones((7, 7), np.uint8))

左:输入图像。右图:二进制图片|作者图片
使用轮廓提取物体边界
下一步是在二进制图像中查找对象。我们在二值图像上使用cv2.findContours()函数来检测物体的边界。轮廓被定义为在图像中形成对象边界的连续曲线。当 cv2。使用 RETR _ 外部标志,仅返回最外面的轮廓。该算法输出轮廓列表,每个轮廓代表图像中单个对象的边界。
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
过滤轮廓并提取平均颜色
为了移除不代表气泡的轮廓,我们将迭代得到的轮廓,并且仅选择那些具有大面积(大于 3000 像素)的轮廓。这将允许我们分离气泡的轮廓,并丢弃任何较小的对象,如字母或背景的一部分。
filtered_contours = []
df_mean_color = pd.DataFrame()
for idx, contour in enumerate(contours):
area = int(cv2.contourArea(contour))
# if area is higher than 3000:
if area > 3000:
filtered_contours.append(contour)
# get mean color of contour:
masked = np.zeros_like(image[:, :, 0]) # This mask is used to get the mean color of the specific bead (contour), for kmeans
cv2.drawContours(masked, [contour], 0, 255, -1)
B_mean, G_mean, R_mean, _ = cv2.mean(image, mask=masked)
df = pd.DataFrame({'B_mean': B_mean, 'G_mean': G_mean, 'R_mean': R_mean}, index=[idx])
df_mean_color = pd.concat([df_mean_color, df])

过滤前(左)和过滤后(右)二值图像上的绿色等高线|作者的图像
为了找到每个气泡的平均颜色,我们将首先通过在黑色图像上绘制白色轮廓来为每个气泡创建一个遮罩。然后,我们将使用原始图像和气泡的遮罩,使用cv2.mean()函数来计算气泡的平均蓝色、绿色和红色(BGR)通道值。每个气泡的平均 BGR 值存储在熊猫数据帧中。
用 K-means 算法对相似颜色进行聚类
最后,我们将应用 K-means 聚类算法将具有相似颜色的气泡分组在一起。我们将使用轮廓的平均颜色值作为来自sklearn库的KMeans算法的输入数据。n_clusters超参数指定算法要创建的聚类数。在这种情况下,由于有 6 个气泡颜色,我们将设置值为 6。
K-means 算法是一种流行的聚类方法,可用于将相似的数据点分组在一起。该算法的工作原理是将一组数据点作为输入,并将它们分成指定数量的聚类,每个聚类由一个质心表示。质心被初始化为数据空间内的随机位置,并且该算法迭代地将每个数据点分配给由最近质心表示的聚类。一旦所有数据点都被分配给一个聚类,质心就被更新为数据点在其聚类中的平均位置。重复该过程,直到质心收敛到稳定位置,并且数据点不再被重新分配给不同的聚类。通过使用以每个气泡的平均 BGR 值作为输入的 K-means 算法,我们可以将具有相似颜色的气泡分组在一起。
一旦初始化了KMeans类,就会调用fit_predict方法来执行聚类。fit_predict方法返回每个对象的分类标签,然后将这些标签分配给数据集中一个新的“标签”列。这允许我们识别哪些数据点属于哪个聚类。
km = KMeans( n_clusters=6)
df_mean_color['label'] = km.fit_predict(df_mean_color)
然后定义draw_segmented_objects函数,用相同颜色的气泡创建一个新的蒙版图像。这是通过首先创建一个二进制蒙版来实现的:具有相同标签的所有气泡的轮廓在黑色图像上以白色绘制。然后,使用来自cv2的bitwise_and函数将原始图像与蒙版结合,产生一个只有相同标签的气泡可见的图像。为方便起见,使用cv2.putText()功能将每种颜色的气泡数量绘制在图像上。
def draw_segmented_objects(image, contours, label_cnt_idx, bubbles_count):
mask = np.zeros_like(image[:, :, 0])
cv2.drawContours(mask, [contours[i] for i in label_cnt_idx], -1, (255), -1)
masked_image = cv2.bitwise_and(image, image, mask=mask)
masked_image = cv2.putText(masked_image, f'{bubbles_count} bubbles', (200, 1200), cv2.FONT_HERSHEY_SIMPLEX,
fontScale = 3, color = (255, 255, 255), thickness = 10, lineType = cv2.LINE_AA)
return masked_image
为每组具有相同标签的气泡调用draw_segmented_objects函数,为每种颜色生成一幅蒙版图像。每种颜色中珠子的数量可以通过在数据帧按颜色分组后计数数据帧中的行数来确定。
img = image.copy()
for label, df_grouped in df_mean_color.groupby('label'):
bubbles_amount = len(df_grouped)
masked_image = draw_segmented_objects(image, contours, df_grouped.index, bubbles_amount)
img = cv2.hconcat([img, masked_image])
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB) )

原始图像(左)沿着每种颜色的分段图像|图像作者
结束语
使用 K-均值聚类进行颜色分割是一种基于颜色识别和量化图像中对象的强大工具。在本教程中,我们演示了如何使用 K-means 算法以及 OpenCV 和 scikit-learn 来执行颜色分割并计算图像中每种颜色的对象数量。这种技术可以应用于各种需要根据颜色对图像中的对象进行分析和分类的场景。
为了您的方便,我们提供了一个包含完整代码的用户友好的 Jupiter 笔记本:
感谢您的阅读!
想了解更多?
- 探索 我写的附加文章
- 订阅 在我发表文章时得到通知
- 在上关注我 Linkedin 上关注我
用于深度学习的图像数据增强
原文:https://towardsdatascience.com/image-data-augmentation-for-deep-learning-77a87fabd2bf
了解什么是图像数据增强,以及如何在深度学习项目中使用 Keras

克里斯·劳顿在 Unsplash拍摄的照片
如果你曾经尝试过使用深度学习进行图像识别,你就会知道一个好的数据集对于训练的重要性。但是,找到足够的图像进行训练并不总是容易的,并且模型的准确性直接取决于训练数据的质量。
幸运的是,您可以使用一些技术来补充他们用于训练的图像数据集。其中一种技术叫做图像数据增强。在本文中,我将讨论什么是图像数据增强,它如何工作,为什么它在深度学习中有用,以及最后如何使用 Keras 库执行图像数据增强。
什么是图像数据增强?
图像数据增强是一种从现有图像中创建新图像的技术。为此,您可以对它们进行一些小的更改,例如调整图像的亮度,旋转图像,或者水平或垂直移动图像中的主题。
图像增强技术允许您人为地增加训练集的大小,从而为您的模型训练提供更多的数据。这允许您通过增强模型识别训练数据的新变量的能力来提高模型的准确性。
图像数据增强的类型
图像增强有多种形式,下面是一些常见的形式——垂直移动、水平移动、垂直翻转、水平翻转、旋转、亮度调整和放大/缩小。
使用 Keras 的图像增强
我将首先使用 Python 和 Keras 演示各种图像增强技术。如果您想尝试,请确保您安装了以下软件和软件包:
- 蟒蛇。你可以从 https://www.anaconda.com/products/distribution/下载蟒蛇。
- 张量流。您可以使用命令
pip install tensorflow安装 TensorFlow。
安装 Anaconda 和 TensorFlow 后,创建一个新的 Jupyter 笔记本。
垂直移位
我想展示的第一个图像增强技术是垂直移动。垂直移动随机上下垂直移动图像。对于这个例子,我将使用一个名为747.jpg的图片,它与我的 Jupyter 笔记本位于同一个文件夹中。

图片来源:https://commons . wikimedia . org/wiki/File:Qantas _ Boeing _ 747-438 er _ VH-OEI _ at _ lax . jpg。本文件在知识共享 署名-共享 2.0 通用许可下获得许可。
下面的代码片段使用 Keras 中的ImageDataGenerator类来垂直移动图像。
Keras 的
ImageDataGenerator类通过实时数据扩充生成批量图像数据。
#---import the modules---
import numpy as np
import matplotlib.pyplot as pltfrom tensorflow.keras.preprocessing.image import load_img
from tensorflow.keras.preprocessing.image import img_to_array
from tensorflow.keras.preprocessing.image import ImageDataGenerator#---load the image---
image_filename = '747.jpg'
img = load_img(image_filename)#---convert the image to 3D array---
image_data = img_to_array(img)#---convert into a 4-D array of 1 element of 3D array representing
# the image---
images_data = np.expand_dims(image_data, axis=0)#---create image data augmentation generator---
datagen = ImageDataGenerator(width_shift_range=0.2)#---prepare the iterator; flow() takes in a 4D array and returns
# an iterator containing a batch of images---
train_generator = datagen.flow(images_data, batch_size=1)rows = 5
columns = 4#---plot 5 rows and 4 columns---
fig, axes = plt.subplots(rows,columns)for r in range(rows):
for c in range(columns):
#---get the next image in the batch (one image since batch
# size is 1)---
image_batch = train_generator.next()
#---convert to unsigned integers for viewing---
image = image_batch[0].astype('uint8') #---show the image---
axes[r,c].imshow(image)#---set the size of the figure---
fig.set_size_inches(15,10)
上述代码片段产生以下输出:

从上面的输出可以看出,每次从train_generator对象中调用next()方法,都会得到一个略有变化的图像。在上面的代码片段中,每次调用next()方法时,都会返回一个基于原始图像高度移动了 20%的新图像:
datagen = ImageDataGenerator(**width_shift_range=0.2**)
有趣的是,对于这个版本的
ImageDataGenerator(tensorflow.keras.preprocessing.image)类,指定width_shift_range参数会垂直移动图像,而不是水平移动(这是来自keras.preprocessing.image模块的旧*ImageDataGenerator*的行为)。同样,如果你想让图像水平移动,你需要使用height_shift_range参数(见下一节)。
请注意,next()方法将根据您的需要多次返回一个增强的图像。在上面的代码片段中,我们调用了它 20 次(5 行乘以 4 列)。
水平移动
您现在可以尝试使用height_shift_range参数水平移动图像:
datagen = ImageDataGenerator(**height_shift_range=0.2**)
train_generator = datagen.flow(images_data, batch_size=1)rows = 5
columns = 4fig, axes = plt.subplots(rows,columns)
for r in range(rows):
for c in range(columns):
image_batch = train_generator.next()
image = image_batch[0].astype('uint8')
axes[r,c].imshow(image)fig.set_size_inches(15,10)
上述代码片段产生以下输出:

水平翻转
有时水平翻转图像是有意义的。就飞机而言,飞机的前部可能面向左,也可能面向右:
datagen = ImageDataGenerator(**horizontal_flip=True**)
train_generator = datagen.flow(images_data, batch_size=1)rows = 2
columns = 2fig, axes = plt.subplots(rows,columns)
for r in range(rows):
for c in range(columns):
image_batch = train_generator.next()
image = image_batch[0].astype('uint8')
axes[r,c].imshow(image)fig.set_size_inches(15,10)
对于上面的代码片段,生成四个图像就足够了,因为飞机的正面可以面向左或右:

请记住,翻转是随机的(有时您会得到所有四个原始图像,有时您会得到水平翻转的图像)。很可能上面的四个图像都是相同的。如果发生这种情况,只需再次运行该代码块。
垂直翻转
就像水平翻转一样,您也可以执行垂直翻转:
datagen = ImageDataGenerator(**vertical_flip=True**)
train_generator = datagen.flow(images_data, batch_size=1)rows = 2
columns = 2fig, axes = plt.subplots(rows,columns)
for r in range(rows):
for c in range(columns):
image_batch = train_generator.next()
image = image_batch[0].astype('uint8')
axes[r,c].imshow(image)fig.set_size_inches(15,10)
以飞机为例,把我们的飞机翻个底朝天可能没什么意义!如果您正在尝试执行图像识别,那么您的平面图像很可能是垂直的,因此训练您的模型识别颠倒的平面可能不太常见。对于其他情况,垂直翻转很有意义。

旋转
旋转,顾名思义,就是旋转你的图像。这对我们的飞机图像非常有用。以下代码片段将图像随机旋转 50 度:
datagen = ImageDataGenerator(**rotation_range=50**)
train_generator = datagen.flow(images_data)rows = 5
columns = 4fig, axes = plt.subplots(rows,columns)
for r in range(rows):
for c in range(columns):
image_batch = train_generator.next()
image = image_batch[0].astype('uint8')
axes[r,c].imshow(image)fig.set_size_inches(15,10)
通过旋转,输出显示了不同位置的飞机,模拟起飞和着陆位置:

聪明
另一种增强技术是调整图像的亮度。以下代码片段设置了亮度偏移值的范围:
datagen = ImageDataGenerator(**brightness_range=[0.15,2.0]**)
train_generator = datagen.flow(images_data, batch_size=1)rows = 5
columns = 4fig, axes = plt.subplots(rows,columns)
for r in range(rows):
for c in range(columns):
image_batch = train_generator.next()
image = image_batch[0].astype('uint8')
axes[r,c].imshow(image)fig.set_size_inches(15,10)
输出包含一系列不同亮度的图像:

变焦
您还可以放大或缩小图像:
datagen = ImageDataGenerator(**zoom_range=[5,0.5]**)
train_generator = datagen.flow(images_data, batch_size=1)rows = 5
columns = 4fig, axes = plt.subplots(rows,columns)
for r in range(rows):
for c in range(columns):
image_batch = train_generator.next()
image = image_batch[0].astype('uint8')
axes[r,c].imshow(image)fig.set_size_inches(15,10)
输出以各种缩放比例显示图像:

请注意,缩放图像会改变图像的纵横比。
结合所有的增强
当然,到目前为止我所讨论的各种增强技术都可以结合起来:
datagen = ImageDataGenerator(**width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True,
rotation_range=50,
brightness_range=[0.15,2.0],
zoom_range=[5,0.5]**)train_generator = datagen.flow(images_data, batch_size=1)rows = 8
columns = 8fig, axes = plt.subplots(rows,columns)
for r in range(rows):
for c in range(columns):
image_batch = train_generator.next()
image = image_batch[0].astype('uint8')
axes[r,c].imshow(image)fig.set_size_inches(15,10)
请注意,我省略了垂直翻转,因为它对我们的示例没有意义。
现在,输出显示了应用了各种放大效果的图像:

对图像类别使用 ImageDataGenerator
前面几节介绍了图像数据增强的基础知识,以及如何将其应用于单个图像。在深度学习中,我们经常会处理一组图像。现在让我们看看如何将图像增强应用于一组图像。为了便于说明,我假设在包含 Jupyter 笔记本的文件夹中,有一个水果文件夹和以下子文件夹:
**Fruits**
|**__banana**
|__banana1.jpg
|__banana2.jpg
|__banana3.jpg
|__ ...
|**__durian**
|__durian1.jpg
|__durian2.jpg
|__durian3.jpg
|__ ...
|**__orange**
|__orange1.jpg
|__orange2.jpg
|__orange3.jpg
|__ ...
|**__strawberry**
|__strawberry1.jpg
|__strawberry2.jpg
|__strawberry3.jpg
|__ ...
每个子文件夹包含一组图像。例如,香蕉文件夹包含许多名为banana1.jpg、banana2.jpg等的图像。子文件夹的名称将作为各种图像的标签。这意味着香蕉文件夹下的所有文件都包含香蕉的图像,以此类推。
为了从磁盘加载一系列图像,现在调用ImageDataGenerator实例的flow_from_directory()方法,而不是flow()方法(用于从内存加载图像):
train_datagen = ImageDataGenerator(
horizontal_flip=True,
vertical_flip=True,
rotation_range=50,
)**batch_size = 8**train_generator = **train_datagen.flow_from_directory(
'./Fruits',
target_size=(224,224),
color_mode='rgb',
batch_size=batch_size,
class_mode='categorical',
shuffle=True)**
注意,我现在将
batch_size设置为 8。您将很快看到批量大小的用法。
使用返回的迭代器,我可以找到各种水果的标签(香蕉、榴莲、橙子和草莓):
class_dictionary = train_generator.class_indices#---create a dictionary of labels---
class_dictionary = { value:key for key,value in
class_dictionary.items()}#---convert the dictionary to a list---
class_list = [value for _,value in class_dictionary.items()]
print(class_list)
您将看到以下输出:
Found 54 images belonging to 4 classes.
['banana', 'durian', 'orange', 'strawberry']
总共有 54 张图片在 4 个文件夹中。另外,class_list变量包含水果列表。
我现在将打印出由ImageDataGenerator类创建的一组增强图像。我将任意地将行数设置为 10,对于每一行,我想打印出返回的一批图像(在本例中是 8):
rows = 10fig, axes = plt.subplots(rows,batch_size)for r in range(rows):
#---get the batch of augmented images---
image_batch = train_generator.next() #---get the number of images returned---
images_count = image_batch[0].shape[0]
for c in range(images_count):
#---convert to unsigned integers for viewing---
image = image_batch[0][c].astype('uint8')
#---display the image---
axes[r,c].imshow(image) #---display the label of the image---
axes[r,c].title.set_text(
class_list[np.argmax(image_batch[1][c])]) #---hides the x and y-ticks---
axes[r,c].set_xticks([])
axes[r,c].set_yticks([])fig.set_size_inches(15,18)
因为batch_size现在被设置为 8(而不再是 1),所以每次调用train_generator.next()方法时,它都会返回一批的八幅增强图像。返回的图像数量基于您之前在flow_from_directory()方法中设置的batch_size:
train_generator = train_datagen.flow_from_directory(
'./Fruits',
target_size=(224,224),
color_mode='rgb',
**batch_size=batch_size,** # batch_size = 8
class_mode='categorical',
shuffle=True)
image_batch变量的值(由next()方法返回)是两个元素的元组:
- 第一个元素(
image_batch[0])是一个由 batch_size 图像组成的数组(4D 数组) - 第二个元素(
image_batch[1])包含图像的标签
上述代码片段产生以下输出:

请注意,在第七行,有两个没有图像的空图表。回想一下,图像集中总共有 54 幅图像,由于每批返回 8 幅图像(每行),前七行将显示总共 54 幅图像(8x6 + 6)。下图说明了这一点:

请注意,您可以将
rows设置为任何数字,并且ImageDataGenerator类将继续为您生成新的增强图像。
使用迁移学习建立模型
您现在知道如何使用ImageDataGenerator从磁盘加载图像集以进行增强。但是怎么用它来训练呢?以下代码片段展示了如何使用迁移学习构建深度学习模型。
迁移学习是一种机器学习方法,其中为一项任务开发的模型被重新用作第二项任务模型的起点。迁移学习减少了你需要花在培训上的时间。
from tensorflow.keras.models import Model
from tensorflow.keras.applications import VGG16
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D#---number of fruits---
NO_CLASSES = max(train_generator.class_indices.values()) + 1#---load the VGG16 model as the base model for training---
base_model = VGG16(include_top=False, input_shape=(224, 224, 3))#---add our own layers---
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(1024,activation='relu')(x) # add dense layers so
# that the model can
# learn more complex
# functions and
# classify for better
# results.
x = Dense(1024,activation='relu')(x) # dense layer 2
x = Dense(512,activation='relu')(x) # dense layer 3preds = Dense(NO_CLASSES,
activation='softmax')(x) # final layer with
# softmax activation#---create a new model with the base model's original
# input and the new model's output---
model = Model(inputs = base_model.input, outputs = preds)#---don't train the first 19 layers - 0..18---
for layer in model.layers[:19]:
layer.trainable=False#---train the rest of the layers - 19 onwards---
for layer in model.layers[19:]:
layer.trainable=True
#---compile the model---
model.compile(optimizer='Adam',
loss='categorical_crossentropy',
metrics=['accuracy'])
解释迁移学习如何工作超出了本文的范围。我将把它留给另一篇文章。
使用生成的图像进行训练
要使用增强图像进行训练,将train_generator传递到模型的fit()方法中:
#---train the model---
**step_size_train = train_generator.n // train_generator.batch_size**model.fit(**train_generator**,
**steps_per_epoch=step_size_train,**
epochs=15)
steps_per_epoch参数基本上表示一个纪元中有多少步骤——这取决于您拥有的图像数量以及之前定义的批次大小。如果你把这个设置得很高,那么你就是在做重复训练。您应该根据以下公式进行设置:
图像数量/批量
在我们的示例中,我们总共有 54 幅图像。因此在每个纪元中ImageDataGenerator类将变换所有 54 幅图像进行训练。在每个纪元中,模型将获得不同的图像变化。如果您有 15 个纪元,则总共将生成 15x54 个图像变体,并将其输入到训练模型中。
*ImageDataGenerator*类允许您的模型在每个时期接收图像的新变体。但是请记住,它只返回转换后的图像,而不会将其添加到您已有的图像集中。
https://weimenglee.medium.com/membership
摘要
我希望这篇文章已经给了你一个好主意,什么是图像数据增强的一切,为什么你需要他们在训练你的深度学习模型。特别是,我已经使用 TensorFlow 库中的 Keras 模块进行了演示。
这个微小的 Python 包创建了巨大的扩充数据集
原文:https://towardsdatascience.com/image-data-augmentation-pipeline-9841bc7bb56d
如何创建图像数据扩充管道来生成大量合成数据点

经过几个月的努力,你和你的团队已经为你的机器学习项目收集了大量的数据。
项目预算快结束了,剩下的只够训练模型。
但是一旦你训练了这个模型,你就会发现这个模型不能很好地概括问题。你收集的数据还不够。训练精度是如此之好,但在验证集上,它急剧下降。
从技术上讲,这就是我们著名的所谓过度拟合。
处理过度拟合有不同的方法。但是你的团队认为它们都不起作用。
剩下的是两个选项之一:
- 收集更多数据
- 通过轻微调整创建现有数据点的副本(数据扩充)
数据扩充被证明在不收集进一步数据的情况下提高机器学习模型的准确性。这是许多从业者经常使用的普遍技术。
在许多情况下,收集数据是一项昂贵的任务。你可能需要为设备、权限付费,更不用说在收集后给它们贴标签了。
以医学图像分类问题为例。对于如何收集医疗保健数据,T4 有法律限制。而在收集之后,要给它们贴上标签,就需要医生等熟练专业人士的专业知识。
这篇文章将通过例子讨论一个叫做albuminations的图像数据增强工具。这是一个在 MIT 许可下发布的开源 Python 库。
您可以从 PyPI 存储库中用下面的命令安装它。如果您正在寻找高级说明,请查阅官方文档。
pip install -U albumentations
数据增强能做什么?
数据扩充通过一些转换创建现有数据点的副本。例如,我们可以裁剪和旋转图像,使其看起来像新的一样。通过这种集成方法,我们可以将数据集进行多重化,并训练模型以提高其准确性。
这里有一个说明它的意思的例子。

图像数据增强如何工作的图解——作者使用来自 Pexels 的 Domenico Bertazzo 拍摄的斯里兰卡大象的照片
我们用不同的技术转换了基础图像。在许多照片中,我们结合使用了不止一种技术。通过继续这个过程,我们可以生成大量的数据点。
使用白蛋白创建图像增强管道。
使用白蛋白创建扩增管道非常简单。
最初,我们需要通过配置一系列转换来构建一个增强管道。然后我们可以使用任何图像处理库,如 Pillow 或 OpenCV 从文件系统中读取图像。每次我们通过我们配置的转换列表传递一个图像,它都会给我们一个改变的图像。
这里有一个例子,你可以复制开始使用。
在上面的例子中,我们使用了四种类型的转换。
1.我们从随机位置开始裁剪图像。我们配置了 RandomCrop API 来生成一张 256x256 大小的图片。
2.我们用了水平翻转。请注意,此操作并不总是适用。我们已经为它配置了 0.5 的概率值。这意味着通过这条管道的每个图像都有 50%的机会被水平翻转。
3.对于 0.5 和 0.3 的限制,随机亮度和对比度 API 将改变各自的图像特征。我们没有像水平翻转那样明确提到概率值。但是 API 的默认值是 0.5。因此,每张图像都有 50%的机会轻微改变亮度和对比度。当它适用时,亮度不会改变超过 50%,对比度最大值为 30%。
4.最后,我们随机旋转图像。我们在这里没有覆盖任何默认值。
然后,我们使用 Pillow 从文件系统中读取一个图像,Pillow 是一个广泛使用的用于图像处理的 Python 库。我们还把它转换成了一个 NumPy 数组。
最后,我们通过配置好的管道发送图像并显示结果。
蛋白沉积有几十个这样的转变。你可以从他们的 API 文档中了解更多细节。
如何创建带注释的扩充数据?
大多数计算机视觉应用必须处理带注释的图像。这些是在照片中标记和标注的对象,用于训练 ML 模型。
当扩充这样的数据集时,我们还需要知道那些被注释对象的新位置。

在增强过程中改变注释图像位置的图示。由作者使用来自 Pexels 的marián sicko的照片创建
我们的增强技术应该相应地重新计算坐标。这项任务在蛋白沉积方面毫不费力。我们需要向管道提供初始坐标和类别 id 的注释对象。结果会有它的新位置。
我们已经修改了上面创建增强管道的例子中的 Compose 方法。我们添加了另一个输入 bbox_params 及其配置。
这样,我们告诉 Albumentation 使用 coco 格式来扩充带注释的图像,并使用 category_label 来查找它的标签。Coco 是该库中可用的四种重记方法之一。
最后,我们在运行时为每个图像传递两个额外的参数。第一个通过它的起始坐标、宽度和高度来定义盒子。然后我们定义的每个容器的标签。
结果是这样的。

变换后,对象的位置发生了变化。然而,管道已经正确地找到了它的新位置。
最终想法
通过重用现有图像,数据扩充节省了大量的时间、精力和预算。我们可以用任何图像处理库做到这一点。但是一些特定的任务可能需要额外的努力才能正确完成。
例如,带注释的图像需要重新注释增强图像上的对象。在这些情况下,像 Albumentations 这样的工具就派上用场了。
这篇文章简要介绍了我们可以用这个 Python 库做什么。我希望下次你训练机器学习模型时,你会用它来提高准确性,而不用考虑进一步的数据收集。
💔-ways-to-deploy-machine-learning-models-in-production-cdba15b00e> </5-python-gui-frameworks-to-create-desktop-web-and-even-mobile-apps-c25f1bcfb561>
感谢阅读,朋友!在LinkedInTwitterMedium上跟我打招呼。
还不是中等会员?请使用此链接 成为会员 因为,不需要你额外付费,我为你引荐赚取一小笔佣金。
照相机的成像和针丨孔丨模型
原文:https://towardsdatascience.com/image-formation-and-pinhole-model-of-the-camera-53872ee4ee92
Python 中图像形成和相机校准综合教程系列的第 1 部分

介绍
相机如何捕捉图像的基本概念很简单,但它是我们拥有的最强大的工具之一。在这一系列教程中,我们将学习如何对相机进行数学建模,并实现其在自动驾驶汽车、场景重建、多视图几何等方面的全部潜力。一旦你理解了这一点,你会惊讶于你能做的所有事情。
我强烈主张通过做来学习,我们将在这个系列中做很多实际操作的例子,这将加强你的理解并使事情变得有趣。
先决条件
你应该熟悉矩阵、向量、线性变换和线性代数的基本概念。然而,你总是可以边走边学。
如果你是一个完全的初学者,我强烈推荐你先上 Gilbert Strang 的线性代数课。
好吧,我们开始吧。
图像形成
成像的基本思想是捕捉从物体反射到介质上的光线。现在,我们可以简单地把媒介放在物体前面并捕捉它,如果你这样做,你会在整个胶片上只看到灰色。原因是来自物体不同点的光线在胶片上相互重叠,造成一片混乱。下图对此进行了说明:

我们要的是世界上的点和影片中的像素一一对应。我们如何实现这一目标?这个想法是在物体和胶片或介质之间放置一个带有针丨孔丨的屏障。这样我们可以确保没有重叠,如下图所示:

然而,这一次图像将被倒置在胶片上。这就是所谓的相机针丨孔丨模型,广泛应用于数学建模。
针丨孔丨摄像机
为了对针丨孔丨摄像机进行数学建模,我们做出以下假设:
- 像平面,即捕捉光线的胶片或介质,位于针丨孔丨的前面。然而,在现实世界中,它位于针丨孔丨的后面。这种假设使得投影建模更容易,因为我们不必担心反转图像。
- 来自不同点的所有光线会聚在针丨孔丨处,针丨孔丨也可以称为投影中心或相机中心。
其思想是,点的图像是该点在图像平面上的投影,或者是从相机中心到该点的线与图像平面相交的地方。
世界空间是相机、点和对象所在的位置。

照相机的针丨孔丨模型
上图显示了相机针丨孔丨模型的简单设置。这里,相机中心位于原点,图像平面平行于 XY 平面,并位于离它一定距离的位置(这个距离称为焦距,我们将在后面看到)。
左手系统与右手系统
给你提个问题:图中所示的坐标系是左手系还是右手系?现在,如果你尝试,你将能够沿着坐标轴对齐你的右手,所以这是一个右手坐标系。

右手系统与左手系统
我们假设在右手系统中相机指向-ve Z 轴。
几何摄像机校准
现在,摄像机可以放在世界上的任何地方,也可以以任何方式定向。我们的工作是弄清楚它是如何将世界空间中的点投射到图像平面上的。换句话说,我们必须弄清楚世界上的点和它们在相机拍摄的图像上对应的像素之间的关系。
我们需要搞清楚两个转变:
- 世界坐标系到相机坐标系的基变换。本质上,这让我们从相机的角度看世界。这被称为相机姿势或相机外物。
- 将从相机观察到的世界中的点投影到相机的图像平面上的投影变换。这就是所谓的相机固有特性。
找出这些转换的过程被称为相机校准,也是本系列的目标。
结论
现在我们已经看到了图像是如何形成的,我们可以把图像的定义重新思考为从三维空间到二维空间的投影。
在下一篇文章中,也就是系列文章的第 2 部分,我们将通过一个实际操作的例子深入探讨相机的本质。
参考
图像制作者名单
本文中的所有图片和数字,除非在标题中明确提及其来源,否则均由作者提供。
Python 中的图像(元)数据特征提取
原文:https://towardsdatascience.com/image-meta-data-feature-extraction-in-python-5fdf5778508a
探索照片图像的元数据和颜色相关特征,以便进一步用于分析和 ML
在处理图像数据时,对象识别及其应用一直是数据分析师和数据科学家最关心的问题。很少关注利用这些图像的元数据。事实是,我们可以仅从图像元数据中获得相当多的洞察力。在本文中,我将分享我们如何从图像(照片)文件中提取一些突出的元数据相关特征,以便进一步处理和分析。
本分析中使用的完整代码在这个 Github 项目下共享。
理解数字图像
计算机将数字图像存储为按列和行排列的正方形像素(图片元素)的阵列或矩阵:换句话说,是一个二维矩阵。根据这些正方形像素的大小,图像可能看起来更像马赛克(像素化)或更平滑;我们称之为图像分辨率。

数字图像是方形像素的阵列(图片由作者提供)
这些像素包含关于颜色和强度的信息。对于一幅灰度图像,像素没有颜色信息,但有一个 8 位整数的强度信息,给出 256 种可能的不同灰度。对于彩色图像,像素用 RGB — 3 层二维阵列表示,其中三层用相应的 8 位整数表示图像的红色、绿色和蓝色通道的强度。
来自数码相机(包括智能手机和扫描仪)的图像具有标准化的附加元数据,称为可交换图像文件格式(Exif)。引用维基百科,Exif 数据可以覆盖很广的范围,包括:
- 相机设置:相机型号和品牌、旋转、光圈、快门速度、焦距、测光模式、ISO 速度信息等
- 图像指标:像素尺寸、分辨率、色彩空间和文件大小
- 日期和时间信息
- 位置信息
- 版权信息
要从图像文件中提取的信息主要是直接数据或者只是上述主要属性的变换。
功能探索
为了这次探索,我使用了两个主要的 Python 库进行图像处理: imageio (用于图像读写)和skimage(高级图像处理)。
提取图像属性
将图像数据导入 Python 记事本后,我们可以直接开始从图像中提取数据。可以生成的一些基本属性包括:
- 对象的类型(图像数组)
- 图像的形状:高度、宽度、大小(百万像素)
- 图像的维数:图像的数组维数;彩色图像通常为 3(R-G-B 通道)
import imageio
import matplotlib.pyplot as plt# Load and show image
pic = imageio.imread('Documents/Photos/olivia-singapore-s1_11.jpg')plt.imshow(pic)# Getting basic properties
print('Type of the image : ',type(pic))print('Shape of the image : {}'.format(pic.shape))
print('Image Height : {}'.format(pic.shape[0]))
print('Image Width : {}'.format(pic.shape[1]))
megapixels = (pic.shape[0]*pic.shape[1]/1000000)
print('Megapixels : {}'.format(megapixels))print('Dimension of Image : {}'.format(pic.ndim))
ImageIO 还允许将可用的元数据加载到字典中,可通过 meta 属性访问。
# Print the available metadata fields
print(pic.meta.keys())
****每个图像的元数据可能因图像的捕获和处理方式而异。例如,在 Adobe Photoshop 或 Lightroom 等软件中编辑过的图像可以有附加元数据来捕获应用程序配置。

不同图像文件上的不同元数据度量
可以使用meta函数提取每个元数据度量的值。
print('DPI : ',pic.meta['dpi'])
print('Adobe : ',pic.meta['adobe'])
print('Adobe Transform : ',pic.meta['adobe_transform'])
print(pic.meta['exif'])
和在同一个元数据度量中,值可能不同。例如,使用智能手机拍摄的图像的“EXIF”元数据可能有 GPS 位置数据,而使用数码相机(无 GPS)拍摄的图像可能没有。

不同影像文件上 EXIF 元数据的不同值
此 EXIF 元数据可用于多种用途:
- ****图像取证(调查图像真实性、来源等)
- ****配置设置(即复制相同的设置/颜色用于未来拍摄的图像)
提取颜色属性
如上所述,数字图像是具有颜色和强度信息的像素的集合。因此,图像中要探索的主要属性是颜色和强度本身。
这些信息被记录在一个三层矩阵“ndarray”中。第一和第二维是像素的行和列,而第三维是三个颜色通道。为了获得特定像素的颜色强度,我们可以使用常用的数组访问语法array[row,column,color]来访问它,如下所示。
# Accessing intesity for pixel located at Row : 100 ; Column : 50
# Each channel's value of it, gradually R,G,Bprint('Value of only R channel {}'.format(pic[100, 50, 0])) #Red
print('Value of only G channel {}'.format(pic[100, 50, 1])) #Green
print('Value of only B channel {}'.format(pic[100, 50, 2])) #Blue

访问特定像素的颜色强度
我们还可以剖析每个颜色通道的图像,即 R ed、Ggreen 和 B lue 通道,并使用图像可视化和直方图按顺序探索亮度的变化。
# Visualizing each color's channel# Red channel
plt.title('R channel')
plt.ylabel('Height {}'.format(pic.shape[0]))
plt.xlabel('Width {}'.format(pic.shape[1]))
plt.imshow(pic[ : , : , 0])
plt.show()# Green channel
plt.title('R channel')
plt.ylabel('Height {}'.format(pic.shape[0]))
plt.xlabel('Width {}'.format(pic.shape[1]))
plt.imshow(pic[ : , : , 1])
plt.show()# Blue channel
plt.title('R channel')
plt.ylabel('Height {}'.format(pic.shape[0]))
plt.xlabel('Width {}'.format(pic.shape[1]))
plt.imshow(pic[ : , : , 2])
plt.show()

红色、绿色和蓝色通道的可视化
# Showing color intensity distribution in a histogram# Obtain the red channel
red_channel = pic[:, :, 0]
# Plot the red histogram with bins in a range of 256
plt.hist(red_channel.ravel(), bins=256)
# Set title and show
plt.title('Red Histogram')
plt.show()# Obtain the green channel
green_channel = pic[:, :, 1]
plt.hist(green_channel.ravel(), bins=256)
plt.title('Green Histogram')
plt.show()# Obtain the blue channel
blue_channel = pic[:, :, 2]
plt.hist(blue_channel.ravel(), bins=256)
plt.title('Blue Histogram')
plt.show()# Combined Histogram
plt.hist(red_channel.ravel(), bins = 256, color = 'red', alpha = 0.5)
plt.hist(green_channel.ravel(), bins = 256, color = 'green', alpha = 0.5)
plt.hist(blue_channel.ravel(), bins = 256, color = 'blue', alpha = 0.5)plt.xlabel('Intensity Value')
plt.ylabel('Count')
plt.legend(['Red_Channel', 'Green_Channel', 'Blue_Channel'])plt.title('Combined Histogram')
plt.show()

颜色强度直方图
这里我们可以看到,对于图像的每个颜色通道,大多数像素都具有高颜色强度(看到直方图在 250 强度值附近的尖峰)。蓝色通道具有相对较高的像素和较低的强度,这解释了整个 B 通道可视化中更多的填充颜色。
理解这一点对图像处理很有用。例如,如果图像想要在美学上被增强为更加暖色调 或冷色调。要使图像更加暖色调,您可以增加 R 通道的亮度,而要使图像更加冷色调,您可以增加 B 通道的亮度。****
高级功能
从上面的颜色强度数据提取中进一步探索,我们可以转换和外推它们以获得进一步的见解,如获取图像调色板和边缘检测。
获取图像调色板可用于分析图像相似性(就光照和颜色而言),而无需直接处理对象检测。想法是获得每个颜色通道的强度数据,然后将具有相似强度的像素聚集在一起。提取的主色是聚类中心数组。这种特征提取的代码和指南可以在极客指南中找到。****

提取图像主色
可以对图像数据进行的其他变换是边缘检测和面部检测。这两者都可以使用 Python 中的skimage库进行处理。
边缘检测通过检测像素亮度(强度值)的不连续性来工作。这种边缘检测的流行算法之一是 Sobel 。这是一个关于撇帐的功能,使用指南可以在撇帐文档中找到。

Sobel 边缘检测
结束语
除了直接处理和对象检测之外,图像数据还可能包含大量有价值的信息。范围从元数据到内容—颜色/强度提取和转换。元数据(特别是 EXIF 值)对于数字取证分析和调查-检查图像文件的真实性非常有用。内容相关的特性(颜色)本身对调色板/vibes 探索很有用。结合其他先进的处理和算法,它们可以用于具有各种应用的图像检测。
本分析中使用的完整代码在这个 Github 项目下共享。
图像处理:平凡化
原文:https://towardsdatascience.com/image-processing-trivialized-575bfa4bec3b
深度学习,对于一切,为什么?

杰姆·萨哈冈在 Unsplash 上的照片
图像处理的意义正在递减,对于图像分类、物体检测等很多任务,它都被深度学习所取代。对于一些人来说,我同意深度学习比图像处理提供了更好的结果。但对于其他人,我会否认。在机器学习中,不能一刀切。根据我的经验,机器学习任务都是为了找到一个简单且接近准确的解决方案。客户和管理者都没有兴趣处理复杂和僵化的解决方案,除非它们比简单的解决方案好得多。
当采用深度学习代替图像处理时,也会出现类似的情况。它成为过度工程的典型案例。我最近在处理表面裂纹检测数据集时遇到了类似的情况。它受“CC by 4.0”许可。数据集包含有裂缝和无裂缝的各种混凝土表面的图像。数据集的目标是对图像进行分类。我喜欢浏览别人发表的作品。在这样做的时候,我惊讶地发现,他们中的大多数都实现了用于图像分类的卷积神经网络。我脑海中出现的即兴问题是,
为什么选择卷积神经网络?为什么不是图像处理?
这让我对自己实施图像处理的决定产生了怀疑,因为他们中的大多数人实施了用于图像分类的卷积神经网络。因此,我决定深入了解问题陈述,并设计合适的图像处理算法来破解它。在这篇文章中,我们将经历我所承担的整个过程。
目录
当前工业方法
裂缝是监测混凝土结构健康和诊断其劣化的一个重要方面。如果在早期阶段发现裂缝,可以采取进一步措施改善结构健康状况。
混凝土结构的检查每五到十年进行一次,以评估刚度和抗拉强度。在此过程中,经验丰富的土木工程师会目视检查裂缝及其特征,如长度、形状和深度。接下来,他们画出所有裂缝的草图,并准备检查报告。这一过程耗时、成本高,并且容易出现人为错误。这些行业最近开始使用摄像机进行基于图像的检测,以克服这些问题。公共数据集是可用的,包括有裂缝和没有裂缝的混凝土结构的图像。
数据集概述
用于问题陈述的数据集是“表面裂纹检测”。Kaggle 上也有。数据集包含有裂缝和无裂缝的各种混凝土表面的图像。图像数据分为两类,即负像(无裂纹)和正像(有裂纹),放在单独的文件夹中进行图像分类。每个类有 20000 幅 RGB 图像,每幅图像的尺寸为 227 x 227 像素。高分辨率图像在表面光洁度和照明条件方面差异很大。
可用的解决方案
Kaggle 上可用于该数据集的大多数笔记本要么应用定制的 CNN 架构,要么微调预训练的模型。那很好,但是为什么不保持简单。在我们的领域中,图像处理是一个被严重低估的方面。对图像处理的深入理解有助于解决几个问题。
我用 OpenCV 开发了一个图像处理算法来解决图像分类的问题。对于那些不知道 OpenCV 的人来说,它是一个 python 库,提供了调整图像和对图像执行一些数学运算的功能。
图像处理算法
博客的这一部分将描述算法的代码演练,这将提供对解决方案的理解。首先,我比较了两类图像的数量,以确认我有足够的样本来测试我的算法。
Total number of positive images are 20000
Total number of negative images are 20000
可以实现翻转、旋转和颜色增强等数据增强技术来增加数据量,但对于图像处理来说似乎没有必要。首先,我们将看一些消极和积极类的例子。


用于分类的混凝土裂缝图像的负像
下一步我已经用 OpenCV 创建了一个图像处理算法。我把这个过程分成了四个简单的步骤。
- 锐化:每一张通过算法的图像都会被初步锐化。这一步骤的目的是确保图像中的每个边缘都是明显可见的。
- 模糊:我们只对捕捉大边缘感兴趣,因为它们代表裂缝。如果在不模糊的情况下应用锐化,它也将捕捉小边缘。因此,我在第二步中对图像进行了模糊处理,以消除小边缘。锐化的强度必须高于模糊,以确保大边缘在此步骤中不会模糊。
- 阈值处理:首先将图像转换为灰度图像(单通道),然后将灰度图像转换为二值图像。二进制图像是像素值为 0 或 255 的图像。通俗地说可以认为是黑白图像。我使用了一个简单的阈值技术,基本阈值为 230。
- 轮廓检测:从第三步获得的二值图像中检测轮廓。对于每幅图像,我都提取了具有最大像素点的轮廓。图像分类的两个关键条件如下:
- 正片图像的最大轮廓应该超过 N 个像素。
- 负像的最大轮廓应该少于 N 个像素。
如果正类的分数接近于零,负类的分数接近于 20000,则该算法工作良好。在这种情况下,我们将 N 取为 100。可以对其进行优化以获得更好的结果。上面定义的方法在数据集的所有图像上实现。
{'Positive': 1582, 'Negative': 19962}
上述结果可以解释如下:
- 1582 张正面图像被错误分类
- 19962 张底片图像被正确分类
我也计算了算法的准确性。
Accuracy is 95.95%
结论
在代码演练之后,您可能会想,“这很简单。我也可以做到这一点”。确实如此。比起检测裂缝,我更感兴趣的是传播对图像处理重要性的认识。深度学习并不是所有问题的答案。一些用例可以用图像处理来解决,如寻找停车位、评估 MCQ 考试等。此外,还有许多额外津贴,如下所列。

图像处理津贴。来源:作者图片
除了 OpenCV,Scikit-image 也是一个著名的对图像进行操作的库。我希望通过这篇文章,你学会了图像处理的重要性。下面列出了一些通过 OpenCV 学习图像处理的资源,
就这些了,伙计们。快乐学习。
不同生成对抗网络产生的深度赝品图像质量评估
研究由不同 GAN 结构产生的深度假图像的质量

马库斯·斯皮斯克在 Unsplash 上的照片
如果你喜欢今天的阅读,请关注我,并告诉我你是否还有其他话题想让我探讨!如果你没有中等账号,就通过我的链接 这里 报名吧!另外,在LinkedIn上加我,或者随时联系!感谢阅读!
介绍
我最近完成了一项计算机视觉研究,并发现了我的 deepfake 图像的一个非常有趣的结果:GANs 的架构在 deep fake 的输出中创造了不同水平的“质量”。在这个定义中,质量可以联系到用手机的低像素镜头拍摄照片和专业摄影师使用的更复杂的设置之间的差异。
生成对抗网络
你可能想知道,什么是生成性对抗网络(GAN)?简而言之,GAN 有两个子角色,称为鉴别器和生成器,它们相互作用。这些连续的相互作用导致合成数据的输出。不仅是创建的数据是人工的,更重要的是它真实地呈现了它试图模仿的真实数据的分布和特征。看看我在下面创建的 deepfake 图片。人眼很难分辨真假!GANs 可以帮助平衡数据集,并为您的数据提供独特的样本,以增加数据集的多样性。
技术性较低的深潜
正如我所说,GAN 是一个由两部分组成的网络,一旦经过训练,它就会输出合成数据。请记住,一个“好”的 GAN 产生的数据我们无法分辨是假还是真!
- 注意:虽然我说的是 GAN 会产生图像,但它也能产生表格数据!
鉴别器
GAN 中鉴别器的工作是确定图像是真是假。我们给鉴别者真实的图像,而假图像来自发生器。随着时间的推移,鉴别器学会了如何更好地检测更逼真的假图像和真实图像之间的差异。
发电机
生成器的工作是创建假图像,称为 deepfakes 。一般来说,在模型训练的每一轮之后,生成器将被告知其图像基于鉴别器标记其样本的真实程度(如果许多样本被标记为“真实”,则生成器将继续制作与这些图像对齐的图像,因为它们欺骗了鉴别器)。随着发生器欺骗鉴别器的能力越来越强,看起来更真实的数据和图像就产生了。生成器正在通过提供给它的资源构建样本以提供给鉴别器。
更多技术深度潜水
如上所述,GAN 由两个模型组成:鉴别器和发生器。这是一个最小-最大游戏,输出是真实的合成数据。

甘函数(图片来自作者)
生成器的目标是两个最小化上面的函数(即将函数发送到 0)而鉴别器的目标是最大化上面的函数(即发送到 1)。该函数表示一个 纳什均衡问题 ,其中鉴别器和发生器将有希望一起达到均衡。在模型训练中,重要的是鉴别器和生成器都不比另一个强,以便它们能够更好地一起检测和生成假图像。
鉴别器

鉴别器的功能(图片来自作者)
鉴别器希望最大化图像为真的对数概率减去图像为反的对数概率。这在数学上是有意义的。该算法在任何时候将假图像误标为真图像时都会对鉴别器进行处罚。在训练鉴别器时,建议给出一批真的和假的样品,而不是单个的混合样品,以增加稳定性。
发电机

生成器的功能(图片来自作者)
现在,之前我已经说过生成器试图最小化整体函数,但是从数学上来说,这个函数可以通过查看图像是假的而不是真实的概率来表示为最大化函数。生成器希望最大化图像是假的对数可能性。
您可能想知道,GAN 是如何创建假数据的?当向生成器提供来自向量空间的随机噪声时,它会这样做。随着时间的推移,生成器将学会将不同的特征映射到该向量空间,并将这些特征汇集在一起,以创建我们从其输出中获得的真实数据。生成器实际上是从零开始构建数据!
我使用的 GANS 类型
由于我使用了创建 deepfake 图像的 GANs,鉴别器和生成器网络是卷积神经网络(CNN)。阅读下面关于 CNN 的更多信息!
鉴别器是典型的 CNN,而发生器是一个 transpose CNN,它会随着时间的推移进行上采样并创建 deepfake 图像。我使用的 GAN 的变体是深度卷积 GAN (DCGAN)(在此了解更多关于此变体的信息)、条件化 DCGAN (cDCGAN)、Wasserstein GAN (WGAN)(在此了解更多关于此变体的信息)和条件化 WGAN (cWGAN)。
我不会深入每个变体的本质,但 DCGAN 是所有 GAN 变体中最不稳定的。GAN 模型通常在本质上是不稳定的,并且可能难以训练。通过调节 GAN,在这种情况下,我在图像标签上调节 GAN,我们可以获得更高质量的输出。Wasserstein GAN 比 DCGAN 更稳定,其架构有多种变化,包括采用推土机距离度量作为模型训练函数,该函数查看两个概率分布之间的相似性。
结果
我试图再造的一件物品是一辆汽车:

汽车(图片来自作者)
有趣的是,汽车图像的质量刚刚好,甘实际上是要清除一些明显的模糊。

Deepfake 汽车(图片来自作者)
请从左到右注意图像质量如何提高。在 DCGAN 中,你可以看到许多模糊的 RBG 噪声的迹象。当我们从左向右前进时,RGB 和模糊随着时间慢慢消失,cWGAN 变体能够产生最高质量的图像。所有的 GANs 都接受了相同数量的训练。这表明 GAN 的稳定性如何直接影响 deepfake 图像的“纯净”和逼真程度。
另一个有趣的发现是 WGAN 变体如何产生比原始图像更高质量和更清晰的图像。这可能部分是由于架构中增加的稳定性允许 GAN 捕捉高质量的像素特征并产生更真实的深度赝品。此外,WGANs 产生了更加逼真的阴影。
结论
如果您计划在研究中使用 GAN,我强烈建议您使用 WGAN 变体,并对数据集的某个要素进行调节(使用嵌入向量对图像标签上的 GAN 进行调节非常容易)。我希望这项研究向你展示了在任何模型中稳定性是多么重要,并且能够产生更强的输出。我的发现显示了不同的 GAN 变体如何产生不同质量的图像,以至于看起来好像 deepfakes 是用不同类型的相机拍摄的。当您想要平衡数据集或增加数据集的多样性以生成更通用、性能更高的模型时,这些发现会派上用场。感谢阅读!
基于 K 均值的图像量化
原文:https://towardsdatascience.com/image-quantization-with-k-means-db7127503bcb
使用 python、scikit-learn、numpy、PIL 和 matplotlib 通过量化进行图像压缩的简单实践教程
量子化指的是一种用单个量子值来表示一系列值的技术。对于图像,这意味着我们可以将整个颜色范围压缩成一种特定的颜色。这种技术是有损耗的,也就是说,我们故意丢失信息以支持更低的内存消耗。在本教程中,我将向你展示如何用很少的代码自己实现颜色量化。我们将使用 Python 和 scikit-learn、numpy、PIL 和 matplotlib。
让我们从下载由 Pascal van Soest 拍摄的“Storr 老人”的美丽图像开始,我们将使用它(如果您使用 Windows 或没有 wget 访问权限,只需下载图像并将其保存为 image.jpeg):
wget -O image.jpg [https://unsplash.com/photos/ZI9X8Kz3ccw/download?ixid=MnwxMjA3fDB8MXxhbGx8MjN8fHx8fHwyfHwxNjY1MzE2ODE1&force=true](https://unsplash.com/photos/ZI9X8Kz3ccw/download?ixid=MnwxMjA3fDB8MXxhbGx8MjN8fHx8fHwyfHwxNjY1MzE2ODE1&force=true)

原始(调整大小)图像。由帕斯卡尔·范·索斯特在 Unsplash 上拍摄的照片。
接下来,我们可以加载图像,调整其大小以获得更好的性能,并将其作为 numpy-array 查看:
from PIL import Image
import numpy as np
import matplotlib.pyplot as pltimg = Image.open("image.jpg").resize((960, 600))
x = np.asarray(img)
图像编码成宽高通道大数组(这里: 960 * 600 * 3 )。通常,彩色图像存储为 RGB,并具有 3 个颜色通道(红色、绿色和蓝色)。你可以把它想象成一个大的 2D 数组,每个条目包含 3 个值。每个值代表 0 到 255 (2**8-1)之间的特定颜色通道的强度。其实这本身已经是 8 位量化了。
对于全局量化,我们丢弃关于每个通道的信息,并简单地将我们阵列中的所有强度作为一个大向量来处理。我们可以使用 matplotlib 轻松绘制结果直方图:
plt.figure(figsize=(16, 6))
plt.hist(x.ravel(), bins=np.arange(256), density=True, linewidth=0)
plt.xlabel("Value")
plt.ylabel("Density")

颜色强度的全局分布。
为了量化,我们希望用一个更小的数来代替这 256 个值,例如 8。为了实现这一点,我们可以简单地将空间平均分成 8 个“箱”,并将其中的所有值映射到该箱的平均值。但我们可以看到,我们的图像中的强度并不是均匀分布的:在略高于零的位置有一个较大的峰值,在 160°附近有一个较大的强度累积。如果我们平均划分空间,我们将忽略偏斜的分布,并且低估/高估特定的强度。相反,我们希望在密度高的区域有更窄的面元以获得更高的精度,而在密度较低的区域有更宽的面元,因为我们在那里没有太多的样本。
我们可以通过使用 K-Means 来实现这一点。这是一种无监督聚类算法,常用于在给定数据中寻找 k 聚类中心(称为质心)。你可能已经看到了在多维问题中的应用,但它也适用于 1D 分布,如我们的问题。我不打算在这里介绍 K-Means——有无数的文章解释得比我更好,或者,如果你更喜欢一个视频,我强烈建议你看一下 Josh Starmer 的 StatQuest 。
对于本文,我们将使用 K-Means 的一个略有不同的版本,称为 MiniBatchKMeans。类似地,对于深度学习中的优化,这里的想法是不在所有样本上计算聚类,而是通过在较小的批次上计算聚类来贪婪地逼近解决方案。这大大加快了收敛速度!
借助 scikit-learn,培训迷你批处理方式变得非常简单:
from sklearn.cluster import MiniBatchKMeansk_means = MiniBatchKMeans(k, compute_labels=False)
k_means.fit(x.reshape(-1, 1))
注意,我们将x.reshape(-1, 1)传递给 MiniBatchKMeans。这将我们的 3D 矩阵展平为一个向量,并增加了一个大小为 1 的伪维度,因为估计器只支持 2D 形状的阵列。此外,我们告诉评估者不要通过compute_labels=False计算每批标签,否则会显著增加训练时间。训练之后,我们希望将颜色强度映射到最近的质心。估计器没有直接这样做的功能,但是我们可以预测每个样本的质心标签,然后使用这个标签来求解质心的值:
labels = k_means.predict(x.reshape(-1, 1))
q_x = k_means.cluster_centers_[labels]
我们现在已经有了原始图像的量化表示,但我们需要将数组重新整形为原始图像形状,并将 scikit-learn 处理的所有浮点数转换回整数:
q_img = np.uint8(q_x.reshape(x.shape)
让我们把它们放在一个函数中,这个函数将把量化图像作为 numpy 数组返回:
from sklearn.cluster import MiniBatchKMeansdef quantize_global(x, k):
k_means = MiniBatchKMeans(k, compute_labels=False)
k_means.fit(x.reshape(-1, 1))
labels = k_means.predict(x.reshape(-1, 1))
q_x = k_means.cluster_centers_[labels]
q_img = np.uint8(q_x.reshape(x.shape)
return q_img
让我们看看量化 k=8 后强度分布会发生什么变化:
quantized_img = quantize_global(x, 8)plt.figure(figsize=(16, 6))
plt.hist(x.ravel(), bins=np.arange(256), density=True, linewidth=0, label="original")
plt.hist(quantized_img.ravel(), bins=np.arange(256), density=True, linewidth=0, label="quantized")
plt.xlabel("Value")
plt.ylabel("Density")

量化前后颜色强度的全局分布(全局,k=8)。
正如我们所看到的,正如我们所要求的,我们的原始分布已经被 8 个值所取代。请注意,根据原始分布的密度,质心的间距是不相等的。
最后,让我们测试一下 k 的值,看看这会如何影响我们的结果:
Image.fromarray(quantize_global(x, k))






全局 K-表示 k=1、2、4、8、16 和 32(从上到下,从左到右)的量化。
对于 k=1,我们只看到一个灰色的图像。这并不奇怪,因为我们只剩下一种颜色强度,它将位于我们颜色空间的中间(即大约 125)。 (125,125,125) 在 RGB 中是灰色。随着我们增加 k ,我们看到结果图像更准确地代表了原始图像,因为我们学习了更多的强度来描述我们的图像。现在,注意一下 k=8 的图像—图像前景看起来非常准确,但背景非常分散。由此有两个重要的收获:1)量化使渐变(比如在灰色的天空中)看起来很糟糕;2)由于我们的 KMeans 方法,我们更关注前景,其看起来具有更密集的强度分布。
你可能会惊讶地发现每个图像中不止有 k 种颜色(例如,看到 k=2 的图像),但解释相当简单:尽管我们只学会用 k 的强度来表示我们的图像,但我们仍然有 3 个通道,这给了我们可以表示的 k**3 种颜色组合。
但是图像大小会怎样呢?如果我们将图像保存到磁盘上,我们已经可以看到图像大小的减小,尽管在保存过程中还做了更多我们不知道的处理。

磁盘上的图像大小(如 PNG)随着质心数量(k)的增加而增加。
在科学领域,为你的方法设定基准是很好的实践。所以你可能想知道我们重建原始图像的效果如何。让我们通过使用 numpy 计算量化图像和原始图像之间的绝对误差和平方误差来进行测试,并将误差绘制成柱状图:
plt.figure(figsize=(8, 5))
plt.bar(range(9), [np.linalg.norm((x - quantize_global(x, 2**i)).ravel(), ord=1) for i in range(9)])
plt.xlabel(r"Number of Centroids ($2^k$)")
plt.ylabel(r"Absolute Error")plt.figure(figsize=(8, 5))
plt.bar(range(9), [np.linalg.norm((x - quantize_global(x, 2**i)).ravel(), ord=2) for i in range(9)])
plt.xlabel(r"Number of Centroids ($2^k$)")
plt.ylabel(r"Squared Error")


不同 k 值下量化图像的绝对(左)和平方(右)误差。
我们可以看到,绝对误差随着质心数量的增加而减小,最终在 k=256 时变为零(这时我们没有压缩)。尽管如此,还是存在一些误差,例如,由于我们在没有舍入的情况下对 uint8 进行了简单的转换,这导致了一些可见的平方误差。有趣的是,平方误差似乎也在增加,直到 k=4。请理解,误差是一种从数学上捕捉差异的好方法,但我们的人眼可能对这种差异不太敏感。毕竟,从这个意义上来说,这个错误可能并不意味着什么。问问你自己:我能发现 k=32 和上面的原始图像之间的错误吗?
逐通道量化
到目前为止,我们已经将所有强度视为相似的,与通道无关。然而,如果我们绘制每个通道的强度分布,我们可以看到一些差异,特别是在蓝色通道中:
plt.figure(figsize=(16, 6))plt.hist(x[:, :, 0].ravel(), color="red", bins=np.arange(256), density=True, linewidth=0, alpha=0.5)
plt.hist(x[:, :, 1].ravel(), color="green", bins=np.arange(256), density=True, linewidth=0, alpha=0.5)
plt.hist(x[:, :, 2].ravel(), color="blue", bins=np.arange(256), density=True, linewidth=0, alpha=0.5)plt.xlabel("Value")
plt.ylabel("Density")

颜色强度的通道式分布。
我们还可以轻松调整代码,计算每个通道的量化,而不是全局量化:
def quantize_channels(x, k):
quantized_x = x.copy()
for d in range(3):
channel = x[:, :, d].copy()
k_means = MiniBatchKMeans(k, compute_labels=False)
k_means.fit(channel.reshape(-1, 1))
labels = k_means.predict(channel.reshape(-1, 1))
quantized_x[:, :, d] = np.uint8(k_means.cluster_centers_[labels]).reshape(channel.shape)
return quantized_x
然后根据全局量化,通过上面的代码对损失进行基准测试:


采用全局和通道量化时,不同 k 值下量化图像的绝对(左)和平方(右)误差。
然而,在最好的情况下,它比全局量化稍好,有时甚至更差。事实上,它需要多达 3 倍的内存,训练成本也是 3 倍以上的昂贵,似乎全局量化是一个更好的方法。
我试图理解为什么通道量化性能如此之差,似乎原因很简单,我们独立处理颜色通道,质心没有太大差异:
quantized_img = quantize_channels(x, 8)plt.figure(figsize=(16, 6))
plt.hist(quantized_img[:, :, 0].ravel(), bins=np.arange(256), density=True, linewidth=0, label="R", color="red")
plt.hist(quantized_img[:, :, 1].ravel(), bins=np.arange(256), density=True, linewidth=0, label="G", color="green")
plt.hist(quantized_img[:, :, 2].ravel(), bins=np.arange(256), density=True, linewidth=0, label="B", color="blue")
plt.xlabel("Value")
plt.ylabel("Density")

k=8 时逐通道量化后的色彩强度分布。
更好的方法可能是将每种颜色视为 3D RGB 向量,并在该空间中应用聚类。但我会让你决定的!根据上面的代码片段,你应该可以很容易地创建它。
我们已经成功地应用量化来压缩我们的颜色空间,但是也可以在其他域中应用量化,例如频域,这允许更大的压缩。敬请期待下一部分!
可以用 Google Colab 重现本文所有结果:https://Colab . research . Google . com/drive/1 _ touwup 30 e 23 vkpqt 4 ohw kjy _ ddhlqn 9?usp =共享
感谢您阅读这篇文章!如果你喜欢它,请考虑订阅我的更新。如果你有任何问题,欢迎在评论中提出。
基于迁移学习的图像识别算法
原文:https://towardsdatascience.com/image-recognition-algorithm-using-transfer-learning-cae1deb2818f
从头开始训练神经网络需要大量的时间和巨大的计算能力。克服这两个障碍的一个方法是实施迁移学习。

来源:pixabay.com
没有足够的数据、时间或资源是构建高效图像分类网络的一个关键难题。在本文中,我给出了一个简单的实现,可以绕过所有这些资源不足的限制。我们将看到什么是迁移学习,为什么它如此有效,最后,我将一步一步地建立一个图像分类学习模型。
我将开发的模型是羊驼与非羊驼分类器,即能够识别输入图像是否包含羊驼的神经网络。我选择这个有限的任务有几个原因:
- 原来的预训练模型不知道一个“羊驼”类。我想用看不见的课探索迁移学习的潜力。
- 我没有很多羊驼和非羊驼的对比数据。我想用几个数据点来评估迁移学习能做什么。
最后,我将用我最近一次徒步旅行中亲自拍摄的一些羊驼图片来测试该算法。这些照片是在不同的光线条件下拍摄的,羊驼并不总是特写。
代码和笔记本可以在 GitHub 资源库中找到:
https://github.com/andreoniriccardo/transfer-learning-image-classifier
用于第二步训练的数据集将从 Google Open Images V6 数据集中获取。
迁移学习
假设您想要构建一个图像分类器,但您负担不起几周的学习模型训练费用,也没有顶级的 GPU 来完成这项任务。你可以下载一个现有的模型并对其进行更多的训练,而不是从头开始开发一个神经网络。这种技术被称为迁移学习:它包括使用一个完善的神经网络(或只是它的一部分),并使它适合于您特定的计算机视觉项目。
多年来,学术研究人员和公司开发了非常深度的卷积神经网络,在图像识别任务中实现了最先进的精度水平。这些网络有几十或几百层深,并在数百万张图像上(通常在 ImageNet 数据库上)进行了长时间的训练。开源预训练网络的例子有 ResNet-50 、 Inceptionv3 、 MobileNetV2、等等。

Inception v3 网络的架构示意图。资料来源:researchgate.net。
在卷积神经网络(CNN)中,第一个卷积层检测简单的特征,如边缘或形状,中间层识别物体的部分(如人脸识别中的眼睛或嘴巴),最后,最终的卷积层可以识别更复杂的特征,如人脸。由于这个原因,CNN 的初始层完成更一般的任务。相反,最后几层更加专门化。卷积神经网络的这一特殊特征允许采用现有的预训练网络,冻结除最后几层之外的所有层的参数(权重和偏差),并训练网络几个额外的时期。因此,我们可以利用在巨大数据集上训练的深度网络,同时,使其专门用于更具体的图像识别项目。根据卷积层对其原始任务的专门化程度,我们可以选择冻结网络中更大或更小的部分。
迁移学习在开发不同数据可用性条件下的计算机视觉算法中起着重要的作用。如果我只有一些数据来训练网络,我会冻结除输出层之外的所有预训练网络的权重:只有 softmax 层会用新实例重新训练。另一种情况是,如果我有更大的训练集可用。在这种情况下,我会冻结更少的层,并重新训练更多的层。最后,如果我可以为网络提供大量的训练集,我会使用预先训练的权重作为网络的初始化点。通过这种方式,我可以加速收敛。
创建张量流数据集
导入所需的库后,下一步是生成两个 TensorFlow 数据集,一个用于训练,一个用于验证。20%的图像用于验证。测试集和验证集合并成 32 个批次。
请查看 Jupyter 笔记本,了解如何从 Google Open Images 下载图片。
为了生成数据集,我使用了[**image_dataset_from_directory**](https://www.tensorflow.org/api_docs/python/tf/keras/utils/image_dataset_from_directory)函数。我提供了包含每个类的子目录的目录的路径。我的情况是“羊驼”和“非 _ 羊驼”。设置了**validation_split**参数后,我必须指定哪一组用于训练,哪一组用于验证。最后,我设置了一个种子来避免两个数据集之间的重叠。
TensorFlow API 的一个很大的优点是它自动从子文件夹名称中读取类标签。通过对数据集对象应用**claa_names**属性,我们可以看到:

我们可以通过打印一些来看看训练图像是什么样的。羊驼实例有不同的姿势和大小,非羊驼实例主要是动物形状玩具上的动物。

一些训练例子的预览。来源:作者。
导入预先训练的模型
TensorFlow API 允许轻松导入预先训练的模型。对于这个应用程序,我将使用 MobileNetV2 网络,因为它的架构是基于剩余连接的,这导致了一个快速的网络,也可以在智能手机等低计算设备上使用。
首先要做的是通过调用**tf.keras.applications.MobileNetV2**函数导入网络:
它需要提供:
- 输入形状,它是通过将颜色的尺寸添加到图像形状中获得的
- 是否包含最终图层。在这种情况下,我不会导入最终层,因为我想为我的特定任务训练一个全新的输出层
- 是否导入预训练的重量。在这种情况下,我导入了在 Imagenet 数据集上训练得到的权重
通过打印网络总结,我们可以看到它的样子:

上面的图像只描述了前 4 层,因为整个网络(156 层,不包括最后一层)不适合一个图像。你可以在我上传到 GitHub 库的 Jupyter 笔记本上看到所有图层的描述。
修改网络并训练模型
如上所述,我将向网络添加一个特定的输出层,该层将从头开始训练。
我现在将解释代码的每一行。
- MobileNetV2 是在归一化范围[-1,1]上预先训练的。出于这个原因,我复制了相同的输入规范化层
- 预训练模型的权重被设置为不可训练
- 定义形状的输入层(160,160,3)
- 应用输入标准化步骤
- 添加预训练模型
- 应用一个平均池层来减少复杂图像的尺寸
- 添加一个下降层,以应用一些正则化(从而减少过度拟合)
- 添加输出层,该层由具有 Sigmoid 激活功能的单个单元组成。对于二元分类问题,单个单元就足够了
- 最后,通过指定输入和输出来组合模型
一旦定义了模型,就该编译和训练它了。我使用亚当优化器和二元交叉熵作为损失函数。作为评估标准,我使用准确性。

无数据扩充的学习曲线。来源:作者。
准确度分数有机地上升到大约 95%的平台期。训练和验证准确性是成对的,这意味着算法不会过度适应训练数据。
我想在我徒步旅行时拍摄的一批羊驼图像上测试该算法。我在测试集中添加了一些随机的非羊驼图片(比如金鱼或巧克力蛋糕),只是为了解决最终的假阳性错误。鉴于测试图像的数量有限,我使用这个简单的片段进行测试:
没有假阳性的报道,但是,我的一些羊驼图片被贴错了标签:

错误分类的图像。来源:作者。
左边的图片实际上与训练示例非常不同:动物在背景中,并且部分被栅栏覆盖。然而,右边的图像被错误地标注了,即使动物清晰可见并处于焦点上。
我将尝试通过添加一些数据增强层来使神经网络更加健壮。
数据扩充
我将跳过关于什么是数据增强及其优势的解释。对于所有细节和实际应用,我建议阅读这篇关于数据扩充的文章。
为了实现数据扩充,我添加了网络的一个连续部分,它由两层组成:一层随机水平翻转图像,一层执行图像的随机旋转。
在对增强模型进行 20 个时期的训练,并在验证集上达到 97%的准确性之后,上面的两张照片都被正确地标记为羊驼。
结论
迁移学习的可能性数不胜数。在本文中,我介绍了如何利用开源的预训练网络来轻松构建图像分类 CNN。我在验证集上达到了令人满意的准确度,但是通过一些安排,它还可以改进得更多。一些改进可能是添加更密集的层(具有 ReLu 激活功能),执行更多的增强步骤(剪切、镜像、缩放),以及重新训练原始 MobileNetV2 网络的更多最终层。
使用 Keras 模型的图像分割、UNet 和深度监督损失
用于分割的深度 CNN 经常遭受消失梯度。我们能通过计算不同输出水平的损耗来解决这个问题吗?
图像分割需要将图像像素分成不同的类别。一些应用包括识别医学图像中的肿瘤区域,在无人机图像中分离陆地和水域等。与 CNN 输出类别概率得分向量的分类不同,分割需要 CNN 输出图像。

网球运动员的图像分割(来源 - 知识共享署名 4.0 许可
因此,传统的 CNN 架构被调整以产生期望的结果。包括变压器在内的一系列架构可用于分割图像。但除了改进网络设计,研究人员还在不断尝试其他方法来提高分段性能。最近,我偶然看到一部作品,它简要描述了在多个输出级别计算损耗(深度监督损耗)的想法。在这个帖子里,我也想分享一下。
我们将实现一个类似于 UNet 的模型,这是一个常用的分段架构,并使用 Keras 模型子类训练它的监督损失。代码可以参考附带的 Github / Kaggle 链接。我假设你熟悉 Keras 的基础知识。由于我们有很多内容要介绍,我将链接所有资源并跳过一些事情,如骰子损失,使用 model.fit 、图像生成器等的 keras 训练。
我们先从了解图像分割开始。
1.图象分割法
通俗地说,分割就是像素分类。如果一幅图像有一只猫和一只狗,我们希望机器识别猫和狗的像素,并在输出中将它们标记为 1(猫)或 2(狗)。每隔一个像素(背景、噪声等)为 0。为了训练这样的模型,我们使用成对的图像和遮罩。

来源 ( 知识共享署名 4.0 许可)
假设您想在 MRI 扫描中识别脑瘤。您将首先创建一组阳性(肿瘤)和阴性(非肿瘤)图像的训练集。然后,您将为每个对象创建相应的遮罩。怎么做到的?进行 MRI 扫描,定位肿瘤区域,将该区域中的所有像素值转换为 0,并将所有其他像素设置为 1。自然地,非肿瘤口罩将是全黑的。在这些对(输入=MRI,输出=掩模)上训练的模型将在 MRI 扫描中识别肿瘤。有用,不是吗?

来源——来自我建立的一个肿瘤识别模型
现在,让我们深入研究分割图像所需的神经网络架构。
2.Unet
传统上,CNN 善于识别图像中的内容。对于分割,CNN 还需要学习精确定位图像成分。UNet 有能力做到这一点。最初的 UNet 论文将其描述为一个分为两部分的网络——收缩(编码器)和扩张(解码器)。让我们从编码器部分开始(注意,我对 UNet 论文中提出的架构做了一些小的修改)。
# Important Libraries to import
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os
import cv2
import tensorflow as tf
from tensorflow.keras import Input
from tensorflow.keras.models import Model, load_model, save_model
from tensorflow.keras.layers import Input, Activation, BatchNormalization, Lambda, Conv2D, Conv2DTranspose,MaxPooling2D, concatenate,UpSampling2D,Dropout
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint,ReduceLROnPlateau
from tensorflow.keras import backend as K
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from sklearn.model_selection import KFold
from tensorflow.keras.losses import BinaryCrossentropy
import random
2.1 编码器
编码器的功能类似于普通的 CNN。它不断地分解输入,以辨别与图像中的对象相关联的特征。该过程在多个块中重复进行(编码器块)。每个块由以下部分组成:
- 具有填充和(3,3)内核的两个卷积层相继出现(我们称之为卷积块)。必要时还可以包括批量标准化/删除层[原始文件使用无填充卷积]。我们将使用 relu 作为激活函数。
- 跨距为 2 的最大池层,用于压缩图像
# Functions to build the encoder path
def conv_block(inp, filters, padding='same', activation='relu'):
*"""
Convolution block of a UNet encoder
"""* x = Conv2D(filters, (3, 3), padding=padding, activation=activation)(inp)
x = Conv2D(filters, (3, 3), padding=padding)(x)
x = BatchNormalization(axis=3)(x)
x = Activation(activation)(x)
return x
def encoder_block(inp, filters, padding='same', pool_stride=2,
activation='relu'):
*"""
Encoder block of a UNet passes the result from the convolution block
above to a max pooling layer
"""* x = conv_block(inp, filters, padding, activation)
p = MaxPooling2D(pool_size=(2, 2), strides=pool_stride)(x)
return x, p
如果您注意到,encoder_block 返回两个值-max-pooling 前后的图像。UNet 将后者(p)传递给下一个块,并将前者(x)存储在内存中。为什么?我们稍后将对此进行阐述。

以上是在 UNet 论文中描述的第一个编码器模块的副本。它包括两个卷积层,顺序应用 64 个滤波器,随后是最大池层(由向下的绿色箭头表示)。我们将用上面讨论的修改来复制它。对于像 UNet 这样不包含密集(平坦)图层的网络,输入形状规范不是强制性的,但我们将输入形状定义为(256,256,1)。
# Building the first block
inputs = Input((256,256,1))
d1,p1 = encoder_block(inputs,64)
第一个块之后是三个类似的块,具有滤波器= 128,256,512。这四个模块一起构成收缩路径/编码器。
#Building the other four blocks
d2,p2 = encoder_block(p1,128)
d3,p3 = encoder_block(p2,256)
d4,p4 = encoder_block(p3,512)
1.2 中部
是的,我们讨论过网络有两个部分,但我喜欢分开处理中间部分。

它从先前的编码器模块中获取最大汇集输出,并使其通过 1024 个滤波器的两个连续(3,3)卷积。就像卷积块一样,你问?是的,只是这一次,输出没有通过最大池。因此,我们将使用 conv 块,而不是编码器块,来创建中间部分。
# Middle convolution block (no max pooling)
mid = conv_block(p4,1024) #Midsection
这个最终输出现在将被上采样。
1.3 解码器
UNet 已经将图像塑造成许多特征地图,对输入图像中的内容有了一个公平的想法。它知道图像包含的不同类别(对象)。现在,它需要预测所有这些类的正确位置,并在最终输出中相应地标记它们的像素。为此,UNet 利用了两个关键理念——跳过连接和上采样。
1.3.1 置换卷积
在遍历编码器并通过中间块之后,输入转换为 shape (16,16,1024)[您可以使用 keras 的 model.summary( ) api 检查这一点]。UNet 然后应用转置卷积对输出进行上采样。那么什么是转置卷积呢?看看,如果下图回答了你的问题。

转置卷积
本质上,我们将输入中的每个条目乘以内核权重,并将所有(2,2)输出缝合在一起,得到最终输出。在共同指数中,这些数字相加。像卷积核一样,转置卷积核中的权重也是可学习的。网络在反向传播期间学习它们,以精确地对特征图进行上采样。参考这篇文章以了解转置卷积的更多信息。
跳过连接
对于每个编码器模块,UNet 还有一个共轭解码器模块。我们已经讨论过,解码器模块学习对图像进行上采样。为了增强他们的学习能力,并确保像素在最终输出中的位置正确,解码器会向相应的编码器寻求帮助。他们以跳过连接的形式利用这一点。
跳过连接是 UNet 的本质。如果您以前使用过 resnets,您应该对这个概念很熟悉。在 1.1 中,我们讨论了 UNet 将卷积模块的输出(x)存储在内存中。这些输出与来自每个解码器模块的上采样图像连接在一起。下图中的水平箭头表示跳过连接。

研究人员认为,随着输入图像在网络中滚动得越来越深,更精细的细节,如图像中不同对象/类别的位置,会丢失。跳过连接从初始层转移放错位置的信息,使 UNet 能够创建更好的分段图。
然后,连接的上采样图像流入卷积块(2 个连续的卷积层)。因此,我们使用以下函数来创建解码器模块。
# Functions to build the decoder block
def decoder_block(inp,filters,concat_layer,padding='same'):
#Upsample the feature maps
x=Conv2DTranspose(filters,(2,2),strides=(2,2),padding=padding)(inp)
x=concatenate([x,concat_layer])#Concatenation/Skip conncetion with conjuagte encoder
x=conv_block(x,filters)#Passed into the convolution block above
return x
1.4 最终的联合国电子信息网网络
下面是我们最后的 UNet 网络。e5 之后的输出具有形状(256,256,64)。为了将其与输入(256,256,1)匹配,我们将使用一个带有 1 个过滤器的(1,1)卷积层。如果你对 1,1 卷积感兴趣,一定要看这个吴恩达的视频。
# Bulding the Unet model using the above functions
inputs=Input((256,256,1))
d1,p1=encoder_block(inputs,64)
d2,p2=encoder_block(p1,128)
d3,p3=encoder_block(p2,256)
d4,p4=encoder_block(p3,512)
mid=conv_block(p4,1024) #Midsection
e2=decoder_block(mid,512,d4) #Conjugate of encoder 4
e3=decoder_block(e2,256,d3) #Conjugate of encoder 3
e4=decoder_block(e3,128,d2) #Conjugate of encoder 2
e5=decoder_block(e4,64,d1) #Conjugate of encoder 1
outputs = Conv2D(1, (1,1),activation=None)(e5) #Final Output
ml=Model(inputs=[inputs],outputs=[outputs,o1],name='Unet')
2.图像分割和深度监控
好了,该实施我们所学的内容了。
我们将在这个新冠肺炎胸部 x 光 ( 主数据集)数据库上执行图像分割。它包括四个图像类别— Covid、正常、肺部阴影和病毒性肺炎。通过这篇文章,我只想分享如何使用监督损失和 Keras 模型子类来分割图像。性能在这里不是一个因素。
因此,我将构建一个简单的问题。我们将从 Covid 类获取图像,并将它们的像素分割成肺和非肺。(注意,我们并不试图训练模型来识别 covid 受影响的区域,而是绘制肺部所占据的空间)。然而,典型地,医学成像涉及极其复杂的情况,如发现肿瘤影响的器官等。虽然我们不会在这里讨论它们,但是您可以使用/修改所附的代码来实现这些引人注目的应用程序。
我们使用以下代码块从目录中检索图像/遮罩路径
# Block to read image paths, will be used in image data generator
df = pd.DataFrame(columns=['img_path','msk_path','img_shape','msk_shape','class'])
for cat in ['COVID']:
dir_ = f"../input/covid19-radiography-database/COVID-19_Radiography_Dataset/{cat}"
for f in os.listdir(f"{dir_}/images"):
s1 = cv2.imread(f"{dir_}/images/{f}",config.img_type_num).shape
s2 = cv2.imread(f"{dir_}/masks/{f}",config.msk_type_num).shape
dic={'img_path':f"{dir_}/images/{f}",'msk_path':f"{dir_}/masks/{f}",'img_shape':s1,
'msk_shape':s2}
df = df.append(dic,ignore_index=True)
以下是数据集中的一些图像及其对应的遮罩。如上所述,掩码有两类:
0:肺
1:非肺

作者图片
2.1 功能缺失和深度监管缺失
训练掩码只有两个值,0 和 1。因此,我们可以使用二元交叉熵来计算它们和我们最终输出之间的损耗。现在,让我们解决房间里的大象——监督缺失。
深度神经结构的一个问题是梯度损失。一些架构如此之深,以至于梯度在反向传播到初始层时消失,导致初始层中最小的权重偏移,从而导致学习不足。此外,这使得模型不成比例地依赖于更深层的性能。
为了提高梯度流,这篇论文建议计算不同解码器级别的损耗。你会问,具体是怎样的?首先,如下所示,我们将从网络中提取一个额外的输出‘O1’。我们将从倒数第二个解码器(e4)获得形状为(128,128,128)的结果,并使用(1,1)卷积滤波器将其缩小到(128,128,1)。
# Adding output from 2nd last decoder block
inputs=Input((256,256,1))
d1,p1=encoder_block(inputs,64)
d2,p2=encoder_block(p1,128)
d3,p3=encoder_block(p2,256)
d4,p4=encoder_block(p3,512)
mid=conv_block(p4,1024) #Midsection
e2=decoder_block(mid,512,d4) #Conjugate of encoder 4
e3=decoder_block(e2,256,d3) #Conjugate of encoder 3
e4=decoder_block(e3,128,d2) #Conjugate of encoder 2
o1 = Conv2D(1,(1,1),activation=None)(e4) # Output from 2nd last decoder
e5=decoder_block(e4,64,d1) #Conjugate of encoder 1
outputs = Conv2D(1, (1,1),activation=None)(e5) #Final Output
然后,我们将 o1 作为模型输出添加到 Keras 的模型 API 的输出列表中。
# Adding output to output list in keras model API
ml=Model(inputs=[inputs],outputs=[outputs,o1],name='Unet')
接下来,为了计算这个级别的损失,我们还需要将输入的副本调整为(128,128,1)。现在,最后的损失将是:

我们也可以对这两个损失进行加权组合。计算不同层的损耗也使它们能够更好地逼近最终输出。
2.2 使用 Keras 模型子类进行培训
好的,现在剩下的就是训练了。虽然从技术上讲,您可以在著名的“ml.fit”中传递输入列表和调整大小的输入,但我更喜欢使用 keras model 子类。它允许我们更多地使用损失函数。
我们创建一个继承 tf.keras.Model 的类网络。在初始化类网络的对象时,我们将传递要使用的模型(ml)、损失函数(二进制交叉熵)、度量(骰子损失)和损失权重(对两个解码器级别的损失进行加权)。
# Defining network class which inherits keras model class
class network(tf.keras.Model):
def __init__(self,model,loss,metric,loss_weights):
super().__init__()
self.loss = loss
self.metric = metric
self.model = model
self.loss_weights = loss_weights
然后,我们将使用函数 train_step 覆盖“ml.fit”中发生的情况。输入图像通过网络的整个流程和损失计算都是在' tf 范围内完成的。GradientTape' ,仅用两条线表示梯度。
# Overriding model.fit using def train_step
def call(self,inputs,training):
out = self.model(inputs)
if training==True:
return out
else:
if type(out) == list:
return out[0]
else:
return out
def calc_supervision_loss(self,y_true,y_preds):
loss = 0
for i,pred in enumerate(y_preds):
y_resized = tf.image.resize(y_true,[*pred.shape[1:3]])
loss+= self.loss_weights[i+1] * self.loss(y_resized,pred)
return loss
def train_step(self,data):
x,y = data
with tf.GradientTape() as tape:
y_preds = self(x,training=True)
if type(y_preds) == list:
loss = self.loss_weights[0] * self.loss(y,y_preds[0])
acc = self.metric(y,y_preds[0])
loss += self.calc_supervision_loss(y,y_preds[1:])
else:
loss = self.loss(y,y_preds)
acc = self.metric(y,y_preds)
trainable_vars = self.trainable_variables #Network trainable parameters
gradients = tape.gradient(loss, trainable_vars) #Calculating gradients
#Applying gradients to optimizer
self.optimizer.apply_gradients(zip(gradients, trainable_vars))
return loss,acc
当我们操作监督损失时,网络返回列表中的输出,我们调用函数 calc_supervision_loss 来计算最终损失。
类似地,我们可以覆盖验证步骤
# Overriding validation step
def test_step(self,data):
x,y=data
y_pred = self(x,training=False)
loss = self.loss(y,y_pred)
acc = self.metric(y,y_pred)
return loss,acc
从现在开始,一切都是惯例。我们将使用 Keras ImageDataGenerator 来传递用于训练的图像-遮罩对。
# Keras Image data generator
def img_dataset(df_inp,path_img,path_mask,batch):
img_gen=ImageDataGenerator(rescale=1./255.)
df_img = img_gen.flow_from_dataframe(dataframe=df_inp,
x_col=path_img,
class_mode=None,
batch_size=batch,
color_mode=config.img_mode,
seed=config.seed,
target_size=config.img_size)
df_mask=img_gen.flow_from_dataframe(dataframe=df_inp,
x_col=path_mask,
class_mode=None,
batch_size=batch,
color_mode=config.msk_mode,
seed=config.seed,
target_size=config.img_size)
data_gen = zip(df_img,df_mask)
return data_gen
接下来,我们创建训练集和验证集,设置优化器,实例化我们上面创建的网络类并编译它。(正如我们从 keras 继承类网络一样,我们可以使用。直接编译功能)
train=img_dataset(train_ds,'img_path','msk_path',config.batch_size)
val=img_dataset(val_ds,'img_path','msk_path',config.batch_size)
opt = Adam(learning_rate=config.lr, epsilon=None, amsgrad=False,beta_1=0.9,beta_2=0.99)
model = network(ml,BinaryCrossentropy(),dice_coef,[1,0.5])
model.compile(optimizer=opt,loss=BinaryCrossentropy(),metrics=[dice_coef])
进入训练循环
# Custom training loop
best_val = np.inf
for epoch in range(config.train_epochs):
epoch_train_loss = 0.0
epoch_train_acc=0.0
epoch_val_acc=0.0
epoch_val_loss=0.0
num_batches = 0
for x in train:
if num_batches > (len(train_ds)//config.batch_size):
break
a,b = model.train_step(x)
epoch_train_loss+=a
epoch_train_acc+=b
num_batches+=1
epoch_train_loss = epoch_train_loss/num_batches
epoch_train_acc = epoch_train_acc/num_batches
num_batches_v=0
for x in val:
if num_batches_v > (len(val_ds)//config.batch_size):
break
a,b = model.test_step(x)
epoch_val_loss+=a
epoch_val_acc+=b
num_batches_v+=1
epoch_val_loss=epoch_val_loss/num_batches_v
if epoch_val_loss < best_val:
best_val = epoch_val_loss
print('---Validation Loss improved,saving model---')
model.model.save('./weights',save_format='tf')
epoch_val_acc=epoch_val_acc/num_batches_v
template = ("Epoch: {}, TrainLoss: {}, TainAcc: {}, ValLoss: {}, ValAcc {}")
print(template.format(epoch,epoch_train_loss,epoch_train_acc,
epoch_val_loss,epoch_val_acc))
2.3 结果

预测(图片由作者提供)
预测的掩模相当准确。该模型的验证骰子得分为 0.96,验证损失为 0.55。然而,正如我们所讨论的,我们不应该过多地解读这些值,因为手头的问题是粗糙的。目的是展示如何使用监督损失。在上面提到的论文中,作者使用三个解码器的输出来计算最终损耗。
谢谢你一直读到最后。我希望这能使你在将来使用监督损失。如果你喜欢这份工作,一定要检查我的 Github。
参考资料:
https://arxiv.org/abs/1505.04597
https://arxiv.org/abs/2110.03352v2 https://www.jeremyjordan.me/semantic-segmentation/ https://github.com/shashank14k/Medical-Imaging/tree/main/BrainMRI_Image Segmentation https://ieeexplore.ieee.org/document/9144185
https://linking hub . Elsevier . com/retrieve/pii/s 001048252100113 x
如何利用深度学习实现图像相似性
原文:https://towardsdatascience.com/image-similarity-with-deep-learning-c17d83068f59
我们可以使用机器学习在适当的上下文中返回有意义的相似图像、文本或音频。简单快捷。
想象一下,实现一种算法来直观地比较不同的 t 恤以找到匹配的 t 恤,需要付出多少编程上的努力。用传统的编程方法归档好的结果是一项挑战。
通过深度学习,我们可以轻松地将其扩展到无限数量的不同用例。我们通过使用深度学习模型的已学习视觉表示来做到这一点。
本文介绍了一种简单快速的方法来实现图像相似性搜索。
为了简单起见,我们使用图像,但是同样的方法也可以推广到任何可以用向量表示的东西。

作者图片——基于深度学习的图像相似度结果,来源:【https://ioannotator.com/image-similarity-search
直接跳到笔记本上编码
如果你想运行本文中的代码,可以去 Google Colab 笔记本。
您需要在笔记本中上传并引用自己的图片。
https://colab.research.google.com/drive/1fgFMR-9dpx13CKZm_uYRwbji79PT83Pu?usp=sharing
用例
在我们深入挖掘技术细节之前,让我们从几个用例开始启发您。用例是无穷无尽的,您可以在许多不同的领域使用图像相似性。
我在过去几年中参与的一些用例:
电子商务
电子商务有许多相似性搜索的用例。最明显的一个,根据实际的产品图片找到相似的产品。还有一些不太明显的问题,比如通过防止上传同一产品的多个图片来保持产品目录的整洁。
数字文献档案
想象一下,一个拥有数百万扫描文档的大型非结构化数字归档。放置正确的标签来查找相似的文档是非常耗时的。深度学习方法消除了在图像上贴标签的需要。
机器学习
高质量的训练数据是成功的机器学习项目的关键。训练数据中有重复项会导致不好的结果。图像相似性可用于在数据集中查找重复项。
图像的视觉表示
当使用深度学习模型时,我们通常使用模型的最后一层,即输出层。这一层为我们提供了图像的类。图像上这是猫、车、桌子、狗,还是老鼠?
我们可以移除这个分类层,并获得为输入图像生成的模型的视觉表示。该表示是介于 0 和 1 之间的浮点数的向量。

作者照片—分类与特征向量
让我们概括这种方法,并将其与我们人类感知世界的方式进行比较。我们从小就知道独特的细节,比如汽车大部分是方形的,有轮胎、灯和门。我们向量的数字可能代表相似的细节。深度学习模型在训练过程中学习了这些信息。
模型学到的是“多半是”一个黑箱。最近的研究帮助我们更好地理解这一点。如果你对特定的主题感兴趣,我推荐你多读一些关于可解释的人工智能的书。
为了得到这样一个向量(也称为嵌入),我们只需要几行代码和一个预先训练好的模型。
预训练模型
我们可以利用许多预先训练的深度学习模型中的一个。它们概括得足够好,适合大多数用例。根据您的具体使用情况对预训练模型进行微调是有意义的。但是在你投入精力之前,我建议不要微调。
TensorFlow Hub 可以轻松重用已经预先训练好的图像特征和矢量模型。
我们使用 TensorFlow Keras 加载模型。输入形状定义了模型被训练的图像大小。这意味着模型学会了寻找特定大小的图案,我们通过遵循推荐的图像大小来做正确的事情。
如果你改变模型的网址,请记住不同的模型期望不同的图像形状。
把...嵌入
为了得到嵌入,我们输入一幅图像,并让模型进行预测。图像本身需要一些预处理
- 调整到模型训练时的大小
- 将图像转换成每个像素的颜色表示
- 将 0 和 1 之间的值标准化
有了这几行代码,我们就可以得到 0 到 1 之间浮点数的可视化表示、向量。向量的长度因型号而异。在我们的例子中,使用的模型具有 1280 个数字的向量长度。
我们可以用这种视觉表现来计算图像有多相似。
如何度量图像相似度
为了计算图像的相似性,我们需要一个度量。为了简单起见,我们只涵盖最常见的euclidean、cosine和dot。
为了避免不必要的数学,我试图尽可能实际地描述它。
想象一个有 3 个轴x、y,和z的坐标系,并假设我们的特征向量有 3 个元素的长度,而不是 1280。
让我们也想象我们有在该坐标系中表示的 3 幅图像的以下特征向量。
- 卡特彼勒
[0.1, 0.1, 0.2](蓝点) - 猫
[0.2, 0.12, 0.2](橙点) - 工作台
[0.8, 0.6, 0.6](绿点)

作者照片
euclidean距离顾名思义就是测量点与点之间的距离。我们可以清楚地看到,与桌子(绿色)相比,猫(橙色和蓝色)之间的距离更小。
虽然这种方法适用于这种特殊情况,但可能并不总是适用。让我们添加两个额外的向量。
- 鼠标
[0.8, 0.6, 0.6](红点) - 猫
[0.2, 0.16, 0.2](紫点)
我们可以看到老鼠(红色)靠近猫(紫色),如果我们测量欧几里德距离,事实上,它甚至比另一只猫(蓝色)更近。我们需要另一个因素来准确衡量相似性。我们可以用坐标系内点的方向。

作者照片
如果我们画几条线来可视化方向,它会变得更清楚,这样我们可以看到相似的图像共享相同的方向。与euclidean相比,dot度量使用距离和方向来计算相似性。

作者照片
最后一个度量是cosine相似度,它只考虑方向,而不考虑两点之间的距离。您需要选择哪一个取决于您的具体用例。如果你不确定,我建议从cosine开始评估所有 3 个。
我们刚刚提到的例子是一种简化,我们从模型中得到的特征向量具有更高的维数。
为了计算相似性值,我们不需要自己实现数学运算。许多图书馆为我们提供了这种方法,随时可以使用。
对于这个例子,我们使用 SciPy 但是你也可以使用 sklearn 来仅仅提到两个。
值越小,图像越相似。你可以看到猫彼此非常接近,数值为 0.345。而猫与火箭相比具有更高的 0.731 和 0.690 的数值,这意味着更低的相似性。

作者照片
如何将它放大到数百万张图片
如果我们每次想要找到一个相似的图像时都需要处理所有的图像,那么迭代数百万个图像来找到最相似的图像并不能很好地扩展。为了优化这个过程,我们需要建立某种索引,并找到一种更有效地迭代它的方法。幸运的是,Spotify、谷歌和脸书针对这个问题开源了他们的解决方案。但是,如果您不想自己负责所需的基础架构和扩展,也可以选择托管服务。
- 像脸书的 Faiss 和 Spotify 这样的开源框架惹恼了,谷歌的 ScaNN
- 类似 Google 顶点 AI 匹配引擎、 IO 相似度搜索、松果、 Milvus 等服务
接下来呢?
本文涵盖的方法可以很容易地应用于其他机器学习领域,如文本、视频、音频,甚至表格数据。输出特征向量的每个模型都是合适的。
如果您有兴趣深入了解某个开源框架或托管服务,请告诉我。
我希望这篇文章能启发您思考您特定领域中的用例。如果你打算将相似性搜索投入生产,我有一篇谷歌顶点人工智能匹配引擎的文章和视频。
https://medium.com/google-cloud/all-you-need-to-know-about-google-vertex-ai-matching-engine-3344e85ad565 https://medium.com/google-cloud/real-time-deep-learning-vector-similarity-search-8d791821f3ad
感谢阅读
非常感谢您的反馈和问题。你可以在 Twitter@ HeyerSascha找到我或者通过 LinkedIn 与我联系。更好的是,订阅我的YouTube频道 ❤️ 。
图像超分辨率:研究现状综述
对流行技术和剩余挑战的回顾

两幅图像,其中左半部分对应于低分辨率图像,右半部分描绘高分辨率图像。超分辨率方法的目的是改善低分辨率图像,使其尽可能接近高分辨率图像。由拉斯·博·尼尔森(左)和索拉萨克(右)在 Unsplash 上拍摄的原始图像
在之前的一篇文章中,给出了超分辨率(SR)的概述以及它为什么会成为一个重要的研究课题。概括地说,它是对低分辨率(LR)图像进行上采样以恢复基础高质量图像的过程。除了分辨率,SR 方法通常需要考虑其他因素,如模糊、传感器噪声和压缩,这些因素也可能降低图像质量。
存在许多解决 SR 的方法,这些方法可以大致分为“非盲”,其中假定影响图像的退化是已知的,或者“盲”,其中影响图像的确切退化是未知的。
无论采用哪种方法,它们都必须定义一个退化模型,用于确定要使用的 LR 图像的类型。鉴于大多数现代方法都是基于深度学习的,因此已经对要使用的损失函数和评估指标进行了大量研究,这些损失函数和评估指标会显著影响最终结果的外观和质量。
我们将更详细地讨论所有这些,并概述软件无线电领域最流行和最先进的方法。
概括来说,本文将涉及的主题如下:
退化模型
任何 SR 算法的关键组成部分之一实际上与方法本身无关,而是与所使用的数据有关。具体来说,应用了一个退化模型,以便制定要使用哪种数据。
大多数方法倾向于基于监督学习的方法,需要使用地面真实信息(在 SR 的情况下对应于原始 HR 图像),以便算法知道当超解析给定的 LR 图像时最终结果应该是什么样子。因此,这种退化模型被直接应用于 HR 图像以产生低分辨率图像。典型地,诸如所考虑的退化幅度的参数被改变以产生多个 LR 图像。
“经典”退化模型是最常用的,它考虑了下采样、模糊和噪声:

其中⊗表示卷积运算, k 是核(通常是高斯模糊核,但是它也可以表示诸如点扩展函数(PSF)之类的其他函数), n 表示加性噪声,并且↓ s 是缩减操作,其通常被假定为具有比例因子 s 的双三次下采样。
该模型被批评为过于简单,利用它的方法不能很好地概括更复杂的退化,这种退化通常在现实世界的图像中发现。因此,最近的努力试图设计更真实的退化模型,例如通过包括由图像中非常普遍的压缩引起的伪像:

其中 C 是 JPEG 等压缩方案。
还提出了其他方案,例如通过不止一次地应用经典退化模型的、随机打乱应用退化的顺序的,以及最近的使用“随机门控制器”,其随机选择要使用哪些基础退化。
SR 的目的是然后反转所考虑的任何退化过程,以恢复原始的基本高保真图像。现在将提供执行该任务的流行方法的概述。
非盲随机共振方法
大多数作品倾向于了解使用了哪种退化模型,因此被称为“非盲”方法。这与“盲目”模型相反,盲目模型不能“看见”,因此对可能影响图像的退化一无所知。虽然非盲方法不太现实,但它们已经形成了包括盲 SR 方法在内的更复杂方法的基础。
超分辨率卷积神经网络(SRCNN) [1,2]被认为是使用深度学习和卷积神经网络完成 SR 任务的开创性工作。它仅由三层组成,要求 LR 图像在被网络处理之前使用双三次插值进行上采样,但它的性能优于当时的最新方法,如 A+ [3]和基于稀疏表示的方法[4]。研究还表明,基于稀疏编码的方法等效于卷积神经网络,这影响了 SRCNN 的超参数设置。
虽然 SRCNN 已被许多研究人员用作基准,但它现在已被广泛超越,不再经常用于比较目的。然而,由于其相对简单,它可以作为任何对基于深度学习的人工智能领域感兴趣的人的良好起点。

SRCNN 方法的架构[1,2]
残差网络(ResNet)架构[5,6]主要用于随着层数的增加而简化网络的训练。事实上,虽然文献中的许多作品都表明,更深的网络提供了更好的性能,但准确性会达到饱和,然后迅速下降。研究表明,这不是由于过拟合,而是由于优化和训练非常深的网络的困难。特别是,有人指出,深层网络可能会发现很难学习身份功能。
为了解决这个问题,许多跳过/快捷连接被用来将网络的任何给定级别的特征地图直接馈送到更高层,因此对应于身份功能。这样,网络将只需要学习抑制中间层的响应(将输出驱动到零)的函数。
在[7]中,ResNet 被应用于 SR 域以创建 SRResNet,其中它也被用作基于生成对抗网络(GAN)的网络(称为 SRGAN)的基础。

ResNet 中剩余学习的构建模块[5,6]

SRGAN 的生成器(基于 SRResNet)和鉴别器网络的架构,其中 k 表示内核大小, n 是特征映射的数量, s 是步距[7]
增强型深度超分辨率(EDSR)方法[8]也是基于 ResNet,并结合了 SRResNet 等先前工作中报告的结论。所提出的方法的贡献之一是去除了批标准化层,以禁用特征值的限制并减少训练期间的内存使用,从而允许使用更多的层和过滤器。残差缩放还用于确保网络的稳定性(尤其是在使用大量要素时)。
还设计了称为多尺度深度超分辨率(MDSR)的多尺度网络,其本质上包括用于三个不同上采样因子(2,3,4)的公共网络,以及在预处理阶段的尺度特定模块和在由卷积和“洗牌”层组成的网络末端的上采样模块。因此,所提出的方法不需要像在一些其他工作中(例如 SRCNN)那样对输入图像进行双三次插值。提出的方法被证明超过了 NTIRE 2017 竞赛中评估的方法的性能[9]。

EDSR 建筑[8]

ResNet(左)、SRResNet(中)和 EDSR(右)中使用的残差块之间的差异[8]
剩余通道注意力网络(RCAN)是在[10]中提出的,以抵消训练非常深的 CNN 的困难和跨通道的低频信息的同等重要性的分配。因此,RCAN 由“剩余组”组成,每个“剩余组”包含多个“剩余频道注意块”。“长跳跃连接”与残差组一起使用,使得信息能够从网络的早期阶段传播到最终阶段,而“短跳跃连接”在残差块内使用,用于以更精细的级别传播信息。作者还指出,这种残差中残差的架构能够训练非常深的 CNN(超过 400 层)。
RCAN 的表现优于美国有线电视新闻网(SRCNN)和 EDSR 等方法,并与更现代的方法保持相当的竞争力。它还被证明对于物体识别是有效的,其中当与通过其他方法上采样的图像相比时,由 RCAN 输出的上采样图像能够实现更高的精度。

RCAN 建筑[10]
SRGAN 在[11]中进行了改进,以产生增强的 SRGAN (ESRGAN),改进集中在:(I)网络架构,其中去除了批量归一化(类似于 EDSR ),并且残差中残差密集块(RRDB)被提议作为网络的基本构建块,以实现更高的网络容量并促进训练,(ii)对抗损失,其被修改以确定图像的相对“真实度”(即,如果图像比假数据更真实或者比真实数据更不真实, 而不是简单的真实或虚假),以及(iii)感知损失,通过使用在卷积发生之后(即在激活之前)立即计算的特征来减少特征的稀疏性并更好地监控亮度一致性和纹理恢复。 “网络插值”也使用户能够平衡感知质量和 PSNR 之间的折衷。
与包括 SRCNN [1,2],EDSR [8],RCAN [10]和 SRGAN [7]在内的其他方法相比,结果表明,ESRGAN 产生的图像边缘更清晰,纹理更好,同时减少了不希望的伪影。ESRGAN 还赢得了 PIRM2018-SR 挑战赛[12]。

在 ESRGAN 中,移除了 SRGAN 中存在的批量标准化层,同时使用了新的 RRDB 模块。还采用了剩余比例参数β[11]。
Transformers ,最初提出用于自然语言处理(NLP)领域,是深度学习模型,它使用一种注意机制来决定序列中的哪些部分最重要。它们已经成为一个热门的研究课题,并已被应用于计算机视觉任务,视觉变压器(ViT)被认为是这一领域的开创性工作[13]。最近,在[14]中提出了基于变换器的方法的应用,命名为高效超分辨率变换器(ESRT)。
轻量级 Transformer Backbone (LTB)的任务是捕获图像中跨局部区域的长期空间依赖性。如果图像在不同位置包含相似的补丁,这将特别有用。CNN 也被用作轻量级 CNN 主干(LCB)的一部分,它可以动态调整特征大小以提取深层特征,同时保持低计算成本。实验表明,ESRT 可以在性能和计算复杂度之间取得很好的平衡。


ESRT 的建筑(上)和高保护街区(HPB)(下)。LCB、LTB、HPB 和 ET 分别代表轻量级 CNN 主干、轻量级变压器主干、高保护块和高效变压器。在 HPB 架构中,HFM 和 ARFB 代表高频滤波模块和自适应残差特征块[14]。
盲随机共振方法
虽然上面提到的非盲方法为 SR 的网络开发提供了许多有用的见解,但是它们往往不完全适用于真实世界的场景。这是因为这些方法假定了一个已知的固定退化过程,因此当遇到与它们专门训练的退化不同且更复杂的退化时,往往会失败,这在现实世界中是常见的。此外,影响图像的退化类型通常是未知的。

非盲 SR 方法的失败案例,在模糊输入的情况下无法锐化纹理,在有噪声输入的情况下保持噪声[15]
盲 SR 方法试图通过减少所做的假设来对这个问题更加鲁棒,即使大多数方法仍然对输入降级做一些假设。刘等人[15]的一项调查将开创性的 SRCNN 的第一作者(C. Dong)列为其作者之一,该调查提出了一种基于所用数据类型及其建模方式的分类法。

盲 SR 方法的分类和一些相应的代表性方法,如[15]中所述,其中也揭示了研究缺口。
一组方法通过除了 LR 图像之外还允许输入表示估计退化的其他特征来执行 SR。因此,这些方法的主要任务是如何最好地利用这些附加信息。
用于多重降级的 12 层超分辨率网络(SRMD) [16]就是这样一种方法,它考虑了高斯模糊、噪声和下采样。使用“维度拉伸”方法,其中使用主成分分析(PCA) 将模糊内核矢量化并投影到更小的维度,然后将其与降级图像的噪声级别连接。然后复制该向量中的每个维度(元素)以获得其宽度和高度与输入 LR 图像的宽度和高度相同的矩阵。

SRMD 建筑[16]

SRMD 提出的维度拉伸方法[16]
还提出了扩展现有非盲方法以利用退化信息的技术[17,18]。这种方法的缺点是,它们需要精确的退化信息(这不是一项简单的任务),因为估计输入的任何偏差都会导致内核不匹配,从而对性能有害。

[18]中提出的“元注意力”块,以及在典型的单幅图像超分辨率网络中的放置
为了缓解这些问题,已经提出了在 SR 过程中估计退化核的方法,例如迭代核校正(IKC) [19]。这种方法利用了任何核不匹配都可能导致规则模式的观察结果,实现了对核的估计,并使用校正器网络以迭代方式对其进行校正,以逐步改善超分辨率图像。结合非盲网络(也在[19]中提出),空间特征变换多重降级(SFTMD)网络,据报道实现了优于诸如 SRMD 的方法的性能。

SFTMD(上图)、预测器和校正器网络(下图)的架构[19]
深度交替网络(DAN) [20](也称为 DANv1)及其更新版本 DANv2 [21]通过在单个端到端可训练网络内统一 SR 和内核校正器网络来构建 IKC,这可以使两个网络更有效地相互操作。为了提高核估计的鲁棒性,校正器也被修改为使用以中间 SR 结果为条件的 LR 输入,而不是像在 IKC 中执行的那样,以估计的核为条件来调节超分辨率图像。

丹建筑[20]

DANv2 [21]中的(a)双路径条件块(DPCB)、(b)双路径条件组(DPCG)、(c)恢复器和(d)估计器的架构。‘GAP’表示全局平均池,f_basic 表示基本输入,f_cond 表示条件输入。
面向内核的自适应局部调整网络(KOALAnet) [22]考虑图像内的空间变化特征,并因此尝试执行局部自适应。这使得能够区分由于不期望的效果引起的模糊和出于美学目的故意引起的模糊(例如散景效果)。

KOALAnet 架构[22]
如果 LR 图像包含与训练模型时考虑的退化不同的退化,则上述方法可能仍然表现出较差的性能,假设它们依赖于核估计。因此,另一组方法,如 KernelGAN [23]和“零炮”SR (ZSSR) [24]利用自然图像的内部统计和观察,一些模式在尺度内(即在图像内的不同位置)和跨尺度(即本质上不同的大小)重复。这使得模型可以自我监督,一次适应一幅图像。
然而,有争议的是,在尺度内和跨尺度具有重复出现的斑块的假设可能并不适用于所有图像(例如,那些包含各种各样的内容或者相反地具有大部分同质区域的图像)。

KernelGAN 建筑[23]

在外部数据集上训练的网络(a)和 ZSSR 方法(b)之间的差异,在后者中,网络在图像上训练,以自我超分辨[24]
另一组方法试图隐式地对潜在的退化模型进行建模,以便对真实世界的 LR 图像更加鲁棒,其中 HR 图像不可用并且因此是未知的。循环中循环 GAN (CinCGAN) [25]就是这样一种方法,它由两个循环 GAN [26]网络组成。第一个网络试图对输入图像去噪(以产生“干净的 LR”图像),而第二个网络则学习从 LR 空间到 HR 空间的映射,以及基于 EDSR 的 SR 网络[8]。

CinCGAN 架构,其中 G1-G3 是生成器,D1-D2 是鉴别器,SR 是超分辨率网络[25]
另一种方法 Real-ESRGAN [27]是 ESRGAN [11]的扩展,它使用“高阶”退化过程生成合成图像,其中退化模型实际上应用了两次。这种方法的主要缺点是需要大量的数据。

Real-ESRGAN 架构[27]

用于 Real-ESRGAN 的合成数据生成管道[27]
虽然退化 GAN [17]等方法试图通过学习 HR 到 LR 的退化过程来解决这一问题,从而允许在 SR 模型的训练过程中生成和使用真实的 LR 样本,但大多数为隐式退化建模而设计的模型倾向于使用不易训练的 GAN,并可能引入对取证或旧照片恢复等应用有害的假纹理或伪影。

退化 GAN 的架构和训练管道[17]
一些方法还考虑了多种建模模式和数据源,例如混合专家(MoESR) [28],其中不同的退化内核分别由特定的 SR 网络(称为“专家”)处理。然后,最好的专家被用于内核预测,而图像的内部统计数据则被用于执行微调。

教育和科学研究部各种网络的培训计划[28]
最近重新兴起的一股研究潮流是对比学习,它也被应用于诸如降解感知 SR (DASR)网络【29】和为遥感图像设计的方法【30】等方法中的 SR。对比学习是自我监督学习的一种形式,其中从表现出与“锚”表示相似的特征的图像样本(通常称为“正”样本)获得的特征表示被拉近,而从具有与锚不同的特征的样本(通常称为“负”样本)获得的表示被推开。

DASR 建筑(下)[29]

DASR 使用的无监督退化表示学习方案的示例[29]
损失函数和评估指标
上述所有方法都使用机器学习,其中需要一些方法来确定学习的参数是否朝着正确的方向移动。然后进行任何调整以进一步优化这些参数,并进而产生(希望)更令人满意的结果。这是使用所谓的损失函数来执行的,该函数测量网络在训练阶段输出的结果的质量。
最常用的损失函数直接比较 LR 图像和目标(HR)图像,例如 L2 损失,其形成了均方误差(MSE) 和密切相关的峰值信噪比(PSNR) 的基础。这些所谓的全参考图像质量评估(FR-IQA)度量本质上是在超分辨率的情况下测量 HR 和 LR 图像的对应像素之间的差异。训练函数的任务是理想地最小化 MSE 到零,相反地尽可能最大化 PSNR。
然而,这些指标长期以来一直被批评为与主观质量感觉不太相关,因为它们对可能的解决方案进行像素平均,从而导致模糊的结果。因此,与看起来质量较低的图像相比,在感知上看起来更令人愉悦的图像可能产生较低的 PSNR 值。此外,即使在垂直或水平方向上移动图像一个像素也会导致 MSE 变大(并且 PSNR 非常低),即使要评估的图片与原始图像相同。

MSE / PSNR 的问题:可能的解决方案被平均,导致模糊的结果;换句话说,由于 MSE [31]固有的平均特性,模糊性受到鼓励
上述问题引发了对感知指标的使用,该指标旨在衡量与人类感知质量相关的图像整体质量。结构相似性指数(SSIM) [32]旨在解决这一问题,在 SR 方法的开发和评估中非常常用。
然而,SSIM 仍然将被评估的图像与原始 HR 图像进行比较,因此仍然对一些因素敏感。此外,自从 SSIM 创立以来,已经开发了许多方法,这些方法被证明与人类对图像质量的感知更好地相关。
那么为什么不使用这些相关性更好的方法呢?嗯,MSE、PSNR 和 SSIM 是简单的指标,计算速度非常快(当你需要评估数千甚至数百万张图像时很方便),并且在许多常见的编程库中实现,使它们非常方便易用。此外,它们是可微分的,这是使深度学习方法能够更新其参数所必需的属性。
还设计了不需要原始 HR 图像的感知度量,称为无参考 IQA (NR-IQA)度量。例子包括 BRISQUE [33]和 NIQE [34],但这些往往主要用于评估,因为它们是不可微的。
一种在增强感知愉悦图像方面非常流行的方法是从其他网络中提取和使用中间特征,以产生一种类型的感知损失。
例如,在人脸 SR 的情况下,为人脸识别而设计和训练的诸如 VGG-Face [35]的网络可以用于强制在处理 HR 图像时获得的中间特征和在使用超分辨率图像时获得的特征彼此相似。这样,损失是在特征级而不是图像级计算的。
此外,假设为面部训练的网络提取与辨别面部最相关的显著特征,那么特征相似性的实施也将面部的身份和特征实施为相似的。这有助于防止网络“幻觉”原始图像中可能不存在的新细节,从而防止身份的改变,这种改变在一些应用中是有害的,如前一篇文章中的所述。
感知损失函数可以用于几乎任何 SR 应用,而不仅仅是人脸。事实上,感知损失最初应用于普通图像内容的超分辨率[36]。

双三次插值、使用基于像素的损失的超分辨率、SRCNN [1,2]和使用特征重建损失(一种感知损失函数)的超分辨率之间的比较。每个图像还显示了 PSNR / SSIM。从[36]获取的图像。
在上面的图像中,可以注意到使用感知损失获得的方法的 PSNR 值比使用每像素损失获得的结果低(差),而 SSIM 值仅略高。此外,PSNR 和 SSIM 都低于不仅通过 SRCNN 超分辨的图像获得的值,而且还低于通过基本双三次插值上采样的图像获得的值。
然而,可以认为,与使用基于像素的损失和 SRCNN 导出的图像相比,基于感知损失的结果实际上更清晰且更接近地面真实图像。[36]的作者指出,与基线方法相比,放大后可见的轻微交叉图案会损害 PSNR 和 SSIM 值。这突出了使用与主观质量感觉更好相关的 IQA 度量的需要。
尽管 PSNR 等度量标准存在缺点,但它们仍然有助于确保超分辨率图像的内容与原始图像保持相似。换句话说,如果不进行逐像素的比较,如果图像只是好看,网络可以彻底改变图像内容而不会受到惩罚。事实上,这是 GANs 的缺点之一(在某些应用中也是一个优点),它往往以合成原始图像中可能不存在的纹理和内容为代价来产生好看的图像。
如上所述,这在一些应用中可能是有害的,例如在人脸识别中,如果身份被改变,错误的人可能被定罪,而实际的犯罪者可能无法被识别。例如,在由于等人 (2018) 提出的工作的图 5 中,所提出的方法的超分辨率结果看起来相当好,特别是考虑到要超分辨率的低分辨率图像的大小仅为 16 × 16 像素。然而,有人可能会说,一些人的身份与原始图像中的身份不同。
针对一种损失函数类型调整网络往往会降低另一种损失函数类型的性能,反之亦然。例如,SRResNet 在 PSNR 方面优于 SRGAN,但在感知质量方面不如 SRGAN。因此,通常需要在这两种度量之间进行权衡,以确保图像内容与原始内容相似,同时使结果在感觉上令人满意。显然,这取决于目标应用程序,因此可能需要给予一种类型的度量比其他类型的度量更大的重要性。
结论和未来方向
可以看出,在软件无线电领域已经做了许多工作,但仍然存在许多需要克服的挑战。其中一个主要问题是 SR 方法的开发,该方法真正能够在盲设置中对真实世界的图像进行操作,在盲设置中没有关于可能影响图像的退化的信息被假定。
此外,以上讨论集中在通用图像内容上;有一些方法是为特定应用设计的,例如人脸 SR。这些方法也是一次对一幅图像进行操作,因此被称为单幅图像 SR (SISR)方法;虽然也做了工作来使用多幅图像(例如视频和多光谱卫星图像)提取更多信息,从而提高性能,但与 SISR 相比,这方面的进展更为有限。
总之,未来的研究有很多途径,这至少保证了一件事:软件无线电的未来是光明的(即使有时事情看起来模糊和扭曲😉)!
你对这篇文章有什么想法吗?欢迎直接在 LinkedIn 上留言、评论或给我发消息!
此外,确保 关注 me,以确保您在未来文章发表时得到通知。
作者是马耳他大学 博士后研究员 在 深松 项目,该项目是与 Ascent 软件 合作完成的,由马耳他科学委员会&技术(MCST)资助,并代表科学基金会&技术,通过
参考
[1] C. Dong、C. C. Loy、K. He 和 X. Tang,学习用于图像超分辨率的深度卷积网络 (2014),欧洲计算机视觉会议(ECCV,2014)
[2]董,卢,何,唐,基于深度卷积网络的图像超分辨率处理 (2016),IEEE 模式分析与机器智能汇刊
[3] R .蒂莫夫特、V. D .斯梅特和 L. V .古尔, A+:用于快速超分辨率的调整锚定邻域回归 (2014),亚洲计算机视觉会议(ACCV,2014)
[4] J. Yang,J. Wright,T. S. Huang,和 Y. Ma,基于稀疏表示的图像超分辨率技术 (2010),IEEE 图像处理汇刊
[5]何国光,张,任,孙,深度残差学习用于图像识别 (2016),IEEE 计算机视觉与模式识别会议(2016)
[6]何国光,张,任,孙,深度剩余网络中的身份映射 (2016),欧洲计算机视觉会议(ECCV,2016)
[7] C. Ledig,L. Theis,F. Huszr,J. Caballero,A. Cunningham,A. Acosta,A. Aitken,A. Tejani,J. Totz,Z. Wang 和 W. Shi,使用生成式对抗网络的照片真实感单幅图像超分辨率 (2017),IEEE 计算机视觉和模式识别会议(CVPR 2017)
[8] B. Lim、S. Son、H. Kim、S. Nah 和 K. M. Lee,(2017),IEEE 计算机视觉和模式识别研讨会会议(CVPRW 2017)
[9] E. Agustsson 和 r .蒂莫夫特,2017 年单幅图像超分辨率挑战:数据集和研究 (2017),IEEE 计算机视觉和模式识别研讨会会议(CVPRW 2017)
[10] Y. Zhang,K. Li,K. Li,L. Wang,B. Zhong,和 Y. Fu,使用极深残差通道注意网络的图像超分辨率 (2018),欧洲计算机视觉会议(ECCV,2018)
[11] X. Wang,K. Yu,S. Wu,J. Gu,Y. Liu,C. Dong,Y. Qiao,C. C. Loy, ESRGAN:增强型超分辨率生成对抗网络 (2018),欧洲计算机视觉会议(ECCV,2018)
[12] Y. Blau,R. Mechrez,r .蒂莫夫特,T. Michaeli 和 L. Zelnik-Manor,2018 年感知图像超分辨率 PIRM 挑战赛 (2018),arXiv
[13] A. Dosovitskiy,L. Beyer,a .科列斯尼科夫,D. Weissenborn,X. Zhai,T. Unterthiner,M. Dehghani,M. Minderer,G. Heigold,S. Gelly,J. Uszkoreit 和 N. Houlsby,一幅图像相当于 16x16 个词:大规模图像识别的变形金刚 (2021),国际学习表征会议(ICLR 2021)
[14] Z. Lu , J. Li , H .刘, C .黄, L .张, T .曾,单幅图像超分辨率变换 (2022),IEEE 计算机视觉与模式识别研讨会(CVPRW 2022)
[15] A. Liu,Y. Liu,J. Gu,Y. Qiao,C. Dong,盲图像超分辨率:综述与超越,(2022),IEEE 模式分析与机器智能汇刊
[16] K. Zhang,W. Zuo,和 L. Zhang,学习单个卷积超分辨率网络的多重退化 (2018),IEEE/CVF 计算机视觉和模式识别会议(CVPR,2018)
[17] A. Bulat,J. Yang,G. Tzimiropoulos,学习图像超分辨率,先用 GAN 学习如何做图像退化 (2018),欧洲计算机视觉会议(ECCV 2018)
[18] M. Aquilina,C. Galea,J. Abela,K. P. Camilleri 和 R. A .法鲁吉亚,使用元注意层提高超分辨率性能 (2021),IEEE 信号处理快报
[19]顾,陆,左,董,迭代核校正的盲超分辨率算法 (2019),IEEE/CVF 计算机视觉与模式识别会议(CVPR,2019)
[20]罗,黄,李,王,谭,(2020),神经信息处理系统进展
[21]罗,黄,李,王,谭,盲超分辨率的端到端交替优化 (2021),arXiv
[22] S. Y. Kim,H. Sim,M. Kim, KOALAnet:使用面向内核的自适应局部调整的盲超分辨率 (2021),IEEE/CVF 计算机视觉和模式识别会议(CVPR 2021)
[23] S. Bell-Kligler、A. Shocher 和 m .伊拉尼,使用内部 g an 的盲超分辨率核估计 (2019),神经信息处理系统进展(NeurIPS)
[24] A. Shocher,N. Cohen 和 m .伊拉尼,“零射击”使用深度内部学习的超分辨率 (2018),IEEE/CVF 计算机视觉和模式识别会议(CVPR 2018)
[25]Yuan,S. Liu,J. Zhang,Y. Zhang,C. Dong 和 L. Lin,使用循环生成对抗网络的无监督图像超分辨率 (2018),IEEE/CVF 计算机视觉和模式识别研讨会(CVPRW 2018)
[26] J. Zhu,T. Park,P. Isola 和 A. A. Efros,使用循环一致对抗网络的不成对图像到图像翻译 (2017),IEEE 计算机视觉国际会议(ICCV,2017)
[27] X. Wang,L. Xie,C. Dong,和 Y. Shan, Real-ESRGAN:用纯合成数据训练真实世界盲超分辨率 (2021),国际计算机视觉研讨会会议(ICCVW 2021)
[28] M .艾玛德、m .皮曼和 H. Corporaal, MoESR:使用核感知专家混合的盲超分辨率 (2022),IEEE/CVF 计算机视觉应用冬季会议(WACV 2022)
[29] L. Wang,Y. Wang,X. Dong,Q. Xu,J. Yang,W. An,和 Y. Guo,用于盲超分辨率的无监督退化表示学习 (2021),IEEE/CVF 计算机视觉和模式识别会议(CVPR 2021)
[30] G. Yin,W. Wang,z . Yu,W. Ji,D. Yu,S. Sun,t-s . Chua,和 C. Wang,用于多降级的盲超分辨率的条件超网络 (2022),IEEE 图像处理汇刊
[31] M. S. M. Sajjadi、B. Schlkopf 和 M. Hirsch, EnhanceNet:通过自动纹理合成实现单幅图像超分辨率 (2017),IEEE 计算机视觉国际会议(ICCV,2017)
[32]纣王,A. C .博维克,H. R .谢赫和 E. P .西蒙切利,图像质量评估:从错误可见性到结构相似性 (2004),IEEE 图像处理汇刊
[33] A. Mittal,A. K. Moorthy 和 A. C. Bovik,空间域中的无参考图像质量评估 (2012),IEEE 图像处理汇刊
[34] A. Mittal、R. Soundararajan 和 A. C. Bovik,制作“完全盲”图像质量分析仪 (2013),IEEE 信号处理快报
[35] O. M. Parkhi,A. Vedaldi 和 A. Zisserman,深度人脸识别 (2015),英国机器视觉大会(BMVC 2015)
[36] J. Johnson,A. Alahi,l .飞飞,实时风格转换和超分辨率的感知损失 (2016),欧洲计算机视觉会议(ECCV,2016)
不平衡的数据?停止使用 ROC-AUC,改用 AUPRC
原文:https://towardsdatascience.com/imbalanced-data-stop-using-roc-auc-and-use-auprc-instead-46af4910a494
AUPRC 在数据不平衡的情况下衡量绩效的优势——解释清楚

照片由 Unsplash 上的 Piret Ilver 拍摄
在 C urve (ROC-AUC)度量下的 R 接收方 O 操作方 C 特征量 A rea U 被广泛用于评估二元分类器的性能。然而,有时候,基于测量 P 精度- R ecall C 曲线(AUPRC)下的 A rea U 来评估您的分类器更合适。
我们将详细比较这两种方法,并附有实验结果和图表。Scikit-learn 实验也可在相应的笔记本中获得。
准备工作——计算曲线
我假设你熟悉精度和召回以及混淆矩阵的元素(TP,FN,FP,TN)。如果你需要的话,维基百科的文章是很好的复习资料。
现在,让我们快速回顾一下 ROC 曲线和 PRC 的计算。 我们将使用下图,这大大有助于我的理解。
假设我们有一个训练好的预测概率的二元分类器。也就是说,给定一个新的例子,它输出正类的概率。接下来,我们采用一个包含 3 个肯定和 2 个否定的测试集,并计算分类器的预测概率——我们在下图中按降序排列它们。在相邻的预测之间,我们放置一个阈值,并计算相应的评估指标,TPR(相当于召回),FPR 和精度。每个阈值代表一个二元分类器,其预测值对于阈值以上的点为正,对于阈值以下的点为负-评估测量值是针对该分类器计算的。将上述内容放到一个图形中:

图 1: 计算 ROC 曲线和 PRC,给定概率和地面真相。这些点按正类概率排序(最高概率在顶部),绿色和红色分别代表正或负标注。归功于我的同事。
使用这些计算,我们可以绘制 ROC 曲线和 PRC:

图 2: 根据图 1 所示的数据,绘制 ROC 曲线和 PRC。
计算每条曲线下的面积现在很简单——面积如图 2 所示。请注意,AUPRC 也被称为AveragePrecision(AP),这是一个来自信息检索领域的术语(稍后将详细介绍)。
在 sklearn 中,这些计算对我们来说是透明的,我们可以使用[sklearn.metrics.roc_auc_score](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.roc_auc_score.html)和[sklearn.metrics.average_precision_score](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.average_precision_score.html)。
ROC-AUC 和 AUPRC 的比较
让我们直接看结果,然后讨论实验。
在图 3 中,我们看到两个强模型(高 AUC ),它们的 AUC 得分略有不同,因此橙色模型略好。

图 3:两个看似相似的模型,橙色的那个(“另一个模型”)显示出一点优势。
然而,在图 4 中,情况完全不同——蓝色模型明显更强。

图 4:两个模型,其中蓝色的模型(“首选模型”)显示了很大的优势。
什么意义上的更强?考虑到 ROC 曲线告诉我们一个不同的故事,这真的有趣吗?在回答这些问题之前,让我们先描述一下我们的实验。
这里的关键是分类标签的分布:
- 20 个阳性
- 2000 张底片
这是一种严重的不平衡。根据这些数据,我们模拟了两个模型的预测。第一个模型在其前 20 个预测中找到 80%的肯定结果,第二个模型在其前 60 个预测中找到 80%的肯定结果,如图 5 所示。其余的积极因素平均分布在其余的例子中。

图 5:图 3 和图 4 中考虑的模型的前 100 个预测。
换句话说,这两种模型的区别在于它们发现积极因素的速度。让我们看看为什么这是一个重要的属性,以及为什么 ROC-AUC 未能捕捉到它。
解释差异
ROC 曲线的 x 轴是 FPR。鉴于不平衡的数据,FPR 的变化与召回的变化相比是缓慢的。这一因素决定了所有的差异。
为了理解这一点,在看了 100 个例子后,回到我们的不平衡数据集并考虑 FPR。我们可能看到最多 100 个阴性(假阳性),最少 80 个,因此 FPR 在区间[0.04,0.05]内。相比之下,我们的模型在 100 个例子中已经实现了 80%的召回率,在召回率方面几乎没有改进的空间,并导致了高 AUC。
另一方面,对于 PRC,获得假阳性具有显著的影响,因为每次我们看到一个假阳性时,精确度都显著降低。因此,“其他模式”表现不佳。但是为什么这里的精度很有趣呢?
考虑一下欺诈检测、疾病识别和 YouTube 视频推荐的任务。由于正面的例子很少,它们有着相似的数据不平衡的性质。然而,如果我们模型的用户能更快地找到他们的优点,他们将会节省很多时间。也就是说,积极因素的分数是至关重要的,即使差异在概率的排序列表中高出几个点。AUPRC 抓住了这一要求,而 ROC-AUC 没有做到这一点。
解释差异——ROC-AUC 作为概率
ROC-AUC 有一个很好的概率解释(在[2]中提到了额外的等价解释,在[4]或[5]中提供了一个证明)。

也就是说,ROC-AUC 是“均匀随机抽取的阳性比均匀随机抽取的阴性得分高的概率”。**
让我们在面对如上所述的严重数据不平衡时思考一下这种解释。当我们统一抽取一个随机的负值时,很可能是一个不感兴趣的负值或者是一个“容易”的负值,因为这通常是不平衡数据的原因——负值更容易收集。因此,当我们一致地抽取一个随机的正时,给它分配一个比这个“容易”负的分数更高的分数是微不足道的。即上述概率会很高。
我们感兴趣的是积极因素与“硬”消极因素相比是如何评分的,这些消极因素出现在我们预测的顶部。ROC-AUC 没有区分这些负面因素,但 AUPRC 正是这样做的。
关于排序的一个注记
对不平衡数据的分类可以被看作是一种积极的检索任务(例如,web 文档检索),在这种情况下,我们只关心来自我们的分类器(或排序器)的前 K 个预测。通常使用平均精度(AUPRC)来测量 top-K 预测,因为这是评估通用检索系统的最先进的方法[3]。因此,如果你发现你的不平衡任务类似于检索任务,强烈建议考虑 AUPRC。
代码实验
要重现本文的结果,请参见下面的知识库。您也可以使用参数,并检查它们如何影响结果。
https://github.com/1danielr/rocauc-auprc
结论
尽管 ROC-AUC 封装了许多用于评估的有用信息,但它不是一个万能的衡量标准。我们进行了实验来支持这一说法,并使用 ROC-AUC 的概率解释提供了理论依据。由此,我们得出结论,在处理数据不平衡时,AUPRC 可以为我们提供更多的信息。
总的来说,ROC 在评估通用分类时是有用的,而 AUPRC 在分类罕见事件时是更好的方法。
作为一个旁注,我们提到在高度不平衡的数据中的分类有时更适合作为一个积极的检索任务。在下一篇文章中,我们将回顾专门为这类任务定制的排名方法——如果你有兴趣,可以考虑关注我。
参考
- 戴维斯、杰西和马克·戈德里奇。"精确回忆与 ROC 曲线的关系" ICML 。2006.
- https://stats . stack exchange . com/questions/132777/what-does-AUC-stand-for-and-what-is-it
- 巴克利,克里斯和埃伦 m .沃尔赫斯。"评估评估措施的稳定性."ACM SIGIR 论坛。2017.
- https://stats . stack exchange . com/questions/180638/how-to-derive-the-probabilical-interpretation-of-the-AUC
- https://stats . stack exchange . com/questions/190216/why-is-roc-AUC-equivalent-to-the-probability-that-two-random-selected-samples
感谢阅读!我很想听听你的想法和评论😃
使用 BERTopic 库实现您的主题建模
原文:https://towardsdatascience.com/implement-your-topic-modeling-using-the-bertopic-library-d6708baa78fe
Python 中使用 BERT 的主题建模

马丁·范·登·霍维尔在 Unsplash 上的照片
介绍
文本可以包含大量信息,无论是隐式的还是显式的。像其他数据一样,这些文本可能包含隐藏信息。
这些信息是我们肉眼看不到的。我们需要数据科学来帮助我们解决这个问题。在本文中,我将向您展示如何使用 BERTopic 库实现主题建模。没有进一步,让我们开始吧!
该机制
在我们深入建模过程之前,让我向您展示库背后的机制。总之,该算法将做三个步骤。这些嵌入在文本中,对它们进行聚类,最后是主题建模。步骤如下所示:
- 首先,BERT 模型为每个文档生成一个表示向量。
- 然后,UMAP 算法降低了每个向量的维数。
- 接下来,HDBSCAN 算法用于聚类过程。因此,每个组都包含具有相似含义的文本。
- 之后,c-TF-IDF 算法检索每个主题最相关的单词。
- 最后,为了最大化多样性,使用最大化候选相关性算法。

来源:BERTopic 文档。
履行
获取并加载数据
我们将使用 Kaggle 的一个数据集,名为 Gabriel Preda 的 2020 年东京奥运会推文。
该数据集汇编了关于 2020 年东京夏季奥运会的推文。使用 Twitter API 通过查看标签为#Tokyo2020 的 tweets 来检索数据集。您可以在这里 查看关于数据集 的详细信息。

截图为作者截图。
为了下载数据,我们可以使用 Kaggle API 来简化这个过程。确保获得一个包含用于访问 Kaggle 的 API 密钥的 JSON 文件。
要创建该文件,请在 Kaggle 的您的个人资料页面上打开“帐户”选项卡。然后,单击创建新的 API 令牌。在我的例子中,它看起来像这样:

截图为作者截图。
获得 JSON 文件后,下一步是将文件复制到。kaggle 文件夹。您可以使用下面的代码来复制该文件:
如果没有问题,您可以使用下面的脚本下载数据集,不会出现任何错误:
让我们使用下面的脚本解包数据集:
完成之后,现在我们可以加载数据了。代码如下所示:
注意:
数据集最初包含大约 10 万条推文。为了使我们的过程高效,我们对 2 万个数据进行采样。我以前在主题建模过程中使用过完整的数据集。最后因为回忆之类的资源不够,过程做不下去。
如果您有足够的计算资源,可以使用完整的数据集尝试建模过程。
清理数据
文本数据不干净。在我们进入建模过程之前,我们必须首先清理数据。这是最佳实践。因此,我们可以从中获得高质量的信息。
为了清理文本,我们可以使用 NLTK 和 re 这样的库。这个库将过滤单词、提及、标签、链接等等。
现在让我们清理数据。这样做的代码如下所示:
使用 BERT 对数据建模
在我们有了干净的数据之后,我们现在可以进行主题建模过程了。对于建模过程,我们将使用 BERTopic 库。
在我们可以使用这个库之前,让我们先使用 pip 安装这个库。下面是实现这一点的代码:
现在我们可以使用图书馆了。让我们使用下面的代码导入库:
在我们导入库之后,下一步是准备两个数据。第一个是推文本身。第二个是时间戳,描述了 tweet 发布的时间。下面是实现这一点的代码:
做完这些后,现在让我们做主题建模。主题建模的过程很简单。
您所需要的就是初始化 BERTopic 对象。然后,该模型将拟合和转换推文,以基于推文生成主题。
如果您以前已经使用过 scikit-learn 库,您会发现它很容易使用。下面是实现这一点的代码:
从这段代码中,现在我们可以检索数据集上存在的主题。要显示包含主题和 tweets 数量的表格,可以运行下面的代码:
从上面可以看出,数据集内有 357 个主题。-1 主题不包括,因为这些推文没有重要的意义。
从数据集中,我们可以看到 304 条推文谈论了他们观看奥运会的活动。然后,接下来的话题是人们对为国家赢得奖牌的运动员的赞赏,以及巴西排球运动员坦达拉和菲娜达·加雷的故事。
但是这些信息不够有趣。让我们看看能否用图表把它形象化。幸运的是,BERTopic 有内置的函数来做这件事。
想象结果
我们可以创建的第一个可视化是主题之间的距离图。为了形象化,您可以运行下面的代码:
您可以做的第二个可视化是每个主题中最常出现的单词的条形图。您可以运行下面的代码来可视化:
尽管这个图表有很好的视觉效果,但是它不能显示前八个主题之外的其他主题。
您可以做的第三个可视化是可视化热图。如果您想要查看哪个主题与另一个主题高度相关,热图非常有用。为了形象化,您可以运行下面的代码:
一段时间内出现的主题
如你所知,我们把时间戳作为一个变量。我们可以使用该变量来可视化一个主题随时间变化的趋势。为了在时间戳旁边建模主题,您可以运行下面的代码:
从那里,您可以可视化时间序列的集合,每个集合代表一个主题的趋势。您可以运行下面的代码来可视化:
结论
干得好!您已经学习了如何使用 BERT 和 BERTopic 库进行主题建模。我希望你能在这里学到新的东西。同时,我希望它能帮助你从文本中获取有意义的信息。
如果你有任何问题,你可以通过我的电子邮件联系我。还有,你可以在 LinkedIn 上和我联系。
谢谢你看我的文章!
参考
[1]【https://maartengr.github.io/BERTopic/index.html
【2】https://maartengr . github . io/ber topic/algorithm/algorithm . html
插补方法的实施和局限性
原文:https://towardsdatascience.com/implementation-and-limitations-of-imputation-methods-b6576bf31a6c

作者图片
不幸的是,对于多少是人为的,多少是适当的,并没有硬性规定。这在很大程度上取决于用户偏好。研究文章常常甚至忽略了对其插补方法的详细解释,甚至忽略了需要插补的数据比例。缺乏对这些方法的解释,尤其是在涉及到所需的插补种类和数量时,应该是一种失礼。对于进行假设、插值等的医疗保健数据来说尤其如此。关于病人身份。
您必须将丢失的数据(在结构化数据的情况下)视为 2D 矩阵,在该矩阵中,您可以考虑行和列的连续性。例如,如果某个变量(列)系统地丢失,会给模型带来更多的干扰,而不是帮助,那么您可能要考虑将它从模型中全部删除。或者,如果患者或观察缺少大部分数据,您可能希望对观察执行按行删除。
许多模型无法处理输入数据中的缺失值。支持向量机、glmnet 和神经网络不能容忍任何数量的缺失值。能够容忍缺失值的少数模型是朴素贝叶斯和 CART 方法下的一些基于树的模型[1]。
在本文中,我们将讨论最流行/最普遍的插补方法的数学、实现和局限性。
KNN

KNN 可视化,作者图片
k-最近邻(KNN)插补的工作方式非常类似于分类算法。我们基于 n 维空间中最近的点来近似该值。需要定义 KNN 算法的超参数,包括:邻居数量和权重。
选择数量的邻居 ( n_neighbors )将是噪声和因此的概化和计算复杂性之间的折衷。小 K =更多噪声/更快,大 K =稳健我们的结果将会面对噪声/计算复杂。在二进制[0,1]插补的情况下,通常建议选择 K 的奇数值作为平局决胜值。
在 KNN 插补之前,我们需要标准化我们的数据。像逻辑回归一样,如果不进行标准化,KNN 也受到大值的影响。
权重:统一表示每个邻域中的所有点将被同等加权。相反,'距离'意味着每个点的权重将是邻域内距离的倒数

优势:
- 易于实施
- 小型数据集和数值数据类型的最佳选择
局限性:
- 随着预测变量和实例的增多,计算会变得更加困难(扩展性不好)
- 可以用于分类变量,但在名义分类数据的情况下需要转换为虚拟变量,在顺序数据的情况下需要进行数值转换。
from sklearn.impute import KNNImputer# Instantiate KNN imputer from sklearn
knn_imputer = KNNImputer(n_neighbors=5, weights='uniform')# imputing the missing value with knn imputer
array_imputed = knn_imputer.fit_transform(df)#convert to dataframe:
df_imputed = pd.DataFrame(array_imputed, index = DF_INDX, columns=column_names).reset_index()
平均
使用平均值的插补计算简单、快速[2]。由于这个特性,它可以很好地适应大型数据集。计算复杂度随着这种插补方法线性增加——O(n)。但是,如果您的数据存在异常值,您可能希望选择'中值 ' 策略,而不是使用平均值。😃

优势:
- 简单/容易实施
- 计算速度快
- 适用于大型数据集
缺点:
- 易受偏态分布/异常值的影响
- 不应用于名义分类数据
- 理论上可以处理有序分类数据(需要数字转换和舍入)
- 可能导致结果的偏差,因为它改变了潜在的分布(峰度)
from sklearn.impute import SimpleImputer# calling the Simple Imputer 'mean' class
imp = SimpleImputer(missing_values=np.nan, strategy='mean')#imput dataframe (will return an array)
array_imputed = imp.fit_transform(df)#convert from array to dataframe:
df_imputed = pd.DataFrame(array_imputed, index = DF_INDX, columns=column_names).reset_index(‘id’)
最频繁
非常类似于上面描述的“均值”选项。我们简单地用参数中的策略替换掉“最频繁”。
优势:
- 适用于分类数据
- 相当于对数值数据类型使用“mode ”!
- 计算简单且计算快速。由于这个特性,它可以很好地适应大型数据集
缺点:
- 可能会导致结果偏差,因为它会像“均值”(峰度)一样改变分布
- 因为如果只有几个实例丢失,则偏置是最好的
from sklearn.impute import SimpleImputer# calling the most frequent class
imp = SimpleImputer(missing_values=np.nan, strategy='most_frequent')#impute the dataframe
array_imputed = imp.fit_transform(df)#convert from array to dataframe:
df_imputed = pd.DataFrame(array_imputed, index = DF_INDX, columns=column_names).reset_index(‘ID’)
移动平均数

移动平均线需要一个确定的数据窗口。等式如下所示,其中“I”表示总值减去窗口大小加 1,“k”是窗口大小,“n”是观察值的总数,“p”是单个观察值。我们可以使用在 Python 中实现一个简单的移动平均。卷()【熊猫法】。

优势:
- 适用于时间序列数据
- 也可以使用简单移动平均的其他变体,如加权移动平均
- 保持时间序列的总体趋势
不足之处
- 如果窗口案例中有太多的缺失值会给插补带来问题。例如,如果窗口大小为 10,并且一行中有 12 个缺失值
- 如果时间序列有很大的方差,可能会极大地影响计算的平均值
# window size = 50df['SMA50'] = df['col1'].rolling(50).mean()
多重输入链式方程(小鼠)
MICE 是目前最流行的插补方法之一。MICE 也称为序列回归插补、完全条件规范或吉布斯抽样,是由 Rubin 等人开发的。铝[3]。在 MICE 算法中,使用一系列(链)回归方程来获得插补。这意味着我们实际上使用简单的插补方法,如平均值,但在数据的不同部分重复该过程几次,并对这些变量进行回归,选择一个最终与我们的分布最相似的方法。
步骤是:
- 1.计算数据集中每个缺失值的平均值。这些插补的值可以被认为是占位符
- 2.根据插补模型中的其他变量对未缺失变量的观察值进行回归
- 3.然后用回归模型的预测值(插补值)替换变量的缺失值
- 4.对每个丢失数据的变量重复这个过程。单次迭代被视为每个变量的循环。在每个周期结束时,缺失值已被预测值(来自回归)所取代,反映了观察到的(未受影响的)数据之间的关系。这最终保持了原始分布的形状。这一过程循环重复,每次都更新插补值,然后汇集估计值,得出最终结果
优势:
- 在插补前后保持相对分布相似
- 适用于有序分类数据
- 用于名义分类数据需要转换成虚拟变量
不足之处
- 用于有序分类数据将需要。round() 方法,因为结果将是一个浮点
# import fancyimpute library
from fancyimpute import IterativeImputer# calling the MICE class
mice_imputer = IterativeImputer()# imputing the missing value with mice imputer
array_imputed = mice_imputer.fit_transform(df)#convert to dataframe:
df_imputed = pd.DataFrame(array_imputed, index = DF_INDX, columns=column_names).reset_index('ID')
imputed_final['col1'] = imputed_final['col1'].round().astype(int)
数据假发
Datawig 是一种深度学习插补方法,采用长短期记忆(LSTM)网络进行插补。它可以执行分类插补和数字插补。与大多数深度学习方法一样,Datawig 经常利用您在数据中感兴趣的任何结果(目标)列。
它需要一个特定版本的 mxnet ,目前只有 Python 3.7 支持,后续版本不支持。它是一种非常强大的插补方法,但是您需要创建一个单独的环境,以便将其作为插补方法使用。描述该方法的论文可以在这里找到,文档这里找到。使用simple inputr类将自动检测必要的列编码器 (SequentialEncoder、BowEncoder、CategoricalEncoder、NumericalEncoder)和特征器(lstmfeaturezer、BowFeaturizer、EmbedingFeaturizer、NumericalFeaturizer)。
优势:
- 适用于数字数据
- 适用于分类序数/名义插补
- 使用 SimpleImputer 类自动检测列中的数据类型,并对它们进行正确编码
缺点:
- 需要大量数据才能准确
- 可能需要目标(结果)变量
- 需要将插补数据分为训练和测试(这可能很困难,取决于您缺失数据的数量和结构)
- 在执行有序分类数据时,仍需要执行舍入
# importing datawig library
import datawig# calling the datawig class
imputer = datawig.simple_imputer.SimpleImputer(
input_columns=['col1','col2','col3','col4'], output_column='col5', output_path = 'imputer_model2')
#split data without any nans and use as trainging dataimputer.fit(train_df = df_nona_train)imputed = imputer.predict(df_na_test)
df_imputed_copy['col5'].round().astype(int)
时间序列的双向递归插补

顾名思义,时间序列双向递归插补(BRITS)是针对时间序列数据的数值插补。特别是多个相关时间序列。它采用双向递归神经网络(RNN)进行插补。它的代码/GitHub repo 可以在这里找到。还有这里的学术论文【4】。该算法有两个后续部分,一个递归部分(RNN)和一个回归部分(全连接神经网络)。
优势:
- 适用于数字时间序列数据
- 不是为分类插补设计的
缺点:
- 仅适用于时间序列数据
- 不适用于分类(序数或名义)时间序列数据
插补方法汇总表

汇总表,按作者分类的图像
评估插补的标准
如果你有原始数据(稀有),但如果你正在开发一种新的插补方法,那么你会想要完整的数据集,并以 MCAR 和马尔的方式在数据中制造“缺失”。那么,我们希望用什么指标来衡量我们的估算是否充分呢?
在连续(区间数据)的情况下,我们通常使用均方根误差(RMSE)来评估拟合优度,以确定估算值与原始值的距离。
在分类数据的情况下,我们通常使用 F1 来评估拟合优度,以确定估算值与原始值的距离。
- 连续数据= RMSE
- 分类数据= F1
下面是在 scikit learn [5]中执行 RMSE 错误的代码片段:
# import necessary library for MSE
from sklearn.metrics import mean_squared_errorMSE = mean_squared_error(df_orginal['col1'], df_imputed['col1'])RMSE = math.sqrt(MSE)
和 F1 错误:
# import necessary library for F1 score
from sklearn.metrics import f1_scoreF1 = f1_score(df_original['col1'], df_imputed['col1], average='micro')
请注意,F1{ '微观','宏观','加权','二进制' }[T5[6]]有不同的指标:
- 微观指标是针对真阳性、真阴性、假阳性和假阴性进行系统计数的(跨整个列)
- 宏-计算每个标签的指标。注意标签的不平衡是很重要的,因为它们的存在没有被考虑在内
- 加权-与宏非常相似,只是现在考虑了标签不平衡
- 二元-仅报告单一类别的结果,此选项仅适用于二元变量的插补。
如果你喜欢读这篇文章,并且想支持像我一样的作家和其他人,考虑注册 Medium。这是 5 美元一个月,如果你使用我的链接,我赚一小笔佣金,这反过来有助于燃料更多的内容!😃https://medium.com/@askline1/membership
参考
[1]布莱曼、弗里德曼、奥尔申和斯通。1984.分类和回归树。纽约:查普曼;halls
【2】sci kit Learn,https://sci kit-Learn . org/stable/modules/generated/sk Learn . impute . simple imputr . html,访问时间:2022 年 5 月 3 日
【3】Rubin DB(1987)。调查中无应答的多重插补。纽约约翰·威利父子公司。
[4]曹等。al, BRITS:时间序列的双向递归插补,第 32 届神经信息处理系统会议(NeurIPS 2018),蒙特利尔,加拿大。2018
【5】Scikit Learn,https://Scikit-Learn . org/stable/modules/generated/sk Learn . metrics . mean _ squared _ error . html,访问时间:2022 年 5 月 3 日
【6】Scikit Learn,https://Scikit-Learn . org/stable/modules/generated/sk Learn . metrics . f1 _ score . html,访问时间:2022 年 5 月 3 日
从头开始实现决策树
原文:https://towardsdatascience.com/implementing-a-decision-tree-from-scratch-f5358ff9c4bb
从头做起
通过仅使用 Python 和 NumPy 实现决策树,递归地完善您的理解

从头开始的决策树[图片由作者提供]
决策树简单且易于解释。它们可以很容易地用图形显示,因此可以进行更简单的解释。在机器学习竞赛中,它们也是一种非常受欢迎和成功的选择武器(例如ka ggle)。
然而,表面上的简单并不意味着算法和底层机制是乏味甚至琐碎的。
在接下来的小节中,我们将使用 Python 和 NumPy 一步步实现一个分类决策树。我们还将学习熵和信息增益的概念,它们为我们提供了评估可能的分裂的方法,从而允许我们以合理的方式生长决策树。
但是在直接进入实现细节之前,让我们建立一些关于决策树的基本直觉。
决策树 101:树木学
基于树的方法对于解释来说是简单而有用的,因为其潜在的机制被认为与人类的决策非常相似。
这些方法包括将预测空间分层或分割成多个更简单的区域。当进行预测时,我们简单地使用新观测值所属区域的平均值或模式作为响应值。

分段预测空间的示例[图片由作者提供]
由于分割预测器空间的分割规则可以通过基于树的结构来最好地描述,因此监督学习算法被称为决策树。
决策树可用于回归和分类任务。

决策树的简化示例[图片由作者提供]
现在,我们知道了什么是决策树,以及它为什么有用,我们需要知道如何建立一个决策树?
种下一颗种子:如何种出一棵决策树
粗略地说,构建决策树的过程主要包括两个步骤:
- 将预测器空间分成几个不同的、不重叠的区域
- 预测任何新观测值所属区域的最常见类别标签
听起来很简单,但一个基本问题出现了——我们如何分割预测空间?
为了将预测器空间分割成不同的区域,我们使用二进制递归分割,这将增长我们的决策树,直到我们达到停止标准。因为我们需要一个合理的方法来决定哪些拆分是有用的,哪些是无用的,所以我们还需要一个度量标准来进行评估。
在信息论中, 熵 描述了信息或不确定性的平均水平,可定义如下:

我们可以利用熵的概念来计算由可能的分裂产生的 信息增益 。
假设我们有一个包含不同患者数据的数据集。现在,我们想把每一个病人分为患心脏病的高风险或低风险。想象一个可能的决策树,如下所示:

计算信息增益的示例决策树[图片由作者提供]
为了计算分裂的信息增益 (IG) ,我们简单地计算子节点的加权熵之和,并将其从父节点的熵中减去。
让我们通过我们的例子来进一步阐明事情:



信息增益为 1 可能是最好的结果。然而,在我们的例子中,分裂产生大约 0.395 的信息增益,因此包含更多的不确定性,或者换句话说,更高的熵值。
有了熵和信息增益的概念,我们只需要在树的当前生长阶段评估所有可能的分裂(贪婪方法),选择最好的一个,并继续递归生长,直到我们达到停止标准。
介绍算法
现在,我们已经涵盖了所有的基础知识,我们可以开始实施学习算法。
但是在直接进入实现细节之前,我们将快速浏览一下该算法的主要计算步骤,以提供一个高层次的概述以及一些基本结构。
主要算法基本上可以分为三个步骤:
- 参数初始化(例如,最大深度、每次分割的最小样本)和帮助类的创建
- 构建决策树,包括二叉递归分裂,在当前阶段评估每个可能的分裂,并继续增长该树,直到满足停止标准
- 进行预测,这可以描述为递归遍历树并返回最常见的类标签作为响应值
由于构建树包含多个步骤,为了保持代码尽可能的干净,我们将非常依赖助手函数的使用。
该算法将在两个类中实现,主类包含算法本身,辅助类定义一个节点。下面,我们可以看一下骨架类,它们可以被解释为某种蓝图,指导我们完成下一节的实现。
从头开始实施
基本设置和节点
让我们从一些基本的日常工作开始我们的实现。首先,我们为我们的主类定义一些基本参数,即停止标准max_depth、min_samples_split和root node。
接下来,我们定义一个小的助手类,它将我们的拆分存储在一个节点中。该节点包含关于feature、threshold值以及连接的left和right子节点的信息,当我们递归遍历树以进行预测时,这些信息将非常有用。
构建树
现在,事情变得有点复杂了。因此,在接下来的内容中,我们将非常依赖于使用几个辅助方法来保持有组织性。
我们通过调用fit()方法开始我们的构建过程,该方法简单地调用我们的核心方法_build_tree()。
在核心方法中,我们简单地收集关于数据集(样本、特征和唯一类标签的数量)的一些信息,这些信息是确定是否满足停止标准所必需的。
我们的助手方法_is_finished()用于评估停止标准。例如,如果剩余的样本少于每个分割所需的最小样本,我们的方法返回True,构建过程将在当前分支停止。
如果我们的构建过程已经完成,我们将计算最常见的类标签,并将该值保存在一个leaf node中。
注:停止准则是一种退出策略,用于停止递归增长。如果没有适当的停止机制,我们将会创建一个死循环。
我们继续通过计算当前阶段的最佳分割来增长我们的树。为了获得最佳分割,我们循环通过所有特征索引和唯一阈值来计算信息增益。在前面的章节中,我们已经学习了如何计算信息增益,它基本上告诉我们提议的拆分可以消除多少不确定性。
一旦我们获得了特定特征-阈值组合的信息增益,我们就将结果与我们先前的迭代进行比较。如果我们找到更好的分割,我们将相关的参数存储在字典中。
在遍历所有组合后,我们将最佳特征和阈值作为元组返回。
现在,我们可以通过递归增长子节点来完成我们的核心方法。因此,我们通过利用最佳特征和阈值将数据分成左和右分支。
接下来,我们从自身调用我们的核心方法(这是这里的递归部分),以便为孩子开始构建过程。
一旦我们满足停止标准,该方法将递归返回所有节点,允许我们建立一个完整的决策树。
做预测——或者遍历树
到目前为止,我们已经完成了大部分艰巨的工作——我们只需要再创建一个 helper 方法,就大功告成了。
可以通过递归遍历树来实现预测。也就是说,对于数据集中的每个样本,我们将结点要素和阈值与当前样本的值进行比较,并决定是左转还是右转。
一旦我们到达一个叶节点,我们简单地返回最常见的类标签作为我们的预测。
这就是了!我们完成了决策树的实现。
测试分类器
完成实现后,我们仍然需要测试我们的分类器。
出于测试目的,我们将使用经典的二元分类乳腺癌威斯康星数据集[1]。该数据集总共包含 30 个维度和 569 个样本。
导入数据集后,我们可以将其分别拆分为训练样本和测试样本。
我们实例化我们的分类器,使其适合训练数据,并做出我们的预测。利用我们的辅助函数,我们获得了大约 95.6 %的准确度,这允许我们确认我们的算法是有效的。
结论
在本文中,我们仅使用 Python 和 NumPy 实现了一个用于分类的决策树。我们还学习了潜在的机制和概念,如熵和信息增益。
理解决策树的基础知识在处理更高级的扩展时会很有用,比如装袋、随机森林和 boosting 。当试图优化基于决策树的学习算法的超参数时,对算法的更深入理解也将是有帮助的。
你可以在我的 GitHub 上的这里找到完整的代码。

从零开始的 ML 算法
View list6 stories


喜欢这篇文章吗?成为 中级会员 继续无限学习。如果你使用下面的链接,我会收到你的一部分会员费,你没有额外的费用
https://medium.com/@marvinlanhenke/membership
参考资料/更多资料:
- [1]使用的数据集:https://archive . ics . UCI . edu/ml/datasets/breast+cancer+Wisconsin+(诊断)许可:CC BY 4.0,Dua,D. and Graff,C. (2019)。UCI 机器学习知识库[http://archive . ics . UCI . edu/ml]。加州欧文:加州大学信息与计算机科学学院。
- 加雷斯·詹姆斯,丹妮拉·威滕,特雷弗·哈斯蒂,罗伯特·蒂布拉尼。《统计学习导论:在 r .纽约的应用》: Springer,2013 年。
用 Python 实现文件监视器
原文:https://towardsdatascience.com/implementing-a-file-watcher-in-python-73f8356a425d
通过文件到达进行基于事件的处理

在这篇博客中,我们将从头开始构建一个所谓的“文件监视器”。文件监视器是一个进程,它监视特定目录中任何文件的到达。文件监视器在任何文件到达时,将触发后续过程。
例如,想象一下,一个日常流程需要来自另一个部门的文件到达。除非有文件监视器,否则在手动开始相关的日常进程之前,需要有人手动监视所述文件的到达。
通常,文件监视器提供文件的创建、修改、删除、存在和重命名的功能。他们也经常通过本地操作系统事件这样做。然而,这些通常不能支持共享驱动器。
出于我们的目的,我们将构建一个非常简单的东西,它适用于所有情况,只包括新的文件到达用例。
基本面
最终,构建一个简单的文件监视器是一个非常简单的过程。我们将轮询一个目录的更改,而不是注册到本机操作系统事件。整个事情可以用三个简单的函数来解决:
- 返回在目录中找到的文件列表的函数
- 突出显示两个列表之间差异的函数
- 持续轮询的功能
事不宜迟,让我们来介绍一下这三个函数。
目录中的文件:
首先要做的是构建一个返回目录中所有文件的函数。
运行这个函数,会返回一个文件列表,如下所示:

返回在预先指定的目录中找到的文件列表
两个列表之间的差异:
接下来,我们希望能够比较两个列表并返回差异。这样,我们可以比较两次轮询之间的列表,从而确定是否出现了新文件。
以下是它的外观示例:

比较两个列表并返回差异
定期检查差异:
最后,我们需要一个轮询功能来将整个事情联系在一起。带有睡眠计时器的无限循环可以解决这个问题。
因此,我们创建了一个无限循环(而 True ),在每个循环中,我们获取当前文件的快照,并将它们与前一个循环进行比较,以确定是否出现了任何新文件。
该函数接受两个输入:
- my_dir:要监控的目录
- pollTime:检查更改之间的时间(秒)
利用我们之前的 listComparison 函数,我们可以跟踪新文件出现的时间,此时我们可以调用一个新函数 doThingsWithNewFiles,传递新文件的列表来做一些事情。
好了,伙计们,在不到 50 行的代码中,我们编写了自己的可读的 FileWatcher 代码。
Jupyter 笔记本
下面我把所有的东西放在一个 Jupyter 笔记本里,你可以在你自己的项目中使用
最后的想法
我希望您会发现这很有用,并且对一些简单的用例有所帮助。你对如何扩展和构建它有什么想法或主意吗?让我知道!
实施企业推荐系统
原文:https://towardsdatascience.com/implementing-an-enterprise-recommendation-system-89dd439db444
实现“真实世界”基于内容的推荐系统的端到端研究

我最近完成了一个推荐系统,它将作为一个高流量全球网站的新闻提要的一部分发布。由于必须具备亚秒级的建议响应时间,这些要求带来了巨大的设计挑战。
与将部署到生产环境中的任何应用程序一样,需要对以下主题做出重要决策
- 性能,
- 可用性,
- 数据准备就绪,以及
- 总成本,
每一个都需要适当的考虑。
在我最初的项目评估期间,我发现很少有公开可用的企业级机器学习(ML)资源,这些资源旨在为现实世界项目的端到端决策提供信息。这篇文章旨在帮助从业者在企业推荐系统项目的各种决策点中跋涉。
项目要求
需求需要一个带有 RESTful API 的推荐系统来集成网站的新闻提要。通过让访问者访问个性化内容,推荐系统可以成为洞察和参与的重要驱动力,这是新闻订阅源的主要目标。
其他关键要求:
- 可衡量的模型性能和商业价值
- 低于 500 毫秒的亚秒延迟。
- 高可用性和可扩展性,服务级别协议(SLA)级别为 99.9%,相当于每天最多 1.44 分钟的停机时间。
- 使用自动化进行数据提取和模型构建-部署-监控生命周期。
- 用于持续改进的全面模型和 API 监控。
- 总拥有成本(TCO)也是一个因素,尤其是经常性的托管成本。
步骤 1:探索性数据分析
在任何机器学习项目中,最重要的第一步是获得最佳质量的可用数据。项目通常没有容易访问的数据,这些数据被标记并准备好用于培训。在这种情况下,开发定制接口是为了从多个来源、供应商托管的 API、内部内容管理系统和 Google Analytics 报告 API 提取数据集。
数据提取后,探索性数据分析显示,语料库的规模有限,约有 6800 篇新闻稿和 700 篇专题文章。这些数据还缺乏一致的跨数据集元数据或标签来帮助生成候选数据。更重要的是,进一步的分析发现没有用户数据或明确/隐含的访问者反馈历史来作为推荐的基础。
根据现有数据,有必要建立一个无人监管的基于内容的推荐系统,以及一种以支持个性化推荐的方式跟踪访问者的方法。

基于内容的过滤与协同过滤(图片由作者提供)
步骤 2:开发推荐模型
对于推荐,每篇新闻稿和专题文章都需要语料库中最相似的内容来响应 API 请求。为了选择最佳的无监督自然语言处理(NLP)技术,有必要评估几种可以将文本转换为特征向量的算法。选择 SentenceBERT、Doc2Vec、FastText 和 TF-IDF 来评估哪种算法对用例及语料库最有效。
一旦为每个算法生成了语料库中文档的向量表示,就必须确定 7500 个文档之间的相似性。余弦相似性是计算向量之间相似性的优选度量,只要向量不太大,其中性能可能是一个问题。余弦相似性提供了一种基于文档的矢量化得分或嵌入来衡量文档与另一个文档相似程度的方法。

预处理的 TF-IDF 生成特征的词云(图片由作者提供)
在没有可依赖的标记数据和标准评估度量的情况下,计算在余弦相似性矩阵中自己作为最高得分文档的文档的百分比的准确性度量被用作估计模型的一般准确性的方式。该指标还用于执行随机交叉验证,以在产生最终比较之前调整超参数和预处理步骤。总体结果对于一般的推荐系统来说是好的,但是 TF-IDF 与其他模型相比具有稍微更好的推荐。

T2 的算法比较。Xlarge 实例(图片由作者提供)
为了确认最初的发现,每个模型都通过比较大量的建议样本和观察结果进行了验证,确认 TF-IDF 提供了良好的准确性,是项目的最佳选择。
与计算单词和句子嵌入的其他 NLP 技术不同,TF-IDF 通过重视不太频繁的术语来提供直接的特征提取。TF-IDF 还提供了更快、资源更少的模型生成,并且实现非常简单,可以很容易地向涉众解释。
重新排列建议
为了进一步改进推荐,通过包括流行度和新鲜度特征来重新排列相似性分数。使用流行度是一种促进推荐的好方法,在这种情况下,流行度分数基于从 Google Analytics 提取的页面视图指标,而新鲜度基于文档年龄。两个分数都被标准化,然后在合并成最终分数之前对重要性进行加权平均。
为了确保只提供最高质量的建议,每个文件的 API 建议被限制在三个最高分。推荐也限于访问者最近没有查看的内容。没有与新闻订阅源内容交互的新访问者将获得新闻订阅源中最受欢迎内容的混合。

推荐系统概述(图片由作者提供)
未来状态
一旦收集了足够的访问者历史数据,就可以试验更有效的技术,包括结合协作和基于内容的过滤的混合方法。
步骤 3:个性化推荐
拥有新闻订阅源访问者历史对于 API 能够提供个性化网站推荐至关重要。由于网站的性质,在这种情况下,访问者没有通过身份验证或跟踪。谷歌分析用于跟踪一般网站活动,但不允许提取未采样的用户级数据。为了避免不必要的复杂性,选择了一种依赖浏览器 cookies 的方法来跟踪新闻提要中最近的访问者选择。

网站推荐生命周期(作者图片)
每当访问者点击新闻内容时,访问者的浏览器中就会更新一个 cookie,反映最近浏览的页面。这些 id 随后被传递给 API 以提供个性化的推荐。
步骤 4:设计部署架构
现在是设计整体 API 部署架构的时候了,同时保持亚秒级响应时间和 TCO 需求。该架构还需要驻留在私有云环境的范围内,并且必须满足其他专有的安全限制,这里不做介绍。
考虑了各种 AWS 部署选项,包括 SageMaker 端点和 Elastic Beanstalk。SageMaker 端点可以使部署模型变得相对简单,但是根据我的经验,不是在 SageMaker 中创建的模型部署起来可能会很麻烦,并且文档可能会缺乏,有时甚至会过时。从成本角度来看,SageMaker 部署通常使用 EC2 虚拟服务器实例,即使在需求较低的时候也是按小时计费的。对于新闻订阅来说并不理想,因为预计在工作日晚上和周末的需求会更低。
另一种选择是使用 Elastic Beanstalk 来部署带有负载平衡和自动伸缩 EC2 实例的 API,以实现高可用性。每个实例将运行 Flask、Django 或 FastAPI 来处理传入的 API 请求。尽管 Elastic Beanstalk 是部署应用程序的有效方法,但它也有类似的成本问题;这项服务是免费的,但是任何启动的 EC2 实例都要 24x7 付费,不管它们是否被充分利用。
无服务器 Lambda
无服务器架构很快成为部署推荐 API 的最佳选择。有了预先生成的相似性矩阵,就不需要实时推理,并且只要针对新内容更新模型,就可以使用查找表。一个查找表提供了使用 DynamoDB 进行模型存储的灵活性,并与 Lambda 相结合,最大化了性能、成本和可用性。Lambda 函数仅在被使用时计费,并且可以提供显著的成本节约。

高级 API 架构(图片由作者提供)
除了节省成本,Lambda 还支持可伸缩性和可用性。Lambda 函数可自动扩展,以处理每个区域 1,000 多个并发执行,并在多个可用性区域中运行,满足 99.9%的 SLA 要求。
DynamoDB
对于模型存储,选择了使用 dynamo db(AWS 的 NoSQL 数据库)的非传统模式。数据库中的每一项都是一个 JSON 文档,主要包含一个惟一的内容 ID 和三个得分最高的内容 ID。这使得 API 查找速度非常快,互联网响应时间平均为 250 毫秒。
在可伸缩性和高可用性方面,DynamoDB 会随着表大小的增长自动扩展容量和重新分区数据。数据还在 AWS 区域的三个设施之间复制,提供 99.99%的 SLA。
尽管 DynamoDB 非常有效,但用它来存储 ML 模型在许多情况下可能并不可行。为了存储和加载用于推理目的的大型模型,无服务器 Lambda 可以与 AWS 弹性文件系统(EFS)一起用于各种用例。关于 Lambda/EFS 模式的更多信息可以在参考资料部分找到。
AWS Lambda 注意事项
与标准的负载平衡 EC2 架构相比,无服务器架构提供了许多优势,但它们也有一个固有的冷启动问题。根据部署包的大小和函数的初始化时间,通常可以在 1%的调用中看到执行延迟。保持部署包的轻量级很重要,但是如果这不是一个选项,那么可以启用供应并发来保持请求数量的执行环境被初始化并准备好响应传入的请求。
对于新闻订阅源,将在高峰时段启用供应的并发性,以确保 API 可以扩展以满足具有低延迟建议的流量。
全局考虑
对于全球使用案例,评估内容交付网络(CDN)的使用是至关重要的。根据通过 API 发送的数据量和缓存的情况,CDNs 可以显著缩短全球响应时间。对于 newsfeed,推荐缓存得很好,AWS CloudFront 的边缘位置将在调用 API 时为全球各地的访问者提供快速和一致的性能。
步骤 5: MLOps 和最终确定部署策略
机器学习操作,或 MLOps,简化了将 ML 模型投入生产,然后维护和监控它们的过程。虽然基本的 MLOps 自动化管道有许多选项,但我们强烈推荐其中一些,而其他的则取决于数据敏感度、团队规模和 API 可用性需求。下面,我总结了我对每个关键部署主题的策略,以及一些一般考虑事项。

基本 MLOps 自动化管道(图片由作者提供)
自动化模型构建
新创建的内容将由计划的模型构建每小时自动处理一次。可以实现接近实时的处理,但这不是必需的,因为新闻订阅源内容不是以高频率发布的。托管在 EC2 实例上的 Python 脚本使得从多个来源收集数据、生成模型并将结果推送到 DynamoDB 成为可能。大量使用带有错误警报的日志记录可以确保快速检查和解决任何故障。

自动化模型构建架构(图片由作者提供)
安全性
安全性是整体设计的重要组成部分,需要遵循组织策略。除了 AWS 服务之间的默认资源策略保护之外,到 API 的流量需要通过内部网络安全基础设施进行 TLS 加密和过滤。根据项目需要,可以使用各种各样的安全措施,包括 IP 过滤、自定义承载令牌身份验证和 AWS Cognito,用于单点登录或社交登录的(联合)身份验证。
API 监控
持续的应用程序监控是部署 API 的另一个重要方面。AWS CloudWatch 默认捕获 Lambda 执行指标,但也将用于详细的日志记录以及流量指标和健康检查触发的警报。

AWS CloudWatch 日志和警报(图片由作者提供)
对于要求高连续性的情况,CloudWatch 警报可以集成到组织的现有服务台基础架构中,用于优先生成票证。日志还可以整合到 Splunk 等工具中,以获得它们提供的额外好处。
模型漂移监控
一旦进入生产环境,模型需要持续的监控来重新验证它们的健康和业务价值。随着时间的推移,更改和转移数据是所部署模型的准确性降低的主要原因。对于新闻订阅源,在没有可依赖的标记数据或“基本事实”标签的情况下,通过计算所有文档的最高(非父)相似性得分的平均值来设计相似性分布得分指数。然后,总分数被索引到基线。
此外,用于超参数调整的精度度量是监控模型持续精度的另一种方式。在每个模型建立后计算,这两个漂移分数将随着时间的推移被跟踪,并被用作领先指标和故障排除工具。

模型漂移仪表板示例(图片由作者提供)
CI/CD 和版本控制
根据项目的规模,可以实现持续集成/持续交付管道,以加强构建、测试和部署的自动化。有多种 CI/CD 选项可用,包括 AWS 的 CodeDeploy 和 SAM 管道。对于这个项目,PyCharm 和 AWS Toolkit 以及 GitLab 插件允许管理对 Lambda 的版本控制和部署,而生产就绪的模型构建脚本会在每次计划运行之前自动下载到 EC2 实例。
第六步:A/B 测试
推荐是估计的预测,并不保证网站的观众会对推荐策略做出什么反应。一旦推荐系统部署到生产中,将进行 A/B 测试,通过比较网站指标,如有推荐和无推荐的点击率(CTR) 、每次会话的页面数、和平均会话持续时间,来衡量其有效性。实验期间收集的度量数据将有助于验证(或否定)策略。最终,A/B 测试也将用于比较当前和未来的推荐策略。
结论
我希望这篇文章能为开发和部署推荐系统提供一些思路。从整体上看一个项目,将生产放在心上,将有助于交付真正的商业价值,并对组织内部和外部如何看待项目产生积极的影响。
我在这里介绍的是一个更大的主题。例如,像混合模型这样的实现可能需要多种部署方法,并且会给所讨论的每个方面带来额外的挑战。欢迎来到企业推荐系统的世界!
资源
[1] AWS, AWS Lambda 开发者指南(2022) ,亚马逊网络服务
[2] AWS, AWS DynamoDB 开发者指南(2022) ,亚马逊网络服务
[3] AW, AWS API 网关开发者指南(2022) ,亚马逊网络服务
[4] AWS, AWS CloudFront 开发者指南(2022) ,亚马逊网络服务
[5] AWS, AWS CloudWatch 开发者指南(2022) ,亚马逊网络服务
[6] N. Jain,A. Vaidyanathan,S. Debnath 和 V. Krishnamoorthy,在 AWS Lambda 和亚马逊 EFS 上部署多个机器学习模型进行推理(2021) ,预测黑客
[7] I. Njanji,了解如何利用亚马逊 CloudWatch 警报在亚马逊网络服务 ServiceNow (2018) 中创建事件
[8] Splunk,如何将 AWS CloudWatch 日志流式传输到 Splunk (2017) ,Splunk
[9] M. Mantosh,使用 AWS 工具包开发和部署无服务器 API(2021),JetBrains
实现人工蜂群算法解决商业问题

图片由 Silv3rXArt 提供。经许可使用。
完整的,可重复使用的代码 ABC 算法与 Github 链接。
人工蜂群算法是一种基于蜜蜂群体智能觅食行为的优化算法。
我们将通过 ABC 算法的目的、实现和功能来详细了解它。然后,我们将解决一些优化基准函数的问题,如 Sphere、Himmelblau 和下面显示的跨托盘函数。我们还将研究 ABC 算法在现实世界商业问题中的应用。在 Github 上可以获得完整的、可重用的实现代码。

蜜蜂活动,同时解决跨托盘功能。图片由作者提供。
目的
在 AAXIS Digital ,我们经常会遇到棘手的业务优化问题,这些问题需要打破常规的思考。为了对这些通过计算来解决的业务问题进行建模,我们需要将它建模为一个代表候选解决方案的决策变量列表,并且能够计算一个“良好的度量”,称为目标函数。该算法的目的是找到具有最大优度的候选解。
让我们用一个例子来探讨这个想法。在下面显示的球函数的情况下,最低点(最小值)在我们想要找到的(x,y) = (0,0)处。一个候选解可能是(1,1),另一个可能是(2,2)。定义球函数的函数 f(x,y) =(x + y)表明 f(1,1)=1 是比 f(2,2)=4 更理想的解,因为它低 3。

球形函数。图片由 Gaortizg , CC BY-SA 3.0 ,通过维基共享
另一个示例模型可以用于采购决策。我们有一个产品,它有一个预测的需求曲线和每个制造商的成本清单以及采购时间。决策变量或候选解决方案将是每个制造商在月初的订单数量数组。给定这个数量的候选解决方案,我们可以计算出它的成本以及我们能够满足需求的部分。然后我们可以计算这个月的利润并优化它。
由于这些业务问题通常没有连续的优化函数,通常可以使用直接、随机或群体算法来解决。举个具体的例子:蛮力、蒙特卡洛法和进化算法。进化算法特别令人感兴趣,因为当问题不能用一个等式来表达,但良好性仍然可以像上面的采购案例一样进行测量时,它们擅长生成“好”和“足够好”的解决方案。
大体上,进化算法和相关技术使用受生物进化或自然界觅食行为启发的人工智能机制来解决问题,在维基百科上有更详细的描述。这些算法从随机的初始群体(候选解)中构建,这些群体在每一个连续的世代中变异并变得更好,朝着一个解努力。遗传算法、蜂群算法、蚁群算法、粒子群算法,以及人工蜂群算法都属于这一类。
人工蜂群算法受蜂群觅食行为启发,在多个点随机探索解空间,收敛到解。

蜜蜂活动解胡默布劳函数。图片由作者提供。
上图显示了使用这种技术的 Himmelblau 函数的解的收敛,也就是说,你可以看到小点(蜜蜂)在解(食物源)周围收敛。
人工蜂群算法
现在我们已经了解了 ABC 算法的目的,我们可以深入了解它的实现。
人工蜂群(ABC) 算法 [1]由 Karaboga 于 2005 年提出,是一种基于群体的元启发式算法,用于优化数值问题,其灵感来自蜜蜂的智能觅食行为。
该模型由四个基本组件组成:
食物来源代表正在解决的问题的解决方案。每种食物来源产生的花蜜与问题的解决方案成比例。
雇佣蜜蜂在先前食物源附近寻找比先前食物源有更多花蜜的新食物源。这种搜索是随机的,但是利用了来自其他食物来源的已知信息。
看客蜜蜂观看受雇蜜蜂的摇摆舞蹈,了解食物来源位置。在实践中,这是通过使用轮盘赌选择算法来实现的,这导致以更大的概率选择具有更高花蜜的食物位置。
侦察蜂随机选择它们的食物来源,以取代那些在预定数量的试验中无法进一步改善的废弃食物来源。
因此,PHP 中的人工蜂群算法如下:
初始化食物源后,运行主循环。在循环中,我们执行三个阶段:雇佣蜂阶段、旁观者蜂阶段和侦察蜂阶段。
初始阶段
在初始化阶段,我们为每只被雇佣的蜜蜂生成足够的食物源。
实际的食物来源的产生取决于我们正在解决的问题的类型。AbstractFoodSource 类实现了一个随机生成器,它在配置文件中定义的下限和上限之间生成一个随机数。
在业务问题的情况下,会生成一个介于 0 和 1 之间的随机整数。然后,在计算均方根之前,可以将这个数乘以最大值。
下面显示的是在求解 Rastrigin 函数的循环 1 中生成的随机蜜蜂。

初始化阶段后随机分配蜜蜂。
蜜蜂分布在解空间中,是随机生成的。一些研究人员还将它们均匀地分布在空间中;这对于某些解空间来说可能会更好。
就业蜂阶段
雇佣蜜蜂阶段包括每只蜜蜂外出寻找食物来源。在这个过程中,蜜蜂探索附近地区,如果它们发现有更多花蜜的食物源,它们的食物源就会被更新更好的食物源取代。
“getNewBasedOn”函数根据给定的食物源和另一个不是蜜蜂食物源的相邻食物源获得一个新的解。
旁观者蜜蜂阶段
受雇的蜜蜂然后回家,开始它们的摇摆舞。每只旁观的蜜蜂都能感知到每只蜜蜂从食物源中获得的花蜜量,但会有一些误差。所以,每只旁观者蜜蜂,根据它们对食物源产生的花蜜的感知,会选择食物源。花蜜越高,旁观的蜜蜂就越有可能采到它。
旁观者蜜蜂可以被认为是在最有希望的食物来源周围提供额外的探索。本质上,它们和受雇的蜜蜂做同样的事情,但是更集中在有希望的食物来源周围。因此,当被雇佣的蜜蜂确保我们不会太快放弃解决方案时,旁观者阶段促进了最佳食物来源的快速发展。这样,我们可以更快地融合,并保持我们的渠道畅通。
侦察蜂阶段
当食物来源的邻近地区被充分探索后,它就被抛弃了。每探索一个食物源,我们就增加尝试计数器。当试验计数超过最大配置值时,我们将其从食物源数组中删除,如下图所示,并找到一个新的随机食物源。

侦察蜂阶段的食物源删除
侦察蜂阶段的实现如下所示。
侦察兵不需要确保随机食物源比旧食物源更好,或者它在旧食物源的附近;它们只是像在初始阶段一样,随便找一个食物来源。把侦察阶段想象成增加新的探索领域,这样我们就不会陷入局部最小值。
其余的实现在 Github 源代码中,您可以自由探索。我留下了有用的评论来引导你前进。
使用标准测试功能进行验证
现在,让我们用一些标准测试函数和已知的解决方案来测试我们的实现,如下所示:
球函数由下式给出

给定上述函数,函数值可以表示如下。
球形食物源
给定这个函数值,花蜜值可以计算为

下面是 nectar 的价值实现:
花蜜价值计算
解是⨍(x1,x2,x3,… xn) =⨍(0.5,0.5,0.5,…0.5) = 0
绘制时,该图看起来像下图所示的球体。

球形函数。图片由 Gaortizg , CC BY-SA 3.0 ,通过维基共享
当运行 4 维时,ABC 算法的输出如下

我还实现了二维的解决方案来帮助可视化,如下所示。所有可视化的基本等高线图都是在acedemo.org使用计数器绘图仪生成的。

球函数解可视化。图片由作者提供。
到第 5 周期时,您已经可以看到良好的收敛(左下角)。我相信你注意到了,在每次迭代中,蜜蜂倾向于东西向(沿 X 轴)或南北向(沿 Y 轴)移动;这是故意的。ABC 算法每次迭代只修改食物源的一个维度。根据问题的不同,人们可以独立地考虑每个维度,并让它们在同一次迭代中发展。
Himmelblau 的功能定义为:

为了更好地理解这个函数,让我们看一下 3D 图,

Himmelblau 在 3D 中的作用。图片来自 Morn the Gorn ,公共领域,通过维基共享
如你所见,Himmelblau 的函数有四个极小值。运行时,ABC 算法正确识别了四个最小值,如下所示。

现在,为了更好地理解 ABC 算法是如何工作的,让我们形象化地描述解决方案的演变。

Himmelblaus 函数解可视化。图片由作者提供。
我们看到在第 15 个周期有良好的趋同,在第 30 个周期完全趋同。我注意到一些食物来源的随机配置确实倾向于只找到 4 个全局最小值中的 3 个。也许,人们可以考虑使用均匀分布的食物来源。就我而言,增加食物来源的数量给了我一致的结果。
交叉托盘函数也有四个最小值,定义为:

函数的曲线是这样的,

交叉托盘功能。图片由 Gaortizg , CC BY-SA 3.0 ,通过维基共享
虽然这个函数有几个局部极小值,但只有 4 个全局极小值。当运行时,ABC 算法准确地识别 4 个解决方案,成功地避免了局部最小值。

跨托盘功能解决方案可视化。图片由作者提供。
查看该函数的解的演变,我们可以看到到第 12 周期时的良好收敛和到第 20 周期时的完全收敛。一些散兵游勇的蜜蜂坚持在局部最小值,直到第 20 个周期,这种行为是意料之中的。记住,每只蜜蜂在放弃食物来源之前,都会探索食物来源,探索的次数是可以设定的。
Rastrigin 函数 ,也很流行,在 n 维域上,定义为,

其中 A=10,x 为(-5.12,+5.12)。它有一个全局最小值 X = 0。

拉斯特里金函数。 Diegotorquemada ,公共领域,通过维基共享
Rastrigin 食物源的实现如下所示。
运行 ABC 算法时,会产生以下主要结果

这是等值线图的可视化结果(来自维基百科的基本图像)

Rastrigin 函数解可视化。图片由作者提供。
注意,在二维中,Rastrigin 函数在 x=0 和 y=0 处只有一个全局最小值。蜜蜂在第 12 个周期左右找到了答案。然而,对大量局部极小值的探索甚至持续到第 100 周期。这是因为局部最小值,特别是靠近中心的那些,在值上非常接近全局最小值。
商业问题的应用
既然我们已经用测试函数验证了我们的实现,那么是时候看看我们如何将它应用于现实世界的业务问题了。
Joe 为 ACME Chemicals 工作,ACME Chemicals 是一家虚构的不合格企业对企业(B2B)化学品分销商,每天向客户销售几车化学品。他们销售的七种产品,从 A 到 G,每一种都来自六个制造商的轨道车,从 1 到 6。每节轨道车只包含一个制造商生产的一种化学品。他需要将这些轨道车从托运设施的 15 个不同轨道上取走。一条轨道包含必须占有的几节轨道车,从轨道上的第一节到最后一节(无跳跃)。此外,所有被占用的轨道车必须从设施中移除(不返回轨道选项)。
在一天开始时,他有如下所示的每条轨道上的轨道车序列。例如,在磁道 1 上,需要先删除 D1,然后才能删除 C1 或 E1。

轨道上的轨道车
他还有一份需要交付给客户的产品清单,以及当天他对每个制造商的承诺。
╔════════════════╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
║ Product ║ A ║ B ║ C ║ D ║ E ║ F ║ G ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Orders ║ 2 ║ 3 ║ 4 ║ 3 ║ 5 ║ 3 ║ 2 ║
╚════════════════╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝
╔════════════════╦═══╦═══╦═══╦═══╦═══╦═══╗
║ Manufacturer ║ 1 ║ 2 ║ 3 ║ 4 ║ 5 ║ 6 ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Commitment ║ 3 ║ 5 ║ 5 ║ 2 ║ 4 ║ 3 ║
╚════════════════╩═══╩═══╩═══╩═══╩═══╩═══╝
为了简单起见,让我们假设制造商不重要,而销售产品和产品对制造商的承诺不重要。例如,在上表中,Joe 需要从任何制造商(A1、A2 等)为化学品 A 收集 2 辆轨道车。).同样,任何产品都可以提货,以满足制造商的承诺(A1、B1 等)。
解决方案模型
建模可能是解决过程中最重要的部分,因为模型可能决定你解决问题的成败。
每节轨道车就像是堆栈中的一个元素。每辆车应从最后一辆到第一辆卸载(后进先出)。一个解决方案可以用从每条轨道卸载的轨道车的数量来表示。

轨道车问题的候选解决方案
所示的候选解可以用数学方法表示为:
[2,3,2,2,0,2,1,0,2,0,3,1,2,1,1,2]
也就是说,轨道 1 移除了 2 辆车,轨道 2 移除了 3 辆车,轨道 3 移除了 2 辆车,依此类推。对于 ABC 算法,这将是我们的 15 维食物来源。
目标函数
现在我们有了食物来源,我们需要计算目标函数。
因为我们知道哪些汽车被移走了,我们可以计算出每个产品和制造商的汽车数量。下表显示了从跟踪中删除的每个产品和每个制造商的数量。
╔════════════════╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
║ Product ║ A ║ B ║ C ║ D ║ E ║ F ║ G ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Orders ║ 2 ║ 3 ║ 4 ║ 3 ║ 5 ║ 3 ║ 2 ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Actual ║ 2 ║ 3 ║ 4 ║ 3 ║ 5 ║ 3 ║ 2 ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Error ║ 0 ║ 0 ║ 0 ║ 0 ║ 0 ║ 0 ║ 0 ║
╚════════════════╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝╔════════════════╦═══╦═══╦═══╦═══╦═══╦═══╗
║ Manufacturer ║ 1 ║ 2 ║ 3 ║ 4 ║ 5 ║ 6 ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Commitment ║ 3 ║ 5 ║ 5 ║ 2 ║ 4 ║ 3 ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Actual ║ 4 ║ 5 ║ 4 ║ 2 ║ 4 ║ 3 ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Error ║-1 ║ 0 ║ 1 ║ 0 ║ 0 ║ 0 ║
╚════════════════╩═══╩═══╩═══╩═══╩═══╩═══╝
有了这些信息,我们可以将目标函数计算为均方根误差,如下所示:
目标函数= ∑(产品数量-产品订单)+ ∑(制造商数量-制造商承诺)
对于建议的候选解决方案,目标函数是 2。
这是轨道车问题的目标函数的实现。
如果这是一个真正的问题,我们可以做得更好。我们可以计算交付产品的利润率,如果交付不足,考虑销售的美元金额和失去客户信任的粗略美元金额。然后,我们可以减去未售出火车车厢的存储成本,或者,如果仓库允许,减去重新进货的成本。使用这种方法可以捕捉更复杂的条件。目标将是利润最大化。
解决方案
该配置在 GitHub 上的轨道车-2.ini 中给出。我们将配置输入到 ABC 算法中。

运用 ABC 算法解决有轨车问题
如上所述,上面的解决方案数组表示从每条轨道上移除的轨道车数量。最佳解决方案如下所示:

轨道车问题的解决方案
下面是订单和承诺的数量、计算的解决方案和错误。
╔════════════════╦═══╦═══╦═══╦═══╦═══╦═══╦═══╗
║ Product ║ A ║ B ║ C ║ D ║ E ║ F ║ G ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Orders ║ 2 ║ 3 ║ 4 ║ 3 ║ 5 ║ 3 ║ 2 ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Actual ║ 2 ║ 3 ║ 4 ║ 3 ║ 5 ║ 3 ║ 2 ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Error ║ 0 ║ 0 ║ 0 ║ 0 ║ 0 ║ 0 ║ 0 ║
╚════════════════╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝╔════════════════╦═══╦═══╦═══╦═══╦═══╦═══╗
║ Manufacturer ║ 1 ║ 2 ║ 3 ║ 4 ║ 5 ║ 6 ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Commitment ║ 3 ║ 5 ║ 5 ║ 2 ║ 4 ║ 3 ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Actual ║ 3 ║ 5 ║ 5 ║ 2 ║ 4 ║ 3 ║
╠════════════════╬═══╬═══╬═══╬═══╬═══╬═══╣
║ Error ║ 0 ║ 0 ║ 0 ║ 0 ║ 0 ║ 0 ║
╚════════════════╩═══╩═══╩═══╩═══╩═══╩═══╝
订单和承诺是我们想要的(理想的解决方案)。我将实际值显示为算法计算出的值,将误差显示为与理想值的差值。
这是乔世界里的另一天。

轨道上的轨道车
产品订单和与制造商的合同如下:
╔════════════════╦═══╦═══╦═══╦═══╗
║ Product ║ A ║ B ║ C ║ D ║
╠════════════════╬═══╬═══╬═══╬═══╣
║ Orders ║ 2 ║ 3 ║ 4 ║ 5 ║
╚════════════════╩═══╩═══╩═══╩═══╝
╔════════════════╦═══╦═══╦═══╦═══╗
║ Manufacturer ║ 1 ║ 2 ║ 3 ║ 4 ║
╠════════════════╬═══╬═══╬═══╬═══╣
║ Commitment ║ 3 ║ 4 ║ 4 ║ 2 ║
╚════════════════╩═══╩═══╩═══╩═══╝
不存在精确的解决方案。赛道上根本没有 5 辆 D 产品的车。乔将不得不寻找其他地点来完成订单。
这里有四个解决问题的方法:

问题的四大解决方案
最佳解决方案的产品和制造商数量如下所示。不出所料,算法无法完成 d 的订单。
╔════════════════╦═══╦═══╦═══╦═══╗
║ Product ║ A ║ B ║ C ║ D ║
╠════════════════╬═══╬═══╬═══╬═══╣
║ Orders ║ 2 ║ 3 ║ 4 ║ 5 ║
╠════════════════╬═══╬═══╬═══╬═══╣
║ Actual ║ 2 ║ 3 ║ 4 ║ 4 ║
╠════════════════╬═══╬═══╬═══╬═══╣
║ Error ║ 0 ║ 0 ║ 0 ║ 1 ║
╚════════════════╩═══╩═══╩═══╩═══╝╔════════════════╦═══╦═══╦═══╦═══╗
║ Manufacturer ║ 1 ║ 2 ║ 3 ║ 4 ║
╠════════════════╬═══╬═══╬═══╬═══╣
║ Commitment ║ 3 ║ 4 ║ 4 ║ 2 ║
╠════════════════╬═══╬═══╬═══╬═══╣
║ Actual ║ 3 ║ 4 ║ 4 ║ 2 ║
╠════════════════╬═══╬═══╬═══╬═══╣
║ Error ║ 0 ║ 0 ║ 0 ║ 0 ║
╚════════════════╩═══╩═══╩═══╩═══╝
现在,Joe 可以查看解决方案,并选择一个他认为最合适的解决方案,然后在一天的剩余时间里从事只有他能做并且他真正喜欢做的工作。
ABC 算法的其他应用
一个更简单的例子是大学课程的安排。Vijini Mallawaarachchi 在她的课程表编排使用遗传算法中描述了一种将遗传算法应用于大学课程表编排的方法【2】。她建议用学生的阶级冲突作为衡量标准。在这个例子中,所有学生的理想值是 0(无冲突)。因此,适应度函数可以是理想解的均方根误差。
在同行评审的科学期刊中也可以找到更多的例子,
- 周凯·高等人【3】,将其应用于作业车间调度。
- R. Srinivasa Rao 等人对配电网络配置进行了优化,以降低损耗。[4]使用 ABC 算法。
- 的埃桑·阿·阿马尔等人解决了配电网中分布式电源的最优规模和位置的类似问题。[5]
- Garro et al[6] 将 ABC 算法应用于 DNA 微阵列的分类,以发现最佳基因集,从而正确地对癌症样本进行分类。
结论
在本文中,我们研究了人工蜂群算法的目的和实现,以及它是如何工作的。我们探索了蜜蜂的四个阶段;初始化、雇佣、旁观和侦察。然后,我们回顾了作为食物源实现的测试函数,并看到了在求解计算过程中蜜蜂活动的可视化。然后,我们讨论了如何建模和应用 ABC 算法来解决日常问题。
总的来说,ABC 算法在优化数学函数以及解决方案架构师经常遇到的实际业务问题方面非常灵活。
在这里找到代码。
如果您对 ABC 算法的业务解决方案方面更感兴趣,请阅读我即将发表的后续文章。
参考文献
[1]卡拉博加,德尔维斯。“人工蜂群算法。”scholar pedia5,第 3 期(2010): 6915。
[2]维吉尼·马拉瓦拉赫。《利用遗传算法进行课表编排》Medium,(2017),https://vijini . Medium . com/time-table-Scheduling-2207 ca 593 b4d
[3]高、、Ponnuthurai Nagaratnam Suganthan、全、蔡泰进、陈顺冲和田。"一种求解模糊加工时间柔性作业车间调度问题的改进人工蜂群算法." 专家系统及应用65(2016):52–67。
[4] Rao,R. Srinivasa,S. V. L. Narasimham 和 M. Ramalingaraju。"基于人工蜂群算法的配电网降损结构优化." 《国际电力与能源系统工程杂志》 1,第 2 期(2008):116–122。
[5]阿马尔、埃桑、基兰·法扎纳、阿萨德·瓦卡尔、穆罕默德·阿米尔、阿兹哈尔·哈克、穆罕默德·扎希德和梅穆纳·巴图尔。"基于 ABC 算法的配电网分布式电源多目标优化配置." Ain Shams 工程学报 12,第 1 期(2021):697–708。
[6]加罗、比阿特丽斯·a、卡蒂亚·罗德里格斯和罗伯托·a·巴斯克斯。"利用人工神经网络和 ABC 算法对 DNA 微阵列进行分类."应用软计算 38(2016):548–560。
实施贝叶斯线性回归
原文:https://towardsdatascience.com/implementing-bayesian-linear-regression-9375a9994f98
本文介绍了贝叶斯线性回归的基础,包括一个供感兴趣的程序员使用的 Python 实践示例。

线性回归试图通过将线性方程拟合到观察到的数据来模拟两个变量之间的关系。人口简单线性回归可表述如下:

其中:
- y 是因变量,X 是自变量
- β0 是总体截距,β1 是总体斜率。这些是我们要估计的回归参数。
然后,使用最小二乘回归原理,我们可以计算最小化均方误差成本函数的权重和偏差的值。如果你已经知道了这些概念,那么你就熟悉了 frequentist 线性回归方法。
然而,除了频率主义者方法,还有另一个统计推断学派,叫做贝叶斯。一方面,frequency ist方法将概率理解为由重复实验确定的事件的可测量频率。另一方面,贝叶斯方法将概率描述为对事件可信度的测量,使用从观察数据中获得的先验知识。
本文重点关注贝叶斯线性回归,并通过以下方式进行介绍:(1)介绍一些基本概念,如贝叶斯定理,(2)描述贝叶斯线性回归背后的理论,以及(3)展示 Python 中的实际操作示例。
1.基本概念
贝叶斯定理在数学上表述为以下等式:

其中:
- P(A|B) 被称为后路;这就是我们试图估计的。
- P(B|A) 称为可能性;这是根据我们最初的假设,观察到新证据的概率。
- P(A) 称为先于;这是我们假设的概率,没有任何额外的先验信息。
- P(B) 称为边际可能性;这是观察到证据的总概率。
在机器学习中,变量 B 可以指示观察到的数据,这意味着利用生成模型 𝑃 ( 𝐵 | 𝐴 )和一个可能的模型参数 P(A)的先验分布,我们可以得到给定观察到的数据的模型参数的后验。我们将在下一节描述贝叶斯线性回归时看到更多细节。
2.贝叶斯线性回归
在贝叶斯分析中,目标是通过结合来自观察数据的关于参数的信息来更新参数的概率分布。使用贝叶斯公式,这可以表述为:

其中:
- P(β|x,y) 描述给定观测数据、目标和先验分布的参数的概率。
- P(y|x,β) 描述给定数据和参数的目标值的概率
- P(β) 描述了关于参数的初始知识。如果我们没有关于分布的任何估计,我们可以使用参数的非信息先验,如正态分布。
- P(x,y) 描述了数据和目标的联合概率。
总之,为了计算后验概率,我们将可能性 P(y|x,β) 与先验概率 P(β) 相乘,前者表明我们认为数据是如何分布的,后者表明我们认为 β 是如何分布的。随着数据数量的增加,与先前的相比,可能性变得更加显著,并且解决方案越来越接近从最小二乘法获得的值。
然而,还有归一化项 P(x,y) ,其通过对所有可能的参数值进行积分来估计:

尽管这个公式看起来很简单,但即使是简单的模型,也很难用封闭形式来计算。这就是所谓的马尔可夫链蒙特卡罗(MCMC)方法发挥作用的时候了。简单地说,这种方法包括一类从概率分布中取样的算法。因为详细解释这种方法本身就需要一整篇文章,所以我在这里为感兴趣的人留下了一个很好的参考资料。
总结整个逻辑,要计算贝叶斯线性回归,必须遵循以下步骤:
- 设定关于参数 P(β)分布的初始信念。
- 收集一些数据。
- 使用贝叶斯公式计算参数β的后验概率分布。
- 对未知数据进行推断,计算输出的后验分布参数,而不是点估计。
3.编码
最后,这里有一个 Python 中的贝叶斯线性回归的实际例子。本节分为两部分。第一部分(评估贝叶斯回归模型)包括 frequentist 和贝叶斯回归模型之间的比较,以及用不同数量的数据进行训练时参数分布的分析。第二部分(更新先验贝叶斯回归模型)展示了在获得更多数据时如何重新训练模型,并展示了每次训练迭代的输出分布的演变。
评估贝叶斯回归模型
对于第一部分,我们创建了一个包含 1000 个数据点的数据集,其斜率为 2,截距为 1。然后,我们也给数据点添加了噪声以增加随机性。
然后,我们计算了普通最小二乘法,以查看使用 frequentist 线性回归时的输出。

Intercept: 0.986230345022046
Slope: 2.071679208492629

如上所述,斜率和截距近似于生成数据时设定的参数。
然后,让我们建立贝叶斯模型
- 截距~ N( μ=0,σ=20)
- 斜率~半柯西(β=10)
- μ =截距+斜率* x
- σ ~半柯西(β=10)
- μ =截距+斜率* x
- y ~ N(μ=μ,σ=σ)
现在,我们使用 Hamiltonian MC No U-Turn 采样器(NUTS)从 10000 个随机 MCMC 样本中创建模型。


类似于 frequentist 线性回归模型,我们可以看到模型得出了相同的结果:斜率在 2 左右,截距在 1 左右。然而,参数的分布表明模型仍然是不确定的,例如 94%的
例如,斜率的 94%的最高密度区间在 1.9 和 2.3 之间,这仍然是一个相当宽的区间。拦截也是如此。贝叶斯推理的伟大之处在于,我们提供的数据越多,模型就变得越确定。这可以在下一个示例中观察到,在该示例中,用 500 个数据点(总数据的一半)对模型进行了训练。

这里,与上述模型相比,斜率的区间在[1.7,2.3]之间,而不是[1.9,2.3]。
一旦回归模型被训练,就有可能进行预测。

这里,当增加数据数量时,也可以观察到模型的确定性,因为右边的分布(1000 个数据点)比左边的分布(500 个数据点)窄。
更新先验贝叶斯回归模型
最后,这是一个如何用新数据重新训练模型的例子。为此,我们创建了一个包含两个独立变量的新数据集。
这是上面为前 100 个数据点创建的相同模型。
为了更新我们关于参数的信念,我们使用后验分布,它将在后验地用作下一个推断的先验分布。
用于每次推理迭代的数据必须独立于之前的迭代。否则,相同的(可能是错误的)信念会被一遍又一遍地注入系统,放大错误并误导推论。

该图显示了每次迭代的回归模型的分布。可以观察到,我们用新数据训练模型越多,参数就越接近设定值。
如果你喜欢这个帖子,请考虑 订阅 。你将获得我所有的内容+所有其他来自牛逼创作者的文章!
参考
[1] 贝叶斯线性回归
[2]中等,贝叶斯线性回归中的 Python
[3] PyMC3,更新先验知识
[4] Kaggle,pymc 3 中的贝叶斯线性回归
[5] GitHub,贝叶斯线性回归
[6]中,贝叶斯推理和马尔可夫链蒙特卡罗抽样中的 Python
在 PyTorch 中实现 ConvNext
原文:https://towardsdatascience.com/implementing-convnext-in-pytorch-7e37a67abba6

作者图片
击败变形金刚的新康文网
嘿,我在LinkedIn过来打个招呼👋
你好。!今天,我们将在 PyTorch 中实现著名的 ConvNext,这是在 2020 年代的 ConvNet 中提出的。
我们开始吧!
本文提出了一种新的基于卷积的体系结构,它不仅优于基于变换器的模型(如 Swin ),而且可以随数据量的增加而扩展!下图显示了不同数据集/模型大小下的 ConvNext 精度。

因此,作者开始采用众所周知的 ResNet 架构,并根据过去十年的新的最佳实践和发现对其进行迭代改进。作者关注 Swin-Transformer,并密切关注其设计选择。论文一流,强烈推荐阅读:)
下图显示了所有不同的改进以及每项改进后各自的性能。

他们将路线图分为两部分:宏观设计和微观设计。宏观设计是我们从高层次的角度所做的所有改变,例如阶段的数量,而微观设计更多的是关于更小的事情,例如使用哪个激活。
我们现在将从一个经典的瓶颈块开始,一个接一个地应用每个更改。
起点:ResNet
如你所知(如果你没有我有一篇关于在 PyTorch 中实现 ResNet 的文章)ResNet 使用了一个残余瓶颈块,这将是我们的起点。
让我们检查它是否工作
torch.Size([1, 64, 7, 7])
我们再定义一个Stage,一个blocks的集合。每个阶段通常以因子2对输入进行下采样,这在第一个模块中完成。
torch.Size([1, 64, 4, 4])
酷,注意输入是如何从7x7减少到4x4的。
ResNet 也有所谓的stem,它是模型中的第一层,对输入图像进行大量的下采样。
很好,现在我们可以定义ConvNextEncoder来保存一个阶段列表,并将一个图像作为输入来产生最终的嵌入。
torch.Size([1, 2048, 7, 7])
这是你的普通resnet50编码器,如果你附加一个分类头,你会得到一个很好的旧 resnet50,准备好接受图像分类任务的训练。
宏观设计
更改阶段计算比率
在 ResNet 中,我们有 4 个阶段,Swin 变压器使用比率1:1:3:1(因此第一阶段有一个模块,第二阶段有一个模块,第三阶段有三个模块...).将 ResNet50 调整到这个比率((3, 4, 6, 3)->-(3, 3, 9, 3))会导致性能从78.8%增加到79.4%。
将词干改为“Patchify”
ResNet stem 使用非常激进的 7x7 conv 和 maxpool 对输入图像进行大幅缩减采样。然而,《变形金刚》使用了“patchify”词干,这意味着它们将输入图像嵌入到补丁中。Vision Transfomers 使用非常激进的补丁(16x16),作者使用 conv 层实现的 4x4 补丁。精度从79.4%变为79.5%,表明修补工作正常。
ResNeXt-ify
ResNetXt 对瓶颈中的 3x3 conv 层采用分组卷积来减少 FLOPS。在 ConvNext 中,它们使用深度卷积(就像在 MobileNet 和后来的 EfficientNet 中一样)。深度方向卷积是分组卷积,组数等于输入通道数。
作者注意到,这与自我关注中的加权和运算非常相似,后者只在空间维度上混合信息。使用深度方向的 convs 会降低精度(因为我们没有像 ResNetXt 那样增加宽度),这是意料之中的。
所以我们在BottleNeck块中把我们的 3x3 conv 改为
倒置瓶颈
我们的瓶颈首先是通过 1x1 conv 减少功能,然后应用 3x3 conv,最后将功能扩展到原始大小。一个倒置的瓶颈块,做相反的事情。我有一整篇文章对它们进行了很好的可视化。
所以我们从wide -> narrow -> wide到narrow -> wide -> narrow。
这类似于变形金刚,因为 MLP 层遵循narrow -> wide -> narrow设计,MLP 中的第二个密集层将输入的特征扩展了四倍。
大型内核
现代视觉转换器,如 Swin,使用更大的内核大小(7x7)。增加内核大小将使计算更昂贵,所以我们向上移动大的深度方向 conv,这样我们将有更少的通道。作者指出,这类似于变形金刚模型,其中多头自我关注(MSA)在 MLP 层之前完成。
这增加了从79.9%到80.6%的精确度
微观设计
用 GELU 替换 ReLU
既然最先进的变形金刚都用葛鲁,为什么不在我们的模型里用呢?作者报告说准确性保持不变。在nn.GELU的 PyTorch GELU。
更少的激活功能
我们的模块有三个激活功能。而在变压器模块中,只有一个激活功能,即 MLP 模块内部的激活功能。作者移除了除了中间 conv 层之后的激活之外的所有激活。这提高了81.3%匹配 Swin-T 的准确性!
更少的标准化图层
与激活类似,变形金刚块具有较少的规范化层。作者决定删除所有批次,只保留 conv 中部之前的批次。
用 LN 代替 BN
嗯,他们用图层替换了批次图层。他们注意到在最初的 ResNet 中这样做会影响性能,但是在我们做了所有的修改之后,性能提高到了81.5%
所以,让我们应用它们
分隔缩减像素采样图层。
在 ResNet 中,下采样由stride=2 conv 完成。变压器(和其他 conv 网)有一个单独的下降采样块。作者移除了stride=2,并使用2x2 stride=2 conv 在三次转换之前添加了一个下采样模块。在下采样操作之前需要进行归一化,以保持训练期间的稳定性。我们可以将这个模块添加到我们的ConvNexStage中。最后,我们到达82.0%超越 Swin!
现在我们可以清洁我们的BottleNeckBlock
我们终于到达了最后一个街区!让我们测试一下
torch.Size([1, 62, 7, 7])
最后润色
他们还添加了随机深度,也称为下降路径,(我有一篇关于它的文章和图层比例。
好了🎉我们找到了!看看有没有效果!
torch.Size([1, 62, 7, 7])
超级!我们需要在编码器中创建丢弃路径概率
torch.Size([1, 2048, 3, 3])
为了得到用于图像分类的最终 ConvNext,我们需要在编码器上应用一个分类头。我们还在最后一个线性图层前添加了一个LayerNorm。
torch.Size([1, 1000])
现在你有了!
结论
在本文中,我们一步步地看到了作者为从 ResNet 创建 ConvNext 所做的所有更改。我希望这有用:)
感谢您的阅读!
弗朗西斯科
在 Azure 上实现数据网格
原文:https://towardsdatascience.com/implementing-data-mesh-on-azure-c01ee94306cd
数据网格是一种令人兴奋的设计和开发数据架构的新方法。在我的第一系列文章和更多关于数据产品和数据域的最新资料之后,我收到了关于解释 Azure 上的实际实现可能是什么样子的询问。我将在这篇博文中尝试回答这个问题。
Azure 数据管理和分析场景
为了在 Azure 上构建数据网格,我建议你密切关注 Azure 数据管理和分析场景。这个框架是一个可部署的参考架构,附带(开源)模板和最佳实践。该体系结构使用两个主要的构造块,它们是所有部署选择的基础:
- 数据管理登陆区:这是你的数据架构的基础。它包含数据管理的所有关键功能,如数据目录、数据沿袭、API 目录、主数据管理等等。
- 数据登陆区:这些是托管您的分析和人工智能解决方案的订阅。它们包括托管分析平台的关键功能。

下图显示了具有数据管理登录区和单个数据登录区的数据管理和分析场景平台的概述。图中并没有显示所有的 Azure 服务。它已经过简化,以突出在该体系结构中如何组织资源的核心概念。(来源:【Microsoft.com】T2)
该框架没有明确规定您必须提供的数据架构的确切类型。您可以将它用于许多常见的数据管理和分析解决方案,包括(企业)数据仓库、数据湖、数据湖库和数据网格。对于这篇博客文章——实际设计一个数据网格——,我们将放大到后者。
Azure 上的数据网格
在展示任何解决方案之前,我想明确声明,每个架构都遵循所有数据网格原则:域所有权、数据即产品、自助式数据平台和联合计算治理。接下来,不同的路径将导致数据网格架构。没有对错之分。归根结底就是做出正确的权衡和自己的偏好。
单一数据着陆区
构建数据网格架构最简单的部署模式是使用一个数据管理登陆区和一个 单一数据登陆区 。在这种情况下,您的数据架构在概念上类似于下图:

单一数据登陆区,对小型公司或绿地项目有意义
在这种体系结构中,您的所有功能性数据域都将位于同一个数据着陆区。这意味着单个订阅包含一组标准的服务和资源组,用于隔离 data domains 和数据产品。标准服务将适用于所有领域,例如 Azure Data Lake Store、Azure Data Factory、Azure Logic Apps 和 Azure Synapse Analytics。
所有数据域都遵循数据网格的原则:数据遵循域所有权,数据被视为产品。此外,该平台允许自助服务,尽管由于单一着陆区配置,服务的变化预计是有限的。
此部署选项的一个考虑因素可能是,您希望所有域都严格遵守相同的数据管理原则。这种部署方式也适用于小型公司或新建项目。你想拥抱数据网格,但不要把事情搞得太复杂。最后,当您开始构建更复杂的东西时,这种部署也是一种选择。你计划在晚些时候扩展到多个着陆区。
源系统和消费者一致的着陆区
在之前的模型中,我们没有考虑其他订阅或内部应用。因此,我想对前面的模型提出一个微妙的变化,即添加一个与源系统一致的登录区来管理所有传入的数据。拥有两个数据登陆区是有意义的,因为数据登陆是一个困难的过程。在使用大量数据时,这仍然是最具挑战性的问题之一。它还经常需要额外的工具来解决集成挑战,这与数据消费的方式形成对比。因此,区分提供和消费数据是合乎逻辑的。

源系统和消费者一致的数据登陆区,用于优化数据上线和数据消费(作者:Piethein Strengholt)
对于左边架构中的数据登陆区,您通常会期望服务来促进数据的加入,比如 CDC ,拉 API 的服务,或者数据湖服务,用于动态构建不可变数据集。来自该平台的服务可以从内部、云环境或 SaaS 供应商处获取数据。这种平台通常还会有更多的开销,因为与底层操作应用程序有更多的耦合。因此,您可能希望将其与任何数据使用区别对待。
对于与消费一致的数据着陆区,您可以优化数据使用,并期待旨在将数据转化为价值的服务:探索、可视化、机器学习、报告等等。这方面的领域将遵循数据网格的所有原则。它们拥有数据的所有权,并被允许直接将数据分发给其他域。
中枢、通用和特殊数据着陆区
下一个部署选项是前一个设计的另一个迭代。这种部署遵循一种管理的网状拓扑:数据通过一个中央集线器分发,在该集线器中,数据按域进行分区,在逻辑上是隔离的,因此不是集成的。该模型中的中心使用其自己的(与域无关的)数据登陆区,并且可以由一个中央数据治理团队拥有,该团队监督哪些数据被分发到哪些其他域。该中心通常还提供有助于数据上线的服务,这解释了与之前部署选项的重叠之处。

通过中央和管理中心进行数据分发的数据着陆区(作者:Piethein Strengholt)
对于需要标准服务来消费、使用、分析和创建新数据的域,将使用通用的数据登陆区。这种单一订阅将包含一组标准服务,在某种程度上类似于之前看到的单一登录区部署。在这种配置中,我鼓励您应用数据虚拟化,因为您的大多数数据产品已经存在于 hub 中。不需要额外的数据复制。
此外,这种部署允许“特殊”:当无法对域进行逻辑分组时,提供额外的数据着陆区。例如,当区域或法律边界适用时,或者当域具有独特且相反的要求时。或应用强有力的全球子公司治理的情况,海外活动除外。在这种隔离的情况下,您可以部署额外的数据着陆区。
对于迫切需要控制哪些数据由哪些其他域分发和使用的组织来说,hub 部署选项是一个考虑因素。在解决大型数据消费者的时变和非易失问题时,这也可能是一个有效的选择。如果是这样的话,你应该在数据产品的设计上严格标准化,允许你的域进行时间旅行和再传送。我经常看到金融机构或受监管机构使用这种模式。
功能和区域一致的数据着陆区
一种可伸缩性的方法是提供多个数据登陆区,用于根据工作和共享数据的方式的内聚性和效率对功能域进行分组。在这种情况下,您所有的数据着陆区都遵循相同的控制和审计,但是您允许不同团队使用的不同数据着陆区之间的灵活性和设计变化。

多个数据着陆区,用于可伸缩性和将域分组在一起(作者:Piethein Strengholt)
有许多方面决定了哪些功能数据域将被逻辑分组在一起,并成为共享数据着陆区的候选对象。例如,区域边界可能导致实施相同的蓝图。所有权、安全性或法律界限可能会迫使域被隔离。灵活性、变化速度或潜在的出售或分离能力也是重要的驱动因素。我就这个主题写了一篇很长的博文,为您提供了一些细微差别和注意事项:https://towards data science . com/data-domains-where-do-I-start-a 6d 52 fef 95d 1
不同的数据着陆区不自立。如果您有多个区域,这些区域可以连接到其他区域中托管的数据湖。这种方法允许域在整个企业中协作。您还可以应用多语言持久性来混合不同的数据存储技术,允许域直接从其他域读取数据,而不需要数据复制。这更好地允许域获得数据的完全所有权。
当部署多个数据登录区时,您应该意识到每个数据登录区都有一些管理开销:必须在所有数据登录区之间应用 VNET 对等,必须管理额外的私有端点,等等。数据登录区和域的过于细粒度的对齐可能会破坏您的可伸缩性目标,因此您应该找到分组功能域的正确平衡。
当您的数据架构较大时,部署多个数据着陆区是很好的考虑。您可以通过向架构添加更多的着陆区来扩展数据网格架构,以满足不同领域的共同需求。这些着陆区使用虚拟网络对等连接到数据管理着陆区和所有其他着陆区。这种模式允许您跨区域共享数据集和资源。通过划分成单独的区域,您可以将工作负载分散到 Azure 订阅和资源中。这种方法允许您有机地实现数据网格
需要不同数据管理区的大型企业
最后一个部署选项是面向在全球范围内运营的超大型企业。在这种情况下,组织的不同部门之间可能会有截然不同的数据管理需求。为了解决这个问题,您可以一起部署多个数据管理着陆区和数据着陆区。这种架构如下图所示:

用于对比数据管理需求的多个数据管理着陆区
需要强调的是,多个数据管理着陆区,应该证明额外开销和集成复杂性的合理性。例如,一个额外的数据管理登陆区对于不允许其他人看到组织的(元数据)数据的情况是有意义的。
包扎
向数据网格的过渡主要是一种文化转变,伴随着细微差别、权衡和考虑。这种转变可能相当困难和乏味,因此我建议在迁移和使数据域独立之前,通过合并数据网格原则慢慢开始。
对于技术部分,我推荐使用加速器。您可以利用 Azure 数据管理和分析场景来获得最佳实践和可执行资源。blogpost 中描述的参考架构可以作为启动实施的起点。
实施深度卷积 GAN
原文:https://towardsdatascience.com/implementing-deep-convolutional-gan-f76e9b9af270

文森特·梵高的《红色葡萄园》
据纽约时报报道,数据中心使用的能源有 90%被浪费了,这是因为公司收集的大部分数据从来没有以任何形式被分析或使用,这更具体地被称为黑暗数据。
暗数据是通过各种计算机网络操作获取的数据,但不以任何方式用于获取见解或制定决策。一个组织收集数据的能力可能会超过其分析数据的能力。在某些情况下,组织可能甚至没有意识到数据正在被收集。IBM 估计,大约 90%由传感器和模数转换产生的数据从未被使用过。— 维基百科上的黑暗数据定义
从机器学习的角度来看,这些数据对于获得任何见解都没有用的一个关键原因是缺乏标签。这使得无监督学习算法非常有吸引力,可以释放这些数据的潜力。
生成对抗网络
2014 年,Ian Goodfellow 等人提出了一种通过对抗过程来估计生成模型的新方法。它涉及同时训练两个独立的模型,一个生成器模型试图对数据分布进行建模,一个鉴别器试图通过生成器将输入分类为训练数据或虚假数据。
这篇论文在现代机器学习领域树立了一个非常重要的里程碑,为无监督学习开辟了新的途径。深度卷积 GAN 论文(拉德福德等人 2015)通过应用卷积网络的原理成功产生 2D 图像,继续构建这一思想。
通过这篇文章,我试图解释本文中的关键组件,并使用 PyTorch 实现它们。
甘有什么了不起的地方?
为了理解 GAN 或 DCGAN 的重要性,让我们看看是什么让它们如此受欢迎。
- 由于大部分真实世界的数据是未标记的,GANs 的无监督学习特性使它们成为这种用例的理想选择。
- 生成器和鉴别器对于具有有限标记数据的用例来说是非常好的特征提取器,或者生成额外的数据来改善次级模型训练,因为它们可以生成假样本而不是使用增强。
- GANs 提供了最大似然技术的替代方案。它们的对抗性学习过程和非启发式成本函数使它们对强化学习非常有吸引力。
- 围绕 GAN 的研究非常有吸引力,其结果也是关于 ML/DL 影响的广泛辩论的来源。例如,Deepfake 是 GAN 的应用之一,它可以将人的面部覆盖在目标人物上,这在本质上一直是非常有争议的,因为它有可能被用于邪恶的目的。
- 最后但也是最重要的一点是,与它一起工作很酷,该领域的所有新研究都令人着迷。
体系结构

深度卷积 GAN 的架构(图片由作者提供)
正如我们之前所讨论的,我们将通过 DCGAN 来实现 GAN 的核心思想,用于能够生成逼真图像的卷积网络。
DCGAN 由两个独立的模型组成,一个是生成器(G ),它试图模拟随机噪声向量作为输入,并试图学习数据分布以生成伪样本,另一个是鉴别器(D ),它获取训练数据(真实样本)和生成的数据(伪样本),并试图对它们进行分类,这两个模型之间的斗争就是我们所说的对抗性训练过程,其中一个人的损失是另一个人的利益。
发电机

生成器图(图片由作者提供)
生成器是我们最感兴趣的一个,因为它是一个生成假图像来试图欺骗鉴别器的生成器。
现在让我们更详细地看看生成器架构。
- 线性层:噪声向量被送入一个全连接层,其输出被整形为 4D 张量。
- 批量标准化层:通过将输入标准化为零均值和单位方差来稳定学习,这避免了像消失或爆炸梯度这样的训练问题,并允许梯度在网络中流动。
- 上采样层:根据我对这篇论文的理解,它提到了使用上采样,然后在其上应用一个简单的卷积层,而不是使用卷积转置层来上采样。但我见过有人用卷积转置,你自己决定吧。
- 2D 卷积层:当我们对矩阵进行上采样时,我们让它通过一个步长为 1 且填充相同的卷积层,以允许它从上采样数据中学习
- ReLU Layer: 论文提到使用 ReLU 代替 LeakyReLU 用于生成器,因为它允许模型快速饱和并覆盖训练分布的颜色空间。
- TanH 激活:论文建议我们对发电机输出使用 TanH 激活函数,但没有详细说明原因,如果我们不得不猜测这是因为 TanH 的性质允许模型更快地收敛。
第 2 层至第 5 层构成核心生成器模块,可重复 N 次以获得所需的输出图像形状。
这里是我们如何在 PyTorch 中实现它。

用 PyTorch 实现的生成器。(作者代码)
鉴别器

鉴别器图(图片由作者提供)
现在,鉴别器更像是一个图像分类网络,只做了一些小的调整,例如,它不使用任何池层来进行下采样,而是使用步长卷积层来学习自己的下采样。
让我们更详细地看看鉴别器架构。
- 串联层:该层将假图像和真实图像组合在一批中,以馈入鉴别器,但这也可以单独进行,仅用于获取发电机损耗。
- 卷积层:我们在这里使用步幅卷积,它允许我们对图像进行下采样,并在一次通过中学习过滤器。
- LeakyReLU: 正如论文提到的,与原始 GAN 论文的 max-out 函数相比,它发现 LeakyReLU 对鉴别器有用,因为它允许更容易的训练。
- 退出:仅用于训练,有助于避免过度适应。该模型具有记忆真实图像数据的趋势,并且训练可能在该点崩溃,因为鉴别器不能再被生成器愚弄
- 批量规范化:文中提到在除第一个鉴别器块之外的每个鉴别器块的末尾应用批量规范化。文中提到的原因是在每一层上应用批量归一化会导致样本振荡和模型不稳定。
- 线性:一个完全连接的图层,它从应用的 2D 批处理规范化图层中提取一个整形后的矢量。
- Sigmoid 激活:当我们处理鉴别器输出的二进制分类时,进行 Sigmoid 层逻辑选择。
第 2 层到第 5 层构成了核心鉴别器块,它可以重复 N 次,以使每个训练数据的模型更加复杂。
这里的是我们如何在 PyTorch 中实现它。

用 PyTorch 实现的鉴别器(代码由作者编写)
对抗训练
我们训练鉴别器(D)以最大化将正确标签分配给训练样本和来自生成器(G)的样本的概率,这可以通过最小化 log(D(x))来完成。我们同时训练 G 使 log(1D(G(z))最小,其中 z 是噪声矢量。换句话说,D 和 G 用价值函数 V (G,D)玩以下两人极大极小游戏:

对抗成本函数(来源
实际上,上述等式可能无法为 G 提供足够的梯度来学好。在学习早期,当 G 很差时,D 可以以很高的置信度拒绝样本,因为它们与训练数据明显不同。这种情况下,log(1D(G(z))会饱和。我们可以训练 G 使 logD(G(z))最大化,而不是训练 G 使 log D(G(z))最小化。这个目标函数导致 G 和 D 的动态的相同的固定点,但是在学习的早期提供更强的梯度。——来源
由于我们同时训练两个模型,这可能会很棘手,而 GANs 是出了名的难以训练,我们稍后将讨论的一个已知问题称为模式崩溃。
论文建议使用学习率为 0.0002 的 Adam 优化器,如此低的学习率表明 GANs 往往很快发散。它还使用值为 0.5 和 0.999 的一阶和二阶动量来进一步加速训练。该模型初始化为具有零均值和 0.02 标准差的正态权重分布。
这里是我们如何为此实现一个训练循环。

DCGAN 的训练循环(由作者编写代码)
模式崩溃
理想情况下,我们希望我们的生成器生成各种各样的输出,例如,如果它生成一张脸,它应该为每个随机输入生成一个新的脸。但是,如果发生器产生一个看似合理的输出,足以骗过鉴别器,它可能会一次又一次地产生相同的输出。
最终,发生器对单个鉴别器过度优化,并在一小组输出之间旋转,这种情况称为模式崩溃。
以下方法可用于补救这种情况。
- Wasserstein 损失:wasser stein 损失通过让您将鉴别器训练到最佳状态来减轻模式崩溃,而不用担心消失梯度。如果鉴别器没有陷入局部最小值,它会学习拒绝发电机稳定的输出。所以,发电机必须尝试新的东西。
- 展开的 gan:展开的 gan 使用生成器损耗函数,该函数不仅包含当前鉴别器的分类,还包含未来鉴别器版本的输出。因此,生成器不能对单个鉴别器进行过度优化。
应用
- 风格转移:面部修饰应用程序现在都是炒作,面部衰老、哭脸和名人脸叠加只是社交媒体上已经广泛流行的一些应用程序。
- 视频游戏:3D 物体的纹理生成和使用图像的场景生成只是帮助视频游戏行业更快开发更大游戏的一些应用。
- 电影行业: CGI 已经成为模范电影院的一个重要组成部分,凭借甘带来的潜力,电影制作人现在可以比以往任何时候都有更大的梦想。
- 语音生成:一些公司正在使用 GANs 来改进文本到语音的应用,通过使用它们来生成更真实的声音。
- 图像恢复:利用 GANs 对被破坏的图像进行去噪和恢复,对历史图像进行着色,通过产生丢帧来改善旧视频,提高其帧率。
结论
GAN 和 DCGAN 是一篇里程碑式的论文,在无监督学习方面开辟了新的途径。对抗性训练方法提供了一种新的训练模型的方法,这种方法可以很好地模拟真实世界的学习过程。观察这一领域的发展将会非常有趣。
希望你喜欢这篇文章。
你可以在我的 GitHub 上找到完整实现
你可以在 Linkedin 上关注我
你可以阅读我在 媒体上的其他文章
用 Python 从头开始实现期望最大化算法
通过从头开始构建 EM 算法来揭开 EM 算法的神秘面纱

混合模型。使用 Tableau 创建。
期望最大化(EM)算法是一种统计机器学习方法,用于寻找具有未知潜在变量的模型的最大似然估计。我相信这句话对你们中的一些人来说是没有意义的。不要担心!本文旨在揭开 EM 算法的恐怖方程式和令人困惑的词汇的神秘面纱。不幸的是,没有适当的数学,人们就不能真正理解 EM 算法的本质。所以这就是我要做的。本文的每一部分都将包括一个全数学部分和一个你需要知道的一切部分。完整数学部分包括算法的完整推导,而所有你需要知道的部分总结了没有推导的基本细节。按照全数学部分,我会假设你熟悉概率论、统计理论和基础微积分。在你需要知道的一切部分仍会有一些数学知识,但不会那么复杂。
要理解算法,首先要理解我们要处理的数据。EM 算法处理来自多个聚类(即混合模型)的数据。每个分类都有数据来自该分类的概率,但是我们不知道这些概率。每个数据簇都有它的分布,这是我们知道的,但我们不知道分布参数。请参见下面泊松混合模型的示例。

在本例中,有两个集群。 Zᵢ 表示数据的簇 Xᵢ 。π表示来自某个集群的概率。

因此,所有π值的总和应该等于 1。对于本教程,我将假设这些参数值。

如果数据点来自第一个聚类,则它来自具有参数λ = 2的泊松分布,如果数据点来自第二个聚类,则它来自具有λ = 6的泊松分布。请记住,我们不知道这些参数值,并试图估计它们。
EM 算法的目的是找到这个混合模型的参数估计值。在上面的例子中,EM 算法会估计出 π₁、π₂、λ₁和λ₂ 。 π 是聚类的概率, λ 是泊松分布的参数。因此,如果聚类的分布是高斯(正态)而不是泊松,EM 算法将估计 μ 和 σ 而不是 λ。让我把这个泊松 pdf 函数留在这里。请调整它以适合你自己想要的分布。
您可以更改它以适合您的发行版。
实际上,用户想要多少个集群就有多少个集群,每个集群的分布可以是我们想要的任何形式。我将再次提醒您,我们不知道任何参数值,也不知道每个数据点来自哪个集群。这种未知的聚类分配就是聚类被称为潜在变量的原因。EM 算法对于处理这种不可观察的变量非常有用。
由于 EM 算法是迭代的,我将使用θ来表示新的参数估计,使用θ⁰来表示先前的迭代估计。其他一些变量;n 表示样本总数,K 表示聚类总数。
您应该跳到第 4 节。算法直接进入揭秘部分,或者如果你正在寻找算法的非数学总结。
1.完全对数似然的期望
完全对数似然相当于数据的对数似然。我们使用术语“完全”是因为,在后面的章节中,将有另一个统计称为“不完全对数似然性”。对于本节,我将仅使用来互换使用“完全对数似然和“对数似然”。
全数学
如果你对推导不感兴趣,跳到你需要知道的一切部分。
为了找到一些数据的分布参数,我们通常采用最大化该数据可能性的参数。EM 算法在这里应用相同的概念,试图最大化完全可能性。

完全可能性
我们可以将上面的公式展开如下,

完全可能性
指数函数是一个指示函数,如果里面的条件为真,则返回 1,否则返回 0。为了简化后面的计算,我们希望这个指示函数不在指数中。让我们来看看可能性的对数。

完全对数似然
这是最大化的一种更直接的形式。不幸的是,我们无法计算这个对数似然,因为我们不知道每个数据来自哪个集群。因此,我们不知道指标函数的输出和要使用的分布参数。这个问题的解决方案是使用对数似然的期望值,而不是实际的对数似然。让我们将对数似然的期望值定义如下:

对数似然的期望
对数似然的期望用后验概率代替指示函数。我们将在后面的章节中计算后验概率的值。
对于我们上面的泊松例子,期望变成如下,

对数似然的泊松期望
你只需要知道
关于完全对数似然,你需要知道的只是我们想要找到使其最大化的参数。不幸的是,由于未知的聚类,很难计算完整的对数似然。为了解决这个问题,我们计算对数似然的期望值并最大化它,而不是实际的对数似然。
完全对数似然的期望被表示为 Q(θ,θ⁰) 。下面给出了 Q(θ,θ⁰) 的公式。

完全对数似然的期望
对于我们的泊松例子,这变成了,

对数似然的泊松期望
现在,这就是你需要知道的关于完全对数似然的期望。
2.后验概率
全数学
如果你对推导不感兴趣,跳到你需要知道的一切部分。
后验概率是给定数据点的值和当前迭代中的参数,从聚类中获得特定数据点的概率。这不要与π表示的概率相混淆。让我们来定义它们,

后验定义
如果你意识到我使用了条件概率定义来得到这个,这将会有所帮助。我们可以将这个表达式扩展为:

我不会深入我在这里应用的概率属性,但它是上面的精确条件概率定义。如果你注意到,分子是数据和聚类的似然性。我们在前面的章节中已经看到了这些概率。分母只是所有聚类可能性的总和。
对于泊松例子,后验概率变成,

泊松后验公式
注意,用于计算后验概率的参数是前一次迭代的参数。因此,在完全对数似然的期望中,后验概率将被视为常数。
你只需要知道
关于后验概率,你只需要知道,它是在给定当前数据和参数的情况下,获得一个聚类的概率。我们需要这个概率来计算完全对数似然的期望。值得庆幸的是,这并不太难找到,所以我只是给出了公式。

后验概率
这个公式很简单,因为我们已经知道了一切。分子是数据和聚类的似然性,而分母是相同数据和所有可能聚类的似然性之和。

泊松后验公式
在前面的例子中,我们只有两个可能的集群。因此,该示例的第一聚类的后验概率正好是第一聚类的似然性除以第一聚类的似然性和第二聚类的似然性之和。

k = 1 时的泊松后验公式
我希望你能把这个例子推广到其他例子。
Python 代码
以泊松混合为例,下面是一个计算后验概率的函数。
上面的函数返回列表的列表,其中每个内部列表表示一个聚类,内部列表的内容是后验概率。试着把这个 Python 代码和上面的泊松后验公式图片匹配一下。
3.最大化
全数学
如果你对推导不感兴趣,跳到你需要知道的一切部分。
最大化是指通过最大化完全对数似然的期望值来获得参数。我们通过找到期望值的导数并将其设置为零来实现这一点。让我们回到公式。

对数似然的泊松期望
我将使用这个符号来表示后验概率,以简化方程。

我们将从寻找最优π₁.开始因为我们只有两个集群,我们可以用 1 - π₁.来代替π₂现在让我们删除 Q(θ,θ⁰) 中不包含π₁.的所有项

我们被允许这样做,因为我们将要推导这个关于π₁.的方程这意味着所有其他不包含π₁的项都将变成 0,所以让我们把它们去掉,这样方程就更容易理解了。下一步是扩展内在西格玛。

此时,求导就相对简单了。

将导数设置为 0 将产生以下结果。

用一些简单的代数,解出π₁会让我们,

最佳配方
对这个结果的解释是,利用当前获得的后验概率,我们可以将π₁更新为这个方程的任何结果。
我不会为λ重复这个过程,因为它太长了。如果你有兴趣,请看我的计算这里。或者,访问您需要知道的全部信息部分,仅获取最终估计值。
你只需要知道
关于最大化过程,你需要知道的是,为了获得参数估计,我们需要最大化完整的对数似然期望。让我们回到公式。

对数似然的泊松期望
最大化的方法是对每个参数取 Q(θ,θ⁰) 的导数,并将其设置为 0。请记住,在求导时,后验概率是常数,因为它是使用前一次迭代的估计值计算的。
我将向你们展示泊松例子的推导结果。

最佳配方
因此,为了获得每次迭代的新参数估计,我们需要计算这些方程。
Python 代码
使用泊松混合的例子,下面是计算最佳参数估计的函数。
上面的函数分别返回π和λ估计值的两个列表。尝试将此 Python 代码与上面的最佳公式图片匹配。
4.算法
到目前为止,我们还没有了解到这篇文章的神秘之处。我知道一切可能还是很模糊。因此,让我们总结一下我们所知道的一切,并让算法发挥作用。
EM 算法的步骤:
- 初始化随机参数值。
- 导出完全对数似然的期望, Q(θ,θ⁰).
- 计算后验概率。
- 给定后验概率,通过对每个参数求微分 Q(θ,θ⁰) w.r.t 找到最优参数,设置导数为 0,求解参数。
- 将当前参数更改为步骤 4 中获得的参数。
请注意,在步骤 5 中更改参数估计后,重新计算后验概率会产生不同的结果(见泊松后验公式图)。但是如果后验概率现在不同了,那么重新进行第 4 步肯定会产生不同的参数估计(见最佳公式图)。这就是 EM 算法的本质。让我们用英语重复一遍。EM 算法是,
- 使用当前参数,计算后验概率。
- 使用当前后验概率,更新参数。
我们一遍又一遍地重复这些步骤。这并不神秘,不是吗?当然,数学可能仍然很重,但算法的本质就是这两个小步骤。让我们试着把这个翻译成代码。
现在,不需要理解每个函数的内容,这个 python 代码应该能够突出 EM 算法的核心。它的核心是数学和复杂的,但算法很简单。
剩下的唯一未解决的难题是停止标准。既然我们已经了解了每个迭代中完成了什么,我们想知道什么时候停止迭代。下一节将解释这个停止标准。
5.不完全对数似然
在这一节中,我将介绍不完全对数似然的概念。不完全对数似然的概念类似于完全对数似然。我将不讨论数学细节,但本质上,不完全对数似然不关心数据聚类。它计算所有聚类的可能性,并将它们相加。而完全对数似然性仅取数据来源的聚类的似然性。

不完全对数似然
我要把这个公式翻译成 Python。
上面的函数返回不完全对数似然的值。
由于一些我不会探究的性质,不完全对数似然性将总是在每次迭代中增加。当不完全对数似然的变化不再显著时,EM 算法迭代停止 。
5.摘要
让我们把一切都编码。
该函数的输出是最终的参数估计。
现在你可能想知道为什么我说 EM 算法是一种机器学习方法,而它显然是一种估计方法。我们一直在计算后验概率,我们可以用它来获得每个数据点的聚类。我们通过计算属于每个聚类的数据点的后验概率并将该类分配给具有最高概率的聚类来做到这一点。因此是一种无监督的机器学习方法。
上述参数lambdas和probs指的是来自 EM 算法的最终估计值。运行这个函数将返回一个 NumPy 数组的数据簇X。
6.恭喜
您已成功完成这篇文章。如果你设法理解并重现了算法,我会为你鼓掌。但如果不是,这是一个高级算法,通过更多的练习你会得到正确的答案。查看我在 GitHub 上的完整代码,了解如何在实际应用中使用我们的函数。如果你喜欢这篇文章,请考虑鼓掌并关注。请在评论中分享你的反馈和想法。感谢您的阅读!
在 10 分钟内实现 FastAPI
原文:https://towardsdatascience.com/implementing-fastapi-in-10-minutes-d161cdd7c075
开发、测试和使用您的自定义 API

Levi Elizaga 在 Unsplash 上拍摄的照片
API(应用编程接口) 是计算机程序之间的连接。通俗地说,我们可以把 API 想象成一个 Python 函数,用户“调用一个 API”就可以执行一个任务或者得到一定的结果。一个流行的例子是把 API 比作一个服务员,你告诉服务员你从菜单中选择的订单,服务员将把它传达给厨房(一个“系统”),厨房准备你的订单,服务员将把你完成的订单返回给你。
API 是有价值的,可以货币化——现在几乎所有东西都是 API!想知道天气预报,网站界面调用一个天气 API 。如果你想知道从一个地方到另一个地方的最短路线,谷歌地图网站/移动应用程序调用谷歌方向服务 API 。你明白了。
可以使用多种框架在 Python 中实现 API,其中一些框架包括 Django、Flask 或 FastAPI。本文是关于如何使用 FastAPI 框架开发、测试和使用自定义 API 的分步指南。如果您已经对 API 有所了解,可以跳过前两节。
更新 :本文是系列文章的一部分。查看其他“10 分钟内”话题 此处 !
目录
HTTP 方法和状态代码
重要的是要知道 API 可以执行什么样的任务,并为正确的任务选择正确的 HTTP 方法。回到服务员的例子,HTTP 方法将菜单上的项目比作“开胃菜”、“主菜”和“甜点”是菜单的一部分,但是像“Bedsheet”这样的东西不属于菜单,也不能订购。
有 5 种流行的 HTTP 方法,即GET、POST、PUT、PATCH和DELETE,可以用来管理资源的状态。
GET:检索现有资源(只读)POST:创建新资源PUT:更新现有资源PATCH:部分更新现有资源DELETE:删除资源
用 HTTP 方法 处理一个请求后,响应中会包含一个 HTTP 状态码 。一般来说,状态代码的含义是这样的,
2xx:操作成功3xx:重定向4xx:客户端错误5xx:服务器错误
状态代码的完整列表可在处找到。查看状态代码以确保操作成功执行是很重要的,如果有错误,则进行调试,并将适当的结果返回给用户。
为什么选择 FastAPI?
FastAPI 于 2019 年推出,晚于 Django (2006)和 Flask (2011)等流行框架,并因其快速和高性能而受到欢迎。与 Flask 框架相比,FastAPI 有以下优点:
- Asyncio for concurrency:用关键字
async和await调用 - 用于数据验证的 Pydantic:在运行时实施模式并检测数据类型
- 用于自动化文档生成的 Swagger UI:支持使用扩展名 /docs 测试 API 端点。文档也有助于理解。Swagger UI 的一个替代品是 Redoc,可以用扩展 /redoc 访问。
- 安全和认证功能
FastAPI 设置
FastAPI 很容易设置,只需在终端上运行以下命令。
**$** pip install fastapi
**$** pip install uvicorn[standard]
事不宜迟,让我们开始编写一些 API 吧!
用 FastAPI 编写 API
实现 API 遵循相同的整体结构,
- 定义一个 API 对象,如
app = FastAPI() - 使用 Pydantic 定义数据类型
- 将 HTTP 方法和路径映射到相关的 Python 函数。前面提到的不同的 HTTP 方法可以用
@app.get()、@app.post()、@app.put()等来调用。分别与可插入的路径耦合为@app.get("/path") - 启动 API 应用程序,在命令行上运行
uvicorn main:app --reload,其中main指的是 Python 文件名,app指的是在步骤 1 中创建的 API 对象
下面是 FastAPI 实现演示的代码片段,
以下部分将引用上面的代码片段,请确保您理解该代码片段!
测试 API
成功启动 API 应用程序后,控制台日志将打印托管 API 的 URL。通常在http://127.0.0.1:8000举办。主页遵循主"/"路径中定义的内容(第 14–16 行)。

图 API 主页——作者图片
我们可以用定义的路径测试各种端点。在本例中,我正在测试/path扩展(第 19–21 行)。注意,输入路径扩展名会执行一个GET请求。

图 API 的扩展/路径——作者图片
类似地,我创建了一个/path/{path_id}扩展(第 29–31 行),其中path_id是一个int值。如果我输入一个非整数值,将会返回一条错误消息,指出我的类型无效。

图 API 端点的类型检查——作者图片
上面的例子都是在执行一个GET请求,要执行一个POST请求,我们可以通过进入 /docs 路径扩展并测试一个示例POST请求来使用 Swagger UI。

图 Swagger UI 主页——作者图片
或者,我们可以使用一个curl命令来执行一个POST请求。Swagger UI 提供了确切的curl命令来运行,

图 5:在 Swagger UI 上测试 POST 请求——作者图片
恭喜您,您现在已经开发并测试了您自己的自定义 API!我们现在来看看如何在 Python 脚本中实现它,因为 API 的最终用户可能并不总是使用 curl 命令或 Swagger UI。
使用 API
要在 Python 脚本中访问和使用您的 API,请使用requests库,即requests.get()或requests.post()分别用于GET和POST方法。输入参数包括表示 API 端点的url和表示输入字典的json。
结果存储在res对象中,包含res.json()中的输出和res.status_code中的 HTTP 状态代码。

图 6:在 Python 脚本中使用 API 作者图片
希望本文介绍了什么是 API,以及如何开发、测试和使用 FastAPI 框架。展望未来,探索如何将您的 API 与后端数据库连接起来,或者甚至在线托管您的 API,以便更多用户可以使用它(不仅仅是在您的本地笔记本电脑上!).
感谢您的阅读!如果你喜欢这篇文章,请随意分享。
相关链接
fastapi文档:https://fastapi.tiangolo.com/
fastapi官方 GitHub:https://github.com/tiangolo/fastapi
从头开始在 Python 中实现梯度下降
原文:https://towardsdatascience.com/implementing-gradient-descent-in-python-from-scratch-760a8556c31f
从头开始用代码实现梯度下降算法,了解它的工作原理。

作者图片(使用 python 中的 matplotlib 创建)
机器学习模型可能有几个特征,但是一些特征可能比其他特征对输出有更高的影响。例如,如果模型预测公寓价格,公寓的位置对输出的影响可能比公寓建筑的层数更大。因此,我们提出了权重的概念。每个特征都与一个权重(一个数字)相关联,即特征对输出的影响越大,与之相关联的权重就越大。但是,如何决定应该给每个特征分配多大的权重呢?这就是梯度下降的用武之地。
梯度下降是一种优化算法,可以帮助您找到模型的最佳权重。它通过尝试各种权重并找到最适合模型的权重来实现,即最小化成本函数。成本函数可以定义为实际产量和预测产量之间的差异。因此,成本函数越小,模型的预测输出就越接近实际输出。成本函数在数学上可以定义为:
**𝑦=𝛽+θnXn,**
where x is the parameters(can go from 1 to n), 𝛽 is the bias and θ is the weight
而另一方面,梯度下降的学习率被表示为 α。学习率是每个梯度所走的步的大小。虽然较大的学习率可能会给我们的 𝛽 和 θ 的优化值较差,但学习率也可能太小,这需要大量增加获得收敛点(𝛽和θ的最佳值点)所需的迭代次数。该算法给出α 、 𝛽和θ的值作为输出。
如果你想更详细的了解梯度下降和代价函数,我推荐这篇文章。
现在我们知道了什么是梯度下降以及它是如何工作的,让我们开始用 python 实现它。但是,在我们开始相同的代码逻辑之前,让我们先来看看我们将要使用的数据和为了我们的目的将要导入的库。
对于这个实现,我们将使用广告数据集。这是一个数据集,它给出了不同产品在电视、广播和报纸上销售后的总销售额。使用我们的算法,我们可以找出哪种媒体对我们的销售表现最好,并相应地为所有媒体分配权重。该数据集可从以下链接下载:
转到导入,我们将使用库 pandas 和 numpy,它们将用于读取数据以及对数据执行数学函数。我们还将使用 matplotlib 和 seaborn 来绘制我们的发现,并以图形方式解释结果。一旦我们导入了库,我们将使用 pandas 来读取我们的数据集,并打印前 5 列以确保数据被正确读取。一旦我们读取了数据,我们将为我们的梯度下降曲线设置 X & Y 变量。
import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport seaborn as sndf=pd.read_csv('Advertising.csv')df.head()
输出:

按作者分类的图像(数据集的前 5 行)
x 代表电视、广播和报纸,而 Y 代表我们的销售。由于所有这些销售可能在不同的规模,然后我们正常化我们的 X & Y 变量。
X=df[[‘TV’,’Radio’,’Newspaper’]]Y=df[‘Sales’]Y=np.array((Y-Y.mean())/Y.std())X=X.apply(lambda rec:(rec-rec.mean())/rec.std(),axis=0)
一旦我们有了一个标准化的数据集,我们就可以开始定义我们的算法。要实现梯度下降算法,我们需要遵循 4 个步骤:
- 随机初始化偏差和权重θ
- 给定偏差和权重,计算 Y 的预测值
- 根据 Y 的预测值和实际值计算成本函数
- 计算梯度和权重
首先,我们将为偏差和权重取一个随机值,它可能实际上接近最佳偏差和权重,也可能离得很远。
import randomdef initialize(dim):b=random.random()theta=np.random.rand(dim)return b,thetab,theta=initialize(3)print(“Bias: “,b,”Weights: “,theta)
输出:
Bias: 0.4439513186463464 Weights: [0.92396489 0.05155633 0.06354297]
这里,我们创建了一个名为 initialise 的函数,它为我们提供了一些偏差和权重的随机值。我们使用随机库来给出符合我们需要的随机数。下一步是使用这些权重和偏差计算输出(Y)。
def predict_Y(b,theta,X):return b + np.dot(X,theta)Y_hat=predict_Y(b,theta,X)Y_hat[0:10]
输出:
array([ 3.53610908, 0.53826004, 1.4045012 , 2.27511933, 2.25046604, 1.6023777 , -0.36009639, -0.37804669, -2.20360351, 0.69993066])
Y_hat 是预测的输出值,而 Y 将是实际值。这两者之间的差异将给出我们的成本函数。这将在我们下一个函数中计算。
import mathdef get_cost(Y,Y_hat):Y_resd=Y-Y_hatreturn np.sum(np.dot(Y_resd.T,Y_resd))/len(Y-Y_resd)Y_hat=predict_Y(b,theta,X)get_cost(Y,Y_hat)
输出:
0.5253558445651124
这是我们的成本函数,我们的目的是尽可能地减少这个,以获得最准确的预测。为了获得更新的偏差和权重,我们使用梯度下降公式:

图片作者(更新的 theta 公式),
传递给该函数的参数有
- x,y:输入和输出变量
- y_hat:带有当前偏差和权重的预测值
- b_0,θ_ 0:电流偏置和权重
- 学习率:调整更新步长的学习率
def update_theta(x,y,y_hat,b_0,theta_o,learning_rate):db=(np.sum(y_hat-y)*2)/len(y)dw=(np.dot((y_hat-y),x)*2)/len(y)b_1=b_0-learning_rate*dbtheta_1=theta_o-learning_rate*dwreturn b_1,theta_1print("After initialization -Bias: ",b,"theta: ",theta)Y_hat=predict_Y(b,theta,X)b,theta=update_theta(X,Y,Y_hat,b,theta,0.01)print("After first update -Bias: ",b,"theta: ",theta)get_cost(Y,Y_hat)
输出:
After initialization -Bias: 0.4733071000028529 theta: [0.56745402 0.43024717 0.0722811 ]
After first update -Bias: 0.46384095800279584 theta: [0.57117721 0.43202382 0.07171437]0.37245638135702513
如我们所见,新的偏差和权重降低了成本函数。目前我们将学习率定为 0.001,我们也将尝试 0.01,看看它是否能成为更好、更优的学习率。
现在我们已经创建了所有需要的函数,我们可以创建一个主梯度下降函数,它运行它们一个特定的迭代次数,并为我们找到最佳的偏差和权重。对于这个实现,我们运行这个函数 200 次迭代。
def run_gradient_descent(X,Y,alpha,num_iterations):b,theta=initialize(X.shape[1])iter_num=0gd_iterations_df=pd.DataFrame(columns=[‘iteration’,’cost’])result_idx=0for each_iter in range(num_iterations):Y_hat=predict_Y(b,theta,X)this_cost=get_cost(Y,Y_hat)prev_b=bprev_theta=thetab,theta=update_theta(X,Y,Y_hat,prev_b,prev_theta,alpha)if(iter_num%10==0):gd_iterations_df.loc[result_idx]=[iter_num,this_cost]result_idx=result_idx+1iter_num +=1print(“Final Estimate of b and theta : “,b,theta)return gd_iterations_df,b,thetagd_iterations_df,b,theta=run_gradient_descent(X,Y,alpha=0.001,num_iterations=200)
输出:
Final Estimate of b and theta : 0.31516040347417285 [0.39731522 0.31571747 0.61334115]
theta 是 3 个数字的列表,因为我们有 3 个输入,电视,广播和报纸。如果我们打印每次迭代的成本函数,我们可以看到成本函数的减少。我们也可以绘制迭代的成本函数来查看结果。
gd_iterations_df[0:10]
输出:

按作者分类的图像(迭代的输出)
%matplotlib inlineplt.plot(gd_iterations_df[‘iteration’],gd_iterations_df[‘cost’])plt.xlabel(“Number of iterations”)plt.ylabel(“Cost or MSE”)
输出:

按作者分类的图像(成本函数与迭代)
正如我们可以看到的,成本函数随着迭代次数的增加而减少,但我们仍然没有达到收敛。现在,让我们尝试使用α=0.01 进行 2000 次迭代,并将其与α=0.001 进行比较,找出哪个学习率对于该数据集更好。
alpha_df_1,b,theta=run_gradient_descent(X,Y,alpha=0.01,num_iterations=2000)alpha_df_2,b,theta=run_gradient_descent(X,Y,alpha=0.001,num_iterations=2000)plt.plot(alpha_df_1[‘iteration’],alpha_df_1[‘cost’],label=”alpha=0.01")plt.plot(alpha_df_2[‘iteration’],alpha_df_2[‘cost’],label=”alpha=0.001")plt.legend()plt.ylabel(‘cost’)plt.xlabel(‘Number of iterations’)plt.title(‘Cost Vs. Iterations for different alpha values’)
输出:

作者图片
可以看出,0.01 是更优的学习速率,因为它比 0.001 收敛得更快。0.01 在 100 大关附近收敛,而 0.001 需要 1000 次迭代才能达到收敛。
因此,我们成功地在 python 上构建了梯度下降算法。记住,学习率的最佳值对于每个数据集都是不同的。
希望你今天学到了新的有意义的东西。
谢谢你。
用 SpaCy 实现 Hearst 模式
原文:https://towardsdatascience.com/implementing-hearst-patterns-with-spacy-216e585f61f8
上下位关系的自动提取
⚠️在这篇文章中,我将主要集中在赫斯特模式,实现和上位词提取的用法。然而,我将使用命名实体识别(NER)和专利数据集;所以我建议查看我在这个周期的前一篇帖子。
模式…模式无处不在…
为什么我们在自然语言处理的上下文中关心模式?因为它们大大减少和简化了工作,基本上,它是一个简单的模型。尽管处于变形神经网络时代,模式仍然是有益的。近 20 年来,上位词自动提取一直是一个活跃的研究领域。这是一个关键的工具,当应用于下游任务,如问题回答,查询,信息。提取等。
下义关系的有用性
上位词——…一个具有广泛意义的词,构成一个有更具体意义的词所属的范畴;
下位词——…是反义词;比适用于它的一般术语有更具体含义的词。
为了便于理解,我们举个例子:

在这里,“光盘”和“硬盘”是“存储单元”的的下位词。反过来,“存储单元”是“光盘”和“硬盘”的上位词。
这种词汇关系是自然语言处理任务的重要组成部分。这些任务的种类取决于目标,例如:
- 分类预测:为术语识别更广泛的类别,建立分类关系(如 WikiData GraphAPI)
- 信息提取(IE): 从文本中自动检索特定信息对于被搜索的实体来说是高度可靠的。
- 数据集创建:高级模型需要学习实例来识别实体之间的关系。
赫斯特图案
那么,我们如何检测和提取这样的关系呢?是时候谈谈计算语言学研究员马蒂·赫斯特的工作了。她最受欢迎的研究之一是建立一套测试模式,用于从文本中提取有意义的信息。这些图案俗称“赫斯特图案”。
我们可以将这种模式形式化为“X 是 Y”,其中 X 是上义位,Y 是下义位。这是赫斯特模型的众多模型之一。这里有一个列表,让你对这个想法有一个直觉:

图片作者|检测 hyper 的模式表。\rhyper 关系
这些表格模式按超和押韵(反向上位词)分类。通常,顺序并不重要,但有时它对训练信息抽取系统很有帮助。
你可能会说这种方法现在看起来过时了,过于简单了,我们可以使用 ML 和复杂的模型。
但是,这并不完全正确!
在来自 FB(Meta)研究小组的 这篇论文中,他们表明
“…在通用基准数据集上,简单的基于模式的方法始终优于分布式方法。”
有时候,可靠的老工具已经足够了,🛠
空间实现
从理论走向实践。通常,您不想提取所有可能的下位词关系,而只想提取特定领域中的实体。对特定领域中实体的识别被称为 NER。目前最简单的方法是使用空间。有了这个库,你可以训练一个定制的 NER 模型来识别比默认域更具体的域。

图片由作者提供|来自我之前帖子的自定义 NER 模型的结果
数据
例如,我将使用专利的G06K(数据的识别/数据的呈现)小节中的专利文本。最重要的是,我训练了一个定制的 NER 模型来识别技术术语。我在我的上一篇文章中详细描述了这个数据集。
⚠️数据不受版权保护,可安全用于商业目的。根据USPTO:除了 37 CFR 1.71(d) & (e)和 1.84(s)中反映的有限例外,专利的文本和图纸通常不受版权限制 。”
履行
在 SpaCy 内部创建模式非常简单。由于我们使用的是 NER 模型,我们可以依靠识别来过滤我们感兴趣的领域之外的实体。
可以用 JSON 格式创建模式。这里有一个基于空间的规则匹配文档的例子。

作者图片| JSON 格式的模式示例
你可以看到,通过指定ENT_TYPE,我们利用 NER 模型只匹配这个领域中的单词。
Python 上的实现非常简单。我们读取文本,初始化matcher,从 JSON 读取模式,并将它们添加到匹配器中。
将模式加载到空间匹配器的代码片段
简单地说,通过做matcher(doc),我们提取上位词关系的列表。连同提取的模式,我们得到了一些关于匹配的信息,比如模式的名称(在我们的例子中是 hyper\rhyper)以及它是否是一个多词关系。
在文本范围内使用匹配器的代码片段
多词模式
我们面临的最常见的问题是多词上位词关系。

图片由作者提供|可能的 hyper 示例。与多个实体的关系
由于匹配器不能识别一个模式下的各种实体,这里我们提出一个有用的提示;)
找到匹配的模式后,我们进一步检查句子中的其他实体。如果它们在我们的领域之下,并且位于连接词之间,那么这些词也是超韵关系的一部分。


图片由作者提供|多词关系匹配的可视化说明
代码中的主要技巧是我们用'连续词【T4]创建一个列表,并检查有多个实体匹配的句子。
多实体模式提取的代码片段
结果和注释
瞧✨ !我们提取了自定义域中的上位词关系。

图片作者|提取的 hyper 最终结果表。关系
您可以在此处找到完整的代码以及详细的笔记本和数据集:
https://github.com/kinivi/patent_ner_linking/blob/main/project.ipynb
即使我们已经有了结果,验证它们也是很好的。在这个“专利”系列的下一篇也是最后一篇文章中,我将展示如何使用 Wiki API 在任何自定义数据集上自动验证提取的上位词关系。敬请关注😉
确认
特别感谢我的团队: Marwan MASHRA 和 gatan serré。
从零开始实施 KNN
原文:https://towardsdatascience.com/implementing-knn-from-scratch-70c800f6f64b
从头做起
从零开始实施 KNN
用 Python 和 NumPy 分解一个基本的机器学习算法

乔恩·泰森在 Unsplash 上的照片
深入理解和欣赏机器学习算法的内部工作原理的最有效方法之一是从头开始实现它。
从头开始实现它迫使我们不仅要将算法分解成主要的计算步骤,还要理解数学描述。我们还获得了如何使用参数的宝贵见解。
在接下来的部分,我们将从头开始实现一个经典的机器学习算法: K 近邻。我们将使用 Python 和 NumPy 逐步实现它。
但是在讨论一些实现细节之前,我们需要打下基础,对算法有一个大致的了解。
概述
由于 1951 年首次开发的 K 近邻(KNN)是一种非参数学习算法。KNN 通常被认为是简单的,因为底层模型基本上不存在,仅仅由存储的训练数据集定义。
KNN 很大程度上依赖于距离的概念,通过计算新观察值与训练数据集的每一行之间的欧几里德距离,将新观察值与存储的训练数据进行比较。
在检索和排序距离之后,可以选择 k 个最近邻居(索引)。超参数 k 指定相关邻域的大小,必须手动调整。
k-最近邻可用于分类和回归任务。
分别返回类别成员的多个投票结果或 k 个最近值的平均值。
我们可以将算法的主要步骤描述如下:
- 计算新观测值与训练数据集的每一行之间的距离。
- 对距离进行升序排序(从最小到最大)并检索 k 个最近邻的索引。
- 获取相应的类标签并返回最常用的标签。
- 对每个新观察重复步骤 1-3。
在下一节中,我们将在一个类中实现主要步骤。整体结构可以通过下面的骨架类来描述,它将指导我们完成接下来的部分。
从头开始实施
在我们直接进入实际算法之前,我们需要打下一些基础——定义超参数 k 并拟合训练数据集,或者换句话说,存储完整的训练数据。这将为我们即将到来的计算做好准备。
现在,我们已经准备好继续学习那些更有趣的东西了。
计算距离
我们已经存储了完整的训练数据集,允许我们计算新观察和训练数据的每一行之间的距离。
为了计算距离,我们定义了一个辅助函数,返回每对行的欧几里德距离。
欧几里德距离就是两行之间的平方差的平方根(向量) —或者更正式地表述为以下等式:

helper 函数的实现相对简单,因为我们可以依靠 NumPy 来处理向量操作。
一旦定义好了,我们就可以调用 helper 函数,并在一个简单的列表中存储新观察和完整训练数据之间的所有距离。
获取 k-最近索引
接下来的步骤也很简单。我们需要按照升序对距离列表进行排序(从最小到最大),允许我们只选择 k 个最近的邻居。
借助下图,我们可以想到由 k 定义的选择半径:

k 选择半径的示例说明[图片由作者提供]
注意:k 值是一个重要的参数,因为大的 k 值扩大了半径,允许更好的泛化。另一方面,较小的 k 值提供了更大的灵活性,但更容易受到噪声的影响。我们可以使用可视化技术,如“肘法”来调整超参数。
我们可以使用内置的 NumPy 函数numpy.argsort()和 Python 切片符号对索引进行排序和选择。
返回最常用的标签
现在,我们只剩下一件事要做了—我们需要检索相应的类标签,并返回最常见的标签作为我们的预测。
基于 k-最近邻的索引,我们可以从我们的训练数据中选择相关联的标签,并将它们存储在列表中。
利用两个不同的 NumPy 函数——numpy.bincount()和numpy.argmax()——我们可以选择最常见的标签作为返回值。
预测并把它们放在一起
到现在为止,我们已经完成了大部分艰苦的工作。
我们需要做的最后一件事是对测试数据集中的每一行重复这些步骤,这就完成了我们的实现,并把它们放在一起。
测试分类器
完成我们的实现后,我们需要用真实的数据集来测试它。
为此,我们将使用虹膜数据集,该数据集由 150 个样本组成,具有 4 个不同的特征(萼片长度、萼片宽度、花瓣长度、花瓣宽度)。我们可以通过绘制前两个特征来可视化数据。

用k=3实例化我们的分类器,用n_splits=5进行交叉验证,我们得到了大约 96%的准确率——对于这样一个简单的算法来说,这绝对是不错的。
注意:由于算法依赖于距离,如果数据包含不同的物理单位或位于非常不同的尺度上,归一化作为预处理步骤可能是必要的。
结论
在本文中,我们从头开始一步一步地实现了 K 近邻。我们了解了根据距离对新观测值进行分类所需的主要计算步骤。
虽然 KNN 是一个简单明了的算法,但从头开始实现它可以让我们获得更深入的理解。当算法应用于不同的环境时,例如缺失值的插补,这可能证明是特别有用的。
你可以在我的 GitHub 上找到的完整代码。

从零开始的 ML 算法
View list6 stories


喜欢这篇文章吗?成为 中等会员 继续无限学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@marvinlanhenke/membership
从零开始用梯度下降实现线性回归
从头做起
从零开始用梯度下降实现线性回归
通过充分理解和推导梯度下降,建立强大的机器学习基础

Pawel Czerwinski 在 Unsplash 上的照片
在我个人在机器学习和数据科学领域的旅程中,我基本上跳过了所有的基础知识,带着一种荒谬的紧迫感奔向被认为是更奇特的东西。
不用说,我也忽略了线性回归。我认为这是浪费时间,因为这是一个简单的初学者算法,没有什么需要学习的。
回顾过去,我大错特错了。
在接下来的章节中,我们将使用 Python 和 NumPy 一步步实现线性回归。我们还将学习梯度下降,这是机器学习领域最常见的优化算法之一,通过从头开始推导它。
但是在直接进入实现细节之前,让我们建立一些关于线性回归和梯度下降的基本直觉。
为什么是线性回归?
线性回归是一种简单的监督学习算法,用于预测定量反应。它已经存在了相当一段时间,与更现代的机器学习方法相比,它被认为是非常基础的。
然而,线性回归可以作为一个很好的起点,因为它涵盖了机器学习中许多重要和基本的概念。
*
机器学习基础
View list7 stories


假设线性,线性回归的目标是通过优化权重和偏差以最小化残差平方和来找到最佳估计量。
或者简单地说,我们试图找到最佳拟合线、平面或超平面,使预测值和真实值之间的平方距离最小化。

简单线性回归最小化 MSE 的例子[图片由作者提供]
更正式地说,我们试图最小化的损失函数可以表述为下面的等式

其中 n 描述样本数量, Y 等于真实值, Y-hat 定义我们的预测值。
根据特征的数量,我们可以将线性回归视为简单的线性回归或多重线性回归。
导出梯度下降
尽管在 1847 年首次提出,梯度下降仍然是机器学习中最常见的优化算法之一。
主要思想是通过在梯度的相反方向上迭代地采取小步骤来寻找可微函数的局部最小值。
我们可以把梯度想成包含函数在某一点的一阶偏导数的向量。不严格地说,梯度描述了一个函数的斜率,告诉我们最快增长的方向和速率。
因为我们想向下移动并找到函数的最小值,所以我们需要向相反的方向移动。

梯度下降的例子[图片由作者提供]
注意:步长由一个叫做学习率的超参数控制。
在线性回归的情况下,这意味着我们必须通过找到其局部最小值来最小化损失函数 (MSE) 。因此,我们需要计算梯度,并在相反的方向上迭代地采取小的步骤。
为了能够计算梯度,我们有一些工作要做——我们需要导出损失函数相对于每个参数的一阶偏导数。
但首先,让我们重申并稍微修改损失函数:

n =样本;w =重量;b =偏差;x =特征
注:我们添加了一个常数“2”只是为了在求导时方便。
重量偏导数:
现在,我们需要对外部函数和内部函数求相对于权重的偏导数,并应用链式法则。
如果我们认为括号内的表达式为z = ((Xw + b)-Y),那么外部函数可以描述为z²。

接下来,我们需要考虑内部函数,并对权重求导。

应用链式法则,我们可以计算损失函数相对于权重的偏导数。

偏导数 w.r.t .偏差:
现在,我们简单地重复之前的步骤,但是这一次对内函数求导。

再次应用链式法则,我们得到如下结果:

就是这样——我们已经计算完了权重和偏差的梯度。当我们在下一节实现梯度下降时,这两个方程都会派上用场。
介绍算法
在我们从头开始编码和实现线性回归之前,我们还有一件事要做——我们需要知道算法一般是如何工作的。
基本上,我们需要执行 3 个步骤:
- 初始化权重和偏差
- 执行 n 次迭代的梯度下降,这包括进行预测、计算梯度以及更新权重和偏差
- 做最后的预测
由于第二步涉及多个动作,我们将把它分解成几个辅助函数。
我们将用 Python 和 Numpy 在一个类中实现该算法。下面的骨架级可以被视为某种蓝图,指导我们通过下一节。
从头开始实施
基本设置
首先,我们需要做一些内务处理,设置一些参数(学习率,迭代次数)以及权重和偏差。
主算法
现在,我们能够实现算法的核心计算步骤。
我们要做的第一件事是计算样本的数量和训练数据的特征。接下来,我们调用助手函数来初始化权重和偏差。
继续进行梯度下降 —在一次迭代中,我们必须做出预测,计算权重和偏差的梯度,并相应地更新参数。
做出预测非常简单,因为它只需要我们计算训练数据和权重的点积。可以在顶部添加偏置。
一旦我们获得了初始预测,我们需要计算梯度。
上面代码中的等式对我们来说应该很熟悉,因为我们已经做了一些工作,并在前面的部分中推导了它们。
我们要做的最后一件事是用计算出的梯度更新权重和偏差。
这也很简单——我们只需将学习率乘以相关的梯度,然后从当前值中减去乘积。
预测并把它们放在一起
到目前为止,我们基本上完成了所有的艰苦工作——我们只需要实现最后一个方法来进行最终预测,并将所有代码放在一起来完成我们的最终类。
测试回归量
已经完成了我们自己的梯度下降线性回归的实现,我们仍然需要测试它。
出于测试目的,我们将简单地使用sklearn.datasets.make_regression()并创建一个只有一个特征的基本数据集(这使我们更容易可视化)。

数据集的散点图[图片由作者提供]
现在,我们可以分割数据集,实例化并拟合我们的回归变量来进行预测。
运行上面的代码,我们得到大约 20.5 的均方根误差。通过绘制我们的结果,我们可以看到最佳拟合线,看起来我们的算法是可行的。

估计最佳拟合线[图片由作者提供]
结论
在本文中,我们从零开始一步一步地实现了线性回归。我们还学习了梯度下降是如何工作的,以及如何从头开始推导它。
虽然与更现代、更花哨的机器学习模型相比,线性回归似乎非常基础和枯燥,但它仍然对我们有巨大的价值。
例如,更深入地了解梯度下降的实际工作方式,可以让我们建立一个强大的基础——为我们在机器学习领域的旅程中更复杂的算法做准备。
你可以在我的 GitHub 上的这里找到完整的代码。
*
从零开始的 ML 算法
*View list6 stories

**
# 30 日
View list30 stories


喜欢这篇文章吗?成为 中等会员 继续无限学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
*https://medium.com/@marvinlanhenke/membership *
实现命名实体识别以在 pdf 中查找日期
命名实体识别是一种自然语言处理技术,可以帮助从 PDF 文档中提取感兴趣的日期

数据很重要。句号。虽然人们可以找到专门为某个数据科学任务定制的数据集,但总有一天这些数据集会变得缺乏原创性并被过度使用。最近我一直在用 Python 处理 pdf,老实说,这一开始并不是最容易的任务。您希望能够使用 PDF 文档的原因是,它们在互联网上非常容易访问,并且可以为您的数据集提供大量新数据。你可以看看我以前做过的 pdf 文件。此后,我清理并更改了我使用的一些代码,展示了您的代码是如何不断发展变化的!
→为什么日期在自然语言处理中很重要
如果您目前正在进行 NLP 项目,查找感兴趣的日期可能有助于在数据中找到趋势或模式。具体事件是什么时候发生的?事件发生的时间有规律吗?文本中有哪些重要的日期?围绕语料库中的日期,你可以也应该问自己很多问题。
→让我们深入研究代码!
这段代码需要的主要库是 PyPDF2 , Spacy ,以及re(Python 固有的)。首先,我们将创建我们的 PDF 解析类。我们想要创建的第一个函数是 pdf 阅读器,它允许我们将 PDF 读入 Python。
PDF 阅读器(来自作者)
我创建了一个快速示例 pdf 来展示这个解析类的强大功能。pdf 是:
我叫 Ben,从 2020 年春天开始从事数据科学的工作。今天是 2022 年 8 月 31 日。这个类可以帮助找到感兴趣的日期。例如,也许你想了解 2014 年初发生的一件事的更多信息。这有助于找到这些日期
要将 pdf 读入 Python,只需将 pdf 的文件路径放入 pdf 解析对象类中。
pdf = PdfParser('your file path here')
text = pdf.pdf_reader()
现在我们的 PDF 已经处理完毕,我们可以创建一个函数来提取日期。
日期查找功能(来自作者)
这个函数使用命名实体识别在标注为 DATE 的语料库中查找任何标签。为此,您需要确保从 Spacy 下载“en_core_web_lg”。了解如何在这里!在该函数中,创建了一个日期集,以防有任何重复(可能我们会两次找到同一年)。如果您关心文本正文中的日期频率,您可以将它更改为列表。接下来,让我们在 pdf 的文本上运行这个函数。
text.date_extractor()
运行此代码会产生以下输出:
['early 2014', 'Today', 'August 31, 2022', '2020']
嘣!有用!就像这样,我们可以将 pdf 转换成 python 可读的格式,并找到感兴趣的日期!
结论
今天,我们不仅学习了如何将 PDF 读入 Python,还学习了如何从 PDF 中提取感兴趣的日期。虽然在自然语言处理中找到文本的情感很重要,但知道一个事件何时发生,或者人们何时对一家公司做出负面评论,可能是有见地的。例如,在分析消费者评论时,可以在业务分析中使用该函数。如果一家公司收到的所有负面评价都发生在夏季,那该怎么办?也许这是由于公司由于预期消费者需求减少而减少了员工数量(许多人在夏天度假)。该公司可以将这些评论归因于该方面,而不是一个有缺陷的产品(在进一步调查后),因为创建了这个功能,我一直在我的分析中寻找时间线模式,它无疑帮助我发现了潜在的模式。让我知道这如何为你工作!
如果你喜欢今天的阅读,请关注我,让我知道你是否还有其他想让我探讨的话题(这对我的帮助超乎你的想象)!如果没有中等账号,在这里 报名 !另外,在LinkedIn**上加我,或者随时联系!感谢阅读!**
这里是完整的代码!希望这能对你以后的项目有所帮助!
完整代码(来自作者)
在 16 行 raw Julia 中实现神经网络
原文:https://towardsdatascience.com/implementing-neural-networks-in-16-lines-of-raw-julia-aaa9512f007
再用一些来训练它

在构建神经网络和深度学习模型时,Tensorflow 和 PyTorch 是事实上的标准。虽然日常模型可以快速实现,但定制算法有时会导致代码看起来非常冗长。
这在一定程度上是由于 Tensorflow 和 PyTorch 都只是将 Python 作为其低级 API 的“前端”。另一方面,Julia 承诺用单一语言实现端到端的可区分机器学习。这无疑给更干净的代码库带来了希望!
因此,在准备 Julia 的介绍性演讲时,我试图用尽可能少的代码实现一个简单的前馈神经网络。如果没有空行,代码甚至可以减少到 14 行。然而,这会大大降低可读性。结果如下:
让我们一步一步地检查每个组件。如果你不熟悉前馈神经网络的内部结构,你可能会发现这些维基百科参考文献很有用。
将前馈层编写为 Julia 结构
这是前馈层的标准设置。我们需要一个权重矩阵, W ,一个偏置向量, b ,以及一个激活函数。接下来,我们定义前馈层的前馈通道:
Julia 允许我们通过实例化的结构调用函数。在某种程度上,这使得层和网络实例的后续使用更加优雅。
和标识激活函数的实现应该是不言自明的。
将层聚集到全前馈神经网络中
网络本身只不过是其各个层的集合:
我们在构造函数中使用一个Vararg参数来允许任意数量的层。虽然我们可以在构造函数调用中只使用一个Vector,但是这种方法为我们节省了两个括号:)。
最后,我们要定义全局前馈通路。请记住,这可以表示为所有层的功能组合:

(图片由作者提供)
这里, f 表示总共 L 个前馈层的层函数。我们可以在 Julia 中这样写:
由于我们的函数组合是一个关联操作,我们可以使用reduce()函数。这使得我们可以在 Julia 中把这个关键元素变成另一个俏皮话。
如果你仔细观察,你会注意到我们在reduce()期间颠倒了layer::Vector{Layer}的顺序。虽然从从左到右推理网络拓扑更容易,但是合成操作本身是从从右到左执行的。
此外,请注意,如果我们想要优化网络,我们不能预先计算这个组成。让我们实际这样做,以便验证我们的实现是否如预期的那样工作。
在一个玩具例子上训练我们的 Julia 神经网络
由于梯度计算和优化器实现将是一个更大的任务,我们将使用两个 Julia 库来帮助完成这一步。对于玩具数据集,让我们在[-3.0,3.0]中的插值线上使用正弦函数:
总之,上面的代码片段执行以下步骤:
- 创建玩具数据并定义损失函数
- 实例化神经网络
- 从网络实例中提取目标参数(网络权重)
- 训练模型(即优化其参数)
检查输出
最后,让我们验证我们迄今所做的一切确实是正确的。如果一切顺利,我们的模型应该已经学会近似正弦函数。
这会产生以下情节:

输出证实了我们的实现确实是正确的。我们可以进一步利用这个例子,用 Julia 以紧凑的方式实现例如 正则化 。(图片由作者提供)
太好了!现在我能放下 Tensorflow 和 PyTorch 吗?
肯定看情况!一方面,Julia 是一种以高效方式编写快速机器学习算法的优秀语言。另一方面,Julia 还是一门比较年轻的语言。就我个人而言,我还不会在任何非个人的商业产品中使用朱莉娅。
以下两个我亲身经历或在网上讨论中看到的问题仍然阻止我向我的客户推荐 Julia:
对于 JIT 编译来说,可部署性是具有挑战性的——一般来说,Julia 提供了成功部署模型甚至复杂应用程序所需的所有功能。然而,JIT 编译器的预热时间使得很难有效地这样做。
每当您开始一个新的 Julia 会话时,您基本上必须等待一两分钟,直到您的代码可以使用为止。如果您需要您的应用程序快速伸缩以适应突然的使用高峰,这可能是一个令人望而却步的问题。
然而,对于做研究或创建新算法的原型来说,Julia 是一种很棒的语言。一旦你的 Julia 神经网络或相关模型表现良好,你可以通过 ONNX 轻松将其转移到 PyTorch 或 Tensorflow。从那里,您可以使用常用的、经过验证的工具集来部署它。
如果你愿意尝试,并且能处理偶尔的怪癖,你绝对应该给朱莉娅一个机会。
原载于 2022 年 2 月 28 日 https://sarem-seitz.comhttps://sarem-seitz.com/blog/implementing-neural-networks-in-16-lines-of-raw-julia/。
用 Python 从头开始实现管道机制
原文:https://towardsdatascience.com/implementing-piping-mechanisms-from-scratch-with-python-802f13430140
总是有一种用 Python 重现一些迷人语法的驱动力。管道是一个有趣的例子。

由 Towfiqu barbhuiya 在 Unsplash 上拍摄的照片
在之前的教程中,我们看到了如何使用 Dunder 方法实现高级行为,以及 functools 模块提供的分部方法如何利用参数多样性。
我感兴趣的是使用这两个 Python 支柱来实现一个类似于 PIPEY 的行为,即构建一个管道机制,该机制将对一段数据实现一系列连续的操作,如下所示:
pipe | operation1 >> operation2 >> operation3 >> .. >> operationN
这个管道是可调用的,它将数据作为输入。
为此,我们将:
- 首先,构建一个基本的
Partial可定制类,它将充当functools.partial模块。 - 给
Partial级注入更多的能力。 - 通过一个
Pipe类链接起来。
实验
正如你已经知道的,在functools中提供的partial方法用来冻结一个函数的一部分参数,然后给出一个新的函数。

作者图片
partial模块大致有如下实现:
让我们在官方文档中提到的同一个例子上测试它:
>>> basetwo = Partial(int, base=2)
>>> basetwo('1001')
... 9
这个Partial类将作为下一部分的构建模块。
为了模拟算术行为,能够使用反射右移( >> )并且能够使用可调用的管道,我们必须实现两个特殊的方法:__rshift__和__call__。让我先给你演示一下是怎么做的,然后再解释:
在实例初始化方面,与第一个基本Partial实现的区别在于属性的性质。我们需要构造一种累加器,在右移另一个Partial实例时从中受益。
请注意__rshift__方法是如何编写的,它总结起来就是将other的部分信息(函数、参数和关键字参数)附加到第一个信息上,从而跟踪管道的定位和执行顺序。一旦建立了管道,可调用的部分链从左到右启动计算。第一个可调用函数计算结果,该结果又成为下一个函数的输入。
让我们举个例子:
>>> first_pipeline = Partial(pow,2) >> Partial(pow,3) >> Partial(pow, 5)
>>>
>>> first_pipeline(4)
... 1152921504606846976
>>> ((4 ** 2) ** 3 ) ** 5
... 1152921504606846976
你可以停下来一会儿,惊叹于语法的美丽和结果的可信程度。
然而,我们当然不希望每次在管道中添加内容时都要明显地调用 Partial。
让我们写下我们需要的调整过的幺正函数。让我们定义一个power函数和一个add函数:
power = lambda x: Partial(lambda x_, n: pow(x_, n), x)
add = lambda x: Partial(lambda x_, n: x_ + n, x)
乍一看并不十分优雅,但让我们深入研究一下:
在幂的例子中,部分变量冻结了x变量,这样我们就能够改变幂项。
Partial(lambda x_, n: pow(x_, n), x)冻结 lambda 函数的第一个参数x_。
因为x是一个变量,它可以表示输入或者先前调用的结果,所以将这个Partial包装到另一个lambda函数中是有意义的。
>>> power = lambda x: Partial(lambda x_, n: pow(x_, n), x)
>>> add = lambda x: Partial(lambda x_, n: x_ + n, x)
>>> element = power(2) >> add(4) >> power(2) >> add(2)
>>> element(16)
>>> 67602
快速检查:
>>> ((((16 ** 2) + 4 ) ** 2 ) + 2)
>>> 67602
非常管用!
投资更多优雅如何?让我们把它们组装成一个Pipe类:
有了我们的Pipe事情变得更有意义:
>>> element = Pipe() | power(2) >> add(4) >> power(2) >> add(2)
>>> element
... <__main__.Pipe at 0x7f869c3606a0>
>>> element(16)
... 67602
element不再是一个Partial实例,而是一个Pipe实例,它基本上提供了一种更高级的制作管道的方法。通常,Python 会抛出一个TypeError来警告你一种类型的冲突。但是因为我们在Partial类中指出了它应该如何处理两个Partial实例的反射右移,所以它很好地认识到如何用更多的抽象来完成。
我们用另一种类型的数据来测试我们的机制,比如说..名单!显然,我们宣布接下来的作品:
add_list和power_list需要额外的参数,因此使用外部lambda函数。sorted_list和uniq_list都不需要额外的参数。将它们包装成Partial类就足够了。
这是最后一个例子:
>>> list_elements = [1,2,3,3,4]
>>> element_list = Pipe() | add_list(2) >> power_list(3) >> sorted_list >> uniq_list
>>> element_list(list_elements)
... [64, 27, 125, 216]
即使我们有不同类型的数据,该机制也能正常运行。
结论
如果你做到了这一步,我非常感谢你的耐心和好奇心。
这是一个相当有趣的实验,用来测试我们在高级 python 工具中看到的一些隐藏功能。不可避免地,它们让我们质疑我们能达到的语法优雅程度。
在 PyTorch 中实现 RepVGG
原文:https://towardsdatascience.com/implementing-repvgg-in-pytorch-fc8562be58f9

Alberto Restifo 在 Unsplash 上拍摄的照片
让您的 CNN 速度快 100 倍以上
你好。!今天我们将看看如何在 PyTorch 中实现 RepVGG,这是在 RepVGG:让 VGG 风格的网络再次伟大中提出的
我们开始吧!
该论文提出了一种新的架构,可以在训练后进行调整,使其在现代硬件上运行更快。我说的更快是指更快的照明速度,这个想法被苹果的手机型号所采用。

单分支与多分支模型
许多最近的模型使用多分支,其中输入通过不同的层传递,然后以某种方式聚合(通常有加法)。

作者图片
这很棒,因为它使多分支模型成为众多较浅模型的隐式集合。更具体地说,该模型可以解释为 2^n 模型的集合,因为每个模块将流量分成两条路径。
不幸的是,多分支模型比单分支模型消耗更多的内存,速度也更慢。让我们创建一个经典的ResNetBlock来看看为什么(查看我在 PyTorch 中关于 ResNet 的文章)。
存储residual双倍内存消耗。这也显示在论文的下图中

作者注意到多分支结构仅在训练时有用。因此,如果我们有办法在测试时删除它,我们就可以提高建模速度和内存消耗。
从多分支到单分支
考虑以下情况,您有两个由两个3x3conv 组成的分支
torch.Size([1, 8, 5, 5])
现在,我们可以创建一个 conv,姑且称之为conv_fused,这样conv_fused(x) = conv1(x) + conv2(x)。很简单,我们只需将两个 convs 的weight s 和bias相加即可!因此我们只需要运行一个conv而不是两个。
让我们看看它快了多少!
conv1(x) + conv2(x) tooks 0.000421s
conv_fused(x) tooks 0.000215s
几乎50%更少(请记住,这是一个非常幼稚的基准,稍后我们会看到一个更好的)
融合 Conv 和 BatchNorm
在现代网络架构中,BatchNorm被用作卷积块之后的正则化层。我们可能希望将它们融合在一起,因此创建一个 conv,如conv_fused(x) = batchnorm(conv(x))。这个想法是改变conv的权重,以便结合BatchNorm的移动和缩放。
该文件解释如下:

代码如下:
让我们看看它是否有效
是的,我们融合了一个Conv2d和一个BatchNorm2d图层。PyTorch 也有一篇关于这个的文章
因此,我们的目标是将所有分支融合在一个 conv 中,使网络更快!
作者提出了一种新型砌块,称为RepVGG。与 ResNet 类似,它有一个快捷方式,但它也有一个身份连接(或更好的分支)。

在 PyTorch:
重新参数化
我们有一个3x3 conv->bn,一个1x1 conv-bn(有时)一个batchnorm(身份分支)。我们希望将它们融合在一起,创建一个单一的conv_fused,这样conv_fused = 3x3conv-bn(x) + 1x1conv-bn(x) + bn(x),或者如果我们没有身份连接,则conv_fused = 3x3conv-bn(x) + 1x1conv-bn(x)。
我们一步一步来。要创建conv_fused,我们必须:
- 将
3x3conv-bn(x)融合成一个3x3conv 1x1conv-bn(x),然后将其转换为3x3conv- 将身份
bn转换为3x3conv - 将所有三个
3x3conv相加
由下图总结:

第一步很简单,我们可以在RepVGGBlock.block(主3x3 conv-bn)上使用get_fused_bn_to_conv_state_dict。
第二步类似,get_fused_bn_to_conv_state_dict上RepVGGBlock.shortcut(下1x1 conv-bn)。然后我们在每个维度上用1填充融合的1x1的每个内核,创建一个3x3。
身份bn更加棘手。我们需要创建一个3x3 conv作为身份函数,然后使用get_fused_bn_to_conv_state_dict将其与身份bn融合。这可以通过使1位于对应通道的对应内核的中心来实现。
回想一下,conv 的重量是一个张量in_channels, out_channels, kernel_h, kernel_w。如果我们想要创建一个身份 conv,比如conv(x) = x,我们需要为该通道创建一个单独的1。
例如:
https://gist.github.com/c499d53431d243e9fc811f394f95aa05
torch.Size([2, 2, 3, 3])
Parameter containing:
tensor([[[[0., 0., 0.],
[0., 1., 0.],
[0., 0., 0.]], [[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]]], [[[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]], [[0., 0., 0.],
[0., 1., 0.],
[0., 0., 0.]]]], requires_grad=True)
看,我们创建了一个Conv,它的行为就像一个身份函数。
现在,把所有东西放在一起,这一步正式称为重新参数化
最后,我们来定义一个RepVGGFastBlock。它只由一个conv + relu组成
并在RepVGGBlock中添加一个to_fast方法来快速创建正确的RepVGGFastBlock
RepVGG
让我们用一个方便的switch_to_fast方法来定义RepVGGStage(块集合)和RepVGG,该方法将就地切换到快速块:
让我们来测试一下!
我在benchmark.py中创建了一个基准测试,在我的 gtx 1080ti 上运行不同批量的模型,结果如下:
模型每级两层,四级,宽度64, 128, 256, 512。
在他们的论文中,他们将这些值缩放了一定的量(称为a和b),并使用了分组转换。因为我们对重新参数化部分更感兴趣,所以我跳过它们。

作者图片
是的,所以基本上再参数化模型和普通模型相比,是在不同的时间尺度上。哇!
让我复制并粘贴我用来存储基准的数据帧
你可以看到默认模型(多分支)对于一个batch_size=128花费了1.45 s,而参数化模型(快速)只花费了0.0134 s,即 108x🚀🚀🚀
.
结论
结论在本文中,我们已经一步步地了解了如何创建 RepVGG 使用巧妙的重新参数化技术的超快的模型。
这项技术也可以移植到其他体系结构中。
感谢您的阅读!
弗朗西斯科
在 PyTorch 中实现 SegFormer
原文:https://towardsdatascience.com/implementing-segformer-in-pytorch-8f4705e2ed0e

作者图片
一种快速、高效、轻量级的图像分割模型
你好。!今天我们将看看如何在 PyTorch 中实现 SegFormer,这是在 SegFormer:用变形器进行语义分割的简单有效的设计中提出的。
这里的代码是,这篇文章的互动版本可以从这里下载。
我们开始吧!
提出了一种新的基于变换的图像分割模型。即使“变形金刚”是时下的流行语,模型本身也只有基本的注意机制。该模型有两个主要优点,首先 SegFormer 包括一个新颖的分级结构的变压器编码器,它输出多尺度特征。然后,不需要位置编码,从而避免了当测试分辨率不同于训练时导致性能下降的位置代码插值。
有趣的是,我们正在研究中倒退,这两个优点从一开始就存在于 convnet 中,我们将看到 SegFormer 最终只是一个 convnet + attention。
下图显示了 SegFormer 在 ADE20K 数据集上针对不同模型/大小的性能,他们有 sota。

它比老式的 FCN-R50 要好,速度快两倍。既然它少了 24 次翻牌,我想知道为什么它的速度只有两倍。
体系结构
该模型是一个典型的编码器-解码器/主干-颈部。附加一个头部来预测最终的分割掩模。

我们将以自下而上的方式实现它,从解码器内部的最低模块开始。
报纸上的图像是错误的🤦,我不明白为什么有评论者指出,也许我错了。在官方的实现中没有第一次补丁嵌入。重叠补丁合并块(紫色的那个)应该在自我效能注意块之前。
它应该是这样的:

作者图片
用一点 photoshop

作者图片
参见处的代码
解码器
使用的解码器叫做MixVisionTransformer ( MiT),另一个ViT,中间加了一些随机的东西,我们就叫它SegFormerDecoder。让我们从积木本身的第一个独立组件OverlapPatchMerging开始。
重叠合并

作者图片
OverlapPatchMerging块可以用一个stride小于kernel_size的卷积层来实现,因此它与不同的面片重叠。这与几年前提出的使用大于 1 的stride来减少输入的空间维度是一样的。在SegFormer中,conv 层之后是层规范。
由于 PyTorch 中的nn.LayerNorm适用于形状为batch, ...., channels的张量,我们可以创建一个LayerNorm2d,首先将channels轴与最后一个轴交换,然后应用层规范,并将其交换回来。我将使用[einops](https://github.com/arogozhnikov/einops)使代码更具可读性
那么我们的OverlapPatchMerging只是一个 conv 层,后面跟着我们的层范数
高效的自我关注

作者图片
我们都知道注意力有一个平方复杂度O(N^2),而在我们的例子中N=H*W。我们可以将N减少R的一个因子,复杂度变成O(N^2/R)。一个简单的方法是平坦的空间维度,并使用线性层。
我们将空间大小减少了r=4,因此在每个维度上减少了2(height和width)。如果你考虑一下,你可以用一个kernel_size=r和一个stride=r的卷积层来达到同样的效果。
由于注意力等于softmax((QK^T/scale)V),我们需要使用约化张量计算K和V,否则,形状不会匹配。我们可以用 PyTorch 的MultiheadAttention来计算注意力。
torch.Size([1, 8, 64, 64])
MixMLP

作者图片
细心的读者可能已经注意到我们没有使用位置编码。SegFormer 使用了一个3x3深度 conv。引用论文我们认为位置编码对于语义分割是不必要的。相反,我们引入了混合 FFN,它考虑了零填充的影响,以泄漏位置信息。我不知道这意味着什么,所以我们会认为这是理所当然的。
我很确定它被称为 Mix,因为它使用3x3 conv 来混合信息。
图层由dense layer->-3x3 depth-wise conv->-GELU->-dense layer组成。像在 ViT 中,这是一个逆瓶颈层,信息在中间层展开。
编码器(变压器)模块

作者图片
让我们把所有东西放在一起,创建我们的编码器模块。我们将遵循一个更好的(imho)命名惯例,我们称SegFormerEncoderBlock具有自我关注和混合 fpn 的部分和SegFormerEncoderStage整个重叠片合并+ N x SegFormerEncoderBlock
与ViT非常相似,我们有跳过连接和标准化层+随机深度,也称为丢弃路径,(我有一篇关于它的文章)。
torch.Size([1, 8, 64, 64])
好吧,让我们创造一个舞台。我不知道为什么,他们在最后应用层规范,所以我们也这样做:)
最后的SegFormerEncoder由多个阶段组成。
我添加了函数chunks来保持代码的整洁。它是这样工作的
[[1, 2], [3, 4, 5]]
这很方便,因为drop_probs是一个列表,包含每个阶段的块的下落路径概率,我们需要将一个列表和正确的值传递给每个阶段。
从编码器中,我们返回一个内部特征列表,每个阶段一个。
解码器/颈部
幸运的是,解码器/neck 的图片与原始代码相匹配。他们称解码器部分为MLP Layer。

它所做的非常简单,它采用大小为batch, channels_i, height_i, width_i的F特征,并输出相同空间和通道大小的F'特征。空间大小固定为first_features_spatial_size / 4。在我们的例子中,由于我们的输入是一个224x224图像,输出将是一个56x56遮罩。
因此,单个SegFormerDecoderBlock包含一个上采样层(用于空间维度)和一个 conv 层(用于通道)。需要scale_factor参数来告诉它我们想要对特征进行多大程度的上采样。
在这种情况下,我们没有阶段,所以我们的SegFormerDecoder只是一个块列表。它获取要素列表,并返回具有相同空间大小和通道的新要素列表。
SegFormer 头

我们快到了!解码器的功能在通道轴上串联(记住它们都具有相同的通道和空间维度)。然后,它们被传递到分段头,以将它们从channels * number of features减少到channels。最后,密集层输出最终分割。
SegFormer
嗯,我们的最终模型只是encoder + decoder + head。容易的事
我们试试吧!
torch.Size([1, 100, 56, 56])
输出是正确的,我们期望一个空间形状的遮罩image_size // 4和224 // 4 = 56。
我们做到了!🎉🎉🎉
结论
在本文中,我们已经一步一步地看到了如何创建 SegFormer 一种快速有效的图像分割模型。
感谢您的阅读!
弗朗西斯科
在 PyTorch 中实现随机深度/落差路径
原文:https://towardsdatascience.com/implementing-stochastic-depth-drop-path-in-pytorch-291498c4a974

作者图片
DropPath 在 眼镜 我的计算机视觉库中
介绍
今天我们将在 PyTorch 中实现随机深度,也称为 Drop Path!黄高等人引入的随机深度是一种在训练过程中“去激活”某些层的技术。我们将坚持使用 DropPath 。
让我们来看一个使用剩余连接的普通 ResNet 块(像现在几乎所有的模型一样)。如果你不熟悉 ResNet,我有一篇文章展示了如何实现它。
基本上,块的输出被添加到它的输入:output = block(input) + input。这被称为剩余连接

作者图片
这里我们看到四个类似 ResnNet 的块,一个接一个。

作者图片
随机的深度/下落路径会降低一些石块的重量

作者图片
履行
让我们从导入我们最好的朋友torch开始
import torch
from torch import nn
from torch import Tensor
我们可以定义一个 4D 张量(batch x channels x height x width),在我们的例子中,让我们只发送 4 个图像,每个图像一个像素,这样更容易看到发生了什么:)
x = torch.ones((4, 1, 1, 1))
我们需要一个形状为batch x 1 x 1 x 1的张量,使用一个给定的 prob 来将批处理中的一些元素设置为零。伯努利来救援了。
keep_prob: float = .5
mask: Tensor = x.new_empty(x.shape[0], 1, 1, 1).bernoulli_(keep_prob)# mask = tensor([[[[1.]]],
[[[1.]]],
[[[0.]]],
[[[0.]]]])
Btw,这相当于
mask: Tensor = (torch.rand(x.shape[0], 1, 1, 1) > keep_prob).float()
我们想把x的一些元素设置为零,因为我们的遮罩是由0和1组成的,我们可以把它乘以x。在我们这样做之前,我们需要用x除以keep_prob来降低训练期间输入的激活,参见 cs231n 。因此
x_scaled : Tensor = x / keep_prob
最后
output: Tensor = x_scaled * mask
# output =
tensor([[[[2.]]],
[[[0.]]],
[[[2.]]],
[[[2.]]]])
看看批处理中的一些元素是如何被设置为零的。我们可以把它放在一个函数中
def drop_path(x: Tensor, keep_prob: float = 1.0) -> Tensor:
mask: Tensor = x.new_empty(x.shape[0], 1, 1, 1).bernoulli_(keep_prob)
x_scaled: Tensor = x / keep_prob
return x_scaled * mask
drop_path(x, keep_prob=0.5)
我们也可以就地做手术
def drop_path(x: Tensor, keep_prob: float = 1.0) -> Tensor:
mask: Tensor = x.new_empty(x.shape[0], 1, 1, 1).bernoulli_(keep_prob)
x.div_(keep_prob)
x.mul_(mask)
return x
drop_path(x, keep_prob=0.5)
然而,我们可能想在其他地方使用x,用x或mask除以keep_prob也是一样的。让我们来看看最终的实现
def drop_path(x: Tensor, keep_prob: float = 1.0, inplace: bool = False) -> Tensor:
mask: Tensor = x.new_empty(x.shape[0], 1, 1, 1).bernoulli_(keep_prob)
mask.div_(keep_prob)
if inplace:
x.mul_(mask)
else:
x = x * mask
return x
x = torch.ones((4, 1, 1, 1))
drop_path(x, keep_prob=0.5)
drop_path仅适用于 2d 数据,我们需要从输入的尺寸中自动计算出维数,以使其适用于任何数据时间
def drop_path(x: Tensor, keep_prob: float = 1.0, inplace: bool = False) -> Tensor:
mask_shape: Tuple[int] = (x.shape[0],) + (1,) * (x.ndim - 1)
# remember tuples have the * operator -> (1,) * 3 = (1,1,1)
mask: Tensor = x.new_empty(mask_shape).bernoulli_(keep_prob)
mask.div_(keep_prob)
if inplace:
x.mul_(mask)
else:
x = x * mask
return x
让我们创建一个漂亮的DropPath nn.Module
class DropPath(nn.Module):
def __init__(self, p: float = 0.5, inplace: bool = False):
super().__init__()
self.p = p
self.inplace = inplace
def forward(self, x: Tensor) -> Tensor:
if self.training and self.p > 0:
x = drop_path(x, self.p, self.inplace)
return x
def __repr__(self):
return f"{self.__class__.__name__}(p={self.p})"
使用剩余连接
我们有我们的DropPath,酷!我们如何使用它?我们需要一个剩余块,我们可以使用一个经典的 ResNet 块:老朋友BottleNeckBlock
from torch import nn
class ConvBnAct(nn.Sequential):
def __init__(self, in_features: int, out_features: int, kernel_size=1):
super().__init__(
nn.Conv2d(in_features, out_features, kernel_size=kernel_size, padding=kernel_size // 2),
nn.BatchNorm2d(out_features),
nn.ReLU()
)
class BottleNeck(nn.Module):
def __init__(self, in_features: int, out_features: int, reduction: int = 4):
super().__init__()
self.block = nn.Sequential(
# wide -> narrow
ConvBnAct(in_features, out_features // reduction, kernel_size=1),
# narrow -> narrow
ConvBnAct( out_features // reduction, out_features // reduction, kernel_size=3),
# wide -> narrow
ConvBnAct( out_features // reduction, out_features, kernel_size=1),
)
# I am lazy, no shortcut etc
def forward(self, x: Tensor) -> Tensor:
res = x
x = self.block(x)
return x + res
BottleNeck(64, 64)(torch.ones((1,64, 28, 28)))
要停用程序块,操作x + res必须等于res,因此我们的DropPath必须在程序块之后应用。
class BottleNeck(nn.Module):
def __init__(self, in_features: int, out_features: int, reduction: int = 4):
super().__init__()
self.block = nn.Sequential(
# wide -> narrow
ConvBnAct(in_features, out_features // reduction, kernel_size=1),
# narrow -> narrow
ConvBnAct( out_features // reduction, out_features // reduction, kernel_size=3),
# wide -> narrow
ConvBnAct( out_features // reduction, out_features, kernel_size=1),
)
# I am lazy, no shortcut etc
self.drop_path = DropPath()
def forward(self, x: Tensor) -> Tensor:
res = x
x = self.block(x)
x = self.drop_path(x)
return x + res
BottleNeck(64, 64)(torch.ones((1,64, 28, 28)))
Tada!现在,随机地,我们的.block将被完全跳过!
结论
在本文中,我们看到了如何实现 DropPath 并在 residual 块中使用它。希望,当你阅读/看到下落路径/随机深度时,你知道它是如何制作的
保重:)
弗朗西斯科
从头开始实现支持向量机
原文:https://towardsdatascience.com/implementing-svm-from-scratch-784e4ad0bc6a
从头做起
从头开始实现支持向量机
通过从头推导理解具有梯度下降和铰链损失的最大间隔分类器

阿曼德·库利在 Unsplash 上拍摄的照片
当我决定学习一种机器学习算法时,我总是想知道它是如何工作的。
我想知道引擎盖下是什么。想知道是怎么实施的?我想知道它为什么有效。
从头开始实现机器学习算法迫使我们寻找所有这些问题的答案——这正是我们在本文中试图做的。
在接下来的小节中,我们将使用 Python 和 NumPy 一步步实现支持向量机 。我们还将学习基本的数学原理,铰链损失函数,以及如何应用梯度下降。
但是在深入实现细节之前,让我们建立一些关于支持向量机的基本直觉。
支持向量机
由计算机科学界在 20 世纪 90 年代开发的支持向量机(SVM)是一种常用的监督学习算法,最初用于二进制分类设置。
它通常被认为是最好的“开箱即用”分类器之一。
SVM 是一种简单而优雅的算法,称为最大间隔分类器的推广。然而,这种分类器不能应用于所有情况,因为它严重依赖于数据集是线性可分的假设,因此,存在几种扩展。
注意:在下文中,我们将只讨论最大间隔分类器,有意避免不同的扩展。这使我们能够对基本原理和学习算法的主要目标有更深的理解。
让我们假设,我们有一个数据集,其中每个数据点属于两个类中的一个。假设数据集是线性可分的——最大间隔分类器试图找到最佳分离超平面,顾名思义——最大化间隔。
换句话说,我们只是通过最大化到任一类的最近数据点的距离来寻找分隔两类的决策边界。

最大间隔分类器对超平面的可视化[图片由作者提供]
然而,一个基本问题浮现在脑海中——我们如何找到最佳超平面?
在回答这个问题之前,我们需要做一些基础工作。更准确地说,我们需要知道什么是超平面,它是如何定义的,以及如何通过计算和最大化边际来选择最优解。
了解超平面
一般来说, p 维的超平面可以描述为一个维数为 p-1 的平坦仿射子空间。
例如,在二维空间中,超平面可以由平坦的一维子空间来定义,或者换句话说,可以由一条简单的直线来定义。类似地,在三维中,超平面可以描述为二维子空间或平面。
现在,假设我们有一个二维空间的数据集,可以线性分离。因此,将数据点按其类别{-1,1}划分的超平面 (H₀) 是一条直线。

一个分离超平面的例子[图片由作者提供]
更正式地说,超平面由满足以下等式的点集 x 定义:

这个定义特别有用,因为它允许我们把超平面看作一个决策边界。不满足上述等式的任何点 x ,必定位于超平面的一侧或另一侧。
重新构建之前的情况,我们也可以认为这是选择另外两个超平面 (H₁,H₂) ,它们与 H₀、的距离相同,确保每个数据点都位于正确的一侧,中间没有数据。

H2 H1 等距超平面的一个例子
超平面 (H₁,H₂) 因此可以定义为

我们可以将它组合成一个用于分离超平面的单一方程:

现在,我们知道分离超平面是如何定义的,但如何找到最优解的问题仍然没有答案。
寻找最优分离超平面
由于我们的数据集可以使用超平面完美地分离,并且任何超平面都可以通过微小的推动来移动和旋转,因此我们可以从无限量的可能解决方案中进行选择。
因此,我们需要一种合理的方法来决定使用哪个可能的超平面。

可能的分离超平面的例子[图片由作者提供]
不严格地说,最优分离超平面是离最近数据点最远的解——或者换句话说,是最大化了余量。
我们也可以把它想象成另外两个超平面(支持向量),它们之间的距离最大化。最佳分离超平面可以想象成一条将边缘分成两半的平行线。

最大化边距[图片由作者提供]
为了最大化两个超平面之间的距离,我们需要找到一种计算边距的方法。
边缘也可以解释为垂直于任何超平面的向量,其大小等于边缘。因此,我们只需要定义这个向量,然后求解它的大小。

计算边距[图片由作者提供]
由于我们的权重向量 w 已经垂直于超平面,我们可以用下面的等式计算与 w 方向相同的单位向量 u :

现在,我们只需要将单位向量 u 缩放一个标量 m 就可以得到一个方向和大小与边距相同的向量 k 。

让我们想象一个任意的点 x₀ 并加上我们的向量 k ,得到 z₀ = x₀ + k 。

导出边距[图片由作者提供]
由于 z₀ 和 x₀ 分别位于超平面 H₁ 和 H₂ 上,我们可以如下求解余量:

通过仔细研究我们的最终等式(8),我们可以看到,为了最大化裕量,我们只需最小化分母——向量 w 的范数。
就是这样,我们最终收集了计算最优分离超平面所需的所有成分。剩下唯一要做的就是把所有的成分组合成一个单一的目标函数。
主要目标函数
有了如何定义分离超平面以及如何计算两个超平面之间的距离的知识,我们可以导出主目标函数。

到目前为止,这个等式的某些部分对我们来说应该很熟悉。
第一项基本上负责最大化裕度,表示为具有附加正则化参数λ的最小化问题。
注:当导出梯度时,简单地增加乘以 1/2 是为了方便。
第二项——包含我们对分离超平面的定义——是一个称为铰链损失的损失函数。不严格地说,这个术语负责确保我们以足够的余量预测正确的类标签。
例如,如果 yᵢ = 1 和 xᵢ 被正确分类,则计算铰链损耗的结果为零,因为 max(0,1–1)= 0。但是,如果错误地预测了类别标签,则铰链损耗将导致大于零的值。
由于我们要用梯度下降优化损失函数,我们需要计算函数相对于权重和偏差的偏导数。一旦我们获得梯度,我们可以简单地在相反的方向采取小步骤,以尽量减少损失。
为了获得(子)梯度,我们必须区分两种情况下的目标函数:

对第一种情况取偏导数,得到以下梯度:

对第二种情况重复相同的步骤得到:

我们应该把梯度和导数放在手边,因为它们在实现梯度下降和下一节的主要算法时会派上用场。
介绍算法
在直接进入实现细节之前,我们将快速看一下算法的主要计算步骤,以提供一个高层次的概述以及一些基本结构。
主算法基本上可以分解为 4 个步骤:
- 权重和偏差的基本设置和初始化
- 将类别标签从{0,1}映射到
- 执行 n 次迭代的梯度下降,这涉及梯度的计算并相应地更新权重和偏差。
- 做最后的预测
由于第三步由多个动作组成,我们将把它分解成几个辅助函数。
该算法将用 Python 和 Numpy 在一个类中实现。下面,我们可以看一下骨架类,它可以解释为某种蓝图,指导我们完成下一节的实现。
从头开始实施
基本设置
我们从一些简单的内务开始,通过初始化参数(学习率,迭代次数)并定义用于初始化权重和偏差的辅助函数。
主算法
基本设置完成后,我们现在能够实现算法的核心计算步骤。
首先,我们需要初始化权重和偏差并映射我们的二进制类标签从{0,1}到{-1,1},这使我们能够检查数据点是否位于分离超平面的正确一侧。
可以借助内置的 NumPy 函数np.where()来执行类映射,该函数简单地将 0 的每个类标签变成-1。
现在,继续进行梯度下降——在单次迭代中,我们必须检查是否满足分离超平面的约束,计算权重和偏差的梯度,并相应地更新参数。
为了计算正确的梯度,我们需要知道目标函数的哪种情况与特定的数据点相关。因此,我们需要检查数据点是否满足约束

一旦我们知道是否满足约束,我们就可以相应地计算梯度。我们应该能够识别出前面部分的导数。一个简单的 if 语句完成了决定计算哪组渐变的工作。
有了手中的梯度,我们只需再做一件事——我们必须更新权重和偏差,允许我们向相反的方向迈出一小步,使损失函数最小化。
预测并把它们放在一起
进行预测非常简单,因为它只需要我们计算训练数据和权重的点积,并在顶部添加偏差。
我们还计算类标签的符号,并在返回预测标签之前,将它们从{-1,1}映射回原始值{0,1}。
到目前为止,我们基本上完成了所有的艰苦工作——我们只需要将所有的东西放在一起,完成我们的实现。
测试分类器
我们已经完成了最大间隔分类器的实现,我们仍然需要测试它。
出于测试目的,我们将简单地使用sklearn.datasets.make_blobs()并创建一个包含 250 个样本的基本数据集,其中只有两个特征。我们可以将数据可视化如下:

简单数据集的可视化[图片由作者提供]
现在,我们可以分割数据集,实例化并拟合我们的分类器来进行预测。
将预测的类别标签与真实的类别标签进行比较,得到 100 %的准确度分数。我们还可以通过绘制超平面来可视化结果,这验证了我们的算法是正确工作的。

结果超平面的可视化[图片由作者提供]
结论
在本文中,我们以循序渐进的方式从头开始实现了最大间隔分类器。我们还学习了基本的数学概念,如何实现铰链损失函数,以及如何应用梯度下降。
然而,最大间隔分类器仅覆盖数据集是线性可分的简单情况。因此,存在几个扩展,人们可以并且可能应该更深入地探索。
从零开始实现最大间隔分类器使我们能够对基本的和潜在的概念建立更深的理解。当探索一些建立在最大间隔分类器之上的更复杂的扩展时,这些知识将被证明是有用的。
你可以在我的 GitHub 上的这里找到的完整代码。

从零开始的 ML 算法
View list6 stories


喜欢这篇文章吗?成为 中等会员 继续无限学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@marvinlanhenke/membership
参考资料/更多资料:
- SVM 教程
- 加雷斯·詹姆斯,丹妮拉·威滕,特雷弗·哈斯蒂,罗伯特·蒂布拉尼。《统计学习导论:在 r .纽约的应用》: Springer,2013 年。
用 Python 实现各种 NLP 文本表示
原文:https://towardsdatascience.com/implementing-various-nlp-text-representation-in-python-84781da0ec2d
一键编码、单词包、N-Grams 和 TF-IDF

图片由 阿玛多 上的 Unsplash
自然语言处理(NLP)是处理语言和语义的机器学习的子集。机器通过训练来学习单词的语义,就像典型的机器学习是如何工作的一样。当我们意识到几乎所有常用的机器学习模型都只能接受数字输入时,问题就出现了。因此,为了使用文本数据训练机器,我们必须找到一种将文本表示为数字向量的方法。本文将演示一些简单的数字文本表示,以及如何使用 Python 实现它们。
对于本教程,我们将使用以下数据。这个数据的背景是一门大学学科的复习。我对数据进行了预处理,即去除停用词、标点符号和词条释义。这些文字都是虚构的。
我们正在使用的数据
让我们从最简单的表示开始。我们用熊猫来称呼上面的数据,
df = pd.read_csv("data.csv")
1。一键编码(OHE)
这个模型的想法是列出所有文本中存在的每个单词。我们可以使用下面的函数做到这一点。
运行下面的代码,
BagOfWords(df["Comments"])
会返回数据中所有唯一的单词,
array(['although', 'amazed', 'bad', 'beautiful', 'bob', 'caution', 'clear', 'concept', 'definitely', 'difficult', 'experience',
'fantastic', 'find', 'first', 'found', 'funny', 'hard', 'however', 'implication', 'interesting', 'jim', 'learned', 'lecturer', 'lot', 'much', 'order', 'people', 'practical', 'really', 'reasonably', 'revision', 'rewarding', 'rubbish', 'scraped', 'spent', 'still', 'subject', 'take', 'taking', 'taught', 'terrible', 'though', 'time', 'trying', 'understand', 'warned', 'way'], dtype='<U32')
然后,我们将这些单词中的每一个设置为新数据帧中的列,如果该单词存在于文档中,则将值设置为 1 ,否则设置为 0 。
我们使用下面的函数来这样做,
第 7 行是这个表示的要点,说明如果文档中有一个单词,则将该值设置为 1 否则设置为 0 。
运行这些代码行将把 df 转换成 OHE 表示,
BoW = BagOfWords(df["Comments"])
dfOHE = TransformOHE(df, BoW)
变量dfOHE现在是data.csv的独热编码表示。
2.单词袋(蝴蝶结)
弓形文本表示类似于 OHE 表示。我们首先列出语料库中的每个单词。我们使用与上面相同的BagOfWords()函数。
在 OHE 表示中,如果一个单词存在于文档中,我们将该值设置为 1 ,否则设置为 0 。在 BoW 表示中,我们计算文档中出现的单词数。
举例。对于文档“蓝天碧海”,单词“蓝色”的 OHE 表示将返回 1 ,单词“蓝色”的弓形表示将返回 2 。
下面的函数将把我们的数据帧转换成 BoW 表示。
第 7 行是这种表示的要点,说明一个单词(列)的条目值是该单词在文档条目中的计数。
运行这些代码行将 df 转换成 BoW 表示,
BoW = BagOfWords(df["Comments"])
dfBoW = TransformBoW(df, BoW)
变量dfBoW现在是data.csv的单词包表示。
3.N-Grams
当使用弓形表示时会出现一个问题。字数统计的使用有时可能会忽略句子的语义。
举例。句子“a pple 击败竞争对手”和“c 竞争对手击败苹果”将具有精确的 BoW 文本表示。相比之下,它们的含义完全相反。
为了解决这个问题,我们可以将单词袋方法修改为短语袋方法。我们可以计算短语的出现次数,而不是计算每个单词的出现次数。
我们可以将任何短语定义为任何 N 个连续单词的组合,其中用户指定 N 的值。
为了演示这是如何做到的,让我们以下面的文档为例,“给予比接受更好”,
这份文件的标题是,
["giving", "better", "than", "receiving"]
一袋 2 克的文件,
["giving better", "better than", "than receiving"]
该文件的 3 克袋的袋子是,
["giving better than", "better than receiving"]
然后,我们将所有唯一的 N 元语法设置为一列,并计算文档中出现的短语。这种方法理论上比 BoW 更好,因为它保留了句子的语义。
举例。为了展示这种模式的优势,使用同一个句子,“一个苹果公司打败了竞争对手”,这个句子的 2 个字母的表达是“苹果公司打败了”和“打败了竞争对手”。与“竞争对手击败苹果”的 2-gram 表示相比,其具有“竞争对手击败”和“击败苹果”的 2-gram 表示。现在应该很明显,这些 2-gram 表示保留了句子的语义。
我们使用下面的函数来得到这袋 N-grams。
运行下面的代码,
BagOfNGrams(df["Comments"], 2)
会输出下面的数组,
array(['although warned', 'amazed much', 'bad experience', 'bad lecturer', 'beautiful clear', 'bob bad', 'bob terrible', 'caution taking', 'clear practical', 'concept still', 'definitely hard', 'difficult first', 'difficult however', 'difficult subject', 'experience bob', 'fantastic subject', 'find subject', 'first take', 'found reasonably', 'funny lecturer', 'hard rewarding', 'hard spent', 'hard subject', 'however lot', 'however really', 'interesting subject', 'jim funny', 'learned revision', 'lecturer amazed', 'lecturer rubbish', 'lot concept', 'much learned', 'order found', 'people hard', 'practical implication', 'really beautiful', 'really hard', 'reasonably difficult', 'rewarding although', 'rubbish subject', 'scraped though', 'spent time', 'still hard', 'subject however', 'subject jim', 'subject really', 'subject scraped', 'subject taught', 'subject way', 'take caution', 'taking subject','taught difficult', 'terrible lecturer', 'though bad', 'time trying', 'trying understand', 'understand concept', 'warned people', 'way order'], dtype='<U32')
像 BoW 一样,我们将这些 N 元文法中的每一个都设置为列,并计算它们在文档中的出现次数。我们可以回收TransformBoW()函数来做转换。我将重写下面的函数,
运行这几行代码会将 df 转换成 N-grams 表示,
# Your choice of N
N = 2BoN = BagOfNGrams(df["Comments"], N)
dfBoN = TransformBoN(df, BoN)
变量dfBoN现在是data.csv的 N 元表示。
4.TF-IDF
使用前面三种表示法的另一个问题是,较长的文档比较短的文档有更多的非零条目。这是一个问题,因为我们不能假设越长的文档在一定范围内得分越高。
举例。句子“精彩的主题”和“这是一个很棒的主题,清晰的解释,吉姆是一个很好的讲师”在积极程度上可以说是相似的。但是使用 BoW 或 N-grams 表示会使模型偏向较长的文档。
为了解决这个问题,我们引入了 TF-IDF 文本表示。TF-IDF 代表“术语频率和逆文档频率”。我们通过将两个部分相乘来计算 TF-IDF,这两个部分是术语频率和逆文档频率。
类似于前面的表示,TF-IDF 表示的每一列是语料库的单词包中的每个单词。
对于单词包中的每个单词 t (列),我们计算,
- 词频:该组件计算单词 t 在文档中的出现次数,类似于 BoW 表示。然后,我们对其进行“标准化”,以适应文档的长度。

检索词频率
- 逆文档频率:文档频率计算有多少文档包含单词 t 。逆文档频率将 N (文档总数)除以文档频率。我们通常采用 IDF 的对数值,以减少大型数据集对 IDF 值的影响。

逆文档频率
上面的 IDF 公式很好,假设单词包中的每个单词至少出现在一个文档中。如果您在训练数据上拟合 TF-IDF 并使用它来转换测试数据,情况可能就不一样了。换句话说,您正在使用训练数据的单词包来计算测试数据的 TF-IDF。因此,一些单词可能不会出现在任何文档中。在这种情况下,我们得到一个除以零的误差。为了防止这种情况,我们可以对 IDF 公式进行平滑处理。有许多关于不同平滑方法的文献。我下面提供的方法是scikit-learn库的默认方法。

平滑反转文档频率
因此,为了计算列(word) i 和行(document) j 的 TF-IDF,我们使用以下公式:

TF-IDF
正如我前面提到的,我们需要从语料库中获取单词包。我们可以再次使用我们的BagOfWords()函数。
我们使用下面的函数计算 TF-IDF,
运行这几行代码会将 df 转换成 N-grams 表示,
BoW = BagOfWords(df["Comments"])
dftf = TFIDF(df, BoW)
变量dftf现在是data.csv的 TF-IDF 表示。
如果需要应用平滑,记得在TFIDF()功能中指定smoothing = True。您可以修改我们的代码,在 TF-IDF 中使用 n 元语法。
恭喜
我们已经完成了这个关于基本文本表示的简单教程。我鼓励你探索更复杂的表示,比如word2vec或doc2vec。访问我的 GitHub 库查看数据集、表示输出和完整代码。如果你喜欢这篇文章,请考虑鼓掌并关注。请在评论中分享你的反馈和想法。感谢您的阅读!
用 Python 实现各种求根算法
原文:https://towardsdatascience.com/implementing-various-root-finding-algorithms-in-python-67917ef090b3
通过在数据科学和逻辑回归中实际应用

Doggo 寻找根
在数据科学中,你会发现我们的许多任务包括最大化或最小化统计数据。在回归分析中,你会找到使误差平方和最小化的参数。在朴素贝叶斯中,你识别出最大化后验概率的类别。还有很多其他的例子,比如决策树最大化信息增益,SVM 最大化余量,EM 算法最大化完全对数似然的期望,等等。这些计算中有一些非常基本,简单的代数就能很好地完成。然而,一些更复杂的计算需要数值算法来近似它们。
用于机器学习的最流行的优化算法当然是梯度下降算法。但是也有其他方法可以在数值上逼近一个函数的最大值或最小值。本文将向您展示一些可以替代梯度下降算法的求根算法。注意,每种求根算法都有特定的收敛要求;因此,没有一种算法可以普遍适用于所有问题。
求根算法是对满足任何连续函数 f(x) 的 f(x) = 0 的 x 值进行近似的数值方法。设 g(x) 为 f(x) 的导数。然后最大化或最小化 f(x) 可以通过找到 g(x) 的根来完成,其中 g(x) = 0 。我们使用求根算法来找到这些根。
注意,这里给出的所有算法都是迭代算法。因此,如果我提到“当前的 x 的值”,它仅仅意味着当前迭代的值。
对于我们所有的例子,我将在下面使用这个函数,

我们的主要功能
我将试图找出这个函数的根。如果你想最大化或最小化,你必须先求导。
1.定点方法
为了让我们开始,我选择了最简单的算法(在我看来)来让你感受一下求根算法是如何工作的。这个算法的思想是,在你设置了 f(x) = 0 之后,你要把剩下的方程排列成下面的形式 x = g(x) 。让我向您展示如何使用我们的函数来实现这一点,

x = g(x)格式的最终结果
请注意,您可以为任何 f(x) 找到多个 g(x) ,每个 g(x) 具有不同的收敛特性。接下来是从迭代的角度来看 x = g(x) 方程。

就迭代而言,x = g(x)
LHS 是 x,的下一次迭代更新,RHS 包含 x 的当前值。
让我们重述一下,要使用定点迭代找到 f(x) 的根,您必须:
- 设置 f(x) = 0
- 重新排列为 x = g(x)
- 设置一个初始值 x⁰
- 将 x 改为 g(x) 进行更新
- 如果 |f(x)| 的值不小于εϵ,则转到步骤 4
你们中的一些人可能通过查看代码更容易理解这个概念,所以下面是我在 Python 中是如何做的,
我们不需要在定点方法的代码中指定我们的 f(x)函数,只需要指定 g(x) 形式。
如果我调用下面的代码,
FixedPoint(g, 4)
我会得到
2.965794554141745
定点方法到此结束。
2.牛顿-拉夫森方法
牛顿-拉夫森方法是最常用的求根方法之一,在数据科学中有实际应用。我们将在后面探索这个应用程序。
牛顿-拉夫森方法的思想是,给定我们当前的 x 的值,我们想要在 x 处画出 f(x) 的切线。然后我们在切线与 x 轴相交的截距处设置 x 的下一个值。
让我们做数学。回想一下在初中数学课上,我们被告知给定在 xy 平面上的 2 个点的坐标,我们可以用这个公式求出通过这 2 个点的直线的斜率,

梯度公式
把这个转化成我们的问题,我们有两组坐标。我们的第一个坐标是我们当前迭代的 x 及其在函数中对应的 y 值,或者简称为 f(x) 。我们的第二个坐标是新的 x 值及其对应的 y 值。但是回想一下,我们将新的 x 值设置为切线和 x 轴的交点,这意味着它的 y 值将始终是 0 。

我们的坐标
给定这些坐标,我们想要找到通过这些点的线的梯度。梯度就是由集合定义在当前 x 值处评估的 f(x) 的导数。

获得牛顿-拉夫逊公式
计算的最后一行是牛顿-拉夫森公式。概括地说,这个算法的步骤非常简单,
- 设置一个初始值 x⁰
- 给定 x 的电流值,找到 f(x) 和f’(x)
- 使用牛顿-拉夫森公式更新 x
- 如果 |f(x)| 的值不小于εϵ,则转到步骤 2
为了将此代码化,
注意,我使用了一个外部库来计算导数。如果能手动计算导数就更理想了。
运行下面的代码行,
NewtonRaphson(f, 4)
会回来
2.9657944029135495
这就结束了牛顿-拉夫森方法。
3.割线法
割线法是牛顿-拉夫逊法的一种近似。我们不是使用 x 的当前值来计算 x 的下一个值,而是使用 x 的当前值和先前值来计算 x 的下一个值。当函数的导数很难得到时,我们就用割线法。
为此,我们使用在当前值 x 处评估的导数 f(x) 的以下近似值。

割线法导数逼近
注意,这个近似值使用了当前和先前的 x 的迭代。然后我们把这个近似值代入牛顿-拉夫森公式。

从牛顿-拉夫逊到割线
最后一行是割线公式,我们将用它来逼近函数的根。让我们回顾一下,
- 设定一个初始值 x⁰ 和 x ,表示 x 的先前和当前值
- 给定 x 的最后两个值,使用割线公式更新 x
- 如果 |f(x)| 的值不小于εϵ,则转到步骤 2
为了将此代码化,
运行下面的代码行,
SecantMethod(f, 2, 4)
会回来
2.9657943958960433
割线法到此结束。
4.二分法
二分法是一种括号法,这意味着它需要两次初始猜测。但是与割线法不同,在割线法中,最初的两次猜测是连续的,二分法要求最初的两次猜测将根括起来。
设 L 为下界猜测,为上界猜测。大意是如果f(Lf(U)<0或者换句话说,f(L)和f(***U我们用一种有点类似于二分搜索法的方法来求这个根。注意,这意味着 L 和 U 必须是不同的符号。*****
设 R 为我们当前对 f(x)的根的猜测,我们设 R 为 L 和 U 的中点。

二分法更新公式
如果 R 不是 f(x)的实际根,我们将需要评估根是在 R 之下还是之上。我们可以使用与之前类似的逻辑对此进行测试,即测试是否f(L)f(R)<0 和 f(R)f(U<0)如果前者成立,那么我们希望将搜索区域限制在 L 和 R 内,因此我们更新**=R并保持 L 的值。同样如果后者成立,我们更新L=R并保持 U 的值。我们一直这样做,直到f(R)足够接近零。**
让我们回顾一下,
- 设置一个初始化的猜测值 L 和 U ,其中至少有一根 R 必须在区域L<R<之间
- 使用二分法更新公式将 R 设置为 L 和 U 的中点。
- 检查是否有f(L)f(R)<0 和 f(R)f(U
- 如果是前者,设置U=R并保持 L 的值。如果是后者,设置L=R并保持 U 的值。
- 如果|f(r)|的值不小于εϵ,则转到步骤 2
让我们把它编码,
运行下面的代码行,
*BisectionMethod(f, 0, 5)*
会回来
*2.96579440290151*
这就结束了二分法。
5.假定位法
对于假定位法,我会非常简要的说明一下。该方法与二分法相同,除了我们在每次迭代中使用不同的公式更新 R 。

错误位置更新公式
这个更新和对分更新的区别在于,这个考虑的是f(L)和f(U)哪个更接近零。
我们可以从二分法中回收代码来提高效率。但是为了清楚起见,我将再次给出完整的代码。
运行下面的代码行,
*FalsePosition(f, 0, 4)*
会回来
*2.965794050755957*
这就结束了伪定位方法。对于本文中的所有算法,您可以通过设置一个较小的 epsilon 值来增加近似的准确性,以换取运行时间。
真实数据科学应用
这是一个关于牛顿-拉夫森方法如何用于数据科学的数学解释推导。如果你只是在寻找关于求根方法的解释,跳过这一节。
我确实提到过牛顿-拉夫森方法通常用于数据科学。事实上,它被用来估计广义线性模型(GLM)的参数。最流行的 GLM 当然是逻辑回归(LR)。我将演示在 LR 拟合中使用牛顿-拉夫森方法。
LR 以下面的方式模拟结果的概率,

逻辑回归
拟合 LR 包括找到使其似然性或对数似然性最大化的所有β参数。为了简单起见,我们通常选择后者。

似然和对数似然
请注意,估计概率(或等式中的 p-hat)是β的函数。因此,我们现在的任务是找到使对数似然最大化的β。
回想一下牛顿-拉夫森公式,

牛顿-拉夫逊求根公式
这个公式用来求一个函数 f(x) 的根。如果我们想找到使函数最大化的 x ,我们需要在f’(x)上应用这个函数。牛顿-拉夫森公式因此被更新为:

函数最大化的牛顿-拉夫逊公式
将此转化为我们的 LR 问题,

LR 的牛顿-拉夫逊公式
注意到,

衍生产品的定义
一阶导数只是由每个参数导出的对数似然的向量( n×1 )。二阶导数等于负雅可比矩阵( n×n )。负雅可比矩阵的逆也是一个( n×n )矩阵。当二阶导数的倒数乘以一阶导数时,我们得到一个( n×1 )向量。
如果我们用雅可比矩阵相对于 y 的期望值来代替雅可比矩阵,这个算法就叫做 Fisher Scoring 。

费希尔信息
费希尔评分算法现在可以定义为:

费希尔评分
估计参数现在只是这个费希尔评分公式的迭代。如果你使用 R(编程语言)通过远程包来做你的 GLMs,默认的参数估计技术是 Fisher 评分算法。

以 R 表示的 GLM 输出
注意最后一行陈述了费希尔评分迭代的次数。这仅仅意味着牛顿-拉夫森方法的迭代次数。
恭喜
你已经完成了基本求根算法的所有解释。特别感谢那些完成并理解逻辑回归部分的人。在 GitHub 上找到完整的代码。如果你喜欢这篇文章,请考虑鼓掌并关注。请在评论中分享你的反馈和想法。感谢您的阅读!
从头开始在 PyTorch 中实现 Word2vec
原文:https://towardsdatascience.com/implementing-word2vec-in-pytorch-from-the-ground-up-c7fe5bf99889
训练 Word2vec 模型的分步指南

布雷特·乔丹在 Unsplash 上的照片
介绍
自然语言处理(NLP)的一个重要组成部分是将单词、短语或大量文本翻译成连续数字向量的能力。有许多技术可以完成这项任务,但在本文中,我们将重点关注 2013 年发布的一项技术,名为 word2vec。
Word2vec 是 Mikolov 等人在一篇题为向量空间中单词表示的高效估计的论文中发表的算法。这篇文章值得一读,尽管我将在 PyTorch 中从头开始构建它时提供一个概述。简单地说,word2vec 使用单个隐藏层人工神经网络来学习密集的单词嵌入。这些单词嵌入允许我们识别具有相似语义的单词。此外,单词嵌入允许我们应用代数运算。例如"Vector(' King ')-Vector(' Man ')+Vector(' Woman ')产生最接近单词 Queen" 的矢量表示的矢量(“矢量空间中单词表示的有效估计”2)。

图 1:单词嵌入示例— 演职员表:https://thegradient.pub/nlp-imagenet/
图 1 是三维单词嵌入的例子。单词嵌入可以学习单词之间的语义关系。“男-女”的例子说明了“男人”和“女人”之间的关系与“国王”和“王后”之间的关系非常相似。句法关系可以通过嵌入来编码,如“动词时态”的例子所示。
单词嵌入概述
在我们进入模型概述和 PyTorch 代码之前,让我们先来解释一下单词嵌入。
为什么我们甚至需要单词嵌入?
计算机只是抽象的计算器。他们在数学计算方面确实很有效率。任何时候我们想向计算机表达我们的思想,我们必须使用数字语言。如果我们想了解 yelp 评论的观点或一本流行书籍的主题,我们需要首先将文本转换成向量。只有这样,我们才能使用后续程序从文本中提取感兴趣的信息。
最简单的单词嵌入
单词嵌入正是我们如何将我们的思想翻译成计算机可以理解的语言。让我们来看一个取自维基百科关于 python 的文章的例子,该文章称“python 一直是最受欢迎的编程语言之一。” 这个句子包含 11 个单词,那么我们为什么不创建一个长度为 11 的向量,其中如果单词存在,每个索引取值 1,如果不存在,取值 0?这就是俗称的一键编码。
python = [1,0,0,0,0,0,0,0,0,0,0]
consistently = [0,1,0,0,0,0,0,0,0,0,0]
ranks = [0,0,1,0,0,0,0,0,0,0,0]
as = [0,0,0,1,0,0,0,0,0,0,0]
one = [0,0,0,0,1,0,0,0,0,0,0]
of = [0,0,0,0,0,1,0,0,0,0,0]
the = [0,0,0,0,0,0,1,0,0,0,0]
most = [0,0,0,0,0,0,0,1,0,0,0]
popular = [0,0,0,0,0,0,0,0,1,0,0]
programming = [0,0,0,0,0,0,0,0,0,1,0]
languages = [0,0,0,0,0,0,0,0,0,0,1]
这种将单词转换成向量的方法可以说是最简单的。然而,有一些缺点将为 word2vec 嵌入提供动力。首先,嵌入向量的长度随着词汇表的大小线性增加。一旦我们需要嵌入数百万个单词,这种嵌入方法在空间复杂度方面就成了问题。第二个问题是这些向量是稀疏的。每个向量只有一个值为 1 的条目,所有剩余条目的值为 0。同样,这是对内存的极大浪费。最后,每个单词向量与所有其他单词向量正交。因此,没有办法确定哪些单词最相似。我认为“python”和“编程”这两个词应该比“python”和“ranks”更相似。不幸的是,这些单词中的每一个的向量表示都与其他向量完全不同。
改进的单词嵌入
我们的目标现在更加精确了:我们能否创建固定长度的嵌入,让我们能够识别哪些单词彼此最相似?一个例子可能是:
python = [0.5,0.8,-0.1]
ranks = [-0.5,0.1,0.8]
programming = [0.9,0.4,0.1]
如果我们取“python”和“ranks”的点积,我们会得到:

如果我们取“python”和“编程”的点积,我们会得到:

由于“python”和“ranks”之间的得分低于“python”和“编程”之间的得分,我们会说“python”和“编程”更相似。通常,我们不会使用两个嵌入之间的点积来计算相似性得分。相反,我们将使用余弦相似度,因为它消除了向量范数的影响,并返回更标准化的分数。无论如何,我们面临的一次性编码方法的两个问题都解决了——我们的嵌入向量是固定长度的,它们允许我们计算单词之间的相似度。
Skipgram Word2Vec 架构
既然我们已经掌握了单词的嵌入,问题就变成了如何学习这些嵌入。这就是 Mikolov 的 word2vec 模型发挥作用的地方。如果你对人工神经网络不熟悉,下面的部分会不清楚,因为 word2vec 基本上是基于这种类型的模型。如果你对这些材料不熟悉,我强烈推荐你去看看迈克尔·尼尔森的免费在线深度学习和神经网络课程和 3Blue1Brown 的 YouTube 系列关于神经网络的课程。
skip 程序
回想一下前面的句子,“python 一直是最流行的编程语言之一。” 想象有人不知道“编程”这个词,想弄清楚它的意思。一个合理的方法是检查相邻的单词,以获得关于这个未知单词的意思的线索。他们会注意到它被“流行”和“语言”所包围。这些话可以给他们一个暗示,暗示“编程”的可能含义。这正是 skipgram 模型的工作方式。最终,我们将训练一个神经网络来预测相邻的上下文单词,给定一个输入单词。在图 2 中,绿色单词是未知的目标单词,它周围的蓝色单词是我们的神经网络将被训练来预测的上下文单词。

图 2: Skipgram 方法
在本例中,窗口大小为 2。这意味着每个目标单词将被两个上下文单词包围,模型需要预测这两个上下文单词。由于单词“rank”在左边有 2 个单词,在右边有 2 个单词,因此得到的训练数据是该目标单词的 4 个例子。
模型架构
用于学习这些单词嵌入的神经网络是单隐层前馈网络。网络的输入是目标单词。标签是上下文单词。单一的隐藏层将是我们选择嵌入话语的维度。对于这个例子,我们将使用 300 的嵌入大小。

图 3: Skipgram 模型架构
让我们通过一个例子来说明这个模型是如何工作的。如果我们想嵌入一个单词,第一步是找到它在词汇表中的索引。然后,该索引作为嵌入矩阵中的行索引被传递给网络。在图 3 中,输入单词是词汇向量中的第二个条目。这意味着我们现在将进入绿色嵌入矩阵的第二行。这一行的长度为 300 —嵌入维度,N。然后,我们将这个隐藏层的向量乘以形状为N x V的第二个嵌入矩阵,得到长度为V的向量。
注意,在第二个嵌入矩阵(紫色矩阵)中有V列。这些列中的每一列代表词汇表中的一个单词。将矩阵乘法概念化的另一种方法是认识到它导致目标单词(隐藏层)的向量和词汇表中的每个单词(紫色矩阵的列)之间的点积。结果是一个长度为V的向量,代表上下文单词预测。因为我们的上下文窗口大小是 2,我们将有 4 个长度为V的预测向量。然后,我们将这些预测向量与相应的地面真实向量进行比较,以计算我们通过网络反向传播以更新模型参数的损失。在这种情况下,模型参数是嵌入矩阵的元素。稍后将在 PyTorch 代码中详细讨论这个训练过程的机制。
负采样

照片由 Unsplash 上的 Edge2Edge Media 拍摄
在 Mikolov 等人题为单词和短语的分布式表示及其组合性的论文中,作者提出了对原始 word2vec 模型的两种增强——负采样和子采样。
在图 3 中,注意每个预测向量的长度V。将与每个预测向量进行比较的基础真实向量也将具有长度V,但是基础真实向量将非常稀疏,因为向量中只有单个元素将被标记为 1——模型被训练来预测的真实上下文单词。这个真实上下文单词将被称为“正面上下文单词”。词汇表中的每隔一个单词,即V — 1单词,将被标记为 0,因为它们不是训练示例中的上下文单词。所有这些单词将被称为“负面上下文单词”。
Mikolov 等人提出了一种叫做负采样的方法,这种方法减少了真实向量的大小,从而减少了预测向量的大小。这降低了网络的计算要求并加速了训练。Mikolov 提出了一种方法,使用条件概率分布从现有的V — 1否定上下文单词中采样少量否定上下文单词,而不是使用所有否定上下文单词。
在提供的代码中,我实现了一个负采样过程,它不同于 Mikolov 提出的方法。它构造起来更简单,并且仍然能够产生高质量的嵌入。在 Mikolov 的论文中,选择负样本的概率是基于在目标词的上下文中看到候选词的条件概率。因此,对于词汇表中的每个单词,我们将为词汇表中的每个其他单词生成一个概率分布。这些分布表示在目标单词的上下文中看到另一个单词的条件概率。然后,负上下文单词将以与上下文单词的条件概率成反比的概率被采样。
我以稍微不同的方式实现了负采样,避免了条件分布。首先,我找到了词汇表中每个单词的出现频率。我通过求整体频率忽略了条件概率。然后,用与单词频率成比例的词汇索引来填充任意大的负采样向量。例如,如果单词“is”包括语料库的 0.01%,并且我们决定负采样向量的大小应该是 1,000,000,则负采样向量的 100 个元素(0.01% x 1,000,000)将用单词“is”的词汇索引来填充。然后,对于每个训练示例,我们从负采样向量中随机采样少量元素。如果这个小数字是 20,词汇表是 10,001 个单词,我们只是将预测向量和基础真实向量的长度减少了 9,980 个元素。这种减少大大加快了模型训练时间。
二次抽样
子采样是 Mikolov 等人提出的另一种方法,用于减少训练时间和提高模型性能。产生二次抽样的基本观察结果是,高频词“提供的信息价值比稀有词少”(“词和短语的分布式表示及其组成性”4)。例如,像“是”、“该”或“在”这样的词经常出现。这些词极有可能与许多其他词同时出现。这意味着这些高频词周围的上下文词几乎不传递关于高频词本身的上下文信息。因此,我们不是使用语料库中的每个词对,而是以与词对中词的频率成反比的概率对词进行采样。确切的实现细节将在下一节中解释。
PyTorch 实现
在概述了单词嵌入、word2vec 架构、负采样和子采样之后,让我们深入研究一下代码。请注意,有些框架已经抽象出了 word2vec 的实现细节。这些选项非常强大,并为用户提供了可扩展性。例如, gensim 提供了一个 word2vec API ,它包含了额外的功能,比如使用预训练模型和多词 n 元语法。然而,在本教程中,我们将创建一个 word2vec 模型,而不利用任何这些框架。
我们将在本教程中回顾的所有代码都可以在我的 GitHub 上找到。请注意,随着我的工作,存储库中的代码可能会发生变化。出于本教程的目的,这段代码的简化版本将在 Google Colab 笔记本中呈现。
获取数据
我们将使用 PyTorch 提供的名为 WikiText103 的维基百科数据集来训练我们的 word2vec 模型。在下面的代码中,您将看到我如何导入和打印数据集的前几行。第一段文字来自维基百科关于女武神编年史三的文章。
设置参数和配置
现在,我们将设置参数和配置值,它们将在代码的其余部分中使用。这个片段中的一些参数与笔记本后面的代码相关,但是请耐心等待。
这里我们构造了一个 dataclass,它包含定义 word2vec 模型的参数。第一部分控制文本预处理和 skipgram 构造。我们将只考虑出现至少 50 次的单词。这由MIN_FREQ参数控制。SKIPGRAM_N_WORDS是我们在构建 skipgrams 时要考虑的窗口大小。这意味着我们将查看目标单词前后的 8 个单词。T 控制我们如何计算二次抽样概率。这意味着频率在第 85 个百分位数的单词被二次抽样的概率很小,正如我们在上面的二次抽样部分所描述的。NEG_SAMPLES是用于每个训练示例的负样本数,如上面的负样本部分所述。NS_ARRAY_LEN是负采样向量的长度,我们将从中对负观测值进行采样。SPECIALS是不符合最低频率要求的词汇的占位符字符串。TOKENIZER指的是我们要如何将语料库中的文本转换成记号。“basic_english”标记器按空格分割所有文本。
第二部分定义模型配置和超参数。BATCH_SIZE是用于训练网络的每个迷你批次中的文档数量。EMBED_DIM是我们将用于词汇表中每个单词的嵌入维度。EMBED_MAX_NORM是每个嵌入向量可以得到的最大范数。N_EPOCHS是我们将为其训练模型的时期数。DEVICE告诉 PyTorch 是使用 CPU 还是 GPU 来训练模型。CRITERION是使用的损失函数。损失函数选择的讨论将在我们讨论模型训练过程时继续。
积累词汇
为 word2vec 模型准备文本数据的下一步是构建词汇表。我们将构建一个名为Vocab的类,它将拥有允许我们查找单词的索引和频率的方法。我们还能够通过索引查找单词,并获得整个文本语料库中的单词总数。
不用回顾代码中的每一行,注意到Vocab类有stoi, itos,和total_tokens 属性以及get_index(), get_freq(),和lookup_token()方法。下面的要点将展示这些属性和方法的作用。
stoi 是一个字典,其中关键字是单词,值是关键字的索引和频率的元组。例如,单词“python”是第 13,898 个最常见的单词,出现了 403 次。这个单词在stoi字典中的条目是{"python": (13898, 403)}。itos类似于stoi,,但是它的关键字是索引值,因此“python”的条目将是{13898: ("python", 403)}.。total_tokens属性是整个语料库中的标记总数。在我们的例子中有 77,514,579 个单词。
get_index()方法将一个单词或单词列表作为输入,并返回这些单词的索引或索引列表。如果我们调用Vocab.get_index("python"),返回值是13898。get_frequency()方法将一个单词或单词列表作为输入,并以整数或整数列表的形式返回单词的频率。如果我们调用Vocab.get_freq("python"),返回的值是403。最后,lookup_token()方法接受一个整数并返回占据该索引的单词。例如,如果我们要调用Vocab.lookup_token(13898),该方法将返回"python"。
以上要点中的最后一个函数是yield_tokens()和build_vocab()函数。yield_tokens()功能对文本进行预处理和标记。预处理只是删除所有不是字母或数字的字符。build_vocab()函数获取原始的维基百科文本,对其进行标记,然后构造一个Vocab对象。同样,我不会检查这个函数中的每一行。关键是这个函数构造了一个Vocab对象。
构建我们的 PyTorch 数据加载器
这个过程的下一步是用二次抽样构建 skipgrams,然后为我们的 PyTorch 模型创建数据加载器。关于为什么数据加载器对于训练大量数据的 PyTorch 模型如此重要的概述,请查看文档。
这个类可能是我们所学过的最复杂的一个类,所以让我们从最后一个方法collate_skipgram()开始,彻底地检查每一个方法。我们首先初始化两个列表,batch_input和batch_output。这些列表中的每一个都将填充词汇索引。最终,batch_input列表将具有每个目标单词的索引,而batch_output列表将包含每个目标单词的肯定上下文单词索引。第一步是循环遍历批中的每个文本,并将所有标记转换为相应的词汇索引:
for text in batch:
text_tokens = self.vocab.get_index(self.tokenizer(text))
下一步检查以确保文本足够长以生成训练示例。回想一下前面的句子,“python 一直是最流行的编程语言之一。” 有 11 个字。如果我们将SKIPGRAM_N_WORDS设置为 8,那么 11 个单词长的文档是不够的,因为我们在文档中找不到一个单词,它前面有 8 个上下文单词,后面也有 8 个上下文单词。
if len(text_tokens) < self.params.SKIPGRAM_N_WORDS * 2 + 1:
continue
然后,我们创建一个目标单词列表和围绕该目标单词的所有上下文单词列表,确保我们始终拥有一个完整的上下文单词集:
for idx in range(len(text_tokens) - self.params.SKIPGRAM_N_WORDS*2):
token_id_sequence = text_tokens[
idx : (idx + self.params.SKIPGRAM_N_WORDS * 2 + 1)
]
input_ = token_id_sequence.pop(self.params.SKIPGRAM_N_WORDS)
outputs = token_id_sequence
现在,我们实现子采样。我们根据给定的频率查找目标词被丢弃的概率,然后根据该概率删除它。我们将很快看到如何计算这些丢弃概率。
prb = random.random()
del_pair = self.discard_probs.get(input_)
if input_==0 or del_pair >= prb:
continue
然后,如果前一步骤没有移除目标单词本身,我们对目标单词周围的上下文单词执行相同的子采样过程。最后,我们将结果数据分别添加到batch_input和batch_output列表中。
else:
for output in outputs:
prb = random.random()
del_pair = self.discard_probs.get(output)
if output==0 or del_pair >= prb:
continue
else:
batch_input.append(input_)
batch_output.append(output)
我们如何计算每个单词的丢弃概率?回想一下,我们对一个单词进行二次抽样的概率与该单词在语料库中的出现频率成反比。换句话说,这个词出现的频率越高,我们就越有可能从训练数据中丢弃它。我用来计算丢弃概率的公式是:

丢弃概率
这与 Mikolov 等人提出的公式略有不同,但它实现了类似的目标。小的区别是分母中的+t成分。如果+t被排除在分母之外,频率大于t的单词将被有效地从数据中移除,因为平方根中的值将大于 1。这是在_create_discard_dict()方法中实现的公式,它创建了一个 python 字典,其中的键是单词索引,值是丢弃它的概率。下一个问题是t从哪里来?回想一下我们的Word2VecParams有参数T。在我们的代码中,这个参数被设置为 85。这意味着我们找到第 85 个百分位数的词频,然后将t设置为该值。这有效地使得随机抽样频率在第 85 百分位或以上的单词的概率接近但略大于 0%。这个计算就是SkipGram类中的_t()方法所实现的。
创建我们的负采样阵列
定义 PyTorch 模型之前的最后一步是创建负采样数组。高级目标是创建一个长度为 5,000,000 的数组,并用与词汇表中单词的频率成比例的词汇表索引填充它。
_create_negative_sampling()方法完全按照上面的说明创建数组。唯一的细微差别是,如果一个单词的频率意味着它在负采样向量中应该少于 1 个条目,我们确保这个单词索引仍然存在于负采样数组的 1 个元素中,因此当我们对负上下文单词进行采样时,我们不会完全丢失这个单词。
sample()方法返回一个列表列表,其中外部列表中包含的列表数量等于批处理中的示例数量,内部列表中的样本数量是每个示例的负样本数量,我们已经在Word2VecParams数据类中将其设置为 50。
定义 PyTorch 模型
最后,我们可以在 PyTorch 中构建 word2vec 模型。构建 PyTorch 神经网络的惯用方法是在构造函数中定义各种网络架构层,并以一种称为forward()的方法通过网络向前传递数据。幸运的是,PyTorch 已经抽象出了更新模型参数的向后传递,因此我们不需要手动计算梯度。
PyTorch 模型总是从torch.nn.Module 类继承而来。我们将利用 PyTorch 嵌入层,它创建一个单词向量的查找表。我们在 word2vec 模型中定义的两层是self.t_embeddings,和self.c_embeddings,前者是我们感兴趣学习的目标嵌入,后者是图 2 中的次级(紫色)嵌入矩阵。这两个嵌入矩阵都是随机初始化的。
如果我们想要训练网络中的每个参数,我们可以在这一点上放弃负采样,并且forward方法会简单一点。但是,负采样已被证明可以提高模型精度并减少训练时间,因此值得实施。让我们深入向前传球。
让我们想象一下,我们有一个训练例子,我们通过一批。在这个例子中,inputs只包含与索引 1 相关的单词。正向传递的第一步是在self.t_embeddings表中查找这个单词的嵌入。然后,我们使用.view()方法对其进行整形,这样我们就有了一个单独的向量来表示通过网络的输入。在实际实现中,批量大小为 100。.view()方法为该批 100 个训练示例中的每个单词创建一个(1 x N)矩阵。图 4 将帮助读者想象出forward()方法的前四行是做什么的。

图 4:正向传递输入嵌入
然后,对于每个输入,我们需要获得上下文单词嵌入。对于这个例子,假设实际上下文单词与self.c_embeddings表的第 8 个索引相关联,而否定上下文单词与self.c_embeddings表的第 6 个索引相关联。在这个玩具示例中,我们只使用了 1 个阴性样本。图 5 是 PyTorch 下面两行代码的可视化效果。

图 5:向前传递上下文单词嵌入
目标嵌入向量的维数为(1 x N),我们的上下文嵌入矩阵的维数为(N x 2)。因此,我们的矩阵乘法产生了一个维度为(1 x 2)的矩阵。图 6 是forward()方法的最后两行完成的工作。

图 6:输入-上下文矩阵乘法
另一种概念化负采样正向传递的方法是将它视为目标单词和上下文中每个单词(正上下文单词和所有负上下文单词)之间的点积。在这个例子中,我们的正面上下文单词是词汇表中的第 8 个单词,而负面上下文单词是词汇表中的第 6 个单词。得到的(1 x 2)向量包含两个上下文单词的逻辑。因为我们知道第一个上下文单词是正面上下文单词,第二个上下文单词是负面上下文单词,所以该值对于第一个元素应该是大的,而对于第二个元素应该是小的。为了实现这一点,我们将使用torch.nn.BCEWithLogitsLoss作为损失函数。我们将在后面的章节中重新讨论损失函数的选择。
Model类中的后 3 个方法是normalize_embeddings()、get_similar_words()和get_similarity()。没有进入每个方法的细节,normalize_embeddings()方法缩放每个嵌入的单词,使得它是一个单位向量(即,具有 1 的范数)。get_similar_words()方法将获取一个单词,并返回前 n 个最相似单词的列表。使用的相似性度量是余弦相似性。换句话说,该方法将返回向量表示“最接近”感兴趣的单词的单词,这是通过两个向量之间的角度来测量的。最后,get_similarity()将取两个单词,并将返回两个单词向量之间的余弦相似度。
创建培训师
这个过程的最后一步是创建一个我称之为Trainer的类。Trainer类代码如下:
这个类将编排所有以前开发的代码来训练模型。这个类中的方法有train()、_train_epoch()、_validate_epoch()和test_testwords()。train()方法是我们将调用来开始模型训练的方法。它遍历所有的历元并调用_train_epoch()和validate_epoch()方法。在 epoch 训练和验证之后,它将通过调用test_testwords()方法打印出测试字,这样我们就可以直观地检查嵌入是否在改进。这个类中最关键的方法是_train_epoch()和_validate_epoch()方法。这些方法在功能上非常相似,但有一点小小的不同。下面我们来深究一下_train_epoch()的方法。
我们首先使用self.model.train()告诉模型它处于训练模式。这允许 PyTorch 在训练期间使某些类型的网络层如预期那样运行。这些层类型没有在这个模型中实现,但是通知 PyTorch 这个模型正在训练是一个最佳实践。下一步是循环遍历每一批,获取正面和负面上下文单词,并将它们发送到适当的设备(CPU 或 GPU)。换句话说,我们创建了context张量,它从我们用SkipGrams类构建的数据加载器中访问批量数据,并将其与我们用NegativeSampler类生成的负样本连接起来。接下来我们构建基础真理张量y。因为我们知道context张量中的第一个元素是正上下文单词,所有后续元素是负上下文单词,所以我们创建一个张量y,其中张量的第一个元素是 1,所有后续元素是 0。
现在我们已经有了输入数据、上下文数据和基本事实标签,我们可以执行向前传递了。第一步是告诉 PyTorch 将所有的渐变设置为 0。否则,每次我们通过模型传递一批数据时,都会添加梯度,这不是我们想要的行为。然后,我们用下面的代码行执行向前传递:
outputs = self.model(inputs, context)
接下来,计算损耗。我们使用torch.nn.BCEWithLogitsLoss目标函数,因为我们有一个二元分类问题,其中张量的第一个元素y是 1,后面的元素是 0。有关该损失函数的更多信息,请参考文档。Sebastian Raschka 的博客对 PyTorch 中的二元交叉熵损失有一个非常好的概述,也可以提供进一步的见解。
loss = self.params.CRITERION(outputs, y)
PyTorch 将在损失计算过程中自动计算梯度。梯度包含对模型参数进行微小调整和减少损失所需的所有信息。这种自动计算是在以下代码行中完成的:
loss.backward()
对模型参数的小更新在下面一行中完成。注意,我们使用的是[torch.optim.Adam](https://pytorch.org/docs/stable/generated/torch.optim.Adam.html) 优化器。 Adam 是最前沿的凸优化算法之一,是随机梯度下降的后代。我不会在这篇文章中详细介绍 Adam,但请注意,它往往是一种更快的优化算法,因为它利用了自适应学习和梯度下降。
self.optimizer.step()
_validate_epoch()方法与_train_epoch()方法相同,只是它不跟踪梯度,也不使用优化器步骤更新模型参数。这都是用线with torch.no_grad()完成的。此外,_validate_epoch()方法只使用验证数据,不使用训练数据。
把所有的放在一起
下面是完整的 word2vec 笔记本:
我用 GPU 在 Google Colab 实例中运行了这个笔记本。如你所见,我对模型进行了 5 个时期的训练,每个时期花费 42 到 43 分钟。所以,整个笔记本在不到 4 个小时的时间内就完成了。请随意使用它,并提供任何反馈或问题!
结果
经过不到 4 个小时的训练后,观察上面片段中的结果。除了减少损失,观察最相似的单词是如何随着纪元的训练而提高的。经过第一个纪元的训练,与军事最相似的前 5 个词分别是:的,的,虽然,是,任何。经过 5 个时代的训练,与军事最相似的前 5 个词分别是:军队、部队、军官、领导、和士兵。这与减少的损失一起,表明模型正在学习的嵌入准确地表示词汇中单词的语义。
感谢您的阅读!如果你觉得这有帮助,或者你有任何问题或顾虑,请留下评论。
结论
最后,我们回顾了带有负采样和子采样的 word2vec 的 PyTorch 实现。这个模型允许我们将单词转换成 n 维向量空间中的连续向量。学习这些嵌入向量,使得具有相似语义的单词被紧密地分组在一起。有了足够的训练数据和足够的训练时间,word2vec 模型也可以学习文本数据中的句法模式。单词嵌入是 NLP 的基础组件,在更高级的模型中非常关键,比如基于 Transformer 的大型语言模型。对 word2vec 有一个透彻的了解,对于进一步的 NLP 学习是一个极其有帮助的基础!
除特别注明外,所有图片均为作者所有。
参考
[1] T. Mikolov,K. Chen,G. Corrado 和 J. Dean,向量空间中单词表示的有效估计 (2013),谷歌公司。
[2] T. Mikolov,I. Sutskever , K. Chen,G. Corrado 和 J. Dean,单词和短语的分布式表示及其组合性 (2013),谷歌公司。
[3] S .拉什卡,损失教训 (2022),https://sebastianraschka.com
数据科学需要学习的重要数学主题
原文:https://towardsdatascience.com/important-math-topics-you-need-to-learn-for-data-science-4a58dba7fbe9
没有这些,你的潜力将受到严重限制

Artturi Jalli 在 Unsplash 上拍摄的照片
介绍
数学。
它总是房间里的大象:没有人想谈论它,但每个人最终都必须解决它。
从我的经验来看,问数据科学是否需要学数学是一个多余的问题。相反,这几乎总是一个关于你需要学习多少和什么类型的数学的问题。
作为一名数学出身的人,我可以说我在数学学位期间所学的大部分知识从未在现实生活中明确使用过。
那次我们必须证明毕达哥拉斯定理。不——我从来不需要它。
但这并不意味着你可以只掌握绝对的基础知识。问题是,你需要学习的数学知识会因你所追求的数据科学角色的类型而有很大的不同。
也就是说,我认为大多数入门级数据科学职位都需要最低数量的数学知识;这为从事数据科学和学习更高级的概念创造了良好、坚实的基础。
如果你想看些别的,你可以看看我下面的视频。
你可以看我在 YouTube 上关于这个话题的视频,而不是看这篇文章。
函数、变量和图形

丹-克里斯蒂安·pădureț在 Unsplash 上拍摄的照片
在进入更高级的主题之前,熟悉基础知识是很重要的。
对于大多数读到这里的人来说,你可能已经知道什么是函数、变量和图形。但是如果你没有,那么这些主题就构成了探索性数据分析和统计/机器学习建模等任务的基础。
当我在数据科学硕士学位期间学习机器学习时,不熟悉或忘记这些主题的学生在开始时会有更难的进展。
有几个学生很难画出简单的方程式和解释图表。没多久他们就捡起了这个,但其重要性不可低估。
统计数字

由 Unsplash 上的 Edge2Edge 媒体拍摄
基本的统计理解可能是数据科学中最重要的技能。
统计学是关于量化不确定性的。它让你严格地解释你的结果,从而帮助你做出更明智的决定。
概率论
一个基本的统计学主题是概率论:这是关于量化不确定性和理解随机性。
初级统计课程通常以这个主题开始,因为它构成了许多高级统计概念的基础;例如,它有助于理解统计分布、假设检验和推断统计。
如果你还没有统计学的基础,我建议你从概率论开始。
描述统计学
描述性统计用于分析和理解数据的基本特征。
我们使用描述性统计来理解:
- 数据的分布。
- 数据的中心趋势,即平均值、中值和众数。
- 数据的分布,即标准差和方差。
通过了解数据的基本构成,您将能够知道应用哪种统计方法。这对你的结果的可信度有很大的影响。
假设检验
顾名思义,假设检验就是检验你的假设的合理性。
这类似于 A/B 测试。区别在于 A/B 测试是一项随机对照试验:这是我们比较治疗组和对照组的地方,两组用户都是随机的。在假设检验中,我们将一组实验的结果与“无效”组的结果进行比较,看是否有任何统计上的显著差异。
假设检验评估你的实验结果的重要性,让你根据数据科学地提出问题。
回归
回归常用于预测和预报。
它模拟变量之间的关系,即一个因变量和一个或多个自变量。对于被视为回归模型的模型,因变量需要是连续的。
许多公司以某种方式使用回归来预测或预报每年发生的销售或季节性事件。
如果你很了解回归,那么它会对理解机器学习有很大的帮助,因为有很大的重叠。
模型评估
评估模型的性能在数据科学中极其重要。
在不知道使用哪个模型的情况下训练多个模型是没有意义的。能够评估您的统计或机器学习模型将为您提供一种合适的方式来选择最佳模型,以用于您的数据科学项目。
线性代数

罗宾·斯皮尔曼在 Unsplash 上的照片
像深度学习这样的机器学习算法的整个基础都是基于线性代数。因此,如果你想认真对待机器学习,这是一个重要的话题。
向量和矩阵
向量和矩阵是线性代数的基础。此外,大型数据集更容易处理,我们用向量和矩阵的形式表示它们;这在机器学习中至关重要。
在机器学习中,我们在成本函数、神经网络、支持向量机等等中使用它们。
如果你想编写更快的数据处理管道,像 NumPy 这样的流行 Python 库也可以非常高效地处理向量和矩阵。
特征向量和特征值
在熟悉了向量和矩阵之后,阅读特征向量和特征值是有意义的。
当我们把矩阵分解成最简单的表示时,我们得到了特征向量和特征值。这些为矩阵的性质提供了有价值的见解。正如我们已经说过的,以矩阵和向量的形式处理大型数据集要容易得多。
我们还需要特征向量和特征值来理解主成分分析(PCA),这是一种在减少信息损失的同时降低数据维度的技术。这是在大型数据集中查找要素的一项重要技术。
结石

微积分并不像统计学那样被广泛使用,但是我们需要它来解决最优化问题。你至少应该熟悉基本的导数和积分是如何工作的,因为它们是微积分的基础。
在机器学习中,我们经常谈论损失函数,损失函数有许多不同的类型。这些函数使用一种基于导数的技术,称为梯度下降,以找到最佳的参数集。因此,如果不了解衍生品是如何工作的,你就不会真正知道这些参数是如何计算出来的。
此外,神经网络在反向传播过程中使用积分,这是一种在给出预测后微调其权重的技术。大多数训练神经网络的人甚至不知道它为什么工作,但是通过理解积分,你将来会更容易理解它。
离散数学

Alexandre Debiève 在 Unsplash 上的照片
现代计算机科学几乎完全建立在离散数学的基础上。
这里有几个例子来说明这一点:计算机将数据存储为 0 和 1,它们使用布尔代数来对数据进行计算;低级编程语言依赖逻辑运算符;像区块链、密码学和计算机安全也使用数论。
算法复杂性
了解算法有多复杂将有助于你更好地了解它们需要运行多长时间,以及使用它们解决问题有多困难。
由于我没有计算机科学背景,这是我后来才知道的。你可能听说过这种“大 O 符号”。
集合论
在攻读数学学位期间,我一直认为集合论有点毫无意义,直到我开始学习关系数据库。
集合基本上是元素的集合。这些元素可以是任何种类的数学对象。在数据库的上下文中,您可以将集合看作一个表,它的元素是表中的行。
你不需要集合论来处理数据库,但是知道它绝对有好处。集合论有助于理解 SQL 连接是如何工作的,它将帮助您更好地优化数据库模型。
图论
图论是图形数据库的基础。这种类型的数据库用于对由节点和关系组成的数据进行建模。
社交网络就是一个很好的例子。每个人都是一个节点,每当有人“关注”另一个人时,这就是一种关系。
大量的社交网络数据保存在图形数据库中。要做任何社交网络分析,可能需要一些图形知识,以及如何在这种情况下应用算法。
结论
最后,数学在数据科学中是不可避免的。没有良好的数学基础,你作为数据科学家的潜力将受到严重限制。
希望这已经给了你一些关于从哪里开始和你实际需要学习多少的想法。当然,这也取决于你是什么样的数据科学家,或者打算成为什么样的数据科学家。
对于专家数据科学家来说,如果我错过了什么,请告诉我。一如既往,如果你喜欢这篇文章,你可以看看我在 YouTube 上的其他视频。如果你想通过电子邮件了解我在做什么,你可以考虑注册我的简讯!
原载于 2022 年 2 月 15 日【https://leonlok.co.uk】。
Python 中 Elasticsearch 8 的重要语法更新
原文:https://towardsdatascience.com/important-syntax-updates-of-elasticsearch-8-in-python-4423c5938b17
一些帮助你应对弹性搜索突变的建议

图片由皮沙贝的杰拉德拍摄
Elasticsearch Python 客户端库第 8 版有不少突破性的改动,在你从 7 版更新到 8 版的时候会给你带来很多麻烦。尽管这可能是一项痛苦的任务,但仍然建议将库更新到最新版本,因为已经添加了许多新功能,并且应该更加用户友好。在这篇文章中,我们将概述 Elasticsearch 8 的重要语法更新,这将有助于您重构代码,使其与最新版本的库一起工作。
准备
如果您想测试本文中演示的代码片段,最好在本地运行一个 Elasticsearch 服务器。我们将使用 Docker 启动 Elasticsearch 和 Kibana containers ,使用 Docker 图像的最新版本,在撰写本文时是 8.2.2 版,但应该也适用于 8 版的其他图像。
此外,我们需要安装 Elasticsearch Python 客户端库的版本 8。最好将它安装在一个虚拟环境中,这样它就不会影响你系统中现有的库。
对连接使用严格的客户端配置
在 Elasticsearch 版本 8 中,删除了方案、主机和端口的默认值,现在我们需要明确指定它们。否则会有一个ValueError:
有关客户端配置的更多示例,请查看此 GitHub 问题。
使用 Elasticsearch 而不是 IndicesClient 来管理指数
我们曾经使用[IndicesClient](https://elasticsearch-py.readthedocs.io/en/v7.12.0/api.html#indices)类来管理索引。然而,它已经过时了,我们现在应该使用Elasticsearch类来直接管理索引。实际上,在本文中,我们将使用上面创建的es_client对象来执行所有与索引相关的操作。
API 只允许关键字参数
在 Elasticsearch 版本 8 中,我们只能对所有 API 使用关键字参数。现在使用关键字参数将引发一个TypeError:
使用 client.options()指定传输参数
像ignore这样的每请求选项现在应该用client.options()来指定,而不是在 API 方法中。另外,ignore现在改名为ignore_status。
更多用client.options()指定的选项可在这里找到。
使用顶级参数而不是 body 字段
这可能是影响最大的变化。我们曾经使用body字段来指定所有与 Elasticsearch 相关的设置或搜索查询,如本文中的所示。现在我们需要将它们全部改为顶级参数。它不仅仅影响search API,而是影响所有的 API。例如,create API 需要更新如下:
如您所见,我们只需要将body参数扩展到顶级参数。如果你不想一个接一个地明确指定顶级参数,你可以使用字典扩展语法**[configuaration](https://gist.github.com/lynnkwong/3c5ed5b3225a1e4e56e9bc6b739881e2#file-elasticsearch-index-configurations-py)。这一变化同样适用于所有的 Elasticsearch APIs。
批量 API 的更新
当我们有大量文档需要索引时,我们可以使用bulk API 来批量加载它们。现在语法也不同了。首先,我们需要明确地指定索引。此外,我们应该使用operations而不是body参数来指定动作:
还要注意,在旧语法中,actions是一个长字符串,每个动作由一个换行符分隔。然而,在新的语法中,actions可以是作为字典的操作列表,这意味着我们现在不需要将每个操作作为 JSON 字符串转储。我们还可以使用带有新语法的filter_path参数来指定在响应中显示哪些字段。
API 响应现在是对象而不是字典
在以前的版本中,Elasticsearch APIs 的响应是字典,你通常通过resp['hits']['hits']得到结果。然而,在 Elasticsearch 8 中,响应不再是字典,而是类ObjectApiResponse的实例。它有两个属性,meta和body,分别用于访问请求元数据和结果。
神奇的是,我们仍然像以前一样直接访问hits键。但是,我们只能像访问字典键一样访问它,而不能访问对象属性。
这是为了与旧的行为保持一致。我认为这也可能会被否决,因此,更安全的方法是从body访问hits,如上所示。
使用更细粒度的错误类
在以前的版本中,TransportError既包括传输错误(如连接超时)也包括 API 错误(如未找到索引)。但在 Elasticsearch 版中,TransportError只覆盖了传输错误,新的ApiError需要用于 API 错误,对调试更有帮助。
在本帖中,我们介绍了在 Python 中使用 Elasticsearch 8 的一些重要语法更新。它们中的许多都是突破性的改变,这意味着你需要更新你以前的 Python 代码,以使它们适用于最新的版本。有了本文中展示的列表,代码的重构应该会简单得多。您也可以参考官方文档和 GitHub 问题来更好地了解变化的原因以及更多技术细节。
相关文章:
从 Python 模块导入对象可能有风险
原文:https://towardsdatascience.com/importing-objects-from-a-python-module-can-be-risky-59dac696c51f
您是否知道在导入原始对象后,您是否会处理它们?或许用他们的复制品?也许有些是原创,有些是复制品?

哪匹马是原作,哪匹是复制品?太难说了!来源:图片由作者提供。
您应该导入模块(import module)还是从模块(from module import obj1, obj2)导入对象并不是一个新问题。在这篇文章中,我不打算重新发明轮子。我们知道导入一个模块有更多的优点,但是在某些情况下,直接从一个模块导入对象也很好;可以让一个 app 快一点,代码可以短一点。如果你想更多地了解这个话题,马克·卢茨的书(Lutz 2013)提供了很好的阅读材料。
关于进口,有一个事实很容易忘记。这两个方法中的一个不从另一个模块导入 对象,而是由创建它们的副本。虽然通常这不会造成很大的差别,但有时会,而且差别会很大。
在本文中,我比较了从模块中复制对象和导入对象。我将展示导入一个模块相对于从一个模块导入对象的一个优点,这个优点有时可以省去你很多麻烦。
导入模块:原始对象的作用
假设我们有一个由三个模块组成的应用程序:
helpers,包含主应用中使用的变量和函数;action,包含应用的主要功能;__main__,负责运行应用程序。
这是helpers的内容:
我尽可能让它简单。现在action:
所以,run_app()是应用程序的主要功能。它将在负责运行应用程序的__main__ 模块中被调用。当然,在我们的例子中,__main__也过于简化了:
这符合预期。当我们导入helpers模块时,我们可以使用它的foo()函数和helpers_value全局变量。注意helpers_value不是大写,因为它不是常数;它只是一个全局变量。这里,“全局”是指模块的全局;但这是意料之中的:这就是 Python 中范围的工作方式(Lutz 2013,拉马尔霍 2022)。然后,我们改变helpers.helpers_value和helpers.foo,这个改变反映在原来的位置,也就是在helpers里。我们可以通过调用action.run_app()看到这一点,这个函数调用helpers.helpers_value和helpers.foo。
从模块导入:实际复制
现在,让我们改变在__main__中导入helpers对象的方式:
那是相当不同的!发生了什么事?
我们更改了helpers_value和foo,但这并不影响位于helpers的原始对象。这是因为import from创建了导入对象的副本。这一点至关重要,所以让我强调一下:
import from创建导入对象的副本。
因此,虽然我们对helpers_value和foo()的更改影响了这些副本,但它们不影响来自helpers模块的原始值。action.run_app()功能不使用这些副本;它使用来自helpers的原始对象,这就是为什么我们在两种情况下观察到不同的行为。
这是什么意思?
这意味着很多,也改变了很多。幸运的是,在大多数情况下,这些都不重要,因为我们很少在一个模块中改变另一个模块中的对象。然而,有时我们会,然后我们必须小心。
为了使事情变得更加复杂,我们需要考虑两种情况:从模块中导入不可变对象和导入可变对象。
不可变对象
当您从一个模块中导入一个不可变的对象时,就像在from helpers import helpers_value中一样,创建了该对象的一个副本。因此,您的全局名称空间现在将包含两个名为helpers_value : helpers.helpers_value和__main__.helpers_value的对象。你预料到会发生这种事吗?
同理,foo()功能也没有在原地改动:导入后有两个foo()功能:helpers.foo()和__main__.foo()。
因此,当我们导入一个模块(import helpers)时,我们直接从模块中使用原始的(不可变的)对象;当它们改变时,我们使用改变的对象。
尽管如此,当我们直接从一个模块(from helpers import foo, helpers_value)导入不可变对象时,Python 会制作它们的副本,我们使用这些副本——而不是原始对象。因此,当原始对象在模块中改变时,这不会影响我们正在使用的对象——因为我们正在使用它们的副本。
可变对象
可变对象不能被复制,对其原始对象的任何更改或所谓的副本都是对原始对象进行的。这是因为 Python 不会创建可变对象的副本;相反,会创建一个引用原始对象的新名称。
可变对象的这个特性影响了导入的工作方式。
让我们简化我们的应用程序。现在它将只包含一个对象,一个字典,这是一个可变的 Python 对象。所以,helpers如下:
action模块包含run_app()函数,它现在简单地返回helpers_value字典:
这是__main__模块:
你看到发生了什么吗?这一次,虽然我们从helpers导入了helpers_value,但是我们得到了一个不同的结果:原来的对象被就地改变了。
这是可变对象的典型行为。Python 不会创建它们的副本;相反,它会创建指向同一对象的不同名称。因此,更改分配给这些名称中任何一个的对象的值都会产生相同的效果:原始对象会受到影响。
合并导入
结合这两种类型的导入不会改变任何事情。不可变对象的行为类似于不可变对象,可变对象的行为类似于可变对象。让我们看看它是如何工作的。你可以把它当作一次练习。
这一次,我们将只使用两个模块,helpers和__main__。前者将定义两个对象,一个不可变,一个可变:
在__main__中,我们将以两种方式导入它们,然后改变对象。在继续之前,尝试猜测下面代码片段的输出。在每个print()之后,我在你应该猜测输出的地方添加了引号。
这是输出结果:
我希望你没弄错。理解导入对象和模块的这个方面将帮助您避免当您的应用程序包含几个具有某种依赖层次结构的模块时可能发生的奇怪错误(例如,module1导入module2,后者导入module3)。
摘要
正如我上面提到的,理解 Python 导入的这些复杂性应该有助于您在由几个模块组成的应用程序中避免错误。请记住:
import module使您能够将该模块中的对象用作module.obj。这意味着你使用原始对象,位于module的那个。因此,对这个对象的任何更改,在代码中的任何地方作为module.obj = …完成,都将反映在对module.obj的调用中。- 在不可变对象的情况下,
from module import obj创建对象的副本。这意味着该副本位于模块范围内(在您导入obj的模块中)。改变这个副本的值(即obj)不会影响原对象(module.obj)。 - 在可变对象的情况下,
from module import obj仅创建对象的新名称。因此,改变它的值(即obj = …)会影响原来的对象(module.obj)。 - 因此,导入模块(
import module)而不是从中导入对象(from module import obj)更安全。这是因为您总是在直接位于模块中的对象上工作,而不会创建它们的副本。因此,跟踪原始对象(位于module的对象)发生的事情就足够了,无论其他各种模块对这个对象做什么,它们都会对原始对象这样做。当你需要创建一个副本时,你可以在你需要的地方显式地做。
最后一点也许是最关键的。当你确实需要一个副本时,创建它,但是在你需要这个副本的地方明确地做它。通过特定的导入方式来创建副本,肯定是隐性的、间接的、不明确的。初学者甚至一些中级 python 爱好者可能会忘记,或者根本不知道,from module import obj创建了module.obj的副本或新名称,并且__main__模块的作用域现在包含两个对象:
- 在不可变对象的情况下,
module.obj和obj对象; - 在可变对象的情况下,它实际上是一个有两个名字的对象:
module.obj和obj.
当然,当您在一个模块中更改另一个模块中的对象时,所有这些都很重要。那你一定要小心,这是我们讨论的所有这些事情都可能发生的时候。
我认为通过导入来避免创建对象副本的混乱要好得多。这就是为什么,至少在这种情况下,导入一个模块比从它导入对象更安全。
资源
- Lutz M. (2013 年)。学习 Python 。第五版。奥莱利媒体。
- 拉马尔霍湖(2022 年)。流畅的 Python。清晰、简洁、有效的编程。第二。版本。奥赖利。
从其他脚本导入 Python 函数
原文:https://towardsdatascience.com/importing-python-functions-from-other-scripts-8769ca65a3da
停止将函数复制和粘贴到新脚本中

有时,一个新项目需要现有的代码。更聪明地工作(而不是更努力地工作)意味着利用现有的工作。目前,从一个脚本复制粘贴到另一个脚本似乎是一个快速、无害的解决方案。然而,在一个文件中定义函数并导入它简化了过程并消除了潜在的错误。
并非所有函数都需要单独的脚本文件。但是,如果您希望在多个项目中重用该函数,请单独保存它。
在外部脚本中存储函数的好处
- 组织
用描述性名称将相关功能存储在脚本中更容易找到。例如,在conversions.py中寻找转换函数比在lab_report_1.py、lab_report_2.py等中搜索更直观。 - 版本控制
在多个地方重新创建一个函数意味着一个脚本中的编辑和更新不会转移到其他脚本。在一个地方定义函数并导入它,允许所有调用该函数的脚本都有最新的定义。 - 可读性
将函数存储在外部可以更容易地看到脚本的总体目标。同行评审者不需要看到每个函数的定义,尤其是那些简单的函数。一个描述性的函数名通常就足够了。 - 简单性
与复制粘贴方法相比,导入方法使得重用函数更加容易。代码行不会被意外删除。不需要打开额外的脚本。简化的流程消除了可能的错误。
条款
为清晰起见,以下是描述import语句时常用的一些术语:
- 库:NumPy、Pandas 等相关模块的集合。
- 模块:带有“.py”扩展名;与脚本互换使用
- 模块名:不带“,”的文件名。py "扩展
- 子模块:一个带有“的文件。子目录中的 py "扩展名
directory
|-- module.py
|-- subdirectory
|-- sub_module.py
从目录中的脚本导入特定函数
要从当前工作目录中的脚本导入函数,请添加以下内容:
from script_in_cwd.py import specific_function
为了给specific_function一个不同的名字,在import语句中添加as。
from script_in_cwd.py import specific_function as new_name
脚本不能处理同名的两个函数。使用as避免导入错误。
从脚本导入所有函数和模块
要导入脚本中的所有函数,请使用*。
from script_in_cwd.py import *
这将导入在script_in_cwd.py中定义的所有函数。如果script_in_cwd.py有import语句,那么*也会导入那些库、模块和函数。例如,如果script_in_cwd.py有import numpy,那么上面的语句也会导入numpy。导入对象的名称将被绑定在本地名称空间中,这意味着脚本将独立识别这些名称。换句话说,导入的对象可以在不引用父模块名称的情况下被调用(script_in_cwd)。
导入所有函数的另一种方法是:
import script_in_cwd.py
像以前一样,这个方法导入所有定义的函数和任何用import语句调用的东西。模块名称(script_in_cwd)将被本地绑定,但其他导入对象的名称不会被本地绑定。那些对象必须在父模块的名字之后被调用。例如:
# To call function after importscript_in_cwd.specific_function()
如果两个导入的对象使用相同的名称,使用import {module}方法代替from {module} import *,因为 Python 不能导入两个同名的对象。然而,使用import {module},这些对象的名称被绑定到它们唯一的父模块名称上。
从子目录中的脚本导入
要从子目录导入:
from subdirectory.submodule import *
像以前一样,导入对象的名称被绑定到本地名称空间。
另一种选择是:
import subdirectory.submodule
同样,导入的对象名称将而不是被本地绑定,但是subdirectory.submodule将被本地绑定。要调用导入的对象,首先引用父模块的名称(subdirectory.submodule)。
从目录外的脚本导入
从当前工作目录之外导入需要sys.path,这是 Python 搜索的所有目录的列表。要添加新的搜索路径:
import sys
sys.path.append('/User/NewDirectory')
这会将新路径附加到sys.path的末尾。Python 按顺序搜索这些路径。使用sys.path.insert强制 Python 更快地搜索路径。例如:
import sys
sys.path.insert(1, '/User/NewDirectory')
这些附加内容不仅适用于当前脚本。Python 还会在这些路径中搜索未来的项目,除非它们被删除。要删除不需要的搜索路径:
import sys
sys.path.remove('/User/NewDirectory')
结论
感谢您阅读我的文章。如果您喜欢我的内容,请考虑关注我。此外,欢迎所有反馈。我总是渴望学习新的或更好的做事方法。请随时留下您的评论或联系我 katyhagerty19@gmail.com。
https://medium.com/@katyhagerty19/membership
使用 S3 魔术提交器提高 Apache Spark 性能
使用 Spark 3.2 和 Hadoop 3.3 的最新 S3 magic committer 实现高达 65%的性能提升!
大多数 Apache Spark 用户忽略了 S3 提交器的选择(Spark 在将输出结果写入 S3 时使用的一种协议),因为它相当复杂,并且关于它的文档很少。每当 Spark 向 S3 写入数据时,这种选择都会对性能产生重大影响。由于对于 AWS 用户来说,Spark 工作的很大一部分都花在了给 S3 写信上,所以选择合适的 S3 提交者非常重要。
随着 Apache Spark 3.2 于 2021 年 10 月发布,一种特殊类型的 S3 提交器魔法提交器得到了显著改进,使其性能更高、更稳定、更易于使用。
我们在我们客户的一些管道上测试了 S3 提交器,并意识到对于像 Weather20/20 这样的客户,它将 Spark 作业的速度提高了 65%。我们现在向所有 AWS Spark 用户推荐使用该提交器。
以下是我们将在这篇博文中涉及的内容:
- 什么是 S3 委员?为什么我应该使用魔法提交器?
- 它能带来哪些性能优势?
- 我怎样才能打开魔法提交器?
- Spark 3.2 发生了什么?对未来的 Spark 版本有什么期待?
什么是 S3 委员?为什么我应该使用魔法提交器?

下面是默认提交器(FileOutputCommiter)的工作方式。图片作者。
Apache Spark 和之前的 Hadoop MapReduce 一样,将工作的输出写在文件系统中。更准确地说,许多 Spark 任务并行运行,每个 Spark 任务将其输出作为文件写入。系统必须产生一致的输出,即使:
- 一些任务可以在进行中被中断(例如,定点清除、执行器内存不足错误)并在另一个执行器上重试
- 同一任务的多个副本可以在不同的执行器上并行执行(一种称为推测的机制,有助于提高性能)
为了解决这个问题,Hadoop MapReduce 使用了一种称为提交协议的技术,它列出了中间输出目录,并将文件重命名到它们的最终位置。这在 Hadoop 分布式文件系统(HDFS)上运行良好,因为列出目录会产生一致的结果,重命名文件是一个快速的“O(1)”操作。
虽然对 HDFS 来说是这样,但对 S3 来说却不是这样。在 S3 上重命名文件不是原子操作,它是作为一个复制和一个删除操作来实现的,运行起来大约需要 6MB/秒。
因此,Spark 的默认作业提交器(称为fileoutputcommitter)与 S3 一起使用是不安全的。例如,如果在重命名操作进行过程中出现故障,数据输出可能会损坏。除了不安全之外,它还可能非常慢。
为了解决这个问题,社区为 S3 开发了特殊的提交器,称为 S3A 提交器:
- 网飞开发的 staging committer。它工作得很好,但是它需要像 HDFS 或 NFS 这样的集群级共享存储来存储中间输出文件,这不太方便设置,特别是对于 Spark-on-Kubernetes 用户来说。
- magic committer,由社区领导,是新的 Hadoop 默认值。
最初,magic committer 有一个很大的缺点:它需要安装一个 Dynamodb 数据库来启用一个名为 S3Guard 的 S3 客户端机制(“保护”您避免不一致的结果)。
自 2020 年 12 月以来, S3 在全球范围内提供了强大的写后读一致性,这意味着在写入文件后,当您列出目录时,您一定会看到文件(就像在笔记本电脑的硬盘上一样)。因此,没有必要再安装 S3Guard,这使得 magic committer 更易于使用。
要深入了解这个主题,请参阅官方 Hadoop 文档和这篇由 Steve Loughran 和其他 Apache 贡献者撰写的关于“零重命名提交者”的研究论文。
重要提示:如果您使用纯清单表格式,如 Delta.io、Apache Iceberg 或 Apache 胡迪,S3 提交器与您无关,因为这些表格式处理提交过程的方式不同。
S3 提交者可以实现哪些性能优势?
确切的性能收益取决于工作负载,但是根据经验,如果您的 Spark 工作的很大一部分用于向 S3 写入数据(这是一种非常常见的情况),收益将会非常显著。除了性能优势之外,使用 magic committer 代替默认的 FileOutputCommitter 还可以保护您在边缘情况下免受讨厌的数据损坏错误(执行器丢失、推测等)。
让我们评估一下我们的一个客户 Weather20/20 在真实世界 Spark 管道上的性能优势,这是一个天气分析平台。在使用 magic committer 之前,他们的管道将运行许多 Spark 作业和任务一个小时,然后“休息”近一个小时,在此期间,Spark 似乎是空闲的,因为没有执行任何任务,然后最终退出。
通过查看我们的免费 Spark 监控工具 Delight ,这个问题非常明显:

右边的灰色区域表示所有 Spark 执行器都空闲了将近一个小时——然而 Spark 作业并没有退出。图片作者。
Spark 应用结束时的 48 分钟中断在 Spark driver 日志中也清晰可见:
21/11/08 20:52:11 INFO Dag scheduler:作业 7 已完成:在 nativemethodaccessorimpl . Java:0 处插入,耗时 3495.605049 秒
21/11/08 21:40:13 信息文件格式写入程序:写入作业 13 ca 8 CB 6–5fc 0–4fe 9–9fd 0-bb a5 cf 9 e 2f 7 f 已提交。
最后一行提示在 20:52 到 21:40 之间,Spark 正在运行 FileOutputCommitter 协议的作业提交步骤。如前所述,S3 重命名操作非常慢(对于大型数据集)。除了缓慢的 S3 调用之外,提交者正在进行成千上万的 S3 调用,在某些情况下,这些调用可以被 S3 抑制(您可以通过启用 S3 日志并查找 503 API 响应来确认这一点)。
通过切换到 magic committer,下面是应用程序愉悦图的样子:

Spark 应用结束的空闲时间几乎完全消失了。图片作者。
这条管道现在只需 40 分钟,而以前需要 1 小时 48 分钟。这是一个 63% 的提升!这条管道可能是一个特别极端的改进例子,但我们经常看到向 S3 写入大量数据的管道有 15–50%的改进。所以你绝对应该亲自尝试一下。
我怎样才能打开魔法提交器?
您可以通过在 sparkConf 中插入一个配置标志来打开它:" spark . Hadoop . fs . s3a . bucket . all . committer . magic . enabled ":" true "
注意:以前需要在您的 spark 配置键spark . Hadoop . fs . s3a . bucket .
. committer . magic . enabled . 中提供 bucket 名称,这是因为最初您必须在每个 bucket 的基础上安装 S3Guard。现在 S3 已经非常稳定了,这已经没有必要了。如果传递标志spark . Hadoop . fs . s3a . bucket . all . committer . magic . enabled,magic committer 将在所有 bucket 上使用。
您还需要将 spark-hadoop-cloud 库包含在 docker 映像中或作为一个依赖项,因为它提供了 S3A 提交者使用的类。如果您缺少这个依赖项,您将会得到一个类似这样的错误:Java . lang . classnotfoundexception:org . Apache . spark . internal . io . cloud . pathoutputcommitprotocol
要验证是否使用了神奇的提交器,最简单的方法是在 Spark 驱动程序日志中查找单词“committer”。
如果您看到这样的日志:
21/11/08 19:53:54 WARN abstracts 3a Committer factory:21/11/08 19:53:54 INFO FileOutputCommitter:文件输出提交器算法版本为 1 使用标准 File Output Committer 提交工作。这既慢又有潜在的不安全。
那么就有一个问题了——使用了标准的 FileOutputCommitter。正如警告所说,它速度慢,而且有潜在的不安全因素。但是,如果您看到下面的日志,那么您就知道 magic committer 被正确地使用了:
21/11/14 00:05:11 信息摘要 3ACommitterFactory: 使用 committer magic 将数据输出到 s3a://…
Spark 3.2 发生了什么?对未来的 Spark 版本有什么期待?
Spark 3.2 使得魔术提交器更容易使用( SPARK-35383 ),因为你可以通过插入一个配置标志来打开它(以前你必须传递 4 个不同的标志)。Spark 3.2 也建立在 Hadoop 3.3.1 的基础上,Hadoop 3 . 3 . 1 包含了针对 magic committer 的错误修复和性能改进。阅读我们关于 Spark 3.2 的文章,了解更多关于这个版本的主要特性和改进。
注: 另一个无关的 Hadoop 3.3 改进。如果通过配置标志" spark . Hadoop . fs . s3a . directory . marker . retention ":" keep "Hadoop 会停止不必要的删除目录标记。您需要选择加入这种行为(您需要传递标志),因为它不是向后兼容的。只有当所有 Spark 应用程序都使用 Hadoop 3.3+时,才应该传递这个标志。
Steve Loughran(自 2000 年以来一直是 Apache 软件基金会的成员)正在为 Azure 和 GCP 的对象存储( MAPREDUCE-7341 )构建类似的任务清单提交算法,并提高 magic committer(HADOOP-17833)的性能。这是一个巨大的工作量(谢谢史蒂夫!)首先需要贡献给 Hadoop,然后由一个新的 Spark 版本来完成。
结论
从 Spark 3.2 开始,我们向通过管道向 S3 写入数据的 AWS Spark 用户强烈推荐 magic committer(除非他们已经在使用像 Delta、胡迪或 Iceberg 这样的表格式)。通过切换单个配置标志,您可以在管道上实现高达 65%的性能提升,并避免由默认的 FileOutputCommitter 导致的严重的数据损坏错误。
原 NetApp 博客现场发布:https://spot.io/blog/
通过使用这 5 种评分方法来改善数据的健康状况
最终帮助您改善数据健康状况的步骤

马库斯·斯皮斯克在 Unsplash 上的照片
最近,有人问我这样一个问题:什么比数据质量更重要?我意识到我可能在我的数据质量(DQ)博客中提供了一个不完整的叙述。
DQ 无疑是数据的一个重要方面;然而,除了简单地提高其质量之外,还有许多其他方面。仅仅提高数据质量并不能给你带来所有的好处。我将这些归纳为我们应该确保数据正确的五件事。
让我们深入了解这些:
1.数据的可访问性如何?
数据可访问性一直是这十年的挑战。如此多的数据却无法访问。严苛的企业访问政策、团队知识有限、单点故障、过度监管政策。你知道这是怎么回事。
总体数据健康的一个重要方面是数据的可访问性如何?有人可以使用工具和流程来获取这些数据吗?如果存在流程,它们是手动依赖于您的 IT 团队还是自动化的?
存储在孤岛式仓库或湖泊中的数据没有任何价值,它们需要由业务用户掌握,以做出关键决策或发现新的见解。数据民主化正试图解决这个问题;然而,它很少被正确实现(那是另一个时间的博客!)
2.数据有上下文吗?
是的,我可以接触到这些数据,但是我看不懂。这些列有技术名称,逻辑和聚合没有解释。知识在工程师的大脑里。该数据没有业务术语表或目录。想象一下,试图在亚马逊上找到一个没有任何目录或描述的产品。
需要捕获湖泊的元数据来为数据提供上下文。您应该设置一个带有技术和业务数据解释的基本搜索功能。最起码,数据需要被编目和搜索。
3.数据质量如何?
我不是说过质量还是很重要的吗?现在,您可以访问数据,了解数据背后的上下文;质量足够好的数据可以被消费吗?您是否缺少列/行?重复条目怎么办?表之间存在一致性问题吗?
这些都是 DQ 常见的问题,在自助式数据服务成为现实之前,应该先解决这些问题。关于这一点,我已经写了很多;参见下面的文章。
4.数据保护得如何?
让数据可访问不应该意味着践踏保护数据的法规和道德要求。它在使数据可访问和保护数据之间取得平衡。
应制定数据政策,包括访问、保留和删除。确保正确实施 GDPR(或其他等效法规)的监管政策也应到位。如果您天生就有使用这些数据的目的,并在面向客户的系统中最大限度地减少收集这些数据,那将是最好的。
5。数据治理得如何?
最后,我们谈到数据治理。我不是指传统意义上的委员会、论坛、所有者、管理者等。我的意思是为上述所有功能的运行提供一个结构。
- 框架中是否概述了核心角色和职责?
- 谁提供访问权限?
- 谁对 DQ 负责?
- 谁负责编目和术语表?
- 谁负责数据保护/隐私?
如果您不能明确回答以上问题,那么您在数据治理领域还有一些工作要做。
结论
您可以对每个问题进行简单的加权,例如,DQ 占 20%。这种加权将允许您计算出数据运行状况的总体平均值。从这种加权练习中产生的分数可以在您的企业中推广,或者成为首席数据办公室的核心指标。
目标应该是将该指标提高到整个企业可接受的目标。如果这引起了你的共鸣,请在下面留下评论来分享你的想法。
如果您想在您的数据之旅中了解更多关于 DQ 的信息,请查看这篇文章:
如果你没有订阅 Medium,可以考虑使用我的推荐链接订阅。它比网飞便宜,而且客观上能更好地利用你的时间。如果你使用我的链接,我会获得一小笔佣金,而你可以在 Medium 上获得无限的故事。
我也定期在推特上写东西;跟着我这里。
利用 AugMix 增强图像分类模型的鲁棒性
原文:https://towardsdatascience.com/improve-image-classification-robustness-with-augmix-59e5d6436255
添加 AugMix 增强影像之间的一致性损失,以增强影像分类模型的泛化能力

图 1 -可视化 AugMix:原始图像(左)和两个增强版本。|作者图片
简介
图像分类模型最能够预测来自与训练数据相同的分布的数据。但是,在现实世界中,输入数据可能会有变化。例如,当使用不同的相机进行推理时,照明条件、对比度、颜色失真等。可能会因定型集而异,并显著影响模型的性能。为了解决这一挑战,由 亨德里克斯等人【1】提出的 AugMix 算法可以应用于任何图像分类模型,以提高其鲁棒性和不确定性估计。**
AugMix 是一种生成每个训练图像的增强变化的数据增强技术。当与一致性损失结合时,它鼓励模型对同一图像的所有版本做出一致的预测。虽然使用这些扩充数据版本训练模型需要更长的时间,但生成的模型变得更加稳定、一致,并且对大范围的输入具有抵抗力。
AugMix 增强技术
训练图像的增强版本是通过应用三个并行链来生成的,这三个并行链由一个到三个随机选择的增强操作组成,例如平移、剪切和对比度,具有随机确定的强度。然后,这些链以不同的权重与原始图像组合,以产生单一版本的增强图像。增强版本包含了几个随机性来源,包括操作的选择、这些操作的强度、增强链的长度和混合权重。

图 2:aug mix 扩增链的实现,摘自 Hendrycks 等人(2020)。
对于每个训练图像,AugMix 生成两个保留图像语义内容的增强版本(augmix1 和 augmix2)。您可以在图 1 中查看图像及其增强版本的演示。
加载带有 AugMix 版本的数据集
数据加载器应该处理原始图像及其使用 AugMix 技术生成的修改版本。PyTorch 图像模型(timm)库[2]为创建 PyTorch 数据集和生成这些 AugMix 增强提供了一个方便的实现。
**train_dir = '/path/to/training/images/dir/'
train_dataset = ImageDataset(train_dir)
train_dataset = AugMixDataset(train_dataset, num_splits=3)
loader_train = create_loader(train_dataset,
input_size=(3, 224, 224),
batch_size=32,
is_training=True,
scale=[0.08, 1.],
ratio=[0.75, 1.33],
num_aug_splits=3)**
上面的代码创建了一个训练数据集和一个数据加载器。ImageDataset类用于从训练图像创建数据集对象。AugMixDataset类用于通过生成原始图像的附加修改版本来扩充训练图像。num_splits参数指定了每幅图像应该生成多少个增强版本。例如,如果num_splits=3,那么对于数据集中的每个图像,除了原始图像之外,还会生成两个修改版本。论文作者建议使用两个增强版本,因此 num_splits 应该设置为 3。create_loader函数用于创建数据加载器对象,该对象可用于在训练过程中小批量迭代训练数据集。
詹森-香农一致性损失
在每次向前传递中,原始图像与两个增强图像 augmix1 和 augmix2 一起传递给模型。为了鼓励模型对相同输入数据的不同增强版本做出一致的预测,将 Jensen-Shannon 散度(JS)添加到原始交叉熵损失(L)中,并通过 lambda 超参数进行加权。

Jensen-Shannon 一致性损失由原始交叉熵损失(L)和 Jensen-Shannon 散度(JS)组成
詹森-香农散度(JS)通过首先获得平均预测概率来计算:

然后,计算每个图像版本和平均预测概率之间的平均 KL 散度。

Jensen-Shannon 一致性损失迫使模型相似地嵌入同一图像的所有版本,这可以帮助它学习更鲁棒和可概括的特征。
timm 库包括 Jensen-Shannon 一致性损失的用户友好实现。num_splits参数指定了扩充版本的数量。alpha参数,在原始论文中也称为加权因子λ,指定了 JSD 项的加权因子。默认值为 12,但应进行调整以最适合用于定型模型的数据的特征。
**loss_fn = JsdCrossEntropy(num_splits=3, alpha = 12)**
代码概述
下面的代码演示了如何使用 timm 库将 AugMix 数据扩充技术合并到简化的训练循环中。该代码首先使用 AugMix 技术创建一个训练数据集,以生成原始图像的修改版本。数据加载器迭代训练数据集,处理原始图像及其增强版本。还创建了模型、损失函数、优化器和调度器。最后,训练循环在数据加载器上迭代指定数量的时期。
**from timm import create_model
from timm.data import ImageDataset, AugMixDataset, create_loader
from timm.loss import JsdCrossEntropy
from timm.optim import AdamP
from tqdm import tqdm
from timm.scheduler import CosineLRScheduler
from matplotlib import pyplot as plt
epochs = 50
train_dir = '/path/to/training/images/dir/'
train_dataset = ImageDataset(train_dir)
train_dataset = AugMixDataset(train_dataset, num_splits=3)
loader_train = create_loader(train_dataset,
input_size=(3, 224, 224),
batch_size=1,
is_training=True,
scale=[0.08, 1.],
ratio=[0.75, 1.33],
num_aug_splits=3)
model = create_model('resnet18', pretrained=True, num_classes=2).cuda()
loss_JSL = JsdCrossEntropy(num_splits=3)
optimizer = AdamP(model.parameters(), lr=0.01)
scheduler = CosineLRScheduler(optimizer, t_initial=epochs)
for epoch in tqdm(range(epochs)):
for batch in loader_train:
inputs, targets = batch
# Forward pass
outputs = model(inputs)
loss = loss_JSL(outputs, targets)
# backward_pass
loss.backward()
optimizer.step()
optimizer.zero_grad()
scheduler.step(epoch + 1)**
结束语
总之,AugMix 是一种强大的数据处理技术,用于提高图像分类器的鲁棒性和不确定性估计。在部署过程中遇到的数据分布可能不同于训练分布的情况下,例如使用不同的摄像机捕获图像时,这种方法很有用。AuxMix 已被证明可以有效地提高泛化性能,而无需对基本模型进行任何更改,并且在 ImageNet-C 上的域泛化挑战中被公认为最先进的(SOTA)算法,ImageNet-C 是一个基准数据集,包括应用于 ImageNet 数据集图像的各种损坏。它实现起来很简单,并且值得它增加到训练过程中的计算开销。就个人而言,我发现它在我的项目中很有用,我推荐它作为一种工具,为图像分类器的健壮性增加额外的价值。
感谢您的阅读!
想了解更多?
参考文献
[1] 亨德里克斯博士,金佩尔 k .,&金,B. (2020)。AugMix:一种提高稳健性和不确定性的简单数据处理方法。预印本 arXiv:2006.13718。
PyTorch 图像模型(timm)库。https://timm.fast.ai/
使用脸书预言家模型提高时间序列预测性能
时间序列特征工程和预测基本指南

科林·伯伦斯来自的图片
时间序列预测涉及基于历史时间戳数据值和外部因素构建模型,以做出科学预测,推动未来的战略决策。鉴于其对相关决策的直接影响,为准确可靠的预测训练稳健的时间序列预测模型是最具挑战性的任务之一。时间序列预测模型的稳健性完全取决于建模前进行的特征工程和数据分析。
在之前的一篇文章中,我讨论了一个开源包 tsfresh,它可以为您的时间序列用例生成数百个相关特性。
即使在包括 tsfresh ad 外部特征之后,时间序列模型有时也不会预测到与业务预期相匹配。在本文中,我们将讨论和实现如何使用脸书先知模型的特征来提高监督时间序列模型的性能。
开始使用:
我们将使用自定义生成的基于时间的样本数据集,该数据集具有 8 个独立特征和一个连续的从属特征“目标”。我们将为不同的特征工程策略训练一个轻型 GBM 模型:
- 具有外部特征的轻型 GBM
- 轻型 GBM,带外部特征+滞后
- Light-GBM 带外部功能+ lags + facebook prophet 功能
我们将实现并比较上述每个特征工程策略的性能,并得出脸书先知特征在训练稳健模型中是否有效的结论。
数据:
基于时间的原始数据是基于时间的,并且具有 8 个独立特征和作为从属特征的“目标”。我已经创建了小时、日和月特性来捕获数据中的时间因素。在下面找到一个数据示例:

(图片由作者提供),样本数据快照
训练轻型 GBM 模型:
我们将在上述原始样本数据上训练一个 light-GBM 模型,并计算其最佳性能特征,以及用于基准测试的 MAE(平均绝对误差)。


(图片由作者提供),左:绘制可视化推理数据的真实值和预测值,右:轻型 GBM 模型的最佳性能特征
对于推断数据,我们得到了的MAE53.79,表现最好的特征是小时、日、湿度等。
带滞后的轻型 GBM:
先前的模型仅根据外部因素/数据进行训练,不涉及从属特征“目标”的滞后。为了计算要包含的滞后数,我们可以观察自相关。

(图片由作者提供),相关特征“目标”的自相关图
从上面的关联图中,我们可以观察到“目标”功能在 1 小时、24 小时、48 小时等时间内都具有很高的相关性。因此,我们可以创建计算滞后要素的要素:
- 通过将目标值移动 1 小时,获得 1 小时滞后变量
- 通过将目标值移动 1 天,获得 1 天的滞后变量
- 通过将目标值移动 2 天,获得 2 天滞后变量
- 通过将目标值移动 1 周,获得 1 周的滞后变量
我们将包括先前的静态特性和滞后特性。


(图片由作者提供),左:绘制可视化推理数据的真实值和预测值,右:轻型 GBM 模型的最佳性能特征
对于推断数据,我们得到了 21.37 的 MAE,性能最好的特性是小时、前一小时滞后、前一天滞后等。
带 lags + FB Prophet 功能的轻型 GBM:
我们现在将包括脸书先知包的功能。想法是在训练数据上训练 FB Prophet 模型,并利用来自**prophet.predict()** API 的训练和推理特征来生成 22 维统计特征。


(图片由作者提供),左:绘制可视化推理数据的真实值和预测值,右:轻型 GBM 模型的最佳性能特征
对于推断数据,我们得到了 20.81 的 MAE,性能最好的特性是前一小时滞后、当前小时、每周、前一天滞后、附加特性等。
结论:
在本文中,我们已经讨论了一些时序用例的特性工程策略。根据我们对数据样本的实验,原始数据集给出的 MAE 为 53.78,在包括滞后之后,MAE 提高到 21.37 。在引入脸书先知 API 的统计特征后,MAE 进一步提高到 20.81 。
虽然在包含 FB Prophet 特性后 MAE 的改进还不够,但它仍然可以在其他用例或真实世界数据集的更大样本中表现良好。
参考资料:
[1]https://facebook.github.io/prophet/脸书先知文献:
感谢您的阅读
从新冠肺炎疫苗试验中吸取 9 个教训,提高你的 A/B 检验
在线实验的实践者能从一个复杂的过程中学到什么和总结什么

自从新冠肺炎疫苗的第一次临床试验结果出来以来,整整一年过去了。大量的随访观察数据现在正在传播,这使得关于这些试验的优缺点的一些结论成为可能。由于现代医学试验是最严格和最仔细的试验之一,它们可以作为在线试验应用的案例研究,与在线试验有更多的相似之处。
多年来,我一直在推广和实施在线 A/B 测试中的统计方法,这些方法与大多数新冠肺炎试验中使用的方法非常接近。在这里,我提供了我对这些试验给在线实验从业者带来的教训的看法。这篇文章关注的是实验的设计和相关的统计数据,并没有对我的医学或流行病学专业知识做出明确或隐含的声明。
焦点尤其集中在辉瑞和 Moderna 的试验上,一个提供了一个频繁主义者的观点,另一个则有些相反。希望它能帮助你提高自己 A/B 测试的严谨性。
继续第一课:
通过致盲防止观察者偏差
这两次审判在这一点上提供了一个很好的例子。以 Moderna 的研究方案标题[2]为例:
“一项 3 期、随机、分层、 观察者盲 、安慰剂对照研究,旨在评估 mRNA-1273 新型冠状病毒疫苗对 18 岁及以上成人的疗效、安全性和免疫原性”
(强调我的)
从一开始,大多数临床试验的一个重要特性就变得显而易见,那就是消除研究中由观察者引起的偏见。观察者盲是指大多数参与实验的人员既不能获得整体结果,也不能获得部分数据。这就只剩下少数几个人——即那些确保治疗按照处方进行的人,他们是唯一能够获得关于哪个人被分配到哪个治疗的实际数据的人。这些人被排除在决策过程之外,因此知识不会影响决策过程。
DSMB(数据和安全监控委员会)是唯一可以在预先指定的时间点看到实际(非盲)结果的人。只有他们能够根据这些数据做出决策。理想情况下,DSMB 的成员不参与临床试验,并且在试验结果中的利益最小。
如何在 A/B 测试中实施盲法原理?盲观察者方法的一个应用示例是确保只有主要的 A/B 测试从业者可以查看完整的实际数据。其他人都可以看到一个盲版本,理想情况下只能在预先指定的时间点看到,直到实验以某种方式结束。
例如,测试结果可以用中性测试组名称表示,如“变体 A”、“变体 B”等。在数据的盲法版本中,对照组也被称为“变体”。这样做大大减少了决策链中任何人偏心或干扰测试执行的机会,只要它没有明显偏离预先约定的参数。
我们经常听到主管人员想缩短测试,而不遵守商定的监控时间表,以监控感知的有效性、缺乏有效性或无效性。虽然这种做法不会被完全抵制,但盲法至少可以防止偏向某一特定 A/B 测试变量的结果。
通过顺序监控,高效测试并获得可靠的结果
在这两个临床试验中,测试监测时间主要是根据评估时间(辉瑞开始后的天数)或总目标病例的百分比(Moderna)预先确定的。构建统计停止界限以确保统计误差保证成立,尽管有多个可能的数据评估。
这是 Moderna 试验协议中的内容[2]:
“根据 Lan-DeMets O'Brien-Fleming 近似值支出,IAs 和主要分析的主要终点的总体 I 类错误率严格控制在 2.5%(单侧)。”
此外:
“在两个治疗组中,有两个计划的 ia,分别占总目标病例的 35%和 70%。IAs 的主要目的是早期发现 ve 高于 30%的可靠证据。Lan-demes O ' brien-Fleming 近似支出函数用于计算疗效界限,并相对于假设保持 IAs 和主要分析(当观察到目标病例数时)的(单侧)0.025 假阳性错误率:“其中 IAs 代表“中期分析”。
令人耳目一新的是,即使是辉瑞-BioNtech 试验的所谓贝叶斯分析也包含在设计措施中,以防止由于中期分析而导致的错误概率膨胀[3]:
“最终分析使用 98.6%的成功边界作为疫苗功效大于 30%的概率,以补偿中期分析,并将总体 1 型错误率控制在 2.5%。此外,依次评估主要和次要疗效终点,以将家族性 1 型错误率控制在 2.5%。”
这是在线 A/B 测试中贝叶斯方法的许多支持者否认是适当的或必要的,甚至吹捧它是一个巨大的好处,它不适用于贝叶斯分析。
这里的一个教训是,当延迟有益干预的分发或继续徒劳的试验存在相关成本时,实验者可以转向对试验数据的顺序监测,以便“尽早”做出决定(与固定样本设计相反)。根据定义,这是任何 A/B 测试的情况。
另一个洞见是,如果要在数据收集时评估结果并采取行动,则需要适当的统计设计,并调整由此产生的统计估计值。否则“窥视”发生,错误率变得膨胀,并且统计保证可能严重受损。
我支持错误开销方法和 Lan DeMets 函数族,Lan-DeMets alpha 和 beta 开销边界是在Analytics-Toolkit.com中实现的,因为它开始提供顺序监控的 A/B 测试。
误差支出法的一个重要特征是,根据每次分析所获得的样本量,分析的时间可以有一定的灵活性,如果中期结果或其他考虑需要,分析的次数也可以调整。对于大多数在线测试来说,这几乎是必要的,因为在这些测试中,由于各种类型的冲击和季节性,每周的流量水平和事件发生率都会发生波动。
在收集测试数据时对其进行监控,即使有适当的停止界限,也会在停止测试后计算的原始估计值中引入条件和无条件偏差。因此,使用误差支出的一个不可或缺的部分是实现无条件和有条件偏倚缩减估计所需的调整,如效应大小估计、p 值和置信区间。
选择适当的显著性阈值
显著性阈值是统计检验必须满足的最小证据阈值,以便得出关于检验假设的结论,并在合理的不确定性水平下采取后续行动。
在一个高度监管的行业中,满足最低监管标准通常是为股东获取最大价值的方式,因此看到监管最低标准被用作事实上的标准并不奇怪。在 FDA 监测的试验中,该最小值被规定为 0.05(相当于 95%的置信水平)的双侧检验的显著性阈值。
因为结果是有方向性的,所以两个试验协议都将它们的误差保证声明为单侧α[1–4]。在 0.025 时,它们完全符合 FDA 对 0.05 双面的最低要求。
这里的一个教训是,使用单边测试没有什么特别或不寻常的。使用单尾检验和单边假设并不意味着统计检验只在真实效应是单向的情况下才有效。事实上,有必要根据一个片面的显著性阈值来构建误差控制,如果由此产生的推论是方向性的,例如“疫苗提供了针对感染和不良健康结果的保护”。它在样本大小和统计能力方面也更有效。
在 A/B 测试中,一个方向性声明的例子是“用户体验 A 产生了比当前更好的业务成果”。单侧 p 值和单侧置信区间需要与这样的声明一起报告。
回到临床研究。一方面,人们可以批评 0.025 阈值的选择过于宽松,因为假阳性的财政和健康后果太大,不能满足这一标准。另一方面,也有可能出现这样的情况:如果采用更严格的阈值,将会降低测试的功效(下一节将详细介绍),或者需要更大的样本量,这可能会延迟对大量目标个体的益处释放,或者如果在短时间内进行测试,会使更多的个体暴露在风险中。
适当的讨论需要深入了解疫苗制造商和监管机构面临的复杂权衡,这超出了本文的范围。
我们仍然可以从中吸取教训,并将其应用到商业实验中。由于在大多数 A/B 测试场景中,人们可以计算出大多数相关成本和回报的合理估计,因此通过风险回报分析整合这些信息可以产生投资回报最佳的在线实验。这意味着无论结果如何,其样本量和显著性阈值都将实现风险和回报的最佳平衡。
确定所需的样本量
考虑样本大小的一种方式是作为统计功效、感兴趣的最小效应和给定显著性阈值的函数。两次试验中使用的功率水平均为 90%。然而,Moderna 试验的目标是 60%的有效率,而 BioNtech 的目标是 30%。感兴趣的最小效应的差异导致后者的样本量要求明显高于前者(约 43,548 对 30,420)。
A/B 测试的一个教训是,在其他条件相同的情况下,测试越快,就意味着越有可能错过较小幅度的有意义的实际提升。在一个典型的观察到的效应大小(更不用说实际的了)在个位数百分比的范围内,有时甚至更低的领域,检测有意义效应的能力低于 90%对于 A/B 测试程序来说是毁灭性的。这可能会导致太多的假阴性。这些都是错失的改善业务的机会,也是开发和测试工作的浪费。
这个问题是什么样的样本量或测试持续时间为一个测试是不可避免地联系在一起的选择统计显著性阈值。由于“有意义”的定义取决于样本大小/测试持续时间,因此在确定适当的测试持续时间和显著性阈值时会出现反馈循环。使用一种基于业务度量的方法来确定 ROI 最优参数是正面处理这个问题的唯一解决方案。
监控多个指标
两个疫苗实验都列出了许多指标(终点),包括主要疗效终点和次要疗效和安全性终点。这是一个很好的实践,因为它允许同一个实验产生一组有用的数据,而不仅仅是一个比较。
虽然通常不会对所有端点进行测试,但是基于几个关键指标监控性能仍然是一个好的实践。
在 A/B 测试中,这包括监控所谓的护栏指标,但也包括次要指标。某些次要 KPI 的负面结果可能会抵消主要指标的正面影响。通常,A/B 测试之后的行动将首先由主要度量标准的结果提供信息,主要度量标准本身可能是几个不同度量标准的组合。然而,当度量具有不同的预期效果窗口时,会发生什么呢?例如,就每封发送的电子邮件的销售收入而言,电子邮件活动具有良好的平均回报(短期利益),但这会激怒其他电子邮件订户并促使他们取消订阅。根据取消认购的比例,这可能会导致长期亏损。
除了以上所述,次要指标通常可以提供对驱动主要结果的因素的进一步了解。
报告几个统计估计
在辉瑞-BioNtech 的 bnt162b2 和 Moderna 的 mRNA-1273 之后的报告中,共享了主要和次要终点的多个统计估计值。例如[1]:
“疫苗有效率为 94.1% (95% CI 为 89.3-96.8%;p<0.001)
对于 mRNA-1273 和 bnt162 [3]:
“在先前有 SARS CoV-2 感染证据的参与者和没有 SARS CoV-2 感染证据的参与者中,在疫苗接受者中观察到 9 例新冠肺炎,在安慰剂接受者中观察到 169 例,对应于 94.6%的疫苗效力(95% CI,89.9 至 97.3)。”
一些 A/B 测试从业者只关注一个估计——无论是效果大小(也称为百分比提升)、p 值或置信水平,还是置信区间。虽然每种方法都有其自身的优点,但从新冠肺炎疫苗试验中可以吸取的一个教训是许多评估在平行使用时效果最好。
关于这两个医学试验的报道,有必要说几句。Moderna 应该报告准确的 P 值,而不仅仅是“P<0.001”,因为这将传达更多的信息。尽管辉瑞承诺在其统计设计中进行 I 型误差控制,但遗憾的是,辉瑞选择忽略报告与其相关的最具信息性的测量(观察到的 p 值)。然而,可以从 CI 中推断出,它很可能接近 Moderna。
在我看来,两者都应该报告用来计算 P 值的零假设,以帮助避免可能的误解,例如,“(P = 0.0006H0: VE <= 30%)”,基于“对于主要终点的分析,该试验是为 mRNA-1273 疫苗的功效为 30%或更低的无效假设而设计的。”值得赞扬的是,这一点在统计分析部分单独陈述。
最大限度地提高样本的代表性
辉瑞和 Moderna 的实验都有仔细的纳入标准,旨在涵盖最有可能在实验后阶段经历治疗的人群,从而确保结果的代表性。
辉瑞在几个不同的国家招募参与者,这意味着结果应该更容易跨国传播。Moderna 的试验仅限于美国(据我所知),这意味着可能会出现潜在的外部有效性问题。
Moderna 也有一个分层标准,确保实验将包括一定比例的疫苗所针对的关键人口统计数据(按年龄和风险状况)。当需要对结果进行分析并对特定亚人群采取行动时,这是有保证的,否则最终的样本量会导致具有高度不确定性的估计。
观察之间的时间间隔足够长,即使试验提前停止,也能让短期效应显现出来。长期的负面结果将被监测,即使试验在中期分析时被停止,以说明可能存在的问题。
在在线 A/B 测试中,可以通过平滑与时间相关的偏差和抑制任何学习效应来提高代表性。这是通过以避免一些与时间相关的偏差的方式来间隔分析,以及通过运行更长时间的测试来实现的。例如,可能的一周中的某一天的影响可以通过每周或更长时间的分析来避免,而运行更长时间的测试是解决这两种学习影响的主要方法。
一般来说,避开统计计划是一个好主意,因为面对过大的观察效应,统计计划可能会在几天内结束测试。
关于地理和文化的有效性问题,我建议不要在一个国家进行测试,然后将结果解释为适用于全世界,无论是积极的还是消极的。如果企业对具有不同关键特征的市场感兴趣,那么在实施变革之前,最好对每个市场进行一次实验。
随着时间的推移,即使是最严格的结果,其变化也可能是毁灭性的
新冠肺炎试验的这个教训与上面讨论的测试结果的外部有效性密切相关,有一个微小但重要的转折。即使我们假设一个完美的测试设计和执行,并且在进行实验的时候没有普遍性的问题,这样的问题也可能在未来的任何时候出现。
虽然我不知道疫苗试验的情况是否如此,但通过观察研究和进一步的实验研究,很明显,病毒突变、病毒繁殖率、卫生政策等变化导致了疫苗在预防疾病甚至可能预防严重疾病和致命后果方面的预期和观察效力之间的差异。简而言之,即使有非常可靠的实验,将一个环境的结果应用到另一个环境也是一件危险的事情。
可悲的是,即使是最好的 A/B 测试的结果也存在类似的威胁。
这种威胁源于这样一个事实,即人类行为可以而且确实会随着时间的推移而改变,有时会突然而显著地改变。在在线 A/B 测试中,这些变化的风险来自整体技术环境、竞争对手的行动、设计和 UX 范式的转变、不断变化的经济条件、不断变化的法律法规等。
将清晰的行动与每个可能的结果联系起来
最后,也是最重要的一点,每一个实验都应该有一套明确的行动来遵循其可能的结果。辉瑞和 Moderna 试验以及大多数大型现代临床试验都是如此。虽然可能会有关于实验执行的不同方面以及如何解释某个数据的争论,但一旦这些问题得到解决,从那里到具体行动的道路在实验获得批准之前就已经基本确定了。
在商业测试中,经常会听到一个进行得很好的实验,结果非常令人信服,却被一只河马抛弃。为了尽可能地避免这种情况,如果测试已经通过了某些检查并达到了阈值,那么应该获得不同行动过程的预批准,例如“实现副本更改”。
应该注意的是,这并不意味着“应该盲目地遵循数字”,而是对于要进行的实验来说,在测试开始之前,从整体上来看,所有经过测试的变体都应该被认为是实施的良好候选。如果不是,那么他们首先就没有被测试的地方。
结论
我们对医学研究领域的短暂探索到此结束。虽然不是对疫苗试验设计的详尽检查,但提出的要点有望帮助你提高 A/B 测试水平。
参考
[1] Baden 等人 2020,“mRNA-1273 新型冠状病毒疫苗的有效性和安全性”,《新英格兰医学杂志》384(5):403–416,doi:10.1056/nejmoa 2035389
[2]Moderna 方案 mRNA-1273-P301,“一项评估成人 mRNA-1273 新型冠状病毒疫苗的有效性、安全性和免疫原性的 3 期随机、分层、观察者盲、安慰剂对照研究 一项 1/2/3 期、安慰剂对照、随机、观察者盲、剂量发现研究,用于评估 SARS-COV-2 RNA 候选疫苗在健康个体中抗新冠肺炎病毒的安全性、耐受性、免疫原性和有效性
关于作者

格奥尔吉·格奥尔杰夫
Georgi 是 Web Focus 的管理所有者和 Analytics-toolkit.com 的创建者,他在在线营销、网络分析、统计和为数百个网站设计商业实验方面拥有 17 年的经验。
他是《在线 A/B 测试中的统计方法》一书和 A/B 测试统计分析白皮书的作者,也是数十个会议、研讨会和课程的讲师,包括 Google 区域培训师。
原载于 2022 年 1 月 12 日【https://blog.analytics-toolkit.com】。
使用 ColumnTransformer 和管道改进数据预处理
使用 Sklearn 的 ColumnTransformer 创建高度定制和有组织的预处理管道

西蒙·卡杜拉在 Unsplash 上的照片
介绍
数据预处理可能是机器学习/数据科学管道中最耗时的步骤之一。
在大多数现实场景中,可用的原始数据是无格式的、脏的,并且不适合机器学习模型/数据分析,需要几个步骤清理和特征工程。
在结构化数据(表)的上下文中,开发人员需要处理各种各样的问题,比如缺失值、非规范化数据、无格式字符串、重复行等。
他们还需要通过标准化数字特征、嵌入分类特征、创建更有意义的列以及许多其他步骤来提高 ML 模型性能或改进仪表板的质量,从而改进数据表示。
这种对数据帧实施特定规则的需求很容易导致产生错综复杂的代码,难以维护和更新,并且容易出错。

数据预处理。图片作者。弗里皮克的图标。
Sklearn 的 Pipelines with ColumnTransformer 是一种以标准方式应用转换规则的简单方法,创建了一个更有组织和更干净的代码。
了解列转换器
如果您已经熟悉了来自 sklearn 的 ColumnTransformer 模块,您可以跳过这一节。
在处理表格数据时,通常要对不同的数据列执行几个清理步骤。
例如,数字特征“价格”可能需要用数据平均值替换空值的操作。正如你可能已经知道的,Sklearn 提供了一个转换器来实现这个功能,即简单估算器。
ColumnTransformer 允许的是只在一组列中应用 Sklearn 的转换器。

了解 ColumnTransformer。图片作者。弗里皮克的图标。
让我们看看这在代码中是如何工作的。
ColumnTransformer 对象接收一个元组列表,该列表由转换器名称(这是您的选择)、转换器本身以及要应用转换的列组成。参数 remainder 指定需要对所有其他列做什么。
下图显示了代码输出。

DataFrame 列转换(1)。图片作者。
替换操作只发生在指定的列中,而其余的保持不变(由余数 =“通过”指定)。熊猫数据帧也被 Numpy 数组取代,因为这是 Sklearn 变形金刚的默认行为。
让我们看一个更复杂的例子。
在上面的例子中,“Price”列的空值被替换为平均值,“A”列的空值被替换为中值,所有其他列的空值被替换为值-1。下图显示了结果。

数据帧列转换(2)。图片作者。
提示:如果你正在使用 Jupyter 笔记本,通过在 Sklearn 的配置中设置 display =“diagram ”,可以非常容易地在交互图中显示评估者。
下面的代码进行了这种配置,图像显示了前一个示例的可视化效果。

希望您已经理解了 ColumnTransformer 类的强大功能。这是一种对许多列执行转换的简单方法,所有的逻辑都封装在一个对象中。
对管道使用列转换器
ColumnTransformer 相当有用,但还不够。在许多情况下,一个列需要分多个步骤进行处理。
例如,数字特征“价格”可能需要用数据平均值替换空值的操作、更对称地分布数据的对数变换以及使其值更接近区间[-1,1]的标准化。
不幸的是,sklearn 中没有转换器来完成所有这些工作,这就是管道的用武之地。
通过管道,我们可以将多个变压器连接起来,形成一个复杂的流程。因为管道对象相当于一个简单的转换器(例如,它有相同的)。fit() 和。transform() 方法),它可以插入到 ColumnTransformer 对象中。
您还可以将 ColumnTransformer 放在管道中,因为它也是一个简单的 Transformer 对象,并且这个循环可以根据您的需要继续下去。
这就是 Sklearn 架构的妙处之一:所有的变形金刚模块共享同一个接口,因此它们可以轻松地协同工作。
让我们看看这在代码上是如何工作。
管道对象有一个非常直观的界面。它接受一个元组列表,每个元组代表一个转换器,带有您选择的名称和转换器对象本身。它以指定的顺序应用转换。
下图显示了之前创建的转换器。

带管道的柱式变压器。图片作者。
正在做一个例子
让我们通过在 Sklearn 上为葡萄酒分类数据集[4]制作一个分类器管道,快速探索这种技术如何在“真实案例”中有所帮助。
该数据集包含 13 个关于葡萄酒化学性质的数字特征,分为 3 类。下面的代码导入数据。
导入数据
绘制特征分布图,我们可以选择每个人需要的治疗方法。

特征分布。图片作者。
让我们假设选择了以下处理方法:
- 苹果酸 : 最小-最大结垢
- 镁 : 对数变换,离散化 4 个仓中的值
- 灰:掉落特征
- 非类黄酮 _ 酚类:无
- 任何其他:标准定标器
之后,数据需要经过 PCA 降维,然后到达 RandomForestClassifier。
下面的代码显示了如何构建这个管道。
管道看起来像这样:

然后,我们可以将该对象用作普通分类器:
这个练习的主要目标是展示将所有这些复杂的逻辑封装到一个单一的对象(估计器)中是可能的。最终的对象与所有其他 sklearn 模块兼容,这可以使生活变得容易得多。
例如,可以执行网格搜索,从预处理步骤到分类器本身改变超参数。由于 sklearn 架构,创建新模块(转换器、分类器、回归器等)相当容易..)所以,如果你需要一个非常具体的任务,创建一个新的类并使之与管道兼容是非常容易的。estimator 架构也很容易序列化,所以它可以以各种格式存储,比如 JSON 和 XML。
最后,代码是干净的和标准化的,这使得更新和维护更容易。
结论
预处理数据是任何数据科学过程中至关重要的一步。然而,当每个数据集的特殊性出现时,预处理步骤可能变得非常复杂,并且充满了特定的领域规则。
在这种情况下,在单个对象中维护所有的转换可能非常有用,因为单个实例可以很容易地移动、存储和更新。
这篇文章探讨了 Sklearn column transformer 类如何通过将所有预处理逻辑封装在一个地方,同时保持对象之间的高度标准化,来提高代码质量和组织。
尽管这篇文章专门探讨了 ColumnTransformer 类,但是逻辑可以扩展到其他 sklearn 模块,因为这篇文章的主要目标是展示如何生成更健壮的代码。Sklearn 的架构非常有条理,了解更高级的能力可以让你的生活轻松很多。所以,我希望这篇短文能帮到你。
感谢阅读!😃
参考
[1] Scikit-Learn,关于 ColumnTransformer
的官方文档【2】Scikit-Learn,关于管道的官方文档
【3】géRon,A. (2019)。使用 Scikit-Learn、Keras 和 TensorFlow 进行机器学习:构建智能系统的概念、工具和技术。奥莱利媒体公司。UCI:葡萄酒数据集。知识共享署名 4.0 国际 (CC BY 4.0)许可”
使用 Pandas 中的滚动功能改进您的数据科学工作流程
熊猫滚动特征指南

安妮·斯普拉特在 Unsplash 上的照片
我们今天看到的许多表格数据分析都是由流行的 Pandas 基于系列的方法驱动的,这些方法一次性考虑所有数据进行分析。这些方法通常包括使用value_counts()评估序列分布,使用unique()确定唯一值,使用groupby()找到基于另一列中的值分离的一列的分布,或者使用crosstab()从多列中生成值的交叉表等。
然而,另一组经常被数据科学家忽视的强大的特征工程方法是基于窗口的数据分析。这里,不是在整个数据列上估计特定的数据统计/度量,而是考虑预定义数量的连续记录(或值窗口)来进行评估。
例如,考虑下面的虚拟销售数据。我们可以通过同时聚合三个记录来计算每个销售日的平均三天滚动销售额,如图所示。

商店销售的虚拟数据集(图片由作者提供)
下图描述了计算三天滚动平均值的数学公式。

计算三天滚动平均值的公式(图片由作者提供)
这类特征生成方法尤其适用于时间序列数据。寻找滚动平均值、滚动总和、滚动最小值、和滚动最大值是基于窗口的特征工程方法的一些最常见的应用。
这篇文章是熊猫滚动特征的介绍性指南。我将借助虚拟数据框演示计算滚动特征的方法。
你可以在这里找到这篇文章的笔记本。
我们开始吧🚀!
资料组
出于本文的目的,我创建了一个从7th Jan 2020到13th Jan 2020为期 7 天的虚拟每日商店销售数据集。对应每个日期,我们知道它是weekend还是weekday。此外,我生成了一个随机销售号码,对应于现金和信用卡支付模式。这在下面实现。
下图显示了虚拟销售数据集的前五行:

数据帧的前五行(图片由作者提供)
滚动功能
如上所述,窗口函数每次考虑行的子集,以估计给定数据的统计/测量。在 Pandas 中,可以使用[df.rolling()](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.rolling.html#pandas.DataFrame.rolling)类来访问这个函数家族。创建的 python 对象的类型如下所示:
滚动总和
顾名思义,滚动和方法计算预定义数量的连续记录的移动和。这在下面的虚拟数据集上有详细说明:

窗口大小为 2 时滚动求和的结果(图片由作者提供)
对于window=2,我们打算对2连续记录上的“销售”列求和。另外,请注意滚动总和列的第一个值是NaN。
如果您想只考虑第一条记录来计算第一条记录的滚动和,请在滚动类的定义中传递min_periods=1,如下所示:

在最小周期数为 2 的窗口大小上滚动求和的结果(图片由作者提供)
min_periods参数指定生成滚动值所需的当前窗口中的最小观察次数;否则结果是NaN。
几天的累计总和
接下来,假设您想要计算过去两天的滚动总和,而不是上面讨论的前两个记录。
在这种情况下,获取几天的滚动总和而不是记录是非常重要的。这是因为我们有多个对应于同一日期的记录。因此,如果您想要查找多日窗口内的销售额,您可以执行以下操作:

两天的累计结果(图片由作者提供)
参数window=’2d’被滚动类解释为“2 天”。同样,如果您想获得 2 秒内的滚动和,您可以指定window=’2s’。
这里,如果您只想过滤对应于每个日期的最后一条记录,您可以使用如下的groupby()方法:

过滤滚动总和以获得每天的最后一条记录(图片由作者提供)
分组轧制
最后,假设您希望分别为支付和现金卡模式生成滚动总和。换句话说,滚动求和应该在一列上分组。这也可以通过向数据帧添加一层groupby()并计算各个组的滚动特征来实现。下面演示了这一点:

按 Payment_type 分组的窗口大小为 2 的滚动求和结果(图片由作者提供)
其他轧制方法
类似于上面讨论的基于总和的窗口操作,Pandas 还允许您执行各种其他滚动功能。这些包括滚动平均值、标准偏差、最小值和最大值等。你可以在这里找到所有支持的方法。下面我们来讨论其中的一个。
滚动平均值
滚动平均,也称为移动平均,计算值窗口内的值的平均值。移动平均是数据科学中用于时间序列数据的一种流行的数据分析技术。
就 Pandas 中的语法而言,我们上面讨论的一切都保持不变,除了我们用mean()代替sum()来寻找移动平均线。这将在下面演示。
注意:为了演示移动平均线,我创建了三个月的虚拟数据,从1st Jan 2020到31st Mar 2020。

min_periods = 1 时窗口大小为 6 的移动平均结果(图片由作者提供)
您可以使用 Matplotlib 绘制移动平均值,如下所示:

描绘移动平均值和实际值的折线图(图片由作者提供)
总之,在这篇文章中,我们讨论了 Pandas 中的滚动功能,以及如何在下一个时间序列分析的表格数据分析中使用它们。
要了解 Pandas 的“窗口”模块中可用的更多方法,我强烈推荐在这里查看 Pandas 的官方文档。
一如既往,感谢阅读。下面是我的一些其他文章,你可能会喜欢读:)
</20-of-numpy-functions-that-data-scientists-use-80-of-the-time-d8bd9c7d144b>
使用未标记的数据提高模型的性能
原文:https://towardsdatascience.com/improve-your-models-performance-with-unlabeled-data-d6e78a57fadb
半监督学习及其在非结构化数据中的应用

克里斯多夫·伯恩斯在 Unsplash 上拍摄的照片
数据科学家通常解决的大多数机器学习问题要么是监督学习(即观察值可获得基本事实或实际标签,算法对条件概率进行建模以准确预测基本事实或实际标签),要么是非监督学习(即每个观察值没有标签,因此我们可以在观察值中识别聚类、模式或减少的潜在维度)。
半监督学习通常试图将上述两个任务结合在一起,其中,我们试图通过利用通常与另一个任务相关的信息来提高这两个任务之一的性能(范·恩格伦,J.E .,胡,H.H 2020)。这一系列算法的本质在于,它允许使用大量的未标记数据结合少量的标记数据来构建更通用(并且可能更准确)的算法。
半监督学习(SSL)是一个正在进行积极研究的非常广阔的主题领域,我们相信,它肯定是一个在未来几年有显著增长的领域。该领域已经取得了一些重大进展,一些论文对 SSL 技术进行了一些调查,包括范·恩格伦、J.E .、霍斯、H.H. [1]的工作,这些工作可以在这里访问:https://link . springer . com/article/10.1007/s 10994-019-05855-6 # sec 54。这是一本强烈推荐的读物,尽管有点技术性。我们将利用、解释和简化本次调查中的一些关键概念,以巩固对不同类型的半监督学习技术的理解。
虽然半监督学习在所有形式的数据中都是可能的,但文本/非结构化数据集更棘手,标记起来更耗时。几个例子包括:根据意图对电子邮件进行分类,预测电子邮件对话中的滥用/不当行为,对没有许多标签的长文档进行分类。标签数量越少,使用有限的标签数据就越困难。
本条的内容将按以下顺序排列:
- 半监督学习的入门概念和假设
- 理解两类半监督学习(SSL)方法
半监督学习的起始概念和假设
半监督学习使用来自监督学习(即,对输入数据分布和标签分布之间的关系进行建模)和非监督学习(将未标记数据分组到同质组中)的概念。
让我们假设预测电子邮箱中是否有辱骂内容的任务。这是一个预测监督的学习任务,我们需要一组标记为滥用或非滥用的电子邮件,以便训练分类器。现在,这种带标签的电子邮件不可能自然存在,我们将需要人类在浏览这种电子邮件的内容后进行注释,从而提供一个标签。鉴于电子邮件是高度个性化的,获取成千上万封电子邮件进行标记可能不是一件容易的事情。如果我们构建一个只有少量标记观察值的分类器,分类器可能只是在我们提供给分类器的有限标签的单词出现之间形成关系,而不是概括上下文,这仅仅是因为缺乏足够的标记数据。
开发语言模型或使用预训练的语言模型(如 BERT)可以通过概括上下文和单词替换来显著缓解上述问题,但我们仍然需要足够的标签来训练好分类器。我们将在第 3 部分中选择上述情况,但是现在,如果我们能够利用未标记文章上的输入数据,并允许模型以某种方式从中学习。这将理想地改进分类器。这就是半监督学习的用武之地。
下面的图片取自范·恩格伦,J.E .,霍斯,h . h .【1】的调查报告,从视觉上突出了讨论。在图像中,我们可以看到两个彩色类的固有分布,但只有两个观察值(实心形状)作为标签提供,其他所有内容都只作为未标记的数据提供。对于两类问题,当分类器仅将两个实心点(三角形和加号)视为标记观察值时,它构建的最自然的决策边界是平分两个数据标记点之间最短距离的边界。从实线和虚线的方向差异可以清楚地看出,我们的标记分类器与最佳决策边界显著不同(范·恩格伦,J.E .,胡斯,H.H 2020)。

图片由范·恩格伦,J.E .胡斯,H.H .拍摄自https://link . springer . com/article/10.1007/s 10994-019-05855-6/figures/1
这就带来了一个自然的问题——如何在创建正确的决策边界方面实现上述改进?
正如范·恩格伦、J.E .霍斯和 H.H .所做的调查所确定的那样[1],半监督学习最广泛认可的假设是:
1.平滑度假设(如果两个样本 x 和 x’在输入空间中接近,它们的标签 y 和 y’应该相同),
2.低密度假设(决策边界不应穿过输入空间中的高密度区域),以及
3.流形假设(同一低维流形上的数据点应该有相同的标签)。
尽管上述情况经常发生,但没有保证性能提升,就像在监督学习中一样,没有一种算法是最好的。有时,半监督模型会降低监督模型的性能,特别是当上述假设不成立时,即,您通过输入与监督模型已知的现有(和真实)条件分布有显著不同分布的输入数据,向分类器添加噪声。
除此之外,不同的半监督算法在标记数据的大小、数据流形及其分布、甚至所使用的特定标记数据点方面表现出不同的性能。因此,对一系列数据集和标注的数据大小进行比较非常重要,这样才能真正执行任何类型的评估。
了解两大类半监督学习(SSL)方法
关于目前可用的各种半监督算法,如范·恩格伦、J.E .和霍斯进行的调查所确定的[1]:
M 方法的不同之处在于它们所基于的半监督学习假设,(1)它们如何利用未标记的数据,以及(2)它们与监督算法相关的方式。
让我们从 SSL 算法通常属于的两个明显不同的家族开始:
- 归纳:Romeyn,J. W. (2004 年)发表的著作[2]指出了归纳算法的如下作用:
归纳预测过程从过去和当前的样本中得出关于未来实例的结论,即它通常依赖于由现象的特定实例组成的数据集。
这与依赖于条件概率建模的经典机器学习是一样的。他们如何使用未标记的观察值是不同归纳 SSL 算法的不同之处。
- 直推式:根据维基百科【3】:
转导或转导推理是从观察到的特定(训练)案例到特定(测试)案例的推理。这与归纳形成对比,归纳是从观察到的训练案例中推理出通用规则,然后应用于测试案例。
简而言之,直推方法通过利用所有未标记的数据,使用未标记的观察值内的距离和对照标记的观察值,为未标记的数据点产生预测的标记。最终目标是一个目标函数,当它被优化时,会对标记的观察结果产生正确的预测,并允许基于相似性对未标记的观察结果进行预测。
归纳法的执行和实现要简单得多。可用的学习算法的整个家族可以从范·恩格伦,J.E .,霍斯,H.H 的调查论文中读到[1]。
通过这个系列,我们计划使用两种类型的归纳半监督方法进行实验。在第一种方法中,我们仅从已标记的数据开始并构建模型,在模型有信心提供标签的地方,我们顺序地向其中添加未标记的数据。在第二种方法中,我们一起处理整个数据集,并通过添加微小变化和噪声来增加数据集,以减少输入分布中的边界分离。
- 使用伪标签的自我训练: 使用伪标签的自我训练:我们从有标签的数据开始,通过利用初始模型,在有标签的数据上训练,预测无标签的数据。这些预测被称为伪标签,因为这些预测被缓慢地(即,如果它们高于要被包括的某个阈值,或者如果它们是单次迭代中允许的前 N 个的一部分)添加以重新训练分类器。重复这个过程直到收敛。通过将这些伪标签包含在更简单的包装器函数中,可以生成这些伪标签并将其添加到任何监督学习算法之上的再训练中(范·恩格伦、J.E .、胡斯、H.H. 2020)。我们将在第二部分中通过实验实现这一点。
- 扩充输入数据:这些算法通过一起使用标记和未标记的数据来训练模型。除了原始标记样本之外,还通过对增加的数据点进行训练来实现这一点(范·恩格伦,J.E .胡思,H.H. 2020)。通过比如说反向翻译或生成合成数据,将噪声添加到现有的观察数据中,允许模型更好地从未标记的数据中进行归纳和学习。我们将在第 3 部分的实验中实现这一点。

图片由 Hamed-Hassanzadeh 提供,直观解释自我训练,摘自https://www . research gate . net/publication/326733520 _ Clinical _ Document _ class ification _ Using _ Labeled _ and _ Unlabeled _ Data _ cross _ Hospitals
这就是你们——我们已经从根本上理解了半监督学习技术试图在引擎盖下做什么,以及一些更直接的技术如自我训练实际上是如何工作的。
接下来,在第二部分中,我们将用 Python 在不同的文本数据集上运行这些算法。到时见,感谢您的阅读!
包括本文在内的 3 部分系列是 Sreepada Abhinivesh 和 Naveen Rathani 的合作成果,Sreepada Abhinivesh 是一位热情的 NLP 数据科学家,拥有印度科学研究所(IISC)的硕士学位,而 Naveen rat Hani是一位应用机器学习专家和数据科学爱好者。
[1]范·恩格伦,J.E .,胡斯,H.H .关于半监督学习的调查。马赫学 109,373–440(2020)。https://doi.org/10.1007/s10994-019-05855-6
[2]罗梅因,J. W. (2004 年)。“假设和归纳预测:包括坠机数据的例子” (PDF)。合成。141(3):333–64。doi:10.1023/B:synt . 0000044993.82886 . 9 e。JSTOR2011 8 4 86。S2CID121862013。
[3]转导(机器学习)。"https://en . Wikipedia . org/wiki/Transduction _(machine _ learning)
使用纯 Python 提高您的 SQL 技能
原文:https://towardsdatascience.com/improve-your-sql-skills-using-pure-python-9e50736b3d9c
而不需要安装任何东西。

在 Unsplash 上由 Iewek Gnos 拍摄的照片
如果你从事数据科学领域的工作,无论职位高低,SQL 都是你需要具备的最重要的技能之一。我会把它和 Python 一起放在前 3 名。
当我刚开始学习数据科学时,我发现建立一个练习 SQL 的环境有点困难。我需要安装一个数据库引擎和一个 IDE 来编写查询,如 MySQL Workbench 或 pgAdmin。
进行这种设置并不复杂,当你在数据科学领域找到一份工作时,你可能会使用这样的环境。然而,当你刚开始学习时,有一个更简单的练习方法。
这个选项是 sqlite3,它是一个内置的 Python 模块。它提供了一个易于使用的 SQL 接口。sqlite3 模块基于 sqlite,这是一个 C 语言库,提供了一个轻量级的基于磁盘的数据库。
既然我们大多数人都是从安装 Python 开始数据科学之旅的,为什么不利用它来学习 SQL 呢?
在本文中,我们将通过几个示例来学习如何:
- 创建数据库
- 创建表格
- 填充表格
- 查询表格
使用 sqlite3 模块。
创建数据库
第一步是创建一个数据库。我们可以使用连接功能来完成这项任务。它用于连接数据库,如果数据库不存在,connect 函数也会创建它。
import sqlite3
con = sqlite3.connect("sample.db")
下一步是创建数据库游标,这是执行 SQL 语句和获取结果所必需的。
cursor = con.cursor()
我们可以使用下面的查询和 fetchall 函数来查看这个数据库中的表。我们基本上是在查询内置的 sqlite_master 表。
check_tables = """SELECT name FROM sqlite_master WHERE type='table';"""
cursor.execute(check_tables)
cursor.fetchall()
# output
[]
因为数据库不包含任何表,所以 fetchall 函数返回一个空列表。
创建表格
让我们创建一个表。我们将编写查询来创建一个表,然后使用游标来执行它。
create_products_table = """CREATE TABLE IF NOT EXISTS products (
id integer PRIMARY KEY,
category text NOT NULL,
price real,
cost real
);"""
cursor.execute(create_products_table)
让我们再次检查示例数据库中的表。
check_tables = """SELECT name FROM sqlite_master WHERE type='table';"""
cursor.execute(check_tables)
cursor.fetchall()
# output
[('products',)]
我们现在看到数据库中存在 products 表。
我们可以使用光标执行任何查询。例如,下面的代码片段执行查询来选择 products 表中的所有行,并将结果赋给一个名为 result 的变量。然后,对结果变量调用 fetchall 函数,以查看它包含的内容。
result = cursor.execute("SELECT * FROM products")
result.fetchall()
# output
因为 products 表是空的,所以它不返回任何内容。让我们使用 insert into 语句填充它。
populate_products_table = """INSERT INTO products VALUES
(1001, 'A', 15.9, 12.9),
(1002, 'B', 24.9, 20.5),
(1003, 'A', 13.5, 10.6),
(1004, 'A', 17.5, 13.5),
(1005, 'B', 28.9, 23.5)
;"""
cursor.execute(populate_products_table)
注意:insert 语句隐式打开一个事务,需要在修改保存到数据库之前提交。
con.commit()
products 表现在应该有 5 行。让我们通过执行上面的 select 语句来确认。
result = cursor.execute("SELECT * FROM products")
result.fetchall()
# output
[(1001, 'A', 15.9, 12.9),
(1002, 'B', 24.9, 20.5),
(1003, 'A', 13.5, 10.6),
(1004, 'A', 17.5, 13.5),
(1005, 'B', 28.9, 23.5)]
当然,我们可以编写更高级的查询。例如,以下查询返回属于类别 a 的行。
result = cursor.execute("SELECT * FROM products WHERE category='A'")
result.fetchall()
# output
[(1001, 'A', 15.9, 12.9),
(1003, 'A', 13.5, 10.6),
(1004, 'A', 17.5, 13.5)]
创建熊猫数据框架的一行代码
sqlite3 模块的一个好处是它兼容熊猫。因此,我们可以很容易地将查询结果写入 Pandas 数据帧。
我们可以用查询和连接对象调用熊猫的 read_sql_query 函数。
products = pd.read_sql_query("SELECT * FROM products", con)
products

产品数据框架(图片由作者提供)
让我们创建另一个表并填充它。
create_customers_table = """CREATE TABLE IF NOT EXISTS customers (
id integer PRIMARY KEY,
customer id integer NOT NULL,
is_member integer NOT NULL,
purchase_date text,
purchased_product integer,
purchase_quantity integer
);"""
populate_customer_table = """INSERT INTO customers VALUES
(1, 110, 0, "2022-12-23", 1002, 5),
(2, 112, 0, "2022-12-14", 1001, 4),
(3, 113, 1, "2022-12-08", 1003, 6),
(4, 113, 1, "2022-12-14", 1002, 4),
(5, 114, 0, "2022-12-21", 1004, 10)
;"""
cursor.execute(create_customers_table)
cursor.execute(populate_customer_table)
con.commit()
我们可以用 customers 表创建一个数据框架,如下所示:
customers = pd.read_sql_query("SELECT * FROM customers", con)
customers

客户数据框架(图片由作者提供)
更复杂的查询
让我们编写一个查询,从 products 和 customers 表中检索数据。它将包括一个连接语句。
query = '''
SELECT
customer_id,
purchased_product,
purchase_quantity,
price
FROM customers c
LEFT JOIN products p ON c.purchased_product = p.id
'''
cursor.execute(query)
cursor.fetchall()
# output
[(110, 1002, 5, 24.9),
(112, 1001, 4, 15.9),
(113, 1003, 6, 13.5),
(113, 1002, 4, 24.9),
(114, 1004, 10, 17.5)]
您可以使用 description 属性查看查询输出中的列名:
cursor.description
# output
(('customer_id', None, None, None, None, None, None),
('purchased_product', None, None, None, None, None, None),
('purchase_quantity', None, None, None, None, None, None),
('price', None, None, None, None, None, None))
# save the names in a Python list using list comprehension
col_names = [description[0] for description in cursor.description]
col_names
# output
['customer_id', 'purchased_product', 'purchase_quantity', 'price']
关闭连接
我们可以使用 close 方法来关闭当前到数据库的连接。
con.close()
为了验证更改是否保存在数据库中,我们可以创建一个新的连接并编写一个查询来进行检查。
con = sqlite3.connect("sample.db")
cursor = con.cursor()
result = cursor.execute("SELECT * FROM products")
result.fetchall()
# output
[(1001, 'A', 15.9, 12.9),
(1002, 'B', 24.9, 20.5),
(1003, 'A', 13.5, 10.6),
(1004, 'A', 17.5, 13.5),
(1005, 'B', 28.9, 23.5)]
是的,更改保存在 sample.db 数据库中。
结论
sqlite3 模块为实践 SQL 提供了一个易于使用的环境,这对于在数据科学生态系统中工作的几乎所有人来说都至关重要。
不必安装任何额外的工具或数据库引擎,这使得使用 sqlite3 更加令人鼓舞。此外,将查询结果保存到 Pandas 数据框架中只需要一行代码。我认为这是一个很棒的特性,因为 Pandas 是使用最广泛的数据操作和分析工具。
你可以成为 媒介会员 解锁我的全部写作权限,外加其余媒介。如果你已经是了,别忘了订阅https://sonery.medium.com/subscribe如果你想在我发表新文章时收到电子邮件。
感谢您的阅读。如果您有任何反馈,请告诉我。
利用随机和确定性成分分解改进时间序列分析
如何将时间序列分解成可以用动力系统和随机过程方法预测的部分

在我的上一篇文章中,我谈到了如何使用 Taken 的嵌入定理,通过标准的机器学习算法来预测确定性的时间序列数据。
我在那篇文章中没有谈到的是,如果我们正在处理的序列不是完全确定的,我们可以做什么。正如我们在文章中看到的,当系列出现一些噪音时,预测的质量会下降很多。
在本文中,我们将分析并实现论文“的结果,该论文应用经验模式分解和互信息来分离嵌入信号【1】中的随机和确定性影响,该论文提出了一个框架,在给定时间序列的情况下,将其分解为随机和确定性成分。
能够生成这种分解将提高我们利用图克嵌入定理所能实现的预测,并且还将允许我们单独对随机分量建模。
这里的主要思想是分析由应用于时间序列的经验模式分解方法产生的 IMFs 的相位谱。让我们来理解这意味着什么,以及我们如何将它应用到现实世界的问题中。
经验模态分解
第一步是把我们的时间序列分成所谓的固有模态函数(IMF)。为此,我们将使用一种称为经验模式分解(EMD)的方法,它将接收一个时间序列作为输入,并从中生成 IMF。
但什么是国际货币基金组织?
IMF 本身是一个时间序列,它满足两个条件:
- 序列的最大值和最小值之间的差值绝对不能大于 1
- IMF 的平均值必须为零
EMD 将通过应用以下算法来生成这些 IMF:
- 找出数列的所有最大值和最小值
- 使用三次样条插值它们,生成我们所说的包络。对于计算机科学的人来说,你可以把它看作是大 O 和大ω函数
- 计算这个包络的平均值,并从中减去原始信号
我们可以在这个图上看到我所说的上述过程的意思:

基于 EMD 方法的包络创建。由作者生成的图像
我们从平均值中减去原始时间序列后获得的这个新信号将被测试,以验证它是否是一个 IMF。如果一个或多个条件不满足,那么算法将重复自身,但是这次是在这个新的候选上而不是在原始的时间序列上。直到我们找到一个国际货币基金组织。
一旦找到一个 IMF,就从这个 IMF 中减去原始时间序列,产生一个新的信号,我们称之为 h。然后,我们将对 h 重复整个过程,直到找到下一个 IMF,然后从 h 中减去,依此类推。
只有当我们找到一个单调的序列(即,它只增长或减少),或者换句话说,一个只有一个极值点(最大值或最小值)的序列时,这个过程才会停止。这一系列将被称为剩余。
解释 IMFs 并编码
由于这个过程是如何完成的,人们可以很容易地看到,如果我们得到所有的 IMF 和残差,并将它们相加,我们将得到原始序列。
这就是为什么我们称这种方法为“分解”。我们正在将我们的系列分解成一组组件。除此之外,第一个 IMF 将包含噪声最大的信号,而后面的 IMF 将呈现更具确定性的成分,如下所示。
现在,让我们把它编码起来。为此,我们需要安装一个已经实现了 EMD 的库,名为 PyEMD。我们可以通过 pip 轻松做到这一点:
pip install EMD-signal
我们还将使用 sknet 库中的一些免费数据集和 sklearn 库中的一个函数,因此我们也必须安装它们:
pip install scikit-learn
pip install sktime
现在,让我们创建一个函数,它将返回给定信号的 IMFs:
import numpy as np
from scipy.fft import fft
import matplotlib.pyplot as pltfrom PyEMD import EMD
from sktime.datasets import load_airline, load_shampoo_sales
from sklearn.feature_selection import mutual_info_regressiondef emd(signal):
emd = EMD(DTYPE=np.float16, spline_kind='akima')
imfs = emd(signal.values)
t = [i for i in range(len(signal))]
N = imfs.shape[0]
fig, axs = plt.subplots(N + 1, 1, figsize=(15,9)) axs[0].plot(t, signal)
axs[0].set_title('Original Signal')
for n, imf in enumerate(imfs):
axs[n+1].plot(t, imf)
axs[n+1].set_title(f'IMF {n}')
return imfs
在这个函数中,我们只是从 PyEMD 库中调用 EMD,并将它们与原始数据一起绘制。让我们将它应用到来自 sktime 的航空公司数据[2]中,这是我们可以免费使用的,看看会发生什么:

航线数据集的 EMD 分解[3]。图片由作者生成。
你可以看到 IMF 3 实际上表现得像一个残渣。
现在我们有了自己的国际货币基金组织,我们能用它们做什么?它们对我们如何区分时间序列中的随机成分和确定成分有用?
相位谱
生成 IMF 后,我们可以分析这些元件在频谱上的表现。一个随机信号的频谱到处都是,没有清晰的频率。另一方面,确定性信号会表现得更好。
一种直观的方法是在频率空间上观察这些成分的相位谱。为此,我们必须首先通过快速傅里叶变换(FFT)获得 IMF 的频率表示,然后用虚部除以实部。
现在,我们将从那个阶段的角度来看,这将使我们更容易看到发生了什么。为此,我们必须应用反正切函数。
让我们看看这在代码中是什么样子的:
def phase_spectrum(imfs):
imfs_p = []
fig, axs = plt.subplots(len(imfs), 1, figsize=(15,9))
for i, imf in enumerate(imfs):
trans = fft(imf)
imf_p = np.arctan(trans.imag / trans.real)
imfs_p.append(imf_p)
axs[i].plot(imf_p, 'o')
axs[i].set_title(f'IMF {i}')
return imfs_p
我们只是应用 Scipy 库中的 FFT,然后进行我之前描述的计算。最后,我们还绘制了阶段图,生成了下图:

国际货币基金组织的阶段。由作者生成的图像
可以看到,当我们沿着 IMF 向下接近余数时,随着角度开始对齐,一种模式开始出现。这对我们来说是决定性行为的一个标志。
所以,看这个图,最后两个部分似乎比前两个更具决定性。因此,我们可以这样选择它们,并手动定义随机和确定性组件。然而,有一种更好的方法。
利用互信息选择 IMFs
评估两个分布之间关系的一种方法是互信息。这一指标的逻辑是,通过观察一个变量,衡量一个人可以从另一个变量获得多少信息。
两个随机信号之间的互信息应该为零。如果它们中只有一个是确定性的,那么这个值也应该相当接近于零。如果我们有两个确定性信号,那么这个值应该更大。
知道了这一点,我们可以在我们的 IMF 上应用互信息,并观察它何时达到峰值。在这一点上,我们应该开始将 IMFs 视为确定性组件。
让我们把它编码起来:
def phase_mi(phases):
mis = []
for i in range(len(phases)-1):
mis.append(mutual_info_regression(phases[i].reshape(-1, 1), phases[i+1])[0])
return np.array(mis)
这里我们利用了 sklearn 库中的计算。
现在,有很多方法可以使用交互信息。在这篇文章中,我们将寻找一个分界点。
分割信号
现在我们有了将时间序列分成两部分所需的一切。让我们看看代码:
def divide_signal(signal, imfs, mis, cutoff=0.05):
cut_point = np.where(mis > cutoff)[0][0] stochastic_component = np.sum(imfs[:cut_point], axis=0)
deterministic_component = np.sum(imfs[cut_point:], axis=0)
t = [i for i in range(len(signal))]
fig, axs = plt.subplots(3, 1, figsize=(15,12))
axs[0].plot(t, signal.values)
axs[0].set_title('Original Signal')
axs[1].plot(t, stochastic_component)
axs[1].set_title('Stochastic Component')
axs[2].plot(t, deterministic_component)
axs[2].set_title('Deterministic Component')
return stochastic_component, deterministic_component
这里,我们定义多少互信息将被视为确定性分量的开始,然后我们对 IMF 求和,并绘制原始信号旁边的分量:

现在,有了这两个分量,我们就可以利用图森嵌入定理预测确定性分量,利用 ARIMA 等预测工具预测随机分量,以捕捉可能仍存在于该分量上的某些确定性残差。
结论
现在,我们的工具包中又多了一个工具,可以用来改进我们的时间序列分析。使用这种方法和图克的嵌入定理,我们有一个很好的框架来开始分析和预测时间序列数据。
当然,没有一种方法是完美的,在某些情况下,这个框架不会产生最好的结果。但这就是数据科学的美妙之处,没有先验的正确答案,所以我们必须深入问题,找出前进的最佳方式。
感谢
我必须在这里感谢 Rodrigo Mello,他在 2019 年在我们大学的一堂课上首次向我介绍了这些概念。
[1]里奥斯,里卡多·阿劳若;罗德里戈·费尔南德斯·德·梅洛(2016 年)。应用经验模式分解和互信息分离嵌入信号中的随机和确定性影响。信号处理,118(),159–176。doi:10.1016/j.sigpro
[2] Box,G. E. P .,Jenkins,G. M .和 Reinsel,G. C. (1976 年)时间序列分析、预测和控制。第三版。霍尔登日。g 系列。
改善咖啡萃取指标:强度半径
原文:https://towardsdatascience.com/improving-coffee-extraction-metrics-intensity-radius-bb31e266ca2a
咖啡数据科学
结合 TDS 和 EY,更好地比较不同产出比的咖啡
在过去的三年里,我一直在研究咖啡数据科学。我通常根据味道和提取率来评价事物(EY)。然而,EY 的一个一贯问题是产量。我试着让我的浓缩咖啡产量彼此接近,但这和其他技术相比如何呢?这就是为什么我在看一个我称之为强度半径的东西,但是很有可能其他人也在做同样的事情,称它为别的东西。
绩效指标
我使用两个指标来评估技术之间的差异:最终得分和咖啡萃取。
最终得分 是记分卡 7 个指标(尖锐、浓郁、糖浆、甜味、酸味、苦味和回味)的平均值。当然,这些分数是主观的,但它们符合我的口味,帮助我提高了我的拍摄水平。分数有一些变化。我的目标是保持每个指标的一致性,但有时粒度很难确定。
用折射仪测量总溶解固体量(TDS),这个数字结合咖啡的输出重量和输入重量用于确定提取到杯中的咖啡的百分比,称为提取率(EY)** 。**
*EY = ( TDS 输出)/输入
然后,您可以在控制图中比较 TDS 和 EY,如下所示:

所有图片由作者提供
产出产量
产量就是一杯浓缩咖啡的重量(g)。我通常用 22 克咖啡,22 克到 28 克之间。所以我的产出投入比范围是 1:1 到 1.3:1。
通常在浓缩咖啡中,人们会选择 2:1 的比例或者 1:2 的比例(我用另一种方式写,因为基于我的工程背景,这样感觉更正确)。有些人要求允许 4:1 或 5:1 的赔率。
这些较长的镜头 TDS 较低,但 ey 较高。那么如何比较断奏和全奏呢?

强度半径(IR) 定义为 TDS vs EY 控制图上原点的半径,所以 IR = sqrt( TDS + EY)。这一指标有助于标准化产量或酿造比的击球性能。

强度半径示例
所以我们可以用强度半径来看这张控制图。这意味着,如果你在 27%的 EY 拍摄一个 5% TDS 的镜头,它的 IR 为 27%,这与在 20% EY 拍摄一个 18% TDS 的镜头是一样的。目标是获得更高的 IR。

我自己的数据
我回顾了我的一些数据,看看 IR 会显示什么,这在两个变量的情况下很难看到。当看一张意大利腊肠照片时,我最初画的是 EY,但是我在这张图上加了 TDS 和 IR。

IR 开始很高,下降,然后达到稳定状态,略有下降。这可能有助于理解何时达到提取的物理极限,因为 IR 应该开始随时间而降低。
短镜头对长镜头
对于一大块数据,我有 TDS 和 EY 的两个数据点。我收集了我通常拍摄的 1:1 和 3:1 的照片。我有一个杠杆式咖啡机,所以当我拿掉杯子后,咖啡会一直流出来。因此,我分别测量了 TDS 和重量,以通知 EY 拍摄更长的镜头。
然后,我绘制了这些数据对的强度半径,以观察它们的趋势。

这个数据表明,大约 28%的红外辐射,拉一个更长的镜头会降低红外辐射。一个例子是 20% TDS 的拍摄,即 1:1 拍摄,因此 EY 也是 20%,但 IR 是 28.3%。随着拍摄时间延长,红外线会增加还是减少?不清楚。
我的目的是帮助更公平地比较我的镜头以及其他镜头和技术的变化。即使在我拉的各种镜头中,我也希望有更好的对比。断奏拍摄拉非常高的 EY 在 1:1,其中断奏捣实拍摄达到最大 EY 约 1.3:1,但这是棘手的比较两者,因为他们最好的 EY 和口味达到不同的比例。
如果你愿意,可以在推特、 YouTube 和 Instagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以关注我在中和订阅。
我的进一步阅读:
使用机器学习方法改进营销组合建模
使用基于树的集合构建 MMM 模型,并使用 SHAP 解释媒体通道性能

T 有许多方法可以建立营销组合模型(MMM ),但通常来说,由于其简单的可解释性,它可以归结为使用线性回归。更复杂的非线性模型的可解释性是过去 5-6 年的研究主题,因为在机器学习社区中提出了时间或 SHAP 这样的概念来解释模型的输出。然而,这些新概念在营销归因领域似乎几乎无人知晓。在本文中,我继续研究营销组合建模的实用方法,使用随机森林构建基于树的集合,并使用 SHAP 概念解释媒体渠道绩效。
在我的上一篇文章中,我使用贝叶斯编程建立了一个营销组合模型,并将结果与 Robyn 框架进行了比较。我的主要兴趣是调查这两种方法是否具有可比性,在讲故事时是否一致。由于 Robyn 生成了多个解决方案,我能够找到一个与贝叶斯解决方案一致的解决方案,即两个模型的效果份额始终高于或低于每个渠道的支出份额。百分比差异可归因于方法的差异和模型很好地拟合数据的能力。然而,这两种方法的共同点是都描述了媒体支出和回应之间的线性关系,因此无法捕捉更复杂的变量关系,如互动。
我能找到的在营销组合建模中使用更复杂算法的第一个商业概念证明之一是由 H2O.ai 描述的,如梯度推进机(GBM)和 SHAP。
我总结了转向更复杂算法背后的主要动机:
- 线性回归等经典方法具有挑战性,需要时间和专业知识来识别适当的模型结构,如变量相关性、相互作用或非线性关系。在某些情况下,应该移除高度相关的特征。交互变量应该被明确地设计。不同的转换应该明确地引入像饱和和收益递减这样的非线性。
- 为了更容易的模型可解释性,牺牲了一些模型结构,这可能导致较差的模型性能。
- 更复杂的机器学习算法,如基于树的集成,在存在高度相关的变量时工作良好,可以捕捉变量之间的交互,是非线性的,并且通常更准确。
SHAP 对于模型解释背后的细节在很多文章和书籍里都有解释。我将 SHAP 背后的主要直觉总结如下:
SHAP(SHapley Additive explaints)是一种解释任何机器学习模型输出的博弈论方法。它将最优信用分配与使用博弈论及其相关扩展的经典 Shapley 值的本地解释联系起来
- SHAP 是一种解释个体预测的方法,并回答了问题每个特征对这个预测有多大贡献
- SHAP 值是特征重要性的度量
- SHAP 值可以是负的也可以是正的,并显示相对于所有预测平均值的预测量。绝对量值表示特定个体预测的特征强度
- 每个特征的 SHAP 值的绝对值的平均值表示该特征的全局重要性
- 在某种程度上,SHAP 特征重要性可以替代排列特征重要性。与 SHAP 相反,排列特征重要性基于模型性能的整体下降。
MMM 转向更复杂模型的最大挑战是缺乏解释单个媒体渠道影响的工具。虽然机器学习社区正在广泛使用像 SHAP 这样的模型可解释性方法,这是由数百篇论文和会议讨论提出的,但在 MMM 上下文中找到 SHAP 用法的例子仍然非常困难。这篇精彩的文章将 MMM 与 SHAP 联系起来,并解释了我们如何解读营销组合的结果。受这篇文章的启发,我写了一个几乎通用的解决方案来模拟营销组合,结合 Robyn 的趋势和季节性分解方法的思想,使用随机森林估计器(可以很容易地改变为其他算法),并使用 Optuna (超参数优化框架)优化 adstock 和模型特定的参数。该解决方案允许在 MMM 中通常使用的单目标优化和 Robyn 使用的多目标优化之间切换。
数据
在我的第一篇文章中,我继续使用由 Robyn 在麻省理工学院许可下提供的数据集进行一致性和基准测试,并通过应用 Prophet 来分解趋势、季节性和假日,遵循相同的数据准备步骤。
该数据集包含 208 周的收入(从 2015 年 11 月 23 日到 2019 年 11 月 11 日),包括:
- 5 个媒体消费渠道:电视、网络、印刷品、facebook、搜索
- 2 个也有曝光信息(印象,点击)的媒体渠道:facebook_I,search_clicks_P(本文未使用)
- 无支出有机媒体:时事通讯
- 控制变量:事件、节假日、竞争对手销售额(competitor_sales_B )
分析窗口为 2016 年 11 月 21 日至 2018 年 8 月 20 日的 92 周。
库存/结转效应
不考虑建模算法,广告素材在 MMM 中起着重要的作用。因此,我们必须决定我们要试验哪种 adstock,以及对于每个媒体频道,它可能具有的最小和最大值是什么(请参考我的以前的文章以了解各种 adstock 功能的概述)。优化算法将尝试定义值范围内的每个 adstock 值,以找到最小化优化标准的最佳值。
我正在使用 scikit 中实现的几何 adstock 函数,如下所示:
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.utils import check_array
from sklearn.utils.validation import check_is_fittedclass AdstockGeometric(BaseEstimator, TransformerMixin):
def __init__(self, alpha=0.5):
self.alpha = alpha
def fit(self, X, y=None):
X = check_array(X)
self._check_n_features(X, reset=True)
return self
def transform(self, X: np.ndarray):
check_is_fitted(self)
X = check_array(X)
self._check_n_features(X, reset=False)
x_decayed = np.zeros_like(X)
x_decayed[0] = X[0]
for xi in range(1, len(x_decayed)):
x_decayed[xi] = X[xi] + self.alpha* x_decayed[xi - 1]
return x_decayed
收益递减/饱和效应
我已经提到过,线性模型无法捕捉不同水平的广告支出和结果之间的非线性关系。因此,在建模之前,对媒体信道应用了各种非线性变换,例如幂、负指数和山。
基于树的算法能够捕捉非线性。因此,我没有明确地应用任何非线性变换,而是让模型自己学习非线性。
造型
建模由几个步骤组成:
Adstock 参数
广告能有多长时间的效果取决于媒体渠道。因为我们正在寻找一个最佳的吸附衰变率,我们必须对参数的可能范围持现实态度。例如,众所周知,电视广告可能具有持久的效果,而印刷广告具有较短的效果。因此,我们必须灵活地为每个媒体通道定义现实的超参数。在这个例子中,我使用 Robyn 在他们的演示文件中提出的精确范围。
adstock_features_params = {}
adstock_features_params["tv_S_adstock"] = (0.3, 0.8)
adstock_features_params["ooh_S_adstock"] = (0.1, 0.4)
adstock_features_params["print_S_adstock"] = (0.1, 0.4)
adstock_features_params["facebook_S_adstock"] = (0.0, 0.4)
adstock_features_params["search_S_adstock"] = (0.0, 0.3)
adstock_features_params["newsletter_adstock"] = (0.1, 0.4)
时间序列交叉验证
我们希望找到能很好地概括未知数据的参数。我们必须将数据分成训练集和测试集。由于我们的数据代表了时间线上发生的支出和收入,我们必须应用时间序列交叉验证,以便训练集只包含测试集中事件之前发生的事件。
机器学习算法在对大量数据进行训练时效果最佳。随机森林算法也不例外,为了捕捉变量之间的非线性和相互作用,应该对大量数据进行训练。正如我前面提到的,我们总共只有 208 个数据点,在分析窗口中有 92 个数据点。我们需要在泛化能力和模型的学习能力之间做一些权衡。
经过一些实验,我决定通过分配 20 周的数据(大约 10%)作为测试集来使用 3 个 cv-splits。
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=3, test_size = 20)
每个连续的训练集分裂都比前一个大。
tscv = TimeSeriesSplit(n_splits=3, test_size = 20)
for train_index, test_index in tscv.split(data):
print(f"train size: {len(train_index)}, test size: {len(test_index)}")#train size: 148, test size: 20
#train size: 168, test size: 20
#train size: 188, test size: 20
使用 Optuna 优化超参数
超参数优化由大量实验或试验组成。每次审判大致可以分为三步。
- 使用一组 adstock 参数在媒体频道上应用 adstock 变换
for **feature** in **adstock_features**:
adstock_param = f"{feature}_adstock"
min_, max_ = adstock_features_params[adstock_param]
**adstock_alpha** = trial.suggest_uniform(f"adstock_alpha_{feature}", min_, max_)
adstock_alphas[feature] = adstock_alpha
#adstock transformation
x_feature = data[feature].values.reshape(-1, 1)
temp_adstock = **AdstockGeometric**(alpha = adstock_alpha).fit_transform(x_feature)
data_temp[feature] = temp_adstock
- 为 随机森林 定义一组建模参数。
#Random Forest parameters
n_estimators = trial.suggest_int("n_estimators", 5, 100)
min_samples_leaf = trial.suggest_int('min_samples_leaf', 1, 20)
min_samples_split = trial.suggest_int('min_samples_split', 2, 20)
max_depth = trial.suggest_int("max_depth", 4,7)
ccp_alpha = trial.suggest_uniform("ccp_alpha", 0, 0.3)
bootstrap = trial.suggest_categorical("bootstrap", [False, True])
criterion = trial.suggest_categorical("criterion",["squared_error"])
- 交叉验证并测量所有测试集的平均误差
for train_index, test_index in **tscv.split**(data_temp):
**x_train** = data_temp.iloc[train_index][**features**]
**y_train** = data_temp[target].values[train_index]
**x_test** = data_temp.iloc[test_index][features]
**y_test** = data_temp[target].values[test_index]
#apply Random Forest
**params** = {"n_estimators": n_estimators,
"min_samples_leaf":min_samples_leaf,
"min_samples_split" : min_samples_split,
"max_depth" : max_depth,
"ccp_alpha" : ccp_alpha,
"bootstrap" : bootstrap,
"criterion" : criterion
}
#train a model
rf = **RandomForestRegressor**(random_state=0, **params)
rf.**fit**(x_train, y_train)
#predict test set
prediction = rf.**predict**(x_test)
#RMSE error metric
rmse = **mean_squared_error**(y_true = y_test, y_pred = prediction, squared = False)
#collect errors for each fold
scores.**append**(rmse)#finally return the average of the cv error
**return np.mean(scores)**
每次试验都将 adstock、模型参数和误差度量作为用户属性返回。这允许在最佳试验中容易地检索参数。
trial.set_user_attr("scores", scores)
trial.set_user_attr("params", params)
trial.set_user_attr("adstock_alphas", adstock_alphas)
开始优化的主要函数是 optuna_optimize。它返回包含所有试验的 Optuna 研究对象,包括最佳试验(具有最小的平均 RMSE 误差)
tscv = TimeSeriesSplit(n_splits=3, test_size = 20)adstock_features_params = {}
adstock_features_params["tv_S_adstock"] = (0.3, 0.8)
adstock_features_params["ooh_S_adstock"] = (0.1, 0.4)
adstock_features_params["print_S_adstock"] = (0.1, 0.4)
adstock_features_params["facebook_S_adstock"] = (0.0, 0.4)
adstock_features_params["search_S_adstock"] = (0.0, 0.3)
adstock_features_params["newsletter_adstock"] = (0.1, 0.4)OPTUNA_TRIALS = 2000#experiment is an optuna study object
experiment = **optuna_optimize**(**trials** = OPTUNA_TRIALS,
**data** = data,
**target** = target,
**features** = features,
**adstock_features** = media_channels + organic_channels,
adstock_features_params = adstock_features_params,
**media_features**=media_channels,
**tscv** = tscv,
is_multiobjective=False)
最佳试验各折的 RMSE 分数:
experiment.best_trial.user_attrs["scores"]#[162390.01010327024, 114089.35799374945, 79415.8649240292]
对应于最佳试验的 Adstock 参数:
experiment.best_trial.user_attrs["adstock_alphas"]#{'tv_S': 0.5343389820427953,
# 'ooh_S': 0.21179063584028718,
# 'print_S': 0.27877433150946473,
# 'facebook_S': 0.3447366707231967,
# 'search_S': 0.11609804659096469,
# 'newsletter': 0.2559060243894163}
对应于最佳试验的模型参数:
experiment.best_trial.user_attrs["params"]#{'n_estimators': 17,
# 'min_samples_leaf': 2,
# 'min_samples_split': 4,
# 'max_depth': 7,
# 'ccp_alpha': 0.19951653203058856,
# 'bootstrap': True,
# 'criterion': 'squared_error'}
最终型号
我通过提供分析的开始和结束时段,使用优化的参数构建最终模型。该模型首先基于截至分析期结束时的所有数据。仅检索分析期间的预测值和 SHAP 值。
**best_params** = experiment.best_trial.user_attrs["params"]
**adstock_params** = experiment.best_trial.user_attrs["adstock_alphas"]
result = **model_refit**(data = data,
target = target,
features = features,
media_channels = media_channels,
organic_channels = organic_channels,
model_params = best_params,
adstock_params = adstock_params,
**start_index** = START_ANALYSIS_INDEX,
**end_index** = END_ANALYSIS_INDEX)
结果
要检查的第一个图是模型与 92 周分析期数据的拟合程度:

作者图片
与贝叶斯方法相比,MAPE 提高了 40%,NRMSE 提高了 17%。
接下来,让我们绘制支出份额与效果份额的对比图:

作者图片
使用分析间隔内每个媒体通道的 SHAP 值的绝对和来计算效果份额,并通过所有媒体通道的 SHAP 值的总和来归一化。
效果份额与上一篇的效果份额几乎一致。我观察到在搜索频道的效果份额之间只有一个不一致。
收益递减/饱和效应
我没有应用任何非线性转换来明确地模拟收益递减。所以让我们来看看随机森林是否能捕捉到任何非线性。
这是通过散点图实现的,散点图显示了单个媒体渠道对模型所做预测的影响,其中 x 轴是媒体支出,y 轴是该媒体渠道的 SHAP 值,它表示知道特定支出会在多大程度上改变该特定预测的模型输出。水平线对应于 SHAP 值 0。垂直线对应于渠道中的平均花费。绿线是一条较低的平滑曲线。





纵观所有媒体渠道,我们可以看到,更高的支出与收入的增加相关联。但是这种关系并不总是线性的。
以 print_S 为例,我们可以观察到支出达到 25K 时收入略有下降。然后它开始增加到大约 90K,在那里收入的增加减慢。
以 facebook_S 为例,我们可以观察到花费高达 9 万英镑和超过 25 万英镑的收入几乎没有变化。9 万到 25 万之间的花费可能是最理想的花费。
一些媒体渠道如 facebook_S,print_S,和 search_S 对于同样的花费,SHAP 值之间有很大的差异。这可以通过与其他媒体渠道的互动来解释,应该进一步调查。
多目标优化
这个解决方案可以管理多目标优化。这个想法来自 Robyn,他引入了第二个优化指标 RSSD(分解均方根距离)
距离说明了花费份额和渠道的系数分解份额之间的关系。如果距离太远,其结果可能太不现实——例如,花费最小的媒体活动获得最大的效果
在多目标优化的情况下,所谓的 Pareto 前沿,即所有最优解的集合,将由 Optuna 确定。该过程将与单个优化情况相同:对于属于 Pareto 前沿的每个模型,我们检索其参数,构建最终模型并可视化结果。
在下图中,所有带红色的点都属于最优集合。

作者图片
结论
在这篇文章中,我继续探索通过使用更复杂的算法来改进营销组合模型的方法,这些算法能够捕捉非线性和可变的交互。结果,通过省略非线性变换步骤,简化了整个流水线,当使用线性回归时,总是应用非线性变换步骤。使用 SHAP 值可以进一步分析效应份额和反应曲线。我的第二个目标是在不同的方法之间达到一致的结果。我使用贝叶斯建模的上一篇文章的结果与本文的结果之间的比较显示,每个媒体频道的分解效果具有高度的一致性。
完整的代码可以从我的 Github repo 下载
感谢阅读!
使用半监督包装器方法提高模型性能
使用 Python 实施和验证自我培训的实践指南

桑德·韦特林在 Unsplash 拍摄的照片
半监督学习是机器学习领域中一个活跃的研究领域。它通常用于通过利用大量未标记的数据(即输入或特征可用但基础事实或实际输出值未知的观察值)来提高监督学习问题的概化能力(即基于提供的输入和基础事实或每个观察值的实际输出值来训练模型)。在标记数据的可用性有限的情况下,这通常是一种有效的策略。
半监督学习可以通过各种技术来执行。其中一个技巧就是自我训练。你可以参考第一部分了解它的详细工作原理。简而言之,它充当一个包装器,可以集成在任何预测算法之上(能够通过预测函数生成输出分数)。原始监督模型预测未标记的观察值,并且反馈该模型的最有把握的预测,用于重新训练监督模型。这个迭代过程有望改进监督模型。
首先,我们将设置几个实验来创建和比较基线模型,这些模型将在常规 ML 算法的基础上使用自我训练。
实验设置
虽然半监督学习对于所有形式的数据都是可能的,但文本和非结构化数据是最耗时和最昂贵的标记。一些例子包括根据意图对电子邮件进行分类,预测电子邮件对话中的滥用或不当行为,在没有许多标签的情况下对长文档进行分类。预期的唯一标签数量越多,使用有限的标签数据就越困难。因此,我们选取了以下 2 个数据集(从分类角度看,以复杂性递增的顺序排列):
IMDB 评论数据:由斯坦福主持,这是一个电影评论的情感(二元-正面和负面)分类数据集。请参考此处了解更多详情。
20Newsgroup 数据集:这是一个多类分类数据集,其中每个观察都是一篇新闻文章,并标有一个新闻主题(政治、体育、宗教等)。这些数据也可以通过开源库 scikit-learn 获得。关于这个数据集的更多细节可以在这里阅读。
使用这两个数据集在不同的标记观察量下进行多个实验,以获得通用的性能估计。在这篇文章的最后,我们提供了一个对比来分析和回答以下问题:
- 自我训练对两种算法都有效且一致吗?
- 自我训练对二元和多类分类问题都适用吗?
- 随着我们添加更多的标签数据,自我训练会继续增值吗?
- 添加更大量的未标记数据会继续提高模型性能吗?
这两个数据集都可以从上面提供的各自来源下载,或者从 Google Drive 下载。所有代码都可以从 GitHub 中引用。一切都是用 Python 3.7 在 Google Colab 和 Google Colab Pro 上实现的。
为了评估每个算法在两个数据集上的表现,我们获取了多个标记数据样本(从少量到大量的标记数据)并相应地应用自我训练。
理解输入
新闻组训练数据集提供了 20 个类别的 11,314 个观察结果。我们由此创建了一个 25%的带标签的测试数据集(大约 2800 条观察结果),并将剩余的随机分为带标签的(大约 4200 条观察结果被认为带有新闻组标签)和不带标签的(大约 4300 条观察结果被认为不带新闻组标签)。在每个实验中,考虑该标记数据的一部分(从标记训练量的 20%开始,直到 100%),以查看训练中标记观察量的增加是否导致表现饱和(对于自我训练)。
对于 IMDB 数据集,考虑两个训练批次:(1)使用 2000 个未标记的观察值和(2)5000 个未标记的观察值。使用的标记观察值为 20、100、400、1000、2000。同样,假设仍然是增加标记观察的数量,将减少监督学习者和半监督学习者之间的性能差距。
实现自我训练和伪标签的概念
每个监督算法都在 IMDB 电影评论情感数据集和 20 个新闻组数据集上运行。对于这两个数据集,我们正在为分类问题建模(前一种情况下为二元分类,后一种情况下为多类)。每个算法都在 5 个不同级别的标记数据上进行性能测试,从非常低(每类大约 20 个标记样本)到高(每类 1000 多个标记样本)。
在本文中,我们将对几个算法进行自我训练——逻辑回归(通过 sklearn 使用对数损失目标实现 sgd 分类器)和香草神经网络(通过 sklearn 实现多层感知器模块)。
我们已经讨论过自我训练是一种包装方法。这可直接从 sklearn 获得,用于 sklearn 的 model.fit()方法,并作为 sklearn.semi_supervised 模块的一部分。对于非 sklearn 方法,我们首先创建包装器,然后将其传递给 pytorch 或 Tensorflow 模型。稍后会详细介绍。
让我们从读取数据集开始:
读取新闻组数据集
读取 IMDB 数据集
让我们也创建一个简单的管道来调用监督 sgd 分类器和使用 sgd 作为基本算法的自我训练分类器。
Tf-Idf — logit 分类
上面的代码很容易理解,但是让我们快速分解一下:
- 首先,我们导入所需的库,并使用几个超参数来建立逻辑回归(正则化 alpha,正则化类型为 ridge,损失函数在本例中为 log loss)。
- 接下来,我们提供计数向量器参数,其中 n_grams 由最少 1 个单词和最多 2 个标记组成。不考虑出现在少于 5 个文档或超过 80%的语料库中的 Ngrams。tf-idf 转换器获取计数矢量器的输出,并创建每个文档单词的文本的 tf-idf 矩阵作为模型的特征。你可以分别在这里和这里阅读更多关于计数矢量器和 tf-idf 矢量器的信息。
- 然后,我们定义一个空数据帧(在本例中为 df_sgd_ng ),它存储所有性能指标以及所使用的已标记和未标记卷的信息。
- 接下来,在 n_list 中,我们定义了 5 个级别来定义按标记传递的训练数据的百分比(从 10%到 50%,增量为 10%)。类似地,我们定义了两个要迭代的参数列表(kbest 和 threshold)。通常,训练算法提供每个观察的概率估计。估计值高于阈值的观察值被该迭代中的自训练视为伪标签传递给训练方法。如果阈值不可用,则可以使用 kbest 来建议应该考虑将基于其值的前 N 个观察值作为伪标签传递。
- 接下来是通常的训练测试分割。在此之后,使用屏蔽向量,通过首先将 50%的数据保持为未标记的来构建训练数据,并且在剩余的 50%的数据中,使用 n_list,标记的量被认为是递增的(即,在每次后续运行中递增地添加总标记数据的 1/5,直到使用了所有的标记数据)。同样标记的训练数据每次也独立地用于常规的监督学习。
为了训练分类器,构建了一个简单的管道:从(1)计数矢量器开始创建标记,然后是(2) TfIdf 变换器,使用 term_freq*Inverse_doc_freq 计算将标记转换为有意义的特征。随后是(3)将分类器拟合到 TfIdf 值矩阵。

Python 3.8 上的基本监督管道。作者图片
其他一切保持不变,创建并调用自训练的管道,而不是直接拟合分类器,它被包装到 SelfTrainingClassifier()中,如下所示:

Python 3.8 上的基本半监督管道。作者图片
最后,对于每种类型的分类器,通过对eval _ and _ print _ metrics _ df()的函数调用,在测试数据集上评估结果

Python 3.8 上的模型评估模块。作者图片
下图显示了在运行监督分类后再运行自训练分类时控制台输出的样子:

在 Python 3.8 上运行的 SGD 监督和半监督管道的控制台输出。作者图片
Tf-Idf — MLP 分类
MLP 分类器(即普通的前馈神经网络)的自训练以完全相同的方式进行,唯一的区别在于分类器的超参数及其调用。
注意:scikit-learn 实现的 MLP 是一个非 GPU 实现,因此具有非常密集的层或深度网络的大型数据集的训练将非常慢。上面是一个更简单的设置(2 个隐藏层,100 和 50 个神经元,max_iter=25)来测试自我训练的可推广性。在第三部分,我们将利用 Tensorflow 和 Pytorch 在 Google Colab Pro 上使用 GPU 支持来实现所有基于 ann、CNN、rnn 和 transformers 的自我训练。
对于 MLP,第一个控制台输出如下所示:

Python 3.8 上运行的 MLP 监督和半监督管道的控制台输出。作者图片
上面的控制台输出表明,在首先运行监督分类器时,模型适合 10%的训练数据(如标签所示)。这种分类器在测试数据上的性能是 0.65 微 F1 和 0.649 准确度。在用除了现有标记体积之外的 4,307 个未标记样本开始自训练时,如果未标记观测值的伪标记对于任何一个类的输出概率至少与阈值一样高(在这种情况下为 0.4),则它们将被推送用于训练的下一次迭代【仅 。在第一次迭代中,为分类器的第二次训练,将 3,708 个伪标签添加到 869 个标记的观察值中。在随后的预测中,添加另外 497 个伪标签,依此类推,直到不能再添加伪标签。
为了使比较指标一致,我们在这里创建了一个简单的实用程序。这需要训练和测试数据集以及拟合的分类器(根据我们在评估步骤中是使用监督学习还是半监督学习而变化),以及在半监督学习的情况下,我们是使用阈值还是 kbest(当不可能像朴素贝叶斯或 SVM 那样直接输出概率时)。此函数创建了一个性能字典(微平均 F1 和准确度),包括不同级别的标记和未标记音量,以及阈值或 kbest(如果使用)信息。这个字典被附加到整体性能数据帧中(就像早期 Github gists 中显示的 df_mlp_ng 和 df_sgd_ng )。
最后,让我们看看完整长度的比较输出,并认识到当在不同阈值下用自我训练考虑不同量的标记观察时,我们迄今为止已经发现了什么。

用新闻组数据集进行自我训练前后逻辑回归的准确性。作者图片
逻辑回归的上表可以解释如下:
我们从 5 种大小的标记数据集开始(869、1695 等等,直到 4178)。使用标记数据的每次迭代,我们首先训练监督分类器模型,并记录测试数据上的模型性能(阈值列= NaN 的精度数)。在同一次迭代中,我们继续添加 4,307 个未标记数据的样本。生成的伪标签被选择性地传递到监督分类器中,但是只有当它们超过预先确定的概率阈值时。在我们的实验中,我们使用 5 个等间距的阈值(0.4 到 0.8,步长为 0.1)。基于自训练的各个阈值的精度反映在每个标记体积迭代中。在大多数情况下,可以看出,使用未标记的数据会导致性能提高(但是,一般来说,随着标记量的增加,自训练以及一般来说其他半监督方法的提高也会饱和)。
下表提供了使用 MLP 的比较:

用新闻组数据集进行人工神经网络前后自训练的准确性。作者图片
下面为 IMDB 情感分类数据集生成了类似的表格。代码继续保存在同一个笔记本中,可以在这里访问。
使用半监督逻辑回归的 IMDB 评论情感分类:

自我训练前后逻辑回归的准确性(具有 2,000 个未标记观察值的 IMDB 数据)。作者图片

自我训练前后逻辑回归的准确性(具有 5,000 个未标记观察值的 IMDB 数据)。作者图片
使用半监督 MLP/神经网络的 IMDB 评论情感分类;

MLP 自我训练前后的准确性(具有 2000 个未标记观察值的 IMDB 数据)。作者图片

MLP 自我训练前后的准确性(具有 5000 个未标记观察值的 IMDB 数据)。作者图片
应该清楚地观察到的两个关键且一致的见解是:
- 自我训练,几乎总是,似乎提供了一个小而多样的,但在不同数量的标记数据明确的推动。
- 添加更多未标记的数据(2,000 对 5,000 个未标记的观察值)似乎可以持续提供增量性能改进。
跨上述数据集和算法的许多结果也强调了一些稍微奇怪的东西。虽然在涉及自我训练的大多数情况下有性能提升,但是有时在中低置信度伪标签(概率大约为 0.4 到 0.6)处添加标签以及高置信度标签会导致总体上更好的分类器。对于中高阈值,这是有意义的,因为使用非常高的阈值基本上意味着使用接近监督学习的东西,但低阈值的成功违背了正常的启发式和我们的直觉。在这种情况下,这可以归因于三个原因- (1)概率没有很好地校准,以及(2)数据集在未标记数据中具有更简单的线索,即使在较低的置信水平下,这些线索也会在初始伪标记集中被拾取(3)基础估计器本身不够强,并且需要在很大程度上被调整。自我训练应该在稍微调整的分类器之上应用。
所以,你有它!您的第一套自我训练管道可以利用未标记的数据和起始代码来生成一个简单的比较研究,了解有多少已标记的数据和伪标签的阈值可以显著提高模型性能。在这个练习中还有很多工作要做——(1)在将概率传递给自我训练之前校准概率,(2)使用不同折叠的标记和未标记数据集进行更鲁棒的比较。这有望成为自我训练的良好开端。
这篇文章中使用的所有代码都可以从 GitHub 获得。
利用 GANs 和回译潜入 SOTA
近年来,研究人员已经开始利用数据增强,通过使用 GANs 和反向翻译来改善半监督学习。我们希望更好地理解这些,并通过使用 CNN、RNNs 和 BERT ,将这些算法与神经网络和变压器上采用的自我训练进行比较。最近用于半监督学习的 SOTA 技术是mixt和用于一致性训练的无监督数据扩充,这也将是第 3 部分中涉及的主题。
感谢阅读,下期再见!
包括本文在内的 3 部分系列由 Abhinivesh Sreepada 和 Naveen Rathani 共同完成,Abhinivesh Sreepada 是一位热情的 NLP 数据科学家,拥有印度科学研究所(IISC)的硕士学位,nave en rat Hani 是一位经验丰富的机器学习专家和数据科学爱好者。
参考资料:
[1]https://sci kit-learn . org/stable/modules/generated/sk learn . linear _ model。SGDClassifier.html
https://ai.stanford.edu/~amaas/data/sentiment/
[5]http://KDD . ics . UCI . edu/databases/20 news groups/20 news groups . html
表格数据的转换(第二部分):线性数字嵌入
基于 FT-Transformer 的表格数据深度学习

尼克·希利尔在 Unsplash 上的照片
介绍
在上一篇关于 TabTransformer 的文章中,我描述了这个模型是如何工作的,以及如何应用到你的数据中。这篇文章将建立在它的基础上,所以如果你还没有阅读它,我强烈建议你从那里开始,然后再回到这篇文章。
在一些数据集上,TabTransformer 的性能优于传统的多层感知器(MLPs ),接近梯度提升树(GBTs)的性能。然而,该架构有一个明显的缺点——在构建上下文嵌入时,它没有考虑数字特征。这篇文章深入探讨了 Gorishniy 等人(2021)的论文,该论文通过引入 FT-Transformer(特征标记器+ Transformer) 解决了这个问题。
TabTransformer 与 FT-Transformer
两个模型都使用变压器( Vaswani 等人,2017 )作为其模型主干,但有两个主要区别:
- 数字嵌入的使用
- 使用 CLS 令牌进行输出
数字嵌入
传统的 TabTransformer 接受分类嵌入,并通过 Transformer 块将它们转换成上下文相关的嵌入。然后,数字特征与这些上下文嵌入连接,并通过 MLP 得到预测。

TabTransformer 图。图片作者。
大部分的魔法都发生在变形金刚内部,所以很遗憾数字特征被忽略了,只在模型的最后一层使用。Gorishniy 等人(2021)提出通过嵌入数字特征来解决这个问题。
FT-Transformer 使用的嵌入是线性的,这意味着每个特征在通过一个简单的完全连接的层后被转换为密集矢量。需要注意的是,这些密集图层不共享权重,因此每个数字要素有一个单独的嵌入图层。

线性数字嵌入。图片作者。
您可能会问自己—如果这些特征已经是数字了,为什么还要这样做?主要原因是数字嵌入可以与分类嵌入一起通过转换器块。这增加了更多可以学习的上下文,从而提高了表示质量。

数字嵌入变压器。图片作者。
有趣的是,已经证明(例如这里)这些数字嵌入的添加可以提高各种深度学习模型的性能(不仅仅是 TabTransformer),因此它们甚至可以应用于简单的 MLP。

数字嵌入的 MLP。图片作者。
CLS 代币
CLS 令牌的用法来自 NLP 领域,但它可以很好地转换成表格任务。基本思想是,在我们嵌入了我们的特征之后,我们向它们附加另一个“嵌入”,它代表一个 CLS 令牌。通过这种方式,分类、数字和 CLS 嵌入通过转换程序块得到语境化。之后,上下文化的 CLS 令牌嵌入充当简单 MLP 分类器的输入,该分类器产生期望的输出。
傅立叶变换变压器
通过用数字嵌入和 CLS 令牌扩充 TabTransformer,我们得到了最终提出的架构。

FT-变压器。图片作者。
报告的结果

FT-Transformer 的报告结果。来源:戈里什尼等人(2021 年)
从结果中我们可以看出,FT-Transformer 在各种数据集上都优于梯度提升模型。此外,它优于 ResNet,ResNet 是一个强大的表格数据深度学习基线。有趣的是,超参数调整并没有改变 FT-Transformer 结果太多,这可能表明它对超参数不是那么敏感。
验证结果
本节将向您展示如何通过验证成人收入数据集的结果来使用 FT-Transformer。我将使用一个名为tabtransformertf的包,它可以使用pip install tabtransformertf来安装。它允许我们使用表格变压器模型,而无需大量的预处理。下面您可以看到分析的主要步骤和结果,但请务必查看补充笔记本了解更多详情。
数据预处理
数据可以从这里下载或者使用一些 API。数据预处理步骤与本文无关,因此您可以在 GitHub 上找到完整的工作示例。特定于 FT-Transformer 的预处理类似于 TabTransformer ,因为我们需要创建分类预处理层并将数据转换成 TF 数据集。
FT-变压器初始化
模型的初始化相对简单,每个参数都有注释。三个 FT-变压器特定参数是— numerical_embeddings、numerical_embedding_type和explainable
numerical_embeddings—类似于category_lookup,这些是预处理层。对于 FT-Transformer 来说是None,因为我们没有对数字特征进行预处理。numerical_embedding_type—linear用于线性嵌入。更多类型将在下一篇文章中介绍。explainable-如果设置为True,模型将输出每行的特征重要性。它们是从注意力权重中推断出来的。
模特培训
培训程序类似于任何 Keras 模型。唯一需要注意的是,如果您已经将explainable指定为True,那么您需要两个损失和指标,而不是一个。
训练需要大约 70 个时期,下面你可以看到损失和度量值的进展。您可以减少提前停止的回合数,或者进一步简化模型(例如,减少注意力头数),以加快训练速度。

培训/验证损失和指标。作者的情节。
估价
测试数据集使用 ROC AUC 和 PR AUC 进行评估,因为这是一个不平衡的二元分类问题。为了验证报告的结果,我还包括了假设阈值为 0.5 的准确性指标。
得到的准确度分数为 0.8576,略低于报告的分数 0.86。这种差异可能是由于训练过程中的随机变化或不同的超参数造成的。尽管如此,结果与报道的足够接近,,所以这是一个好迹象,表明这项研究是可重复的。
可解释性
FT-Transformer 最大的优点之一是内置的可解释性。由于所有的特征都通过一个转换器,我们可以得到它们的注意力图,并推断出特征的重要性。这些重要性是使用以下公式计算的

特征重要性公式。来源:戈里什尼等人(2021 年)
其中 p_ihl 是从第I个样本的第 l 层向前传递的【CLS】令牌的第 h 个头部注意图。该公式基本上总结了跨越不同注意头(heads参数)和变压器层(depth参数)的【CLS】令牌的所有注意分数,然后将它们除以heads x depth。局部重要性( p_i )可以跨所有行进行平均,以获得全局重要性( p )。
现在,让我们看看成人收入数据集的重要性。
从上面的代码中,您可以看到模型已经输出了我们需要的大部分信息。对其进行处理和绘图会得到以下结果。

特征重要性。由作者策划。
前五个特征确实有道理,因为收入较高的人往往年龄较大,已婚,受教育程度较高。我们也可以通过查看最大预测和最小预测的重要性来检测局部重要性。


前 3 名贡献。由作者创建。
同样,重要性有直观的意义。最大概率赚 50K 以上的人,资本收益大,受教育 15 年,年龄大。可能性最低的人只有 18 岁,完成了 10 年的教育,每周工作 15 个小时。
结论
在这篇文章中,你看到了 FT-Transformer 是什么,它与 TabTransformer 有何不同,以及如何使用tabtransformertf包来训练它。
总的来说,FT-Transformer 是深度表格学习领域的一个有前途的补充。与 TabTransformer 相比,该模型不仅嵌入了分类特征,还嵌入了数字特征,因此能够显著提高其性能,并进一步缩小深度模型与 XGBoost 等梯度增强模型之间的差距。此外,该模型是可解释的,这有利于许多领域。
我的下一篇文章将讨论不同的数字嵌入类型(不仅仅是线性的),这将进一步提高性能。敬请期待!
文献学
- 成人收入数据集(知识共享署名 4.0 国际许可(CC BY 4.0)) — Dua,d .和 Graff,C. (2019)。UCI 机器学习知识库[http://archive . ics . UCI . edu/ml]。加州欧文:加州大学信息与计算机科学学院。
- Yury Gorishniy 等人,2021 年,重新审视表格数据的深度学习模型
- 瓦斯瓦尼等人,2017 年,【https://arxiv.org/abs/2106.11959
用专利文本改进命名实体识别(NER)模型| SpaCy、Prodigy 和一点神奇的🪄
命名实体识别(NER)——是信息提取的一个子任务,寻求定位 命名实体 …并将其分类到预定义的类别中,如人名、组织、地点、医疗代码、时间表达式、数量、货币值、百分比等。
… NER 也简称为实体识别、实体分块和实体提取。
简而言之,我们识别文本中有意义的实体,并根据类别对它们进行分类。
为什么有用?
它在许多方面都有帮助:
- HR (通过汇总简历加快招聘过程)
- 医疗保健(从实验室报告中提取重要信息)
- 推荐引擎【Booking.com】查看这个故事!)
如何改进?
拥有最受欢迎的实体类别的数据集已经存在,比如组织或品牌。但是定制数据集呢?通常,人们手动注释数据集,这可能是昂贵的,时间长,并且质量仍然取决于初始数据。
在这篇文章中,我提出了另一种方法。我使用已经分类的文本,其中充满了相关术语,来训练特定领域的 NER 管道。
专利数据集

专利,准确地说是专利的文本,是提供专利发明高度具体信息的文件。换句话说,它根据其领域中的功能来描述发明。这意味着专利将包含特定于该领域的术语,文本的简洁有助于数据检索的方便。
例如,其中一个来源是谷歌专利,它提供对国际专利分类数据库的访问:
但更重要的是版权免费。根据 USPTO
除了 37 CFR 1.71(d) & (e)和 1.84(s)中反映的有限例外情况外,专利的文本和图纸通常不受版权限制。
这意味着我们可以自由地使用这些文本来训练我们的模型,而不用担心版权问题,这对构建商业系统至关重要。
对于实验,我选择了专利的【G06K】(数据识别/数据呈现) 小节,而G06——是计算/计算小节。这样的文本应该有助于训练对特定于数据分析领域的技术实体的识别,例如计算机视觉、信号处理等。
提取术语
由于专利文本是特定于其领域的,我们需要从中提取每个频繁命名的实体。为此,我们可以使用,例如, Wikicorpus 。在项目的存储库中,您会发现已经策划好的名为manyterms.lower.txt的列表。
要从文本中提取相关术语,我们可以使用 scikit 中的CountVectorizer-learn。通过这种方式,我们可以去除比某个阈值频率低的术语,并分别留下具有更多提及和更多句子的术语来训练。
利用计数向量机从专利文本中抽取实体
模特培训
提取潜在术语后,我们可以开始训练模型。这里我使用了 SpaCy 库,因为准备 NER 管道很简单,而且有全面的文档。但在此之前,我们需要以适当的格式制作数据集。
这里我使用库的PhraseMatcher类从预定义的维基列表中查找实体。
然后,用自定义标签TECH和标注每个实体,并以 SpacCy 格式保存。
当训练和评估时。布景准备好了,我们可以开始训练模特了。由于 SpaCy 是为生产量身定制的,所以配置非常广泛。然而,对于一个简单的例子,基本的就足够了。为此,只需进入网站,选择型号并下载base-config.cfg。

我们使用的配置选项|截图来自 spacy.io
之后,使用以下命令初始化完整配置:

配置初始化命令

作者图片|配置初始化后的输出
在这个命令之后,spacy 填充所有其他配置参数,并为训练做准备。现在我们可以运行一个命令来训练:

带参数的训练命令

图片作者|培训期间输出
瞧,我们的 NER 模特训练好了!现在我们可以看到结果了。
重要注意事项!经过训练的 NER 模型将不仅从预先标记的训练数据中学习标记实体。*它将学习根据给定的上下文查找和识别实体。*
只需加载model-best并运行所需的文本输入来运行测试。SpaCy 提供了一个很好的可视化 NER 标签,在 Jupyter 笔记本内支持。
对文本样本运行模型推理的代码

图片由作者|以上代码输出
额外收获:主动学习,天赋异禀
⚠️ Prodigy 是业内广泛使用的付费脚本注释工具,由 SpaCy 团队开发并集成到他们的 NLP 库中。这不是创建 NER 模型的关键部分,但对一些人来说还是有帮助的。⚠️
完美是无止境的,因此我们可以用 Prodigy 进一步改进我们的 NER 模型。它的很酷的一个特点就是主动学习。简而言之,这意味着我们可以根据手动标记的数据来调整模型。然而,由于我们的模型已经训练好了,我们只需要一堆带注释的例子,而不是整个数据集。
如何选择例子?这就是为什么神童是有用的。自动给出模型置信度最低的测试集中的例子。

图片由作者提供|使用 Prodigy 运行主动学习的命令
该命令将打开注释窗口。在这里,我们可以检查标签并修复它们。

图片作者| Prodigy 注释窗口
这些注释会自动保存到 prodigy 数据集。在完成对几个例子的注释之后,我们现在可以对模型进行微调了。由于与 SpaCy 的集成,我们可以直接从 prodigy 运行我们的模型的训练。

使用 prodigy 注释数据运行模型调整的命令
结果
就是这样!我们训练了 NER 模型,它现在可以识别和标记特定领域的实体。利用专利数据集可以快速准备数据,而无需人工管理。
完整的代码和解析的数据可以在这里找到:
*https://github.com/kinivi/patent_ner_linking
附言
NER 是创建实体链接的关键步骤,它允许在被认可的实体之间创建层级和连接。在这种情况下,模型验证同样复杂。但这将在我的下一篇文章中描述😉*
NSVQ:用于神经网络训练的改进矢量量化技术
用于机器学习优化的高效矢量量化。向量量化变分自动编码器),优于直通估计器

vackground.com在 Unsplash 上拍照
矢量量化(Vector quantization,VQ)是一种类似 k-means 聚类算法的数据压缩技术,它通过一些有代表性的称为码本的矢量来模拟数据的概率密度函数。矢量量化最近被广泛用于基于机器学习的应用中,尤其是那些基于矢量量化变分自动编码器(VQ-VAE)的应用;例如图像生成[1]、语音编码[2]、语音转换[3]、音乐生成[4]和文本到语音合成[5]。
根据下图,矢量量化是将所有数据点(在 voronoi-cell 中)映射到其最近的码本点的操作。换句话说,它使用下面的公式从一组码本向量(c_k)s 中找到最接近输入数据向量(x)的码本向量(c_k),并且通过用最接近的码本(c_k)替换 x 的值来将 x 映射(量化)到 x_hat。
矢量量化公式

矢量量化运算(图片来自此处)
显然,矢量量化不能直接用于基于机器学习的优化,因为没有为它定义真正的梯度(对于上述方程)。因此,我们需要应用一些技巧来估计 VQ 函数的梯度,并使其通过反向传播来传递这些梯度并更新码本向量。
VQ-VAE 在 Oord 等人的论文神经离散表示学习中首次提出。他们应用 VQ 来模拟变分自动编码器的潜在空间的离散表示。换句话说,他们将 VQ 应用于编码器的输出,并为此找到最佳嵌入(码本),然后将这些嵌入传递给解码器。为了在反向传播(反向传递)中传递 VQ 函数的梯度,本文和上述所有文章都使用直通估计器(STE) [7],它只是简单地复制 VQ 模块上的梯度,如下图所示。

直通估计器(图片来自此处
然而,直通估计器(STE)没有考虑量化的影响,并导致梯度和矢量量化的真实行为之间的不匹配。此外,对于使用 STE 的方法,有必要在全局损失函数中增加一个额外的损失项,以使 VQ 码本(嵌入)得到更新。因此,附加损失项的加权系数是一个新的超参数,需要手动调整。奥尔德等人[6]引入的 VQ-VAE 损失函数如下所示。公式中的第二项和第三项是 VQ 模块所需的损失项,加权系数为α和β。关于损失函数的更多细节,请参见论文。
奥尔德等人[6]在 VQ-VAE 论文中使用的损失函数
在这篇文章中,我们想介绍我们最近提出的基于机器学习方法的矢量量化技术,它以“ NSVQ:机器学习矢量量化中的噪声替换【8】的标题发表。NSVQ 是一种技术,其中通过将噪声添加到输入向量来模拟向量量化误差,使得模拟的噪声将获得原始 VQ 误差分布的形状。NSVQ 的框图如下所示。

NSVQ 框图:矢量量化中的噪声替代(图片由作者提供)
我们在三个不同的实验场景中测试我们的 NSVQ 方法,
- 语音编码:对语音信号的频谱包络进行建模(在【9】中提出的语音编码方法)
- 图像压缩:对[6]中提出的 VQ-VAE 的潜在表示进行建模
- 玩具示例:对一些众所周知的数据分布进行建模。
基于我们的论文【8】中的结果(在下面的图中也提供了这些结果),已经表明 NSVQ 不仅可以通过后向通道中的梯度,而且可以为码本提供比 STE 更精确的梯度。此外,与 STE 相比,NSVQ 导致更高的量化精度和更快的收敛。此外,在多次运行单个实验时,NSVQ 的性能更具确定性(表现出更小的性能差异)。使用 NSVQ 的一大好处是,它不需要在全局损失函数中添加任何额外的损失项。因此,它不会导致任何额外的超参数调整(STE 额外损失项的系数)。
在语音编码场景中,我们使用语音质量感知评估(PESQ)和感知加权信噪比(pSNR)作为客观度量来评估 STE 和我们提出的 NSVQ 方法的解码语音质量。下图显示了结果。

在语音编码场景中,在 8、9.6、13.2、16.4、24.4 和 32 kbit/s 的总比特率下,针对 12 比特 VQ,在 PESQ 和 pSNR 度量方面,所提出的 NSVQ 和 STE 的性能;实线指的是 PESQ 和 pSNR 的平均值,对应的填充区域指的是它们的 95%分位数。(图片由作者提供)
在图像压缩场景中,我们采用结构相似性指数度量(SSIM)和峰值信噪比(峰值 SNR)作为客观度量来评估 VQ-VAE 解码器端的重建图像的质量。下图展示了结果。

在图像压缩场景中,在 15 k 训练更新和超过 20 次单独实验之后,STE 的 SSIM 和峰值 SNR 值以及针对不同 VQ 比特率提出的 NSVQ 实线指的是 SSIM 和峰值 SNR 的平均值,相应的填充区域指的是它们的 95%分位数。(图片由作者提供)
根据上图,当增加 VQ 比特率时,所提出的 NSVQ 获得严格上升的 SSIM 和峰值 SNR 值,这证实了它的行为与比特率的增加一致。这种行为是我们对真正的 VQ 的期望。然而,STE 方法不遵循相同的行为。我们认为原因是 STE 没有考虑 VQ 函数在反向传播中的影响。
在图像压缩场景中,除了客观评价之外,我们还研究了另外两件事;训练收敛速度和码本向量混乱度(平均码本使用率)。这是结果。

在图像压缩场景中,在 15 k 训练更新和超过 20 次单独实验之后,STE 的平滑训练误差(MSE)和平滑平均码本使用(困惑)以及针对 11 比特 VQ 提出的 NSVQ 实线指的是 MSE 和困惑度的平均值,对应的填充区域指的是它们的 95%分位数。(图片由作者提供)
在本文的第三个场景中,我们使用我们提出的 NSVQ、STE 和 MiniBatchKmeans(来自 scikit-learn 库)方法对几个著名的机器学习数据分布进行建模。这里,我们仅示出了使用 8 比特(2⁸=256 码本)在 2D 空间中对瑞士滚分布应用的矢量量化。我们从数据分布中初始化码本向量的位置,以使 VQ 更具挑战性。下图显示了 NSVQ、STE 和 MiniBatchKmeans 方法的最终优化码本位置。

使用 NSVQ 方法的 8 位矢量量化的最终优化码本(图片由作者提供)

使用 STE 方法的 8 位矢量量化的最终优化码本(图片由作者提供)

使用 MiniBatchKmeans 方法的 8 位矢量量化的最终优化码本(图片由作者提供)
根据这些图,NSVQ 捕获瑞士卷分布形状比 STE 好得多,并且具有少得多的死码本数量(从数据分布中选择的并且对解码阶段无用的码本)。因此,可以得出结论,与 STE 相比,NSVQ 对码本的初始化不太敏感。请注意,NSVQ 的这种行为部分是由于我们论文中提到的码本替换功能。
此外,NSVQ 以比 MiniBatchKmeans 方法更统一和同类的方式定位码书。查看上图中黑框中的密集码本分布,它显示了 MiniBatchKmeans 方法的码本位置。这种现象在 NSVQ 方法中没有发生(注意,数据分布的性质在黑盒区域中并不密集)。对于所有这三种方法,我们已经计算了原始数据分布与其矢量量化版本之间的均方误差(MSE)。根据我们在论文【8】中发表的 MSE 值,NSVQ 用比 STE 和 MiniBatchKmeans 方法更低的 MSE 值量化数据。
最后,我必须提到,NSVQ 技术适用于训练任何机器学习应用程序,这需要在计算图中的某个位置进行矢量量化。然而,请注意 NSVQ 仅应用于训练阶段以便学习矢量量化码本。但是,它不用于推理阶段,因为我们不需要在反向传播中传递任何梯度。因此,在推理阶段,使用上面提到的原始矢量量化公式会更好、更准确。
我们在以下公开网页中提供 NSVQ 的 PyTorch 代码:
https://gitlab.com/speech-interaction-technology-aalto-university/nsvq
确认
特别感谢我的博士生导师Tom bck strm教授,他支持我,也是这项工作的另一位贡献者。
参考
A. Razavi,A. van den Oord 和 O. Vinyals,“用 VQ-VAE-2 号生成多样的高保真图像”,在 Proc。第 33 届 Int。糖膏剂神经感染。过程。系统。,2019 年第 32 卷,第 14866–14876 页。
【2】c . Garbacea,A. V. den Oord,Y. Li,F. S. C. Lim,A. Luebs,O. Vinyals 和 T. C. Walters, Proc .IEEE Int。糖膏剂声音。,语音信号处理。(ICASSP) ,2019 年 5 月,第 735–739 页。
【3】s . Ding 和 R. Gutierrez-Osuna,“非并行语音转换中矢量量化变分自动编码器的分组潜在嵌入”,Inter-speech论文集,2019,第 724–728 页。
【4】p . Dhariwal,H. Jun,C. Payne,J. W. Kim,a .拉德福德,I. Sutskever,《点唱机:音乐的生成模型》, arXiv 预印本 arXiv:2005.00341 ,2020 年。
a . TJ andra,B. Sisman,M. Zhang,S. Sakti,H. Li,S. Nakamura,“用于 Zerospeech challenge 2019 的 VQVAE 无监督单元发现和多尺度 code2spec 反相器”, arXiv 预印本 arXiv:1905.11449 ,2019。
【6】**a . van den Oord,O. Vinyals,K. Kavukcuoglu,“神经离散表征学习”,载于第 31 届国际神经信息处理系统会议论文集,2017 年,第 6309–6318 页。
****【7】y . beng io,n . Le \u onard,a .库维尔,“通过随机神经元估计或传播梯度用于条件计算”, arXiv 预印本 arXiv:1308.3432 ,2013 年。
****【8】m . h .瓦利和 t . bck strm,“NSVQ:用于机器学习的矢量量化中的噪声替换”, IEEE Access ,第 10 卷,第 13 598–13 610 页,2022 年。
m . h .瓦利和 t .贝克斯特罗姆,“语音和音频编码频谱包络的端到端优化多级矢量量化”,载于 Pr oc。Interspeech,【2021 年 8 月,第 3355–3359 页。
用 DataWig 对表中缺失数据的插补
原文:https://towardsdatascience.com/imputation-of-missing-data-in-tables-with-datawig-2d7ab327ece2
用 Python 实现 Amazon 的 DataWig 来估算表格数据中的缺失值

亨特·哈里特在 Unsplash 上的照片
现实世界数据集中的缺失值是一种常见现象,给所有数据从业者带来了重大挑战。当数据集包含异构数据类型时,这个问题甚至更具挑战性。
在本文中,我们将探讨 DataWig 如何帮助我们有效且高效地对表格数据中的缺失值进行插补。
内容
(1) 缺失数据的类型和插补技巧(可选)(2)关于 Data wig(3)Data wig 如何工作
(1)缺失数据的类型和插补技术
(可选底漆)
在我们开始之前,最好了解缺失数据的类型和各种可用的插补技术。为了保持这篇文章的简短,我已经将底漆放在一篇单独的文章中。如果您已经熟悉这些概念,可以跳过这一部分。
(2)关于 DataWig
data wig由 Amazon Science 开发,是一个软件包,它将缺失值插补应用于包含异构数据类型的表格,即数字、分类和非结构化文本。
目标是建立一个健壮的、可扩展的框架,允许用户在没有大量工程工作或机器学习背景的情况下估算缺失值。
(DataWig 的工作原理
DataWig 运行三个组件对异构数据进行插补:编码、特征器和插补器。
我们可以通过一个包含非数字数据的例子来了解 DataWig 是如何工作的。假设我们有一个 3 行的产品目录数据集,其中第三行的' Color '列缺少一个值。
因此,' 颜色 '列是待估算列(又名输出列),而其他列是输入列。
目的是使用前两行(包含完整数据)来训练插补模型,并预测第三行中缺失的' 、颜色 '值。

改编自 DataWig JMLR 论文 |图片在 CC-BY 4.0 下使用
- 首先使用试探法自动确定列的数据类型。例如,如果一个列的行数至少是唯一值的十倍,则该列被定义为分类而不是纯文本。
- 使用列编码器将特征转换成数字表示,例如,一键编码。
- 数字格式的列被转换成特征向量。
- 特征向量被连接成潜在的表示,以被解析成用于训练和预测的插补模型。
让我们来探索这三个组件:
(一)编码器
column encoder类将原始数据转换成数字表示。不同的数据类型有不同类型的编码器,例如:
- sequential encoder—字符串符号(如字符)的序列
- BowEncoder —将字符串表示为稀疏向量的单词包表示
- 分类编码器 —用于分类变量(一键编码)
- 数字编码器 —用于数值(数值归一化)
㈡特色化
在编码成数字表示之后,下一步是使用特征化器将数据转换成特征向量。
目的是将数据作为矢量表示输入插补模型的计算图,用于训练和预测。
还有不同类型的特征来迎合不同的数据类型:
- lstmfeaturer—使用 LSTM 将输入序列映射成潜在向量
- bow featurezer—将字符串数据转换成稀疏向量
- 嵌入分类器 —将编码的分类数据映射成矢量表示(即嵌入)
- 数字分类器 —使用完全连接的层提取特征向量
㈢估算者
最后一部分是创建插补** 模型,执行训练,并生成预测以填充缺失值。**
DataWig 采用 MICE 技术进行插补,内部使用的模型是用 MXNet 在后端训练的神经网络。
简而言之,深度学习模型使用包含有用信息的列来估算待估算列中的缺失值。
假设将有不同的数据类型,适当的损失函数(例如,平方损失或交叉熵损失)也被自动选择。
(DataWig 的插补性能
亚马逊科学团队对 DataWig 进行了评估,将它与五种输入缺失数值的流行技术进行了比较。
这些其他插补技术包括均值插补、kNN、矩阵分解(MF)和迭代插补(线性回归和随机森林)。这种比较是在具有不同数量的缺失数据和缺失类型的合成数据和真实数据之间进行的。

改编自 DataWig JMLR 论文 |图片在 CC-BY 4.0 下使用
根据归一化均方误差,DataWig 优于其他方法,即使在困难的 MNAR 遗漏类型中。结果显示在上面的图中。
评估的更多细节(包括非结构化文本)可在研究论文中找到。
作者的想法:鉴于 DataWig 在处理分类和文本特征方面据称的优势,我很惊讶研究论文的评估重点是缺失的数值。
(5) Python 的实现
为了展示 DataWig 的工作原理,我们将使用 心脏病数据集 ,因为它包含数字和分类数据类型。
注:你可以在这里 找到这个项目的 GitHub 回购 和完整的 Jupyter 笔记本演示 这里 。
特别是,作为演示的一部分,我们将进行两次插补:
- ****数值插补:在数值
**MaxHR**栏填写缺失值(人达到的最大心率) - ****分类插补:在分类
**ChestPain**栏填写缺失值(遇到的胸痛类型)
步骤 1 —初始设置
- 用 Python 版本 3.7 创建并激活一个新的 conda 环境。原因是 DataWig 目前支持 3.7 及以下版本。
conda create -n myenv python=3.7
conda activate myenv
- 通过 pip 安装 DataWig
pip install datawig
- 如果您希望环境出现在您的 Jupyter 笔记本中,您可以运行以下命令:
python -m ipykernel install --user --name myenv --display-name "myenv"
注意:确保 pandas、NumPy 和 scikit-learn 库更新到最新版本。
步骤 2 —数据预处理
插补前有两个预处理步骤要做:
- 执行随机洗牌训练-测试分割(80/20)
- 随机隐藏测试数据集中任意比例(如 25%)的值,以模拟缺失数据。对于插补模型的训练,训练集将保持完全不缺失。
下面是一个测试集的例子,缺失的数据显示为NaN:

测试集样本|作者图片
步骤 3 —设置插补模型
构建和部署插补模型的最简单方法是使用SimpleImputer类。它会自动检测列数据类型,并使用一组默认的编码器和特征,在各种数据集上产生良好的结果。
我们首先定义一个输入列的列表,这些输入列对于预测待估算列中的缺失值非常有用。该列表基于用户的领域知识和批判性判断。****
然后我们创建两个SimpleImputer的实例,每个实例对应两个要估算的列(即**MaxHR** 和**ChestPain**)
步骤 4 —拟合插补模型
准备好模型实例后,我们可以将它们放入我们的训练数据集。除了简单的模型拟合,我们还可以利用SimpleImputer的超参数优化(HPO) fit_hpo功能找到最佳插补模型。
HPO 函数在超参数(例如,学习率、批量大小、隐藏层数)的定制网格上使用随机搜索。
如果不需要 HPO,我们可以省略超参数搜索参数(如分类插补示例所示)
步骤 5-执行插补并生成预测
下一步是通过对有缺失值的测试集运行经过训练的插补模型来生成预测。
输出是原始数据帧加上估算数据的新列。

输出预测数据框(红色框中的原始列和估算列)|作者图片
第 6 步—评估
最后,让我们看看我们的插补模型如何处理这些评估指标:
- 数值插补的均方误差(MSE)
- 分类插补的马修相关系数( MCC )
本次演示中,均方误差为 342.4,MCC 为 0.22 。这些值构成了与其他插补技术进行比较的基准。
(6)高级功能
除了前面描述的基本实现,我们还可以利用高级 DataWig 特性来满足我们特定的项目需求。
㈠估算者
如果我们希望对模型类型和插补模型中的预处理步骤有更多的控制,我们可以使用Imputer类。
与SimpleImputer中的默认设置相比,它为模型参数的定制规范(如特定的编码器和特征)提供了更大的灵活性。
下面是一个例子,说明如何在Imputer中明确定义每一列的编码器和特征:
然后可以使用Imputer实例来执行.fit()和.predict()。
作者的想法:列类型的具体定义可能是有帮助的,因为自动编码和特征化可能并不总是完美地工作。例如,在该数据集中, *SimpleImputer* 将分类 *Thal* 列误识别为文本列。
(ii)标签移位检测和校正
SimpleImputer类有一个方便的函数check_for_label_shift,帮助我们检测数据漂移的问题(特别是标签移位)。
当训练数据和真实数据之间的边际分布不同时,就会发生标签偏移。通过理解标签分布是如何变化的,我们可以解释插补的变化。
check_for_label_shift函数记录偏移的严重程度,并返回标签的权重因子。下面是权重的输出示例:

标签移位检查|作者图片的输出
然后,当我们重新拟合插补模型以纠正偏移时,我们通过传递权重来重新训练具有加权似然性的模型。
包装它
我们已经介绍了如何使用 DataWig 有效地估算数据表中的缺失值。
一个重要的警告是,像 DataWig 这样的插补工具不是处理缺失数据的灵丹妙药。
处理缺失数据是一个具有挑战性的过程,需要适当的调查以及对数据和背景的深刻理解。本演示中展示了一个清晰的示例,其中用户需要决定将哪些输入要素输入到模型中,以准确估算输出列。
这个项目的 GitHub repo 可以在 这里 找到。
在你走之前
欢迎您加入我的数据科学学习之旅!点击此媒体页面,查看我的 GitHub ,了解更多精彩的数据科学内容。同时,享受用 DataWig 输入缺失值的乐趣吧!
** **
现代数据堆栈上的插补
原文:https://towardsdatascience.com/imputation-on-the-modern-data-stack-6c0e37e20e7a
规模估算

由于大多数机器学习算法不处理空值,缺失值插补是特征工程管道的标准部分。这种插补有多种技术,从非常简单到创建复杂的模型来推断值应该是多少。
最简单的方法是为缺失值估算一个值,最常见的是要素的平均值或中值。对于基于树的方法,输入极值(大于要素的最大值或小于最小值)也是可行的。通常,数据科学家认为这些简单的技术不足以替代丢失的值。在这些情况下,可以创建一个模型,根据该观察的其他特征来估算值。这可以从相对简单的(kNN 模型)到与原始机器学习工作范围相似的复杂模型。虽然这种基于模型的方法可能很有吸引力,但现代机器学习算法的强大功能意味着,输入单个值并创建一个缺失指标也可以轻松完成。在这个博客中,我们将估算平均值,但是对代码的简单修改将允许我们估算其他值。
Python 中的插补
在 Python 中,有两种常用的方法来执行这种插补。首先,这个插补可以用熊猫来做。
df['varname'] = df['varname'].fillna(df['varname'].mean())
为了创建缺失的指标,需要首先创建它
df['varname_mi'] = df['varname'].isna()
df['varname'] = df['varname'].fillna(df['varname'].mean())
通常,这在 Scikit-learn 中作为特性工程管道的一部分来完成。使用train_test_split创建训练和测试分区,可以创建一个SimpleImputer来替换丢失的值。
from sklearn.impute import SimpleImputer
from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.2, random_state=1066)imp = SimpleImputer(missing_values=np.NaN, strategy='mean')
X_train = imp.fit_transform(X_train)
X_test = imp.transform(X_test)
与 pandas 不同,SimpleImputer可以通过改变调用来同时创建一个缺失值指示器,包括将参数 add_indicator 设置为 True。
imp = SimpleImputer(missing_values=np.NaN,
strategy='mean',
add_indicator=True)
挑战
在这两种情况下,这些方法都适用于可在单个工作站上管理的数据集。在数据集包含数百千兆字节或万亿字节数据的问题中,这种方法不再有效。虽然 Dask 之类的包可以让您处理这些大规模数据集,但您仍然需要将数据移动到您的机器或集群中。对于这些大小的数据集,移动数据的时间可能会很长。即使数据大小易于管理,当最终模型投入生产时,这些基于 Python 的方法仍会造成问题,因为这些插补需要在生产代码中重复。当生产环境不是 Python 或者不支持加载 Python 对象时,这可能是一个挑战。
利用云数据仓库的力量进行插补
使用 RasgoQL 可以缓解这两个问题。RasgoQL 在数据仓库中执行插补,并允许您保存所有特征工程步骤,以便在预测时可以在仓库中重新运行。
要使用 RasgoQL 的转换,首先用RasgoQL连接到系统。
import rasgoql
creds = rasgoql.SnowflakeCredentials.from_env()
rql = rasgoql.connect(creds)
并用rql.dataset(fqtn=<Fully Qualified Table Name>)得到需要对其进行缺失值插补的数据集(数据仓库中的一个表或视图)。所有支持的转换都可以在这里找到或者以编程方式列出:
transforms = rql.list_transforms()
**print(transforms)**
若要转换数据,请对数据集调用转换名称。
newset = dataset.impute(imputations={'VARIABLE_1': 'mean',
'VARIABLE_2': 'mean'},
flag_missing_vals=False)
请注意,插补转换采用需要插补的字段字典,并允许不同的插补方法:平均值、中值、众数或单个值。此外,将 flag_missing_vals 设置为 True 将导致为插补字典中的每个字段创建缺失指标特征。
转换链接
通过将多个调用附加到转换函数上,或者通过在前一个转换的输出上运行一个转换函数,可以将多个转换相互链接起来。这允许您从相对简单的转换构建更复杂的转换,并通过一个 SQL 调用创建最终的数据集。转换链的结果可以被视为一个数据帧,它包含:
df_transform = newset.preview()
print(df_transform)
默认情况下,preview返回十行。要将整个数据作为数据帧下载,调用to_df。
由于这些转换是在云数据仓库上运行的,所以它运行的是 SQL 代码。转换链的 SQL 语句可以通过运行
print(newset.sql())
虽然这段代码创建了转换链,但是它还没有执行,结果保存在仓库中。为了保存它,需要通过调用链上的save来创建一个新表。
newset.save(table_name='<NEW TABLE NAME>',
table_type='view')
该数据被保存为视图。为了将其保存为表格,请将 table_type 改为‘表格’。
此时,Rasgo 已经针对数据仓库中存储的全部数据运行了缺失值插补,并将结果作为任何 Python 环境中的数据帧返回,并且可以使用标准工具用于建模或可视化。此外,如果这被保存为视图,当新数据被添加到数据仓库中的基础表时,不需要重新运行数据准备阶段,因为估算数据会自动运行并可供下载。
这种准备是自动运行的,这影响了将数据准备管道投入生产的能力。无需打包 python 代码或将其重写为生产系统中使用的语言,数据会自动在仓库中保持最新,相关数据行可通过已创建的插补提取。
现在,不用浪费几周或几个月的时间重写(或等待软件工程重写)和测试代码的输出,数据立即可用。
最后,由于这种插补在数据仓库中立即可用,这意味着其他数据科学家和分析师不再需要提取原始数据并准备供自己使用。相反,最终转换后的数据可以直接从仓库中提取,并用于建模、可视化或报告。
为了了解这些工具的强大功能,我们创建了一个数据集,其中包括几个关键字段中缺失的数据,并上传到我们的数据仓库。在创建和不创建缺失指标的情况下,对缺失字段的平均值进行估算。最后,为两种情况训练一个 CatBoost 分类器,并计算对数损失和 AUC。
y = transformed_df[target]
X = transformed_df.drop(columns=[target])
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=.2,
random_state=1066)categorical_features = X_train.select_dtypes(exclude=[np.number])train_pool = Pool(X_train, y_train, categorical_features)
test_pool = Pool(X_test, y_test, categorical_features)model = CatBoostClassifier(iterations=1000,
max_depth=5,
learning_rate=0.05,
random_seed=1066,
logging_level='Silent',
loss_function='Logloss')model.fit(X_train,
y_train,
eval_set=test_pool,
cat_features=categorical_features,
use_best_model=True,
early_stopping_rounds=50)y_predict = model.predict_proba(X_test)logloss = log_loss(y_test, y_predict[:, 1])
auc = roc_auc_score(y_test, y_predict[:, 1])print(logloss, auc)
这给出了最终结果
- 无缺失指标:AUC: 0.6881,对数损失:0.2574
- 缺失指标:AUC: 0.7045,对数损失 0.2563
在这种情况下,使用 CatBoost,缺少指标的模型比没有指标的模型略胜一筹
如果您想查看 RasgoQL,可以在这里找到文档,在这里找到资源库。
用简单和先进的技术输入缺失数据
均值、众数、时间序列、KNN 和 MICE 插补教程

缺失数据当数据集中没有存储感兴趣变量的数据时发生。根据其数量,丢失的数据可能会损害任何数据分析的结果或机器学习模型的稳健性。
在使用 Python 处理缺失数据时,Pandas 的dropna()函数派上了用场。我们用它来删除包含空值的行和列。它还有几个参数,如轴定义是否删除行或列,如何确定是否在任一或所有行/列中出现缺失值,以及子集选择一组要应用 drop 函数的列或标签。
df.dropna(axis=0, how='any', subset=None, inplace=False)
然而,还有其他可能 更好的处理缺失数据的方法。在这篇文章中,我们将看到如何用简单和先进的技术来估算(替换)丢失的数据。我们将首先介绍简单的单变量技术,如均值和众数插补。然后,我们将看到时间序列数据的向前和向后填充,我们将探索用于填充缺失值的线性、多项式或二次插值。稍后,我们将探索先进的多变量技术,并学习如何使用 KNN 和小鼠的机器学习来估算缺失值。
在阅读本文的同时,我鼓励您查看我的 GitHub 上的 Jupyter 笔记本以获得完整的分析和代码。
抓紧,让我们开始吧!🦾
数据
在本文中,我们将使用来自 OpenMV.net的旅行时间数据集。使用下面的代码,我们将通过解析 Date 和 StartTime 列并将随机值转换为 MaxSpeed 列的 10%上的缺失值来加载数据。
让我们打印数据的前 5 行:

数据的前五行
检测缺失值
我们可以使用isna()或isnull()方法来检测数据中的缺失值。我们可以用sum()得到每一列中缺失值的总数,或者用mean().取平均值
df.isnull().sum()
day ofweek:0
going to:0
Distance:0
max speed:22
avg speed:0
avg moving speed:0
fuel economy:17
total time:0
moving time:0
take 407 all:0
评论:181
df.isnull().mean()*100
星期:0.00%
去向:0.00%
距离:0.00%
最大速度:10.73% 平均速度:0.00%
平均移动速度:0.00%
燃油经济性:8.29%
总时间:0.00%
移动时间:0.00%
耗时 40
因为我们用 np.nan 随机替换了 MaxSpeed 列中 10%的值,所以它有大约 10%的丢失值就不足为奇了。
我们还可以使用missingno包来生成数据缺失的可视化表示。如果您试图检测缺失值之间的关系,例如与其他列一起缺失或在特定周、月等期间缺失,这是一个非常有用的工具。
在下面的矩阵视图中,我们可以用空行看到缺失值,用黑线看到未缺失值。正如所料,我们丢失的值随机出现在 MaxSpeed 列。

缺少值,缺少编号
1.基本插补技术
1.1.均值和众数插补
我们可以使用 scikit-learn 中的SimpleImputer函数用一个填充值替换缺失值。简单插补器函数有一个名为策略的参数,它为我们提供了四种选择插补方法的可能性:
strategy='mean'使用列的平均值替换缺失值。strategy='median'使用列的中间值替换缺失值。strategy='most_frequent'使用列的最频繁(或模式)替换缺失值。strategy='constant'使用定义的填充值替换缺失值。
下面我们将在 MaxSpeed 列上使用 SimpleImputer transformer,它有 10%的随机缺失值。我们首先用 strategy=mean 定义均值估算器,然后在列上应用 fit_transform。
让我们绘制一个散点图,x 轴是 AvgSpeed,y 轴是 MaxSpeed。正如我们所知,AvgSpeed 列没有缺失值,我们用列平均值替换了 MaxSpeed 列中缺失的值。在下图中,绿点是转换后的数据,蓝点是原始的非缺失数据。

在绿色中,查看 MaxSpeed 的平均值为~127 的估算数据点。
我们可以对中间值、最频繁值或常量值重复同样的操作。🌀
这里是用最频繁值(mode)进行输入的例子。

在绿色,看到估算数据点的最小模式的最大速度,约 124。
注:如果有一种以上的模式,则插补使用最小模式。
但是,均值插补可能会使标准误差产生偏差,如果值不是随机缺失的,也可能会使该列的实际均值/众数产生偏差。根据缺失量的不同,这种方式的输入也会影响列之间的真实关系。如果该列有许多异常值,则中位数插补应优先于平均数插补。
1.2.时间序列插补
在加载数据集时,我们用日期和开始时间列的组合定义了索引,如果不清楚,请参见上面的数据部分。☝️
估算时间序列数据中缺失值的一种方法是用上一个或下一个观察值填充它们。Pandas 有 fillna() 函数,该函数有方法参数,我们可以选择 "ffill" 填充下一个观察值或 "bfill" 填充上一个观察值。
下图按时间显示了 MaxSpeed 列的前 100 个数据点。
df['MaxSpeed'][:100].plot(title="MaxSpeed", marker="o")

这里可以看到缺少的值
如果我们想用下一个观察值填充缺失值,我们应该使用 method="ffill "。

用下一个观察值填充缺失值
如果我们想用以前观察到的值来填充缺失的值,我们应该使用 method="bfill "。

用先前观察值填充的缺失值
除了 bfill 和 ffill,我们还可以使用 Pandas 的[interpolate()](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html)函数,并选择方法 =“线性”来填充缺失值,在前一个和下一个观察值之间按升序排列。重要的是要注意,这个函数通过忽略索引来威胁等距值。

用线性插值填充的缺失值
除了 method= linear 进行插值,我们还可以选择多项式,样条,最近,,二次等。如果你对插值技术感兴趣,可以看看这个熊猫文档。📖
2.先进的技术
2.1.k 近邻(KNN)插补
估算缺失值的另一种方法是预测它们。最近邻插补被广泛应用,并被证明是缺失值插补的有效方法。
我们可以使用 Scikit-learn 中的[KNNImputer](https://scikit-learn.org/stable/modules/generated/sklearn.impute.KNNImputer.html),其中缺失值使用训练集中找到的 K- 最近邻的平均值进行估算。
from sklearn.impute import KNNImputerKNNImputer(*missing_values=np.nan*, *n_neighbors=5*, *weights='uniform'*, *metric='nan_euclidean'*)
KNNImputer 有几个参数,例如默认设置为 np.nan 的 missing_values ,默认设置为 5 的用于插补的相邻样本的选定数量的 n_neighbors ,默认设置为‘nan-euclidean’的用于搜索邻居的距离度量的 metric ,但也可以使用用户定义的函数进行定义。
KNNImputer 可以处理连续、离散和分类数据类型,但不能处理文本数据。因此,我使用选定的列子集过滤数据——Distance、MaxSpeed、AvgSpeed 和 AvgMoovingSpeed。此外,我使用了 scikit-learn 中的MinMaxScaler,在 0 和 1 之间规范化这个数字数据。因为 KNNImputer 是基于距离的算法,所以缩放是流水线中的重要一步。
在定义了 KNNImputer 之后,我们进行 fit_transform 并保存新的(估算)数据。下面是估算数据与原始数据的散点图,绿色显示了 MaxSpeed 的估算值。

在绿色中,请参见使用 KNN 估算器估算的数据点
KNNImputer 有几个优点,比如易于实现,能够处理数字和分类数据类型。然而,定义邻居的数量— k 可能很棘手,因为它引入了鲁棒性和速度之间的权衡。如果我们选择一个小的 k ,那么我们有快速的计算,但是不太稳健的结果。相比之下,如果我们选择一个大的 k ,那么我们有一个更健壮但是更慢的计算。
2.2.用链式方程进行多元插补——小鼠
MICE 算法可能是最常用的插补技术之一,也是一个常见的面试问题。😈
MICE 首先计算有缺失值的每一列的平均值,并将平均值用作占位符。然后,它运行一系列回归模型(链式方程)来依次估算每个缺失值。与任何回归模型一样,MICE 使用特征矩阵和目标变量进行训练,在这种情况下,目标变量是缺少值的列。MICE 预测并更新目标列上缺失的值。通过不断地用前一次迭代的预测来改变占位符变量,MICE 反复多次重复这个过程。最后,得到一个稳健的估计。
为了应用 MICE 算法,我们将使用 scikit-learn 中的[IterativeImputer](https://scikit-learn.org/stable/modules/generated/sklearn.impute.IterativeImputer.html#sklearn.impute.IterativeImputer)。这个估计器还在实验中,所以我们必须导入enable_iterative_imputer.
IterativeImputer(*estimator=None*, *missing_values=np.nan*, *sample_posterior=False*, *max_iter=10*, *tol=0.001*, *n_nearest_features=None*, *initial_strategy=’mean’*, *imputation_order=’ascending’*)
见下图,估算数据与原始数据的散点图。

绿色表示使用鼠标插补的插补数据点
结论
在本文中,我们探索了不同的方法来估算数据集中的缺失值。我们从使用无丢失包来检测丢失的数据开始。然后,我们使用简单的估算器计算缺失数据列的平均值和最频繁值。由于数据中存在时间元素,我们还讨论了使用时间序列填充缺失值的回填、前向填充和线性插值。最后,我们转向更先进的技术;k-最近邻插补(KNN)和使用机器学习填充缺失值的链式方程多元插补(MICE)。
我希望你喜欢阅读关于估算缺失值的文章,并发现这篇文章对你的工作有用!
如果你喜欢这篇文章,你可以在这里阅读我的其他文章和 关注我的中如果有任何问题或建议,请告诉我。✨**
喜欢这篇文章吗? 成为会员求更!
参考
在数据科学中,最好的学习方法是实践
原文:https://towardsdatascience.com/in-data-science-the-best-way-to-learn-is-by-doing-6d16e38d7f1a
我们中的许多人都在接近一年中的这个时候,在寒假之前,工作或学校的事情都在放缓。对于一些人来说,这是一个完美的时机,可以保持时间表清晰,计划最少,专注于放松和与家人和朋友保持联系。如果这听起来像你:太棒了!把这个版本的变量放在书签里,在合适的时候重新访问它。
然而,对其他人来说,这段安静的时间提供了一个学习和尝试新话题的好机会。如果您是一名数据从业者,正在寻找这样的机会,我们可以为您提供帮助。
本周,我们选择了一些最近最好的实践教程:它们提供了易于理解的解释,提供了大量代码片段供您修改,并涵盖了广泛的令人兴奋的领域。只需选择一个(或…六个),卷起袖子,开始行动。
- 扩展你的 NLP 工具包 。为了获得从文本数据中提取洞察力的宝贵经验,跟随法扎德·马哈茂德·迪诺巴向我们介绍情感分析的本质细节。他的演练展示了使用 NLTK、scikit-learn 和 TextBlob 的完整实现。
- 如何在 PyTorch 中选择合适的学习速率。想在深度学习领域获得一些实用的技能培养吗? Leonie Monigatti 的新视觉指南解释了如何使用学习率调度器,它允许你调整和优化训练时段之间的节奏。
- 利用您的设备数据和编程知识的全新方式 。如果你对公开可用的数据集感到无趣,为什么不充分利用你自己的数据呢? Rosie Young 展示了如何使用 Python 讨论和探索您的 Strava 数据,以及如何得出关于您的习惯和活动水平的有趣见解。
- 概率分布终极指南。如果你在假期活动之间有一些空闲时间,比如说 97 分钟,我们希望你能找到一种比礼萨·巴盖里耐心而全面的教程更有启发性的方式来度过这段时间。它将统计和编程结合在一起,涵盖了概率分布的数学基础,并展示了如何用 Python 和 SciPy 库生成概率分布。

照片由 Junseong Lee 在 Unsplash 上拍摄
- 温习一下你的 MySQL 技能 。如果你预计在 2023 年从事一些数据库繁重的项目,现在可能是积累和扩展你的 SQL 知识的最佳时机。 Lynn Kwong 的最新帖子解决了几个高级 MySQL 查询和多种计算中值的方法。
- 为自己的特写镜头准备好图像 。近年来,视觉数据的真实使用案例激增,每一个都需要满足特定要求的图像。Conor O'Sullivan 的有用教程是关于深度学习项目的图像增强:它从收集一个健壮的数据集开始,然后涵盖了用 Python 处理图像的技术。
- 你的代码大概能跑快一点 。根据 Casey Cheng 的说法,不可能指出 Python 中最快的循环方法:最适合你的方法可能因情况而异。这使得他的详细概述更加有用,因为它引导我们通过多个选项(每个选项都包含代码示例)。
- 是时候提升你的张量流技能了吗? “最好的学习方法是实践,”拉希达·纳斯林·阿摩林说。如果你有一些 ML 经验,并且想学习如何定制你的 TensorFlow 层、损失函数和激活函数,Rashida 最近的文章既清晰又简洁。
随着这一年接近尾声,我们一如既往地感谢读者的支持。如果你想产生最大的影响,考虑成为一个中等成员。
直到下一个变量,
TDS 编辑
NLP 初学者的深入空间教程
原文:https://towardsdatascience.com/in-depth-spacy-tutorial-for-beginners-in-nlp-2ba4d961328f
学习 Scikit-学习自然语言处理

照片由 安妮荣凯
介绍
不,我们今天不会用数十亿个参数来构建语言模型。我们将从小处着手,学习 spaCy 的 NLP 基础知识。我们将仔细研究这个库是如何工作的,以及如何使用它来轻松解决初级/中级 NLP 问题。
帖子已经很长了,我就截到这里,直接跳到文章的正文。
https://ibexorigin.medium.com/membership
获得由强大的 AI-Alpha 信号选择和总结的最佳和最新的 ML 和 AI 论文:
https://alphasignal.ai/?referrer=Bex
spaCy 是什么?
spaCy 就像自然语言处理的 Sklearn。它是一个具有大量功能的行业标准,能够以一流的速度、准确性和性能解决许多 NLP 任务。
其核心是管道,你可以把它想象成已经在数百万文本实例上训练过的特定于语言的模型。
它也是 spaCy 生态系统的领导者,该生态系统包括几十个库和工具,如 Prodigy、Forte、displaCy、explacy、ADAM、Coreferee 等。
spaCy 还可以与来自 TensorFlow、PyTorch 和其他框架的定制模型握手。
现在,spaCy 支持 66 种语言作为独立的管道,新的语言正在慢慢增加。
空间基础
在了解 spaCy 如何工作之前,让我们安装它:
pip install -U spacy
spaCy 有三条英语管道,对于复杂的任务有不同的大小和功能。在本教程中,我们将只需要安装小型和中型管道,但为了完整起见,我也包括了大型管道:
导入 spaCy 后,我们需要加载刚刚安装的一个管道。现在,我们将加载小的并存储到nlp:
在 spaCy 生态系统中命名任何加载的语言模型nlp是一个惯例。现在可以在任何文本上调用该对象来启动信息提取:
# Create the Doc object
doc = nlp(txt)
doc对象也是一个约定,现在它已经填充了关于给定文本的句子和单词的额外信息。
一般来说,doc对象只是一个迭代器:
您可以使用切片或索引符号来提取单个令牌:
>>> type(token)spacy.tokens.token.Token>>> len(doc)31
记号化就是把句子拆分成单词和标点符号。单个标记可以是一个单词、一个标点符号或一个名词块等。
如果您提取了多个令牌,那么您就有了一个 span 对象:
>>> span = doc[:5]
>>> type(span)spacy.tokens.span.Span>>> span.text'The tallest living man is'
spaCy 也是为了提高内存效率而构建的。这就是为什么token和span对象都只是doc对象的视图。没有重复。
预训练的英语管道和许多其他管道具有特定于语言的规则,用于标记化和提取它们的词汇属性。这里有 6 个这样的属性:
一些有趣的属性是lemma_,它返回去掉了任何后缀、前缀、时态或任何其他语法属性的基本单词,还有like_num,它既能识别文字数字,也能识别字母数字。
你将把大部分时间花在这四个物体上——nlp、doc、token和span。让我们仔细看看它们是如何关联的。
架构和核心数据结构
让我们从nlp开始,它是一个隐藏的语言对象:
语言对象在数百万个文本实例和标签上进行预训练,并以其二进制权重加载到 spaCy 中。这些权重允许您在新数据集上执行各种任务,而不必担心繁琐的细节。
正如我前面提到的,spaCy 拥有 22 种语言的完整训练管道,其中一些你可以在下面看到:
对于其他+40 语言,spaCy 只提供基本的标记化规则,其他功能正在慢慢与社区的努力集成。
也可以直接从lang子模块加载语言模型:
在处理一个文本后,单词和标点符号被存储在nlp的词汇对象中:
>>> type(nlp.vocab)spacy.vocab.Vocab
这个Vocab在文档之间共享,这意味着它存储所有文档中的所有新单词。相比之下,doc对象的词汇表只包含来自txt的单词:
>>> type(doc.vocab)spacy.vocab.Vocab
在内部,spaCy 以散列方式通信以节省内存,并有一个名为StringStore的双向查找表。您可以获取字符串的哈希值,或者如果有哈希值,则获取字符串:
>>> type(nlp.vocab.strings)spacy.strings.StringStore>>> nlp.vocab.strings["google"]1988622737398120358>>> nlp.vocab.strings[1988622737398120358]'google'
当令牌进入Vocab时,它们会丢失所有特定于上下文的信息。所以,当你从词汇库中查找单词时,你是在查找lexeme s:
>>> lexeme = nlp.vocab["google"]
>>> type(lexeme)spacy.lexeme.Lexeme
词汇不包含特定语境的信息,如词性标记、形态依赖等。但是它们仍然提供了这个词的许多词汇属性:
>>> print(lexeme.text, lexeme.orth, lexeme.is_digit)google 1988622737398120358 False
orth属性是针对词素的散列。
所以,如果你正在通过doc对象看一个单词,它就是一个令牌。如果是来自Vocab,那就是一个词位。
现在,我们再来谈谈doc这个物体。
在文本上调用nlp对象生成文档及其特殊属性。
您可以通过从tokens模块导入Doc类来手动创建文档:
Doc需要三个参数——来自nlp的词汇表、一个单词列表和另一个指定单词后面是否有空格的列表(包括最后一个)。doc所有 tokes 都有这个信息。
>>> len(doc)4>>> doc.text'I love Barcelona!'
Spans 也是它们自己的一个类,公开了一系列属性,尽管它们只是doc对象的一个视图:
要手动创建 span 对象,请将 doc 对象和标记的开始/结束索引传递给Span类:
命名实体识别(NER)
自然语言处理中最常见的任务之一是预测命名实体,如人、地点、国家、品牌等。
在太空中表演 NER 简单得可笑。处理完一个文本后,只需提取doc对象的ents属性:
克利奥帕特拉被认为是一个人,而埃及是一个地缘政治实体(GPE)。要了解其他标签的含义,您可以使用explain功能:
除了打印文本,您还可以使用 spaCy 的可视实体标记器,可通过displacy获得:

作者图片
图像显示亚历山大大帝没有被识别为一个人,因为它不是一个普通的名字。不过没关系,我们可以手动给亚历山大贴上一个人的标签。
首先,通过给它一个标签(PERSON)来提取全名作为 span:
然后,用跨度更新ents列表:
doc.ents = list(doc.ents) + [alexander]
现在,displacy也对其进行标记:

作者图片
您也可以用set_ents函数设置新实体:
# Leaves the rest of ents untouched
doc.set_ents([alexander], default="unmodified")
预测词性(POS)标签和句法依赖性
spaCy 还为文档的语法分析提供了丰富的工具选择。标记的词汇和语法属性作为属性给出。
例如,让我们来看看每个标记的词性标签及其句法依赖性:
输出包含一些令人困惑的标签,但我们可以推断出其中的一些,如动词、副词、形容词等。让我们看看其他几个人的解释:
上表中的最后一列表示单词关系,如“第一个”、“第一个脚印”、“保持在那里”等。
spaCy 包含许多更强大的语言分析特性。作为最后一个例子,下面是你如何提取名词块:
从 spaCy 用户指南的本页了解更多关于语言特性的信息。
基于自定义规则的令牌化
到目前为止,spaCy 已经完全控制了标记化规则。但是一种语言可能有许多文化特有的特质和不符合 spaCy 规则的边缘情况。例如,在更早的例子中,我们看到“亚历山大大帝”作为一个实体被遗漏了。
如果我们再次处理相同的文本,spaCy 将实体视为三个令牌。我们需要告诉它,像亚历山大大帝或破碎的布兰这样的有头衔的名字应该被认为是一个记号,而不是三个,因为把它们分开没有意义。
让我们看看如何通过创建自定义令牌化规则来实现这一点。
我们将从创建一个模式作为字典开始:
在 spaCy 中,您可以组合使用大量的关键字来解析几乎任何类型的令牌模式。例如,上面的模式是一个三标记模式,第一个和最后一个标记是一个字母数字文本,中间的一个是一个停用词(如 the、and、or 等)。)。换句话说,我们是在匹配“亚历山大大帝”而没有明确告诉 spaCy。
现在,我们将用这种模式创建一个Matcher对象:
处理完文本后,我们在doc对象上调用这个 matcher 对象,它返回一个匹配列表。每个匹配是一个包含三个元素的元组——匹配 ID、开始和结束:
你可以随意调整图案。例如,您可以在 reGex 关键字中使用像OP这样的量词,或者使用 reGex 本身:
你可以从这里了解更多关于定制的基于规则的匹配。
词向量和语义相似度
NLP 的另一个日常用例是预测语义相似度。相似性得分可用于推荐系统、抄袭、重复内容等。
spaCy 使用单词向量(如下所述)计算语义相似度,单词向量在中型模型中可用:
所有的doc、token和span对象都有这个similarity方法:
这三个类也可以相互比较,就像 span 的一个标记:
>>> doc1[0:2].similarity(doc[3])0.8700238466262817
使用单词向量来计算相似度,单词向量是单词的多维数学表示。例如,下面是文档中第一个标记的向量:
关于管道的一切
在引擎盖下,语言模型不是一个管道,而是一个集合:
当用nlp处理一个文本时,它首先被标记化,并从上面的列表传递给每个管道,管道又修改并返回带有新信息的 Doc 对象。以下是空间文档中对此的说明:

图片来自空间文档
但是,spaCy 没有为您在现实世界中可能遇到的任何 NLP 问题提供定制管道。例如,您可能希望在将文本传递给其他管道之前执行预处理步骤,或者编写回调来提取管道之间不同类型的信息。
对于这种情况,您应该学习如何编写定制的管道函数并将它们添加到nlp对象中,这样当您对文本调用nlp时,它们就会自动运行。
以下是如何做到这一点的基本概述:
你需要通用的Language类,并在你的函数上修饰它的component方法。自定义函数必须接受并返回 doc 对象的单个参数。上面,我们定义了一个简单的管道,打印出 doc 对象的长度。
让我们将它添加到nlp对象中:
如您所见,自定义管道被添加到末尾。现在,我们将在一个示例文本上调用nlp:
>>> doc = nlp("Bird dies, but you remember the flight.")There are 9 tokens in this text.
按预期工作。
现在,让我们做一个更严肃的例子。我们将回到“亚历山大大帝”的例子,并编写一个管道来添加征服者的名字作为一个实体。我们希望所有这些都自动发生,而不需要找到管道外的实体。
以下是完整的代码:
我们定义模式,创建匹配器对象,将匹配添加到实体中,并返回文档。让我们来测试一下:
它正在按预期工作。
到目前为止,我们一直在末尾添加自定义管道,但是我们可以控制这种行为。add_pipe函数有参数来精确地指定您想要插入函数的位置:
结论
今天,你已经朝着掌握自然语言处理的 Scikit-learn 迈出了大胆的步伐。有了这篇文章的知识,你现在可以自由地浏览 spaCy 的用户指南,它和 Scikit-learn 的一样大,信息丰富。
感谢您的阅读!
https://ibexorigin.medium.com/membership https://ibexorigin.medium.com/subscribe
我的更多故事…
https://ibexorigin.medium.com/in-depth-guide-to-building-custom-sklearn-transformers-for-any-data-preprocessing-scenario-33450f8b35ff </10-minute-effortless-sql-tutorial-for-die-hard-pandas-lovers-a64c36733fd0>
通过一个完整的例子深入理解 NeuralProphet
利用气象数据探索脸书算法
NeuralProphet 是由脸书创造的 Prophet 算法的一种进化,是一种时间序列预测算法。在这篇文章中,我将基于我在研究过程中遇到的一个具体而完整的问题来详述这个算法。

语境
大多数时间序列问题需要容易理解的预测。同时,需要一个有效的预测。这两个愿望导致了一个权衡:可解释性与性能。
由于更复杂的模型,效率的显著提高通常是。然而,复杂性很少等同于可解释性。
简而言之,存在两类模型:
- ****统计算法(ARIMA,GARCH …),这些算法在理论上是可以用数学方法解释的
- ****神经网络模型,这些模型非常复杂,但已经证明了它们的性能
最初,脸书和 Prophet 的意愿是提供一个简单可行、可调整和可解释的工具来预测时间序列。然而,一个问题依然存在:性能不佳。为了解决这个问题,NeuralProphet 应运而生。
简而言之, NeuralProphet =神经网络+预言家
因此,其目的是通过在 Prophet(一种统计算法)中包含神经网络来结合前面两个类别。目的:提高效率,同时限制可解释性的损失。****
这个介绍部分可以通过下图大致描绘出来:

关于时间序列算法复杂性和性能的简化示例。为普及和解释目的而制作,并可能进行调整。(作者自制)
问题
作为解释的基础,我将概述我在学习期间与 12 个人一起贡献了 18 个月的一个项目。这个问题是我感兴趣的枝节问题,不是项目的主要焦点。
法国电网必须稳定地输送大量电能以满足各种需求。这种供需平衡至关重要,因为:
- 如果产量低于需求水平,那么电网可能会出现不正常情况,例如电力故障或电压下降。
- 如果产量高于需求水平,那么未售出的能源就被浪费了,因为电能的储存是具有挑战性的(低效且繁重)
幸运的是,这种平衡一直得到保持,这主要归功于发电厂有资格作为可调度的发电,因为的电力是按需微调的。风力涡轮机等所谓的“T4”间歇性能源就不是这样了,它依赖于一个随机因素:风。
目标是在风能生产更多的时候优化风能生产,反之,当风能生产下降的时候补偿其间歇性。
专家们目前正在通过接收大量数据(每秒 40 000 比特)并相应地采取行动来完成这项任务。超越自己的控制来运行它不是很吸引人吗?
智能电网旨在智能管理电力网络。在能源领域,人工智能显然是一个有前途的解决方案,这篇文章提供了在这种背景下使用神经营养体的选择。
为了监控风电场,我们选择了负载系数,它是发电量除以装机容量的比率(在给定的时间段内)。因此,监控这一负载系数大体上相当于调节有效风力涡轮机的数量,从而调节它们向电网提供的能量。
在学校环境中,已经在互联网上挖掘了 300 多个业余气象站的温度和风速(用于其他目的)。假设负载系数的预测将受益于这些数据。****
正如 RTE 所强调的,天气会显著影响功耗。考虑到温度是值得的,负载系数与功耗相关。此外,由于可用的风能主要取决于风速,这也可能是有益的。
数据洞察
在 2019 年 3 月 1 日至 2019 年 9 月 30 日(约 7 个月)期间,对于 300 个气象站中的每一个气象站,以每小时的时间步长提取温度和风速。这两个特征将在后面作为观察变量提及。****
与此同时,通过从 RTE 网站获取风力和装机容量,可以获得现有的负载系数。负载系数将被称为“目标”。
培训将在前 6 个月(训练组)进行,测试组是上个月的。数据已经标准化。****
之后,只保留了 57 个气象站,以保证模型的运行速度,并去除其中的等效站(附近的)。
下图可能总结了问题和可用的数据:

50 个气象站目标图(作者制作)
选择的评估指标是 RMSE。我们的基线模型是一个简单的算法,它在测试集上的 RMSE 为 0.109 。
模型:神经原植物
NeuralProphet 模型预测未来若干时间步 h 。 h 称为“视界”。
“神经营养模型的一个核心概念是它的模块化可组合性”。
设定 h 的目标值后,根据以下关系预测到 h 的每个时间步长:

神经营养模型(作者制作)
因此,例如,提前一步的预测相当于用 h = 1 写出过去的关系,即:

一个只有 1 个地平线的例子,即向未来迈进一步(作者制作)
****6 个特定模块放在一起构成了神经营养体。训练要求每个模块修改其正确的预测,以最小化整体损失。
文章的其余部分基于上面提出的问题,集中在每个模块的解释和动机上。
1.1 趋势
概括地说,趋势与信号的总体变化有关(而不是信号内的小波动)。****
NeuralProphet 学会检测趋势发生明显变化的日期。这些点叫做变点。在这两者之间,趋势应该是线性的。
该问题类似于线性多元回归,其中线性回归在 2 个变化点之间进行估计。****
这些点沿时间轴统一初始化。然后,每个线性回归的成本被添加到模型的总损失中。梯度下降使损失最小化,并随后改善回归。特别地,线性回归优化的参数是斜率,称为生长值。****
此外,执行 L1 调整以删除不必要的变化点。额外的变化点也可以手动添加。
在测试阶段,趋势是最后一次线性回归的外推。 10%的数据(可参数化)留在最后,以确保一致的最终趋势。

通过梯度下降法估计参数的趋势计算公式(作者制作)
截距不是一个可训练的参数,而是取决于增长率以确保连续性。
从视觉上看,这相当于:

每条红色竖线是一个变化点,每一步定义了算法的改进(作者做)
关于我们的负载系数示例,我们有以下结果:

默认参数趋势结果— 11 个变点(作者制作)
在解释了下一个模块:季节性之后,将绘制预测图。
1.2 季节性
时间序列的季节性是由它的性质决定的信号的周期性运动。
例如,很明显的温度遵循的每日季节性,因为一种模式每天都在重复:晚上比白天冷。气温也遵循年度季节性:冬天比夏天冷,等等。
为了对这种季节性进行建模,NeuralProphet 假设目标是一个周期性的连续函数。因此,它可以表示为傅立叶级数。

假设某些假设(作者做出的)写成傅立叶级数的 p 周期函数
此公式的两个备注:
- 总和前面的常数项(即偏移量)对于神经原素来说可能是不必要的,因为趋势隐含地考虑了它。
- 无穷多个 a 项和 b 项的计算不可行。因此,仅估算前 k 项(带有 k 个参数)。

论文关系(作者自制)
该模型的目标是找到最适合目标的 **2k 参数。默认情况下,有 3 个周期被激活:p = 1 的日周期、p = 7 的周周期和 p = 365,25 的年周期。对于每个周期,我们分别有:k = 6,k = 3 和 k = 6。(如果目标覆盖至少 2 倍于周期,则激活一个周期)。***
与趋势相同,通过添加与建模的季节性和实际目标之间的差异相关的成本来计算参数。
如果指定了参数“乘法”,则季节性与趋势是可相乘的。

(作者制作)
由于只有 6 个月的数据可用,年度期间未激活(强制激活的选项)。但是,其他时段可以根据数据类型手动添加(如果是每月,p = 30)。
对于我们的问题,我们让两个默认季节性。事实上,我们假设日季节性存在(与昼夜节律相关的负荷因子的相关性?).还假设了周季节性(与工作相关的负荷系数相关性?).
取 p = 1 & 7,并考虑附加季节性(相对于乘法),我们有:

在我们的例子中与 p = 1 和 p = 7 的关系(由作者提出)
拟合数据后,我们得到这些图。

负荷系数的周、日季节性(作者自制)
通过结合趋势和季节性,执行以下预测。

由 NeuralProphet 作出的仅激活趋势和季节性的预测(由作者作出)
毫无疑问,预测表现不佳,RMSE 为 1.32。即使是天真的算法也能获得更好的结果(RMSE 为 0.109)。
之后,其他模块将包含在整体模型中,希望有更好的预测(剧透:是)。
1.3 自动回归
****自回归(AR)过程可能是最常用的时间序列过程,用于捕捉序列中随机变量随时间的相关性。
将 p(先前值的数量)包括在内,AR 过程定义为:

AR 过程与 c 截距的关系以及最后一项 a 白噪声过程(作者提出)
这种白噪声是一种随机过程,必须符合三个标准,即:

白噪声过程必须验证这三个标准
白噪声的分布没有规定,但是例如,具有零均值和恒定标准偏差的独立同分布正态分布是可行的。这种白噪声是世界分解的核心概念,是时间序列理论的一个关键定理。****
然而,AR 过程仅预测 1 次进入未来。给定目标的 p 个过去值,预测时间 t。因此,如果我们有 h > 1,我们需要 h 个不同的模型。有了 NeuralProphet 选择的 AR-Net 模型,这个问题就迎刃而解了。
使用时间 t 作为预测原点(即预测的第一步),前一个 p 值预测下一个 h 值。
****预测起点是有意义的,因为对于给定的预测时间步长,存在不同的预测起点。
例如,如果 h = 3(即,我们希望预测未来的 3 个时间步长),在 t = 5000 的预测将来自 3 个不同的起点:在 t = 4998、4999 和 5000。该研究论文在一封新的信 A 中包含了这一概念:

预测原点示例(由作者制作)

AR-Net 关系(作者自制)
在这一阶段,仍然存在几个问题。
如何权衡 p?
根据研究论文:
应该根据过去观测中相关背景的大致长度来选择该参数。实际上,很难准确确定,通常设定为最内部周期的两倍或预测范围的两倍”。
AR-Net 应该使用哪种神经网络架构?
默认情况下,神经网络利用最简单的现有架构** e:没有中间层,没有激活函数,只有 p 个输入连接到 h 个输出,例如:**

具有 p 个输入和 h 个输出的最简单神经网络架构的图示(由作者制作)
提醒一下,NeuralProphet 是简单的,以上面定义的方式,可以通过分析权重的值来解释这个架构。****
作为可解释性损失的回报,有可能添加隐藏层以使模型复杂化。隐藏层的数量,以及神经元的数量,是可参数化的。为了减少这种损失,AR-Net 使用了 L1 正则化,这导致了权重的稀疏性。然而,我觉得深入探讨这一点并不有趣,因为除了使用的正则化函数的形式之外,它主要涉及深度学习的基础。我让读者在这里查看原始的 AR-Net 研究论文。
这是在不同中间层数目和 p 值的测试集上,h = 1 的 NeuralProphet 的结果。

不同超参数的结果(默认隐藏层的维度为 p + h)。结果可能会有所不同,仅供讨论之用(由作者制作)
在我们的问题中,隐藏层数似乎不影响 RMSE 。在训练时间较短的情况下,“密集”架构无法反映输入信号的复杂性。如果超参数以适当的方式和更多的训练时间进行调整,情况应该不是这样。
用 p = 2 天 (224 = 48 分),并且隐藏层数= 0,我们有如下预测:*

趋势、季节性和 AR 过程的结果(作者制作)
结果:测试集上的 RMSE 为 0.0844,比 0.109 的原数据集好 21%。
我们还可以通过绘制模型的组件来测量主要影响预测的滞后。

不同滞后的 AR 相关性
滞后 1 & 2 负责约 60%的预测。奇怪的是,在预报起点前 24 小时的负荷系数的值似乎是一个影响因素。****
现在,让我们继续详细介绍剩余的模块。
1.4 滞后回归变量
在这个阶段,介绍了神经营养体的主要概念:趋势、季节性和 AR-Net。
滞后回归变量就是我们之前定义的“观察变量”:温度和风速。在我们的整体模型中包含这样的信息可能也是相关的,希望得到更好的结果。
观察变量通过…一个 AR-Net 被附加到 NeuralProphet 模型中!!
除了输入是观察变量的 p 个先前值(而不是目标的 p 个先前值)。
提醒一下,观察到的变量是温度和风速。具体来说,我们有多达 114 个变量(57 个站点 x 每个站点 2 个变量)。因此,神经营养细胞产生了 114 个不同的阵列。
默认情况下,历元数自动设置为 125,批处理大小设置为 32。之前用“lr 范围测试”来估计学习率。
理论上,如果 RMSE 低于 0.0844 (没有滞后回归的值),我们观察到的变量会积极影响预测。
不幸的是,在用不同的配置(滞后回归量的数量、调整超参数……)进行多次尝试后,RMSE 仍然在 0.0844 以上,最低为 0.095
结论:这些观察到的变量(风速和温度)似乎没有隐含的相关信息来帮助负荷系数的预测。几个原因可能是:****
- 一个更现实的解决方案是着眼于地区或部门层面,而不是预测整个国家的单一负载系数值
- 底层模型不够复杂无法捕捉观察变量和目标之间的关系
- 每个滞后回归变量都与一个唯一的 AR-Net 相关联。参数不共享。因此,没有充分考虑观察变量之间的相关性。
- 以此类推……
1.5 未来回归变量
未来变量是未来已知的变量。在每个时间步 t,这些变量的值是已知的。****
例如,未来可能有用的回归变量是法国风力发电厂的装机容量。实际上,负载系数取决于该安装容量,并且该变量是可以通过跟踪例如正在进行的建筑工地而预先知道的数据。
从技术上来说,如果我们想要预测 t = 5000 时的目标,则 t 时的未来回归量的值由可训练系数“d”加权。
然后和季节性一样,模 F(t)可以是“乘法”。

(作者自制)
然而,我手头没有未来的回归变量,因此它的影响在我们的问题中无法衡量。
1.6 活动和假期
这最后一个模块处理可能影响目标的偶然事件。如果事件发生,则事件是在时间 t 等于 1 的二进制时间序列。(否则为 0)。除此之外,它遵循与前一模块 F(t)** 相同的**流程。****

(作者自制)
NeuralProphet 提供了一个内置命令自动添加国家的节假日。****
对于每个事件,可以添加一个上下窗口,也就是说,例如,如果我们将圣诞节视为下窗口为-2,上窗口为+ 1 的事件,则除了 12 月 23 日、24 日、25 日和 26 日之外,该事件每次都等于 0,值为 1(圣诞节前为-2,圣诞节后为+1)。****
此外,如前所述,可训练系数允许量化最终预测中每个事件的重要性。****
最近,在 2021 年 12 月,法国可能会发生电力短缺的风险,这种事件在预测负载系数时可能是有用的。
让我们看看法国假期对目标预测的影响。

AR 砝码(作者自制)
对于每个事件,我们都有它的参数值。NeuralProphet 在一行代码中毫不费力地提供了这些视觉效果。因此,结果是方便解释。****
然而,在我们的例子中,与没有这个事件相比,使用相同的 RMSE,多次尝试仍然不成功。
结论
模块化的可组合性已经被证明与可解释性的目的非常相关。的确,用 NeuralProphet 向非科学观众解释这个结果变得非常简单明了。
下一步是获得更多业务洞察以潜在地添加相关滞后回归量、未来回归量、事件或调整 AR-Net 的超参数(p、隐藏层等)。
NeuralProphet 有一个活跃的社区,即将推出令人兴奋的扩展。
我希望你喜欢这篇文章的技术方面和提出的问题。
如果你喜欢它,不要犹豫,请鼓掌并关注我的未来文章:)
来源
在机器学习中,失败和不确定性有时是成功的必要因素
作者聚焦
“我想成为一个用我的技能和知识来帮助我周围的人的人。”
在 Author Spotlight 系列中,TDS 编辑与我们社区的成员谈论他们在数据科学领域的职业道路、他们的写作以及他们的灵感来源。今天,我们很高兴与 阿尼·马杜尔卡 分享我们的对话。

照片由 Ani Madurkar 提供
Ani 是一位资深数据科学家,对机器学习& AI 的艺术和科学充满热情。他对健康、环境和运动科学的应用特别感兴趣。构建数据科学产品来赋能社区和加强网络是他的兴趣所在。
不做项目的时候,Ani 喜欢骑自行车,拍摄风景和街头摄影,阅读哲学,并在媒体上写下他的想法。
你是如何决定成为一名数据科学家的?
通过一系列毁灭性的失败,我找到了进入数据科学的道路。我原本想成为一名内科医生,专攻神经科学,但我两次都没有通过 MCAT 考试,这让我当时很迷茫。获得哲学学位毕业后,我觉得自己完全没有资格掌握任何有价值的技能来为他人解决问题。
我当时的研究让我接触到了数据分析,所以我想我应该尝试一下这个职业——主要是因为它激发了我的好奇心。从零开始,我自学了 SQL,并获得了一个数据分析师的职位。这第一步拓宽了我对数据世界的视野,我的好奇心增长到了无法满足的程度。
我能够很快掌握数据分析技术,并希望成为一名数据科学家,因为我觉得这更适合我。这需要大量的技能和经验,而我当时并不具备这些。因此,我自学了 Python,掌握了统计学和线性代数的基础,并在密歇根大学注册了应用数据科学硕士项目,同时工作并领导实践社区。
这份工作很大程度上是我的天职,而不是医学。从编程到数学,这些概念自然而然地来到我的面前,我能够快速地学习大量的信息。毕业后,我很快发现自己在构建企业机器学习系统,为备受瞩目的商业项目带来价值。
进入这个领域后,你面临过哪些挑战,你是如何应对的?
我的旅程还有更多细节,但基本概念是它充满了失败和不确定性。在大部分时间里,我觉得自己完全力不从心,在进行一场艰难的战斗。
我用三个心智框架来处理这些问题:永不满足的好奇心、不懈的动力和合作成长。我很幸运地找到了一个完全激起我的兴趣,充满了我深深感兴趣的理论和应用的工艺。
为了让你自己和你周围的人实现指数级增长,你应该通过合作来实现。
我的动力主要来自于我知道我的父母为了给我一个健康快乐的生活牺牲了多少,以及我有多接近放弃它,因为我认为太多的事情是理所当然的。我再也不想回到那个版本的自己,所以为了让他们和我自己感到骄傲,我坚持不懈。
最后,我相信,如果你尽你所能努力工作,但独自一人,你最多只能实现线性增长。为了让你自己和你周围的人实现指数级增长,你应该通过合作来实现。我想成为一个用我的技能和知识来帮助我周围的人的人,即使目前我不是最好的,这是我随身携带的原则。即使我现在正在做这些事情,我总是有时间帮忙。
你如何选择你从事的以数据为中心的项目?
这些天我主要发现自己被贝叶斯方法和图形 ML 项目所吸引。我认为这两种方法不仅是解决问题的有趣方式,也是看待世界的令人难以置信的迷人方式。我认为,在多任务学习和强化学习方面也有一些前沿工作正在进行,但我还没有找到许多行业现成的应用程序。
在接下来的几个月里,我想进一步专注于构建机器学习系统,而不仅仅是机器学习模型。TensorFlow 有一个很好的基础设施来构建远远超出 Jupyter 笔记本的系统。我认为大多数组织都有足够的人阅读 sklearn 文档和少量文章,在 Jupyter 中进行基本的机器学习设置,但仍然非常需要知道如何从这些模型中大规模创造价值的专家。MLOps 现在感觉很像狂野的西部,但我开始喜欢那种不确定性。
是什么促使你开始公开撰写关于你的职业和其他数据相关话题的文章?
我知道我现在想创建自己的品牌,但在开始之前,我想在这个行业有一个更好的立足点。我从事摄影已经快七年了,所以我被通过一种新的艺术形式创造一个在线的存在所深深吸引。我在我的硕士项目的顶点工程期间开始,并在写作之前初步确定了我想在网上拥有什么样的声音。
我注意到有大量的内容是给初学者的,也有大量的内容是给研究人员的,但是对于寻求有效和高效成长的中级专家来说,这些内容显得分散和杂乱。我想适应这个利基,所以我选择以一种应用的方式谈论方法和技术,这种方式讨论的理论足以在你自己的项目中工作。
中高级专家不需要被理论淹没,也不需要只有代码片段——他们两者都需要就够了。因此,在我的一些介绍技术概念的文章中,我提供了足够的理论,并将其与应用于数据集的代码结合起来,以指导全面的理解。
此外,我一直对教学充满热情,因为我来自一个前世是教师的家庭。我认为写下我的想法和学习是分享我所拥有的和向他人学习的最快和最有效的方式。
在 TDS 和其他地方的写作中,你融合了几种不同的兴趣。你是如何为你的帖子选择主题的?
我首先假设,如果我在努力理解某件事,很可能我的同龄人也是如此。我花大量时间阅读有关问题、概念或技术的资料,然后试图把我的想法写在纸上。我通常是一个思维活跃的人,所以我对源源不断的事物充满好奇。这创造了一个流动的内容引擎,包含了机器学习的艺术、科学和哲学的一切。
确定你的价值观和身份赋予你的内容个性;它给了人们某种联系。
我尽量给别人写一篇,给自己写一篇,以此来平衡我的写作。我会阅读大量的博客或文章,这让我很好地了解人们试图学习或正在努力学习的内容,然后我也会对我想学习和好奇的东西产生自己的兴趣。我会试着为读者和我自己各做一个,有时在一个故事里平衡两种需求。不过,最终,我还是会非常关注什么能给别人带来价值。
你对那些想写自己作品,但不知道从哪里开始或如何找到时间的人有什么建议吗?
我认为当务之急是首先要知道你是谁。你对什么感兴趣?你最引以为豪的工作是什么?确定你的价值观和身份赋予你的内容个性;它给了人们某种联系。
当我考虑创建内容时,我会分三个阶段来考虑:
- 创作娱乐内容。这是基本形式,因为它可能只是告知一项新技术,或总结一本书或课程,或澄清一些概念。这一阶段最好的可以是信息丰富的。这些内容会产生视图。
- 创作真实性内容。这是你在故事中加入自己的想法、观点和努力的时候。你可以在书中谈论一种新的建模技术,这很好,但如果你能添加一个你尝试它的轶事以及它是如何进行的,或者用你自己的声音对原始材料做出贡献,那就更好了。在内容中加入你的个人风格可以创造出人们真正感兴趣的故事。这些内容会产生追随者。
- 根据你的价值观创作内容。内容创作的最终形式是当你确定你的价值观是什么,你相信什么,你代表什么,然后以各种方式公开强化它们。你想成为人工智能道德实践的拥护者吗?把这个写进你的故事里。当你谈论一种新的 ML 模式时,做一些研究,并谈论它可能会给社会带来什么样的新问题。这很难,主要是因为很难坐下来知道你的价值观是什么。把事情搞砸了,一路编辑自己,没关系;从某个地方开始,失败,学习,提高,无休止地迭代。这些内容会产生合作伙伴。
这些阶段如何转化为你的写作流程?
仅仅为了娱乐或信息目的而创造内容没有错,但是要知道创造你自己的品牌远不止是在包装文档中重复例子。
至于找时间写作,我认为这可能很难,取决于你想写什么和写作对你来说有多容易。如果你只想写一些包含代码的以项目为中心的故事,你可能需要很多前期时间来做准备。
通常,我会试着在写作和足够的阅读量之间取得平衡。我总是试着随身携带一本新书、一篇文章或一篇博客文章(主要是在我的手机上),这样我就可以不断地阅读,哪怕是一次一点点。这样做的原因是它让我的大脑以我不会有的方式思考问题。我不是说要有零空闲时间,这对于创造力是非常有用的。我提倡创建你自己的内容引擎。创造方法让灵感来找你,而不是你去找它。
我读得越多,我给自己创造新故事的空间就越大。我的大部分时间不是花在感觉超级有动力和灵感上;事实上,我大部分时间都在感受相反的东西。而在那些时刻,最好是读书。所以,当灵感和能量袭来时,我准备好放下我的想法去写作。
展望未来一两年,你希望在数据科学领域看到什么样的变化?
我是一个超级技术乐观主义者,所以我希望在未来看到很多东西,但我会从可能是最有争议的开始。我希望看到范围更好的机器学习项目。今天,ML 项目没有按照计划进行有很多原因,但我觉得其中太多原因是因为它们是由那些可能不完全理解机器学习以及如何积极地使数据有用的人领导或提出的。让没有做过这项工作的人领导这样的大规模项目可能非常危险,因为它会导致不准确的时间表、精疲力竭的科学家和工程师、次质量系统的建立等等。
学习这些概念可能有一个较低的门槛,但这不应与优秀的低门槛相混淆。这是一个很难做好的行业,因此也是一个很难领导的行业。我做过的最好的项目是由科学家/工程师领导的,他们职业生涯的大部分时间都在做统计或机器学习,并逐渐学习业务。显然,这是罕见的,所以我希望未来能带来一种更明智、更可行的方法来构建成功概率更高的项目。
我们相互联系和多样化决策的能力,将是创造利大于弊的系统的必要一步。
我也对以数据为中心的 ML 感到非常兴奋。对高质量数据的高度关注将是未来数据科学和技术的重要一步。随着我们开始成为一个以技术为中心的社会,我们今天在数据方面面临的任何挑战(并且有很多挑战)都将变得更加困难。
技术已经成为我们用来应对社会最棘手问题的武器,比如气候变化、公共卫生等。不管我们喜欢与否,我们正在走向一个未来,在这个未来中,我们拥有传感器、相机、虚拟助手,并且更加无缝地融入社会。这带来了过多的道德问题和数据问题。这将要求我们超越 Jupyter 笔记本的思维,这不是一个人的工作。我们需要在大规模处理和部署数据方面变得更好,以应对这些新问题,我们最好的防御是我们的协作和集体性质。开源、虚拟社区和广泛的知识共享是我们成功构建机器学习和人工智能系统来解决我们最具挑战性的问题的能力的关键部分。
该领域正在快速发展,每天都有新的模型、工具和论文发布。我们相互联系和多样化决策的能力,将是创造利大于弊的系统的必要一步。我非常期待和更多启发我的人一起解决复杂的问题。
要了解更多阿尼的作品,请前往他的媒体简介,或者在推特上关注他。或者看看他最近的一些 TDS 帖子:
为了支持 TDS 作者的工作并无限制地访问我们的档案,请考虑立即成为媒体会员。
注意:为了长度和清晰度,本次采访稍作编辑
内存数据质量检查——充满期待的教程
将数据质量检查嵌入任何数据管道的实用代码片段
TL;速度三角形定位法(dead reckoning)⏱
我们提供了教程和抽象类来将 Great Expectation validator 嵌入到任何只有五行代码的 Jupyter 笔记本中。Google Colab 中的完整教程在本文中间。
介绍😔
一年半前,我为你的数据分析写了一篇关于数据质量检查的文章——熊猫教程。直到今天,这篇文章已经成为我发表的最好的文章之一,许多人仍然在从事它。这一参与数字反映了数据质量在一段时间内的重要性。
不幸的是,当谈到商业世界时,许多人在演示幻灯片上强调它是多么重要,但他们没有提供足够的资源来实施和维持它。
这种技术数据债务从兴趣点或应用点通过底层数据库流向分析人员,分析人员汇总数字并向管理人员报告。
通常,错误发生在没人注意的地方。它可能来自前端办公室、中间系统或最近部署的计算逻辑。然而,当高管们发现报告中有问题时,他们往往不信任最终结果数据和给他们发送信息的分析师,尽管这可能不是他们的错。这可能只是因为分析师是他们最后一个可以发泄不满的人。

由克里斯蒂安·埃尔富特在 Unsplash 上拍摄的照片
以我个人的经验来看,这就像你时不时的抱着炸弹,不知道什么时候会被触发。运气够好,可以平安回家;否则,有一天你可能会被解雇。
分析师如何在这种情况发生之前避免它?
作业环境🔥
作为数据分析师,我们通常会根据首席技术官的指示使用许多分析工具。例如,工具可以从 Jupyter 笔记本或您首选的 IDE 中的低级语言(python、R、SQL)到 SAP、SAS 等高级企业软件。
最可悲的是,我们通常只能有限地访问分析工具,而不能访问另一个系统。例如,我们不能接触服务器的底层终端,也不能为最近发现的最新工具和技术提供新的服务器。
这种限制让我们的生活变得痛苦,因为我们需要向 IT 解决方案管理部门请求最新的技术,并且一直等到它被实施。
补充介绍一下,你不仅手里拿着炸弹,还得等着有人来救你脱离这种情况。但是,当然,你没有能力阻止这场灾难。所以这是一个非常可怕的情况。

我们有没有一个变通方法可以自己解决这个问题,而不必依赖其他方?
这是🥳的答案
是的,我们可以应对巨大的期望!我给可能不知道什么叫大期待的人介绍一下。 【远大前程】 是用于验证数据和生成数据质量报告的开源工具。
为什么是远大前程?🤔
您可以使用 Pandas、Pyspark 或 SQL 编写一个自定义函数来检查您的数据质量。然而,它要求您维护您的库,并且不利用他人的力量。另一方面,Great Expectation 是一个开源项目,许多功能都非常有用。你不需要重新发明轮子,但是你可以成为社区的一部分,让事情变得更好。所以我们对开源贡献越多,我们的数据社区就会越强大。
对于其他工具,比如 dbt,我也在研究它,发现需要设置一个专用的服务器,或者开发人员必须在您可以使用它之前设置好基础设施。然而,最近的巨大期望提供了一种使用RuntimeDataConnector和RuntimeBatchRequest来验证内存中数据的替代方法。这些对象使得我们在不设置服务器的情况下使用 Great Expectations 功能变得更加容易。
为什么是这个教程?🤔
有很多基本用法的教程都写了如何使用它,但是我很少找到强调RuntimeDataConnector应用的教程。甚至《远大前程》的官方文件也提供了一个例子。我发现它安排得不够好,不足以让我快速实现我的用例(实际上,为了写这篇文章,我花了很多天来理解如何在我的工作笔记本环境中使用它)。因此,我今天在这里写作,并向你展示如何做到这一点,并将代码嵌入到你的任何数据管道。
假定📚
- 下面的实现是在笔记本环境下,比如 Google Colab 或者 Databricks。这种工具代表了在分析环境范围之外你不能做任何事情的情况。
- 此外,因为您处于分析环境中,所以您没有数据库的键访问权;因此,要连接数据,只能使用我们前面提到的
RuntimeDataconnector。 - 您没有权限为报告设置服务器,也没有权限调整永久存储以显示数据质量报告。
从长远来看,那些有权使用传统的“远大前程”方式调整或提升服务器的人将是更好的选择。
我们要做什么?🏃🏻♂️
- 建立一个临时的地方来存放大期望文档,例如 Google Colab 中的临时空间或者 Databricks 环境中的 data bricks 文件系统。
- 设置一个类/函数来验证您的数据,并将其嵌入到您拥有的每个数据管道中。
- 我们的类/函数应该在笔记本中显示质量报告,以跟踪运行时的数据质量。
- 使用永久存储(如 Google Drive、亚马逊 S3、Azure blob 存储等)定期保存报告。
这个解决方案是如何解决问题的?⭐️
现在,您应该考虑一下所提出的解决方案与互联网上的其他教程之间的区别。关键的区别在于,现在我们可以将报告嵌入到正在运行的笔记本中(耶!我不需要等人来为我实现发布的文档服务器)。此外,没有预定义的数据源可以连接(作为一名数据分析师,我没有数据库的钥匙),这意味着你可以将其嵌入到你工作的任何环境中,而不必依赖于其他方的基础设施。
就系统设计而言,这种方法可能不是最好的方法,但对于数据分析师来说,在提交下一份分析报告之前知道他们的数据可能有什么问题就足够了。
对于那些无法想象结果的人来说,这里有一个例子。
假设您使用这个笔记本来执行 ETL 过程。您可以在每次完成时将其导出,并保留历史更改以供将来审核。
辅导的💻
在我们深入了解细节之前,这里有一个示例 Google Colab 笔记本供您参考。
https://colab.research.google.com/drive/1CV69el6lrIHGDx-e9FDT-RWvahWW_7sS?usp=sharing [## 谷歌联合实验室
内存数据质量检查——充满期待的教程](https://colab.research.google.com/drive/1CV69el6lrIHGDx-e9FDT-RWvahWW_7sS?usp=sharing)
快速启动
Google Colab 中的 DataQuality 类提供了对 Great Expectation 库的抽象。我对它进行了简化,以便您可以嵌入五行代码来在运行时验证您的数据。
在 Google Colab 中,我们提供了一个抽象版本和一个详细的教程,以满足巨大的期望。有很多术语,比如 DataSource、DataConnector、Checkpoint 等。,你应该明白利用伟大的期望的力量。这里我们不赘述术语,但是你可以在远大前程文档中找到。有据可查,很快会好起来的。
让我们回到我们的话题。首先,您可以使用下面一行自定义类来启动数据质量检查器。
启动期望套件

使用 customer 类创建数据质量检查器。
上面的代码片段首次使用您提供的数据创建了一个期望套件。您可以从 Great Expectation 使用分析工具自动创建它(参数 with_profiled = true ),或者通过 validator 对象手动调整它。
我们将把所有的期望套件保存在组织好的数据源名称下,当您必须验证一个新的数据集时,您可以沿着目录进行遍历而不会产生混淆。您还可以使用以下三行代码来验证基于现有期望套件的数据。
验证新数据

用现有的期望套件验证您的新数据。
在你的 ETL 脚本的末尾嵌入上述所有内容,就这样。在运行脚本的末尾,您将看到一个 HTML 格式的数据质量报告。
所有上述代码片段只需要您的分析环境中的临时空间来保存结果。之后,你可以定期将《远大前程》文件夹复制到永久存储器,比如 Google Drive、亚马逊 S3 或者 Azure blob 存储器。
尽管本教程示例适用于 spark 数据框,但它也适用于 Pandas 数据框。所以,我会让你自己做,这样你会更熟悉大期望库。
🛠投入运作

在我的工作时间,当我需要创建一个数据产品的原型,并想为生产阶段做计划。我通常添加上面的代码片段来了解我的数据,以及它是否仍然与历史数据相似。
如果您是高级用户,您可以设置更复杂的检查点,例如当数据质量不合格时向电子邮件、slack 或 Microsoft 团队发送通知。当您收到通知时,您可以回到管道并从 Jupyter 运行笔记本中检查日志。与我们通常在操作系统中使用的日志文件相比,它相当不错。
我目前将它与数据砖中的作业调度一起使用,当作业运行时,所有的数据质量报告都将保存在 HTML 输出中,对我来说,调试它更容易,而无需更改日志系统和源代码之间的 IDE。
此外,随着我在这个旅程中工作得越来越多,我将需要维护更多的东西,无论是简单的分析报告还是机器学习模型。在错误发生之前知道它是一件幸事。
如果你的组织没有安排和跟踪工作的工具,我也建议你看一看 MLFlow(这是我过去写的关于它的文章,用 MLflow 改善你的机器学习管道)。您可以使用工件日志的概念来集成 Great Expectation,因此您可以更有效地组织事情。
最后的想法🧡

我们的生命太短暂了,不能每次都修复我们不知道的 bug。我希望本教程能够帮助您将这一伟大的期望融入到日常生活中,让您作为数据分析师的生活变得更加轻松。我觉得这是我认识的每个数据分析师都会遇到的痛苦,我想成为一个让他们的工作生活更快乐的人。直到我们再次相遇🏄🏻♂️.
帕泰鲁什·西达
如果你喜欢我的工作,想支持我, 成为会员
寻找完美的机器学习模型
原文:https://towardsdatascience.com/in-search-of-the-perfect-machine-learning-model-cf4e97b95e64
概率表现和几乎免费的午餐

首席研究员:戴夫·古根海姆博士
简介
在机器学习中,没有免费的午餐定理(NFLT)表明,当每个学习模型的性能在所有可能的问题上平均时,它们表现得一样好。因为这种平等,NFLT 是明确的——预测分析没有单一的最佳算法(机器学习,也没有免费的午餐定理| Brainfuel 博客)。
与此同时,学习模型也有了显著的改进,特别是在打包和提升集合方面。随机森林模型是 bagging 的一个版本,针对 179 个不同的分类器和 121 个数据集进行了测试,发现它们在更多时候更准确(Fernández-Delgado,Cernadas,Barro 和 Amorim,2014 年)。但 XGBoost 算法是 boosting 的一个版本,于 2015 年推出,此后一直统治着其他学习模型(Chen et al .,2015)。它因为创造了许多 Kaggle 竞赛获奖者而获得了这个称号(XGBOOST 是什么?|数据科学与机器学习| Kaggle )。它的部分吸引力在于广泛的超参数调整功能,这些功能允许对不同数据集进行定制拟合( XGBoost 参数— xgboost 1.6.2 文档)。
为此,我们将关注随机森林和 xgboost 模型,将其作为核心的底层集成,我们将向其添加额外的学习模型,从而创建“极端集成”,以找到一个能够很好地概括的模型。这项研究将探索极端系综的默认配置,并将它们与高度调优的 XGBoost 模型进行比较,以确定超调的功效和实际准确性的限制。最后,在“堂吉诃德”时刻,我们将尝试从实际意义上发现一个通用模型,该模型在所有数据集上都处于或接近顶级水平。
研究问题
- 如果我们预测多种未来,而不是只有一种,我们的模型会是什么样子?
2.数据质量如何影响多未来模型性能?
3.超参数调优是做什么的?
4.是否需要超参数调整,或者未调整的模型能否达到同样的性能?
5.如果没有免费的午餐,那么有几乎免费的午餐吗?
触发警告
请查看以下陈述:
- 数据只是另一种机器学习资源。
- 训练/测试分割的随机种子值可以是您想要的任何整数,因此 1、42、137 和 314159 都是好的。
- 超参数调整是数据挖掘过程中最重要的步骤之一,超调整的 XGBoost 模型是最好的全方位机器学习算法。
- Kaggle 竞赛反映了真实的数据科学,准确率为 90.00%的人击败了所有达到 89.99%的人。
如果你认同这些说法中的任何一个,那么这就是你的“红色药丸”时刻——在继续阅读之前,也许你应该找回你的情感支持动物,并泡一杯热茶。
因为系好金凤花,这将是一段颠簸的旅程。
车型
这项研究创建了 21 个模型,从默认的随机森林和 xgboost 到极端集成的发明,其中 bagging 或 boosting 与其他学习算法相结合,以从数据中捕捉更多信息(有关更多信息,请参见图 1-4)。
随机森林或 xgboost 与逻辑回归相结合,用于获取线性信息,和/或与具有径向基核的支持向量分类器相结合,用于发现平滑曲线关系。此外,特征缩放集成被添加到支持向量分类器中,因为它在原始特征缩放研究中表现出优异的性能(特征缩放的奥秘最终被解决|由戴夫·古根海姆|走向数据科学)。特征缩放集成没有与逻辑回归相结合,因为早期的研究仅显示了多项数据的改进(逻辑回归和特征缩放集成|由 Dave Guggenheim |向数据科学发展),并且这里的所有数据都表示二元分类。
对于极端集成,使用投票分类器(sk learn . ensemble . voting classifier-scikit-learn 1 . 1 . 2 文档)或堆叠分类器(sk learn . ensemble . stacking classifier-scikit-learn 1 . 1 . 2 文档)来组合算法的默认配置。
更多详细信息请参见以下模型描述:
1)RF:random _ state = 1 的默认 RandomForestClassifier
2)XGB:random _ state = 1 的默认 XGBoostClassifier
-
RF_XGB VOTE:默认随机森林和 xgboost 与 VotingClassifier 相结合
-
RF_XGB STACK:默认随机森林和 xgboost 与 StackingClassifier 相结合
-
RF_LOG 投票:使用投票分类器的默认随机森林和逻辑回归
-
RF_LOG 堆栈:具有堆栈分类器的默认随机森林和逻辑回归
-
RF_SVM 投票:具有投票分类器的默认随机森林和支持向量分类器
-
RF_SVM 堆栈:具有堆栈分类器的默认随机森林和支持向量分类器
9)RF _ SVM _ 对数投票:默认随机森林、支持向量分类器和具有投票分类器的逻辑回归
10)RF _ SVM _ 对数堆栈:默认随机森林、支持向量分类器和具有堆栈分类器的逻辑回归
-
RF_SVM_FSE 投票:默认随机森林和支持向量分类器,其具有带有投票分类器的特征缩放集成
-
RF_SVM_FSE 堆栈:具有特征缩放集成的默认随机森林和支持向量分类器与堆栈分类器
-
XGB_LOG VOTE:使用 VotingClassifier 的默认 XGBoost 和逻辑回归
-
XGB_LOG 堆栈:使用 StackingClassifier 的默认 XGBoost 和逻辑回归
-
XGB_SVM 投票:默认 XGBoost 和支持向量分类器,带有投票分类器
-
XGB_SVM 堆栈:具有 StackingClassifier 的默认 XGBoost 和支持向量分类器
-
XGB_SVM_FSE 投票:默认 XGBoost 和支持向量分类器,具有与投票分类器相结合的特征缩放集成
-
XGB_SVM_FSE 堆栈:默认 XGBoost 和支持向量分类器,具有与堆栈分类器相结合的特征缩放集成
-
XGB_SVM_LOG 投票:默认 XGBoost、支持向量分类器和具有投票分类器的逻辑回归
20)XGB _ SVM _ 日志堆栈:默认 XGBoost、支持向量分类器和带有堆栈分类器的逻辑回归
- XGB 调优:超调优 XGBoost 模型,使用具有多个调优周期递归试探法。
逻辑回归详情
许多教科书会告诉你选择 l1 或套索正则化而不是 l2,因为它在特征选择方面有额外的能力。他们没有告诉你的是,l1 的运行时间可能是它的 50-100 倍。一个模型组使用 l1 运行了超过 72 小时而没有完成(风暴期间断电);使用 l2 在两个小时内完成了相同的模型集。是的,我现在有一个不间断电源系统。
**低训练样本数(<2000):**LogisticRegressionCV(penalty = " L1 ",Cs=100,solver='liblinear ',class_weight = None,cv=10,max_iter=20000,scoring="accuracy ",random_state=1)
**高训练样本数(>= 2000):**LogisticRegressionCV(penalty = " L2 ",Cs=50,solver='liblinear ',class_weight = None,cv=10,max_iter=20000,scoring="accuracy ",random_state=1)
make _ pipeline(standard scaler(),log_model)
支持向量分类器详情
SVC(kernel = 'rbf ',gamma = 'auto ',random_state = 1,probability=True) # True 启用 5 重 CV
make _ pipeline(standard scaler(),svm_model)
支持向量分类器与特征尺度集成
SVC(kernel = 'rbf ',gamma = 'auto ',random_state = 1,probability=True)
make _ pipeline(standard scaler(),svm_model)
make _ pipeline(robust scaler(copy = True,quantile_range=(25.0,75.0),with_centering=True,with_scaling=True),svm_model)
voting classifier(RF _ SVM _ FSE 显示)
voting classifier(estimators =[(' RF ',rf_model),(' std _ 标准',标准 _ 处理器),(' std _ 罗布',罗布 _ 处理器)],voting = '软')
堆叠分类器(RF_SVM_FSE 显示)
估计器= [('RF ',rf_model),(' SVM 标准',标准 _ 处理器),(' std _ 罗布',罗布 _ 处理器)]
stacking classifier(estimators = estimators,final _ estimator = LogisticRegressionCV(random _ state = 1,cv=10,max_iter=10000))
在更世俗的方面,当一个极端集合包含一个逻辑回归模型时,那么使用“ drop_first = True ”的一键编码被编码。如果不是,那么' drop_first = False '就是标准。
超调 xgboost 分类器
有许多不同的超调算法使用贝叶斯优化(optuna、Hyperopt 等。),但这是一个使用 GridSearchCV 的有趣实现的例子,它递归地迭代精化的超参数。在修改了这种自动调整启发式算法以适应分类问题之后,我测试了这种算法,发现尽管运行时间很长,但它经常实现同类最佳的性能。以下是详细情况:
sylwiaoliwia 2/xgboost-auto tune:在 Python 中自动调优 xgboost 的总结。(github.com)

图 1 基本模型和随机森林极限集合(图片由作者提供)

图 2 更多随机森林极限合集(图片由作者提供)

图 3 XGBoost Extreme 合集(图片由作者提供)

图 4 更多 XGBoost Extreme 系综和超调 XGBoost(图片由作者提供)
但是我们如何确定哪个是最好的模型呢?
介绍性能概率图
单点准确性不一定不诚实,但肯定是不真诚的。数据科学竞赛之于商业分析,就像真人秀之于现实世界一样,因为使用单个随机种子值对数据进行单次分割,就可以预测一个预定义的未来,一个预先编写好的未来。一些数据具有这种远见。但是我们知道方差,一个随机函数,在大多数情况下有其他的计划——也就是引起混乱。
性能概率图(PPG) 展示了更接近真实的情况,其中使用 100 个训练/测试数据的排列生成 100 个模型,同时保持相同的比率(训练/测试和类别不平衡)。生成的直方图不仅展示了新数据的潜在性能,还展示了实现相同性能的可能性(见图 5)。核密度估计器表示概率密度函数,而直方图对应于概率质量函数。
如果您的业务决策需要 87.9%的准确性,即图 5 中的中值,那么您将有 50%的机会是正确的(这种情况与“一半正确”相混淆)。如果你的决策需要 94%,这个模型将实现它,即使只是短暂的。所有剩下的模型都代表分析失败。相反,如果你把你的决策阈值设置为 81%,你的模型在大多数时候都会成功。PPG 是保守的,因为它没有考虑数据漂移,或者新数据如何超出我们当前人口的界限。思考这一现象的一种方式是对于训练/测试分割的每个排列,我们向模型呈现相同的预测器质量但不同的样本质量,并且每个模型将这些属性转换成实现准确度的概率。
作为使用 PPGs 的初步调查,我们将比较箱线图和直方图,以寻找整个范围移动到更高精度的模型,而不仅仅是单个新的异常值结果。但是需要更多的工作来有效地比较这些通常非正态的图。

图 5 性能概率图(图片由作者提供)
本研究中使用了 12 个开源数据集,代表了混合的数据类型和复杂性。这里有全浮点和全整数数据,以及数字和分类类型的融合。提出的一个关键指标是样本与预测因子的比率,或样本比率,因为广义误差、预测因子和使用 Vapnik-Chervonenkis 维数界限的这个方程的训练样本数之间的关系,所以包括这个指标:

图 6 样本大小与使用 VC 维的一般误差(图片由作者提供)
你可以在这里了解更多关于这项技术的内容:slides lect 07 . DVI(rpi.edu)。本系列讲座基于从数据中学习(Abu-Mostafa,Magdon-Ismail,& Lin,2012)。本研究中的数据集可容纳从最低约 12 个样本/预测值到超过 440 个样本/预测值。
将采样误差视为一个物理常数,因为它决定了所有模型的精度上限。采样比率将向我们展示关于 XGB 模型调优(也称为超调)的一些非常重要的东西。
因为 N,期望的样本数出现在等式的两边,如果你在 Excel 中编码,那么你需要在选项菜单中启用“迭代计算”,然后为 N 设置一个种子值以准备解决方案。我建议使用 10 *预测因子的数量作为一个好的起点,这也是加州理工学院推荐的所需最小样本数。哦,如果你有一个大的样本比率和预测数,你将需要在 Wolfram Alpha ( Wolfram|Alpha:计算智能(wolframalpha.com)中执行计算,因为 Excel 不能处理极大的数字。
除电信公司流失外,所有缺失值都用 miss forest(miss forest PyPI)进行估算;它的 11 个缺失值从 7000 多个样本中被删除。所有无信息预测都被丢弃(id 等。).当然,所有数据处理都是通过对训练数据使用“fit_transform”和对测试数据使用“transform”来执行的,每个排列和每个模型都保持了数据分区的完整性。测试规模由三个设定点组成:25%、30%和 50%,以管理样本比例,但仍保持普遍性。
和往常一样,所有的性能结果,所有的 25,200+预测,都是基于测试数据,那些模型还没有看到的样本。
数据集及其预测性能
数据集#1
澳大利亚信贷来源: UCI 机器学习库:Statlog(澳大利亚信贷审批)数据集
样本比率:每个预测值 12.32 个样本
呈现给模型的预测值:float64(3),int64(3),uint8(36)
预期抽样误差:7.36%,置信区间为 90%
根据 Delmaster 和 Hancock (2001 年,第 12 页),该数据集的训练/测试分割被设置为实现二元分类问题所需的最小样本数。68).
有关基线默认随机森林、默认极端梯度提升和默认极端集合的详细信息,请参见图 7。

图 7 澳大利亚信贷基线 RF_XGB 箱线图(图片由作者提供)
请注意 RF 和 XGB 范围之间的重叠,这些是模型相同的性能量。我们可以看到,结合这两个整体并没有提高性能。
默认 XGB 型号的最大 PPG 范围是从 81.5%到 93%,这表明存在数据质量问题。您可以将中位数精度视为预测值质量的代理,将 PPG 离差视为样本质量(即所有观测值的一致性)。大约 88%的中值准确度可能对许多决策很有效,因此预测器可能是好的。
很容易看出,100 次排列中的每一次的超调谐过程都没有将 PPG 缩小到一个很窄的范围内,或者使其更加精确(见图 8);事实上,这与未调整的 XGB 模型非常相似,但四分位间距(IQR)缩小到了随机森林模型。这显示了超调的局限性— 由于改进很小,数据质量差无法从模型中“调整”出来。数据比模型更重要的另一个标志。

图 8 澳大利亚信用 XGB 调谐盒图和 PPG(图片由作者提供)
从所有模型中捕获描述性统计数据,我们将尝试从中找到“最佳”模型,而不使用单点性能值。更多信息参见表 1。

表 1 按模型分类的澳大利亚信贷描述性统计(图片按作者分类)
从每个数据集中过滤 2100 个结果需要设计排序算法来发现“最佳”模型。使用了两种不同的排序方法,并且出现在列表和中与顶级模型的精确度在 0.3%以内(百分之零点三)的任何模型都被选为顶级模型。严格的精度阈值被选择作为实际需求和对新数据的适应性之间的折衷,但这将在即将到来的工作中重新讨论。
排序#1 中位数排序
a.50%从最大到最小
1.IQR 从最小到最大
I .从最大到最小为 75%
排序# 2 75%百分比排序
a.75%从最大到最小
1.IQR 从最小到最大
I .从最大到最小各占 50%
均值排序对于算法选择来说不是一个好的度量,因为 PPG 很少是正态分布,而是具有尖峰,这使得模态量子成为一个更有趣的度量。详细情况请参考图 8。
由于极低的样本数和较差的数据质量,中值范围很广,只有两个模型通过了严格的 0.3%测试(表 2 中的前两个),但更多的模型使用第 75 个百分位排序在下一个量程进行分组:

表 2 澳大利亚信贷最佳表现模型(图片由作者提供)
总体最佳极端系综(XGB_SVM 投票和 XGB_SVM 堆栈)的有趣之处在于,投票具有较窄的 IQR,而堆栈具有较高的中值:

图 9a 澳大利亚信贷最佳表现模型箱线图(图片由作者提供)

图 9b 澳大利亚信贷最佳表现模型 PPG(图片由作者提供)
为了解析数据质量误差和抽样误差之间的区别,就像我们在澳大利亚信贷中看到的那样,数据集#2 将确认数据质量的巨大贡献。
数据集#2
Wisc 诊断来源: UCI 机器学习知识库:乳腺癌 Wisconsin(诊断)数据集
样本比率:13.28
呈现给模型的预测值:float64(30)
预期抽样误差:6.97%,置信区间为 90%
在样本比率仅为~13 的情况下,中值都接近或高于 96%,这表明可能唯一的误差来自于样本比率。与数据集#1 相比,样本质量有所提高,箱线图范围约为 6.5%,这可能是由于这 30 种浮点生物测量值的一致性及其相对相似性。请参见图 10,了解低采样率和良好预测性能数据集的详细信息。

图 10 Wisc 诊断 RF_XGB 基线箱线图(图片由作者提供)
同样,与未经调整的版本相比,超调整的 XGBoost 模型缩小了 IQR,但降低了第 75 百分位。更多信息参见图 11。

图 11 Wisc 诊断超调 XGB 箱线图和 PPG(图片由作者提供)
使用规定的两种排序机制和 0.3%的准确度阈值,这些是由中位数排序确定的该数据集的排名最高的模型:

表 3 Wisc Diag 排名榜(图片由作者提供)
请注意,有一个单一的顶级模型,接下来的八个被分组到一个较低的量子级别,共享的中值为六位小数。总体最佳极限合奏,XGB _ SVM _ 日志投票看起来是这样的:

图 12 Wisc Diag 最佳表现车型 PPG(图片由作者提供)
对于该数据集,尽管样本数量较少,但由于预测器的复杂性导致 100 个模型的运行时间较长(使用英特尔 12700K、32MB DDR4 3200 RAM 和 2TB PCIe4 固态硬盘时超过 3 天),因此对逻辑回归模型采用了 L2 正则化。
这就是如何分析数据集的,所有剩余的数据集将在附录 A 中讨论,以加快我们对完美模型的搜索。
寻找完美的模特
我们收集并分析了所有结果,但在开始搜索之前,我们需要解释图 40–42 中的信息,即:
样本比率:每个预测器的样本比率。
惩罚:用于逻辑回归模型的正则化惩罚。
最差模型:根据排序机制,这是该数据集所有 21 个模型中性能最低的模型。
PPG 最大范围:使用最差模型,这是所有 100 个模型的最大精度范围,从一致性来看表示数据质量。记住——更高的 T4 中值准确度=更好的预测器,更窄的 PPG 范围=样本之间更好的同质性。
击败宣传:这显示了我们的通用模型极限合奏是否胜过超调 XGB 模型。
除了澳大利亚信贷和输血,模型排名中的每个模型与表现最佳的模型相差不超过 0.3%,处于第一位。并非所有型号都出现在这些列表中,因此性能最差的型号可能不会出现在此处。

图 40 完美模型搜索路径 1(图片由作者提供)
快速浏览一下图 40 中的表格,可以发现在这些数据集上没有一个表现最好的模型。从最严格的角度来看,没有免费的午餐定理成立。然而,使用我们严格的 0.3%阈值(同样是 0.3%),有一个通用模型出现在“噪声”之外——XGB _ SVM _ 对数叠加极端系综(见图 42-44)。诚然,澳大利亚信贷和输血是例外,但这是数据质量超过抽样误差的重要一课,在这两种情况下,极端集合仍优于超调谐 XGB 模型。

图 41 完美模型搜索路径 2(图片由作者提供)

图 42 完美模型搜索路径 3(图片由作者提供)
当采样比达到 40.36(spam base 数据集)时,超调 XGB 模型终于出现在了榜首,但它落后于 extreme ensemble。这种定位在电信客户流失中重复出现,但超调 XGB 模型和 extreme ensemble 都没有出现在输血的列表中,输血是一个既有不良预测因素(低中值)又有高样本异质性(宽 PPG)的数据集。尽管没有达到 0.3%的阈值,并且比顶部低一个量程,但 XGB _ SVM _ 对数堆栈模型仍然以 93.5 的采样比率击败了超调优 XGB,因为超调优模型低两个量程。
不过,通过最后两个数据集,超调 XGB 性能优于极端集成,因此该模型似乎更喜欢非常大的采样比。基本上,在每个预测器超过 100+样本的某个点上,超调 XGB 模型优于大多数极端集成。但在此之前,它的总体表现很差,仅在这项研究中的 42%的数据集上名列前茅。另一方面,XGB_SVM_LOG 堆栈极端集合在 85%的时间里达到顶级。调谐 XGB 模型需要比建模所需更多的样本,而极端集合不需要调谐。
没有完美的模型,但这里有两个可能适用于连续系列中的所有数据集——几乎免费的午餐就在这里。从 12 到大约 100+的最小样本比率,使用极端集合,通用模型。100+以上,转超调 XGB 型号。从实际意义上来说,这两个模型应该可以在任何地方处理表格数据。而且不是只有一个注定的未来,而是有许多可能的未来。
特殊奖金数据集
作为额外的检查和测试采样率上限,对 MAGIC Gamma 望远镜数据执行了另一个 2100 模型运行,其采样率为 951: UCI 机器学习库:MAGIC Gamma 望远镜数据集。结果见图 43:

图 43 MAGIC Gamma 性能结果(图片由作者提供)
同样,由于采样比率非常高,超调 XGB 模型是最好的模型,但通用模型 XGB_SVM_LOG 堆栈仍然使用 0.3%阈值的中值排序机制进入了短名单。
extreme ensemble 有计算成本,需要用低级语言重新编码以提高速度,就像 XGB 一样。
结论
这项研究打开了极端组合的大门,还需要更多的工作来探索这些算法组合。此外,需要更多的研究来开发性能概率图之间的比较分析,也许是基于它们从建模过程中分离误差成分的独特能力。
也就是说,假设采样误差为“常数”,PPG 允许我们将模型误差区分为两个不同的组:
1)预测器质量以中值准确度衡量。
2)通过 PPG 系列本身测量的样品质量(一致性)。
更高的中值精度由单个模型决定,并且受到采样误差和预测器质量的影响。虽然数据质量是存在的,但它是通过中值计算“平均”出来的。
相反,更宽的 PPG 范围是由样本一致性(一种数据质量形式)决定的,因为模型精度之间的差异是由数据划分的差异(即方差的产生)预见的。这种误差源的分离可以指导未来的行为。例如,银行营销数据集的 PPG 最大范围为 0.46%,因此训练/测试分割无关紧要,因为这些数据高度相似。了解这一点将有助于我们专注于提高预测器的质量,因为这是妨碍更好性能的误差。
由于 PPG 的范围很窄,我们可以回到决策阈值的单点精确度,因为大量可能的未来已经崩溃为那些密切相关的。
以下是其他一些重要的要点:
1.数据质量是首要的指示,是道德的要求。花时间获取更好的数据,而不是探索另一种算法,因为如果数据质量差,所有算法都会有学习障碍。
2.超调一个 XGB 模型只能在大采样率的情况下提供高性能,因为调优需要的样本远多于建模;在那之前,它是一个表现不佳的模型。但是如果你有那么大的抽样率,那么这就是可以使用的模型。
3.有一个极端的系综,XGB _ SVM _ 日志堆栈在这项研究的 12 个数据集的 10 个中表现最佳——几乎免费的午餐。此外,它还登上了 Magic Gamma 数据集的入围名单——这是又一次确认,使它在 13 个获奖者中占了 11 个。
计划中的研究将探索超调 XGB 模型持续处于顶级的样本比率。鉴于极端集合中的三个模型中有两个对异常值具有鲁棒性,其他工作正在设计中,以调查 PPG 最大值范围的样本质量来源。
要点:如果您不知道您的 PPG 的宽度,那么输入一个随机的种子值进行数据分区就是掷骰子,纯粹是为了让您的模型在未来得到适当的定位。
参考文献
Abu-Mostafa,Y. S .、Magdon-Ismail,m .、和 Lin,H.-T. (2012 年)。从数据中学习(第 4 卷)。美国纽约 AMLBook:
陈、汤、何、贝内斯蒂、米、霍季洛维奇、唐、赵、h……其他。(2015).Xgboost:极限梯度提升。 R 包版本 0.4–2, 1 (4),1–4。
德尔马斯特和汉考克(2001 年)。数据挖掘解释。马萨诸塞州波士顿:数字出版社
Fernández-Delgado,m .,Cernadas,e .,Barro,s .,和 Amorim,D. (2014 年)。我们需要数百个分类器来解决现实世界的分类问题吗?《机器学习研究杂志》, 15 (1),3133–3181。
附录 A
数据集#3
Lending Club 来源:所有 Lending Club 贷款数据| Kaggle
样本比率:15.35
呈现给模型的预测值:float64(4),int64(1),uint8(15)
预期抽样误差:90%置信区间时为 6.38%
缺失值插补:缺失森林

图 13 Lending Club 基线 RF_XGB 箱线图(图片由作者提供)
最后,超调 XGB 模型已经将整个 PPG 范围移到更高的地面。详情参见图 14。这足以将超调 XGB 模型转移到 0.3%阈值内的最高性能列表中,尽管是在最后一位(见表 4)。

图 14 借贷俱乐部 XGB_TUNE Boxplot 和 PPG

表 4 Lending Club 排名靠前的车型(图片由作者提供)
尽管超调模型有所改进,但这是一个表现最佳的极端组合模型。参见图 15,了解 RF_LOG 堆栈性能概率质量函数。

图 15 借贷俱乐部最佳表现模式(图片由作者提供)
数据集#4
德国信用来源: UCI 机器学习知识库:Statlog(德国信用数据)数据集
样本比率:16.67
呈现给模型的预测值:int64(30)
预期抽样误差:90%置信区间时为 6.31%
全整数预测器显示了随机森林模型相对于 XGBoost 的明显优势(见图 16)。过度调整 XGB 模型并没有改善结果的范围,只是略微缩小了四分位数范围(见图 17)。

图 16 德国信贷基线 RF_XGB 箱线图(图片由作者提供)

图 17 Lending Club XGB_TUNE Boxplot 和 PPG(图片由作者提供)
数据集#5
HR 流失来源: IBM 流失数据集| Kaggle
样本比率:19.34
呈现给模型的预测值:int64(19),uint8(19)
预期抽样误差:5.99%,置信区间为 90%
采样比率再次增加,数据类型现在是整数和二进制预测值的均匀混合。在基线箱线图中,极端系综显示出最好的性能,投票分类器挤掉了堆叠分类器;投票运行速度比堆叠快得多,所以当你可以选择一个投票极端合奏时,推荐。详情参见图 18。

图 18 HR 流失基线 RF_XGB 箱线图(图片由作者提供)
超调 XGB 模型确实限制了最小-最大距离,并且与默认模型相比,它将 IQR 压缩到更窄的范围内,所有这些都是准确性和稳定性提高的迹象(见图 19)。即便如此,超调模型也没有在这个阈值为 0.3%的数据集上排名第一(见表 6)。

图 19 HR Churn XGB 调谐盒图和 PPG(图片由作者提供)

表 6 人力资源流失排名靠前的模型(图片由作者提供)

图 20 人力资源流失最佳表现模型 PPG(图片由作者提供)
数据集#6
ILPD: UCI 机器学习知识库:ILPD(印度肝病患者数据集)数据集
样本比率:26.5
呈现给模型的预测值:float64(5),int64(4),uint8(2)
预期抽样误差:4.84%,置信区间为 90%
不考虑模型,该数据集显示了非常强的模式和弱的预测器以及宽的 PPG 范围(更多细节见图 22 和 23)。请注意,默认的随机森林模型在这个数据上胜过了默认的 XGB 模型。

图 21 ILPD 基线 RF_XGB 箱线图(图片由作者提供)

图 22 ILPD XGB 调谐盒图和 PPG(图片由作者提供)

表 7 ILPD 排名靠前的车型(图片由作者提供)

图 23 ILPD 最佳表演模特 PPG(图片由作者提供)
剩余的数据集将只作为结果呈现给限定的文本。
数据集#7
NBA 新秀: NBA 新秀|卡格尔
样本比率:34.97
呈现给模型的预测值:float64(18),int64(1)
预期抽样误差:4.43%,置信区间为 90%

图 24 NBA 新秀基线 RF_XGB 箱线图(图片由作者提供)

图 25 NBA 新秀 XGB TUNE Boxplot 和 PPG(图片由作者提供)

表 8 NBA 新秀顶级模特(图片由作者提供)

图 26 NBA 新秀最佳表演模特 PPG(作者图片)
数据集#8
Spambase: UCI 机器学习库:Spambase 数据集
样本比率:40.36
呈现给模型的预测值:float64(55),int64(2)
预期抽样误差:4.41%,置信区间为 90%

图 27 Spambase 基线 RF_XGB 箱线图(图片由作者提供)

图 28 Spambase XGB 调谐盒图和 PPG(图片由作者提供)

表 9 Spambase 排名靠前的模型(图片由作者提供)

图 29 Spambase 顶级表演模特 PPG(图片由作者提供)
数据集#9
电信客户流失:电信客户流失|卡格尔
样本比率:78.25
呈现给模型的预测值:float64(2),int64(2),uint8(41)
预期抽样误差:3.24%,置信区间为 90%

图 30 电信客户流失基线 RF_XGB 箱线图(图片由作者提供)

图 31 电信客户流失 XGB 调谐盒图和 PPG(图片由作者提供)

表 10 电信客户流失排名模型(图片由作者提供)

图 31 电信客户流失最佳表现模型 PPG(图片由作者提供)
数据集#10
样本比率:93.5
呈现给模型的预测值:int64(4)
预期抽样误差:2.62%,置信区间为 90%

图 32 输血基线 RF_XGB 箱线图(图片由作者提供)

图 33 输血 XGB 调谐盒图和 PPG(图片由作者提供)

表 12 输血顶级模型(图片由作者提供)
数据集#11
成人收入: UCI 机器学习知识库:成人数据集
样本比率:246.67
呈现给模型的预测因子:int64(6),uint8(60)
预期抽样误差:1.18%,置信区间为 90%
注意:预测值“国家”被删除,因为许多国家的样本太少。

图 34 成人收入基线 RF_XGB 箱线图(图片由作者提供)

图 35 成人收入 XGB 调盒图和 PPG(图片由作者提供)

表 13 成人收入最高的模型(图片由作者提供)

图 36 你会选择哪个决策阈值?(图片由作者提供)
第 12 号数据集
银行营销: UCI 机器学习知识库:银行营销数据集
样本比率:443.25
呈现给模型的预测因子:int64(7),uint8(44)
预期抽样误差:1.10%,置信区间为 90%

图 37 银行营销基线 RF_XGB 箱线图(图片由作者提供)

图 38 银行营销 XGB TUNE Boxplot 和 PPG(图片由作者提供)

表 14 银行营销顶级模型(图片由作者提供)

图 39 银行营销最佳表现模型 PPG(图片由作者提供)
在 Power BI 漩涡中——理解循环依赖
循环依赖错误是 Power BI 数据建模中最讨厌的事情之一!了解为什么会发生这种情况以及如何避免

作者图片
通过阅读我以前的文章,您可能已经理解了,创建星型模式并不一定意味着您的数据建模任务已经完成。还有很多方面需要注意——虽然你可以偷偷摸摸地不去微调每一个细节——但其中一些“细节”当然更重要,需要更好地理解。
其中一个很容易把你拉入“漩涡”的“细节”,叫做——循环依赖!我很确定我们所有人至少有一次(希望只有一次)对这个消息感到恼火:“检测到一个循环依赖…”
但是,在我们继续解释什么是循环依赖以及为什么您应该关注它之前,让我们首先了解…
依赖到底是什么…
依存性顾名思义就是一个事实,一个事物(或事件)依赖于另一个事物的行为。
我给你举个简单的例子:比如说,除了其他因素外,公共汽车票价取决于燃料价格。这意味着,如果燃油价格发生变化,公共汽车票价也将发生变化。
Bus Ticket Price = [Fuel Price] + [Factor A] + [Factor B] + ... [Factor N]
这就是所谓的规则依赖,它存在于每一种编程语言中(也存在于日常生活中)。但是,如果公交车票价格发生变化会怎么样呢?这是否一定意味着燃料价格也会改变?不要!公交票价取决于燃油价格,而不是相反!
我为什么问这个?因为在某些情况下,两个事实或事件之间可能存在相互依存关系。
假设您想要降低燃料桶的运输成本。实现这一目标的一个潜在步骤是降低燃料成本(因为燃料成本会影响整体运输成本)。因此,为了降低运输成本,我们需要降低燃料成本。同时,为了降低燃料成本,我们需要降低燃料的运输成本。这就是循环依赖…
如果事实 A 依赖于事实 B,而同时事实 B 也依赖于事实 A,我们说的就是循环依赖!
好了,现在我们知道了什么是循环依赖,让我们研究一下为什么它会出现在 Power BI 中,以及我们如何消除它。正如您可能会想到的,Marco 和 Alberto 已经写了一篇关于 DAX 中循环依赖内部的优秀文章,所以我鼓励您去阅读它,以便更好地理解这个“特性”。
让我们运行一个非常基本的用例,在 CALCULATE()函数的帮助下,在我们的数据模型中创建一个计算列。

作者图片
正如您可能在上面的插图中看到的,我有一个简单的数据模型,由两个表组成:事实表 Bets,它存储关于我们的客户所下赌注的数据;以及保存竞争属性的竞争维度表。
假设我想用额外的 computed 列来丰富我的数据模型,它将只计算比赛英超联赛的下注额。简单的工作!
Bets Premier League =
CALCULATE(
SUM(Bets[Amount]),
Competition[Competition Name] = "Premier League"
)
应用后,我的下注表看起来是这样的:

作者图片
很酷,对吧?我们确信这是可行的,所以让我们尝试创建另一个相同的列:

作者图片
等等,什么?!这不是一分钟前完美运行的公式吗?
让我们从数据模型中删除这两个新列,切换到 DAX Studio,并尝试理解公式引擎生成的查询计划:

作者图片
请注意逻辑查询计划的第 1 行,它告诉我们新列依赖于 Bets 表中所有现有的列!我们期望新的列 Bets Premier League 依赖于 CompetitionID,这是我们的竞争表的外键,我们在这里应用我们的过滤。但是,事实并非如此。为什么会这样?!
当我们创建一个计算列时,我们的表达式被逐行计算(行上下文)。当我们在行上下文中使用 CALCULATE 时,该函数应用一个上下文转换,因此我们在范围内的所有列上都有一个过滤器!
简单来说,这是我们计算的伪代码:
Calculation: Bet Amount
--Filters
Bets[Bet ID] = 333
Bets[Bet Date] = 2021-11-06
Bets[Customer ID] = 2
Bets[Competition ID] = 789
Bets[Amount] = 200
正如你可能已经看到的那样,这很好。但是,当我们想用相同的公式创建另一列时,会发生什么呢:

作者图片
和前面的例子一样,新列依赖于 Bets 表中的所有原始列,但是,它还依赖于我们之前创建的 Bets Premier League 列!由于该列是在刷新时计算的,Power BI 无法解决这种依赖性,并抱怨循环依赖性。
简单地说,如果您能够在数据模型中创建这两列,那么一旦您刷新数据集,就会发生以下情况:
栏目投注英超将依赖于所有来源栏目+投注英超新栏目。另一方面,Bets 英超联赛新列将依赖于所有源列+Bets 英超联赛列。这就是 Power BI 拒绝创建这样一个模型的原因,它在两个计算列之间有循环依赖。
通过扩展原始列表达式并使用 REMOVEFILTERS 函数删除所有由于上下文转换而应用的过滤器,可以很容易地解决这个问题:
Bets Premier League NEW =
CALCULATE(
SUM(Bets[Amount]),
Competition[Competition Name] = "Premier League",
REMOVEFILTERS(Bets[Bets Premier League])
)
一旦应用,Power BI 不再抱怨循环依赖,新列成为数据模型的一部分:

作者图片
好的,我们解决了这个难题,但是让我向您展示如果我尝试在维度表中而不是在事实表中用相同的公式创建一个计算列会发生什么。
我将转到“竞赛”表,并粘贴“下注英超联赛”计算列的代码:

作者形象
这里到底发生了什么?!以前导致“循环依赖”错误的相同代码,现在工作起来像一个符咒!
问题在于,当表中有一列具有唯一值时(在我们的例子中,竞争 ID 是竞争表的主键),上下文转换被优化以避免过滤除具有唯一值的列之外的所有其他列。在竞争表(位于关系的一侧)和下注表之间建立 1:M 关系时,引擎确认了该列的唯一性。
然而,正如在 SQL BI 的本文中所解释的,这种在维度表中创建计算列以避免循环依赖问题的“技术”不是推荐的做法,因此应该尽可能避免。
结论
循环依赖是 Power BI 数据建模过程中最恼人的事情之一!每当您创建两个相互依赖的对象时,您都有遇到此问题的风险。有时,确定根本原因可能是一项简单的任务,但在某些情况下,有必要了解 DAX 做事方式的细微差别。
谢谢你的阅读!
使用 vs 代码片段提高您的工作效率
原文:https://towardsdatascience.com/increase-your-productivity-with-vscode-snippets-6055c6fa7b4f
和三个有用的 Python 数据科学片段

穆罕默德·拉赫马尼在 Unsplash 上拍摄的照片
Visual Studio 代码(VSCode)是当今最具可定制性和生产力的代码编辑器之一。使用代码片段是提高工作效率的好方法— 但是你知道你可以创建自己的代码片段吗?本教程将探讨如何创建您自己的代码片段来提高您的数据科学生产力!
VS 代码中的片段是什么?
代码片段是可重用编码模式的模板。它们让你自动化键盘命令,甚至让你在不同的参数间循环。
这个更好直观的解释,我们来看看:

VSCode 中的代码片段非常有趣!(来源:作者)
片段可以是简单的文本扩展。但是正如上面的例子所示,你甚至可以通过不同的扩展参数循环!
深信不疑?让我们看看如何创建我们自己的片段!
创建您自己的片段
VS 代码不仅让你创建自己的代码片段,甚至让你定义它们适用于哪种语言。要开始制作自己的代码片段,您可以按照以下步骤操作:
- 打开命令面板,搜索
Snippets: Configure User Snippets。 - 选择
python.json为 Python 创建代码片段。
这将打开以下编辑器,它为您提供了一个创建代码片段的模板:
我们可以看到,每个片段都有一个名称,然后放在一个字典中,有以下几个 kets:
prefix:使用键盘命令body:片段的内容description:描述代码片段本身的字符串
通过使用$1值,您可以插入占位符,以显示光标指向的位置。这些占位符可以从 1 开始重复出现。$0指占位符的结束位置。
让我们从创建更多的代码片段开始!
Python 和 Pandas 的示例片段
下面是三个片段,可以激发你的创造力,创造你自己的创意:
修改熊猫专栏
我们可以通过将熊猫列重新分配给它自己来修改它。就我个人而言,我觉得写出来有点乏味,尤其是对于较长的列名。下面的代码片段简化了这个过程:
该代码片段允许您只编写一次数据帧名称和列名称,然后跳转到行尾。

修改熊猫专栏片段(来源:作者)
过滤熊猫数据帧
我们可以通过使用不同的修饰符来过滤熊猫数据帧,例如==或>=。VSCode 允许您通过用||字符将它们包装起来,将它们定义为下拉菜单。
让我们看看这个是什么样子的:

轻松过滤熊猫专栏(来源:作者)
if name == "main "
考虑到这种情况的普遍性,编写这个保护命令会让您想知道为什么以前从来没有使用过代码片段!
这看起来像下面这样:

简化 if name …语句(来源:作者)
结论
VSCode 片段允许您简化重复的代码。然而,它们也远远超出了文本扩展器,让您可以选择使用占位符,您可以轻松地通过 tab 键。如果你发现自己一遍又一遍地写同样的代码,我鼓励你自己做!
使用否定处理提高情感分类的准确性
一种快速有效提高情感分类准确率的新方法

介绍
外行人的想法是,情感分类模型的准确性主要取决于 ML/DL 模型本身的质量和功能。然而,这并不完全正确。数据质量及其对模型的可用性是任何预测驱动任务的主要定义特征。全世界的数据科学家至少有 80%的时间花在数据角力上(如蓝鼎 AI CEO 吴恩达所说)。探索性数据分析、特征提取、去除噪声、为数据选择正确的模型,然后将数据准备好作为模型的输入,是任何旨在从数据中获得洞察力的过程中最耗时的步骤。此外,这些甚至发生在选择任何机器学习或深度学习算法之前,这在本质上可能变得极其复杂。
这里所做的研究表明,在采用否定处理(这提高了数据的质量)之后,使用简单算法如逻辑回归和朴素贝叶斯分类器的情感分类的准确性显示出与复杂的单词嵌入模型相当的结果。
方法学
我的 Github repo 中提供了求反处理程序的功能。下面显示了一个函数输出的示例。“否定”是对标记化句子调用的主要函数,如图所示。

作者图片
在该函数中,每当遇到否定词(如“not”、“n't”、“non-”、“un-”等),就会为否定词旁边的词生成一组认知同义词,称为同义词集。这些同义词集在一个名为 WordNet 的词汇数据库中通过概念语义和词汇关系相互连接。WordNet 是 NLTK python 库的一部分。创建完同义词集后,该方法检查 WordNet 中是否存在该单词的反义词。
如果一个反义词不存在,这意味着这个词要么是一个动词,要么是一个实体,在 WordNet 中没有对应的词。所以,它被保留了,句子的上下文也被保留了。但是如果一个反义词存在于词汇数据库中,那么就会创建一个反义词列表。然后使用函数 wup_similarity 找到这些反义词中的每一个的相异系数。两个单词的相异系数是:-
相异度=(1—word 1 . wup _ similarity(word 2))
在所有的反义词中,具有最高相异度的一个被考虑,并且否定之后的第一个词被替换为该反义词。然后否定词被去掉,结果我们得到一个极性相反的句子。用最不相似的反义词替换确保了极性最大程度的反转。这样就防止了“漂亮”这样的词被“不好”这样的东西代替,这是一个间接的反义词。考虑如下所示的另一个示例:

作者图片
现在,什么是同集?
它是一种特殊的接口,驻留在 NLTK 库中,帮助在 WordNet 词汇数据库中查找单词。同义词集实例是表达相同概念的同义词的组合。与训练复杂的模型或使用大量的单词嵌入相比,使用同义词集进行否定处理提供了一种更简单的方法来找到不同单词之间的关系,这需要更高的处理能力和其他资源。
结果和分析
使用朴素贝叶斯分类器、随机森林分类器和 GloVe 进行了大量实验。然而,这里的目标是强调数据质量对情感分析的影响,即使是在使用最简单的机器学习模型时。因此,在下面的观察中,我们将主要关注逻辑回归和朴素贝叶斯分类器。

作者图片
从上表可以看出,使用否定处理函数后,精确度提高了。这里要注意的要点是,只有通过提高数据的质量,我们才能够提高情感分类的准确性,而不需要对 ML/DL 管道进行任何改变。
所使用的数据集是 IMDB 电影评论数据集,它具有 50000 个带有二元情感的带标签的电影评论。数据集可在 Kaggle 上获得【来源:此处。表 1 显示了使用逻辑回归计算的观察值。

不包含否定处理的模型的二元分类(逻辑回归)结果的可视化(图片由作者提供)

结合否定处理的模型的二元分类(逻辑回归)结果的可视化(图片由作者提供)
正如在上面的条形图中观察到的,当使用否定处理函数时,包含矢量器和简单算法(如逻辑回归)的模型显示出与单词嵌入模型几乎相等的性能。
结论

作者图片
数据质量是情感分类中最重要的因素之一。当与适当的模型结合时,好的数据可以给出好的结果。对于情感分析的工业应用,需要以最小的成本和最大的资源利用率获得快速的周转时间,使用繁重而复杂的算法通常会导致大量的开销和资源。我们不应将时间和资源花费在复杂的 ML 和 DL 算法上,这些算法象征性地位于数据管道的最后一段,而应专注于数据管道的第一段,即准备数据和提高数据质量。这里实现的函数做的完全相同,并且将数据质量提高到一定程度,结果几乎可以与复杂的算法相比,但是使用更少的资源和更简单的机器学习模型。
参考文献—
- 单位 Lal 和 P. Kamath,“使用 WordNet 词汇数据库中的同义词集进行情感分类的有效否定处理方法”,2022 年第一届电气、电子、信息和通信技术国际会议(ICEEICT),2022 年,第 01–07 页,doi:10.1109/icee ICT . 536767676
- 数据来源(许可证:CC BY 4.0):安德鲁·l·马斯、雷蒙德·e·戴利、彼得·t·范、黄丹、安德鲁·y·Ng、克里斯托弗·波茨。2011.学习用于情感分析的词向量。在第 49 届计算语言学协会年会会议录:人类语言技术,142-150 页,美国俄勒冈州波特兰市。计算语言学协会
通过利用混合管道、并行化和 GPU 加速来提高复杂模型的速度
理解大数据
通过利用混合管道、并行化和 GPU 加速来提高复杂模型的速度
如何选择正确的工具来处理复杂的模型
数据科学正面临着对 CPU 周期的巨大需求,因为科学家们试图处理复杂性增长速度超过摩尔定律的数据集。考虑到快速迭代和重新训练的需要,几年来,模型复杂性已经超过了可用的计算资源和 CPU,并且这个问题正在快速增长。数据科学行业将需要采用并行化和 GPU 处理来高效利用日益复杂的数据集。
鉴于最新模型越来越复杂,任何在大数据集上进行机器学习(ML)的企业最终都将面临这一挑战。像 AlexNet 这样的模型有 61M 的参数和超过 600M 的连接。尝试将其与 ImageNet 中的 120 万幅图像进行对比,你会遇到一个巨大的计算挑战。AlexNet 是下图中最简单的模型之一——想想今天的 SOTA 模型需要什么。
问题是计算需求——这是减缓模型训练的原因。计算需求是基于模型复杂性和数据集大小的函数,用于训练复杂模型的 CPU 需求的增长速度超过了摩尔定律。如果我们看看从 AlexNet 到 AlphaGo Zero 的计算需求,我们会看到一个指数级的增长,三至四个月翻一番。相比之下,摩尔定律有两年的倍增期。自 2012 年以来,最新模型的计算需求增长了 300,000 倍以上——摩尔定律只会产生 7 倍的增长。

从 2013 年的 AlexNet 到今天的 AlphaGo Zero,计算需求不断增长的图示;数据点的指数拟合给出了 3.43 个月的倍增时间,如 Kozma,Robert & Noack,Raymond & Siegelmann,Hava。(2019).受大脑能量管理启发的情境智能模型。567–572.10.1109/SMC。18969.868686868617
为任务找到合适的框架
当您训练一个预测模型时,您会经历从预处理到模型训练、模型验证和操作化的阶段。传统的方法是端到端地使用 Spark,投入更多的计算资源来解决问题,然后等待结果。但是,如果您为管道的不同部分使用不同的框架,您可以划分工作,并为每个工作使用最好的工具来处理每个阶段。
混合管道应对现代挑战
如果您试图围绕一个单一的工具来编排您的整个模型生命周期,您将会受到“这个工具能做什么?”心态。切换到混合管道可以让您自由地为每个阶段选择最佳和最有效的工具。维护集群也非常昂贵。如果您有一个包含 50 个工作节点的本地 Spark 集群,您的管理员会希望将尽可能多的工作负载放在该集群上,因为企业需要支付其计算和维护费用。这反过来意味着群集可能会被大量利用并且资源有限。
另一方面,如果您每周或每月都在重新培训您的模型,而不是维护那些在您需要之前会耗尽资金的资产,您可以寻找一些方法来启动集群,然后根据需要取消配置。例如,如果您每天收集数据,每周重新训练模型,您可以设置一个 GPU 加速的 Spark 集群,该集群在周日自动供应,执行管道,并在完成后取消供应。这不仅可以节省资金,因为您只需在需要时为集群付费,更重要的是,使用 GPU 加速可以保证集群在更短的时间内完成流水线,从而节省更多资金。以下是如何设置这一切的方法:带有 GPU 加速的按需 Spark 集群
Spark 是加州大学伯克利分校创建的通用计算引擎,是并行大数据处理领域的公认领导者。SparkSQL 引擎是其他任何引擎都无法比拟的。但是 Spark 的 ML 库不像其他框架那样先进,而且由于其特定的数据处理范例和 API,Spark 的学习曲线有些陡峭。从这个意义上说,Spark 生活在自己的世界里,而像 Dask 和 Ray 这样的挑战者一直在稳步增长。
与 Spark 相反,Dask 最初的设计原则是“不要发明任何东西”。这个决定背后的想法是,一直使用 Python 进行数据分析的开发人员应该对使用 Dask 感到熟悉,并且加速时间应该是最短的。Ray 是加州大学伯克利分校的另一个项目,由两个主要组件组成——Ray Core,这是一个分布式计算框架,以及 Ray 生态系统,广义而言,这是许多与 Ray 打包在一起的特定于任务的库(例如,Ray Tune——一个超参数优化框架,用于分布式深度学习的 RaySGD,用于强化学习的 RayRLib 等。)
Spark 仍然非常适合 ETL 工作负载,但 Ray 更适合强化学习等特定任务,Dask 在对 Pandas DataFrames 和 NumPy 数组的开箱即用支持方面处于领先地位。你需要能够挑选最好的。
关于框架之间的差异,请参见: Spark、Dask 和 Ray:选择正确的框架
加速速度
由于大多数公司使用的是遗留的基础设施,使用多种工具和框架还不是一种常见的做法。如果您有一个 Spark 集群需要它来管理,那么您可能不得不使用 Spark——获取其他任何资源都是一个 IT 难题。但是在数据科学中,你需要敏捷,如果到了紧要关头,要有快速失败的自由。
数据科学是一个非常动态的领域。如果您想尝试一种新的并行处理框架,您不希望等待 6–7 周的时间来调配和配置集群。最坏的情况是,如果您的想法在使用 IT 资源并等待数周后仍未成功,您将失去所有时间,并且您在公司的信誉可能会受到影响。你想让尝试新方法变得容易。
此外,要解决计算量大的问题,不能只依靠快速 CPU 和混合流水线。对于像网格搜索和超参数调整这样的任务,您需要成百上千次地重新训练您的模型。你可以并行计算,或者使用 GPU 加速,或者两者兼而有之。如果不使用并行,您将需要越来越大的计算实例。不是一天重新训练你的模型,而是一个月。这对模型速度不好。
转向并行和 GPU 处理
对于 ML 项目,许多问题是基于矩阵的代数计算,有大量简单的数学计算。GPU 的一个优势是,它不是有十几个或二十几个核心来计算值,而是有数百个核心,可以非常快速地进行非常基本的计算。这是一个非常适合 ML 工作负载的解决方案——您可以将矩阵数学卸载到 GPU,同时使用 CPU 进行更多的顺序计算。麻省理工学院的 Neil C. Thompson 和 Kristjan Greenewald 在深度学习的计算极限中写了 CPU 的挑战和 GPU 的优势。
人们使用 GPU 进行模型训练已经有一段时间了。但是,试图建立一个可以协调多台机器和 GPU 加速的基础设施是非常复杂的——编写代码来分布和管理多台机器上的 GPU 执行真的很难。这里的创新在于,我们创建了特定的加速器来管理分布式并行化框架。NVIDIA 为 Apache Spark 推出了 RAPIDS 加速器,为 Spark 增加了 GPU 加速。Dask 和 Ray 等框架现在可以处理并行化,并与 Tensorflow 和 PyTorch 等 GPU 框架集成,以提供并行化的 GPU 执行。我们能够以前所未有的方式将并行化和 GPU 加速结合起来。驾驭 GPU 加速的分布式处理的能力对于在合理的时间内打破训练复杂模型的限制变得至关重要。
结论
展望未来,模型的规模和复杂性只会增加。整个行业中的公司并不是因为新的和新奇的东西而试图得到模型驱动,他们这样做是因为竞争压力让他们别无选择。
如果您想成为一家领先的公司,您应该采取一种方法,通过对您的数据进行分区,使用 GPU 设置多台机器以闪电般的速度处理代数,并部署一种支持您轻松供应和管理不同框架和工具的能力的基础架构,来充分利用当前和新兴的工具。
当您准备好采取下一步措施来加速您的数据科学计划时,请考虑您将如何实现一个由 GPU 加速支持的分布式处理混合管道。当您着手下一个项目时,您可以考虑每种方法的不同功能,并考虑在没有太多管理或 IT 难题的情况下合并多个框架的方法。通过考虑我上面讨论的要点,您将能够使用越来越复杂的模型交付及时的结果。
Power BI 中的增量更新—您需要知道的一切
厌倦了缓慢而低效的数据刷新过程?了解如何利用增量刷新功能来加速和优化数据刷新

作者图片
你有多少次吹嘘过这样的事情:哦,不,我需要刷新我的事实表,但是花的时间太长了…可能是因为你没有使用数据刷新过程中的一个关键性能特性。
让我们试着简单解释一下增量刷新的概念。如果你认为增量刷新是 Power BI 独有的东西,那你就大错特错了!这种方法已经存在了几十年,基本上在每个传统的数据仓库解决方案中都有实现。
增量刷新是在上一个数据加载周期之后加载新数据或更新数据的过程!
在这个定义的背后,有各种重要的方面需要理解。首先,为什么增量刷新如此重要?假设您有一个巨大的事务数据库,其中主表包含数百万行。为了使您的分析工作负载保持同步,您需要将数据从事务数据库加载到数据仓库中。所以,想象一下,如果每次都加载整个巨大的表,会发生什么?!确切地说,它将消耗大量资源,还可能对事务数据库产生影响:

作者图片
因此,您应该只检查和消耗那些在最后一次刷新过程之后到达或更改的记录!这大大减轻了事务数据库的压力,因为只需要处理一小部分数据。
假设您在昨天午夜同步了数据。我们将只检查同时更新或插入的记录(可能有几千行),然后只同步这一小部分,而不是再次提取整个数百万的表:

作者图片
增量更新的优势
在您的 Power BI 解决方案中实施增量更新有多种优势:
- 刷新速度更快——显然,随着数据量的减少,数据加载过程会运行得更快
- 降低资源消耗—同样,处理较小的数据块将有助于您降低内存消耗,并更高效地使用其他电源 BI 和数据源系统资源
- 可靠的更新流程—如果您决定“全包”并反对增量更新,长时间运行的连接可能会变得脆弱和不可靠。增量刷新消除了这一挑战
- 设置简单—只需点击几下鼠标,您就可以为数据集定义增量刷新策略
电源 BI 中的增量刷新
一旦您将数据模型发布到 Power BI Service,每个表都包含一个分区。该分区包含所有的行,正如我们已经解释过的那样,对于大型表来说,数据刷新可能会非常困难。
当您配置增量刷新时,Power BI 将自动对您的表进行分区—一个分区将包含必须频繁刷新的数据,而另一个分区将保存不变的行。
用最简单的方式来说,这就是增量刷新后工作流的样子:

作者图片
您可能会注意到,比较前一个和当前数据窗口,窗口是滚动的,在前一个窗口中被视为实时的数据现在成为增量刷新分区的一部分。这是一个持续的过程,被称为“滚动窗口模式”。
增量刷新的先决条件
为了能够在您的 Power BI 解决方案中实施增量更新功能,需要具备一些先决条件:
- 日期列 —要应用增量刷新的表必须包含日期列,可以是日期/时间或整数数据类型。这是因为您需要设置用于在分区之间分离数据的参数
- 查询折叠 —现在,您可能会问自己:什么是查询折叠?所以,我们先来解释一下这一条。我已经在这篇文章中更详细地描述了它,但是,简单地说,查询折叠是 Power Query 生成单个 SQL 查询的能力,该查询将在 SQL 数据源端执行。为什么查询折叠对增量刷新很重要?嗯,您的日期范围参数需要转换成 SQL 中的 WHERE 子句,以便在相关分区中分隔数据。因此,如果没有查询折叠,就没有 WHERE 子句,不可能进行分区,因此也不可能进行增量刷新
- 单一数据源 —意味着所有分区都必须从单一数据源查询数据
等等……还有呢!
通过利用混合表特性,您可以进一步增强数据刷新过程。本质上,这种想法是为表设置增量刷新,但在 DirectQuery 模式下设置具有最新数据的分区,同时将较旧的数据保留在使用导入模式的分区中。
通过这种方式,您可以两全其美:针对旧数据的分析查询的超快的性能,以及与来自原始数据源的最新数据的实时同步。但是,目前,混合表功能仅适用于 Power BI Premium 许可证。
在 Power BI 中设置增量刷新
配置过程的第一步是定义参数,并设置在过滤应加载到 Power BI Desktop 中的数据时应用的默认值。您应该保持这个范围较短,并且只包括最近的数据(在我的例子中是 3 天),因为一旦您配置了增量刷新策略,这些值无论如何都会被覆盖。

作者图片
请记住,您应该为参数使用预定义的 RangeStart 和 RangeEnd 名称。这样,Power BI“知道”这些参数将用于设置增量刷新。
下一步是根据我们新创建的参数过滤数据:

作者图片
设置过滤条件时要小心,您应该避免在两个参数上使用等式(即,是在之后或等于/是在之前或等于),因为这可能会提取重复的数据,并在您的报告中产生不正确的结果!
我现在可以在 Power Query Editor 中点击 Close&Apply,您可能会注意到 Power BI 将只加载一小部分数据——在我的例子中,不是最初的 1260 万行,而是这次只加载 36000 行。
现在到了工作流的关键部分—我们将为我们庞大的事实表定义增量刷新策略:

单击表名旁边的三个点,然后选择增量刷新
弹出增量刷新对话框后,有各种选项可供选择:

作者图片
在我打开增量刷新该表属性后,我将定义归档周期(在我的示例中为 3 年)和应用增量刷新策略的周期(3 天)。最酷的是,您可以立即看到哪些日期将包含在哪个分区中。
在可选设置下,如果您勾选了使用 DirectQuery 实时获取最新数据旁边的框,您可以利用我们在上一篇文章中已经描述过的混合表特性。
如果您将数据刷新过程安排在比如说凌晨 3 点(假设此时底层数据源的压力较小),但是您不希望仅将午夜和凌晨 3 点之间插入源系统的这些记录导入 Power BI,那么仅刷新完整天数选项非常方便。启用此选项后,将只加载已完成天数的记录。
检测数据更改可能会为数据刷新过程带来更大的性能提升,因为它仅支持处理自上次运行以来发生更改的记录。要使该选项起作用,您需要在数据源中有一个包含特定记录上次更新时间信息的特定列(例如,LastUpdated、LoadDate 等)。该列必须是日期/时间数据类型,并且不应该是用于对事实表进行分区的同一列(在我们的例子中,它不应该是 DateKey 列)。
最后,如果向下滚动,您可以看到可视化的增量刷新滚动窗口:

作者图片
点击“应用”,现在我已经准备好发布报表和数据集了。一旦数据集位于 Power BI 工作区中,您必须刷新它。第一次刷新将加载所选存储期间的历史数据,以及增量刷新策略中定义的新数据和更新数据。
第一次数据加载可能需要一段时间,具体取决于数据量,但所有后续刷新的运行速度应该会快得多,因为只会加载增量刷新策略中指定时间段内的数据。
结论
当您处理相对较小的数据模型时,您可能会偷偷摸摸地不实现数据的增量刷新。但是,一旦数据量开始扩大,增量刷新将很快成为您优化数据刷新流程的最佳选择。
而且,这不仅仅是将新数据引入 Power BI 数据集所需的时间,还包括重新处理整个巨大的表所需的资源。
感谢阅读!
Python 类中属性的指示和捉迷藏隐私
PYTHON 编程
了解“公共”和“私有”在 Python 类及其属性的上下文中的含义。

公共与私人——说与想
一般在编程中,当某个东西是公共的,你就可以访问它,使用它;当它是私人的时候,你不能。这就像想某事和说某事:当你想某事时,它仍然是你的;但是无论你大声说什么,都不再仅仅是你的,而是公开的。
Python 中的工作方式不同。你可能听说过在 Python 中没有什么是真正私有的。这是什么意思?Python 有私有属性和方法吗?
我们在 Python 类的方法和属性的上下文中使用这两个术语,公共和私有。当一个属性是私有的,你不应该使用它;当一个方法是私有的,你不应该调用它。你可能注意到我用了“应该”这个词。正如我已经提到的,这是因为 Python 中的工作方式不同:当某个东西是公共的时,你可以访问和使用它;当这是私人的事情时,你不应该做,但这并不意味着你不能做。因此,当你用 Python 思考某件事时,它应该属于你——但是任何人都可以通过简单的方法听到它。
如你所见,Python 在隐私方面并不严格。它建议你遵守一些规则,而不是让你去遵守它们。它建议一个类的用户而不是访问私有方法和属性——但是用户无论如何都可以这样做,更重要的是,他们不必为此付出太多努力。
在这篇文章中,我将用简单的词语和例子来解释这些事情。
当你想某事时,它仍然是你的;但是无论你大声说什么,都不再仅仅是你的,而是公开的。
当你用 Python 思考某件事时,它应该属于你——但是任何人都可以通过简单的方法听到它。
“私有”方法和属性
Python 中没有真正的隐私。Python 提供的是伪隐私,或者准隐私。它有两个层次,我称之为指示隐私和捉迷藏隐私。
指示隐私
您可以指示特定属性是私有的。要做到这一点,只需在其名称中添加一个前导下划线即可。这样做,你指示,或者建议,或者建议,该方法/属性应该被视为私有的,意味着它不应该在类之外使用。
因此,instance.do_it()是一个常规(公共)方法,而instance._do_it()是一个表示为私有的方法。因此,作为该类的用户,您被要求不要使用它。它在那里是因为它服务于一些实现的目的——而你与它无关。这不是秘密。你可以看一下,没有人对你隐瞒什么。但这不是给你的。接受别人给你的东西,不要碰别人给你的东西。
让我们考虑一个简单的例子:
# class_me.py
class Me:
def __init__(self, name, smile=":-D"):
self.name = name
self.smile = smile
self._thoughts = []
def say(self, what):
return str(what)
def smile_to(self, whom):
return f"{self.smile} → {whom}"
def _think(self, what):
self._thoughts += [what]
def _smile_to_myself(self):
return f"{self.smile} → {self.name}"
(如果你不知道为什么我写的是self._thoughts += [what]而不是self._thoughts += what,请访问附录 1。)
好的,我们有一个类Me,它代表你——至少在你创建它的时候。它具有以下属性:
.name,一个公共属性→你的名字肯定是公共的。.smile,一个公共属性→你的笑容在外面是可见的,所以肯定是公共的。._thoughts,一个私人属性→你的想法肯定是私人的,不是吗?
如您所见,两个公共属性的名称没有前导下划线,唯一的私有属性的名称有。
现在让我们来看看可用的方法:
.say(),一个公开的方法→当你说一件事的时候,别人能听见,所以你的话是公开的。.smile_to(),一个公共方法→当你对某人微笑时,这个人和周围的人都能看到你在微笑。._smile_to_myself(),一个私密的方法→这是一种别样的微笑;它是为类的作者保留的(在我们的例子中,是为您保留的),并且是在没有人注意的时候完成的——这就是为什么它是一个私有方法。._think(),私法→当你想某件事的时候,那是你的私以为;如果要大声说出来,就要用 public.say()的方法。
让我们和全班一起玩。我将为自己创建该类的实例,所以我将它命名为marcin。您可以为自己创建一个实例。
>>> from class_me import Me
>>> marcin = Me(name="Marcin")
>>> marcin # doctest: +ELLIPSIS
<__main__.Me object at 0x...>
>>> marcin.say("What a beautiful day!")
'What a beautiful day!'
>>> marcin.smile_to("Justyna")
':-D → Justyna'

我使用了doctest来格式化上面块中的代码。它帮助我确保代码是正确的。您可以从下面的文章中了解关于这个文档测试框架的更多信息:
如果您想将代码复制并粘贴为 doctest,并自己以这种方式运行,请访问本文末尾的附录 2,其中包含以这种方式格式化的剩余代码(例如,Me类的代码)。

好的,一切看起来都很好。然而,到目前为止,我们还算客气,甚至还没有看私有方法和属性;我们只用过公共的。是时候淘气一点了:
>>> dir(marcin) #doctest: +NORMALIZE_WHITESPACE
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__le__', '__lt__',
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'__weakref__', '_smile_to_myself', '_think', '_thoughts', 'name',
'say', 'smile', 'smile_to']
我们看到了什么?实际上,一切。我们当然会看到公共属性.name和.smile以及公共方法.say()和.smile_to()。但是我们也看到私有属性._thoughts和私有方法._think()和._smile_to_myself()。此外,我们会看到更多不是我们创建的方法和属性。
请记住,使用.__name__()约定命名的方法是 dunder 方法,而不是私有方法。我们改天再谈这个。
既然我们能够看到私有属性,很可能我们也能够使用它们:
>>> marcin._think("My wife is so beautiful!")
>>> marcin._think("But let this be my secret!")
什么都没发生?那也许就没事了?也许我们可以使用私人方法,但无论他们在做什么都瞒着我们?
当然不是。只是._think()方法不返回任何东西(或者说返回None),而是将想法保存到._thoughts属性,也是私有的。让我们看看你是否能看到我的私人想法:
>>> marcin._thoughts
['My wife is so beautiful!', 'But let this be my secret!']
是的,你可以。最后一个测试:让我们看看你是否能看到我对自己微笑:
>>> marcin._smile_to_myself()
':-D → Marcin'
你也可以。因此,您可以清楚地看到私有属性,并且可以使用私有方法——尽管我通过在这些属性和方法的名称前添加下划线明确指出它们是私有的,所以我不希望您使用它们。使用私有方法或属性有点像在淋浴时偷窥我——你可以看到我想对你隐藏的东西。
然而,有时出于这样或那样的原因,您可能想要修改一个现有的类;这可能意味着覆盖私有属性或方法。这就是 Python 方法的亮点。理论上,这些属性是私有的,所以你不应该使用它们;有时,使用它们甚至可以打破一个类。这也是一种保护措施。你知道这些属性是隐私,所以最好不要碰它们。
但是当你知道你在做什么,当你的目的要求你使用私有属性时——Python 使这成为可能。这为 Python 开发人员带来了许多额外的机会。
使用私有方法或属性有点像在淋浴时偷窥我——你可以看到我想对你隐藏的东西。
这为 Python 开发人员带来了许多额外的机会。
有点夸张,在 Python 里你可以为所欲为。您可以覆盖内置函数、异常等。(如果你想了解更多关于重写异常的信息,请阅读这篇更好编程文章。)并且可以使用私有属性。这很好,假设——就像任何代码的情况一样——您不想对用户的计算机造成任何损害。
我相信你会同意这种隐私是脆弱的,因为用户可以像使用公共属性和类一样使用私有属性和类。然而,Python 提供了一种更严格的隐私方法,我称之为捉迷藏隐私。
捉迷藏隐私
隐私的指示级别仅包括指示属性是私有的还是公共的,而捉迷藏级别则更进一步。你马上就会看到,在某种程度上,它帮助你保护私有属性。
这是否意味着这一次,私有属性和方法将真正被隐藏,用户将无法使用它们?不完全是。正如我所写的,捉迷藏隐私提供了某种程度的保护——但不是完全的保护。Python 之所以能做到这一点,要归功于一种叫做 name mangling 的方法。
当您想要使用名称篡改,因此需要隐藏隐私时,您需要向私有属性的名称添加两个前导下划线,而不是一个。在我们的Me类中,比如说,.__thoughts和.__think()。多亏了名称管理,私有属性或方法以一种特殊的方式被修改,使得从类外部访问它们变得更加困难。
让我们在工作中看到这一点。我们先修改我们的Me类;让我们把它的名字改成PrivateMe(关于doctest ing 格式的代码,见附件 2):
# class_me.py
class PrivateMe:
def __init__(self, name, smile=":-D"):
self.name = name
self.smile = smile
self.__thoughts = []
def say(self, what):
return str(what)
def smile_to(self, whom):
return f"{self.smile} → {whom}"
def __think(self, what):
self.__thoughts += [what]
def __smile_to_myself(self):
return f"{self.smile} → {self.name}"
首先,让我们创建一个实例—同样,这将是我的一个实例—并使用公共方法:
>>> marcin = PrivateMe(name="Marcin")
>>> marcin.say("What a beautiful day!")
'What a beautiful day!'
>>> marcin.smile_to("Justyna")
':-D → Justyna'
(如果你在疑惑 Justyna 是我老婆还是我在对另一个女生微笑,你可以放心;她是!)
到目前为止一切顺利,但这并不令人惊讶——毕竟,我们已经使用了公共方法。以前,我们成功地使用了私有方法,比如._smile_to_myself()。让我们试试这次是否能成功。为了验证这一点,我会试着用.__smile_to_myself()方法对自己微笑:
>>> marcin.__smile_to_myself()
Traceback (most recent call last):
...
AttributeError: 'PrivateMe' object has no attribute '__smile_to_myself'
哈!我们知道PrivateMe类有__smile_to_myself()方法,但是我们不能使用它。显然,它是受保护的,任何私有方法都应该如此。
尽管如此……看起来这个方法是完全受保护的,而不久前我还声称在 Python 中,私有属性没有受到完全保护。这是怎么回事?
我们刚刚经历了如何命名 mangling 工程。它隐藏了私有属性——或者说,不管听起来有多奇怪,它隐藏了私有属性的名称。换句话说,它以一种特殊的方式改变了他们的名字;新名称将遵循以下_ClassName__attribute符号:
class MyClass:
__privacy = None # this becomes ._MyClass__privacy
def __hide_me(self): # this becomes ._MyClass__hide_me()
pass
这样,不能使用属性的原始名称访问属性,但可以使用通过名称管理更新的名称访问属性。在我们的PrivateMe类中,它是这样工作的:
>>> marcin._PrivateMe__smile_to_myself()
':-D → Marcin'
你可以看到这个属性就在那里,只是被重命名了。我们肯定会在dir()函数的输出中看到这一点:
>>> dir(marcin) # doctest: +NORMALIZE_WHITESPACE
['_PrivateMe__smile_to_myself', '_PrivateMe__think',
'_PrivateMe__thoughts', '__class__', '__delattr__',
'__dict__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__le__', '__lt__',
'__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__',
'__weakref__', 'name', 'say', 'smile', 'smile_to']
我们的私有方法和属性可以使用新的名称:
.__smile_to_myself()→._PrivateMe__smile_to_myself().__think()→._PrivateMe__think().__thoughts→._PrivateMe__thoughts
名称篡改使我们能够实现隐私的捉迷藏水平。
还记得一件事。当您想要通过添加两个前导下划线来使属性成为私有属性时,不要在名称末尾添加两个额外的下划线。这样命名的方法就变成了所谓的 dunder(ddouble-score)方法——而且这些绝对是不是私有的;实际上,它们是与私有相对的。我们改天再谈。要使用名称管理,记住这条命名规则就足够了:不要对私有方法使用.__name__()约定,因为这不起作用。

克里斯蒂安·沃克在 Unsplash 上拍摄的照片
结论
我们已经在 Python 面向对象编程的上下文中讨论了隐私的概念。编写类时,有时可能希望隐藏一些实现细节,通过将类的一些属性和方法设为私有,可以实现这一点。但它们从来都不是真正的隐私。
这种方法对我来说听起来不自然。当我想到一个私有属性时,我把它想象成一个不能在类外看到和使用的属性。同样,它是一个公共属性,可以以这种方式看到和使用。
如果您的想象力以类似的方式工作,您需要使用改变世界的眼镜,以便您可以在 Python 世界中移动,而不会不时摔倒。每次用 Python 都要戴上这种眼镜。迟早,它们会帮助您习惯 Python 的不同世界,在这个世界中,隐私的概念是如此不同。
您需要使用改变世界的眼镜,这样您就可以在 Python 世界中移动,而不会不时摔倒。
迟早,它们会帮助您习惯 Python 的不同世界,在这个世界中,隐私的概念是如此不同。
总之,Python 不能让你完全保护一个类的属性。然而,它提供了两级保护,我称之为指示和捉迷藏隐私。
**指示隐私。您可以将一个属性指定为私有,并相信没有人会在类之外使用该属性。指示方法是基于信任的:我们相信类的用户不会使用它的私有属性。除此之外,该方法不使用任何保护措施。
指示方法是基于信任的:我们相信类的用户不会使用它的私有属性。除此之外,该方法不使用任何保护措施。
**捉迷藏隐私。这是更高层次的隐私——就类属性的隐私而言,我们可以从 Python 中获得最多的隐私。在指示 privacy 的情况下,你可以像使用 public 属性一样使用 private 属性,但是在这里你不能。你的私人属性得到了一定程度的保护。它仍然不是完全的保护;私有属性由于名字的改变而被隐藏。你仍然可以找到、访问和使用它们——但至少它们受到了某种程度的保护。它们并没有真正被隐藏,因为dir()将向我们展示所有的类属性,包括公共的和私有的,但是后者将会改变名称。
感谢阅读这篇文章。我希望 Python 类环境中的隐私不再对您构成问题。虽然乍一看这个主题似乎很难,或者至少很奇怪,但是您会很快习惯 Python 隐私的奇怪世界。请放心,许多 Python 开发人员都很欣赏这些东西在 Python 中的工作方式。如果你不这样做,你迟早会加入他们的行列。
对我来说,我不仅不反对 Python 对待隐私的方式,我甚至欣赏它。我多次使用过这种方法,知道它就在那里是很好的,以防万一,等待我去窥探类的属性和方法。
如果你喜欢这篇文章,你也可以喜欢我写的其他文章;你会在这里找到它们。如果你想加入 Medium,请使用我下面的推荐链接:
*https://medium.com/@nyggus/membership
脚注
记住,在 Python 中,方法是类的属性。因此,每当我提到属性的私有性时,我指的是属性的私有性,包括方法。
命名有两个目的:
- 它增加了对类的私有属性和方法的保护级别。
- 它确保父类的私有属性不会被从它继承的类覆盖。因此,当您使用两个前导下划线时,您不必担心类中的这个属性会被继承类覆盖。
本文讲的是第一点。然而,第二个问题超出了本文的范围;我们改天再讨论它。
附录 1
这个附录解释了为什么在编写Me类时,我写道
self._thoughts += [what]
而不是
self._thoughts += what
就地串联+=的工作方式如下:
>>> x = [1, 2, 3]
>>> y = [4, 5, 6]
>>> x += y
>>> y
[4, 5, 6]
>>> x
[1, 2, 3, 4, 5, 6]
如您所见,该操作添加了两个列表;作为就地操作,它影响第一个,而第二个保持不变。然而,这不适用于不可迭代的对象,比如数字(这里,int):
>>> x += 5
Traceback (most recent call last):
...
TypeError: 'int' object is not iterable
因此,您可以使用就地串联向列表中添加另一个 iterable,如列表、元组、range对象和生成器:
>>> x += (10, 20)
>>> x
[1, 2, 3, 4, 5, 6, 10, 20]
>>> x += range(3)
>>> x
[1, 2, 3, 4, 5, 6, 10, 20, 0, 1, 2]
>>> x += (i**2 for i in range(3))
>>> x
[1, 2, 3, 4, 5, 6, 10, 20, 0, 1, 2, 0, 1, 4]
字符串也是可迭代的,所以您也可以将它们添加到列表中:
>>> x += "Cuma"
>>> x
[1, 2, 3, 4, 5, 6, 10, 20, 0, 1, 2, 0, 1, 4, 'C', 'u', 'm', 'a']
如您所见,"Cuma"字符串被视为其单个字符的可重复项,添加到x的是这些字符,而不是单词本身。
这就是为什么self._thoughts += what不起作用的原因。如果我们使用它,我们将会达到以下不良效果:
>>> marcin._think("I am tired.")
>>> marcin._thoughts
['I', ' ', 'a', 'm', ' ', 't', 'i', 'r', 'e', 'd', '.']
因此,我们需要将思想添加到._thoughts中作为列表的元素,即[what]。这个单元素列表是要添加到._thoughts的 iterable。
附录 2
为doctest格式化的类Me:
>>> class Me:
... def __init__(self, name, smile=":-D"):
... self.name = name
... self.smile = smile
... self._thoughts = []
... def say(self, what):
... return str(what)
... def smile_to(self, whom):
... return f"{self.smile} → {whom}"
... def _think(self, what):
... self._thoughts += [what]
... def _smile_to_myself(self):
... return f"{self.smile} → {self.name}"
为doctest格式化的类PrivateMe:
>>> class PrivateMe:
... def __init__(self, name, smile=":-D"):
... self.name = name
... self.smile = smile
... self.__thoughts = []
... def say(self, what):
... return str(what)
... def smile_to(self, whom):
... return f"{self.smile} → {whom}"
... def __think(self, what):
... self.__thoughts += [what]
... def __smile_to_myself(self):
... return f"{self.smile} → {self.name}"
基于 SLAM 的室内机器人定位
原文:https://towardsdatascience.com/indoor-robot-localization-with-slam-f8b447bcb865
在 Raspberry Pi 上使用 RP LIDAR A1 进行同步定位和制图(SLAM)导航,并使用 MQTT 进行远程“点云”可视化
在工厂或酒店,机器人越来越多地取代了人类。但是在不了解环境的情况下机器人如何在室内动态导航?如果你已经有一张地图,那么你就知道你在哪里。否则,你需要生成地图(制图),还要了解你在地图上的位置(定位)。想象一下,即使对一个人来说,在一个陌生的地方,这有多难。
SLAM(同步定位和地图绘制) 算法使用 LiDAR 和 IMU 数据在中同时定位机器人,并生成周围 地标的连贯地图,如建筑物、树木、岩石和其他世界特征,同时。****
这个经典的先有鸡还是先有蛋的问题已经使用粒子滤波、扩展卡尔曼滤波(EKF)、协方差交叉和图形 SLAM 等方法近似解决了。** SLAM 可在 GPS 定位不可用的地方(如室内空间)实现精确测绘。**
因此, SLAM 是用于自动驾驶汽车、机器人导航、机器人地图、虚拟现实和增强现实的核心算法。如果我们可以在 RPi 上进行机器人定位,那么就很容易制造出可以在室内自主行驶的移动汽车或步行机器人。
首先来讨论一下 Graph SLAM 做一个自定义实现。然后我们将尝试将 RPi 与 RPLidar A1 M8 集成,使用 5V 3A 电池运行,并与** 激光雷达点云地图的可视化一起进行 SLAM,以辅助导航甚至生成楼层地图。最后使用 MQTT 将激光雷达点云地图 在远程机器上可视化。**
****项目演示:与 RPi 集成的 RPLidar A1 部署了 BreezySLAM
图表猛击
假设 2D 世界中的一个机器人,试图从 x 向右移动 10 个单位到 x’。由于运动不确定性,x' = x + 10 可能不成立,但它将以 x + 10 为中心呈高斯分布。当 x’接近 x + 10 时,高斯将达到峰值

图片由作者提供:机器人从 x0 到 x1 到 x2 的运动由两个高斯函数表征
如果 x1 与 x0 相差 10 个单位,卡尔曼滤波器将使用高斯函数(x1–x0–10)对不确定性进行建模。因此,仍然存在与位置< 10 and > 10 相关联的概率。
在 x2 处有另一个相似的高斯分布,具有更高的扩散。整条路线的总概率是两个高斯的乘积。我们可以去掉常数,因为我们只需要在给定 x0 的情况下,最大化位置 x1 的可能性。因此高斯的乘积变成了指数项的和,即约束只有 x 和 sigma。
Graph SLAM 将约束条件建模为线性方程组(SLEs) ,其中一个ω矩阵包含变量系数,一个ξ向量包含约束条件的极限值。每次在两个姿势之间进行观察时,在 4 个矩阵元素 上进行“局部相加”(因为高斯的乘积变成了指数的和)。
比方说,机器人从 x0 移动到 x1,再到 x2,它们相距 5 和-4 个单位。

图片作者:奥米加矩阵和 Xi 向量经过 2 次运动
x 的系数和 RHS 值被加到相应的单元上。考虑地标 L0 距离 x1 9 个单位。

图片由作者提供:奥米加矩阵和 Xi 向量在考虑了地标 L1 之后
如上填写ω矩阵和ξ向量后,计算下面的等式以获得所有机器人位置的最佳估计值:****

估计机器人的位置
自定义实现:Graph SLAM
您需要更新 2Dω矩阵和ξ矢量中的值,以考虑 x 和 y 方向上的运动和测量约束。
自定义 SLAM 实现的完整源代码和结果可以在 IPython 笔记本这里找到。****
我们可以看到 SLAM 的实际应用,如果部署在配备 RPLidar A1 M8 的 RaspberryPi 上,使用 5V 3A 电池运行。见下面组装好的小工具。

作者图片:SLAM 组装设备
正如您在顶部的视频中所看到的,便携式装置被带到我家的各个房间,实时轨迹被传输到 MQTT 服务器,并存储在 RPi 的 SD 卡上。
你可以看到激光雷达点云地图的可视化和使用 PyRoboViz 估计的机器人轨迹,如下图。在上面的视频中,你可以看到我穿过地板上的不同房间。

有趣的是,我们可以使用 MQTT 为实时可视化将实时可视化重新路由到远程机器。机器人的位置、角度和地图可以编码为字节数组,由 MQTT 客户端解码。****
请注意,激光雷达地图中的高密度线性点云代表稳定的障碍物,如墙壁。因此,我们可以使用像霍夫变换这样的算法在这些线性点云上找到最佳拟合线来生成楼层地图。****
从 3D 激光雷达点云中,我们甚至可以使用来自 Motion 技术的结构来构建周围的 3D 地图,****
- 使用检测器如 SIFT、SURF、ORB、Harris 来查找特征,如角落、渐变、边缘等。
- 使用描述符如 HOG 对这些特征进行编码。
- 使用匹配器如 FLANN 在图像间映射特征。
- 使用 3D 三角测量重建 3D 点云。
我们可以利用 SLAM 室内导航的想法在封闭的环境中部署自主移动机器人如机场、仓库或工厂。
此解决方案的源代码可从这里获得
如果您有任何疑问或建议,可以在这里 联系我https://www.linkedin.com/in/ananduthaman/
参考
1.轻松实现满贯:https://github.com/simondlevy/BreezySLAM**
注:已联系 Breezy SLAM 的作者,华盛顿和李大学 CSE 系【Simon D. Levy 教授,了解可视化期间的激光雷达扫描数据错误。我已经通过将激光雷达扫描代码分离为单独的线程修复了代码,并实现了线程间通信*他确认了修复。*******
2.【https://github.com/simondlevy/PyRoboViz】激光雷达点云可视化:
3. Udacity 计算机视觉纳米度:https://www.udacity.com/course/computer-vision-nanodegree-nd 891
4.Adafruit 的激光雷达数据扫描码存根:https://learn . Adafruit . com/remote-IOT-environmental-sensor/code**
5.激光雷达距离估计:*【https://en.wikipedia.org/wiki/Lidar ***
6. RPLIDAR A1 M8 硬件规格:https://www . generation robots . com/media/RP lidar-A1 M8-360 度-激光-扫描仪-开发-套件-数据表-1.pdf
知识图中的归纳链接预测
原文:https://towardsdatascience.com/inductive-link-prediction-in-knowledge-graphs-23f249c31961
开始新的感应链路预测挑战 2022
自从 2011 年以来,知识图上的表征学习领域一直由一项任务主导:直推式链接预测。2022 年还相关吗?🤔不太可能。

直推式设置:训练和推理在同一个图上进行。归纳:推论在新图上。彩色箭头代表不同的边类型(关系)。问号表示要预测的边。作者图片
在直推式设置(🖼 ☝️)中,我们在训练时看到的相同的图上执行推理(我们的链接预测)。我们还假设我们没有任何预先计算的节点特征。这个事实:
- 🔐在训练和推理时将实体集锁定为相同
- 🥚允许浅嵌入模型学习图形中每个节点的唯一向量
- 🙅不支持使用预先训练的模型对新图表进行推理
是时候给基于三重 kg 的直推式链路预测最后一击,让它退役了
随着图在工业中的增长(100M 到 10B+节点)以及每次图改变时重新训练的巨大计算成本,KG 表示学习的焦点正转向不受上述限制的归纳模型。
实际影响:我为什么要关心?
嗯,对普通三基 KG 的直推式链接预测停止了跟踪 KG 表示学习的大部分进展:这里是代码为的论文的摘录,可视化了标准FB15k-237基准中的进展。

自 2019 年以来,直推式 LP 没有实质性进展。作者图片
🤨自 2019 年以来,你看到任何重大进展吗?(是的,我也没有)
几年来,普通的转导链路预测已经相当陈旧了。相反,2021 年至 2022 年表征学习的大部分进展(如 神经贝尔曼-福特网络 或 节点块 )要么是在新的 KG 模态(如超关系 KG)上实现的,要么是记住了归纳属性👉查看我们最近的文章了解更多详情。
什么是“感应式”设置?
在归纳设置中,我们取消了在训练和推理时具有相同图形的要求。在我们的 ISWC 2021 论文中(谦虚地提一下它获得了最佳研究论文奖😊)我们定义了两种类型的感应设置:
- 全归纳**:完全脱离训练图的新图。因此,仅在不可见的实体上执行链接预测(从不可见到不可见的模式)。**

完全归纳设置—推理图与训练图断开。问号表示要预测的边缘。作者图片
2.半归纳:一个更大的更新图,包括并扩展了训练图。链接预测可以涉及可见和不可见的实体,因此模式可见到不可见和不可见到不可见。

半归纳设置:推理图用新节点(橙色)扩展了训练图。问号表示要预测的边缘。图片作者。
✅归纳模型的一些直接好处:
- ****没有浅节点嵌入!在存在新的看不见的节点时,它们是无用的,我们需要新的方法来学习实体表示。
- 预训练模型不需要重新训练,可以立即用于新的或更新的图表。
归纳模型带来了额外的表示学习挑战,即,我们不能再对每个节点使用浅层向量分配,我们需要更有效的方法来构建能够推广到新的看不见的节点的节点特征。GNNs 和消息传递在这个探索中似乎很有前途。
关于完全归纳设置中的归纳 LP 的第一项工作可追溯到由 Teru、Denis 和 Hamilton(ICML 2020)提出的论文,他们引入了基于局部节点邻域的归纳特征化方法。由于这种方法的可扩展性不强,因此采样的归纳数据集也相对较小,大多为 2000–5000 个节点。是时候扩大规模了!🚀****
感应链路预测挑战 2022
🐍在团队 PyKEEN 中,我们设计了一个新的归纳链接预测挑战 (ILPC),旨在巩固社区在创建归纳推理模型方面的努力。对于 2022 年,我们提出了在全归纳模式下的归纳链接预测挑战,即当训练图和推理图不相交时。
随着 新论文 描述基准,ILPC 2022 特性:
- 新的数据集ilpc 22-小型和ilpc 22-大型从 维基数据 中取样,最大的公开公斤数。较小的版本(s)非常适合假设检验🧪和有限的计算资源,而较大的版本(l)甚至对现代 gnn 构成了重大挑战,特别是在推理图大小方面——据我们所知,这是模型第一次必须推广到如此大小的看不见的图👀。

ILPC 2022 数据集。作者图片
- 公共分割包含训练、验证和测试集,但我们还保留了一个隐藏测试 集,用于对每个数据集大小提交的模型进行最终评估(例如,就像他们在 Kaggle 上做的那样)。此外,我们在 Zenodo 上公开了数据集
- 一个代码库,包含一组不同的指标和一个标准化的评估程序。顺便说一下,我们最近发表了一份👉 新作 👈设计与数据集大小无关的排名指标,我们计划在最终评估中也使用这些指标!
- 两个强基线采用归纳版本的节点件作为组合特征:1️⃣普通节点件+非参数解码器;具有 2 层 CompGCN 消息传递编码器+非参数解码器的 2️⃣节点。数据集非常具有挑战性,而且远未解决:

ILPC22-Small 上的基准性能。

ILPC22-Large 上的基准性能。
- 提交材料非常容易制作,并将在♻️公开复制
- 最后,ILPC 发布了新的 PyKEEN 1.8.0 版本,具有归纳链接预测管道、构建归纳模型的新接口以及许多新的评估指标📏
摘要
一个新的和开放的感应链路预测挑战 2022 已经开始,尽你所能 GNNs 和其他架构!
- 📜论文:arxiv
- 💾数据集: GitHub 和芝诺多,开放 CC0 许可
- 🛠 Github 知识库,包含排行榜和说明
- ♻️ 芝诺多条目

我们将关注您提交的内容!资料来源:gfycat.com
没有查尔斯·塔普利·霍伊特 (哈佛大学)和马克斯·贝伦多夫 (LMU) 这部作品就不可能完成🙌
卷积神经网络的推理优化
原文:https://towardsdatascience.com/inference-optimization-for-convolutional-neural-networks-e63b51b0b519
量化和融合以实现更快的推理

现代深度学习研究主要集中在为各种问题(如对象检测、分割、自我监督学习等)创建和改进新的、更好的和优化的解决方案。这正在帮助许多企业和初创公司开始为他们的产品使用更容易获得的人工智能解决方案。然而,当涉及到现实世界的应用时,尤其是当模型是为边缘设备开发时,我们面临着许多限制,如模型大小大和低延迟。
有许多方法可以让移动设备和其他边缘设备访问 AI 模型。一种选择是使用为移动设备设计的小型号(比如为移动设备设计的 MobileNet 和 Yolo)。其他方法包括推理级别的优化。后者包括模型剪枝、量化、模块融合等方法。在这篇博文中,我们将探讨卷积神经网络的量化和融合方法。我们将使用 PyTorch 的量化模块,并比较有量化和没有量化的模型的大小和延迟。
博客概述:
- 什么是量子化?
- 量化技术
- 什么是模块融合?
- PyTorch 中的应用与比较
什么是量化?
量化是一种在推理阶段加速深度学习模型的简单技术。这是一种压缩信息的方法。模型参数存储在浮点数中,模型操作使用这些浮点数进行计算。浮点运算具有很高的精度,但是,它们非常占用内存,计算量也很大。量化将 32 位浮点数转换为 8 位整数。它对 8 位整数执行部分或全部操作,这可以将模型大小和内存需求减少 4 倍。
然而,这是有代价的。为了减小模型的规模,提高执行时间,我们会牺牲一些精度。因此,在模型准确性和大小/延迟之间会有一个折衷。
为了进行量化,我们需要一个将浮点数映射为整数的映射函数。最常见和最简单的方法是线性变换。
Q(r) = round(1/S + Z),其中
- r 是输入,
- s 是比例因子,即输入范围与量化输出范围之比。
找到比例因子最简单的方法是使用输入和输出范围的最小值和最大值。但是 PyTorch 还有 MSE 最小化、熵最小化等其他方法。寻找比例因子的过程称为校准。

- z 是零点,此参数有助于将零点从输入空间正确映射到输出空间。

PyTorch 有一个观察器模块,可以用来校准模型。它收集输入值的统计数据,并计算 S 和 Z 参数。量化参数也可以通过张量或通道来计算。在第一种情况下,观察者将获取整个张量并从中提取统计数据,在第二种情况下,分别为每个通道计算 S 和 Z 参数。
量化技术
在 PyTorch 中,有 3 种不同的方法来实现量化:
- 动态量化
模型权重被量化,推理时激活被动态量化。这可以提高精度,因为对每个输入都进行了校准。这项技术在 LSTM、GRU、RNN 都有效 - 训练后静态量化
在这种情况下,模型权重和激活是预先量化的。对验证数据进行校准。这比动态量化更快,但是,它可能需要不时地重新校准以保持稳健。 - 量化感知训练(QAT)
这些技术旨在改善训练后的量化。它在训练损失中增加了量化误差。s 和 Z 参数可以在训练期间学习。
在本帖的续篇中,我们将对卷积神经网络使用训练后量化。
什么是模块融合?
在继续讨论 PyTorch 代码之前,让我们先了解另一种技术,这就是 fusion。融合旨在将多个层合并为一层,这可以节省推理时间并减少内存访问。它将合并后的操作序列发送到一个低级别的库,该库可以一次性计算出来,而无需将中间表示返回给 PyTorch。然而,这种技术也是有代价的。现在,当各层合并时,模型很难调试。融合只对以下图层组有效:[Conv,雷鲁],[Conv,巴奇诺姆],[Conv,巴奇诺姆,雷鲁],[线性,雷鲁]。
py torch 中的应用和比较
# Import packages
from torch import nn
from torchsummary import summary
import torch
import os
首先,让我们创建一个简单的卷积神经网络。
从上面的代码片段可以看出,我们已经创建了一个小型卷积网络,它有两个卷积层,后面是 relu 和 maxpooling。最后,我们有两个完全连接的层和一个 Softmax 输出。
n = Net().cuda()
summary(n, (3, 224, 224))

模型摘要显示,我们有大约 7000 万个参数,估计模型大小为 294MB。
为了创建同一模型的量化版本,我们将创建 2 个新属性来量化和反量化模型。接下来,在正向传递期间,我们将在 softmax 之前对网络输入进行量化和去量化。
为了运行 eval()设置的量化模型,我们需要定义配置。Torch 有两个用于量化的后端:用于支持 AVX2 或更高版本的 x86 CPUs 的“fbgemm”和用于 ARM CPUs(移动/嵌入式设备)的“qnnpack”。
接下来,我们需要使用torch . quantization . prepare来准备模型。这将运行一个观察器方法来收集输入的统计数据。并且torch . quantization . convert从观察者状态转换到量子化状态。
# Define original and quantized models and prepae for evaluation
net = Net()
net.eval()
net_quant = NetQuant()
net_quant.eval()# Prepare model quantization and convert to quantized version
net_quant.qconfig = torch.quantization.get_default_qconfig("fbgemm")
torch.backends.quantized.engine = "fbgemm"
net_quant = torch.quantization.prepare(net_quant.cpu(), inplace=False)
net_quant = torch.quantization.convert(net_quant, inplace=False)
检查模型尺寸
# Check model size
def print_model_size(mdl):
torch.save(mdl.state_dict(), "tmp.pt")
size = round(os.path.getsize("tmp.pt")/1e6)
os.remove('tmp.pt')
return sizenet_size = print_model_size(net)
quant_size = print_model_size(net_quant)print(f'Size whitout quantization: {net_size} MB \n Size whit quantization: {quant_size} MB')
print(f'Size ratio: {round(net_size/quant_size, 2)}')

现在,我们得到了比原来尺寸小 4 倍的模型。
检查模型延迟
# input for the model
inpp = torch.rand(32, 3, 224, 224)
# compare the performance
print("Floating point FP32")
%timeit net(inpp)print("Quantized INT8")
%timeit net_quant(inpp)

CPU 上的原始模型运行速度为 162 ms,而量化后的模型大约快 1.7 倍。
融合
接下来,让我们实现融合以实现更多优化。正如我们在模型结构中看到的,只有 3 种方法来融合层:
# Perpare blocks for the fusionmoduls_to_fuse = [['conv1', 'relu1'],
['conv2', 'relu2'],
['fc1', 'relu3']]net_quant_fused = torch.quantization.fuse_modules(net_quant, moduls_to_fuse)net_fused = torch.quantization.fuse_modules(net, moduls_to_fuse)
现在让我们再次检查延迟:
print("Fused and quantized model latency")
%timeit net_quant_fused(inpp)print("Fused model latency")
%timeit net_fused(inpp)

正如我们所看到的,通过使用带量化或不带量化的融合,我们节省了一些运行时间。然而,这并不像量化那样影响准确性。
结论
优化推理模型不是一件容易的事情。PyTorch 使得使用这些优化技术变得更加容易。但是,一定要记得在量化前后仔细检查模型的精度,确保你的模型不仅快而且准。
在 GitHub 上找到所有代码:https://github.com/LilitYolyan/inference_optimization_cnn
检查参考资料:
https://pytorch.org/blog/quantization-in-practice/
信息论——简介
原文:https://towardsdatascience.com/information-theory-a-short-introduction-a37f09959a1e
理解数据背后的东西&人工智能

数据和人工智能的核心
当我举办关于人工智能的研讨会或在座谈会上发表论文时,我总是试图用几分钟的理论来引导话题。信息论,贝叶斯定理,语言学,偏见,混沌,复杂性,创新,破坏,以及其他一大堆。我这样做是因为我发现,在许多人列出的高科技职位头衔与该头衔所应具备的知识之间存在严重差距。
似乎所有放在我办公桌上申请编程职位的简历都附有相同的流行语。人工智能、大数据和机器学习(ML)是其中的热门。虽然这些才华横溢的个人确实知道人工智能编程堆栈,并且是 python 和算法的“忍者”,但令人痛苦的是,大多数人显然没有足够的理论知识和他们所做事情背后的结构。
缺乏这方面的知识会产生创造力缺口。在人工智能中,加上海量的数据和预测分析,有这么多因素要考虑,无法展示创造性思维可能是一个巨大的障碍。
我真的希望以下关于人工智能核心的各种主题和理论的短文有助于阐明和教育。人们不应该认为它们是全面的,而是进一步深入感兴趣的领域的起点。
布尔函数——慢慢成长的种子

罗伯特·阿纳施在 Unsplash 上的照片
通常情况下,重大的发现和创新需要一颗种子来发展。这种种子本身往往非常重要。信息论就是这种情况,其重要性是无法量化的。如果没有信息论,任何形式的数字信息都不会存在。
它始于 1854 年乔治·布尔关于代数逻辑的论文,“对思维规律的研究,这是逻辑和概率的数学理论的基础。”布尔的代数和逻辑概念今天被称为“布尔函数”,并且从很早的时候就渗透到我们的思维过程中。计算机程序员完全依赖于布尔逻辑操作符,如果没有这些用代码表示的命题,就不可能发展出任何水平的编程复杂度。
布尔通过找到用符号和等式表达逻辑语句的方法,彻底改变了逻辑。他给真命题赋值 1,给假命题赋值 0。“一组基本的逻辑运算——比如 and、or、not、要么/or 和 if/then——可以使用这些命题来执行,就像它们是数学方程一样。”⁴
“与或”和“真-假”命题的迷人简单性经历了近 100 年的重大变化。存在两种选择:真和假。线性发展始于一个与或命题。“在数学中,布尔函数是这样一种函数,它的自变量和结果采用二元集合的值(通常是{真,假}、{0,1}或{-1,1})。”⁵
上图有两扇门。红色和黄色。每一个都有一个门把手。然而,除了颜色之外,它们在所有方面都是一样的。把它们想象成开关。如果一个打开,另一个保持关闭,这可能意味着或情况。如果两个都打开,这可能意味着一个和的情况。穿过一系列红色和黄色的门,每一对门或者打开或者关闭,或者一个门打开而另一个门关闭,可以近似一个布尔函数。
然而,通过 Claude Shannon 的想法,当真-假世界向编程和/或构造敞开大门时,信息和数据的历史永远改变了。
信息论的核心

从布尔提出他的数学命题开始,信息论的种子萌发了将近一个世纪。1948 年,在贝尔实验室,克劳德·香农发表了一篇论文,对现代技术和数据分析产生了不可估量的影响。在如今被称为“信息时代的大宪章”的“communication',⁶的数学理论”中,香农引入了令人难以置信的概念,即信息可以量化和测量。他将布尔逻辑应用于一个全新的宇宙,同时加入了他个人的天才。
香农发现电路可以通过开关的排列来执行这些逻辑运算。例如,为了执行 and 功能,可以将两个开关按顺序放置,这样两个开关都必须打开,电流才能流动。为了实现“或”功能,开关可以并联,这样,如果其中一个开关接通,电流就会流动。“功能稍微多一点的逻辑门开关可以简化这个过程,”⁷
在香农的论文发表之前,信息被视为一种定义模糊的瘴气液体。但是在香农的论文之后,很明显,信息是一个明确定义的,最重要的是,可测量的量…
…香农的信息论提供了信息的数学定义,并精确地描述了系统的不同元素之间可以交流多少信息。这听起来可能没什么,但香农的理论支持了我们对信号和噪声之间关系的理解,以及为什么信息在任何系统(无论是人造系统还是生物系统)中的传播速度都有一定的限制。'⁸
香农写道,由此产生的单位可以被称为二进制数字,或者更简单地说,比特。⁹
比特现在加入了英寸、磅、夸脱和分,成为一个确定的量——一个基本的计量单位。但是衡量什么呢?“测量信息的单位”,香农写道,就好像信息是可以测量和量化的一样。⁰
在此之前,没有人认为信息可以服从数学公式或计算分析。随着这篇开创性论文的发表,在香农之前有了世界,在香农之后有了 T2。
也许香农最大的成就是独立于“意义”分析“信息”的反直觉方法。简而言之,在处理信息时,人们不需要考虑信息的含义。事实上,“意义”对于实际内容来说是多余的。“意义”实际上是没有意义的。
正如他在《交流的数学理论》的第二段引言中所写的:
交流的基本问题是在一点上准确地或近似地再现在另一点上选择的信息。通常这些信息是有意义的;也就是说,它们指的是或根据某些系统与某些物理或概念实体相关联。交流的这些语义方面与工程问题无关。重要的方面是实际消息是从一组可能的消息中选择的一个。系统的设计必须能适应每一种可能的选择,而不仅仅是实际选择的那一种,因为这在设计时是未知的。
值得关注的是概率和不确定性。随着 bit 的诞生,Shannon 将布尔函数的 0,1->真/假结构提升到了一个全新的高度。香农理论的核心是“噪音”和“惊奇”。所有的交流——人类的、计算机的、通过电线的、信号的、数字的——作为一个普遍的基础——都有“噪音”和“惊喜”的成分。令人惊讶的是,噪音消除后留下的是什么(任何沟通渠道都有噪音),而不会干扰原始信息。
那么,什么是信息呢?它是从信息中挤出每一点自然冗余,去除每一个无目的的噪音之后剩下的东西。“这是一种不受约束的本质,从计算机传递到计算机,从卫星传递到地球,从眼睛传递到大脑,并且(经过许多代的自然选择)从自然界传递到每个物种的集体基因库。”
香农的信息论实际上催生了数字时代。没有它,人们将淹没在噪音和对他们分享的信息的真实性的不确定性中。没有它,所有的交流方式都将陷入混乱的信息和不连贯的含义中。
矛盾的是,通过忽略信息的意义,通过展示“意义”对实际信息来说是多么微不足道,香农给了世界真正的意义和安全一致地处理大量数据的能力。
简单来说,信息论是一切的基础。
但是在香农之前,信息作为一种思想,一种可测量的量,一种适合于硬科学的对象,几乎没有什么意义。在香农之前,信息是一封电报,一张照片,一段文字,一首歌。香农之后,信息完全被抽象成比特。发送者不再重要,意图不再重要,媒介不再重要,甚至意义也不再重要:一次电话交谈,一份莫尔斯电报,一页侦探小说都被纳入一个共同的代码
在他的论文中,以及在他的余生中,香农向世界介绍了一系列全新的概念:
- 他将一条信息——二进制单位——命名为“比特”
- 他向我们展示了意义与信息无关。事实上,我们越关注意义,“信息中的噪音”就变得越大。
- 通过忽略噪音,他能够产生一种通用的传递和破译信息的方法。
- 最后,香农给我们留下了一个几乎无法理解的信息噪音术语。这个想法也将动摇科学、技术和数字时代的基础,并在随后的时代留下足迹。他称之为“信息熵”
在本系列的下一篇文章中,我希望揭开熵和“信息熵”的神秘面纱。然而,正如任何研究过熵的人都会告诉你的,热力学第二定律是不可能确定一个定义的。然而,熵在混沌和复杂性理论中扮演着重要的角色,而混沌和复杂性理论是人工智能的核心。
关于作者:
泰德·格罗斯是“假设-假设”的联合创始人兼首席执行官。Ted 担任 R&D 首席技术官兼副总裁多年,擅长数据库技术,专注于 NoSQL 系统、NodeJS、MongoDB、加密、人工智能、颠覆、混沌和复杂性理论以及奇点事件。他在虚拟世界技术领域有超过 15 年的专业经验,在增强现实领域有 6 年的经验。Ted 继续在专业学术期刊和脸书 If-What-if Group 、 Medium 和 LinkedIn 网站上撰写许多关于技术主题的文章。你也可以在这里或者在 Substack 上注册免费的 If-What-If 时事通讯。
参考资料:
1.维基百科(未注明)“信息论”,可在:https://en.wikipedia.org/wiki/Information_theory(2021 年 7 月 29 日访问)。
2.维基百科(未注明),思想法则,可在:https://en.wikipedia.org/wiki/The_Laws_of_Thought(2021 年 8 月 19 日访问)。
3.维基百科(未注明)“布尔函数”,可在:https://en.wikipedia.org/wiki/Boolean_function(2021 年 8 月 19 日访问)。
4.艾萨克森,w .(2014)“创新者:一群黑客、天才和极客如何创造数字革命”,西蒙&舒斯特,纽约,纽约,Kindle 版,位置 943。
5.维基百科(未注明)‘布尔函数’,可在:https://en.wikipedia.org/wiki/Boolean_function获得(2021 年 8 月 19 日访问)。
6.c . Shannon(1948)“通信的数学理论”,贝尔系统技术杂志,第 27 卷,7 月/10 月,第 379–423 页
7.艾萨克森,参考。4 以上,位置 943
8.斯通,j . v .(2018)信息论:教程介绍,塞伯特出版社,Kindle 版,位置 82
9.香农裁判。6 以上。
10.Gleick,J. (2011) ' The Information ',纽约,万神殿图书公司,Kindle 版,位置 66。
11.香农裁判。6 以上。
12.斯通,参考文献 8,地点 359。
13.Soni,j .和 Goodman,r .(2017)“一种思维在发挥作用:克劳德·香农如何发明了信息时代”,西蒙&舒斯特,纽约,纽约,Kindle 版,位置 69。
*为了与 Medium 的披露政策保持一致,上面列出的所有亚马逊图书链接都是假设分析的附属链接。
信息论和集成模型
原文:https://towardsdatascience.com/information-theory-and-ensemble-models-ded31db10d8

图片由 Pexels 提供
我们应该如何更好地集成时间序列预测?
从疫情出来后,统计学家们遇到了一系列地缘政治难题,进一步加大了准确预测商业变量的能力。乌克兰在 2022 年推动零售价格上涨,还是 2021 年的量化宽松是罪魁祸首?一些模型说一套,一些说另一套,这使得准确预测通货膨胀变得困难。
在计量经济学的基础上,我们依赖于在同一个域(时间,频率)中最小化两点(预测,实际)之间的距离(MSE,RMSE 等)。所有这些都极大地提高了预测的准确性。这些指标受欢迎有几个原因:
- 他们是非参数。这意味着使用不同假设和结构开发的模型都可以进行比较,因为最终输出是相同的,即准确性。
- 从历史上看,这些指标提供了足够的残差分布变化,让我们可以对不同的模型进行分类,并对它们的性能进行聚类。例如,它们帮助我们回答一类 ARMA 模型更适合数据还是一类状态空间模型。更一般地说,它有助于对最适合的算法进行分类。
- 通常,它们位于欧几里德几何中,并具有良好的属性,使得在它们之上构建更新更复杂的方法变得更加容易。测量的拓扑结构不会改变。这释放了对数据、模型或两者进行转换和表示的潜力,以提取更深层次的关系。现代 ML 模型就是利用这一关键特性构建的。WLOG,为了本文的目的,我们将集中讨论简单的经济计量模型。
自 40 年代后期以来,这些好处极大地帮助改进了统计软件包,以至于今天我们有了更通用的软件包,能够选择最佳拟合模型,而用户甚至不必假设结构。
然而,任何情况下都是如此,继续优化相同的度量标准,每次迭代带来的改进越来越少。衡量距离的指标不再能够在优化的模型之间提供足够的分离,从而很难对性能进行排名。
那我们该怎么办?
潜在的解决方案有两种不同的途径:
- 在相同的拓扑结构中提出新的指标可以改进最佳位分类——社区已经提出并广泛使用新的指标,如 AIC、AICc、BIC、BICc 等,但这些指标通常是特定于模型的,因为它们可能允许我们在彼此内部对 ARMA 模型进行排序,但无法比较 ARMA 与 ETS 模型。
- 提出新的几何和拓扑改进方法——我通过格兰杰因果网络探索了这一想法的一个版本,这是一项正在进行的工作,但仍显示出巨大的潜力(鉴于我对该主题的兴趣,可能我有偏见)。感兴趣的读者可以在这里找到关于我的方法的入门。
让我们首先从实际数据中的论点开始。我将分析通胀趋势和变量,以及我们使用现有模型预测 CPI 的准确度。几乎每个计量经济学家在考虑通货膨胀模型时都会用到以下变量:
- 消费者价格指数同比增长——消费者通货膨胀的衡量标准;需要面
- 生产者价格指数同比增长 —生产者通货膨胀的衡量标准;供应侧
- 储蓄率——储蓄占工作收入的百分比;衡量需求方的摩擦
- 企业库存 MoM Growth —企业每个月拥有的商品过剩库存;测量供应方的摩擦

下面是 4 个变量的双变量格兰杰因果图:

因果网络已经帮助解释了一些关于数据的关键细节。首先,储蓄率可以影响经济的需求方和供给方。其次,对生产者来说,通胀意味着对消费者的一些反馈。这些都是很好的健全检查,以确保我们正在考虑正确的变量。
更深入一点,让我们拟合一个通用的非参数风险值模型,并考虑一些对非因果和随机诱发冲击的敏感性:



曲线图显示了对拟合风险值模型的脉冲响应。
顺便说一下,第一个情节证实了臭名昭著的永久收入假说,该假说由米尔顿·弗里德曼于 1957 年概念化:今天人们储蓄的意外增加,比如说因为政府决定发放刺激支票,导致那些过剩的储蓄在随后的时期被注入经济,导致需求拉动型通货膨胀。它还会导致企业库存压缩,直到它们再次恢复到正常水平。
如果没有明确的理由迫使人们这样做,即使我们增加他们的可支配收入,我们也无法说服他们明天存更多的钱。消费者的未来预期在政策判断中起着至关重要的作用。
类似地,由于生产者经历了更高的生产成本,通过 PPI 的增加很明显,他们将其传递给消费者,导致供应驱动的通货膨胀,如最后一个图所示。
至少在方向上,VAR 模型证实了一些经济理论。不幸的是,21 世纪需要的是准确性,而不是方向。即使我们对定性数据有信心,计量经济学家也有微调决策和政策的任务。为什么美联储每个季度加息 75 个基点,而不是 65 个基点?
我们如何使用应用于相同数据的多个模型进行精确的测量?将 3 种不同的预测模型拟合到我们的通胀数据中,下面是一些衡量样本外预测准确性的指标。很明显,我们无法有效区分三种型号中的两种型号的性能。



模型组装
对预测集合的需求源于这样一个事实,即由于我们传统的准确性度量标准没有提供足够的性能分离,我们永远不能有把握地说一种类型的模型可以捕捉一组时间序列之间的真实和因果动态。所以,如果没有一个模型看起来是最好的,我们能不能以某种方式衡量所有模型的输出,作为一个组合的整体呢?
接下来的任务就是想出衡量预测的最佳方法,这又需要以某种形式衡量绩效差异。
回到起点。
这里我们回到概念化新指标的想法。
集合建模的问题在最近的文献中激发了许多解决方案,所有的解决方案都有一个特别的特点——它们从一个简单的想法中获得灵感,即当从不同的角度看待一个问题时,我们可以更多地了解这个问题,并调整我们的方式以适应增加的复杂性。
本着同样的精神,我想提出一个基于信息论的集合建模的框架,尽管这个框架还处于萌芽状态,还需要进一步修正。我将省去读者的数学框架,并试图完全出于实用的直觉来形成解释。
令人生畏的希腊符号已经吓走了许多学习新学科的人,我不打算造成这种损失。我宁愿让 的观点 传遍读者,而不是 的行话 。对于那些想要框架的工程布局的读者来说,法兹罗拉·m·礼萨的《信息论》是一个很好的入门资料,可以直观地类比计量经济学中的问题。
信息论
传统的预测方法包括 ARIMAX、指数平滑、多项式回归、调和回归、状态空间模型,其中每一种都需要对时间序列本身的结构进行假设。在信息论术语中—
A.这些方法中的每一种都观察一个发送信息的源。
B 。理解了信息的本质后,他们试图预测来自信息源的未来信号。
C.如果这些方法能够很好地理解声源的动态,他们将能够在未来更好地预测,除了传输中的一些随机噪声之外,不会浪费任何信息。
如果我们可以量化这种信息的测量,或者它的缺乏,也许我们可以开始建立量化的集合方案来优化。
让我们首先定义这种信息度量:

Shanon 熵的定义
其中𝑓̂(𝜆)是数据的谱密度的估计。这一指标看起来可能很复杂,但却是法兹罗拉·m·礼萨(Fazlollah M. Reza)专业强调的组合逻辑的自然产物。
频谱密度是在频域中定义的一种度量,它描述了承载信息的信号在不同频率上的分布密度:


就像度量两点之间距离的度量一样,熵也具有使其成为一个有用度量的属性。例如:
- 它是非参数化的。熵以[0,1]为界。数据越接近白噪声,也就是没有可辨别的信息,熵值就越接近 1。如果我们擅长预测信息,残差应该看起来更接近白噪声。
- 它不会改变拓扑。只需要一个傅立叶变换,它是一个内射变换(即,它只是重塑数据中的信息,而不是弯曲或扭曲它)。
这就只剩下的关键属性提供了待测试模型之间的性能分离。

瞧啊。所有三种型号的性能差异。下面是一个粗略的方案,介绍了基于熵的推断,以估计 3 个模型之间的集成权重。

需要注意的是,以上是一个推理方案,而不是一个优化方案。用户指定模型权重应该满足什么熵阈值,而优化集成将找到最大化残差熵的权重;这将需要一个带有适当约束的目标函数来确保解决方案。
熵优化是自然的下一步,但超出了本文的范围。
这个方案的直觉如下:我们正在估计我们可以说模型 X 是我们发出信息的来源的一个很好的代表的概率。那个信息度量是熵。将我们的方案付诸实施,以下是一些来自通货膨胀数据的结果:


对于熵推理集成,我将最小熵阈值设置为 0.75,有趣的是,预测残差的样本外熵要高得多,精度性能与基于距离的集成相当。然而,它的熵仍然低于基于距离的集合,这意味着在残差中仍然有我们的集合没有解析出来的信息。我们的新组合不如人意有几个原因:
- 降低训练的熵阈值。由于这是一个基于推理的模型,所以不同的训练阈值组合会产生更高的样本外残差熵也就不足为奇了。
- 基于距离的集成是无界的 而熵集成是有界的。对于这个项目,距离模型分配的集合权重可能变成负值或放大,并可能导致过度拟合。
- 没有足够的独特型号用于组合。这个练习只看了三个模型来一起合奏;这些模型的系统熵相关的劣性也将为集合增加劣性。幸运的是,集合方案可以扩展到 N 个模型。

基于距离的集成

基于熵的集合-模型 1 与模型 2 共享相同的权重,之后与模型 3 共享相同的权重。因此它的重量在图中被掩盖了

基于熵的集成中模型 1 的权重
以下是两个群体使用他们自己的数据做出的与圣路易斯美联储有关的通货膨胀预测。

总的来说,社区需要退后一步,用新的视角来看待预测问题,这可以为我们如何建模解决方案以及我们当前的解决方案的效率提供许多进步。熵就是这样一种方法,而且还有很长的路要走。
我希望读者能够在他们正在解决的问题中找到相似之处,并评估拓扑结构的变化或像熵这样的新指标是否能帮助他们更接近解决方案。
除非另有说明,所有图片均由作者提供。
韦丹特·贝迪是万事达卡的分析师,在不结盟运动投资组合开发团队工作。他拥有 NYU 大学的数学和经济学学士学位,对数据科学、计量经济学及其在金融领域的许多应用有着浓厚的兴趣。
Vedant 还是美国历史最悠久的学术荣誉协会 Phi Beta Kappa(纽约分会)的成员。
信息论在世界上的应用
原文:https://towardsdatascience.com/information-theory-applied-to-wordle-b63b34a6538e
应用人工智能
玩流行游戏 Wordle 的简单算法

除非特别说明,所有图片均由作者提供
沃尔多在 2022 年已经火了。你可能在社交媒体上看到过这些绿色琥珀和灰色瓷砖。Wordle 是一个简单而有趣的游戏,你有 6 次机会猜对 5 个字母的单词。每次你做一个猜测,你都会收到关于最终答案的信息。在这篇文章中,我将向你展示我如何制作一个机器人,它几乎每次都能在 4 到 5 次猜测中解决所有可能的单词。
介绍

GIF 1:演 Wordle 的算法
就像 Mark Rober 会说的,我在 Wordle 上很烂,但我擅长数据科学,这意味着我实际上非常擅长 Wordle!
如果你读过我最近的文章,你可能会认为我已经用强化学习解决了这个问题。然而,这可能有些矫枉过正。对于所有数据科学问题,您希望首先尝试最简单的方法。你能得到的最简单的解决方案通常是最划算的。这可能是由于更少的培训时间、更短的交付时间、更简单的代码等。希望这种说法会阻止你们中的一些人尽可能多地应用深度学习。
该算法的灵感来源于 3blue1brown 的视频关于这个话题,请查看他的频道,他制作了令人惊叹的视频。
信息论
信息论广泛应用于数据挖掘和机器学习领域。在它的许多应用中,例如数据挖掘问题中的特征选择、数据压缩和合成数据的评估。
什么是信息?
信息是用比特来衡量的。当测量信息时,我们测量的是比特的数量,或者是为了得到相同的结论我们必须问的“是或否”的问题的数量。让我们看一个例子:

图 1 棋盘信息示例
如果你拿一个棋盘,我让你选一个方块,你的答案包含了多少信息?我们要问你多少是或否的问题才能知道你在董事会的位置?

图 2:棋盘 1 位信息
我们可以先问你是否在棋盘的左边。然后我们可以问你是在那一半的上面还是在下面,等等。

图 3 棋盘 3 位信息
要知道这个位置,我们必须问 6 个问题,这意味着这个位置包含 6 位信息。
既然有 64 个方块,那么在任何一个方块中的概率是 1/64。我们可以得出一个作为事件概率函数的位数公式:

等式 1:信息是概率的函数
在棋盘的例子中,由于有 64 个方格,信息= -log₂ 1/64 = 6 位,得出与前面相同的结论。
熵
我们看了国际象棋的例子,你可能会说落在每个方格的概率是相同的。但是如果每个方块有不同的概率,概率分布为 p(x)。对于每个位置 x,我们可以有不同的概率,这意味着每个方块上的信息会不同。我们可以给每个 x 分配一个信息值。
回到国际象棋的例子,如果方块有相同的概率(1/64),那么信息的期望值显然是 6 位。然而,如果它们具有不同的概率,则可以这样计算期望信息:

等式 2:香农熵,信息的期望值
这种期望是有用的,因为它告诉我们,通过知道棋盘上的位置,我们将平均接收到多少信息。概率分布 p(x)上信息的期望值称为香农熵。
沃尔多
好了,够了,让我们解决沃尔多!
规则
Wordle 的目标是猜测 5 个字母的单词。你有 6 次猜测的机会,每次你猜的时候,你都会收到关于隐藏单词的信息,这取决于你猜的是什么。
回答后,每个字母将是绿色,琥珀色或灰色。绿色表示字母在正确的位置,琥珀色表示字母在单词中但不在那个位置,灰色表示字母不在隐藏单词中。
解决单词
在 Wordle 中,我们试图确定隐藏的 5 个字母的单词。我下载了一个由 5757 个五个字母组成的英语单词列表,我想这就是全部了。
在游戏的开始,有(log₂(5757))12.49 位的隐藏信息/不确定性。当你玩游戏时,你获得了关于隐藏单词的信息。目标是创建一种算法,在每次猜测中选择增加最多信息的单词(最大程度地减少不确定性)。

图 4:答案很棒的单词
看上面的 wordle 游戏。最初的猜测是“稗子”。猜测之后,我们得到了隐藏单词的信息。我们知道隐藏的单词包含字母“T”,“A”,“R”,“E”,而且这些字母的位置不对。有了这些信息,我们可以缩小可能的单词的搜索范围。事实上,英语中只有 12 个单词符合这些标准。其中有“心”,“额外”,“警觉”,“反应”,“光栅”,“硼酸”和“伟大”。如果你好奇的话,这里有一个完整的列表:

图 5:猜“稗子”后的单词
通过我们最初的猜测,我们已经将不确定性从 12.5 位(5757 个可能的单词)降低到 3.6 位(12 个单词),这意味着我们的猜测使我们获得了 8.9 位的信息。在第二个猜测“orate”之后,唯一可能的词是 great。
所以我们的目标是选择尽可能减少我们可能的单词列表的单词。这是一个通过猜测获得最大信息量的词。在不知道答案的情况下,我们无法知道获得的信息,因此,我们猜测获得的信息的期望值最大的单词!
最大化熵
所以我们在寻找具有最大熵的单词,也就是猜测后获得的最大预期信息。
每次你在 Wordle 中猜测都有⁵可能的结果。你可以得到所有灰色,4 灰色 1 琥珀色,4 灰色 1 绿色等。这些可能的结果中的每一个都将产生一个新的可能单词列表。
就拿“模糊”这个词来说,如果我们看不含字母“F”、“U”、“Z”、“Y”的词,总共有 3543/5757 个(占全部 5 个字母词的 62%)。这意味着,如果我们猜测单词“fuzzy ”, 62%的情况下我们会得到这样一个单词:

图 6:作为初始猜测的“模糊”
我们可以计算所有可能结果的概率,将它们从最高概率到最低概率排序,并绘制分布图:

图 7:单词“模糊”的分布
上面是单词“Fuzzy”的分布,其中每个条形代表一种可能的结果(最左边代表所有字母为灰色的概率)。在这种情况下,所有灰色是获得的信息量最少的结果,理想情况下,我们希望猜测尽可能多地减少可能的单词数。“模糊”显然不是在 Wordle 中用作初始猜测的最佳词汇。

图 8:单词“tares”的分布
现在看看“tares”作为初步猜测。全灰的概率只有 7%左右。因此,93%的情况下,我们至少对其中一个字母有所了解。
有了这些概率,我们就可以计算出每个结果所获得的信息。然后,我们可以使用我之前展示的公式,获得每个单词所获得信息的期望值。
单词“fuzzy”具有 2.28 比特的预期信息增益,而单词“tares”具有 6.21 比特的预期信息增益。这意味着平均来说,单词“fuzzy”会将我们的可能单词列表从 5757 减少到 1185,而“tares”会将其减少到 78。
该算法
所以我们知道如何计算每个单词的期望信息。现在,我们需要做的就是迭代计算所有单词的期望信息,然后猜测熵最高的单词,直到我们得到正确的答案。算法是这样的:
- 计算所有可能单词的熵
- 选择熵值最高的单词
- 向 Wordle 提交 guess 并获取输出
- 更新剩余可能单词的列表
- 重复步骤 1-4,直到猜测与答案相同
让我们来看看模型的性能:

图 9:算法的性能
我让这个算法用英语中最流行的 5 个字母的单词玩 50 个单词的游戏。该模型在大约 4 或 5 次猜测中完成大部分游戏。在一些游戏中,要猜中正确的单词需要猜很多次。总的来说,我对它的表现很满意,尽管肯定还有改进的空间。
这是它播放的最后一张 GIF,因为看起来非常令人满意:

GIF 2:演 wordle 的算法
结论
这篇文章是对信息论以及我如何应用它来解决 Wordle 的一个小介绍。我解释信息论和熵的基础。通过选择具有最高预期信息(最高熵)的单词,我的算法解决了大多数单词游戏,平均不到 5 次猜测。显然还有改进的空间,例如,可以根据单词在英语中的受欢迎程度来给它们增加权重。
信息论中还有许多其他概念,我可以在未来探索,比如交叉熵、条件熵、互信息等等,但我会把这些留到下次。
格兰特·桑德森关于这个话题的视频给了我很大的灵感。看看他的频道,他制作的视频很棒。
支持我
希望这对你有帮助,如果你喜欢它,你可以 跟我来!
您也可以成为 中级会员 使用我的推荐链接,访问我的所有文章和更多:https://diegounzuetaruedas.medium.com/membership
你可能喜欢的其他文章
基础设施作为代码
原文:https://towardsdatascience.com/infrastructure-as-code-f153d810428b
用代码管理基础设施资源

在我的工程之旅中,我意识到我们使用的工具总是会改变,这是技术不断进步的结果。我认为我们都需要接受这一现实,以便能够跟上能够真正影响我们构建系统和应用程序的技术。
在过去十年中,全球大多数公司都不得不经历一次技术提升(也称为数字化转型),从内部基础架构迁移到云。我相信在这样的项目中,最大的困难是让人们相信一项新技术(比如云)应该以一种不同于你所习惯的方式来管理。可以帮助您管理云(甚至本地)资源的最佳工具之一是基础设施即代码。
什么是基础设施即代码(IaC)
基础设施即代码是 DevOps 采用的一种方法,它让团队能够以编程方式管理基础设施资源(如计算、存储和网络资源)。换句话说,IaC 可以帮助您将基础设施定义和部署为连续交付机制的一部分。
像技术中的几乎所有东西一样,IaC 工具发展得相当快,现在大多数现代工具都使用人类和机器可读的代码。这是一个非常重要的特性,因为它支持各种基础设施组件的可重用性。

谷歌趋势上的基础设施代码——来源:作者
IaC 的优势
基础设施作为代码范例的最大优势是一致性。如果没有 IaC,组织需要以手动方式维护基础架构资源部署或修改。
这意味着,在某些时候,要重现这种配置是极其困难的,因为需要一些特别的步骤,而其他一些步骤是以不同的顺序执行的。基础设施即代码通过允许用户使用代码来表示基础设施环境来加强一致性。因此,资源的部署和修改将总是一致和幂等(即每次执行一个特定的操作,都会产生相同的结果)。
此外,IaC 工具将节省您的时间和精力,否则这些时间和精力将花费在变更的手动部署上。现代 IaC 工具利用声明性代码文件,用户可以在其中描述组织内基础设施的最终状态。这意味着您不必太担心底层的逻辑,因为达到最终状态所需的操作将由 IaC 工具自动执行。
此外,IaC 工具通常提供增强可重用性的机制。这个特性使您的代码库不那么冗长,可读性更好,同时鼓励团队成员应用最佳实践。
IaC 的另一大优势是协作。由于基础设施资源是在配置文件中定义的,这意味着这些文件可以进行版本控制。在任何给定的时间,团队都能够协作来修改环境,甚至能够查看基础架构资源的历史(来自提交)。这使得调试更加容易和准确。
使用 Terraform 将基础设施作为代码
DevOps 世界中最流行的 IaC 工具之一是 HashiCorp Terraform ,这是一个开源工具,您可以使用它在声明性配置文件中定义和更新您的基础设施资源。
该工具可用于定义内部和云资源,并管理您组织的基础架构的生命周期。从本质上讲,Terraform 工作流程由三个不同的步骤组成,即编写、计划和应用。
- 写:这个步骤涉及资源定义(本地或云)。这些资源可以是存储和计算等低级组件,也可以是 SaaS 和 DNS 条目等更高级别的组件。
- 计划:在这一步,Terraform 将准备一份执行计划,其中将描述所有涉及的步骤。这些步骤可能与基于上一步中所做的更改的资源的创建、更新或删除相关。
- 应用:在最后一步中,Terraform 将执行(在用户确认/批准后)计划步骤中描述的操作。例如,如果您更新虚拟专用云网络,那么 Terraform 会根据新配置重新创建资源。
事实上,Terraform 社区已经编写了 1700 多个不同的提供者,可以用来管理大量不同类型的资源。此外,该工具允许用户定义他们自己的定制模块,这也可以增强代码库中的可重用性。
除了 IaC 功能,Terraform 还可用于多云部署、Kubernetes 和网络基础设施管理、策略即代码(PoC)和虚拟机映像管理。
其他 IaC 工具
Ansible 是另一个提供 IT 自动化的流行工具。
它处理配置管理、应用部署、云供应、临时任务执行、网络自动化和多节点协调。Ansible 使复杂的变化变得简单,如使用负载平衡器的零停机滚动更新
Chef 还提供基础设施管理自动化工具:
Chef Infrastructure Management 使 DevOps 团队能够跨任何云、虚拟机和/或物理基础架构建模和部署安全且可扩展的基础架构自动化。
除了第三方工具,一些云提供商还提供本地 IaC 工具。
Deployment Manager 是 Google 云平台上的原生 IaC 管理工具,可以帮助您自动管理云上的基础设施资源。
微软 Azure 提供了对基础设施作为代码的原生支持,Azure Resource Manager (ARM)允许开发人员将基础设施资源定义为 ARM 模板。
最后的想法
作为代码的基础设施是能够真正影响团队构建、测试和维护系统和应用程序的方法之一。有各种工具可以帮助您定义基础架构,并持续一致地部署资源。
IaC 可以帮助您摆脱基础架构资源的手动配置和部署,并提供一种自动化(最重要的是一致性)的方式来实现这一点。
此外,使用 IaC,您能够对定义基础设施资源的文件进行版本控制,并实施最佳实践,如模块化和代码可重用性。
最后,我们浏览了一些最受欢迎和最常用的基础设施,如代码工具,包括 Terraform、Ansible、Chef,甚至是一些由云提供商(如亚马逊、谷歌和微软)原生提供的工具。
成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。
https://gmyrianthous.medium.com/membership
相关文章你可能也喜欢
在云之前摄取元数据
原文:https://towardsdatascience.com/ingesting-metadata-before-the-cloud-3a8c4b063d8b
使用 Spark、Hive 和 OpenMetadata
这可能感觉像是那些以“从前”开始的故事之一,但并不是每个人都(还)在云上。然而,元数据已经并将永远存在。我们只需要学会如何充分利用它。
虽然数据旨在让组织深入了解其业务,但元数据有助于团队确保他们的数据可靠且有意义。此外,元数据允许我们缩小数据和人之间的差距,深入到数据所有权、使用、和治理等主题。
元数据摄取是解开这个巨大价值链的第一步。在本文中,我们将回顾过去,准备一个本地 Spark 应用程序,将其数据存储到外部 Hive metastore 中。然后,我们将使用 OpenMetadata 将生成的元数据提取到一个集中的平台中。

解决方案图。图片由作者提供。
本地设置
我们将按照这里列出的步骤,使用更新版本:
- Java 11(使用 sdkman 安装)
- Hadoop 3.3.4
- 蜂巢 2.3.9 (稳定发布)
目标是提升 Hive ,它在底层使用 Hadoop。
下载完软件包后,你可以用下面的exports更新你的.zshrc或.bashrc文件,我在这里找到了~/dev/下的文件:
export HIVE_HOME=~/dev/apache-hive-2.3.9-bin
export HADOOP_HOME=~/dev/hadoop-3.3.4export PATH=$HIVE_HOME/bin:$PATH
export PATH=$HADOOP_HOME/bin/hadoop:$PATH
Hadoop 配置
对于 Hadoop,我们将遵循官方指南中的伪分布式操作:
- 编辑
$HADOOP_HOME/etc/hadoop/core-site.xml
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://localhost:9000</value>
</property>
</configuration>
- 编辑
$HADOOP_HOME/etc/hadoop/hdfs-site.xml
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
</configuration>
- 运行
$HADOOP_HOME/sbin/start-all.sh在本地启动 datanodes 和 namenodes。
~/dev/hadoop-3.3.4 ❯ $HADOOP_HOME/sbin/start-all.sh
WARNING: Attempting to start all Apache Hadoop daemons as pmbrull in 10 seconds.
Starting namenodes on [localhost]
Starting datanodes
Starting secondary namenodes [Peres-MBP.lan]
Starting resourcemanager
Starting nodemanagers
注意,这最后一步需要您的用户在没有密码的情况下 SSH 到localhost。如果你不能ssh localhost,那么你需要跑:
ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 0600 ~/.ssh/authorized_keys
如果您是 macOS 用户,您可能需要在“系统偏好设置”中启用远程登录:

在 OSX 系统偏好设置中设置远程登录。图片由作者提供。
蜂巢配置
按照设置指南,我们将在 HDFS 创建tmp和 metastore 仓库目录,并具有在其中写入数据的必要权限:
$HADOOP_HOME/bin/hadoop fs -mkdir /tmp
$HADOOP_HOME/bin/hadoop fs -chmod g+w /tmp
$HADOOP_HOME/bin/hadoop fs -mkdir -p /user/hive/warehouse $HADOOP_HOME/bin/hadoop fs -chmod g+w /user/hive/warehouse
下一步将是设置 Hive 存储关于表的元数据的位置。最后,我们将使用一个本地 MySQL 实例,在这里我们将执行一个模式迁移来准备所需的数据库结构。
- 在 MySQL 实例中,创建一个数据库并向用户提供:
mysql> create database demo_hive;
Query OK, 1 row affected (0.01 sec) mysql> create user APP identified by 'password';
Query OK, 0 rows affected (0.01 sec) mysql> GRANT ALL PRIVILEGES ON demo_hive.* TO 'APP'@'%' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)
- 根据提供的模板准备您的
hive-site.xml文件:
cp $HIVE_HOME/conf/hive-default.xml.template $HIVE_HOME/conf/hive-site.xml
- 并编辑具有以下属性的
$HIVE_HOME/conf/hive-site.xml:
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:mysql://127.0.0.1:3306/demo_hive</value>
</property>
<property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>APP</value>
</property>
<property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>password</value>
</property>
<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>com.mysql.cj.jdbc.Driver</value>
</property>
<property>
<name>hive.metastore.warehouse.dir</name>
<value>hdfs://localhost:9000/user/hive/warehouse</value> </property>
注意,我们在这里选择的是 MySQL JDBC 驱动程序。你可以从这里下载,放在$HIVE_HOME/lib下。
- 我们现在可以运行
schematool来用上面的配置填充数据库:
$HIVE_HOME/bin/schematool -dbType mysql -initSchema
- 最后,让我们从以下内容开始 metastore:
$HIVE_HOME/bin/hive --service metastore
该命令将在localhost:9083启动本地运行的配置单元 metastore。
创建数据(和元数据)
我们现在将使用 Spark 3.3.0 (为 Hadoop 3.3+预构建)来创建和保存一些数据,并探索不同的成分是如何涉及的。
请注意,您也可以准备此导出:
export SPARK_HOME=~/dev/spark-3.3.0-bin-hadoop3/
这里唯一的要求是将 Spark 指向外部 Hive metastore。我们可以通过编辑$SPARK_HOME/conf/hive-site.xml来做到这一点,如下所示:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>hive.metastore.uris</name>
<value>thrift://localhost:9083</value>
</property>
</configuration>
然后,创建一个指向正确的 metastore jars 和在设置 Hadoop 时配置的仓库目录的 Spark shell:
$SPARK_HOME/bin/spark-shell \
--jars \
$HIVE_HOME/lib/hive-metastore-2.3.9.jar,\
$HIVE_HOME/lib/hive-exec-2.3.9.jar,\
$HIVE_HOME/lib/hive-common-2.3.9.jar,\
$HIVE_HOME/lib/hive-serde-2.3.9.jar,\
$HIVE_HOME/lib/guava-14.0.1.jar \
--conf spark.sql.hive.metastore.version=2.3 \
--conf spark.sql.hive.metastore.jars=$HIVE_HOME"/lib/*" \
--conf spark.sql.warehouse.dir=hdfs://localhost:9000/user/hive/warehouse
一旦进入外壳,让我们具体化一个样本数据帧:
import spark.implicits._val someDF = Seq(
(8, "bat"),
(64, "mouse"),
(-27, "horse")
).toDF("number", "word")// Convert the DataFrame into a table we can reach
// from within Spark SQL
someDF.createOrReplaceTempView("mytempTable")// Materialize the table
spark.sql("create table some_df as select * from mytempTable");
一旦这些操作成功运行,我们就可以放大体系结构来验证发生了什么:
- 当我们定义指向 HDFS 路径
/user/hive/warehouse的spark.sql.warehouse.dir时,我们可以看到数据文件确实出现在我们的新表some_df中:
❯ $HADOOP_HOME/bin/hadoop fs -ls /user/hive/warehouse/some_df
Found 3 items
-rwxr-xr-x 3 pmbrull supergroup 6 2022-08-27 22:11 /user/hive/warehouse/some_df/part-00000-e99ca76a-477c-47d3-829a-c4aa4e03c3a3-c000
-rwxr-xr-x 3 pmbrull supergroup 9 2022-08-27 22:11 /user/hive/warehouse/some_df/part-00001-e99ca76a-477c-47d3-829a-c4aa4e03c3a3-c000
-rwxr-xr-x 3 pmbrull supergroup 10 2022-08-27 22:11 /user/hive/warehouse/some_df/part-00002-e99ca76a-477c-47d3-829a-c4aa4e03c3a3-c000
- 当 Hive metastore 插入 MySQL 实例时,关于新表的元数据也在那里创建:

保存 metastore 数据的 MySQL 实例。图片由作者提供。
元数据摄取
在最后一步中,我们将使用 OpenMetadata 将我们刚刚生成的元数据接收到这个开源元数据管理解决方案中。
数据和元数据在公司中最受欢迎。
目标是打破元数据孤岛,将组织聚集在一起,在单一位置进行协作。最简单的开始方式是使用本地 Docker 部署。
设置完成后,我们可以继续配置一个 DeltaLake 服务。

三角洲湖服务配置。图片由作者提供。
注意,当 OpenMetadata 在 Docker 内部运行时,Hive Metastore 存在于我们的本地。为了到达主机的网络,我们需要将 url 写成
host.docker.internal
按照准备摄取管道的步骤,我们可以定期将 metastore 上的任何更改带到 OpenMetadata。第一次摄取后,可以浏览和共享新创建的:

OpenMetadata 中摄取的元数据。图片由作者提供。
让我们的数据可被发现是转向数据产品的第一步。共享的数据可以讨论和改进。
摘要
在本帖中,我们有:
- 配置 HDFS 存储数据,
- 创建了一个插入 MySQL 实例的外部 Hive Metastore,
- 用 Spark 生成了一些数据,
- 使用 OpenMetadata 摄取了元数据。
管理数据架构的整个生命周期使我们能够更好地理解这个世界中涉及的所有成分,在这个世界中,云提供商抽象了大部分内容,解决方案看起来几乎像魔术一样。
参考
插入排序解释——数据科学家算法指南
原文:https://towardsdatascience.com/insertion-sort-explained-6ff0fa1cb15c

特雷弗·瓦诺伊在 Unsplash 上的照片
介绍
算法在数据科学和机器学习领域很常见。算法驱动着社交媒体应用、谷歌搜索结果、银行系统等等。因此,数据科学家和机器学习从业者拥有分析、设计和实现算法的直觉是至关重要的。
当应用于大规模计算任务时,高效的算法为公司节省了数百万美元,并减少了内存和能耗。本文介绍了一个简单的算法,插入排序。
尽管知道如何实现算法是必不可少的,但本文还包括数据科学家在选择使用时应该考虑的插入算法的细节。因此,本文提到了算法复杂性、性能、分析、解释和利用等因素。
为什么?
重要的是要记住为什么数据科学家应该在解释和实现之前研究数据结构和算法。
数据科学和 ML 库和包抽象了常用算法的复杂性。此外,由于抽象,需要数百行代码和一些逻辑推理的算法被简化为简单的方法调用。这并没有放弃数据科学家研究算法开发和数据结构的要求。
当给定一组要使用的预构建算法时,要确定哪种算法最适合这种情况,需要了解基本算法的参数、性能、限制和鲁棒性。数据科学家可以在分析后了解所有这些信息,在某些情况下,还可以重新实现算法。
选择正确的特定问题算法和排除算法故障的能力是理解算法的两个最重要的优势。
K-Means ,BIRCH 和 Mean Shift 都是常用的聚类算法,绝不是数据科学家拥有从头实现这些算法的知识。不过,数据科学家有必要了解每种算法的属性及其对特定数据集的适用性。
例如,基于质心的算法适用于高密度数据集,其中聚类可以被清楚地定义。相比之下,在处理有噪声的数据集时,基于密度的算法,如 DBSCAN(含噪声的应用程序的基于密度的空间聚类)是首选。
在排序算法的环境中,数据科学家会遇到数据湖,如果包含的数据经过排序,数据库遍历元素以确定关系会更有效。
确定适用于数据集的库子程序需要理解各种排序算法和首选数据结构类型。快速排序算法在处理数组时是有利的,但是如果数据以链表的形式出现,那么合并排序会更有效,尤其是在大型数据集的情况下。不过,两者都使用分而治之的策略来排序数据。
背景
什么是排序算法?
排序问题是数据科学家和其他软件工程师面临的一个众所周知的编程问题。排序问题的主要目的是按升序或降序排列一组对象。排序算法是执行的顺序指令,用于有效地对列表中的元素进行重新排序,或者按照期望的顺序排列。
排序的目的是什么?
在数据领域,数据集中元素的结构化组织支持高效遍历和快速查找特定元素或组。在宏观层面上,用高效算法构建的应用转化为引入我们生活的简单性,例如导航系统和搜索引擎。
什么是插入排序?
插入排序算法包括基于列表中每个元素与其相邻元素的迭代比较而创建的排序列表。
指向当前元素的索引指示排序的位置。在排序开始时(index=0),将当前值与左边的相邻值进行比较。如果该值大于当前值,则不修改列表;如果相邻值和当前值是相同的数,情况也是如此。
但是,如果当前值左侧的相邻值较小,则相邻值位置将向左移动,并且只有在其左侧的值较小时才停止向左移动。
该图示出了在未排序列表上的插入算法中采用的过程。下图中的列表按升序排序(从最低到最高)。

算法步骤和实现(Python 和 JavaScript)
步伐
若要按升序对元素列表进行排序,插入排序算法需要以下操作:
- 从一个未排序的元素列表开始。
- 遍历未排序元素的列表,从第一项到最后一项。
- 在每一步中,将当前元素与左侧所有先前位置的元素进行比较。
- 如果当前元素小于前面列出的任何元素,它将向左移动一个位置。
用 Python 实现
# Initialise the unsorted list
A = [3, 6, 2, 7, 2, 7, 1, 6, 3]# Iterate for the entire length of the list
for i in range(0, len(A)):
# Initialise the key variable to hold the current value
key = A[i]
# Initialise a variable that references the position to the left
j = i - 1
# Execute instructions within while loop if conditions are met
# Condition1: Index of element to the left is greater than 0 (ensure we don't have negative indexes)
# Condition2: The value of the element to the left of the current value is greater than the current value
while j >=0 and A[j] > key:
A[j + 1] = A[j]
j = j - 1
A[j + 1] = key
print(A)
用 JavaScript 实现
// Initialize the unsorted list
var A = [3, 6, 2, 7, 2, 7, 1, 6, 3];
// Iterate for the entire length of the list
for (let i = 0; i < A.length; i++) {
// Initialize the key variable to hold the current value
var key = A[i];
//Initialise a variable that references the position to the left
var j = i - 1;
// Execute instructions within while loop if conditions are met
// Condition1: Index of element to the left is greater than 0 (ensure we don't have negative indexes)
// Condition2: The value of the element to the left of the current value is greater than the current value
while (j >= 0 && A[j] > key) {
A[j + 1] = A[j]
j = j - 1
}
A[j + 1] = key
}
console.log(A)
性能和复杂性
在计算机科学领域,“大 O 符号”是一种衡量算法复杂性的策略。在这里,我们不会对大 O 符号过于专业。尽管如此,值得注意的是,计算机科学家根据他们的时间和空间要求,使用这个数学符号来量化算法。
大 O 符号是根据输入定义的函数。字母“n”通常表示函数输入的大小。简单来说,n 代表列表中元素的数量。在不同的场景中,从业者关心函数的最坏情况、最好情况或平均复杂度。
插入排序算法的最坏情况(和平均情况)复杂度是 O(n)。这意味着,在最坏的情况下,对列表进行排序所需的时间与列表中元素数量的平方成正比。
插入排序算法的最佳时间复杂度是 O(n)时间复杂度。这意味着对列表进行排序所需的时间与列表中元素的数量成正比;当列表已经处于正确的顺序时就是这种情况。在这种情况下只有一次迭代,因为当列表已经按顺序排列时,内循环操作是微不足道的。
插入排序常用于排列小列表。另一方面,插入排序并不是处理包含大量元素的大型列表的最有效方法。值得注意的是,在处理链表时,插入排序算法是首选。尽管该算法可以应用于以数组形式构造的数据,但是其他排序算法,例如快速排序。
摘要
最简单的排序方法之一是插入排序,它涉及一次一个元素地构建一个排序列表。通过将每个未检查的元素插入到排序列表中小于它和大于它的元素之间。正如本文所展示的,这是一个很容易掌握的算法,可以在许多语言中应用。
通过清楚地描述插入排序算法,并伴随着所涉及的算法过程的逐步分解,数据科学家能够实现插入排序算法并探索其他类似的排序算法。
对于许多数据科学家来说,算法可能是一个敏感的话题。可能是由于题目的复杂性。“算法”这个词有时与复杂性联系在一起。有了适当的工具、训练和时间,即使最复杂的算法也很容易理解。算法是数据科学中使用的基本工具,不可忽视。
感谢阅读。
我希望这篇文章对你有所帮助。
要联系我或找到更多类似本文的内容,请执行以下操作:
- 成为推荐媒介会员,支持我的写作
- 订阅我的 邮件列表 获取我的简讯
- 通过 LinkedIn 联系我
- 在 Twitter 上获取我的实时更新
内部人工智能成熟度模型
原文:https://towardsdatascience.com/inside-ai-maturity-model-3ff645a484b3
以数据为中心的人工智能工程转型的五个步骤
人工智能/人工智能研究和发展的过去十年具有历史意义。从认知学习到智能分析,深度学习(DL)的演进开启了前所未有的人工智能新时代。最新的人工通用智能(AGI)和自我监督学习(SSL)也很有吸引力和前景。Gartner 估计,2022 年全球人工智能软件市场将达到 620 亿美元,比 2021 年增长 21.3%。IDC 预测,到 2024 年,人工智能研究和应用的投资将达到5000 亿美元。普华永道预测,到 2030 年,人工智能将为全球经济贡献15.7 万亿美元。

瑞尼尔山框架(作者)
AI 已经成为企业数字化转型和技术创新的必备技术。它可以显著提高组织和个人的性能和可伸缩性。在新冠肺炎危机中,人工智能的采用变成了一场疯狂的争夺。然而,驾驭和应用人工智能面临着多重挑战。当务之急是采用人工智能成熟度模型来指导和加速人工智能在商业和其他领域的采用。
什么是 AI 成熟度模型?
软件吞噬了世界。软件成熟度模型(又名能力成熟度模型, CMM )成为企业标准化和改进软件开发过程的必要框架。现在, AI 正在吃天下。人工智能成熟度模型对于人工智能的采用和改进也至关重要。
随着 AlphaGo 在 2016 年取得惊人的胜利,Gartner、Element AI、微软、IBM 和其他公司开始建立人工智能成熟度模型和框架。根据他们的观点,他们定义了不同的成熟度模型和框架,但是他们都是从低基础到高转换水平的曲线。有些人将这些层次分成多个维度,形成了 2D 模型网格。例如,元素 AI 为每个阶段(即级别)定义了五个维度(战略、数据、技术、人员和治理),从探索到试验、形式化、优化,再到转型。

Gartner 和微软人工智能成熟度模型解释(作者)
Gartner 根据人工智能成熟度模型在五个层面上衡量一个组织,从意识到活跃,运营,系统和转型。微软已经定义了四个成熟度等级,从基础到接近、理想和成熟。请参见上图中的详细描述。处于转型阶段的公司已经在使用人工智能来实现重大的商业价值。例如,Google 和 Meta 广泛使用 ML 来对页面/帖子和广告进行排名。ML 推荐的相关产品或电影已经成为亚马逊和网飞上客户的首选,这已经是旧闻了。
这似乎是一个有效的过程。如果我们执行它,我们就能逐步实现它。然而,在利用人工智能成熟度模型时,有两类挑战:人工智能转型的独特特征和企业人工智能采用的挑战。
人工智能转型的特点
软件和人工智能在成熟度模型和影响方面有一些相似之处。例如 DevOps vs. MLOps。但是 AI/ML 更复杂,更难扩展和操作。MLOps 可以解释为 ML 加 DevOps。AI 技术和变革呈现出五个独有的特点。
- 虽然人工智能在历史上并不新鲜,并且在过去十年里取得了巨大的飞跃,但人工智能本身仍处于早期阶段。目前的人工智能应用仍然局限于特定的问题,尽管将有充分的潜力在深度和广度上扩展人工智能应用。
- AI 和 ML 是基于学习的模型,依赖于数据、特征、超参数,甚至标签(用于监督学习)。因此,数据量和质量会显著影响人工智能的有效性。大数据是作为数据驱动的决策(BI 相关)和数据密集型深度学习(AI)的十年流行语创造的。
- 人工智能在算法、建模、基础设施和框架方面发展迅速。例如生成型 AI 几乎每半年就在从 BERT 升级到 RoBERTa,GPT-2,T5,TuringNLG,GPT-3,到 DALL E 和 DALL E 2。ML 计算需求的增长速度是摩尔定律的近 17.5 倍。需求的增加发生在处理和内存两个方面。
- 人工智能发展和变革的过程是复杂的。它经常涉及多个人一起工作:企业主、产品经理、数据工程师、数据科学家、ML 科学家、ML 工程师等。除了他们的密切合作,它可能需要 ML 专家来建模和解释。
- 可操作的人工智能远远落后于其快速多样的创新和应用。除了缺少工具,还需要推广。例如 MLflow 和 Kubeflow 都是开源的 ML 平台,需要托管和运营。而亚马逊 SageMaker 是专门做 AWS 云的。
企业人工智能采用的挑战
另一方面,越来越多的企业对人工智能感到兴奋,但在采用人工智能和利用人工智能进行转型方面面临多重挑战。我们可以把它分成五个部分。
- 组织共识:人工智能对许多组织来说是新的,理解和使用起来很复杂。这可能导致不一致的观点,并且在采用和开发上达成一致可能并不容易。有些人可能过于乐观,认为 AI 现在是一种全方位使用的技术,但另一些人可能过于悲观,认为 AI 不切实际或风险太大。自上而下的协议方式可能是高效的,但通常缺乏创新。
- 用例不明确:AI 在特定用例或领域问题上工作得很好,比如需求预测和人脸识别。对于复杂的企业流程和运营来说,确定合适的场景是首要任务。
- 数字强度不足:AI/ML 是基于学习的机制。数字化和数据收集应该是决定用例后的第一步。此外,曾经用于 BI 分析的数据可能对 AI 来说不够用。
- 团队准备度:根据不同的用例,它需要多样化的团队来实现 AI 转型,从业务到产品到工程和运营。
- 繁琐的流程:在今天,任何好的技术,如果动作不快,都会打折扣。对制造业和其他工业部门有效的过程可能不适合人工智能应用。
做好准备的五个有效步骤
那么如何解决这些问题呢?AI 成熟度模型还实用吗?好消息是,一些公司已经广泛使用人工智能来转变他们的业务。例如,谷歌和 Meta 已经用人工智能改变了广告游戏,因为广告是他们的主要收入来源。通过向谷歌、Meta、亚马逊、微软和苹果等人工智能先进公司学习,有五个实际步骤可以加快步伐。

准备好的五个步骤(作者)
- 组织人工智能思维:全公司范围的人工智能优先思维可以激励团队,加速实验和采用。这是一种以人工智能转型为目标的文化转变,包括数据驱动和人工智能支持的产品和服务。例如,自 2015 年以来,微软一直在全公司范围内教育和激励员工有关人工智能/人工智能的知识。人工智能/人工智能不再是 MSR(微软研究院)和一些专门团队的特权。
- 可衡量的人工智能价值:如果人工智能价值可以为一个企业(或未来的商业价值或/和对非营利组织的有意义的影响)进行衡量,这将是令人信服的和可持续的。不要太宽泛。这些是开始的好领域:预测(例如,需求预测和销售预测)、分类(例如,销售团队绩效分类)、异常检测(例如,用户交易异常值)、CV(例如,员工面部 ID)和 ASR/TTS(例如,自动语音客户服务)。此外,如果分析或统计可以更好或更有效地工作,则可能没有必要应用 AI/ML。例如,许多商业决策可以通过 BI 分析做出,而不需要基于 ML 的分类或预测。
- 以数据为中心的原则:数据是 AI/ML 的核心。以数据为中心的原则可以提高数据质量、可用性和可观察性。此外,它还可以提高模型质量和服务精度。面对以模型为中心的挑战,这是一个转折点。
- 人工智能工程:它是一个开发和实践领域,旨在推进、运营和规模化生产人工智能/人工智能。它可以简化操作并自动化从数字化和数据收集到特征工程再到建模、培训和服务的流程。MLOps 对于运营 AI 来说是一个很好的开始,但是 AI 工程对于运营自动化之外的数据工程和建模来说更系统化。
- 双速迭代:如上所述,AI/ML 是快速进化的,基于学习的,过程复杂的。这些需要双速迭代,以进行快速迭代实验、开发和操作。例如,亚马逊已经改造了很多,从 AWS 服务到履行网络到设备(Kindle、Echo 等)。).但是每一次成功都依赖于它的快速迭代发明和开发。
这五个步骤与人员、文化、技术和流程相关。他们一起为一个组织工作,为人工智能转型做准备。以数据为中心的原则和人工智能工程与技术紧密相关。
借助以数据为中心的人工智能工程加速发展
Gartner 已经将人工智能工程确定为 2022 年的顶级战略技术趋势之一。AI 工程是人工智能研究和应用之间的桥梁。从数据收集和特征工程到建模、培训、验证、服务和监控,它超越了 ML 操作自动化的 MLOps 。它可以系统地解决数据质量、模型优化、用户有效性,以及工程学科的数据和模型治理。
以数据为中心的 AI 工程是以数据为核心,以工程为学科的集成框架。在 AI 成熟度模型的组织中开发和操作以数据为中心的 AI 是一个很好的实践。

实现人工智能成熟度模型(由作者解释)
简单地
AI 成熟度模型是一个采用 AI 和用 AI 转型的框架。AI 转型是其最高阶段,也是 AI 成熟度模型的目标。面对这些挑战和特征,作为五个转型步骤之一的以数据为中心的人工智能工程是实现这些努力的关键。它可以加速人工智能与应用程序的集成,并在发布后继续为人工智能解决方案增加价值。
云和人工智能都已成为数字化转型的基础技术。现在云是现代化的解决方案,AI 是创新的策略。以数据为中心的人工智能工程是重塑人工智能应用和重塑未来的加速器。
参考
- 什么是以数据为中心的 AI 工程?:https://towards data science . com/what-is-data-centric-ai-engineering-d29b 3468062 e
- 数字化转型 2.0:https://medium . com/@ luhuihu/digital-Transformation-2-0-BBC 97 cc 8 b 285
- Gartner 人工智能成熟度模型:https://www . Gartner . com/smarterwithgartner/the-CIOs-guide-to-artificial-intelligence
- AI 成熟度和微软组织:https://query . prod . CMS . rt . Microsoft . com/CMS/API/am/binary/re 4d ivg
- IBM 的企业应用 AI 成熟度框架:https://www.ibm.com/downloads/cas/OB8M18WR
- AI 成熟度框架 by Element AI:https://S3 . Amazon AWS . com/Element-AI-website-bucket/AI-Maturity-Framework _ White-Paper _ en . pdf
内外咖啡折光仪测量
原文:https://towardsdatascience.com/inside-and-out-coffee-refractometer-measurements-ebfc3952c49c
咖啡数据科学
最好知道折射仪
使用折光仪测量咖啡中的总溶解固体(TDS)是基于一些与 TDS 和折光率相关的数据。我不确定这个数据是基于什么,但是我在 TDS 测量中看到了一些奇怪的东西,与浓缩咖啡的 groundtruth 相比。
这种差异让我思考,那么豆子的内部和外部呢?咖啡豆各部分溶解的固体会导致不同的测量结果吗?
方法
我首先通过研磨和筛除小于 300 微米的东西来分离出里面的 T4 粉末。然后,我取出较粗的粉末,放回研磨机中进行细磨,筛出小于 300 微米的粉末以弥补外面的粉末。

所有图片由作者提供
然后,我将 4g 样品与 11g 废咖啡混合。我用相同的轮廓拍摄每张照片,然后把每张照片分成三个杯子。我专注于前两个杯子(第一,第二),因为第三个杯子的 TDS 很低。
对于每个样本,我将样本稀释几次,以测量 TDS 如何响应,假设每个样本的第一个和最高 TDS 读数是 groundtruth。如果你加水,在其他条件不变的情况下,它会线性稀释溶解的固体总量。
数据
数据并没有很好地显示这种稀释的线性趋势。我们可以看看第一个杯子,那是 1:1 的镜头。对于外部罚款,读数最终高于地面真相。

我们可以放大,看到读数的一些差异。

我们也可以把内外分开来看,但是要比较一个镜头的前半部分和后半部分。前半部分(第一部分)高估了基本事实,而后半部分(第二部分)略微低估了基本事实。外部微粒的差异比内部微粒更明显。


如果我们只关注秒,内部和外部微粒的行为是相似的,考虑到容易提取的可溶物已经不存在了,这是可以预料的。

这项研究的目的是表明我们对折光率仪如何表征咖啡萃取没有很好的理解。我怀疑这种描述最初是在较长时间的酿造过程中完成的,并没有考虑到咖啡豆的非同质性。当计算从折光率到 TDS 的转换时,不包括输出比的输入,但即使这样,也存在同质提取的假设。通常水温是被考虑的。
折光仪是目前最好的和最普遍的量化咖啡萃取和浓度的工具,但我们需要更好的工具来更好地了解咖啡。
如果你愿意,可以在推特、 YouTube 和 Instagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以关注我在中和订阅。
我的进一步阅读:
工作和学校故事集
内部上位网
原文:https://towardsdatascience.com/inside-epistatic-nets-d9afeaf80689
正则化神经网络以更好地预测生物适应度景观

艾玛·戈塞特在 Unsplash 上的照片
将你的归纳偏见与你的问题空间相匹配
所有机器学习(ML)算法都有一个固有的归纳偏差。归纳偏差是我们对遇到某种解决方案的可能性所做的假设。归纳偏差的经典例子是奥卡姆剃刀,我们假设简单的例子比复杂的更有可能。在 ML 中,我们经常通过施加稀疏性约束来将归纳偏向简单性转化为实践。 LASSO 回归例如,惩罚学习解中非零系数幅度的总和——LASSO 偏向系数更小、更少的解。
并非所有的归纳偏差都适用于所有的问题空间(没有免费的午餐)。通过根据问题空间定制学习算法的归纳偏差,对于给定的努力,您将获得更好的性能——也就是说,您将需要更少的训练数据、更少的训练迭代,并且您的模型将更好地推广到看不见的示例。
生物适应度景观稀疏
生物学中一个常见的挑战是预测突变对健康的影响。生物有基因组,基因组由一系列基因组成,其中一些基因相互作用;更少的是一式三份;更高阶的更少;而且大部分基因根本不交互作用。在这种情况下,我指的是“上位性”方面的相互作用——也就是说,同时删除基因 A 和 B 可能会产生比你预期的 A 和 B 单独产生的影响更大(或更小)的影响。
一个生物体的整体“适应度”(其适应度“景观”)不仅取决于基因本身的作用,还取决于基因之间的相互作用网络。适用于基因组的相同原理也适用于单个蛋白质;使得整个蛋白质的适合度不仅取决于其氨基酸序列,还取决于氨基酸之间的相互作用。例如,新型冠状病毒的新变种可能或多或少具有传染性(或多或少“适合”),这取决于它们的蛋白质中包含的一系列突变,以及这些突变在蛋白质内和基因间如何相互作用。
在最近的一篇 PNAS 论文【1】中,加州大学伯克利分校的 Listgarten 小组表明,就位点之间的上位性相互作用而言,上述生物景观——对于生物体或单个蛋白质——往往非常稀疏。大多数基因或蛋白质中的大多数位点不相互作用。更少的是一式三份或四份。并且高阶相互作用不能从低阶相互作用中预测。
规则化可以加强特定的归纳偏差
正如上面的 LASSO 回归例子所展示的,正则化 ML 模型是加强特定归纳偏差的一种方式。深度神经网络(DNN)模型能够表示比套索模型复杂得多的功能,能够表示生物适应度景观中潜在存在的更高阶相互作用。然而,对于 DNNs,大多数默认的正则化方法适用于单个层。例如, L1 正则化可以应用于 DNN 的单层,使得该特定层的权重偏向更稀疏的解决方案。
然而,在 Berkeley 的 Listgarten 和 Ramchandran 小组最近发表的一篇自然通讯论文中,他们指出了一个关键的细节:当使用 DNN 预测生物序列的适合度时,简单地将 DNN 层 T10 限制为稀疏不会导致整个 DNN 水平的稀疏解。引用他们 2021 年的论文:
尽管有其好处,促进上位相互作用的稀疏性并没有在 DNNs 中作为一种归纳偏差进行研究。障碍在于找到一种方法来提高 DNNs 中的上位稀疏性。不幸的是,直接惩罚具有促进稀疏先验的 DNNs 的所有或一些参数(权重)不可能导致稀疏上位正则化,因为上位系数是 DNNs 中权重的复杂非线性函数。
换句话说,当输入要素通过 DNN 传播时,即使具有稀疏权重的图层也可以(并且确实)重新组合成非稀疏解决方案。
总结一下:
- 我们喜欢 dnn,因为它们可以表示任意程度的相互作用。
- 但是,dnn 是如此灵活,它们很容易过度拟合,损害了通用性。
- 就特征之间的上位性交互而言,生物景观往往是稀疏的。
- 我们需要一种方法来加强整个 DNN 的上位稀疏性,以便更好地推广到新数据(即,施加更合适的归纳偏差)。
现在我们到了有趣的部分。Listgarten 和 Ramchandran 小组通过在 DNN 训练期间向损失函数添加上位稀疏度的测量,解决了上述问题。
使用沃尔什-哈达玛变换测量上位稀疏度
测量变量间相互作用稀疏性的一种方法是通过沃尔什-哈达玛(WD)变换。

排序后的沃尔什矩阵(维基百科)
有几种不同的方式来思考 WD 转换。第一个,是作为一个离散的傅立叶变换。花点时间看看上面的沃尔什矩阵,你可以看到一个由低到高频率交替变化的模式。第一行(和列,因为矩阵是对称的)是一系列 1。第二个是半 1,和半 1。第三行更频繁地交替 1 和-1,直到最后一行每隔一个位置交替 1 和-1。从“频谱分析”/傅立叶的角度来看,这些行的线性组合可以表示任何布尔函数。也就是说,您可以使用这些行的组合来表示健身景观。WD 变换是用于识别沃尔什矩阵系数的过程(“沃尔什频谱”),该矩阵可精确分解布尔函数。WD 变换的稀疏性是指非零系数的数量。
边注:沃尔什矩阵根据被分解的布尔函数的维数改变维数。上面的矩阵是 16 阶的(2⁴或四维布尔函数)。显然,更大的空间需要更大的沃尔什矩阵。
从生物学角度来看,WD 变换的一个补充方法是列举生物序列中所有可能的上位性相互作用(具体来说,“平均上位性”)。关于这种联系的全面解释,请参见德克萨斯大学阮冈纳赞小组的这篇 2016 年论文【3】。想法是沃尔什矩阵的每一行对应于用于计算上位相互作用的系数。
例如,考虑一个三维布尔函数(2 或 8 种可能的特征组合)。在生物景观中,这种布尔表示可能意味着许多事情,例如在蛋白质的位置 1、2 或 3 处特定突变的存在/不存在。每个特征组合的适合度由 y 值表示,因此“y101”是样本的 y 值,其中第一个和第三个元素为 1,第三个元素为 0。
特定位置对适应度的影响可能略有不同,这取决于存在的其他突变。例如,背景(00)中位置 3 (001)的效果将被计算为[y001-y000]。背景中位置 3 的影响(01)将被计算为[y011-y010]。这两个效应可以用 1/2*[(y001-y000) + (y011-y010)]来平均。结果是位置 3 对整体健康的平均影响(见这里的等式 7)。高阶上位相互作用可以用同样的方法计算。以矩阵形式表示完整的一系列方程会产生沃尔什矩阵,使用这些函数分解适应度景观会产生完整的上位相互作用集,这相当于 WD 谱。
因此,WD 变换允许我们将适合度测量转换为上位交互。我们可以通过计算非零值的数量来衡量这些相互作用的稀疏性。
用 WD 变换正则化 DNN
我们现在知道,WD 变换提供了一种量化 DNN 学会的上位相互作用的稀疏性的方法。通过计算非零系数的数量,我们量化了深度网络已经学会如何稀疏(或不稀疏)地表示变量之间的相互作用。我们可能看不到这将如何明显地转化为一个改进的 DNN 架构(至少,我不这样认为),但这有什么关系呢?通过反向传播的奇迹,我们需要做的就是定义一个合适的损失函数。
在这种情况下,Listgarten 和 Ramchandran 小组以如下方式将 WD 变换包含在损失函数中。对于每次迭代训练:
- 列举布尔输入特征的所有可能组合,并使用当前的 DNN 权重来计算预测的适应度前景。
- 对预测的适应值执行 WD 变换
- 计算 WD 变换中非零系数的数量,并将该数量添加到损失函数中,乘以权重。
边注:步骤 3 是理想化的过程,但实际上,L0 范数(计算非零系数的数量)是不可微的,因此不适合反向传播。相反,他们放宽了对 L1 范数(系数绝对值之和)的限制。
总之,在输入为布尔型而输出为适应值的回归场景中,损失函数变为均方误差(MSE)加上 WD 变换的加权 L1 范数。给定这个损失函数,模型被训练以最小化预测误差和解决方案的稀疏度。
使用这个损失函数正则化 DNN 在本文中被称为“上位网”(EN)。
上位网络提高生物景观预测任务的性能
作者在几个公共数据集上证明,当使用上述 EN loss 函数进行正则化时,训练用于预测生物序列适合度的 DNNs 表现更好。约束完全 DNN 来学习输入变量之间的稀疏上位关系允许模型更好地泛化。例如,下图显示了 GFP 荧光数据集的改进。

有和没有 EN 正则化的 DNN 预测的 GFP 荧光。图 3 来自 2021 年 Listgarten 和 Ramchandran 的论文。
tensor flow 中的上位网络实现
让我们使用 TensorFlow 2 和 Python 在玩具数据集上实现一个上位网络。完整的实现可以在 GitHub 上以 Jupyter 笔记本的形式获得。
我们的玩具数据集将是 6 维的。这意味着有 6 个布尔输入特征,以及这些特征的 64 种可能的组合。我们将包括一个稀疏函数和一个稠密函数。稀疏函数将包括 1-4 度的 4 项。密集函数将包括 13 个 1-4 度项。
sparse_fun = lambda x: 2*x[0]*x[3] - 2*x[2] + 2*x[0]*x[1]*x[4] \
+ 2*x[0]*x[2]*x[3]*x[5]dense_fun = lambda x: 2*x[0]*x[3] - 2*x[2] + 2*x[0]*x[1]*x[4] \
+ 2*x[0]*x[2]*x[3]*x[5] \
- 2*x[1]*x[3] - 2*x[5] + 2*x[3]*x[1]*x[5] \
+ 2*x[0]*x[4] - 2*x[0] + 2*x[3]*x[4]*x[5] \
- 2*x[2]*x[5] + 2*x[1] - 2*x[2]*x[3]*x[4]# itertools.product for generating all combinations of features
n_bits = 6
all_comb = list(product([0, 1], repeat=n_bits))
为了计算 WH 变换,我们将使用 scipy.linalg.hadamard 函数来生成沃尔什矩阵,然后使用张量流函数执行矩阵乘法(以便梯度带可以跟踪运算)。
from [scipy.linalg](https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.hadamard.html) import hadamard# bool model predicts fitness using Boolean inputs
y_all = tensorflow.squeeze(bool_model(all_comb))# Walsh-Hadamard matrix (without normalizing constant)
had_mat = tensorflow.convert_to_tensor(
hadamard(2**n_bits),
dtype=tensorflow.float32
)# Multiply Walsh matrix by predicted fitness values
wh_t = tensorflow.linalg.matvec(had_mat, y_all)# Sum absolute value of coefficients
coeff_l1 = tensorflow.reduce_sum(
tensorflow.cast(
tf.abs(wh_t),
dtype=tensorflow.float64)
)
然后,用于训练模型的损失函数变成 MSE 加上 WH 变换的系数之和,如下所示:
mse = tensorflow.reduce_mean(
tensorflow.math.squared_difference(y_pred, y_true)
)# Here wh_weight balances the contribution of MSE and WH transform
# This code adds the weighted factors together into a single loss
loss_sum = tensorflow.add(
tensorflow.cast(mse, dtype=tensorflow.float32),
tensorflow.cast(wh_weight * coeff_l1, dtype=tensorflow.float32)
)
现在我们有了运行实验的基本组件(参见全 Jupyter 笔记本了解所有细节)。我们比较了三种类型的 DNN 正则化:非正则化,分层 L2 正则化,WH 稀疏正则化(又名上位网)。我们可以在 25%的稀疏函数景观上训练每个模型,然后尝试推广到剩余的 75%。将使用 MSE 作为损失函数来训练每个模型,除了上位网,上位网将使用 MSE 加上稀疏性约束。

上位网(“wh_reg”)在稀疏和密集数据集上都更好地概括,因为它偏向于学习稀疏特征交互。每个方框代表 10 个随机的训练/测试分割。顶行显示了模型预测与保留数据的皮尔逊相关性。底部一行显示了训练模型的相互作用系数大小的总和。左栏是稀疏的风景,右栏是密集的。
从上图中我们可以看到,稀疏度约束的“wh _ reg”DNN 在稀疏和密集景观(顶行,皮尔逊相关系数)中都能更好地推广测试数据。不出所料,同一个模型学会了用更大的稀疏性来表示特征交互(底部一行)。
结论
根据问题空间定制 ML 模型的归纳偏差可以带来显著的改进。就特征交互而言,生物适应度景观往往非常稀疏。使用 WH 变换来测量上位稀疏性,并将该度量包括在损失函数中,导致在预测生物序列的适合度时模型性能大大提高(一种称为上位网的训练过程)。我们已经在 TensorFlow 中完成了上位网络的实现,并演示了它的完美工作!
参考文献
[1] D. Brooks,A. Aghazadeh 和 J. Listgarten,关于适应度函数的稀疏性及其对学习的影响 (2021), PNAS 119(1):e2109649118
[2] A. Aghazadeh,H. Nisonoff,O. Ocal,D. Brookes,Y. Huang,O. Koyluoglu,J. Listgarten 和 K. Ramchandran,上位网允许深度神经网络的稀疏谱正则化以推断适应度函数 (2021),Nature communication s12:5225
[3] F. Poelwijk,V. Krishna 和 r .阮冈纳赞,突变的背景依赖性:形式主义的联系 (2016), PLoS 计算生物学 12(6):e1004771
如何安装 dbt(数据构建工具)
为您的特定数据仓库安装数据构建工具

数据构建工具 (dbt)无疑是现代数据堆栈中最强大的工具之一,因为它允许团队和组织以可伸缩、高效和有效的方式管理和转换数据模型。dbt 将处理所有数据模型的相互依赖性,并为您提供您需要的一切,以便对您的数据执行测试,并提高您的数据资产的数据质量。
根据您使用的数据平台,您必须安装一些额外的适配器来使 dbt 工作并与该平台正确通信。在接下来的几节中,我们将演示如何在虚拟环境中安装dbt和所需的适配器,以便开始使用数据构建工具。
创建虚拟环境
首先,我们需要创建一个虚拟环境,该环境独立于主机上安装的任何设备:
虚拟环境是在现有 Python 安装的基础上创建的,称为虚拟环境的“基础”Python,并且可以选择与基础环境中的包隔离,因此只有明确安装在虚拟环境中的包才可用。— Python 文档
python3 -m vevn dbt-venv
然后激活新创建的 venv:
source dbt-venv/bin/activate
如果一切顺利执行,您应该能够在终端的每一行看到一个(dbt-venv)前缀。
安装 dbt-core
dbt 提供了两种与工具本身交互和运行项目的可能方式——一种是在云上,另一种是通过命令行界面(cli)。在本教程中,我们将演示如何安装所需的软件包,让您可以从本地机器上使用 dbt。
因此,您需要安装的第一个依赖项是dbt-core。以下命令将安装 PyPI 上可用的最新版本:
pip install dbt-core
如果您希望安装一个特定的版本,那么您必须在安装命令中指定它:
pip install dbt-core==1.3.0
安装完成后,您可以通过运行以下命令来确保安装成功,该命令将简单地在终端上打印出安装在本地计算机上的 dbt 版本:
dbt --version
为您的数据平台安装 dbt 插件
现在,为了让 dbt 成功运行,它需要与您(或您的团队)使用的数据平台建立连接。使用一个适配器插件,数据构建工具可以扩展到任何平台。你可以把这些插件想象成我们在上一步中安装的dbt-core正在使用的 Python 模块。
dbt 实验室维护他们自己的一些适配器,而其他一些适配器最初是由社区创建的(并且正在被积极地维护)。你可以在这里找到可用插件的完整列表。下面我将分享其中一些的安装说明:
BigQuery(谷歌云平台)
pip install dbt-bigquery
雅典娜
pip install dbt-athena-adapter
Postgres 和 AlloyDB
pip install dbt-postgres
天蓝色突触
pip install dbt-synapse
数据块
pip install dbt-databricks
红移
pip install dbt-redshift
雪花
pip install dbt-snowflake
火花
pip install dbt-spark
后续步骤
现在,您已经成功地安装了dbt-core和基于您正在使用的数据平台的所需适配器,您已经准备好创建您的第一个 dbt 项目和与目标数据平台交互所需的概要文件。在接下来的几天里,我将分享更多关于如何做到这一点的教程,所以请确保订阅并在这些文章发布时收到通知!
最后的想法
如果您还没有尝试过数据构建工具,我强烈建议您尝试一下——您可能会惊讶地发现它是如何帮助您的团队最小化构建、管理和维护数据模型的工作的。
在今天的简短教程中,我们介绍了在本地机器上安装 dbt 所需的步骤。本指南将帮助您安装 dbt CLI 以及创建、管理、运行和测试数据模型所需的适配器(基于您的首选数据平台)。
成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。
https://gmyrianthous.medium.com/membership
相关文章你可能也喜欢
</2-rules-groupby-sql-6ff20b22fd2c>
在 Apple Silicon Macs 上安装 TensorFlow 和 Jupyter 笔记本
了解如何安装 configure tensor flow with Jupyter Notebook,以便在您的 M1/M2 MAC 上使用

如果你对机器学习或深度学习感兴趣,你肯定会对 Anaconda 包和 Jupyter 笔记本很熟悉。对于那些刚刚拥有崭新闪亮的运行苹果芯片的 MacBook Air 或 Mac Studio 的人来说,当你试图在 Anaconda 中安装 TensorFlow 包时,你可能会有点失望。
去年,我写过一篇关于如何在你的 Apple Silicon Mac 上安装 TensorFlow 的文章:
[## 在 M1 Mac 上安装 TensorFlow
towardsdatascience.com](/installing-tensorflow-on-the-m1-mac-410bb36b776)
今年我给自己弄了一个 Mac 工作室,所以我想看看有什么变化。因此,在本文中,我将带您了解在 Apple Silicon Mac 上安装 TensorFlow 的最新步骤。好消息是程序现在更加简单了。所以让我们直接开始吧!
苹果硅上的张量流
不幸的是,在苹果芯片上安装 TensorFlow 并不像在 Windows 上安装 Anaconda 然后使用pip install tensorflow命令那样简单。原因如下。
TensorFlow 不正式支持 Apple Silicon——这意味着 TensorFlow 没有针对 Apple Silicon 架构(arm64)预编译的包。
TensorFlow 只为 x86 架构(Linux、Windows、Mac(在 Intel 芯片上))和 Raspberry PI (arm64)分发官方轮子。
为了克服这一限制,苹果保留了自己的tensorflow-macos包:

作者图片
使用 Tensorflow 的 PluggableDevice API ,苹果能够将 TensorFlow 操作翻译成苹果芯片(M1 和 M2)的 GPU 可以理解的代码。
你可以从https://blog . TensorFlow . org/2021/06/PluggableDevice-device-plugins-for-tensor flow . html了解更多关于 tensor flow 的 PluggableDevice API
tensorflow-macos包依赖于另外两个包:
tensorflow-deps—在 arm64 上运行 Tensorflow 的依赖关系,例如python、numpy、grpcio和h5py。tensorflow-metal—确保 TensorFlow 能够在金属上运行的插件
Metal 框架让你的应用直接访问设备的图形处理单元(GPU)。本质上,这意味着您的 TensorFlow 代码现在能够利用新 Apple Silicon 中可用的强大 GPU。
注意,如果没有
tensorflow-metal包,你的 TensorFlow 代码仍然可以在你的 Apple Silicon Mac 上运行,只是 TensorFlow 将无法利用 M1 或 M2 的 GPU(它只能使用 CPU)。
在 Apple Silicon Mac 上安装 TensorFlow 的步骤
在 Apple Silicon Mac 上安装 TensorFlow 非常简单。我在下面几节中列出了这些步骤。
下载 Conda
不使用 Anaconda ,而是使用 MiniForge ,一个轻量级 Python 解释器,可以完全访问 conda 生态系统。
Conda 是一个运行在 Windows、Mac OS 和 Linux 上的开源包和环境管理系统。
对于苹果硅机,可以直接从https://github . com/conda-forge/miniforge/releases/latest/download/miniforge 3-ma cosx-arm 64 . sh下载 MinForge 安装程序
下载完miniforge 3-ma cosx-arm 64 . sh文件后,打开终端,输入以下命令:
$ **chmod +x ~/Downloads/Miniforge3-MacOSX-arm64.sh**
$ **sh ~/Downloads/Miniforge3-MacOSX-arm64.sh**
遵循屏幕上的安装说明。安装完成后,将为您创建一个基本的 Python 环境。您可以使用以下命令来验证这一点:
$ **conda info --envs**
# conda environments:
#
base * /Users/weimenglee/miniforge3
激活环境
使用以下命令激活基本环境:
$ **source ~/miniforge3/bin/activate**
如果您的机器只有一个环境,这一步是可选的。
安装 TensorFlow 依赖项
键入以下命令安装 TensorFlow 所需的依赖项(tensorflow-deps包):
$ **conda install -c apple tensorflow-deps**
安装 TensorFlow
键入以下命令安装适用于 Mac OS 的 TensorFlow:
$ **python -m pip install tensorflow-macos**
安装 tensor flow-金属插件
键入以下命令安装tensorflow-metal软件包:
$ **python -m pip install tensorflow-metal**
安装其他基本库
除了安装 TensorFlow 库,您还很可能需要为您的机器学习和深度学习项目安装其他包,如 matplotlib 、 sklearn 、 OpenCV 、 Pandas 等。因此,请继续安装它们,如下所示:
$ **conda install -c conda-forge matplotlib -y**
$ **conda install -c conda-forge scikit-learn -y**
$ **conda install -c conda-forge opencv -y**
$ **conda install -c conda-forge pandas -y**
**-y**选项为您省去安装库时回答确认的麻烦。
安装 Jupyter 笔记本电脑
最后,您可能希望安装 Jupyter 笔记本:
$ **conda install notebook -y**
就是这样!要测试 Jupyter 笔记本安装是否正确,请启动它:
$ **jupyter notebook**
如果你能看到以下内容,你就万事俱备了!

作者图片
创建一个新的 Jupyter 笔记本,并键入以下代码:
import tensorflow as tf
print(tf.__version__)
print(tf.keras.__version__)
如果您没有看到任何报告的错误,您应该会看到类似如下的输出:
2.9.2
2.9.0
既然这样,恭喜你!您已成功安装 TensorFlow!
升级 TensorFlow
如果您要升级到 TensorFlow 的新版本,可以遵循以下步骤:
- 卸载
tensorflow-macos和tensorflow-metal包:
$ **python -m pip uninstall tensorflow-macos**
$ **python -m pip uninstall tensorflow-metal**
- 重新安装
tensorflow-deps包:
$ **conda install -c apple tensorflow-deps --force-reinstall**
如果你的 TensorFlow 安装在一个特定的环境中,那么在上面的命令中添加
**-n *<environment_name>***选项。
- 重新安装
tensorflow-macos包:
$ **python -m pip install tensorflow-macos**
https://weimenglee.medium.com/membership
摘要
希望这篇短文能让你在苹果 Silicon Mac 上轻松安装 TensorFlow。对于长期使用 Anaconda 的用户,请注意,在本文中您现在使用的是 MiniForge,我只安装了一些常用的包,如 matplotlib、sklearn、OpenCV 和 Pandas。当您处理项目时,很可能需要使用pip命令手动安装所需的包(而如果您使用 Anaconda,那么已经预安装了很多包)。尽管如此,这只是一点点不便,而不是一个交易破坏者。跟 TensorFlow 玩得开心!
希尔伯特变换的瞬时相位和幅度
使用希尔伯特变换更好地理解周期性数据

马克西姆·托尔钦斯基在 Unsplash 上拍摄的照片
动机
在其他文章中,我们探讨了傅立叶变换在信号频率成分分解中的应用。
然而,傅立叶变换并不能告诉我们太多关于原始信号的时间或空间动态。我们知道哪些频率组成了我们的原始信号,但不知道何时 / 频率在哪里或者它们如何随着时间演变。让我们通过构建我们的数字信号处理工具包来解决这一缺陷吧!这里,我们将考察希尔伯特变换对实值信号的一个简单应用,以了解它的实际重要性。
为什么这对数据科学家很重要?轻松点。当我们处理周期性数据时,我们通常最感兴趣的是相位和幅度的动态变化,而不是原始值。从时间生物学到气候科学,希尔伯特变换对于理解数据至关重要。
样本信号
为了演示希尔伯特变换的应用,让我们考虑一个简单的连续波(CW)脉冲。
在这个代码片段中,第一个余弦为我们的载波定义了一个恒定的频率。正弦函数缩放该载波,对脉冲进行幅度调制。
希尔伯特变换的实部和虚部
在图 1a 中,显示了模拟的 CW 脉冲。正如预期的那样,该信号是实值的,并且包括由较低频率调制的单一恒定频率幅度。

图 1 : (a)简单 CW 脉冲和(b)该实值信号的希尔伯特变换。图片作者。
希尔伯特变换返回值的实部和虚部如图 1b 所示。对我们的实值信号调用希尔伯特变换是…复杂的!实分量与我们的输入脉冲相同。然而,虚部是相移的复制品。
相位和幅度估计
好的,希尔伯特变换给我们的信号增加了一个虚部……为什么?另外,我们不能自己改变脉冲的相位吗?
我们会问“为什么?”一会儿,但在我们继续之前,有必要回答第二个问题。我们当然可以!但是,请记住,我们设计了这个简单的信号来演示其机制。正确地对具有任意频率成分的信号进行相移可能很棘手!我们把它作为一个练习留给读者😉

图 2 : (a)希尔伯特变换的相位和(b)幅度。图片作者。
现在是魔法!让我们检查希尔伯特变换的复值结果的相位和幅度。
引入这种相移复制信号作为虚部,我们可以连续测量相位和幅度!如果您需要复习一下相位和幅度在这种情况下反映了什么…
- https://towards data science . com/the-Fourier-transform-3-magnitude and-phase-encoding-in-complex-data-8184 e2ef 75 f 0
- https://towards data science . com/mind-your-is-and-q-s-the-basics-of-I-q-data-D1 F2 b 0 DD 81 f 4
虽然相角值对于信号的稳定前导和尾随零值尾部没有意义,但它显示了脉冲持续时间的预期包裹相位级数(图 2a )。
使用我们通过希尔伯特变换恢复的幅度信息,我们可以绘制信号的“包络”:限制信号的平滑曲线(图 2b )。
结论
希尔伯特变换扩展了我们的 DSP 工具包,允许我们估计输入信号的相位和幅度。处理调幅信号时,这一点至关重要!如果你看一下支持我的 NOAA 信号解码文章的源代码,你会看到希尔伯特变换扮演着核心角色。
接下来,我将介绍如何使用小波来恢复频率信息,同时保留时间/空间信息!感谢阅读!
我没有发出美德信号,而是签约写了 300 张 GOTV 明信片
只不过它们不是我写的,是 Python 为我写的。

我的 GOTV 项目
意图:
我想把我的两个爱好结合起来,编码和在市政技术部门工作。我曾是一家大型公共部门联盟的数据科学家,现在是一名联邦数据科学顾问。
也许这也是因为当我在一家大型公共部门工会担任数据科学家时,我尝到了“组织”和“团结”这两个词的味道,但是(请原谅我的法语),要让人们通过积极参与为一项运动做出贡献,而不仅仅是在社交网站上发帖,这真的很难。我想,我唯一一次看到大多数同龄人(包括我自己)聚集在一起,是在 2020 年乔治·弗洛伊德(George Floyd)和布莱恩娜·泰勒(Bryana Taylor)被谋杀后的黑人的命也是命。这超出了我在社交媒体上交叉发布内容的社交圈,但实际上:
- 打电话给他们的代表
- 捐钱给保释债券(包括我自己,我从来没有这样做过)
- 写电子邮件
- 参与当地市政厅关于警察预算的讨论
- 对地方政策进行投票
我想,在现场为这场精彩的战斗奋战过的人也注意到,在这段时间里,人们不仅兴趣大增,而且积极参与。
我第一次听说“美德信号”这个词是来自贾·托伦蒂诺(Jia Tolentino),她是《恶作剧之镜》(Trick Mirror)的作者,我非常欣赏她的散文和智慧,因为她剖析了从互联网到资本主义、女权主义等文化层面。在她写的互联网中的我的那一章中,这句话让我印象深刻。“但美德信号是两党合作的,甚至是非政治性的行动。Twitter 上充斥着对第二修正案的戏剧性效忠誓言,这种誓言起到了权利内美德信号的作用,当人们在名人去世后发布自杀热线时,它可能有点像美德信号。我们中很少有人完全不受这种做法的影响,因为它与对政治诚信的真正渴望交织在一起。正如我写这篇文章时所做的那样,张贴抗议边境家庭分离的照片是一个微观上有意义的行动,是一种真正原则的表达,也不可避免地是某种表明我很好的尝试”。
再加上同一章的另一段引文。“互联网是如何扩大我们的认同感的;第二,它如何鼓励我们高估自己的观点;第三,它如何最大化我们的对立感;第四,它如何贬低了我们对团结的理解;最后,它如何摧毁我们的规模感。”
有些事情我们在同龄人的背景中看不到,我们日常行为中的微小贡献也有助于社会对道德的共识,这也很重要。这是一篇展示我如何打破自己的美德来寻求改变的文章。
我对我们的投票权充满热情,希望每个人都能行使这一权利。我真的相信,当每个人都投票的时候,我们的政府功能/看起来更像它应该服务的人群。我报名为 2020 年大选写 200 张明信片,通过 明信片到摇摆州组织 去摇摆州。我的手抽筋了,因为我写了这个人的名字,通用的信息,以获得投票(GOTV)和他们的地址。当我抱怨我的手抽筋时,我妈妈主动提出帮忙。
这次是中期选举,我心想。我一定有办法让这部分自动化。我要的是 excel 文件,而不是他们为你打印的和明信片一起邮寄的纸张。
这让我想到了我想学 Python 的原因,就是想把事情自动化,节省自己的时间。当我第一次学习 Python 的时候,我把这本书作为参考资料:用 Python 把枯燥的东西自动化。
代码:
这是一个非常简单的 Python 程序,如果他们是以前的投票人,我会根据 P 列的值进行定制;如果是第一次投票,我会根据 F 列的值进行定制。我提取了要包含在消息中的第一个名字。在 excel 中,地址栏已经被我很好地分开了。
import pandas as pd df = pd.read_csv('300 Arizona.csv') # read in the excel file#split out the First and Last name based on space and extract F Name
def first_name(x):
x = str(x)
x = x.split(' ')
x = x[0]
return(x)df['first_name'] = df.Name.apply(first_name) #did it via a functiondef message(x):
if x[1]== 'P':
vote = 'previous'
else:
vote = 'first time'
first_name = x[0]
message = f''' Hi {first_name}, Thank you for being a {vote} voter! \n When and how will you vote in the Tues. Nov 8th election? \n Please plan ahead! - Monica'''
return message# you can use 2 arguments in an apply pandas methoddf['message'] = df[['first_name','Vote']].apply(message,axis=1)df.to_csv("300_Arizona_wmessage.csv",index=False)


作者提供的图片
流程:
在投资 30 美元购买了 Homegoods 的切纸机后,我使用了 Word 中的邮件地址合并功能,并使用元数据将邮件和地址写入 Word,然后在一张纸上打印 4 个邮件地址,然后切割成方块,与邮资一起粘贴。
上次我花了大约 5-7 分钟的时间制作每张明信片,总共 17-23 个小时。现在总共花了我 5 个小时。大约一个小时的准备工作,包括编写 Python 程序和解决 Word 文档的邮件合并。然后在 300 张明信片上剪贴邮票,用了大约 4 个小时。这相当于每张明信片 1 分钟。这对我来说节省了大量时间。
行动号召:
对我来说,这是一个小小的壮举,但对我的时间和精力却有影响。没有什么比得上疫情期间发生的一些公民技术的巨大成就,例如这位软件工程师,他创建了一个网站来众包可用的疫苗预约,这些预约在他看到利用当前的地方政府资源找到一个可用的预约有多难之后,实时发布在 Twitter 上。
作为一名狂热的程序员,我认为掌握一项技能并将其应用于让世界变得更美好是一件很棒的事情,尤其是在公民技术领域。
不幸的是,明信片到摇摆州不赞助邮票,所以我这次花了 180 美元买了 300 张明信片,手写的时候花了 120 美元。如果你想赞助我的工作,你可以在 https://www.buymeacoffee.com/dsmoni 买一杯咖啡给我,或者更好的是,你可以自己重复这个过程并注册 GOTV 明信片!
此外,我很想知道你认为可以在哪里应用你的编码技能来支持一个公民技术领域。或者如果你已经知道了,很想知道!
原生 C 语言中深度学习的纯整数推理
原文:https://towardsdatascience.com/integer-only-inference-for-deep-learning-in-native-c-e57f29a20adc
教程:通过统一量化和定点表示,转换深度神经网络以部署在低延迟、低计算设备上。
纯整数推理允许压缩深度学习模型,以部署在低计算和低延迟设备上。许多嵌入式设备使用本机 C 编程,不支持浮点运算和动态分配。然而,通过统一量化和定点表示,小型深度学习模型可以部署到具有纯整数推理流水线的这种设备。
我们采用这些方法在网络接口卡(NIC)上部署深度强化学习(RL)模型(Tessler 等人 2021[1])。在不支持浮点运算的设备上成功部署 RL 模型需要 O(微秒)的推理延迟。小延迟限制抑制了通过 GPU 运行模型。为了实现我们的目标,我们使用了郝等人 2020[2]指定的训练后量化(PTQ)技术和 TensorRT 的 pytorch 量化包。
在本教程中,我们将学习郝等人 2020[2]之后的量子化和 PTQ 技术。此外,我们将了解定点表示,并给出一个详细的示例,将在 MNIST 上训练的 PyTorch 分类器转换为原生 C 语言,以最小的性能损失进行纯整数推理。
目录
Q 量化
训练后量化
∘ 校准
唯整数推理
∘ 定点表示
∘ 定点运算
教程—唯整数推理在原生 c 中用于 MNIST 分类
∘ 模型训练和量化在 Python 中
量化
量化是将数值从一个大集合(通常是连续的)映射到一个较小集合的过程。在我们的例子中,我们感兴趣的是减小神经网络的大小和加快计算速度,以便它可以在低计算量、低内存的设备上运行推理。我们将集中讨论均匀整数量化,这样我们可以在整数域中执行矩阵乘法和卷积。更具体地说,我们将量化网络的输入和参数,从 float32 到 int8。
均匀量化— 在均匀量化中,输入x∈【β,α】被变换到范围

其中 b 是比特数。输入范围之外的值被剪切到最近的界限。在我们的例子中,变换后的范围是[-128,127],为了对称,我们将使用[-127,127]。
均匀量化通过两个函数完成:
- 量化- 将 float32 值映射到 int8 范围,然后进行舍入和限幅(float32 → int8)
- 反量化— 将 int32 值映射到 float32
假设我们想要最小化运算的数量,我们将使用比例量化来实现这些运算。
在标度量化中,输入范围为 x ∈ [-α,α],并且校准最大α值(amax)以最大化精度。校准α后,通过乘以/除以比例因子 s 进行映射。

比例因子计算,其中α最大值(a max)在校准期间计算,b=8 位。
对于比例量化,我们的操作定义如下:

张量量化粒度
张量粒度指的是共享量化参数(例如,比例因子)。在精度和计算成本之间存在权衡,其中用不同的比例因子缩放每个张量元素将导致最高的精度和计算成本,相反,用单个因子缩放整个张量导致最低的精度和计算成本。
线性层量化

PyTorch 中实现的线性层,X 是 NxK 输入张量,W 是 MxK 权重张量,b 是 M 长度偏移向量,Y 是 NxM 输出张量。
我们用每个张量的粒度来量化层输入(所有张量元素共享相同的比例因子)。相比之下,层的权重以每行粒度进行量化(每行有不同的比例因子)。这种选择允许分解比例因子,并执行整数的矩阵乘法。

通过将一个向量连接到输入并向图层权重添加另一个维度,可以轻松实现偏差量化。
卷积层量化
与线性情况一样,我们可以从分解比例因子中获得计算上的好处。这是通过为层输入选择每张量粒度和为层权重选择每通道粒度来实现的。

在 PyTorch 的实现之后,输入张量 X 具有维度 N,C_in,L ,输出张量 Y 具有维度 N,C_out,L_prime ,然后是具有步长 1、膨胀 1 和无填充的 1D 卷积。
训练后量化
在训练后量化中,我们通过构建绝对值的直方图(一个用于层输入,一个用于参数)来校准每个模型层的比例因子。推理在图层输入的样本数据集上运行,而图层参数校准可以离线完成。
校准
校准部分选择量化精度损失最小的α。有三种主要方法来为每个直方图选择合适的α:
- 最大值—使用绝对值分布的最大值进行校准
- 熵——最小化量化分布和原始分布之间的 KL 散度
- 百分点-选择与绝对值分布的第 k 个百分点相对应的条柱

ResNet 50 第三层输入和校准范围的绝对值直方图。图片摘自(郝等 2020) [2]
一旦校准完成,我们可以在推理中构建一个量化的线性层。
纯整数推理
到目前为止,我们讨论了用于执行线性运算的量化,例如卷积和整数的矩阵乘法。另一方面,非线性激活不适合均匀量化,应该近似。非线性函数可以用查找表、分段函数和其他方法来近似,这些方法值得专门撰写一篇文章。相反,我们将不讨论非线性近似,而是使用 ReLU 激活函数,因为它不需要近似。
然而,我们仍然将 float32 作为量化/去量化操作的输入/输出。当我们在量化 / 去量化操作期间用比例因子乘/除数值时,我们无法避免处理分数。为了处理这种整数运算,我们将使用定点表示法。
定点表示法
定点表示法是一种用整数表示分数的方法。我们可以拆分组成整数的 K 位来表示一个数的整数部分和小数部分。使用符号幅度格式,我们保留 1 位表示符号,其他位表示分数。基数将剩余的 K-1 位拆分为代表整数值的 M 个最高有效位(MSB)和代表分数的 N 个最低有效位(LSB)。 M 和 N 的选择导致表示范围和精度之间的折衷。

32 位有符号整数的定点 16 位表示形式。16 LSB 表示小数,15 MSB 表示整数。
浮点和它的定点表示之间的转换是通过乘以/除以 2 的定点值的幂来完成的。

浮点数与其定点 16 位表示形式之间的转换。
定点算法
定点运算的三条经验法则:
- 两个定点数之和就是定点数。
2.整数与定点数的乘积就是定点数。
3.两个定点数的乘积除以 2 的定点数的幂就是一个定点数。
定点表示用整数代替剩余的浮点运算。

具有量化图层输入和权重的纯整数线性图层的方案
教程—针对 MNIST 分类的纯整数 C 推理
我们将在 PyTorch 的 MNIST 数据集上训练一个简单的分类器。接下来,我们将网络参数量化为 int8,并校准其比例因子。最后,我们将用原生 c 语言编写一个纯整数的推理代码。
Python 中的模型训练和量化
模型 —一个多层感知器(MLP),具有两个隐藏层(分别为 128、64),并在 MNIST 数据集上进行 ReLU 训练。MLP 不包含使模型参数和计算开销最小化的偏差。
让我们在 PyTorch 中定义一个简单的 MLP 模型。
并训练它十个纪元
Epoch: 1 — train loss: 0.35650 validation loss: 0.20097
Epoch: 2 — train loss: 0.14854 validation loss: 0.13693
Epoch: 3 — train loss: 0.10302 validation loss: 0.11963
Epoch: 4 — train loss: 0.07892 validation loss: 0.11841
Epoch: 5 — train loss: 0.06072 validation loss: 0.09850
Epoch: 6 — train loss: 0.04874 validation loss: 0.09466
Epoch: 7 — train loss: 0.04126 validation loss: 0.09458
Epoch: 8 — train loss: 0.03457 validation loss: 0.10938
Epoch: 9 — train loss: 0.02713 validation loss: 0.09077
Epoch: 10 — train loss: 0.02135 validation loss: 0.09448
Evaluate model on test data
Accuracy: 97.450%
模型量化— TensorRT 是一款用于高性能深度学习推理的 SDK。它包含 pytorch-quantization,允许 pytorch 模型的直接量化(假的,量化感知训练,PTQ)。
让我们从导入相关模块开始。
quant_nn包含 PyTorch 层的量化版本,如 nn.Linear。calib是用于构建直方图和计算比例因子的校准模块。quant_modules允许用量化版本动态替换 PyTorch 层。QuantDescriptor定义如何量化张量。
第一步是使用直方图校准器定义量化。然后,我们用期望的量化描述来设置我们的量化线性层。最后,通过调用 initialize(),用量化版本为 PyTorch 模块打补丁。
我们只在定义量化方案和猴子修补 PyTorch 模块之后加载训练好的 MLP 模型。
让我们定义一个函数,在推断过程中输入用于校准的统计数据。首先,我们禁用量化来从浮点值构建直方图。然后,我们执行推理,并在我们希望使用量化模型进行推理的情况下重新启用量化。
现在让我们定义一个函数来计算 amax 值(比例因子= 127 / amax)。
为了减少推理时的运算次数,我们离线计算比例因子值。为了避免定点除法,我们可以将它们反转,并在 c 中乘以反转后的值。
可以将 反量化 比例因子组合成一个变量。尽管如此,它可能会降低精度,因为对于定点 16,结果数可能非常小(参见教程中的 反量化 部分的示例)。
一旦我们定义了函数,我们就可以运行代码了。
本机 C 代码中的模型推理
实施 MLP 模型需要定义:
- 矩阵乘法
- 定点乘法
- 数字转换
- 去量化
- 热卢
- argmax(代替 softmax 进行推理)
- 线性层
假设神经网络的架构和参数是预先确定的,并且我们不能使用动态分配,我们将不定义矩阵和张量的一般结构。相反,在本教程中,我们将把矩阵/张量视为展平的 1D 阵列,并使用它们的形状来应用运算。
矩阵乘法
我们在两个平坦的 int8 矩阵之间执行矩阵乘法。为了避免溢出,结果存储在一个比 int8 大的整数中。
Y = XW,其中 X 是 NxK 矩阵,W 是 KxM 矩阵,结果 Y 是 NxM 矩阵。乘法和累加运算以大整数类型存储,以避免溢出
定点乘法
两个定点 16 数之间的简单乘法如下所示:

因此,我们需要将结果除以 2 的 16 次方。
在 C 中,左移运算符可以看作是乘以 2 的幂,而右移运算符可以看作是除以 2 的幂。我们可以在 C 中定义一个定点乘法函数,其中两个输入都用相同的定点值表示,如下所示:
当乘以定点值时有两个主要问题
- 舍入
- 泛滥
舍入— 默认情况下,使用左移和右移的乘法/除法会导致截断(地板)。一种简单的舍入方法是通过 round half up 方法舍入到最接近的整数。

带舍入的定点乘法如下所示:
采用四舍五入法的定点乘法。
溢出— 很直观的看到,两个大定点数的乘积在右移之前很容易溢出。一个简单的解决方案是将值存储在更大的类型中(如 int64),但在某些情况下,定点数已经用最大的可用类型表示了。一种替代方法是按部分执行定点乘法。这意味着我们在两个整数的乘积不会溢出的假设下,将定点数分成一个整数和一个小数部分。
部分乘的定点乘法实现如下所示:
既然我们已经讨论了定点乘法,我们可以编写量化函数了。
量化
量化函数将两个定点数、层的输入和比例因子相乘,然后裁剪到 int8 范围[-127,127]。在执行定点乘法之前,我们通过将输入与反转比例因子的乘积和我们的限幅范围的边界进行比较来降低溢出的风险。与标准的定点乘法相反,在量化中,我们希望乘积是一个四舍五入的整数。因此,我们不会将输出转换为定点。
定点量化函数的实现。FXP 值= 16,INT8 最大值= 127,舍入常量= (1 << FXP 值— 1)。注意——我们不像在定点乘法实现中那样右移最终值,因为(input*scale_factor)的定点表示代表一个四舍五入的整数。
反量化
反量化操作需要将一个整数(矩阵与 int8 相乘的乘积)与图层输入(标量)和图层权重(按每行粒度量化的向量)的反向比例因子相乘。这意味着我们需要混合常规和定点乘法。一般来说,整数和定点数相乘有很高的溢出风险。另一方面,当定点数表示小分数时(在我们的例子中确实如此),将它们相乘会导致精度损失。当他们右移前的乘积可能小于我们右移的值时,就会出现这种情况。因此,它们的输出会被错误地归零。
为了说明这一点,在下面的例子中,我们用两个整数乘以不同大小的值:50,500。我们比较右移前或右移后的乘法。
a: 0.001000 b: 0.000400 int: 50, c_before: 1 c_after: 0
a: 0.001000 b: 0.000400 int: 500, c_before: 13 c_after: 0
a: 0.100000 b: 0.000400 int: 50, c_before: 130 c_after: 150
a: 0.100000 b: 0.000400 int: 500, c_before: 1300 c_after: 1500
a: 0.001000 b: 0.400000 int: 50, c_before: 1300 c_after: 1300
a: 0.001000 b: 0.400000 int: 500, c_before: 13000 c_after: 13000
a: 0.100000 b: 0.400000 int: 50, c_before: -14 c_after: 131050
a: 0.100000 b: 0.400000 int: 500, c_before: -140 c_after: 1310500
我们可以看到,对于小的 a 和 b 值,我们受益于右移前的乘法,但当它们的值较大时,我们会溢出。另一方面,当我们在(精度损失)之后乘时,我们得到稍微不同的结果。最后,只有当 a=0.001 和 b=0.4 时,两种方法才输出相同的结果。
一个简单的解决方案是计算比例因子的乘积,并将其与 1(定点)进行比较。当它们大于 1 时,我们有过度拟合的风险,我们应该在定点乘法后乘以矩阵值。否则,我们在定点乘法中乘以矩阵值。
最后,我们将反量化定义为:
ReLU
ReLU 的 C 实现很简单
argmax
我们通过 NxM 矩阵的列来计算 argmax(其中 N 是批次大小,M 是标签的数量)。
线性图层
线性层由所有先前定义的部分组成:
MLP —将几个线性图层组合在一起
对于本教程,模型参数和比例因子编码在一个头文件中,并且使用 C 类型从 python 调用 run_mlp 函数。
模型评估
Evaluating integer-only C model on test dataAccuracy: 97.27%
这比 python 中的浮点模型少了 0.18%。
结论
在本文中,我们讨论了训练后量化、定点表示,以及如何使用 PyTorch 中完全精确训练的分类器在本机 C 中运行纯整数推理。
完整的代码,包括卷积神经网络版本,请访问 Github 知识库:https://Github . com/benja 263/Integer-Only-Inference-for-Deep-Learning-in-Native-C
感谢您的阅读!
参考
[1] Tessler,c .,Shpigelman,y .,Dalal,g .,Mandelbaum,a .,卡萨科夫,D. H .,Fuhrer,b .,Chechik,g .,和 Mannor,S. (2021)。数据中心拥塞控制的强化学习。【http://arxiv.org/abs/2102.09337
[2]吴,h .,贾德,p .,张,x .,伊塞耶夫,m .,&米契克维丘斯,P. (2020)。深度学习推理的整数量化:原理与实证评估。[http://arxiv.org/abs/2004.09602](/
Python 中整数与线性编程的比较
原文:https://towardsdatascience.com/integer-programming-vs-linear-programming-in-python-f1be5bb4e60e
使用 Google OR-Tools 优化混合整数规划

图片由作者提供,表情符号由open moji(CC BY-SA 4.0)
为什么线性规划这么叫?
这两个术语都令人困惑:
- 线性暗示非线性编程存在;
- 编程实际上在这个上下文中的意思是“策划”。
综上,与代码无关:线性与否。是关于用各种约束优化变量。
在本文中,我们将讨论另一种类型的优化:整数编程。我们将会看到为什么对我们所面临的问题有一个很好的理解对于选择正确的解算器是必要的。最后,我们将编写一个能够接受更大挑战的模型,并实际解决一整类优化问题。
你可以用下面的 Google Colab 笔记本 运行本教程的代码。

图片由作者提供,表情符号由open moji(CC BY-SA 4.0)
📊一、优化问题类型
在线性规划介绍中,我们优化了一个军队构成。结果如下:
================= Solution =================
Solved in 87.00 milliseconds in 2 iterations
**Optimal power** = 1800.0 💪power
Army:
- 🗡️**Swordsmen** = 6.0000000000000036
- 🏹**Bowmen** = 0.0
- 🐎**Horsemen** = 5.999999999999999
怎么会有 5.999……骑兵?我们指定我们的变量应该是带有VarInt的整数。我们的代码有什么问题?
问题不在于模型,而在于求解者的选择。
GLOP 是一个纯线性规划求解器。这意味着它无法理解整数的概念。它仅限于具有线性关系的连续参数。
这就是线性编程(LP)和整数线性编程(ILP)的区别。总之,LP 求解器只能使用实数而不能使用整数作为变量。如果不考虑变量,为什么我们把变量声明为整数呢?
GLOP 不能解决 ILP 问题,但是其他解决者可以。其实他们很多都是混合整数线性规划 (MILP,俗称 MIP)求解器。这意味着他们既可以考虑连续的(实数),也可以考虑离散的(整数)变量。离散值的一个特殊情况是布尔变量用 0-1 值来表示决策。
其他解算器如 SCIP 或 CBC 可以解决 MILP 和(混合整数非线性规划)问题。多亏了 OR-Tools,我们可以使用相同的模型,只需将求解器更改为 SCIP 或 CBC。
================= Solution =================
Solved in 3.00 milliseconds in 0 iterations**Optimal value** = 1800.0 💪power
Army:
— 🗡️**Swordsmen** = 6.0
— 🏹**Bowmen** = 0.0
— 🐎**Horsemen** = 6.0
严格地说,我们的变量仍然是浮点数(type(swordsmen.solution_value()) = float),但是我们可以看到它们不再有奇怪的小数:CBC 求解器实际上认为它们是整数。
在本例中,我们通常只需将这些值向上取整,因为误差很小。然而,重要的是要记住根据所研究的问题选择合适的求解器:
- LP 为连续变量;
- 连续和离散变量组合的 MIP/MILP 。
还有其他类型,如二次** (QP)或非线性 (NLP 或 MINLP,例如具有指数目标函数或约束)问题。它们适用于不同的环境,但遵循与 LP 或 MIP 解算器相同的原则。**

作者图片
🧱二世。建立一个通用模型
但是如果我们的资源发生变化呢?还是一个单位的成本进化了?如果我们升级了骑士,他们的力量增加了呢?
OR-Tools 最好的好处之一是它使用了 Python 这样的通用编程语言。代替静态数字,我们可以将我们的参数存储在类似于字典或列表的对象中。
代码不那么可读,但它变得更加灵活:实际上,它非常灵活,我们可以在不改变模型(只是参数)的情况下解决一整类优化问题。
让我们将输入参数转换成 Python 列表,并通过函数将它们提供给求解器。
================= Solution =================
Solved in 2.00 milliseconds in 0 iterations**Optimal value** = 1800.0 💪power
Army:
— 🗡️**Swordsmen** = 6.0
— 🏹**Bowmen** = 0.0
— 🐎**Horsemen** = 6.0
我们获得了相同的结果:我们的代码似乎工作正常。现在让我们改变参数来解决一个稍微复杂一点的问题。
想象我们有更多的资源:🌾183000 ,🪵 90512 ,🪙 80150 ,所以我们还可以生产更多的单位!这是新的表格:

请注意,我们转换了💪功率分为两个值:💪攻击和❤️ 生命值,后者更详细一点。生命值高于攻击值,这就是为什么我们要增加一个加权因子,让它们更有可比性。
我们以 10 为例,那么*力量= 10 攻击+生命值。我们的目标函数变成了:

让我们的代码适应这个新问题实际上很简单:我们只需要改变输入参数并更新目标函数。
================= Solution =================
Solved in 74.00 milliseconds in 412 iterations**Optimal value** = 1393145.0 💪power
Army:
— 🗡️**Swordsmen** = 2.0
— 🛡️**Men-at-arms** = 1283.0
— 🏹**Bowmen** = 3.0
— ❌**Crossbowmen** = 0.0
— 🔫**Handcannoneers** = 454.0
— 🐎**Horsemen** = 0.0
— ♞**Knights** = 0.0
— 🐏**Battering rams** = 301.0
— 🎯**Springalds** = 0.0
— 🪨**Mangonels** = 0.0
这个问题需要人类花很长时间来解决,但是 ILP 解决者在眨眼之间就解决了。比这更好的是:它也给了我们保证我们的解决方案是最优的,这意味着我们的敌人无法以同样的成本找到更好的军队构成!
我们可以增加单元的数量,并提供数十亿的资源,但你会明白:这只是需要更长的时间来获得一个解决方案,但它不会改变问题。
⚔️三世。组合约束
现在,假设我们侦察了我们的敌人,知道他们的军队有一支💪100 万的功率。我们可以建立一支更好的军队,但是我们的资源很宝贵,而且效率不高:我们所要做的就是建立一支拥有的军队💪功率高于 1,000,000** (即使 1,000,001 也足够)。**
换言之,总功率现在是一个约束(💪> 1,000,000)而不是目标最大化。新的目标是最大限度地减少生产这支军队所需的资源。然而,我们可以重用我们的输入参数,因为它们没有改变。
新的约束可以翻译为“所选单元的功率之和必须严格大于 1,000,000”。

在代码中,我们可以遍历我们的单元和资源来设计这个约束。
目标函数也必须改变。我们的目标是将花费在建军上的资源最少化。

同样,我们可以循环使用我们的资源,在 OR-Tools 中实现它。
================= Solution =================
Solved in 4.00 milliseconds in 0 iterations**Optimal value** = 111300.0 🌾🪵🪙resources
**Power** = 💪1001700.0
Army:
— 🗡️**Swordsmen** = 0.0
— 🛡️**Men-at-arms** = 0.0
— 🏹**Bowmen** = 0.0
— ❌**Crossbowmen** = 0.0
— 🔫**Handcannoneers** = 0.0
— 🐎**Horsemen** = 0.0
— ♞**Knights** = 0.0
— 🐏**Battering rams** = 371.0
— 🎯**Springalds** = 0.0
— 🪨**Mangonels** = 0.0Resources:
— 🌾**Food** = 0.0
— 🪵**Wood** = 111300.0
— 🪙**Gold** = 0.0
规划求解找到了一个最优解:我们需要建造 371🐏攻城槌总共花费 111300 🪵wood.等等,如果我们没有那么多木头怎么办?在上一节中,我们只有🪵90512:,我们无法生产 371🐏攻城锤。😱
那么有没有可能将这些有限的资源考虑在内,同时仍然努力打造最好的军队?实际上,这非常简单:我们只需要复制/粘贴上一节中的约束。
在这个版本中,我们有两种类型的约束:
- 总功率必须大于 100 万;
- 我们不能花费超过我们有限的资源。
================= Solution =================
Solved in 28.00 milliseconds in 1 iterations**Optimal value** = 172100.0 🌾🪵🪙resources
**Power** = 💪1000105.0
Army:
— 🗡️**Swordsmen** = 1.0
— 🛡️**Men-at-arms** = 681.0
— 🏹**Bowmen** = 0.0
— ❌**Crossbowmen** = 0.0
— 🔫**Handcannoneers** = 0.0
— 🐎**Horsemen** = 0.0
— ♞**Knights** = 0.0
— 🐏**Battering rams** = 301.0
— 🎯**Springalds** = 0.0
— 🪨**Mangonels** = 0.0 Resources:
— 🌾**Food** = 68160.0
— 🪵**Wood** = 90320.0
— 🪙**Gold** = 13620.0
既然我们现在有一个🪵wood的有限资源,数量🐏攻城锤可悲的从 371 降到了 301。作为交换,我们得到了 681 个🛡️men-at-arms,失去了 1 个🗡️swordsman(欢迎他们)。
军队的总花费是 172,100 ,这比我们之前发现的 111,300 要高得多(+65%的增长),但是在这些限制下这确实是最优的解决方案。这表明我们应该生产更多的木材,因为这些🐏攻城锤性价比极高!
这个例子向展示了** LP 模型的模块化程度。可以在另一个模型中重用部分代码,比如约束,以组合它们并解决更复杂的问题。**
🧠四世。线性规划 vs 机器学习
让我们来谈谈房间里的大象。为什么不用机器学习(广义)代替线性规划?这并不是说这个问题不能用遗传算法来解决。
数学优化往往被机器学习技术所忽视,但两者都有其优点:
- 线性规划可以在不确定的时间内(可能需要数年)产生一个最优解,而机器学习可以在任何时间内逼近复杂函数。
- LP 里没有训练,但是需要专家建立数学模型。机器学习需要数据,但模型可以用作黑盒来解决问题。
- 根据经验,没有特定时间约束和/或不是非常复杂的问题可以有利地用线性规划来解决。****

图片由作者提供,表情符号由open moji(CC BY-SA 4.0)
结论
在本教程中,我们深入了解了数学优化。
- 我们讲了最优化问题的求解器和类型: LP,MIP,NLP;
- 我们以最佳方式建模并解决了一个极其常见的优化问题,并且通过一个函数推广了我们的模型;
- 我们重新架构了这个问题,合并了两组约束,以最低的价格获得最佳的军队构成;
- 我们对比了线性编程和机器学习的利弊。
还有很多可以应用优化的问题。例如,如何创建满足每个人要求的学校时间表?如何在最短的时间内交付 1000 份不同的订单?在哪里建造一条新的地铁线以最大化其效用?
在以后的文章中,我们将讨论这些技术的新型应用,包括可满足性和非线性问题。
我希望你喜欢这篇更高级的文章。如果你喜欢机器学习和优化, 我们在 Twitter 上联系一下 !
相关文章
** **
在您的数据科学技能组合中集成偏差检测
原文:https://towardsdatascience.com/integrate-bias-detection-in-your-data-science-skill-set-982a699653f2
不要忘记偏见会影响你的项目

克里斯蒂安·卢在 Unsplash 上的照片
当我们在数据科学世界中谈论偏差时,它会指机器学习在学习输入数据时出现错误,无法客观地给出预测。如果我们打一个人类的比方,机器学习中的偏见可能意味着该模型偏爱特定的预测/条件。为什么我们需要关注偏见?
模型偏差是我们的数据科学项目中的一个潜在问题,因为输入数据和输出数据之间的关系不能反映真实世界的情况,这可能会在多个层面上导致问题,包括法律问题。
机器学习模型中的偏差是不可接受的,但它仍在发生。有偏见产生灾难性结果和挫折的例子;比如皂液机只给白人的手配或者自动驾驶汽车更有可能撞到黑人的情况。这就是为什么进行了大量的研究来检测和减轻偏见。
避免偏差的机器学习研究仍然是数据科学中的一个大问题,如何完全检测偏差仍然是一个大问题。然而,与过去相比,我们已经取得了很大进步。本文将讨论为什么我们的机器学习模型会引入偏差以及检测偏差的一些方法。
事不宜迟,我们开始吧。
为什么机器学习会有偏差?
机器学习模型是一种从我们的数据中学习的工具。这意味着我们的数据是我们的模型中为什么会出现偏差的主要来源。然而,我们需要更深入地了解为什么数据(以及 ML 模型)可能包含偏差。
准确地说,我们需要知道偏差是如何在建模过程的每个阶段发生的。如果我们划分阶段,我们数据中的偏差可能出现在以下阶段:
- 收藏品
- 预处理
- 特征工程
- 剧烈的
- 模特培训
- 估价
让我们简单了解一下为什么每个阶段都会引入偏差。
数据收集
数据是机器学习模型的核心,我们通过收集数据来获取数据。然而,当我们的数据收集充满偏见的假设时,偏见问题可能会出现。偏见可能早在数据收集阶段就出现了。
当我们收集数据时错误地关注趋势,或者当特征收集不符合业务问题时,可能会发生错误。这就是为什么我们必须完全理解数据需求,并与业务专家合作。
例如,当团队想要建立一个机器学习项目来评估信用卡可信度时,可能会出现数据收集偏差。然而,数据收集可能包括种族特征,并反映历史数据中的社会偏见。如果我们在机器学习训练中隐式使用这些数据,这可能会导致灾难性的后果。
数据预处理
在数据收集发生后,我们需要对数据进行预处理;具体来说,我们做数据预处理来检查和清理数据,以适应数据科学项目。然而,在这个阶段可能会出现偏差,因为我们缺乏业务理解或适当的领域理解。
比如我们有一个全公司的薪资数据集,这个数据中出现了缺失数据。我们从工资中进行平均插补,以填补缺失的数据。你认为这些数据会发生什么?当我们用平均工资填充缺失的数据,而没有检查工资与其他特征的关系时,这将引入偏差。
上面的例子会导致不一致偏差,因为我们取了公司每个员工的工资并进行平均。有些工资肯定会高一些,有些会低一些——这取决于水平和经验。
我们需要理解数据集,并在清理数据时使用正确的策略来最小化预处理偏差。
特色工程
在预处理步骤之后,我们对数据集进行特征工程。这一步将数据转换为机器学习模型可消化的形式,并产生可能有助于模型更好地预测的特征。然而,特征工程也可能引入偏差。
如果特征工程是基于社会经济、性别、种族等。,如果我们处理不好,这些功能可能会引入偏见。从有偏差的代表的集成创建的特征可能偏向特定的段。
特征之间的不同尺度也可能从统计角度呈现偏差。想象一个特色工资和工作年限;它们的规模非常不同,可能需要标准化——否则,我们的机器学习模型就会出现偏差。
我们的特征工程中的选择包括、删除、标准化、聚合等。,可能会影响我们机器学习模型中的偏差。这就是为什么我们需要在任何特征工程之前理解数据。
数据分割
如果拆分不能反映真实世界的人口,数据拆分可能会给训练数据带来偏差。当我们在拆分过程中没有进行随机选择时,这种情况经常发生。例如,我们选择了前 70%的数据用于训练,但不知不觉地,底部的数据包含了选择没有捕捉到的变化。这在不知不觉中给训练数据带来了偏差。
为了避免数据分割中的偏差,尝试使用随机抽样方法,如分层抽样或 K 倍交叉验证。这些技术将确保我们分离的数据是随机的,并使偏差最小化。
模特培训
当模型的输出通常远离实际情况时,模型训练中就会出现偏差。可以证明,当模型在训练数据中产生高度量,但不能在测试数据中重复时,存在偏差。在我们的机器学习模型中,模型选择对于最小化偏差是至关重要的。
我们需要了解我们的数据和模型算法,以避免我们的机器学习模型中的偏差。不是每个算法都适合所有数据集。当您有线性数据时,您可能希望使用线性模型;但也许对于另一个数据集,它需要一个神经网络模型。了解每个项目需要什么模型来减少偏差。
车型评测
最后,如果我们没有使用适当的度量标准来测量,并且没有使用看不见的数据来验证模型,则模型评估可能包括机器学习模型中的偏差。
了解指标(例如,精确度、召回率、准确度、RMSE 等。)才能知道哪个适合你的用例。例如,不平衡的情况可能不适合使用准确性作为度量标准,因为大多数预测只关注一个类。
如何用 Python 检测偏差
正如我提到的,在我们的机器学习项目中,已经做了很多研究来帮助检测和减轻偏见。在本文中,我们将使用名为 Fairlearn 的开源 Python 包来帮助检测偏见。还开发了各种 Python 包来包含我们可以使用的偏差检测技术,但我们将重点关注 Fairlearn。
请注意,该软件包有助于在模型训练和评估期间检测偏差。在此阶段之前,我们需要根据我们的领域、业务和统计知识来检测偏差。
对于项目示例,我将使用 UCI 的心脏病预测数据集 (License: CC BY 4.0)。在这个例子中,我们将使用各种函数来检测偏差,并根据我们拥有的特征来减轻偏差。
如果您还没有安装 Fairlearn 包,您可以用下面的代码来完成。
pip install fairlearn
为了开始分析,让我们开始导入包函数并加载数据集。
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_score, recall_score
from lightgbm import LGBMClassifier
from fairlearn.metrics import (
MetricFrame,
false_positive_rate,
true_positive_rate,
selection_rate,
count
)
from fairlearn.reductions import ExponentiatedGradient, DemographicParitydf = pd.read_csv('HeartDisease.csv')
然后,我们将使用数据集加载对数据集进行预处理,这样数据就可以供模型学习了。
#One-Hot Encode the categorical features
df = pd.get_dummies(df, columns = ['sex', 'chest_pain_type', 'fasting_blood_sugar', 'rest_ecg','exercise_induced_angina', 'slope', 'vessels_colored_by_flourosopy', 'thalassemia'],drop_first = True )
当数据准备好时,我们将把数据集分成训练和测试。
X_train, X_test, y_train, y_test = train_test_split(df.drop('target', axis = 1), df['target'], train_size = 0.7, random_state = 42,stratify = df['target'] )
接下来,我们将训练一个分类器预测。
clf = LGBMClassifier()
clf.fit(X_train, y_train)
分类器准备就绪后,我们将使用模型来检测预测中的偏差。首先,我们需要指定我们建立的敏感特性。敏感特征可以是我们认为敏感的任何东西,可能是因为隐私(例如,性别、婚姻状况、收入、年龄、种族等。)或者别的。关键是我们想避免敏感特征在预测中的偏差。
在这种情况下,我会将敏感特性设置为性别。因为我们已经创建了一个 OHE 特征,所以性别在性别 _ 男性(男性)中是特定的。
sensitive_feat = X_test['sex_Male']
然后,我们将使用测试数据从我们的分类器准备预测结果。
y_pred = clf.predict(X_test)
接下来,我们将基于敏感特性来度量指标。例如,我想测量基于敏感特征的回忆分数的差异。
gm = MetricFrame(metrics=recall_score, y_true=y_test, y_pred=y_pred, sensitive_features=sensitive_feat)print(gm.by_group)

作者图片
在回忆分数上,性别、男性和女性之间似乎存在差异,男性的回忆分数略高。在这种情况下,我们可以看到基于回忆的偏见。
我们还可以看看另一个指标,比如选择率(标签为“1”的人口的百分比)。
sr = MetricFrame(metrics=selection_rate, y_true=y_test, y_pred=y_pred, sensitive_features=sensitive_feat)sr.by_group

作者图片
选择率显示,两种性别的预测 1 的人群都偏向于女性,所以存在偏倚。
我们可以使用下面的代码来创建所有指标的图表。
metrics = {
'accuracy': accuracy_score,
'precision': precision_score,
'recall': recall_score,
'false positive rate': false_positive_rate,
'true positive rate': true_positive_rate,
'selection rate': selection_rate,
'count': count}
metric_frame = MetricFrame(metrics=metrics,
y_true=y_test,
y_pred=y_pred,
sensitive_features=sensitive_feat)
metric_frame.by_group.plot.bar(
subplots=True,
layout=[3, 3],
legend=False,
figsize=[12, 8],
title="Show all metrics",
)

作者图片
从上面的图中,我们可以看到各种指标都略微偏向女性。
那么,如果我们想减轻模型中出现的偏差呢?Fairlearn 提供的选择很少,但让我们使用一种称为人口统计均等的方法作为偏差约束,并使用指数梯度算法来创建分类器。
np.random.seed(42)
constraint = DemographicParity()
clf = LGBMClassifier()
mitigator = ExponentiatedGradient(clf, constraint)
sensitive_feat = X_train['sex_Male']
mitigator.fit(X_train, y_train, sensitive_features=sensitive_feat)
然后,我们将使用我们的减轻分类器再次进行预测。
sensitive_feat = X_test['sex_Male']
y_pred_mitigated = mitigator.predict(X_test)sr_mitigated = MetricFrame(metrics=selection_rate, y_true=y_test, y_pred=y_pred_mitigated, sensitive_features=sensitive_feat)
print(sr_mitigated.by_group)

作者图片
选择率的百分比略有增加,比以前更接近了。通过这种方式,我们试图减少预测中的偏差。你可以用各种算法做更多的实验来减轻这种偏见。
结论
机器学习中的偏差是指模型偏好某些预测/条件而非其他预测/条件。每个阶段的不同方面都可能导致偏差。从数据收集到模型评估,可能会出现偏差,我们需要非常了解我们的项目。
为了检测和减轻机器学习模型中的偏差,我们可以使用 Fairlearn Python 包。我们可以探索各种函数来检测偏差。
希望有帮助!
访问我的 社交媒体进行更深入的交谈或有任何问题。
如果您不是作为中等会员订阅,请考虑通过 我的推荐 订阅。
将 Neo4j 与 PyTorch Geometric 集成以创建推荐
利用 PyTorch Geometric 的强大功能,为您的应用开发和训练自定义图形神经网络
自从我看到 PyTorch Geometric (pyG)宣布与斯坦福大学合作举办他们的研讨会以来,我就一直想写关于 PyTorch Geometric 的文章。PyTorch Geometric (pyG)是一个建立在 PyTorch 基础上的库,可以帮助您轻松地为您的应用程序编写和训练自定义图形神经网络。在这篇博文中,我将展示如何从 Neo4j 获取数据,以 PyTorch Geometric 创建电影推荐。
我们将使用的图表是 MovieLens 数据集,它可以作为 Neo4j 沙盒项目方便地获得。之前不知道,pyG 中有一个示例,它也使用 MovieLens 数据集来完成链接预测任务。用户和电影之间的链接是有评分的。然后,我们可以使用图形神经网络模型来预测用户可能对哪些未看过的电影评价较高,然后使用该信息来推荐它们。
这篇文章的主要目的是向您展示如何将 Neo4j 图转换为异构 pyG 图。作为一个附带目标,我们还将使用图形数据科学库准备 Neo4j 中的一些节点特性,并将它们导出到 pyG。
议程
- 开发电影嵌入,根据演员和导演捕捉电影相似性
- 导出 Neo4j 图并构建一个异构 pyG 图
- 在 pyG 中训练 GNN 模型
- 创建预测,并选择性地将其存储回 Neo4j
和往常一样,如果你想了解这篇文章中的例子,我准备了一个 Google Colab 笔记本。
开发电影嵌入,根据演员和导演捕捉电影相似性
首先,您需要在 Neo4j 沙箱中创建并打开建议项目,该项目已经填充了 MovieLens 数据集。接下来,您需要在笔记本中定义 Neo4j 连接。
您可以在沙盒界面的连接详情选项卡下找到凭证。

沙盒连接详细信息。图片由作者提供。
Neo4j 实例包含一个具有以下图表模式的图表。

MovieLens 图形架构。图片由作者提供。
pyG 中的链接预测示例使用标题的单词嵌入和流派的一键编码作为节点特征。为了让它更有趣一点,我们还将开发电影节点特性,封装演员和导演的相似性。我们可以,类似于流派,一次性编码演员和导演。但相反,我们将选择另一条路线,根据电影中出现的演员来捕捉电影的相似之处。在这个例子中,我们将使用 FastRP 嵌入模型来产生节点嵌入。如果你想了解更多关于 FastRP 算法的知识,我建议你去看看我的朋友 CJ Sullivan 写的这篇优秀的文章。
FastRP 算法将只考虑电影和人物的二分网络,而忽略类型和分级。这样,我们可以确保只根据电影中出现的演员和导演来捕捉电影的相似性。首先,我们需要投影 GDS 内存图。
CALL gds.graph.create('movies', ['Movie', 'Person'],
{ACTED_IN: {orientation:'UNDIRECTED'},
DIRECTED: {orientation:'UNDIRECTED'}})
现在,我们可以继续在投影图上执行 FastRP 算法,并将结果存储回数据库。
CALL gds.fastRP.write('movies',
{writeProperty:'fastrp', embeddingDimension:56})
在执行 FastRP 算法之后,每个电影节点都有一个节点属性 fastrp ,它包含了基于当前演员和导演封装相似性的嵌入。
导出 Neo4j 图并构建一个异构 pyG 图
我从这个例子中获得了很多构建自定义异构 pyG 图的灵感。该示例从多个 CSV 文件创建一个 pyG 图。我简单地重写了示例,从 Neo4j 而不是 CSV 文件中获取数据。
从 Neo4j 检索数据时创建节点映射和特征的通用函数如下:
类似地,创建边索引和特征的函数是:
如上所述,代码几乎与 pyG 示例相同。我只是改变了熊猫数据帧是由从 Neo4j 中检索的数据而不是 CSV 文件构建的。在下一步,我们必须定义特征编码器,但我将在文章中跳过它们,因为它们与示例相同。但是,我显然已经将它们包含在 Colab 笔记本中,所以如果你感兴趣,可以查看一下。
最后,我们可以从 Neo4j 获取数据,并构建用户映射和特性,它们将被用作 pyG 异构图的输入。我们将从构造用户节点输入开始。它们没有可用的节点特性,所以我们不需要包含任何编码器。
接下来,我们将构建电影的映射和特征。
对于电影,我们有几个节点特征。首先,序列编码器使用句子转换器库根据标题产生单词嵌入。使用流派编码器对流派进行一次性编码,最后,我们简单地将 FastRP 嵌入转换成正确的结构,以便能够在 PyTorch Geometric 中使用它们。
在我们构建 pyG 图之前,我们必须获取关于收视率的信息,这些信息被表示为用户和电影之间的加权链接。
我们有我们需要的所有信息。现在,我们可以继续构建一个异构的 pyG 图。在异构图中,不同类型的节点包含不同的特征。
构建异构 pyG 图的过程似乎很简单。首先,我们定义每种节点类型的节点特性,然后添加这些节点之间的任何关系。记住,GNNs 要求所有节点都包含节点特性。这里有一个例子,说明对于没有预先存在的特性的用户节点可以做些什么。
与所有机器学习流程一样,我们必须执行训练/测试数据分割。pyG 库用 RandomLinkSplit 方法使这变得非常容易。
准备好 pyG 图。现在,我们可以继续定义我们的 GNN。我只是简单地从 pyG 示例中复制了定义,所以我不会在这里展示它,因为它是相同的。GNN 将预测用户对电影的评分在 0 到 5 之间。我们可以认为这是一个链接预测任务,我们预测用户和电影之间的新链接的关系属性。一旦我们定义了 GNN,我们就可以继续训练我们的模型。
由于数据集不是很大,训练模型应该不会花很长时间。如果可以的话,使用 Google Colab 环境下的 GPU 模式。
最后,我们将预测用户和电影之间的新链接,并将结果存储回 Neo4j。对于电影推荐,我们将只考虑预测评级等于 5.0 的链接,这是最高的可能评级。
我只为每个用户选择了前十个推荐,这样做很简单,不必将成千上万的关系导入 Neo4j。需要注意的一点是,我们不会在预测中过滤掉现有的链接或评级,所以我们会在导入到 Neo4j 数据库时跳过它们。让我们在推荐关系下将这些预测导入 Neo4j。
如果您打开 Neo4j 浏览器,您应该能够在数据库中看到新的推荐关系。

用户和高评分电影之间的预测链接,我们可以使用推荐。图片由作者提供。
结论
PyTorch Geometric 是一个强大的库,允许您开发和训练自定义图形神经网络应用程序。我期待着更多地探索它,并看到我可以使用它创建的所有应用程序。
MovieLens 数据集包含几个我们没有使用过的节点特性,比如可以测试的发行日期或电影预算。你也可以尝试改变 GNN 的定义。如果你发现了任何对你有用的令人兴奋的方法,请告诉我。
和往常一样,代码可以作为一个 Colab 笔记本获得。
特别感谢 马提亚斯·菲 对代码编写的支持和帮助!
将 Pythonic 可视化报告集成到 ML 管道中
原文:https://towardsdatascience.com/integrating-pythonic-visual-reports-into-ml-pipelines-a163d150ed04
每一个工作流的执行都值得被记录(自动地)

作者图片
作者:瓦雷·戴夫、维尔·图洛斯、西罗·格列柯和雅格布·塔利亚布埃
TL;在这篇文章中,我们很高兴介绍用于机器学习管道的 DAG 卡!这些卡片使得在每个工作流程中附加定制的可视化报告变得非常容易,而不需要安装任何额外的工具或基础设施。这项功能是与 Metaflow 在 Coveo 的用户一起开发的,其动机是现代以数据为中心的 ML 工作流的普遍需求。如果你想马上开始,你可以直接进入文档。或者看下面的视频快速游览一下(没有声音)!
每个 ML 管道的人类可读的可视化报告
模型文档需要涵盖整个 ML 渠道。
数据科学和机器学习从业者需要更好、更符合人体工程学的工具来可视化和询问他们的工作流。仅仅关注模型监控是不够的,因为现代 ML 工作流涉及一组异构的功能:要将模型投入生产,您需要非常关注培训之前的工作(例如数据收集、准备和标记)和之后的工作(例如测试、服务、监控和数据漂移)。简单来说,机器学习由构建、测试、部署和维护模型组成,每个步骤都需要多个子步骤。
这在一个我们认为特别重要的重大转变中变得很明显:焦点从建模转向数据,通常被称为以数据为中心的人工智能。尽管一些著名的学者已经谈论它有一段时间了,但以数据为中心的人工智能直到最近才引起整个社区的注意。在 2021 年末, NeurIPS 举办了第一次关于以数据为中心的人工智能的研讨会,在那里我们有幸展示了我们在机器学习工作流程的 DAG 卡方面的工作。
作为一个具体的例子,考虑来自 Chia 等人的这个推荐管道,其中端到端管道是一系列具有显式依赖关系的任务。有些任务必须在其他任务之前执行,有些可以并行运行(红框);有的需要 GPU(蓝色),有的是轻计算;当失败时,有些可能需要重启其他一些,有些可能不需要——等等。

作者图片
我们非常重视这最后一点,我们相信文档也应该包含这些方面:模型文档需要整合到整个 ML 管道中。由于 ML 管道实际上是一个有向无环图(DAG ),我们很高兴为元流推出 DAG 卡,这是一个开源框架,我们在网飞开始并继续在 Outerbounds 开发。在过去的四年里,我们一直在与不同公司的数据科学家一起系统地开发 Metaflow,添加卡片等功能,以提高他们的生产力和所交付项目的质量。
DAG Cards 的想法最初是由雅格布·塔利亚布埃和一家 B2B 公司的团队开发的,这家公司为从电子商务到工作场所的客户服务等各种业务提供基于 ML 的搜索和推荐。雅格布是这样描述这个想法的起源的:
我们有大量的生产模型,但只有其中一些是完全不言自明的。我们拥有的模型在很大程度上依赖于用例,因为用例会改变数据流和用户行为。最后,要理解我们的许多模型做什么,需要一定程度的领域知识。由于我们的团队发现 Metaflow 对于 ML 管道来说是一个灵活的、符合人体工程学的工具,自然要做的事情就是尝试构建一个 DAG 卡生成器,它位于 Metaflow 现有类的顶部。
我们最初受 模型卡 概念的启发,于 2019 年由玛格丽特·米歇尔和同事推出,并由 谷歌云 进一步开发。模型卡是伴随经过训练的 ML 模型的简短文档,提供关于不同条件下模型行为的基准评估和关键信息。我们立刻喜欢上了这个想法。该提议的美妙之处在于它的包容性:技术人员可以使用模型卡来构建更好的 ML 系统,而非技术人员可以使用它们来思考模型的优点和缺点,并负责任地使用它们。我们意识到这对于工作数据科学家是多么有用,并且喜欢为每个工作流获得人类可读报告的想法。但是在实践中应该如何编写报告呢?
如何制作卡片?
找到一种符合人体工程学且毫不费力的方式来生成静态的可视报告,如 DAG 卡,以补充现有工具。
许多现实世界的 ML 管道仍然没有被记录和监控的原因是这些特性不是免费的。特别是,当数据科学家想要监控特定于用例的业务指标和属性时,他们需要花费大量的时间来寻找合适的报告工具,设计和实现报告,并将其连接到生产工作流。
最后一点尤其令人痛苦。虽然现代工具使构建令人惊叹的仪表板成为可能,但让仪表板呈现建模工作流的最新(更不用说历史)结果并不容易,因为您必须连接两个独立的工具。尽管如此,我们还是希望能够看到一份关于曾经生产过的每个模型的报告,包括原型和生产。
在从事了许多以各种形式面临这一问题的数据科学项目之后,我们对当今的工具前景有了以下的高度理解:

在工具的多功能性(使用它可以构建多复杂和多高级的报告)和生成最简单的报告所需的工作量之间存在固有的权衡。明确地说,这种评估是专门从在 ML 管道中生成报告的角度进行的。对于一组不同的需求,排序看起来会有所不同(我们鼓励您在大部分时间里进行这样的评估)。
在左下角,我们有一个最简单的解决方案:从每个建模任务中生成文本日志,这很简单。为每个模型分别存储和访问日志也不难。一个挑战是文本日志不是通用的——特别是,人们不能使用它们来产生任何类型的可视化,而这是数据科学的关键。
笔记本是制作可视化效果的流行选择。打开笔记本,制作图表,并不需要太多的努力。然而,基于现有的参数化模板自动生产笔记本,虽然可能使用像 Papermill 这样的工具,但需要更多的工具和基础设施,以及以新方式使用笔记本的新心智模型。笔记本当然仍然是元流堆栈的关键部分,因为它们对于临时探索和快速原型制作是不可或缺的,而当你知道管道的哪些方面受益于自动化报告时,卡片就派上了用场。
像 Streamlit 和 Plotly Dash 这样的工具使得创建交互式仪表板变得非常容易,但是将它们连接到工作流并不简单,并且在生产环境中可靠地托管它们需要付出努力。对于这个用例来说,这些工具可能太强大了,因为我们的卡片不需要交互。沿着同样的路线,在右上角,还有其他复杂的工具,如 Tableau 和定制 web 应用程序,它们需要大量的前期投资,我们已经看到它们主要用于少数高级用例。我们的目标是降低报告成本,这样我们就可以期望每个渠道都至少包含一个基本卡。
为了实现这一愿景,我们需要一个极其简单的解决方案
- 允许丰富的视觉输出,
- 可以像简单的 print()语句一样轻松地使用,
- 轻松连接到 ML 工作流,并且
- 可以很容易地与非技术利益相关者分享。
由元流卡实现的这些需求在图表上占据了独特的位置:它们使得在每个管道中生成静态的、可视化的报告变得简单,就像 DAG 卡一样。因为它们关注于一组特定的用例,所以它们是对现有工具的补充而不是竞争。
卡片入门
如果你是元流新手,从安装元流开始,看看元流教程。在这之后,你就准备开始用卡了!
最新版本的 Metaflow 带有一个内置的 默认卡 ,您可以将它附加到任何现有的流上——不需要修改代码!就这样运行您的“带卡”流程:
python myflow.py run -with card
运行完成后,您可以通过指定步骤名称来查看运行生成的卡片。例如,要查看开始步骤生成的卡片,请键入:
python myflow.py card view start
这将在您的浏览器中打开卡片。默认的卡片显示了一个任务产生的所有工件,关于它的元数据,以及流 DAG 的可视化。这些信息在开发过程中会派上用场,因为您可以快速观察任务的结果是否有效。
按照我们最初的动机,我们想让从任何元流任务中产生丰富的视觉输出变得非常容易。例如,以下代码片段输出一个表:
from metaflow import card, current
from metaflow.cards import Table, Markdown@card(type='blank')
@step
def start(self):
current.card.append(Markdown("# Here's a table"))
current.card.append(Table([['first', 'second'], [1, 2]]))
您可以像以前一样通过“卡片视图开始”看到的卡片看起来像这样:

有关更多详细信息和示例,请参见 卡组件 的文档。如果您安装了元流 GUI ,卡片在任务视图中自动可见,就像这个使用 Altair 进行可视化的卡片:

如上所示,您可以使用卡片在 GUI 中嵌入定制的可视化和图表。例如,您可以在模型训练步骤中附加一个定制的基准测试卡,它会自动将新模型的性能与现有模型的性能进行比较。参与该项目的任何人都可以在 GUI 中实时看到结果。
为了支持更高级的用例,您可以创建一个定制的 卡片模板 ,它可以使用任何 Javascript 库并输出任意 HTML。卡片模板可以作为公共 Python 包发布和共享,因此您可以与您的团队和更广泛的元流社区共享自定义可视化。
感谢阅读!我们希望听到您对这一新功能的反馈,以及对新卡片模板的想法。最简单的方法是加入我们和 Metaflow 社区 Slack 的 900 多名数据科学家和工程师👋
原载于 2022 年 1 月 25 日【https://outerbounds.com】。
意式浓缩咖啡中的有意通灵
原文:https://towardsdatascience.com/intentional-channeling-in-espresso-e5f288c05578
咖啡数据科学
为了科学,故意弄乱镜头
在一次在线讨论中,有人提出有意的窜流并不总是会降低提取率。所以我决定尝试重复一个实验,并引起一些有意的通灵。然后我可以看看味道和提取率。
我从一个普通的冰球开始,然后在夯实后用牙签在冰球上打了一个洞。

所有图片由作者提供
我打了一针,虽然有一些通道,但它似乎部分自愈。

然后我又拉了一个没有意向渠道的镜头。我还拉了一个凹分布的额外镜头,你可以在下图中看到。这是微妙的,但边缘比中心有更多的咖啡,使他们有更高的密度。

凹型分布,中间咖啡较少,以形成更密集的边缘。
设备/技术
咖啡:家庭烘焙咖啡,中杯(第一口+ 1 分钟)
镜头准备:断奏夯实
预灌注:长,约 25 秒
输液:压力脉动
过滤篮 : 20g VST
其他设备: Atago TDS 计、 Acaia Pyxis 秤
绩效指标
我使用了两组指标来评估技术之间的差异:最终得分和咖啡萃取。
最终得分 是记分卡 7 个指标(尖锐、浓郁、糖浆、甜味、酸味、苦味和余味)的平均值。当然,这些分数是主观的,但它们符合我的口味,帮助我提高了我的拍摄水平。分数有一些变化。我的目标是保持每个指标的一致性,但有时粒度很难确定。
使用折射仪测量总溶解固体量(TDS),这个数字结合咖啡的输出重量和输入重量用于确定提取到杯中的咖啡的百分比,称为提取率(EY)** 。**
数据
对于这三个镜头,我将它们分别放入三个杯子中,这样我就可以看到提取率如何随着时间的推移而发展。有意通灵做得最差。

至于味道,有意通灵在味道上也明显更差,而凹面在味道上稍微好一些。

对 TDS 和 EY 来说,有意通灵就没那么好了。

就时间而言,有意通灵跑得更快,这可能是由于通过通道的流动更快。

我看了看冰球的底部,在冰球的底部没有明显的通道。我以为会有,而凹型分布的黑点更多。

我观察了有意的通道,对味道和提取率有明显的影响。然而,这种效果并不总是容易在镜头的视频中看到,也不容易在冰球的底部看到。这提供了一些有趣的证据,表明一些在镜头中诊断问题的工具并不总是清晰的。
如果你愿意,可以在 Twitter 、 YouTube 和 Instagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。你也可以关注我的媒体和订阅。
我的进一步阅读:
Jupyter 笔记本的交互式仪表盘,带有 Mercury 框架

你刚刚在笔记本上准备了分析报告。恭喜你!🎉你想与他人分享你的结果和发现。坏消息是你不能直接和你的朋友分享 Jupyter 笔记本代码,因为他们不会说 Python🐍。他们不能安装所需的包,不能从 GitHub 库提取代码,也不能在本地运行笔记本。好消息是,你可以轻松地将你的笔记本转换成一个独立的网络应用程序。
在这篇文章中,你将学会如何:
- 在 Jupyter 笔记本中创建仪表板,
- 将笔记本转换为独立的 web 应用程序,
- 将基于笔记本的应用程序部署到 Heroku。
这篇文章的灵感来自于上一篇文章Ruben Winastwan的《从 Jupyter Notebook 创建一个交互式仪表板》。
鲁本在他的文章中创造了旧金山犯罪的仪表板。他的代码可以在 GitHubhttps://github.com/marcellusruben/sf-crime-voila获得
笔记本中的仪表板
创建仪表板的代码可以分为三个部分:
- 从 CSV 文件加载数据集。
- 设置过滤数据的参数。
- 将数据绘制成地图和直方图。
代码在下面的要点中给出。
创建仪表板的代码。
您将需要在requirements.txt文件中的以下包来运行笔记本:
mljar-mercury
pandas
numpy
matplotlib
folium
仪表板显示在下面的屏幕截图中:

Jupyter 笔记本中的仪表板视图
创建仪表板的代码只是 Python 代码。请注意,仪表板中使用的参数只是简单的变量。没有使用小部件。如果你想改变仪表板中的一些东西,你需要改变变量并重新执行笔记本。
激动人心的事情将发生在🪄的下一步
将笔记本转换为 Web 应用程序
现在我们需要一些魔法。鲁本在他的文章中,使用 ipywidgets 和 Voila 将笔记本变成了网络应用。这里我将使用一个 Mercury 框架。它允许您通过在笔记本的开头添加 YAML 标题来定义笔记本的小部件。YAML 在下面:
作为第一个原始单元格添加到笔记本中的 YAML 标题。
YAML 标题应添加在笔记本的开头,作为一个 原始 单元格。

完整的笔记本代码和 YAML 标题。
YAML 配置用于为笔记本创建小部件。

小部件是基于 YAML 配置定义的。
以下微件面板是基于 YAML 创建的:

从 YAML 标题生成的窗口小部件面板。
需要注意的是,YAML 中的变量应该与代码中的变量同名。稍后,在点击Run按钮后,widgets 值将被赋给代码中的变量。

YAML 参数应该与变量名相同。
YAML 准备好了。是时候检查一下它在水星里的样子了。
您可以在笔记本开发过程中观察 web 应用程序的外观。笔记本上有一个命令mercury watch — 用于观看代码更新。请运行以下命令:
mercury watch SF_crime.ipynb
您将在 http://127.0.0.1:8000/ 上运行应用程序

在监视模式下运行的应用程序。
每次您更新笔记本时,它都会在 Mercury 中自动更新(无需按 F5😊)
部署到 Heroku
仪表板已经准备好了,让我们把它部署到 Heroku 上。
我假设你已经安装了 Heroku 账号和 Heroku 命令行(CLI)工具(如果需要的话,查看 文档 )。
让我们运行以下命令来创建一个 Heroku 应用程序:
heroku create dashboard-mercury
在项目目录中。(你应该在创建命令中使用不同的名称)
我们将需要添加一个Procfile到我们的项目中:
web: mercury runserver 0.0.0.0:$PORT --runworker
让我们在 dyno 中设置必要的环境变量:
heroku config:set ALLOWED_HOSTS=dashboard-mercury.herokuapp.com
heroku config:set SERVE_STATIC=True
heroku config:set NOTEBOOKS=*.ipynb
您可以通过运行以下命令来部署应用程序:
git push heroku main
仅此而已。你应该能够检查在 https://dashboard-mercury.herokuapp.com 运行的应用程序。以下是笔记本电脑仪表盘在移动和桌面上的视图。

仪表板应用程序的移动视图。

仪表板应用程序的桌面视图。
结论
Jupyter 笔记本是分析数据和可视化结果的绝佳工具。由于有了 Mercury 框架🧰.,用 Python 创建的仪表板可以作为独立的 web 应用程序轻松共享交互部件由 YAML 头定义。小部件的值被分配给代码中的变量,因此不需要重写或更改笔记本的代码。该应用程序可以很容易地在免费的 Heroku dyno 上发布。
仪表板代码和数据可从 GitHub 库获得。
我是 水星 框架的作者。
使用 Julia 和 Pluto 笔记本进行交互式数据可视化
Pluto 是一项重要的发展,它允许您轻松地与数据进行交互。我们使用 Julia 绘图包快速浏览一下,轻松创建令人印象深刻的图表。

一些朱莉娅情节-作者图片
有人认为朱莉娅是新的巨蟒。如果这是真的,那么冥王星可能就是新的木星笔记本。
我们将非常简要地看一下如何在 Julia 中绘制图表,同时,稍微熟悉一下(相当)新的 Pluto 笔记本和它们的交互特性。
我们将开发的交互的预览图——图片由作者提供
Pluto 是 Jupyter 笔记本上 Julia 语言的一个改进,我预测,我们将来会更多地使用它。
对于以下内容,我不假设任何知识的朱莉娅或布鲁托。
朱莉娅
Julia 是一种相对较新的语言,具有高级语法,旨在易于使用和理解。
然而,与 Python 不同的是,它是一种编译语言,这意味着尽管它像 Python 一样容易编写,但它运行得更快,因为它被转换为更容易被计算机理解的低级代码。如果您必须处理需要大量处理的大型数据集,这非常有用。

Julia 徽标——由 Julia 项目提供的公共域图像
与 Python 相比,Julia 对程序的布局也没有那么挑剔。
Julia 是一种通用语言,拥有现代编程语言的所有特性。在这里,我们将看看 Julia 的数据可视化功能,它既令人印象深刻又易于使用。
一旦你安装了 Julia 和 Pluto,你就可以创建运行 Julia 代码的 Pluto 笔记本,执行它们并导出为 HTML、PDF 或独立的 Julia 程序。您可以将可视化保存为标准的 png 文件,并将其保存到您的文档中。
普路托
冥王星是一个笔记本环境,让人想起 Jupyter,但有一些显著的不同。有了 Jupyter,你很容易就能找到一种新的笔记本电脑,它会给你带来与以前不同的结果。

Pluto 徽标—图片由 Pluto 作者提供(麻省理工学院许可)
在 Jupyter 中,每个单元都是单独运行的,但是所有单元都可以访问所有全局声明的变量,无论它们被分配到哪个单元。例如,假设我们有三个细胞,这一个
a = 13
接下来是这个,
print(a)
接下来是这个,
a = 10
如果运行整个笔记本,第二个单元格将打印出值 13。但是如果你单独运行第二个单元格,它将打印出 10。您可能会认为运行相同的代码但得到不同的结果不是最佳的。
更糟糕的是,如果您删除第一个和第三个单元格,然后运行第二个单元格,它仍然会打印出一个值,因为该变量的值仍然存在于笔记本中,即使定义它的单元格已被删除。
这不可能发生在冥王星。Pluto 跟踪单元依赖关系,并根据需要更新它们。在上面的例子中,一旦第三个单元被执行,Pluto 将检测到第二个单元需要被更新,并这样做。在第一个和第三个单元格被删除的情况下,Pluto 将重新运行第二个单元格,它将检测到该变量不再存在并报告相应的错误。
安装朱莉娅和布鲁托
要安装 Julia,你需要去https://julialang.org/下载适合你操作系统的版本。安装很简单,只需按照说明操作。
一旦你安装了它,你需要做几件事情。因此,启动 Julia,您将在如下窗口中看到命令提示符:

第一件事是安装冥王星。在提示符下键入],然后输入add Pluto:
julia> ]
pkg> add Pluto
方括号告诉 Julia 您将使用包管理器,而Pluto是您将要添加的包。
要返回 Julia 提示符,请在空行上按 backspace 或 Ctrl-C。
在一个正常的 Julia 程序中,我们需要添加更多的包,但是 Pluto 会自动为我们处理这些,所以接下来我们需要做的就是运行它。
julia> import Pluto
julia> Pluto.run()
这将在浏览器中启动 Pluto(Pluto 的人推荐最新版本的 Firefox 或 Chrome)。

冥王星欢迎屏幕——作者图片
点击new notebook,我们将开始。

一个空的冥王星笔记本——作者图片
我们已经准备好了。
朱莉娅·图斯
与大多数其他语言一样,Julia 依赖于特定专业用途的代码库。因为我们要研究 Julia 中的绘图,我们最初对名为 Plots 的包感兴趣。这为我们提供了创建数据可视化的能力。但是我们还需要使用另外两个包, CSV 和 DataFrames,来读取 CSV 文件并将其加载到 dataframe 中。
所以我们需要执行的第一段代码是这样的:
using Plots, CSV, DataFrames
注意 DataFrames 有一个大写字母“F”。
你将会看到的是:

准备下载软件包—图片由作者提供
点击单元格右下角的 play 按钮,单元格就可以执行了,小云符号告诉我们需要下载三个包。
运行单元后,云将被标记所取代,表示软件包已成功下载:

软件包已下载—图片由作者提供
你的第一次想象
Pluto 只允许在一个单元中运行一段代码,然而,在begin和end块中包含多行代码也是可行的。如果你忘记了,冥王星会提醒你并自动帮你修正。这是我们想要运行的代码。
x = 1:10; y = rand(10);
plot(x,y, label="my label")
将鼠标悬停在前一个单元格上,点击左下角的+符号,打开一个新单元格。

我们的第二个细胞——作者图片
现在试着运行它。

一个错误-作者的图像
Pluto 已经检测到了这个错误,并给出了两种可能的解决方案,将代码放在 begin/end 块中,或者将代码分成两个单元。您可以单击两个解决方案中的任何一个。

第一张图表—作者提供的图片
选择修复会自动运行单元格,您可以在代码上方看到结果。
需要注意的是:第一次运行单元时,需要一段时间来执行。这是因为 Julia 需要在第一次运行时编译代码。后续运行将会更快。例如,上面的代码第一次运行需要大约 8 秒钟(我使用的是普通的 Windows 笔记本电脑),但是随后的运行只需要几百微秒。
让我解释一下这是怎么回事。
x = 1:10; y = rand(10);
这位代码创建两位数据,一位称为
x,另一位称为y。 *x*被赋予从 1 到 10 的数字范围的值,而y被赋予 10 个伪随机数的范围(每个将具有 0 到 1 之间的值)。因此,我们在这里有一个图形的基础:一个范围从 1 到 10 的 x 轴和 x 轴上每个点的 y 值。
下一步很简单。
plot(x,y, label="my label")
这段代码调用一个函数来绘制图形,我们所做的就是给它 x 和 y 值——我们也给了它一个标签。
冥王星反应性
现在让我们尝试一些不同的东西来展示冥王星的一个强大的特征。让我们将多行单元格一分为二。

新版——作者图片
运行细胞给出了一个熟悉的结果。现在再次运行第二个单元。
因为数据是随机产生的,所以x和y中的值会改变。Pluto 将检测到创建绘图的第三个单元格依赖于第二个单元格中的值,然后自动运行它以生成更新的绘图。印象深刻?你应该害怕。

这是冥王星的一个重要特征,确保笔记本始终一致。
来自 CSV 数据的图表
这很简单,但让我们来看一些真实的数据。
我有几个表,我在其他文章中用过。这是一组关于过去几十年英国伦敦天气的数据。我是从英国气象局提供的表格中推导出来的(这些是公共领域的数据,我的版本可以从我的 Github 库中免费下载)。
数据记录了每个月记录的最高温度、最低温度、降雨量和日照时数。我有两个文件,一个是几年的数据集,一个是 2018 年的,只有。它们是 CSV 格式的,就像您可以导入到电子表格中一样。
下面一行将数据读入一个变量d,它是一个 DataFrame。
dforig = CSV.read("londonweather.csv", DataFrame)
运行代码的结果是,我们现在有了一个如下所示的数据表:

我们下载的数据是一个表格,有 6 栏:Year、Month、Tmax (最高温度)、Tmin (最低温度)、Rain (降雨量,单位为毫米)和Sun (日照时数)。
最初,我们只关注一年。因此,让我们在一个新单元格中定义年份:
year = 2018
然后,我们将创建一个新的 dataframe,其中包含该年的数据子集。
d=filter(:Year => y -> y==year, dforig)
现在我们将从新的数据框架d中绘制一些图表。
条形图
要在条形图中绘制每个月的最高温度,我们可以这样做:
bar(d.Month, d.Tmax)
bar是一个绘制条形图的函数(还有什么?)并且我们提供 x 和 y 轴的列作为参数。我们通过使用 dataframe 的名称,后跟列的名称来实现这一点。这两个名字用一个点隔开。
这里,我们将列Month 作为 x 轴,将Tmax作为 y 轴,因此我们在表格中绘制了 12 个月中每个月的最高记录温度。
将它放入一个新的代码单元并运行它,您会惊喜地(我希望)看到这个图表:

条形图—按作者分类的图像
按照英国的标准,七月相当暖和了!
折线图
如果你想制作一个折线图,你可以做同样的事情,但是使用函数plot
plot(d.Month, d.Tmax)

折线图—按作者分类的图像
(注意,在这个图中,我添加了几个参数来隐藏图例并显示标题。)
而且,如果你想在同一个图表上绘制最高和最低温度,只需将 x 值(和标签)放在一个向量中。:
plot(d.Month, [d.Tmax, d.Tmin], label=["Tmax","Tmin"])
两个值d.Tmax和d.Tmin ,在方括号中组合在一起,并用逗号分隔。这是向量或一维数组的符号。此外,我们还为线条添加了标签,这些标签以相同的方式分组。我们得到了这样一个图表:

散点图
或者散点图怎么样?散点图通常用于查看是否可以在数据中检测到模式。这里我们绘制了最高温度与日照时数的关系图。正如你所料,这是一种模式:一种明显的相关性——日照时间越长,气温越高。
scatter(d.Tmax, d.Sun)

圆形分格统计图表
这是一个饼图,显示了每个月的年降雨量比例。
pie([d](http://localhost:1234/edit?id=080a07f0-7209-11ec-3da3-45f784b1d6fd#d).Rain)

柱状图
现在,我们将加载更多的数据:
d2 = CSV.read("londonweather.csv"`, DataFrame)
这类似于我们一直使用的数据表,但更大,因为它涵盖了几十年的数据,而不仅仅是一年。这给了我们大量的降雨量数据,这样我们就可以看到伦敦在一段较长的时间内的降雨量分布。
histogram(d2.Rain, label="Rainfall")
这是结果。

保存图表
在 Jupyter 环境中看到这些图表当然很好,但是为了有用,我们需要能够保存它们以便在我们的文档中使用它们。
您可以像这样保存图表:
savefig("myhistogram.png")
运行此代码时,不会显示图表,但会以给定的文件名保存。
交互性
现在真正有趣的部分来了。使用 Pluto,我们可以将一个 HTML 控件(比如一个滑块)绑定到一个 Julia 变量。通过这种方式,我们可以交互式地改变绘制的数据。
从上面的代码中,您需要删除将年份定义为2018的单元格,并用以下单元格替换它:
using PlutoUI
这将安装 Pluto UI 包,让我们绑定到 HTML 控件。然后这个:
[**@bind**](http://twitter.com/bind) **year Slider(1957:2018, default = 1990)**
此代码创建一个滑块,其默认值为 1990,范围为 1957 到 2018。并且绑定到变量year。这个单元格将在笔记本中创建一个滑块元素,当它被调整时,year的值将被相应地设置。
当您将此与所有使用 year 的单元格的自动更新相结合时,我们会得到一个动态笔记本,它的图表会随着我们移动滑块而自动更新。
滑块看起来是这样的:

当我们移动滑块时,我们会看到所有的图表都在实时更新。
那有多好!
结论
我希望这是有用的——我们已经查看了 Julia Plots 中可用的基本图表——比我们在这篇短文中看到的要多得多——但我希望你已经发现这篇介绍激起了你寻找更多关于 Julia、Plots,特别是冥王星的欲望。
下载
右键单击下面的链接下载文件,然后将它们复制到您将用于 Pluto 笔记本的目录中
一如既往,感谢阅读。如果你想知道我什么时候发表新文章,请考虑注册我的临时子栈简讯。
之前的文章可以看这里。
PowerBI 中具有形状地图可视化的交互式地理空间可视化
使用公共收入和数字边界数据的逐步指南/演示

作者图片
动机
对于大多数公司来说,PowerBI 是一个流行的商业智能报告工具。除了可视化结构化表格数据的常见用例之外,它还能够读取地理空间数据,并将它们转换为交互式可视化,其中可以添加参考图层(如特定地区的平均收入)以了解潜在的人口统计模式和关系。
数字边界将地图划分为不同大小的地理区域。一个众所周知的数字边界是国家边界。尽管 PowerBI 在其地图设置中提供了一些内置的数字边界,如下所示,但对于商业应用程序来说,它通常被认为是不够的,因为商业应用程序通常需要较低的粒度来输出更多的“区域”数据。

图 1: PowerBI 内置数字边界。图片作者。
这个问题的一个解决方案是将自定义地图导入到 PowerBI 中,并使用所需的数字边界设置。要做到这一点,需要很多步骤,其中一些步骤利用了外部依赖性,但是当我第一次尝试这个任务时,通过在线搜索(更不用说在 Medium 平台上,甚至是 ChatGPT 上)很少有文章能够提供足够的帮助。
使用 Shape Map Visual(可以在 PowerBI 中免费访问),本文提供了从将自定义地图导入 PowerBI 到将地图数据与参考图层进行匹配,以及最终创建交互式地理空间可视化(为数据提供上下文)的一步一步的指导,最终可能会给业务决策者留下深刻印象!
步骤 1:根据特定的数字边界准备自定义地图数据
出于本演示的目的,我将使用澳大利亚的统计区域 3(“SA3”)数字边界。这个演示可以很容易地推广或扩展到其他国家设置的数字边界。
作为背景,澳大利亚的 SA3s 旨在输出区域数据,该数据涵盖 30,000 至 130,000 人之间的人口(相比之下,SA2 为 3,000 至 25,000 人,SA4 为 100,000 人以上)。
SA3 数字边界的数据可以来源于这里的。特别是,Shapefile 被下载到一个 zip 文件中。

图 2: SA3 数据下载。图片作者。
下载的 zip 文件(不需要解压缩)然后被转换成 PowerBI 可读的 JSON 文件。这可以通过以下两(2)个步骤完成:
- 进入 mapshaper.org的,导入下载的 zip 文件,按照下面的提示选择“导入”。

图 3:导入 shapefile。图片作者。
- 特定数字边界的地图应出现在屏幕上,如下所示。通过单击右上角的“导出”将此导出为 JSON 文件(GeoJSON 或 TopoJSON)。

图 4:导出地图。图片作者。
值得一提的一个技术性问题是,使用上述步骤导出的 JSON 文件对于 PowerBI 导航来说几乎肯定是计算开销很大的(例如,由于地图特征和属性的维数很高,在尝试放大和缩小地图时可能会有相当大的延迟)。因此,建议在导出之前,通过点击右上角附近的“简化”来“缩小”形状文件。出于本演示的目的,地图数据已按 0.3%的简化比例进行了简化(这并未对地图要素和属性的质量产生实质性影响,您将在后面看到这一点)。
步骤 2:准备参考数据
出于本演示的目的,我将通过 SA3 数字边界可视化人口收入。
这里公布了各个统计领域的个人收入数据(表 2)。下面提供了要导入到 PowerBI 的参考数据片段。这是以 Excel/CSV 格式编写的。

图 5:参考数据。图片作者。
步骤 3:增强 BI 形状地图的视觉效果
在我们匹配 PowerBI 中的地理空间和参考数据之前,需要首先启用 Shape Map Visual。在 PowerBI 中,这可以通过转到文件(左上角) >选项和设置>选项>预览功能并选中“形状映射可视化”复选框来完成。
然后,我们导入步骤 2 中准备的参考数据。成功导入后,数据中的四(4)个字段应出现在字段窗格中,如下所示。

图 6:参考数据导入。图片作者。
现在,使用以下步骤导入在步骤 1 中创建的自定义地图。
- 选择位于可视化效果窗格中的形状映射图标,并选中字段窗格中某个字段旁边的框(只是为了在下一步中激活可视化效果窗格下的选项),如下所示。

图 7:导入步骤。图片作者。
- 导航到“可视化效果”窗格中“构建可视化效果”图标右侧的“可视化效果格式”图标。在“Map Setting”下拉列表中,选择 Custom map 并导入在步骤 1 中创建的 JSON 文件,如下所示。

图 8:导入步骤继续。图片作者。
- 导航回“构建视觉”图标,将 SA3_Name 和 Average_Annual_Income 字段从“字段”窗格分别拖到“位置”和“颜色饱和度”字段,如下所示。

图 9:匹配数据。图片作者。
最后,现在 PowerBI 主机中应该会出现一个如下所示的视图。这种可视化通过色标来表示不同的收入水平,并通过在地图上选择特定的 SA3 区域时显示参考数据来与用户进行交互。

进一步定制
您可以通过编辑色标、将不同的参考数据放在 tooptip 上、更改缩放设置或在侧面添加滑块(例如,对于 State,当选择特定状态时会自动缩放可视化)来进一步定制可视化。
此外,如果您按日期排列参考数据,PowerBI 允许动画显示参考数据如何随时间变化。例如,您将能够看到特定区域的颜色如何随时间变化。对于一个时间序列的形状图数据,本链接中的视频提供了一个很好的教程。
总之,本文提供了如何将表格参考数据与地理空间数据结合起来的分步指南,最终使用 PowrBI 中的 Shape Map Visual 将它们可视化。
对于对其他可视化技术或数据科学应用感兴趣的读者,我在媒体上写了关于它们的博客!
参考
[1]澳大利亚统计局(2021),数字边界文件,澳大利亚统计地理标准(ASGS)第 3 版 , ABS 网站,2022 年 12 月 17 日访问,(在https://creativecommons.org/licenses/by/4.0/获得许可)
[2]澳大利亚统计局(2021) 澳大利亚个人收入 , ABS 网站,2022 年 12 月 17 日访问,(https://creativecommons.org/licenses/by/4.0/许可)
美国的代际流动——一个数据科学问题(1/5)
本系列由 Ibukun Aribilola 和 Valdrin Jonuzi 共同撰写,是社会公益数据科学教程的一部分。

图片来自 Julian Hochgesang (Unsplash)。
界定代际流动
虽然衡量代际流动的方法可能有所不同,但现有文献中对代际流动的定义通常是指成年子女与其父母的社会经济表现的比较。
在美国国家经济研究局(National Bureau of Economic Research)最近发表的一篇由乔利和杜劳夫(Cholli and Durlauf)撰写的工作论文中,代际流动性被定义为“父母和子女的社会经济地位之间的关系”。在经济学家中,社会经济地位是指收入。人们提出了一系列机制来解释父母与子女地位之间的联系,这些机制大致分为家庭和社会因素。家庭因素可以包括财富、教育、家庭构成等。另一方面,社会模型关注社会环境,尤其关注学校和社区(Cholli & Durlauf,2022)。
在研究邻里关系对代际流动的影响时,Chetty 和 Hedren (2018a)将代际流动定义为子女的预期收入取决于父母的收入。Chetty 等人(2014 年)提供了代际流动性的等级定义,将收入分配相对于父母向上移动的机会描述为父母和子女收入百分位等级之间的相关性。Deutscher (2018)使用等级定义测量了澳大利亚环境下的代际流动性,并得出了与 Chetty 之前工作一致的结果。
另一项关于美国代际流动性下降的研究使用邓肯社会经济指数(SEI)得分——职业排名的指标——来判断职业随着时间推移的相对地位(Song et al .,2019)。然而,对邓肯社会经济指数的一个主要批评是,它的计算是基于男性人口普查数据,可能不代表女性人口(Marsh,1968)。因此,我们决定不使用代际流动的职业定义,而使用百分位等级定义。
这些研究中有许多是在作为西方世界一部分的美国进行的,当外推至重视经济成功以外的特征的社会和文化时,这些研究是有限的。这类研究是在核心家庭是常态的市场经济国家进行的。例如,Titma 等人(2002 年)注意到苏联社会是如何重视等级而不是经济成功的,将自己定位为精英统治。
衡量代际流动性
大多数代际流动研究使用线性回归来预测孩子的收入,父母的收入和其他变量是预测变量。线性回归的许多限制之一是它不能产生贫困或富裕陷阱;认为贫穷家庭更难向上爬或富裕家庭更难向下爬的观点。非线性模型弥补了这一点。
研究还表明,代际流动因地理区域、人口统计和时间而异。2012 年,美国总统经济顾问艾伦·克鲁格(Alan Krueger)提出了了不起的盖茨比曲线(Great Gatsby curve),强调了收入不平等和代际流动性之间明显的直接关系(Krueger,2012)。
Chetty 和 Hendren (2018a)测量了童年邻里接触对代际流动性的因果影响,方法是取搬到一个新地区的儿童的平均等级结果与一直生活在所搬到地区的儿童的平均结果之间的差异。他们还使用线性回归来衡量协变量的影响,如父母的婚姻状况,流离失所的冲击(父母收入的变化,自然灾害),以及兄弟姐妹的比较。在他们的附带出版物中,Chetty 和 Hendren (2018b)使用了一个固定效应回归模型来确定儿童成长时收入高于父母的县的特征,如种族和收入隔离、收入不平等、教育质量等。
在对美国代际流动的地理特征的描述性分析中,Chetty 等人为他们的等级定义提供了两个相关的模型。他们根据儿童相对于同一出生队列中其他儿童的收入进行排名,并根据这些儿童的父母相对于这些出生队列中有儿童的其他父母的收入进行排名。在回归父母和子女的等级分布后,作者通过截距和斜率与父母百分位数的乘积来测量绝对流动性——来自任何给定百分位数家庭的子女的预期等级。Chetty 等人创造了“等级-等级”一词来描述这种回归父母和子女收入等级分布来计算代际流动性的方法。另一方面,相对流动性只是回归模型的斜率,这意味着流动性是聚合的,而不是以孩子的父母为条件。其他一些关于流动性的著名著作也使用了这种等级-等级方法来衡量流动性。此类研究包括代际流动性的近期趋势研究(Chetty 等人,2014 年)和 Deutscher 关于童年接触对流动性影响的研究。
另一方面,Titma 等人(2002 年)试图通过使用苏联的数据来衡量代际流动性。等级在社会中的重要性,加上劳动自由的限制和职业归属政策,使得教育在预测代际流动时具有更高的权重。使用参与者的性别和教育水平以及他们父母的级别和教育水平的数据,并应用对数线性模型(特别是逻辑回归),该假设得到了证实。虽然与西方世界的结果相比,遗传对流动性的影响较小,但教育和文化资本(父母的教育水平)的影响更大。
费瑟曼-琼斯-豪泽假说认为,虽然工业化国家之间观察到的(或结构性的)流动性可能不同,但循环流动性不会。从本质上讲,流通流动性因素存在于个人地位与其父母地位之间的独立关联中,不受技术变革、特定职业的供求变化、家庭规模等因素的影响(McMurrer 等人,1997)。这个假设受到了极大的质疑;Slomczynski 和 Krauze (1987)对 16 个国家的样本进行的比较似乎否定了这一观点,而小岛康誉和马克在 1994 年对日澳数据进行的研究则支持这一观点。如果是真的,这可能意味着在美国进行的研究结果可以推广到其他工业化国家。
设计我们的方法
最初,我们的计划是预测和比较美国和德国的代际流动性。交付内容是一个交互式数据新闻网络项目,因为我们的团队分布在旧金山和柏林,我们打算采访当地人并分享他们的故事。由于缺乏德国代际流动的可用数据,我们决定将研究重点放在美国。
之后,我们查看并整合了来自机遇地图集和谷歌数据共享空间的代际流动性和相关数据。在回顾该领域的研究(美国经济学家和领域专家哈吉·柴提占据了压倒性优势)时,我们设法拿到了他的一篇论文中使用的数据。在进行了初步的探索性数据分析后,我们得出结论,我们需要转向;我们发现的结果并不特别有趣,继续研究方法不太可能产生足够的原创内容,使我们在网络项目上花费的努力值得。
最终,我们选定了这一系列针对对数据科学感兴趣的同学的文章,在这些文章中,我们将以代际流动为例来解释数据科学的思想和方法。该系列的概念是由 Ines Montani 的自然语言处理课程,高级自然语言处理与空间的启发。虽然这个项目和类似的项目动机相似,但据我们所知,我们的项目是唯一一个将代际流动作为案例研究的项目。
收集数据
既然我们知道这项研究的主要目标是预测美国的代际流动,我们需要收集数据来帮助我们的分析。我们将结合来自三个来源的县级数据,即来自 Chetty & Hendren 论文的全国人口普查数据、机会地图集和数据共享空间。
切蒂-亨德伦公司
正如本文前面提到的,Chetty 和 Hendren 研究了儿童的代际流动机会是如何受到他们成长环境的影响的。他们使用了 1996 年至 2012 年间举家跨县迁移的 500 多万儿童的人口普查数据。
Chetty & Hendren 制作的主要数据之一是县级因果效应估计和协变量的表格。换句话说,除了一系列可能影响代际流动的其他协变量之外,他们还提供了一系列迁移到一个县对代际流动的因果影响。该数据集提供了我们县级结果数据的主要来源,可以在 Opportunity Insights 网站上找到,以及他们论文中的其他数据集。具体来说,我们使用“在线数据表 4”和附带的变量描述。
机会图册
本分析的第二个数据来源是机会图谱。机会地图集是一个互动平台,旨在分享关于为儿童提供最佳脱贫机会的社区的特征或机会的数据。我们获得了县和地区层面的社会流动性因素的数据,如监禁率和就业率。该数据可在 Opportunity Atlas 网站上下载(侧栏>‘下载数据’),并可根据您对特定变量、地理粒度(通勤区、县或地区)和子群(父母收入、孩子种族和孩子性别)的需求轻松定制。

截图来自机遇图集网站作者。
为了我们的分析目的,我们下载了县和地区级别的七个特征的数据,即 2012-2016 年 35 岁时的家庭收入、监禁率、35 岁时结婚的比例、高中毕业率、大学毕业率和贫困率。所有 14 个数据表都可以在这个 Google Drive 文件夹中找到。
数据共享空间
最后,我们从数据共享空间中获得了其他县的特征。Data Commons 数据集中的变量既不在 Chetty & Hendren 的数据集中,也不在 Opportunity Atlas 数据集中。数据共享 Python 和PandasAPI 使我们能够使用 Python 脚本下载数据。要使用,首先导入 Python 中的模块。
import datacommons as dc
import datacommons_pandas
现在我们已经设置了数据共享 API,我们可以查询县一级的变量列表。
dcid_usa = "country/USA"
dcids = dc.get_places_in([dcid_usa], "County")[dcid_usa]stat_vars_to_query = ["Median_Age_Person",
"Median_Income_Person",
"Population",
"Count_Person_PerArea",
"Count_Person_Literate",
"Count_Person_BornInStateOfResidence",
"Count_Person_AbovePovertyLevelInThePast12Months",
"Count_Household",
"Count_HousingUnit",
"UnemploymentRate_Person",
"LifeExpectancy_Person",
"Count_Person_DetailedEnrolledInCollegeUndergraduateYears",
"Count_Person_DetailedHighSchool",
"Count_School",
"dc/c58mvty4nhxdb", *#Mean Cohort Scale Achievement of Student*
"GenderIncomeInequality_Person_15OrMoreYears_WithIncome",
"LifeExpectancy_Person",
"Count_Person_15OrMoreYears_NoIncome",
"Median_Earnings_Person",
"Count_Person_1OrMoreYears_DifferentHouse1YearAgo",
"Median_Income_Household",
"Count_Household_LimitedEnglishSpeakingHousehold"
]df = datacommons_pandas.build_multivariate_dataframe(dcids, stat_vars_to_query)
df.insert(0, 'name', df.index.map(dc.get_property_values(df.index, 'name')).str[0])
df.insert(1, 'lat', df.index.map(dc.get_property_values(df.index, 'latitude')).str[0])
df.insert(2, 'lng', df.index.map(dc.get_property_values(df.index, 'longitude')).str[0])
display(df)
如本笔记本的最后几个单元格所示,进一步清理数据集后,您将获得最终的数据集。(点击此处查看完整表格)。
结论
在这篇文章中,我们总结了代际流动领域其他杰出研究人员的工作,并解释了为什么我们坚持 Chetty 和 Hendren 对代际流动的百分位数排名定义——相对于父母收入分配上升的机会。我们还带领读者通过这个项目的迭代过程,从美国和德国的比较到我们现在拥有的一系列数据科学教程风格的文章。我们还通过介绍我们的三个数据源(Google 的数据共享、Opportunity Atlas 和 Chetty & Hendren 的数据集)暗示了下一篇文章,并解释了为什么每个数据源对即将进行的分析都很重要。
这是一个关于如何使用代际流动作为案例研究来进行数据科学项目的 5 部分演练。第二篇文章展示了我们如何合并和清理数据集,以创建一个可用于各种分析的综合数据集。第三篇文章侧重于执行探索性数据分析,以理解数据并开始思考如何预测代际流动。第四和第五篇文章分别描述了如何使用回归和分类方法预测代际流动。
参考
Chetty、n . Hendren、p . Kline、e . Saez 和 n . Turner(2014 年)。美国还是一片充满机遇的土地吗?代际流动的最新趋势。《美国经济评论》,第 104 卷第 5 期,第 141-47 页。
Chetty,n . Hendren,p . Kline,Saez,E. (2014 年 11 月)。机会之地在哪里?美国代际流动的地理分布,《经济学季刊》,第 129 卷,第 4 期,2014 年 11 月,第 1553-1623 页,https://doi.org/10.1093/qje/qju022
切蒂。新泽西州亨德伦市(2018 年 8 月)。邻里关系对代际流动的影响 I:童年接触效应,《经济学季刊》,第 133 卷,第 3 期,2018 年 8 月,第 1107–1162 页,【https://doi.org/10.1093/qje/qjy007
Chetty,r .,& Hendren,N. (2018 年)。邻里关系对代际流动的影响 II:县级估计。经济学季刊,133(3),1163–1228。
北卡罗来纳州乔利,南卡罗来纳州杜劳夫(2022 年 2 月)。代际流动性(NBER 工作文件第 29760 号)。美国国家经济研究局。【http://www.nber.org/papers/w29760
德国北卡罗来纳州(2018 年)。地点、工作、同伴和青少年时期的重要性:暴露效应和代际流动性。
琼斯,F. L .,小岛康誉,h .,,马克斯,G. (1994)。比较社会流动性:1965-1985 年日本和澳大利亚父子流动性的长期趋势。社会力量,72③,775。https://doi.org/10.2307/2579780
克鲁格,A. B. (2012 年 1 月 12 日)。美国不平等的兴起和后果【论文陈述】。美国华盛顿特区美国进步中心。https://obamawhitehouse . archives . gov/sites/default/files/Krueger _ cap _ speech _ final _ remarks.pdf
麦克莫勒博士,康登,m .,&索希尔,I. V. (1997 年 5 月 1 日)。美国的代际流动。城市学院。从 http://webarchive.urban.org/publications/406796.html取回
马什,R. M. (1968)。美国的职业结构。彼得·m·布劳和奥蒂斯·达德利·邓肯。纽约:约翰·威利父子公司,1967 年。520.
Slomczynski,K. M .,& Krauze,T. K. (1987)。社会流动模式的跨国相似性:对费瑟曼-琼斯-豪泽假说的直接检验。美国社会学评论,52(5),598。https://doi.org/10.2307/2095597
宋,x,梅西,C. G .,罗尔夫,K. A .,费里,J. P .,罗斯鲍姆,J. L .,,谢,Y. (2020)。自 19 世纪 50 年代以来,美国代际流动性的长期下降。美国国家科学院学报,117(1),251–258。
蒂特玛,m .,马图,N. B .,,k .罗斯玛(2003 年)。教育是苏联社会代际流动的一个因素。欧洲社会学评论,19(3),281–297。【http://www.jstor.org/stable/3559612 号
美国的代际流动——分类(5/5)
原文:https://towardsdatascience.com/intergenerational-mobility-in-the-us-classification-5-5-707c6191034a
本系列由 Ibukun Aribilola 和 Valdrin Jonuzi 共同撰写,是社会公益数据科学教程的一部分。
在的第二篇文章中,我们从 Chetty & Hendren 的论文中收集并清理了数据,并将其与来自机会图谱和数据共享的数据结合起来。然后,在第二篇文章中,我们探索了我们可能对数据集执行的潜在分析。
在我们系列的第五篇也是最后一篇文章中,我们将尝试几种分类方法,以确定哪些方法最能让我们了解每个县的哪些特征有助于代际流动(IGM)。为了将这变成一个分类问题,我们将具有正流动性的县分类为1,将负流动性的县分类为0。
代际流动性的因变量和标志是所有父母的子女在 26 岁时国民收入第 75 个百分点的收入变化。
在本文结束时,您将学会如何:
- 对分类问题进行降维
- 选择适当的指标来比较结果
- 在 Python 中执行几种分类方法
- 有意义地解释分类结果
我们将主要使用 Python scikit-learn 库,您可以在这里找到本文的所有代码。点击此处获取文章的互动版本。
降维
主成分分析
我们在前两篇文章中学习了 PCA。PCA 是一种无监督的降维技术,因为它不需要标签,所以它可以应用于回归和分类问题。主成分分析告诉我们什么样的混合变量导致了数据中的一定量的变化。
from sklearn.decomposition import PCA
*# Make a PCA model*
pca = PCA(n_components = 20)
pca.fit(X_train)
在对我们的训练数据运行上述代码之后,我们可以在一个 scree 图中绘制出每个组件所解释的变化量。

图片来自作者。
我们可以看到,主成分 1 ( PC1)占数据变化的 90%以上。太多了!但是什么是PC1。我们可以打印出加载分数来查看:
loading_scores = pd.Series(pca.components_[0], index = X.columns)
sorted_loading_scores = loading_scores.abs().sort_values(ascending=False)top_10 = sorted_loading_scores[:10].index.values
print(loading_scores[top_10])

图片来自作者。
加载分数显示了每个变量与PC1的相关性。这些符号显示了这种相关性是正还是负。比如 2000 年的县人口与PC1有 0.610 的相关性,也就是说人口每增加 1 个单位,IGM 排名就增加 1 个单位。
基于这些结果,我们可以只对PC1进行改造。这意味着我们将把所有的数据点向下投射到一个维度上。如果我们使用PC1和PC2,我们将基于这两个组件提供的轴将数据点投影到二维空间中。这个想法是,将数据转换到这些不同的空间将有助于我们分离类。
然而,我们不是限制主成分的数量,而是迭代地遍历多个成分,以查看哪一个对每种分类方法给出了最好的结果。
线性判别分析(LDA)
类似地,LDA 将我们的数据点带入一个不同的多维空间,帮助我们进行分类。与 PCA 不同,LDA 将类别标签考虑在内,使其成为一种受监督的降维方法。此外,LDA 更侧重于最大化类之间的可分性,而不是确定哪些变量导致数据中的最大变化量。
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
lda = LinearDiscriminantAnalysis().fit(X_train, y_train)*# Transform data*
lda_train =lda.transform(X_train)
lda_test = lda.transform(X_test)
运行代码很简单,但是到底发生了什么呢?LDA 基于我们所有的变量创建了一个轴来转换我们的数据,以优化两件事:
- 最大化两个类别之间的手段分离
- 最大限度地减少每个类别中的差异。
print(lda_train.shape)
>> (1893, 1)
转换的结果是一维数据集。
聚类(高斯混合模型)
在第二篇文章中,我们使用高斯混合模型对数据进行了聚类实验。我们将在此测试移除多数聚类之外的县是否会改善我们的结果。
韵律学
我们如何确定哪个模型比另一个模型表现得更好?准确度、精确度和召回率是最常用的三个分类标准。通常,在这些指标之间进行选择,决定了我们是更关心假阳性、假阴性,还是不特别关心。高精度表示低假阳性,而高召回表示低假阴性。
就我们的目的而言,我们并不特别担心县被误归类为 IGM 阳性或阴性。但是,我们更喜欢我们的模型提供平衡的结果,这意味着,更可取的是,该模型因准确标记两个类别中的县而受到奖励。F 分数确保了精确度和召回率之间的平衡,并且基于以下公式计算:

F 值为 0 意味着精度或召回率为 0,而 F 值为 1 意味着精度和召回率都为 1。在解释结果时,我们还将考虑其他指标,但我们将使用 F 分数作为选择模型参数的主要指标。
我们需要从 scikit-learn 获得以下信息:
from sklearn.metrics import classification_report, f1_score
即使有这些指标,我们如何知道这些结果是否“好”呢?我们需要确定一个成功的基准,我们可以通过将我们的结果与其他进行类似分析或其他启发式方法的人的结果进行比较来选择。例如,也许我们的基准是 50%的准确率,而我们的模型在预测哪些县 IGM 阳性时比无偏的抛硬币更准确。
正如上一篇文章中所讨论的,我们还将把我们的结果分成一个训练集和一个测试集,并进行交叉验证,以使我们的结果偏差最小化。(如果你需要复习,请回到上一篇文章!)
分类方法
这是我们可以应用的可能分类方法的非详尽列表,但我们选择它们是为了展示如何根据数据集的特征选择模型。在这一小节中,我们将提供对这些方法如何工作、所需参数的高级理解,并根据我们对数据的理解评估我们期望这些方法的工作效果。
记住,运行代码进行分类并不难。挑战来自于对正在发生的事情有足够的理解来恰当地解释结果!
逻辑回归
在上一篇文章中,我们介绍了几个处理未标记的连续数据的回归模型。在线性回归中,我们的目标是预测一个特定的值,而逻辑回归预测一个二元类。
如果我们只处理一个独立变量,我们将基于对数标度转换数据,因此 y 轴现在表示数据被分类为0或1的可能性。然后,我们拟合一个逻辑函数(S 形线)。数据点的分类基于其在逻辑函数上的概率。一般如果概率超过 50%,那么我们就认为是那一类。我们调整了基于最大似然的逻辑函数。最大似然是通过在给定类别中标记的所有数据点的联合概率来计算的。
我们这里只有一个变量,但当我们添加更多的变量并将逻辑函数移动到多维空间时,情况是一样的。
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression(C= c)
Rdige 分类
岭分类器背后的想法是向逻辑回归引入一些偏差以减少方差,这意味着我们故意使训练结果不太准确,以便我们在测试数据中获得更好的结果。
逻辑回归旨在最大化可能性之和,即属于某个类别的数据点与其实际所属类别之间的置信度差异。如果一个县出现阳性 IGM 的可能性为 0.9,并且其实际类别为“1”,则可能性为 0.9。然而,如果该县的实际分类为“0”,那么其可能性为 0.1,因为它被正确分类的可能性较小。岭分类为这种优化增加了一个惩罚项。
- 对数回归→最大值(可能性之和)
- 岭分类→最大化(可能性之和+$ \ n 乘以变量**2$)
λ 控制我们引入的偏差量,我们可以通过惩罚自变量对因变量的影响来调整它作为一个参数。更高的 λ 意味着逻辑函数的“S”形不太陡。在 scikit-learn 中,我们称这个参数为alpha。
from sklearn.linear_model import RidgeClassifier
clf = RidgeClassifier(alpha=alpha)
至少与逻辑回归相比,这种方法可以改善我们的测试结果。
k-最近邻(KNN)
KNN 是关于投票的!假设我们的数据集如下图所示,我们有一个未赋值的数据点。如果我们将k设置为 5,那么我们将采用 5 个最近邻居的投票来决定它应该属于哪个类。有 3 张绿色票和 2 张蓝色票,因此多数票获胜,该点被归类为绿色。

图片来自作者。
灰色节点从它的 5 个最接近的邻居那里获得投票。
我们将根据交叉验证后给出最佳 F 值的那个来设置k。
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=k)
我们的数据越分离,KNN 的结果就越好。我们将看看我们的降维技术是否能增加可分性并改善结果!
一个与 KNN 相关的方法是半径最近邻法(RNN)。对于 RNN,我们不是通过确定最近邻的数量来估计属于某个组的可能性,而是从感兴趣的点(我们考虑的数据点)确定半径。RNN 的一个问题是,如果你对变量不够熟悉,可能很难估计一个合适的半径。
对于 KNN 和 RNN,我们需要考虑的最后一点是权衡邻国。虽然在大多数情况下使用统一加权,但也可以使用基于距离的加权,最近的数据点对结果的影响更大。
随机森林
随机森林是更一般化的决策树。这里有一个决策树的例子:

图片来自作者。
这是一个分类树,因为它的叶节点代表二进制值,而回归树的叶节点代表连续值。
我们可能构建的任何单一决策树都会因用于构建它的数据而异。这导致任何结果都有很大的偏差。随机森林通过向构建的决策树类型添加更多变化来解决这个问题。
首先,引导数据集进行自我测试。这里的自举意味着算法通过替换从自身随机采样,直到它具有设定数量的数据点。这导致了树与树之间的差异!
第二,每棵树都是从x变量的随机子集而不是所有变量构建的。然后每棵树都变得明显不同。该树分割每个节点,直到叶子以设定的最小样本数结束。
第三,数据通过每棵树传递,并且所有树的分数对于森林(创建的所有树)是聚合的。选择获得最高投票的标签。
最后,我们使用外袋数据集(即所有不包括在引导数据集中的数据点)来测试随机森林。然后,我们可以回过头来改变x,直到我们实现一个具有最佳精度的随机森林。
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(class_weight = "balanced_subsample", max_depth = k)
max_depth决定我们允许树在停止之前分裂它的节点到什么程度。我们在这里限制它是为了便于计算,也是为了防止树变得太具体而产生偏差。设置class_weight = “balanced_subsample”意味着每个标签的权重是基于标签在每个引导数据集中的比例而不是整个数据集中的比例来计算的。这导致更精确的树。
随机森林的优势在于,与 KNN 和逻辑回归不同,它不依赖于类的可分性。随机森林假设数据被一种极其复杂的模式所分隔,这种模式无法通过扭曲维度来解决。根据我们探索的数据,这种方法似乎会更好。
高斯朴素贝叶斯(GNB)
还记得我们在探索性数据分析文章中创建了这个图吗?

图片来自作者。
我们将数据集一分为二—一个是 IGM 阳性的县,另一个是 IGM 阴性的县。然后,我们通过绘制它们的高斯分布来比较它们的变量分布。GNB 使用这些分布来计算一个县属于每个类的可能性。它比较具有一组要素的未分类县的联合概率,并根据哪个概率更高来标注它。
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
除了先验概率(我们对每个类的每个变量的分布情况的最佳猜测)之外,没有其他参数需要设置。我们不需要设置任何东西,因为当我们有足够大的训练时,先验并不重要。
我们可能认为这个方法会失败,因为这两个类的分布没有什么不同,但是结果会让你吃惊。
结果
这些结果都是基于对测试数据集的预测。

图片来自作者。
我们在表格中突出显示了三个最高(绿色)和最低(红色)的 F 值。请注意,即使高斯朴素贝叶斯的结果可以进入前三名,如果我们基于准确性来衡量标准,他们的结果对于一个类来说会比另一个类更准确。这意味着该模型在预测具有负或正代际流动性的县时会比另一个更好。相反,考虑到我们分析的需要,F 值给了我们一个更平衡的画面。
总的来说,我们的结果告诉我们,移除少数聚类、执行 PCA 并通过高斯朴素贝叶斯模型运行数据会给我们最好的结果。对未改变的数据集执行岭分类给出了第二好的结果,执行 LDA 和随机森林给出了第三好的结果。没有一种降维或分类方法比另一种方法表现得更好。
最终,我们期望所有这些方法的表现和掷硬币一样好。这并不意味着厄运,因为我们的结果可能会更糟,这只是意味着我们需要考虑其他方法来转换或分割数据,以获得更好的结果!
结论
在本文中,我们讨论了降维以及一些最流行的分类方法。我们了解到:
- 用于比较回归方法的度量取决于项目的具体情况和您试图避免的错误类型
- 执行降维几乎一致地改善了结果
- 尽管 IGM 阳性和 IGM 阴性的县之间变量的分布并不明显,但高斯朴素贝叶斯方法产生了不错的结果
在进行回归分析之前,我们使用 PDA 和 LDA 通过降低数据的维度来捕捉数据的本质。我们注意到,PDA 的目标是最大化它所能解释的数据的变化,而 LDA 的目标是首先识别导致这种变化的变量。
我们看到了带有逻辑回归的 sigmoid 函数如何允许我们对二元预测变量使用概率模型。另一方面,在尝试随机森林分类器时,我们使用了 bootstrapping 来创建几个决策树,以最大化我们的测试准确性
像分类一样,回归是一种有用的方法,可以应用于给定一组预测因子来预测分类变量。不同的方法都有各自的优缺点,但是通过实验,你可以找到对你的研究产生最佳结果的方法。本文总结了我们通过数据科学分析代际流动的系列文章。我们希望你喜欢它们!
美国的代际流动——数据预处理(2/5)
本系列由 Ibukun Aribilola 和 Valdrin Jonuzi 共同撰写,是社会公益数据科学教程的一部分。
在跨越了数据收集的障碍之后,我们得到了一系列不同形式的数据,这些数据的原始形式不适合我们的预期分析。为了解决这个问题,我们将合并数据集并解决丢失数据的问题。因为我们所有的数据集都有县级数据,所以我们将根据县 id 进行合并。
合并单个数据集
这个项目的数据合并可以分为三个阶段。我们从合并单个机会图谱数据集开始,以创建一个大的机会图谱数据集。然后,我们将 Opportunity Atlas 数据集与 Data Commons 数据集合并,后者又与 Chetty & Hendren 数据集合并。
合并机会图谱数据集
在“收集数据”部分,我们描述了如何从机会地图集下载七个特征的县和区域级数据。正如在“数据预处理”一节的介绍中提到的,我们将只使用县级数据,因此我们将在这一节中介绍七个县级数据集的合并。
首先,我们需要导入所有必要的库,然后我们将各个数据集从 Google Drive 加载到 Python 笔记本中。
import pandas as pd
import functools*# County-level data*
*## Household income at age 35*
c_household_income = pd.read_csv('https://drive.google.com/uc?id=1dRLKhKqBjs2ARcynSUT99Y8CoJPoC5IZ')
*## Incerceration rate*
c_jail_rate = pd.read_csv('https://drive.google.com/uc?id=14K-QJ_bZi8Dtvod9J_s72tLE8TpWf25T')
*## Fraction married at age 35*
c_married = pd.read_csv('https://drive.google.com/uc?id=1MY_ulJnGFSBotgDtbf4dSYRR_SqW5rNq')
*## Employment rate at age 35*
c_working = pd.read_csv('https://drive.google.com/uc?id=1sZxD3OqA0IqbiDn1L22HXHfFE3J_Q6yX')
*## High school graduation rate*
c_hs = pd.read_csv('https://drive.google.com/uc?id=1cOFWFjPNyYn-dBdBH5s8PZ7V8B1ZNpC9')
*## College graduation rate*
c_coll = pd.read_csv('https://drive.google.com/uc?id=1CquuVRG_c-7wgW6P7yjXL6uUxt3VBb2v')
*## Poverty rate in 2012-16*
c_poor = pd.read_csv('https://drive.google.com/uc?id=166vtYSczyKAHMXP1qbjaMZBNIZlO3DjB')
当我们使用df.shape命令检查数据集的形状时,我们发现它们有 218 行,其中两行是县 ID 和名称。剩余的 216 个是 6 个父母收入百分位数水平、6 个种族亚组、3 个儿童性别亚组和 2 个儿童群组亚组的组合(6×6×3×2 = 216)。
我们还注意到,“c_poor”数据集中的“county name”列是以标题“name”的形式书写的,而其他数据集中的列名是以小写“Name”标记的。因为 Python 是一种区分大小写的语言,所以我们希望确保所有数据集中的 county name 列的标签都是相似的,所以我们只需更改列名。
c_poor = c_poor.rename(columns = {"Name": "name"})
现在,所有的数据集都可以使用 Pandas 的“合并”功能进行合并了。我们将合并县名和 ID 上的数据集,以便大型数据集将包含唯一的县名和 ID(没有重复)。
*# Make a list of all the datasets that need to be merged*
c_datasets = [c_household_income,
c_jail_rate,
c_married,
c_working,
c_hs,
c_coll,
c_poor]*# Batch merging the datasets into one*
oa_county_dataset = functools.reduce(lambda left, right:
pd.merge(left, right, on = ["cty", "name"], how = "outer"), c_datasets)
现在可以将合并的数据集导出到您选择的文件夹中。(点击此处查看完整的牌桌。)
合并后的数据集包含多达 1300 列,这意味着可能有太多我们不需要的变量。快速检查显示,大多数列是不同收入百分位数的居民的数据条目和码本中未描述的“晚期群体”的数据条目。因为我们想要删除的变量比保留的要多,所以我们可以列出想要保留的变量。对于这个项目,我们感兴趣的是整个样本、种族和性别分组的收入、结婚率、就业率、高中毕业率、大学毕业率和贫困率。
oa_data.info()
*# the output of the cell shows that there are 1300 columns*
我们使用df.columns.difference命令来选择 opportunity atlas 数据集的所有列,通过列出我们想要保留的列来删除这些列。因此,如果你想选择一组不同的变量来进行分析,在这一阶段必须将它们列出来。然后,我们使用df.drop命令删除那些不需要的变量。
数据共享空间+机会图谱
在为 Opportunity Atlas 数据创建单个数据集并对其进行修整以仅包含我们进行分析所需的变量之后,我们将把它与 Data Commons 数据合并。两个数据集都有一个专用于县 ID 的列,即 Opportunity Atlas 数据集中的cty列和 Data Commons 数据集中的“地点”列。我们希望将跨数据集的每个县的属性合并到一个数据集,因此我们将基于县 id 进行合并。为此,两个县 ID 列必须采用相同的格式。目前,县 Id 以不同的格式书写,因为它们以不同的字母为前缀,即“ctyxxxxx”和“geoId/xxxxx”。我们可以通过分割每个字符串并删除前缀来确保县 id 具有相同的格式。我们还将每个数据集的县 ID 变量重命名为“geo_id ”,以确保变量名称在数据集之间保持一致。
*# slice the elements of the 'cty' and 'place' columns in oa_data and dc_data, respectively*
*## the goal is to isolate the geo IDs of each county*
cty_new = []
place_new = []
for i in oa_data['cty']:
cty_new.append(i[3:])for j in dc_data['place']:
place_new.append(j[6:])*# replace the geo ID columns without surrounding text*
oa_data['cty'] = cty_new
dc_data['place'] = place_new
oa_data = oa_data.rename(columns={"cty":"geo_id"})
dc_data = dc_data.rename(columns={"place":"geo_id"})
有了一致的县 ID 格式,我们就可以使用pd.merge命令合并 Opportunity Atlas 和 Data commons 数据集了。我们将对geo_id列进行外部合并,合并两个数据集中的县 id,这样两个数据集中表示的每个县在合并的数据集中只出现一次。查看 pd.merge 文档以了解合并数据集的不同方法。
*# merge the datasets*
merged_data = pd.merge(oa_data, dc_data, how="outer", on=["geo_id", "geo_id"])
merged_data = merged_data.drop(columns="name_y")
merged_data = merged_data.rename(columns = {"name_x": "name"})
merged_data.geo_id = merged_data.geo_id.astype(str)
merged_data
(点击此处查看完整表格。)
Chetty & Hendren +数据共享+机会图谱
合并过程的最后一步是将 Chetty & Hendren 的数据集与之前合并的数据共享空间和机会地图集数据集合并。为了做到这一点,我们进一步削减了每个数据集中的变量列表。例如,我们放弃了所有与永久居民代际流动相关的变量,因为我们对永久居民和流动者之间的区别不感兴趣。
IGM = pd.read_stata("../data/raw/online_table4.dta")
df = pd.read_csv("../data/processed/merged_df.csv")*# Remove permanent resident intergenerational mobility from Chetty's 2014*
*# because we aren't interested in the permanent residents vs movers distinction*
perm_res_IGM = list(filter(re.compile("perm_res_.*").match, IGM.columns))
IGM = IGM.drop(columns=["csa", "csa_name", "cbsa", "cbsa_name", "intersects_msa"]+ perm_res_IGM)
我们还从合并的(数据共享+机会地图集)数据集中删除了一些变量,如所有种族和性别子群的县数据。
最后一个预合并步骤是确保县 ID 列在数据集之间的命名一致。如果列名不一致,我们只需重命名其中一个以匹配另一个。
现在,数据集已准备好进行合并和导出。
df_subset.geo_id = df_subset.geo_id.astype(int)
df_subset.rename(columns={"geo_id":"cty2000"}, inplace=True)merged_data = pd.merge(IGM, df_subset, on=["cty2000", "cty2000"])
merged_data = merged_data.drop(columns=['name'])merged_data
(点击此处查看完整表格。)
电报密码本
这是一条漫长的道路,但我们最终获得了一个全面的数据集,并很高兴开始分析。然而,我们必须创建一个数据码本,定义数据集中的每个变量,并使浏览数据集的任何人都容易理解每个变量的含义。
当构造码本时,有不同的格式可供选择。例如, Chetty 混合使用了第 1-3 页的列表式变量描述和第 4-5 页的表格格式。这里的是我们在这个分析中使用的数据集的码本。
因变量选择
当前的数据集包含一个结果变量的洗衣清单,所有这些我们都不一定感兴趣。因此,我们去掉了所有变量,只有一个除外——搬到一个县对 26 岁时父母收入排在第 75 百分位的孩子的收入排名的因果影响(“因果 _p75_cty_kr26”)。虽然这是我们分析感兴趣的结果变量,但还有其他有趣的结果变量值得探索,所以不要犹豫,进一步探索数据吧!
IGM_df = pd.read_pickle("../data/processed/IGM_merged_df.pkl")*# Drop irrelevant columns*
other_causal = list(filter(re.compile("causal_.*").match, IGM_df.columns))
perm_res = list(filter(re.compile("perm_.*").match, IGM_df.columns))
df = IGM_df.drop(columns=other_causal+perm_res+ ['cty1990', 'cz_name', 'cz_pop2000', 'csa', 'csa_name', 'stateabbrv',
'cbsa', 'cbsa_name', 'intersects_msa'])*# Select the outcome variable*
pred = "causal_p75_cty_kr26"pred_df = df.copy()
pred_df.insert(0, pred, IGM_df[pred])
另一个数据预处理最佳实践是指定丢失数据问题的处理方式。在该数据集中,我们以分层格式处理缺失值。如果缺少与某个县相关的特定值,我们将输入通勤区域中值。如果通勤区中间值不存在,我们插入州中间值,如果州中间值为空,我们输入国家中间值。
df_cz_median = df.groupby("cz").median()
df_state_median = df.groupby("state_id").median()
df_country_median = df.median()*# Impute by commuting zone median*
for col in df.columns[np.where(df.isna().sum()>0)]:
df[col] = df.apply(
lambda row: df_cz_median.loc[row['cz']][col] if np.isnan(row[col]) else row[col],
axis=1
)*# Impute by state median*
for col in df.columns[np.where(df.isna().sum()>0)]:
df[col] = df.apply(
lambda row: df_state_median.loc[row['state_id']][col] if np.isnan(row[col]) else row[col],
axis=1
)*# Impute with country median*
for col in df.columns[np.where(df.isna().sum()>0)]:
df[col] = df.apply(
lambda row: df_country_median[col] if np.isnan(row[col]) else row[col],
axis=1
)
替换 NA 值后,3221 行中仍有 767 行缺少结果变量值,因此我们将它们与其他不必要的列一起删除,并导出清理后的数据集。
结论
从变量选择到数据清理,许多必要的工作都是为了准备用于数据分析的数据集,每个步骤都必须仔细完成并记录在案,以便于与受众顺利沟通。在下一篇文章中,我们将解释如何执行探索性数据分析来理解数据,并将其用作对数据进行头脑风暴分析的工具。
美国的代际流动——探索性数据分析(3/5)
本系列由 Ibukun Aribilola 和 Valdrin Jonuzi 共同撰写,是社会公益数据科学教程的一部分。
本文是预测美国代际流动系列文章的第三部分。在第一篇和第二篇文章中,我们从 Chetty & Hendren 的论文中收集并清理了数据,并将其与来自机会图谱和谷歌数据共享的数据结合起来。
本文的目标是执行探索性数据分析(EDA)以了解我们拥有的数据,并开始探索我们可能执行的分析类型,以预测有助于美国代际流动的县的特征。
代际流动性的因变量和标志是所有父母的子女在 26 岁时国民收入第 75 个百分点的收入变化。
在本文结束时,您将学会如何:
- 深入了解数据集
- 选择用于分析的变量
- 确定要执行的可能分析
- 在 Python 中执行 EDA
你可以在这个 Jupyter 笔记本中试用本教程的所有代码,并在这里找到文章的交互版本。
理解变量
对于每个变量,我们想知道它的分布、类型、取值范围、潜在的异常值等。我们可以使用 Pandas Profiling 在报告中生成汇总统计数据,而不是对每条信息的所有变量进行循环。你可以点击查看完整报告。
报告显示:
- 没有空值。
- 因变量比负值稍微偏向正值。
- 因变量“causal_p75_cty_kr26”的平均值为 0.024,标准偏差为 2.859(意味着 68%的数据与平均值相差如此之远)

我更强调因变量,因为我们的结果是基于可用值的变化来解释的。(图片来自作者)
- 所有的自变量都是数值型的,不需要转换。
- 所有的自变量都是连续的,所以我们可以一视同仁地对待它们,而不需要处理分类变量。
- 每列中的所有值都属于同一类型。
- 作为行 ID 的 county code 列是唯一的。
- 自变量的分布大多是偏斜的。
映射变量
由于我们也在处理地理空间数据,我们可以使用 Plotly 来创建我们数据的交互式地图。Plotly 有一个 geojson 文件,其中包含绘制每个县的形状的坐标。
绘制我们的数据显示,3,006 个县中有 2,319 个(77.15%)缺少数据,特别是在美国中西部。根据 Chetty 和 Hendren 的第一篇论文,他们“将样本限制在人口超过 250,000 的 CZs,以最小化采样误差”。他们还讨论说,他们已经调整了人口规模的临界值,以确保这个规模将导致最稳健的结果。因此,我们不能用这些数据填充某些县,因为通勤区数据被删除了。

去这里看不同变量的分布。(图片来自作者)
变量选择
当处理许多变量时,我们希望选择最终用于分析的变量,因为太多的变量可能会产生噪声。此外,如果两个变量高度相关,它们的数据将在一个方向上对最终结果施加更大的拉力,即使它们代表相同的东西。
主成分分析
PCA 尝试使用多维空间中的组件(在这种情况下是每个县的特征)将县分成组,以确定哪一个最能描述数据的分离。对于每个组件,它在空间中绘制一条线,使方差最大化,这意味着数据点的总距离将尽可能远离原点。
下面的 scree 图显示了每个部分在多大程度上有助于解释各组县之间的差异。第一个成分占方差的 90%以上,考虑到我们有超过 100 个成分,这是很重要的。这种维数的减少使得分类更快,并且减少了可能来自如此多的组件的噪声。

(图片来自作者)
不幸的是,我们无法想象县与县之间的区别,因为我们无法在三维之外绘图。但是这两个方差最大的部分的图应该有助于说明数据可能是什么样子。颜色代表该县是具有正的代际流动性“1”还是负的代际流动性“0”。正如我们所见,表现出代际流动的县和没有表现出代际流动的县之间几乎没有什么区别。

(图片来自作者)
然而,当我们不知道这些县是如何被分开的时候,盲目依赖这些结果是危险的。PCA 执行的最佳分离对于我们试图解决的问题可能不是最佳的。我们可以将这种技术与后向消除结合起来,后向消除更有意识地确定要删除哪些值。
反向消除
注意:代码在回归笔记本里。
另一种常见的特征选择方法是向后排除法,这种方法包括从潜在预测值的完整列表开始,根据选择的标准测试每个变量的性能,并删除变量,直到模型停止改进。我们将使用 Python 模块 statsmodels 来运行普通最小二乘(OLS)回归模型,用于我们的反向消除过程。这些步骤改编自 Jatin Grover 的文章。查看本笔记本中的完整反向消除代码。
向后消除涉及四个步骤,下面将对它们进行概述,并在接下来的几个小节中进行解释。
- 确定剔除标准,如变量显著性水平 p=0.05
- 用所有 125 个候选变量或预测值拟合模型
- 删除 p 值最高的变量
- 重复步骤 3,直到满足停止标准
步骤 1 —确定淘汰标准
由于我们使用 OLS 回归,我们得到了分析中每个变量的显著性水平。因此,我们将使用标准的 5%置信水平。目标是选择 p 值为 0.05 或更小的变量。
步骤 2-用所有变量拟合模型
反向消除过程的下一步是用所有 125 个潜在预测变量拟合回归模型。

“summary”命令的输出是一个结果表,包括我们感兴趣的可变系数及其相应的 p 值。

步骤 3 和 4 —删除 p 值最高的变量,直到终止
接下来,我们找到具有最高 p 值的变量,并将其从我们的分析中删除。我们重复这个过程,直到我们满足我们的停止标准。如果这三个陈述中的任何一个为假,我们就终止反向消除过程:至少有 10 个预测值,所有预测值的显著性水平都高于阈值,所有预测值的显著性水平都低于阈值。
请注意,10 是一个任意的数字,它仅仅意味着我们对具有最高统计显著性的 10 个代际流动性预测指标感兴趣。这种方法的妙处在于它的灵活性。您可以选择以更少或更多的变量结束,或者甚至选择完全不同的终止标准。例如,当 r 平方或均方根误差停止改善时,您可以停止模型。下面的代码单元格显示了一个函数,该函数完成了反向消除过程,并返回最终的 OLS 模型和所选最终变量的数据框架。

当我们打印' backwards _ elimination '函数的输出时,我们得到如下结果。请注意,这些变量在 5%的水平上都是显著的,但是 r 平方值从 0.078 下降到 0.029。我们将在回归文章[链接]中讨论 r 平方值作为回归模型的性能度量的重要性。

执行反向消除后的结果摘要。(图片来自作者)
分类问题
到目前为止,我们已经把这个问题作为一种方式来看待,以找出哪些特征有助于告知代际流动的确切数量。我们可以用另一种方式来看待这个观点,这种方式有助于我们用一种有趣的方式来重建我们的问题。理想情况下,我们希望根据某些特征清楚地分辨出哪些县显示出代际流动性。
不幸的是,真实世界的数据很少如此清晰,你会经常看到混合。我们的数据中的变量不能区分县,但如果我们增加更多的维度,并改变我们描绘县的方式,如下所示,我们可能会找到一种可行的方法。

(图片来自作者)
我们可以试着找出这两个不同等级的县在他们的分组中有什么显著的共同特征,而不是寻找一种方法来找出所经历的流动性的确切数量。
- 将因变量转换为二进制数,其中“1”表示正等级变化,而“0”表示没有或负等级变化。
- 比较其要素的分布,查看等级变化为正或负的县的要素之间是否有任何明显的差异。
下图显示了两个标签之间分布间隔最大的三个变量。不幸的是,没有一个单一的变量能显示一个县有无代际流动的明显差异。也许这些差异的总和会给我们指出一个有趣的方向。

转到这里来试验其他变量。(图片来自作者)
使聚集
也许这些县在独立于代际流动的方式上是相似的。我们可以试着把相似的县放在一起,看看每个组内是否有更多的差异。有多种方法来执行聚类,我们需要选择最适合我们数据的方法。
我们将在这里试验两种流行的聚类方法:DBSCAN 和高斯混合模型。
基于密度的噪声应用空间聚类
该方法在根据各县的特征将各县带入多维空间后,根据它们彼此之间的距离对它们进行聚类。让我解释一下算法在二维空间中是如何工作的:
- 基于两个参数选择核心点-最小邻居数量和邻居之间的最大距离

(图片来自作者)
- 随机选择一个核心点,并将其接触的所有点分组。作为集群一部分的其他核心点可以扩展集群,但是非核心点只能成为成员而不能扩展集群。一旦没有其他点可以添加,随机选择一个核心点不分组,同样操作。

(图片来自作者)
- 瞧啊。我们有我们的集群。
代码运行起来很简单,其中“eps”是指邻居之间的最大距离,“min_samples”是邻居的最小数量。

不幸的是,即使调整了参数,我们的聚类结果也没有产生任何重要的聚类。
高斯混合模型
GMM 假设数据集是由一定数量的高斯混合而成的。因此,根据这一假设,我们应该能够确定这些高斯分布是什么,并根据创建每个点的高斯分布对它们进行聚类。该算法适合几个不同的高斯,直到它找到最大化每个高斯之间的距离和最小化每个高斯内的方差。

图片来自 Oscar Contreras Carrasco 。
代码看起来像这样,其中“n_components”是我们对存在的高斯数的猜测。

在尝试增加“n_components”后,我们的结果仅显示两个集群。

图片来自作者。
群组 1 有 2330 个县,群组 2 有 37 个县。这些分类的标准差相差很大,而分类 2 的可变性要大得多,这表明它可能包含数据集的所有异常值。
当我们尝试分析时,我们可以看到删除这 37 个县是否会导致更好的结果!
结论
在本文中,我们已经了解了如何使用 Pandas Profiling 和地理空间映射从宏观层面理解数据。这些见解表明:
- 我们不需要执行任何其他数据清理或转换
- 我们只有 3006 个县中的 2319 个县(77.15%)的数据
- 如果我们进行回归,均方误差大于数据的标准偏差(2.859)会显示不稳定的结果,因为平均而言,超过 68%的数据会包含在该误差中。
我们还看到了两种可以通过 PCA 修整数据以获得更好结果的方法,即向后消除和聚类。我们的 PCA 初步结果表明,我们可能只需要少量的转换来预测代际流动性。在使用高斯混合模型进行聚类时,我们确定了 37 个县,我们可能会尝试删除这些县,以查看它是否能为聚类 1 创建更好的预测。我们将使用 PCA 和聚类作为分类方法的变量选择技术和回归方法的反向消除技术。
最后,我们看到了分析数据的两种方法——回归和分类。基于变量在两个类之间的分布,分类看起来并不乐观,但是当我们把所有的变量放在一起时,可能会有一个组合效应,所以我们将尝试一下。
探索性数据分析是关于创造性地思考问题,所以如果你能想到不同的方法,我们可能会转换,修剪,集群等。数据,那就去争取吧!在接下来的两篇文章中,我们将讨论回归和分类技术。
美国的代际流动——回归(4/5)
原文:https://towardsdatascience.com/intergenerational-mobility-in-the-us-regression-85b25499e6e6
本系列由 Ibukun Aribilola 和 Valdrin Jonuzi 共同撰写,是社会公益数据科学教程的一部分。
在上一篇文章中,我们使用 Pandas Profiling 和地理空间制图来获得对数据集的整体理解。我们还研究了如何使用主成分分析和反向消除来缩减变量列表,以提高分析性能。
在本文中,我们将讨论交叉验证以及为什么要这样做,扩展本系列第三篇文章中的普通最小二乘(OLS)回归模型,并使用 XGBoost 运行梯度推进回归模型。最后,我们将使用均方根误差和 r 平方等指标来讨论这些模型的性能。本节使用的代码的完整笔记本可以在这里找到。
交叉验证
在设计预测模型时,我们需要采取多种措施来确保模型的稳健性。换句话说,模型在外部数据集上产生的结果应该与在训练数据集上产生的结果相似。交叉验证是一种帮助我们建立稳健模型的程序,因为它涉及对数据集进行重新采样,根据数据子集训练模型,并根据不同的数据子集测试模型,以查看它与测试数据集的吻合程度。它也被称为 k 倍交叉验证,k 指的是数据被分成的子集的数量。
在此分析中,我们通过将数据集随机分为两组(训练集和测试集)来进行双重交叉验证。我们使用来自 sklearn 的train_test_split函数来随机拆分数据集,其中三分之二在训练集中,三分之一在测试集中。
from sklearn.model_selection import train_test_split
*# load data*
df = pd.read_csv("../../data/processed/processed_data.csv")*# start with all 125 predictors in the dataset*
predictors = df[df.columns[6:]]
*#pull out the outcome variable column*
outcome = df['causal_p75_cty_kr26']*#cross-validation, train-test split*
pred_train, pred_test, outcome_train, outcome_test = train_test_split(
predictors, outcome, test_size=1/3, random_state=10)
OLS 回归
让我们回顾一下上一篇文章。逆向选择过程留给我们 10 个变量。下图显示了反向选择过程的输出,这是一个包含这 10 个变量的 OLS 回归模型。

图片来自作者。
这个模型的(差)表现将在下一小节讨论,所以让我们集中解释变量对代际流动性的影响大小。一些影响最大的变量是frac_5、frac_9和num_inst_pc。换句话说,在国民收入分配的第五和第九个十分位数中,父母每增加一个分数,代际流动性就分别增加 24.4 和 15.7。另一方面,根据这个效应大小为-12.9 的模型,人均大学数量对代际流动有负面影响。
韵律学
你可能会发现自己想知道这个模型的结果意味着什么,或者如何判断这个模型是否“好”。有几个指标可以帮助我们确定我们的回归模型是否能很好地预测结果变量。在本文中,我们将关注其中的两个指标:均方根误差(RMSE)和 r 平方。
均方根误差
RMSE 是通过计算模型的预测结果值和实际结果值之差的平方根得到的一个指标。目标是使 RMSE 为零或尽可能接近零。Python 的“statsmodels”库有一个 rmse 特性,可以自动计算这个值。在下面的代码单元格中,我们用我们开始的所有 125 个变量计算 OLS 模型的 RMSE(向后选择的第 2 步)。这个模型的 RMSE 是 2.44。
import statsmodels.api as sm*# Run regression model on all variables*
OLS_mod = sm.OLS(outcome_train, pred_train).fit()
y_pred_init = OLS_mod.predict(pred_test)
rmse_init = sm.tools.eval_measures.rmse(y_pred_init, outcome_test)
print("RMSE:", rmse_init)
OLS_mod.summary()
下面的代码单元格显示了我们如何计算有 10 个变量的后向选择模型的 RMSE。这个降维模型的 RMSE 是 2.28,与第一个 OLS 模型相比下降了 6.5%,这是一个改进。
p = 0.05
back_elim = backwards_elimination(outcome_train, pred_train, p)
ols = back_elim[0]
features = back_elim[1]
*# use model to predict outcome variable*
y_pred = ols.predict(pred_test[features.columns])
*# calculate rmse*
rmse = sm.tools.eval_measures.rmse(y_pred, outcome_test)
print("RMSE: %.3f,
Features: %s" % (rmse, features.columns.values), "
Model summary:
", ols.summary())
r 平方
r 平方值是一个度量标准,它描述了输入变量在结果变量中所占的百分比。在包含所有 125 个输入变量的第一个 OLS 模型中,r 平方值为 0.078 或 7.8%,这仅仅意味着还有其他代际流动性的预测因素没有包括在我们的数据中。第二个模型有 10 个输入变量,其 r 平方值为 0.029 或 2.9%,这是有意义的,因为我们已经放弃了许多可能在某种程度上预测代际流动的变量。
我们还可以在预测值与实际值的曲线图上直观显示两个模型的拟合情况。该图传递了与 RMSE 相同的信息,即预测值和实际值之间的差异。拟合良好的模型将显示位于 45 度线附近的分散点,表明它们高度相关。后向消除模型比第一个模型具有更好的拟合,如第三个图中其直线的陡度所突出显示的。

图片来自作者。
总之,两个 OLS 回归模型都不是很稳健,因为模型中没有包括某些变量,这些变量解释了 90%以上的代际流动性差异。但是,后淘汰模型有更好的拟合。
XGBoost 回归
第二个回归模型使用梯度提升树算法来预测结果变量。我们使用 Python 中的 XGBoost 模块运行回归模型并进行特征选择,即减少模型中包含的变量数量。让我们从将这个分析所需的包导入 Python 开始。
*# load relevant packages*
from sklearn.feature_selection import SelectFromModel
from sklearn import metrics
from sklearn.compose import ColumnTransformer
from xgboost import plot_importance
from xgboost import XGBRegressor
在高层次上,XGBoost 中的特性选择非常类似于本系列第三篇文章中概述的 OLS 特性选择步骤,主要区别在于变量选择标准。此分析的选择标准是特征重要性分数 f-score,也称为“增益”。增益衡量变量对其所添加的分支带来的精度提高。在这篇文章中阅读更多关于特性重要性分数的信息。下面的代码单元格显示了如何用所有 125 个输入变量拟合一个回归模型,并生成一个重要性图。
*# fit model with training data*
model = XGBRegressor()
model.fit(pred_train, outcome_train)
*# feature importance*
print(model.feature_importances_)
*# plot importance*
fig, ax = plt.subplots(figsize=(10, 35))
plot_importance(model, ax=ax, grid=False, importance_type="gain")
plt.show()
下面是重要性图的一个片段。它以降序显示每个变量及其重要性分数,最“重要”的变量位于顶部。

图片来自作者。
我们可以使用 RMSE 评估该模型的拟合度,代码如下所示。
*# run training model on the test dataset*
y_pred = model.predict(pred_test)
*# evaluate the model using the mean squared error*
rmse = np.sqrt(metrics.mean_squared_error(outcome_test, y_pred))
print(" Root Mean squared error:", rmse)
特征选择是通过按重要性分数的升序对变量进行排序,并使用具有相同或更高特征重要性分数的变量运行回归分析来完成的。在这种情况下,重要性分数是阈值或变量选择标准。对于每个模型,我们计算 RMSE,以便我们可以检查每个模型的性能,然后选择一个最适合我们的模型。下面的代码单元显示了我们如何完成这个特性选择过程。
thresholds = np.sort(model.feature_importances_)
for thresh in thresholds:
*# Select features using threshold=importance. Only variables with equal or higher importance will be selected*
selection = SelectFromModel(model, threshold=thresh, prefit=True) *# Get indices of selected variables and find the corresponding column names. Source: Liang (https://stackoverflow.com/a/42527851)*
feature_idx = selection.get_support()
feature_name = pred_train.columns[feature_idx] *# Subset the predictor dataframe with the selected features*
select_X_train = selection.transform(pred_train) *# Train model*
selection_model = XGBRegressor()
selection_model.fit(select_X_train, outcome_train) *# Evaluate model*
select_X_test = selection.transform(pred_test)
y_pred = selection_model.predict(select_X_test)
rmse = np.sqrt(metrics.mean_squared_error(outcome_test, y_pred)) *# Only print out the list of features when we have 10 or less features to save space*
if select_X_train.shape[1]<=10:
print("Thresh=%.3f, n=%d, RMSE: %.3f, Features: %s" % (thresh, select_X_train.shape[1], rmse, feature_name))
else:
print("Thresh=%.3f, n=%d, RMSE: %.3f" % (thresh, select_X_train.shape[1], rmse))
这个代码单元的结果是 125 行文本,每一行都说明了重要性分数阈值、满足阈值的变量数量以及模型的 RMSE。

图片来自作者。
RMSE 最低(2.680)的模型以本科院校人数为输入变量。第二好的模型是 RMSE 模式(2.684),使用本科院校的人数和人均院校数量作为输入变量。
虽然 XGBoost 模型产生的模型的 RMSEs 没有 OLS 回归模型小,但 XGBoost 仍然是一个很好的工具,因为它产生的预测模型通常表现更好。
XGBoost 部分的代码改编自 Jason Brownlee 的文章。
结论
在本文中,我们讨论了交叉验证及其对防止预测模型过度拟合的重要性。我们还评估了反向消除前后的 OLS 回归模型。我们讨论了作为替代回归工具的 XGBoost,并介绍了如何运行 XGBoost 模型和进行特性选择。我们使用 RMSE 和 r 平方作为我们的回归模型的度量,并发现后向后消除模型比前向后消除模型和最佳 XGBoost 模型具有更好的拟合。在特征选择之后出现的一些值得注意的变量是人均大学数量、大学生数量以及在国民收入排名中处于第五十分位数的父母的比例。在下一篇文章中,我们将学习如何使用分类方法来预测代际流动以及这种方法的利弊。
面向自然语言处理的中级 EDA 技术
原文:https://towardsdatascience.com/intermediate-eda-techniques-for-nlp-2c898cc96d1d
如何对自然语言处理的文本数据进行探索性数据分析

图片由作者提供。
文本数据的探索性数据分析(EDA)不仅仅是计算字符和术语。为了让你的 EDA 更上一层楼,你可以查看每个单词并对其进行分类,或者你可以分析文本的整体情绪。
对文本数据的探索性数据分析不仅仅是计算字符和术语。
在本文中,我们将研究一些针对文本数据的中级 EDA 技术:
这篇文章是我上一篇文章“NLP 的基本 EDA 技术”的延续。你可以在这里阅读:
正如在上一篇文章中一样,我们将在本文中再次使用来自 Kaggle 的女装电子商务服装评论数据集。
为了简化示例,我们将使用 450 条正面评论(rating == 5)和 450 条负面评论(rating == 1)。这将数据点的数量减少到 900 行,将评级类别的数量减少到两个,并平衡正面和负面的评论。
此外,我们将只使用两列:评论文本和评级。
精简数据集的数据帧头如下所示:

简化的女性电子商务服装评论数据集负责人(图片由作者提供)
词性标注
在基本 EDA 技术中,我们涵盖了最常见的单词和双字母组合,并注意到像“棒极了”和“完美”这样的形容词是正面评论中最常见的单词。
有了词性标注,您可以根据最常用的术语来细化 EDA。例如,你可以探索哪些形容词或动词最常用。
词性标注获取文本中的每个标记,并将其分类为名词、动词、形容词等,如下所示:

如果你对我是如何形象化这句话感到好奇,你可以在这里查看我的教程:
为了检查哪些 POS 标签是最常见的,我们将从创建数据框架中所有评论文本的语料库开始:
corpus = df["text"].values.tolist()

作为评论文本列表的语料库(图片由作者提供)
接下来,我们将标记整个语料库,为词性标注做准备。
from nltk import word_tokenize
tokens = word_tokenize(" ".join(corpus))

令牌列表(图片由作者提供)
然后,我们将使用粗标记集“universal”对语料库中的每个标记进行 POS 标记:
import nltk
tags = nltk.pos_tag(tokens,
tagset = "universal")

POS 标记令牌(图片由作者提供)
正如在上一篇文章的词频分析中一样,我们将通过删除所有停用词来创建一个标签列表。此外,我们将只包括特定标签的单词,例如形容词。
然后我们要做的就是像在上一篇文章中一样使用Counter类。
from collections import Countertag = "ADJ"
stop = set(stopwords.words("english"))# Get all tokens that are tagged as adjectives
tags = [word for word, pos in tags if ((pos == tag) & ( word not in stop))]# Count most common adjectives
most_common = Counter(tags).most_common(10)# Visualize most common tags as bar plots
words, frequency = [], []
for word, count in most_common:
words.append(word)
frequency.append(count)
sns.barplot(x = frequency, y = words)
下面,你可以看到负面和正面评论中最常见的 10 个形容词:

最常见的形容词由“评级”类分隔(图片由作者提供)。
从这个技巧中,我们可以看到像“小”、“适合”、“大”、“大”这样的词是最常见的。这可能表明顾客对一件衣服的合身程度比对它的质量更失望。
情感分析
情感分析的主要思想是理解一个文本是积极的还是消极的语气。例如,句子“我爱这件上衣。”有一个积极的情绪,和句子“我讨厌颜色。”有负面情绪。
您可以使用 TextBlob 进行简单的情感分析,如下所示:
from textblob import TextBlobblob = TextBlob("I love the cut")blob.polarity
极性是一个陈述是正还是负的指标,是一个介于-1(负)和 1(正)之间的数字。句子“我喜欢剪裁”的极性为 0.5,而句子“我讨厌颜色”的极性为-0.8。
组合句子“我喜欢这种剪裁,但我讨厌这种颜色”的极性为-0.15。
对于文本中的多个句子,您可以获得每个句子的极性,如下所示:
text = “I love the cut. I get a lot of compliments. I love it.”
[sentence.polarity for sentence in TextBlob(text).sentences]
这段代码返回一个极性数组[0.5,0.0,0.5]。这意味着第一句和最后一句有积极的情绪,而第二句有中性的情绪。
如果我们像这样把这种情感分析应用到整个数据框架中,
df["polarity"] = df["text"].map(lambda x: np.mean([sentence.polarity for sentence in TextBlob(x).sentences]))
我们可以用下面的代码绘制一个箱线图比较:
sns.boxplot(data = df,
y = "polarity",
x = "rating")
下面,您可以看到负面和正面评价的极性箱线图:

由“评级”类分隔的极性(图片由作者提供)。
正如你所料,我们可以看到负面评论(rating == 1)的极性总体低于正面评论(rating == 5)。
结论
在本文中,我们研究了一些针对文本数据的中级 EDA 技术:
- 词性标注:我们以词性标注以及如何使用词性标注来获取最常见的形容词为例。
- 情感分析:我们看了情感分析,探索了评论文本的极性。
下面是所有可快速复制的代码片段:
喜欢这个故事吗?
这里收集了我的其他自然语言处理文章:

莉奥妮·莫尼加蒂
自然语言处理
View list3 stories


如果你想把我的新故事直接发到你的收件箱, 订阅 !
成为媒介会员,阅读更多其他作家和我的故事。报名时可以用我的 推荐链接 支持我。我将收取佣金,不需要你额外付费。
https://medium.com/@iamleonie/membership
在 Twitter 上找我 ,LinkedIn,Kaggle!**
资料组
《nicapotato》《女装电商服装评论》。(License:CC0:Public Domain)https://www . ka ggle . com/datasets/nica potato/women-ecommerce-clothing-reviews(2022 年 8 月 30 日访问)。
数据科学数据可视化中级指南(下)
原文:https://towardsdatascience.com/intermediate-guideline-to-data-visualization-abf64bce91d2
Python 数据可视化:标准指南

动机
“数字有一个重要的故事要讲。他们依靠你给他们一个清晰而令人信服的声音——斯蒂芬几。
如果没有可视化,数据就是一些数值或分类值的集合。适当的可视化为找到有价值的信息铺平了道路。视觉化越舒缓,人们就越能掌握信息。
我们大多数人都熟悉像*scatter plots, bar charts, pie charts, histograms, line plots, etc.* 这样的普通图,除了这样的普通图,许多其他图可以更精确地表示信息。
本文将向您介绍一些有趣的文章来可视化信息。
【注意 Python 中有很多数据可视化库。为了使内容简单,我使用了 matplotlib、seaborn 和 plotly 库。】
目录
[**Dataset Description**](#01e1)[**Stacked Bar Chart**](#6a06)[**Grouped Bar Chart**](#9f3d)[**Stacked Area Chart**](#9858)[**Pareto Diagram**](#8b5b)[**Donut chart**](#5710)[**Heatmap**](#0cd4)[**Radar Chart**](#83eb)[**Tree Map**](#1f8c)
数据集描述
出于演示的目的,我创建了一个孟加拉蔬菜的合成数据集。数据集包含变量**Vegetable, Season, Weight and Price**,其中Vegetable and Season, Weight 和Price 是连续的分类和数值变量。
在这里找到数据集 。一瞥数据集。
堆积条形图
堆积条形图是一种特殊类型的条形图。我们可以在堆积条形图中集成比传统条形图更多的信息。
为了更好地理解,我们举一个用 python 编写的例子。
在上面堆叠的条形图中,三个条形代表每种类型seasonal vegetable的频率。这些频率也可以用一个简单的条形图来表示。但是我们发现从stacked bar 图中产生的额外信息是代表不同颜色的每个 v egetable for each season的频率计数。
It is worth mentioning that stacked bar chat is applicable only for categorical variables.
分组条形图
“分组条形图”这个名称表明——这是一种分成不同组的特殊类型的条形图。主要用于比较两个分类变量。
看上面的图表。‘Vegetable’和‘Season’ 是两个分类变量。‘x-axis’ 表示不同蔬菜的名称,‘y-axis’表示频率。如果你仔细看图表,你会发现不同蔬菜结合季节的频率是存在的。第一个组条显示‘Bean.’在组中出现的频率,有两个不同颜色的条。这两个条代表【豆】在**winter** 和**summer**两个季节出现的频率。其他成组的条形也表示相同的意思。
堆积面积图
***The Stacked Area Chart***以多个区域系列叠加的形式绘制。每个系列的高度由每个数据点中的值决定[1]。
通过这个堆积面积图,我们可以比较数据点的两个或多个数据变量的值。它还为我们提供了两个或多个变量的值如何彼此不同的信息。
*[The synthetic dataset I have created, mentioned at the beginning, is not fit for the graph. So, I have created another dataframe.]*
绘制堆积面积图。
图表显示了每个班级的“A 区”和“b 区”面积
排列图
帕累托图包含条形图和折线图,其中各个值由条形图以降序表示,而折线图表示累计总数。
在带条形图的帕累托图中,蔬菜的频率按降序分布。双轴被用来绘制图表。左边的y-axis 代表频率,右边的y-axis代表 累计频率 。是发现数据趋势的最好方法。
圆环图
甜甜圈图是一个简单的圆形中心切割图。虽然它代表了与饼图相同的含义,但它也有一些优点。在饼图中,我们经常会混淆每个类别所占的面积。由于饼状图的中心被从环形图中移除,这强调了读者要关心饼状图的外部弧[2]。内圈也可以用来显示附加信息。
用matplotlib 实现环形图如下,用于表示**‘Season’** 变量的频率。
热图
热图是一个矩形图,分为用不同颜色表示不同值/强度的子矩形。
这是相关性热图,其中x-axis和y-axis表示不同的变量,每个矩形代表两个变量的相关性。热图不仅可以表示相关值,还可以表示与变量相关的其他值。
雷达图
雷达图是一种以二维图表的形式显示多元数据的图形方法,该二维图表是从同一点[3]开始在轴上表示的三个或更多量化变量。来自中心的辐条,称为半径,代表变量[4]。半径之间的角度不包含任何信息[3]。
我已经使用了**plotly** 库来绘制雷达图。有了这个图,我们可以清楚的发现两点([1,5,2,2,3] and [4,3,2,5,1,2])的不同。数据点[1,5,2,2,3]表示— processing cost:1, mechanical properties: 5, chemical stability: 2, thermal stability: 2, device integration: 3, ,点【4,3,2,5,1,2】也是如此。
树形地图
树形图用于以嵌套的矩形形式显示**hierarchical data**[6]。
我已经使用了**plotly** 库来生成树形图。库的内置数据集用于绘制。看一下数据集就知道了。
*[N.B. — I have used the* [*gapminder*](https://www.kaggle.com/datasets/tklimonova/gapminder-datacamp-2007) *dataset which is a public domain dataset under “*[*CC0: Public Domain*](https://creativecommons.org/publicdomain/zero/1.0/)*” license.*]
数据集的树形图。
仔细看这张图表。它在分层嵌套的矩形中显示信息。对于树形图,**world** 位于层次结构的顶端。在 **world, continents**下被分配。在每一个**continent**,都有一些**countries**。群体的数量决定了矩形的大小。颜色因**life expectations**的刻度而异。
结论
作为一个人,我们希望获得精确的、有美感的信息。在策划之前,我们应该小心情节的性质。你对图表也有一个清晰的概念。
本文是数据可视化系列的延续。我也发表过关于 基础 和 高级数据可视化 的文章。
***Keep an eye out for my next article.***
*[I like to write a series of articles on different data science topics. Please, keep your eyes on the next article.]*
最后,如果你觉得这篇文章有帮助,别忘了
[***follow***](https://medium.com/@mzh706)我。你也可以用我的[***referral link***](https://mzh706.medium.com/membership)加入 medium。通过电子邮件获取我所有的文章更新[***subscribe***](https://mzh706.medium.com/subscribe)。
参考
- 堆积面积图| Chartopedia | AnyChart
- 什么是圆环图?| TIBCO 软件
- 雷达图—维基百科
- 什么是雷达图?| TIBCO 软件
- 树形图—维基百科
此处提供了完整的数据可视化指南..
我为你挑选了一些有趣的文章。别忘了朗读。
</11-less-used-but-important-plots-for-data-science-dede3f9b7ebd>
面向所有人的中级 SQL
原文:https://towardsdatascience.com/intermediate-sql-for-everyone-fe35c541147a
创建数据库,学习有用的语法,构建信息查询

马修·布罗德在 Unsplash 上的照片
当我开始学习 SQL 时,我发现很难超越绝对的基础。我喜欢数据营的课程,因为我可以直接在屏幕上的控制台上输入代码。但是一旦课程结束,我怎么能实践我学到的东西呢?当我找到的所有教程都是代码片段,而没有我自己可以查询的底层数据库时,我该如何继续改进呢?
我发现自己陷入了一个“先有鸡还是先有蛋”的问题——我需要访问数据库来学习足够的 SQL 以便被录用,但是我所知道的唯一数据库是我试图被录用的那些公司的!
事实证明,创建自己的数据库很简单。在本帖中,我们将创建一个简单的关系数据库,它将让我们探索基础知识之外的 SQL 主题。如果你理解了下面的问题,那么你就为这篇文章的其余部分做好了准备。(如果没有,请查看工程基础帖子中的 SQL 初级读本和 SQL 与 NoSQL 深度探讨。)
目录
- 安装
- 有用的语法
- 高级查询
- 往引擎盖下看:
EXPLAIN
安装
学习一门新语言时,练习至关重要。阅读这篇文章并点头附和是一回事,能够自己探索想法是另一回事。所以让我们从在你的电脑上建立一个数据库开始,这样你就可以实验和练习了。虽然这听起来有点吓人,但实际上很简单!

作者图片
第一步是在您的计算机上安装 SQL。我们将使用 PostgreSQL (Postgres) ,一种常见的 SQL 方言。为此,我们访问下载页面,选择我们的操作系统(如 Windows),然后运行安装。如果您在数据库上设置了密码,请在下一步使用它!(由于我们的数据库不会公开,所以使用类似admin的简单密码就可以了。)

作者截图
下一步是安装 pgAdmin ,这是一个图形用户界面(GUI ),可以轻松地与我们的 PostgreSQL 数据库进行交互。我们通过进入安装页面,点击我们操作系统的链接,然后按照步骤操作。
(仅供参考,本教程使用 Postgres 14 和 pgAdmin 4 v6.3。)
一旦两者都安装了,我们打开 pgAdmin 并点击“添加新服务器”这一步实际上建立了一个到现有服务器的连接,这就是为什么我们需要先安装 Postgres。我将我的服务器命名为home,并传递了我在 Postgres 安装过程中定义的密码。
我们现在准备创建一些表格!让我们制作一组表格来描述学校可能有的数据:学生、教室和成绩。我们将对数据进行建模,假设一个教室由多个学生组成,每个学生都有多个年级。
我们可以用 GUI 完成所有这些工作,但是我们将编写代码来使我们的工作流可重复。要编写将创建我们的表的查询,我们将右键单击postgres(在home > Databases (1) > postgres下),然后单击查询工具。

作者截图
让我们从创建classrooms表开始。我们将保持这个表的简单:它将只包含一个id和teacher名称。在查询工具中键入以下代码,然后点击 run。
在第一行中,DROP TABLE IF EXISTS classrooms,删除已经存在的classrooms表。(有道理!)如果其他表指向它,Postgres 将阻止我们删除classrooms,所以我们指定CASCADE来覆盖这个约束。[1]这没关系——如果我们删除了classrooms,我们可能会从头开始重新生成所有内容,因此所有其他表也会被删除!
在CREATE TABLE之前添加DROP TABLE IF EXISTS让我们在脚本中编写我们的数据库模式,如果我们决定以某种方式改变我们的数据库——添加表、改变列的数据类型等等,这是很方便的。我们可以简单地将生成数据库的指令存储在一个脚本中,当我们想要进行更改时更新该脚本,然后重新运行它。[2]
我们现在也能够版本控制我们的模式并共享它。事实上,本文中的整个数据库都可以从这个脚本中重新创建,所以请随意尝试!

作者图片
第 4 行可能也会引起您的注意:这里我们指定id是主键,这意味着该列中的每一行都必须包含一个值,并且每个值都必须是唯一的。为了避免需要跟踪哪些id值已经被使用,我们使用了GENERATED ALWAYS AS IDENTITY,这是对 序列 语法的替代。因此,在将数据插入该表时,我们只需要提供teacher名称。
最后,在第 5 行,我们指定teacher是一个最大长度为 100 个字符的字符串。[3]如果我们碰到一个老师的名字比这个长,我们就不得不缩写他们的名字或者修改表格。
现在让我们创建students表。我们的表将由一个唯一的id、学生的name和一个指向classrooms的 外键 组成。
我们在创建表之前再次删除它,然后指定一个自动递增的id和一个 100 字符的name。我们现在包括一个classroom_id列,并在第 7-9 行指定该列指向classrooms表的id列。
通过指定classroom_id是一个外键,我们已经设置了一个关于如何将数据写入students的规则。Postgres 不允许我们在students中插入一个不存在于classrooms中的带有classroom_id的行。
现在让我们创建一些教室。因为我们指定了id列将自动递增,所以我们只需插入教师姓名。
太好了!现在我们有了一些教室,我们可以向students添加记录,并引用这些教室。
如果我们得到一个还没有被分配教室的学生,会发生什么?是不是要等他们收到教室才能记录到数据库里?
答案是否定的:虽然我们的外键需求会阻止写入引用 **classrooms** 中不存在的 id,但它允许我们传入一个**NULL****classroom_id**。我们可以通过为classroom_id显式声明NULL或者只传入name来做到这一点。****
最后,我们来记录一些成绩。由于分数对应于作业——比如家庭作业、项目、出勤和考试——我们实际上将使用两个表来更有效地存储我们的数据。assignments将包含作业本身的数据,而grades将记录每个学生在作业中的表现。
不过,现在让我们通过 CSV 上传数据,而不是手动插入行。你可以从这个 repo 下载文件或者自己写。请注意,要允许 pgAdmin 访问数据,您可能需要更新文件夹(下面的db_data)的权限。
最后,让我们看一看,以确保一切就绪。下面的查询查找按教师分组的每个作业类别的平均分。
建立数据库做得好!我们现在准备试验一些更复杂的 SQL 概念。我们将从您可能还没有遇到过的语法开始,这会让您更好地控制您的查询。然后,我们将介绍一些其他的连接,以及在查询增长到几十行或几百行时组织查询的方法。

作者图片

照片由 Shifaaz shamoon 在 Unsplash 拍摄
有用的语法
过滤器:WHERE与HAVING
您可能熟悉WHERE过滤器,并且您可能听说过HAVING。但是它们到底有什么不同呢?让我们在grades上执行一些查询来找出答案。
首先,让我们从grades中抽取一些行来提醒我们自己数据是什么样子的。我们使用ORDER BY RANDOM()来洗牌,然后用LIMIT来取 5。(仅仅为了获得一个样本而对表中的所有行进行排序是非常低效的,但是如果表很小的话,这也没什么。)
每一行都是学生在作业中的分数。现在,假设我们想知道每个学生的平均分数。我们会执行一个GROUP BY,使用AVG(score)和四舍五入来保持整洁。
现在,假设我们想要上表,但是只包含那些avg_score在 50 和 75 之间的行。换句话说,我们只想显示学生 2。如果我们使用WHERE滤镜会发生什么?
那看起来一点也不对。学生 5 正确地消失了,但是学生 1、3 和 4 仍然在那里。更糟糕的是,他们的avg_score值变了!如果这些数字被写入一份重要的报告,而你不明白发生了什么,这可能会引起一些恐慌。
我们实际上想要做的是使用一个HAVING滤镜。看下面的区别。
这两个查询返回截然不同的结果,因为**WHERE** 和 **HAVING** 在聚合的不同阶段过滤数据。上面的WHERE查询在聚合之前过滤数据,而HAVING过滤结果。

作者图片
上述 **WHERE** **查询中的汇总结果发生了变化,因为我们更改了用于计算每个学生平均分数的原始数据。学生 5 没有任何介于 50 和 75 之间的分数,因此被放弃。与此同时,HAVING查询只是在计算之后过滤结果。****
一旦你熟悉了WHERE和HAVING,你就可以使用它们来创建非常具体的查询,例如查找平均家庭作业分数在 50 到 75 之间的学生。
如果——那么:CASE WHEN & COALESCE
在一个列上需要某种类型的if - else逻辑是很常见的。例如,您可能有一个模型预测表,您希望通过某个阈值将值二进制化为正负标签。
在我们的数据库中,假设我们想将grades表中的分数转换成字母等级。我们可以用CASE WHEN轻松做到这一点。
我们传递给CASE WHEN的逻辑可以扩展到多个列。例如,我们可以从我们的students表中生成一个instructor列,其中包含学生的老师(如果有的话),否则包含他们自己的名字。
如果我们所做的只是处理空值,那么COALESCE是一个更干净的选择。COALESCE返回传递给它的参数中的第一个非空值。重写上面的查询,我们得到这样的结果:
漂亮又干净!上面的第 4 行与第二个CASE WHEN示例中的第 4–7 行相同:如果teacher不为空,则返回该值。否则返回name。
COALESCE将下移您提供的参数,直到找到一个非空值。如果所有值都为空,则返回 null。

作者图片
最后,是 Postgres 中的一个IF语句,但是它用于多个查询的控制流,而不是在一个查询中。作为一名数据科学家,你不太可能经常使用IF——即使作为一名数据工程师,我也认为你会在像 Airflow 这样的协调器中处理这样的逻辑,所以我们在这里跳过它。[4]
设置运算符:UNION、INTERSECT和EXCEPT
当我们JOIN表格时,我们横向追加数据。例如,在下面的查询中,我们将来自students、grades和assignments表的亚当的数据放在一起,创建一个这些列并排的表。
水平附加我们的数据在大多数时候对我们很有用。但是如果我们想垂直堆叠查询结果呢?**
让我们想象一下,我们的学校腐败得令人难以置信,用学生的名字来决定他们是否毕业,而不是成绩。如果学生的名字 1)以A或B开头,或者 2)有五个字母长,则通过考试。我们可以通过查找符合每个标准的学生来查找所有即将毕业的学生,然后使用UNION ALL将这些行堆叠起来。
我们已经在第 4–11 行和第 18–24 行看到了第一个子查询,它是更复杂查询的构建块。注意,这些子查询需要被命名为(x和y),以便UNION ALL能够工作。
你可能还注意到我们使用了UNION ALL而不是UNION。区别在于UNION ALL返回所有的行,而UNION删除重复的行(包括在x和y内)。这个查询的结果是相同的,因为 Betty 满足两个条件,但是如果我们不包括reason列,我们将只看到 Betty 和UNION一次。**
选择UNION或UNION ALL取决于您想要如何处理副本。当编写复杂的查询时,我更喜欢使用UNION ALL来确保结果表具有我期望的行数——如果有重复的,我很可能在前面的某个地方搞砸了JOIN。如果您在源头解决问题,而不是在最后过滤,那么您的查询将会更高效。

作者图片
UNION和UNION ALL是 集合操作符 ,从子查询 A 和 B 返回所有行(sans 与UNION重复)。另外两个操作符INTERSECT和EXCEPT,让我们只返回符合特定标准的行。INTERSECT只返回出现在和子查询中的行,而EXCEPT返回 A 中不属于 b 中而不是的行。****

作者图片
这里我们演示了INTERSECT,它查找子查询之间共享的行(即 id 为 2 或 3 的行)。与UNION不同,我们不需要命名子查询。
这里我们展示了相同的查询,但是使用了EXCEPT,它在 A 中查找不在 B 中的而不是的行(即 ID 为 1 的行)。**
集合操作符共同赋予我们组合查询结果(UNION)、查看重叠记录(INTERSECT)以及精确查看表之间哪些行不同(EXCEPT)的能力。不再需要打印表格来堆叠或手动比较它们!
数组函数
关系数据库中的数据通常是 原子 ,其中每个单元格包含一个值(例如grades表中每行一个分数)。但有时将值存储为数组会很有用。对于这种类型的数据,Postgres 提供了广泛的数组函数,让我们可以创建和操作数组。
一个有用的函数是ARRAY_AGG,它将行转换成数组。下面,我们将ARRAY_AGG(score)和GROUP BY name结合起来,为每个学生创建所有分数的数组。
我们可以使用CARDINALITY来查找数组的长度,使用ARRAY_REPLACE来替换指定的值。(或者,ARRAY_REMOVE删除它们。)
您可能会发现最后一个有用的函数是UNNEST,它将数组解包为行。(本质上与ARRAY_AGG相反。)
太好了!已经介绍了过滤器、 if-then 逻辑、集合操作符和数组函数,现在让我们继续构建更高级的查询。

作者图片

高级查询
自连接
有时,我们可能希望将表与自身连接起来,以获得我们需要的数据。一个常见的例子是“经理”问题,我们在这里将它重新表述为“最好的朋友”问题。其思想是,如果表中的行包含指向表中其他行的的值(比如 IDs),那么我们可以将表连接到其自身*以获得对应于这些值的附加数据。*****

作者图片
让我们从添加然后填充一个best_friend_id列到我们的students表开始。
将最好的朋友的身份存储为一个数字是有效的,但是可读性不是很好。为了确定每个学生最好的朋友是谁,我们进行了一次自我加入。我们将students 连接到自身,其中一个表中的id列是另一个表中的best_friend_id。在我们的例子中,我们用别名x和y来区分这些表。
窗口功能
窗口函数类似于聚合函数(任何带有GROUP BY的函数),因为它们将计算应用于一组值。然而,与聚合函数不同,窗口函数不会减少行数。**
假设我们取每个学生的平均分。在下面的第 4–6 行,我们添加了OVER和PARTITION BY来将聚合转换成窗口函数。
对于像AVG、MIN或MAX这样的聚合器,PARTITION BY分组中的每一行都将具有相同的值。这可能对某些分析有用,但它并不能真正体现窗口函数的优势。
一个更有用的案例是排名每个学生的分数。首先,这是我们如何对所有学生的分数进行排名。我们使用RANK() OVER,然后传入列进行排名。**
对每个学生的分数进行排名是一行代码的修改:我们简单地将PARTITION BY s.name添加到OVER子句中。**
其他窗口功能包括计算领先值和滞后值(如时间序列的前一个值)、累积分布以及密集或百分比等级。

作者图片
WITH
让我们通过介绍一种技术来结束这一节,这种技术将使我们能够编写尽可能复杂的查询,只需将子查询串在一起。**WITH** 让我们命名一个子查询,意味着我们可以在其他地方引用该子查询的结果。
比方说,我们想标记学生在grades的分数是否高于他们的平均分。在一个查询中消除这个问题似乎很简单——我们只需要用一个GROUP BY计算每个平均值,然后做一些类似于g.score > avg的事情,对吗?先说GROUP BY聚合。
很简单。但是,我们如何将个人得分与这些平均值进行比较呢?以下两种尝试都会引发错误。
我们可以通过调用一个窗口函数两次让这个工作起来,但是看起来有点难看。**
一个更干净、更具可伸缩性的替代方法是使用WITH。我们可以将我们的查询分成两个子查询——一个计算平均值,另一个将平均值表加入到grades中。
WITH查询比只写两次窗口函数要长得多。何必呢?这个更详细的查询提供了两个重要的优势:可伸缩性和可读性。****
查询可能会变得非常长——在 Meta(脸书),这是我遇到的最长的查询(到目前为止!)超过 1000 行,调用了 25 个表。如果没有WITH子句,这个查询将完全不可读,这些子句划分了代码中不同的、命名为的部分。**
在处理大数据时,我们没有时间依次运行这些子查询,将结果保存到 CSV,然后用 Python 执行合并和分析。所有的数据库交互都需要一次性运行。

作者图片
这是另一个例子。比方说,我们学校腐败的成绩及格政策被曝光,管理人员被解雇。现在,通过一门课的标准是 1)你的加权平均分数至少是 85%,或者 2)你的传记项目分数超过 70%。将这个逻辑捆绑到一个CASE WHEN中会很痛苦,但是如果我们用WITH分解查询,这就很简单了。
让我们首先确定哪些学生会通过考试,因为他们的加权平均分数超过了 85%。
艰苦的学校!(但是加油卡罗琳!)现在,让我们来确定哪些学生通过了考试,因为他们的传记项目获得了 70%以上的分数。
我们希望找到符合任一标准的学生,因此我们需要一个类似如下的查询:
这对于WITH来说很简单。我们简单地将上面的两个查询命名为weighted_pass和project_pass,然后像上面一样引用它们。

往引擎盖下看:EXPLAIN
在结束这篇文章之前,让我们讨论最后一个概念。我们对 SQL 了解得越多,就有越多的方法来构建复杂的查询。应该用EXCEPT还是NOT IN?我们应该执行几个额外的JOIN还是使用WITH和UNION ALL?
本质上,我们如何知道一个查询比另一个查询更高效还是更低效?
Postgres 其实可以告诉你这个。关键字EXPLAIN提供了一个 执行计划 ,详细描述了Postgres 如何在幕后执行您的查询。从本文开头重新回顾一下查询,我们看到 Postgres 执行查询的顺序与我们编写查询的顺序完全不同。**

作者图片
我们可以用EXPLAIN ANALYZE更进一步,它将运行查询并详述性能。
例如,我们在上面看到,Postgres 顺序扫描(Seq Scan)我们的grades和students表,因为这些表没有索引。换句话说,Postgres 不知道students表中后面的行将比前面的行具有更低还是更高的 ID(如果我们要对 ID 进行索引)。这种次优的性能对于我们的小型数据库来说并不是一个大问题,但是如果我们的数据库增长到数百万行,我们肯定需要识别并修复这样的瓶颈。[5]

丹尼尔·劳埃德·布兰克-费尔南德斯在 Unsplash 上的照片
结论
这篇文章概述了一些 SQL 技巧,一旦你超越了基础,这些技巧就会变得有用。我们从安装 Postgres 和 pgAdmin 开始,通过在我们自己的笔记本电脑上进行 SQL 实验,为自己的成功做准备。
然后,我们讨论了构建更复杂查询的有用语法。我们从过滤器开始,概述了WHERE和HAVING之间的区别。然后我们讨论了 if-then 逻辑,展示了如何用CASE WHEN存储值,用COALESCE处理空值。我们用集合操作符从水平合并转移到垂直合并,详细说明了UNION、UNION ALL、INTERSECT和EXCEPT如何在提供的表中保留一些或所有行。然后,我们通过展示如何创建和处理数组来结束这一部分。
接下来,我们讨论了更高级查询的组件,比如使用自连接来连接自身的表、用于组内比较的窗口函数以及命名子查询的WITH。最后,我们讨论了EXPLAIN和EXPLAIN ANALYZE如何量化我们的查询性能并指出需要改进的地方。
想要不断拓展自己的知识面?总是有更多的函数需要学习,比如CAST(用于转换数据类型,例如将浮点数转换为整数),或者用户自定义函数,用于进一步简化您的代码。这些都是有用的,但是实际上我建议考虑让尽可能优化你的查询。即使在计算能力基本不受限制的 FAANG 公司,如果查询需要的内存超过了服务器的处理能力,查询也会失败。为复杂的查询选择正确的方法会使数据管道更容易维护,这意味着当它中断时,您不太可能在午夜被调用。(希望如此!)**
最好,
哑光

作者图片
脚注
1.安装
当我们在删除一个表时指定CASCADE时,实际上会发生什么?一点演示可能会有帮助。
假设我们删除了classrooms而没有删除任何其他表。students中的数据不受影响——我们仍然可以看到原始的教室 id。**
如果我们现在重新创建classrooms并输入不同的教师,students和classrooms之间的关系不再准确。**
这是因为 **CASCADE** 删除了 **students** 中的外键引用。我们可以通过手动将students中的classroom_id(现在不是外键)更新为不在classrooms中的 ID 来看到这一点,但是无法通过grades中的student_id(其中是外键)来做到这一点。
关于CASCADE的最后一点说明。如果我们在students中创建外键时指定了ON DELETE CASCADE,那么删除classrooms中的行将会删除students中的链接行。出于隐私原因,这可能很重要,例如,如果您希望在客户或员工离开您的公司后删除他们的所有信息。
2.安装
编写我们的数据库模式是一个工程最佳实践,但是对于实际数据,我们将执行数据库备份。有多种方法可以做到这一点,从占用大量内存的完整备份到相对较轻的更改快照。理想情况下,这些文件被发送到地理上远离存储我们数据库的服务器的某个地方,因此自然灾害不会摧毁您的整个公司。**
3.安装
我们将teacher列指定为最多 100 个字符的字符串,因为我们不认为我们会遇到比这更长的名称。但是,如果我们将行限制为 100 个字符而不是 200 或 500 个字符,我们真的节省了存储空间吗?
在 Postgres 中,无论我们指定 10、100 还是 500,从技术上来说都无关紧要。因此,指定一个限制可能更像是向未来的工程师(包括您自己)传达您对本专栏中的数据的期望的最佳实践。
但是在 MySQL 中,大小限制对有影响:临时表和MEMORY表将存储长度相等的字符串,填充到表模式中指定的最大值,这意味着如果没有值接近该限制,那么VARCHAR(1000)将浪费大量空间。
4.如果——那么:CASE WHEN & COALESCE
如果你很好奇,下面是带有IF语句的 Postgres 代码的样子。
5.引擎盖下看:EXPLAIN
随着数据库大小的增长,在表上设置索引对于确保性能至关重要。我们可以很容易地为grades中的分数设置一个索引,例如,使用下面的查询:
然而,如果我们再次运行EXPLAIN ANALYZE,我们将看到 Postgres 仍然运行顺序扫描。这是因为 Postgres 的优化已经超出了一篇“中级 SQL”博客文章所能教给你的!如果表中的行数相对较少,执行顺序扫描实际上比使用索引更快,因此 Postgres 使用更快的方法执行查询。
可解释的和可说明的石灰 NER
原文:https://towardsdatascience.com/interpretable-and-explainable-ner-with-lime-d643512c524
一步一步的教程,了解你的 NER 模型如何工作

图片来自 Pixabay
虽然在开发最新最伟大、最先进、具有大量参数的深度学习模型方面取得了很多进展,但在解释这些模型的输出方面却付出了很少的努力。
在 2020 年 12 月的一次研讨会上,Gradio 的首席执行官阿布巴卡尔·阿比德(Abubakar Abid)通过使用提示“两个 __ 走进一个 __”检查了 GPT-3 生成宗教文本的方式在观察了各种宗教的前 10 个回答后,他发现 GPT-3 提到了暴力,犹太人、佛教徒和锡克教徒各一次,基督教徒两次,但是十次有九次提到了穆斯林。
后来,阿比德的团队展示了将关于穆斯林的正面文本注入到一个大型语言模型中,减少了近 40%关于穆斯林的暴力提及。即使是 GPT 3 的创造者 OpenAI 也在 2020 年 5 月发表了一篇论文,通过测试发现 GPT 3 对黑人的评价普遍较低,并表现出性别歧视和其他形式的偏见。嵌入在这些大型语言模型中的这种类型的社会偏见的例子不胜枚举,从种族主义言论到有毒内容。
深度学习模型就像一个黑匣子;给它一个输入,它给你一个输出,而不解释决定的原因,无论是文本分类,文本生成,还是命名实体识别(NER)。密切监控这个模型的输出,更重要的是能够解释这些模型的决策过程,这是至关重要的。解释输出背后的推理会让我们更有信心相信或不相信模型的预测。
用石灰解释 NER 模型
在本教程中,我们将重点解释使用 LIME(本地可解释模型不可知解释)的命名实体识别模型的预测。可以从原论文中了解更多。
LIME 是模型不可知的,这意味着它可以用来解释任何类型的模型输出,而无需对其进行峰化处理。这是通过扰动目标预测周围的局部特征并测量输出来实现的。在我们的具体例子中,我们将改变目标实体周围的标记,然后尝试度量模型的输出。
下图说明了石灰的工作原理。
这里有一个来自 LIME 网站的解释“原始模型的决策函数用蓝色/粉色背景表示,明显是非线性的。亮红色的十字就是正在解释的实例(姑且称之为 X)。我们对 X 周围的扰动实例进行采样,并根据它们与 X 的接近程度对它们进行加权(这里的权重由大小表示)。我们获得原始模型对这些扰动实例的预测,然后学习线性模型(虚线),该线性模型在 x 附近很好地逼近该模型。注意,这种情况下的解释不是全局忠实的,但是在 x 附近是局部忠实的。”

石灰法。来源
LIME 输出具有对模型预测的贡献分数的记号列表(参见下面的文本分类示例)。这提供了局部可解释性,并且还允许确定哪些特征变化将对预测产生最大影响。

由 LIME 生成的一个文档类的解释。来源
在本教程中,我们将重点解释 NER 模型的石灰输出。
以下是步骤:
- 为我们的模型生成训练数据
- 在我们定制的带注释的数据集上训练 NER 模型
- 选择一个目标词来解释
加载数据
在本教程中,我们将训练一个 NER 模型,从职位描述中预测技能、经验、文凭和文凭专业。数据是从 Kaggle 获得的。关于使用 UBIAI 的数据注释部分的更多细节,请参考这篇文章。
为了训练 NER 模型,我们将使用 CRF 算法,因为它可以很容易地输出每个预测实体的置信度得分,这是 LIME 工作所需的。
第一步是将带注释的数据加载到我们的笔记本中;数据被格式化为 IOB 格式。
这里有一个小例子:
-DOCSTART- -X- O O2 B-EXPERIENCE+ I-EXPERIENCEyears I-EXPERIENCEexperience Oin Othe Oonline B-SKILLSadvertising I-SKILLSor Oresearch B-SKILLS
接下来,我们导入几个包,并将数据预处理成一个元组列表(令牌、标签):
! pip install -U 'scikit-learn<0.24'!pip install sklearn_crfsuite #Installing CRF!pip install eli5 # Installing Lime
让我们看看这个列表是什么样子的:
train_file_path = r"/content/train_data.tsv"train_sents = import_documents_set_iob(train_file_path)print(train_sents)#Small sample of the output
('of', 'O'), ('advanced', 'B-SKILLS'), ('compute', 'I-SKILLS'), ('and', 'O')
数据预处理
为了训练 CRF 模型,我们需要将带注释的文本转换成数字特征。更多信息请查看 CRF 文档:
完成这些之后,我们就可以开始训练了——我们只需要将训练/测试特性和目标标签放入它们各自的列表中:
X_train = [sent2features(s) for s in train_sents]y_train = [sent2labels(s) for s in train_sents]X_test = [sent2features(s) for s in test_sents]y_test = [sent2labels(s) for s in test_sents]
模特培训
我们通过 100 次迭代来启动培训:
crf = sklearn_crfsuite.CRF(algorithm='lbfgs',c1=0.1,c2=0.1,max_iterations=100,all_possible_transitions=True)crf.fit(X_train, y_train)
训练后,我们得到 0.61 的 F-1 分数,虽然不高,但考虑到标注数据集的数量,这是合理的。每个实体的分数:
sorted_labels = sorted(labels,key=lambda name: (name[1:], name[0]))print(metrics.flat_classification_report(y_test, y_pred, labels=sorted_labels, digits=3))

作者图片:培训后实体得分
用石灰解释 NER
既然我们已经训练好了模型,我们就可以使用 LIME 算法来解释它的标签预测了。首先,我们初始化我们的 NERExplainerGenerator 类,它将从输入文本中生成特性,并将其输入到我们的模型中:
我们将使用工作描述中的以下句子进行测试:
text = '''6+ years of Web UI/UX design experienceProven mobile web application design experience'''explainer= NERExplainerGenerator(crf)for index,word in enumerate(word_tokenize(text)):print(index,word)0 6+
1 years
2 of
3 Web
4 UI/UX
5 design
6 experience
7 Proven
8 mobile
9 web
10 application
11 design
12 experience
最后,我们需要设置时间解释器算法。以下是每个功能的含义:
- MaskingTextSampler: 如果你还记得在前面的介绍中,我们提到过 LIME 会试图干扰局部特征并记录我们模型的输出。它通过用“UNK”令牌随机替换 70%的令牌来做到这一点。如果需要,可以调整百分比,但默认值是 70。
- 样本,相似度:LIME 模型会通过对带有“UNK”令牌的原句进行随机化,生成很多句子。这里有几个例子。
['6+ years of UNK UNK/UX design experience\nProven UNK web UNK UNK experience', 'UNK+ years UNK Web UI/UX design experience\nProven mobile web application UNK UNK', '6+ UNK of Web UI/UX design experience\nProven UNK web application UNK experience', 'UNK+ years of Web UI/UNK UNK UNK\nUNK mobile web application UNK experience']
对于每个句子,我们将有一个来自我们的 NER 模型的预测标签。然后,LIME 将使用线性白色模型对数据进行训练,该模型将解释每个令牌的贡献:te.fit(text,func)
例如,让我们试着解释单词“UI/UX”的标签,它有一个 word_index =4 :
word_index = 4 #explain UI/UX labelfunc = explainer.get_predict_function(word_index)sampler = MaskingTextSampler(replacement="UNK",max_replace=0.7,token_pattern=None,bow=False)samples, similarity = sampler.sample_near(text, n_samples=4)print(samples)te = TextExplainer(sampler=sampler,position_dependent=True,random_state=42)te.fit(text, func)#the explainer needs just the one instance text from texts listexplain = te.explain_prediction(target_names=list(explainer.model.classes_),top_targets=3)print("WORD TO EXPLAIN", word_tokenize(text)[word_index])explain
这是输出结果:

作者图片:石灰输出
绿色表示令牌对预测标签有积极贡献,红色
Lime 还提供了每个令牌的贡献,非常简洁:

作者图片:每个功能的贡献
我们注意到,网络、设计和年份对预测的 I-SKILLS 标签的贡献最大。
结论:
随着我们走向大型复杂的黑盒人工智能模型,理解预测背后的决策过程对于能够信任模型输出至关重要。
在本教程中,我们将展示如何训练自定义 NER 模型,并使用石灰算法解释其输出。LIME 是模型不可知的,可以用于解释任何复杂模型的输出,无论是图像识别、文本分类还是本教程中的 NER。
如果您有任何问题或想要为您的特定案例创建定制模型,请在下面留言或发送电子邮件至 admin@ubiai.tools。
在 Twitter 上关注我们
概率的解释
原文:https://towardsdatascience.com/interpretations-of-probability-b254ad33fe50
在这个“数据驱动”的世界里,我们随意使用“概率”这个词,但当我们谈论“概率”或“机会”时,我们到底指的是什么

马库斯·温克勒在 Unsplash 上的照片
概率的解释
概率的口语用法涵盖了一个令人惊讶的范围。
当我们说有 1/6 的机会骰子被掷出“3”时,我们是什么意思或者说,当我们说弦理论有 30%的可能性是正确的时候,我们指的是什么?或者,当气候科学家认为水位可能上升 1 米时,我们指的是什么?
这些机会到底是什么,它们都一样吗?
概率是什么?
一些作者*指出,概率经常在(数据)科学、常规科学、哲学和流行写作中被引用,而没有具体说明科学家们具体指的是哪种概率。
这个问题因为我们使用诸如“机会”、“可能性”、“发生”和“可能性”这样的术语而变得更加严重。
TL;博士 : 不是所有类型的概率都是一样的。一些哲学家认为概率是现实世界的一部分,其他人认为它是重复事件的结果,还有一些人认为它是个人的信仰程度。
物理概率
一些哲学家认为概率是现实世界的一部分。这是自然的一个特征,并嵌入在我们周围的元素中。这很直观。当考虑原子衰变和某些元素的半衰期时,我们可以有一个预期的衰变概率,但它似乎不依赖于任何观察者的倾向。它就这样发生了。
这是铀元素衰变的倾向,这种倾向与你的个人观点无关。
但是当我们说:“这个客户有 38%的可能性购买一个产品”时,这种物理概率的想法就没有意义了。购买或不购买产品似乎不是客户的一部分。这种概率似乎与重复事件(或大样本量)有关。
主观概率
因此,这种物理概率的想法似乎与我们的个人信念截然不同。"我认为明天有 90%的可能会下雨。"我很可能错了,因为我还没有查看新闻。然而,这种概率度量似乎不同于原子的物理概率。
所以有些哲学家认为概率是主观的。据说人们对自己对某些命题(比如“明天会下雨”)的相信程度有多深。大多数信仰都是极其世俗的:太阳升起来了,公共汽车在工作日会稍微晚点,我们有脑袋。但是有些信念要复杂得多。例如,认为经济增长受到电晕流行病的阻碍。
认知概率
最后,还有另一组概率既不属于现实世界,也不属于人类,这被称为认知概率。“认知”与“我们如何知道”事物有关,是对“知识”的研究。
认知概率是证据支持假设的程度。例如,科学证据和研究在多大程度上(概率)支持大爆炸或弦理论。鉴于没有人目睹真正的大爆炸,我们也无法观察到这些所谓的弦——我们必须有支持(或反驳)各种理论的研究网络。
概述
- 物理概率(或偶然,有时是统计概率),随着那些认为概率是物理属性、频率或倾向的人之间的进一步区分,这些概率被视为现实世界的特征;
- 主观概率(也作 credences,有时也作 logical probability),对给定命题、给定证据的信念的度量你有多坚定地持有(或应该持有)一种信念,概率是人类的一个特征,而不是外界的;
- 认知概率(有时是归纳概率),证据在多大程度上支持假设或模型,理论之间的关系是逻辑的和客观的,但非经验的,它围绕非决定性证据用理论扩展逻辑。
事情变得更加复杂
不是每个人都同意这种概率三分法是正确的。其他作者区分了四个层次。然而,最重要的一点是,并不是所有的概率都指向同一个“东西”
- 逻辑理论概率是一种理性程度的信念,被试从相似的信念中给出相似的证据;
- 主观理论概率受到特定个体的约束,相似的证据会导致不同的信念;
- 频率理论概率是一个结果在一系列事件中出现的频率;
- 倾向理论概率是发生的倾向,如果一个事件可以发生多次,它会产生一个结果发生的频率。
所有现代概率哲学家的共同点是,他们反对“经典解释”,经典解释松散地说,概率的解释是因为我们人类无法理解事物确定性联系的复杂性。这一经典解释因法国数学家拉普拉斯而闻名。
简而言之,各种理论认为概率是现实世界的一部分,其他人认为它是重复事件的结果,还有一些人认为它是信念的个体度量。
但是有解决办法
梅勒,一位研究概率解释的哲学家,提供了一种在正式讨论概率时区分概率类型的有用方法。
一般来说,当我们在技术背景下讨论概率时,我们大多数人做的是,给定一个命题 A,p(A)表示 A 的概率。这可能会在区分投掷硬币正面落地的概率是 1/2 和正面落地的概率(可能类似)时造成混淆。
你可以相信在一长串“正面”之后,下一次投掷将是一个“反面”,但这不会改变硬币本身的机会。
Mellor 建议在考虑所有三个因素时使用 p(A ),当我们具体谈论机会时使用 ch(A ),当讨论可信度时使用 cr(A ),当谈论认知概率时使用 e p(A)。
无论如何,当谈论概率时,最好考虑一下你(或你的对话伙伴)到底指的是什么。
稍微思考一下这个问题,可能会有助于消除今后的许多困惑——或许对概率还有另一种解释。
**本文是我在鹿特丹伊拉斯谟大学攻读博士学位时进行的一些文献研究的推广。它建立在大量研究的基础上,这些研究在这里很难引用,完整的文献综述可以在:*www.jeroenvanzeeland.com/research找到
解读用于时间序列预测的 ACF 和 PACF 图
如何确定 AR 和 MA 模型的阶数

图片由作者通过 Kaggle 提供
自相关分析是时间序列预测探索性数据分析的重要步骤。自相关分析有助于检测模式和检查随机性。当您打算使用自回归移动平均(ARMA)模型进行预测时,这一点尤为重要,因为它有助于确定其参数。该分析包括查看自相关函数(ACF)和偏自相关函数(PACF)图。
这篇文章帮助你建立一个解释 ACF 和 PACF 情节的直觉。
这篇文章帮助你建立一种直觉来解释这些 ACF 和 PACF 情节。我们将简要回顾 ACF 和 PACF 的基本原理。然而,由于重点在于对剧情的解读,对底层数学的详细讨论超出了本文的范围。我们将参考其他资源。
本文是我的 Kaggle 笔记本 的改版,原载于 2021 年 12 月。您可以在那里下载或派生代码。
基本原则
ACF 和 PACF 图用于计算 AR、MA 和 ARMA 模型的阶数。在这一节中,我们将只简要介绍相关术语。对于详细的解释,我们将参考其他资源。
自回归和移动平均模型
自回归模型
自回归(AR)模型假设当前值(y_t)依赖于先前值 ( y_( t-1),y_(t-2),…)。因为这个假设,我们可以建立一个线性回归模型。

要弄清楚一个 AR 模型的顺序,你需要看一下 PACF** 。**
移动平均模型
移动平均(MA)模型假设当前值(y_t)取决于误差项,包括当前误差(𝜖_t,𝜖_(t-1),…).因为误差项是随机的,所以在当前值和误差项之间没有线性关系。

要搞清楚一个 MA 型号的顺序,需要看 ACF** 。**
前提条件:平稳性
ACF 和 PACF 假设基础时间序列平稳。
自相关函数(ACF)和偏自相关函数(PACF)
ACF 和 PACF 用于计算 AR、MA 和 ARMA 模型的阶数。
如果你需要一些关于 ACF 和 PACF 的介绍或复习,我推荐以下视频:
自相关函数
自相关是时间序列与其自身滞后版本之间的相关性。ACF 从滞后 0 开始,这是时间序列与其自身的相关性,因此导致相关性为 1。
我们将使用statsmodels.graphics.tsaplots库[5]中的plot_acf函数。在这篇文章中,我们将只看 15 个滞后,因为我们使用了最少的例子。
from statsmodels.graphics.tsaplots import plot_acfplot_acf(time_series_values, lags = 15)
ACF 图可以回答以下问题:
- 观察到的时间序列是否为白噪声/随机?
- 一个观察值是否与一个相邻的观察值相关,一个被删除两次的观察值,等等?
- 观察到的时间序列可以用 MA 模型建模吗?如果是,顺序是什么?
偏自相关函数(PACF)
滞后 k 处的部分自相关是滞后 1 到𝑘−1.没有考虑的 X_t_t 和 X_(t-k)之间的自相关[4]
我们将使用statsmodels.graphics.tsaplots库中的plot_pacf函数,带有参数method = "ols"(时间序列的滞后和常数回归)[5]。
from statsmodels.graphics.tsaplots import plot_pacfplot_pacf(time_series_values, lags = 15, method = "ols")
侧注: *method* 的默认参数是 *yw* (Yule-Walker 在 acovf 的分母中调整样本大小)。但是,该默认值会导致样本数据中出现一些高于 1 的不可信自相关。因此,我们将 *method* 参数更改为不会导致此问题的参数。 *ywmle* 也能很好地工作,就像这篇 StackExchange 帖子中建议的那样[3]。
PACF 图可以为以下问题提供答案:
- 观察到的时间序列可以用一个 AR 模型建模吗?如果是,顺序是什么?
AR、MA 和 ARMA 模型的阶数
下面你可以看到一个 ACF 和 PACF 图的例子。这些地块被称为“棒棒糖地块”[2]。

ACF 和 PACF 图的示例。(图片由作者通过 Kaggle 提供)
ACF 和 PACF 都以 0 的滞后开始,这是时间序列与其自身的相关性,因此导致 1 的相关性。
ACF 和 PACF 的区别在于计算中是否包含间接相关性。
此外,您可以在 ACF 和 PACF 图中看到一个蓝色区域。这个蓝色区域描绘了 95%的置信区间,并且是显著性阈值的指示器。这意味着,蓝色区域内的任何东西在统计上接近于零,蓝色区域外的任何东西在统计上不为零。
要确定模型的顺序,您需要检查:
“在下一个棒棒糖进入蓝色区域之前,有多少棒棒糖高于或低于置信区间?”— [2]

图片由作者 via Kaggle 启发而来[1]
例子
在本节中,我们将查看一些时间序列示例,并了解:
- ACF 和 PACF 图是什么样子的
- 如何确定是用 AR 模型还是 MA 模型对时间序列建模
- 如何确定 AR 或 MA 模型的阶数
- 如何找到 AR 或 MA 模型的参数
AR(1)过程
下面的时间序列是一个 AR(1)过程,有 128 个时间步和alpha_1 = 0.5。它满足平稳性的前提条件。

虚构的样本时间序列:alpha_1 = 0.5 的 AR(1)过程(图片由作者通过 Kaggle 提供)
下图显示了生成的 ACF 和 PACF 图:

ACF 和 AR(1)过程的 PACF 图。(图片由作者通过 Kaggle 提供)
我们可以做出如下观察:
- 有几个明显非零的自相关。因此,时间序列是非随机的。
- PACF 图中相邻(滞后= 1)之间的高度自相关
- ACF 图中的几何衰减

基于上表,我们可以使用一个 AR(1)模型来建模这个过程。
对于 AR(p=1),公式为

可以重写为以下内容:

为了找到参数alpha_1,我们拟合 AR 模型如下:
from statsmodels.tsa.ar_model import AutoRegar_model = AutoReg(X_train, **lags = 1**).fit()
ar_model.summary()

AR 模型拟合的参数。(图片由作者通过 Kaggle 提供)
如您所见,AR(1)模型符合一个alpha_1 = 0.4710,它与我们设定的alpha_1 = 0.5非常接近。
AR(2)过程
以下时间序列是具有 128 个时间步长的 AR(2)过程,alpha_1 = 0.5和alpha_2 = -0.5。它满足平稳性的前提条件。

虚构样本时间序列:alpha_1 = 0.5,alpha_2 = -0.5 的 AR(2)过程(图片由作者通过 Kaggle 提供)
下图显示了生成的 ACF 和 PACF 图:

ACF 和 AR(2)过程的 PACF 图。(图片由作者通过 Kaggle 提供)
我们可以做出如下观察:
- 有几个明显非零的自相关。因此,时间序列是非随机的。
- PACF 图中相邻(滞后= 1)和近相邻(滞后= 2)观测值之间的高度自相关
- ACF 图中的几何衰减

图片由作者通过 Kaggle 获得灵感[1]
基于上表,我们可以使用一个 AR(2)模型来建模这个过程。
对于 AR(p=2),公式为

可以重写为以下内容:

为了找到参数alpha_1和alpha_2,我们拟合 AR 模型如下:
from statsmodels.tsa.ar_model import AutoRegar_model = AutoReg(X_train, **lags = 2**).fit()
ar_model.summary()

AR 模型拟合的参数。(图片由作者通过 Kaggle 提供)
如你所见,AR(2)模型符合一个alpha_1 = 0.5191和alpha_2 = -0.5855,与我们设定的alpha_1 = 0.5和alpha_2 = -0.5相当接近。
MA(1)过程
以下时间序列是具有 128 个时间步长和beta_1 = 0.5的 MA(1)过程。它满足平稳性的前提条件。

虚构的样本时间序列:beta_1 = 0.5 的 MA(1)过程(图片由作者通过 Kaggle 提供)
下图显示了生成的 ACF 和 PACF 图:

ACF 和 MA(1)过程的 PACF 图。(图片由作者通过 Kaggle 提供)
我们可以做出如下观察:
- 有几个明显非零的自相关。因此,时间序列是非随机的。
- ACF 图中相邻(滞后= 1)之间的高度自相关
- PACF 图中的几何衰变

图片由作者 via Kaggle 启发而来[1]
基于上表,我们可以使用一个 MA(1)模型来对这个过程建模。
用 MA(q=1),公式

可以重写为以下内容:

为了找到参数beta_1,我们拟合 MA 模型如下:
from statsmodels.tsa.arima_model import ARMAma_model = ARMA(X_train, **order = (0, 1)**).fit()
ma_model.summary()

(AR)MA 模型拟合的参数。(图片由作者通过 Kaggle 提供)
正如你所看到的,MA(1)模型符合一个beta_1 = 0.5172,它与我们设定的beta_1 = 0.5非常接近。
MA(2)过程
下面的时间序列是一个 MA(2)过程,有 128 个时间步和beta_1 = 0.5和beta_2 = 0.5。它满足平稳性的前提条件。

虚构的样本时间序列:beta_1 = 0.5 和 beta_2 = 0.5 的 MA(2)过程(图片由作者通过 Kaggle 提供)
下图显示了生成的 ACF 和 PACF 图:

ACF 和 MA(2)过程的 PACF 图。(图片由作者通过 Kaggle 提供)
我们可以做出如下观察:
- 有几个明显非零的自相关。因此,时间序列是非随机的。
- ACF 图中相邻(滞后= 1)和近相邻(滞后= 2)观测值之间的高度自相关
- PACF 图中的几何衰变

图片由作者 via Kaggle 启发而来[1]
根据上表,我们可以使用一个 MA(2)模型来对这个过程建模。
用 MA(q=2),公式

可以重写为以下内容:

为了找到参数beta_1和beta_2,我们拟合 MA 模型如下:
from statsmodels.tsa.arima_model import ARMAma_model = ARMA(X_train, **order = (0, 2)**).fit()
ma_model.summary()

(AR)MA 模型拟合的参数。(图片由作者通过 Kaggle 提供)
如你所见,MA(2)模型符合一个beta_1 = 0.5226和beta_2 = 0.5843,与我们设定的beta_1 = 0.5和beta_2 = 0.5相当接近。
期刊
以下时间序列是周期性的,T=12。它由 48 个时间步长组成。

虚构的样本时间序列:T=12 的期刊(图片由作者通过 Kaggle 提供)
下图显示了生成的 ACF 和 PACF 图:

ACF 与周期过程的 PACF 图。(图片由作者通过 Kaggle 提供)
我们可以做出如下观察:
- 有几个明显非零的自相关。因此,时间序列是非随机的。
- PACF 图中相邻(滞后= 1)和近相邻观测值之间的高度自相关
- 从 ACF 和 PACF 图中,我们可以看到与相邻观测值(滞后= 1)的强相关性,以及滞后 12,这是 t 的值

图片由作者 via Kaggle 启发而来[1]
对于 AR(p=12),公式为

可以重写为以下内容:

为了找到参数alpha_1到alpha_12,我们拟合 AR 模型如下:
from statsmodels.tsa.ar_model import AutoRegar_model = AutoReg(X_train, **lags = 12**).fit()
ar_model.summary()

AR 模型拟合的参数。(图片由作者通过 Kaggle 提供)
如你所见,MA(2)模型符合参数alpha_1..11 = -0.0004和alpha_12 = 0.9996,与我们设定的alpha_1..11 = 0和alpha_12 = 1相当接近。
有了这些参数,公式可以改写如下:

白噪声
以下时间序列是随机的。它由 48 个时间步长组成。

虚构的时间序列样本:白噪声(图片由作者通过 Kaggle 提供)
下图显示了生成的 ACF 和 PACF 图:

ACF 和白噪声的 PACF 图。(图片由作者 via Kaggle
我们可以做出如下观察:
- 只有一个自相关在滞后为 0 时显著非零。因此,时间序列是随机的。
建模白噪声是困难的,因为我们无法从 ACF 和 PACF 图中检索任何参数。
结论
在本文中,我们查看了 AR 和 MA 过程、周期性时间序列和白噪声的各种示例,以帮助您建立解释 ACF 和 PACF 图的直觉。
本文讨论了:
- 如何检测时间序列中的随机性
- 如何确定是用 AR 模型还是 MA 模型对时间序列建模
- 如何确定 AR 或 MA 模型的阶数
- 如何找到 AR 或 MA 模型的参数
下图以备忘单的形式直观地总结了本文:

解读 ACF 和 PACF 备忘单(图片由作者提供)
喜欢这个故事吗?
成为一名媒体会员,阅读更多来自我和其他作家的故事。报名时可以使用我的 推荐链接 支持我。我将收取佣金,不需要你额外付费。
https://medium.com/@iamleonie/membership
参考
[1] S. Ali,“解读 ACF 和 PACF 阴谋——缺失的手册/备忘单”。linkedin.com。https://www . LinkedIn . com/pulse/reading-ACF-pacf-plots-missing-manual-cheat sheet-saq IB-Ali/(2022 年 7 月 27 日访问)
[2]“Arauto”,“如何为模型选择参数”。arauto . readthe docs . io .https://arauto . readthe docs . io/en/latest/how _ to _ choose _ terms . html(2022 年 7 月 29 日访问)
[3]“交叉验证”,“非常高的 PACF 值(> 10)意味着什么?”。stackexchange.com。https://stats . stack exchange . com/questions/380196/what-do-very-high-pacf-values-10-mean(2022 年 7 月 27 日访问)
[4] NIST,“6.4.4.6.3。偏自相关图”。nist.gov。https://www . ITL . NIST . gov/div 898/handbook/PMC/section 4/PMC 4463 . htm(2022 年 7 月 27 日访问)
[5]“stats models 0 . 14 . 0(+497)”,“statsmodels.tsa.stattools.acf”。statsmodels.org。https://www . stats models . org/dev/generated/stats models . TSA . stat tools . ACF . html(2022 年 7 月 27 日访问)
解读 EDA:第一章
原文:https://towardsdatascience.com/interpreting-eda-chapter-i-8d0999e372a2
想知道数据是如何讲述故事的吗?观想叙述它们。
2017 年,数据超越石油成为这个星球上最有价值的资产。每个部门都会产生大量的数据。根据 cloud tweaks 的数据,每天至少产生 2.5 万亿字节的数据(也就是 2.5 后面跟着惊人的 18 个零)。太神奇了。但是,人类的大脑没有能力处理哪怕是 1%的如此庞大的数据。因此,为了将这些令人受宠若惊的数据文件变成现实,并将它们转化为信息,我们需要探索性的数据分析。通常被称为“探索性数据分析之父”的约翰·图基说:
“一张照片的最大价值在于它能迫使我们注意到我们从未想过会看到的东西。”

安娜斯塔西娅·马莱在 Unsplash 上拍摄的照片
标题中的“解释”一词是有原因的。一起,我们将理解可以从一些常见的可视化中提取的结论。可视化不仅仅是美丽、引人注目的图片,每种类型的情节都有自己的故事要讲述。在这个博客系列的第一部分中,我将介绍三种重要的绘图类型,即直方图、散点图和计数图。所有的可视化都是在著名的“虹膜数据集”上计算的,该数据集是关于根据花瓣和萼片特征对三种花进行分类的。

数据样本。来源:图片由所有者提供
目录
柱状图
直方图用于分析和理解样本的分布。它可能是正态分布、左偏、右偏、均匀或其他。理解分布的一个原因是它有助于处理缺失值,
- 如果分布是左偏或右偏的,那么我们用中值来代替空值,因为中值不受极值的影响。
- 如果是正态分布,那么就用均值来代替空值。
为了比较两个分布,有必要确保 X 轴和 Y 轴上的值相同,并且面元大小相同。箱的大小会影响分布的形状。直方图有助于理解分布是单峰、双峰还是多峰。此外,它还显示了分布范围有多广。它有助于识别数据集中的异常值和高杠杆点。
就箱的大小而言,可以有许多方法来选择箱的大小,但其中之一是斯特奇法则,
K = 1 + 3.22 logN
在哪里,
k 是箱的数量
n 是观察值的数量

来源:作者图片

来源:作者图片

来源:作者图片

来源:作者图片
当你仔细观察萼片宽度的直方图时,它们近似呈正态分布。而其他三个不是因为以下原因,
- 花瓣长度和花瓣宽度不是正态分布。对于 1 和 0.5 的箱大小,它分别围绕平均值不对称。
- 就萼片长度而言,它是双峰的,大小为 0.5。
如果你想详细了解正态分布,可以看看我上一篇博客“揭秘估计:基础知识”。直方图的问题在于它的形状会随着容器大小的变化而变化。另一种检验分布是否正态的方法是使用一个正态 q-q 图,我们将在本系列的下一部分中讨论。
散点图
散点图有助于理解两个连续变量之间关系的本质。它可以是,
- 线性关系
- 正:两个变量一起增加。从左到右上坡
- 负:一个变量增加,另一个变量减少。从左向右下坡
2.非线性关系:曲线模式
3.无模式:无相关性
具有线性模式的散点图的点似乎通常落在一条线上,而非线性模式似乎沿着一些曲线。不管模式是什么,我们用它来描述变量之间的关联。如果没有明确的模式,那就意味着我们正在研究的变量之间没有明确的关联或关系。在完美线性图的情况下,我们可以得到类似于 X 增加使 z 乘以 y 的关系。点越接近,它们之间的相关性越好。
相关性并不意味着因果关系。如果存在关系,并不一定意味着 X 的增加/减少会导致 y 的增加/减少。如果任何特征相对于目标变量呈散点图,并且具有高相关性(正/负),则意味着它可以很好地解释目标变量,并且是预测目标变量的良好特征。
它有助于理解:
- 离群点:远离其他点但在回归线路径上的点。它不符合回归线,可能会对回归线产生较大影响。
- 高杠杆观察:远离回归线/点模式。它改变了回归线,但幅度不大。
散点图的问题在于人眼无法理解重叠的点。

来源:作者图片
- 上面的图是结对图。配对图包括特征的所有可能组合的散点图和直方图。
- 花瓣长度和花瓣宽度之间的关系是最线性的,这意味着它们具有很强的相关性。
- 随着数据点从左到右上坡,它们具有正的关系。这意味着花瓣长度随着花瓣宽度的增加而增加。
- 花瓣长度和萼片长度也是具有正相关关系的最线性的图之一。
- 大致来说,图中没有大的异常值,但也许有一些高杠杆点。
- 在所有散点图中,一段点与大多数数据点分离。为了更深入地挖掘,我绘制了配对图,将目标变量“物种”作为第三个变量。

来源:作者图片
- 从上面的散点图中可以看出,“鸢尾-刚毛鸢尾”是与其他两个分开的一组,特别是在花瓣长度和花瓣宽度方面。
- 花瓣长度和花瓣宽度的直方图表明‘鸢尾’的长度和宽度小于其他两类。
- 因此,花瓣长度和花瓣宽度是区分鸢尾类和其他两类的重要特征。
- 另外,其他两类很难分类。尽管如此,关于花瓣长度和花瓣宽度,它们大部分是可以分开的,中间有一个很小的间隙。
计数图
因为这是一个分类问题,所以有必要检查目标特征是否是倾斜的,即不平衡的。如果不平衡,我们可以选择任何一种方式,
- 随机过采样是复制最不频繁类别的观察值,以匹配最频繁类别的频率。
- 随机欠采样是去除最不频繁类的观测值,以匹配最频繁类的频率。
- 使用 SMOTE 等技术创建合成数据。
理解上述技术仅应用于训练数据集是很重要的。否则,会给结果增加偏见。

来源:作者图片
- 如上面的计数图所示,目标变量没有不平衡/偏斜。所以,我们不需要任何技术来平衡数据。
结论
在本系列的第一篇博客中,我们学习了如何解释直方图、散点图和计数图。我们学习了两种类型的解释,
- 一般解读:是关于可视化可以得出什么样的结论。例如,散点图显示了两个连续变量之间关系的本质。
- 具体项目解读:是关于我们从剧情中学到了什么。例如,花瓣长度和花瓣宽度散点图显示正线性关系。因此,它们可以被认为是区分鸢尾和其他两类的良好特征。
在本系列的下一章中,我们将学习其他类型的可视化,如小提琴图、普通 q-q 图、蜂群图、热图和箱线图。我已经使用 python 中的 seaborn 库在这个数据集上实现了探索性数据分析。您可以访问我的 Kaggle 笔记本“ Iris:探索性数据分析”进行代码演练。
就这些了,伙计们。快乐学习。
解读回归系数:希望我以前就知道
回归估计量的直观解释

在 Unsplash 上由 Samia Liamani 拍摄的照片
如果你学过应用计量经济学/统计学/数据科学的入门课程,你必须熟悉使用回归估计的线性模型中预测因子系数的解释。今天我要分享一个直观的例子来强调这样一个事实——就像这个作者——你(大概)带着对兴趣系数的误解通过了入门课程/课程。
** 这篇文章是我上一篇文章的延续,我将使用相同的玩具示例(不同的数字)。万一你错过了,你可以考虑快速浏览一下。然而,阅读是可选的,因为我要重复必要的部分。**
假设的例子
上个月,一家连锁杂货店 XYZ 向所有光顾其 ABC 市商店的顾客提供购物卡。有些顾客接受了这张卡,有些则没有。该连锁店在其他 10 个城市有商店,在其他城市引入该卡之前,他们试图了解接受购物卡在影响每月消费方面的效果如何。你被聘为分析师来调查这个问题!
首先,你意识到购物卡既不是随机提供的,也不是随机接受的。由于您接受过因果建模培训,因此您担心混淆变量,即影响治疗(接受购物卡)和结果(每月在商店消费)的因素。
你对接受卡片的人(治疗组)和不接受卡片的人(对照组)的特征做一些背景研究。仔细探索之后,你发现收入是两个群体唯一的区别。高收入的顾客更有可能接受购物卡;此外,根据你的先验知识,你知道高收入顾客更有可能在商店花费更多。你有信心做出这样的假设:除了收入,这两个群体平均来说是相同的。

让我们看看数据集。为了简单起见,我们假设只有三个收入群体:50k,75k 和 100k。

这个假数据集有 400 个观察值,但您只能关注这 16 个观察值。我将这 16 个观察值复制粘贴了 25 次,使样本量达到 400,这样我就得到统计上显著的结果:D
手动估计平均因果效应
(Angrist and Pischke,2009) 在他们著名的计量经济学教材《大多无害计量经济学》中提到了以下关于平均待遇效应(ATE)的表述:

** 我在本文中使用术语平均因果效应来指代相同的估计需求(即我试图估计的东西)**
在我们的例子中, delta_x 是在 X_i (收入类别)的每个值下接受卡的人和不接受卡的人之间的平均月支出的差。 P(X_i=x) 是 X_i 的概率质量函数(即三个收入类别中每一个的总回答者比例)。
简而言之,你要做以下事情:
- 在三个收入类别( X_i )的每一个中,计算接受卡的人和不接受卡的人之间的平均月支出的差异( delta_x) )。
- 将每个特定收入类别的差异( delta_x) 乘以该特定类别中总受访者的比例P(X _ I = X),并将它们相加得到一个估计值。

= 10.75 (5/16)-20 (6/16)+18.75(5/16)=1.72
太好了!但是,正如我们在上一篇文章中所讨论的,我们更喜欢使用回归来估计平均因果效应,因为我们不仅想得到点估计,还想得到标准误差(也因为我们很聪明🤓)。
使用回归估计平均因果效应
您使用回归估计以下传统模型:
月支出= B0+****B1*购物卡受理+ b2Income50 +b3Income75+ e
其中购物卡接受度是一个虚拟变量,如果顾客接受该卡,则取值 1,否则取值 0;Income50 是一个虚拟变量,如果客户的收入为 5 万美元,则取值为 1,否则取值为 0;Income75 是一个虚拟变量,如果客户的收入为 7.5 万美元,则取值为 1,否则取值为 0。并且, e 是误差项,其包括每月花费的所有其他原因;重要的是,这些其他原因对顾客是否接受购物卡没有任何影响(因为你假设它只取决于你已经在模型中考虑的收入)。最后,在这个常规模型中, b1 是的回归估计量的平均因果效应。
******* 我会这样解读 B1;否则,在这种特定的环境下使用回归有什么意义呢?😕 *****
这是我使用回归估计上述模型时得到的结果:

天哪!😲
虽然真实的平均因果效应为 1.72,但是的回归估计平均因果效应为 -2.06,尽管控制了混杂变量!
让我们暂时忘记因果关系。
让我们假设你正在做一项描述性/相关性研究。在这种情况下,您会得出以下一个或两个结论:
- 在控制收入的情况下,购物卡的接受度与月支出呈显著负相关。
- 在控制收入的情况下,平均来说,接受购物卡的顾客比不接受购物卡的顾客每月花费少 2.06 美元。这种差异在 5%的显著性水平上具有统计显著性。
猜猜看!你的(描述性的/ 相关性的)结论,基于回归估计,将是明显错误的。而且,你的老板——他相信数据驱动的决策——会将这一结论解释为购物卡对每月支出有整体负面影响的证据。🥺**
了解回归估计量
现在,让我们试着理解为什么回归估计与真正的平均因果效应相差如此之大。
(Angrist 和 Pischke,2009) 提到普通最小二乘(OLS)回归使用不同的权重*来估计平均因果效应。回归估计量可以表示为:*

*** 在我们的案例中,D 是待遇(购物卡接受度),X 是混杂变量/协变量(收入)***
让我们使用这个公式,看看我们是否得到与回归(即-2.06) 相同的估计值。我们使用以下步骤:
(1) 计算收入类别权重:

(2) 计算 增量 _ 回归 :
delta _ Regression=(10.75 * 0.05+20 * 0.09375+18.75 * 0.05)/(0.05+0.09375+0.05)=-2.06
很有趣,不是吗?🤔
根据 (Angrist 和 Pischke,2009),“回归将最大的权重放在治疗状态的条件方差最大的协变量细胞上。”因此,基本上,回归估计器估计一个方差加权平均因果效应,这是我们不希望看到的* 🥺.在我们的示例中,三种收入类别中购物卡接受度的差异为:***

*** 我用的是 VAR。excel 中的 s 函数***
由于收入等于 75,000 美元的组在购物卡接受度方面的差异最大,因此收入等于 75,000 美元的组中接受购物卡的人与未接受购物卡的人之间的月支出差异获得了最高权重。**
这种加权的结果是什么?在我们的例子中,5 万美元、7.5 万美元和 10 万美元组的平均因果效应分别为 10.75、-20 和 18.75。在回归估计量中,7.5 万美元组的权重最高,这解释了为什么我们使用回归得到了治疗变量(购物卡接受度)的负系数(尽管真正的平均因果效应是正的)。
有什么补救措施?
(和康,2008) 提到了解决这个问题的办法。它包括以下内容:
【1】通过从每个协变量中减去该协变量的样本均值,使所有协变量居中。这可以通过用(X_i - E(X_i))替换向量 X_i 来一次对所有协变量完成。**
【②】计算治疗变量与各居中协变量的乘积。**
基于以上所述,在我们的例子中,我们必须估计下面的修正模型:
月消费= B0+****B1*购物卡受理+B2 * c _ income 50+B3 * c _ income 75+B4 购物卡受理 c _ income 50+b5 购物卡受理c_Income75 + e
重要的是,c_Income50 和 c_Income75 以样本均值为中心。在这个修正模型中, b1 是平均因果效应的回归估计量。
现在,我使用回归估计上述模型,并得到以下结果:

令人惊异的🤩我们调整了我们的模型,找到了一种通过回归得到真实平均因果效应的方法!
在 R 中实现它
如果你认为如果你有许多协变量,实现居中将是一项艰苦的任务,这里有一些好消息!我们可以使用margin effects包在 R 中轻松实现整个过程。代码如下:
*library(dplyr)
library(marginaleffects)
library(readr)**#Import the dataset**
data <- read_csv("[https://raw.githubusercontent.com/vivdas92/Medium-Blogs/main/regression_toy_example_data.csv](https://raw.githubusercontent.com/vivdas92/Medium-Blogs/main/regression_toy_example_data.csv)")**#Estimate the model**
model<-lm(Monthly_Spending~Shoppers_Card+Income50+Income75+Shoppers_Card:Income50+Shoppers_Card:Income75,data=data)
summary(model)**#Average Causal Effect**
marginaleffects(model, variables = "Shoppers_Card") %>%
summary()*
以下是 R 输出:

回归估计量何时估计平均因果效应?
从上面的例子中,我们可以得出两个观察结果:
(1) 常规回归估计量估计真实的平均因果效应,如果在每个水平的添加协变量内的效应( delta_x )是相同的(同质处理效应)*。在我们的例子中,如果这三个收入类别的影响都是 10,那么:*
平均因果效应=10 * 0.3125+10 * 0.375+10 * 0.3125 =10
delta _ Regression=(10 * 0.05+10 * 0.09375+10 * 0.05)/(0.05+0.09375+0.05)=10
因此,如果你愿意假设加入模型的协变量水平的处理效果是同质的,那么你可以声称传统的回归估计器估计了真正的平均因果效应。
(2) 如果添加协变量的每个水平内治疗状态的方差相同*,常规回归估计器估计真实的平均因果效应。例如,让我们看看下面的数据集:*

有了这些数据,如果你使用回归估计没有交互作用的传统模型,你得到真实的平均因果效应(18.11)。有趣的是,这里的因果关系在不同的收入类别中并不一致;然而,购物卡接受度的方差在所有三个收入类别中都是相同的(0.3)。
一般来说,这意味着如果满足两个条件中的任何一个,常规回归估计量就会估计真实的平均因果效应:
(1)治疗效果同质性
(2)协变量水平内治疗状态的等方差
我们可以从上面的数据集得到的另一个有趣的观察结果是,它看起来完全像块随机实验数据(或完全平衡的数据)。显然,在这里,收入甚至不是一个混淆因素!为什么?因为这三种收入类别接受购物卡的可能性都是一样的。这意味着,如果你不愿意用相互作用来估计模型,那么你必须找到一种方法,在你的非实验数据集中达到这种平衡。实现平衡分析样本的两种流行方法是(1)倾向得分匹配和(2)逆概率加权。
最后的想法
回归一直是分析师工具箱中一个令人惊奇的工具,而且在未来也将继续如此。尽管有这些限制,但如果建模得当,回归可能是许多经验背景下最合适的估计技术(尤其是当你试图估计做某事的平均因果效应时)。
最后,我要重申几个方法论者多年来推荐的一个要点。✨并不把任何实证方法视为灵丹妙药,而是希望🙏🏽它会像预期的那样自动工作——在任何分析的开始,问我们自己这两个问题是一个好主意:1)我们试图估计什么,以及 2)我们正在估计我们相信我们正在估计的东西吗?一旦我们搞清楚了这些问题的答案,就要据此制定分析计划!
*除非特别注明,所有图片均为作者。这些是来自 MS Excel,MS Word,和 r 的截图。
**您可以找到编制好的 excel 文件和。本文使用的 csv 文件如下:【https://github.com/vivdas09/Medium-Blogs *
如果你想阅读我以前关于如何尝试了解未知的的一些帖子,这里有一些建议:
*https://vivdas.medium.com/how-to-explore-the-effect-of-doing-something-1d5f0e70a778 https://medium.datadriveninvestor.com/does-money-buy-happiness-4ad92109c303 [## 金钱能买到幸福吗?
探索这个问题的实用教程](https://medium.datadriveninvestor.com/does-money-buy-happiness-4ad92109c303)
参考
安格里斯特博士和皮施克博士(2009 年)。大多无害的计量经济学:一个经验主义者的伴侣。普林斯顿大学出版社。
、李俊林、康(2008)。非随机研究的平均因果效应:实践指南和模拟实例。心理方法, 13 (4),279。*
解释 ROC 曲线和 ROC AUC 进行分类评估
我多么希望我第一次学 ROC 曲线的时候就有人教我

原文来自威尔·弗朗西斯上的 Unsplash
评估分类器预测能力的一个非常有效的方法是绘制 ROC(接收器操作特性)曲线。
这是众所周知的,但你知道如何解读 ROC 曲线吗?
ROC 曲线直觉
这条曲线通过绘制两个变量向我们展示了每个阈值的分类器行为:真阳性率(TPR)和假阳性率(FPR)。
真实阳性率通常被称为回忆/敏感度,定义为:

而假阳性率定义为:

在下图中,我们展示了给定数据集的逻辑回归模型的输出。当我们将阈值定义在 50% 时,没有实际的正观测值会被归类为负,所以 FN = 0,TP = 11,但是 4 个负例会被归类为正,所以 FP = 4,15 个负观测值被归类为负,所以 TN = 15。

改编自谷歌开发者
当我们将阈值移动到 75% 时,只有阳性观测值会被归类为阳性,所以 TP = 7,FP = 0,而所有阴性观测值都会被归类为阴性,TN = 19。我们仍有 4 个阳性观察结果被归类为阴性,因此 FN = 4。
我们可以计算每个阈值的 TPR 和 FPR,并对它们进行比较:

最佳阈值取决于模型的目标。如果更重要的是将所有的正面分类为正面,即使这意味着将一些负面分类为正面,50%的阈值更好(见下面的例子)。
1)汽车故障预测——高召回,低精度
假设您为一家从汽车中收集数据的汽车制造商工作,您的模型试图预测汽车何时会发生故障,以便提醒客户前往修理厂进行检查。
在这种情况下,你可能想要一个高召回,这意味着所有有潜在缺陷的车主将被警告进行检查。然而,通过最大化召回,我们也可能向不太可能很快损坏的汽车发送警告(误报),从而降低精确度。假阳性汽车的车主将面临一个小小的不便,即去修理店却发现他的汽车很好,但另一方面,大多数可能损坏(甚至可能导致事故)的汽车情况都包括在内。
我们减少了 FN(提高了召回率),但增加了 FP(降低了准确率)。
现在,如果我们希望有一个对每个被分类为正面的观察都有高置信度的模型,即使这意味着将一些正面的观察错误分类为负面的,75%的阈值是最佳选择(参见下面的选股示例)。
2)选股预测——低召回、高精度
这里你是一个股票交易者,希望建立一个模型来帮助你挑选股票。这个模型将把高收益概率的股票归类为正数。
在这种情况下,你希望只买最好的股票,因为你的钱有限,你不想承担太大的风险。这种情况下,你要提高精确度,只挑选最有可能产生回报的股票,即使这意味着一些好的股票可能会被遗漏(假阴性)。
通过只挑选最好的,我们减少了假阳性(并提高了精确度),同时接受增加假阴性(并减少召回)。
解读 ROC 曲线
ROC 曲线的目的是显示该模型对于每个可能的阈值的效果,作为 TPR 与 FPR 的关系。所以基本上,为了绘制曲线,我们需要计算每个阈值的变量,并绘制在一个平面上。
在下图中,绿线代表 TPR = FPR,而蓝线代表分类器的 ROC 曲线。如果 ROC 曲线正好在绿线上,说明分类器的预测能力和抛硬币一样。

作者图片
在左图中,蓝线相对靠近绿线,这意味着分类器是坏的。最右边的图显示了一个好的分类器,ROC 曲线靠近轴,而“肘”靠近坐标(0,1)。中间的一个是足够好的分类器,更接近于从真实世界数据中可能得到的结果。
解释 ROC 曲线的另一种方式是考虑类别的分离,我们可以用直方图来说明,如下所示。

作者图片
坏的分类器(左)有太多的类别重叠,因此无法做出好的预测,并且没有阈值能够分离类别。正如预期的那样,好的分类器(右)几乎没有重叠,因此我们可以很容易地找到一个好的阈值来分离正确类别中的预测。最后,中间的一个处于中间位置:有一些重叠,但通过相应地设置阈值可以获得良好的结果。
ROC AUC
现在你知道 ROC 曲线有多有用了,但是怎么评价呢?答案是:曲线下面积(AUC)。
AUROC 曲线(ROC 曲线下的面积)或简称为 ROC AUC 得分,是一种允许我们比较不同 ROC 曲线的指标。
绿线是下限,该线下的面积是 0.5,完美的 ROC 曲线的面积应该是 1。我们模型的 ROC AUC 越接近 1,就越有利于分类和做出更好的预测。
我们可以使用 sklearn 轻松计算 ROC AUC:
from sklearn.metrics import roc_auc_score
score = roc_auc_score(y_real, y_pred)
print(f"ROC AUC: {score:.4f}")
输出是:
ROC AUC: 0.8720
当使用y_pred时,ROC 曲线将只有“1”和“0”来计算变量,因此 ROC 曲线将是一个近似值。为了避免这种影响并获得更准确的结果,在计算 ROC AUC 时,建议使用y_proba并获得类别“1”的概率:
score = roc_auc_score(y_real, y_proba[:, 1)
print(f"ROC AUC: {score:.4f}")
输出是:
ROC AUC: 0.9271
从头开始绘制 ROC 曲线
我认为理解一个概念的最好方法是通过实验,所以让我们从头开始学习如何绘制 ROC 曲线。稍后,我将展示如何使用 sklearn 库轻松做到这一点。
您可以在我的 github 存储库中找到可用的代码,所以可以跳过这一部分。
https://github.com/vinyluis/Articles/tree/main/ROC Curve and ROC AUC
首先,我们需要在数据集中训练一个分类器模型:
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB# Split train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.25)
# Create the model object
model = GaussianNB()
# Fit the model to the training data
model.fit(X_train, y_train)
# Predict the classes on the test data
y_pred = model.predict(X_test)
# Predict the classes on the test data, and return the probabilities for each class
y_proba = model.predict_proba(X_test)
然后,我们定义一个函数,根据前面给出的公式计算每个实例的 TPR 和 FPR。
from sklearn.metrics import confusion_matrixdef calculate_tpr_fpr(y_real, y_pred):
# Calculates the confusion matrix and recover each element
cm = confusion_matrix(y_real, y_pred)
TN = cm[0, 0]
FP = cm[0, 1]
FN = cm[1, 0]
TP = cm[1, 1]
# Calculates tpr and fpr
tpr = TP/(TP + FN) # sensitivity - true positive rate
fpr = 1 - TN/(TN+FP) # 1-specificity - false positive rate
return tpr, fpr
我们希望评估每个阈值的 TPR 和 FPR,因此我们定义了一个函数,该函数将创建“n”个阈值,并在这些阈值上迭代,计算变量并将它们存储在一个列表中。这些将是 ROC 曲线点的坐标。
在二元分类器中,预测可以是“0”或“1”,并且移动阈值不会有任何影响。为了确保我们能够得到正确的曲线,我们需要使用将每个观察分类为“1”类的概率,我们通过model.predict_proba(X_test)方法得到这些概率。
def get_n_roc_coordinates(y_real, y_proba, n = 50):
tpr_list = [0]
fpr_list = [0]
for i in range(n):
threshold = i/n
y_pred = y_proba[:, 1] > threshold
tpr, fpr = calculate_tpr_fpr(y_real, y_pred)
tpr_list.append(tpr)
fpr_list.append(fpr)
return tpr_list, fpr_list
最后,我们可以使用 seaborn 绘制点和曲线,方法是将 tpr 和 fpr 列表传递给下面的函数:
import seaborn as sns
import matplotlib.pyplot as pltdef plot_roc_curve(tpr, fpr, scatter = True):
plt.figure(figsize = (5, 5))
if scatter:
sns.scatterplot(x = fpr, y = tpr)
sns.lineplot(x = fpr, y = tpr)
sns.lineplot(x = [0, 1], y = [0, 1], color = 'green')
plt.xlim(-0.05, 1.05)
plt.ylim(-0.05, 1.05)
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
结果是一个相当好的 ROC 曲线,使用直线作为没有计算坐标的线段的近似值。
# Calculates 10 coordinates of the ROC Curve
tpr, fpr = get_n_roc_coordinates(y_test, y_proba, resolution = 10)
# Plots the ROC curve
plot_roc_curve(tpr, fpr)

用 Scikit-Learn 绘制 ROC 曲线
当然,您不会每次需要时都从头构建 ROC 曲线,所以我将展示如何用 scikit-learn 绘制它。
看看有多简单:
from sklearn.metrics import roc_curve
from sklearn.metrics import RocCurveDisplaydef plot_sklearn_roc_curve(y_real, y_pred):
fpr, tpr, _ = roc_curve(y_real, y_pred)
roc_display = RocCurveDisplay(fpr=fpr, tpr=tpr).plot()
roc_display.figure_.set_size_inches(5,5)
plt.plot([0, 1], [0, 1], color = 'g')# Plots the ROC curve using the sklearn methods - Good plot
plot_sklearn_roc_curve(y_test, y_proba[:, 1])# Plots the ROC curve using the sklearn methods - Bad plot
plot_sklearn_roc_curve(y_test, y_pred)
roc_curve函数计算所有 FPR 和 TPR 坐标,而RocCurveDisplay将它们用作绘制曲线的参数。线条plt.plot([0, 1], [0, 1], color = 'g')绘制绿色线条,可选。
如果用model.predict_proba(X_test)[:, 1]的输出作为参数 y_pred,结果就是一条漂亮的 ROC 曲线:

但是,如果直接使用model.predict(X_test)的输出,该方法将没有构建所有点的所有必要信息,并且绘图将是两条线段的近似:

结论
本文的结论是:ROC AUC 最终是二元分类器中类之间分离的度量。我希望在我开始作为一名数据科学家的旅程时,这是如何向我解释的,我希望这将对本文的所有读者产生影响。
如果你喜欢这个帖子…
支持我一杯咖啡!
给我买杯咖啡!
如果你喜欢这个主题,可以看看我解释 ROC 曲线用于多类分类的文章:



浙公网安备 33010602011771号