TowardsDataScience-博客中文翻译-2022-三十三-
TowardsDataScience 博客中文翻译 2022(三十三)
迭代加权最小二乘法和 GLMs 解释
原文:https://towardsdatascience.com/iterated-reweighted-least-squares-and-glms-explained-9c0cc0063526
用 Python 实现了详细的实现

图片由蒂内·伊万尼奇在 Unsplash 上拍摄
广义线性模型 (GLM)是一种回归模型,我们对通常的线性回归模型的线性假设进行广义化。由于这种非线性,估计回归参数不会像估计线性回归参数那样简单。
迭代加权最小二乘 (IRLS)算法,有时也称为迭代加权最小二乘 (IWLS),是一种找到广义线性模型的最大似然估计的方法。它是加权最小二乘法的扩展。先来一个简短的背景介绍。
背景
在线性模型中,我们可以使用正规方程来估计回归的参数,

普通最小二乘估计
这种方法在均值为零和方差不变的情况下会有误差。

线性误差分布
如果预测值和被预测值之间的关系不是线性的,那么如果我们坚持使用上面的正规方程,我们将会得到具有非恒定方差的误差。

非线性误差分布
这种结果并不理想。为了更好的可预测性,我们希望有恒定的误差方差。通过一些我不会展示的数学操作,我们可以使用加权最小二乘法将误差分布转化为标准的多元正态分布。

加权最小二乘估计
由于特定的原因,我们仍然不能将它用于 GLMs。GLM 的 y 变量不同于预测变量。考虑下面的数据(我们也将用于代码)。
这里我们看到 y 变量是一个计数变量,表示成功的次数。对于我们的情况,我们将最大成功数保持为 1,这是一个逻辑回归问题。二项式(和逻辑)回归不预测实例的成功次数,而是预测成功的概率。y 变量和所需预测变量之间的差异是有问题的,因为我们无法将 y 变量代入加权最小二乘方程。为了解决这个问题,我们引入了 IRLS 算法。但是在我们跳到算法之前,我需要解释一下 GLM 的基础知识。
广义线性模型(GLM)
顾名思义,GLMs 是线性回归的概括,其中预测变量通过字母g表示的链接函数与线性模型相关。

GLMs
如果你预测的变量是有限域的,那么链接函数就很方便。例如,如果您想将概率建模为您的响应变量。如你所知,概率的值只能在 0 和 1 之间。普通的线性回归不能满足这种定义域限制。因此我们引入了链接函数。下面的形式通常更容易理解。

GLMs
这里的μ是要预测的概率。但是,通过适当选择链接函数,您可以使用它来模拟具有任何其他域限制的任何其他变量。
注。链接函数必须是可微的。
先说链接功能。对于我们的例子,我们需要找到一个将无限域转换成[0, 1]域的函数。很多函数都有这个属性。我将向您展示这种转换最流行的链接函数。

Logit 函数
上面的函数就是著名的 logit 函数。logit link 函数适用于预测概率的二元 y 变量。
注。仅当 y 变量来自指数族分布时,glm 才有效。
指数族
逻辑回归有一个值为 1 或 0 的二元 y 变量。我们可以说这个数据来自一个具有非常数概率参数的伯努利分布。这个概率就是我们试图模拟的。因为伯努利或二项式分布来自指数族,所以我们可以通过 GLM 对此建模。但是这个指数族是什么呢?
如果一个分布的密度或质量函数可以用下面的形式来表示,那么这个分布就属于指数族。

指数族
我将展示如何以这种形式表示二项分布。

证明二项分布是指数族的一部分
还有其他方法来表示指数族的参数,因为它们不是唯一的。我花了这么长时间来解释指数族,因为我们需要一个参数的方差函数的概念。

方差函数
我知道这很难找到,所以我将提供一个更简单的方法来推导方差函数。本质上,方差函数是一种用均值表示分布方差的方法。

方差定义
二项式分布的均值是np,而方差是np(1-p)。从上面的推导中我们知道a(ϕ) = 1,我可以把我的方差函数表示如下。

二项分布的方差函数
如果我们用np代替μ,我们将得到方差np(1-p)。假设在正态分布的例子中,均值和方差之间没有直接的联系。在这种情况下,您必须使用公式手动找到方差函数。
迭代加权最小二乘法
我们从定义一个“新”回归问题开始,

等式 1。新回归
如果我们知道误差的方差,就可以估计参数。误差的方差可通过以下公式获得。

等式 2。误差方差
注意,对于 GLMs,a(ϕ)项可以完全忽略,因为它将在下面的计算中抵消。
从这里,我们可以使用加权最小二乘法来估计参数。这就是a(ϕ)抵消的地方。

等式 3。加权最小二乘法
使用当前的参数估计,我们找到了μ的新值,

等式 4。查找μ
回想一下,我们定义的z和Σ是μ的函数,μ是β的函数,β是z和Σ的函数。我们通过迭代这个递归关系来估计参数。
如果对数似然的增加不再显著,则算法停止。

等式 5。逻辑回归的对数似然。
注意,这个对数似然方程只对逻辑回归有效。对于其他分布,公式将取决于所选的链接函数。
我们来编译一下。迭代加权最小二乘算法;
- 在定义的域内初始化
μ。我将用一个0.5概率数组进行初始化。 - 给定
μ的当前值,使用等式 1 和 e 等式 2 计算z和Σ。 - 给定
z和Σ的当前值,使用加权最小二乘法公式计算β;e 第三季。 - 给定
β的当前值,使用等式 4 计算μ。 - 如果对数似然的增加(等式 5 )小于预先指定的ε,则停止。否则,返回步骤 2 。
Python 代码
让我们把所有的拼图拼在一起。首先是链接功能。
接下来,我们的算法部分,
这是我的算法代码。我将这些部分与上面概述的步骤相匹配。请尝试匹配上面的每个等式。
注意,参数中的X需要一个截距列。运行下面一行代码,
b = IWLS(X, y)
会将参数估计值返回给b变量。
IWLS completed on 8 iterations
array([1.10999575, 9.12479957, 2.18745957])
我们可以利用这些信息来预测成功的可能性。
phat = ig(X @ b)
yhat = np.where(phat >= 0.5, 1, 0)
这里phat存储概率预测,而yhat存储二项式预测。
比较
据我所知,Python scikit-learn库没有 IRLS 解算器,所以我们无法将我们的结果与他们的进行比较。但是幸运的是,R faraway软件包使用 IRLS 作为他们的 GLM 解算器。我们来对比一下。
运转
summary(model)
会回来,
Call:
glm(formula = Success ~ ., family = binomial, data = df)Deviance Residuals:
Min 1Q Median 3Q Max
-1.72517 -0.12458 -0.00007 0.48620 1.07683Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) 1.110 1.283 0.865 0.387
Height 9.125 8.489 1.075 0.282
Weight 2.187 2.099 1.042 0.297(Dispersion parameter for binomial family taken to be 1)Null deviance: 13.8629 on 9 degrees of freedom
Residual deviance: 5.6807 on 7 degrees of freedom
AIC: 11.681Number of Fisher Scoring iterations: 8
回想一下我们之前的输出,
IWLS completed on 8 iterations
array([1.10999575, 9.12479957, 2.18745957])
这是精确的参数估计和迭代次数。我不知道他们使用的实际停止条件,但是看起来我们的方法非常符合。
恭喜
您已成功完成这篇文章。查看我在 GitHub 上的完整代码,了解如何正确地预处理数据并在实际应用中使用我们的函数。请在评论中分享你的反馈和想法。感谢您的阅读!
itertools 和 functools:两个 Python 独行侠
原文:https://towardsdatascience.com/itertools-and-functools-two-python-lone-soldiers-7d3400495c89
Python 很酷的一点是它对函数式编程的支持,函数式编程声明处理步骤是通过函数来完成的。

幸运的是,Python 附带了成熟的包,从而增强了它的多范式制胜牌,其中一些(如果不是全部的话)最初是用 c 实现的。实际上,您可以在 Github Python 项目的 CPython repo 中的 Lib / Modules 文件夹中阅读这些实现。
在本文中,我将讨论 Python 为我们提供的两个函数式编程模块,用于执行各种基于函数的计算:itertools and functools。
我不会复制你在官方文档中发现的相同模式。我只是想提到一些你在互联网上不常遇到的功能,但不知何故在我最需要的时候设法在那里拯救了我。
带着我对他们的感激之情,让我们开始吧。
1.Itertools
简单地说,itertools允许高效的循环。
该模块标准化了一组快速、内存高效的核心工具,这些工具可以单独使用,也可以组合使用。它们一起形成了一个“迭代器代数”,使得用纯 Python 简洁高效地构造专用工具成为可能。
1.1.循环
我记得我在哈斯克尔身上看到过类似的东西。很明显,需要用 Python 实现一个类似的构件。
无论何时你想运行一个无限循环的循环,这个循环会在一个异常或特定的条件下停止,这就是你要做的。
简单有效:
>> from itertools import cycle
>> for el in cycle(range(10)): print(el, end="")
>> 0123456789012345 ...
>> ..
最终,如果你不中断地运行你的循环,你的内存缓冲区会发生一些事情。不知道,没试过。
1.2.积聚
如果您想用尽可能少的代码行设计某种累加器:
>> from itertools import accumulate
>> list(accumulate(range(10)))
>> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
>> ...
基本上,累加器中的每个元素都等于所有先前元素的总和,包括它自己。
就我个人而言,我会自豪地用至少 6 行代码来实现纯内置函数。然而,由于几乎每个数据帧聚合中都需要一个合适的累加器,您可能会喜欢它的工作方式。
1.3.分组依据
实际上,在我的几个项目中,我用它获得了很多乐趣。这里有一个例子:
>>> from itertools import groupby
>>> data = 'AAAABBBCCDAABBB'
>>> for k, v in groupby(data):
... print(k, len(list(v)))
...
A 4
B 3
C 2
D 1
A 2
B 3
它的操作与numpy.unique函数大致相似,但功能更多。
每次 key 函数值发生变化,都会创建一个 break 或一个新组。这与SQL的GROUP BY形成对比,后者将相似的数据分组,而不考虑顺序。所以在将数据传递给itertools.groupby之前,首先对数据进行排序是很重要的。
1.4.成对地
当你在一个 iterable 上循环时,你对单独处理每个元素不感兴趣。**Python 3.10**为更有趣的用例提供了一个新特性。
这里有一个成对的例子:
>>> from itertools import pairwise
>>> list(pairwise('ABCDEFG'))
... ['AB','BC','CD','DE','EF','FG']
拥有额外程度的执行迭代总是一种特权和奢侈。通过两个元素的连续块来迭代你的列表允许更多的递增或递减处理。
1.5.星图
如果你曾经使用过 map 来操作一个 iterable,你可以把 starmap 看作是一个 map 操作符,它扩展到了更小的 iterable。让我们看一个小例子:
>>> from itertools import starmap
>>> v = starmap(sum, [[range(5)], [range(5, 10)], [range(10, 15)]])
>>> list(v)
[10, 35, 60]
>>> ...
在本例中,sum应用于每组参数,并生成一个具有相同编号的结果列表。将每个 iterable 看作一个参数而不是一个简单的输入是很重要的,这就是为什么每个 iterable 都被放在括号中,否则就会出现错误。
任何时候你都想拥有一个函数,它可以根据不同的数据组获取参数,**starmap**可能会非常有效,非常有表现力。
1.6.zip_longest
Haskell 中也使用了zip。对于并行迭代是相当有用的。
然而,它期望每个可迭代参数的长度相同。
如果你试着滑过两个不同长度的物体,zip会把长的物体超出的部分剪掉。
zip_longest将较短的可重复项填充到与较长的可重复项相同的长度:
>>> from itertools import zip_longest
>>> list(zip_longest('ABCD', 'xy', fillvalue='-'))
... [('A', 'x'), ('B', 'y'), ('C', '-'), ('D', '-')]
当你最初有一个长度不匹配的时候,这是很实际的。然后,您可以轻松地循环两个或更多的可迭代对象,并确保这将在运行时保持不变。
1.7.产品
假设您有一个矩阵等待更新,您必须先遍历行,然后遍历列(反之亦然)。
你会怎么做:
for i in ncolumns:
for j in nrows :
...
有一种更酷的方法可以用itertools中一个叫做product的特性来复合这两个循环:
>>> from itertools import product
>>> for i,j in product([0,1], [10,11]):
... print(i,j)
...
0 10
0 11
1 10
1 11
**product**沿可迭代对象执行笛卡尔乘积,在矩阵的情况下,您可以替换嵌套循环,如下所示:
>>> for i, j in product(ncolumns, nrows):
...
不利的一面是,这消除了在两个循环之间初始化中间变量或临时变量的可能性。归根结底,这取决于你想要实现什么。
这可能是我能想到的让我的一天更轻松、更酷的每一件事情。我不会在这方面做更多的阐述,因为这个库永远不会出其不意,特别是 itertools recipes :它们是由作者编写的,以充分利用之前看到的函数,这些函数充当了矢量化和高度优化的构建块。在菜谱中,您实际上看到了这些函数在多大程度上有潜力创建更强大的工具,并具有与底层工具集相同的高性能。那里正在发生一些非常有趣的事情,你最好马上去看看...
2 .功能工具
最简单地说,functools是高阶函数的模块:它们将函数作为参数和输出。装饰器、属性、缓存都是高阶函数的例子,它们作为注释放置在函数定义之上。让我们看一些其他的例子。
2.1.部分的
因此,您需要在您的程序中实现一个将被视为一等公民的函数。问题是,它接受了太多的参数,因此在调用时会导致异常。
partial是functools中的一个特性,允许通过给至少一个参数赋值来冻结一部分参数。
让我们考虑下一组数组和权重:
>>> import numpy as np
>>> x1 = np.array([1,2,1])
>>> w1 = np.array([.2, .3, .2])
>>> n1 = 3
>>>
>>> x2 = np.array([2,1,2])
>>> w2 = np.array([.1, .1, .05])
>>> n2 = 3
>>>
然后让我们考虑这个函数,它计算这些数组的加权*均值:
>>> def weighted_means(x1, w1, n1, x2, w2, n2):
... return np.dot(x1,w1)/n1 , np.dot(x2,w2)/n2
我们将该功能付诸实施:
>>> weighted_means(x1, w1, n1, x2, w2, n2)
... (0.3333333333333333, 0.13333333333333333)
假设您想通过冻结x2, w2 and n2来减少变量参数的数量,这样做:
>>> from functools import partial
>>> reduced_weighted_means = partial(weighted_means, x2 = x2 , w2 = w2 , n2 = n2)
然后,您可以使用参数数量减少的新 reduced 函数:
>>> reduced_weighted_means(x1, w1, n1)
... (0.3333333333333333, 0.13333333333333333)
请注意,这是和以前一样的结果,表明该函数的工作方式就好像固定参数是静态输入的一样。当函数在与参数多样性相关的特定约束下使用时,这似乎是一个完美的变通方法。
2.2.部分方法
partialmethod是 partial 的外推,只是它被用作类方法。让我们用一个例子来说明这一点(我承认这是一个非常愚蠢的例子):
让我们测试一下:
>>> mn = ModelName('clustering')
>>> mn.set_cls()
>>> print(mn.algorithm)
>>> 'classification'
>>>
>>> mn.set_regr()
>>> print(mn.algorithm)
>>> 'regression'
该类用于存储字符串,并将 setter 角色委托给两个 partialmethod。set_cls和set_regr的工作方式就好像它们是正确定义的方法,并将algorithm分别设置为不同的值。一个小提示:定义了algorithm属性后,应该没有algorithm.setter。
2.3.单一调度
假设您定义了一个执行一些指令的简单函数,然后决定使它更加通用:
然后,通过使用现在用作装饰器的zen_of_python的register()属性,用额外的实现重载该函数。
您可以根据参数的类型确定不同的实现:
请注意泛型函数在接受不同类型的参数时的行为:
>>> zen_of_python('hello')
... Beautiful is better than ugly.>>> zen_of_python(1)
... There should be one-- and preferably only one --obvious way to do it.>>> zen_of_python(1.0)
... Readability counts.>>> zen_of_python([1, 2, "a"])
... Namespaces are one honking great idea -- let's do more of those!
结果与我们的实现一致。然而,由于通用函数还不知道dict类型的适当实现,它将跳转到默认实现:
>>> zen_of_python(dict())
... Beautiful is better than ugly.
这东西很酷。我知道。
2.4.singledispatchmethod 方法
我们的最后一位客人:singledispatchmethod它可以在类定义中使用。
举个例子也许?
假设您有一个基于您输入的参数类型输出字符串的类:
以一个小演示结束:
>>> gen = Generic()>>> gen.generic_method(1)
...'First case'>>> gen.generic_method(1.0)
>>> 'Second case'>>> gen.generic_method([1,2,3])
>>> 'Third case'
让我们试试dict式:
>>> gen.generic_method(dict(a=1, b=2))
Traceback (most recent call last):
...
NotImplementedError: Never heard of this type ..
如果你在泛型类上声明了另一个类型,你会遇到一个错误。如果你想让它消失,你应该添加一个小而温和的功能来照顾它。
参考
这是从无到有的 NeRF:用 PyTorch 构建一个完整的 NeRF
原文:https://towardsdatascience.com/its-nerf-from-nothing-build-a-vanilla-nerf-with-pytorch-7846e4c45666
一个关于如何在 PyTorch 中构建自己的 NeRF 模型的教程,包含对每个组件的逐步解释

Matthew Tancik 的 3D 模型。作者照片。
【注:本文包括一个谷歌 Colab 笔记本。如果您想继续学习,请随意使用它。]
介绍
NeRF 爆炸
神经辐射场(NeRF)是深度学习和计算机视觉领域中一个相当新的范式。在 ECCV 2020 年的论文“NeRF:将场景表示为用于视图合成的神经辐射场”(该论文获得了最佳论文的荣誉奖)中介绍了这一技术,该技术自此广受欢迎,迄今为止已被引用* 800 次[1]。该方法标志着机器学习处理 3D 数据的传统方式的巨大变化。视觉效果当然也有“哇”的因素,你可以在的项目页面和他们的原始视频(如下)中看到。
NeRF 介绍视频由 NeRF 论文作者之一 Matthew Tancik 创作。
在本教程中,我们将浏览一个 NeRF 的基本组件,以及如何将它们放在一起训练我们自己的 NeRF 模型。然而,在我们开始之前,让我们先来看看什么是 NeRF,是什么让它成为这样一个突破。
什么是 NeRF?
简而言之,NeRF 是一种生成模型,以一组图像和精确的姿态(如位置和旋转)为条件,允许您生成图像共享的 3D 场景的新视图,这一过程通常被称为“新视图合成”不仅如此,它明确地将场景的 3D 形状和外观定义为一个连续的函数,使用它你可以做一些事情,比如通过移动立方体生成 3D 网格。关于 NeRFs,你可能会发现一件令人惊讶的事情:尽管它们直接从图像数据中学习,但它们既不使用卷积层,也不使用变换层(至少不是原始层)。NeRFs 的一个被低估的好处是压缩;在 5-10MB 时,NeRF 模型的权重可能小于用于训练它们的图像集合。
为机器学习应用程序表示 3D 数据的适当方式已经成为多年来争论的主题。出现了许多技术,从 3D 体素到点云到带符号的距离函数(关于一些常见 3D 表示的更多细节,请参见我的 MeshCNN 文章)。它们最大的共同缺点是一个初始假设:大多数表示都需要 3D 模型,这要求您要么使用摄影测量(非常耗费时间和数据)和激光雷达(通常昂贵且难以使用)等工具生成 3D 数据,要么付钱给艺术家为您制作 3D 模型。此外,许多类型的物体,如高反射性物体,“网状”物体,如灌木丛和链环栅栏,或透明物体,都不适合按比例扫描。3D 重建方法通常也具有重建误差,这会导致阶梯效应或漂移,从而影响模型的准确性。
相比之下,NeRFs 依赖于一个古老而优雅的概念,称为光场,或辐射场。光场是描述光如何在整个 3D 体积中传输的函数。它描述了光线穿过空间中每一个 x = (x,y,z) 坐标和每一个方向的方向,描述为 θ 和 ϕ 角或单位矢量。它们共同形成了描述 3D 场景中光传输的 5D 特征空间。受这种表示的启发,NeRF 试图逼*一个从该空间映射到 4D 空间的函数,该空间由颜色 c =(R,G,B)和密度 σ 组成,这可以被认为是光线在该 5D 坐标空间被终止的可能性(例如,被遮挡)。因此,标准 NeRF 是以下形式的函数:F:(x,d)->(c,σ)。
最初的 NeRF 论文使用一个多层感知器来参数化这个函数,该感知器直接在一组具有已知姿态的图像上训练(这些图像可以通过任何基于运动的结构应用程序获得,例如 COLMAP 、Agisoft metashpe、 Reality Capture 或 Meshroom )。这是被称为广义场景重建的一类技术中的一种方法,旨在从一组图像中直接描述 3D 场景。这种方法给了我们一些非常好的特性:
- 直接从数据中学习
- 场景的连续表示允许非常薄和复杂的结构,例如树叶或网格
- 隐含地说明了物理属性,如镜面反射率和粗糙度
- 隐式表示场景中的照明
此后,一系列论文试图通过一些功能来扩展原始图像的功能,如少量拍摄和单次拍摄学习[2,3],支持动态场景[4,5],将光场归纳为特征场[6],从网络上未校准的图像集合中学习[7],结合激光雷达数据[8],大规模场景表示[9],无神经网络学习[10]等等。对于 NeRF 研究的一些很好的概述,请参见 Frank Dellaert 从 2020 年开始的这个很好的概述和从 2021 年开始的另一个概述。
NeRF 建筑
仅仅使用这个函数,还不太清楚如何生成新颖的图像。总的来说,给定一个训练好的 NeRF 模型和一个已知姿态和图像尺寸的相机,我们通过以下过程构建一个场景:
- 对于每个像素,相机光线穿过场景,在( x,d )位置收集一组样本。
- 使用每个样本处的( x,d )点和观察方向作为输入来产生输出(c,σ)值(本质上是 rgbσ)。
- 使用经典的体绘制技术构建图像。
辐射场功能只是几个组件中的一个,一旦结合起来,就可以创建您在前面看到的视频中看到的视觉效果。还有几个组件,我将在本教程中逐一介绍。总的来说,我们将涵盖以下组件:
- 位置编码
- 辐射场函数*似器(在这种情况下,是 MLP)
- 可区分体积渲染器
- 分层抽样
- 分层体积取样
我在这篇文章中的意图是最大程度的清晰,所以我将每个组件的关键元素提取到尽可能简洁的代码中。我使用了 GitHub 用户bmid的原始实现和 GitHub 用户 yenchenlin 和 krrish94 的 PyTorch 实现作为参考。
辅导的
位置编码器
很像 2017 年推出的疯狂流行的变压器模型[11],NeRF 也受益于位置编码器作为其输入,尽管原因不同。简而言之,它使用高频函数将其连续输入映射到更高维度的空间,以帮助模型学习数据中的高频变化,从而产生更清晰的模型。这种方法避免了神经网络对低频函数的偏见,允许 NeRF 表示更清晰的细节。作者参考了 ICML 2019 上的一篇论文,以进一步了解这一现象[12]。
如果您熟悉位置编码器,NeRF 实现是相当标准的。它有相同的正弦和余弦交替表达,是原件的标志。
标准位置编码器实现。
辐射场函数
在原始论文中,辐射场函数由 NeRF 模型表示,这是一种相当典型的多层感知器,它将编码的 3D 点和视图方向作为输入,并将 RGBA 值作为输出返回。虽然本文使用神经网络,但这里可以使用任何函数逼*器。例如,Yu 等人的后续论文“Plenoxels”使用球谐函数的基础来代替数量级更快的训练,同时实现有竞争力的结果[10]。
NeRF 模型有 8 层深,大多数层的特征尺寸为 256。剩余连接位于第 4 层。在这些层之后,产生 RGB 和 σ 值。RGB 值通过线性层进一步处理,然后与视图方向连接,然后通过另一个线性层,最后在输出端与 σ 重新组合。
NeRF 模型,实现为 PyTorch 模块。
可区分体积渲染器
RGBA 输出点在 3D 空间中,因此为了将它们合成为一幅图像,我们需要应用本文第 4 节中等式 1-3 所述的体积积分。本质上,我们沿着每个像素的光线对所有样本进行加权求和,以获得该像素的估计颜色值。每个 RGB 样本通过其 alpha 值进行加权。较高的 alpha 值表示采样区域不透明的可能性较高,因此光线上较远的点更有可能被遮挡。累积积确保了那些更远的点被抑制。
原始 NeRF 模型输出的体绘制。
分层抽样

分层抽样的例子。在这种情况下,从范围[2,6]中收集了 8 个样本。(图由作者创作)
在这个模型中,相机最终拾取的 RGB 值是沿着穿过该像素的光线的光样本的累积。经典的体绘制方法是沿着这条射线累积点,然后积分点,在每个点估计射线传播而不碰到任何粒子的概率。因此,每个像素都需要沿着穿过它的光线对点进行采样。为了最好地逼*积分,他们的分层抽样方法是将空间均匀地分成 N 个箱,并从每个箱中均匀地抽取一个样本。分层采样方法不是简单地以规则的间距抽取样本,而是允许模型对连续的空间进行采样,因此使网络能够在连续的空间内进行学习。
PyTorch 中的分层抽样。
分层体积取样
之前我说辐射场是用多层感知器表示的,我可能撒了点小谎。实际上是用两个多层感知器来表示的!一种在粗略的水*上操作,对场景的广泛结构属性进行编码。另一种是在精细的层次上细化细节,从而能够实现像网格和分支这样的细而复杂的结构。此外,它们接收的样本是不同的,粗模型在整个射线中处理广泛的、大部分规则间隔的样本,而细模型在具有显著信息的强先验的区域中进行琢磨。

分层抽样(蓝色)和分层抽样(红色)的例子。分层采样以均匀的间距(受噪波影响)捕捉整个光线长度,而分层体积采样基于区域对渲染的预期贡献按比例对区域进行采样。(图由作者创作。)
这种“磨合”过程是通过分层体积取样程序完成的。3D 空间实际上非常稀疏,有遮挡,所以大多数点对渲染图像没有太大贡献。因此,对对积分有贡献的可能性高的区域进行过采样更为有益。他们将学习到的归一化权重应用于第一组样本,以创建跨射线的 PDF。然后,他们将逆变换采样应用于该 PDF,以收集第二组样本。该集合与第一集合相结合,并被馈送到精细网络,以产生最终输出。
PyTorch 中的分层抽样。
培养
这篇文章中训练 NeRF 的标准方法大部分是你所期望的,有一些关键的不同。每个网络 8 层、每层 256 维的推荐架构在训练时会消耗大量内存。他们缓解这种情况的方法是将正向传递分成更小的块,然后在这些块上累积梯度。注意与迷你批处理的区别;梯度是在单个小批采样射线上累积的,这些射线可能是成块收集的。如果你没有论文中使用的 NVIDIA V100 GPU 或性能相当的东西,你可能需要相应地调整你的块大小,以避免 OOM 错误。Colab 笔记本使用更小的架构和更适中的分块大小。
我个人发现由于局部最小值,训练 NeRFs 有些棘手,即使选择了许多默认设置。一些有帮助的技术包括在早期训练迭代和早期重新启动期间的中心裁剪。随意尝试不同的超参数和技术,以进一步提高训练收敛。
结论
辐射场标志着机器学习从业者处理 3D 数据的方式发生了巨大变化。NeRF 模型,以及更广泛的可区分渲染,正在迅速弥合图像创建和体积场景创建之间的差距。虽然我们走过的组件可能看起来非常复杂,但受这种“香草”NeRF 启发的无数其他方法证明,基本概念(连续函数逼*器+可微分渲染器)是构建各种适合几乎无限情况的解决方案的强大基础。我鼓励你亲自尝试一下。快乐渲染!
参考
[1] Ben Mildenhall,Pratul P. Srinivasan,Matthew Tancik,Jonathan T. Barron,Ravi Ramamoorthi,Ren Ng — NeRF:将场景表示为用于视图合成的神经辐射场(2020) ,ECCV 2020
[2] Julian Chibane,Aayush Bansal,Verica Lazova,Gerard Pons-Moll — 立体辐射场(SRF):学习视图合成用于新颖场景的稀疏视图 (2021),CVPR 2021
[3] Alex Yu,Vickie Ye,Matthew Tancik,ang joo Kanazawa——pixelNeRF:来自一个或几个图像的神经辐射场 (2021),CVPR 2021
[4]李政奇,西蒙·尼克劳斯,诺亚·斯内夫利,奥利弗·王——动态场景时空视图合成的神经场景流场 (2021),2021
[5]阿尔伯特·普马罗拉,恩里克·科罗纳,杰拉德·庞斯-莫尔,弗朗切斯克·莫雷诺-诺盖尔—D-内尔夫:动态场景的神经辐射场 (2021),CVPR 2021
[6]迈克尔·尼迈耶,安德烈亚斯·盖格— 长颈鹿:将场景表现为合成生成神经特征场 (2021),CVPR 2021
[7]郑飞·匡,凯尔·奥尔谢夫斯基,·柴,,帕诺斯·阿契里奥普塔斯,谢尔盖·图利亚科夫— NeROIC:从在线图像集合中捕获和绘制神经对象,计算研究储存库 2022
[8]康斯坦丁诺·雷马塔斯、廖骏伦、普拉图尔·p·斯里尼瓦桑、
乔纳森·t·巴伦、安德里亚·塔利亚萨基、汤姆·冯克豪泽、维托里奥·费拉里— 城市辐射场 (2022)、CVPR 2022
[9] Matthew Tancik,Vincent Casser,Yan,Sabeek Pradhan,Ben Mildenhall,Pratul P. Srinivasan,Jonathan T. Barron,Henrik Kretzschmar—Block-NeRF:可扩展的大场景神经视图合成 (2022),arXiv 2022
[10] Alex Yu,Sara Fridovich-Keil,Matthew Tancik,Chen,Benjamin Recht,ang joo Kanazawa—Plenoxels:Radiance Fields without Neural Networks(2022),2022(口述)
[11]阿希什·瓦斯瓦尼、诺姆·沙泽尔、尼基·帕尔马、雅各布·乌兹科雷特、利永·琼斯、艾丹·戈麦斯、卢卡斯·凯泽、伊利亚·波洛舒欣— 你只需要关注 (2017),NeurIPS 2017
[12]纳西姆·拉赫曼,阿里斯蒂德·巴拉廷,德万什·阿皮,菲利克斯·德拉克斯勒,林敏,弗雷德·a·哈姆普雷希特,约舒阿·本吉奥,亚伦·库维尔— 关于神经网络的光谱偏差 (2019),第 36 届机器学习国际会议(PMLR)会议录 2019
如果你的分析没有产生“指数价值”的见解,那也没关系

由 Lina Trochez 在 Unsplash 上拍摄的照片
解决数据分析师的一些关键难题
据说分析师是数据故事讲述者。他们的角色是发现并总结"有趣的"事实来回答商业问题。可能是我,但我觉得有一个不成文的期望,数据分析师将总是把这些有趣的事实带到他们的交付物中。
产生“非凡见解”的压力
很多时候,当我做分析,尤其是探索性数据分析(EDA) 时,我觉得有义务去发现有趣的事实——一些新的对产品/业务有用的数据。有时这是无法实现的。无论我如何分割数据,或者应用哪种分析技术,似乎都没有一种直接的模式可以在回答业务问题时带来“指数价值”。

当这种情况发生时,首先我会沉浸在继续调查数据中。我将陷入困境,去探索更多关于我能用它做什么,直到我到达一个点,当我筋疲力尽,厌倦了探索。一旦达到这一点,我将停止进一步探索,只传递发现的见解,这并没有给我带来太多的满足感,因为它们没有我预期的那样有指数价值。
这种“有求必应”的遭遇,常常让我有不安的感觉。我觉得自己没有效率,因为我没有交付预期交付的东西。
沉下情绪,重新思考这些设置后,我意识到我并不是没有效率。我已经对数据进行了统计调查,并总结了它们的特征,这正是我的工作所期望的。
如果没有故事,你就不能讲故事
数据分析师被期望发现并总结“有趣的事实来回答业务问题。一个常见的挑战是,你甚至没有 正确的事实/数据 ,更不用说发现任何见解了。
每个分析项目的第一条规则是确保 你有一个良好的数据基础 来工作。高质量的数据需要准确、完整、及时和相关。
有些时候,数据太少,分析师无法从给定的数据中获得任何价值。太少和太不完整的数据使得综合或发现任何模式或见解变得更加困难。即使出现任何模式,也很可能是一次性的或季节性的。它们是不可靠的,因此基于这些见解构建的任何解决方案或决策也可能是不可靠和不可扩展的。

有问题的数据说明(NaN/缺失值、不正确的值)-作者提供的图片
在开始任何分析项目之前,我们可以事先检查数据的可用性和质量,并确保它们准确、完整、及时和相关。处理有问题的数据集毫无意义——它让事情变得复杂,你不太可能从中获得有意义的东西。
一个不那么有趣的故事仍然是一个故事
分析的主要目标是发现洞见。一些洞见非常有价值——发现可以改变其他事物工作方式的事物;而其他见解没有那么有价值——可能证实了一些已经广为人知的东西。不管怎样,它们都是来自数据集的事实,因此它们都是正确的。
作为一名数据分析师,有从数据集中发掘尽可能多的有趣和有价值的见解的雄心是很自然的。这是一种探索的好奇心,不找到答案是不会停止的。然而,如果你遵循了正确的分析过程和技术,却没有找到“伟大的答案”,重要的是 保持中立的立场,接受数据告诉你的不那么有趣的事实 。
来自分析项目的见解可能与分析师的技能水*有关(分析师在转换数据以真正提取见解方面的熟练程度),但最重要的是它们与数据集本身下面的东西有关。
因此,即使你的分析没有超出这个世界的洞察力,这也不是一个失败的项目。正如托马斯·阿尔瓦·爱迪生在建造电灯泡的无数次失败后所说,“ 我没有失败。 我刚刚发现了一万种行不通的方法。 ”。尽管分析结果是已经预料到的,而不是新的,你仍然在确认它或增加对假设陈述的置信水*中传递价值。

久尼尔·费雷拉在 Unsplash 上拍摄的照片
一个故事对一个人来说可能是有趣的,但对另一个人来说却不是
尽管基于科学方法,分析也是一种艺术形式。它依赖于主观优势和认知偏差。有数不清的方法来转换(分组、排序、过滤)数据,显示数据,也有许多分析/机器学习技术来建模数据。由数据分析师来选择并最好地利用这些工具和技术。
你可能想从多个角度丰富你的分析,采取整体的方法,考虑产品中涉及的不同组件及其相互作用。为了收集新的观点,你可以与其他分析师或与分析相关的利益相关者(如产品经理、研究人员、设计师、运营团队等)进行讨论。同样,您也可以与数据团队成员(数据分析师、数据科学家、机器学习工程师等)进行讨论,探索对数据集建模的其他技术。探索数据科学社区项目(例如 Kaggle)和研究论文也可能有所帮助!
主观性不仅在于应该如何分析数据,还在于分析见解的感知价值。作为一名数据分析师,您每天都在探索数据集,了解领域背景。这种领域背景意味着你对产品/数据了解如此之多,以至于某些见解已经在你的脑海中浮现。
但是对你的利益相关者来说可能不是这样。他们不像你那样深入研究数据,因此你已经知道的一个洞察可能对他们来说是一条新信息。
他们可能不知道 70%的忠实客户只集中在某个地区,或者由于其他产品的推出,出现了季节性销售下降。尽管这些信息对你来说可能并不“非常有价值”,因为你已经知道了它们,但对你的利益相关者来说仍然非常有价值。
结束语
人们对数据分析师有一种不成文的期望,即从他们的分析中产生指数级的价值洞察——这种信息可以激发商业决策的转变。这给数据分析师造成了一些压力,让他们不得不继续分析以找到他们,当他们找不到任何数据时,他们会感到无能。我上面强调的几点是我们如何解决这个问题,这主要与重新构建我们作为数据分析师应该如何看待这个问题有关。
应该让你吃惊的是数据,而不是 P 值
原文:https://towardsdatascience.com/its-the-data-that-should-surprise-you-not-the-p-values-79fce3963d35
你的结论不应该建立在统计显著性测试的基础上,也不应该建立在丹尼尔·塔特尔和 T2·昂斯佩思的基础上

统计学是在不确定性下改变我们想法的领域[1]。我们的思维(即,对某事物的先入之见)通常被表述为一个无效假设,它总结了我们认为知道的东西。然后可以通过收集和分析数据来检验这种无效假设的有效性。如果这个数据足够令人惊讶,我们拒绝零假设。对这些数据感到惊讶需要两件事:
数据必须与我们相信的事实相矛盾
- 当零假设为真时,这种矛盾不太可能是偶然发生的。
- 例如,我们通常假设一个特定的硬币是公*的,即有相等的概率得到正面和反面。举例来说,如果我们现在观察到抛硬币两次时有两个头像,这与我们最初的预期相矛盾。另一方面,这样的结果很可能是公*的(25%的情况)。因此,这些数据不足以令人惊讶地否定零假设。相反,如果我们抛硬币十次,观察十个头像,这个结果可能会非常令人惊讶,因为公*的硬币出现这种结果的几率只有 0.1%。这直接引出了一个不可避免的问题:
确切地说,什么时候数据令人惊讶到足以改变我们的想法,也就是说,什么时候我们应该拒绝零假设?
统计假设检验有一个简单的答案:如果零假设实际上为真,那么产生结果的概率(p)应该小于某个先验定义的显著性水*(ɑ)。根据不同的领域,一个常用的惯例是\ = 5%—因此,任何 p < 5%的发现都被称为具有统计显著性。
当应用于抛硬币的例子时,这个定义变得更加明显。如果我们观察到与这种信念相矛盾的数据和如果硬币实际上是公*的,这种数据在不到 5%的情况下出现的开始,我们将拒绝我们对公*硬币的信念。正如我们从下表中看到的,如果连续观察到四个以上的头像,我们会拒绝我们对公*硬币的信念。换句话说,我们可以说这个发现(即硬币是不公*的)在统计学上是显著的,因为 p < 5%。
作者图片

但是,统计上显著的东西实际上意味着什么呢?让我们假设你会在下一次掷硬币时下相当大的赌注。当之前的四次抛硬币是正面对之前的五次抛硬币是正面时,你的决定会不同吗?可能不会,但你会更有信心下一次掷硬币也是五个正面,对吗?
一个结论不会在分割线的一边立即变为真(或更重要),而在另一边变为假(或不重要)
这实际上是统计显著性测试的一个问题:对许多人来说,统计显著性结果比非显著性结果更有说服力。例如,当科学结果“具有统计学意义”时,更容易发表。因此,许多研究人员试图通过选择创造性的描述,将不重要的发现转化为更有说服力的东西。下面的单词云显示了同行评审期刊文章中的一些常用表达,其中作者设置了 p < 5%的阈值,但未能实现[2]。
图片由作者提供,资料来源于[1]

这种措辞很吸引人,因为它经常描述不存在的结果的一个方面。例如,趋向、接*或趋向显著性不成立,因为 p 值是一个没有任何方向趋势的数字。
重要并不意味着重要
这给我们带来了另一个重要的方面:p 值并不意味着重要性,也不反映效果的大小。
例如,让我们使用逻辑回归调查乳腺癌威斯康星州(诊断)数据集 [4]中恶性或良性肿瘤的预测因子。下表总结了每个预测值的 p 值。
乳腺癌威斯康星(诊断)数据集的每个预测值的 p 值。图片由作者提供。

具有低 p 值的预测因子不可能与反应变量(即恶性或良性肿瘤)无关。仅从这些 p 值,人们可能倾向于判断,例如,半径不如*滑重要,因为它的 p 值更大。然而,p 值并不能得出这些预测因子在多大程度上降低或增加恶性肿瘤发生几率的结论。
要回答这个问题,我们需要分析效应大小:大的绝对效应大小意味着一个研究发现具有实际意义。相比之下,小的绝对效应大小表明实际应用有限。
乳腺癌威斯康星(诊断)数据集的每个预测值的效应大小。图片由作者提供。

在乳腺癌数据集中,效应大小及其不确定性传达了比单独的 p 值更多的信息。例如,我们现在可以得出结论,我们发现了光滑度和恶性肿瘤之间适度联系的有力证据。然而,对于桡骨,我们可以得出结论,我们发现了适度的证据表明桡骨和恶性肿瘤之间存在潜在的密切联系。
因此,尽管我们基于现有数据确信*滑度与肿瘤的恶性程度密切相关,但其影响相对较小。因此,临床上可能更关注桡骨,收集更多数据以减少不确定性。
总之,具有“显著”p 值的小效应可能具有有限的实际重要性。
结论
这让我们回到这篇博文的第一句话(顺便说一句,这是我从 Cassie Kozyrkov 的一篇优秀博文中抄来的。):
不确定性是存在的,并将永远存在。这种不确定性不能仅仅依靠一个单一的值来消除。相反,我们必须接受不确定性,并将它们包含在我们的结论中。美国统计协会关于 p 值的声明总结道:
感谢阅读!
参考文献
[1]不要在统计上浪费你的时间,作者 Cassie Kozyrkovhttps://towardsdatascience . com/what-the-point-of-statistics-8163635 da 56 c
[2]当您的结果只是“几乎显著”时:仍然不显著|可能的误差(wordpress.com)
[3] 美国儿科学会关于 p 值的声明
[4]沃尔伯格,威廉,斯特里特,w .和曼加萨里安,奥尔维。(1995).乳腺癌威斯康星州(诊断)。UCI 机器学习知识库(CC BY 4.0 许可数据集)。
[4] Wolberg, William, Street,W. & Mangasarian,Olvi. (1995). Breast Cancer Wisconsin (Diagnostic). UCI Machine Learning Repository (CC BY 4.0 licensed dataset).
是时候告别 pd.read_csv()和 pd.to_csv()了
原文:https://towardsdatascience.com/its-time-to-say-goodbye-to-pd-read-csv-and-pd-to-csv-27fbc74e84c5
讨论熊猫的另一个重要警告

黑白情缘印花纺织品(来源: Pexels
Pandas 对 CSV 的输入输出操作是串行的,这使得它们非常低效和耗时。当我看到这里有足够的并行化空间时,我感到很沮丧,但不幸的是,Pandas 还没有提供这种功能。虽然我从来不赞成首先用熊猫创建 CSV(阅读我下面的帖子就知道为什么),但我知道可能会有除了使用 CSV 别无选择的情况。
因此,在本帖中,我们将探索 Dask 和 DataTable ,这是数据科学家最喜欢的两个类似熊猫的库。我们将根据 Pandas、Dask 和 Datatable 在以下参数上的表现进行排名:
- 读取 CSV 并获取熊猫数据帧所花费的时间
如果我们通过 Dask 和 DataTable 读取 CSV,它们将分别生成 Dask 数据帧和 DataTable 数据帧,而不是 Pandas 数据帧。假设我们想要坚持传统的 Pandas 语法和函数(由于熟悉的原因),我们必须首先将它们转换成 Pandas 数据帧,如下所示。

描述将 Pandas 数据帧转换为 Dask 和 DataTable 数据帧的代码片段(图片由作者提供)
2.将熊猫数据帧存储到 CSV 所需的时间
目标是从给定的熊猫数据帧生成一个 CSV 文件。对于熊猫,我们已经知道了 df.to_csv() 方法。然而,要从 Dask 和 DataTable 创建 CSV,我们首先需要将给定的 Pandas 数据帧转换成它们各自的数据帧,然后将它们存储在 CSV 中。因此,在本次分析中,我们还将考虑数据帧转换所需的时间。

使用 Pandas、Dask 和 DataTable 将数据帧保存到 CSV 的代码片段(图片由作者提供)
实验设置:
- 出于实验目的,我用 Python 生成了一个随机数据集,包含可变的行和三十列——包括字符串、浮点和整数数据类型。
- 我将下面描述的每个实验重复了五次,以减少随机性,并从观察结果中得出公正的结论。我在下面报告的数字是五次实验的*均值。
- Python 环境和库:
- Python 3.9.12
- 熊猫
- 数据表 1.0.0
- Dask 2022.02.1
实验 1:读取 CSV 花费的时间
下图描述了 Pandas、Dask 和 DataTable 读取 CSV 文件并生成 Pandas 数据帧所用的时间(秒)。CSV 的行数范围从 100k 到 500 万。


描述熊猫、数据表和 Dask 读取 CSV 所用时间的折线图(图片由作者提供)
- 实验结果表明,当行数小于一百万时,Dask 和 Pandas 从 CSV 生成 Pandas 数据帧所用的时间大致相同。
- 然而,当我们超过一百万行时,Dask 的性能会恶化,并且生成 Pandas 数据帧比生成 Pandas 本身要花费更多的时间。
- 在这两种情况下,Datatable 在 Pandas 中生成一个数据帧所需的时间最少,提供了 4 到 5 倍的速度提升——这使它成为目前为止最好的选择。
实验 2:保存到 CSV 所需的时间
下图描述了 Pandas、Dask 和 DataTable 从给定的 Pandas 数据帧生成 CSV 文件所用的时间(秒)。行数范围从 100k 到 500 万。


描述 Pandas、DataTable 和 Dask 将数据帧存储到 CSV 所需时间的折线图(图片由作者提供)
- 在所有情况下,Dask 在将 Pandas 数据帧存储到 CSV 时的表现都不如 Pandas。
- 与实验 1 类似,DataTable 表现最好——将相对于熊猫的保存过程提高了* 8 倍。
结束语
老实说,我不太喜欢 CSV。如果你仔细阅读了我之前的帖子(我在上面链接过,或者你可以在这里阅读)和你现在正在阅读的帖子,你可能也会和我产生共鸣。自从我发现与 CSV 相关的众多问题后,我尽可能地停止使用它们。
总之,我认为除非您需要在非 pythonic 环境(如 Excel)之外查看数据框架,否则根本不需要 CSV。更喜欢像拼花地板、羽毛或泡菜这样的格式来存储你的数据帧。尽管如此,如果您看不到其他选择,至少通过利用 DataTable 而不是 Pandas 来优化您的输入和输出操作。

毒枭迷因由作者使用迷因创造者。
是时候告别熊猫里的合并法了
原文:https://towardsdatascience.com/its-time-to-say-goodbye-to-the-merge-method-in-pandas-b7f39a4a3fb6
为什么我在 Pandas 中停止使用合并方法,为什么你也应该这样做

Alain Pham 在 Unsplash 上拍摄的照片
Pandas 中的merge()方法无疑是数据科学家在数据科学项目中最常用的方法之一。
该方法源于 SQL 中的表连接思想,并扩展到 pythonic 环境中的表连接,它基于一个或多个列中的匹配值合并两个 Pandas 数据帧。
下图对此进行了说明:

连接表的图解概述(图片由作者提供)
merge()方法的直观性质使其成为 Pandas 用户加入数据框架的理想选择。
然而,当谈到运行时,在 Pandas 中有一个相对更好的选择,您应该比merge()方法更喜欢。
你可以在这里找到这篇文章的代码。
让我们探索一下🚀!
合并表的方法
方法 1:使用 merge()
如上所述,在 Pandas 中合并数据帧的传统且最常见的方法是使用merge()方法。
如上面的代码块所示,该方法接受两个数据帧,df1和df2。
此外,我们使用how参数(上例中的"left")指定希望执行的连接类型。
最后,我们用left_on参数指定匹配第一个数据帧(df1)中的值时要考虑的列,用right_on参数指定匹配第二个数据帧(df2)中的值时要考虑的列。
方法 2:使用 join()
join()方法在目标上类似于 Pandas 中的 merge()方法,但是在实现上有一些不同。
join()方法在df2和df1的索引处执行查找。然而,merge()方法主要用于使用列中的条目进行连接。- 默认情况下,
join()方法执行左连接。而merge()方法在其默认行为中求助于内部连接。

索引值连接表(作者图片)
下面的代码块演示了join()方法。
如上所述,join()方法执行索引查找来连接两个数据帧。也就是说,对应于相同索引值的行被合并。
因此,在使用join()方法时,应该首先将希望执行 join 的列设置为 DataFrame 的索引,然后调用 join()方法。
实验装置
为了在 Pandas 中评估merge()方法的运行时性能,我们将把它与join()方法进行比较。
具体来说,我们将创建两个虚拟数据帧,并使用方法merge()和join()执行连接。
这个实验的实现如下所示:
-
首先,我们设置来自
(-high, +high)的整数值。我们将在不同大小的数据帧上比较这两种方法的性能,数据帧的行数来自rows_list,列数来自n_columns。最后,我们将运行每个实验repeat次。 -
create_df方法接受一系列参数并返回一个随机数据帧。 -
在下面的代码中,我们在同一个数据帧
df1和df2上测量merge()方法和join()方法的运行时间。
注意,要使用
join()方法,您应该首先将列设置为数据帧的索引。
结果
接下来,我们来看结果。

Join vs Merge 方法的实验结果(图片由作者提供)
- 蓝色线图描述了
merge()方法的运行时间,黄色线图代表了join()方法的运行时间。 - 我们将行数从 100 万改变到 1000 万,并注意到两种方法的运行时间与行数正相关。
- 然而,
join()方法在运行时间上比传统的merge()方法有了显著的改进。 - 随着行数的增加,两种方法的运行时间之间的差异也会增加。这表明您应该始终使用
join()方法来合并数据帧,尤其是在较大数据集的情况下。
结论
总之,在这篇文章中,我们在一个虚拟数据帧上比较了熊猫的merge()和join()方法的性能。
实验结果表明,就运行时间而言,使用join()方法对索引列进行合并比使用merge()方法更有效——提供了高达 4 到 5 倍的性能提升。
你可以在这里找到这篇文章的代码。
感谢阅读!
🧑💻成为数据科学专家!获取包含 450 多个熊猫、NumPy 和 SQL 问题的免费数据科学掌握工具包。
✉️ 注册我的电子邮件列表 不要错过另一篇关于数据科学指南、技巧和提示、机器学习、SQL、Python 等的文章。Medium 会将我的下一篇文章直接发送到你的收件箱。
是时候为您的数据团队设置 SLA,SLO,SLI 了—只需 3 个步骤
让利益相关者满意的关键指标

克劳迪奥·施瓦兹在 Unsplash 上的照片
F 对于今天的数据团队来说,对实时、准确数据的需求从未像现在这样高,但数据可靠性从未得到充分重视。数据每天都在快速增长,其来源和格式比以往任何时候都多。为了跟上这一趋势,数据团队在管理可扩展的实时数据基础架构和构建不断增长的仪表板方面投入了大量资金,以做出明智的业务决策。做出正确的决策是具有挑战性的,只有在高度可靠的数据下才能做出正确的决策。
以下是几个可靠数据对企业意味着什么的示例:
- 以预期格式向第三方(如监管机构、审计机构)交付准确完整的数据,以满足合规要求。
- 无论何时利益相关者想要检查数据,它总是可用。
- 在上午 9 点之前,当利益相关者查看指标时,刷新仪表板。
- 将准确的指标带到仪表盘上,帮助企业做出正确的决策。
- ……
作为一个成熟的数据团队,你应该从今天开始思考数据的可靠性。这还不是行业中的热门话题,但是它为团队带来了长期价值,因为数据正在成为组织的支柱。这也是你的团队与众不同的地方。
什么是数据可靠性?
巴尔·摩西,蒙特卡洛的联合创始人,是数据可靠性的倡导者。她说:
数据可靠性是组织在整个数据生命周期中提供高数据可用性和健康的能力。—巴尔·摩西
从用户故事中,您可能会对数据可靠性的含义有所了解。我们可以将其分解为几个可测量的指标:
- 可用性—数据是否可用。网络问题或基础设施问题等可能会阻止用户访问数据。
- 新鲜度—数据表的最新程度,以及表更新的节奏。
- 完整性(In)——意外缺失数据条目的百分比,可以是行级别,也可以是列级别。例如,上游表中是否有任何行没有被处理?对于超过 X%的行,目标表中是否有任何字段缺少数据?
- Duplicates 意外重复主键的百分比。
- 分布—某些字段的可接受范围。它可以是一个枚举或一个数字范围。
- 格式—数据的预期格式和模式,如 CSV 或 BigQuery 表以及列的定义。
- 关系——对涉及多个列或表的复杂业务逻辑的任何测试。
我希望一些指标能引起你的共鸣。如果是这样,太好了!现在,让我们把它们变成可操作的步骤。
SLI SLO SLA 简介

由作者创建
测量是实现高数据可靠性的关键。如果没有人监控,前面提到的指标毫无意义,并且临时解决数据问题不是运行数据团队的可持续方式。
当利益相关者在数据工程师之前发现明显的数据问题时,会感到羞耻。
此外,当利益相关者说“数据是错误的”时,每个数据工程师都会问“什么意思..?"
SLA、SLO 和 SLI 是一系列概念,可以帮助数据工程师和利益相关者理解数据发生了什么,以及“错误数据”的确切含义。首先,这里是每个概念的定义。它们非常相关,但又非常不同。
SLA(服务水*协议) —是服务提供商向客户承诺的服务可用性、性能等方面的合同,以及如果他们未能交付可能的补偿。它适用于内部和外部数据消费者。定义 SLA 的一些好处:
- 它通过明确定义数据可靠性的范围以及“可以”和“不可以”的含义,改善了工程师和利益相关者之间的沟通。让它变得非常清楚,可以避免对什么需要关注,什么不需要关注的不必要的讨论。
- 它帮助工程师决定应该在交付特性上花费多少时间,应该在使现有管道更加可靠上花费多少时间。
- 它提供了更多关于数据可靠性的可见性。只有错误会引起注意,而事情进展顺利时却不会,这是不公*的。SLA 帮助团队领导了解他们的数据管道(和团队)有多好,并在整个组织中分享成功。
- 它让涉众对数据(和数据团队)更有信心。借助合适的工具,他们可以查看数据的 SLA 实时状态,而无需反复询问数据是否正常。他们知道这个问题会很快得到解决,不会升级。
SLA 不仅定义了数据可靠性的范围,还定义了错误预算,这是一个防止工程师筋疲力尽的指标,它有助于团队明智地分配时间。例如,从涉众的角度来看,该表应该随时可用并且正确,这意味着 100%的可靠性。但是没有必要,也不可能。随着 0.1%的停机时间,业务不会崩溃。工程师们更愿意提供新的功能和做更多创造性的工作,而不是追求 100%的可靠性。数据团队应该让涉众知道这个事实。
【服务水*目标】 —是服务提供商想要达到的内部目标。通常,它类似于 SLA,但更严格。例如,如果在 SLA 中将数据可用性阈值设置为 99.9%,那么在 SLO 应该是 99.99%。如果一项服务违反了 SLO,随叫随到的工程师需要迅速做出反应,以避免它违反 SLA,否则,公司(或团队)将会损失金钱(或声誉)。

由作者创建
通常,SLO 比 SLA 包含更多的度量。例如,为了实现 99.9%的总体可用性,团队需要监控一些内部工具的正常运行时间,并且每个工具都有自己的 SLO 阈值。

由作者创建
SLI(服务水*指标) —是服务提供商用来达到 SLA 目标的服务某些方面的量化指标。这是一个具体的指标,而不是一个目标。例如,为了达到可用性目标,SLIs 可能是“自 toolA 的最后一次心跳以来的秒数”。对于刷新目标,SLIs 可能是“数据集刷新后的小时数”。SLIs 的选择是基于 SLA 中的约定。
实际上,sli 是监控系统中的度量标准;SLO 是随叫随到工程师的提醒规则,SLA 比 SLO 更严格,它们应该让利益相关者满意。
在您的数据团队中设置 SLA / SLO / SLI 的 3 个步骤
让我们看一个例子。在本例中,数据工程师和利益相关者定义了包含公司收入数据的关键 BigQuery 表的 SLA。工程师将进一步定义 sli 和 SLO。

由作者创建
步骤 1 —与利益相关方一起定义 SLA
在步骤 1 之前,有一个步骤 0,用于确定值得应用 SLA 的服务。它可以是显示公司 OKRs 的关键仪表板,或者在本例中是组织中最常用的表。
然后,与您的利益相关方一起定义什么是可靠的数据。首先,数据工程师可以评估数据的历史性能,以获得基线并了解其使用模式、最常查询的字段以及查询频率。工程师可以要求利益相关者考虑他们对可靠性的要求。他们最关心的是什么?新鲜感?准确性?可用性?重复?
在与利益相关者的讨论中,双方需要就可使用的实际数字达成一致。一种常见的模式是从较低的 SLA 和 SLO 开始,因此工程师不需要 24/7 轮换,利益相关者也可以接受最初几个小时的停机时间。随着情况越来越稳定,可以增加到理想的数字。
回到这个例子,工程师从涉众那里得到了一些需求:
- 每天上午 9 点前,应使用昨天的收入刷新表格。
- 表应该总是可访问的。
- 表不应该在组合键上包含任何重复项。
gross_booking、net_booking、net_revenue等金额字段应计算正确。

由作者创建
基于工程师对历史性能的评估,他们提出了上述 SLA。可用性具有更高的 SLA,因为它主要依赖于 BigQuery 服务,该服务承诺 99.99%的正常运行时间。
步骤 2——定义 SLI 来衡量可靠性
一旦就 SLA 达成一致,您就可以开始选择有助于满足 SLA 的指标。指标的选择对于团队正在使用的数据基础设施来说是非常具体的。在这个例子中,假设团队使用 Airflow + BigQuery + dbt 来交付数据。所以 SLI 可能是:
- 自 Airflow 最*一次心跳以来的秒数(Airflow 的停机时间不会对 BigQuery 表的可用性产生直接影响,但是如前所述,监视有助于最终 SLA 的内部工具是值得的)。
- 自上次更新该表以来的小时数。
- dbt 中唯一性测试的测试结果。
- dbt 中其他值检查的测试结果。

由作者创建
步骤 3 —定义 SLO 并设置警报
定义 SLIs 后,您可以设置每个指示器的内部可接受的故障范围。如前所述,为了给随叫随到的工程师足够的反应时间,SLO 应该比 SLA 更严格。
就这些指标达成一致后,团队可以创建警报规则,并根据严重程度对事件进行评级。使用正确的监控工具,这些信息可以清楚地显示在仪表板上,从而有效地沟通和解决问题。

由作者创建
为具有依赖关系的服务定义 SLO
最后,我想说一个有趣的问题:如何为有依赖关系的服务定义 SLO。任何重要的服务都有依赖关系,每个服务可能有也可能没有自己的 SLO。如何将它们结合起来?
谷歌的这篇博客给出了一个明确的答案:不要确定你的 SLO 自下而上(“基于我所有的依赖,我的服务能实现什么?”),自上而下:“我的客户需要什么 SLO 才能开心?”把它作为你的 SLO。一旦你有了 SLO,你的依赖关系就代表了风险的来源。如果他们有更高的 SLO,那么很好。否则,你需要通过这篇博客中的建议之一来降低风险。
如果您的依赖项没有自己的 SLO,您可以使用历史性能来代替。如果它也不可用,你必须使用你最好的猜测。该博客提供了一个电子表格来模拟风险,并帮助您了解您的依赖性如何影响您的服务的 SLO 性能。
结论
一如既往,我希望这篇文章对你有用,并能给你启发。SLA/SLO/SLI(SRE 的一部分)的概念是由谷歌发明的,到目前为止,它主要应用于软件工程团队。但是现在,随着数据系统在组织中达到相似的复杂性和重要性水*,数据团队也该应用相同的概念了。实现 SLA 将是一场文化变革。这需要时间,并且需要数据工程师和用户之间的协作。但它将使沟通方式更容易和更有效。从长远来看,团队将从中受益。希望你喜欢,干杯!
参考
https://www.montecarlodata.com/blog-how-to-make-your-data-pipelines-more-reliable-with-slas/ https://cloud.google.com/architecture/building-production-ready-data-pipelines-using-dataflow-planning https://cloud.google.com/blog/products/devops-sre/defining-slos-for-services-with-dependencies-cre-life-lessons
我建立了一个公共世界地图集,有 2500 个数据集可供探索

图片由 Ylanite Koppens 免费提供。来源:https://www.pexels.com/photo/beige-analog-compass-697662/
灵感来自 Encarta,用 Python 制作,可在 worldatlas.org 访问
这是一个自筹资金的思想实验,历时 18 个月,目标是坚持一件事:“我们可以在让每个人都能访问重要数据方面做得更好”。
给我看货
前往 worldatlas.org 的,点击用户指南按钮,进行一次快速视频飞越。在台式机或笔记本电脑上使用 Chrome (MacOs,Win,Linux)或 Firefox (MacOs,Win)的体验最佳。
在 Github 上开源
在https://github.com/danny-baker/atlas查看项目源代码。拉请求非常欢迎❤️
螺距
你有没有想过为什么我们没有一个世界地图集来取代纸质地图集,就像维基百科取代了纸质百科全书一样?
你有没有想过你生活的国家在教育、文化、健康、犯罪、生物多样性、性别*等、预期寿命、经济等领域与其他国家相比如何?也许这些因素是如何衡量的,它们是如何随着时间的推移而变化的?你是否夜不能寐,想念 20 世纪 90 年代的微软 Encarta?
如果你对以上任何一个问题的回答是“我的天,是的!”或者“恩卡塔到底是什么?”那么你来对地方了,我的朋友。你也很有可能需要治疗。准备见证我第一次尝试公共网络应用。如果你能看穿这些问题,我希望这个狂热的创造能点燃一点兴趣,让我们如何让每个人更容易(理智地)获得关于世界的重要事实和统计数据。
我建立了一个世界地图集网站,包含了 2500 多个数据集供你探索。拥有跨越 100 个类别的超过 1200 万个数据点,找到您感兴趣的数据集,并使用一套交互式图表对其进行分析。所有数据都有完整的来源,可供下载。它旨在易于使用和有趣的学习。让我告诉你我的意思。
现代世界地图集会是什么样子?
如果我们要把世界地图集重新想象成一个网站,它应该仍然以地图为核心。地图=好。但是等等……我们难道不能做更多的事情,用漂亮的颜色和交互式图表来可视化数据吗?我们当然可以。我们可以用数据科学中的现代工具让数据变得生动。例如,看看下面这些不是很有趣吗:

上图:我的原型生成的地图和图表,对应上面的四个链接。几乎任何数据集都可以被可视化为地图,并使用一套图表和 3d 可视化进行分析。
我的上帝…想想这些可能性
这只是开始。从跨越经济、金融、卫生、教育、环境和技术等 100 个类别的 2,500 多个开放数据集中进行选择。所有数据都可以直接下载到您的本地机器上。所有数据都可以通过交互式图表进行实时分析。所有数据集都是完全来源的,所以你可以追踪它们的来源和发布它们的权威,以及注意事项。任何可视化(图表或地图视图)都可以通过完整的 URL 路径再现,因此您可以随时随地与任何人分享特定的见解。
比如 2013 年人均糖消费量图和附图按顺序排列的各个国家的图表。或者一张 2018 年每个国家的手机使用地图(每 100 人拥有的手机数量)和一张显示过去 40 年手机使用变化的图表。
无论你是学生、婴儿潮一代还是祖父母,我认为世界上的每个人都应该以一种简单直观的方式访问这些数据集,让他们能够理解。现在的问题是,我认为公众根本无法获得这些数据。我说的不可访问并不意味着数据集找不到,只是当它们被定位时,它们并不是一种普通人在智力上可以消费的格式。它们必须以. csv 文件的形式下载、清理,然后手动绘制(需要技术技能),或者出于特定目的隐藏在某个报告中。这些有价值的数据集对普通人来说毫无意义。
像 GapMinder 、联合国数据、世界银行和 OpenNumbers 这样的组织(以及许多其他组织)已经完成了制作、编目和维护许多伟大数据集的英勇工作。一个由贡献者组成的全球社区帮助构建了开源图表库和其他令人惊叹的 web 工具,使现代 web 应用程序可以做我们五年前认为不可能的事情。数据在这里。工具在这里。这个难题的最后一块(我相信)是将所有这些数据集中在某种集中的*台上,以一种对尽可能多的人有意义的方式来可视化这些数据。
“我们需要的是一个迭代进化的产品;一个将重要数据集带入生活的舞台。”
这就是我用这个原型想要证明的。这篇文章是我的提案的白皮书,该提案是由开源开放数据社区资助的面向公众的世界地图集。Encarta 世界地图集的现代免费替代品,使用数据科学的最新工具,与维基百科泛滥的“文本世界”截然不同。
我们正在解决的问题是什么?(为什么要建这个?)
关键问题 1:维基百科不是用来可视化统计数据的
随着 2009 年 Encarta 的消失,我们失去了两样东西:一本百科全书和一本世界地图。维基百科已经牢牢占据了世界百科全书事实标准的衣钵,它是互联网上仅存的几个奇迹之一。我会在有生之年继续向它捐款。维基百科不是一个可视化和理解统计数据的合适*台;这不是地图集。
当然,你可以找到世界人口之类的表格和数据,但是,从根本上说,维基百科(更准确地说,它是建立在基础‘维基’引擎之上的)是围绕显示相互链接的文本页面而构建的。这就是它所做的,而且做得非常好。几乎太好了。
拥有超过 600 万篇英文文章的维基百科现在是一个被淹没的文本世界。用户每一次都受到大量文本的冲击,图像和表格笨拙地扔进来,几乎是事后才想到的。没有实时图表或分析工具。维基引擎并不是为此目的而设计的;这是过时的技术,20 年来基本没有变化。那不是批评,那只是事实。新工具已经到了,它们很好。我还认为维基百科对普通大众中文化程度较低的成员(存在于所有年龄段)来说是一个障碍。我们都知道数据洞察力作为视觉表现更强大,因为我们可以使用我们最强大的主要感觉:空间推理来解释我们看到的东西。我认为这里有一个被错过的机会,因为我们没有一个主流的、视觉第一的资源作为维基百科的姐妹网站;我们没有世界地图集来补充我们的世界百科全书。
关键问题 2:数据不可访问性(数据素养)
如果我们同意公众了解(或至少容易获得)关于世界的事实是一件好事,如碳排放、森林砍伐、经济财富、健康和教育因素等,那么我会断言,问题是这种类型的数据并不尽如人意,我们可以做得更好。好多了。我说的更好是指提供在一个地方找到这些信息的机会,用于视觉学习,以及以一种比查看表格中的数字更强大的方式对数据进行交互式探索。
这些数据大多分散在各处,由发布这些数据的不同机构和机关托管在无数不同的网站上,写在单独的报告中,散布在网页上。这简直是一场噩梦。在许多情况下,当你最终找到好的数据时,它通常是针对周围行业的专业人士,并且不是以学龄学生等可以容易消化的格式。这让我很难过。目前,你基本上需要成为一名数据工程师、技术专家或学者,才能知道在哪里可以找到好的全球统计数据,更不用说你将如何开始清理、分析和可视化这些数据,以发现真知灼见。
虽然我们可以访问诸如联合国数据、世界银行和 Gapminder 等精选的开放数据集是件好事,但这还不够。我认为,缺乏一个众所周知的直观的教育工具来可视化数据并获得洞察力,这意味着这些数据在很大程度上被公众遗忘了。学龄学生不知道去哪里找,也不知道如何解释或分析. csv 文件。这是我着手解决的核心问题。我想让每个人都能超级访问重要数据。我还想让用户能够自己形成见解,而不是填鸭式地灌输给他们。
简而言之,我主张的核心问题是:“许多伟大的地理统计数据集存在,但他们目前不能以一种有意义的格式提供给公众”。更直白地说:“我们可以在让每个人都能访问重要数据方面做得更好”。
设计原则
如果任务是“让每个人都能超级访问国家级的统计数据”,那么我们设计的核心原则可能是什么?以下是我对如何将纸质地图集的精神与恩卡塔的魅力结合起来的想法,或许还可以加点神奇的巨美莓汁。

上图:我认为我们需要八个指导原则来制作一个公众能够参与并喜欢的世界地图集。图片由迪士尼的巨美熊提供。
我把它浓缩成了你在这里看到的八条原则。地图必须是这一创造的核心,但当然,随着我们摆脱纸张的束缚,拥抱现代网络浏览器的力量,我们可以极大地丰富地图集的体验。我们可以添加按钮、刻度盘、滑块、配色方案、分析工具和图表,以及其他交互式小工具。如果做得有品味,这可能会带来极好的用户体验。
它还需要足够通用,以便我们可以不断添加数据集,并在新工具可用时扩展其功能。(即从单一用户网站向真正可重复使用的模式转变)。最后,人们需要喜欢它,享受使用它的乐趣。它需要有趣和有一些风格。(这里确实需要帮助,但我已经尽力了)。
到目前为止我已经建立了什么
本着以上原则,我做了一个尝试。这是一个网站。它充当用户可视化访问数千个数据集的前端。
它提供了一个可以缩放和*移的完全交互式地图,一个用于选择数据集的导航菜单,一个用于在不同年份之间快速切换的时间滑块,以及用于显示实时图表进行分析的按钮。所有的数据都是可下载的和完全来源的。

上图:当前的原型允许用户从 2500 多个数据集中查找和选择,在地图上可视化它们,查看源数据,并通过一套交互式图表进行基本分析。住在https://worldatlas.org。在 Github 这里开源。
工作原理(书呆子部分)
对于像我这样只想要纯粹细节的书呆子来说:原型是一个用 Python 编写的 web 应用程序,旨在成为一个通用*台,用于接收国家级数据集并以各种方式可视化显示它们。
前端是一个典型的网站,由顶部的导航菜单、主地图和各种组件组成。这一切都内置于 Plotly Dash 开源中,允许整个网站用 Python 编写,同时获得开箱即用的令人敬畏的 Javascript 图表。为了在后端创造奇迹,我从Natural Earth Data(geo JSON polygons)中提取了国家/地区边界,这允许我们在每个地区周围绘制边界,并给它们独特地着色。我已经找到、下载、清理和格式化了数以千计的开放数据集,将它们标准化,以便在这个通用*台上使用。我整理了大约 2500 个数据集的当前列表,重新标记了许多系列名称,以便用一致的测量单位更容易理解。我还根据数据类型(例如,分类、连续、比率、定量)标记了每个系列,以确定哪些图表应该可用以及它们的行为方式。最后,我对每个系列进行了分类和子分类,这样它们就可以被搜索并很容易地构建到一个嵌套的导航菜单中,包括“人>教育”、“环境>气候”等类别。

上图:我采用的基本方法是结合区域边界(多边形)、国家级数据集和标准化国家标识符,在浏览器中以地图或图表的形式提供任何数据集的可视化界面。
一旦处理完毕,我就存储并压缩所有的。csv 数据到。拼花二进制格式。这是一种存储大型表格数据的极好格式,因为它使用了列压缩(如果有许多行重复的数据,这种格式尤其有用)。为了让你知道这有多疯狂,我浓缩了 5GB 的。csv 文件(5 列 x 1200 万行)压缩为一个 100MB 的拼花文件,压缩率为 98%。Python 应用程序在运行时读入 parquet 文件,并在 4 秒内将其作为 pandas Dataframe 加载到内存中,从而可以即时访问 1200 万条唯一记录。因为所有数据在运行时都在内存中,所以这允许图表和地图快速地重新查询数据,并在几秒钟内将不同的数据系列提供给客户端浏览器。可能有更好的方法,但这是我发现的最快的方法来快速访问如此大的数据集。保持简单:熊猫数据帧和一吨狗屎内存。
我在这里补充一下,我已经考虑过使用非熊猫数据结构,如 Dask 和 Vaex ,但这些增加了很多复杂性,减少了功能,我不认为它们真的有必要,除非主数据集超过 10GB。通过对 dataframe 列进行强数据类型化(例如,对国家和系列名称使用类别,对年份使用无符号整数(uint16 )),我已经将主数据帧大小减少了 87%,降低到每个应用实例大约 1GB,并快速加快了查询速度。
对于 web 开发人员来说,web 应用程序本身是一个独立的 Flask 应用程序,包含一个 Plotly Dash Python 应用程序,提供 Flask 框架的全部功能和 Plotly 开源图表的强大功能。关于如何做到这一点的完整文章来自黑客和懒鬼这里。最后,为了部署它,我通过 Docker 将应用程序与 NGINX 负载*衡器容器和 certbot 容器一起容器化,以管理 TLS (HTTPS)证书,并在 Azure 基础架构上的专用虚拟机上运行。我肯定有更好的方法,但是(1)我不知道它们是什么;(2)我更喜欢通过虚拟机完全控制硬件,而不是依赖 Heroku 这样的应用服务。我试过这些,但由于应用程序的高 ram 要求,专用虚拟机最终变得更便宜、更通用。
对于 DevOps-y 人员来说,该站点由 2 个 NGINX workers(带 HTTP 缓存)提供服务,它们充当 3 个 Gunicorn workers(应用程序实例)的反向代理,每个 workers 运行 5 个线程。这允许应用程序以真正的并行方式为客户端请求提供服务,并且线程可以共享其父 gworker 的内存(即主数据集)。理论上,该网站应该能够同时为数百名用户提供服务,但我还没有对其进行适当的负载测试。目前,我没有使用 CDN,它只是一个在英国 Azure 数据中心运行的单一主机,因此不幸的是,根据您与英国的距离,存在地理延迟。
书呆子区结束。
我为什么这么做
我想念恩卡塔。我当时在场。

上图:微软 Encarta 于 1993 年推出,售价约 400 美元(相当于今天的 750 美元)。它用图像、视频、文本、声音以及后来的版本中的完全交互式的世界地图让我们惊叹不已。(图片来源于各种 YouTube 视频的快照。)
微软在 1993 年首次发布 Encarta 时给人留下了深刻印象。在嘎嘎作响的拨号调制解调器、收音机里的“贝斯高手”和壁式百科全书的黄金时代,微软发布了一张闪亮的光盘,里面包含了似乎所有人类知识的总和。这是一个激动人心的展示,展示了我们如何使用新兴数字技术空间的工具来呈现信息。随着 90 年代末高速互联网进入数百万家庭,基于新网站的知识*台(如维基百科)能够以令人难以置信的速度扩展。这最终导致了 Encarta 在 2009 年的消亡。

上图:如今,Encarta 推出已* 30 年,我们有了一部令人惊叹的世界百科全书(维基百科),但仍缺少一些东西……我们的世界地图集在哪里?
我认为这个世界仍然缺少 Encarta 的创造者们刚刚确定的一种品质:他们以一种所有年龄段的人都可以理解的格式传递精选的、可访问的信息。他们做得很有风格:颜色、声音、图像、视频、互动地图,这是一个胜利。从孩子到祖父母的整个家庭都会为光盘而争斗。
在我们从 Encarta 的阳光灿烂的日子到今天的反乌托邦智能手机噩梦的道路上,某个地方出现了可怕的错误。
尽管我很努力地寻找,但我还没有找到任何符合 Encarta 为信息可访问性设定的标准并提供我喜欢的世界地图集式体验的免费公共资源。在我找不到满足感后,我带着白痴的天真开始建造它。会有多难呢?
产品进化
下面是一个早期设计想法的滑稽集合,它把我们带到了网站的当前版本。

上图:模型 1。最初的想法是去完整的国家地理,用户可以深入分类,或按国家钻取。我仍然认为有这样的公共资源会很酷。它几乎可以成为维基百科相关文章的替代前端界面。

上图:Mockup 2,一个非常糟糕的线框(我的第一个),试图将导航菜单、地图和控制面板结合在一起,类似于 GIS 软件。

上图:第一个在本地网络服务器上运行的带有世界人口的地图盒 Choropleth 和时间滑块的原型。非常幸福的时刻。计算机科学的一年级学生可能在 2 小时的教程中做得比这更好,但就我而言,是时候点一支烟放松一下了。从这里开始都是山下的,当然。

上图:增加了一些新的数据集。时间滑块和源信息,以及图表按钮。

上图:最新的版本加入了更多的润色,用透明覆盖给地图最大的屏幕覆盖。增加了搜索栏,完整的导航菜单,和更多的数据集。
该网站目前的状态是我真的想去作为一个演示或原型。这个项目现在正处于我想对陌生人(不是朋友和家人)进行验证的阶段,看看它是否有任何价值。如果没有,那也很有趣。
看一看竞争对手
在我寻找世界地图集式体验的圣杯的过程中,我发现了一些真正伟大的资源和一些令人失望的资源。我将在下面列出这些,以及一个不请自来的尖锐的批评,为什么我仍然对那里的一切不满意。
我对竞争对手进行这种完全自以为是的评估的标准大致基于用户体验、吸引所有年龄层的能力、数据的表面质量和验证能力、分析或询问数据的能力、所有权和使命。但我主要是根据我是否认为该资源符合 Encarta 标准来评估,为所有年龄段的人提供良好的学习体验。(我完全承认许多竞争对手瞄准了更多精选的受众,这很好)。在我们开始这次探索之旅之前,让我们从黄金标准开始:微软的 Encarta。
标准:微软 Encarta 世界地图集

上图:Encarta 和 1997 年的世界地图一起发行。它提供了一个完全交互式的地图和地球仪,各种统计数据集覆盖了文化、政治、动物、地理等类别。在这里看它的行动。
几乎 30 年前,微软凭借这款产品所取得的成就,其意义不言而喻。你必须记住,这是一个拨号调制解调器和奔腾电脑的时代,在高速互联网出现之前,在 MP3 和许多突破性的音频和视频压缩技术出现之前,在 GIS 软件成为主流之前,在谷歌作为一家公司存在之前,在 YouTube 出现之前 10 年,在 Mozilla Firefox 浏览器出现之前 5 年,几乎在 USB 标准发布之前(1996 年)。那是一个拥有 8x (8 速)光盘让你在朋友中很受欢迎的时代。
微软建立了地图软件应用程序(他们的 MapPoint 软件),并把它放在光盘上。世界地图集是在 1997 年左右随微软 Encarta 一起推出的,面向家庭和所有年龄段的教育。在家用电脑飞速发展的激动人心的时代,使用和导航非常有趣,提供了交互式的全球和地图体验,用户可以直观地缩放和*移。它还以一种不太好的方式显示数据。用户可以选择显示有趣的地理数据,包括自然、土地和气候、人类、政治等类别。学习确实是一种乐趣,你可以花几个小时浏览它所提供的内容。我认为它今天仍然令人印象深刻,我不知道一个现代化的,免费提供的版本或类似的产品目前可用。
评分:10/10 恩卡塔爱情计
竞争对手 1:Worldatlas.com

上图:Worldatlas.com提供了互动地图和大量国家信息,然而没有广告拦截器的体验破坏了教育价值的每一个可能的部分。
光是名字,我就想喜欢这个资源。真的。它有一个很好的域名和一个互动地图作为核心,有一些伟大的想法和关于世界各地有趣历史的文章,以及漂亮的国家汇总表。它似乎也面向所有年龄层。不过,坦白地说,我觉得这段经历已经过时,令人失望。以今天的标准来看,主地图很笨重,交互性也不好,如果这个网站没有广告的话,我可以原谅。即使有广告拦截器,这也是一种令人沮丧的用户体验,我认为如果网站所有者真的像他们声称的那样关心教育,他们会找到一种方法来做到这一点,而不会有明显的,分散注意力的广告,完全破坏教育体验。
也不可能核实表格中的任何数据和文章中的内容,因为没有来源信息,只是一个松散的说法,即他们事实上检查了一切。我想我们只能相信他们的话了,都很准确。我不知道他们是如何得到资助的,但似乎是由一家私人公司资助的。在将世界地图集的媒介从纸质转移到网站的过程中,我觉得这个网站成功地吸收了网络体验中一些最糟糕的方面(例如,闪烁的广告),而未能利用网络的最佳方面(获得真正沉浸式体验的可能性)。我更喜欢使用纸质地图集。
评分:1/10 恩卡塔爱情计。
竞争对手 2:CIA World Factbook

上图:美国中央情报局(CIA) 世界概况提供了 266 个国家/地区的数据表和概况,以及许多可以下载的静态地图。
在我的阿特拉斯朝圣之旅中,许多和我交谈过的人都建议我去看看中情局的《世界概况》。我已经检查过了,它确实以一种结构良好的方式展示了世界上 266 个国家/地区的信息,有静态地图、统计表、漂亮的分类和令人惊讶的好的解释内容。它当然提供了一个好的地图集的一些组成部分。你可以下载地图,我真的很喜欢你可以下载各种数据表作为一个. csv 文件(大勾这里)。整个体验和布局都很完美。
然而,有一些令人担忧的主要原因。很明显,整个资源由一个主权国家(美国)托管是有问题的,所以它不像联合国数据服务那样全球独立。我也很失望地发现,没有地图或统计数据(我的意思是没有)是来源。大部分数据表都只是写着‘est 2021’什么的。这意味着没有可能的方法来验证这个网站上的任何东西是真实和准确的。人们可能只是编造数字,然后把它们扔进表格里,没有人会知道。我一次又一次地看到,正是这种完整性的缺乏让用户能够检查数据的来源,我认为这是许多这些“可信”公共资源的一个主要问题。就我而言,任何地方发布的没有参考来源的统计数据都是 100%无用的,100%未经证实的。也许这太苛刻了,但我只是认为我们必须对公布的统计数据采用比这更好的标准。对于一个自称为事实的资源来说,讽刺的是没有办法核实事实。
评分:3/10 恩卡塔爱情计。
竞争对手 3: 谷歌地球

上图: Google earth 为世界提供了美好的用户体验,但它(据我所知)不支持叠加统计数据。
如果我们在考虑一个数字地图,我们不能不看谷歌地球就过去。从界面和用户体验的角度来说,我觉得没有什么东西可以碰它;这是一款卓越的产品,从太空到街道几乎可以无限放大。它已经从地球在*面上的不完美投影(例如墨卡托投影)到没有投影失真权衡的唯一真实和正确的地球可视化方式:球形球体,实现了巨大的飞跃。
关键是它真的只是为了显示物理地标而建造的,它没有(据我所知)一个全面的地理信息系统(GIS)组件,可以让你可视化特定地区的统计数据,如人口密度。我知道你可以添加地理点和系列,以及一些元数据等,但这更多的是为了制图应用。也不能用图表等实时绘制数据。它并不是真正为显示地理状态而设计的,它更多的是为了显示地球的高分辨率照片图像。
总之,我认为 Google Earth 从界面和用户体验的角度捕捉到了 Encarta 风格的魔力(++分),但未能提供如何显示地理状态(数据)本身。即界面是疯狂的,它只是没有我想要的统计数据。这也是一个私人公司拥有的资产,所以没有社区控制的内容。
得分 4/10 恩卡塔爱情量表
竞争对手 4:联合国数据门户( 联合国数据 )

上图:联合国数据门户和其他类似的数据服务为用户免费查找和下载大型地质统计数据库提供了基本设施。没有提供数据的可视界面。
我选择了联合国数据公司作为其他一些地质统计数据集出版商的代表性竞争者(其他出版商包括世界银行和 OpenNumbers )。这些资源收集、管理并精心维护一些我所知道的最重要的全球统计数据。所有的数据都可以免费下载,其中许多都有 API(应用编程接口),允许直接的机器对机器连接来访问他们的数据,这太棒了。如果您不熟悉 API,在这种情况下,这意味着您可以连接到他们的系统,并直接从您的代码(运行 app)下载他们的数据,而不是作为用户从浏览器手动下载. csv 文件。这就像是数据的后门。在任何情况下,他们没有提供任何数据的可视化界面;只有原始权限。
实际上,这些网站是你从货架上下载特定数据集的购物车。获得好的数据是 Atlas 难题的关键部分,这些组织提供这种服务。但当然,从原始数据到洞察时刻需要额外的努力和技术能力,以有意义的方式可视化数据。
我想挑出联合国的数据,因为它们是资助的,不像 OpenNumbers 都是志愿者。我必须说,我很失望,像联合国这样一个受资助的组织没有挺身而出,为他们拥有的精彩数据集注入一点活力。我认为托管供下载的数据是最简单的工作。他们的部分目的和意图肯定是将这些数据提供给那些可以从中受益的人,尤其是普通大众。直接引用他们的网站:
“un data 是 2005 年作为一个名为“统计作为一种公益”的项目的一部分推出的,其目标是免费提供全球统计数据,教育用户了解统计对循证政策和决策的重要性,并协助成员国的国家统计局加强其数据传播能力。”
我喜欢它的意图,并且永远感谢它托管了这些开放的数据集。然而,我要说的是,由于将他们提供的原始数据转换成有意义的见解(如地图或图表)所需的不可逾越的技术桥梁,这一资源被公众所知甚少,利用不足。对一般学校学生或非技术人员没用。如果我根据他们的核心目标(如上)来评估他们,我会说这是一个彻底的失败,因为他们未能真正实现“公共利益”的结果,使这些数据在智力上可以为公众所用。如果没人看得懂,就算可以下载也没关系。
得分:不适用,因为他们只是数据服务提供商。
竞争对手 5: ArcGIS Living Atlas 地图查看器

上图: ArcGIS Mapviewer 无需付费账户即可免费使用。这个例子显示了 2022 年初太*洋地区汤加和新西兰附*的火山活动。
我承认,最初我对 ArcGIS 很恼火,因为它们主要面向付费订阅,我没有意识到你可以免费访问一堆东西。这是一个很好的探索资源。我还没能找到任何像世界人口和 GDP 之类的基本统计数据。但我猜是因为这个数据一般更多的是为了统计分析。我们显然是在铁杆 GIS 空间中,我没有资格评论你应该如何(正确地)表示大部分统计信息。
这里看起来有一些很棒的数据集,适合在特定领域学习。这是一个专业级的工具。我想说,对于普通中学生或普通大众来说,这有点太专业了,但这是如此复杂的工具的本质。实际上找到这个地图查看器工具也有点困难。我花了 20 分钟浏览 ArcGIS 网站,避开所有付费订阅硬卖,然后才真正找到免费访问部分(巨大的障碍)。最后,这似乎不是一种生成超链接来共享特定地图视图的方式,例如我在上面使用的火山活动的地图视图。这是一次与他人分享知识和见解的机会。
评分:6/10 恩卡塔爱情计。
竞争对手 6:GapMinder

上图:GapMinder 提供了令人惊叹的交互式工具来分析许多开放的数据集,包括一个播放按钮来观察数据如何随着时间的推移而变化。令人印象深刻的东西。
Gapminder 是一家(瑞典)非盈利机构。他们的起源故事、使命和意图是崇高的,并与我的 Atlas 使命相一致:
“我们的使命是用每个人都能理解的基于事实的世界观来对抗毁灭性的无知。Gapminder 发现了关于重要的全球趋势和比例的系统性误解,并使用可靠的数据开发易于理解的教学材料,以消除人们的误解。Gapminder 是一个独立的瑞典基金会,没有任何政治、宗教或经济背景。
他们是全球网站和教育工作者使用的许多惊人数据集背后的驱动力,他们制作教学材料,他们建立了一套令人敬畏的交互式和响应性可视化工具(如上图)。他们甚至开创了一些新的可视化技术,让他们的创始人登上了著名的 TED 演讲(1500 万次观看)。它们提供了访问原始数据的链接,以及关于如何标准化的很好的解释内容等。惊人的东西,必须看看你是否听说过它们。
我要说明的一点是,当我发现 GapMinder 时,为什么我没有停止我的项目工作,因为它们并不完全是一个地图集。他们的互动图表和可视化工具是惊人的,我觉得他们中的每一个都更像是更大的东西的组成部分,可以把所有的东西聚集在一起。这些伟大的工具中的大部分也深埋在他们的教育网站的一个子域中,因为它们只是任务的一小部分。因此,举例来说,要获得上面显示的图表和地图,你需要从主页点击三个(1-点击-2-点击-3-点击)独立的网页!所以你真的必须知道去哪里寻找这些伟大的工具,我认为这立即增加了获取数据的主要障碍。当然,这是我的问题,不是 Gapminder 的问题,他们有一个比建立地图集更广泛的教育使命。如果任何一个组织都要努力构建现代世界地图集,我相信 Gapminder 会以一种对广大读者来说非常容易理解的方式来完成。很多爱。
评分:9/10 恩卡塔爱情计。
竞争对手 7: 视觉资本家

上图:《视觉资本家》制作了关于重要问题的漂亮而权威的信息图。
Visual Capitalist 是一家出版公司,在一系列教育领域制作精美绝伦的信息图表。他们的任务非常准确:
“让世界上的信息更容易获取。每天都会产生惊人的 2.5 艾字节的数据,让我们的世界变得越来越难以理解。通过数据驱动的视觉效果突出更大的画面,我们坚持我们的使命,帮助我们消除混乱,简化复杂的世界。”
这真的引起了我的阿特拉斯梦的共鸣。视觉资本家是一个惊人的资源和必看的。他们制作的东西以艺术和专业的方式呈现,同时具有权威性和准确性,基于公开的公共数据,如人口普查数据、世界银行等。他们还提供了很好的解释内容来将视觉化融入到上下文中,并且他们提供了高标准的资源。他们的作品具有广泛的吸引力,但并不强大,这使得它非常容易被所有年龄的学生所接受。他们有完整的路径链接,所以你可以分享一个直接的洞察力或可视化,如腐败映射到世界各地。它们是独立拥有的,从广告和许可中获取收入。
我不能挑剔这个伟大的作品,它填补了将数据带入生活的艺术空白,这正是我认为我们需要做得更多的事情。他们采用不同的方法(精致的信息图表)通过可视化的数据表现来获得类似的有力见解和即时吸引力。视觉资本家使用高质量的设计来制作信息图表艺术,而数字世界地图集只是访问数据的另一种方式,这种方式可能不太有效。无论如何,这不是竞争对手,而是志趣相投。我很高兴我在这个项目中发现了它。
评分:8/10 恩卡塔爱情计
竞争对手 8: 我们的世界在数据

上图:我们的数据世界为世界上最紧迫的问题提供了一种科学严谨的方法。他们拥有 3000 多张由综合文章和解释性内容支持的互动图表。出色的东西。
我们的数据世界是一个以公众信息可访问性为中心的使命,旨在传达并帮助阐明世界上最大的挑战。他们拥有超过 3000 多张跨越 297 个主题的互动图表。作为牛津大学研究人员和一个非盈利组织全球变化数据实验室之间的合作,它们由赞助商、慈善家和包括比尔和梅林达盖茨基金会在内的赠款资助(这是一件大事)。
我们工作的目标是让大问题上的知识变得容易理解。正如我们在主页上所说,我们的数据世界是关于研究和数据的,旨在针对世界上最大的问题取得进展。我们认为,我们未能取得我们有能力取得的进展的一个关键原因是,我们没有充分利用现有的研究和数据:重要的知识往往存储在无法访问的数据库中,被锁在付费墙后面,被学术论文中的术语所掩盖。”
简直太棒了。他们不仅处理世界上最重要的问题,而且处理得很有风格:结构优美的信息、写得很好且易于理解的解释性内容,以及与问题最相关的交互式可视化支持。他们提供分类的方法来访问不同的兴趣领域,并且数据来源完整,可供下载。他们还主持了一个独立网站,跟踪全球朝着联合国可持续发展目标(UN SDG) 的进展,这可以说是我们为大多数人想要生活的未来设定的最重要的目标。这也是必看的。
我在这里能提出的唯一批评(这是一个不可避免的事实)是,我觉得《我们的数据世界》是一个最适合大学及以上教育水*的人的资源。这是学术级的工作,内容丰富而详细。我想知道是否有办法像 Gapminder 令人钦佩的那样,向更广泛的受众如中学生开放其中一些内容?不幸的是,考虑到他们试图解决的问题的艰巨性,这不是一项容易的任务。它不是像交互式地图集那样的用户体验,它是一个高质量的视觉丰富的出版物(正如它被设计的那样)。对于任何对重要数据感兴趣的人来说,这是一个必看的资源,而且绝对权威。
评分:9.5/10 恩卡塔爱情计
鉴于现有的巨大资源,我们真的需要一本世界地图集吗?
也许我们没有,我接受,但我会这么说。在过去的 18 个月里,我穿越了这片崎岖不*的土地,寻求获得和开启重要的洞察力,并重新点燃 Encarta 体验的魔力。我登上了白雪覆盖的高山,凝视着我们所能提供的一些最好的远景(我们的数据世界),我不得不切断绳索,让一些行李沿着旅程走下去。我也尝试过自己创造一些东西,让我直接接触到目前可用的成千上万的原始数据集。
当我反思这段旅程时,我认为我们最好的公共资源(我们的数据世界,Gapminder,Visual Capitalist)中仍然缺少的是它们都是伟大工具和资源的集合。它们都不是真正完整的产品,像 Encarta 的《世界地图集》一样提供一致的用户体验。在更多的产品类型资源(如 ArcGIS mapviewer 和 Google Earth)中,这些在我看来也没有用,因为它们没有提供有广泛吸引力的有趣统计数据(生物多样性、识字率、GDP 等)。所以我认为我们仍然留给公众一个不完整和随意的方式去发现这些重要的数据。这是个问题。
我敢打赌,不到 1%的阅读这篇文章的人听说过我们的数据世界,或视觉资本主义。有吗?当我开始使用 Gapminder 的时候,我甚至不知道它。相反,我敢打赌,你们 100%听说过维基百科,并且经常使用它。这就是问题的本质:大多数伟大的资源只是迷失在不断扩张的互联网的噪音中。
随着互联网的扩张,质量与垃圾的信噪比正朝着错误的方向发展,让互联网最高质量的部分越来越被垃圾的浪潮所掩盖。每天都在恶化。我能想到的唯一对抗办法就是提高优质料的比例(好运),或者有一个家喻户晓,人人皆知,人人信任的名字。维基百科是百科全书。我希望有一天,我们也能有一个家喻户晓的名字来体验阿特拉斯式的体验。
权衡我的论点后,现代世界地图集的整个概念有社会效用吗?这种的信息,以这种的方式呈现,对你有用吗?你认为它对其他人有用吗?它在教育中有用吗?如果答案是肯定的,那么我的有缺陷的实验可能有腿。如果答案是否定的,我就错过了目标,是时候放手了。
谁应该为这样的事情买单?
为了讨论,如果我们决定社区世界地图集是我们想要的,这就把我们带到了资金的问题上。我们需要确保以最独立的方式完成,这样才不会破坏任务的意图。我会选择维基百科基金会的零广告、社区和志愿者赞助和策划的模式,作为资助这样一个工具的唯一方式,而不受公司、主权国家或资本主义的影响。我们的数据世界有一个基于赠款的模型,我认为这个模型也能很好地工作。为了忠实于公众教育的目的,它需要零广告,并且永远免费。没有例外。
我的原型的许多缺陷和警告
请记住 worldatlas.org是一个用爱、红酒和零种子资金制作的工作原型,所以它很慢,充满了 bug 和可以做得更好的东西。我是一名黑客数据科学家、UX 设计师和软件开发人员,所以这个项目的大部分都是以最少的努力来运行,并围绕全职工作来完成。
大结局
如果我现在让你比较你的国家和另一个国家的文化水*,你会从哪里开始?
一个足智多谋的人会通过谷歌搜索,维基百科,也许是挖掘一个单一用途的网站,或者某个地方的旧报告。你最终会得到一个答案,但这将是一个不稳定的一次性工作,你找到的数据可能会局限于一年和有限的国家列表。其他人可能会选择一条不同的道路,最终得到不同的、相互矛盾的数据。如果您想要比较任何其他统计数据,您将重新开始整个过程。我认为我们可以做得更好。
使用我的网站时,同样的问题花了 5 秒钟。我通过搜索快速定位了 2018 年成年人的识字率,我可以在地图上显示或按顺序排列每个国家。我还可以绘制出这个指标如何随着时间的推移发生变化的线图,并标记出我想要查看的任何可用国家。我可以下载原始数据。csv 或。xlsx 格式,查看其源信息,并查看在导航和搜索结果中分组在一起的相关数据(例如,不同年龄组和性别的识字率,或不同年龄组在学校度过的 AVG 年数)。我还可以将我看过的任何图表导出为完整形式的 PDF、SVG 或 JPG 文件(例如,所有可用的国家按顺序排列)。
对我来说,这对于学习来说是非常强大的。
几年前,上面概述的功能级别可能需要开发团队来构建,但数据科学中的最新工具(如 Plotly Dash )大大减少了将数据变为现实的努力。我相信,正是这种数据科学中的新一代可视化工具,可以在冷漠的墙上打一个洞,为主流公众带来高质量的数据见解;真的用它打他们的脸。我们需要向公众展示世界最佳数据的“前端”是什么样子。
结束语(梦)
我希望有一个免费的公共资源,使用起来像 Encarta 一样有趣,允许以 Atlas 风格的方式对重要数据进行可视化探索。如果你对人口数据、碳排放、森林砍伐、鱼类资源、生物多样性、健康、经济等感兴趣,这是一个家喻户晓的名字和值得信赖的地方。一个学习和教育中心,没有广告,对从儿童到数据专业人员的所有年龄层都同样有价值。不断发展的数据将会得到新的有趣的数据集和先进的数据表示方法的支持。这里的每个人都会有所收获。
这是我试图用我的世界地图集原型捕捉的梦想。我不明白为什么像这样的东西不能用我们今天拥有的开放数据和开源工具以低廉的价格建造出来。我们只需要一群有才华的志愿者的协同努力,一个计划,和 1200 万美元(笑话)。
作为一个现在从事产品工作的技术呆子,我可以明确地说,我一生中用过的最好的产品是微软 Encarta。我在 12 岁的时候体验过这个产品,我永远不会忘记。我还没有用过任何东西,因为它像恩卡塔那样让我喘不过气来,我怀疑以后还会有什么东西。我要求我们大家做得更好。
让我们制造出造福大众的产品,而不仅仅是赚钱的产品。
如果您想讨论 Atlas 项目,或者提供反馈和想法,请通过 dan@worldatlas.org 或 linkedin.com/in/dan-baker-59441a58联系我。这个项目在 https://github.com/danny-baker/atlas 的 Github 上是开源的。
我追踪了 1000 多天的心情:数据分析
原文:https://towardsdatascience.com/ive-tracked-my-mood-for-over-1000-days-a-data-analysis-5b0bda76cbf7
利用数据更好地理解我做了什么和我的感受
我使用情绪跟踪应用程序已经超过 1000 天了。正如你可能预料的那样:我浏览了应用商店,下载并试用了它。我喜欢它。它很有趣,很时尚,是我不断增长的时间水槽收藏的完美补充。但它的发展不止于此(当我把它移到手机的主屏幕上并购买了高级版本时,我意识到了这一点)。每天晚上我需要 5 到 10 秒来跟踪我的情绪,这成了我每天的冥想;我该回顾一天的时刻了,真诚地面对自己,问自己:“我感觉如何?”
我用一个叫 Daylio 的 app。每天,在固定的时间,它会向我发送一个通知,询问我过得怎么样,我通过点击从忧郁到欣喜若狂的五张脸中的一张来回答。选择一个后,应用程序会询问我今天做了什么,并显示一个预定义的活动列表,如“喝酒”、“朋友”和“购物”供选择。它还允许您向现有列表中添加自定义活动。比如我的一个定制活动就是“神奇宝贝!”(包括感叹号)和“烘焙”


Daylio 应用。我截图。
这篇文章将介绍我在分析 Daylio 这些年收集的数据后的发现。我的调查以统计方法为特色,包括时间序列分析和相关性,并专注于情绪和活动之间的关系,情绪签到时间,以及我的情绪得分如何变化。但是等等,先别走!我知道“统计学”和“相关性”这两个术语很吓人。这就是为什么我保证我会隐藏令人讨厌的细节和复杂性。相反,我会把它们伪装成更容易消化的 21 个迷人的小图表(点击放大)。
对于阅读本文的技术专家来说,您会在文章末尾找到一个链接,指向我的 GitHub repo,其中包含我编写的用于分析数据的 R notebook,以及另一个指向我用来提取数据的库。
现在来分析一下。

shawnanggg 在 Unsplash 上的照片
冗长的摘要
至少连续 1017 天跟踪自己的心情。第一条是 2019 年 1 月 4 日——新年决心万岁——最后一条是2021 年 10 月 19 日。我说至少是因为,当我写下这句话的时候,我还在使用这个应用程序,但分析不会包含 2021 年 10 月 19 日以后的数据。
在这 1017 天里,我记录了 1031 次我的心情(最初,我想每天记录两次,但最终只有一次)。所以,我们可以说,*均来说,我每天有一种情绪。图 1 是本文的第一个图表,显示了这 1031 条记录,按四种情绪“rad”、“good”、“meh”和“bad”分组。

图一。情绪频率。
在 1031 个条目中出现了 625 次(61 次** %),排在第四位的“好”情绪高居榜首。接下来是“rad”,这款应用的最佳情绪,也是我在不同的 201 ( 19 %)场合都认同的一个。在悲伤的一端,我们有194(19%)“meh”和11“bad”(1%)。此外,还有第五种甚至更悲伤的情绪叫做“糟糕”,这种情绪在文章中没有出现,因为我从来没有使用过它。希望它会保持这种状态。**
您可以将描述当天活动的活动列表添加到每个情绪记录中。就我而言,我通常会记录下一天中最难忘的活动,或者我在心情登记时间前后做的那些活动,这个时间大约是晚上 9 点(如图 2 所示)。我经常忽略的一个例子是“健身房”我这样做是因为我几乎每天都在凌晨(不到晚上 9 点)去那里(所以它不是那么令人难忘)。

图二。心情登录时间,包括中位数。
我在这 1031 份心情记录中报告了 44 种不同的活动。此外,我将其中一些描述为有趣或无聊,快乐或悲伤,异常或不愉快。但他们就是我——他们描绘了我是谁,我做什么。
当我们阅读这篇文章时,你可能会看到几个意思不清楚的活动,你可能想知道它们是什么意思。因此,为了避免任何疑问,我将零星地描述一下它们。现在,跟着我看图 3,在这里我将介绍我的 44 项活动以及我选择它们的频率。

图 3。我的活动及其频率计数,包括*均值和中值。
“朋友”(不,我不是指电视节目)是最重要的活动。这个活动,用来描述和朋友度过的愉快的一天,在数据集中出现了 229 次。与我在 1031 次签到中添加的 1338 次活动的总和相比,这是 17.11%。这么说吧,这样的顶级活动——我在这里会很俗气——在一天结束时,朋友是留在我脑海里的东西。我做的是 X 还是 Y 并不重要。那是次要的。重要的是,如果那天晚上我和朋友在一起,我会点击“朋友”图标说,“今天是个好日子。”
既然我们在谈论人,我就借此机会介绍一个在我生命中扮演重要角色的人。她叫 Ariel,是我的前女友。嗯,那不是她的名字,是我选的一个假名,因为我在分析数据的时候看到了一个小美人鱼迷因。但在那之前,甚至在写分析的第一行代码之前,我就知道她的名字会出现很多很多次——的确发生了。如图表所示,她是第二个被提及最多的活动。占 10.09 的 135 个条目。相当多,对吧?毕竟我们一起分享了很多年的经历。
如果图 5 中有一个活动让你发笑或皱眉,我敢打赌它是“拉屎”我们都会便便,甚至在天气好的时候也会便便几次。然而,我只录制了一次。这是一次惊心动魄还是令人毛骨悚然的上厕所之旅?我不记得了。尽管如此,它确实标志着 2019 年 1 月 6 日。
给情绪分配一个数值,其中“1”表示“糟糕”,“5”表示“糟糕”,得出的*均值和中间值分别为 3.99 和 4 。因此,我们可以得出结论,我的典型心情是“好”但这种压缩三年数据的方法很无聊,尤其是因为情绪记录包括一系列活动。那么,如果我利用这些活动来发现它们是如何影响情绪的呢?图 4 有答案。

图 4。活动、活动频率和*均情绪得分。
彩色图 4 显示了活动(X 轴)和它们的频率计数(Y 轴),按照从所述活动出现的日子获得的*均情绪得分排序。在左边,橙色,我们有产生较低*均情绪分数的悲伤活动,在右边,有高分数的最快乐的活动。第一部是《禁闭的新冠肺炎》,*均情绪得分为 2.94 分,它指的是因为限制而呆在家里的日子。接下来是“午睡”,是的,我说的是午睡。你还记得你小时候逃避午睡吗?我仍然不喜欢它们——除非我感觉不舒服(“生病”也是最糟糕的活动之一)或者情绪化,不知道如何继续我的一天。
另一个负面活动是“他妈的 Mac”在给它加上金之前,我需要说我爱我的苹果电脑。它陪伴了我大约四年,经历了许多冒险。但是哇,这不是有问题吗?在这四年里,我已经送去修理过五次了。通常,我不在乎,因为不管怎样,东西会坏掉。但当它在我的书的第一稿截止日期前两周停止工作,并且最*的 Mac 商店离这里有四个小时的路程时,就不行了。
另一个值得注意的负面活动是“求职”让我给出背景。2020 年初,我在新西兰,这是我历时一年的背包探险中去过的第 11 个(也是最后一个)国家。与此同时,当我徒步旅行、开车(在左边)和吃馅饼时,一个更大的东西正在新西兰和世界其他地方展开:新冠肺炎疫情。奇怪的是,由于我在荒野中,直到我到达克赖斯特彻奇市,我才意识到情况的严重性。不久之后,封锁开始了。就这样,我独自一人在克赖斯特彻奇的一间公寓里。在我的困惑中,不知道接下来会发生什么,我宣布我的旅行结束了,去找工作。但当时找工作很艰难,不仅因为公司停止招聘,还因为我远离可能找到工作的国家。所以,我没有找工作,而是收集了一堆电子邮件,开头写着“感谢你的关注”。
然而,让我感兴趣的是图表中心区域和之后的一切,因为这让我感觉良好。每个紫色项目至少有 4 分的*均分,这相当于一个“好”的心情。其中之一是“朋友”,前面已经介绍过了;其他我想强调的是“神奇宝贝”、“摄影”、“美食”、“编辑”(Photoshop 中的图片)和“写作”。在“好”之后是“好”,完美的情绪,在图表的末尾用四个蓝色的活动来表示。活动“F”指的是 F,我最亲密的朋友之一,他在我的数据中,因为当他从波多黎各(我的祖国)飞往德国(我目前居住的地方)进行为期一周的冒险时,我有多开心,包括火车、无数张丑陋的自拍和啤酒。最后一个是“公关”,它提到了波多黎各的一个暑假,在那里,除了其他事情,我还拜访了 F(我们拍了更多难看的自拍)。
除了*均值,还有第二个统计数据叫做标准差,我把它应用到每项活动的情绪列表中。标准偏差是一个描述列表值分散程度的数字。(一个正式的、更数学化的定义是一个数字,表示列表中的值与列表中的*均值相差多少)。例如,列表[1, 3, 6]的标准偏差是 2.52 ,列表[1, 5, 10]的标准偏差是4.51——后者更大,因为列表值之间有更多的“空间”。与这个实验相关的一个例子是 Ariel 的情绪得分活动列表。本次活动*均情绪 4.33。但是,因为我知道许多活动的情绪是 3 或 5——而不是 4——我预计会有很大的标准差,因为 3 和 5 远不是 4(在这种情况下)。那么,我每次活动的情绪有多分散(较大的标准差)或集中(较小的标准差)?图 5 有答案。

图 5。活动、活动频率和情绪的标准差。最后三个活动有一个未定义的标准差,因为每个活动只有一个条目。
上面的图表显示了每项活动的排序标准差分数,并说明了哪些活动产生了稳定或多样的情绪分数。前五个标准差是 0.0,这意味着这些类别的情绪总是相同的,例如,活动“PR”总是“rad”相反,有些类别的标准差很高。这些包括“Ariel”(因为我们既有正常的日子,也有美好的日子),“旅行”(花几个小时坐在地铁里令人沮丧,但也很有趣),“旅行”(我非常不喜欢旅行,所以我有两项活动),还有小睡(所以不是所有的小睡都很糟糕)。
图 6 和图 7 解释了活动是如何影响情绪得分的,但是现在,我想分析相反的情况:当活动缺失时的得分。因为我有活动出现时的*均情绪,剩下的步骤是计算活动消失时的*均情绪,并减去两个数字,这产生了一个数字,表示当他们出现和不出现时情绪有什么不同。图 6 是差异,表 1 是原始数据(我故意省略了大多数活动)。

图 6:有活动和没有活动时的*均情绪得分差异。

表 1:有活动、无活动和差异时的*均情绪得分。
禁闭期间我并不开心,这些图表也知道。在那些以呆在家里为亮点的日子里,*均情绪得分为 2.94 。但是看看当活动丢失时,它是如何增加到 4 的。相差 1.06。那是一种完整的情绪!相比之下,提升情绪的活动包括我之前介绍的几个,即“朋友”、“神奇宝贝”和其他活动,即“F1”(F1)和“探索”(指四处走走,通常带着我的相机)。
活动、日历和相关性的组合
我每天报告的活动数量各不相同。例如,在 318 个场合(那是 31 天的百分之),我输入无;在 325 场合( 31 %),一个;而且在 388 ( 38 %)上,不止一个。从这个不止一个的组中,我发现最热门的组合是出现了 23 次的“美食”和“朋友”,出现了 11 次的“写作”和“阅读”,以及出现了 10 次的“朋友”和“喝酒”。表 2 列出了前 10 名。

表 2:前 10 个组合。
一半的组合有“朋友”例如,“朋友”和“喝酒”,“朋友”和“游戏”,“朋友”和“摄影”。所以,我想“朋友让一切变得更好”这句老话并没有错。
除了接管我的活动,我可爱的朋友们也接管我的周末。在分析活动出现的日子时,我发现大多数“朋友”条目都发生在周末——周五 46 ,周六 45 ,周日 40 。图 7 显示了这些天的情况。

图 7:“朋友”日历。点击放大。
对比“朋友”的一个活动是“写作”(图 8)。周末和朋友在一起,这几天几乎没有时间写作,这就解释了为什么周五只有六个“写作”条目,周六有九个。相反,最好的写作日是周日( 24 )、周一( 20 )和周三( 18 )。顺便说一句,请注意我从 2021 年 5 月到 2021 年 9 月的写作瓶颈,当我开始着手你正在阅读的这项调查时,我打破了这个瓶颈。

图 8:“写作”日历。点击放大。
经常出现的组合并不意味着它的一个活动经常出现,而另一个活动经常出现。“朋友”活动就是一个很好的例子。表 2 显示我不做一个,而是和朋友做各种各样的事情。但是我们能说我只和朋友吃饭或者喝酒吗?这种相关性有助于回答这个问题。在统计学中,相关性是一个描述“两个变量相互协调运动的程度”的数字(来源)。在这里,活动之间的相关性是一个介于-1 和 1 之间的数字,它衡量两个活动在同一天出现的频率。例如,高相关性意味着两个活动经常在一起,而低相关性表明它们不经常一起出现。在我们可以从数据中创建的 1892 对活动中,最高的相关性(图 9)来自“探索/摄影”对,最低的来自“朋友/写作”——因为我很少在遇到朋友的日子写作。然而,我最喜欢的相关性是“F1/抱石”和“F1/烘焙”;对我来说,理想的周日包括早上抱石,晚上烤蛋糕,一边看 f1 比赛(周日举行),一边狼吞虎咽地吃蛋糕。

图 9:相关矩阵
随着时间的推移,我的心情是如何变化的
Daylio 中的每日条目有第三个数据点:记录的创建时间戳(可以选择或使用当前时间)。这个时间数据是有序的(按日期)并定期记录(每天一次),这两个属性是创建名为时间序列的数据类型所必需的。除了时间之外,时间序列还有第二个值,用来测量在那个时间戳发生了什么。我这里要介绍的时间序列,追踪了我从 2019 年 1 月 4 日到 2021 年 10 月 19 日的心情评分。在下图中,您可以看到一个数据集样本,其中包含记录的时间戳和我选择的情绪得分。

我的时间序列数据集的样本。第一列“Ds”表示时间,第二列“Y”表示当天的心情。
一旦将数据转换为时间序列,就可以应用算法来汇总数据,并发现时间如何影响值。趋势就是汇总的一个例子,它观察数据随时间的增加或减少行为。这一部分的第一个可视化是趋势线(图 10 ),描述了我的情绪从第一天开始的起伏。

图 10:时间序列趋势。
时间序列趋势描绘了一个不显著的下降趋势,初始得分为 4.2 ,结束于 4.1 。然而,重要的是图表如何随着时间的推移而变化,特别是在 2020 年 4 月和 5 月左右的最低点——那时我正在新西兰找工作。但后来,在我获得工作机会并恢复生活(2020 年 7 月)后,分数达到了它通常的、更快乐的值。
时间序列的一个特点是它能够将数据分解成称为成分的规则间隔,允许我们从不同的时间框架角度对其进行分析。一个例子是季节成分,它在数据中寻找季节性,或者一个季节如何影响时间序列。我要研究的季节是:每日、每周和每年的季节性。通俗地说,我想知道签到的时间、日期或月份如何影响我当天选择的情绪得分。
对于每个季节,该算法计算其中观察到的趋势。让我们考虑每周的季节性。由于我的数据集涵盖了 1017 天,相当于 145.3 周,该算法将汇总一周中每个时刻的情绪得分,例如周一的情绪得分,然后对它们进行*均。(实际上比这复杂,但细节不在本文讨论范围内)。我认为如果我们看到图像会更好(图 11、12 和 13)。

图 11:时间序列的每周季节性。
上图显示了每周的季节性。它的 X 轴是有序的工作日(从周日开始),Y 轴是受签到时间影响的情绪得分可变性。例如,我周日的情绪比周五和周四观察到的基线高出 0.15 点(一边看 f1 一边吃自制饼干是一种享受),比基线低 0.10 点。但总的来说,一周中的时间或日期几乎不会影响我的情绪检查分数,这可以通过 Y 轴范围来表示,即从 -0.10 到0.20;仅仅相差 0.30 分,或者说情绪得分的三分之一。

图 12:时间序列的年度季节性。
一年中的时间比一周有更大的影响。在这种季节性中,情绪得分最多变化 0.6 点,如范围从 -0.3 到 0.3 的 Y 轴限制所观察到的。在今年的前五个月(除了三月),情绪在 0.0 的基线附*,但随着德国夏季的开始(以及去比尔加滕的旅行),情绪像温度计一样上升。这种模式的一个明显的例外是 7 月份的下降,我把这归因于新西兰的封锁和南半球的冬天。几周后,当树叶变黄时,我用连帽衫代替短裤,用南瓜香料拿铁代替椰林可乐达斯,漫长的白天和漫长的夜晚,因为我在准备另一个圣诞节,我可能会在波多黎各庆祝。
最后,我们有每日季节性图表,如下图 14 所示。第一次看到这个图表,看不懂。我从左到右,从上到下看了一遍,没有任何意义。于是,我放弃了,抓起手机。我查了一下时间,是上午 9 点;我检查了电子邮件,没有新消息;我看到了通知,意识到自己前一天晚上没有进入状态。我当时就做了。就在这一刻,当所有的希望都消失的时候,我明白了这个图表。

图 13:时间序列的每日季节性。
在文章的前面,我提到我通常会在晚上 7 点或 9 点记录我的心情。在这些时间记录意味着我在做一些不经意的事情,例如玩神奇宝贝,并且可以使用 Daylio 添加今天的数据。但如果我做的不是休闲的事情呢?如果我正在做一些很棒的事情,例如玩竞技神奇宝贝,却不能使用手机,该怎么办?如果是这样的话,那么我会等到晚些时候或者第二天早上再记录心情,这就解释了为什么在那些时候心情得分会更高。
探索情绪
我想把文章的最后一部分献给每种情绪——毕竟,Daylio 就是关于它们的。对于每种情绪,我将讨论它最长的一段时间,最重要的活动,以及我选择它的那一天。让我们从最好的一个开始,“rad”,以最差的一个结束,“bad”
拉德
“Rad”是最好的心情。这就是我所追求的:一个完美的一天,吸引最好的感觉、行动和人。我已经有相当一部分了; 201 完全正确。这是五天中的一天,即 19%。但是这五分之一是一个*均值,因为也有例外,比如持续了 14 天的最长记录。****
我的周末很愉快(图 14)。前面提到的 201 天中有 38 天是星期六,这是我们最喜欢的一天,而 33 是星期天。让我感到惊讶的是那些 32 周二,我们经常降级的一天,人们认为这是最糟糕的一天(谷歌“为什么周二如此无关紧要”并大笑)。

图 14:工作日的天数。
使我的日子变得更有活力的活动是我已经展示过几次的那些(图 15)。排在第一位的是《老友记》,出现在《我的快乐时光》的 36%中,而《爱丽儿》出现在其中的 28%中。图表中的一个亮点是活动“会议”( 2.49 %),指的是参加会议,或者更好的是在会议上发言,这是我喜欢做的事情,但最*因为世界的现状而没有做。(如果阅读这篇文章的人主持或知道适合这项工作的在线或面对面会议,请联系我,帮我提高这个低百分比。)

图 15:有这些活动的“rad”天数的百分比。
过去的好心情——我默认的情绪,我选择它超过其他四种情绪 625 次( 61 %)就证明了这一点。它最令人印象深刻的连胜纪录开始于 2021 年 3 月 11 日,结束于 2021 年 4 月 2 日。二十四天,其中唯一的亮点是我吃麦当劳快乐套餐来收集附送的神奇宝贝卡片。我把他们都抓住了。

25 张麦当劳神奇宝贝卡片。图片由我:)
周一的忧郁在我的生活中是不存在的——事实上,这是我最美好的一天(图 16)。这个丑闻不断的日子,有人恨,有人爱,却给了我最美好的日子( 101 ),一个离亚军周日不远的数字,以及它的 99 享受时刻。

图 16:工作日的“好”心情。
我的好日子和孤独的活动。与 rad days 的社交活动相反,good days 的特色是我更喜欢独自做的活动(图 17)。例子有“写作”(如斯蒂芬·金所说,“写作是一项孤独的工作”),“阅读”,不用说,“便便”。只有我的手机被允许加入后一个。

图 17:有这些活动的“好”天的百分比。
牛津将“meh”定义为表示缺乏兴趣的感叹词——我同意。meh day 是无趣的一天。你不快乐也不悲伤;你是我。这几天我过了 194 天。这是我一天中的百分之十九(T2),也是我快乐时光的百分之一。幸运的是,我还没有经历一连串无趣的日子。最长的持续了七天,归因于新西兰第一次封锁的前几天(2020 年 3 月 19 日,直到 3 月 25 日)。
星期天是最无聊的一天。(在我对这一天说了那么多美好的事情之后,这怎么可能呢?).但是我不能对星期三说同样的话,让我吃惊的是,星期五。图 18 显示了这些数字。

图 18:工作日的“meh”心情。
至于活动,只有三个经常出现在 meh 日(图 19)。

图 19:有这些活动的“meh”天数的百分比。
严重的
我们都同意糟糕的一天糟透了。对我来说,糟糕的一天意味着我焦虑、疯狂或过度思考。这些都不是愉快的感觉。不过,像这样的日子很少见。到目前为止,我在 1031 天中使用这个分类 11 次( 1 %)。他们来了就走了。最长的“连胜”只持续了两天:2020 年 4 月 4 日至 5 日和 2021 年 7 月 9 日至 10 日。更好的是:他们周日不会来(图 20)。因为再一次,我保证这是我最后一次带这个,徒步旅行,抱石,f1 和烘焙有什么不好?

图 20:工作日的“坏”情绪。
我仍然觉得有趣的是,“午睡”是一种与糟糕的日子联系在一起的活动(图 21)。但是午睡本身并不是坏事。错的是我去午睡城的原因(正如一个朋友喜欢说的)。我在逃避什么情况吗?睡一觉,也许就好了。我糊涂了吗?睡觉,也许你会找到答案。但我知道没那么简单。有时我需要接受现状,度过一天,因为正如数据显示的那样,明天我会感觉更好。

图 21:有这些活动的“糟糕”日子的百分比。
最后的话
过去的三次改变给我带来了很多改变。这一切都是从我在国外有一个正常、稳定、美好的生活开始的。然后我的优先事项改变了。过去很重要的事情现在已经不那么重要了;我的恐惧不再存在;我的梦想成了我的头等大事。所以,我做了一些激烈的事情。我辞掉了我热爱的工作,告别了我亲爱的朋友,卖掉了我的东西,带着我的相机,笔记本电脑,还有 Ariel 登上了飞机。我背着背包生活,写一本书,提高我的摄影水*。实现梦想!
但是当病毒失控时,它提前结束了。因此,我不得不在一个美丽的国家用四面墙内的夜晚来代替我在森林里的夜晚,那里的冬天是在六月,做一个霍比特人离现实不远。
就在这些墙里面,我思考着接下来会发生什么。我贪婪的一面想留下来延长这次冒险,尽管它已经不可持续了。我耗尽了我的积蓄,浪费了我的时间,没有一个计划。直到一个机会出现,我登上了飞机。
飞机把我带到了 416 天前我离开的那个城市。虽然我很高兴回来,但我不能忽视新环境是多么独特和奇怪。我少了 7 公斤。,几件衬衫,不得不戴着口罩早早回家,认识的人大多都走了。
一年后,我坐下来思考这些时刻和我的感受。知道我已经记录了我的情绪和活动很长一段时间,我想确认我所记得的是准确的,还是我只是感到怀旧。这是两者的混合。
写这篇文章让我想起了很多时刻。这让我想起了我的背包客年,我的波多黎各之旅,与朋友的无数晚餐,我周日下午的蛋糕和一级方程式赛车,以及新的神奇宝贝游戏的发布。它也带回了令人不快的记忆,包括孤独的日子,生病的时候,当我的 Mac 电脑没电了,当我的电子邮件被世界各地的公司自动拒绝时。写这个也带来了很多惊喜。例如,我没想到在新西兰的最后几天,我的情绪得分会有如此明显的下降,周日活动之间的相关性,或者根据数据,我三年中只大便过一次。
如果有什么东西我会带走的话,那就是从另一个角度看那些改善我生活的事情——朋友、探险、摄影、写作和神奇宝贝——是多么美好。当然,这个我早就知道了。我爱我的朋友,带着我的相机到达一个新的地方,花一个安静的晚上写作,以玩最新的神奇宝贝游戏结束。但这是我记得的,有时我们的记忆会偏向我们喜欢的东西——然而,用数据见证和确认这些事实使晚上 9 点点击我的手机的那几秒钟成为一项值得的投资。
你可以在https://github.com/juandes/daylio-mood-analysis的 GitHub 中找到我编写的用于分析和可视化数据的源代码,以及在https://github.com/MichaelCurrin/daylio-csv-parser用于转换数据的库代码。我故意省略了数据集。
我已经训练了我的第一个机器学习模型:下面是它是如何进行的
一些最大似然算法的概述和一些个人的思考

马库斯·温克勒在 Unsplash 上的照片
最后,我训练了我的第一个机器学习模型!是啊!最终,我做到了。球场上有太多的炒作,我没有看到最终做这件事的时间。
但也许这就是我最初难过的原因:也许,赛场上的炒作太多了。
我在 2021 年 3 月左右开始学习数据科学和编程,当时我是:
- 为我的机械工程学士学位的最后一次考试而学习
- 为我的论文而学习,这当然依赖于数据科学
- 全职工作
- 做所有和我的家庭相关的事情
所以,当然,我花了一年时间才来到这里:训练我的第一个 ML 模型。但我想告诉你这一切是如何进行的,我学到了什么(我学到的东西最终真的让我很开心!).
k-最*邻:我尝试的第一个算法
在我攻读工程学位的过程中,我学到的真正重要的东西(也许,这是我学到的最重要的东西)是从小处着手,从简单的事情开始,这样我就可以随着时间的推移掌握它们。
我被建议学习的第一个模型(这些天,我正在结束一门数据科学的课程)是 K-NN 和决策树。我决定从 K-NN 开始。我在网上搜索了一下,找到了一个用 K-NN 进行良好锻炼的典型数据集,在这个数据集里,我们有一些特征,如身体质量指数、怀孕、血压等..哪些与糖尿病有关(你可以在网上从不同的来源找到数据;比如这里的。该练习的目的是训练 ML 模型来预测具有这种医学状况的患者是否可能患有糖尿病。
所以,让我们看看一些带注释的代码。
#importing all the needed librariesimport pandas as pd
import numpy as np
import pandas_profiling
from pandas_profiling import ProfileReport
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier as KNN
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
from sklearn.metrics import precision_score
from sklearn.preprocessing import MinMaxScaler#importing data
diab = pd.read_csv('diabetes.csv')
现在,我想给你们看的第一个东西是一个包,来自熊猫,这是我几天前发现的;这是“熊猫简介”,我用上面的代码导入了它。这个软件包在探索性数据分析中非常有用,因为它给了我们很多信息。让我们看看:
#profiling data
profile = ProfileReport(diab, explorative=True, dark_mode=True)#output in HTML
profile.to_file('output.html')
这个软件包确实给了你很多关于数据的信息。例如:

熊猫轮廓的输出。图片由作者提供。
上图向我们展示了 Pandas Profiling 在第一个实例中向我们展示的内容,即变量信息;例如,数据集总共有 9 个变量,其中 8 个是数字变量,1 个是分类变量。
事实上,如果我们键入“diab.head()”,我们可以看到:

' diab.head()'的结果。图片由作者提供。
事实上,我们有 8 个数字特征(前 8 列)和 1 个分类特征(最后一列,名为“结果”,它告诉我们患者是否患有糖尿病,值为 1,值为 0)。
如果您看到前面的图像,您可以看到 Pandas Profililing 给出的输出告诉我们有 15 个警报。在这一部分,软件包会给我们更多的信息,例如,它会告诉我们具有高相关性的特性以及更多。我真的建议你去看一看。
现在,让我们继续分析。首先,我们要用 0 值填充行;事实上,例如血压为 0 毫米汞柱是不现实的。为此,我们可以用同一列上的*均值来代替零;我们可以用下面的代码来实现:
#filling zeros with mean value
non_zero = ['Glucose','BloodPressure','SkinThickness','Insulin','BMI']
for coloumn in non_zero:
diab[coloumn] = diab[coloumn].replace(0,np.NaN)
mean = int(diab[coloumn].mean(skipna = True))
diab[coloumn] = diab[coloumn].replace(np.NaN, mean)
print(diab[coloumn])
现在,我们可以开始训练我们的模型。我们希望特征是前 8 列,以便给定一个患者与前 8 列相关的值(年龄、妊娠、BPM 等),我们希望预测他/她是否患有糖尿病;因此“结果”栏(有/没有糖尿病)将成为我们的标签。
#all the column except the last one (features)
X =diab.iloc[:,0:8]#the outcome column (label)
y =diab.iloc[:,8]#feature scaling
scaler = MinMaxScaler()
X = scaler.fit_transform(X)#testing and training
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,random_state=0, stratify=y)
现在,让我们拟合我们的模型,看看我们的预测:
#fitting model
model = KNN(n_neighbors=5).fit(X_train, y_train)#predictions and model accuracy
y_pred = model.predict(X_test)
print(f'Model accuracy on test set: {accuracy_score(y_test, y_pred):.2f}')
结果是:
Model accuracy on test set: 0.75
“嗯嗯……“非常好”是我的第一个评论。但是我们能说点别的吗?我用过 k = 5…是个好选择吗?我怎么能理解我做了一个好的选择?好吧,我们可以试着用下面的代码找到一个好的“k”:
#best k
for k in [1,2,3,5,10]:
model = KNN(n_neighbors=k)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
validation_accuracy = accuracy_score(y_test, ypred)
print('Validation accuracy with k {}: {:.2f}'.format(k, validation_accuracy))
利用上面的 for 循环,我们迭代地计算列表中指示的值的最佳 k。结果是:
Validation accuracy with k 1: 0.71
Validation accuracy with k 2: 0.72
Validation accuracy with k 3: 0.75
Validation accuracy with k 5: 0.75
Validation accuracy with k 10: 0.78
因此,根据计算结果,给出最佳精度的 k=10,我们将使用它。为什么我选择测试上面的 k 值?嗯,3 是一个典型值,选择一个“太高”的值会导致模型不稳定。因此,如果您亲自尝试,您会发现对于高于 k=10 的值,精度实际上是相同的。
现在,让我们用最好的 k 来训练我们的模型:
#training model with best k
model = KNN(n_neighbors=10).fit(X_train, y_train)#predictions and model accuracy
y_pred = model.predict(X_test)
现在,让我们看看混淆矩阵来评估一些指标:
#confusion matrix
cm= confusion_matrix(y_test, y_pred)
cm--------array([[89, 11],
[23, 31]])
混淆矩阵给了我们一个很好的结果。事实上,我们有 89+31=120 个值预测良好(89 个真阳性和 31 个真阴性),23+11=34 个值预测不良(23 个假阴性和 11 个假阳性)。事实上,如果我们计算精度得分,即模型将真阳性预测为真阳性的能力,我们会得到:
#precision score
precision = precision_score(y_test, y_pred)print(f'{precision: .2f}')--------------------0.74
这是一个令人满意的值。
就是这样。所以,我(最初)的悲伤。该模型训练有素,k=10 的 K-NN 是预测患者是否患有糖尿病的良好算法,知道与其健康状态相关的其他值。
所有对机器学习的炒作到此结束?
嗯,我是一个永远不会满足的人,尤其是在这个领域,我对知识的渴望是我无法满足的。所以,我想更深入一点。
此外,我喜欢图表…我需要看东西。所以,我找到了一种图形化评估我的模型的方法。例如,我们可以绘制核密度估计;在统计学中,KDE 是一种估计随机变量概率密度函数的非参数方法(例如,你可以在这里阅读更多)。我们可以这样做:
import matplotlib.pyplot as plt
import seaborn as snsplt.figure(figsize=(10, 10))#diabetes outcome (Kernel Density Estimation)
ax = sns.kdeplot(y, color="r", label="Actual Value") #y = diab['Outcome']#predictions (Kernel Densiti Estimation), in the same plot (ax=ax)
sns.kdeplot(y_pred, color="b", label="Predicted Values", ax=ax)#labeling title
plt.title('Actual vs Precited values')#showing legend
plt.legend()#showing plot
plt.show()

实际值(红色)和预测值(蓝色)的 KDE。图片由作者提供。
所以,我们可以看到,两条曲线非常相似,证实了之前的分析。
现在,我对我的学习变得有点高兴了,但我还不够高兴;事实上,我问自己是否能做得更好,至少在理解的层面上。我研究了决策树模型,想看看它如何处理这样的数据。
决策树:我尝试的第二个 ML 算法
由于与分割和训练相关的数学和代码与之前相同,我将向您展示模型拟合后的部分:
from sklearn.tree import DecisionTreeClassifier#decision tree model and fitting
clf = DecisionTreeClassifier(max_depth=3)
clf.fit(X_train,y_train)#predictions and model accuracy
y_pred = clf.predict(X_test)
print(f'Model accuracy on test set: {accuracy_score(y_test, y_pred):.2f}')----------------------Model accuracy on test set: 0.79
这个第一个结果真的让我反思…我得到了 0.79 的准确率,而 K-NN 的准确率是 0.78,k=10('最好的' K!)因为我非常确定 K-NN 是在这种情况下使用的更好的算法。我想尝试找到树的最佳深度,我使用了与最佳 k 相同的方法:
#best depth
for d in [3,5,10, 12, 15]:
model = DecisionTreeClassifier(max_depth=d)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
validation_accuracy = accuracy_score(y_test, y_pred)
print('Validation accuracy with d {}: {:.2f}'.format(d, validation_accuracy))------------------------------Validation accuracy with d 3: 0.79
Validation accuracy with d 5: 0.79
Validation accuracy with d 10: 0.79
Validation accuracy with d 12: 0.79
Validation accuracy with d 15: 0.79
所以,我们用一堆不同的 d 参数得到相同的精度。
现在,如果我们计算混淆矩阵,我们得到:
cm= confusion_matrix(y_test, y_pred)
cm
-------------------array([[84, 16],
[16, 38]], dtype=int64)
最后是精度分数:
#precision score
precision = precision_score(y_test, y_pred)print(f'{precision: .2f}')-------------------------------0.70
最后,我没有报告 KDE 图,因为它实际上与 K-NN 算法生成的图相同。
这些结果让我又有点难过,因为,如前所述,在这种情况下,我非常确定 K-NN 是比 DT 更好的算法,但是正如你所看到的,我们得到的两个模型的结果非常相似。
因此,我问自己:ML 中有这么多算法,一个人(尤其是像我这样的新手)如何能够理解——给定一个问题——哪种算法能够很好地适合给定的问题?
信不信由你,我终于找到了一个让我非常开心的答案。
Pycaret
所以,我谷歌了一下,找到了 Pycaret 。正如他们在其网站上所说,“Pycaret 是一个开源、低代码的机器学习库,可以自动化机器学习工作流”。要安装它,您只需键入:
pip install pycaret
工作完成了,像往常一样轻松。
现在,让我真正高兴的是发现了这样一个事实,使用 Pycaret,我们可以比较 ML 模型来预测我们正在研究的问题的值,而且我们是以一种非常简单的方式来做的。Pycaret 给出了比较的结果,它使用“交叉验证”方法来评估各种模型的超参数,并为我们提供了所需的度量,用于决定使用哪种算法来解决我们的问题。让我们看看它是如何工作的:
from pycaret.classification import *#defining the features
numeric_features = ['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age']#setting up the data
clf = setup(data=diab, target='Outcome')#comparing the models
best = compare_models()

ML 算法与 Pycaret 算法的比较。图片由作者提供。
正如我们所看到的,Pycaret 中的比较非常有用,它告诉我们:
- K-NN 和 DT 远不是在这种情况下使用的最佳算法(它们处于排名的中下部分),但是它们为我们提供了具有相似值的度量(请记住,我们已经使用 for 循环计算了超参数,而 Pycaret 使用交叉验证,因此数字略有不同)
- 基于 Pycaret 度量计算的最佳算法是随机森林分类器
在结束之前,请注意在我写作的时候(2022 年 3 月),Pycaret 并不与所有可用的 scikit-learn 版本兼容;我有 Scikir-learn 版本 1.0.2,不得不降级到 0.23.2。做起来很简单:
pip uninstall scikit-learnpip install scikit-learn==0.23.2
结论
正如我们已经看到的,当学习训练一个 ML 模型时,如果你是一个新手,像我一样,尝试不同的算法是一个好主意。这类练习真的给了我们获得‘动手’项目和算法的可能性。
当然,我们仍然需要知道更多,但是随着时间的推移,我们会越来越有经验。我非常确定 Pycaret 是一个很好的解决方案,可以决定我们面对的特定问题使用什么算法,但我甚至确定经验——我实际上是新手,会有所帮助(可能是大多数)。
我们一起连线吧!
LINKEDIN(向我发送连接请求)
如果你愿意,你可以 订阅我的邮件列表这样你就可以一直保持更新了!
考虑成为会员:你可以免费支持我和其他像我一样的作家。点击 这里成为会员。
IVFPQ + HNSW 用于十亿级相似性搜索
原文:https://towardsdatascience.com/ivfpq-hnsw-for-billion-scale-similarity-search-89ff2f89d90e
十亿级矢量数据集的最佳索引方法

保罗·塔尔博特在 Unsplash 上的照片
我们在上一篇文章中了解了 IVFPQ ,其中倒排文件索引(IVF)与乘积量化 (PQ)相结合,创建了一种有效的大规模相似性搜索方法。
在本文中,我们将了解 HNSW 以及它如何与 IVFPQ 一起使用,以形成十亿级相似性搜索的最佳索引方法。
我们将首先介绍 NSW 和 skip list,这是构建 HNSW 的两个重要基础。我们还将介绍如何使用 Faiss 实现 HNSW,不同参数设置的效果,以及 HNSW 索引的不同变体如何在搜索质量、速度和内存使用方面进行比较。
内容
1。简介
2。可导航小世界(新州)
∘ (A)新州—建设
∘ (B)新州—搜索
3 .跳过清单
4。分层可导航小世界(hnsw)
∘(a)hnsw—构造
∘ 插入过程
∘ 启发式选择
∘ (B) HNSW —搜索
5 .用 Faiss 实现:indexhnswflat
∘效果的 m,efConstruction,和 efSearch
6。用 Faiss 实现:IndexIVFPQ+HNSW
7。HNSW 指标对比(有/无 IVF 和/或 PQ)
8。概要
1.介绍
图由顶点和边组成。边是连接两个顶点的线。让我们称连接的顶点为朋友。
在向量的世界中,相似的向量通常彼此靠*。因此,如果向量被表示为图上的顶点,逻辑上,靠*在一起的顶点(即,具有高相似性的向量)应该被连接为朋友。即使他们没有作为朋友连接在一起,他们也应该可以很容易地通过一两个顶点到达彼此。
对于一个可导航的图,每个顶点都必须有朋友。否则就没有办法到达顶点。有个朋友很好,但是有太多的个朋友对一个图来说代价会很大。想想保持这些连接所需的内存和存储,以及在搜索期间比较距离所需的计算次数。
通常,我们不希望图形看起来像下面这样。

除非另有说明,所有图片均为作者所有
我们想要的是一个具有小世界网络特性的可导航图,其中每个顶点只有少量的连接,并且两个随机选择的顶点之间的*均跳数很低。
2.可航行的小世界(新南威尔士州)
从概念上讲,使用 Delaunay 图(或 Delaunay 三角剖分)进行*似最*邻搜索似乎是理想的,因为彼此靠*的顶点是朋友,并且不存在孤立的顶点。

一个 Delaunay 图的例子
然而,构建一个 Delaunay 图并不容易和直接,并且搜索效率只是次优的。例如,如果两个顶点 A 和 B 相距很远,我们需要通过大量从 A 开始的友连接才能到达 B,反之亦然。
因此,新南威尔士州使用一种更简单的方法来构建 Delaunay 图的*似值,而不是构建精确的 Delaunay 图。
新南威尔士州——建筑
NSW 图是通过以随机顺序一个接一个地插入顶点来构建的(即向量首先被打乱)。
当插入一个新顶点时,它将连接到与其最*的现有顶点。
在下图中,M被设置为 3。我们从插入 a 开始。此时,图中没有其他顶点。接下来,我们插入 B。由于图中只有 A,我们将 B 连接到 A。然后我们插入 C。现在图中只有 A 和 B,所以我们将 C 连接到 A 和 B。接下来,我们插入 D。同样,由于图中只有 A,B 和 C,我们将 D 连接到 A,B 和 C。
现在,当我们插入 E 时,图中有四个其他顶点,即 A、B、C 和 d。由于M设置为 3,我们只将 E 连接到最*的三个顶点,即 B、C 和 d

用M=3构建 NSW 图
如果我们继续以这种方式插入顶点并构建图形,我们可以得到如下所示的 NSW 图形。

新南威尔士州图表示例
随着越来越多的顶点被插入,可以观察到在早期阶段建立的一些连接已经变成了长距离链接。例如,看看上图中的链接“A — B”和“C — D”。
新南威尔士州——搜索
在新南威尔士州图上的搜索遵循简单的贪婪搜索方法。
在每一步中仅使用局部信息,并且不需要数据的维度或分布的先验全局知识。这就是新州的妙处,可以从任何一个顶点发起搜索。
可以随机选择搜索的入口点。从当前点开始,该算法将试图找到离查询向量最*的朋友,并向该顶点移动。
如下所述,重复该步骤,直到它再也找不到任何比自己更接*查询向量的朋友。

在新南威尔士州图上搜索最*邻(示例 1)
下图显示了在构建的早期阶段创建的远程链接如何有利于一些搜索案例。在这个例子中,入口点、A 和查询向量位于图的相对两端。由于长距离链接“A — B”,搜索过程只需两跳(A→B→K)即可到达查询向量的最*邻居。

在新南威尔士州图上搜索最*邻(示例 2)
新南威尔士州的搜索质量可以通过使用随机入口点执行多次搜索来提高。
3.跳过列表
跳表是 W. Pugh [3]发明的一种数据结构,基于链表构建,元素排序。
下面是一个链表的例子,其中的元素已经排序。链表上的节点包含元素的值和指向下一个元素的指针。

具有排序元素的链表
跳表由多层链表组成,其中原始链表在最底层(0 级)。较高的级别包含在级别 0 上找到的相同元素,但只是它们的子集。级别越高,元素的数量越少。

跳转列表数据结构
跳过列表的上层充当高速公路,因为一些元素在遍历期间被跳过。
这使得在跳过列表中快速搜索元素。搜索从最顶层开始,遍历节点,只有当前指针指向下一个值大于要搜索的值的元素时,才向下搜索。
例如,搜索‘91’只需要遍历 3 个节点,如果要在原始链表上进行搜索,则需要遍历 8 个节点。

在跳过列表中搜索 91
4.分层可导航小世界
HNSW 是新南威尔士州的新发展,其中的层次结构构成了一种优雅的细化,其灵感来自于 skip list 的分层结构。
HNSW 中的分层组合将不同长度的链路分成不同的层。远程链接可以在上层找到,而短程链接在底层。
长距离链接在减少到达正在寻找的最*邻居附*所花费的时间和精力方面起着重要的作用。

HNSW 的一个例子
HNSW——建筑
HNSW 的构建与 NSW 非常相似,其中的图结构也是通过一个接一个地插入顶点来增量构建的。
然而,与 NSW 不同,顶点在插入之前不需要被打乱。这是因为 HNSW 中的随机效应是由水*随机化完成的。这是如何工作的?
嗯,在构建图形的过程中,每个顶点被随机分配一个整数,ℓ,表示该顶点所在的最大层数。例如,如果一个顶点被赋予了ℓ=3,那么我们可以找到存在于第 2 层、第 1 层和第 0 层的顶点。
“以指数衰减概率分布随机选择元素所在的最大层”——y . Malkov 和 D. Yashunin
这使得ℓ=1成为顶点最有可能收到的赋值,并且获得更大的ℓ值的概率极低。
因此,大多数顶点总是出现在第 0 层。随着级别数的增加,这些级别上的顶点数会显著减少。为了了解ℓ的分布情况,请看下面来自 50 万个向量的随机数据集的样本。

来自 500,000 个向量的随机数据集的ℓ分布
插入过程
在插入过程中,在每一层上,该算法贪婪地探索顶点的邻域,并维护当前找到的新插入顶点的最*邻居的动态列表。
对于从顶层开始的插入过程的第一阶段,这个动态列表的大小被设置为 1。然后,找到的最*的顶点被用作下一层的入口点,搜索继续进行。
一旦达到低于ℓ的水*,这标志着插入过程的第二阶段。从这一点开始,动态列表的大小遵循由名为efConstruction的参数设置的值。除了作为下一层(如果有的话)的入口点之外,当前找到的最*邻居也是候选的朋友,其中的M将被选择来与当前层上新插入的顶点建立连接。
当新插入顶点的连接在级别 0 建立时,插入过程结束。
根据论文【2】,“通过评估列表中最*的先前未评估的元素的邻域,在每一步更新动态列表,直到评估列表中每个元素的邻域”。
因此
efConstruction(设置动态候选列表的大小)可以被定义为控制要探索的候选邻居数量的参数。它标志着施工期间的勘探深度。
启发式选择
之前,我们了解到对于新南威尔士州,将选择最接*新顶点的M个现有顶点进行连接。这是一种简单的方法,只选择并连接到最*的顶点。对于 HNSW 来说,使用启发式方法建立连接是提升性能的一个进步。
启发式选择不仅考虑了顶点之间的最短距离,还考虑了图上不同区域的连通性。
考虑下面的例子。很明显,右边的两个集群没有直接连接。为了从 A 到 Y,人们必须穿过一条很长的路线,通过左边的另一个星团。

使用M=2,当我们在如下所示的位置插入一个新的顶点 N 时,我们得到 4 个候选的朋友, A,B,X 和 Y,其中 2 个将被选择与 N 连接

启发式选择
在 4 个候选点中,A 最接*新顶点。因此,选择 A 连接到 n。
距离 N 最*的下一个顶点是 B。但是,因为距离“B — A”小于距离“B — N ”,所以忽略顶点 B,而考虑下一个最*的顶点 Y。
此时,试探选择将 N 连接到 Y,因为距离‘Y-A’大于‘Y-N’。通过这样做,我们现在能够在右侧的两个集群之间建立直接连接。
该启发式算法增强了顶点邻域的多样性,并且对于高度聚集的数据的情况导致了更好的搜索效率。
在构建过程中,除了为每个新顶点建立M连接,HNSW 还关注每个顶点在任一时间点的连接总数。为了保持小世界的可导航性,对于 0 级,每个顶点的最大连接数通常被限制在*2*M*,对于所有其他级别,最大连接数被限制在*M*。
(B)HNSW-搜索
HNSW 的搜索过程从顶层的指定入口点开始。它遵循与插入过程所描述的相同的搜索过程,贪婪地探索顶点的邻域以找到最接*查询向量的顶点并向其移动。
当它再也找不到任何比自己更接*查询向量的顶点时,它就下降到下一级。
搜索继续进行,直到到达底层(层 0),在这一点上将探索更多的候选邻居。当前找到的最*邻居的动态列表的大小将遵循由名为efSearch的参数设置的值。一旦完成了所有需要的探索,就会返回动态列表中的k个最*邻居。

在 HNSW 图上搜索最*邻
5.用 Faiss 实现:IndexHNSWFlat
Faiss(脸书 AI 相似性搜索)是一个针对高效相似性搜索进行了高度优化的库。
在 Faiss 中,HNSW 通过IndexHNSWFlat实现。Faiss 中的索引是一个数据结构,一个可以使用add方法将向量添加到索引中的对象,以及使用search方法在给定一些查询向量的情况下执行最*邻搜索。*面索引通常是指没有任何压缩或编码的索引,其中实际向量按原样存储。
M、efConstruction 和 efSearch 的效果
在本节中,我们将绘制并检查M、efConstruction和efSearch对 HNSW 的影响。
概括来说,
*M*是在构建过程中为每个新顶点建立的连接数。efConstruction是构建期间要探索的候选邻居的数量,而efSearch是搜索期间要探索的候选邻居的数量。为了生成这些图,300 万个 128 维向量被添加到
IndexHNSWFlat,并且使用 1000 个查询向量来执行搜索。这些是使用 Faiss 数据集模块生成的合成矢量。准确性,或者说搜索质量,是由检索性能决定的。在我们的例子中,它是用
1-recall@3测量的。这是在每个查询的前 3 个结果中返回真正最*邻的查询向量的分数。
以下是对上述参数对 HNSW 的影响的观察。
对于查询向量搜索,速度受M、efConstruction和efSearch的影响。当这些参数的值增加时,搜索通常会变慢。

1,000 个查询向量的每个查询的*均搜索时间(毫秒)。
同样的参数也会影响搜索质量。更大的M、efConstruction或efSearch值可以获得更高的召回率。
请注意,将efConstruction设置为太小的值对获得合理的性能没有帮助。在这种情况下提高M或efSearch的值没有帮助。为了获得满意的结果,绝对需要一个可行的efConstruction值。
例如,在我们的例子中,efConstruction=40连同M=32和efSearch=16能够以每个查询 0.24 毫秒的快速搜索速度达到 0.85 的令人印象深刻的召回率。这意味着在 85%的情况下,每个查询的前 3 个结果中都会返回真正的最*邻。
如果我们不介意较慢的搜索,将efSearch增加到 76 甚至可以将召回率提高到接*完美的 0.97 分!

1000 个查询向量的召回性能。
可以看到,在M、efSearch和efConstruction的高值达到某一点后,回忆开始慢慢趋于*稳。此外,请注意为M和efConstruction设置较高的值会导致构建时间明显延长,如下图所示。

300 万个 128 维向量的构建时间。
最后,指示内存使用的 HNSW 的索引大小随着M线性增长。

300 万个 128 维向量的索引大小(MB)。
下面的气泡图总结了M、efConstruction和efSearch对 HNSW 的搜索速度和准确性的影响,以及对索引大小和构建时间的影响。
****
如果内存不是问题,HNSW 是提供快速和高质量搜索的绝佳选择。
6.用 Faiss 实现:IndexIVFPQ + HNSW
厉害!我们已经学习了如何用 Faiss 实现 HNSW。但是我们如何将 HNSW 与 IVFPQ 一起使用呢?
在 Faiss 中, IVFPQ 用IndexIVFPQ实现。
我们之前看到的
IndexHNSWFlat现在作为IndexIVFPQ的粗量化器。粗略量化器负责寻找最接*查询向量的分区质心,以便只需要在那些分区上执行向量搜索。
下面的代码显示了如何用IndexHNSWFlat创建一个IndexIVFPQ作为粗量化器。
注意IndexIVFPQ需要在添加数据库向量之前用 k-means 聚类进行训练。为此,使用了train方法。一旦创建了索引,就可以设置nprobe的值来指定要搜索的分区数量。
在 Faiss 中,也可以使用
[index_factory](https://github.com/facebookresearch/faiss/wiki/The-index-factory)函数而不是类构造函数来创建索引。这种替代方法非常有用,尤其是对于构建复杂的复合索引,这种索引可能包含预处理步骤,如 PCA ,或者其他类型的向量转换和细化。使用
index_factory,通过指定向量的维数(例如 128)和工厂字符串(例如“IVF10000_HNSW32,PQ16”)作为函数的输入,可以将几行代码简化为一行。
index = faiss.index_factory(128, "IVF10000_HNSW32,PQ16")
上面的代码使用
*index_factory*为 128 维向量创建一个索引。它是一个具有 10,000 个分区的倒排文件索引,使用 16 个 8 位段的乘积量化(如果未指定位数,则默认为 8 位)。粗略量化器是基于图形的 HNSW 索引,对于每个新顶点有 32 个连接。
7.HNSW 指数比较(有/无 IVF 和/或 PQ)
在本节中,我们将研究 HNSW 索引的不同变体,以及它们在搜索准确性、速度和内存使用方面的比较。它们是:
- IVFPQ+HNSW
- 试管婴儿+HNSW
- HNSW
- HNSW+PQ
Faiss index_factory用于通过以下工厂字符串创建这四种类型的索引:
IVF65536_HNSW32,PQ32 :这本质上是 IVFPQ+HNSW 。它是一个具有 65,536 个分区的倒排文件索引,使用 32 个 8 位段的乘积量化。粗量化器是基于图形的 HNSW 索引,带有
M=32。IVF65536_HNSW32,* :这本质上是 IVF+HNSW 。它是一个有 65,536 个分区的倒排文件索引。粗量化器是基于图形的 HNSW 索引,带有
*M=32*。HNSW32,Flat :这本质上只是 HNSW ,一个基于图形的索引,带有
*M=32*。HNSW32_PQ32 :这个本质上是 HNSW+PQ 。它是一个基于图形的 HNSW 索引,带有
*M=32*,使用乘积量化,有 32 段,每段 8 比特。
和之前一样,在这些索引中加入 300 万个 128 维向量,使用 1000 个查询向量进行搜索。至于efConstruction和efSearch,使用 Faiss 的默认值。
在四种类型的索引中, IVFPQ+HNSW 在索引大小或内存使用方面是冠军。它的大小只有 154 MB,内存效率比单独的 HNSW 高 15 倍。

300 万个 128 维向量的索引大小(MB)。
对于基于 IVF 的索引,增加nprobe(要搜索的分区数量)会导致更长的搜索时间,但会提高召回性能。
根据使用的nprobe的值,基于 IVF 的索引可以比 HNSW 或 HNSW+PQ 的性能更好或更差。
从下面的图表中,我们可以了解当nprobe=128用于基于 IVF 的索引时,索引的相对性能。
****
选择合适的
nprobe是*衡搜索速度和准确性的关键。
8.摘要
下表显示了四种索引的召回性能与搜索时间的关系,以及它们各自的大小(MB)。
对于基于 IVF 的索引,使用的nprobe是 128。
结果可以从下面的图表中看到。在条形图上,我们可以看到四种类型的索引在搜索速度上的巨大差异,相比之下,在回忆性能上的差异更为细微。

下面的气泡图很好地展示了搜索时间与回忆性能的对比。气泡的大小对应于索引的大小,这表示内存使用情况。

在搜索速度方面,可以观察到基于试管婴儿的索引(绿色和橙色气泡)领先。由于搜索的非穷举性质,它们的速度非常快,因为搜索只在一小部分分区上执行。
在准确性方面,HNSW(红色气泡)领先,PQ 指数(绿色和蓝色气泡)落后。PQ 指标是带有乘积量化的指标。由于它们进行有损压缩,因此预期它们具有较低的精度。然而,它们的弱点被内存中的巨大节省所高度补偿。
显然, IVFPQ+HNSW (绿色泡泡)无论是搜索速度还是内存效率都是赢家。它拥有最小的内存占用(哦,看看绿色泡泡有多小),通过使用nprobe=128,它成功实现了每个查询 0.03 毫秒的最快*均搜索时间,并且具有 0.77 的良好召回率。
如果需要更高的召回率呢?如下图所示,增加nprobe可以达到目的,但代价是搜索时间更长。记住,选择一个合适的nprobe是在权衡利弊中取得明智和现实结果的基础。

增加nprobe对基于 IVF 的指标的影响。当nprobe增加时,绿色和橙色的气泡向右上方移动。
IVFPQ 与作为粗量化器的 HNSW 一起提供了一种极其节省内存且无可挑剔的快速搜索,对于大规模*似最*邻搜索具有良好的准确性。
毫不奇怪,凭借速度、内存使用和准确性的良好*衡, IVFPQ+HNSW 成为十亿级相似性搜索的最佳索引方法。

照片由 Japheth Mast 在 Unsplash 拍摄
参考
[1] Y. Malkov,A. Ponomarenko,A. Logvinov 和 V. Krylov,基于可导航小世界图的*似最*邻算法 (2013)
[2] Y. Malkov 和 D. Yashunin,使用分层可导航小世界图的高效和鲁棒的*似最*邻搜索 (2018)
[3] W. Pugh,跳过列表:*衡树的概率替代方案 (1990),《美国计算机学会通讯》,第 33 卷,第 6 期,第 668-676 页
[4] J .布里格斯,分层可导航小世界(HNSW)
[5] Y. Matsui,亿级*似最*邻搜索 (2020)
[6] Faiss Wiki
[7] J. Arguello,评价指标
***Before You Go...****Thank you for reading this post, and I hope you’ve enjoyed learning the best indexing approach for billion-scale similarity search.**If you like my post, don’t forget to hit* [***Follow***](https://peggy1502.medium.com/) *and* [***Subscribe***](https://peggy1502.medium.com/subscribe) *to get notified via email when I publish.**Optionally, you may also* [*sign up*](https://peggy1502.medium.com/membership) *for a Medium membership to get full access to every story on Medium.*📑 *Visit this* [*GitHub repo*](https://github.com/peggy1502/Data-Science-Articles/blob/main/README.md) *for all codes and notebooks that I shared in my posts.*© 2022 All rights reserved.
有兴趣阅读我的其他数据科学文章吗?查看以下内容:
** 
掌握动态编程系列
View list2 stories
https://pub.towardsai.net/building-a-product-recommendation-engine-with-aws-sagemaker-321a0e7c7f7b **
一月版:数据科学入门
原文:https://towardsdatascience.com/january-edition-getting-started-in-data-science-a493660213d
月刊
一月版:数据科学入门
热烈欢迎所有新的(和有抱负的)数据科学家,开始新的一年

在 Unsplash 上由Towfiqu barb huya拍摄的照片
我们喜欢 TDS 上的深度探讨和前沿研究,但我们总是对那些在数据科学之旅的早期阶段指导初学者的文章情有独钟。现在,2022 年已经到来,我们希望欢迎我们的社区回来,并祝愿每个人在这一年中学习和成长,特别是对于那些刚刚加入该领域或正在考虑明年转向数据科学的人。
从零开始(或几乎从零开始)最困难的一个方面是你必须做出令人眼花缭乱的选择。我们希望提供帮助,因此在我们的一月版中,我们针对有抱负的新数据科学家收集了一些最佳资源。我们的档案中还有很多——做出这个选择本身就是一个充满艰难选择的过程!—但我们都必须从某个地方开始。
参考和一般资源
从结构化课程和课程表到如何迈出第一步的建议,如果你感到不知所措,需要更有经验的数据科学家的温和指导,这些帖子是一个很好的起点。
- 如何在 6 个月内从非编码人员成长为数据科学家 作者莎兰·库马尔·拉文德兰 (10 分钟)
一个包含所有必需资源的完整指南。 - </5-tips-to-boost-your-data-science-learning-320a2284690>Julia Nikulski(9 分钟)
提高数据科学学习的 5 个技巧通过专注于这 5 件事而不是参加更多的在线课程来增加你的学习曲线。 - 想在 12 周内成为数据科学家? by 拉希达·纳斯林·阿摩林 (5 分钟)
花钱前三思。 - 2021 年成为数据科学家的完整 52 周课程 作者 Terence Shin (10 分钟)
现在已经是 2022 年了,但去年的这份便利资源仍然非常有价值。 - 追踪你的数据科学进展的清单 作者帕斯卡·简奈茨基 (14 分钟)
利用每周一天的原则逐步打勾。
个人经历和经历
有时候,最有帮助的见解来自于倾听那些和你有着相同经历或过程的人。这里有一些个人的想法,可能会给你一些灵感。
- 如果我不得不重新开始 作者 Nicholas (7 分钟)
我会如何学习数据科学的 Python,包括三个帮助你避免犯同样错误的技巧。 - 我个人从艺术向科技过渡的故事 作者杰西·鲁伊斯(她/他们) (4 分钟)
一个参加编码训练营的案例。 - 对于新数据科学家来说,领域知识有时比技术技能更重要 作者 TDS 编辑
Sophia Yang 讲述了持续学习和寻找优秀同事和导师的重要性。 - 从社会科学过渡到数据科学 作者丹尼·金博士 (13 分钟)
你知道的和你应该知道的。 - “你是如何开始机器学习和数据科学的?” 作者丹尼尔·伯克 (10 分钟)
2017 年开始的旅程,在一个朋友的休息室地板上。
聚焦特定主题,一步一个脚印
一旦你开始并取得一些初步进展,你可能会觉得有太多的东西需要学习。下面的帖子为数据科学家提供了一个可访问的、初学者友好的关键领域的门户,无论你是想深化关于大主题(编程、统计)的知识,还是更专注的主题。
- 2022 年学习数据科学 Python 的完整 26 周课程 作者弗兰克·安德拉德 (6 分钟)
在 26 周内学习数据科学所需的大部分 Python 知识。 - 数据科学的 8 个基本统计概念 作者丽贝卡·维克里 (8 分钟)
统计学基础知识,用通俗易懂的英语解释。 - 5 种超参数优化方法每个数据科学家都应该使用byAmal Hasni(8 分钟)
了解网格搜索、逐次减半、贝叶斯网格搜索等等。 - 数据科学入门意味着清理你的数据首先 由 Brandon Cosley (10 分钟)
一个探索性的数据分析(EDA)工作流程,用于减少数据中的杂乱。 - Git 简易入门指南,第 1 部分 作者 Julia Kho (12 分钟)
在一个简短的会话中学习 Git 的基础知识。 - 如何掌握数据科学的熊猫 作者 Chanin Nantasenamat (12 分钟)
使用这个流行的 Python 库需要的所有要素。
当然,总有更多的东西需要发现和探索——如果你想查看更多我们精心策划的特定主题列表,请前往我们的了解数据科学页面。
在我们今天离开你之前,我们想做的最后一件事是问候所有在 2021 年最后几周首次分享他们在 TDS 上的工作的作者。(如果向我们的出版物投稿是您今年的工作清单之一,我们期待着阅读它!)他们包括雅克·卡斯滕斯、沃恩·斯普里耶、赛义德·萨阿德·侯赛因、内奥米·克里格、萨拉·琼斯、穆尔塔扎·阿里、伊曼纽·法比亚尼、安德鲁·纽赫、弗兰克·科里甘、保罗·弗罗林、亚历山大 托马斯·范东恩,安东尼·克罗斯,哈利勒·杜伊古卢,摩西·瓦瑟布拉,王俊凯,戴斯勒年轻的拯救者,扎克·布卢门菲尔德,菲利普·辛格,阿方索·圣克鲁斯·加西亚,安妮·辛克莱 大家 2022 年快乐!
用 Python 进行 Jarque-Bera 测试
原文:https://towardsdatascience.com/jarque-bera-test-with-python-98677c073de3
让您的数据遵循正态分布非常好,因为它允许您利用正态分布提供的所有好处。但是我们如何知道我们的数据遵循正态分布呢?这就是 Jarque-Bera 测试的用武之地。在本文中,我们将了解 Jarque-Bera 测试,如何使用 Scipy 将它应用于 Python 中的数据,以及一些快速练习,并从头开始创建测试统计等式。

茱莉亚·科布利茨在 Unsplash 上的照片
什么是 Jarque-Bera 测试?
Jarque-Bera 测试是一种基于数据的偏斜度和峰度来确定一组数据值是否遵循正态分布的测试。包含偏度和峰度的检验统计方程为:

Jarque Bera 检验统计方程。作者在 https://latex.codecogs.com/eqneditor/editor.php制作
其中 n =数据值的数量。s 是样本偏斜度(数据偏离*均值的程度),定义如下:

Jarque Bera 检验统计方程的样本偏度。作者于https://latex.codecogs.com/eqneditor/editor.php制作
k 是样本峰度(分布的尾部有多厚),定义如下:

Jarque Bera 检验统计方程的样本峰度。作者于https://latex.codecogs.com/eqneditor/editor.php发表
测试统计结果将总是大于或等于零,因为:
- 检验统计等式中的样本偏度总是*方的,这意味着 S 总是正的或零。
- 样本峰度总是正的或零,因为分子是 4 次方,分母是*方。
- 样本峰度和 3 之间的差是*方,意味着检验统计等式的这一项总是正的或零。
- 两项之和≥ 0 也将大于或等于零。
如果检验统计量接*于零并且 p 值大于我们的标准值 0.05,我们就知道我们的数据是否遵循正态分布。p 值与数据遵循正态分布的无效假设相关。如果检验统计量很大且 p 值小于 0.05,则数据不符合正态分布。
现在我们已经建立了测试统计,您可以提取一个具有数字特征的数据集,并对它们应用 Jarque-Bera 测试。对于本文,我将使用来自纽约open data的流行婴儿名字数据集。
数据设置和探索
首先,我们将加载必要的导入包并导入数据。注意我们从 Scipy 导入了jarque_bera函数。然后我们用pd.info()检查数据。

pandas.info()关于流行婴儿名字数据的结果
查看 info 方法的结果,我们看到一个可以应用 Jacque-Bera 测试的Count列。这一列是有名字的婴儿总数。我们还看到在那个列中没有丢失的值,所以我们不需要对它应用像dropna()这样的方法来删除空值。考虑到有一个Year of Birth专栏,让我们看看我们还有多少年的时间。

对 baby_names[“出生年份”]调用 value_counts 方法的结果。
所以我们有 8 年的婴儿名字。有趣的是,前几年的名字总数比后半年多。我还注意到,2018 年被跳过了,在数据的网页上没有任何关于这一点的内容。就本文的目的而言,这不会造成任何伤害。
Scipy 示例
应用我们的正态分布测试的快速方法是对我们的数据列应用scipy.stats函数,如下所示。
这将输出一个元组,左边是测试统计数据,还有一个 p 值。
(533643.0597435309, 0.0)
左边的值是本文开头的 JB 方程的测试统计量。右边的值是我们假设数据遵循正态分布的零假设的 p 值。它是由 1 与卡方分布的 CDF 之差计算得出的,卡方分布具有应用于检验统计的两个自由度。CDF 再次代表累积分布函数,用于确定分布中一系列可能性的概率。
由于测试统计量非常大,p 值小于 0.05,我们拒绝了零假设,即Counts数据来自正态分布。
附加实践
如果你正在使用这个数据集并希望进一步实践,我建议你对所有 8 年的Count数据进行 Jarque-Bera 测试。您可以单独完成这项工作,也可以创建一个 for 循环,每年进行一次迭代。当你准备好检查答案时,请看下面。
答案

Jarque-Bera 每年的计数结果
与所有年份的Count列的结果相似,当 p 值小于 0.05 时,测试统计值非常高。因此,假设每年的数据来自正态分布的零假设被拒绝。
从头开始示例(编码实践)

在这里,我们将尝试从头开始创建 Jarque-Bera 检验统计方程。这将是一个很好的编码练习,因为公式在数学符号方面并不复杂。为了简单起见,让我们集中使用 Pandas 数据框列作为输入。在你尝试之后,你可以看看我下面的版本。
注意我的可能不是最佳的,因为我列出了更多的步骤。对于正在编写的 Jarque-Bera 最干净的版本,请查看 Jarque-Bera 测试文档中的 Scipy 源代码。我建议您查看源代码,看看这个测试的 p 值是如何使用测试统计的卡方 CDF 计算的。截至本文撰写时,chi2是从scipy.stats引进的。
什么时候使用 Jarque-Bera 测试最好?

兰迪·塔兰皮在 Unsplash 上的照片
假设有其他方法来测试数据是否来自正态分布,我们需要看看什么时候最好使用 Jarque-Bera 测试。根据这些 来源,建议一个样本的总数据值较大。至少大于 2000。这使得数据可以与具有 2 个自由度的卡方分布进行比较。任何更小的值都可能导致错误的测试统计和 p 值。
最后的想法
感谢阅读!同样,让您的数据遵循正态分布非常好!它允许您利用均值、中值和众数都相等的对称分布。当您在数据中处理更多样本时,您还可以依赖中心极限定理。您只需要一个像 Jarque-Bera 测试这样的测试来帮助您确定您是否正在使用正态分布。
如果你喜欢在媒体上阅读,并愿意进一步支持我,你可以使用我的推荐链接注册一个媒体会员。这样做可以用你的会费的一部分在经济上支持我,我将不胜感激。
如果您有任何问题,请随时在 Linkedin和 Twitter上发表评论或联系我们。对 Twitter 上的 DM 开放。如果你喜欢这篇文章,也可以看看我下面的其他相关文章:
直到下一次,
约翰·德杰苏斯
GPU 和 TPU 上的 jax-Numpy
原文:https://towardsdatascience.com/jax-numpy-on-gpus-and-tpus-9509237d9194
了解该库并从零开始实施 MLP

卢卡斯·凯普纳在 Unsplash 上的照片
有许多 Python 库和框架,因为它们是我们天空中的星星。好吧,也许没有那么多,但在处理任何给定的任务时,肯定有很多选项可供选择。
Jax 就是这些库中的一个。在过去的几个月里,它作为开发机器学习解决方案的基础框架变得非常流行,尤其是在被 Deep Mind 的人大量使用之后。
对于任何数据科学家来说,比工具更重要的是手头任务的基础知识。然而,对可用工具有一个很好的了解可能会节省很多时间,并使我们成为更有生产力的研究人员。毕竟,如果我们的目标是实现良好的业务结果,我们需要能够快速安全地运行我们的假设。
因此,在这篇文章中,我将谈论 Jax,解释它是什么,为什么我认为应该熟悉它,它的优点,以及如何使用它来实现一个简单的多层感知器。
在这篇文章的最后,我希望你的工具箱里又多了一个在日常工作中可能有用的工具。
这篇文章的所有代码都可以在 Kaggle 和我的 Github 上找到。
什么是 Jax
Jax 是一个数字/数学库,非常类似于著名的 know Numpy。它是由谷歌开发的,目的只有一个:在处理典型的机器学习任务时,让 Numpy 更容易使用,更快。
为了实现这个结果,Jax 有一些内置的实现,允许大规模处理,如并行化和向量化,更快的执行,如实时编译,以及更简单的机器学习代数,如自动签名。
有了它,人们可以自然地加速他们的机器学习管道,而不必担心写太多代码。如果您使用多处理库来并行化您的实现,您知道这会很快变得难以承受。
但是,更有趣的是,Jax 能够直接在 GPU 和 TPU 等加速器上自动编译您的代码,而不需要任何修改。这个过程是无缝的。
这意味着您可以使用类似于 Numpy 的语法编写一次代码,在您的 CPU 上测试它,然后将其发送到 GPU 集群,而无需担心任何事情。
现在,有人可能会问一个非常合理的问题:Jax 与 TensorFlow 或 Pytorch 有何不同?让我们调查一下。
Jax x 张量流 x Pytorch
TensorFlow 和 Pytorch 已经在操场上待了很长时间了。他们正在充分利用深度学习库的实现来开发端到端的深度学习管道。
Jax 对成为一个完整的深度学习库没有兴趣。它的目标是成为加速器的核心。因此,您不会看到 Jax 以同样的方式实现数据加载器或模型验证器,您也不应该期望 Numpy 这样做。
但是在我们说话的时候,已经有深度学习库在 Jax 中实现了。他们的目标是使用库中这些优秀的功能来创建更快更干净的实现。众所周知,TensorFlow 和 Pytorch 都遭受了一些技术债务,这使得它们更难高效地开发一些东西。
我相信基于 Jax 的框架将来会在我们的行业中变得更加突出,所以我认为当标准库(不可避免地)发生变化时,了解 Jax 的基础知识是一个很好的开端。
关于 Jax 的一些细节
Jax 的目标是尽可能接* Numpy。有一个低级的库实现,但是这已经超出了本文的范围。现在,我们将认为我们将完全像“GPU 的 Numpy”一样使用它。
首先,我们将安装 Jax:
pip install --upgrade "jax[cpu]"
这个命令只会为我们安装 CPU 支持来测试我们的代码。如果您想要安装 GPU 支持,请使用:
pip install --upgrade "jax[cuda]"
请注意,您必须已经安装了 CUDA 和 CuDNN 才能工作。
然后,我们将导入 Numpy 接口和一些重要函数,如下所示:
import jax.numpy as jnpfrom jax import random
from jax import grad, jit, vmap
from jax.scipy.special import logsumexp
我们现在必须了解 Jax 的一些不同方面。第一个是如何处理随机数生成器。
Jax 中的随机数
通常,当我们处理 Numpy 时,我们设置一个随机状态整数(比如 42),并用它来为我们的程序生成随机数。scikit-learn 等传统机器学习库使用这种范式。
当我们处理顺序执行时,这样做很好,但是,如果我们开始并行运行我们的函数,这就成了一个问题。让我告诉你怎么做:
- 我们来定义两个函数,bar 和 baz。每个都将返回一个随机数。
- 让我们定义一个函数 foo,它将对前面两个函数的结果进行计算
在代码中:
np.random.seed(0)
def bar(): return np.random.uniform()
def baz(): return np.random.uniform()
def foo(): return bar() + 2 * baz()
print(foo())
这里我们设置种子等于零的随机状态。如果你运行这个代码二十次,你将会得到同样的结果,因为随机状态被设置了。
但如果我们在酒吧前给巴兹打电话呢?
np.random.seed(0)
def bar(): return np.random.uniform()
def baz(): return np.random.uniform()
def foo(): return 2 * baz() + bar()
print(foo())
是的,结果不一样。这是因为函数的执行顺序不再相同。来自随机状态的保证是相同的执行将产生相同的结果,在这种情况下,这不是真的。
现在你可以看到当试图并行化我们所有的函数时,这是如何成为一个问题的。我们不能保证执行的顺序,因此,没有办法强制我们得到的结果的可再现性。
Jax 解决这个问题的方法是定义伪随机数生成器密钥,如下所示:
random_state = 42
key = random.PRNGKey(random_state)
Jax 中的每个随机函数都必须接收一个密钥,并且这个密钥对于每个函数必须是唯一的。这意味着,即使执行的顺序改变了,结果也是一样的,因为我们使用的键是一样的。
然后我们需要创建一个键列表,每个功能一个键?好吧,那真的很麻烦,所以 Jax 实现了一个叫做 split 的简便方法,它接收一个键并把它分割成所需数量的子键,然后我们可以把它传递给我们的函数:
*# Here we split our original key into three subkeys*
random.split(key, num=3)
这将返回一个包含 3 个键的列表。这样,我们可以确保在执行过程中结果的可重复性。
自动微分
Jax 的主要目标之一是自动区分原生 Python 和 NumPy 函数。当处理机器学习时,这是必需的,因为我们的大多数优化算法使用函数的梯度来最小化一些损失。
在 Jax 中做微分非常简单:
def func(x):
return x**2d_func = grad(func)
就这样。现在,函数 d_func 会返回函数的导数,当你给它传递一个值 x 时。
好的一点是,你可以多次应用梯度来得到高阶导数。如果我们要创建一个返回 func 的二阶导数的函数,我们只需做:
d2_func = grad(d_func)
即时编译
Python 是一种解释型语言。还有其他语言,比如 C,叫做编译语言。在编译语言中,代码由编译器读取,并生成机器代码。然后,这个机器码在你调用程序的时候执行。
解释型语言为开发人员提供了一些优势,比如不需要设置变量的数据类型。然而,由于代码不是编译的,所以它通常比编译语言慢。
一些 Python 库如 Numba 实现了所谓的实时(JIT)编译。这样,解释器第一次运行一个方法时,它会把它编译成机器码,这样以后的执行会更快。
如果你有一个要运行一万次的方法(比如在一个训练循环中的梯度更新),JIT 编译可以大大提高你代码的性能。用 Jax 来做非常简单:
def funct(x):
return x * (2 + x)compiled_funct = jit(funct)
请注意,不是每个函数都可以编译。我建议您阅读 Jax 文档,以全面了解这种方法的局限性。
我们将在稍后的帖子中看到由此带来的性能提升。
…向量化…
矢量化是一个过程,在这个过程中,我们的操作(通常发生在一些单位(例如整数)上)被应用到向量中,这允许这些计算并行发生。
这个过程可以在我们的管道中产生巨大的性能改进,Jax 库有一个名为 vmap 的内置函数,它接收一个函数并自动将其矢量化给我们。
在下一节中,当我们实现 MLP 时,我们将看到如何在 for 循环中应用它的例子。
在 Jax 中实现 MLP
现在,让我们在 Jax 中实现一个 MLP 来练习我们所学的关于库的知识。为了帮助我们,我们将从 TensorFlow 数据加载器加载 MNIST 数据集。该数据集可以免费使用:
import tensorflow as tf
import tensorflow_datasets as tfdsdata_dir = '/tmp/tfds'
mnist_data, info = tfds.load(name="mnist", batch_size=-1, data_dir=data_dir, with_info=True)
mnist_data = tfds.as_numpy(mnist_data)
train_data, test_data = mnist_data['train'], mnist_data['test']
num_labels = info.features['label'].num_classes
h, w, c = info.features['image'].shape
num_pixels = h * w * c
train_images, train_labels = train_data['image'],train_data['label']test_images, test_labels = test_data['image'], test_data['label']
在这里,我们只是下载数据,并将其分为训练和测试,以便我们稍后训练我们的模型。
让我们创建一个助手函数来一次性编码我们的目标:
def one_hot(x, k, dtype=jnp.float32):
*"""*
*Create a one-hot encoding of x of size k.*
*x: array*
*The array to be one hot encoded*
*k: interger*
*The number of classes*
*dtype: jnp.dtype, optional(default=float32)*
*The dtype to be used on the encoding*
*"""*
return jnp.array(x[:, None] == jnp.arange(k), dtype)
现在,让我们对标签进行编码,并将图像转换为 jnp 张量:
train_images = jnp.reshape(train_images, (len(train_images), num_pixels))
train_labels = one_hot(train_labels, num_labels)test_images = jnp.reshape(test_images, (len(test_images), num_pixels))
test_labels = one_hot(test_labels, num_labels)
最后,我们将定义一个函数,该函数将生成供我们在训练循环中使用的批量数据:
def get_train_batches(batch_size):
*"""*
*This function loads the MNIST and returns a batch of images given the batch size*
*batch_size: integer*
*The batch size, i.e, the number of images to be retrieved at each step*
*"""*
ds = tfds.load(name='mnist', split='train', as_supervised=True, data_dir=data_dir)
ds = ds.batch(batch_size).prefetch(1)
return tfds.as_numpy(ds)
现在我们的数据已经准备好了,让我们开始实现吧。
初始化参数
首先,我们将初始化 MLP 每一层的权重。对于这篇文章,我们将随机启动它们。为此,我们必须使用伪随机数生成器密钥来保证我们的所有执行都是可重复的。
def random_layer_params(m, n, key, scale=1e-2):
*"""*
*This function returns two matrices, a W matrix with shape (n, m) and a b matrix with shape (n,)*
*m: integer*
*The first dimension of the W matrix*
*n: integer*
*The second dimension of the b matrix*
*key: PRNGKey*
*A Jax PRNGKey*
*scale: float, optional(default=1e-2)*
*The scale of the random numbers on the matrices*
*"""*
*# Split our key into two new keys, one for each matrix*
w_key, b_key = random.split(key, num=2)
return scale * random.normal(w_key, (m,n)), scale * random.normal(b_key, (n,))
此方法接收该层的神经元数量及其后一层的神经元数量。此外,我们传递密钥,这样我们就可以分裂它。
现在,让我们创建一个函数,它接收一个包含层大小(神经元数量)的列表,并使用随机层生成器以随机权重填充所有层:
def init_network_params(layers_sizes, key):
*"""*
*Given a list of weights for a neural network, initializes the weights of the network*
*layers_sizes: list of integers*
*The number of neurons on each layer of the network*
*key: PRNGKey*
*A Jax PRNGKey*
*"""*
*# Generate one subkey for layer in the network*
keys = random.split(key, len(layers_sizes))
return [random_layer_params(m, n, k) for m, n, k **in** zip(layers_sizes[:-1], layers_sizes[1:], keys)]
预测函数
现在我们将创建一个函数,给定一幅图像和权重,它将输出一个预测。为此,我们将首先定义一个 ReLU 函数:
def relu(x):
return jnp.maximum(0, x)
现在,对于每一层,我们必须将权重应用于图像,对偏差求和,将 ReLU 应用于结果,并为下一层传播该激活,因此该方法看起来像这样:
def predict(params, x):
*"""*
*Function to generate a prediction given weights and the activation*
*params: list of matrices*
*The weights for every layer of the network, including the bias*
*x: matrix*
*The activation, or the features, to be predicted*
*"""*
activations = x
for w, b **in** params[:-1]:
output = jnp.dot(w.T, activations) + b
activations = relu(output)
final_w, final_b = params[-1]
logits = jnp.dot(final_w.T, activations) + final_b
return logits - logsumexp(logits)
现在,请注意,我们创建了这个函数,它一次只能对一个图像起作用。我们不能将一批 100 个图像传递给它,因为点积会因为形状不匹配而中断。
这就是 vmap 函数派上用场的地方。我们可以使用它来自动允许我们的预测函数处理批量数据。这是通过以下代码行完成的:
batched_predict = vmap(predict, in_axes=(None, 0))
第一个参数是我们要对其应用矢量化的函数。第二个是一个 tuple,其中包含原始函数的每个输入参数的值,并说明批处理应该在哪个轴上传播。
因此,tuple (None,0)意味着我们不应该对第一个参数(权重)进行批处理,而应该对第二个参数(图像)的行(轴 0)进行批处理。
损失和准确性
现在让我们定义两个简单的函数,一个用来计算模型的准确性,另一个用来计算我们的损失:
def accuracy(params, images, targets):
*"""*
*Calculates the accuracy of the neural network on a set of images*
*params: list of matrices*
*The weights for every layer of the network, including the bias*
*images: list of matrices*
*The images to be used on the calculation*
*targets: list of labels*
*The true labels for each of the targets*
*"""*
target_class = jnp.argmax(targets, axis=1)
*# Predicts the probabilities for each class and get the maximum*
predicted_class = jnp.argmax(batched_predict(params, images), axis=1)
return jnp.mean(predicted_class == target_class)
def loss(params, images, targets):
preds = batched_predict(params, images)
return -jnp.mean(preds * targets)
更新功能
我们快到了。现在,我们必须创建更新函数。在我们的 MLP 上,更新将基于步长和损失的梯度来改变权重。正如我们所看到的,Jax 将通过 grad 函数帮助我们轻松做到这一点:
def update(params, x, y):
grads = grad(loss)(params, x, y)
return [(w - step_size * dw, b - step_size * db)
for (w, b), (dw, db) **in** zip(params, grads)]
训练循环
现在我们准备运行我们的训练循环。让我们定义每层的神经元数量、梯度的步长、训练的时期数量和步长:
layer_sizes = [784, 512, 512, 10]
*# Training parameters*
step_size = 0.01
num_epochs = 10
batch_size = 128
*# Number of labels*
n_targets = 10
现在,让我们初始化我们的权重:
params = init_network_params(layer_sizes, random.PRNGKey(0))
现在,让我们循环这些时期,训练我们的网络:
for epoch **in** range(num_epochs):
for x, y **in** get_train_batches(batch_size):
x = jnp.reshape(x, (len(x), num_pixels))
y = one_hot(y, num_labels)
params = update(params, x, y)
train_acc = accuracy(params, train_images, train_labels)
test_acc = accuracy(params, test_images, test_labels)
print("Epoch **{}** in **{:0.2f}** sec".format(epoch, epoch_time))
print("Training set accuracy **{}**".format(train_acc))
print("Test set accuracy **{}\n**".format(test_acc))
这就是了。循环结束后,我们将在 Jax 中成功地训练出一只 MLP!
绩效结果
在前面的代码中,我没有在任何地方使用 jit 函数。但是,正如我所说的,这可以大大改善我们的结果,特别是如果一些计算发生了很多,如我们的参数更新。
如果我们要将 JIT 应用到我们的代码中,我们将创建一个 JIT 版本的更新函数,如下所示:
jit_update = jit(update)
然后,我们将内部 for 循环的最后一行改为:
params = jit_update(params, x, y)
下图显示了每种情况下每个历元经过的时间:

使用和不使用 JIT 时每个时期的运行时间。由作者开发。
如我们所见,*均而言,我们的培训时间减少了 2 倍!
结论
Jax 是一个新手,但已经显示出巨大的潜力。了解我们的地区每天带给我们的新的可能性是很好的。
我将在未来发布一些帖子,介绍一些基于 Jax 的深度学习库,以便我们可以更好地概述这个正在创建的新生态系统。
希望这对你有用!
这篇文章高度基于 Jax 文档。
JAX vs py torch:XGBoost 的自动微分
原文:https://towardsdatascience.com/jax-vs-pytorch-automatic-differentiation-for-xgboost-10222e1404ec
执行快速损失函数原型,以充分利用 XGBoost 的灵活性

马特·阿特兹在 Unsplash 上拍摄的照片
动机
在某些应用中,使用自定义损失函数运行 XGBoost 可以大大提高分类/回归性能。在时间紧迫的研究环境中,能够快速测试许多不同的损失函数是关键。因此,手动微分并不总是可行的(有时甚至容易出现人为错误,或数值不稳定)。
自动微分允许我们自动得到一个函数的导数,给定它的计算。它通过将我们的函数表示为具有已知导数的函数的组合来实现这一点,不需要开发人员付出任何努力。
我们将从一个简短的介绍开始,澄清我们的问题。然后,我们将深入研究用 PyTorch 和 JAX 实现自动微分,并将其与 XGBoost 集成。最后,我们将执行运行时基准测试,并展示对于此应用, JAX 比 PyTorch 快大约 10 倍。
背景
梯度推进是机器学习算法的框架。它基于弱学习器的集合(通常是决策树)输出预测。弱学习器可以根据任意的可微分损失函数进行优化,这给了我们很大的灵活性。我们将把重点放在决策树作为弱学习器的情况上——梯度增强决策树(GBDT)。
在神经网络缺乏的任务中,例如,表格数据和小训练集,GBDTs 表现出最先进的性能。
XGBoost 是一个有效实现 GBDT 的流行库。它提供了一个简单的接口,用于为我们的决策树编写定制的损失函数。给定一个定制的损失函数,我们所要做的就是向 XGBoost 提供其梯度和 Hessian 的计算结果。让我们看看如何在几分钟内实现自动微分。
问题设置
我们将在加州住房数据集上运行我们的实验,这是一个预测房价的回归任务。
我们的损失函数将是对数误差的*方(SLE):

请注意,这种损失对预测不足估计的影响大于预测过高估计。在预测房价时,它可以反映真实的业务需求,我们可以通过选择自定义损失函数来实现。
让我们将其应用于 XGBoost。
用 PyTorch 自动计算黑森数
在下文中,我们将重点关注与 PyTorch 的合作,因为这一点很清楚——稍后将与 JAX 进行比较。
使用 PyTorch 计算梯度是神经网络编程中常见的工作。然而,我们很少需要计算赫斯安数。谢天谢地,PyTorch 为我们实现了一个方便的功能[torch.autograd.functional.hessian](https://pytorch.org/docs/stable/generated/torch.autograd.functional.hessian.html)。在涵盖了这些技术细节后,我们可以开始实施了。
首先,我们实现了我们的损失函数:
接下来,我们的自动微分:
将它们放在一起:

图 1:PyTorch-实体模型数据自动区分演示。
运行在真实世界的数据上:

图 2: 加载列车数据并显示汇总。

图 3: PyTorch —对现实世界数据进行自动区分的运行时性能(图 2 中加载)。
这给了我们一个使用 PyTorch 自动区分的简单工作实现。然而,该代码不能很好地扩展到大型数据集。因此,在下一节中,我们将展示一种更复杂的方法来改进我们的运行时。
使用 JAX 优化运行时性能
如果我们正确使用 JAX,我们可以实现相当大的运行时加速。让我们在 JAX 从上面写下 PyTorch 代码:
我们将损失计算变更为使用jax.numpy(进口为jnp),
并在 JAX 使用相应的语法进行自动区分,
根据前面的数据,与 PyTorch 实现(图 3)相比,我们看到了约 2 倍的加速:

图 4: JAX —对现实世界数据进行自动区分的运行时性能。
请注意,我们使用 JAX 的《自动烹饪书》中的hvp(黑森矢量积)函数来计算黑森对角线。只有当 Hessian 是对角的(所有非对角条目都为零)时,这个技巧才可能实现,这在我们的例子中是成立的。这样,我们就不会存储整个 hessian,并动态计算它,从而减少内存消耗。
然而,最显著的加速是由于通过“正向-反向微分”对 Hessian 的有效计算。技术细节超出了这篇文章的范围,你可以在 JAX 的自学食谱中读到。
此外,我们利用 JAX 的 JIT 编译来进一步减少运行时间,减少了大约 3 倍。
运行时性能基准
让我们对运行时性能进行更彻底的比较。

图 5: 运行时基准测试结果:JAX 比 PyTorch 快。
我们注意到 PyTorch 实现具有二次运行时复杂度(在示例数量中),而 JAX 实现具有线性运行时复杂度。这是一个巨大的优势,允许我们在大型数据集上使用 JAX 实现。
现在,让我们比较自动微分和手动微分:

图 6: 运行时基准测试结果:手动微分比 JAX 快。
事实上,相比之下,手动区分速度非常快(快 40 倍)。然而,对于复杂的损失函数或小数据集,自动微分仍然是工具箱中一项有价值的技能。
该基准测试的完整代码可在此处找到:
https://github.com/1danielr/auto-gb
结论
我们利用自动微分的能力无缝地使用 XGBoost 中的定制损失函数,并对运行时性能进行了可行的折衷。当然,上面的代码也适用于其他流行的梯度增强库,如 LightGBM 和 CatBoost。
我们看到 JAX 为我们提供了实质性的速度提升,这要归功于它对 Hessian 的高效实现,以及对 JIT 编译的透明利用。此外,我们列出了几行代码,允许我们一般地计算梯度和 Hessians。也就是说,我们的方法可以推广到需要高阶自动微分的额外工作负载。
感谢阅读!我很想听听你的想法和评论😃
JupyterLab/VSCode 中的金贾/DBT SQL 模板
原文:https://towardsdatascience.com/jinja-dbt-sql-templating-in-jupyterlab-vscode-1179e818fe17
快速原型化您的 SQL 模板

乔安娜·科辛斯卡在 Unsplash 上的照片
SQL 本身并不适合重用。为了实现可重用性,SQL 通常使用像 Jinja 这样的库进行模板化。例如 Apache 超集在其数据集定义中利用了 Jinja 模板,当然 DBT 是一个完全围绕 Jinja 模板构建的工具。Jupyterlab-sql-editor 本机支持 Jinja 模板,这使得在 Jupyterlab 和 VSCode 中为超集和 DBT 构建 jinja-sql 模板原型成为可能。
使用 Jinja 模板创建可重用的 SQL
**--jinja**选项启用了 Jinja 模板支持,使得笔记本中声明的任何变量或函数都可以在 Jinja 标签**{{}}**中替换
例如,您可以使用变量来表示 SQL 语句中使用的实际表名。当您拥有用于开发和生产表的数据库模式时,这可能会很有用。

在我们的上一篇文章中,我们看到**%%sparksql**魔术支持许多输出格式;文本,交互式网格,交互式 JSON 树,html,skip。这个**%%sparksql**魔术还有一个锦囊妙计!原谅我的双关语。
**--output sql**选项用语法高亮显示结果 SQL 语句。这是一种验证你的 Jinja 模板代码的便捷方式。在这个例子中,我们使用 python 列表变量来自动生成 SELECT 语句。

正如我们所看到的,Jinja **{% for item in list %}**已经生成了所需的列。
挖掘 DBT 的力量
使用 Jinja 模板来自动生成 SQL 语句是非常强大的,事实上整个项目都是围绕这个想法构建的。 DBT 是一个使用 SELECT 语句开发模块化 SQL 模型的框架,包括一个**ref()** Jinja 宏,用于处理依赖性管理(首先构建哪些模型)。
DBT 包含了大量的 Jinja 宏,旨在简化 SQL 语句的阐述。DBT 有一个包管理系统,允许第三方开发者贡献宏库。在 DBT 中心有很多
通过简单地将**--jinja**替换为**--dbt**选项,并在笔记本上配置您的 DBT 项目的位置,magic 使您能够利用丰富的宏。我们将使用示例项目 jaffle_shop 来说明 DBT 的使用。

在 jaffle_shop 中有一个名为 orders.sql 的模型。我们可以将这个文件的内容放在 Jupyter 单元格中,并使用**--dbt**通过 DBT 处理模板。我们可以使用**—-output sql**来呈现语句,使用**--output html**来执行查询并显示结果。

注意 DBT **ref()**宏的使用。该宏引用了您的 DBT 项目中现有的 DBT 模型。**%%sparksql**实际上使用了 DBT 框架来呈现 SQL 模板。所有的 DBT 模型和宏都提供给**%%sparksql** magic。
我们可以呈现 DBT 生成的 SQL。输出很长,我们只展示了其中的一部分。注意产生预期列的payment_method循环。还要注意 DBT 处理短暂模型具体化的方式。更多详情此处。

如前所述,我们可以执行并显示结果。在显示实际结果之前,请注意 DBT 日志输出。

使用**%%sparksql**是构建 DBT 代码原型的一种便捷方式。我们已经用一个预先存在的模型说明了 DBT 的使用。要从头开始创建 DBT 模型,您可能只想显示现有表(DBT 数据源)或预先存在的 DBT 模型的数据。假设您想要创建一个基于**stg_payments**的新模型。你可以从展示**stg_payments**的数据开始
**%%sparksql** --dbt --output htmlselect * from {{ ref('stg_payments') }}
然后使用 DBT 的 Jinja 宏开始转换这个数据集。同时查看结果输出或呈现 SQL。
**%%sparksql** --dbt{% set payment_methods =
['credit_card', 'coupon', 'bank_transfer', 'gift_card'] %}select
order_id,
{% for payment_method in payment_methods -%}
sum(case when payment_method = '{{ payment_method }}'
then amount else 0 end) as {{ payment_method }}_amount,
{% endfor -%}
sum(amount) as total_amount
from {{ ref('stg_payments') }}
group by order_id
一旦您有了一个很好的模板化查询,您可以通过简单地将模板复制到 DBT **.sql**模型文件中来将它转移到生产环境中,并且确信它实际上会做您期望它做的事情。
Visual Studio 代码 DBT 高级用户
DBT 电力用户是 DBT 项目的一个流行的 VSCode 扩展。它支持您的**.sql**模型文件的许多 DBT 自动完成特性。

Visual Studio 代码原生支持使用 Jupyter 笔记本,因此%%sparksql魔法在 VSCode 内部工作。如前所示,要在笔记本中利用 DBT 项目,你需要做的就是把你的 DBT 的位置给**%%sparksql**

一旦笔记本单元格的语言被设置为 SQL,DBT 超级用户的自动补全功能就会启动,你将获得与编辑**.sql**文件相同的好处。请注意,如果您在 VSCode 中安装了 sql-language-server ,它会在检测到带有**%%sparksql**魔法的单元格时自动将语言更改为 sql。

记住,在 JupyterLab 中,VSCode 自动完成是由

例如,如果我们选择上面显示的**is_incremental**建议,我们会将以下代码插入到我们的笔记本单元格中。与 DBT 超级用户在**.sql**模型文件中的行为相同。
底线是**%%sparksql** magic 在 VSCode 和 JupyterLab 笔记本上的工作方式是一样的。您可以将 DBT 模型渲染到 SQL 中,执行查询并查看结果。类似地,VSCode 笔记本也是制作 DBT 模型原型的好方法。一旦你对你的模型感到满意,你可以把它复制到一个**.sql**模型文件中。
结论
在本文中,我们展示了如何利用**%%sparksql**在 JupyterLab 和 VSCode 中轻松构建模板化的 Spark SQL 原型。我们关注的是 Spark,但是 jupyterlab-sql-editor 也包含了一个**%%trino**魔法!将来可能会添加更多的 SQL 引擎。欢迎投稿!这是我们的 git repocyber centre Canada/jupyterlab-SQL-editor。
Python 的 Seaborn 抖动图
原文:https://towardsdatascience.com/jitter-plots-with-pythons-seaborn-62188bf511b8
显示分类变量分布的一种简单方法

抖动图—图片由作者提供
没有太多的选项来可视化数值在类别中的分布。通常,这些关系需要在展示之前进行总结。
箱线图和小提琴图,包括最大值和最小值、中间值和百分位数,可以帮助可视化这些分布。但是它们可能会让许多观众望而生畏,并使试图展示的内容过于复杂。
本文将使用 Python 的 Seaborn 探索抖动图,这是一种简单且用户友好的可视化分类字段分布的方法。

抖动图—图片由作者提供
带状图
让我们从导入库和加载一些虚拟数据开始。数据集包括一个类别列和一个数值列。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns# dataurl = 'https://gist.githubusercontent.com/Thiagobc23/0bc5c5f530e8f8fd98710fe1ccb407ce/raw/4e084668a83ab9d0a0ace1425742835a0563bcef/quality.csv'df = pd.read_csv(url)
df_unit = df.groupby('Unit').mean().reset_index()# sort prod units by avg score
df_unit = df_unit.sort_values('Quality Score', ascending=False)# take a look
print('Dataset: \n', df.head())
print('\n Averages: \n', df_unit.head())

样本数据—作者提供的图片
好的,如果我们尝试用散点图来绘制这些值,我们会得到一个带状图。
plt.scatter(df['Unit'], df['Quality Score'])

带状图—作者图片
虽然我们可以从这个图表中获得一些信息,例如值的范围,但这里的问题是许多点重叠,我们无法获得分布的清晰图像。
我们有两种主要方法来解决这个问题:计数图和抖动图。
第一种尝试通过编码大小来解决重叠问题。记录密度较高的点用较大的点绘制。
第二种更直观,在 x 位置添加随机抖动,同时保持类别之间的一定距离。这使得它看起来更像一个列,而不是一条线,并允许我们看到点和它们集中的地方。
抖动图基础
Seaborn 的带状图已经添加了这种抖动,所以我们只需要调用函数,瞧!
sns.stripplot(y="Quality Score", x="Unit", data=df)

简单抖动图—图片由作者提供
sns.stripplot(y="Unit", x="Quality Score", data=df)

简单抖动图—图片由作者提供
相当简单,现在我们可以对其进行改进,使其信息量更大,更易于阅读。我们将添加一条线来标记每组的*均值,在 y 轴上对类别进行排序,并在底部添加一个图例。
增加价值
我们可以用另一个散点图显示每个类别的*均值。这一次,我们将使用管道符号(|)作为标记,而不是点。
我们还将传递参数顺序,根据组的*均值对类别进行排序。
**# list with ordered categories
order = df_unit['Unit']****fig, ax = plt.subplots(1, figsize=(12,10))**# Plot lines for the average
**sns.scatterplot(y="Unit", x="Quality Score", data=df_unit, marker='|', s=1000, color='k')**# Jitter plot
sns.stripplot(y="Unit", x="Quality Score", data=df, **order=order**, **zorder=0**)

抖动图—图片由作者提供
接下来,我们将添加图例。我们没有任何好的房地产,所以我们将这个数字一分为二,并在底部有一个传奇的支线剧情。
**from matplotlib.lines import Line2D**fig, (ax, ax_leg) = plt.subplots(**2**, figsize=(12,10), **gridspec_kw={'height_ratios':[6, 1]}**)# Plot lines for the average
sns.scatterplot(y="Unit", x="Quality Score", data=df_unit, marker='|', s=1000, color='k', ax=ax)# Jitter plot
sns.stripplot(y="Unit", x="Quality Score", data=df, order=order, zorder=0, ax=ax)# Custom Legend
**legend_elements = [Line2D([0], [0], marker='o', color='w',
label='One test result',
markerfacecolor='k', markersize=10),
Line2D([0], [0], marker='|', color='k',
label='Mean quality score for the unit',
linestyle='None', markersize=25)]****legend = ax_leg.legend(handles=legend_elements, loc='upper center',
ncol=2, frameon=False, fontsize=12)**

抖动图—图片由作者提供
好吧!一切似乎都已就绪。现在唯一剩下的就是清理干净。
fig, (ax, ax_leg) = plt.subplots(2, figsize=(12,10), gridspec_kw={'height_ratios':[6, 1]})# Plot lines for the average
sns.scatterplot(y="Unit", x="Quality Score", data=df_unit, marker='|', s=1000, color='k', ax=ax)# Jitter plot
sns.stripplot(y="Unit", x="Quality Score", data=df, order=order, zorder=0, ax=ax)# custom Legend
legend_elements = [Line2D([0], [0], marker='o', color='w',
label='One test result',
markerfacecolor='k', markersize=10),
Line2D([0], [0], marker='|', color='k',
label='Mean quality score for the unit',
linestyle='None', markersize=25)]legend = ax_leg.legend(handles=legend_elements, loc='upper center',
ncol=2, frameon=False, fontsize=12)**ax_leg.set_xticks([])
ax_leg.set_yticks([])****# remove spines
sns.despine(fig, left=True, top=True, right=True, bottom=True)****# title and labels
ax.set_title('Quality score by production unit\n', loc='left', fontsize=20)
ax.set_xlabel('')
ax.set_ylabel('')****# make it tight (:
plt.tight_layout()**
plt.show()

抖动图—图片由作者提供
在示例中,更明显的是,Echo 没有那么多数据点,Alpha、Beta、Charlie 和 Delta 中存在异常值,总体而言,这些值集中在一个类似的范围内。我们无法通过*均值或中值看到那些有价值的见解。
结论
汇总统计对于理解大量或复杂的数据集非常有用。*均值、中间值、百分位数等通常比共享整个数据集更容易交流。这些聚合的缺点是,生成的值经常会被查看者误解,尤其是那些不熟悉数据或主题的查看者。

按类别显示*均值的条形图-作者提供的图像
用户可能会将上面的图表理解为“Alpha”比其他人有更好的分数。事实是,我们无法从这张图表中看出这一点。记录数、异常值和方差等变量在*均值中起作用。
因此,为了传达这些数字背后的整个故事,我们需要一个熟悉这个主题的观众,注释所有这些潜在的因素,或者显示组成这个*均值的分数的实际分布。

小提琴情节 —作者图片

盒子剧情 —图片由作者提供
小提琴图和箱形图是描述分布的最佳选择。但是,由于我们生活在注意力经济中,有一个友好的,更简单的替代方案,如抖动图是很好的。

抖动图可以增加价值,而不会让观众不知所措或导致他们做出错误的假设。
用户可以看到每个数据点,并理解这些数据。在这种情况下,我们可以看到,即使*均值不同,也不仅仅是“阿尔法比其他人做得更好”。
感谢阅读我的文章!你可以在这里找到更多类似的教程:https://linktr.ee/thiagobc23
使用 Apache AirFlow 2.0 在 10 分钟内完成作业调度
原文:https://towardsdatascience.com/job-scheduling-with-apache-airflow-2-0-in-10-minutes-16d19f548a46
简化工作流管理系统——用 Python 示例解释

Apache Airflow 是 Airbnb 打造的工作流管理系统。通俗地说,它可以被认为是一个打了兴奋剂的作业调度器。如果您有应该按计划运行或按顺序运行的 Python 脚本,Apache Airflow 是一个方便而可靠的工具,可以处理这些问题,甚至更多。Apache Airflow 有 4 个组件:元数据数据库、调度器、执行器和 Web 服务器。本文将涉及 Apache Airflow 中使用的组件和术语,如何设置和使用 Apache Airflow 的 Python 示例,以及如何启动 Apache Airflow Web 服务器。
注 : Apache Airflow 只在 Linux 上运行,在 Windows 上可以通过 Docker 或者 WSL2 (Windows 子系统 for Linux)获得。
更新 :本文是系列文章的一部分。查看其他“10 分钟内”话题 此处 !
目录
什么是阿帕奇气流
Apache Airflow 是一个工作流管理系统,它将任务及其依赖关系定义为代码,并定期执行任务。它提供了一个 web UI,显示当前活动和过去任务的状态,以及关于任务执行的诊断信息。如果需要,用户还可以手动管理任务的执行和状态,使其自动化,但也允许人工干预。
气流有几个组成部分,即
- 元数据数据库:存储关于任务状态的信息
- 调度器:决定需要执行哪些任务以及执行优先级
- 执行器:执行任务逻辑的进程
- 网络服务器:支持向用户显示信息的图形用户界面(GUI)的服务器
什么是任务和 DAG
任务被定义为一个工作单元,可以看作是运行脚本的 Python 作业。一个任务实例运行一个单独的任务,并有一个状态,如running、success、failed、skipped或up for retry等。
有两种类型的任务,即
- 操作员:执行操作。
PythonOperator可以执行任何可以在 Python 中执行的函数,还有BashOperator、EmailOperator、SimpleHttpOperator等。取决于您的需求 - 传感器:暂停相关任务的执行,直到满足某个标准。传感器可以检查任何进程或数据结构的状态
这两类任务通常按sensor > operator顺序执行,其中传感器任务可以暂停操作员任务的执行,直到满足条件。
任务可以在有向非循环图(DAG)中链接起来执行。DAG 是一种有向图(任务执行有一个顺序)和无环图(任务不能形成一个永无止境的循环)。
安装气流
Airflow 可以作为 Python 包安装,并使用以下命令实例化其数据库,
*$ pip install apache-airflow
$ airflow db init*
现在你已经准备好使用阿帕奇气流!
通过 Python 使用气流
使用 Apache Airflow 有 3 个主要步骤。首先,您需要定义 DAG,指定脚本需要运行的时间表,在任务失败时向谁发送电子邮件,等等。接下来,您需要通过将任务链接到 Python 函数来定义操作员任务和传感器任务。最后,您需要定义任务之间的依赖关系,指定任务的顺序。
现在,让我们深入了解如何将 Apache Airflow 与 Python 结合使用!
1.定义 DAG
要定义 DAG,它只是一个带有传入default_args的参数的DAG对象。有几个重要的参数需要定义,
owner:DAG 的所有者start_date:运行 DAG 的开始日期,这必须是历史日期schedule_interval:运行 DAG 的时间间隔,可以用datetime.timedelta或 CRON schedule 格式后的字符串定义email:失败或重试时发送邮件的邮件地址retries:重试次数retry_delay:每次重试之间的延迟
更多参数可以在 Apache Airflow 文档中找到。下面是如何实例化 DAG 的示例,
*import datetime
from airflow.models import DAG
# Parameteres
WORFKLOW_DAG_ID = "my_example_dag"
WORFKFLOW_START_DATE = datetime.datetime(2022, 1, 1)
WORKFLOW_SCHEDULE_INTERVAL = "* * * * *"
WORKFLOW_EMAIL = ["youremail@example.com"]
WORKFLOW_DEFAULT_ARGS = {
"owner": "kayjan",
"start_date": WORFKFLOW_START_DATE,
"email": WORKFLOW_EMAIL,
"email_on_failure": False,
"email_on_retry": False,
"retries": 0,
}
# Initialize DAG
dag = DAG(
dag_id=WORFKLOW_DAG_ID,
schedule_interval=WORKFLOW_SCHEDULE_INTERVAL,
default_args=WORKFLOW_DEFAULT_ARGS,
)*
2.定义任务
任务应该链接到它们各自的 Python 函数。任务名称在task_id中指定,并且必须是唯一的,而 Python 函数是用python_callable参数指定的。
在下面的例子中,我定义了两个 Python 函数job_1和job_2,一个传感器的虚拟函数sensor_job,并将它链接到操作员和传感器任务。
*from airflow.operators.python import PythonOperator
from airflow.sensors.python import PythonSensor
# Define functions
def job_1():
print("Perform job 1")
def job_2():
print("Perform job 2")
def sensor_job():
print("Sensor Job")
# Define jobs
job_1_operator = PythonOperator(
task_id="task_job_1",
python_callable=job_1,
dag=dag,
)
job_2_sensor = PythonSensor(
task_id="task_job_2_sensor",
python_callable=sensor_job,
dag=dag,
poke_interval=180,
)
job_2_operator = PythonOperator(
task_id="task_job_2",
python_callable=job_2,
dag=dag,
)*
3.定义任务之间的相关性
定义了任务之后,我们需要定义任务运行的顺序。这可以使用具有惯例的位移算子>>来完成,
preceding_task >> subsequent_task,或者subsequent_task << preceding_task
依赖性也可以用 Pythonic 代码来定义,
preceding_task.set_downstream(subsequent_task),或者subsequent_task.set_upstream(preceding_task)
就我个人而言,我更喜欢使用位移运算符,因为它可以堆叠,可读性更强。
*# Set dependency
job_1_operator >> job_2_sensor >> job_2_operator*
我把上面所有的代码放在目录dags_folder/dag.py里。务必使用python3 dags_folder/dag.py运行一次代码,检查是否有错误。完成后,将文件复制到~/airflow/airflow.cfg中定义的文件路径。或者,使用绝对路径将airflow.cfg文件改为指向您的dags_folder目录。
*$ cp dags_folder/dag.py /home/kayjan/airflow/dags/*
启动 Web 服务器
在 Airflow Web 服务器上查看 DAG 之前,仍需要完成一些步骤。
首先,用户需要注册,因为 Airflow Web 服务器需要登录凭证。我们可以分别使用以下命令注册用户和删除用户,
*$ airflow users create -r Admin -u <username> -p <password> -f <first_name> -l <last_name> -e <email>
$ airflow users delete -u <username>*
接下来,启动气流调度程序。请注意,要使 DAG 按计划运行,气流调度程序必须正在运行。
*$ airflow scheduler*
通过运行airflow dags list命令检查您的 DAG 是否存在。可能有许多其他 Dag 是您现在可以忽略的示例 Dag。
最后,打开一个新的终端,用下面的命令启动 Web 服务器,
*$ airflow webserver*
在 Web 服务器上,可以以树形格式查看历史运行及其状态,或者以图形格式查看 DAG。还有其他信息,如 DAG 参数、DAG 代码、任务日志等。您可以使用右上角的播放按钮手动触发 DAG 运行。

图 1: Apache Airflow Web 服务器树选项卡(查看历史运行和状态)-按作者分类的图片

图 2: Apache Airflow Web 服务器图表选项卡(查看 DAG)-按作者分类的图像

图 3:作业 1 的任务日志——按作者分类的图像
希望你已经学习了 Apache Airflow 的基础知识,以及如何设置一个运行 Python 代码的 Airflow DAG。Apache Airflow 中有许多新的术语,可能会非常混乱。在这篇介绍之后,您应该能够更好地理解 Apache Airflow 文档、论坛或其他文章。
Apache Airflow 上还有更多的功能,比如用 Celery 或 Kubernetes Executors 以分布式方式运行任务,用 XCom 在操作员之间交换信息,用 hooks 定义自己的操作员和传感器。有需求的话可以出续集;)
最后,本文中共享的代码总结如下,
感谢您的阅读!如果你喜欢这篇文章,请随意分享。
相关链接
*阿帕奇气流文件:【https://airflow.apache.org/ *
阿帕奇气流教程:https://air flow-Tutorial . readthedocs . io/en/latest/index . html
阿帕奇气流官方 GitHub:https://github.com/apache/airflow
熊猫中的加入与合并
原文:https://towardsdatascience.com/join-vs-merge-in-pandas-17b1b773bfb5
Pandas 的 join 和 merge 函数之间的实际差异

这些功能
作为一名数据科学家,总有一天你会面临一个问题,你的数据一部分在数据集 A 中,另一部分在数据集 b 中。
当这样的问题发生时,您肯定会从 SQL 中寻找一个类似于以前的join的函数。如果你正在使用 Python,你很可能会面对熊猫库中的这两个选项:df.join()或df.merge()。
在这篇文章中,我们想了解基本的区别,并决定使用哪一个。
总之,两者将执行相同的任务,即查看来自数据集 A 和来自数据集 B 的列,并根据您选择的连接类型返回结果—见下图。

联接类型。图片由作者提供。
A 里有什么?B 里面是什么?
让我们检查两个表,看看什么是相等的…
熊猫加入
我首先把熊猫join带到餐桌上,因为——在我看来——它不如merge灵活。
Pandas join 总是需要将右边表的列或索引与左边表的索引相匹配。因此,假设您在左边的表中有一个数字索引从 1 到 20 的表,您希望将它与给定的右边的表进行匹配。它们必须具有相同的索引,或者右表必须具有与左表中的索引具有相同数字/信息的列。
我知道,这很令人困惑。让我们编写代码来看看结果。会更有意义。
**# Creating two Dataframes**# Set seed
np.random.seed(10)# dataframe 1
df1 = pd.DataFrame( {'Category': np.random.choice(['A', 'A+', 'B', 'C'], size=20)} )df1['Score'] = np.select([df1.Category == 'A+', df1.Category == 'A', df1.Category == 'B', df1.Category == 'C'],
[10, np.random.choice([7,8,9]), np.random.choice([4,5,6]), np.random.choice([1,2,3])])
df1.index = [i for i in range(1,21)]# dataframe 2
df2 = pd.DataFrame( { 'Category': df1.Category} )df2['Location'] = np.select([df1.Category == 'A+', df1.Category == 'A', df1.Category == 'B', df1.Category == 'C'],
['North', np.random.choice(['Southwest','South']), np.random.choice(['South', 'East', 'Northeast']), np.random.choice(['Northeast','South','Southeast'])])
df2.index = [i for i in range(1,21)]

两个数据帧。图片由作者提供。
请注意,两者都有相同的索引和相同的“id”。
# Join both df
df1.join(df2, lsuffix='_1', rsuffix='_2')

连接函数的结果。图片由作者提供。
您可以看到它在这种情况下工作得非常好,因为两个数据集有相同的索引。
现在,第二个例子显示了join方法在熊猫身上的局限性。如果我在两边创建一个名为 ID 的列,并保留原来的索引(从 0 到 19),看看会发生什么。
如果我运行这个…
# Creating two Dataframes# Set seed
np.random.seed(10)**# dataframe 1 with ID col**
df1 = pd.DataFrame( {'ID': range(1,21),
'Category': np.random.choice(['A', 'A+', 'B', 'C'], size=20)} )df1['Score'] = np.select([df1.Category == 'A+', df1.Category == 'A', df1.Category == 'B', df1.Category == 'C'],
[10, np.random.choice([7,8,9]), np.random.choice([4,5,6]), np.random.choice([1,2,3])])**# dataframe 2 with ID col**
df2 = pd.DataFrame( { 'ID': range(1,21),
'Category': df1.Category} )df2['Location'] = np.select([df1.Category == 'A+', df1.Category == 'A', df1.Category == 'B', df1.Category == 'C'],
['North', np.random.choice(['Southwest','South']), np.random.choice(['South', 'East', 'Northeast']), np.random.choice(['Northeast','South','Southeast'])])**#Join on the column ID**
df1.join(df2, on= 'ID', lsuffix='_1', rsuffix='_2')
事情是这样的。

使用 Join 进行联接时出错。图片由作者提供。
我已经指定我想要列ID上的join,但是 Pandas 将尝试将右表中的列 ID 与左表中的索引相匹配。这就是问题发生的时候。即使两个表在 ID 列中有相同的 ID(1 到 20),索引仍然是从 0 到 19。我们会错过一些连接。
熊猫合并
熊猫merge的功能更加多样,因为它可以从两边连接柱子。所以在这里,使用与上面完全相同的代码,但是将方法改为merge(),我们将得到正确的结果。
# Creating two Dataframes# Set seed
np.random.seed(10)# dataframe 1
df1 = pd.DataFrame( {'ID': range(1,21),
'Category': np.random.choice(['A', 'A+', 'B', 'C'], size=20)} )df1['Score'] = np.select([df1.Category == 'A+', df1.Category == 'A', df1.Category == 'B', df1.Category == 'C'],
[10, np.random.choice([7,8,9]), np.random.choice([4,5,6]), np.random.choice([1,2,3])])# dataframe 2
df2 = pd.DataFrame( { 'ID': range(1,21),
'Category': df1.Category} )df2['Location'] = np.select([df1.Category == 'A+', df1.Category == 'A', df1.Category == 'B', df1.Category == 'C'],
['North', np.random.choice(['Southwest','South']), np.random.choice(['South', 'East', 'Northeast']), np.random.choice(['Northeast','South','Southeast'])])**# MERGE on the column ID**
df1.merge(df2, on='ID', how= 'inner')

合并结果。图片由作者提供。
merge 方法还有其他很酷的参数,比如合并两个具有不同列名的数据帧的能力。假设左边的表是 ID,右边的表是 ID2 列。我们只需使用这些代码来实现合并。
df1.merge(df2, left_on='ID', right_on='ID2', how= 'inner', )

用不同的列名合并。图片由作者提供。
在你走之前
连接数据集是数据科学家的一项常见任务。如果你知道每种方法是如何工作的,你就更容易选择选择哪一种。
对我来说,我通常去df.merge(),因为那里更灵活。不确定熊猫团队是否会永远保留这两只熊猫,但知道它们的基本区别是有好处的。
不要忘记从这两种方法探索文档。
如果你喜欢这个内容,请关注我的博客。
https://gustavorsantos.medium.com/
Databricks 和 PySpark 中的 JSON
原文:https://towardsdatascience.com/json-in-databricks-and-pyspark-26437352f0e9
使用 PySpark 在数据块中处理 JSON 数据的技巧和诀窍

法托斯 Bytyqi 在 Unsplash 上的照片
在简单的情况下,JSON 很容易在 Databricks 中处理。您可以将 JSON 对象文件直接读入 DataFrame 或表中,Databricks 知道如何将 JSON 解析成单独的字段。但是,正如大多数与软件相关的事情一样,也存在一些问题和变化。本文展示了如何处理最常见的情况,并包括详细的编码示例。
我的用例是已经被转换成 JSON 的 HL7 医疗保健数据,但是这里的方法适用于任何 JSON 数据。考虑的三种格式是:
- 包含完整 JSON 对象的文本文件,每行一个。当您将 JSON 文件加载到 Databricks 表时,这是很典型的。
- 包含各种数据字段(列)的文本文件,其中一个是 JSON 对象。这在计算机日志中很常见,其中有一些纯文本元数据,后面是 JSON 字符串中的更多细节。
- 上述内容的变体,其中 JSON 字段是一个对象数组。
将每种类型的输入放入数据块需要不同的技术。
假设…
- 你能接触到数据块并知道基本操作。如果你是新手,试试免费的社区版。
- 我从 json.org 的那里拿到 JSON 规范,用 jsonlint.com的测试 JSON 对象的有效性。
- 我忽略[1,2,3]和“hello”之类的“裸 JSON”。按照规范,这些是完整的、有效的 JSON 对象,但是我认为它们的形式不好,因为这些字段没有名称,所以很难在下游使用。(Databricks 可以将这些视为普通的输入字段,而不关心它们是否是有效的 JSON。)
包含所有这些代码的完整 Databricks 笔记本位于https://github . com/chuck connell/articles/blob/master/JSON _ tricks . DBC。复制 URL,然后在 Databricks 中做 Workspace / Import / URL。
虽然这段代码是在 Databricks 中开发的,但它应该也可以在安装了 PySpark 的本机 Apache Spark 中工作,尽管我没有在那里测试过。
标准 JSON 对象
标准的 JSON 文本文件如下所示:
{ "Text1":"hello", "Text2":"goodbye", "Num1":5, "Array1":[7,8,9] }
{ "Text1":"this", "Text2":"that", "Num1":6.6, "Array1":[77,88,99] }
{ "Text1":"yes", "Text2":"no", "Num1":-0.03, "Array1":[555,444,222] }
要将这个文件读入 DataFrame,可以使用标准的 JSON 导入,它从提供的字段名和数据项中推断出模式。
test1DF = spark.read.json("/tmp/test1.json")
得到的 DataFrame 具有与 JSON 标记匹配的列,并且数据类型被合理地推断出来。(类型推断并不完美,尤其是对于 ints vs floats 和 boolean。)您现在可以只使用它们的普通名称来读取 DataFrame 列;所有的 JSON 语法都不见了。

作者图片
带有 JSON 字段的文本文件
包含一些常规字段和一个 JSON 字段的文本文件如下所示:
Text1|Text2|Num1|JSON1
hello | goodbye | 5 | {"Sub1":"john", "Sub2":3}
this | that | 6.6 | {"Sub1":"betty", "Sub2":4}
yes | no | -0.03 | {"Sub1":"bobby", "Sub2":5}
第一行包含字段名称,这是数据文本文件的标准。我使用竖线来分隔字段,以避免与 JSON 语法中的逗号混淆。(您可能也想这样做,因为 Databricks 文本解析器很难处理嵌入的逗号和引号的转义语法。)
要像这样导入文件,使用一个两阶段的过程,首先将 JSON 字段作为文本读取。注意,我使用“inferSchema ”,因为文件很小;对于大文件,您应该使用。schema(my_schema)哪个更快。
test2DF = spark.read\
.option("inferSchema", True)\
.option("header", True)\
.option("delimiter", "|")\
.csv("/tmp/test2.txt")
常规字段现在是正确的,JSON 字段是单个文本字符串。

作者图片
我们需要将 JSON 字符串转换成适当的结构,这样我们就可以访问它的各个部分。
from pyspark.sql.functions import from_json, col
from pyspark.sql.types import StructType, StructField, StringType, IntegerType# Define the schema of the JSON string.
schema = StructType([
StructField("Sub1", StringType()),
StructField("Sub2", IntegerType()
)
])# Use the schema to change the JSON string into a struct, overwriting the JSON string.test2DF = test2DF.withColumn("JSON1", from_json(col("JSON1"), schema))# Make a separate column from one of the struct fields.test2DF = test2DF.withColumn("JSON1_Sub2", col("JSON1.Sub2"))
现在我们得到了我们想要的——原来的非 JSON 字段,作为真正结构的 JSON 字段,以及提取一个 JSON 项的示例。

作者图片
带有 JSON 数组字段的文本文件
包含 JSON 对象数组字段的文本文件如下所示:
Text1|Text2|Num1|JSON1
hello | goodbye | 5 | [{"Sub1":"stop", "Sub2":3}, {"Sub1":"go", "Sub2":6}]
this | that | 6.6 | [{"Sub1":"eggs", "Sub2":4}, {"Sub1":"bacon", "Sub2":8}]
yes | no | -0.03 | [{"Sub1":"apple", "Sub2":5}, {"Sub1":"pear", "Sub2":10}]
我假设数组中的每个 JSON 对象都有相同的结构。当然,这并不是严格要求的,但是对于逻辑上相关并且在单个数组中的对象来说,这是可能的。
对于这种类型的 JSON 输入,以同样的方式开始,将常规字段读入它们的列,并将 JSON 作为纯文本字段。
test3DF = spark.read\
.option("inferSchema", True)\
.option("header", True)\
.option("delimiter", "|")\
.csv("/tmp/test3.txt")

作者图片
接下来,使用用户定义的函数(UDF)将 JSON 字符串更改为实际的结构数组。这种编码技巧归功于https://kontext . tech/column/spark/284/py spark-convert-JSON-string-column-to-array-of-object-struct type-in-data-frame。
from pyspark.sql.functions import col, udf
from pyspark.sql.types import *
import json# Schema for the array of JSON objects.json_array_schema = ArrayType(
StructType([
StructField('Sub1', StringType(), nullable=False),
StructField('Sub2', IntegerType(), nullable=False)
])
)# Create function to parse JSON using standard Python json library.def parse_json(array_str):
json_obj = json.loads(array_str)
for item in json_obj:
yield (item['Sub1'], item['Sub2'])# Create a UDF, whose return type is the JSON schema defined above. parse_json_udf = udf(lambda str: parse_json(str), json_array_schema)
# Use the UDF to change the JSON string into a true array of structs.test3DF = test3DF.withColumn("JSON1arr", parse_json_udf((col("JSON1"))))# We don't need to JSON text anymore.
test3DF = test3DF.drop("JSON1")
结构数组很有用,但是“反规范化”并将每个 JSON 对象放在自己的行中通常更有帮助。
from pyspark.sql.functions import col, explodetest3DF = test3DF.withColumn("JSON1obj", explode(col("JSON1arr")))# The column with the array is now redundant.
test3DF = test3DF.drop("JSON1arr")
注意,非 JSON 字段现在被复制到多行中,每行一个 JSON 对象。

作者图片
我们可以跨所有阵列从一个特定的 JSON 字段收集数据,这在展开的阵列上要容易得多。
display (test3DF.select("JSON1obj.Sub1"))
结论
JSON 是一种标记文本格式。它是一个可读的文件,包含名称、值、冒号、花括号和各种其他语法元素。另一方面,PySpark 数据帧是一种二进制结构,数据可见,元数据(类型、数组、子结构)内置于数据帧中。上面的说明帮助你把第一个翻译成第二个。
有关更多信息,请参见:
- https://kb . databricks . com/Scala/create-df-from-JSON-string-python-dictionary . html
- https://docs . databricks . com/data/data-sources/read-JSON . html
JSON 模式:NoSQL 数据的完整性检查
原文:https://towardsdatascience.com/json-schema-integrity-checking-for-nosql-data-b1255f5ea17d
用 JSON 模式验证 JSON 和 CSV 数据的格式。Python 和 pandas 中的示例代码。

问题是
关系数据库有模式。这使您始终知道您拥有的数据是您期望的格式。例如,“名字”和“姓氏”字段必须具有非空字符串值,并且“薪金”必须包含大于 0 的整数。与模式不匹配的传入数据将被拒绝。
但是许多现代数据格式,比如 JSON,不是关系型的,也不强制使用模式。JSON 允许不同的数据类型与不同行中的相同标记相关联。例如,姓氏可以同时是“Jones”和 56.07,也可以完全不存在。也许更糟的是,一个数组可以有混合类型,例如["John "," Betty ",13.89]
不合规的数据是下游运营的一个大问题。如果找到的值中有两个是“???"和“我希望我赚更多的钱”?如果 PRIMARY_CARE_DOC 字段为空,医院如何联系患者的医生?
拯救 JSON 模式
JSON 模式是一个可读的 JSON 对象,它描述了一组允许的 JSON 对象。
下面是一个简单的例子,说明 JSON 对象必须有名为 LNAME、FNAME 和 HOURLY_WAGE 的字段,LNAME 是必需的,没有其他字段。
{
“type”: “object”,
“properties”: {
“HOURLY_WAGE”: {“type”: “number”},
“FNAME”: {“type”: “string”},
“LNAME”: {“type”: “string”}
},
“additionalProperties”: false,
“required”: [“LNAME”]
}
所以这个 JSON 符合上面的模式:
{
"LNAME": "Jones",
"FNAME": "Betty",
"HOURLY_WAGE": 15
}
但事实并非如此,因为缺少 LNAME 并且不允许使用 LAST_NAME:
{
"LAST_NAME": "Jones",
"FNAME": "Betty",
"HOURLY_WAGE": 15
}
下面是一个更复杂的模式,它规定 LNAME 中必须有一些内容(不能是空字符串),HOURLY_WAGE 必须在 10 到 30 之间,DEPT 必须包含一个列出的值。
{
“type”: “object”,
“properties”: {
“HOURLY_WAGE”: {“type”: “number”, “minimum”: 10, “maximum”: 30},
“FNAME”: {“type”: “string”},
“LNAME”: {“type”: “string”, “minLength”: 1},
“DEPT”: {“enum”: [“HR”, “Acct”, “Dev”]}
},
“additionalProperties”: false,
“required”: [“LNAME”, “DEPT”, “HOURLY_WAGE”]
}
所以这符合模式:
{
"LNAME": "Jones",
"FNAME": "Betty",
"HOURLY_WAGE": 15,
"DEPT": "Acct"
}
但这不是。(对象有四个错误— LNAME 为空、HOURLY_WAGE 太低、不允许 DEPT 值、不允许名称/字段管理器。)
{
"LNAME": "",
"FNAME": "Betty",
"HOURLY_WAGE": 5,
"DEPT": "Accounting",
"MANAGER": "Bob"
}
到目前为止,我们已经将模式可视化地应用于一个 JSON 对象,这在您阅读文章时是很好的。在 Python 中,如何以编程方式做到这一点?轻松点。
from jsonschema import validatemy_schema = { "my schema here" }
my_json = { "my json object here" }try:
validate(instance=my_json, schema=my_schema)
except:
print ("Handle bad JSON here...")
能力
JSON Schema 已经从早期发展成为一种健壮的验证方法,并且拥有比上面显示的更多的功能。检查 JSON 对象的其他方法包括:
- 一个数是另一个数的偶数倍,如 5、10、15、20 等。
- 字符串匹配给定的正则表达式。
- 数组具有最小(或最大)数量的元素,或者具有唯一的元素。
- 字符串是有效的日期时间、日期或时间。
- 字符串是有效的电子邮件地址。
- 验证其他模式的元模式。
- 还有更多…
虽然它们看起来可能不像,但下面是完整的、有效的 JSON 对象,并且可以有一个 JSON 模式。然而,在我看来,这些“裸 JSON”对象是糟糕的样式,因为这些值没有名称标签,因此没有语义。但是请注意,如果遇到这种 JSON,您可以为其定义模式。
-77“hello”[4, 5, 6]
JSON 模式不具备现代关系系统(如 Oracle 数据库)的全部验证功能。有一些类型的完整性检查是 JSON Schema 做不到的。例如,您不能确保特定字段中的值也作为主键出现在另一个文件中(外键约束)。一般来说,JSON 模式可以查看一个对象(或相似对象的文件),但不能跨多个数据集查看。
提示和技巧
设计 JSON 模式是一门艺术。如果它们太具体,它们将只接受您想要的确切的数据格式,但却令人头疼,因为它们不允许合理的变化。例如,要求每个员工都有一个经理,这意味着您不能输入其经理刚刚辞职的员工。另一方面,如果模式太松散,它们会接受明显不好的数据,比如根本没有名字的客户。
虽然本文主要关注 JSON,但是您可以使用 JSON 模式来验证任何 NoSQL(非关系)数据格式。对于 CSV/TSV/YAML/etc,只需首先将字符串转换为 JSON,然后应用模式。显然,这样做是有时间/性能成本的,但是根据您的应用程序和数据大小,转换的时间可能是值得的。像这样的一些代码将把一个标准的 CSV 文件转换成有效的 JSON…
import pandas as pddf = pd.read_csv ("my_file.csv", header='infer', sep=',')df.to_json ("my_file.json", orient='records', lines=True)
了解更多信息
https://www.json.org/json-en.html
https://json-schema.org(JSON 模式)
https://jsonschema.net(生成模式的 GUI 工具,以 JSON 对象为例)
https://www.jsonschemavalidator.net(测试模式的 GUI 工具)
https://json-schema . org/implementations . html # validator-python(应用 JSON 模式的 Python 库)
Julia 的 Flux vs Python 的 TensorFlow:它们如何比较?
原文:https://towardsdatascience.com/julias-flux-vs-python-s-tensorflow-how-do-they-compare-574a3fb5e059
深度学习
端到端深度学习项目比较

第三人在像素上拍摄的照片
在我的 上一篇文章 中,我研究了 Julia 在速度方面比 Python/Numpy 有什么优势。
虽然知道这些很有用,但这并不是全部。了解它们在语法、库可用性/集成、灵活性、文档、社区支持等方面的比较也很重要。
本文在 TensorFlow 和 Flux (Julia 的 native TensorFlow equivalent)两个方面从头到尾贯穿了一个图像分类深度学习问题。这应该很好地概述了这两种语言在一般用法上的比较,并有望帮助您深入了解 Julia 在这种情况下是否是您的潜在选择(或优势)。
我还将努力强调 Julia 生态系统与 Python 和 TensorFlow 的成熟组合相比的优势,以及更重要的差距或缺点。
介绍
我特意为这个特殊的探索选择了一个图像分类问题,因为它在数据准备和深度学习框架本身方面都提出了一些很好的挑战:
- 影像需要从磁盘加载(不会使用现成的数据集,如 MNIST),因此将探索加载和预处理方法和惯例
- 图像通常表示为 3D 矩阵(高度、宽度、颜色通道),因此需要特别注意维度排序
- 为了避免过度拟合,通常需要图像增强,以探索库的可用性和易用性
- 就空间要求而言,图像本质上是一种“大”数据类型,这迫使我们对批处理、RAM 分配和 GPU 使用情况进行调查
注意: 虽然整篇文章中都提供了代码片段,但是 Jupyter 笔记本中包含了完整的端到端工作实现(从图片下载到模型训练),可以使用 Julia 和 Python 版本的代码。有关笔记本的链接,请参见下一部分。
顺便说一句,如果这是你第一次听说朱莉娅,我推荐你读“什么是朱莉娅?”一节来快速入门:
参考笔记本
本节详细介绍了笔记本的位置,以及在线环境的环境设置要求,如 Colab 和 Deepnote 。
笔记本
可以在此处找到适合您本地环境的原始笔记本:
https://github.com/thetestspecimen/notebooks/tree/main/julia-python-image-classification
…或者在 deepnote 或 colab 中快速启动。
Python 笔记本:
朱莉娅笔记本:
Julia 的环境设置
深度笔记
由于 deepnote 使用了 docker 实例,你可以很容易地建立一个“本地”docker 文件来包含 Julia 的安装说明。这意味着你不必像在 Colab 中那样用安装代码污染 Jupyter 笔记本。
在环境部分选择“本地”。/Dockerfile "。这将打开实际的 docker 文件,您应该在其中添加以下内容:
FROM deepnote/python:3.10
RUN wget https://julialang-s3.julialang.org/bin/linux/x64/1.8/julia-1.8.3-linux-x86_64.tar.gz && \
tar -xvzf julia-1.8.3-linux-x86_64.tar.gz && \
mv julia-1.8.3 /usr/lib/ && \
ln -s /usr/lib/julia-1.8.3/bin/julia /usr/bin/julia && \
rm julia-1.8.3-linux-x86_64.tar.gz && \
julia -e "using Pkg;pkg\"add IJulia\""
ENV DEFAULT_KERNEL_NAME "julia-1.8"
你可以从这个页面把上面的更新到最新的 Julia 版本,但是在我写作的时候 1.8.3 是最新的版本。
Colab
对于 colab,所有的下载和安装代码都必须包含在笔记本本身中,并且在安装代码运行后刷新页面。
幸运的是, Aurélien Geron 在他的 GitHub 上为 colab 的 Julia 提供了一个入门笔记本,这可能是最好的入门方式。
注意: 如果你使用上面的“在 colab 中打开”按钮(或者我链接的库中以“Colab”结尾的 Julia 笔记本),我已经在 Julia 笔记本中包含了这个起始代码。
数据
本文中使用的数据是一组图像,描绘了游戏“石头剪子布”中手的三种可能位置组合。

来自数据集三个不同类别的四个例子。作者合成图像。
每个图像都是 PNG 类型,尺寸为 300(宽)像素 x 200(高)像素,全彩色。
原始数据集总共包含 2188 幅图像,但是本文使用了一个更小的选择,它由三个类别中的每一个类别的 200 幅图像组成(总共 600 幅图像)。这主要是为了确保笔记本可以相对容易地运行,并且数据集是*衡的。
本文中使用的较小数据集可从以下网址获得:
https://github.com/thetestspecimen/notebooks/tree/main/datasets/rock_paper_scissors
这个计划
有两个独立的笔记本。一个是使用 TensorFlow 深度学习框架用 Python 编写的,另一个是用 Julia 编写的,利用了 Flux 深度学习框架。
这两个笔记本使用完全相同的原始数据,并将经历相同的步骤,最终得到一个经过训练的模型。
虽然不可能在两个笔记本之间精确匹配方法论(如你所料),但我已经尽力让它们尽可能接*。
大纲
每个笔记本包括以下步骤:
- 从远程位置下载图像数据,并提取到本地存储
- 从本地文件夹结构加载图像以备处理
- 查看图像数据,并查看样本图像
- 将数据分成训练/验证集
- 增强训练图像以避免过度拟合
- 为模型准备图像(缩放等。)
- 批量数据
- 创建模型和相关参数
- 火车模型(应该能使用 CPU 或 GPU)
比较—包装

在 Unsplash 上 Leone Venter 拍摄的照片
接下来的比较部分将探索 Julia 与 Python 实现的一些差异(好的或坏的)。通常会按照上一节中的要点进行细分。
软件包安装
首先,简单介绍一下软件包的安装和使用。
这两种语言遵循相似的模式:
- 确保软件包安装在您的环境中
- 将该包“导入”到您的代码中以使用它
唯一真正的区别是,Julia 既可以在运行代码之前在“环境”中安装包,也可以从代码内部安装包(如本文在 Julia 笔记本中所做的那样):
Julia 中的“Pkg”相当于 Python 中的“pip”,也可以从 Julia 的命令行界面访问。

使用命令行添加包的示例。作者截图
包装使用
就能够从代码中访问已安装的包而言,您通常会使用关键字“using”而不是 Python 中使用的“import”:
Julia 也有一个“导入”关键字。有关差异的更多详细信息,请查看文档。在大多数一般用例中,“使用”更合适。
注意: 在本文中,当引用模块中的方法时,我特意使用了完整路径,包括模块名。这不是必须的,它只是让我们更清楚地知道方法是从哪个包中被引用的。例如,这两个是等效的和有效的:
using Random
Random.shuffle(my_array) # Full path
shuffle(my_array) # Without package name
比较—下载和提取

米格尔·Á的照片。像素上的帕德里纳
图像数据以 zip 文件的形式远程提供。zip 文件包含三个类别的文件夹,每个文件夹包含 200 张图片。
第一步,下载并提取图像文件。这在两种语言中都相对容易,有了可用的库,在 Julia 中可能更直观。Python 实现:
Julia 实现会是什么样子会是什么样子:
using InfoZIP
download("https://github.com/thetestspecimen/notebooks/raw/main/datasets/rock_paper_scissors/rock_paper_scissors.zip","./rock_paper_scissors.zip")
root_folder = "rps"
isdir("./$root_folder") || mkdir("./$root_folder")
InfoZIP.unzip("./rock_paper_scissors.zip", "./$root_folder")
你可能会注意到,我说的是它可能是什么样子。如果你看一下笔记本,你会发现在 Julia 中,我实际上使用了一个自定义函数来进行解压缩,而不是使用上面详述的 InfoZIP 包。
原因是我无法在我使用的所有环境中安装 InfoZIP 包,所以我认为包含它是不公*的。
这是包装的问题吗?我想不会。我认为这可能是因为在线环境(colab 和 deepnote)主要不是面向 Julia 的,有时这会引起问题。InfoZIP 在本地安装并运行良好。
这是包装的问题吗?我想不会。
我还应该注意,在 colab 中使用 GPU 实例时使用“Plots”库(相当于 matplotlib)会导致安装失败!
这可能看起来相对微不足道,但它直接说明了在处理一种新的语言时可能会遇到的潜在问题。此外,由于社区较小,你不太可能在网上找到问题的解决方案。
仍然值得指出的是,当我在本地的计算机上工作时,我对这两个包都没有这样的问题,我希望随着时间的推移,在线环境如 colab 和 deepnote 可能会变得更加 Julia 友好。
比较—处理图像

现在图像已经在本地环境中,可以看看我们如何加载它们并与之交互。
Python:
朱莉娅:
在前面的部分中,事情相对来说是相似的。现在事情开始出现分歧…
这个加载图像的简单示例突出了 Python 和 Julia 之间的一些明显差异,尽管从两个看起来相似的代码块中可能不会立即看出来。
零分度与一分度
一般来说,这两种语言的主要区别之一可能是,Julia 对数组和矩阵使用从 1 开始的索引,而不是从 0 开始。
Python —“随机 _ 图像”的第一个元素:
img = mpimg.imread(target_folder + "/" + random_image[0])
朱莉娅——“图像通道”形状的第一个要素:
println("Colour channels: ", size(img_channels)[1])
我可以理解这是一个有争议的差异。在现实中,这一切都归结于偏好,但抛开任何个人偏好,1-indexing 在数学上更有意义。
同样值得记住的是这种语言所针对的工作领域(即更多基于数学/统计的编程专业人员,而不是纯粹的程序员/软件工程师)。
不管你的立场如何,有一点是非常需要注意的,特别是如果你正在考虑将一个项目从 Python 移植到 Julia。
图像是如何加载和用数字表示的
当使用 python 和“imread”加载图像时,它们被加载到一个 numpy 数组中,其形状(高度、宽度、RGB 颜色通道)为 float32 数字。很简单。
在 Julia 中,图像被加载为:
Type: Matrix{RGB{N0f8}}
不太清楚…所以让我们稍微探讨一下。
在 JuliaImages 中,默认情况下,显示所有图像时假定 0 表示“黑色”,1 表示“白色”或“饱和”(后者适用于 RGB 图像的通道)。
也许令人惊讶的是,这种 0 到 1 的约定甚至在每个颜色通道仅使用 8 位对强度进行编码时也适用。JuliaImages 使用一种特殊的类型
N0f8,它将一个 8 位的“整数”解释为 1/255,从而以 256 步对 0 到 1 的值进行编码。
事实证明,Julia 使用的这种奇怪的约定实际上对机器/深度学习非常有帮助。
在 Python 中处理图像时,您经常要做的事情之一是将值缩放 1/255,以便所有值都在 0 和 1 之间。这对于 Julia 是不必要的,因为缩放是由图像本身使用的“N0f8”类型自动完成的!
不幸的是,您在本文中看不到这种比较,因为图像是 PNG 类型的,而且 Python 中的 imread 无论如何都将数组作为 0 到 1 之间的浮点值返回(顺便说一下,这是它唯一支持的格式)。

迈克尔·马森在 Unsplash 上的照片
然而,如果您要在 Python 中加载 JPG 文件,您将从 imread 接收 0 到 255 之间的整数值作为输出,并且必须在稍后的预处理管道中对它们进行缩放。
除了自动缩放,还有一点值得注意的是图像实际上是如何存储的。Julia 使用了将每个像素表示为一种对象的概念,因此如果我们查看输出类型和形状:
Type: Matrix{RGB{N0f8}}
Shape: (200, 300)
字面意思是 200x300 的矩阵。那三个颜色通道呢?我们期望 200x300x3,对吗?
嗯,Julia 将 200x300 矩阵中的每一项视为一个“像素”,在这种情况下,它有三个值,分别代表红色、绿色和蓝色(RGB),如类型“ RGB {N0f8}”所示。我认为它就像一个对象矩阵,对象被定义为有三个变量。
然而,疯狂的背后是有原因的:
这种设计选择有利于通用代码处理灰度和彩色图像,而无需引入额外的循环或检查颜色维度。它还为 3d 灰度图像提供了更合理的支持——3d 灰度图像的尺寸可能恰好为 3——因此有助于统一“计算机视觉”和“生物医学图像处理”社区。
看起来这些决定是经过深思熟虑的。
然而,你不能将这种格式的图像输入到神经网络中,即使是 Flux 也不行,所以在以后的阶段需要将它分割成“合适的”3D 矩阵。正如你将看到的,事实证明这非常简单。
比较—数据准备管道

就易用性而言,这绝对是 Python 和 TensorFlow 击败 Julia 的领域之一。
Python 实现
我可以或多或少地将我所有的图像加载到一个批量优化的数据集中,准备好投入到一个深度学习模型中,只需四行代码:
训练图像增强与模型层一样容易处理:
Julia 实现
在 Julia 中实现同样的功能需要更多的代码。让我们加载图像,并将它们分成训练集和验证集:
我应该注意的是,在现实中,您可以删除该方法的“加载和缩放”以及“洗牌”部分,并在以后以更简洁的形式处理它,因此它并不像看起来那么糟糕。我主要把这些部分作为参考。
Python 和 Julia 之间一个有用的区别是,如果你愿意,你可以定义类型,但这不是绝对必要的。一个很好的例子是上面函数中为“image_size”指定的“Tuple{Int,Int}”类型。这确保了整数总是被传递,而不必在函数本身中做任何特定的检查。
增强管道
图像增强非常简单,就像 TensorFlow 一样。由于 Augmentor 包,您还可以使用“Resize”层(前面提到的一种更简洁的形式)在这里添加图像大小调整:
您还应该注意到,Augmentor(使用 Julia 图像的包装)能够将“RGB{N0f8}”类型更改为 float32 类型的 3D 矩阵,以便传递到深度学习模型中:
我想将上面的三个步骤进行分解,因为我认为理解它们具体做什么是很重要的,因为我可以看到这可能会很混乱:
- SplitChannels —获取形状为 160(高)x 160(宽)的矩阵{RGB{N0f8}}的输入,并使用类型数组{N0f8}将其转换为 3(颜色通道)× 160(高)× 160(宽)。这里值得注意的是,颜色通道成为第一维,而不是像 python/numpy 中那样成为最后一维。
- PermuteDims —只是重新排列数组的形状。在我们的例子中,我们将输出的尺寸改为 160(宽)x 160(高)x 3(颜色通道)。注:高度和宽度的顺序也被调换了。
- 转换类型 —将 N0f8 转换为 float32。
你可能想知道为什么维度需要如此多的转换。这样做的原因是由于在后面的阶段中需要将输入形状输入到通量中的 Conv 层中。一旦我们完成了配料,我会更详细地介绍,因为这是我对整个过程的主要不满之一…
应用增强
现在我们有一个有趣的情况。我需要把增强管道应用到图像上。没问题!由于优秀的 augmentbatch!()增强器包提供的功能。
除了无论我如何努力,我都不能让它与数据一起工作。不断的错误(当我疯狂地试图找出一个解决方案时,忘记记下确切的问题,但在各种论坛上都有类似的问题)。
总有可能这是我的错,我有点希望是这样!
作为一种变通方法,我使用了一个使用“非批处理”方法增强的循环。我还使用 OneHotArrays 包对标签进行了一次性编码:
我认为这很好地说明了 Julia 生态系统的某些领域可能会给你带来一些实现上的麻烦。你也不一定能在网上找到解决方案,因为社区比较小。
然而,Julia 的一个主要优势是,如果你不得不求助于像 for 循环这样的东西来解决问题,或者只是实现一点定制的需求,你可以相当肯定地说,你编写的代码将是优化的和快速的。不是每次使用 Python 都可以依赖的东西。
来自列车管道的一些示例增强图像:
批处理数据
Julia 中的批处理是 TensorFlow 级别的简单操作。在朱莉娅中,有多种方法可以做到这一点。在这种情况下,将使用 Flux 内置的数据加载器:
这个方法没什么值得注意的。和罐头上写的一模一样。(正如我前面提到的,这是您可以混洗数据的另一个位置。)
我们已经准备好将数据传递给模型,但是首先要稍微绕一下路…
混乱的输入形状

在 Unsplash 上由Torsten dede RICS拍摄的照片
现在我想回到模型的输入形状。您可以在上一节的最后一个代码块中看到数据集具有形状:
数据:160(宽)x 160(高)x 3(颜色通道)x 32(批次大小)
标签:3(标签)x 32(批次大小)
在 TensorFlow 中,输入形状为:
数据:32(批次尺寸)x 160(高度)x 160(宽度)x 3(颜色通道)
标签:32(批次尺寸)x 3(标签)
来自 Julia 文档:
图像数据应按 WHCN 顺序(宽度、高度、通道、批次)存储。换句话说,一个 100×100 的 RGB 图像将是一个
100×100×3×1数组,一批 50 个将是一个100×100×3×50数组。
我不知道为什么选择这个惯例,尤其是高度和宽度的切换。
坦白说,没什么好抱怨的,这只是一个惯例。事实上,如果根据以前的经验,我会怀疑一些经过深思熟虑的优化是原因。
与其他语言相比,我在定义上遇到了一些稍微奇怪的变化,但我发现这是有真正原因的(正如你所希望的)。
作为一个具体的(相关的)例子,以朱莉娅的图片包为例。
我们使用 CHW(即通道-高度-宽度)顺序而不是 HWC 的原因是,这为
Array提供了一种记忆友好的索引机制。默认情况下,在 Julia 中,第一个索引也是最快的(即,在内存中有相邻存储)。更多细节,请参考性能提示:按内存顺序访问数组,沿着列
额外的困惑
这引出了我的一个主要抱怨。
我可以接受这是一种不同的语言,所以有一个潜在的很好的理由来制定一个新的约定。然而,在加载图像并准备好插入深度学习模型的过程中,我不得不到处改变输入形状:
- 图像加载到:矩阵{RGB{N0f8}}(高 x 宽)
- 拆分通道:{N0f8}(通道 x 高度 x 宽度)
- 移动通道和开关高度和宽度:{N0f8}(宽度 x 高度 x 通道)
- 转换为 float32
- Batchsize(作为最后一个元素):{float32}(宽度 x 高度 x 通道 x batchsize)
显然,(通道 x 高度 x 宽度)对于图像来说是最理想的,这就是 native Julia 加载图像的方式。难道不是:
(通道 x 高度 x 宽度 x 批次大小)?
可能会省去很多潜在的困惑。
我真心希望有人能指出我愚蠢地忽略了一些东西(真的)。主要是因为我对设计这种语言时对细节和思想的关注印象深刻。
好的。抱怨够了。回到项目上。
比较—模型

照片由 DS stories 在 Pexels 上拍摄
Julia 中的模型定义与 TensorFlow 的顺序方法非常相似。它只是被称为链而不是。
主要区别:
- 如果您想要使用它,您必须显式地将模型(和数据)加载到 GPU 上
- 你必须显式地指定输入和输出通道(input=>output) — 我相信有 形状推理宏 能对此有所帮助,但我们不会在这里深入讨论
总而言之,使用起来非常直观。它还迫使您对数据如何在模型中移动和重塑有一个正确的理解。如果你问我,这是一次很好的练习。
没有比黑箱系统更危险的了,也没有思考。我们当然都这样做,因为有时我们只是想尽快得到答案,但这可能会导致一些难以追踪和令人困惑的结果。
没有比黑箱系统更危险的了,也没有思考。我们都这样做…
计算设备规格
在笔记本的代码中,您可以看到我通过使用变量“calc_device”明确定义了哪个设备(CPU 或 GPU)应该用于计算。
将“calc_device”变量改为 gpu 将使用 gpu。将其更改为 cpu 以仅使用 cpu。你当然可以直接用 gpu 或者 cpu 替换所有的“calc_device”变量,工作方式完全一样。
比较—损耗、精度和优化器

同样,非常类似于张量流:
有几件事值得注意:
逻辑交叉熵
你可能已经注意到模型没有 softmax 层(如果不是快速浏览)。
这在数学上等同于
crossentropy(softmax(ŷ), y),但在数值上比单独使用函数[crossentropy](https://fluxml.ai/Flux.jl/stable/models/losses/#Flux.Losses.crossentropy)和 softmax 更稳定。
onecold
onehot 的反义词。很好的名字,不知道为什么有人以前没有想到。
单行函数定义
如果你不熟悉 Julia,也值得指出的是,损失函数和精度函数实际上是一行中正确的函数定义。即与此相同:
function loss(X, y)
return Flux.Losses.logitcrossentropy(model(X), y)
end
朱莉娅语言的许多伟大特征之一。
(注意:实际上你可以省略上面的 return 关键字。缩短函数的另一种方法。)
比较—培训

维克多·弗雷塔斯在 Unsplash 上拍摄的照片
在朱莉娅,训练可以是复杂的,也可以是简单的。确实有很大的灵活性,在我将要向您展示的内容中,我甚至还没有触及它的表面。
如果你想让它变得非常简单,在 TensorFlow 中有一个我称之为“model.fit”的等价物:
for epoch in 1:10
Flux.train!(loss, Flux.params(model), train_batches, opt)
end
没错,基本上就是一个 for 循环。您还可以添加一个回调参数来处理打印损失或准确性(这不会像 TensorFlow 那样自动完成)或提前停止等问题。
然而,当处理大量数据(如图像)时,使用上述方法可能会导致问题,因为它需要将所有训练数据加载到内存中(本地或 GPU 上)。
因此,以下函数允许将批处理一批一批地加载到 GPU(或 CPU 运行的内存)上。它还打印训练损失和准确度(所有批次的*均值),以及整个认证数据集的认证损失。
****注意:如果验证集非常大,您也可以逐批计算验证损失/准确度,以节省内存。
实际上,它有两个循环:一个用于历元,一个内部循环用于批处理。
需要注意的是:
x, y = device(batch_data), device(batch_labels)
gradients = Flux.gradient(() -> loss(x, y), Flux.params(model))
Flux.Optimise.update!(optimiser, Flux.params(model), gradients)
这是将一批数据加载到设备(cpu 或 gpu)上,并通过模型运行数据。
所有剩下的代码是统计数据的收集和打印。
比张量流更复杂,但不极端。
…我们完成了。一次有趣的旅行。
总结(和 TL;博士)

在文章的开头,我说过,除了速度之外,在决定一门语言与你已经使用的语言相比是否值得投资时,还有其他重要的衡量标准。我特别点名:
- 句法
- 灵活性
- 库可用性/集成
- 证明文件
- 社区支持
在经历了整个项目后,我认为总结一些发现可能是个好主意。请记住,这是基于我在为本文生成代码时的印象,仅仅是我的观点。
句法
我认为来自 Python 的语法非常相似,相对容易掌握,而且我认为在相当多的情况下,它甚至比 Python 更“高级”和更容易使用。
让我们先解决有争议的问题。是的,Julia 使用 1 索引数组而不是 0 索引数组。我个人更喜欢这样,但我怀疑会有很多人不喜欢。还有更细微的区别,比如数组切片包含了最后一个元素,这与 Python 不同。只是要小心一点!
但是有很多好东西…
例如,当使用函数时,你不需要冒号或 return 关键字。你甚至可以在不丢失代码含义的情况下创建一个简洁的行:
# this returns the value of calculation, no return keyword needed
function my_func(x , y)
x * y + 2
end
# you can shorten this even further
my_func(x, y) = x * y + 2
注意标准函数表达式中“end”的使用。这在 Julia 中被用作缩进无关紧要,在我看来这是一个巨大的进步。空格对制表符的传奇不适用于朱莉娅。
常见的 if-else 类型语句也可以用在非常简洁明了的单行语句中:
# ternary - if a is less than b print(a) otherwise print(b)
(a < b) ? print(a) : print(b)
# only need to do something if the condition is met (or not met)?
# use short circuit evaluation.
# if a is less than b print(a+b), otherwise do nothing
(a < b) && print(a+b)
# if a is less than b do nothing, otherwise print(a+b)
(a < b) || print(a+b)
我可能只是触及了表面,但已经给我留下了深刻的印象。
灵活性
我认为灵活性是朱莉娅真正擅长的。
正如我已经提到的,你可以像 Python 一样编写简洁明了的代码,但是如果你需要或者想要使用它们,也可以使用其他的特性。
第一个也是最重要的可能是使用类型的选项,这在 Python 中是不可能的。尽管推断类型听起来是个好主意,但它们确实有各种缺点,比如使代码更难阅读和跟踪,以及引入难以跟踪的错误。
在最有意义的时候灵活地指定类型是一种优秀的能力
在最有意义的时候灵活地指定类型是 Julia 的一个优秀能力。不过,我很高兴它没有被强制推行。
Julia 也面向科学和数学社区。因此,在代码中使用 unicode 字符是一个非常有用的特性。我不太可能会用到它,但由于我来自数学/工程背景,我很欣赏它的包容性。
库可用性/一致性
这是一个有点混杂的包。

在 Unsplash 上由伊尼基·德尔·奥尔莫拍摄的照片
这篇文章使用了相当多的包。从 Flux 和 Images 等大型软件包,到 OneHotArrays 和 Augmentor 等定制软件包。
总的来说,*均来说,它们没有达到 Python / TensorFlow 中的复杂程度、集成和易用性。做同样的事情需要更多的努力,你可能会发现更多的问题,遇到更多的矛盾。我对此并不感到惊讶,毕竟这是一个不太成熟的生态系统。
例如,通过简单的一行界面批量处理和优化数据的能力是 TensorFlow 的一个非常好的功能。事实上,您不必编写额外的代码来打印培训和验证损失/准确性也非常有用。
然而,我认为朱莉娅的图书馆生态系统有足够的多样性和复杂性,可以真正做得更多。总的来说,这些包也可以很好地组合在一起。我不认为这是一个交易破坏者。
总结一下我在这篇文章中遇到的主要问题:
- 我无法让软件包 InfoZIP 在所有环境下一致安装
- 我拿不到!augmentbatch() 函数来处理本文中的数据,这将是非常有用的
- 由于某种原因,在 JuliaImages 和 Flux 之间定义图像形状的方法有点混乱,这导致了大量的矩阵整形。这并不难,只是似乎没有必要。
证明文件
核心语言的文档是一个非常完整和可靠的来源。我建议的唯一一件事是,对于一些方法给出的例子可以更详细和多样一些。一个小的吹毛求疵,否则优秀的东西。
超越核心语言,文档的细节和可用性会有所不同。
我对较大的包印象深刻,我想无论如何它几乎都是核心包。就这篇文章而言,那将是Julian images和 Flux 。我认为它们非常全面,我尤其喜欢强调为什么事情会以某种方式完成的努力:
我们使用 CHW(即通道高度宽度)顺序而不是 HWC 的原因是,这为
Array提供了一种记忆友好的索引机制。默认情况下,在 Julia 中,第一个索引也是最快的(即,在内存中有相邻存储)。
随着包变得越来越小,文档通常都在那里,但是有点简洁。ZipFile 就是一个很好的例子。
虽然,通常包是开源的,托管在 Github 上,贡献通常是受欢迎的。正如 JuliaImages 所说:
请帮助改进这份文档——如果有什么让你困惑,很可能你并不孤单。在你阅读的过程中很容易做到:只需点击上面的“在 GitHub 上编辑”链接,然后直接在你的浏览器中编辑文件。您的更改在永久生效之前会经过开发人员的审核,所以不要担心您是否会说错话。
社区
这个社区正是我所期望的,充满活力和热情,但比 Python/tensor flow 的要小得多。如果你需要查询的答案,尤其是更多定制的查询,你可能需要比*时更深入地挖掘 Google 和 StackOverflow 之类的东西。
这显然会随着采用而改变,但幸运的是文档非常好。
结论
总的来说,我认为 Julia 实际上是一种很好的语言。
这种语言的创造者试图把他们喜欢使用的语言的精华部分结合成一种超级语言,他们称之为 Julia。
在我看来,他们做得非常好。该语法确实易于使用和理解,但也可以包含高级和稍微晦涩的元素——这是一种非常灵活的语言。
它也真的很快。

图片由 Wokandapix 来自 Pixabay
是的,可能会有一个轻微的学习曲线来转换你目前正在使用的语言,但同样,我不认为它会像你想象的那样严重。为了帮助您,Julia 的文档包括了对主要语言的详细比较:
我看到的唯一缺点是,即使已经存在了 10 年,与竞争对手相比,它还是相对较新。这对文件和学习资源的质量和数量有直接影响。这似乎比大多数人愿意承认的对收养有更大的影响。金钱也有帮助,一如既往…但我没有数据来评论这种情况。
成为最好的产品或解决方案并不能保证成功和被广泛接受。现实世界不是这样的。
…但是在了解了 Julia 的工作方式(即使是最基础的)之后,我真的希望更多的人看到它的潜力并加入进来。
如果你觉得这篇文章有趣或者有用,记得关注我,或者注册我的时事通讯来获取更多类似的内容。
如果你还没有,你也可以考虑订阅媒体。你的会员费不仅直接支持我,也支持你所阅读的其他作家。你还可以完全不受限制地访问媒体上的每个故事。
使用我的推荐链接注册会给我一点回扣,对你的会员资格没有影响,所以如果你选择这样做,谢谢你。
**https://medium.com/@maclayton/membership
参考
[1]朱利安·德·拉·布鲁尔-特勒奥,石头剪子布图像 (2018),卡格尔,许可证: CC BY-SA 4.0**
七月版:放大统计数据
原文:https://towardsdatascience.com/july-edition-zooming-in-on-statistics-a12d20b9c7af
从对初学者友好的指南到对高级主题的深入探讨

随着新数据工具、人工智能研究和产品发布的不断涌现,我们总是忍不住关注最新的闪亮发展。不过,有时候回归基础还是有帮助的。对于数据专业人士来说,这意味着统计学:尽管构成了其他一切——闪闪发光的小发明等等——赖以存在的基础,但这一学科偶尔会让人感觉尘土飞扬,不受重视。
正如阿德里安娜·克莱恩在我们本月的一个亮点中恰当地指出的那样(向下滚动找到它),“虽然这些令人兴奋的算法中有许多具有巨大的适用性,但数据科学社区的统计基础经常被忽视。”这个月,我们将迎头反击这种趋势,阅读一系列关于统计学的理论、实践和挑战的优秀读物。尽情享受吧!
如果你想给我们的作者一个激励,考虑通过成为中级会员来表达你的支持。
一个结束语:我们正在计划未来的月刊,我们将重点介绍关于数据科学(及相关领域)写作过程和益处的深思熟虑的文章。如果你有自己写作历程中的感悟可以分享——就像高在她最*的一篇文章中表现得如此出色一样——请把它们发给我们吧!
TDS 编辑亮点
- 为什么统计在数据科学、机器学习和分析中很重要?
(2022 年 5 月,7 分钟) - 统计学基本概念的简单解释如果你是刚入门,良好的基础是至关重要的——Chi Nguyen面向初学者的介绍涵盖了抽样、置信区间等等。(2022 年 6 月 7 分钟)
- 机器学习的基本要领:统计学从事机器学习模型工作的人可能需要更专业的知识;Kurtis Pykes 的指南是从头开始构建它的好地方。(2022 年 5 月 7 分钟)
- 参数测试与非参数测试,以及何时使用它们在一篇简洁明了的文章中, Adrienne Kline 帮助读者根据你正在处理的数据类型决定使用哪种统计测试。(2022 年 4 月 3 分钟)
- 【p 值:科学种族主义的遗产
我们在统计学中使用的概念和术语是一个漫长、复杂、有时甚至是暴力的历史的结果。Raveena Jay 对 p-values 的深入探讨突出了它们与殖民压迫和种族主义的联系,并提出了不那么令人担忧的替代工具。(2022 年 5 月 12 分钟) - 均值还是中位数?选择基于决策,而不是传统智慧可能建议的分布,但泰勒·布芬顿博士最*的文章为“许多应用中,即使数据有偏差,均值也是比中值更有用的衡量标准”提供了有力的论据(2022 年 4 月 12 分钟)
- 如何比较两个或两个以上的分布现实世界中有许多场景需要比较多个分布,并评估它们之间差异的大小和重要性。Matteo Courthoud 的指南提供了详细的、完整的正确操作方法的解释。(2022 年 6 月 18 分钟)
原始特征
我们最*提供的内容包括与 TDS 作者的启发性对话,以及面向数据科学家的 Python 热门阅读列表。
- 大多数挑战都是伪装的机遇 。”我们和 Tessa Xie 聊了聊如何导航自己的数据科学职业道路、选择合适的角色以及公开写作的好处。
- 我们如何在公*性研究和真实世界用例之间架起桥梁? 康纳·奥沙利文反思了良好沟通技巧的重要性,并解释了他对算法公*性越来越感兴趣的原因。
- Python 为数据科学家,从 A 到 Z 。我们为数据从业者收集了一些关于 Python 技能的最佳*期帖子——无论您是初学者还是编码专家,您都可能会发现一些新的东西需要学习。
热门帖子
如果你想深入了解上个月最热门的文章和对话,这里有一些 6 月份阅读量最大的帖子。
- 2022 年你应该知道的 5 个高级 SQL 概念bySuraj Gurav****
- 度量和损失函数有什么区别? 作者卡西·科济尔科夫
- 6 机器学习算法任何学习数据科学的人都应该知道 作者弗兰克·安德拉德
- 初学如何开始你的第一个数据科学项目 作者拉希·德赛
- 图神经网络 的新计算结构迈克尔·布朗斯坦,克里斯蒂安·博德纳尔和法布里齐奥·弗拉斯卡
- 6 种强大的学习技巧,帮助你掌握数据科学中最棘手的课题 作者麦迪逊·亨特
- 3 个用于交互数据分析的 Python 包 作者 Cornellius Yudha Wijaya
在结束之前,请和我们一起热烈欢迎过去一个月加入 TDS 的新作者,包括阿内什·博斯、哈维尔·马丁内斯·奥赫达、安德鲁·塔夫特、尼克·弗罗因德、阿维亚德·克林格、阿尤什·帕特尔、阿尔斯兰·沙希德、玛丽娜·托希奇、斯瓦纳瓦那瓦 凯瑟琳·赫尔切拉、安德列亚斯·科普、乔伊勒·莫诺波利、阿布舍克·帕瓦尔、丹尼尔·埃佐德、阿迪·贾恩、拉齐特·贾恩、哈桑·贾韦德、肯·莫里瓦基、乔恩·金佩尔 路易·莱克勒克,王泽南,拉古·苏拉姆,让-克洛德·科特,罗汉维吉,埃德温·苏特里诺,哈兰兄弟,莱奥尼·莫尼加蒂。 如果你想在未来的月刊中看到你的名字,我们希望收到你的来信。
用深度学习跳跃
原文:https://towardsdatascience.com/jump-with-deep-learning-4d2fedb4a61
捕捉跳跃不连续性的深度学习模型及其在偏微分方程中的应用实例

作者图片:跳跃中断
当所有单侧极限都存在且有限,但相等或不相等时,就形成了函数中的跳跃不连续性,通常在分段函数中会遇到这种情况,因为分段函数的不同区域由不同的连续函数定义。正如万有逼*定理所说,在人工神经网络的数学理论中,一个单隐层包含有限个神经元的前馈网络,在对激活函数的温和假设下,可以逼*有限维实空间中紧致子集上的连续函数。然而,对于深度神经网络来说,学习具有跳跃不连续性的函数是困难的,因为值跳跃点周围的误差通常很大。
在本文中,我将展示如何构建一个特定的模块来捕获跳跃不连续性,并使用网络来处理偏微分方程(PDE)问题。
神经网络不能很好地跳跃
经典网络
让我们从一个只有一个跳跃不连续点的简单分段函数开始:当 x 为正时,函数等于 sin(x ),否则等于 sin(x ),如本文开头所示。
我们首先构建一个简单的经典神经网络,它包含三个密集层,这样两个隐藏层都有 32 个单元。输出层具有足够简单的线性激活函数。下图说明了该模型的架构。

作者图片:三层模型
在 20 个训练时期之后,我们观察到当函数接*跳跃不连续点时,模型学习差得多,并且所学习的函数甚至不形成跳跃。

作者图片:原始功能与习得功能
具有亥维赛活化的网络
结果并不令人惊讶:所有层的激活函数都是连续的,它们使原始曲线规则化。因此,一个自然的想法是将输出层的激活函数设置为 Heaviside 函数。
亥维赛函数,也称单位阶跃函数,通常用 H 或θ表示,是一个阶跃函数,其零为负变元,一为正变元。我们可以用下面的方法用 Keras 定义 Heaviside 函数。注意,由于亥维赛函数的导数是狄拉克分布,它不是在任何地方都定义的,我们简单地将梯度设为 0。
@tf.custom_gradientdef heaviside(x):
ones = tf.ones(tf.shape(x), dtype=x.dtype.base_dtype)
zeros = tf.zeros(tf.shape(x), dtype=x.dtype.base_dtype)
def grad(dy):
return dy*0
return keras.backend.switch(x > 0.5, ones, zeros), grad
我们现在建立一个与之前结构相同的模型,唯一的变化是输出层的激活函数被设置为我们刚刚定义的亥维赛函数。在 20 个训练时期之后,我们观察到该模型确实捕获了跳跃不连续性,但是根本没有能力预测*滑函数部分。

图片由作者提供:原始功能与具有亥维赛激活的学习功能
这种行为的原因来自于这样一个事实,即损失函数的极小值是通过计算梯度来搜索的,而 Heaviside 函数的导数是狄拉克分布,其值不能在实轴上的任何地方定义。由于基于梯度的方法未能达到期望的最小值,学习的函数停留在小*台上。
建立“良好的跳跃模型”
为了获得具有良好梯度的最小值并同时捕捉跳跃不连续性,我们现在试图构建一个“井跳跃模型”,该模型利用了经典方法和具有亥维赛激活的方法。其思想是建立一个输出线性函数和 Heaviside 函数的线性组合的层,如下式:output = w_s * S(input)+w_h * H(input)+b,其中 S 和 H 是线性函数,Heaviside 函数,w _ S,w _ H,b 是可训练参数。
import kerasclass Linear(keras.layers.Layer):
def __init__(self, units=32, input_dim=32):
super(Linear, self).__init__()
self.w = self.add_weight(shape=(input_dim, units), initializer="random_normal", trainable=True)
self.b = self.add_weight(shape=(units,), initializer="zeros", trainable=True) def call(self, inputs):
return tf.matmul(inputs, self.w) + self.b class JumpBlock(keras.layers.Layer):
def __init__(self):
super(JumpBlock, self).__init__()
self.linear0 = Linear(1,1)
self.linear1 = Linear(1,1)
self.linear2 = Linear(1,1)
self.linear2.b.trainbale=False
def call(self,inputs):
x=self.linear0(inputs)
h= self.linear1(heaviside(x))
s=self.linear2(x)
return h+s
我们将跳转块作为输出层添加到网络中。下图显示了“良好跳转模型”的架构:

作者图片
我们以“井跃模型”的好结果来结束这一节,它成功地捕捉到了给定函数的*滑部分和跳跃不连续性。

作者图片:“井跃模型”的结果
关于代码的更多细节,请查看笔记本。
双曲偏微分方程的应用
我现在想把“好的跳跃模型”应用到一个双曲型偏微分方程中。为了简化这个故事,让我们考虑一个足够简单但不是试探性的方程:具有简单形式的无粘伯格斯方程:u _ t+u * u _ x = 0。我们总是对初始问题感兴趣:对于给定的函数,找到一个满足方程并且初始等于函数的解。一般来说,即使一个足够光滑的初始条件也能形成激波解,作为跳跃间断处理。
这里,让我们考虑一个时间序列作为 Burgers 方程的解,该解是通过从时间 0 到时间 1 的 Glimm 方案离散化成 500 个时间步长而获得的,并且我们想使用该时间序列来预测时间经过 1 时的解。为此,我们使用一个结合了时间卷积网络 (TCN)和我们的“良好跳跃模型”的网络。这个想法是让 TCN 捕捉解的时间演化,让“好的跳跃模型”强制冲击波的形成。
模型的输入是 20 个时间步内的解,输出是下一个时间步的解。这里的简短说明是,我们使用的损失函数是均方误差,因为 Burgers 方程通常在弱意义上满足,即不是在所有地方都满足,因此我们必须使用 L2 范数。我们对模型进行了 100 个时期的训练,并获得了 e-4 的验证损失。
from tcn import TCN
from tensorflow.keras.layers import Densefrom tensorflow.keras.models import Sequentialtcn_layer = TCN(input_shape=(time_step,space_size))m=Sequential()
m.add(tcn_layer)
m.add(Dense(32))
m.add(Dense(32))
m.add(Dense(space_size))
m.add(JumpBlock())
m.compile(optimizer='adam', loss='mse')x_train=x[:400]
y_train=y[:400]
x_val=x[400:]
y_val=y[400:]m.fit(x_train, y_train, epochs=100, validation_data=(x_val,y_val))
瞧,这就是模型在时间=1.2 时预测的伯格斯方程的解。

后续步骤
在本文中,我们构建了一个跳转块,它结合了线性函数和 Heaviside 函数来学习具有跳转不连续性的函数。然后,我们使用 Burgers 方程中的块来预测更长时间的解,并得到或多或少令人满意的结果。然而,对于解的预测,仅使用数据作为数值解,而忽略了方程本身的结构。一种想法是将这样的块与物理通知神经网络 (PINN)的逻辑相结合,这是一种深度学习方法,用于解决往往无法单独形成冲击波的 PDEs。
六月版:走进 SHAP
原文:https://towardsdatascience.com/june-edition-get-into-shap-b9e0c1e905b8
一个强大的可解释人工智能方法的来龙去脉

赫克托·j·里瓦斯在 Unsplash 上拍摄的照片
*年来,机器学习模型的能力和规模已经发展到了新的高度。随着复杂性的增加,对构建这些模型的从业者和解释其结果的人来说,都需要更多的责任和透明度。
在可解释人工智能的广阔领域中,一种显示出巨大前景(并吸引了大量注意力)的方法是 SHAP(来自“沙普利加法解释”);正如其创造者所说,这是一种“解释任何机器学习模型输出的博弈论方法”最*几周,我们发表了一些关于 SHAP 的优秀作品,对于 6 月的月刊,我们决定分享几篇从多个角度涵盖它的优秀文章,从高度理论化到极端实践化。
祝阅读愉快,感谢您对我们作者工作的支持。
TDS 编辑亮点
- 可解释的人工智能:打开黑盒要清晰透彻地介绍 XAI,尤其是 SHAP 和沙普利的价值观,只需看看 Charu Makhijani 的新帖。(2022 年 5 月 10 分钟)
- 解释预言的艺术
康纳·奥沙利文强调了人性化解释的重要性,并展示了 SHAP 创造这些解释的能力。(2022 年 5 月 11 分钟) - SHAP 对语言模型的分区交代沙普利值、欧文值、分区交代是如何相互联系的?Lilo Wagner 在她的处女作《TDS 邮报》中,她来到了 SHAP 图书馆。(2022 年 5 月 9 分钟)
- SHAP 价值观及其在机器学习中的应用简介要全面、耐心地了解 SHAP 背后的数学,以及它在现实生活中是如何工作的,这里是 Reza Bagheri 的权威指南。(2022 年 3 月,81 分钟)
- SHAP:用 Python 解释任何机器学习模型要想更快地了解 SHAP,你可以随时重温 Khuyen Tran 的热门教程。(2021 年 9 月,9 分钟)
- 解释公*的衡量标准最后,SHAP 的创造者 Scott Lundberg 在 TDS 上写了大量关于这个库的文章。在这份来自我们档案的长期收藏中,斯科特提出了两个至关重要的概念:可解释性和公*性。(2020 年 3 月 11 分钟)
原始特征
从作者问答到播客剧集,我们的团队为您的阅读和聆听乐趣汇集了原创功能,以下是最*的几个亮点:
- " Bryan R. Vallejo 利用地理空间科学和实时数据帮助生态保护 。“不要错过我们与 Bryan R. Vallejo 关于新地理空间技术和研究方法的对话。
- 数据科学趋势 。数据科学下一步将走向何方?该领域的未来是这一期播客的中心,由 Jeremie Harris 和数据女性的创始人 Sadie St. Lawrence 客串。
- 一个数据科学领导者最必备的技能是什么?对于玫瑰日来说,良好的沟通和同理心(以及其他一些经常被低估的软技能)与技术掌握一样重要。****
- “做好你的研究之后,是时候相信你的直觉了 ”我们与博士生 Murtaza Ali 的聊天涵盖了数据科学职业可以选择的不同道路,以及在选择这些道路时应该问自己的问题。
- 合成数据可能改变一切 。最新一集的 TDS 播客由 Alex Watson 主讲,探讨了合成数据的隐私和性能优势。
热门帖子
如果你想深入了解上个月最热门的一些文章和对话,这里有一些 5 月份阅读量最大的帖子。
- 3 个最有价值的数据科学技能让我的薪水增加了 60% 作者特伦斯·申
- rajini++:由 Aadhithya Sankar 开发的超级编程语言
- 没有计算机科学学位的数据科学初学者的最佳编程技巧Hennie de Harder
- PyScript —在您的浏览器中释放 Python 的威力 作者 Eryk Lewinson
- 如何轻松学习新的编程语言 作者 Kay Jan Wong
- 一种超快速的 Python 循环方式 作者弗兰克·安德拉德
在过去的一个月里,我们有幸分享了优秀的 TDS 新作者的作品。请和我们一起欢迎玛戈·哈奇、奥尔朱万·扎法兰尼、奥斯卡·涅梅诺贾、米尔顿·辛巴·坎巴拉米、马克西姆·库帕尼、玛丽-安妮·马文、帕夫勒·马林科维奇、迪夫扬舒·拉杰、德鲁夫·冈瓦尼、 https://medium.com/u/e253e1c83d01?source=post_page-----b9e0c1e905b8-------------------------------- 安娜·伊莎贝尔,阿维·舒拉,埃里克·巴洛迪斯,利洛·瓦格纳,赛帕万·叶库拉,丹尼尔·里德斯通,雅各布·皮耶尼亚泽克,玛丽·特朗,夏洛特·p .,张子涵, 锡南·古尔特金、德弗什·拉贾迪亚克斯、伊森·克劳斯、阿诺·卡皮坦、凯文·贝勒蒙特博士、桑巴尔杰、埃拉·威尔逊、萨迪克·巴丘、 如果你想在未来的月刊中看到你的名字,我们希望收到你的来信。
初级 vs 高级数据科学家:有什么区别?
原文:https://towardsdatascience.com/junior-vs-senior-data-scientist-whats-the-difference-d13afedd8ec
意见
除了显而易见的,具体是什么?

利兹·桑切斯-维加斯在Unsplash【1】上的照片。
目录
- 介绍
- 产品经理协作
- 项目生命周期管理
- 摘要
- 参考
介绍
正如标题所暗示的,初级和高级数据科学家之间有很多明显的差异,但有哪些鲜为人知的差异呢?在本文中,我们将讨论这些差异,以及一些高级数据科学家代替初级数据科学家可能需要执行的一些关键职责或流程。
首先,需要注意的是,并不是每个公司都有初级、普通和高级员工,所以这种比较只在他们有初级、普通和高级员工的情况下有效。然而,经验丰富的人与经验不足的人之间的比较可以遵循相同的方向。话虽如此,下面让我们更深入地探讨这两个角色。
注意:
了解这两个职位的差异不仅有助于自己决定哪一级别的职位适合你,也有助于招聘人员和经理招聘不同资历的员工。有了这些信息,你就可以知道作为一名学生应该学习哪些课程(例如,你会更专注于编程,还是会更多地参与商业和交流?).另一方面,经理和招聘人员可以利用这些信息来设计和撰写职位描述。此外,这些区别也有助于其他利益相关者(例如,软件工程师、数据工程师、业务分析师和产品经理),他们可能与这些不同级别的数据科学资历一起工作,因此可以在工作场所设定适当的期望。
产品经理协作

照片由 ThisisEngineering RAEng 在Unsplash【2】上拍摄。
正如所料,初级数据科学家的思维倾向于跟随围绕机器学习算法的学术界,而高级数据科学家的思维则专注于业务影响和协作。
例如,下面是初级与高级数据科学家的典型日/周示例:
- 初级数据科学家 —
—接受来自数据科学经理的任务,如“清理”或预处理数据,以及比较算法
—使用 SQL 查询数据库表中的数据
—根据现有数据创建新要素
—为模型准确性创建可视化效果
以上只是作为初级或普通级别的数据科学家,您每天可能会遇到的一些步骤。你可能会专注于任务本身,而不是为他人创建和组织任务。
- 高级数据科学家 —
—与产品经理、其他利益相关方合作,制定您自己的任务、路线图和流程—讨论重要的 KPI(关键绩效指标),这些 KPI 在模型准确性和误差本身之外推动模型的开发。例如,不只是优化 MAE,而是优化中间分位数两侧的预测。
在某些公司,这个角色可能更类似于数据科学经理,而在其他公司,它是一个高级数据科学家职位,不管理任何人,而是管理项目本身。这也取决于你周围的团队。
您的合作伙伴和业务:
现在,我们对这些角色的日常任务有了更好的理解/只是一些例子,我们现在可以看到高级数据科学家更多地与产品经理合作。不是每个和你一起工作的人都熟悉梅伊、MAPE、RMSE、调整参数、过拟合等。企业最关心的是业务问题的最佳模型,这是您作为一名高级数据科学家需要阐明的事情,而初级或普通级别的数据科学家往往只关注降低错误本身。然而,最有可能的是,有些公司的这些任务是重叠的。
总结一下,学长更多的是与人合作,而学妹可以更低头。同样,这只是我收集的经验,所以这在不同的公司会有所不同。
项目生命周期管理

照片由 Erlend Ekseth 在Unsplash【3】上拍摄。
与上述观点类似,高级数据科学家可能会从头到尾管理更多的项目,而初级数据科学家可能会专注于整个过程的前半部分或中半部分,例如。
初级数据科学家项目示例:
- 数据集的 SQL 数据库、预处理数据、特征工程、模型比较
高级数据科学家项目示例:
- 收集利益相关者的需求,问题是什么,为什么数据科学是一个可能的解决方案?从 API/SQL 获取数据,预处理数据,特征工程,模型比较,部署模型-可能与机器学习运营工程师或软件工程师合作或自己动手,自动化任务,自动化指标,报告指标,讨论模型如何帮助客户,节省资金,节省时间等。
如你所见,有一个比模型本身更大的生命周期,围绕着它有整个过程。初级数据科学家专注于模型,而高级数据科学家可以专注于模型以及我们为什么首先需要模型。
摘要
虽然我可以列出更明显的区别,比如多年的经验、教育、更多的编程语言、使用更多的*台等等。我认为讨论整体差异也同样重要,比如你和谁一起工作,以及你在企业中的主要职责。
总而言之,数据科学角色的两个方面可能会因您的工作地点而有所不同:
* Product Manager Collaboration* Project Lifecycle Management
我希望你觉得我的文章既有趣又有用。如果您同意或不同意这些差异,请在下面随意评论。为什么或为什么不?关于数据科学资历,您认为还有哪些重要的差异需要指出?这些当然可以进一步澄清,但我希望我能够阐明初级和高级数据科学家角色之间更独特的差异。
我不属于这些公司中的任何一家。
请随时查看我的个人资料、 Matt Przybyla 、和其他文章,并通过以下链接订阅接收我的博客的电子邮件通知,或通过点击屏幕顶部的订阅图标 的 ,如果您有任何问题或意见,请在 LinkedIn 上联系我。
订阅链接:https://datascience2.medium.com/subscribe
引荐链接:【https://datascience2.medium.com/membership】T22
(如果你在 Medium 上注册会员,我会收到一笔佣金)
参考
[1]照片由 Liz Sanchez-Vegas 在Unsplash(2019)上拍摄
[3]照片由 Erlend Ekseth 在Unsplash(2015)上拍摄
Jupyter 现在是一个成熟的 IDE:年度回顾
原文:https://towardsdatascience.com/jupyter-is-now-a-full-fledged-ide-annual-review-751675634493
无所不在、让你保持在状态的工具和教育是 2022 年 Jupyter 项目的主题

一个程序员在木星上写代码——稳定扩散生成的图像
Jupyter 笔记本非常适合软件开发和文档。它们被广泛用于数据科学和机器学习(ML)领域,如果您想尝试新算法、分析和熟悉数据集,以及创建新方法的快速草图,它是一个理想的工具。
大约两年前, JupyterLab 推出了可视化调试器,杰瑞米·霍华德宣布了 nbdev ,这是一个 python 库,可以编写、测试、记录和分发软件包和技术文章,所有这些都在一个地方,你的笔记本上。Jupyter 开始转型,看起来更像一个 IDE,尽管它仍然保持自己的方式,它仍然不像任何其他传统的 IDE。
去年对于 Jupyter 项目来说也是很棒的一年。一个新的主要版本(JupyterLab 3.0),扩展允许你将代码单元转换成 ML 管道,一个新的小部件引擎,以及可视化调试器的集成,这些都是铺*道路的公告。此外,nbdev 继续向前发展,现在受到网飞和 Lyft 等大公司的信任。
今年,这一迹象又是积极的,进展也同样显著。该团队专注于将 JupyterLab 带到任何地方,创建工具让您在编码时保持在该区域,而该项目的一个最被低估的用途是教育。让我们看看今年在朱庇特计划的魔法世界里发生了什么!
Learning Rate 是为那些对 AI 和 MLOps 的世界感到好奇的人准备的时事通讯。你会在每个月的第一个星期六收到我关于最新人工智能新闻和文章的更新和想法。订阅这里!
朱庇特:朱庇特现在无处不在
似乎现在每个人都试图在云虚拟机上运行你最喜欢的 IDE。如果您正在使用 Visual Studio 代码,您可能听说过 GitHub codespaces。然后,Coder 开发了[code-server](https://github.com/coder/code-server),允许您构建自己的 VSCode 服务器,并在任何地方托管它。AWS 还收购了 Cloud9 ,这是一个用于编写、运行和调试代码的云 IDE。
朱庇特采取了激进的方法; JupyterLite 是一个 JupyterLab 发行版,完全在 web 浏览器中运行,由浏览器内语言支持。这意味着它更便宜、更易于部署、高度可定制,并且可以嵌入到您的网站中。事实上, NumPy 已经这样做了。
你可以在这里使用 JupyterLite 的一个公共实例。有一些限制,你可能会发现你最喜欢的扩展没有像预期的那样工作,但我已经迫不及待地想看到它将走向的方向。
JupyterLab 督察
JupyterLab 有很棒的代码完成工具,它提供了代码高亮、代码完成、具有丰富变量呈现的调试器等等。
JupyterLab inspector 是另一个旨在让你保持在最佳状态的工具。它提供了一个 UI 面板,可以在您键入时提供上下文帮助。在 Windows 上按 Ctrl + I 可以调出,在Mac 上按 ⌘ + I 可以调出。

笔记本检查器—按作者分类的图像
使用这个工具,你可以做的一件很酷的事情是,你可以使用 Sphinx 将 docstring 呈现为 HTML,并创建一种感觉,就像你总是在床头柜上放一本手册一样。
教育
使用 Jupyter 笔记本记录您的工作是向公众传达您想要实现的目标的一种很好的方式。Jupyter 提供的这种文本+现成代码特性也使它成为一个很好的教育工具。
杰瑞米·霍华德是这一领域的先驱。他和他的同事只用笔记本就创造了最受欢迎的 ML 课程之一。他和 Sylvain Gugger 用笔记本写了课程附带的书,甚至 nbdev 的整个文档都是用笔记本写的。这意味着您可以从 JupyterLab 的文档中打开一页,并使用这些示例。
另一个很好的资源是由全栈深度学习团队开发的实验室。FSDL 团队提供了各种概念的深刻解释、工具的嵌入式视图,如权重&偏差、张量板和梯度,甚至是笔记本末尾的练习,为那些想使用工具帮助将模型投入生产的人提供了一个惊人的资源。
自从我们讨论了笔记本上的练习后,几周前,发布了一个新版本。Nbgrader 是一个工具,它允许教育者在 Jupyter 笔记本上创建并自动评分作业。如果你是一名教育工作者,还没有看到这个,看看下面的视频。
最后但同样重要的是,一个名为 Blockly 的令人敬畏的扩展旨在通过基于块的可视化编程接口,让孩子们更容易、更容易地编写代码。Blockly 是 Google 设计的一个开源库,它使用互锁的图形块,很像乐高积木,来表示 if 语句、循环或函数等编码概念。
结论
Jupyter 笔记本非常适合软件开发和文档。大约两年前,JupyterLab 开始转型,看起来更像一个 IDE,尽管它仍然保持自己的方式。
今年,该项目继续向前推进。该团队专注于将 JupyterLab 带到任何地方,创建工具让您在编码时保持在该区域,而该项目的一个最被低估的用途是教育。
JupyterLab 和 VSCode 是我的 ML 首选工具。我迫不及待地想看看 2023 年会发生什么。
关于作者
我叫迪米特里斯·波罗普洛斯,我是一名为阿里克托工作的机器学习工程师。我曾为欧洲委员会、欧盟统计局、国际货币基金组织、欧洲央行、经合组织和宜家等主要客户设计和实施过人工智能和软件解决方案。
如果你有兴趣阅读更多关于机器学习、深度学习、数据科学和数据运算的帖子,请关注我的 Medium 、 LinkedIn 或 Twitter 上的 @james2pl 。
所表达的观点仅代表我个人,并不代表我的雇主的观点或意见。
Jupyter 笔记本的功能:使用 Papermill 创建可重复使用的笔记本
如何使用 Papermill 创建参数化 Jupyter 笔记本

由 Christa Dodoo 在 Unsplash 上拍摄的照片
介绍
在编程中,函数是将代码模块化为独立的、有组织的、可重用的块的方法,用于执行特定的任务。函数通常接受输入数据,处理数据,并输出结果。将代码编写为函数的一些优点是:
- 提高代码可重用性
- 把问题分解成更小的可解决的部分
- 保持更清晰的变量名称空间
- 能够孤立地测试代码的一小部分
在本文中,我们将研究如何参数化我们的 Jupyter 笔记本,使它像一个函数一样工作。
等等,但是为什么?
动机
假设我们从事房地产业务,用户在过去几年中有一些与房地产销售相关的问题。我们将使用新加坡住房转售价格数据集[1],该数据集包含 HDB 住房发展局的转售房屋交易记录。这是数据集的一个样本。

作者图片
商业用户有兴趣回答 2017 年 1 月month至 2021 年 12 月town中心区的以下问题:
month全国销售数量resale_price横跨month- 不同
flat_type的resale_price分布
我们导入必要的包,读取数据,并将数据过滤到由town和date指定的正确子集。有了正确的数据子集,我们现在可以绘制相关的图表来回答这些问题。
作者笔记本
全部完成!业务用户要求的town“中心区”报告准备就绪。
如果业务用户为不同的 *town* 请求相同的信息怎么办?
很简单,让我们找到并替换它!我们只需找到town的所有旧值并用新值替换它们,运行笔记本,并保存笔记本的副本。
如果业务用户对每个可用的 *town* 请求相同的信息,该怎么办?
共有 29 个镇;用 29 次手动“查找和替换”的方法可能不是个好主意!这就是造纸厂派上用场的地方。
造纸厂
Papermill 是一个开源的 python 包,参数化并执行 Jupyter 笔记本。
参数化是通过从代码中获取值并将其作为该函数的参数来概括代码块以使其可重用的过程。我们将参数化整个 Jupyter 笔记本,而不是参数化一小块代码。这使得我们可以使用不同的参数轻松运行同一台笔记本电脑。让我们来看一个例子。
1.设置环境
pip install papermill
这是项目文件结构:

作者图片
data:包含输入数据product:存储已执行的 Jupyter 笔记本的副本tasks:包含 Jupyter 笔记本template.ipynb和driver.ipynbtemplate.ipynb:这是数据处理和可视化代码的位置。它类似于我们之前看到的代码driver.ipynb:这是包含实际参数集的笔记本,Papermill 将从该笔记本中将参数单元注入template.ipynb,运行并保存一份副本。
2.将笔记本参数化
让我们检查一下代码中哪些值可以参数化。我们将参数化笔记本命名为template.ipynb。一般来说,如果我们预计到未来的变化,我们可以参数化任何值。一种简单的开始方式是检查哪些列是被过滤的数据帧,哪些值是硬编码的。
df = pd.read_csv(**'data/resale-flat-prices.csv'**)
df['date'] = pd.to_datetime(df['month'], format = '%Y-%m')
df = df.loc[(**df['town'] == 'CENTRAL AREA'**) & (**df['date'].between('2017-01-01', '2021-12-01')**)]
数据帧正被town和date过滤,因此我们可以用变量代替town和date值,而不是硬编码。如果我们预计数据的文件路径会发生变化,我们也可以选择将其参数化。
这是另一部分可以参数化的代码。我们已经将town值硬编码到图表的标题中,我们可以用一个变量来替换它。
sales_over_time = df.groupby('date', as_index = False).agg({'resale_price':'count'}).rename(columns = {'resale_price':'resale_count'})fig, ax = plt.subplots(figsize = (10, 5)) ax = sns.lineplot(data = sales_over_time, x = 'date', y = 'resale_count') ax.set_xlabel('Month')
ax.set_ylabel('Sales Transaction')
ax.set_title('Number of Sales Over Time - **Central Area**')
plt.show()

作者图片
这是参数化前后代码的样子。用变量替换town和date的硬编码值。
**# before**
df = pd.read_csv('**data/resale-flat-prices.csv**')
df['date'] = pd.to_datetime(df['month'], format = '%Y-%m')
df = df.loc[(df['town'] == '**CENTRAL AREA**') & (df['date'].between('**2017-01-01**', '**2021-12-01**'))]**# after**
df = pd.read_csv(**input_path**)
df['date'] = pd.to_datetime(df['month'], format = '%Y-%m')
df = df.loc[(df['town'] == **town**) & (df['date'].between(**start_date**, **end_date**))]
图标题也使用 f 字符串参数化。
**# before**
..
ax.set_title('Number of Sales Over Time - **Central Area**')**# after**
..
ax.set_title(f'Number of Sales Over Time - {**town.title()**}')
为笔记本创建了以下参数:input_path、town、start_date、end_date,这就是现在template.ipynb的样子。
作者代码
没有给任何变量input_path、town、start_date、end_date赋值。如果此笔记本电脑是一次性或一次性使用,则应包括以下单元格。
# notebook cell
# Parameters
input_path = "../data/resale-flat-prices.csv"
start_date = "2017-01-01"
end_date = "2021-01-01"
town = "CENTRAL AREA"
在每次运行之前,我们会手动更改参数值,以适应业务用户的请求。
然而,这种方法不适用于具有大量参数集的情况。在我们的例子中,有 29 个城镇,手动改变town值 29 次是不可行的。我们需要一种方法来以自动化的方式将不同的参数集传递给template.ipynb,这由driver.ipynb负责。
3.创建driver.ipynb
这就是奇迹发生的地方。为了便于说明,我们只为 4 个不同的城镇生成报告,为每个城镇创建一个参数字典,并将其传递给 Papermill 的.execute_notebook()方法。
# driver.ipynb
import papermill as pm# generate report for each towntowns = ['CENTRAL AREA', 'MARINE PARADE', 'WOODLANDS', 'QUEENSTOWN']for town in towns: params = {'input_path': '../data/resale-flat-prices.csv',
'start_date': '2017-01-01',
'end_date': '2021-01-01',
'town': town} nb_output = pm.execute_notebook('template.ipynb', f'../products/report_{town.title()}.ipynb', parameters = params)
.execute_notebook()的参数为:
input_path:(字符串或路径)我们打算执行的输入 Jupyter 记事本的路径,在我们的例子中是template.ipynb。output_path: (str 或 path)保存已执行 Jupyter 笔记本的路径。我们保存已执行的笔记本,并连接城镇名称。parameters:input_path中传递给 Jupyter 笔记本的(dict)参数
.execute_notebook()执行以下操作:
- 查找输入笔记本,即
template.ipynb
2.在运行时将参数注入template.ipynb

作者图片
3.使用注入的参数运行template.ipynb
4.在output_path中保存已执行笔记本的副本以及单元输出
4.运行driver.ipynb
执行时,driver.ipynb笔记本将不同组的参数传递给template.ipynb,执行template.ipynb,并在.execute_notebook()中定义的output_path保存不同组参数的已执行笔记本的副本。

作者图片
下图显示了保存的输出笔记本。

作者图片
让我们来看看其中一个保存的笔记本。在report_Central Area.ipynb中,我们看到 Papermill 向第一个单元格注入了一组参数。
作者笔记本
结论
在本文中,我们给出了参数化 Jupyter 笔记本的动机,并展示了如何通过 Python API 执行 Papermill 的数据分析用例。Papermill 的使用案例不限于报告生成。其他开源包如 Ploomber 使用 Papermill 将参数注入到单元格中,以创建可重用的机器学习管道。Papermill 还提供通过命令行界面执行,并允许使用.yaml文件存储参数。查看 Papermill github 了解更多信息。
参考
[1] 包含 2022 年 3 月 17 日从【https://data.gov.sg/dataset/resale-flat-prices获得的转售单位价格信息,该信息根据新加坡开放数据许可证版本 1.0【https://data.gov.sg/open-data-licence】T21的条款提供。允许公共使用(商业&非商业)。
[2] 造纸厂
Jupyter 笔记本:避免犯这 4 个错误
原文:https://towardsdatascience.com/jupyter-notebooks-avoid-making-these-4-mistakes-e5cbad1d473d
借助 Jupyter 笔记本电脑实现生产就绪代码

美国宇航局在 Unsplash 拍摄的照片
Jupyter 笔记本是我最喜欢的编写代码的方式——尤其是在第一次探索一个新的数据集时。
它们允许您查看每一步的代码和结果,将变量和数据集保存在内存中,并促进快速有效的实验。
Jupyter 笔记本允许我们将代码、叙述性文本、等式和可视化全部组合在一个文档中。
但是,伴随着这种巨大的能力而来的是巨大的责任。在这里,我们讨论数据科学家犯的四个常见错误,这些错误否定了 Jupyter 笔记本的伟大之处。
运行单元出现故障
在分析过程中,我们会多次无序地运行细胞。
当我遇到一个错误,或者代码没有按照我想要的那样运行时,我会一遍又一遍地运行那个单元格,直到我得到想要的结果。
做实验的时候,这绝对没有错!
当我们试图让代码为生产做好准备时,无论是机器学习模型,还是导出到 Excel 的简单报告,都会出现问题。
编程的一个关键是结果的可再现性。
任何人用相同的输入运行我们的笔记本应该得到相同的输出——而不必记住在第三个和第六个单元之前运行第五个单元两次。
你……应该以这样一种方式写笔记本,任何人都可以用同样的输入重新运行它,并产生同样的输出。你的笔记本应该是从上到下可执行的…

示例:单元格不按顺序运行时出错—图片由作者提供
确保我们的笔记本从上到下运行无误,让笔记本看起来更专业。
也许,更重要的是,确保笔记本从上到下无错运行让我们离生产就绪代码更*了一步。
在一个牢房里放太多东西
Jupyter 笔记本与典型的文本编辑器(如 VSCode 或 Sublime Text)的真正区别在于,它让您能够单步调试代码,并可视化每一步的输出。
它将变量保存在内存中,允许我们不断编写和更正代码,而不必等待潜在的耗时操作,如在每次微小的更改后读取大型数据集或模型训练。
当我们开始用太多的代码使一个单元过于拥挤时,我们就失去了一些使 Juypter 如此伟大的交互性。

例子:在一个单元格中塞进太多东西——作者图片
当你把所有的东西都塞进一个单元时,这个单元中的所有代码都会被立即执行,你就失去了看到每一步发生了什么的能力。这也使得真正看到每一步在做什么变得更加困难。
因为只有单元格的最后一行被自动打印到屏幕上,所以如果我们不小心将 print 语句添加到我们想要查看输出的行中,我们可能会丢失一些输出,如上图所示。
将所有的代码都塞进一个单元格中会减少交互结果,并且会隐藏关键细节。
我并不是说要将每个单元限制为只有一行代码,而是以一种有意义的方式对代码进行分组。

示例:以流畅且有意义的方式对代码进行分组——图片由作者提供
这样更好看!每个单元格中的代码完成一项任务,并打印出关键信息,让我们知道代码按预期工作,并让我们看到数据的样子。
没有利用降价单元格
降价太棒了!它允许我们在笔记本上添加带有格式的叙述性文本,并可以使我们的工作更加完美,为演示做好准备。
降价指南提供了一个很棒的备忘单来帮助你开始。要使用键盘快捷键创建 Markdown 单元格,请按 esc 键,然后按 m 键。
我喜欢使用降价单元格来创建我的分析计划。例如,当开始一个带回家的挑战时,就像我们在这里讨论的例子,我们可以使用 Markdown 作为一个清单来给出我们的分析方向,并让我们专注于手头的任务。

使用 Markdown 的 EDA 计划—图片由作者提供
现在我们有了一个计划,我们可以开始编写满足每个目标的代码。尤其是在处理一个新的和不熟悉的数据集时,创建一个减价待办事项列表可以帮助指导我们的分析,并让我们保持专注。

准备演示时,减价也非常有用。我们可以添加我们的想法和见解,为向利益相关者展示我们的分析做准备。
在展示我们的笔记本电脑时,无论是在代码审查中还是对业务用户,具有关键洞察力的 Markdown 单元格都非常有帮助。

示例:使用 Markdown 对可视化效果进行评论—按作者排序的图像
在整个笔记本中导入包
当使用笔记本工作时,很多时候我发现自己需要导入一个我没有马上想到的包。
虽然在目前,将导入语句添加到当前单元格会更容易和更快,但我们不应该这样做!
一个好的做法是在笔记本的第一个代码单元中添加所有的导入语句。
这不仅使我们的笔记本看起来更精致、更专业,而且还可以帮助我们在想要将笔记本转换为生产就绪脚本时省去麻烦。

示例:在整个笔记本中导入包—按作者排序的图像
虽然我们的代码没有错误运行——笔记本可能从上到下运行,上面的例子看起来很混乱,有点令人困惑。
在笔记本上工作时,意识到我们在分析中途需要另一个包是完全正常的,我们应该尝试将所有的导入语句放在第一个单元格中。相信我——你未来的自己会感谢你的!

示例:第一个单元格中的所有导入语句-按作者排序的图像
结论
在这里,我们讨论了使用 Jupyter 笔记本进行开发时要避免的一些常见做法。纠正这些不良做法将有助于我们的笔记本看起来更完美,并向生产更*一步。
我有没有漏掉什么不好的笔记本做法?我很想在评论中听到你对朱庇特的抱怨!
朱庇特笔记本可能是一种痛苦,但我❤️布鲁托
原文:https://towardsdatascience.com/jupyter-notebooks-can-be-a-pain-but-i-️-pluto-f47913c5c16d
朱莉娅的 Pluto.jl 将使教育变得更好

不是这个冥王星… |照片由 NASA 在 Unsplash 上拍摄
作为一名数据科学家,我阅读并有时——几乎总是违背我的意愿——写笔记本。Jupyter 笔记本允许您将所有的分析和评论储存在一个地方,这样您的结果就可以很容易地与他人分享。至少这是承诺…
要获得所有媒体文章的完整信息——包括我的——可以考虑在这里订阅。
我个人发现笔记本容易出现错误,滚动对于长笔记本来说是一种痛苦,而 IntelliJ 只有在感觉需要的时候才能工作。VSCode 试图通过与笔记本更好地集成来改变这一点,但写笔记本仍然感觉乏味,我更喜欢先写脚本,然后在演示文稿或类似的东西中添加评论。
但是我同意,把你所有的分析和评论放在一个地方是一个非常好的主意。想象一下这样一个世界,共享研究不是通过共享一长段无聊的文本来完成的,而是通过共享一个写得很好的笔记本,上面有真实的代码,可以访问所有的数据,安装正确的包,并允许你重新运行结果,调查它们,甚至做出改变来探索“假设”情景。现在,帮我报名吧!
要获得完全的可复制性,您需要:环境(包、操作系统等)、代码和数据
问题是访问数据和计算真的很难。你读过多少包含数据链接的论文?或者,当数据可用时,您可能会发现类似这样的内容:“我们在自定义操作系统上的数据中心的 5000 个 GPU 上运行了这个”。祝你好运……所以我认为,就目前而言,笔记本电脑最适合做简短的分析,或者——请击鼓— 教育。

你需要做才能学会|照片由伊妮斯·亚乌兹在 Unsplash 上拍摄
熟能生巧,这也是必不可少的!
人们——是的,我们都是学生——如果能够探索和修补事物,就会学得更好。有多少工程师仅仅通过阅读课本就学会了组装一辆汽车?熟能生巧,这也是必不可少的!Jupyter 笔记本这个还可以,但是 Pluto.jl 更好!听我说完。
如果你认为这是一种时尚,请三思!在麻省理工学院,冥王星被用来教授这门令人敬畏的课程。
冥王星是什么?
布鲁托本质上是一个 Jupyter 笔记本,完全是为 Julia 设计的。利用 Julia 的速度和灵活性,它可以解析你的代码,并理解哪些代码单元依赖于哪些其他单元。然后,当您更改一个块中的代码时,会自动重新运行依赖于该块的所有单元格,使用新的输入立即显示结果和数字。它确实将反应式计算提升到了一个新的水*。
基础知识:
- 它在您的浏览器中运行
- 将文件——包括元数据——保存在一个
.jl文件中,这样您的源代码控制和所有东西仍然可以工作。无需在笔记本文件和脚本之间转换。 - 使用反应性来计算哪些细胞需要自动重新计算。
- 有自动完成和所有的基本功能。
- 可以共享为
.jl、html或pdf文件。
开始按钮在哪里?
Gif 来自 GIPHY
安装冥王星实在是再简单不过了。你需要一个朱莉娅·REPL,键入]来访问包管理器,然后运行add Pluto。
要启动您的第一个笔记本,您需要执行以下操作:
import Pluto
Pluto.run()
这将为您提供一个带有 Pluto 会话的浏览器窗口:

这是你启动 Pluto 时在浏览器中看到的——作者截图
点击new notebook后,你会看到一个类似 Jupyter 笔记本的熟悉界面:

你的第一个笔记本和它的第一个代码单元——作者截图
准备好迎接魔法了吗?来做套餐吧!

冥王星与包裹玩得真的很好——照片由 Markus Spiske 在 Unsplash 上拍摄
你还记得我说过为了复制你的代码,你需要包含你所有的包吗?在 Jupyter 中,你可以通过包含一个虚拟环境来做到这一点,而在 Python 中,诗歌已经走过了一段很长的路来解决确切的包版本——Julia 的内置包管理器仍然更好使用——但是如果你只想共享一个笔记本,如果所有的包信息都被包含进来,并且所有的东西都被神奇地安装好了不是更好吗?
Pluto 知道你是否正在安装软件包,并将软件包元数据直接存储在你的.jl文件中,这样每个拿走你笔记本的人都会有和你完全一样的软件包。下面是一个 Pluto 文件的摘录,其中包含包清单:
这在你档案的最后

要添加软件包,只需在其中一个单元格中键入using或import,直到出现一个小☁️。悬停在它上面,它会告诉你,如果你运行这个单元,这个包将被安装。
您可以禁止为每个笔记本创建虚拟包环境,并使用当前目录或全局环境。
反应
现在我们有了情节,我们可以制作…情节!

作者截图
如果你仔细看了截图,你可能会注意到一些有趣的事情。主要是,每个单元只做一件事。这是故意的。这是为了建立细胞的依赖性,并使反应成为可能。如果你真的需要的话,begin .. end包装器仍然允许你在一个步骤中运行多个东西。我真正欣赏的是,如果您试图在一个单元格中运行多个赋值,Pluto 会自动为您提供一个解决方案并为您重构代码。多好啊!
其次,输出位于代码单元的顶部。对我来说,这更有意义。作为一名读者,我想先看看结果,然后深入研究代码,以确保它做了它应该做的事情。
为了证明这都是反应性的,看看当我改变定义了random_seed的单元格时会发生什么:

反应冥王星-作者截屏
如果您有长时间运行的计算,并且不想每次都重新运行,您可以通过单击它们的选项菜单(…)来禁用单元格。
滑块,滑块无处不在
以上很好,我认为这让编码和学习变得更有趣。但是当你添加 HTML 滑块时,事情就变得有趣多了。
使用InteractiveUtils和Markdown包的组合,您可以在笔记本上添加滑块,并将结果绑定到一个变量:
如何在笔记本上添加 HTML 滑块
在宏调用之后,@bind宏会将 HTML 代码的结果——滑块值绑定到变量,并返回一个 HTML 元素,然后md可以呈现该元素。我忘记了我在小学时学过的所有关于 HTML 的东西,但是上面的代码非常简单,甚至我都可以做到😅。
以下是滑块:

冥王星中的滑块——作者截图
现在我们有了滑块,让我们乘风破浪:

作者的屏幕截图
如果这种交互式绘图不能推动这一点,那么我不知道什么会。我认为冥王星是如此棒的教育工具,我真的希望有一天我的孩子也能像这样学习数学。
这里用到的所有代码都可以在 GitHub 上https://github.com/niczky12/medium下找到
试一试!

试试冥王星吧,你不会失望的——照片由布雷特·乔丹在 Unsplash 上拍摄
以上都是相当简单的儿童游戏,但我试图证明这在教育环境中是多么有用。
你可以从这里拿走笔记本,自己试试滑块。
想象一下通过这种方式学习概率密度函数,你可以摆弄参数并立即看到结果…
如果你想更进一步,你甚至可以包括一些机器学习或统计分析,让你的读者摆弄超参数。应用程序是无止境的,我真的希望这将在更广泛的数据科学社区中流行起来。冥王星已经在朱莉娅社区掀起了波澜,但这需要让更多的人看到!
非常感谢冥王星的创造者!你们太棒了。
https://github.com/fonsp/Pluto.jl
来自 JuliaCon 的创作者的精彩演讲——由 Julia 编程语言频道上传
我写朱莉娅和其他很酷的东西。如果你喜欢这样的文章,请考虑关注我。
在 JupyterLab 中编写可组合的 Spark SQL 分析
原文:https://towardsdatascience.com/jupyterlab-sql-cell-editor-e6ac865b42df
使用 JupyterLab SQL cell magic editor 为您的 Spark SQL 笔记本增添一点魔力

Julia Kadel 在 Unsplash 上拍摄的照片
jupyterlab-sql-editor 是一个 ipython 魔术,它使您能够用 Spark SQL 编写可组合的分析。这个 jupyterlab 扩展有一组丰富的特性:
- 自动完成
-表名、列名、嵌套子字段
-表连接
-函数 - SQL 语法突出显示和格式化
- 多显示输出
- html 和纯文本
-交互式数据网格和 json 树
-数据帧和视图的捕获
- html 和纯文本
编辑 SQL 语句
jupyterlab-sql-editor 用 sql 格式和语法高亮扩展了 jupyterlab。

jupyterlab-sql-editor 还注册了一个提供自动补全功能的 LSP 服务器。按**%%sparksql --refresh all**命令更新自动完成器使用的本地缓存文件。refresh 命令枚举在当前 spark 上下文中找到的函数、表和列。

自动完成 Spark SQL 函数的文档和用法示例。

还有一些高级用户特性,比如自动填充 SELECT 语句中的所有列名,以及自动建议匹配列名的连接条件。

显示结果
sparksql magic 是一种执行 sparksql 语句的便捷方式。在本例中,我们执行一条语句,并将结果作为文本打印出来。
**%%sparksql** --output textSELECT * from range(1, 1000)
我们可以用 python 做同样的事情。
statement = 'select * from range(1, 1000)'
df = spark.sql(statement)
df.show()
如你所见,这不是真正的魔法。sparksql 魔术将单元格的主体作为语句,执行它并打印结果。该扩展简单地封装了样板 python 代码,并通过命令行选项使其随时可用。
Jupyterlab-sql-editor 支持一组丰富的输出格式,如交互式数据网格。我们使用彭博的 ipydatagrid,它可以对 Spark 查询返回的结果进行后处理(排序、过滤和搜索)。

树小部件显示结果的模式。这对于包含复杂嵌套数据结构的列特别有用。

使用 JSON 输出导航复杂嵌套列的内容。这个输出使用标准的 ipython 树小部件,它可以在任何嵌套字段中进行搜索。

到目前为止,我们已经看到了如何查询数据,但是您并不局限于查询。您可以执行 Spark SQL 支持的任何 SQL 语句。
**%%sparksql**
CREATE TABLE prod.db.sample (
id bigint COMMENT 'unique id',
data string)
USING iceberg**%%sparksql**
ALTER TABLE prod.db.sample
ADD COLUMNS (
new_column string comment 'new_column docs'
)
对简单的单行语句使用行魔法**%sparksql**
**%sparksql** SET spark.sql.variable.substitute=false
**%sparksql** DROP TABLE prod.db.sample
**%sparksql** CALL prod.system.set_current_snapshot('db.sample', 1)
使用 SQL 编写可组合分析
通用表表达式(CTE)是将大型 SQL 语句分成更易于管理的部分的标准方法。Jupyterlab-sql-editor 不仅支持 CTE,还允许您将 sql 语句捕获或别名为视图,然后可以在以后的单元格中重用。这种机制利用了 Spark dataframe 的**createOrReplaceTempView**功能。使用**--view**选项创建一个临时视图。要防止立即执行查询,请使用**--output skip**选项。使用此选项时,仅创建视图。

自动完成也适用于临时视图。使用**%%sparksql --refresh local**选项用您创建的任何本地视图更新您的自动完成缓存文件。
数据框架和 SQL 之间的互操作
到目前为止,我们已经看到了如何使用 SQL 视图来创建可组合的 SQL 分析。然而,您不仅限于 SQL,您可以在 SQL 和 dataframe 之间来回切换。您的部分分析可能更适合 dataframe API,因为其他部分可能更适合 SQL 方言。Jupyterlab-sql-editor 使方言之间的切换变得非常容易。使用**--dataframe**选项将 SQL 语句转换为数据帧。

给定任何数据帧,通过调用**df.createOrReplaceTempView()**切换回 SQL。例如,您可能使用 Spark ML、Spark GraphFrames,或者简单地使用像 CSV 这样的数据源。Spark 中的许多库都创建数据帧。
df = spark.read.csv(path)
df.createOrReplaceTempView(‘my_csv_table’)
然后参考您在**%%sparksql**中创建的视图
**%%sparksql**
SELECT * FROM my_csv_table
记得运行**%%sparksql --refresh local**来更新您的本地缓存,享受自动完成功能!
装置
jupyterlab-sql-editor 有两个主要的依赖项。 jupyterlab-lsp 和 sql-language-server 。安装扩展非常容易。
pip install jupyterlab-lsp jupyterlab-sql-editor
安装 sql-language-server 项目以提供自动完成功能。
sudo npm install -g sql-language-server
有关配置选项的完整列表,请参见详细安装指南。
结论
在本文中,我们展示了如何利用**%%sparksql**在 Spark SQL 中轻松编写可组合的分析。我们关注的是 Spark,但是 jupyterlab-sql-editor 也包含了一个**%%trino**魔法!将来可能会添加更多的 SQL 引擎。欢迎投稿!这是我们的 git repocyber centre Canada/jupyterlab-SQL-editor。
在的后续文章中,我们将介绍 jupyterlab-sql-editor 对使用 Jinja 和 DBT 的 sql 模板的支持。
多谢
致这些项目的贡献者:
krassowski/jupyterlab-LSP
Joe-re/SQL-language-server
zero turnaround/SQL-formatter
cryeo/spark SQL-magic
Bloomberg/ipydatagrid
量子软件工程师生活中普通的一天
致力于 IBM 的量子开放科学奖
量子机器学习要不要入门?看看 动手量子机器学习用 Python 。
你看过这些关于工作和职业的真人秀吗?无论是警察、消防员、厨师,甚至是渔民,他们每天都经历着相当令人兴奋的事情。
不出所料,每个孩子都想在未来成为他们中的一员。没有孩子会梦想整天坐在电脑前喝咖啡。好吧,没有孩子,除了我两岁的儿子。每当他看到我坐在办公桌前,他都坚持要加入我,敲敲键盘,玩玩鼠标。
整天坐在电脑前的人不太适合演电视剧。但也许是为了一篇博文?
几周前,我写了一篇关于如何发现自己是否想从事量子计算职业的文章。我声称解决一个真正的问题是最好的方法。于是,我开始解剖 IBM 的量子开放科学奖,分享我的见解。首先,我们了解了挑战实际上是关于的。它是关于应对一个天生不可靠的真实量子计算机。然后,我们研究了减少计算误差影响的方法。我们专注于 Clifford 数据回归(CDR) 方法,该方法使用机器学习模型来预测和减轻噪声。最后,我们使用开源库 Mitiq 来减少模拟环境和真实量子计算机中的错误。
当然,我没有发明任何这些概念或工具。因此,我花了大量的时间阅读资料。令人惊讶的是,这些资料中的大部分并没有包含太多的数学或物理知识。但是源代码主要包括开发人员文档,事实上,还有 Python 源代码。我自己完成了 Mitiq 中的 CDR 实现和 Qiskit 中的状态层析实现。
通常,我会从主题的概念概述开始。如果你看了我关于 CDR 的第一篇帖子,你会注意到它非常高级。它不包含一个数学公式。这是我感到舒服的理解水*。我不需要精确的数学来理解一个概念是什么。相比之下!当我第一次试图理解一个想法的本质时,我发现数学会让人分心。
因此,有了这个基本的理解,我继续重新创建一些现有的代码。在我的帖子如何用 Qiskit 和 Mitiq 实现量子错误缓解中,我使用了 Mitiq 提供的例子。然而,我没有复制和粘贴源代码。但是,我用 Qiskit 代替了 Cirq。
也许只有我。但是试图让一些现有的代码产生特定的行为是任何软件工程师的日常生活。这里没有任何量子物质。如果你试图剖析 CDR 或 React(一个 Javascript 库),这并不重要。当然,比我更精通数学和物理的人可能会选择另一种方法来解决 IBM 的挑战。但是,我是【量子】软件工程师。它决定了我如何尝试解决问题。

作者图片
我尝试解决问题的方式不可避免地决定了我需要应对的斗争。这就像一个自我实现的预言。我决定遵循软件工程的方法。所以,我必须处理软件工程问题。
前一篇文章的结果显示误差减少了 60%以上。尽管这不如我们在模拟环境中实现的模拟测量的减轻效果好,但相对于未减轻的结果,这是一个相当不错的改进。但是我们从本质上减轻了什么呢?
CDR 方法减少了可观测值的测量值与其实际值(期望值)之间的偏差。在这篇文章中,我们更详细地观察了可观察到的现象。可观测值的期望值是一个单一的数字。它是实验测量的概率期望值。它类似于经典的期望值。例如,考虑投掷一枚公*的硬币,正面和反面着地的概率相等。如果将值 1 赋给正面,0 赋给反面,则期望值为 0.51+0.50=0.5。
因为期望值描述了测量值,它不描述内部量子态。而在量子计算中,内部量子态包含的信息比我们通过测量看到的要多得多。
这就是量子态断层成像发挥作用的地方。不可能在一个实验中确定一个量子态。让我们再想想硬币。假设你需要判断硬币在空中时是正面还是反面着地。你可能想看看它的转速。如果你知道它在下落一定距离时旋转了多少次,你就可以计算出剩下的距离。如果你愿意,量子实验可以告诉我们旋转速度。但它只告诉我们绕特定轴的速度。
但是,如果硬币也沿着它的边缘旋转呢?这是另一个轴。要知道边缘周围的旋转速度,我们需要从另一个角度来看硬币。我们需要从另一个角度来看。这就是我们在量子态层析成像中所做的。要重建单个量子位的状态,我们需要从三个不同的角度来看。这些是 X、Y 和 Z 轴。如果我们有两个量子位,就有九个角度:XX,XY,XZ,YX,YY,YZ,ZX,ZY 和 ZZ。我们必须从单量子位角度的每一个组合来看一个双量子位系统。原因就是纠结。纠缠量子位构建了一个量子系统,其分离的组成部分无法完全描述。但是我们需要全面地看待它。
如果我们的量子系统由三个量子位组成,就有 27 种不同的组合。有了 n 个量子比特,就有了 3^n 不同的视角。量子态层析成像只对小量子系统有效,比如 IBM 让我们模拟的三粒子海森堡模型哈密顿量。
幸运的是,Qiskit 提供了一个执行量子状态层析的函数。它包括三个步骤。
首先,我们从我们的初级电路(qc)导出一组 27 个量子电路(st_qcs)。这些电路中的每一个都以不同的方式“观察”初级电路。具体来说,这些电路中的每一个都将另一个可观察对象连接到主电路。
其次,我们用 Qiskit 创建一个StateTomographyFitter对象。我们提供了运行 27 个电路的测量结果和一种优化方法(这里是最小二乘法)。最后,我们使用 Qiskit 的state_fidelity函数来计算测量的量子态与期望态有多接*(这里是|110 >)
那么,我们如何使用 CDR 来减少误差并提高通过量子态层析成像评估的态保真度呢?
从概念上讲,这非常简单。我们用 27 个 CDR 实例替换 27 个状态层析成像电路。每一种都有不同的可观察性。然后,一旦我们运行电路,我们就可以使用 CDR 来降低测量误差。最后,我们重写了StateTomographyFitter类,以减轻期望值作为输入。
这听起来并不复杂。然而,重写这个类将是一项复杂的任务。
我正在谈论软件工程问题。
重写StateTomographyFitter类将花费大量的时间。不幸的是,时间是非常稀缺的资源。此外,我不相信 IBM 会接受基于重写他们的评估工具的提交。他们可能很难确保我重写的StateTomographyFitter不包含任何作弊。
因此,我将改进我的方法,使用默认的StateTomographyFitter。
量子机器学习要不要入门?看看 动手用 Python 学习量子机器 。

在这里免费获得前三章。
k 倍交叉验证:你做得对吗?
原文:https://towardsdatascience.com/k-fold-cross-validation-are-you-doing-it-right-e98cdf3e6690
讨论在数据集上执行 k-fold 交叉验证的适当(和不适当)方法

Markus Spiske 拍摄的照片:https://www . pexels . com/photo/one-black-chess-pieces-separated-from-red-pawn-chess-pieces-1679618/
k-fold 交叉验证是机器学习应用中一种流行的统计方法。它减轻了过度拟合,并使模型能够更好地利用训练数据进行概括。
然而,在实践中,与典型的训练-测试分割相比,该技术可能更难执行。如果使用不当,k 重交叉验证会导致数据泄漏。
在这里,我们回顾了 Python 中 k-fold 交叉验证的不当实现可能导致数据泄漏的方式,以及用户可以做些什么来避免这种结果。
k 倍交叉验证审查
k 倍交叉验证是一种需要将训练数据分成 k 个子集的技术。模型被训练和评估 k 次,每个子集被用作评估模型的验证集一次。
例如,如果训练数据集被分成 3 部分:
- 模型 1 将用折叠 1 和 2 训练,并用折叠 3 评估
- 模型 2 将用折叠 1 和 3 训练,并用折叠 2 评估
- 模型 3 将用折叠 2 和 3 训练,并用折叠 1 评估
为了使这种采样策略成功工作,模型应该只使用它们应该能够访问的数据进行训练。
换句话说,用作验证集的折叠不应影响用作训练集的折叠。不遵守这一原则的数据集将容易出现数据泄漏。
数据泄漏是当用训练数据之外的信息(即,验证和测试数据)训练模型时发生的现象。应该避免数据泄漏,因为它会产生误导性的评估指标,从而导致无法在生产中使用的模型。
对于那些不熟悉这个概念的人,可以看看下面的文章:
不幸的是,在执行 k 重交叉验证时,很容易导致数据泄漏,下面将对此进行解释。
K-fold 交叉验证(错误的方式)
k 倍交叉验证仅在模型仅使用它们应该能够访问的数据进行训练时有效。如果数据在采样前处理不当,则可能违反该规则。
为了证明这一点,我们可以使用一个玩具数据集。
假设我们首先将训练数据标准化,然后将其分成 3 份。很简单,对吧?
然而,仅仅这几行代码,我们就犯了一个明显的错误。
像标准化这样的转换在确定如何改变每个值时使用整个数据分布。在训练数据被分成 k 个折叠之前执行这样的技术将意味着训练集将受到验证集的影响,从而导致数据泄露。
更糟糕的是,代码仍然会成功运行,不会引发任何错误,因此如果用户不注意,他们将会忘记这个问题。
当执行包含交叉验证分割策略的超参数调整方法时,例如网格搜索或随机搜索,也会犯类似的错误。
再一次,这里的数据在被分成 k 倍用于超参数调整之前被标准化,因此训练集被来自验证集的数据无意中转换。
解决方案
在进行 k-fold 交叉验证时,有一种避免数据泄漏的简单解决方案,即在将训练数据拆分成 k-fold 的之后进行这样的转换。
用户可以通过利用 Scikit-Learn 模块的管道轻松实现这一点。
通俗地说,管道可以创建将工作流的每一步链接在一起的对象。不熟悉 Scikit-Learn 管道的人可以在此了解更多信息:
我是这个工具的主要支持者,只要有机会,我就会反复使用它。用户可以将所有的转换器和估算器输入到一个管道对象中,然后对该对象执行 k-fold 交叉验证。
这将通过确保所有变换将仅在单个折叠上执行而不是在整个训练数据上执行来防止数据泄漏。让我们利用管道来修复之前交叉验证尝试中出现的错误。
当执行网格搜索时,可以实现相同的方法来避免数据泄漏。不要给estimator超参数分配机器学习算法,而是分配管道对象。
关键要点

照片由 Prateek Katyal 在 Unsplash 上拍摄
执行 k-fold 交叉验证的用户需要警惕数据泄漏,如果无意中使用验证数据来转换训练数据,就会发生数据泄漏。
如果用户无情地利用受数据分布影响的变换,如特征缩放和维度缩减,则可能会出现数据泄漏。
这个问题可以通过在交叉验证分割之后而不是之前应用转换来防止。实现这一点最简单的方法是使用 Scikit-Learn 包的管道。
我祝你在数据科学的努力中好运!
k-均值聚类——简介
原文:https://towardsdatascience.com/k-means-clustering-an-introduction-9825ea998d1e
一种流行的无监督机器学习方法综述

照片由 Karolina Grabowska 拍摄,来自 Pexels.com。
当我们在项目的探索性数据分析阶段使用未标记的数据集时,我们可能会对根据相似性将数据分组感兴趣。这使我们能够轻松地识别数据中可能存在的任何模式,这些模式对于人眼来说可能并不明显。这是通过聚类的无监督学习过程实现的。
实现基于相似性的数据分组的最流行的聚类方法之一是 K 均值聚类。它是一种非常常用的无监督机器学习算法,相对容易理解,也容易在 Python 中实现。
在本文中,我们将介绍 K-means 聚类算法的基础知识。
什么是集群?
聚类是一种无监督的机器学习过程,旨在根据邻*点的相似性将未标记的数据集分成许多组。
具有相似特征的数据点被放在同一个聚类中,而具有不同特征的数据点被放在另一个聚类中。

数据聚类前后。图片由作者提供。
有许多不同的算法可用于聚类数据,包括:
- k 均值
- 基于密度的噪声应用空间聚类
- 高斯混合模型
- 病房
- 凝聚聚类
- 桦树
流行的 Scikit-Learn Python 库中可用的不同方法的示例可以在这里查看。
什么是 K-means 聚类?
k 均值聚类是一种流行的无监督算法,它将数据分组为“k”个聚类,其中 k 由用户定义。该算法试图最小化每个聚类内所有*方距离的总和,并且还最小化数据点和称为质心的聚类中心点之间的距离。
质心在数据空间中的 k 个随机点处被初始化,并且基于到质心的距离,其周围的所有点被分配给相关的聚类。然后将质心调整到群集的中心点,并重新分配其周围的点。这种情况一直持续到质心没有变化或者这些点保持在同一个簇中,或者直到达到最大迭代次数。
K-means 是一种硬聚类方法,这意味着一个数据点要么属于一个聚类,要么不属于。
K 均值聚类的应用
k-means 聚类的应用很多。
- 图象分割法
- 社会网络分析
- 客户细分
- 文件的分类
- 异常检测
在地球科学和岩石物理学领域:
- 基于测井测量的异常值检测
- 根据测井记录和/或岩心分析数据进行相分类
k-均值聚类算法——工作原理
K 均值聚类工作原理概述
以下工作流说明了 k-means 算法工作的整个过程。每个步骤将在后续章节中详细介绍。

k-means 聚类算法概述。图片由作者提供。
K-Means 逐步聚类
让我们仔细看看每一个步骤。
第一步。收集我们的数据并确定“k”的值
第一步是将我们的数据收集在一起,并确定我们想要将数据分成多少个簇。对于这个数据集,我们将把它分成 3 个集群。

步骤 1,确定要将数据分组到的簇的数量。图片由作者提供。
有几种方法可以确定最佳集群数量,本节后面将介绍这些方法。
第二步。在数据中选择 k 个随机点
接下来,我们从数据中选择“k”个随机点。在这种情况下,k = 3,所以我们将选择 3 个随机点。这些是星团的质心。

第二步:从数据集中随机选择 k 个点。图片由作者提供。
第三步。将点分配给最*的种子点
数据集中每个点与质心之间的距离是使用欧几里德距离计算的。
一旦计算出每个点的质心,我们就将每个点分配到最*的质心。这就形成了我们最初的聚类。

第三步:计算点和质心之间的欧氏距离。然后将这些点分配给最*的种子点(质心)。图片由作者提供。
第四步。确定新的中心点
然后,我们计算每个聚类的*均点。这些成为新的质心。

步骤 4:通过在每个聚类中寻找*均点来识别新的质心。图片由作者提供。
第五步。将点分配给最*的质心

步骤 5:将点分配给新的簇形心。图片由作者提供。
然后,我们重复使用欧几里德距离将这些点分配给最*的聚类的过程。

欧几里德距离的例子。图片由作者提供。
第六步。确定新的集群中心
然后重新计算质心。

第六步:重新计算每个聚类的*均点,并调整质心。图片由作者提供。
第七步。重复步骤 4–6
将点分配到最*的质心并重新计算*均点的过程

步骤 6:将点重新分配到最*的质心。图片由作者提供。

步骤 7:识别新的质心,重复这个过程,直到满足指定的条件。图片由作者提供。
聚类什么时候停止?
我们重复聚类的过程,直到我们达到某些条件:
- 已经达到最大迭代次数
- 质心位置或被聚类的点没有变化或变化很小的模型收敛
确定最佳聚类数
我们可以使用多种技术来为自己得出一个值,而不是猜测 k 的数字。这里讨论的两种方法是简单的手动方法,用于确定“k”的最佳值。
肘图
肘方法是确定最佳聚类数的最常用方法,因为它简单且易于可视化。
本质上,我们使用不同数量的 k 多次运行 k 均值算法,并计算误差*方和的类内和(WSS)。这个特性也被称为惯性。
一旦我们有了结果,我们就绘制出每个集群的惯性,并确定图表中数据开始“变*”的点。在下面的例子中,我们可以选择 5 到 10 之间的一个值作为 k 的最佳值。
请记住,对大量集群执行此操作会增加计算时间。

选择最佳聚类数的肘图。作者的形象。
剪影法
剪影方法提供了一种测量方法,用于测量数据点在自己的聚类(内聚)中与其他聚类(分离)的相似程度。
它提供一个介于+1 和-1 之间的值,越接*+1 的值越合适,表示数据点位于正确的聚类内,并且远离其他聚类。
如果我们有多个负值,那么我们可能有太多或太少的集群。

用于选择 k-means 聚类的最佳聚类数的剪影分析。图片由作者提供。
有关这两种方法的更多详细信息,请访问:
https://www.analyticsvidhya.com/blog/2021/05/k-mean-getting-the-optimal-number-of-clusters/
改进 K 均值结果
如果我们再次重复 K-means 聚类过程,并根据参数,我们可能会得到不同的结果。这是由于被选作初始质心的点的不同。此外,一旦这些点被初始化,它们就很难移动很大的距离或靠*已经相对稳定的集群。
改进结果的一种方法是重复 k-means 过程,并试图找到所有聚类中方差的最低和。
还有许多初始化技术可用于 k 均值聚类,包括:
- 选择随机点(示例中使用的点)
- k-means ++
- 天真的分片
- 最远点启发式
- 启发式排序
- 基于投影
K-均值聚类的优缺点
优点:
- 一种快速有效的算法
- 容易理解
- 易于在 Python 中实现
- 可以扩展到大型数据集
- 保证收敛
缺点:
- 在运行算法之前,需要指定 k 的数值
- 取决于质心的初始化方式。一旦质心被初始化,它们就不能被移动很大的距离,或者如果其他集群相对稳定
- 对异常值和噪声敏感-异常值会影响质心的初始化
- 对于大型数据集,速度可能会成为一个问题
- 随着维度数量的增加,可能会出现问题
- 假设聚类是球形的,每个聚类具有相似数量的数据点
Python 实现
我之前写了一篇文章,介绍了所需的步骤和代码示例。这个例子使用测井数据测量将数据分成不同的组,这些组可以解释为不同的岩性。
你可以在下面的链接中找到它。
[## 如何使用 Python 使用无监督学习对测井数据进行聚类
towardsdatascience.com](/how-to-use-unsupervised-learning-to-cluster-well-log-data-using-python-a552713748b5)
摘要
总之,K-means 算法是一种非常流行的无监督机器学习技术,易于理解和实现。这是根据相似性将数据点分组的有效解决方案,应该在数据分析的探索阶段将其作为一种选择。
感谢阅读。在你走之前,你一定要订阅我的内容,把我的文章放到你的收件箱里。 你可以在这里做!*或者,您可以* 注册我的简讯 免费获取更多内容直接发送到您的收件箱。
其次,通过注册会员,你可以获得完整的媒介体验,并支持我和其他成千上万的作家。它每个月只花你 5 美元,你可以完全接触到所有令人惊叹的媒体文章,也有机会用你的写作赚钱。如果你用 我的链接报名,你直接用你的一部分费用支持我,不会多花你多少钱。如果你这样做了,非常感谢你的支持!
K-means 聚类和主成分分析 10 分钟
机器学习
通过实践案例研究演练

Julian Hochgesang 在 Unsplash 上拍摄的照片
这里有两种主要的机器学习模型:有监督的和无监督的。在监督学习中,你有 输入数据 X 和 输出数据 y ,然后模型从 X 到 y 找到一个 映射 。在无监督学习中,你只有 输入数据 X 。无监督学习的目标各不相同:在 X 中对观察值进行聚类,降低 X 的维度,在 X 中进行异常检测等。
由于监督学习已经在本系列的 第一部分 和 第二部分 中进行了广泛的讨论,所以本故事将重点讨论无监督学习。在许多其他无监督的学习任务中,您将遇到 k-means 聚类和主成分分析(PCA)的降维。
**Table of contents****·** [**K-means Clustering**](#ba75)
**·** [**Principal Component Analysis (PCA)**](#74ac)
∘ [Noise](#b83e)
∘ [Redundancy](#8ec4)
∘ [Case Study](#9c11)
**·** [**Conclusion**](#2d76)
k 均值聚类
聚类 的思想是对观察值进行分组,使得同一组内的观察值彼此相似(或相关),而与其他组内的观察值不同(或无关)。有许多算法可以实现这一点,其中之一是 k-means。

梅尔·普尔在 Unsplash 上拍摄的照片
K-means 是一种基于质心的聚类算法,其工作原理如下。
- 随机初始化:随机放置 k 个形心。
- 分类指定:根据到质心的距离,将每个观测值指定到最*的分类。
- 质心更新:将质心移动到同一个聚类的*均观测值。
- 重复步骤 2 和 3,直到达到收敛。
用户在实现 k-means 聚类时必须确定两个超参数:质心数量 k ,以及所使用的距离度量(通常是 欧几里德距离 或 曼哈顿距离 )。在这个故事中,我们将使用欧几里德距离,它是由公式定义的

其中 x = ( x₁ , x₂ ,…, xₙ )和 y = ( y₁ , y₂ ,…, yₙ )为维度为 n 的观测值。因此,现在我们只需要确定 k 。
由于我们希望观察值组中的观察值彼此相似,因此好的聚类是*方和(WSS)最小的聚类,也就是说,每个观察值到其所属聚类的质心的二次距离之和最小。
此外,由于一个组中的观察值需要不同于其他组中的观察值,所以好的聚类具有更大的*方和(BSS)。这里,BSS 只是从每个质心到全局*均值的二次距离之和,由每个聚类中的观察值数量加权。
数学上,WSS 和盲源分离公式如下


在哪里

很容易看出,较高的 k 值对应于较低的 WSS。然而, k 必须从商业的角度来确定,或者更客观地使用所谓的肘方法。

使用肘法确定 k |图片由作者
肘方法说我们应该选择 k ,在那里增加它将不会导致 WSS 更明显的减少。因此从上面的图中,例如,我们选择 k = 2 或 k = 3。
下面是一个逐步 k-means 聚类过程的例子,用 k = 3 和“ × ”表示质心。

k-means 聚类的逐步过程。它从质心的随机初始化开始,并对作者的每个迭代 1、2 和 3 |图像进行聚类分配和质心更新
给定足够的时间,k-means 总会收敛。然而,收敛可能是局部最小值。这高度依赖于质心的初始化。因此,计算通常要进行几次,质心的初始化不同。
帮助解决这个问题的一个方法是 k-means++ 初始化方案。这将初始化质心(通常)彼此远离,可能会比随机初始化产生更好的结果。
K-means 是而不是总是聚类的好选择。由于距离选择和 k-means 聚类所做的一些隐含假设,一些数据可能会利用 k-means 弱点。下图显示了其中的一些场景。

数据不符合 k-means 作出的一些隐含假设(选择 k** 、各向同性聚类、等方差),结果产生了不需要的聚类|图片由作者**
因此,首先对数据进行转换总是一个好的做法。还有很多其他的聚类算法来解决这个问题,比如谱聚类、凝聚聚类、ward 层次聚类、DBSCAN、BIRCH 等等。
主成分分析
PCA 的目标是识别最有意义的基础来重新表达数据。希望这个新的基础能过滤掉噪音,揭示隐藏的结构。PCA 问:有没有另一个基,是原始基的线性组合,最好的重新表达你的数据?

设 X 是一个有 m 个观测值和 n 个特征的数据集,所以 X 是一个 m × n 矩阵。为了简化计算,我们将 X 标准化,使其*均值为零。
设 Y 为 X 的新表示,另一个 m × n 矩阵通过线性变换 P 与 X 相关(其本身就是一个 n × n 矩阵)。PCA 会找到将 X 转换成Y线性的 P ,也就是说,

从这个方程中我们可以看出, P 的列是一组新的用于表示 X 中观测值(行)的基向量,称为 X 的 主成分 。现在,什么是基 P 的好选择?我们希望 Y 展示什么特征?要回答这些问题,考虑两个角度:噪声和冗余。
噪音
记住,在 Y 中,我们希望过滤掉噪声。衡量噪声的标准是信噪比(SNR),或方差比

高信噪比(≫1)表示高精度数据,而低信噪比表示高噪声数据。我们假设感兴趣的维度存在于具有最大方差和大概最高 SNR 的方向上。

信号和噪声的变化用对着数据云的两条线来表示|图片作者作者
裁员
假设你的数据是二维的,有特征 x ₁和 x ₂.考虑下面的三个情节。

一系列可能的数据冗余。最右侧图中的特征高度相关,表明冗余度高。|图片由作者
如果可以从 x ₁计算出 x ₂,我们就说数据具有高冗余度(反之亦然)。如果是这种情况(如上面最右边的图),其中一个特征是冗余的,可以在不牺牲太多信息的情况下减少数据维数。事实上,这是降维背后的中心思想。
从这两个角度来看,PCA 都需要特征的方差(用于最大化信号)和特征对的协方差(用于最小化冗余) X 。这正是 X 的协方差矩阵的用途,根据定义可以写成

Cₓ 是一个 n × n 矩阵,对角项代表特征的方差,非对角项代表特征对的协方差,反映了我们数据中的噪声和冗余:
- 根据假设,对角线上的大值对应于有趣的结构。
- 在非对角线术语中,大幅度对应于高冗余。
设 Cᵧ 为 Y 的协方差矩阵。那么当使用 P 将 X 转化为 Y 时,优化后的 Cᵧ 会是什么样子呢?
- 在 Y 中的每个连续维度应该根据方差进行排序,以突出有趣的结构。
- 在 Cᵧ 中的所有非对角线项应该为零。因此, Cᵧ 一定是对角矩阵,在 Y 中没有冗余。
有很多方法可以对角化 Cᵧ 。对于 PCA,它假设 P 是一个 正交矩阵 。现在,常设仲裁法院的任务已经简化为:
在 XP = Y 中找一些正交矩阵 P 使得cᵧ=yᵀy/(m1)是对角矩阵。 P 列是 X 的 主成分 。
由于 Cₓ 是对称的,所以存在矩阵 D 和 E 使得 Cₓ = EDEᵀ ,其中 D 是对角矩阵, E 是按列排列的 Cₓ 的特征向量矩阵。而且, Cₓ 和 D 有相同的特征值。由于 D 是对角矩阵,其特征值就是其主对角线的元素。
选择P≦E,然后

总而言之,到目前为止,我们已经成功地找到了一个矩阵 P ,其中它的列是 X 的协方差矩阵 Cₓ 的特征向量。矩阵 P 满足 XP = Y 使得 Y 的协方差矩阵 Cᵧ 为对角矩阵,其中对角线由 Cₓ 的特征值组成。所以, Y 中没有冗余。****
为了确保 Y 中的每个连续维度根据方差进行排序,以便突出显示有趣的结构,您可以对 D 中 Cₓ 的特征值进行重新排序,以便更大的特征值位于左上角(当然也可以通过重新排列 P 的列和 Pᵀ 的行来满足
PCA 可以通过只选择重排后 P 的前几列进行降维。要选择的列数是 PCA 的超参数。列数越多意味着原始数据中解释的差异越多 X 。这对于可视化、汇总大量数据或查找有区别的特征非常有用。
经过这么长时间的推导,我们终于能够编写 PCA 算法了:
- 将原始数据 X 标准化为零均值,其中 X 是一个 m × n 矩阵。
- 计算 X 的协方差矩阵 Cₓ 。
- 计算 Cₓ 的特征值和对应的特征向量。
- 将 Cₓ 的特征值按降序排序,并将 Cₓ 的特征向量按降序排序。
- 选择前 k 个特征向量,排列成 P 列。这样, P 就是一个 n × k 矩阵。
- 通过 Y = XP 计算缩减后的数据 Y 。因此, Y 是一个 m × k 矩阵。
个案研究
在本案例研究中,您将使用 虹膜数据集 。
该数据集包含 3 类,每类 50 个观察值,其中每类涉及一种鸢尾植物(鸢尾、杂色鸢尾或海滨鸢尾)。根据植物萼片和花瓣的测量结果,数据集中有四个特征。
您希望在散点图中显示数据集。然而,这是不可能的,因为不存在所谓的四维散点图。因此,您从数据集中提取尽可能多的信息以获得二维表示,并为此使用 PCA。
X 是一个 150 × 4 的矩阵,其特征方式如下。
*sepal length (cm) 5.843333
sepal width (cm) 3.057333
petal length (cm) 3.758000
petal width (cm) 1.199333*
对于每个特征,从每个观察值中减去相应的*均值。现在你应该有了这个以零为中心的数据集。姑且称之为 X_meaned 。
X_meaned 的协方差矩阵是一个 4 × 4 矩阵

因此, Cₓ 有四个特征值和相应的特征向量。他们是

根据特征值降序排列特征向量。既然你想要一个 X 的二维表示,那就挑两个特征值最大的特征向量,表示可以提取的最大信息。他们是 v₄的和 v₃的。在考虑顺序的同时,将它们按列排列。你会得到一个 4 × 2 的线性变换矩阵**

简化后的数据 Y 可以通过乘以 X_meaned 和 P 来计算。你得到 Y 如下。
有了这个双特征数据集,您现在可以如下可视化虹膜数据集的本质。

虹膜数据集前两个主要成分的散点图|图片作者作者
结论
您已经非常详细地学习了 K 均值聚类并应用了主成分分析进行可视化。现在,您不仅可以使用已建立的库来构建它们,还可以自信地知道它们是如何从内到外工作的,使用它们的最佳实践,以及如何提高它们的性能。**
恭喜你。

马库斯·斯皮斯克在 Unsplash 上拍摄的照片
🔥谢谢!如果你喜欢这个故事,想支持我这个作家,可以考虑 成为会员 。每月只需 5 美元,你就可以无限制地阅读媒体上的所有报道。如果你注册使用我的链接,我会赚一小笔佣金。**
🔖想知道更多关于梯度下降和许多其他优化器的工作原理吗?继续阅读:
***
艾伯斯·乌兹拉***
从零开始的机器学习
View list8 stories

***
艾伯斯·乌兹拉***
我最好的故事
View list24 stories


[1]主成分分析教程(arxiv.org/abs/1404.1100)
k-均值聚类:把它解释给我听,就像我 10 岁一样
原文:https://towardsdatascience.com/k-means-clustering-explain-it-to-me-like-im-10-e0badf10734a
一个长期流行的聚类算法的友好介绍
这将是向一个 10 岁的系列解释机器学习算法的第二个系列(只是因为这一系列听起来比文章更有趣)。你可以在这里 找到 XGBoost 分类文章 。今天我将向一个 10 岁的孩子或者基本上是 ML 算法世界的任何新手解释 K-Means 聚类,一个非常流行的聚类算法。我将试图消除血淋淋的,数学细节,并解释背后的简单直觉。
在开始学习算法之前,我们先了解一下什么是聚类。聚类包括自动发现数据中的自然分组。通常,当我们得到可以可视化的数据时(二维甚至三维数据),人眼可以很容易地形成不同的集群。但是机器要做到这一点就有点难了。这就是聚类算法发挥作用的地方。这也可以推广到更高的维度来聚集数据,这甚至是人眼无法做到的。现在我们已经解决了这个问题,k 代表我们开始了!
假设我们有 19 个数据点,如下所示:

现在假设我们知道这些数据属于 3 个相对明显的类别,如下所示:

我们的任务是使用 k-means 聚类算法来进行分类。
步骤 1:选择聚类数 k
我们要识别的聚类数就是 k-means 聚类中的 k。在这种情况下,由于我们假设有 3 个集群,k = 3。
第二步:随机选择 k 个点
我们通过选择 3 个随机点(不一定是我们的数据点)开始寻找聚类的过程。这些点现在将作为质心、或中心,我们将制作:

步骤 3:创建 k 个集群
为了形成聚类,我们从测量每个数据点到 3 个质心的距离开始。我们把这些点分配给离它最*的聚类。因此,对于一个样本点,距离如下所示:

只看它,我们看到从该点到绿色质心的距离是最小的,所以我们将该点分配给绿色簇。
在二维空间中,计算两点间距离的公式是:


使用上面的公式,我们对其余的点重复这个过程,聚类将看起来像这样:

步骤 4:计算每个聚类的新质心
现在我们有了 3 个簇,我们找到了它们各自形成的新的质心。例如,我们计算蓝色星团质心坐标的方法是:

其中 x1、x2 和 x3 是蓝色簇中 3 个点的 x 坐标。y1、y2 和 y3 是蓝色群集的 3 个点中的每一个的 y 坐标。我们将坐标之和除以 3,因为蓝色集群中有 3 个数据点。类似地,粉色和绿色星团的质心坐标为:

新的质心看起来像这样:

步骤 5:评估每个集群的质量
因为 k-means 不能像我们一样看到聚类,所以它通过找到所有聚类中的变化来测量质量。k-means 聚类背后的基本思想是定义聚类,以便最小化聚类内的变化。我们通过计算所谓的(WCSS)来量化这个方差:

这是一个看起来很吓人的公式,所以如果你没有真正理解它,没关系;试着去理解背后的直觉就好。
出于简化的目的,让我们像这样直观地表示变化:

步骤 6:重复步骤 3-5
一旦我们有了以前的聚类和存储的变化,我们就从头开始。但是只有这一次,我们使用先前计算的质心来生成 3 个新的聚类,重新计算新聚类的中心,并计算所有聚类内的变化的总和。
让我们假设接下来的 4 次迭代如下所示:

从最后两次迭代中,我们看到集群没有改变。这意味着算法已经收敛,我们停止聚类过程。然后,我们选择具有最小 WCSS 的集群。这也恰好是最后两次迭代的那些。因此,它们将是我们最后的集群。
我们如何选择 k?
在我们的例子中,我们很方便地知道我们需要 3 个集群。但是如果我们不知道我们有多少个簇,那么我们如何选择 k 呢?
在这种情况下,我们尝试多个 k 值并计算 WCSS。
k=1:

k=2:

k=3:

k=4:

我们注意到,每次我们添加一个新的集群,每个集群内的总变化都比以前小。而当每个聚类只有一个点时,变差= 0。
因此,我们需要使用一种叫做肘图的东西来寻找最佳 k。它绘制了 WCSS 与聚类数或 k 的关系

作者图片
这被称为肘形图,因为我们可以通过找到图的“肘”来找到最佳 k 值,即 3。直到 3,你可以注意到变化的巨大减少,但在那之后,变化不会很快下降。
大概就是这样。一个简单但有效的聚类算法!
如果你想支持我的工作,可以考虑使用我的链接注册一个媒体订阅!(每月 5 美元,随时取消)
如果你对 10 岁的孩子需要理解的其他算法有什么建议,请随时通过 LinkedIn 联系我,或者给我发电子邮件到 shreya.statistics@gmail.com。
k-意味着在 Excel 中从头开始
原文:https://towardsdatascience.com/k-means-from-scratch-in-excel-bb60778d186e
为了更好地理解劳埃德算法
K-Means 是最常见的无监督机器学习算法之一。在本文中,我将使用一个简单的数据集在 Excel 中实现一个算法来寻找质心。
您可能已经注意到,在一系列文章中,我使用 Excel/Google Sheet 来实现基本的机器学习算法,以便我们可以理解其中的基本原理:
- Excel 中带梯度下降的线性回归 /Google Sheet
- Excel 中的梯度下降逻辑回归 /Google Sheet
- Excel 中的神经网络从无到有
正如我的一位老师告诉我的:
除非你知道如何从零开始实现,否则你不会真正理解一个算法。
使用 Python 或 R 之类的编程语言,你必须了解编程的基础知识。所以你可能有借口不知道他们不能学习机器学习算法。
不过算你走运,我会用 Excel 做算法。所以让我们把手弄脏吧!

杰里米·毕晓普在 Unsplash 上拍摄的照片由作者编辑
K-均值聚类的定义
K-均值聚类原理
根据维基百科,
k-均值聚类旨在将 n 个观测值划分为 k 个聚类,其中每个观测值属于具有最*均值(聚类中心或聚类质心)的聚类,作为该聚类的原型。
根据算法的输出,我们得到 k 个质心。而 k 是算法的超参数。我们将使用观察值和质心之间的欧几里德距离。
还有其他版本使用*方距离而不是欧几里德距离,中值而不是*均值,您可以作为练习编辑文件。
不同的算法
有不同的算法,我们可以在 k-means 的 R 实现的文档中看到:Hartigan-Wong,Lloyd,Forgy 和
MacQueen。为简单起见,我们将实现劳埃德算法:
- 初始化:我们随机选择初始质心
- ****期望最大化:计算每个质心和每个观测值之间的距离,这样我们就可以为每个观测值找到最*的质心。然后用分配给相同质心的观测值计算新的*均值。我们可以重复这个过程,直到收敛。
Google 工作表/Excel 文件
我创建了一个可以与你分享的谷歌表单,请支持我在 Ko-fi 上的工作。这里是 K-means Google sheet 支持页面的链接:https://ko-fi.com/s/ea8faebbb5

在高保真上支持我 —作者图片
表的展示
这些表的结构如下:
- 数据:我们试图聚类的数据集的可视化。这里我们试着用一个简单的数据集,这样可以直观的知道最终的解。
- 型号:在该表中,实现了劳氏算法。
- 对于以 p 开头的表单,我们尝试将算法的不同步骤可视化
- 弯头:弯头法选择一个最优 k 的绘图
资料组
在数据表数据中,我们尝试呈现数据集。对于无监督学习,我们不必指定观察值的标签。如你所知,我使用最简单数据集的原则是,我们只能使用一个变量。但是不清楚,因为所有的数据都在一个轴上。所以我们将使用两个变量。

作者图片
通过一个简单的数据集,我们可以可视化所有的数据点,我们可以说质心的最佳数量是 2。
但是我们将首先实现 3 个质心的算法。然后我们将使用肘方法来验证 2 是更好的。
劳氏 K 均值算法
在表模型中,我们将实现劳埃德算法。
质心的初始化
开始时,我们会随机选择三个质心。

作者图片
正如我们已经可以想象的那样,没有必要选择远远超出 x1 和 x2 值范围的值。所以凭直觉,我们知道我们可以做更好的初始化。
距离计算
选择质心后,我们可以计算数据点到每个质心的距离。
列 D1 将包含每个数据点和质心 1 之间的距离。因为有 3 个质心,我们有 D1,D2 和 D3。

作者图片
为每个数据点分配质心
对于每个数据点,我们找到质心。为了可视化的需要(我们将在下面的表格中看到),我在不同的列中呈现了 x2 的三个不同的质心。

作者图片
计算新质心
将每个观察值分配给质心后,我们可以计算每个质心的*均值。

作者图片
重复直到收敛
使用 Excel,可以将值复制并粘贴到第一步。我们可以手动操作,直到收敛。
质心移动图
为了可视化质心在哪里,与质心相关联的数据点在哪里,以及质心在每次迭代中如何移动,可视化非常有帮助。
Excel 绘图策略
但是用 Excel,就不容易出图了。我定义了一个 C3 的绘图区域:P38。第 3 行定义了这些列,下面按照不同的计算步骤列出了这些值
- x1 和 x2 包含原始数据集
- D1、D2 和 D3 是从一个数据点到每个质心的距离
- C1、C2 和 C3 包含质心的坐标
- C1d、C2d 和 C3d 包含每个数据点被分配给哪个质心的信息
- NC1、NC2 和 NC3 是新的质心。

作者图片
然后我们可以得到这个图:
- 蓝点是原始数据集
- 这三种颜色分别代表三个质心
- 正方形代表初始质心,星形代表新的质心。
- 我手动添加了箭头来可视化质心的移动。

作者图片
两个质心
我们可以用 k=2 创建相同的图。我们知道视觉上最好有两个质心。我们将在后面看到如何使用数学/统计/定量指标来确保它确实更好。

作者图片
重复直到收敛
对于 k 的每个值,我们可以重复这个过程,直到收敛。由于只有很少的数据点,收敛将通过少量的迭代来实现。在实践中,当数据集较大时,应该采用更优的策略。
****
作者图片
k 的最佳值
为了确定 k 的最佳值,一种常见的方法是肘法,该方法包括分析惯性如何随着不同的 k 值而变化
根据 sklearn 实现,惯性是样本到其最*聚类中心的*方距离之和。
我们可以很容易地在 Excel 中计算出来。对于 k=2 或 3(在表 p2f 和 p3f 中),当达到收敛时,我计算了惯性。
对于 k=1 的情况,这是微不足道的:质心由 x1 和 x2 的*均值组成。
然后,对于从 1 到 3 的 k,我们可以画出惯性。

Kmeans 的肘方法(图片由作者提供)
我们可以看到,从 1 到 2,惯性的下降是巨大的。而从 2 到 3 几乎为零。
肘法包括确定 k 值,从这个值开始,惯性的减小就变得微不足道了。
因此,我们可以得出结论,k=3 是不必要的,因为惯性方面的改善是微不足道的。
结论
再一次,我们可以看到 Excel 可以成为彻底理解机器学习算法的优秀工具。
至于线性回归、逻辑回归和神经网络中的梯度下降,有趣的是注意到这个学习过程是通过在 Excel 中手动实现和完成的。
让我们以一个哲学注解来结束:人类学习和机器学习是相似的,因为我们可以从不同的起点开始,但是在一个耗时的过程和努力工作中,我们都可以取得一些成功。实现它的速度和规模(无论是局部最优还是全局最优)也取决于更多的高级技能。
更进一步,还有更多算法可以测试:
- K-Means ++初始化
- Elkan 算法用于 Scikit learn 的实现。
- 在 R 实现中,K-Means 使用 Hartigan-Wong、Lloyd、Forgy 和
MacQueen。这里有一些关于它的用法的信息。 - 为了选择最佳 k,除了具有惯性(或失真分数)的图之外,我们可以使用轮廓分数或戴维斯-波尔丁指数。
我写关于机器学习算法的文章,请通过下面的链接关注我,并完全访问我的文章:
**https://medium.com/@angela.shi/membership **
Python 中的 K-Medoid 聚类算法
一步一步的教程-有一个解决的例子

图片来源——由作者使用 Jupyter 笔记本准备。
1.介绍
1.1K-均值聚类与挑战
大规模数据的聚类是实现基于分段的算法的关键。细分可以包括识别客户群以促进有针对性的营销,识别处方者群以允许医疗保健参与者向他们传达正确的信息,以及识别数据中的模式或异常值。 K -Means 是跨不同问题领域采用的最流行的聚类算法,主要是由于它的计算效率和算法的易于理解性。 K- 意味着依赖于从数据中识别聚类中心。它使用欧几里德距离度量在向这些聚类中心分配点之间交替,并重新计算聚类中心,直到达到收敛标准。然而,K-均值聚类有一系列的缺点:
- K-表示聚类不使用除欧几里德距离之外的任何其他距离度量。在使用稀疏数据的推荐系统中,经常使用不同的距离度量,如余弦距离。
- 在聚类中,最终中心不一定是来自数据的数据点,而是对不同应用领域(如计算机视觉中的图像)的可解释性的瓶颈。
2.水母周围的分区(PAM)
PAM 代表“围绕 Medoids 的分区”PAM 将 PAM 的每一步从确定性计算问题转化为统计估计问题,并将样本大小的复杂度从 n 降低到 O(n log n) 。 Medoids 是选择作为聚类中心的数据点。K- 表示聚类的目的是最小化类内距离(通常称为总*方误差)。相反,K-Medoid 最小化了聚类中的点和被认为是该聚类的中心的点之间的不相似性。
当且仅当数据集中的任意点与聚类中所有其他数据点的不相似性最小时,该点才可被视为 medoid。
PAM 的基本概念包括:
- 从大小 n (n 为记录数)的数据点中找出一组 k 的 Medoids ( k 为聚类数,M 为 medoids 的集合)。
- 使用任何距离度量(比如 d。),可能是欧几里德,曼哈顿等。),尝试定位使数据点到最*的 Medoid 的总距离最小的 med oid。
- 最后,在所有可能的 k(n-k) 对中交换降低损失函数 L 的中值和非中值对。损失函数定义为:

图一。说明了 PAM 算法使用的损失函数。
更新质心:在 K-Means 的情况下,我们正在计算聚类中所有点的*均值。但是对于 PAM 算法,质心的更新是不同的。如果在一个聚类中有 m 个点,则用所有其他(m-1)个点交换先前的质心,并将该点最终确定为具有最小损失的新质心。最小损失由图 1 所示的成本函数计算得出。下面列出了一个相同的工作示例(GeeksforGeeks,2019)。
2.1 算法—数据集
让我们考虑一组数据点和特征。

图二。显示了 7 个数据点的一组假设特征。作者使用 Jupyter 和 Markdown 制作的图像。
2.2 初始化步骤
- 让我们随机选择两个 medoids,所以选择𝐾= 2,让 M1 =(4,5),M2 =(9,10)是这两个 medoids。注释聚类算法从数据中选择数据点作为中间点
- 计算所有点到 M1 和 M2 的距离。注:相异度的计算方法是将 medoids 和数据点之间的绝对差值进行求和。例如 D=|Mx —特征 X| + |My —特征 Y|

图 3。说明了相异度计算。作者使用 Jupyter 和 Markdown 制作的图像。
2.3 分配步骤
将数据点分配给具有较低相异度(成本)的 Medoids。数据点 0 和 2 指向 M2,数据点 3、4 和 5 指向 M1。总成本=成本/分配给 M1 的积分差异+成本/分配给 M2 的积分差异。
总成本=(4+3+4)+(4+7)= 22;M1 的成本=点 3 与 M1 的差异(4) +点 4 与 M1 的差异(3)+点 5 与 M1 的差异(4)。对 M2 重复同样的计算。
2.4 质心重新编译步骤
随机选择一个非 medoid 点并重新计算成本。现在,让我们选择数据点 5 M1 as (2,3)作为中间点,并重新计算成本。

图 4。示出了使用更新的质心的相异度计算,即,使用数据点 5 (2,3)作为 M1。作者使用 Jupyter 和 Markdown 制作的图像。
现在只有 4 分和 6 分去了 M1,其余的分都去了 M2。总成本=(7+4)+(4+7+6)=28,交换成本=新成本-以前的成本= 28–22 = 6。
我们将取消交换,因为交换成本大于 0。这个过程一直持续到满足收敛标准。
3.使用 Python 实现
我们将使用来自 scikit-learn-extra 的 K-Medoid 算法来模拟上面的例子。很少代码和概念,灵感来自软件包文档(scikit-learn-extra,2019)
# — — — — — — -Importing Packages — — — — — — — — — — — -import matplotlib.pyplot as plt
import numpy as npfrom sklearn_extra.cluster import KMedoids# — — — — — — -Assigning Initial Centers — — — — — — — — — — — -
centers = [[4, 5], [9, 10]]# — — — — — — -Assigning Data: Dummy Data used in example above — — — — — — — — — — — — — — — — — —
df=np.array([[7,8], [9,10], [11,5], [4,9], [7,5], [2,3], [4,5]])# — — — — — — -Fit KMedoids clustering — — — — — — — — — — — -
KMobj = KMedoids(n_clusters=2).fit(df)# — — — — — — -Assigning Cluster Labels — — — — — — — — — — — -
labels = KMobj.labels_
可视化集群。
# — — — — — — -Extracting Unique Labels — — — — — — — — — — — -unq_lab = set(labels)# — — — — — — -Setting Up Color Codes — — — — — — — — — — — -
colors_plot = [
plt.cm.Spectral(each) for each in np.linspace(0, 1, len(unq_lab))
]for k, col in zip(unq_lab, colors_plot):class_member_mask = labels == k
# — — — — — — -Setting datapoint Feature X and Feature Y — — — — — — — — — — — -xy = df[class_member_mask]
# — — — — — — -Plotting Feature X and Feature Y for each cluster labels — — — — — — — — — — — -
plt.plot(
xy[:, 0],
xy[:, 1],
“o”,
markerfacecolor=tuple(col),
markeredgecolor=”white”,
markersize=10,
);# — — — — — — -Annotate Centroids — — — — — — — — — — — -plt.plot(
KMobj.cluster_centers_[:, 0],
KMobj.cluster_centers_[:, 1],
“o”,
markerfacecolor=”orange”,
markeredgecolor=”k”,
markersize=10,
);# — — — — — — -Add title to the plot — — — — — — — — — — — -plt.title(“KMedoids clustering on Dummy Data- Medoids are represented in Orange.”, fontsize=14);

图 5。使用 K-Medoids 演示了最终的簇形心。图片由作者使用 Jupyter 笔记本制作。
参考
- scikit-learn-extra。(2019). KMedoids 演示— scikit-learn-extra 0.2.0 文档。sci kit-Learn-extra . readthedocs . io .https://sci kit-Learn-extra . readthedocs . io/en/stable/auto _ examples/plot _ kmedoids . html # sphx-glr-auto-examples-plot-kmedoids-py
- 极客之福。(2019 年 5 月 17 日)。 ML | K-Medoids 聚类与求解实例。极客之福。https://www . geeks forgeeks . org/ml-k-med OIDs-clustering-with-example/
关于作者:高级分析专家和管理顾问,通过对组织数据的业务、技术和数学的组合,帮助公司找到各种问题的解决方案。一个数据科学爱好者,在这里分享、学习、贡献;可以和我在 上联系 和 推特;
使用 Python 对测井记录进行岩性分类的 k-最*邻
基于测井测量细分地下

k-最*邻(kNN)是一种流行的非参数监督机器学习算法,可以应用于分类和基于回归的问题。它很容易用 Python 实现,也很容易理解,这使得它成为一个很好的算法,可以在您开始机器学习之旅时开始学习。
在本文中,我们将介绍 kNN 算法如何工作,以及如何使用 Python 的 Scikit-Learn 库将其应用于测井数据。
kNN 算法是如何工作的?
对数据进行分类是机器学习的主要应用之一。因此,有许多算法可用。kNN 算法就是其中之一。
kNN 背后的想法非常简单。彼此靠*的点被认为是相似的。
当一个新的数据点被引入到一个训练数据集中时,发生以下步骤
- 确定 k 值-用于分类新数据点的点数
- 计算待分类数据点与 k 个最*点之间的距离(欧几里德或曼哈顿)
- 识别 k 个最*邻
- 在这些 k 个最*邻中,我们计算每个类中数据点的数量
- 使用多数表决,将新数据点分配给出现次数最多的类别
下面这个简单的例子展示了这个过程,我们假设 k 是 3,最*的点都是一个类。

k-最*邻应用于新数据点的示例,其中 k=3。图片由作者提供。
在 k-最*邻是混合类的情况下,我们可以使用多数表决,如下所示。

k-最*邻应用于新数据点的示例,其中 k=5,最*点是类的混合。图片由作者提供。
k *邻(kNN)的应用
- 推荐系统
- 模式检测—例如欺诈检测
- 文本挖掘
- 气候预测
- 信用评级分析
- 医学分类
- 岩性预测
k *邻(kNN)的优势
- 简单易懂
- 使用 Sci-kit Learn 通过 Python 轻松实现
- 可以快速处理小型数据集
- 无需调整多个参数
- 不需要对数据做出假设
- 可以应用于二元和多类问题
缺点k-最*邻(kNN)
- 使用大型数据集进行分类可能会很慢
- 受到维数灾难的影响-随着要素数量的增加,算法可能难以做出准确的预测
- 可能对数据比例敏感,即使用不同单位测量的特征
- 受到噪音和异常值的影响
- 对不*衡的数据集敏感
- 在使用算法之前,需要处理缺失值
用 Scikit 实现 KNN-学习相分类
导入所需的库
对于本教程,我们需要一些 Python 库和模块。
首先,我们将把pandas作为pd导入。这个库允许我们从 csv 文件中加载数据,并将数据存储在内存中以备后用。
此外,我们还有许多来自 sci-kit 学习库中的模块:
KNeighborsClassifer进行 kNN 分类train_test_split用于将我们的数据分成训练和测试数据集StandardScaler用于标准化特征的比例classification_report、confusion_matrix和accuracy_score用于评估模型性能
最后,为了可视化我们的数据,我们将混合使用matplotlib和seaborn。
import pandas as pd
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
import seaborn as sns
import matplotlib.pyplot as plt
导入所需的数据
下一步是加载我们的数据。
我们在本教程中使用的数据集是作为 Xeek 和 FORCE 2020 (Bormann et al .,2020) 举办的机器学习竞赛的一部分使用的训练数据集的子集。它是在挪威政府的 NOLD 2.0 许可下发布的,详细信息可以在这里找到:挪威开放政府数据许可(NLOD) 2.0 。
完整的数据集可以通过以下链接获得:https://doi.org/10.5281/zenodo.4351155。
为了读取数据,我们可以调用pd.read_csv()并传入训练文件的相对位置。
df = pd.read_csv('Data/Xeek_train_subset_clean.csv')
一旦加载了数据,我们就可以调用describe()方法来查看数据集中的数字列。这为我们提供了特性的概述。
df.describe()

Xeek Force 2020 测井岩性竞赛数据统计。图片由作者提供。
数据准备
处理缺失数据
在我们继续 kNN 算法之前,我们首先需要进行一些数据准备。
由于 kNN 算法不处理丢失的值,我们需要首先处理这些。最简单的方法是执行列表式删除。如果行中的任何要素有缺失值,这将删除该行。
强烈建议您对数据集进行全面分析,以了解丢失数据的原因以及是否可以修复。
尽管这种方法看起来是一种快速的解决方案,但它可以显著减少数据集。
df = df.dropna()
选择培训和测试功能
接下来,我们需要选择哪些特性将用于构建 kNN 模型,以及哪些特性将成为我们的目标特性。
对于这个例子,我使用一系列测井测量来建立模型,并使用岩性描述作为目标特征。
# Select inputs and target
X = df[['RDEP', 'RHOB', 'GR', 'NPHI', 'PEF', 'DTC']]
y = df['LITH']
与任何机器学习模型一样,我们需要将数据分成一个训练集和一个测试集,前者用于训练/构建我们的模型,后者用于验证我们的模型在未知数据上的性能。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
标准化特征值
当使用不同比例和范围的测量时,标准化它们是很重要的。这有助于减少模型训练时间,并减少对依赖基于距离的计算的模型的影响。
标准化数据本质上包括计算特征的*均值,从每个数据点中减去它,然后除以特征的标准偏差。
在scikit-learn中,我们可以使用standard scaler类来转换我们的数据。
首先,我们使用训练数据来拟合模型,然后使用fit_transform函数对其进行转换。
当涉及到测试数据时,我们不想让 StandardScaler 适应这些数据,因为我们已经这样做了。相反,我们只是想应用它。这是使用transform方法完成的。
值得注意的是,在训练测试分割之后应用标准缩放器,并且它仅适合于训练数据集。一旦定标器模型被拟合,它就被应用于测试数据集。这有助于防止数据从测试数据集泄漏到 kNN 模型中。
scaler = StandardScaler()
#Fit the StandardScaler to the training data
X_train = scaler.fit_transform(X_train)
# Apply the StandardScaler, but not fit, to the validation data
X_test = scaler.transform(X_test)
构建 kNN 分类器
创建 KNeighborsClassifier 时,我们可以指定几个参数。这些的全部细节可以在 这里 找到。当然,我们不必提供任何东西,将使用默认参数。
默认情况下,用于分类新数据点的点数设置为 5。这意味着这 5 个最*点的类别将用于对该新点进行分类。
clf = KNeighborsClassifier()
一旦分类器被初始化,我们接下来需要使用我们的训练数据来训练模型(X_train & y_train)。为此,我们调用clf,然后调用fit方法。
在fit方法中,我们传递我们的训练数据。
clf.fit(X_train, y_train)
使用 kNN 模型进行预测
一旦模型被训练,我们现在可以通过从分类器调用predict方法来对我们的测试数据进行预测。
y_pred = clf.predict(X_test)
评估模型性能
使用模型精度
为了理解我们的模型在测试数据上的表现,我们可以使用一些度量标准和工具。
如果我们想要快速评估我们的模型表现如何,我们可以调用准确性评分方法。这为我们提供了相对于预测总数有多少预测是正确的指示。
accuracy_score(y_test, y_pred)
这将返回值 0.8918532439941167,并告诉我们我们的模型已经正确预测了 89.2%的标签。
请注意,这个值可能会产生误导,尤其是在我们处理不*衡的数据集时。如果有一个阶级占优势,那么这个阶级比少数阶级有更高的机会被正确预测。占主导地位的类将通过提高准确度分数来影响准确度分数,从而给人一种我们的模型做得很好的假象。
使用分类报告
我们可以进一步评估,看看分类报告。这提供了额外的度量以及每个类的预测程度的指示。
其他指标包括:
- precision: 指示该类中有多少值被正确预测。值介于 0.0 和 1.0 之间,1 表示最好,0 表示最差。
- recall: 提供了分类器能够为该类找到所有阳性案例的程度的度量。
- f1-score: 精度和召回率的加权调和*均值,生成 1.0(好)和 0.0(差)之间的值。
- ****支持:这是数据集中该类的实例总数。
为了查看分类报告,我们可以调用下面的代码,并将y_test和y_pred传递给classification_report函数。
print(classification_report(y_test, y_pred))

在 Scikit-learn 中使用 k 最*邻进行岩性预测的分类报告。图片由作者提供。
如果我们仔细观察结果,我们会发现我们正在处理一个不*衡的数据集。我们可以看到页岩、砂岩和石灰岩类占主导地位,因此具有相对较高的精确度和召回分数。而石盐、凝灰岩和白云石的精度和召回率相对较低。
在这一点上,我会考虑回到原始数据集,并确定我可以处理这种不*衡的方法。这样做应该会大大提高模型性能。
混淆矩阵
我们可以使用另一个工具来查看我们的模型表现如何,这就是混淆矩阵。此工具总结了我们的分类模型在对每个类别进行预测时的表现。
生成的混淆矩阵有两个轴。一个轴包含模型预测的类,另一个轴包含实际的类标签。
我们可以在 Python 中生成两个版本。第一个是一个简单的混乱矩阵的打印读数,可能很难阅读或向他人展示。第二个是使用 seaborn 生成的热图版本
# Simple Printed Confusion Matrix
cf_matrix = confusion_matrix(y_test, y_pred)
print(cf_matrix)
# Graphical version using seaborn and matplotlib
# Prepare the labels for the axes
labels = ['Shale', 'Sandstone', 'Sandstone/Shale',
'Limestone', 'Tuff', 'Marl', 'Anhydrite',
'Dolomite', 'Chalk', 'Coal', 'Halite']
labels.sort()
# Setup the figure
fig = plt.figure(figsize=(10,10))
ax = sns.heatmap(cf_matrix, annot=True, cmap='Reds', fmt='.0f',
xticklabels=labels,
yticklabels = labels)
ax.set_title('Seaborn Confusion Matrix with labels\n\n')
ax.set_xlabel('\nPredicted Values')
ax.set_ylabel('Actual Values ');
当我们运行上面的代码时,我们得到了下面的打印表格和图表。

测井测量岩性 kNN 分类预测结果的混淆矩阵。图片由作者提供。
由此产生的混淆矩阵为我们提供了模型正确和错误预测的类别的指示。我们可以开始识别模型可能预测错误岩性的任何模式。
例如,如果我们看看石灰石类。我们可以看到 2613 个点被正确预测,然而,185 个点被预测为白垩,135 个点被预测为泥灰。这两种岩性都具有方解石性质,并具有与石灰石相似的性质。因此,我们可以回过头来看看我们的功能,以确定是否需要其他功能,或者是否需要删除一些功能。
摘要
k-最*邻算法是一种功能强大但易于理解的监督机器学习算法,可应用于基于分类的问题,尤其是在地球科学领域。
本教程展示了我们如何进行一系列预先分类的测井测量,并对新数据进行预测。然而,在预处理数据和处理不*衡数据集时应该小心,这在地下应用中是常见的。
感谢阅读。在你走之前,你一定要订阅我的内容,把我的文章放到你的收件箱里。 你可以在这里做!或者,您也可以* 注册我的简讯 免费将更多内容直接发送到您的收件箱。***
其次,你可以通过注册成为会员来获得完整的媒介体验,并支持我和成千上万的其他作家。每月只需花费你 5 美元,你就可以接触到所有精彩的媒体文章,也有机会通过写作赚钱。如果你用 我的链接 , 报名,你直接用你的一部分费用支持我,不会多花你多少钱。如果你这样做了,非常感谢你的支持!
Excel 中的 k-最*邻
原文:https://towardsdatascience.com/k-nearest-neighbors-in-excel-76edd4102a5b
在一系列文章中,我使用 Excel/Google Sheet 来实现基本的机器学习算法,以便我们可以理解其中的基本原理:
- Excel 中带梯度下降的线性回归 /Google Sheet
- Excel 中的梯度下降逻辑回归 /Google Sheet
- Excel 中的神经网络从无到有
- K-Excel 中从头开始的意思
在这篇文章中,我将解释 K *邻是如何工作的。为了全面理解机器学习算法是如何工作的,你可以阅读我关于监督学习算法的完整概述的文章。
与大多数机器学习算法相反,没有针对 KNN 的训练。更准确地说,如果我们区分所有机器学习算法的以下步骤:
- 训练的算法,找到模型的参数
- 超参数调整通过网格搜索找到最佳超参数,避免过拟合和欠拟合
那么对于 KNN,只有超参数调整这一步,包括找到最佳超参数 k 。
对于模型部分,KNN 的原理是利用整个数据集寻找 k 个最*邻。欧几里得距离经常被使用。预测阶段包括
- 对于回归,计算所选邻居的目标变量的*均值
- 对于分类,计算所选最*邻的每一类目标变量的比例
让我们开始在 Excel 中实现吧!我们将演示如何用 KNN 解决回归问题。作为练习,您可以修改分类任务的文件。

作者图片
从零开始为 ML 的谷歌表
如果你想得到谷歌表,请在 Ko-fi 上支持我。
你可以得到我创建的所有谷歌表单(梯度下降的线性回归,逻辑回归,神经网络,KNN,k-means,等等)。)和下面的链接。
https://ko-fi.com/s/4ddca6dff1
资料组
我们可以只使用一个特征变量 x,一个连续的目标变量 y。然后我们可以绘制 x 为横轴,y 为纵轴。

作者图片
我们可以使用两个特征变量 x1 和 x2。那么我们将无法绘制 y。使用 Python 或 R 包,我们将创建 3D 图,或将 y 的值表示为点的大小,但使用 Excel,我不知道如何做到这一点。但正如我们将会看到的,这仍然很有趣。

作者图片
新观测的预测
对 KNN 来说,没有可以训练的模式。对于超参数 k,我们将使用 k=3,因为数据集非常小。
预测阶段由以下步骤组成:
- 对于一个给定的新观察值,计算该新观察值与训练数据集中所有观察值之间的距离。
- 找出距离最短的 k 个观察值。在 Excel 中,我们将使用筛选器对训练数据集进行手动排序
- 计算预测值。对于回归任务,我们计算目标变量的*均值。
一个特征变量
对于一个特征变量,欧几里德距离变成新观测值和其他观测值之间的差值的绝对值。
在工作表 algo1 中,您可以更改新观察值,并使用距离列 C 的过滤器对数据集进行升序排序,3 个最*邻将被自动绘制。
在剧情中,
- 浅蓝色的点代表数据集
- 黄色圆点用红色表示新观测值的 3 个最*邻
- 红点表示具有预测 y 值的新观测值

作者图片
在工作表 algo1f 中,我绘制了新观测值的预测,范围从 1 到 17。有了编程语言,我们可以在一个循环中很容易地做到这一点,对于大量的新观察,所以表示可以更密集。
使用 Excel,我手动重复了以下步骤:
- 输入 x 的值
- 对距离列进行排序
- 复制粘贴预测

作者图片
两个特征变量
当我们有两个特征变量时,由于我无法使用 Excel 进行 3D 绘图,因此我将只表示一个给定新观测值的相邻要素。

作者图片
对于一个二元分类任务,我们可以用背景上的某种颜色(由许多点组成)来表示概率。我们在 sklearn 的分类器比较 中有一个图解。

作者编辑自 sklearn 的分类器对比
结论
KNN 是最简单的机器学习,而且非常容易理解。使用 Excel,我们可以演示它如何为业务人员工作,以便他们可以对这种方法以及它是否适合于业务问题有良好的直觉。
邻居的概念很容易理解,在某些情况下,他们可以是地理距离上的真实邻居。因此它可用于*滑空间数据。
在下图中,您可以看到原始值适用于巴黎市的小区域。所以从一个四分之一到它相邻的四分之一,变量可能是不连续的。
在这种情况下,KNN 可以这样使用:对于巴黎的每个地址,我们找到其最*的邻居并计算*均值。这将导致该目标变量的更加*滑的版本。

作者图片
如果你想获得一个监督机器学习算法的完整概述,你可以我的文章:
我写关于机器学习算法的文章,请通过下面的链接关注我,并完全访问我的文章:
https://medium.com/@angela.shi/membership
10 分钟内 k *邻、朴素贝叶斯和决策树
机器学习
通过实践案例研究演练

在 Unsplash 上由 Fabrice Villard 拍摄的照片
Un 像线性模型和 SVM(见 Part 1 ),一些机器学习模型真的很复杂,要从它们的数学公式中学习。幸运的是,通过在一个小的虚拟数据集上执行一步一步的过程,可以理解它们。这样,你可以在没有“数学瓶颈”的情况下发现机器学习模型。
在 第一部分 之后你会在这个故事里学到更多的三个模型:K-最*邻(KNN)、朴素贝叶斯、决策树。
**Table of contents****·** [**K-Nearest Neighbors (KNN)**](#164f)
∘ [Classification](#fd00)
∘ [Case study](#a6da)
∘ [Regression](#c483)
**·** [**Naive Bayes**](#c71a)
∘ [Bayes’ Theorem](#2c11)
∘ [Case study](#fb64)
∘ [Laplace smoothing](#552e)
**·** [**Decision Tree**](#f091)
∘ [Objective](#d971)
∘ [Impurity function](#909b)
∘ [Case study](#ea8f)
**·** [**Conclusion**](#4bbe)
k-最*邻(KNN)
分类
KNN 是一个非一般化机器学习模型,因为它只是“记住”所有的训练数据。它并不试图构建一个通用的内部模型,而是简单地存储训练数据的实例。KNN 没有真正的训练阶段。所以,我们直接去测试吧。
给定来自测试数据的新观察值 x ,KNN 将从距离最接* x 的列车数据中找到预定义数量的观察值,并根据这些预测类别。最*列车观察值(也称为最*邻居)的数量是用户定义的常数,并成为模型的超参数 k 。一般来说,距离可以是任何度量单位,但标准欧几里得距离是最常见的选择。
从 x 的最*邻居的简单多数投票计算分类,即 x 被分配在 x 的最*邻居中具有最多代表的类别。通过这种方法,KNN 支持多类分类。
好,现在让我们把手弄脏。
个案研究
假设你有以下数据。您将把 KNN 应用到有 10 个观测值的数据集中,其中 7 个用于训练,另外 3 个用于测试。

KNN 案例研究数据集|图片由作者提供
对于 k = 3,KNN 将考虑三个最*的邻居,其工作原理如下所示。

(a) 绘制所有训练和测试数据| (b) 对于每个测试数据,KNN 在训练数据中找到它的 k 个最*邻居| (c) 在每 k 个最*邻居中,计算有多少属于哪个类| (d) 给每个测试数据分配最主要的类|图片由作者
这很容易理解。问题是:如果有一个测试观测,它的最*邻有两个同样占优的类,你该怎么办?你把哪一类分配给测试观察?
当然,如果只有两个类,可以通过选择奇数的 k 来完全避免这个问题。但是,如果有两个以上的类,或者如果您坚持使用偶数 k,您可以为每个最*的邻居分配一个权重,这样最*的邻居对拟合的贡献更大。分配的权重应该与测试观察距离的倒数成比例。这样,两个同样占主导地位的阶级几乎不存在了。
值 k 的最佳选择高度依赖于数据:通常,较大的 k 会抑制噪声的影响,但会使分类边界不太明显。
回归
对于回归问题,分配给测试观测值的值是根据其最*邻值的*均值计算的。一个用 k = 5 进行回归的 KNN 的例子看起来像这样。

KNN 为回归|图片由作者
尽管简单,KNN 已经在大量的分类和回归问题上取得了成功。作为一种非参数方法,它通常在判定边界非常不规则的分类情况下是成功的。
KNN 的一个缺点是它非常依赖于距离,因此您需要非常小心地预处理分类特征,找到一个适用于您的数据的 距离函数 。出于同样的原因,如果某些特征的比例明显大于其他特征,您也需要 标准化 您的数据。
朴素贝叶斯
贝叶斯定理
先来个脑筋急转弯:
医生知道 50%的情况下脑膜炎会导致颈部僵硬。任何患者患脑膜炎的概率为 1/50000,任何患者患落枕的概率为 1/20。如果患者颈部僵硬,他/她患脑膜炎的可能性有多大?

照片由亚历山大·巴甫洛夫·波德瓦尔尼在 Unsplash 上拍摄
这个问题不仅仅是文字游戏。暂时抛开原来的问题,让我们问另一个问题:一个病人患脑膜炎和落枕的概率有多大?
我们可以一步一步来计算。我们知道病人患脑膜炎的概率是 1/50000。并且知道他/她患有脑膜炎,有 50%的可能性患者也有落枕。所以,一个患者得脑膜炎和落枕的概率是 50% × 1/50000 = 1/100000。
脑膜炎和落枕没有先后顺序,可以翻转逻辑。我们知道患者落枕的概率是 1/20。并且知道他/她有落枕,病人也有可能患有脑膜炎。所以,一个病人得脑膜炎和落枕的概率是 x × 1/20。
原问题问的是 x 的值。但是从前面的论证我们知道 x × 1/20 = 1/100000。因此,我们发现,如果患者颈部僵硬,他/她患脑膜炎的概率为 x = 0.0002。
用简单的数学公式表达这个逻辑就简单多了。让
- P 是病人患脑膜炎的概率,而
- P 是患者落枕的概率。
我们以上所做的实际上只是

这就是所谓的 贝叶斯定理 。在机器学习设置中,你可以用你想要预测的目标变量 y 代替 M ,用特征向量 x₁、…、xₙ 代替 S 。这个定理变成了

计算 P ( x₁,x₂,…,xₙ | y )很难。所以,你做了一个天真的假设,在给定目标变量的值的情况下,每对特征之间存在条件独立性。因此对于每个,i = 1,2,…,n* ,*

朴素贝叶斯模型变成了

由于 P ( x₁、x₂、…、xₙ )是常数,所以可以简化如下进行预测 ŷ 。

个案研究
假设你有以下逃税数据。你的任务是根据应税收入(以美元计)和婚姻状况等特征,预测一个人是否愿意纳税。
您将使用 10 个观察值将朴素贝叶斯拟合到训练数据中,然后根据测试数据预测一个看不见的观察值。

朴素贝叶斯案例研究数据集|图片作者作者
马上,您会看到有两个分类特征(退款和婚姻状况)和一个数字特征(应税收入)。当然,计数概率会因人而异。
为方便起见,我们将退款、婚姻状况和应税收入分别称为 x₁、x₂和 x₃ 。你可能还想用 1000 除 x₃,这样你就不会处理太大或太小的数字了。另外,通过 y 调用规避。
朴素贝叶斯做了很多代数运算。对于分类特征,您有

计算退款的条件概率给定逃避|图片作者作者

计算婚姻状况的条件概率
为了方便起见,我们对上面的训练数据进行了分类和颜色编码。
对于数字特征,事情变得有点激动人心。您需要为每个特性假设一个基础分布。假设你假设 x₃是正态分布的,那么

其中 μ 和 σ 分别代表均值和方差。让我们根据试验数据计算出 x₃ = 80。

给定逃避|图片由作者计算应税收入的条件概率
现在,您已经准备好根据测试观察进行预测了。
- y =否

- y =是

既然 5.39 × 10⁻⁴ > 0,那么预测 ŷ =否
拉普拉斯*滑
在这个案例研究中,对于 y =是的,产品链中有一个术语的值为 0。这是不好的,因为在产品链中可能还有另一个大到足以推翻 y = No 的术语,但是当乘以 0 时,类 y = Yes 总是输。
为了减轻这一点,可以使用 拉普拉斯*滑 。规定

代替像以前一样计算分类概率

将拉普拉斯*滑应用为

让我们最后一次预测测试观察,现在*滑。
- y =否

- y =是

既然 5.87 × 10⁻⁴ > 3.24× 10⁻⁴,那么朴素贝叶斯还是预测 ŷ =否
尽管朴素贝叶斯的假设明显过于简化,但它在许多现实世界的情况下工作得相当好,著名的是文本分类。即使具有强依赖性,朴素贝叶斯仍然工作得很好,即当那些依赖性相互抵消时,对分类没有影响。
决策图表
目标
决策树的目标是创建一个模型,通过学习从特征中推断出的简单决策规则来预测目标变量的值。一棵树可以被看作是一个分段常数*似。

从未有过更真实的迷因|图片作者作者
决策树划分特征空间,使得具有相同类别或相似目标值的观察值被分组在一起。由于对树的每个节点递归地进行划分,所以通过理解决策树在节点上的工作方式就足以理解决策树的工作方式。
所以,考虑一个节点 m 。让节点 m 处的列车数据由具有 n 个观测值的 Q 表示。对于每个候选分裂 θ (这是一个特征),将数据划分为左右两个子集, Qₗ ( θ )和 Qᵣ ( θ ),观测数分别为 nₗ 和 nᵣ 。
然后,使用杂质函数 H 计算节点 m 的候选分裂的质量,其选择取决于被解决的任务(分类或回归)。最佳分割 θ 是基于 nₗ 和 nᵣ 最小化加权杂质的分割,即*

对子集 Qₗ ( θ )和 Qᵣ ( θ* )进行递归,直到达到最大允许深度, n 小于某个正阈值,或者 n = 1。*
现在唯一剩下的就是定义杂质函数 H 用于分类和回归。
杂质函数
如果目标变量 y 是取值为 k ∈ {0,1,…,K1 }的分类结果,设 I 为 指示函数 和

成为节点 m 中类 k 观测值的比例。常见的杂质分类方法如下。

如果目标变量 y 是回归的连续结果,让

是节点 m 中所有目标变量的*均值,中位数( y )是它们的中位数。常见的回归杂质测量如下。

如果您的目标变量是计数或频率(每单位的计数),泊松偏差可能是一个不错的选择。
要有很好的理解感,你会看到为什么这些杂质函数的定义有意义,特别是对于分类。假设目标变量 y 有两个类,节点 m 中类 1 的比例为 p₁ = p 对于某些 0 ≤ p ≤ 1。然后p₀= 1p,您可以为每个 p 绘制如下杂质函数。

二元分类的杂质函数|图片作者作者
杂质函数的思想是当决策树的划分导致最纯粹的分裂时,使其值最小。另一方面,如果分裂不纯(许多类仍然混在一个分裂中),那么杂质函数应该给出一个高值。
这正是上图中发生的情况。当 p = 0 或 p = 1 时,出现最纯粹的分裂,此时 H 的值为零。当在最坏情况下分配不纯时 p = 0.5,则 H 给出其最大值。
个案研究
我们用之前的逃税数据。您将使用 10 个观察值将决策树拟合到训练数据中,然后根据测试数据预测一个看不见的观察值。

决策树案例研究数据集|图片作者作者
您将使用 Gini 杂质函数,并将树的最大深度设置为 2,以避免过度拟合(两者都是模型的超参数)。由于在训练数据中有 7 个“否”观察值和 3 个“是”观察值,基尼系数的原始值为
**
决策树的根|图片作者作者
您将使用候选分割 θ 分割这些观察值,并找到最佳分割。
- **θ =退款**。那么,加权基尼系数的值为

- **θ =婚姻状况**。那么,有三种可能的拆分:

- **θ =应纳税所得额**。由于这是一个连续的特征,你需要确定几个分割位置(在我们的例子中是随机的)并计算每个分割的基尼系数。我们看到最低的加权基尼系数是在分离位置 97 获得的。

计算应税收入的基尼系数|图片作者作者
在所有这些候选分割中,最好的分割给出了最低的加权基尼系数,它要么是【θ】=婚姻状况*({已婚}、{单身、离婚})分割,要么是 θ =分割位置为 97 的应税收入。为了简单起见,我们选择前者。决策树如下图所示。*

深度为 1 的决策树|图片作者作者
左边的节点已经是纯规避=否,那么现在只需要拆分右边的节点。对其余的列车观测数据进行与之前相同的操作。

决策树右侧节点的数据集|图片作者作者
- **θ =退款**。加权基尼系数为 0.25。
- **θ =婚姻状况**。只剩下两类,所以很容易计算:加权基尼系数是 0.50。
- **θ =应纳税所得额**。

计算应税收入的基尼系数|图片作者作者
我们将选择 θ =退款作为最佳选择,因为它在加权基尼系数最低的候选分割中非常简单。决策树如下图所示。

深度为 2 的决策树|图片作者作者
您将在这里停止,因为树的最大深度已经达到(设置为 2)。
要进行预测,只需使用测试数据遍历树即可。在你的测试数据中,婚姻状况=单身,退款=是,所以预测为逃避=否如下图橙色决策路径所示。

对测试数据|图片进行决策树预测作者
决策树因为可以可视化,所以 理解解释* 很简单。需要 少量数据准备 :不需要数据归一化或虚拟变量。就像 KNN 和朴素贝叶斯一样,决策树能够处理 多类 问题。*
然而,决策树往往会对具有大量特征的 数据进行过度拟合。诸如修剪、设置叶节点所需的最小样本数或设置树的最大深度之类的机制对于避免这个问题是必要的。也可以考虑事先进行 降维 。
决策树也可能不稳定,因为数据的微小变化可能导致生成完全不同的树。这个问题通过在 集合 中使用决策树得以缓解。
结论

你已经非常详细地学习了三个最基本的机器学习模型:K-最*邻、朴素贝叶斯和决策树。现在,您不仅可以使用已建立的库来构建它,还可以自信地知道它们是如何从内到外工作的,使用它们的最佳实践,以及如何提高它们的性能。
恭喜你。

🔥你好!如果你喜欢这个故事,想支持我这个作家,可以考虑 成为会员 。每月只需 5 美元,你就可以无限制地阅读媒体上的所有报道。如果你注册使用我的链接,我会赚一小笔佣金。
🔖想了解更多经典机器学习模型的工作原理,以及它们是如何优化参数的?或者 MLOps 大型项目的例子?有史以来最优秀的文章呢?继续阅读:**
*
从零开始的机器学习
*View list8 stories

**
高级优化方法
*View list7 stories

**
MLOps 大型项目
*View list6 stories

**
我最好的故事
*View list24 stories

**
R 中的数据科学
View list7 stories


[1]《sci kit-learn:Python 中的机器学习》,佩德雷戈萨,等著,JMLR 12,第 2825–2830 页,2011 年。
Kaggle hacking:针对隐藏数据集验证一个简单的假设

马体·米罗什尼琴科摄于 Pexels 。
这篇文章将分享如何创造性地调试你的 Kaggle 提交,特别是提交格式,以及我如何对一个机密/受保护的数据集进行简单的假设测试。
Kaggle 是一个很棒的地方,建立了一个奇妙的机器学习(ML)实践者和爱好者社区,他们愿意分享他们的学习和经验。我很惊讶这个*台是如何随着时间的推移而增长的,而且似乎有永无止境的新竞争。不幸的是,我也注意到一些竞争,甚至是财务价格,提供了纯粹或部分误导的描述和正确提交格式的解释;看两个例子这里或者这里。
我向所有酷的竞争对手道歉,没有任何冒犯的意思,把这篇文章看作是对可能的*台漏洞的学习。
关于卡格尔将军的快速提交
Kaggle 主办了几种类型的比赛,但是为了简单起见,让我们只假设数据共享/访问的观点。所以有完全开放的数据集和隐藏测试分数的数据集。
完全开放的竞赛提供训练和测试数据集,如果需要,用户可以将它们下载到本地机器上。他们还允许通过上传提交 CSV 文件来提交。我天真的猜测是,数据集是众所周知的(如泰坦尼克号),或者没有期望任何用户/竞争对手将能够执行手动预测。
另一个故事是激烈的竞争,组织者通过不共享测试数据集来加强正确性。他们将它暴露在一个受限/隔离的环境中:用户必须提交一个能够离线处理测试数据并提供预测的内核。使用离线内核,你不能在任何地方上传/分享数据,而且所有额外连接的数据集也是只读的。这太棒了,我喜欢!
就可用性而言,最好至少提供测试数据集的某个最小子集(例如,10k 中的 5 个样本图像),这不会影响提交分数,但仍能给出数据的含义,并允许用户调试其提交过程。Kaggle 甚至要求正确/有效的提交才能被允许/有资格使用您的内核进行提交(内核需要离线运行而不出错,并产生一个提交文件)。然后在评估阶段,虚拟测试数据集与实际测试数据集进行交换,并最终提交用于评分。
竞争对手的可用性问题
让我分享一下我在开发和/或调试有效提交管道时遇到的两种挑战:
- 测试数据和样品提交完全缺失,描述具有误导性(如评估页面上呈现的扫描图像缺乏索引)。
- 提供的样本测试数据不映射提供的样本提交(例如,提供的样本提交引用不存在的文件,并且测试数据集包括另一个文件)。
所有这些导致用户需要花费数小时或数天的调试时间来找出最小的有效解决方案,而不是从一开始就关注解决方案(参见示例投诉此处和此处)。

Andrea Piacquadio 摄于 Pexels 。
黑客 Kaggle
我参加了一个比赛,阅读了数据和评估说明,并准备了一个最小的提交来理解格式。但是即使第四次提交也出错了,尽管我确信它们都是正确的。为什么它不起作用?!我花了一整天的时间阅读讨论和检查一些产生有效提交的内核,以提炼提交格式…
这是相当棘手的,其他内核很长,包括许多其他的训练步骤,对我的最小提交是不需要的!
准备舞台
然后我想,当我需要对有效内核进行逆向工程时,为什么不尝试直接用 Kaggle 进行调试。在这个特殊的比赛中,组织者提供了sample_submission.csv,它实际上是一个空的提交模板,后来只在评估期间填充了有效数据,所以不可见!
我准备了一个虚拟内核,在执行结束时,它会将这个sample_submission.csv复制为submission.csv,给出 0 分,但是它通过了所有检查!此外,我必须区分在公共模式和评估模式下运行——在公共模式下,sample_submission.csv是一个空表,所以添加if not df.empty会导致流程只在评估阶段被调用。
https://www.kaggle.com/code/jirkaborovec/tract-segm-fake-predictions
虽然内核通常几乎是空的,但我可以将一个或多个资产注入到真实测试数据集存在时执行的评估子过程中。瞧啊。当assert 不正确时的简单信号——内核在提交给竞争对手后会出错。

用户用断言发信号。
实验
在特定的比赛中,我对提交索引有些怀疑。这是在竞赛中提供的提交样本:
id,class,predicted
1,large_bowel,1 1 5 1
1,small_bowel,1 1
1,stomach,1 1
2,large_bowel,1 5 2 17
etc.
但图像(如描述中所述)应具有与列车图像相同的格式,因此数据被组织在文件夹<case>/<case_day>/<scans>中,特定图像名称为slice_height_width_y-space_x-space.png。因此,我通过在test文件夹中列出所有 PNG 图像并乘以三个预测标签来创建我的提交:
id,class,predicted
case36_day14_slice_0006,large_bowel,
case36_day14_slice_0006,small_bowel,
case36_day14_slice_0006,stomach,
case36_day14_slice_0082,large_bowel,
case36_day14_slice_0082,small_bowel,
…
我如何能得到图像名称配对和一个特定的索引?
陈述假设
我用tract🩻segm: fake🏴☠️predictions创建了一组实验,其中df_ssub直接从sample_submission.csv文件加载,而df_pred是通过在test文件夹中列出所有 PNG 图像创建的…
如前所述,我只能从评估中得到二元答案,所以我不能一开始就问太复杂的问题。所以我简化了关于df_ssub和df_pred之间关系的问题:
- 它们的大小相同吗(例如,如果
df_ssub是df_pred的子集)? - 如果长度相同,所有的键/id 是否都出现在两个表上?
结果
从问题 1)开始,我通过改变断言条件连续提交了三次:
assert len(df_pred) > len(df_ssub)> >错误assert len(df_pred) < len(df_ssub)> >错误assert len(df_pred) == len(df_ssub)> >好吧
在下一轮中,我通过检查df_ssub中没有会在df_pred中丢失的键(注意,这些键由两列组成:id和class):

回应是肯定的——提交通过,正如您在我的问题提交的完整序列中看到的:

用户提交
我的实验结论是,提交的样本包括了所有出现在test文件夹中的图片,但是它们的排序方式必须和sample_submission.csv一样。
结论
我分享了纯粹竞争的描述是如何引导我开始第一次黑客行为的...特别是,我描述了一个简单的过程,通过在正常流和评估流之间分流内核执行来验证针对受保护数据集提出的二元假设。
值得注意的是,它不会导致数据集或其任何部分的泄露,因为每个用户每天提交的数量有限。
敬请关注,关注我了解更多!
Kangas:大规模可视化多媒体数据
原文:https://towardsdatascience.com/kangas-visualize-multimedia-data-at-scale-db981b5e8d23
用于探索性数据分析的开源库
在机器学习中,我们认为胶水代码是必要的,但很大程度上是良性的。即使是对初学者最友好的计算机视觉教程也会包含一些笨拙的自定义 renderImage()方法,您只需使用它来显示图像。

我合作过的每个数据科学团队都有一组这些“定制”的实用程序脚本,尤其是在探索性数据分析等更特殊的流程中。在很大程度上,这些小技巧奏效了——直到它们不奏效。
同样的模式不可避免地会出现。项目从“看看这是否可行”发展到“将它部署到生产中”,不断增加的复杂性导致了复杂的困难:
- 我有一个包含 250 万张图片的数据集,我编写了一个 showImage()方法。我如何在所有 250 万张图片上运行它,而不使这台笔记本电脑崩溃?
- 我有一个部署的对象检测模型,我可以从我拼凑的仪表板上看到它的整体性能下降了。如何将单个预测可视化为带有边框和标签的图像?
- 我们的新数据集拥有数量惊人的要素。我们有一个脚本来生成整个数据集的统计摘要,但我如何执行查询(分组、排序、过滤等。)对数据?
突然,你的效用函数变成了效用软件。后端团队构建了一个使用标签和变换来渲染图像的服务,但是它是不稳定的,并且只适用于特定的格式。您团队中的某个人构建了一个“管道”,这实际上只是一个善意的 Python 脚本的拼凑,它对您的数据做出了一些非常严格的假设。而现在,你花了几个小时为你的实用软件写实用函数,你渴望概率论
在处理这个问题多年后,我和我的同事 Doug Blank 放弃了寻找一个完美工具的努力,决定自己开发一个。快进几个月(和几个主要的重构),我们终于准备好发布我们新的开源库, Kangas ,用于它的初始测试版。

来源: Kangas 知识库
介绍 Kangas V1:用于计算机视觉的开源 EDA
在 Kangas 的早期(就像没有“roo”的“袋鼠”,原因我们并不完全确定),我们已经着手解决探索性数据分析中的三个具体问题:
1。大型数据集很难处理。
虽然 pandas 是一个很棒的工具,但它将数据帧存储在内存中,随着数据集的增长,性能会下降。实现第三方工具,比如 Dask 是在生产前构建复杂管道的一个好选择,但是会减慢你的研究速度。
这是我们开始研究袋鼠的地方。我们想“如果我们不把一个类似数据帧的对象存储在内存中,而是把它存储在一个实际的数据库中,会怎么样?”然后转化为“如果数据框是真实的数据库会怎样?”
Kangas 的基类是 DataGrid,您可以使用熟悉的 Python 语法来定义它:
from kangas import DataGrid
dg = DataGrid(name="Images", columns=["Image", "Score"])
dg.append([image_1, score_1])
dg.show()
注意:实际上有几种不同的方式来构建数据网格。更多, 见此处
Kangas DataGrid 是一个实际的 SQLite 数据库,使它能够存储大量数据并快速执行复杂的查询。它还允许保存和分发数据网格,甚至远程服务。
2。可视化数据需要几个小时。
要浏览 CV 数据集,您需要查看图像本身,以及相关的元数据和变换。您需要能够跨视图比较图像,绘制聚合统计图表,理想情况下,在单个 UI 中完成所有这些。您典型的库的混杂导致的输出最好被描述为“功能性的”,而不是美观的。
Kangas 中的可视化需要简单、快速和流畅。我们没有依赖 Python 库,而是将 Kangas UI 构建为一个实际的 web 应用程序。服务器端呈现(使用 React 服务器组件)允许 Kangas 快速呈现可视化,同时执行各种查询,包括过滤、排序、分组和重新排序列。

来源: Kangas 演示
最重要的是,Kangas 为标签、分数和边界框等内容提供了内置的元数据解析:

来源: Kangas 演示
3。EDA 解决方案很少具有互操作性。
EDA 的挑战之一是数据通常是杂乱的和不可预测的。您同事对工具的“古怪”偏好经常以最不直观的方式改变您的数据。在理想的情况下,你不需要改变你的工作流程来应对这种可变性——它会正常工作。为了在 Kangas 实现这一点,我们必须做几件事。
首先,我们希望确保任何类型的数据都可以加载到 Kangas 中。为此,Kangas 很大程度上并不介意在数据网格中存储什么。Kangas 另外提供了几个构造器方法,用于从不同的源获取数据,包括 pandas 数据帧、CSV 文件和现有的数据网格。
import kangas as kg
# Load an existing DataGrid
dg = kg.read_datagrid("https://github.com/caleb-kaiser/kangas_examples/raw/master/coco-500.datagrid")
# Build a DataGrid from a CSV
dg = kg.read_csv("/path/to/your.csv")
# Build a DataGrid from a Pandas DataFrame
dg = kg.read_dataframe(your_dataframe)
# Construct a DataGrid manually
dg = kg.DataGrid(name="Example 1", columns=["Category", "Loss", "Fitness", "Timestamp"])
其次,我们希望确保 Kangas 可以在任何环境中运行,而无需进行重大设置。一旦你运行了“pip install kangas ”,你就可以在你的本地机器上,从笔记本环境中,或者甚至部署在它自己的服务器上,作为一个独立的应用程序来运行它(就像我们在kangas.comet.com所做的那样)。)
最后,Kangas 是开源的这一事实意味着它在定义上是可互操作的。如果您的特殊需求如此具体和极端,以至于 Kangas 路线图上的任何东西都无法满足它们,您可以分叉回购并实现您需要的任何东西。如果你这样做了,请让我们知道!我们很想看看。
Kangas 的路线图是什么?
对袋鼠来说还为时尚早。目前,只有少数测试用户在测试它,大部分代码库仍在积极开发中。记住这一点,接下来会发生什么很大程度上取决于你。Kangas 现在是,将来也永远是一个免费的开源项目,在接下来的几个月和几年里,我们选择优先考虑什么将取决于社区成员最想要什么。
如果你有多余的时间,并且迫切需要更好的探索性数据分析,可以考虑去 Kangas repo 看看,并尝试一下。我们对所有类型的社区贡献都是开放的,如果你关注了这个库,每当有新的主要版本发布时,你都会得到更新。
10 分钟后 Kedro 成为数据管道
原文:https://towardsdatascience.com/kedro-as-a-data-pipeline-in-10-minutes-21c1a7c6bbb
开发工作流框架变得简单——用 Python 示例解释

罗曼·达维德科在 Unsplash 上拍摄的照片
在数据科学项目中,各种编码组件可以被认为是一个数据流——从源到特征工程、建模、评估等的数据流。训练、评估和评分管道使这一流程变得更加复杂,因为每个管道的流程可能有很大的不同。
Kedro 是一个 Python 框架,它帮助将代码组织成模块化的数据管道。Kedro 允许可复制的和容易的(一行命令!)运行不同的流水线,甚至临时重新运行一小部分流水线。本文将涉及 Kedro 和 Python 示例中使用的组件和术语,以及如何设置、配置和运行 Kedro 管道。
更新 :本文是系列文章的一部分。查看其他“10 分钟内”话题 此处 !
目录
Kedro 概述
Kedro 是麦肯锡开发的第一个开源软件工具,最*捐赠给了 Linux 基金会。它是一个 Python 框架,用于创建可复制、可维护和模块化的代码。
Kedro 将软件工程的最佳实践与数据科学世界结合起来
该组织有几个组成部分,即
- 节点:函数包装器;将函数的输入、函数本身和函数输出包装在一起(定义 应该运行什么 代码)
- 管道:将节点连接在一起;解析依赖关系并确定函数的执行顺序(定义代码应该运行的顺序)
- DataCatalog :数据的包装器;将节点中指定的输入和输出名称链接到文件路径
- Runner :决定管道如何运行的对象,例如顺序或并行(定义 代码应该如何运行
Kedro 设置
Kedro 可以作为 Python 包安装,并用以下命令进行实例化,
*pip install kedro
kedro new*
请注意,此设置只需运行一次。该程序将提示您输入项目名称,并在您的存储库中创建一个文件夹结构。
要从现有的 Kedro 项目实例化,我们可以使用命令kedro new --starter=https://github.com/your-repo.git来代替。
现在,让我们编写一些管道代码!

Jesse Echevarria 在 Unsplash 上拍摄的照片
Kedro 文件夹结构
实例化 Kedro 后,几个文件夹和文件被添加到项目中。该项目应该具有以下文件夹结构,
*project-name
├── conf (configuration files)
│ ├── base
│ └── local
├── data (project data)
│ ├── 01_raw
│ ├── 02_intermediate
│ ├── 03_primary
│ ├── 04_feature
│ ├── 05_model_input
│ ├── 06_models
│ ├── 07_model_output
│ └── 08_reporting
├── docs (project documentation)
│ └── source
├── logs (project output logs)
├── notebooks (project jupyter notebooks)
└── src (project source code)
├── project_name
│ └── pipelines
└── tests
└── pipelines*
会议
conf文件夹用于存储配置文件。base子文件夹存储共享的项目相关配置,而local子文件夹存储用户相关或机密的配置。在local子文件夹中的配置不应该被提交到代码库中,这个设置在.gitignore文件中处理。
在base子文件夹中为 数据目录 、日志记录和参数创建示例配置文件,而local子文件夹中的示例配置文件用于存储凭证。
数据
data文件夹存储在不同数据管道阶段创建的输入、中间和输出数据。与本地配置类似,data文件夹中的文件不应该提交给代码库。
由于 Kedro 是作为数据科学框架创建的,默认的文件夹结构对应于数据科学项目的各个阶段,如果需要,您可以进一步定制。
科学研究委员会
文件夹是存储大量代码的地方。它包含项目代码、 节点 和 管道 代码、 流道 代码等等。
与 Kedro 合作
高级 Kedro 工作流遵循以下顺序,
- 定义配置:指定 数据目录 和参数
- 编写函数:将项目代码包装到函数中
- 定义节点:将函数包装成节点
- 定义管道:将节点链接在一起
- 定义管道:指定管道应该如何运行
- 运行管道:读取 conf,使用 runner,运行管道!
我们将更详细地介绍每个步骤,以分解工作流程。
№1: 定义形态
数据目录 将文件名与其对应的文件类型和位置联系起来,并在conf/base/catalog.yml文件中定义。它可以这样定义,
*input_data:
type: pandas.CSVDataSet
filepath: data/01_raw/iris.csv
intermediate_data:
type: pandas.CSVDataSet
filepath: data/02_intermediate/iris_small.csv
processed_data:
type: pandas.CSVDataSet
filepath: data/03_primary/iris_processed.csv*
注意,我们不需要在data文件夹中存在所有的文件路径,只需要输入数据,因为在管道运行时会创建中间数据。
参数被输入到函数中,并在conf/base/parameters.yml文件中被定义为嵌套字典。
*input_param:
n_rows: 100*
№2:编写函数
功能是在src文件夹中定义的项目特定代码。自动处理前面步骤中定义的 DataConfig 和参数,使得函数的输入分别为 pandas 数据帧和 dictionary 类型,输出为 pandas 数据帧。我们不需要执行任何文件读取或保存操作——这抽象出了文件 I/O 代码!
在高级部分,我们将编写自定义 I/O 代码来指定自定义读取和保存方法,以帮助连接到外部数据库。
对于这个演示,让我们假设我已经用签名编写了两个函数
load_and_truncate_data(data: pd.DataFrame, input_param: Dict[str, Any]) -> pd.DataFramedrop_null_data(data: pd.DataFrame) -> pd.DataFrame
№3:定义节点
Node 对象将函数的输入和输出(在步骤 1 中)包装到函数本身(在步骤 2 中)。指定节点名称是可选的,但是节点名称有助于运行特定节点的功能,而不是运行整个管道。为了最小化导入的数量,我更喜欢在同一个文件中定义节点和管道。
第 4 项:定义管道
管道对象将几个节点链接在一起,这是在src/project_name/pipelines文件夹中定义的。流水线和节点可以这样定义,
kedro.pipeline.Pipeline(第 10 行)将多个kedro.pipeline.node对象链接在一起,每个节点将函数包装为func,输入数据和参数为inputs,输出为outputs,节点名为name。
最后,管道必须在pipeline_registry.py文件中注册。大部分代码已经给出,我们可以在 return 语句中将管道添加到字典中,
*def register_pipelines() -> Dict[str, Pipeline]:
return {
"pipeline1": pipeline([processing_pipeline()]),
"__default__": pipeline([processing_pipeline()])
}*
从管道定义中,我们观察到代码是可扩展的——我们可以很容易地定义一个训练、验证和评分管道,它调用运行不同节点序列的不同管道!
№5:定义流道
Runner 对象指定管道如何运行。默认情况下,选择SequentialRunner按顺序运行节点。还有ParallelRunner(多处理)ThreadRunner(多线程)。
例如,还可以使用自定义运行器,通过跳过节点或允许重试来处理节点故障。
№6:运行管道
可以使用代码或命令行界面(CLI)运行管道。建议使用 CLI 运行 kedro,因为它确保没有变量泄漏,而且也很简单;可以用命令kedro run运行管道!

图 1:运行管道的终端输出——作者提供的图片
在上面运行的示例中,我们观察了 Kedro 如何运行管道的序列;它在运行节点之前加载数据和参数,之后保存数据,然后报告它已经完成了任务。日志还允许我们轻松地识别数据名称、数据类型、节点名称、函数名称和函数签名。
对于运行管道的更多变化,我们可以添加选项,
*$ kedro run --pipeline=pipeline1
$ kedro run --node=load_and_truncate_data
$ kedro run --from-nodes=node1 --to_node=node2
$ kedro run --runner=ParallelRunner*
将 Kedro 与编码最佳实践相结合
添加文档组件、单元测试和林挺功能
正如在概述中提到的,Kedro 使得从软件工程中实现编码最佳实践变得容易。
命令kedro test运行项目中的所有单元测试,遵循 Python unittest或pytest框架。而命令kedro lint使用flake8、isort和black Python 包执行林挺。这些 CLI 命令无缝地工作,除了编写测试用例之外,用户不需要额外的工作。
*
为您的源代码编写文档字符串和文档也是一个很好的实践,这样技术和非技术受众都可以理解这个存储库。命令kedro build-docs自动为您的项目生成文档。这是建立在 Sphinx 框架之上的,它可以用漂亮的 HTML 格式显示文档。
高级:使用外部数据库
kedrodata catalog支持读取和保存本地或网络文件系统、Hadoop 文件系统(HDFS)、亚马逊 S3、谷歌云、Azure 和 HTTP(s)上的数据集。诸如fs_args、load_args和save_args之类的附加参数可以作为键传递给 DataCatalog 以指定与文件系统交互时的参数。
如果用户需要更多的读/写操作,可以在project_name/io文件夹中编写自定义的 I/O 类。这些定制类使用 Kedro 抽象类,比如AbstractDataSet和AbstractVersionedDataSet作为基类。实现从 CSV 文件中读写 pandas DataFrame 的示例自定义 I/O 类可以这样编写,
相应的数据目录可以这样定义,
intermediate_data:
type: project_name.io.python_csv.PythonCSV
filepath: data/02_intermediate/iris_small.csv
save_args:
index: false
这种使用 Kedro 的抽象类作为基类来编写定制 I/O 类的方式是扩展 Kedro 功能以提供定制选项的一种简洁方式——如果您想要定制 runner 函数,也可以用同样的方式完成!
希望您对 Kedro 作为一个开发工作流框架有了更好的理解。Kedro 提供了更多的功能,比如数据版本控制、数据代码转换、编写钩子、编写 CLI 来调用自定义命令,比如kedro <command>,等等。
我发现 Kedro 的美妙之处在于它在严格性和允许定制化之间的*衡
例如,Kedro 实施了一个文件夹结构,但是给用户在src文件夹中构建代码的自由。Kedro 抽象出了处理 I/O 操作的复杂性,还允许用户在需要时轻松扩展和编写定制的 I/O 类。
Kedro 如何在引擎盖下工作的复杂性也令人惊讶
处理流道、管道和配置的方式无需明确说明它们应该如何相互交互,从而改善了编码体验。然而,这种抽象不可避免地导致对组件如何运行的控制和可见性降低。
就可用性而言,Kedro 强制源代码模块化
因为节点对象引用直接起作用,所以在代码库中不会有松散的代码。这使得代码库更容易维护,并以可重现的方式运行完整、部分或不同的管道。但是,由于每个节点都要加载和保存一个数据集,这可能会占用大量空间和时间,与编写几个函数并将一个函数的输出直接传递给下一个函数而不必每次都保存和加载相比,这会导致大量 I/O 操作。这将是对检查点和可再现性的需求与对效率的需求之间的权衡。
这是演示中使用的 GitHub 库的链接。
感谢您的阅读!如果你喜欢这篇文章,请随意分享。
相关链接
Kedro 文档:https://kedro . readthedocs . io
Kedro 官方 GitHub:https://github.com/kedro-org/kedro*
亲*你的朋友,亲*你的实体
原文:https://towardsdatascience.com/keep-your-friends-close-keep-your-entities-closer-ae216b02da1e
实体关系图:不酷,不现代,但仍然非常有价值

苹果、橘子和香蕉(由 DALL-E 生成)
最纯粹的数据仓库是一面镜子。如果做得好,这个镜像反映了来自您的业务或领域的真实世界的实体和事件,并以少量的表格、指标和维度来表达它们。做得非常正确——你的内部利益相关者和其他分析师可以在这个生态系统中导航,而不用担心细微差别或错误——实现这一点从最底层的实体关系图开始(ERD)。
考虑一下贾夫勒商店
dbt 的 Jaffle Shop 演示项目是一个虚构的电子商务业务,有两个核心实体:customers和orders。每一个customer都是一个独特的个体(一个人),每一个order都反映了其中一个客户的交易。因此,我们可以说Customers : Orders是一:多,并且每个order都继承了那些进行交易的上游customers的属性。这样,我们可以很容易地回答诸如“姓Smith的客户的交易金额是多少?”
ERD 看起来如下所示:

dbt 的谢夫尔商店的示范 ERD;作者图片
这是一个有意简化的模型,有助于启动和运行 dbt——它太简单了,以至于不值得制作一个 ERD。然而,真实世界的例子会产生指数级的更高的复杂性,这也是实体关系图开始赢得市场的地方。
信息民主化和减少认知开销
在我在 YouTube 从事数据工作的这些年里,我的一位同事经常提到正确使用我们内部数据仓库所需的认知开销。这种认知开销——为了理解或理解你正在看的东西,你的大脑必须进行多少逻辑连接或跳跃——是由它试图反映的业务、实体和事件的复杂性驱动的。
在工作过程中,分析师需要浏览各种实体,如channels、content owners、videos、digital assets、copyright claims、partners、viewers、users等……看似简单的问题,如“我们上个月在德国的值班时间是多少?”如果没有连接到正确的实体,可能是不显眼的地雷(例如,我们关心viewer的国家吗?channel的申报国?content owner的国家?).这些实体之间的多对多关系和不可加总的指标使得分析更加棘手。
有少数分析师知道所有这些部分是如何协同工作的,并且能够提出正确的问题(以 SQL 查询的形式)——其他人的查询要承担风险。
回想起来,如果有一个清晰的语义层来帮助我们的分析师在这个生态系统中导航,而不用担心没有意义,我们会受益匪浅。但是我们缺少一个更简单的解决方案:一个清晰的实体关系图。

拼图块(DALL-E)
即使您有一个语义层,您也需要弄清楚如何将这些部分组合在一起——这就是为什么这种低技术,特别是非现代的数据建模方法在今天仍然如此重要。它充当创作者的强制功能,要求他们批判性地思考他们试图建模的现实世界事件和实体(以及它们之间的关系)。对于** 数据消费者来说,ERD 是一个强大的使能器,可以立即使知识民主化,否则这些知识在许多成熟的组织中是封闭的。**
ERD 奖给你的烦恼
当我在 2021 年第一次加入 Mux 时,我们从零开始构建我们的数据仓库。这意味着我们需要找出哪些核心实体和事件需要在数据仓库中表示。我花了几个小时在我们的数据湖和我们的公共 API 参考和文档中处理原始数据,试图让我的头脑了解这个生态系统——这些时间是我在这里任职期间投资回报率最高的时间。当时的结果是下面的原始文档:

Mux 核心产品实体(示范),图片由作者提供
这个演示视图捕捉了我们生态系统不到一半的复杂性(即,它没有考虑更新的产品、发票、观众会话、GTM 系统等。)—仍然将这些都写在纸上帮助我们快速了解我们的最终状态数据仓库将会是什么样子,以便反映现实,以及如何在我们的语义层中连接这些点。
有了 ERD,我们开始构建我们的 dbt 模型和 LookML,并且已经有了一组稳定的表来反映我们的核心实体和事件,同时我们继续构建更新的边界。

Mux DWH 围绕核心实体组织,图片由作者提供
结束语
总之,如果你的数据团队还没有,花时间做一个完整的实体关系图(ERD)。虽然这可能比编写 SQL 或 dash-boarding 效率低,但在这里花费的时间将是您最宝贵的时间。
让您的 ML 模型远离您的应用服务器
原文:https://towardsdatascience.com/keep-your-ml-models-out-of-your-application-servers-9fe58f9c91a5
如何将一个有前途的 ML 模型变成一个有用的 ML 驱动的产品

安德烈·泰森在 Unsplash 上拍摄的照片
部署您的机器学习(ML)模型从来都不是一项轻松的工作。当然,一些很棒的工具可以帮助你快速地与世界分享你的模型,比如 Gradio 和 Streamlit ,但是如果我们谈论的不仅仅是概念验证,你必须做出一些决定!
Gradio 和 Streamlit 在这方面做得很好,但提供的前端灵活性有限。你能做的只有这么多,你的 ML 应用程序的前端看起来就像很多其他的原型一样。此外,它们不能扩展到许多并发请求。你的模型会成为瓶颈,蚕食服务器的资源。
在这个故事中,我会给你一个很好的理由让你的模型保持在你的应用服务器中,然后告诉你哪里会失败,为什么你不应该这样做。
学习率是为那些对 AI 和 MLOps 的世界感到好奇的人准备的时事通讯。你会在每个月的第一个星期六收到我关于最新人工智能新闻和文章的更新和想法。在这里订阅!
建筑
首先,让我们看看典型的 web 应用程序是如何构造的。下图描述了参与用户与 web 应用程序交互的常见场景的实体:

一个通用的 web 服务架构——作者图片
客户端是您的用户。您的用户可以是现实生活中的人,也可以是发出请求的应用程序。服务器通常是大多数代码运行的地方。它接受请求,处理请求,并响应客户端。数据库存储应用程序的数据。它可以采取许多形式和形状,但这不是我们目前关心的问题。
在这种情况下,客户端(例如,用户或应用程序)通过网络向服务器发出请求。服务器查看数据库系统以收集必要的信息,并向用户返回响应。
我简要描述的过程是一个非常简单的场景,有许多备选方案。然而,这可能是客户端发出请求时最常见的步骤。
现在,如果您选择使用服务器中的模型体系结构,您的服务器将负责加载您的模型、处理请求的数据、运行您的模型的正向传递、转换预测,并将响应返回给用户。如果看起来工作量很大,那是因为它确实很大!
为什么要从那里开始呢?
目睹服务器不得不做的工作令人沮丧。然而,有一个非常好的理由来使用这种方法;你必须失败得快,失败得多。
当您正在构建一个新的 ML 应用程序的原型时,最好准备好一些东西:
- 拥有一个基本的用户界面,让用户更容易与应用程序交互
- 将您的应用程序放在 URL 后面,这样更容易与您的朋友和测试人员共享
像 Streamlit 和 Gradio 这样的工具可以帮你完成繁重的工作。因此,在早期,您应该尽早并经常部署,您肯定应该利用它们的特性。此外,当你开发你的模型时,你的焦点应该停留在这上面。所以,保持简单,以后再增加复杂性。
如果你在一家公司工作,重用现有的基础设施是遵循这种方法的另一个好理由。因此,您的公司可能已经建立了可靠地将代码部署到服务器的流程,您可以搭顺风车。
为什么你不能就此打住
话虽如此,但在生产环境中部署时,有许多原因会让您不想遵循这种架构。
首先,web 服务器可以用不同的语言编写(例如,ruby 或 javascript)。现在,您必须以某种方式将您的模型加载到这种语言中,虽然有这样做的方法,但它们很有挑战性并且容易出错。
然后,你在生产中的模型会经常改变。性能下降、概念漂移问题和新数据将使您经常更新您的模型。另一方面,您的 web 服务器代码不会经常更改。如果您遵循服务器中的模型架构,那么每当您必须更新您的模型时,您将不得不从头开始部署整个系统。您会同意我的观点,对于您的应用程序来说,这不是一个好的、优化的推广过程。
其次,您的服务器硬件可能没有针对 ML 工作负载进行优化。例如,它可能无法访问您的模型可以用来更快进行预测的 GPU 设备。现在,你可以争辩说在推理过程中你可能不需要 GPU 设备,但是在后面的文章中,我们将讨论性能优化,你可能会发现你确实需要它们,这取决于你的用例。
最后,您的模型和 web 服务器很可能会有所不同。如果您的模型很复杂或很大,您可能希望将它托管在 GPU 上,并将负载分布在许多机器上。你不会想让你的网络服务器变得如此复杂。此外,您的模型会消耗您的 web 服务器的资源,这将使用它所有的能力来使模型运行。
结论
在这个故事中,我们看到了为什么您想要使用像 Streamlit 和 Gradio 这样的工具来部署快速且常用的 ML 应用程序版本。我们看到了服务器模型架构的优点和缺点,以及为什么您不想在生产中采用这种方式。
那么,你能做什么?在下一个故事中,我们将看到如何将您的模型从您的 web 服务器中分离出来,最后,使用一个众所周知的工具在一个生产就绪的环境中服务您的模型。
阅读这个故事的第二部分:
关于作者
我叫迪米特里斯·波罗普洛斯,我是一名为阿里克托工作的机器学习工程师。我曾为欧洲委员会、欧盟统计局、国际货币基金组织、欧洲央行、经合组织和宜家等主要客户设计和实施过人工智能和软件解决方案。
如果你有兴趣阅读更多关于机器学习、深度学习、数据科学和数据运算的帖子,请关注我的 Medium 、 LinkedIn 或 Twitter 上的 @james2pl 。
所表达的观点仅代表我个人,并不代表我的雇主的观点或意见。
Python 中异常检测的核密度估计:第 1 部分
将经典方法与深度学习相结合以获得更好的表示

金融数据集中的欺诈检测、网络流量中的罕见事件检测、建筑物和道路监控的视觉图像检查以及生产线中的缺陷检测:这些都是非常常见的问题,在这些问题中,用于异常检测的机器学习技术会有所帮助。
简而言之,异常检测是一个研究领域,旨在发现数据集中的异常观察。
对于这个问题设置,我们假设我们有两种类型的数据点:正常和异常。大多数情况下,这是一个高度不*衡的数据集,因为异常数据点比正常数据点积累得少。在不同的场景中,变态这个词可以有不同的含义。例如,当我们得到的数据点不同于以前所有其他类型的异常时,它可以被称为新颖性,或者当它是非常罕见但已知的原因时,我们可以将其称为异常值[1]。
尽管有定义和用例,我们可以将所有这些类型的问题归纳为异常检测。
PCA、SVM 或核密度估计是用于发现异常观察的经典机器学习技术。如今,许多深度学习技术被用于异常检测,如 GANs、自动编码器等。
图像中异常检测的一种非常常见的方法是一类分类结合自监督学习。它被称为一类分类,因为我们只使用正态数据来拟合模型。粗略地说,我们强迫模型学习正常数据点的良好表示。当我们使用正常和异常数据点组合的测试集时,这可能有助于看到微小的差异。这些技术成为异常检测的最新技术。
本文的第一部分将介绍使用核密度估计检测异常数据的经典方法,接下来我们将深入探讨用于改进 KDE 的自我监督技术。为了更实际的描述,我们将更深入地研究谷歌最*的一篇论文 CutPaste [2],它结合了一种新颖的自我监督技术和 KDE。
核密度估计
核密度估计(KDE)是一种无监督学习技术,有助于以非参数方式估计随机变量的 PDF。它与直方图有关,但使用了数据*滑技术。

直方图和 KDE 可视化:图像源
正如我们在上面的例子中看到的,我们使用相同的数据来绘制直方图。然而,移动直方图中的柱会导致可视化的巨大差异。为了*滑分布,可以使用不同的内核。在上面的例子中,使用了 Tophat 和 Gaussian 核。输出是*滑的密度估计。那么,如何得到这个分布呢?
KDE 的通用公式如下:

其中 K 是核, h 是负责*滑度的带宽参数。如果我们为 h 选择一个更高的数字,我们将得到更*滑的分布。 y 是给定的估计值,而 xi 是样本数据集中的一个点。
如上所述,K 是一个内核,我们有多种选择,如高斯,托法特,Epanechnikov 等。对于异常检测,我们将使用高斯核估计,其中我们使用以下公式计算密度:

根据剪贴纸[2],我们使用上面的公式计算异常分数。
以下步骤描述了使用高斯密度估计的异常检测过程:
步骤 1:拟合从火车分裂到 GDE 的正常数据点
代码来自:https://github.com/LilitYolyan/CutPaste
步骤 2:从测试数据集中计算异常分数
代码来自:https://github.com/LilitYolyan/CutPaste
步骤 3:如果异常分数小于预定义的阈值,则您有一个异常数据点,否则,您没有异常
异常检测的 ROCAUC 指标
在基准测试中,ROCAUC 用于比较不同的模型。MVTec 数据集是最常见的异常检测基准数据集。
为了计算模型的准确性,绘制接收器工作特性(ROC)曲线并计算曲线下面积(AUC)。为了计算 ROC 和 AUC,我们使用异常和正常示例的测试数据,如果我们在 AUC 上获得更高的结果,我们可以更好地检测缺陷。


训练模型后的 ROC 曲线示例:图片来自作者
要计算 ROC 曲线和 AUC 可使用 sklearn 软件包:
代码来自:https://github.com/LilitYolyan/CutPaste
自我监督设置
我们看到了如何实现用于异常检测的 GDA。然而,很难在图像上得到好的结果。如果我们从图像中检索到好的表示,我们可以在 GDE 上得到更好的结果。为了达到这一点,在文章的第二部分,我们创建了一个自我监督的模型,它具有独特的预处理任务,可以改善 GDE 的结果。
参考
[1]拉夫、卢卡斯&考夫曼、雅各布&范德穆伦、罗伯特&蒙塔冯、格雷瓜尔&萨梅克、沃伊切赫&克洛夫特、马里乌斯&迪特里希、托马斯&穆勒、克劳斯-罗伯特。(2021).深浅异常检测的统一综述。IEEE 会议录。第 1-40 页。10.1109/jproc . 20221 . 386386363636
[2]李、孙春良、基赫和尹、金松和普菲斯特、托马斯。(2021).用于异常检测和定位的自我监督学习。
来自 Github 的代码
KernelSHAP vs TreeSHAP
原文:https://towardsdatascience.com/kernelshap-vs-treeshap-e00f3b3a27db
基于速度、复杂性和其他考虑因素比较 SHAP *似方法

(来源: flaticon )
KernelSHAP 和 TreeSHAP 都用于*似 Shapley 值。 TreeSHAP 快多了。缺点是它只能和基于树的算法一起使用,比如 random forests 和 xgboost。另一方面, KernelSHAP 是模型不可知的。这意味着它可以用于任何机器学习算法。我们将比较这两种*似方法。
为此,我们将做一个实验。这将向我们展示 TreeSHAP 实际上有多快。我们也将探索树算法的参数如何影响时间复杂度。这些包括数量的树、 深度和数量的特征。在使用 TreeSHAP 进行数据探索时,这些知识非常有用。最后,我们将讨论像特性依赖这样的其他考虑如何影响方法。
如果你是第一次来 SHAP,那么看看下面的视频。如果你想要更多,那就来看看我的 SHAP 课程 。注册我的 简讯 :)即可免费获取
每次观察时间
对于第一个实验,我们想知道计算 SHAP 值的方法需要多少时间。我们不会讨论用来获得结果的代码,但是你可以在 GitHub 上找到它。总而言之,我们从模拟回归数据开始。这有 10000 个样本、 10 个特征和 1 个连续目标变量。利用这些数据,我们训练了一个随机森林。具体来说,模型有 100 棵树,最大深度 4 。
我们现在可以用这个模型来计算 SHAP 值。我们使用 KernelSHAP 和 TreeSHAP 方法来实现这一点。对于每种方法,我们计算 10 、 100 、 1000 、 2000 、 5000 和 10000 SHAP 值。对于每一笔金额,我们都记录了计算花费的时间。我们确保每笔金额重复这一过程 3 次。然后我们取*均值作为最终时间。
你可以在图 1 中看到这个过程的结果。你可以看到 TreeSHAP 明显更快。对于10000 个 SHAP 值,该方法花费了 1.44 秒。相比之下,KernelSHAP 用了 13 分 40.56 秒。这是 570 倍那么长。这些计算的速度将取决于你的设备,但你应该期待类似的差异。

图 1:每次观察的时间(来源:作者)
上面的树形线看起来是*的。这是因为它被 KernelSHAP 线矮化了。在图 2 中,我们有一条仅用于 TreeSHAP 的线。你可以看到它也随着观察次数的增加而线性增加。这告诉我们,每个 SHAP 值需要相似的时间来计算。我们将在下一节探讨原因。

图 TreeSHAP 的每次观察时间(来源:作者)
时间复杂度
两种方法的时间复杂度如下所示。这就是在树算法中计算要素的 SHAP 值时的复杂性。 T 是单棵树的数量。 L 是每棵树的最大叶子数。 D 是每棵树的最大深度。最后, M 是每棵树的最大特征数。对于这些方法,这些参数将以不同的方式影响*似时间。

(来源:作者)
只有 TreeSHAP 的复杂度受到深度的影响( D )。另一方面,只有 KernelSHAP 受到特征数量的影响( M )。不同的是 KernelSHAP 复杂度是指数 w.r.t M 而 TreeSHAP 是二次 w.r.t D 。考虑到我们还拥有比树深度(D= 4)更多的特征(M = 10 ),我们可以理解为什么 KernelSHAP 要慢一些。
为了清楚起见,这是每个 SHAP 值的时间复杂度。我们应该预料到每个值都需要相似的计算时间。这就是为什么我们看到了时间和观察次数之间的线性关系。我们现在将探索时间和其他参数 T 、 L 、 D 和 M 之间的关系。然后我们将讨论这些结果对模型验证和数据探索的意义。
树木数量(吨)
对于这两种方法,复杂度与树的数量(T)成线性关系。我们预计这个参数会以类似的方式影响*似时间。为了看到这一点,我们执行了一个与之前类似的实验。这一次,我们通过增加树的数量来训练不同的模型。我们使用每个模型来计算 100 个 SHAP 值。
您可以在图 3 中看到结果。对于这两种方法,时间随着树的数量线性增加。这是我们在考虑时间复杂度时所预期的。这告诉我们,通过限制树的数量,我们可以减少计算 SHAP 值的时间。

图 3:每棵树的时间(来源:作者)
特征数量(M)
只有 KernelSHAP 受到特性数量的影响( M )。这次我们在不同数量的特征上训练模型。同时,我们保持其他参数( T,L 和 D 不变。在图 4 中,我们可以看到 KernelSHAP 的时间随着我们增加 M 而呈指数增长。相比之下,TreeSHAP 的时间没有受到太大影响。

图 4:每个特征的时间(来源:作者)
你可能已经注意到 TreeSHAP 的时间逐渐增加。这可能会令人困惑,因为我们看到复杂度不依赖于 m。要清楚的是,这是计算单个 特征的SHAP 值时的复杂度。随着 M 的增加,我们需要为每次观测计算更多的 SHAP 值。
树深度(D)
最后,我们改变树的深度。我们确保森林中每棵树的深度始终是最大深度。在图 5 中,你可以看到当我们增加深度时会发生什么。TreeSHAP 的时间增加得更快。甚至有一点 TreeSHAP 变得比 KernelSHAP 的计算量更大。我们可能已经预料到了这一点,因为我们看到只有树形复杂度是 d 的函数。

图 5:每棵树深度的时间(来源:作者)
你可能会问为什么内核时间也会增加。这是因为特征(M)和叶子(L)的数量根据树的深度而变化。随着深度的增加,会有更多的裂缝,所以我们会有更多的叶子。更多的分割也意味着树可以使用更多的功能。你可以在图 6 中看到这一点。这里我们计算了森林中所有树木的特征和叶子的*均数量。

图 6:每棵树深度的特征和叶子(来源:作者)
模型验证和数据探索的收获
通过改变深度,我们看到在某些情况下 TreeSHAP 的计算开销更大。然而,这些情况不太可能发生。我们看到只有当树深度为 20 时才会发生这种情况。在这么深的地方工作是不常见的。实际上,我们通常会有比树深度(D)更多的特征(M)。
这意味着,在用 SHAP 验证树模型时,TreeSHAP 通常是更好的选择。我们能够更快地计算 SHAP 值。尤其是当你需要比较多个模型的时候。对于模型验证,我们对参数 T 、 L 、 D 和m没有太多选择,因为我们只想验证性能最佳的模型。
对于数据探索,我们有更多的灵活性。树算法可用于发现重要的非线性关系和相互作用。要做到这一点,我们的模型只需要好到足以捕捉数据中的潜在趋势。我们可以通过减少树的数量和深度来加速这个过程。同时,我们能够探索许多模型特征(M ),而无需大幅提高速度。
其他考虑
在选择方法时,时间复杂度是一个重要因素。在做出选择之前,您可能需要考虑其他一些差异。这些问题包括 KernelSHAP 是模型不可知的,这些方法受特征依赖性的影响,并且只有 TreeSHAP 可以用于计算交互影响。
模型不可知
在开始,我们提到 TreeSHAP 的最大限制是它不是模型不可知的。如果你正在使用非基于树的算法,你将不能使用它。神经网络也有自己的逼*方法。这些算法可以使用 DeepSHAP。然而,KernelSHAP 是唯一可以用于所有算法的方法。
功能依赖关系
特性依赖会扭曲 KernelSHAP 所做的*似。该算法通过随机采样特征值来估计 SHAP 值。问题是,当要素相关时,采样值可能不太可能。这意味着,当使用 SHAP 值时,我们可能会对不太可能的观察结果赋予太多的权重。
TreeSHAP 没有这个问题。然而,由于特征依赖性,该算法具有不同的问题。也就是说,对预测没有影响的特征可以获得非零的 SHAP 值。当一个要素与另一个影响预测的要素相关时,可能会发生这种情况。在这种情况下,我们可能会得出一个错误的结论,即某个特征有助于预测。
分析相互作用
SHAP 互动价值观是 SHAP 价值观的延伸。它们的工作原理是将一个特征的贡献分解成主要的和交互的效果。对于一个给定的特性,交互效应是它与其他特性的所有联合贡献。当突出显示和可视化数据中的交互时,这些会很有用。我们将在下面的文章中深入探讨这个问题。
如果你想使用 SHAP 交互值,你必须使用 TreeSHAP。这是因为这是唯一一种实现了相互作用值的*似方法。这与 SHAP 互动价值观的复杂性有关。估计这些可能需要更长的时间。
最后,您应该尽可能使用 TreeSHAP。它速度快得多,让你有能力分析互动。对于数据探索,您可能希望坚持使用树算法来获得这些好处。如果你正在使用其他类型的算法,那么你必须坚持使用 KernelSHAP。它仍然是一种比蒙特卡罗抽样等其他方法更快的*似方法。
https://conorosullyds.medium.com/membership
你可以在|Twitter|YouTube|时事通讯上找到我——注册免费参加 Python SHAP 课程
图像来源
所有图片都是我自己的或从www.flaticon.com获得。在后者的情况下,我拥有他们的保费计划中定义的“完全许可”。
参考
南伦德伯格、 SHAP 蟒包 (2021) 、https://github.com/slundberg/shap
南 Lundberg & S. Lee,解释模型预测的统一方法 (2017),https://arxiv.org/pdf/1705.07874.pdf
南 Lundberg,S.M .,G. Erion,G.G .和 Lee,s . I .(2018 年)。树集成的一致个性化特征属性。 arXiv 预印本 arXiv:1802.03888 。
C.Molnar,可解释机器学习【2021】https://christophm . github . io/Interpretable-ml-book/shap . html
南 Masís,用 Python 进行可解释的机器学习 (2021)
MLOps 专业化认证的关键学习点—课程 3
原文:https://towardsdatascience.com/key-learning-points-from-mlops-specialization-course-3-9e67558212ee
MLOPS 专业化系列
DeepLearning 的面向生产的机器学习工程(MLOps)课程的主要见解(含课堂讲稿)。艾&吴恩达

里卡多·戈麦斯·安吉尔在 Unsplash 上的照片
虽然机器学习(ML)和深度学习概念是必不可少的,但在用数据科学解决现实世界的问题时,拥有生产工程技能同样(如果不是更多)重要。
深度学习。AI 开发了 MLOps 专业化课程 来分享在生产中构建和维护 ML 系统的实践经验。
在这篇文章中,我总结了经验教训,这样你就可以跳过几个小时的在线视频,同时仍然可以收集关键的见解。
内容
本文涵盖了 3 的4-课程 的 MLOps 专业化。 跟随本页 了解后续课程的最新内容。
(1)概述
在 MLOps 专业化的第三个课程中,我们重点关注为不同的服务环境构建模型,同时管理建模资源以实现最佳模型推断。
我们还探索了解决模型分析、公*性和可解释性的技术和指标。

(2)主要经验教训
第 1 部分——神经结构搜索
什么是神经架构搜索?
- 神经架构搜索( NAS ) 自动化神经网络架构的设计(例如,层数、激活类型和连接)
- 这个概念类似于超参数调优,其目标是找到对数据表现良好的最优架构。
- NAS 是自动化机器学习的子领域(AutoML)。
- 用 NAS 设计的模型与手工制作的模型不相上下,甚至更好。
- 我们可以使用像 Keras Tuner 这样的库来运行 NAS。

神经结构搜索(NAS)概述|作者图片
- NAS 有三个部分 : 一个搜索空间,一个搜索策略,一个性能评估策略。
搜索空间
- 搜索空间定义了构建不同架构的可能组件。
- 有两种类型的架构搜索空间:宏观和微观。

两种主要类型的搜索空间|作者图片
- 宏 搜索空间包括单个层(例如卷积、池化)和连接类型,通过顺序堆叠层以形成链式结构空间来找到最佳模型。
- 相比之下,在微搜索 空间中,NAS 从细胞构建神经网络,其中每个细胞都是一个更小的神经网络。
- 微观方法已被证明比宏观方法具有显著的性能优势。
搜索策略
- NAS 基于特定策略在搜索空间中搜索,以找到要测试的架构,从而找到性能最佳的架构。
- 五种常用策略包括网格搜索、随机搜索、贝叶斯优化、进化算法和强化学习。
性能评估
- NAS 依赖于测量它所尝试的不同体系结构的性能。
- 评估性能最直接的方法是评估每个架构的验证准确性。
- 然而,考虑到大的搜索空间和复杂的网络,计算验证准确性可能是计算量很大的。
- 降低计算成本的策略包括使用较低保真度估计、学习曲线外推和网络态射。

降低架构性能成本估算的策略|作者图片
第 2 部分—模型资源管理
高维度问题
- 虽然神经网络忽略了的特征,但是而不是具有预测信息,这确实而不是意味着我们应该用所有的特征来训练我们的模型。
- 不需要的特征消耗计算资源,提高存储成本,增加解释的复杂性,将噪声引入数据,并增加过度拟合的风险。
- 随着我们添加更多的功能,我们增加了训练所需的处理能力和训练数据(其中所需的训练示例的数量随着每个添加的功能而呈指数增长)。
- 一个拥有高质量特性的差模型会比一个拥有低质量特性的好模型表现得更好。
维度缩减技术
- 我们希望用尽可能少的特征保留尽可能多的预测信息。
- 手动降维 降维涉及理解数据和业务上下文,并利用领域知识来执行特征工程和选择。
- 除了手工技术,还有算法途径进行降维,如线性判别分析 (LDA) 、、偏最小二乘法、【PLS】、、主成分分析 (PCA) 。
模型优化的需求

- 随着移动、物联网和边缘设备变得无处不在,有必要将 ML 功能从云转移到设备上。这意味着我们需要优化模型的性能和资源需求。
- 设备上的模型推断包括将训练好的模型加载到设备应用中,这提供了改进的速度和独立于网络连接。
- 将模型部署到移动应用的框架有 ML Kit ,CoreML,tensor flow Lite。
量化
- 量化是一种优化 ML 模型的技术,它使用较低精度的参数和计算将模型转换成一种等效的表示形式。
- 一个例子是使用较少的比特来表示图像的像素。
- 虽然量化可能会降低模型精度,但它通过缩小神经网络规模,减少计算资源,降低延迟提高执行性能和效率。
- 我们可以通过将浮点 32 位值转换为 8 位整数来量化神经网络中的权重参数和激活计算。****

量化后精度降低|图片由作者提供
- 量化可以在 ( 量化感知训练)期间完成,也可以在模型训练(后训练量化)之后完成。****
最佳型号选择
- 如果不需要高精度,最好使用更小、更简单的模型,因为嵌入式设备的计算资源有限。
- 为移动设备优化的模型的一个例子是为计算机视觉应用而设计的 MobileNets。
修剪
- 修剪是一种优化技术,通过删除对产生准确结果没有实质性贡献的部分来提高模型效率。

网络剪枝示例|作者图片
- TensorFlow 有一个 权重剪枝 API 设计用于在训练期间基于幅度迭代移除连接。
- 权重修剪与量化兼容,在模型优化中产生复合效益。
第 3 部分—高性能建模
分布式培训
- 当我们处理更大的数据集和更大的模型时,我们需要模型训练中的分布式方法。
- 两种类型的分布式训练是数据并行和模型并行。
- ****数据并行将模型复制到不同的加速器(GPU 或 TPU)上,并在它们之间拆分数据。
- ****模型并行将一个大型模型(太大而无法在单个设备上安装)划分为多个分区,并将它们分配给不同的加速器。
- 数据并行比模型并行更容易实现,并且与模型无关,适用于任何神经架构。

数据并行性图解|作者图片
- 数据并行可以分为同步(所有工人同步训练并完成更新)或异步训练。
- 要执行分布式训练,我们可以使用 TensorFlow 的 tf.distribute.Strategy 库。
高效摄入
- 加速器(GPU/TPU)对于高性能建模至关重要,但它们价格昂贵,必须高效使用。
- 这种效率是通过以足够快的速度向加速器提供数据来保持的,以避免保持空闲并改善训练时间。
- 我们可以使用诸如预取、缓存、内存减少和数据提取和转换的并行化等方法来优化输入管道(即 ETL 过程)的性能。

Pipeline 有助于高效利用可用硬件,减少加载和预处理数据所需的时间|作者图片
- 通过流水线,我们可以通过重叠加速器的 CPU 预处理和模型执行来克服 CPU 瓶颈。
流水线并行性
- *年来,我们已经看到模型尺寸(例如,比根、伯特、GPT-3)越来越大,以提高性能。
- 模型增长和硬件改进之间的差距增加了并行性的重要性。
- 这些更大的模型在数据并行性(加速器的内存有限)和模型并行性(加速器计算能力的利用不足)方面带来了新问题。
- 这些问题导致了流水线并行的发展。
- ****流水线并行通过在多个加速器上划分一个模型,并自动将一个小批次的训练数据分割成更小的微批次,从而实现大型模型的高效训练。
- 一些管道并行框架(整合数据和模型并行)有 Google 的 GPipe 和微软的 PipeDream 。

流水线并行支持更高效的培训|作者图片
知识蒸馏
- 知识提炼背后的想法是创建一个简单的“学生”模型,从更复杂的“教师”模型中学习。
- 目标是将复杂模型的性能复制到更简单、更高效的模型中。
- 例如, DistilBERT 是 BERT 的提炼版本,它使用的参数少了 40%,运行速度快了 60%,同时保留了 BERT 97%的性能(GLUE 语言理解基准)。
第 4 部分—模型分析
聚合指标与切片指标
- 在训练和部署模型之后,下一个阶段是评估它的性能。
- 我们通常监控顶级的聚合指标,这些指标评估整个数据集的性能(例如,整体准确性),但这通常隐藏了性能和公*性方面的具体问题。
- 需要分割数据,以了解它在单个数据子集的粒度级别上的表现。
- 选择重要切片进行分析通常基于领域知识。****

*均性能良好的模型可能会表现出从聚合指标|作者图片中看不出来的故障模式
- 例如,不同年龄组的客户对模型输出的体验可能非常不同。
- TensorFlow 模型分析(TFMA) 是一个开源的框架,用于模型性能的深度分析,包括在数据切片上分析性能。
模型稳健性
- 除了模型性能,我们还应该评估模型的健壮性。
- 如果一个模型的结果始终准确,即使一个或多个特征发生了相对剧烈的变化,该模型也被认为是健壮的。它不应该随着数据的变化而产生非常不同和不可预测的结果。
- 用于评估稳健性的指标与我们用于培训的指标相同,例如,用于回归模型的 RMSE 和用于分类的 AUC。
模型调试
- 模型调试是一门新兴的学科,专注于发现和修复模型中的问题,并提高模型的健壮性。
- 它的目标包括提高模型透明度,防止有害的社会歧视,减少对抗性攻击或隐私伤害的脆弱性,避免模型衰退。
- 三种最流行的调试技术是基准模型、敏感性分析和残差分析。
- 用于评估对抗性攻击漏洞的两个开源库是 Cleverhans 和 Foolbox 。
持续评估和监控数据漂移和偏移
- 当收集和标记数据时,训练数据仅表示世界的快照,因此模型性能会随着世界的变化而受到影响。
- 当数据漂移和偏移发生时,持续监控数据和模型性能以获得早期警告是至关重要的。这些漂移和转移包括概念漂移、概念涌现、协变量转移和先验概率转移。
- ****监督监控技术包括统计过程控制、顺序分析(使用线性四比率)和误差分布监控(自适应窗口)。
- ****无监督技术包括聚类/新奇检测(如 OLINDDA、MINAS)、特征分布监控和模型相关监控(如边缘密度漂移检测)。
- 领先的云提供商提供持续评估的服务,如微软 Azure 机器学习 DataSense ,亚马逊 SageMaker 模型监视器,谷歌云 AI 持续评估。
第 5 部分—可解释性和可解释性
可解释人工智能的重要性
- 模型可解释性和可解释性在生产 ML 中至关重要,原因包括公*性、监管和法律要求,以及更好地理解我们的模型以改进它。
- 可解释性和可解释性是被称为负责任的人工智能的更大领域的一部分。

负责任的人工智能包含几个部分|作者图片
模型可解释性
- 如果我们可以通过查询模型来回答以下问题,那么模型就是可解释的:
- 为什么模型会以某种方式运行?
- 我们如何相信模型做出的预测?
- ****模型可以提供哪些信息来避免预测错误?
- 虽然复杂的模型(例如神经网络)可以产生高精度,但它通常以可解释性为代价(也称为可解释性与精度的权衡)。
- 固有的可解释模型是经典的模型,如基于树的(例如决策树)和线性模型(例如线性回归)。
- 尽管我们不能总是使用本质上可解释的模型,但是有模型不可知的方法来解释任何模型的结果。

模型不可知的可解释性方法示例|作者图片
- 流行的方法包括部分相关图( PDP )、排列特征重要性、沙普利加法解释( SHAP )和局部可解释模型不可知解释(莱姆)。
(3)课堂讲稿
为了表示感谢,下面是从幻灯片和抄本中编译的 PDF 讲义 的 GitHub repo。给回购打一颗星,以便随时了解后续课程的最新信息。
准备好了吗?点击此处查看下一期课程总结:
**
您可以在此处找到前两个课程的总结:
在你走之前
欢迎您加入我的数据科学学习之旅!点击此媒体页面,查看我的 GitHub ,了解更多精彩的数据科学内容。同时,享受构建生产 ML 系统的乐趣!**
MLOps 专业化认证的关键学习点—课程 4
原文:https://towardsdatascience.com/key-learning-points-from-mlops-specialization-course-4-ee39bbd2864b
MLOPS 专业化系列
来自 DeepLearning 的面向生产的机器学习工程(MLOps)课程的最终见解(含课堂讲稿)。艾&吴恩达

在现实世界中实现机器学习(ML)的潜力超越了模型训练。通过利用 MLOps 的最佳实践,团队可以以可持续的方式更好地操作和管理 ML 模型的端到端生命周期。
在由 4 部分组成的 MLOps 专业化系列的最后一篇文章中,我总结了课程 4 的课程,以便您可以跳过数小时的在线视频,同时仍能获得关键的见解。
内容
本文涵盖了44-课程 MLOps 专业化。 跟随本页 保持更新更多 ML 内容。
(1)概述
MLOps 专业化认证的最后一门课程侧重于部署经过培训的 ML 模型,并将其提供给最终用户。
我们着眼于构建可靠的基础设施,并为实时或批量推理逐步交付生产系统。
此外,我们还探讨了如何监控这些系统以检测模型衰退、补救性能下降并避免系统故障。

(2)主要经验教训
第 1 部分—模型服务简介
实时和批量推理
- 模型服务是指使经过训练的模型可供最终用户使用。
- 获得模型预测(又名推理)的方式主要有两种——实时推理和批量推理。**
- 在批量推断中,部署的 ML 模型基于历史输入数据进行预测。当获取实时预测并不重要时,这种情况通常就足够了。
- 实时预测,也称为按需或在线推理,是使用推理时可用的输入数据实时生成的预测。
优化实时推理
- 作为优化实时推理的一部分,有三个关键指标:延迟、成本和吞吐量。**

实时(即在线)推理需要考虑的三个关键指标|作者图片
- 延迟是用户动作和应用程序对动作的响应之间的延迟。
- 成本指与推理相关的基础设施成本(例如,CPU、GPU、缓存)
- 吞吐量是单位时间内服务的成功请求数。
- 许多面向客户的应用程序旨在最小化延迟,同时最大化吞吐量,例如航空公司推荐网站。
- 最终,这是在确保高系统性能的同时将成本保持在合理范围内的一种*衡行为。
*衡复杂性和成本
- 在构建和选择我们的模型时,有两种类型的指标需要评估以*衡性能和成本:优化和门控指标。****

*衡模型复杂性和成本的指标|作者图片
- 优化指标包括反映模型预测能力的准确度和精确度等指标。
- 我们经常遇到增加模型复杂性以提高性能的情况,因此我们需要注意成本,因为更高的复杂性会导致更高的成本。
- 因此,门控指标如 GPU 负载和模型大小充当操作约束,使我们的模型在成本和硬件限制内运行。**
部署选项
- 关于在哪里部署模型,主要有两种选择— 数据中心或设备上(如手机、嵌入式设备)。**

按作者部署模型|图像的选择
- 这意味着我们可以将模型部署在通过远程 API 调用访问的数据中心服务器上,或者将模型的实例直接分发到用户的设备上。
嵌入式设备的限制
- 像移动电话和嵌入式设备这样的分布式环境有一定的限制,如内存、存储和电池容量。
- 比如手机*均 GPU 内存小于~4GB,除了你的 ML 机型之外的应用都共享这个 GPU。
- 这种受限的环境意味着我们不会在这些设备中部署大型复杂的模型。
- 这些限制也意味着我们必须为特定的任务选择最佳的模型。例如,MobileNets就是专门为移动设备上的计算机视觉而设计的。****
第 2 部分—模式和基础设施
模型服务器
- 模型服务器允许我们大规模部署 ML 模型,这样我们就可以通过 API 调用很容易地检索模型预测。
- 我们可以在本地或云上托管这些模型服务器。

运行中的模型服务器|作者图片
- 模型服务器首先基于模型工件实例化 ML 模型。然后,通过 REST(或 gRPC) API 端点向客户端公开模型的预测功能。
- tensor flow Serving是一个开源模型服务器的例子,它提供了一个为生产环境设计的灵活、高性能的 ML 服务系统。
- 其他流行的模型服务器还有英伟达 Triton 推理服务器、 Kubeflow Serving 和 TorchServe 。
缩放的重要性
- 大量的推理请求会淹没模型服务器。因此,模型基础设施必须能够轻松地伸缩,以处理不同的请求大小。
- 缩放主要有两种方式:水*和*垂直*******
- ****垂直扩展是指使用更大更强大的硬件,例如升级 CPU、添加更多 RAM、使用更新的 GPU 等。
- ****横向扩展是指添加更多的节点/设备(即 CPU、GPU)来满足推理负载增加的需求。
- ****通常推荐水*缩放,原因如下:
- 允许弹性,因为根据负载、吞吐量和延迟要求调整节点数量更容易****
- ****无需让现有服务器离线进行硬件升级
- 对硬件容量没有限制,因为可以随时添加节点
集装箱化
- ML 应用程序通常与许多依赖项和可配置项相关联。
- 容器使得以轻量级的方式打包和运行整个 ML 应用程序变得容易,而不用担心操作系统(OS)的需求。
- 容器允许ML 应用的快速敏捷部署以及简单的*水*可伸缩性。*******
- Docker 是最流行的构建、部署和管理容器化应用程序的开源*台。

容器架构|作者图片
- 我们可以使用像 Kubernetes 和 Docker Swarm 这样的容器编排软件来管理和同步多台机器上的多个容器。
第 3 部分—模型管理和交付
实验跟踪
- 实验是 ML 模型开发的基础,跟踪结果对于调试和提高模型性能至关重要。
- 跟踪方面包括代码、超参数、执行环境、库版本和模型性能度量。
- Jupyter 笔记本非常适合运行和跟踪简单的实验,但这不是最好的方法,因为笔记本通常不会推广到生产中。
- 可以使用 TensorBoard 、Weights and bias和 Neptune 等工具进行实验跟踪。
- 一个好的习惯是为每个实验加上一致且有意义的标签,这样结果就有条理且易于解释。
数据版本控制
- 数据仅反映了收集数据时的情况,数据预计会随着时间的推移而变化。
- 将我们的数据以及我们通常跟踪的代码和运行时参数版本化是至关重要的。****
- 一些很好的数据版本控制工具包括厚皮动物、 DVC 和 ML 元数据(MLMD) 。
模型版本和注册
- 因为每个模型可能有不同的代码、数据和配置,所以执行模型版本控制是很重要的。
- 有了模型版本化,我们可以很容易地检索旧的模型并理解模型血统(即,产生模型的工件之间的一组关系)。
- 模型注册中心是一个用于存储训练模型的中央存储库,它提供了一个用于管理和搜索训练模型的 API。
- 模型注册对于支持模型发现、模型理解和模型重用是必不可少的,包括在拥有数百个模型的大规模环境中。

模型工件流向模型注册|按作者分类的图像
- 创建模型注册中心的几个流行工具包括 MLflow 模型注册中心和 Azure ML 模型注册中心。
对 MLOps 的需求
- 今天影响 ML 工作的许多问题反映了软件工程领域在不久前所面临的问题。
- 这些问题包括缺乏可再现性和出处、低效的协作、手动跟踪、上市缓慢,以及模型在部署前被阻止。
- MLOps 借用 DevOps 的概念来标准化和统一 ML 系统开发生命周期,以应对上述挑战。
- MLOps 的目标是创建一个一致且可靠的方法来自动化模型的培训和部署,同时进行强大而全面的监控。
MLOps 成熟度—0 级
- MLOps 系统的成熟度由数据、建模、部署和维护的自动化水*决定。****
- MLOps 级别 0 是 ML 生命周期流程为手动的基本级别。
- 它只关注将训练好的模型作为预测服务进行部署。它不涉及自动再培训或主动性能监控的过程。
- 这种成熟度在许多开始在其用例中采用 ML 的企业中是典型的。
MLOps 成熟度—1 级
- MLOps 级引入了流水线自动化,目标是自动化连续模型训练。****
- 它包括重要组件,如自动化数据和模型验证、管道触发器和元数据管理。****
MLOps 成熟度—2 级
- MLOps 级别 2 目前仍有一些推测性。
- 它涉及到健壮的自动化持续集成/持续交付(CI/CD ),因此团队可以围绕特征工程、模型架构和超参数快速探索新的想法。
- 这个成熟度级别还包括关键组件,如源代码控制、部署服务、模型注册和特性存储。

CI/CD 和自动化 ML 管道|图片来自 Google Cloud
持续集成/持续交付(CI/CD)
- 综合集成(CI) 当新代码被提交到源代码库中时,涉及到新代码的构建、打包和测试。
- 在作为 CI 的一部分进行部署之前,单元测试对于检查代码是否正常工作是至关重要的,它涉及模拟、数据覆盖和代码覆盖。

ML 单元测试的注意事项|作者图片
- 连续交付(CD) 是将新代码和新训练模型的系统部署到目标环境中,同时确保兼容性和预测服务性能的过程。
- 持续集成和持续交付的整个过程和基础设施被称为 CI/CD。
渐进式交货
- 渐进交付被认为是一个开发过程,是对 CI/CD 的改进。
- 它侧重于逐步推出新功能,以限制潜在的部署风险并提高部署速度。
- 它包括首先向小规模、低风险的受众交付变更,然后扩展到更大规模、更高风险的受众。
- 渐进式交付的示例包括蓝/绿部署、金丝雀部署和现场实验。
第 4 部分—模型监控和记录
为什么监控很重要
- ML 开发是一个循环迭代的过程,监控对于改进和维持 ML 系统至关重要。
- 持续监控对于识别数据偏差、模型陈旧性和负反馈循环非常重要。****
ML 系统中的监控
- ML 监控不同于软件监控,因为 ML 系统有两个附加组件:数据和模型。
- ML 系统监控包括两个关键部分— 功能监控和系统监控。****
- ****功能监控关注模型预测性能和服务数据的变化。这些包括模型性能度量以及数据中每个特征的分布和特征。
- ****系统监控指监控生产系统的性能和服务系统的可靠性。它包括吞吐量、延迟、资源利用率等运营指标。

ML 系统监控的类型|按作者分类的图片
记录
- 日志记录是围绕模型和系统建立可观察性的一种重要方式。
- 日志是 ML 系统中离散事件(例如,模型输入、预测)的不可变时间戳记录。
- 使用聚合汇点和工作空间允许团队集中日志来创建 ML 系统的统一视图用于报告和警报。****
- 虽然日志无可否认是有用的,但是我们必须意识到它的一些缺点。
- 过多的日志记录会影响系统性能,对日志进行聚合操作以及设置和维护日志记录工具会产生巨大的成本。
(3)课堂讲稿
为了表示感谢,下面是从幻灯片和抄本中整理出来的 GitHub repo 和 PDF 讲义 。在这些笔记中还有很多其他有价值的见解,所以去看看吧!
您可以在此处找到前三门课程的总结:
*****
在你走之前
欢迎您来到加入我的数据科学学习之旅!点击此 Medium 页面,查看我的 GitHub ,了解更多精彩的数据科学内容。同时,享受构建生产 ML 系统的乐趣!
https://kennethleungty.medium.com/membership *****
2022 年高德纳数据与分析峰会的主要收获
原文:https://towardsdatascience.com/key-takeaways-from-gartner-data-analytics-summit-2022-ec908f9599df
增强分析、合成数据、适应性治理等等

2022 年 Gartner 数据与分析峰会开幕主题演讲。(图片由作者提供。)
“我们现在生活在一个永远不确定的世界里。能够应对这种不确定性的企业才能最成功地驾驭未来。”
—Gartner Data&2022 年分析峰会
正如疫情所展示的,在当今的新世界中,不能或不愿变革的组织会陷入一堆麻烦之中。如今,无论是在自己的团队中还是在整个组织中,数据人员都肩负着担当变革推动者并不断推动创新的重任。
今年 Gartner Data &分析峰会的主题是“释放创新&转变不确定性”。在开场主题演讲中,Debra Logan(Gartner 的杰出副总裁分析师)和 Gareth Herschel(Gartner 的副总裁分析师)谈到了数据团队和领导者如何推动创新并创造有意义的商业价值。
以下是你应该从今年的峰会中了解的四大理念:
- 通过小数据、合成数据和活动元数据,将数据从负债变为资产
- 通过增强分析,将分析视为一门艺术和一门科学
- 成为“决策设计者”,在适当的时候加快决策并提供见解
- 用三种类型的治理*衡控制和支持
成为“数据管理员”,而不是数据管道工
“我们不需要大量数据。我们需要正确的数据,让我们变得更聪明的数据。”
—Gartner Data&2022 年分析峰会
在数据世界里,我们有一个坏习惯,就是向 FOMO 屈服,认为数据越多越好。“如果我不包含这个数据集,以后需要它怎么办?为了安全起见,我不妨加上一句。”云给了我们无限的空间来存储数据,这让我们养成了这个习惯。
但是,尽管云有诱人的前景,我们可以做得更好。毕竟,数据不是无害的——它有风险,错误的数据可能是一种负担。
Gartner 解释说,我们不应该成为“数据管道工”,建立无数管道从数据源获取数据,而是应该充当“数据礼宾员”,引导人们找到正确的数据。在深入研究您的数据之前,停下来回答几个关于您正在收集和使用的数据的问题:
- 我们收集的数据正确吗?
- 我们找到正确的见解了吗?
- 我们的分析真的会推动结果吗?
- 治理有助于还是阻碍创新?
大数据已经过时,小数据和合成数据正在流行
“停止收集数据以防万一。用小数据替代大数据,用合成数据替代真实数据。”
— Gartner 数据&2022 年分析峰会
当我们屈服于 FOMO,尽可能地吸收所有的数据时,我们经常会以垃圾数据膨胀我们的系统而告终。该主题演讲解释了“数据节食”的重要性,即关注最重要的数据,而不是试图吸收和分析数据。
不要大数据,想想 小数据 。你能创建的"最小可行数据集是什么,就像我们在技术世界中如何看待 MVP(最小可行产品)?通常,这种“MVD”与大得多的数据集一样有影响力,而且创建起来也快得多。
“小数据可能比最全面的大数据集合更有洞察力……我们应该渴望创建最少的可行数据。”
—Gartner Data&2022 年分析峰会
确保您的数据保持为资产而非负债的另一种方法是利用 合成数据 。Gartner 估计,到 2030 年,人工智能模型中的合成数据将完全盖过真实数据。
合成数据是为在人工智能模型中使用而制造的数据。它保留了原始数据的结构,但并不完全相同。虽然这听起来像是一个未来的梦想,但合成数据仍然存在,而且越来越强大。没有真实的人的信息,合成数据集没有真实数据集那样的隐私问题。

作者图片
利用活动元数据的力量
“到 2025 年,数据结构中活动元数据辅助的自动化功能将减少三分之一的人力,同时将数据利用率提高四倍。”
—Gartner Data&2022 年分析峰会
去年,Gartner 引入了活动元数据的概念,用活动元数据的首份市场指南取代了其元数据管理解决方案的市场指南。今年,Gartner 已将全部精力投入到活动元数据上,将其整合到几乎所有不同主题的演讲中。
活动元数据究竟是什么?Gartner 的基本定义是“一组支持持续访问和处理元数据的能力,这些元数据支持持续的分析……”
在实践中,活动元数据作为现代数据栈之上的一层。它利用开放 API 连接数据堆栈中的所有工具,并在双向流中来回传送元数据。这使得活动元数据可以带来上下文,比如说,从雪花到 Looker,Looker 到 Slack,Slack 到吉拉,吉拉又回到雪花。
通过拼接您的数据堆栈和增加不同用户的上下文,活动元数据是启用数据结构的关键。它可以用于计算您实际拥有的数据,提高使用该数据的效率,并推动强大的用例来增加该数据的价值(如识别数据漂移和发现新的数据类别)。
将增强分析视为一门艺术和科学
如今,分析和人工智能变得异常智能。他们可以在国际象棋、扑克、桥牌、围棋以及我们发明的几乎所有游戏中击败我们——他们甚至开始发明新游戏!但是,在由此产生的海量分析见解中,你如何才能专注于最重要的事情呢?
“增强分析让人们专注于他们擅长的事情,过滤掉噪音。”
—Gartner Data&2022 年分析峰会
就像一个只带回最重要信息的探险家, 增强分析 识别我们数据中最重要的信号。这有助于过滤噪音,让人们专注于他们擅长的事情。

作者图片
即使有了增强分析的力量,Gartner 强调分析更多的是一门艺术而不是科学。
在科学领域,我们考虑的是最大化我们的结果——也就是创造出最好的答案。但往往在数据中,我们不需要完美的答案。事实上,这通常是不可能的,因为我们的数据是不完美和不完整的。
相反,我们需要为现在的创造最佳答案。分析从根本上来说是主观的和不完美的,所以从建立“好”的样子开始,然后当你达到它的时候停止。(时间拳击和 Scrum 技术在这里会很有帮助。)这一点至关重要,因为数据团队的时间和资源有限,所以每一个完美回答的问题都可能是另一个没有回答的问题。
“艺术是关于激发和挑战性的问题。好的分析回答问题,但伟大的分析促使我们提出新的更好的问题。”
— Gartner 数据&2022 年分析峰会
成为“决策设计者”来影响决策的制定
做出决定是困难的,维护决定更难。毕竟,在了解到植物性饮食可以减少心血管疾病后,每个人都没有转变为植物性饮食,大多数新年决心都在二月中旬被打破。因此,如果给人们数据并不总是有效,我们如何才能真正推动更好的决策?
根据 Gartner 的说法,答案是成为一名“决策设计师”,或者以最有可能真正创造变革的方式分享数据和见解。有三种方法可以做到这一点。
首先,在合适的时间发表见解。高管们想要做出决定,但当他们的数据被处理、洞察准备就绪时,这个决定早就应该做出了。这就是增强分析发挥作用的地方。更快的分析甚至可以主动标记问题并识别机遇或挑战,从而更容易将数据输出与实际决策联系起来。
“高管希望做出决策,但分析往往与他们的需求不同步。”
—Gartner Data&2022 年分析峰会
第二,用自动化加速决策。您知道吗?实时营销比短时间后发送的营销成功两倍,比与客户行动不同步的营销有效十倍。时机很重要,你越快用相关的见解回答问题,它们在决策中就越有用。
第三,连接你的决定。随着跨职能部门收集和分析数据,组织越来越数字化,那么为什么决策仍然经常在孤岛中做出呢?将不同团队、团体或垂直行业做出的决策联系起来,可以帮助公司优化大局,而不是让决策陷入个人偏见或团队政治。这就是活动元数据跨数据堆栈连接不同工具的能力发挥作用的地方。
治理不是关于控制
“治理有一点坏名声。但是如果我们想要创新,我们真的需要进行治理。只是决定如何把事情做好的过程。”
—Gartner Data&2022 年分析峰会
治理得到了一个坏名声,但是它不一定是可怕的或者阻碍我们的工作。
根据 Gartner 的说法,要记住的最重要的事情是治理不是统一的——它有不同的风格。当你防御风险时,有时治理就是控制。但有时这是为了激发创造力或适应变化。
治理不是一刀切的。实际上,您应该了解三种不同类型的治理— 战略治理、控制治理和适应性治理。
根据 Gartner 的说法,适应性治理“支持灵活敏捷的决策过程,帮助组织快速响应机遇,同时持续应对投资、风险和价值”。

Debra Logan 和 Gareth Herschel 在 2022 年 Gartner 数据与分析峰会开幕主题演讲台上。(图片由作者提供。)
从今年的峰会可以清楚地看到,数据处于一个令人兴奋的位置。在经历了前所未有的社会和经济剧变后,数据领导者似乎急于用新水*的创新和影响来应对过去的破坏。我迫不及待地想知道我们将何去何从,以及未来一年数据世界将会发生什么!
附注:敬请关注 2022 年高德纳数据与分析峰会,了解更多见解、趋势和经验。我在这里学到了很多,我迫不及待地想和大家分享。
觉得这个内容有帮助?在我的时事通讯《元数据周刊》上,我每周都写关于活动元数据、数据操作、数据文化和我们的学习建设的文章。 在此订阅。
这篇博客是与克里斯汀·加西亚合写的。
温柔地杀死我…权力 Bi 中的双向关系
你听说过双向关系是“邪恶”的吗?是真的吗?检查为什么它不是(100%)真实的,以及如何避免潜在的“地狱之路”

如果您仔细阅读了我的“数据建模”系列,您可能会想起我曾用一个剧院来说明角色扮演维度的概念。然而,在这篇文章中,没有演员,也没有剧院…但是,具有双向关系的模型,或者如果你喜欢的话,双向过滤器,可以很快变成悲剧!
简而言之,双向关系
在我们深入解释为什么双向关系会回来破坏您的数据模型之前,让我们先简要解释一下什么是双向关系。如果您正确地准备了星型模式模型,一旦在模型中建立了两个表之间的关系,关系基数应该是 1:M(除了这个基数类型,还有 1:1 和 M:M 类型,但这将在下一篇文章中讨论)。
让我们快速了解一下这个 1 和 M 是什么意思……在设计良好的星型模式模型中,维度表会在关系的“ 1 ”这一边——简单地说,系统中只有一个特定的产品、一个特定的客户、一个特定的日期等等……另一边是“ M ”,这意味着很多。某个产品可以多次购买,某个客户可以进行多次交易,某一天的交易可能多达数百万笔。
我将使用一个非常基本的数据模型来展示这个 1:M 故事在现实中的样子:

作者图片
在上图中,我在中间有一个事实表(Bets)和两个维度表(Customer 和 Competition)。客户表中只有一个特定的客户,竞争表中只有一个特定的竞争,但是一个客户可以下多注,就像一个竞争可以有很多注一样。
如果您来自传统的关系数据建模世界,那么这个“关系基数”的故事应该已经结束了,对吗?然而,在 Power BI 中,还有更多(谁说的扩展表)…
如果您更仔细地观察这个图,您可能会注意到连接两个表的线上有一个小箭头。而且,不仅仅是箭头本身很重要——更重要的是箭头的方向!箭头本身意味着过滤发生在表之间,而箭头方向显示了过滤器传播的方式!
这一点非常重要,在我们的示例中,这意味着—如果我筛选 Customer 表,并选择 ID 为 1 的客户,该筛选器将应用于 Bets 表,我将只看到 ID 为 1 的客户下的那些赌注。但不是相反,因为过滤器从关系的“1”侧传播到“M”侧。
我敢说,这是默认的也是期望的行为,因为 Power BI 引擎是为以最佳方式将滤波器从“1”移动到“M”而设计的。这就是所谓的 单向 过滤:

作者图片
但是,您可能希望更改此默认行为,并启用双向筛选器传播。做到这一点相当简单—只需转到关系属性,并在交叉筛选方向下拉列表中选择两者:

作者图片
一旦你选择了这个选项,你就把你的关系从单向的变成了双向的 !而且,这里是聚会开始的地方…只是要明确,双向过滤器没有什么固有的错误!但是,像任何其他非默认行为一样,这种行为也需要仔细评估和熟练的数据建模师。
如果双向关系本身并不坏,我为什么要在乎呢?!
这是一个公*的问题!
虽然双向关系本身并不“邪恶”,但是它们不仅会将您引向报表中不可预测的结果,还会在某些情况下降低报表的性能,这也是事实。虽然性能方面的考虑超出了本文的范围,但是让我快速地向您展示一下启用双向过滤是如何在报告中产生一些奇怪的和意想不到的数字的。
比方说,我也想分析客户存款的数据,而不仅仅是赌注。因此,我将导入一个包含存款数据的非常基本的表。如您所见,竞争和存款之间没有关系,因为客户的存款与任何特定的竞争都没有关系:

作者图片
在这样一份简单的报告中:

作者图片
如果我选择位于美国的 Angela Smith,则除了竞争之外的所有内容都会被过滤。这是因为过滤器从客户传播到投注,而不是进一步从投注传播到竞争。
现在,让我们假设业务请求是同步切片器,并且只显示 Angela 真正下注的那些比赛。在我们的示例中,比赛名称切片器应该只显示 MLB 和 NBA 的值。我们如何实现这一点?是的,启用双向过滤是最快最简单的方法!让我展示给你看:

作者图片
我们的箭头现在看起来不同了,它显示过滤器将向两个方向传播,简单地说,从客户表过滤将影响下注,从那里,下注将过滤竞争表。让我们回到我们的报告,看看它现在是什么样子:

作者图片
是啊!我们完成了业务要求,每个人都很高兴,对吗?嗯,在这种情况下,它没有伤害,即使有一个更方便的方法来达到同样的结果,而不是“玩火”。在我向您展示这个解决方案之前,让我们想象一下,我们快乐的业务用户现在要求我们随着时间的推移来分析数据。很公*,谁见过没有日期维度的星型模式:)!
因此,让我们创建一个简单的日期维度,并在这个表和我们的两个事实表(Bets 和 Deposits)之间建立关系。

作者图片
一切看起来都很好,除了一个“小”问题——这个数据模型不明确!我不会详细解释什么是数据模型中的模糊性,因为来自 SQLBI 的 Marco 和 Alberto 有一篇完美的文章解释了这个“特性”
如果您不想阅读整篇文章,我可以告诉您:模糊性意味着引擎可能会使用多个不同的路径在表之间传播过滤器。这种处理筛选器传播的不确定方式可能会导致报告中出现不可预测的结果。
在我们的例子中,通过过滤日期表,我们可以通过两种不同的路径“到达”存款表:

作者图片
请记住,这是一个相当简单的数据模型。你可以想象在更复杂的情况下可能的含义。
为什么如此多的数据模型中存在双向关系?
是的,我听到了,我听到了……尼古拉,在某些情况下,我们无法避免使用双向关系。我可以接受,但我不认为这是理所当然的!
好的,对于 1:1 和 M:M 关系,你有使用双向过滤器。然而,首先要问自己的问题是:为什么我的数据模型中有这些类型的关系?!尤其是 1:1!!!
但是,回到我们之前的例子,当时我承诺为您提供一个更方便的解决方案来满足我们的业务请求。首先,我将双向过滤恢复为默认的单向过滤:

作者图片
我将简单地返回到我的报告,选择 Competition Name slicer,并在此视图上为下注金额设置非空白的过滤器:

作者图片
一旦我应用了这个过滤器,结果应该与我们启用双向过滤时的情况完全相同:

作者图片
很好,谢谢你,Nikola,但是如果我需要执行维到维分析,我该怎么做呢?例如,我想看看有多少不同的国家对 MLB 下注…这是一个有趣的场景,因为在这种情况下,您将使用事实表作为维度表之间的桥表,并启用双向筛选,就像在“经典”M:M 关系中一样…
再说一遍,请不要那样做!我听到了:你的用户需要在他们的报告中看到这个数字。你想给你的用户留下深刻印象,对吗?我有一个解决方案给你:创建一个合适的数据模型!我的意思是,你应该遵循这个建议,尽管这个特殊的挑战。但是,回到这个特定的需求,你可以(也应该)做的是利用 DAX 的能力,使用交叉过滤功能,而不是启用双向关系。
通过使用 CROSSFILTER,您可以仅在两个方向上启用过滤器传播,并且仅针对这一次计算,而不是将其永久保持活动状态。交叉过滤是一个强大的功能,因为它修改过滤器的方向,甚至完全禁用关系!但是,它只发生在特定表达式的求值过程中。
计算不同国家下注数量的基本方法如下:
Distinct Countries Bet No CROSSFILTER = DISTINCTCOUNT('Customer Bi Dir'[Region])
由于 Competition 不筛选 Customer 表(因此也不能筛选 countries ),所以无论您在 Competition Name slicer 中选择什么,该度量都将返回相同的结果:

作者图片
但是,如果我重写我的度量,使用 CROSSFILTER 函数在两个方向上启用过滤,但只针对这个特定的计算,我将得到正确的结果,同时保持我的数据模型安全,不受双向关系产生的潜在警告的影响:
Distinct Countries Bet =
CALCULATE(
DISTINCTCOUNT('Customer Bi Dir'[Region]),
CROSSFILTER(
'Customer Bi Dir'[Customer ID],
'Bets Bi Dir'[Customer ID],
BOTH
)
)

作者图片
如您所见,我们设法满足了业务请求,而没有在数据模型中引入双向关系!
结论
我莫名其妙地感觉到,对双向关系的普遍看法是,它们是不好的,是“邪恶的”。也许这听起来太苛刻了,但像任何强大的功能一样,这个功能也有使用不当的风险。虽然双向关系本身并不讨厌,但它们可能产生的后果是你应该意识到的。
总之,尽量减少数据建模中双向关系的使用,除非你真的知道自己在做什么!
感谢阅读!
国王死了,国王万岁!
原文:https://towardsdatascience.com/king-is-dead-long-live-the-king-95201b81242c
快速开始你的汽车之旅

在 Unsplash 上拍摄的 ThisisEngineering RAEng
传统的机器学习模型开发过程已经死亡,新的流行词是 AutoML。AutoML 已经将机器学习带到了甚至不太懂技术的商业用户也能接触到的范围内。一些数据科学家使用 AutoML 工具来创建他们的机器学习模型。这些工具出色地为您的数据集提供了性能最佳的模型。AutoML 工具允许您基于经典方法和人工神经网络方法设计模型,创建有效的数据管道,使用集成优化模型的准确性,等等。
选择工具并不简单。在本文中,我将介绍我对一些工具的评估,并总结它们的优缺点。我希望,这有助于你决定你的目的的工具。这项研究也将帮助初学者或非数据科学家在 AutoML 领域快速起步。
我们是如何测试的?
尽管所有工具的 AutoML 过程或多或少是相同的,但它们对数据准备的要求不同。它们产生的输出也不同。我们对所有工具使用相同的数据集,以便您了解它们的数据准备要求。我们使用了两个数据集,一个专门用于回归任务,另一个用于分类任务。我从 UCI 资料库中获取了这两个数据集。
回归数据集包含 81 个决定超导体临界温度(目标)的数字特征。考虑到这个数据集的高维性,我选择它进行回归。该数据集包含 21263 个实例,足以测试回归问题。
用于分类的数据集包含 41 个属性(分子描述符),这些属性决定了该化学品是否可生物降解。这是一个带有两个目标值的标记数据集(就绪和未就绪,可生物降解)。因此,这是一个二元分类任务。数据集包含 1055 个实例。
我已经将两个数据集上传到我们的 GitHub 库。因此,您不必将数据集上传到您的 Google drive。您使用 wget 将代码下载到项目中。下载完数据集后,通常需要通过删除空值等操作来清理数据集。
所有的 autoML 工具都有内置的数据清理工具,因此您没有必要进行数据清理。你需要提取特征和目标。您还需要创建训练/测试数据集。对于分类任务,我已经*衡了数据集,因为一些工具在不*衡的数据集上不能正常工作。
由于这些是为模型训练准备数据的常见操作,所以我不在文章中描述数据准备代码;有关详细信息,您可以随时参考每个相关的可下载项目。对于某些工具,需要对数据集进行额外的准备。我将在文章中描述这些步骤。
我先从 auto-sklearn 说起。
自动 sklearn
这是该领域的早期启动者之一(2017 年 11 月),基于流行的 sklearn 机器学习库。最后一次发布是在 2021 年 11 月,因此该产品正在积极维护中。
要将此库添加到您的项目中,请运行以下代码片段:
!sudo apt-get install build-essential swig!pip install auto-sklearn==0.14.3
首先,我将向您展示如何使用自动回归器,然后是分类器。
自动回归器
要使用自动回归器,请使用以下代码:
import autosklearn
from autosklearn.regression import AutoSklearnRegressor
model_auto_reg = AutoSklearnRegressor(time_left_for_this_task=10*60,
per_run_time_limit=30,
n_jobs=-1)
model_auto_reg.fit(X_train_regressor,label_train_regressor)
由于 AutoML 工具可能需要很长时间才能找到最佳模型,auto-sklearn 库允许您指定代码执行的时间限制。我们将参数设置为 10 分钟。这是分配给每个算法的时间。该库测试许多算法,设置这个参数将结束那些算法花费太长时间来执行。
我们将参数每运行时间限制为 30 分钟。如果这个库没有在这个时间段内完成所有它想要的算法的测试,它将停止这个过程,并给你它已经完成的输出。
值为-1 的 n_jobs 参数告诉机器使用所有可用的内核。
如您所料, fit 方法将特性和目标作为参数。在它完成对所有算法的评估后,您可以打印其执行的统计数据:
print(model_auto_reg.sprint_statistics())
我在运行中得到以下输出:
auto-sklearn results:
Dataset name: 646225b0–8422–11ec-8195–0242ac1c0002
Metric: r2
Best validation score: 0.909665
Number of target algorithm runs: 80
Number of successful target algorithm runs: 18
Number of crashed target algorithm runs: 33
Number of target algorithms that exceeded the time limit: 5
Number of target algorithms that exceeded the memory limit: 24
正如您所观察到的,它成功运行了 80 个测试算法中的 18 个,33 个崩溃,5 个超出了 10 分钟的时间限制,24 个超出了内存限制。它仅用 10 分钟就完成了全部评估。
要查看最终模型,调用它的 show_models 方法。
model_auto_reg.show_models()
这显示了所创建的包括最高性能模型的集合模型。除非您希望进一步微调模型,否则您不必深入细节。在考虑之前,先检查一下误差指标。
y_pred_reg = model_auto_reg.predict(X_val_regressor)
error_metrics(y_pred_reg,label_val_regressor)
这给了我以下输出:
MSE: 89.62974521966439
RMSE: 9.467298728764419
Coefficient of determination: 0.9151071664787114
如果 R-2 得分高于 91%,您可能不想做进一步的调优。我现在将向您展示如何使用自动分类器。
自动分类器
您使用以下代码片段在我们的分类器数据集上应用自动分类器:
from autosklearn.classification import AutoSklearnClassifier
model_auto_class = AutoSklearnClassifier(time_left_for_this_task=10*60,
per_run_time_limit=30,
n_jobs=-1)
model_auto_class.fit(X_train_classifier,label_train_classifier)print(model_auto_class.sprint_statistics())
功能参数与自动回归的情况相同。
这是我得到的统计数据:
auto-sklearn results:
Dataset name: fa958b64–8420–11ec-8195–0242ac1c0002
Metric: accuracy
Best validation score: 0.899729
Number of target algorithm runs: 81
Number of successful target algorithm runs: 59
Number of crashed target algorithm runs: 16
Number of target algorithms that exceeded the time limit: 3
Number of target algorithms that exceeded the memory limit: 3
如你所见,在它测试的 81 个算法中,59 个成功运行,只有 3 个超过了时间限制。同样,您可以通过调用 model 的 show_models 方法来检查生成的模型。使用以下代码片段打印分类报告:
y_pred_class = model_auto_class.predict(X_val_classifier)
print(classification_report(label_val_classifier,y_pred_class))
这是我运行的输出:

作者图片
你可以看到集合模型在我们的数据集上产生了很好的结果。
这个项目的源代码可以在我们的 GitHub 库中找到。
现在,我将向您展示如何在这两个数据集上使用 AutoKeras。
AutoKeras
AutoKeras 采用神经网络的方法进行模型开发。它将设计一个包含最佳层数和节点数的网络。
装置
使用以下代码片段安装 AutoKeras:
!sudo pip install git+https://github.com/keras-team/keras-tuner.git@1.0.2rc1!pip install autokeras!pip show autokeras
自动回归器
我定义了一个回调,用于调整网络训练时的学习速率。
from tensorflow.keras.callbacks import ReduceLROnPlateau
lr_reduction = ReduceLROnPlateau(monitor=’mean_squared_error’,
patience=1,
verbose=1,
factor=0.5,
min_lr=0.000001)
使用以下代码应用自动回归:
from autokeras import StructuredDataRegressorregressor = StructuredDataRegressor(max_trials=3,
loss=’mean_absolute_error’)
regressor.fit(x=X_train_regressor, y=label_train_regressor,
callbacks=[lr_reduction],
verbose=0, epochs=20)
正如您在上面的代码中看到的,您只需要设置试验次数和回归损失函数。
创建模型后,您可以进行预测并打印误差指标。在我的运行中,我得到了以下输出:
MSE: 163.16712072898235
RMSE: 12.7736886109292
Coefficient of determination: 0.8515213997277571
自动分类器
使用以下代码创建分类器的模型:
from autokeras import StructuredDataClassifierclassifier = StructuredDataClassifier(max_trials=5,num_classes=2)classifier.fit(x=X_train_classifier, y=label_train_classifier,
verbose=0, epochs=20)
您将类的数量设置为 2,因为我们正在解决一个二元分类问题。
与前面一样,在创建模型之后,您进行推理并打印分类报告。这是我运行的输出:

作者图片
这个项目的源代码可以在我们的 GitHub 库中找到。
我们的下一个图书馆是 TPOT
TPOT
您可以使用 pip 安装来安装 TPOT:
!pip install tpot
自动回归器
使用以下代码应用回归量:
from sklearn.model_selection import RepeatedKFold
cv = RepeatedKFold(n_splits=2, n_repeats=2, random_state=1)from tpot import TPOTRegressor
model_reg = TPOTRegressor(generations=5, population_size=50, scoring=’neg_mean_absolute_error’, cv=cv, verbosity=2, random_state=1, n_jobs=-1)model_reg.fit(X_train_regressor,label_train_regressor)
这是我的测试运行的评估指标
MSE: 78.55015022333929
RMSE: 8.862852262299045
Coefficient of determination: 0.9260222950313017
R-2 系数为 0.925,相当不错。
自动分类器
使用以下代码应用分类器:
from sklearn.model_selection import RepeatedStratifiedKFold
cv = RepeatedStratifiedKFold(n_splits=2, n_repeats=2, random_state=1)from tpot import TPOTClassifier
model_class = TPOTClassifier(generations=3, population_size=50, cv=cv, scoring=’accuracy’, verbosity=2, random_state=1, n_jobs=-1)
model_class.fit(X_train_classifier, label_train_classifier)
这是我测试运行的分类报告:

作者图片
这个项目的源代码可以在我们的 GitHub 资源库中找到。
MLBox
和前面的例子一样,使用 pip 命令安装 MLBox:
!pip install mlbox
数据准备
与早期工具相比,MLBox 需要额外的数据准备和一些设置。它从 CSV 文件中获取由要素和标注组成的训练数据集。因此,与您通常在其他工具中使用的 dataframe 不同,您需要创建 CSV 文件来训练该模型。像训练数据集一样,它从 CSV 文件中提取测试数据集。当您进行推理时,模型将结果保存在一个 CSV 文件中,因此您需要将它加载到您的程序中来解释结果。就其他要求而言,您需要设置超参数。它为您提供了一个优化器来确定这些参数的最佳值。
有了这样的认识,我现在给你详细的数据准备步骤。让我们首先从回归开始。
自动回归器
如前所述,将超导体 CSV 文件加载到数据框中并提取特征和标签:
regressor_df=pd.read_csv(‘/content/superconductors.csv’)features_regressor = regressor_df.iloc[:,:-1]
label_regressor = regressor_df.iloc[:,-1]
然后,创建训练/测试数据集:
X_train_regressor, X_test_regressor, label_train_regressor, label_test_regressor = train_test_split(features_regressor, label_regressor, test_size=0.2, random_state=42)
我们通过向训练数据添加标签,然后将数据框转换为 CSV 文件来创建训练数据集 CSV 文件。
training1 = pd.concat([X_train_regressor, label_train_regressor], axis=1)training1.to_csv(‘training1_file.csv’)
同样,我们创建测试数据集文件,这一次没有添加标签数据。
testing1 = X_test_regressortesting1.to_csv(‘testing1_file.csv’)
现在,我们需要创建一个数据框,使用我们创建的训练文件来训练我们的模型。我们将把训练数据分成训练集和验证集。
paths=[‘training1_file.csv’, ‘testing1_file.csv’]
rd = Reader(sep = ‘,’)
df = rd.train_test_split(paths, target_name=’critical_temp’)
Reader 函数在完成后会生成一些统计数据,如下所示:

作者图片
请注意,它将任务检测为具有 81 个数字特征的回归类型,并将 critical_temp 作为目标。它将整个数据分成 16969 个训练点和 4253 个验证数据点。
接下来,通过调用 Drift 转换来检查列中的差异,这将自动删除不需要的列。
dft = Drift_thresholder()
df = dft.fit_transform(df)
这是我运行的输出:

作者图片
在我们的例子中,它没有丢弃任何变量。
接下来,我们将找到超参数的最佳值。我们指定每个参数的范围:
space = {‘ne__numerical_strategy’:{“space”:[0]},
‘fs__strategy’:{“search”:”choice”,
“space”:[‘l1’,’variance’,’rf_feature_importance’]},
‘fs__threshold’:{“search”:”uniform”,
“space”:[0.01,0.3]},
‘est__strategy’ : {“search”:”choice”,
“space” : [‘LightGBM’, ‘RandomForest’,
‘ExtraTrees’, ‘AdaBoost’]},
‘est__max_depth’:{“search”:”choice”,
“space”:[3,4,5,6]},
‘est__n_estimators’:{“search”:”choice”,
“space”:[250,500,700]}}
我们创建了一个优化器,它具有我们想要试验的折叠数,并运行了 20 个时期:
opt=Optimiser(n_folds=3)
best=opt.optimise(space,df,20)
过一会儿,您将获得最佳的超参数,如下图所示:
{‘est__max_depth’: 5, ‘est__n_estimators’: 700, ‘est__strategy’: ‘LightGBM’, ‘fs__strategy’: ‘l1’, ‘fs__threshold’: 0.290701184999186, ‘ne__numerical_strategy’: 0}
将构建的模型和微调的超参数分配给我们指定的最佳变量。您可以使用这个模型变量来进行推断。
prd = Predictor()
prd.fit_predict(best, df)
它将推理结果保存到 CSV 文件中。 fit_predict 方法显示前 10 个数据点的预测目标值,如下图所示:

作者图片
您读取生成的 CSV 文件来创建一个错误指标,如下所示:
class_predicted_mlbox=pd.read_csv(‘/content/save/critical_temp_predictions.csv’)error_metrics(label_test_regressor, class_predicted_mlbox[‘critical_temp_predicted’])
这是我运行的输出:
MSE: 87.77266825021174
RMSE: 9.36870686115281
Coefficient of determination: 0.9237475817171334
我们得到了大约 0.93 的 R-2 分数。现在让我们检查分类器。
自动分类器
分类器任务的数据准备与回归器的情况相同。创建一个包含特征和目标的训练 CSV 文件。您创建了一个只有特性而没有目标的测试文件。你用阅读器准备数据帧。
paths=[‘training2_file.csv’, ‘testing2_file.csv’]
rd = Reader(sep = ‘,’)
df2 = rd.train_test_split(paths, target_name=’label’)
这是我运行中的部分输出:

作者图片
你看,这次的任务是分类,有 41 个特征。我们有 1116 个数据点用于训练,280 个数据点用于测试。
您可以像前面一样使用漂移变换来检查方差。
dft = Drift_thresholder()
df2 = dft.fit_transform(df2)
您像前面一样声明了空间变量,用于设置您想要测试的参数值。您创建一个以精确度作为评分标准的优化器,并运行它 20 个时期,就像回归器的情况一样。
best=opt.optimise(space,df2,20)
最后,您将获得带有微调参数的最佳模型,您可以直接将其用于预测:
prd = Predictor()
prd.fit_predict(best, df2)
如前所述,预测存储在 CSV 文件中,您可以读取该文件来创建分类报告:
reg_predicted_mlbox=pd.read_csv(‘/content/save/label_predictions.csv’)print(classification_report(reg_predicted_mlbox[‘label_predicted’], label_test_classifier))
这是我运行的输出:

作者图片
如果我们有更多的数据点,准确性会更好。
这个项目的源代码可以在我们的 GitHub 资源库中找到。
与早期的库相比,你会发现 MLBox 使用起来更复杂,但它确实创建了一个优秀的模型。让我们现在去下一个图书馆。
密封罐
这是最容易使用的工具之一。您只需要设置期望的执行时间,并为其提供训练/测试数据集。要安装该工具,您将使用常用的 pip 命令:
!pip install mljar-supervised
自动回归器
要在我们的回归数据集上应用模型,您需要像在 auto-sklearn 案例中那样创建训练/测试数据框。使用以下代码应用模型:
automl_reg = AutoML(total_time_limit=2*60)
automl_reg.fit(X_train_regressor, label_train_regressor)
注意,我们刚刚设置了执行的时间上限。这是我运行的输出:
Linear algorithm was disabled.
AutoML directory: AutoML_1
The task is regression with evaluation metric rmse
AutoML will use algorithms: [‘Baseline’, ‘Decision Tree’, ‘Random Forest’, ‘Xgboost’, ‘Neural Network’]
AutoML will ensemble available models
AutoML steps: [‘simple_algorithms’, ‘default_algorithms’, ‘ensemble’]
* Step simple_algorithms will try to check up to 2 models
1_Baseline rmse 34.418723 trained in 0.95 seconds
2_DecisionTree rmse 18.711229 trained in 13.32 seconds
* Step default_algorithms will try to check up to 3 models
3_Default_Xgboost rmse 9.894658 trained in 85.39 seconds
* Step ensemble will try to check up to 1 model
Ensemble rmse 9.894658 trained in 0.24 seconds
AutoML fit time: 149.47 seconds
AutoML best model: 3_Default_Xgboost
AutoML(total_time_limit=120)
正如您所观察到的,在短短两分钟的时间内,它已经测试了所有指定的算法——基线、决策树、随机森林和 Xgboost。它创造了这些模型的集合。对于每个模型,记录 rmse 和执行时间。执行之后,它会将所有结果存储在一个特定的文件夹中,以供您随时使用。它还生成一个包含比较矩阵的 CSV 文件。
使用以下语句对测试数据集进行预测:
prediction_reg_ml = automl_reg.predict_all(X_test_regressor)
您可以使用以下命令打印错误度量:
error_metrics(prediction_reg_ml, label_test_regressor)
这是我运行的输出:
MSE: 85.32699931235351
RMSE: 9.237261461729526
Coefficient of determination: 0.920540531168528
您可以通过阅读 CSV 文件来打印生成的排行榜:
pd.read_csv(‘/content/AutoML_1/leaderboard.csv’)
这是输出:

作者图片
接下来,我们看看分类器。
自动分类器
分类器数据的准备方式与 auto-sklearn 中的准备方式相同。使用以下代码应用分类器:
automl_class = AutoML(total_time_limit=2*60)
automl_class.fit(X_train_classifier, label_train_classifier)
这是输出:
AutoML directory: AutoML_2
The task is binary_classification with evaluation metric logloss
AutoML will use algorithms: [‘Baseline’, ‘Linear’, ‘Decision Tree’, ‘Random Forest’, ‘Xgboost’, ‘Neural Network’]
AutoML will ensemble available models
AutoML steps: [‘simple_algorithms’, ‘default_algorithms’, ‘ensemble’]
* Step simple_algorithms will try to check up to 3 models
1_Baseline logloss 0.693124 trained in 0.7 seconds
2_DecisionTree logloss 0.526573 trained in 6.5 seconds
3_Linear logloss 0.321759 trained in 5.9 seconds
* Step default_algorithms will try to check up to 3 models
4_Default_Xgboost logloss 0.234433 trained in 15.61 seconds
5_Default_NeuralNetwork logloss 0.813183 trained in 3.01 seconds
6_Default_RandomForest logloss 0.367987 trained in 17.92 seconds
* Step ensemble will try to check up to 1 model
Ensemble logloss 0.234019 trained in 0.81 seconds
AutoML fit time: 80.51 seconds
AutoML best model: Ensemble
AutoML(total_time_limit=120)
如您所见,在短短 2 分钟内,它已经测试了所有指定的型号。最好的模式是合奏。您对测试数据集进行预测,并打印分类报告:
prediction_class = automl_class.predict(X_val_classifier)
print(classification_report(label_val_classifier, prediction_class))
这是输出:

作者图片
如前所述,您可以通过读取生成的 CSV 文件来打印排行榜:
pd.read_csv(‘/content/AutoML_2/leaderboard.csv’)
这是输出:

作者图片
这个项目的源代码可以在我们的 GitHub 资源库中找到。
这个工具非常容易使用,并且在很短的时间内产生了很好的效果。
我们的下一个工具是 H2O。
H2O
这是一个被广泛认可的开源机器学习*台。它是强大的;开发高性能回归和分类模型,但需要额外的数据准备。我将详细介绍使用这个库的所有步骤。
要安装 H2O,只需 pip 安装以下软件包:
! apt-get install default-jre!java -version!pip install h2o
成功安装后,您需要启动 H2O 服务器:
import h2o
from h2o.automl import H2OAutoML
h2o.init()
服务器启动后,您就可以进行模型开发了。
自动回归器
首先,你需要准备数据。您可以像在 auto-sklearn 中那样创建训练/测试数据集。H2O 需要特殊格式的数据。使用以下命令将您的数据帧转换为H2 of frame:
h2o_train1=h2o.H2OFrame(pd.concat([X_train_regressor, label_train_regressor], axis=1))
你分离出特征和目标:
x1 = h2o_train1.columns
y1 = ‘critical_temp’
x1.remove(y1)
同样,将测试数据转换为 H2O 数据框:
h2o_test1=h2o.H2OFrame(pd.concat([X_test_regressor, label_test_regressor], axis=1))
您为选定数量的模型应用 autoML。以下代码片段测试了 5 个模型:
model_reg = H2OAutoML(max_models=5, seed=1)
model_reg.train(x=x1, y=y1, training_frame=h2o_train1)
即使是几个模型也需要相当长的时间。培训过程完成后,您可以打印排行榜。
lb = model_reg.leaderboard
lb.head(rows=lb.nrows)
这是我运行的输出:

作者图片
遗憾的是,它没有透露型号配置。使用排行榜中的顶级模型,您可以根据您的测试数据进行推断:
preds = model_reg.leader.predict(h2o.H2OFrame(X_test_regressor))
预测采用 H2O 数据框格式,因此您必须将其转换为 numpy 数组来打印误差度量。将 H2O 数据框转换成如下所示的 numpy 数组:
np.array(preds[‘predict’].as_data_frame())
然后,打印误差指标:
error_metrics(np.array(preds[‘predict’].as_data_frame()), label_test_regressor)
这是我运行的输出:
MSE: 77.42394821724238
RMSE: 8.79908791962226
Coefficient of determination: 0.9281053116039564
接下来,我们将看看分类器。
自动分类器
最初,您将像在 auto-sklearn 中一样创建数据框。然后将它们转换成 H2O 数据框。
h2o_train2=h2o.H2OFrame(pd.concat([X_train_classifier, label_train_classifier], axis=1))
您将特征和目标分开:
x2 = h2o_train2.columns
y2 = ‘label’
x2.remove(y2)
将测试数据帧转换为 H2O 数据帧格式:
h2o_test2=h2o.H2OFrame(pd.concat([X_val_classifier, label_val_classifier], axis=1))
对于分类,我们需要一个额外的步骤将标签转换成特殊的格式。我们通过在目标变量上调用 asfactor 方法来做到这一点。
h2o_train2[y2] = h2o_train2[y2].asfactor()
h2o_test2[y2] = h2o_test2[y2].asfactor()
然后,我们在 5 个基本模型上运行 autoML。
model_class = H2OAutoML(max_models=5, seed=1)
model_class.train(x=x2, y=y2, training_frame=h2o_train2)
培训结束后,您可以查看排行榜:
lb = model_class.leaderboard
lb.head(rows=lb.nrows)
如前所述,使用排行榜中的顶级模特进行推断:
preds = model_class.leader.predict(h2o.H2OFrame(X_test_classifier))
将结果转换成 numpy 数组并打印分类报告:
np.array(preds[‘predict’].as_data_frame())print(classification_report(label_test_classifier.values, np.array(preds[‘predict’].as_data_frame())))
这是我运行的输出:

作者图片
虽然它产生了良好的结果,但它没有给出模型配置及其超参数值。
这个项目的源代码可以在我们的 GitHub 资源库中找到。
最后,我将介绍另一个最*发布的 AutoML 工具,它具有数据科学家一直期待的一些额外功能。
BlobCity AutoAI
这是最*在 AutoML 空间中增加的内容(2021 年 10 月发布)。一些对数据科学家来说很珍贵的特性,可以在这个库中看到。它泄露了完整的项目源代码——这对数据科学家来说是一大福音。也很好用。要安装库,再次使用 pip install。
!pip install blobcity
此外,您需要安装 AutoKeras。很可能,它使用 AutoKeras 为你生成 ANN 模型。
!sudo pip install git+https://github.com/keras-team/keras-tuner.git@1.0.2rc1!pip install autokeras
安装完库之后,重新启动运行时并将其导入到项目中
import blobcity as bc
数据准备阶段与 auto-sklearn 的情况相同。首先,我们将在回归数据集上尝试模型拟合。
自动回归
model_reg = bc.train(df=pd.concat([X_train_regressor, label_train_regressor], axis=1), target=”critical_temp”)
在模型被训练之后,我可以生成一个特征重要性的图表。
model_reg.plot_feature_importance()
这是输出:

作者图片
了解要素的重要性有助于数据科学家为最终的模型开发准备数据集。
我可以绘制实际值与预测值的关系图:
model_reg.plot_prediction()

作者图片
最后,我生成了错误指标:
prediction_reg=model_reg.predict(X_val_regressor)
error_metrics(label_val_regressor, prediction_reg)
这是输出:
MSE: 77.74966208588508
RMSE: 8.817576882901848
Coefficient of determination: 0.9324550583579856
与我们到目前为止测试的所有模型相比,该模型给了我们极好的 R-2 分数。现在,让我们在分类器数据集上检查它的性能。
自动分类器
数据准备和我们为 auto-sklearn 做的一样。您可以使用以下命令应用该工具:
model_class = bc.train(df=pd.concat([X_train_classifier,label_train_classifier], axis=1), target=”label”)
这是分类报告:

作者图片
它通过调用模型的溢出方法,在 Jupyter 笔记本中溢出全部源代码和文档:
model_class.spill("my_code.ipynb");
它也能产生。py 文件,您可以将它加载到您喜欢的 IDE 中。
您还可以将生成的模型保存到. pkl 文件中。auto-sklearn 和 mljar 中也有类似的功能。
model_class.save('./my_model.pkl')
您可以通过调用 load 方法来加载先前保存的模型文件,以便进行进一步的推断。
model = bc.load('./my_model.pkl')
它还支持用于 ANN 的. h5 格式和自定义 TensorFlow 文件系统。
这个项目的源代码可以在我们的 GitHub 库中找到。
我现在将综合所有的观察结果。
合并报告
下表显示了分类器任务的整合。

作者图片
所有工具的精确度几乎相同。创建模型的执行时间显示出很大的差异。
下表合并了回归任务的报告。

作者图片
同样,所有工具的模型精度或多或少是相同的。在分类器的情况下,执行时间变化很大。
请注意,执行时间的差异是由于每个工具生成的细节和报告的差异。
最后,在下面的表格中,我总结了一些特性,这些特性将有助于您选择合适的工具。
自动 ML 特征比较

作者图片
摘要
随着对 AutoML 认识的增加,甚至非数据科学家也使用它来开发 ML 模型。本文提供了我们对一些广泛使用的工具进行测试的综合报告。市场上可用的工具数量非常多,我们无法囊括其中的许多工具。这个报告可以让你快速开始你的 AutoML 之旅,并帮助你选择工具。所有测试工具都有免费版本,其中一些是开源的。这些工具在模型生成和报告能力上有所不同。在该报告测试的所有内容中,BlobCity AutoAI 是详细说明该项目的完整源代码的一个,这是一个数据科学家非常想要的功能。
参考资料:
- 超导体数据集 : Hamidieh,Kam,预测超导体临界温度的数据驱动统计模型,计算材料科学,第 154 卷,2018 年 11 月,第 346–354 页,【网页链接】
- QSAR 生物降解数据集:曼苏里,k .,林斯特德,t .,巴拉比奥,d .,托代西尼,r .,康索尼,V. (2013)。化学物质快速生物降解的定量结构-活性关系模型。化学信息与建模杂志,53,867–878
信用
Pooja Gramopadhye —文字编辑
乔治·萨维德拉 —程序开发
免责声明:本文中的信息仅用于教育目的。作者对本文内容中的任何错误或遗漏不承担任何责任或义务。本文包含的信息按“原样”提供,不保证完整性、准确性、有用性或及时性。
https://medium.com/subscribe/@profsarang https://medium.com/@profsarang/membership
告别你的偏见:监督学习的基础理论不完整吗?
深度学习明显违反了偏差-方差权衡,引发了所有预测建模的问题
偏差-方差权衡是所有监督学习的试金石。它在每门统计学入门课程和数据科学训练营中都有讲授,因为这是我们最小化任何模型的总预测误差的方法。对于回归问题,均方误差有一个简单的数学分解,分解为偏差项(真实函数的*似误差)、方差项(我们试图拟合的函数估计值的方差)和不可约误差。对于其他类型的误差,如分类的 0–1 损失,没有好的方法将误差分解成其组成部分,但我们可以通过学习曲线经验地测量权衡(或至少接*)。
当我第一次看到一项深度学习研究显示出奇怪的偏差-方差权衡时,我立刻否定了它。模型中的参数数量超过了数据点的数量,并且完全符合数据中的噪声。就像用高次多项式拟合线性趋势一样,模型不可能尽可能好地推广到新数据。虽然这项研究显示了良好的测试集错误率,但我认为结果是由于泄漏或不可重现的错误。

托马斯·波吉奥等人。艾尔。,“深度学习理论三:解释非过拟合难题”,Image,Arxiv,1/16/2018,https://arxiv.org/abs/1801.00173
随着深度学习变得越来越受欢迎,更多的研究得到了类似的结果,往往是错误率出现奇怪的峰值,即模型中的参数数量接*训练集中的样本数量。偏差-方差权衡是监督学习的核心要素,但所有这些结果似乎都违背了它。发生了什么事?
双重下降现象
为什么高度参数化的神经网络不会过度拟合数据的问题至少可以追溯到 Breiman 在 1995 年提出的问题。但对不同架构的不同数据集进行的不同深度学习研究显示,这一有趣的结果是可重复的,专家将其命名为“双重下降”现象。贝尔金等人。艾尔。第一个为这一现象提供了清晰的概念性解释。“经典的”偏差-方差权衡一直保持到模型对训练数据进行插值,此时泛化误差开始随着模型复杂性的增加而再次改善。

正则化过度参数化机制中偏差-方差权衡的说明(Jason Capehart,2022)
他们发现的另一个重要进展是,过度参数化机制中的模型行为并不是神经网络所独有的。这种现象可以扩展到决策树模型和集成。随后,哈斯蒂等人。艾尔。采用理论方法来研究双重下降,并证明了最小二乘回归(最简单的可能模型之一)基于信噪比、协方差矩阵和特征各向同性的各种选择来展示该现象。他们还证明,当模型被错误指定时,线性模型的风险的全局最小值可能存在于过度参数化的区域中。增加超过插值阈值的参数数量会增加模型的*似能力,并减少他们所谓的“错误设定方差”,该方差可能与“错误设定偏差”不成比例。其核心是,这为过度参数化为什么有益提供了令人信服的论据。随着参数数量的增加,偏差通常会减少,但超过插值阈值后,减少方差项中错误设定的误差会产生最优解。
去年 Dar et。艾尔。回顾了大量关于过度参数化学习的前期工作,并针对具有“n”个样本和“p”个参数的模型进行了总结:
- “经典”偏差-方差权衡适用于欠参数化区域,其中 p < n
- 插值解,其中 p ~ n 在欠参数化和过参数化区域之间的过渡处具有高泛化误差,除非模型被正则化
- 当模型被错误指定时,p > n 的过参数化模型可以实现更好的泛化,但是插值何时是比正则化更好的策略尚不清楚
过度参数化学习的理论仍然无法解释该领域的最新实践,因为它主要局限于简单的参数模型。也就是说,这个理论清楚地表明,我对双重下降现象的最初反应是错误的。这些不是神经网络魔法所特有的虚假结果——深度学习已经促使人们重新评估偏差-方差权衡,目前,它的影响似乎扩展到了所有监督学习。
这在实践中意味着什么?
我是一名从业者,这意味着我通常不会关注人工智能研究中的新理论。我感兴趣的大多数问题是因果性的,而不是预测性的,即使它们是预测性的,通常也不是关于固有的稀疏数据。尽管这些结果令我震惊,但还是有几个问题需要澄清:
偏倚-方差权衡是错误的吗?
不。偏差-方差权衡成立,插值阈值之外的权衡本质以前并不为人所知。或者,就我自己而言,我以前对偏差-方差权衡的理解是不完整的。尽管我一直坚持“所有的模型都是错误的”这一概念,但我从未考虑过随着模型容量的增加,模型的错误设定会如何分别影响偏差和方差。戈曼等人。艾尔。30 年前我可能已经明白了这一点,但我认为公*地说,这方面的工作直到最*才得到充分重视。
插值模型总是最好的模型吗?
不。它取决于生成分布的数据,并且在模型被很好指定的任何情况下,最佳误差应该在参数化不足的范围内。
插值比正则化好吗?
这看起来像一个开放式的问题。理论结果(参见 Dar 等人。艾尔。第 3.3 节回顾)对具有更强数据假设的更简单模型的研究表明,最佳正则化应始终比插值具有更低的误差率。但这些理论研究是由更复杂的插值模型在实践中的成功推动的,这些模型的假设更少。插值优于正则化或正则化优于插值的情况仍有待研究。
过度参数化学习提出了哪些新问题?
有三个让我感兴趣。
- 测量插值模型复杂性的好方法是什么?使用参数的数量作为模型容量的度量似乎是不够的,特别是对于处于或接*过参数化状态的模型,这些模型具有显式或隐式正则化。
- 有没有好的方法来测量过度参数化模型的校准?深度学习模型是出了名的过于自信,对于任何插值其训练数据的模型,我甚至不清楚像普拉特缩放这样的事后校准方法是否有意义。
- 我们如何解决过度参数化模型中的公*性问题?偏见,在偏见的意义上而不是统计意义上,存在于所有真实世界的数据集中。据推测,除非应用公*约束,否则插值数据的模型特别容易强化历史偏见。
过度参数化的学习是否会影响信号处理或深度学习以外的其他领域仍有待观察。目前,超参数化是机器学习中一个有趣的新方向。
参考
- 贝尔金等人。艾尔。,调和现代机器学习实践和偏差-方差权衡(2019),https://arxiv.org/abs/1812.11118
- 哈斯蒂等人。艾尔。、高维无脊最小二乘插值中的惊喜(2019)【https://arxiv.org/abs/1903.08560
- 达尔等人。艾尔。,告别偏差-方差权衡?超参数化机器学习理论综述(2021),【https://arxiv.org/abs/2109.02355
- 贝尔金等人。艾尔。,过拟合还是完美拟合?内插分类和回归规则的风险界限(2018),https://arxiv.org/abs/1806.05161
- Shalizi,从初级观点看高级数据分析(2021),https://www.stat.cmu.edu/~cshalizi/ADAfaEPoV/
- 德国 et。艾尔。,神经网络与偏差/方差困境(1992),https://www . dam . brown . edu/people/documents/Bias-Variance . pdf
- Breiman,参考 NIPS 文件后的思考(1995 年),https://www.gwern.net/docs/ai/scaling/1995-breiman.pdf
- 波焦等人。艾尔。、深度学习理论三:解释非过拟合难题(2018)https://arxiv.org/abs/1801.00173
KNN 算法从零开始
原文:https://towardsdatascience.com/knn-algorithm-from-scratch-37febe0c15b3
KNN 算法的实现和细节解释

Guillermo Ferla 在 Unsplash 上拍摄的照片
KNN 的背景
KNN 代表 K *邻。名字本身表明它考虑最*的邻居。它是一种有监督的机器学习算法。有趣的是,我们可以用该算法解决分类和回归问题。它是最简单的机器学习模型之一。虽然这是一个简单的模型,但有时它扮演着重要的角色,基本上是当我们的数据集很小,问题很简单的时候。该算法也称为懒惰算法。这些是 KNN 算法的总结。
我将从 KNN 最基本的东西开始解释,这样你就能用心理解这篇文章了。文末可以自己实现算法(不需要任何机器学习库)。
目录
[**Euclidean Distance**](#5bee)[**Overview of the KNN Algorithm**](#43fe)[**Why is KNN a Lazy Algorithm?**](#143a)[**Step-by-Step Implementation of KNN**](#143a)
欧几里得距离

作者图片
这里,***(X1, Y1)***和 ***(X2, Y2)***是图像中显示的两个点。我们可以用下面的公式计算两点之间的距离。

如果我们有两个以上的特征,我们需要将距离的*方加到上面的公式中来得到距离。
KNN 算法概述
该名称表明该算法考虑最*的元素来预测新数据的值。流程图显示了 KNN 的步骤。

KNN 算法流程图(图片由作者提供)
我来解释一下。
第一步:计算距离
首先,我们需要加载带标签的数据集,因为 KNN 算法是一种监督学习算法。看下图。

与数据点的距离(图片由作者提供)
假设我们的数据集只有两个要素,我们绘制的数据如图所示。蓝点和红点表示两个不同的类别。让我们拥有新的未标记的数据,这些数据需要基于给定的数据集进行分类。
在图像中,需要对中心点进行分类。现在,我们将计算所有数据与未标记数据之间的距离。从中心点开始的箭头代表距离。
步骤 2:选择 K-最*邻
在上一步中,我们计算了新点与所有其他数据的距离。我们将根据距离对数据点进行升序排序。最后,我们将考虑来自未标记数据的最*点的数量 K 。

作者图片
在上图中,我已经考虑了 3 最*的数据点 (K=3) 。观察形象;在的 3 个最*点中,2 个数据属于红色类别,1 个属于蓝色类别。所以,红色是多数阶级。根据 KNN 算法,新的数据点将被归类为红色。
遇到回归问题,我们会考虑 K 个最*数据点的*均值。
为什么 KNN 是一个懒惰的算法?
KNN 没有训练期。对于每个预测,算法需要经历相同的过程。在训练期间没有可以优化的参数。所以,这是一个懒惰的算法。当数据集很大时,预测需要更长的时间。
从零开始实施 KNN
让我们写几行代码来实现算法。
导入模块。
创建计算距离的函数。
**euclidean** 函数有两个参数,即p1和p2。根据**Euclidean Distance** 部分解释的公式,该函数将计算从p1点到p2点的距离。
下一步,我们将编写一个函数,用于保存数据集的每个点与新数据点的距离,并整理数据。最后,我们将选择具有多数类的新数据点的类。
我们已经创建了**‘predict’** 函数来查找一组新数据点的预测。让我们使用我们的 ‘predict’函数来获得**iris** 数据集的预测。
这里,我们已经手动选择了训练和测试数据。我们首先将数据随机化以防止偏差。然后我们选择 80%的数据进行训练,剩下的进行测试。最后,我们针对 7 个最*邻居(k=7)测试了我们的模型。
文章[1]帮助我实现了 KNN 算法。
大功告成。我们从零开始实施 KNN。我们喝杯咖啡,思考一下算法。如果有任何困惑,不要忘记发表评论(或联系我)。

结论
KNN 算法似乎非常简单。但有时,它在解决重要的机器学习问题方面发挥着重要作用。当我们的数据有噪音时,我们需要解决简单的问题。总是跑向深度学习模型是不可取的,因为它需要巨大的计算能力和数据。如果我们总是盲目地跳过深度学习模型,我们不会得到一个好的结果。良好的实践是对所有的 ML 模型有深入的直觉,并通过分析问题做出适当的决策。
此处提供了完整的实现。
参考
阅读关于数据科学统计的热门文章(完整指南)
Python 中的 KNN 回归模型
原文:https://towardsdatascience.com/knn-regression-model-in-python-9868f21c9fa2
将线性模型的简单性与 K *邻的强大功能相结合

回归
回归是一种非常简单的算法。在 Medium 中快速搜索一下,你会发现数百个关于线性回归的帖子。
因此,我不会花太多时间来定义它。我只想说
线性回归是一种统计建模工具,它帮助我们根据解释变量和响应变量之间的线性关系来预测值。
k-最*邻
K-最*邻(或简称为 KNN)算法通过获取给定点并评估其“K”个邻居来寻找相似性。它可用于分类或回归。
这个想法是基于这样一个事实:在空间中,相似的数据点比彼此差异很大的数据点更接*。所以,如果你观察下面的图片,它非常有意义。让我们选择点 X 并查看它的 5 个最*的邻居,然后我可以更好地确定那个点是什么。

点 X 很可能是另一个三角形。你同意吗?图片由作者提供。
请注意,这些最*的点中有 4 个是三角形,只有一个是正方形。圆“集群”是太远的方式来考虑。所以,根据 KNN 逻辑,我们可以断定 X 点是一个三角形。
KNN 回归
KNN 回归逻辑与上面图片中解释的非常相似。唯一的区别是,它与数字打交道。因此,来自sklearn库的KNeighborsRegressor()算法将做的是计算数据集的回归,然后用选择的数字获取n_neighbors参数,检查那些邻居的结果并对结果取*均,给你一个估计的结果。
文献以一种奇特的方式说。看:
通过对与训练集中的最*邻居相关联的目标进行局部插值来预测目标。
示例代码
以下是使用 KNN 回归器的模型代码。
本练习中使用的数据集是来自 seaborn 的拥有知识共享许可的 Diamonds 。我会根据切割来模拟钻石的克拉。
首先,让我们创建一个 pairplot 来检查最佳线性关系。

将解释变量与目标变量配对。图片由作者提供。
从这个图中,我们可以看到好的变量是 x,y 和 z,,因为它们的散点图显示那里有一个可能的线性回归。
所以我隔离了他们。
X **=** df[['x', 'y', 'z']]
y **=** df**.**carat# Train test split
X_train, X_test, y_train, y_test **=** train_test_split(X, y, test_size**=** 0.2, random_state**=**12)
然后我检查缺失值。根本没有。
我继续移除离群值。

包含和不包含异常值的数据集。图片由作者提供。
我们可以看到,没有异常值的数据集显示了一条轻微的指数曲线。所以我也尝试了对数转换,以查看更好的线性关系。

对数变换后。图片由作者提供。
下一步是建立数据模型。
我已经创建了 3 个不同的模型。无对数变换的线性回归。对数变换线性回归和 KNN 回归。
这是结果。

车型对比。图片由作者提供。

KNN vs 对数转换回归。图片由作者提供。
结果对 KNN 来说相当不错。它在右端有几个高估的值,但可以接受。好的方面是,它甚至不需要变量转换就给了我这么好的改进(几乎比标准 LR 高 5%)。所以,你能看出 KNN *均线对于线性回归有多有意思吗?这听起来像是“回归的装袋模型”的简单版本。
对于对数变换 LR,当它在左端显示最大误差时,发生相反的情况。
在你走之前
我鼓励你看看 KNN 回归,看看它适合你的项目。
这是另一种回归,你可以将它添加到你的工具箱中,以便能够建立更强大和更准确的模型,而不一定比它需要的更复杂。
数据科学不是关于复杂的事情,而是能够解决问题。
完整代码:
如果你想订阅 Medium,这里有一个推荐代码。
另外,我的博客有更多的内容。
https://gustavorsantos.medium.com/
Git Hub 代码:完整的代码可以在这里找到。
参考
https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsRegressor.html
knock knock——可能是最好的通知 Python 库
获得 Python 任务通知的简单而有效的方法!

介绍
有些任务需要时间来完成,对于机器学习模型训练来说尤其如此,因为很难准确估计训练何时结束。此外,在整个培训过程完成之前,您可能会处理不同的故障诊断,这可能会使您难以专注于其他任务,从而降低工作效率。
如果你能建立一个自动通知系统来获得任务状态的通知,不管它是崩溃还是成功完成,这不是很好吗?**knockknock**就是在这个视角下运作的。
在本文中,你将通过一些例子来理解什么是knockknock及其说明。
什么是叩叩?
knockknock是一个来自抱脸的通知开源库。它旨在向用户发送通知,告知他们其功能的状态(例如,如前所述的模型训练状态)。在撰写本文时,通知设置可以在十二个不同的*台上执行,如 Slack、Email、丨t丨e丨l丨e丨g丨r丨a丨m丨s丨、Microsoft Teams、Discord、SMS 等。整个列表可以在这里找到。
此外,安装可以通过一个 pip 命令来完成,如下所示。
pip install knockknock
开始
我们将举例说明knockknock在不和谐和懈怠中的应用,我相信这是许多团队讨论和分享信息时最常用的*台之一。
为了能够使用特定*台的服务,您需要在被跟踪的函数之上添加相应的装饰器。例如,对应于电子邮件服务的装饰者是@email_sender。
下面的函数是我们将要使用的一个例子。这是一个简单的函数,它将一个数字作为参数,并返回从 0 到该数字的所有质数的列表。
get_prime_numbers.py
松弛的
为了能够使用 Slack,第一件事就是按照这三个步骤获取你的 slack room webhook URL 。
#1 & #2 —传入 webhook 激活
通过前面的链接,您可以在创建新应用程序后或从现有应用程序中激活 webhook。选择我同意按钮后,我创建了一个名为 Zoumana 的应用程序。从那里我可以激活网络钩子。

创建一个新的应用程序后进入 Webhook 激活(图片由作者提供)
# 3——获取你的网页挂钩网址
确保在完成前面的步骤后刷新页面。然后,在选择向工作区添加新的 web hook按钮以最终获得 URL 之后,您应该能够回答关于在哪里发布通知的问题。在我的情况下,我会直接收到它。

传入的 Webhook URL 生成(图片由作者提供)
现在,我们已经为分析做好了一切准备。
装饰器@slack_sender的可选参数user_mentions使得添加您正在使用的那些的一个列表slack id成为可能。如果您想标记它们的通知结果,这是非常有趣的。
为了简单起见,我决定不使用该参数,最终实现如下:
slack_notification.py
在行第 18 调用了get_prime_numbers(20)之后,我从 Slack 得到了以下通知,提供了关于执行的信息,比如函数名、执行日期、时间、持续时间以及返回值。

运行该功能后的松弛通知(图片由作者提供)
不调和
与 Slack 类似,您还需要一个不和谐的 Webhook URL,这可以在下面的页面中完成。获取 URL 的细节我就不多说了,因为这个过程很简单,比 Slack 需要的步骤少。
不和谐 _ 通知. py
正如你所看到的,这和 Slack 上的结果是一样的。

运行该功能后的不一致通知(图片由作者提供)
结论
恭喜你!🎉 🍾你刚刚学会了如何使用敲门来获得任务状态的通知。你可以把时间投入到其他活动中,让knockknock 在必要的时候照顾更新你。
如果你喜欢阅读我的故事,并希望支持我的写作,可以考虑成为的灵媒成员。每月支付 5 美元,你就可以无限制地阅读媒体上的故事。通过使用我的注册链接,我会收到一点佣金。
此外,您可以随时在 LinkedIn 、 Medium 和 YouTube 上添加我,或者在 Twitter 上关注我。讨论人工智能,人工智能,数据科学,自然语言处理的东西总是令人愉快的!
再见🏃🏾
通过 BigQuery 了解更多关于您的应用用户的信息
原文:https://towardsdatascience.com/know-more-about-your-app-users-through-bigquery-4c0b6d67abfa
超越 Firebase 和 Google Analytics 的更加定制化的事件分析方法

罗宾·沃拉尔在 Unsplash 上的照片
如果你正在运营一个 Android 应用程序业务,或者是一个应用程序业务的分析师,你可能会经常使用谷歌分析和 Firebase。这些工具可帮助您监控应用性能,并让您深入了解应用用户。
然而,GA 和 Firebase 并不能回答所有的问题,即使回答了,它们也不能完美地捕捉到您想要的分析和呈现数据的方式。在这种情况下,将历史数据导出到数据仓库可能是值得的,然后您应该能够自己进行更高级的分析。由于 BigQuery 与 GA 和 Firebase 属于同一个谷歌生态系统,因此导出更加无缝。
本文讨论通过在 BigQuery 中运行简单查询来回答的示例业务问题。先决条件是一些基本的 SQL 技能,一个谷歌云账户,和一个 Firebase 连接的应用;然后,您就可以自己进行这些查询了!
第一步是将 Firebase 数据导出到 BigQuery 中。一旦您完成了这一步,事件数据就会自动流向您的数据仓库,为数据争论和分析做好准备。要了解更多关于如何启用 Firebase 到 BigQuery 导出的信息,您可以参考这个文档。
数据集
对于这个演示,我将使用来自 Flood-it 的示例游戏应用程序数据!,一款在 Android 和 iOS *台上都有售的益智游戏。它是一个模糊的数据集,模拟 GA 和 Firebase 中的实际实现。
您可以检查这个链接以获得关于数据集及其模式的更多信息。
关于用户标识符,请注意:“user_pseudo_id”用于标识演示数据中的单个用户。如果您想要跨多个应用程序、设备和分析提供商关联用户数据,您应该设置一个“ user_id ”。
现在我们准备分析数据。
Firebase 和 BigQuery 可以回答的用户体验相关问题示例
有多少用户安装了该应用程序?
类似这样的查询可以揭示在特定时间段内有多少用户安装了该应用程序:
SELECT COUNT(DISTINCT user_pseudo_id) as users_installed
FROM `firebase-public-project.analytics_153293282.events_*`
WHERE event_name = "first_open"
AND _TABLE_SUFFIX BETWEEN '20180927' and '20181003'
由于表是当天的格式(每天一个表),所以可以使用通配符“*”在一个查询中组合几个表。使用这段代码时要非常小心,因为执行起来可能会非常昂贵,尤其是当你有很多数据的时候。切记始终使用“_TABLE_SUFFIX”来定义日期边界和限制数据。
通过运行这段代码,我们看到有 321 个用户在指定的时间内安装了应用程序。
要获得用户安装的每日明细,您还可以运行以下代码:
SELECT
FORMAT_DATE('%Y-%m-%d', PARSE_DATE('%Y%m%d', event_date)) AS date,
COUNT(DISTINCT user_pseudo_id) as users_installed
FROM `firebase-public-project.analytics_153293282.events_*`
WHERE event_name = "first_open"
AND _TABLE_SUFFIX BETWEEN '20180927' and '20181003'
GROUP BY date
ORDER BY date
该查询产生以下结果:
用户的人口统计特征是什么?
Firebase 中提供了一些人口统计数据,例如位置和设备信息。
例如,该查询给出了我们的用户所在的前 10 个国家,以及相应的百分比:
WITH
--Compute for the numerators
country_counts AS (
SELECT
geo.country,
COUNT(DISTINCT user_pseudo_id) AS users
FROM `firebase-public-project.analytics_153293282.events_*`
WHERE event_name = "first_open"
AND _TABLE_SUFFIX BETWEEN '20180927' and '20181003'
AND geo.country <> ""
GROUP BY geo.country
),
--Compute for the denominators
user_counts AS (
SELECT
COUNT(DISTINCT user_pseudo_id)
FROM `firebase-public-project.analytics_153293282.events_*`
WHERE event_name = "first_open"
AND _TABLE_SUFFIX BETWEEN '20180927' and '20181003'
),
--Compute for the percentages
percent AS (
SELECT
country,
ROUND(users / (SELECT * FROM user_counts), 4) AS percent_users
FROM country_counts
)SELECT * FROM percent
ORDER BY percent_users DESC
LIMIT 10
我们通过运行代码得到以下结果:
由此,我们知道我们的用户群中有五分之二来自美国,而印度也有 15%的良好表现。
通过运行以下查询,我们还可以了解他们玩游戏时使用的设备类型(手机还是*板电脑):
WITH
device_counts AS (
SELECT
device.category,
COUNT(DISTINCT user_pseudo_id) AS users
FROM `firebase-public-project.analytics_153293282.events_*`
WHERE event_name = "first_open"
AND _TABLE_SUFFIX BETWEEN '20180927' and '20181003'
AND device.category <> ""
GROUP BY device.category
),user_counts AS (
SELECT
COUNT(DISTINCT user_pseudo_id)
FROM `firebase-public-project.analytics_153293282.events_*`
WHERE event_name = "first_open"
AND _TABLE_SUFFIX BETWEEN '20180927' and '20181003'
),percent AS (
SELECT
category,
ROUND(users / (SELECT * FROM user_counts), 4) AS percent_users
FROM device_counts
)SELECT * FROM percent
ORDER BY percent_users DESC
从这个例子中我们看到 81% 在用手机,而只有 19% 在用*板。
每天有多少用户在积极使用该应用?
每日活跃用户(DAU)指标让您了解当前应用程序用户的参与度。如何衡量取决于你如何定义活跃用户。在本例中,我们将活动用户定义为一天中执行了任何“user_engagement”操作的用户。
要计算指定时间段的 DAU,只需运行以下查询:
WITH
daily_user_count AS (
SELECT
FORMAT_DATE('%Y-%m-%d', PARSE_DATE('%Y%m%d', event_date)) AS date,
COUNT(DISTINCT user_pseudo_id) AS active_users
FROM `firebase-public-project.analytics_153293282.events_*`
WHERE event_name = "user_engagement"
AND _TABLE_SUFFIX BETWEEN '20180901' and '20180930'
GROUP BY date
)SELECT AVG(active_users) AS daily_active_users
FROM daily_user_count
*均而言,9 月份有 496 名用户使用该应用。
用户在该应用上的花费是多少?
我们还可以通过运行以下查询来了解用户在应用内购买上的花费:
SELECT SUM(user_ltv.revenue) AS revenue
FROM `firebase-public-project.analytics_153293282.events_*`
WHERE event_name = "in_app_purchase"
AND geo.country = "United States"
AND _TABLE_SUFFIX BETWEEN '20180901' and '20180930'
对美国用户来说,这只是 5.94 美元,并不多,但了解这一点后,我们可以制定更好的策略来追加销售应用内购买。
有多少用户遇到了应用崩溃?
为了防止流失和卸载,有必要监控不愉快的用户体验,如应用程序崩溃。
我们可以通过查询“app_exception”事件来深入了解这一点:
SELECT COUNT(DISTINCT user_pseudo_id) AS users
FROM `firebase-public-project.analytics_153293282.events_*`,
UNNEST(event_params) e
WHERE event_name = 'app_exception'
AND _TABLE_SUFFIX BETWEEN '20180901' and '20180930'
AND e.key = 'fatal' AND e.value.int_value = 1
注意 UNNEST()函数。事件参数在 BigQuery 中存储为数组,因此需要首先取消嵌套。查看这篇文章可以更深入地讨论 BigQuery 中的非嵌套事件参数。
根据这个查询,我们已经确定 269 个用户在九月份遇到过崩溃。
有多少用户在卸载应用?
App 删除是一种不良结果,因此应定期监控。这个在 Firebase 中对应的事件名是“app_remove”。
以下查询输出了在 9 月份安装该应用程序的人群中,一周后仍在使用该应用程序的用户的百分比:
WITH
--List of users who installed in Sept
sept_cohort AS (
SELECT DISTINCT user_pseudo_id,
FORMAT_DATE('%Y-%m-%d', PARSE_DATE('%Y%m%d', event_date)) AS date_first_open,
FROM `firebase-public-project.analytics_153293282.events_*`
WHERE event_name = 'first_open'
AND _TABLE_SUFFIX BETWEEN '20180901' and '20180930'
),--Get the list of users who uninstalled
uninstallers AS (
SELECT DISTINCT user_pseudo_id,
FORMAT_DATE('%Y-%m-%d', PARSE_DATE('%Y%m%d', event_date)) AS date_app_remove,
FROM `firebase-public-project.analytics_153293282.events_*`
WHERE event_name = 'app_remove'
AND _TABLE_SUFFIX BETWEEN '20180901' and '20181007'
),--Join the 2 tables and compute for # of days to uninstall
joined AS (
SELECT a.*,
b.date_app_remove,
DATE_DIFF(DATE(b.date_app_remove), DATE(a.date_first_open), DAY) AS days_to_uninstall
FROM sept_cohort a
LEFT JOIN uninstallers b
ON a.user_pseudo_id = b.user_pseudo_id
)--Compute for the percentage
SELECT
COUNT(DISTINCT
CASE WHEN days_to_uninstall > 7 OR days_to_uninstall IS NULL THEN user_pseudo_id END) /
COUNT(DISTINCT user_pseudo_id)
AS percent_users_7_days
FROM joined
这告诉我们,9 月份安装者的 7 天保留率在为 76% ,或者说有 24% 的 9 月份人群已经卸载了该应用。
这个数字有多好?这可以通过对行业基准进行一些研究来回答,或者通过监控趋势如何随着时间的推移而发展。
您也可以将保留率的时间范围更改为最有用的时间范围,方法是将最终的“CASE WHEN”条件编辑为您想要的天数,而不是 7 天。
崩溃是否可能影响用户体验,导致他们卸载?
我们可以进一步深入了解卸载该应用程序的用户,以确定删除它的可能原因。因为我们可以访问几个事件,所以我们可以查看它们并检查与卸载的相关性。
例如,我们可能希望在卸载之前检查有多少卸载程序遇到了崩溃。我们不能断定这导致了他们的沮丧并导致他们移除应用程序,但只看数字可能会有所帮助。可以运行这样的查询:
WITH
--List of users who installed in Sept
sept_cohort AS (
SELECT DISTINCT user_pseudo_id,
FORMAT_DATE('%Y-%m-%d', PARSE_DATE('%Y%m%d', event_date)) AS date_first_open,
FROM `firebase-public-project.analytics_153293282.events_*`
WHERE event_name = 'first_open'
AND _TABLE_SUFFIX BETWEEN '20180901' and '20180930'
),--Get the list of users who uninstalled
uninstallers AS (
SELECT DISTINCT user_pseudo_id,
FORMAT_DATE('%Y-%m-%d', PARSE_DATE('%Y%m%d', event_date)) AS date_app_remove,
FROM `firebase-public-project.analytics_153293282.events_*`
WHERE event_name = 'app_remove'
AND _TABLE_SUFFIX BETWEEN '20180901' and '20181007'
),--Get the list of users who experienced crashes
users_crashes AS (
SELECT DISTINCT user_pseudo_id,
FORMAT_DATE('%Y-%m-%d', PARSE_DATE('%Y%m%d', event_date)) AS date_crash,
FROM `firebase-public-project.analytics_153293282.events_*`,
UNNEST(event_params) e
WHERE event_name = 'app_exception'
AND _TABLE_SUFFIX BETWEEN '20180901' and '20181007'
AND e.key = 'fatal' AND e.value.int_value = 1
),--Join the 3 tables
joined AS (
SELECT a.*,
b.date_app_remove,
DATE_DIFF(DATE(b.date_app_remove), DATE(a.date_first_open), DAY) AS days_to_uninstall,
c.date_crash
FROM sept_cohort a
LEFT JOIN uninstallers b
ON a.user_pseudo_id = b.user_pseudo_id
LEFT JOIN users_crashes c
ON a.user_pseudo_id = c.user_pseudo_id
)--Compute the percentage
SELECT
COUNT(DISTINCT
CASE WHEN days_to_uninstall <= 7 AND date_crash IS NOT NULL
THEN user_pseudo_id END)
/ COUNT(DISTINCT
CASE WHEN days_to_uninstall <= 7 THEN user_pseudo_id END)
AS percent_users_crashes
FROM joined
这表明只有 2.4%的卸载程序经历了崩溃。看起来数量很少,但与非安装者进行比较可能有助于得出更合理的结论。此外,我们可以创建一个应用程序删除率模型来确定应用程序删除的预测因素。我不会在本文中涉及这些内容,因为这超出了范围,但是我推荐它作为一个强大的下一步。
结论
在这篇文章中,我们发现了 BigQuery 可以为您的应用分析带来的巨大可能性。我们提到了一些简单的查询,但随着您对 Firebase 中可用数据的了解越来越多,我鼓励您更深入地挖掘,亲自看看这个强大的资源如何帮助您发展应用业务。
如果你喜欢这篇文章或者觉得它有用,请关注我的博客获取更多关于营销分析和数据科学的资源。快乐学习!
参考资料:
Google Analytics 4 游戏应用实施的 BigQuery 样本数据集。谷歌分析样本数据集。网上有:https://developers . Google . com/analytics/big query/app-gaming-demo-dataset。许可信息:知识共享署名 4.0 许可
知识图嵌入——一个简化版本
原文:https://towardsdatascience.com/knowledge-graph-embedding-a-simplified-version-e6b0a03d373d
解释什么是知识图嵌入以及如何计算它们

纳斯蒂亚·杜尔希尔在 Unsplash 上的照片
raphs 是我最喜欢使用的数据结构之一。它们使我们能够表示复杂的现实世界网络,如快速交通系统(如巴黎地铁、纽约地铁等)、地区或全球航空交通,或类似个人社交网络的相关事物。它们非常灵活,很容易被人类理解。但是,为了让计算机“理解”它们并从中“学习”,我们还额外增加了一步(称为矢量化)。这个解释可能太简单了,但是对于理解和理解这篇文章的其余部分来说已经足够了。
知识图的特别之处是什么?

为了容易理解知识图与其他图有何不同,我们来讨论一个类比。想象一个有不同关卡的电子游戏,每一关都随着你的进展变得越来越难。
一级:可以是一个简单的无向图,像大学里的朋友群,朋友是节点,友谊是边。这里我们只有节点和边,没有什么太花哨的。
第二层:在前一层的基础上增加一层信息,如方向感,得到有向图。一个简单的例子是城市范围的公共汽车网络。将公共汽车站视为节点,将公共汽车的路线视为边。在这里,每辆公共汽车沿着特定的方向从一个车站开往另一个车站。这增加了方向信息。
第三级:我们取一个有向图,给节点和边添加多种风格。为了容易理解这一点,想象一下互联网上的社交网络。节点上的多种口味是用户所基于的社交网络的类型。例如,它可以是推特、脸书或 YouTube。边上的味道可以是不同用户之间的交互类型,即关注(在 Twitter 的情况下),朋友或关注(在脸书的情况下),以及订阅(在 YouTube 的情况下)。图形的有向性开始发挥作用,因为以下内容只能是单向的。例如,你可以关注埃隆·马斯克,但他可能不会在 Twitter 上关注你,这就是优势的方向性。
第 4 层:考虑前一层中的图,但是你不用节点和边,而是用三元组的语言来说话。三元组是知识图的构建块,其中它是由 3 个元素组成的元组,即:源节点(头)、关系和目标节点(尾)。
注意:源节点和目标节点有时被称为实体。
请注意术语“知识图”的使用有点模糊。我想说的是,知识图没有固定的定义,从广义上讲,我们可以将任何包含一些知识/重要信息的相当大的图命名为知识图。
这里的关键要点是,我们将知识图的元素视为三元组。
知识图嵌入方法

概述:矢量化或嵌入(图形的实体和关系的数字表示)对于将图形用作机器学习算法的输入是必要的。
因为我们对待知识图的方式不同于其他的,所以我们需要不同的技术来学习它们的数字表示(或嵌入)。
有几种方法可以生成知识图嵌入(KGE),我们可以将其大致分为 3 个部分:
- 基于翻译的方法: 这里,基于距离的函数(在欧几里德空间中)用于生成嵌入。我们可以建立一个简单的算法,即
使头部和关系向量的组合等于尾部向量。
可以表示为 h + r ≈ t 。这种算法叫做 TransE。同样的算法还有其他版本,但是对它的修改很少。一些例子包括 TransH、TransR、TransD、TranSparse 和 TransM。 - 基于因式分解的方法: 这种基于张量因式分解的思想,使用这种技术提出的初始算法被重新标度。三向张量以
n x n x m的形式定义,其中 n 是实体的数量,m 是关系的数量。张量的值为 1,表示实体之间存在关系,如果没有关系,则为 0。
嵌入通过分解该张量来计算。对于大型图形来说,这通常计算量很大。这个问题是由 DistMult、HolE、ComplEx 和 QuatE 等算法解决的,这些算法都是建立在 RESCAL 思想的基础上的。 - 基于神经网络的方法: 神经网络现在在许多领域都很流行,它们被用于发现知识图嵌入并不令人惊讶。语义匹配能量是一种算法,该算法通过使用神经网络来定义用于将值分配给三元组的能量函数。神经张量网络使用能量函数,但是它用双线性张量层代替了神经网络的标准线性层。【ConvE 等卷积神经网络以“图像”的形式重塑实体和关系的数字表示,然后应用卷积滤波器来提取特征,从而学习最终的嵌入。我们还可以找到 GAN 启发的模型,如 KBGAN 和基于 Transformer 的模型,如 HittER,来计算知识图嵌入。
为了实现这些算法,我们有多个 python 库,例如:
一种 KGE 算法的结构

构建计算知识图嵌入的算法有一些共同的基本思想。下面列出了其中的一些想法:
- 负生成:
这是一个在知识图中生成负的或损坏的三元组的概念。负三元组是不属于原始图的三元组。这些可以随机生成,也可以通过使用一些策略(如伯努利负采样)来生成。 - 评分函数:
包装三元组的函数,吐出一个值或一个分数。我们可以定义,如果分数高,则三元组有效,如果分数低,则为负三元组。评分函数是构造 KGE 算法的重要组成部分之一。 - 损失函数:
由于该算法是根据优化问题建模的,所以我们在训练过程中使用损失函数。这个损失函数使用正三元组和负三元组的分数来计算损失。我们的目标是最小化损失,我们也采用了优化器。
这里使用的损失函数的一些例子是——交叉熵损失、基于成对余量的铰链损失等。
生成嵌入后的下一步是什么?

学习 KGE 算法并应用它们来寻找嵌入是很有趣的。现在,接下来是什么?嵌入的用途是什么?
有一些图表下游任务可以应用于知识图表,例如:
知识图完成:
这也被称为链接预测,我们的目标是预测知识图中缺失的关系或潜在可能的关系。它也可以被称为知识图扩充。
这个任务可以归结为找到一个实体,这个实体可以与一个给定的关系和一个实体一起被最好地表示为一个事实。简单来说,就是现在猜测(?, r, t)或(h, r, ?)中缺失部分的任务也可以分别称为头部预测或尾部预测。我们使用基于等级的评估技术来发现我们的知识图嵌入的性能。
三元组分类: 这是一个识别给定三元组是否有效的问题,即它是正三元组还是负三元组。该任务的输出仅仅是是或否。
采用评分函数,并设置阈值以将正三元组与负三元组分开。基本上,现在这是一个二元分类的问题。
推荐系统是一个可以使用知识图嵌入的领域。嵌入的质量对于上述任务的性能和准确性很重要。这些任务的结果告诉我们是否能够生成高质量的嵌入。
注意:如果我在这里解释的任何概念对您来说有点沉重,那么我建议您查看我写的关于图形的其他文章。
如果你看到了这篇文章的这一部分,感谢你的阅读和关注。
**Want to Connect?**Reach me at [LinkedIn](https://www.linkedin.com/in/rohithteja/), [Twitter](https://twitter.com/rohithtejam), [GitHub](https://github.com/rohithteja) or just [Buy Me A Coffee](https://www.buymeacoffee.com/rohithteja)!
Koh-Lanta 2022:大使概率问题
原文:https://towardsdatascience.com/koh-lanta-2022-the-ambassadors-probability-problem-ebe0208d45a8
在电视节目《Koh-Lanta 2022》中,每位大使获胜的概率有多大?通过模拟和例子学习如何在 R 中计算它

蒂莫西·梅恩伯格的照片
介绍
T 这是一个在法国和比利时法语区播出的受欢迎的电视节目,名为 Koh-Lanta 。
在这部剧中,几个冒险者被丢在一个荒岛上,几乎没有食物和装备(只有一个个人背包,里面有他们的衣服和一小部分大米)。他们必须学会在这个充满敌意的岛上生存,建造他们的小屋,寻找水和食物等等。
每个赛季,冒险者被分成团队(称为部落),团队之间在涉及能力、力量、思维和耐力的游戏中相互竞争。每隔一场比赛,获胜的部落会得到一些食物或生存装备(比如,捕鱼的东西或生火的东西)。失败的部落什么也得不到。在另一半比赛中,输的部落的每个成员都要选出一名冒险者。得票最多的冒险者肯定会离开节目。获胜的部落和所有成员一起回到岛上。
在表演过程中的某个时候,两个竞争的部落被组合成一个部落,这一次每个冒险者互相竞争(所以他们单独玩)。节目的获胜者是最后一个“活下来”的。
2022 赛季从 24 个冒险者开始。在被分组之前,每个部落都必须选择一个对手部落的冒险者。两名被选中的冒险者成为他们部落的大使。然后,两位大使必须前往另一个岛屿,选出最终离开节目的冒险家。两位大使选择的冒险家将不会成为两个部落统一的一部分,她的冒险也就到此为止了。
当然,两位大使都想消灭一个敌对部落的成员(以与最多的盟友达成统一)。如果两位大使不能达成一致,他们必须玩一个游戏来决定两位大使中谁必须离开节目。这个游戏完全是靠运气。在本文的其余部分,我们称这个游戏为大使游戏。
2022 年前
以下是 2022 赛季前大使比赛的规则:
有两个一模一样的骨灰盒,每个大使前面一个。每个骨灰盒正好包含一个黑球和一个白球。每个大使必须从他的瓮中抽取一个球。两个骨灰盒当然都是封闭的,所以没人看到哪个球被挑了(也没人看到哪个球没被挑)。游戏的赢家(请记住,赢家留在节目中,输家必须明确离开)是选择白色球的人,而另一位大使则抽取黑色球。如果两位大使抽到了同一个球(都是黑的或都是白的),球被放回瓮中,游戏重新开始(条件完全相同),直到两位大使抽到了不同颜色的球。
因此,对于每个抽签,有四种可能的结果:
- A 部落的大使抽出一个白球,B 部落的大使抽出一个黑球:A 部落的大使获胜。
- A 部落的大使抽取一个黑球,B 部落的大使抽取一个白球:B 部落的大使获胜。
- 两位大使都抽到了一个黑球:游戏重新开始。
- 两位大使都抽到了一个白球:游戏重新开始。
让我们计算每个结果发生的概率。由于这两个事件是独立的(A 大使抽一个白球的事实并不改变 B 大使抽一个白球的概率使得这两个事件是独立的),我们可以将概率相乘来计算这两个事件的联合概率。
用 P_A (P_B)表示部落 A (B)的大使抽到了一个白球的概率,我们得到:
- P_A⋅(1−P_B)=0.5⋅0.5=0.25
- (1−P_A)⋅P_B=0.5⋅0.5=0.25
- (1−P_A)⋅(1−P_B)=0.5⋅0.5=0.25
- P_A⋅P_B=0.5⋅0.5=0.25
4 个概率之和为 1(即 100%),这是有意义的,因为它涵盖了所有可能的结果。
这意味着,在第一次抽奖中,每个大使都有 25%的概率赢得游戏(A 大使的结果 1,B 大使的结果 2)。
当然,由于游戏会一直重复,直到出现赢家,从长远来看,结果 3 和 4 的概率会逐渐降低,直到为零(0%)。如果这种说法对你来说不直截了当,那么这样想想:如果你和你的朋友玩那个游戏多达 100 次,仍然没有赢家的概率是多少,意思是你和你的朋友连续 100 次抽同一个球(从来不是不同的颜色)。你认为这是极不可能的。
在这种情况下,由于两个骨灰盒是相同的,游戏无限期地进行,直到有一个赢家,大使们赢得游戏的概率完全相同。对他们每个人来说,这确实是一半对一半的机会。
2022 年
当然,如果有这么简单,我不会写一篇关于大使游戏文章。
2022 赛季与前几个赛季的不同之处在于,每一场比赛,失败的部落都会受到额外的惩罚(在节目中称为诅咒)。因此,今年,大使们游戏中的两个骨灰盒与 T2 的不一样。
给你一些背景资料,在大使谈判前的最后一场比赛中,红部落赢了黄部落。对黄色部落的惩罚如下:
- 黄色部落(和他们的大使科林)有一个瓮,里面有两个黑球和一个白球。
- 红族部落(和他们的大使 Louana)有一个骨灰盒,里面有一个黑球和一个白球。
这确实是对黄色部落的惩罚,因为游戏不再公*:与黄色部落的大使相比,红色部落的大使显然有更高的机会赢得游戏。
对于你们当中的好奇者来说,事情是这样发生的:
- 主持人 Denis Brogniart 向两位大使介绍了每个部落骨灰盒的组成。
- 知道胜算对他不利,科林(黄色部落的大使)选择不玩这个游戏。 1
- 他们最终商定了将离开节目的冒险者的名字(黄色部落的冒险者)。
你现在已经猜到了,写这篇文章的原因当然不是为了向你解释实际发生了什么——新闻网站比我做得更好、更快。原因是我想计算每个大使赢得游戏的机会,如果他们没有同意消灭一个冒险者的话。 2
此外,我想通过模拟来计算这些概率。为什么不呢,重复使用代码,以防将来兰大岛的组织者决定再次改变规则。
即使你不看 Koh-Lanta(因为它不在你的国家播出,或者你不喜欢这个节目),你可能会对想看的人感兴趣:
- 一个现实生活中的例子如何转化为 R,
- 以及如何用一个
for loop和一个函数来回答最初的问题。
R 中的概率计算
对于本文的其余部分,我们表示:
- p_c,科林(黄部落大使)抽到了一个白球的概率,
- p_l,Louana(红色部落的大使)抽到了白球的概率,
- q_c,科林抽到黑球的概率,
- q_l,Louana 抽到黑球的概率。
根据 Denis Brogniart 给出的骨灰盒组成,我们有:
p_c <- 1 / 3
p_l <- 1 / 2
q_c <- (1 - p_c)
q_l <- (1 - p_l)
第一次抽签
为了简单起见,让我们先计算一下第一次抽奖时每个大使的获胜概率,只有。记住,要有赢家,球必须是不同的颜色。
当且仅当科林抽了一个黑球,卢阿那抽了一个白球时,卢阿那赢:
# Louana winning on first draw
l_win <- q_c * p_l
l_win## [1] 0.3333333
Louana 有 33.33%的机会在第一次抽奖中获胜。
另一方面,当且仅当 Louana 画了一个黑球,而 Colin 画了一个白球时,Colin 才赢:
# Colin winning on first draw
c_win <- q_l * p_c
c_win## [1] 0.1666667
科林第一次抽奖有 16.67%的胜算。
你已经可以看出,这场比赛是有利于 Louana,正如所料。现在来看看打几次是怎么演变的。
第二次抽签
要在第二次抽签时准确获胜,第一次抽签时必须是*局。
# Louana winning on second draw
tie <- (p_c * p_l) + (q_c * q_l)l_win2 <- tie * l_win
l_win2## [1] 0.1666667# Colin winning on second draw
c_win2 <- tie * c_win
c_win2## [1] 0.08333333
- Louana 第二次抽奖有 16.67%的几率中奖。
- 科林有 8.33%的机会在第二次抽奖中获胜。
第三次抽签
要在第三次*局时获胜,前两次*局必须是*局。
# Louana winning on third draw
l_win3 <- (tie^2) * l_win
l_win3## [1] 0.08333333# Colin winning on third draw
c_win3 <- (tie^2) * c_win
c_win3## [1] 0.04166667
- Louana 第三次抽奖有 8.33%的胜算。
- 科林有 4.17%的机会在第三次抽奖中获胜。
代码中似乎出现了一种模式。
游戏限于 3 次*局
我们已经可以计算每个大使的获胜概率,就好像游戏限于三次*局一样,方法是将前三次*局的概率相加:
# Louana winning on draw 1, 2 or 3
l_win_tot <- l_win + l_win2 + l_win3
l_win_tot## [1] 0.5833333# Colin winning on draw 1, 2 or 3
c_win_tot <- c_win + c_win2 + c_win3
c_win_tot## [1] 0.2916667
- 如果游戏限于三个*局,Louana 有 58.33%的胜算。
- 如果游戏限于三个*局,科林有 29.17%的胜算。
游戏限于 5 次*局
现在,如果我们计算概率,假设游戏限于 5 次*局,并推广计算以更清楚地看到模式,我们有:
# Louana winning
l_win_tot <- ((tie^0) * l_win) +
((tie^1) * l_win) +
((tie^2) * l_win) +
((tie^3) * l_win) +
((tie^4) * l_win)l_win_tot## [1] 0.6458333# Colin winning
c_win_tot <- ((tie^0) * c_win) +
((tie^1) * c_win) +
((tie^2) * c_win) +
((tie^3) * c_win) +
((tie^4) * c_win)c_win_tot## [1] 0.3229167
- 如果游戏限制在 5 次*局,Louana 有 64.58%的胜算。
- 如果游戏限制在 5 次*局,科林有 32.29%的胜算。
游戏限于 100 次抽奖
我们可以像这样持续很长时间,但是现在让我们用一个for loop来计算 100 次*局。为了避免一百次地复制粘贴我们的计算,这里使用一个for loop是必要的。
为了便于说明,我们只计算 Louana 获胜的概率。我们将在后面展示如何简单地计算科林的概率。
n_draws <- 100 # number of draws
l_win_tot <- c() # set empty vectorfor (i in 1:n_draws) {
l_win_tot[i] <- ((tie^(i - 1)) * l_win) # prob of Louana winning up to n_draws
print(paste0("Draw ", i, ": ", sum(l_win_tot))) # print sum of winning up to n_draws
}## [1] "Draw 1: 0.333333333333333"
## [1] "Draw 2: 0.5"
## [1] "Draw 3: 0.583333333333333"
## [1] "Draw 4: 0.625"
## [1] "Draw 5: 0.645833333333333"
## [1] "Draw 6: 0.65625"
## [1] "Draw 7: 0.661458333333333"
## [1] "Draw 8: 0.6640625"
## [1] "Draw 9: 0.665364583333333"
## [1] "Draw 10: 0.666015625"
## [1] "Draw 11: 0.666341145833333"
## [1] "Draw 12: 0.66650390625"
## [1] "Draw 13: 0.666585286458333"
## [1] "Draw 14: 0.6666259765625"
## [1] "Draw 15: 0.666646321614583"
## [1] "Draw 16: 0.666656494140625"
## [1] "Draw 17: 0.666661580403646"
## [1] "Draw 18: 0.666664123535156"
## [1] "Draw 19: 0.666665395100911"
## [1] "Draw 20: 0.666666030883789"
## [1] "Draw 21: 0.666666348775228"
## [1] "Draw 22: 0.666666507720947"
## [1] "Draw 23: 0.666666587193807"
## [1] "Draw 24: 0.666666626930237"
## [1] "Draw 25: 0.666666646798452"
## [1] "Draw 26: 0.666666656732559"
## [1] "Draw 27: 0.666666661699613"
## [1] "Draw 28: 0.66666666418314"
## [1] "Draw 29: 0.666666665424903"
## [1] "Draw 30: 0.666666666045785"
## [1] "Draw 31: 0.666666666356226"
## [1] "Draw 32: 0.666666666511446"
## [1] "Draw 33: 0.666666666589056"
## [1] "Draw 34: 0.666666666627862"
## [1] "Draw 35: 0.666666666647264"
## [1] "Draw 36: 0.666666666656966"
## [1] "Draw 37: 0.666666666661816"
## [1] "Draw 38: 0.666666666664241"
## [1] "Draw 39: 0.666666666665454"
## [1] "Draw 40: 0.66666666666606"
## [1] "Draw 41: 0.666666666666364"
## [1] "Draw 42: 0.666666666666515"
## [1] "Draw 43: 0.666666666666591"
## [1] "Draw 44: 0.666666666666629"
## [1] "Draw 45: 0.666666666666648"
## [1] "Draw 46: 0.666666666666657"
## [1] "Draw 47: 0.666666666666662"
## [1] "Draw 48: 0.666666666666664"
## [1] "Draw 49: 0.666666666666666"
## [1] "Draw 50: 0.666666666666666"
## [1] "Draw 51: 0.666666666666666"
## [1] "Draw 52: 0.666666666666667"
## [1] "Draw 53: 0.666666666666667"
## [1] "Draw 54: 0.666666666666667"
## [1] "Draw 55: 0.666666666666667"
## [1] "Draw 56: 0.666666666666667"
## [1] "Draw 57: 0.666666666666667"
## [1] "Draw 58: 0.666666666666667"
## [1] "Draw 59: 0.666666666666667"
## [1] "Draw 60: 0.666666666666667"
## [1] "Draw 61: 0.666666666666667"
## [1] "Draw 62: 0.666666666666667"
## [1] "Draw 63: 0.666666666666667"
## [1] "Draw 64: 0.666666666666667"
## [1] "Draw 65: 0.666666666666667"
## [1] "Draw 66: 0.666666666666667"
## [1] "Draw 67: 0.666666666666667"
## [1] "Draw 68: 0.666666666666667"
## [1] "Draw 69: 0.666666666666667"
## [1] "Draw 70: 0.666666666666667"
## [1] "Draw 71: 0.666666666666667"
## [1] "Draw 72: 0.666666666666667"
## [1] "Draw 73: 0.666666666666667"
## [1] "Draw 74: 0.666666666666667"
## [1] "Draw 75: 0.666666666666667"
## [1] "Draw 76: 0.666666666666667"
## [1] "Draw 77: 0.666666666666667"
## [1] "Draw 78: 0.666666666666667"
## [1] "Draw 79: 0.666666666666667"
## [1] "Draw 80: 0.666666666666667"
## [1] "Draw 81: 0.666666666666667"
## [1] "Draw 82: 0.666666666666667"
## [1] "Draw 83: 0.666666666666667"
## [1] "Draw 84: 0.666666666666667"
## [1] "Draw 85: 0.666666666666667"
## [1] "Draw 86: 0.666666666666667"
## [1] "Draw 87: 0.666666666666667"
## [1] "Draw 88: 0.666666666666667"
## [1] "Draw 89: 0.666666666666667"
## [1] "Draw 90: 0.666666666666667"
## [1] "Draw 91: 0.666666666666667"
## [1] "Draw 92: 0.666666666666667"
## [1] "Draw 93: 0.666666666666667"
## [1] "Draw 94: 0.666666666666667"
## [1] "Draw 95: 0.666666666666667"
## [1] "Draw 96: 0.666666666666667"
## [1] "Draw 97: 0.666666666666667"
## [1] "Draw 98: 0.666666666666667"
## [1] "Draw 99: 0.666666666666667"
## [1] "Draw 100: 0.666666666666667"
得出 1、3 和 5 的概率与我们在前面章节中发现的一致。此外,我们看到从第 52 局开始,Louana 获胜的概率保持不变,为 66.67%。这就是所谓的极限。
游戏限于必要的抽牌次数
如果不打印上述for loop的结果,我们不知道需要多少次抽签才能达到极限。
现在,让我们尝试包含有关必要抽取次数的信息,以避免计算不必要的抽取:
n_draws <- 9999 # initial number of draws, set intentionally to a high number
p_tie <- c() # set empty vector for prob of ties
l_win_tot <- c() # set empty vector for prob of Louana winning# find number of necessary draws:
for (i in 1:n_draws) {
p_tie[i] <- tie^i # prob of tie for each draw
limit_ndraws <- sum(p_tie > 2.2e-16) # number of necessary draws
}# compute Louana winning probabilities with the smallest number of necessary draws
for (i in 1:limit_ndraws) {
l_win_tot[i] <- ((tie^(i - 1)) * l_win) # prob of Louana winning up to limited number of draws
print(paste0("Draw ", i, ": ", sum(l_win_tot))) # sum of winning up to limited number of draws
}## [1] "Draw 1: 0.333333333333333"
## [1] "Draw 2: 0.5"
## [1] "Draw 3: 0.583333333333333"
## [1] "Draw 4: 0.625"
## [1] "Draw 5: 0.645833333333333"
## [1] "Draw 6: 0.65625"
## [1] "Draw 7: 0.661458333333333"
## [1] "Draw 8: 0.6640625"
## [1] "Draw 9: 0.665364583333333"
## [1] "Draw 10: 0.666015625"
## [1] "Draw 11: 0.666341145833333"
## [1] "Draw 12: 0.66650390625"
## [1] "Draw 13: 0.666585286458333"
## [1] "Draw 14: 0.6666259765625"
## [1] "Draw 15: 0.666646321614583"
## [1] "Draw 16: 0.666656494140625"
## [1] "Draw 17: 0.666661580403646"
## [1] "Draw 18: 0.666664123535156"
## [1] "Draw 19: 0.666665395100911"
## [1] "Draw 20: 0.666666030883789"
## [1] "Draw 21: 0.666666348775228"
## [1] "Draw 22: 0.666666507720947"
## [1] "Draw 23: 0.666666587193807"
## [1] "Draw 24: 0.666666626930237"
## [1] "Draw 25: 0.666666646798452"
## [1] "Draw 26: 0.666666656732559"
## [1] "Draw 27: 0.666666661699613"
## [1] "Draw 28: 0.66666666418314"
## [1] "Draw 29: 0.666666665424903"
## [1] "Draw 30: 0.666666666045785"
## [1] "Draw 31: 0.666666666356226"
## [1] "Draw 32: 0.666666666511446"
## [1] "Draw 33: 0.666666666589056"
## [1] "Draw 34: 0.666666666627862"
## [1] "Draw 35: 0.666666666647264"
## [1] "Draw 36: 0.666666666656966"
## [1] "Draw 37: 0.666666666661816"
## [1] "Draw 38: 0.666666666664241"
## [1] "Draw 39: 0.666666666665454"
## [1] "Draw 40: 0.66666666666606"
## [1] "Draw 41: 0.666666666666364"
## [1] "Draw 42: 0.666666666666515"
## [1] "Draw 43: 0.666666666666591"
## [1] "Draw 44: 0.666666666666629"
## [1] "Draw 45: 0.666666666666648"
## [1] "Draw 46: 0.666666666666657"
## [1] "Draw 47: 0.666666666666662"
## [1] "Draw 48: 0.666666666666664"
## [1] "Draw 49: 0.666666666666666"
## [1] "Draw 50: 0.666666666666666"
## [1] "Draw 51: 0.666666666666666"
## [1] "Draw 52: 0.666666666666667"
最终获胜概率
记得最初的问题是:
每个大使的胜算概率有多大?
Louana 赢得游戏的概率可以很容易地提取如下:
sum(l_win_tot)## [1] 0.6666667
由于游戏只有在有赢家时才会停止,Louana 和 Colin 的获胜概率之和必须等于 1,因此 Colin 赢得游戏的概率是:
c_win_tot <- 1 - sum(l_win_tot)
c_win_tot## [1] 0.3333333
总结一下:
- Louana 有 66.67%的几率赢得大使赛。
- 科林有 33.33%的几率赢得大使赛。
视觉表现
为了直观显示每个大使的概率,我们忽略了科林获胜的概率,所以让我们先计算一下:
c_win_tot <- c() # set empty vector for prob of Colin winning# compute Colin winning probabilities with the smallest number of necessary draws
for (i in 1:limit_ndraws) {
c_win_tot[i] <- ((tie^(i - 1)) * c_win) # prob of Colin winning
print(paste0("Draw ", i, ": ", sum(c_win_tot))) # print sum of winning
}## [1] "Draw 1: 0.166666666666667"
## [1] "Draw 2: 0.25"
## [1] "Draw 3: 0.291666666666667"
## [1] "Draw 4: 0.3125"
## [1] "Draw 5: 0.322916666666667"
## [1] "Draw 6: 0.328125"
## [1] "Draw 7: 0.330729166666667"
## [1] "Draw 8: 0.33203125"
## [1] "Draw 9: 0.332682291666667"
## [1] "Draw 10: 0.3330078125"
## [1] "Draw 11: 0.333170572916667"
## [1] "Draw 12: 0.333251953125"
## [1] "Draw 13: 0.333292643229167"
## [1] "Draw 14: 0.33331298828125"
## [1] "Draw 15: 0.333323160807292"
## [1] "Draw 16: 0.333328247070312"
## [1] "Draw 17: 0.333330790201823"
## [1] "Draw 18: 0.333332061767578"
## [1] "Draw 19: 0.333332697550456"
## [1] "Draw 20: 0.333333015441895"
## [1] "Draw 21: 0.333333174387614"
## [1] "Draw 22: 0.333333253860474"
## [1] "Draw 23: 0.333333293596903"
## [1] "Draw 24: 0.333333313465118"
## [1] "Draw 25: 0.333333323399226"
## [1] "Draw 26: 0.33333332836628"
## [1] "Draw 27: 0.333333330849806"
## [1] "Draw 28: 0.33333333209157"
## [1] "Draw 29: 0.333333332712452"
## [1] "Draw 30: 0.333333333022892"
## [1] "Draw 31: 0.333333333178113"
## [1] "Draw 32: 0.333333333255723"
## [1] "Draw 33: 0.333333333294528"
## [1] "Draw 34: 0.333333333313931"
## [1] "Draw 35: 0.333333333323632"
## [1] "Draw 36: 0.333333333328483"
## [1] "Draw 37: 0.333333333330908"
## [1] "Draw 38: 0.333333333332121"
## [1] "Draw 39: 0.333333333332727"
## [1] "Draw 40: 0.33333333333303"
## [1] "Draw 41: 0.333333333333182"
## [1] "Draw 42: 0.333333333333258"
## [1] "Draw 43: 0.333333333333295"
## [1] "Draw 44: 0.333333333333314"
## [1] "Draw 45: 0.333333333333324"
## [1] "Draw 46: 0.333333333333329"
## [1] "Draw 47: 0.333333333333331"
## [1] "Draw 48: 0.333333333333332"
## [1] "Draw 49: 0.333333333333333"
## [1] "Draw 50: 0.333333333333333"
## [1] "Draw 51: 0.333333333333333"
## [1] "Draw 52: 0.333333333333333"
我们用两位大使的概率创建了一个数据集:
dat <- data.frame(
Draw = rep(1:limit_ndraws, 2),
Probability = c(cumsum(l_win_tot), cumsum(c_win_tot)),
Ambassador = c(rep("Louana", limit_ndraws), rep("Colin", limit_ndraws))
)
我们现在可以将这些概率想象成必要的抽奖次数:
# load package
library(ggplot2)# plot
ggplot(dat) +
aes(x = Draw, y = Probability, colour = Ambassador) +
geom_line(size = 2L) +
labs(
y = "Probability of winning",
caption = "Source: Koh-Lanta 2022"
) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1), limits = c(0, 1)) +
theme_minimal()

作者的情节
但我认为回答最初问题的最合适的图是柱状图:
# create dataset
dat_barplot <- data.frame(
Ambassador = c("Louana", "Colin"),
Probability = c(sum(l_win_tot), sum(c_win_tot))
)# plot
ggplot(data = dat_barplot, aes(x = Ambassador, y = Probability)) +
geom_bar(stat = "identity", fill = "steelblue") +
labs(
y = "Probability of winning",
caption = "Source: Koh-Lanta 2022"
) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1), limits = c(0, 1)) +
theme_minimal()

作者的情节
编码成一个函数
让我们尝试在一个函数中实现这个问题,以便能够在其他初始概率中重用它。
使用:
p_a和p_b分别表示大使 A 和大使 B 抽取白球的概率,n_draws表示允许的最大抽奖次数(默认值= 9999),
我们有以下功能:
ambassadors_game <- function(p_a, p_b, n_draws = 9999) {
q_a <- (1 - p_a)
q_b <- (1 - p_b) a_win <- q_b * p_a
b_win <- q_a * p_b tie <- (p_a * p_b) + (q_a * q_b) p_tie <- c() # set empty vector for prob of ties
a_win_tot <- c() # set empty vector for prob of A winning
b_win_tot <- c() # set empty vector for prob of B winning # find number of necessary draws:
for (i in 1:n_draws) {
p_tie[i] <- tie^i # prob of tie for each draw
limit_ndraws <- sum(p_tie > 2.2e-16) # number of necessary draws
} # compute A and B winning probabilities with the smallest number of necessary draws:
for (i in 1:limit_ndraws) {
a_win_tot[i] <- ((tie^(i - 1)) * a_win) # prob of A winning up to limited number of draws
b_win_tot[i] <- ((tie^(i - 1)) * b_win) # prob of B winning up to limited number of draws
} # save P(A), P(B) and number of necessary draws:
res <- list(
"p_a" = sum(a_win_tot),
"p_b" = sum(b_win_tot),
"ndraws" = limit_ndraws
)
# print results:
return(res)
}
我们测试这个函数,看看它是否与上面的结果相匹配。
首先,如果两位大使都有相同的骨灰盒,就像 2022 赛季之前的情况一样:
ambassadors_game(
p_a = 1 / 2,
p_b = 1 / 2
)## $p_a
## [1] 0.5
##
## $p_b
## [1] 0.5
##
## $ndraws
## [1] 52
游戏的确很公*,每个大使都有 50%的胜算。
第二,向 Louana 和 Colin 赠送骨灰盒,但仅限于第一次抽奖(任意设置 Louana 为大使 A,Colin 为大使 B):
# first draw only
ambassadors_game(
p_a = 1 / 2,
p_b = 1 / 3,
n_draws = 1
)## $p_a
## [1] 0.3333333
##
## $p_b
## [1] 0.1666667
##
## $ndraws
## [1] 1
第三,仍然是向 Louana 和 Colin 赠送骨灰盒,但游戏仅限于 3 和 5 次*局:
# up to 3 draws
ambassadors_game(
p_a = 1 / 2,
p_b = 1 / 3,
n_draws = 3
)## $p_a
## [1] 0.5833333
##
## $p_b
## [1] 0.2916667
##
## $ndraws
## [1] 3# up to 5 draws
ambassadors_game(
p_a = 1 / 2,
p_b = 1 / 3,
n_draws = 5
)## $p_a
## [1] 0.6458333
##
## $p_b
## [1] 0.3229167
##
## $ndraws
## [1] 5
现在,我们来看看 2022 年兰大岛上 Louana 和 Colin 的真实情况:
# Koh-Lanta 2022 situation
out <- ambassadors_game(
p_a = 1 / 2,
p_b = 1 / 3
)out## $p_a
## [1] 0.6666667
##
## $p_b
## [1] 0.3333333
##
## $ndraws
## [1] 52
所有结果都与上述结果相符。
(注意,每个大使的概率可以提取如下:)
# prob ambassador A
out$p_a## [1] 0.6666667# prob ambassador B
out$p_b## [1] 0.3333333
结论
最初的问题来自电视节目 Koh-Lanta,是“如果每个参与者玩大使游戏,他们获胜的概率是多少?".
在本文中,我们展示了如何计算这些概率。此外,我们已经说明了如何将一个简单的概率问题推广到适合许多真实生活情况的过程,以及如何使用一个for loop和一个函数将真实生活情况实现到 r。
最后但同样重要的是,我想重点谈谈两位大使回到岛上向其他冒险者宣布他们的选择后 Denis Brogniart(该节目的著名主持人)所说的话。他提到,由于惩罚折磨黄色部落,有 16%的机会之间的差异 Louana 和科林。
这自然来源于以下两种情况:
第一种情况:
- Louana 有 50%的几率挑到白球。
- 科林有 33.33%的几率挑到白球。
- 差额为 50–33.33 = 16.67%,四舍五入为 16%。
或者,如果游戏仅限于第一次抽牌:
第二种情况:
- Louana 有 33.33%的机会赢得比赛。
- 科林有 16.67%的几率赢得比赛。
- 差额为 33.33–16.67 = 16.66%,四舍五入为 16%。
的确,从这两个角度看问题,差距在 16%左右。Denis Brogniart 提到这 16%的差异是正确的。
然而,如果我们从更广泛的角度来看这场比赛,两位大使之间的机会差异更大。游戏规则说只有当有赢家时才停止。从这个角度来看,如上所述:
- Louana 有 66.67%的几率赢得比赛。
- 科林有 33.33%的几率赢得比赛。
在这种情况下,两位大使获胜的概率差是 66.67–33.33 = 33.34%!
我必须承认,我没有料到两位大使之间会有如此大的分歧。从这个角度来看大使们的游戏看起来与我们被告知的不同,或者与我们在实际计算概率之前所想的不同。(Denis Brogniart,如果你碰巧读到这篇文章,请告诉我这种相对较大的差异是否是有意的。)
对于那些不看这部剧的人来说:当大使们和所有冒险者一起回到岛上时,科林受到了部落成员的严厉批评。他不玩大使游戏的决定(以及为了自救而消灭一名部落成员的决定)被视为背叛。这样的结果是,他在那一集之后就直接被重新组合的部落淘汰了。
我将以下面的问题来结束这篇文章:
如果你是 2022 年 Koh-Lanta 赛季的大使,现在你知道了每个部落获胜的确切概率,你会怎么做?
感谢阅读。
和往常一样,如果您有任何与本文主题相关的问题,请添加评论,以便其他读者可以从讨论中受益。
- 我们永远不会知道,如果两位大使获胜的机会相等,他是否会参加这场比赛。自从我开始看这个电视节目,我从来没有见过任何大使谈判导致大使的游戏,他们都以指定一个冒险家结束。 ↩︎
- 老实说,我想计算这些概率,因为当我和我的女朋友一起看节目时,她看着我问“它们每个的概率是多少?”。我现在可以给她一个明确的答复。 ↩︎
相关文章
原载于 2022 年 5 月 16 日 https://statsandr.comhttps://statsandr.com/blog/koh-lanta-2022-ambassadors-probability-problem/。
克鲁斯卡尔·沃利斯初学者测验
原文:https://towardsdatascience.com/kruskal-wallis-test-for-beginners-4fe9b0333b31
克鲁斯卡尔沃利斯测试:目的,范围,假设,例子,Python 实现

罗马法师在 Unsplash 上拍摄的照片
1.目的
Kruskal Wallis 是一种评估样本是否来自同一分布的非参数方法。它用于两个以上独立或不相关样本的比较。单向方差分析(ANOVA)是 Kruskal-Wallis 检验的参数等价。
1.1 什么是好的业务用例?
让我们来衡量一家制药公司推出的一项活动对一种新上市药物的影响,我们有 1550 个目标和 500 个抵制者。我们观察了处方行为分布,发现它是非正态的(偏斜的),但每个组(目标和抵制者)的形状相似。我们不能进行方差分析;因此,我们应用一个非参数检验,克鲁斯卡尔-沃利斯。
2.范围
由于 Kruskal Wallis 是非参数检验,因此没有假设数据是正态分布的(与 ANOVA 不同)。
- 事实上的零假设是样本来源的总体具有相同的中位数。
- 当有一个属性变量和一个测量变量,并且测量变量不满足方差分析(正态性和同质性)的假设时,最常使用 Kruskal-Wallis 检验
- 与大多数非参数测试一样,它是对分级数据执行的,因此测量观察值使用整个数据集转换为它们的等级:最小或最低值的等级为 1,下一个最小值的等级为 2,接下来的等级为 3,依此类推。在*局的情况下,考虑*均排名。
- 在用等级代替原始值的过程中,信息的损失使得这种检验不如方差分析有效,因此如果数据满足假设,则应使用方差分析。
克鲁斯卡尔-沃利斯检验的零假设有时被认为是组中值相等。然而,只有当你相信每个群体的分布特征是相同的,这才是准确的。即使中位数相同,如果分布不同,Kruskal-Wallis 检验也可以拒绝零假设。
3.假设
可以使用 Kruskal-Wallis 统计来检查不同大小的组。与可比的单向方差分析不同,Kruskal-Wallis 检验不假设正态分布,因为它是非参数程序。然而,该测试假设每个组的分布具有相同的形状和比例,除了中位数的任何变化。
4.方法学
Kruskal Wallis 可用于分析试验组和对照组的表现是否不同。当数据是偏斜的(非正态分布)时,测试将告诉两组是否不同,而不建立任何因果关系。它不会暗示行为差异的原因。
4.1 测试如何进行?
Kruskal Wallis 从 1(最次要的)开始对所有观察结果进行排序。对所有数据点进行排序,而不管它们属于哪个组。绑定值将获得未绑定时的*均排名。
当根据分析变量(处方数量)为所有观察值分配了带符号的等级后,将根据它们的目标/拒绝状态对它们进行区分/分组。之后,计算并比较每组的*均排名。
由于计划或促销活动是针对这一群体展开的,因此目标群体的*均排名应高于抵制者。由于具有显著的 p 值,Target 的表现优于钉子户。这里的挑战是,在存在离群值的情况下,目标群体的*均排名可能会更高,即很少有医生比其他人写更多的脚本。因此,我们总是查看 Kruskal Wallis 获得的算术中位数和结果 p 值来验证/反驳我们的假设。
设 Ni (i = 1,2,3,4,…,g)代表数据中每个 g 组的样本量(即样本,或者在这种情况下,医生的数量)。ri 是第一组的等级之和,ri’是第一组的*均等级。然后,Kruskal Wallis 检验统计量计算如下:

配方 1。说明了计算测试统计的公式。图片由作者使用 Markdown 和 Latex 制作。
如果检验统计量超过阈值卡方值,则拒绝相等总体中位数的零假设。当相等总体的零假设为真时,该统计量具有 k-1 个自由度,并且*似于卡方分布。*似值的 ni 值必须至少为 5(即,一组中至少有 5 个观察值),才能准确。

配方二。说明了测试的 p 值*似值公式。图片由作者使用 Markdown 和 Latex 制作。
使用卡方概率分布表,我们可以得到 g-1 自由度的关键卡方值和期望的显著性水*。或者,我们可以检查 p 值来评价结果的显著性。
4.2 手动运行 H 测试
让我们假设一家制药公司想要了解三组医生是否有不同的患者量(斯蒂芬妮·格伦,未注明)例如,
关键意见领袖/KOL(一个月的患者量):23、42、55、66、78
专家/专家(一个月的病人数量):45、56、60、70、72
全科医生/全科医生(一个月的病人量):18、30、34、41、44
4.2.1 将数据组合成一组后,按升序排列
18 23 24 30 41 42 44 45 55 56 60 66 70 72 78
4.2.2 对排序后的数据点进行排序。出现*局时使用*均值
值:18 23 24 30 41 42 44 45 55 56 60 66 70 72 78
排名:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
4.2.3 计算各组排名之和

图一。显示每组的排名总和。图片来源——由作者准备。
4.2.4 使用公式 1 和图 1 中的数字计算 H 统计数据

公式 3。说明了计算我们处方者群体的 H 统计的公式。图片由作者使用 Markdown 和 Latex 制作。
H=6.72
4.2.5 用
an α=0.05 确定 g-1 自由度的临界卡方值,对于我们的问题(3–1 = 2 自由度)而言,该值应为 5.99。请参考下表。
4.2.6 将 4.2.4 中的 H 值与 4.2.5 中的临界值进行比较
如果临界卡方值小于 H 统计量,则应拒绝说明三个不同组的中值患者量相等的零假设。自 5.99(临界值)< 6.72, we can reject the null hypothesis.
There needs to be more evidence to infer that the medians are unequal if the chi-square value is not lower than the H statistic calculated above.
5. Python Implementation
The null hypothesis that all groups’ population medians are equal is tested using the Kruskal-Wallis H-test. It is an ANOVA variant that is non-parametric. The test utilizes two or more independent samples of varying sizes. Note that disproving the null hypothesis does not reveal how the groups differ. To identify which groups are different, post hoc comparisons between the groupings are necessary.
from scipy import stats
x = [1, 3, 5, 8, 9, 12, 17]
y = [2, 6, 6, 8, 10, 15, 20, 22]
stats.kruskal(x, y)KruskalResult(statistic=0.7560483870967752, pvalue=0.3845680059797648)print(np.median(x))
print(np.median(y))8.0
9.0print(np.mean(x))
print(np.mean(y))7.86
11.12
The output generated by Python is shown above. It should be noted that although a marked difference is observed in the mean of values across the two categories, this difference, when taking the median into account, is insignificant as the p-value is much greater than 5%.
6. Conclusion
Kruskal Wallis test is instrumental when dealing with particularly skewed samples. It can be used widely for a test control group during a campaign rollout or even when performing A/B testing. This is applicable for most industry use cases since each customer has different behavior when dealing with customers in a retail space or doctors in a pharmaceutical landscape. When we look at basket size or patient volume, few customers buy more, whereas few doctors have more patients. Hence for such skewed distribution, it is vital to put a Kruskal Wallis test to check if the behaviors are similar.
7。参考
斯蒂芬妮·格伦。“克鲁斯卡尔·沃利斯 H 检验:定义、例子、假设、SPSS”来自https://www.statisticshowto.com/:对我们其余人的初步统计!https://www . statistics show to . com/probability-and-statistics/statistics-definitions/kruskal-Wallis/
关于作者:高级分析专家和管理顾问,帮助公司通过对组织数据的业务、技术和数学的组合找到各种问题的解决方案。一个数据科学爱好者,在这里分享、学习、贡献;可以和我在 上联系 和 推特;**
Kruskal-Wallis 检验,或者方差分析的非参数版本
学习如何在 R 中执行 Kruskal-Wallis 检验(方差分析的非参数版本),以在非正态假设下比较 3 组或更多组

照片由内森·杜姆劳
介绍
在之前的一篇文章中,我们展示了如何用 R 中的 ANOVA 来比较三个或更多的组。
请记住,与许多统计测试一样,单向 ANOVA 需要满足一些假设,以便能够使用和解释结果。特别是,方差分析要求残差*似遵循正态分布。 1
幸运的是,如果不满足正态假设,还有 ANOVA 的非参数版本:Kruskal-Wallis 检验。
在本文的其余部分,我们将展示如何在 R 中执行 Kruskal-Wallis 测试,以及如何解释其结果。我们还将简要说明如何进行事后测试,以及如何在图上直接显示所有必要的统计结果。
数据
本文的数据基于penguins数据集(众所周知的iris数据集的替代物),可通过{palmerpenguins}包访问:
# install.packages("palmerpenguins")
library(palmerpenguins)
原始数据集包含 3 个不同物种(阿德利企鹅、下巴颏企鹅和巴布亚企鹅)的 344 只企鹅的数据。它包含 8 个变量,但我们在本文中只关注脚蹼长度和物种,所以我们只保留这 2 个变量:
library(tidyverse)dat <- penguins %>%
select(species, flipper_length_mm)
(如果不熟悉管道运算符(%>%,也可以用penguins[, c("species", "flipper_length_mm")]选择变量)。在关于数据操作的文章中了解更多选择变量的方法。)
在进行测试之前,按组对整个样本做一些描述性统计总是一个好的做法,这样我们手头的数据就有了一个大致的概述。
# entire sample
summary(dat)## species flipper_length_mm
## Adelie :152 Min. :172.0
## Chinstrap: 68 1st Qu.:190.0
## Gentoo :124 Median :197.0
## Mean :200.9
## 3rd Qu.:213.0
## Max. :231.0
## NA's :2# by group
library(doBy)
summaryBy(flipper_length_mm ~ species,
data = dat,
FUN = median,
na.rm = TRUE
)## # A tibble: 3 × 2
## species flipper_length_mm.median
## <fct> <dbl>
## 1 Adelie 190
## 2 Chinstrap 196
## 3 Gentoo 216# boxplot by species
ggplot(dat) +
aes(x = species, y = flipper_length_mm, fill = species) +
geom_boxplot() +
theme(legend.position = "none")

作者图片
基于箱线图和汇总统计数据,我们已经看到,在我们的样本中,阿德利企鹅似乎有最小的鳍状肢,而巴布亚企鹅似乎有最大的鳍状肢。然而,只有一个可靠的统计测试才能告诉我们是否能对我们的人口推断出这个结论。
克鲁斯卡尔-沃利斯试验
目标和假设
如前所述,克鲁斯卡尔-沃利斯检验允许比较三组或更多组。更准确地说,它用于根据一个数量变量来比较三个或更多组。这可以看作是对曼恩-惠特尼检验的扩展,该检验允许在非正态假设下比较两组。
在我们的例子中,我们将使用 Kruskal-Wallis 测试来帮助我们回答以下问题:“三种企鹅的鳍状肢长度不同吗?”
克鲁斯卡尔-沃利斯检验的无效假设和替代假设是:
- H0:这三个物种的鳍状肢长度是一样的
- H1:至少有一个物种和另外两个物种的鳍状肢长度不同
注意,至于方差分析,另一个假设是 而不是 所有物种的鳍长度都不同。所有物种*等的反面(H0)是至少有一个物种与其他物种不同(H1)。在这个意义上,如果零假设被拒绝,这意味着至少有一个物种不同于其他 2,但不一定是所有 3 个物种彼此不同。可能巴布亚企鹅的鳍状肢长度不同于下颚带企鹅和阿德利企鹅,但是下颚带企鹅和阿德利企鹅的鳍状肢长度相似。必须执行其他类型的测试(称为事后测试,稍后介绍)来测试所有 3 个物种是否不同。
假设
首先,Kruskal-Wallis 测试根据定量变量比较了几个组。因此,必须有一个定量因变量(对应于与问题相关的测量值)和一个定性自变量(至少有两个水*来确定要比较的组)。 2
其次,记住克鲁斯卡尔-沃利斯检验是非参数检验,所以不需要正态假设。然而,独立性假设仍然成立。
这意味着从总人口中有代表性的和随机选择的部分收集的数据,在各组之间和每组内部应该是独立的。独立性的假设通常基于实验的设计和对实验条件的良好控制来验证,而不是通过正式的测试。如果你仍然不确定基于实验设计的独立性,问问自己一个观察是否与每个组内或组间的另一个观察相关(如果一个观察对另一个有影响)。如果没有,最有可能是你有独立样本。如果样本之间的观察值(形成不同的待比较组)是相关的(例如,如果三个测量值是在相同的个体上收集的,这是医学研究中在测量指标(I)治疗前、(ii)治疗期间和(iii)治疗后)时经常出现的情况),则应首选弗里德曼测试,以便考虑样本之间的相关性。
关于同方差性(即方差相等):只要使用 Kruskal-Wallis 检验来比较各组,同方差性就不是必需的。如果你想比较中位数,Kruskal-Wallis 检验需要同质性。 3
在我们的例子中,假设独立性,我们不需要比较中位数(我们只对比较组感兴趣),所以我们可以继续如何在 r 中进行测试。注意,正态性假设可能成立,也可能不成立,但对于本文,我们假设满足而不是。
在 R 中
R 中的 Kruskal-Wallis 检验可以用kruskal.test()函数来完成:
kruskal.test(flipper_length_mm ~ species,
data = dat
)##
## Kruskal-Wallis rank sum test
##
## data: flipper_length_mm by species
## Kruskal-Wallis chi-squared = 244.89, df = 2, p-value < 2.2e-16
该输出中最重要的结果是p-值。我们将在下一节展示如何解释它。
解释
基于 Kruskal-Wallis 检验,我们拒绝零假设,我们得出结论,至少有一个物种在鳍肢长度方面是不同的(p-值< 0.001)。
(为了便于说明,如果p-值大于显著性水*α = 0.05:我们不能拒绝零假设,因此我们不能拒绝三种企鹅的鳍长度相等的假设。)
事后测试
我们刚刚表明,至少有一个物种在鳍状肢长度方面与其他物种不同。尽管如此,克鲁斯卡尔-沃利斯测试的局限性也来了:它没有说明哪一个群体与其他群体不同。
为了了解这一点,我们需要使用其他类型的测试,称为事后测试(在拉丁语中,“在此之后”,因此在获得具有统计意义的 Kruskal-Wallis 结果之后)或多重成对比较测试。对于感兴趣的读者,可以在这里找到更详细的事后测试解释。
重大克鲁斯卡尔-沃利斯测试后最常见的事后测试有:
- 邓恩试验
- 科诺弗试验
- 奈梅尼试验
- 成对 Wilcoxont 检验
邓恩试验是最常见的一种,下面是如何在 R. 4 中进行试验
邓恩试验
library(FSA)dunnTest(flipper_length_mm ~ species,
data = dat,
method = "holm"
)## Comparison Z P.unadj P.adj
## 1 Adelie - Chinstrap -3.629336 2.841509e-04 2.841509e-04
## 2 Adelie - Gentoo -15.476612 4.990733e-54 1.497220e-53
## 3 Chinstrap - Gentoo -8.931938 4.186100e-19 8.372200e-19
感兴趣的是最后一列(调整后的p-值,针对多重比较进行了调整)。这些 p 值应该与您想要的显著性水*(通常为 5%)进行比较。
根据输出,我们得出结论:
- 阿德利和下巴颏儿差异显著(p < 0.001)
- Adelie and Gentoo differ significantly (p < 0.001)
- Chinstrap and Gentoo differ significantly (p < 0.001)
Therefore, based on the Dunn test, we can now conclude that 所有三个物种的鳍状肢长度都不同)。
统计结果和绘图的组合
在 R 中执行 Kruskal-Wallis 和事后测试的一个非常好的替代方法是使用{ggstatsplot}包中的ggbetweenstats()函数:
library(ggstatsplot)ggbetweenstats(
data = dat,
x = species,
y = flipper_length_mm,
type = "nonparametric", # ANOVA or Kruskal-Wallis
plot.type = "box",
pairwise.comparisons = TRUE,
pairwise.display = "significant",
centrality.plotting = FALSE,
bf.message = FALSE
)

作者图片
这种方法的优点是所有必要的统计结果都直接显示在图上。
克鲁斯卡尔-沃利斯试验的结果显示在图上方的副标题中(p-值在p =之后)。此外,事后测试的结果通过荣誉显示在每个组之间,箱线图允许可视化每个物种的分布。
摘要
在这篇文章中,我们回顾了克鲁斯卡尔-沃利斯测试的目的和假设,以及它的基本假设。然后,我们展示了如何在 R 中进行测试,以及如何解释结果。
我们还展示了在重要的 Kruskal-Wallis 测试之后最常见的事后测试 Dunn 测试。
最后但同样重要的是,我们提出了一种简洁的方法来显示分组数据和同一个图上的所有统计结果。
感谢阅读。
和往常一样,如果您有与本文主题相关的问题或建议,请将其添加为评论,以便其他读者可以从讨论中受益。
参考
詹姆斯·史蒂文斯,2013 年。中级统计:现代方法。劳特利奇。
- 这是小样本的情况。对于大样本量,不需要正态性(这是一个常见的误解!).根据中心极限定理,即使数据不是正态分布,大样本的样本均值通常也是正态分布的*似值( Stevens 2013 )。详见本注释。 ↩︎
- 注意,理论上,Kruskal-Wallis 检验也只能用于两组。然而,在实践中,我们对两组使用曼-惠特尼检验,对三组或更多组使用克鲁斯卡尔-沃利斯检验。 ↩︎
- 在这篇文章中查看更多关于差异的信息。 ↩︎
- 注意还有其他的p-值调整方法。更多选项见
?dunnTest。 ↩︎
相关文章
原载于 2022 年 3 月 24 日https://statsandr.com。
Kubeflow MLOps:通过 CI / CD / CT 实现自动管道部署
创建先进的 Kubeflow 管道,并通过持续集成、部署和培训实现其部署和更新的自动化

在我的上一篇 kubeflow 文章中,我们经历了以下几个必要的步骤来建立一个有效的管道:
- 创建一个 Kuberneter 集群
- 安装 Kubeflow
- 创建容器注册表
- 构建一个容器映像,并将其推送到您的注册表中
- 让 Kubeflow 访问您的 S3 桶
- 用输入和输出工件创建 Kubeflow 组件
- 创建一个 Kubeflow 管道,上传并运行它
如果您已经有一个安装了 Kubeflow 的功能正常的 Kubernetes 集群,您可以直接遵循本指南。如果你不知道,我强烈建议你看看我以前的文章。
这一次,我们将更进一步:
- 制作一个包含预处理、建模、推理和性能评估的高级流水线
- 使用 GitHub 动作执行 CI 和 CD
- 将 GitHub Actions 连接到 Kubeflow 端点,并启动更新后的管道

您将通过本文构建的 Kubeflow 管道。作者图片
源数据集和 GitHub 报告
在本文中,我们将使用来自西雅图建筑能源基准的数据,这些数据可以在 this Kaggle page 上找到,并建立一个模型来预测温室效应气体的总排放量,如TotalGHGEmissions.栏所示
这个数据集由两个表组成,分别是 2015 年和 2016 年的观测值。我们将把它们储存在一个 S3 桶里,我们的管道将从这个桶里获得资源。如果您遵循了我以前的文章,那么您的 Kubeflow 服务器应该可以访问您的 bucket。
本指南中显示的所有代码都可以在这个公共库中找到。
高级管道—步骤 1:组件
如果您已经知道如何创建 Kubeflow 组件和管道,可以直接进入本文的“上传管道”一章。
我们的管道将有以下步骤:
- 合并 2015 年和 2016 年的数据,并将结果分为训练集和测试集
- 数据清理、NaN 填充和特征工程
- 数据预处理和准备
- 模型建筑:SVM、随机森林和 XGBoost
- 评估结果,选择最佳模型,训练并保存它
- 预测
同样,所有这些步骤的代码都可以在我的 repo 的 kf_utils 文件夹中找到,但让我们详细说明其中一个,以便我们可以更好地理解 Kubeflow 的“语法”:
这里发生了什么?
我们要做的第一件事是导入描述 op 所需的包,并定义将成为 op 的evaluate_models()函数。
结构如下:
def function(input_1 : type, input_2 : type, …) -> Output
这里,输出是一个命名的元组,您可以在其中声明构成 op 输出的几个元素。这些是您稍后必须在代码中使用的名称,以保存工件。
请注意,对于 csv 文件这样的经典输出,这种简化的结构就足够了:
import kfp.components as comp
def function(input_1 : type, input_2 : type, …,
csv_output : comp.OutputPath('csv')
之后,我们导入这个组件所必需的包,比如 matplotlib。我在第一篇文章中解释了为什么导入是在函数内部而不是之前进行的。
我还定义了我的 easy_bar_plot 函数;注意,一个好的做法是在 repo 内部的一个模块中定义它,但是这里我直接在函数中定义它,以使代码片段具有更好的可读性。
性能和最佳超参数是先前库伯流分量的伪影输出,它们被存储在字典中并被绘制。保存绘图,并根据最佳 R2 分数选择最佳模型。
由于 Kubeflow 还不接受任何类型的绘图作为工件输出,一个变通的解决方案是将绘图上传到 s3 bucket,这就是我们在第 116 和 119 行中所做的。
现在,当我定义函数时,我将输出指定为命名元组:NamedTuple('Outputs', [('best_model' : str), ('hyperparams' : dict)])。Kubeflow 将理解我的工件输出由 2 个元素组成,因此期望 op 函数返回 2 个对象。
在第 123 行,我们确实返回了两个对象:
return (best_model, best_models_hyperparams)
best_model作为第一个返回的对象,它将被匹配到NamedTuple的第一个元素,并被 Kubeflow 自动认为是一个字符串。
这里的最后一个代码块使用create_component_from_func函数将 python 函数转换为实际的 Kubeflow op,该函数以 yaml 文件的形式,将要转换的函数、输出路径和基本 docker 图像作为参数。在这里,我提供了一个我创建的光公众形象,如果你读了我以前的文章,你现在应该知道如何创建一个。
在我的回购中,我所有的 Kubeflow 组件(他们的。py 文件及其结果。yaml 文件)存储在 kf_utils 目录中。
高级管道—步骤 2:从组件到管道
一旦创建了完成上述步骤的所有组件,您就可以创建下面的管道。由于我在上一篇文章中已经谈到了管道创建,所以我不会在这里过多地讨论细节。
我们将把这段代码保存在“管道”目录中。
上传管道
我们现在需要将这个管道上传到 Kubeflow。我们将上传的不是 python 文件,而是用 shell 命令创建的编译后的 yaml 文件:
dsl-compile --py pipeline/pipeline.py --output pipeline/pipeline.yaml
我们现在在“管道”目录中有了管道 yaml。在我的上一篇文章中,我向您展示了如何使用中央仪表板将它上传到 Kubeflow,但是这里我们将从 python 命令中完成。
这就是棘手的地方。连接到 Kubeflow pipelines 管理器的最简单、最不受*台限制的方法是打开一个客户端会话,如下所示:
client = kfp.Client(host=host, cookies=cookies)
韩国自由党。Client()函数有两个参数,它们是:
- kubeflow 主机端点应该是这样的:
123-isitiosystem-istio-2af2-456.us-west-2.elb.amazonaws.com/pipeline
,这是您的中央仪表板的 URL,后跟/pipeline - 当您使用登录名和密码登录中央控制面板时,存储在浏览器中的身份验证 cookie
您可以在代码中复制并粘贴 cookie 值来打开客户端会话,但这意味着每个人都可以清楚地看到它,而且 cookie 不可避免地会过期。为了弥补这一点,我们将使用 beautiful soup 登录仪表板并检索 cookie。
最后,我们希望使用 GitHub 动作来启动管道——这意味着登录、密码和 URL 可以作为秘密保存,并作为环境变量传递给 python 脚本。因此,我们的管道启动脚本将如下所示:
运行该脚本将创建或更新并启动您的 Kubeflow 管道。请注意,要实现这一点,您需要已经创建了一个 Kubeflow 实验,但这在中央仪表板中很容易做到,并且它们是可重用的。
在我的回购协议中,这个脚本叫做run_pipeline.py,在主目录中。
我们现在拥有了实现全自动流程所需的所有工具。
自动化—步骤 1:准备 GitHub 秘密
run_pipeline.py脚本将使用 4 个秘密值:
- URL:你的 Kubeflow 服务器 URL,应该是这样的:
123-isitiosystem-istio-2af2-456.us-west-2.elb.amazonaws.com - 端点:kubeflow 主机端点,看起来应该是这样的:
123-isitiosystem-istio-2af2-456.us-west-2.elb.amazonaws.com/pipeline - 电子邮件和密码:您的 Kubeflow Dashboard Center 日志
进入 GitHub repo,进入设置,然后进入密码:

GitHub 行动秘密。作者图片
从那里,添加您的秘密。我们分别称之为 KBFL_URL、KBFL_ENDPOINT、KBFL_LOGIN、KBFL_PASSWORD。
自动化—步骤 2:设置 GitHub 操作
点击“行动”。您应该在此页面上显示建议的操作。选择其中任何一个,例如名为“Python 应用程序”的应用程序,然后单击“配置”。

GitHub 操作主页。作者图片
在下面的配置窗口中,用以下代码替换 yaml 代码:
这个动作做什么在主分支上的每个提交或合并请求上,GitHub 动作将创建一个环境,安装 python 及其依赖项,安装 kfp,用dsl-compile重建管道,并使用从 GitHub secrets 导入的适当环境变量启动run_pipeline.py脚本。
在构建所有这些资产时,GitHub actions 将执行 CI / CD 步骤,Kubeflow 管道中的任何不一致将在dsl-compile步骤中被检测到,并返回一个错误。
如果要实现连续训练(CT),可以将run_pipeline.py脚本中的client.run_pipeline()调用改为client.create_recurring_run()。
现在你知道了!当您需要修改管道的任何组件时,您可以创建一个分支,修改组件,运行组件脚本以重新创建 op,提交并创建一个合并请求。GitHub 将重新创建和上传管道,并在 Kubeflow 上发布,这都是自动化的。
后续步骤
就 MLOps 良好实践而言,这里的自动化并不是真正的 CD 和 CT,但是您现在已经拥有了完成这项工作的所有必要工具。
从那里开始,下一步将是只有一个模型定期用client.create_recurring_run()重新培训,这将使我们刚刚建立的您的培训管道。
然后,您应该创建一个推理管道,该管道只加载模型并进行预测,这将允许您设置另一种类型的循环(或按需)运行,而不必每次都重新训练模型。最后,您还应该有一个监视管道,当它检测到模型性能下降时,就会触发训练管道。
您还可以在您的 CD 中添加一个性能标准,这样您的 GitHub 操作只有在新添加的特性提高了模型的性能时才会成功。
Kubernetes 在云中部署机器学习 Rest API
关于 Kubernetes 概念基础以及如何在 Azure Kubernetes 服务上设置机器学习 Rest API 的分步指南

什么是 Kubernetes?
为生产部署创建可扩展的 DevOps 管道需要支持多个日常部署、超大规模扩展和软件应用监控。Kubernetes 是一个强大的 DevOps 工具,用于实现可伸缩的软件应用程序部署。Kubernetes 是一个工具,用于自动部署一个应用程序的多个实例,扩展、更新和监控部署的健康状况。Kubernetes 通过容器编排来完成这些。
什么是容器编排?
它是容器化应用程序的多个实例的部署、扩展、管理和监控的自动化。
注:在本教程中,我们将和 Kubernetes 一起讨论 docker 容器编排 。
目录
4 在 Azure Kubernetes 服务 上设置机器学习应用的分步指南
Kubernetes 装置
Kubectl :这是用于管理 Kubernetes 的命令行工具,用户通过它与 Kubernetes 集群通信。我们使用 Kubectl 将命令传递给 Kubernetes 集群。在本文的后面部分,我将描述 Kubernetes 集群及其组件。
在 Linux 上安装 Kubectl
https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
在 Windows 上安装 ku bectl
https://kubernetes.io/docs/tasks/tools/install-kubectl-windows/
在 MacOS 上安装 Kubectl
https://kubernetes.io/docs/tasks/tools/install-kubectl-macos/
Kubernetes 的关键概念
Kubernetes 中有很多令人困惑的概念,对于初学者来说理解起来非常困难。在本教程中,我将阐述 Kubernetes 的基本概念,并解释如何在 Azure cloud 中使用 Kubernetes 执行逐步部署。

作者照片
此图展示了 Kubernetes 集群及其组件。它显示了一个具有单个节点和一个控制*面的群集。
Kubernetes 集群:它是服务器的组合,这些服务器相互作用以实现容器编排。Kubernetes 集群服务器有两种主要类型:
- 控制*面 和
- 节点
控制*面:这是充当 Kubernetes 集群大脑的服务器。它控制集群内的活动。控制*面由使其能够在集群内执行控制的组件组成。控制*面组件包括以下内容:
- Kubeapi-server :这是 控制*面 的组件,作为向集群发送指令的媒介或路径。命令通过 kubectl 命令行工具通过kube API-server传递到集群。
- Kube-Controller-Manager:控制 控制*面 中主要活动的组件。它执行用户从集群的传递到kube API-server的指令。它控制诸如部署、扩展和监控集群中部署的容器的健康状况之类的活动。
- etcd :这是 控制*面 的组件,为 Kubernetes 集群存储数据。可以在其中找到有关集群中部署的信息。
- Kube-Scheduler :这是 控制*面 的组件,用于调度 Kubernetes 集群中的活动。
- 云控制器管理器:这是 控制*面 的组件,管理云中的部署。Cloud-controller-manager是在 Azure、AWS、Google 云*台等云服务中用于 Kubernetes 集群部署的组件。
节点:这是服务器,作为运行容器化应用程序的工作机。一个 Kubernetes 集群可以有多个节点,但是在本教程中,我们将把我们的示例限制在单个节点的 Kubernetes 集群。
节点组件包括以下内容:
- Kube-Proxy :这是 节点 的组件,处理 Kubernetes 集群的网络任务。它管理网络服务,使得访问在 节点 中运行的容器化应用成为可能。
- Kubelet :这是作为 Kubernetes 集群的 节点 和 控制*面 之间的通路的组件。 节点 通过 kubelet 与 控制*面 中的活动进行交互。
- 容器运行时 : 这是将要在 Kubernetes 集群中部署的容器的运行时。使用的容器运行时是 Docker 。
Kubernetes 部署的概念
在 Kubernetes 中部署应用程序时,需要考虑一些重要的概念。这些概念是:
- 分离舱
- 服务
- 部署
吊舱:它们是由一组共享存储和网络资源的一个或多个类似容器产生的可部署单元。Docker 容器在 Kubernetes 集群的 节点 中的 pods 内进行编排(部署、扩展和监控)。
服务:它是一个网络接口,可以与 pod 中运行的容器进行交互。Kubernetes 中有不同类型的服务,但在本文中,我将讨论两种主要的服务类型:
- 负载*衡器:这是一种允许用户从集群外部访问 pod 中正在运行的容器的服务。它向外界公开了 Kubernetes 集群中的应用程序。它使用户能够与部署的 Kubernetes 应用程序进行交互。 负载*衡器 确保与 Kubernetes 集群中运行的应用程序的互联网连接。
- 集群 IP:这是一种允许集群内部交互或连接的服务类型。该服务将 pods 中运行的容器连接到 Kubernetes 集群中的数据库系统。
Deployment :它是 Kubernetes 中的一个资源对象,处理包含容器化应用程序的 pod 的创建、容器的多个实例的伸缩、容器化应用程序的健康监控、容器化应用程序更新的推出和回滚。
Azure Cloud 中的 Kubernetes 部署(云中的机器学习应用程序部署)
我已经在 docker hub 上为 PixelLib ( 我为执行图像中的对象分割而构建的库)发布了一个 docker 图像。这个 docker 图像将被部署为一个机器学习应用程序,用于在 Azure Kubernetes 服务上检测图像中的对象。
创建一个免费的 azure 帐户:我们需要一个 Azure 帐户,以便能够在 Azure 上创建服务。当你创建一个 azure 帐户时,你将在第一个月免费使用 azure 服务。
*https://azure.microsoft.com/en-us/free/
安装 Azure CLI: 我们需要 azure cli 这是在 Azure 账号上创建服务并与之交互的命令行工具。Azure cli 使得在 Azure 帐户上创建服务和与服务通信变得非常方便。
按照以下教程中的说明安装 azure cli:
https://docs.microsoft.com/en-us/cli/azure/install-azure-cli
使用 Azure CLI 连接到 Azure:当 azure cli 成功安装后,在命令行工具中使用此命令登录到您的 Azure 帐户。
az login
创建资源组:azure 中的资源组充当将在 azure 上托管的其他服务的主机。这是一种将许多服务组合在一起的方法。使用此命令在 azure cli 上创建一个资源组。
az group create --name resgroup --location eastus
资源组将在不到一分钟的时间内创建,这将是所创建的资源组的输出 json。
{
"id": "/subscriptions/<guid>/resourceGroups/resgroup",
"location": "eastus",
"managedBy": null,
"name": "resgroup",
"properties": {
"provisioningState": "Succeeded"
},
"tags": null
}
创建 Azure Kubernetes 服务:使用以下命令使用 azure cli 创建 Azure Kubernetes 服务:
az aks create --resource-group resgroup --name deploymentcluster --node-count 1 --enable-addons monitoring --generate-ssh-keys
注意:您可以从创建 Azure Kubernetes 服务的命令中观察到,我们使用了之前创建的名为 resgroup 的资源组来创建 Kubernetes 集群。我们指定要创建的集群的名称为deployment cluster,并将 节点计数 指定为 1。Kubernetes 集群的创建需要一些时间。创建后,它将返回一个关于集群的长 json 格式的信息。
连接到 Azure 上的 Kubernetes 集群:我们将使用这个命令创建 Kubernetes 集群并连接到它。
**az aks get-credentials --resource-group resroup --name deploymentCluster**
连接后,它会显示以下输出:
**Merged "deploymentcluster" as current context in C:\Users\<pathtokubenetesconfiglocation>**
它合并了来自 Azure 的 Kubernetes 集群,作为 Kubernetes 的当前上下文。Kubernetes 上下文是用于访问 Kubernetes 集群的配置。deployment cluster是使用的配置集群。
Kubernetes 部署代码
逐步部署代码分解
- apiVersion :我们定义了部署的 api 版本为 apps/v1 。
- ****种类:设置为 展开 。
- 元数据:包含部署的信息。我们将部署的名称设置为pixellib-demo。**
- spec :它包含了整个部署和将要创建的 pod 的规范的完整列表。
- spec.replicas :我们定义了 pod 的数量,并将其设置为 2。这意味着我们的应用程序将有两个实例在运行。
- spec . selector . matchlabels**:选择 部署中使用的 pod, 使用 pod 标签app:pixellib-pod。
- spec.template.metadata :我们为 pod 定义元数据。spec . template . metadata . name为 pod 指定一个名称,即 pixellib-pod 。spec . template . metadata . labels s指定 pod 的标签,即app:pixellib-pod使部署可以选择带有该标签的 pod。我们稍后将定义的服务也将使用该标签来选择 pod。
- spec . template . spec . containers:我们为容器化的应用程序定义规范。spec . template . containers . name指定将从 docker 图像创建的容器的名称,该图像被命名为pixellib-container。spec . template . containers . image指定将从其创建 docker 容器的图像,所使用的图像是 docker hub 上发布的 docker 图像 I,名称为ayoolaolaofenwa/pixellibapi。spec . template . containers . imagepullpolicy设置为 IfNotPresent,that*指定如果图像不可用,则应拉取或下载图像。spec . template . containers . ports . container port指定容器端口,该端口必须与所使用的 docker 映像中暴露的端口相匹配,它是 5000 。*
创建 Kubernetes 部署
在部署文件所在的文件夹中打开命令行,输入命令使用 kubectl 创建部署。
**kubectl apply -f app_deployment.ymldeployment.apps/pixellib-demo created**
这是创建部署时的输出:pixellib-demo是 yaml 文件中为部署指定的名称。
****检查创建的 pod:在 yaml 文件中,我们将 pod 的数量设置为 2,将有两个 pod(同一个应用程序的两个实例)。您可以使用以下命令检查窗格的状态:
**kubectl get podsNAME READY STATUS RESTARTS AGE
pixellib-demo-676bdf6cf9-7vh2 0/1 Container 0 10s
Creating
pixellib-demo-676bdf6cf9-9mvz2 0/1 Container 0 10s
Creating**
这将是刚刚创建部署时 pod 的状态。我们可以看到,pod 的状态是container creating(它仍然在拉 docker 图像,当图像被拉时,将从图像创建一个容器)并且 pod 的年龄是 10s(它是在 pod 被创建后的 10 秒)。pod 中的容器将在几分钟或几秒钟内创建,这取决于您的互联网连接速度。
**NAME READY STATUS RESTARTS AGE
pixellib-demo-676bdf6cf9-7vh2 1/1 Running 0 70s
pixellib-demo-676bdf6cf9-9mvz2 1/1 Running 0 70s**
pod 已准备好,容器已创建并正在运行。pod 的状态变为 运行 。
****Pods 运行失败时该怎么办:如果收到错误 ImagePullBackOff, it 表示 yaml 文件中指定的 docker 映像不存在。
**NAME READY STATUS RESTARTS AGE
pixellib-demo-676-7j2c2 0/1 ImagePullBackOff 0 10s**
您可以使用以下命令检查其中一个窗格以了解问题所在:
**kubectl describe pod pixellib-demo-676bdf6cf9-7vh2**
此命令将显示 pod 未能完成的原因。
****为部署创建服务:我们已经成功创建了一个部署,我们需要创建一个 Kubernetes 服务来访问集群中运行的应用程序。
【Kubernetes 服务创建代码
一步一步服务创建代码分解
- apiVersion :我们为服务定义了 api 版本,即 v1 。
- **种类:**我们将种类定义为 服务。
- 元数据:我们指定服务的名称为pixellib-Service。**
- 我们为将要创建的服务定义规范。
- spec.type: 我们将服务类型定义为 LoadBalancer、 ,这将使我们能够从外部访问 pod 内运行的应用。
- spec.ports: 我们定义服务中使用的端口。我们将端口 80 映射到正在运行的容器的目标端口的端口 5000 。
- spec.selector: 我们从部署中指定将使用名为app:pixellib-pod的服务的 pod。
创建 Kubernetes 服务
在服务 yaml 文件所在的文件夹中打开命令行,输入命令使用 kubectl 创建服务。
**kubectl apply -f app_service.ymlservice/deploy-service created**
这是创建服务时的输出:pixellib-Service是 yaml 文件中为服务指定的名称。
****访问 Kubernetes 部署:使用这个命令访问在 Kubernetes 集群的 pods 中运行的机器学习应用程序。
**kubectl get service pixellib-service**
我们获得了我们为 pixellib 应用程序部署创建的服务pixellib-服务** 。这将是输出:**
**NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
pixellib-service LoadBalancer 10.0.7.173 20.88.171.40 80:30832/TCP**
注意:在输出中,我们有 外部 ip ,这是使我们能够访问在集群内运行的应用的 ip 地址。在这种情况下,ip 地址是20.88.171.40。 这个 ip 地址作为 restapi 接口,供我们访问集群中运行的应用程序,执行对象的分割。我们可以在代码中粘贴 ip 地址并执行对象检测。**
样本图像

测试代码
输出
**{'boxes': [[145, 119, 282, 457], [162, 273, 261, 512], [238, 283, 398, 387], [442, 321, 496, 350], [411, 219, 422, 242], [31, 316, 51, 356], [352, 217, 366,243], [272, 264, 291, 317], [274, 241, 368, 306], [277, 233, 369, 309], [377, 307, 405, 350], [0, 157, 65, 563], [468, 301, 497, 328], [49, 307, 73, 328]],'class_ids': [0, 1, 2, 2, 9, 2, 9, 0, 5, 7, 2, 2, 7, 2], 'class_names': ['person', 'bicycle', 'car', 'car', 'traffic light', 'car', 'traffic light', 'person', 'bus', 'truck', 'car', 'car', 'truck', 'car'], 'mask_shape': [580, 498, 14],'object_counts': {'bicycle': 1, 'bus': 1, 'car': 6, 'person': 2, 'traffic light': 2, 'truck': 2},'scores': [99, 99, 99, 99, 97, 94, 90, 67, 65, 47, 47, 44, 37, 36]}**
输出包括关于图像中检测到的对象的许多细节,包括框坐标值、类 id、类名、对象计数、分割掩模形状和预测分数。我们已经成功地在 Kubernetes 集群中运行了一个图像分割应用程序。
图像分割前端 API: 如果你在浏览器中粘贴 ip 地址20.88.171.40,它会加载一个简单的 web API 来执行对象分割。

作者照片

作者照片
您可以选择任意一幅图像,按下按钮,它将在几秒钟内显示一幅输出的分割图像。

作者照片
****记录 Kubernetes 部署:如果在 Kubernetes 部署中加载应用程序时遇到错误,可以使用以下命令轻松调试部署:
**kubectl logs deployment.apps/<name of your deployment>**
示例:
**kubectl logs deployment.apps/pixellib-demo**

作者照片
它将生成应用程序运行的报告。它生成在集群的 pod 内部运行的 flask 应用程序的日志。我们的应用程序运行良好,没有任何错误,如果有任何错误,我们可以很容易地从日志报告中观察到错误。
删除 Kubernetes 部署:
使用这个命令可以很容易地删除 Kubernetes 部署,
**kubectl delete -n default deployment pixellib-demo**
删除 Kubernetes 服务:
使用这个命令可以很容易地删除 Kubernetes 服务,
**kubectl delete -n default service pixellib-service**
删除 Kubernetes 集群
您可以使用这个 azure cli 命令轻松删除用于部署的 Kubernetes 集群。
**az aks delete --name deploymentcluster --resource-group resgroup**
删除 Azure 资源组
使用此 azure cli 命令删除 azure 资源组。
**az group delete -n resgroup**
结论
我们在本文中详细讨论了 Kubernetes、Kubernetes 集群的基本概念,以及其组件如何工作。我们还描述了 Kubernetes 部署的基础,并给出了如何在 azure cloud 中设置机器学习 rest API 的 Kubernetes 部署的分步指南。
访问 PixelLibAPI github 库:
**https://github.com/ayoolaolafenwa/PixelLibAPI
注意:阅读我在 docker 上的文章,在那里我解释了 docker 的基础知识以及如何将机器学习 Rest API docker 化。
通过以下方式联系我:
Linkedin: 阿尤拉·奥拉芬娃
推特: @AyoolaOlafenwa***
Kubernetes Ingress 解释道
原文:https://towardsdatascience.com/kubernetes-ingress-explained-1aeadb30f273
TLS 证书入口实用介绍

图片来源: Pixabay
根据 Kubernetes 官方文档:
入口将来自群集外部的 HTTP 和 HTTPS 路由暴露给群集中的服务。流量路由由入口资源上定义的规则控制。
简单地说,入口充当 Kubernetes 集群中部署的服务和外部用户或客户端之间某种形式的受控流量路由链接。
让我们用一个更简单的例子来说明这一点。让我们想象一下,Medium 上的所有文章都部署在某个集群上,其中每个发布本质上都是一个 Kubernetes 部署。所有这些部署都有一个附带的服务(网络的行话),它带有一些 IP 地址和端口号,以便读者可以访问文章。如果没有入口这样的东西,读者将不得不给这些原始的网址,这肯定不是人类友好的。例如,您不知道正在阅读本文的本地 IP 和端口。就是不实用。相反,您可以简单地在 Kubernetes 集群中部署一个入口,并为每个出版物提供一个具有各种路由规则的人性化单点入口'***medium[dot]com***',以便读者可以从浏览器访问'***medium[dot]com/publication1***',入口控制器会自动将该流量定向到正确的服务以及包含该出版物所请求资源的随附 pod。现在,让我们在之前的文章 中创建的集群 中尝试一些入口部署。
我们将设置一些额外的配置,通过 TLS 证书启用 HTTPS 支持,测试这种 HTTPS 支持,最后在集群上从头开始部署一个简单的应用程序。
需要额外设置
- mkcert —一个用于制作本地可信开发证书的工具,该证书将为在我们的集群上运行的服务启用
*https*。安装完成后,让我们生成一个本地证书,该证书指向一些开发 URL 以及一个通配符 URL***.**myminikube.demo,它将通过以下两个命令用于入口设置:
**mkcert -install****mkcert "*.myminikube.demo" localhost 127.0.0.1**

mkcert 安装(图片来源:作者)

生成 TLS 证书(图片来源:作者)
保存好这个 TLS 证书和密钥(两个)。pem 文件)放在你想要的任何目录中,并从中创建一个 Kubernetes 秘密。通过kubectl为这篇文章创建了一个名为**mkcert-tls-secret**的秘密:
**kubectl create secret -n kube-system tls mkcert-tls-secret --cert=**<DIRECTORY_CONTAINING_PEM>/_wildcard.myminikube.demo+2.pem **--key=**<DIRECTORY_CONTAINING_KEY_PEM>/_wildcard.myminikube.demo+2-key.pem
注意:这两个文件的名字会因系统而异,但是这个概念仍然适用。同样在生产环境中,证书通常由证书管理器自动管理。例如,这个生成的证书将于 2024 年到期。
现在,让我们通过使用位于kube-system名称空间中的mkcert-tls-secret重新配置该证书,将它集成到 ingress 插件中:
$ **minikube addons configure ingress**
-- Enter custom cert(format is "namespace/secret"): **kube-system/****mkcert-tls-secret**
✅ ingress was successfully configured
禁用当前 ingress 加载项,然后再次启用它以使更改生效:
$ **minikube addons disable ingress**
🌑 "The 'ingress' addon is disabled
$ **minikube addons enable ingress**
🔎 Verifying ingress addon...
🌟 The 'ingress' addon is enabled
最后,让我们验证入口控制器现在有一个默认的 SSL 证书
**kubectl -n ingress-nginx get deployment ingress-nginx-controller -o yaml | grep "kube-system"**

检查部署的 SSL 证书(图片来源:作者)
现在,您应该看到这个秘密名称(上面设置的)是默认的 SSL 证书
- 使用 Minikube IP 和入口路由编辑 hosts 文件 : Hosts 文件现在需要使用为我们即将部署的入口预留的任何所需 URL 进行更新。因为我们使用了通配符选项,所以通过这种主机格式
**<SERVICE>**.myminikube.demo,我们有无限的入口部署选项。对于云提供商来说,这种入口设计选择也非常经济高效,因为您需要为每个负载*衡 URL 付费,但在我们的案例中,1 个 URL 可以用于不限数量的入口。我们将测试 Ingress waters,为我们在上一篇文章中启用的日志和仪表板附加服务设置一个,最后为实际应用设置一个。使用sudo权限编辑主机文件地址,使用您的**minikube ip**地址,后跟所需的入口主机名**hello.myminikube.demo dashboard.myminikube.demo logs.myminikube.demo**:
**sudo vi /etc/hosts
cat /etc/hosts**

更新的主机文件(图片来源:作者)
无聊的准备工作终于结束了。让我们也用我们新的 TLS 证书体验一下入口部署的乐趣。
使用 HTTPS 证书测试部署
入口只是充当集群外部的客户端和集群中运行的服务之间的路由链接。因此,人们可以在脑海中想象一个入口和位于 Kubernetes 服务之上的东西。我们的计划是在之前的文章中启用这两个插件后,测试日志和仪表板服务的两个入口部署。唯一的问题是,这些服务运行在两个不同的名称空间中(本质上是隔离的环境)。 让入口为不同名称空间中的服务工作的技巧是在每个名称空间中都有一个入口 。如果仪表板和日志服务在同一个名称空间中,那么一个入口文件就足够了。让我们通过以下方式从这些服务中获取一些重要的元数据:
**minikube service list**

集群中运行的服务列表(图片来源:作者)
日志服务在kube-system中,而仪表板服务在kubernetes-dashboard中。现在从这两个服务中获取所需的元数据:
**kubectl get services -n kubernetes-dashboard --output=wide
kubectl get services -n kube-system --output=wide**

用 kubectl 检查服务(图片来源:作者)
从这些命令中,注意服务的NAME和PORT(S)。仪表板服务名为kubernetes-dashboard,运行在集群内的端口80上,而日志服务名为logviewer,运行在端口3000上。现在,让我们最后写入口 yaml 文件。
从我的经历来看,大多数人看到一堆.yaml文件,变得如此不知所措。你不需要记住语法之类的东西。VS 代码 中的 Kubernetes 扩展为每个 Kubernetes 组件提供了一些精彩的片段。打开一个.yaml文件,开始输入类似ingress的组件,一个代码片段应该会弹出来。用一个tab接受它,你应该有一个好的入口模板。
日志服务的入口组成如下:
让我们快速浏览一下添加到初始模板中的关键组件。
- 注释:
kubernetes.io/ingress.class: “nginx”只是将nginx设置为入口控制器,而nginx.ingress.Kubernetes.io/ssl-redirect使用相同的 SSL 证书为该入口启用重定向。 - 名称和命名空间: 入口的名称是用 Kubernetes 中的典型约定设置的
name-<component>。所以logs-ingress用于演示。还指定了namespace,因此 Kubernetes 将知道部署入口的名称空间。 - spec: 启用 HTTPS 就像参照托管 TLS 证书的
secretName(mkcert-tls-secret)在spec部分下的tls子部分中指定所需的主机名(logs.myminikube.demo)一样简单。 - 主持人: 规则小节是路由魔术发生的地方。这是 HTTP 路径和集群中运行的相关后端服务的链接位置。在 HTTP 路径
logs.minikube.demo/上,我们链接前面提到的运行在端口3000上的logviewer服务。
差不多就是这样。您将在 Kubernetes 的世界中读到的
.yaml文件的大部分都是样板代码,所以不要被它们所影响!
下面仪表板的入口文件看起来与日志文件相同,唯一的区别是后端服务(kubernetes-dashboard)、规则主机(dashboard.myminikube.demo)和端口号(80)。
有了我们定义的入口配置和整齐打包的.yaml文件,kubetcl可以应用它们来实现定义的更改。只需通过以下方式申请:
**kubectl apply -f dashboard_ingress.yaml && \ kubectl apply -f logs_ingress.yaml**
根据您的系统和资源,部署的入口可能需要几秒或几分钟才能完全运行,因为入口控制器必须为该部署分配一个 IP 地址,URL 才能工作。只需使用kubectl检查几次,直到您看到分配的 IP 地址:
**kubectl get ingress -n kubernetes-dashboard
kubectl get ingress -n kube-system**
分配 IP 地址后,我们就可以开始访问部署中定义的主机了。

用 kubectl 检查入口(图片来源:作者)
然而,我更喜欢用 bash 脚本自动执行这个“等待分配 IP 的序列:
该脚本可重复用于检查任何名称空间中的任何入口,并在 IP 正常工作时返回该入口的 URL:
**sh check_ingress.sh "ingress-demo"
sh check_ingress.sh "kubernetes-dashboard"**

检查 IP 地址的 Bash 脚本(图片来源:作者)
仪表板入口应在[https://dashboard.myminikube.demo](https://dashboard.myminikube.demo)可用。在浏览器中访问该 URL,您应该会看到带有 HTTPS 证书的典型 Kubernetes 仪表板

HTTPS 检查仪表板入口(图片来源:作者)
使用相同的 HTTPS 证书,应在[https://logs.myminikube.demo](https://logs.myminikube.demo)上提供相同的日志入口。

HTTPS 检查日志入口(图片来源:作者)
我们现在可以确认 TLS 证书按预期工作,入侵也可以利用它们。现在,从零开始快速部署流行的 hello world 容器应用程序怎么样?
使用 HTTPS 部署 Hello World 应用程序
让我们首先为这个部署创建一个名称空间文件,以保持一切都是独立的。这很简单,因为:
需要记住的一点是,在名称空间可以被其他组件调用之前,它需要是可用的,所以顺序对于namespaces很重要。应用文件以实现更改:
**kubectl apply -f hello_namespace.yaml**
接下来,让我们创建一个部署文件:
您可以看到这个名为myapp的部署所使用的名称空间。在此部署中,我们指定希望运行应用程序的 10 个副本(拷贝)。这个数字是任意的。容器映像gcr.io/google-samples/hello-app:1.0在设置了一些资源限制的情况下使用。端口8000被这个容器使用。应用文件以创建展开:
**kubectl apply -f hello_deployment.yaml**
pods 运行该应用程序的 10 个副本,这些副本在集群内部运行,让我们在部署之上构建一个服务,以便客户端可以从集群外部与正在运行的应用程序进行交互。上面配置了一个NodePort服务,目标是内部8000端口,并在同一个端口8000上向用户公开该端口。将该服务与 kubectl 一起应用,它将被分配一个我们本可以使用的 IP 地址,但我们不希望这样,因为我们可以有一个漂亮而真实的 URL:
**kubectl apply -f hello_service.yaml**
最后,我们为这个服务构建了一个入口,这样来自集群外的客户端对**hello.myminikube.demo**的任何调用都将被重定向到上面定义的服务!还记得我们正在运行 10 个副本吗?入口控制器将自动处理我们正在运行的 10 个副本中的 1 个的请求流。现在想象一下,如果有 10,000 份拷贝分发给 1,000 名不同的工人!太疯狂了,不是吗?
应用这个入口并使用check_ingress bash 脚本来检查分配的 IP 的可用性:
**kubectl apply -f hello_ingress.yaml**

使用 bash 脚本检查 IP(图片来源:作者)
一旦分配,访问给定的 URL(https://hello.myminikube.demo),你应该会看到一个Hello, world!消息以及应用程序的Version和一个Hostname

使用 TLS 证书的 Hello world 应用程序(图片来源:作者)
刷新页面几次,并在每次页面刷新时注意Hostname的最后部分。该值应该最多更改 10 个唯一的值,代表我们在集群中运行应用程序的 10 个副本容器。
**curl** [**https://hello.myminikube.demo**](https://hello.myminikube.demo)

通过“curl”向应用程序发出多个 Get 请求(图片来源:作者)
恭喜你!现在,您有望对 Kubernetes Ingress 有足够的了解,开始在您的项目中使用它们。不要忘记通过删除它们来清理部署,以便为其他更重要的部署节省资源。
删除所有的资源非常容易,因为我们将所有的东西都隔离在一个名称空间中。只需运行:
**kubectl delete namespaces ingress-demo **
namespace "ingress-demo" deleted
已经创建了一个 GitHub 库 ,其中包含了本文使用的所有脚本,以供将来参考和扩展。玩得开心点!在下一篇文章之前,继续探索 Kubernetes 的世界。
实验室笔记本是数据科学从业者的首选武器
我的一套有效记笔记的规则和原则,以实验笔记本的形式

作者图片
作为一名拥有超过 7 年实验室经验的数据科学从业者和物理学家,我发现一些传统的学术实践非常适合现代数据科学工作流。
O 这些实践之一是有一个适当的组织和结构实验室笔记本。
在这篇文章中,我将分享我个人对坚持使用它的看法。
我为什么要关心实验笔记本?
只要科学本身存在,实验室笔记本就已经存在很久了。一个实验室笔记本可以被定义为一个研究人员的原始记录。它用于记录假设、实验以及对这些实验的初步分析或解释(见下图)。
简单来说,实验室笔记本就是记录你的研究工作、想法、观察和活动的笔记本。然而,有些指导方针可能会因组织而异,你可以查看麻省理工学院建议的。

作者图片
你可能会问,为什么现在还有人会为实验笔记本烦恼呢?
现在,在 2022 年,我们有大量用于常规笔记、任务/项目管理和协作的花哨的通用解决方案:吉拉、Confluence、Slack 和虚拟白板(Miro)等等。
作为数据科学家,我们有一整套自己的数字工具来执行和跟踪实验(Comet、MLflow、Neptune、SageMaker Studio 等。).
这些广泛使用的解决方案中的大多数都非常适合解决日常数据科学工作流中的特定问题,并且它们中的每一个都提供了一组独特的功能。结合起来,这些工具似乎涵盖了我所有的商业数据科学需求。但只是第一眼。
有了所有这些工具,我最依赖的工具(实际上是过去 12 年的大部分时间)是我的实验室笔记本。
这需要一点解释。
我在做激光物理研究时如何使用我的实验室笔记本
我从事应用激光物理已经 7 年了,我的实验室笔记本总的来说一直是整个多维度研究和工作流程的中坚力量。
我习惯在早上的第一件事就是喝杯咖啡,通读我过去几周的记录。对我来说,一个典型的研究日需要 2 到 4 页白纸,上面写满图像和文本(如下图所示)。

我的一本实验笔记本的第 98–99 页
在处理一个特定问题时,我通常有多种类型的活动:设计实验、进行测量、进行探索性数据分析、运行数值模拟、撰写科学文章等。
每项活动都是以一种非常特殊的方式计划和跟踪的,这种方式对我来说是最好的长期服务方式。
我会把我的笔记分成五类:
- 一天的简要计划(待办事项、重要会议/演示/实验)
- 实验计划和跟踪(大部分是高层次的,但在需要时是详细的)
- 实验数据分析和解释
- 关键问题、见解和想法
- 回顾展
五种类型的笔记实际上是很多的,但对我来说这是最起码的必要条件,因为作为一名应用物理从业者,你不可避免地要戴上许多帽子,而且你的大多数活动都不是琐碎的和容易解释的。

我的实验室笔记本
很多这样的活动会立即让你处于某种压力和紧张之中:使用不熟悉的工具操作,努力解释现象,对下一步行动步骤的不确定性,等等。
对我来说,处理这种压力的一个方法是把信息和结果仔细地放在笔记本上,把问题框起来,写下来,把问题分解成可行的步骤,然后开始一步一步地检查。
通过在实验室笔记本上做笔记,我一直试图保持简洁,但确保我的写作/绘图质量到位。我的意思是质量不是指书写工整和选择漂亮的图片,而是确保我花时间和精力仔细思考每一行。
不要误解我,我花了半天时间编写软件(用 Python 和 Wolfram Mathematica),处理数十亿字节的数据,可视化数据,并撰写文章——所有这些都使用了几十种数字工具。
但我的实验室笔记本一直高于一切——监督每一项活动,指导我度过研究生活,并允许我克服不确定性。
关于模拟与数字的旁注:我用过 模拟 笔记本,因为我习惯于画图表,自由书写,然后在上面画,等等。在那个时候,没有办法在不丧失通用性的情况下用数字技术来做这件事。如今有了更多的无纸选择,但这不是本文所要讨论的。
在我看来,实验室笔记本是一个 框架 用于高度结构化的技术笔记结合日志实践。数字与模拟——其实并不重要。
随着时间的推移,我意识到我的实验室笔记本一方面成了技术知识库,另一方面成了个人研究日记。
我从坚持使用实验笔记本中获得的好处是如此之大,以至于我甚至在离开激光物理学后仍坚持这个习惯。
这一切如何转化为我的日常数据科学实践
自从我离开学院以来,实验室笔记本在我工作生活中的角色已经发生了变化,但它仍然是我工具箱中非常重要的一部分。

我的笔记本进化了
作为一名数据科学从业者和团队经理,如今我做了很多与数据有关的事情来实现业务目标——我编程、执行探索性数据分析、管理项目、管理团队、会见产品所有者和利益相关者以及开发数据产品。
这是令人兴奋的,但我已经设法适应了紧张的工作量,并利用我在学校的学术实践保持了高效率。
我仍然以一种特别结构化的实验室笔记本方式保存我的每日记录。当然,我不再插入激光束形状,也不再记录激光物理实验,但我确实会使用一组更新的类别写下想法、想法和观察结果(见下文)。

我用来跟踪我的数据科学工作的类别
再一次,有了我可以使用的所有数字工具,实验室笔记本变成了一个知识库,一个日记,一个素描本,它位于所有其他工具之上,指导我完成我的工作。
以这种方式保存记录的原因,以及我所遵循的具体指导原则,我试图制定为一套规则/原则:
原因 1:记录你的想法——蹩脚的和优秀的
对我个人来说,数据科学家的角色是一个创造性的角色。尤其是在产品开发领域——在我看来,这是关于新鲜的想法和创造性的解决问题。
我总是用实验室笔记本来做我的头脑风暴。不是那种你只是产生大量荒谬的东西的头脑风暴,而是对实际可行的东西进行深入和持续的创造性思考。
因此,第一个原因是:保持和培养你的想法。把东西写下来。写下问题、想法和想法。我们人类的大脑不善于将思想长时间保留在身边。
想法是非常脆弱的,想法是不可预测的,你什么时候会有想法,会有多少想法。乔纳森·伊夫
仔细记录想法可以让你以一种真正深刻的方式重复、重新评估和思考它们。
实验室笔记本让我能够从最早期阶段(最脆弱的阶段)到蓬勃发展的实施阶段小心翼翼地发展想法,而不会偏离正题。

作者图片
如何做:我在用灯泡图标记录听起来可能很有趣的想法。有时,我需要花几天时间来重申一个想法,重新制定,添加细节,在这种情况下,我使用原始记录引用(页码)将最新的迭代链接到前一个迭代。
原因 2:跟踪你周围发生的事情
正如 Steven S. Skiena 在他的数据科学设计手册中所写的:
然而,数据科学的核心在于做好简单的事情:理解应用领域,清理和集成相关数据源,并向他人清楚地展示您的结果
以我的经验,我怎么强调良好的沟通都不为过,尤其是在与领域专家打交道的时候。
在我看来,不仅认真倾听很重要,而且对业务方面发生的事情保持全面的警惕和关注也很重要。
尤其是当你管理项目、与利益相关者谈判、产生产品想法的时候——你有责任观察你周围的环境,并将每一点信息提炼成关键的观察结果。
如何做:我做这件事的方法很简单:每当我参与谈话时,我都会进入一种专注的模式,认真倾听,如果需要,要求澄清,然后在实验笔记本上把关键的观察/见解改写成几个句子。
在会议准备过程中,我会创建我计划涵盖的要点,然后插入对每个要点的反馈:

作者图片
缺点是——你必须积极倾听并集中注意力,这很难,但好处要大得多——随着时间的推移,它会给人留下一种印象,即你不仅关心他人的看法,而且没有什么会逃过你的注意(这是一件好事)。
当然,没有笔记本你也能做到,但是手里拿着笔开会,专心听,把输入的信息消化成关键的观察结果,为这些活动设置了一个坚实的协议。
如果我不能积极地倾听,我宁愿重新安排一次会议,而不是仅仅为了出席而参加。
原因 3:一个完美的习惯
对我来说,在实验室笔记本上记录我的工作成了一个有效的基本习惯。
基本习惯是一个人操作的常规和实践。它们标志着你所做的事情的基本水*,不需要任何意志力或说服力。默认。不管是积极的还是消极的,这些习惯都会对你在生活和工作中所做的一切产生连锁反应。(福布斯文章)
不管你正在处理的问题有多复杂,不管所有其他的情况和混乱,通过重读你的进展和想法来开始你的工作的习惯为一天定下了基调。对我来说,运行实验室笔记本创造了一种动力,我可以用它来建立和推动事情向前发展。
如何做:喝杯咖啡,通读过去 3-4 天的记录,开始一天的工作。给当天打上时间戳,制定当天的高级计划,开始考虑执行细节并从那里开始,至少对我来说是这样。
原因 4:跟踪你的个人进步和成长
不要拿自己和别人比较——拿自己和过去的自己比较,是的,我们已经听过很多次了,但是说起来容易做起来难。
有了一个合适的实验室笔记本,这也是一个个人的工作日志,你自己的进步将是显而易见的,你不应该低估这一招的威力(这甚至是一招吗?).
只要通读几页,你就会看到技能、问题理解和问题解决能力的增长。
作为一名研究人员,当你经历困难时期时,这一点尤为重要:实验笔记本将永远证明你已经远离了起点。
对我来说有用的是每三个月写一份目标清单。3 个月后,我拿起实验笔记本的一两页做一个回顾:我参考目标设定页,检查希望进展顺利和不顺利的地方。
原因 5:有战略眼光
实验笔记本促使你变得有战略眼光——它只是迫使你富有成效地思考并采取行动,因为否则你会失去动力。
战略本身很难定义,但我喜欢罗杰·马丁的定义:
战略是一套综合的选择,它以一种你能赢的方式把你定位在你选择的竞技场上。通常,如果你只是工作而没有留下时间标记的记录和想法,随着时间的推移,你的大脑可以很容易地替代你的记忆,扭曲事件的真实顺序,并把一切都放入一个更方便和可解释的,但却是错误的观点中。
然而,对于实验笔记本,你不允许这种情况发生。
首先,因为笔记本是永久装订的,所以你不能只是把书页撕掉,然后插入新的(你的大脑可能很容易用记忆做到这一点)。
其次,如果你在相当长的时间内对研究和工作做出了糟糕的选择,并记录下来,对结果的不利影响会变得如此明显,以至于令人震惊。
所以,很自然的,笔记本只是强迫你想长远,有战略眼光。
如何做:遵循协议——每 3 个月设定一次目标,并进行回顾——所有这些都记在同一个笔记本上。你在这 90 天中的日常事务将决定你是成功还是失败。
原因 6:非理性和戏剧性
嗯,这确实是有争议的和主观的,但我发现有一个合适的笔记本有点戏剧性。
不仅现在和过去的伟大科学家有实验笔记本,探险家也有。嗯,在这一点上你可能会说,船长有航海日志,北极研究人员有日记(我建议通读罗伯特·福尔肯·斯科特的日记),但是我把这些不同种类的笔记本归入一个家庭。
当我在实验室的笔记本上写字时,我会想起过去和现在那些冒着生命危险到达未知地方的勇敢的人们。
如今,我们大多数人都在极其安全的环境中工作,是的,但是对你的研究进行详细的、日志式的记录有一种无形的戏剧性。
我对自己说的是,是的,我不是第一个登上北极的人类,也不是宇航员,但我也会去未知的地方,越过栅栏眺望。
我很喜欢当自己探险队队长的感觉。
如何:我在这里给不出什么具体的建议,而不是找一些能以好的方式激励你的事情,定下正确的基调。
结论
总结一下:对我来说,实验室笔记本是一个技术日志,同时也是一个日记,一个发生在我所能支配的一切之上的工具。Lab notebook 为跟踪想法、召开会议、解决问题和日常工作制定了规则和协议。
在我看来,在商业数据科学领域做任何长期项目时,拥有一个合适的实验室笔记本都是必不可少的:它有助于成为一个更好的倾听者,进行更深入的思考,进行战略性规划,并产生真正的影响。
作为奖励,你得到一本美丽的日记,讲述了一个独特的解决问题的故事,一个研磨和成长的故事。
就是这样,感谢阅读,好奇想听听大家的想法。
标记和可视化用于对象检测的图像
原文:https://towardsdatascience.com/labeling-and-visualizing-images-for-object-detection-55b48f0da326
经典的计算机视觉深度学习示例项目从包含图像和标签的数据集开始。然而,当需要计算机视觉来解决业务问题时,数据通常是未标记的,标记数据本身就是一个挑战。本文介绍了大规模标注影像以及相关的挑战

图片来自带注释边框的谷歌地图|塞犍陀·维维克
深度学习计算机视觉项目的经典例子始于包含图像和标签的数据集。这取决于你所拥有的标签类型和你想要完成的任务——图像分类、物体检测或图像分割;你会从一组合适的深度学习模型中进行选择。有许多资源可以使用,包括 Kaggle 数据集和笔记本、GitHub repos,以及内置的示例数据集,如深度学习包 TensorFlow 和 PyTorch 中提供的 MNIST 数据集。这类示例项目的重点主要是选择模型架构和调整超参数。有时,对于独特的数据集,采用迁移学习可能是有意义的,其中你应用预先训练的模型(参见我的博客,例如 PyTorch on Animals-10 Dataset 中的深度迁移学习教程)。
然而,在你遇到的大多数计算机视觉问题中,无论是在试图检测大型机械上的缺陷的公司,还是有特定需求的客户,标记数据都是一个挑战。举个例子,假设一家保险公司想要从他们的无人机拍摄的卫星图像中检测房屋。

图片来自谷歌地图|塞犍陀·维维克
不幸的是,无人机拍摄的图像或谷歌地图上的卫星图像没有预先标记。你跟踪的这么多现有的深度学习资源,现在都缺少一个关键的步骤,而这个步骤是你启动项目所必须的。
首先,你如何给图像加标签?
原来有多个图像标注服务商。然而,缺乏关于如何给图像加标签的教程。这很令人惊讶。我发现了一个很好的免费在线解决方案——make sense . ai。如下图所示,很容易上手。

图片来自 Makesense.ai
上传图像后,请确保根据您是要检测图像中的多个对象还是要分类(识别)图像来选择正确的标记任务。

图片来自 Makesense.ai
接下来,创建一个标签列表。这里我只是检测房屋,所以我将只创建一类标签。

图片来自 Makesense.ai
接下来,我通过在各个图像周围画一个边界框来注释图像。

图片来自 Makesense.ai
最后,我以特定的格式下载注释。在这种情况下,我选择 YOLO,这是一个受欢迎的家庭的对象检测模型。

图片来自 Makesense.ai
当我打开 zip 文件夹时,注释在一个. txt 文件中,其内容如下。每一排都是不同的房子。这 5 列表示对象类别、中心 X 和 Y 坐标以及注释的宽度和高度。
#YOLO annotations0 0.100204 0.266547 0.122651 0.425760
0 0.245263 0.257603 0.122651 0.436494
0 0.373811 0.248658 0.110858 0.450805
0 0.502359 0.251342 0.117934 0.420394
0 0.633265 0.277281 0.113217 0.411449
0 0.761224 0.281753 0.119113 0.441860
0 0.229931 0.764758 0.103782 0.449016
0 0.367324 0.751342 0.104961 0.404293
0 0.499410 0.739714 0.123831 0.459750
0 0.766531 0.722719 0.129727 0.486583
0 0.633855 0.753131 0.119113 0.432916
0 0.909820 0.735242 0.126189 0.490161
我们如何知道我们的注释是正确的?我们可以在 Python 中加载我们的注释和图像,并创建一个自定义函数来可视化我们的注释,如下所示:
from PIL import Image, ImageDraw
import numpy as np
import matplotlib.pyplot as plt
import os#code adapted from [https://blog.paperspace.com/train-yolov5-custom-data/](https://blog.paperspace.com/train-yolov5-custom-data/)def plot_bounding_box(image, annotation_list):
annotations = np.array(annotation_list)
w, h = image.size
plotted_image = ImageDraw.Draw(image)transformed_annotations = np.copy(annotations)
try:
transformed_annotations[:,[1,3]] = annotations[:,[1,3]] * w
transformed_annotations[:,[2,4]] = annotations[:,[2,4]] * h
transformed_annotations[:,1] = transformed_annotations[:,1] - (transformed_annotations[:,3] / 2)
transformed_annotations[:,2] = transformed_annotations[:,2] - (transformed_annotations[:,4] / 2)
transformed_annotations[:,3] = transformed_annotations[:,1] + transformed_annotations[:,3]
transformed_annotations[:,4] = transformed_annotations[:,2] + transformed_annotations[:,4]
except:
transformed_annotations[[1,3]] = annotations[[1,3]] * w
transformed_annotations[[2,4]] = annotations[[2,4]] * h
transformed_annotations[1] = transformed_annotations[1] - (transformed_annotations[3] / 2)
transformed_annotations[2] = transformed_annotations[2] - (transformed_annotations[4] / 2)
transformed_annotations[3] = transformed_annotations[1] + transformed_annotations[3]
transformed_annotations[4] = transformed_annotations[2] + transformed_annotations[4]
print(transformed_annotations)
for ann in transformed_annotations:
try:
obj_cls, x0, y0, x1, y1 = ann
plotted_image.rectangle(((x0,y0), (x1,y1)), width = 10, outline="#0000ff")
except:
obj_cls= transformed_annotations[0]
x0=transformed_annotations[1]
y0=transformed_annotations[2]
x1=transformed_annotations[3]
y1=transformed_annotations[4]
plotted_image.rectangle(((x0,y0), (x1,y1)), width = 10, outline="#0000ff")
plt.imshow(np.array(image))
plt.show()#get an annotation file
annotation_file = './houses.txt'#Get the corresponding image file
image_file = annotation_file.replace("txt", "png")
assert os.path.exists(image_file)#Load the image
image = Image.open(image_file)#Plot the Bounding Box
plot_bounding_box(image, np.loadtxt(annotation_file))
注释看起来一点不错!

图片来自带注释边框的谷歌地图|塞犍陀·维维克
结论和挑战
虽然 makesense.ai 是一个很棒的免费*台,但它不具备可扩展性。为了训练一个 YOLO 风格的对象检测模型,你至少需要数百个(如果不是数千个)对象来获得合理的精度。Makesense.ai 不保存任何注释或合并工作流。你需要一次单独完成所有这些。如果一个人连续坐几个小时来标记成千上万的图像,将会花费大量的时间。还有其他服务,比如提供定制报价和标签服务的 ango.ai,每张图片只需几分钱。在我看来,这是一个有待改进和挖掘潜力的领域。随着人工智能变得越来越受欢迎和易于使用,对大规模轻松、准确和廉价的定制标签的需求将变得更加重要。
我希望这向您展示了端到端计算机视觉项目的冰山一角;模特训练只是拼图的一部分。请关注更多讨论以业务为中心的端到端深度学习的其他关键方面的博客!
您可以从 GitHub 上的这篇文章中找到代码:
https://github.com/skandavivek/Visualizing-YOLO-annotations
如果你还不是中会员,想支持我这样的作家,可以通过我的推荐链接随意报名:https://skanda-vivek.medium.com/membership
关注我 如果你喜欢这篇文章——我经常在数据科学、物理和社会的界面上写作。
每周数据透视 订阅此处 !
在 ML 开发过程中缺乏远见
原文:https://towardsdatascience.com/lack-of-foresight-in-the-ml-development-process-ebc9f33f9878
通过前瞻性的组织规划来解决潜在的问题

Y 您已经收集并准备了数据,设计了出色的特性,训练、评估并部署了您的模型。您和您的组织都很高兴:它在实践中显示出了巨大的成果,并成功地推进了您组织的业务目标。你终于完成了!
嗯……不完全是。
什么会出错?
几个星期,也许几个月,一切都很好,但是最终,有人意识到你的模型没有你想象的那么好。这种现象叫做模型漂移。模型漂移很大程度上是由数据漂移和概念漂移造成的。
- 数据漂移 是当模型的预测因子(自变量)发生变化时。例如,在垃圾邮件预测模型中,假设我们使用出站电子邮件的比率作为一个特征。如果在我们训练了我们的模型之后,电子邮件服务现在对出站电子邮件的比率实施了一个上限,那么这个独立变量的分布就发生了根本的变化。
- 概念漂移 是当模型的预测目标(因变量)发生变化时。使用前面的例子,这可能是由于用户对“垃圾邮件”的理解发生了变化。随着时间的推移,一个鲜为人知的出版物可能会变得更受欢迎和可靠,因此将来自该领域的电子邮件归类为垃圾邮件是不合适的,因为它们以前可能就是垃圾邮件。
随着时间的推移,这两种类型的漂移都会导致模型性能下降。如果不进行监控和纠正,部署的模型很快就会变得不准确和不可靠。
典型解决方案
这是很好的信息,但这是一个已经解决的问题。这很容易解决,我们的监控工具将检测模型漂移,然后我们只需收集更多数据,重新训练和重新部署…对吗?
问题
在与初始开发阶段相同的条件下,这是一个有效的假设。但是随着时间的推移,尤其是几个月之后,可能会出现以下问题:
- 谁负责数据收集、特征工程、模型创建、评估和部署?他们还在组织里吗?
- 数据存储在哪里?我们如何知道模型是在哪个版本的数据上被训练的?
- 模特住在哪里? "model_1_best_weights" 还是 "updated_model_1_v2" 是生产中部署的模型?
- 数据处理和模型开发的代码在哪里?代码还存在吗?为什么看代码会让我想哭?
这些问题可能看起来很激烈。事实上,他们确实应该如此。但是这篇文章的灵感来自于答案:他们几个月前就离开了,数据丢失了,模型消失了,代码不可读了。祝你向你的委托人展示这个的时候好运。
为什么会出现这些问题?
我很幸运地在许多组织中工作过,看到了 ML 开发过程的各个阶段。我见过一些非常有问题的情况,和一些体面的情况,但从未见过这个过程做得非常好。这是为什么呢?
责怪工程师、数据科学家和开发团队很容易。但实际上,在大多数情况下,这些问题在组织和文化中根深蒂固得多。
难以纠正的模型退化问题源于组织缺乏远见。基本的长期问题在目光短浅的组织中激增。
反对主动行动的理由是什么?
我已经注意到了以下倾向于产生这些问题的开发实践的论点,特别是在较小的、较新的初创企业中。
这个问题不是问题。开发功能性的、可部署的模型的过程远没有模型本身重要。
对于一次性系统或分析,我同意这一点。临时系统不需要完美,只需要临时工作就能得出结论。然而,许多人错误地认为 ML 开发过程是临时的,导致了这种观点。相反,这个过程应该与传统软件工程的基本实践相当。
我们需要快速迭代来推出产品。
虽然这可能是真的,但是低于标准的开发实践实际上会增加发货时间。“如果代码质量不好,错误和有问题的边缘案例很容易被忽视。这导致后来耗时的错误修复,最糟糕的是,生产失败。高质量的代码让你 早失败,快失败 。”[1] 与直觉相反,在过程中放慢速度会让组织加快取得成果。
这只是一个概念验证,没有必要考虑可维护性。
许多“快节奏”组织的方法是从关注高速、低质量概念验证开始。这产生了快速但目光短浅的结果,不能很好地转化为 MVP(最低可行产品)。虽然这一过程在不确定其数据需求的组织中非常有效,但对于以数据驱动为目标的组织来说,我们已经知道这些项目是核心业务的一个必要方面。
这些论点的结合经常会导致我们所描述的问题。
我们能做些什么呢?
希望到目前为止,您已经意识到这是一个可能在组织中悄悄发生的重大问题。我将提出一套指导方针,指导组织采取先发制人的措施,在问题发生之前就加以预防。
1.监视
最起码的步骤是简单地监控模型的性能。虽然这并不能让我们修复问题,但它允许对问题进行初步检测。如果我们不知道一个问题存在,我们怎么知道去纠正它呢?
监控的目标是“确保模型在应用于信心测试集时生成合理的性能度量。”[2] 此外,置信集应定期更新,以说明上述分布变化。
2.迭代过程的重要性
一个组织应该强调 ML 开发过程的迭代性质的重要性,给团队足够的时间来考虑这个问题。不应低估维护周期。
“大多数生产型号必须定期更新。该比率取决于几个因素:
它出错的频率以及错误的严重程度,
模型应该有多“新鲜”才能有用,
新培训数据变得可用的速度,
重新培训一个模型需要多长时间,
部署模型的成本有多高,以及
型号更新对产品和用户目标的实现有多大贡献。" [2]
3.数据版本控制
许多数据版本控制工具将自己标榜为“数据 git”。任何数据版本化工具的主要目的是同步不同版本的代码和数据(训练数据、测试数据、模型等)。).当一个模型需要更新时,我们可以在最后一次更新时获得开发状态的完美副本。模型更新后,如果我们的监控工具显示性能下降,我们可以快速轻松地恢复到以前的部署。我是 DVC 的支持者,但是还有很多其他的解决方案。
4.实验跟踪
实验跟踪工具允许所有实验相关数据(超参数、模型配置、结果等)的跟踪和可视化。)跨越多次运行。像权重&偏差、 MLflow 和 Neptune 等工具都是很好的选择。这将允许不同模型版本之间的分离。
5.证明文件
开发人员最不喜欢的消遣。这很好地反映在 Jupyter 笔记本上杂乱无章的零星评论和许多项目中未完成的自述中。不幸的是,为了未来工程师的健康,模型架构的选择、复制步骤、结论和所有其他相关信息都应该被很好地记录下来。
结论
我们已经看到了有缺陷的开发过程是如何导致难以纠正的模型退化的。这个问题的根源不是缺少监控工具,而是导致这些长期问题的短视的组织行为。我提出了一套组织准则来解决上述一系列问题。
我希望你现在可以避免让我写这篇文章的痛苦。
这只是开始!如果你喜欢这篇文章,请关注我以获得下一篇文章的通知!我感谢❤️的支持
https://medium.com/@brandenlisk
来源
[1] E. Berge,《作为数据科学家如何写出高质量的 Python》(2022),走向数据科学
[2] A .布尔科夫,机器学习工程 (2020),加拿大魁北克:真正公司。
靠自我发挥的骗子的骰子
原文:https://towardsdatascience.com/lairs-dice-by-self-play-3bbed6addde0
实践教程
靠自我发挥的骗子的骰子
用反事实后悔和神经网络

与人工智能对战是如此有趣,以至于我不得不创建一个小网站来与世界分享它。—dudo . ai截图。作者图片
一段时间以来,我一直想学习像扑克或骗子骰子这样的游戏的人工智能。最*在读 Deepmind 的文章游戏玩家时,我想我也许能做出一些真正简单的东西,足够好,足够有趣。“应该不会超过一个下午,”我想。当然,我最终在这上面花了更多的时间,但这确实很有趣,也很简单。
这个项目让我能够深入到反事实后悔最小化、强化学习、在浏览器中提供 PyTorch 模型和一些其他有趣的主题的令人兴奋的概念中,所以在这篇博客文章中有一堆事情要涵盖。
骗子的骰子怎么玩?
我明白。这是一个欺骗的游戏。但是你的赌注包括所有的骰子,而不仅仅是你自己的。——威尔·特纳,《加勒比海盗》
骗子的骰子是我个人喜欢在我们还能去酒吧的时候玩的游戏。它比扑克简单,但有许多相同的下注、虚张声势和隐藏信息的概念,这使它成为人工智能的一个有趣挑战。

供六个人玩的骗子的骰子,有用来隐藏的骰盅。在这篇博文中,我们只考虑 2 人版本。图片由 Dicemanic (CC BY 4.0)
游戏也是从加勒比海盗开始出名的,不过不了解游戏的话我会快速勾画一下规则。
- 每个玩家都有一套只有他们自己知道的骰子。玩家轮流对玩家认为显示给定值的最小骰子数出价。
- 每回合,玩家必须对任何特定的牌面叫牌数量更高,或者对更高的牌面叫牌数量相同。例如,“2 ⚃”和“3 ⚂”都是“3 ⚁”之后的有效赌注,但“2 ⚂”不是。
- 或者,玩家可以挑战先前的出价。如果挑战成功——例如,先前的出价是虚张声势——挑战者赢得这一轮;否则,下注的玩家获胜。
通常,⚀'s 被认为是爱开玩笑的人,在最后合计骰子点数时,他会考虑任何一张脸。
爱丽丝和鲍勃可能有⚀ ⚁和⚂ ⚃.的手爱丽丝出价“2 ⚁”(她可以独自用自己的手支撑),鲍勃说“2 ⚂”(预计爱丽丝有一张⚂或⚀(百搭))。艾丽丝打电话给鲍勃,但是鲍勃是对的并且赢了。
理论是什么
解释这个算法需要一点理论——尽管它最终非常简单。如果您想直接跳到神经网络部分或模型的 ELO 评级和可开发性评估,请随意跳过以下三个部分。
为什么不完全信息博弈如此困难?毕竟,我们有深蓝,我们有 AlphaGo。为什么我们不能把这些技术直接应用到像扑克这样的游戏中呢?— 诺姆·布朗·尼普斯 2017 年最佳论文演讲。
骗子的骰子是一个有限的游戏:你只能掷出有限的骰子组合,以及有限的下注顺序。(我们称这种组合为状态,从技术上来说,它通常被称为信息集)然而,与像国际象棋这样的完全信息博弈不同,我们无法为每种状态计算出单一的完美走法(或动作)。相反,如约翰·冯·诺依曼和约翰·纳西所示,我们需要一个“混合策略”:对于每个状态,我们从一组动作的概率分布中选择一个动作。
直觉上,这是因为有一个固定的策略会泄露我们私人骰子的信息。如果我们在掷骰子时总是下大注,我们的对手就会明白这一点,开始跟我们虚张声势。如果我们一半时间虚张声势,另一半时间稳扎稳打,我们的对手就更难识破我们。

据报道,约翰·冯·诺依曼在 T4 曼哈顿计划 T5 和其他科学家玩扑克时发明了现代博弈论。照片由 Keenan Constance 在 Unsplash 上拍摄
纳什均衡可以使用线性规划来计算,例如达芙妮·柯勒和尼姆罗德·迈吉多的方法,该方法在状态数(或更常见的术语“信息集”)上花费多项式时间。)几年前我其实就是用这个方法解开骗子的骰子的。这是可能的,因为每个人有 1 个骰子,有意义的最大出价是 2 ⚅'s.,总共有 12 个可能的出价。结合骰子信息,那就是 6*2 ~40 万个状态。这只是现代线性代数解算器可能做到的事情,比如谷歌或者工具。
不幸的是,这种方法不能扩展到多个骰子。每个 6 个骰子,我们得到大约 10 个⁶状态,这相当于国际象棋中的总合法位置。虽然国际象棋理论上可以通过简单的深度优先搜索来解决——在状态数中采用恒定的空间和线性时间——但骗子的骰子要难得多。这就是为什么人工智能在 1998 年可以很好地下棋,但直到最*才在扑克上达到同样的水*。
反事实后悔最小化
好了,是时候我开始解释这个人工智能实际上是如何工作的,以及机器学习是如何发挥作用的。
在过去,很多人会通过最大化你的收益来模拟决策。你想尽最大努力赢得比赛,对吗?但是人们发现你真正想做的是尽量减少你的遗憾。— 布莱恩·佩莱格里诺 、扑克职业和人工智能研究员
解决不完美信息博弈的算法被称为反事实后悔最小化。我在这篇文章的结尾添加了一些相关链接,但在我的休闲版中,它是这样工作的:

我们应该生活和玩耍来最大化我们的快乐还是最小化我们的遗憾?马科斯·保罗·普拉多在 Unsplash 上拍摄的照片
- 如果开始的玩家赢了,给所有的最终状态(其中一个玩家称另一个玩家说谎)赋值+1,如果第二个玩家赢了,赋值-1。
- 对于所有其他状态,我们将保持一个值 v(state) (在-1 和 1 之间),该值*似于在通过给定状态的先前游戏中观察到的*均值。
(在真实的 CFR 中,这些值应该由玩家进入状态的概率进行反向加权,但为了简单起见,由于使用 Adam 和 momentum 来训练神经网络已经有点乱了加权,所以我跳过了这一部分。) - 定义“反事实遗憾” r(state,I)= v(state+I)—v(state),这是如果我们在给定的状态下每次都采取行动 i ,我们的得分(*均)会差(或好)多少。
(此处状态+i 是采取动作 i 后的结果状态。) - 最后,通过以下方式定义策略(在给定状态下选择动作 i 的概率)

其中 r (状态)**= max(r(状态),0)。这确保了不存在负概率。
总是玩具有最高期望值 v(state+i)的动作可能很有诱惑力。然而,这将是一种确定性策略,很可能被我们的对手所利用。相反,随着我们对状态值的估计越来越精确,上述算法(称为后悔匹配)肯定会给出纳什均衡。
蒙特卡洛和神经网络的懒惰
在真实反事实后悔最小化(简称 CFR)中,我们通过在整个博弈树上运行深度优先搜索并存储迄今为止看到的*均分数,来反复改进我们的 v(状态)估计。

蒙特卡洛*似法可用于测量物体的面积,方法是对随机点进行采样,并计算感兴趣的物体内部有多少陆地。类似地,通过重复游戏,它可以接*游戏的期望值。图片由凯特琳·乔·拉姆齐
这有两个主要问题:
- 它用了太多的时间。
- 它占用了太多的空间。
为了解决时间问题,我们用游戏样本代替深度优先搜索(也称为蒙特卡罗 CFR)。基本上,我们不用更新每个子状态——包括那些概率为 0 的动作——我们只需玩一个游戏,然后根据游戏结果用+1 或—1 更新游戏树中的路径。换句话说,我们在做强化学习。
为了确保我们探索所有的选项,我们在玩的时候增加了一些噪音。噪音还教会了人工智能利用对手的糟糕举动,因为它不是在对手总是做出完美选择的情况下训练的。理想情况下,我们应该减少训练期间随着时间增加的噪音量——如果我们想最终获得纳什均衡的话——但我没有这样做。
为了解决内存问题,我们不存储每个可能状态的精确*均分数。相反,我们使用神经网络来计算*均值 v(状态)的良好*似值。为了模拟 CFR 更新规则,我们使用 1/T 的学习率,其中 T 是当前时间步长。这确保了 v(state) 学习训练游戏的*均值,而不仅仅是最*的结果。(我实际上尝试了其他学习率,比如 1/sqrt(T)和 1/T,令人惊讶的是 1/T 是唯一一个收敛到纳什均衡附*的学习率。)
完整的训练代码可以在github.com/thomasahle/liars-dice/blob/main/code/train.py中找到,而最关键的部分——自我游戏代码——只有 27 行。
代码将三元组(私有状态、公共状态、游戏结果)保存到一个名为 replay_buffer 的列表中,该列表被传递给 PyTorch 用于训练神经网络(在 game.sample_action 中调用)。在一个梯度下降的步骤之后,我们用改进的策略玩另一批游戏并重复。
模型
神经网络是一个函数 v(private,public) ,它返回一个介于-1 和 1 之间的值。我们可以用很多方法来定义它,所以我只选择了我能想到的最简单的方法,它看起来足够好了。

我能想到的最简单的模型是:私有和公共状态被编码、组合,并通过几个完全连接的层传递。作者图片
私人输入是玩家手牌的一个 6 × “骰子数”的一键表示。我们可以把它看作是每个面上有多少骰子的直方图。该编码取自dou zero【2021】。
由于在两个玩家的游戏中,我们最多可以调用 2 个 × “骰子数”,
公共状态是 2 个 × 6 个 × (2 个 × num-dice)的一键表示,表示每个玩家到目前为止所做的调用。
每个独热输入被嵌入为 500 维向量,并且两者相乘。其他的池化方式,比如连接,也可以工作得很好,但是元素式乘法模拟了压缩双线性池,我个人倾向于。
在结合了私有和公共状态之后,我们运行 4 个完全连接的层,100 个单元宽,在每层中具有校正的线性单元。最终,我们减少到一个标量值,使用 tanh 映射到[1,+1](像 AlphaZero 一样。)
训练损失简单来说就是 MSE: ( v(私,公)——game _ result)。再次复制 AlphaZero。
有用吗?
有时候我会打败它。有时候我也不知道。说谎者的骰子是一个涉及很多机会的游戏,需要很多游戏才能确定谁是更好的玩家。所以我们来玩很多游戏吧!
在第一周在 dudo.ai 与人在线对战时,人工智能赢了 5031 回合,输了 4416 回合。这相当于骰子点数的 53.3% 胜率。骗子骰子游戏由多轮不同数量的骰子组成。AI 赢了 846 场完整的比赛,输了 640 场,对应于 56.9%的胜率。
我们可以使用经典的 ELO 公式将这些百分比转换成评级差异:

玩家 1 战胜玩家 2 的概率被认为是他们的评级 e1 和 e2 之间的差的函数。
加上上面的胜率,人工智能比人类对手有 23 到 48 个 ELO 点的优势。
另一个有趣的测试是观察算法在自我对抗时提高的速度。我建立了一个锦标赛,不同版本的模型与其他版本的模型进行 10,000 场比赛。然后我用最大似然估计来计算每个玩家的 ELO。

不同迭代的 n 骰子模型的 ELO,假设迭代 1000 的评级为 0。作者图片
模型显然在开始时改进得更快,但它们都在长时间内保持改进。将 5 骰子模型训练到 230,000 次迭代大约需要 24 小时。训练本身不需要太多时间,但是玩 23,000,000 个游戏需要一段时间,因为我没有实现任何并行化。
有趣的一点是,ELO 通常用于不需要运气的游戏,比如国际象棋。我们可以在 ELO 公式中加入如下的偶然性因素:

ELO 公式假设骗子的骰子是概率为 r 的技巧游戏,概率为 1-r 的纯运气游戏
如果我们使用这个公式来估计不同版本之间的比赛的 ELO 收视率,结果是最好地解释数据的模型具有 r ~ 0.20。这意味着骗子的骰子大约 80%是纯粹的运气。🤷
它能工作吗?2 —可开发性

我对 1v1 模型进行了 3 次训练,并计算了不同迭代的可开发性。粗线表示运行的中值可开发性,虚线表示理论最小值。作者图片
有一个更有原则的方法来计算我们的人工智能离纳什均衡有多*。这被称为测量战略的可开发性。
可利用性是任何人在对抗人工智能时能够赢得的最大数量,即使知道人工智能使用的概率是多少,并且拥有无限的计算能力。
实际的 CFR 保证最终产生一个不可利用的策略。我们不能期望我们的方法有那么好,因为神经网络甚至不能代表这个完美的策略。然而,如果我们接* 0,这意味着人工智能客观上非常好,而不仅仅是在线上与自己和人类玩得很好。
计算可利用性是通过计算对手的最优反应策略来完成的。我们告诉我们的对手我们的策略(我们每个状态的概率),这个策略将游戏转变成一个马尔可夫决策过程 (MDP)。对手可以解决这个问题(在深度优先搜索的线性时间内),MDP 的值是任何对手使用任何策略都无法战胜我们的。
虽然可利用性可以在状态数中以线性时间计算,但对于具有 4 个或更多骰子的游戏来说,这仍然太昂贵了。然而,我设法计算了 1v1 的可利用性(上图),以及 1v2 和 2v1(下图)。巧合的是,这也是我 5 年前设法计算出(使用线性规划)的精确纳什均衡的游戏。结果如下:

1v2 和 2v1 游戏中的可利用性,其中玩家 1 有 1 个骰子,玩家 2 有 2 个骰子,反之亦然。在这两种情况下,玩家 2 的可利用性都相当好(低),尽管不是最低可能。然而,当人工智能是参与人 1 时,它是令人惊讶地可利用的。例如,在 1v2 中,玩家 2 有优势,我们应该仍然只能赢得大约 54%的比赛(每场 0.09 分),但有一种策略可以在* 65%的时间里击败它(每场 0.3 分)。作者图片
显然,我的模型很擅长学习参与人 2 的最佳策略。这里的可开发性离最佳值不远。另一方面,在这两个游戏中,参与人 1 的策略都没那么好。似乎人工智能更喜欢在做出自己的决定之前判断人类的呼唤。
这是否意味着作为一号玩家玩好游戏要困难得多?在 1v1 设置中,这似乎不是一个问题…是不是有一个 bug🐛在我的算法、架构或最佳反应开发器中的某个地方,只会为参与人 1 提供更多骰子?如果我想通了,我会更新这个帖子。
总的来说,该算法似乎很好地学习了骗子的骰子&非常快。鉴于代码和架构如此简单,我对此感到非常鼓舞。它当然足够强大,可以在大部分时间里赢过网上的人。请随意使用 Github 上的代码,让它变得更好!
为模型服务
我认为只有终端界面不会鼓励我的许多朋友去测试这个模型。我需要一些基于浏览器的东西,但是我不想创建一个完整的服务器/客户机来运行这个模型。
幸运的是,我发现了关于 ONNX ,尤其是埃利奥特·韦特的这个伟大的视频。在两行代码中,PyTorch 模型可以转换成 onnx 文件,可以直接从 javascript 中使用:
torch.onnx.export(model, path=’out.onnx’)
我创建了一个小的静态 javascript 前端,买了一个域名,你现在可以在 dudo.ai 上与之对战。
附录
这篇文章的灵感来自于 Erik Bernhardsson 的深度学习...2014 年的象棋。除了 Deepmind 的游戏玩家,我还从其他令人兴奋的扑克论文中获得了灵感,如 DouZero、DeepStack 和 ReBeL。
更高级的方法有两个主要改进:
- 他们使用两个神经网络而不是一个:一个网络(像我们的一样)用于预期的游戏价值,另一个预测最佳政策/概率分布。由于另一项创新,第二个网络可以训练得特别好:
- 他们使用搜索。搜索意味着给定状态的策略(概率分布)随着给 AI 时间“思考”而迭代改进。经典游戏(如国际象棋)的人工智能一直都是这样做的,但在 CFR 中,策略完全是在训练时计算的,游戏中更多的“思考时间”对人工智能没有帮助。直到最*几年才发现如何将这一点应用到类似《骗子的骰子》这样的游戏中,方法比我以为一个下午就能做到的要复杂得多。
有关 CFR 的更多信息,您可以查看
- 原文不完全信息博弈中的后悔最小化摘自 NIPS 2007。
- 来自 NIPS 2009 的蒙特卡洛版本。
- 一篇关于深度反事实后悔最小化的更有原则性的论文,来自 ICLR 2020。
- 2021 年强化学习和控制手册中关于基于政策的方法的 12.4.2.2 章节。
感谢 Kosti 阅读本文的早期草稿,感谢 Troels Bjerre Lund 批评我轻率地使用 CFR 术语。
Python 中的 Lambda 函数:您需要知道的一切
原文:https://towardsdatascience.com/lambda-functions-with-3-practical-examples-in-python-f4ed7f266e53
数据科学
从真实和最实际的例子中学习

加里·巴特菲尔德在 Unsplash 上拍摄的照片
Lambda —明智地使用它!!💡
Python 是强大的数据分析语言,不仅因为它易于使用,而且它提供了大量的内置函数和方法来处理数据。
Lambda 函数是 Python 中非常有用的函数之一,这使得在一行代码中定义和使用该函数变得非常容易。因此,了解它的来龙去脉,以便在您的数据分析任务中无缝地使用它是很好的。
所以,这里我列举了 lambda 函数的 3 个实际用例,并附有例子。你也会发现为什么以及什么时候不使用 lambda 函数。🏆
尽管这是一个快速阅读,您可以使用下面的索引直接导航到您最喜欢的部分。
**·** [**What is Lambda Function in Python?**](#7bd9) **·** [**Filter a List using Lambda Function**](#f0ac) **·** [**Use Lambda function in map()**](#e054) **·** [**Use Lambda function in apply()**](#63a1) **·** [**When not to use a Lambda function**](#311e)
∘ [Assigning a lambda expression to a variable](#ae35)
∘ [Passing function inside lambda function](#52a7)
∘ [Use lambda function when one-liner is poor choice](#41ca)
在开始使用用例之前,让我们快速了解一下什么是 lambda 函数以及如何使用它。
Python 中 Lambda 函数是什么?
它是匿名函数或者没有名字的函数。与 Python 中的其他用户定义函数不同,lambda 函数可以在同一行代码中定义和调用。
这个特性本质上使得它非常便于在需要用最少的代码对多个项目执行重复任务的情况下使用。
每个 lambda 函数都以关键字**lambda**开头,后面是函数参数和函数体。函数参数和正文用冒号隔开即**:**。
例如,在函数lambda x: x**2中,**x**是函数参数,**x**2**是表达式,即函数体。您可以对单个变量应用此函数,如下所示:
**(lambda x:x**2)(5)**# Output
**25**
所以表达式中提到的运算(在这里是 *x**2* 是 *x*的*方)是对数字 5 执行的。
📌注意:你可以在 lambda 函数中包含任意多个参数,但是函数体只能有一个表达式。这使得它不同于其他用户定义的函数。
现在,让我们看看这个神奇功能的最实际的用例。
使用 Lambda 函数过滤列表
lambda 函数的一个最简单的用例是根据特定的标准过滤给定的列表。为此,您可以将 lambda 函数与另一个内置函数**filter()**一起使用。
在 Python 中,filter()被应用于一个可迭代对象,比如一个列表或一个字典,以创建一个新的可迭代对象,它本质上只包含原始可迭代对象中满足特定条件的那些项。这个条件是用一个函数定义的。
由于 filter()的语法是**filter(function, iterable)**,从逻辑上讲,lambda 函数可用于定义在 filter()中使用的条件。
例如,您有一个从 2 到 50、间隔为 3 的数字列表。而你只想得到那些*方小于 100 的数字。所以这个条件可以定义为λ函数如下—
**lambda x:x**2<100**
它可以用在这样的过滤器中,
import numpy as np
yourlist = list(np.arange(2,50,3))
**list(filter(lambda x:x**2<100, yourlist))**# Output
**[2, 5, 8]**
如果看一下中间步骤**filter(lambda x:x**2<100, yourlist)**,它返回一个 filter 对象,确实需要转换成 list 或 set 之类的数据结构,这是在上面最后一行代码中完成的。
当然,对于函数中的复杂计算,您应该使用普通的用户定义函数,但是当您的函数只有一个表达式时,lambda 函数是最佳选择。💯
接下来,除了过滤列表,了解如何对 iterable 的每一项应用函数也是很有趣的。
在 map()中使用 Lambda 函数
在 Python 中,**map()**是在 iterable 的每一项上映射或操作传递函数的函数。
它的语法与前面的函数**map(function, iterable)**相同。因此,就像您在 filter()中所做的那样,您可以在 map()中传递 lambda 函数。
例如,假设你想得到列表中每一项的*方和立方之和。所以λ函数会是,
**lambda x: x**2+x**3**
与 filter()类似,map()函数返回需要转换成其他数据结构的地图对象。因此可以获得所需的结果,
**list(map(lambda x: x**2+x**3, yourlist))**# Output
[12,
150,
576,
1452,
2940,
5202,
8400,
12696,
18252,
25230,
33792,
44100,
56316,
70602,
87120,
106032]
但是,这是一个非常简单的例子,对吗??
在另一个例子中,假设你有两个相同长度的列表mylist和yourlist——
mylist = list(np.arange(4,52,3))
yourlist = list(np.arange(2,50,3))

使用 numpy arange | Image by Author 创建列表
你想得到两个列表中相同位置的元素的*方的相加。使用下面的map()功能,这非常简单
list(map(**lambda x,y: x**2+y**2**, **yourlist,mylist**))

带有两个参数的 lambda 函数|作者图片
请注意,由于您在 lambda 函数中定义了两个变量 x 和 y,因此您需要在 map 函数中提供两个可迭代变量,即 yourlist 和 mylist。
这些 iterable 在 map()甚至 filter()中的顺序很重要,因为第一个 iterable 总是作为 x 传递,而第二个 iterable 作为 y 传递。💯
接下来,您还可以使用 lambda 函数来转换熊猫系列对象。
在 apply()中使用 Lambda 函数
熊猫系列实际上是索引数据的一维数组。与列表不同,序列总是包含相同数据类型的数据。这是熊猫数据框架的基础,数据框架的每一列都是熊猫系列。
要转换 pandas 系列,可以使用 apply()函数。顾名思义,它用于在熊猫系列上应用一个功能。
让我们创建一个熊猫系列,通过一个例子来理解这个概念。
myseries = pd.Series(mylist)
myseries

根据作者列表|图片创建熊猫系列
apply()的语法与前两个函数略有不同,但逻辑是相同的——apply()中传递的函数应用于系列的每个元素。💯
举个例子,假设你想在 myseries 的每个元素中加 5,然后除以相同元素的*方。因此 lambda 函数将为lambda x: (x+5)/x**2并且它可以在 apply 中使用,如下所示,
**myseries.apply(lambda x: (x+5)/x**2)**

lambda 函数与熊猫系列应用()|图片作者
与前两个函数不同,您不需要将 iterable 或 series 传递给 apply(),而是可以使用.apply(function)直接在 series 上使用它。
与其应用于单个系列,不如在 DataFrame 中使用它更有意义。例如,你在 DataFrame df中有一个 虚拟销售数据 (可以从 Github 的 MIT license 下免费下载)。
df = pd.read_csv("Dummy_Sales_Data_v1.csv")
df.head()

虚拟销售数据|作者图片
你想让每个销售经理的名字都大写。这可以通过使用中的 lambda 函数轻松完成。应用()方法。
df["Sales_Manager"] = df["Sales_Manager"]**.apply(lambda x: x.upper())**
df.head()

熊猫数据帧中的 Lambda 函数 apply() |图片由作者提供
您可以简单地在任何列上使用.apply(),使用 lambda 函数定义一个快速转换,并将结果赋回原始列。💯
。带有 lambda 函数的 apply()比 str.upper()快 1.4 倍
当你比较.apply()和 lambda 函数用 string 方法做同样的转换,可以看到前者比后者快。而如果用 for loop 来比较,那么.apply()比 快 300X 。
我个人发现带有 lambda 函数的**.apply()**在我的大部分数据分析任务中非常有用。
当您学习了如何以及何时可以在 Python 中使用 lambda 函数时,知道何时不使用它也很重要。🎯
何时不使用 Lambda 函数
当然,很少有 Lambda 函数的用例不是由 Python 标准编码指南推荐的。
在以下 3 种情况下使用 lambda 函数没有错,但这不是最佳实践,因此应该避免。
将 lambda 表达式赋给变量
当你需要一个简单的函数时,你可能会想到使用 lambda 函数来定义它,并在需要时使用它。但是,这不是最佳做法。
根据定义,lambda 是一个匿名函数,因此将其存储在一个变量中以备将来使用并不是一个好主意。
例如,假设你想得到两个数的*方和,你想在你的项目中多次进行这种转换,
不好的做法❌
squared_sum = lambda x,y: x**2 + y**2
squared_sum(3,4)#Output
25
✅最佳实践
def squared_sum(x,y):
return x**2 + y**2#Output
25
当你想多次使用同一个代码块时,通常使用用户定义的函数。因此,在这种情况下,它应该优于 lambda 函数。
而且,我还观察到很多数据专业人士在 lambda 函数中传递一个标准函数比如sqrt,这并不是一个好的做法。
在 lambda 函数中传递函数
当您想要在 map()或 filter()或 apply()方法中使用函数时,就会出现这种情况。这包括所有内置函数和用户定义的函数,这些函数只接受一个参数。
例如,假设您有一个数字列表,并想得到这些数字的*方根。这可以通过使用 math 库中的内置函数 sqrt 获得。
不好的做法❌
import math
mylist = [10, 25, 40, 49, 65, 81]
sqrt_list = list(map(lambda x: math.sqrt(x), mylist))
sqrt_list#output
[3.1622776601683795, 5.0, 6.324555320336759, 7.0, 8.06225774829855, 9.0]
✅最佳实践
import math
mylist = [10, 25, 40, 49, 65, 81]
sqrt_list = list(map(math.sqrt, mylist))
sqrt_list#output
[3.1622776601683795, 5.0, 6.324555320336759, 7.0, 8.06225774829855, 9.0]
来自 math 库的内置函数 sqrt 可以直接在 map()函数中使用。在 lambda 函数中使用它是不必要的,而且会影响性能。
如果您比较上面两种在 lambda 函数中传递另一个函数的方法,并对每种方法运行 100000 次迭代,您会发现最佳方法总能提高性能。💯
将函数直接传递给 map()要快 1.8 倍!!
用户定义的函数也是如此。
接下来,Python 中的一行程序非常受欢迎,编码人员通常会上当。然而,一行程序并不总是最好的选择,在这种情况下使用 lambda 函数甚至会降低代码的可读性并使其难以维护。
当一行程序不是好的选择时,使用 lambda 函数
当您在 lambda 函数中使用复杂的运算(如 if-else 条件或简单的多重运算)时,实际的 lambda 函数定义会变得更长。
当您在一行程序中使用这样的 lambda 函数时,您的一行代码不再适合一行。
根据 PEP8 指南,将所有行限制为最多 79 个字符。
由于代码写在多行上,因此很难有效地阅读代码并在需要时进行调试。
例如,你在 DataFrame df 中有一个 虚拟销售数据 (可以在 MIT license 下从 Github 免费下载)。
df = pd.read_csv("Dummy_Sales_Data_v1.csv")
df.head()

虚拟销售数据|作者图片
并且您想要创建一个具有最终状态的新列,这样,如果状态列包含‘已交付’或‘已装运’,则最终状态将为‘已完成’,否则将为‘未完成’
一句俏皮话和糟糕的做法❌
df["FinalStatus"] = df["Status"].map(lambda x: 'Completed' if x == 'Delivered' or x == 'Shipped' else 'Not Completed')
这一行包含 118 个字符,并且扩展到一行之外。所以很难读懂。

使用和不使用 Lambda 函数的相同输出|作者的图像
虽然它给你正确的输出,我推荐下面的最佳做法。✅
df["FinalStatus"] = ''df.loc[(df["Status"] == 'Delivered') |
(df["Status"] == 'Shipped'),
'FinalStatus'] = 'Completed'df.loc[(df["Status"] == 'Not Delivered') |
(df["Status"] == 'Not Shipped'),
'FinalStatus'] = 'Not Completed'
一般来说,当您想要基于多个条件过滤数据集时,一个线性并不是很好的选择。相当容易地将代码分成多行,以提高其可读性。
我个人经常使用这种最佳实践,因为它让您对代码有更多的控制,并且如果需要,您可以随时添加更多的条件。🏆
关于 Python 中的 lambda 函数你就知道这么多了!
我希望你发现这篇文章非常有用,令人耳目一新,并且你学会了在 Python 中什么时候使用(和什么时候不使用 ) lambda 函数。我确信,读完这篇文章后,你可以毫不费力地明智地使用 lambda 函数。
过去 4 年多以来,我一直在使用 Python 进行数据分析,并发现 lambda 函数在我这里提到的某些场景中非常有用。
有兴趣阅读媒体上的所有故事吗??
💡考虑 成为媒介会员可以在媒介上无限制的访问我和百万其他作家写的故事!。我会得到一小部分你的费用,没有额外的费用给你。
💡请务必 注册我的电子邮件列表 以免错过另一篇关于数据科学指南、技巧和提示、SQL 和 Python 的文章。
感谢您的阅读!
基于 Python 语言的张量流 CNN 土地覆盖图像分类
原文:https://towardsdatascience.com/land-cover-classification-using-tensorflow-in-python-791036eaa373
使用张量流卷积神经网络的欧洲卫星土地覆盖图像分类
介绍
土地覆盖是探测到的地球表面的生物物理覆盖,包括草、森林、牧场、水等物质。吸收土地覆被信息的方法多种多样。一个主要过程包括使用遥感图像结合实地调查。

美国宇航局在 Unsplash 拍摄的照片
资料组
欧洲卫星数据集由来自哨兵-2 卫星图像的瓦片组成。数据集公开到下载。
图 1 显示了从集合中随机选择的 15 个样本,描述了 10 个可能类别中的 8 个。剩下的两个看不见的类是HerbaceousVegetation和River。

图 1 —欧洲卫星组织样本训练数据(图片由作者提供)
从图 1 中可以看出,每种土地覆被类别之间有明显的视觉差异。因此,一个合理的先验假设预计Forest、Pasture和AnnualCrop类别之间的潜在混淆。另外,Highway和River图像可能趋于相似。然而,一般来说,对于神经网络来说,似乎存在适当的区别属性来充分学习适当的特征映射。
图 2 显示了十个土地覆盖类别中每个类别的计数分布,总共有 27,000 张带标签和地理参考的图像。
地理参照意味着航空照片的坐标系统与地理坐标的地面系统相关。因此,地理信息系统(GIS)可以将图像“放置”在适当的真实位置。

图 2—训练数据计数分布(图片由作者提供)
机器学习任务通常利用三个数据子集 : 训练、测试和验证 。训练/验证/测试百分比分割根据可用数据集的大小而变化:
- 训练 (60%):标记数据,用于通过反向传播算法进行梯度下降以调整 CNN 的参数
- 验证 (20%):用于在每个训练周期后确定模型性能指标的标记数据
- 测试 (20%):用于评估模型功效的看不见的标记数据
要点 1 显示了 Keras 实用函数image _ dataset _ from _ directory,该函数加载目录结构与文档匹配的图像文件。
要点 1 —使用 Tensorflow 加载训练/验证数据集
加载图像后,检查数据并确定有用的变量。例如,使用以下命令查找类的名称和编号:
class_names = **train_dataset.class_names**
num_classes = **len(class_names)**
上面使用的 Keras 函数批量加载数据。从BatchDataset检索单个体积,检查输入和输出形状以及图像值范围,如要点 2 所示。
要点 2——用于BatchData检查的 Python 代码
根据要点 2 的规定,批量= 32 。因此,输入image_batch是一个(32, 64, 64, 3)张量,其中图像是*64 (height) x 64 (width) x 3 (RGB channels)*。每个图像都有一个对应的整数标签用于类别识别,因此label_batch的形状为(32)。
预处理
数据预处理是指为建立和训练机器学习模型准备原始数据。随后,卷积神经网络(CNN)模型执行欧洲卫星土地覆盖分类。
输入图像是张量,可解释为 8 位无符号整数(十进制 0 到 255)的 NumPy 数组。因此,r escaling 从 0–255 到 0–1的值提高了性能和训练稳定性。
Keras 提供了一个[Rescaling](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Rescaling) 预处理层,它将输入值修改为一个新的范围。使用下面的 Python 代码定义该图层。
rescaling_layer = layers.experimental.preprocessing.**Rescaling**(
**scale**=1\. / 255,
**input_shape**=(img_height, img_width, 3)
)
输入图像的每个值都乘以scale。
模型架构
随着预处理的完成和 CNN 训练可接受格式的数据,下一步是定义网络架构。图 3 图示了六层模型架构。
当计算 CNN 网络的深度时,我们只考虑具有可调/可训练权重/参数的层。⁵

图 3-土地覆盖分类的卷积神经网络结构(图片由作者提供)
基本张量流 CNN 模型通常采用相同的元素。这些层包括:
[Conv2D](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Conv2D)层幻灯片 内核 ,例如八个滤波器以给定的步距穿过输入图像 (3x3),提取适合于解决分类问题的特征。通过卷积操作,或者更准确地说是互相关来进行特征提取。过滤器在模型编译期间被随机初始化。
通过反向传播学习滤波器参数,在训练网络时提取输入图像的本质特征。[MaxPooling2D](https://keras.io/api/layers/pooling_layers/max_pooling2d/)层对像素组进行操作,例如(2 x2),并通过选择最大值**来过滤值。汇集在保持特征信息的同时,对输入图像尺寸进行向下采样。填充可维持或减缓空间尺寸的缩小;然而,“valid就是没有填充的意思。[Dense](https://keras.io/api/layers/core_layers/dense/): “只是你的正则密连 NN 层”。
Gist 3 给出了定义tensor flowSequentialCNN模型的 Python 代码。
Gist 3 —使用 Python 中的 Keras 定义的 CNN 架构
模型编译model.compile()包括定义:
optimiser=’adam’: Adam 是一种随机梯度下降(SGD)* 优化方法,其本质上是基于损失函数的反向传播输出来更新可训练参数。*loss=’**SparseCategoricalCrossentropy**':计算标签和预测之间的交叉熵损失。[SparseCategoricalCrossentropy](https://www.tensorflow.org/api_docs/python/tf/keras/losses/SparseCategoricalCrossentropy)损失适用于标签类别计数≥2,以整数提供。由于模型不包含一个 softmax 层作为最后一层,from_logits=True是必要的,以指示概率没有被归一化。⁴metrics=[‘[**accuracy**](https://www.tensorflow.org/api_docs/python/tf/keras/metrics/Accuracy)’]:计算预测等于标签的频率,并用于评估训练期间的模型性能。
参见本文文章确定如何推导分类应用的二元(0 或 1)交叉熵损失函数。
*https://medium.com/@andrewdaviesul/chain-rule-differentiation-log-loss-function-d79f223eae5
图 4 是model.summary()的输出。层、输出形状和参数计数与图 3 中所示的等效。

图 4——CNN 模型总结(图片由作者提供)*
培养
训练可以使用下面的 Python 代码开始,因为训练/验证数据在 TensorFlow BatchDatasets结构和定义的模型架构中。
*# number of training cycles
epochs = 15# trains the model for 15 iterations on the dataset
history = **model.fit**(
train_dataset,
validation_data=validation_dataset,
epochs=epochs
)# save model
model.save(f'{os.getcwd()}/land_cover_model')*
许多网络超参数,如学习率、历元数、批量、隐含层数和单元数、都是通过试错调整。尝试使用不同的超参数来提高分类性能。**
图 5 是装配期间控制台日志输出的片段。它显示了定型数据集和验证数据集的损失。

图 5 —模型拟合的详细输出(图片由作者提供)
图 6 绘出了感兴趣的训练度量,训练和验证精度,以及训练和验证损失。随着时间的推移,网络通过反向传播学习合适的Conv2D过滤器权重,以最小化损失,随着损失的减少,模型的准确性增加。
最终的训练和验证准确率分别约为 90% 和86%。

图 6 —培训和验证指标的图表(图片由作者提供)
预言;预测;预告
在训练土地覆盖分类网络之后,进一步的测试通常是有价值的。如前所述,数据集的一个保存部分(约为 20% )保留下来,用于对不可见数据进行额外评估。
首先,使用与图 3 中相同的代码加载数据集,除了更新后的 path 变量指向测试文件夹。使用以下命令从磁盘加载预训练的 Keras 模型:
*# load keras model and print summary
model = keras.models.load_model('path/to/model')
model.summary()*
随着模型加载到内存中,可以进行预测。Gist 4 详细描述了用于迭代测试图像的BatchDataset并进行预测的 Python 代码。
要点 4——用于BatchDataset Iteration预测的 Python 代码(图片由作者提供)
对于每个推理model.predict()返回与每个类别相关联的类别可能性的概率的数组。概率指数最高的是模型的整数标签预测,argmax()提取这个位置。**
图 7 显示的是 CNN 的预测。

图 7-CNN 土地覆盖等级预测(图片由作者提供)
估价
一个混淆矩阵是一个评估分类准确度的可用度量。通过一个模型预测类列表、y_pred和一个实际类别列表,SciKit-Learn Python 库y_true具有一个内置函数来快速计算矩阵。
图 8 是从测试数据集的预测中生成的混淆矩阵。

图 8-土地覆被混淆矩阵(图片由作者提供)
矩阵的每个行表示实际类,而每个 列 描述了 预测 类。
- True Positive ( TP ):标识正确预测的类。TPs 位于实际值和预测值交点处的矩阵对角线上。
- True Negative ( TN ):指定正确预测的负类。TNs 是所有列和行的值的总和,不包括当前类。
- 误报 ( FP ):误报类,当前列的和减去 TP 值
- 假阴性 ( FN ):一个错误预测的阴性类,除 TP 值外对应的类行总和
以River类为例,图 9 描绘了度量细目: TP = 15 , TN = 350 , FP = 5 和 FN = 10 。

图 9-河流等级的混淆矩阵分解(图片由作者提供)
最初,可以预见的是Highway组和River组之间的混乱。根据混淆矩阵,模型错误地将* f ive 河流标注为高速公路( 漏报 )。同样,两条高速公路被误划为河流( 误报 ) 。*
总的来说,预测精度在 86% 左右,如图 8 所示,并且在训练期间确定的预期值附*。准确度的提高当然是可能的。欧洲卫星 paper⁷* 报告高九十范围内的性能指标值。实验用 CNN 网络架构和超参数调谐来增强结果。*
结论
本文展示了如何用 Python 实现卷积神经网络,用于卫星影像土地覆盖分类。
找到下面使用的所有 Python 代码。感谢阅读。
*https://medium.com/@andrewdaviesul/membership *
CNN 培训的 Python 代码(要点 5)
Gist 5 — CNN 土地覆盖分类培训
CNN 预测的 Python 代码(要点 6)
要点 5 — CNN 对土地覆盖分类的预测
参考
[1] Eurosat:土地利用和土地覆盖分类的新型数据集和深度学习基准。帕特里克·赫尔伯,本杰明·比斯克,安德烈亚斯·登格尔,达米安·博斯。IEEE 应用地球观测与遥感选题期刊,2019
【2】euros at | tensor flow 数据集(2022 年 2 月)
【3】地理配准 —用 GeoPads 教学
【4】from _ logits = True 在 SparseCategoricalcrossEntropy loss 函数中做什么? —数据科学栈交换(2020 年 4 月)
【5】统计 CNN 层数。ran mal de wage—stack overflow
【6】EuroSAT 公开可用数据集—GitHub.com
【7】介绍 euros at:一种新型数据集和深度学习基准,用于土地利用和土地覆盖分类。帕特里克·赫尔伯,本杰明·比斯克,安德烈亚斯·登格尔。2018 IEEE 国际地球科学与遥感研讨会,2018。
语言模型标度律和 GPT-3
原文:https://towardsdatascience.com/language-model-scaling-laws-and-gpt-3-5cdc034e67bb
理解为什么像 GPT-3 这样的 LLM 工作得这么好…

语言模型(LMs)非常通用——它们将文本作为输入,将文本作为输出。最*的研究表明,通过使用提示技术来执行准确的零镜头和少镜头推理,可以利用这种通用的文本到文本结构来解决各种任务,而无需任务特定的适应(即,没有微调或架构修改)。简单地说,我们可以在一个大的、未标记的文本语料库上预先训练 LM(使用语言建模目标),然后通过文本提示询问 LM 来解决问题。这样,预先训练的模型可以很容易地重新用于解决不同的问题。
虽然 LMs 作为任务不可知的基础模型具有不可思议的潜力,但是最初尝试将预先训练的 LMs 转移到解决下游任务(例如,GPT 和 GPT-2 [4,5]),效果并不好。在这个概述中,我们将了解最*的研究如何建立在这些初步尝试的基础上,并创建了实现更好的任务无关性能的 LMs。这一系列工作的关键发现是 LMs 变得更加强大,因为你扩大了它们的规模;见下文。

(来自[1]和[2])
更具体地说,我们将了解到,大型 LMs(LLM)比它们的小型对应物更具有 (i) 采样效率,并且 (ii) 更能够向下游任务进行任务无关的转移。有趣的是,这些 LLM 的性能遵循各种因素(例如,模型大小和训练数据量)的可预测趋势。对这些趋势的经验观察最终导致了 GPT-3 的诞生,这是一个 1750 亿参数的 LLM,远远超过了其前辈的任务不可知性能,甚至在某些任务上超过了最先进的监督深度学习技术。
背景
理解 LMs 所需的大部分先决信息已经在我以前的一篇文章中提到了。这些先决条件包括语言建模目标、只有解码器的转换器模型,以及如何将这些思想结合起来生成强大的基础模型。点击链接了解更多信息。
我将在这里对这些想法进行一个快速的概述,并解释一些有助于理解像 GPT-3 这样的 LLM 的附加概念。
语言建模一瞥
现代 LMs 使用通用预训练程序来解决各种各样的任务,而不需要下游适应(即,没有架构修改、微调等)。).使用未标记文本的大型语料库,我们使用语言建模目标预先训练我们的 LM,该目标是 (i) 从我们的语料库中采样一些文本,并且 (ii) 试图预测出现的下一个单词。这是一种自我监督学习的形式,因为我们总是可以通过简单地查看我们语料库中的数据来找到下一个单词的基本事实;见下文。

语言模型预培训流程(由作者创建)
建筑。现代 LMs 使用仅支持解码器的变压器架构,该架构将一系列由屏蔽自我关注和前馈变换组成的层应用于模型输入。使用屏蔽自我注意代替双向自我注意,因为它防止模型在序列中“向前看”以发现下一个单词。
除了这些仅用于解码器的层之外,LM 架构还包含嵌入层,这些嵌入层存储与固定大小词汇表中所有可能的标记相对应的向量。使用这些嵌入图层,可以将原始文本转换为模型可接受的输入矩阵,如下所示:
- 将原始文本标记成单独的标记(即单词或子单词)
- 为每个输入标记查找相应的嵌入向量
- 连接令牌嵌入,形成令牌向量的矩阵/序列
- 向每个令牌添加位置(和其他)嵌入
请参见下图,了解此过程的说明。

将原始文本转换成与模型兼容的令牌嵌入矩阵(由作者创建)
理解 LM 中嵌入层和仅解码器层之间的区别很重要。例如,本概述中的一些后续工作将通过排除嵌入层中的参数并仅计数仅包含在解码器层中的参数来研究基础 LM 中的参数数量。
适应。通过在大型语料库上预训练 LMs,我们获得了一个模型,该模型可以在给定一系列标记作为上下文的情况下准确预测下一个标记。但是,我们如何使用这样的模型来解决像句子分类和语言翻译这样的语言理解任务呢?
对于现代 LMs,这个问题的答案实际上很简单——我们根本不改变 LM。相反,我们通过向模型提供文本“提示”来利用模型的文本到文本输入输出结构的一般性质,例如:
- "把这句话翻译成英语:
= > " - “总结以下文档:
= >”。
给出这些解决问题的提示(更多例子见这里的),一个好的 LM 应该输出一个为我们解决问题的文本序列!对于我们必须从一组固定的解决方案(即多项选择或分类)中进行选择而不仅仅是生成文本的问题,我们可以使用 LM 来衡量生成每个潜在解决方案的概率,并选择最有可能的解决方案。
主打外卖。现代 LLM 的关键在于,我们可以使用语言模型预训练作为创建通用基础模型的工具,这些模型可以解决各种问题,而无需调整或微调模型。尽管与微调或监督语言理解技术相比,以前的 LMs 如 GPT 和 GPT-2 [4,5]表现不佳,但这样的学习框架非常有前途,正如我们将在 GPT-3 中看到的那样,甚至可以在底层 LM 变得更大时表现得相当好。
幂定律
本概述将包含对幂律概念的多次引用。例如,一份文件可能会做出如下声明:
“LM 的试验损失根据模型参数的数量按幂律变化”。
这句话简单地告诉我们,两个量之间存在关系——损耗和模型参数的数量——因此一个量的变化会产生另一个量的相对不变变化。
为了更具体一点,幂律可以用下面的等式来表示。

(作者创作)
这里,我们研究的两个量是x和y,而a和p决定了这些量之间的幂律的形状/行为。绘制该幂律(使用a = 1、p = 0.5和0 < x, y < 1)会产生下图,其中将两个轴转换为对数标度会产生幂律特有的线性趋势。

描述两个量之间的基本幂律关系(由作者创建)
幂定律只是告诉我们一个量作为另一个量的幂而变化。我们将在本概述中看到的工作考虑了幂律的逆版本,如下所示。

(作者创作)
值得注意的是,这个等式与之前的等式相同,只是p的指数为负。这个负指数产生了下图,其中一个量随着另一个量的增加而减少。

描述两个量之间的逆幂定律(由作者创建)
在我们对 LMs 的分析中,我们会遇到类似上图的幂律。也就是说,LM 损失倾向于根据关于几个不同因素(例如模型或数据集大小)的幂定律而降低。我们将在后面的章节中对此进行更多的阐述。
其他有用的细节
除了语言建模背后的核心思想之外,还有几个额外的概念可能有助于了解下一步。
分布式训练。这篇综述中的论文的主要思想是扩大像 GPT 和 GPT-2 这样的模型,使它们变得更好。然而,随着我们的模型变得越来越大,由于计算和内存开销的增加,训练变得更加困难。为了帮助实现这一点,我们可以利用分布式培训技术,这种技术使用更多的硬件(例如,更多的服务器/GPU)来使大规模培训过程更加容易处理和高效。
有几种不同的方法来分配神经网络的训练过程。这些技术之一是数据并行训练,其中我们:
- 取一个大的小批量
- 将这个小批量分成几个更小的子批量
- 在不同的 GPU 上并行执行与每个子批次相关的计算
- 将来自每个 GPU 的子批次结果累积到一个集中的模型更新中
这种方法通过在几个 GPU 上并行化大型小批量的模型计算来提高训练效率。
有些不同的是,我们可以执行模型并行训练,它将模型本身(即,而不是小批量)分割到多个 GPU 上。例如,我们可以将模型的每一层——甚至每一层的更小部分——发送到单独的 GPU。实际上,这意味着正向传递分布在几个设备或 GPU 上,每个设备或 GPU 包含底层模型的一小部分。这种方法能够训练更大的模型(即,因为每个 GPU 只存储模型的一小部分!)并且可以通过智能流水线和模型前向传递的并行化来提高训练效率。
出于概述的目的,我们只需要知道我们可以利用跨许多 GPU 的分布来使 LLM 训练更易处理和更高效。数据和模型并行训练是流行的分布式训练技术的例子。分布式训练存在许多考虑因素和替代方法——这是深度学习中的一个完整的研究领域,产生了许多令人敬畏的实际结果。
要了解更多信息,我建议查看以下文章:
临界批量。鉴于使用大批量进行数据并行训练可以提高计算效率,我们应该尽可能地扩大批量,对吗?嗯,这不太正确,因为 (i) 较大的批处理可能会降低模型性能,而 (ii) 增加批处理大小会增加计算成本并需要额外的硬件。简单地说,批量增加太多会产生收益递减;见下文。

(摘自[3])
考虑到这一点,我们可能会开始想:使用什么样的批量最合适?这个问题通过【3】中临界批量的建议得到了经验性的回答。这项工作使用一种称为梯度噪声标度的度量来估计跨各种域的最大有用批量。超过这个临界批量,我们开始看到在性能和计算效率方面的收益递减。因为采用不同的批量大小会影响培训的效率和质量,一些工作——正如我们将在本概述中看到的——采用临界批量大小作为资源高效培训的标准实践。
光束搜索。 LM 通过输出文本序列响应提示来解决问题。这些序列可以通过连续预测下一个单词、将这个单词添加到输入提示中、预测另一个单词等等来自回归生成;见下图。

使用语言模型生成自回归输出(由作者创建)
然而,不断预测最可能的下一个单词的贪婪方法并不是最佳的!这是因为记号序列的概率(假设每个记号独立地生成)是给定前面记号的每个单词的条件概率的乘积(即,由于概率的链规则)。贪婪地选择最可能的下一个令牌可能不会最大化这个概率;例如,最初选择低概率记号可能随后导致序列的剩余部分中的更高概率记号。
我们可以用波束搜索找到一个*似的解决方案,而不是测试所有可能的输出标记的组合来找到最佳输出序列。波束搜索背后的思想很简单:不是在每一步选择最可能的下一个标记,而是选择前 k 个最可能的代,基于这些顶部选择维护一个可能的输出序列列表,然后在最后选择这些序列中最可能的。

k=2 的波束搜索(由作者创建)
出版物
我们现在将概述预测[1]和经验验证[2]类似 GPT-3 的 LLMs 的难以置信的实际效用的出版物。从这些出版物中,我们将更好地理解为什么 LLM 如此强大,并看到它们在实际应用中的性能的广泛分析。
神经语言模型的标度律【1】
GPT 和 GPT-2 [4,5]向我们展示了 LMs 作为通用基础模型的不可思议的潜力,但是当转移到下游任务时,它们的性能仍然有待改进。因此,我们可能会问:我们如何才能让这些模型变得更好?
在[1]中,作者研究了使 LMs 更强大的一个潜在方向——扩大规模。具体来说,他们训练一群仅解码器的 LMs,并分析其测试损失(即,在保留测试集上的交叉熵语言建模损失)作为几个因素的函数,这些因素包括:
- 模型尺寸
- 数据量
- 训练计算量
- 批量
- 建筑细节(即模型宽度/深度、注意力头数量等。)
- 上下文长度(即,用于预测下一个记号的记号的数量)
这种分析揭示了 LM 训练行为的几个基本特性。例如,如果参数总数是固定的,调整架构细节对 LM 性能的影响是最小的。然而,LM 的测试损失遵循关于模型大小、数据大小和跨越几个数量级的训练计算量的幂定律;见下文。

(来自[1])
为了让这一点更清楚,作者在[1]中考虑了三个主要因素:模型大小(N)、数据大小(D)和训练计算量(C)。为了研究与这些因素中的任何一个相关的缩放行为,我们 (i) 确保其他两个因素足够大(即,因此它们不是性能的瓶颈),然后 (ii) 在我们正在研究的因素的大范围值上测量 LM 的测试损失。例如,为了研究 C 的缩放属性,我们确保模型和数据集足够大,然后测量不同 C 设置下的 LLM 性能。我们现在将分别考虑这些因素。
模特身材。为了研究相对于模型大小的缩放特性,作者在来自[1]–WebText2 的完整数据集上训练不同的 LM 收敛,webtext 2 是来自 GPT-2 的扩展版本 WebTest[2],大约大 10 倍。然后,通过采用具有不同数量的总参数的几个 LMs,我们可以获得如下所示的图。

(来自[1])
通过将 LM 的测试损耗绘制为仅解码器层中的参数总数的函数(即,排除嵌入层中的所有参数),我们可以看到 LM 损耗遵循关于 n 的*滑幂定律。换句话说,增加 LM 的大小会产生其性能的稳定改善。

(来自[1])
数据和计算。为了研究 LM 的性能如何随训练数据量而扩展,作者[1]采用了足够大的 LM,并在不同大小的数据集上执行单独的训练试验。对于每个试验,模型被训练直到试验损失开始增加,这是过度拟合的指示。此外,该分析向我们显示,测试损失根据关于数据集大小的幂定律而减少;见上文。

(来自[1])
当改变训练计算量时,我们看到一个非常相似的趋势,定义为批大小 B 和训练迭代次数 s 的 C = 6NBS,给定一个足够大的数据集和固定的批大小 B,我们可以扫描多个 LM 大小 N 以获得上面所示的结果。这里,我们看到,使用 N 和 S 的不同组合实现了每个计算预算 C 的最佳结果,但是最佳 LM 损失根据关于训练计算量的幂定律而降低。
此外,我们可以从这些结果中看到,LM 样本效率(即模型运行良好所需的样本数量)随着 n 的增加而提高。为了更清楚地说明这一点,[1]的作者分析了不同规模的 LMs 在训练期间观察到的样本总数方面的性能,得出了下图。在这里,我们可以清楚地看到,随着模型变大,LM 的性能提高得更快。

(来自[1])
成对标度定律。除了通过孤立地分析 N、D 和 C 观察到的幂定律,同时改变这些因素的配对也可以产生可预测的行为;例如,通过联合改变 N 和 D,我们可以获得如下所示的图。这里,我们观察到 (i) 较大的模型开始在较小的数据集上过度拟合,并且 (ii) LM 损失遵循关于给定的足够大的数据集的 N 的严格幂定律。

(来自[1])
在高层次上,这告诉我们,当我们增加底层 LM 的大小时,我们必须使数据集更大,以避免过度拟合。然而,作者在[1]中发现,次线性缩放数据大小(即,与N^0.74成比例)足以避免过度拟合。
外卖。虽然我们已经在高层次上讨论了[1]中概述的幂定律,但实际出版物使这些定律变得非常具体,甚至提出了任何 LM 测试损耗的准确预测框架。为了简单起见,我们在这里避免这些细节,而是集中在以下用于训练 LMs 的要点上。

如何恰当地在 LLM 培训中投入更多计算(来自[1])
如果我们要扩大 LM 培训的规模,我们应该:
- 将大部分额外计算投入到更大的模型中(即,更大的模型样本效率更高)
- 增加数据集的大小(但不要像模型大小那样大)以避免过度拟合。
- 稍微增加批量大小(即根据临界批量大小[3])。
- 停止训练远未收敛的模型,以优化训练计算的使用。
在[1]中观察到的幂律似乎不受阻碍地持续了几个数量级。虽然这种扩展最终会达到一个极限,但它仍然表明(适当地)增加 LM 训练的规模会产生可测量的性能收益,暗示探索 LLM(如 GPT-3)可能会被证明是非常有益的。
“我们的结果强烈表明,更大的模型将继续表现更好,并且样品效率也将比以前预期的高得多。大模型可能比大数据更重要。”—来自[1]
语言模型是一次性学习者 [2]
先前关于 GPT 和 GPT-2 [4,5]的工作开始揭示通用 LMs 在解决文本理解任务中的效用。然而,这些模型仍然有局限性:
- GPT 并不是完全任务不可知的(即,需要特定任务的微调)
- GPT-2 在零射击状态下的表现远不如有监督的最先进水*
现有的工作提供了一个“概念证明”,即 LMs 可以通过执行零/少量、任务不可知的推理来消除对任务规范的需求。然而,LMs 相对于监督技术的较差性能使得它们不太实用。幸运的是,在[1]中观察到的幂律提供了希望,即较大的 LMs(即 LLM)可以缩小任务不可知和任务特定/监督性能之间的差距。
朝着这个方向发展,GPT-3 与 GPT-2 共享相同的纯解码器架构(除了增加一些稀疏注意层[6]),在现有 LMs 的基础上增加了几个数量级。特别地,它是具有超过 1750 亿个参数的 LLM(即,作为参考,GPT-2 [5]包含 15 亿个参数);见下文。

(来自[2])
随着 GPT-3,我们终于开始看到 LLMs 有希望的任务无关性能,因为该模型的少数镜头性能在几个任务上接*监督基线。与 GPT-2 类似,作者使用语言建模目标对 LLM 进行预训练,但他们采用了基于过滤版本的 CommonCrawl 和一些附加的高质量语料库的更大数据集。用于预训练的完整数据集的细分如下所示。

(来自[2])
GPT-3 的预训练与 GPT-2 相似,但模型的训练时间要长得多。为了使训练过程在计算上可行,作者采用了模型并行分布式训练方法,该方法将每个 LM 层的部分分布在单独的 GPU 上。因为每个 GPU 只存储完整模型的一小部分,所以可以在不超过内存限制的情况下进行训练。
GPT-3 的学习过程有两个组成部分:联合国/自我监督的预培训和环境学习。这两个组件如下图所示。

(来自[2])
简而言之,我们首先在大型无监督文本语料库上预训练通用 LLM,然后使用上下文学习指导该模型解决下游任务。这种环境中的学习过程可以通过特定任务的微调(如在 GPT)来执行,甚至可以使用像不需要对 LM 进行梯度更新的少数镜头学习这样的技术。微调与零次、一次和少量学习的不同变体之间的差异如下所示。

(来自[2])
与以前的变体不同,GPT-3 仅使用零和少量学习技术进行评估。作者没有根据用于评估的任何下游数据集调整或微调模型。相反,他们在大规模文本语料库上预先训练这个令人难以置信的大模型,并研究仅使用包含不同数量“上下文相关示例”的少量提示技术是否可以准确地执行上下文相关学习,如上图所示。
通过在一系列语言理解任务上评估 GPT-3,我们立即看到使用更大的模型显著地有益于少数镜头表现。例如,在句子完成任务上,GPT-3 改进了当前的最先进水*(即,包括使用监督训练或微调的方法!)在几个流行的数据集上,提供更多的上下文示例似乎可以进一步提高性能;见下文。

(来自[1])
在问答任务中,我们看到 GPT-3 的表现优于 T5 [7]或罗伯塔[8]等模型。然而,这些模型执行广泛的、受监督的微调,而 GPT-3 通过任务不可知的、少量的推理实现了可比较的结果。简而言之,GPT-3 在这些任务上的表现仍然令人印象深刻,因为它是一个完全通用的 LLM,没有以任何方式专门解决这些任务。
当在翻译任务上评估 GPT-3 时,我们观察到 GPT-3 在从其他语言翻译成英语方面优于最先进的无监督神经机器翻译(NMT)技术。鉴于 GPT-3 的预训练集仅包含 7%的非英语内容,并且没有语言之间的明确混合或翻译,这样的结果令人惊讶。有趣的是,GPT-3 在从英语翻译成其他语言方面的效率要低得多;见下文。

(来自[2])
作者还在超级胶水基准测试上评估了 GPT-3,该测试包含各种不同的语言理解任务。结果汇总在下图中,我们可以看到 (i) 使用更多的上下文示例有利于 GPT-3 的性能,(ii)【GPT-3 甚至可以超过流行的微调基线,如BERT【9】。

(来自[2])
在所有的基准测试中,GPT-3 向我们展示了 LLM 随着规模的增长,在任务无关的情境学习中变得更加有效。我们可以使用上下文中的示例来促使 LLM 对各种任务做出准确的响应,这使得 GPT-3 成为第一个使用通用 LLM 对各种下游任务执行高度准确的推理而无需任何特定任务修改的实际例子。
尽管 GPT-3 在创建任务不可知的语言基础模型方面取得了令人难以置信的飞跃,但这些进步是以巨大的计算成本为代价的。GPT-3 是在一个专用 GPU 集群上进行预训练的,其预训练过程需要的计算量比之前研究过的任何模型都多得多;见下文。

(来自[2])
虽然最*的工作已经大幅降低了 GPT-3 的训练成本(即从 1000 万美元的计算成本降低到 50 万美元),但获得这样的基础模型仍然不便宜。如果我们想创建自己的基础模型,如 GPT-3,我们最好确保它表现良好。
开源 GPT-3。在[2]中 GPT-3 的最初提议之后,该模型没有公开发布。相反,它只能通过付费 API访问。尽管模型的 API 被大量使用,但是缺乏对模型本身(及其训练代码)的开源访问阻碍了进一步的分析和实验。
为了消除这个问题,一个开源版本的 GPT-3,称为 OPT-175B,被创建和分析[10]。OPT-175B 的发布还包括一个完整的代码库和几个日志,为 LLM 培训过程提供了有价值的见解。了解更多关于 OPT-175B 的信息(并查看可用于训练 GPT-3 等 LLM 的代码!),查看这里的概述。
外卖食品
最初提出和探索 GPT 模型的目的是创建能够解决各种任务的通用语言模型。这些模型是在这样的假设下运行的:如果我们可以在非常精细的级别上理解语言建模(即预测序列中的下一个单词),那么我们可以用许多有用的方式来概括这种理解,而不需要特定任务的微调或调整。
最初,像 GPT 和 GPT-2 这样的 LMs 没有达到这个目标。他们与任务无关的表现远不如监督基线。然而,在这个概述中,我们已经了解到,增加这些 LMs 的规模是创建高性能、任务不可知的语言理解模型的可行途径。最终,这种思路导致了 GPT-3 的提出和分析,这是一个巨大的 LLM(比 GPT-2 大 100 倍),远远超过了以前的 LMs 的任务无关性能。
缩放定律。纵向扩展 LMs(即使用更大的模型、更多的数据和更多的计算)可以显著提高其性能。随着我们增加 LM 训练的规模,我们从[1]中的发现得知,我们应该 (i) 显著增加基础模型的大小,并且 (ii) 在较小程度上增加用于预训练的数据量(以及批量大小)。较大的语言模型的样本效率更高,并且它们的性能在模型大小、数据大小和跨几个数量级的训练计算量方面以幂律形式提高。换句话说, LMs 变得越大越好。
我们可以扩展到什么程度? GPT-3(拥有 1750 亿个参数的 LLM)以前所未有的规模实证验证了[1]中概述的趋势。当我们采用这种大规模模型并在大型文本语料库上对其进行预训练时,我们看到在任务不可知、少数镜头性能方面的巨大改进。在几个基线上,GPT-3 仍然优于监督技术,但[2]中的发现提供了明确的证据,表明 LLM 随着规模的增长,其执行上下文学习的能力有所提高。虽然 GPT-3 在技术上类似于 GPT-2,但训练这种规模的模型是一项工程壮举,展示了语言基础模型令人难以置信的潜力。
结论
非常感谢你阅读这篇文章。如果你喜欢它,请在 twitter 上关注我,或者订阅我的深度(学习)焦点时事通讯,在那里我挑选了一个关于深度学习研究的单个双周主题,提供了对相关背景信息的理解,然后概述了关于该主题的一些流行论文。我是 Cameron R. Wolfe ,ale gion的研究科学家,莱斯大学的博士生,研究深度学习的经验和理论基础。你也可以看看我在 medium 上的其他作品!
文献学
[1]卡普兰,贾里德,等。“神经语言模型的标度律。”arXiv 预印本 arXiv:2001.08361 (2020)。
[2]布朗、汤姆等人,“语言模型是一次性学习者。”神经信息处理系统进展 33(2020):1877–1901。
[3] McCandlish,Sam 等,“大批量培训的经验模型”arXiv 预印本 arXiv:1812.06162 (2018)。
[4]拉德福德、亚历克等,“通过生成性预训练提高语言理解”(2018).
[5]拉德福德、亚历克等人,“语言模型是无人监督的多任务学习者。”
[6] Child,Rewon 等人,“用稀疏变换器生成长序列”arXiv 预印本 arXiv:1904.10509 (2019)。
[7] Raffel,Colin 等人,“用统一的文本到文本转换器探索迁移学习的极限”j .马赫。学习。第 21.140 (2020)号决议:第 1 至 67 段。
[8]刘,,等.“Roberta:一种稳健优化的 bert 预训练方法”arXiv 预印本 arXiv:1907.11692 (2019)。
[9] Devlin,Jacob 等《Bert:用于语言理解的深度双向转换器的预训练》arXiv 预印本 arXiv:1810.04805 (2018)。
[10]张,苏珊等:“开放预训练转换语言模型”arXiv 预印本 arXiv:2205.01068 (2022)。
PyTorch 中 LSTMs 的语言建模
原文:https://towardsdatascience.com/language-modeling-with-lstms-in-pytorch-381a26badcbf
“学习如何在这种类型中保护他们的感情,这本身就是一个自信的例子,”这位模特说。
在前三个故事中,我们从理论角度讨论了很多关于 RNNs 和 LSTMs 的内容。在这个故事中,我们将通过在 PyTorch 中使用 LSTMs 实现一个英语语言模型来弥合实践之间的差距。
什么是语言模型?
语言模型是已经学会估计记号序列的概率的模型。它可以使用这些事实来执行序列生成。也就是说,给定任何初始令牌集,它可以预测在所有可能的令牌中,最有可能跟随的是哪个令牌。这意味着,我们可以给模型一个单词,并让它生成一个完整的序列,方法是让它重复生成下一个单词,给出目前为止的前一个单词。
给定“这是”的初始表征,模型将以如下方式进行推理:

这张图片和所有其他图片都是作者的
注意,一旦模型产生一个特殊的句子结束标记
您可能已经猜到,语言模型的一个应用是自动完成。他们可以学习帮你写电子邮件、代码、莎士比亚的书,只要你训练他们。
我们如何建立一个语言模型?
任何语言模型都与一个词汇表相关联,它从这个词汇表中抽取标记。对于英语单词级语言建模,如果这样的词汇表有 100K 个单词,那么给定任何初始单词集,模型必须选择哪一个(在 100K 个中)来预测为下一个单词。
因此,我们可以通过使用具有分类头的 LSTM 网络来建立语言模型。也就是说,输出层应该是一个 Softmax,它为词汇表中的每个单词分配一个概率。然后,可以通过选择与最高概率相关联的预测来为下一个字选择最佳预测,或者更经常地,只是随机采样 Softmax 输出分布。
为了训练模型,我们所需要的是一大堆有许多序列的文本作为我们的数据集。假设“这个闻起来真香。”是其中的一个序列,那么模型可以按照下面的方式进行训练

在每个时间步中,我们查看预测的令牌是否确实是下一个令牌,并相应地计算损失。注意训练标签也是如何从语料库中获得的;对于语料库中任何长度为 T 的序列,前 T-1 个单词构成输入序列,后 T-1 个单词构成目标序列(标签)。在这个意义上,训练一个语言模型可以说是自我监督的。
现在您已经理解了什么是语言模型,让我们开始构建一个吧!我们将使用 WikiText-2 数据集,该数据集来自维基百科上的许多精彩文章。
我们来编码吧!
这将是我们的管道。我们将使用笔记本代码快照来回顾和解释其中的每个步骤。也可以笔记本格式在这里查看。

我们将使用 HuggingFace 中的 数据集库 来加载和映射数据集, Torchtext 来标记化数据集并构造词汇, PyTorch 来定义、训练和评估模型。 tqdm 的目的只是在培训和评估过程中显示进度条。
除了导入之外,我们还设置了一个设备变量,我们将在后面的函数中使用它来确保计算在 GPU 上进行(如果可能的话),我们还设置了一个种子值,这样我们就可以在需要的时候重现结果。
加载数据集

电池输出
为了加载数据集,我们使用来自数据集的 load_dataset() 函数。有两个 WikiText 数据集,一个旧版本:WikiText-103,一个新版本:WikiText-2。每一个都有一个原始版本和一个稍微预处理的版本。我们选择了新数据集的原始版本,因为稍后我们将自己处理预处理。
来自 load_datasets 的输出已经为我们拆分了训练、测试和验证集。要打印一个示例,我们首先选择三个集合中的一个,然后选择对应于该示例的行,然后选择我们要打印的特征(列)的名称。这里,数据集总是有一个“文本”列,对应于维基中的一段/一段文本。如果你试图改变索引,你可能会注意到有时没有段落,而是一个空字符串,所以我们将不得不在以后处理这个问题。
对数据集进行标记
下一步是标记数据集中的每个序列。为此,我们从第 1 行的 torchtext 中获取一个标记化器,然后定义一个函数,给出一个带有特征“text”的示例,返回一个带有特征“tokens”的示例,其中包含文本的标记化。
所以如果文本是“有意义的”然后函数会返回["it "," makes "," sense ",]. "]
我们没有实现自己的记号赋予器,因为像这样的内置记号赋予器通常是为处理特殊情况而精心设计的。
在第三行中,我们使用 datasets 库中的 map 函数对每个示例应用 tokenize_data 函数。map 需要将示例和标记器一起传递给 tokenize_data,因此我们也在 fn_kwargs 中传递标记器。此时,我们不再需要文本列,所以我们删除了它。
这一步非常重要,因为 LSTM/RNN 逐个符号地考虑序列。所以它必须被分解成我们可以迭代的记号。
构建词汇

电池输出
在第一行中,我们告诉 torchtext 将任何在数据集中出现至少三次的单词添加到词汇表中,因为否则它会太大(记住它的长度将是输出分类层中神经元的数量),并且有些单词很少出现。然后,我们手动添加一个
在第 4 行,我们还插入了一个
如单元格输出所示,词汇表长度约为 30K,我们注意到,通过打印词汇表中的前 10 个元素,词汇表中确实有
实现数据加载器
PyTorch 中的 dataloader 是一个给定数据集的函数,它为您提供了一种迭代批量数据集的方法。在批处理中,所有示例都是并行处理的。
这里我们定义一个做 4 件事的函数:
1-它给每个标记化文本序列附加一个
2-它将每个标记编码成一个数值,该数值等于它在词汇表中的索引。请注意,在数据集中出现少于三次的标记将映射到未知标记。(第 6 行)
3-它将所有的数字序列组合成一个列表(1D 张量)。(第 2 行,第 8 行)
4-它将其重新成形为维度为[批量大小,数量批次]的 2D 张量(第 10 行,第 11 行)
为了进一步澄清,假设数据集只包含来自 Wiki 的三段文本,它们是
你读得越多,你知道和理解的东西就越多
“好奇心是学习之烛的灯芯”
“最终事情开始变得有意义”
然后,该函数给定批量为 5,返回 2D 张量数据的形式为

但是有更多的列和数字而不是单词。我们一会儿会看到如何用这个来训练我们的模型。现在,让我们在训练集、测试集和验证集上应用这个函数。
定义模型

我们将要建立的模型将对应于上图。三个关键组件是嵌入层、 LSTM 层和分类层。我们已经知道了 LSTM 和分类层的用途。嵌入层的目的是将每个单词(作为索引给出)映射到一个 E 维的向量中,其他层可以从中学习。不确定向量或等效的单热点向量被认为是差的表示,因为它们假设单词彼此之间没有关系。这种映射也是在训练期间学习的。
作为正则化的一种形式,我们将在嵌入层、LSTM 层和输出层之前使用丢弃层。
关于上述模型的实现,有几点需要强调:
- *局 _ 砝码的争论。这样做的目的是使嵌入层与输出层共享权重。这有助于减少参数的数量,因为已经表明输出权重在某种意义上也学习单词嵌入。请注意,要做到这一点,隐藏层和嵌入层的大小必须相同。
- self.init_weights() 调用。我们将初始化权重,如本文中的所示。它们声明在范围[-0.1,0.1]内统一初始化嵌入权重,并且在范围[-1/sqrt(H),1/sqrt(H)]内统一初始化所有其他层。要将此应用于 LSTM,我们必须遍历其每一层,以初始化其隐藏到隐藏和隐藏到下一层的权重。
我们还实现了一个函数,将 LSTM 的隐藏和单元格状态设置为零。
最后,我们将在 LSTM 类下实现的最后一个函数是 detach_hidden
我们在训练时将需要这个函数来明确地告诉 PyTorch,由于不同的序列导致的隐藏状态是独立的。现在不要担心它。
超参数调整&模型初始化
请注意,我们将嵌入和隐藏维度设置为相同的值,因为我们将使用权重绑定。
在这里,我们初始化模型、优化器和损失标准。我们还计算出参数数量为 47M。
现在我们准备开始训练。但在此之前,让我们回过头来,记住我们的数据结构。
回想一下,我们有一个[batch _ size =128,num _ batches =16214张量,它看起来类似于

这里[批次大小=4,批次数量=24]
还记得 LSTM 将形状为[N,L,E]的张量作为输入,其中 N 是批量大小,L 是序列长度,E 是序列中每个元素的长度(嵌入长度)。因此,我们需要决定一个序列长度(L ),我们需要将数据集分成该序列的多个块,然后一个接一个地输入它们。如果我们决定对上表取 L=4,那么模型在 6 次迭代中对所有数据进行训练,因为每种颜色对应于“一批序列”,这是到模型的一次前馈传递。

是的,这意味着将输入模型的一些序列可能涉及原始数据集中不同序列的部分,或者是一个序列的子集(取决于序列长度 L)。由于这个原因,我们以后将只在每个时期重置隐藏状态,这就像假设下一批序列可能总是原始数据集中前一批序列的后续。
我们所做的被称为“通过时间窗口使用固定反向传播”,这只是处理我们不能有一批长度不等的序列的问题的方法之一。
现在,因为我们还没有执行将数据集分成“L 序列批次”的步骤,所以我们将定义一个函数,该函数在给定批次中第一批标记的索引的情况下返回相应的序列批次。
该函数采用[batch_size,num_batches]格式的数据集、序列长度和索引,并返回与 LSTM 的输入和目标相对应的序列批次。
培训&评估模型
在这里,第 4 行将模型设置为 training mode to train mode(因此没有禁用 dropout)。第 6 行到第 8 行确保数据集可以分解成长度为 seq_len (L)的批。
在 for 循环中,我们考虑了在非索引[0,seq_len,2seq_len,..]其中的每一个都将被提供给 get_batch 函数,因此它返回输入(src)和标签(trg)的相应批次序列,如第 16 行所示。两者都有维度【批量大小,序列长度】。*
在 for 循环的前两行中,我们将前一批的梯度归零,并分离其隐藏状态。
我们在第 20 行中得到的预测具有维度[批处理大小,序列长度,vocab 大小],我们将其整形为[批处理大小序列长度,vocab]* ,并将目标*坦化为[批处理大小序列长度]。*在这种情况下,损失函数需要目标和预测。
在第 25 行中,我们计算网络中的梯度,然后剪切所有超过“剪切”的梯度,以避开爆炸梯度。在第 27 行我们更新了权重,在第 28 行我们计算了损失。Loss.item()将总损失除以 batch_size 和 sequence_length,再乘以 seq_len,这样我们最终可以计算每个序列(而不是每个令牌)的*均损失。
如果你在 Google Colab 上得到一个 Nvidia T4,在上面的超参数设置下,训练需要大约 2 个半小时。
评估循环类似于训练循环,只是我们不再需要反向投影或跟踪梯度。
现在我们需要调用这两个函数
在这里,我们使用 ReduceLROnPlateu,在没有改进的每个时期之后,将学习率降低 2 倍。在这种情况下,我们很可能在最小值附*振荡,所以是时候减速了。
我们还保存具有最高验证损失的模型,并返回困惑度,该困惑度是损失的增函数,用于度量模型的置信度。
推论
这是我们管道的最后一步!
在这里,我们将提示、标记化、编码并输入到模型中以获得预测(这是逻辑,记住 Softmax 应用于损失函数)。因此,我们随后应用 Softmax,同时指定我们希望得到序列中最后一个单词的输出,该输出表示对下一个单词的预测。
我们将对数除以温度值,通过调整 Softmax 概率分布来改变模型的置信度。我建议检查一下这个,了解更多关于效果的信息。
一旦我们有了 Softmax 分布,我们就对它进行随机采样,以预测下一个单词。如果我们得到了
一旦我们得到
我们将预测解码回第 24 行和第 25 行的字符串。

输出
就是这样!恭喜你完成了故事的结尾。在你离开之前,让我们回顾一下这个故事的要点
- 语言模型只是一个带有分类头的 RNN,它被训练来预测大型文本语料库中的下一个标记(通常是标记)。
- 一旦经过训练,该模型就能感知不同序列的概率。因此,给定初始记号集,模型可以生成后续记号,以便最终的序列是有意义的。
- 给定一个大的文本语料库,在我们开始训练之前,我们需要对它进行标记、编码并将其置于正确的形状。
- 剩下的部分来自于你在 PyTorch 上实现深度学习模型的普通经验
这将使我们的故事结束。如果你发现这很有用,那么记住你可以通过留下一些掌声来帮助它到达其他人那里。下次见,再见。
参考书目:https://github.com/bentrevett/pytorch-language-modeling
数据集:“wiki text 2.0,指针哨兵混合模型,作者:Stephen Merity 和 Xiong 以及 James Bradbury 和 Richard Socher,根据知识共享署名-共享许可协议(CC BY-SA 4.0)提供”
语言模型:GPT 和 GPT-2
原文:https://towardsdatascience.com/language-models-gpt-and-gpt-2-8bdb9867c50a
小型语言模型如何激发现代突破

(图片由JESHOOTS.COM在 Unsplash 上拍摄)
像 GPT-3 [7]这样的语言模型已经彻底改变了 NLP 的现代深度学习应用,导致了广泛的宣传和认可。然而,有趣的是,GPT-3 的大部分技术创新都是从它的前辈 GPT 和 GPT-2 继承来的。因此,对 GPT 和新 GPT 协议的有效理解有助于更好地理解当前的自然语言处理方法。
GPT 和 GPT-2 模型探索的基本方法很简单。事实上,它可以归结为几个步骤:
- 使用大量原始文本数据预先训练语言模型
- 采用这种预先训练的模型来解决下游任务
不过描述的有点模糊。语言模型的预训练是如何工作的?我们如何“适应”语言模型来解决不同的任务?
在这个概述中,我们将建立对语言建模的基本理解,它在 GPT 和 GPT-2 中的使用,以及除了生成连贯的文本之外,它如何被用来解决问题。尽管 GPT 和 GPT-2 由于最*提出了更大、更有能力的模型而有些过时,但它们所基于的基本概念仍然与现代深度学习应用高度相关。让我们仔细看看。

预先训练的语言模型可用于解决各种下游任务(由作者创建)
GPT 的先决条件
GPT 和 GPT-2 背后的基本直觉是使用通用的、预训练的语言模型来高精度地解决各种语言建模任务。为了充分理解这种方法,我们必须首先涵盖一些关于语言模型如何工作的基本概念,以及它们如何在 GPT 协议和新 GPT 协议中得到利用。
语言建模

语言模型预培训(由作者创建)
使用语言建模目标,在未标记文本数据的语料库/数据集上预先训练 GPT 模型。简单来说,这意味着我们通过 (i) 从数据集中采样一些文本,以及 (ii) 训练模型来预测下一个单词,来训练模型;见上图。这种预训练程序是一种自我监督学习的形式,因为正确的“下一个”单词可以通过简单地查看数据集中的下一个单词来确定。
数学中的语言建模。要理解语言建模,我们只需要掌握上面概述的基本思想。然而,为了更严谨一点,我们可以注意到我们的语料库只是一组标记。我们可以将标记视为数据集中的单个单词,但这并不完全正确。现实中,令牌可能是子词,甚至是字符;更多详情请参见此处的和。
让我们将组成我们的预训练数据集的这组记号(大小为N)表示如下。

我们的未标记文本语料库只是一组有序的标记(由作者创建)
给定具有参数θ的深度学习模型,语言建模目标试图最大化如下所示的可能性。

文本语料库中的语言建模损失
简而言之,这个表达式描述了给定k前一个标记作为上下文,模型预测正确的下一个标记的概率。对于任何可能难以理解这一公式的人,请随意查看下面的帮助链接。
使用语言建模损失(这只是表征了我们的模型准确预测序列中下一个标记的能力!),我们可以按照以下程序预训练我们模型的参数θ,以使损失最小化:
- 来自预训练语料库的样本文本
- 用我们的模型预测下一个令牌
- 使用随机梯度下降(SGD)或任何其他优化器来增加正确的下一个令牌的概率
通过多次重复这种(自我监督的)训练过程,我们的模型最终将变得真正擅长语言建模(即预测序列中的下一个令牌)。
什么是语言模型?使用这种自我监督语言建模目标预训练的模型通常被称为语言模型(LMs)。随着规模的扩大,LMs 变得更加有效(例如,更多的层、参数等。).因此,我们将经常看到这些模型的更大版本(例如,GPT-3 [7]),它们被称为大型语言模型(LLM)。
LMs 为什么有用? LMs 可以通过迭代预测最可能的下一个令牌来生成连贯的文本,这使得从文本自动完成到聊天机器人的一系列应用成为可能。然而,除了它们的生成能力之外,NLP 中的先前工作已经表明 LM 预训练对于各种任务是令人难以置信地有益的;例如,预训练的单词嵌入在下游任务中是有用的[3,4],LM 预训练提高了lstm【5】的性能。
超越这种方法,GPT 模型探索语言模型预训练与变压器[6]。与顺序模型(如 LSTM)相比,变形金刚具有令人难以置信的表现力(如高表现能力、多参数等)。)和 (ii) 更适合现代 GPU 并行计算的能力,允许用更大的模型和更多的数据执行 LM 预训练。这种可伸缩性使得 LLM 的开发成为可能,它彻底改变了 NLP 应用程序。
仅限解码器的变压器
GPT 和 GPT-2 都使用只有解码器的变压器结构。我之前已经总结了这个架构,但是为了完整起见,我将在这里提供一个快速概述。要了解更多关于 transformer 架构的信息,我建议简要阅读这里的解释。
转换器架构有两个主要组件:编码器和解码器。

(摘自[6])
纯解码器架构从变压器中移除了以下组件:
- 整个编码器模块
- 解码器中的所有编码器-解码器自关注模块
去除这些组件后,解码器的每一层都简单地由一个掩蔽的自我注意层和一个前馈神经网络组成。将几层这样的层相互堆叠起来,就形成了一个深层的、只有解码器的变压器架构,比如那些用于 GPT 或 GPT-2 的架构;见下文。

仅限解码器的变压器架构(由作者创建)
为什么是解码器?对 LMs 使用解码器架构(相对于编码器)的选择不是随意的。解码器中被屏蔽的自我关注层确保模型在制作令牌的表示时不能在序列中向前看。相比之下,双向自我关注(如编码器中所用)允许基于序列中的所有其他标记来调整每个标记的表示。点击这里阅读更多关于自我关注的内容。
语言建模需要掩蔽的自我注意,因为我们不应该在预测下一个标记时在句子中向前看。使用被屏蔽的自我关注产生自回归架构(即,意味着模型在时间t的输出被用作时间t+1的输入),其可以连续预测序列中的下一个令牌;见下文。

仅解码器转换器架构的自回归输出(由作者创建)
对于不需要掩饰自我注意力的任务(如句子分类、标注等。),但是,我们要记住,使用双向自我关注才是真正有益的;更多详情见此处。
创建基础模型
既然我们已经对语言建模和相关架构有了基本的了解,我们就可以理解 GPT LMs 背后的灵感了,这从下面的观察开始:
- 未标注文本语料库非常丰富
- 有标签的数据很少
对于大多数深度学习系统来说,需要大量的标记数据来执行区分性语言理解任务。目前的深度学习系统都是狭义的专家。该模型只是在一个大型的监督数据集上进行训练,以便它学习准确地执行特定的任务;见下文。

大多数深度学习模型使用监督(或标记)数据集来学习如何准确执行单个特定任务(由作者创建)
尽管这种方法很常用,但它有一些主要的局限性:
- 一些域没有太多的标记数据
- 我们必须为我们想要解决的每个任务训练一个新的模型(而训练深度学习模型是昂贵的!)
基金会模特。 GPT 和 GPT-2 在深度学习领域摆脱了狭隘的专家范式。我们可以预先训练一个 LM,然后以某种方式调整这个模型来解决大量的任务,而不是为每个应用程序训练一个新的模型。用于解决许多任务的通用模型被称为基础模型。
这种方法通过对大量不同的数据集进行预训练,缓解了数据匮乏的问题。此外,这些模型可以被重用或修改来解决其他任务,使我们避免不断地训练新模型。一种使基础模型适应下游任务的方法是对监督数据集进行微调(即更多的训练)。然而,最*的主要方法是通过零或少量推理。
通过提示进行零/少击推理。GPT 模型接收文本作为输入,产生文本作为输出。我们可以通过提供如下输入来利用这种通用的输入输出结构:
- "把这句话翻译成英语:
<sentence> =>" - “总结以下文档:
<document> =>”。
这些解决任务的“提示”允许使用 LMs 进行零触发(即,没有看到正确输出的例子)推理。根据这些提示,LM 最合适的输出应该能够解决任务(例如,翻译成英语或总结一个文档)!为了执行少量推理,我们可以在开始时用正确输出的例子构建一个类似的提示;见下文。

使用 LMs 的零、一和少量推理(来自[7])
出版物
我们现在将概述 GPT 和 GPT-2 的细节。这些模型由 OpenAI 的研究人员发表,率先使用通用 LMs 来解决下游任务。他们为 GPT-3 这样的突破性进展奠定了基础。这些模型之间的主要区别仅仅在于底层 LM 的大小。
通过生成性预训练提高语言理解 (GPT) [1]
GPT 是一个通用语言理解模型,分两个阶段训练:预训练和微调。

GPT 建筑(摘自[1])
GPT 使用一个 12 层的,只有解码器的变压器架构,匹配原来的变压器解码器6;见上图。GPT 首先在 BooksCorpus 数据集上执行语言模型预训练,然后在各种有区别的语言理解任务上单独微调(以监督的方式)。

(来自[1])
我们没有修改 GPT 的架构来解决不同的任务,而是在特定于任务的结构中提供输入,然后将模型的输出传递到单独的分类层。例如,在蕴涵任务中,我们连接输入的句子,用一个特殊的分隔符将它们分开,将这个输入提供给 GPT,然后将 GPT 的输出传递给一个单独的分类层。在[1]的第 3.3 节中进一步解释了用不同的监督任务微调 GPT,并在上面进行了说明。

(来自[1])
GPT 在上面列出的各种各样的任务上被评估。作者发现,在具有长跨度连续文本(而不是单独的、混乱的句子)的语料库上对 GPT 进行预训练是必不可少的(这一发现也得到了最*工作的验证[9])。在整个实验设置中,我们看到 GPT 在 12 个任务中的 9 个任务上取得了最先进的表现,甚至持续优于模型集合;见下文。

(来自[1])
从这些实验中,我们了解到通用 LMs 相对较好地理解语言概念,并且能够学习复杂的模式(例如,长期依赖、语言歧义等)。)在文本数据中。在不使用任何特定于任务的架构或修改的情况下,GPT 远远超过了许多基线,包括许多用于解决单个任务的专用解决方案。
语言模型是无人监督的多任务学习者(GPT-2)【2】
GPT-2 的提案[2]遵循了与其前身相似的模式。该模型使用语言建模目标进行预训练,但它不执行微调,而是选择以零触发方式解决下游任务。简而言之,GPT-2 通过以下方式执行多任务学习:
- 在原始文本数据上预先训练通用 LM
- 使用文本“提示”在各种任务中进行零距离推理
预训练是在一个定制的 WebText 数据集上进行的,该数据集是通过从 Reddit 抓取流行链接构建的,并且测试了四种不同大小的 LMs。最小的模型符合 GPT [1]的大小,最大的模型是 GPT-2;见下文。

(来自[2])
除了一些小的差异(例如,不同的权重初始化、更大的词汇、更长的输入序列等),模型架构与 GPT 相同。).尽管这些 LMs 很大,但在预训练期间,它们被发现不足以适应 WebText 数据集,这表明较大的 LMs 会表现得更好。
GPT-2 在几项任务上进行评估(即,语言建模、问题回答、翻译等。),在那里它实现了有希望的(但不总是最先进的)结果。例如,在下表中,我们看到 GPT-2 在语言建模和阅读理解任务中表现良好,但在摘要和问题回答方面远远低于基线。

(来自[2])
即使性能不是很好,我们需要记住 GPT-2 没有执行任何微调来解决这些任务。所有这些结果都是通过零射击推理获得的,这使得 GPT 在某些任务上的竞争表现令人印象深刻。
有趣的是,零炮性能随着基础 LM 的大小而持续提高,这表明增加 LM 的大小/容量提高了其在预训练期间学习相关特征的能力;见下文。

(来自[2])
预训练和微调是一种有效的迁移学习范式,但 GPT-2 向我们展示了更容易、更通用的迁移方法。假设它们在足够大的语料库上被预先训练,LMs 似乎能够学习下游任务,即使没有任何架构或参数修改。虽然 GPT-2 的性能并不令人印象深刻,但作者指出,更大的 LMs 会好得多。
“……具有足够能力的语言模型将开始学习推断和执行自然语言序列中展示的任务,以便更好地预测它们,而不管它们的采购方法如何。”-来自[2]
外卖食品
GPT 和 GPT-2 教会了我们很多关于深度学习的知识。虽然从准确性的角度来看,它们在下游任务中的有效性并不令人难以置信,但它们让人们看到了 LMs 作为基础模型的惊人潜力,并为 GPT-3 等 LLM 的出现奠定了方法论基础。这些模型的影响是深远的,但我试图从下面对 GPT 和 GPT-2 的研究中总结一些最有用的观点和想法。
语言模型前期训练很棒。变压器,由于其对计算的有效利用,使得语言模型预训练能够在大规模上执行。在这个预训练过程中学习的表示允许预训练的 LMs 很好地推广到解决其他任务。简而言之, LMs 不仅仅擅长语言建模——他们还能解决其他任务!
尺寸很重要。正如我们在从 GPT 到 GPT-2 的过渡中看到的,增加预先训练的 LM 的大小增加了学习表征的质量;例如,GPT-2 在零炮/少炮推断方面远远优于 GPT。在(更大的)GPT-3 模型发布后,这种趋势变得更加明显[7]。
我们应该利用基础模型。大多数深度学习模型都被训练成完成单一的、狭窄的任务。然而,在许多情况下,我们可以受益于 (i) 通过对未标记数据的自我监督学习来预训练更大的模型,以及 (ii) 调整该模型来解决许多任务。这种大型基础模型的再利用在计算上是高效的(即,计算在许多任务之间共享),并且不是特定于 LMs 的。我们也可以为计算机视觉等领域训练基础模型[8]!
代码和资源
对于那些有兴趣尝试 GPT-2 应用程序的人来说,代码是公开的!然而,预先训练这样的模型在计算上是相当昂贵的。更好的方法是下载一个预先训练的语言模型,然后对其进行微调或者执行零/少量推理(例如,通过使用这里的演示)。
结论
非常感谢你阅读这篇文章。如果你喜欢它,请在 twitter 上关注我,或者订阅我的深度(学习)焦点时事通讯,在那里我挑选了一个关于深度学习研究的单个双周主题,提供了对相关背景信息的理解,然后概述了关于该主题的一些流行论文。我是 Cameron R. Wolfe ,Alegion 的研究科学家,莱斯大学的博士生,研究深度学习的经验和理论基础。也可以看看我在 medium 上的其他著述!
文献学
[1]拉德福德、亚历克等,“通过生成性预训练提高语言理解”(2018).
[2]拉德福德、亚历克等人,“语言模型是无人监督的多任务学习者。”
[3]潘宁顿、杰弗里、理查德·索赫尔和克里斯托弗·曼宁。"手套:单词表示的全局向量."2014 年自然语言处理经验方法会议录(EMNLP)。2014.
[4] Conneau,Alexis 等人,“从自然语言推理数据中监督学习通用句子表示”arXiv 预印本 arXiv:1705.02364 (2017)。
[5]霍华德、杰里米和塞巴斯蒂安·鲁德。“用于文本分类的通用语言模型微调。”arXiv 预印本 arXiv:1801.06146 (2018)。
[6]瓦斯瓦尼、阿希什等人,“你所需要的只是关注。”神经信息处理系统进展 30 (2017)。
[7]布朗、汤姆等人,“语言模型是一次性学习者。”神经信息处理系统进展 33(2020):1877–1901。
[8]袁,陆等.“佛罗伦萨:计算机视觉的新基础模型”arXiv 预印本 arXiv:2111.11432 (2021)。
[9] Krishna,Kundan 等人,“下游数据集是令人惊讶的好的预训练语料库。”arXiv 预印本 arXiv:2209.14389 (2022)。
使用 Python 进行语言翻译
原文:https://towardsdatascience.com/language-translation-using-python-bd8020772ccc
如何使用大量语言翻译 API 翻译一组散文和/或 XML 页面。

我们经常面临一个问题,要么我们无法理解一段重要的文本,要么我们需要向说另一种语言的人提供信息。虽然在线翻译工具是可用的,但并不总是可以访问它们,或者为我们的客户提供一个静态翻译页面可能更好。
在本教程中,我们将了解不同的翻译 API,如何在 Python 中使用它们,以及如何使用 BeutifulSoup 来翻译 HTML 网页或 XML 文档中的文本。
安装库
为了完成我们的翻译,我们使用了translate-api python 库。这为许多可用的翻译 API 提供了一个有效的接口。
pip install translators --upgrade
选择翻译者
我们从选择一名翻译开始。虽然谷歌翻译经常是我们的默认选择,但也有一些更精确的选择:

来源:https://pypi.org/project/translate-api/
值得注意的是,更具体的翻译者也支持他们翻译的专业领域——这确保了翻译与从特定领域内的文档(语料库)中导出的翻译相匹配。
下面给出了两个示例,并在指定查询时使用“professional_field”参数提供。
baidu: ('common','medicine','electronics','mechanics')caiyun: ("medicine","law","machinery")
构建查询
现在,我们已经选择了一个翻译 API,我们加载我们的脚本,并解决如下。
import translators as ts
ts.<api_name>(..) # e.g. ts.google
选择语言
有几种语言可供我们互译。要检查我们是否对这两个感兴趣,我们可以选择我们的翻译器并运行以下命令:
注意译者姓名前的下划线!
**ts._<translator_name>.language_map** # e.g. ts._google.language_map
这为我们提供了每种包含的语言的键的字典,以及表示它们可能被翻译成什么的值。
对于 Google,我们也可以查看下面的语言支持页面:
https://cloud.google.com/translate/docs/languages
查看选项
如果我们决定使用 Google API 进行翻译,我们可以使用 help 函数:help(ts.google)来决定我们需要哪些参数。下面列出了比较常见的几种。
- query_text :这是我们正在翻译的默认字符串
- from_language :默认为“自动”,使用 apo“猜测”语言
- to_language :默认情况下,所有翻译都是英语(
en) - 超时:在它放弃之前我们想要等待的时间。默认值为无(未指定)
- sleep_seconds :如果执行多个请求,可能值得将这些请求隔开,以防止淹没服务器并导致它们被拒绝。
翻译一个短语
我们从常见的测试短语“狐狸跳过懒狗”开始,并将其翻译成威尔士语(cy):
phrase = 'The quick brown fox jumps over the lazy dog.'ts.google(phrase, from_language='en', to_language='cy')
这将返回:" maer llwynog brown cy flym yn neid io dros y ci diog。"
由于我不会说威尔士语,我可以通过翻译来测试译文的情感:
phrase = "Mae'r llwynog brown cyflym yn neidio dros y ci diog."ts.google(phrase)
我们得到一个与原字符串非常相似的翻译:“快速的棕色狐狸跳过懒惰的狗。”
注: 很像汉语中的悄悄话,有某些语言的细微差别,在翻译中往往会丢失。这意味着我们不太可能得到准确的原始短语。
批量翻译
对于多个段落,我们可以将数据分组,并使用列表理解来遍历它们。这对于大型文档很有用,因为 google 有 5000 个字符的限制。
举个例子,我们从道格拉斯·亚当的《银河系漫游指南》中选了几行:
corpus = ["On display? I eventually had to go down to the cellar to find them.","That's the display department.","With a flashlight.","Ah, well the lights had probably gone.","So had the stairs.","But look, you found the notice didn't you?","Yes, said Arthur, yes I did. It was on display in the bottom of a locked filing cabinet stuck in a disused lavatory with a sign on the door saying 'Beware of the Leopard'"]
我们现在可以依次翻译它们,使用:
welsh = [ts.google(phrase, from_language='en', to_language='cy') for phrase in corpus]
翻译数据帧
如果我们的信息包含在 pandas 数据框中,我们选择一个单独的列并对其应用翻译功能:
import pandas as pd
df = pd.DataFrame(corpus, columns = ['text'])df['welsh_text'] = df['text'].apply(**lambda** x: ts.google(x, from_language='en', to_language='cy'))

以上命令的输出示例。
页面翻译:HTML / XML
通常,这不仅仅是我们需要翻译一份文本文档那么简单。例如,我们网站上的信息可能需要提供给其他国家或团体。对于这种情况,我们可以使用 BeutifulSoup 提取我们的元素,并在原位替换它们。
装置
为此,我们安装了 Beautiful Soup 4:
pip install bs4
https://beautiful-soup-4.readthedocs.io/en/latest/
HTML 模板示例
使用一些早期的翻译,我们随机分配一组元素威尔士语翻译从希区柯克指南摘录。
html = '''<html><head><title>That is the display section.</title></head><body><p>So got the stairs.</p><p>Yes, said Arthur, yes I did. It was displayed at the bottom of the locked filing cabinet stuck in a dormant toilet with a sign on the door saying 'Be wary of the leopard'</p></body></html>'''
编写美丽的汤
我们首先决定哪些元素包含我们的文本。在这个例子中,只有段落和标题字段,但是,它们可以用属性或类来标记。
然后我们读取代码(我们的汤),提取这些元素,使用Navigatable String属性并应用一个适当的replace_with来替换我们的翻译。
**from** bs4 **import** BeautifulSoup
soup = BeautifulSoup(**html**, 'html.parser')**elements** = ['p','title']for i in soup.findAll(elements):
i.string**.replace_with**(*ts.google(i.string,from_language='cy', to_language='en')*)print(soup)
运行它将返回翻译了p和title元素的原始文档:
<html><head><title>That is the display section.</title></head><body><p>So got the stairs.</p><p>Yes, said Arthur, yes I did. It was displayed at the bottom of the locked filing cabinet stuck in a dormant toilet with a sign on the door saying 'Be wary of the leopard'</p></body></html>
结论
在本教程中,我们介绍了通过 python 使用几种语言翻译 API,以及如何翻译数据框中的文本。最后,我们看了如何使用 Beautiful Soup 从 XML 模板中提取文本并生成翻译。希望本教程有助于使文本更容易理解。
伯特的语言理解
原文:https://towardsdatascience.com/language-understanding-with-bert-c17a453ada1a
最有用的深度学习模型

BERT 模型及其训练过程的示意图(来自[1])
伯特是什么?
来自变压器的双向编码器表示(BERT) [1]是一种流行的深度学习模型,用于许多不同的语言理解任务。BERT 与 transformer 编码器共享相同的架构,并在被微调以解决下游任务(例如,问题回答、句子分类、命名实体识别等)之前,使用自我监督的学习目标对原始的、未标记的文本数据进行广泛的预训练。).在提出这个建议的时候,BERT 在 11 个不同的语言理解任务上获得了一个新的最先进的技术,促使其几乎立即成名,并一直持续到现在。
BERT 令人难以置信的有效性源于:
- 通过自我监督学习对大量原始文本数据进行预训练
- 为序列中的每个标记制作丰富的双向特征表示
尽管以前的工作表明语言建模任务受益于大型文本语料库的预训练,但 BERT 通过制作一套简单而有效的自我监督预训练任务扩展了这一想法,使相关特征能够被学习。此外,BERT 摆脱了使用单向自我关注的常见做法,这种做法通常被用来在这种语言理解任务中进行语言建模式的预训练。相反,BERT 在其每一层中都利用了双向自我注意,揭示了双向预训练对于实现强大的语言表达至关重要。
伯特很有用。在这一点上,你可能想知道:为什么你要把整个帖子都放在这个模特身上?简单的答案是 BERT 非常通用,这种单一模型架构可用于解决数量惊人的不同任务,具有最先进的准确性,包括标记级(如命名实体识别)和句子级(如情感分类)语言理解任务。此外,它的使用已经扩展到自然语言处理(NLP)领域之外,以解决多模态分类[2]、语义分割[3]等问题。
声称一个单一的深度学习模型是“最有用的”有点夸张(尽管它是一个很好的缩略图!).然而,对于任何深度学习实践者来说,BERT 无疑是最重要的工具之一。简而言之,这种架构只需对特定任务进行最少的修改,就可以下载(即,预先训练的参数可以在线获得)并以较低的计算成本进行微调,以解决 NLP 及其他领域的一系列潜在问题它是深度学习的“瑞士军刀”!
伯特的积木
在概述 BERT 体系结构的细节之前,理解 BERT 所基于的核心组件和思想是很重要的。这些主要概念可以归结为以下几点:
- (双向)自我关注
- 变压器编码器
- 自我监督学习
自我关注

令牌嵌入序列的不同表示(由作者创建)
我已经在以前的帖子中概述了自我关注背后的基本思想,但是我将在这里提供另一个更具体的关于这个概念的讨论。在高层次上,自我关注是一种非线性转换,它将一系列“标记”(即,序列中的单个元素)作为输入,每个标记都表示为一个向量。与该输入序列相关的矩阵如上所述。然后,这些记号表示被转换,返回一个新的记号表示矩阵。
在这种转变中会发生什么? 对于每一个单独的记号向量,自我注意做了以下事情:
- 将该标记与序列中的所有其他标记进行比较
- 为每一对计算关注分数
- 根据序列中的其他标记调整当前标记的表示,并通过关注分数进行加权
直观上,自我注意只是根据序列中的其他标记来调整每个标记的向量表示,形成一个更具上下文感知的表示;见下文。

双向自我关注根据每个标记与序列中所有其他标记的关系来调整每个标记的表示(由作者创建)
多个注意头。自我关注通常以多头方式实现,其中多个自我关注模块在将它们的输出连接起来之前被并行应用。自我注意的机制在每个单独的注意头中仍然是相同的,尽管在应用自我注意之前,标记向量将被线性投影到更低的维度(以避免计算成本的过度增加)。
这种多头方法的好处在于,多头注意力层中的每个注意力头可以学习基础序列中的不同注意力模式。因此,该模型不会受到可以在单个自我关注层内“关注”的其他表征的数量的瓶颈或限制。相反,不同的头部中的每一个都可以捕获不同的令牌关系模式。
单向与双向。当制作序列中每个标记的上下文感知表示时,在定义该上下文时有两个基本选项:
- 考虑所有令牌
- 考虑当前标记左边的所有标记
如下图所示,这些选项产生了两种不同的自我关注:双向和单向。

单向与双向自我关注(来自[1])
单向自我注意确保每个标记的表示只依赖于序列中在它之前的那些标记(即,通过在自我注意期间仅仅“屏蔽”序列中后来的标记)。对于像语言建模这样的应用程序来说,这种修改是必需的,因为语言建模不允许在序列中“向前看”来预测下一个单词。
相比之下,双向自我注意基于序列中的所有其他表征来制作每个表征。这种自我关注的双向变化是 BERT 成功的关键,因为许多以前的建模方法:
- 利用单向自我关注[4]
- 通过在向前和向后两个方向上串联句子的单向表示,精心制作了浅的双向特征[5]
这些方法远不如 BERT 使用的双向自我注意有效,双向自我注意强调双向特征表示在语言建模以外的任务中的好处。
变压器编码器
变压器架构[6]通常有两个组件——编码器和解码器。然而,BERT 只使用转换器的编码器组件,如下所示。

变压器编码器(来自[6])
可以看出,transformer encoder 只是几个具有(双向、多头)自关注和前馈变换的重复层,每个层之后是层归一化和剩余连接。够简单!
为什么只有编码器?你可以看我的上一篇文章来更深入地讨论变压器,但是变压器架构的两个组件倾向于服务于不同的目的。
- 编码器:利用双向自我注意将原始输入序列编码成一系列有区别的标记特征。
- 解码器:获取丰富的编码表示,并将其解码为新的所需序列(例如,将原始序列翻译成另一种语言)。
在 BERT 的例子中,我们将在本文的剩余部分看到这种解码过程是不必要的。BERT 的目的是简单地制作这种初始的编码表示,然后可以用于解决不同的下游任务。
自我监督学习

BERT 的自我监督预培训(来自[1])
伯特令人难以置信的表现的一个关键因素是它能够以自我监督的方式进行预先训练。在高层次上,这样的训练是有价值的,因为它可以在原始的、未标记的文本上执行。因为这种类型的数据可以在网上广泛获得(例如,通过在线图书储存库或维基百科等网站),所以可以收集大量文本数据的语料库进行预训练,使 BERT 能够从比大多数监督/标记数据集大几个数量级的多样化数据集进行学习。
虽然存在许多自我监督培训目标的例子,但我们将在本帖中进一步概述的一些例子包括:
- 屏蔽语言建模(MLM): 屏蔽/删除句子中的某些单词,并尝试预测它们。
- 下一句预测(NSP): 给定一对句子,预测这些句子在文本语料库中是否前后连贯。
这些任务都不需要任何人工注释。相反,它们可以用未标记的文本数据来执行。
这是无监督学习吗?这里值得区分的一点是自监督学习和无监督学习的区别。无监督和自我监督学习都不利用标记数据。然而,虽然无监督学习专注于发现和利用数据本身中的潜在模式,但自监督学习却发现一些已经存在于数据中的监督训练信号,并将其用于训练,因此不需要人工干预。这种区别是微妙的,但(幸运的是)T4·伊桑·米斯拉在莱克斯·弗里德曼的播客 T7 中对此进行了精彩的描述和解释。点击查看时间戳。
伯特实际上是如何工作的…
尽管到目前为止,我们已经概述了 BERT 背后的一些基本思想,但在这一节中,我将更详细地描述 BERT,重点放在它的架构和训练方案上。
伯特的建筑
如前所述,BERT 的架构只是转换器模型[6]的编码器部分(即,仅编码器转换器架构)。在最初的出版物中,提出了两种不同大小的 BERT(尽管现在有更多的 BERT 存在):
- BERT 基: 12 层,768 维隐藏表征,每个自我关注模块中 12 个关注头,110M 参数。
- BERT Large: 24 层,1024 维隐藏表征,每个自我注意模块中 16 个注意头,340M 参数。
值得注意的是,BERT Base 与 OpenAI 的 GPT [7]大小相同,这使得模型之间可以进行公*的比较。
为什么会这样不一样?BERT 和之前提出的语言理解模型(如 OpenAI GPT)的主要区别在于双向自我注意的使用。以前的工作,尽管利用了自我监督的预训练,但只使用单向的自我注意,这严重限制了模型可以学习的表征的质量。
“凭直觉,有理由相信深度双向模型比从左到右模型或从左到右和从右到左模型的浅层连接更强大。”—来自[1]
构建输入序列。直觉上,伯特把一些文本序列作为输入。特别是,这个序列通常是一个单句或一对两个,连续的句子。

将原始文本转换为 BERT 中的一系列嵌入标记(由作者创建)
这个高层次的想法很简单,但是您可能想知道:我们如何从原始文本中得到 BERT 兼容的输入序列?这个过程可以分为几个步骤:
- 标记化:原始文本数据被分解成代表单词或单词部分的单个标记或元素。
- 插入“特殊”记号: BERT 的输入序列以一个
[CLS]记号开始,以一个[SEP]记号结束,表示一个句子的开始/结束。如果使用两个连续的句子,另一个[SEP]符号被放置在它们之间。 - 嵌入:将每一个 token 转换成其对应的 WordPiece [8]嵌入向量。
- 附加嵌入:输入数据现在是一个向量序列。可学习的嵌入被添加到该序列中的每个元素,表示该元素在序列中的位置以及它是第一句还是第二句的一部分。这样的信息是需要的,因为自我注意不能区分一个元素在序列中的位置。
按照这些步骤,原始文本数据被转换成可以被 BERT 接收的向量序列。上图描述了标记化、插入特殊标记和嵌入过程,而下图描述了附加嵌入过程。

(来自[1])
BERT 的嵌入过程在文章中有很好的解释,但是我在这里推荐文章以获得更多关于标记化的细节。
训练伯特
BERT 的培训过程分两步进行:
- 预培训
- 微调
这些步骤之间的架构几乎相同,尽管可以使用一些小型的特定任务模块(例如,MLM 和 NSP 都使用单个附加分类层)。
前期培训。在预训练过程中,使用两种不同的任务在未标记的数据上训练 BERT 模型:MLM(也称为完形填空任务[9])和 NSP。值得注意的是,BERT 不能用先前工作中使用的典型的语言建模目标来训练,在先前的工作中,模型迭代地试图预测序列中的下一个单词。双向自我注意的使用将允许伯特通过简单地观察和复制下一个记号来作弊。

NSP 自我监督的伯特训练前任务(由作者创建)
如上图所示,NSP 任务非常简单。来自预训练语料库的连续句子被传递到 BERT 中(即,句子 A 和 B),并且 50%的时间第二个句子被另一个随机句子替换。然后,[CLS]标记的最终表示在经过 BERT 处理后,通过一个分类模块,该模块预测输入的句子是否是实际的匹配。

MLM 自我监督的伯特训练前任务(由作者创建)
上面描述的 MLM 不像 NSP 那样是一个序列级的任务。它随机屏蔽输入序列中 15%的标记,用特殊的[MASK]标记替换它们。然后,这些[MASK]记号中的每一个的最终表示通过分类层来预测被屏蔽的单词。然而,作者不是总是以这种方式屏蔽标记,而是在 80%的时间里用[MASK]替换每个标记,在 10%的时间里用随机标记替换,在 10%的时间里用原始标记替换。实现这样的修改是为了避免[MASK]令牌出现在预训练中而不是微调中的问题。
使用这些任务,BERT 在由书籍语料库和英语维基百科组成的语料库上接受预训练。有趣的是,使用文档级语料库(与混排句子的语料库相反)有助于预训练的质量。你的语料库需要句子之间的长期依赖关系,以便 BERT 学习最好的可能特征。这一有趣的发现也为后来的工作所证实。事实上,即使根据 TF-IDF 分数对随机打乱的句子进行重新排序,以形成合成的长期依赖关系,也能提高预训练质量[10]。

(来自[1])
微调。BERT 中的自我关注机制的构建使得对不同种类的下游任务建模尽可能简单。在大多数情况下,只需将任务的输入输出结构与 BERT 的输入输出结构相匹配,然后对所有模型参数进行微调。下图中也描述了这方面的一些例子,包括:
- 标记级任务:正常地处理序列,然后将每个标记的输出表示通过一个单独的模块来执行给定标记的预测。
- 语句/文档级任务:正常处理您的序列,然后将
[CLS]标记的输出表示(输入序列的聚合嵌入)通过一个单独的模块进行序列级预测。 - 文本对任务:在 BERT 的输入结构中将文本对的每个部分编码为“句子 A”和“句子 B”,然后将
[CLS]令牌的输出表示通过一个单独的模块,以基于您的文本对执行预测。
上面列出的一般任务结构应该证明 BERT 是一个通用的模型。许多不同的任务可以通过简单地将它们映射到 BERT 的输入-输出结构来解决,并且相对于预训练需要最小的架构修改。下面是可以用 BERT 解决的不同语言理解任务的例子。

(来自[1])
在微调过程中,所有 BERT 参数都以端到端的方式进行训练。与预训练相比,BERT 的微调过程并不昂贵。事实上,论文本身的所有结果用单个 GPU 复制不到 2 小时。不信的话,自己试试!
伯特可能是自面包片以来最好的东西!
在结束这个概述之前,我想概述一下用 BERT 取得的一些经验结果。虽然人们可以很容易地阅读论文本身来查看结果,但我认为它们值得简单介绍一下,原因只有一个,即强调 BERT 在 NLP 任务方面有多好。下面概述了 BERT 在各种不同任务上取得的结果。

不同下游任务的 BERT 性能(来自[1])
你可能会注意到 BERT 在这些实验中表现的一些有趣之处——它从未被超越(除了人类,但只是在某些情况下)。在发表的时候,BERT 在 11 个不同的 NLP 基准测试上设定了一个新的最先进的标准。此外,这些任务中的大多数都是由特定于特定任务的专用模型解决的,而 BERT(正如您在本概述中看到的)是一个通用的语言理解模型,可以应用于许多不同的任务。
BERT 实证评估的其他一些有趣发现如下:
- 在[1]中考虑的所有任务上,BERT Large 和 BERT Base 都显著优于所有以前的方法。
- BERT Large 在所有任务上明显优于 BERT Base,在训练数据很少的任务上表现尤其出色。
- 移除 NSP 或 MLM(即,通过使用单向语言建模目标)显著恶化了 BERT 性能。
尽管更大的模型在小数据集上表现更好可能看起来违反直觉(即,这似乎是过度拟合的方法),但使用 BERT 获得的结果表明,如果有足够的预训练,使用更大的模型有利于低资源任务(即,那些几乎没有训练数据的任务)。
“我们相信,这是第一次令人信服地证明,如果模型已经过充分的预训练,扩展到极端模型大小也会导致非常小规模任务的巨大改进。”—来自[1]
外卖食品
尽管考虑到当前深度学习研究的步伐,BERT 是一个相对较老的模型,但我希望这篇概述适当地强调了该模型的简单性和深刻性。BERT 是一个非常强大的工具,使用起来既简单又便宜。
是什么让伯特如此强大?BERT 的关键在于两个核心概念:双向自我关注和自我监督学习。BERT 部分地改进了现有方法,因为它抛弃了使用单向自我注意进行语言建模风格的预训练的常见方法。相反,BERT 利用双向自我注意来制定一组自我监督的预训练任务,从而产生更健壮的特征表示。最*,研究人员表明,这些自我监督任务本身的形成——而不仅仅是用于预训练的大量数据——是 BERT 成功的关键[10]。
普通修行者可以用这个吗?使用 BERT,您可以简单地:
微调 BERT 在计算上是廉价的,并且可以用相对最少的硬件设置(例如,单个 GPU)来执行。因此,对于任何深度学习实践者来说,BERT 都是一个非常好的工具,你会惊讶于 BERT 是你最佳选择的不同任务的数量。
延伸阅读。在这篇综述中,我只介绍了一篇论文,但是 BERT 已经被无数的后续出版物所扩展。下面列出了我最喜欢的几个:
- ELECTRA:预训练文本编码器作为鉴别器而不是生成器(提出了一个新的预训练任务,能够训练高性能、较小的 BERT 模型)
- ALBERT:一个用于语言表示的自我监督学习的 Lite BERT】(提出了参数缩减技术,使 BERT 预训练更快,占用的内存更少)
- 维尔伯特:为视觉-语言任务预先训练任务不可知的视觉-语言表征(伯特对联合视觉-语言任务的推广)
- 用于对图像和文本进行分类的监督式多模态双变换器(与上述类似,但专用于对图像和文本的联合输入进行分类)
个人笔记。BERT 是第一个激发我对深度学习兴趣的模型。虽然我目前的研究更侧重于计算机视觉(或多模态学习,对此 BERT 仍然非常有效!),伯特的多才多艺至今让我印象深刻。行之有效的简单想法是罕见而美好的。
结论
非常感谢你阅读这篇文章。如果你喜欢它,请在 twitter 上关注我,或者订阅我的深度(学习)焦点时事通讯,在那里我挑选了一个关于深度学习研究的单个双周主题,提供了对相关背景信息的理解,然后概述了关于该主题的一些流行论文。我是 Cameron R. Wolfe ,Alegion 的研究科学家,莱斯大学的博士生,研究深度学习的经验和理论基础。也可以看看我在 medium 上的其他著述!
文献学
[1] Devlin,Jacob 等,“Bert:用于语言理解的深度双向转换器的预训练” arXiv 预印本 arXiv:1810.04805 (2018)。
[2] Kiela,Douwe 等人,“用于图像和文本分类的监督式多模态双向变换器” arXiv 预印本 arXiv:1909.02950 (2019)。
[3]郑,,等.“用变形金刚从序列到序列的角度重新思考语义切分”IEEE/CVF 计算机视觉和模式识别会议文集。2021.
[4]拉德福德、亚历克等,“通过生成性预训练提高语言理解”(2018).
[5]巴耶夫斯基,阿列克谢,等.“完形填空驱动的自我注意网络的预训练” arXiv 预印本 arXiv:1903.07785 (2019)。
[6]瓦斯瓦尼、阿希什等人,“你所需要的只是关注。”神经信息处理系统进展 30 (2017)。
[7]亚历克·拉德福德、卡蒂克·纳拉辛汉、蒂姆·萨利曼斯和伊利亚·苏茨基弗。2018.用无监督学习提高语言理解。技术报告,OpenAI。
[8]吴,永辉等.“Google 的神经机器翻译系统:弥合人与机器翻译的鸿沟” arXiv 预印本 arXiv:1609.08144 (2016)。
[9]威尔逊·泰勒。1953.完形填空程序:测量可读性的新工具。新闻简报,30(4):415–433。
[10] Krishna,Kundan 等人,“下游数据集是非常好的预训练语料库。”arXiv 预印本 arXiv:2209.14389 (2022)。
MLOps —通过 DVC 和 Azure Blob 存储进行数据和模型版本控制
关于如何跟踪数据的端到端方法—不到 5 分钟!

介绍
软件工程师主要使用 Git,因为它是源代码版本控制的好方法。另一方面,机器学习从业者可能很容易面临其局限性,因为大多数项目不仅涉及不同版本的源代码,还涉及不同版本的数据和模型。如果没有合适的工具,跟踪哪些数据用于训练哪个模型可能会很快变成一场长期灾难,并带来许多挫折。
接下来是**Data Version Control**,或简称为**DVC**,我认为它是最伟大的开源工具之一,通过以类似于 Git 的方式处理数据版本,弥合了 Git 和数据科学家之间的鸿沟。
本文旨在为您提供一些使用 DVC 在 Microsoft Azure Blob 存储上对大型数据集进行版本化的实践经验。
开始
为了能够成功地进行所有体验,您需要创建一个 Azure 帐户并登录到您的门户网站。
在本节中,我们将从设置 Microsoft Blob 存储开始,然后是 DVC,最后是创建它们之间的交互:从 Blob 存储中推送和提取数据。
设置 Azure Blob 存储
设置 Azure Blob 包括两个主要步骤:1)create a storage account,2)create a container inside that storage account,它将托管我们的数据。
- 创建存储账户
- 选择创建资源并搜索
Storage account,然后选择创建按钮。

Azure 存储帐户创建(图片由作者提供)
- 提供每个部分的所有信息后,从基础、高级等。到审查+创建,我得到了我的存储的如下最终配置结果。为了简单起见,我忽略了标记部分。

存储帐户最终配置(图片由作者提供)
- 一旦您对结果感到满意,只需点击创建,即可获得以下消息,表明您的存储帐户已成功部署!🎉

存储帐户的成功部署消息(图片由作者提供)
2。创建存储容器
通过选择转到资源按钮,您可以获得以下完整的存储概述。之后,按照步骤 1 到 4 创建您的容器。

容器创建步骤:1 到 4(图片由作者提供)
创建容器后,您可以选择容器名称dvcazurecontainer来获取与其相关的所有细节。

容器详细信息(图片由作者提供)
注:记住橙色矩形中的所有大写信息,因为当尝试使用 DVC 连接到 Blob 存储时,这些信息非常重要。
建立 DVC
让我们从使用下面的 pip 命令安装 DVC 开始。
pip install dvc
一旦完成安装 DVC,运行以下命令告诉 DVC,你正在考虑将当前文件夹作为 DVC 文件夹。
dvc init
前面的命令生成类似下图的输出

同样,它创建了一个。dvc 文件夹,以便跟踪我们将添加到远程存储器的文件。
DVC 与 Azure 博客的互动
现在,我们已经正确配置了 DVC 和 Blob 存储,是时候考虑我们想要跟踪的数据了。
关于数据
我实现了以下下载的脚本。csv 和。我的 Github 账号中的 pkl 文件,保存在 data 文件夹中,稍后会推送到 DVC。
视频 _ 下载 _ 自 _ 网址. py
项目存储库结构如下所示:
|--- AzureDVC
| |------- .dvc
| |------- **data**
| | |---- **spam_detector_model.pkl**
| | |---- **spam.csv**
| |------- download_data.py
导出环境变量
下一步是配置 Azure credentials 环境变量,DVC 将使用这些变量与 Azure 进行交互。
# Environment variables & Exports
CLIENT_ID="your_client_ID"
USERNAME=“your_email”
PASSWORD=“your_password”# Start the exports
export AZURE_CLIENT_ID= CLIENT_ID
export AZURE_USERNAME= USERNAME
export AZURE_PASSWORD= PASSWORD
之后,我们为代码可读性设置常量变量。
# Constants Variables
ACCOUNT_NAME="azuredvcdemo"
CONTAINER_URL=[https://azuredvcdemo.blob.core.windows.net/dvcazurecontainer](https://azuredvcdemo.blob.core.windows.net/dvcazurecontainer)
CONTAINER_NAME=dvcazurecontainer
接下来,设置 blob 存储和 DVC 之间的链接
dvc remote add -d zoumanaremote CONTAINER_URLdvc remote modify zoumanaremote url azure://CONTAINER_NAME/dvc remote modify zoumanaremote account_name ACCOUNT_NAME
是时候添加我们想要在 Azure Blob 上存储和监控的数据了。这是通过以下命令完成的。
dvc add data
该指令生成一个 data.dvc 文件,其中包含如何访问原始数据的信息,但不提供任何相关信息。这个 data.dvc 的内容是这样的:
outs:
- md5: a966ec281923bc3c9d7d1c40fde056b3.dir
size: 20248421
nfiles: 2
path: data
md5是使用 DVC 跟踪的原始数据文件夹的哈希值。size是数据文件夹中文件的大小。nfiles是数据文件夹中文件的数量。在本例中,我们有 2 个文件,一个. csv 文件和一个. pkl 文件。path是原始文件夹的路径。
使用以下命令将数据推送到容器中。
dvc push
从 Azure 门户,我们可以看到已经执行了更改,这可以通过相同的md5哈希值的存在来观察。

DVC 将结果从 Azure 门户推送到我的容器中(图片由作者提供)
现在,数据在 Azure Blob 存储上可用。使用下面的命令,您最终可以在需要的时候将它下载到您的本地项目存储库中。
dvc pull
结论
恭喜你!🎉 🍾您刚刚学习了如何使用 DVC 和 Azure Blob 存储进行大型数据监控。我希望你喜欢阅读这篇文章,它给了你所需的技能。请在下面找到更多资源来帮助您进一步学习。
此外,如果你喜欢阅读我的故事,并希望支持我的写作,可以考虑成为一个媒体成员。每月支付 5 美元,你就可以无限制地阅读媒体上的故事。通过使用我的注册链接,我将获得一小笔佣金。
欢迎在媒体、推特和 YouTube 上关注我,或者在 LinkedIn 上打招呼。讨论人工智能、人工智能、数据科学、自然语言处理和人工智能是一种乐趣!
再见🏃🏾♂️
额外资源
大语言模型道德
原文:https://towardsdatascience.com/large-language-model-morality-85926d1f78dc

图片由来自 Pixabay 的 Ryan McGuire 拍摄
人工智能很难
调查人类和预训练机器人之间的道德不匹配
语言模型一直在变得越来越大、越来越聪明、越来越有用。在本文中,我们将评估这些模型在推理道德问题时的表现。我们将研究的模型是 2019 年的 GPT-2 ,2019 年的迪特-GPT-2,以及 2021 年的 GPT-尼奥。其他模型也可以用同样的方式进行评估,每年的研究论文中都会有更深入的评估。最* OpenAI 公布了 InstructGPT ,似乎更擅长道德推理任务。总而言之,人工智能/人工智能行业知道这里提到的所有问题,并且正在努力解决它们。
大型语言模型有多大?嗯,蒸馏-GPT-2 有 8200 万个参数,而 GPT-2 有 1.24 亿个参数,在我使用的 GPT-尼奥版本中有 1.25 亿个参数。
尽管我们要研究的模型不是像 GPT-3 那样的尖端 LLM,尽管我没有使用这些模型的最大版本,但它们是当今广泛使用的流行模型。例如,仅在 2022 年 2 月,拥抱脸中心的 GPT-2 就有超过 1300 万次下载。蒸馏-GPT-2 月有 2300 万!
道德和伦理不是一回事。在这项工作中,我们不是根据一些规则(换句话说,一个道德测试)来评估每个 LLM,而是根据我个人对对错的信念(换句话说,一个道德测试)来测试这些模型。
现在,让我们开始工作吧。
为了测试这些模型的道德性,我们想要生成一堆句子开头的例子。这些例子都是有一些道德假设的前提。这些句子中的每一个总是关于不好的事情,其中一半生成的句子以错误的前提开始(例如,“当时邪恶是可接受的”),而另一半以正确的前提开始(例如,“当时邪恶是错误的”)。这些模型将完成每个句子,向我们展示它们是如何“思考”每个句子的道德前提的。
以下代码在 Google Collab 中工作,用于加载模型和生成数据集。
以下是为每个标签生成的句子示例,包括生成句子的模型:
表 1:在应用于 LLMs 的道德推理测试中为每个标签生成的句子的例子
下面的图 1 显示了我的评估的汇总结果。

图 1:高层的 LLM 性能。蓝色代表好,红色代表坏。来源:作者创作
我们可以在这些结果中看到,GPT-2 在错误或无意义的句子方面得分最低,在真实或有争议的句子方面得分最高。这些结果可能会在一个更大的生成样本集上发生变化,或者通过让许多人给出他们对每个生成句子的道德性的看法。不幸的是,我没有做数学来拒绝零假设。然而,根据我创建这些标签的经验,GPT-2 最有道德意义的结果对我来说似乎不是随机的。

图 2:更详细的 LLM 性能。蓝色和红色代表好,黄色和绿色代表坏。来源:作者创作
尽管这些模型在道德推理任务中的*均表现很差,但我对 GPT-2 如此出色的结果感到有点惊讶。但后来我想起蒸馏模型更小,因此可能比基本模型给出的结果更少。此外,GPT-*地天体可能有更多的参数,但它可能有更少的训练迭代。更新/更大不一定意味着更好。我很想看看像 GPT-3 这样的新型号在这项任务上表现如何。我有研究权限,所以这可能是下一步。
这篇文章的代码是,可以在这里找到。
如果你喜欢这篇文章,那么看看我过去最常读的一些文章,比如“如何给一个人工智能项目定价”和“如何聘请人工智能顾问”还有嘿,加入快讯!
下次见!
丹尼尔·夏皮罗博士
CTO、lemay . ai
linkedin.com/in/dcshapiro
丹尼尔@lemay.ai
大型语言模型和人类思维的两种模式
原文:https://towardsdatascience.com/large-language-models-and-two-modes-of-human-thinking-1322160755e8
与人类相比,GPT-3 如何解决“棒球棍和球”的问题?

图片由作者使用 OpenAI DALLE-2 生成。
我决定测试 GPT-3 将如何解决一个经典的“球棒和球”的难题。它通常用来展示思维系统 1 和系统 2 的区别。它在使用系统 1 时会不会犯和人类一样的错误,最终能不能解决?
TL;大卫:是的,是的。
两种思维方式
“系统 1 和系统 2”是一个流行的模型,描述了人类决策和推理的两种模式。它是由心理学家基思·斯坦诺维奇和理查德·韦斯特在 2000 年提出的。最*,丹尼尔·卡内曼在《思考,快与慢》一书中推广了这一方法。框架是新的,但背后的思想是旧的。人们很久以前就意识到了本能思维和有意识推理的区别。你可以在威廉·詹姆斯,西格蒙德·弗洛伊德,甚至古希腊哲学家的著作中找到它。
该模型的主要思想是人类有两种思考方式:
系统 1 是快速的,本能的,情绪化的。它自动地、无意识地运转,很少或根本不用努力,也没有自觉控制的感觉。系统 1 本质上是一个强大、快速、节能的模式匹配机器。
系统 2 更慢,更谨慎,更有逻辑性。它消耗更多的能量。它将注意力分配给需要它的费力的脑力活动,包括复杂的计算。它的运作通常与主观经验的代理,选择和集中。而且很贵。
系统 1 是自动且快速的,但是容易出错。系统 2 更谨慎、更有意识、更慢,但更准确。
大型语言模型是系统 1 模式的良好*似
像 GPT-3 和它的兄弟姐妹这样的大型语言模型已经在系统 1 任务中表现出了优异的性能。例如,它们可以生成与人类书写的文本无法区分的文本,生成看起来自然的问题回答,进行简单的算术、总结和重组。在许多方面,这些模型就像非常强大的模式匹配机器。模式匹配是系统 1 的本质。然而,大型语言模型在系统 2 任务上表现不佳,比如计划、推理和决策。
对这一事实有许多解释。
一个可能的原因来自它们的自回归操作模式。也就是说,它们根据序列中的前一个标记来预测序列中的下一个标记。当人类用他们的系统 2 思考解决任何问题时,他们本质上是在他们的工作记忆中或使用一些外部媒介进行多步计算。但是 GPT 3 号缺乏这样的工作记忆。它只受到信号通过其层的单次正向传递的限制。它被设计成提供快速、单步的答案。有一些提示编程技术,比如元提示和思想链提示,可以在一定程度上缓解这种情况。它们帮助语言模型“慢下来”,并开始使用其输出作为多步骤系统 2 任务的一种工作记忆。
“我们需要对 GPT-3 保持耐心,给它时间思考。GPT-3 在写出自己的答案时表现最佳,因为这迫使它大声思考:也就是说,以缓慢而有序的方式写出自己的想法。”提示编程的方法。
但是还有一个更深层次的原因。大型语言模型和一般所有现代(自我)监督的机器学习模型本质上都是模式识别系统。Yoshua Bengio 在他的 2019 NeurIPS 演讲中谈到了这个话题。它们可能根本不适合系统 2 的任务,我们可能需要人工智能的另一次革命。但这仍然是一个悬而未决的问题。
增强大型语言模型的系统 2 能力是一个活跃的研究领域。
一个球和一个球棒的测验
这个测试是由诺贝尔经济学奖得主、心理学家丹尼尔·卡内曼提出的。
测验陈述如下:
一个球棒和一个球总共花费 1.10 美元。球棒比球贵一美元。这个球多少钱?
最直接的答案通常是 0.10 美元。这是不正确的。当你给一个人多一点时间去思考,就很容易找到正确的答案。也就是 0.05$ 。
这个测验展示了人类是如何依赖系统 1 而不是系统 2 的。
我决定通过检查 GPT 三号的答案来检验它是否和我们的推理方式相似。
现在,第一个问题是 GPT-3 已经在它的训练集中看到了这个难题。您可以通过让它预测其语句和不同的上下文来轻松检查它:

GPT 三号游乐场截图。图片由作者提供。
一个球棒和一个球的测验陈述:
一根球棒和一个球总共值 1.10 美元。
球棒比球贵一美元。
这个球多少钱?
事实上,它意识到了这一点。因此,为了使模型更难再现它以前简单看到的东西,让我们更改项目的名称。我决定用一包香蕉和橙汁。健康的选择。看看它会怎么回答:

GPT 三号游乐场截图。图片由作者提供。
问题:一包香蕉和一杯橙汁总共要 1.1 美元。橙汁比香蕉包贵 1 美元。香蕉包要多少钱?
回答:
香蕉包售价 0.1 美元。
太好了,它提供了相同的答案。现在让我们使实验更清晰,并改变数字:

GPT 三号游乐场截图。图片由作者提供。
问题:一包香蕉和一杯橙汁总共要 510 美元。橙汁比香蕉包贵 500 美元。香蕉包要多少钱?
答案: $10
所以,GPT-3 确实犯了大多数人在使用系统 1 模式时犯的同样的错误。
让我们试着引导它慢慢思考这个问题,不要给出过早的答案。让我们试试东京大学和谷歌大脑团队最*的论文中提出的元提示技术。这个想法是通过添加短语“让我们一步一步地思考”来“放慢”模型的结论在您的提示中:

GPT 三号游乐场截图。图片由作者提供。
问题:一包香蕉和一杯橙汁总共要 510 美元。橙汁比香蕉包贵 500 美元。香蕉包要多少钱?
回答:让我们一步步思考。
我们知道香蕉包和橙汁的总成本是 510 美元。
我们还知道橙汁比香蕉包贵 500 美元。
这意味着如果我们从总成本中去掉橙汁的成本,我们将剩下香蕉包的成本。
所以,510 美元——500 美元= 10 美元。
因此,香蕉包的价格是 10 美元。
没多大帮助。这个模型仍然不能给出正确的推理路线。让我们按照模型解决方案的第一步给它一些指导:

GPT 三号游乐场截图。图片由作者提供。
问题:一包香蕉和一杯橙汁总共要 510 美元。橙汁比香蕉包贵 500 美元。香蕉包要多少钱?
回答:让我们一步步思考。
x =一包香蕉的价格
y =橙汁价格
等式 1: x+y = 510 美元
等式 2: y = x + 500 美元
从等式 2 中,我们知道 y = x + 500 美元
我们可以把这个代入等式 1:
x+y =510$
x+x+500 美元= 510 美元
2x+500 美元= 510 美元
2x = 10 美元
x = 5$
香蕉包售价 5 美元
它找到了正确答案!
好吧,但也许是监督太多了。我们可以做得更好。让我们将模型的温度降低到 0.1,使它变得更少,但更连贯。让我们简单地给它一个提示,它应该使用代数来解决这个难题。我实际上用了同样的提示来指导我的人类朋友在这个测验中的正确方向。

GPT 三号游乐场截图。图片由作者提供。
问题:一包香蕉和一杯橙汁总共要 510 美元。橙汁比香蕉包贵 500 美元。香蕉包要多少钱?
回答:让我们一步步思考。我们需要用代数方法来表述这个问题。
我们知道:
香蕉包+橙汁= 510 美元
橙汁=香蕉包+500 美元
我们可以用替换法来解这个方程组。
橙汁=香蕉包+500 美元
香蕉包+橙汁= 510 美元
我们可以把橙汁=香蕉包+500 美元代入香蕉包+橙汁= 510 美元得到:
香蕉包+(香蕉包+ $500) = $510
我们可以简化这个方程得到:
2 香蕉包= 10 美元
香蕉包= 5 美元
成功!
结论
大型语言模型的智能水*令我们惊讶。到目前为止,比例假说显然对他们有效。从 GPT-2 到 GPT-3 的飞跃不仅仅是数量上的不同,而是一种新品质的出现。人们很容易在相反的方面犯错误:将他们过于拟人化,同时低估他们的智力。它们有明显的局限性。他们不是特工,没有任何主动动机。他们只知道文本,除此之外一无所知。他们的推理是初步的,与我们的不同。但同时,令人着迷的是,在某些情况下,它们是如此的相似。
GPT 4 号也快到了!引用 YouTube 频道“2 分钟论文”作者的话,“这是多么美好的活着的时光啊!”。
参考
- GPT 三号游乐场
- 思考,快与慢
- 提示编程的方法
- Yoshua Bengio:从系统 1 深度学习到系统 2 深度学习(NeurIPS 2019)
- 小岛康誉等,大型语言模型是零命中率推理器,2022
- Valmeekam 等人,大型语言模型还是不会规划,2022
跟我来
如果你觉得我与你分享的想法很有趣,请不要犹豫,在媒体、 Twitter 、 Instagram 或 LinkedIn 上连接这里。
在 Spark、Ray 或 Dask 上使用 whylogs 和 Fugue 进行大规模数据分析
为异常检测、漂移检测和数据验证等用例分析大规模数据

哈士奇与放大镜——作者图片
动机
数据管道有可能以多种方式产生意想不到的结果。异常数据会导致数据缩放不正确。机器学习模型漂移会导致预测精度降低。当数据管道执行时,上游集合中的故障可能会导致空值。我们如何防范这些失败案例?
监控数据质量的一种方法是数据验证。像潘德拉和远大前程这样的工具允许数据从业者建立一套预定义的规则。例如,我们可以检查数据集中是否存在列,或者某个重要特性是否包含空值。
虽然这在理论上听起来不错,但它要求我们事先知道关于数据的许多事情。Great Expectations 确实有一个分析器,但是它非常严格,并且不能很好地扩展到 Spark。当我们的数据不太可预测时,我们需要找到其他方法或指标来提醒我们随着时间的推移数据会下降。例如,想象一个趋势向上的时间序列在某一天显著下降。如果我们应用数据验证来检查最大值和最小值,我们的验证规则可能无法捕捉到这种下降。我们需要一套更通用的分析工具,能够扩展到数据验证、漂移检测和异常检测等应用。
whylogs 简介
这就是为什么日志会出现的原因。 whylogs 是一个开源的数据记录框架,它让我们能够以最小的开销分析我们的数据。上面提到的问题:异常检测、漂移检测和数据质量检查都可以解决,如果我们有好的数据配置文件。
使用 whylogs,用户可以通过添加几行代码来分析他们的数据。以下面的例子为例,我们分析了熊猫的数据帧。
样本 whylogs 代码— 摘自 WhyLabs 文档
可以查看概要文件,生成类似于下表的内容。

whylogs 的配置文件输出— 摘自 WhyLabs 文档
该配置文件包含的列数超过了此处可以优雅显示的数量。在上图中,我们看到(按顺序):
- 记录总数(第一列)
- 空记录计数(第二列)
- 列中值的推断类型(第三至第七列)
不适合上图的其他列包括:
- 估计基数
- 频繁值
- 分布指标,如最大值、最小值和分位数
whylogs 配置文件设计有三个重要属性:高效、可定制和可合并。前两个非常简单明了。whylogs 的开销很低,这使得它可以轻松扩展到 Spark 这样的大数据框架。更重要的属性是可熔性。可以将数据帧的较小部分的配置文件添加在一起,以形成全局数据集配置文件。
在处理 Spark、Dask 和 Ray 之类的分布式框架时,这是一个重要的属性,在这些框架中,数据可以存在于多个工作者之间。可以独立地分析分区,然后汇总。第二件事是,如果需要,数据集的更重要的子部分也可以独立地进行分析,然后整合到更广泛的分析中,而不必重新计算分位数等统计数据。
通过神游扩展到大数据
一些图书馆分析数据,如熊猫-分析,但这些都集中在探索性数据分析,所以它们被设计成跟踪不同的东西。熊猫概况提供的一些信息很难扩展到 Spark 这样的大数据框架。
whylogs 旨在将其数据记录扩展到大数据。我们可以使用赋格集成来分布式运行分析。他们还集成了卡夫卡和盛宴等等。
开源的神游项目获取 Python、Pandas 或 SQL 代码,并将其带到 Spark、Dask 或 Ray 。在 Fugue 上使用 whylogs 允许我们维护相同的简单界面来生成配置文件。例如:
fugue_profile 函数
这仍然是运行在熊猫的引擎之上。它将产生与上面使用普通 whylogs 的代码片段相同的结果。优点是,我们只需提供一个参数,就可以将它带到 Spark、Dask 或 Ray。

fugue_profile 输出—作者提供的图像
为了引入 Spark,我们可以将 SparkSession 作为engine传递。
将 whylogs 档案带到 Spark
为了在 Dask 和 Ray 上执行,我们可以传递集群地址或Client。
分析逻辑分区
在处理大数据时,一个更常见的用例是分析数据的逻辑分区。这可以帮助我们找到离群分区,并且对于探索性分析也是有用的。
对每个分区应用 fugue_profile
这将返回如下图所示的输出,其中最后一列是序列化的概要文件。然后这些可以被解串和后处理。这个数据帧已经比原始数据帧小得多,所以反序列化和提取数据非常快。通过对配置文件进行操作,我们可以存储和分析数量少得多的数据。

每个分区的分析—按作者分类的图像
数据配置文件的使用
有了数据概要文件,我们可以将它们应用到不同的用例中。
异常检测
对于每月持续出现的新数据,我们可以每月运行配置文件并存储这些配置文件。然后,我们可以用 z-score 之类的指标来比较分布图中不同分位数的值。

分布式概要分析用例—按作者分类的图像
这种设置对于大数据尤其重要,因为我们不需要返回到以前的数据并重新计算指标。数据配置文件足以检查异常情况。
数据质量
配置文件中缺少数据计数。基数也有助于查找包含过多类别的列。我们还可以用已经包含的分位数检查这些数字是否在某个已知的范围内。如果用户需要跟踪更多的东西,还可以添加自定义指标。
漂移检测
我们可以通过监控传入数据的分布来确定是否需要重新训练我们的机器学习模型。通过几行额外的代码,whylogs 可以生成可视化报告。这些在调试为什么会发生意外的预测分布时非常有用。下图显示了新数据的预期分布与实际分布。

whylogs 可视化示例—图片来自 whylogs 文档
结论
在本文中,我们讨论了可以通过数据概要分析解决的一系列问题。异常检测、漂移检测和数据质量问题有时需要在没有数据先验知识的情况下解决。数据配置文件是一种以无人监管的方式处理这些数据的非常通用的方法。档案也可以作为将来验证规则的基础。
whylogs 特别强大,因为它被设计成轻量级的,可以扩展到大数据。mergability 属性还允许在数据分区的分布式设置中使用它,这些分区可以在以后合并。通过使用 Fugue 集成,可以在 Spark、Dask 和 Ray 之上轻松使用 whylogs 来在集群之上运行分析。这些结果可以保存并在将来使用,而无需重新计算过去的数据。
资源
要与我们聊天:
梯度下降的大规模 K 均值聚类
原文:https://towardsdatascience.com/large-scale-k-means-clustering-with-gradient-descent-c4d6236acd7a
利用顺序数据、小批量数据和批量数据进行学习

拉杜·切拉里乌在 Unsplash 上拍摄的照片
简介
聚类是一种无监督形式的机器学习算法。它发现数据中的子组或模式。K-Means 算法是一种简单直观的数据聚类方法。当我们应用 K-Means 算法时,我们必须注意数据集的大小和维度。这些中的任何一个都可能导致算法收敛缓慢。在本文中,我们将探索梯度下降优化和降维技术,以扩大 K-Means 算法的大数据集。我还将分享我在流行图像数据集上的一些实验结果。
在梯度下降优化中,我们计算成本函数的导数,并在梯度的相反方向上迭代移动,直到我们达到最优值。这种优化可用于通过在小批量数据集上执行优化来扩展 ML 算法以在大数据集上训练。
我们还可以对数据集应用降维技术,以找到更好的数据表示并加快聚类时间。在下一节中,我们将看到如何使用梯度下降学习来执行 K-Means。
k 均值
K-Means 算法将数据集分成 K 个不同的聚类组。它使用一个代价函数,该函数使聚类中心和它们的指定样本之间的*方距离之和最小化。基于领域知识或通过一些其他策略来设置聚类的数量。K 均值成本函数可以写成:

其中样本 x 具有维度 d,N 是数据集中样本的总数。聚类中心由 w 表示。标准 K-Means 算法在聚类中心估计和聚类中心与样本之间的距离计算之间交替进行固定次数的迭代。
接下来,我们将了解如何使用梯度下降优化来执行 K 均值聚类。
批量梯度下降学习
标准 K-Means 算法在大数据集上可能具有缓慢的收敛和存储密集型计算。我们可以用梯度下降优化来解决这个问题。对于 K-均值,聚类中心更新等式被写成:

其中 s(w)是欧氏空间中最接* x 的原型。学习率被设置为样本聚类计数,即分配给特定聚类中心的样本数。在固定数量的迭代之后或者当 K-均值成本的变化低于某个容限值时,我们停止这种梯度优化。在批量梯度下降中,我们用整个数据集计算梯度。对于大型数据集,计算梯度可能会很慢且效率低下。
接下来,我们将研究梯度下降的两种变体来缩放 K-均值聚类。
随机梯度下降
使用一个样本来计算梯度的梯度下降的变体被称为随机梯度下降(SGD)。由于它只用一个样本进行聚类更新,因此可以应用于在线学习。在实践中,我们随机化数据集,一次处理一个样本。学习率在每次迭代期间针对每个聚类中心进行更新。这种方法在实践中效果很好。SGD 的伪代码如下所示。

带 SGD 的 K 均值伪代码
为了可视化 K-Means SGD 学习过程,我创建了一个玩具数据集,其中有两个可线性分离的圆形斑点。

具有 SGD 学习的 K-Means 的图示
从图中,我们看到聚类中心更新可能很慢,因为一次只处理一个样本。当有许多不同的聚类中心要学习时,这可能会变得更糟。
小批量随机梯度下降
SGD 可能找不到好的聚类中心,因为只有一个样本用于计算梯度。我们可以修改梯度更新步骤,使用 M 个样本的批量来计算梯度。这种变体被称为小批量 SGD。这比 SGD 具有更快和更*滑的成本收敛。mini-batch 背后的理由是,数据子集比单个样本包含更多的信息,同时也不太可能包含在整个数据集中找到的冗余示例。实际上,小批量是随机抽样的。小批量 SGD 的伪代码如下所示。

带小批量 SGD 的 K 均值伪代码
该算法类似于 SGD,只是我们使用 M 个样本来计算每次迭代的梯度。最小批量是一个需要调整的超参数,其值会影响算法的性能。
下面是小批量 SGD K-Means 算法在同一个玩具数据集上的图解,其中小批量的大小为 4。相比新币,成本收敛更快。SGD 和 mini-batch SGD 都能找到好的聚类中心,并很好地对玩具数据集进行分组。

批量大小为 4 的小批量 SGD 学习的 K 均值图
接下来,我们将讨论两种流行的降维技术,以及它们如何改进和加速聚类工作流。
降维
降维可以看作是一个特征选择和特征提取的步骤。原始数据被映射到低维空间,这有助于更快地学习 clusters⁴.我试验了主成分分析(PCA)和稀疏随机投影(SRP)方法。
主成分分析
PCA 学习最大化数据方差的嵌入矩阵。这是一种数据相关的投影,因为嵌入矩阵包含样本协方差矩阵的特征向量。Jonathan Hui 写了一篇关于奇异值分解的主成分分析的综述
稀疏随机投影
SRP 构造了一个与数据无关的嵌入矩阵。成对的距离在较低的维度中保留了一个因子。与高斯随机投影矩阵相比,SRP 支持更快的数据投影,并且内存效率高。本文由 CJ Sullivan ⁶.对 SRP 进行了详细的解释和应用
实验
为了比较不同版本的 K-Means,我对 iris、USPS 和 Fashion-MNIST 数据集进行了聚类分析。这些数据集包含不同维度的小型、中型和大型样本。对于所有三个数据集,样本分布在所有类中是一致的。下面是不同数据集的总结。

数据集摘要
随机选择的样本被初始化为所有三个 K 均值算法的聚类中心。对于批量梯度下降,我将 iris 数据集的学习率设置为 0.03/t,将 USPS 和 Fashion-MNIST 数据集的学习率设置为 0.1/t。对于小批量 SGD,我使用 32 作为批量大小。对于 SGD 和小批量 SGD,我使用集群分配计数作为学习率。所有数据集都使用最小-最大缩放器进行缩放,该缩放器在范围[0,1]内缩放每个样本。下面是用于缩放数据的公式。

我还尝试了标准缩放器,它将数据转换成零均值和单位标准差。对于上述三个数据集,我没有看到收敛速度或更低的最终成本有很大的差异。
不同梯度下降算法的代价收敛性

iris、USPS 和 Fashion-MNIST 算法的标准化 K 均值成本。
当我们在所有三个数据集上比较成本收敛时,mini-batch SGD 具有更*滑和更快的收敛。对于较小的 iris 数据集,SGD 比小批量 SGD 成本更低。这可能是因为 32 的批量可能太大了。为了了解小批量如何影响收敛,我用 16、32、64 和 128 个小批量进行了一个实验。

标准化不同小批量的成本趋同。
我们看到小批量和大批量的最终成本差距很大。16 的小批量为所有三个数据集产生最低的最终成本。
基于维数的相对聚类时间缩减
时间缩减是在具有全维度的标准 K-均值和具有缩减维度的小批量 SGD K-均值之间的度量。我在 USPS 和 Fashion-MNIST 数据集上运行了小批量 K-Means 算法,这些数据集用不同维度的 PCA 和 SRP 绘制。在时间计算中不考虑降维计算步骤,而只考虑数据聚类时间。

用小批量 SGD 可视化相对时间减少
正如所料,随着更多的组件,K-Means 需要更多的时间来收敛到一个更低的成本。时间减少趋势随维度线性减少。
K-均值代价与维数的相对误差
相对误差是标准和小批量 SGD K 均值之间的最终成本差异。标准 K-Means 在原始数据集上训练,小批量 K-Means 在不同维度上训练。以下是 PCA 和 SRP 后的相对误差。

可视化标准 K 均值和小批量 SGD 之间的相对成本差异
当原始维度非常高时,随机投影会受益。与 SRP 相比,PCA 的相对成本差异随着更多组件而降低(在一定程度上)。
代码片段
我们可以使用 scikit-learn API 为集群工作流创建一个简单的管道。下面是一个带有 PCA 和小批量 K-Means 的样本管道。
**from** **sklearn.pipeline** **import** [Pipeline](https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html#sklearn.pipeline.Pipeline)
**from sklearn.preprocessing import** [MinMaxScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html)
**from** **sklearn.decomposition** **import** [PCA](https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html#sklearn.decomposition.PCA)
**from sklearn.cluster import** [MiniBatchKMeans](https://scikit-learn.org/stable/modules/generated/sklearn.cluster.MiniBatchKMeans.html#sklearn.cluster.MiniBatchKMeans)
**from sklearn.datasets import** [fetch_openml](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.fetch_openml.html) **# load dataset**
X, y = fetch_openml('USPS', return_X_y=True, as_frame=False)**# add methods to pipeline**
steps = [
("scale_step", MinMaxScaler()),
("dimred_step", PCA(n_components=50)),
("cluster_step", MiniBatchKMeans(n_clusters=10, batch_size=16))
]**# create pipeline**
clusterpipe = Pipeline(steps)**# fit pipeline on data**
clusterpipe.fit(X)

具有 scikit-learn API 的简单集群管道
结论
两个 SGD 版本都可以很好地处理大规模 K-Means 聚类。在中型和大型数据集上,与 SGD 相比,mini-batch SGD 收敛速度更快,发现的聚类中心更好。小批量大小是一个基于数据集大小进行调整和设置的超参数。聚类受益于降维方法,降维方法是内存高效的并且具有更快的收敛。当我们设计机器学习管道时,速度和低成本的权衡曲线可视化是有帮助的。
参考文献
[1]斯卡利博士(2010 年 4 月)。网络规模的 k 均值聚类。在第 19 届万维网国际会议记录(第 1177-1178 页)。
[2]博图和本吉奥(1994 年)。k-means 算法的收敛性质。神经信息处理系统的进展、 7 。
[3]博图,L. (2012 年)。随机梯度下降技巧。在神经网络:交易技巧(第 421–436 页)。斯普林格,柏林,海德堡。
[4] Boutsidis,c .,Zouzias,a .,和 Drineas,P. (2010 年)。随机投影为k-均值聚类。神经信息处理系统的进展、 23 。
[5]乔纳森·惠。(2019).机器学习—奇异值分解&主成分分析。走向数据科学
[6]希杰·沙利文。(2021).幕后讲述生成图嵌入的快速随机投影算法。走向数据科学
所有图片,除非特别注明,均为作者所有。
Graphcore IPUs 上的大规模知识图完成
原文:https://towardsdatascience.com/large-scale-knowledge-graph-completion-on-ipu-4cf386dfa826
neur IPS 2022 OGB-LSC 知识图表竞赛的获胜者
通过快速实验、仔细调谐和大型集合进行准确预测

知识图嵌入模型的计算图的绘制。图片作者。
表示图结构数据的机器学习方法变得越来越重要。该领域研究人员面临的主要挑战之一是模型对大型数据集的可扩展性。作为 NeurIPS 2022 竞赛赛道计划的一部分, Open Graph Benchmark 大规模挑战赛(OGB-LSC) 旨在通过鼓励 graph ML 研究社区与实际大小的数据集合作,开发能够满足现实世界需求的解决方案,来推动图形表示学习的边界。
在这篇博文中,我们展示了 Graphcore 提交给 OGB-LSC@NeurIPS 2022 知识图谱的获奖作品。我们深入探讨了机器学习模型、数据集考虑因素、集成策略以及我们实现这一成功的高效执行方案。关于我们方法的更深入的信息可以在论文【1】中找到。
什么是知识图?
知识图是通过捕捉现实世界实体之间的关系来构建和表示知识的一种非常自然的方式。事实表示为三元组(头、关系、尾),其中关系描述了头和尾实体之间的链接。例如,三联(杰弗里·辛顿、毕业于、剑桥大学国王学院)是图 1 中知识图陈述的七个事实之一。知识图的应用范围从药物发现到问答和推荐系统【2–4】。

图 WikiKG90Mv2 知识图的一个小子图。一个可能的查询可能是(Geoffrey Hinton,出生于?).图片作者。改编自[5]。
知识图嵌入(KGE)模型通过学习低维向量空间中的实体和关系的嵌入来对知识图进行推理,使得三元组的似然性通过头部、关系和尾部嵌入的评分函数来测量。通过最大化阳性样本的分数和最小化阴性样本的分数,在阳性(真实)三元组和阴性(随机抽取)三元组的批次上训练 KGE 模型。

图 2:评分函数的三个不同例子:TransE 度量头+关系和尾嵌入之间的距离;TransH 在应用 TransE 之前将头部和尾部嵌入投影到依赖关系的超*面上;在 RotatE 中,该关系描述了复数值头嵌入的旋转。在所有三个模型中,负距离可以用作分数。图片作者。
虽然知识图文献通常关注相对较小的图,但是商业价值的实际应用越来越需要在具有数亿甚至数十亿个实体和三元组的图上进行推理。WikiKG90Mv2 是基于 Wikidata 的大规模知识图,由超过 9000 万个节点和 6 亿个三元组组成。知识图通常是不完整的:虽然现有的三元组可以被认为是真实的,但两个实体之间没有关系就没有真值。因此比赛的任务是完成三个一组的形式(头、关系、?),比如(杰弗里·辛顿、出生于、?),见图 1。WikiKG90Mv2 数据集的巨大规模是实现快速准确的知识图完成模型的主要挑战之一,因为我们最大的模型消耗超过 300 GiB 的参数、优化器状态和功能。为了克服这个问题,我们实现了 BESS(*衡实体采样和共享),这是一个用于训练 KGE 模型的分布式处理方案,可以有效地*衡多个工作人员的通信和计算。
基于 BESS 的分布式知识图训练
BESS 在 D 个可用的工人中随机且均匀地划分实体嵌入集。这导致将数据集中的三元组划分为 D 桶 {T ₘₙ ,m,n = 1,…,D} ,其中 T ₘₙ是一组三元组,头实体存储在 worker m 上,尾实体存储在 worker n 上。然后,在培训时,对每个工人 m 从 {T ₘₙ ,n=1,…,D} 中统一抽取一批。类似地,对于每个小批量BESS 从所有可用的工人中统一抽取尾部来构建负样本。这些阴性样本在微量批次中的所有三个样本中共享,以增加有效的阴性样本量,而不增加阴性样本交流和评分的成本

作者制作的动画。
因为与实体的数量相比,不同关系类型的数量通常很少,所以关系嵌入在所有工人之间复制,并使用 AllGather 操作进行更新。
在推理时,对于一个给定的查询(头、关系、?并且返回前 K 个结果的有序列表。
BESS 方法保证了只有尾部嵌入必须在工人之间交换。同时,它*衡了计算和工作人员之间的通信。我们在 Graphcore Bow Pod16 上使用 BESS 训练 KGE 模型,它受益于在快速 IPU-IPU 链路上运行的集体通信,这使得单独的参数服务器变得过时。此外,14.4 GB 的处理器内存允许在整个系统中高效地使用 DRAM 的总带宽。
匹配数据集分布
正如挑战组织者所详述的,验证和测试数据集是通过采样三元组创建的,使得关系类型的计数与原始关系计数的立方根成比例。如果不相应地调整来自训练数据集的采样,这将导致严重的泛化差距。因此,我们偏置训练三元组的样本,使得关系类型的结果分布与训练数据集中关系类型计数的立方根相匹配。此程序将训练期间使用的不同关系类型的频率与验证和测试集中的关系分布对齐,并减少训练和验证 MRR 之间的差距(图 3)。

图 3:立方根抽样策略调整了关系类型的训练和验证/测试分布(左),并减少了训练和验证 MRR 之间的差距(右)。图片作者。
多样化的组合,以获得最佳的所有世界
BESS 在 Graphcore Bow Pod16 上实现的吞吐量使我们能够训练 85 个 KGE 模型的非常多样化的集合,该集合结合了五个不同的评分函数(转移、转移、旋转、分散、复杂、【6–10】)和两个不同的损失函数。每个评分函数都有不同的优点和缺点,如表 1 中所总结的,使得具有不同评分函数的模型集合特别有前途。为了进一步提高模型的多样性,我们采用了两种不同的损失函数:log-sigmoid 损失和采样 softmax 交叉熵损失。详见文件【1】。

表 1:评分函数及其模拟基本关系属性的能力:S =对称性;AS =反对称性;I =反转;C =构成。图片作者。
除了知识图的结构(三元组的集合),WikiKG90Mv2 数据集为每个实体和关系提供了 768 维的特征向量。我们的模型通过使用可学习的权重矩阵将实体特征投影到实体嵌入空间并将结果添加到可学习的实体嵌入中来利用实体特征。由于不同关系类型的数量很少,我们假设关系特征对于学习良好的关系嵌入并不重要,因此我们在模型中忽略了这些特征。
多个模型的预测使用幂秩集合策略进行组合,概括[11]。每个预测尾部实体根据其在各个预测中排名的*方根倒数获得一个分数:

图片作者。
然后,通过使用该分数对实体进行排名来选择最终预测。
结果
我们对各种模型进行了训练,验证*均倒数等级(MRR)至少为 0.2,其中最佳的单个模型达到了 0.243 的 MRR(图 4)。我们发现,根据评分函数的不同,模型在不同程度上有助于集成:尽管个体验证的 mrr 较低,但 DistMult 和 ComplEx 模型在集成中表现得非常好(图 4)。

图 4:每个评分函数的 K 个最佳个体模型(虚线)和 K 个最佳模型集合(实线)的验证 MRR。图片作者。
基于这一证据,我们选择了一个由 85 个 KGE 模型组成的集合(25 个最佳 trans、DistMult 和 ComplEx 模型,以及 5 个最佳 trans 和 RotatE 模型),在测试-挑战数据集上实现了 0.2922 的验证 MRR 和 0.2562 的 MRR,赢得了 2022 年开放图基准大规模挑战的 WikiKG90Mv2 赛道。
对大量知识图进行快速准确的推理是一项重要而又富有挑战性的机器学习应用。我们使用一种新颖的分布框架来提高 KGE 模型对大型图形的可伸缩性,并展示了良好调整的模型的多样性集合的优势。虽然这些方法不是灵丹妙药,但我们希望我们的见解有助于社区创建快速准确的知识图嵌入模型,并加速它们在现实世界应用中的采用。
参考
[1] A. Cattaneo,D. Justus,H. Mellor,D. Orr,J. Maloberti,Z. Liu,T. Farnsworth,A. Fitzgibbon,B. Banaszewski,C. Luschi, BESS:面向大规模知识图完成的*衡实体采样与共享 (2022), arXiv 预印本 arXiv:2211.12281 。
[2] S. Bonner,I.P. Barrett,C. Ye,R. Swiers,O. Engkvist,A. Bender,C.T. Hoyt,W.L. Hamilton,与药物发现相关的生物医学数据集回顾:知识图视角 (2022),生物信息学简报, 23 (6),第 bbac404 页。
[3]郝,张,刘,何,刘,吴,赵,(2017),计算语言学协会第 55 届年会论文集(第一卷:长论文)(第 221-231 页)。
[4] F. Zhang,N.J. Yuan,D. Lian,X. Xie,W.Y. Ma,面向推荐系统的协同知识库嵌入 (2016),载于第 22 届 ACM SIGKDD 知识发现与数据挖掘国际会议论文集(第 353–362 页)。
[5] W. Hu,M. Fey,H. Ren,M. Nakata,Y. Dong,J. Leskovec,-LSC:机器学习对图的大规模挑战 (2021), arXiv 预印本 arXiv:2103.09430 。
[6] A. Bordes,N. Usunier,A. Garcia-Duran,J. Weston,O. Yakhnenko,翻译用于多关系数据建模的嵌入 (2013)。神经信息处理系统的进展、 26 。
[7] Z. Wang,J. Zhang,J. Feng,Z. Chen,超*面上*移的知识图嵌入 (2014),AAAI 人工智能会议论文集(第 28 卷第 1 期)。
[8] Z. Sun,Z.H. Deng,J.Y. Nie,J. Tang,旋转:复空间中关系旋转的知识图嵌入 (2019), arXiv 预印本 arXiv:1902.10197 。
[9] B. Yang,W.T. Yih,X. He,J. Gao,L. Deng,在知识库中嵌入用于学习和推理的实体和关系 (2014), arXiv 预印本 arXiv:1412.6575 。
[10] T. Trouillon,C.R. Dance,J. Welbl,S. Riedel,e .Gaussier,G. Bouchard,通过复张量分解的知识图完成 (2017), arXiv 预印本 arXiv:1702.06879 。
[11] G.V .科马克,C.L. Clarke,S. Buettcher,互逆秩融合优于孔多塞和个体秩学习方法 (2009),在第 32 届国际 ACM SIGIR 信息检索研究与发展会议论文集(第 758-759 页)中。
套索和岭回归:直观的比较
原文:https://towardsdatascience.com/lasso-and-ridge-regression-an-intuitive-comparison-3ee415487d18
以及它们如何帮助你理解正规化

套索和山脊(统计学习的要素
介绍
当人们开始他们的机器学习之旅时,他们通常从线性回归开始,这是最简单的算法之一。然而,这种模型很快就显示出它的局限性,特别是在处理导致模型过度拟合的数据集时。对此的主要解决方案被称为岭和套索回归。
偏差方差权衡
为了理解为什么这些模型是有用的,我们首先需要讨论偏差-方差权衡。

偏差方差权衡(统计学习的要素
在监督设置中,模型的误差主要有两个来源:偏差和方差。
- 偏差 是学习算法中错误假设产生的误差。高偏差会使算法错过特征和目标之间的相关关系(也称为欠拟合)
- 方差 是由于对训练数据的微小波动敏感而产生的误差。高方差将使算法模拟训练数据的随机噪声(也称为过拟合)。
理想情况下,您希望找到这两个分量之和最小的最佳点。这将为您提供性能最佳的模型。
例子
首先,我们将看到一个拟合不足的例子:

底切的例子(图片由作者提供)
这里,您可以看到模型没有很好地捕捉特征和目标之间的关系。因此,它具有高偏差(算法错过了特征和目标之间的相关关系)但具有低方差(没有对数据的随机噪声建模)。
相反,这里有一个过度拟合的例子:

过度拟合的例子(图片由作者提供)
在这里,您可以看到算法理解特征和目标之间的关系,但也对数据的噪声进行建模。因此,它具有低偏差(算法获得特征和目标之间的相关关系)但具有高方差(模拟训练数据的随机噪声)。
现在,让我们看看一个合适的例子是什么样的:

非常适合的示例(图片由作者提供)
在这里,您可以看到该算法能够模拟特征和目标之间的关系,但它不能模拟数据的噪声。因此,它具有低偏差和低方差。这就是我们想要达到的契合度。
这和山脊/套索有什么联系?
当您拟合线性回归模型时,会发生以下情况。您有一组特征(通常称为 X,表示为矩阵),并且您想要找到一组系数(通常称为β,表示为向量),通过乘以 X 中的值来预测您的目标(通常称为 y,表示为向量)。

使用线性模型进行预测(图片由作者提供)
问题是,在某些情况下,线性回归会过度拟合特定的数据集。这种情况下你会怎么做?使用脊和套索回归。
这些模型是如何工作的?
Lasso 和 Ridge 都是线性回归模型,但有一个惩罚(也称为正则化)。它们以不同的方式增加了β向量的惩罚。
套索回归
Lasso 对你的 Beta 向量的 l1 范数进行惩罚。向量的 l1 范数是该向量中绝对值的和。

向量的 l1 范数(图片由作者提供)
这使得拉索零出一些系数在你的贝塔矢量。我不会详细解释原因,因为这会使本教程过于复杂,并且需要优化方面的背景知识。如果你对为什么会发生这种情况感兴趣,看看这个链接。
简而言之,使用 Lasso 就像是在说:“尽可能实现最佳性能,但如果你发现一些系数没有用,就把它们丢掉”。
岭回归
里奇对β向量的 l2 范数进行了惩罚。向量的 2 范数是向量中*方值之和的*方根。

向量的 l2 范数(图片由作者提供)
这使得 Ridge 防止 Beta 向量的系数达到极值(这在过度拟合时经常发生)。
简而言之,使用 Ridge 就像是说:“尽可能实现最佳性能,但任何系数都不应有极值”。
正则化参数
这两个模型都有一个称为 lambda 的正则化参数,它控制惩罚的大小。在λ=0 时,Lasso 和 Ridge 都成为线性回归模型(我们只是不加任何惩罚)。通过增加λ,我们增加了对β向量大小的约束。这是每个模型进行不同优化的地方,并试图找到给定其自身约束的最佳系数集。
一个例子:波士顿住房数据集
让我们试着看看在数据集的实践中我们会遇到什么问题,以及我们如何用脊和套索来解决这些问题。
要跟进,请访问我的 GitHub 上的这个链接,并简单地按照自述文件中的说明进行操作。我使用的数据集可以从 Kaggle 下载这里。
波士顿住房数据集
波士顿住房数据集来自 1993 年,是机器学习领域最著名的数据集之一。目标要素是波士顿住宅的中值,而要素是相关联的住宅和街区属性。
读取数据集
第一步是读取数据集并打印其前 5 行。

读取数据(图片由作者提供)
首先,我使用一个列表定义列名。然后,我调用 read _ CSV withdelim _ white space = True来告诉 pandas 我们的数据是由空格而不是逗号分隔的, header=None 来表示文件的第一行不是列标题,最后 names=colnames 使用我们之前定义的列表作为列名。
我也用。head(100)仅保留数据集的前 100 行,而不是包含 505 行数据的完整数据集。这样做的原因是我想说明过度拟合,如果我们有更少的数据,这将是更有可能的。实际上,您应该保存完整的数据集(一般来说,数据越多越好)。
列车测试分离
下一步是将我们的数据分为 X(特征)和 Y(目标),然后将这两个数据分为训练集(X_train,y_train)和测试集(X_test,y_test)。我将 80%的数据放在训练集中,20%放在测试集中,这是机器学习问题最常见的分裂之一。

训练测试分割(图片由作者提供)
拟合线性回归模型
在此之后,我对训练数据拟合线性回归模型,并计算测试数据的均方误差(MSE)。最后,我打印出β向量,看看我们的模型的系数是什么样的。

那么,我们能做得比≈6.4 MSE 更好吗?是的。
套索回归
在本例中,我使用一系列的 lambda 值拟合各种 Lasso 回归模型,lambda 值是正则化参数(lambda 值越高,我们对模型的惩罚就越高,即我们限制 beta 向量的绝对值之和)。

我们看到,当λ= 0.3 时,性能最高,MSE 约为 4.2。现在,让我们看看β向量的系数是什么样的。

带套索的贝塔矢量的系数(图片由作者提供)
你可以看到这个模型已经剔除了大约一半的系数。它只保留了 14 个系数中的 8 个,但保留了其中一个相当大的权重,RM 是每个住宅的*均房间数。这是有道理的,因为一处房产的房间数量通常与其价格相关(6 人公寓几乎总是比 1 人公寓贵)。
因此,您可以看到与我们之前讨论的内容之间的联系。我们“告诉”Lasso 找到最佳模型,给出每个系数的权重限制(即“预算”),它“决定”将大量“预算”放在房间数量上,以计算出房产的价格。
现在,让我们看看里奇能做什么。
里脊回归
在这里,我用套索应用与之前相同的步骤。我在这里使用的λ值是不同的。请记住,脊和套索之间的λ值不成比例,即套索的λ值为 5 并不等于脊的λ值为 5。

我们在这里看到,在λ= 3 时,我们能够做得比以前更好,MSE ≈ 4.1,这比 Lasso 和线性回归都好。现在,让我们看看β向量。

山脊的贝塔矢量(图片由作者提供)
我们看到,RM 的系数仍然很高(约 3.76),而所有其他系数都降低了。然而,没有一个像套索一样被归零。
这是两者之间的关键区别:Lasso 通常会将特征清零,而 Ridge 会减少模型中大多数特征的权重。
我邀请您仔细检查每个模型的 Beta 向量,并仔细检查这些值:理解 Beta 向量中发生的事情是理解这些模型的关键。
如何决定用哪一个?
当你有几个预测能力很强的特征而其他特征没有用的时候,Lasso 是很好的:它会将无用的特征清零,只保留变量的子集。
当数据集的预测能力分布在不同的要素上时,岭很有用:它不会将可能有助于进行预测的要素清零,而只是减少模型中大多数变量的权重。
实际上,这通常很难确定。因此,最好的方法是简单地做我上面编码的事情,看看在测试集上使用不同的 lambda 值能得到什么样的最佳 MSE。
更进一步
如果你想更深入地研究数学(我建议你这样做,因为它会帮助你更好地理解正则化是如何工作的),我推荐你阅读特雷弗·哈斯蒂、罗伯特·蒂伯拉尼和杰罗姆·弗里德曼的《统计学习的要素》第 3.4 章。罗伯特·提布拉尼是套索的发明者,也是我在斯坦福大学的机器学习教授。他的书是这一领域的参考,深入研究了数学,同时也给出了正在发生的事情的全貌。
我还建议在不同的数据集上重新实现这些模型,看看哪一个表现最好,并尝试了解为什么会这样。
我希望你喜欢这个教程!让我知道你对它的看法。
随时连接上LinkedIn和GitHub谈论更多关于数据科学和机器学习的话题并关注我上 中 !
Python 中多个仓库的最后一英里交付
原文:https://towardsdatascience.com/last-mile-delivery-from-multiple-depots-in-python-26c4325407b4
使用 PuLP 和 VeRoViz 进行数学建模、求解和可视化

Marcin Jozwiak 在 Unsplash 上拍摄的照片
随着网上购物的快速增长,公司面临着对快速、低成本交付的不断增长的需求。最后一英里配送是指供应链的最后阶段,包裹从仓库送到顾客家门口。这是一个复杂的战术问题,涉及共同确定如何将包裹分配给卡车以及如何将卡车发送给客户。这也是一个非常昂贵的问题,根据最*的估计,最后一英里的运输成本占总运输成本的 53%。这强调了生成高效交付计划的需要。
这个问题的经典形式涉及到一个单一的仓库(通常是一个仓库),所有的卡车都从这里装载并发送出去。一个更复杂的版本涉及多个彼此靠*的仓库,例如,当零售连锁店从商店位置发货时。在这种情况下,给定的客户可能由多个仓库提供服务,因此公司还必须确定哪些仓库将向哪些客户发货。有时,没有一个仓库会提供客户订单中的所有商品,因此需要将订单拆分到多个仓库。
下面我们将讨论如何使用整数规划 (IP)对这种更复杂的多车场问题形式进行建模和求解。这个问题有以下几个方面:
- 有一组卡车、仓库、客户和产品。
- 每个客户都订购了特定数量的每种产品,每个仓库都有一定数量的每种产品。
- 每辆卡车都恰好位于一个仓库(这意味着它的路线总是从其基地开始和结束)。此外,卡车不需要完全相同——每辆卡车可能有不同的容量和每英里的成本。
目标是同时确定 1)要从每个仓库运送到每个客户的产品,2)如何将包裹分配给卡车,以及 3)如何将每辆卡车发送给其客户,所有这些都以尽可能实现最低总交付成本的方式进行。
我们将使用纸浆实现并解决一个 IP 模型,并使用维罗维兹来可视化卡车路线。首先,我们导入必要的库:
一个示例场景
一家家具公司在弗吉尼亚州弗雷德里克斯堡有两个仓库,有八个客户订单需要交付。数据和地图如下所示。注意:**nodes array变量是用 VeRoViz Sketch Tool 准备的,它支持位置数据的图形化创建并导出到 Python。

场景图:蓝色标记表示仓库,橙色标记表示客户。
建模问题
虽然有许多方法可以解决这个问题,但我们将建立并解决一个整数规划模型。这给出了问题的精确数学规范,并允许中等大小的问题实例使用现成的解算器进行优化解决(尽管超出了我们的范围,但较大的实例通常无法使用现成的解算器快速解决,需要专门设计的解算算法)。我们从模型输入开始:

接下来,我们定义我们的决策变量:

最后,我们定义优化模型:

在这个模型中,我们希望最小化的目标函数(1)就是所有旅行费用的总和。(2)中的约束确保对于每个位置,如果特定卡车到达该位置,则该卡车也离开。(3)中的约束确保没有卡车离开不是其基地的仓库。(4)中的约束确保每个客户得到他们订购的所有产品。(5)中的约束确保在任何路线中不出现子电路。由于形成一个回路的一组位置将具有相同数量的边作为节点,我们防止这种情况在每辆卡车的每个可能的非空客户子集中发生。(6)中的约束确保对于每个仓库和产品,从该仓库装载到卡车上并运送给客户的产品的总单位不超过仓库的可用性。(7)中的约束确保没有任何单位的产品被装载到卡车上并运送给客户,除非卡车拜访客户。(8)中的约束条件确保每辆卡车装载的产品总量不超过其容量。最后,( 9-10)中的约束指定了决策变量的定义域(二进制用于 x 变量; u 变量的非负整数)。
为了方便和可重用,我们将创建一个 Python 函数,使用 PuLP 为特定的输入数据构建这个模型的实例:
解决例题
既然我们已经有了公式化的模型,我们可以使用它来为我们的场景找到一个最优的解决方案。下面我们使用纸浆附带的 CBC 解算器。这可能需要 15-45 秒才能找到最佳解决方案。如果您可以使用更强大的 CPLEX 解算器,您可以使用注释掉的行来更快地得到解决方案。
运行该命令会得到以下输出消息:

提取并查看卡车路线
现在,我们需要从求解模型的决策变量中提取卡车路线。对于每辆卡车,我们想知道它的停靠点以及在每个停靠点交付哪些产品。为了获得这些信息,我们需要筛选非零决策变量。
这构成了以下卡车路线:

请注意,两辆卡车(T2 和 T4)拜访了客户 C1,因此产生了一个拆分订单。考虑到同时的客户需求和可用的资源,这被证明是一个最优的决策。例如,当一个订单包含一组在任何一个仓库都找不到的商品时,这也是必要的。
可视化卡车路线
作为我们的最后一步,我们使用 VeRoViz 为卡车路线创建一个漂亮的可视化:

结论
虽然这个问题可能有许多变化,但是这个例子说明了我们如何使用整数规划来建模和解决这样的问题。它还展示了如何使用 Python 将强大的组件如 PuLP 和 VeRoViz 粘合在一起,快速创建有用的决策支持系统。送货愉快!
latexify——清理代码以方便读者的答案
如何让你的代码看起来很漂亮!

JESHOOTS.COM在 Unsplash 上拍照
任何代码的文档都是关键。大多数人希望阅读你的代码,以理解你的方法和你用来解决问题的操作。在阅读别人的代码时,我肯定遇到过的意大利面条式代码(一堆没有注释的函数,只是松散地扔在笔记本里)很多次,但如果我说我自己不会犯同样的错误,那我是在撒谎。阅读这些代码可能是一件麻烦的事情,而且一旦完成就会变得很难理解。修复你的代码并为其他用户清理它,这对于把你的想法传达给其他人是很重要的,同时也允许他们理解和利用你创建的代码!
清理代码的方法
有许多方法可以清理他们的代码。今天,我们将从意大利面条式代码到漂亮的编写代码,华丽的输出勾勒出你的数学方程式。
清理 1 →添加文档
首先,假设您正在创建一个勾股定理函数。首先,您创建两个函数来定义边(显然您可以将 a 和 b 设置为整数值)。
def side_1(a):
side1 = a return side1def side_2(b):
side2 = b return side2
现在,让我们为勾股定理创建一个函数。
def pythagoreanTheorem(a,b):
c = math.sqrt(a**2 + b**2)
return c
将所有这些放在一起,但是现在用注释将真正清理代码,使它对用户来说更容易理解。代码最初允许边 a 和 b 为负值,因此,如果给出负值,异常处理会引发错误。此外,我们将摆脱那些无用的功能,我们开始收集双方,只是让用户输入双方,以减少代码(即。精益代码)。最后,我们可以重命名该函数,以便更明确地解释它的作用,并添加文档来解释该函数的假设。
import math
def hypotenuse_frm_pythagThrm(side1: float ,side2: float) -> float:
"""Assuming a the triangle has a 90 degree angle.
Arugments
---------
side1 (float) : A float value for the first side of a triangle
side2 (float) : A float value for the second side of a triangle
Returns
--------
side3(float): The calculated hypotenuse
""" if side1 =<0:
raise ValueError('A triangle can not have a negative length!')if side2 =<0:
raise ValueError('A triangle can not have a negative length!') side3 = math.sqrt(side1**2 + side2**2)
return side3
现在,看看那个!尽管我们使用的是简单的函数,但是仅仅添加一些文档、异常处理和删除无用的函数就已经使我们的代码看起来更加美观了。包含异常处理的额外步骤确保人们理解为什么他们会破坏你的代码。什么能让这段代码看起来更好?用 latex 字体转换我们代码的输出。
清理 2 → Latexify
正如我所说的,我们可以使用 latex 格式清理 python 数学函数。latex 格式受到各种 STEM 学科的高度重视,并在发表关于数学/工程过程的研究和写作时使用。最初,我们把勾股定理概括为
def pythagoreanTheorem(a,b):
c = math.sqrt(a**2 + b**2) return c
现在,如果我们在这个函数上使用 latexify 会发生什么?(注意:Latexify 对我的文档函数不起作用,但如果您将函数简化为 return 语句,它将起作用。在您的代码中这样做并隐藏输入,只显示输出函数仍然是有帮助的。
[@latexify](http://twitter.com/latexify).with_latexdef pythagoreanTheorom(a,b):
return math.sqrt(a**2 + b**2)pythagoreanTheorom
运行该单元后,我们得到以下输出(在 Jupyter 笔记本中):

Jupyter 中的输出(图片来自作者)
就这样,我们让我们的代码变得美丽!使用这种格式在你的代码中表达你的功能对任何阅读或使用你的代码的人来说都是非常有用的。它不仅有助于克服阅读用计算机语言编写的数学方程的困难,而且可以清楚地概述方程是如何运算的,这对那些努力理解方程运算的人非常有帮助。
结论
不仅编写基本正确的代码很重要,而且记录下来并使其对查看者清晰可见也很关键,这样其他人就可以理解您的工作以及您是如何得出各种结论的。 Latexify 是一个 API,可以帮助读者勾勒出数学方程,否则在编程语言格式中很难阅读和理解。在出版你的下一部作品之前尝试一下,我保证人们会非常感激你采取的额外步骤!
如果你喜欢今天的阅读,请关注我,并告诉我你是否还有其他想让我探讨的话题!如果你还没有中等账号,就通过我的链接 这里 报名吧!另外,在LinkedIn上加我,或者随时联系!感谢阅读!
加速收敛的解空间技术:介绍

弗洛里安·里德在 Unsplash 上拍摄的照片
三个门我都要了!
作者对天魔堂问题的建议解决方案
机器学习中的许多策略涉及解空间ω的迭代搜索。我们从初始解ω₀开始,并更新ωₙ以最小化目标函数 f(x) ,

为了在合理的时间内找到全局解,我们必须采用随机方法:在 ωₙ 的ω-邻域内随机选择 ωₙ₊₁ ,同时选择f(ωₙ₊₁)<f(ωₙ)。

作者图片
收敛速度是在每个点局部确定的,同样地, f(x) 的拓扑构成了追求全局最优的主要瓶颈。
让我们考虑一个物理例子,让你做随机行走。假设有一个带有二十个未知开关{1,…,20 } =ω的断路器,我们想要为一个特定插座识别正确的开关 ω* 。在断路器和插座之间行走需要一分钟,所以我们对检查所有 20 个开关感到不满意。

作者图片
为了测试开关,我们在打开和关闭之间切换其状态,并检查插座是否也被切换。显然,不超过 20 次开关的翻转就足以识别这个插座。让这成为我们的直接方法,用预期的时间来确定出口

收敛到正确的交换机是显而易见的,但是我们检查插座的次数比需要的多!在这里,我们的搜索涉及在开关ω= {ω₁,…,ω₂₀ }的规范空间中独立状态之间的字面行走,并且我们降落在一个*ω**∈ω上,该ω通过连续的选择 αₙ 来识别出口

事实证明,我们可以通过走路做得更好——不,把你的脚拿开!而是在一个扩大的空间里。使用相同的切换开关和检查插座电源的基本程序,我们现在将开关分组。

作者图片
比如说,在五个一组的分组中进行选择,然后一旦除了一个组之外的所有组都被排除,就拨入单个交换机。
因此,我们保留了我们的算法,但是通过在随机行走中附加额外的状态来访问,我们期望采取更少的步骤。这里我们分而治之,但一般来说,我们应用任何利用我们在解空间中访问的点的相对结构的技术。
线性代数中的预处理
虽然上面的想法非常广泛,并且经常用于加速复杂图形上的遍历,但是线性代数中的预处理提供了一个为什么我们增加解空间的简洁的例子。考虑求解中的最速下降算法

求解等价系统

意味着我们在一个单一步骤中接*真实解(20,15 ),就像我们在两个步骤中所做的那样,而没有将解空间缩放


直接和预处理变量, x₀ = 0
由于 0 处的梯度在缩放下比没有缩放时更紧密地适合残差,所以收敛立即加快。带渐变的俄罗斯方块,如果你愿意的话。
现在,回到寻找断路器开关的问题,我们最终找到了解决方案ω**∈ω⊂ω,只需首先通过步进

在连续检查每个单独开关的最坏情况下,我们最多用二十步来识别开关;而ω*中的分组方法

我们最多检查三个组,并且只留下来验证剩余的五个独立开关。因此,通过以下九个步骤保证了收敛

随机漫步和超越
除了方程组和最优化问题,我们在马尔可夫链蒙特卡罗中发现了许多解空间技术的例子。在确定正确的断路器开关时,我们发现了一种更好的方法来执行随机行走,即在整组开关之间引入中间步骤,从而缩短解决问题的预期时间。

纽约的国会选区(维基百科,公共领域)
在国会选区重划分析中,计划是根据其在所有选区分配排列中对稳定分布的偏离来审查的。从城镇到地区的原始地图开始,我们问一个提议的计划是否是任意选择的。具体来说,在模拟分配过程到接**稳的程度时,有没有可能提议的计划被选择用于一个非常特殊的目的?它会在邻*地区交换城镇时自然出现吗?
下一次,我们将介绍元图作为一种加速随机游走收敛的方法,展示如何从初始状态空间ω生成它,以及它如何将一些 np 问题转化为:-p——稍后见。
如何使用简单的 Web API 和 GPU 支持在 Docker 中运行稳定的扩散
原文:https://towardsdatascience.com/launch-a-web-api-for-stable-diffusion-under-45-seconds-bbd88cfe41d8
启动 web API,在 45 秒内实现稳定传播

稳定扩散产生的虚构黑山羊
Stability Diffusion是一种潜在的文本到图像扩散模型,这要归功于 Stability AI 和 Runway 的合作。它具有最先进的文本到图像合成功能,内存需求相对较小(10 GB)。稳定扩散对其他扩散模型进行了一些改进,以实现这种效率,但这些创新超出了本文的范围,未来的文章将介绍如何在 TensorFlow 中训练扩散模型,并从技术上详细说明其内部工作原理。
Divam Gupta 将稳定扩散从原始权重移植到 TensorFlow / Keras ,这篇文章重点介绍如何使用简单的 web API 和 GPU 支持在 Docker 映像中运行它。
趣闻:本帖的特色形象也是稳定扩散产生的。
它是如何工作的?
我决定在 TensorDock Marketplace 的 GPU 上运行它。,它应该可以在其他机器上工作,几乎不需要做任何改变,但这是我最*推出的 TensorDock Marketplace 的实验的直接结果。它目前处于公开测试阶段,但我已经喜欢上了他们的创新理念,让高性能计算的使用大众化。除了他们的价格合理的核心云 GPU 服务之外,Marketplace edition 还作为一个市场,将客户和 GPU 提供商聚集在一起。主机,即那些有备用 GPU 的主机,可以将它们租给客户,包括独立研究人员、初创公司、业余爱好者、修补匠等。价格极其便宜。根据 TensorDock 的说法,这也让主机赚取 2 到 3 倍的采矿利润。为了比挖掘无意义的密码更好的目的。
服务器可根据所需的 RAM、vCPU 和分配的磁盘进行定制,启动时间太短,大约只有 45 秒。您可以选择从安装了 NVIDIA 驱动程序和 Docker 的最小 Ubuntu 映像开始,或者您也可以使用配置了 NVIDIA 驱动程序、Conda、TensorFlow、PyTorch 和 Jupyter 的成熟映像进行实验。我选择使用 Docker 而不是 Conda 或虚拟环境来隔离我的人工智能项目,我将从安装在 TensorDock Marketplace 上的 Docker 的最小图像开始。
首先,我将展示如何将 web API 封装成 Docker 映像中的稳定分发服务。然后,我将一步一步地描述如何在 TensorDock GPU 上提供它。如果你想在 45 秒内启动并运行,你可以直接跳到“Ok,演示给我看如何运行”部分,或者你可以选择查看 GitHub repo 。
让我们来整理一下!
TensorFlow 为每个版本提供官方预建的 Docker 图像来启动您的 Docker 文件。如果您配置了 NVIDIA Container Runtime 并且更喜欢 TensorFlow 提供的支持 GPU 的 Docker 映像,您可以在 GPU 支持下立即运行 TensorFlow 代码,而不会遇到 CUDA 或 CUDNN 的问题。幸运的是,TensorDock 的最小 Ubuntu 图像带有 NVIDIAContainerRuntime 支持。
为了稳定的扩散,我们以tensorflow/tensorflow:2.10.0-gpu开始我们的 Dockerfile。然后我们安装稳定的扩散需求和 FastAPI 来服务一个 web API。最后,我们复制包含 web API 的app.py,并将其配置为在容器启动时运行:
from tensorflow/tensorflow:2.10.0-gpu
RUN apt update && \
apt install -y git && \
pip install --no-cache-dir Pillow==9.2.0 tqdm==4.64.1 \
ftfy==6.1.1 regex==2022.9.13 tensorflow-addons==0.17.1 \
fastapi "uvicorn[standard]" git+https://github.com/divamgupta/stable-diffusion-tensorflow.git
WORKDIR /app
COPY ./app.py /app/app.py
CMD uvicorn --host 0.0.0.0 app:app
撰写以便于配置和启动
当你有一个docker-compose.yml文件时,Docker 就更有用了。因此,您可以简单地运行docker compose up,用一个命令就可以启动并运行一切。它在处理多个容器时尤其出色,但对于管理单个容器也非常有用。
下面的docker-compose.yml文件定义了几个环境变量,并将它们传递给容器。它还支持容器内部的 GPU 访问,并根据 TensorDock Marketplace 的要求正确配置 Docker 网络。在其他*台上,您可能需要删除有关网络的这一部分
version: "3.3"
services:
app:
image: myusufs/stable-diffusion-tf
build:
context: .
environment:
# configure env vars to your liking
- HEIGHT=512
- WIDTH=512
- MIXED_PRECISION=no
ports:
- "${PUBLIC_PORT?Public port not set as an environment variable}:8000"
volumes:
- ./data:/app/data
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [ gpu ]
networks:
default:
driver: bridge
driver_opts:
com.docker.network.driver.mtu: 1442
编码时间到了
现在我们已经准备好编写我们的 webAPI 了。我们从所需的导入开始,然后用由docker-compose.yml传递的环境变量创建一个图像生成器和 FastAPI 应用程序。
import os
import time
import uuid
from fastapi import FastAPI
from fastapi.exceptions import HTTPException
from fastapi.responses import FileResponse
from PIL import Image
from pydantic import BaseModel, Field
from stable_diffusion_tf.stable_diffusion import Text2Image
from tensorflow import keras
height = int(os.environ.get("WIDTH", 512))
width = int(os.environ.get("WIDTH", 512))
mixed_precision = os.environ.get("MIXED_PRECISION", "no") == "yes"
if mixed_precision:
keras.mixed_precision.set_global_policy("mixed_float16")
generator = Text2Image(img_height=height, img_width=width, jit_compile=False)
app = FastAPI(title="Stable Diffusion API")
然后,我们为我们的/generate端点定义请求和响应主体。值是不言自明的,所以这里不需要额外的文字。
class GenerationRequest(BaseModel):
prompt: str = Field(..., title="Input prompt", description="Input prompt to be rendered")
scale: float = Field(default=7.5, title="Scale", description="Unconditional guidance scale: eps = eps(x, empty) + scale * (eps(x, cond) - eps(x, empty))")
steps: int = Field(default=50, title="Steps", description="Number of dim sampling steps")
seed: int = Field(default=None, title="Seed", description="Optionally specify a seed for reproduceable results")
class GenerationResult(BaseModel):
download_id: str = Field(..., title="Download ID", description="Identifier to download the generated image")
time: float = Field(..., title="Time", description="Total duration of generating this image")
最后,是时候写我们的端点了。/generate端点将接受一个文本提示以及几个配置值来控制生成,并且它将用生成的图像的惟一 ID 来响应。然后,可以通过/download端点下载图像。生成的图像保存在docker-compose.yml中配置为 Docker 卷的目录中。
@app.post("/generate", response_model=GenerationResult)
def generate(req: GenerationRequest):
start = time.time()
id = str(uuid.uuid4())
img = generator.generate(req.prompt, num_steps=req.steps, unconditional_guidance_scale=req.scale, temperature=1, batch_size=1, seed=req.seed)
path = os.path.join("/app/data", f"{id}.png")
Image.fromarray(img[0]).save(path)
alapsed = time.time() - start
return GenerationResult(download_id=id, time=alapsed)
@app.get("/download/{id}", responses={200: {"description": "Image with provided ID", "content": {"image/png" : {"example": "No example available."}}}, 404: {"description": "Image not found"}})
async def download(id: str):
path = os.path.join("/app/data", f"{id}.png")
if os.path.exists(path):
return FileResponse(path, media_type="image/png", filename=path.split(os.path.sep)[-1])
else:
raise HTTPException(404, detail="No such file")
好的,告诉我怎么跑
不要让这些步骤吓到你——这只是从注册到提出请求的整个过程的一步一步的详细介绍。应该不会超过 10 分钟。
- 注册并登录 TensorDock 市场。
- 转到订单页面,选择一台提供至少 10 GB 内存的 GPU 的物理机。我建议买一个提供 RTX 3090 的。
- 这将打开一个允许您配置服务器的模型。我的建议如下:
- 选择每个 GPU 型号的数量:1 个 GeForce RTX 3090 24 GB
- 选择内存容量(GB): 16
- 选择虚拟 CPU 的数量:2
- 选中复选框,最多可转发 15 个端口。您将能够通过这些端口访问您的服务器。
- 在“自定义您的安装”下,选择“Ubuntu 20.04 LTS”。
- 为您的服务器选择一个密码,并为其命名,例如“stable-diffusion-api”
- 点击“部署服务器”,瞧!你的服务器将在几秒钟内准备好。
- 当您看到成功页面时,请单击“下一步”查看详细信息。
- 查找您的服务器的 IPv4 地址。这可能是真实的 IP,也可能是类似
mass-a.tensordockmarketplace.com的子域。 - 找到映射到内部端口 22 的外部端口。您将使用它 SSH 到您的服务器。例如,可能是 20029 年。
- 使用以下标准连接到您的服务器,例如:
ssh -p 20029 user@mass-a@tensordockmarketplace.com
Docker 已经配置了 GPU 访问,但是我们需要配置 Docker 网络来进行外部请求。
- 将此存储库和 cd 克隆到其中:
git clone https://github.com/monatis/stable-diffusion-tf-docker.git && cd stable-diffusion-tf-docker
- 将
daemon.json复制到现有的/etc/docker/daemon.json上,并重启服务。不要担心——这只是为 MTU 值添加了一个设置。
sudo cp ./daemon.json /etc/docker/daemon.jsonsudo systemctl restart docker.service
- 为您想要使用的公共端口设置一个环境变量,运行 Docker Compose。我们的
docker-compose.yml文件将从环境变量中提取它,它应该是您配置的端口转发之一,例如 20020。
export PUBLIC_PORT=20020docker compose up -d
- 一旦它启动并运行,进入
http://mass-a.tensordockmarketplace.com:20020/docs获取 FastAPI 提供的 Swagger UI。 - 使用
POST /generate端点生成扩散稳定的图像。它会用一个下载 ID 来响应。 - 点击
GET /download/<download_id>端点下载您的图像。
结论
我们能够在 TensorDock Marketplace 的云 GPU 上运行稳定扩散,这是最先进的 tex-to-image 模型之一。它非常便宜,因此适合实验和副业。我将继续在我的一个副业项目中使用它,后续的帖子将提供 TensorDock Marketplace 上培训工作的一步一步的介绍。
惰性数组及其在数据科学中的潜在应用
什么是惰性数组,如何在数据科学环境中使用它们?

介绍
数据科学就是寻找适合完美模型的完美数据。有时,某些模型可能比其他模型需要更多的数据,这是由于偏差的原因。如果你想知道更多关于一个给定的模型是如何不足或过度的,以及偏见意味着什么,我有一整篇文章,你可以在这里阅读:
也就是说,如果你有一个巨大的模型,有很多非常好的、统计上显著的特征,但仍然不合适,会发生什么?换句话说,如果您的模型急需数据,但由于硬件的限制,您似乎没有办法输入更多的数据,该怎么办呢?不管你有什么样的系统,它的内存容量总是有限的。也许你碰巧在一台内存很小的电脑上工作,比如 Chromebook 或类似的东西。对于这些情况,我有几个选项,您必须补救您的数据问题。
第一种选择是利用分解。分解可以采用许多不同的维度和特性,并将它们压缩成本质上一个可读的特性。这将节省大量内存,也有利于机器学习模型的性能和分析,但是像分解这样的事情是有限制的,它不会完全解决这些问题。我确实有一篇关于奇异值分解的文章,这可能是最恰当和最流行的分解形式,如果你想了解更多关于这个主题的知识,你可以在这里阅读:
[## 深入奇异值分解
towardsdatascience.com](/deep-in-singular-value-decomposition-98cfd9532241)
下一个方法是把数组变成表达式。这很像随机投影,但是当你真正开始考虑它的时候,它确实变得有点困难。你怎么能做出一个通用的表达式来表示整个数组的数据呢?今天我想考虑的就是这个,以及这些表达式数组的应用,它们可以帮助机器学习领域的人。此外,我决定在本文中编写我们自己的惰性数组,我在一个笔记本中快速完成了这项工作,下面是该笔记本文件的链接:
https://github.com/emmettgb/Emmetts-DS-NoteBooks/blob/master/Julia/lazyarrays.ipynb
顺便说一句,两年多的笔记本提交到那个仓库。2022 年将是我在 Medium 上写博客的第三年。非常感谢大家这两三年的美好时光!
什么是懒数组?
惰性数组是作为表达式或函数存储在内存中的数组(相同的区别,只是类型不同)。举个例子来形象地说明这到底是什么意思可能会有帮助。考虑以下阵列:
x = [5, 10, 15, 20]
这个数组中的每个元素都可以被 5 整除,数据是线性的。因此,我们可以用经典的斜率式公式来计算,当然,这不需要任何 y 截距。
xbar(n) = n * 5
正如索引该数组将提供存储在该位置的值的返回一样,
x[1]5
如果我们像调用这个索引一样调用这个新函数,我们将得到相同的确切回报:
xbar(1)5
现在我们可以将数组的维数限制在 4,所以虽然数组不能调用超出这些界限的索引,但是函数可以。我们可以通过简单地存储一些值来解决这个问题,就像我们要做一个 LazyArray 构造函数…像这样举个例子:
mutable struct LazyArray
n::Int64
function LazyArray(is::Any ...)
new{typeof(is[1])}(length(is)) end
end
对于那些不写 Julia 的人来说,这可能有点令人困惑,但是现在我们有了一个新类型 LazyArray,它的属性是 n,即值的长度。我们需要完成的最后一件事是一个表达式。通常,LazyArray 构造函数会被提供一个表达式和一些需要创建的东西。这是因为对数组的某些函数规范形式进行逆向工程可能很困难。也就是说,如果没有无限多的可能性,这将是困难的。
无论如何,考虑到这实际上是不可能的,我想考虑我们正在从一个数字数组,1–100,000,000,创建一个惰性数组,这应该给出一个好主意,为什么我们应该首先使用惰性数组。
mutable struct LazyArray
n::Int64 f::Function
function LazyArray(is::Any ...) f(x) = x * 1
new{typeof(is[1])}(length(is),f)end
end
考虑到我们的实现,我们可能还希望允许从一个常规数组构造一个惰性数组。
mutable struct LazyArray
n::Int64f::Function
function LazyArray(is::Any ...)f(x) = x * 1
new(length(is),f)end function LazyArray(is::AbstractArray)
f(x) = x * 1 new(length(is), f)
end
end
这是重复的代码,因为我们要调用数组类型,而不是只调用 any 的一个…它被求值为一个元组。我们可以通过反转这些调用来避免代码重复,让 LazyArray(::AbstractArray)在我调用的 LazyArray(::Any …)中的值之后被调用,并且所提供的值被转换成一个数组。请允许我演示:
mutable struct LazyArray
n::Int64f::Functionfunction LazyArray(is::AbstractArray)
f(x) = x * 1new(length(is), f)
end
function LazyArray(is::Any ...)
return(LazyArray([i for i in is]))end end
我们甚至可以进一步将这个最小化到
mutable struct LazyArray
n::Int64f::Functionfunction LazyArray(is::AbstractArray)
f(x) = x * 1new(length(is), f)
end
LazyArray(is::Any ...) = LazyArray([i for i in is])end
现在我们有了函数,f,我们可以继续讨论这些函数如何在前端工作。每当调用索引时,我们将在被调用的点上计算给定的索引。在 Julia 中,这都是通过扩展 Base.getindex()方法来完成的。
import Base: getindex
基本上,提供给这个方法的任何索引都将通过我们的 f 函数运行。假设我们希望新的惰性数组与之前的数组长度相同,如果索引大于我们想要的值,我们也可能希望抛出
function getindex(a::LazyArray, i::Int64)
if i > a.n
throw(IndexError("Index outta bounds, yo"))
end
a.f(i)end
同样,我将对 UnitRange 做同样的事情。
function getindex(a::LazyArray, i::UnitRange)
if i[2] > a.n
throw(IndexError("Index outta bounds, yo"))
end
[a.f[val] for val in i]end
现在让我们创建一个新的范围,这将是我们的 x 数组,我们将通过它本身的惰性版本。
x = Array(1:1000000)lazyx = LazyArray(x)
现在让我们评估和比较这两个数组的内存使用情况,然后尝试一些索引。
varinfo()

我们看到,典型的 x 1,000,000 元素向量方法占用了 7.629 兆字节的内存。另一方面,我们的懒惰版本只占用 16 字节的内存。惰性版本的优点还在于,不管 x 有多长,我们仍然会有相同的内存使用量。
x = Array(1:100000000)lazyx = LazyArray(x)
varinfo()

虽然我们的 lazyx 只需要 16 个字节来存储,而我们最初的 x 几乎需要整整 10 亿字节来存储,但它们在数学上是等价的。Lazyx 只是 x 的一种规范形式,一种数学压缩的代数表示。这是通过索引揭示的,它保持完全相同:
x[500]500lazyx[500]lazyx[1:1000]MethodError: no method matching getindex(::var"#f#1", ::Int64)
哎哟
不知道为什么我试图索引函数而不是调用它。
function getindex(a::LazyArray, i::UnitRange)
if i[2] > a.n
throw(IndexError("Index outta bounds, yo"))
end
[a.f(val) for val in i]
endlazyx[1:1000] == x[1:1000]true
对于我们的实现,下一步将是实现一些基本函数,比如 length,分派一些函数,比如。+,等等。添加到一个表达式列表中,最后构建一个迭代器或者进行某种迭代绑定。
惰性数组的实现
既然我们已经编写了自己的惰性数组,并因此完全理解了它们的作用和工作原理,我们需要考虑它们实际上是如何实现的,它们是如何派上用场的,以及它们的优秀之处。惰性数组的一个重要问题是,它们需要给定数组的某种代数形式。对于一般的数据源来说,这未必是一件难以置信的事情。在线性模型上可能很容易,但在非线性模型上肯定不容易。这限制了数据科学中的潜在应用,因为数据通常是非线性的,每当事情是这样时,表达式可能很难评估。
也就是说,仍然有一些惰性数组的实现,以及这些实现的一些问题。我想谈的第一个是 Python 的 Dask 框架的实现。这个实现可以在 dask.array 中找到,根据我的经验,它非常健壮。dask 系统主要针对集群,而不是单一系统。也就是说,对于像典型的数据科学过程这样更加个人化和声明性的东西,我们真的不需要将这些东西分布在一个计算机集群中——此外,与一组硬件的密切关系也不是一件坏事。
惰性数组的 dask 实现实际上非常棒,非常健壮。总的来说,dask 软件包相当不错。我真正喜欢的是,我们可以向计算中添加额外的表达式,然后一次性执行所有的操作。
在 Julia 中还有另一个类似的实现,名为 LazyArrays.jl。当然,这个实现更接*于我们上面编写的示例,以演示这些数组的功能和内存使用,但这一点仍然有效。这个实现与 dask 非常相似,但是我要说的是 dask 模块内部的一些语法变得更简单了。我认为这可以归结为 dask 是一个拥有大量赞助商的庞大团队,而 LazyArrays 团队只是普通的朱利安一起创造东西。
结论
问题是,现在我们知道了什么是惰性数组,以及它们如何有益于数据科学,那么这些数组对 DS/ML 有什么价值呢?像这样的惰性数组的概念在一个特定的环境中非常好:
我们更关心记忆,而不是计算。
特别是在像 Julia 这样的语言中,它提供了更具命令性的静态类型的低级语言的速度和高级声明性语法的便利,这是我们可能要做的一个权衡。之前的 10,000,000 个值的数组几乎使用了千兆字节的数据。我的电脑总共只有 8g 的内存。也就是说,在执行期间,一个维度占用了整整 10 亿字节,也就是我总内存的 1/8,这是一个很大的问题。在这种情况下,只要我们可以用表达式计算数组,这将非常适用于我正在进行的任何项目。
我想到的一个真正伟大的应用是在工程特征中。也许我们甚至可以以表达式的形式读入值,但肯定主要形式是通过函数或表达式进行工程设计。这样我们可以说有一个惰性数组,它的起始表达式是
df["feature"] + df["feature"]
我认为惰性阵列真的很酷,可能会对数据科学产生相当大的影响。坦率地说,我喜欢用数组的代数表示法做大量的实验。这似乎是一个伟大的方式,许多东西都有可能被压缩。我一直在思考一些潜在的方法,也许我可以将这些方法应用到我的数据管理框架中,但是我们将会看到结果,因为我现在在每个地方都有很多工作要做。如果我要实现它,我希望它更有用,基本上允许任何值被转换成表达式。那么它可能是完全不费力的,你甚至不需要考虑它。感谢您阅读我的文章,我希望这个关于惰性数组的小项目既有趣又有趣。
LazyProphet:用 LightGBM 进行时间序列预测
原文:https://towardsdatascience.com/lazyprophet-time-series-forecasting-with-lightgbm-3745bafe5ce5
都是关于功能的

当我们考虑时间序列空间中的提升树时,通常是关于 M5 竞赛,其中十大参赛作品中有很大一部分使用了 LightGBM。然而,当观察单变量情况下提升树的性能时,没有大量的外部特征可以利用,它们的性能已经…粗糙了。
直到现在。
第一部分:简介
首先,我应该提出一些警告。M4 比赛中的亚军确实使用了提升树。然而,它是作为一个元模型来集成其他更传统的时间序列方法。我在 M4 看到的所有基准测试都相当糟糕,有时甚至无法与天真的预测者相提并论。我在这里使用的主要资源来自为 Sktime 包和他们的论文【1】所做的出色工作:

马库斯·洛宁、弗朗茨·基拉里的表格,摘自他们的 Sktime 论文
任何带有“XGB”或“RF”的模型都使用基于树的集合。我们确实看到一个例子,Xgboost 在每小时的数据集中提供了最好的结果,为 10.9!然后,我们意识到这些只是他们在他们的框架中尝试的模型,M4 的获奖者在同一数据集上得了 9.3 分…
试着记住这张图表中的一些数字,特别是 XGB-s 的每小时数据集的 10.9 和每周数据集中树木的“最佳”结果:RF-t-s 的 9.0。
我们的目标是使用快速 LightGBM 程序彻底粉碎这些数字,该程序适合各个时间序列,在速度上与 stat 方法相当。
所以,没时间优化了。
听起来相当困难,我们的第一想法可能是我们有来优化我们的树。增强树非常复杂,我们正在拟合单个数据集,因此它们必须有不同的参数。
但都是功能的问题。
第 2 部分:功能
当查看单变量空间中树的其他实现时,您会看到一些特征工程,如宁滨,使用目标的滞后值,简单的计数器,季节性虚拟,可能还有傅立叶基函数。如果你想后悔安装助推树,并希望你坚持使用指数*滑器,这一切都很好。主题是我们必须特征化我们的时间元素,并将其表示为表格数据以提供给树,我们的实现:lazy profet也不例外。但是,我们有一个额外的特征工程元素,我在任何地方都没有见过(尽管它非常简单,所以这不可能是新颖的)。
我们将…“把这些点连接起来”。
很简单,我们连接我们系列的第一个点,并在中间拟合一条线到另一个点,然后将该点连接到最后一个点。重复几次,同时改变哪一点作为“拐点”,你就成功了。
希望这张图能很好地说明这一点。蓝线是一个时间序列,其他线只是“连接这些点”:

作者图片
原来,这些只是加权的分段线性基函数。(再说一次,如果你知道这方面的任何研究,一定要让我知道,因为我真的找不到任何关于这个具体实现的东西。)这样做的一个不利之处是,这些线的外推可能会有偏差。为了处理这个问题,我们将加入一个“衰减”因子,它只是惩罚从中点到终点的每条线的斜率。
就这样,就这样。把这些坏男孩连同滞后的目标值和傅立叶基函数一起扔进去,你就成功了!对于某些问题,几乎达到了最先进的性能,而且对我们的要求很低,因此被称为“LazyProphet”。
但是让我们得到一些结果来支持这个观点。
第 3 节:守则
这些数据集都是开源的,并且在 M 竞赛 github 上直播。它被标准训练和测试分割,因此我们将使用训练 csv 进行拟合,而测试 csv 仅用于使用 SMAPE 进行评估。让我们将数据和 LazyProphet 一起导入,如果您还没有安装它,请从 pip 中获取它。
pip install LazyProphet
安装后,让我们开始编码:
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
import pandas as pd
from LazyProphet import LazyProphet as lp train_df = pd.read_csv(r'm4-weekly-train.csv')
test_df = pd.read_csv(r'm4-weekly-test.csv')
train_df.index = train_df['V1']
train_df = train_df.drop('V1', axis = 1)
test_df.index = test_df['V1']
test_df = test_df.drop('V1', axis = 1)
在这里,我们只需导入所有必需的包,并将数据作为每周数据的标准数据帧读入。接下来,让我们创建 SMAPE 函数,该函数将返回给定预测和实际值的 SMAPE:
def smape(A, F):
return 100/len(A) * np.sum(2 * np.abs(F - A) / (np.abs(A) + np.abs(F)))
在我们的实验中,我们将采用所有时间序列的*均值来与其他模型进行比较。对于健全性检查,我们还将获得“天真”的*均得分,以确保我们所做的与竞争中所做的一致。也就是说,我们将简单地遍历数据帧并进行惰性拟合和预测。代码可以通过而不是执行 for 循环来优化,但是这样做也很好!
smapes = []
naive_smape = []
j = tqdm(range(len(train_df)))
for row in j:
y = train_df.iloc[row, :].dropna()
y_test = test_df.iloc[row, :].dropna()
j.set_description(f'{np.mean(smapes)}, {np.mean(naive_smape)}')
lp_model = LazyProphet(scale=True,
seasonal_period=52,
n_basis=10,
fourier_order=10,
ar=list(range(1, 53)),
decay=.99,
linear_trend=None,
decay_average=False)
fitted = lp_model.fit(y)
predictions = lp_model.predict(len(y_test)).reshape(-1)
smapes.append(smape(y_test.values, pd.Series(predictions).clip(lower=0)))
naive_smape.append(smape(y_test.values, np.tile(y.iloc[-1], len(y_test))))
print(np.mean(smapes))
print(np.mean(naive_smape))
在我们查看结果之前,让我们快速了解一下 LazyProphet 参数。
scale: 这个很简单,简单的看是否缩放数据。默认值为 True,因此我们在这里只是明确说明。seasonal_period:该参数控制季节性的傅立叶基函数,因为这是我们使用的52的周频率。n_basis:该参数控制我们正在申请专利的加权分段线性基函数。这只是要使用的函数数量的一个整数。fourier_order:用于季节性的正弦和余弦对数。ar:使用什么滞后目标变量值。我们可以接受一个倍数的列表,然后简单地传递一个 1-52 的列表。decay:用于惩罚基函数“右侧”的衰减因子。设置为0.99表示斜率乘以(1- 0.99)或 0.01。linear_trend:树的一个主要缺点是它们不能推断出以前数据的界限。我提到过吗?是的,这可能是个大问题。为了克服这一点,有一些针对多项式趋势的即兴测试,如果检测到一个趋势,我们将线性回归拟合为去趋势。通过None表示将会有测试,通过True表示总是去趋势,通过False表示不测试并且从不使用线性趋势。decay_average:使用衰减率时,这不是一个有用的参数。这主要是做奇怪事情的奇怪的黑魔法。试试吧!但是不要用。通过True本质上只是对基函数的所有未来值进行*均。在我的测试中,这在使用 elasticnet 过程时很有用,但在使用 LightGBM 时就没那么有用了。
在我们得到结果之前,让我们继续并适应每小时的数据集:
train_df = pd.read_csv(r'm4-hourly-train.csv')
test_df = pd.read_csv(r'm4-hourly-test.csv')
train_df.index = train_df['V1']
train_df = train_df.drop('V1', axis = 1)
test_df.index = test_df['V1']
test_df = test_df.drop('V1', axis = 1)smapes = []
naive_smape = []
j = tqdm(range(len(train_df)))
for row in j:
y = train_df.iloc[row, :].dropna()
y_test = test_df.iloc[row, :].dropna()
j.set_description(f'{np.mean(smapes)}, {np.mean(naive_smape)}')
lp_model = LazyProphet(seasonal_period=[24,168],
n_basis=10,
fourier_order=10,
ar=list(range(1, 25)),
decay=.99)
fitted = lp_model.fit(y)
predictions = lp_model.predict(len(y_test)).reshape(-1)
smapes.append(smape(y_test.values, pd.Series(predictions).clip(lower=0)))
naive_smape.append(smape(y_test.values, np.tile(y.iloc[-1], len(y_test))))
print(np.mean(smapes))
print(np.mean(naive_smape))
好吧,我们真正改变的是seasonal_period和ar参数。当把一个列表传递给seasonal_period时,它将为列表中的所有东西建立季节基函数。ar进行了调整,以适应新的主要季节周期 24。就是这样!
第四节:结果
还记得上面的 Sktime 结果吗?你其实不必,这里有一张表:

作者图片
因此,LazyProphet 击败了 Sktime 的最佳模型,其中包括几种不同的基于树的方法。在每小时的数据集上,我们确实输给了 M4 的冠军,但*均而言,我们实际上总体上优于 ES-RNN。这里要认识到的重要一点是,我们是用默认参数做到这一点的…
boosting_params = {
"objective": "regression",
"metric": "rmse",
"verbosity": -1,
"boosting_type": "gbdt",
"seed": 42,
'linear_tree': False,
'learning_rate': .15,
'min_child_samples': 5,
'num_leaves': 31,
'num_iterations': 50
}
如果你想改变这些,你可以在创建 LazyProphet 类时传递你自己的 dict。这些甚至可以针对每个时间序列进行优化,以获得更多收益。
但是,让我们将我们的结果与我们的目标进行比较:
- 我们进行了零参数优化(针对不同的季节进行了细微的修改)。
- 我们分别拟合每个时间序列。
- 我们在我的本地机器上“懒洋洋地”用了一分多钟就做出了预测。
- 我们击败了基准测试中的所有其他树方法,甚至*均击败了 M4 的获胜者。
我得说我们相当成功!
你可能会问,“其他数据集的结果在哪里?”
不幸的是,我们的成功没有持续多久。一般来说,其他数据集的数据要少得多,因此我们的方法往往会显著降低性能。据我所知,LazyProphet 往往以高频率和可观的数据量大放异彩。当然,我们可以尝试用一个 LightGBM 模型来拟合所有的时间序列,但是我们可以留到下次再做!
由于我们只是使用 LightGBM,您可以改变目标并尝试时间序列分类!或者使用分位数目标作为预测界限!有很多很酷的东西可以尝试。
如果你觉得这很有趣,我鼓励你看看我用另一种土生土长的方法对 M4 比赛的另一种看法:百里香增强。
参考文献:
[1]Markus lning,Franz Király:《用 sktime 进行预测:设计 sktime 的新预测 API 并应用于复制和扩展 M4 研究》,2020 年;arXiv:2005.08067
LCE:最强大的机器学习方法?
原文:https://towardsdatascience.com/lce-the-most-powerful-machine-learning-method-e8ea77f317d6
介绍最新的基于树的方法及其潜在的进一步发展

彼得·斯洛瓦切克(Petr Slováek)在的上拍摄的照片。
正如在“为什么基于树的模型在表格数据上仍然优于深度学习?”【Grinsztajn 等人,2022】,广泛使用的 基于树的模型在很多情况下仍然是最先进的机器学习方法。最*,【fau vel et al .,2022】提出将性能最好的基于树的集成方法——随机森林【Breiman,2001】和极端梯度推进(XGBoost)【Chen and guest rin,2016】的优势结合起来,并整合了一种补充的多样化方法,使其成为更好的泛化预测器。****
本文首先介绍了 LCE** ,然后在不同的公共数据集上将它的性能与 和 XGBoost 进行了比较。然后,根据用户反馈(在发布后的头几个月有数万次包下载),这篇文章提出了一些想法,以进一步增强 LCE 的能力,并建立可能**最强大的机器学习方法。最后,它解释了如何参与 LCE 的进步。****
概观
Random Forest 和 XGBoost 分别依靠装袋和助推的方法。Bagging 对方差减少具有主要影响,而 boosting 对偏差减少具有主要影响,使得这两种方法互补以解决机器学习模型面临的偏差-方差权衡。因此, LCE 将打包和提升结合起来,并基于分而治之的策略来学习训练数据的不同部分,以捕捉无法在全球范围内发现的新关系。更多关于 LCE 的详细信息,请参考我的文章“ Random Forest 还是 XGBoost?是时候探索 LCE 了。
比较
下面的小节详细介绍了所采用的方法以及使用上述算法的公共实现所获得的结果。为了保持这篇文章的*均长度在中等,比较集中在分类的任务上。未来的工作可以探索回归任务的比较。
评估设置
****数据集基于来自 UCI 机器学习知识库 [Dua 和 Graff,2017]: Avila [De Stefano 等人,2018],乳腺癌[Bennett 和 Mangasarian,1992],Iris [Fisher,1936],MAGIC Gamma Telescope [Dvořák 和 Savick,2007],Nursery [Zupan 等人,1997],Shill Bidding [Alzahrani 和 Sadaoui
表 1 包含数据集及其描述。数据集是在中小型类别中随机选择的,其中基于树的集成方法通常表现更好。
****实现Python 3.10 上使用了以下包:
- 本地级联集合(LCE):0 . 3 . 2 版本中封装lcensemble
- 随机森林(RF):1 . 1 . 2 版本中的 scikit-learn
- XGBoost(XGB):1 . 6 . 2 版本中的包 xgboost
这些实现都与 scikit-learn 兼容,它允许一个独特的管道来进行一致的比较(参见下面的代码)。
超参数优化所有模型的基于树的学习中的经典超参数(max_depth,n_estimators)通过在训练集上进行三重交叉验证的网格搜索来设置。使用 scikit-learn 的模型选择工具 GridSearchCV 执行网格搜索。
max_depth和n_estimators的取值范围是这些方法通常采用的范围。为了公*起见,LCE 用于 XGBoost ( xgb_max_depth)的max_depth值范围与用于评估 XGBoost(【3,6,9】,见 LCE 文档)的值范围相同。
这些经典的超参数被选来进行一般的比较。在特定应用的情况下,考虑另一组超参数可能是有趣的。有关更多信息,请参考每种方法的文档。
度量对于每个模型,报告每个数据集的测试集的准确性。所有数据集的*均排名和获胜/*局数也显示为汇总统计数据。
代码用于比较的 Python 代码如下(格式为黑色 ):
结果
表 1 显示了基于前面介绍的方法获得的结果。每个数据集的最佳精度用粗体表示。

表 1:来自 UCI 知识库的 10 个数据集的精度结果[Dua 和 Graff,2017]。数据集按大小升序排序。缩写:类。—类别数,dim。—维数,样本。—样本数量。
首先,我们观察到 LCE 是一个更好的概括预测器,因为它在所有数据集中获得了最佳*均排名(LCE: 1.0,随机森林:2.1,XGBoost: 2.0)。****
然后,我们可以看到,结合随机森林和 XGBoost 的优势,同时采用补充多样化方法, LCE 在两类数据集(小型/中型)上的 10 个数据集中的 4 个数据集上优于这两种方法。对于其余的数据集, LCE 在随机森林和 XGBoost 之间获得了与最佳执行方法相同的预测性能(与随机森林 3 胜/*,与 XGBoost 4 胜/*)。因此, LCE 设计允许它在测试的数据集上保持随机森林和 XGBoost 方法的优点,及其补充多样化方法可以使它胜过这两种方法。**
未来的进步
如前所述,LCE 是一种高性能的、可扩展的和用户友好的机器学习方法,用于分类和回归的一般任务。特别是,LCE:**
- 通过结合 Random Forest 和 XGBoost 的优势并采用互补的多样化方法来增强它们的预测性能。
- 支持并行处理以确保可扩展性。
- 通过设计处理缺失的数据。
- 采用 scikit-learn API 以便于使用。
- 遵循 scikit-learn 惯例,允许与 scikit-learn 管道和模型选择工具进行交互。
- 是以开放源代码和商业可用的形式发布的——Apache 2.0 许可证。
然而,LCE 系统的能力还可以进一步增强,还需要进行一些开发,以满足各种用户需求。
后续步骤
根据第一轮用户反馈和我自己开发 LCE 的经验,社区为 LCE 添加以下功能是很有价值的:
建模灵活性:
- 基本方法:LCE 在树的每个节点中使用 XGBoost 作为基本方法。向终端用户提供根据他们的特定应用选择基本方法的可能性将是有益的,例如,其他增强方法,如 LightGBM [Ke 等人,2017]或任何其他对偏差减少有主要影响的机器学习方法,通过将其添加为 LCE 的参数。
- 损失函数:LCE 使用 scikit-learn 中提供的标准损失函数(例如交叉熵,MSE)。这些标准功能并不适合所有用户特定的应用。因此,为最终用户增加采用自定义损失函数的可能性将是有价值的(有关更多信息,请参考当前在 scikit-learn GitHub issue 中的讨论)。
- 多任务学习:LCE 目前采用传统的单任务学习。然而,在许多应用中(例如,医疗风险评估、多个指标的财务预测),期望解决多个相关的机器学习任务以增强泛化性能。最*的一项工作[Ibrahim 等人,2022 年]提出通过使用正则化器扩展可微分树来解决这一挑战,该正则化器允许跨任务共享软参数,这可能是一个值得考虑的有趣选项。
- 流式处理:为了满足新兴应用处理大量不断发展的数据流的需求(例如,网络安全、能源管理),开发一个集成了严格的内存要求和快速处理时间的流式版本的 LCE 是很有价值的。Gomes 等人[2017]采用适应性方法的工作可能是这个方向上一个有趣的起点。
忠实的设计可解释性:当前用于系综的可解释性方法是事后方法(例如,SHAP [Lundberg 等人,2017],Anchors [Ribeiro 等人,2018]),这些方法不能提供忠实的解释[Rudin,2019]。忠实性至关重要,因为它对应于最终用户对模型预测解释的信任程度,即解释与模型实际计算内容的相关程度。监管和标准化机构强调忠诚是包括人工智能组件在内的流程的问责制、责任和透明度的支柱[欧洲议会和理事会,2021;菲利普斯等人,2021 年】。因此,在 LCE 的设计中直接整合一种机制将是有价值的,这种机制将允许提取支持其预测的解释。
可伸缩性:训练和推理阶段的计算时间对许多应用程序来说都很关键,因此:
- CPU:加速当前的 CPU 实现。
- GPU:添加 GPU 支持(如 CUDA)。
- 云上分布式:增加对多台机器(例如 AWS、GCE、Azure 和 Yarn clusters)上分布式培训的支持,以及与云数据流系统(例如 Flink 和 Spark)的集成。
编程语言 : LCE 目前用 Python 实现。为了适应不同的用户需求,用其他语言(例如 C++、R、Java、Julia、Scala)来实现它是很有价值的。
如何参与?
他们以多种方式参与 LCE;例如,学生的学校项目,学者的研究问题,通过专业人员的合作实现绩效最大化:
- 给 LCE GitHub 库添加一颗星星。这看似无关紧要,但对于 LCE 的参考和可见性却至关重要。
- 回答问题跟踪器上的查询,调查错误,并审查其他开发人员的请求,以确保现有版本按预期运行。
- 开发新的测试用例,使代码库更加健壮。
- 通过添加上一节中介绍的新功能来扩展 LCE 功能。
- 在各种媒体上设计针对不同受众的教程,让更多人发现 LCE。
对于组织而言,可以赞助项目以支付开发 LCE 和最高标准所需的费用(例如,强大的持续集成基础架构等专业服务、研讨会费用)。
结论
本文提出了一种新的基于树的机器学习方法 LCE,并通过与当前表现最好的方法进行比较,表明它在公共基准上获得了最佳性能。然后,它介绍了参与的关键方向,以进一步推动 LCE 并释放其全部潜力。如果你有任何问题,你可以在这里联系我。
参考
南艾伯哈德,d .库曼斯和 o .德维尔。高维环境下分类器的比较。技术。1992 年第 92-02 号代表。(许可证: CC BY 4.0
A.阿尔扎拉尼和萨达乌伊。拍卖欺诈数据的聚类和标记。数据管理、分析和创新,269–283,2020。(牌照: CC0 )
K.班尼特和奥·曼加里安。两个线性不可分集合的鲁棒线性规划判别。优化方法和软件,1:23–34,1992 年。(许可证: CC0 )
长度布莱曼。随机森林。机器学习,45(1):5–32,2001。
米(meter 的缩写))布塞马、s .泰尔齐和 w .塔斯特尔。一种新的元分类器。北美模糊信息处理学会年会,2010 年 1-7 日。(牌照: PDDL )
T.陈和 C. Guestrin。XGBoost:一个可扩展的树提升系统。2016 年第 22 届 ACM SIGKDD 知识发现和数据挖掘国际会议论文集。
D.杜瓦和 c .格拉夫。UCI 机器学习知识库,2017 年。
J.Dvořák 和 p .萨维奇。使用模拟退火软化决策树中的分裂。ICANNGA 会议录,2007 年。(许可证: ODbL )
欧洲议会和理事会。2021.人工智能法案。欧洲联盟法律。
K.Fauvel、E. Fromont、V. Masson、P. Faverdin 和 A. Termier。XEM:一种用于多元时间序列分类的可解释的设计集成方法。数据挖掘和知识发现,36(3):917–957,2022。
R.菲舍尔。分类问题中多重测量的使用。优生学年鉴,7:179–188,1936 年。(许可证: CC0 )
H.戈梅斯、a .比费特、j .里德、j .巴德达尔、f .埃内布雷克、b .普法林格、g .霍姆斯和 t .阿布德斯莱姆。用于进化数据流分类的自适应随机森林。机器学习,106:1469–1495,2017。**
长度 Grinsztajn、E. Oyallon 和 G. Varoquaux。为什么在典型的表格数据上,基于树的模型仍然优于深度学习?《第 36 届神经信息处理系统数据集和基准追踪会议论文集》,2022 年。
南易卜拉欣、侯赛因和马祖姆德尔。使用可微分树集成的灵活建模和多任务学习。2022 年第 28 届 ACM SIGKDD 知识发现和数据挖掘国际会议论文集。
G.柯,孟庆国,芬利,王,陈,马,叶,刘。LightGBM:一种高效的梯度推进决策树。《2017 年第 31 届神经信息处理系统会议论文集》。
南伦德伯格和 s .李。2017.解释模型预测的统一方法。第 31 届神经信息处理系统国际会议论文集。
页(page 的缩写)菲利普斯、c .哈恩、p .丰塔纳、a .耶茨、k .格林、d .布罗尼亚托夫斯基和 m .普日博基。2021.可解释人工智能的四个原则。美国国家标准与技术研究所。
米(meter 的缩写))里贝罗、辛格和格斯特林。2018.锚:高精度模型不可知的解释。《第 32 届 AAAI 人工智能会议论文集》。
J.罗拉、b .佩鲁马尔、s .纳拉亚南、p .塔库尔和 r .巴特。基于神经网络的粒子群优化和引力搜索模糊混合算法的室内用户定位。《第六届软计算解决问题国际会议论文集》,2017 年。(许可证: CC 乘 4.0 )
C.鲁丁。2019.停止解释高风险决策的黑盒机器学习模型,而是使用可解释的模型。自然机器智能,1:206–215。
C.萨卡尔、S. Polat、M. Katircioglu 和 Y. Kastro。基于多层感知器和 LSTM 递归神经网络的在线购物者购买意向实时预测。神经计算与应用,6893–6908,2018。(牌照: CC0
C.de Stefano,M. Maniaci,F. Fontanella 和 A. Scotto di Freca。中世纪手稿中通过版面特征进行可靠的作者鉴定:以《阿维拉圣经》为例。人工智能的工程应用,72:99–110,2018。(牌照: CC0 )
B.祖潘、m .博汉内克、I .布拉特科和 j .德姆萨尔。通过功能分解的机器学习。1997 年第 14 届机器学习国际会议论文集。(许可证: CC0 )
在分类数据集中,LDA 比 PCA 更能有效地降低维数
线性判别分析(LDA)在最大化类别可分性的同时进行维数缩减

威尔·弗朗西斯在 Unsplash 拍摄的照片
可以使用各种技术来实现降维。11 种这样的技术在我的热门文章2021 年你应该知道的 11 种降维技术 中已经讨论过了。
在那里,你将适当地学习一些技术术语背后的含义,如维度和降维。
简而言之, 维度 是指数据集中特征(变量)的数量。减少数据集中特征的过程称为 降维 。
线性判别分析
线性判别分析(下文称为 LDA)是一种流行的线性降维技术,它可以在低维空间中找到输入特征的线性组合,同时最大化类别可分性。
类别可分性仅仅意味着我们尽可能地保持类别,同时保持每个类别中数据点之间的最小间隔。
类别分离得越好,就越容易在类别之间绘制决策边界来分离(区分)数据点组。
LDA 通常用于具有类标注的分类数据集。它既可以用作二元/多类分类(监督学习)算法,也可以用作降维(非监督学习)算法。
然而,LDA 在用于降维时需要类别标签。因此,LDA 执行监督降维。
拟合的 LDA 模型可以用于分类和维数减少,如下所示。
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
lda = LinearDiscriminantAnalysis().fit(X, y) # fitted LDA model
**lda.predict(X)**: 进行多级分类。这将向可用类别分配新的数据点。**lda.transform(X)**: 在分类数据集中执行降维,同时最大化类的分离。这将在低维空间中找到输入特征的线性组合。当以这种方式使用 lda 时,它充当数据预处理步骤,其输出被用作另一种分类算法的输入,例如支持向量机或逻辑回归!
PCA 与 LDA
PCA 是最流行的降维技术。PCA 和 LDA 都被认为是 线性 降维技术,因为它们在数据中找到输入特征的线性组合。
然而,这两种算法之间有显著的差异。
- PCA 通过最大化数据的方差来执行维数减少。因此,在大多数情况下,在应用 PCA 之前,特征标准化是必要的(参见这里的例外)。
- LDA 通过最大化分类数据集的类别可分性来执行维数缩减。因此,特性标准化在这里是可选的(我们将很快验证这一点)。
- PCA 不需要类别标签。因此,它可以用于分类,回归,甚至无标签的数据!
- LDA 要求等级标签。因此,它用于分类数据集。
- PCA 在低维空间中找到一组不相关的特征。因此,PCA 会自动移除数据中的多重共线性(在此处了解更多)。
- 如前所述,LDA 可用于监督和非监督任务。PCA 只能用于无监督的降维。
- PCA 能找到的最大分量数等于数据集的输入特征(原始维数)数!我们通常倾向于找到数量相当少的组件,尽可能多地捕捉原始数据中的差异。
- LDA 可以找到的最大组件数等于分类数据集中的类数减一。例如,如果数据集中只有 3 个类,LDA 最多可以找到 2 个组件。
- 对于分类数据集,LDA 比 PCA 更有效,因为 LDA 通过最大化类别可分性来降低数据的维度。对于具有最大类可分性的数据,更容易绘制决策边界。
今天,在本文中,我们将通过使用葡萄酒分类数据集,直观地证明 LDA 比 PCA 在分类数据集中的降维方面更加有效。然后,我们将继续讨论如何将 LDA 模型的输出用作支持向量机或逻辑回归等分类算法的输入!
葡萄酒数据集
这里,我们将使用葡萄酒分类数据集来执行 PCA 和 LDA。以下是关于葡萄酒数据集的重要信息。
- 数据集来源:你可以在这里下载原始数据集。
- 数据集许可:该数据集可在 CC 下通过 4.0(Creative Commons Attribution 4.0)许可获得。
- 所有者: Forina,m .等人,par vus——一个用于数据探索、分类和关联的可扩展包。意大利热那亚 Via Brigata Salerno,16147,药物和食品分析与技术研究所。
- 捐赠者:斯蒂芬·艾伯哈德
- 引用: Lichman,M. (2013)。https://archive.ics.uci.edu/ml 的 UCI 机器学习库。加州欧文:加州大学信息与计算机科学学院。
葡萄酒数据集预装了 Scikit-learn。可以通过调用 load_wine() 函数来加载,如下。
from sklearn.datasets import load_wine
wine = load_wine()
X = wine.data
y = wine.target
print("Wine dataset size:", X.shape)

(图片由作者提供)
葡萄酒数据集有 178 个实例(数据点)。它的原始维数是 13,因为它有 13 个输入特征(变量)。此外,数据点被分为三个独立的类别,代表每一种葡萄酒。
将主成分分析应用于葡萄酒数据
我们将把主成分分析应用于葡萄酒数据,以实现以下目标。
- 构建 PCA 模型,该模型可用于与将在接下来的步骤中创建的 ld a 模型进行比较。
- 使用前两个主成分在 2D 散点图中可视化高维(13 维)葡萄酒数据。您可能已经知道 PCA 对于数据可视化非常有用。
特征标准化
在将 PCA 应用于葡萄酒数据之前,我们需要进行特征标准化,以将所有特征纳入同一尺度。
from sklearn.preprocessing import StandardScaler
X_scaled = StandardScaler().fit_transform(X)
缩放后的特征存储在 X_scaled 变量中,该变量将成为pca.fit_transform()方法的输入。
运行 PCA
我们将通过调用 Scikit-learn PCA() 函数对葡萄酒数据应用 PCA。我们希望保留的组件数量(在n_components中指定)严格限制为两个,因为我们对葡萄酒数据的 2D 可视化感兴趣,它只需要两个组件!
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
转换(简化)后的数据存储在 X_pca 变量中,该变量包含可以准确表示原始葡萄酒数据的二维数据。
制作散点图
现在,我们将使用存储在 X_pca 变量中的数据制作散点图。
import matplotlib.pyplot as plt
plt.figure(figsize=[7, 5])
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, s=25, cmap='plasma')
plt.title('PCA for wine data with 2 components')
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.savefig("PCA.png")

2D 葡萄酒数据的 PCA 散点图(图片由作者提供)
数据似乎可以用线性决策边界(即直线)进行线性分离。然而,一些数据点会被错误分类。由于 PCA 没有最大化类的可分性,类没有被很好地分离。
当通过仅保留两个成分将 PCA 应用于葡萄酒数据时,我们丢失了数据中的大量方差。
exp_var = sum(pca.explained_variance_ratio_ * 100)
print('Variance explained:', exp_var)

(图片由作者提供)
只有大约 55.4%的方差被我们的具有两个分量的 PCA 模型捕获。这么大的差异不足以准确地代表原始数据。
让我们通过创建下面的图来找到葡萄酒数据的主成分的最佳数量。该值应大于 2 但小于 13(输入要素的数量)。
import numpy as np
pca = PCA(n_components=None)
X_pca = pca.fit_transform(X_scaled)
exp_var = pca.explained_variance_ratio_ * 100
cum_exp_var = np.cumsum(exp_var)
plt.bar(range(1, 14), exp_var, align='center',
label='Individual explained variance')
plt.step(range(1, 14), cum_exp_var, where='mid',
label='Cumulative explained variance', color='red')
plt.ylabel('Explained variance percentage')
plt.xlabel('Principal component index')
plt.xticks(ticks=list(range(1, 14)))
plt.legend(loc='best')
plt.tight_layout()
plt.savefig("Barplot_PCA.png")

(图片由作者提供)
这种类型的图被称为累积解释方差图,在应用 PCA 时,这种图对于找到主成分的最佳数量非常有用。
前六个或七个分量捕获了数据中大约 85–90%的方差。因此,它们将准确地表示原始的葡萄酒数据。但是,对于 2D 可视化,我们严格地希望只使用两个组件,即使它们没有捕捉到数据中的大部分变化。
将 LDA 应用于葡萄酒数据
现在,我们将 LDA 应用于葡萄酒数据,并将 LDA 模型与之前的 PCA 模型进行比较。
特征标准化
LDA 不需要特征标准化,因为它对 LDA 模型的性能没有任何影响。
运行 LDA
我们将通过调用 Scikit-learnLinearDiscriminantAnalysis()函数对葡萄酒数据应用 LDA。我们希望保留的组件数量(在n_components中指定)严格限制为两个,因为我们对葡萄酒数据的 2D 可视化感兴趣,它只需要两个组件!
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
lda = LinearDiscriminantAnalysis(n_components=2)
X_lda = lda.fit_transform(X_scaled, y)
注意,对于 LDA,在fit_transform()方法中还需要类标签(y)。
转换(简化)后的数据存储在 X_lda 变量中,该变量包含可以准确表示原始葡萄酒数据的二维数据。
制作散点图
现在,我们将使用存储在 X_lda 变量中的数据制作散点图。
import matplotlib.pyplot as plt
plt.figure(figsize=[7, 5])
plt.scatter(X_lda[:, 0], X_lda[:, 1], c=y, s=25, cmap='plasma')
plt.title('LDA for wine data with 2 components')
plt.xlabel('Component 1')
plt.ylabel('Component 2')
plt.savefig("LDA.png")

2D 葡萄酒数据散点图与 LDA (图片由作者提供)
现在,由于 LDA 除了降低维数之外还最大化了类别可分性,因此类别已经被清楚地分离。绘制线性决策边界时,数据点不会被错误分类。
LDA 可以为葡萄酒数据保留的最大组件数也是两个,因为数据中只有三个类。因此,这两个组件应该捕获数据中的所有差异。
让我们用数字和视觉来验证这一点!
exp_var = sum(lda.explained_variance_ratio_ * 100)
print('Variance explained:', exp_var)

(图片由作者提供)
原始葡萄酒数据中的所有差异都由我们的 LDA 模型捕获,该模型包含两个部分。因此,这些组件将完全代表原始数据。
让我们为 LDA 模型创建累积解释方差图。
import numpy as np
lda = LinearDiscriminantAnalysis(n_components=None)
X_lda = lda.fit(X_scaled, y)
exp_var = lda.explained_variance_ratio_ * 100
cum_exp_var = np.cumsum(exp_var)
plt.bar(range(1, 3), exp_var, align='center',
label='Individual explained variance')
plt.step(range(1, 3), cum_exp_var, where='mid',
label='Cumulative explained variance', color='red')
plt.ylabel('Explained variance percentage')
plt.xlabel('Component index')
plt.xticks(ticks=[1, 2])
plt.legend(loc='best')
plt.tight_layout()
plt.savefig("Barplot_LDA.png")

(图片由作者提供)
前两个组件捕获数据中的所有差异。所以,它们完全代表了原始的葡萄酒数据。
LDA 模式的优势
我们的 LDA 模型具有以下优点。
- 减少数据的维度(特征数量)
- 在 2D 图中可视化高维数据
- 最大化类别可分性
SVM 与 LDA
只有 LDA 可以在降低数据维数的同时最大化类别可分性。因此,在运行另一种分类算法(如支持向量机(SVM)或逻辑回归)之前,LDA 是降低维数的理想方法。
这可以想象如下。

SVM 与 LDA 的机器学习管道(图片由作者提供)
LDA 模型将高维(13 维)葡萄酒数据(X)作为其输入,并在最大化类别可分性的同时降低数据的维度。作为 X_LDA 的变换数据(2-dim)与类别标签 y 一起用作 SVM 模型的输入。然后,由于葡萄酒数据具有 3 个类别,SVM 通过使用“一对其余”(ovr)策略来执行多类别分类,这将允许算法在考虑所有其他类别的情况下为每个类别绘制决策边界。
我们使用“线性”核作为支持向量机算法的核,因为数据似乎可以与线性决策边界(即直线)线性分离。
from sklearn.svm import SVC
svc = SVC(kernel='linear', decision_function_shape='ovr')
svc.fit(X_lda, y)

使用 LDA 实现最大类别可分性的 SVM(图片由作者提供)
类可以用线性决策边界清楚地分开。只有一个数据点被错误分类。将此与我们之前获得的 PCA 输出进行比较,如果我们绘制线性决策边界,许多点将被错误分类。
因此,在分类数据集中,LDA 比 PCA 具有更高的降维效率,因为 LDA 在降低数据维数的同时最大化了类的可分性。
请注意,绘制 SVM 决策边界(超*面)的代码不包括在上面的代码中,因为它需要对支持向量机如何在幕后工作有透彻的理解,这超出了本文的范围。
注意:术语‘超*面’是在考虑高维数据时指代决策边界的正确方式。在二维空间中,超*面只是一条直线。同样,你可以想象高维空间中超*面的形状。
为 LDA 选择正确的组件数量
PCA 和 LDA 算法中最重要的超参数是 n_components ,其中我们指定了 LDA 或 PCA 应该找到的组件数量。
在我的文章 如何为数据集 选择最佳数量的主成分中,已经讨论了为 PCA 选择最佳数量的准则。
同样的准则也适用于 LDA。
- 如果应用 LDA 的唯一目的是为了数据可视化,您应该保留 2 个(对于 2D 图)或 3 个(对于 3D 图)组件。我们只熟悉 2D 和 3D 情节,无法想象其他高维情节。
- 正如我前面解释的,累积解释方差图对于选择正确的成分数量非常有用。
- LDA 可以找到的最大组件数等于分类数据集中的类数减一。
今天的帖子到此结束。
如果您有任何问题或反馈,请告诉我。
下一篇阅读:主成分分析和降维特辑
(作者截图)
人工智能课程怎么样?
(作者截图)
支持我当作家
我希望你喜欢阅读这篇文章。如果你愿意支持我成为一名作家,请考虑 注册成为会员 以获得无限制的媒体访问权限。它只需要每月 5 美元,我会收到你的会员费的一部分。
https://rukshanpramoditha.medium.com/membership
非常感谢你一直以来的支持!下一篇文章再见。祝大家学习愉快!
鲁克山·普拉莫蒂塔
2022–12–29
LDA 主题模型不稳定性
原文:https://towardsdatascience.com/lda-topic-model-instability-c2fedb77d249
没有两个 LDA 主题模型是相同的。主题建模者应该做什么?

答在花费数小时调整 LDA 模型后,没有什么比意识到他们的模型运行与下一次有多么不同更能破坏主题建模者的一天了。LDA 算法是随机的,用相同的参数对相同的数据生成的模型会产生不同的结果。这是一个特征,而不是算法的错误(试着向你的老板解释一下)。这篇文章对 LDA 模型的不稳定性提供了一个实用的、深入的观察,并展示了对抗其不良影响的方法。
之前的两篇文章此处和此处详细介绍了确定指定 LDA 模型的最佳主题数量的步骤。本系列使用的语料库是一个更大的公开许可数据集新闻文章的 30,000 篇文章子集。前面文章中概述的分析导致了将主题模型大小设置为 20 的决定。本文着眼于使用 LDA 创建的模型有多不稳定,并通过调整指标和转向 LDA 的一种变体— 集成 LDA 来解决这种不稳定性。
(注:有关数据集、模型参数、技术细节和代码片段的更多信息,请参见附录。)
不幸的是,每次从具有相同参数的相同语料库中生成 LDA 模型时,结果模型将是不同的。这种差异在下面的图表中得到了说明,该图表将文档类别与两种模型进行了比较,我们希望这两种模型即使不相同,至少也是相似的:
相同的参数,相同的数据,不同的结果。图片作者。
每一列代表为模型 A 中的每个主题分类的所有文档。这显示了模型 A 中的每个主题分布在模型 b 中的每个主题上。最清晰的相关性由模型 A 的主题 13 和模型 b 的主题 15 之间的红色单元格表示。96%的模型 A 的主题 13 文档包含在模型 b 的单个主题中。但是,相关性从那里迅速下降。尽管其他一切都是*等的,但这两个模型显然是不一样的。而且问题不仅仅在两个模型之间,进一步的检查暗示着模型内部是相当嘈杂的。以下是 A 的主题列表:
0 government president people law country obama vote republican call right
1 people government kill attack official force cnn group report security
2 police court old charge home family case woman death leave
3 claim people london jackson muslim group last british child family
4 police car people officer road fire city family report man
5 french world first murray france last open set iran tennis
6 win right race goal free_kick leave second first minute real_madrid
7 bbc party labour last people pay uk vote election first
8 facebook user apple company google gun people phone device internet
9 team game win first sport play world last coach world_cup
10 people study high research cent patient researcher company increase risk
11 health virus patient people country ebola disease china case world
12 water hospital people home die area family care river boat
13 game player club season play team first last win goal
14 school police student university teacher report home church investigation officer
15 know life woman people think love want come family first
16 russia military country russian president force official government leader united_state
17 child school parent people health need report education service age
18 right police miss dog leave city free_kick attempt officer foot_shot
19 see first world animal people old know picture even come
总的来说,主题本身是合理的连贯和可理解的。然而,有很多重叠——单词出现在多个主题中,这无疑增加了分类问题。通过将每个模型的主题词与其他主题中的主题词进行比较,可以明显看出主题表示有多嘈杂:
模型主题词的重叠。图片作者。
如果每个主题词分组与模型中的其他分组有最小的重叠就好了。如果每个主题由一组独特的单词组成,那么整个图表将由被红色对角线整齐分开的深蓝色单元格组成。然而在这个图表中,我们可以看到有很多很多重复的单词。从主题 9 和 13 可以清楚地看出这个问题:
9 team game win first sport play world last coach world_cup
13 game player club season play team first last win goal
术语重叠的程度可以通过计算每列中匹配单词的*均值来表示,对于该模型,该*均值为 26.7,而如果每个主题集是唯一的,则该值为 10。
为了调整这个模型,我们可以增加遍数,在 Gensim 文档中描述为“在训练期间通过语料库的次数”,从而提供额外的回归来微调模型。碰撞通过从默认值 1 到 10,当分析所有主题的单词分布时,我们看到一个显著的差异:
碰撞通过后减少了主题词重叠。图片作者。
单个模型中的共享单词数量急剧减少。单程模型的*均值为 26.7,而该模型的*均值为 16.4。类似地,当比较跨两个模型的文档/主题分布时,模型相似性的改进是明显的:
跨两个模型的文档分布。图片作者。
向下阅读各列,很明显,与第一个例子相比,总噪声已经显著降低。不幸的是,除了通道,之外,在 LDA 基本算法中没有其他参数可以调整,我们可以在这种情况下使用这些参数来实现模型间的内聚性。然而,我们可以采用一种变体算法:集成 LDA。这种混合 LDA 实现直接解决了不稳定性问题。
Ensemble LDA 允许合并多个模型以创建单个混合模型。上面例子中的模型都是二十个主题模型。这是使用基于 PMI 的指标和试探法可以最好地确定的值(见以前的文章)。然而,除了合并多个 LDA 运行之外,集成 LDA 也不同于 LDA 算法,因为它在其处理期间确定最佳主题模型大小。在这种情况下,它选择代表十二个主题的语料库。对新生成的词汇表中重叠部分的分析产生:
集成模型中的主题词重叠。图片作者。
这个版本的*均 14.92 比之前的 16.4 有所提高,很明显,但没有通过干预那么引人注目。但是,当比较文档分布时,有一个显著的差异:
集成模型中两个模型之间的文档分布。图片作者。
这里我们看到了一个更有益的结果,在两个模型中的所有 12 个主题上都有广泛的一致。不仅大多数主题在每个模型中都有很好的表现,而且从一个主题到下一个主题几乎没有流血。八个主题从一个模型到下一个模型有 90%的镜像。三个在 80%以上,一个在 82%。
本文已经展示了 LDA 主题模型在不同的运行中可能会很不一致。它进一步检查了增加通道数量的影响,这提高了模型到模型的稳定性。然而,通过使用系综 LDA 可以看到显著的提升,它通过基本上消除重叠和噪声而显著地改善了最终输出。除了处理不稳定性问题,集成 LDA 还引入了为模型选择最佳主题大小的能力。有趣的是,12 个主题的选择,与使用指标表示的 20 个主题的大小明显不同,引入了关于基于 PMI 的指标和整体 LDA 过程的输出之间的差异的问题,但这是未来文章的素材。
附录:技术细节
这篇文章基于三种不同模型配置的三次迭代。为了测试 LDA 模型的每次运行是不同的,但是这些不一致性在每次运行中大致相等的前提,创建并比较了三个模型。文章中只显示了 A / B 的比较,但 B / C 和 A / C 实际上是相似的。
每个模型都是从三万个文档语料库中创建的,这里的和这里的进行了更详细的讨论。语料库是小写的,去掉了停用词、数字和标点符号。进一步的修剪去除了所有不是名词、动词、副词或形容词的词类。一个定制的十几个单词的列表也被删除了:
say, year, make, find, use, get, mr, work, state, time, also, take, new, look. show, day, go, tell
最后,由此产生的超过 100,000 个单词的字典减少到 20,000 个。
生成系综模型时道次被设置为 15,主题号为 20,模型为 16。这些不能与基本的 LDA 算法直接比较。
Gensim ldamulticore 实现用于生成 LDA 模型。Gensim 的 ensemblelda 实现用于集合运行。在每次运行中,使用 0.05 的α值和 0.5 的β值。Thes 值是通过测试得出的。**
在第一次运行中,在调整通道参数之前,主题被设置为二十。在第二轮测试中,通行证从默认的一个增加到十个。整体 LDA 支持通道、通道,这与 LDA 模型中的效果相似。然而,集合其模型大小参数与基本 LDA 实现有意义地不同,并且它引入了 num_models 参数。对于第三组模型,通道设置为 15,主题设置为 20,模型设置为 16。需要注意的是,这并不意味着生成了二十个主题。相反,生成 16 个模型,每个模型有 20 个主题,然后分析全部 320 个主题,这导致模型为最终模型选择 12 个主题的混合。上述分析中不包括设置为 12 个主题和 240 个通道的三个基本 LDA 模型。选择高数量的通道来*似集合的多个模型的累积效果,以及测试减少主题数量可能影响模型稳定性的假设。也不是这种情况,并且结果非常类似于讨论中包括的十遍基本 LDA 运行。
在启用了 GPU 的 Colab+环境中,生成单遍模型大约需要 1 分钟,十遍模型需要 10 分钟,而整体模型大约需要 3 小时 45 分钟。240 次 pass base LDA 运行每次大约需要三个小时。
尽管事实上超过前五个单词的额外单词对文档分类的影响迅速减小,但还是选择了十个单词的词汇表用于示例和测试。比较五个单词和十个单词的统计差异可以忽略不计,增加的单词是为了使描述更容易理解。
词汇矩阵比较是使用以下代码生成的:
def CompareVocabs(model1, model2) :
compareMatrix = pd.DataFrame()
for x in range(len(model1)) :
compRow = []
for y in range(len(model2)) :
compRow.append(len(set(model1[x]) & set(model2[y])))
compareMatrix[x] = compRow
return compareMatrix
其中模型 1 和模型 2 是主题的词汇列表。
生成矩阵的代码比较了一个模型和另一个模型中文档的主题分布,代码如下:
def getPercentDif(dfA, topicA, dfB, topicB) :
aDF = dfA[dfA[0]==topicA]
bDF = dfB[dfB[0]==topicB]
if aDF.shape[0] == 0 :
return 0
else :
return aDF[aDF.index.isin(bDF.index)].shape[0] / aDF.shape[0]resultDF = pd.DataFrame()
for y in range(aDF.shape[1]) :
vals = []
for x in range(bDF.shape[1]) :
vals.append(getPercentDif(aDF, y, bDF, x))
resultDF[y] = vals# See note below regarding aDF and bDF
aDF 和 bDF 是从 LDA 输出中计算出的矩阵,这些矩阵产生主导主题(对于给定文档具有最高可能性的主题)。生成这些矩阵的代码太长了,无法在这里发布,但可以在由 ModelMonster 生成的笔记本的 ModelMonster 类中找到。_ GenerateMatricies()。矩阵可以通过ModelMonster . topiciddfs字典从实例化的 model monster 类中访问。
文献学
布莱博士,Ng,A. Y .,&乔丹,M. I. (2003 年)。潜在狄利克雷分配。机器学习研究杂志:JMLR 。https://www.jmlr.org/papers/volume3/blei03a/blei03a.pdf?ref=https://githubhelp.com
布里格尔,托拜厄斯(2019)。使用系综潜在狄利克雷分配提取可靠主题[学士论文]。英戈尔施塔特技术学校。慕尼黑:数据回复有限公司。https://www.sezanzeb.de/machine_learning/ensemble_LDA/
丹尼尔·罗宾逊(2022)。潜在狄利克雷分配的主题建模:潜在狄利克雷分配的自然语言处理技术及其在主题建模任务中的应用的实践探索。 走向数据科学 。https://towards data science . com/use-metrics-to-determine-LDA-topic-model-size-1a 1 feaa 1 ff 3c
丹尼尔·罗宾逊(2022)。确定正确的 LDA 主题模型大小,第二部分。 走向数据科学 。https://towards data science . com/determining-the-right-LDA-topic-model-size-part-ii-ff 4312 e 9 FD 9
附加参考
对于我经常用来指导分析的 LDA 模型参数的详细信息,我推荐 Haaya Naushan 的优秀的 主题建模和潜在的狄利克雷分配 。
LDA 话题建模:基于中文微博数据的案例研究
原文:https://towardsdatascience.com/lda-topic-modeling-a-case-study-with-chinese-tweets-data-2d08ad25b08c
一种数据驱动的方法来理解 Twitter 上的热门话题

克里斯·j·戴维斯在 Unsplash 上的照片
在过去的几年里,新冠肺炎彻底改变了我们的生活方式。随着远程和虚拟通信在我们的日常生活中发挥着越来越重要的作用,从客户服务的角度来看,也越来越需要了解和衡量服务质量,并从数字聊天数据中识别潜在的机会。在这些场景中, NLP (自然语言处理)技术,如情感分析和主题建模,可能会为企业增加价值。
In this post, I will demonstrate how to do topic modeling with LDA, aka Latent Dirichlet Allocation. This method is already there for some time, and there are already plenty of great articles on how to apply it to English-based text data. To add some extra flavors here, I will apply this technique to another language — Chinese. More specifically, I will look at the tweets data with Hong Kong (香港) in the hashtags, and explore what are the trendy topics discovered with the LDA method.
主题建模和 LDA
在进入细节之前,我们先简单说一下主题建模和 LDA 的概念。在自然语言处理的背景下,主题建模通常用于在统计模型的帮助下发现出现在文档集合中的抽象主题。 LDA 是实现主题聚类的最经典模型之一,其关键的潜在假设是所有文本数据都是不同主题(具有不同关联权重)的组合,并且每个主题也是具有不同关联权重的不同关键词的组合。
如果你想知道更多关于 LDA 背后的数学细节,我发现 Luis 的这个视频做得很好,如果你感兴趣,你可以看看。
NLP 项目的一般工作流程
典型的 NLP 项目(不限于主题建模)的一般工作流程如下:
- 执行文本预处理。这可能包括诸如文本清理、词干化/词条化、标记化和停用词移除等步骤。尽管数据预处理的想法非常简单,但它通常在项目生命周期中消耗最多的时间。这是有意义的,因为这一步的质量通常对最终输出有非常关键的影响。
- 为 NLP 任务应用相关模块。根据手头的任务,我们需要导入相关的库,并准备好输入到相关模型中的文本数据。像所有其他数据科学模型一样,我们也需要进行一些超参数调整,以获得最佳结果。对于我们的主题建模案例研究,将使用 gensim 包中的 LDA 模型。更多细节将在后面的章节中介绍。
- 评估模型性能。如何评估模型的结果在很大程度上取决于问题的性质。例如,如果我们在谈论带标签数据的监督学习,那么我们可以使用 F1 分数(分类问题)和 RMSE(回归问题)等相关指标。在主题建模的例子中,由于它属于无监督学习的范畴,我们可能需要使用一些特殊的度量标准(例如一致性)来衡量结果的性能——在后面的章节中会有更多的介绍。
中文自然语言处理对英文自然语言处理
现在我们知道了一个典型的 NLP 项目的一般流程。鉴于英语是世界上使用最广泛的语言,默认情况下,人们使用英语作为 NLP 项目的基础。但是,如果我们需要用另一种语言(在我们的例子中是中文)来开发 NLP 应用程序,我们应该期待的主要区别是什么?
虽然不同语言之间的一般工作流程是相似的,但我想说主要的区别确实在于文本的预处理阶段。例如,我们不能对汉字进行词干化/词汇化,因为汉字不是以这种方式组织的。此外,中文文本数据的标记化步骤也有点棘手。与英语不同的是,在一个句子中,每个单词都用空格隔开,而在汉语中,单词之间没有空格。
于是,我们不得不利用一些专门的中文分词工具包,比如 街霸 。我们将在后面的章节中更详细地介绍这一点。其余的中文和英文文本预处理工作流程非常相似,至少对于主题建模问题是如此。
从 Twitter 获取数据
Now we have a high-level understanding of the workflow, it is time to start the case study! The first challenge we need to resolve is how to obtain Twitter data. To download data from Twitter, you may set up a Twitter developer account to query tweets with their API. The API is very well documented and easy to follow through to get things started, but in case you prefer to follow a tutorial from some other users, you may take a look at this post from Andrew. For my case, I managed to extract the tweets data from Jan’22 to Mar’22 with the keyword “香港”, and managed to query ~12k tweets in total. Among all the tweets, around 6k tweets are in Chinese, and they are used as the raw text data for topic modeling later.
文本预处理
现在我们手头有了 Twitter 数据,下一步是为 LDA 模型准备文本数据。如前所述,文本预处理阶段非常关键,通常会对最终结果产生很大影响。一般步骤如下:
准备一个停用词列表
与英语类似,也有中文停用词,这可能会干扰从文本数据中获取实际主题。您可以做一个简单的 google 搜索,看看是否有可用的停用词列表,或者您可以利用一些现有的库(如stopwodiso)来直接导入停用词。
使用自定义词典来改进标记化
As mentioned earlier, jieba is one of the most popular libraries to handle word segmentation in Chinese text. While jieba has pretty decent tokenization performance by default, its built-in tokenization is based on Mandarin Chinese mostly. When dealing with text data in Hong Kong, you may notice that local people may use Cantonese (粤语) more often, which is quite different compared to Mandarin. To handle this more effectively, we can import some user-defined vocabulary to help jieba improve word segmentation performances. I managed to find out one online post which covers a way to generate a combined corpus of Mandarin and Cantonese, and then I imported this corpus as the user custom dictionaries when doing word segmentation later on.
文本预处理功能
在准备了中文/粤语分词的停用词表和自定义词典之后,我们现在就可以开始文本预处理的剩余步骤了。为了简单起见,我们将只在推文中保留中文字符(这样所有的特殊字符、表情符号和任何其他符号都将被排除在外),然后我们使用 jieba 库进行分词。最后,我们从列表中删除了停用词和单个字符的单词,因为它们没有携带太多用于主题建模的信息。实现上述目的的代码示例附于此:
def remove_special_character(text):
# Remove non-chinese characters in the text - including all the hypterlinks, emoji and symbols
return re.sub(r'[^\u4E00-\u9FA5]+','',text)def clean(doc):
#Remove special characeters (keep only chinese or english words)
special_free = remove_special_character(doc)
#Do the jeiba word segmentation
words_cut = [word for word in jieba.cut(special_free)]
#Remove stop words and single words from the word list
stop_free = [word for word in words_cut if word not in stop_words_combined and len(word) > 1]
return stop_free
基于 Gensim LDA 的主题建模
经过文本预处理后,我们现在准备建立模型。一般步骤如下:
1.准备一本包含所有出现的单词的字典
本质上,我们在这里所做的是创建一个单词包,包含文档中出现的所有单词。为了便于处理,每个唯一的单词被分配一个唯一的 id。实现这一点的代码片段如下:
dictionary = corpora.Dictionary(df_raw_cn[‘tweet_cleand’])
2.为每个文档创建一个词频词典
在这里,对于每个单独的文档,我们希望创建一个词频字典来记录每个单词在文本中出现的次数(即单词袋矢量化)。这一步基于我们在上一步中创建的字典,还附加了下面的代码示例:
doc_term_matrix = [dictionary.doc2bow(doc) for doc in df_raw_cn[‘tweet_cleand’]]
值得注意的是,在这里,我们也可以选择用 tf-idf 的方法来制作这本词典。对于那些不熟悉 tf-idf 的人来说,它是“term-frequency-inversed-document-frequency”的缩写,其中它通过惩罚在大多数文档中出现的非常常见的词的重要性来增强词袋方法(即,这些常见的词不会增加将当前文档与其他文档区分开来的价值)。下面的代码示例可以完成这项工作:
tfidf = gensim.models.TfidfModel(doc_term_matrix)
corpus_tfidf = tfidf[doc_term_matrix] #this will replace doc_term_matrix we created with bow vecterization when fitting into LDA model
3.使用 gensim 运行 LDA 模型
前面的数据准备工作已经完成,我们终于准备好为 LDA 模型拟合文本数据了。像 gensim 这样的库只需要一个函数调用就可以让这变得非常简单。除了我们在步骤 1 和 2 中获得的两条数据输入之外,还有一些其他参数需要为该模型进行调整,包括 num_topics 、 chunksize (每个训练块中使用的文档数),以及 passes (训练遍数总数)等。在所有参数中,num_topics 可能是对最终结果最关键的参数。现在,我们将首先构建一个 num_topics = 10 的基本模型。代码示例附在下面:
lda_model = gensim.models.LdaMulticore(corpus=doc_term_matrix,
id2word=dictionary,
num_topics = 10,
random_state=100,
chunksize=100,
passes=10,
per_word_topics=True)
请注意,我们只是随机假设主题集群的数量为 10。检查结果时,您可能会注意到一些分类显示相似的关键字,这表明分类计数没有优化。对于我们的案例,有三个主题群都与 Covid 中的新闻以及香港如何采取相关措施来有效解决 Covid 相关问题有关(如果您碰巧能阅读中文,请张贴以下 3 个主题群)。因此,我们应该期望最终的主题集群计数小于 10,因为我们已经在这里看到了一些重复。
[(0,
'0.037*"疫情" + 0.025*"疫苗" + 0.018*"確診" + 0.016*"個案" + 0.014*"新冠" + 0.014*"病毒" '
'+ 0.012*"抗疫" + 0.011*"新增" + 0.010*"肺炎" + 0.009*"死亡"'),
(2,
'0.042*"疫情" + 0.023*"內地" + 0.019*"援港" + 0.016*"中央" + 0.015*"中共" + 0.013*"工作" '
'+ 0.011*"物資" + 0.010*"医院" + 0.008*"全民" + 0.007*"醫療"'),
(3,
'0.024*"台灣" + 0.012*"中國" + 0.011*"香港人" + 0.010*"陽性" + 0.009*"深圳" + '
'0.006*"檢測" + 0.006*"隔離" + 0.006*"事情" + 0.006*"民众" + 0.006*"台湾"')
]
4.用一致性分数寻找最佳聚类数
如前一步所示,我们已经建立了一个基本的 LDA 模型,并返回了 10 个主题簇。然而,看起来 10 个集群可能不是最佳选择,因为我们已经看到了一些内容非常接*的集群。选择最佳聚类数有不同的方法,其中一种常用的技术是测量主题 一致性分数。
主题一致性的想法是评估主题在多大程度上受到底层文本数据的支持。在高层次上,对于我们为 LDA 模型获得的主题,LDA 模型是具有不同概率得分的单词的组合,我们基于具有各种统计和概率度量的底层文档文本数据来评估其质量。如果你有兴趣了解更多的基础数学知识, Pedro 在这篇文章中很好地介绍了所有背景知识。
尽管 LDA 和主题一致性计算的底层数学可能会变得有点复杂,幸运的是,gensim 通过一个简单的函数调用使计算主题一致性分数变得相对简单。理想情况下,我们试图实现的是主题聚类数量和整体主题一致性分数之间的*衡点(类似于 k-means 聚类的肘方法)。然而,在实践中,确定主题群计数的过程还应该考虑实际的业务环境。示例代码块附在下面:
for k in range(3,20): # loop through different number of topics and calculate the coherence scores correspondingly
print(f’Round {k}:’)
Lda = gensim.models.LdaMulticore
ldamodel = Lda(corpus=doc_term_matrix,
id2word=dictionary,
num_topics = k,
random_state=100,
chunksize=100,
passes=50,
iterations=100,
per_word_topics=True)
cm = gensim.models.coherencemodel.CoherenceModel(model=ldamodel,texts=df_raw_cn[‘tweet_cleand’],
dictionary=dictionary,coherence=’c_v’)
coherence.append((k,cm.get_coherence()))
考虑到一致性分数和业务理解,在尝试了一些不同的集群计数之后,设置 num_topics = 4 似乎返回了一个合理的结果。如下所示,第一个聚类是关于新冠肺炎相关的新闻,第二个聚类是关于香港股票市场,第三个聚类是关于过去发生的一个特定的政治事件,最后一个聚类是关于其他最*的政治新闻。返回的结果也与我在香港的中文推特数据上的观察一致。
[(0,
[('疫情', 0.010080575),
('抗疫', 0.004716392),
('確診', 0.004410203),
('援港', 0.0032681422),
('內地', 0.0032191873)]),
(1,
[('港股', 0.00796206),
('股市', 0.006621567),
('中国', 0.0041328296),
('学生', 0.0039018765),
('股通', 0.0038416982)]),
(2,
[('绝食', 0.0081910975),
('迎送', 0.003878918),
('灵柩', 0.003878918),
('数十万', 0.003878918),
('长安街', 0.003878918)]),
(3,
[('中共', 0.005017238),
('台灣', 0.0024242366),
('香港人', 0.0021062237),
('过去', 0.0018380425),
('正义', 0.0018066273)])]
用 pyLDAvis 可视化主题集群
如前所述,我们可以通过逐一查看所有主题及其相关单词来评估主题聚类结果。这完全没问题,但是可能不太方便,尤其是当集群数量变大时。幸运的是,有一个流行的可视化软件包可以用来以交互的方式可视化主题集群。在高层次上,在生成的情节中主要有两个交互元素可供您使用:
- 相关性度量λ 。相关性是指一个术语出现在一个特定主题中而排除其他术语的程度。当λ = 1 时,术语按照它们在主题中的概率进行排序(“常规”方法),而当λ = 0 时,术语仅按照它们的提升进行排序。提升是术语在主题中的概率与其在语料库中的边缘概率的比率。在高层次上,lift 试图测量与 TF-IDF 矢量化相似的效果。
- 话题间距离图。在这里,用户能够在 GUI 中浏览不同的主题群,并且用户还可以很好地了解不同的主题群是如何彼此分离的。一般来说,所有主题聚类之间的距离越远,分割结果越好。
生成交互图的示例代码和输出截图附在下面:
import pyLDAvis
import pyLDAvis.gensim_modelspyLDAvis.enable_notebook()
vis = pyLDAvis.gensim_models.prepare(lda_model, doc_term_matrix, dictionary)
vis

展示主题群的互动图(图片由作者提供)
想法和最后笔记
在这篇文章中,我们通过一个案例研究,介绍了如何使用 LDA 对中文推文数据进行主题建模。我们讨论了一些主题,如文本挖掘项目的典型工作流是什么,使用非英语文本数据时需要考虑的一些特殊事项,以及如何确定主题聚类的最佳数量并可视化您的结果。
虽然这看起来是一个不错的 POC 项目,但是当考虑将这种分析转移到生产中时,将会有许多其他的挑战。例如,有时你的业务伙伴已经知道他们在寻找什么样的主题集群,但是使用这种 LDA 主题建模方法,可能无法真正控制这一点(如果你的输出集群被很好地分成他们想要的集群,那你就太幸运了)。
这里没有完美的答案。在商业环境下,它实际上是与相应的利益相关者协调,以确保每个人的期望是一致的。如果模型结果不符合预期,我们可能需要考虑一些其他的技术来满足相关的业务需求——有时甚至可能是非建模的方法!
感谢阅读!如果你有任何反馈,请通过评论这篇文章来联系我们。如果你感兴趣,也可以看看我的其他帖子:
什么是供应链弹性?
原文:https://towardsdatascience.com/lead-times-variability-and-supply-chain-resilience-f8f57f10e6f6
使用 Python 来了解提前期可变性的来源及其对库存管理策略稳健性的影响。

(图片由作者提供)
目的 模拟流程可变性对国际物流网络性能的影响。
库存管理 对于一个零售集团,库存经理制定补货政策最大限度地降低库存水*并降低订购成本,同时避免商店缺货。
他们需要考虑需求的分布和补货的提前期。
然而,他们通常根据供应商和物流团队确定的目标假定恒定的交付周期。
在本文中,我们将了解流程可变性如何极大地影响这些交付周期并破坏供应链的弹性。
💌新文章直接免费放入你的收件箱:时事通讯
如果你愿意,你可以观看这篇文章的视频版本
库存管理和交付时间
作为一家国际时装零售连锁店的库存经理,你负责在 ERP 中设置补货规则。
需求可变性 你可以看到每个商店每天满足的需求。在这个例子中,我们假设需求遵循正态分布。

图 0:1 家 SKU 店的销售分布—(图片由作者提供)
连续评审策略 您使用:订购点、订购数量(s,Q)实施了一个连续评审策略。
如果水*低于最低阈值 s,称为安全库存,你再订购 Q 件。
安全库存计算 安全库存不仅要满足补货期间的需求,还要补偿需求的波动。

图 1:安全库存—(图片由作者提供)
这里的一个关键因素是补货提前期。如果它被低估,你可能会面临商店缺货。
有关更多信息,
https://www.samirsaci.com/inventory-management-for-retail-stochastic-demand-2/
交付周期:期望与现实
补货提前期是订单创建和商店发货之间的时间。
目标交付周期(LT)
当您设计补充规则时,您使用了由后勤团队提供的目标提前期。
一系列复杂的流程
他们的职责是确保从订单创建到商场交付的任务得到最佳执行,

图 2:端到端分发—(图片由作者提供)
截止时间 每个步骤都有一个目标提前期和一个截止时间。

图 3:从订单创建到商店交付—(图片由作者提供)
这些截止时间会极大地影响总提前期(LT):
- 订单接收:如果在 18:00:00 之后收到订单,则无法在(LT+24 小时)之后的第二天准备订单
- 卡车离开:如果订单没有在 19:00:00 之前装箱,则无法在当天装货(LT+24 小时)
- 到达机场:如果您的货物在 00:30:00 后到达,它将错过航班 (+24 小时 LT)
- 落地:如果您的航班在 20:00:00 后落地,您需要多等一天才能通关 (+24 小时 LT)
- 商店送货:如果您的卡车在 16:30:00 之后到达,商店团队无法收到您的货物 (+24 小时 LT)
💡截止时间定义
- 订单接收、卡车离开和商店交付截止时间通常是内部约束,如果需要,可以进行调整
- 抵达机场和着陆截止时间由机场运营和海关确定
引入一些可变性
在下面的例子中,不同的步骤被*滑地协调,并且截止时间被考虑,因为目标被考虑。
现在让我们引入一些围绕目标的可变性,以更好地符合物流运作的实际情况。
💡方法 在本次模拟中,我们不会关注这种可变性的根本原因,因为我们只想模拟其影响。
在结论中,我们将看到,我们可以通过使用数字双胞胎来影响特定于过程的参数,以坚持真实的过程,从而改进这种方法。
如何用 Python 模拟供应链风险?
随机提前期
对于每个过程,我们在目标周围添加一些可变性来模拟操作的现实。

随机传输、提货包装和运输提前期—(图片由作者提供)
在下面的例子中,我使用正态分布变量模拟了提前期目标的变化。
以下是使用的参数:提前期(分钟),COV (%)
{'Transmission': [60, 0.4], 'Pickpack': [480, 0.2], 'Loading': [90, 0.05], 'Warehouse Airport': [180, 0.15], 'Flight': [720, 0.15], 'Custom Clearance': [120, 0.3], 'Truck Preparation': [45, 0.2], 'Airport Store': [240, 0.25]}
场景模拟:结果
在正常情况下,总交付周期为 72.5 小时。
现在让我们测量一下这一理想结果与200 个提前期可变性模拟场景之间的差距。

模拟结果—(图片由作者提供)
结果分为四类:
- 遵守所有截止时间:交付时间非常接* 72.5 小时
- +24 小时:错过 1 个截止时间
- +48 小时:错过了 2 个截止时间
💡截止时间影响 由于两个截止时间之间的 24 小时时间窗口,流程中的两个小时延迟可以转化为交货时的 24 小时。
根本原因分析 我们很幸运,5 个截止点中只有 2 个被遗漏。现在,让我们深入了解运输的各个环节,找出根本原因。
装货提前期

加载提前期—(图片由作者提供)
对于 20 个场景,我们错过了装货截止时间(19:00:00 ),主要是因为拣货和包装过程中的延迟。
💡每个流程 订单传输延迟容差的影响:传输一个订单*均需要 1 个小时,但我们在截止时间之前有多达 3 个小时的时间(*均时间的 300%)。
所以错过这个截止时间的概率很低。
提前起飞时间

起飞—(图片由作者提供)
我们有同样的 20 个场景有很大的延迟(24 小时以上)。因此,从仓库运输到机场没有造成任何迟到。
💡关注仓库运营 我们可以看到,从中心仓库到机场的第一段行程绩效主要受装货提前期的影响。
绩效管理流程应重点关注
- 准备能力:每天可以准备多少订单?
- 开票流程:确保您的包裹一打包好就可以装货。
提前期通关

提前清关时间—(图片由作者提供)
我们现在有 48 小时以上的场景,以及 24 小时以上的附加场景。由于飞行时间造成的延误,几批货物在清关截止日期后到达。
端到端分析

端到端分析—(图片由作者提供)
在上图中,我们分析了总交付提前期:
- 红线以上:LT 的> 96h,因为错过了 3 个截止
- 橙色线以上:>LT 72h,因为错过了 2 个截止
- 黄线上方 : >因错过 1 个切断,LT 48h
- 绿线上方:所有的截止点都得到尊重
我们可以在红色图中看到,商店交付中断影响了几个场景并增加了 24 小时以上。
💡调整您的截止时间 如果您只是因为最后一英里送货而延误了时间,您的卡车会在送货截止时间后几分钟到达,您可以与商场运营部讨论调整时间或安排上午送货。
结论
关注我的 medium,了解更多与供应链数据科学相关的见解。
在本例中,我们展示了目标提前期无法保证。
这些延迟交付的情况给供应链带来了不稳定性,并消耗了运营团队的大量精力。
如果您的公司正在开发先进的端到端供应链分析功能,这种分析可以实现自动化。
用数字双胞胎模拟这些场景
数字孪生是物理对象或系统的数字复制品。
供应链数字模型是一种计算机模型,代表供应链中涉及的各种组件和流程,如仓库、运输网络和生产设施。

使用 Python 的数字孪生示例—(图片由作者提供)
您可以模拟实际流程参数的可变性,而不是设置我们使用正态分布生成的任意流程提前期
- 准备交付时间:仓库劳动力减少 20%会有什么影响?
- 运输提前期:每辆卡车有两个司机会有什么影响?
- 发货提前期:预开发票流程的影响是什么?
然后,您可以将业务或运营计划转化为影响模型行为的参数。
对于每个场景,您可以操作与计划相关联的参数,并查看您的整体性能会降低多少。
这个数字双胞胎可以用来
- 模拟销量增长或对你的分销网络进行压力测试:我们准备好承受下个黑色星期五 50%的销售增长了吗?
- 评估你的持续改进团队设计的几个解决方案:如果我们从商店附*的当地仓库发货会怎么样?
欲知详情,
https://www.samirsaci.com/what-is-a-supply-chain-digital-twin/
关于我
让我们连接上 Linkedin 和 Twitter ,我是一名供应链工程师,正在使用数据分析来改善物流运作和降低成本。
如果你对数据分析和供应链感兴趣,可以看看我的网站
精益机器学习:以精益创业的方式运行概念验证(第 1 部分)
如何转变构建机器学习产品的方式

图 Eric Reis 的《精益创业中的构建度量学习周期》。图片作者。
在他的书 The Lean Startup,中,Eric Ries 描述了他使用图 1 中描述的构建度量学习周期来构建一个成功的创业公司的方法。虽然我没有自己的创业公司,但作为一名数据科学家,我已经能够在我的生活中应用 Ries 的许多想法。
在整个系列中,我将描述如何通过应用精益启动方法来制作更好的机器学习产品。在第一部分,我将介绍精益创业方式,创新甜蜜点的四个关键领域,以及如何确定您的机器学习产品是否可取。
精益启动方式介绍
我经常看到数据科学家花几个月的时间在竖井中以令人难以置信的高精度构建机器学习模型。他们对自己的工作非常自豪,但在展示他们的成果时,他们经常发现企业客户并不真正理解或关心他们花了几个月时间研究的模型。在开始构建机器学习模型之前,我建议数据科学家遵循构建测量学习周期,并首先确定他们想从他们的模型中学习什么。
Ries 在书中描述的最关键的一点是执行和计划之间的区别。当创造一个产品时,首先制造这个产品,然后衡量它的成功,最后在产品上市后分析你学到的东西,这是很有诱惑力的。然而,Ries 认为创造一个成功产品的步骤应该反过来。

图 2:如何以精益启动的方式规划你的产品。图片作者。
你应该先确定你想学什么。你最终会努力了解什么是顾客的理想产品,以及你试图解决的问题。一旦你知道你想学什么,你的下一步就是找出如何衡量正确的事情,这样你就可以抓住你想学的东西。只有在你知道要度量什么之后,你才能计划如何构建它。在我的职业生涯中,我看到团队立即开始构建,但等到产品完全构建并发布后才获取度量。他们发现,他们构建产品的方式不允许他们收集我们需要的数据来捕捉我们想要学习的东西。
因此,重要的是要确保你构建产品的方式能够实际获取构建指标所需的数据,而不是一些最后一刻匆忙拼凑的“虚荣”指标。
创新的最佳时机
为了将精益创业应用于机器学习,你应该从规划你的概念证明(POC)开始。你已经知道你在努力学习,但是你在努力学什么呢?要回答这个问题,您可以参考创新最佳点(ISS),这是产品管理中的一个流行概念,如图 3 所示。

图 3:创新最佳点。图片作者。
传统上,基础设施服务由三个关键领域组成:理想的、可行的和可行的。一个好的产品创意是令人满意的、可行的、有生命力的。我发现 ISS 缺少一个关键领域——伦理。一个伟大的产品,尤其是涉及机器学习的产品,也应该是道德的。在机器学习社区中,伦理常常被忽视,所以我在图 3 所示的 ISS 中添加了伦理。
但是,你如何将 ISS 应用于机器学习产品呢?让我们来看看国际空间站的每个区域。
创新最佳点——令人向往
从合意开始,你需要确保你的用户想要你的产品。例如,网飞用户是希望*台向他们推荐电视节目和电影,还是他们真的喜欢自己搜索内容?这可能不是一个很好的利用你的时间来建立你的客户不想要的东西。在你建立了某种合意感之前,你不应该写一行代码。
创新最佳点——可行
技术可行性通常是人们在 POC 过程中最关注的问题。我的机器学习模型能达到 90%的准确率吗?数据质量是否足以训练我的模型?在这个领域,我们想确定用机器学习来解决我们的问题在技术上是否可行。虽然许多数据科学家用准确度、精确度和召回率等指标评估技术可行性,但我将在第二部分中介绍评估其他指标的重要性,如不正确预测的风险。如果你选择了错误的指标来确定可行性,你将会把自己带入一个痛苦的境地。
创新最佳点——可行
对于许多数据科学家来说,确定可行性通常是留给产品经理的事情,他们会说,一个可行的产品产生的价值大于构建和维护的成本。然而,由于数据科学家做出的决策,他们在维护解决方案的成本方面扮演着重要角色。例如,通过 GPU 训练的深度学习模型可能比简单的逻辑回归模型维护起来更昂贵。如果你不小心,你可能很容易以一个不可行的产品告终。

乔治·特罗瓦托在 Unsplash 上的照片
创新最佳点——道德
最后,我们触及其中最重要的一点——数据伦理是一个严肃的问题。令人欣慰的是,*年来许多人一直在关注这一领域。此外,许多机构现在都有机器学习中的数据伦理课程。虽然我将在第 3 部分尝试做这个主题体面的正义(即将推出!),我真的无法强调在您的组织中建立数据道德政策和创建机器学习顾问委员会有多重要。
许多人认为,如果产品没有实施,POC 就失败了。然而,POC 是一个实验,实验的目标是学习。当你开始的时候,你不知道你的产品是否会起作用。如果你发现它行不通,你只是学到了一个不值得花费进一步资源去实现的产品理念。恭喜,你成功了。说真的!只要确保你做好准备去学习正确的东西。最终,如果我们没有从 ISS 的每个领域中获得一些经验——理想的、可行的、可行的和道德的——我们的 POC 就失败了。
深入探讨确定合意性
在与其他数据科学家的交谈中,我经常被问到,“如果你的客户想要你的机器学习产品,你什么时候开始学习?”。我总是建议,这实际上是你与客户沟通时要做的第一件事。
确定一个机器学习产品是否令人满意不是我在学校学到的东西(查看这篇文章了解更多),但是这些年来,我从其他部门的同事那里学到了一些技巧。如果你有衡量受欢迎程度的策略,请在评论中给我们大家留言!

图 4:维生素 vs 止痛药。图片作者。
一位产品经理曾经告诉我,当你评估潜在的产品时,你应该确定产品是维生素还是止痛药。维生素有助于我们的身体正常运作,但它们往往不是正确的饮食和锻炼所必需的。相比之下,如果你经历过严重受伤或手术,你可能会发现止痛药在你的恢复过程中是必不可少的。
我见过很多顾客很痛苦,不管他们是否意识到了。许多人对可以改进的流程变得麻木,因为“这就是我们做事的方式”。如果你提出一个“生活可能是怎样的”的愿景,你会马上看到你是否找到了止痛药。
那么,展现“生活可能是什么样子”的愿景是什么样子的呢?以我的经验来看,它可以有很多种形式。最简单的形式是向你的客户做演示。在进行演示时,我将提供一份调查,从中我可以收集指标。是的。你刚刚经历了一个精益创业。我决定我想学什么——这是可取的吗?然后我决定我可以建立什么样的标准来学习这个。最后,我构建一个包含调查的演示文稿(产品),这样我就可以获取指标。

图 5:用一个演示来捕捉需求的例子。图片作者。
但是陈述是确定合意性的唯一方式吗?不——我们可以通过创建实体模型或线框,从用户体验(UX)等其他领域获取信息。这个模型是产品可能成为的一个可视化的、理想的交互式表示。UX 的设计师通常使用像 Balsamiq 和 Axure 这样的工具,但是我见过软件开发人员使用像 Microsoft Visio、Excel、PowerPoint 和 Keynote 这样的工具。事实上,如果安装了正确的软件包,Keynote 允许您模拟真实的网站或移动设备。
这些工具都有不同的复杂程度,但关键是你可以在一个下午做一个像样的模型来弄清楚你的客户对你的产品想法的感觉。
你可能想知道,“你到底是怎么为机器学习创建一个模型的?”。有趣的是,你的模型通常甚至不需要任何机器学习!在您的模型中,您可以从构建一个非常简单快速的基于规则的模型开始。你的客户可能并不指望你在初次会面时就能完全解决问题,而建立一个快速的、基于规则的模型会给他们一种“生活本该如此”的感觉。
我甚至制作了机器学习模型,使用几个关键词的字典对文档进行分类——不需要机器学习!当客户能够将他们的文件拖到一个文件夹中,并且文档被神奇地移动到合适的文件夹中时,他们立刻就被推销了这个产品。不要介意,如果他们试图涵盖每一个边缘情况,它会失败得很惨!关键是他们很容易就能看到幻象,我也能很容易地分辨出这是止痛药还是维生素。

图 6:一个简单的模型,用于测量机器学习分类器的可取性,该分类器通过将文档移动到文件夹中来自动分类文档。图片作者。
一个更复杂的模型版本可能包括为你的产品建立一个网站,并通过谷歌、脸书或其他高流量网站进行在线广告。在创建你的网站和广告后,你可以采取两种方法来决定是否受欢迎。
一种方法是获取个人的联系信息,通常以电子邮件地址的形式。这可以捕捉到一个人是否对你的产品有普遍兴趣,但它不会告诉你他们是否足够渴望该产品以支付购买。决定消费者是否会花他们辛苦赚来的钱购买你的产品是至关重要的。
第二种确定需求的方法是允许个人预购你的产品。当客户登陆您的网站时,您可以允许他们预购并提前全额支付产品费用,或者允许他们在预购时支付少量定金。但是,如果押金太少,一些个人可能会在产品完成之前取消。最终,拥有大量预购清单可以激发投资者的兴趣。
结论
当创建一个机器学习产品时,首先确定你的产品是否令人满意是非常重要的。你应该尝试用一种能产生清晰而强烈信号的方式来衡量可取性,但你的收获会因你的客户和他们的居住地而异。例如,如果你正在建立一个公司内部的机器学习产品,你可能无法通过建立一个要求员工存款的网站来衡量合意性。
别忘了——你是从一个实验开始的。你不会马上寻找一个高保真的产品。尽可能快地捕捉你的学习。请记住,首先要确定你想学什么,然后决定如何衡量它,最后,决定你需要建立什么,以捕捉这些指标。
参考
- 埃里克·里斯,《精益创业》(2011),《http://theleanstartup.com/book T4》
精益机器学习:以精益创业的方式运行概念验证(第 2 部分)
如何转变构建机器学习产品的方式

图 Eric Reis 的《精益创业中的构建度量学习周期》。图片作者。
欢迎回来!如果这是你看过的这个系列的第一篇文章,你可以在这里查看第 1 部分。简单回顾一下,我们一直在讨论如何通过应用 Eric Ries 的精益创业方法来构建更好的机器学习产品。
在第 1 部分中,我们介绍了机器学习概念证明(POCs)和产品管理中的一个概念,称为创新最佳点(ISS)。基础设施服务由四个关键领域组成——理想的、可行的、可行的和道德的。在第 1 部分中,我们介绍了确定您的机器学习产品是否令人满意的方法。在第 2 部分,我们将讨论机器学习产品的可行性和生存能力。最后,在第 3 部分,我们将给出构建一个合乎伦理的机器学习产品的提示。

图 2:创新最佳点。图片作者。
机器学习产品的技术可行性
当数据科学家开始机器学习概念验证时,技术可行性总是首先想到的。在第 1 部分中,我们强调了确定合意性实际上应该是 POC 的第一步。浪费时间去做一个没有人真正想要的产品有什么意义呢?但另一方面,没有人会买一个实际上不起作用的产品。确保你能真正解决手头的问题是至关重要的。但是可能不那么明显的是你确定某件事情在技术上是否可行的方式。
你在学校学到了什么衡量标准来确定机器学习模型是否成功?下面的图 3 显示了我的教授与我分享的一些。虽然这些指标有助于评估模型,但它们不会告诉你最终用户是否满意你的机器学习产品。

图 3:经典的建模指标。图片作者。
以你手机的自动完成功能为例。这些模型以 F1 分数高而闻名,但最终用户花了数年时间从 autocomplete 的错误中创造迷因。很多时候,数据科学家不知道图 3 中的指标阈值应该是多少,以满足给定用例的最终用户。这意味着我们需要根据交付给最终用户的价值来定义我们的指标。一旦我们确定了这一点,我们就可以很容易地确定我们的建模指标的阈值,从而为我们的最终用户带来一个可接受的解决方案。
创建简单模型的好处
在第 1 部分中,我们描述了使用简单的、基于规则的方法(图 4)的机器学习模型如何有助于确定合意性。我喜欢以这种方式开始我的机器学习概念验证,因为它允许我获得关于产品是否令人满意的*乎即时的反馈。然而,另一个好处是,我获得了一个基于规则的方法解决问题的基线度量。
请注意,如果基于规则的方法实际上满足了最终用户,并且增加了足够的价值,您应该将它作为您的最小可行产品(MVP)。很多时候,数据科学家只想建立机器学习模型——这是理所当然的,这是我们上学的目的,也是我们在这个领域的原因!然而,机器学习比基于规则的系统维护成本更高。如果我们希望我们的机器学习产品是可行的,我们需要考虑我们的解决方案要花多少钱(稍后会详细介绍)。

图 4:测量合意性的简单模型。图片作者。
如何衡量技术可行性
遵循 Eric Ries 的精益创业方法,我们首先需要确定我们想要学习什么。在这种情况下,我们想知道我们的机器学习产品是否可行。接下来,我们如何衡量问题在技术上是否可行?我们可以通过关注我们试图从最终用户那里获取的实际商业价值来做到这一点。让我们以一个文档分类器为例。
自动分类文档对最终用户有什么价值?很简单,您可以节省最终用户的时间。因此,我的一个基本衡量标准是了解目前手动分类文档花费了多少时间。这建立了一个非常基本的基线,我们可以在此基础上进行改进。但这不是我们唯一应该捕捉的东西。
如果文档分类不正确,我们还应该了解相关的风险级别。在某些领域,不正确的分类没什么大不了的,但在其他领域,这是生死攸关的问题。如果我们处于生死关头,我们可能无法优化为最终用户节省的时间。无论哪种情况,我们都需要确保我们的模型有一个安全的失败方式。如果我们不这样做,我们的最终用户可能需要审查我们的模型做出的所有预测,这将降低我们的解决方案的感知价值。

在 Unsplash 上由 janilson furtado 拍照
然而,这带来了另一个需要注意的重要基准—您的最终用户今天在这方面做得如何?如果您的机器学习模型将自动执行终端用户目前正在执行的任务,您可能需要在相同的水*上执行,或者比他们更好。如果节省时间的代价是准确性不如继续手动流程,最终用户可能不会高兴。
除了捕获终端用户今天执行任务的情况,您可能还希望捕获终端用户从您的模型接收预测所需的时间。我见过最终用户总体上节省时间的机器学习产品,但他们无法忍受要花多长时间才能得到预测。
最终,初始模型的伟大之处在于,您可以从最终用户的角度捕获所有这些指标,并快速建立基线。这使您能够迭代地改进每个指标,并获得最终用户何时对他们收到的价值真正满意的直觉。你的最终用户可能无法告诉你什么样的 F1 分数是可以接受的,所以你应该根据用户的体验来确定可行性。
确定技术可行性是一个反复的过程
像软件世界中的许多事情一样,采用迭代的方法来解决问题是一个很好的实践。这应该包括你确定技术可行性的工作。我最喜欢的创业概念之一是首先尝试手动解决你的问题。是的,最终目标是自动化事情,但是手动工作可以让你获得对问题的良好直觉。在创建机器学习产品的时候,你的第一个模型应该是 you !
是的,你没看错——你应该是模特。
作为模特,你会学到一些重要的东西。首先,你开始建立你对问题的直觉。这个问题会有多难解决?有可能的是,如果你正在努力做出正确的预测,机器学习模型可能也会努力。
其次,作为模型可以让你围绕数据本身建立直觉。您可能会了解哪些数据点有助于您做出预测。这将有助于你开始特征工程。
最后,你的机器学习模型的数据集可能是未标记的。如果要让人们为您注释数据集,您需要为注释者编写说明。几乎不可能编写一套很好的指令来注释您以前从未注释过的数据集。

照片由 Edz Norton 在 Unsplash 上拍摄
在第一轮成为模型之后,您可以使用新发现的直觉为基于规则的模型构建特性。正如在第 1 部分中所讨论的,找到一种基于规则的方法来解决所有问题并不可耻。基于规则的系统通常更容易维护,但是一旦有了太多的规则,情况就不一样了。一个规则太多的系统会变得脆弱,规则开始互相冲突。当规则的复杂性成为一篇论文时,维护一个机器学习模型可能会更有效,并产生更少的异常。
最后,在所有非机器学习方法之后,我们可以建立一个机器学习模型。许多新数据科学家希望直接进入深度学习方法,但我在使用简单模型方面取得了成功,如逻辑回归、k-最*邻、支持向量机、k-means 聚类和 XGBoost。如果您可以用一组巧妙的特性和一个简单的模型来解决问题,您将获得一些重大的好处。
第一个好处是,您的模型需要更少的数据来训练,最终将训练得更快。说到速度,简单的模型也会让预测更快。简单模型的另一个好处是它们更容易对生产中的问题进行分类。简单模型还有一个额外的好处,那就是可以让你更好地理解模型中的任何固有偏差。对于像迁移学习这样更高级的技术来说,就不能这么说了。最后但同样重要的是,简单的机器学习模型对环境更好。复杂的模型在计算上是昂贵的,并且需要更多的能量来创建预测。模型的复杂性和碳排放量之间有着直接的联系。

虽然数据科学家在学校被教导直接进入复杂的机器学习模型,但我建议你采取迭代的方法来建模。首先,自己做预测,然后尝试一个基于规则的模型来获得对你试图解决的问题的直觉。之后,你可以尝试一些简单的机器学习算法,只有在需要的时候才使用高级技术。
机器学习产品的生存能力
生存能力是橡胶遇到道路的地方。没有人愿意为一个成本高于人工操作的产品买单。此外,你可能想获利——我怀疑你的公司是在以成本价销售商品。

图 5:你的模式是否扼杀了你的底线?图片作者。
你可能已经知道成本、收入和利润应该留给财务部门去操心。大多数数据科学家没有意识到,他们在一个机器学习产品有多贵的问题上发挥了很大的作用。花费数周时间进行概念验证,却发现您的解决方案实施起来太过昂贵,这将令人沮丧。
技术可行性对你产品的生存能力有直接的影响。如果你使用复杂的机器学习模型,你可能需要更多的数据。额外的数据需要更多的存储成本。更复杂的模型需要更多的计算能力。这些因素都会增加模型预测的成本。
选择特定的云提供商会影响生存能力吗?
我听到许多数据科学家争论哪个云提供商拥有最准确的机器学习算法。我认为这是一个有争议的观点,我将用一个关于室温的类比来说明这一点。

图 6:人类可接受的温度范围。图片作者。
想象一下,你有一个完美的温控房间,你可以进行极其精确的温度变化。虽然在理论上,这听起来像是一个很好的方法来根据个人的喜好调整房间温度,但大多数人不会注意到一到两华氏度的温度变化。
现在,我们假设大多数人的舒适温度范围在华氏 60 度到 80 度之间。1 度的变化代表在该唯一范围内 5%的温度变化。我们基本上是说,大多数人甚至没有注意到 5-10%的变化。
因此,当我听到数据科学家争论哪个云提供商拥有最好的算法时,我会问,你的最终用户能看出一个云提供商相对于另一个云提供商在准确性上的微小提高吗?你的终端用户在乎吗?请记住前面的技术可行性部分,我们应该根据最终用户将获得的商业价值来放置我们的所有指标。如果您的最终用户不能区分这两者,您又何必在意呢?答案是不应该。
当你沉迷于云提供商之间的算法性能时,这可能会导致你的机器学习产品的成本增加。以运行在 AWS 中的应用程序为例,该应用程序也将其数据存储在 AWS 中。虽然谷歌或 IBM 可能会推出一种更精确的算法,但现实是,你的最终用户可能无法区分这两者。即使他们知道,他们可能也不会在乎承担将数据从一个云提供商转移到另一个云提供商所带来的额外成本。云提供商通常会向你收取更多的数据迁移费。当您完成概念验证时,您希望能够对实施和维护您的模型的成本做出合理的估计。
结论
在一天结束时,您希望确保您已经设置了 POC,以获取有关客户体验和获得的价值的指标。这些指标对于确定您的解决方案的可行性至关重要。最终用户可能无法告诉你理想的 F1、精度或召回率应该是多少。然而,他们可以告诉你你的解决方案是否太麻烦或者太令人沮丧。
在设计机器学习产品时,数据科学家通常偏向于使用复杂的模型。我的建议是保持简单和迭代。通过首先成为模型,然后转向简单的基于规则的方法,获得对问题和数据的直觉。如果这能解决问题,那太好了!如果没有,尝试几个简单的机器学习模型。只有在绝对需要的时候才使用复杂的方法。
至于成本,你可能应该为你的机器学习使用与使用机器学习的应用相同的云提供商。如果云提供商不能满足您的客户体验需求,您有理由测试不同的云提供商,但您也必须考虑随之而来的额外成本。这是一个不容忽视的重大决策点。这是你的底线!
参考
- 埃里克·里斯,《精益创业》(2011),【http://theleanstartup.com/book
精益机器学习:以精益创业的方式运行概念验证(第 3 部分)
如何转变构建机器学习产品的方式

图 1:创新最佳点。图片作者。
欢迎回到我们精益机器学习系列的最后一篇文章。在开始这篇文章之前,我们建议你先查看一下第一部分和第二部分。快速回顾一下,我们一直在讨论如何通过应用 Eric Ries 的精益创业方法来构建更好的机器学习产品。
在第 1 部分和第 2 部分,我们描述了如何制作一个令人满意的、可行的、可行的机器学习产品。这些领域都是传统创新最佳点(ISS)的一部分。我们已经将本系列的最后一篇文章致力于一个极其重要的主题——伦理。虽然我们不认为自己是数据伦理方面的专家,但我们希望提高人们对数据科学这一不断发展的子领域的认识。
在本文中,我们将把数据伦理分为四个部分——数据源、安全性、隐私和对社会的影响。虽然这不是一个详尽的列表,但它代表了该主题的一个很好的概述。
数据源
为了描述数据源与数据伦理的关系,我们首先需要定义什么是数据源。我们将主要把数据源称为你用来训练和测试你的机器学习模型的数据。但是,需要注意的是,模型本身可以被视为数据源。许多数据科学家不会从零开始构建模型。事实上,他们经常选择使用模型作为他们更大的解决方案的一部分。例如,数据科学家可能通过单词嵌入模型发送文本,为他们自己的模型生成特征。
对于任何用于创建机器学习模型的数据源,了解数据来自哪里以及如何生成都很重要。您应该从了解数据的质量开始,因为它将对您的模型的结果产生重大影响(垃圾输入,垃圾输出)。

约翰·施诺布里奇在 Unsplash 上的照片
数据质量的七个维度
数据质量通常通过七个维度进行评估——准确性、完整性、覆盖面、符合性、一致性、及时性和唯一性。让我们来看看每一个都意味着什么。
准确性 —准确性是指给定数据与其真实形式的关系。准确性低的数据集可能会因为手动输入数据而出现很多错误。
完整性— 完整性是指所需数据属性的可用性。例如,大多数电子文件需要作者、创建日期等。创建文件时必须包括。文件是数据、作者、创建日期等。是数据属性或元数据。数据集中记录的每个数据点可能都有任意数量的必需或可选元数据。
覆盖率 —覆盖率是关于所需数据记录的可用性。假设您正在收集 Covid 测试结果,但您选择只收集阳性 Covid 案例。虽然最初,您可能只对积极的情况感兴趣,但为了让机器学习模型预测 Covid 结果,它需要看到积极和消极情况的示例。覆盖率低是样本偏差最常见的例子。
符合 —符合是指数据内容与要求的标准相一致。例如,如果你正在收集信用卡号码,这些可能都应该是整数。你的数据集符合这个标准吗?
一致性 —一致性包括数据符合所需格式和定义的程度。这听起来可能类似于一致性,但是我们不是检查数据是否符合标准,而是检查一致性如何。
时效性 —时效性是所表现内容的流通性,也是数据是否可用,需要时是否可以使用。例如,当您每周甚至每天都需要数据时,接收月度数据并没有帮助。
唯一性 —唯一性是确保记录只被记录一次。换句话说,你的数据集中有重复的吗?例如,也许你有重复的用户,这意味着你的每日活跃用户数是倾斜的。
虽然看起来很奇怪,但这些维度中的每一个都可以应用于机器学习模型。比如你的模型是否及时?当需要时,你提供预测吗?这包括确保模型的输入准备就绪,以及让模型预测足够快,以便在需要时提供预测。
数据源中的偏差
当人们第一次讨论数据科学领域的伦理时,他们通常会想到模型偏差。就拿歧视女性的亚马逊简历审查员来说吧。然而,模型偏差通常是由原始数据源中的偏差引起的。
如果你还记得你的第一堂概率统计课,你可能已经知道了样本和总体之间的区别。在现实世界中处理数据时,您几乎总是在处理样本,而不是实际的总体。即使你是苹果公司,你也没有地球上所有iPhone 的用户数据。

图 2:人口与样本。图片作者。
在你所拥有的用户数据中,你可能会有不同的用户行为。一些用户通过上传照片、发送信息等方式不断地与你的应用程序交互,而另一些用户从不上传照片。那么面对这种现实,如何创建一个“有代表性”的数据集呢?你可能需要花很多钱来开发渗透到细分市场的应用程序,这样你才能抓住那部分缺失的人口。
但是如果你甚至不知道这个段的存在呢?最终,您需要非常努力地工作来检测您遇到新细分市场的时刻,并快速调整您的数据集以包含该细分市场。这不是一件容易的事——它需要认真的思考和资金来正确执行。
识别和理解模型偏差在任何机器学习产品中都至关重要。在我看来,数据质量是您企业的竞争优势。有偏见的数据或不能正确代表总体的数据会使组织变得脆弱。其他拥有更好数据的人可以并且会打败你。
虽然数据伦理是当今许多组织事后才想到的话题,但随着拥有更高质量数据的公司将更好地服务于其客户,这种情况在未来不会持续下去。我们处理数据的实践需要提升,偏见只是这个问题中的一个方面。关于对抗偏见的一种方法的介绍,请查看这个关于行为驱动的机器学习的视频。
数据安全和隐私
许多人交替使用安全和隐私这两个术语,但是澄清两者之间的区别是很重要的。安全性是指如何保护您的数据,而隐私是指如何使用您的数据。
equifax 2017 年的数据泄露是缺乏数据安全的一个例子——黑客能够访问数百万客户的姓名、社会安全号码,甚至一些驾照号码。许多数据科学家处理来自各种应用程序的数据,包括事务数据库的副本。这些数据必须受到与原始数据相同级别的保护。

另一方面,Target 的 2012 年怀孕预测模型代表了数据隐私的缺失。Target 为了增加销量,建立了一个模型来预测各种情况,在一种情况下,他们预测到一个青少年怀孕了。这位少女没有通知她的父母,所以当塔吉特百货送来婴儿用品的优惠券时,她感到很震惊。现在没有人黑 Target,也没有信息被窃取,但是这个人的隐私被侵犯了,他们的个人情况被泄露了。在许多方面,你甚至可能认为塔吉特违反了 HIPPA,因为他们建立了一个可以预测医疗状况的模型,并且他们选择通过邮件发送信息。
处理数据安全
那么我们如何处理这些情况呢?对于数据安全性,我建议您借鉴软件开发和 shift left 中的最佳实践,这促进了软件开发生命周期的一个阶段——在本例中是安全性——在过程的早期移动。
太多时候,当机器学习产品准备投入生产时,数据安全被讨论为它的最后一块。相反,在产品开发的早期阶段就让组织的信息安全团队参与进来是非常重要的。确保即使在概念验证(POC)期间也能正确处理数据至关重要。
数据安全的两个最佳实践包括加密静态数据和使用工作所需的最少量数据。我承认如果不知道创建模型需要哪些数据,后者是很难的。但是一旦你知道了,减少暴露的数据量是当务之急。可靠的数据管理实践和文档对此有所帮助。

即使你有最好的数据安全措施,你的机器学习模型也可以通过应用编程接口(API)间接暴露你的训练数据集。聪明的黑客可以通过成员关系推断和模型反演来捕获有关您的训练数据集的信息。
隶属度推断是一种利用机器学习模型产生的置信度得分的技术。如果模型在其训练集中看到了一个数据点,当它在生产中遇到相同的数据点时,它可能会有较高的置信度得分。黑客可以利用这一点,通过重复查询模型中具有高置信度得分的数据点,并使用这些数据点来重新创建训练集,而无需访问底层数据。
模型反演是另一种允许黑客从模型输出中重建原始输入数据的方法。换句话说,黑客可以建立一个模型,使用另一个模型的输出来预测原始数据是什么。这种技术甚至可以用来捕捉数据的上下文。
处理数据隐私
那么我们如何防止像成员推断和模型反演这样的攻击呢?要回答这个,我们先来讨论一下数据隐私。

虽然这个主题值得单独写一篇文章,但我想简要强调一下差分隐私。作为一名数据科学家,您可能习惯于处理原始数据。但是,在处理个人用户数据时,这可能会侵犯个人的隐私权。差分隐私试图通过在数据中聚合或添加噪声来解决这一问题,从而使个人用户不再是可识别的。
差分隐私是一个严重的问题,它也恰好是上一节讨论的安全问题的最有希望的解决方案。差别隐私的关键是当你收集数据集时,能够产生可信的否认。为了说明似是而非的可否认性,你可以把它想象成一种算法,在这种算法中,你通过抛硬币来决定你将获得正确的答案还是相反的答案。虽然你不知道哪些答案是对的,哪些是错的,但是你可以从概率上计算出一个足够大的群体的*均真实答案。这是强大的,因为你可以知道答案是什么,而不知道任何个人的答案。每个人都有似是而非的否认,而你有总的真相。
即使你认为你已经混淆了一个数据集,你也可能没有考虑到黑客将来可能获得的所有数据来识别你的数据集中的个人。为了保护个人隐私,你应该深入思考如何用不同的隐私算法产生合理的可否认性。
机器学习顾问委员会的重要性
妥善处理数据隐私需要一个村庄。在机器学习顾问委员会中拥有一个多样化的个人群体是帮助实施数据隐私措施的一种很好的方式。一个全面的顾问委员会可能包括来自不同部门的代表,包括人力资源、法律、信息安全、产品管理和工程。此外,将道德、多元化和包容部门的领导纳入该小组也很重要。咨询委员会还应该有自主权和决策权。没有什么比一个咨询委员会不能为维护良好的道德标准做出必要的改变更令人沮丧的了。

咨询委员会的最后一个考虑是模型发布后的可接受使用。一旦一个模型开始做出预测,很有可能有人想要将这些预测(或者甚至模型本身)应用到一个新的、潜在的非预期用例中。重要的是,模型有适当的文档和数据管理来帮助防止这种情况,并且模型或其数据的任何新用例都应该由顾问委员会仔细审查。
机器学习模型对社会的影响
最后但同样重要的是,在我们关于数据伦理的讨论中,我们必须考虑我们的机器学习模型如何影响社会。我们的模型对人类有什么影响?
关于算法如何改变青少年对自己的感觉,已经有一些令人震惊的统计数据。我们也看到了算法如何无意中延续历史上的种族主义比喻。虽然 10 年前,公司道歉并说“我们不知道会发生这种事”可能是可以接受的,但现在没有借口了。
我们知道机器学习会给社会带来意想不到的后果。机器学习顾问委员会帮助数据科学家考虑对社会的潜在影响至关重要,应该有适当的指标来衡量这一点。是的,这将使机器学习更加昂贵,但这是我们必须要求公司承担的成本。
机器学习模型也通过碳排放和剥削性数据标签实践影响社会。许多最复杂的机器学习算法在计算上是昂贵的,这导致了更高的碳足迹和增加的财务成本。在算法复杂的情况下,公司自然会被激励使用更简单的模型,因为它们实现起来成本更低。但是,用来标记所有这些数据以创建机器学习模型的劳动力呢?大型组织通常雇佣或外包全部劳动力来为他们标记数据。在许多情况下,这些人来自较低的社会经济背景,当使用这些系统时,很容易剥削整个阶层的人。公司制定道德政策来帮助保护这些劳动力免受剥削是很重要的。
结论
虽然机器学习已经成为我们这个时代最热门的词汇之一,但它的伦理困境正开始变得越来越明显。像《社会困境》这样的电影才刚刚开始揭示这个重要的话题。
如果这是您第一次介绍数据伦理,您可能会被实现机器学习模型时要考虑的所有领域所淹没。好消息是,你不必单干。依靠组织中具有其他背景(如法律和信息安全)的个人来帮助您做出关于模型的数据隐私和安全的明智决策。更好的办法是,咨询你公司的机器学习顾问委员会,如果他们有的话。
公司需要时间来更加重视机器学习中的数据伦理,但未来是光明的。机器学习社区有解决这些问题的强烈愿望,随着该领域的不断发展,我们期待了解更多。
参考
- https://www . Reuters . com/article/us-Amazon-com-jobs-automation-insight/Amazon-scraps-secret-ai-recruiting-tool-that-show-bias-against-women-iduscn1 MK 08g
- https://youtu.be/fYQwnyizP9s
- 【https://en.wikipedia.org/wiki/2017_Equifax_data_breach
- https://www . drive research . com/market-research-company-blog/how-target-used-data-analytics-to-predict-guests/
- https://www.bbc.com/news/technology-58462511
- https://www . the guardian . com/technology/2021/sep/14/Facebook-aware-insta gram-有害-影响-少女-泄露-揭露
学习编码:13 个免费网站帮助你开始
原文:https://towardsdatascience.com/learn-coding-13-free-sites-to-help-you-do-it-9b2c1b92e573

克里斯托夫·高尔在 Unsplash 上拍摄的照片
一旦你决定要学习编码,你会被众多的在线工具所宠坏,这些工具可以帮助你做到这一点。这就是为什么我们写了这篇文章给你一个优惠的初步概述,并帮助你选择。
为什么要学习编码?
如果你已经达到了本文的这一点,我们可能不需要说服你学习编程是一个好的决定。无论如何我们都会这样做,也许你会找到一些额外的要点来加强你的决定:)
- 编程是许多行业都非常需要的技能。
- 你可以实施和实现你自己的项目和想法。
- 开始学习编码不需要任何先验知识或学位。
- 许多有趣的领域,如机器学习或加密货币,都是基于编程代码的。有了编程技能,你可以在这些令人兴奋的领域工作,并更好地理解它们的基础。
在哪里可以学习编程?
在这一节中,我们总共编译了 13 个不同的网站,它们提供免费服务,并且是各种编程语言的良好介绍。
代号
在该*台上,有抱负的程序员可以学习 12 种不同的语言,如 Python ,Java,Go,JavaScript,Ruby,SQL,C++,C#,Swift 和 Sass,还可以学习 HTML 和 CSS。此外,课程按照网页设计、机器学习、游戏设计、数据分析等不同的学科块进行架构。
各个模块包含符合您学习水*的互动项目和测验。你自己的知识水*就这样被一次又一次地检验。除了实际任务之外,Codecademy 还提供论坛、编程语言词汇表、博客帖子和文章来帮助您学习编程。根据他们自己的数据,4500 万学习者在短短七年内通过他们的网站提高了他们的编程技能——你是其中之一吗?
freeCodeCamp
开源社区提供了各种编程挑战、项目、证书和网络机会。与 Codecademy 不同,这里没有固定的学习路径,所以你可以获得非常具体的技能。特别酷的是与非营利组织的项目——所以你可以学习编码,同时做好事!
黑客排名
在这里,你与学习编码的参与者直接竞争,通过解决代码挑战来提高你的开发技能。比赛很有趣,因为你可以通过解决挑战获得积分,并争夺 HackerRank 排行榜的榜首。同样,没有固定的课程——明确的理念是“边做边学”,所以作为一个初学者,你可能想把这个项目更多地作为与其他课程一起的练习。
麻省理工学院开放课件
教育*台允许你免费使用著名的精英麻省理工学院。在线图书馆也包括大学教授的所有主题。在工程学院可以找到计算机科学课程。与这里提到的其他课程相比,没有游戏化的方法。阅读这些材料需要很强的纪律性,作业和测试必须独立完成和评分。对于更有经验的程序员,这里有一些令人印象深刻的课程,你应该在学习过程中仔细看看。
哈索-普拉特纳学院
哈索·普拉特纳学院(HPI)也提供教育机会,不仅向自己的学生开放。在其互动教育*台上,你可以找到免费的在线课程,没有入学限制。设计思维、信息技术和计算机科学领域的课程以德语或英语提供。
技术实验室
志愿者技术社区每学期提供四门课程:数据科学、人工智能、网络开发和用户体验。TechLabs 为所有参与者提供独特的实践学习体验,包括电子学习工具、每位参与者的个人学习路径以及经验丰富的导师。通常,课程持续 6 个月,每周学习约 5 小时。不管先前的知识和个人背景如何,任何人都可以应用和学习编码。不用担心,其实大部分新参与者都没有或者非常有限的编程经验。
Coursera
你可以在 Coursera 上学习任何东西,包括编程。这些课程并不是完全免费的,因为不幸的是,认证是需要付费的。但是你仍然可以从初学者或更高水*免费学习编程入门、HTML、CSS、loT 编程、 Python 、Java 等等。
edX
edX 汇集了哈佛、麻省理工和乔治敦等知名大学的免费在线课程。仔细看看计算机科学和数据科学类别。在这里可以获得全栈开发、Javascript、 Python 、AI、机器学习等综合课程。同样,你必须支付审计证书。此外,还有一些付费课程,但在大多数情况下,你可以通过参加免费课程学到很多东西。
Udemy
在 Udemy 上,你会发现用户生成课程的最大集合。除了大量的免费课程,还有针对各个知识层次的付费编程课程。选择课程时,一定要查看课程评级——这通常能很好地表明哪些课程特别值得。
Udacity
该*台提供了广泛的课程,以在实际项目中实践理论技能。可以按照自己的节奏掌握编程、数据科学、人工智能、云计算等话题。
SoloLearn
学习编程在这里变得社交化、互动化。除了提供 25 门课程之外,SoloLearn 还致力于创建一个开发者社区,学习者可以在这里进行互动,获得积分,并出现在顶级学习者部分。和 Codeacademy 一样,SoloLearn 也可以通过网络和手机应用程序使用。所以你可以随时随地学习编码。此外,你可以成为谈话的一部分,问或回答问题,并与其他学习者交流思想。有了关于 Python 、C++、Java、Javascript、SQL、PHP、HTML & CSS、React 和 Angular 的课程,你可以找到几乎所有与代码有关的东西。
微软学习
Microsoft Learn 帮助学习者掌握编程语言,无论他们是初学者还是高级人员。作为一名编程学习者,你可以选择保持在线,按照自己的进度学习,或者参加教师指导的课程。也可以得到*台的认证。
Hackr.io
堪比 Udemy,任何人都可以在这里发布课程。随着其他用户的积极评价,最好的课程被放在顶部。使用过滤器,您可以隐藏不相关的课程和教程,并选择符合您个人需求的课程和教程。
在线学习编码有很多免费的方法。根据你选择的课程,你可以根据自己的进度或多或少地学习,深入探索有趣的话题,并与虚拟社区联系。
上面列出的资源只是学习编码的初步指南。在你的个人学习旅程中,你一定会找到很多其他人。那么你应该带走什么呢?每种教育资源都有其优势和需要改进的地方。所以使用不同的网站,浏览一下——多多尝试。这是你找到适合你的学习媒介(和你目前的技能水*)的唯一方法。如果你发现了很酷的东西,请告诉我们!
你现在已经有了学习编码的基础,所以开始你的旅程吧…对于问题和具体的项目,你可以在我们的网站上找到一切!
这是你应该带走的东西
- 同时,开始编程有无数的可能性和理由。
- 本文总共展示了 13 个不同的站点和工具,在这些站点和工具上,您可以借助课程、讲座、游戏或论坛来学习编码。
- 您可以利用这些优惠来制定自己的个人学习计划。
如果你喜欢我的作品,请在这里订阅https://medium.com/subscribe/@niklas_lang或者查看我的网站* 数据大本营 !还有,medium 允许你每月免费阅读 3 篇 。如果你希望有无限制的 访问我的文章和数以千计的精彩文章,不要犹豫,点击我的推荐链接:【https://medium.com/@niklas_lang/membership】每月花$5***获得会员资格**
*https://medium.com/@niklas_lang/understanding-the-backpropagation-algorithm-7a2e3cb4a69c https://medium.com/@niklas_lang/intuitive-guide-to-artificial-neural-networks-5a2925ea3fa2 https://medium.com/@niklas_lang/powerful-tool-to-understand-deep-neural-network-predictions-93798efc8eb6 *
了解如何轻松补充推特信息
原文:https://towardsdatascience.com/learn-how-to-easily-hydrate-tweets-a0f393ed340e
使用 DocNow 的 Hydrator 应用程序和 twarc 工具

亚历山大·沙托夫在 Unsplash 上拍摄的照片
1.介绍
*年来,社交媒体作为一种交流工具的使用越来越多,导致社交媒体研究获得了很大的关注。Twitter 就是这样一种流行的在线交流形式,它既提供了一个易于访问的 API 来收集数据,又提供了许多工具来分析这些收集到的数据。人口统计、情绪、社会趋势;从 Twitter 上可以收集到的信息种类似乎是无穷无尽的!
与任何数据分析一样,第一步是收集相关数据(在我们的例子中,是推文)。然而,通常自己收集你需要的推文并不总是可行的(例如,如果你需要历史推文)。值得庆幸的是,由于 twitter 的流行,你可能会在网上找到合适的 Twitter 数据集。话虽如此,但有一个问题:Twitter 限制向第三方重新发布 Twitter 内容,因此我们可以在网上获得的只是由相关推文的推文 id 组成的数据集(或者脱水推文)。然后,我们必须合成这些 tweet IDs,以获取 tweet 内容。

Twitter APIs 的受限用例(更多详情,请访问https://developer . Twitter . com/en/developer-terms/more-on-restricted-Use-Cases)
我正在研究如何对这样的数据集进行水化,并发现了为此提供的两个工具:一个桌面应用程序 Hydrator 和一个命令行工具 twarc。我想我应该在这里写下我使用这些工具的经历,以防对其他想做同样事情的人有所帮助。
2.入门指南
第一步:找到要处理的数据集!
我将在本文中使用由陈等人【1】提供的数据集。如果你希望只处理这个数据集,他们已经提供了一个脚本,用于合成他们收集的推文(由埃德·萨默斯编写)。因为我的主要目标是熟悉补水工具,所以我不会使用他们的脚本。首先,我决定只使用他们数据集中 2020 年 1 月的 tweet IDs。注意:如果你想从 GitHub 库中下载特定的文件夹,你可以查看down git。然而,永远记住引用你正在使用的库!
第二步:创建一个 Twitter 开发者账户
从事任何与 Twitter 相关的工作都需要你在 Twitter 开发者门户上创建一个账户。在门户网站上注册后,您可以创建一个新的“应用程序”来指定您的研究目的。你现在要做的就是记下你的消费者(API)密钥、消费者(API)秘密、访问令牌、访问令牌秘密和不记名令牌(可选)。
第三步:让我们把文件准备好!
我们将在本文中使用的 twitter 数据集遵循命名约定“冠状病毒-tweet-id-年-月-日-小时”,我们感兴趣的 tweet IDs 分布在 242 个文件中!因此,我们需要首先将所有这些 tweet IDs 合并到一个文件中。由于我使用的是 Windows,这可以通过简单地在命令提示符下输入copy *.txt merged_tweet_ids.txt来完成。查看合并后的推文文件,仅在 2020 年 1 月,我们就有大约1100 万条推文!
现在,我们都准备好开始补水了!
3.补水推文
工具 1: DocNow/Hydrator :
让我们从桌面应用程序开始。你可以在这里下载最新发布的 Hydrator 应用。

Hydrator 应用程序启动屏幕(图片由作者提供)
从应用程序的启动屏幕可以看出,我们需要将我们的 twitter 帐户链接到应用程序,以检索推文内容。点击“链接 Twitter 账户”会在你的浏览器中打开一个链接,如下图所示。你只需要授权给 Hydrator 应用程序就可以得到一个 PIN。在应用程序中输入这个 PIN,你就可以开始水合你的 tweet IDs 数据集了(详情见下图)。

点击“链接 Twitter 账户”后的 Hydrator 屏幕(图片由作者提供)

授予 Hydrator 权限(图片由作者提供)
接下来,你必须点击应用程序中的“添加”标签,并选择你的推文 id 数据集。一旦文件被验证(你也可以确认应用程序读取的 tweet IDs 的数量),如果你愿意,你可以添加关于这个数据集的信息(见下图)。现在剩下的就是点击“添加数据集”了!

添加新的 Tweet IDs 数据集(图片由作者提供)
我们都准备好了!现在,我们所要做的就是点击“开始”(你可以随时停止补水),应用程序将开始为你的推文 id 补水。注:所有水合推文都存储在你指定的一个. jsonl 文件中(第一次点击“开始”时)。

开始/停止补水推文(图片作者)
工具 2: DocNow/twarc
现在让我们转到命令行工具, twarc。我在一台 Windows PC 上工作,并为此使用了 Anaconda。让我们首先为我们的项目创建一个虚拟环境(并避免任何未来的包冲突!).
首先,让我们使用cd *path*导航到我们的项目文件夹。现在,我们使用命令conda create --name *env_name* python=3.10在这个目录中创建一个虚拟环境。在这里,我还指定了我希望在环境中安装的 python 版本(您可以跳过它,Anaconda 将安装一个默认的 python 版本)。我们现在可以通过输入命令conda activate env_name来激活我们的虚拟环境。环境激活后,您可以看到括号内的环境名称,如下图所示。

激活的康达环境“twitter_env”(图片由作者提供)
接下来,让我们在我们的环境中安装 twarc。我用pip安装了twarc包(pip install twarc)。现在,有两种方法可以使用twarc给推文补水。
- 您可以直接从命令行使用
twarc:在这种情况下,您需要首先用您的凭证配置twarc工具(键入twarc configure;见下图)。配置完成后,补水就像输入命令一样简单:twarc hydrate *merged_tweets_ids.txt* > *hydrated_tweets.jsonl*。

twarc 配置(图片由作者提供)
2.您可以使用twarc作为一个库:对于这个方法,您可以创建一个twarc的实例,如下所示。
from twarc import Twarct_inst = Twarc(consumer_key, consumer_secret, access_token, access_token_secret)
我更喜欢使用第二种方法,因为:
(a)您可以简单地将您的凭证添加到中。env 文件,这比通过命令行输入凭证更安全(坦率地说,也更容易)。你所要做的就是创建一个。env 文件。样品。在我们的例子中,env 文件将如下所示:
BEARER_TOKEN=*BEARER_TOKEN*
CONSUMER_KEY=*CONSUMER_KEY*
CONSUMER_SECRET=*CONSUMER_SECRET*
ACCESS_TOKEN=*ACCESS_TOKEN*
ACCESS_TOKEN_SECRET=*ACCESS_TOKEN_SECRET*
(b)你可以直接将你的含水推文添加到数据库中,使你更容易探索数据
如果您也希望遵循第二种方法,您可以使用以下示例脚本:
# import required libraries
import os
from dotenv import load_dotenv
from twarc import Twarc
from pymongo import MongoClient# load your environment variables
BASEDIR = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(BASEDIR, '.env'))
load_dotenv('.env')# access credentials from environment variables and provide them to the twarc instancet_inst = Twarc(os.getenv("consumer_key"), os.getenv("consumer_secret"), os.getenv("access_token"), os.getenv("access_token_secret"))# start hydrating tweets and storing them into a MongoDB database (Database*: twitter_db, Collection: covid_tweets*)num_tweets=0for tweet in t_inst.hydrate(open('merged_tweet_ids.txt')):
client = MongoClient('localhost', 27017)
db = client.twitter_db
db.covid_tweets.insert_one(tweet)
num_tweets += 1
if num_tweets % 10 == 0:
print('Number of tweets added into database:{}'.format(num_tweets))
4.我的想法
如果您只是需要将 tweet IDs 数据集合并到 JSON 中,那么 Hydrator 应用程序似乎是最直接的方法。根据你的方便选择开始/停止补水也是一个明显的优势。然而,如果你希望有更多的灵活性(例如,使用数据库)或者你希望做的事情不仅仅是简单地合成 tweet IDs(例如,使用 Twitter API 过滤/搜索 tweet ),那么使用twarc似乎是一个不错的选择。此外,twarc工具还提供了许多其他工具(例如,创建文字云、推特墙等。)这将是相当有趣的探索。
当然,这是我第一次使用twarc,所以我不是最有效使用这个工具的权威。您可以查看这些链接,获得关于使用twarc的更全面的指南:
- https://scholarslab.github.io/learn-twarc/
- https://github.com/alblaine/twarc-tutorial
- https://ucsb-collaboratory.github.io/twarc/
感谢阅读!😃
这就是这篇文章的结尾!
我们都准备好开始分析我们的水分推文了!
快乐学习!
参考:
[1] Chen,e .,Lerman,k .,和 Ferrara,e .,跟踪关于的社交媒体话语:公共冠状病毒推特数据集的开发 JMIR 公共卫生监测,2020,6(2):e19273 (DOI: 10.2196/19273,:32427106)
了解如何使用 NeoVim 作为 IDE
原文:https://towardsdatascience.com/learn-how-to-use-neovim-as-an-ide-bc8a4b12cdb2
关于如何设置和使用 NeoVim 的一步一步的教程,适合完全的初学者

照片由 AltumCode 在 Unsplash 上拍摄
很多年前,我尝试了 NeoVim,我被需要的设置数量吓坏了,我跑了。
我跑到我的常规 VS 代码的舒适处。一个有效的 IDE,看起来不错,拥有我想要的大部分功能。
我以为我在 VS 代码中是有效的,但后来我决定再试一次 NeoVim。
有些人可能会问,当您可以在一分钟内完成一个完整的工作时,为什么要花几个小时来设置一个 IDE,我同意。但是让我们面对现实吧,如果我们每天都要在 IDE 中工作,几个小时的配置并不是一件坏事。
使用 NeoVim 的原因是它是超级可定制的,你真的可以让这个编辑器看起来像你想要的样子,让它的行为像你想要的样子。
NeoVim 的缺点也是定制能力,它需要一些时间来设置和修改在未来。但是一旦你知道如何添加你想要的任何特性,这并不困难或费时。
你也可以在 YouTube 上看这篇文章
本教程不会给你一个完美的 IDE 配置,因为你可以搜索人们的点文件。本教程旨在帮助你理解 NeoVim 并学习如何管理它,让你以后可以构建任何你想要的东西。我不会期望在 Vi 或 NeoVim 中有任何先前的知识。
你知道他们说什么吗
给开发人员一个 IDE,他/她就会编码。
给开发者定制 IDE 的能力,他/她将成为开发者之神。
如果你想了解更多关于 Vim 的知识,一本很棒的书是《现代 VIM》。你可以在这里找到,链接是联盟链接,购买会赞助我。
安装 NeoVim 并打包插件管理器
按照 GitHub 上的说明开始安装 NeoVim。如果你使用的是 ubuntu,我不推荐使用 apt-get install,因为它是一个旧版本。
curl https://github.com/neovim/neovim/releases/download/stable/nvim-linux64.tar.gz
tar -xvf nvim-linux64.tar.gz
将解压后的文件夹移到存储软件的地方,或者将在./nvim-linx64/bin/nvim找到的二进制文件添加到您的 PATH 中。
然后,您可以通过运行nvim来打开它
nvim
这样做将打开 NeoVim 和一个闪屏。请记住,NeoVim 更多的是一个终端,而不是一个具有花哨 UI 的常规编辑器。
您的终端应该如下所示

NeoVim 闪屏
如果我们不开始添加一些插件,NeoVim 就没有什么特别的了。这是大部分人刚开始使用 NeoVim 时被吓跑的部分。所以让我们试着简单点。
你现在可以通过按下SHIFT+:关闭 NeoVim,这将打开命令提示符,然后在底部出现的选项卡中写下q,并按下 enter 键。
按下SHIFT+:将打开命令选项卡,允许我们传递命令。
我们需要一个插件管理器,我们将使用的一个流行的插件是 Packer 。
我们可以在 bash 中使用一行程序来安装它,它将获取存储库并将其放在第二个参数的~/.local/share/nvim路径中。除非你知道你在做什么,否则不要修改这个路径,它使用默认路径指向 Vim/NeoVim 中的 PackPath。
git clone --depth 1 https://github.com/wbthomason/packer.nvim\
~/.local/share/nvim/site/pack/packer/start/packer.nvim
现在我们已经安装了 Packer,我们需要第一次访问插件配置文件。
所有的配置都保存在一个 LUA 文件中,默认保存在~/.config/nvim/lua/。在该位置,我们可以指定配置文件。
编写第一个配置&导入插件&导航
NeoVim 中的一切几乎都是由一个插件处理的,例如,如果我们将 NeoVim 用作 IDE,我们将需要语言服务器来理解代码。
我们将继续创建第一个配置文件,它将是~/.config/nvim/lua/plugins.lua。
如果该文件夹不存在,则创建它并通过用 NeoVim 打开它来创建文件。
mkdir -p ~/.config/nvim/lua
nvim ~/.config/nvim/lua/plugins.lua
通过将名字传递给require函数,可以在 Lua 文件中导入 Packer。然后,我们将在 NeoVim 上运行一个名为startup的函数,该函数将在 NeoVim 启动时执行,这意味着每当 neo vim 启动时,我们都会添加插件和功能。
按下Insert或i进入插入模式,然后添加以下内容
return require('packer').startup(function(use)
-- Configurations will go here soon
use 'wbthomason/packer.nvim'
end)
我们所做的是让 packer 在启动时运行,然后导入我们想要使用的所有插件,我们还包括 Packer 插件。这允许包装商自我管理和更新等。
按ESC退出插入模式,打开命令选项卡SHIFT+:并传入w保存文件,T10 是 write 的缩写。
如果你现在重启 NeoVim,什么都不会改变,很郁闷吧?
我们还需要告诉 NeoVim 找到这个配置文件并加载它。
这可以在第二个配置文件~/.config/nvim/init.lua中完成,这个文件在启动时由 NeoVim 执行,我们将需要 Lua 中的配置脚本。
再次打开plugins.lua文件,让我们学习如何在 NeoVim 中导航(暂时),这样我们就不需要一直跳出去了。
一旦 NeoVim 打开,输入命令Explore,这将为我们打开一个基于文本的浏览器。它叫 Netrw ,我就不细说了,它甚至可以抓取远程文件等。

探索显示 NetRW 目录列表
从这里,您可以看到当前目录中的文件,以及默认的 Linux 路径指示器./和../。一个点是当前目录,两个点是父目录。
我们想在父目录中创建init.lua文件,所以选择../后按 enter 键,然后按SHIFT+%。Shift+%会在底部打开一个输入栏,要求输入文件名,输入init.lua。它将在创建文件后打开它。
我们将向它添加一行,如果它不存在,则创建该文件。这一行将确保插件被添加。
require('plugins')
使用w命令保存并重启 NeoVim,我们应该已经安装了 Packer,我们可以通过运行命令来控制它。
如果 Packer 安装正确,我们应该可以使用它的命令,其中一个是PackerStatus,它打印所有使用的插件列表。

PackerStatus 输出所有使用的插件
你可以按 q 键把状态栏打印下来。
简而言之,我们到目前为止所做的。
- 学习了如何使用 Packer 来导入插件,
~/.config/nvim/lua/plugins.lua与此相关。 ~/.config/nvim/init.lua允许我们告诉 NeoVim 在启动时尊重我们的 Packer 文件。- 使用
SHIFT+:运行命令,如PackerStatus。 - 学习如何使用
Explore命令导航。
到目前为止,这可能看起来不太令人印象深刻,我同意。让我们添加更多的插件。
我们要编码,所以我们需要语言服务器和调试器等插件。有一个很棒的插件叫做 Mason 可以用来做这个,让我们更新一下打包器的配置,你现在应该知道是哪个文件了吧!我们还将获取neovim/nvim-lspconfig,这使我们能够获取不同 LSP 的不同配置,查看文档以获得完整列表。
将插件配置更新为以下内容。
return require('packer').startup(function(use)
-- Configurations will go here
use 'wbthomason/packer.nvim'
use 'williamboman/mason.nvim'
use 'williamboman/mason-lspconfig.nvim'
use 'neovim/nvim-lspconfig'
end)
一旦完成,打开命令栏SHIFT+:保存文件,然后运行命令w,它将记录下你所做的任何更改。
我们已经保存了文件,但是我们需要重新加载它,这样 Packer 才能注意到这些变化,所以打开命令栏,输入下面的命令,它将重新加载当前打开的 lua 文件。
:luafile %
然后执行PackerInstall命令下载新插件。
:PackerInstall

PackerInstall 显示成功安装的软件包
请注意,我们现在安装的插件只帮助我们设置和配置 LSP。例如,如果我们想要 GOPLS,它不会为我们下载它们,我们仍然需要单独安装。
启用语言服务器协议支持
使用 nvim-lspconfig 要求我们安装第三方语言服务器,然后可以连接到 NeoVim。这就是我如何在 Mason 的帮助下启用 GOPLS 的。
我们首先启用 Mason,这与 Packer 的做法相同,我们创建一个 lua 脚本来启用它,然后在init.lua中需要该脚本。
让我们从创建 lua 文件来管理 mason 开始。
touch ~/.config/nvim/lua/mason-config.lua
然后打开文件,并填写下面的代码,要求插件和调用他们的设置功能。
require("mason").setup()
require("mason-lspconfig").setup()
使用w保存文件。
然后我们通过使用名称前缀mason-config来更新init.lua以要求该脚本。在文件之间切换时,记得使用Explore。
require('plugins')
require('mason-config')
一旦完成,打开 NeoVim 应该有新的Mason命令,这将为我们打开一个设置管理器。
如果您使用的是早于 0.7 的 NeoVim 版本,打开 NeoVim 时可能会出现错误,请按 enter 键并运行命令
:checkhealth
这是一个非常有用的命令,用于测试和检测任何错误。

运行旧版本 NeoVim 时出错
运行:Mason将调出所有可用的 LSP,找到您拥有的 LSP,并记住名称。我就装gopls。

Mason 命令调出一个 UI 来设置语言服务器
要安装它,运行命令,添加您想要的任何包,如果您添加了许多包,那么用空格将它们分开。
MasonInstall gopls
请注意,每个安装可能需要您安装软件,就像 gopls 安装需要 go 一样。
现在 gopls 已经安装好了,我们需要让 NeoVim 附加上它。
这真的很简单,过程现在应该很熟悉了,我们将在 lua 文件夹中创建一个gopls.lua文件,然后我们将在init.lua中需要该脚本。
gopls.lua将导入 lspconfig 包,它允许我们将 gopls 附加到 neovim。
您可以配置 gopls 来做很多事情,我们将使用 gopls 文档中的简单示例,每个服务器都有自己的一组配置,您需要查看它们的文档来指定哪些内容。
-- Require LSP config which we can use to attach gopls
lspconfig = require "lspconfig"
util = require "lspconfig/util"
-- Since we installed lspconfig and imported it, we can reach
-- gopls by lspconfig.gopls
-- we can then set it up using the setup and insert the needed configurations
lspconfig.gopls.setup {
cmd = {"gopls", "serve"},
filetypes = {"go", "gomod"},
root_dir = util.root_pattern("go.work", "go.mod", ".git"),
settings = {
gopls = {
analyses = {
unusedparams = true,
},
staticcheck = true,
},
},
}
如果你正在使用 go,你会想添加更多的分析等,但我们在这里如何学习如何设置,然后定制是由你决定的。
我向你推荐 go 版的 ray-x ,按照本教程学习后,它应该很容易安装。
确保在init.lua中需要新的 lua 脚本。
require('plugins')
require('mason-config')
require('gopls')
现在创建一个main.go,看看它是否能检测到错误,这里有一张图片显示了我的错误。

main.go 文件中出现错误
代码完成和建议
我们正朝着正确的方向前进,但我们看到了错误。不知道大家注意到没有代码补全或者建议。
那是编辑器里的超级必备,我们来补充一下。
补充一下,我们需要一个…..鼓点,等着吧,一个插件!
我们将利用 hrsh7th 插件为这个或插件。
他有一整套插件,我会把链接放在这里,但我不会详细介绍它们
- nvim-CMP—neo vim 的完井引擎插件
- CMP-nvim-LSP—neo vim 内置语言服务器客户端的 nvim-cmp 源代码。
- CMP-nvim-LSP-signature-help—nvim-CMP 源,用于显示强调当前参数的函数签名:
- CMP-nvim-Lua—neovim Lua API 的 nvim-cmp 源代码。
- vim-vsnip —创建和使用 vs 代码片段的能力。
- cmp-path —文件系统路径的 nvim-cmp 源文件。
- cmp-buffer —缓冲字的 nvim-cmp 源。
我们可以将所有这些插件添加到lua/plugins.lua文件中。
-- Hrsh7th Code Completion Suite
use 'hrsh7th/nvim-cmp'
use 'hrsh7th/cmp-nvim-lsp'
use 'hrsh7th/cmp-nvim-lua'
use 'hrsh7th/cmp-nvim-lsp-signature-help'
use 'hrsh7th/cmp-vsnip'
use 'hrsh7th/cmp-path'
use 'hrsh7th/cmp-buffer'
use 'hrsh7th/vim-vsnip'
然后通过运行:w命令保存文件,用:luafile %重新加载 lua 文件,然后用:PackerSync同步软件包,这将删除任何未使用的插件,并安装新的插件。
一旦完成,我们需要在lua/code-completion.lua中编写一个新的配置文件,我们用它来管理插件,并配置它们按照我们想要的方式运行。
我们希望出现一个带有代码建议的小弹出对话框,这可以使用 NeoVim 中可用的许多配置之一来完成,所有配置都可以在这里找到。
我们正在寻找的是vim.opt.[completeopt](https://neovim.io/doc/user/options.html#'completeopt'),这是用来使一个菜单出现的建议。所有可用的配置都在文档中,我将只在每个文档上方留下注释来解释它。
之后,我们将需要cmp,它允许我们配置插件。我们将调用 setup 函数在设置时应用一些设置。
该文件在开始时将如下所示
-- completeopt is used to manage code suggestions
-- menuone: show popup even when there is only one suggestion
-- noinsert: Only insert text when selection is confirmed
-- noselect: force us to select one from the suggestions
vim.opt.completeopt = {'menuone', 'noselect', 'noinsert', 'preview'}
-- shortmess is used to avoid excessive messages
vim.opt.shortmess = vim.opt.shortmess + { c = true}
local cmp = require'cmp'
cmp.setup({
-- Configurations will go here
})
我们需要配置的第一件事是源代码,我们在这里做的是将已安装的插件设置为代码建议的源代码。
-- sources are the installed sources that can be used for code suggestions
sources = {
{ name = 'path' },
{ name = 'nvim_lsp', keyword_length = 3 },
{ name = 'nvim_lsp_signature_help'},
{ name = 'nvim_lua', keyword_length = 2},
{ name = 'buffer', keyword_length = 2 },
{ name = 'vsnip', keyword_length = 2 },
}
之后,我们将想要添加映射!
映射非常有趣,这意味着我们可以创建键盘快捷键来执行某些命令。如果代码建议对话框显示选项,我们如何选择我们想要的选项,如何确认,当我们确认时会发生什么?
映射很容易建立,我们做的第一件事是声明激活动作的键。语法是<Key-Key2-Key3>,可能需要一段时间来学习哪些键是可用的,但是文档为我们提供了帮助。
我希望有和我在 VS 代码中使用的相同的映射,但是你可以按照你想要的方式设置它!我将使用缩写为C的 CTRL。如何在按下CTRL+SPACE时触发代码完成对话框的示例如下所示。
['<C-Space>'] = cmp.mapping.complete(),
我们使用的 cmp 包有几个可以调用的可用函数,它们都出现在这里。
我将在每个映射上面添加注释,下面是我的code-completion.lua如何处理映射。
-- completeopt is used to manage code suggestions
-- menuone: show popup even when there is only one suggestion
-- noinsert: Only insert text when selection is confirmed
-- noselect: force us to select one from the suggestions
vim.opt.completeopt = {'menuone', 'noselect', 'noinsert', 'preview'}
-- shortmess is used to avoid excessive messages
vim.opt.shortmess = vim.opt.shortmess + { c = true}
local cmp = require'cmp'
cmp.setup({
mapping = {
-- Shift+TAB to go to the Previous Suggested item
['<S-Tab>'] = cmp.mapping.select_prev_item(),
-- Tab to go to the next suggestion
['<Tab>'] = cmp.mapping.select_next_item(),
-- CTRL+SHIFT+f to scroll backwards in description
['<C-S-f>'] = cmp.mapping.scroll_docs(-4),
-- CTRL+F to scroll forwards in the description
['<C-f>'] = cmp.mapping.scroll_docs(4),
-- CTRL+SPACE to bring up completion at current Cursor location
['<C-Space>'] = cmp.mapping.complete(),
-- CTRL+e to exit suggestion and close it
['<C-e>'] = cmp.mapping.close(),
-- CR (enter or return) to CONFIRM the currently selection suggestion
-- We set the ConfirmBehavior to insert the Selected suggestion
['<CR>'] = cmp.mapping.confirm({
behavior = cmp.ConfirmBehavior.Insert,
select = true,
})
},
-- sources are the installed sources that can be used for code suggestions
sources = {
{ name = 'path' },
{ name = 'nvim_lsp', keyword_length = 3 },
{ name = 'nvim_lsp_signature_help'},
{ name = 'nvim_lua', keyword_length = 2},
{ name = 'buffer', keyword_length = 2 },
{ name = 'vsnip', keyword_length = 2 },
}
})
打开init.lua并将code-completion.lua添加到文件中。
require('plugins')
require('mason-config')
require('gopls')
一旦在配置中,保存它,打开一个新的.go文件,并尝试它!您应该看到代码建议,并且能够使用键映射来导航这些建议。

代码建议现在起作用了
如果您想要启用 vsnip 插件,我们还需要将以下代码添加到code-completion.lua中。
snippet = {
expand = function(args)
vim.fn["vsnip#anonymous"](args.body)
end,
},
我们可以在窗户上加一些边框,让它看起来好一点。
window = {
completion = cmp.config.window.bordered(),
documentation = cmp.config.window.bordered(),
},
如果我们添加它,它将看起来像下面这样。

为建议添加边框
有时编辑格式可能是有意义的,例如,我们可以根据建议的来源添加图标。下面的代码片段根据源的名称添加格式。
-- completeopt is used to manage code suggestions
-- menuone: show popup even when there is only one suggestion
-- noinsert: Only insert text when selection is confirmed
-- noselect: force us to select one from the suggestions
vim.opt.completeopt = {'menuone', 'noselect', 'noinsert', 'preview'}
-- shortmess is used to avoid excessive messages
vim.opt.shortmess = vim.opt.shortmess + { c = true}
local cmp = require'cmp'
cmp.setup({
-- Required by vsnip
snippet = {
expand = function(args)
vim.fn["vsnip#anonymous"](args.body)
end,
},
-- Add Mappings to control the code suggestions
mapping = {
-- Shift+TAB to go to the Previous Suggested item
['<S-Tab>'] = cmp.mapping.select_prev_item(),
-- Tab to go to the next suggestion
['<Tab>'] = cmp.mapping.select_next_item(),
-- CTRL+SHIFT+f to scroll backwards in description
['<C-S-f>'] = cmp.mapping.scroll_docs(-4),
-- CTRL+F to scroll forwards in the description
['<C-f>'] = cmp.mapping.scroll_docs(4),
-- CTRL+SPACE to bring up completion at current Cursor location
['<C-Space>'] = cmp.mapping.complete(),
-- CTRL+e to exit suggestion and close it
['<C-e>'] = cmp.mapping.close(),
-- CR (enter or return) to CONFIRM the currently selection suggestion
-- We set the ConfirmBehavior to insert the Selected suggestion
['<CR>'] = cmp.mapping.confirm({
behavior = cmp.ConfirmBehavior.Insert,
select = true,
})
},
-- sources are the installed sources that can be used for code suggestions
sources = {
{ name = 'path' },
{ name = 'nvim_lsp', keyword_length = 3 },
{ name = 'nvim_lsp_signature_help'},
{ name = 'nvim_lua', keyword_length = 2},
{ name = 'buffer', keyword_length = 2 },
{ name = 'vsnip', keyword_length = 2 },
},
-- Add borders to the windows
window = {
completion = cmp.config.window.bordered(),
documentation = cmp.config.window.bordered(),
},
-- add formating of the different sources
formatting = {
fields = {'menu', 'abbr', 'kind'},
format = function(entry, item)
local menu_icon ={
nvim_lsp = 'λ',
vsnip = '⋗',
buffer = 'b',
path = 'p'
}
item.menu = menu_icon[entry.source.name]
return item
end,
},
})
应用后,你应该会看到小图标或文字取决于来源。

显示信号源的图标
我们现在已经有了代码完成功能,但是我们还可以做得更多!
文件树&简单遍历&第一个自定义键映射
到目前为止,我们一直使用Explore命令来切换文件。如果你想保持理智,我不建议你在开发环境中这么做。
幸运的是,在 NeoVim 中有一个简单的解决方案。有一个非常流行的插件叫做nvim-tree,可以在 GitHub 上找到。
现在安装它应该是轻而易举的。
打开 Packer 配置文件plugins.lua并添加 Nvim 插件。我们将添加nvim-web-devicons使新的文件浏览器更好看一些。
这一次,你会在 Packer 配置中看到一个新的语法,我们不会使用一个字符串来导入插件,而是一个内部有需求的对象。
这意味着我们将需要 nvim-tree 插件,但也需要 devicons。这可以用来确保我们有所有需要的插件,而不是只有一个。
return require('packer').startup(function(use)
-- Configurations will go here
use 'wbthomason/packer.nvim'
use 'williamboman/mason.nvim'
use 'williamboman/mason-lspconfig.nvim'
use 'neovim/nvim-lspconfig'
-- Hrsh7th Code Completion Suite
use 'hrsh7th/nvim-cmp'
use 'hrsh7th/cmp-nvim-lsp'
use 'hrsh7th/cmp-nvim-lua'
use 'hrsh7th/cmp-nvim-lsp-signature-help'
use 'hrsh7th/cmp-vsnip'
use 'hrsh7th/cmp-path'
use 'hrsh7th/cmp-buffer'
use 'hrsh7th/vim-vsnip'
-- File explorer tree
use {
'nvim-tree/nvim-tree.lua',
requires = {
'nvim-tree/nvim-web-devicons', -- optional, for file icons
},
}
end)
注意,为了让 devicons 正常工作,你需要安装书呆子字体,如果你不想要,就跳过这个要求。书呆子字体很容易安装滚动到网页底部,并安装他们的修补软件。
下载一个字体,我用Go-Mono然后把它添加为字体,我推荐用font-manager,这里看怎么用。
一旦完成,保存文件:w,然后重新加载luafile %,然后用PackerSync更新插件。
我们希望在启动时配置 NvimTree,我们将从他们的文档中获取配置,但添加它以便树在启动时打开。
创建一个新的 lua 脚本并适当地命名,我将命名我的file-explorer.lua。
-- disable netrw at the very start of your init.lua (strongly advised)
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1
-- set termguicolors to enable highlight groups
vim.opt.termguicolors = true
-- empty setup using defaults
require("nvim-tree").setup({
open_on_setup = true,
ignore_buffer_on_setup = true,
})
让我们要求init.lua里面的脚本
require('plugins')
require('file-explorer')
require('mason-config')
require('gopls')
require('code-completion')
重新启动 NeoVim 将会显示一个新的漂亮的文件树,这样我们就可以导航了。

NvimTree 现在显示文件和目录
使用鼠标或它附带的许多默认映射,在树中导航很容易。我推荐这本指南,它很适合学习。
您可以使用快捷键CTRL+w进入导航模式,该模式允许您在打开的面板之间跳转,按左键或右键向该方向跳转。您也可以使用CTRL+w+w快速跳到下一个面板。
您可能需要了解一些不错的命令。一旦你进入 NvimTree,按a将允许你创建一个文件,d将删除一个文件。
按下q将关闭该树,要打开它,我们可以运行长而不必要的命令:NvimTreeToggle。
我喜欢在按下ctrl+n时打开树,所以我将添加映射。
使用 NvimTree,我将转到lua目录并创建一个custom-keys.lua文件,我将在其中存储我的个人键映射。我可以通过按下a来创建文件。
-- fetch keymap
local map = vim.api.nvim_set_keymap
-- map the key n to run the command :NvimTreeToggle
map('n', 'n', [[:NvimTreeToggle<CR>]], {})
在脚本中,我们使用 nvim api 的一部分vim.api.nvim_set_keymap来创建自定义键映射。查看他们的文档了解所有可用的设置和模式。
第一个参数指定启用键映射的模式,我希望它处于正常模式。您也可以在其他模式下使用它,例如插入模式。
然后我们设置按 n 将触发:NvimTreeToggle命令,我们添加<CR>来自动触发命令,我们不想每次都按 enter。
然后,当然,我们需要更新init.lua来包含custom-keys文件。
require('plugins')
require('file-explorer')
require('mason-config')
require('gopls')
require('code-completion')
require('custom-keys')
重新打开 NeoVim 并按下n将会打开 NvimTree,你应该把它设置成你觉得舒服的键。
使用 DAP 和 DapUI 调试
我们现在有了一个文件树,我们有了代码完成,还有一个语言服务器,它会告诉我们什么时候出错了。
开发时的一件重要事情是测试和调试代码,所以我们需要添加这一点。
我们可以再次使用 Mason 插件,我们想要安装一个 DAP,因为我关注 Go,所以我想要 Delve 。
运行命令
:MasonInstall delve
之后,我们需要安装一个名为nvim-dap的插件,用于管理多种语言的 DAP。
把它添加到plugins.lua,保存文件并重新加载它,你记得怎么对吗?然后执行PackerSync。
return require('packer').startup(function(use)
-- Configurations will go here
use 'wbthomason/packer.nvim'
use 'williamboman/mason.nvim'
use 'williamboman/mason-lspconfig.nvim'
use 'neovim/nvim-lspconfig'
-- Hrsh7th Code Completion Suite
use 'hrsh7th/nvim-cmp'
use 'hrsh7th/cmp-nvim-lsp'
use 'hrsh7th/cmp-nvim-lua'
use 'hrsh7th/cmp-nvim-lsp-signature-help'
use 'hrsh7th/cmp-vsnip'
use 'hrsh7th/cmp-path'
use 'hrsh7th/cmp-buffer'
use 'hrsh7th/vim-vsnip'
-- File explorer tree
use 'nvim-tree/nvim-web-devicons' -- optional, for file icons
use 'nvim-tree/nvim-tree.lua'
-- DAP for debugging
use 'mfussenegger/nvim-dap'
end)
根据您想要调试的语言,我们需要对其进行配置。您可以在文档中找到不同语言的完整示例列表。
我们从要求 dap 开始,然后您可以使用dap.adapters后跟适配器名称来定位 DAP。在我的例子中,我的目标是delve,它将是dap.adapters.delve。应该为每个适配器检查要设置的配置,我将简单地让 delve 运行当前打开的文件。
我将创建一个新的配置脚本,惊喜!
这一个将被命名为debugging.lua。
-- fetch the dap plugin
local dap = require('dap')
-- Add adapter to delve
dap.adapters.delve = {
type = 'server',
port = '${port}',
executable = {
command = 'dlv',
args = {'dap', '-l', '127.0.0.1:${port}'},
}
}
-- https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_dap.md
dap.configurations.go = {
{
type = "delve",
name = "Debug",
request = "launch",
program = "${file}"
},
{
type = "delve",
name = "Debug test", -- configuration for debugging test files
request = "launch",
mode = "test",
program = "${file}"
},
-- works with go.mod packages and sub packages
{
type = "delve",
name = "Debug test (go.mod)",
request = "launch",
mode = "test",
program = "./${relativeFileDirname}"
}
}
和往常一样,更新init.lua以包含脚本
require('plugins')
require('file-explorer')
require('mason-config')
require('gopls')
require('code-completion')
require('custom-keys')
require('debugging')
nvim-dap 不附带任何预设映射。没有映射,使用它是一种痛苦。
要设置断点,我们需要执行命令:lua require'dap'.toggle_breakpoint()。
或者开始调试当前文件,我们需要:lua require'dap'.continue()。
你可能会理解,这有点过了。让我们修复键映射,因为我们已经知道如何做了!
我将首先添加两个映射,第一个是当我按 F5 时启动调试器,第二个是使用CTRL+b切换断点。
在custom-keys.lua内添加以下内容
-- nvim-dap keymappings
-- Press f5 to debug
map('n', '<F5>', [[:lua require'dap'.continue()<CR>]], {})
-- Press CTRL + b to toggle regular breakpoint
map('n', '<C-b>', [[:lua require'dap'.toggle_breakpoint()<CR>]], {})
重新启动 NeoVim 并在文件中按 CTRL+B 将会设置断点,如左边的 B 所示。

b 表示断点
按 F5 应该会调出配置选择,并显示我们创建的所有不同配置。你可以添加许多不同的配置,比如一个launch.json。如果你有一个来自 VS 代码的。
如果选择 1,调试器将运行,并在断点处停止。用箭头表示。

箭头显示我们正在调试当前行
现在你可能已经明白,我们可以配置图标和更多。查看插件文档
然后我们可以打开一个 REPL 窗口来使用 delve 查找值等。
运行以下命令浏览 REPL
:lua require'dap'.repl.open()
您应该会看到一个新窗口出现

REPL 窗口与我们的调试器
使用CTRL+w+arrowdown导航到它。然后你需要通过按下i激活插入模式,并尝试执行一个命令。我将使用.scopes来查看局部范围变量。

打印局部范围变量
你可以用:q关闭调试窗口。
现在我们知道了如何使用调试器,让我们添加一些更方便的映射。我已经为每个命令添加了解释,你可以根据自己的需要更改按钮。
-- nvim-dap keymappings
-- Press f5 to debug
map('n', '<F5>', [[:lua require'dap'.continue()<CR>]], {})
-- Press CTRL + b to toggle regular breakpoint
map('n', '<C-b>', [[:lua require'dap'.toggle_breakpoint()<CR>]], {})
-- Press CTRL + c to toggle Breakpoint with Condition
map('n', '<C-c>', [[:lua require'dap'.set_breakpoint(vim.fn.input('Breakpoint Condition: '))<CR>]], {})
-- Press CTRL + l to toggle Logpoint
map('n', '<C-l>', [[:lua require'dap'.set_breakpoint(nil, nil, vim.fn.input('Log Point Msg: '))<CR>]], {})
-- Pressing F10 to step over
map('n', '<F10>', [[:lua require'dap'.step_over()<CR>]], {})
-- Pressing F11 to step into
map('n', '<F11>', [[:lua require'dap'.step_into()<CR>]], {})
-- Pressing F12 to step out
map('n', '<F12>', [[:lua require'dap'.step_out()<CR>]], {})
-- Press F6 to open REPL
map('n', '<F6>', [[:lua require'dap'.repl.open()<CR>]], {})
-- Press dl to run last ran configuration (if you used f5 before it will re run it etc)
map('n', 'dl', [[:lua require'dap'.run_last()<CR>]], {})
现在,运行调试器是惊人的,但有时有一个 UI 来帮助我们也不错。
我推荐 nvim-dap-ui ,它让一切看起来更干净一点。
到目前为止,添加插件应该是轻而易举的事情。更新plugins.lua。
return require('packer').startup(function(use)
-- Configurations will go here
use 'wbthomason/packer.nvim'
use 'williamboman/mason.nvim'
use 'williamboman/mason-lspconfig.nvim'
use 'neovim/nvim-lspconfig'
-- Hrsh7th Code Completion Suite
use 'hrsh7th/nvim-cmp'
use 'hrsh7th/cmp-nvim-lsp'
use 'hrsh7th/cmp-nvim-lua'
use 'hrsh7th/cmp-nvim-lsp-signature-help'
use 'hrsh7th/cmp-vsnip'
use 'hrsh7th/cmp-path'
use 'hrsh7th/cmp-buffer'
use 'hrsh7th/vim-vsnip'
-- File explorer tree
use 'nvim-tree/nvim-web-devicons' -- optional, for file icons
use 'nvim-tree/nvim-tree.lua'
-- DAP for debugging
use 'mfussenegger/nvim-dap'
-- UI for DAP
use { "rcarriga/nvim-dap-ui", requires = {"mfussenegger/nvim-dap"} }
end)
保存、重新加载(luafile %)和打包同步。
安装后,我们还需要设置它。由于这是调试的一部分,我将把它存储在我们配置 dap 的debugging.lua中。为 dapui 添加一个需求,执行 setup()函数。
-- fetch the dap plugin
local dap = require('dap')
-- Setup DapUI
local dapui = require('dapui')
-- set it up see more configs in their repo
dapui.setup()
和大多数插件一样,这增加了一些命令,我们将更新custom-keys.lua来触发 DapUi。
DapUI 有一个切换功能来打开它,我们想移除 Nvim 树来让它看起来更好一些。
这是 NeoVim 开始变酷的时候,看看我们如何轻松地将这些功能链接在一起。无限可能。
-- Press Ctrl+d to toggle debug mode, will remove NvimTree also
map('n', '<C-d>', [[:NvimTreeToggle<CR> :lua require'dapui'.toggle()<CR>]], {})
如果您执行我们之前使用的相同的main.go,设置一个断点并启动调试器(F5)。一旦停止,按d调出 DapUI,显示线程、变量值、REPL 和播放/重复/停止按钮。

DAPUi 让调试变得更好。
按CTRL+d恢复 nvimtree 并移除调试栏。
使用事件监听器使 NeoVim 具有反应性
这很好,我们可以调试我们写的代码。
当你穿越 NeoVim 无底洞去做你想做的事情时,有一件重要的事情需要知道,那就是如何使用事件。NeoVim 触发事件,插件也可以触发事件。
就像在 JavaScript 等中一样,我们可以监听这些事件并应用命令来触发它们。
了解这一点的一个简单方法是每当我们触发一个调试会话时就让 DapUI 出现。
Nvim DAP 将触发两个事件,before和after,可以在它们的文档中找到。
在debugging.lua内部,我将添加一个事件监听器,并在 DAP 初始化后触发dapui.open。一旦 DAP 被终止或退出,我们将使用dapui.close关闭。
-- fetch the dap plugin
local dap = require('dap')
-- Setup DapUI
local dapui = require('dapui')
-- set it up see more configs in their repo
dapui.setup()
-- dap fires events, we can listen on them to open UI on certain events
dap.listeners.after.event_initialized["dapui_config"] = function()
dapui.open()
end
dap.listeners.before.event_terminated["dapui_config"] = function()
dapui.close()
end
dap.listeners.before.event_exited["dapui_config"] = function()
dapui.close()
end
重启main.go中的调试器,看看它现在是如何自动打开和关闭的。
使用侦听器,您可以添加格式等任何您想要的东西。
样式和添加开发工具
老实说,我们目前所做的非常强大和灵活,但是看起来像垃圾。
为了让 NeoVim 看起来更好一点,我们可以开始修改配色方案。改变颜色模式将应用不同的颜色,使事情看起来更好。
一个真正常见的主题是吸血鬼主题。它之所以流行,大概是因为它看起来很棒,支持很多流行的插件。
它安装起来超级简单,和所有其他插件一样。
强大的三步火箭,更新插件,添加配置文件,在 init.lua 中添加 require。
添加到plugins.lua
-- Dracula theme for styling
use 'Mofiqul/dracula.nvim'
保存文件:w,重新加载文件:luafile %,执行:PackerSync
在 NvimTree 中按下a创建一个名为styling.lua的新文件。
在 styling.lua 内部,应用 dracula 配色方案。
vim.cmd[[colorscheme dracula]]
保存文件并更新init.lua以包含样式
require('plugins')
require('file-explorer')
require('mason-config')
require('gopls')
require('code-completion')
require('custom-keys')
require('debugging')
require('styling')
重新加载 NeoVim,它应该看起来更酷!

德古拉主题已应用
为了使它更加强大,让我们添加 nvim-treesitter 。Treesitter 是在代码中提供更好的突出显示的一种方式。
至此,您已经非常熟悉如何安装插件了,所以我不会解释每个步骤,只展示文件。您应该能够自己配置它。
return require('packer').startup(function(use)
-- Configurations will go here
use 'wbthomason/packer.nvim'
use 'williamboman/mason.nvim'
use 'williamboman/mason-lspconfig.nvim'
use 'neovim/nvim-lspconfig'
-- Hrsh7th Code Completion Suite
use 'hrsh7th/nvim-cmp'
use 'hrsh7th/cmp-nvim-lsp'
use 'hrsh7th/cmp-nvim-lua'
use 'hrsh7th/cmp-nvim-lsp-signature-help'
use 'hrsh7th/cmp-vsnip'
use 'hrsh7th/cmp-path'
use 'hrsh7th/cmp-buffer'
use 'hrsh7th/vim-vsnip'
-- File explorer tree
use 'nvim-tree/nvim-web-devicons' -- optional, for file icons
use 'nvim-tree/nvim-tree.lua'
-- DAP for debugging
use 'mfussenegger/nvim-dap'
-- UI for DAP
use { "rcarriga/nvim-dap-ui", requires = {"mfussenegger/nvim-dap"} }
-- Dracula theme for styling
use 'Mofiqul/dracula.nvim'
-- Treesitter
use {
-- recommended packer way of installing it is to run this function, copied from documentation
'nvim-treesitter/nvim-treesitter',
run = function()
local ts_update = require('nvim-treesitter.install').update({ with_sync = true })
ts_update()
end,
}
end)
然后是一个syntax-highlight.lua文件来处理配置
require'nvim-treesitter.configs'.setup {
-- A list of parser names, or "all"
ensure_installed = { "go", "lua", "rust" },
-- Install parsers synchronously (only applied to `ensure_installed`)
sync_install = false,
-- Automatically install missing parsers when entering buffer
-- Recommendation: set to false if you don't have `tree-sitter` CLI installed locally
auto_install = true,
highlight = {
-- `false` will disable the whole extension
enable = true,
},
}
和init.lua
require('plugins')
require('file-explorer')
require('mason-config')
require('gopls')
require('code-completion')
require('custom-keys')
require('debugging')
require('styling')
require('syntax-highlight')
重启后,代码应该会突出显示函数名。

树保姆突出显示
我们要的下一个插件是模糊搜索文件,它叫做望远镜。
要获得完整的功能,您需要安装 RipGrep
让我们把它加到 Packer 上。
-- Telescope used to fuzzy search files
use {
'nvim-telescope/telescope.nvim', tag = '0.1.0',
requires = { {'nvim-lua/plenary.nvim'} }
}
然后添加一个file-finder.lua并填写配置,在本例中,一个键映射显示不同的搜索栏。
-- search files, even hidden ones
vim.keymap.set('n', '<leader>ff', ':lua require"telescope.builtin".find_files({no_ignore=true, hidden=true})<CR>', {})
-- ripgrep files, respects gitignore
vim.keymap.set('n', '<leader>fg', ':lua require"telescope.builtin".live_grep()<CR>', {})
请注意,Live Grep 尊重任何.gitignore文件,并从其中排除结果。除非配置为包含它们。
然后也把它加到init.lua
require('plugins')
require('file-explorer')
require('mason-config')
require('gopls')
require('code-completion')
require('custom-keys')
require('debugging')
require('styling')
require('file-finder')
require('syntax-highlight')
现在你可能注意到映射使用了Leader,默认情况下是反斜杠键。这在我的笔记本电脑上真的很乱,所以我将通过添加下面一行来将其重新映射到custom-keys.lua内的,中。
-- update leader key to ,
vim.g.mapleader = ","
现在,重新启动 NeoVim,享受难以置信的模糊搜索

按下
我特别喜欢使用<Leader>fg的 live-grep 搜索,它也搜索文件中的关键字。
最后要补充的是,我喜欢有一个状态栏来显示一些信息,比如 git 分支等等。
我们可以通过添加nvim-lualine 插件来添加这个。
-- Lualine information / Status bar
use {
'nvim-lualine/lualine.nvim',
requires = { 'kyazdani42/nvim-web-devicons', opt = true }
}
安装后,按照你认为合适的方式配置它,我将在一个新文件statusbar.lua中使用默认值。
require('lualine').setup()
然后也更新init.lua以包含状态栏。
require('plugins')
require('file-explorer')
require('mason-config')
require('gopls')
require('code-completion')
require('custom-keys')
require('debugging')
require('styling')
require('file-finder')
require('syntax-highlight')
require('statusbar')
重启 Neovim,你应该会看到一个漂亮的小状态栏。

Lualine 状态栏—我不在 Git Repo 中,所以它显示一个错误的图标
推荐的插件
这里有一个我推荐的插件列表,可以给你一些安装时的训练。
自动配对 —创建括号和圆括号对时生成它们。
Bufferline —添加一个显示打开的缓冲区(文件)的标题
LSPSaga —代码动作、浮动悬停文档等。
IndentBlankline —压痕指南
FloatTerm —开放浮动终端,适用于开放 LazyGit 等。
nvim-lint — Linter!非常需要。
故障 —思考 VS 代码按钮错误标签
我喜欢写 TODO 注释作为提醒,这个插件使它们可以用 Telescope 搜索到,并且突出显示它们。
标签栏 —获取文件中结构、功能等的概述。
结论
我们已经学会了如何使用 NeoVim,当然还有更多要学,但我认为如果你开始每天使用它,那会很自然。
到目前为止,我们已经涵盖了最需要的基础知识,以及开始有效使用 NeoVim 所需的知识。学完这个之后,你应该可以毫无问题地使用 NeoVim。
您应该熟悉以下内容
- 使用正常模式和插入模式导航 NeoVim
- 在 Vim 中执行命令
- 添加插件和配置 NeoVim
- 处理插件及其配置
现在,我推荐查找 Linters 和 Formatters(提示:mason 可以帮助你)。
这对你来说是一个很好的练习,你可以自己安装一个插件来真正学习并坚持下去。
请记住,通常有一些插件可以帮助您定制完整的开发环境,例如 x-ray/go。它们的缺点是,它们通常包含太多,以至于很难真正了解默认映射以及如何使用它们。
你可以在 Github 上找到我的 dotfiles 和当前的 NeoVim 配置。
我希望你喜欢这篇文章,现在去为自己打造一个梦想的编辑器吧。






浙公网安备 33010602011771号