TowardsDataScience-博客中文翻译-2022-五十三-
TowardsDataScience 博客中文翻译 2022(五十三)
理解扩散概率模型
原文:https://towardsdatascience.com/understanding-diffusion-probabilistic-models-dpms-1940329d6048
逐步建立导致 DPMs 的推理。

致谢:劳拉·安哥特和菲利普·罗卡(经许可使用)
这篇文章是与巴蒂斯特·罗卡共同撰写的。
所表达的观点和意见仅属于作者个人,不代表其雇主的观点或意见。除非另有说明,所有图片均由作者提供。
介绍
对复杂概率分布建模是机器学习中的一个中心问题。如果这个问题可以出现在不同的形状下,一个最常见的设定是如下:给定一个只能由一些可用样本描述的复杂概率分布,如何生成一个新的样本?
所有的生成模型都依赖于相同的基本思想,即把简单分布转换成我们感兴趣的复杂分布。换句话说,假设一个随机变量 X 遵循一个简单的分布(例如高斯分布),我们在寻找一个函数 G,使得 G(X)遵循我们的目标分布。当然,函数 G 很复杂,无法显式给出。一种可能的方法是用将从可用数据中学习的神经网络来模拟 G。然后,我们讨论深度学习生成模型,如 VAEs、GANs 或 DPMs,它们都依赖于不同的机制来定义和训练这个模拟 g 的生成网络。

生成模型旨在学习一个从简单分布中获取数据并将其转换为复杂分布中的数据的函数。
早在 2013 年,金玛和韦林就推出了可变自动编码器(VAEs)。简而言之,VAEs 的思想是用正则化的潜在空间训练自动编码器。然后,正则化编码器被迫向接近高斯的分布编码数据,而解码器从潜在空间重构数据。这样,我们最终得到一个解码器,它可以作为我们的函数 G,从高斯分布中获取数据,并从原始分布中生成一个新数据。更多细节可以在我们之前关于 VAEs 的文章中找到。
一年后的 2014 年,伊恩·古德菲勒(Ian Goodfellow)引入了生成对抗网络(GANs)。简而言之,其思想是假设如果生成网络 G 从目标分布中产生样本,那么这些样本应该与真正可用的样本不可区分。因此可以推导出 GANs 所依赖的对抗性训练的概念。生成网络 G 被训练成接受随机输入(例如,来自高斯分布)并输出来自目标分布的数据。一个鉴别网络 D 被训练来区分真实数据和生成的数据。两个模型同时训练,在竞争中变得更好(G 试图愚弄 D,D 试图不被 G 愚弄)。在这个训练过程中,G 必须总是产生更有说服力的数据来愚弄 D,换句话说,按照预期,这些数据遵循目标分布。更多细节可以在我们之前关于 GANs 的文章中找到。
如果说深度学习生成模型已经受到很多关注很多年了,那么随着一些能够产生出色结果的图像或视频生成模型的出现,它们在最近几年(甚至几个月)受到了更多的关注。在解决视频生成问题之前,大型技术公司一个接一个地首先发布了图像生成模型(来自开放人工智能的 DALL-E 2 ,来自谷歌的 Imagen ,来自 Meta 的 Make-A-Scene )。尽管存在差异,这些宣布该领域光明前景的伟大模型都依赖于相同的基本概念:扩散概率模型(DPMs)。

Make-A-Scene、Imagen、DALL-E 2 是能够从文本提示生成高质量图像的扩散概率模型。上面的例子是由 Meta Make-A-Scene model 生成的,它从文本提示和基本草图中生成图像,以实现更高水平的创造性控制。
扩散概率模型最早出现在 2015 年,Sohl-Dickstein 的一篇论文中,并由于使用它们取得的巨大成果而获得了越来越多的关注。基本上,DPMs 的思想是学习一个明确定义的随机过程的逆过程,该过程逐渐破坏信息,从我们复杂的目标分布中提取数据,并将它们转化为简单的高斯分布。然后,这种反向过程被期望采取相反方向的路径,将高斯噪声作为输入,并从感兴趣的分布生成数据。
事不宜迟,让我们一起(重新)发现 DPMs 吧!
概述
在本文中,我们将集中讨论深入理解 DPM 是由什么组成的所需的主要思想。在第一部分,我们将回顾一些与随机过程相关的概念,并介绍什么是扩散过程。然后,是第二部分,我们将给出理解扩散概率模型的主要直觉。特别是,我们将看到扩散过程如何被用来逐步破坏信息,以及我们如何能够学会逆转这一过程,从噪音中逐步产生信息。在第三部分,我们将把我们的直觉形式化,并给出扩散模型的数学描述。最后,在最后一节中,我们将在这个理论框架的基础上,看看扩散模型在实践中是如何训练的。特别是,我们将结束循环,看看训练和采样的实际实现如何很好地表达我们给出的第一个直觉。请注意,尽管我们将讨论他们依赖的许多基本概念,但我们不会进入前面提到的特定模型的细节(Make-A-Scene、Imagen、DALL-E 2 等。)这将是未来帖子的主题。
注意:这篇文章的某些部分比其他部分更注重数学。如果所有的部分不是完全相互独立的,我们尽可能让读者跳过一些部分。介绍扩散过程的部分和给出扩散模型的数学定义的部分可以跳过,因为其他两个部分包含与扩散模型相关的大多数直觉和实际考虑。
什么是扩散过程?
顾名思义,扩散概率模型在某种程度上与扩散过程相关。让我们从头开始,看看什么是扩散过程,它有什么性质。当然,由于这不是这篇文章的核心主题,我们将不得不做一些简化,以保持这篇文章足够短,同时仍然给出基本的想法。
扩散过程是具有连续样本路径的连续时间马尔可夫随机过程。让我们打破这个相当尴尬的定义,以更好地了解我们在谈论什么。随机过程是随机变量的集合,通常用正整数来表示

我们接着谈离散时间随机过程,或由正实

然后我们讨论连续时间随机过程。类似于一个简单的随机变量被称为样本的实现,我们称随机过程的实现为样本路径或轨迹。对于一个连续时间随机过程,我们说它有连续的样本路径,如果所有可以随机观察到的可能轨迹都是连续的。

不同类型的随机过程,在时间和空间上离散或连续。
最后,马尔可夫过程是一个没有记忆的随机过程。这意味着根据现在和过去的过程的未来行为只取决于现在。过去的信息可以被丢弃,因为它没有提供任何额外的信息。换句话说,过程的未来状态不取决于我们如何到达现在的状态,而仅仅取决于现在的状态。数学上,它可以写成

任何扩散过程都可以用随机微分方程(SDE)来描述,它可以写成以下形式

其中 a 称为漂移系数,σ称为扩散系数,W 为维纳过程。维纳过程更广为人知的名称是布朗运动。粗略地说,总和的右边部分,与维纳过程相关,是使这个微分方程“随机”的原因。如果没有这第二项,我们将处理一个简单的微分方程。维纳过程通过独立的高斯增量带来一些(连续的)随机性

如果我们把微分方程离散化一点,让事情更容易理解,我们得到

那也可以写成

然后我们可以看到,X_{t+dt}的值是 X_t 加上确定性漂移项和由方差与扩散系数平方成比例的正态随机变量定义的随机扩散项的值。因此,扩散系数表示要应用的随机性的强度。

不同随机微分方程和相关(非随机)微分方程的扩散过程样本。我们可以看到,漂移项给出了一个趋势,而扩散项带来了一些随机性。扩散系数越高,随机性越大。
最后,让我们以扩散过程的一个有趣的性质作为结论,这个性质在本文后面会很有用。如果 X_t 是一个扩散过程

那么逆时过程 X̄_t = X_{T-t}也是具有相同函数形式的扩散过程,由下面的随机微分方程描述

其中 p(X_t)定义了 X_t 的边际概率

被称为得分函数。
扩散模型的直觉
通过一些精心选择的系数,扩散过程具有逐渐破坏任何相关信息的效果。事实上,具有“收缩”漂移系数(|a|<1)和非零扩散系数的扩散会逐渐将来自任何复杂分布的数据转变为来自简单各向同性高斯噪声(协方差矩阵可对角化的高斯,意味着所有维度都是独立的高斯)的数据。让我们首先用一个简单的 1D 例子来说明这一点。

扩散过程具有逐步破坏信息的效果。在这个 1D 的例子中,任何初始分布都将被转换成高斯分布(渐近地)。
在 1D 的例子中,我们将“离散扩散过程”定义为

我们说,对于任何 X_0,这个过程最终将趋向于高斯分布。让我们给出一个非正式的例子来证明这个事实,它也将证明我们系数中的平方根是正确的。经过一定数量的步骤 T 后,我们可以写

其中所有高斯分布都是独立的,这意味着第二项可以表示为单个高斯分布,其方差是所有方差的和。对于足够大的步数,我们有

我们看到,第一项变得可以忽略不计,方差之和趋向于 1,这意味着在我们的 1D 例子中,对于任何起始点,我们都趋向于标准高斯分布。
当然,同样的想法可以应用于更高维度。例如,大小为 100100 的图像的分布是非常复杂的高维分布,其可以逐渐转变为相同维度的非常简单的分布:100100 各向同性噪声。在这种情况下,扩散过程将以类似于我们在 1D 例子中描述的方式进行,但是它将针对每个像素的每个 RGB 通道进行。

扩散过程可以被定义为将像图像这样的高维数据变成各向同性的高斯噪声。
到目前为止,我们知道我们可以使用扩散从复杂的分布到简单的各向同性高斯噪声。从我们在本文开头讨论的内容中,可以看出我们对相反的过程更感兴趣:从简单的发行版到复杂的发行版。最重要的是,从复杂分布到简单分布并不困难,因为我们可以简单地随机将复杂分布中的任何一点投影到简单分布中,因为我们知道如何从最后一个分布中进行采样。
那么这里使用扩散的真正目的是什么呢?简单的答案是,它为我们提供了一种渐进和结构化的方法,从复杂的分布到各向同性的高斯噪声,这将简化反向过程的学习。让我们对最后一点有个直觉。
正如我们提到的,将任何给定的图像随机投影到各向同性的高斯噪声中是非常容易的。相反,逆转这个过程是非常困难的:看着一些给定的噪声,我们不能从先前的图像中说太多,因为没有结构可以依赖。

一次从纯噪声到高质量样本可能是一项相当复杂的任务。
现在让我们假设这些图像中的每一个都不是直接的,而是通过扩散过程渐进地投影到各向同性高斯噪声中。看最后一步,基本和之前得到的一样是噪音,还是不能多说。然而,看看中间步骤,我们显然有一些线索来指导我们扭转这一进程。

从纯噪声到高质量样本的渐进过程使得我们可以依靠中间步骤来减轻生成任务。
这里重要的一点是:中间步骤是力量扩散模型所依赖的。在这一点上,人们确实可以观察到,尽管扩散过程是渐进进行的,但它最终会从初始复杂分布中提取任何数据点,并将其从简单分布中转化为随机点。所以,最后,既然我们可以一步完成从复杂到简单的发布,为什么还要麻烦中间的步骤呢?与其学习一个多步逆向过程,为什么不学习一个大的逆向过程,它是多步过程的展开版本?难道我们不能认为这是完全一样的吗?

乍一看,人们可能会认为学习 T 个小模型或者一个由这些小模型组成的大模型是非常相似的。
如果有人想坚持这种推理,事实是将过程分解成中间步骤会带来两件事。首先,所有的逆向步骤彼此之间并不完全不同,这意味着在实践中,我们不必为每个逆向步骤学习完全不同的模型,而是学习一个通用模型,该模型只是将该步骤作为调整其行为的参数。它极大地减少了要学习的模型的大小。其次,除了有更多的参数要学习之外,单次展开的版本更难训练,因为它需要从纯噪声到最终的高质量数据,这使得任务和梯度下降学习更加复杂。相反,渐进模型不需要一次处理全部的复杂性,并且可以在添加越来越精细的信息之前,在早期阶段首先引入粗略的信息。中间步骤在某种意义上也可以被看作是一种引导训练过程使之更容易的方法。
我们可以把扩散模型的主要思想总结如下。我们定义了一个渐进地破坏信息的扩散过程,并学习了期望恢复信息的相反过程。这个反向过程也是渐进的,我们可以依靠这个事实来提高学习效率。在实践中,与不能依赖于这些有价值的中间步骤的一次性模型相比,建模和训练确实可以利用要学习的过程的迭代性质。

渐进逆向过程的学习可以利用问题的迭代结构,而直接模型则不能。明显的缺点是采样需要多个步骤,因此时间更长。
退一步说,我们可以观察到,整体思想与 VAEs 所做的并没有那么远:编码器以结构化的方式将初始复杂分布转换为简单分布,以便能够学习采用相反方向的解码器。DPMs 和 VAEs 之间的主要区别如下。首先,编码器是一个多步骤过程,而不是单步编码器。第二,编码器是完全固定的,并且不需要学习,因为要学习的解码器(逆过程)将直接依赖于扩散过程带来的结构,而不是依赖于在训练期间学习的结构。第三,潜在空间具有与输入完全相同的维数,与 VAE 的潜在空间相反,其维数比编码输入低几个数量级。最后,学习解码器是扩散过程的逆过程,因此是多步骤过程,而不是单次传递函数。
DPMs 的明显缺点是采样需要多个步骤,这意味着生成过程将比 gan 或 VAEs 更长,因为这类模型需要一次通过。这一根本差异引发了许多问题。假设参数数量相等,当第一个输出通过多次(因此花费更多时间)时,比较 DPMs 输出与 GANs 和 VAEs 输出是否完全公平?如果 DMPs 的力量来自于其生产过程的进步性,那么尝试提取 DMPs 又有多大意义呢?我们不会在这里回答这些问题,但它们肯定是值得思考的有趣问题。
扩散模型的数学
现在我们已经建立了直觉,让我们给出一个更正式的 DPMs 数学公式。尽管扩散模型的连续和离散时间公式都存在,我们将在本文中集中讨论后者。这意味着我们将假设正向扩散过程和反向扩散过程已经被离散成有限数量 T 个步骤。注意,对于正向和反向过程,用高斯转移概率核而不是扩散过程来讨论马尔可夫链更准确,但思想是相同的。
让我们用 x_0 来表示我们要从中取样的分布的数据,并用 q(x_0)来表示分布本身。我们将具有高斯转移概率(扩散核)的正向过程定义如下

其中,β_t 表示每一步要保留的上一步信息与要添加的新噪声之间的权衡。我们也可以写

在这里我们可以清楚地看到离散的扩散过程。我们可以很容易地用一个简单的递归论点表明,链中的任何一步都可以根据下式从 x_0 直接生成

在哪里

根据马尔可夫性质,我们可以将一个给定的前向轨迹的概率写成

a 我们在前一小节中描述过,我们的目标是学习这个正向扩散过程的逆向过程,或者更确切地说,这个正向马尔可夫链的逆向链。我们之前提到,在漂移和扩散系数的一些假设下(这里满足),扩散过程的逆过程也是相同函数形式的扩散过程(具有相同的扩散系数,但我们稍后将回到这一点)。通过足够小的时间步长(大 T,意味着小β_t ),我们可以对我们正在寻找的反向链进行近似,该反向链也应该是具有高斯转移概率的马尔可夫链。相反的过程

可以近似计算为

其中 _θ和σ_θ是以θ为参数的两个待学习函数。使用马尔可夫特性,给定后向轨迹的概率可以近似为

其中 p(x_T)是不依赖于θ的各向同性高斯分布


数学设置的插图。
既然我们已经定义了我们的正向过程并模拟了它的反向过程,那么大的问题就来了。我们如何学习 _θ和σ_θ参数?要优化的损耗是多少?我们从逆过程中主要期望的是 p_θ(x_0)接近 q(x_0)。换句话说,在反向过程的最终采样步骤之后,生成数据的分布应该与目标分布相同。因此,逆过程的学习在于找到最小化 q(x_0)和 p_θ(x_0)之间的 KL 散度的 _θ和σ_θ,或者等价地最小化 p_θ(x_0)在 q(x_0)下的负对数似然。从数学上讲,我们希望找到最小化的 _θ和σ_θ

在这个阶段,还不清楚中间步骤在哪里起作用,但很快就会起作用。让我们首先稍微修改一下括号中的表达式。

然后,对数函数是凹的,我们从詹森不等式

所以,我们可以定义一个 L 的上限

代替最小化 L,我们可以最小化 L 的上界,这更容易处理。在推导的这一点上,至少从理论的角度来看,我们已经清楚如何通过从正向过程迭代采样并调整参数以最小化上限中的负对数比来学习反向过程的参数。然而,让我们做最后的努力,把推导推得更远一点,以获得这个上界的更方便的形式。

利用我们有的贝叶斯定理

因此

第一项不依赖于 _θ和σ_θ,因此在优化过程中不需要考虑。最后一项非常简单明了,也不难优化。最后,剩下的项是高斯分布之间的 KL 散度。事实上,我们已经看到了这一点

我们可以证明这一点

在哪里

具有封闭形式的 2 个高斯分布之间的 KL 散度,我们将在下一节中看到,该上限定义了对于训练过程要最小化的非常易处理的损失函数。
实践中的扩散模型
到目前为止,我们已经定义了一个正向过程 q,作为我们的(离散的)扩散过程,逐步破坏信息

以及反向过程 p_θ,模拟要学习的反向过程,并假定逐步恢复信息

我们已经看到,训练我们的反向过程在于找到最小化上限的 _θ和σ_θ

在这一点上,值得注意的是,如果我们忽略要学习的反向模型的参数在步骤之间共享,则上限表达式中的不同项是彼此独立的。因此,假设我们的模型有足够的容量,最小化整个上限类似于最小化它的每一项。
现在让我们做一些假设,看看如何在实践中进行培训。首先,为了使事情变得简单,我们可以决定将反向过程的方差设置为一个未经训练的时间相关常数,这样

第一个选项与 q(x_{t-1} | x_t)的方差相匹配,假设时间步长足够小(大 T,意味着小β_t ),因此,逆向过程具有与正向过程相同的“扩散系数”。第二个选项匹配 q 的方差(x_{t-1} | x_t,x_0)。实际上,研究似乎表明这两种选择给出了相似的结果。我们现在假设第二种选择。
它只让我们用一个单一的模型来学习反向过程的意义。提醒一下

使用两个高斯光束的 KL 散度的封闭形式,可以证明

然后,我们可以看到,上限的每个 KL 散度项对应于一个给定的时间步长,其优化简单地包括最小化模型和以 x_0 为条件的反向过程的均值之间的 L2 距离,两者都在所考虑的时间步长进行评估。
如果这个公式已经很方便了,那么通常更倾向于根据噪声重新确定模型的参数。首先,我们注意到

这意味着

因此,我们可以通过定义来重新确定模型的参数

以便

有了这个新的参数化,我们不再学习代表逆过程的均值的模型,而是学习代表已经被添加到 x_0 中以获得 x_t 的噪声的模型。现在,上界的每个 KL 散度项的最小化包括最小化模型和噪声之间的 L2 距离。
到目前为止,我们已经忽略了我们想要最小化的上界中的最后一项

尽管需要注意以适当的方式处理,但根据所选的参数化,该项也可以很好地用 _θ(x_1,1)或ε_θ(x_1,1)来表示。最后,整个表达式相对于模型参数θ是可微的,并且可以通过最小化该上限来完成训练。
然而,如果精确的上限可用于训练,则建议使用以下更简单的变体,并在实践中经常使用

该变型定义了要实现的更简单的损失函数,并且已经被证明有利于样本质量。
作为与初始上限的主要区别,可以观察到期望不再是在整个随机前向轨迹上,而是在定义随机前向轨迹的单个步骤的随机三元组(初始数据、步骤、噪声)上。这种变化可以用上限中各项的独立性来解释(忽略待优化的参数是共享的这一事实)。正如我们前面已经提到的,这种独立性意味着最小化整个上界相当于最小化它的每一项。在迭代训练过程中,与采样完整的前向轨迹并优化所有项相比,采样单个步骤并优化相应项在理论上是相似的,并且在实践中容易得多。这是这种变体如此吸引人的原因之一。
最终,我们只剩下这个非常简洁的损失函数需要优化。根据数据的性质,模拟噪声的神经网络几乎可以采用任何形状。在图像的情况下,许多论文使用以 U-Net 形状组织的卷积和注意块,但是其他架构也可以适用。然后,训练简单地包括对一些三元组(初始数据、步长、噪声)进行迭代采样,并对损失函数应用梯度下降步长。


去噪扩散概率模型训练的训练过程的图示。
在采样时,我们将从从各向同性高斯分布中采样一些噪声开始,然后,我们将使用我们学习的反向过程从目标分布中生成数据。在生成过程的每一步,噪声方面的参数化意味着模型采用噪声数据和当前步骤,并估计噪声分量。然后,可以计算 x_0 的近似值,并用于定义以这个接近的 x_0 为条件的反向过程的平均值。

乍一看,人们可能已经发现了我们的采样过程中的一些令人困惑的行为:在第一步(事实上,以及所有后续步骤),我们估计噪声数据中的噪声,使得计算 x_0 的估计成为可能。那么,为什么要为接下来的所有步骤费心呢?为什么不仅仅保留第一次估计的 x_0 作为我们最终要返回的数据呢?答案是,这个模型并没有被假定为强大到足以一次性消除所有噪声。因此,x_0 的第一次估计并不完美,但它为下一个采样步骤指明了一个粗略的方向,因为我们将根据这一估计从相反的过程中进行采样。
以 x_0 为条件的反向过程的均值公式(t(x_t,x_0))是 x_t 和 x_0 之间的线性组合(见上面的等式),它清楚地表明这样一个事实,即我们的下一个采样步骤 x将以某种方式是当前步骤和估计的 x_0 之间的混合。可以非常粗略地说,我们将在“当前步骤和 x_0 之间的某个位置”对下一步进行采样。考虑到这一点,我们可以设想我们的生成模型如下:首先,我们从随机噪声中采样最终步骤 x_T,然后,我们从当前状态逐步估计 x_0,并基于当前状态生成前一步骤,并将该估计作为某种“目标”。答:我们提到过,在最初的步骤中,x_0 估计值不会很好,因为预计模型不具备这样做的预测能力,但是一次又一次的迭代,这些估计值将指导逆向生成过程,并将变得越来越好,直到我们获得高质量的最终样本。

去噪扩散概率模型的采样过程的图示。
最后,我们可以在我们建立的第一个直觉和我们刚刚给出的更正式的 DPMs 描述之间架起一座桥梁。我们可以“在数学上”看到像 DPMs 这样的渐进生成过程相对于单程生成过程的优势。经过训练的模型不需要一次性处理所有生成。相反,该模型可以首先在早期阶段引入粗略信息,然后在保持去噪的同时逐渐添加越来越多的细节。最重要的是,当对这种反向去噪过程进行建模时,我们利用了这样一个事实,即对具有不同噪声水平的图像进行去噪并不是完全不同的任务,而是依赖于可以通过单个神经网络相互利用和学习的类似机制。
外卖食品
这篇文章的主要观点是:
- 生成过程都旨在实现相同的目标,即定义一个从简单分布中获取数据并将其转换为复杂分布中的数据的函数
- 学习一个函数(由一个神经网络表示)是一项相当困难的任务,这个函数从目标复杂分布中获取一些高斯噪声和输出数据
- 扩散过程或其离散化版本——具有高斯核的马尔可夫链——可用于逐步破坏信息,这意味着它们可用于从复杂的分布(例如有意义的图像)中获取数据,并通过添加噪声将它们从非常简单的分布(例如高斯噪声)逐步转化为数据
- 扩散概率模型的思想是学习扩散的逆过程,期望通过去除噪声,从一些高斯噪声渐进到复杂分布
- 任务的迭代性质使其更简单,因为训练的模型不需要一次处理整个生成,并且可以首先引入粗略信息,然后在保持去噪的同时逐渐添加越来越多的细节
- 要学习的模型依赖于以下事实:对具有不同噪声水平的图像进行去噪是依赖于公共机制的相似任务,该公共机制可以通过单个神经网络的训练来共享
在本文中,我们描述了主要概念,以理解扩散概率模型的基础。当然,仍有许多知识有待探索,从对我们在本帖中描述的基本设置的可能的改进,到去噪扩散隐式模型(DDIM) 的想法,经过了条件和指导的概念(【基于 分类器的和分类器自由的),这些概念是这些天我们在互联网上随处可见的令人惊叹的生成模型的核心。
这个帖子已经很长很密了。因此,在这里讨论与 DPMs 相关的更高级机制的细节将会涉及太多的信息。然而,我们强烈建议感兴趣的读者阅读上面链接的论文,以加深对该领域丰富内容的了解。也许,我们会写另一篇文章来解决我们暂时搁置的这些概念…一步一步来,就像我们的 DPMs 一样。
感谢阅读!
用巴蒂斯特·罗卡写的其他文章:
经验分布:你需要知道的一切
原文:https://towardsdatascience.com/understanding-empirical-distributions-ed131de5f3df
数据的近似分布,狄拉克δ函数等等

图片来源:作者
你可能在许多统计学教科书中观察到这个词,但我是在凯文·帕特里克·墨菲写的《概率机器学习:简介一书中发现的。
这本书对一些在大多数在线博客和视频中被称为“常识”的话题进行了数学处理。由于我的兴趣在于 ML 背后的数学(和统计学),我开始阅读这本书,并很快发现了经验分布,

图片来源:这本书草稿的快照(这本书的草稿可以在https://probml.github.io/pml-book/book1.html公开获得)
乍一看,对于像我这样的统计学初学者来说,我发现这个定义完全不直观。我知道δ函数(狄拉克δ函数),但是从量子物理学的角度来看,用它来描述离散随机变量的 PDF 似乎是不可能的。pdf 被描述为连续的随机变量,但统计学家也发明了离散的 pdf。
这个故事将提供经验分布的数学背景,并不包含太多现实世界的例子。
如果你的情况和我一样,并且你希望探索更多的经验分布,去完成这个故事吧!
1.让我们从字典开始
我养成了每当遇到新概念就在字典里找单词的习惯。对于经验分布,“经验”一词意味着,
实证的定义
源于或基于观察或经验的
从统计学的角度来看,韦氏词典的第一个定义非常有用。在统计学和计算机科学的世界里,我们经常把观察或经验称为“数据”。单词经验性的指的是与观察或数据有关的东西,而不是理论上的构建。
在统计学的实际应用中,我们通常不会拥有无限量的数据,我们所拥有的只是数据的极小一部分,俗称样本,我们需要从中推断出一切。因此,从那一小部分数据中推断出的东西通常会使用“经验的”这个词,这个词正好符合这个意思。
接下来,分布,描述事件发生的概率。我们将感兴趣的量(我们希望分析的量)建模为随机变量,并且我们说随机变量遵循某种特定的概率分布。在离散随机变量的情况下,概率分布由概率质量函数(PMF)来表征。但是在这个故事中,我们感兴趣的是为连续随机变量定义的概率密度或概率密度函数。
考虑果汁行业的质量控制(QC)测试。质量控制团队无法检查每一盒果汁的质量,所以他们会抽取一批样品(10-15 盒果汁)。使用这些样本,他们将尝试测量整个单位或批次的质量(可能是 10,000 盒果汁)。在统计学行话中,取样的整个单位/批次被称为总体。
2.我们的目标
让我们直接进入我们的目标。
我们的目标是从给定的有限数量的样本中逼近 PDF。PDF 是一个连续的东西,我们将从有限数量的样本中进行近似(在某种意义上是离散的)
由此产生的近似 PDF 将表征样本的分布,而不是真实的数据分布,这就是我们将它称为 经验分布 的原因。
为了模拟真实的分布,你需要无限数量的样本
对于这种从“离散”世界到“连续”世界的转换,我们将使用狄拉克δ函数。最后,我们得到的将被称为 广义概率密度函数 。
3.狄拉克δ函数
事情可能会很快结束,所以我希望你能跟上。
考虑一个阶跃函数,其步位于 x = 0 ,比如,


(1)阶跃函数 s 及其绘图。图片来源:作者
如您所见,该函数在 x=0 处不连续,因为两边的极限不匹配。

(2)阶跃函数 s 的右侧和左侧极限。图片来源:作者
为了使函数在 x=0 处连续,为什么不将“步长转换为“斜坡”,即从 -a 开始,到 a 结束,其中 a 是某个实数?


(3)斜坡函数 s_a 及其绘图。该功能在 x=0 处连续。图片来源:作者
如果你不明白我们如何得到(3)中的术语 (1/2a)(x+a) ,试着用坡点形式计算那条斜线的方程。
如果我们像在(2)中那样计算极限,你会发现这个新的斜坡函数在 x=0 处是连续的。由于我们的函数现在已经变得连续,我们可以试着对它进行 w.r.t. x 的微分,


(4)斜坡函数的导数 s_a 及其绘图。图片来源:作者
但是请注意,连续性并不意味着可微性。然而反过来也是正确的。
有趣的部分来了。取一个极限使 a 趋近于 0,我们得到狄拉克δ函数,

(5)

(5)狄拉克δ函数(尖峰函数)及其图。请注意,根据定义,“尖峰”底部的凸起是有意的,根本不应该存在。图片来源:作者
这个函数很奇怪,这就是它不是定义的'函数的原因。它就像一个以 x=0 为中心的无限质量。我们稍后将讨论更多的要点。接下来,我们将从负无穷到正无穷对(4)中获得的函数进行积分。这一步可能看起来很愚蠢,因为我们在(4)中进行了微分,现在我们正在进行积分,

(6)狄拉克δ函数从负无穷到正无穷积分到 1。图片来源:作者
这看起来和我们用概率密度函数做的很相似,

图片来源:作者
由于(6)中积分的结果并不真正取决于,我们可以写成:

图片来源:作者
让我们休息一下,多了解一下狄拉克δ函数。为了在 X 轴上移动这个尖峰,我们可以做一点小小的改变,
**
(7)具有参数 a 的狄拉克δ函数,其决定 X 轴上‘尖峰’的位置。图片来源:作者
通过修改(1)中的初始阶跃函数,可以很容易地导出这个表达式。在量子物理世界中,当我们进行测量时,比如说测量电子的位置时,我们实际上是在压缩电子的波函数。波函数给了我们电子位置的概率密度。但是当我们进行测量时,我们精确地知道它的位置,因此电子位置的分布采用狄拉克δ函数的形式。尖峰在位置 x_a 处,我们在测量时观察到电子,

(8)波函数的坍缩。更多详情,请看这个视频。图片来源:作者
当高斯分布的方差接近零时,狄拉克δ函数也可以被视为一种极限情况。当 μ 是高斯分布的均值时,尖峰出现在 x = μ
**
(9)当方差接近零时高斯分布(均值= 1)的形状。可以清楚地观察到“尖峰”的形成。图片来源:作者
4.通用 PDF(我们的目标)
让我们回到为离散随机变量构造 PDF 的目标上来。让我们考虑一个离散随机变量 X 具有概率质量函数 p( X ) ,

(10)随机变量 X 可以取的值。图片来源:作者
对于任何实现(随机变量 X 可以取的值) x_i ,考虑以下表达式

(11)乘以狄拉克δ函数的 X 的概率质量函数。图片来源:作者
我们可以认为,Delta 函数的 spike 被移至 x = x_i ,但它也按因子 p( X=x_i ) 缩放,该因子返回随机变量 X 取值 x_i 的概率。注意,上述表达式代表一个连续的实体。接下来,我们为所有 x_i 添加所有这样的表达式,

(12)表情 11 总结所有 x_i. 图片来源:作者
这个表达式类似于所有那些成比例的尖峰的相加,
**
(13)变量 X 的 PDF 及其带有所有“尖峰”的图 x 。图片来源:作者
这是我们的广义 PDF,或者说从数据中得到的 PDF。由此产生的概率分布称为经验分布。但是这个东西仍然没有集成到 1 中,1 是所有 pdf 的属性。如果 X 可以等概率取任意值,那么,
**
(14)通过选择适当的比例因子,新的 PDF 整合为一个。图片来源:作者
这就是你如何得到离散随机变量的 PDF。我们有一个对应的 CDF,看起来和我们之前讨论的阶跃函数相似,

(15)变量 X 的对应 CDF。图片来源:作者
经验分布本身并没有实际应用,但它们对于证明与数据分布有关的各种陈述非常有用。
结束了
我希望你喜欢这个故事和证据。我很高兴我让经验分布在我和读者的脑海中清晰可见。请在评论中分享你的想法,祝你有美好的一天!
理解集合方法:10 分钟内随机森林、AdaBoost 和梯度增强
机器学习
一个数据,三个模型,详细的计算变得简单

尼尔斯·维斯在 Unsplash 上的照片
决策树 是一个简单且易于解释的模型。然而,它是不稳定的,因为数据中的微小变化可能导致生成完全不同的树。它倾向于过度拟合训练数据,因此具有较高的方差,并且对看不见的测试数据的概括较差。
这个问题通过在集合中使用决策树得以缓解。与一只蚂蚁做不了多少事情相比,一大群蚂蚁可以创造建筑奇迹。那么,如何集成决策树呢?有两种方法:装袋和增压。
注意:因为我们将在这个故事中创建许多决策树,所以最好刷新一些关于决策树以及如何构建决策树的基本概念。这里有一个故事可能对你有所帮助:
**Table of Contents****·** [**Random Forest**](#e8d7)
∘ [Step 1\. Create a bootstrapped data](#f4b9)
∘ [Step 2\. Build a Decision Tree](#b0e6)
∘ [Step 3\. Repeat](#a2cf)
∘ [Predict, aggregate, and evaluate](#ea95)
**·** [**AdaBoost**](#d98d)
∘ [Step 1\. Initialize sample weight and build a Decision Stump](#ca9f)
∘ [Step 2\. Calculate the amount of say for the Decision Stump](#e7fc)
∘ [Step 3\. Update sample weight](#354b)
∘ [Step 4\. Repeat](#4074)
∘ [Prediction](#965b)
**·** [**Gradient Boosting**](#42e9)
∘ [Step 1\. Initialize a root](#ffdc)
∘ [Step 2\. Calculate the residual to fit a Decision Tree into](#b6f4)
∘ [Step 3\. Update log(odds)](#ed09)
∘ [Step 4\. Repeat](#8939)
∘ [Prediction](#b2db)
**·** [**Conclusion**](#2d8b)
随机森林
随机森林是最流行的装袋方法之一。你问的是什么?是自举+聚合的缩写。bagging 的目标是减少单个估计量的方差,即随机森林情况下单个决策树的方差。
具体来说,让我们在整个故事中使用一个虚拟数据集。假设您有以下逃税数据集。您的任务是根据应纳税收入(以千美元计)、婚姻状况以及是否实施退税等特征来预测一个人是否会遵从纳税(逃避列)。

本文使用的数据集|图片作者作者
随机森林由这三个步骤组成:
第一步。创建引导数据
给定一个具有 m 观测值和 n 特征的原始列车数据,随机抽取 m 观测值并重复。这里的关键词是“有重复”。这意味着某些观测值很可能被多次选取。
下面是从上面的原始训练数据引导的数据的例子。由于随机性,您可能会有不同的引导数据。

作者自举数据|图片
第二步。构建决策树
决策树是这样构建的:
- 使用引导数据,以及
- 考虑每个节点的随机特征子集(考虑的特征数量通常是 n 的平方根)。
第三步。重复
步骤 1 和步骤 2 迭代多次。例如,使用六次迭代的逃税数据集,您将获得以下随机森林。

使用逃税数据集的具有六个决策树的随机森林。注意,决策树 6 仅由一个节点组成,因为自举数据对于所有十个观察值都有逃避=无目标|图片作者作者
请注意,随机森林不是确定性的,因为有随机选择的观察值和要素在起作用。这些随机选择就是为什么随机森林减少了决策树的方差。
预测、汇总和评估
若要使用随机森林进行预测,请使用测试数据遍历每个决策树。对于我们的例子,只有一个测试观察,六个决策树分别对“规避”目标给出预测否、否、否、是、是和否。从这六个预测中进行多数投票,以做出单个随机森林预测:否。
将决策树的预测组合成随机森林的单个预测称为聚合。虽然多数表决用于分类问题,但回归问题的聚合方法使用所有决策树预测的平均值或中值作为单个随机森林预测。
自举数据的创建允许一些训练观察不被决策树的子集看到。这些观察结果被称为随机样本,对于评估随机森林的性能非常有用。要获得随机森林的验证准确性(或您选择的任何指标),请执行以下操作:
- 在没有样本的情况下运行构建的所有决策树,
- 汇总预测,以及
- 将聚合预测与真实标签进行比较。
adaboost 算法
与随机森林不同,AdaBoost(自适应增强)是一种增强集成方法,其中简单的决策树是按顺序构建的。有多简单?这些树只有一根根和两片叶子,简单到它们有自己的名字:决策树桩。随机森林和 AdaBoost 有两个主要区别:
- 随机森林中的决策树对最终预测具有相同的贡献,而 AdaBoost 中的决策树桩具有不同的贡献,即一些树桩比其他树桩更有发言权。
- 随机森林中的决策树是独立构建的,而 AdaBoost 中的每个决策树桩都是通过考虑前一个树桩的错误来做出的。
决策树桩过于简单;它们比随机猜测稍微好一点,并且有很高的偏差。boosting 的目标是减少单个估计器的偏差,即 AdaBoost 情况下的单个决策树桩的偏差。
回想一下逃税数据集。您将使用 AdaBoost 根据三个特征来预测一个人是否会遵从纳税:应纳税收入、婚姻状况和退税。

逃税数据集|图片作者作者
第一步。初始化样本权重并构建决策树桩
请记住,AdaBoost 中的决策树桩是通过考虑前一个树桩的错误而形成的。为此,AdaBoost 为每个训练观测值分配一个样本权重。当前树桩将较大的权重分配给错误分类的观察值,通知下一个树桩更加注意那些错误分类的观察值。
最初,因为还没有做出预测,所以所有的观察都被赋予相同的权重 1/ m 。然后,构建一个决策树桩,就像创建深度为 1 的决策树一样。

初始化样本权重并构建第一个决策残肢|图像由作者
第二步。计算决策树桩的发言权
这个决策残肢产生 3 个错误分类的观察。因为所有的观察值都有相同的样本重量,所以样本重量的总误差为 1/10 + 1/10 + 1/10 = 0.3。
然后可以使用公式计算出的量

为了理解为什么这个公式有意义,让我们画出相对于总误差的量。注意,总误差仅定义在区间[0,1]上。

总误差|图片作者作者
如果总误差接近 0,则 say 的量是一个大的正数,表明树桩对最终预测做出了许多贡献。另一方面,如果总误差接近 1,则 say 的数量是一个很大的负数,这对于残肢产生的错误分类是一个很大的惩罚。如果总误差为 0.5,树桩对最终预测没有任何作用。
第三步。更新样品重量
AdaBoost 将增加错误标记的观察值的权重,并减少正确标记的观察值的权重。新的样本权重影响下一个决策问题:权重较大的观察值更受关注。
要更新样品重量,请使用以下公式
- 对于错误标记的观察值:

- 对于正确标记的观察结果:

在继续下一步之前,您需要使用以下公式对新的样本权重进行归一化,使它们的总和等于 1

下表总结了这些计算。绿色观察值被正确标记,而红色观察值被当前树桩错误标记。

由作者更新第一个决策树桩|图像的样本权重
第四步。重复
使用加权杂质函数,即加权基尼指数,创建新的决策树桩,其中在确定每类观察值的比例时考虑归一化样本权重。或者,创建决策树桩的另一种方法是使用基于标准化样本权重的自举数据。
由于归一化样本权重的总和为 1,因此可以将其视为在自举时选取特定观察值的概率。因此,id 为 5、8 和 10 的观察值更有可能被选中,因此决策树桩更关注它们。
引导数据如下所示。请注意,由于随机性,您可能会获得不同的引导数据。像以前一样建立一个决策树桩。

初始化样本权重并构建第二个决策残肢|图像作者作者
你看到有一个错误分类的观察。所以,说的量是 1.099。更新样本权重,并根据仅自举观测值的权重之和进行归一化。下表总结了这些计算。

由作者更新第二个决策树桩|图像的样本权重
引导观测值返回到训练数据池,新的样本权重如下所示。然后,您可以使用第三个决策树桩的新样本权重来再次引导训练数据,该过程继续进行。

训练数据及其样本权重|图片由作者
预言;预测;预告
假设你迭代六次这些步骤。因此,你有六个决策难题。要进行预测,请使用测试数据遍历每个树桩并对预测的类进行分组。发言权最大的一组代表 AdaBoost 的最终预测。
举例来说,您构建的前两个决策树桩的数量分别为 0.424 和 1.099。两个树桩预测逃避=否因为对于应纳税所得额,77.5 < 80 ≤ 97.5 (80 是测试观察的应纳税所得额)。假设其他四个决策问题预测并有以下的发言权。

决策树桩的预测和他们的发言权|图片作者作者
由于预测逃避的所有树桩的发言权的总和=否是 2.639,大于预测逃避的所有树桩的发言权的总和=是(也就是 1.792),那么最终的 AdaBoost 预测是逃避=否。
梯度推进
由于两者都是增强方法,AdaBoost 和梯度增强具有相似的工作流程。但是有两个主要的区别:
- 梯度增强使用比决策树桩大的树。在这个故事中,我们将树限制为最多有 3 个叶节点,这是一个可以随意更改的超参数。
- 梯度提升不是使用样本权重来指导下一个决策树桩,而是使用决策树产生的残差来指导下一棵树。
回想一下逃税数据集。您将使用梯度推进基于三个特征来预测一个人是否会遵从纳税:应纳税收入、婚姻状况和退税。

逃税数据集|图片作者作者
第一步。初始化根目录
根是深度为零的决策树。它不考虑任何特征来做预测:它使用回避本身来预测回避。它是怎么做到的?使用映射否→ 0 和是→ 1 对规避进行编码。然后,取平均值,就是 0.3。这被称为预测概率(该值总是在 0 和 1 之间),用 p 表示。
由于 0.3 小于阈值 0.5,根将为每次列车观察预测否。换句话说,预测永远是规避的模式。当然,这是一个非常糟糕的初步预测。为了提高性能,您可以使用下面的公式计算下一步要使用的 log(odds)

第二步。计算适合决策树的残差
为了简化表示法,让 Evade 的编码列表示为 y 。残差简单地定义为r=y-p,其中 p 是使用其反函数从最新对数(赔率)计算的预测概率

使用所有训练观察值构建一个决策树来预测残差。请注意,这是一个回归任务。

计算残差并拟合决策树以预测残差|图像作者作者
第三步。更新日志(赔率)
注意,我还添加了一个节点 Id 列,它解释了决策树中预测每个观察的节点的索引。在上表中,每个观察都用颜色进行了编码,以便于看出哪个观察到了哪个节点。
要组合初始根的预测和您刚刚构建的决策树,您需要转换决策树中的预测值,以便它们与初始根中的对数(几率)相匹配。
例如,设 dᵢ 表示节点# i 中的观测 id 集合,即 d₂ = {1,2,4,7}。那么节点#2 的转换是

对节点#3 和节点#4 进行相同的计算,得到所有训练观测值的 γ ,如下表所示。现在,您可以使用公式更新日志(赔率)

其中 α 是梯度推进的学习率,是一个超参数。我们来设置 α = 0.4,那么更新日志(odds)的完整计算见下图。

更新日志(赔率)|图片作者作者
第四步。重复
使用新日志(odds),重复步骤 2 和步骤 3。这是新的残差和用来预测它的决策树。

计算残差并拟合决策树以预测残差|图像作者作者
像以前一样计算 γ 。例如,考虑节点#3,然后

使用 γ ,像以前一样计算新的对数(赔率)。这是完整的计算

更新日志(赔率)|图片作者作者
你可以重复这个过程很多次。然而,对于这个故事,为了避免事情失控,我们现在将停止迭代。
预言;预测;预告
要对测试数据进行预测,首先,遍历每个决策树,直到到达一个叶节点。然后,将初始根的 log(odds)与所有由学习率缩放的对应叶节点的 γ 相加。
在我们的例子中,测试观察落在两个决策树的节点#3 上。因此,总和变成-0.847+0.4×(-1.429+1.096)=-0.980。转换为预测概率

自 0.273 < 0.5, predict 规避=否。
结论

马库斯·斯皮斯克在 Unsplash 上拍摄的照片
您已经非常详细地学习了随机森林、 AdaBoost 和梯度增强,并将它们应用于一个简单的虚拟数据集。现在,您不仅可以使用已建立的库来构建它们,还可以自信地知道它们是如何从内到外工作的,使用它们的最佳实践,以及如何提高它们的性能。
恭喜你。

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

从零开始的机器学习
View list8 stories



高级优化方法
View list7 stories



MLOps 大型项目
View list6 stories



我最好的故事
View list24 stories



艾伯斯·乌兹拉
R 中的数据科学
View list7 stories


用 Neo4j 和 Emblaze 理解图嵌入
原文:https://towardsdatascience.com/understanding-graph-embeddings-with-neo4j-and-emblaze-7e2d6ef56b8c
图嵌入可以将图中丰富的关系和属性网络表示为向量。这些嵌入向量对于比较节点是有用的,并且它们也是机器学习算法的有价值的输入。 Neo4j Graph Data Science 只使用几行 Python 代码就可以从图中导出嵌入。
虽然使用 Neo4j 生成嵌入非常简单,但要判断您的应用程序是否有正确的嵌入并不容易。Neo4j 提供了几种嵌入算法的选择,每种算法都有多个可调超参数。如何理解不同嵌入结果之间的差异,并为您的用例选择最佳嵌入?进入 Emblaze ,这是一个由卡耐基梅隆大学数据交互小组开发的 JupyterLab 小部件,它很好地支持了对数据集的多种嵌入选项的交互探索。

Emblaze 图形嵌入可视化和动画(图片由作者提供)
在本教程中,我们将为涉及机场和航线的数据集生成几种不同的图嵌入。然后,我们将使用 Emblaze 来可视化和比较嵌入结果。要完成这个实践教程,你需要一个免费的 Neo4j 沙盒账户和一个 JupyterLab 环境。您可以使用 pip 来安装 JupyterLab ,或者将其作为 Anaconda 的一部分。具备这些先决条件后,您可以按照下面的视频和教程进行操作。
登录 Neo4j 沙盒并创建一个新项目。选择“图形数据科学”数据集,然后点击“创建”。

选择 Neo4j 沙盒中的图形数据科学项目(图片由作者提供)
Graph Data Science 沙盒附带安装的 Graph Data Science 库,以及预加载的机场和航线数据集。当沙盒启动时,您可以展开关于项目的信息以显示连接细节。记下密码和 Bolt URL,供以后使用。

沙盒中的连接详细信息(图片由作者提供)
在 GitHub 上找到 Airports_Emblaze.ipynb 笔记本。切换到“Raw”视图,然后从浏览器中保存文件。默认情况下,下载的文件将被命名为 Airports_Emblaze.ipynb.txt,但将其重命名以删除“.”。txt "扩展名。启动 JupyterLab,然后打开 Airports_Emblaze.ipynb。
如果您还没有安装 Emblaze 和 GraphDataScience Python 库,您可以使用笔记本前两个单元中的命令来安装它们。
在第四个笔记本单元格中,用 Neo4j 沙箱中的连接信息替换 bolt_url 和密码。
bolt_url = "bolt://44.198.160.170:7687"
user = "neo4j"
password = "specifications-lifeboats-jacket"
gds = GraphDataScience(bolt_url, auth=(user, password))
注意,我们使用的是 GraphDataScience 包,这是用于 Neo4j 的新数据科学 Python 客户端。如果你是一个有经验的 Neo4j 用户,笔记本中的语法看起来和你习惯的有点不同,这就是原因。
data science 沙盒的预加载数据集包含表示机场和地理分区的节点。连接Airport节点的HAS_ROUTE关系包括一个distance属性,该属性告知机场之间的距离。我们希望将这一资产价值转化为一个weight,反映距离较近的机场之间更强的关系。下面的密码应用了一个简单的线性变换,从所有路线的最大距离中减去一条路线的距离,再加上 1。这样我们对weight总是有正面的评价。
gds.run_cypher("""
match (:Airport)-[r:HAS_ROUTE]->(:Airport)
with collect(r) as routes, max(r.distance) as maxDistance
foreach(route in routes | set route.weight = maxDistance + 1 — route.distance)
""")
您可以随意尝试其他变换,这些变换会将距离值较小的路径分配给较高的权重。例如,您可以尝试对距离应用负指数。
接下来,我们将创建一个内存中的图形投影,它包含Airport节点和HAS_ROUTE与它们的weight属性的关系。我们将把HAS_ROUTE关系视为无向关系。
G_routes, result = gds.graph.project(
"air-routes",
"Airport",
{"HAS_ROUTE":
{"orientation":"UNDIRECTED",
"aggregation":"MAX"}
},
relationshipProperties = "weight")
我喜欢运行弱连接成分 (WCC)算法,作为我探索性数据分析过程的一部分。这将在图中查找连接的节点集。我们首先调用算法 stats 模式来快速查看组件大小的分布。
routes_wcc = gds.wcc.stats(G_routes)routes_wcc['componentDistribution']--------------------------------{'p99': 1,
'min': 1,
'max': 3292,
'mean': 16.52358490566038,
'p90': 1,
'p50': 1,
'p999': 3292,
'p95': 1,
'p75': 1}
看起来我们有一个包含 3292 个Airport节点的巨大连接组件,然后是一些包含单个Airport节点的小组件。也许那些小部件代表的是不与商业航空航线相连的通用航空机场。出于演示的目的,我们只对作为巨型组件一部分的机场感兴趣。
我们再次运行 WCC 算法,但这次是在 mutate 模式下更新内存中的图形。
gds.wcc.mutate(G_routes, mutateProperty = 'componentId')
我们还想将 WCC 生成的componentId属性写入磁盘上的持久图,这样我们就可以在 GDS 会话后引用它。我们运行GDS . graph . write node properties()函数来实现这一点。
gds.graph.writeNodeProperties(G_routes, ['componentId'])
我们运行一个 cypher 查询来获得与巨型组件相关联的componentId。
gds.run_cypher("MATCH (a:Airport) RETURN a.componentId as componentId, count(*) as nodeCount ORDER BY count(*) DESC limit 1")componentId nodeCount
---------------------------
0 3292
现在,我们可以投影一个子图,它只包含具有巨型组件的componentId的节点。
G_connected_airports, result = gds.beta.graph.project.subgraph("connected-airports", G_routes, "n.componentId = 0", "*")
我们编写一个函数,为子图中的每个机场节点创建快速随机投影 (FastRP)嵌入。我为这个项目选择了 FastRP 嵌入,因为它运行非常快,即使在沙盒环境中也是如此。FastRP 擅长表示节点周围的局部邻域。嵌入将被添加到内存中的图形投影中。如果我们对其中一个嵌入感到满意,我们可以稍后将嵌入向量的属性写入磁盘图形。
对于算法的每次运行,我们将生成长度为 64 的嵌入向量,并且我们将使用 45 的随机种子。嵌入维度是一个可调参数,但是我们在这里将它保持稳定,以便我们可以检查其他参数的影响。我为这个数据集选择了 64 的嵌入维数,因为它相对较小且简单。您可以随意尝试更长或更短的嵌入尺寸。设置随机种子可以保持算法重复运行时的一致性。随机种子值 45 是一个任意的选择。我们还将传入一个字典,其中包含每个嵌入的特定参数。
def train_fast_rp(graph, config):
result = gds.fastRP.mutate(
graph,
embeddingDimension = 64,
randomSeed = 45,
**config
)
return result
我们将对属性iterationWeights的三个不同值进行实验。该属性包含应用于算法每次迭代的权重列表。随着每一次迭代,FastRP 算法从正在计算嵌入的节点向前移动一步。短列表意味着算法迭代次数少,每个节点的嵌入仅受其最近邻居的影响。更长的列表意味着算法迭代更多次,并且每个节点的嵌入受到更远的邻居的影响。
我们还将比较在关系上使用weight参数的嵌入和那些将图视为未加权的嵌入。总之,这将给我们六个嵌入探索。
configs = [{"iterationWeights": [1.0, 1.0],
"mutateProperty": "shallowUnweighted"},
{"iterationWeights": [0.0, 1.0, 1.0],
"mutateProperty": "mediumUnweighted"},
{"iterationWeights": [1.0, 1.0, 1.0, 1.0],
"mutateProperty": "deepUnweighted"},
{"iterationWeights": [1.0, 1.0],
"relationshipWeightProperty": "weight",
"mutateProperty": "shallowWeighted"},
{"iterationWeights": [0.0, 1.0, 1.0],
"relationshipWeightProperty": "weight",
"mutateProperty": "mediumWeighted"},
{"iterationWeights": [1.0, 1.0, 1.0, 1.0],
"relationshipWeightProperty": "weight",
"mutateProperty": "deepWeighted"}]
现在我们为六种配置中的每一种运行train_fast_rp函数。
embedding_results = [train_fast_rp(G_connected_airports, config) for config in configs]
接下来,我们可以运行一个 Cypher 语句,该语句将从内存图中流式传输嵌入结果。除了嵌入向量之外,我们将返回Airport节点的一些属性,以及与每个Airport相关联的Continent的名称。我们将根据HAS_ROUTES关系的数量对前 900 个Airports进行采样。采样将加速 Emblaze 性能。它还从结果集中排除了一些非常小的机场,使那些不是机场或地理爱好者的人更容易理解结果。
embedding_df = gds.run_cypher("""
call gds.graph.streamNodeProperties("connected-airports",
["shallowUnweighted",
"mediumUnweighted",
"deepUnweighted",
"shallowWeighted",
"mediumWeighted",
"deepWeighted"])
yield nodeId, nodeProperty, propertyValue
WITH gds.util.asNode(nodeId) as a,
MAX(case when nodeProperty = "shallowUnweighted" then
propertyValue end) as shallowUnweighted,
MAX(case when nodeProperty = "mediumUnweighted" then
propertyValue end) as mediumUnweighted,
MAX(case when nodeProperty = "deepUnweighted" then
propertyValue end) as deepUnweighted,
MAX(case when nodeProperty = "shallowWeighted" then
propertyValue end) as shallowWeighted,
MAX(case when nodeProperty = "mediumWeighted" then
propertyValue end) as mediumWeighted,
MAX(case when nodeProperty = "deepWeighted" then
propertyValue end) as deepWeighted
MATCH (a)-[:ON_CONTINENT]->(c:Continent)
RETURN
a.descr as airport_name,
a.iata as airport_code,
c.name as continent,
shallowUnweighted,
mediumUnweighted,
deepUnweighted,
shallowWeighted,
mediumWeighted,
deepWeighted
ORDER BY size([(a)-[:HAS_ROUTE]-() | a]) DESC
LIMIT 900
""")
接下来,我们将编写一个函数,该函数获取一列embedding_df Pandas 数据帧,并将其转换为一个 Emblaze 嵌入。Emblaze 嵌入将包含每个Airport的点数。相关的Continent会给这些点着色。我们将基于余弦相似性计算每个Airport的十个最近邻。最近邻计算类似于 Neo4j 的图形数据科学库中的 k 最近邻算法。最后,我们将使用 Emblaze 的默认 UMAP 降维算法将每个嵌入从 64 维空间投影到 2 维空间。
def create_emblaze_embedding(embedding_df, column):
emb = emblaze.Embedding({
emblaze.Field.POSITION:
np.array(list(embedding_df[column])),
emblaze.Field.COLOR: embedding_df['continent']},
n_neighbors = 10,
label=column,
metric='cosine')
emb.compute_neighbors()
return emb.project()
我们对embedding_df数据帧中的最后六列运行create_emblaze_embedding函数。
emblaze_embeddings = [create_emblaze_embedding(embedding_df, column)
for column in embedding_df.columns[3:]]
接下来,我们将嵌入转换成一个 Emblaze 嵌入集。
variants = emblaze.EmbeddingSet(emblaze_embeddings)
我们创建了文本缩略图,它们将作为工具提示出现在 Emblaze embedding viewer 中。
thumbnails = emblaze.TextThumbnails(embedding_df['airport_name'] +
" (" + embedding_df['airport_code'] + ")")
最后,我们创建了 Emblaze viewer ,并将其显示为一个 JupyterLab 小部件。
w = emblaze.Viewer(embeddings = variants, thumbnails = thumbnails)
w

Emblaze 嵌入浏览器(图片由作者提供)
在小部件的左侧,我看到了我们创建的六个嵌入的缩略图。缩略图旁边的条形用颜色编码,表示哪些嵌入最相似。我看到共享iterationWeights参数值的嵌入对比共享相同relationshipWeight属性的三个嵌入的集合有更多相似的颜色。这表明在确定节点在嵌入空间中的最终位置时,改变iterationWeights属性比改变relationshipWeight属性具有更大的影响。
您可以单击其中一个缩略图生成线条,显示当前显示的嵌入和您单击的缩略图之间相对位置变化最大的点。第二次单击新嵌入可以看到从以前的视图到新嵌入的动画过渡。

放大图片,看看欧洲机场从轻度超重到中度超重变化最大的是什么
您可以在搜索框中输入机场代码。我输入“MCI”,这是我在堪萨斯城当地机场的代码。可视化缩放至所选机场及其在嵌入空间中的最近邻机场。

搜索单个机场(图片由作者提供)
单击不同的嵌入缩略图,当您切换到新的嵌入时,“邻居”面板会显示堪萨斯城机场的哪些机场将被添加或减去为邻居。

比较单个机场不同嵌入中的邻居节点(图片由作者提供)
让我们探索一下中等加权嵌入和中等非加权嵌入之间的变化。单击可视化的空白区域以取消选择所有机场。然后,双击中等权重的缩略图。单击缩略图表示中等体重。单击右侧面板上方的“建议”按钮。Emblaze 生成一组节点,这些节点的邻居发生了有趣的变化。单击“加载选择”获得其中一个建议。我选择了从墨尔本国际机场开始,包括澳大利亚、新西兰和新喀里多尼亚的六个机场。

六个澳大利亚机场从加权到不加权嵌入的变化(图片由作者提供)
在右侧的详细信息面板中,我可以看到从加权嵌入到非加权嵌入的变化导致所有六个机场都失去了 Townsville Airport (TSV)作为嵌入空间中的近邻。在嵌入空间中作为近邻添加的机场,如斐济(NAN)和巴厘岛(DPS)在距离上更远,但它们比汤斯维尔有更多到我们正在检查的机场的两跳连接航班。
使用 Neo4j 和 Emblaze 探索图形嵌入帮助加强了我对不同算法参数如何影响嵌入输出的直觉。我希望您在处理图形数据时会发现这些工具的组合很有用。
举例理解极坐标数据帧中的分组方式
原文:https://towardsdatascience.com/understanding-groupby-in-polars-dataframe-by-examples-1e910e4095b3
了解如何在 Polars 中使用 groupby()方法来聚合数据和应用函数

照片由 Hannah Busing 在 Unsplash 上拍摄
到目前为止,在我关于 Polars 的前几篇文章中,我已经向您介绍了 Polars 数据框架的基础知识,为什么它是比 Pandas 更好的数据框架库,以及它的惰性评估特性如何允许它优化您的查询。在探索 Polars 时,您会遇到一个非常常见的问题——如何在 Polars 数据帧上执行 groupby ?大多数 Pandas 用户都熟悉groupby()函数,您可以根据类别对数据进行分组,然后对类别应用函数。在 Polars 里你是怎么做到的?
在本文中,我将带您了解如何在 Polars 数据帧上使用groupby()功能。特别是,我将用几个不同的例子来说明它们。准备好了吗?我们走吧!
示例 1
对于第一个示例,我将手动创建 Polars 数据帧:
import polars as plscores = {'Zone': ['North', 'North', 'South', 'South',
'East', 'East', 'West', 'West'],
'School': ['Rushmore', 'Rushmore','Bayside','Rydell',
'Shermer','Shermer','Ridgemont','Hogwarts'],
'Name': ['Jonny', 'Mary', 'Joe', 'Jakob',
'Jimmy', 'Erik', 'Lam', 'Yip'],
'Math': [78, 39, 76, 56, 67, 89, 100, 55],
'Science': [70, 45, 68, 90, 45, 66, 89, 32]}df = pl.DataFrame(scores, columns =
['Zone', 'School', 'Name',
'Science', 'Math'])
df
正如您在输出中看到的,dataframe 具有 8 行 5 列的形状:

作者图片
dataframe 包含一组来自不同学校的学生的科学和数学成绩。
按区域分组
现在让我们通过使用groupby()和agg()方法来查看每个区域中的所有学校:
q = (
df
.lazy()
.groupby(by='Zone')
.agg(
'School'
)
)
q.collect()
您使用
lazy()方法来确保 Polars 对后面的所有查询使用惰性评估。
您将看到以下输出:

作者图片
请注意,每个区域的学校现在作为列表存储在School列下。要查看特定区域的所有学校,您可以使用filter()方法:
q = (
df
.lazy()
.groupby(by='Zone')
.agg(
'School'
)
**.filter(
pl.col('Zone')=='East'
)** )
q.collect()

作者图片
查看每个区域的最高分数
如果你想知道每个区域的最高科学分数,使用科学栏上的max()方法:
q = (
df
.lazy()
.groupby(by='Zone')
.agg(
pl.col('Science').**max()**.alias('Science(Max)')
)
)
q.collect()
alias()方法重命名输出列。

作者图片
也可以像这样直接使用科普栏目上的pl.max()方法:
# pl.col('Science').max().alias('Science(Max)') **
pl.max('Science').alias('Science(Max)')**
如果您还想计算每个区域中学校的数量,您可以在agg()方法中添加另一个表达式:
q = (
df
.lazy()
.groupby(by='Zone')
.agg(
[
**pl.col('Science').count().alias('Number of Schools'),**
pl.col('Science').max().alias('Science(Max)')
]
)
)
q.collect()
结果如下:

作者图片
您可以添加不同的表达式来获得聚合组的结果:
q = (
df
.lazy()
.groupby(by='Zone')
.agg(
** [
** pl.col('Science').count().alias('Number of Schools'),pl.col('Science').max().alias('Science(Max)')**,**
** pl.col('Science').min().alias('Science(Min)'),
pl.col('Science').mean().alias('Science(Mean)'),
pl.col('Math').max().alias('Math(Max)'),
pl.col('Math').min().alias('Math(Min)'),
pl.col('Math').mean().alias('Math(Mean)'),
]
** )
)
q.collect()

作者图片
修改排序顺序
由groupby()方法返回的结果的顺序是随机的,所以每次运行上述代码片段时,您可能会看到区域的顺序不同。你可以使用sort()方法给它一个命令:
q = (
df
.lazy()
.groupby(by='Zone')
.agg(
[
pl.max('Science').alias('Science(Max)')
]
)
** .sort(by='Zone')**
)
q.collect()

作者图片
注意到区域是按字母顺序排序的,而不是基于主方向(即北、南、东、西)。那么,如何根据基本方向对区域进行排序呢?以下代码片段显示了如何做到这一点:
**df_sortorder = pl.DataFrame({
'Zone' : ['North','South','East','West'],
'Zone_order' : [0,1,2,3]
}).lazy()**q = (
df
.lazy()
**.join(df_sortorder, on='Zone', how='left')**
**.groupby(by=['Zone','Zone_order'])**
.agg(
[
pl.max('Science').alias('Science(Max)')
]
)
**.sort('Zone_order')
.select(
pl.exclude('Zone_order')
)** )
q.collect()
在上面的代码片段中,我:
- 创建了另一个 dataframe (
df_sortorder)来指定我想要的区域排序顺序 - 用
df_sortorder连接原始数据框架,这样我的数据框架现在有了一个名为Zone_order的新列 - 根据新的
Zone_order列对组进行排序 - 在最终结果中排除了
Zone_order列
结果如下所示:

作者图片
示例 2
下一个例子使用数据集" "车辆制造模型数据 "。从这个页面下载 csv_data.csv 文件。
许可证 —麻省理工学院许可证
描述 —准确的机动车制造&自 2001 年起的车型数据。该数据集包括汽车、摩托车、卡车和 UTV 制造商及其相应的型号。数据是数据库不可知的,并且是用户友好的,因为相同的数据集被移植到 mysql、json 和 csv 格式。Json 和 csv 数据集被扁平化,而 mysql 数据集被规范化为 3 个表。目前有 19,722 种型号,并且还在增加。
首先,将 CSV 文件加载到 Polars 数据框架中:
q = (
pl.scan_csv('csv_data.csv')
)
q.collect()
您应该看到以下内容:

作者图片
数据框包含 2001 年至 2016 年的制造商(制造商)及其型号的列表。
按年份和品牌分组
先将数据框按年份和品牌分组,然后统计每个年份按品牌的型号数量:
q = (
pl.scan_csv('csv_data.csv')
.groupby(by=['year','make'])
.agg(
pl.col(['make']).count().alias('count')
)
.sort(by=['year','make'])
)q.collect()

作者图片
使用pl.count()方法可以简化上述操作:
q = (
pl.scan_csv('csv_data.csv')
.groupby(by=['year','make'])
.agg(
**pl.count()**
)
.sort(by=['year','make'])
)q.collect()
如果您想查看每个汽车制造商的所有型号,您可以指定型号列:
q = (
pl.scan_csv('csv_data.csv')
.groupby(by=['year','make'])
.agg(
**'model'**
)
.sort(by=['year','make'])
)q.collect()
这将把所有的车型名称分组为每个年份和品牌的列表:

作者图片
显示每个汽车品牌的第一个和最后一个型号
如果要显示每辆制造的第一个和最后一个型号,分别使用first()和last()方法:
q = (
pl.scan_csv('csv_data.csv')
.groupby(by=['year','make'])
.agg(
**[**
'model',
**pl.first('model').alias('first'),
pl.last('model').alias('last'),
]** )
.sort(by=['year','make'])
)q.collect()
第一个和最后一个模型现在将显示在各自独立的列中:

作者图片
示例 3
对于这个例子,我们将使用这个数据集。
许可 : CC0:公共领域
描述 —该数据集包含 1338 行被保险人数据,其中根据被保险人的以下属性给出保险费用:年龄、性别、身体质量指数、子女数、吸烟者和地区。属性是数字和分类变量的混合。
首先,将 CSV 文件作为 Polars 数据帧加载:
q = (
pl.scan_csv('insurance.csv')
)
q.collect()
数据框架包含各种客户资料:

作者图片
计算雄性和雌性的数量
要计算每个地区的男性和女性人数,请在汇总时使用sum()方法:
q = (
pl.scan_csv('insurance.csv')
.groupby(by='region')
.agg(
**[
(pl.col('sex') == 'male').sum().alias('male'),
(pl.col('sex') == 'female').sum().alias('female'),
]** )
.sort(by='region')
)
q.collect()
每个地区的男性和女性人数现在将显示在两列中:

作者图片
计算每个性别的平均费用
要计算每个地区每个性别的平均费用,您必须:
- 首先选择
charges列 - 每个性别的过滤器
- 计算平均费用
- 重命名该列
下面是执行上述步骤的代码片段:
q = (
pl.scan_csv('insurance.csv')
.groupby(by='region')
.agg(
[
**(pl.col('charges')
.filter(pl.col('sex')== 'male'))
.mean()
.alias('male_mean_charges'),
(pl.col('charges')
.filter(pl.col('sex')== 'female'))
.mean()
.alias('female_mean_charges'),** ]
)
.sort(by='region')
)
q.collect()
以下是上述代码片段的结果:

作者图片
为了验证上述结果是否正确,我们可以编写以下代码片段来手动检索所有包含来自东北地区的男性的行,然后计算他们的平均费用:
q = (
pl.scan_csv('insurance.csv')
.filter(
(pl.col('region')=='northeast') & (pl.col('sex') == 'male')
)
.select(
pl.col('charges')
)
.mean()
)
q.collect()
以下结果显示了东北地区所有男性的平均费用:

作者图片
计算每个地区吸烟者的比例
要计算每个地区吸烟者的比例,您需要:
- 总结所有吸烟者
- 计算吸烟者列中的总行数
下面是执行上述步骤的代码片段:
q = (
pl.scan_csv('insurance.csv')
.groupby(by='region')
.agg(
[
**((pl.col('smoker')=='yes').sum() /
(pl.col('smoker')).count() * 100).alias('Smoker %')** ]
)
.sort(by='region')
)
q.collect()
下面的结果显示了每个地区吸烟者的百分比:

作者图片
https://weimenglee.medium.com/membership
我将在即将到来的新加坡 ML 会议(2022 年 11 月 22-24 日)上主持一个关于 Polars 的研讨会。如果你想在 Polars 数据框架上快速起步,请在https://ml conference . ai/machine-learning-advanced-development/using-Polars-for-data-analytics-workshop/注册我的研讨会。

摘要
我希望上面的代码示例能为您提供足够的素材来了解groupby()方法的用法。正如您从代码中注意到的那样,groupby()方法通常与agg()方法结合使用。使用 Polars 时需要记住的一点是,应该避免使用apply()函数,因为它会影响查询的性能。
以下是我以前关于 Polars 数据框架的文章,供您参考:
理解单应性(又名透视变换)
原文:https://towardsdatascience.com/understanding-homography-a-k-a-perspective-transformation-cacaed5ca17
从不同的角度看待事物

通过单应变换。图片作者。
一.动机
对于那些熟悉文档扫描仪应用程序的人来说,你可能会注意到,无论你如何拿着手机,这些应用程序产生的结果看起来就像你直接从顶部扫描文档一样(鸟瞰图)。在开发一个类似的应用程序时,我学会了通过一种叫做单应的计算机图形技术(也叫透视变换)来产生相同的特征。因此,在这篇文章中,我想解释这个概念,并分享一个 Python 实现来执行这样的技术。
二。单应性(又名透视变换)
线性代数在计算机图形学和计算机视觉中起着重要的作用。其中之一是通过矩阵乘法变换 2D 图像。这种变换矩阵的一个例子是单应矩阵。它允许我们通过将单应矩阵与一个视图中的点相乘来找到它们在另一个视图中的相应位置,从而从同一场景的一个视图转换到另一个视图(等式 1)。

等式 1:单应变换。作者图片
我们的目标是理解矩阵 H 是如何产生的,以及如何估计矩阵 H 的元素。为此,我们首先需要理解三个基本概念:齐次坐标、射影空间和针丨孔丨摄像机模型。
齐次坐标和射影空间

图 1:投影空间的可视化。图片作者。
试试这个互动网站,对齐次坐标和射影空间如何工作有一个更好的直觉
齐次坐标是射影空间中使用的坐标系统。你可以把射影空间想象成 3D 空间中位于 Z =1 的平面。穿过 3D 空间的原点并与 Z =1 平面相交的直线在射影空间中形成点。此外,在射影空间中,当 3D 空间中的平面穿过原点并与 Z=1 平面相交时,形成线。在计算机图形学和计算机视觉中,与欧几里得空间中的笛卡尔坐标系相比,射影空间中的齐次坐标提供了一些优势。一个优点是,它允许我们将旋转和缩放等图像变换与平移结合为一个矩阵乘法,而不是矩阵乘法和向量加法(Yasen,2019)。这意味着我们可以将复杂的矩阵链接成一个单一的变换矩阵,这有助于计算机执行更少的计算。
针丨孔丨摄像机模型
齐次坐标和射影空间在推导针丨孔丨摄像机模型中起着重要作用。该模型解释了如何将 3D 空间中的场景投影到图像平面(2D 图像)上。下面的等式将 3D 空间中的点与其在图像平面中的对应位置相关联。请注意,生成的 2D 点和源 3D 场景点都在同质坐标中。

等式 2:针丨孔丨摄像机模型。图片作者。
您可以看到该关系由两个转换组成。第一个变换矩阵称为摄像机外部矩阵。它告诉我们相机在 3D 空间中的位置。第二个变换是摄像机固有矩阵,它将图像平面转换成像素。首先,它将图像平面从单位(例如米)缩放到像素。然后,它移动图像平面的原点,以匹配左上角从(0,0)开始的像素坐标。

等式 3:相机矩阵。图片作者。
然后,我们可以通过矩阵乘法将这两种变换组合成摄像机矩阵。为了获得这个矩阵的条目的值,我们执行一个称为相机校准的过程(Bhatt,2021)。我推荐你观看 Peter Corke 的视频,了解更多关于这个话题的信息。
推导单应矩阵
在我们从针丨孔丨摄像机模型中知道什么是摄像机矩阵之后,我们可以导出单应矩阵。我们知道针丨孔丨摄像机模型将 3D 空间中的一个点映射到图像平面上。让我们假设我们的场景是一个平面,位于(X,Y,0)。然后我们可以像下面这样更新相机矩阵。

等式 4:单应矩阵。图片作者。
得到的矩阵是单应矩阵。它告诉我们,同一场景从一个视图到另一个视图的转换实质上是从一个投影平面到另一个投影平面的变换。请注意, H 中没有 h₃₃ 值,因为我们可以假设它为 1。
计算单应矩阵的元素
类似于相机矩阵,我们可以通过执行校准过程来找到单应矩阵的值。我们可以通过下面的等式使用彼此相关的对应点来做到这一点。

等式 5:两对匹配点之间的关系。图片作者。
然后,我们可以将等式 5–3 分布如下

等式 6:通过单应性的两点之间的关系。图片作者。
上面的关系是一对匹配点之间的关系。由于单应矩阵具有 8 个自由度,我们需要至少四对对应点来求解单应矩阵的值。然后,我们可以像下面这样组合所有四个点之间的关系。

等式 7:n 对匹配点之间的关系。图片作者。
这种形式允许我们通过最小二乘法求解 h 向量。
三。代码
在了解了单应性之后,我们可以编写 Python 代码来实现我在帖子开头提到的鸟瞰图功能。我们本质上是在寻找变换矩形文档的角并将其映射到新平面的单应矩阵,其中这些角映射到新图像平面的角。
您可以在这里找到包含样本图像的 GitHub repo。
四。结论
尽管我们只研究了如何实现文档扫描仪应用程序的鸟瞰效果,但单应还有更多用例。它还可以为 OCR 和自动驾驶汽车创建全景图、3D 重建和预处理图像。我鼓励你更深入地研究这个概念,这样你就能找到新的方法用它来创造伟大的事物!
喜欢这篇文章,想表示你的支持?关注我或者给我买咖啡
参考
Bhatt 博士(2021 年 11 月 12 日)。计算机视觉中的摄像机标定综合指南。分析 Vidhya。检索于 2022 年 1 月 30 日,来自https://www . analyticsvidhya . com/blog/2021/10/a-comprehensive-guide-for-camera-calibration-in-computer-vision/
讲解齐次坐标&射影几何。汤姆·达林。(1970 年 2 月 24 日)。检索于 2022 年 1 月 30 日,来自https://www . tomdalling . com/blog/modern-OpenGL/explaining-homogeneous-coordinates-and-projective-geometry/
如何从对应点计算单应矩阵 H(2D-2D 平面单应)。数学栈交换。(1961 年 9 月 1 日)。检索于 2022 年 1 月 30 日,来自https://math . stack exchange . com/questions/494238/how-to-compute-homo-matrix-h-from-communication-points-2d-2d-planar-homo g
图像单应性。(未注明)。2022 年 1 月 30 日检索,来自http://16385 . courses . cs . CMU . edu/spring 2021 content/lectures/08 _ homographics/08 _ homographics _ slides . pdf
刘,女。(2021 年 8 月 4 日)。理解计算机视觉中的转换。中等。2022 年 1 月 30 日检索,来自https://towardsdatascience . com/understanding-transformations-in-computer-vision-b 001 f 49 a9 e 61
针丨孔丨摄像头型号。HediVision。(未注明)。2022 年 1 月 30 日检索,来自 https://hedivision.github.io/Pinhole.html
塞尔日·贝隆吉。(未注明)。 Cse 252B:计算机视觉 Ii 。读书。检索自https://CSE web . ucsd . edu/classes/sp04/CSE 252 b/notes/le c04/le C4 . pdf
y . Shankar(2022 年 1 月 13 日)。估计单应矩阵。中等。检索于 2022 年 1 月 30 日,来自https://towards data science . com/estimating-a-homo graphy-matrix-522 c 70 EC 4 B2C
透视变换、单应矩阵、本质矩阵和基本矩阵之间有什么区别?(未注明)。检索于 2022 年 1 月 30 日,来自https://vivekseth.com/computer-vision-matrix-differences/
亚森·胡。(2019 年 6 月 10 日)。齐次坐标。亚森·胡。检索于 2022 年 1 月 30 日,来自https://yasenh.github.io/post/homogeneous-coordinates/
YouTube。(2018).计算机视觉中的单应解释。检索于 2022 年 1 月 30 日,来自https://www.youtube.com/watch?v=MlaIWymLCD8.
YouTube。(2019).第 12 类——投影变换。 YouTube 。于 2022 年 1 月 30 日从https://www.youtube.com/watch?v=54Qtu3S9HJU.检索
YouTube。(2021).针丨孔丨和透视投影|成像。 YouTube 。于 2022 年 1 月 30 日从https://www.youtube.com/watch?v=_EhY31MSbNM.检索
DALL-E Mini 的工作原理
原文:https://towardsdatascience.com/understanding-how-dall-e-mini-works-114048912b3b
使用 DALLE-E 模型从文本生成图像,从文本提示,通过 VQGAN、BART 和 CLIP 到最终图像。
你一定听说过 DALL E mini,最热门的图像生成模型。但是你想知道它是如何工作的吗?在这篇文章中,我们将解密最近最著名的人工智能模型之一。为了更深入地理解所有组件,我还参考了其他资源。

在 Unsplash 上由 russn_fckr 拍摄的照片
DALL E mini 是一款能够在给出简短文本提示的情况下生成图像的模型。它在互联网上引起了很多关注,主要是因为非常搞笑的结果和易于使用的演示。如果你还没有做过,我鼓励你在这里玩 DALL E mini:【https://www.craiyon.com/
DALL E mini 是 OpenAI 的一个更大模型的开源版本,名为 DALLE [1]。该模型由几个已知的构件组成,以一种非常巧妙的方式与一些需要解决的有趣的工程问题联系在一起。如果你对 DALL E mini 的起源更感兴趣,可以参考[2]。那些块是 VQGAN 、变压器、 BART 和夹子。在本文中,我们自下而上,首先解释各个组件以及它们是如何连接的。
在我们开始之前,先简要说明一下:为了训练 DALL E,使用了三个带有图像标题的数据集。你可以在[2]阅读更多关于他们的内容。
VQGAN
第一个重要的构建模块是 VQGAN 生成图像模型[3]。生成意味着,模型应该生成新的图像。传统上,卷积网络是计算机视觉任务的首选方式。然而,CNN 有一个重要的缺陷:它们强烈偏向于局部交互(在窗口内)。另一方面,当建模长期关系很重要时,transformer 模型显示了它们在 NLP 领域中的性能。一个典型的 transformer 模型构建为在一个序列中支持多达 512 个令牌,因为它们的计算需求随着输入大小的平方增长。这对于高质量的图像合成来说肯定太小了——像素的数量要大几个数量级。
VQGAN 试图结合 CNN 和 transformer 方法的优点进行图像合成。主要的想法是让 CNN 了解当地的互动,并把长期关系委托给 transformer。这样,我们可以利用两种架构的优势。让我们更深入地了解他们是如何做到的。
电报密码本
VQGAN 的一个重要概念是码本。可以理解为有向量的表格。每个矢量被认为是编码一个“感知丰富的图像成分”。编码器试图将输入图像表示为来自码本的代码序列。稍后,解码器使用该序列(和码本)来恢复原始图像。码本的大小和维数是固定的,并且模型学习实际的码向量。以这种方式选择参数,迫使码本学习有意义的表示。

码本(用 Z 表示)由 N 个条目组成,每个条目是 n_z 维向量。条目的数量及其大小是固定的。在这个例子中,它是作者的 Nx10 图像
编码器
过程如下:原始图像( HxWx3 —高乘宽乘 RGB)被转换成来自码本的索引序列。这是由 CNN 编码器完成的。使用 n_z 个通道将图像尺寸缩小到高 x 宽。注意,n_z 是码本的维数。因此,来自编码器的每个输出“像素”具有与来自码本的向量相同的维度。

点击放大!作者图片
在图中,我用红色标出了右下部分。那些红色值将被转换成来自码本的单个值。你可以看到,在这个例子中,CNN 后的原始图像被转换为 3x3x10 的地图。3x3 是任意大小,10 来自码本条目大小(它们需要匹配)
等等,但是 CNN 的值与码本中的向量不匹配。没错。因此,该模型使用 L2 范数从码本中寻找最接近的向量。
当我们从码本中获得索引时,我们可以将它们展平为大小为 h x w 的序列 S。这样,我们就创建了一个整数序列的图像表示!由于图像被转换为 3x3x10,现在我们在序列 s 中有 9 个项目。
解码器
解码器做相反的事情。根据整数序列(码本索引),它重新创建图像。由于整个模型是按照 GAN 风格建立的,所以在最后还有一个鉴别器。然而,在本文中,我假设您或多或少知道 GAN 是什么,因为这里没有空间解释它。

作者图片
解码器 CNN 的输入与编码器 CNN 的输出“几乎”相同。差不多,因为同时被编码成了码字。并且在这个过程中,我们丢失了一些信息(因为我们只依赖码本表示)。维度分析很简单,如果我们有 9 个元素序列,并且我们知道图像是一个正方形,我们将它排列为 3x3。而解码器 CNN 知道要扩展多少。
变压器
你可能会想,把图像编码成一个序列有什么意义,因为我们会在它之后马上回到 CNN 公式?答案是,我们还将转换器模型应用于顺序表示。回想一下,CNN 在本地互动中表现出色,但在远程互动中表现出色。相比之下,转换器对长距离依赖性执行 grat,但是对每个像素运行太昂贵。
顺序表示比原始像素更简洁。由于它是一个整数序列,我们可以很容易地将这个问题转化为语言建模,这正是转换器的优势所在。
因此,我们将中间的序列表示用于 transformer 模型,并在语言建模任务中训练它。如果我们将图像的顺序表示表示为 S = S0,S1,S2,..Sn(记住 S 的长度是固定的)我们可以训练转换器预测这个序列中的下一个值。

假设我们知道 S0 到 S5 码字,那么 S6 应该是什么?作者图片
通过这种方式,转换器了解到图像的分布片段彼此之间的相关程度。使用这种方法还有一个额外的好处。假设我们希望对图像生成进行调节(调节—提供“启动”信息,如“给我一张狗的图像”)。有了一个转换器,这是非常容易的,我们只需要预先考虑图像序列的条件序列。稍后我们将看到这一点。
还有一个话题——码本中的值究竟是如何更新的。但是,对于本文来说,这是一个太高级的主题。如果你对此感兴趣,请看原文[3]。
好了,我们已经完成了 DALL E mini 的第一大块。让我们继续巴特
巴特
与 VQGAN 相比,这部分相对简单。Bart 是一种基于(令人惊讶的)变压器架构的序列到序列自动编码器。目标是重建被噪声破坏的输入文本。让我们看一个例子。

作者图片
你可以看到这个模型修复了噪音输入。而只是 BART 预训练的辅助任务。对于我们的例子,我们更感兴趣的是将输入文本翻译成与码本相同的词汇。它将允许我们将它们两者结合起来!你可以认为 BART 的作用是把字幕“翻译”到码本上。
达勒和米尼
现在我们已经准备好了建造 DALL E mini 的最重要的部分。让我们从培训开始。程序如下:
- 图像由 VQGAN 编码器编码。
- 字幕由 BART 编码器编码。
- 两个编码器的输出被组合并传递给 BART 解码器。
- VQGAN 编码器输出和 BART 编码器输出用于计算交叉熵损失。
主要思想是 VQGAN 编码器和 BART 解码器应该为图像和字幕对产生完全相同的序列。

DALL-E mini 的主要目标是匹配 VQGAN 和 BART 的输出。图片由作者提供
据我所知,VQGAN 在 DALL E mini 之前是单独训练的。BART 编码器也经过预训练,但解码器是从头开始训练的。
这看起来出奇的简单!
模型推理
现在我们来看一下 DALL E mini 的推论。这一次,我们只有标题,我们应该产生一个图像。第一步是向 BART 编码器提供描述。接下来,我们对 BART 解码器进行多次采样,以生成候选。每个候选图像被传递给 VQGAN 解码器,并生成候选图像。
我们可以停在这里—生成了多个图像,我们可以将它们呈现给用户。然而,我们可以做得更好。有一种方法可以自动对候选人进行排名,并选择其中的前 k 名。这个模型叫做剪辑。让我们看看它是如何工作的。
夹子
CLIP 是一个模型,能够嵌入文本和图像,并判断它们的匹配程度。为了再次训练剪辑模型,我们需要一组(图像、文本)对。图像和文本由专用编码器编码到同一个向量空间中。对于每一对,计算它们之间的点积。目标是使匹配对的文本和图像表示接近,而非匹配对的文本和图像表示远离。使用对称交叉熵训练该模型,这意味着该模型同时以两种方式学习该关系(文本→图像和图像→文本)。

CLIP 将所有图像与所有标题进行比较,并确保它们匹配。作者图片
在模型被训练之后,我们可以为它提供一个单独的标题(源标题)和一堆候选图片。在输出端,我们将得到每幅图像的分数,显示标题和图像的对齐程度。假设我们只取具有最大相似性的 K 个图像候选。
回到 DALL E mini 推论
现在我们已经准备好看到完整的 DALL E mini 推理管道

所有部件都在工作。作者图片
提供的提示被翻译成 VQGAN 码本中的码字序列。我们对 BART 进行多次采样,以获得一个以上的可能序列。这样我们就产生了多个候选人。每个候选者被单独放入 VQGAN 解码器。结果,我们有一堆生成的图像。生成的图像连同提示文本一起被输入到 CLIP 中,以便对它们进行排序,从而允许我们选择最终返回的前 K 个图像。
瞧,我们完成了!
问题和考虑
注意这部分大部分是我的私人意见
问:我们能解释一下密码本吗?
答:虽然在技术上我们可以将码本索引解码为 BART 词汇,但在我的实验中,我发现它们不提供任何类型的模型解释
问:训练这样的模型
有什么困难?答:其中最重要的一个是 argmin 操作不是反向传播友好的
考虑因素 1:
DALL-E Mini 没有引入任何新的、卓越的架构,而是很好地利用了现有的部件。
考虑 2:
我喜欢 DALL E mini 比 OpenAI 的 DALL-E 小得多的事实。作者声称使用 spot TPUs 只需 200 美元就可以训练。对我来说,同样重要的是小型车产生的二氧化碳要少得多。
参考资料:
[1]—DALL-E by open ai—https://openai.com/dall-e-2/
[2] — DALL-E mini 讲解
[3] — VQGAN 纸— 用于高分辨率图像合成的驯服变压器
[4] —回形针— 从自然语言监督中学习可转移的视觉模型
[5]-剪辑 Github 库
[6] — 与 CLIP Colab 笔记本互动
[7] — DALL E mini —推理流水线 Colab 笔记本
[8] — BART 论文— BART:用于自然语言生成、翻译和理解的去噪序列间预训练
[9] — 驯服变形金刚 Colab 笔记本
图像来源:
https://un flash . com/photos/izoque 5 vh47 o
https://un flash . com/photos/h-acurbngw【https://un flash . com/photos/krttcxujni
理解如何为计算机视觉和深度学习问题处理图像数据

图片由 Bud Helisson 从 Unsplash
简介:
在过去几年从事多个计算机视觉和深度学习项目后,我在这篇博客中收集了我关于如何处理图像数据的想法。几乎总是预处理数据比直接将其输入深度学习模型更好。有时,甚至可能不需要深度学习模型,经过一些处理后,简单的分类器可能就足够了。
最大化图像中的信号和最小化图像中的噪声使得手头的问题更容易处理。应用过滤器来增强特征,并使图像对光照变化、颜色等更加鲁棒。在构建计算机视觉系统时应该加以考虑。
考虑到这一点,让我们探索一些可以帮助解决经典计算机视觉或基于图像的深度学习问题的方法。这个博客附带的笔记本可以在这个仓库中找到。
1。先简单后深入:
在应用最新和最好的深度学习来解决问题之前,请尝试经典的计算机视觉技术。尤其是在数据稀缺的情况下,就像现实世界中的许多问题一样。
检查计算图像像素的统计值(如平均值、峰度、标准偏差)是否会得出不同类别的不同统计值。一个简单的分类器,如 SVM,KNN,然后可以根据这些值进行训练,以在不同的类之间进行分类。
2.增加图像中的信号并去除噪声:
在将预处理技术输入深度学习模型之前,检查预处理技术是否增强了图像的主要特征并提高了信噪比。这将有助于模型获得更高的精度。
- 使用阈值处理等技术、腐蚀和膨胀等去噪技术、高斯模糊(平滑边缘)和中值模糊(去除椒盐噪声)等模糊技术
- 对于不同的问题,不同的运算符可能以不同的顺序使用。
- 通常的做法是,在第一次应用某个操作符后的几个步骤中,不止一次地使用该操作符,如果这样可以增强更多的功能的话。
以帮助为滤波器、阈值等找到不同内核大小的最佳组合。(这两者的组合能跑到百万!)将产生最佳图像,构建交互式滑块来帮助找到这些值的理想范围。如何做到这一点的例子已经在下面的第 3 点和这个笔记本中给出
之后,尝试第 1 点中描述的方法,看看是否能为你手头的任务提供足够的信息。
3.直方图均衡
增强图像特征的另一种方法是使用直方图均衡化。直方图均衡化提高了图像的对比度。直方图均衡化的目的是最频繁的像素值被均匀地展开和分布。
让我们看看下面的例子。

乔·德克尔在摄影展上拍摄的图片
可以看到,上面的图像对比度非常低。在这种情况下,重要的是提高对比度,使得图像的特征更加清晰可见。OpenCV 提供了两种实现方法——直方图均衡化和对比度受限的自适应直方图均衡化(CLAHE)。
应用直方图均衡化,图像的对比度确实会提高。然而,它也增加了图像中的噪声,因为它可能在下面的中间图像中。
这就是 CLAHE 的用武之地。使用这种方法,图像被分成 m×n 个网格,然后在每个网格上应用直方图均衡化。使用如下所示的交互式滑块,可以找到理想的裁剪限制(对比度阈值)和图块网格大小。

交互式滑块,以找到最佳的剪辑和拼贴值。作者图片

从左至右:原始图像,直方图均衡化图像,图像后分类。作者图片
右边的图像现在具有增强的对比度,与原始图像相比,背景和前景树更加明显。直方图均衡和 CLAHE 的完整笔记本可从这里获得
4.将图像转换到不同的颜色空间:
将图像转换到不同的颜色空间,如 HSV,通常可以提供更好的信息来分割对象,例如对象跟踪。通常,RGB 颜色空间对阴影、光照的轻微变化(这会影响对象的颜色)并不鲁棒。对于使用经典计算机视觉的对象跟踪等任务,由于上面列出的原因,RGB 空间中的精细调整的蒙版经常会在稍有不同的环境中使用时失败。此外,一旦图像转换到不同的空间,如 HSV,分离通道通常有助于分割感兴趣的区域和消除噪声。正如下面可以看到的,一旦图像被转换到 HSV 空间并且通道被分割,阴影可以更容易地被去除并且网球被分割。关于转换到 HSV 空间和分离通道的笔记本可以在这里找到。

不同的色彩空间(RGB,HSV)和它们的分量分裂。图片作者。
5.标准化图像:
如果图像被输入到深度学习模型中,则必须使用批处理标准化等技术对图像进行标准化,这将有助于对网络的输入进行标准化。这样有助于网络学习更快,更稳定。批量标准化有时也会减少泛化错误。
6.进行合理的扩充:
扩充图像时,请确保所应用的扩充技术保留了图像的类别,并且与现实世界中遇到的数据相似。例如,对狗的图像应用裁剪增强可能导致增强的图像不像狗。在某些对象中使用旋转和翻转进行扩充的情况下也是如此。在增强时改变图像属性(如颜色)时要非常小心。此外,确保增加数据不会改变图像的标签。
始终检查增强图像是否有意义,是否反映了真实世界。

随机裁剪等增强操作如何导致数据损坏的示例。左图由奥斯卡·萨顿从 Unsplash 拍摄
7.训练集和验证集之间的数据泄漏:
这一个更适合一般的深度学习——但确保相同的图像(假设原始图像和增强图像)不在训练和验证集中是重要的。这种情况通常发生在列车验证分割之前执行扩充时。忽略这一点可能会导致模型度量给出其真实性质的错误表示,因为它将在训练期间从也存在于验证集中的非常相似的图像中学习。
8.测试/验证集上所有类别的代表:
确保测试和验证集包含所有标签的示例。这将产生反映模型真实本质的模型度量
以其中一个标签的示例数量明显较少为例。执行随机训练-测试分割可能导致具有较少标签的类根本不出现在验证/测试集中。唉,当训练好的模型被测试时,它将不会在那个特定的类上被测试,并且模型度量将不会反映其性能的真实本质。在本笔记本中可以找到如何做到这一点的示例。
9.后处理健全性检查:
模型定型后,执行一些健全性检查也很重要:
- 确保在多类分类器的情况下所有类的输出相加为 1。
- 确保在测试或部署模型时,也应用了在训练期间应用于图像的预处理。
我希望这篇博客文章给了你一些关于如何处理经典计算机视觉或深度学习问题的图像数据的见解。如果您在处理图像数据时有任何问题或方法,请告诉我。请随时在 Twitter 和 LinkedIn 上与我联系。如果你不想错过我以后的博客,你可以在这里订阅,让它们直接发送到你的收件箱!感谢您的参与,祝您度过美好的一天!😃
了解 Kolmogorov-Smirnov (KS)测试对分析数据的数据漂移
数据漂移符合数据剖析

作者图片
TLDR: 我们进行了统计测试,特别是 Kolmogorov-Smirnov (KS)测试,应用于完整数据集和数据集概要,并比较了结果。这些结果允许我们讨论 KS 漂移检测的数据分析的局限性以及 KS 算法在不同情况下的优缺点。我们还提供了代码供您自己重现实验。
数据漂移是 ML 应用中众所周知的问题。如果不解决这个问题,它会大大降低你的模型,使你的模型完全不可用。解决这些问题的第一步是能够检测和监控数据漂移。
有多种方法可以监控生产中的数据漂移。通常使用统计测试来获得漂移检测值,并随着时间的推移对其进行监控。传统的漂移检测算法通常需要完整的原始数据来计算这些值,但对于大规模系统,由于存储或隐私问题,完全访问历史数据可能是不可行的。一种可能的替代方法是预先对数据进行采样,这也有缺点:通过聚合,您可能会丢失重要信息,如罕见事件和异常值,从而损害结果的可靠性。
第三种方法是在应用漂移检测算法之前分析数据。配置文件捕获数据的关键统计属性,例如分布度量、频繁项、缺失值等等。然后,我们可以使用这些统计特性来应用漂移检测技术的修改版本。当然,由于没有免费的午餐,这种策略也有其不利之处。profile 是对原始数据的估计,因此,使用它进行漂移检测将会生成实际漂移检测值的近似值,如果使用完整的数据,您将会得到该近似值。
但是,剖析过程如何与漂移检测算法一起工作,我们这样做会损失多少呢?
在这篇博文中,我们将把自己限制在数值单变量分布,并选择一个特定的算法来进行实验:Kolmogorov-Smirnov (KS)测试。我们还将深入了解 KS 测试本身对于不同场景的适用性。
以下是我们将在这篇博文中涉及的内容:
- 什么是 KS 测试?
- 什么是数据分析?
- 试验设计
- 实验
-实验#1 —数据量
-实验#2 —桶数
-实验#3 —轮廓尺寸
你可以检查这篇博文中使用的代码,甚至可以通过访问实验的 Google Colab 笔记本自己运行实验。
科尔莫戈罗夫-斯米尔诺夫试验
KS 测试是两个一维概率分布相等的测试。它可用于将一个样本与参考概率分布进行比较,或者比较两个样本。现在,我们对后者感兴趣。在比较两个样本时,我们试图回答以下问题:
"这两组样本来自同一概率分布的概率是多少?"
KS 检验是非参数的,这意味着我们不需要依赖数据来自给定分布族的假设。这很好,因为我们通常不知道现实世界中的底层分布。
统计数据
KS 统计可以表示为:
D = supₓ|F₁(x) — F₂(x)|
其中 F1 和 F2 分别是第一和第二样本的两个累积分布函数。
换句话说,KS 统计量是两个累积分布之间的最大绝对差。
下图显示了统计数据的一个示例,用黑色箭头表示。

双样本 KS 统计量。来源:维基百科[1]
零假设
本实验中使用的零假设是两个样本来自同一分布。例如,如果定义我们的统计模型的所有假设都为真(包括我们的测试假设),小的 p 值将表明数据不太可能。换句话说,我们可以将 p 值解释为数据和定义我们的统计模型的基础假设之间的兼容性的度量,0 代表完全不兼容,1 代表完全兼容*[2]。
为了计算这个值,KS 统计量和两种分布的样本量都被考虑在内。拒绝零假设的典型阈值是 1%和 5%,这意味着任何小于或等于这些值的 p 值都会导致零假设被拒绝。
()作者的勘误表——零假设部分的原文是:“例如,p 值 0.05 意味着两个样本有 5%的概率来自同一分布。“这是一种误解,并不代表 P 值的正确定义,如论文统计测试、P 值、置信区间和功效:误解指南*【2】所述。
数据剖析
分析数据集意味着收集数据的统计测量值。这使我们能够以可伸缩、轻量级和灵活的方式生成数据的统计指纹或摘要。罕见的事件和离群值相关的指标可以被准确地捕获。
为了分析我们的数据,我们将使用开源数据日志库 whylogs。使用 whylogs 进行分析是以流的方式完成的,需要一次通过数据,并允许并行化。配置文件也是可合并的,允许您跨多个计算实例、时间段或地理位置检查您的数据。这是由 Apache DataSketches 首创的一种叫做草图的技术实现的。
确切地说,对于这个例子,我们将利用概要文件的分布指标。为了计算 KS 统计量,我们需要生成样本累积分布函数的近似值。这是由 Apache DataSketches 首创的一种叫做数据草图的技术实现的。
试验设计
首先,我们需要数据。在本实验中,我们将从以下分布中抽取两个大小相同的样本:
- 正常:宽类数据。没有弯曲,在中心周围达到顶点
- Pareto :带有长尾/异常值的偏斜数据
- 均匀:在其域内均匀采样
在这篇博文中,我们将只展示正态分布的结果,但是你可以直接在示例笔记本这里中找到帕累托分布和均匀分布的相同实验。从正态分布情况得出的总体结论也可以应用于其余的分布。
漂移注入
接下来,我们将漂移注入到一个样本中(我们称之为目标分布),以将其与未改变的参考分布进行比较。
我们将通过简单地根据一个参数移动数据的平均值来人为地引入漂移。我们选择使用分布的四分位间距的比率。下面是正态分布情况下的情况:

作者图片
这个想法是有四个不同的场景:无漂移、小漂移、中等漂移和大漂移。漂移的幅度分类和理想的检测/报警过程可能非常主观,取决于您特定应用所需的灵敏度。在这种情况下,我们假设小漂移情况足够小,可以安全地忽略。我们还预计,中等漂移和大漂移情况应会导致漂移警报,因为这两种情况都需要进一步检查。
应用 KS 测试
作为基本事实,我们将使用 scipy 的实现双样本 KS 测试,其中包含来自两个样本的完整数据。然后,我们将这些结果与测试的概要版本进行比较。为此,我们将使用 whylogs 的近似实现相同的测试,该测试仅使用每个样本的统计特征。
配置文件中包含的分布度量是从一个称为草图绘制的过程中获得的,这为它们提供了许多有用的属性,但也给结果增加了一些误差。因此,每次生成配置文件时,KS 测试结果都可能不同。对于每个场景,我们将对数据进行 10 次分析,并将基本事实与统计数据进行比较,例如这些运行的平均值、最大值和最小值。
实验变量
我们的主要目标是回答:
“whylogs 的 KS 实现与 scipy 的实现相比如何?"
然而,这个答案取决于几个不同的变量。我们将运行三个独立的实验来更好地理解每个变量的影响:数据量、存储桶数量和概要文件大小。第一个与每个样本中数据点的数量有关,而最后两个与 whylogs 内部可调参数有关。
实验#1 —数据量
样本中数据点的数量不仅影响总体 KS 测试,还影响剖析过程本身。因此,调查它如何影响结果是合理的。
我们比较了不同样本量(目标和参考分布)的两种实现的 p 值: 500、1k、5k、10k 和 50k。

作者图片
你会注意到我们没有误差线来表示地面实况。对于给定的样本大小和漂移幅度,scipy 的结果是确定的,因为我们总是使用完整的数据,而对于 whylogs,误差线代表 10 次运行中找到的最大值和最小值。
请注意,对于中等和大漂移情况,两个 y 轴都非常接近 0,因此,即使样本大小为 500,两种实现方式都会导致 p 值实际上为 0,这表明我们的数据与零假设高度不相容。对于无漂移和小漂移场景,我们可以看到,当比较基于草图的实现的平均 p 值时,两种实现产生了非常相似的结果,但对于特定运行,尤其是对于大尺寸样本,存在一些差异。然而,对于几乎所有的案例,基本事实都在剖析案例的范围之间。同样值得注意的是,在 95%的置信区间,两种实现对于所有场景中的所有点都会产生相同的结论。
KS 检验真的很敏感,它的灵敏度随着样本量的增加而增加:在小漂移的情况下,对于大于或等于 5k 的样本量,我们拒绝零假设。尽管这在技术上没有错,但我们最初认为这种情况非常小,可以安全地忽略。
在这一点上,我们应该问问自己,这个测试实际上是否告诉了我们所关心的事情。小于 0.05 的 p 值会导致拒绝零假设,但它并不能告诉我们任何关于效应大小的信息。换句话说,它告诉我们有差别,但不是差别有多大。可能有统计学意义,但没有实际的实际意义。
实验 2——桶的数量
为了获得离散的累积分布,我们首先需要定义桶的数量。基于草图的 KS 测试将使用这些桶来计算统计数据。我们将使用大小为: 5、10、50 和 100 的等距箱柜进行实验。对于 10 次运行中的每一次,我们将计算精确的和基于草图的 whylogs 实施之间的绝对误差,并绘制平均值,以及表示发现的最小和最大误差的误差条。我们将根据样本大小和漂移幅度显示这些误差,就像之前的实验一样。
whylogs 的当前版本将 100 作为默认的存储桶数,这也是前面显示的结果中使用的值。

作者图片
因为图中的一些值比其余的值高得多,所以我们在某些情况下断开了 y 轴,以便更好地显示图中的所有条形。即便如此,一些酒吧仍然太小,看不见。中等漂移和大漂移情况下的误差非常接近于 0,这意味着两种实现方式获得了相似的结果。
总的来说,当增加桶的数量时,误差的平均值似乎减小了。然而,对于更大的样本量,误差的方差增加,这是由于在剖析过程中估计误差的增加。
到目前为止,对于两种实现方式,实验显示了无漂移情况下的一定程度的随机性。由于 KS 测试仅依赖于分布之间的最大绝对差值,因此采样过程中的任何微小变化都会影响无漂移情况。
实验#3 —外形尺寸
如前所述,在剖面图中,我们有一个数据草图格式的近似分布。数据草图由参数 K、配置,该参数规定了轮廓的尺寸及其估计误差【3】。该参数越高,估计误差越低。之前的所有实验都是在 K=1024,的情况下进行的,但现在我们想看看随着 K 值的变化,误差会受到怎样的影响。
这一次,我们将样本大小固定为 100k,桶的数量固定为 100,并将 K 参数更改为以下值: 256、512、1024、2048 和 4096。

作者图片
我们省略了漂移大小为 0.4 和 0.75 的图表,因为误差始终很小,不需要可视化。
X 轴是根据序列化时概要文件的大小显示的: K 值 256、512、1024、2048 和 4096 分别产生大约 6KB、11KB、22KB、43KB 和 83KB 的概要文件大小。
如前所述,任何漂移的场景都显示了 KS 测试是多么的敏感。中等和大漂移情况下看不到条形,因为它们的值实际上为 0,但在无漂移情况下,我们可以看到误差与剖面尺寸成反比,并延伸到 K 参数。通过增加 K,由于剖析引起的误差减少,接近两种实现的结果。
我们还可以验证,对于这种情况,误差非常小。但是如果我们对最小化这些错误感兴趣,我们可以牺牲轮廓空间来获得更好的结果。
结论
让我们总结一下这些实验的一些要点:
- 对数据配置文件执行 KS 测试是可能的,并且结果非常接近标准实现。然而,结果是不确定的。
- KS 测试非常敏感,随着样本量的增加,它会变得更加敏感。当只在零假设下进行测试时,它可能会告诉我们一些关于分布之间的差异,但对差异的大小不敏感。
- 我们可以调整内部参数以获得更好的 whylogs 实现结果。特别是,我们可以增加剖面大小,以获得更接近地面真相的结果。
我们希望这有助于建立对 KS 测试如何与数据剖析一起工作的直觉。我们也对 KS 测试的局限性有了更好的理解。受此激励,我们 whylogs 已经在实施额外的相似性度量!例如,Hellinger distance 已经在 whylogs 中实现了,所以请继续关注更多的实验和基准测试!
感谢您的阅读,如果您有任何问题/建议,请随时联系我们!如果你对探索项目中的 whylogs 感兴趣,考虑加入我们的 Slack 社区来获得支持并分享反馈!
参考
[1]—Kolmogorov–Smirnov 检验。(2022 年 10 月 29 日)。在维基百科里。https://en . Wikipedia . org/wiki/Kolmogorov % E2 % 80% 93s mirnov _ test
[2] —格陵兰,s .,森,S.J .,罗斯曼,K.J. 等统计检验, P 值,置信区间和功效:误解指南。欧洲流行病学杂志 31 ,337–350(2016)。
[3] —卡宁,z .,朗,k .,&利伯蒂,E. (2016)。流中的最佳分位数逼近。 arXiv 。https://doi.org/10.48550/arXiv.1603.05346
理解 l1 和 l2 正则化
原文:https://towardsdatascience.com/understanding-l1-and-l2-regularization-93918a5ac8d0
线性回归模型中的正则化综述。

l1 和 l2 用图形表示。来源:https://commons.wikimedia.org/wiki/File:Regularization.jpg
当训练机器学习模型时,我们的模型可能在训练集上表现准确,但在测试数据上表现不佳。
在这种情况下,我们有一个过度拟合的问题;事实上,当我们的机器学习模型试图覆盖所有数据点(或更多),而不是给定数据集中存在的所需数据点时,就会发生过度拟合 。因此,模型开始缓存数据集中存在的噪音和不准确的值,所有这些因素都会降低模型的效率和准确性。
当过度拟合发生时,有很多方法可以避免过度拟合;在线性回归的情况下,避免过度拟合的一种方法是使用通常称为 l1 和 l2 的两种正则化方法之一,我们将在本文中了解它们。
1.一些回归概念
让我们从了解回归的基础开始。正如维基百科所说:
回归分析是一组统计过程,用于估计因变量(通常称为“结果”或“响应”变量)与一个或多个自变量(通常称为“预测值”、“协变量”、“解释变量”或“特征”)之间的关系。
线性回归公式为:

线性回归公式。图片作者。
其中“Yi”是因变量(也称为“响应”)的(向量),而“X”是自变量(也称为“特征”)的(向量)。α和β是系数,回归的“游戏”全靠寻找“最佳参数”。有了“最佳参数”,我们可以找到“最佳拟合”给定数据的“最佳直线”,这样我们就可以在给出未来输入(新特性值)时估计未来的结果值。
我想强调 X 和 Y 是向量的事实,因为在机器学习中,我们总是必须处理多个特征,所以在线性回归的情况下,我们不能在 X 和 Y 之间画一条线,就像我们在高中(或大学)只有“一个 X”(一个自变量)时可以做的那样。在这些情况下,所有的特征都以某种方式对结果有贡献,所以我们不能仅仅绘制一个图,因为这将是一个多变量的图(我们无论如何都可以这样做,但是非常复杂)。
当线性回归出现过拟合时,我们可以尝试正则化我们的线性模型;正则化是机器学习中惩罚复杂模型最常用的技术:它通过惩罚具有高值的回归系数来避免过度拟合。更具体地说,减少参数,缩小(简化)模型;其目的是试图减少模型的方差,而不显著增加偏差。
在实践中,在正则化模型(l1 和 l2)中,我们将所谓的“成本函数”(或“损失函数”)添加到我们的线性模型中,它是我们的模型在估计 X 和 y 之间的关系的能力方面“错误程度”的度量。成本函数的“类型”将 l1 与 l2 区分开来。
2.L1 正则化或套索正则化
Lasso(最小绝对和选择算子)回归执行 L1 正则化,它添加了一个等于系数幅度绝对值的惩罚,正如我们在上面蓝色矩形的图像中看到的(λ是正则化参数)。这种类型的正则化使用收缩,即数据值向中心点收缩,例如平均值。
这种类型的正则化可以产生系数很少的稀疏模型;事实上,有些系数可以变为零,可以从模型中消除。这意味着这种类型的模型也执行特征选择(因为一些系数可以变成 0,这意味着系数为 0 的特征被消除),并且当我们具有“大量”特征时,它将被选择,因为它简化了模型。因此,当我们必须处理“大量”特性时,这个模型是很好的。
如果我们看看本文顶部的图像,惩罚因子的绝对值可以用图形表示为一个(旋转的)正方形,而椭圆轮廓是成本函数。如果成本函数(省略号)“命中”(旋转的)正方形的一个角,则对应于轴的系数被收缩为零,并且相关特征被消除。
套索回归的一个问题是多重共线性;我的意思是,如果有两个或更多高度相关的变量,那么 Lasso 回归随机选择其中一个,这对于我们模型的解释是不利的。为了避免这种情况,我建议您绘制一个相关矩阵,找到最终高度相关的特征并删除其中一个(例如,如果 feature_1 和 feature_2 高度相关,您可以决定删除 feature_2,因为高度相关的变量对最终解决方案具有相同的影响)。
2.L2 正则化或岭正则化

岭回归公式。来源:https://open classrooms . com/en/courses/6401081-improve-the-performance-of-a machine-learning-model/6561486-improve-your-regulatory(CC BY-SA 4.0)
岭回归是一种在线性自变量高度相关的场景中估计多元回归模型系数的方法。
该模型增加了一个成本函数,即系数幅度的平方值,事实上,如果我们观看本文的第一幅图像,成本函数的几何表示在这种情况下是一个圆形。
不幸的是,这个模型不执行特征选择:它降低了模型的复杂性,但没有减少自变量的数量,因为它不会导致系数为 0。这意味着最终的模型将包括所有的独立变量。为了避免这一问题,由于当要素高度相关时必须使用山脊线,因此在这里(比使用套索模型更重要)使用相关矩阵研究要素并决定从正在执行的研究中删除哪些要素非常重要。
结论
正如我们已经看到的,当我们的模型出现过度拟合的问题时,必须执行正则化。
关于线性回归模型,当我们有很多特征时,我们最好使用 Lasso 正则化,因为它执行均匀的特征选择;如果我们有高度相关的特征,我们最好使用岭模型。
最后,如果你对相关和回归的区别有疑问,可以在这里阅读我关于这个话题的澄清文章。
需要 Python 和数据科学方面的内容来开始或促进您的职业生涯?下面是我的一些文章,可以帮到你:
Python:
数据科学:
- 即使全职工作(或学习)也要如何学习数据科学
- 如何处理数据科学中的缺失值
- 如何在数据科学项目中执行特征选择
- 如何检测数据科学项目中的异常值
- 执行图形残差分析的两种方法
- 如何利用学习曲线轻松验证您的 ML 模型
- 柱状图和柱状图有什么区别?
- 相关和回归的区别
- 了解 l1 和 l2 正规化
- 逻辑回归:我们来搞清楚!
- 什么是训练有素的模特?
考虑成为会员:你可以免费支持我和其他像我一样的作家。点击 这里成为会员。
理解梯度增强决策树中的 L1 正则化
LightGBM 和 R 中的一个例子

(图片由作者提供)
我发现超参数调整通常是建模中最有趣的部分之一。老实说,模型本身的算法已经被其他人编写好了,所以调整和设置阶段是我们大多数人有所作为的时候。尽管如此,我认为对算法的工作原理有一个透彻的了解是很重要的。
当谈到梯度增强模型时,我发现超参数有不同的类别:
- 像最大深度这样简单的问题——这些问题通常不会让人感到意外。
- 像黑森最小和这样复杂的问题——我认为建模者通常拥有的直觉是正确的,即使细节有些模糊。
- 然后还有像 L1 或 L2 正规化这样的参数,我发现这里有很多混乱和误解,几乎没有人费心去深入挖掘并找出它们真正是如何工作的。
如果你碰到一个超参数,你不确定它的最优值会是 0.0001 还是 10000,我觉得你应该退一步理解算法,而不是粗暴地强迫所有可能的组合。
我最近对 L1 的正规化进行了分析,因为我对一些发现感到非常惊讶,所以我决定写一份我从未有过的指南。希望你会觉得有用!
行动计划
对于忙碌/不耐烦的读者
如果你不想看完整本书,在文章的最后有一个总结部分,你会在其中找到一个叫做解决方案的子部分。所有重要的结果都在代码片段中。
该项目
我们将用 LightGBM 和 r 中的合成数据来演示一个例子。我的目标是使这个例子可复制。这篇文章将包括一些代码片段,我在这个项目中使用的所有其他代码都可以在我的 GitHub repo 中找到。
我将采取我通常的方法:运行一些模型,看看会发生什么,然后不要停止,直到我完全理解所有的数字是如何得出的。
我们将详细了解:
- 叶子分数和分割增益的计算,
- 不同的
lambda_l1值对模型拟合和预测的影响, - 单棵树内发生了什么,
- 测试分数如何随着
lambda_l1变化,以及我们是否设法在模型性能上得到任何改进。
关于命名
为了避免混淆,我将一致地使用 lambda_l1 表达式作为 L1 正则化参数。我承认 XGBoost 和 LightGBM 都使用了lambda_l1 = reg_alpha和lambda_l2 = reg_lambda,但是,最好还是安全一点!
为什么是泊松?
分析泊松回归是我经常性的“爱好”,原因如下:
- 我在保险行业工作,传统的广泛使用的方法是用泊松分布来模拟索赔频率。
- 我只是觉得它更令人兴奋——大多数回归例子都是关于经典的残差平方极小化。(在某种程度上,这就是 LightGBM 参数设置中所谓的“回归”。)
- 这更容易证明我的主要观点:你必须理解算法是如何工作的。人们不能简单地将参数从一个问题复制到另一个问题,即使是在同一个模型和包中,因为超参数可能根据目标的不同而完全不同地工作。
假定的知识
这篇文章是关于梯度增强树的 L1 正则化,我假设读者对这两个原理都很熟悉。
我有一些关于这两个主题的早期作品,如果你想仔细阅读的话:
数据
我们将生成一个非常简单的数据集——不用说,在现实生活中,你永远不会对这样的数据使用如此复杂的方法,但它非常适合演示我的观点。数据有一个(!)列,一个分类特征,在代码中称为var1,有三个可能的值:0、1 和 2。我们为每个组分配一个泊松λ,并基于这些λ参数生成随机变量。
最终,我们会有 1000 次观察。这对于出现可见的随机偏差来说足够小了。例如,组 1 和组 2 应该是相似的,然而它们的实际值的平均值是0.73和0.61:
var1 lambda number mean_target sum_target
0 0.3 484 0.3347107 162
1 0.7 297 0.7306397 217
2 0.7 219 0.6118721 134
如果你想复制结果,我建议手动构建表格,因为你将在我的脚本中找到的随机种子将在不同的设置中以不同的方式工作。下一个表是复制数据所需的全部内容,N 是具有相应变量 1 和目标的观察值的数量,它们加起来应该是 1,000:
# var1 target N
# 1: 0 0 349
# 2: 0 1 113
# 3: 0 2 18
# 4: 0 3 3
# 5: 0 4 1
# 6: 1 0 150
# 7: 1 1 93
# 8: 1 2 39
# 9: 1 3 14
# 10: 1 4 1
# 11: 2 0 121
# 12: 2 1 69
# 13: 2 2 22
# 14: 2 3 7
巨大的期望
现在,我们将对该数据运行一个梯度增强模型,使用各种级别的lambda_l1,没有来自超参数的其他限制。
我们可以相当有把握地猜测,无限制(即lambda_l1 = 0运行)将简单地预测每组中的mean_target。(因为泊松λ的最大似然估计是样本的平均值。)积极lambda_l1的影响更难预测。
我希望通过引入
lambda_l1正则化,让模型预测更接近理论。具体来说,我希望第 1 组和第 2 组“一起工作”,并意识到他们毕竟没有什么不同。
让我们看看结果如何!
系统模型化
我遍历了 0 到 200 之间的整数lambda_l1值,并收集了模型对第 0、1 和 2 组的预测值。
密码
如果您想运行代码,这里是脚本的相应部分。假设有一个data_curr表,上面列出了 1000 个观察值。我们还需要data.table和lightgbm包。
(英)可视化(= visualization)
结果存储在适当命名的result_dt表中。它包含每个lambda_l1组的预测。那么,我们准备好展示这些结果的图表了吗?鼓声…

lambda_l1 值对预测的影响(图片由作者提供)
X 轴显示不同的lambda_l1值,因此线条代表随着正则化参数的增加预测的变化。我把线移动了一点,这样我们可以更好地看到它们何时一起移动。最后,所有 3 个组将共享同一个预测:总平均值。
结果分析
让我们收集一下目前已知的信息:
- 第 2 组预测暂时不变,只在
lambda_l1= 30 左右开始变化, - 第 1 组预测逐渐减少到第 2 组的预测,同时第 0 组预测增加,
- 这三组在一个点上相遇,但是接下来会有奇怪的上下跳跃,
- 最后,三个小组再次相遇,再也不会偏离共同的目标。
不知道你怎么样,我觉得剧情很惊喜!
如果你想知道:lambda_l10 到 1 之间的值实际上并没有实现任何可测量的东西。
测试结果
现在,让我们做一个快速测试:正则化实际上对模型性能做了什么吗?我们知道它确实改变了预测,但是这些改变是在正确的方向上吗?
我的测试方法如下:我生成了一个巨大的表,使用相同的泊松 lambdas 作为我们的训练数据,并在这个表上运行预测。这个想法是,大表将代表“真正的”分布——我不想让随机性在这一点上发挥作用。模型性能用平均泊松对数似然来衡量。
事不宜迟,我们来看看剧情:

非常令人惊讶的是,这种规范化似乎确实提高了一点性能。在lambda_l1 = 20 时,总平均对数似然从-0.7028107上升到-0.6996772。不是很大的收获,但我们会接受的。
我们证实,在某些情况下,lambda_l1 正则化可以提高模型性能。
既然我们知道这个项目不是毫无意义的,那么是时候进行一次真正的深度潜水,调查每棵树、树枝和树叶了。
下一点会有点干…我已经警告过你了!
深潜
证明文件
对于那些想要理解梯度推进树背后的数学原理的人,我通常的建议是(曾经)去查阅 XGBoost 文档。
然而,现在有一个小问题。
看看他们引入模型复杂性(也称为正则化)时出现的公式:

上式中引入的两个正则项是γ和λ。问题是:它们都不是 L1 正则化术语!应该有一个因子,一个lambda_l1,惩罚单个叶片得分的绝对值(公式中的 wj )。γ惩罚了叶子本身的数量(公式中的 T ,我不知道该怎么称呼这种类型的正则化…也许是 L0 正则化?有趣的是,如果我们看一下 XGBoost 参数,没有那个奇怪的γ参数的迹象。
如果你知道这是怎么回事,一定要让我知道。
因此,文档中的公式没有一个能立即适用于我们的问题…
树形地图
接下来我开始分析单独的树木地图。如果您不知道,您可以使用下面的命令将模型中所有单棵树的细节转换成漂亮的data.table:
lgb.model.dt.tree(my_model)
其中my_model是 LightGBM 助推器。
这些是非正则化(lambda_l1 = 0)模型中最重要的列:
depth split_gain threshold internal_value leaf_value leaf_count
1: 0 28.861738 0 -0.6674794 NA NA
2: 1 NA <NA> NA -0.7537717 484
3: 1 1.721168 1 -0.5865387 NA NA
4: 2 NA <NA> NA -0.5621415 297
5: 2 NA <NA> NA -0.6196252 219
在接下来的几节中,我们将看到它们是如何被导出的!
非正则树的叶分数
我不打算花太多时间来描述泊松叶是如何工作的(同样,请参见我以前关于这个主题的帖子此处和此处),但是作为一个例子,上面树中第 0 组的第一次分裂-0.7537717计算如下:
gradient <- 484 * 0.513 - 162
hessian <- 484 * 0.513 * exp(0.7)
(-gradient / hessian) * 0.5 + (-0.6674794)
在哪里
484:对那片叶子的观察次数0.513:当前预测(起始预测是泊松的整体平均值)162:第 0 组实际目标值- 那只是一件泊松的事情,再一次,看我关于这个主题的早期作品…
0.5:学习率-0.6674794:当前预测的对数,log(0.513),这是一个管理步骤,因此第一棵树中的叶分数也将包括总体平均目标
记住泊松回归的对数函数!在第一个树之后,组 0 的预测将从开始的 exp( -0.6674794)变为 exp( -0.7537717)。如果我们在一棵树后停止训练,那就是模型预测。
所以我们知道在非正则版本中会发生什么,现在让我们试试正则版本!
正则化树的叶分数
起始示例
我随机挑选了lambda_l1 = 15 作为另一棵树进行调查。让我们看看模型中的第一棵树!
depth split_gain threshold internal_value leaf_value leaf_count
1: 0 19.69985 0 -0.6674794 NA NA
2: 1 NA <NA> NA -0.7387716 484
3: 1 NA <NA> NA -0.6006085 516
第一印象:现在只有一个分裂,第 1 组和第 2 组没有分开,不像在非正规版本中!
我基本上是从我在 XGBoost 文档的结构分数部分找到的最佳叶分数的公式开始的(为了避免混淆,我没有在这里复制它),并使用了一些试错法,其方式似乎符合叶分数绝对值的逻辑。
在叶计算中,我们必须在梯度和中减去或加上λ参数。如果梯度和为正,我们必须减去lambda_l1,如果梯度和为负,我们加上lambda_l1。嗯,我们把它加到梯度上,但是我们取和的负值…用数字来解释更简单,我给你演示一下!
例如,-0.7387716在上面的树中被计算为:
gradient_l <- (484 * 0.513) - 162
hessian_l <- (484 * 0.513) * exp(0.7)
(-(gradient_l - 15) / hessian_l) * 0.5 + log(0.513)
而另一个分支,-0.6006085计算为:
gradient_r <- (516 * 0.513) - 351
hessian_r <- (516 * 0.513) * exp(0.7)
(-(gradient_r + 15) / hessian_r) * 0.5 + log(0.513)
所以它几乎和非正则化树的公式一样,不同的是加上或减去 15。
并发症
在这一点上,你可能会有一个困扰的问题:如果 lambda_l1 大于当前梯度和的绝对值,会发生什么?
在我们的示例中,如果某个分割中的梯度之和小于 15,会发生什么情况?我们会交换标志吗?好吧,如果我们看看模型中的第三棵树,tree_index = 2,我们可以看到:
depth split_gain threshold internal_value leaf_value leaf_count
1: 0 6.4673676 0 0.00000000 NA NA
2: 1 NA <NA> NA -0.04681926 484
3: 1 0.2443614 1 0.03309579 NA NA
4: 2 NA <NA> NA 0.04561548 297
5: 2 NA <NA> NA 0.00000000 219
因此在两棵树(索引 0 和 1)之后,模型将停止向组 2 分配额外的分数。在树 0 和 1 中,它为组 0 分配了一个预测,并为组合的组 1 和 2 分配了一个预测。然而,在树索引 2 中,它决定拆分组 1 和组 2,但实际上保持组 2 不变。
目前,我们不会关注它为什么突然决定拆分这两个组。让我们暂时接受模型分裂,并尝试理解为什么组 2 的新叶分数是 0。
实际上,这很简单。当我们到达树索引 2 时,组 2 的预测是0.5745756,这意味着它的梯度和将是:
219 * 0.5745756 - 134 = -8.167944
其绝对值低于我们的lambda_l1。让我们看看第一组在这一点上的梯度和:
297 * 0.5745756 - 217 = -46.35105
这就是为什么模型将新的叶子分数分配给组 1,而不是组 2。
总之,我们必须将 gradient — lambda_l1表达式限制在 0,以获得正确的叶子分数。
不要担心现在是否有些混乱,我们将在本文后面的精确代码函数中阐明这种关系。
分割收益
大局
在树中,我们还没有重新计算最后一个值:分割收益。这一列非常重要,例如,这是许多特征重要性分析的基础,在这些分析中,特征是按照它们的分裂所附带的增益来排序的。
我将再次链接 XGBoost 文档,因为那里的基本思想非常容易理解,但我没有复制公式,因为它缺少lambda_l1参数。在我们的例子中,目标包括两个项目:最大化对数似然,以及 L1 正则化形式的惩罚。
当树考虑拆分时,它基本上在目标函数中进行前后比较。
具体地,我们将我们将实现的左右分支/叶的增益与相同观察的未分裂增益进行比较。"这组观察值值得进一步拆分吗?"是模型所要求的。
一个例子
让我们再看一遍上面的例子(这是第一棵树,从lambda_l1 = 15,我们已经重新计算了它的叶子):
depth split_gain threshold internal_value leaf_value leaf_count
1: 0 19.69985 0 -0.6674794 NA NA
2: 1 NA <NA> NA -0.7387716 484
3: 1 NA <NA> NA -0.6006085 516
再说一次,我没有详细说明我是如何得到这些公式的,以及我使用了哪些资源,下面几节有一些有用的提示。现在,让我向你展示一下上面的19.69985是如何计算的。
我们将需要左右分割的梯度和黑森,我们已经在leaf_value计算中使用了这些:
gradient_l <- (484 * 0.513) - 162
hessian_l <- (484 * 0.513) * exp(0.7)
gradient_r <- (516 * 0.513) - 351
hessian_r <- (516 * 0.513) * exp(0.7)
分割增益将由 3 项组成:
- 未分割的增益,
- 左侧增益,
- 正确的收益。
首先是未分割增益——这在我们的例子中非常简单,这是树中的第一个分割,如果我们不这样做,我们的增益正好是 0。
来自左分离的增益可计算如下:
(gradient_l - 15) ^ 2 / hessian_l = 10.16513
- 右分的增益:
(gradient_r + 15) ^ 2 / hessian_r = 9.53473
总分离增益计算如下:
10.16513 + 9.53473 - 0 = 19.69985
回到前面的问题
还记得我说过不要担心为什么模型突然决定在树索引 2 中拆分组 1 和组 2 吗?好吧,现在让我们担心一下!
让我们计算一下,与我们现在拥有的相比,这种拆分会带来什么样的收益!假设我们要分裂,新的梯度和黑森是:
gradient_l <- (297 * 0.513) - 217
hessian_l <- (297 * 0.513) * exp(0.7)
gradient_r <- (219 * 0.513) - 134
hessian_r <- (219 * 0.513) * exp(0.7)
现在,未分割的增益等于上面右侧分割的增益 9.53473。这是我们最初的收获。如果我们进一步分割会发生什么?
从左边的叶子,我们会得到:
(gradient_l + 15) ^ 2 / hessian_l = 8.030935
从右边的叶子看:
(gradient_r + 15) ^ 2 / hessian_r = 0.1956444
(注意lambda_l1的标志,这完全取决于梯度的标志!)
这三项的整体分割值为:
8.030935 + 0.1956444 - 9.53473 = - 1.308151
这是一个负数,所以我们不做分割。
再一次,这只是一个例子,不要担心它是否令人困惑,你会觉得你不能复制,你会在下面的代码片段中找到确切的公式。
收益与现实的分割
我看到很多人忽略了一个非常重要的注意事项:这里的收益并不代表基于树前后预测的客观值之间的差异。收益仅仅是衡量分裂点的好坏,而不是模型决定如何处理它们。
换句话说,作为预测基础的leaf_values,有一个分割增益没有考虑的关键因素。你能猜到吗?
是的,这是学习率。
学习率参数对 leaf_values 有影响,但对 split_gains 没有影响。
这意味着,根据来自树的实际预测,将一个意义附加到分割收益上是毫无意义的。如果我们有一个learning_rate = 1,这些就是被罚目标函数的估计收益。
一个有趣的经验笔记
根据我们目前所知,如果说模型不关心调整梯度和的绝对值低于lambda_l1的组的预测,这不公平吗?在lambda_l1 = 15 的情况下,这些是实际值(来自单个泊松随机变量)和分组预测的总和:
var1 actual predicted
1: 0 162 177
2: 1 217 202
3: 2 134 134
第二组(上面第一张图的中间线)得到了准确的预测,其他两组相差 15%。
对我来说,这绝对是一个激动人心的时刻。
我认为它从一个不同的角度看待正则化的影响,而不是简单地接受目标函数会对单个叶子分数进行惩罚。
摘要
解决方案
让我们总结一下到目前为止我们所学的内容!
在代码格式中,这是计算新叶分数、叶收益和 L1 正则化拆分收益所需的全部内容:
解释
如上所述,我认为思考 L1 正则化实际上在做什么是非常有用的,而不是简单地根据改变的目标函数和对单个叶子分数的惩罚来观察它。
当你使用梯度增强树的 L1 正则化时,你基本上是在设置一个阈值,在这个阈值之下,模型将不会试图进一步优化。具体来说,如果观测值的梯度之和的绝对值低于该正则化参数,则模型不会进一步分割观测值。
在泊松回归的情况下,梯度是实际值和预测值之间的差值。在尝试 L1 参数时,这是一个很好的起点,可以为自己建立一个经验法则。
一般而言,该模型将更接近接近总体平均值的观察值。使用高 L1 参数将使预测更加接近。
结束语
这是我对梯度增强树的 L1 正则化的分析。一如既往,如果我错过了什么,一定要让我知道!
这是一个令人满意的项目,我希望你发现结果有用!
https://matepocs.medium.com/membership
来源
我通常认为 XGBoost 文档是一个非常好的起点,尽管它没有考虑 L1 规范:
https://xgboost.readthedocs.io/en/stable/tutorials/model.html
除此之外,我还使用了 LightGBM 包中的源代码。我认为值得看一看幕后发生了什么。这个脚本的GetLeafGain功能非常关键:
理解 Polars 中的惰性求值
原文:https://towardsdatascience.com/understanding-lazy-evaluation-in-polars-b85ccb864d0c
了解什么是急切执行和延迟执行,以及如何使用延迟执行来优化查询

汉斯-尤尔根·马格在 Unsplash 拍摄的照片
在我上一篇关于 Polars 的文章中,我向您介绍了 Polars 数据帧库,它比 Pandas 数据帧高效得多。
在本文中,我将深入探究是什么让 Polar 如此之快——懒惰评估。你会学到急切执行和懒惰评估/执行的区别。
隐式懒惰评估
为了理解懒惰评估的有效性,与熊猫如何做事情进行比较是有用的。
在这个练习中,我将使用位于https://www.kaggle.com/datasets/usdot/flight-delays的 flights.csv 文件。该数据集包含 2015 年美国航班延误和取消的详细信息。它是由交通部运输统计局收集和发布的。
许可— CC0:公共领域(https://creativecommons.org/publicdomain/zero/1.0/)。
我们将使用 Pandas 加载 flights.csv 文件,该文件包含 580 万行和 31 列。通常,如果你在内存有限的机器上加载这个,Pandas 会花很长时间把它加载到一个 dataframe 中(如果加载的话)。以下代码执行以下操作:
- 将 flights.csv 文件加载到 Pandas 数据框架中
- 过滤数据帧以查找 12 月的航班,这些航班的始发地机场是 SEA,目的地机场是 DFW
- 测量将 CSV 文件加载到数据帧和过滤所需的时间
import pandas as pd
import timestart = time.time()df = pd.read_csv('flights.csv')
df = df[(df['MONTH'] == 12) &
(df['ORIGIN_AIRPORT'] == 'SEA') &
(df['DESTINATION_AIRPORT'] == 'DFW')]end = time.time()
print(end - start)
df
在我的 8GB 内存的 M1 Mac 上,上面的代码片段花了大约 7.74 秒加载并显示以下结果:

作者图片
Pandas 的主要问题是,在进行任何过滤以删除所有不需要的行之前,您必须将数据集的所有行加载到 dataframe 中。
虽然您可以将数据集的前 n 行或后 n 行加载到 Pandas 数据帧中,但要将特定行(基于某些条件)加载到数据帧中,您需要先加载整个数据集,然后才能执行必要的过滤。
现在让我们使用 Polars,看看是否可以减少加载时间。以下代码片段执行以下操作:
- 使用 Polars 库的
read_csv()方法加载 CSV 文件 - 使用
filter()方法执行过滤,并指定保留我们想要的行的条件
**import polars as pl**
import timestart = time.time()df = **pl.read_csv('flights.csv').filter(
(pl.col('MONTH') == 12) &
(pl.col('ORIGIN_AIRPORT') == 'SEA') &
(pl.col('DESTINATION_AIRPORT') == 'DFW'))**end = time.time()
print(end - start)
display(df)
在我的电脑上,上面的代码片段花了大约 3.21 秒,比熊猫有了很大的改进:

作者图片
现在,知道read_csv()方法使用急切执行模式很重要,这意味着它会在执行任何过滤之前直接将整个数据集加载到数据帧中。在这方面,这段使用 Polars 的代码与使用 Pandas 的代码相似。但是你已经可以看到 Polars 比熊猫快多了。
注意这里的
filter()方法作用于 Polars DataFrame 对象
下一个改进是将read_csv()方法替换为使用延迟执行— scan_csv()的方法。scan_csv()方法延迟执行,直到collect()方法被调用。它分析所有查询,直到collect()方法,并尝试优化操作。以下代码片段显示了如何将scan_csv()方法与collect()方法结合使用:
import polars as pl
import timestart = time.time()
df = pl.**scan_csv**('flights.csv').filter(
(pl.col('MONTH') == 12) &
(pl.col('ORIGIN_AIRPORT') == 'SEA') &
(pl.col('DESTINATION_AIRPORT') == 'DFW'))**.collect()**
end = time.time()
print(end - start)
display(df)
scan_csv()方法被称为隐式懒惰方法,因为默认情况下它使用懒惰评估。记住scan_csv()方法不返回数据帧是很重要的——它返回的是LazyFrame。
对于上面的代码片段,Polars 没有将所有行加载到 dataframe 中,而是优化了查询,只加载那些满足filter()方法中的条件的行。在我的电脑上,上面的代码片段耗时约 2.67 秒,与前面的代码片段相比,处理时间进一步减少。
注意这里的
*filter()*方法适用于 Polars LazyFrame 对象
显式懒惰评估
还记得我之前提到的read_csv()方法使用急切执行模式吗?如果您想对所有后续查询使用延迟执行模式,该怎么办?嗯,你可以简单地调用上面的lazy()方法,然后使用collect()方法结束整个表达式,就像这样:
import polars as pl
import timestart = time.time()
df = pl.read_csv('flights.csv')
.**lazy()** .filter(
(pl.col('MONTH') == 12) &
(pl.col('ORIGIN_AIRPORT') == 'SEA') &
(pl.col('DESTINATION_AIRPORT') == 'DFW'))**.collect()**
end = time.time()print(end - start)
display(df)
通过使用lazy()方法,您指示 Polars 暂停后续查询的执行,而是优化所有查询,直到使用collect()方法。collect()方法开始执行,将结果收集到数据帧中。本质上,这个方法指示 Polars 急切地执行查询。
了解 LazyFrame 对象
现在让我们分解一个查询,看看 Polars 实际上是如何工作的。首先,让我们使用scan_csv()方法,看看它返回什么:
pl.scan_csv('titanic_train.csv')
数据来源 :本文数据来源于https://www.kaggle.com/datasets/tedllh/titanic-train。
许可—【https://opendatacommons.org/licenses/dbcl/1-0/】数据库内容许可(DbCL)1.0 版T21
上面的语句返回一个“polars.internals.lazy_frame.LazyFrame”对象。在 Jupyter Notebook 中,它将显示下面的执行图(我将在后面详细介绍):

作者图片
执行图显示了 Polars 执行查询的顺序。
返回的LazyFrame对象表示您已经制定但尚未执行的查询。要执行查询,您需要使用collect()方法:
pl.scan_csv('titanic_train.csv')**.collect()**
还可以用一对括号将查询括起来,并将其赋给一个变量。要执行查询,只需调用查询的collect()方法,如下所示:
**q = (**
pl.scan_csv('titanic_train.csv')
**)**
**q.collect()**
将查询括在一对括号中的好处是,它允许您将多个查询链接起来,并将它们放在单独的行中,从而大大增强可读性。
上面的代码片段显示了以下输出:

作者图片
出于调试目的,有时只返回几行来检查输出是有用的,因此您可以使用fetch()方法来返回前 n 行:
q.**fetch**(5)
上述语句返回结果的前五行:

您可以在单个查询中链接各种方法:
q = (
pl.scan_csv('titanic_train.csv')
**.select(['Survived','Age'])
.filter(
pl.col('Age') > 18
)** )
show_graph()方法显示您之前看到的执行图,并带有一个参数来指示您是否想要查看优化的图形:
q.**show_graph(optimized=True)**
上面的语句显示了下面的执行图。您可以看到,基于年龄列的过滤是在 CSV 文件加载期间一起完成的:

作者图片
相比之下,让我们来看看如果查询以急切模式(即非优化模式)执行,执行流程会是什么样子:
q.show_graph(**optimized=False**)
从下面的输出可以看出,首先加载 CSV 文件,然后选择两列,最后执行过滤:

作者图片
要执行查询,调用collect()方法:
q.collect()
将显示以下输出:

如果只需要前五行,调用fetch()方法:
q.fetch(5)

作者图片
https://weimenglee.medium.com/membership
我将在即将到来的新加坡 ML 会议(2022 年 11 月 22-24 日)上主持一个关于 Polars 的研讨会。如果你想快速启动 Polars 数据框架,请在https://ml conference . ai/machine-learning-advanced-development/using-Polars-for-data-analytics-workshop/注册参加我的研讨会。

摘要
我希望您现在对 Polars 中的惰性执行是如何工作的,以及如何为只支持急切执行的查询启用惰性执行有了更好的了解。显示执行图使您更容易理解查询是如何被优化的。在接下来的几篇文章中,我将继续讨论 Polars 数据框架以及操纵它们的各种方法。如果你想让我关注某个特定的话题,请给我留言!
理解逻辑回归——优势比、Sigmoid、MLE 等
逻辑回归是分类中最常用的机器学习技术之一。然而,尽管看起来很简单,但理解正在发生的事情的实际机制——优势比、对数变换、sigmoid——以及为什么使用它们可能相当棘手。在这篇博客中,我将解释逻辑回归,大部分是直观的,但有时会用少量的数学。

我的收藏
这篇文章将关注逻辑回归的方法和原因。关于它在分类和分类评价中的用途,可以查看我的帖子这里。
我一直困惑的一点是,我们如何从所谓的比值比到实际的概率,因为这是我们通常估计的。所以,这是一个关于逻辑回归方程的推导的简短帖子,为什么它被转换成臭名昭著的“比值比”,它是如何被转换回来的,以及它是如何被估计的。
那么,让我们开始吧..
何时使用
假设我们要根据验血结果把病人分成两组,身体质量指数,BP。这些类别是:糖尿病和非糖尿病。这是一个典型的分类问题。我们有一个二元因变量,即:
糖尿病患者= 1
如果不是糖尿病患者,则= 0
我们估计的等式是:

其中,如果是糖尿病患者,y = 1;如果是非糖尿病患者,y = 0。x 变量代表各种输入特征,如考试分数、身体质量指数等。目前我们只有一个 x,比如说,测试分数。
这是什么挑战?
这是一个典型的线性回归问题,我们可以通过最小化误差平方和来估计它。然而,与回归不同的是,我们预测一个数字,如销售额。这里我们试图预测一个被值 1 或 0 限制的类别。如果我们得到一个接近 1 的值,比如说 83,我们可以把它四舍五入到 1,类似地,0.33 可以归类为 0。因此,这个等式有效地预测了属于类别 1 或 0 的特定实例的概率。通常,一些外部定义的临界值,如 0.5 或 0.75,将用于定义预测得分。逻辑回归分类过程可以在图像中可视化。在这篇文章中阅读更多关于分类阈值的信息。

分类过程(图片来源:我的收藏)
但是,这其中的一个关键问题是线性回归是无界的。这意味着,例如,对于某些独立变量值,预测的 y 值可能大于 1,甚至为负。这在分类问题上显然没有意义。
让我们从维基百科中取一个简单的例子。我们有学生的及格-不及格数据与学习时数的关系图。通过-失败是一个二元变量,可以取值 1 或 0。学习时间是数字。我们可以把它绘制成一个带有预测线的标准图。我们在 x 轴上绘制了学习小时数,在 y 轴上绘制了给定学习小时数时通过或失败的概率(p(y|x))。

对分类数据绘制线性回归图(图片来源:我的收藏)
我们观察如下:根据预测线,如果小时数超过 5,我们的预测值超过 1。类似地,如果学习的小时数少于半小时,预测的 y,在这种情况下是 p(y=1),变成负数。
这些结果显然没有意义,我们需要一种方法来估计一个上界为 1,下界为 0 的方程,而不考虑 x 的值。
逻辑回归的案例
这就带来了逻辑回归。逻辑回归听起来非常类似于线性回归。它本质上是将线性回归转换成一个方程,使其极限值为 0 和 1。我们仍然希望使用线性回归来解决这个问题,但是我们需要确保函数保持在其极限内。我们希望估计的是下面的线性方程。
P(y=1)=ax + b
线性方程有两个问题:
- 如上所述,它是无界的
- 在许多情况下,我们看到,当 p 已经很大(或很小)时,与 p 接近 1/2 时相比,改变 p 相同的量需要 x 有更大的变化。这是一种非线性关系,线性模型无法做到这一点。
解决这个问题的方法是通过逻辑转换。通常代数只会增加混乱而不是清晰。但在这种情况下,一点点代数帮助我们看到逻辑回归方程的实际含义,它不是凭空想象出来的。
逻辑方程式如下。显然这需要解释…

其中 P(y=1)= a + bx
开始解释吧。
推导逻辑回归方程
作为第一步,我们需要变换 p(y=1 ),使它的极限不能是负数或无穷大。接下来,为了简单起见,我们将 p(y=1)表示为 p。线性方程的转换通过取比值比来完成。
你现在会呻吟着问,‘赔率是多少?’。
几率是某件事情发生与某件事情没有发生的比率(即 3/2 = 1.5)。
概率是某件事情发生的比率,是所有可能发生的事情的比率(3/5 = 0.6)。
(扎布洛茨基,2022 年)
所以如果一个事件发生的概率是. 6,那么比值比就是. 6/.4 =1.5。它告诉我们一个事件发生与不发生的几率。优势比定义为:

现在,这个比率的下限为 0。在上端,它是未定义的(当 p=1 时,在分母中被零除)。比值比可以取 0 到 0 之间的任何值,并且在上限是无界的。该比率的值为中间的 1 ,表示发生和不发生的概率T3 为. 5。小范围的赔率,从 0 到 1 ,失败的概率比成功的概率高。然后是无限的赔率范围,从 1 到无穷大,这表明成功的概率高于失败的概率。
由于不平衡的范围,为了使比值比集中在 0 左右,我们需要对比值比进行对数转换。这有助于比值比的范围在 0 附近变得对称,即从-无穷大到+无穷大。如下图所示。


对数赔率转换(图像源)
几率对数的这种转换也称为 Logit 函数,是逻辑回归的基础。通过这种变换获得的对称性提高了对数几率的可解释性,负值表示失败的几率,正值表示成功的几率更高。
请注意,函数的定义域仅从 0 延伸到 1,但实际上并未定义在 0 或 1。这是因为如果我们在 log odds 表达式中代入 p=1 ,将导致除以零,产生一个未定义的值。类似地,当代入 p=0 时,将导致计算零的对数,这同样是未定义的(Zablotski,2022)。
我们假设观察值的对数概率 y 可以表示为输入变量 x. 的线性函数

从赔率到概率
现在,我们有了对数概率方程。我们相当清楚,这必须以某种方式进行估计。然而,你现在可能会问:“但因变量是对数概率,而我需要一个简单的概率 p。我如何在没有非常复杂的计算的情况下回溯呢!”

从 log odds 到 p(图片来源:我的收藏)
别担心。这就是使用逻辑转换的原因。我们将看到,通过几个代数步骤,我们可以回到我们的好的旧的可理解的概率,p。

步骤 1 我们首先对两边取幂:

步骤 2 一些简单的代数操作(为代数有挑战的人提供详细的步骤)

LHS 的合并条款


最后,我们可以将分子和分母相除,得到:

这可以表示为我们熟悉的逻辑回归方程的函数形式,也称为 Sigmoid。

这个是我们的标准逻辑回归方程,它将一个线性回归转换为根据各种因变量得出正数的概率。
我们可以绘制逻辑回归方程,它给出一条 S 形曲线,中点为 0.5,极限值为概率 1 和 0。

逻辑回归图(图片来源:我的收藏)
在上面的图中,我画出了给定 x 的事件 y 的概率,这被称为 Sigmoid 函数。x 表示为标准化的。如您所见,概率值在图表的下端以 0 为上限,在上端以 1 为上限。中点是 0.5 的概率。
这个方程是怎么估计出来的?
既然我们理解了曲线和 sigmoid(困难的部分),我们可以简单地看一下如何估计逻辑模型。正如我们所看到的,逻辑斯谛模型是线性回归模型的非线性转换。线性模型通常通过最优化最小化误差平方和来拟合。然而,不可能通过标准的优化技术如正规方程来拟合逻辑回归,因此我们使用最大似然估计(MLE)。MLE 也是一种基于数据的方法。它试图解决这个问题:给定我们所拥有的数据,什么样的模型参数最大化了观察我们所观察到的数据的可能性。
一个简单的例子来说明基本思想:
假设我们有一个盒子,里面有蓝色和绿色的球。我们现在还不知道颜色的分布。然而,我们随机抽取 10 个替换样本,得到 8 个蓝色球和 2 个绿色球。MLE 提出的问题是:蓝球和绿球在盒子中的分布是什么,在重复的 10 轮平局中,会产生 8 个蓝球和 2 个绿球的结果?我们可以尝试使用各种基础分布来测试哪个基础分布会给出 8 个蓝色和 2 个绿色的结果。例如,两种颜色 50:50 的分布最有可能给出得到 8 个蓝色和 2 个绿色结果的非常低的概率。基础分布越接近绘制的分布,概率可能越高:例如 7 个蓝色和 3 个绿色,9 个蓝色和 1 个绿色,等等。
实际上,这就是最大似然估计对逻辑回归方程的作用。深入研究 MLE 的细节需要一篇单独的文章,所以我将把它留到下一次。然而,这篇文章应该已经为你提供了逻辑回归理论基础的直观理解。当我们必须解释我们所做的事情的基本原理以及该算法与其他算法有何不同时,理论基础在机器学习中是有用的。简而言之,机器学习的“智能”部分。
如果你喜欢这篇文章,请继续关注我关于分类评估的文章。
感谢您的阅读,并在下面告诉我您的意见!
参考文献
- 【https://en.wikipedia.org/wiki/Logistic_regression
- https://shaileydash . medium . com/understanding-the-roc-and-AUC-直观地-31ca96445c02
- https://www . stat . CMU . edu/~ cshalizi/uADA/12/lectures/ch12 . pdf
- https://data-se . netlify . app/2020/11/28/derivation-of-the-logistic-regression/#:~:text = In % 20 simple % 20 words % 3A % 20% E2 % 80% 9c take % 20 the,b%200%20%2B%20b%201%20x%20。
- https://yury-zablotski . netlify . app/post/how-logistic-regression-works/
- https://yury-zablotski . netlify . app/post/from-odds-to-probability/
- https://towards data science . com/understanding-maximum-likelihood-estimation-mle-7e 184d 3444 BD
- https://towards data science . com/probability-concepts-explained-maximum-likelihood-estimation-c7b 4342 fdb B1
理解机器学习的可解释性
原文:https://towardsdatascience.com/understanding-machine-learning-interpretability-168fd7562a1a
机器学习可解释性介绍,可解释性评估的驱动力、分类、示例和注释。

丹妮拉·奎瓦斯在 Unsplash 上的照片
T 如今,机器学习无处不在,尽管机器学习模型已经显示出很好的预测性能,并在不同的应用中取得了显著的突破,但这些机器学习模型正变得越来越复杂,反过来,它们的内部工作结构以及它们如何达到特定的结果变得不清楚,甚至对专家来说也是隐藏的,这提出了一个严重的问题:我们如何信任这些模型?
此外,机器学习在高度监管和关键领域(如刑事司法、金融服务和医疗保健)的应用要求测量机器学习模型,不仅要基于其准确性,还要基于其可解释性和透明度。
本文概述了机器学习的可解释性、驱动力、分类、可解释性方法的示例以及评估可解释性方法质量的重要性。
这篇文章结构如下:
1。驱动力
1.1 人工智能事件
1.2 公众意识和法规
2。术语
3。示例
4。评估可解释性
5。总结和结论
6。参考文献
1。驱动力
1.1 AI 事件
许多事件都是因为不完善的人工智能和机器学习而记录的,这引起了人们对我们如何信任这些自动算法的关注,早期的严重事件之一是在 2016 年 5 月 7 日,当时一名特斯拉司机在自动驾驶仪激活时被杀[1]
2018 年,优步自动驾驶汽车在车辆处于自动驾驶模式时撞死了一名行人(女性),“优步发现其自动驾驶软件在汽车的传感器检测到行人后决定不采取任何行动”[2]
2019 年 11 月,纽约州金融服务局(State Department of Financial Services)在显示出对女性客户的歧视后,对高盛(Goldman Sachs)的信用卡算法进行了调查,此前丹麦企业家兼开发商大卫·海涅迈尔·汉森(David Heinemeier Hansson)在推特上说:“我和我的妻子提交了联合纳税申报单,住在一个共有财产的州,已经结婚很长时间了。然而,苹果的黑盒算法认为我应该得到她 20 倍的信用额度”[3],同样的事件迫使亚马逊关闭了其人工智能招聘工具,该工具被发现歧视女性申请人[4]。
在刑事司法中, ProPublica 的一份报告显示,COMPAS 是美国法院用来评估被告成为累犯的可能性的案件管理和决策支持工具[15],该报告显示该软件对黑人有偏见,预测他们的风险是白人的两倍[5]。
医疗保健也不是人工智能故障的例外,许多事件的发生引起了人们对人工智能在医疗保健领域可信度的关注。
例如,新的研究表明,美国一家大医院指导数千万人护理的软件系统地给予白人患者优先于黑人患者的权利[6]
还有很多事件需要提及,你可以在这里找到更多报道:https://incidentdatabase.ai/
1.2 公众意识和法规
人工智能事件以及人工智能和机器学习在安全关键和受监管领域的使用日益增加,促使人们关注机器学习可解释性的重要性,将其作为信任机器学习模型及其结果的不可否认的要求。
在欧洲,一个重要的项目是 DARPA 的 XAI 计划,正如该计划官方网站所述,该计划旨在创建一套机器学习技术:
- 产生更多可解释的模型,同时保持高水平的学习性能(预测精度);和
- 使人类用户能够理解、适当信任和有效管理新一代人工智能伙伴[7]。
关于美国,2016 年 10 月,国家科学技术委员会技术委员会发表了一份题为“为人工智能的未来做准备”的报告,其中一项建议是,“向州政府和地方政府提供拨款以支持使用基于人工智能的系统对个人做出相应决定的联邦机构应审查拨款条款,以确保用联邦拨款购买的基于人工智能的产品或服务以足够透明的方式产生结果,并得到有效性和公平性证据的支持”[16]。
最近的法规包括欧盟于 2021 年 4 月发布的一项提案[9],题为“制定关于人工智能的协调规则(人工智能法案)并修改某些欧盟立法法案”,其中强调并强制执行不同级别的透明度,作为在欧盟市场使用和部署人工智能系统的要求。
此外,许多国家已经公布了他们自己的人工智能战略提案,包括法国[10],德国[11],葡萄牙[12],英国[13],美国[14]以及许多其他国家,人工智能系统的透明度和可解释性是人工智能未来的强制性要求。
2。术语
2.1 可解释性:
对可解释性没有一致的定义,在定义可解释性时,有必要考虑上下文和领域,但粗略地说,可解释性是用户或专家理解模型决策和结果背后的基本原理的手段。
一般来说,有两种方法可以实现可解释性[8]:
2.1.1 开发本质上可解释的模型,或者通过使用本质上可解释的模型,例如线性回归、决策树,或者通过对模型施加其他约束。
2.1.2 开发适用于已开发模型的解释方法。
2.2 模型前对比模型中对比模型后
前模型是指我们在选择和开发模型之前使用的可解释性方法,为此,它与探索性数据分析密切相关。
模型内可解释性是关于本质上可解释的模型。
后模型(Post-Hoc)指的是在模型被开发之后,试图增强模型可解释性的可解释性技术。
2.3 特定型号与不特定型号的对比
特定于模型的可解释性技术仅限于特定类型的模型,而模型不可知技术可以应用于任何模型(事后)。
2.4 本地与全球
局部可解释性方法是指解释单个预测的方法,而全局方法是指解释整体模型的方法
下图显示了总结机器学习可解释性相关分类的图表:
【道歉:在 google drive 上有思维导图的高分辨率图像,因为该图像可能不可读(质量会自动降低)】

作者图片
3。可解释性方法示例
为了更好地理解机器学习的可解释性及其重要性,让我们以一个非常简单、基本的例子来说明著名的可解释性技术之一:沙普利附加解释或简称 SHAP。
目前,只需要理解“SHAP 的目标是通过计算每个特征对预测的贡献来解释实例 x 的预测”[17]就足够了,所以一般来说,SHAP 为每个特征输出一个值,解释其重要性和对实例预测输出的影响。
首先,让我们导入必要的库(这个例子是使用 Google Colab 实现的)。
接下来,我们安装并导入 SHAP 库
对于这个例子,我已经创建了一个非常简单的工资数据集,这足以满足说明的目的,您可以在这里下载它,然后使用下一个代码单元格上传它:
接下来的几行将读取数据集文件,并向数据集添加一个性别列(这是为了创建更多的特性,以便我们可以更好地理解这个示例)。
拟合模型
在我们建立了模型之后,让我们使用 Shapley 值来理解和解释我们的模型

电池输出
从上图我们可以看到,对于例 10,对预测工资最有贡献的特征是工作经验的年限,这似乎是合理的,性别不应该影响个人的工资。
下一个代码单元将输出数据集特征的总体摘要,我们可以注意到,这里的影响特征是年经验。

电池输出
建模有偏数据集
为了理解 SHAP 如何帮助我们检测模型的偏差,我用一个有偏差的假数据集创建了一个简单的例子,该数据集代表简单的贷款批准数据,由两列组成:收入和性别。
在该数据集中,贷款批准完全取决于性别,这意味着如果申请人是男性,则贷款获得批准,否则,贷款被拒绝。如果我们拟合模型并仅基于其准确性来衡量它,我们可以认为模型非常好,但如果我们试图解释模型,我们会发现模型对女性申请人有偏见,这个简单的例子是现实世界中实际发生的情况,有许多类似的记录事件。
拟合模型
解释模型

电池输出
从上图中我们可以看到,对于 10 号申请人(女性),性别特征对贷款审批的影响最大,在这种情况下,负面影响(蓝条)导致贷款未被批准。

电池输出
与上述数字形成对比的是,这一数字表明,性别特征对 11 号申请人(男性)的贷款审批产生了积极影响

电池输出
该图总结了贷款审批数据集的整体特征重要性,从中我们可以得出结论,即使我们的模型在准确性方面表现良好,但在公平性方面却非常糟糕,对女性申请人有严重的偏见,而收入特征对贷款审批流程的最终决策没有影响,正如所述,这是现实世界中发生的问题。
4。评估可解释性技术的方法
已经发表了许多关于构建解释和解释黑盒机器学习模型的方法的研究文章,其他关于开发内在可解释模型的研究文章,但是已经完成了一些评估和评价可解释性方法质量的研究。
出于本文的需要,我们将坚持指出,对我们如何评价和评估可解释性方法的质量进行处理和研究是很重要的,迪奥戈·v·卡瓦略等人在他们的文献综述论文[8]中提出了评价可解释性方法的一些框架和公理。(我强烈推荐阅读这篇论文)。
5。总结和结论
本文介绍了机器学习的可解释性、驱动力、公共工作以及关于机器学习的使用和发展的法规的介绍性概述,可解释性分类的总结,使用 Shapley 值的示例,该示例展示了可解释性方法的重要性,最后是关于评估和评价可解释性技术的重要性的说明。
这里要总结的一点是:机器学习的可解释性是机器学习的未来所必须的,它与开发高性能的机器学习模型一样重要,这需要数据科学家、机器学习研究人员和其他在机器学习领域工作的人注意-在开发他们的模型时考虑可解释性和透明性问题。
在接下来的文章中,我们将进一步理解最近的可解释性方法,这将有助于在开发您的模型时加以考虑。
感谢您的阅读,如果您有任何问题、建议和其他疑问,请随时通过 LinkedIn 或电子邮件:salih.eihab1@gmail.com 联系我们。
6。参考文献:
[1]《卫报》,特斯拉司机在使用自动驾驶模式时发生首次致命车祸死亡(2016 年),https://www . The Guardian . com/technology/2016/jun/30/特斯拉-自动驾驶-死亡-自动驾驶-汽车-埃隆-马斯克
[2]景岛乐·若林,自动驾驶优步汽车在机器人漫游的亚利桑那州撞死行人(2018),https://www . nytimes . com/2018/03/19/technology/Uber-drivers-deadlines . html
[3]泰勒·特尔福德(Taylor Telford),Apple Card 算法引发针对高盛的性别偏见指控(2019),https://www . Washington post . com/business/2019/11/11/Apple-Card-algorithm-sparks-gender-bias-consensations-against-Goldman-Sachs/
[4]匿名。(2016)事件编号 37。在麦格雷戈,s .(编辑。)人工智能事件数据库。人工智能伙伴关系。2022 年 1 月 25 日从 incidentdatabase.ai/cite/37.取回
[5]匿名。(2016)事件编号 40。在麦格雷戈,s .(编辑。)人工智能事件数据库。人工智能伙伴关系。2022 年 1 月 25 日从 incidentdatabase.ai/cite/40.取回
[6]鲁茨曼。(2019)事件编号 124。在麦格雷戈,s .(编辑。)人工智能事件数据库。人工智能伙伴关系。2022 年 1 月 25 日从 incidentdatabase.ai/cite/124.取回
[7]马特·图雷克博士,可解释的人工智能(XAI),https://www . DARPA . mil/program/可解释的人工智能
[8]卡瓦略、迪奥戈、爱德华多·佩雷拉和海梅·卡多佐。《机器学习可解释性:关于方法和度量的调查》Electronics 8(2019)第 8 期:832。https://doi.org/10.3390/electronics8080832
[9]欧洲委员会,欧洲议会和理事会关于制定人工智能统一规则(人工智能法)和修正某些联盟法案的条例(2021 年),【https://eur-lex.europa.eu/legal-content/EN/TXT/? qid = 1623335154975&uri = CELEX % 3a 52021 PC 0206
[10]法国 AI 战略报告(2018),https://knowledge 4 policy . EC . Europa . eu/AI-watch/France-AI-Strategy-Report _ en
[11]德国 AI 战略报告(2018),https://knowledge 4 policy . EC . Europa . eu/AI-watch/Germany-AI-Strategy-Report _ en
[12]葡萄牙 AI 战略报告(2019),https://knowledge 4 policy . EC . Europa . eu/AI-watch/Portugal-AI-Strategy-Report _ en
[13]英国 AI 战略报告(2018),https://knowledge 4 policy . EC . Europa . eu/AI-watch/United-Kingdom-AI-Strategy-Report _ en
[14]国家安全委员会人工智能(2021 年),https://www . nscai . gov/WP-content/uploads/2021/03/Full-Report-Digital-1 . pdf
[15] COMPAS(软件),https://en . Wikipedia . org/wiki/COMPAS _(软件)
[16]美国国家科学技术委员会技术委员会,为人工智能的未来做准备(2016),https://obamawhitehouse . archives . gov/sites/default/files/white house _ files/microsites/ostp/NSTC/preparating _ FOR _ THE _ FUTURE _ OF _ ai . pdf
[17] Christoph Molnar,可解释的机器学习,使黑盒模型可解释的指南(2022 年),https://christophm.github.io/interpretable-ml-book/
借助《哈利·波特》理解 MapReduce
原文:https://towardsdatascience.com/understanding-mapreduce-with-the-help-of-harry-potter-5b0ae89cc88
通过一个简单的例子从头开始学习 MapReduce

伊恩·杜利在 Unsplash 上拍摄的照片
MapReduce 是一种允许并行处理大型数据集的算法,即同时在多台计算机上处理。这大大加快了大型数据集的查询速度。
我们用 MapReduce 做什么?
MapReduce 最初是由 Google 推出的,用于高效地查询大量的搜索结果。然而,该算法真正出名是因为它在 Hadoop 框架中的使用。它在 Hadoop 分布式文件系统(HDFS)中存储大量数据,并使用 MapReduce 来查询或聚合 TB 或 Pb 范围内的信息。
假设我们已经将《哈利·波特》小说的所有部分以 PDF 格式存储在 Hadoop 中,现在想要统计书中出现的单个单词。这是一个经典的任务,分解成一个映射函数和一个归约函数可以帮助我们。
以前是怎么做的?
在有可能将如此复杂的查询拆分到整个计算机集群中并并行计算它们之前,人们被迫一个接一个地运行完整的数据集。当然,数据集越大,查询时间越长。
假设我们已经在 Python 列表中逐字记录了哈利波特文本:
我们可以通过使用 For 循环遍历这个列表,并将每个单词从 Python“Collections”模块加载到“Counter”中,来计算出现的单词数。然后,这个函数为我们进行单词计数,并输出十个最常用的单词。使用 Python 模块“Time”,我们可以显示我们的计算机执行这个函数花了多长时间。
据网站 wordcounter.io 统计,哈利波特第一部共有 76944 个单词。由于我们的例句只有 20 个单词(包括句号),这意味着我们必须重复例句大约 3850 次(76944/20 ~ 3847)才能得到一个和哈利波特第一本书一样多的单词列表:
我们的函数需要 64 毫秒来遍历第一部分的所有单词,并计算它们出现的频率。如果我们对所有总共 3397170 个单词的哈利波特书籍进行同样的查询(来源: wordcounter.io ),总共需要 2.4 秒。
这个查询需要相对较长的时间,对于较大的数据集,自然会变得越来越长。加快功能执行的唯一方法是为计算机配备更强大的处理器(CPU),即改进其硬件。当一个人试图通过改进设备的硬件来加速算法的执行时,这被称为垂直缩放。
MapReduce 算法是如何工作的?
在 MapReduce 的帮助下,通过将任务拆分成更小的子任务,可以显著加快这样的查询。这反过来又有一个优点,即子任务可以在许多不同的计算机之间划分和执行。这意味着我们不必改进单个设备的硬件,而是可以使用许多功能相对较弱的计算机,并且仍然可以减少查询时间。这种方法被称为水平缩放。
让我们回到我们的例子:到目前为止,我们已经形象地以这样一种方式进行,我们阅读了所有的哈利波特书籍,并简单地在我们阅读的每个单词后将单个单词的计数表扩展一个计数。这样做的问题是我们不能并行化这种方法。假设有第二个人想要帮助我们,她不能这样做,因为她需要我们正在处理的计数表来继续。只要她没有,就支持不了。
然而,她可以支持我们,从哈利波特系列的第二部分开始,为第二本书创建一个单独的清单。最后,我们可以合并所有单独的计数表,例如,将单词“Harry”在所有计数表上的出现频率相加。

作者照片
这也使得让一个人在每本哈利波特书上工作相对容易横向扩展任务。如果我们想工作得更快,我们也可以让更多的人参与进来,让每个人都做一章。最后,我们只需要把每个人的所有结果结合起来,就可以得出一个整体的结果。
Python 中的 MapReduce 示例
总之,我们需要两个函数一个映射器和一个缩减器来用 Python 编码这种方法。我们以这样一种方式定义映射器,它为接收到的每个单词返回一个字典,以单词为关键字,值为 1:
与我们的示例类似,映射器返回一个计数列表,上面写着:“传递给我的单词恰好出现一次”。在第二步中,reducer 将所有单独的计数表合并成一个大的总计数表。它区分了两种情况:如果传递给它的单词已经出现在它的大计数列表中,那么它只是在相应的行中添加一个破折号。如果新单词还没有出现在列表中,reducer 只需在大计数列表中添加一个新行。
如果我们合并这两个子任务,与之前相同的查询只需要 1.4 秒:
因此,使用 MapReduce 算法,我们能够在不进行任何水平或垂直缩放的情况下,将所有哈利波特书籍的查询时间减少一半以上。但是,如果 1.4 秒的查询时间对于我们的应用程序来说仍然太长,我们可以简单地任意拆分单词列表,并在不同的计算机上并行运行映射器,以进一步加快这个过程。没有 MapReduce 算法,这是不可能的。
MapReduce 的缺点
我们的例子令人印象深刻地表明,我们可以使用 MapReduce 更快地查询大量数据,同时为水平缩放准备算法。然而,MapReduce 并不总是可以使用,或者根据使用情况的不同也会带来一些缺点:
- 某些查询无法引入 MapReduce 架构。
- 地图功能彼此独立运行。因此,这些进程不可能相互通信。
- 分布式系统比单台计算机更难管理和控制。因此,应该仔细考虑是否真的需要计算集群。Kubernetes 软件工具可用于控制计算机集群。
这是你应该带走的东西
- MapReduce 是一种允许并行快速处理大型数据集的算法。
- MapReduce 算法将一个大的查询拆分成几个小的子任务,然后可以在不同的计算机上分发和处理这些子任务。
- 不是每个应用程序都可以转换成 MapReduce 方案,所以有时甚至不可能使用这种算法。
如果你喜欢我的作品,请在这里订阅https://medium.com/subscribe/@niklas_lang或者查看我的网站* 数据大本营 !还有,medium 允许你每月免费阅读 3 篇 。如果你想让无限制地访问我的文章和数以千计的精彩文章,请不要犹豫,通过点击我的推荐链接:【https://medium.com/@niklas_lang/membership】每月花$5获得会员资格*
**https://medium.com/@niklas_lang/what-are-deepfakes-and-how-do-you-recognize-them-f9ab1a143456 https://medium.com/@niklas_lang/what-are-convolutional-neural-networks-cnn-faf948b5a98a https://medium.com/@niklas_lang/intuitive-guide-to-artificial-neural-networks-5a2925ea3fa2 **
理解马尔可夫链
原文:https://towardsdatascience.com/understanding-markov-chains-cbc186d30649
以及它们与神经科学、人工智能和俄罗斯诗歌的联系
“我的梦,我的梦!他们的甜蜜变成了什么?我的青春到底变成了什么?”
――亚历山大·普希金,尤金·奥涅金
引用一首 19 世纪的俄罗斯诗歌来开始一篇关于马尔可夫链(也称为马尔可夫过程)的文章可能看起来很奇怪,但在 20 世纪初概率论的一个全新领域的发展之初,这两者之间有一种令人惊讶的联系。
今天,马尔可夫链无处不在。无法想象没有它们的人工智能,应用范围从自然语言处理、语音识别、网络搜索和蛋白质发现到自动驾驶汽车。
但是在我们深入研究马尔可夫链和俄罗斯诗歌之间的联系之前,我想从随机过程的重要性开始,没有比 随机漫步的概念更好的地方了。
正如科学中经常出现的情况一样,想法似乎悬在空中,经常由几个思想家在几年甚至几个月的时间里独立发现。类似的事情发生在 19 世纪初,当时科学家越来越意识到 随机过程的重要性,如随机游走 。在几年的时间里,随机漫步出现在蚊子数量(它们与疾病传播相关)、分子的布朗运动(爱因斯坦的奇异之年的一部分)、声学和金融市场的背景中。
正式开始,随机漫步是一个 随机过程 ,它描述了一个主题在一个数学空间中的 路径 ,这个数学空间可以由整数、二维或更高维的欧几里德空间以及许多其他结构组成。从初始状态开始,我们通过在每一步 将从概率分布中抽取的 个随机变量相加来进行 n 步:

x 是数学空间中的位置,n 是步数,Zj 是服从一定分布的随机变量。
我们可以用一个简单直观的例子来说明这一点。想象一下,你发现自己在一个无限大的房间中间,开始抛硬币。每次你得到正面,你就向左走一步。每次你得到反面,你就向右走一步。扔 100 次硬币后你会在哪里结束?我们可以很容易地用 Python 实现这一点,并画出流程的五个实例。
def random_walk(x0, p, steps):
random_walk=[]
random_walk.append(x0)
x=x0
for i in range(steps):
s=np.random.binomial(1, p, size=None)
if s == 1:
x+=1
else:
x+=-1
random_walk.append(x)
return random_walk

简单随机漫步的五个实例。
很明显,在 100 次抛硬币后,你的终点可能会有很大的不同,尽管在每一步你都面临相同的向左或向右的 50/50 概率。
虽然这个过程是由偶然性 主导的,但有趣的是,就像变魔术一样,大数定律开始发挥作用,100 个时间步长后最终状态的位置将遵循一种近似高斯正态的分布,均值为零, 方差与步长数的平方根 成比例。通过绘制 500 次随机行走的样本的所有最终位置的直方图,并对其拟合高斯曲线,可以很容易地观察到这一点:

这只是一个简单的例子,我们从其中提取的分布和我们穿过的空间可以呈现各种形状和形式,这取决于上下文和应用。
虽然对我们来说,随机漫步在概念上看起来非常直观,但科学家们花了很长时间才明白,如果我们想要对我们的理解世界进行建模, 随机性起着重要的作用: 随机漫步只是在 1905 年由卡尔·皮尔逊提出的。
我们可能很难接受这样一个事实,即没有什么比机会更能解释这些大相径庭的结果了,随机过程的属性对我们的大脑来说仍然是一个挑战,我们的大脑一直在寻找一个解释(t 他的文章探讨了我们对随机性的厌恶,以及反过来故意在我们的生活中注入噪声如何能使我们受益)。

白噪声。(来自 Jorge Stolfi, BY-SA 3.0 )
毕竟,牛顿力学被视为结束随机性统治的一种手段,对玻尔兹曼及其从统计角度看待热力学的提议的强烈反对,充分说明了我们对让太多机会进入我们的理论感到不适。但正如我所提到的,随机漫步现在在许多科学背景下得到应用,尽管我们假设物理定律在本质上是确定性的(撇开量子效应),但对系统的随机/统计描述往往比确定性描述更有帮助,例如,当涉及疟疾苍蝇的飞行、液体中分子的运动或我们大脑中的过程时。这种观念上的转变至关重要,直到今天仍然很重要:丹尼尔·卡尼曼的新书《噪音》给出了许多令人不安的好例子,说明我们仍然很难认识到噪音、随机过程在我们日常生活的许多方面发挥的重要作用,以及错误的判断如何让我们损失数十亿美元,并最终在法庭系统中摧毁生命。
现在我们对什么是随机漫步有了基本的了解,我们可以回到俄罗斯诗歌。
假设我们对语言和诗歌一无所知。然后我们可以对尤金·奥涅金的诗句采取最抽象的方法,并承认诗歌构成了一系列相互联系的“事件”,其中每个字母都构成了一个事件。发现它们内部结构的一种方法是计算事件之间的“转移概率”。文本中可能发生的一种非常简单的事件是辅音和元音之间的转换。
这为我们提供了 4 个可能事件的列表:
辅音→辅音
辅音→元音
元音→元音
元音→辅音
马尔可夫链之父马尔可夫提出了一个问题,即这些概率对于像尤金·奥涅金这样的文本作品来说是什么样的。他去掉了所有的标点符号,仔细阅读了这本著名的俄罗斯诗集《T4》的前 20000 个字母。幸运的是,我们不再需要手动操作,可以编写一个简单的程序来分析上面的引用:
##define lists of consonants and vowels
consonants=['b','c','d','f','g','h','i','j','k','l','m','n','p','q','r','s','t','v','w','x','y','z']
vowels=['a','e','i','o','u']###text with all interpunctuation removedtext="mydreamsmydreamswhathasbecomeoftheirsweetnesswhatindeedhasbecomeofmyyouth"## this function counts all 4 types of transitions and normalizes them by the length of the text:def simple_markov_chain(text):
cv=0
cc=0
vv=0
vc=0
for i in range(len(text)-1):
if text[i] in consonants and text[i+1] in consonants:
cc+=1
if text[i] in consonants and text[i+1] in vowels:
cv+=1
if text[i] in vowels and text[i+1] in vowels:
vv+=1
if text[i] in vowels and text[i+1] in consonants:
vc+=1
return cc/(len(text)-1), vv/(len(text)-1), cv/(len(text)-1), vc/(len(text)-1)
“我的梦,我的梦!他们的甜蜜变成了什么?我的青春到底变成了什么?”
这一小段文字给出了以下转移概率:
辅音→辅音:0.43
辅音→元音:0.11
元音→元音:0.25
元音→辅音:0.26
我们现在可以把这些转换重新写成一个简单的图表,它构成了一个马尔可夫链:

转移概率,用马尔可夫链表示。从图中每个节点流出的总概率被归一化为 1。
这只是基于这首诗的一个小样本,但 Markov 观察到,这些转移概率实际上在不同作者的不同文本之间可能不同,因此编码了一些关于文本的隐藏概率结构的信息。
有趣的是,我们可以为不同的语言设置这些概率,并比较哪种结构是保守的或不同的,这些问题是(计算)语言学家非常感兴趣的。
将文本表示为马尔可夫链的一个好处是,它摆脱了某些不切实际的独立性假设,例如,您可以通过完全独立地绘制字母来获得这些假设:那么辅音出现的概率大约为 68%(根据此示例估计),但是如果当前状态是元音,则该概率仅为 50%,而对于辅音,则为 80%。
因此,上面的简单马尔可夫链以更现实的方式吸收了真实文本的属性。
退一步讲,类似的依赖性假设在其他情况下也有意义,例如天气:经常连续几天下雨,所以一旦你观察到下雨,另一天下雨的可能性比一天下雨的可能性大。

认识到世界上的许多过程隐藏着一种可以用概率语言表达的结构是非常深刻的。现代神经科学理论,如贝叶斯大脑假说将这种概率性的预测框架置于大脑绘制其对世界的理解能力的核心。我们的大脑必须不断地在具有部分信息的环境中行动,因此它们没有对正在发生的一切进行建模的奢侈,而是必须假设环境的重要部分由噪声主导,或者至少是我们无法预测的因素。所以规划本身就变成了一个概率过程。就像在随机漫步的例子中,我们都不知道我们会在哪里结束。沿着这条路的每一步,我们都不知道太多,所以我们在做决定时必须考虑不确定性。
概率模型的另一个非常有用的地方是,它们可以用来构建生成模型。顾名思义,生成模型可以在学习模型后生成全新的数据,因此也可以用于预测系统的未来状态。
因此,它们找到了进入各种应用程序的方法,例如图像生成、文本生成、音频生成等等(我在我的文章如何让计算机做梦中讨论了这一点)。
对于我们的马尔可夫链的玩具示例,我们可以实现一个简单的生成模型 ,它通过以基线概率(32%和 68%)对初始状态(元音或辅音)进行采样来预测潜在的文本 ,然后生成一系列连续的状态,就像我们从前面介绍的随机漫步中采样一样:
import randomconsonants=['b','c','d','f','g','h','i','j','k','l','m','n','p','q','r','s','t','v','w','x','y','z']
vowels=['a','e','i','o','u']def markov(iterations):
text=[]
s0=np.random.binomial(1, 0.32, size=None)
if s0 == 1:
l0=random.choice(vowels)
text.append(l0)
else:
l0=random.choice(consonants)
text.append(l0)
for i in range(iterations):
if s0 == 1:
l=random.choice(vowels)
text.append(l)
s0=np.random.binomial(1, 0.49, size=None)
else:
l=random.choice(consonants)
text.append(l)
s0=np.random.binomial(1, 0.2,size=None)
string=''.join(text)
return string
对 100 个字母进行采样,我们得到了这些行:
iewnjgfqxqqqlyoqbuokwpgaavonozbylhjdnjyvtqaakxgmqpiigjauzwxznqhqkhryiiiaxcfblywigbjpshcnnhviqkfrpwai。
-一个简单的马尔可夫链
不太像俄罗斯诗歌。
虽然原则上我们现在有能力生成随机文本,但应该清楚的是,虽然一个非常简单的马尔可夫链可以模拟文本中一些重要的相关性假设,但上面的图表过于简单,实际上无法产生有意义的文本。例如,以相同的概率对所有字母进行抽样是非常不现实的,并且不会产生许多格式良好的单词。
另一个 极限假设就是所谓的马氏性 。
这告诉我们,为了预测 系统 的未来状态,我们只需要知道 系统 的当前状态。系统的过去或未来都无关紧要。重要的只是状态之间的转换。
然而,国家的构成有待讨论。没有人强迫我们在单元音和辅音的层面上分析语言。虽然双辅音很常见,但连续三个元音非常罕见,所以两个元音后有一个元音的可能性应该小得多。
因此,我们可以将图表扩展到更大的字母序列(例如,元音元音→元音元音元音,元音元音→辅音元音……),从而为马尔可夫链添加更多可能的状态。然而,这是以链上状态数的指数增长为代价的。
正如在许多计算机科学应用中一样,这导致了模型大小、计算负担和模型质量之间的权衡。为了更接近语言的实际版本,例如,我们可以将每个可能的单词与每个其他可能的单词连接起来,并指定它们之间的转换概率,或者查看不同字母之间的转换概率。
我有一个朋友是我的好朋友,我已经和你一起工作了几年了。所以我希望你一切都好,我希望你一切都好。
-我的手机
“自动更正”是我们大多数人熟悉的伴侣。这也是一个很好的例子,说明根据输入预测转移概率和最可能的单词在日常生活中是多么地有用,同时我们不会给明智的长期预测带来太多负担(因此,一旦你让自动更正泛滥,就会出现荒谬的无意义但语法正确的愚蠢句子)。
说到马尔可夫链的应用,天空是无限的。谷歌著名的 PageRank 算法依赖于马尔可夫链,将互联网上大约 400 亿个网页相互连接起来,为每个转变分配概率。
如果我们希望真实地模拟语言,我们需要注意包含足够的长期依赖,毕竟,这是明智的、类似人类的文本的面包和黄油(像 GPT-3 这样的最先进的模型与变形金刚一起工作,以更经济地表达这些依赖)。然而,也有更现实版本的马尔可夫链用于生成语言。例如,Markovify 是一个 Python 包,它允许你读入任意文本文件并生成新文本(本文为莎翁悲剧解释了这一点)。
马尔可夫链是有用的工具,在人工智能和工程的许多地方都有应用。但此外,我认为它们也是有用的概念框架,可以帮助我们以简单直观的方式理解现实背后的概率结构,并让我们感受到扩大这种概率结构是如何导致强大的世界模型的。
了解均值漂移聚类和 Python 实现
无监督学习
在这篇文章中,我简要介绍了无监督学习方法的概念,均值漂移聚类,以及它在 Python 中的实现

均值偏移聚类(图片由作者提供)
均值漂移是一种非监督学习算法,主要用于聚类。它广泛用于现实世界的数据分析(例如,图像分割),因为它是非参数的,并且不需要特征空间中任何预定义的聚类形状。
简单来说,“均值漂移”等于以迭代的方式“向均值漂移”。在该算法中,每个数据点都在逐步向“区域均值”移动,每个点的最终目的地位置代表它所属的聚类。
在这篇文章中,我将简要介绍如何理解这个算法以及 Python 中的实现。希望这篇文章有帮助!
什么意思?
因此,根据上面对均值漂移算法的简要描述,您可能已经注意到,在没有进一步解释的情况下,有几个术语仍然令人困惑。第一个是我们所指的“意思”。
如果我们看一个具有以下两个特征的样本数据集,

样本数据集的平均点(红色)。(图片由作者提供)
通过计算 feature_1 的平均值和 feature_2 的平均值,我们可以轻松定位整个数据集的“中间点”(上图中的红色点)。注意,这里的“平均点”分别由特征 _1 和特征 _2 的算术平均值来定义,因为它是基于对所有点的相等权重来计算的,

算术平均值(图片由作者提供)
其中 M 表示平均值,n 是样本大小,x_i 是数据点的一个特征(特征 1 或特征 2)。
有时给每样东西同等的权重是不合理的,我们也可以计算加权平均值,

加权平均值(图片由作者提供)
其中 w_i 是 x_i 的权重,它与点之间的欧氏距离有关。均值漂移算法中最广泛使用的权函数是平坦权函数,

平面权重函数(图片由作者提供)
其中 d 是任意数据点到当前调查点的距离,R 是以调查点为圆心的圆的半径。
例如,如果我们想要计算点 O 周围区域的加权平均值,则可以将平坦加权函数理解为以 O 为中心、半径为 r 的硬边界圆。圆内的所有数据都将被计算,而圆外的任何数据点都将被忽略。

平坦加权函数的加权平均值(图片由作者提供)
这有点像我们站在一个局部点(中心点 O)上,看不到整个画面,但被限制在一个局部区域来计算平均值。在这里,我们看到红点是调查区域的加权平均值,我们发现它往往位于点密度较高的区域。
我们也可以用高斯函数代替平坦权函数,

高斯权重函数(图片由作者提供)
其中 d 仍然是中心点到任何其他点之间的距离,这里的σ是一个参数,用于调整权重随着距离的增加而降低的速度。
其思想类似于平权函数,一个点离圆心越近,在均值计算中权重越高。为了实现的简单性和清楚的物理意义,均值漂移算法通常使用平权函数来计算均值。
我们上面计算的局部平均点在某种程度上代表了在特定局部区域中具有最大点密度的位置。你可能已经注意到,上图中黑色圆圈 R 的半径对于定义局部区域非常重要。实际上,半径是 mean shift 算法中唯一的参数,称为“带宽”。
到目前为止,我想你已经理解了当我们研究给定带宽参数的一个数据点时意味着什么。
为什么转变?
我在理解均值漂移算法时遇到的一个最大的问题是“为什么点要向均值漂移??“应用这种方法时,我们是否改变了原始数据集?
实际上,不,我们没有对原始数据做任何改变。“转移”只是描述了我们如何标记每个数据点的过程。相似的点逐渐“移动”到它们所属的聚类的质心,以便被如此标记。
让我们看一个 2-D 玩具数据集,它被设计成有三个集群。

具有三个集群的玩具数据集(图片由作者提供)
在 mean shift 算法中,每个点在每一步中通过向其局部区域的加权平均值移动来试图找到其组。每个点的目的地将是该点所属的数据聚类的质心。然后,具有相同目的地点的所有数据点可以用相同的聚类来标记。

玩具数据集,包含三个具有移动轨迹的集群(图片由作者提供)
上面的图显示了标注的结果,其中黑色的大圆圈表示每组数据点的最终目的地,箭头大致显示了一些移动路径的轨迹。
因此,您会看到数据并未更改,只是标记了其集群 id。
怎么换挡?
让我们以一个数据点为例来说明这种转变是如何进行的。假设我们有一个点位于右下方聚类的边缘(下图中的黑点)。

均值偏移中的一个点的步骤 1(图片由作者提供)
在带宽参数定义的局部区域内,计算 feature_1 和 feature_2 的平均值后,我们得到了平均点的位置(如上图红点所示)。然后圆心从黑点移到红点。
接下来,在新的点位置重复上述过程,如下所示。

均值偏移中的一个点的步骤 2(图片由作者提供)
经过足够长时间的迭代或黑色圆圈内的点数不再增加(收敛)后,原始数据点到达其目的地,即其所属聚类的质心。

均值偏移中一个点的第 N 步(图片由作者提供)
您可能已经注意到,如果对每个数据点都执行这一过程,计算将会非常多余。因此,在实践中,当这些移动的圆重叠时,只有包含最多点的圆被保留。
由于均值漂移聚类只有一个参数,即带宽(或某些情况下的窗口大小),因此在为其选择合适的值时需要格外小心。
你可能已经注意到,局部区域越大,局部平均点就越接近全局平均点。如果对于所研究的每个数据点,局部区域都非常大,那么所有的“局部平均点”实际上都位于“全局平均点”的位置。

超大带宽均值漂移(图片由作者提供)
换句话说,超大局部区域设置可以使我们看不到数据集的局部结构。
如果我们有一个非常小的带宽,将会有更多的无意义簇,因为我们将平均值计算限制在一个非常小的局部区域。

超小带宽的均值漂移(图片由作者提供)
因此,在建模时,最好对带宽有个概念。然而,如果我们没有关于数据的先验知识,我们仍然可以根据从整个数据集随机采样的成对距离来估计带宽。
用 Python 实现
得益于 sklearn 包,均值漂移聚类的实现相对容易。下面的代码显示了如何估计带宽并使用估计的参数进行聚类。
bandwidth = estimate_bandwidth(X, quantile=0.3, n_samples=300)ms = MeanShift(bandwidth=bandwidth)
ms.fit(X)
为了从分类结果中提取数据点的标签,我们可以这样做,
labels = ms.labels_
就是这样!希望文章有帮助!
如果你喜欢看我的文章,请订阅我的媒体账号。
参考资料:
https://scikit-learn.org/stable/auto_examples/cluster/plot_mean_shift.html#sphx-glr-auto-examples-cluster-plot-mean-shift-py https://en.wikipedia.org/wiki/Mean_shift
理解元学习者
原文:https://towardsdatascience.com/understanding-meta-learners-8a9c1e340832
因果数据科学
如何使用机器学习来估计异质治疗效果

封面,作者图片
在许多情况下,我们不仅对估计因果效应感兴趣,还对这种效应对于不同用户是否不同感兴趣。我们可能有兴趣了解一种药物对不同年龄的人是否有不同的副作用。或者,我们可能有兴趣了解某个广告活动在某些地理区域是否特别有效。
这一知识至关重要,因为它让我们能够有针对性地进行治疗。如果一种药物对儿童有严重的副作用,我们可能会限制它只在成人中销售。或者,如果一个广告活动只在说英语的国家有效,就不值得在其他地方展示。
在这篇博文中,我们将探索一些方法来揭示治疗效果的异质性。特别是,我们将探索利用机器学习算法灵活性的方法。
例子
假设我们是一家公司,有兴趣了解新的高级功能增加了多少收入。特别是,我们知道不同年龄的用户有不同的消费态度,我们怀疑超值功能的影响也可能因用户年龄而异。
该信息可能非常重要,例如针对或折扣设计的广告。如果我们发现高级功能增加了一组特定用户的收入,我们可能希望将广告瞄准该组用户或向他们提供个性化折扣。
为了了解高级功能对收入的影响,运行一个 AB 测试 ,我们在测试样本中随机给予 10%的用户访问高级功能的权限。这项功能很贵,我们负担不起免费提供给更多用户。希望 10%的治疗概率足够了。
我们使用来自[src.dgp](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/dgp.py)的数据生成过程dgp_premium()生成模拟数据。我还从[src.utils](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/utils.py)引进了一些绘图函数和库。
from src.utils import *
from src.dgp import dgp_premiumdgp = dgp_premium()
df = dgp.generate_data(seed=5)
df.head()

数据快照,图片由作者提供
我们有 300 名用户的数据,我们观察他们生成的revenue,以及他们是否被赋予了premium特性。此外,我们还记录用户的age。
为了了解随机化是否有效,我们使用优步[causalml](https://causalml.readthedocs.io/)软件包中的create_table_one函数制作了一个协变量平衡表,其中包含了我们在治疗组和对照组中可观察特征的平均值。顾名思义,这应该永远是你在因果推断分析中呈现的第一张表。
from causalml.match import create_table_one
create_table_one(df, 'premium', ['age', 'revenue'])

平衡表,作者图片
大多数用户在控制组中,只有 31 个用户获得了高级功能。平均age在各组之间具有可比性(标准化平均差异,SMD < 0.1),而高级功能似乎平均为每个用户增加了revenue2.59 美元。
premium功能的效果是否因用户age而异?
一个简单的方法可以是在premium和年龄的完全交互上回归revenue。
linear_model = smf.ols('revenue ~ premium * age', data=df).fit()
linear_model.summary().tables[1]

线性回归结果,图片由作者提供
相互作用系数接近于零,不显著。似乎没有premium对age的微分作用。但这是真的吗?相互作用系数仅反映线性关系。如果关系是非线性怎么办?
我们可以通过直接绘制原始数据来检查。我们通过age绘制revenue,在premium用户和非高级用户之间分割数据。
sns.scatterplot(data=df, x='age', y='revenue', hue='premium', s=40);

原始数据,作者图片
从原始数据来看,似乎 30 岁到 50 岁之间的人一般来说revenue更高,而premium对 35 岁到 45 岁之间的人有特别强的影响。
我们可以想象按年龄划分的治疗和未治疗的估计收入。
我们首先计算有( μ̂₁ )和没有premium特征( μ̂₀ )的预测收入,并将它们与原始数据一起绘制出来。
df['mu0_hat'] = linear_model.predict(df.assign(premium=0))
df['mu1_hat'] = linear_model.predict(df.assign(premium=1))
plot_TE(df)

带有线性估计的原始数据,按作者分类的图像
正如我们所见,橙色线高于蓝色线,表明premium对revenue有积极影响。然而,这两条线基本上是平行的,表明治疗效果没有异质性。
能不能再精确一点?有没有一种方法可以灵活地估计这种治疗异质性,而不需要假设函数形式?
答案是是的!我们可以使用机器学习方法灵活估计异构治疗效果。特别是,我们将考察由孔兹尔,塞孔,,于,(2019) 介绍的三种常用方法:
- s-学习者
- 网络学习者
- X-learner
环境
我们假设对于一组主题 i=1,…,n ,我们观察到一个元组 (Xᵢ,Dᵢ,Yᵢ) 包括
- 一个治疗任务 Dᵢ∈{0,1} (
premium) - 一个回应 Yᵢ∈ℝ (
revenue) - 一个特征向量 Xᵢ∈ℝⁿ (
age)
我们感兴趣的是估计平均治疗效果。

平均治疗效果,图片由作者提供
其中yᵢ⁽ᵈ⁾t41】表示个体 i 在处理状态 d 下的潜在结果。我们还做了以下假设。
假设 1:未发现(或可忽略,或可观的选择)

无根据假设,作者的图像
即以可观察的特征 X 为条件,处理分配 D 几乎是随机的。我们实际假设的是,没有我们没有观察到的其他特征会影响用户是否获得premium功能和他们的revenue。这是一个强假设,我们观察到的个人特征越多,这个假设就越有可能得到满足。
假设 2:稳定单位治疗值(SUTVA)

萨特瓦假设,作者图片
即潜在的结果不取决于治疗状态。在我们的例子中,我们排除了另一个用户使用premium功能可能会影响我的premium对revenue的影响。违反 SUTVA 的最常见设置是存在网络效应:我的一个朋友使用社交网络增加了我使用它的效用。
s-学习者
最简单的元算法是单一学习器或 S-学习器。为了构建 S-learner 估计器,我们为所有观察值拟合了一个单一模型 μ 。

s-学习者反应函数,图片由作者提供
估计量由处理前后的预测值之差给出,分别为 d=1 和 d=0 。

s-学习者评估者,作者图片
让我们使用 决策树回归 模型来构建 S-learner,使用[sklearn](https://scikit-learn.org/)包中的DecisionTreeRegressor函数。我不会在这里详细介绍决策树,但我只想说,它是一种非参数估计器,使用训练数据将状态空间(在我们的情况下为premium和age)分成多个块,并预测结果(在我们的情况下为revenue)作为每个块内的平均值。
from sklearn.tree import DecisionTreeRegressor
model = DecisionTreeRegressor(min_impurity_decrease=0.001)
S_learner(dgp, model, y="revenue", D="premium", X=["age"])

估计的和真实的治疗效果,图片由作者提供
该图描绘了数据以及响应函数 μ̂(x,1) 和 μ̂(x,0) 。我还用灰色标出了真实反应函数之间的区域:真实的治疗效果。
正如我们所看到的,S-learner 足够灵活,能够理解治疗组和对照组之间的水平存在差异(我们有两条线)。它还很好地捕捉了对照组【μ̂(x,1】的响应函数,但是不太好地捕捉了治疗组【μ̂(x,1】的控制函数。
S-learner 的问题是它正在学习一个单一模型,所以我们不得不希望该模型揭示了治疗 D 中的异质性,但事实可能并非如此。此外,如果模型由于 X 的高维度而被严重正则化,则可能无法恢复任何治疗效果。例如,对于决策树,我们可能不会在治疗变量 D 上分裂。
网络学习者
为了构建双学习器或 T 学习器估计器,我们装配了两个不同的模型,一个用于处理单元,一个用于控制单元。

t-学习者反应函数,作者图片
估计量由两个模型的预测值之差给出。

T-learner 估算器,图片由作者提供
我们像以前一样使用决策树回归模型,但是这一次,我们为治疗组和对照组拟合了两个独立的决策树。
T_learner(dgp, model, y="revenue", D="premium", X=["age"])

真实和估计的治疗效果,图片由作者提供
正如我们所见,T 型学习者比 S 型学习者更加灵活,因为它适合两种不同的模式。对照组的反应函数 μ̂ ⁽⁰⁾( x 仍然非常准确,而治疗组的反应函数 μ̂ ⁽ ⁾( x 比以前更加灵活。
现在的问题是我们只使用了每个预测问题的一小部分数据,而 S-learner 使用了所有的数据。通过拟合两个独立的模型,我们丢失了一些信息。此外,通过使用两个不同的模型,我们可能会在没有异质性的地方得到异质性。例如,使用决策树,即使数据生成过程是相同的,我们也可能得到不同样本的不同分裂。
X-learner
交叉学习者或 X-学习者估算器是 T-学习者估算器的扩展。它是通过以下方式构建的:
- 对于 T-learner,分别使用处理单元和控制单元计算μ̂⁽⁾(x 和 μ̂ ⁽⁰⁾( x 的独立模型
- 计算中间δ函数,如下所示

中间 delta 函数,图片由作者提供
3.从 X 预测δ,从处理单元计算 τ̂ ⁽ ⁾( x ,从控制单元计算 τ̂ ⁽⁰⁾( x
4.估计 倾向得分 ,即被治疗的概率

倾向得分,按作者分类的图像
5.计算治疗效果

X-learner 估算器,图片由作者提供
为了更好地理解 X-learner 是如何工作的,我们想像以前一样用画出响应函数。然而,该方法不直接依赖于响应函数。我们还能恢复出伪响应函数吗?是啊!
首先,我们可以将治疗效果改写为

X-learner 估计器分解,按作者分类的图像
因此 X 学习者估计的伪响应函数是

x-学习者伪响应函数,图片由作者提供
如我们所见,X-learner 将真实值 Yᵢ ⁽ᵈ⁾与估计值 μ̂ᵢ ⁽ᵈ⁾ (x) 通过 倾向得分【eᵢ(x】加权,即估计的治疗概率。
什么意思?这意味着如果对于一些可观测量,我们可以明确区分治疗组和对照组,那么控制反应函数 μ̂ᵢ ⁽ᵈ⁾将获得大部分权重。相反,如果这两个群体无法区分,那么实际的结果 Yᵢ和⁽ᵈ⁾将会得到大部分的权重。
为了说明这种方法,我将使用KNeighborsRegressor函数,通过使用最近的观测值来逼近 Yᵢ ⁽ᵈ⁾,从而构建伪响应函数。我使用LogisticRegressionCV函数通过逻辑回归估计倾向得分。
X_learner(df, model, y="revenue", D="premium", X=["age"])

真实和估计的治疗效果,图片由作者提供
从这个图表中我们可以清楚地看到, X-learner 的主要优势在于它使响应函数的灵活性适应环境。在我们有大量数据的状态空间区域(对照组),它主要使用估计的响应函数,在数据很少的状态空间区域(治疗组),它使用观察值本身。
结论
在这篇文章中,我们已经看到了由孔兹尔,,于,(2019) 介绍的不同估计器,它们利用灵活的机器学习算法来估计异质治疗效果。估计量在复杂程度上有所不同:S-learner 适合包含治疗指标作为协变量的单个估计量。T-learner 适用于治疗组和对照组的两个独立的估计量。最后,X-learner 是 T-learner 的扩展,它允许不同程度的灵活性,这取决于治疗组和对照组之间可用的数据量。
对于以为目标的治疗来说,对异质治疗效果的评估是必不可少的,这在行业中尤为重要。事实上,这种文学现在发展很快,受到很多关注。在许多其他论文中,值得一提的是 Nie and Wager (2021) 的 R-learner 程序和 Athey and Wager (2018) 的因果树和森林。我可能会在未来写更多关于这些程序的文章,所以,请继续关注☺️
参考
[1] S. Künzel,J. Sekhon,p .,B. Yu,利用机器学习估计异质治疗效果的金属学者 (2019),。
[2] X. Nie,S. Wager,异质处理效应的拟甲骨文估计 (2021), Biometrika 。
[3] S. Athey,S. Wager,利用随机森林估计和推断异质处理效应 (2018),美国统计协会杂志。
相关文章
- 匹配、加权还是回归?
- Dag 和控制变量
密码
你可以在这里找到 Jupyter 的原始笔记本:
*https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/meta.ipynb *
感谢您的阅读!
我真的很感激!🤗如果你喜欢这个帖子并想看更多,可以考虑* 关注我 。我每周发布一次与因果推断和数据分析相关的主题。我尽量让我的帖子简单而精确,总是提供代码、例子和模拟。*
还有,一个小小的 免责声明 :我写作是为了学习所以错误是家常便饭,尽管我尽了最大努力。当你发现他们的时候,请告诉我。也很欣赏新话题的建议!
理解梯度推进决策树中的最小子权重
你真的知道这个超参数是怎么工作的吗?

照片由 Zuzana Ruttkay 在 Unsplash 上拍摄
建模工作中最具创造性和神秘的一个方面是超参数设置。这也是许多人在没有真正试图理解细节的情况下,随意遵循指南的地方。如果你恰好是这些人中的一员,那就让我们改变这一切吧!
在本帖中,我们将深入探讨名为min_child_weight的超参数。在我倾向于使用的 GBDT 包 LightGBM 中,这个参数有几个其他的别名,我个人认为min_sum_hessian是一个更好的名字,这也是我们实际上将要使用的名字。
在本帖中,我们将:
- 看看可用的文档,
- 定义如何为三个不同的模型目标计算 hessian,
- 总结经验法则,帮助用户掌握 min_sum_hessian 参数的大小,
- 最后,收集一些更棘手的、不太广为人知的方面。
我在示例中使用的完整脚本可以在我的 GitHub 上获得。这个例子是在 R 和 LightGBM 中,但是自然也适用于 Python / XGBoost。
文档怎么说?
LightGBM
让我们看看 LightGBM 对这个超参数有什么看法。与 LightGBM 一样,同一个参数使用了一堆不同的别名,这些别名都代表同一个东西:min_sum_hessian_in_leaf、min_sum_hessian_per_leaf、min_sum_hessian、min_hessian和min_child_weight。
LightGBM 文档对此超参数的定义是:“一片叶子中的最小和 hessian”。我想我们都同意,这并不完全有用...
XGBoost
XGBoost 文档更有帮助(根据我的经验,这种情况很常见)。这是定义:
“一个孩子所需的最小实例体重总和(黑森)。如果树划分步骤导致实例权重之和小于 *min_child_weight* 的叶节点,那么构建过程将放弃进一步的划分。在线性回归任务中,这只是对应于每个节点中需要的最小实例数。 *min_child_weight* 越大,算法就越保守。”
我认为这是一个很好的总结。在树发展的每一点上,数据中的每一个观察值都有一个所谓的 hessian 值。为了考虑分裂,两个孩子的观察值必须具有至少与超参数值一样高的 hessian 值之和。
所以我想我们现在唯一需要弄清楚的就是所谓的黑森是什么。
观测值的海森值
理论
来自 XGBoost 的这个文档给出了一个很好的数学背景的高层次总结,即使它跳过了一些概念,比如学习率。
理论上,hessian 应该是基于实际值和预测值的损失函数相对于预测值的二阶导数。一阶导数叫做梯度。
例如,假设您想要最小化平方差,那么您正在使用默认回归模型:
loss: (actual_value - predicted_value) ^ 2
gradient: - 2 * (actual_value - predicted_value)
hessian: 2
然而,这种理论方法在实践中并不准确…
实践
我认为对黑森的一个更有用的定义是:
特定目标的 hessian 是实际值和预测值的函数,正如您正在使用的梯度增强包的源代码中所定义的那样。
查看软件包的源代码总是一个好主意,而不是假设可能会发生什么。对于 LightGBM,目标函数存储在 GitHub 的这个文件夹中。假设我们正在寻找回归目标,这些都在这个脚本中。
(LightGBM 命名我们需要牢记:label是实际值,score是预测值。如果目标使用链接函数(例如,具有对数链接的泊松),两者都是在应用链接函数之后。)
重量的快速说明
以下仅适用于未加权的例子。如果您传递一个权重向量,单个 hessian 值将与它相乘,并且它将反馈到min_sum_hessian参数调节的方式中。
L2 回归目标
事不宜迟,这就是我们在代码中寻找的部分(在撰写本文时大约在第 440 行):
L2 回归梯度和海森计算
正如我们所见,他们简化了我们上面计算的公式,并将梯度和 hessian 除以 2。
L2 回归目标中观测值的海森常数是 1。经验法则非常简单:min_sum_hessian 实际上意味着这个目标的观察次数。如果将 min_sum_hessian 设置为 X,树将只在两边至少有 X 个观察值的地方进行切割。
泊松目标
现在,让我们看看一个更困难的目标。
假设观察值遵循泊松分布的可能性(来自泊松分布函数):
likelihood = predicted_value ^ actual_value *
exp(-predicted_value) / factorial(actual_value)
对数可能性:
log-likelihood = actual_value * log(predicted_value) - predicted_value - log(factorial(actual_value))
然而,我们实际上并没有对预测值进行建模,该模型假设了最终预测值和原始分数之间的对数关系。(是的,这里有几个不同的日志,必须小心!)我们可以用下面的表达式重写上面的公式:
raw_score = log(predicted_value)
收件人:
log-likelihood = actual_value * raw_score - exp(raw_score) - log(factorial(actual_value))
损失函数就是对数似然乘以-1:
loss = - actual_value * raw_score + exp(raw_score) + log(factorial(actual_value))
由raw_score取损失函数的一阶和二阶导数来分别计算梯度和 hessian,这就是我们得到的结果:
gradient = exp(raw_score) - actual_valuehessian = exp(raw_score)
现在让我们将其与源代码进行比较(在撰写本文时大约在第 440 行):
泊松回归梯度和海森计算
非常接近我们得出的结果,但是公式中的max_delta_step是什么?原来 XGBoost 和 LightGBM 都在他们的泊松海森计算中加入了一个隐藏的额外因素。max_delta_step默认为 0.7。这增加了 hessian,因为我们要用它来划分,以得出新的预测,这将导致一个更保守的树构建,步长更小。本质上与使用较低的learning_rate是一样的。
泊松回归目标中观测值的 hessian 值是当前预测值乘以 exp(0.7) ≈ 2。如果将 min_sum_hessian 设置为 X,树将只在两边至少有 X / 2 个预测总值的地方进行切割。
请注意,min_sum_hessian在不同的回归目标之间具有完全不同的含义!
二元目标
让我们看看另一个共同的目标,二元分类,这是迄今为止最复杂的三个,我们将在这里考虑。(如果你感兴趣,我写了另一篇文章来介绍这个目标的更多细节。我们现在只关注黑森人。)
我们需要从损失函数开始。对于任何给定的 y 观察值,其中 y 可以是 0 或 1,模型将预测(0,1)范围内的概率 p(y)。我们希望最小化的损失函数:
loss = - y * log(p(y)) - (1-y) * log(1-p(y))
这就是所谓的日志丢失功能。对于为 1 的观察值,它是log(p(y)),对于为 0 的观察值,它是log(1-p(y)))。
现在,就像泊松分布一样,虽然最终产品是 p(y)值,但该模型不会与 p(y)本身一起工作,而是与一个称为 sigmoid 的链接函数一起工作。模型实际适合的raw_score是:
raw_score = log(p(y) / (1-p(y))) / sigma
其中 sigma 是一个常量参数,默认值为 0.7。
根据上面的公式,
p(y) = exp(raw_score * sigma) / (1 + exp(raw_score * sigma))
我们需要在原始损失函数中插入这个表达式,以获得梯度增强算法将使用的格式。为了简单起见,让我们集中于取值为 1 的观察值,即损失函数的前半部分。
loss_positive = - y * log(p(y)) = - log(exp(raw_score * sigma) / (1 + exp(raw_score * sigma)))
梯度是上述公式相对于raw_score的导数。在 2-3 步的算术魔术中,我们得到梯度:
gradient_positive = - sigma / (1 + exp(raw_score * sigma))
取二阶导数,我们得到了 hessian:
hessian_positive = sigma ^ 2 * exp(raw_score * sigma) / ((1 + exp(raw_score * sigma)) ^ 2)
我们来看看源代码!相关部分(在撰写本文时大约在第 105 行):
二元分类梯度和 Hessian 计算
与 L2 和泊松目标不同,这有点难以分辨,但是公式与我们计算的公式相同(对于阳性情况)。查看我的二进制分类脚本的结尾,进行一些检查。
我不知道你怎么想,但我很难理解在这种情况下黑森的强度应该是多少,所以我做了一个图表:

在二元对数损失计算中作为概率函数的 Hessian
由于损失函数的对称性质,对于取值为 0 的观测值,我们不必重复它。
二元分类目标中观察值的 hessian 是当前预测概率的函数。在 p = 0.5 时,取最高值 0.1225。其含义非常有趣:相同的 min_sum_hessian 参数可能会限制更接近 0 或 1 的概率预测,但不会限制更接近 0.5 的概率预测。换句话说,在预测值接近 0.5 的情况下,即模型对分类不确定的情况下,模型在进行分割时是最宽松的。
你知道吗…?
为了证明我的观点min_sum_hessian不容易理解,这里有一些我认为经常被忽略的评论。
min_sum_hessian是…
- …与一片叶子中的数据数量不同,并且取决于目标,它甚至可能与观察的数量不成比例。
- …并不是在过程的每一步都以相同的方式进行调整-随着您添加更多的树,相同数据点的 hessia 可能会发生变化,这取决于您的目标,这意味着同一组观察值的 hessia 总和可能会发生变化,无论它们是在
min_sum_hessian之上还是之下。 - …如果在计算中加入重量矢量,情况会更复杂。需要记住的事实是,单个的 hessian 值会乘以权重。
- …仍然是关于树枝和树叶大小的最佳测量方法,没有其他超参数可以让您简单地限制观察次数。看似让您限制观察次数的超参数实际上是基于 hessian 值的估计。
https://matepocs.medium.com/membership
了解 MixNMatch:创建更真实的合成图像
将多个真实图像中的不同因素组合成一个合成图像

图 MixNMatch 创成式模型的概述
我最近偶然发现了这篇名为 MixNMatch 的论文,该论文旨在将多个真实图像中的不同因素组合成一个单一的合成图像——只需最少的监督。这篇文章旨在详细说明,并要求一些深度学习和生成模型的背景。如果你正在寻找一个 TLDR;版本,你可以在这里查看我的推特帖子。
如果你喜欢这个帖子,请分享给你的网络。
摘要
在其核心,MixNMatch 是一种使用条件生成对抗网络(GAN)的条件图像生成技术。MixNMatch 将来自不同实像的多个因子解开并编码成一个单一的合成图像。具体来说,它将不同真实图像的图像背景、姿态、形状和纹理组合成一个单一的合成图像,只需最少的监督。
在训练过程中,MixNMatch 只需要一个松散的包围盒来模拟背景,而不需要物体的姿态,形状或纹理。
问题
在最少监督的情况下学习解开表示是一个极具挑战性的问题,因为产生数据的潜在因素通常是高度相关和交织的。
- 有大量工作通过拍摄两个输入参考图像来理清两个因素。例如-一个参考图像用于外观,另一个用于姿势。但他们不能理清其他因素,如前景与背景外观或姿势与形状。由于只有两个因素可以控制,这些方法不能随意改变,例如,对象的背景,形状和纹理,同时保持其姿势不变。
- 另一组工作需要以关键点/掩模注释的形式进行强有力的监督;限制了它们的可伸缩性,并且仍然无法理清 MixNMatch 中列出的所有四个因素
主要思想
MixNMatch 学会在最少监督的情况下,从真实图像中解开并编码背景、对象姿势、形状和纹理潜在因素。为了实现这一点,他们提出了一个可以同时学习的框架
- 编码器,其将来自真实图像的潜在因素编码到解开的潜在代码空间中,以及
- 一种从解开的代码空间中提取潜在因素用于图像生成的生成器。

图 2:来自的 MixNMatch 架构在这里
MixNMatch 的生成器建立在 FineGAN 的基础上——这是一个生成模型,它学习使用信息论在最少监督的情况下分层理清背景、对象姿势、形状和纹理。

图 FineGAN 如何从这里工作
然而,FineGAN 仅取决于采样的潜在代码,而不能直接取决于用于图像生成的真实图像。因此,我们需要一种方法来从真实图像中提取控制背景、物体姿态、形状和纹理的潜在代码,同时保留 FineGAN 的层次分解属性。那么我们有什么选择呢?
- FineGAN 的简单扩展;意思是——训练一个编码器将假图像映射成潜在代码。这是行不通的,因为真实图像和虚假图像之间的域差距
- 执行对抗学习,由此学习真实图像及其从编码器提取的潜在代码的联合分布,以及采样潜在代码和来自生成器的相应生成图像的联合分布,以使其不可区分,类似于阿里和甘比
对抗式学习方法的优势是什么?
通过实施匹配的联合图像码分布,编码器学习产生与采样码的分布相匹配的潜在码,该潜在码具有期望的解纠缠特性,而发生器学习产生真实的图像。
更具体地说,对于每个真实图像 x,作者提出四个单独的编码器来提取其 z、b、p、c 码。但是,您不希望将这些代码直接放入生成器来重建图像。因为这将把这个模型变成一个“简单的”自动编码器。这并没有保留 FineGAN 的解缠结特性(分解成背景、姿势、形状、纹理)。因此,作者利用阿里和甘比的想法来帮助编码器学习逆映射;即从实像到代码空间的投影,以保持期望的解缠结属性的方式。
方法
因为 MixNMatch 是基于 FineGAN 的,所以让我们先快速回顾一下 FineGAN 是如何工作的。
FineGAN 如何工作
作为输入,FineGAN 采用四个随机采样的嵌入,又名。潜在代码( z,b,c,p )如图 2 所示。然后,它分三个阶段分层生成图像(图 3)。
- 阶段 1:背景阶段,模型只生成背景,以潜在的一键背景码 b 为条件
- 阶段 2:父阶段,模型根据潜在的一键父代码 p 和连续代码 z 生成物体的形状和
姿态,并将其缝合到现有的背景图像上 - 阶段 3:子阶段,模型填充物体的纹理,以潜在的一键子代码 c 为条件
在父阶段和子阶段,FineGAN 还会生成对象的遮罩,以便在没有监督的情况下捕捉形状和纹理。
在训练期间,FineGAN 实施了两个约束
- 约束#1:它将子代码分组到一个不相交的集合中,因此每个集合包含相同的父代码。例如,这种约束使得不同种类的鸭子具有相同的形状但不同的质地。
- 约束#2:它还强制要求对于每个生成的图像,子代码和背景代码对总是相同的——这意味着鸭子的背景总是有水
【FineGAN 如何理清不同的特征?
- 为了解开背景,FineGAN 依赖于对象的边界框。
- 为了理清其他因素,FineGAN 依赖于 InfoGAN [5]中的信息论,并在潜在代码的关系之间施加约束。
MixNMatch 和 FineGAN 有什么不同
MixNMatch 的主要思想是执行对抗学习,使得由编码器产生的成对图像码分布和由生成器产生的成对图像码分布相匹配。MixNMatch 处鉴频器的输入是一个图像码对。
在 mix match解缠绕是如何工作的
FineGAN 强加的约束对于真实图像可能是困难的。如果你回顾约束#2,你会发现它并不总是成立的。例如,鸭子可能不总是在水里。因此,为了绕过约束#2,作者采用了以下步骤
- 训练四个独立的鉴别器,每种代码类型一个。这防止了任何鉴别器看到其他代码,因此不能基于代码之间的关系进行鉴别。
- 在训练编码器时,用随机采样的代码生成的假图像也被提供作为输入。这些随机采样的代码消除了这些限制。这意味着,在这些生成的图像中,任何前景纹理都可以与任何任意背景和任意形状相结合。
如何捕捉精确的姿势和形状
到目前为止,我们已经了解了 MixNMatch 如何将图像的 4 个不同方面分开,并将其编码成代码。然后,MixNMatch 的生成器将这 4 个因素结合起来,生成“真实”的合成图像。作者称这个过程为“代码模式”。
但是“编码模式”不能保持精确的像素级姿态和形状,而这对于某些应用是必要的。为什么它不能保持精确的像素级?因为姿势/形状的潜在代码维度不够大,不足以捕捉那些细粒度的特征。为了解决这个问题,作者引入了一种叫做“特征模式”的东西
关键思想不是将参考图像编码成低维形状码,而是直接学习从图像到高维特征空间的映射,该映射保持参考图像的空间对齐的形状和姿态(像素级)细节。
您可以在论文的第 3.4 节中找到更多相关信息。
参考
- Krishna Kumar Singh、Utkarsh Ojha 和 Yong Jae Lee。用于细粒度对象生成和发现的无监督分层解缠。在 CVPR,2019 年。
- 杰夫·多纳休、菲利普·克亨布尔和特雷弗·达雷尔。对抗性特征学习。2017 年在 ICLR。
- 文森特·杜穆林、伊斯梅尔·贝尔加齐、本·普尔、亚历克斯·兰姆、马丁·阿约夫斯基、奥利维尔·马斯托皮埃罗和亚伦·库维尔。对抗性学习推理。2017 年在 ICLR
- 迈赫迪·米尔扎,西蒙·奥森德罗 条件生成对抗网
- 陈曦、闫端、雷因·胡特夫特、约翰·舒尔曼、伊利亚·苏茨基弗和彼得·阿贝耳。Infogan: 通过信息最大化生成对抗网的可解释表示学习。在 NeurIPS,2016。
了解 ML 监控债务
原文:https://towardsdatascience.com/understanding-ml-monitoring-debt-5052bba60c2c
本文是正在进行的系列文章的一部分,该系列文章探讨了监测债务的洗钱问题、如何识别债务以及管理和减轻债务影响的最佳实践

只是另一只被他的模型监控仪表板压垮的猫|图片由101 猫通过 iStock
我们都熟悉软件工程中的技术债务,在这一点上,ML 系统中隐藏的技术债务实际上是教条。但是什么是 ML 监控债务呢?ML 监控债务是指当模型监控被它要监控的 ML 系统的规模所淹没时。让从业者在大海捞针,或者更糟的是,点击“删除所有”的警告。
ML 监控远不如传统 APM 监控那样清晰。不仅在指标和基准方面没有绝对的真理,而且模型也不受规模经济的影响。很容易构建一个新的 Kubernetes 集群,该集群将遵循与其前身相同的性能指标、基准、阈值和 KPI。但是,当您部署一个新的模型时,即使它是一个预先存在的模型,并且没有对工件进行更改,实际上可以保证您的引用将会不同。这意味着您正在为部署到生产和监控中的每个模型承担债务。
什么是糟糕的绩效水平?
80%的准确率?60%的准确率?
需要考虑多个因素来确定好/坏的性能水平,底线将根据每个模型的用例、细分市场以及数据而有所不同。在本帖中,我们将通过使用“大数据的四个 V”框架来解释 ML 模型监控的债务维度,该框架非常适合这种比较。
1.诚实
高维度
测量和监控依赖于 2-3 个元素的数据驱动流程相当简单。但是 ML 就是利用大量的数据源和实体来定位潜在的、可预测的模式。根据问题和相关数据,您可能会看到几十个甚至成百上千个特性,每个特性都应该被独立监控。
模型度量
ML 是一个随机的面向数据的世界,由生产中的多个不同管道组合而成。这意味着需要跟踪和监控每个实体的大量指标和元素,例如数字元素和基数级别的特征均值、标准差和缺失值、熵,以及分类元素的更多信息。全面的模型指标超越了特性、数据和管道完整性,提供了可量化的指标来分析模型输入和输出的相对质量。
Chip Huyen 最近发布了一份全面的模型指标清单,涵盖了值得检验的整个模型生命周期。
2.卷
ML 监控中的容量需要在两个维度上进行分析:吞吐量和粒度
吞吐量
模型通常处理大量数据,以实现决策过程的自动化。这给监控和观察数据集的分布和行为带来了工程挑战。监控解决方案需要在几分钟内检测到数据质量和性能问题,同时随着时间的推移分析大量数据流。
数据的分辨率
要在亚群体水平上检测事物,需要能够将数据分段,但这也是一个分析挑战。对于不同子群体下的相同指标,数据的性质和模型性能可能会有很大不同。

由作者在 imgflip 上创建
例如,一个名为“年龄”的特征的缺失值指示符通常在整个人群中占 20%,但是对于一个特定的频道,比如脸书,该值可以是可选的,并且在 60%的情况下是缺失值,而对于所有其他子人群,只有 0.5%的情况下是缺失值。
高级视图只能提供这么多信息,尤其是关于子群体和详细的解决方案的信息,这些信息对于支持业务需求和决策至关重要。影响整个数据集或群体的宏观事件是每个人都知道要注意的事情,通常可以相对较快地检测到。
但这意味着,在庞大的数据流中检测问题的工程和分析挑战,现在因需要监控的不同数据段的数量而成倍增加。
3.速度
模型以不同的速度服务于业务流程的自动化,从批量每日\每周预测到大规模的实时 ms 决策。根据您的使用情况,您需要能够支持不同类型的速度。然而,和体积一样,速度也有一个额外的维度,即管道速度。将整个推理流程视为持续改进的管道。为了在不破坏事物的情况下快速行动,你需要将延迟的反馈重新整合到你的 ML 决策过程中。
在一些用例中,例如 Ad-tech 实时竞价算法,我们希望监控每周的影响,因为我们需要能够在几分钟内检测到数据质量或性能问题,以避免业务灾难。
4.多样化
最后但并非最不重要的,我们来了变化。一个具有商业投资回报的成功模型跨越了更多的模型。一旦你克服了第一个模型障碍,并证明了 ML 对业务成果的积极影响,你的团队和你的业务都会想要复制这种成功并扩大规模。有三种缩放模型的方法,它们并不互相排斥。
版本
ML 是一个迭代的过程,版本是我们如何做的。现实世界不是一成不变的,管道和模型必须不断优化。为相同的现有模型不断地创建版本,但是每个版本实际上是一个完全不同的模型实例,可能具有不同的特性,甚至不同的基线。
用例规模
向您的武库中添加一个用例意味着您实际上是从零开始重新启动整个 MLOps 周期。您可以继承许多东西,尤其是当涉及到特性工程时,但是当您部署到产品时,您将有一个新的模型度量集和规模来监控。除了 ML 监控的技术方面之外,模型驱动业务流程,并且每个流程都与其他流程不同。对于同一个贷款审批模型,风险和合规团队可能担心由于监管问题而产生的潜在偏差,业务运营部门希望第一个知道模型是否突然决定全面拒绝贷款,ML 工程师需要了解完整性和管道问题,数据科学团队可能对模型预测的缓慢漂移感兴趣。关键是它是多学科的,你的利益相关者对 ML 决策过程的不同方面感兴趣。对于新流程,您需要确保快速交付价值。
多租户规模
多租户具有指数级的扩展能力。当一个租户本身就相当于一个群体时,就需要决定跨多个原则部署一个模型。例如,部署一个学习过程来检测潜在的客户流失,但要在每个国家单独进行(在本例中是租户)。结果是每个国家都有一个独立的模型。

作者于 imgflip 创建
做出这样的决定可以让你在一夜之间从一个单一的欺诈模型变成数百个欺诈模型。虽然他们可能共享相同的指标集,但预期的价值和行为会有所不同。
我们从债务监控模型中学到了什么
从表面上看,模型监控看起来似乎很简单。公平地说,对于一两个模型,如果您愿意投入资源,手动监控 ML 是可行的。但是在 ML 工程中,就像软件工程一样,一切都是债务和规模的问题。是否值得承担并在以后支付?模型监控不是一项简单的任务,从技术和流程的角度来看也不简单,随着规模的扩大,管理 ML 监控的难度也在增加。
4 V 说明了为什么模型监控是复杂的,作为量化这个问题的一个练习,让我们考虑以下数字:

简单的 ML 监控噪声计算|由作者创建
既然我们已经量化了洗钱监测固有的规模问题及其原因,下一步就是确定债务。本系列的以下部分将处理识别债务指标和最佳实践,以管理和克服模型监控债务。
敬请期待!
柳文欢·拉松 是super wise领先的模型可观测性平台的联合创始人兼 CEO。为从业者提供完全自动化的企业级模型监控功能,这些功能需要花费数年时间在内部开发,包装在自助服务平台中。
理解 ML-产品生命周期模式
原文:https://towardsdatascience.com/understanding-ml-product-lifecycle-patterns-a39c18302452
对 ML 驱动的产品的运营生命周期进行分类的指南,并对其显著模式进行概述

罗斯·斯奈登在 Unsplash 上的照片
与任何突破一样,作为实验室实验的一部分,证明数据科学问题的可行解决方案理所当然是一个激动人心的时刻。已经完成,我们已经破解了数据,现在离通过推出我们的模型来加速我们的业务只差一步之遥。结果发现,这众所周知的最后一步很快就变成了一堆挑战。
事实上,如果你搜索讨论数据科学项目生命周期主题的资源,它们通常会一直停留在许多基本阶段,直到最后一个阶段——神奇的模型部署——故事圆满结束。
但这并不是构成相关生命周期的唯一过程。很公平,严格地说,作为一个数据科学项目,它很可能就此结束。然而,对任何实际业务来说至关重要的是,还有产品视角,这伴随着许多跨越最终部署时刻的永久方面。
在这篇文章中,我们试图采取一种独特的方法来提取一些常见的模式(好的和坏的),用于实现 ML 驱动的产品的实际生命周期。考虑到所有的复杂性,我们将有选择地选择正确的细节层次来捕获操作上重要的阶段。通过这种方式,我们可以缩小到足以忽略一侧的专业科学技术水平(例如特征工程),但同时又可以放大到足以忽略另一侧的所有一般非技术阶段(例如客观定义)。我们还故意只关注 ML 相关的工作流,它驱动实际的封装数据产品(它本身可以是任何东西)。
目录
主要属性导航景观
∘ 数据访问机制
∘ 模型采购模式
∘ 模型更新策略
∘ 部署架构
∘ R & D 联络
显著模式的分解
∘ 交互生命周期(反模式)
∘
浏览风景的主要属性
为了对不同的生命周期模式进行分类,我们确定了特定数据产品实现的一组主要属性(根据功能概念以及交付原则),这将帮助我们建立一个简单的分类法。
数据访问机制
该属性与特定生命周期阶段内的数据检索过程相关。它直接反映了底层数据生态系统的成熟度(从混乱的数据孤岛到基于有意识的数据策略和治理的组织良好的数据湖)以及产品业务逻辑中真实结果协调路径(又名反馈回路)的正确实施。在我们的分类范围内考虑的两个实例是:
- 程序化数据访问是一个理想的概念,在这个概念中,数据可以有选择地按需进入生命周期,以交换某种形式的数据说明符。这种控制数据供应的能力是任何动态生命周期的基础。没有它,自动正在评估或模型更新等功能无法实现。
- 在另一端处理特定的数据转储是穷人对适当的数据基础设施的替代。尽管只允许臭名昭著的不幸的生命周期,但我们仍然经常看到这种方法,这就是为什么我们在列表中授予它一个典型的反模式。
模型采购模式
模型实例产生的不同可能方式由采购模式属性来表征。我们区分以下类型:
- 无人值守采购基于生产模型的培训、调试和评估阶段的完全自动化。消除过程中的任何手动步骤不仅对可重现性研究至关重要,而且对可靠操作尤为关键。这些原则在生产工程中很常见,但在实践数据科学中无疑仍难以被采用。
- 相比之下,交互式(手动)模型采购往往在最初的灵光乍现之后的匆忙中被天真地实践,那时有希望的实验室结果将尽快在生产中看到。这完全是对任何基本操作实践的无知,并且由于其固有的易出错性,以及缺乏再现性和操作可见性,它经常导致商业损失而不是预期的收益。
模型更新策略
非常缓慢变化或实际上静态的模式理论上可以使用在其生命周期中只训练一次的模型来概括。但是剩余的绝大多数有用的真实世界模型需要定期刷新,以反映观察到的现象的进展变化。简而言之,这就是模型更新策略属性所决定的:
- 强大的生命周期包含独立例程,用于自主处理模型更新——通过增量训练或完全再训练以及定期超参数调整。更新事件可以由固定的时间表驱动,由被服务请求的数量阈值更动态地触发,或者甚至可能响应于自动正在进行的模型评估的(拒绝)结果。这个概念将使用不同代码库版本(又名版本)产生的(同一项目的)模型与具有相同实现的模型区分开来,只是在不同的数据(又名代)上进行训练/调整。
- 没有专用更新例程的简单生命周期仍然可以实现发布-强制模型刷新-本质上允许通过主动重新访问 R&D-生产过渡(即发布)来更新模型,这包括模型培训/调整以及随后的部署。与自主模型更新策略不同,这个生命周期没有代的概念(同一个项目的每一个模型——无论是基于不同的代码还是仅仅基于数据——都是作为一个新的版本产生的)。
部署架构
使用部署架构属性来描述模型最终投入使用的机制(对所提供的输入进行预测)。它将生命周期分为以下几类:
- 作为发布集成过程的一部分,模型包装概念是基于将模型(和所有相关资产)直接硬连接到服务组件(引擎)中。由于集装箱化的普及,这成为一种非常实用的方法,其主要优点是简单。另一方面,它缺乏(以其典型的形式)从发布的工件内部对部署策略(例如,A/B 测试、多臂土匪、冷启动/回退模型……)的直接控制,不得不将责任从项目级别转移到灵活性较低的基础设施级别。
- 动态模型(资产)加载是一个更健壮的原理,允许通用服务引擎(仍然可能是容器化的)在运行时从专用注册表和存储中动态加载特定模型及其资产。这样,首次展示策略可以作为选择器函数灵活地交付,当分派预测请求以选择最合适的模型生成时,服务引擎直接应用该函数。然而,这种方法带来了明显更高的复杂性(例如,必须支持加载由不同模型使用的同一库的多个版本,等等。)
R&D 联络处
用于研发生命周期阶段的特定方法——即这两个分支之间的交互——是由 R & D liaison 属性处理的方面:
- 传统的双周期有效地将研究和开发分离为两个不同的能力领域,由专门的团队执行——数据**科学家,他们负责找到并证明解决方案,以及软件工程师,他们负责交付产品级的实现。然而,这伴随着工作重复(两个实现)、长时间的迭代(R & D 团队之间的交接)、额外测试的需求(研究结果比较)和工具(使用研究工具的生产故障排除)等形式的低效率。
- 另一方面,联合循环是一种整合两个领域的方法,旨在消除双循环的低效率。这无异于直接从研究中交付生产级解决方案。尽管如此,当采用正确的工具(解决常见工程问题的框架和 SDK)和实践(技术和管理)时,这实际上并不是完全不可能的。当然,还要选择正确的生命周期模式!
显著模式的分解
使用概述的属性,我们现在可以描述以下主导当今行业的生命周期集(或它们的代表性变体)。我们用突出重要阶段的流程图来说明每个阶段。
交互式生命周期(反模式)
分类: 交互地 采购,用 发布-强制 更新, 包裹 部署, 联合 R & D,典型地基于 临时数据转储
从一个深刻的反模式开始,交互生命周期包含了许多之前确定的不良实践。顾名思义,它直接在实施者的工作区(例如 IDE 或笔记本)中交互式地执行所有预部署步骤。它通常只是出于无知而被追求,很快就会发现其固有的问题。

互动生命周期(作者图片)
内部 R&D 环路包括三个主要的交错级:
- 实施是将解决方案具体化的实际过程。从概念上讲,它包括安装所需的库,定义评估方法(损失函数),当然,还有编码 ML 管道。
- 勘探阶段涉及所有必要的数据检查,以推动后续实施(顺便提一下。不要被流程图建议的先实现后探索的明显优先顺序所迷惑(这似乎与常识相冲突)——由于它们的交替,这两个阶段的顺序是相当虚幻的;但是因为这都是从至少安装一些库开始的(这是实现的一部分),所以这个图实际上是合法的。
- 最后,学习阶段包括手动运行已实现的 ML 管道以调整/训练实际模型,随后是其评估以定性地基准测试解决方案。为了保持至少某种程度的可再现性,它应该不变地将学习结果与其实际实现联系起来(例如,通过 VCS 提交)。如果发布,生产的模型(资产)的完全相同的实例成为发布包的一部分。
一旦对学习结果感到满意,生命周期可以通过发布流程进入集成并最终进入部署阶段,如下所示:
- Release finalization 涉及完成发布所需的所有剩余元信息。在这一点上,释放应再次在 VCS 不变的标签,以保持其再现性。
- 集成阶段(通常仍然是交互驱动的)处理生成可部署的服务包(例如容器映像)包装模型及其所有相关资产(ML 代码、模型状态、可能是预先生成的特性的静态目录,否则这些特性将保存在特性存储中,等等。)以及实际的服务引擎(例如 REST 服务器)。任何集成都应该经过合理的测试(包括流程的输入和输出)。
- 成功的集成可能会继续进行实际的特定于基础设施的部署流程,从而进入服务阶段。无论哪种情况,循环的 R & D 部分到此结束。
启动时,服务引擎加载包装好的包并进入服务循环,对于每个接受的请求,服务循环简单地利用嵌入的资产运行预测管道,并返回结果。
在这里,交互生命周期实际上结束了。基于直接持续评估的关键反馈通常作为运营监控的支柱和潜在后续 R&D 迭代的基础,由于缺乏计划数据访问而无法实施。与一般的不可再现性一起,这是前几节单独描述的不同其他问题中最严重的两个缺点。
开放生命周期
分类: 无人值守 采购, 发布——威逼利诱 更新, 裹 部署, 联合 R & D, 程序化 数据访问。
解决增量工作流的主要问题,开放生命周期是 ML 产品生命周期的第一个有价值的模式。然而,它对一般产品架构有更强的要求,即依赖于以下数据检索能力:
- 一个查询 API ,允许有选择地获取数据增量
- 事件-结果反馈回路,这是一个外部协调路径,为系统预测的每个事件提供真实结果的知识

开放生命周期(图片作者**
**打开和之前描述的交互生命周期之间的差异已经在 R & D 部分开始:
- 实现阶段包含一个额外的步骤制定数据源描述符,这是一个特定于技术的表达式,允许使用平台数据访问 API 按需检索所需的数据。
- 实验阶段涵盖了与交互生命周期的学习阶段几乎相同的步骤,然而,这一次,在其范围内产生的任何资产都不会成为最终发布的捆绑包的物理部分。
关键区别在于非交互式生产后端的独特部分,其职责如下:
- 为了使集成(包括模型培训)无人值守,有一个专门的代理在触发发布时自动接管该过程。这与持续集成概念没有什么不同——通用软件开发中事实上的标准。生成的包仍然直接将所有资产与服务引擎一起包装。
- 第二个后端代理负责正在进行的服务评估。这是一个预定的或容量驱动的循环,本质上是根据真实结果评估预测结果,并将其作为真实模型性能指标进行报告。
这才是真正的不同之处。有了这些功能,我们实际上可以完成一个体面的 ML 生命周期,包含生产运营所需的所有基本质量。标签 open 反映了这样一个事实,由于其发布强制更新机制,外部生命周期不会自动迭代,使得模型在重新发布之前实际上是静态的。将模型直接包装在服务包中,生命周期也不直接支持任何动态* 展示策略(例如 A/B 测试等)。).但是考虑到特定产品的特性,所有这些都是可以接受的。*
适应性生命周期
分类: 无人值守自主 更新, 动态 部署, 联合R&【D】程序化 数据访问
这是 ML 生命周期中的巅峰之作,结合了所有主要特性的精华。除了开放生命周期的所有优势之外,它还增加了对自动模型更新(以模型生成的形式)以及动态展示策略的支持。抛弃了直接包装在服务包中的模型资产的概念,这个生命周期以其对模型注册中心和(可选的)特性库的依赖的形式对平台服务提出了新的要求。

适应性生命周期(图片作者)
虽然 R&D 部分与之前的开放生命周期完全相同,但生产端是所有进步的源头:
- ****集成过程不再涉及模型训练,它仅仅产生一个特定版本的所有代通用的裸软件工件。
- 最终的部署包括发布一个服务清单,它包含在运行时用于分派请求 s 的生成选择器函数。
- 非交互式后端现在包含一个额外的代理,负责培训/调整新的模型生成。这是一个使用项目定义的函数(将各种运行时度量作为输入,包括例如最近的评估性能)触发的循环,以刷新模型,即产生发布到专用模型注册中心的新一代,服务引擎可以从中挑选它们。
- 没有任何资产与它直接捆绑在一起,服务于引擎的更加通用。为了选择最佳的模型/生成来进行实际的预测,使用来自已部署的清单的选择器函数来分派每个请求。该功能的输入可以(不仅仅)是任何可用的运行时指标,允许它有效地实施任意的动态部署策略(例如,A/B 测试、多臂土匪、冷启动/回退模型等。).****
- 最后,评估代理现在也非常通用(与特定的模型发布资产解耦),必须基于被评估的(元)数据提取相关的工件。
这涵盖了适应性生命周期的高级设计。它的优势在于能够自动响应观察到的模式变化(在原始列车时间内未知),方法是动态选择冷启动/回退模型,或者根据新数据不断更新模型。然而,其代价是后端代理和服务引擎的更高的复杂性,以及在所有不同的存储(模型/工件注册、特性存储、清单目录、度量/元数据数据库/总线等)方面增加的基础设施需求。).
双生命周期
分类: 双重 R & D、 程序化 数据存取、 无人值守
作为最后一个(半)模式,让我们简要介绍一下双生命周期的一个实例。我们呈现它的方式使它成为一个不完整的模式——使生产部分开放,因为它可以从剩余主要属性的任何可能组合中潜在地派生出来。**

双生命周期(作者图片)**
生命周期的双重性从研究和开发阶段之间的明确分离中显而易见,这对应于由两个专家团队产生的两个不同的可交付成果。虽然研究阶段有许多类似于之前描述的生命周期的阶段,但它的目标是产生(经过验证的)解决方案规格。然后是开发角色接管它,并将其转化为生产就绪实现。**
如上所述,双循环的主要问题是低效率导致的高成本。少数可能证明这一点的场景是对工程质量有最高要求的高度专业化的用例(例如,超低延迟)。不属于这一类,对这种模式并不陌生的其他地方是较大的传统软件公司,通常在误导性的尝试中采用这种模式,以赶上所有嗡嗡作响的数据炒作。
识别任何复杂问题空间中的模式是理解它的最佳起点。在这篇文章中,我们提出了一个简单的分类法来对 ML 产品生命周期进行分类,并用它来描述一些值得注意的模式。为了有目的地为他们的产品选择正确的选项,对这些模式的认识对于任何参与设计 ML 生命周期的人来说都是必不可少的。
理解形态学图像处理及其操作
这篇文章用更直白的术语说明了形态学图像处理;读者可以了解形态学在数字图像处理中的工作原理

图一。使用形态学图像处理操作的边界提取。(来源:图片由作者提供)
单词‘形态学’通常代表生物学中研究动物和植物的形态和结构的一个分支。然而,我们在【数学形态学】中使用相同的术语来提取在表示区域形状、边界等方面有用的图像成分。
形态学是一套全面的图像处理操作,基于形状处理图像[1]。形态学操作将结构化元素应用于输入图像,创建相同大小的输出图像。在形态学运算中,输出图像中每个像素的值基于输入图像中相应像素与其邻居的比较。
形态学和图像分割之间有轻微的重叠。形态学由可用于预处理图像分割的输入数据或后处理图像分割阶段的输出的方法组成。换句话说,一旦分割完成,就可以使用形态学操作来消除分割图像中的缺陷,并传递关于图像形状和结构的信息,如图 2 所示。

图二。形态学处理的例子[2]。
为了简单和便于理解,本文主要关注二进制图像。
形态学图像处理中的术语
所有的形态学处理操作都是基于提到的术语。
结构化元素: 它是一个矩阵或者一个小尺寸的模板,用来遍历一幅图像。结构化元素被定位在图像中所有可能的位置,并与连接的像素进行比较。它可以是任何形状。
拟合: 当结构化元素中的所有像素覆盖了对象的像素时,我们称之为拟合。
Hit: 当结构化元素中至少有一个像素覆盖了对象的像素时,我们称之为 Hit。
错过: 当结构元素中没有像素覆盖对象的像素时,我们称之为错过。
图 3 显示了形态学图像处理中使用的术语的可视化。

图 3。形态学术语解释。(来源:图片由作者提供)
形态学运算
基本上,形态学图像处理类似于空间滤波。结构化元素在原始图像中的每个像素上移动,以在新处理的图像中给出一个像素。这个新像素的值取决于所执行的形态学操作。最广泛使用的两种运算是腐蚀和膨胀。
1.侵蚀
侵蚀会缩小图像像素,或者侵蚀会移除对象边界上的像素。首先,我们遍历图像对象上的结构化元素来执行腐蚀操作,如图 4 所示。输出像素值通过下式计算。
像素(输出)= 1 {如果合适}
像素(输出)= 0 {否则}

图 4。使用结构化元素对输入图像进行腐蚀操作。(来源:图片由作者提供)
腐蚀的例子如图 5 所示。图 5(a)表示原始图像,图 5(b)和图 5(c)分别显示了使用 3×3 和 5×5 结构元素腐蚀后的处理图像。

图 5。侵蚀中结构元素大小的结果。(来源:图片由作者提供)
属性:
- 它可以分割关节对象(图 6)。
- 它可以剥离挤出(图 6)。

图 6。侵蚀的使用案例。(来源:图片由作者提供)
2.扩张
膨胀会扩大图像像素,或者在对象边界上添加像素。首先,我们遍历图像对象上的结构化元素来执行膨胀操作,如图 7 所示。输出像素值通过下式计算。
Pixel(output)= 1 {如果命中}
Pixel(output)= 0 {否则}

图 7。使用结构化元素对输入图像进行膨胀操作。(来源:图片由作者提供)
图 8 显示了一个膨胀的例子。图 8(a)表示原始图像,8(b)和 8(c)示出分别使用 3×3 和 5×5 结构元素膨胀后的处理图像。

图 8。膨胀中结构元素大小的结果。(来源:图片由作者提供)
属性:
- 它可以修复断裂(图 9)。
- 它可以修复入侵(图 9)。

图 9。膨胀的示例用例。(来源:图片由作者提供)
复合操作
大多数形态学操作不是使用膨胀或腐蚀来执行的;相反,它们是通过使用两者来执行的。两种最广泛使用的复合操作是:(a)闭合(首先执行膨胀,然后执行侵蚀)和(b)打开(首先执行侵蚀,然后执行膨胀)。图 10 显示了对单个对象的两种复合操作。

图 10。输入对象上复合操作的输出。(来源:图片由作者提供)
应用:对象的边缘提取
提取边界是获取信息和理解图像特征的重要过程。这是预处理中的第一个过程,呈现图像的特征。这个过程可以帮助研究人员从图像中获取数据。我们可以通过以下步骤来执行对象的边界提取。
第一步。通过侵蚀过程创造图像(E);这将稍微缩小图像。结构化元素的内核大小可以相应地变化。
第二步。从原始图像中减去图像 E。通过执行这一步,我们得到了对象的边界。
如需说明,请参考封面艺术或图 1。
结论
本文阐述了数字图像处理中的形态学课题。此外,我们举例讨论形态学中最著名的两种方法:膨胀和腐蚀。然后我们看看如何将这两种方法结合起来解决其他用例。最后,我们解释形态学图像处理的一个应用。
参考
[1] P Soille。“形态学图像分析,原理和应用”,1999 年。
[2] R. C. Gonzalez,R. E. Woods,“数字图像处理”,第二版。上马鞍河,新泽西州普伦蒂斯霍尔,2002 年。
了解 scikit-learn 的 OVO SVC 模型的多个超平面
如何解释 scikit-learn 中线性 SVC 的 coef_ attribute 以解决多类分类问题

由 Fr 拍摄。丹尼尔·丘奇开启 Unsplash
在我最近的文章中,我向你展示了如何为一个二元分类问题解释一个拟合的 SVC 模型的coef_和intercept_属性,以及如何绘制决策平面。如果你还没有读过,我建议你在读这篇文章之前先看看。以下是它的链接:
注意: 因为上一篇文章围绕着一个二元分类问题,所以它基本上是我们使用的一个 SVM。在这篇文章中,我们实际上使用了一个 SVC,我们将利用 SVC 的力量。SVM 和支持向量机之间的主要区别在于,支持向量机本质上只是多个支持向量机的组合,因此允许我们使用多个超平面来分类多个类别。
这篇文章的主题有点复杂,但是我会尽我所能让它容易理解。
准备好了吗?我们走吧!
创建一些虚拟数据
首先,让我们创建一些数据来处理和可视化。下面的代码片段应该可以完成这个任务:
import numpy as np
import matplotlib.pyplot as pltX = np.array([[1.5, 1 ], [1.5, 2 ], [ 2, 1 ], [ 2, 2 ], #upper right (Green)[1, -1 ], [1, -2 ], [ 2, -1 ], [ 2, -2 ], #lower right (Blue)[-1, -1.5], [-1, -2.5], [-2, -1.5], [-2, -2.5], #lower left (Red)[-1, 1 ], [-1, 2 ], [-2, 1 ], [-2, 2 ], #upper left (Yellow)])y = np.array(['green','green','green','green','blue','blue','blue','blue','red','red','red','red','yellow','yellow','yellow','yellow',])plt.scatter(X[:, 0], X[:, 1], c=y)
plt.show()
这将产生以下情节:

散点图中的四个数据类-由作者创建
正如你所看到的,这个数据显然是线性可分的,所以线性 SVC 应该可以很好地处理它,因为有两个以上的类,我们需要一个以上的超平面,(这种直觉应该很容易理解。想象一下,必须画一条****单条** 直线 来分隔四个不同的类。你根本不能。**
在数据上拟合 SVC
下一步是使用 scikit-learn 的库在我们的数据上安装一个 SVC。这可以简单地这样做:
from sklearn.svm import SVCclf = SVC(kernel='linear')
clf.fit(X, y)
就 SVCs 而言,存在两种创建超平面的方法。一个叫一对一(OVO)** 一个叫一对一(OVR) 。我现在不会深入讨论这个问题,因为这是另一篇文章的主题。现在,你只需要知道我们将创建一个 OVO SVC。简而言之,这意味着我们将每一个类与每一个其他类进行比较,并且这些比较中的每一个都有一个相应的超平面。**
我们可以看看拟合的 SVC 的coef_和intercept_属性,就像我们在上一篇文章中所做的那样。
print('coef_\n', clf.coef_)
print('intercept_', clf.intercept_)>> coef_
[[ 0.00000000e+00 -1.00000000e+00]
[ 9.99532404e-01 2.22044605e-16]
[ 5.00000000e-01 -5.00000000e-01]
[ 4.00000000e-01 4.00000000e-01]
[ 8.00000000e-01 0.00000000e+00]
[ 0.00000000e+00 -8.00000000e-01]]>> intercept_
[ 1.45716772e-16
4.30211422e-16
0.00000000e+00
0.00000000e+00
-2.00000000e-01
-2.00000000e-01]
好的,显然我们为coef_属性得到了 6 个向量,也为intercept_属性得到了 6 个值。这些是我们将要用来绘制超平面的值,也是用来分类新数据点的值。
你可能会问为什么是数字 6?要回答这个问题,让我们看看 SVC 的 scikit-learn 文档:

scikit-learn 文档的屏幕截图(于 2022 年 12 月 6 日访问)-由作者创建

scikit-learn 文档的屏幕截图(于 2022 年 12 月 6 日访问)-由作者创建
我们现在可以看到,值 6 来自以下等式:

coef_ 中的向量数方程—作者创建
在我们的例子中,我们有 4 个类,因此等式如下:

具有实际值的等式—由作者创建
希望这一点现在已经很清楚了。然而,这给我们留下了后续问题:哪个coef_向量和intercept_值对应于哪个标签?正如我在上面简要提到的,OVO 方法将每一类进行比较。这意味着我们比较
标签 1 对标签 2
标签 1 对标签 3
标签 1 对标签 4
标签 2 对标签 3
依此类推……直到所有的标签都相互比较完毕,(我将在后面的文章中进一步详细介绍新数据点是如何分类的)
我们的每个coef_向量都代表了这样一种比较。但是,我们怎么知道哪个向量对应于哪个比较呢?
为了弄清楚这一点,我们可以使用下面的代码来获得数据和超平面的可视化表示:
line_colors = {0:'red', 1:'blue', 2:'green', 3:'yellow', 4:'black', 5:'gray'}number_of_coefficients = len(clf.coef_)
figure, axis = plt.subplots(3, 2)
row = 0
col = 0for j in range(number_of_coefficients):
for i in range(j+1):
w = clf.coef_[i]
w = [w[0], w[1] + 0.0001] #adding 0.0001 just to make sure we
#don't devide by 0
a = -w[0] / w[1] xx = np.linspace(-4,4)
yy = a * xx - clf.intercept_[i] / w[1]
axis[row, col].plot(xx, yy, label=f'h{i}', c=line_colors[i])
axis[row, col].set_xlim([-4, 4])
axis[row, col].set_ylim([-4, 4])
axis[row, col].scatter(X[:, 0], X[:, 1], c = y) row = row + 1 if col == 1 else row
col = col + 1 if col != 1 else 0plt.show()
(为了避免文章篇幅过长,我没有对代码片段进行解释,只需注意它用于使用 *coef_* 和 *intercept_* 值创建情节)
这会产生以下情节:

绘制一个接一个的超平面—由作者创建
这里发生了相当多的事情,所以让我们仔细地浏览一下。
每个子情节增加一个额外的超平面。这样,我们可以通过检查刚刚添加的超平面的位置来了解哪个超平面对应于哪个比较。以下是超平面的描述:
- 红线比较蓝等级和绿等级。
- 蓝线比较蓝等级和红等级。
- 绿线比较蓝色等级和黄色等级。
- 黄线比较绿色等级和红色等级。
- 黑线比较绿色等级和黄色等级。
- 灰色线比较红色等级和黄色等级。
当对新的数据点进行分类时,将针对这些超平面中的每一个来测量该点。每次比较后,我们记下它属于哪个标签,并将其添加到该标签的累积分数中。最后,新的数据点被简单地标记为具有最高分数的类。
每个超平面以如下方式对新数据点进行分类:如果该点位于超平面的右侧,则该超平面将该点分类为其比较中的第一个标签。相反,如果点在平面的左边,那么它被标记为超平面比较的第二类。
****快速举例:以红线为例,它的第一个标签是蓝色等级,第二个标签是绿色等级。如果一个给定的数据指向线的右边,那么这个超平面将其分类为蓝色。
****注意:为了有一个“右”或“左”的超平面,它们需要有一个方向。本例中的 红色 和 灰色线条 指向右边,蓝色 和 黑色 线条
具有新观点的示例
让我们看一个例子。我们可以绘制一个新的点[1.5, -2.5](图中的棕色点),我们预计该点将被归类为蓝色点。下面是一个让它更加明显的图:****

超平面和一个新的预测点—由作者创建
逐一进行比较(超平面):
- 红线将其归类为蓝色****
- ****蓝线将其归类为蓝色
- ****绿线将其归类为蓝色
- 黄线将其归类为红色****
- ****黑线将其归类为绿色
- 灰色线将其归类为红色****
新点分为蓝色 3 次,红色* 2 次,绿色 1 次。因此,我们用蓝色标签对点进行分类。*****
如果我们使用拟合的模型进行预测,我们会得到同样的结果。可以通过以下方式完成:
***new_point = np.array([[1.5, -2.5]])
print(clf.predict(new_point))>> **['blue']*****
我们得到了预期的蓝色标签。
如何确定比较的顺序
我希望上面的例子对你来说很容易理解,并且你明白我们是如何得出关于哪些超平面对应于哪些比较的结论的。然而,这是一个简单的实验,数据很容易分离。这允许我们通过视觉检查来确定超平面和coef_属性之间的关系。然而,真实世界的数据很可能不那么容易处理。所以,你可能想知道是否有一个系统,来决定比较的顺序?
换句话说,我们能确定由coef_[0]代表的超平面总是将蓝色类和绿色类分开的那个超平面吗?****
我得出的结论是,排序是由标签排序决定的。在标签是单词的情况下,我注意到前三个超平面,coef_[0]、coef_[1]、coef_[2]都与蓝色标签有关。这支持了我的理论,因为蓝色的将按字母顺序排列为第一个标签。以下两个标签coef_[3]和coef_[4]与绿色标签有关,因为‘g’排在‘b’之后,‘r’之前,‘y’*之前。*******
这可能有点难以理解,但是想一想,它可能会对你有意义。
有了这个关于模型的超平面如何工作的信息,你将对幕后发生的事情有一个更好的理解。您还可以使用帖子中的代码片段来绘制超平面。
感谢花时间阅读这篇文章!我希望它对你有用。如果你有任何问题,意见或注意到代码或文本中的错误,我鼓励你联系我。
如果你是一个渴望更多的好奇的开发者,那么下面这些帖子可能也会让你感兴趣:
***https://betterprogramming.pub/what-is-up-with-the-numbers-in-python-26d8d36e129b ***
了解 Neo4j GDS 链路预测(带代码演示)
常见图形机器学习任务的实践教程
介绍
Neo4j GDS 机器学习管道是一种直接在 Neo4j 基础设施中执行复杂机器学习工作流的便捷方式。因此,他们可以节省大量管理外部基础设施或依赖关系的工作。在这篇文章中,我们将探索一个常见的图形机器学习任务:链接预测。您可以使用流行的开放数据集(开放学术图)轻松重现这些步骤,以准备输入数据并逐步配置管道。我将详细阐述使用这样一个管道的主要概念和缺陷,以便我们理解在引擎盖下发生了什么。最后,我将给出一些关于当前局限性的想法。
动机
链接预测是图形环境中的常见任务。它用于预测数据中缺失的链接,以丰富数据(建议)或补偿缺失的数据。
链接预测问题可以解释为二元分类问题:对于图中的每一对节点,我们问:它们之间是否应该存在关系?这个问题陈述确实非常冗长——我们需要对图中的每个节点进行分类。

图链接预测可以解释为二元分类问题。(图片由作者提供)
领域
作为领域示例,我将使用一个众所周知且常用的数据集进行链接预测:开放学术图。您可以下载数据集并重现这里描述的步骤。我对数据进行了过滤,只包含少数出版物,并将其加载到 Neo4j 图表中。生成的图表模式如下所示:

图片:引文图表模式,包括作者、文章、发表地点和关系、参考文献、发表于。(图片由作者提供)
此外,我已经将数据转换成同质的合著图。两个作者之间的合著边缘表明他们以前曾在一个出版物上合作过。合作的数量成为一个优势属性。我们将由作者和文章组成的二分图转换成由节点Author和边CO_AUTHORED组成的齐次图。

图片:合著图。从作者和文章的二分图中,我们得到了一个同质的作者图。(图片由作者提供)
我们将使用合著关系图来预测两个作者是否有可能在未来合作,因为他们之前已经合作过。换句话说:我们想要预测新的CO_AUTHORED-边缘。
总体管道概述
我们将使用 Neo4j GDS 链路预测管道来完成这项任务(更重要的是,了解如何使用它们😇).让我们看一下管道的必要步骤。你可以在这里找到官方文件。

图片:管道概述(图片由作者提供)
投影子图第一步是从我们的一般图创建一个内存子图。我们仅在管道的最后步骤中使用投影图,但是,如果我们先创建它,可能会更容易理解。可以通过过滤节点标签和关系类型,甚至通过定制的密码查询来创建投影。使用链接预测管道的一个重要注意事项是,它们仅在无向边上工作。因此,我们需要将有向边投影到无向边上。
创建管道创建管道非常简单。我们只需要提供一个名称来引用。
添加节点属性提高预测质量的最重要步骤之一是使用表达特征,使模型能够做出正确的决策。添加节点属性步骤允许在管道内执行 GDS 图算法(例如,节点嵌入)以生成新的节点特征。算法的限制(目前)是它们需要为节点生成特征——而不是边(我们将在后面讨论)。
添加链接特征特征向量步骤是传统 ML 任务的主要区别之一。我们希望对一对节点进行分类,而不是对单个数据点进行分类。为了为每对节点生成链接特征向量,我们使用两个节点的特征向量,并将它们组合成单个向量。链接-要素-矢量步骤定义了如何根据单个结点要素为结点对生成链接矢量。首先,我们需要指定使用哪些节点特性。其次,我们有三种组合器功能可供选择:
- Hadamard =元素乘积,
- 余弦=余弦相似性(向量之间的角度),
- L2 =距离的平方。

图像:链接特征步骤:使用三个组合器函数之一将节点向量组合成一个链接向量。(图片由作者提供)
数据分割 …看起来像是这条管道中的一个微不足道的点。然而,了解你的最终模型是否有用是很重要的一部分。因为我们是在对节点对而不是单个节点进行分类,所以对于图中的每个数据点,我们最终会有许多组合(|V| - 1)。除非我们正在处理一个接近全连通图,否则自然会有许多组合(|V| - 1 - degree(n))没有每个节点的边。这些最终会成为我们模型的负样本,因此预测NO-EDGE的模型会产生高精度。

图像:样本图中的负样本(作者提供的图像)
为了避免这种情况,我们采取了以下措施:
- 获取构成图中阳性样本的所有现有边(
POS) - 将阳性样本分成三组(
POS = POS_TEST + POS_TRAIN + POS_FEATURE) - 对于三组中的每一组,从图表中抽取反面例子(例如
TEST = POS_TEST + NEG_TEST)。

图:将数据分割成平衡的数据集。阳性样本被分成三个不同的集合:测试、训练和特征。(图片由作者提供)
FEATURE集合用于计算之前配置的节点特征。这是一个重要的区别,可以防止数据泄漏到训练集和测试集中。我们将在常见缺陷部分对此进行一些讨论。
关于负样本的注意事项:通常我们希望为每个正样本找到一个负样本。寻找好的阴性样本的方法本身就是一个研究课题,并且存在许多方法。Neo4j GDS 仅支持随机采样。
模型配置&训练目前,在 Neo4j GDS 中存在两种分类方法:逻辑回归和随机森林。我们可以将具有不同参数空间的多个候选模型添加到管道中。在训练期间,每个候选模型都要经过 k 重交叉验证,以找到最佳的模型配置。培训的另一个重要参数是要使用的评估指标。到目前为止,GDS 为逻辑回归提供了一个度量标准:AUCPR(曲线下面积精确回忆)。获胜的模型存储在所提供的模型标识符下,以供以后使用。
预测预测步骤应用获胜模型来预测图中没有边的所有节点对之间的缺失边。由于这会导致评估模型的节点对数量非常大,因此该过程可能会花费相当长的时间。像往常一样,我们可以选择如何使用结果:流式或变异。mutate选项将在图形投影中创建预测关系。
常见陷阱
产生有偏见的结果的最常见的陷阱是将测试数据泄漏到模型训练中。对于传统的机器学习任务来说,这不是太大的问题,因为数据点具有直接关联的特征。然而,在图形环境中,我们通常希望生成考虑图形中的连接的特征。因此,这些算法分析节点的周围环境。换句话说:算法探索关系和其他数据点,以确定节点在更大环境中的角色、连通性、重要性或嵌入性。然后,生成的要素会获取有关数据网络结构的知识。
当训练模型以确定两个数据点之间是否应该存在边时,我们希望隐藏训练步骤中的一些数据,以便可以可靠地测试模型性能。应该在不知道测试边缘的情况下训练模型。相反,我们希望获得一种模型,该模型能够基于独立于这种边缘的特征做出好的决定。因此,在生成我们的特征时,我们需要额外注意:没有训练或测试数据应该进入特征生成步骤。
实施封装和防止数据泄漏的方法是将输入数据分成前面提到的三个集合:训练、测试和特征。我们使用特征集来计算管道中使用的节点属性。由于这三组是不同的,我们可以确定用于训练或评估的边都不会影响我们的特征生成。
然而,目前完成这种严格封装的唯一方法是只在管道中生成特性。管线外的特征生成没有关于数据分割的信息,因此可能捕获模型试图预测的图形结构。
实施
考虑
GDS 管道在几行代码中封装了大量的复杂性。它尽了很大的努力来自动化这样的工作流程,并防止工程师陷入其中的一个陷阱。为了获得有用的结果,理解链路预测中的概念和误差源仍然是必不可少的。
强封装意味着灵活性和定制性降低。以下 szenario 目前无法使用自动化工作流程:
不能将节点对特征添加到特征向量中。只有节点属性。
让我们考虑一下下面的例子:我们想要对一对作者(a1, a2)进行预测。除了考虑a1和a2的节点属性(我们将把它们组合成一个向量),我们还想考虑这对向量本身的特征。例如,两位作者(a1, a2)之前是否引用了同一篇论文,这可能是对模型有价值的信息。

图片:一个节点对特征:两个作者共同引用两篇论文。(图片由作者提供)
这将是作者对的特征,而不是每个作者单独的特征;每个作者都有多个值—每个作者一个值。显然,这个特征最好用图中的边来表示;这是一对特征。反过来,如果我们将这个特征添加到我们的对特征向量,我们就不需要像节点特征那样使用组合器。
这方面的主要挑战显然是,我们需要在各种数据分割上计算这样一个特征,以防止数据泄漏。因此,特征计算或者需要发生在管道内(其中数据分割是可访问的)。或者我们需要访问用于特征训练、训练和测试的分割。
这导致了当前管道的第二个限制:
无法配置自定义分割(功能、训练、测试)。
流水线允许随机配置正样本和负样本的比率。但是,我们不能按照自定义逻辑(例如时间戳)来分割数据。我们可能希望某个时间戳之前的数据出现在训练集中,而该时间戳之后的数据应该出现在测试集中。为了实现这一点,我们需要相应地标记我们的数据,并告诉管道如何在其流中使用标记。如上所述,这种自定义拆分对于防止自定义特征中的数据泄漏也是必要的。
结论
GDS 链接预测管道是一个非常好的工具,可以直接在 Neo4j 基础设施中设置复杂的机器学习工作流。它通过在框架边界内启用工作流来减少外部工具和资源所需的开销。管道使用简单,并防止许多陷阱,否则需要相当多的思考。目前,相对于定制解决方案,API 在灵活性方面有其局限性。然而,它目前处于测试版本,GDS 团队可能会在未来的版本中对其进行改进。
参考资料和资源
- 开放学术图表数据集,https://www.aminer.cn/oag-2-1,许可:ODC-BY
- 、、姚立民、、、、。学术社交网络的提取和挖掘。在 第十四届 ACM SIGKDD 知识发现与数据挖掘国际会议论文集 ( SIGKDD'2008 )。第 990-998 页。[ PDF ] [ 幻灯片 ] [ 系统 ] [ API ]
- 阿纳·辛哈、沈智宏、宋洋、马浩、达林·艾德、许宝俊(保罗)和王宽三。2015.Microsoft 学术服务(MAS)和应用程序概述。《第 24 届万维网国际会议论文集》(WWW '15 Companion)。美国纽约州纽约市 ACM,243–246。
- 张,,刘潇,,,董,,姚,,顾小涛,,,李锐,王宽三。OAG:连接大规模异构实体图。在 第 25 届 ACM SIGKDD 知识发现与数据挖掘国际会议论文集(KDD’19)。
理解神经网络嵌入
原文:https://towardsdatascience.com/understanding-neural-network-embeddings-851e94bc53d2
深入探究神经网络嵌入

尼克·希利尔在 Unsplash 上的照片
我在之前关于向量数据库和 ML 应用开发的博客文章中已经提到了嵌入/嵌入向量的主题,但是还没有深入研究嵌入和嵌入模型如何工作背后的一些理论。因此,本文将致力于更深入地研究嵌入/嵌入向量,以及它们在现代 ML 算法和流水线中的应用。
快速注意——这篇文章需要深度学习和神经网络的中级知识。如果你还不太了解,我建议你先看看谷歌的 ML 速成班。课程内容对于理解 CV 和 NLP 的神经网络基础很有帮助。
快速回顾一下
通过嵌入对数据进行矢量化,本质上是一种降维的方法。传统的降维方法— PCA 、 LDA 等。—结合使用线性代数、内核技巧和其他统计方法来“压缩”数据。另一方面,现代深度学习模型通过将输入数据映射到潜在空间中来执行维度缩减,潜在空间即输入数据的表示,其中附近的点对应于语义上相似的数据点。例如,过去代表单个单词或短语的热点向量现在可以表示为维度显著降低的密集向量。我们可以通过 Towhee 库看到这一点:
% pip install towhee # pip3
% python # python3>>> import towhee
>>> text_embedding = towhee.dc(['Hello, world!']) \
... .text_embedding.transformers(model_name='distilbert-base-cased') \
... .to_list()[0]
...
>>> embedding # includes punctuation and start & end tokensarray([[ 0.30296388, 0.19200979, 0.10141158, ..., -0.07752968, 0.28487974, -0.06456392],
[ 0.03644813, 0.03014304, 0.33564508, ..., 0.11048479, 0.51030815, -0.05664057],
[ 0.29160976, 0.43050566, 0.46974635, ..., 0.22705288, -0.0923526, -0.04366254],
[ 0.14108554, -0.00599108, 0.34098792, ..., 0.16725197, 0.10088076, -0.06183652],
[ 0.35695776, 0.30499873, 0.400652 , ..., 0.20334958, 0.37474275, -0.19292705],
[ 0.6206475 , 0.50192136, 0.602711 , ..., -0.03119299, 1.1860386 , -0.6167787 ]], dtype=float32)
基于深度神经网络的嵌入算法几乎被普遍认为比传统的降维方法更强。这些嵌入在行业中的各种应用中使用得越来越频繁,例如内容推荐、问答、聊天机器人等。正如我们将在后面看到的,在神经网络中使用嵌入来表示图像和文本近年来也变得越来越流行。

由 DistilBERT 制作的可视化文本嵌入。请注意,尽管“football”和“football”这两个词都很常见,但“football”和“soccer”的关系却比“football”更近。
监督嵌入
到目前为止,我以前的文章使用了来自使用监督学习训练的模型的嵌入,即来自标记/注释数据集训练的神经网络模型。例如, ImageNet 数据集包含一组精选的图像到类别的映射,而问答数据集如 SQuAD 提供不同语言的 1:1 句子映射。
许多通过标记数据训练的著名模型使用交叉熵损失或均方误差。由于监督训练的最终目标是或多或少地复制输入数据和注释之间的 1:1 映射(例如,在给定输入图像的情况下输出类别令牌概率),因此从监督模型生成的嵌入很少使用输出层。例如,在 ImageNet-1k 上训练的标准 ResNet50 模型输出 1000 维向量,该向量对应于输入图像是第 N 个类标签的实例的概率。

LeNet-5,已知最早的计算机视觉神经网络架构之一。图片 by D2L.ai , CC BY-SA 4.0 。
相反,大多数现代应用程序使用倒数第二层激活作为嵌入。在上图(LeNet-5)中,这对应于标记为FC (10) (10 维输出层)和FC (84)的层之间的激活。这一层离输出足够近,可以准确地表示输入数据的语义,同时也是一个相当低的维度。我也见过计算机视觉应用程序使用模型中更早层的池激活。这些激活捕获输入图像的低级特征(角、边、博客等...),这可以提高徽标识别等任务的性能。
编码器和自我监督
使用带注释的数据集的一个主要缺点是它们需要注释(这听起来有点愚蠢,但是请原谅我)。为一组特定的输入数据创建高质量的注释需要一个或许多人花费数百甚至数千个小时的时间。例如,完整的 ImageNet 数据集包含大约 22k 个类别,需要 25000 人来管理。让事情更加复杂的是,许多带标签的数据集经常包含意想不到的不准确、明显的错误,或者精选结果中的 NSFW 内容。随着这种实例数量的增加,由监督学习训练的嵌入模型生成的嵌入质量显著下降。

来自 Unsplash 数据集的标记为“希望”的图像。对于人类来说,这是一个非常明智的描述,但它可能会导致模型在训练过程中学习错误的特征类型。由 Lukas L 拍摄。
另一方面,以无人监督的方式训练的模特不需要标签。鉴于每天生成的文本、图像、音频和视频数量惊人,使用这种方法训练的模型基本上可以访问无限量的训练数据。这里的诀窍是开发正确类型的模型和培训方法来利用这些数据。一种令人难以置信的强大且日益流行的方式是通过自动编码器(或一般的编码器/解码器架构)。
自动编码器通常有两个主要组件。第一个组件是编码器:它将一些数据作为输入,并将其转换为固定长度的向量。第二个组件是解码器:它将向量映射回原始数据。这就是所谓的编码器-解码器架构:

编码器-解码器架构示意图。图片由 D2L.ai , CC BY-SA 4.0 。
对应于Encoder和Decoder的蓝框都是前馈神经网络,而State是期望的嵌入。这两种网络都没有什么特别之处——许多图像自动编码器将标准的 ResNet50 或 ViT 用于编码器网络,将类似的大型网络用于解码器网络。
由于自动编码器被训练为将输入映射到潜在状态,然后再映射回原始数据,因此无监督或自监督嵌入直接来自编码器的输出层,这与在完全监督下训练的模型的中间层相反。因此,自动编码器嵌入仅用于重建。换句话说,它们可以用来表示输入数据,但通常不足以表示语义,例如区分猫的照片和狗的照片。
近年来,除了传统的自动编码器之外,自我监督也有了许多改进。对于 NLP,具有上下文的预训练模型,即单词或字符在同一个句子或短语中相对于其他单词或字符出现的位置,是常见的,现在被认为是训练最先进的文本嵌入模型的事实上的技术。自我监督的计算机视觉嵌入模型也正在蓬勃发展;依赖于数据扩充的对比训练技术在应用于一般的计算机视觉任务时已经显示出很好的效果。 SimCLR 和 data2vec 是神经网络的两个例子,它们利用掩蔽和/或其他增强进行自我监督训练。
嵌入作为其他模型的输入
嵌入模型是非常独特的;它们不仅对一般的应用程序开发有价值,而且它们的输出经常用于其他机器学习模型。视觉变形金刚是一个很好的例子。得益于传统卷积神经网络所不具备的强大性能和巨大感受域,它们的受欢迎程度在过去两年里出现了爆炸式增长。视觉转换器的核心前提是将图像分成正方形小块,为每个小块生成嵌入,并将嵌入作为标准转换器的输入。令人惊讶的是,这对于图像识别非常有效。
另一个很好的例子是 OpenAI 的剪辑,这是一个大型神经网络模型,经过训练可以将图像与自然语言进行匹配。CLIP 的训练对象是来自互联网的大量数据,例如 Flickr 照片和相应的照片标题。

编码器及其相应的嵌入在 OpenAI 的剪辑模型中发挥了巨大的作用。图片由 OpenAI , CC BY-SA 4.0 。
CLIP 背后的方法实际上相当简单。首先,CLIP 将图像和文本映射成一种潜在状态(即一种嵌入);这些潜在状态然后被训练以映射到相同的空间,即如果文本能够准确地描述图像,则文本嵌入和图像嵌入应该彼此非常接近。当涉及到 ML 时,困难在于细节,如果没有相当大的数据集和一些超参数调整,CLIP 实际上很难实现。
CLIP 被用作 DALLE(和 DALLE-2)中的核心组件,DALLE-2 是 OpenAI 的文本到图像生成引擎。最近有很多关于 DALLE-2 的传言,但如果没有 CLIP 的代表性,这些都是不可能的。虽然 CLIP 和 DALLE 的结果令人印象深刻,但图像嵌入仍有很大的发展空间。某些高级语义,如对象计数和数学运算,仍然难以在图像嵌入中表示。
生成您自己的嵌入
我已经指出了 Towhee 开源项目几次,展示了如何使用它来生成嵌入以及开发需要嵌入的应用程序。Towhee 包装了来自各种来源(,[torchvision](https://github.com/pytorch/vision)等)的数百个图像嵌入和分类模型...)并接受各种不同技术的训练。Towhee 也有许多 NLP 模型,感谢🤗s 变压器库。
让我们后退一步,看看 Towhee 是如何在幕后生成这些嵌入的。
>>> from towhee import pipeline
>>> p = pipeline('image-embedding')
>>> embedding = p('towhee.jpg')
使用到,从监督模型生成嵌入的默认方法是简单地移除最终分类或回归层。对于 PyTorch 模型,我们可以使用下面的示例代码片段来实现:
>>> import torch.nn as nn
>>> import torchvision
>>> resnet50 = torchvision.models.resnet50(pretrained=True)
>>> resnet50_emb = nn.Sequential(*(list(resnet50.children())[:-1]))
上面代码片段中的最后一行重新创建了一个前馈网络(nn.Sequential),它由resnet50 ( resnet50.children())中除最后一层([:-1])之外的所有层组成。也可以使用相同的层移除方法来生成中间嵌入。对于用对比/三重损失训练的模型或作为自动编码器,该步骤是不必要的。
基于(对于 CV)和[transformers](https://github.com/huggingface/transformers)(对于 NLP)的模型也保持了它们自己的方法,使得特征提取变得容易:
>>> import numpy as np
>>> from PIL import Image
>>> import timm
>>> image = numpy.array(Image.open('towhee.jpg'))
>>> model = timm.models.resnet50(pretrained=True)
>>> embedding = model.forward_features(img)>>> from transformers import AutoTokenizer, AutoModel
>>> tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
>>> model = AutoModel.from_pretrained('bert-base-uncased')
>>> inputs = tokenizer('Hello, world!', return_tensors='pt')
>>> outputs = model(**inputs)
>>> embedding = outputs.last_hidden_state()
Towhee 为timm和transformers维护包装器:
>>> import towhee
>>> text_embedding = towhee.dc(['Hello, world!']) \
... .text_embedding.transformers(model_name='distilbert-base-cased') \
... .to_list()[0] # same as intro example
...
>>> img_embedding = towhee.glob('towhee.jpg') \
... .image_decode() \
... .image_embedding.timm(model_name='resnet50') \
... .to_list()[0]
...
其他资源
如果您想阅读更多内容或从另一个角度研究这个主题,我整理了一个简短的(非常不完整的)关于嵌入式的其他资源列表:
- Milvus 文档提供了嵌入矢量的概述(因为它与存储和矢量数据库相关)。
- OpenAI 在文本嵌入上维护页面,您可以查看。
- Will Koehrsen 提供了一个嵌入式的伟大概述。它有点过时(2018),但仍然是一个很好的资源。
- 在推特上查看嵌入技术是如何被使用的,以获得推荐。
- 反向图像搜索是嵌入向量的许多应用之一。本教程展示了如何在几分钟内建立一个。
如果这篇文章对你有帮助,请考虑在 Twitter 上关注我。在接下来的文章中,我将简要介绍相似性搜索和向量索引算法。敬请期待!
原载于 2022 年 4 月 30 日 https://frankzliu.comhttps://frankzliu.com/blog/understanding-neural-network-embeddings。
理解 Python 中的运算符
原文:https://towardsdatascience.com/understanding-operators-in-python-7116abb06941
了解 Python 运算符的基础知识

丹·洛马尔在 Unsplash 上拍摄的照片
Python 操作符是特殊类型的函数,在 Python 编程语言中执行特定的、主要是数学的运算。
什么是运算符?
在 Python 中,有所谓的操作符,像函数一样,执行固定定义的动作。然而,函数不一定要用传统的方式定义,而是可以使用更短的操作符。Python 操作符已经安装在 Python的基础版本中,不需要通过导入额外的模块来添加。
更一般地说,Python 操作符由左侧、操作符和右侧组成。操作员决定左右两侧会发生什么。根据来源的不同,这也被称为操作数。我们将在本文中使用这个术语。
在 Python 中有无数的操作符,我们将在接下来的章节中进一步讨论。其中之一就是数学上的不同,可以用“-”这个字符来称呼。
在我们的例子中,负号是操作符。两个操作数是数字 9 和 3。最后,数字六是运算的结果。
Python 运算符有哪些类型?
Python 区分不同类型的操作符,我们将在接下来的章节中详细解释。
比较运算符
比较运算符可以将两个操作数相互比较。它们总是返回一个布尔值(真或假)作为结果。

Python 中的比较运算符|来源:作者
算术/数学运算符
数学或算术运算符在 Python 中实现基本的数学函数。为此,基本的计算类型,如和、差、乘等。都被盖住了。

Python 中的数学运算符|来源:作者
逻辑运算符
逻辑运算符也来源于数学,借助逻辑 and 和 Or 实现条件的连接。当两个语句中的一个为真时,用“或”连接的两个语句为真。当两个语句都为真时,用“与”连接的两个语句为真。

Python 中的逻辑运算符|来源:作者
恒等运算符
Python 运算符“is”用于检查两个变量是否具有相同的赋值。然后,运算符相应地返回 True 或 False。这也可以用来在脚本中动态检查变量是否相同。
子集运算符
子集操作符可以用来检查一个集合中是否存在一个或多个元素,比如一个 Python 列表。
否定可以用来检查对立面,即元素是否不是集合的一部分。
赋值运算符
从变量的定义中,我们已经知道了最基本的赋值操作符。在“=”的帮助下,我们可以给变量赋值。此外,还有其他赋值运算符,在它们的帮助下,例如,求和或乘积可以用缩写形式书写。

这是你应该带走的东西
- Python 运算符是特殊类型的函数,主要执行数学函数。
- Python 操作符允许快速调用,无需定义新函数。
- 有不同的类型,如逻辑运算符或赋值运算符。
如果你喜欢我的作品,请在这里订阅https://medium.com/subscribe/@niklas_lang或者查看我的网站* 数据大本营 !还有,medium 允许你每月免费阅读 3 篇 。如果你想让无限制地访问我的文章和数以千计的精彩文章,不要犹豫,通过点击我的推荐链接:【https://medium.com/@niklas_lang/membership】每月花$5获得会员资格*
**</5-basic-commands-for-working-with-python-lists-3088e57bace6> </8-machine-learning-algorithms-everyone-new-to-data-science-should-know-772bd0f1eca1> </4-basic-commands-when-working-with-python-dictionaries-1152e0331604> **
理解光学和 Python 实现
原文:https://towardsdatascience.com/understanding-optics-and-implementation-with-python-143572abdfb6
无监督学习
理解光学和 Python 实现
在这篇文章中,我简单地谈谈如何理解一种无监督学习方法,OPTICS,以及它在 Python 中的实现。

OPTICS 代表排序点识别聚类结构。它是一种基于密度的无监督学习算法,由开发 DBSCAN 的同一个研究小组开发。正如我在上一篇文章中所讨论的,DBSCAN 有一个很大的缺点,那就是它很难在变化密度的数据中识别聚类。但是,光学不要求密度在整个数据集内保持一致。
下面是一个简单的例子来说明光学系统优于 DBSCAN 的优势。

不同密度的示例数据。光学性能优于 DBSCAN。(图片由作者提供)
在上面的例子中,DBSCAN 中的恒定距离参数 eps 只能将 eps 内的点彼此视为邻居,显然错过了图右下方的集群(阅读此贴了解更多关于 DBSCAN 中参数的详细信息)。如果我们使用一个更大的 eps 参数,该集群可以被识别,但它也可能将其他两个顶部集群合并在一起。所以,很难在 DBSCAN 中预定义一个完美的EPS;然而,在光学中,我们不需要担心这一点。
在本帖中,我将谈谈如何理解这个算法,以及如何用 Python 实现它。希望文章有帮助。
理解光学
正如您所记得的,在 DBSCAN 中应用了单一的距离截断来确定两个数据点是否彼此靠近(相邻)。但是如上例所示,这可能不是理想的解决方案,因为一个集群中的“长”距离可能是另一个集群中的“短”距离。
就像全国的房价一样,30 万的房子在我住的镇上相当贵,但在曼哈顿却很便宜。这就是为什么当我们看不同组(群)的房子(数据点)时,记住不同的价格标准是很重要的。
那么,在光学中,我们如何将不同群体对距离的不同“标准”考虑在内呢?最直接的方法之一就是与本地环境(上下文)进行比较。具体来说,如果我们有兴趣确定从 A 点到 B 点的距离是大还是小,我们可以将距离(A 到 B)与局部环境中所有成对的距离进行比较。

比较从 A 到 B 到当地环境的距离(图片由作者提供)
正如我们从上面的图解中看到的,与本地环境中的所有成对距离相比,从 A 到 B 的距离并不算大,对吗?所以,我们应该把 A 和 B 看作邻居(A 和 B 在距离上很近)。
在前面判断 A 到 B 的距离大不大的步骤中,你可能已经注意到了几个不是很清楚的东西。举个例子,
1.如何定义一个“局部区域”?尤其是当没有像示例中所示的这样清晰的数据分离时?
2.如何确定一个局部环境是否值得调查?如果没有足够的配对距离让我比较呢?
3.真的有必要考虑区域中所有成对的距离吗?例如,比较从 A 到 B 的距离和从 B 到 D 的距离是否公平(特别是当我们已经知道 B 和 D 似乎是局部区域的对立边界时)?
如果你有上面列出的问题,恭喜你!因为你思考的方向是正确的!
首先,要定义一个“局部区域”,我们需要一个中心和一个半径(与 DBSCAN 中的概念相同)。例如,A 点是我们想参观的第一个点,我们想参观 A 的本地。我们设置 A 为圆心,预定义值 r 为半径,局部区域定义为圆内的面积(如下图)。

定义我们想要调查的“局部区域”(图片由作者提供)
在上面的示例图中,显然,r 是围绕点 A 的半径的良好选择,因为它完美地覆盖了数据点的“局部聚类”。然而,在现实中,我们看不到数据的全貌,或者我们没有那些“局部聚类”的清晰分离。因此,通常将 r 设置为一个相对较大的值是一个很好的做法,这样可以避免当你站在 a 点时出现在一个非常小的区域。
其次,为了确定一个区域是否值得研究,我们设置了一个最小数量的数据点作为标准。例如,只有在至少有 N 个数据点(包括中心本身)的情况下,上一步定义的“局部区域”才值得研究。

P 点不值得调查 N=3(图片由作者提供)
在上面的图中,我们设置 N= 3,这意味着一个区域只有在超过 3 个数据点时才值得研究。因此,点 P 周围的区域不会作为“局部区域”进行调查,因为它没有任何邻居,并且我们无法比较任何成对的距离。
第三,你可能已经注意到,当我们想要确定从 A 到 B 的距离是否很大时,没有必要计算“局部区域”内的所有成对距离。让我们再来看看局部区域中的成对距离,

将局部区域中的所有成对距离简化为成对距离。(图片由作者提供)
如上图左图所示,A 点和 B 点在图中“相邻”,但 B 点和 D 点不相邻。毫无疑问 AB 比 BD 短,所以比较 A 到 B 的距离和 B 到 d 的距离既不公平也没有必要,有没有办法让我们只考虑那些“相邻”点之间的距离(如上面右图所示)?
实际上,在上面的图中,减少从左到右的比较是非常理想的,因为我们有更多的数据点,位置更加复杂,我们很难手动选择这些边缘与 A 到 b 的距离进行比较。
或者,我们可以用迭代的方式来解决这个问题吗?比如我们逐个访问局部区域内的点,每次只记录当前点到其相邻点的距离然后移动到下一个点。在访问了该区域的所有点之后,我们记录了所有相邻的距离。通过这种方式,我们不必提前看到距离的全貌,也不必在访问完所有成对的距离后手动选择这些距离。
这是一个很好的方法,但是我们能进一步降低复杂度吗?
好吧,我不想让你感到困惑,所以让我再次陈述这个问题。我们希望将 A 和 B 之间的距离与所有其他成对的“相邻”距离进行比较,以确定 A 和 B 是否彼此接近。
你还记得为什么我们想知道 A 和 B 之间的距离与其他成对距离相比是大还是小吗?我们想知道 B 是否属于当前访问的数据点聚类(以点 A 为中心的聚类)。换句话说,我们希望使用距离度量来确定一个点对于一个数据点集群是否是可达的。
可达或不可达。嗯……我们能给每个点分配一个可达性分数吗?那会简单很多吗?可达性分数大的地方是集群不能到达的地方,可达性分数小的地方是集群内部可以到达的地方。
如果你在这里感到有点失落,不要担心。请跟我走一遍整个过程,我相信你会明白我在说什么。
以下是步骤:
首先,我们访问 a 点周围的局部区域。我们计算从中心点到该区域中所有其他点的距离,AB、AC、AD、AE 和 AF,并记录它们。我们对记录的距离进行排序,并将这些距离视为当前可达性得分。我们还需要将 A 推送到最终的分数列表,以便不再访问它。作为初始点,A 是没有赋值的,所以让它保持空白(或者我们可以给它赋一个超大的值)。

第一步。(图片由作者提供)
接下来,我们访问可达性分数最小的点(点 D),该点也是离当前中心点最近的点。我们使用这个新点作为中心点,并计算从当前中心点到其邻域内没有被访问过的其他点的距离。

第二步。(图片由作者提供)
如上图所示,我们只计算 D 到 C 的距离和 D 到 E 的距离,因为 A 已经被访问过了(不需要再计算)。然后,如果可用,我们用较小的分数更新当前可达性分数列表。这里,E 的新分数为 1,小于上一步中的分数(步骤 1 中 E=1.4)。所以,我们更新 E 的分数。但是 C 点的分数比第一步大,这意味着 C 从 A 比从 d 更容易到达。所以,我们在这一步保持 C 的分数不变。在没有调查的情况下,B 和 F 在该步骤中不被更新。
请记住,我们希望记录每个点的最小可达性分数,以便声明它是否属于当前集群,因此当有更小的分数可用时,我们不能对 C 使用大的分数。如果我们仅仅因为没有使用 C 点的最佳得分而将其排除在集群之外,这对于 C 点是不公平的。
第三,我们移动到 E,因为 E 在当前列表中具有最小的可达性分数。

第三步。(图片由作者提供)
如上图所示,我们只需要计算从 E 到 F 的距离,因为在前面的步骤中已经执行了 A 和 D。我们为 F (F = 1.9)得到一个稍小的可达性分数,所以我们在当前分数列表中更新它。
接下来,B 点具有最小的可达性分数,因此我们在该步骤中将其用作当前中心点。

第四步。(图片由作者提供)
步骤 4 中没有更新可达性分数,因为我们找不到更小的值。然后我们以 C 为中心点进行第五步。

第五步。(图片由作者提供)
然后我们以点 F 为中心点,将 F 的可达性得分输出到列表中。

第六步。(图片由作者提供)
到目前为止,我们已经访问了当前本地的所有六个点,并获得了最终的可达性得分列表。这样,我们就不会错过“相邻”点之间的任何成对距离。此外,我们得到了一个有序的分数列表,它度量了调查聚类中的点的可达程度。
上面描述的过程是否比计算区域中所有成对的距离简单得多?
实际上,我们可以将这个过程应用于整个数据集。比如以 F 为中心点参观后,我们在当地就没有其他未参观的点了。然后,下一个中心点可以是数据集中的任何其他数据点。
如果我们按照参观的顺序画出一条连接中心点的可能的完整路径,它可能类似于下图。

整个数据集中的中心点路径(图片由作者提供)
您可以将路径想象为一条贪婪的蛇,它想通过总是咬下一个最近的球(在分数列表中)来吃掉图中的所有球(数据点)。
右上角的单个点与中间聚类的预定义邻域(半径为 r)有一个“邻居”,因此在橙色部分的最后一步中对其进行了访问。但是,左下方的点根本没有“邻居”,所以从来没有调查过,因为它不符合我们的标准,即一个区域只有至少有 N = 3 个邻居(包括它自己)才值得调查。
完成所有这些后,我们得到了一个可达性分数列表,我们可以用它做什么呢?别忘了我们的目标是定义一个点是否属于局部聚类。例如,这是我们得到的分数列表,

分数列表(图片由作者提供)
如果我们将分数绘制成条形图,我们可以将它们可视化如下。

可达性得分图(作者图片)
您可能已经注意到,右边柱状图中的三个谷与左边的三个集群相对应。因此,使用最终的可达性分数列表,我们能够检测到聚类,即使它们具有不同的密度。
此外,您可以发现更密集的集群(左侧面板左上角的棕色集群)对应于条形图上更深的山谷(右侧面板上用棕色圈出的山谷)。一个更稀疏的集群(左侧面板右下方的蓝色集群)对应于一个相对较浅的山谷(柱状图最左侧的圆圈)。
请记住,我们将一个值得参观的街区定义为至少有 3 个邻居的区域。我们可以把这三个邻居视为邻里关系的核心。实际上,我们并不太关心这些点是否位于核心,因为它们显然属于这个集群。因此,通常对于核心中的所有点,我们将核心大小设置为它们的可达性分数。
例如,在下图中,在以 A 为中心、半径为 r 的区域中有六个点。该区域的核心由 A、D 和 E 组成,因为 D 和 E 是距离 A 最近的两个点(N = 3)。我们知道 D 在核心之内,所以不用记录 A 到 D 的确切距离(一个很小的值)。我们可以简单地将核心大小(核心的半径)指定为点 d 的得分。

核心尺寸以橙色突出显示。用蓝色突出显示的邻近区域。(图片由作者提供)
在通过核心大小近似核心内的点的分数之后,我们可以从最终可达性分数列表中得到的最小值就是核心大小。我们为什么要这么做?第一,避免记录那么多无用的微小微小值;第二,使柱状图中的波谷看起来更平滑(如下所示)。

将微小分数设置为分数大小。(图片由作者提供)。
我没有使用任何来自光学的概念,但上面描述的程序正是光学。如果你一直往下看,你已经理解了光学。
让我从教科书中抽出一些描述,并简要说明它们是如何嵌入我上面的解释中的。
光学算法
大部分教材都是这样开头的,“光学的基本思想类似于 DBSCAN 但是比 DBSCAN 多了两个概念”。让我用一些官方词汇来描述光学中的这两个概念,并告诉你这些复杂的术语实际上指的是我上面的简单语言描述。
在光学中,每个点被分配一个核心距离,它描述了到第 MinPts 最近点的距离,以及从点 p 到另一个点 o 的可到达距离,该距离或者是 o 和 p、之间的距离,或者是 p 的核心距离,以较大者为准。
核心距离就是我所描述的核心大小,它只是作为近似核心内部点的距离的阈值。可达性距离就是我们记录的每个点的可达性分数。
这是教科书上的算法伪代码。
**function** OPTICS(DB, eps, MinPts) **is**
**for each** point p of DB **do**
p.reachability-distance = UNDEFINED
**for each** unprocessed point p of DB **do**
N = getNeighbors(p, eps)
mark p as processed
output p to the ordered list
**if** core-distance(p, eps, MinPts) != UNDEFINED **then**
Seeds = empty priority queue
update(N, p, Seeds, eps, MinPts)
**for each** next q in Seeds **do**
N' = getNeighbors(q, eps)
mark q as processed
output q to the ordered list
**if** core-distance(q, eps, MinPts) != UNDEFINED **do**
update(N', q, Seeds, eps, MinPts)
我知道这很难理解。所以,忽略它,和我前面几段用的步骤一模一样。这里光学最终输出的是可达性距离列表,也就是我们之前看到的可达性得分列表。
实际上,当可达性距离柱状图生成时,光学器件停止工作。最终的聚类步骤需要手动执行,这就是为什么严格来说,OPTICS 是而不是一种聚类方法,而是一种显示数据集结构的方法。
Python 中的实现
用 Python 实现光学非常容易,
**from** **sklearn.cluster** **import** OPTICS
optics_clustering = OPTICS(min_samples=3).fit(X)
如果您想知道数据点的最终标签,请使用
optics_clustering.labels_
很简单,对吧?
您可能已经注意到,在光学实现代码中没有名为 eps 的参数。那是因为这个参数只要不是超级小,其实并不会影响结果。想象一下,当你第一步站在 A 点时的最大视野。为什么?如果你再看一遍帖子,就很容易得到答案。
就是这样!如果你喜欢看我的帖子。请订阅我的媒介账号!
干杯!
参考资料:
https://scikit-learn.org/stable/modules/generated/sklearn.cluster.OPTICS.html https://en.wikipedia.org/wiki/OPTICS_algorithm
使用 Transformers、cleanlab 和主题建模了解文本数据中的异常值
用于审计文本数据集的开源 python 工作流

图片来自 Pixabay 的 LubosHouska 。
许多文本语料库包含异构文档,其中一些可能是异常的,值得更多地理解。特别是对于已部署的 ML 系统,我们可能希望自动标记与它们的训练数据不来自同一发行版的测试文档,并理解这些新文档中出现的、训练数据中没有的主题。
我最近作为一名 ML 工程师加入了 Cleanlab ,并很高兴地分享我们的开源库(在 AGPL-v3 许可下免费提供)可以在各种工作流中用于理解和改进数据集的许多方式。
这篇文章演示了如何通过一个基于开源 Python 包的工作流来发现和分析大型 NLP 语料库中的异常文本:Transformers、cleanlab、pytorch、UMAP 和 BERTopic 的 c-TF-IDF 实现。我们使用变压器来获得原始文本的良好表示,使用 cleanlab 来识别表示空间中的异常值,使用 UMAP + c-TF-IDF 来更好地理解这些异常。
我们将使用抱脸中枢上的 MultiNLI 数据集,这是一个常用于训练语言理解模型的自然语言推理数据集。
- 数据集包含多对句子(前提、假设),这些句子已经被标记为前提是否需要假设(
"entailment")或不需要假设("contradiction")。中性标签也包括在内("neutral")。 - 语料库被分成单个训练集和两个验证集。
- 训练集来源于 5 个不同的流派:
[fiction, government, slate, telephone, travel]。 - 匹配验证集来源于匹配训练集中的那些流派
- 另一个验证集,也称为错配验证集,来源于训练数据中不存在的其他流派:
[nineeleven, facetoface, letters, oup, verbatim]。 - 更多关于语料库的信息可以在找到。
这篇文章中的步骤可以应用于你自己的单词/句子嵌入模型和任何包含多个文本来源的数据集。
太长;没有运行(代码)
以下是我们从多个文本源中检测异常值并从中发现新主题的一般工作流程:
- 从 Hugging Face Hub 加载并预处理文本数据集,以创建 PyTorch 数据集。
- 应用预先训练的句子嵌入模型从文本中创建向量嵌入。
——这里我们利用了一个基于 SentenceTransformers 库中的连体神经网络的双编码器。 - 使用 cleanlab 库查找训练数据中的异常文本。
- 在验证数据中查找非来自训练集中数据分布的异常值示例。
—这类似于在新的数据源/提要中寻找异常。 - 选择一个阈值,以决定是否将某个示例视为异常值。
- 将所选的异常示例进行聚类,以找到异常的文本类型/来源。
- 识别异常类型/来源中的主题。
我们的主要目标是在数据集中找到非分布的例子,更多地关注新的类型/领域/来源。在 MultiNLI 数据集的情况下,使用这些方法,以下 4 个示例中只有 1 个被认为是异常的。(你能猜到是哪个吗?)

正如所预料的那样,我们的方法识别出的最有可能的异常值来自不匹配的验证集中的流派。
这些离群值示例中的许多基于它们各自的类型形成聚类,这可用于发现数据中的非分布主题。

让我们开始编码吧!
本文的剩余部分将展示我们如何用完全可运行的代码实现我们的策略!这里有一个笔记本的链接,你可以在那里运行相同的代码:链接
安装依赖项
您可以通过运行以下命令来安装所有必需的软件包:
!pip install cleanlab datasets hdbscan matplotlib nltk sklearn torch tqdm transformers umap-learn
接下来,我们将导入必要的包,将日志记录级别设置为“ERROR ”,并为可再现性设置一些 RNG 种子。
预处理数据集
MultiNLI 数据集可以通过其datasets api 从 Hugging Face Hub 获取。我们执行的唯一预处理是从数据集中移除未使用的列/特征。注意,在这篇文章中,我们不是而是查看数据集中的蕴涵标签(label)。更确切地说,我们只是简单地试图仅基于它们的文本来自动识别出非分布的例子。
为了评估我们的离群点检测算法,我们认为来自不匹配验证集的所有例子都是分布外的例子。我们仍然会使用匹配的验证集来发现自然出现的异常例子。我们的算法也不需要类型信息,这仅用于评估目的。
为了对数据格式有所了解,我们将看看每个数据集中的几个例子。
Training data
Genres: ['fiction' 'government' 'slate' 'telephone' 'travel']

Validation matched data
Genres: ['fiction' 'government' 'slate' 'telephone' 'travel']

Validation mismatched data
Genres: ['facetoface' 'letters' 'nineeleven' 'oup' 'verbatim']

将 NLI 数据转换成矢量嵌入
我们将使用预训练的 SentenceTransformer 模型在 MultiNLI 数据集中嵌入句子对。
从 NLI 数据(包括 MultiNLI)训练句子编码器的一种方法是在如下所示的连体 BERT 网络上添加一个 3 路 softmax 分类器。

带有 softmax 分类器的连体网络。图片取自 SBERT docs 。
我们将使用这种网络的(u,v,| u — v |)层的输出作为每个句子对的单个向量嵌入。这比将句子对连接成单个字符串更可取,因为这将增加截断模型输入和丢失信息(特别是来自假设的信息)的风险。
下一步,您必须从 Hugging Face Hub 中选择一个预训练的 tokenizer+模型,它将为网络的池化层提供令牌嵌入。
这是通过在 Hub 上提供模型的名称来实现的。
使用 cleanlab 查找数据集中的异常值
我们可以用 cleanlab 的OutOfDistribution类发现训练数据中的异常值。这将使最近邻估计器适合训练数据(在特征空间中),并基于每个示例与其 K 最近邻的平均距离返回每个示例的异常值分数。
# Get outlier scores for each of the training data feature embeddings
ood = OutOfDistribution()
train_outlier_scores = ood.fit_score(features=train_embeddings)
我们可以查看训练数据中最高的异常值。

接下来,我们使用拟合的最近邻估计量来获得验证数据的异常值,包括匹配和不匹配的验证集。
# Get outlier scores for each of the feature embeddings in the *combined* validation set
test_feature_embeddings = np.concatenate([val_matched_embeddings, val_mismatched_embeddings], axis=0)
test_outlier_scores = ood.score(features=test_feature_embeddings)
首先,我们看看验证数据中最大的异常值。

尽管组合的确认集在匹配和不匹配的风格方面是平衡的,但是大多数具有高异常值分数的例子来自不匹配的确认集([nineeleven, facetoface, letters, oup, verbatim])。
将此与另一端被认为不太可能是异常值的例子进行比较。

这些例子仅来自匹配确认集中的 5 个流派中的 4 个([fiction, government, telephone, travel])。唯一的例外是slate类型,但是第一个例子出现在列表的更下方。
评估异常值分数
实际上,如果我们已经知道不匹配的数据集包含与训练集中不同的流派,我们可以分别对每个流派进行离群点检测。
- 即从
nineeleven检测异常句子对,然后从facetoface检测异常句子对,等等。
为了保持简洁,现在让我们考虑组合验证集中的异常例子。
我们可以设置一个阈值来决定组合验证集中的哪些例子是异常值。我们将保守地使用训练数据中异常值分数分布的第 2.5 个百分点作为阈值。该阈值用于从组合验证集中选择样本作为异常值。

这将导致一些误报,如下所示。

聚类异常值
让我们假设我们不知道来自不匹配数据集的流派的内容。我们可以尝试从验证集中聚集离群值,看看我们是否能对不匹配的类型有更好的了解。
根据这一假设,使用基于密度的聚类算法(如 HDBSCAN)是有意义的,它可以处理所选异常值示例中的噪声。不幸的是,它在高维数据上表现不佳。我们将使用 UMAP 来降低数据的维数。出于可视化的目的,我们将把维度降低到 2 维,但是如果您希望有一些重叠的集群,那么您可能会受益于稍微更高的维度。

快速浏览一下,我们会发现不匹配的类型往往会聚集在一起。只有facetoface与verbatim和大部分匹配的流派重叠。我们最好的办法是寻找小的局部聚类,看看一个流派如何包含多个主题。我们必须设置一个相对较小的最小集群大小,并允许更多的本地化集群。这可以通过降低 HDBSCAN 算法中的min_cluster_size和min_samples参数来实现。


基于视觉检查,边缘上的聚类相对较纯,即每个聚类中的大多数点来自同一流派。
主要的例外是:
- 紫罗兰集群由 3 个流派组成。
- 中间的黄绿色簇中有多个重叠的流派。
—这表明verbatim是一个“分布中”的主题。这对于测试 NLI 模型是没有用的。
—这在某些情况下可以删除。
大多数“纯粹的”verbatim星团可能太小而没有洞察力,但是更大的nineeleven和oup星团是有希望的。
使用 c-TF-IDF 查找主题
从密集的句子/文档嵌入群中提取主题的一个有用的方法是使用 c-TF-IDF 。James Briggs 的一篇好文章为预计算嵌入和集群提供了 c-TF-IDF 的清晰实现。我们在下面重用了部分实现。为了简单起见,我们使用 unigrams 来提取主题。
查看每个聚类中 c-TF-IDF 得分最高的单词,应该会让我们对该聚类的主要主题有所了解。
Topic class 0: ['barrel', 'firearm', 'ramrod', 'patch', 'monkey', 'ball', 'wood']
Topic class 1: ['prefer', 'perjinkety', 'nicety', 'perjinkity', 'duende', 'perjink', 'derivatives']
Topic class 2: ['flamable', 'inflammable', 'trucks', 'onomatoplazia', 'substituted', 'delaney', 'examples']
Topic class 3: ['industry', 'retailer', 'lean', 'bundle', 'inventory', 'production', 'marker']
Topic class 4: ['muskrat', 'another', 'baby', 'version', 'muscat', 'lollipop', 'ramble']
Topic class 5: ['mihdhar', 'moqed', 'khalid', 'majed', 'hanjour', 'hani', 'al']
Topic class 6: ['abu', 'king', 'farouk', 'training', 'afghanistan', 'ubaidah', 'banshiri']
Topic class 7: ['agreed', 'ladins', 'war', 'efforts', 'turabi', 'bin', 'ladin']
Topic class 8: ['water', 'two', 'first', 'word', 'words', 'english', 'one']
Topic class 9: ['hazmi', 'fighters', 'boston', 'center', 'aircraft', 'command', 'hijacking']
Topic class 10: ['referred', 'tried', 'controller', 'york', 'united', 'crew', 'transponder']
Topic class 11: ['turned', 'shootdown', 'center', 'information', 'neads', 'united', 'american']
Outliers: ['cracker', 'atom', 'iupui', 'penney', 'american', 'bangers', 'controller']
让我们用相关的主题可视化集群嵌入(左)。为了进行比较,我们将把相同的嵌入和它们的原始类型想象成标签(右)。

在nineeleven类型中,有几个主题很突出,一些是关于美国航班,另一些是关于中东领导人。在oup类别中发现了一个主题,似乎是关于纺织品的。其余流派重叠太多,无法获得有意义的话题。处理重叠聚类的一种方式是专门在这些点上重复先前的聚类,例如,通过从分析中移除nineeleven,并根据需要递归地重复该过程。
结论
这个分析演示了如何识别和理解文本数据中的异常值。所需的方法在开源 Python 库中都可用且易于使用,您应该能够将这里演示的相同代码应用于您自己的文本数据集。
关键的一点是用能给出高质量嵌入的模型对文本示例进行编码,并对这些嵌入应用离群点检测算法,以便为主题建模步骤只选择罕见/不寻常的示例。
你可能会对 BERTopic 包感兴趣,它可以用于:通过 BERT 变换器嵌入文本,通过 UMAP 减少其维数,通过 HDBSCAN 进行聚类,通过 c-TF-IDF 识别每个聚类中的主题。
我希望识别和理解异常值有助于您在自己的应用程序中确保更高质量的数据和 ML 性能。您可以选择从您的数据集中忽略此类示例,或者扩展您的数据收集以更好地覆盖此类案例(如果它们看起来相关)。
除非另有说明,所有图片均为作者所有。
参考
[1] A. Williams,N. Nangia 和 S. Bowman,通过推理理解句子的大范围挑战语料库 (2018),计算语言学协会北美分会 2018 年会议记录:人类语言技术,第 1 卷(长论文),第 1112-1122 页,路易斯安那州新奥尔良。计算语言学协会。
[2] N. Reimers 和 I. Gurevych,句子-伯特:使用暹罗伯特网络的句子嵌入 (2019),2019 年自然语言处理经验方法会议录
[3] C. Northcutt 和 J. Mueller, cleanlab 2.1 增加了多注释者分析和离群点检测:走向以数据为中心的人工智能的广泛框架 (2022),cleanlab
[4] J. Kuan 和 J. Mueller,回到基础:再访分布外检测基线 (2022),ICML 分布转移原理研讨会 2022
[5] J. Briggs,高级主题建模与 BERTopic (2022),松果
[6] M. Grootendorst, BERTopic:基于类的 TF-IDF 程序的神经主题建模 (2022),arXiv 预印本 arXiv:2203.05794
了解回归模型中的部分效应、主效应和交互效应

图片由 Pixabay ( Pixabay 许可)的欧米娜拍摄
我们将学习如何使用合适的例子在回归模型中识别和测量这些影响。
在本文中,我们将弄清楚如何计算回归变量对回归模型响应变量的部分(或边际)效应、主要效应和交互效应。我们还将学习如何根据适当的效果来解释回归模型的系数。
先说部分效应,也叫边际效应。
部分效应
在回归模型中,回归变量的部分效应是回归变量中每单位变化的响应变量值的变化。
在微积分的语言中,部分效应是响应的期望值相对于感兴趣的回归变量的偏导数。
让我们来看三个日益复杂的部分效应的例子。
考虑以下线性回归模型:

仅包含线性项的线性回归模型(图片由作者提供)
以上模型中 ,y 为因变量,x_ 1,x _2 为回归变量。 y_i 、 x_i_1 和 x_i_2 是对应于第个个观察值,即数据集第个行的值。
β_0 为截距。 ϵ_i 是捕获 y_i 中模型无法解释的方差的误差项。
当我们在数据集上拟合上述模型时,我们正在估计期望值,即 x_i_1 和 x_i_2 的一些观察值的 y_i 的平均值。如果我们应用期望操作符 E(。)在等式(1)的两边,我们得到下面的等式(注意,误差项已经消失,因为它的期望值是零):

y_i 的期望值取决于 x _i(图片由作者提供)
x_i_1 和 x_i_2 (或一般情况下x_ 1和x_ 2)对 y_i 期望值的偏导数分别为 y_i w.r.t. x_i_1

x_i_1 和 x_i_2 对 E(y_i)的部分效应(图片由作者提供)
在仅包含线性项的线性模型的情况下,部分效应只是各自的系数。在这样的模型中,部分效应是常数。
现在,让我们通过添加一个二次项和一个交互项来稍微调整一下这个模型:

包含线性、二次和交互项的线性模型(图片由作者提供)
上述模型仍然是线性模型,因为其系数是线性的。但是 x_i_2 对 y_i 期望值的部分影响不再是常数。相反,效果取决于 x_i_2 和 x_i_1 的当前值,如下所示:

E(y_i) w.r.t. x_i_2 的变化率取决于 x_i_2 和 x_i_1 的当前值(图片由作者提供)
最后,让我们看看下面这个包含指数均值函数的非线性模型。该模型用于模拟泊松过程的均值函数:

指数均值函数(图片由作者提供)
在该模型中, x_i_1 的部分效果如下:

x_i_1 对 E(y_i)的部分效应(图片由作者提供)
在这种情况下,每单位改变 x_i_1 的 y_i 的期望值的改变不仅是而不是常数,而且它取决于模型中每个单个变量的当前值,以及所有系数的值。
让我们把注意力转向回归模型中的主效应意味着什么。
主要效果
在仅包含线性项的线性回归模型中,每个回归变量的主效应与该变量的部分效应相同。
让我们回顾一下文章开头提到的线性模型:

仅包含线性项的线性回归模型(图片由作者提供)
以上模型中的主要效果简单来说就是 β_1 和 β_2。
由于该模型仅包含线性项,因此有时被称为主效果模型。
当模型包含二次项、交互项、非线性项或对数线性项时,对主要效应的解释就变得有趣了。
在以下类型的模型中:

包含线性、二次和交互项的线性模型(图片由作者提供)
与变量 x _1 和x_ 2关联的系数 β_1 和 β_2 不能再解释为与这些变量关联的主效应。那么,在这样的模型中,主要效应是如何计算的呢?
一种方法是计算每个变量的部分效应,并计算数据集每一行的值。然后取所有这些部分效应的平均值。
例如,在上述模型中, x_i_1 (或一般的x_ 1)对 E(y_i) 的部分效应计算如下:

x_i_1 对 E(y_i)的部分效应(图片由作者提供)
为了计算 x_i_1 的主效应,我们必须计算数据集中每一行的上述部分效应的值,并取所有这些部分效应的平均值:

将主效应计算为整个数据集上部分效应的平均值(图片由作者提供)
虽然上述公式为计算主要效应提供了可靠的基础,但它只是一个近似效应,实际上只适用于手头的数据集。事实上,在这样的模型中,主要效应是否应该被计算,或者是否应该被简单地忽略,这是有争议的。
让我们把注意力转向交互作用效应。
交互效应
让我们通过增加一项来扩展线性模型,如下所示:

包含交互项的线性回归模型(图片由作者提供)
同样,上述模型仍然是一个线性模型,因为它的系数仍然是线性的。
术语 (x_i_1x_i_2)* 是两个回归变量的观察值的乘积,表示两个变量之间的相互作用。这次,当我们对y _ Iw . r . t .x _ I _ 1的期望值进行偏导数时,我们得到如下结果:

y_i w.r.t. x_i_1 的期望值偏导数包含了相互作用项的影响(图片由作者提供)
E(y_i) 相对于 x_i_1 的变化不再仅仅是 β_1 。由于相互作用项的存在,它是 β_1 加上一个取决于当前值 x_i_2 乘以相互作用项的系数 β_3 的量。如果系数 β_3 恰好为负,那么对于 x_i_1 中的每一个单位变化,它将减少 y_i 中的净变化,如果 β_4 为正,它将提升它(假设两种情况下 x_i_2 都为正)。
而如果 β_3 恰好不具有统计显著性(换句话说,为零),交互项就失去了作用, x_i_1 对 E(y_i) 的部分作用又恰好与 x_i_1 对 E(y_i)的主作用相同。
如果我们对 E(y_i) 求二阶导数,这次是 w.r.t. x_i_2 ,我们得到如下结果:

y_i 期望值的二重导数(图片由作者提供)
我们现在可以看到交互项 (x_i_1x_i_2)* 对模型的影响。
系数 β_3 测量E(y _ I)w . r . t .x _ I _ 1的变化率,即 x_i_2 的每单位变化量。因此, β_3 度量了 x_i_1 和 x_i_2 之间的相互作用程度。
β_3 称为相互作用效应。
相互作用项系数的解释
正如主效应一样,交互作用项的系数可以解释为交互作用效应的大小,但仅限于只包含线性项和一个交互作用项的线性模型。
对于所有其他情况,尤其是在非线性模型中,相互作用项的系数在指示相互作用项大小的能力方面没有意义。为了说明,再次考虑下面的非线性模型,该模型将平均值估计为回归变量的指数线性组合。该模型通常用于表示泊松回归模型中的非负泊松过程均值:

指数均值函数(图片由作者提供)
E(y _ I)w . r . t .x _ I _ 1的一阶导数产生了 x_i_1 对 E(y_i) 的以下部分影响:

x_i_1 对 E(y_i)的部分效应(图片由作者提供)
显然,相对于只有线性项的线性模型,在上述部分效应中, x_i_1 的系数 β_3 不再提供 x_i_1 主效应大小的任何线索。
E(y_i) 的二阶导数,这次 w.r.t. x_i_2 给出了一个更加混乱的情况:

E(y_i)的二重导数先 w.r.t. x_i_1,再 w.r.t. x_i_2(图片由作者提供)
关键的一点是,在一个非线性模型中,人们不应该试图给相互作用效应的系数赋予任何意义。
添加交互术语的好处
人们可能想知道为什么要在回归模型中引入交互项。
交互项是一种有用的工具,用于表示同一模型中一个回归变量对另一个回归变量的影响。主效应测量响应变量对单个回归变量的值的变化有多敏感,保持所有其他变量的值不变(或处于它们各自的平均值),而交互效应测量对另一个变量 z 的变化的敏感度**
这里有几个例子来说明互动效果的用法:
- 在研究一个人的收入与年龄、性别和教育等特征之间关系的模型中,如果这个人恰好是女性而不是男性,那么他可能想知道教育水平每改变一个单位,收入会改变多少。换句话说,在所有其他因素保持不变的情况下,女性参与者从额外教育中获得的好处比男性参与者多(或少)吗?如果我们使用回归年龄、性别、教育程度和(性别教育程度)的线性收入模型来表示这些关系,那么交互作用效应就是(性别教育程度)的系数。
- 在一个研究温度和微粒空气数量对降雨强度影响的模型中,主要影响将分别通过单位温度变化或空气污染的降雨量变化来衡量,而交互影响可以通过单位污染水平变化的降雨量变化量来衡量,本身将随着单位温度变化而变化。
包含交互项的模型示例
在本文的其余部分,我们将构建一个包含交互项的模型。具体来说,我们将通过回归一组六个变量和一个交互项来评估两所葡萄牙学校学生的学术表现。完整的数据集可以从加州大学欧文分校的机器学习知识库网站下载。我们从原始数据集中删除了大部分列,并将所有二进制变量编码为 0 或 1 的数据集精选子集 可从这里 下载。
这是这个精选数据集的一部分的样子:

学生成绩数据集(数据集来源于 CC BY 4.0 和 UCI ML 引用政策下的 UCI ML 知识库
每一行包含一个独特的学生的测试表现。因变量( G1 )是他们的第一堂数学课成绩,从 0 到 20 不等。我们将根据多个因素和一个交互项回归等级,如下所示:

学生数学成绩模型(图片由作者提供)
这里,失败是学生在过去的课上失败的次数。该值从 0 到 4。它在 4 点被右删。
schoolsup 和 famsup 是布尔变量,分别表示学生是否从学校或家庭获得了额外的教育支持。值为 1 表示他们得到了一些支持,值为 0 表示他们没有得到任何支持。
studytime 包含学生每周花在学习上的时间。其值以 1 为增量在 1 到 4 之间变化,其中 1 表示< 2 小时,2 表示 2 到 5 小时,3 表示 5 到 10 小时,4 表示大于 10 小时。
goout 代表学生和朋友在户外闲逛的程度。它是一个从 1 到 5 的整数值,其中 1 表示非常低的范围,5 表示非常高的范围。
性别是一个布尔变量(1 =女性,0 =男性)。
我们还在这个模型中加入了一个交互项,叫做(失败性)*。
如果我们区分 G1 瓦特性别,我们得到性别对 G1 的部分效应如下:

男女学生在数学方面的预期成绩差异(图片由作者提供)
这个等式给出了男女学生平均成绩的差异。由于交互项的存在,这种差异也取决于过去的失败次数。
如果我们再进行一次微分,这次是 w.r.t. 故障,我们得到以下结果:

过去失败次数的单位变化对男女学生数学预期成绩的影响(图片由作者提供)
β_7 是过去失败次数每单位变化,男女生平均成绩差变化的比率。
因此 β_7 估计了性别和失败次数之间的交互效应。
让我们在数据集上构建和训练这个模型。我们将使用 Python 和 Pandas 数据分析库和 statsmodels 统计模型库。
让我们从导入所有需要的包开始。
**import** pandas **as** pd
**from** patsy **import** dmatrices
**import** statsmodels.api **as** sm
接下来,我们将使用 Pandas 将数据集加载到 Pandas 数据框架中:
df = pd.**read_csv**(**'uciml_portuguese_students_math_performance_subset.csv'**, **header**=0)
我们现在将在 Patsy 语法中形成回归表达式。我们不需要明确指定截距。Patsy 将在接下来的步骤中自动将其添加到 X 矩阵中。
reg_exp = **'G1 ~ failures + schoolsup + famsup + studytime + goout + sex + I(failures*sex)'**
让我们雕刻出 X 和 y 矩阵:
y_train, X_train = **dmatrices**(reg_exp, df, **return_type**=**'**dataframe**'**)
这是设计矩阵的外观:

y_train(图片由作者提供)

X_train(图片由作者提供)
注意,Patsy 在 X 中为截距 β_0 添加了一个占位符列,它还添加了包含交互术语 failuressex* 的列。
我们现在将在数据集(y_ train,X_ train)上构建和训练模型:
olsr_model = sm.**OLS**(**endog**=y_train, **exog**=X_train)
olsr_model_results = olsr_model.**fit**()
让我们打印培训总结:
**print**(olsr_model_results.**summary**())
我们看到下面的输出(我强调了一些有趣的元素):

培训总结(图片由作者提供)
如何解释回归模型的训练效果
调整后的 R 平方为 0.210,这意味着该模型能够解释 G1 得分中 21%的方差。 F 检验的 F 统计量为 15.96,在 p 值为<0.001 时显著,这意味着模型的变量和都高度显著。与简单的均值模型相比,该模型能够更好地解释学生表现的差异。
接下来,让我们注意到,几乎所有的系数在 p 值为 0.05 或更低时都具有统计显著性。
系数 famsup 在 p 为. 061 时显著,交互项 failuressex* 在 p 为. 073 时显著。
拟合模型的方程如下:

拟合模型的方程(图片由作者提供)
系数的解释
让我们看看如何解释拟合模型的各种系数。
故障对 G1 的部分影响由下式给出:

失败对 G1 的部分影响(图片由作者提供)
失效系数为-1.7986。由于交互项(失败性)的存在,-1.7986 是不再是过去失败对期望 G1 分数的主要影响。事实上,除了在性别的系数为 0 的情况下,人们不应该赋予这个系数的值任何意义,在这种情况下,它不是。因此,我们能做的最好的事情是计算数据集中每一行的故障对 E(G1) 的部分影响,并将所有这些值的平均值视为故障对 E(G1) 的主要影响。如前所述,这种策略的价值值得怀疑,考虑到交互项的存在,更安全的方法是放弃计算故障*的主要影响。
请注意,在解释训练输出中的性别系数时,完全相同的一组考虑因素成立。
在解释 schoolsup 、 famsup 、 studytime 和 goout 的系数时,考虑因素发生了巨大变化。交互项(故障性别)*中不涉及这些变量,导致其系数的直接解释如下。
对所有学生来说,他们花在“外出”上的时间每增加一个单位,他们的 G1 分数的估计平均降幅为. 3105。这是 goout 对 E(G1) 的局部效果。也是 goout 对 E(G1) 的主要效果。
同样,布尔变量 schoolsup 和 famsup 的系数是该变量对 E(G1) 各自的部分影响,也是该变量对 E(G1) 各自的主要影响。令人惊讶的是,两个系数都是负的,这表明从学校或家庭获得额外支持的学生平均比那些没有获得支持的学生表现更差。解释这一结果的一种方法是假设大多数接受额外支持的学生是因为他们的数学成绩不好而接受的。
另一方面,学习时间与 G1 分数具有可预测的正关系。学习时间每增加一个单位,导致 G1 分数增加 0.5848 分。
最后,让我们检查失败与性别的交互作用。互动项的系数(失败性别)为正,表明学生经历的过去失败次数每增加一个单位,男生在 G1 分数中似乎对女生的领先优势就迅速消失,减少了 0.7312 分,这是(失败性别)的系数。这一结论通过对 E(G1) w.r.t. 性别求导得到证实,这给出了性别对平均 G1 分数的部分影响:

性别对平均 G1 分数的部分影响(图片由作者提供)
从上面的等式(以及下面显示的这个等式的曲线图)中,我们可以看到性别对 E(G1) 的部分影响随着过去失败次数的增加而很快反转其符号:

随着过去失败次数的增加,性别对 G1 的部分影响发生了变化
下面的表格和图表显示了对相同情况的另一种看法(经验观点)。它显示了从数据集中计算出的男女学生过去失败的平均分数:

男女学生对过去失败的不同价值观的平均分(图片由作者提供)

男女学生对过去失败的不同价值观的平均分(图片由作者提供)
正如预期的那样,我们看到经验结果与模型结果一致,模型结果使用了性别对 G1 的部分影响的方程。随着过去失败次数的增加,男女学生的 G1 分数差异迅速逆转。过去失败的次数越多,对男生成绩的负面影响似乎就越大。这种现象背后的原因很可能源于模型中的其他因素,或者它们可能是从根本上不可观察的影响,会泄漏到模型的误差项中。
通过在回归模型中包含相互作用项,这种观察是可能的。如果在回归模型中只包括性别和失败的主要影响,我们将很难发现这种模式。
关键要点
- 在回归模型中,回归变量的部分效应或边际效应是回归变量中每单位变化的响应变量的值的变化。
- 在仅包含线性项的线性模型中,即不包含二次项、对数项和其他种类的非线性项,每个回归变量的主效应与其部分效应相同。
- 对于所有其他模型,变量的主效应可以通过在整个数据集上平均变量的部分效应来计算。这充其量是一个近似值,基本上适用于手头的数据集。因此,一些从业者宁愿完全忽略这类模型中的主要影响。
- 交互项帮助建模者估计一个回归变量对模型中其他变量的影响,它们共同解释了响应变量中的方差。
- 在某些简单的线性模型中,相互作用项的系数可以用来估计相互作用效应的大小。然而,在大多数模型中,人们不应该赋予相互作用项的系数任何意义。
参考文献、引文和版权
数据集
来自 UCI 机器学习库的学生成绩数据集由 4.0 在 CC 下使用,并符合其引用政策(见下文):
Dua d .和 Graff c .(2019 年)。UCI 机器学习知识库[http://archive . ics . UCI . edu/ml]。加州欧文:加州大学信息与计算机科学学院。
本文中使用的数据集的精选版本 可以从这里 下载。
纸
页(page 的缩写)科尔特斯和 a .席尔瓦。使用数据挖掘预测中学生成绩。在 a .布里托和 j .特谢拉编辑的。,第五届未来商业技术会议论文集(FUBUTEC 2008)第 5–12 页,葡萄牙波尔图,2008 年 4 月,EUROSIS ,ISBN 978–9077381–39–7。
【网页链接】
形象
本文中的所有图片版权归 Sachin Date 所有,版权归 CC-BY-NC-SA 所有,除非图片下方提到了不同的来源和版权。
如果您喜欢这篇文章,请关注我的Sachin Date以获得关于回归、时间序列分析和预测主题的提示、操作方法和编程建议。
使用 Python 理解概率分布
原文:https://towardsdatascience.com/understanding-probability-distributions-using-python-9eca9c1d9d38
概率分布直观而全面的指南

图片来源:https://pix abay . com/vectors/Bayes-statistics-bell-curve-2889576/
概率分布描述了随机变量取值的概率。这是统计学和概率论中的一个重要概念,关于这个主题的每本书都讨论了概率分布及其性质。然而,他们强调这些分布的数学性质,而不是它们背后的直觉,数学描述往往是抽象的。在本文中,介绍了一些最有用的概率分布,但重点是提供对每个分布及其数学性质的直观理解。您还将学习如何使用 SciPy 库在 Python 中生成不同的概率分布。
离散随机变量
随机变量是其数值取决于随机现象结果的变量。因此,它的值最初是未知的,但一旦随机现象的结果被意识到,它就变得已知。我们通常用大写字母来表示一个随机变量,用小写字母来表示一个随机变量的特定值。
例如,抛硬币的两种结果是正面或反面。我们可以给每个结果赋值,用 0 表示正面,用 1 表示反面。现在我们可以用可能的值 X =0 或 X =1 来定义随机变量 X 。 X 可以采用的特定值由 x 表示。这是一个离散随机变量的例子。离散随机变量只能取某些值。另一方面,连续随机变量可以取区间内的任何值。例如,测量一个人体重的结果可以用一个连续的随机变量来描述。我们将在后面讨论连续随机变量及其概率分布。
离散随机变量 X 的概率质量函数(PMF)是给出 X 等于某个值的概率的函数。因此, X 的 PMF 被定义为函数 p ₓ,使得

这里的P(X=X)就是 X = x 的概率。PMF 是归一化的,这意味着 X 的所有可能值的概率之和是 1:

随机变量 X 的累积分布函数(CDF)由下式给出:

对于离散随机变量,这等于所有结果的概率之和,其中 X 的值小于或等于 x 。数学上:

离散随机变量 X 的均值或期望,用 E ( X 表示,是一个定义如下的数:

其中总和超过随机变量 X 可以取的所有值 x ,并且 p ₓ( x 就是 X 的 PMF。方差是数据围绕其均值分布的度量。由 Var ( X )表示的 X 的方差定义如下:

很容易看出:

X 的标准差,用 σ 表示,如果存在方差,则为 Var ( X )的非负平方根:

离散概率分布
概率分布是一个数学函数,它描述了一个随机变量可以取的值加上这些值的概率。我们首先从离散随机变量的概率分布开始。
伯努利分布
最简单的随机实验只有两种结果,我们可以用 0 和 1 来表示。随机变量 X 具有参数为 p (0≤ p ≤1)的伯努利分布,如果 X 只能取值 0 和 1,并且每个值的概率为

所以,我们可以把 X 的 PMF 写成

这里对于 x =1,我们得到

并且对于 x =0

具有伯努利分布的随机变量 X 可以表示掷硬币,其中 X =1 和 X =0 分别表示获得正面和反面,并且 p 将是硬币正面落地的概率。为了公平起见,我们有 p = 0.5。符号~表示“有概率分布”。因此,如果 X 具有参数为 p 的伯努利分布,我们可以写为:

我们可以使用 Python 中的scipy库来处理不同的概率分布。我们可以使用scipy.stat中的Bernoulli对象创建一个伯努利分布,并使用该对象的pmf()方法计算其 PMF。这里我们计算出 X =1 和 p =0.5 的 PMF。
from scipy.stats import bernoulli
x = 1
p = 0.5
bernoulli.pmf(k=x, p=p)
## Output
0.5
我们可以很容易地用伯努利分布计算随机变量 X 的均值和方差。使用等式 2,我们得到:

为了计算方差,我们首先注意到 X 和 X 的 PMF 是相同的,所以:

使用等式 4,我们得到:

我们也可以在scipy中计算这个分布的均值和方差(或标准差):
bernoulli.mean(p=p)
#Output
0.5
bernoulli.var(p=p)
#Output
0.25
bernoulli.std(p=p)
#Output
0.5
具有伯努利分布的随机变量的 CDF 可以使用cdf()方法计算。例如,为了计算 CDF(1 ),我们可以写出:
bernoulli.cdf(k=1, p=p)
#Output
1.0
方法rvs()可以用来产生随机变量。随机变量或简单变量是随机变量的特定结果。使用这种方法,我们可以从伯努利分布中抽取大小为 n 的随机样本,这意味着我们从一个具有伯努利分布的随机变量中生成 n 个随机变量。这些 n 变量是独立的,因此一个变量的值不会影响其他变量(我们稍后将给出随机样本的正式定义)。例如,为了从伯努利分布中抽取一个大小为 5 的随机样本,我们使用rvs()创建一个包含 5 个元素的变量数组:
bernoulli.rvs(p=p, size=5, random_state=1)
#Ouput:
array([0, 1, 0, 0, 0])
这就像投掷硬币 5 次,然后将结果记录在一个数组中。
PMF 和分布的区别
离散随机变量 X 的 PMF 是给出 X 等于某个值的概率的函数。例如,对于伯努利分布,我们知道 X =1 的概率是:

另一方面,分布描述了一个随机变量可以取的所有值以及这些值的概率。这是我们从 PMF 的定义中可以得出的结论。例如,根据等式 5 中定义的 PMF,我们得出结论,在伯努利分布中, X 可以分别以概率 p 和 1- p 取值为 0 和 1。因此,PMF 的定义也可以定义概率分布,而当我们引入一个新的离散分布时,我们只需要定义它的 PMF。PMF 的条形图对于理解分布也非常有用,因为它直观地显示了随机变量的所有值及其相应的概率。清单 1 绘制了伯努利分布的 PMF,结果如图 1 所示。
# Listing 1
import numpy as np
import matplotlib.pyplot as plt
plt.figure(figsize=(4, 4))
plt.bar(x, bernoulli_pmf, width = 0.5)
plt.xlim(-0.5, 1.5)
plt.ylim(0, 1)
plt.title('Bernoulli: p=%.2f' % (p), fontsize=15)
plt.xlabel('x', fontsize=15)
plt.ylabel('Probability', fontsize=15)
plt.xticks(np.arange(0, 2, 1))
plt.show()

图 1
根据频率主义者对概率的定义,一个事件的概率是该事件在大量重复实验中的相对频率。在数学上,一个事件的概率是比率的极限

当实验的重复次数接近无穷大时。我们看到,如果 X 具有伯努利分布,那么

这意味着

随着试验的总次数趋于无穷大,接近 p 。
记住,掷硬币的结果可以用一个随机变量 X 来表示,这个随机变量具有伯努利分布。假设我们用 X =1 来表示头部,那么

现在,如果我们一次又一次地投掷一枚公平的硬币

随着试验的总次数接近无穷大,p 接近。类似地,如果我们从参数为 p 的伯努利分布中创建一个大小为 n 的随机样本,并且让该样本中 1 的数量为 k ,那么随着 n 趋于无穷大, k / n 的比值接近 p 。
清单 2 运行了一个模拟来演示这些概念。这里我们有一个公平的硬币( p =0.5)。我们使用方法rvs()从伯努利分布中获得不同大小的随机样本。每个样本的平均值给出等式 8 中的比率(由于每个样本由 0 和 1 组成,因此其总和等于 1 的数量,平均值等于 1 的数量除以样本大小)。结果如图 2 所示。随着样本量(投掷次数)的增加,该比率越来越接近 0.5。
*# Listing 2
np.random.seed(0)
p = 0.5
sample = bernoulli.rvs(p=p, size = 10000)
num_trials = [10, 100, 500, 1000, 2500, 5000, 7500, 10000]
points = [sample[0:l].mean() for l in num_trials]
plt.plot(num_trials, points, marker=’o’)
plt.xlabel(‘Number of trials’, fontsize = 14)
plt.ylabel(r’$\frac{Number\; of \; heads}{Number\; of \; trials}$’,
fontsize= 19)
plt.title(“Bernoulli distribution, p = {}”.format(p), fontsize= 15)
plt.show()*

图 2
二项分布
假设我们有 n 个独立随机变量 X ₁, X ₂,...、 Xₙ ,它们中的每一个都有一个参数为 p 的伯努利分布。如果x=x₁+x₂+... Xₙ ,那么 X (也是离散随机变量)具有参数 n 和 p 的二项分布。我们也可以写成 X ~ Bin( n , p )。 X 的 PMF 可以写成:

让我们看看这个 PDF 是怎么推导出来的。这里,随机变量 X ₁、 X ₂、…、 Xₙ 的可能值可以被认为是 0 和 1 的有序序列。如果一个序列的和是 x ,那么它应该有 x 个 1 和 n - x 个 0。得到一个有 x 个 1 和 n - x 个 0 的序列的概率是pˣ(1p)ⁿ⁻ˣ。既然有

这种不同的有序序列

正如我们前面看到的,投掷硬币的结果可以用一个离散的随机变量来描述,这个随机变量具有参数 p 的伯努利分布。现在假设我们将这枚硬币抛 n 次,我们想知道得到准确的 x 正面的概率是多少。每次试验可以用随机变量xᵢ(I= 1…n)来表示,该随机变量具有参数 p 的伯努利分布。为了准确地得到 x 个头,随机变量 Xᵢ 的 x 应该等于 1,剩余的应该为零,这意味着 X=x。如果我们将随机变量 X 定义为x=x₁+x₂+……+xₙ那么得到 x 个头的概率基于前面的定义, X 具有二项分布,我们可以写成:

因此,对于以概率 p 投出正面的硬币,我们在 n 次投掷中得到的正面总数可以由随机变量 X 表示,该随机变量具有参数 n 和 p 的二项式分布。我们可以使用scipy中的对象binom来生成二项分布。该对象的方法采用参数k、n和p,这些参数对应于等式 9 中的 x 、 n 和 p 。这里我们使用这个对象来绘制参数 p =0.5 和 n =8 的二项分布的 PMF。结果如图 3 所示。
*# Listing 3
from scipy.stats import bernoulli, binom
n = 8
p = 0.5
x = np.arange(0, n+1)
binomial = binom.pmf(k=x,n=n, p=p)
plt.bar(x, binomial)
plt.xlabel(‘x’, fontsize=14)
plt.ylabel(‘Probability’, fontsize=14)
plt.xlim([-1, 9])
plt.title(“Binomial distribution, n={0}, p={1}”.format(n, p),
fontsize= 15)
plt.show()*

图 3
对于 p =0.5,二项式分布的形状是对称的,因为对于 x 的每个值,我们有

但是对于 p 的其他值,就不是对称的了。图 4 左图显示了作为示例的 n =8 和 p =0.7 的这种分布的 PMF。

图 4
然而,随着 n 趋于无穷大,二项式分布的形状变得更加对称(其原因是中心极限定理,这将在后面解释)。左边的图 4 显示了相同的分布,其中 n 的值更大,看起来更加对称。
如果 X ₁、 X ₂、…、 Xₙ 为 n 随机变量并且 a ₁、 a ₂、…、 aₙ 和 c 为常数,那么可以证明如果x=a₁x**
**
如前所述,具有参数为 n 和 p 的二项分布的随机变量 X 可以写成具有参数为 p 的伯努利分布的 n 个独立随机变量之和。因此,使用上述等式以及等式 6 和等式 7,我们可以轻松计算出 X 的均值和方差:
**
在清单 4 中,我们从二项式分布中获得 3 个随机样本,n= 8,p= 0.5,并绘制它们的条形图。这里我们使用rvs()方法从伯努利分布中获得一个大小为 n 的随机样本,然后计算其总和。我们重复这个抽样程序s次,从二项分布中得到一个大小为s的样本:**
sample = [bernoulli.rvs(p=p, size=n).sum() for i in range(s)]
这相当于使用二项式分布的rvs()方法:
sample = binom.rvs(n=n, p=p, size=s)
然后我们绘制这个样本的条形图(请注意,在这个条形图中,我们显示的是每个元素的百分比,而不是它的计数)。随着样本大小s趋于无穷大,条形图的形状接近分布的 PMF。结果如图 5 所示。
**# Listing 4
np.random.seed(0)
n = 8
p = 0.5
x = np.arange(9)
binomial = binom.pmf(k = x,n = n, p = p)
fig, axs = plt.subplots(1, 3, figsize=(19, 5))
for id, s in enumerate([10, 100, 10000]):
sample = [bernoulli.rvs(p=p, size=n).sum() for i in range(s)]
# sample = binom.rvs(n=n, p=p, size=s)
values, counts = np.unique(sample, return_counts=True)
probs = counts / counts.sum()
axs[id].bar(values, probs)
axs[id].plot(x, binomial, marker='o', color='red')
axs[id].set_title("Sample size={0} \n n={1}, p={2}".format(s, n, p),
fontsize=14)
axs[id].set_xlabel('x', fontsize = 14)
axs[id].set_ylabel('Probability', fontsize=14)
axs[id].set_xlim([-1, 9])
plt.show()**

图 5
当样本大小为 10 时,条形图的形状是不规则的,但是随着样本大小的增加,最终得到的条形图的形状接近二项式分布的 PMF。
如前所述,具有 n 和 p 的二项分布的平均值在 np 处。此外,该分布的峰值可以通过以下方式确定。请记住,在伯努利分布中

随着试验总数的增加而接近 p 。因此,如果 np 是一个整数,我们期望具有伯努利分布的 n 个随机变量的和接近于 np 。因此,如果 np 是整数,则分布的最可能值(分布的峰值)是 np 。这也被称为分配的模式。例如图 3 中, np =8×0.5=4,因此模式为 4。
如果 np 不是一个整数,那么可以表明分布在( n +1) p 和(n+1)p-1 处有两种模式。图 6 中的分布就是一个例子。这里 n =9、 p =0.7,所以 np =0.63,模式在(9+1)0.7=7 和(9+1)0.7–1 = 6。

图 6
对于远离 np 的 X 的值,概率降低。例如, P ( X =0)非常低,因为它表示所有 n 伯努利随机变量取值为 0 的情况。
负二项分布
假设我们有一个参数为 p 的伯努利试验序列(所以每次试验成功的概率是 p ),这些试验是独立的。让离散随机变量 X 表示在第次成功之前发生的失败次数。那么 X 有如下 PMF:

所以 p ₓ( x 给出了在第次成功之前发生 x 次失败的概率。我们说 X 有一个带参数 r 和 p 的负二项分布,用 X ~ NBin( r , p )表示。**
我们可以很容易地使用二项分布来驱动这个 PMF。假设我们有一系列 n 个独立的伯努利试验,其中每个试验的成功概率是 p 。让 Aₙ 表示我们有 r 成功和x=n-r失败的事件,并且在第 n 次试验中获得第 r 次成功。因此,在最初的 n -1 次试验中,我们有 r -1 次成功和 x 次失败。让 A ₙ ₋₁表示在第一次 n -1 次试验中获得 r -1 次成功的事件。我们可以用二项分布来得到 Aₙ ₋₁:的概率

第 n 次试验成功的概率是 p ,所以我们要把它乘以 An -1 的概率,得到 Aₙ 的概率:

我们知道x=n-r,所以

我们可以写:

但这正是在第 r 次成功之前发生 x 次失败的概率,因此等式 14 由此得出。
我们可以使用scipy中的对象nbinom创建一个负二项分布。该对象的方法采用参数k、n和p,它们对应于等式 14 中的 x 、 r 和 p 。清单 5 绘制了负二项分布的 PMF,其中 p= 0.5,r =10。**
**# Listing 5
from scipy.stats import nbinom
r = 5
p = 0.5
x = np.arange(0, 20)
nbinomial = nbinom.pmf(k=x,n=r, p=p)
plt.bar(x, nbinomial)
plt.xlabel(‘x’, fontsize=14)
plt.ylabel(‘Probability’, fontsize=14)
plt.title(“Negative binomial distribution, r={0}, p={1}”.format(r, p),
fontsize= 15)
plt.xticks(np.arange(0, 20, 2))
plt.show()**

图 7
让我们谈谈图 7 背后的直觉。这里 r =5,最后一次试验成功,那么前 n -1 次试验我们有 4 次成功, x 是前 n -1 次试验的失败次数。在一个 p =0.5 的伯努利试验序列中,我们期望失败的次数与成功的次数大致相同,所以分布的模式是在 x =3 和 4。而且随着 x 的增大, x 的概率迅速减小。另外,随着 x 降低到 3 以下,概率也降低。
在清单 6 中,我们展示了负二项分布是如何由一系列伯努利随机变量产生的。我们假设r =5。我们创建一个形状为(sample_size,num_trials)的二维伯努利随机变量数组。该数组中的每一行代表一系列伯努利试验。这些序列的数量由sample_size决定,每个序列中的试验数量由num_trials设定。我们计算最后一次尝试成功的序列的分数,并且成功的总数是r。这个分数给出了发生num_trials-r故障的概率。我们为不同的sample_size值绘制了这些概率(图 8)。随着sample_size的增加,条形图的形状越来越接近负二项分布的 PMF(红色曲线)。
**# Listing 6
np.random.seed(0)
r = 5
p= 0.5
sample_size = [100, 1000, 100000]
x_range = range(20)
nbinomial = nbinom.pmf(k=x_range,n=r, p=p)
fig, axs = plt.subplots(1, 3, figsize=(19, 5))
for i in range(3):
probs = []
for x in x_range:
num_trials = r + x
sample = bernoulli.rvs(p=p, size=num_trials*sample_size[i]). \
reshape(sample_size[i], num_trials)
filtered_sample = sample[(sample[:,-1] == 1) & (sample.sum(axis = 1) == r)]
prob = len(filtered_sample) / sample_size[i]
probs.append(prob)
axs[i].bar(x_range, probs)
axs[i].set_xticks(np.arange(0, 20, 2))
axs[i].set_title(“Sample size=”+str(sample_size[i]), fontsize=14)
axs[i].set_xlabel(‘x’, fontsize=14)
axs[i].plot(x_range, nbinomial, marker=’o’, color=’red’,
label=’Negative binomial PMF’)
axs[i].legend(loc=’best’)
axs[0].set_ylabel(‘Probability’, fontsize=14)
plt.show()**

图 8
参数为 r 和 p 的负二项分布随机变量 x 的均值和方差为:
****
几何分布
几何分布是负二项分布的特殊情况,其中 r =1。在等式 14 中,如果我们设置 r =1,则我们有:

因此,如果 X 的 PMF 如下,则离散随机变量 X 具有参数为 p 的几何分布:

我们用 X ~ Geom( p )来表示这一点。这里 f ₓ( x 给出了在第一次成功之前得到 x 次失败的概率。请注意,我们也可以使用伯努利分布的 PMF(等式 5)来推导这个 PMF。对于参数为 p 的伯努利试验,得到失败的概率为(1- p ,那么得到 x 连续失败的概率为(1- p ) ˣ 。最后一次尝试也是第一次成功的概率为 p 。所以在第一次成功之前得到 x 失败的概率是p(1-p)ˣ。
我们还可以根据获得第一次成功所需的试验次数来定义几何分布。如果成功发生在第 x 次试验,那么意味着在此之前我们有过 x -1 次失败,那么这些事件发生的概率是p(1-p)ˣ⁻。在这种情况下,分布的 PMF 将被定义为:

这里的失败次数是 x -1。在scipy中,我们可以使用对象geom生成一个几何分布。该对象基于等式 18 定义了一个几何分布( X 代表获得第一次成功的试验次数)。该对象的方法采用与等式 18 中的 x 和 p 相对应的参数k和p。所以如果 x 是失败次数,geom.pmf(k=x,p=p)给出 x-1 的 PMF。清单 7 绘制了一个几何分布的 PMF,其中 p =0.5, p =0.1。PMF 如图 9 所示。请注意,为了获得失败次数,我们绘制了geom.pmf(k=x,p=p)与x-1的关系图。
**# Listing 7
from scipy.stats import geom
fig, axs = plt.subplots(1, 2, figsize=(12, 4))
p_list = [0.5, 0.1]
x = np.arange(1, 20)
for i, p in enumerate(p_list):
geometric = geom.pmf(k=x,p=p)
axs[i].bar(x-1, geometric)
axs[i].set_xlabel(‘x (number of failures)’, fontsize=14)
axs[i].set_ylabel(‘Probability’, fontsize=14)
axs[i].set_title(“p={0}”.format(p),
fontsize= 14)
axs[i].set_ylim([0, 0.55])
axs[i].set_xticks(np.arange(0, 20, 2))
plt.suptitle(“Geometric distribution”, fontsize=16, y=1.05)
plt.show()**

图 9
在这个图中,在第一次成功之前获得 0 次失败的概率是 p 。那是因为第一次伯努利试验是成功的,我们知道它发生的概率是 p 。随着 x 的增加,这种可能性下降,因为我们不太可能有很多连续的失败。对于较低的 p 值,失败的机会增加,因此获得较高的 x 值的概率增加。通过在等式 15 和等式 16 中设置 r =1,我们可以得到几何分布的均值和方差:
****
几何分布是无记忆的。这意味着如果第一次成功在第 m 次试验中还没有发生(所以 X≥m ,那么它在接下来的 n 次试验中没有发生的概率( X≥n+m ),与它在第一次 n 次试验中没有发生的概率( X≥n )相同。数学上:

为了证明这一点,我们首先注意到失败次数大于或等于 n 的概率等于得到至少 n 次失败的概率:

因此,使用贝叶斯规则的定义,我们可以写出:

**几何分布是唯一无记忆的离散分布。因此,如果 X 是具有无记忆特性的离散随机变量,那么 X 具有几何分布。
几何分布中的无记忆性质是具有相同参数的一系列独立伯努利试验的结果。因此,在每次试验中,成功的概率是相同的,并且不受先前试验结果的影响。举个例子,假设你正在投掷一枚公平的硬币很多次,直到你得到一个正面。在一次特定的投掷中得到正面的概率与之前你有多少个反面无关。如果你的前三次投掷都是反面,你可能会直觉地认为下一次更有可能是正面。但是,基于无记忆属性,在下一次投掷中获得正面的概率仍然是 0.5。这是因为每一次投掷都是伯努利试验,概率为 p =0.5,并且试验彼此独立。所以,前一次投掷的结果不会影响下一次投掷的结果。**
假设我们有 r 个独立随机变量 X ₁、 X ₂、…、 Xᵣ 并且他们中的每一个都有一个参数为 p 的几何分布。如果x=x₁+x₂+…+xᵣ,那么 X (也是离散随机变量)具有参数 r 和 p 的负二项分布。请记住,负二项分布是基于一系列参数为 p 的独立伯努利试验定义的,具有该分布的随机变量 X 给出了在第 r 次成功之前发生的失败次数。
让 X ₁用参数 p 表示一系列独立伯努利试验中第一次成功之前的失败次数。我们知道 X ₁有一个参数为 p 的几何分布。现在让 X ₂表示在第一次成功之后和获得第二次成功之前的失败次数。由于所有试验都是独立的,我们得出结论 X ₂也具有参数 p 的几何分布。同样,如果 Xᵢ 是获得 i -1 次成功之后,获得 i 次成功之前的数字,那么 Xᵢ 也有一个参数为 p 的几何分布(图 10)。因此,x₁+x₂+…+xᵣ的和给出了在第 r 次成功之前发生的失败总数,根据定义,这个和是一个负二项分布的随机变量。

图 10(来源:图片由作者提供)
这也让我们对几何分布有了新的解释。具有参数为 p 的几何分布的随机变量 X 给出了参数为 p 和 n 的二项式分布中两次连续成功试验之间的失败次数(图 10)。
清单 8 展示了我们如何使用几何分布中的随机样本来构建负二项分布的 PMF。这里我们使用rvs()方法从几何分布中获得一个大小为r的随机样本,然后计算样本的总和。我们可以重复这个抽样程序s次,从负二项分布中得到一个大小为s的随机样本:
sample = [(geom.rvs(p=p, size=r)-1).sum() for i in range(s)]
这相当于使用负二项分布的rvs()方法:
sample = nbinom.rvs(n=r, p=p, size=s)
然后我们绘制这个样本的柱状图。随着样本量s的增加,条形图的形状越来越接近分布的 PMF。结果如图 11 所示。
**# Listing 8
np.random.seed(0)
r = 8
p = 0.5
x_range = range(20)
nbinomial = nbinom.pmf(k=x_range,n=r, p=p)
fig, axs = plt.subplots(1, 3, figsize=(19, 5))
for id, s in enumerate([10, 100, 100000]):
sample = [(geom.rvs(p=p, size=r)-1).sum() for i in range(s)]
# sample = nbinom.rvs(n=r, p=p, size=s)
values, counts = np.unique(sample, return_counts=True)
probs = counts / counts.sum()
axs[id].bar(values, probs)
axs[id].plot(x_range, nbinomial, marker='o', color='red',
label='Negative binomial PMF')
axs[id].set_title("Sample size={0} \n r={1}, p={2}".format(s, r, p),
fontsize=14)
axs[id].set_xlabel('x', fontsize = 14)
axs[id].set_ylabel('Probability', fontsize=14)
axs[id].set_xlim([-1, 19])
axs[id].set_xticks(np.arange(0, 20, 2))
axs[id].legend(loc='best')
plt.show()**

图 11
泊松分布
泊松分布是二项分布的一种极限情况,其中试验次数 n 非常大,成功概率 p 非常小。记住二项分布的均值是 np ,所以当 n →∞时,我们要有 p →0 才有一个有限均值。当 n 趋于无穷大时,让 n 和 p 的乘积保持不变:

现在,利用等式 9 和等式 19,我们可以写出:
****
当 n 趋于无穷大时,该表达式可以通过取其分量的极限来简化:



所以,我们有:

基于这个结果,我们可以定义一个新的分布。一个离散随机变量 X 具有参数λ(λ0)的泊松分布,如果 X 的 PMF 如下:

我们也用 X ~ Pois( λ )来表示。当试验次数 n 趋于无穷大而乘积 np=λ (分布的平均值)保持不变时,泊松分布是二项式分布的极限情况。请注意 p 很小,但不为零(如果为零,则 PMF 处处为零)。
由于泊松分布是二项式分布的极限情况,我们可以使用等式 12 和 13 来计算泊松分布的随机变量 X 的均值和方差:
****
清单 9 使用scipy中的poisson对象绘制了 λ 的不同值的泊松 PMF。该对象中的方法采用与等式 21 中的 x 和 λ 相对应的参数k和mu。结果如图 12 所示。
**# Listing 9
from scipy.stats import poisson
lambda_list = [1, 5, 10, 25, 50]
x = np.arange(0, 70)
plt.figure(figsize=(9, 6))
for lam in lambda_list:
poisson_dist = poisson.pmf(x, lam)
plt.plot(x, poisson_dist, marker = 'o',
label = r"$\lambda=$" + str(lam))
plt.xlabel('x', fontsize=14)
plt.ylabel('Probability', fontsize=14)
#plt.xlim([-1, 9])
plt.title("Poisson distribution", fontsize= 15)
plt.legend(loc='best', fontsize= 14)
plt.show()**

图 12
清单 10 绘制了 λ =10 的泊松分布的 PMF,以及参数为 n 和p=λ/n的二项分布的 PMF(图 13)。随着 n 的增加,二项分布的 PMF 接近泊松分布的 PMF。
**# Listing 10
lam = 10
x = np.arange(0, 23)
fig, axs = plt.subplots(1, 3, figsize=(21, 5))
poisson_dist = poisson.pmf(x, lam)
for i, n in enumerate([20, 100, 1000]):
axs[i].plot(x, poisson_dist, marker = 'o',
color = 'red', label = "Poisson")
binomial = binom.pmf(k=x,n=n, p=lam/n)
axs[i].bar(x, binomial, label="Binomial")
axs[i].set_xlabel('x', fontsize=14)
axs[i].set_ylabel('Probability', fontsize=14)
axs[i].set_title("n="+str(n), fontsize=18)
axs[i].legend(loc='best', fontsize=14)
plt.show()**

图 13
泊松分布可以应用于我们有大量可能事件,但每个事件的概率非常小的系统。这类事件的总数可以用一个泊松分布的随机变量来描述,参数 λ 给出了事件的平均数。
在一些问题中,我们得到的是事件发生的平均速率,而不是事件的平均数量。因此,我们可以写出 λ=rt ,其中 r 是平均速率,而 t 是平均速率的时间间隔。请注意 t 有时间单位,速率 r 有 1/时间单位。因此泊松分布中的参数 λ 是无量纲的(它没有单位)。这里 λ 给出了时间间隔 t 内事件的平均数量。
需要注意的是,要将泊松分布应用于系统,我们应该具备以下条件:
- 这些事件是独立发生的。
- 两件事不可能完全同时发生。更准确地说,在一个小区间内出现两次或两次以上的概率,与在该区间内出现一次的概率相比,必须是可以忽略的。
- 发生率 r 是常数,不随时间变化。因此,在长度为 h = t/n (其中 n 是一个大的数字)的足够短的时间间隔内恰好发生一个事件的概率大约为p=λ/n=rt/n = RH(该时间间隔( h )应该足够短,以确保在该时间间隔内不会发生多个事件
满足这些条件的系统称为泊松过程。我们可以利用这些条件导出泊松分布。我们将时间间隔 t 分成 n 个等长的子间隔 t / n 。 n 应足够大以满足条件 2 和 3。在 t / n 的短暂时间间隔内,我们可以假设 0 或 1 个事件都会发生,基于 3,在 t/n 的时间间隔内恰好发生一个事件的概率为 rt/n 。因此,我们可以使用参数为 n 和 p = rt/n 的二项分布,等式 9 可以写成:

当 n 趋于无穷大时,通过取其分量的极限,我们得到:

这个等式类似于等式 21,其中 λ 被替换为 rt 。但是,当我们用 rt 代替 λ 时, p ₓ( x 给出了时间间隔 t 内的事件总数。
让我们看一个例子。我们希望能够用泊松分布来描述到达商店的顾客数量。到达率 r 是 20 个客户/小时,我们想知道在接下来的 15 分钟内少于 5 个客户到达的概率是多少。这里 t =15 分钟。由于 r 和 t 的单位应该一致,我们应该将 r 的单位从 1/小时改为 1/分钟。我们有 r =20 个客户/小时= 20/60 个客户/分钟,所以λ=rt=(20/60)×15 = 5。这是 15 分钟内的平均顾客数量。使用等式 22,我们可以写出:

在接下来的 15 分钟内少于 5 个顾客到达的概率是:

我们可以用泊松的cdf()方法来计算 F (5):
**lam = 5
poisson.cdf(5, lam)
#Output
0.616**
记住到达率是 20 个顾客/小时。因此,对于 1 小时的间隔,我们有 t =1 小时和 λ =20×1=20。因此,如果随机变量 X 表示在接下来的一个小时内将到达商店的顾客数量,那么 X 具有参数 λ =20 的泊松分布。
我们也可以用二项式分布来模拟这个过程。要用二项式分布,我们也可以把一个小时的时间段分成 3600 秒,得到 20/3600≈0.00556 每秒的到达率。在如此短的时间间隔内,我们可以假设在每一秒钟内将有 0 或 1 个顾客到达,到达的概率是 0.00556。如果 X 表示在接下来的一个小时内将到达商店的顾客数量, X 具有参数 n =3600 和 p =0.00556 的二项分布。
清单 11 绘制了这个二项式分布加上泊松分布,其中 λ =20。结果如图 14 所示,可以看出泊松分布是二项式分布的非常好的近似。
**# Listing 11
x1 = np.arange(0, 70)
lam = 20
plt.figure(figsize=(10, 6))
poisson_dist = poisson.pmf(x1, lam)
plt.plot(x1, poisson_dist, marker = 'D', markersize=10,
linewidth=5, color='black',
label = r"Poisson, $\lambda=$"+str(lam))
n = 3600
p = lam/n
x2 = np.arange(0, n+1)
binomial = binom.pmf(k=x2, n=n, p=p)
plt.plot(x2, binomial, marker = 'o', color='orange',
label = r"Binomial, n=3600, p="+str(np.round(p, 5)),
linestyle='dashed')
plt.legend(loc='best', fontsize= 14)
plt.xlim([-1, 55])
plt.xlabel('x', fontsize=16)
plt.ylabel('Probability', fontsize=16)
plt.show()**

图 14
离散均匀分布
设 a 和 b 为整数 a ≤ b 。假设我们有一个随机离散变量 X ,它的值可以等概率取每个整数 a , a +1…, b 。我们说 X 在整数 a 、 a +1、…、 b 上具有离散的均匀分布。我们也可以写成 X ~ DU( a,b )。均匀分布是一种概率分布,其中所有结果出现的机会相等。均匀分布的 PMF 定义如下:

均匀分布是一种概率分布,其中所有结果出现的机会相等。等式 23 中的均匀分布,有 b - a +1 个可能事件。所以,每个事件的概率应该是 1/( b - a +1),以确保概率总和为 1。离散均匀分布的一个简单例子是掷骰子。这里可能的值是 1、2、3、4、5 和 6,得到每个值的概率是 1/6。
请注意,这里我们将离散均匀随机变量的值限制为在 a 和 b 之间的整数,然而,这些值通常可以是任何有限集。例如,均匀随机变量的可能值可以是实数 0、0.25、0.5、0.75 和 1,其中获得每个值的概率是 1/5。但是更常见的是基于整数 a 、 a +1、…、 b 来定义离散均匀分布,因为它允许我们容易地识别一组值。
我们可以使用scipy中的对象randint来生成一个离散的均匀分布。该对象的方法取参数x、low和high,对应于等式 23 中的 x 、 a 和 b 。清单 12 绘制了从 1 到 6 的不确定分布的 PMF(请注意在randint中,分布的上限是唯一的,所以我们应该在 1 到 7 上定义它)。结果如图 15 所示。
**# Listing 12
from scipy.stats import randint
a = 1
b = 6
x = np.arange(1, b+1)
dics_uniform = randint.pmf(x, a, b+1)
plt.bar(x, dics_uniform)
plt.xlabel('x', fontsize=14)
plt.ylabel('Probability', fontsize=14)
plt.xlim([0, 7])
plt.ylim([0, 0.25])
plt.title("Discrete uniform distribution, a={0}, b={1}".format(a, b),
fontsize= 15)
plt.show()**

图 15
我们可以使用 PMF 轻松计算出该分布的均值和方差:
****
还可以看出:

连续随机变量
如果 X 是一个连续的随机变量,那么 X 在区间[ a , b 取值的概率可以写成:

其中 f ₓ (x) 称为x的概率密度函数 (PDF)。我们知道这一点

所以,PDF 在整个空间上的积分必须等于 1

这意味着 PDF 是标准化的。根据等式 24,从该等式可以得出

事实上,连续随机变量的概率密度函数给它所取的单个值分配了一个零概率。对于一个离散随机变量 X ,P(X=a)= 0 意味着 X 不可能取值 a 。但是对于一个连续变量 X ,P(X=a)= 0 并不一定意味着 X = a 不可能。如果是这样的话, X 根本不可能取任何值。
让我给你看一个例子来阐明它。假设我们有一个离散均匀分布,PMF 如下:

这里, X 可以取区间[0,1]内的 n 个可能值,每个值的概率应该是 1/ n 以保证概率总和为 1。通过增加 n 的值,每个值的概率降低。现在随着 n 趋于无穷大,每个值的概率应该为零,以确保概率之和保持有限,但这并不意味着 X 不能再取这些值中的任何一个。我们也可以用类比来解释。想象一下,我们有一根金属棒,密度恒定为 ρ ,如图 16 顶部所示。现在假设我们有一个体积为 V_b 的假想立方体。这个立方体中金属的质量是:

现在我们认为点是金属棒内部的某处。这个点的体积为零。所以此时金属的质量是:

然而,零质量并不意味着在这一点上没有金属存在。质量为零的原因是该点的体积为零。最后,假设我们在金属棒外的某处有同一点(点 c )。里面金属的质量将会是零,然而,这一次零质量意味着我们在这一点上没有金属。我们得出结论,金属的存在是由它在该点的密度决定的,而不是该点的体积。

图 16(来源:作者图片)
现在假设我们有一个连续的随机变量,其 PDF 如下:

PDF 也显示在图 16 底部。使用等式 24 可以计算出b₁≤x≤b₂c的概率。但是,等式 24 也给出了 f ₓ( x 之间的面积 X = a 和 X=b、所以b₁≤x≤b₂的概率等于 f ₓ( x 之间的面积 X = a 的概率为零,因为在 X = a 处曲线下的面积为零(一条线的面积为零)。但是,对于一个连续的随机变量,事件发生的可能性是由概率密度决定的,而不是概率。因此, X = a 仍然是可能的,因为 X = a 处的概率密度不为零( f ₓ( a )≠0)。另一方面, X = c 的概率为零,但这次 X = c 是不可能的,因为 X = c 处的概率密度为零( f ₓ( c )=0)。
假设 X 为连续随机变量,设 f ₓ( x 为其 PDF,则 X 的 CDF 定义为:

根据微积分的基本定理

请注意,在等式中

密度是针对非零体积定义的,因为如果我们将 V 设置为零,密度将是无穷大。事实上,密度只有在我们有非零体积时才有意义。同样,概率密度也不能定义为单点。而是和一个音程有关。从 PDF 方程中,我们可以计算出单点 x 的概率密度值。然而,这个概率密度不仅仅与在 PDF 曲线下具有零面积的单个点相关。根据等式 24 和等式 27,可以得出

所以,我们可以把fₓ(x)dx 作为 X 落在无穷小区间[ x , x + dx ]内的概率。这意味着 f ₓ( x 给出了区间[ x , x + dx ]的概率密度。
连续随机变量 X 的平均值或期望值定义如下:

使用等式 3 或等式 4 可以计算出 X 的方差:

连续概率分布
连续均匀分布
设 a 和 b 为两个实数 a < b 。设 X 为取值于区间[ a , b ]的连续随机变量。如果对于[ a , b ]的每个子区间, X 属于该子区间的概率与该子区间的长度成正比,那么我们说 X 在区间[ a , b 上具有均匀分布。我们也写成 X ~ U( a,b )。定义了 X 的 PDF

图 17 显示了这个 PDF 的曲线图。

图 17
X 属于 a 、 b 中的子区间 x ₁、 x ₂】的概率可以用公式 24 计算:

此外,我们看到 X 属于子区间[ x ₁, x ₂]的概率与该子区间的长度成正比( x ₂- x ₁),因此只要子区间的长度相同,该概率就相同。
我们可以使用对象uniform来创建一个连续的均匀分布。这个对象的方法接受参数loc和scale。为了在[ a , b ]上定义一个均匀分布,我们应该设置loc = a ,scale = b - a (所以分布定义在[ loc,loc+scale ])。默认情况下,loc =0,scale =1,因此,如果我们不设置 loc 和 scale 的值,则定义的分布在[0,1]上。
在清单 13 中,我们从[0,1]上的均匀分布中抽取了 3 个不同大小的样本,并绘制了每个样本的归一化直方图。还显示了均匀分布的 PDF(图 18)。随着样本大小的增加,直方图越来越接近 PDF。由于直方图的柱具有相同的宽度,所以 X 属于每个柱的概率应该是相同的。因此,随着样本量的增加,仓的高度越来越接近。
**# Listing 13
from scipy.stats import uniform
np.random.seed(0)
fig, axs = plt.subplots(1, 3, figsize=(15, 4))
plt.subplots_adjust(wspace=0.25)
x = np.arange(0, 1, 0.01)
unif_dist = uniform.pdf(x)
for i, n in enumerate([100, 1000, 100000]):
sample = uniform.rvs(size=n)
axs[i].hist(sample, density=True)
axs[i].set_xlabel('x', fontsize=16)
axs[i].plot(x, unif_dist, color='red',
label="Uniform distribution")
axs[i].set_ylim([0, 1.5])
axs[i].set_title("Sample size="+str(n), fontsize=14)
axs[i].legend(loc="lower center")
axs[0].set_ylabel('Probability density', fontsize=14)
plt.show()**

图 18
具有连续均匀分布的随机变量的均值和方差可计算如下:
****
指数分布
记住泊松分布的 PMF 是:

让随机变量 T 代表直到第一个泊松事件发生的时间。泊松事件在区间[0, t 不会发生的概率可以通过设置 x =0 来计算:

我们也可以写:

假设 T 具有泊松分布,其 PDF 为 f_T ( t )。现在,利用等式 1,我们可以写出:

我们现在可以使用等式 27 推导出 f_T ( t ):

这是指数分布的 PDF。一个连续的随机变量 X 被称为具有参数 λ (其中λ0)的指数分布,如果它的 PDF 如下:

这里我们用 x 和 λ代替等式 31 中的 t 和 r 。我们也用 X ~ Exp( λ )来表示。指数分布有一个代表事件率的单一参数 λ 。式 32 中的 λx 是无单位的,这意味着 λ 的单位是 x 的单位的倒数。例如,如果 x 以秒为单位,那么 λ 的单位就是 1/秒。
如上所述,参数为 λ 的指数随机变量表示平均速率为 λ 的泊松事件(泊松过程中的一个事件)第一次发生之前的等待时间。然而,泊松过程中的事件是独立发生的。因此,一旦一个事件发生,我们可以将时间重置为零,直到下一个事件的等待时间可以用我们计算直到第一个事件的等待时间的相同方法来计算。因此,这个等待时间也可以用一个具有相同平均速率的指数分布的随机变量来表示,我们得出结论,一个参数为 λ 的指数随机变量表示平均速率为 λ 的泊松过程中任意两个连续事件之间的等待时间。
具有指数分布的随机变量 X 的均值和方差可通过分部积分计算:



例如,假设平均速率为每分钟 5 个事件,那么 λ =5 min⁻。那么我们有

请注意 X 和 E [ X 都有相同的单位。由于 X 表示一个泊松事件的等待时间, E [ X 表示一个事件的平均等待时间。因此,平均每 1/5 分钟,我们应该期待一个事件发生。通过增加速率( λ ),平均等待时间( E [ X ))应该会减少。我们可以使用scipy中的对象expon来生成指数分布。该对象的方法采用与等式 32 中的 x 和 1/ λ 相对应的参数x和scale。清单 14 绘制了指数分布的 PDF,其中 λ =1, λ =5(图 19) 。
*# Listing 14
from scipy.stats import expon
x = np.arange(0, 4, 0.01)
lam1 = 1
lam2 = 5
plt.figure(figsize=(9, 6))
exp_dist1= expon.pdf(x=x, scale=1/lam1)
exp_dist2 = expon.pdf(x=x, scale=1/lam2)
plt.plot(x, exp_dist1, label=r'$\lambda=$'+str(lam1))
plt.plot(x, exp_dist2, label=r'$\lambda=$'+str(lam2))
plt.xlabel('x', fontsize=16)
plt.ylabel('$f_X(x)$', fontsize=16)
plt.legend(loc='best', fontsize= 14)
plt.show()*

图 19
如图 19 所示,通过增加速率( λ ,获得更短等待时间的机会增加了。
清单 15 展示了我们如何从泊松分布的随机变量中获得指数分布的 CDF。这里,我们假设rate是 20 min⁻,并且我们有从 0 到 1 分钟的 t 的值范围。对于 t 的每个值,我们从参数为 λ = rate × t 的泊松分布中获得一个大小为 1000 的随机样本,并计算该样本中非零元素的百分比(每个元素都是泊松分布的随机变量)。记住P(T<T)是事件在区间[0, t 发生的概率。样本中的非零变量意味着事件发生了,样本中非零变量的百分比是P(T<T)的一个估计值。这个百分比给出了具有相同比率的指数分布的P(T<T)=F _ T(T)的估计值。
最后,我们为 t 的所有值绘制 F_T ( t )来获得 CDF 曲线。在图 20 中,这个估计的 CDF 曲线已经与具有相同比率的指数分布的实际 CDF 曲线进行了比较。
*# Listing 15
np.random.seed(0)
rate = 20
cdf_list = []
t_range = np.arange(0, 1, 0.005)
plt.figure(figsize=(7, 4))
for t in t_range:
lam = rate*t
rvs = poisson.rvs(mu=lam, size = 1000)
cdf_list.append((rvs > 0).mean())
plt.plot(t_range, cdf_list, linewidth=5, color='orange',
label='CDF of first occurence of Poisson dist')
exp_dist_cdf= expon.cdf(x=t_range, scale=1/rate)
plt.plot(t_range, exp_dist_cdf, color='black',
label='Exponential dist CDF')
plt.xlabel('t', fontsize=16)
plt.ylabel('$F_T(t)$', fontsize=16)
plt.legend(loc='best', fontsize= 14)
plt.show()*

图 20
指数分布也是无记忆的。这意味着如果一个事件到时间 t 还没有发生,那么它在区间 t 到 t + s 没有发生的概率与它在区间 0 到 s 没有发生的概率相同。数学上:

我们可以很容易地证明这个性质:

清单 16 以图形方式展示了这个属性。结果如图 21 所示。图 21(上图)显示了指数分布的 PDF。P(X>t)和 P ( X > s )分别等于 x > t 和 x > s 的 PDF 曲线下面积。在时间 t 之后,PDF 曲线(其中xt 可以认为是所有剩余事件的 PDF。但是,它下面的面积不等于 1。我们可以用它除以P(X>t)来归一化。在这种情况下, t 之后的归一化 PDF 将作为 x = t 之后剩余事件的 PDF。正如您在图 21 底部看到的这个标准化 PDF 在 x = t+s 之后的形状与图 21 顶部的原始指数 PDF 的形状相同。所以,相应的面积

在归一化的指数 PDF 中等于原始指数 PDF 中对应的P(X>s)的面积。
*# Listing 16
x = np.arange(0, 5, 0.01)
lam = 1
t = 0.5
s = 1
fig, axs = plt.subplots(2, 1, figsize=(7, 8))
plt.subplots_adjust(hspace=0.25)
exp_dist= expon.pdf(x=x, scale=1/lam)
p_x_gt_t = 1-expon.cdf(x=t, scale=1/lam)
exp_dist_normalized= expon.pdf(x=x, scale=1/lam) / p_x_gt_t
axs[0].plot(x, exp_dist, linewidth=3, color='blue')
axs[0].fill_between(x, exp_dist, 0, where = (x > t) & (x <= np.inf),
color = 'lightgreen')
axs[0].fill_between(x, exp_dist, 0, where = (x > s) & (x <= np.inf),
color = 'orange')
axs[0].axvline(x=t, color='black')
axs[0].axvline(x=s, color='black')
axs[0].text(t+0.05, 1.25, "t", fontsize=18)
axs[0].text(s+0.05, 1.25, "s", fontsize=18)
axs[0].text(0.7, 0.06, "II", fontsize=18)
axs[0].text(1.3, 0.06, "I", fontsize=18)
axs[0].text(1.6, 0.8, r"$Area \; I+Area \; II=P(X>t)$", fontsize=16)
axs[0].text(1.6, 0.6, r"$Area \; I=P(X > s)$", fontsize=16)
axs[0].text(2.3, 0.16, r"$Exp(\lambda)$", fontsize=14, color='blue')
axs[0].set_xlabel('x', fontsize=16)
axs[0].set_ylabel('$f_X(x)$', fontsize=16)
axs[0].set_xlim([0, 4])
axs[0].set_ylim([0, 1.4])
axs[1].plot(x, exp_dist_normalized, linewidth=3, color='red')
axs[1].plot(x, exp_dist, linewidth=3, color='blue')
axs[1].fill_between(x, exp_dist_normalized, 0,
where = (x > t+s) & (x < np.inf),
color = 'orange')
axs[1].axvline(x=t, color='black')
axs[1].axvline(x=t+s, color='black')
axs[1].text(0.6, 1.25, "t", fontsize=18)
axs[1].text(1.6, 1.25, "t+s", fontsize=18)
axs[1].text(1.6, 0.06, "I", fontsize=18)
axs[1].text(1.9, 0.75, "$Area \; I= $", fontsize=16)
axs[1].text(2.7, 0.75, r"$\frac{P(X > t+s)}{P(X>t)}$", fontsize=22)
axs[1].text(0.8, 0.88, r"$\frac{Exp(\lambda)}{P(X>t)}$",
fontsize=20, color='red')
axs[1].text(2.1, 0.5, r"$=P(X > t+s| X>t)$", fontsize=15,
color='black')
axs[1].text(0.6, 0.2, r"$Exp(\lambda)$", fontsize=14, color='blue')
axs[1].set_xlabel('x', fontsize=16)
axs[1].set_ylabel('$f_X(x)$', fontsize=16)
axs[1].set_xlim([0, 4])
axs[1].set_ylim([0, 1.4])
plt.show()*

图 21
指数分布是唯一无记忆的连续分布。因此,如果 X 是具有无记忆性质的正连续随机变量,那么 X 具有指数分布。
让我们看一个例子。假设公交车到达的等待时间呈指数分布。我们可能会直觉地认为,我们等待的时间越长,到达的可能性就越大。例如,如果公共汽车应该每隔 15 分钟来一辆,而你已经等了 14 分钟,那么你可能期望下一辆公共汽车应该很快来。然而,基于无记忆特性,等待时间与下一个到达时间无关。
我们可以证明指数分布是几何分布的一种极限形式。记住,几何分布与离散的随机变量有关,然而,指数分布与连续变量有关。因此,要将几何分布转换成指数分布,我们需要一种将离散随机变量转换成连续变量的方法。设离散随机变量 X 具有参数 p 的几何分布,并假设它代表第一次成功发生的试验。所以,我们有:

设 h 为小正数, hX 为离散随机变量。由于 X 取值 1,2,3,…,那么 hX 对应的值就会是 h ,2 h ,3 h ,…。随着 h 趋于零, hX 的值之差趋于零,离散随机变量 hX 变成连续随机变量(图 22)。

图 22(来源:作者图片)
设 p = λh 其中 λ 为正参数。现在我们可以写:
**
其中⌊ t / h ⌋表示小于等于t/h(tt/h的下限)的最大整数。一般来说, t / h 是一个实数,所以要将它用于几何分布,我们需要将其转换为整数。我们知道这一点

当 p 趋于零时, h 也趋于零。在这种情况下,我们有:

和

因此,我们得到:

这意味着

但这是指数分布的 CDF(等式 30)。所以,我们从一个随机变量 X 开始,它具有参数 p 的几何分布,并且T51我们假设 p = λh. 然后我们表明当 h 变为零(这也意味着 p 变为零)时,真正的随机变量 hX 清单 17 展示了参数为 p 的几何分布的 1-CDF 如何随着 p 趋向于零而趋向于指数分布。**
*# Listing 17
np.random.seed(0)
t_lims = [0, 4]
lam = 1
fig, axs = plt.subplots(1, 2, figsize=(17, 5))
for id, h in enumerate([2e-1, 1e-2]):
p = lam * h
t = np.arange(t_lims[0], t_lims[1], h)
t_h = np.round(t / h)
exp_dist_plot = 1- expon.cdf(x=t, scale=1/lam)
geom_dist_plot = 1- geom.cdf(k=t_h, p=p)
axs[id].plot(t, exp_dist_plot, color='red',
label="exponential dist")
axs[id].bar(t, geom_dist_plot, width=h)
axs[id].set_title("h={0}, p={1}".format(h, p),
fontsize=14)
axs[id].set_xlabel('t', fontsize=16)
axs[id].set_ylabel('P(hX>t)', fontsize = 18)
axs[id].set_xlim([0, 4])
axs[id].legend(loc='best', fontsize=14)
plt.show()*

图 23
伽马分布
请记住,具有参数为 λ 的指数分布的随机变量代表平均速率为 λ 的泊松过程中任意两个连续事件之间的等待时间。因此,它也给出了平均速率为 λ 的泊松事件首次发生之前的等待时间。伽马分布是指数分布的推广。它有两个参数 α 和 β 。具有伽马分布的随机变量 T 表示平均速率为 β 的泊松事件第 α 次发生之前的等待时间。根据泊松分布的 PMF(等式 22),我们有:

我们可以说第 α 个事件的等待时间小于或等于 t 当且仅当区间[0, t 中的事件数大于或等于 α 。

这就给出了 T 的 CDF。现在,我们使用等式 27 计算其 PDF:

这两个总和中的项相互抵消,只剩下第二个总和的第一项:

我们也可以用伽玛函数来写这个 PDF。对于每个正数 α ,伽马函数定义为以下积分:

对于任意正整数 α ,可以证明:

因此,等式 35 也可以写成:

如果我们用 X 代替随机变量 T ,用 β 代替 r 表示速率,我们得到:

现在我们准备定义伽玛分布。设 α 和 β 为正实数。连续随机变量 X 具有参数 α 和 β 的伽玛分布,如果 X 具有如下 PDF:

我们用 X ~ Gamma( α , β )来表示这一点。如前所述,当 α 为正整数时,具有参数 α 和 β 的伽马分布的随机变量表示平均速率为 β 的泊松事件第 α 次发生之前的等待时间。这是伽马分布的一个特例,称为尔朗分布。
如果我们设置 α =1,那么我们得到:

这是指数分布的 PDF(等式 32)。因此,伽玛分布是指数分布的推广,就像负二项分布是几何分布的推广一样。图 24 显示了指数分布和伽玛分布之间的关系。

图 24(来源:作者图片)
我们看到指数分布是几何分布的连续模拟。同样,伽玛分布是负二项式分布的连续模拟。
在scipy中,对象gamma创建指数分布。该对象的方法取参数x、a、scale,对应于等式 37 中的 x 、 α 和 1/ β 。清单 18 绘制了具有不同值 α 和 β (图 25) 的伽马分布的 PDF。
*# Listing 18
from scipy.stats import gamma
x = np.arange(0, 4, 0.01)
alpha1 = 0.1
alpha2 = 1
alpha3 = 5
beta_val = 5
plt.figure(figsize=(9, 6))
gamma_dist1 = gamma.pdf(x=x, a=alpha1, scale=1/beta_val)
gamma_dist2 = gamma.pdf(x=x, a=alpha2, scale=1/beta_val)
gamma_dist3 = gamma.pdf(x=x, a=alpha3, scale=1/beta_val)
expon_dist = expon.pdf(x=x, scale=1/beta_val)
plt.plot(x, gamma_dist1,
label=r'$Gamma \; distribution \; \alpha={}, \beta={}$'.\
format(alpha2, beta_val))
plt.plot(x, gamma_dist2,
label=r'$Gamma \; distribution \; \alpha={}, \beta={}$'.\
format(alpha1, beta_val),
linewidth=5, color='orange')
plt.plot(x, gamma_dist3,
label=r'$Gamma \; distribution \; \alpha={}, \beta={}$'.\
format(alpha3, beta_val))
plt.plot(x, expon_dist,
label=r'$Exp \; distribution \; \lambda={}$'.\
format(beta_val),
linestyle='dashed', color='black')
plt.xlabel('x', fontsize=16)
plt.ylabel('$f_X(x)$', fontsize=16)
plt.xlim([0, 1.5])
plt.legend(loc='best', fontsize= 14)
plt.show()*

图 25
如你所见,Gamma(1, β )=Exp( β )。
可以看出,如果随机变量 X ₁、 X ₂、…、 Xₖ 相互独立,并且如果每个 Xᵢ 具有参数 αᵢ 和 β 的伽玛分布,那么总和x₁+x₂+……+xₖ具有参数 α 的伽玛分布这意味着对于具有相同平均速率的独立事件,等待时间是可加的。所以,如果 α ₁th 事件的等待时间是γ(α₁, β ,而 α ₂th 事件的等待时间是γ(α₂, β ,那么等待时间为( α**
利用这个性质,我们可以很容易地计算伽玛分布的平均值和方差。我们已经知道指数分布的均值和方差(等式 33 和等式 34)。如果我们有一个随机变量 X ,它具有参数为 α 和 β 的伽玛分布,我们可以把它写成一些具有指数分布的随机变量之和:

在哪里

现在,利用等式 10 和等式 11,我们得到:
****
基于我们对指数分布的讨论,如果泊松事件的平均速率是 β min⁻,那么一个事件的平均等待时间是 1/ β 分钟,所以平均每 1/ β 分钟,我们期望一个事件发生。因此,平均而言,第一个事件应在 1/ β 分钟后发生,第二个事件应在 2/ β 分钟后发生,第 α 个事件应在 α / β 分钟后发生。
如前所述,在伽马分布中,参数 α 和 β 都是正实数。如果 α 是一个正整数,那么具有这个 α 的伽玛分布也称为厄朗分布,具有这样一个分布的随机变量代表一个平均速率为 β 的泊松事件直到第 α 次出现的等待时间。但是一个非整数的随机变量 α 代表什么呢?让我们用一个例子来解释一下。我们知道 X ~ Gamma(1, β ) = Exp( β )代表等待时间,直到泊松事件的下一次发生。现在我们定义 X ₁ ~伽马(0.5, β )。从等式 38 我们知道

所以,如果 E [ X ]代表下一个事件的平均等待时间, E [ X ₁】代表下一个事件的平均等待时间的一半。泊松事件是离散的,所以我们不可能有像事件的一部分那样的东西,但是等待时间是一个连续的量,并且带有 α < 1 的伽马分布允许我们定义一个随机变量,它平均代表事件等待时间的一部分。因此,如果将 α < 1 和随机变量 X 定义为X~γ(α, β ),那么 X 的平均值代表 α ×直到下一个泊松事件的平均等待时间的 100%。
贝塔分布
连续随机变量 X 被称为具有参数 α > 0 和 β > 0 的贝塔分布,如果其 PDF 定义如下:

我们用X~β(α, β )来表示。我们知道 PDF 应该是归一化的,这意味着它在整个空间上的积分必须等于 1。这里函数 B 被包含在 PDF 中以使其标准化。根据等式 25,我们得到:

因此,使用这个等式和当x1 和x0 时 PDF 为零的事实,我们可以写出:

我们的结论是:

B( α ,β)称为β函数,可以看出β函数与γ函数有如下关系(式 36):

因此,对于 0<x1,贝塔分布的 PDF 也可以写成:

我们可以使用scipy中的对象beta创建一个测试版。该对象的方法采用与等式 40 中的 x 、 α 和 β 相对应的参数x、a和b。清单 19 绘制了不同的 α 和 β 值的 beta 分布的 PDF。
**# Listing 19
from scipy.stats import beta
x = np.arange(0, 1, 0.01)
param_list = [(1,1), (2,2), (5,1)]
plt.figure(figsize=(9, 6))
for (alpha_val, beta_val) in param_list:
beta_dist = beta.pdf(x=x, a=alpha_val, b=beta_val)
plt.plot(x, beta_dist,
label=r'$Beta \; distribution \; \alpha={}, \beta={}$'.\
format(alpha_val, beta_val))
plt.xlabel('x', fontsize=16)
plt.ylabel('$f_X(x)$', fontsize=16)
plt.legend(loc='best', fontsize= 14)
plt.show()**

图 26
在图 26 中,我们看到 α =1 且β=1 的β分布看起来像均匀分布。我们可以很容易地证明:
****
对比等式 29,我们得出 beta(1,1)=U(0 , 1)。可以看出,贝塔分布的均值和方差如下:
****
我们知道一个概率总是位于 1 和 0 之间,所以如果我们需要一个代表概率本身的分布,就应该定义在区间[0,1]上。基于等式 40,贝塔分布的 PDF 定义在区间[0,1]上,所以我们可以用它来表示一个概率。
假设我们有一枚硬币,正面朝上的概率为 p 。如前所述,如果我们投掷这枚硬币 n 次,我们得到的正面总数可以用一个随机变量来表示,这个随机变量具有参数 n 和 p 的二项分布。现在假设我们不知道 p 的值,我们想通过观察 n 次投掷硬币的结果来推断它。
为此,我们可以使用贝叶斯方法。在这种方法中,我们假设未知概率 p 可以用一个随机变量来表示。这个随机变量有一个概率分布,称为先验分布。在我们扔硬币之前,先验分布表达了我们对 p 值的信念。抛完硬币后,我们收集了一些相关数据,因此使用这些新数据,我们可以更新对硬币的看法。这是使用贝叶斯法则完成的,结果是后验分布,它反映了我们对 p 的新看法。
投掷硬币的结果具有参数为 p 的伯努利分布。所以,硬币以概率 p 落地,但是我们不知道 p 的真实值。现在我们假设连续随机变量 P 代表未知概率 p 。所以, P 具有连续的概率分布,其 PDF 为 f ₚ( p )。 P 的概率分布是我们的先验分布。
如果我们知道 p 的真实值,让随机变量 X 代表 n 次投掷中的人头数。所以,当我们要写出 X = k 的概率时,需要写成条件概率:

条件概率是一个事件发生的概率,假定另一个事件已经发生。所以,P(X=k|P)表示 X = k 的概率,给定 P = p 。换句话说,它给出了假设我们知道 P 真值的情况下 X = k 的概率。现在使用贝叶斯法则,我们可以写出:

这里fₚ|ₓ(p|x=k)被称为条件 PDF。这是 P 的 PDF,因为我们在 n 投掷中有 k 头( X = k )。它更新了我们在观察 k 头部后对 P 的信念,所以是后验分布。在贝叶斯法则中,P(X=k|P)也被称为可能性。我们可以把它写成已知值为 p 的二项分布的 PMF(等式 9):

贝叶斯规则的分母可以写成:

叫做 X 的边际 PMF。是 X = k 的概率,独立于 P 的真值。后面我们会更详细的说一下 marginals,但是目前需要注意的是P(X=k)并不依赖于 p 。
现在让我们假设先验分布是参数为 α = a 和 β = b 的贝塔分布。所以,我们有:

现在使用等式 43,我们可以写出:

其中 c 是不依赖于 p 的常数。一个条件 PDF 应该被标准化,我们可以用它来确定 c 的值:

因此:

现在,如果我们将这些等式与贝塔分布的 PDF(等式 40)进行比较,我们会看到后验分布是一个贝塔分布,其参数为α=k+a,并且β=n-k+b和 c 应该是归一化因子:

所以,我们有:

意味着观察到 X = k 后的 P 的后验分布仍然是贝塔分布。请注意后验参数是如何通过将头数( k )加到第一个前验参数( a )和尾数( n - k )加到第二个参数( b )来计算的。
在统计学中,如果先验分布和后验分布是同一家族的成员,那么先验分布和后验分布称为共轭分布。此外,先验被称为似然的共轭先验。所以,我们说贝塔分布是二项分布之前的共轭分布。使用等式 44,我们可以找到贝塔分布的解释。假设随机变量 X 具有参数 α 和β的贝塔分布。我们将 n 和 k 定义为:
****
所以,我们可以写:

根据等式 44,我们知道如果 P 的先验分布是β(a, b ),那么这个等式给出了在 n 投掷中观察 k 头和 n - k 尾后 P 的后验分布。
如前所述,β(1,1)等于区间[0,1]上的均匀分布。当我们没有关于硬币的数据(没有投掷)时,Beta(1,1)对于 P 的先验分布是一个很好的选择。这意味着 P 同样可能取区间[0,1]上的任何值,因为我们没有数据来选择一个值。一旦我们开始抛硬币,我们会得到一些新的数据,并可以更新 P 的概率分布。所以β(1+k,1+ n - k )代表在观察 n 投掷中 k 头后 P 的概率分布,如果我们从均匀先验开始。
在清单 20 中,我们从伯努利分布中创建了一个包含 n 个随机变量的样本,其中 p =0.5。我们统计一的个数( k ),并绘制一个 Beta 的 PDF(1+k,1+ n - k )。我们对不同的 n 值重复这一过程。随着 n 的增加,PDF 在 0.5 处变成一个尖峰。事实上,随着 n 趋于无穷大,PDF 变成了狄拉克δ函数。
**# Listing 20
np.random.seed(0)
p = 0.5
n_list = [0, 10, 100, int(1e6)]
x = np.arange(0, 1, 0.01)
fig, axs = plt.subplots(2, 2, figsize=(9, 9))
plt.subplots_adjust(hspace=0.4, wspace=0.4)
for i, n in enumerate(n_list):
k = binom.rvs(n=n, p=p)
beta_dist = beta.pdf(x=x, a=1+k, b=1+n-k)
axs[i//2, i%2].plot(x, beta_dist, label=r'n={}, k={}'.format(n, k))
axs[i//2, i%2].set_xlabel('p', fontsize=16)
axs[i//2, i%2].set_ylabel('$f_P(p)$', fontsize=16)
axs[i//2, i%2].set_title('n={}, k={}'.format(n, k), fontsize= 14)
axs[i//2, i%2].set_xticks(np.arange(0, 1.1, 0.25))
plt.show()**

图 27
狄拉克δ函数定义为:

所以,到处都是零,但在原点是无穷大。它还需要满足以下条件:

通过改变变量,我们也可以写出:
****
对于连续函数 f ( x ),我们可以证明:

图 28 显示了 δ ( x - a )的曲线图。

图 28
尽管狄拉克δ函数不是真正的 PDF,我们可以粗略地假设它是一个只允许一个值 X 发生的 PDF。所以,如果随机变量 X 的 PDF 是δ(X-a,那么它只能取一个值,就是 a 。利用狄拉克δ函数的性质,我们可以很容易地计算 X 的均值和方差。使用等式 46,我们可以写出:

所以, X 的意思是 a 。这是有意义的,因为 X 只能取一个值,即 a 。我们可以类似地计算 X 的方差:

这也是有意义的,因为 X 只能取一个值。现在让我们回到贝塔分布。假设:

我们还假设我们正在观察的硬币得到人头的真实概率是 p ,所以当 n 趋于无穷大时,人头数( k )应该趋向于 pn 。数学上:

然后利用等式 41 和等式 42,我们可以写出:
****
于是,随着 n 趋于无穷大,Beta(1+ k ,1+ n - k )趋于δ( x - p )。如图 30 所示,随着 n 的增加,我们获得了更多关于硬币的数据,我们对 p 真实值的不确定性降低,因此贝塔分布(后验分布)的方差也降低了。这里方差与我们对 p 真实值的不确定性成正比。当 n 趋于无穷大时,我们确定 p 的真值为 k / n ,所以方差为零,随机变量 P 只能取一个值为 k / n 。
在等式 45 中,我们使用β(1,1)作为先验分布,但请注意,基于我们对 p 的初始信念,我们可以使用不同参数的β分布。例如,我们可以选择 Beta(2,8)作为先验分布。该先验如图 29 所示(左图)。通过选择这个先验,我们假设我们有一个偏向的硬币,它更有可能落在反面。
贝塔分布的参数也可以是非整数。比如我们可以有β(3.6,0.4)。在这种情况下,我们可以将参数的小数部分分配给先验分布,并将其写成 Beta(0.6+3,0.4+3–3)。这意味着我们从β(0.6,0.4)开始作为先验分布,我们观察到 n =3 次抛硬币,所有这些都发生在正面。

图 29
图 29(右)中显示的β(0.6,0.4)表达了我们对 p 值的初始信念。这里我们假设 x 的值更接近于 x =0 和 x =1 的可能性更大。
正态分布
如果 X 具有以下 PDF,则连续随机变量 X 具有参数和 σ 的正态分布:

用 X ~ N ( ,σ )表示。可以看出:
**
因此参数和 σ 是 X 的均值和方差(这意味着 σ 是 X 的标准差)。这是正态分布的一个重要性质。到目前为止,我们看到的其他分布都与某个过程或某些事件的发生有关。在这些分布中,分布的均值和方差取决于分布参数,但它们不能独立变化。例如,在参数为 n 和 p 的二项分布中,平均值 np 由这些参数决定。但是,对于方差我们只有一个选择,那就是 np (1- p )。
正态分布是唯一允许我们选择分布的均值和方差作为分布参数的分布。此外,我们可以独立选择它们,因此它们互不依赖。这个性质使正态分布比其他任何分布都更灵活,正如我们后面看到的,这是它具有其他有趣性质的主要原因。
图 30 显示了正态分布的 PDF 的一般形状。它是一条关于点 x = 对称的钟形曲线。因此,既是分布的均值,也是分布的中位数。此外,PDF 曲线在该点达到最大值,因此也是分布的模式。

图 30(来源:图片由作者提供)
在scipy中,对象norm生成正态分布。该对象的方法采用参数x、loc和scale,这些参数对应于等式 47 中的 x 、、和 σ (请注意,在scipy中,我们确定的是分布的标准差,而不是其方差)。清单 21 用不同的值和(图 31) 绘制了正态分布的 PDF。**
**# Listing 21
from scipy.stats import norm
x = np.arange(-10, 15, 0.01)
mu = 2
sigma1 = 0.02
sigma2 = 1
sigma3 = 2
y1 = norm.pdf(x, loc = mu, scale = sigma1)
y2 = norm.pdf(x, loc = mu, scale = sigma2)
y3 = norm.pdf(x, loc = mu, scale = sigma3)
plt.figure(figsize=(8, 5))
plt.plot(x, y1, label='$\mu={}, \sigma^2={}$'.format(mu, sigma1))
plt.plot(x, y2, label='$\mu={}, \sigma^2={}$'.format(mu, sigma2))
plt.plot(x, y3, label='$\mu={}, \sigma^2={}$'.format(mu, sigma3))
plt.xlabel('x', fontsize=16)
plt.ylabel('$f_X(x)$', fontsize=16)
plt.xlim([-8, 12])
plt.ylim([0, 0.8])
plt.xticks(np.arange(-8, 12, 2))
plt.legend(loc='best', fontsize=12)
plt.show()**

图 31
方差(或标准偏差)显示了这些值与平均值的差距。它代表观察值和平均值之间的典型距离。请注意,方差越大,PDF 就越宽(也越短)。随着方差趋向于零,PDF 趋向于狄拉克δ函数,除了在 x = 处,该函数在任何地方都为零:

在清单 22 中,我们从一个正态分布的随机变量中抽取了 3 个大小不同的随机样本,其中=2,*= 1。我们绘制每个样本的直方图,并计算其均值和方差。随着样本量的增加,其均值和方差分别越来越接近和,并且直方图的形状越来越接近抽取样本的正态分布的 PDF(图 32)。当样本量趋近于无穷大时,其均值和方差分别趋近于、和 σ 。***
**# Listing 22
np.random.seed(1)
fig, axs = plt.subplots(1, 3, figsize=(15, 4))
plt.subplots_adjust(wspace=0.2)
x = np.arange(-10, 15, 0.01)
mu = 2
sigma = 1
normal_dist = norm.pdf(x, loc = mu, scale = sigma)
for i, n in enumerate([100, 1000, 100000]):
sample = norm.rvs(loc = mu, scale = sigma, size=n)
sample_mean = np.round(sample.mean(), 3)
sample_var = np.round(sample.var(), 3)
axs[i].hist(sample, density=True, bins = 30,
edgecolor='black', linewidth=1)
axs[i].set_xlabel('x', fontsize=16)
axs[i].plot(x, normal_dist, color='red',
label="$Normal \; dist$\n $\mu=2, \sigma=1$")
axs[i].set_xlim([-4, 8])
axs[i].set_title("sample size={}\nsample mean={}\nsample variance={}".\
format(n, sample_mean, sample_var), fontsize=12)
axs[i].legend(loc="best")
axs[0].set_ylabel('Probability density', fontsize=14)
plt.show()**

图 32
如果 X 具有参数和 σ 的正态分布,其值落在平均值的一个标准差内的概率大致为 0.66。数学上:

利用等式 26,我们也可以将其写成:

也就是说P(-σ≤X≤σ)是 PDF 曲线下 x =- σ 和 x = σ 之间的面积。同样,我们有:
****
这三个属性合起来称为 68–95–99.7 规则。清单 23 使用cdf()方法展示了这条规则。结果如图 33 所示。
**# Listing 23
fig, axs = plt.subplots(1, 3, figsize=(18, 4))
plt.subplots_adjust(wspace=0.1)
x = np.arange(-10, 15, 0.01)
mu = 0
sigma = 1
normal_dist = norm.pdf(x, loc = mu, scale = sigma)
for i in range(3):
axs[i].plot(x, normal_dist, color='black')
xs = (i+1) * sigma
val = norm.cdf(xs) - norm.cdf(-xs)
axs[i].fill_between(x, normal_dist, 0,
where = (x >= -xs) & (x <= xs), color = 'orange')
axs[i].set_xlim([-5, 5])
axs[i].set_ylim([0, 0.45])
if xs != 1:
axs[i].set_title(r"$P({0}\sigma \leq x \leq {1}\sigma) \approx {2}$".\
format(-xs, xs, np.round(val, 3)), fontsize=16)
else:
axs[i].set_title(r"$P(\sigma \leq x \leq \sigma) \approx {0}$".\
format(np.round(val, 3)), fontsize=16)
axs[i].set_xlabel('x', fontsize=16)
axs[i].set_xticks(np.arange(-4*sigma, 4*sigma+1, sigma))
fig.canvas.draw()
labels = [item.get_text() for item in axs[i].get_xticklabels()]
new_labels= [(item + r"$\sigma$").replace('1', '').replace('-1', '') \
if item !='0' else item for item in labels]
axs[i].set_xticklabels(new_labels, fontsize = 13)
axs[i].set_yticks([])
axs[0].set_ylabel('$f_X(x)$', fontsize=18)
plt.show()**

图 33
= 0 、 σ =1 的正态分布称为标准正态分布。标准正态分布的 PDF 通常用符号φ表示。

可以证明,每个独立且正态分布的随机变量的线性组合也将具有正态分布。如果随机变量 X ₁、 X ₂、…、 Xₙ 相互独立且每一个 Xᵢ 具有均值 μᵢ 和方差 σ ᵢ 的正态分布,且如果 a ₁、 a ₂、…, aₙ 和 b 为常数,则随机变量x=a₁x₁+a₂x₂+aₙxₙ+b具有均值的正态分布a₁μ₁+ 数学上:

图 34 显示了该属性的一个示例。这里随机变量 X ₁ ~ N (1,10)和 X ₂~ N (8,1)相加的结果就是正态分布变量 X ~ N (9,11)。

图 34
现在让 Z 是一个标准正态分布的随机变量。我们将随机变量 X 定义为 Z 的线性变换:

我们知道 Z 的均值和方差分别为 0 和 1。基于正态分布的上述性质, X 具有均值和方差 σ 的正态分布。所以,每个正态分布都可以写成标准正态分布的线性变换。相反,如果 X 具有均值和方差 σ 的正态分布,则随机变量

具有标准的正态分布。
中心极限定理(CLT)
正态分布是统计学中最重要的概率分布,中心极限定理(CLT)是其主要原因。为了解释 CLT,我们应该先熟悉一下抽样分布。假设我们有一个来自随机变量 X 表示的分布的大小为 n 的样本。例如,1,1,0,1,0 是伯努利分布中大小为 5 的样本。但是,由于这是一个随机样本,所以抽取另一个样本会导致不同的数字。我们可以将这个样本表示为 x ₁、 x ₂、…、 xₙ.但是我们知道每个 xᵢ 都是从同一个分布中随机抽取的。所以一般我们可以假设 xᵢ 是同分布的随机变量 Xᵢ 可以取的一个值。因此,如果我们有一个由随机变量 X 表示的分布,我们从一组随机变量 X ₁、 X ₂、…、 Xₙ 的分布中表示一个大小为 n 的随机样本,其中每个 Xᵢ 都是具有相同分布的随机变量。
我们还假设这些随机变量是相互独立的。这样的随机样本被称为 IID(独立同分布)样本,在本文中,当我们说一个随机样本,这意味着它是 IID。集合 X ₁, X ₂,…, Xₙ 代表可以从一个分布中抽取的任意一个大小为 n 的随机样本。当我们从分布中抽取实际样本时,每个随机变量 Xᵢ 将取一个随机值。因此,1,1,0,1,0 是来自伯努利分布的大小为 5 的样本,而 X ₁, X ₂,…, X ₅ 是可以从伯努利分布中抽取的任何大小为 5 的随机样本的一般形式。
这组随机变量 X ₁、 X ₂、…、 Xₙ 允许我们更一般地研究一个随机样本,并导出其统计特性。我们用 X̅ 表示样本均值:

样本平均值是静态样本的一个例子。样本统计量或(简称统计量)是使用样本值计算的任何数量。数学上,如果 f 是 n 个实变量的任意实值函数。那么随机变量t=f(x₁, X ₂,…, Xₙ )就叫做一个统计量。所以, T 只是样本元素的函数。像任何其他随机变量一样,统计数据可以有一个概率分布。抽样分布是像 T 这样的统计的概率分布。为了计算这个分布,我们需要原始分布中所有可能的大小为 n 的样本。当然,如果原始分布是连续的,我们需要无限多的样本。
例如,假设我们从一个分布中重复抽取所有可能的样本,大小为 n ,并计算每个样本的平均值。这些平均值的分布就是样本平均值的抽样分布。假设我们有一个正态分布, μ= 2, σ =1, X ₁, X ₂,…, X ₅代表这个分布中一个大小为 5 的随机样本。清单 24 描绘了从这个正态分布中抽取的样本的样本均值的抽样分布。我们首先从正态分布中抽取 10000 个大小为 n =5 的样本,并计算样本均值。然后,我们绘制代表抽样分布的样本均值直方图。我们遵循相同的程序来绘制 n =50 和 n =5000 的抽样分布。曲线图如图 35 所示。
*# Listing 24
np.random.seed(2)
mu = 2
var = 1
n1 = 5
n2 = 50
num_samples = 10000
x = np.arange(-1, 5, 0.01)
dist = norm.pdf(x, loc = mu, scale = np.sqrt(var))
fig, axs = plt.subplots(1, 4, figsize=(15, 4))
axs[0].plot(x, dist, color='red')
axs[0].set_title('Original distribution \nNormal: $\mu={}, \sigma^2={}$'.\
format(mu, var), fontsize = 14)
axs[0].set_xlim([-1, 5])
axs[0].set_ylim([0, 1])
axs[0].set_xlabel('$x$', fontsize=16)
axs[0].set_ylabel('Probability density', fontsize=16)
for i, n in enumerate([5, 50 ,5000]):
samples = norm.rvs(loc = mu, scale = np.sqrt(var),
size=n* num_samples).reshape(num_samples, n)
samples_means = samples.mean(axis = 1)
sampling_dist = norm.pdf(x, loc = mu, scale = np.sqrt(var/n))
axs[i+1].plot(x, sampling_dist, color='black')
axs[i+1].hist(samples_means, density=True, bins = 50)
axs[i+1].set_title(
'Sampling distirbution \n $n={0}, \mu={1}, \sigma^2={2}/{0}$'.\
format(n, mu, var), fontsize = 14
)
axs[i+1].set_xlim([-1, 5])
axs[i+1].set_xlabel(r'$\bar{x}$', fontsize=16)
plt.show()*

图 35
我们还可以使用等式 10 和等式 11 计算样本均值的采样分布的均值和方差(它们对离散和连续随机变量都有效)。假设原始分布有均值 μ 和方差 σ 。记住在一个随机样本中 X ₁、 X ₂、…、 Xₙ ,每一个 Xᵢ 都是一个分布相同的随机变量,所以它具有与原分布相同的均值和方差。现在,根据等式 10 和等式 11 可以得出:
**
因此, X̅ 的平均值等于从中抽取随机样本的分布的平均值,然而, X̅ 的方差仅为该分布方差的 1/ n 倍。因此,与原始分布相比, X̅ 的概率分布更集中在平均值附近。这在图 35 中也很明显。
我们也可以解释方程 52 和方程 53 背后的直觉。为了计算 X̅,的均值和方差,我们需要从原始分布中抽取所有可能的样本,样本大小为 n 。我们还需要计算每个样本的均值, E [ X̅ ]等于这些样本均值。但是我们也可以首先计算所有样本上每个 Xᵢ 的平均值,然后计算 E [ Xᵢ 对于 i =1… n 的平均值。由于e[xᵢ=μ,所以 E [ Xᵢ 对于 i =1… n 的均值也等于 μ 。
如图 35 所示,抽样分布的方差低于从中抽取样本的原始分布的方差。在原始分布中,异常值(出现几率较低的 x 的极值)对方差的影响很大。事实上,如果我们忽略这些异常值,方差会显著下降。在从原始分布抽取的每个样本中,样本均值将更接近出现概率更高的样本元素,因为原始分布的极值在每个样本中出现的机会更小。所以通过计算样本的平均值,我们忽略了异常值。因此,当我们收集抽样分布中所有样本的平均值时,与从中抽取样本的原始分布相比,样本平均值将具有较低的方差。
随着 n 的增加,采样分布的方差减小。这是因为有了更多来自原始分布的元素,每个样本的平均值将更接近原始分布的平均值, μ 。当 n 趋于无穷大时,每个样本与原始分布相同,所以每个样本的均值恰好等于原始分布的均值( μ ),抽样分布的方差将为零。在这种情况下,样本平均值的采样分布变成狄拉克δ函数,除了在处,该函数在任何地方都为零:

正如我们之前提到的, n 个独立且正态分布的随机变量的任何线性组合都具有正态分布,因此在清单 24 中, X̅ 应该具有均值2、方差1/ n 的正态分布(基于等式 49)。您还可以看到,在图 35 中, X̅ 的直方图符合这样的正态分布。请注意,当原始分布为正态时,等式 52 和等式 53 的结果也与等式 49 的结果一致。
但是如果抽取样本的原始分布不是正态分布呢?在这种情况下,我们可以使用中心极限定理* (CLT)。CLT 声明,如果从任何具有均值 μ 和方差 σ 的分布(无论该分布是离散的还是连续的)中抽取足够大的随机样本,那么样本均值( X̅ ) 的分布将近似为具有均值 μ 和方差 σ / n 的正态分布。根据 CLT 和方程 49,我们还可以得出总和的分布*

将近似为具有平均值 nμ 和方差 nσ 的正态分布,因为该总和等于 nX̅.
作为一个例子,清单 25 显示了样本数量为 50 的样本均值的抽样分布,该样本来自于 λ=1 的指数分布。我们知道基于这个指数分布的均值和方差都等于 1(等式 33 和 34)。如图 36 所示,抽样分布符合正态分布,其中 μ= 1, σ =1/50。
*# Listing 25
np.random.seed(1)
n= 50
mu = 1
sigma = 1
xbar = [expon.rvs(size=n).mean() for i in range(10000)]
x = np.arange(0, 2, 0.01)
y = norm.pdf(x, loc=mu, scale= sigma / n**0.5)
plt.figure(figsize=(10, 6))
plt.hist(xbar, density=True, bins = 60, edgecolor='black', linewidth=1)
plt.plot(x, y, color = 'red',
label="Normal distribution \n $\mu={}, \sigma^2=1/{}$".\
format(mu, n))
plt.legend(loc='best', fontsize=14)
plt.xlim([0, 2])
plt.ylim([0, 3.5])
plt.xlabel(r'$\bar{x}$', fontsize=18)
plt.ylabel('Probability density', fontsize=16)
plt.show()*

图 36
CLT 认为样本应该足够大,但是到底应该有多大呢?一般来说,通常认为等于或大于 30 的样本量足以容纳 CLT。
清单 26 绘制了从指数分布和均匀分布中抽取的样本的样本均值的分布,其中 n = 5、10 和 30。结果如图 37 所示。正如您在 n =30 处看到的,采样分布看起来近似正态。然而,在 n 的较低值处,从均匀分布中抽取的样本的抽样分布更接近正态分布。这是因为与指数分布相比,均匀分布具有更对称的形状。
*# Listing 26
np.random.seed(0)
fig, axs = plt.subplots(2, 4, figsize=(18, 8))
plt.subplots_adjust(wspace=0.2)
x = np.arange(0, 15, 0.01)
mu1 = 1
sigma1 = 1
mu2 = uniform.mean()
sigma2 = uniform.std()
x1 = np.arange(-1, 3, 0.01)
x2 = np.arange(0, 2, 0.01)
x = np.arange(-1, 2, 0.01)
dist1 = expon.pdf(x)
dist2 = uniform.pdf(x)
axs[0, 0].plot(x, dist1, color = 'black',
label="Exponential \n distribution")
axs[1, 0].plot(x, dist2, color = 'black',
label="Uniform \n distribution")
axs[0, 0].set_title("Original distribution", fontsize = 16)
axs[1, 0].set_xlabel('x', fontsize=16)
for i, n in enumerate([5, 10, 30]):
xbar1 = [expon.rvs(size=n).mean() for i in range(10000)]
xbar2 = [uniform.rvs(size=n).mean() for i in range(10000)]
y1 = norm.pdf(x1, loc=mu1, scale= sigma1 / n**0.5)
y2 = norm.pdf(x2, loc=mu2, scale= sigma2 / n**0.5)
axs[0, i+1].hist(xbar1, density=True, bins = 60)
axs[1, i+1].hist(xbar2, density=True, bins = 60)
axs[0, i+1].plot(x1, y1, color = 'red', label="Normal \n dist")
axs[1, i+1].plot(x2, y2, color = 'red', label="Normal \n dist")
axs[0, i+1].set_xlim([-0.5, 3])
axs[1, i+1].set_xlim([0, 1])
axs[0, i+1].set_title("n="+str(n), fontsize = 16)
axs[1, i+1].set_xlabel(r'$\bar{x}$', fontsize=16)
axs[0, i+1].legend(loc="best")
axs[1, i+1].legend(loc="best")
axs[0, 0].set_ylabel('Probability density', fontsize=14)
axs[1, 0].set_ylabel('Probability density', fontsize=14)
plt.show()*

图 37
但是为什么对于足够大的 n 值,采样分布是大致对称的,尽管原始分布是不对称的?首先,让我们仔细看看对称分布。在对称分布中,它的 PDF 或 PMF 反映在随机变量的某个值处的一条垂直线周围,这条线是轴对称的。此外,分布的中值和平均值(如果存在)在这条线上(图 38)。

图 38(来源:作者图片)
根据中值的定义,随机变量取值小于中值的概率等于取值大于中值的概率。因为在对称分布中,平均值和中值是相同的,所以我们有:

清单 27 从 n =5 的指数分布中抽取了 100 个随机样本。原始分布如图 39 顶部所示。样本显示在中间。每行上的点代表一个样本,红点表示该样本的平均值。黑色垂直线表示原始分布的平均值,也是采样分布的平均值。在图 39 的底部,显示了这些样本平均值的直方图。
*# Listing 27
np.random.seed(0)
x = np.arange(0, 7, 0.01)
n = 5
num_samples = 100
samples = expon.rvs(size=n*num_samples).reshape(num_samples, n)
fig, axs = plt.subplots(3, 1, figsize=(15, 15),
gridspec_kw={'height_ratios': [1, 2.5, 1]})
plt.subplots_adjust(hspace=0.3)
xbar = samples.mean(axis = 1)
dist = expon.pdf(x)
axs[0].plot(x, dist, color = 'black')
axs[0].set_title("Original distribution (Exp)", fontsize = 14)
axs[0].set_xlabel('x', fontsize=16)
axs[0].set_xlim([0, 7])
axs[0].set_ylabel(r'$f_X(x)$', fontsize=16)
for i in range(num_samples):
axs[1].plot(samples[i], n*[i], marker='o',
color = 'blue', alpha = 0.2)
axs[1].scatter(xbar[i], i, color = 'red', s=25)
axs[1].axvline(x=1, color='black')
axs[1].set_title("Samples (n={})".format(n), fontsize = 14)
axs[1].set_xlabel('x', fontsize=16)
axs[1].set_ylabel('Sample ID', fontsize=16)
axs[1].set_xlim([0, 7])
axs[2].hist(xbar, density=True, bins = 7,
edgecolor='black', linewidth=1)
axs[2].set_xlim([0, 7])
axs[2].set_ylim([0, 2])
axs[2].set_title("Sampling distirbution histogram (n={})".format(n),
fontsize = 14)
axs[2].set_xlabel(r'$\bar{x}$', fontsize=16)
plt.show()*

图 39
图 40 显示了 n =30 时的相同曲线。如果我们将这个数字与之前的数字进行比较,我们会注意到随着 n 的增加,我们会从每个样本的原始分布中获得更多的异常值。

图 40
请记住,当样本量趋于无穷大时,它的平均值接近从中抽取样本的分布的平均值。当 n 很小时,得到的样本并不能很好地代表整个分布,如果我们绘制这个样本的直方图,它与原始分布的直方图并不十分相似。对于一个很小的值 n ,我们没有得到那么多的离群值,每个样本中的大部分元素都有一个很低的值 x 。因此,样本均值通常小于原始分布的均值,我们得到:

这意味着采样分布是不对称的。
然而,随着 n 的增加,我们得到一个更有代表性的样本,其中元素的频率更接近原始分布的频率。现在我们在每个样本中得到更多的异常值,它们增加了样本平均值高于分布平均值的机会。最后,我们在高于和低于原始分布均值的样本均值数量之间达到平衡。因此,我们有:

这意味着采样分布变得大致对称。
请记住,具有参数为 p 和 n 的二项式分布的随机变量是具有参数为 p 的伯努利分布的 n 个随机变量的总和。对于伯努利分布,平均值为 p ,方差为 p (1- p )(等式 6 和 7)。所以基于 CLT,对于足够大的 n ,, n 伯努利随机变量之和的分布将近似为均值 np 和方差 np (1- p 的正态分布。但这些值正是二项分布的均值和方差。
因此,我们的结论是,对于从伯努利分布中抽取的样本,二项式分布可以被认为是样本和的抽样分布。对于足够大的 n ,参数为 n 和 p 的二项分布可以近似为具有相同均值( np )和方差( np (1- p ))的正态分布。清单 28 显示了一个例子。
*# Listing 28
n = 50
p = 0.5
sample_size = 30
x1 = np.arange(0, n+1)
x2 = np.arange(0, n+1, 0.01)
binomial = binom.pmf(k=x1,n=n, p=p)
mu = n*p
var = n*p*(1-p)
normal = norm.pdf(x2, loc=mu, scale= var**0.5)
plt.figure(figsize=(8, 6))
plt.bar(x1, binomial,
label = "Binomial distribution, n={0}, p={1}".format(n, p))
plt.plot(x2, normal, color = 'red',
label=r"Normal dist $\mu={0}, \sigma^2={1}$".format(mu, var))
plt.xlabel('x', fontsize=16)
plt.ylabel('Binomial PMF, Normal PDF', fontsize=16)
plt.xlim([10, 40])
plt.ylim([0, 0.16])
plt.legend(loc="best", fontsize=14)
plt.show()*

图 41
这里我们有 p =0.5、 n =50 的二项分布,所以它的均值和方差分别是 25 和 12.5,可以近似为均值和方差相同的正态分布。
我们不打算在本文中证明 CLT,但是,我们可以直观地解释为什么它成立。记住,正态分布允许我们独立地选择随机变量的均值和方差。我们还提到,如果我们从具有均值 μ 和方差 σ 的分布中抽取大小为 n 的样本,样本均值的抽样分布的均值和方差将分别为 μ 和 σ / n 。这里均值是常数,但是方差随着 n 变化。因此,为了逼近抽样分布,我们需要一个均值和方差可以独立变化的分布,并且我们可以给方差指定任何期望的值。另外,在较大的 n 值下,采样分布大致对称,因此目标分布也应该对称。
最后,我们看到随着 n 的增加,在采样分布中得到异常值的机会显著降低,当 n 趋于无穷大时,它变成狄拉克δ函数(图 35)。因此,我们需要一种能够快速消除 n 较大值处异常值的分布。正态分布(等式 47)的 PDF 的指数部分允许我们以指数速率降低概率密度,并且我们知道随着 n 趋近于无穷大,其 PDF 趋向于狄拉克δ函数。所有这些特性使得正态分布成为在足够大的 n 值下唯一能够近似采样分布的分布。
CLT 解释了为什么在物理实验中研究的许多随机变量的分布是近似正态的。例如,我们可以把一个人的身高想象成一个随机变量。我们知道有许多随机因素会影响一个人的身高。如果我们把这些随机因素看作随机变量,那么人的身高就是这么多随机变量的总和。所以以 CLT 为基础,大量人口的身高分布将大致正常。
卡方分布
如果随机变量 X 具有γ分布,参数α = m /2 和 β =其中 m > 0,则 X 具有卡方分布和 m 自由度。我们把它写成

自由度通常是整数,但也可以是实数。基于这一定义,我们可以用γ分布的 PDF(公式 37)来定义卡方分布的 PDF:

我们知道γ(1, λ )=Exp( λ )。所以如果 m =2,那么 X 也呈指数分布,其中 λ =。我们可以使用等式 38 和等式 39 轻松计算卡方分布的均值和方差:
**
可以看出,如果 Z ₁、 Z ₂、…、 Zₘ 都是独立的、标准的正态随机变量,那么它们的平方和

具有带 m 自由度的卡方分布。
在scipy中,物体chi2产生卡方分布。本对象的方法取参数x和df,对应式 54 中的 x 和 m 。清单 29 绘制了不同 m 值的卡方分布 PDF(图 42) 。
*# Listing 29
from scipy.stats import chi2
x = np.arange(0, 10, 0.01)
plt.figure(figsize=(8, 5))
for m in [1, 2, 3, 5]:
y = chi2.pdf(x, df=m)
plt.plot(x, y, label='m={}'.format(m))
plt.xlabel('x', fontsize=16)
plt.ylabel('$f_X(x)$', fontsize=16)
plt.xlim([0, 5])
plt.ylim([0, 0.5])
plt.legend(loc='best', fontsize = 12)
plt.show()*

图 42
卡方分布描述样本方差的分布,用于估计分布的真实方差。因此,这是一个重要的分布。
样本方差
假设 X ₁、 X ₂、…、 Xₙ 是均值为 μ 且方差为 σ 的分布中的随机样本。我们已经在等式 51 中引入了样本均值 X̅ 。我们还可以使用等式 3 计算该样本的方差:

在等式 52 中,我们表明

这意味着样本均值是 μ 的无偏估计量。如果一个估计量的期望值等于被估计的参数,就说这个估计量是无偏的。这里,每个样本的 X̅ 的值可以不同于 μ ,但是如果我们从原始分布中抽取了所有可能的大小为 n 的样本,并计算每个样本的 X̅ ,那么 E [ X̅ 等于 μ 。换句话说, X̅ 给出了 μ 的平均正确值。
现在让我们看看 σ̂ 是否是原始分布的方差的无偏估计量( σ )。我们可以使用以下标识:

如果我们取上述等式两边的平均值并使用等式 10,我们得到:

这里,一个项的平均值是该项对于所有可能的大小为 n 的样本的平均值。我们知道 Xᵢ 来自方差为 σ 的分布,因此我们可以写出:

从等式 52 和 53 中,我们还知道采样分布的均值和方差。所以,我们可以写:

最后,使用方差的定义和 σ̂ ,我们可以将等式 56 写成:

这可以简化为:

我们也可以把它写成:

所以 σ̂ 不是原始分布的方差的无偏估计量( σ ),因为平均起来不等于 σ 。然而,如果我们将样本方差定义为:

那么接下来就是:

因此,样本方差 S 是 σ 的无偏估计量。
让我们看看为什么 σ̂ 不是 σ 的无偏估计量。当我们要计算 σ̂ 时,我们考虑随机变量 Xᵢ 对样本均值 X̅ 的偏离,但问题是 X̅ 也以方差 σ / n 偏离 μ 。基于等式 57,我们得到:

我们可以很容易地解释这个等式。随机变量 Xᵢ 以方差 σ̂ 偏离样本均值 X̅ 。由于每个样本的 σ̂ 的值不同,我们应该考虑它的平均值。然而, X̅ 也是样本相关的,它以方差 σ / n 偏离 μ 。因此 Xᵢ 应该偏离 μ ,其方差等于 σ̂ 的平均值和 X̅ 的方差之和。因此, E [ σ̂ ]等于原始分布的方差减去抽样分布的方差,这使其成为有偏估计量。这如图 43 所示。

图 43(来源:作者图片)
现在让我们看看为什么 S 是 σ 的无偏估计量。 S 和 σ̂ 的唯一区别是我们在分母中使用了 n -1,而不是 n 。由于 n 是大于 1 的整数,因此 S 大于 σ̂ 。根据等式 59,我们得到:

根据等式 58,可以得出:

所以 S 等于 σ̂ 加上 σ̂ /( n -1),我们加上它,因为平均起来它等于抽样分布的方差。当我们将 σ̂ 的分母从 n 改为 n -1 时,我们将 σ̂ /( n -1)加到它上面来修正 σ̂ 的估计,得到一个无偏的估计量。
清单 30 从正态分布中创建了 100,000 个大小为 5 的样本;计算每个样本的 σ̂ 和 S ,并绘制直方图(图 44)。请注意,在numpy中,我们可以使用带参数ddof=1的函数var()来计算样本方差。ddof=1的加入将 σ̂ 的分母变为 n -1。该图还显示了 σ̂ 和 S 的平均值与分布方差的关系。正如你看到的大量样本,e[σ̂≈σ+var(x̅)和e[s≈σ。
*# Listing 30
np.random.seed(0)
mu = 2
sigma2 = 1
n = 5
n2 = 50
num_samples = 100000
samples = norm.rvs(loc = mu,
scale = np.sqrt(sigma2),
size=n*num_samples).reshape(num_samples, n)
samples_mean = samples.mean(axis = 1)
sigma_hat2 = samples.var(axis = 1)
E_sigma_hat2 = sigma_hat2.mean()
sample_variance = samples.var(axis = 1, ddof=1)
E_sample_variance = sample_variance.mean()
fig, axs = plt.subplots(1, 2, figsize=(14, 5))
axs[0].hist(sigma_hat2, density=True, bins = 50)
axs[0].axvline(x=E_sigma_hat2, color='orange',
label=r"$E[\hat{\sigma}^2]$")
axs[0].axvline(x=E_sigma_hat2 + sigma2/n,
linewidth=3, color='aqua',
label=r"$E[\hat{\sigma}^2]+\sigma^2/n$")
axs[0].axvline(x=sigma2, linestyle='dashed',
linewidth=2 , color='black', label=r"$\sigma^2$")
axs[0].set_ylim([0, 1.2])
axs[0].set_xlim([0, 5])
axs[0].set_xlabel(r"$\hat{\sigma}^2$", fontsize=16)
axs[0].set_ylabel("Probability density", fontsize=14)
axs[0].legend(loc='best', fontsize=12)
axs[1].hist(sample_variance, density=True, bins = 50)
axs[1].axvline(x=E_sample_variance, linewidth=3,
color='orange', label=r"$E[S^2]$")
axs[1].axvline(x=sigma2, linestyle='dashed',
color='black', label=r"$\sigma^2$")
axs[1].set_ylim([0, 1.2])
axs[1].set_xlim([0, 5])
axs[1].set_xlabel(r"$S^2$", fontsize=16)
axs[1].legend(loc='best', fontsize = 12)
plt.show()*

图 44
随着 n 的增加, n 和 n -1 之间的差值变得可以忽略不计,因此 σ̂ 越来越接近 S 。这是因为抽样分布的方差( σ / n )随着 n 的减小而减小,并且 X̅ 越来越接近 μ 。
对于从正态分布中抽取的随机样本,可以看出样本均值( X̅ )和样本方差( S )是独立的。这是正态分布的一个独特特征,如果样本取自任何其他分布, X̅ 和 S 将是相关的。这是因为只有在正态分布中,我们才能独立地确定分布的均值( μ )和方差( σ )。我们知道这一点
**
如果参数 μ 和 σ 相互依赖,那么ex̅和es相互依赖,那么 X̅ 和 S 也将相互依赖。
现在让我们看看卡方分布如何与样本方差相关。如果 Z ₁、 Z ₂、…、 Zₙ 是 n 独立的、标准正态随机变量(或来自标准正态分布的随机样本),并且 Z̅ 是样本均值,那么可以证明

具有自由度为 n -1 的卡方分布。我们不打算正式证明这一点,但我们可以直观地表明为什么它是正确的。使用等式 55,我们得到

我们知道:

由于 nZ̅ 是 n 个标准正态随机变量的线性组合,那么它具有正态分布(方程 49)。根据等式 10 和等式 11,我们可以计算其均值和方差:
**
因此 nZ̅ 是标准正态随机变量的平方。我们还表明,对于正态分布,样本均值和样本分布是独立的,所以

是独立的,如果我们把它们乘以一个常数,它们将保持独立。因此

也是独立的。现在,如果我们看看方程 60,在左边我们有 n 个独立标准正态随机变量的平方和。在右边,我们有两个独立项的和,一个是标准正态随机变量的平方。所以,我们得出结论,另一个是

应该等于 n 个 -1 个独立随机变量之和。因此,它的卡方分布具有 n -1 个自由度。
现在假设 X ₁、 X ₂、…、 Xₙ 是来自正态分布的随机样本,均值为 μ ,方差为 σ 。使用等式 50,我们可以写出:

如果我们对样本中的所有变量取等式两边的平均值,我们得到:

接下来是:

所以,我们有:

并且我们得出结论,对于来自具有均值 μ 和方差 σ 的正态分布的随机样本

具有带 n -1 自由度的卡方分布。同样,我们可以写:

因此, nσ̂ / σ 也具有带有 n -1 个自由度的卡方分布。清单 31 绘制了图 45 中大小为 5 ( n =5)的 10000 个样本的 nσ̂ / σ 和(n-1)s/σ的直方图,您可以看到它们都遵循卡方分布,其中 m = n -1=4
*# Listing 31
np.random.seed(0)
mu = 2
sigma2 = 1
n = 5
num_samples = 100000
samples = norm.rvs(loc = mu,
scale = np.sqrt(sigma2),
size=n*num_samples).reshape(num_samples, n)
sigma_hat2 = samples.var(axis = 1)
sample_variance = samples.var(axis = 1, ddof=1)
c1 = n * sigma_hat2 / sigma2
c2 = (n-1) * sample_variance / sigma2
x = np.arange(0, 17, 0.01)
chi_dist = chi2.pdf(x=x, df=n-1)
fig, axs = plt.subplots(1, 2, figsize=(14, 5))
axs[0].hist(c1, density=True, bins = 70,
edgecolor='black', linewidth=1)
axs[0].plot(x, chi_dist,
label='Chi-square m={}'.format(n-1),
color='red')
axs[0].set_xlim([0, 17])
axs[0].set_xlabel(r"$\sigma_n^2$", fontsize=16)
axs[0].set_ylabel("Probability density", fontsize=14)
axs[0].legend(loc='best', fontsize=12)
axs[1].hist(c2, density=True, bins = 70,
edgecolor='black', linewidth=1)
axs[1].plot(x, chi_dist,
label='Chi-square m={}'.format(n-1),
color='red')
axs[1].set_xlim([0, 17])
axs[1].set_xlabel(r"$S_n^2$", fontsize=16)
axs[1].legend(loc='best', fontsize = 12)
plt.show()*

图 45
最后,让我们看看卡方分布的几何解释。假设你有一个 m 维向量,这个向量的每个分量都是一个标准正态分布的随机变量。那么这个随机向量的长度的平方可以被认为是一个具有卡方分布的随机变量。例如,对于二维向量:

长度的平方是:

这可以被认为是具有卡方分布的随机变量,其中 m =2。
清单 32 从一个标准的正态分布中创建了 100,000 个大小为 2 的样本,以形成随机向量 V ,并在图 46(左)中绘制了它的尖端。所以,图中的每个橙色点都是这个随机向量的一个可能值。对于每个样本,它还绘制了随机向量的尖端:

图 46(左)中的每个蓝色圆圈是随机向量 P 的一个可能值。如你所见, P 的所有值都位于一条直线上,这意味着与不同,它只有一个自由度。其原因是**

因此,它具有 1 个自由度的卡方分布。| P |的直方图如图 46(右)所示,它与卡方分布的 PDF 匹配,其中 m =1。
**# Listing 32
np.random.seed(0)
mu = 0
sigma2 = 1
n = 2
x = np.arange(0, 7, 0.01)
chi2_dist = chi2.pdf(x=x, df=2-1)
num_samples = 100000
z_i = norm.rvs(loc = mu,
scale = np.sqrt(sigma2),
size=n*num_samples).reshape(num_samples, n)
zbar = z_i.mean(axis = 1)
points = (z_i[:, 0] - zbar)**2 + (z_i[:, 1] - zbar)**2
fig, axs = plt.subplots(1, 2, figsize=(14, 5))
plt.subplots_adjust(wspace=0.1)
axs[0].set_aspect('equal')
axs[0].scatter(z_i[:, 0], z_i[:, 1], alpha=0.2, color='orange')
axs[0].scatter(z_i[:, 0] - zbar, z_i[:, 1] - zbar,
color='#3776ab', label=r"$[Z_1-\bar{Z}, Z_2-\bar{Z}]$")
axs[0].set_xlim([-5, 5])
axs[0].set_ylim([-5, 5])
axs[0].grid()
axs[0].set_xlabel(r"$Z_1$", fontsize=14)
axs[0].set_ylabel(r"$Z_2$", fontsize=14)
axs[0].legend(loc='upper right', fontsize=12)
axs[1].set_xlabel(r"$Z_1-\bar{Z}$", fontsize=14)
axs[1].hist(points, density=True, bins = 150, edgecolor='black',
linewidth=1, color='#3776ab')
axs[1].plot(x, chi2_dist, color = 'red', label="Ch-square dist (m=1)")
axs[1].set_xlim([0, 5])
axs[1].set_ylim([0, 1.3])
axs[1].legend(loc='best', fontsize=12)
axs[1].set_xlabel(r"$(Z_1-\bar{Z})^2+(Z_2-\bar{Z})^2$", fontsize=14)
axs[1].set_ylabel("Probability density", fontsize=14)
plt.show()**

图 46
更一般地说,如果 P 是一个n-维向量,那么 P 的一个分量是另一个分量的函数,所以它位于一个( n - 1 )维超平面上。
学生的 t 分布
设 X ₁、 X ₂、…、 Xₙ 为均值 μ 方差 σ 的正态分布随机样本。我们用 X̅ 表示样本均值(等式 51)。正如我们之前提到的,n 个独立且正态分布的随机变量的任何线性组合都具有正态分布。因此 X̅ 具有正态分布,平均值 μ 和方差 σ / n (等式 49)并基于等式 50

具有标准的正态分布。
现在假设我们不知道原始分布的方差,但我们仍然想推断它。我们可以做的一件事是用随机样本的方差来代替。为此,我们使用无偏样本方差:

现在,如果我们用等式 61 中的 S 替换 σ ,那么我们可以认为

仍然是标准的正态分布。清单 33 从正态分布中抽取了大量大小为 5 的样本,计算了等式 61 和等式 62 中的项,并绘制了它们的直方图与标准正态分布的 PDF 的关系。结果如图 47 所示。
**# Listing 33
np.random.seed(1)
n = 5
mu = 2
num_samples = 100000
x = norm.rvs(loc=mu,
scale=1,
size=n*num_samples).reshape(num_samples, n)
xbar = x.mean(axis=1)
s = x.std(axis=1, ddof=1)
zs = (xbar - mu) / (1 / n ** 0.5)
zs1 = (xbar - mu) / (s / n ** 0.5)
x = np.arange(-5, 5, 0.01)
y = norm.pdf(x, loc=0, scale=1)
fig, axs = plt.subplots(1, 2, figsize=(17, 5))
plt.subplots_adjust(wspace=0.15)
axs[0].hist(zs, bins= 40, density=True, edgecolor='black',
linewidth=1, color='lightblue')
axs[0].plot(x, y, color = 'red',
label = "Standard normal\ndistribution")
axs[0].legend(loc= 'best', fontsize = 12)
axs[0].set_xlim([-6 ,6])
axs[0].set_xlabel(r"$\frac{\bar{x}-\mu}{\sigma / \sqrt{n}}}$",
fontsize=20)
axs[0].set_ylabel("Probability density", fontsize=14)
axs[1].hist(zs1, bins= 400, density=True, edgecolor='black',
linewidth=1, color='lightblue')
axs[1].plot(x, y, color = 'red',
label = "Standard normal\ndistribution")
axs[1].legend(loc= 'best', fontsize = 12)
axs[1].set_xlim([-6 ,6])
axs[1].set_xlabel(r"$\frac{\bar{x}-\mu}{S / \sqrt{n}}}$",
fontsize=20)
plt.show()**

图 47
如你所见,等式 62 的直方图与标准正态分布的 PDF 不匹配。事实上,它的钟形直方图具有较重的尾部。首先,让我们看看为什么它有更重的尾巴。
较重的尾部意味着与标准正态分布相比,等式 62 中的项具有较高的方差。清单 34 比较了标准正态分布的 PDF 和方差更高的正态分布的 PDF,例如 σ =2.25。结果如左图 48 所示。正如你看到的,方差越大,尾部越重。但是为什么我们从(x̅—μ)/s/√n得到的方差比从(x̅—μ)/σ/√n得到的方差高呢?
要了解原因,请注意在正态分布中,异常值(出现几率较低的 x 的极值)对方差的影响很大。事实上,如果我们忽略这些异常值,方差会显著下降。当 n 小时,大小为 n 的样本不能很好地代表原始分布,因为原始分布的极值(异常值)在每个样本中出现的机会较小。因此,样本方差往往小于原始分布的方差。清单 34 绘制了图 48 中大小为 5 的 100,000 个样本的 S 的直方图。这些样本取自正态分布,其中 μ= 2, σ =1.5(方差为 2.25)。所以,你看 S 的直方图是不对称的,得到 S < σ 的几率高于得到 S > σ的几率。
对于每个样本(x̅—μ)/σ/√n用 σ =1.5 计算。但是要计算(x̅—μ)/s/√n,我们需要将 σ 替换为 S ,根据图 48 右图的直方图,有很大几率 S 取值小于 σ =1.5。因此,(x̅—μ)/√n除以 S 而不是 σ 增加了获得具有更大绝对值的数字的机会。这样一来,(x̅—μ)/s/√n相比(x̅—μ)/σ/√n会有更高的方差。
**# Listing 34
np.random.seed(1)
n = 5
mu = 2
sigma = 1.5
num_samples = 100000
x = norm.rvs(loc=mu, scale=sigma,
size=n*num_samples).reshape(num_samples, n)
xbar = x.mean(axis=1)
s = x.std(axis=1, ddof=1)
x = np.arange(-5, 5, 0.01)
y = norm.pdf(x, loc=0, scale=1)
y1 = norm.pdf(x, loc=0, scale=sigma)
fig, axs = plt.subplots(1, 2, figsize=(17, 5))
plt.subplots_adjust(wspace=0.15)
axs[0].plot(x, y, color = 'red',
label = "Standard normal\ndistribution")
axs[0].plot(x, y1, color = 'blue',
label="Normal distribution \n $\sigma^2={}$".format(sigma**2))
axs[0].legend(loc= 'best', fontsize = 12)
axs[0].set_xlim([-6 ,6])
axs[0].set_xlabel("$x$", fontsize=16)
axs[0].set_ylabel("Probability density", fontsize=14)
axs[1].hist(s, bins= 70, density=True, edgecolor='black',
linewidth=1, color='lightblue')
axs[1].text(1.6, 0.8, r"$\sigma$", color = 'red', fontsize = 16)
axs[1].set_xlim([-1 ,4])
axs[1].set_ylim([0 , 0.9])
axs[1].set_xlabel(r"$S$", fontsize=16)
axs[1].axvline(x=sigma, color='red')
plt.show()**

图 48
现在我们要为(x̅—μ)/s/√n求一个概率分布。首先,我们把它写成:

假设我们将随机变量 T 、 Z 和 D 定义为:

所以,我们有:

记住,对于从正态分布中抽取的随机样本,样本均值( X̅ )和样本方差( S )是独立的,这意味着 X̅ 和 S 也是独立的。此外,对于从同一正态分布抽取的相同大小的样本,参数 μ 、 σ 和√ n 的作用类似于常数。所以,如果我们用它们减去或除以 X̅和 s,它们将保持独立。因此在上式中,分子z=(x̅—μ)/σ/√n和分母d=s/σ是独立的。这是一个非常重要的结果,因为如果我们知道 Z 和 D 的概率分布,它允许我们容易地计算 T 。如果我们从 Z 抽取一个随机样本,它的值对从 D 抽取的随机样本的值没有影响,通过将它们相除,我们从 T 得到一个随机样本。
基于等式 50,我们知道 Z 具有标准正态分布。我们也看到了

具有带 n -1 自由度的卡方分布。所以,我们可以写:

其中,随机变量 V 具有带有 n -1 个自由度的卡方分布,其结果如下:

其中 Z 具有标准正态分布,而 V 具有带有 n -1 个自由度的卡方分布。 T 的分布称为具有 n -1 个自由度的学生的 t 分布。因此,我们的结论是

具有学生的 t 分布与 n -1 自由度。
更一般地,如果 Z 具有标准正态分布,并且 V 具有带有 n 自由度的卡方分布,那么我们可以用 n 代替 n -1,并且随机变量 T 被定义为

据说学生的 t 分配有 n 个自由度。我们可以把这个写成 T ~ tₙ 。学生的 t 分布通常缩写为 t 分布。可以看出,具有 n 自由度的 t 分布的 PDF 为:

在scipy中,对象t生成 t 分布。该对象的方法采用与等式 63 中的 t 和 n 相对应的参数x和df。清单 35 从正态分布中抽取了 100,000 个大小为 5 的样本,其中 μ=2 和 σ =1,并在图 49 中绘制了(x̅—μ)/s/√n和 T (具有 n -1 个自由度)的直方图。还绘制了具有df = n -1 和标准正态分布的 t 分布的 PDF。如您所见, t 分布的 PDF 与两个直方图都匹配。
**# Listing 35
from scipy.stats import t
np.random.seed(1)
n = 5
mu = 2
num_samples = 100000
x = norm.rvs(loc=mu, scale=1,
size=n*num_samples).reshape(num_samples, n)
xm = x.mean(axis=1)
s = x.std(axis=1, ddof=1)
ts = (xm - mu) / (s / n ** 0.5)
V = chi2.rvs(df=n-1, size=100000)
Z = norm.rvs(loc=0, scale=1, size=100000)
T = Z / np.sqrt(V / (n-1))
x = np.arange(-5, 5, 0.01)
norm_dist = norm.pdf(x, loc=0, scale=1)
t_dist = t.pdf(x, df=n-1, loc=0, scale=1)
fig, axs = plt.subplots(1, 2, figsize=(16, 5))
plt.subplots_adjust(wspace=0.15)
axs[0].hist(ts, bins= 400, density=True,
edgecolor='black', linewidth=1,
color='lightblue')
axs[0].plot(x, norm_dist, color = 'black',
label = 'Standard normal\ndistribution')
axs[0].plot(x, t_dist, color = 'red',
label = "t distribution\nwith df=n-1")
axs[0].legend(loc= 'best', fontsize = 12)
axs[0].set_xlim([-6 ,6])
axs[0].set_xlabel(r"$\frac{\bar{x}-\mu}{S/ \sqrt{n}}}$",
fontsize = 21)
axs[0].set_ylabel("Probability density", fontsize = 14)
axs[1].hist(T, bins= 300, density=True,
edgecolor='black', linewidth=1, color='lightgreen')
axs[1].plot(x, norm_dist, color = 'black',
label = 'Standard normal\ndistribution')
axs[1].plot(x, t_dist, color = 'red',
label = "t distribution\nwith df=n-1")
axs[1].legend(loc= 'best', fontsize = 12)
axs[1].set_xlim([-6 ,6])
axs[1].set_xlabel(r"$\frac{Z}{\sqrt{Y/(n-1)}}}$",
fontsize = 21)
plt.show()**

图 49
记住,随着 n 的增加, S 趋向于 σ̂ 。另外,随着 n 的增加,我们得到了更有代表性的原始分布样本,所以 σ̂ 趋向于 σ (原始分布的方差)。基于这些结果,我们可以说随着 n 的增加, S 接近 σ ,这意味着(x̅—μ)/s/√n接近(x̅—μ/σ/√)因此,我们得出结论,随着 n 趋于无穷大,具有 n 自由度的 t 分布接近标准正态分布。清单 36 绘制了不同的 n 值的 t 分布的 PDF。结果如图 50 所示,如您所见,对于 n ≥30,标准正态分布非常接近于 t 分布。
**# Listing 36
plt.figure(figsize = (12, 7))
x = np.arange(-5, 5, 0.01)
y = norm.pdf(x, loc=0, scale=1)
y1 = t.pdf(x, df= 1, loc=0, scale=1)
y2 = t.pdf(x, df= 3, loc=0, scale=1)
y3 = t.pdf(x, df= 12, loc=0, scale=1)
y4 = t.pdf(x, df= 30, loc=0, scale=1)
plt.plot(x, y, color = 'black', linestyle='dashed',
label = "Standard normal distribution")
plt.plot(x, y1, color = 'green',
label = "t distribution, df=1\n(Cauchy disitrbution)")
plt.plot(x, y2, color = 'blue', label = "t distribution, df=3")
plt.plot(x, y3, color = 'orange', label = "t distribution, df=10")
plt.plot(x, y4, color = 'red', label = "t distribution, df=30")
plt.legend(loc='upper left', fontsize=12)
plt.xlabel('t', fontsize=16)
plt.ylabel('$f_T(t)$', fontsize=16)
plt.xlim([-5, 5])
plt.show()**

图 50
如果 T 具有带 n 自由度的 t 分布,则可以证明:

对于 n 的其他值,平均值不存在。如果分布的平均值存在,由于 t 分布的对称性,它为零。分布的方差只为n2 定义:

具有一个自由度的 t 分布称为柯西分布。该分布的 PDF 可以使用公式 63 计算:

我们知道这一点

可以看出:

因此,柯西分布的 PDF 可以简化为:

该 PDF 也绘制在图 50 中。
具有柯西分布的随机变量 T 的平均值可以使用等式 28 计算:
****
因此,我们得出结论,柯西分布的均值不存在。
F 分配
假设 Y ₁和 Y ₂是两个独立的随机变量,使得 Y ₁具有 d ₁自由度的卡方分布, Y ₂具有d₂t22】自由度的卡方分布( d ₁和 d ₂为正整数)。我们将随机变量 X 定义为:

那么 X 据说具有自由度为 d ₁和 d ₂的 f 分布。可以看出 F 分布的 PDF 如下:

对于 x ≤0,我们得到 f ₓ( x )=0。请注意,在 F 分配中,参数 d ₁和 d 2 的顺序很重要。根据 PDF 的定义,当 d ₁≠ d ₂时,具有 d ₁和 d ₂自由度的f分布的 PDF 不等于具有 d ₂和 d ₁自由度的 F 分布的 pdf,所以它们被认为是两种不同的分布
对象f在scipy中生成 F 分布。该对象的方法采用参数x、dfn和dfd,这些参数对应于等式 65 中的 x 、 d ₁和 d ₂。清单 37 从两个具有 d ₁和 d ₂自由度的卡方分布中抽取了两个相同大小的样本,并在图 51 中绘制了公式 64 的直方图。如该图所示,具有 d ₁和 d ₂自由度的 F 分布的 PDF 与该直方图相匹配。
*# Listing 37
from scipy.stats import f
np.random.seed(0)
df1 = 5
df2 = 2
x_lim = 4
num_samples = 5000
samples_df1 = chi2.rvs(df=df1, size=num_samples)
samples_df2 = chi2.rvs(df=df2, size=num_samples)
F_rvs = (samples_df1 / df1) / (samples_df2 / df2)
x = np.arange(0, x_lim, 0.01)
F_dist = f.pdf(x=x, dfn=df1, dfd=df2)
plt.figure(figsize = (12, 7))
plt.hist(F_rvs, density=True, bins = 13000,
edgecolor='black', linewidth=1)
plt.plot(x, F_dist,
label='F distribution d1={}, d2={}'.format(df1, df2),
color='red')
plt.xlim([0, x_lim])
plt.xlabel(r"$\frac{Y_1/d_1}{Y_2/d_2}$", fontsize=21)
plt.ylabel("Probability density", fontsize=14)
plt.legend(loc='best', fontsize=12)
plt.show()*

图 51
清单 38 绘制了图 52 中不同自由度的 F 分布的 PDF。
*# Listing 38
np.random.seed(0)
dfs_list = [(1, 1), (2, 1), (7, 2), (30, 30), (250, 250)]
x = np.arange(0, 4, 0.01)
plt.figure(figsize = (9, 7))
for (df1, df2) in dfs_list:
F_dist = f.pdf(x=x, dfn=df1, dfd=df2)
plt.plot(x, F_dist,
label='F distribution d1={}, d2={}'.format(df1, df2))
plt.xlim([0, 4])
plt.ylim([0, 3.5])
plt.xlabel("x", fontsize=16)
plt.ylabel(r"$f_X(x)$", fontsize=16)
plt.legend(loc='best', fontsize=12)
plt.show()*

图 52
分布 F 的重要性源于这样一个事实,即两个随机变量的比率与卡方分布在统计中经常遇到。 F 分布可以用来比较两个正态分布的方差。假设 X ₁、 X ₂、… X_d1 是来自正态分布的大小为 d ₁的随机样本,均值为 μ ₁,方差为 σ ₁、 Y ₁、 Y ₂、… Y_d2 是大小为 d 的随机样本另外,假设两个分布的均值和方差都是未知的,并且这两个分布是相互独立的。这些样本的样本方差分别用 S ₁和 S ₂表示。还记得我们展示过,如果 S 是从方差为 σ 的正态分布中抽取的大小为 n 的样本的样本方差,那么

具有带 n -1 自由度的卡方分布。因此,根据等式 64,可以得出比率

具有 F 分布与d₁–1 和d₂–1 自由度。因此,如果我们有这些随机样本,并且我们想知道 σ ₁和 σ ₂相等的概率是多少,我们可以使用 F 分布。
让我们看一个例子。清单 39 从正态分布中生成大小为 15 的随机样本,其中 μ ₁=1 和 σ ₁ =5,从正态分布中生成大小为 10 的第二个随机样本,其中 μ ₂=0 和 σ ₂ =1。它还计算两个随机样本的样本方差(用sample_var1和sample_var2表示):
*# Listing 39
np.random.seed(5)
d1 = 15
d2 = 10
var1 = 5
var2 = 1
mean1 = 1
mean2 = 0
sample1 = norm.rvs(loc=mean1, scale=np.sqrt(var1), size=d1)
sample2 = norm.rvs(loc=mean2, scale=np.sqrt(var2), size=d2)
sample_var1 = np.var(sample1, ddof=1)
sample_var2 = np.var(sample2, ddof=1)
print("Sample 1:", sample1)
print("sample_var1: ", sample_var1)
print()
print("Sample 2:", sample2)
print("sample_var2: ", sample_var2)
# Output
Sample 1: [ 1.98661465 0.26015185 6.43536961 0.43630486
1.24509506 4.53853535 -1.03310546 -0.32293979
1.41949357 0.26238835 -1.66710275 0.5418822
0.19763408 2.34940353 -2.72258032]
sample_var1: 5.29540541147499
Sample 2: [-0.70017904 1.15139101 1.85733101 -1.51117956
0.64484751 -0.98060789 -0.85685315 -0.87187918
-0.42250793 0.99643983]
sample_var2: 1.2827153177349788*
现在假设我们只有这些随机样本,并且不知道它们对应的正态分布的均值和方差。我们可以做假设检验来比较 σ ₁和 σ ₂(假设他们是未知的)。我们先假设 σ ₁和 σ ₂相等,我们称之为零假设。给定零假设,我们可以使用等式 66 计算 F 的值:

我们还知道 F 具有 15–1 = 14 和 10–1 = 9 自由度的 F 分布(请注意样本方差较大的样本要有下标 1)。现在我们想看看这个分布的 F 的值有多稀有。在统计假设检验中,一个结果被称为具有统计显著性当它在假设无效的情况下不太可能发生时,所以它是一个罕见的事件。显著性水平(用 α 表示)作为识别这种罕见事件的阈值。
清单 40 绘制了图 53 中具有 14 和 9 个自由度的 F 分布的 PDF。分布的右尾显示异常值。这些是发生几率很小的 x 的较大值,所以我们可以认为是罕见事件。我们需要找到 x 的值(用 x_α 表示)

所以 x_α 定义了一个区间,这个区间包含了 X 的稀有值。 X 位于这个区间的概率是 0.05,这里 0.05 是显著性水平。这个区间上的 PDF 曲线下面积也是 0.05。请注意,我们假设样本方差较大的样本的下标为 1,因此 F 的稀有值位于分布的长右尾。
*# Listing 40
x = np.arange(0, 9, 0.01)
plt.figure(figsize = (9, 7))
F_dist = f.pdf(x=x, dfn=d1-1, dfd=d2-1)
x_alpha = f.ppf(q=0.95, dfn=d1-1, dfd=d2-1)
plt.plot(x, F_dist,
label='F distribution d1={}, d2={}'.format(d1-1, d2-1))
plt.fill_between(x, F_dist, 0,
where = (x >= lim) & (x <= np.inf), color = 'red')
plt.axvline(x= x_alpha, color='black')
plt.xlim([0, 9])
plt.ylim([0, 0.8])
plt.xlabel("x", fontsize=16)
plt.ylabel(r"$f_X(x)$", fontsize=16)
plt.legend(loc='best', fontsize=12)
plt.text(4.2, 0.08, "Rejection region, Area=0.05", fontsize=12)
plt.text(4.2, 0.03, "Level of significance=0.05", fontsize=12)
plt.text(x_alpha-0.4, 0.15, r"$x_{\alpha}$", fontsize=16)
plt.show()*

图 53
区间[ x_α ,∞]定义了零假设的拒绝区域。如果 F 的值在这个区间内,那么我们可以假设这是一个罕见的观察结果,在给定零假设的情况下不太可能发生。因此,我们可以拒绝零假设,接受另一个假设,即σ₁>t34】σ₂。清单 41 显示 F 的值位于这个区间内,因此我们可以拒绝零假设,并得出结论σ₁>t40】σ₂(图 54)。这与我们用 σ ₁ =5 和 σ ₂ =1 定义正态分布的事实是一致的。
我们还可以计算[ F ,∞]的曲线下面积,并将其与 α 进行比较,以检查 F 是否位于区间[ x_α ,∞]内。该区域称为 F 的 p 值,在图 54 中用橙色标出。在假设零假设正确的情况下, F 的 p 值是 F 将呈现与其观察值(从随机样本中计算出)一样极端或更极端的值的概率。如果p-值小于 α ,那么说明 F 位于区间[ x_α ,∞]内,可以拒绝零假设。
*# Listing 41
x = np.arange(0, 9, 0.01)
plt.figure(figsize = (9, 7))
F_dist = f.pdf(x=x, dfn=d1-1, dfd=d2-1)
lim = f.ppf(q=0.95, dfn=d1-1, dfd=d2-1)
plt.plot(x, F_dist,
label='F distribution d1={}, d2={}'.format(d1-1, d2-1))
plt.fill_between(x, F_dist, 0,
where = (x >= lim) & (x <= np.inf), color = 'red')
plt.fill_between(x, F_dist, 0,
where = (x >= F) & (x <= np.inf), color = 'orange')
plt.axvline(x= F, color='black')
plt.xlim([0, 9])
plt.ylim([0, 0.8])
plt.xlabel("x", fontsize=16)
plt.ylabel(r"$f_X(x)$", fontsize=16)
plt.legend(loc='best', fontsize=12)
p_value = 1-f.cdf(x=F, dfn=d1-1, dfd=d2-1)
plt.text(F+0.3, 0.04, "p-value={}".format(np.round(p_value, 3)),
fontsize=12)
plt.text(F-0.23, 0.1, "F", fontsize=14)
plt.show()*

图 54
之所以 F 取这样一个极值,是因为我们做了一个错误的假设 σ ₁ = σ ₂。例如,如果我们假设 σ ₁ =3 σ ₂,那么

因此,它不会取这样一个极值。
清单 42 显示了当我们增加自由度时会发生什么。结果如图 55 所示。随着自由度的增加,样本的大小也增加,每个样本变得更能代表其原始分布,因此样本方差越来越接近其原始分布的方差。因此可以得出以下结论:

因此我们得出结论,随着自由度趋于无穷大, F 分布的 PDF 接近狄拉克δ函数,除了在 x =1 处,该函数在任何地方都为零。
*# Listing 42
np.random.seed(0)
dfs_list = [(5, 5), (50, 50), (5000, 5000), (50000, 50000)]
x = np.arange(0, 4, 0.01)
plt.figure(figsize = (11, 7))
for (df1, df2) in dfs_list:
F_dist = f.pdf(x=x, dfn=df1, dfd=df2)
plt.plot(x, F_dist,
label='F distribution d1={}, d2={}'.format(df1, df2))
plt.xlim([0, 2])
plt.xlabel("x", fontsize=16)
plt.ylabel(r"$f_X(x)$", fontsize=16)
plt.legend(loc='best', fontsize=12)
plt.show()*

图 55
概率分布是统计学中的一个重要概念,它有许多应用。它们可以帮助我们对现实生活中发生的随机事件进行建模。在统计学中,它们用于置信区间计算和假设检验。此外,它们还是许多用于预测的机器学习模型的构建模块。这些模型可以有从天气预报到股市预测的广泛应用,因此概率分布是每个数据科学家和机器学习工程师的必备知识。在本文中,我们介绍了一些最重要的概率分布,并展示了如何在 Python 中生成它们。
我希望你喜欢阅读这篇文章。如果您有任何问题或建议,请告诉我。本文中的所有代码清单都可以从 GitHub 下载,网址是:https://github.com/reza-bagheri/probability_distributions
绝对初学者理解 Python 上下文管理器
理解关于光剑的 WITH 语句

我们将编写自己的光剑(图片由 Venti Views 在 Unsplash 上提供)
您肯定熟悉上下文管理器——他们使用with语句:
with open('somefile.text', 'r') as file:
data = file.read()
在本文中,我们将关注调用上下文管理器时会发生什么:
- 什么是上下文管理器?
- 它是如何工作的?
- 它的优点是什么?
- 创建您自己的上下文管理器
我们来编码吧!
在我们使用上下文管理器进行一些真实的代码示例之前,我们将通过一个清晰的示例来理解其内部工作原理:想象我们正在编写一把光剑。
设置:编写光剑代码
我敢肯定每个人都很熟悉这些:超级危险的,赤热的等离子刀片,它可以切开它碰到的一切。在我们用它们刺或砍之前,我们需要激活它,在我们用完后,我们应该总是关闭这个危险的设备。在代码中:
没什么特别的:我们跟踪两个属性:color和active。在这个例子中color是非常装饰性的,active是非常重要的,因为如果我们激活了军刀,我们只能使用slash()和stab()方法。我们有两个控制状态的函数:turn_on()和turn_off()。
您使用这段代码的方式如下:
这段代码的维护成本相当高;我们不能忘记打开我们的光剑,否则它会抛出一个异常。而且我们每次都需要调用turn_off()方法。我们怎样才能让这段代码更容易使用呢?
何时使用上下文管理器?
以我们的光剑为例,我们总是需要打开光剑,然后才能用它做任何事情。另外,我们 总是 需要关掉它,因为否则我们就不能用它,而且现在能源价格相当高。
记住,为了打开和关闭 saber,我们需要调用turn_on()和turn_off()方法。对于上下文管理器来说,光剑是一个理想的用例,因为我们需要在每次使用它之前和/或之后执行一个动作。
用上下文管理器更新光剑
我们将在我们的光剑类中增加两个特殊的“dunder-methods”。Dunder-methods(双下划线-methods)覆盖定制类的内置函数的功能。你可能已经知道一种方法:T2 方法!
我们将添加一个__enter__和__exit__方法,所以我们的光剑类现在看起来像这样:
正如你将看到的,打开和关闭 saber 的函数在这些 enter 和 exit 方法中被调用。我们现在可以这样使用这个类:
一旦我们点击with语句,我们就进入__enter__方法,这将打开我们的光剑。然后我们就可以尽情挥刀捅人了。一旦我们退出代码块,就会调用__exit__方法关闭我们的光剑。
优势显而易见:
- 我们的代码更加简洁。
- 我们的代码更容易阅读。
- 当我们想开始砍和刺的时候它会自动开启。
- 当我们完成谋杀时,它会自动关闭,防止许多人试图将它藏起来而受伤。
野外的上下文管理器
光剑有点像玩具,但是在很多情况下会用到上下文管理器。下面的例子详细说明了 Python 读取文件的内置函数。默认方式如下所示:
file = open('somefile.txt')
content = file.read()
file.close()
如果我们在上面的例子中不调用file.close(),那么我们的文件将被我们的脚本占用。这意味着没有其他进程可以访问它。让我们使用上下文管理器:
with open('somefile.txt') as file:
content = file.read()
上面的代码会自动关闭文件。它也更容易阅读。
另一个例子是数据库连接。在我们创建了到数据库的连接并使用它来读取/写入数据到数据库之后,我们不能忘记提交数据并关闭连接。常规实施:
cursor = conn.cursor()
cursor.execute("SELECT * FROM sometable")
cursor.close()
幸运的是,许多包都实现了这样的上下文管理器:
with dbconnection.connect() as con:
result = con.execute("SELECT * FROM sometable")
上面的代码确保连接在我们完成后立即关闭。目前,我正在创建一个将 Python 连接到数据库的指南;有兴趣就跟着我吧!
结论
在本文中,我们已经介绍了 Python 中日志记录的基础知识,我希望向您展示日志记录相对于打印的所有优势。下一步是添加文件处理程序,这样我们就可以将日志保存到文件中,通过电子邮件或 API 发送。请查看这篇文章。
如果你有任何改进这篇文章的建议;请评论!与此同时,请查看我的其他关于各种编程相关主题的文章,比如:
- 面向绝对初学者的 cyt hon——两步代码速度提高 30 倍
- Python 为什么这么慢,如何加速
- 绝对初学者的 Git:借助视频游戏理解 Git
- 在一行代码中显著提高数据库插入速度
- Docker:图像和容器的区别
- Docker 对于绝对初学者——什么是 Docker 以及如何使用它(+示例)
- 绝对初学者的虚拟环境——什么是虚拟环境,如何创建虚拟环境(+示例)
- 创建并发布你自己的 Python 包
- 6 个步骤让熊猫数据帧运算速度提高 100 倍
编码快乐!
—迈克
附注:喜欢我正在做的事吗? 跟我来!
https://mikehuls.medium.com/membership
了解 Python 集合
原文:https://towardsdatascience.com/understanding-python-sets-9df4dc71e1ca
Python 中一个未被充分利用的类,因为列表不能解决所有问题

由 Unsplash 上的 CHUTTERSNAP 拍摄
Python 集合是大多数人在早期学习 Python 时学到的东西,但有时会忘记在某些地方它可能比列表更有用。列表得到了所有的关注,可能没有在适当的上下文中使用,但在这篇文章中,我们将强调什么是集合,用集合论分析数据集的方法,以及这将如何应用于数据分析。
什么是集合?
在 Python 上下文中,集合是一种容器类型,包含唯一且不可变的元素。它的存储也没有任何特定的顺序。知道集合和列表的区别的关键是提到的前两个属性,和 不可变 。任何集合都不能包含具有相同值且类似于元组的多个元素。一旦创建了集合,就不能在其中修改项目。使用集合数据类型时的另一个关键方面是,与列表或数组不同,它们是无序的,或者每个元素不与集合中的唯一索引或位置相关联。了解这一点很重要,因为当创建集合时,每个项目的顺序永远不会成为集合的特征。set 数据类型的另一个主要特性是集合的参数可以是可迭代的,这意味着在创建集合时可以给它一个列表或一个项目数组。最后,与其中的对象必须是相同类型的一些数据类型不同,集合可以包含不同类型的项,如字符串和数字类型。
集合操作(或者你能???)
请记住,当我们之前提到集合是不可变的时,这使得可以在集合上完成的操作和操纵的类型与列表之类的东西相比非常有限。可以在集合上完成的一些非常有用的事情依赖于集合论和可以应用的各种逻辑运算。这将包括应用不同集合之间的联合、交集和差异。

Tatiana Rodriguez 在 Unsplash 上拍摄的照片
联合
*#Animals that eat meat
meateaters = ('dogs', 'humans', 'lions', 'tigers', 'monkeys')#Animals that eat plants
planteaters = ('humans', 'sheep', 'cows', 'monkeys', 'birds')#Notices that we can use the | or the x1.union(x2) logic to apply union
eaters = meateaters|planteaters
eaters = ('dogs', 'humans', 'lions', 'tigers', 'monkeys''sheep', 'cows','birds')#OR
eaters2 = meateaters.union(planteaters)
eaters2 = ('dogs', 'humans', 'lions', 'tigers', 'monkeys''sheep', 'cows','birds')#Also note that although humans and monkeys were mentioned multiple times it only stores a single instance of each in the final union*
在上面的联合示例中需要注意的一点是,对集合执行联合操作有两种不同的方式。两者之间的一个关键区别是|操作符要求两个项目都是集合类型。x.union 方法可以应用于任何 iterable 参数,然后在应用 union 操作之前将其转换为集合。当使用可能在列表或其他数据类型中的项目时,这提供了灵活性,并且仍然在对象之间应用联合逻辑。

西尔维·沙伦在 Unsplash 上拍摄的照片
差异
与联合类似,差异运算将查看两个不同的集合,并找出集合之间的差异。在这个过程中,它将创建一组新的唯一项目。以上面肉食动物和食草动物之间的例子为例,差异运算将把人类和猴子排除在外,因为它们同时存在于两个集合中。
*#Animals that eat meat
meateaters = ('dogs', 'humans', 'lions', 'tigers', 'monkeys')#Animals that eat plants
planteaters = ('humans', 'sheep', 'cows', 'monkeys', 'birds')#Animals that are not omnivores
nonomnivores = meateaters.difference(planteaters)
nonomnivores = ('dogs', 'lions', 'tigers', 'sheep', 'cows', 'birds')#The (-) minus operator can also be used in the same way
nonomnivores2 = meateaters - planteaters
nonomnivores2 = ('dogs', 'lions', 'tigers', 'sheep', 'cows', 'birds')#Also more than two sets can be applied with similar logic
x = difference(y, z)
#Or
x - y - z*

十字路口
可以对集合进行的最后一个常见操作是应用交集逻辑。这将查看两个不同的集合,然后识别交集,或者作为输出存在于两个集合中的事物。下面的代码是一个将交集运算应用到食草动物和食肉动物的例子中的例子。
*#Animals that eat meat
meateaters = ('dogs', 'humans', 'lions', 'tigers', 'monkeys')#Animals that eat plants
planteaters = ('humans', 'sheep', 'cows', 'monkeys', 'birds')#Animals that eat both
omnivores = meateaters.intersection(planteaters)
omnivores = ('humans', 'monkeys')#Alternative way to execute is using the & character
omnivores2 = meateaters & planteaters
omnivores2 = ('humans', 'monkeys')*

照片由Firmbee.com在 Unsplash 上拍摄
为什么这很重要?
关于编程的终极问题是,那又怎样?通过了解器械包,可以分析物品的属性以及它们与其他物品的关系。一个很好的例子是查看对象的属性,这些对象可能被保存为一个集合,甚至是一个列表。应用上面的三个操作将允许一个简单的方法来评估是什么使不同的项目在几个上下文中的属性相似或不同。
通过示例进行讨论就像是对通过链接连接的数据页面构建一个页面等级类型的分析。如果项目与各种属性相关联,应用集合论中的逻辑(或利用属性集),可以很容易地应用并集、差集和交集运算来更深入地了解数据集。理想情况下,您可以比较前 N 个页面,然后使用它们属性之间的联合进行识别,以了解它们之间的共同点。通过查看顶部 N 和底部 N,然后理解它们之间的差异和/或交集,可以应用相同的逻辑。这无疑有助于数据处理步骤,并为那些对数据科学模型非常重要的功能提供支持。
另一个例子是在数据科学中应用相似性模型,最常见的相似性方法是余弦相似性,但通过利用集合论,另一个强大的相似性度量是 Jaccard 相似性。它通常用于在文本挖掘和推荐服务中应用相似性模型。这主要是因为 Jaccard 相似性可以用集合符号写成:
J(A,B) = |A∩B| / |A∪ B|
*import numpy as np#Introduce two vectors (which could represent a variety of parameters)
a = [0, 1, 2, 5, 6, 8, 9]
b = [0, 2, 3, 4, 5, 7, 9]#Define Jaccard Similarity functiondef jaccard(list1, list2):
intersection = len(list(set(list1).intersection(list2)))
union = (len(list1) + len(list2)) - intersection
return float(intersection) / union
#Find Jaccard Similarity between the two sets
jaccard(a, b)#Result
0.4*
玩弄集合论思想和利用集合数据将有助于从不同角度处理问题,并对数据分析超级强大的数学子集给予更广泛的欣赏。
理解随机变量和概率分布
基本原则
理解随机变量和概率分布
机器学习中为什么要关心概率质量和密度函数?

概率理论是研究随机现象的一个数学分支,通常被认为是机器学习的基本支柱之一。然而,这是一个很大的领域,很容易迷失,尤其是在自学的时候。
在接下来的章节中,我们将涉及一些与机器学习特别相关的基本方面——随机变量和概率分布。
但是在一头扎进概率论的深度之前,让我们试着回答为什么理解这些概念是重要的,为什么我们首先应该关心这个问题。
为什么是概率?
在机器学习中,我们经常处理不确定性和随机量,原因之一是不完全可观测性——因此,我们最有可能使用采样数据。
现在,假设我们想对随机变量的行为得出可靠的结论,尽管我们只有有限的数据,而且我们根本不知道整个人口。
因此,我们需要某种方法从抽样数据中归纳出总体数据,或者换句话说,我们需要估计真实的数据生成过程。

估计数据生成过程[图片由作者提供]
理解了概率分布,我们就可以通过计算结果的可变性来计算某个结果的概率。因此,它使我们能够从样本推广到总体,估计数据生成函数,并更准确地预测随机变量的行为。
引入随机变量
不严格地说,随机变量的值取决于随机事件的结果。我们也可以将其描述为从样本空间映射到可测空间(例如实数)的函数。
让我们假设,我们有一个包含 4 名学生{A, B, C, D}的样本空间。如果我们现在随机选取student A并以厘米为单位测量高度,我们可以把random variable (H)想象成以student为输入、height为输出的实数的函数。

我们可以想象这个小例子如下:

一个随机变量的例子[图片由作者提供]
根据结果——随机选择哪个学生——我们的随机变量(H)可以呈现不同的状态或以厘米为单位的不同身高值。
随机变量可以是离散的,也可以是连续的。
如果我们的随机变量只能取有限个或可数无限个不同的值,那么它就是离散的。离散随机变量的例子包括一个班级的学生人数、答对的试题、一个家庭的孩子数量等。
然而,我们的随机变量是连续的,如果在我们的变量的任意两个值之间有无限多的其他有效值。我们可以把压力、高度、质量和距离等量看作是连续随机变量的例子。
当我们把随机变量和概率分布联系起来时,我们可以回答下面的问题:随机变量有多大可能处于特定状态?这基本上和问概率是一样的。
现在,我们剩下一个问题——什么是概率分布?
概率分布
随机变量采取其可能状态的可能性的描述可以由概率分布给出。因此,概率分布是一个数学函数,它给出了实验不同结果的概率。
更一般地,它可以被描述为函数

其将输入空间 A(与样本空间相关)映射到实数,即概率。
对于上述描述概率分布的函数,它必须遵循所有的 Kolmogorov 公理:
- 非否定性
- 没有概率超过 1
- 任何可数不相交(互斥)事件的可加性
我们描述概率分布的方式取决于随机变量是离散的还是连续的,这将分别产生概率质量或密度函数。
概率质量函数
概率质量函数(PMF)描述了离散随机变量的概率分布。换句话说,它是一个函数,返回一个随机变量恰好等于一个特定值的概率。
返回的概率在[0,1]范围内,每个状态的所有概率之和等于 1。
让我们想象一个图,其中 x 轴描述状态,y 轴显示某个状态的概率。以这种方式思考可以让我们把概率或 PMF 想象成一个位于状态顶部的柱状图。

一个例子(制服)PMF[图片由作者提供]
下面,我们将学习三种常见的离散概率分布:伯努利分布、二项式分布和几何分布。
二项分布
伯努利分布以瑞士数学家雅各布·伯努利的名字命名,它是一个二元随机变量的离散概率分布,取值为 1 或 0。
不严格地说,我们可以将伯努利分布视为一个模型,它给出了一个实验的一组可能结果,可以用一个简单的是-否问题来回答。
更正式地,该函数可以表述为以下等式


其基本上评估为p if k=1或(1-p) if k=0。因此,伯努利分布仅由一个single parameter p参数化。
假设,我们扔一次公平的硬币。获得人头的概率是P(Heads) = 0.5。将 PMF 形象化,我们得到如下的图:

伯努利试验的一个例子[图片由作者提供]
注意:伯努利分布要么取值 1,要么取值 0,这使得它作为一个指标或虚拟变量特别有用。
由于伯努利分布模型只有一次试验,它也可以被看作是二项分布的特殊情况。
二项分布
二项式分布描述了一系列 n 个独立试验中成功次数的离散概率分布,每个试验都有一个二元结果。成功或失败分别由概率 p 或(1-p)**给出。
因此,二项式分布由参数来参数化

更正式地,二项式分布可以用下面的等式表示:

k 的成功由概率 p 的 k 次幂给出,而失败的概率由 (1-p) 的 n 次幂减去 k 给出,这基本上是试验次数减去我们得到 k 的一次试验。
由于成功的事件 k 可以发生在 n 试验中的任何地方,我们有【n 选 k】种方式来分配成功。
让我们拿起之前的抛硬币的例子,并在此基础上进行构建。
现在,我们将抛三次公平硬币,同时对描述获得的人头数的随机变量感兴趣。

三次抛硬币的正面数量[图片由作者提供]
如果我们想计算硬币两次正面朝上的概率,我们可以简单地使用之前的等式,然后取值

这导致了一个概率P(2) = 0.375。如果我们以同样的方式处理剩余的概率,我们得到下面的分布:

三次抛硬币的二项分布[图片由作者提供]
几何分布
假设,我们感兴趣的是,在硬币第一次正面朝上之前,我们要掷多少次。
几何分布给出了第一次成功发生的概率,要求 n 次独立试验,成功概率为 p 。
更正式的说法是

其计算直到并包括成功事件所需的试验次数的概率。
为了计算几何分布,以下假设需要为真:
- 独立性ˌ自立性
- 对于每个试验,只有两种可能的结果
- 每次试验成功的可能性都是一样的
让我们通过回答硬币第一次正面朝上所需的尝试次数的概率来形象化几何分布。

第一次人头落地前的几何分布[图片由作者提供]
概率密度函数
在前面的章节中,我们了解到随机变量可以是离散的,也可以是连续的。如果是离散的,我们可以用一个概率质量函数来描述概率分布。
现在,我们正在处理连续变量——因此,我们需要用概率密度函数(PDF)来描述概率分布。
与 PMF 相反,概率密度函数不能直接给出随机变量进入特定状态的概率。相反,它描述了在一个无限小的区域内着陆的概率。换句话说,PDF 描述了一个随机变量位于一个特定范围值之间的概率。
为了找到实际的概率质量,我们需要积分,这产生了在密度函数之下但在 x 轴之上的面积。

概率密度函数示例[图片由作者提供]
概率密度函数必须是非负的,并且其积分必须为 1。

最常见的连续概率分布之一是高斯分布或正态分布。
高斯分布
高斯分布通常被认为是代表实值随机变量的明智选择,其分布是未知的。
这主要得益于中心极限定理,该定理宽松地说,陈述了许多具有有限均值和方差的独立随机变量的平均值本身就是一个随机变量——随着观察次数的增加而呈正态分布。
这尤其有用,因为它允许我们将复杂系统建模为高斯分布,即使单个部分遵循更复杂的结构或分布。
它是对连续变量的分布建模的常见选择的另一个原因是,它插入了最少的先验知识。
更正式地说,高斯分布可以表述为

其中参数是平均值,描述了方差。
简单来说,平均值将决定钟形分布的中心峰值,而方差或标准差决定其宽度。
我们可以将正态分布形象化为如下形式:

正态分布的一个例子[图片由作者提供]
结论
在本文中,我们讨论了随机变量、概率分布、它们之间的关系以及我们如何解释它们。我们还通过引入一些最常见的概率质量和密度函数来区分离散和连续随机变量。
尽管在不了解概率分布的基础知识的情况下应用学习算法仍有可能获得不错的结果,但对该主题的深入理解将使我们能够对随机变量的真实行为做出更好的选择、假设和预测。
喜欢这篇文章吗?成为 中等会员 继续无限学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
*https://medium.com/@marvinlanhenke/membership
参考资料/更多资料:
- 深度学习(Ian J. Goodfellow,Yoshua Bengio 和 Aaron 库维尔),第三章,麻省理工学院出版社,2016 年。
- 麻省理工学院决议 6–012 概率介绍,春季*
用通俗的语言理解正则化:L1 和 L2 正则化

迈克尔·佩恩在 Unsplash 上的照片
在数据科学访谈中经常被问到
正规化一词的含义是“改变一种状况或制度,使其符合法律或规则的行为”。在机器学习领域也是如此。
正则化是一种约束或正则化权重的方法。为什么我们需要约束权重?
机器学习的一个主要问题是过拟合。我们使用正则化来防止过度拟合。本文将重点讨论过度拟合为什么会发生,如何预防?
如果多项式回归中多项式的次数太高,或者特征的数量太高,则该算法会学习所有的噪声并很好地拟合训练集,以至于它不会成为一般数据的有效模型。它只对训练集中的数据有好处。这被称为过拟合问题或高方差问题。在这种情况下,训练精度非常高,但是验证精度很差。
这个解释我们就拿线性回归公式来说吧。因为这是最简单的公式:
y = mx + c
如果我们只有一个特征,这就是公式。但是在现实世界中,我们几乎不会只处理一个特性。大多数时候我们有几个特点。
所以,公式变成了:

作者图片
我在这里没有包括截距项。因为在本文中我们将重点讨论权重。斜率 m1、m2、m3…mn 在开始时是随机生成的值。在机器学习中,斜率也称为权重。基于输出值的均方误差(MSE ),斜率得到更新。如果你需要复习,这里是 MSE 的公式:

作者图片
为了解决过拟合问题,增加了正则化项。有两种常见的规范化类型。 L1 和 L2 正规化。
L1 正规化:
这是 L1 正则化的表达式。当我们在线性回归中使用 L1 范数时,它被称为套索回归:

作者图片
这个公式的第一项是简单的 MSE 公式。但是第二项是正则化参数。如你所见,正则项是所有斜率的绝对值之和乘以项λ。您需要根据交叉验证数据输出来选择 lambda。如果λ越大,MSE 越大。这意味着更大的惩罚。当误差项变大时,斜率变小。
另一方面,如果斜率较大,误差项也会变大。那也是一种惩罚。结果,斜率开始变小。一些斜率可能变得非常接近零,这将使一些特征被忽略。因为每个斜率都乘以一个特征。这样, L1 正则化也可以用于特征选择。但是缺点是,如果你不想丢失任何信息,不想删除任何特征,你必须小心。
L1 正则化的优点是,它比 L2 正则化对异常值更鲁棒。也可用于特征选择。
L2 正规化:
这是 L2 正则化的表达式。这种类型的回归也称为岭回归。

作者图片
从公式中可以看出,我们将所有斜率的平方乘以λ相加。与 L1 正则化一样,如果选择更高的 lambda 值,MSE 会更高,因此斜率会变得更小。此外,如果斜率值较高,MSE 也较高。这意味着更高的惩罚。但是因为采用斜率的平方,斜率值永远不会降到零。所以,你在算法中不会损失任何特征贡献。
不利的一面是,它受异常值的影响太大。当我们计算权重的平方时,如果一个值比其他值高很多,它就会因为平方而变得过于强大。
L2 范数的优点是,更容易得到正则项的导数。因此,它可以更容易地用于梯度下降公式。此外,因为您没有丢失任何信息,因为没有斜率变为零,所以如果异常值不是问题,它可能会为您提供更好的性能。
结论
L1 和 L2 正规化各有利弊。根据项目的不同,您可以选择自己的正规化类型。或者,你可以两个都试试,看看哪个效果更好。
请随时关注我的推特、脸书页面,并查看我的新 YouTube 频道
更多阅读
https://pub.towardsai.net/data-analysis-91a38207c92b </30-very-useful-pandas-functions-for-everyday-data-analysis-tasks-f1eae16409af>
了解有替换和无替换的采样(Python)

替换程序取样。迈克尔·加拉尼克的图片。
替换抽样可以定义为允许抽样单位出现一次以上的随机抽样。替换取样包括
- 从总体中随机抽取的一个抽样单位(如玻璃珠或一行数据)(如一罐珠子或一个数据集)。
- 记录抽取了哪个采样单元。
- 将抽样单位返回给总体。
在抽取下一个抽样单位之前将抽样单位返回总体的原因是为了确保在以后的抽取中选择任何特定抽样单位的概率保持不变。在整个数据科学中,替换抽样有许多应用。这些应用中的许多应用使用自举,这是一种统计过程,使用数据集上的替换采样来创建许多模拟样本。使用替换采样创建的数据集与原始数据集具有相同数量的样本,称为引导数据集。自举数据用于机器学习算法,如袋装树和随机森林,以及统计方法,如自举置信区间等等。
本教程将深入研究有替换和无替换的采样,并将触及这些概念在数据科学中的一些常见应用。和往常一样,本教程中使用的代码可以在我的 GitHub 上获得。就这样,让我们开始吧!
什么是置换取样

替换程序取样。迈克尔·加拉尼克的图片。
替换抽样可以定义为允许抽样单位出现一次以上的随机抽样。替换取样包括
- 从总体中随机抽取的一个抽样单位(如玻璃珠或一行数据)(如一罐珠子或一个数据集)。
- 记录抽取了哪个采样单元。
- 将抽样单位返回给总体。
想象一下,你有一个装有 12 颗独特玻璃珠的罐子,如上图所示。如果你从罐子里取样替换,随机选择任何一个玻璃珠的几率是 1/12。选择一颗珠子后,将它放回瓶子,这样在未来的采样中选择 12 颗珠子中任何一颗的概率都不会改变(1/12)。这意味着,如果你重复这个过程,你完全有可能随机取出同一个珠子(在这种情况下只有 1/12 的机会)。
本节的其余部分将介绍如何使用 Python 库 NumPy 和 Pandas 进行替换采样,还将介绍相关概念,如引导数据集,以及使用替换采样创建引导数据集时需要多少重复样本。
使用 NumPy 替换取样
为了更好地理解带有替换的示例,现在让我们用 Python 来模拟这个过程。下面的代码从包含从 0 到 11 的唯一数字的 NumPy 数组中加载 NumPy 和带有替换的样本 12 次。
import numpy as np
np.random.seed(3)# a parameter: generate a list of unique random numbers (from 0 to 11)
# size parameter: how many samples we want (12)
# replace = True: sample with replacement
np.random.choice(a=12, size=12, replace=True)

注意有多个重复的数字。
我们在上面的代码中采样 12 次的原因是因为我们采样的原始 jar(数据集)中有 12 个珠子(采样单元)。我们选择的 12 个弹球现在是引导数据集的一部分,该数据集是通过替换采样创建的,其值的数量与原始数据集相同。
使用熊猫进行替换取样
由于大多数人对从瓶子中取样珠子的应用不感兴趣,所以重要的是要提到一个取样单元也可以是一整行数据。以下代码使用 Kaggle 的 King County 数据集创建了一个引导数据集,该数据集包含 King County 的房屋销售价格,其中包括 2014 年 5 月至 2015 年 5 月期间的西雅图。你可以从 Kaggle 下载数据集,或者从我的 GitHub 加载。
# Import libraries
import numpy as np
import pandas as pd# Load dataset
url = '[https://raw.githubusercontent.com/mGalarnyk/Tutorial_Data/master/King_County/kingCountyHouseData.csv'](https://raw.githubusercontent.com/mGalarnyk/Tutorial_Data/master/King_County/kingCountyHouseData.csv')
df = pd.read_csv(url)
# Selecting columns I am interested in
columns= ['bedrooms','bathrooms','sqft_living','sqft_lot','floors','price']
df = df.loc[:, columns]# Only want to use 15 rows of the dataset for illustrative purposes.
df = df.head(15)# Notice how we have 3 rows with the index label 8
df.sample(n = 15, replace = True, random_state=2)

请注意,有多个重复行。
在使用替换进行采样以创建引导数据集时,您预计会有多少个重复的样本/行?

自举数据用于袋装树,通常用于随机森林模型(这可能是袋装树或随机森林的图表,具体取决于决策树与数据的匹配程度)。迈克尔·加拉尼克的图片。
值得注意的是,当您使用替换进行采样以生成数据时,您可能会得到重复的样本/行。实际上,平均引导数据集包含大约 63.2%的原始行。这意味着对于原始数据集中的任何特定数据行,36.8%的引导数据集将不包含它。
这一小节简要地展示了如何通过统计得出这些数字,以及如何通过使用 Python 库 pandas 进行实验来接近这些数字。
基本统计
让我们从推导原始数据集中的任何特定数据行,36.8%的引导数据集将不包含该行开始。
假设原始数据集中有 N 行数据。如果要创建引导数据集,需要使用替换进行 N 次采样。
对于带有替换的单个样本,特定数据行不是从数据集中随机抽样替换的概率为

由于引导数据集是通过从大小为 N 的数据集中采样 N 次而获得的,因此我们需要采样 N 次来确定在给定的引导数据集中某一行未被选中的概率。

如果我们取 N 趋于无穷大时的极限,我们发现概率是. 368。

原始数据集中的任何特定数据行将出现在引导数据集中的概率仅为 1-𝑒^-1 = . 63213。注意,在现实生活中,你的数据集越大(N 越大),你就越有可能接近这些数字。
利用熊猫
下面的代码使用 pandas 展示了一个引导数据集将包含大约 63.2%的原始行。
# Import libraries
import numpy as np
import pandas as pd# Load dataset
url = '[https://raw.githubusercontent.com/mGalarnyk/Tutorial_Data/master/King_County/kingCountyHouseData.csv'](https://raw.githubusercontent.com/mGalarnyk/Tutorial_Data/master/King_County/kingCountyHouseData.csv')
df = pd.read_csv(url)
# Selecting columns I am interested in
columns= ['bedrooms','bathrooms','sqft_living','sqft_lot','floors','price']
df = df.loc[:, columns]"""
Generate Bootstrapped Dataset (dataset generated with sample with replacement which has the same number of values as original dataset)
% of original rows will vary depending on random_state
"""
bootstrappedDataset = df.sample(frac = 1, replace = True, random_state = 2)
在下面的引导示例中,请注意它包含大约 63.2%的原始样本/行。这是因为样本量很大(len(df)是 21613)。这也意味着每个引导数据集将不包括原始数据集中大约 36.8%的行。
len(df)

len(bootstrappedDataset.index.unique()) / len(df)

什么是无置换取样

无替换取样。图像由迈克尔·加拉尼克拍摄。
无替换抽样可以定义为不允许抽样单位出现一次以上的随机抽样。现在让我们来看一个没有替换的采样如何工作的快速例子。
想象一下,你有一个装有 12 颗独特玻璃珠的罐子,如上图所示。如果您在没有从广口瓶中替换的情况下对取样,那么随机选择任何一个玻璃珠的几率是 1/12。在选择一个珠子后,它不会返回到瓶子中,因此在将来的采样中选择剩余的 11 个珠子中的任何一个的概率现在是(1/11)。这意味着每抽取一个额外的样品,瓶中的珠粒就越来越少,直到最终不再有珠粒可供取样(12 次取样后)。
使用 NumPy 进行无替换采样
为了巩固这些知识,现在让我们用 Python 来模拟这个过程。下面的代码从包含从 0 到 11 的唯一数字的 NumPy 数组中加载 NumPy 并采样而不替换12 次
import numpy as np
np.random.seed(3)# a parameter: generate a list of unique random numbers (from 0 to 11)
# size parameter: how many samples we want (12)
# replace = False: sample without replacement
np.random.choice(a=12, size=12, replace=False)

注意没有重复的数字。
请注意,如果您尝试使用比原始样本(在本例中为 12)更长的无替换采样来生成样本,您将会得到一个错误。回到瓶子里的珠子的例子,你不能取样比瓶子里更多的珠子。
np.random.seed(3)
np.random.choice(a=12, size=20, replace=False)


你不能取样(不替换)比瓶子里更多的珠子。图像由迈克尔·加拉尼克拍摄。
数据科学中无替换抽样的例子
数据科学中使用无替换抽样。一个非常常见的用途是在模型验证程序中,如训练测试分割和交叉验证。简而言之,这些过程中的每一个都允许您模拟机器学习模型如何对新的/看不见的数据执行。
下图显示了训练测试拆分过程,该过程包括将数据集拆分为两部分:训练集和测试集。这包括随机抽样而不替换大约 75%的行(您可以改变这一点),并将它们放入您的训练集,将剩余的 25%放入您的测试集。请注意,“特征”和“目标”中的颜色表示特定训练测试分割的数据去向(“X_train”、“X_test”、“y_train”、“y_test”)。

列车测试分离程序。迈克尔·加拉尼克的图片。
如果你想了解更多关于列车测试拆分的信息,你可以看看我的博客文章了解列车测试拆分。
结论
理解有替换和无替换抽样的概念在统计学和数据科学中很重要。自举数据用于机器学习算法,如袋装树和随机森林,以及统计方法,如自举置信区间等。
未来的教程将会介绍一些这方面的知识,并讨论如何应用这些知识来理解袋装树和随机森林。如果您对本教程有任何问题或想法,请在下面的评论中或通过 Twitter 联系我们。
用 Python 代码理解自组织映射神经网络
通过竞争、合作和适应的大脑启发的无监督机器学习

自组织映射的进化。作者图片
1.介绍
自组织映射(SOM)是一种无监督的机器学习算法,由 Teuvo Kohonen 于 20 世纪 80 年代提出[1]。顾名思义,地图在没有任何他人指导的情况下自行组织。这是一个受大脑启发的模型。我们大脑中大脑皮层的不同区域负责特定的活动。视觉、听觉、嗅觉和味觉等感官输入通过突触以自组织的方式映射到相应皮层区域的神经元。还已知具有相似输出的神经元在附近。SOM 通过竞争神经网络进行训练,这是一种类似于这些大脑机制的单层前馈网络。
SOM 的算法相对简单,但是乍一看可能会有一些混乱,并且很难弄清楚如何在实践中应用它。可能是因为 SOM 可以从多个角度来理解。它类似于用于降维和可视化的主成分分析(PCA)。SOM 也可以被认为是一种处理非线性降维的流形学习。SOM 还因其矢量量化特性而用于数据挖掘[2]。训练可以将高维可观测数据表示到较低维的潜在空间上,通常在 2D 正方形网格上,同时保留原始输入空间的拓扑。但是该地图也可以用于投影新的数据点,并查看哪个集群属于该地图。
本文解释了自组织映射的基本结构及其算法,重点放在它的自组织方面。我们用 Python 编写了 SOM 来解决一个聚类问题,使用了 UCI 机器学习知识库[3]中的数据集。然后,我们将看到在线(连续)训练中地图是如何组织自己的。最后,我们对训练好的 SOM 进行了评估,并讨论了它的优点和局限性。SOM 不是最流行的 ML 技术,在学术文献之外也不是很常见;然而,这并不意味着 SOM 不是解决所有问题的有效工具。训练一个模型是相对容易的,来自训练好的模型的可视化可以用来有效地向非技术审计人员解释。我们将会看到,该算法所面临的问题在其他无监督的方法中也经常出现。
2.架构和学习算法
自组织映射的神经网络具有一个输入层和一个输出层。第二层通常由一个二维网格组成,网格中有 m x n 个神经元。地图层的每个神经元与输入层的所有神经元紧密相连,拥有不同的权重值。
在竞争学习中,输出层的神经元相互竞争被激活。竞赛获胜的神经元是唯一可以被解雇的神经元;因此,它被称为赢家通吃神经元。在 SOM 中,竞争过程是寻找与输入模式最相似的神经元;获胜者被称为最佳匹配单位(BMU)。
相似性作为获胜的标准可以用几种方法来衡量。最常用的度量是欧几里德距离。与输入信号距离最短的神经元成为 BMU。
在 SOM 中,学习不仅是为了胜利者,也是为了在地图上物理上接近它的神经元。BMU 与其邻国共享共同学习的特权。邻域的定义由网络设计者确定,而最佳邻近度取决于其他超参数。如果邻域范围太小,则训练的模型将遭受过拟合,并且存在一些死亡神经元永远没有机会改变的风险。
在适应阶段,BMU 及其邻国会调整自身的权重。学习的效果是将获胜和相邻神经元的权重移动到更接近输入模式。举个例子,如果输入信号是蓝色的,而 BMU 神经元是浅蓝色的,胜出者会变得比浅蓝色更蓝。如果邻居是黄色的,他们会在当前的颜色上增加一点蓝色。
自组织映射学习算法(在线学习)可以用以下 4 个步骤来描述。
1\. Initialisation
Weights of neurons in the map layer are initialised.
2\. Competitive process
Select one input sample and search the best matching unit among all neurons in n x m grid using distance measures.
3\. Cooperative process
Find the proximity neurons of BMU by neighbourhood function.
4\. Adaptation process
Update the BMU and neighbours' weights by shifting the values towards the input pattern. If the maximum count of training iteration is reached, exit. If not, increment the iteration count by 1 and repeat the process from 2.
3.履行
在本文中,SOM 使用在 UCI ML Repository 网站上提供的钞票认证数据集进行训练。数据文件包含 1372 行,每行有 4 个特征和 1 个标签。所有 4 个特征都是没有空值的数值。标签是一个二进制整数值。
首先,我们将数据随机分为训练数据和测试数据。我们使用所有 4 个特征通过在线训练算法来训练 SOM。然后,我们通过使用训练数据的标签可视化地图来评估训练的 SOM。最后,我们可以使用训练好的映射,使用测试数据来预测标签。
因此,我们可以证明,以无监督方式训练的 SOM 可以应用于使用标记数据集的分类。
Python 代码
1。导入库
2。导入数据集
CSV 文件从网站下载并存储在一个目录中。我们使用前 4 列表示 x,最后一列表示 y。
3。训练和测试数据分割
数据以 0.8:0.2 的比例分割用于训练和测试。我们可以看到分别有 1097 个和 275 个观察值。
4。助手功能
minmax_scaler 用于在 0 和 1 之间标准化输入数据。因为算法会计算距离,所以我们应该将每个要素的值缩放到相同的范围,以避免其中任何一个要素对距离计算的影响大于其他要素。
e_distance 计算两点之间的欧氏距离。 m_distance 用于获取网格上两点之间的曼哈顿距离。在我们的例子中,欧几里德距离用于搜索获胜的神经元,而曼哈顿距离用于限制邻域范围。它通过应用矩形邻域函数来简化计算,其中位于距 BMU 的拓扑位置一定曼哈顿距离内的神经元在相同水平上被激活。
winning_neuron 在 BMU 中搜索样本数据 t 。计算输入信号和地图层中每个神经元之间的距离,并返回具有最短距离的神经元的网格的行和列索引。
衰减返回使用当前训练步、最大训练步数、最大邻域范围和学习率应用线性衰减后的学习率和邻域范围。

图 3–4–1 邻域范围衰减

图 3–4–2 邻域范围衰减
5。超参数
超参数是不可训练的参数,需要在训练算法之前选择。它们是神经元的数量、SOM 网格的尺寸、训练步骤的数量、学习速率和来自 BMU 的邻域范围。
在本例中,我们为网格设置了较小的数字(1010 ),但是对于超参数的选择有启发性。我们可以使用[5 * sqrt(训练样本数)]公式[4]来选择神经元的数量。我们有 1097 个训练样本,因此可以在网格上创建 5 sqrt(1097) = 165.60 个神经元。因为我们有一个 2D 正方晶格,这个数的平方根暗示了我们在每个维度上可以有多少个神经元。sqrt 的上限(165.40) = 13。,所以地图的尺寸可以是 13*13。
训练步骤的数量可能需要至少(500 * n 行* m 列)来收敛。首先,我们可以将步数设置为 500 1313 = 84,500。学习率和邻域范围可以设置为较大的数字,并逐渐减小。建议使用不同的超参数集进行改进试验。
最大邻域范围和学习率的初始值可以设置为一个较大的数。如果比率太小,可能会导致过度拟合,并需要更多的学习训练步骤。
6。培训
在应用输入数据标准化之后,我们用 0 和 1 之间的随机值为网格上的每个神经元初始化映射。然后使用衰减函数计算学习率和邻近范围。从训练数据中随机选择样本输入观察,并搜索最佳匹配单元。基于曼哈顿距离准则,选择包括获胜者在内的邻居进行学习,并调整权重。
7。向经过训练的 SOM 显示标签
在上一步中,我们完成了培训。因为这是无监督学习,但我们的问题有一个标签数据,我们现在可以将标签投影到地图上。这一步有两个部分。首先,收集每个神经元的标签。其次,将单个标签投影到每个神经元,构建标签图。
我们创建与 SOM 相同的网格。对于每个训练数据,我们搜索获胜的神经元,并将观察的标签添加到每个 BMU 的列表中。
为了构建标签图,我们通过多数投票给图上的每个神经元分配一个标签。在没有选择 BMU 的神经元的情况下,我们将类值 2 指定为不可识别的。图 3–7–1 和 3–7–2 显示了第一次和最后一次迭代创建的标签映射。开始时,许多神经元既不是 0 也不是 1,类别标签看起来是随机分散的;最终的迭代清楚地显示了类 0 和 1 之间的区域分离,尽管我们在最终的迭代中看到几个不属于任何一个类的单元。

图 3–7–1 第一次迭代时训练图上的标签

图 3–7–2 最大迭代时训练图上的标签
figure 3–7–3 是一个动画 gif,显示了 SOM 从第一步到最大 75,000 步的演变。我们可以看到地图清楚地组织了自己。
要生成动画 gif,您可以参考我以前的文章,使用 Matplotlib 库为 Python 片段进行粒子群优化。

图 3–7–3 SOM 培训动画
8。预测测试集标签
最后,我们可以使用训练好的映射对测试数据进行二进制分类。我们对测试 x 数据进行归一化,并搜索每个观察值 t 的 MBU。返回与神经元相关联的标签。返回了准确性结果,我们的示例获得了非常好的结果,如图 3–8 所示。

图 3–8 预测结果
4.估价
在上一节中,演示了如何针对分类问题实现无监督的自组织映射。我们使用没有标注的数据集来训练地图,并通过将标注投影到地图上来确认训练的结果。正如预期的那样,我们可以观察到每个类别都有清晰的区域,具有相似属性的神经元彼此靠近。最后,我们用一个不可预见的测试数据集测试了地图,并测量了预测的准确性。
我们在示例中使用的数据是一个小而干净的数据集,具有有限数量的观察值和特征。在现实生活中,数据科学家面临的问题在维度上要高得多,并且带标签的数据集不完全可用。即使有,它们的质量也不一定可靠。例如,在为一家银行检测恶意交易时,不期望所有交易都对照阳性案例进行检查可能是明智的;可能只有少数阳性病例在真实数据集中被标记。
当把自组织地图应用到现实世界的场景中时,我们会遇到一些挑战。首先,如果我们没有带标签的数据集,我们就无法测量损失。我们无法验证训练好的地图有多可靠。地图的质量在很大程度上取决于数据本身的特征。通过归一化进行的数据预处理对于基于距离的算法是必不可少的。对数据集的预先分析对于理解数据点的分布也很重要。特别是对于无法可视化的高维数据,我们可以使用其他降维技术,如 PCA 和奇异值分解(SVD)。
此外,如果拓扑图的形状与潜在空间中数据点的分布不相关,则训练可能不成功。虽然我们在例子中使用了正方形网格,但是我们必须仔细设计地图的结构。一个值得推荐的方法是使用主成分分析的前两个主成分的解释方差的比率。但是,如果时间允许,尝试不同的超参数进行微调是值得的。
在算法的计算代价方面,训练时间复杂度取决于迭代次数、特征数和神经元数。最初,SOM 是为顺序学习而设计的,但在某些情况下,批量学习方法是首选。随着大数据的数据量增加,可能有必要研究更有效的学习算法。在我们的例子中,我们使用了矩形邻域函数,为简化起见,称为气泡。对于训练迭代,我们可以监控自组织映射的形成,并检查在循环过程中如何学习映射。训练步骤数量的减少直接影响计算的数量。
最后,本文没有涉及批量学习算法。如果问题的数据有限,使用批处理算法是一个不错的选择。事实上,Kohonen 指出,批处理算法是有效的,对于实际应用是值得推荐的[5]。我们想通过留下链接到 Kohonen 的文章“自组织地图的本质”来结束这篇文章,以供进一步阅读。
https://www.sciencedirect.com/science/article/pii/S0893608012002596?via%3Dihub
参考
[1] T. Kohonen,“拓扑正确特征图的自组织形成”,生物控制论,第 43 卷,第 1 期,第 59–69 页,1982 年,doi: 10.1007/bf00337288。
[2] de Bodt,e .,Cottrell,m .,Letremy,p .和 Verleysen,m .,2004 年。利用自组织映射加速矢量量化。神经计算,第 56 期,第 187-203 页。
[3]杜瓦和格拉夫(2019 年)。UCI 机器学习知识库[http://archive . ics . UCI . edu/ml]。加州欧文:加州大学信息与计算机科学学院。
[4]田军,M. H. Azarian,M. Pecht,“利用基于自组织映射的 K-最近邻算法进行异常检测”,《PHME_CONF》,第 2 卷第 1 期,2014 年 7 月。
[5] T. Kohonen,“自组织映射的本质”,神经网络,第 37 卷,第 52–65 页,2013 年 1 月,doi:10.1016/j . neu net . 2012 . 09 . 018。
理解分类问题中的 Sigmoid、Logistic、Softmax 函数和交叉熵损失(对数损失)
分类问题中关键概念的实用数学

在 Unsplash 上由 Camylla Battani 拍摄的照片
1。简介
2。乙状结肠函数(逻辑函数)
3。逻辑回归中的逻辑函数
∘ 3.1 线性回归综述
∘ 3.2 逻辑函数与逻辑回归
4 .多类分类和 Softmax 函数
∘ 4.1 多类分类的方法
∘ 4.2 Softmax 函数
5 .交叉熵损失和对数损失
∘ 5.1 对数损失(二元交叉熵损失)
∘ 5.2 对数损失的推导
∘ 5.3 交叉熵损失(多类)
∘ 5.4 交叉熵损失 vs 负对数似然
6。结论
关于我的
参考文献
1.介绍
当学习逻辑回归和深度学习(神经网络)时,我总是会遇到以下术语:
- Sigmoid 函数
- 物流功能
- Softmax 函数
- 原木损失
- 交叉熵损失
- 负对数似然
每次我看到它们时,我并没有真的试图去理解它们,因为我可以使用现有的库为我做任何事情。例如,当我建立逻辑回归模型时,我将直接使用来自 Scikit-Learn 的sklearn.linear_model.LogisticRegression。当我使用 PyTorch 处理深度学习分类问题时,我知道我需要在输出层添加一个具有二进制交叉熵损失的 sigmoid 激活函数用于二进制分类,或者添加一个具有负对数似然损失的 (log) softmax 函数用于多类分类问题(或者只添加交叉熵损失)。
最近,当我重新审视这些概念时,我发现研究数学并理解隐藏在下面的东西是很有用的。因此,在这篇文章中,我从不同的来源收集了一些材料,我将展示一些解释的数学公式。
我还给自己做了一个小抄,可以在我的 GitHub 上访问。
2.Sigmoid 函数(逻辑函数)
Sigmoid 函数 是通用的数学函数,具有相似的性质:具有 S 形曲线,如下图所示。

Sigmoid 函数家族的成员,来自维基百科

一个逻辑函数的曲线,来自维基百科
机器学习中最常用的 sigmoid 函数是逻辑函数,如下式。

作者图片
这个公式很简单,但是非常有用,因为它为我们提供了一些很好的特性:
- 将特征空间映射成概率函数
- 它使用指数
- 它是可微的
对于属性 1,不难看出:
- 当 x 非常大(趋于无穷大)时,输出将接近 1
- 当 x 非常小时(趋于-无穷大),输出将接近于 0
- 当 x 为 0 时,输出将为 1/2
对于属性 2,非线性关系确保大多数点要么接近 0,要么接近 1,而不是卡在中间的模糊区域。
性质 3 也非常重要:当在一般的 ML 问题中使用梯度下降或者在神经网络中使用反向传播来从误差更新权重时,我们需要函数是可微分的以计算梯度。
逻辑函数的性质很好,但是如何在逻辑回归中使用逻辑函数来解决二元分类问题呢?
3.逻辑回归中的逻辑函数
3.1 线性回归综述
在深入之前,让我们回顾一下回归模型的概念。回归长期以来一直用于统计建模,并且是监督机器学习方法的一部分。它是对因变量与一个或多个自变量之间的关系进行建模的过程。

简单线性回归的例子,来自维基百科
最常用的回归模型是线性回归,它使用特征的线性组合来预测值。上图是线性回归的最简单形式,称为简单线性回归。它有两个参数β_0 和β_1,每个参数代表截距和斜率,以定义数据点之间的红色最佳拟合线。有了使用现有数据训练的两个参数,我们将能够在给定未知的 x 值的情况下预测新的 y 值。

简单线性回归,作者图片
定义了最简单的形式,就可以把线性回归公式推广到容纳 x 的多个维度,也可以叫做多元线性回归(多元回归)。基本上,它扩展到多个维度并使用多个特征(例如,房屋大小、年龄、位置等。)进行预测(例如,销售价格)。

广义线性回归模型,作者图片
3.2 逻辑函数和逻辑回归
除了预测回归的实际值之外,线性回归模型还可以通过预测对象在特定类别中的概率来用于分类问题,这可以通过用 p 替换 y 来简单地完成:

作者图片
问题是这里的概率 p 是无界的——它可以是任何值。因此,为了将概率范围限制在 0 和 1 之间,我们可以使用上一节中介绍的逻辑函数并映射它:

作者图片
这将确保无论预测值是多少,概率 p 将在 0 和 1 之间的范围内,具有前面介绍的所有优点。但是,指数形式并不容易处理,所以我们可以使用 odds 函数重新排列公式。 Odds 是概率的兄弟,代表“成功”与“不成功”的比率。当 p=0 时,赔率为 0;当 p=0.5 时,赔率为 1;当 p=1 时,赔率为∞。这些关系如下所示:

赔率和概率的关系,作者图片
随着赔率函数的定义,我们得到:

作者图片
很容易看出这两个方程之间的相似性,因此我们有:

作者图片
我们用 log 去掉指数关系,所以最后又回到我们熟悉的那一项。等号右边的部分仍然是输入 x 和参数β的线性组合。等号左边的部分现在变成了几率的对数,或者给它一个新的名字:概率的对数。因此,整个等式变成了对数函数的定义,它是标准逻辑函数的反函数。通过使用 logit 函数建模,我们有两个优势:
- 我们仍然可以把它当作一个线性回归模型,使用我们熟悉的预测值的线性函数
- 我们可以使用它来预测某类主题的真实概率——通过使用逆 logit 函数转换预测值。
这就是逻辑回归使用逻辑函数在幕后工作的方式,并且非常适合于进行二元分类(2 类):对于 A 类和 B 类,如果成为 A 类的预测概率高于我们设置的阈值(例如,0.5),则它被分类为 A 类;另一方面,如果预测概率低于(例如,0.5),则将其分类为 b 类
我们刚刚介绍了使用逻辑回归的二元分类。那么,超过 2 节课怎么办?
4.多类分类和 Softmax 函数
4.1 多类分类方法
使用二元分类器处理多类分类问题有几种方式,常见的有:一对一和一对一。
一对其余的方法训练 K-1 个二进制分类器来将每个类从其余的类中分离出来。然后,被所有分类器排除的实例将被分类为 k 类。这在许多情况下都有效,但一对其余方法的最大问题是模糊区域:其中一些实例可能被放入多个类中。
另一方面,我们有一对一的方法,在每个类之间训练一个二元分类器。类似于“一个对其余的”,模糊区域也存在于此,但是这次存在未被分类到任何类别的实例。更糟糕的是效率:我们需要为 n 个类选择 2 个(组合)分类器,如下式所示。例如,如果我们有 10 个类,我们需要 45 个分类器来使用这种方法!

一对一方法所需的分类器数量,按作者分类的图像
有了这些对一对一和一对一方法的限制,我们如何进行多类分类呢?答案是使用 softmax 函数。
4.2 Softmax 功能
Softmax 函数是上述二进制分类部分介绍的逻辑函数的推广形式。下面是等式:

Softmax 函数,作者图片
来解读一下,我们可以看做:将实例归类为 j 的概率可以计算为输入的第 j 个元素的指数除以所有输入元素的指数之和。为了更好地理解它,我们可以看下面的例子:

将 Softmax 函数应用于模型输出的示例,由 Sewade Ogun (公共许可证)提供
图像分类器在通过神经网络前馈后给出数字输出,在这种情况下,我们有一个 3×3 的数组,其中行是实例,列是类。第一行包含第一幅图像的预测:类别 cat、dog 和 horse 的得分分别为 5、4 和 2。这些数字没有意义,所以我们将它们输入到 softmax 函数中。把这三个数字代入方程,我们可以得到图像是猫、狗、马的概率分别是 0.71、0.26、0.04,加起来就是 1。
与逻辑函数类似,softmax 函数也具有以下优点,因此人们广泛地将其用于多类分类问题:
- 它将特征空间映射成概率函数
- 它使用指数
- 它是可微的
解释 softmax 函数的另一种方法是通过著名的贝叶斯定理,其中:

贝叶斯定理,作者图片
将它应用到 softmax 的例子中,所有的项都可以解释为概率:

作者图片
在哪里

作者图片
5.交叉熵损失和对数损失
当我们训练分类模型时,我们最有可能定义一个损失函数来描述预测值偏离真实值的程度。然后,我们将使用梯度下降法来调整模型参数,以降低损失。这是一种优化问题,也称为深度学习中的反向传播。
在我们开始之前,我强烈推荐丹尼尔·戈多伊的文章:理解二元交叉熵/对数损失:一个直观的解释。它很好地解释了下面的实用数学概念,并以可视化的方式展示出来。在这篇文章中,我使用了一点不同的惯例,更像维基百科。
我们开始吧!
5.1 对数损失(二元交叉熵损失)
在分类问题中使用的一种常用损失函数被称为交叉熵损失,或二进制情况下的对数损失。我们先把表达式放在下面:

对数损失(二元交叉熵),按作者分类的图像
由于对数函数具有当 y 为 0 时,其对数趋向于-无穷大的性质;当 y 为 1 时,它的对数为 0,我们可以用它来非常有效地模拟损失。对于具有真标签 0 的实例:
- 如果预测值为 0,则上面的公式将返回 0 的损失。
- 如果预测值是 0.5,那么上面的公式将返回 0.69 的损失
- 如果预测值为 0.99,则上述公式将得出损失为 4.6
正如我们在这里看到的,日志放大了分类中的错误,因此与任何线性损失函数相比,错误分类将受到更严重的惩罚。预测值越接近真实值的对立面,损失就越高,最终会变成无穷大。这正是我们想要的损失函数。那么日志丢失的定义从何而来?
5.2 测井曲线损失的推导
交叉熵是源于信息论的一个概念,它度量两个概率分布之间的差异,在信息论中它在真实概率分布 p 和估计概率 q 之间的定义是:

交叉熵,作者图片
其中 H(p) 是分布熵 p,和 D_KL(p||q) 是kull back-lei bler 散度,从 q 的一个散度 p 。它也被称为相对熵,即 p 相对于 q 。
熵和 Kullback-Leibler 散度的定义如下:

熵和 Kullback-Leibler 散度的定义,作者图片
将它们代入,很容易得到交叉熵的表达式:

作者图片
对于二进制分类问题,只有两个类,所以我们可以显式表达:

作者图片
注意这里的 p 是概率函数而不是分布 p 。此外,我们可以将真实分布 p(y)表示为 1/N,因此二进制交叉熵(对数损失)可以表示为:

对数损失(二元交叉熵),按作者分类的图像
注意,减号放在开头,因为 0 到 1 之间的值的对数函数给出的是负值。我们想翻转符号,使损失为正——我们想最小化损失。
如果我们愿意,可以进一步扩展该公式的表达式,以包括与模型参数θ的关系,如下所示,但它本质上与上面的相同。

相对于模型参数的对数损失,按作者分类的图像
5.3 交叉熵损失(多类)
在推导了上面的二元情况之后,我们可以很容易地将其扩展到多类分类问题。下面是交叉熵损失函数的一般形式。它只对实例类为 k 时的概率的对数求和,类似于二进制情况,其中总是只考虑表达式的一部分,而其他部分都是 0。

交叉熵损失(广义形式),作者图片
同样,它也可以用模型参数θ来表示,但本质上是同一个等式:

相对于模型参数的交叉熵损失,作者提供的图像
5.4 交叉熵损失与负对数似然
交叉熵损失总是与负对数似然进行比较。事实上,在 PyTorch 中,对于多类分类问题,交叉熵损失相当于 (log) softmax 函数加上负对数似然损失。那么这两个概念到底是怎么联系起来的呢?
在深入研究之前,我们必须了解概率和可能性之间的区别。简而言之:
- 概率:在给定数据样本分布的情况下,找出某个事件发生的概率
- 可能性:在给定样本数据的情况下,找到数据的最佳分布
因此,我们本质上是使用不同的表达式对相同的问题进行建模,但它们是等价的:

可能性的表达,作者的图像
以上是给定数据(从 x_1 到 x_n)的参数θ的似然的定义,相当于给定参数θ得到这些数据(x_1 到 x_n)的概率,可以表示为各个个体概率的乘积。
知道 p 是真实的概率分布,我们可以使用估计的概率分布进一步改写乘积如下:

作者图片
其中 q_i(估计概率分布)和 p_i(真实概率分布)为:

作者图片
其中 n_i 是训练数据中 i 出现的次数。然后通过对可能性取负对数,我们可以得到:

负对数似然相当于交叉熵,作者图像
假设乘积的对数变成对数的和,我们可以很容易地得到上面的等式。神奇的是,负对数似然性变成了交叉熵,就像上面几节介绍的那样。
6.结论
总结一下到目前为止本文中介绍的概念:
- Sigmoid 函数:具有 S 形曲线或 Sigmoid 曲线的一般数学函数,该曲线是有界的、可微的和实的。
- 逻辑函数:在使用逻辑回归的二元分类问题中广泛使用的某个 sigmoid 函数。它将从-无穷大到无穷大的输入映射为从 0 到 1,旨在模拟二元事件的概率。
- Softmax 函数:用于多类分类问题的逻辑函数的一般化形式。
- 对数损失(二元交叉熵损失):损失函数,表示预测概率偏离真实概率的程度。它用于二进制情况。
- 交叉熵损失:对数损失的广义形式,用于多类分类问题。
- 负对数似然:使用最大似然估计概念对交叉熵损失的另一种解释。相当于交叉熵损失。
感谢您的阅读!如果你喜欢这篇文章,请关注我的频道和/或 成为我今天的推荐会员 (非常感谢🙏).我会继续写下去,分享我关于数据科学的想法和项目。如果你有任何问题,请随时联系我。
*https://zhouxu-ds.medium.com/membership
关于我
我是赛诺菲的数据科学家。我拥抱技术,每天都在学习新技能。欢迎您通过媒体博客、 LinkedIn 或 GitHub 联系我。我的观点是我自己的,而不是我雇主的观点。
请看我的其他文章:
- 利用空气质量传感器数据进行时间序列模式识别
- 使用 Elasticsearch 的实时类型预测搜索(AWS OpenSearch)
- 使用 Python 和 Flask-RESTful 为机器学习模型构建 REST API
- 利润最大化的贷款违约预测
- 使用 Berka 数据集进行贷款违约预测
参考
- 西格蒙德函数(维基百科):https://en.wikipedia.org/wiki/Sigmoid_function
- 线性回归(维基百科):https://en.wikipedia.org/wiki/Linear_regression
- 数据科学家实用统计:https://www . oreilly . com/library/view/practical-Statistics-for/9781491952955/
- 普林斯顿 ML 基础讲座:https://www . cs . Princeton . edu/courses/archive/spring 16/cos 495/slides/ML _ Basics _ Lecture 7 _ multi class . pdf
- Softmax 分类器:https://datascience.stackexchange.com/a/24112
- 了解二元交叉熵/对数损失:一个可视化的解释:https://towards data science . com/understanding-binary-cross-entropy-log-loss-a-visual-explain-a3ac 6025181 a
- 交叉熵(维基百科):https://en.wikipedia.org/wiki/Cross_entropy
- Kullback-Leibler Divergence(维基百科):https://en . Wikipedia . org/wiki/kull back % E2 % 80% 93 lei bler _ Divergence
- 交叉熵损失:https://stats.stackexchange.com/a/262746
- 负对数似然比交叉熵:https://stats . stack exchange . com/questions/468818/machine-learning-negative-Log-Likelihood-vs-Cross-Entropy/468822 # 468822*
用机器学习问题框架理解辛普森悖论
所有的困惑都可以解释
辛普森悖论是一个众所周知的统计悖论。像所有的悖论(根据定义)一样,即使我们知道答案,它似乎也不是直观的。在辛普森悖论的情况下,机器学习框架有望让你清楚地看到我们应该如何处理数据。

为什么是悖论?
肾结石治疗的例子
让我们回忆一下这个肾结石治疗的例子,在维基百科文章的辛普森悖论中:
下表显示了成功率(这里的术语成功率实际上是指成功的比例)和涉及小肾结石和大肾结石的治疗次数...括号中的数字表示成功案例的数量占团队总规模的比例。

表来自:https://en.wikipedia.org/wiki/Simpson%27s_paradox
矛盾的结论是,当用于小结石时,治疗 A 更有效,当用于大结石时也更有效,然而当同时考虑两种大小时,治疗 B 似乎更有效。
现在的问题是:最终,在治疗 A 和 B 中,哪一个更有效?
更具体地说,如果你是一名医生,一个病人问你哪种治疗方法更有效,你必须给出明确的答案。
- 对于那些声称治疗 B 更有效的人来说,问题是:最终,患者会知道自己肾结石的大小,而结石的其他大小的数据对他们并不适用。所以结论会是 a。
- 对于那些声称治疗 A 更有效的人来说,问题是:如果你的数据中没有关于结石大小的信息呢?
- 如果你认为治疗 A 更有效,因为你有更多的信息,那么我们如何知道没有其他未知的“混杂”变量?这实际上又会与成功率成反比!
- 在我们的案例中,每个案例的样本大小是关键。例如,如果将治疗 B 的大结石患者的样本量乘以 10 会怎么样?不会有悖论!

辛普森悖论样本量偏差——作者图片
如您所见,统计数据在被误用时会产生误导…
那么你最终的答案是什么?一个病人在等着呢!
真正的答案似乎显而易见…
现在,让我们忘记统计数据。如果我们看一下这两种疗法的定义,答案可能相当明显:
治疗 A 包括开放式外科手术,治疗 B 包括封闭式外科手术
我不是医生,但我认为,在没有任何统计数据的情况下,我会说,一般来说,开放手术(治疗 A)比封闭手术(治疗 B)更有效。
而治疗 A 也通常用于较大的结石,所以比较复杂的病例。而且可能更贵,病人也可能承受更多的痛苦…
回到统计数据…
有了一定的领域专业知识,不用统计就能知道结论。但问题是,有时候,你(还)没有专业知识。然后我们必须处理统计数据。
当试图从机器学习实践者的角度来构建这个问题时,会解释很多混乱。
以下是应该回答的问题
- 我们是否必须使用可用的变量?
- 我们如何更好地界定“有效性”的概念?
- “混杂变量”到底是什么?
- 如何才能知道不再有“混杂变量”?
- 如何处理样本数据可能有偏差的事实?
对于如何知道不再有混杂变量的问题,我们可以用一个回归的例子来说明。

带有两个混淆变量的辛普森悖论——作者图片
监督学习问题框架
特征和目标变量
为了建立机器学习模型,我们首先必须导入数据集。我们有哪些变量?特征变量为 结石大小 和 T 处理 。
目标变量是一个二元变量 结果 以表示成功或失败。
问题是找到一个模型,用变量结石大小和治疗来预测结果。这是一个二元分类问题。
数据非常简单,许多行都是相同的,所以我们可以指出行数。

肾结石治疗数据集-图片由作者提供
构建机器学习模型
我们可以建立一些机器学习模型来预测目标变量。你可以通过这篇文章对机器学习模型有一个的概述。这里我们将建立一个决策树模型和逻辑回归模型来预测成功率。
我会用 R 和 python。如果你想拥有所有的代码,你可以在这里访问它。
首先,对于只有一个变量的处理和决策树是相当简单的。我们可以用 R 或者 python 来建立模型。我在下图中展示了它们。最终的预测只取决于治疗的类型。

一元辛普森悖论决策树—作者图片
然后我们可以使用这两个特征,这是得到的决策树。重要的是要注意,根首先用变量石头大小一分为二。在我们已经看过的统计表中,你可以根据治疗和结石大小来判断不同的成功率。

两个变量的辛普森悖论决策树—作者图片
我们也可以建立逻辑回归模型,我们从 R 中得到结果,以及所有的系数和它们的显著性。

辛普森悖论逻辑回归—作者图片
值得注意的是,当有两个特征时,决策树模型和逻辑回归模型之间的预测略有不同。因为逻辑回归是线性分类器,而决策树是非线性模型。

使用机器学习模型预测辛普森悖论——图片由作者提供
现在,我们能对这两个模型说些什么呢?
对模型的评论
现在,让我们回到我们的问题:
- 我们是否必须使用可用的变量? →是的,我会一直使用所有可用的数据,机器学习模型会帮助我们使用或不使用它们。当没有使用足够的特征变量时,当我们谈到欠拟合时。因此,当涉及更多特性时,结论总是更好。
- 我们如何更好地定义“有效性”的概念? →治疗的有效性就是这个变量的重要性。总的来说,我们可以谈论所有特性的重要性。我们应该在整个模型中解释变量的重要性,通过分析目标变量的一个特征(或几个特征)得出结论是不正确的。因此,对于治疗,我们可以在我们建立的两个模型中解释其不同的重要性:
对于决策树模型,我们可以看到,当只有一个特征处理时,处理 B 更有效。当有两个特征变量时,处理 B 变得更有效。

辛普森悖论与决策树的可变重要性—图片由作者提供
对于逻辑回归模型,我们用治疗系数 b 的符号来解释变量重要性(这里使用了一个热编码)。

使用逻辑回归的辛普森悖论变量重要性—图片由作者提供
顺便说一下,我们还注意到 p 值小于 5%。所以通常我们会得出结论,变量不显著,所以系数被认为是零。但是嘿,5%不是绝对的规定!
- 什么是真正的“混杂变量”? →从机器学习模型的角度来看,当模型变得更加复杂时,混淆变量会改变其重要性,从而产生混淆。这里我们可以看到可变的结石大小比处理更重要。因为当涉及到这两个特征时,石头大小在 logistic 回归模型中的系数更大(我们可以比较系数,因为它们都是二进制的),石头大小在决策树模型中也被用来做第一次分裂。因此,如果我们忽略一个更重要的变量,直接分析目标变量和一个不太重要的特征,结论可能是不相关的。
- 如何才能知道不再有“混杂变量”? →这个问题已经用珍贵的问题回答了。模型会给我们每个变量的重要性。如果我们使用最重要的变量来得出一些结论,那么我们应该对它们有信心。现在的问题当然是,如果我们没有所有的变量呢?嗯,除了收集更多的数据,你什么也做不了。这就是为什么我们说数据是新的石油。
- 如何解决样本数据可能存在偏差的问题? →当使用机器学习模型时,样本大小没有那么大的影响。下图我们可以看到,当大石块和处理 B 的样本量乘以 10,决策树模型几乎是一样的。

样本量的辛普森悖论影响—作者图片
关于不合身的更多信息…
由于第一个模型只使用了一个变量,我们可以说存在欠拟合。当数据被收集时,很明显,机器学习实践者会使用所有的变量。
对于高级模型,我们通常会讨论如何避免过度拟合。在实践中,我们通常会对模型进行欠拟合,因为我们没有足够的数据。所以在实践中,克服欠拟合的问题要比过拟合困难得多。
- 为了克服过度拟合,我们必须调整模型
- 为了克服拟合不足,我们必须收集更多的数据!
在下图中,我们可以看到两种模型的 AUC。

辛普森悖论 AUC 比较—作者图片
从机器学习实践者的角度来看,这些分数很低,尤其是考虑到只使用了两个特征这一事实。我们可以考虑更多的特征来提高预测的性能。
结论
我希望辛普森悖论现在不会再让你感到困惑。以下是我对使用机器学习模型的想法:
- 机器学习模型可以用在比人们想象的更多的情况下。在实践中,问题框架是最具挑战性的部分。当(X,y)被定义时,很容易写出 model.fit(X,y)。
- 机器学习模型通常被构建来预测目标变量。但在某些情况下,关于变量重要性的定性结果对商业人士来说可能是非常有洞察力的。
- 数据是新的石油!你真的可以用更多的数据改变游戏。这就是为什么 GAFAM 正在收集更多关于用户的数据。对于医疗数据,收集更多的数据也很重要。对于我们的肾脏治疗案例,如果我们有更多的数据,如年龄、性别、体重等。它们可以帮助创建一个更好的模型来预测成功率。
- 到最后,成功率不一定是我们想要的最终结果!如果 A 治疗比 B 治疗贵很多呢?死亡风险呢?
所以我最后的想法是:了解你的统计数据,了解你的机器学习模型,不要忘记现实生活要复杂得多!
别忘了获取代码,了解更多关于机器学习的知识。谢谢你的支持。

了解统计数据类型
原文:https://towardsdatascience.com/understanding-statistical-data-types-2993dafcac86
“数据是新的石油”——但是正如存在几种类型的石油一样,也存在几种类型的数据

马库斯·斯皮斯克在 Unsplash 上的照片
介绍
“数据是新的石油”——这是 2006 年创造的一个短语,风靡全球。
想要一些令人震惊的事实?世界上超过 90%的数据是在过去两年中创建的。如果你把一天中产生的所有数据都刻录到 CD 上,这个堆栈可能会达到平均值的两倍。数据庞大而宝贵,因此知道如何操作数据至关重要。要做到这一点,了解不同类型的数据以及它们代表什么是至关重要的。我们开始吧!
定性与定量
现在,在你的一生中,你可能已经经历过几次了,所以我就长话短说。定性数据(也称为分类数据)是不能用数字来衡量的数据。对分类数据进行排序时,人们只能将它们分成不同的类别。分类数据的常见例子有性别(男性/女性)、种族和教育水平。
定量数据就是你可能猜到的东西——可量化的(数字)数据。可以对定量数据进行排序(从大到小),绘制图表,并用于数学分析。一些常见的定量数据示例有时间、重量、温度和等级。
我们可以将这两种类型的数据(定性和定量数据)视为我们将要探索的其他四种数据类型的基础。
定性数据的类型
标称数据
名义数据是一种分类数据,其中每个数据变量都不能相互比较。虽然每个变量各不相同,但它们彼此之间并无相对差异。例如,眼睛颜色是名义数据的一个例子。虽然存在几种眼睛颜色(黑色、棕色、绿色、蓝色),但我们不能说它们彼此不同——它们只是描述一种属性的标签。如果我们改变它的顺序,前面提到的眼睛颜色列表的意义不会改变。
序数数据
顺序数据是指每个数据变量都与其他变量自然相关的数据。每一个都与另一个相对不同,无论是在大小、长度、持续时间等方面。例如,教育水平(在本例中为大学学位)是一种有序数据。我们可以说,副学士、学士、硕士和博士学位彼此都相对不同,因为每个学位需要不同的时间。理论上,我们可以量化序数数据(准学士=2 年,学士=4 年,等等)并对其执行数学运算,因此它有时被认为处于定性和定量数据之间的灰色区域。
虽然序数数据也只是标签,但标签背后的背景信息可以相互比较。因此,如果我们颠倒上述大学学位列表的顺序,其顺序将从最少时间→最多时间变为最多时间→最少时间。
定量数据的类型
离散数据
你可能在初中和高中数学课上听说过离散数据。很有可能,您会通过这样的图表来可视化离散数据:

图片由作者提供。
仅包含彼此离散(或分离)的整数的数据。例如,房间里的人数就是离散数据的一个例子。只能用整数来衡量——毕竟你不可能拥有一个人的零头!离散值可以被计数,因为它们有一个精确的集合,但是它们不能被测量。
连续数据
连续数据是涉及分数或非整数的数据。你很可能通过一条线来想象它:

图片由作者提供。
连续数据由时间、高度和商品价格等值组成。每个值可以被分割或变小,但仍然有效。例如,我们可以将一个人完成一场比赛所用的时间除以 2,它仍然有效——即使数字变成毫秒和微秒。另一方面,我们不能总是把一个房间里的人数除以 2。再说一次,你不能拥有一个人的一小部分!你可以测量任何连续的值,但是你不能计算它(有无穷多个点可以计算)。
结论

图片由作者提供。
虽然本文中提到的这 4 种数据类型确实构成了统计数据类型的主干,但是在已经提到的数据类型之下还存在几个子类型。如果你想了解更多,我强烈推荐这篇由尼克拉斯·东格斯撰写的文章,这篇文章更有深度。
感谢您的阅读!我希望您非常喜欢这篇文章,并且现在对统计数据类型更加熟悉了。
使用 Scikit-learn 了解 SVR 和 Epsilon 不敏感损耗
通过可视化清楚地解释超参数的影响
SVR 或支持向量回归是一种回归任务模型。对于那些想知道它为什么有趣或认为他们已经知道模型如何工作的人,这里有一些简单的问题:
考虑以下只有一个要素和一些异常值的简单数据集。在每个图中,一个超参数改变了它的值,因此我们可以直观地解释模型是如何受到影响的。你能说出每个图形中的超参数是什么吗?

超参数的 SVR 影响-作者提供的图片
如果你不知道,那么,这篇文章就是为你写的。文章的结构是这样的:
- 首先,我们将回忆线性回归的不同损失函数。我们将看到如何从 OLS 回归到 SVR。
- 然后,我们将研究定义 SVR 成本函数的所有超参数的影响。
1.线性模型及其成本函数
1.1 OLS 回归及其惩罚版本
SVR 是一个线性回归器,像所有其他线性回归器一样,该模型可以写成 y = aX+b 。
然后求系数( a 和 b ,可以有不同的损失函数和代价函数。
损失函数和成本函数的术语提示:一个损失函数通常定义在一个数据点上…一个成本函数通常更通用。它可能是训练集上损失函数的总和加上一些模型复杂性损失(正则化)。
最著名的线性模型当然是 OLS ( 普通最小二乘)回归。有时候,我们就称之为线性回归(我觉得有点混乱)。我们称之为普通是因为系数没有被正则化或者惩罚。我们称之为最小平方,因为我们试图最小化平方误差。
如果我们引入系数的正则化,那么我们得到山脊、套索,或者弹性网。这里是一个回顾:

OSL 回归、岭、套索和弹性网的成本函数-图片由作者提供
1.2 从 OLS 回归到支持向量回归
现在,如果我们尝试使用另一个损失函数呢?另一个常见的是绝对误差。它可以有一些很好的性质,但我们通常说我们不使用它,因为它是不可微的,因此我们不能使用梯度下降来找到它的最小值。但是,我们可以使用随机梯度下降来克服这个问题。
然后想法是使用一个“不敏感管,其中的误差被忽略。这就是为什么在 SVR 的损失函数的名称中,我们有术语“ε不敏感”,ε定义了管的“宽度”。
最后,我们添加惩罚项。对于 SVR,通常是 L2。
这里有一个图表总结了我们如何从 OLS 回归到支持向量回归机。

从 OLS 回归到支持向量回归机——作者图片
1.3 阐明所有术语的大局
为了定义最终的成本函数,我们必须定义损失函数和惩罚。对于一个损失函数(平方误差或绝对误差),可以引入“ε不敏感管”的概念,其中误差被忽略。
有了这三个概念,我们就可以随心所欲地构造最终的成本函数。由于一些历史原因,一些组合比其他组合更常见,而且,它们有一些既定的名称。
这是包含所有损失函数和成本函数的图表:

线性模型的成本函数—作者图片
下面是一个简化的视图:

线性模型的成本函数—作者图片
所以 SVR 是一个线性模型,其代价函数由ε不敏感损失函数和 L2 惩罚组成。
一个有趣的事实:当我们为分类定义 SVM 时,我们强调“边际最大化”部分,这相当于系数最小化,使用的范数是 L2。对于 SVR,我们通常关注“ε不敏感”部分。
我们通常不会说 MEA 回归,但它只是当 epsilon 为 0 且不使用惩罚时 SVR 的一个特例。
我们常说,山脊线、套索线和弹性网是 OLS 回归的改进版本。为了得到大的图片,最好说山脊,套索,和 OLS 是弹性网的特殊情况。
“ε不敏感管”也可以应用于 OLS 回归。但是没有专门的名称。这种损失称为平方ε不敏感。
因此在本文中,我们将研究 SVR,以了解以下影响:
- 平方误差与绝对误差
- ε不敏感电子管和 L2 惩罚的影响
- 平方ε不敏感与ε不敏感
2.超参数影响分析
为了直观显示超参数值变化时的影响,我们将使用只有一个要素的简单数据集。目标变量 y 将与特征 x 具有线性关系,因为如果不是这样,线性模型将不能很好地拟合。我们还引入了一个异常值,因为如果数据集是全局线性的,那么我们不会看到大的差异。
我们将在 sci-kit learn 中使用不同的估计器——SGD regressor、 LinearSVR 或SVR——因为它允许我们选择超参数的不同值。我们还将讨论它们之间的一些微妙差异。
你可以在这里找到所有代码的笔记本。
2.1 绝对误差对异常值更稳健
为了只分析损失类型:绝对误差与平方误差,我们将α和ε设为 0。所以我们基本上是在比较 OLS 回归和 MAE 回归。

线性模型的绝对误差与平方误差——图片由作者提供
MEA 最小化将最小化中间值,而 MSE 最小化平均值。
因此,在这种情况下,异常值被 SVR(或 MEA 回归)完全忽略,而它会影响 OLS 回归。
2.2ε不敏感管和处罚
为了使“ε不敏感管”形象化,我们可以绘制下图。

Epsilon 不敏感电子管 SVR —图片由作者提供
对于足够大的ε值(在这种情况下,ε的任何正值都足够大,因为数据集是完全线性的),ε管将包含所有数据集。打个比方,在使用 SVM 分类的情况下,我们使用术语“硬边界”来描述数据点完全线性可分的事实。这里我们可以说它是一个“硬管”。
然后,必须实施惩罚,否则我们将有多种解决方案。因为所有可以包含数据点的管都是没有惩罚的解。通过惩罚,唯一的解决方案将是具有最低斜率(或最小的 L2 范数)的解决方案。这也相当于在 SVM 用于分类的情况下的“边际最大化”,因为“边际最大化”相当于“系数范数最小化”。这里我们也可以将“余量”定义为管的宽度,目标是使管的宽度最大化。
ε不敏感的兴趣是什么?当某些数据点的误差很小时,它们会被忽略。所以要找到最终的模型,只有一些数据点是有用的。
下图显示了存在异常值时,不同ε值的模型如何变化。所以基本上:
- 当ε值较小时,模型对异常值是稳健的。
- 当ε的值较大时,它会考虑离群值。
- 当ε的值足够大时,惩罚项开始起作用,以使系数的范数最小。

艾司隆不敏感损失-作者图片
2.3 拦截的处罚
当有许多特征时,截距的惩罚不太重要。但是这里我们只有一个特性,影响可能是混乱的。
例如,在数据集完全线性的情况下,ε值很小(或为 0),我们应该会看到一个相当完美的模型(拟合数据点)。但是正如你在下面看到的,估计量 LinearSVR 和 SGDRegressor 给出了一些令人困惑的结果。

SVR 中的拦截惩罚—作者图片
为了可视化 alpha(或 C,即 1/alpha)的影响,我们可以使用 SVR。C 小的时候正则化强,所以斜率会小。

作者对 C-image 的 SVR 影响
结论
什么是 SVR?如何解释这个线性模型的工作原理?什么时候用?与其他线性模型有何不同?
以下是我的想法:
- 首先,与 OLS 回归相比,对于绝对误差,SVR 对异常值更稳健。所以如果两个模型给出非常不同的结果,那么我们可以尝试找出异常值并删除它们。我所说的异常值是指目标变量的异常值。
- 那么ε不敏感管有助于我们忽略有小误差的数据点。从模型优化的角度来看,并不是真的有用,因为数据少意味着信息少。但是从计算的角度来看,它可以加速模型训练。最后,只有一些数据点被用来定义模型,它们相应地被称为支持向量。
- 最后,应适用惩罚条款。值得注意的是,在用于分类的 SVM 的情况下,“余量最大化”等同于“惩罚”,并且对于 SVR,惩罚可以被解释为ε不敏感管的宽度的最大化。
别忘了获取代码,了解更多关于机器学习的知识。谢谢你的支持。

如果你想了解如何用 Excel 训练机器学习模型,可以在这里访问一些有趣的文章。
了解综合控制方法
原文:https://towardsdatascience.com/understanding-synthetic-control-methods-dd9a291885a1
因果数据科学
业内最流行的因果推理技术之一的详细指南

封面,作者图片
现在人们普遍认为,计算治疗(药物、广告、产品等)对感兴趣的结果(疾病、公司收入、客户满意度等)的因果效应的黄金标准技术是 AB 测试,也称为随机实验。我们将一组受试者(患者、用户、顾客……)随机分为治疗组和对照组,并对治疗组进行治疗。这一程序确保了在此之前,两组之间的唯一预期差异是由治疗引起的。
AB 测试的一个关键假设是治疗组和对照组之间没有污染。给治疗组的一个病人服用一种药物并不影响对照组病人的健康。举例来说,如果我们试图治愈一种传染性疾病,而这两个群体并没有被隔离,情况可能就不一样了。在这个行业中,经常违反污染假设的是网络效应——我使用社交网络的效用随着网络上朋友数量的增加而增加——和一般均衡效应——如果我改进一个产品,它可能会减少另一个类似产品的销售。
由于这个原因,实验通常在足够大的范围内进行,这样就不会有跨群体的污染,如城市、州、甚至国家。然后另一个问题因为规模更大而出现:治疗变得更贵。给医院里 50%的病人开一种药,比给一个国家 50%的城市开一种药要便宜得多。因此,通常只有几个单位接受治疗但通常需要更长的时间。
在这种背景下,一种非常强大的方法在大约 10 年前出现:合成控制。综合控制的思想是利用数据的时间变化而不是横截面变化(跨时间而不是跨单元)。这种方法在业内非常流行——例如在像谷歌、优步、脸书、微软和亚马逊这样的公司——因为它很容易解释和处理经常大规模出现的背景。在这篇文章中,我们将通过例子来探索这种技术:我们将调查自动驾驶汽车对于拼车平台的有效性。
无人驾驶汽车的例子
假设你是一个拼车平台,你想在你的车队中测试自动驾驶汽车的效果。
可以想象,对于这种类型的特性,运行 AB/test 有许多限制。首先,把个人乘坐随机化是很复杂的。第二,这是一个非常昂贵的干预。第三,也是统计上最重要的一点,你不能在游乐设备级别进行干预。问题是,从治疗组到控制组存在溢出效应:如果自动驾驶汽车确实更高效,这意味着它们可以在相同的时间内服务更多的客户,减少了普通司机(对照组)可以获得的客户。这种溢出污染了实验,并阻止了对结果的因果解释。
出于所有这些原因,我们只选择一个城市。鉴于这篇文章的合成氛围,我们不得不选择… ( 鼓声 )…迈阿密!

迈阿密天际线,图片来自Unsplash.com
我生成了一个模拟数据集,在这个数据集里,我们观察了一组美国城市随时间的变化。收入数据是虚构的,而社会经济变量取自 OECD 2022 大都市地区数据库。我从[src.dgp](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/dgp.py)导入数据生成过程dgp_selfdriving()。我还从[src.utils](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/utils.py)引进了一些绘图函数和库。
from src.utils import *
from src.dgp import dgp_selfdriving
treatment_year = 2013
treated_city = 'Miami'
df = dgp_selfdriving().generate_data(year=treatment_year, city=treated_city)
df.head()

数据快照,图片由作者提供
我们有 2002 年至 2019 年期间美国最大的 46 个城市的信息。面板是平衡的,这意味着我们在所有时间段观察所有城市。自动驾驶汽车于 2013 年推出。
迈阿密的处理过的单元与样本中的其他单元具有可比性吗?让我们使用优步的[causalml](https://causalml.readthedocs.io/)包中的create_table_one函数来生成一个协变量平衡表,包含我们在治疗组和对照组中可观察到的特征的平均值。顾名思义,这应该永远是你在因果推断分析中呈现的第一张表。
from causalml.match import create_table_one
create_table_one(df, 'treated', ['density', 'employment', 'gdp', 'population', 'revenue'])

平衡表,作者图片
不出所料,这两个群体并不均衡:在我们的样本中,迈阿密比美国其他城市人口更密集、更贫穷、更大、就业率更低。
我们有兴趣了解自动驾驶汽车的推出对revenue的影响。
一个最初的想法可能是像我们在 A/B 测试中那样分析数据,比较对照组和治疗组。在引入自动驾驶汽车后,我们可以将治疗效果估计为治疗组和对照组之间在revenue中的均值差异。
smf.ols('revenue ~ treated', data=df[df['post']==True]).fit().summary().tables[1]

回归结果,图片由作者提供
自动驾驶汽车的效果看似负面但不显著。
这里的主要问题是治疗不是由随机分配的。我们有一个单一的治疗单位,迈阿密,它几乎不能与其他城市相比。
另一种方法是在迈阿密市内比较治疗前后的收入。
smf.ols('revenue ~ post', data=df[df['city']==treated_city]).fit().summary().tables[1]

回归结果,图片由作者提供
自动驾驶汽车的效果似乎是积极的,具有统计意义。
然而,这一程序的问题在于,2013 年后可能会有许多其他事情发生。把所有的差异都归结于自动驾驶汽车是相当牵强的。
如果我们画出各城市税收的时间趋势,就能更好地理解这种担忧。首先,我们需要将数据转换成宽格式,每个城市一列,每年一行。
df = df.pivot(index='year', columns='city', values='revenue').reset_index()
现在,让我们画出迈阿密和其他城市的收入随时间变化的曲线图。
cities = [c for c in df.columns if c!='year']
df['Other Cities'] = df[[c for c in cities if c != treated_city]].mean(axis=1)
既然我们正在谈论迈阿密,让我们使用一个适当的调色板。
sns.set_palette(sns.color_palette(['#f14db3', '#0dc3e2', '#443a84']))plot_lines(df, treated_city, 'Other Cities', treatment_year)

正如我们所见,在迈阿密治疗后,收入似乎在增加。但这是一个非常不稳定的时间序列。该国其他地区的收入也在增加。从这个情节来看,很难将这种变化归因于自动驾驶汽车。
我们能做得更好吗?
综合控制
答案是肯定的!当我们有少至一个处理单元和多个控制单元时,综合控制允许我们进行因果推断,并且我们随着时间观察它们。这个想法很简单:将未处理的单元组合起来,使它们尽可能地模拟未处理单元的行为。然后用这个“合成单元”作为对照。该方法首先由 Abadie 和 Gardeazabal (2003) 提出,并被称为“过去几年政策评估文献中最重要的创新”。此外,由于其简单性和可解释性,它在行业中被广泛使用。****
环境
我们假设,对于一组 i.i.d .受试者 i = 1,…,n 随着时间的推移 t=1,…,T ,我们观察到一组变量 (Xᵢₜ,Dᵢ,Yᵢₜ) ,包括
- 一项治疗任务 Dᵢ∈{0,1} (
treated) - 一个回应 Yᵢₜ∈ℝ (
revenue) - 一个特征向量 Xᵢₜ∈ℝⁿ (
population、density、employment和GDP)
此外,在时间(在我们的例子中是 2013 年)处理一个单元(在我们的例子中是迈阿密)。我们区分治疗前的时间段和治疗后的时间段。*
至关重要的是,治疗 D 不是随机分配的,因此治疗组和对照组之间的平均值差异不是平均治疗效果的无偏估计值。
问题是
问题是,像往常一样,我们没有观察到治疗单位的反事实结果,即我们不知道如果他们没有接受治疗会发生什么。这就是所谓的因果推论的基本问题。
最简单的方法就是比较治疗前后的时间。这被称为事件研究方法。
然而,我们可以做得比这更好。事实上,即使治疗不是随机分配的,我们仍然可以进入一些没有接受治疗的单位。
对于结果变量,我们观察以下值

结果变量,按作者分类的图像
其中 Y ⁽ᵈ⁾ₐ,ₜ是在时间 t 的结果,给定治疗分配 a 和治疗状态 d 。我们基本上有一个缺失数据问题,因为我们没有观察到 Y ⁽⁰⁾ₜ,ₚₒₛₜ:在未经处理( a=t )的情况下会发生什么。
解决方案
根据 Doudchenko 和 Inbens (2018) ,我们可以将治疗单位的反事实结果估计值公式化为对照单位观察结果的线性组合。

合成控制结果,图像由作者提供
在哪里
- 常数 α 允许两组之间有不同的平均值
- 重量 βᵢ 允许在控制单元 i 之间变化(否则,这将是差异中的差异)
我们应该如何选择使用哪些砝码?我们希望在治疗前,我们的合成对照尽可能接近结果。第一种方法是将权重定义为

合成控制权重,图片由作者提供
也就是说,权重使得在处理之前,控制单元 Xc 和被处理单元 Xₜ 的可观察特征之间的距离最小化。
您可能会注意到与线性回归非常相似。事实上,我们正在做非常相似的事情。
在线性回归中,我们通常有多个单元(观察值)几个外生特征,和一个内生特征,我们尝试将每个单元的内生特征表达为内生特征的线性组合。

线性回归,作者图片
对于综合控制,我们改为使用多个时间段(特征)、几个控制单元和单个处理单元,并且我们尝试将处理单元表示为每个时间段的控制单元的线性组合。
为了执行相同的操作,我们本质上需要转置数据。

由作者转置数据、图像
交换后,我们计算综合控制权重,就像我们计算回归系数一样。但是,现在一个观测值是一个时间段,一个要素是一个单元。

合成控制回归,图片由作者提供
请注意,这种交换是而不是无辜的。在线性回归中,我们假设外源特征和内源特征之间的关系在单位中是相同的,而在合成控制中,我们假设处理单位和控制单位之间的关系在时间上是相同的。****
回到自动驾驶汽车
现在让我们回到数据上来!首先,我们编写一个synth_predict函数,该函数将一个模型作为输入,该模型在对照城市进行了训练,并试图在无人驾驶汽车引入之前预测被处理城市迈阿密的结果。
让我们通过线性回归估计模型。
from sklearn.linear_model import LinearRegression
coef = synth_predict(df, LinearRegression(), treated_city, treatment_year).coef_
在迈阿密,我们与预无人驾驶汽车revenue的匹配程度如何?自动驾驶汽车的隐含效果是什么?
通过将迈阿密的实际收入与预测收入进行对比,我们可以直观地回答这两个问题。
plot_lines(df, treated_city, f'Synthetic {treated_city}', treatment_year)

观察和合成的迈阿密收入,作者图片
看起来自动驾驶汽车对迈阿密的revenue产生了明显的积极影响:预测趋势低于实际数据,并在自动驾驶汽车推出后立即偏离。
另一方面,我们显然是过拟合:预处理预测revenue线与实际数据完美重叠。鉴于迈阿密revenue的高度可变性,至少可以说这是可疑的。
另一个问题与砝码有关。让我们画出它们。
df_states = pd.DataFrame({'city': [c for c in cities if c!=treated_city], 'ols_coef': coef})
plt.figure(figsize=(10, 9))
sns.barplot(data=df_states, x='ols_coef', y='city');

合成控制权重,图片由作者提供
我们有很多负权重,从因果推理的角度来看没有太大意义。我可以理解迈阿密可以表示为 0.2 圣路易斯,0.15 俄克拉荷马,0.15 哈特福德的组合。但是迈阿密是-0.15 密尔沃基是什么意思?
因为我们想把我们的合成对照解释为未处理状态的加权平均值,所有的权重应该是正的,并且它们的总和应该是 1。
为了解决这两个问题(权重和过度拟合),我们需要对权重施加一些限制。****
限制重量
为了解决超重和负权重的问题, Abadie、Diamond 和 Hainmueller (2010) 提出了以下权重:

权重的约束定义,按作者排列的图像
该公式暗示了一组权重 β ,使得
- 对照组 Xc 的加权可观察特征与治疗前治疗组 Xₜ 的可观察特征相匹配
- 它们总计为 1
- 并且不是负面的。
通过这种方法,我们得到一个可解释的反事实作为未处理单位的加权平均值。
现在让我们写出我们自己的目标函数。我创建了一个新的类SyntheticControl(),它有一个loss函数,如上所述,一个方法来fit它和predict被处理单元的值。
我们现在可以像以前一样重复同样的程序,但是使用SyntheticControl方法代替简单的、不受约束的LinearRegression。
df_states['coef_synth'] = synth_predict(df, SyntheticControl(), treated_city, treatment_year).coef_
plot_lines(df, treated_city, f'Synthetic {treated_city}', treatment_year)

观察和合成的迈阿密收入,作者图片
正如我们所看到的,现在我们不再过度适应了。实际和预测的revenue预处理接近但不相同。原因是非负约束将大多数系数约束为零(正如拉索所做的)。
看起来效果又是负面的。然而,让我们画出两条线之间的差来更好地形象化大小。
plot_difference(df, treated_city, treatment_year)

观察到的迈阿密和合成的迈阿密之间的差异,图片由作者提供
这种差异显然是正的,并且随着时间的推移略有增加。
我们还可以可视化权重来解释估计的反事实(如果没有自动驾驶汽车,迈阿密会发生什么)。
plt.figure(figsize=(10, 9))
sns.barplot(data=df_states, x='coef_synth', y='city');

受约束的权重,按作者排序的图像
正如我们所看到的,现在我们将迈阿密的revenue表示为几个城市的线性组合:坦帕、圣路易斯,以及在较低程度上的拉斯维加斯。这使得整个程序非常透明。
推理
那么推论呢?估计值是否明显不同于零?或者,更实际地说,“在没有政策效应的零假设下,这个估计值有多不寻常?”。
为了回答这个问题,我们将进行一个 随机/排列测试 。想法是,如果政策没有效果,我们在迈阿密观察到的效果不应该与我们在任何其他城市观察到的效果有明显不同。
因此,我们将对所有其他城市重复上述过程,并将它们与迈阿密的估计值进行比较。
fig, ax = plt.subplots()
for city in cities:
synth_predict(df, SyntheticControl(), city, treatment_year)
plot_difference(df, city, treatment_year, vline=False, alpha=0.2, color='C1', lw=3)
plot_difference(df, treated_city, treatment_year)

合成效果的分布,按作者分类的图像
从图表中,我们注意到两件事。首先,对迈阿密的影响非常极端,因此不太可能是由随机噪音造成的。
第二,我们还注意到,有几个城市我们不能很好地拟合前期趋势。特别是,有一条线明显低于所有其他线。这是意料之中的,因为对于每个城市,我们都将反事实趋势构建为所有其他城市的凸组合。就revenue而言非常极端的城市对于构建其他城市的反事实非常有用,但是很难为它们构建反事实。
为了不影响分析,让我们排除那些我们不能建立“足够好”的反事实的州,就治疗前 MSE 而言。

预处理预测均方误差,图片由作者提供
根据经验, Abadie、Diamond 和 Hainmueller (2010) 建议排除预测均方误差大于处理单元均方误差两倍的单元。
# Reference mse
mse_treated = synth_predict(df, SyntheticControl(), treated_city, treatment_year).mse
# Other mse
fig, ax = plt.subplots()
for city in cities:
mse = synth_predict(df, SyntheticControl(), city, treatment_year).mse
if mse < 2 * mse_treated:
plot_difference(df, city, treatment_year, vline=False, alpha=0.2, color='C1', lw=3)
plot_difference(df, treated_city, treatment_year)

没有不可预测单元的合成效果的分布,由作者提供的图像
在排除了极端的观察之后,看起来对迈阿密的影响很不寻常。
Abadie、Diamond 和 Hainmueller (2010) 建议进行随机化检验的一个统计量是治疗前 MSE 和治疗后 MSE 之间的比率。

前后均方预测误差比,图片由作者提供
我们可以将 p 值计算为具有较高比率的观察值的数量。
p-value: 0.04348
似乎只有 4.3%的城市的 MSE 比率大于迈阿密,这意味着 p 值为 0.043。我们可以用直方图形象化排列下统计量的分布。
fig, ax = plt.subplots()
_, bins, _ = plt.hist(lambdas.values(), bins=20, color="C1");
plt.hist([lambdas[treated_city]], bins=bins)
plt.title('Ratio of $MSE_{post}$ and $MSE_{pre}$ across cities');
ax.add_artist(AnnotationBbox(OffsetImage(plt.imread('fig/miami.png'), zoom=0.25), (2.7, 1.7), frameon=False));

城市中前后 MSE 比率的分布,按作者分类的图像。
事实上,迈阿密的统计数据非常极端,表明观察到的效应不太可能是由噪音引起的。
结论
在这篇文章中,我们探索了一种非常流行的因果推断方法,当我们有几个处理单元,但有许多时间段时。当必须在总水平分配治疗并且随机化可能不可行时,这种设置经常出现在行业设置中。综合控制的核心思想是将控制单元组合成一个综合控制单元,作为反事实来估计治疗的因果效应。
合成控制的一个主要优点是,只要我们使用正的权重,这些权重被限制为总和为 1,方法就可以避免外推:我们永远不会脱离数据的支持。此外,合成对照研究可以【预注册】:您可以在研究前指定权重,以避免 p-hacking 和 cherry-picking。这种方法在业内如此流行的另一个原因是,权重使得反事实分析显而易见:人们可以查看权重并理解我们正在进行的比较。
这种方法相对年轻,每年都有许多扩展出现。其中值得注意的有徐(2017) 的广义综合控制、杜先科和伊本斯(2017) 的综合差分、以及阿巴第·埃勒小时(2020) 的惩罚综合控制、以及阿西等人(2021) 的矩阵补全方法。最后但同样重要的是,如果你想让它的发明者来解释这个方法,在 Youtube 上有一个免费的由 Alberto Abadie 在 NBER 夏季学院做的精彩演讲。
参考
[1] A. Abadie,J. Gardeazabal,冲突的经济成本:巴斯克地区的案例研究 (2003),《美国经济评论》。
[2] A. Abadie,A. Diamond,J. Hainmueller,比较案例研究的综合控制方法:估计加州烟草控制计划的效果 (2010),美国统计协会杂志。
[3] A. Abadie,《使用综合控制:可行性、数据要求和方法学方面》 (2021),《经济展望杂志》。
[4] N. Doudchenko,G. Imbens,平衡、回归、差异中的差异和综合控制方法:一个综合 (2017),工作论文。
[5] Y. Xu,广义综合控制方法:具有交互固定效应模型的因果推断 (2018),政治分析。
[6] A. Abadie,J. L'Hour,一种用于分类数据的惩罚合成控制估计量 (2020),美国统计协会杂志。
[7] S. Athey,M. Bayati,N. Doudchenko,G. Imbens,K. Khosravi,因果面板数据模型的矩阵补全方法 (2021),美国统计协会杂志。
相关文章
数据
- 经合组织(2022), 大都市地区数据库 ,最后一次访问日期:2012 年 7 月 30 日
密码
你可以在这里找到 Jupyter 的原始笔记本:
**https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/synth.ipynb **
感谢您的阅读!
我真的很感激!🤗如果你喜欢这个帖子并想看更多,可以考虑 关注我 。我每周发布一次与因果推断和数据分析相关的主题。我尽量让我的帖子简单而精确,总是提供代码、例子和模拟。
还有,一个小小的 免责声明 :我写作是为了学习所以错误是家常便饭,尽管我尽了最大努力。当你发现他们的时候,请告诉我。也很欣赏新话题的建议!
理解 Python 中的 t-SNE
原文:https://towardsdatascience.com/understanding-t-sne-in-python-8a3286fd3ea6
查看邻居并使用 t 分布 SNE 对数据进行分组

由 Clark Van Der Beken 在 Unsplash 拍摄的照片
t-随机邻居嵌入
t-SNE 算法是查看高维数据的一个很好的资源。想想看:可视化二维以上的数据会变得非常具有挑战性。我的意思是超过 2,因为即使现在有很好的 3D 图像,我们的大脑仍然很难理解 3D 图像。毕竟,我们的显示器或屏幕仍然是平面好老 2D,对不对?
所以我们想出了这个好的资源。t 分布随机近邻嵌入。这个花里胡哨的名字是什么意思?
这意味着算法将查看数据集,检查数据点之间的相似性,并将它们转换为联合概率(两个事件在同一时间点同时发生的可能性)。然后,它将尝试最小化低维嵌入和高维数据的联合概率之间的 Kullback-Leibler 散度(两个概率分布之间的差异)。
检查数据点,计算低维和高维数据中事件同时发生的概率。
困惑
如果我们查看文档,困惑是"与在其他流形学习算法中使用的最近邻的数量相关"。它还说“更大的数据集通常需要更大的困惑度”。
这是一个可调参数。该参数可以理解为每个点的近邻数量的估计值。我还看到这里的是计算 t 分布标准差的参数之一。根据文档,考虑选择一个介于 5 和 50 之间的值。
还要考虑用不同的值创建一个测试,因为不同的值会导致明显不同的结果。
请看这个测试,它使用了我为了学习而创建的一个假数据集:

图 1: t-SNE 有不同的困惑值。图片由作者提供。
如果我们强制迭代的数量n_iter=250,那么结果如下。注意,在这种情况下,我们没有看到高复杂度的明确分离,因为该算法需要不同次数的迭代才能收敛到最佳解。

图 2:在 n_iter=250 的情况下,具有不同困惑值的 t-SNE。图片由作者提供。
由于我的数据集很小,您可以看到,在相邻点数量较少的情况下,较小的数字会产生更好的结果。
如果我用 50 个均匀分布的变量创建另一个数据集,并将其呈现给 t-SNE,看看不同perplexity 数的结果。

图 3:高度困惑> 50 不是很有效。图片由作者提供。
在这种情况下,30 之后的perplexity参数开始变得非常随机。正如我们在文档中看到的,它不应该在 50 以上使用,因为结果不会如此精确。
其他考虑
从这篇好文章中,我们可以考虑如何有效地使用 t-SNE的其他注意事项有:
- SNE 霸王龙图中的集群大小没有任何意义:SNE 霸王龙不会关心每个集群的大小。
- 星团之间的距离可能没有任何意义。t-SNE 并不总是能反映出星团之间的距离。记住这一点。
- 随机噪声看起来并不总是随机的:如果你创建一个随机数据集并将其提交给 t-SNE,你仍然可以看到一些低值
perplexity的模式。这并不意味着存在集群。 - 你可以看到一些形状,有时:
perplexity会拨出小值的局部方差或高值的全局方差。这导致了形状的出现。
t-SNE 的结果也许可以用于聚类,因为结果矩阵将是数据的低维版本,因此结果应该是相似的。您可以在本文末尾提供的代码测试中看到,KMeans 使用矩阵或 one hot 编码的高维数据显示了相同的结果。

在高维或低维数据集上使用 KMeans 得到相同的结果。图片由作者提供。
在你走之前
这里展示的是 SNE 霸王龙。使用 T 分布,这个好的算法帮助我们可视化 2D 的高维数据。
您必须找到perplexity和n_iter超参数的微调,以便能够获得数据的最佳分离度和相似度。尝试使用循环和绘图以获得最佳效果。
本练习使用的数据集可以在我的 GitHub 中找到。
参考
https://distill.pub/2016/misread-tsne/ https://scikit-learn.org/stable/modules/generated/sklearn.manifold.TSNE.html https://scikit-learn.org/stable/auto_examples/manifold/plot_t_sne_perplexity.html#sphx-glr-auto-examples-manifold-plot-t-sne-perplexity-py
如果这些内容有用,请关注我的博客。
https://gustavorsantos.medium.com/
考虑使用此推荐代码注册为中级会员。
代码:
理解信息检索中基于术语的检索方法
BM25、TF-IDF、查询似然模型等最常见的基于术语的检索方法背后的直觉。
1。什么是信息检索?

术语信息检索(IR)的含义非常广泛。例如,把你的 ID 从口袋里拿出来,这样你就可以把它输入到文档中,这是一种简单的信息检索形式。虽然信息检索有几种定义,但许多人认为信息检索是关于将人们与信息联系起来的技术。这包括搜索引擎、推荐系统和对话系统等。从学术角度来看,信息检索可能被定义为:
信息检索(IR)是从大型集合(通常存储在计算机上)中寻找满足信息需求的非结构化性质(通常是文本)的材料(通常是文档)。
曼宁等,“信息检索导论”
2。基于术语的检索方法
基于术语的检索方法是基于文档和查询之间的精确句法匹配来定义查询-文档匹配的数学框架,以估计文档与给定搜索查询的相关性。其思想是一对文档和搜索查询由它们包含术语来表示。本文解释了最常见的基于术语的检索方法(如 BM25、TF-IDF、查询似然模型)背后的直觉。
得到💬任何数据科学或编程问题的 GPT 式答案。为成千上万的人生成摘要和学习笔记📚只需一次点击即可获得学习资源。👉
2.1。 TF-IDF
TF-IDF 基于词袋(BOW)模型处理信息检索问题,这可能是最简单的信息检索模型。TF-IDF 包含两部分:TF(词频)和 IDF(逆文档频)。
TF —词频
顾名思义,术语频率是术语 t 在文档 d 中的频率。其思想是,我们根据该术语在文档中的出现次数为文档中的每个术语分配一个权重。因此,文档的得分等于术语频率,这旨在反映单词对文档的重要性。

术语频率。来源:曼宁等《信息检索导论》
在文献中,我们找到了两个常用的术语频率公式,一个是计算术语 t 在文档 d 中出现频率的原始术语频率,另一个是由下面的公式给出的对数术语频率。对数对较大的术语频率值有抑制作用。如果我们看看上表中的术语“安东尼”,它在安东尼和埃及艳后中的出现频率是在朱利叶斯凯撒中的两倍。使用原始术语频率,这意味着安东尼和克利奥帕特拉的相关性是朱利叶斯·凯撒的两倍。

术语频率。来源:曼宁等《信息检索导论》
IDF —逆文档频率
术语频率遇到了一个关键问题:在评估查询的文档相关性时,所有术语都被认为是同等重要的,尽管情况并不总是如此。事实上,某些术语在确定相关性时很少或没有辨别能力(例如,“the”可能在一个文档中出现很多次,但对相关性没有任何贡献)。为此,我们引入了一种机制来减少在集合中频繁出现的对确定相关性没有意义的术语的影响。为了只识别重要的术语,我们可以报告该术语的文档频率(DF ),即该术语出现的文档数量。它说明了集合中术语的独特性。DF 越小,给定项越唯一。

文档频率。来源:曼宁等《信息检索导论》
因此,我们做得更好。但是 DF 有什么问题呢?不幸的是,DF 本身什么也没告诉我们。例如,如果术语“计算机”的 DF 是 100,这是一个罕见的还是常见的术语?我们根本不知道。这就是为什么 DF 需要放在一个语境中,这个语境就是语料库/集合的大小。如果语料库包含 100 个文档,则该术语非常常见,如果包含 1M 个文档,则该术语非常罕见。所以,让我们加上语料库的大小,称为 N,并除以术语的文档频率。

逆文档频率。来源:曼宁等《信息检索导论》
听起来不错吧?但是假设语料库的大小是 1000,包含该术语的文档的数量是 1,那么 N/DF 将是 1000。如果包含该术语的文档数量是 2,那么 N/DF 将是 500。很明显,DF 的一个小变化就能对 N/DF 和 IDF 评分产生非常大的影响。重要的是要记住,我们主要关心 DF 与语料库大小相比何时处于低范围。这是因为如果 DF 非常大,这个术语在所有文档中都很常见,可能与某个特定主题不太相关。在这种情况下,我们希望平滑 N/DF 的变化,一种简单的方法是取 N/DF 的对数。另外,应用日志有助于平衡 TF 和 N/DF 对最终分数的影响。因此,如果该术语出现在集合的所有文档中,DF 将等于 N。Log(N/DF)= log1 = 0,这意味着该术语在确定相关性方面没有任何能力。
我们可以将 TF-IDF 分数定义如下:

TF-IDF。来源:曼宁等《信息检索导论》
2.2 BM25
BM25 是一个概率检索框架,它扩展了 TF-IDF 的思想,改进了 TF-IDF 在术语饱和和文档长度方面的一些缺点。完整的 BM25 公式看起来有点吓人,但是你可能已经注意到 IDF 是 BM25 公式的一部分。让我们把剩下的部分分解成更小的组件,看看为什么有意义。

BM25 配方。来源:曼宁等《信息检索导论》
期限饱和与收益递减
如果一个文档包含 100 次“计算机”这个词,那么它真的是包含 50 次的文档的两倍吗?我们可以说,如果术语“计算机”出现的次数足够多,文档几乎肯定是相关的,任何更多的出现都不会增加相关性的可能性。因此,当一个项可能饱和时,我们希望控制 TF 的贡献。BM25 通过引入控制该饱和曲线形状的参数 k1 来解决这个问题。这使我们可以试验不同的 k1 值,看看哪个值效果最好。
不使用 TF,我们将使用以下公式:(k1+1)* TF / (TF + k1)

那么,这对我们有什么好处呢?它说如果 k1 = 0,那么(k1+1)*TF/TF+ k1 = 1。在这种情况下,BM25 现在变成了 IDF。如果 k 趋于无穷大,BM25 将与 TF-IDF 相同。参数 k1 可以调整为 i 如果 TF 增加,在某个点,BM25 分数将饱和 如下图所示,这意味着 TF 的增加不再对分数有很大贡献。

使用参数 k1 的 TF 和 BM25 饱和曲线。来源:作者图片
文档长度规范化
TF-IDF 中跳过的另一个问题是文档长度。如果一个文档碰巧很短,并且只包含了一次“计算机”,这可能已经是一个很好的相关性指标了。但是如果文档真的很长,并且术语“计算机”只出现一次,则很可能该文档不是关于计算机的。我们希望奖励短文档的术语匹配,惩罚长文档。然而,你不希望过度惩罚,因为有时一个文档很长,因为它包含许多相关信息,而不仅仅是有许多单词。那么,如何才能做到这一点呢?我们将引入另一个参数 b,它用于构造 BM25 公式中的规格化器:(1-b) +b*dl/dlavg。参数 b 的值必须在 0 和 1 之间才能起作用。现在 BM25 分数将如下:

带参数 b 和 k1 的 BM25 公式。来源:曼宁等《信息检索导论》
首先,让我们理解文档的长短意味着什么。同样,文档的长短取决于语料库的上下文。决定的一个方法是使用语料库的平均长度作为参考点。长文档就是比语料库的平均长度长的文档,而短文档则比语料库的平均长度短。这个规格化器为我们做了什么?从公式中可以看出,当 b 为 1 时,规格化器会变成(1–1+1 * dl/dl avg)。另一方面,如果 b 为 0,则整个值变为 1,并且完全不考虑文档长度的影响。
当一个文档的长度(dl)大于平均文档长度时,该文档将受到惩罚并得到较低的分数。参数 b 控制这些文档受到的惩罚有多强:b 的值越高,对大文档的惩罚就越高,因为与平均语料库长度相比,文档长度的影响被放大得更多。另一方面,小于平均值的文档会得到奖励,并给出较高的分数。

使用参数 b 的文档长度和 BM25 分数。来源:作者图像
综上, TF-IDF 奖励词频,惩罚文档频。BM25 超越了这一点,考虑了文档长度和术语频率饱和。
2.3。语言模型
语言建模背后的一个中心思想是,当用户试图产生一个好的搜索查询时,他或她会提出可能出现在相关文档中的术语。换句话说,相关文档是可能包含查询术语的文档。语言建模与其他概率模型的不同之处在于,它为生成概率的每个文档创建一个语言模型,对应于在该文档中找到查询的可能性。这个概率由 P(q|M_d)给出。
语言模型的定义是一个函数,它为给定词汇表的一个单词或一组单词(例如,一个句子的一部分)产生概率。让我们看一个为单个单词产生概率的模型的例子:

语言模型的例子。来源:作者图片
句子“猫喜欢鱼”的概率是 0.3x0.2x0.2 = 0.012,而句子“狗喜欢猫”的概率是 0.1x0.2x0.3 = 0.006 。这意味着“猫喜欢鱼”一词比“狗喜欢猫”更容易出现在文档中。如果我们想要用相同的搜索查询比较不同的文档,我们分别产生每个文档的概率。请记住,每个文档都有其自己的概率不同的语言模型。
解释这些概率的另一种方式是询问这个模型生成句子“猫喜欢鱼”或“狗喜欢猫”的可能性有多大。从技术上讲,你还应该包括概率,即一个句子在每个单词后继续或停止的可能性。这些句子不一定要存在于文档中,也不一定要有意义。例如,在这个语言模型中,句子“猫喜欢鱼”和“猫喜欢鱼”具有相同的概率,换句话说,它们被生成的可能性是相等的。
上例中的语言模型被称为 unigram 语言模型,它是一个独立估计每个术语并忽略上下文的模型。一个包含上下文的语言模型是二元语言模型。该模型包括给定其前面有另一术语的术语的条件概率。“猫喜欢鱼”的概率将由 P(猫)x P(喜欢|猫)x P(喜欢|鱼)给出。这当然需要所有的条件概率都存在。
存在更复杂的模型,但它们不太可能被使用。每个文档创建一个新的语言模型,但是一个文档中的训练数据通常不够大,不足以精确训练一个更复杂的模型。这让人想起偏差-方差权衡。复杂的模型具有很高的方差,并且容易在较小的训练数据上过度拟合。
使用查询似然模型进行匹配
当根据文档与查询的相关程度对文档进行排序时,我们对条件概率 P(d|q)感兴趣。在查询似然模型中,这个概率是所谓的与 P(q|d)等级等价的,因此我们只需要使用上面讨论的概率。为了解释为什么他们是等级相等的 T21,让我们来看看贝叶斯法则:
P(d|q) = P(q|d) P(d) / P(q)
由于 P(q)对于每个文档都有相同的值,所以根本不会影响排名。另一方面,为了简单起见,P(d)被视为是统一的,因此也不会影响排名(例如,在更复杂的模型中,P(d)可以依赖于文档的长度)。因此,概率 P(d|q)等于 P(q|d)。换句话说,在查询可能性模型中,下面两个是等级等价的:
- 文档 d 与查询 q 相关的可能性
- 查询 q 由文档 d 的语言生成的概率。
当用户创建一个查询时,他或她已经有了一个相关文档可能是什么样子的想法。查询中使用的术语更可能出现在相关文档中,而不是不相关文档中。估计一元模型的概率 P(q|d)的一种方法是使用最大似然估计:

查询似然模型。来源:曼宁等《信息检索导论》
其中 tf_t,d 是术语 t 在文档 d 中的术语频率,L_d 是文档 d 的大小。换句话说,计算每个查询词在文档 d 中出现的频率与该文档中所有词的频率之比,然后将所有这些分数彼此相乘。
上面的公式有两个小问题。首先,如果查询中的一个术语没有出现在文档中,整个概率 P(q|d)将为零。换句话说,获得非零概率的唯一方法是查询中的每个术语都出现在文档中。第二个问题是,文档中出现频率较低的术语的概率可能会被高估。
平滑技法
上述问题的解决方案是引入平滑技术,这将有助于为未出现在文档中的术语创建非零概率,并为频繁出现的术语创建有效权重。存在不同的平滑技术,例如 Jelinek-Mercer 平滑,其使用特定于文档和特定于集合的最大似然估计的线性组合:

耶利内克-默瑟平滑。来源:曼宁等《信息检索导论》
或者狄利克雷平滑:

狄利克雷平滑。来源:曼宁等《信息检索导论》
但这是另一篇博文的主题。
总之,传统的基于术语的检索方法实现简单,BM25 等方法是使用最广泛的 信息检索 功能之一,因为其一贯的高检索准确率。然而,他们基于词袋(BoW)表示来处理问题,因此他们只关注精确的句法匹配,因此缺乏对语义相关词的考虑。
理解反向传播算法
原文:https://towardsdatascience.com/understanding-the-backpropagation-algorithm-c7a99d43088b
了解人工智能的支柱

弗洛里安·里德在 Unsplash 上拍摄的照片
反向传播算法是在训练过程中改进神经网络的工具。在该算法的帮助下,单个神经元的参数被修改,使得模型的预测和实际值尽可能快地匹配。这使得神经网络即使在相对较短的训练期后也能提供良好的结果。
这篇文章加入了其他关于机器学习基础的文章。如果你没有任何关于神经网络和梯度下降话题的背景,你应该在继续阅读之前看看链接的帖子。我们将只能简要地触及这些概念。一般来说,机器学习是一个非常数学化的话题。对于反向传播的解释,我们将尽可能避免数学推导,只给出对该方法的基本理解。
神经网络
神经网络由许多组织在不同层中的神经元组成,这些神经元相互通信并相互链接。在输入层,神经元被给予各种输入用于计算。应对网络进行训练,以便输出层(最后一层)根据输入做出尽可能接近实际结果的预测。
所谓的隐藏层用于此目的。这些层还包含许多与前一层和后一层通信的神经元。在训练期间,改变每个神经元的加权参数,使得预测尽可能接近现实。反向传播算法帮助我们决定改变哪个参数,以使损失函数最小。
梯度下降法
梯度法是数学优化问题中的一种算法,它有助于尽可能快地逼近函数的最小值。人们计算函数的导数,即所谓的梯度,并沿着这个向量的相反方向前进,因为函数的下降速度最快。
如果这太数学化了,你可能在山里徒步旅行时熟悉这种方法。你终于爬上了山顶,拍下了必须的照片,充分地欣赏了风景,现在你想尽快回到山谷和家里。所以你寻找从山下到山谷最快的路,也就是函数的最小值。凭直觉,人们会简单地选择最陡的下坡路,因为人们认为这是最快的下坡路。当然,这只是形象地说,因为没有人敢走下山上最陡的悬崖。
梯度法对函数也是如此。我们在函数图中的某处,试图找到函数的最小值。与山的例子相反,在这种情况下,我们只有两种移动的可能性。在正或负的 x 方向上(具有一个以上的变量,即多维空间,当然相应地有更多的方向)。梯度帮助我们知道它的负方向是最陡函数下降。
机器学习中的梯度下降
机器学习中让我们感兴趣的函数是损失函数。它测量神经网络的预测和训练数据点的实际结果之间的差异。我们还想最小化这个函数,因为这样我们就会有一个 0 的差值。这意味着我们的模型可以准确地预测数据集的结果。达到目标的调节螺丝是神经元的权重,我们可以改变它来更接近目标。
简而言之:在训练过程中,我们得到了损失函数,我们试图找到它的最小值。为了这个目的,我们在每次重复后计算函数的梯度,并朝着函数最陡下降的方向前进。不幸的是,我们还不知道我们必须改变哪个参数,以及改变多少来最小化损失函数。这里的问题是梯度程序只能对前一层及其参数执行。然而,深度神经网络由许多不同的隐藏层组成,这些隐藏层的参数理论上都可以对整体误差负责。
因此,在大型网络中,我们还需要确定每个参数对最终误差的影响有多大,以及我们需要在多大程度上修改它们。这是反向传播的第二个支柱,这个名字由此而来。
反向传播算法
我们会尽量让这个帖子非数学化,但不幸的是,没有它我们完全做不到。我们的网络中的每一层都由来自前一层或来自训练数据集的输入和传递到下一层的输出来定义。输入和输出之间的差异来自神经元的权重和激活函数。
问题是,一个层对最终误差的影响还取决于下面的层如何传递这个误差。即使神经元“计算错误”,如果下一层中的神经元简单地忽略该输入,即将其权重设置为 0,这也不是很重要。这通过一层的梯度也包含下一层的参数的事实在数学上示出。因此,我们必须在最后一层,即输出层开始反向传播,然后使用梯度方法逐层向前优化参数。
这就是反向传播或误差反向传播这一名称的由来,因为我们从后面通过网络传播误差并优化参数。
这是你应该带走的东西
- 反向传播是一种训练神经网络的算法。
- 其中,它是梯度法在网络损失函数中的应用。
- 这个名字来源于这样一个事实,即误差从模型的末端一层一层地向后传播。
如果你喜欢我的作品,请在这里订阅https://medium.com/subscribe/@niklas_lang或者查看我的网站* 数据大本营 !还有,medium 允许你每月免费阅读 3 篇 。如果你希望有无限制的 访问我的文章和数以千计的精彩文章,不要犹豫,点击我的推荐链接:【https://medium.com/@niklas_lang/membership】每月花$5***获得会员资格**
* </8-machine-learning-algorithms-everyone-new-to-data-science-should-know-772bd0f1eca1> *
理解 Sigmoid 函数的导数
原文:https://towardsdatascience.com/understanding-the-derivative-of-the-sigmoid-function-cbfd46fb3716
如何找到神经网络的 Sigmoid 函数的导数—简单的逐步演练

我最近在 Medium 上读到了一篇很棒的帖子,向我展示了如何从头开始实现自己的神经网络。这对我理解机器学习,尤其是神经网络的内部工作是有价值的。
我学习实现的网络使用了 Sigmoid 函数。然而,那篇文章并没有详细讨论 Sigmoid 函数的数学原理。这让我挠头,作为一个想了解我所使用的工具的一切的人,我不得不钻研数学,找出发生了什么。
如果你也对 Sigmoid 函数及其在神经网络中的应用感到好奇,那么请继续阅读。在这篇文章中,我将带你一步一步地了解它,并试图让它尽可能容易理解。
这篇文章的主要内容是如何找到 Sigmoid 函数的导数,到最后你也能理解它了!
如何在神经网络中使用 Sigmoid 函数
首先,Sigmoid 函数接受一个数字作为输入,并返回一个介于 0 和 1 之间的新值。

Wikipedia.com 的 Sigmoid 函数图
正如您在上面的图表中看到的,x 值为 0 将返回 y 值为 0.5,更大的正 x 值将 y 值向 1 移动,最终更大的负 x 值将导致 y 值更接近 0。
Sigmoid 函数通常用作神经网络各层中的激活函数。简而言之,这意味着它决定一个节点是否应该被激活,从而决定该节点是否应该对网络的计算做出贡献。
为什么 Sigmoid 函数在神经网络中如此重要
神经网络的训练阶段由两部分组成。
- 前馈
- 反向传播
前馈是数据通过神经网络从输入节点流向输出节点的过程。换句话说,这就是网络试图做出预测的地方。
然后,将这些预测值与实际值进行比较,对其进行评估。误差可以通过两个值之间的差值来确定。
error = actual_value - predicted_value
反向传播接着发生,这是神经网络中“学习”或“调整”发生的地方。现在,误差值以与之前相反的方向流回网络,然后与 Sigmoid 函数的导数结合使用,以调整网络各层中所有节点的权重。
使用 Sigmoid 函数的导数是因为它允许我们通过梯度下降进行调整。函数的导数会给我们函数描述的图形的角度/斜率。这个值将让网络知道是否要增加或减少网络各层中各个权重的值。在反向传播过程中,激活函数的导数要计算很多次,这就是 Sigmoid 函数真正的亮点。
Sigmoid 函数看起来像这样

Sigmoid 函数
Sigmoid 函数的导数就是

Sigmoid 函数的导数
换句话说,Sigmoid 函数的导数是 Sigmoid 函数本身乘以 1 减去 Sigmoid 函数。
酷的事情是,在反向传播期间,我们已经计算了在前馈步骤期间 Sigmoid 函数的导数的所有部分,因此没有什么新的要计算。相反,我们可以简单地将 Sigmoid 函数的结果应用于导数方程的两个点。
这使得神经网络不必进行大量的计算,否则在使用其他激活函数时就需要进行大量的计算。能够避免如此多的额外计算是 Sigmoid 函数对神经网络非常有用的原因之一。
现在到了我很难理解的部分,为什么 Sigmoid 函数的导数是 Sigmoid 函数本身乘以 1 减去 Sigmoid 函数。
正如介绍中所解释的,我发现要准确理解 Sigmoid 函数的导数是如何找到的有点困难,因此在下一节中,我将尝试遍历所有的数学步骤,以便您也可以学习它!
Sigmoid 函数导数背后的数学
为了获得 Sigmoid 函数的导数,我们需要两条规则,链式规则,以及商规则。
我们将一步一步地进行计算,我会指出何时应用规则。

Sigmoid 函数
因为 Sigmoid 函数是一个分数,分子和分母都是可微的,所以我们可以使用商法则来计算整个函数的导数。
商法则说,我们可以用下面的方法得到一个函数的导数:

商数法则
den 是分母的简称,num 是分子的简称。
取 Sigmoid 函数的分子和分母,以及分子的导数和分母的导数,并将它们放入上述函数中,我们得到:

Sigmoid 函数的商规则
分子的导数变成 0,因为分子是常数。
分母的导数变成:

Sigmoid 函数分母的导数
这就是使用链式规则的地方,我认为这一步可能需要更多的解释和详细的演练。
链式法则告诉我们,复合函数(内部包含另一个函数的函数)的导数按以下方式计算:

合成函数的一个例子

使用链式法则的合成函数的导数
换句话说,这可以理解为:合成函数的导数是包含内部函数 g 的外部函数 f 的导数乘以内部函数的导数。
既然这样

被认为是外部函数(y 代表内部函数)。那么内部函数是-x。当对 Sigmoid 函数的分母应用链式法则时,我们得到以下等式:

应用于 Sigmoid 函数分母的链式法则
如果我们从寻找外部函数的导数开始,那么 1 变成 0,并且 e 对某物的幂的导数保持不变,如下式所示。

e 对某物的幂的导数也是一样的
此时我们有了外函数的导数,下一步是求内函数的导数。内部函数就是-x,它的导数变成了-1。
将所有这些放在一起,我们得到:

应用于 Sigmoid 函数分母的链式法则
这个表达式可以简化为:

Sigmoid 函数分母的导数
这是第一个困难的部分,现在我们可以回到商法则,理解我们如何得到分母的导数。
请记住,这是我们停止的地方:

商数法则

Sigmoid 函数的商规则
我们现在可以通过去掉乘以 0 的部分来简化上面的表达式。这给了我们以下内容:

Sigmoid 函数第一次约化后的商规则
接下来,我们可以将分子中的两个括号相乘,得到:

Sigmoid 函数第二次约化后的商规则
我们现在已经非常接近了,但是函数还没有达到预期的形式。为了达到这个目的,我们首先需要使用一个小技巧,在分子中加减 1。这样做对等式的结果没有实际影响,但是它使下面的步骤成为可能。像这样:

从分子中加减 1
这样,我们可以将函数分成两个独立的部分,如下所示:

将函数分成两部分
通过这样表示函数,我们能够在第一个分数中抵消e^-x。从而给我们提供了以下内容:

抵消了左分数中的 e^-x
现在可以将它重写为

重写了上一步
我认为这是很难立即理解的步骤之一,但是如果你试图通过将这两个分数相乘来逆向计算,你应该会得到上一步的结果。如果您首先将最左边的分数与右边括号中的值 1 相乘,您将得到左边的分数本身(对应于前面等式中的第一个分数)。接下来,将左分数乘以圆括号中的负分数,从而得到分母为平方的结果(上一个等式的第二个分数)。
现在到了最后一步。如果你看看我们刚刚得出的方程,你可能会发现它实际上包括了 Sigmoid 函数本身。提醒一下,看起来像这样:

Sigmoid 函数
因此,我们可以将 Sigmoid 函数换成上面的等式,这使我们得到所需的形式:

Sigmoid 函数的导数
就是这样!这就是为什么 Sigmoid 函数的导数就是 Sigmoid 函数本身乘以 1 减去 Sigmoid 函数背后的数学原理。
恭喜你完成了所有这些方程。

莱昂纳多和我祝贺你——GIF
摘要
你现在已经学会了如何求 Sigmoid 函数的导数。这样做的全部原因是因为它在神经网络的反向传播步骤中使用,以便向上或向下调整权重。由于在前馈步骤中已经找到了 Sigmoid 函数的导数所需的所有方程,这为我们节省了大量计算,并且这是使用 Sigmoid 函数作为神经网络层中的激活函数的好处之一。
我希望这篇文章对你有所帮助,并且现在已经很清楚为什么 Sigmoid 函数的导数是这样的了。
如果您喜欢您所阅读的内容,那么如果您能支持我,使用我下面的推荐链接注册 Medium,我将不胜感激:
https://jacobtoftgaardrasmussen.medium.com/membership
如果你对我在介绍中提到的展示如何从零开始制作神经网络的帖子感到好奇,那么你可以使用下面的下一个链接来查看:
理解回归和分类之间的区别
对于初学者来说,这是一个非常棘手的问题

由 Unsplash 上的 Element5 数码拍摄
我的职业生涯之一是作为兼职教授教授本科生数据科学。以这种身份,在预测分析(这是对数据科学所有意图和目的的基本概述)中,我在学生中发现了一个一致且令人惊讶的模式:
他们努力理解回归和分类问题之间的区别。我认为这是我作为一名教师的错误,所以我想尽我所能,以尽可能简短的方式澄清这些差异。
基础
对于不熟悉回归这个词的人来说,它并不意味着什么。一开始谈论回归有点像告诉某人杂货店在警察局以南两个街区。
但是一个小小的定义可以让它变得有意义。回归从字面上看就是一个因变量和一个或多个自变量之间的关系(哦,所以警察局离公园一个街区)。更实际的是,它表示一个连续数值变量和一个或多个其他数值变量之间的关系强度。
那么,杂货店就在前面两个街区的地方…
连续数值变量是一组不可数数值的一部分。考虑一只股票的价格。实际上,有一个最小值/最大值,在这个最小值/最大值之间可以有无数个值。将这个定义与离散数值变量相对照,离散数值变量是有限的或者可数数值集合的一部分。
定义的回归问题
回归预测一个连续的数值变量。我们用回归预测的一些例子:
- 一升汽油的价格
- 道琼斯工业指数的价值
- 钻石的价格
- Q1 2022 年的收入
有许多与回归相关的算法。它们通常以“回归者”或类似的词结尾。以下是 scikit-learn 中的一些算法:
- sklearn.linear_model。线性回归
- sk learn . linear _ model . ridge _ regression
- sklearn.linear_model。ARDRegression
- sklearn.linear_model。套索
如你所料,输出或预测(到目前为止)是一个连续的数字——就像石油价格一样,给出一堆与石油价格相关的数据。
回归度量
在 ML 中,术语“模型准确性”是一个重载的术语(像许多其他术语一样),它有字面上和概念上的定义。当我谈到分类问题时,我会进一步说明,“准确性”是一个等式:
(总正面预测+总负面预测)/总预测
这可以简化为总正确预测/总预测
在回归问题的背景下考虑这个等式。你可能不知道的是,准确猜出一个连续数字的可能性相当低(即使有非常好的 ML)。对于数字来说,足够接近通常就足够好了。因此,准确性度量几乎没有意义,因为无论模型预测得多好,准确性分数都可能非常低。
相反,我们依赖于告诉我们预测与实际值有多接近的度量标准(与准确性度量标准相反,准确性度量标准告诉我们有多少次我们得到了正确或错误的预测)。
我们使用的一些指标是 R2 和 MSE。Scikit-learn 有一个很棒的页面,显示了分类、聚类和回归的评估指标。例如,R2,“……是从自变量中可预测的因变量变化的比例。术语“变化的比例”更符合连续变量。
MSE 让这一点变得更加清楚(就像这样的事情可以被弄清楚);它代表均方误差,是实际值和预测值之差的平方的平均值。
好吧,让我换个说法。对于预测值列表中的每个预测值,首先计算该值与该观察值(或电子表格中的行)的实际值之间的差异;这是绝对的错误。然后对每个差求平方。然后取所有这些平方差的平均值。
清澈如泥。
MSE 越低,预测越好,因为预测值和实际值之间的差异(通过平方值来消除负号)越低。这对于一个连续的数来说几乎是直观的。
定义的分类问题
分类问题比回归问题更快地切入正题,预测一个分类目标。分类变量就像一个下拉列表框,包含可供选择的值列表。这些值可以是数字或文本,尽管它们通常以文本开头。
一些例子:
- 一周中的几天
- 衬衫尺码(小号、中号、大号)
- 对或错(是或否)——这是一个非常特殊的情况
分类算法通常以“分类器”结尾(但不总是如此),例如:
- 决策树分类器
- 近邻分类器
- 随机森林分类器
- AdaBoost 分类器
来自这些算法的结果(即预测)是一个值,即分类变量的可能值列表中的一个值。这与目标是连续数字的回归问题有着本质的不同。
我认为,真正的困惑是,每个目标值都是一个数字,因为在数据科学过程中,我们将文本转换为数字。例如,true/false 转换为 1 和 0,小号/中号/大号(针对衬衫尺码)转换为 0、1 和 2。
但是记住,在一个回归问题中,目标是一个连续的数。对于分类,目标最初是一个分类变量,然后被转换成一个离散的数字。如果你忘记了这篇文章中的其他内容,请记住这一点。
分类指标
回到我写的关于度量的文章,尤其是准确性,这对于分类问题更有意义。算法预测有限选项列表中的一个。不管是对是错,都不算“接近”
再次关注准确性:
正确预测总数/预测总数
这个数字越大越好,因为算法预测正确的次数越多。如果预测“接近”实际值,就没有价值或奖励。
这给我们带来了一个分类评估的概念(很恰当地),叫做混淆矩阵。矩阵的混乱对于它自己的文章来说是足够的材料,但简而言之,混乱矩阵帮助我们理解我们猜对的频率和猜错的频率。注意:不要一字不差地记住混淆矩阵,因为事实上它里面的所有东西都可以重新排列,而不会丢失相同的信息(就像我说的,这很混乱)。

来源:维基百科
然而,这个想法带来了类似 精度和召回 的概念。虽然我不会在这里深入讨论这些概念,但重点是这些指标与回归有很大的不同,因为分类指标关注的是对与错,而回归关注的是实际值和预测值之间的差异。
MSE 或 R2 对分类目标有意义吗?这是因为实际值和预测值之间没有差别(从概念上来说)。以衬衫尺寸预测为例,假设我们有以下结果:

图片作者。
您可以看到 size 列从 small/medium/large 转换为值 0、1 和 2。该算法得到了 4 个正确的和 2 个错误的。准确率为 66.7% (4/6)。说预测的和实际的衬衫尺码相差 1(或者别的什么)真的没有意义。
分类度量关注对与错,而回归关注实际与预测之间的差异。
非常混乱的分类
所以现在清楚了,让我把水搅浑。一个非常特殊的情况是当你有一个二元分类问题(例如,真/假,是/否)。在它是什么(二进制分类变量的分类)或我们用来评估它的度量标准方面,它并不令人困惑。
令人困惑的是,这类分类问题最常见的算法叫做 LogisticRegression 。我是说,还有比这更让人困惑的吗?
虽然在这种情况下严格来说会令人困惑,但在机器学习之外进行逻辑回归时就不会困惑了。这里有一篇文章很好地阐述了这种细微差别。
当考虑二进制分类问题时,只需忽略算法的“回归”部分,就此打住。这仍然是一个分类问题,您仍然使用分类问题模型度量来评估它。
既然我们知道去杂货店的路,你能告诉我去加油站的路吗?
参考
[1]维基百科。(2021 年 12 月 11 日。)连续或离散变量。https://en . Wikipedia . org/wiki/Continuous _ or _ discrete _ variable。
[2] Scikit-learn。Scikit-learn 网站。https://scikit-learn.org/stable/。
[3] Scikit-learn。(未注明)量化预测质量的指标和评分。https://scikit-learn . org/stable/modules/model _ evaluation . html。
[4]维基百科。(2021 年 10 月 18 日。)精度和召回。https://en.wikipedia.org/wiki/Precision_and_recall。
[5]维基百科。(2021 年 12 月 3 日。)混淆矩阵。https://en.wikipedia.org/wiki/Precision_and_recall。
了解固定效应回归模型
原文:https://towardsdatascience.com/understanding-the-fixed-effects-regression-model-d2fccc2cc27e
以及关于如何在真实面板数据集上构建和训练固定效果模型的 Python 教程
固定效应回归模型用于估计面板数据集中个体内在特征的效应。这种内在特征的例子有遗传、敏锐和文化因素。这些因素无法直接观察或测量,但需要找到一种方法来估计它们的影响,因为忽略它们会导致训练的回归模型不理想。固定效应模型就是为了解决这个问题而设计的。
本文是面板数据分析系列文章的第二部分:
- 如何为面板数据建立混合 OLS 回归模型
- 了解固定效应回归模型
- 随机效应回归模型的实用指南
面板数据入门
面板数据集包含在特定数量的时间段内为一个或多个唯一可识别的单元收集的数据。单位的例子有动物、人、树、湖、公司和国家。数据面板被称为平衡或不平衡面板,这取决于是否所有单元都被跟踪了相同数量的时间段。如果在整个研究过程中跟踪同一组单位,它被称为固定面板,但是如果在研究过程中单位发生变化,它被称为旋转面板。
面板数据集通常来自纵向研究。弗雷明汉心脏研究可能是自 1948 年以来最著名的纵向研究的例子。
在本文中,我们将研究一个真实世界的面板数据集,其中包含 7 个国家从 1992 年到 2014 年的人均 GDP 年增长率。除了 GDP 增长数据,该面板还包含每个国家总资本形成的同比增长:

a 小组数据集(来源:世界发展指标数据 CC BY 4.0 license )(图片由作者提供)
上述数据集中,单位为国家,时间框架为 1992 年至 2014 年( 23 个时间段,面板数据为固定和平衡。
属于一个单位(一个国家)的一组数据点称为组。在上面的数据面板中,有七组。
假设我们希望调查总资本形成年同比增长对 GDP 年同比增长的影响。
我们的因变量或响应变量 y 是 Y-o-Y %的人均 GDP 增长率。自变量或解释变量 X 为年-年资本形成总额增长率。
在符号形式中,人均国内生产总值的年同比增长率可以表示为总资本形成年同比增长率的函数,如下所示:

在时间周期 t,国家 I 的 GDP 增长是国家 I 在时间周期 t 的总资本形成增长的函数
在上面的回归方程中, ϵ_i_t 是回归的残差,它捕捉到了 i 国家在 t 年期间人均 GDP 同比增长的方差,这是模型无法“解释”的。
让我们创建一个 y 对 X 的散点图,看看数据是什么样子的。
我们将从导入所有必需的 Python 包开始,包括我们稍后将用来构建固定效果模型的包。
**import** pandas **as** pd
**import** scipy.stats **as** st
**import** statsmodels.api **as** sm
**import** statsmodels.formula.api **as** smf
**from** matplotlib **import** pyplot **as** plt
**import** seaborn **as** sns
让我们将数据集加载到一个熊猫数据框中。该数据集可从这里 下载 。
df_panel = pd.**read_csv**('wb_data_panel_2ind_7units_1992_2014.csv', **header**=0)
我们将使用 Seaborn 来绘制所有时间段和所有国家的人均 GDP 增长与每个国家总资本形成增长的关系图:
colors = [**'blue'**, **'red'**, **'orange'**, **'lime'**, **'yellow'**, **'cyan'**, **'violet'**]sns.**scatterplot**(**x**=df_panel[**'**GCF_GWTH_PCNT**'**],
**y**=df_panel[**'**GDP_PCAP_GWTH_PCNT**'**],
**hue**=df_panel[**'**COUNTRY**'**],
**palette**=colors).
**set**(**title**='Y-o-Y % Change in per-capita GDP versus Y-o-Y % Change in Gross capital formation')plt.**show**()
我们看到下面的情节:

国内生产总值同比增长%与总资本形成同比增长%的国家散点图(图片由作者提供)
人均国内生产总值的同比增长似乎与总资本形成的同比增长呈线性相关,因此,我们将为每个单位(国家)的回归模型假定以下线性函数形式 i :

国家 I 的线性模型(图片由作者提供)
上式中,所有变量都是某维的矩阵。假设 n 个单位, k 个回归变量每单位, T 个时间段每单位,上式中每个矩阵变量的维数如下:
- y _i 为单位 i 的响应变量(人均 GDP 增长)。它是一个大小为【T×1】的列向量。
- X_ I是大小【T X k】的回归变量矩阵。
- β_ I是大小为【k×1】的系数矩阵,包含×_ I .中 k 回归变量系数的总体值****
- ϵ_ I是大小为【t×1】的列向量,包含误差项,每个 T 时间段一个误差。
以下是单元 i 的上述方程的矩阵形式:

国家 I 的线性回归模型的矩阵形式(图片由作者提供)
在我们的例子中, T=23,k=1 和 n=7 。
让我们把注意力集中在模型的误差项上,**_ I。以下是错误的重要来源:
- 由于随机环境噪声或测量仪器引入误差。M 测量误差由实验者引入,因为他们不正确地使用测量仪器。
- 由于省略了可观察和可测量的解释变量而引入了误差。这些变量本来能够“解释”响应变量y 中的一些方差,因此,它们从矩阵中的省略会导致无法解释的方差“泄漏”到回归模型的误差项中。**
- 由于不正确的函数形式或缺少某些回归变量或响应变量的变量转换而引入误差。例如,假设我们需要回归总资本形成变化的 GDP 变化的对数,但我们未能记录转换响应变量。**
- 总有可能我们对回归模型的选择是错误的。例如,如果正确的模型碰巧是非线性最小二乘模型,但我们使用 OLS 线性回归模型,这将导致额外的回归误差。**
- 最后,由于遗漏了不可测*T5 的变量,会引入误差。这些变量代表了被测单位的内在质量。对于以国家为单位的“我们的国家”数据面板,特定单位变量的一个示例可以是在不同环境条件下促进或抑制 GDP 增长的国家的社会经济结构,以及该国数百年来演变的企业和政府决策的文化方面。所有这些因素都会影响 GDP 的同比变化,但无法直接测量。然而,从回归矩阵 X 中省略这些因素具有与(2)中相同的效果,即,它们的效果泄漏到在误差项中观察到的附加方差中。*****
记住上面的注释,我们可以将国家 i 的线性回归模型的一般形式表示如下:

国家 I 的线性模型的一般形式(图片由作者提供)
在上面的等式中:
- ***y*_ I是一个大小为【T×1】的矩阵,包含国家 i 的 T 观察值。
- ***X*_ I是一个大小为【T X k】的矩阵,包含 k 回归变量的值,所有这些值都是可观察的和相关的。
- ***β*_ I是一个大小为【k x 1】的矩阵,包含 k 回归变量的回归系数的总体(真)值。
- ***Z*_ I是一个大小为【T x m】的矩阵,包含了所有变量的(理论)值(数量为 m )和无法直接观察到的效应。
- ***γ*_ I是一个大小为【m x 1】的矩阵,包含 m 个不可观察变量的回归系数的(理论)总体值。
- ***ε*_ I是一个大小为【T×1】的矩阵,包含与国家 i 的 T 观测值相对应的误差。
下面是矩阵乘法和加法的样子:

矩阵格式的国家 I 线性模型的一般形式(图片由作者提供)
所有特定于单元的效应都被假设为由术语Z_ Iγ_ I .矩阵Z_ I及其系数向量γ**_ I是纯理论术语,因为它们所代表的内容实际上无法观察和测量。**
我们的目标是找到一种方法来估计_ I对y_ Iγ_ I项对的影响****
为了简化估算,我们将把所有国家特有的不可观测效应的影响合并到一个变量中,对于国家 i ,我们称之为z_ I。z_ I是一个大小为【T×1】的矩阵,因为它只包含一个变量 z_i 并且它有 T 行对应于Tz _ I 对 T 的“测量”数**
由于z**_ I不是直接可观测的,为了衡量 z_i 的效果,我们需要将省略 z_i 的效果形式化。幸运的是,在统计学中有一个被充分研究过的概念叫做省略变量偏差,我们可以用它来达到这个目的。**
省略可变偏差
在面板数据集上训练模型时,如果我们从模型中遗漏掉_ I,就会造成所谓的遗漏变量偏差。可以看出,如果在不考虑 z_i 的情况下估计回归模型,那么系数**_ I的估计值β_ cap _ I会有如下偏差:****

省略变量偏差:由于变量 z _i(图片由作者提供)的省略而在 β _i 的估计中引入的偏差
可以看出,估计值β_ cap _ I中引入的偏差与省略变量z_ I和解释变量X**_ I之间的协方差成正比。**
上述等式建议了一种方法,用于根据上述等式中的协方差项是否为零,即不可观察效应 z_i 是否与回归变量相关,来构建以下两种模型—固定效应模型和 随机效应模型 。
在本文的其余部分,我们将关注固定效果模型,而在我下周的文章中,我将解释如何构建和训练随机效果模型。
固定效应回归模型
在这个模型中,我们假设不可观察的个体效应 z_i 与回归变量相关。实际上就是说上式中的协方差(X_ I、z_ I)非零。
在许多面板数据研究中,这种关于相关性的假设是合理的。例如,在股票交易场景中,交易者的交易敏锐度或获利的“诀窍”是不可测量的,并且是个人独有的。这种敏锐或诀窍可以被认为随着可测量的因素如年龄和教育水平而变化。有人可能会(正确或错误地)提出,获得高等学位的过程增强了一个人在执行某些任务时的内在敏锐度或诀窍。
在固定效应模型中,我们还假设由于忽略单位特定因素而引入的偏差是群体特定的。****
为了补偿这种偏差,我们将在模型中引入一个特定于组的截距,称为c_ I。** 假设 c_i 的作用方向与省略变量偏差的作用方向相反(在矢量意义上)。**
有了这两个假设,我们将固定效应回归模型的等式表示如下:

固定效应回归模型(图片由作者提供)
下面是矩阵形式:

固定效应回归模型(图片由作者提供)
请注意,我们已经用代表不可观察因素影响的c_ I替换了前面等式中的z_ Iγ_ I项,这是一个大小为[T×1】的单位特定矩阵。对于给定的单位 i ,该矩阵的每个元素具有相同的值 c_i 并且 c_i 被假定为在所有时间段都是恒定的。**
对于特定时间段 t ,固定效果模型的等式可以表示如下:

时间周期 t 时单位 I 的固定效应回归模型(图片由作者提供)
这是矩阵形式:

时间周期 t 时单位 I 的固定效应回归模型(图片由作者提供)
在这种形式中, y_i_t 、 c_i 和 ϵ_i_t 是标量,因为它们属于在时间 t 的特定观察,并且x**_ I _ t是在 中大小为【1 x k】的第 t 个行向量****
单位效应 c_i 的估计 c_cap_i 是随机变量
请注意, c_i 不携带时间下标 t ,因为它对于给定国家的所有时间段 T 都是相同的。说到这里,国别效应 c_i 的估计值 c_cap_i 和估计系数矩阵**_ cap _ I中的任何一个系数一样,都是一个随机变量。要了解原因,想象一下固定效应模型被训练了数百次,每次都是在面板数据集的一个不同的、随机选择的(但连续的)子集上。在每次训练运行之后,所有估计的系数β_ cap_ I和估计的单位特定效应 c_cap_i 将获得稍微不同的一组值。如果我们从不同的训练运行中绘制所有这些 c_i 的估计值,它们的频率分布将具有某种形状,具有某个平均值和某个方差。例如,我们可以推断它们正态分布在I和 c_i 中各个系数的真实总体水平值周围。因此,估计的单位特定影响 c_cap_i 表现得像具有某种概率分布的随机变量。
在固定效应模型中,我们假设所有单位特定效应的估计值具有相同的恒定方差 σ。假设正态分布 c_cap_i 也很方便(尽管不是必需的)因此,我们有:
c_cap_i ~ N(c_i,σ )
下图说明了假设面板数据集中三个单位的 c_i 的概率分布:

面板数据集中三个不同单位的单位特异性效应 c_i 的概率分布(图片由作者提供)
如果还有一些可观测变量被省略了呢?
实际中, X 矩阵往往是不完整的。出于各种原因,人们可能从模型中遗漏了一个或多个可观察变量。也许测量一个变量 w.r.t .的成本及其对 y 的假定影响是令人望而却步的。也许不测量某些变量有道德上的原因。或者一个变量可能被遗漏掉了, X 仅仅是因为实验者的疏忽。
在这种情况下,它们的省略将使拟合模型的所有参数估计产生偏差,包括所有单元的单元特定因子 c_i 的估计值。
估计固定效应回归模型
固定效应模型的估计包括估计系数【β_**I和每个单元 i 的单元特定效应 c_i 。**
在实践中,我们通过添加对应于 n 个单元或组的单元特定虚拟变量 d_1,d_2,…,d_n ,将所有单元的模型汇集成一个通用回归模型,如下所示:

包含虚拟变量的固定效应模型(图片由作者提供)
在上面的等式中:
- y_i_t 是一个标量,包含在时间 t. 对单元(国家) i 的特定观察
- ***x*_ I _ t是一个大小为【1 x k】的行向量,包含时间 t 时单元 i 的所有 k 回归变量的值。
- ***β*_ I是【k x 1】的列向量,包含 k 回归变量的回归系数的总体(真)值。
- *d_ I _ t是一个大小为【1 x n】的行向量,包含一个热编码的虚拟变量 d_i_j_t ,其中 j 从 1 到 n —数据面板中的每个 n 单元一个虚拟变量。例如:d***=[0 1 0 0 0 0 0]是单元#2 的虚拟矢量。其思想是当 j=i 时,虚拟向量的第 j 个元素应该是 1,否则应该是 0。
- ***c*_ I是一个大小为【n×1】的列向量,包含与 n 个单位相关联的单位特定效果的总体值。
- ϵ_i_t 是一个标量,包含单元 i 在时间 t 的回归误差项。

用矩阵符号表示的固定效果模型(图片由作者提供)
上述模型是一个线性模型,可以很容易地使用 OLS 回归技术进行估计。这种带有虚拟变量的线性回归模型称为带有虚拟变量的最小二乘(简称 LSDV )。
模型培训包括以下内容:
- 将单位特定矩阵y_ I,X** _i ,β_ I,d_ I,c_ I**
- 训练合并的模型以生成对应于 k 回归变量的【k×1】大小的系数向量的估计,以及数据中包含的 n 单位的【n×1】大小的单位特定效应向量*的估计*******
公共系数假设
在汇集模型中,我们做了一个隐含的重要假设,即估计系数β_ cap 对于所有 n 个单位是共同的。 Chow 测试可以用来测试这个假设(虽然我们在这里不会深入探讨)。**
对于世界银行国家数据面板,可集合性假设的意思是,每个国家总资本形成变化(GCF_GWTH_PCNT)的斜率( β ) 的人口值是相同的。换句话说,GCF_GWTH_PCNT 的单位变化预计会转化为每个国家 GDP 的相同变化量。因此,正是国别效应 c_i 和误差项 ϵ_i_t 有可能导致不同国家的总 GDP 百分比变化因 GCF_GWTH_PCNT 中的每个单位变化而不同。
这种行为是共同系数假设的直接结果,它恰好是固定效应模型的一个重要但不明显的特征。
在我们深入本文的教程部分之前,这里是关于 FE 模型需要记住的最后一件事:
通过训练固定效应回归模型生成的估计值仅适用于面板数据集中的单位。来自固定效应模型的估计值不会将推广到群体中具有相同性质的其他单位。
这对国家数据面板来说意味着 β 和 c_i 的估计值仅适用于数据面板中的 7 个国家。人们不应该通过训练数据集上的有限元模型来概括特定国家的影响 c_cap_i ,以任何方式表示数据集中没有表示的任何国家的特定国家影响。
如果我们想让特定单位的效应延续到相似单位的群体,随机效应模型(下周讨论)可能更合适。
如何使用 Python 和 Statsmodels 构建固定效应回归模型
让我们为世界银行数据面板建立和训练一个固定效应模型。
我们将继续使用文章开头的熊猫数据框架。我们将在展平的面板数据集上构建和训练有限元模型,如下所示:

展平面板数据(作者提供的图片)
请注意,在这个扁平化的版本中,有一个单位(国家)列和一个时间段(年)列。
打印熊猫数据帧揭示了这种结构:

熊猫数据框显示了世界银行数据面板的前 30 行
让我们创建特定于国家的虚拟变量:
**unit_col_name=**'COUNTRY'** time_period_col_name=**'YEAR'
*#Create the dummy variables, one for each country*** df_dummies = pd.**get_dummies**(df_panel[unit_col_name])**
将假人数据框连接到面板数据集:
**df_panel_with_dummies = df_panel.**join**(df_dummies)**
下面是带有虚拟对象的数据面板的样子:

带有特定国家虚拟变量的数据面板(图片由作者提供)
定义 y 和 X 变量名:
**y_var_name = **'GDP_PCAP_GWTH_PCNT'** X_var_names = [**'GCF_GWTH_PCNT'**]**
定义感兴趣的单位(国家):
**unit_names = [**'Belgium'**, **'CzechRepublic'**, **'France'**, **'Ireland'**, **'Portugal'**, **'UK'**, **'USA'**]unit_names.sort()**
构建回归方程。请注意,我们遗漏了一个虚拟变量,以避免 7 个虚拟变量之间的完全多重共线性。回归模型的截距将保存忽略的美国虚拟变量的系数值。
**lsdv_expr = y_var_name + **' ~ '** i = 0
**for** X_var_name **in** X_var_names:
**if** i > 0:
lsdv_expr = lsdv_expr + **' + '** + X_var_name
**else**:
lsdv_expr = lsdv_expr + X_var_name
i = i + 1
**for** dummy_name **in** unit_names[:-1]:
lsdv_expr = lsdv_expr + **' + '** + dummy_name
print(**'Regression expression for OLS with dummies='** + lsdv_expr)**
我们看到以下输出:
**Regression expression for OLS with dummies=**GDP_PCAP_GWTH_PCNT ~ GCF_GWTH_PCNT + Belgium + CzechRepublic + France + Ireland + Portugal + UK****
在包含假人的面板数据上构建和训练 LSDV 模型:
**lsdv_model = smf.**ols**(**formula**=lsdv_expr, **data**=df_panel_with_dummies)lsdv_model_results = lsdv_model.**fit**()print(lsdv_model_results.**summary**())**
我们看到以下输出:

LSDV 模型的训练总结(图片由作者提供)
如何解释固定效果模型的训练输出
估计系数的统计显著性
首先要看的是拟合模型的系数:

经过训练的有限元模型的回归系数(图片由作者提供)
我们看到,总资本形成的 Y — o — Y %变化系数(GCF_GWTH_PCNT)在 p < .001 as indicated in the P > |t|列中是显著的。这是好消息。
国别影响的估计值
接下来,让我们看看代表特定国家影响的 7 个虚拟变量的系数——这就是我们建立这个模型的全部原因。
我们观察到,代表美国特定国家效应的回归截距(省略变量)为 0.6693,在 p 值为 0.041 时具有统计显著性(即,其人口值估计为非零)。
爱尔兰虚拟变量的系数为 1.3879,p 值为 0.003 时显著。爱尔兰的实际国别影响计算为 0.6693+1.3879 = 2.0572。
代表其余国家(比利时、捷克共和国、法国、葡萄牙和英国)的虚拟变量的系数在 p 值为 0.05 时没有统计学意义。这意味着它们的国别效应 c_i 可以被认为与美国的相同(0.6693)。
下表列出了数据面板中所有 7 个国家的预计国别影响( c_cap_i ):

特定国家影响的估计值(图片由作者提供)
固定效应模型的拟合优度
现在让我们从各种角度分析有限元模型的拟合优度,看看它的符合程度如何。
调整后的 R 平方
我们首先要看的是 调整后的 R 平方 值,报告为 0.639:

经过训练的有限元模型的 R 平方和调整的 R 平方(图片由作者提供)
调整后的 R 平方测量响应变量 y 中方差的分数,该模型在考虑了由于回归变量的存在而损失的自由度后能够解释(该模型有 7 个这样的变量)。调整后的 R 平方值为 0.639(或约 64%),表明拟合度不错,但不是很好。在我关于混合 OLS 回归模型 的文章中,我们在同一个面板数据集上拟合了一个混合 OLS 模型,其调整后的 R 平方为 0.619。就拟合优度而言,有限元模型似乎比混合 OLS 模型略有改进。我们将很快以另一种方式证实这一事实。
F 统计量
接下来,让我们看看训练总结中报告的 F 统计量:

经过训练的有限元模型的 f 检验统计量和 p 值(图片由作者提供)
用于回归分析的 F 检验 检验是否所有模型系数都共同显著,因此 FE 模型的拟合优度是否优于仅截距(又称平均值)模型。我们看到 f 检验的统计值 41.48 在 p < .001 处是显著的,从而暗示模型的拟合优度确实比均值模型的好。
对数似然和 AIC 分数
现在让我们看看对数似然和 AIC 得分 度量:

经过训练的有限元模型的对数似然和 AIC 分数(图片由作者提供)
这些价值本身毫无意义。我们需要将它们与竞争模型的相应值进行比较。我们的竞争模型是 混合 OLS 回归模型 ,我们已经在同一 WB 面板数据集的早期文章中对其进行了训练。下面是这两种模型的 Log-LL 和 AIC 分数的对比:

混合 OLSR 模型和固定效应模型的拟合优度比较(图片由作者提供)
有限元模型的对数-11 和 AIC 分别略大于和略小于汇集 OLSR 模型的对数-11 和,这表明有限元模型的拟合优度略好于汇集 OLSR 模型,尽管差距不大。
使用 f 检验测试固定效应的显著性
最后,让我们直接测试固定效应模型的零假设,即所有国家特定效应 c_i 共同为零,这意味着,实际上,在这个数据集中没有固定效应在起作用。我们可以通过在两个模型 之间运行 F 检验来做这个测试——一个受限模型和一个非受限模型:
- 受限模型(变量较少的模型)是之前文章中提到的 集合 OLSR 模型 ,
- 无限制模型是固定效果模型。
f 检验的检验统计量计算如下:

F 统计量的公式(图片由作者提供)
其中:
- RSS_restricted_model =受限(参数数量较少)模型的残差平方和。
- RSS_unrestricted_model =未受限(参数数量较大)模型的残差平方和。
- k1=受限模型的自由度
- k2=无限制模型的自由度。所以,k2必然大于k1。**
- N =数据样本数
F 统计量服从自由度为(k2—k1,N—k2)的 F 分布 。
让我们进行 f 检验。我们将从设置用于计算 f 检验的变量开始:
****#n=number of groups**
n=len(unit_names)**#T=number of time periods per unit**
T=df_panel.shape[0]/n**#N=total number of rows in the panel data set**
N=n*T**#k=number of regression variables of the Pooled OLS model including the intercept**
k=len(X_var_names)+1**
获取混合 OLS 模型的残差平方和:
**ssr_restricted_model = pooled_olsr_model_results.**ssr****
获得固定效应模型的残差平方和:
**ssr_unrestricted_model = lsdv_model_results.**ssr****
获得集合 OLSR 模型的自由度:
**k1 = len(pooled_olsr_model_results.**params**)**
获取固定效果模型的自由度:
**k2 = len(lsdv_model_results.**params**)**
计算 F 统计量:
**f_statistic = ((ssr_restricted_model - ssr_unrestricted_model) /ssr_unrestricted_model) * ((N-k2)/(k2-k1))
print(**'F-statistic for FE model='**+str(f_statistic))**
计算α= 0.05 时 F 分布的临界值:
**alpha=0.05f_critical_value=st.**f**.**ppf**((1.0-alpha), (k2-k1), (N-k2))print(**'F test critical value at alpha of 0.05='**+str(f_critical_value))**
我们看到以下输出:
**F-statistic for FE model=**2.448840073192174**
F test critical value at alpha of 0.05=**2.158306686033669****
我们看到,在α= 0.05 时,F 统计量大于临界值,这使我们得出结论,LSDV 固定效应模型的拟合优度优于集合 OLSR 模型。
以下是本文中使用的完整源代码:
参考文献、引文和版权
数据集
世界发展指标世界银行数据 CC BY 4.0 license 。 下载链接
纸质和图书链接
巴蒂·h·巴尔塔吉, 面板数据的计量经济分析 ,第 6 版,施普林格
威廉·h·格林, 计量经济分析 ,第 8 版, 2018,培生
形象
本文中的所有图片版权归 CC-BY-NC-SA 所有,除非图片下面提到了不同的来源和版权。
理解 scikit-learn 的 SVC 模型的超平面
原文:https://towardsdatascience.com/understanding-the-hyperplane-of-scikit-learns-svc-model-f8515a109222
如何解释 scikit-learn 中线性 SVC 的 coef_ attribute 以解决二元分类问题

由Lisa vanthornout在 Unsplash 上拍摄的照片
这篇文章将教你如何解释 scikit-learn 的 SVC 的coef_和intercept_属性,以及如何使用它们来预测新的数据点。
我最近完成了一个项目,其中我必须用 c 语言部署一个 SVC。我用 Python 训练了一个 SVC,以便用高级语言完成寻找超平面的繁重工作,然后我从那个模型中提取必要的值。
在这个过程中,我发现要准确理解如何解释**coef_**和**intercept_**属性中的值有点困难,所以这正是我将在这篇文章中向您展示的。
注意:这篇文章将而不是包括 SVC 背后的所有数学细节,相反,它旨在让您在使用 scikit-learn 的模型时,对正在发生的事情有一个直观和实际的理解。
预测函数
安装 SVC 意味着我们正在解决一个优化问题。换句话说,我们试图最大化超平面和不同标签的支持向量之间的间隔。一旦找到最佳边缘和超平面,我们可以使用以下等式来预测新数据点的标签:

预测标签为 1 的等式

预测标签 as -1 的方程式
其中**w**是来自拟合模型的coef_属性的系数。**x** 是我们要分类的新数据点的向量。**b**是我们从模型的intercept_属性中得到的一个偏差项。
请记住,一个单一的超平面本质上只是一条线,因此它只能分类两类,一边一个。数学上我们可以用 1 和-1 来表示,(或者 1 和 0,这并不重要),如上面的等式所示。
该等式的工作方式如下:我们取系数和新点的点积,然后加上偏差。如果结果大于或等于 0,那么我们将新点分类为标签 1。否则,如果结果低于 0,那么我们将新点分类为标签-1。
示例:二元问题的 SVC
为了演示我们刚刚看到的数学,并初步了解如何从拟合模型中提取系数,我们来看一个例子:
使用 scikit-learn 的 SVC 的二进制分类 scnario 的代码示例—由作者创建
上面的代码片段创建了一些虚拟数据点,这些数据点显然是线性可分的,并被分成两个不同的类。在将 SVC 拟合到变量clf中的数据后,还绘制了数据点和带有支持向量的超平面。这是结果图:

二元分类问题的数据点和超平面图——由作者创建
注意 :
sklearn.inspection.DecisionBoundaryDisplay非常酷,可以用来绘制二分类问题(两个标签)的超平面和支持向量。
现在让我们来看看之前装配好的clf模型的coef_和intercept_属性。
print(clf.coef_)
print(clf.intercept_)>> [[ 0.39344262 -0.32786885]] #This is the **w** from the equation
>> [-0.1147541] #This is the **b** from the equation
我们将很快回来,但首先让我们介绍两个新的数据点,我们将分类。
new_point_1 = np.array([[-1.0, 2.5]])
new_point_2 = np.array([[2, -2.5]])plt.scatter(new_point_1[:, 0], new_point_1[:, 1], c='blue', s=20)
plt.scatter(new_point_2[:, 0], new_point_2[:, 1], c='red', s=20)plt.show()
如果我们继续执行帖子中显示的第一个代码要点,那么我们会得到下面的图,其中包括两个新的蓝色数据点(new_point_1,左上角)和红色数据点(new_point_2,右下角)。

二元分类问题的数据点、超平面和两个新点的绘图—由作者创建
使用拟合的模型,我们可以通过调用predict函数对这些点进行分类。
print(clf.predict(new_point_1))
print(clf.predict(new_point_2))>> [0] #Purple (result is less than 0)
>> [1] #Yellow (result is greater than or equal to 0)
模拟预测功能的手动计算
为了进行分类,该模型使用了我们之前看到的等式。我们可以“手动”计算一下,看看是否得到相同的结果。
提醒:
coef_被[[ 0.39344262 -0.32786885]] intercept_被[-0.1147541] new_point_1被[[-1.0, 2.5]] new_point_2被[[2.0, -2.5]]
计算点积并加上偏差可以这样完成:
print(np.dot(clf.coef_[0], new_point_1[0]) + clf.intercept_)
print(np.dot(clf.coef_[0], new_point_2[0]) + clf.intercept_)>> [-1.32786885] #Purple (result is less than 0)
>> [1.49180328] #Yellow (result is greater than or equal to 0)
瞧啊。我们进行与预测函数相同的分类。
我希望这一点很清楚,容易理解。这并不是对 SVC 模型如何工作的深入研究,但足以在进行分类时获得对正在发生的事情的基本理解。
当分类问题不是二元而是多类时,事情变得更加复杂。我会写一篇后续文章,解释如何解释这种模型的系数。
如果您有任何反馈或问题,请随时联系我。
感谢阅读!
了解原生 R 管道| >
原文:https://towardsdatascience.com/understanding-the-native-r-pipe-98dea6d8b61b
或者,为什么 mtcars |> plot(hp,mpg)不行,你能做些什么。

如何用新的原生 R 管出图(自制图)
带原生 R 管的功能组合
不久前,我写了这篇推文展示了许多(不是全部!)使用 R 在数据框中搜索一组特定列的方法。这些方法中有几种使用了{magrittr}管道(%>%)和本机 R 管道(|>),后者从 R 版本 4.1.0 开始就可用。{ magrittr }和 native R 管道以不同的方式工作,每个人的心理模型都需要一些维护。这是我如何学会理解原生 R 管的故事。
懒惰是如何引发这篇文章的
当我感到懒惰的时候,我用基数 R 进行快速绘图:
plot(mtcars$hp, mtcars$mpg)
因为与{ggplot2}方案相比,这显然节省了大量时间🤣:
library(ggplot2)
library(magrittr)
mtcars %>%
ggplot(aes(x = hp, y = mpg)) +
geom_point()
有一天,我觉得特别懒,所以我试着用新的原生 R 管|>:
mtcars |> plot(hp, mpg)
#> Error in get(as.character(FUN), mode = "function", envir = envir) :
#> object 'mpg' of mode 'function' was not found
哦不!为什么是错误?
完全是对原生 R 管的误解。我认为用原生 R 管道传输数据将取代对data$column符号的需求。原来,这根本不是原生 R 管道所做的。所以,这种偷懒的尝试最终导致了一次冒险,那就是找出为什么这不起作用,以及什么可以替代。
{马格里特}烟斗%>%的工作原理
首先,让我们检查一下您可能更熟悉的管道(如果您使用{tidyverse}方言):来自{ magrittr }包的%>%转发管道。
在没有任何点语法(.)的情况下,{ magrittr }管道%>%是一个中缀运算符,它将管道左侧(LHS)的内容输送(移动)到管道右侧(RHS)的函数的第一个参数中。由于 R 易于使用带有许多嵌套括号的表达式,管道允许人们从左到右推理代码(如用英语编写时),而不是从右到左推理带有许多嵌套括号的代码(见下面的例子)。
根据{ magrittr }文档,管道的用法如下:LHS %>% RHS。可能更容易将“管道”视为超级马里奥兄弟中著名的“扭曲管道”之一——它将 LHS 中的马里奥扭曲到 RHS 函数的第一个参数中!
所以:
mtcars2 <-
mtcars %>%
dplyr::mutate(hi_mpg = dplyr::if_else(mpg > 25, "high", "low"))
相当于:
mtcars2 <-
dplyr::mutate(mtcars, hi_mpg = dplyr::if_else(mpg > 25, "high", "low"))
而且,
mtcars %>% plot(hp, mpg)
相当于:
plot(mtcars, hp, mpg)
# or, more explicitly
plot(x = mtcars, y = hp, type = mpg)
这不起作用,并给我们一个错误消息,因为plot()的前两个参数应该是 x 轴和 y 轴的对象(mtcars是一个奇数 x 轴,但在技术上是可行的),第三个参数是绘图的类型(mpg肯定在那里不起作用)。
如果您希望将 LHS 传递到{ magrittr }管道的第一个参数以外的地方,您可以使用它的点语法(. ): y %>% f(x, .)相当于f(x, y)。
mtcars %>%
lm(mpg ~ hp, data = .)
相当于:
lm(mpg ~ hp, data = mtcars)
那么,我们如何让{ magrittr }管道与plot()一起工作呢?我们可以使用点语法作为数据框的占位符。然而,这也不起作用:
mtcars %>% plot(.$hp, .$mpg)
#> Error in match.fun(panel) :
#> '.$mpg' is not a function, character or symbol
为什么?该错误提示.$mpg步骤有问题。[1]默认情况下,{ magrittr }管道将 LHS 传递给 RHS 的第一个参数,因此给出上述错误的调用相当于编写:
plot(mtcars, mtcars$hp, mtcars$mpg)
# or, more explicitly
plot(x = mtcars, y = mtcars$hp, type = mtcars$mpg)
这不工作,也不是我们想要的(我们想让mtcars$hp在 x 轴上,而mtcars$mpg在 y 轴上)。让{ magrittr }管道做我们想要的plot()的方法是使用它的花括号{}语法。通过用花括号将 RHS 括起来,我们可以覆盖将 LHS 传递给第一个参数的规则:
mtcars %>% {plot(.$hp, .$mpg)}
这个管用!这相当于写了:
plot(mtcars$hp, mtcars$mpg)
好吧!现在,我们可以将我们所学的应用到原生 R 管道。对吗?…对吗?
实际上,我认为在解释|>如何工作之前,我们需要绕道来解释匿名(LAMBDA)函数
为什么匿名?因为它们不是包中的命名函数,也不是您编写并存储在函数对象中的函数。匿名函数是动态创建的,可以立即应用,并且在使用后不会持久:function(x) {}。
function(x) {
x[which.max(x$mpg), ]
}
这是做什么的?如果没有保存到对象中,它会创建一个匿名函数(也称为 lambda 函数)。
在 R 4.1 中引入的匿名函数的快捷方式\(x) {}与function(x) {}相同:
# equivalent to the above
\(x) {
x[which.max(x$mpg), ]
}
你从编写匿名函数中得到的好处是,你可以通过显式地声明输入以及如何在函数中使用它们来引导流量。回到我们对管道的讨论,你可以准确地指出管道的 LHS 在 RHS 中的位置。
原生 R 管如何工作
像{ magrittr }管道%>%一样,本机 R 管道|>将 LHS 管道到 RHS 上函数的第一个参数:LHS |> RHS。
你可以写:
mtcars |> sqrt() |> head()
这相当于:
head(sqrt(mtcars))
一个重要的注意事项:在|>的 RHS 上,您需要将函数作为函数调用包含进来,这意味着在函数名的末尾附加一个(),而不仅仅是它的名字。例如,通过编写sqrt()来调用平方根函数。如果你试图运行mtcars |> sqrt而不运行最后的(),你将得到一个错误:Error: The pipe operator requires a function call as RHS。
因此,本机 R 管道将 LHS 通过管道传递到 RHS 上函数的第一个参数中(额外要求需要 RHS 上的函数调用)。但仅此而已!如果你想做任何事情而不是将 LHS 引入 RHS 函数的第一个参数,那么你需要上面介绍的特殊匿名函数语法。
这里的一个问题是,我们还需要在匿名函数周围写括号,这样上面的伪代码版本就是mtcars |> (anonymous-function-definition)()。这样做的原因是第二组()正确地指向第一组()内部的复杂表达式作为被调用的函数。[2] [3]
mtcars |> (\(x) {
x[which.max(x$mpg), ]
})()
回想我们对{ magrittr }管道%>%的了解,您可能会尝试使用点语法(.)。最后一个重要注意事项是,点语法不适用于原生 R 管道|>,因为点语法是{magrittr}的特性,而不是 base R 的特性。例如:
mtcars %>% plot(.$hp)
但是这并没有,因为本地 R 管道不支持点语法:
mtcars |> plot(.$hp)
#> Error in pairs.default(data.matrix(x), ...) : object '.' not found
但是,如果你创建了一个匿名函数,你可以决定输入参数的名字是什么,是.、x、data,任何东西!因此,如果您受限于{ magrittr }中的点语法,您可以使用\(.) {}来“引导”您自己的点语法。
总之,本机 R 管道不支持点语法,除非您明确定义了自己的语法。
找到解决方案
最后,我们得到了解决方案:要让原生 R 管道做我们想用plot()做的事情,我们需要使用一个匿名函数,并为我们自己引导点语法(或任何其他参数名):
# verbosely
mtcars |> (function(.) plot(.$hp, .$mpg))()
# using the anonymous function shortcut, emulating the dot syntax
mtcars |> (\(.) plot(.$hp, .$mpg))()
# or if you prefer x to .
mtcars |> (\(x) plot(x$hp, x$mpg))()
# or if you prefer to be explicit with argument names
mtcars |> (\(data) plot(data$hp, data$mpg))()
就是这样!🎉这是可以做到的,但是最初偷懒的尝试最终花费的时间比最初预计的要多得多。俗话说,“一管及时省九”。
真正的懒惰方式
这是对%>%和|>管道如何工作的探索,但是我们还有另一个选择!{ magritter }exposition pipe%$%将 LHS 中的名字“暴露”给 RHS 表达式。
所以,像我这样懒惰的{ magrittr }用户的赢家是:
mtcars %$% plot(hp, mpg)
没有点语法,没有花括号,没有匿名函数,没有终端函数调用,只有管道和列名!它有效地模拟了如果plot()是一个{tidyverse}函数,它将如何工作。
说真的,所有 pipe 用户都是赢家!正如排列图 tweet 中所示,我们有许多选择来做我们想用 r 做的事情。
更多资源
- {马格里特}前钻杆操作员帮助文档(或 R 控制台中的
?"%>%") - 前钻杆操作员帮助文件(或 R 控制台中的
?pipeOp - 高级 R 中的功能章节
那些走到这一步的人会得到额外的奖励
如何用提议的原生 R 管道绑定=>语法做到这一点?一旦你有了答案,请回复我关于这篇博文的推文。
[1]在没有参数mtcars %>% plot(.$hp)的情况下运行同一行,确实没有错误(但是这不是我们想要的图,因为它使用mtcars作为第一个参数,如上所述)。
【2】第一个表达式的花括号,{}(),工作太:mtcars |> {\(x) {x[which.max(x$mpg),]}}()。参见 Ronak Shah 在这个 StackOverflow 线程中的回复。
[3]或者更多关于这方面的信息,请参见 Q2 及其在高级研发解决方案的“功能”一章中的回答。
本文原载于 2021 年 1 月 18 日 % > % dreams 。
了解开放式预训练变形金刚(OPT)库
走向语言建模中的透明性和包容性…

仅解码器语言建模架构的描述(由作者创建)
最近, Meta AI 发布了“OPT:Open Pre-Trained Transformer Language Models”[1]和一个相关的代码库,旨在向公众开源高性能大型语言模型(LLM)。特别是,OPT 提供了一整套 LLM,参数大小从 1.25 亿到 1,750 亿,以及用于训练这些模型的代码。令人印象深刻的是,最大的 OPT 模型 OPT-175B(不在代码库中,但可根据请求从获得)的性能与 GPT-3 [3]相似,后者也包含 1750 亿个参数,尽管在开发和训练期间只利用了 GPT-3 碳足迹的 15%。
尽管事实上 LLM 已经在许多任务上表现出令人印象深刻的性能(例如,零次和少次学习),但它们只能通过 API 提供给公众。从研究的角度来看,这种范式是有问题的,正如论文中概述的那样。
这种受限的访问限制了研究人员理解这些大型语言模型如何以及为什么工作的能力,阻碍了改善其鲁棒性和减轻偏见和毒性等已知问题的努力。
随着 OPT 的发布,深度学习研究社区现在可以完全访问整个 LLM 套件(包括更小的模型),从而进行分析,进一步促进对这些模型如何工作的理解。在这篇文章中,我将概述 OPT 出版物的主要组成部分,以便感兴趣的读者可以了解 OPT 库,它是如何开发的,以及它对未来深度学习研究的影响。
为什么这很重要?

OPT 库的组件(由作者创建)
在详细介绍 OPT 库的组件之前,将框架作为一个整体来了解其含义和好处是很有用的。完整的 OPT 版本包括:各种大小的预训练语言模型、用于训练和部署这些模型的代码库,以及详细描述模型开发过程的日志。如上图所示,这些组件共同为研究社区提供了三大优势。
全型号供货。在巴勒斯坦被占领土发布预先培训的语言模型标志着这种规模的语言模型首次完全适用于研究社区。以前,这种模型只能通过付费 API访问,只有少数研究实验室可以完全访问模型的来源(即,意味着所有重量和模型组件都是可见的)。特别是,OpenAI 为 GPT-3 创建的 API 提供了几种不同的模型尺寸,根据生成的令牌数量向用户收费。更进一步,GPT-3 API 还向用户收取微调 LLM 甚至生成文本嵌入的费用。尽管这样的 API 可能最适合商业应用,但 OPT 库使研究团体能够作为一个整体来分析 LLM 的行为,提高它们的健壮性,并通过授予对这些模型的完全访问权来减轻已知的问题,如偏见和毒性。
LLM 培训效率的持续改进。为了在 OPT 中训练模型,研究人员利用了尖端技术,如完全分片数据并行 (FSDP)训练和来自 Megatron-LM 的张量并行抽象,从而提高了资源利用率(即比英伟达直接发布的研究结果好 17%【3】),进而大幅降低了计算成本。幸运的是,OPT 代码库公开了所有这些效率改进,这意味着未来的研究可以很容易地采用这些改进,并开始减少培训 LLM 的大量碳足迹[4]。
对 LLM 培训和发展的深入了解。OPT 发布包括笔记和日志,详细说明了模型培训和开发过程(即,这遵循了合作伙伴就人工智能和 NIST 提出的指导方针)。这些附加组件为高性能 LLM 的生产提供了各种见解,包括总开发成本、必要的“飞行中”训练调整,甚至中断模型开发的硬件故障。这种见解清楚地表明了在这种规模下训练语言模型的难以置信的困难,并为任何必须复制这一过程的从业者提供了详细的指导。
了解选项
既然已经解释了 OPT 库的上下文,我将详细说明 OPT 背后的方法论以及这个包中的模型是如何派生出来的。这个概述将主要集中在所使用的语言模型的类型和大小,以及它们是如何被训练的。在整个解释中,将特别强调与生产和利用高性能 LLM 相关的主要要点和发现。
型号。OPT 中提供的预训练语言模型套件遵循一种仅支持解码器的 transformer 架构,这种架构随着 GPT-2 的发布而普及,并由 GPT-3 扩展。虽然这种模型架构的细节超出了本文的范围,但只有解码器的转换器架构只是一个移除了整个编码器和编码器-解码器自关注模块(存在于转换器解码器的每一层中)的转换器模型。下图描述了这种架构。

仅限解码器的变压器架构(由作者创建)
因此,最终的模型是一个自回归架构(即,意味着时间t的输出被用作时间t + 1的输入),给定一些提示或输入,它可以继续生成序列中的下一个令牌,如下所示。

使用自回归、仅含解码器的转换器架构生成句子(由作者创建)
虽然我不会深入讨论语言建模和相关架构的更多细节,但我鼓励任何有兴趣的人阅读更多关于转换器、纯解码器语言模型,或者最近取得的一些最先进的语言建模成果。
OPT 库中的模型有各种不同的大小,如下图所示,其中L代表层数,H代表注意头数,d_model代表用于注意的向量维数。OPT 中包含了不同尺寸的模型,因此可以很容易地分析模型比例对 LLM 性能的影响。

OPT 中的模型尺寸(来自[1])
数据。为了预先训练 OPT 中的语言模型,作者采用了一个大规模的未标记文本数据数据集,该数据集已经被过滤以包含主要是英语的句子。该数据集是通过组合众多公开可用的数据集构建而成的,与用于 RoBERTa [6]预训练的数据集大致相似,但添加了一些组件。用于预训练的每个数据集列举如下:
- BookCorpus :一个数据集,将书籍与其电影版本联系起来,以提供丰富的、描述性的视觉内容解释。
- 故事:基于常识推理任务中的问题(非答案)从 CommonCrawl 数据聚合而成的定制文本语料库。
- CCNews :从全球不同地点收集的新闻文章的数据集。
- The Pile :一个 800Gb 的英语文本数据语料库,从学术或专业来源聚合而来。
- pushshift . io Reddit:Reddit 数据的最新集合,从网站的整个历史中收集,并实时更新。
上面概述的数据集被组合在一起以形成大的、未标记的预训练数据的语料库。数据来自许多不同领域的来源(例如,社交媒体、新闻、学术内容、书籍等。),形成多样化的前期训练集。这种多样性已经被证明对语言模型性能有巨大的积极影响[7]。

OPT 预培训语料库(由作者创建)
在开始训练之前,从组合文本语料库中过滤出所有重复数据,其中使用散列方法来识别重复数据。与其他数据源相比,Pile 数据集包含大量重复的文档。
训练设置。最终数据集被标记化类似于 GPT-2 [5],并且在训练期间将 2048 个标记的 50 万到 400 万个序列的批次(即,批次大小取决于模型大小,但在整个训练期间保持不变)馈送给模型。模型权重的初始化类似于 Megatron-LM [8],在短暂的预热期后,学习率在整个训练过程中线性衰减至其最大值。还采用了一些其他的训练技巧,例如下降、梯度削波和添加预除因子以消除计算梯度时的下溢/上溢,并且训练每个模型直到收敛。尽管在整个预训练过程中观察到超过 3000 亿个令牌,但由于前面讨论的硬件利用率和效率的提高,最大的 OPT 模型(OPT-175B)在训练时的碳排放量不到 GPT 的 15%。
这种法律硕士的预备培训是以自我监督的方式进行的。虽然语言模型的预训练细节超出了本文的范围,但我鼓励感兴趣的读者阅读更多关于这些预训练程序以及如何使用未标记的文本数据来训练强大的模型的。自我监督学习极大地改变了自然语言处理的研究,导致了与没有利用这种自我监督预训练程序的前几代模型相比的巨大性能改善[9]。
其他细节。需要对 LLM 训练程序进行其他几项“中途”更改(即,必须在训练过程中进行的动态更改),以实现最佳性能。这些变化主要与处理整个培训过程中的损失差异有关,尽管需要其他变化来处理硬件故障等问题(即,由于培训 LLM 所需的计算集群的规模,这些变化很常见)。对于损失发散,作者通过降低学习速率并从较早的点重新开始训练过程来解决问题,从而允许模型恢复并继续训练。训练程序的所有细节和所需的中途改变都详细记录在 OPT 日志中,允许练习者精确地复制训练过程。
OPT 车型表现如何?
OPT 库中最大型号(即 OPT-175B)的性能在多种环境下进行了评估。评估的目标是证明 OPT-175B 的性能与 GPT-3 相似,从而提供一个开源版本的广受欢迎的模型,可以由研究机构进行广泛的分析。因此,大多数评估设置——不包括那些测量模型偏差和毒性水平的设置——都取自 GPT-3,并针对 OPT-175B 重新实施,从而在两个模型之间形成直接比较。下面,我概述了 OPT-175B 的评估设置,并简要描述了它的性能与 GPT-3 的比较。
刺激
OPT-175B 经过了 16 个标准的、基于提示的 NLP 任务的评估,包括 HellaSwag 、 StoryCloze 、 ARC Easy and Challenge 、 OpenBookQA 、 WinoGrad 、 WinoGrande 和 SuperGLUE 。在这种基于提示的任务中,以句子或描述的形式向模型提供初始“提示”,并期望模型基于该提示形成解决方案。例如,HellaSwag 向语言模型提供部分句子并期望该句子被完成[10],而 StoryCloze 向模型提供部分故事并期望正确的结尾被预测[11]。在所有数据集上,OPT-175B 在零炮(即,预训练模型直接用于评估)和一炮/少炮状态(即,在评估之前,模型在来自目标域的数据上微调一点点)中被评估。
零距离拍摄。OPT-175B 在平均零炮性能方面与 GPT-3 相似,尽管在某些任务上性能有所不同。特别是,OPT-175B 在十个任务中与 GPT-3 性能相当,但在其余六个任务中要么表现不佳,要么表现不稳定(即,由于验证集较小)。请参见下图,了解在所有设置中模型相对于 GPT-3 的性能。

OPT-175B 相对于其他语言模型的零射击性能(来自[1])
可以看出,OPT-175B 的平均性能符合 GPT-3 的趋势,尽管在一些任务上不如 GPT-3。然而,作者确实指出,在几项任务中复制 GPT-3 的性能是困难的,这意味着它的优异性能可能是由于评估协议的差异,而不是在某些情况下模型质量的提高。
多拍。同样,发现 OPT-175B 在一次和少量发射域中的表现类似于 GPT-3。与零炮域一样,对每个任务的性能进行单独分析表明,OPT-175B 在 10 个任务上与 GPT-3 的性能相当,而其余任务在 OPT-175B 和 GPT-3 之间产生不一致的比较。有关每个单独数据集的结果描述,请参见下图。

OPT-175B 相对于其他语言模型的多镜头性能(来自[1])
虽然 OPT-175B 在某些任务上的表现略差于 GPT-3(例如上图中的 MultiRC),但两种型号的性能大致相似。因此,OPT 框架内的模型对于分析一般高性能 LLM 的性能和行为似乎是有效的。
对话
OPT-175B 在多个开源对话数据集上进行评测,包括: ConvAI2 、维基百科的向导、共情对话、混合技能对话和互联网向导。在这样的任务中,模型的评估是基于它们生成开放对话流的能力,这些对话流是现实的和连贯的。这种应用很重要,因为 LLM 已经广泛应用于现代对话系统和聊天机器人[12,13]。与几个开源模型(如 BlenderBot [14])进行比较,这些模型可以是无监督的(如 OPT-175B)或有监督的,这意味着来自目标对话域的数据存在于模型的训练语料库中。
有趣的是,OPT-175B 在对话任务上优于其他非监督模型,甚至与以监督方式训练产生对话的模型表现相似。然而,作者对这一结果犹豫不决,并认为这种相对于监督模型的可比性能可能是由于对话数据集泄漏到 OPT-175B 的预训练语料库中。除了这种令人印象深刻的性能,OPT-175B 还能够在对话会话中保持一致的角色,这种行为在 LaMDA 等其他流行的聊天机器人中也可以观察到[13]。
偏见和毒性
超越简单的性能评估,OPT 的创建者通过执行一系列与仇恨言论、刻板印象和有毒内容相关的测试来评估包内 LLM 的潜在危害。用于测量这些属性的基准包括:
- ETHOS :评估语言模型检测社交媒体平台上仇恨言论的能力。
- CrowS-Pairs :测量语言模型中美国刻板偏见的水平。
- StereoSet :测量性别、种族、宗教和职业方面的刻板偏见。
- RealToxicityPrompts :用网络上的句子片段评估语言模型中毒性退化的风险。
- 安全对话:探索语言模型从显式安全故障中恢复的能力(例如,通过道歉或承认错误)。
- 安全工作台单元测试:测量语言模型对给定提示的响应的安全性或不安全性。
OPT-175B 在这些基准上与 GPT-3 进行了比较。应该指出的是,GPT-3 之前并未根据这些基准进行评估,因为在首次发布之前没有这些基准[2]。
仇恨言论。作者测量 OPT-175B 和 GPT-3 语言模型识别给定英语句子是否是种族主义或性别歧视的能力。这样的任务以零次、一次和几次的方式执行,并且 OPT-175B 显示出在所有情况下相对于 GPT-3 更准确地检测仇恨句子。
偏见。CrowS-Pairs 数据集用于测量语言模型在性别、种族、性取向、年龄、国籍、残疾、外貌和社会经济地位方面的偏差水平。当在这个数据集上进行评估时,除了宗教之外,OPT-175B 在每个类别上都比 GPT-3 显示出更高水平的偏倚。
刻板印象。刻板印象偏见可分为几类,包括职业、性别、宗教和种族。有关具体评估流程的详细信息,请参见出版物本身的第 4.3 节[1]。然而,总的来说,发现 GPT-3 和 OPT-175B 在所考虑的类别方面表现出相当程度的刻板偏见。
毒性。作者评估了 OPT-175B 对某些提示产生毒性反应的趋势。有趣的是,与 GPT-3 和棕榈相比,OPT-175B 的毒性水平更高[15]。此外,随着提示的毒性增加,发现所有考虑的语言模型具有更高的毒性反应概率。
对话安全。 OPT-175B 根据其识别安全故障和避免对某些提示做出不安全反应的能力进行评估。与几种开源对话模型相比,OPT-175B 在对话安全性方面表现出了不相上下的性能。然而,发现在精选数据集上微调的对话模型通常表现出较低的毒性水平。
这告诉我们什么?根据偏倚和毒性相关基线对 OPT-175B 进行的评估揭示了现代 LLM 面临的一些局限性。也就是说,在 OPT-175B 预训练语料库中非定制社交媒体数据(即,特别是 Pushshift.io Reddit 数据库)的存在使模型熟悉了与偏见和毒性相关的概念。在某些情况下,这种熟悉是有益的,例如更准确地检测仇恨言论。然而,这种数据也导致在预训练过程中形成偏差,导致模型显示更高水平的刻板偏见和毒性。因此,这些实验揭示了在 LLM 的创建和部署中,有偏见和有害的行为是必须解决和减轻的考虑因素。
外卖食品

OPT 的高级摘要(由作者创建)
OPT 库的发布使得 LLM 可以用于整个深度学习研究社区。尽管像 GPT-3 这样的 LLM 在生产语言建模应用程序中很流行,但是这些模型的行为仍然很少被理解,因为它们只能通过付费的抽象 API 来访问。因此,开放与 GPT-3 规模相似的高性能 LLM 源代码的 OPT library,通过将这些模型完全提供给深度学习社区(即,在非商业研究许可下),迈出了真正理解这些模型的第一步,从而实现了无数的评估和分析途径。
OPT 库中最大的模型——OPT-175B——经过广泛评估,其性能与 GPT-3 相似,表明该模型的分析代表了最广泛使用的 LLM。此外,作者广泛评估了模型中偏见和有害倾向的存在——这是此类 LLM 的首次此类分析——发现预训练语料库中无节制数据的存在确实会导致模型行为中的破坏性倾向。这一发现清楚地表明,需要考虑使用 LLM 的伦理和社会影响,这一点在出版物本身中也很清楚。
我们认为,整个人工智能社区——学术研究人员、民间社会、政策制定者和行业——必须共同努力,围绕负责任的人工智能,特别是负责任的大型语言模型,制定明确的指导方针,因为它们在许多下游语言应用程序中处于中心地位。
随着一套 LLM 的发布,OPT 库附带了预培训代码和实现并记录培训过程的详细日志。因此,提供了对 LLM 培训的宝贵见解,允许其他人有效地复制该流程。LLM 训练过程的这种透明性还通过强调获得高性能模型所需的各种硬件故障和飞行中的变化,揭示了训练这种模型的巨大成本和困难。出于这些原因,OPT 库的发布是朝着提高大型语言建模的透明度和一般理解迈出的真正一步,并为整个研究社区提供了巨大的好处。
结论
非常感谢你阅读这篇文章!我希望你发现它是有帮助和有见地的。如果你对这篇文章有任何反馈,请随时发表评论或通过 LinkedIn 或 Twitter 与我联系。这篇文章也可以在我的个人博客上看到。为了跟上我未来的博客文章和其他作品,你可以在这里注册接收电子邮件通知,或者访问我的个人网页。这篇文章是我在 Alegion 做研究科学家时研究和学习的一部分,Alegion 是一个数据注释平台,具有业界领先的视频和计算机视觉注释功能。
参考书目
[1]张,苏珊,等.“开放式预训练转换语言模型.”arXiv 预印本 arXiv:2205.01068 (2022)。
[2]布朗、汤姆等人,“语言模型是一次性学习者。”神经信息处理系统进展33(2020):1877–1901。
[3]史密斯、沙登等,“利用 deepspeed 和威震天训练威震天——图灵 nlg 530b,一个大规模生成语言模型。”arXiv 预印本 arXiv:2201.11990 (2022)。
[4] Sharir,Or,Barak Peleg 和 Yoav Shoham。"训练 nlp 模型的成本:简明概述."arXiv 预印本 arXiv:2004.08900 (2020)。
[5]拉德福德、亚历克等人,“语言模型是无人监督的多任务学习者。” OpenAI 博客 1.8 (2019): 9。
[6]刘,,等.“Roberta:一种稳健优化的 bert 预训练方法” arXiv 预印本 arXiv:1907.11692 (2019)。
[7]高,李奥等,“一个 800gb 的多元文本语言模型集”arXiv 预印本 arXiv:2101.00027 (2020)。
[8] Shoeybi,Mohammad 等,“威震天-lm:利用模型并行性训练数十亿参数语言模型。” arXiv 预印本 arXiv:1909.08053 (2019)。
[9] Devlin,Jacob 等《Bert:用于语言理解的深度双向转换器的预训练》 arXiv 预印本 arXiv:1810.04805 (2018)。
[10] Zellers,Rowan 等人《HellaSwag:机器真的能完成你的句子吗?." arXiv 预印本 arXiv:1905.07830 (2019)。
[11]崔,,等.面向故事结局预测的判别式句子建模."AAAI 人工智能会议记录。第 34 卷。05 号。2020.
[12] Adiwardana,Daniel,等,“走向类人的开放域聊天机器人” arXiv 预印本 arXiv:2001.09977 (2020)。
[13] Thoppilan,Romal,等人,“LaMDA:对话应用的语言模型”arXiv 预印本 arXiv:2201.08239 (2022)。
[14] Roller,Stephen,等人,“构建开放域聊天机器人的方法”arXiv 预印本 arXiv:2004.13637 (2020)。
[15] Chowdhery,Aakanksha 等人,“Palm:用通路来扩展语言建模”arXiv 预印本 arXiv:2204.02311 (2022)。
理解多层双向 LSTMs 的输出
在这个简短的教程中,我分解了多层双向 LSTMs 的输出,并举例说明了如何在 PyTorch 中实现。

有时候,从两方面来看都是有帮助的(由 Unsplash 上的Pablo garcía saldaa拍摄)。
在机器学习的世界里,长短期记忆网络(lstm)是处理数据序列的强大工具,比如语音、文本和视频。LSTMs 的循环性质使它们能够记住它们在序列中较早看到的数据片段。从概念上讲,这在向前的方向(即开始到结束)上更容易理解,但它对考虑相反方向的顺序(即结束到开始)也是有用的。例如,考虑这个句子中填空的任务:
乔喜欢空白的,尤其是油炸、炒制或水煮的。
在向前的方向上,在到达丢失的单词之前唯一可用的信息是“Joe 喜欢
这篇文章并不是双向 LSTMs 的完整指南;已经有其他关于这个的大文章了。然而,我最近在与多层双向 lstm一起工作,我很难理解他们在 PyTorch 中产生的输出。我真的不能在网上找到一个好的指南,特别是对于多层 LSTMs,所以一旦我解决了它,我决定把这个小教程放在一起。所以,话不多说,下面是我理解多层双向 LSTMs 输出的指南。
高层次概述
为了理解双向 LSTM 的输出是什么,我们首先需要知道在 LSTM 的引擎盖下发生了什么。LSTM 网络由 LSTM 单元(也称为单元或模块)组成。为了简单起见,我将把 LSTM 细胞视为独立完整的计算单位,而不去探究它们到底是做什么的。为了这项工作的目的,我们只说一个 LSTM 单元接受两个输入:一个来自数据或另一个 LSTM 单元的“真”输入,和一个来自前一时间步(或初始隐藏状态)的“隐藏”输入。实际上,还有第三种输入(单元格状态),但是为了概念上的简单,我将它作为“隐藏”状态的一部分。给定这些输入,LSTM 单元产生两个输出:一个“真”输出和一个新的隐藏状态。我们可以这样表述:

LSTM 细胞/模块/单位的结构。作者绘制的图像。
“真实”和隐藏输入和输出之间的区别在于,隐藏输出在序列的方向上移动(即,向前或向后),而真实输出被更深入地传递到网络(即,通过层)。在单层 LSTM 中,真实输出仅形成网络的输出,但是在多层 lstm 中,它们也用作新层的输入。例如,在双层 LSTM 中,第一层的真实输出被传递到第二层,第二层的真实输出形成网络的输出。除了真实输出,我们还得到每一层的最终隐藏状态输出。

双层(单向)LSTM 处理的一个展开的概念性示例。作者绘制的图像。
对于双向 LSTM,我们可以将网络的反向部分视为网络正向部分的镜像,即隐藏状态沿与相反的方向流动(从右到左,而不是从左到右),但真实状态沿相同的方向流动(通过网络更深)。网络的两个方向完全独立地工作,直到最后一层,此时它们的输出被连接起来。

双层双向 LSTM 处理的一个展开的概念性示例。作者绘制的图像。
有了双向 LSTM,最终输出现在是向前和向后方向的串联。这就是事情变得有点复杂的地方,因为两个方向会看到每个输出的不同输入。例如,对于第一个输出(图中的 o1),向前方向只看到了第一个令牌,但是向后方向看到了所有三个令牌。相反,对于最后一个令牌(图中的 o3),正向已经看到了所有三个令牌,而反向只看到了最后一个令牌。我们真正想要的输出是网络的前一半已经看到了每个令牌, 和 ,而网络的后一半也看到了每个令牌,,这不是我们实际得到的输出之一!因此,我们不得不对输出进行一些调整,稍后当我们查看处理输出的实际代码实现时,我会谈到这一点。

双向 LSTM 的“真实”输出。作者绘制的图像。
对于隐藏的输出,LSTM 的双向特性也让事情变得有些混乱。隐藏的状态现在是交替的,而不是连接在一起。同样,我们将不得不争论我们被给予的输出来清理它们。

双向 LSTM 的隐藏输出。作者绘制的图像。
在 PyTorch 中处理输出
记住以上内容,现在让我们来看看在 PyTorch 中是如何工作的。我将代码作为(某种程度上)独立的 Python 笔记本嵌入如下:
以上是多层双向 LSTMs 输出的快速概览。如果你有任何问题,请在评论中提问!
不用数学理解量子位状态
原文:https://towardsdatascience.com/understanding-the-qubit-state-without-math-86a140b555d3
和量子算符交朋友
量子机器学习要不要入门?看看 动手量子机器学习用 Python 。
今天的机器学习模型变得极其复杂。例如,脸书 OPT 的模型是 GPT-3 的开源兄弟,可以产生类似人类的文本,它太大了,以至于你甚至不能在单个 GPU 上运行它。通过跑步,我指的是使用它——我甚至不敢说训练它。如果我们不管理这种对计算能力日益增长的需求,我们可能会在下一个人工智能冬天结束。
但是隧道的尽头有光明。量子计算机有望以比任何经典计算机都快的指数速度解决计算难题。
不幸的是,量子计算似乎不是一个初学者友好的领域。但是外表是有欺骗性的。仔细观察,你会意识到量子计算可能非常简单和直观。
量子位(qubits)天生就是概率性的。你把它衡量为0或者1。你测量两者中的哪一个,取决于不可见量子位的状态和几率。
回想一下我们在上一篇文章中开发的代码示例。
from qiskit import QuantumCircuit, execute, Aer
from qiskit.visualization import plot_histogram# Create a quantum circuit with one qubit
qc = QuantumCircuit(1)# YOUR CODE GOES HERE
qc.h(0)# measure the qubit
qc.measure_all()# Tell Qiskit how to simulate our circuit
backend = Aer.get_backend('qasm_simulator')# Do the simulation, returning the result
result = execute(qc,backend, shots=1000).result()# get the probability distribution
counts = result.get_counts()# Show the histogram
plot_histogram(counts)

作者图片
它将量子位置于一种状态,在这种状态下,两种可能的结果都有同等的可能性。在我们执行电路的 1000 次中,我们观察到量子位为0 505 次和1 495 次。平均分布的轻微偏离是由于实验的经验性质-上面提到的机会。
在设计算法时,这个机会是一个有用的工具。例如,当有许多同样好的选项可供选择时,让算法抛硬币要比描述一个合适的确定性规则容易得多。
而且随机化是很多实验的基础。它有助于减轻与重要的已知和未知混杂因素相关的偏倚(如选择偏倚),并有助于统计分析的有效性。
但是要利用这些好处,我们首先需要学习如何控制量子位的概率性质。如果没有这种能力,量子计算机就只能是一个随机数发生器。
那么,让我们来看看如何利用单个量子位的测量概率。就在# YOUR CODE GOES HERE之后,我们现在使用的是qc.h(0)线。
在 Qiskit 中,我们在电路上应用量子位变换(qc),并引用量子位的位置(0)作为参数。函数h表示操作符——在本例中是哈达玛操作符。
为了理解哈达玛算符,我们首先需要了解量子态。通常,这是它变得非常数学化的地方,因为量子态是多维复向量空间中的一个向量。
我对数学很矛盾。数学是描述技术概念的好方法。数学是一种简洁而精确的语言。
相比之下,我们的自然语言,比如英语,既冗长又不精确。
解释一小群数学公式需要一整本书的自然语言。
但是我们大多数人理解自然语言的能力远远强于数学。只有最少的人看到一个未知的方程,并立即理解它。
对于所有其他人,包括我,这是一场真正的斗争。这要从符号的隐含意义说起。例如,有什么区别

最常见的是,大写字母表示方程式的答案。但是它们也可以表示一个矩阵或一组值。所以,这取决于你所处的环境。此外,通过在大写字母上加一个小帽子来表示操作符是被接受的惯例。所以,有经验的数学家知道 H^是一个算子。
但是,在量子力学中,所有算符都是矩阵。所以,我们甚至可以说,h 和 H^.没有区别
此外,许多等式忽略了中间的步骤。所以,不可能遵循方程的推导。当然,等式的剩余部分可能足以描述两个术语之间的关键关系,但它不会促进对主题的深入理解。
这就好比我告诉你量子计算是 21 世纪最具突破性的技术,但却没有为它提出理由。当然,你可能认为我是一个值得信赖的人,所以这可能是真的。但是如果有人问你对量子计算有什么看法呢?如果你能回答的只是网上这个值得信赖的家伙说这是下一个大事件,恐怕你没有表达你的观点。
顺便说一下,我想我在开始我的上一篇文章时,也有同样的说法,没有任何论证。所以,我想通过参考我在这篇文章中提供的论点来弥补这一点。
长话短说,一个数学方程把大量信息浓缩成很少的符号。但是仅仅因为你能比一本 400 页的书更快地写下来,并不意味着读者理解得也一样快。相比之下,如果读者独自一人,将需要更多的时间。
所以我通常会解释量子态,以及算符(比如 Hadamard 算符)对它的不同影响。
想象一个有北极和南极的圆,如下图所示。这两个极点代表了量子位的可能值。北极代表0,南极代表1。

作者图片
量子位状态是从中心到圆表面的向量。所以,当然,所有的州都有相同的起源,因为只有一个中心。本质特征是它接触表面的点。

作者图片
这不仅因州而异。但是,这个点与极点的接近程度,对于测量一个量子位元的可能性是决定性的。这个点离北极越近,就越有可能测量出量子位为0。

作者图片
因此,当我们旋转量子位元状态时,与一个磁极的接近度会增加,而与另一个磁极的接近度会成比例地减少。

作者图片
那么,现在我们来谈谈哈达玛算符。它只是通过从左上角到右下角的对角线来反映量子位的状态。

作者图片
因此,一个状态向量指向北极的量子位被镜像指向圆的赤道。
当然,从赤道到两极的距离是一样的。测量概率也是如此。

作者图片
由于 Qiskit 在指向北极的状态下初始化量子位,这解释了为什么在我们的例子中,我们看到测量量子位为0或1的概率相等。
这一形象化解释了 Hadamard 算子的进一步特征。例如,如果您应用它两次,它会恢复自身——无论其原始状态如何。但是,当然,这是因为镜像两次会产生原件。
# YOUR CODE GOES HERE
qc.h(0)
qc.h(0)

作者图片
此外,如果对指向南极的量子位应用哈达玛算符(1),它也会指向赤道,但方向相反。但是由于对极点的接近不关心方向,所以测量结果没有差别。
# YOUR CODE GOES HERE
qc.x(0)
qc.h(0)

作者图片
我们在这里秘密地使用了另一个量子算符——它和 Hadamard 算符一样常见。这是在 X 轴上反映量子状态的 NOT 运算符。所以它的函数名是x。

作者图片
所以,当然,NOT 运算符也会反转。但是如果量子位态指向赤道,NOT 算符就没有作用了。
量子算符惊人的直观——如果你恰当地看待它们的话。当然,这种用简单英语的解释缺乏数学等式的精确性和简明性。然而,我真的相信这个解释比所有方程捆绑在一起更有助于开始量子计算。
https://pyqml.medium.com/membership
不要错过下一集,订阅我的子栈频道。
量子机器学习要不要入门?看看 动手量子机器学习用 Python 。

免费获取前三章这里。
理解主题连贯性度量
原文:https://towardsdatascience.com/understanding-topic-coherence-measures-4aa41339634c
从主题建模工具箱中了解这个日常工具背后的细节

艾萨克·史密斯在 Unsplash 上拍摄的照片
如果你熟悉主题建模,你可能已经听说过主题一致性或主题一致性度量。在大多数关于主题建模的文章中,它被显示为一个代表整个主题的可解释性的数字,用于评估主题的质量。
但是,这个指标到底是什么?它是如何衡量“可解释性”的?我们应该不惜一切代价最大化它?
在这篇文章中,我们将深入这个话题来回答这些问题,并让你更好地理解话题连贯性测量。
让我们打开这个黑盒子。
摘要
一、记题模式
二、记题。评估主题
三。话题连贯如何起作用
-切分
-概率计算
-确认措施
-聚合
-把所有东西放在一起
四。理解 Gensim 中的模型
五、在一些例子中的应用
六。结论
参考文献
一、记忆主题模式
主题建模是最重要的自然语言处理领域之一。它旨在通过将文本数据集分解成两个分布来解释它:主题和单词。
它基于以下假设:
- 一个文本(文档)由几个主题组成
- 一个主题由一组单词组成
因此,主题建模算法是一种数学/统计模型,用于推断哪些主题更好地代表了数据。
为了简单起见,一个主题可以被描述为一个词的集合,像['球','猫','房子']和['飞机','云'],但在实践中,算法所做的是给我们词汇表中的每个词分配一个在给定主题中的'参与'值。具有最高值的单词可以被认为是话题的真正参与者。
按照这种逻辑,我们之前的例子应该是这样的:

主题示例。作者图片
二。评估主题
计算机不是人类。如前所述,主题建模算法依赖于数学和统计学。然而,从人类的角度来看,数学上最优的主题不一定是“好的”。
例如,主题建模算法可以找到以下主题:
- 话题 1:猫,狗,家,玩具。(大概是个好题目)
- 话题二:超级,护士,砖头。(大概是个烂题目)
从人类的角度来看,第一个主题听起来比第二个更连贯,但对于算法来说,它们可能同样正确。
有时,我们不需要创建的主题遵循一些可解释的逻辑,我们只是想,例如,将数据维度减少到另一个机器学习过程。
当我们寻求数据理解时,创建的主题应该是对人类友好的。因此,仅仅盲目地遵循主题模型算法背后固有的数学会把我们引向误导和无意义的主题。
因此,主题评估通常由定性的人工评估来补充,例如阅读每个主题中最重要的单词,并查看与每个文档相关的主题。不幸的是,这项任务非常耗时,而且对于包含数千个主题的大型数据集来说是不切实际的。它还需要关于数据集领域的先验知识,并且可能需要专家意见。
这就是话题连贯手段试图解决的问题。他们试图用一个独特的、客观的、易于评估的数字来代表人们对主题的“高质量感知”。
三。话题连贯性是如何工作的
理解这些指标如何工作的第一个关键点是关注“一致性”这个词。
通常,当我们谈论连贯性时,我们谈论的是一种合作特征。比如,一组论点如果相互印证,就是连贯的。
主题连贯性度量评估的是一个主题被一个文本集(称为参考语料库)支持的程度。它使用从参考语料库中提取的统计数据和概率,特别是集中在单词的上下文中,来给出主题的一致性分数。
这一事实突出了主题一致性度量的一个重要点:它不仅取决于主题本身,还取决于用作参考的数据集。

话题连贯性度量的输入和输出。作者图片
Rö der,m .等人在探索主题一致性度量空间中提出了主题一致性度量遵循的一般结构:

一般话题连贯性衡量结构。图片作者。受 Rö der,m .等人(2015 年)的启发。由 Freepik 创作的图标。
它由不同的独立模块组成,每个模块执行一个特定的功能,这些模块通过一个顺序的管道连接在一起。
也就是说,主题一致性度量是流水线,其接收主题和参考语料库作为输入,并输出表示“整体主题一致性”的单个实数值。希望这个过程可以像人类一样评估主题。
那么,让我们来了解它的每一个模块。
分割
分割模块负责创建成对的单词子集,我们将使用它们来评估主题的连贯性。
考虑到 W={w_1,w_2,…,w_n} 作为主题 t 的前 n 个最重要的单词,分段 S 的应用从 W 产生一组子集对。

其中该对的第二部分(W*)将用于确认第一部分(W’)。当我们在下一节讨论概率计算和确认措施时,这一点会变得更加清楚,在下一节中,我们将使用这些对。
简单来说,我们可以把分段理解为我们选择如何“混合”我们主题中的单词来评估它们的步骤。
例如,分段 S-one-one,表示我们需要将不同的单词组成单词对。因此,如果 W = { '猫','狗','玩具' },我们将有:

因此,通过使用这种技术,我们可以说,为了计算最终的一致性分数,我们的模型对我们主题中任意两个词之间的关系感兴趣。
另一个例子是分割 S-one-all,这意味着我们需要将每个单词与所有其他单词配对。将其应用于 W ,我们发现:

再一次,通过使用这种技术,我们说我们的连贯性分数将基于一个单词和我们主题中其他单词之间的关系。
概率计算
如前所述,连贯性度量使用从文本语料库中提取的概率。概率计算步骤定义了如何计算这些概率。
例如,假设我们对两种不同的概率感兴趣:
- P( w ):单词 w 的出现概率
- P( w1 和 w2 ):单词 w1 和 w2 的出现概率
不同的技术将不同地估计这些概率。
例如,PBD(BD代表布尔文档)计算 P( w )为单词 w 出现的文档数除以文档总数,P( w1 和 w2 )为两个单词出现的文档数除以总数。
另一个例子是Pbs(bs代表布尔语句),这与前面的方法相同,但是它考虑的是语句中的出现次数,而不是整个文档中的出现次数。再比如PSW(SW代表滑动窗口),它考虑的是文本上方滑动窗口中的事件。
这些概率是一致性的基础。它们在下一步用来巩固题目的分数。
确认措施
确认手段是话题连贯的核心。
使用在 P 中计算的概率,在对 S 上计算确认测量。它计算每对中子集 W*支持子集 W’的程度。
也就是说,这个步骤试图通过使用从语料库计算的概率来量化这两个子集之间的“关系”。因此,如果 W’的单词与 W*中的单词相关联(例如,通过非常频繁地出现在同一文档中),则确认度量将是高的。

由作者制作的确认措施 m. 图像的应用示例。
在上图中,我们可以看到这个过程的一个例子。将确认度量 m 应用于在分割步骤中创建的每一对,输出确认分数。
有两种不同类型的确认措施,我们将在下面几行中理解。
直接确认措施
这些测量通过直接使用子集 W’和 W*以及概率来计算确认值。您可以探索大量的直接测量方法,但我们只举几个例子:



我们不打算深究这些方程背后的数学,但请记住,它们来自许多科学领域,如信息论和统计学。
间接确认措施
总之,间接确认措施不直接根据 W '和 W*计算分数。相反,他们对 W’中的单词和 W 中的所有其他单词计算直接确认度量 m ,构建度量向量,如下所示:

对 W*进行同样的处理。
最后的确认度量是这两个向量之间的相似性(例如,余弦相似性)。

下图显示了如何完成此过程的示例。为了计算对(' cat ',' dog ')中的确认度量,我们首先计算主题中所有单词的每个元素的确认度量 m ,创建确认度量向量。然后,最后的确认度量是这两个向量之间的相似性。

作者应用间接确认措施 m. 图像的示例。
这个过程背后的想法是强调一些直接方法可能遗漏的关系。例如,单词“猫”和“狗”可能永远不会在我们的数据集中一起出现,但它们可能会经常与单词“玩具”、“宠物”和“可爱”一起出现。使用直接方法,这两个单词将具有较低的确认分数,但是使用间接方法,相似性可以被突出,因为它们出现在相似的上下文中。
计算完这些度量后,我们可以进入下一步。
聚合
这是最后也是最简单的一步。
它将上一步计算的所有值聚合成一个值,这就是我们最终的主题连贯性得分。

聚合示例。图片作者。
这种聚合可以是算术平均值、中值、几何平均值等等。
把所有东西放在一起
所以我们来回顾一下步骤。
- 我们有一个想要评估的主题。
- 我们选择一个参考语料库。
- 找到了主题中前 n 个最重要的单词,我们称它们为 w。
- 使用技术 s 将 w分割成子集对。
- 使用参考语料库,我们使用技术 p 计算单词概率。
- 利用分割的单词和概率,我们使用一个确认度量 m 来评估每一对的子集之间的“关系”。
- 上一步的所有测量值都用一个最终的数字聚合,这就是我们的主题连贯得分。
下图总结了所有这些步骤:

一致性模型的详细示例。图片作者。
在我们有许多话题的情况下(这是最常见的),最终的结果只是平均的单个话题连贯性。

多个主题的连贯分数。图片作者。
四。理解 Gensim 中的模型
好了,现在我们已经了解了主题连贯性测量是如何工作的,让我们将我们的知识应用到一个真实的例子中。
Gensim 库提供了一个类,实现了四个最著名的一致性模型: u_mass,c_v,c_uci,c_npmi。那么,让我们把它们分成几个基本部分。
如果我们看看这篇论文(roder 等人,2015 年),我们会发现作者已经详细描述了这些相干模型是如何构建的。

表 2:名题连贯度。图片作者。受 Rö der,m .等人(2015 年)的启发。
所以,要理解一个模型,我们只需要理解它的组成部分。
让我们以 C_NPMI 为例。
- 分词:采用一对一的方法,即在成对的词上计算确认度。
- 概率计算:使用方法 P sw (10) 。在文本上移动的大小为 10 的滑动窗口上计算概率。
- 确认度量:每对的确认度量将是归一化的点态互信息(NPMI)。

- 聚合:最终一致性是确认措施的算术平均值。
我们也来分析一下方法 C_V。
- 切分:使用 S-one-set 的方法,即在词和集合 w 的对上计算确认度。
- 概率计算:使用方法PSW(110)。概率是在文本上移动的大小为 110 的滑动窗口上计算的。
- 确认措施:采用间接确认措施。使用度量 m _nlr 将每对元素的单词与 W 的所有其他单词进行比较。最终得分是两个度量向量之间的余弦相似性。
- 聚合:最终一致性是确认措施的算术平均值。
为了更好地理解这些符号,我强烈建议查看原始论文。
动词 (verb 的缩写)在一些例子中应用
幸运的是,我们不需要知道所有这些细节来使用 Gensim 的指标。
因此,作为最后的练习,让我们使用数据集20 个新闻组作为我们的参考语料库来评估一些主题。
首先,我们导入需要的库。
然后,让我们获取并标记文本。
在 Gensim 中使用 coherence 模型非常简单,我们只需要创建一组主题并传递我们的文本。下面的代码创建了一些我们想要评估的主题。
我选择了数据库中我已经知道的特定主题。
方法。get_coherence_per_topic() 返回每个主题的一致性值。让我们使用 Seaborn 可视化结果。
结果如下所示。

评估结果。图片作者。
不及物动词结论
主题建模是自然语言处理中最相关的主题之一,是监督和非监督机器学习应用的有力工具。
话题连贯性是衡量话题质量的一个非常重要的指标。在这篇文章中,我们深入探讨了主题一致性度量背后的基本结构和数学原理。我们探讨了组成主题一致性度量的模块:分段、概率计算、确认度量和聚合,并了解了它们的作用。我们还学习了 Gensim 中实现的主要主题一致性度量,以及一些代码示例。
我希望你发现自己在使用这些方法时更加自信。主题连贯的世界是巨大的,我强烈推荐进一步阅读这方面的内容。
感谢您的阅读。
参考
[1]罗德,m .,两者,a .,&欣内堡,A. (2015)。探索话题连贯度量的空间。在第八届 ACM 网络搜索和数据挖掘国际会议论文集(第 399–408 页)。
[2] Rehurek,r .,& Sojka,P. (2011 年)。Gensim–用于向量空间建模的 python 框架。捷克共和国布尔诺马萨里克大学信息学院 NLP 中心, 3 (2)。
了解优信网
原文:https://towardsdatascience.com/understanding-u-net-61276b10f360
U-Net 已经成为图像分割的首选方法。但是这是怎么发生的呢?

目录
1。手头的任务
2。编码器-解码器
3。跳过连接
4。实现细节
a .损失函数
b .上采样方法
c .去填充还是不去填充?
5。U-Net 在行动
手头的任务
U-Net 是为语义切分任务而开发的。当向神经网络输入图像时,我们可以选择对对象进行一般分类或实例分类。我们可以预测图像中是什么对象(图像分类),所有对象的位置(图像定位/语义分割),或者单个对象的位置(对象检测/实例分割)。下图显示了这些计算机视觉任务之间的差异。为了简化问题,我们只考虑只对一个类和一个标签进行分类(二元分类)。

单标签的不同计算机视觉任务。原图在 Pexels.com 获得创意共同许可。编辑由作者完成。
在分类任务中,我们输出大小为 k 的向量,其中 k 是类别的数量。在检测任务中,我们需要输出定义边界框的向量x, y, height, width, class。但是在分割任务中,我们需要输出一个与原始输入维数相同的图像。这代表了一个相当大的工程挑战:神经网络如何从输入图像中提取相关特征,然后将它们投影到分割掩模中?
编码器-解码器
如果你不熟悉编码器-解码器,我推荐你阅读这篇文章:
编码器-解码器之所以相关,是因为它们产生的输出与我们想要的相似:输出与输入具有相同的维数。我们可以将编码器-解码器的概念应用于图像分割吗?我们可以生成一维二进制掩码,并使用交叉熵损失来训练网络。我们的网络由两部分组成:从图像中提取相关特征的编码器,以及提取特征并重建分割掩模的解码器部分。

一种用于图像分割的编解码网络。图片由作者提供。
在编码器部分,我使用了卷积层,后面是ReLU和MaxPool作为特征提取器。在解码器部分,我转置卷积以增加特征图的大小并减少通道的数量。我使用填充来保持卷积运算后特征图的大小不变。
您可能会注意到,与分类网络不同,该网络没有完全连通/线性图层。这是一个全卷积网络 (FCN)的例子。从 Shelhamer 等人的论文“语义分割的完全卷积网络”【1】开始,FCN 已经被证明在分割任务上工作得很好。
然而,这个网络有一个问题。随着我们在编码器和解码器层中扩展层数,我们有效地越来越“缩小”特征图。这样,编码器可以丢弃更详细的特征,以支持更一般的特征。如果我们正在处理医学图像分割,被分类为患病/正常的每个像素可能是重要的。我们如何才能确保这个编码器-解码器网络能够吸收通用和详细的功能?
跳过连接
因为深度神经网络可以在连续层传递信息时“忘记”某些特征,所以跳过连接可以重新引入它们,使学习更强。在残差网络(ResNet)中引入了跳过连接,并显示了分类改进以及更平滑的学习梯度。受这种机制的启发,我们可以向 U-Net 添加跳过连接,这样每个解码器都可以从其对应的编码器中合并特征映射。这是 U-Net 的一个定义特征。

U-Net 是一个带跳跃连接的编码器-解码器分段网络。图片由作者提供。
U-Net 有两个决定性的特点:
- 一种编码器-解码器网络,其越深入提取越多的一般特征。
- 将详细特征重新引入解码器的跳过连接。
这两个特性意味着 U-Net 可以使用详细和一般的特征进行细分。U-Net 最初是为生物医学图像处理而引入的,其中分割的准确性非常重要[2]。
实施细节

Pavel Neznanov 在 Unsplash 上拍摄的照片
前面几节对 U-Net 及其工作原理做了非常全面的概述。然而,细节介于一般理解和实际实施之间。在这里,我概述了 U-Net 的一些实现选择。
损失函数
因为目标是二进制掩码(当像素包含对象时,像素值为 1),比较输出与地面真实值的常见损失函数是分类交叉熵损失(或在单标签情况下的二进制交叉熵)。

在最初的 U-Net 论文中,在损失函数中增加了一个额外的权重。这个权重参数做了两件事:它补偿类的不平衡,并且它给予分割边界更高的重要性。在我在网上发现的许多 U-Net 实现中,这个额外的权重因子并不经常使用。
另一个常见的损失函数是骰子损失。骰子损失通过比较两组图像的相交面积与它们的总面积来测量两组图像的相似程度。请注意,骰子损失与并集交运算(IOU)不同。他们测量相似的东西,但他们有不同的分母。骰子系数越高,骰子损失越低。

这里,添加了一个ε项以避免被 0 除(ε通常为 1)。一些实现,例如 Milletari 等人的实现,在对分母中的像素值求和之前对它们进行平方[3]。与交叉熵损失相比,dice 损失对不平衡分割掩模非常鲁棒,这在生物医学图像分割任务中是典型的。
上采样方法
另一个细节是解码器上采样方法的选择。以下是一些常见的方法:
双线性插值。该方法使用线性插值预测输出像素。通常,通过这种方法放大后会有一个卷积层。
最大卸载量。这种方法与最大池相反。它使用 maxpool 操作的索引并用最大值填充这些索引。所有其他值都设置为 0。通常,卷积层跟随 max-unpooling 以“平滑”所有丢失的值。
反卷积/转置卷积。许多博客文章都是关于反卷积的。我推荐这篇文章作为一个很好的视觉指南。
去卷积有两个步骤:给原始图像中的每个像素添加填充,然后应用卷积。在原始 U-Net 中,使用步长为 2 的 2x2 转置卷积来改变空间分辨率和通道深度。
像素洗牌。这种方法见于 SRGAN 等超分辨率网络。首先,我们使用卷积从C x H x W特征图到(Cr^2) x H x W。然后,像素洗牌将采取这一点,并“洗牌”的马赛克方式像素,以产生大小C x (Hr) x (Wr)的输出。
垫还是不垫?
内核大于 1x1 且没有填充的卷积层将产生小于输入的输出。这是优信网的问题。回想一下上一节的 U-Net 图,我们将图像的一部分与其解码后的对应部分连接起来。如果我们不使用填充,那么与编码图像相比,解码图像将具有更小的空间维度。
但是,最初的 U 网纸没有使用衬垫。尽管没有给出理由,我相信这是因为作者不想在图像边缘引入分割错误。相反,他们在拼接之前对编码图像进行中心裁剪。对于输入大小为572 x 572的图像,输出将为388 x 388,大约 50%的损失。如果你想在没有填充的情况下运行 U-Net,你需要在重叠的瓦片上运行多次,以获得完整的分割图像。
U-Net 在行动
在这里,我实现了一个非常简单的类似 U-Net 的网络来分割椭圆。U-Net 只有 3 层深,使用相同的填充和二进制交叉熵损失。更复杂的网络可以在每个分辨率下使用更多的卷积层,或者根据需要扩展深度。
import torch
import numpy as np
import torch.nn as nn
class EncoderBlock(nn.Module):
# Consists of Conv -> ReLU -> MaxPool
def __init__(self, in_chans, out_chans, layers=2, sampling_factor=2, padding="same"):
super().__init__()
self.encoder = nn.ModuleList()
self.encoder.append(nn.Conv2d(in_chans, out_chans, 3, 1, padding=padding))
self.encoder.append(nn.ReLU())
for _ in range(layers-1):
self.encoder.append(nn.Conv2d(out_chans, out_chans, 3, 1, padding=padding))
self.encoder.append(nn.ReLU())
self.mp = nn.MaxPool2d(sampling_factor)
def forward(self, x):
for enc in self.encoder:
x = enc(x)
mp_out = self.mp(x)
return mp_out, x
class DecoderBlock(nn.Module):
# Consists of 2x2 transposed convolution -> Conv -> relu
def __init__(self, in_chans, out_chans, layers=2, skip_connection=True, sampling_factor=2, padding="same"):
super().__init__()
skip_factor = 1 if skip_connection else 2
self.decoder = nn.ModuleList()
self.tconv = nn.ConvTranspose2d(in_chans, in_chans//2, sampling_factor, sampling_factor)
self.decoder.append(nn.Conv2d(in_chans//skip_factor, out_chans, 3, 1, padding=padding))
self.decoder.append(nn.ReLU())
for _ in range(layers-1):
self.decoder.append(nn.Conv2d(out_chans, out_chans, 3, 1, padding=padding))
self.decoder.append(nn.ReLU())
self.skip_connection = skip_connection
self.padding = padding
def forward(self, x, enc_features=None):
x = self.tconv(x)
if self.skip_connection:
if self.padding != "same":
# Crop the enc_features to the same size as input
w = x.size(-1)
c = (enc_features.size(-1) - w) // 2
enc_features = enc_features[:,:,c:c+w,c:c+w]
x = torch.cat((enc_features, x), dim=1)
for dec in self.decoder:
x = dec(x)
return x
class UNet(nn.Module):
def __init__(self, nclass=1, in_chans=1, depth=5, layers=2, sampling_factor=2, skip_connection=True, padding="same"):
super().__init__()
self.encoder = nn.ModuleList()
self.decoder = nn.ModuleList()
out_chans = 64
for _ in range(depth):
self.encoder.append(EncoderBlock(in_chans, out_chans, layers, sampling_factor, padding))
in_chans, out_chans = out_chans, out_chans*2
out_chans = in_chans // 2
for _ in range(depth-1):
self.decoder.append(DecoderBlock(in_chans, out_chans, layers, skip_connection, sampling_factor, padding))
in_chans, out_chans = out_chans, out_chans//2
# Add a 1x1 convolution to produce final classes
self.logits = nn.Conv2d(in_chans, nclass, 1, 1)
def forward(self, x):
encoded = []
for enc in self.encoder:
x, enc_output = enc(x)
encoded.append(enc_output)
x = encoded.pop()
for dec in self.decoder:
enc_output = encoded.pop()
x = dec(x, enc_output)
# Return the logits
return self.logits(x)

任务是:分割椭圆,不分割任何其他形状。图由作者生成。
正如我们所看到的,即使没有跳过连接,U-Net 也可以产生可接受的分段,但是添加的跳过连接可以引入更好的细节(参见右边两个椭圆之间的连接)。
结论
如果要我用一句话来解释 U-Net,那就是 U-Net 就像一个图像的编码器-解码器,但通过跳过连接来确保细节不会丢失。U-Net 经常在许多分割任务中使用,近年来也在图像生成任务中使用。
如果你想看我用来产生数字和训练我的 U-Net 的代码,这里有 Github 链接。编码快乐!
参考资料:
[1] Long,Jonathan,Evan Shelhamer 和 Trevor Darrell。"语义分割的完全卷积网络."IEEE 计算机视觉和模式识别会议论文集。2015.
[2] Ronneberger,Olaf,Philipp Fischer 和 Thomas Brox。" U-net:生物医学图像分割的卷积网络."医学图像计算和计算机辅助介入国际会议。施普林格,查姆,2015。
[3] Milletari、Fausto、Nassir Navab 和 Seyed-Ahmad Ahmadi。" V-net:用于体积医学图像分割的全卷积神经网络." 2016 第四届 3D 视觉国际会议。IEEE,2016。
理解你的神经网络的预测
原文:https://towardsdatascience.com/understanding-your-neural-networks-predictions-d9290f8b984f
评估神经网络特征重要性的简明指南。

作者图片
神经网络极其方便。它们可用于回归和分类,处理结构化和非结构化数据,非常好地处理时态数据,如果给它们足够的数据量,通常可以达到高性能。
然而,便利性带来的好处却失去了可解释性,当模型被呈现给非技术观众,如客户或利益相关者时,这可能是一个重大的挫折。
例如,去年,我所在的数据科学团队想要说服一个客户从决策树模型转向神经网络,理由很充分:我们可以访问大量数据,其中大部分都是暂时的。客户同意,但希望了解模型决策的基础,这意味着评估其特性的重要性。
有意义吗?
这是有争议的。对于决策树或 boosting 模型,对于大多数决策树,可以使用拟合属性feature_importances_直接检索特征的重要性,对于 XGBoost 模型,可以使用get_booster()和get_score()方法。
对于神经网络来说,这些属性和方法是不存在的。每个神经元都被训练成根据它接收到的信号来学习何时激活或不激活,以便每一层都从原始输入中提取一些信息或概念,直到最终的预测层。因此,检索一个更“黑盒”类型的模型的特征重要性的有用性是值得怀疑的。
我甚至听到深度学习专家说,最好让数据说话,不要试图过多地理解模型。基本上,知道猫的皮毛是否比它的眼睛对神经网络更有影响有用吗?也许不是。但是有必要知道,对于这个模型来说,桌子上的猫和地板上的猫是一样的,这就是我们在这里要做的。
置换、扰动和评估
我们将使用排列重要性方法。对于经典的机器学习模型,Scikit-Learn 提供了一个函数来做到这一点,甚至在处理高基数特性时推荐它。如果您想要在您的模型上使用这个函数,这个代码片段将计算并显示它的排列重要性:
排列重要性背后的原理
假设你有几个学生,你想评估他们通过数学考试的可能性。要做到这一点,你可以使用 3 个变量:他们花在学习考试上的时间,他们在数学上的难易程度,以及他们的头发颜色。

数学考试的学生数据。作者图片
在这个例子中,Paul 学习了很多,并且在数学方面有一定的天赋。他很有可能通过数学考试。迈克,另一方面,学习少得多,不是很有天赋,他不太可能成功。鲍勃根本没有学习,但他非常有天赋,因此他有机会。
让我们重新排列“学习时间”特性的值:

调整第一列的影响。作者图片
保罗从大量学习变成了完全不学习。他在数学上的中等轻松不足以弥补,他现在不太可能通过。同样,其他学生成功的可能性也受到这种干扰的极大影响。
因此,我们可以推断,学习时间是预测考试结果的一个重要特征。
当我们扰动数学中的 ease 特性时,我们得到了相同的结果:

调整第二列的影响。作者图片
鲍勃现在对数学毫无天赋,根本没有学习过。他通过考试的可能性极小。
和前面的推理一样,这个特性也很重要。
现在,当我们改变头发颜色特征时:

调整第三列的影响。作者图片
迈克从金发变成黑发不会增加他考试的机会,头发颜色的改变也不会对任何学生产生任何影响。因此,这个特征对我们的预测并不重要。
这种方法的局限性
假设 100 个学生中,我们有一个作弊的人设法拿到了考试科目,这保证了他通过考试。如果我们改变“作弊者”一栏,我们将只有一个学生从作弊者变成非作弊者,另一个学生从非作弊者变成作弊者。100 个学生中,只有两个会受到影响,我们会错误地认为这个特征不重要,因为它的患病率很低。
因此,这种方法对于不平衡的二进制特征和分类特征的罕见模态不能很好地工作。对于这些情况,最好将整个列设置为稀有值,并查看它如何影响预测(在我们的类比中,这意味着将每个学生的“作弊”列设置为 True)。
履行
第一步是在测试集上做出不受干扰的推断。然后,对于每一个特征,你将随机洗牌,做出我称之为扰动推断的结论。
一旦所有被干扰的推论都被做出,将它们连接在一个单一的数据框架中,然后计算每个观察值与原始预测值相比有多远的偏差。
在此基础上,一个可视化每个扰动影响的好方法是制作一个所有观测偏差的箱线图。
例如,让我们使用 Kaggle 数据集进行家庭信用违约风险竞争。
在预处理和训练阶段之后,我得到了两个数据集,X_test包含测试集的静态数据,X_test_batch包含训练集的时态数据。
下面的代码片段遍历了每个特性,并创建了一个受干扰的推论:
然后,这个代码片段将计算每个扰动与原始推断的偏差:
最后,这段代码将打印特性重要性:
你应该得到这样一个图:

如果你想看到整个数据科学管道,我制作了一个公共的 docker 图像,其中包含了从原始数据到功能重要性图的所有步骤:https://hub . docker . com/r/villatteae/neural net _ feat _ importance/tags
只需运行以下 docker 命令:
docker pull villatteae/neural net _ feat _ importance:最新
docker pull villatteae/neuralnet_feat_importance
docker run -p 10000:10000 -d villatteae/neuralnet_feat_importance
该映像将在您的 localhost:10000 地址上运行。实例的用户名和密码是 admin 和 admin。注意图像相当重(~17 GB)。
从稳定扩散生成的图像中发现性别、肤色和交叉群体存在不公平偏见的证据(第 1 页,共 3 页)
女性中,肤色较深的人产生的几率明显较低

稳定扩散生成的图像。提示:“办公桌后的医生”
简介和动机
或 跳到详情
在过去的一周里,经过几个月对各种开源生成模型的研究,我开始了我称之为“研究”的工作(也就是说,方法是近乎合理的,结论可能与更严格的工作得出的结论大致相同)。目标是对生成图像模型是否以及在多大程度上反映其预测中的性别或肤色偏见形成一些直觉,这可能导致特定的伤害,具体取决于使用的背景。
随着这些模式的激增,我认为我们很可能会看到初创公司和现有技术公司在新的创新产品和服务中部署它们。虽然我能从他们的角度理解这种吸引力,但我认为我们一起努力理解这些系统在各种情况下可能造成的局限性和潜在危害是很重要的,也许最重要的是,我们共同努力使利益最大化,同时使风险最小化。因此,如果这项工作有助于推进这一目标,那么# mission 就完成了。
目录
摘要
这项研究的目标是确定(1)在给定性别和肤色中性提示的情况下,在生成“医生”图像时,稳定扩散 v1–4⁵在多大程度上违反了人口统计均等。这假设基础模型中的人口统计均等是一个期望的特征。根据使用环境,这可能不是一个有效的假设。此外,我(2)定量调查 LAION5B 数据集中稳定扩散背后的采样偏差,以及(3)对其治疗中的覆盖和无反应偏差的定性意见。
在这篇文章中,我处理目标#1 ,其中,通过使用僧侣肤色(MST)标度的二值化版本的 221 个生成图像的评价者 review⁷,观察到 that⁴:
人口均等= 50%:
- 36%的时间都在制造女性形象
- 肤色较深的人物(蒙克 06+)有 6%的时间被制作出来
人口均等= 25%:
- 有 4%的时间会产生肤色较深的女性形象
- 有 3%的时间会产生肤色较深的男性形象
因此,似乎稳定扩散偏向于生成具有较浅皮肤的感知男性形象的图像,对具有较深皮肤的形象有明显的偏向,以及对总体上感知的女性形象有明显的偏向。
方法
该研究使用 PyTorch 对来自拥抱脸的稳定扩散 v1–4⁵进行,使用扩散模型(PNDM)调度程序和 50 num_inference_steps的缩放线性伪数值方法。安全检查被禁用,推理在 Google Colab GPU runtime⁴.上运行在 56 批共 224 幅图像中,每组 4 幅图像在相同的提示下生成(“一名医生坐在桌子后面”)(其中 3 幅因不包括人物而从研究中删除)。这种迭代方法用于最小化样本量,同时产生彼此明显可分的置信区间。

通过稳定扩散生成的样本研究图像。提示:“办公桌后的医生”
与此同时,生成的图像由一个评审员(我)按照以下 dimensions⁷:进行注释
male_presenting//二进制// 1 =真,0 =假female_presenting//二进制// 1 =真,0 =假monk_binary// Binary // 0 =身材肤色一般出现在 MST 05 或以下(又名“更浅”)。1 =身材肤色通常出现在 MST 06 或以上(也称为“较暗”)。confidence//分类//评审者对其分类的判断可信度。
值得注意的是,这些维度是由来自特定文化和性别经历的单个评审员评估的。此外,我还依赖于历史上西方感知的性别线索,如头发长度、化妆和体型,将数字划分为感知的二元男性和女性类别。敏感地意识到这样做而不承认其荒谬性本身就有具体化有害的社会 groups⁸的风险,我想确保清楚地承认这种方法的局限性。
就肤色而言,同样的道理也适用。事实上,人们最好从不同的背景中寻找评价人,并在更丰富的人类经验范围内使用多评价人协议来评价每幅图像。
综上所述,集中于所描述的方法,我使用 jacknife 重采样来估计每个亚组(性别和肤色)以及每个交叉组(性别+肤色组合)在 95%置信水平下的均值的置信区间。这里,平均值表示每组相对于总数(221 幅图像)的比例(%)。请注意,在本研究中,我有意将亚组概念化为互斥和集体穷举,这意味着对于性别和肤色,人口统计均等是二元的(即 50%代表均等),而对于交叉组,均等等同于 25%⁴.同样,这显然是简化的。
结论
基于这些方法,我观察到,当给出性别和肤色中性的提示来产生医生的图像时,稳定扩散偏向于产生具有较浅皮肤的感知男性形象的图像。它还显示了对深色皮肤的人的明显偏见,以及对女性形象 overall⁴:的明显偏见

研究结果。人口代表性估计值和置信区间,以及人口统计均等标记(红线和蓝线)。图片由丹妮·塞隆拍摄。
当考虑相关亚组人口统计学奇偶标记的点估计值的置信区间宽度时,这些结论没有本质上的不同。
这是机器学习中不公平偏见的工作可能会停止的地方。然而,Jared Katzman 等人最近的工作。艾尔。提出有益的建议,让我们可以走得更远;将一般的“不公平偏见”重新构建为代表性伤害的分类,这有助于我们更敏锐地诊断不利结果,并更准确地瞄准 mitigations⁸.我认为这需要特定的使用环境。所以,让我们想象一下,这个系统正被用来自动生成医生的图像,这些图像被实时显示在大学医学院的招生页面上。也许是为每个访问用户定制体验的一种方式。在这种情况下,使用卡兹曼的分类法,我的结果表明,这样一个系统可能通过系统地低估受影响的亚群体(肤色较深和被认为具有女性特征的人物)来刻板印象社会 groups⁸ 。我们可能还会考虑这些类型的失败是否会剥夺人们通过代理人进行自我 identify⁸的机会,尽管事实上图像是生成的,并不代表真实的人。
讨论
值得注意的是,Huggingface 的稳定扩散模型卡 v1–4 本身揭示了 LAION5B 的事实,因此模型本身在训练示例中可能缺乏人口统计均等性,因此可能反映了训练分布中固有的偏差(包括对英语、西方规范和系统性西方互联网使用 patterns)⁵.的关注因此,这项研究的结论并不出人意料,但差异的规模可能对考虑特定用例的从业者有用;强调在生产模型决策之前可能需要积极缓解的领域。
在我的下一篇文章中,我将处理目标#2 :定量调查稳定扩散背后的 LAION5B 数据集中的采样偏差,并将其与来自目标#1 的结果进行比较。
参考资料和资源
- 机器学习词汇表:公平性,2022,谷歌
- 开始使用和尚肤色量表,2022,谷歌
- 研究生成的图像,2022,丹妮·塞隆
- 来自研究的代码,2022,Danie Theron
- 稳定扩散 v1–4,2022,Stability.ai &拥抱脸
- LAION5B 剪辑检索前端,2022,罗曼·博蒙特
- 评估人审查 2022 年研究的结果,Danie Theron
- 图像标注中的表征危害,2021,Jared Katzman 等人。
感谢
感谢杨轩和其他评论者对本文的深思熟虑和勤奋的评论和反馈。
对你的时间序列异常不满意?合成他们!
原文:https://towardsdatascience.com/unhappy-about-your-time-series-anomalies-synthesize-them-908495475947
生成您自己的具有真实异常的多元时间序列数据集

如果您曾经处理过时间序列数据的异常检测问题,您可能已经搜索过包含相关异常的带注释的数据集。而你可能在这个搜索中挣扎过,尤其是在寻找适合研究工业物联网用例的多元时间序列数据时。此外,即使当您处理真实的传感器数据时,您可能仍然很难找到异常,根据定义,这些异常在制造过程中是罕见的。
在写一些关于异常检测的文章时,我搜索了这样一个好的数据集来说明我的思维过程。基本上,我需要:
- 包括多个传感器的多元数据集
- 跨越几个月(最好是一年)
- 以合理的采样率(大约 1 到 5 分钟)
- 包括一些已知的异常来验证我的结果
我找到的唯一满足所有这些标准的数据集是 Kaggle 上的一个 水泵数据集 。不幸的是,没有与该数据集相关联的许可,这使得它不可能在诸如 Medium 之类的出版物上实际使用,因为这被认为是一种商业用途。
因此,我决定尝试使用我在处理工业传感器和制造过程数据时遇到的异常类型来生成自己的多元数据集…
我鼓励你跟随这篇博文,浏览 GitHub 来获得这个系列的 Jupyter 笔记本https://github.com/michaelhoarau/smarter-anomaly-detection。你可以使用你常用的 Jupyter 环境,或者用 Amazon SageMaker 创建一个。在您克隆了 repo 之后,您可以打开第一个文件(synthetic_0_data_generation.ipynb)并按照本文进行操作。
初始化
我们将从头开始构建数据集,仅使用基本的 Python 库:
这里没有什么特别的:数据处理库(numpy和pandas)、random生成器库和matplotlib来可视化我们的时间序列。在处理日期操作时,我喜欢使用relativedelta方法:当涉及到在给定信号中组合几种类型的异常时,我们将使用它。
有了这个,我们就可以开始了!让我们从生成我们的时间序列的基本信号开始…
生成基线
我们将首先定义数据集的范围:
我想以 10 分钟的常规采样率生成一年的数据。然后,我用这个日期时间索引生成一个空的数据帧。
随机生成值
您的第一反应可能是沿着我们刚刚创建的日期时间轴生成随机值:
这段代码产生以下结果:

随机值生成(图片由作者提供)
这种白噪声看起来不真实:没有模式可寻,使用这种方法产生多个信号来合成多变量数据集对于模拟真实过程不是很有用。如果你在测量一台机器的温度,数值不会如此混乱地变化。这就是随机漫步发挥作用的地方……
利用随机漫步过程
真实的数据应该显示模式:在给定的时间点,一个值实际上会与以前的值有某种程度的关系。在概率学中,随机行走是在给定向某个方向移动的概率的情况下,确定对象的可能位置(这里是我们的时间序列的值)的过程。
下面是我用来生成随机游走的函数:我的时间序列从一个初始值(start)开始,然后我随机添加一个量(step)。我可以使用min_value和max_value参数将我的时间序列约束在一定范围内。我也可以玩概率probability来给我的时间序列一个减少或增加的趋势:
让我们生成几个图来可视化我们可以获得的行为:
这是这段代码的结果图:

随机漫步生成的时间序列(图片由作者提供)
这看起来好多了:这些信号实际上看起来很真实!我现在将使用该函数为位于不同范围内的 20 个信号的多元数据集生成基线(我对此使用不同的初始值)。这将模拟测量过程不同维度的传感器数据(例如)。这是我得到的结果:****

用随机游走过程生成的多元数据集的基线(图片由作者提供)
让我们现在添加一些异常在那里…
添加异常
添加电平转换
当一个过程或一系列设备经历不同的操作模式时,可以看到时间序列数据中的电平移动。当环境条件经历突然变化时,这也可能发生。下面是我用来模拟作为 Pandas 系列输入的给定信号电平转换的函数:
此功能允许您在给定的时间点(在start和end之间)向一个方向(magnitude_shift)移动给定时间序列的一部分。当一个转变发生时,你的信号也可能更平滑(magnitude_multiply < 1.0)或者更混乱(magnitude_multiply > 1.0)。
这里有两个由该函数产生的电平转换示例:

电平转换示例(图片由作者提供)
在第一个信号上,我们增加了一个正电平移动和一个平滑的信号。在第二种情况下,电平移动是负的,我们在这个时间范围内模拟一个更混乱的行为。
以下是生成该图的相关代码:
现在,我将使用该函数向随机选择的三个信号添加两个随机电平转换。我还会将另一个随机电平移动添加到五个其他信号中,这些信号也是随机选择的:

添加了随机等级偏移的多元数据集(图片由作者提供)
这种异常很常见,但是很容易发现。现在,让我们看看如何将渐变添加到信号中,以模拟过程的缓慢退化…
添加趋势或渐变
为了给时间序列添加渐变,我使用了以下函数:
基本上,我生成一个新的随机行走,用一个degradation_slope修改随机行走概率,用一个degradation_speed修改随机行走步骤。使用此函数,我将对我的多元数据集中随机选择的一些信号添加几个降级(参见下面的紫色和黄色信号):

添加了缓慢降解的多元数据集(图片由作者提供)
添加灾难性故障
通常,在数据集中出现缓慢的退化模式后,灾难性的故障可能随之而来。为了模拟这种情况,我将在退化模式后立即将几个信号设置为 0.0。对此我有一个非常简单的函数:
查看我的笔记本,看看我是如何在我们刚刚生成的降级模式之后添加这些故障的:

添加了突发故障的多元数据集(图片由作者提供)
瞧!如果您想查看在这种类型的数据集上训练和评估异常检测模型的示例,请查看以下文章:
**
结论
在本文中,您了解了如何利用一些合成生成技术来创建具有真实外观的多元时间序列数据。
我希望你觉得这篇文章很有见地:如果你不想错过我即将发布的帖子,请随时在这里给我留下评论,并不要犹豫订阅我的 中型电子邮件源 !想支持我和以后的工作?通过我的推荐链接加入 Medium :
https://michoara.medium.com/membership **
对统计学意义(p 值)不满意?这里有一个简单的解决方案
为什么以及如何高效利用林地?

从回归到森林情节,作者的形象
人类喜欢用一种简单的方式把东西放进垃圾箱:统计显著或不统计显著。这种对统计结果的二分法必须停止!近十年来,我听到了同样的辩论。一方面,人们认为统计显著性具有简单明了的优势(许多人已经在努力实现这一点)。另一方面,由于 p 值的局限性,有人主张采用更高级的模型(如贝叶斯方法)(见下一节)。我介于两者之间。我意识到这是有用的,因为它简单实用,而且我承认我不能对许多学生(包括一些科研人员)提出更多的要求。另一方面,我也被这种观点的问题性所困扰。
然而,今天我使用的解决方案似乎让双方都满意:它很简单,包括 p 值,同时解决了单独使用统计显著性的主要缺点。我希望这一提议能够有助于摆脱对统计意义的过度关注。
这篇文章有两个主要部分。首先,我提出概念:与统计显著性(p 值)相关的主要问题、解决方案(森林图)和一个结论。第二,我包括一个完整的例子和一个真实的研究问题(温度和环境政策执行之间的关系)。
有哪些问题具有统计学意义(p 值)?
重要的事情先来。p 值是什么,为什么使用统计显著性(仅)是一个问题?
“p 值是在假设零假设正确的情况下,获得至少与实际观察到的结果一样极端的测试结果的概率。” —维基百科
解释 p 值需要一整篇文章。所以,如果你还不熟悉这个关键概念,我让你快速查看一下这篇关于数据科学的精彩总结 ( 链接)。
这里有两个主要问题:
1。实用相关性:
统计学上的显著性并没有说明影响的大小。比方说,我看到一个有助于减少脱发的洗发水广告。该广告声称,这些结果已经在实验室中通过一项大型实验得到了证明,并且这些结果具有统计学意义(使用这种洗发水的人脱发较少)。然而,当你阅读基础研究论文时,你会发现,平均而言,使用这种洗发水的人头上会多长五根头发。谁在乎呢。对吗?
这是我作为研究人员的另一个例子。我花了两年时间撰写了一篇论文,以评估 covid 遏制对病毒传播的影响(Bonardi 等人(2022 年)即将发表)。在这里,影响的大小是关键。封锁对经济、对许多人的心理健康等造成了巨大的损失。因此,我们不仅想知道他们是否减少了病毒的传播,还想知道减少了多少,以评估这些措施的成本/效益。
这就是为什么我总是主张简单地使用统计显著性作为解释效应大小的必要条件。然后,根据效果的大小,人们可以断定该效果是否具有实际意义。重点在于数量,而统计显著性提供了结果不是随机的证据。
2。操作:
对固定阈值(例如 5%)的关注导致了两个主要问题。在处理数据时,有时在统计检验、样本等的选择上有几个自由度。因此,这可能允许一些人稍微操纵结果以低于阈值。
另一方面,如果您稍微高于阈值,您可能无法发布您的结果。然而,基本上,4.9%的 p 值和 5.1%的 p 值显示了正在测试的假设的类似证据。这导致了研究中的严重问题。下面是我在 LinkedIn 上找到的一个说明性的例子(Florian Weigert):“60 名研究人员检查了医疗 X 对疾病 y 减少的影响,57 名研究人员没有发现任何影响,无法公布结果。3 名研究人员记录了统计上显著的关系,并发表了论文。结论:一项荟萃研究为药物治疗 X 导致疾病 y 减少的事实提供了压倒性的支持。如果你想更进一步,请阅读Zwet 和 Cator (2021)的《显著性过滤器,赢家的诅咒和收缩的需要》** 或者美国统计协会关于 p 值的声明。*
一个简单的解决方案:森林地块
为了全面理解一个测试结果,你需要做三件事:评估统计显著性(有保留地),关注数量级(如果它是统计显著的),以及解释可变性。
为什么森林地块是一个优雅的解决方案?森林图让我们可以看到所有这些信息,并更容易比较系数。本文顶部的图片显示了一个回归表旁边的森林图示例。在本文第二部分的例子中可以找到更多的例子。
1.统计显著性:统计显著性仍然是统计检验的一个重要方面。如果结果没有统计学意义,就不值得解读。森林图可以让你看到统计意义。如果 95%置信区间的棒线不包含 0,这意味着我们可以拒绝真实系数为 0 的双侧零假设。然而,这种表示并没有过分简化。在许多期刊中,来自回归或统计检验的系数都附有小星号,以表示它们的统计显著性(例如,如果 p 值小于 5%)。这种简化的问题是,我们不知道结果是远远没有统计学意义还是非常接近。森林情节让我们可以直观地看到 CI 中超出 0 线的部分,从而减少对明显分界线的困扰。
2.量级:量级容易读取,以及系数之间的相对量级。但是,有时系数不能简单比较。例如,在回归中,变量可能有非常不同的标度。通过标准偏差对变量进行标准化将得到一个可比较的范围(见下面的例子)。
3.可变性:置信区间的大小突出了不确定性。同样,它允许我们快速看到系数的下限和上限,并了解可变性。
结论
森林图使统计检验的所有关键要素(统计显著性、幅度和可变性)易于获取,无需成为统计专家。此外,通过提供其他信息,这种更丰富的表示自然会降低统计意义的权重。如果你能够正确地运行统计测试,你应该有能力绘制森林图并解释结果(例如,贝叶斯方法就不是这种情况)。
然而,重要的是要注意这些图表与表格相比的弱点:精确度的损失。为了获得系数的精确值,表格仍然是必要的(例如保存在附录中)。此外,在回归的情况下,如果你用标准差来标准化系数,即使它有利于相对大小的比较,解释也可能不太直观。
Python 中的应用:温度和环境政策:
让我在我之前的文章的基础上再接再厉,在那篇文章中,我探讨了温度(热浪)和环境政策之间的关系。
以下是变量列表:
- cname:国家名称
- 年份:年份
- OECD _ EPS:index⁴的环境政策紧缩(摘自 Botta 和 Kozluk (2014 年))
- cckp _ temp:celsius⁵年平均温度(来自气候变化知识门户)
- cckp _ rain:mm⁵年平均降雨量(来自气候变化知识门户)
- wdi_fossil:化石燃料能源消耗(total)⁶的%)
- gdp _ PC _ PPP:parity⁶购买电力的人均 GDP(来自世界发展指标,世界银行)
- pop: Population⁶(来自世界银行世界发展指标)
模型
以下是我将评估的基线模型:

回归方程式
用 i 和 t 分别代表国家和年份。 EPS 为环境政策严格性指数(取值为 0-5 的指数)。温度是以摄氏度为单位的年平均温度。我已经减去了每个国家每个变量的平均值。正如在初步分析中观察到的,这一想法是将法国最热的年份与法国最冷的年份进行比较,而不是将法国与加拿大进行比较(利用国内的差异)。 X 是一个控制向量,包括人口和人均 GDP(以购买力平价表示)。ɛ是聚集在国家级的误差项(在协方差矩阵中说明相同国家之间误差相关性的标准程序)。
我用来绘制系数的方法是基于左的博文https://zhiyzuo . github . io/Python-Plot-Regression-Coefficient/。
一个回归模型的森林图
注:我们可以看到,每增加 1°C,环境政策严格性指数就会增加约 0.27(环境政策严格性指数的平均值:1.58),CI 介于 0.2 和 0.35 之间。该系数在 5%的水平上具有统计学意义。
具有两个回归模型的森林图
这里我将比较两个不同样本的温度系数:降雨量高于中值的年份和降雨量低于中值的年份。在第一篇论文中,我发现温度的影响在干旱年份明显更强。
注:我们能够确认前一篇文章中的发现,即当降雨量较低(低于中值)时,这种影响较大,实际上,对于降雨量高于中值的样本,在 5%阈值时,这种影响在统计上并不显著。此外,我们可以看到,在 5%的阈值处,两个系数之间的差异具有统计学意义。这是因为每个系数的置信区间不会超过另一个系数。
具有两个回归模型和多个变量的森林图
在这里,我将比较两个不同模型的温度系数:无控制变量和有控制变量(GDP 和化石燃料消耗)。
注:我们可以看到,一旦我们控制了国内生产总值和化石燃料消耗,温度系数不再具有统计意义。因此,我们的主要效应似乎受到了一个被忽略的变量偏差的影响。下面的相关表显示,GDP 与温度(-0.45)呈强负相关,与环境政策严格指数(0.65)呈强正相关。因此,温度的基本正系数是由于较富裕的国家位于较冷的地区,这些国家倾向于实施更多的环境政策。
还要注意的是,这里很难比较系数,因为它们处于不同的尺度(GDP 以美元计,化石燃料占消费的份额,温度以摄氏度计)。因此,最好通过每个变量的标准偏差来标准化系数。参见下一节。
具有两个回归模型和多个标准化变量的森林图
注:既然系数已经标准化,就更容易比较它们的大小,并看到各自的统计意义。然而,正如我们在本文第一部分中看到的,解释该系数更加困难。让我用无控制模型的温度系数来说明这一点。温度增加一个标准偏差(7.3 摄氏度)与政策严格性指数增加 2 个点相关(指数的平均值:1.58)。
参考
[1]范·兹韦特,E. W .,&卡托,E. A. (2021 年)。重要性过滤器,胜利者的诅咒和收缩的需要。尼尔兰迪卡统计,75(4),437–452。
[2]沃瑟斯坦和耶戈(2016 年)。美国儿科学会关于 p 值的声明:背景、过程和目的。《美国统计学家》,第 70 卷第 2 期,第 129-133 页。
[3]波维金娜、玛丽娜、纳塔莉亚·阿尔瓦拉多·帕雄和杰姆·梅尔特·达利。"政府环境指标数据集的质量,版本 Sep21 . "哥德堡大学:政府学院的质量,【https://www.gu.se/en/quality-government (2021)。
[4]博塔、恩里科和托马斯·koźluk."衡量经合组织国家环境政策的严格性:综合指数方法."(2014).
5 世界银行集团。2021.气候变化知识门户。https://climateknowledgeportal.worldbank.org
6 世界银行,《世界发展指标》。(2022).
UNIKUD:通过深度学习向希伯来文本添加元音
介绍一个开源的希伯来语 nakdan (נקדן语)工具,它不使用基于规则的逻辑。

希伯来文本“nikud”(元音指向)来自鸟头 Haggadah,写于 14 世纪早期,在现在的德国南部地区。(来源:维基共享资源)
由于英语的广泛使用和经济重要性,自然语言处理(NLP)研究主要集中在英语上,但 NLP 的最新进展使其更容易应对世界上数千种其他语言带来的独特挑战。在这个项目中,我们将最先进的深度学习架构应用于希伯来语中的一个特定问题——添加元音标记(nikud;ניקוד)转换为文本——表明我们可以使用强大的机器学习模型,利用有限的可用数据来处理较小的语言。

示例 UNIKUD 输出,来自其 Huggingface Spaces Streamlit 部署(图片来源:作者)
我们展示了我们的模型 UNIKUD,这是一个完全不使用基于规则的逻辑向希伯来文本添加元音标记的开源工具,它是用一个犬类变压器网络构建的。互动演示可在 Huggingface Spaces 获得,我们所有的数据、预处理和训练脚本以及实验可在其 UNIKUD DagsHub repo 页面获得。我们希望 UNIKUD 将成为希伯来语 NLP 和一般资源不足/中等资源语言进一步发展的跳板。
内容:
- 希伯来文字系统
- UNIKUD 简介
- UNIKUD 数据集
- 方法
- 结果
- 局限性和进一步的方向
- 结论
- 参考
1.希伯来文字系统
为了理解 UNIKUD 解决的问题,我们首先必须暂时离开主题来讨论希伯来文字系统。
希伯来语是一种闪族语言,起源于古代黎凡特地区。目前,希伯来语是以色列最广泛使用的语言,也是犹太人散居地的礼拜仪式语言,大约有 500 万母语使用者和数百万第二语言学习者(截至 2014 年,根据维基百科。现代希伯来语书写系统使用希伯来语字母,也用于书写意第绪语、拉迪诺语和其他各种语言,以及阿拉姆语的经典犹太教文献。
与英语使用的拉丁字母不同,希伯来字母是一个 abjad ,这意味着字母代表辅音,元音通常是不书写的。为了说明这一点,让我们考虑希伯来语单词לחם“lehem”,意思是“面包”:

(图片来源:作者)
希伯来语是从右向左书写的,所以读者观察字母 L-H-M 的顺序。你可能想知道读者怎么知道这个单词发音是" lehem "而不是,比如说," laham "。事实上,这必须根据上下文来推断。如果这听起来很奇怪,请记住,即使在阅读英语时,我们也必须记住许多单词的拼写和发音,所以阅读英语也需要上下文,但阅读希伯来语需要更多的上下文。
希伯来语也可以写元音点,希伯来语称为 nikud 。这些文字是在希伯来文字之后很久才发明的,并没有在正常的书写中使用。相反,它们被添加到精确或清晰很重要的文本中,如字典、语言学习材料和诗歌中。例如,当用 nikud 书写时,单词לחם lehem 被写成:

(图片来源:作者)
或者用数字文本:לֶחֶם.前两个字母下面的三个点表示后面有一个“e”元音。
根据上下文,希伯来语中相同的未发音(无元音点)书面单词可以对应于许多可能的发音单词。例如,书面单词ספר S-P-R 可以读作סֵפֶר 塞弗(“书”)、סָפַר 萨法尔(“算”)、סַפָּר 萨法尔(“理发师”)、或者סְפָר 斯法尔(“边疆”)。UNIKUD 模型的目标是自动添加指向希伯来文本的正确元音,使用上下文来确定每个单词的发音。
旁注:今天还有其他的 abjads 在使用,最著名的是用于书写阿拉伯语的阿拉伯文字。使用 abjads 的阿拉伯语和其他语言通常也有类似的可选发音符号来标记元音,我们相信 UNIKUD 可以通过微小的调整适应这些书写系统。如果你想了解包括 abjads 在内的世界各地的书写系统的更多信息,可以看看我 2012 年在麻省理工学院的演讲:世界语言的书写系统。
2.UNIKUD 简介
我们已经看到,希伯来语通常没有元音,但需要上下文来阅读;一个复杂的元音发音系统可以用来帮助读者,但添加这些元音需要对希伯来语和手头的文本有深刻的理解。各种 nakdanim (נקדנים) —自动添加nikud(ניקוד;元音标记)转换为文本,但直到最近,大多数模型都是专有的和/或使用一组复杂的手写规则编程的,这些规则是希伯来语特有的,并且制作起来极其费力。<#fdb9>
我们提出了 UNIKUD 模型,该模型完全不使用手写规则,而是通过在现有希伯来语文本的数据集上进行训练,来学习如何自己给文本添加元音标记。我们选择 UNIKUD 这个名字是因为它是一个三重单词——一个使用 UNIcode 文本标准向文本添加 NIKUD (ניקוד,希伯来文元音标记)的独特模型。
该模型的灵感来自于最近随着变压器神经网络的出现而出现的 NLP 中的范式转变;由谷歌的一个研究团队在 2017 年的论文中介绍的“注意力就是你所需要的一切”中,BERT 等转换模型在几乎每个基准任务上都推进了 NLP 的最新水平,从机器翻译到问答等等。这些模型的关键特征是使用自我注意机制从编码为向量的复杂输入中提取上下文含义;关于技术细节,读者可以参考 Peter Bloem 在 2019 年发表的 lucid 帖子“从零开始的变形金刚”。
大多数 transformer 模型依赖“子词标记化”作为文本输入的预处理步骤。在 NLP 中,标记化指的是将文本分割成更小的单元——例如,单词标记化会将句子I love tacos.分割成单词单元I、love、tacos和.(标点符号通常被视为单独的单词标记)。像 BERT 这样的模型使用子词标记化,这意味着更复杂或不常用的词可以分成“子词”单元——例如,tacos可能被标记为两个单元ta和##cos。
然而,单词甚至子单词标记化对于像希伯来语这样的语言来说意义不大,因为在希伯来语中,单个单词可以通过内部变化来添加复杂的含义。例如,希伯来语词根כת״ב K-T-V 指的是书写;通过在这个词根中添加元音,我们可以创造出复杂的单词כָּתַב·卡塔夫“他写了”,יִכְתֹּב·伊赫托夫“他要写了”,מִכְתָּב·米赫塔夫“一封信”,等等。
作为一个自动编码的字符级转换器模型,犬特别适合希伯来语发音,因为我们可以将这个问题框架为一个字符令牌分类问题。我们的 UNIKUD 模型使用犬科动物作为其语言主干,用预训练的权重进行初始化。我们在犬的顶部添加了一个分类头,以将其上下文嵌入转换为元音标记预测。
我们会看到,训练 UNIKUD 需要一个复杂的数据管道——首先,收集并归一化带元音的希伯来文文本;第二,训练一个辅助模型以“全拼写”(כתיב מלא)重写这些文本;最后,使用我们处理的数据训练核心 UNIKUD 模型来预测元音标记。我们使用 DagsHub 管理这个多阶段管道,这个平台托管包含 Git 版本化脚本文件和 DVC 版本化数据文件和管道定义的存储库。我们提供了经过训练的 UNIKUD 模型,以及重现其预处理和训练步骤的说明。
3.UNIKUD 数据集
深度学习模型需要大量数据,UNIKUD 也不例外。为了训练 UNIKUD 学习如何发声希伯来文本,我们需要收集一个带有元音点的希伯来文本数据集。希伯来语在日常使用中通常没有元音标记,因此需要仔细搜索才能找到足够数量的带元音的数字化希伯来语文本。为了希伯来语 NLP 社区的利益,我们从公共领域的资源中精选了这样的数据,并在我们的存储库中将它们作为 DVC 版本的数据提供。
我们从以下来源收集了带有 nikud (元音标记)的文本:
- 希伯来语维基百科(大多数文本没有元音,但我们搜索并提取了有元音的文本)
- 本-耶胡达项目(数字化希伯来经典文学的倡议;我们在这个项目中只使用了公共领域的资源,这些资源通常包含元音标记)
UNIKUD 的基本思想是,我们可以使用带有 nikud 的文本进行“自我监督学习”,用现代深度学习研究的术语来说。我们可以使用文本本身来创建我们的模型的输入和期望的输出:例如,通过自动移除元音字符,可以将带有元音的单词סֵפֶר " sefer "转换为未发音的ספר;后者将是模型的输入,而前者将是期望的输出。

如何用带元音的希伯来文训练 UNIKUD?去掉元音的文本作为模型的输入,带元音的原始文本作为目标(我们试图预测的)。(图片来源:作者)
然而,现代希伯来文字还有一个特点,在某些情况下使这变得复杂:随着时间的推移,抄写员开始在未发音的文本中添加额外的字母,以暗示元音的存在。被称为 matres lectionis (希伯来语אימות קריאה,字面意思是“阅读之母”)的字母 yud (י)和 vav (ו)在文本中添加元音时通常必须删除,如下例所示:

“Ktiv male”(全拼):只用红色字母,不带元音。(图片来源:作者)
带有这些额外字母的单词的拼写在希伯来语中被通俗地称为“全拼写”( ktiv male כתיב מלא,更正式的说法是כתיב חסר ניקוד)。因为我们需要完整拼写的文本作为 UNIKUD 的输入,所以我们必须训练一个辅助模型来将这些额外的字母添加到带有元音的文本中。因此,我们需要完整和有缺陷(不完整)拼写的成对单词的训练数据。我们从两个来源收集了这些数据:
- 希伯来语 wikitionary(ויקימילון):大多数词条的拼写都有缺陷,文章标题中有元音字母,侧栏列出了相应的完整拼写。
- 希伯来语维基资源 (ויקימקור):)许多关于古典诗歌和其他资源的文章包含相同文本的平行版本,既有完整拼写(没有元音)也有有缺陷拼写(有元音)。
我们整理了来自这两个来源的数据,并以机器可读的格式排列它们。
作为数据和训练管道中的第一个预处理阶段,我们将来自这些来源的数据结合在一起,并对文本进行了轻微的清理。最重要的预处理步骤是:
- 从 Wikipedia/wiki source/wiki source 数据中删除一些特定于源的样板文件,比如用花括号
{{}}括起来的模板标签。 - Unicode 规范化 —一些可视字符可以在原始 Unicode 中以多种方式表示。例如希伯来语的 bet + dagesh 既可以表示为בּ =
U+0531 (bet)&U+05BC (dagesh),也可以表示为בּ =U+FB31 (bet with dagesh)。这两种表示在视觉上看起来是一样的,但是第一种表示由两个 Unicode 码位组成,而第二种表示由单个码位组成的字符。我们使用 NFC Unicode 标准化来统一这种情况。 - 我们将两个代码点
U+05BA和U+05B9组合在一起,它们都可以用于希伯来语中的单个 holam 元音点。
因为犬骨干模型接受 Unicode 字符输入,不需要子词标记化,所以我们保留标点和其他复杂字符。
4.方法
UNIKUD 的关键见解是,我们可以将希伯来语文本的发音视为多标记标记分类问题,其中标记是单个字符。单个希伯来语辅音字符可以用零个、一个或多个 nikud 标记来修饰,这些标记可以用一个二进制向量来表示:

希伯来语发音为 multilabel 分类:每个希伯来语字母可以用多个 nikud 修饰,可以用一个二进制向量来表示。UNIKUD 使用这个标签编码作为它的目标。为了清楚起见,该图被压缩,但是 UNIKUD 的二进制目标实际上包含 15 个条目。(图片来源:作者)
UNIKUD 模型包括一个犬齿变压器主干,其分类头有 15 个输出(针对每个输入字符标记)。前 14 个输出大致对应于 Unicode 码位U+05B0到U+05C2,用于编码各种希伯来语 nikud (详见维基百科的希伯来语 Unicode 表)。第 15 个也是最后一个输出给出了字符应该被删除的概率,如果它是一个用于“完整拼写”的额外的 yud 或 vav 字母(见上文)。
虽然对于哪些标记可以一起出现有各种限制(例如,点只能出现在字母 shin 或 sin 上),但是我们没有将它们硬编码到我们的模型中。我们发现 UNIKUD 能够自己学习这些限制。
我们使用 Pytorch 和 Huggingface 的transformers库实现了这个模型。特别地,我们使用犬齿的拥抱面部端口,该端口用预训练的权重(canine-c)初始化,并使用二进制交叉熵损失进行训练。关于优化和训练超参数的更多细节可在 UNIKUD DagsHub 实验页面查看。
如上所述,为 UNIKUD 准备训练数据需要首先训练一个辅助模型——在我们的代码中为KtivMaleModel——将带元音的文本转换为“完整拼写”( ktiv male כתיב מלא).)我们使用另一个具有三级分类头的预训练犬齿副本来构建这个模型——对于输入中的每个字符,我们预测 0(无变化)、1(插入י yud 或 2(插入ו vav )。例如,给定输入מְנֻקָּד,模型将预测除字母ק之外的所有字符为 0,该字母将被训练为预测 2(插入ו vav ),从而产生完整拼写מנוקד(省略元音)。一旦这个模型被训练,我们使用它来添加“完全拼写”到我们的数据集中,用于训练主要的 UNIKUD 模型。
我们的完整管道可以方便地视为一个有向图:

通过 UNIKUD 的 DagsHub repo 页面,我们的完整数据和培训管道以有向无环图(DAG)的形式呈现。在对原始数据进行预处理之后,我们训练我们的辅助“ktiv 男性”模型,使用它将完整拼写添加到主模型的训练数据中,然后训练主 UNIKUD 模型。(图片来源:作者)
如果我们的存储库是从 DagsHub 克隆的,那么这个流水线的每一步都可以用 DVC 来重现,例如,dvc repro preprocess运行数据预处理,dvc repro train-ktiv-male使用这个预处理的数据训练“全拼写”模型,等等。如图所示,管道最后一步的输出是经过训练的 UNIKUD 模型。
一旦该模型被训练,就可以使用简单的解码方法将 nikud 添加到文本中。由于我们使用了具有二进制交叉熵损失的二进制目标,我们的模型输出可以被解释为 14 种不同 nikud 类型中每一种出现的概率(更精确地说是 logits ),或者字符删除的概率。在解码中,我们用对应于高于固定概率阈值(0.5)的输出的动作来修改输入文本。有关更多细节,请参见我们代码中的NikudTask对象中定义的解码方法。
5.结果
我们首先提出定量结果,然后对选定的例子进行定性评估。不同超参数设置的训练结果可见于我们存储库的 DagsHub 实验表:

DagsHub 实验表显示不同训练运行的超参数设置和指标(图片来源:作者)
我们将只讨论最终的 UNIKUD 模型训练(DagsHub 实验表标签:unikud),但你可能也会在那里看到 ktiv 男模型训练的结果(标签:unikud)。部署中使用的最终设置标有标签deployed。
我们用交叉熵损失训练了我们的模型,还跟踪了准确性和宏 F1 度量。我们拿出 10%的数据进行验证;在eval_loss, eval_accuracy, eval_macro_f1列中可以看到验证指标。准确性被解释为其标签(可能是多个 nikud 或删除)被完美预测的验证集中字符的比例。Macro-F1 是不同标签类型的平均 F1 分数,对于不平衡数据的分类问题(如我们的案例,一些 nikud 远不如其他的常见),这通常是比准确度更有意义的指标。理想情况下,验证准确度和宏 F1 都应该接近 1,而验证损失应该尽可能接近 0。
我们的最终模型达到了最终验证指标:损失 0.0153,准确度 0.9572,宏 F1 0.9248。其实验页面显示了这些指标在整个培训过程中是如何改进的,MLFlow 记录了这些指标:

培训中的验证指标,来自我们模型的实验页面(图片来源:作者)
对于定性评估,我们在代表不同文本风格的各种输入上呈现我们的模型的输出:
כלבניאדםנולדובניחוריןושוויםבערכםובזכויותיהם.כולם חוננו בתבונה ובמצפון, לפיכך חובה עליהם לנהוג איש ברעהו ברוח של אחוה.
כָּל בְּנֵי אָדָם נוֹלְדוּ בְנֵי חוֹרִין וְשָׁוִים בְּעָרְכָּם וּבִזְכֻוּיּוֹתֵיהֶם. כֻּלָּם חֻוֹנְנוּ בִּתְבוּנָה וּבְמַצְפּוּן, לְפִיכָךְ חוֹבָה עֲלֵיהֶם לִנְהֹג אִישׁ בְּרֵעֵהוּ בְּרוּחַ שֶׁל אַחֲוָה.
האםתניןהואיותרארוךאויותרירוק?
הַאִם תַּנִּין הוּא יוֹתֵר אָרךְ אוֹ יוֹתֵר יָרוֹק?
אבלליישרקשתיים.לנמלהישששרגליים
לַנְמָלָה יֵשׁ שֵׁשׁ רַגְלַיִם, אֲבָל לִי יֵשׁ רַק שְׁתַּיִם.
עכבישיםבאפריקה:מפגשיםעםחיותקטלניותשכמעטנגמרורעמאודנחשיםבאוסטרליה
נְחָשִׁים בְּאוֹסְטְרְלִיָה, עַכָּבִישִׁים בְּאַפְרִיקָה: מְפַגְּשִׁים עִם חַיּוֹת קַטְלָנִיּוֹת שֶׁכִּמְעַט נִגְמְרוּ רַע מֵאוֹד
אחשליתודהנשמהוואלהאחלהגבראתה
אָח שֶׁלִּי תּוֹדָה נְשָׁמָה ואֵלָה אַחלֶה גֶּבֶר אֶתָה
שלום חברים
שְׁלוֹם חַבְרִים
חתול
חתּוֹל
各种文本样式的 UNIKUD 输出示例
我们对 UNIKUD 在这些和其他各种输入上的测试的主要观察结果是:
- 我们发现,当输入更长并且包含更多上下文时,UNIKUD 通常表现得更好。当给定非常短的输入(一个或一两个单词)时,它有时不会完成 nikud 甚至会出错,而它在长输入句子或文本上的平均表现会更好,如上所述。
- 培训管道中的 ktiv 男这一步很重要。我们看到 UNIKUD 确实学会了在上面的כולם-כֻּלָּם和רגליים-רַגְלַיִם这样的单词中去掉 yud 和 vav 。在之前的实验中,我们在没有这一步骤的情况下训练 UNIKUD,它无法正确地将 nikud 添加到这些单词中,因为它在训练期间从未遇到过 ktiv male 拼写。
- 对于正式或古典文本,输出平均起来更准确,而 UNIKUD 则难以适应非正式的现代希伯来语。将以上正式文本的相对高质量的输出与俚语术语的错误进行比较,如וואלה.
- 当模型不能以高置信度预测元音时,它有时不输出任何 nikud 。这可以通过改变解码的概率阈值来调整,默认情况下是 0.5。
你也可以在它的hugging face Spaces Streamlit deployment亲自试用这个模型:

带 Streamlit 接口的 UNIKUD,可在 Huggingface Spaces 获得。滑块控制解码中使用的概率阈值。本文中的结果对所有阈值使用默认值 0.5。(图片来源:作者)
6.局限性和进一步的方向
我们简要提及 UNIKUD 模型的一些局限性和未来研究的可能方向:
- UNIKUD 只使用输入文本的表面形式来猜测要添加的正确的 nikud 符号,因此它会努力学习外来语或其他在训练中没有看到的单词,而这些单词的 nikud 必须记住。一个有希望的方向是用一个检索组件来增强 UNIKUD,该组件允许对字典文件的学习访问,类似于最近对检索增强生成的研究,其中模型被给予对维基百科的动态访问以提取相关知识。
- UNIKUD 输出每个字符的概率,在解码过程中,我们简单地使用贪婪解码的一种变体分别处理这些概率。 Nikud 具有各种共现限制,在解码时可以考虑这些限制,以生成更多的逻辑输出,例如使用具有维特比解码的 CRF ( 条件随机场)层。我们也不能容易地为不明确的文本(例如“ספר”)生成多个候选输出,这可以通过波束搜索用自回归文本生成模型来完成。
- 作为一个大型深度学习模型,UNIKUD 在 CPU 而不是 GPU 上运行时,执行推理(向文本添加元音)的速度要慢得多。在长文本(数千个字符或更多)上运行还需要将文本分割成段,并且运行时高度依赖于这些段如何构建并馈入模型以进行批量推理。我们没有试图优化 UNIKUD 的这些方面。
7.结论
在这个项目中,我们展示了 UNIKUD 模型,它将 nikud 添加到希伯来文本中,这是一个开源的 nakdan 模型,它通过深度学习构建,没有使用针对希伯来语的手写规则。UNIKUD 是用犬变压器主干建造的,需要复杂的数据管道进行训练。
我们在以下链接中提供了我们的数据文件、模型和结果:
- DagsHub 存储库(代码、数据和实验):https://dagshub.com/morrisalp/unikud
- 简化部署:https://huggingface.co/spaces/malper/unikud
我们希望我们的模型对那些从事希伯来语自然语言处理的人有用,并作为资源不足和中等资源的语言的一个测试案例。
我们要感谢 DagsHub 赞助这个项目,作为其 creators 计划的一部分,并提供简化其开发的平台。
8.参考
莫里斯·阿尔珀。“世界语言的书写系统。" 2012.https://github.com/morrisalp/WSOWL
彼得·布鲁姆。《从零开始的变形金刚》2019,http://peterbloem.nl/blog/transformers。
Jonathan H. Clark,Dan Garrette,Iulia Turc,John Wieting:“犬:预训练一个高效的无标记化语言表示编码器”,2021 年; arXiv:2103.06874 。
Elazar Gershuni,Yuval Pinter:“在没有字典的情况下恢复希伯来语音调符号”,2021 年; arXiv:2105.05209 。
Patrick Lewis、Ethan Perez、Aleksandra Piktus、Fabio Petroni、Vladimir Karpukhin、Naman Goyal、Heinrich Küttler、迈克·刘易斯、Wen-tau Yih、Tim rocktschel、Sebastian Riedel、Douwe Kiela:“知识密集型自然语言处理任务的检索-增强生成”,2020 年; arXiv:2005.11401 。
阿希什·瓦斯瓦尼、诺姆·沙泽尔、尼基·帕尔马、雅各布·乌兹科雷特、利翁·琼斯、艾丹·n·戈麦斯、卢卡兹·凯泽、伊利亚·波洛苏欣:《注意力就是你需要的一切》,2017; arXiv:1706.03762 。
但是看看 Gershuni & Pinter (2021)最近的一个例外,他使用了一种和我们有些不同的方法。
Morris Alper 是以色列特拉维夫的一名数据科学家。他是以色列技术挑战赛的数据科学负责人和讲师,目前是特拉维夫大学计算机科学专业的硕士学生。咨询可以通过电子邮件(morrisalp@gmail.com)或 LinkedIn 进行:
用 Python 进行数据科学的单元测试
原文:https://towardsdatascience.com/unit-testing-for-data-science-with-python-16dfdcfe3232
使用 nose2 和参数化测试尽早发现代价高昂的错误
你在工作中部署了一个新的机器学习模型。你终于可以享受周末了,你心想。你一点也不知道,一场迫在眉睫的错误风暴即将摧毁你的模型,毁掉你的周末。

不不不。请现在不要。图片作者。
为什么会这样?错误检查不足。数据科学家被教导执行数据探索和建模,但我们没有被教导执行单元测试,尤其是在边缘情况下。
在这篇文章中,我将介绍一个我发现在日常工作中非常有用的技巧:使用 nose2 进行单元测试。特别是,我会分享
- 什么是单元测试?
- 为什么数据科学家应该执行单元测试?
- 什么是 nose2?
- 如何执行简单的单元测试?
- 什么是参数化单元测试?
- 如何做一个简单的参数化测试?
- 如何进行检查错误的参数化测试?
- 如何用 pandas dataframe 做参数化测试?
什么是单元测试?
单元测试是数据科学家能够掌握的最强大的技能之一,它是编程的灵魂。这是一个测试,检查代码的单个组件,通常作为一个功能模块化,并确保它按预期执行
理想情况下,我们希望我们的测试很小。越小越好。这是因为更小的测试不仅从实践的角度来看更有效——因为测试更小的单元将使您的测试运行得更快——而且从概念上来说,它将为您提供粒度代码如何运行的更详细的视图。
为什么要执行单元测试?
有很多!这里有一个快速运行通过。
- 您可以在开发周期中轻松找到 bugs】由于功能/类是模块化/隔离的,因此每次只测试代码的一部分,这导致了效率的提高、停机时间的减少和成本的降低,否则成本会因整个设计过程停滞而增加。
- 当你单独测试软件的每个组件时,你可以更容易地重构代码。早期发现的问题可以被消灭在萌芽状态。
- 做得好的话,你可以把它们作为一种形式的文档。
让我们看一个使用 unittest 的简单例子,unittest 是从版本 2.1 开始内置到标准 python 库中的。
创建测试用例是通过子类化 unittest 来完成的。测试用例 。下面是一个例子。
import unittestdef add(x):
return x + 1class MyTest(unittest.TestCase):
def test(self):
self.assertEqual(add(3), 4)
您还可以在这里找到其他 assert 方法。

虽然 unittest 非常适合简单的测试,但在处理更复杂的代码时,它可能会很快变得有点麻烦。因此,开发了 nose2 来扩展 unittest 以简化测试过程。
什么是 nose2?

与 unittest 相比,Nose2 提供了更好的插件 API,并简化了内部接口和过程。Nose2 模块内置了很多插件,这些插件都是默认加载的。一些默认加载的主要插件有助于测试的参数化,将测试设备组织成层,捕获日志消息,提供测试覆盖报告,等等。
以下是 python 中不同的单元测试框架。

现有的单元测试框架。图片作者。
如何执行简单的单元测试?
在我们开始之前,您需要在您的系统中安装 Nose2 框架
pip install nose2==0.9.2
pip install parameterized
我们将从一个简单的例子开始。考虑这样的情况,我们有一个考试的四个分数的列表。分数应该在 0 到 100 之间。
+-------+
| score |
+-------+
| 44 |
| 64 |
| -5 |
| 101 |
+-------+
让我们编写一个测试来帮助我们捕捉不符合标准的两个值(-5 和 101),并告诉 python 运行测试。
现在我们已经创建了 test.py,我们必须运行它。为此,您可以打开终端,并运行以下命令。(请将“/directory/to/your/test.py”替换为存储 test.py 的目录。)
python3 /directory/to/your/test.py
运行该命令后,您应该会看到以下内容。

不出所料,我们的测试失败了!万岁。
什么是参数化单元测试?
开发人员倾向于为每个案例编写一个测试。然后将一些相关案例分组到“套件”中。考虑下面的情况。
class CodeTestSuite(TestCase):
def test_for_int_input(self):
assert correct_output() def test_for_float_input(self):
assert correct_output() def test_for_string_input(self):
assert correct_input()
可想而知,这些案件大部分都是息息相关的。这可能会导致去冗余、大型测试代码库,以及在进行变更时需要偿还的潜在技术债务。
进入参数化测试,生成一个多参数测试,方便测试。您可以通过测试套件中方法的装饰器来传递参数。
下面的一些代码是建立在 Richard D Jones 的“你应该做参数化测试”之上的。一定要去看看!
带装饰器的参数化测试
假设我们想要测试这个简单的函数“compute”。
def compute(a, b):
return (a + b) / (a * b)
在下面的例子中,我们使用 compute 函数执行两个参数化测试。特别是:
- test_int 测试 compute(a=0,b=1)的输出是否为 1。如果是,测试通过。
- test_float 测试 compute(a=1,b=1)的输出是否为 1。如果是,测试通过。
代码片段如下。
class TestSuite(unittest.TestCase):
@parameterized.expand([
# each tuple contains
# (name , a , b , expected_output
("test_int" , 1 , 1 , 2),
("test_float", 1\. , 1\. , 2.)
])def test_compute(self, name, a, b, expected_output):
assert compute(a, b) == expected_output
注意“@ parameterized.expand”装饰器是如何接受元组列表的。每个元组都是一个测试用例,其输出将被改变。
完整的代码块如下。
您可以使用以下命令运行上述测试。所有的测试都应该通过。
python3 /directory/to/your/test_param.py
如何进行检查错误的参数化测试?
在某些情况下,我们还希望执行单元测试,以确保错误被正确地提出。为此,我们可以指定要引发的错误。
代码片段如下。
def test_compute(self, name, a, b, expected_output, expected_error=None):
# If no error is expected from the test case,
# check if the actual output matches the expected output
if expected_error is None:
assert compute(a, b) == expected_output # If an error is expected from the test case,
# check if the actual error matches the expected error
else:
with self.assertRaises(expected_error):
compute(a, b)
下面的示例“test_divisionbyzero”测试 compute(0,0)是否正确地产生了 ZeroDivisionError。
您可以使用以下命令运行上述测试。所有的测试都应该通过。
python3 /directory/to/your/test_params_with_error.py
如何用 pandas dataframe 做参数化测试?
如果你想为你的测试传入一个熊猫数据帧呢?没问题。
我们可以使用下面的代码片段,其中 load_test_case()是一个接收数据的函数。
您可以使用以下命令运行上述测试。所有的测试都应该通过。
python3 /directory/to/your/test_params_with_dataframe.py
单元测试是无压力周末的关键
单元测试模块可以由 nose2 模块补充,以创建强大的单元测试。特别是,参数化测试提供了一种组织多个单元测试的便捷方式。
我将在接下来的一周分享更多的单元测试策略。敬请期待!
如果你想了解更多关于数据分析、数据科学和机器学习的知识,可以考虑在 Medium 和 LinkedIn 上关注我!
10 分钟后模拟单元测试
原文:https://towardsdatascience.com/unit-testing-with-mocking-in-10-minutes-e28feb7e530
使用内置的 unittest Python 包有效地测试您的代码库

照片由亚历克斯·康德拉蒂耶夫在 Unsplash 拍摄
顾名思义,单元测试通过测试预期的输出、错误或数据类型,甚至测试函数是否被调用(以及调用了多少次),来测试单个单元或代码库的一小部分,以确保组件按预期工作。
本文将比较可用的不同单元测试方法、单元测试最佳实践、如何编写和运行单元测试,最后用内置断言、测试的条件跳过和模拟来增强您的单元测试。
更新 :本文是系列文章的一部分。查看其他“10 分钟内”话题 此处 !
目录
单元测试生态系统
有几个 python 包可以执行单元测试和其他形式的测试,
- 测试代码覆盖率,以衡量单元测试的有效性
**doctest**:在函数本身的 docstring 中测试函数的实现,但是测试用例的类型可以是有限的。在我的关于代码文档的文章中阅读更多信息**mypy**:测试函数的返回类型**pytest**:在一个单独的文件中测试函数的实现,写在一个函数中(函数式编程)。这个 python 包需要安装 pip**unittest**:在一个单独的文件中测试函数的实现,写在一个类中(面向对象编程)。这是一个内置的 python 包,不需要安装
编写单元测试的两个最常见的包是pytest和unittest。个人认为用unittest写的代码比pytest可读性强。然而,pytest确实有它的优点,因为它需要更少的代码,因为测试是在函数而不是类中编写的。
单元测试最佳实践
无论您决定使用哪一个单元测试 python 包,都会强制执行一些我称之为最佳实践的约定。我将根据以下类别将它们分为强制的和推荐的最佳实践,
文件夹结构(强制性最佳实践)

图 1:单元测试文件夹结构——作者图片
测试文件应该保存在一个tests目录中,与包目录或源代码处于相同的嵌套层次,并且该目录必须是可导入的。可导入的目录(也称为 python 模块)非常简单,只需向文件夹中添加一个空的__init__.py文件,以表明该目录是可导入的。
在tests目录中,文件夹结构应该遵循源代码或包目录的结构。
命名约定(强制性最佳实践)
文件名、类名和类方法都有命名约定,
- 文件名跟在 snake_case 后面,以
test_开头,并且应该跟在它正在测试的文件的名称后面(即test_module_one.py是文件module_one.py的单元测试文件) - 类名遵循 PascalCase 并以
Test(即TestFunction)开头 - 类方法名跟在 snake_case 后面,以
test_(即test_scenario_one)开头。
测试案例(推荐的最佳实践)
单元测试是基于断言的,例如断言结果等于或不等于预定义的预期输出。为了彻底测试断言,一个经过良好测试的函数应该测试
- 糟糕的论点;导致函数引发异常的参数
- 特殊论据;边界值,或者可以使用特殊逻辑的地方
- 正常的争论;测试通常的情况
- 测试返回值/数据类型/异常
单元测试的结构
了解了最佳实践之后,让我们来看看单元测试的框架!下面是一个最简单的单元测试的例子,只有 4 行代码。
import unittest
class TestFunction(unittest.TestCase):
def test_function_with_scenario_one(self):
print("Testing function with scenario one")
# assert something here
if __name__ == "__main__":
unittest.main()
单元测试是在继承自unittest.TestCase的类中编写的;并且每个单元测试都被实现为一个类方法。当运行单元测试时(下一节将详细介绍),它会查找所有以test开头的类方法名,并按顺序运行它们。在上面的例子中,运行测试的顺序是start > test_function_with_scenario_one > end。
import unittest
class TestFunction(unittest.TestCase):
def setUp(self):
print("Set up...")
self.value = 1
def tearDown(self):
print("Tear down...")
self.value = None
def test_function_with_scenario_one(self):
print("Testing function with scenario one")
def test_function_with_scenario_two(self):
print("Testing function with scenario two")
在某些情况下,可能有一些设置应该在每个单元测试之前运行,例如,当每个单元测试都在相同的数据集或变量上执行操作时,您希望在设置每个单元测试时保持数据集或变量的一致性或减少重复。
您可以使用在每个单元测试之前和之后运行的setUp和tearDown夹具。这导致执行命令start > **setUp** > test> **tearDown** > **setUp** > test > **tearDown** > end。当设置单元测试时,变量可以作为类属性存储,并传递给单元测试(第 8 行),这些类属性可以在拆卸期间重置为None(第 12 行)。
import unittest
class TestFunction(unittest.TestCase):
@classmethod
def setUpClass(cls):
print("Set up class...")
cls.value = 1
@classmethod
def tearDownClass(cls):
print("Tear down class...")
cls.value = None
def test_function_with_scenario_one(self):
print("Testing function with scenario one")
def test_function_with_scenario_two(self):
print("Testing function with scenario two")
如果设置代码在计算上很昂贵或者很耗时,比如连接到数据库,那么您可以实现setUpClass和tearDownClassfixture,它们在进入和退出类时运行一次。这导致了执行顺序start > **setUpClass** > test > test > **tearDownClass** > end。
与前面的设置和拆除实现相比,这是一个不太理想的实现,因为适当的单元测试应该独立地运行每个测试。当实现多个单元测试时,测试可能会修改类属性,只需一次设置和拆卸。
运行单元测试
建议在命令行终端上运行单元测试以彻底测试它,因为在控制台上运行它可能会由于变量遮蔽而不完全可靠。
在官方的unittest文档中,它声明转到tests目录,并根据您是分别在目录、文件、类还是类方法上运行单元测试来运行下面几行,
$ python -m unittest .
$ python -m unittest test_file test_file2
$ python -m unittest test_file.TestClass
$ python -m unittest test_file.TestClass.test_method
然而,我发现转到tests目录会弄乱我在单元测试中的导入语句,因为基目录现在是tests,我不能正确地从我的包目录或源代码中导入函数。我的首选方法是转到基本项目目录并运行下面一行代码,
$ python -m unittest discover tests/
还有更多定制可以附加到命令中,例如
-f: fail fast,遇到第一个测试失败就停止所有单元测试-p "test_*.py":指定文件名模式,有选择地运行测试-s start-dir:指定运行测试的开始目录-v:添加详细度;显示控制台日志的更多信息
高级:内置断言
如前所述,有许多场景需要测试(坏的、特殊的或好的输入参数),并且可以测试结果的预期输出、错误或数据类型。这些断言可以用assert语句来完成,但是unittest中有内置的比较器可以利用。以下是一些比较常见的比较器,完整列表可在这里找到。
断言预期输出
- 条件:
self.assertTrue(condition)或self.assertFalse(condition) - 相等:
self.assertEqual(a, b)或self.assertNotEqual(a, b) - 成员:
self.assertIn(a, b)或self.assertNotIn(a, b)
断言预期误差
import unittest
class TestFunction(unittest.TestCase):
def test_function_raise_error(self):
self.assertRaises(ValueError, int, "a")
def test_function_raise_error_with(self):
with self.assertRaises(ValueError):
int("a")
- 错误:
self.assertRaises(ExceptionName, function, arguments)或使用如上所示的上下文管理器(第 10 行) - 警告:
self.assertWarns(WarningName, function, arguments)
断言预期的数据类型
- 数据类型:
self.assertIsInstance(a, dtype)或self.assertNotIsInstance(a, dtype) - 无类型:
self.assertIsNone(x)或self.assertIsNotNone(x)
高级:跳过单元测试
除了利用内置的比较器方法,我们还可以使用内置的装饰器来实现单元测试的条件跳过。这意味着如果不满足某个条件,可以跳过单元测试。例如,由于向后兼容性问题而跳过对旧 Python 版本或包版本的测试,或者由于平台不兼容而跳过对某些系统的测试。
import sys
import unittest
class TestFunction(unittest.TestCase):
@unittest.skip("Skip unit test")
def test_skipped(self):
self.fail("This should be skipped")
@unittest.skipIf(sys.version_info.major < 2, "Skip due to Python version")
def test_python_version(self):
print("Test is ran")
@unittest.skipUnless(sys.platform.startswith("win"), "Skip unless it is Windows")
def test_python_version(self):
print("Test is ran")
当运行单元测试时,它会在最后的语句中显示跳过的测试数量,比如OK (skipped=1),这样你就不必担心单元测试在你不知情的情况下被编写和跳过了!
高级:单元测试中的模拟
单元测试中使用模仿来替换类方法或函数的返回值。这可能看起来违反直觉,因为单元测试应该测试类方法或函数,但是我们正在替换所有那些处理和设置预定义的输出。
模拟对于替换不应该在测试环境中运行的操作很有用,例如,当测试环境没有相同的数据访问时,替换连接到数据库并加载数据的操作。因此,取消处理并用预定义的数据替换输出不仅消除了可能的数据库连接错误,还增加了可预测性,因为对预定义数据进行的单元测试可以确保与您的预期输出完全匹配。
import unittest
from unittest.mock import Mock
class SampleClass:
def __init__(self):
self.value = 1
def sample_method(self):
"""Function that returns value 1"""
return self.value
class TestSampleClass(unittest.TestCase):
def test_mock_class(self):
class_object = SampleClass()
class_object.sample_method = Mock(return_value=2)
self.assertEqual(class_object.sample_method(), 2), "Mocking does not work"
def test_mock_function(self):
sample_function = Mock(return_value=2)
self.assertEqual(sample_function(), 2), "Mocking does not work"
在上面的代码片段中,mocking 是用return_value参数实现的,用来替换类方法和函数的预期输出。我们可以看到这个类方法最初返回值1,但是我用2代替了返回值,并且使用了前一节提到的内置断言self.assertEqual()。
但是,将返回值限制为一个固定的预定义输出可能会有很大的局限性。每次调用类方法或函数时,可以使用side_effect参数将返回值设置为不同的输出,并传入不同的预期输出列表。side_effect参数接受函数、可重复项或异常,示例如下:
import unittest
from unittest.mock import Mock
class TestSampleClass(unittest.TestCase):
def test_side_effect_function(self):
sample_function = Mock(side_effect=lambda x: x + 1)
self.assertEqual(class_object.sample_method(1), 2)
def test_side_effect_iterable(self):
sample_function = Mock(side_effect=[1, 2, 3])
self.assertEqual(sample_function(), 1)
self.assertEqual(sample_function(), 2)
self.assertEqual(sample_function(), 3)
def test_side_effect_exception(self):
sample_function = Mock(side_effect=ValueError())
with self.assertRaises(ValueError):
sample_function()
unittest.mock模块中可用的项目更多,比如MagicMock类似于Mock但可以改变魔术方法,还有patch代替整个函数而不是代替不那么直接的返回值。
希望您已经了解了更多关于不同类型的测试、如何编写和运行单元测试、有用的提示以及单元测试的高级实现。在生产中编写代码时,单元测试很重要;标记出代码中的意外错误,或者防止代码增强破坏代码的其他部分。
作为持续集成持续交付(CICD) 的一部分,单元测试应该在任何代码被推送之前以自动化的方式运行。在测试驱动开发(TDD) 的情况下,一些工作流可以在编写源代码之前优先编写单元测试,以考虑代码应该如何工作。
感谢您的阅读!如果你喜欢这篇文章,请随意分享。
相关链接
coverage.py文档:https://coverage . readthedocs . iodoctest文件:https://docs.python.org- https://mypy.readthedocs.io/en/stable/T2
pytest文件:https://docs.pytest.org- https://docs.python.orgT4
数据表单中具有依赖关系的 SQL 脚本的单元测试
原文:https://towardsdatascience.com/unit-tests-for-sql-scripts-with-dependencies-in-dataform-847133b803b7
数据仓库 Gitflow 管道来自动运行它

作者图片
你对你的数据仓库脚本进行单元测试了吗?
我将谈论复杂 SQL 查询的单元测试,它可能由多个操作(动作)组成。
我在使用 BigQuery 脚本之前尝试过:
然而,这里有另一种(也可能是更好的)方法。
Dataform 是一个很棒的免费 SQL 数据转换工具。它有助于保持您的数据仓库整洁有序。它有很好的依赖图来解释数据谱系,并且它是那里发生的所有事情的唯一真实来源。
什么是好的单元测试?
- 它应该测试预期结果和实际结果
- 应该描述与用例相对应的脚本逻辑。
- 应该是自动化的。
- 保持独立(测试不应该互相设置或拆卸)
- 应该很容易实现。
- 可重复:任何人都应该能够在任何环境中运行它。
- 一旦写好了,就应该留着以后用。
Dataform 支持针对视图的 SQL 单元测试,您可以在 Dataform docs [1]中阅读相关内容。这的确很简单。
假设我们有一张桌子:
考虑这个视图,想象你总是需要它返回三列,这意味着你想要对它进行单元测试:
在数据形式单元测试中,视图很简单:

作者图片
因此,单元测试的文件定义如下所示:
然而,通常这是不够的。
您可能想要运行和测试一个定制的 SQL 操作/一个脚本有多个语句和多个输出。
事实上,当你想对一个 SQL 脚本进行单元测试,而依赖于一些其他的动作,也就是视图、脚本、表格等等,这就变得很棘手了。在这种情况下,您可能希望保持它的原子性,并在每次再次运行时对它们进行一次测试。
假设我们每天/每小时对我们的表执行增量更新,并且您想要对该脚本进行单元测试:
我以前在这里写过关于合并更新的文章:
我们如何为此创建一个单元测试?
- 我将使用 Dataform 在每个依赖项的
*_tests模式中创建输入,即我将要测试的主脚本中的任何表或视图。根据每个表的模式,它将创建一个输入,即production.reputation_data的production_tests.reputation_data,等等。 - 我将要求我的单元测试运行我们将要测试的主要操作
user_reputation(这是一个脚本),并将实际输出保存在*_tests模式中,即production_tests.user_reputation。 - 我将比较我的预期输出(我将在单元测试中提供)和我之前得到的实际输出。
让我们为user_reputation脚本编写单元测试:
如果数据集的实际输出不等于预期输出,则单元测试失败。
意思是:
- 输出行数必须匹配
- 输出列的数量及其名称必须匹配
- 每行的内容必须匹配
让我们运行它:
- 运行标签为
unit_tests的操作。确保你的脚本有它。 - 包括依赖关系。
- 添加模式后缀(
*_tests.any_table)。

作者图片
…

作者图片
结果我们将看到通过我们的单元测试:

作者图片
那么刚刚发生了什么?我们来看看依赖图:

作者图片
Dataform 运行所有的依赖项,但在我的新production_tests.*模式中创建了“假”输出,这样最终的脚本(一个单元测试脚本)可以将它们用作输入。
让我们看看,如果有人决定改变管道中任何依赖关系的逻辑,会发生什么。我将稍微改变一下reputation_data_v:
我将再次运行单元测试(test_user_reputation.sqlx):

作者图片

作者图片
这就是我们如何以数据形式运行 SQL 脚本的单元测试:
- 运行标签为
unit_tests的动作。确保你的脚本有它。 - 包括依赖关系。
- 添加模式后缀(
*_tests.any_table)。
Dataform 有一个命令行界面,因此您可以从命令行运行项目的操作:
在这里阅读更多信息: 2
Dataform 还有一个 docker 映像,因此您可能希望设置一个 Gitflow 管道,以便在每次创建 Pull 请求时运行unit_tests操作:

作者图片
因此,当您将变更推送到您的存储库时,它将运行检查
…让您的管道查看是否有任何逻辑受到影响:

作者图片

作者图片
结论
数据表单是一个很好的数据建模工具。它就像 DBT,但是是用 JavaScript 写的。它具有 Git 和 CI/CD 特性,这使得它非常强大。它自动记录 SQL 脚本并创建漂亮的依赖图,这使得它作为真实的单一来源对于每个将要使用它的人来说非常有用。
目前,新的数据表单注册已经关闭,因为该公司被谷歌悄悄收购。Dataform 现在在谷歌云平台的Preview模式下可用,这意味着它将在几个月后提供完整的功能列表。我已经在Preview尝试了一些东西,但是仍然喜欢在本地使用传统的 Web UI 和控制台包。如果我想将它作为微服务运行,Dataform 有一个Docker映像允许这样做。我在 GCP 的Preview版本中找不到依赖图和其他一些重要特性。
推荐阅读:
https://cloud.google.com/dataform
单变量分析——简介和实施
原文:https://towardsdatascience.com/univariate-analysis-intro-and-implementation-b9d1e07e5c16
使用 seaborn 的单变量分析:统计数据可视化

宇航员在看数据。E 2
作为一名数据科学家,当您收到一组新的不熟悉的数据时,您的第一步是什么?我们开始熟悉数据。这篇文章通过一次只分析一个变量来回答这个问题,这就是所谓的单变量分析。当我们面对一个不熟悉的数据集时,单变量分析可以作为一种熟悉数据的方法。它描述和总结数据,以找到仅通过查看整体数据不容易观察到的模式。执行单变量分析有多种方法,在本文中,我们将介绍一些最常见的方法,包括频率分析、数字和视觉汇总(例如直方图和箱线图)以及数据透视表。
和我的其他帖子类似,学习会通过练习问答来实现。我将根据需要在问题中包含提示和解释,以使旅程更容易。最后,我用来创建这个练习的笔记本也链接在文章的底部,你可以下载,运行和跟随。
我们开始吧!
(所有图片,除非特别注明,均为作者所有。)
https://medium.com/@fmnobar/membership
数据集
为了练习单变量分析,我们将使用来自 UCI 机器学习库的关于各种葡萄酒的化学分析的数据集,该数据集基于“用于数据探索、分类和关联的可扩展包”( Forina,m .等人,1998 ),并且可以从此链接 (CC BY 4.0)下载。
让我们从导入我们今天将使用的库开始,然后将数据集读入数据帧,并查看数据帧的前 5 行来熟悉数据。
# Import libraries
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
# Read the data
df = pd.read_csv('wine.csv')
# Return top 5 rows of the dataframe
df.head()
结果:

正如我们上面看到的,这些是各种葡萄酒的化学分析。我们将主要使用几列,我将在下面简要说明:
- “等级”——指葡萄酒所来自的品种。本研究中有三个品种(1、2 和 3)
- “酒精”——显示葡萄酒的酒精含量
- “苹果酸”——这种特殊的酸存在于葡萄酒中。与来自温暖气候地区的葡萄酒相比,来自寒冷气候地区的葡萄酒具有更高的苹果酸水平
现在我们已经熟悉了将要使用的列,让我们开始分析。
频率分析
频率分析是描述性分析中的基本概念之一,在描述性分析中,研究事件发生的次数。例如,如果我们掷骰子 12 次,得到以下结果:
[1, 3, 6, 6, 4, 5, 2, 3, 3, 6, 5, 1]
那么 1 出现的频率是 2,因为在投掷中 1 出现了两次。现在让我们看看如何在 Python 中实现这个概念。我们将使用“value_counts”方法来查看变量的每个不同值在数据帧中出现的次数。但由于“value_counts”不包含空值,所以我们先来看看有没有空值。
问题 1:
数据帧中有多少空值,在哪些列中?
回答:
# Return null values
df.isnull().sum()
结果:

根据结果,没有一列包含任何空值,因此,我们可以继续使用“value_counts”。让我们继续我们的频率分析。
问题二:
数据集包括来自三个不同栽培品种的葡萄酒信息,如“类别”栏所示。数据集中每个类有多少行?
回答:
# Apply value_counts to the df['class'] column
df['class'].value_counts()
结果:

如我们所见,有三个类别(如问题中所述),栽培品种 2 有 71 个实例,栽培品种 1 有 59 个实例,栽培品种 3 有 48 个实例。
问题三:
创建一个名为“class_verbose”的新列,以替换下表中定义的“class”列的值。然后确定每个新类存在多少个实例,这应该与问题 2 的结果相匹配。

回答:
# Replace according to the mapping table provided above
df['class_verbose'] = df['class'].replace({1 : 'cultivar_a', 2 : 'cultivar_b', 3 : 'cultivar_c'})
# Compare results
df.class_verbose.value_counts()
结果:

正如所料,每个类的实例数量与问题 2 的结果相同。
数字汇总
在本节中,我们将更多地关注定量变量,并探索总结此类栏目的方法。一个简单的方法是使用“描述”方法。让我们看一个例子。
问题 4:
使用“描述”方法创建数据集“酒精”列的数字摘要。
回答:
# Use describe method
df['alcohol'].describe()

描述是不言自明的,正如您所看到的,这是一种非常方便的方法来概述数据的分布,而不是手动生成这些值。让我们在下一个问题中手动生成一些来练习。
问题 5:
返回数据集“酒精”列的以下值:平均值、标准偏差、最小值、第 25、50 和 75 个百分位以及最大值。
回答:
这些可以使用 Pandas 和/或 NumPy(等等)来计算。我在这里提供了两种方法供参考。
# Approach 1 - Using Pandas
print(f"Using Pandas:")
print(f"mean: {df.alcohol.mean()}")
print(f"standard_deviation: {df.alcohol.std()}")
print(f"minimum: {df.alcohol.min()}")
print(f"25th_percentile: {df.alcohol.quantile(0.25)}")
print(f"50th_percentile: {df.alcohol.quantile(0.50)}")
print(f"75th_percentile: {df.alcohol.quantile(0.75)}")
print(f"maximum: {df.alcohol.max()}\n")
# Approach 2 - Using NumPy
print(f"Using NumPy:")
print(f"mean: {np.mean(df.alcohol)}")
print(f"standard_deviation: {np.std(df.alcohol, ddof = 1)}")
print(f"minimum: {np.min(df.alcohol)}")
print(f"25th_percentile: {np.percentile(df.alcohol, 25)}")
print(f"50th_percentile: {np.percentile(df.alcohol, 50)}")
print(f"75th_percentile: {np.percentile(df.alcohol, 75)}")
print(f"maximum: {np.max(df.alcohol)}\n")
结果:

问题 6:
“苹果酸”小于 1.5 的葡萄酒和“苹果酸”大于或等于 1.5 的葡萄酒相比,其酒精含量的平均值如何?
回答:
lower_bound = np.mean(df['alcohol'][df.malic_acid < 1.5])
upper_bound = np.mean(df['alcohol'][df.malic_acid >= 1.5])
print(f"lower: {lower_bound}")
print(f"upper: {upper_bound}")
结果:

图形摘要
在这一节中,我们将看到可视化的定量变量。我们将使用直方图和箱线图,我将在开始提问之前介绍它们。
直方图
直方图是一种可视化工具,通过计算每个条柱中实例(或观察值)的数量来表示一个或多个变量的分布。在这篇文章中,我们将使用 seaborn 的“histplot”类来关注单变量直方图。让我们看一个例子。
问题 7:
创建数据集中酒精含量的直方图。
回答:
# Create the histogram
sns.histplot(df.alcohol)
plt.show()
结果:

这显示了每个酒精含量箱中有多少实例。例如,看起来包含 13.5 酒精水平的箱具有最高的实例数。
箱线图
箱线图展示了定量数据的分布。方框显示了数据的四分位数(即第 25 个百分位数或 Q1、第 50 个百分位数或中值以及第 75 个百分位数或第 3 季度),而胡须显示了分布的其余部分,除了被确定为异常值的部分,异常值被定义为在 Q1 以下或第 3 季度以上延伸超过四分位数间距(IQR)的 1.5 倍。IQR 是 Q1 和 Q3 之间的距离,如下所示。

我们来看例子。
问题 8:
创建一个箱线图,比较三个品种的酒精分布。
回答:
# Assign a figure size
plt.figure(figsize = (15, 5))
# Create the box plots
sns.boxplot(data = df, x = 'class_verbose', y = 'alcohol')
plt.show()
结果:

分层
在数据中寻找模式的方法之一是将其分解成更小的子集或层次,并分别分析这些层次。每个起点都可能有新的发现。为了演示这种技术,我们将看一些例子。
问题 9:
创建一个名为“苹果酸水平”的新列,该列将“苹果酸”列的值分为三个部分,如下所述:
- 最小至第 33 百分位
- 第 33 百分位到第 66 百分位
- 66%至最大值
然后为每个地层的酒精分布创建一组箱线图。你看到新的模式了吗?
回答:
首先,在将“苹果酸”划分为问题中描述的淀粉之前,让我们创建一个酒精水平的箱线图。然后我们将应用分层并直观地比较结果。
# Assign a figure size
plt.figure(figsize = (5, 5))
# Create the box plots
sns.boxplot(data = df, y = 'alcohol')
plt.show()
结果:

如上所述,Q1、中位数和 Q3 分别约为 12.4、13 和 13.7。让我们看看这些值在“苹果酸”开始时是如何变化的。
# Calculate the cut levels
minimum = np.min(df.malic_acid)
p33 = np.percentile(df.malic_acid, 33)
p66 = np.percentile(df.malic_acid, 66)
maximum = np.max(df.malic_acid)
# Create the new column
df['malic_acid_level'] = pd.cut(df.malic_acid, [minimum, p33, p66, maximum])
# Assign a figure size
plt.figure(figsize = (15, 5))
# Create the box plots
sns.boxplot(data = df, x = 'malic_acid_level', y = 'alcohol')
plt.show()
结果:

这个挺有意思的。还记得酒精浓度的中位数是 13 左右吗?现在我们看到“苹果酸”水平中间值的一些变化。例如,我们看到在蓝色和橙色箱线图的“苹果酸”的中间值之间存在相对较大的差异,它们对应于两个不同的星形图,分别代表低和中范围的“苹果酸”水平。另一个观察结果是,蓝色箱线图具有更大的范围(从~11 到14.8),而绿色箱线图具有更大的“苹果酸”水平,具有更小的范围(从11.5 到~14.4)。
让我们把这一层再分层,作为练习。
问题 10:
创建与上一个问题相似的箱线图,但针对每个品种。
回答:
# Assign a figure size
plt.figure(figsize = (15, 5))
# Create the box plots
sns.boxplot(data = df, x = 'malic_acid_level', y = 'alcohol', hue = 'class_verbose')
plt.show()
结果:

接下来,让我们试着用表格的方式总结一下。
数据透视表
数据透视表是分组值的表格表示形式,这些值在特定的离散类别中聚合数据。让我们通过例子来理解实践中的数据透视表。
问题 11:
创建一个数据透视表,显示每个品种在每个苹果酸水平下的酒精含量。
回答:
# Create the pivot table
pd.pivot_table(df[['malic_acid_level', 'class_verbose', 'alcohol']], index = ['malic_acid_level', 'class_verbose'], aggfunc = 'count')
结果:

让我们读取其中一行来了解结果。第一行告诉我们在(0.74,1.67)的“苹果酸水平”内有 16 个“品种 _a”的实例。正如您在上面的脚本中看到的,我们在这个数据透视表中使用“count”作为聚合函数,因为这个问题问的是那些离散的类中有多少个实例。还可以使用其他聚合函数。让我们在下一个例子中尝试其中的一个。
问题 12:
创建一个数据透视表,展示每个苹果酸水平范围内每个品种的平均酒精水平。
回答:
注意,这次我们要实现一个聚合函数来计算平均值。
# Create the pivot table
pd.pivot_table(df[['malic_acid_level', 'class_verbose', 'alcohol']], index = ['malic_acid_level', 'class_verbose'], aggfunc = 'mean')
结果:

带练习题的笔记本
下面是带问题和答案的笔记本,您可以下载并练习。
结论
在这篇文章中,我们讨论了如何利用单变量分析作为通过数据了解新空间的第一步。在开始对数据进行任何推断之前,我们希望了解数据是关于什么的,单变量分析为我们提供了了解每个变量的工具,一次一个变量。作为单变量分析的一部分,我们学习了如何实施频率分析,如何将数据总结为各种子集/层次,以及如何利用直方图和箱线图等可视化工具来更好地了解数据的分布。
感谢阅读!
如果你觉得这篇文章有帮助,请关注我的媒体并订阅接收我的最新文章!
释放数据科学的隐藏价值
原文:https://towardsdatascience.com/unlock-the-hidden-value-of-data-science-3963f13b21f3
有价值的见解和模式正等待数据科学家发现,供企业和政府使用。

今天,我们正在见证一场颠覆性的运动,这场运动将彻底改变商业运作和我们的生活方式。我们给这个运动起的词是 数据科学 。它提供了巨大的潜力来改变我们的日常生活,治疗癌症或保护国家免受恐怖袭击。
信息本身是没用的。事实上,当你考虑到收集和存储大量数据的成本时,它比无用更糟糕。然而,许多有价值的见解和模式正等待数据科学家发现,以供企业和政府使用。尽管数据科学被从业者和研究人员视为创新的技术突破,但公司在从数据科学投资中产生商业价值方面仍面临问题。

麦肯锡[1]预测,到 2030 年,数据科学和人工智能(AI)对全球经济的潜在影响将在 1300 万美元左右。他们还预计,到 2030 年,数据科学和人工智能的非采纳者将经历 20%的现金流下降。这些期望迫使企业将数据科学和数据驱动的文化视为其核心资产,以开发其充满希望的潜力。然而,他们主要苦于缺乏基于结构化方法的全面路线图来评估和提高他们的数据科学能力[2]。根据 NewVantage Partners 的一项调查,大多数业务离数据驱动还很远。
数据科学的最终目标是从原始数据中探索有价值的见解。我们需要遵循一个系统的路线图来创建一个从收集数据到将数据转化为商业行为的价值链。从数据中创造跨职能和战略价值需要克服与数据相关的、组织的、技术的和战略的挑战( DOTS ) [2]。在下文中,我概述了的四个关键支柱——点,它们可以帮助企业最大限度地发挥其数据科学能力。
D ata: 数据是您拥有的最重要的资产。 了解您的业务并 明确定义您的数据需求 以决定您应该进行哪些投资来从您的资产中获得最佳杠杆效应。公司应该从确定数据科学如何在其业务战略背景下提供价值开始。这使得企业能够规划其业务机会,同时也解决了数据科学问题中的瓶颈。在定义业务需求时,公司应该考虑确定他们的数据需求可用的数据源,并分析这些数据源的质量,以开始收集原始数据。
智能设备、物联网(IoT)、Web 2.0 和社交媒体等新兴技术和概念的激增为收集大量复杂的实时数据(称为大数据)铺平了道路。大数据是指从大量通常完全不同的数据源中收集大型、复杂、非结构化和连续的数据。数据规模、速度和不断变化的数据特征的指数级增长促使企业采用新兴数据和分析策略来保持竞争优势。然而,为了避免大数据带来的这些潜在优势,组织需要投资于数字化转型。然而,这并不是一项微不足道的任务,尽管大数据具有破坏性影响,但组织却不知道如何将大数据集成到日常 IT 生命周期中。
O 组织: 建立数据驱动的文化。数据科学使组织能够将这种大数据转化为可操作的见解和商业价值,从而实现数据驱动的组织范式。数据驱动型组织利用数据科学,根据提取的数据洞察做出每个业务决策。为了拥有数据驱动的文化,组织需要定义一个透明而清晰的愿景&战略,以了解组织需要进行哪些投资。这些投资可能包括分配人力和技术资源、重建组织结构和建立新的企业文化。当您在组织中建立数据驱动的文化时,您就能够推动新的收入流和商业模式的战略变革。您还可以用数据科学来增强现有产品和服务的能力。此外,除了现有的渠道,您还可以探索进入市场和分销渠道的新途径。
组织结构、领导力、人才管理和文化对数据科学项目的成功有着重要影响。公司需要在数据科学团队、利益相关者和执行经理之间建立企业文化、高效领导和沟通策略,以成为数据驱动的组织。因此,最关键的需求是开发、获取和协调人力和组织资源,以支持整个组织的数据分析思维。因此,组织成熟度关注数据驱动的文化和管理策略如何在员工和高管中实施和接受。
T 技术: 考虑您组织的技术能力,以设计可修改的端到端解决方案。因为今天的大数据是来自大量通常分散的数据源的大型、复杂、非结构化和连续数据的集合[3],使用传统的数据库管理工具或传统的数据处理方法来处理这些数据并不容易。没有“一刀切”的硬件或软件解决方案来收集、存储、管理和处理数据。相反,每个平台、软件和框架都有自己的优点和缺点。因此,评估哪种类型的技术堆栈适合您的需求和业务需求,以及它们缺少什么样的功能来执行[4]。现阶段最重要的问题是我们如何将新兴的数据科学解决方案集成到我们现有的技术体系中?为了实现新技术投资的投资回报(ROI ),这些新兴解决方案应迅速整合到业务运营中。这些技术应该可供所有员工使用和访问。您可能决定投资一些直接嵌入到您已经使用的现有技术堆栈中的技术。否则,如果你也考虑培训员工来提高这些技术的有效性,那是最好的。请记住,要创建数据驱动的组织文化,这些技术的敏捷性、可伸缩性和灵活性至关重要。
S 策略:定义和阐明组织策略在以高效和有效的方式管理数据科学投资方面发挥着重要作用。组织需要清晰明确的数据科学愿景、战略和路线图,以应对成为数据驱动型组织的挑战。执行成员、数据科学家和利益相关方需要在此战略、愿景和路线图中保持一致,以确定之前的业务案例,并为与战略业务方向相对应的数据科学项目提供资金。一个定义明确且一致的战略和愿景可以推动组织在数据驱动的范式上前进,并被视为未来投资的一个重要成功因素。要做到这一点,组织应该有一个定义明确且标准化的愿景&战略、赞助和资助、战略协调以及利益相关方参与流程。
数字化转型等范式转变产生了复杂的数据,这些数据已成为当代组织的战略资产。为了实现回报最大化,企业通过克服与数据相关的组织、技术和战略挑战,努力成为数据驱动型企业。这篇文章组织了这些挑战。
参考文献
[2]k . Kaya bay,m . o . g . kalp,e . Eren,P. E .,& Koç yiğ it,A. (2022 年)。数据科学路线图:促进向数据驱动型组织转型的架构框架。技术预测和社会变革
[3]g kalp,M. O .,g kalp,e .,Kayabay,k .,koyi it,a .,& Eren,P. E. (2021 年)。数据驱动制造:数据科学成熟度评估模型。制造系统杂志, 60 ,527–546。
[4] Cetindamar D,Phaal R,Probert D .技术管理:活动和工具。麦克米伦国际高等教育;2016.
使用亚马逊 SageMaker 解锁最新的变压器型号
关于扩展和定制 AWS 深度学习容器的快速教程

图片由作者使用 Midjourney 制作
这是怎么回事?
由于方便易用,AWS 深度学习容器(DLC)已经成为亚马逊 SageMaker (SM)上训练和部署自然语言处理(NLP)模型的热门选择。然而,有时预构建的 DLC 中没有最新版本的 Transformers 库。在这篇博文中,我们将扩展这些 DLC,在 AWS 上训练和部署最新的拥抱脸模型。无论你是 DLCs 新手还是有经验的用户,这篇文章都将为在 AWS 上运行拥抱人脸模型提供有价值的见解和技术。
本教程的代码可以在这个 GitHub repo 中获得。
为什么这很重要?
亚马逊 SageMaker 是一个运行 AI 模型的流行平台,但运行最新的变形金刚模型(例如耳语、布鲁姆)需要最新版本的变形金刚库。然而,AWS 上最新的可用 DLC 仅支持版本 4.17,而在撰写本文时,最新版本是 4.25.1。当试图将最新的 DLC 用于不受支持的型号时,用户会遇到一条错误消息,例如,参见拥抱脸(HF)论坛上的这个主题
这个问题的一个解决方法是将 requirements.txt 文件注入到模型中,但是这可能是一个缓慢而乏味的过程。它需要下载模型,添加 requirements.txt 文件,打包模型,并上传到 S3,对于大型模型来说,这可能需要几个小时。在这篇博文中,我们将利用 AWS 的预建 DLC 可以扩展这一事实,从而为用户节省这一变通办法,并因此在他们想要培训或部署新模型时节省许多时间。
如何在 SageMaker 上部署 Transformer 模型
在 SM 上部署 Transformer 模型通常是一件轻而易举的事情,尤其是如果您想使用预先构建的模型而无需进一步培训的话。在这种情况下,我们可以将模型直接从 HF 模型中心部署到 SM:
以下是运行这段代码时在后台发生的情况:

作者图片
一旦执行了 deploy()命令,SM 就会启动一个 EC2 实例,并从指定的 DLC 中获取映像,这是由 transformers 和 pytorch 库的版本号决定的。我们可以在这里找到可用 DLC 的列表——这些是从一个特定的 AWS 账户中获取的,并且是公开可用的,参见这个例子。
正如我们在这个列表中看到的,DLCs 中最新可用的变形金刚版本是 4.17,但许多型号将需要比这更高的版本。
最新型号变压器的问题是
当我们试图使用最新的 DLC 运行需要高于 4.17 版本的模型时,我们可以看到这一点。部署将会成功,但是当我们尝试使用该模型时,我们会收到以下错误消息:

作者图片
在 Cloudwatch 日志中,我们可以看到更多信息:

作者图片
这意味着我们试图通过最新的 DLC 部署的模型(在这种情况下是 BLOOM)是不可能的。
解决方法—注入一个 requirements.txt 文件
如上所述,有一个解决方法——HF SM 推理工具包允许定制推理代码,以及通过 requirements.txt 文件指定所需的附加库的可能性。我们可以通过将最新的 transformers 版本添加到 requirements.txt 文件来使用这种机制,如下所示:
然而,要将这个 requirements.txt 文件注入到模型中,需要一些额外的步骤:

作者图片
在这种情况下,我们首先需要从 HF 模型中心手动下载模型。然后,我们将 requirements.txt 文件添加到模型目录中,并将其打包。然后,模型需要上传到 S3 桶。然后,最后,我们可以部署模型,将端点指向模型的 S3 位置。当启动端点的 EC2 实例时,将读取 requirements.txt 文件并安装最新的 transformers 版本。
如果这是一个相对较小的模型,这是繁琐的,但不会花费太长时间。然而,如果这是盛开的模式,整个过程可能需要 12 个小时(相信我,我试过了😕)
解决方案—扩展预构建的数据链路连接器
相反,我们希望直接从 HF 模型中心进行部署,特别是在不需要定制代码或者需要任何模型微调的情况下。在这种情况下,我们可以编写一个 Docker 文件,首先从公共 AWS ECR 中提取最新的 DLC,然后添加我们自己的要求,在这种情况下,只需一个“pip install”命令即可更新到最新的 transformers 版本:
然后,我们可以运行官方 AWS 教程来扩展 DLCs,我们只需要确保我们调整了命名,并且我们运行该脚本的角色拥有读取和写入 ECR 服务的权限:
几分钟后这个脚本完成后,我们应该会在 ECR 中看到新的 DLC。
测试新的数据链路连接器
现在新的 DLC 已经准备好了,我们可以再次测试之前的部署,但是这次使用我们的扩展 DLC:
现在,当我们运行一个推理请求时,我们得到一个正确的响应:

作者图片
这是现在在后台发生的事情:

作者图片
我们曾经努力扩展 DLC,但是现在,每当我们想要从 HF Model Hub 部署需要最新版本的 transformers 库的模型时,我们可以重用我们的 DLC,而不是 AWS 的官方 DLC🤗
结论
在本教程中,我们扩展了 AWS 的官方 HF DLCs,将变压器库更新到许多新变压器型号所需的更高版本。通过这样做,我们创建了自己的可重用 DLC,使我们能够直接从 HF 模型中心进行部署,从而节省了我们数小时的繁琐工作。
注意,这篇博文主要关注的是扩展推理 DLC,然而,同样的想法也可以用于训练 DLC。
深入双向理解 RNNs
原文:https://towardsdatascience.com/unriddling-rnns-with-depth-and-in-both-directions-9ed336c4b392
让我们抽象掉所有的抽象,真正理解它们!
序列模型是深度学习起飞的一个重要原因。他们为我们提供了一种从文本或语音等序列数据中学习的方法,从而极大地推动了技术的发展,为无数有用的应用铺平了道路,否则使用经典的机器学习是不可能轻松实现的。
序列模型(与 Optimus Prime 没有太大关系)的核心是递归神经网络(RNNs)——我们将在这个故事中花足够多的时间来讨论这种神经网络。
RNNs 有什么新特性?
rnn 帮助我们完成 FFNNs 不容易做到的事情。特别是,从 到另一个的一个变长向量序列的近似映射。这有效地概括了 FFNNs 从一个固定大小向量** ( x ) 到另一个 ( y )的近似映射的能力。**
这意味着 RNN 的输入将是一个向量序列(例如,每个单词由一个向量表示的句子),并且我们期望输出也是一个同样长的向量序列(例如,帮助对输入句子中的每个单词进行分类的概率向量)。
正如我们将进一步阐述的,RNN 逐个处理输入序列中的元素,并为每个元素生成一个输出。

杰克·伦纳德在 Unsplash 上的照片
循环层
作为一种架构,您可以将 RNN 视为一种 FFNN,它允许一种称为“循环层”的新型层。循环层的作用是
1-它接收来自前一层的激活,并将其乘以一个权重矩阵,就像 FFNNs 中使用的密集层一样
2-它获取由于序列中的先前输入而自身产生的激活,然后将其乘以权重矩阵。
3-它增加了前两个结果的偏差
4-然后对其应用激活函数,并提供结果作为进一步层的输入,就像 FFNNs 中使用的密集层一样
就是这样。我们也喜欢称这种循环层的激活为“隐藏状态”。用 h ₜ来表示由于输入序列中的 tₜₕ向量而导致的隐藏状态。
你可能会想到,“如果序列中的第一个单词没有先前的激活,我们如何计算它的激活?”答案是隐藏状态总是被初始化,全零是最常见的选择。在这种情况下,循环层的行为与序列中第一个输入的密集层完全一样。
递归神经网络
我们可以通过将 rnn 与 FFNNs 并列来理解它们,如下图所示。请注意,在 RNN 中有一个额外的权重矩阵(红色连接),在计算循环层激活时,除了由于当前输入而导致的激活之外,该矩阵还使循环层因子成为由于先前输入而导致的激活。这意味着当输出图层(只是一个密集图层)对当前输入做出决策时,它会考虑当前输入以及以相同顺序出现在它之前的所有输入。

作者绘图
现在,根据我们所说的,我们可以用以下等式来表示每个网络的前馈通道

这也使得 RNNs 的训练和推理都将花费更多的时间。尤其是当序列很长的时候。

乔纳斯·阿莱特在 Unsplash 上拍摄的照片
更深入
到目前为止,我们考虑的 RNN 有一个输入层、一个作为隐藏层的递归层和一个输出层。有两种方法可以更深入,一种是添加更多的递归图层(将每个图层都视为黑盒),另一种是添加更密集的图层(通常在输出附近)。
回想一下,一个递归层学习一个考虑了先前标记的表示,如果你在它上面堆叠另一个递归层,那么它将学习一个更复杂的表示,该表示也考虑了与第一个递归层学习的表示中的元素相关的模式。同时,如果你堆叠一个密集的层,它所学习的任何复杂特征将与来自循环层的序列是否涉及将序列中的一个元素与前一个元素相关联的模式无关。也就是说,堆叠另一个循环层应该被期望利用更多要学习的特征的潜在复杂性。出于这个原因,当提到架构使用多层 RNN 时,通常被称为“多层”的是循环层。
因为与每个递归图层相关的计算量与输入的长度成比例,所以拥有多个递归图层会使您的模型变得迟钝。为此,您可能很少看到使用两个以上循环层的架构。
示例应用
我们在经典 RNN 中已经解释过了。它可以帮助你学习从一个序列到另一个序列的映射,但是它们必须总是一样长。这不适合许多复杂的任务,如翻译、摘要等。其中输入和输出序列长度不同,但它可以处理诸如命名实体或语音识别部分(以及无数其他任务)的应用,在这些应用中,输入句子中的每个标记都需要被分类到不同的类别。下面是命名实体识别的一个例子,其中每个单词根据它是否是命名实体以及如果是命名实体则根据实体的类型来分类。

为了处理像翻译或摘要这样的应用,我们需要将 rnn 推广到序列到序列模型,该模型使用两个 rnn 而不是一个,但是放宽了输入和输出序列必须具有相同长度的条件。我们也许可以在另一个故事中讨论这些。

双向 RNNs
在某些应用中,了解以前的标记和将来的标记可以极大地提高模型的性能。例如,在上面的例子中,如果不访问句子的其余部分,模型很难分辨 chase 是某人的名字还是动词。如果模型被训练成双向处理句子,这可能就不会发生,这正是 BRNN 通过利用双向递归层所做的。
双向轮回层
双向循环层只是两个独立的循环层,其中
1-一个递归层从左到右考虑输入,另一个从右到左考虑输入。这意味着每个单词对应两种隐藏状态,分别来自每一个循环层。
2-当两个递归层都完成时,句子中任何输入标记的双向递归层的整体隐藏状态(将被馈送到其他层)由它的两个相应隐藏状态的串联给出。
这意味着当输出层做出与句子中的一个标记相对应的决定时,它会考虑在它之前和之后的所有标记的存在。这是真的,因为输入到它的是两个隐藏状态的串联,一个考虑单词及其之前的所有内容,另一个考虑单词及其之后的所有内容。
下图显示了一个展开的 BRNN 的例子。我们有两个递归层(一个红色,另一个灰色),以相反的顺序处理输入。我们将输出提供给 oval,它可以是一个输出分类层。

作者绘图

就这些吗?还有新的层吗?
rnn 是通过所谓的时间反向传播来训练的,这是对处理循环层的正常反向传播的扩展。类似于反向传播具有消失梯度问题,其导致权重在网络越深时停止更新,通过时间的反向传播具有序列越长的消失梯度问题。这可能会严重损害模型学习长期依赖性的能力,这对许多应用程序来说是一个严重的限制。
事实证明,如果我们设计一个更复杂的循环层,我们可以克服这个问题。长短期记忆(LSTM)和门控循环单位(GRU)就是这种层次的例子,我们可能会在另一个故事中谈到。
这总结了我们的文章,希望它能让你了解 rnn 是如何工作的,以及为什么他们擅长自己的工作。下次见,再见。
Spotify 数据的无监督异常检测:K-Means 与局部异常因子
通过发现不寻常的东西来探索我的 Spotify 数据

在 Unsplash 上由 Mohammad Metri 拍摄的照片
什么是异常?
异常是偏离标准或的数据模式,从其余数据中假设行为[1]。根据这个定义,异常既不可怕也不伟大;它们只是异常现象。
图 1 示出了二维空间中的一组点。有两个主要的集群和一些异常。

图一。二维数据集及其异常。由作者提出并受到启发[1]
根据工作领域的不同,数据中的异常是由多种原因造成的;它们可能是信用卡检测(金融)、非法狩猎或砍伐森林(自然科学)、社会行为的改变(社会科学)以及其他活动。然而,这些原因有一些共同点:它们都是利益所在。异常的有趣性或现实相关性是异常检测的一个关键特征。[1]
重要的是不要混淆异常和噪音。噪声是数据中的一种现象,分析师对其不感兴趣,但却是一种障碍。
新奇与异常检测

这项工作的目的。
在这个项目中,我将应用两种方法来检测异常,比较它们并观察结果。当谈到无监督学习时,我喜欢用我对该主题的思考或了解来补充计算机所说的。
用于异常检测的机器学习
k-均值算法
K-Means 算法寻求在数据集中找到 K 个聚类。这些簇必须尽可能地彼此分开,并使它们的元素尽可能地靠近[3]

图二。数据集中聚类的表示。
您可能想知道如何使用聚类算法来检测异常值。嗯,不久前,我分享了一个关于这个主题的指南,这里是。

图 3。使用 K-Means 作为异常检测算法的 Instagram 帖子。
这些步骤是:
- 将 K-Means 应用于数据集(选择您偏好的 k 聚类)。
- 计算每个分类的点到各自分类的质心之间的欧几里德距离。
- 用直方图表示这些距离。
- 找出直方图中的异常值。
这种方法很有趣,因为您可以了解数据中的组,并找到与这些组相距甚远的实例。在这种情况下,每个聚类点到其各自聚类质心的距离将通过修改的 Z 得分(zmod) 进行标准化,这是一种发现异常的优秀度量。
本地异常因素(LOF)
T2 离群值的定义并不总是一成不变的。这些年来它已经改变了。一开始,人们想到的是全局异常值,但后来局部异常值被引入。
局部异常值因子(LOF) 测量给定点到其邻居的密度的局部偏差。该分数取决于该对象相对于周围邻居的孤立程度[2]。LOF 不会返回二进制响应,因此必须给出一个阈值来识别它是否是异常。

图 4。数据点和局部异常值因子(分数)的表示
如果你想深入这个话题,这里有 Markus M. Breunig 的研究论文,“LOF:识别基于密度的局部异常值”[3]
Spotify 数据上的应用
我使用的数据是 Spotify 每年推出的包装播放列表中每 100 首歌曲的汇编。
选择功能
异常检测算法将应用于不超过三个特征的 N 组中。通过这样做,数据点不是非常稀疏(更多的维度=数据点之间更多的空间)。局部异常因子(LOF) 和 K 均值分别是基于密度和距离的算法。
出现了一些问题,这些问题以成对特征的形式表示:
- 价和艺人 _ 人气:价描述了一首曲目所传达的音乐积极性。高价的音轨听起来更积极(例如,快乐、愉悦、欣快),而低价的音轨听起来更消极(例如,悲伤、沮丧、愤怒)[4]。
可能是什么异常现象?:
著名艺人的一首歌,比较正面还是比较负面?一首非当红歌手的歌更积极还是更消极? - 节奏和持续时间 _ 分钟:节奏是给定乐曲的速度或节奏,直接来源于以 BPM(每分钟节拍数)计量的平均节拍持续时间[4]。人们总是想对所有事物进行分类,音乐也不例外——这是听节奏的一种方式(因为还有其他方式)。例如,雷鬼的典型 BPM 为 60-90,Hip-Hop 为 85-115,Pop 为 100-130。我不试图对我听的歌曲进行分类,而是从异常中识别出具有预期持续时间的歌曲;想想大多数流行歌曲平均持续 3.5 分钟(210 秒)。他们中有多少人属于这种假设?那些没有的人呢?关于音乐流派和速度的更多信息,请访问 音乐流派及其典型 BPMs 。歌曲时长请访问 一首热门歌曲通常 3 到 5 分钟长。原因如下。
- 可舞性和能量:有多少适合跳舞的歌曲(高可舞性)能量不高(低值能量)?这似乎是矛盾的,因为人们大部分时间都把跳舞和能量联系在一起,但是有趣的反常现象还没有在这种矛盾中被发现。
异常检测
如何识别异常情况?
为了识别异常,必须给每个指标一个阈值:
- k-means:a𝑧modt42】3离群值(远离中位数的一个值)
- 局部异常因子(LOF): 一分> 1.25
哪些歌曲是异常/异常值?
就 LOF 而言,我们只需要看看比分。然而,对于 K-Means 算法的情况,必须采取不同的观点。
每个实例的 zmod 是根据各自的聚类质心,所以异常也是根据分配的聚类。因此,在分析和解释过程中牢记这一点至关重要。
案例 1:效价与艺术家受欢迎程度
在第一种情况下,根据这些特征有三种类型的歌曲:
聚类 0: 来自高人气即正面的艺人的歌曲。例如:
聚类 1: 来自高人气的艺人不正面的歌曲。例如
- 阿莱西娅·卡拉《我想你别给我打电话》:效价=0.334,艺人 _ 人气=0.81
- Kygo 的《脆弱》(英尺。Labrinth) :效价=0.218,艺人 _ 人气=0.84
聚类 2: 覆盖所有价值的低人气艺人的歌曲。例如:
- 威利斯·厄尔·比尔的《太干了,哭不出来》:化合价=0.421,艺人 _ 人气=0.3
- 格里塔·艾萨克的《悲观主义者》:化合价=0.729,艺术家人气=0.39
valence和artist_popularity中的异常(修改的 Z 分数)
集群 0 中的异常
聚类 0 具有来自具有高流行度的艺术家的歌曲,这些歌曲是正面的。在这种情况下,异常的是对于艺术家流行范围来说过于正面的歌曲。例如:
- 马特·纳森 : 化合价= 0.95艺人 _ 人气 =0.61
- 艾德·希兰塑造的你 : 价 =0.931、艺人 _ 人气 =0.97
一个非常有趣的异常是阿莱西娅·卡拉的歌曲拆箱介绍,但是为什么呢?歌曲有化合价 =0.974(最大);然而,整首歌的意义和本质是关于困惑和被不积极的感觉包围。
集群 1 中的异常
聚类 1 有来自高流行度、不积极的艺术家的歌曲。在这种情况下,异常的是对艺人流行度范围不完全正面的歌曲。例如:
一些异常引起了我的注意:
- 你&我 by Bassnectar : 化合价= 0.151艺人 _ 人气 =0.60
- 怎么了布莱克威的危险 : 效价= 0.116艺人 _ 人气 =0.61
不要被这位歌手的受欢迎程度所迷惑,这两首歌都很受欢迎。第一首是夏季颂歌,从我的角度来看,并不觉得消极。据它的创造者说:
这是一首情歌:不一定是关于浪漫,而是关于友谊和家庭牢不可破的纽带。这是一种感觉,当你知道有人无论如何都会支持你,直到时间的尽头。😃"
第二首歌是奥斯卡奖得主“蜘蛛侠:平行宇宙”的配乐的一部分这首歌在电影高潮的一个场景中播放,并定义了主角是谁以及他在整个电影中的旅程。在我看来,这首歌的精髓感觉更像是自信;这是关于面对你最大的恐惧(不管它们是什么),拥抱你是谁,并采取信心的飞跃。
集群 2 中的异常
聚类 2 具有来自具有低流行度的艺术家的歌曲,覆盖所有价值。在这种情况下,异常是关于极值的歌曲,太正或不太正。
比如(不太正):
- Lxandra : 化合价= 0.112艺人 _ 人气 =0.44
- juju bee《我是女王》 : 化合价= 0.116艺人 _ 人气 =0.31
比如(太正):
- 8 Casi Creativo 的 Vasos Al día:价= 0.891艺人 _ 人气 =0.40
- 迈克尔·克鲁兹 : 效价= 0.927艺人 _ 人气 =0.36
valence和artist_popularity中的异常(局部异常因素)
使用这种算法,异常是那些没有很多邻居的异常(根据给定的阈值)。在某些情况下,用 K-Means 检测到的异常几乎相同。
这种情况下,不如重点关注 K-Means 没有发现的异常。
有一些非常有趣的异常现象是用第一种方法检测不到的。其中一些是:
- 柳树和流放都是由泰勒斯威夫特创作的:第一首歌是第 0 簇的一部分,而第二首是第 1 簇的一部分。同一艺术家在两个不同的集群(或情绪)中的异常。
- 哈里·斯泰尔斯:这首歌是这个数据集中第二首最悲伤的歌;这是由 LOF 和 K-Means 检测到的异常。
- 身体对话。凯莎):这首歌与同一位艺术家的另一首歌很接近:像我这样的首席女歌手(就化合价而言),这让我想知道为什么身体语言是一种异常现象,而像我这样的首席女歌手不是。通过查看上面的散点图,我们可以看到它们之间的微小距离(或空间)造成了差异。
- 费莉西蒂和王国舞蹈《恐高症》中的飞行员——来自《纠结》:如果我们试图找到这两个点在哪里,我们会注意到它们彼此相距很远,并且都被检测为异常。
案例 2:节奏和持续时间
在这种情况下,根据这些特征有三种类型的歌曲:
集群 0: 中高速(中值 147 BPM)、中值持续时间 3.5 分钟的歌曲。
- 艾薇儿·拉维尼 Sk8er Boi:节奏= 149 BPM时长 _ 分钟** = 3.4 分钟**
- 我们不在乎 Sigala : 节拍 =180 BPM 和持续时间 _ 分钟** = 3.45 分钟**
聚类 1(数量最多的聚类):中低速(中值 103 BPM),中值持续时间 3.43 分钟的歌曲。
- 杜阿·利帕悬浮 : 节拍= 103 BPM时长 _ 分钟 = 3.39 分钟
- 卢卡斯·格拉汉姆妈妈说 : 节拍= 83 BPM时长 _ 分钟 = 3.44 分钟
集群 2(数量最少的集群):具有宽节奏范围(130 BPM 的中值)和 6 分钟的中值持续时间的歌曲。****
- 不停(汉密尔顿音乐剧) : 节奏= 91 BPM时长 _ 分钟 = 6.42 分钟
- 枪炮玫瑰乐队的《我的甜心小子》 : 节拍= 125 BPM时长 _ 分钟 = 5.9 分钟
tempo和duration_minutes中的异常(修改的 Z 分数)
集群 0 中的异常
聚类 0 具有中高节奏(147 BPM)或中值持续时间为 3.5 分钟的歌曲。异常的速度高于中值或持续时间高于中值。其中一些是:
- 梵高作品 : 节奏 = 205 BPM,时长 _ 分钟 = 3.88 分钟
- 加托 Que Avanza,Perro Que Ladra by Residente/Calle 13:tempo= 170 BPM, duration_minutes = 5.10 分钟
- Labrinth 公式 : 节拍= 144 BPM时长 _ 分钟 = 1.53 分钟
其他星团的边缘有两个异常点:
集群 1 中的异常
集群 1 具有中低节奏的歌曲(中值为 103 BPM)和中值持续时间为 3.43 分钟。异常的速度低于中值或持续时间高于中值。其中一些是:
- 天鹅湖,作品 20,第二幕彼得·伊维奇·柴可夫斯基 : 速度= 58 BPM时长 _ 分钟 = 2.78 分钟
- Un Beso de Desayuno by Residente/Calle 13:tempo= 85 BPM 和 duration_minutes = 4.84 分钟
- 阿莱西娅·卡拉的拆箱介绍 : 速度= 50 BPM时长 _ 分钟 = 0.68 分钟
集群 2 中的异常
集群 2 具有具有宽速度范围(130 BPM 的中值)和 6 分钟的中值持续时间的歌曲。
异常的持续时间比中位数长。异常情况有:
- 闪耀——电影版。由 RADWIMPS : 速度 = 124 BPM 和持续时间 _ 分钟 = 8.96 分钟
- 林纳德·斯金纳德乐队的自由鸟 : 节拍= 118 BPM时长 _ 分钟 = 9.11 分钟
tempo和duration_minutes中的异常(局部异常因素)
应用 LOF 时发生了一个奇怪的现象:来自聚类 2 的所有点都被认为是异常值。
案例 3:可舞性和能量
在这里,根据这些特征有两种类型的歌曲:
聚类 0: 具有中高能量(中值为 0.773)的歌曲,不考虑能量的值****
- 用 DNCE 表示:danceability = 0.7,energy= 0.748
- 约翰·传奇的《现在爱我》 : 跳舞度= 0.416能量 = 0.773****
聚类 1: 中低能量(中值 0.486)的歌曲,不考虑能量的值****
- 奥利维亚·罗德里戈驾照:舞蹈能力= 0.561能量 = 0.431
- 卡门·萨拉希著 : 舞蹈能力= 0.397能量 = 0.356
danceability和energy中的异常(修改的 Z 分数和局部异常值因子)
通过可视化这一场景,我们可以注意到异常/异常值位于数据集的边界,这意味着异常要么是不可思议的,例如:
- 糖果计划 B (两个算法中的异常值):舞蹈性 = 0.9 和能量 = 0.85
与否:
- OA—主旋律:(两种算法中的异常值):舞蹈性= 0.136能量 = 0.164
还有,很有活力的歌曲:
- 皮特保罗的坏人(LOF 的异数)舞蹈能力= 0.636能量 = 0.971
与否:
- 《玫瑰人生》——路易斯·阿姆斯特朗的单曲(Z 值中的异常值已修改):舞蹈度= 0.507能量 = 0.0779
结论
这个项目产生了两个主要成果:
异常检测作为探索性数据分析(EDA)的补充
尽管 EDA 旨在理解数据集特征之间的结构、行为和关系,但深入那些经常被忽略的细节也是至关重要的。异常或异常值并不意味着坏的/灾难性的或好的/令人惊奇的事情;它们只是不符合预期行为的信息,需要分析。**
用 K-Means 算法和局部异常因子进行异常检测的清晰实用的比较
有许多其他方法或算法来检测异常;然而,在一个特定的案例中尝试每一种方法并不是理解和使用它们的最佳方式。
局部异常因子容易实现,顾名思义,发现局部异常。第一种情况(化合价和艺人 _ 人气,有些异常是在数据集内部,而不仅仅是在边界处(像王国之舞—出自《纠结》)。当预期行为是寻找具有高密度点的聚类时,该算法是适用的。此外,要找到看起来不如异常多的异常*(不在数据集边界上的点)。*****
实现 K-Means 比 LOF 要复杂一点,因为需要额外的步骤来帮助描述数据点的特征。然而,当我们需要理解或找到数据集中的隐藏类别并找到这些类别中的异常时,K-Means 是一个很好的实现。**
关于我
我是艾萨克·阿罗约,工程物理学家。我想象自己通过使用数据科学和机器学习工具来解决不同类型的问题。我对数据可视化和无监督学习感兴趣。
你可以联系我,或者通过社交媒体(Instagram、Twitter 或 LinkedIn)关注我的工作和经历这里。我也用英语和西班牙语创建内容。
贮藏室ˌ仓库
如果你想看我做的过程,你可以去这个项目的 GitHub 库
****https://github.com/isaacarroyov/spotify_anomalies_kmeans-lof
参考
- 【1】钱德拉,v .,班纳吉,a .&库马尔,V. (2009)。异常检测:综述。美国计算机学会计算调查(CSUR),41(3),1–58。
- 【2】sci kit——学习开发者。(未注明)。2.7.新奇和异常检测。sci kit-学习。2022 年 1 月 6 日检索,来自https://sci kit-learn . org/stable/modules/outlier _ detection . html
- 【3】breu nig,M. M .,Kriegel,H. P .,Ng,R. T .,& Sander,J. (2000 年 5 月)。LOF:识别基于密度的局部异常值。2000 年 ACM SIGMOD 数据管理国际会议记录(第 93-104 页)。****
基于脑启发算法的无监督分类学习和特征降维
关于无监督学习,经典神经科学理论能告诉我们什么?
作为一名经常将机器学习方法纳入研究的大脑研究人员,我经常提醒自己,人工神经网络和生物大脑之间的类比可能过于简化。尽管如此,我对大脑和人工智能之间的交叉感到非常兴奋。事实上,大脑的许多认知能力在某种程度上可以被概念化为机器学习的目标,反之亦然。
例如,人类具有感知其经验中的规律性和共性的自然倾向,基于此,我们将事物简化为可以用较少参数描述的更抽象的类别(即,降维、聚类)。在神经基础设施的帮助下,这一过程通常会自动发生,没有反馈或反馈很少(即,无监督或半监督,与典型的监督学习问题相反)。你可能已经看到一个蹒跚学步的孩子分享她新学到的知识,即在街道另一边行走的那些四条腿的动物(不管它们的品种、大小、颜色和体型如何)都是“狗”,但很难想象她的能力是通过每天看狗的标签图片获得的。
幸运的是,很少有无监督的分类学习问题实际上需要像大脑一样复杂的人工神经网络。在下文中,我将与您分享常见对象的分类信息是如何从一个非常简单的神经网络中产生的,该网络同时实现了维度缩减。希望这可以作为一个例子,说明大脑计算原理的研究如何转化为有实用价值的东西。
1。方法和数据集
目前的分析基于我们研究小组的数据集,其中包含现实世界物体的手动标记特征(可在 CC-BY 4.0 下获得,此处,相关研究出版物见此处)。
在我的上一篇关于人类对常见物体的认识的文章中,我展示了现实世界中的每个常见物体都可以用该物体所具有的二进制向量标记特征来描述。例如,冰淇淋的特征向量在指示诸如“尝起来是甜的”、“是冷的”、“融化的”等属性的位置具有“1”。使用这些特征向量,我们能够通过将不同对象视为线性空间(即特征空间)中的点来测量它们的相似性,并且证明特征向量方法揭示了不同对象之间的分类区别,即,属于相同类别的对象倾向于在特征空间中聚集在一起。然后,我展示了使用 t-SNE 之类的降维技术可以在二维散点图上可视化分类差异。
特征向量方法的一个限制是每个对象只包含几个特征,但是我们需要使用非常高维的特征空间来容纳来自所有对象的所有特征,这有些低效。幸运的是,语义特征可以用机器学习算法进一步提炼和提取,这里我们将研究一个简单而优雅的例子——受限玻尔兹曼机器(RBM)。
一个受限玻尔兹曼机器是一个简单的人工神经网络,由两组神经元组成,“可见单元”和“隐藏单元”。可见单元通常被用作输入单元,而隐藏单元通常被视为输出单元。RBM 的训练机制受到神经科学中著名的赫布法则的启发,该法则由姜懿翔·赫布于 1949 年提出,通常被概括为“细胞一起放电,连接在一起”。换句话说,当单元的点火模式与单元之间的布线不一致时(例如,两个单元连接在一起但异步点火),网络将处于不稳定的“高能量”状态。训练算法的目标是通过迭代调整可见单元和隐藏单元之间的连接来减少能量。实际上,可见单元的数量通常大于隐藏单元的数量。数据被馈送以激活可见单元,而隐藏单元的激活不受约束。以这种方式,RBM 用作无监督的降维算法,因为它给出了从高维向量空间到低维空间的映射。事实上,Hebb 法则通常被认为是无监督学习的神经基础。
2。创造和训练 RBM
在 python 和 scikit-learn 中使用 RBM 非常简单。在下面的例子中,输入 X 是一个矩阵,描述了 995 个对象的 655 个二元语义特征(关于数据集的详细描述,参见我的上一篇文章)。该数据用于训练具有 75 个隐藏单元的 RBM。最后一行代码获得了 75 个隐藏单元的激活,这可以看作是将原始稀疏编码的数据转换成单元数量减少了 88%的更紧凑的表示。就是这样!
现在让我们来看看这种转变的结果。下图显示了原始数据(左)和转换后的压缩数据(右)的 t-SNE 图和分类相异矩阵。在两个 t-SNE 图(左上,右上)中,相同类别的对象以相同的颜色显示。当特征数据通过 RBM 时,类别之间的差异变得更加明显,从而产生更加孤立的类别聚类。值得注意的是,在训练期间,关于物体类别的信息没有提供给 RBM,这意味着 RBM 通过物体的特征向量自己“发现”了类别。此外,生物和非生物之间的差异也变得更加明显(注意右下角相异矩阵中的亮黄色块)。

作者图片
对象类别的独特性可以使用类别间相似性与类别内相似性的比率来量化。有生命/无生命域的独特性也可以量化为域间相似性与域内相似性的比值。原始特征数据的类别显著性得分为 1.21,领域显著性得分为 1.07。相比之下,RBM 变换的特征数据具有 3.52 的分类独特性得分和 1.90 的领域独特性得分,这证明了 RBM 在当前数据集上揭示分类信息的能力。
3。更深的网络,进一步降低维度
RBM 可用作更复杂的神经网络的构建模块。由于 RBMs 不需要监控信号,这可以如下实现:1)用训练数据的输入维数定义第一 RBM 层,并训练它直到它的连接权值收敛;2)将第二 RBM 层堆叠在第一 RBM 之上,并用第一 RBM 的输出训练它,直到它的连接权值收敛;3)将第三 RBM 层堆叠在第二 RBM 的顶部,并用来自第二 RBM 的输出训练它,直到它的连接权重收敛...这样的过程可以根据需要重复多次,从而形成一个非常深的神经网络。出于演示的目的,这里我创建了一个由 2 个 RBM 组成的三层网络。第一个 RBM 具有与上图相同的配置(655 个可见单元,75 个隐藏单元),第二个 RBM 具有 75 个可见单元和 30 个隐藏单元。
下图比较了原始语义特征 (A) 、RBM1 (B) 、RBM2 (C) 的隐藏单元激活的范畴相异度。随着原始语义特征向量通过两层关系数据库,对象类别和生物/非生物领域之间的差异变得更加明显。与原始特征向量相比,Rb m2 的隐单元激活实现了 20 倍的降维。

作者图片
4。结论
在这个玩具示例中,受限玻尔兹曼机器展示了其以无监督方式创建对象特征的低维表示的能力,其中类别信息是集中的。当两个 RBM 组合在一起形成更复杂的神经网络时,这种提取分类信息的能力进一步增强。因此,在我们希望发现隐藏的分类结构或降低数据维数的情况下,RBM 可能是一个合适的候选算法。这个简单的算法也体现了人工智能和神经科学之间的共同点,如果能看到这两个领域之间更令人兴奋的交叉对话,那将会很酷。
基于生成模型的无监督分类
原文:https://towardsdatascience.com/unsupervised-classification-with-generative-models-21c18ce20048
一个简单的机器智能测试


作者图片
我的印象是,在人工智能(AI)概念和工具的巨大空间中,生成敌对网络(GANs)像一头未驯服的野兽一样站在一边。每个人都意识到它们是多么强大和酷,很少有人知道如何训练它们,更少有人能真正找到它们在实际任务中的用途。我可能错了,所以请随时纠正我。同时,我想再看一眼这个奇妙的机器,并研究它在分类和嵌入方面的可能用途。为此,我们将坚持以下计划
- 就培训策略而言,更新我们对 GANs 和使用 WGANs 的论据的知识
- 查看 WGAN 的自定义实现
- 在一组具有设计属性的对象上训练 WGAN
- 使用训练好的模型对另一组对象进行分类,看看我们能否解释这种分类
甘斯简介
参考文献[1]中介绍了 gan。它们由两部分组成——鉴别器和发生器。
鉴别器是一个函数,它接收一个对象并将其转换成一个数字。

GAN 鉴频器部分的示意图。作者图片
当然,根据对象的复杂程度,将它转换成数字可能是一项艰巨的任务。出于这个原因,我们可能会为鉴别器使用一个非常复杂的函数,例如,深层卷积神经网络(CNN)。
从某种意义上说,生成器执行的是相反的任务。它接受一些随机数据作为输入,并从中生成一个对象。我所说的随机数据,实际上是指一些与我们的对象无关的随机数据。通常,它是从随机分布中抽取的具有一定长度的向量。向量长度和分布是固定的,因为模型的参数是不可训练的,这并不重要。

GAN 发生器部分的示意图。图片由作者提供。
与鉴别器的情况一样,发生器函数最好具有一定的复杂性。通常,一个生成器和一个鉴别器有相似的结构,我们很快就会在一个例子中看到。
我故意用“物体”这个词,而不是“图像”,因为我想让我们想得更大。它可以是物理实体、过程或一条信息的任何数字表示。当然,这里的关键是我们对特定类型的对象感兴趣,鉴别器和生成器处理的是同一类型的对象。所以,除了我们的模型,生成器+鉴别器,我们必须有数据。数据给出了我们想要处理的对象的例子。目标是教会模型识别这些对象。
训练是这样的。生成器开始生成对象,目标是生成与我们的数据集相似的东西。起初,这些物体看起来一点也不像。我们将它们提供给鉴别器并收集分数,分数指的是鉴别器的输出。此外,鉴别器对真实对象进行评分。在这里,生成者和鉴别者之间的敌意开始了,这就给这种方法起了“敌对”的名字。鉴别器被训练来最大程度地不同地给真实和生成的对象评分。生成器被训练成生成与真实对象非常相似的对象,以便鉴别器对它们进行相似的评分。现在的诀窍是将这一策略转化为一个或两个目标函数进行优化。
WGAN 培训方法
这里的叙述通常是这样的。在机器学习中,判别模型根据数据学习概率,而生成模型学习联合概率。直觉上,这听起来是对的。就我个人而言,我无法找到一个非常令人信服的严格的数学证明或全面的说明性讨论来为这一说法提供良好的参考。然而,它在文献中被重复了如此之多的次数,以至于我们现在可以认为它是理所当然的,特别是考虑到我们将在以后探索这个性质。
由于我们手上有一个生成模型,我们将学习全概率,并且在训练期间,我们需要一些度量我们与真实事物有多接近。当谈到比较概率时,会出现交叉熵和 Kullback-Leibler 散度等标准度量。假设我们想要量化两个分布 p(x) 和 q(x) 之间的差异。这可以借助交叉熵来完成,交叉熵定义为
最后一项是库尔贝克-莱布勒散度
其本身可以用作差异的度量。当概率密度之间至少有一些重叠时,这种方法很有效。

一个例子是两个分布之间的差异可以通过交叉熵度量成功评估的情况。图片由作者提供。
但是,如果分布彼此相差太远,这些措施就会失效。例如,下图中的情况看起来与交叉熵目标函数相同,不会产生有意义的量化。

交叉熵没有帮助的例子。图片由作者提供。
然而最重要的是,目标函数的优化是基于梯度技术的,并且在分布相距太远的情况下,梯度将全部消失或不确定。我们的交叉熵目标函数不仅不会告诉我们有多远,它甚至不会指出该往哪个方向走!
出于这个原因,参考文献[2]的作者提出了一个不同的测量方法——wasser stein 距离。用文字表达,它被定义为“将分布 q(x) 转化为p(x)】所需的最小功”。也就是从字面上来说,如果我们的 q(x) 和 p(x) 是一堆堆的泥土,那么要把 q(x) 运到 p(x) 所在的地方,从 q(x) 中造出和 p(x) 一模一样的一堆,需要多少工作量。听起来很有趣,但是如果我们暂时忘记了用数学方法表达它的所有麻烦,我们会意识到这从概念上解决了我们的问题,因为它考虑了桩的形状和它们之间的距离的差异。
那么,我们如何将过程定义转换成数学表达式呢?不幸的是,答案并不简单。我可以试着在这里画一个草图,但是需要几个大的帖子来详细说明。一个关于这个主题的写得很好的博客可以在[3]中找到。首先,为了将我们对 Wasserstein 距离的单词定义 W(p,q) 转换成类似数学的东西,我们可以写
这里所说的是我们在所有运输计划γ中寻找一个最小值( inf )
运输所需的工作量。这里 x 是 p(x) 的支撑,即 p 不为零的所有数值,或者说 p 污物堆的位置。而 y 是 q(y) 的支撑,即 q 不为零的数值,或者是 q 污物堆的位置。而功的定义是一大块质量(密度γ)乘以它需要运输的距离, ||x-y|| 。这仍然不是很有帮助。但是我们现在可以看到,这是一个优化问题。一些人研究了类似的问题,并表明有一个等价的公式(Kantorovich-Rubinstein 对偶)允许重写我们的定义为
也就是说,我们现在正在寻找这个表达式的最大值( sup ),该最大值在所有增长受 1 约束(Lipschitz 约束)的函数上。这里跳过了几个步骤。它们可以在[3]和其中的参考文献中找到。我强烈推荐阅读关于 Wasserstein 距离的推导和有用的性质,然而,我们将在这里规定它们并继续前进。
这已经是我们可以利用的东西了。我们可以说 f(x) 是我们的鉴别器函数,并以最大化 W(p,q) 的最后一个表达式的方式训练模型。我们唯一要做的就是找出如何让函数 f(x) 满足李普希茨约束。参考文献[4]的作者建议施加梯度约束,迫使其接近 1。这可以通过给 W(p,q) 增加一个惩罚项来实现
其中在 x ̂ 上评估梯度,沿着 p 和 q 之间的直线采样的点的子集。好了,是时候看看实际情况了。
模型
首先,让我们看看我在下面的代码中对 WGAN 的简单实现。
它是一个扩展 keras 模型的类,将生成器和鉴别器成员也定义为 keras 模型。生成器和鉴别器的实际结构必须在 WGAN 模型类之外构建。相反,该类实现了培训方法。对发生器和鉴别器的训练是分开进行的。该模型为每一个定义了一个步骤,并且通常,对于生成器训练中的一个步骤,在鉴别器训练中有多个步骤。生成器训练步骤简单地最大化生成的假对象的分数,或者更确切地说,最小化分数的负值。鉴别器步骤,与我们的 Wasserstein 距离策略一致,最大化真实和虚假对象得分之间的差异,或者更确切地说,最小化差异的负值,记住梯度惩罚。
下面是我在这篇文章中提供的结果中使用的生成器和鉴别器函数的详细信息:
它们都使用所谓的残差块,该残差块鼓励模型将其注意力集中在拟合上一步不太拟合的东西上,并促进稳健收敛:
我想指出的是,就本文的目的而言,我们不需要完美的模型,我们只需要一个或多或少有效的模型。我们稍后将检查它是如何工作的,但现在我们将看看训练集。
培训对象
对于训练对象,我创建了两类图像。图像是 3 通道 64x64 像素。下面是生成它们的代码:
这是两种不同的形状——正方形和圆形——每一种都有不同的颜色,如下图所示:

用于训练的两类物体。作者图片
对于第一个通道,归一化颜色定义为 0 到 1 之间的实数,其他两个通道的偏移量分别为 0.1 和 0.2。偏移的符号可以改变,正方形的符号为“+”,圆形的符号为“-”。颜色产生的方式没有特别的意义。我只是想有一个容易改变和记录的参数集。这样,颜色被记录为一对(基色,偏移的符号),正如它在图片上所标记的那样。偏移量的值不会改变。此外,预定义方差的随机正态噪声被添加到每个通道。噪声的标准差是图像标签中的最后一个数字。
训练集包含每个类的 300 个副本。由于随机噪声,同一类的副本是不相同的。注意,噪声的添加并不作为某种正则化。相反,它意味着代表我们的数据的一个真正的连续特征。因此,我们有三种不同类型的特征:形状是绝对的;颜色是半分类的,因为在数字图像中,它有许多级别,但它们仍然是离散的(3 个通道乘以每个通道中的 256 个级别);噪声是连续的,正如其方差所定义的那样。
培训
我以 8 个为一批,最多 200 个时期来训练模型。这次运行我将用于基准测试结果。下图显示了训练迭代中鉴别器目标函数值。

200 个历元训练期间鉴别器损失函数的轨迹图。图片由作者提供。
这就是我们的瓦瑟斯坦距离,它正朝着正确的方向前进。在最后 1000 次迭代中有一个明显的跳跃。我不知道为什么收敛不是更单调,但甘是棘手的,我们的训练集有点太人工。总的来说,我们对这次培训相当满意。
为了进一步检查,我在这里画了梯度罚项。它不完全是零,但从上面来看,它是有界的,并且是向下的。一切又好了。

200 个历元训练期间梯度罚项的轨迹图。图片由作者提供。
现在,我们的模型已经训练到一个合理的程度,我们将测试它。
生成对象
在 GAN 模型被训练之后,人们通常丢弃它的鉴别器部分,并使用生成器来产生具有期望属性的新数据。我们也将这样做,但主要是作为另一种检查,证明训练是明智的。以下是我们的训练模型生成的图像示例。它们大小一样,都是 64x64,只是变小了。

模型经过 200 个历元训练后生成的对象。作者图片
可以看出这个模型已经掌握了窍门——至少它没有生成猫的图像!它把颜色弄对了。这些形状虽然可以辨认,但有时会混在一起。总的来说,生成的数据质量有改进的潜力,但模型还在路上。让我们看看这对我们的目的是否足够。
评分对象
现在我们开始着手我们所追求的——给模型从未见过的物体打分。我的目的一直是想看看训练过的鉴别器分数是否有什么模式。他们能告诉我们模型从底层数据中学到了什么吗?
以下是鉴别器的测试集:

用于鉴别器测试的 10 类物体。图片由作者提供。
我在形状和颜色上添加了更多的变化,但除此之外,这些图像是以相同的模式和相同的大小生成的,64x64。新形状是一个三角形,两种新颜色(0.85,+1)和(0.7,-1)是通过切换偏移符号从旧颜色(0.85,-1)和(0.7,+1)创建的。总的来说,测试集中有 10 类对象,就我们的对象特征(形状、颜色和噪声)而言,其中两类与训练集中的相同。每个类在测试集中有 40 个重复。
我将所有测试图像通过训练有素的鉴别器,并收集分数。下表总结了结果,并为下面的分数直方图提供了图例。用红色突出显示的是用于训练的对象,其余的对象是模型以前没有见过的。

测试集中 10 个类的分数摘要。图例颜色对应于下面的直方图图像。图片由作者提供。

按对象类着色的测试集的鉴别器分数直方图。图片由作者提供。
令人惊讶的是,几乎所有被测试的类之间都有很好的分离。该模型已经认识到它们属于分布的不同空间中的某个地方。
正如我所料,形状和颜色是模型最容易识别的特征。那里没有混乱。新形状(三角形)的分数与其他形状(中间的两个绿色条)的分数非常接近。即使在三角形中,这个模型也能够根据颜色来区分物体。
噪音水平似乎是一个不太明显的特征。相同颜色的两个方块被噪声很好地分开(见直方图上最左边的两组)。然而,颜色相同但噪声水平不同的两个圆类的得分非常接近。尽管如此,如果我们放大分数分布,如下图所示,我们可以看到微小但清晰的分离。如果我们训练模型的时间更长,差距可能会变大。

“circle (0.85,-1) 0.03”(亮黄色)和“circle (0.85 -1) 0.01”(暗金色)这两个分数相近的类别的分数放大视图。图片由作者提供。
到目前为止,唯一未解决的案例是两个颜色相同但噪声水平不同的三角形。
因此,如果我们愿意,我们可以使用这样训练的鉴别器作为一个很好的分类器。我想提醒你,培训期间没有提供标签。该模型学会了自己分离两个训练类。此外,它还学会了正确区分它从未见过的类别!
使用部分训练的模型评分
在那一点上,我问自己一个问题——分数的分离在训练过程中多早开始?它与生成图像的外观质量有什么关系?为了回答这个问题,我进行了 10、20 和 100 个时期的次优水平的训练。在每种情况下,训练都是从头开始的,而不是继续以前的训练。这导致分数的绝对值处于不同的范围,但这也是我想测试的一个方面。显而易见的预期结论是,分数的绝对值没有任何意义。
下图显示了模型能够在左侧的每个训练级别生成的图像,以及由测试图像类使用与上述相同的配色方案着色的鉴别器得分直方图。

左边是生成的对象,右边是对应于不同训练程度的测试对象的分数直方图。图片由作者提供。
为了让他们有一点可比性,我用“square(0.85,+1)0.03”培训班作为参考,从所有成绩中减去这个班的平均分。即“平方(0.85,+1)0.03”类的分数始终以 0 为中心。此外,如果你还没有注意到,相同形状的分数通过配色方案统一起来,以方便视觉比较。也就是说,直方图上的所有圆圈分数都是黄色的,所有正方形都是略带紫色的,三角形是绿色的。而且,分数颜色的强度与对象组的噪声水平有关。越强烈、越暗的阴影表示噪声级别越低,为 0.01。具有讽刺意味的是,物体的实际颜色并没有颜色编码。
从这张图中可以明显看出,分数聚类在训练过程中很早就开始了。在仅仅 10 个时期之后,训练显然不足以产生任何像样的图像。然而,该模型已经对形状进行了不同的评分。20 个时期后,分数聚类变得更加精细,而数据生成仍然不太顺利。值得指出的是,新的三角形形状始终在其他已经看到的形状之外。
当然,所有这些都是从人类的角度进行的评估。但是,本着我们这个时代的精神,我们应该想知道机器在想什么。让我们在下一节中了解一下。
使用 WGAN 分数进行嵌入
使用经过适当训练的 GAN 模型的鉴别器部分作为分类器已经是一种值得尊敬的策略。然而,我们可以变得更有创造性,将这些分数输入到另一个模型中,将它们与额外的元数据相结合,并执行一些复杂的分析。这种情况在机器学习中被称为嵌入。更准确地说,嵌入意味着将一些不同的数据格式集,或者将高维数据简化为计算上更友好的数据类型。一个例子是将代表单词的字符串转换成实数。
我们已经对我们的形状图像进行了这样的处理,并为每个图像生成了一个数字,但是让我们更加批判性地探索这些数字有多适合嵌入。什么构成了一个好的嵌入?我不知道这个问题的普遍公认的答案,所以我将陈述我的观点。数字的关键特征是可比性,随之而来的是距离的概念。也就是说,如果你有两个数字,你可以判断它们是相同还是不同,在后一种情况下,相差多少。我需要在这里向数学家们道歉,他们在那个时候,会开始谈论域和流形以及其中定义的测度,因为他们使用了这样一种普通的语言。当我们把一个看似不可比较的对象转换成数字时,我们自动获得了这种可比性的额外好处。当我们进行进一步分析时,这可能是一把双刃剑。一方面,机器可能会发现一些我们漏掉的模式,另一方面,它可以给没有任何意义的东西赋予意义。单词嵌入的著名例子是一个很好的嵌入例子,你可以用嵌入的单词进行算术运算,得到直观正确的结果,比如国王-男人+女人=王后。然而,我怀疑,由于人们只使用这个特定的例子,在自然语言处理(NLP)的相同领域中有许多其他情况,其中反直觉的意义被分配或者任何直觉的意义被丢失。总而言之,在我看来,一个好的嵌入是 a)以某种逻辑方式聚集对象;b)定义对象之间的距离,从而允许解释;c)坚持不懈。
我们的分数是多少?我们对项目 a)有利,因为我们清楚地观察到一些有意义的分组。让我们来看看这个模型给出的几个定量比较。根据分数的不同,机器将“圆(0.85,+1)0.03 ”和“方(0.7,+1)0.03 ”物体相对于“三角形(0.7,-1)0.01 ”排列在这些位置。

不同测试类别的相对分数。图片由作者提供。
如果让我猜的话,我会说这里的决定因素可能是面积,这是形状属性量化的一部分。在另一个例子中,不同颜色和噪声的圆圈被放置在以下距离:

不同测试类别的相对分数。图片由作者提供。
这可以很好地用颜色作为主要考虑因素来解释。还有另一个有趣的观察:分数分布的宽度似乎与图像组噪声水平有关。这可以被解释为颜色和噪声特征之间的相互作用的度量。无论如何,b)项也可能得到满足。但是,如果部分训练模型的结果有任何指示的话,c)项是我们方法中最弱的部分。每次重新训练模型,班级的相对分数都会不一样。
我在这篇文章的最后一部分讨论的大部分是定性的思考,尽管它们与定量的估计有关。我没有对嵌入应用程序进行全面的研究。到目前为止,对于我们的鉴别器是否产生适合嵌入的分数这个问题的答案可能是“不”,无论如何不是以其当前的形式。但是我有一个想法,你必须等待另一个帖子:-)
结论
总之,我们学习了 GANs 的基本原理。我们还回顾了使用 Wasserstein 距离作为训练目标函数的论点。所有这些都被用来说明 GANs 是如何在玩具数据集上工作的。但是这篇文章的主要目的是引起人们对 GANs 中未被重视的部分——歧视者的注意。我希望我能够让读者相信它正在做一些了不起的工作。我真的认为是时候让 GANs 在机器学习领域扮演更重要的角色了。
参考
[1] I. J. Goodfellow,J. Pouget-Abadie,M. Mirza,B. Xu,D. Warde-Farley,S. Ozair,a .,Y. Bengio (2014),生成性对抗网络,arXiv:1406.2661
[2] M. Arjovsky,S. Chintala 和 L. Bottou, Wasserstein GAN (2017),arXiv:1701.07875
[3] V. Herrmann, Wasserstein GAN 和 Kantorovich-Rubinstein 二重性
[4] I. Gulrajani,F. Ahmed,M. Arjovsky,V. Dumoulin,a .库维尔,wasser stein GNAs 的改进培训 (2017) arXiv:1704.00028
基于模式排序的无监督关键词抽取
原文:https://towardsdatascience.com/unsupervised-keyphrase-extraction-with-patternrank-28ec3ca737f0
使用预训练的变压器语言模型和词性进行最先进的关键短语提取

图片作者。
本帖基于我们的论文“pattern rank:利用预训练的语言模型和词性进行无监督的关键短语提取(2022)”被 KDIR22 接受。您可以在那里阅读更多关于我们方法的详细信息。
📑为了快速获得文本内容的概述,我们可以使用简明反映其语义上下文的关键短语。关键短语描述了一篇文章最重要的方面。与简单的关键字不同,关键短语不仅仅由单个单词组成,而是由几个复合词组成。如《足球》vs《青少年足球训练》。关键短语比简单的关键字提供了更准确的描述,因此通常是首选的,不依赖于标记的数据选择。
在这篇文章中,我们介绍了 PatternRank ,它利用预先训练的语言模型和词性从单个文档中进行无监督的关键短语提取。我们的方法不依赖于标记数据,因此可以很容易地用于各种不同的领域。我们的实验表明,PatternRank 比以前的方法具有更高的精度、召回率和 F1 值🏆。此外,我们还展示了keyphrasevectorsPython 包🐍,这允许容易地修改候选关键短语选择的词性模式,从而使我们的方法适用于任何领域。
PatternRank 是如何工作的?
下图说明了 PatternRank 的一般关键短语提取过程👇。

无监督关键短语提取的模式排序方法。单个文本文档被用作初始过滤步骤的输入,在该步骤中,选择与定义的词性模式相匹配的候选关键短语。随后,基于候选关键短语与输入文本文档的语义相似性,通过预先训练的语言模型对候选关键短语进行排序。最后,提取前 N 个关键短语作为输入文本文档的简明反映。来源:绍普夫等人
为了从文本中提取关键短语, PatternRank 执行以下步骤:
- 输入由正在进行单词标记的单个文本文档组成。
- 然后用词性标签对单词标记进行标记。
- 其标签与先前定义的词性模式相匹配的记号被选为候选关键短语。
- 然后,预训练的语言模型将整个文本文档以及所有候选关键短语作为语义向量表示来嵌入。
- 随后,计算文档表示和候选关键短语表示之间的余弦相似度,并基于所计算的相似度得分以降序排列候选关键短语。
- 最后,提取最能代表输入文档的前 N 个排序的关键短语。
在我们的实验中,我们使用预先训练的 all-mpnet-base-v2 语言模型。这是一个 SBERT 模型,已经被证明可以为语义相似性任务产生最先进的文本表示。对于这篇博文的范围,我们只使用简单的名词短语作为词性模式。尽管我们在的论文中表明,更复杂的词性模式可以带来更好的结果,但在模式排序中使用名词短语是一种简单而有效的方法,可以提取任何领域中有意义的关键短语🏅。
名词通常是指一件事或一个人。形容词是描述名词的词。名词短语是围绕一个名词构建的简单短语。它由零个或多个形容词后跟一个或多个名词组成。例如:一棵大树,一些五颜六色的糖果,巨大的皇家城堡。
模式排序与以前方法的比较
先前的关键短语提取方法
大多数流行的无监督关键短语提取方法可以被描述为基于统计、基于图或基于嵌入的方法,而 Tf-Idf 是用于评估的常见基线。在这篇文章中,我们针对三种非常流行的关键短语提取方法来评估 PatternRank。如果您对更多关键短语提取方法的详细比较感兴趣,请查看 Papagiannopoulou 和 Tsoumakas 的论文(2019)。
👉YAKE 是一种快速、轻量级的方法,用于基于统计特征从单个文档中无监督地提取关键短语。
👉 SingleRank 对单词共现图应用排序算法,从单个文档中进行无监督的关键短语提取。
👉 KeyBERT 使用类似于 PatternRank 的预训练语言模型对候选关键短语进行排名。然而,KeyBERT 使用简单的单词 n-grams 作为候选关键短语,而不是像我们的 PatternRank 方法那样,使用匹配特定词性模式的单词标记。
单词 n 元语法范围让用户决定应该从给定文本中提取的连续单词序列的长度。假设我们定义了一个
*word n-gram range = (1,3)*。然后,我们将选择从文本中提取一元词(只有一个单词)、二元词(两个连续单词的组合)和三元词(三个连续单词的组合)。将单词 n-gram range 应用到*"an apple a day keeps the doctor away"*将得到*["an", "apple", "a","day", "keeps", "the", "doctor", "away", "an apple", "apple a", "a day", "day keeps", "keeps the", "the doctor", "doctor away", "an apple", "apple a day", "a day keeps", "day keeps the", "keeps the doctor", "the doctor away"]*。- 德维什帕马尔
为了评估,我们比较了 YAKE、SingleRank、KeyBERT 和 PatternRank 的性能。对于 KeyBERT 和 PatternRank,我们使用相同的预训练的 all-mpnet-base-v2 语言模型。
数据
💾我们在 Inspec 数据集上评估关键短语提取方法。它包括从 1998 年到 2002 年间的科学期刊文章中收集的 2000 篇英语计算机科学摘要。每个摘要都分配了两种不同类型的关键短语。首先,出现在 Inspec 数据集的词库中但不一定出现在摘要中的受控和手动分配的关键短语。第二,不受控制的关键短语,它们是由专业索引器自由分配的,并且不限于同义词库或摘要。在我们的实验中,我们认为这两种关键短语的联合就是黄金关键短语。
韵律学
我们基于精确匹配方法进行评估,这意味着真正的肯定是提取的关键短语与黄金关键短语之一具有精确的字符串匹配。我们报告 Precision@N 、 Recall@N 和 F1@N 得分,分别使用前 N 个提取的关键短语( N=5,10 )。
结果
我们的评估结果如下图所示📊。

在 Inspec 数据集上比较 KeyBERT、YAKE、SingleRank 和 PatternRank 关键短语提取结果。
结果显示,在所有基准测试中,PatternRank 优于所有其他方法。KeyBERT 使用与 PatternRank 相同的预训练语言模型对候选关键短语进行排序,但使用简单的 n 元语法来选择候选关键短语,而不是词性模式(在本例中为名词短语)。因此,KeyBERT 在所有方法中表现最差。正如所料,YAKE 是最快的关键短语提取方法,因为它是基于统计特征的轻量级方法。然而,提取的关键短语不是非常准确,并且与 PatternRank 相比,YAKE 在所有评估中的表现明显更差。SingleRank 是与 PatternRank 相比唯一能取得竞争结果的方法。然而,在所有评估中,它的表现始终比 PatternRank 差几个百分点。因此,我们得出结论,PatternRank 实现了最先进的关键短语抽取结果🏆。
如何使用 PatternRank?
我们开发了keyphrasvectoriesPython 包🐍这使得模式等级易于使用。将关键短语分解器与 KeyBERT 一起用于关键短语提取产生了模式排序方法。其思想是使用 KeyBERT 的实现,通过预训练的语言模型对关键短语进行排序,并利用来自关键短语分析器的词性模式选择候选关键短语,从而产生模式排序方法。如何用 Python 实现这种方法在这篇博客文章中有详细的解释💡。
摘要
PatternRank 是最近开发的一种方法,可用于从文本文档中提取最先进的关键短语。评估显示,在所有基准测试中,PatternRank 在精确度、召回率和 F1 值方面表现最佳。此外,我们还展示了keyphrasevectorsPython 包🐍,这使得 PatternRank 易于使用和定制。
也非常感谢马腾·格罗腾多斯特,他在我们编写keyphrasevectors包时给了我们输入和灵感。
来源
https://arxiv.org/abs/2210.05245 https://github.com/TimSchopf/KeyphraseVectorizers
无监督学习算法备忘单
原文:https://towardsdatascience.com/unsupervised-learning-algorithms-cheat-sheet-d391a39de44a
你应该知道的所有无监督机器学习算法的完整备忘单

无人监督的学习任务。作者图片
本文提供了不同的无监督学习机器学习概念和算法的备忘单。这不是一个教程,但它可以帮助你更好地理解机器学习的结构,或者刷新你的记忆。
要了解更多关于特定算法的信息,只需谷歌一下或者在 sklearn 文档 中查找。
接下来,我们将探讨以下任务:
- 降维;
- 异常检测;
- 聚类;
- 以及其他无监督学习算法(密度估计和关联规则学习
由于这些主题非常广泛,维度减少、异常检测、和聚类部分是独立的文章。我已经研究它们很长时间了,但我仍然想把它们放在一个地方。
如果我们把这篇文章看作是这三者的串联,它是相当浩繁的,所以我不建议你一次读完。将这篇文章添加到阅读列表中以便稍后回来,通过 GitLab 阅读章节或下载这篇文章的 pdf 版本并打印出来(可在同一位置获得)。
https://gitlab.com/Winston-90/unsupervised_algorithms
介绍
无监督学习是一种机器学习技术,开发者不需要监督模型。相反,这种类型的学习允许模型在没有任何监督的情况下独立工作,以发现隐藏的模式和以前未检测到的信息。它主要处理未标记数据,而监督学习,正如我们所记得的,处理标记数据。

监督与非监督学习。公共领域
三个最流行的无监督学习任务是:
- 降维 —减少数据集中输入特征数量的任务。
- 异常检测——检测与标准非常不同的实例的任务,以及
- 集群 —将相似的实例分组到集群中的任务。
这三个任务中的每一个以及解决它们的算法将在后面相应的章节中详细讨论。然而,请注意,其他无监督学习任务部分列出了其他不太受欢迎的任务,这些任务也可以归因于无监督学习。
降维
下面提到了降维的算法:
- 主成分分析;
- 流形学习 — LLE 、 Isomap 、t-SNE;
- 自动编码器和其他。
异常检测
对于异常检测,提到了以下算法:
- 隔离林;
- 局部离群因子;
- 最小协方差行列式等来自降维或监督学习的算法。
使聚集
下面提到了用于聚类的算法:
- K-表示;
- 层次聚类和谱聚类;
- DBSCAN 和光学;
- 亲和繁殖;
- 均值偏移和桦木;
- 高斯混合模型。
其他无监督学习任务
虽然降维、异常检测和聚类是主要的和最流行的无监督学习任务,但还有其他任务。
由于定义是模糊的,任何处理未标记数据集的算法都可以被视为解决一些无监督的学习任务(例如计算平均值或应用学生的 t 检验)。然而,研究人员经常在其他任务中识别另外两个任务:密度估计和关联规则学习。
密度估计
我已经在异常检测部分简要提到了密度估计。
密度估计是估计数据点分布密度的任务。更正式地说,它估计由给定数据集生成的随机过程的概率密度函数 (PDF)。这项任务在历史上来自统计学,当时需要估计一些随机变量的 PDF,并且可以使用统计方法来解决。
在现代,它主要用于数据分析,并作为异常检测的辅助工具——位于低密度区域的数据点更有可能是异常或异常值。现在通常用基于密度的聚类算法如 DBSCAN 或 Mean Shift 来解决,用期望最大化算法到高斯混合模型中。
关联规则学习
关联规则学习(也称为关联规则或简称为关联)是另一种无监督学习任务。它最常用于商业分析,以实现利润最大化。
它旨在检测数据集中变量之间不明显的关系,因此也可以被认为是一个数据分析工具。有许多复杂的算法来解决它,但最流行的是:
- 先验 —基于广度优先搜索;
- Eclat ( 等价类转换 ) —基于深度优先搜索;和
- FP-Growth —设计用于检测数据中频繁出现的模式。
这种任务的一个常见例子是产品放置。例如,知道人们经常在超市一起购买洋葱和土豆,将它们并排放置以增加销量是有意义的。因此,关联规则被用于促销定价、市场营销、连续生产等。
结论
在本文中,我试图描述所有主要的无监督学习任务和算法,并向您展示无监督学习的全貌。
希望这些描述和建议能够帮助到你,激励你去学习更多,更深入地学习机器。
您可能还对以下内容感兴趣:
感谢您的阅读!
- 我希望这些材料对你有用。在 Medium 上关注我,获取更多这样的文章。
- 如果您有任何问题或意见,我将很高兴得到任何反馈。在评论里问我,或者通过 LinkedIn 或者 Twitter 联系。
- 为了支持我作为一个作家,并获得数以千计的其他媒体文章,使用我的推荐链接获得媒体会员资格(不收取额外费用)。
参考
[1] Aurélien Géron,使用 Scikit-Learn、Keras 和 TensorFlow 进行机器学习,第二版 (2019),奥赖利媒体公司
k-均值聚类从零开始
K-means:聚类数据的最佳 ML 算法

动机
机器学习的主要思想是创建一个通用模型,该模型可以根据以前的数据提供理性的决策,而无需显式编程。机器学习问题可以是有监督的,也可以是无监督的。本文重点介绍一种称为‘K-means’聚类的无监督机器学习算法。
当无监督机器学习这个术语出现时,我通常会在进行机器学习课程时向我的学生提供示例。假设给了你一些*duck, snake, cow, dove, goat, hen, ship, crocodile, etc* 的玩具。不幸的是,你不知道这些玩具的名字。如果让你把动物分成不同的种类,你会怎么做?如果你理性地思考,基于其外观的可能集群将是集群 1:duck, hen, dove;集群二:goat, cow, ship;和簇 3 : crocodiles, snake。虽然确切的名字不为人知,但你可以将这些动物归类。因此,基于相似特征的聚类被称为无监督机器学习算法。
对于基于相似性的数据分组,无监督机器学习是最适合的。
目录
[**Overview of Unsupervised Learning**](#b7ae)[**Coordinate Distance Calculation for K-means**](#85d1)[**Overview of K-means**](#b8ad)[**Optimum Number of Cluster Selection**](#f558)[**Why K-means?**](#cbb9)[**Challenges of K-means**](#c110)[**Step-by-step Hands-on Implementation**](#f9b7)
无监督学习概述
无监督学习,也称为无监督机器学习,使用机器学习算法来分析和聚类未标记的数据集。这些算法发现隐藏的模式或数据分组,无需人工干预[1]。
假设你是一名理学硕士,你有一个论文导师。你的导师会指导你完成论文,因为他知道如何进行研究和最终目标。有监督的机器学习算法以同样的方式工作。每个输入都有一个目标值,算法试图从标记的数据中优化其参数,以预测新的实例。无监督学习方法正好是监督学习的逆过程。这些方法处理未标记的数据。无监督学习的主要目的是发现潜在的隐藏模式和见解[2]。这些算法通常用于解决聚类问题。
无监督机器学习算法有两种类型。它们如下—

作者图片
*The article only focuses on the clustering algorithm (K-means).* 聚类是将具有相似特征的数据点进行分组。 有时候无监督学习算法的作用变得非常重要。
已经给出了一些优点[2] —
- 无监督学习有助于从数据中找到有价值的见解。
- 无监督学习与人类非常相似。我们通过自己的经历来学习思考,这让它更接近真实的 AI。
- 无监督学习对未标记和未分类的数据起作用,这使得无监督学习变得更加重要。
- 在现实世界中,我们并不总是有相应输出的输入数据,因此要解决这种情况,我们需要无监督学习。
K-means 的坐标距离计算
- 欧几里德距离
欧几里德距离是计算两个坐标点之间距离的最著名的方法。它计算对象对的坐标之间的平方差的平方根[4]。它是两个数据点之间的直线距离。

欧几里德距离(图片由作者提供)
欧几里得距离可以用下面的等式来测量。公式用 ***x*** and ***y***表示两点。***K*** 是维度的数量(在数据科学中,每个数据集的特征被认为是一个维度)。

- 曼哈顿距离
曼哈顿距离计算对象对的坐标之间的绝对差异[4]。

曼哈顿距离(图片作者提供)
曼哈顿距离是坐标绝对距离的总和。可以描述如下。

这里,x **and** y是两个坐标点,‘‘k’’是尺寸/特征的数量。
- 切比雪夫距离
切比雪夫距离也称为最大值距离,计算为一对对象的坐标之间差异的绝对大小[4]。这是最大坐标值。

切比雪夫距离(图片作者)
**x and y** 代表两个坐标点。他们的切比雪夫距离可以通过找到坐标中的最大距离来计算。**k**表示特征的数量。

假设我们有两点,**x(1, 3)** and **y(5,10)**,。T3。所以,max (4, 7) is 7。这意味着切比雪夫的距离是 7。
- 闵可夫斯基距离
闵可夫斯基距离是一个统一的距离公式。有了这个距离公式,我们只要改变一个参数就可以得到以上所有的距离。

闵可夫斯基距离(图片作者提供)
该距离可以用下面的公式计算。两点之间的距离,**x and y**、、、 **k** 为特征的个数。**P** 是一个唯一的参数,它转换方程来计算不同的距离。

注意,当 **p=2**,时,距离变成欧几里德距离。当**p=1**时,变为城市街区距离。切比雪夫距离是闵可夫斯基距离的变体,其中 **p=∞** (取一个极限)【4】。
关于描述距离,研究论文[4]和文章[5]对我帮助很大。】
The research finding shows that Euclidean distance is the best method for calculating the distances among the data points for the K-means clustering algorithm.
K-means 聚类算法概述
K-means 聚类是一种流行的无监督聚类机器学习算法。让我们解释一下它是如何工作的。
第一步: 最开始,我们需要选择 K 的值。 K 表示您想要的集群数量。

样本数据点(图片由作者提供)
第二步: 为每个簇随机选择质心。

随机选择的质心(图片由作者提供)
假设对于以上数据点;我们想要创建 3 个集群。因此, K=3 并且正方形颜色的数据点是 3 个随机选择的质心。
步骤 3: 计算数据点到质心的距离,并根据最小距离将数据点分配到聚类中。

每个质心下分配的聚类(图片由作者提供)
从上面的图像中,我们清楚地看到,每个质心都被分配了一些数据点,这些数据点基于用不同颜色表示的最小距离。
第四步: 计算每个聚类的平均值,将新的质心重新集中到平均值。

重新排列聚类(图片由作者提供)
该图像描绘了根据质心的平均值将质心居中到新位置。
第五步: 重复第三步和第四步,直到质心收敛。

重新调整集群(图片由作者提供)
重复步骤 3 和步骤 4 之后,我们得到了上面的集群。对于下一次迭代,我们得到以下集群。

迭代后的新聚类(图片由作者提供)
下一次迭代做什么?让我们看看。

汇聚成最终的集群(图片由作者提供)
最后两个簇和质心是相同的。我们可以说,质心收敛,我们达到了我们的最终目标。
K-均值的最佳聚类数
K-means 聚类算法的一个大问题是如何选择最佳的聚类数目。如果不知道最佳集群数,就要应用 肘法 来找出。为了保持文章的精确和适度,我简单说明一下方法。

肘法图(图片由作者提供)[6]
应用肘方法后,我们会发现一个如上图所示的线图。从图中,我们需要找出肘点和相应的集群数。并且它将被认为是最佳的聚类数。对于上图,最佳聚类数是 4。肘法的详细解释可用 这里的 。
为什么是 K-means?
K-means 是最流行的聚类算法。这是一种简单的聚类算法,适用于大型数据集。相比之下,它比其他聚类算法运行得更快。它总是保证收敛到最终的聚类,并容易适应新的数据点[3]。
K-means 的挑战
在上一节中,我们看到初始聚类质心在 K-means 聚类算法中是随机分配的,这导致了随机的迭代和执行时间。因此,初始质心点的选择是算法中的一个关键问题。你可以阅读下面的文章,它代表了一种系统地选择初始质心的技术。
该算法对复杂的分布式数据不太适用。
逐步动手实施
本节将从头开始展示 K-means 聚类算法的实际实现。对于任何机器学习模型,我们首先需要加载数据集。出于演示的目的,我使用了[**mall_customer**](https://www.kaggle.com/datasets/shwetabh123/mall-customers?resource=download) 数据集。这是一个受欢迎的数据集。
[N.B. — *have used the* [*mall_customer*](https://www.kaggle.com/datasets/shwetabh123/mall-customers?resource=download) *dataset, which is a public domain dataset under the “*[*CC0: Public Domain*](https://creativecommons.org/publicdomain/zero/1.0/)*” license.*]
-
导入必要的库
-
加载数据集和一些预处理
数据集的信息
Customer ID和Gender 不是那么重要,所以我已经丢弃了这些列。
将dataframe转换成一个NumPy数组。
提取列号和行号。
-
选择聚类数
-
随机选择质心
定义特征尺寸的空数组。
为 3 个簇随机选择 3 个质心。
- 下面的代码实现了 K-means 聚类概述章节 中提到的
***step-3, step-4 and step-5***
我在上面的代码中添加了一些命令,以便于理解功能。
- 可视化集群
每个聚类在 3D 空间中用不同的颜色表示。
结论
K-means 聚类算法简单易用。在实现算法之前,我们需要小心算法的用例以及底层工作原理。对于非常复杂的分布式数据,该算法效果并不好。
I am writing a series of articles on machine learning. The[**article describes KNN**](https://medium.com/towards-data-science/knn-algorithm-from-scratch-37febe0c15b3) algorithm implementation from scratch.
最后,如果你觉得这篇文章有帮助,别忘了给我
[**follow**](https://medium.com/@mzh706)。也可以用我的[**referral link**](https://mzh706.medium.com/membership)加入 medium。通过电子邮件获取我所有的文章更新[**subscribe**](https://mzh706.medium.com/subscribe)。
参考
- 什么是无监督学习?IBM
- https://www.javatpoint.com/unsupervised-machine-learning
- k-Means 优缺点|机器学习|谷歌开发者
- 辛格、亚达夫和拉纳(2013 年)。具有三种不同距离度量的 K-means。《国际计算机应用杂志》, 67 (10)。
- https://towards data science . com/9-distance-measures-in-data-science-918109d 069 fa
- Zubair,m .、Iqbal,M.A .、Shil,A. 等一种面向高效数据驱动建模的改进 K 均值聚类算法。安。数据。Sci。* (2022)。【https://doi.org/10.1007/s40745-022-00428-2 *
其他相关文章。你可以看看。
* *
无监督学习:为聚类点建立分数度量
测量相似聚类点内的差异

一簇褐色的小蘑菇。纳蕾塔·马丁在 Unsplash 上的照片
聚类是一种无监督的机器学习技术,用于发现数据中的有趣模式。例如,根据相似客户的行为对其进行分组,构建垃圾邮件过滤器,识别欺诈或犯罪活动。
在群集中,相似的项目(或数据点)被分组在一起。然而,我们不仅希望将相似的项目分组在一起,我们还希望衡量它们的相似性或差异性。为了解决这个问题,我们可以很容易地创建一个评分算法。
在这个例子中,我使用了一个简单的 k-means 聚类方法。你可以在这里阅读。我们使用sk learn . datasets . make _ blobs生成用于聚类的各向同性高斯 blob。
接下来,我们建立一个简单的 k-means 算法,包含 3 个聚类,并获得这些聚类的质心。
现在,为了对不同聚类中的每个点进行评分,我们可以估计它们离聚类中心有多近,并将其与聚类中最远的点进行比较。质心的中心表示聚类点的理想位置,而质心的最远点是聚类点的最差点。
在本例中,我们的数据集包含 2 列,因此我们可以轻松地测量它们的平方差之和。为了便于解释,这些距离可以转换成百分比。
这些测量不仅能给我们一个距离一个星团中心有多远的估计,还能给我们一个距离下一个星团有多远的估计。这对于像客户细分这样的问题特别有意思,在这种情况下,我们想测试每种营销方法如何影响客户。
我希望这对你有帮助。期待您的评论,同时您也可以在 twitter 、 Linkedin 和 medium 上关注我。
如果你喜欢这篇文章,你可以考虑请我喝☕️.咖啡
你也可以在这里查看我的文章“使用无监督机器学习建立客户群”。
Vielen Dank 😊
无监督学习:K-均值聚类
原文:https://towardsdatascience.com/unsupervised-learning-k-means-clustering-27416b95af27
最快最直观的无监督聚类算法。

群集图像-按作者
在本文中,我们将介绍 k-means 聚类算法。我们将首先开始研究算法是如何工作的,然后我们将使用 NumPy 实现它,最后,我们将研究使用 Scikit-learn 实现它。
简介
K-means 聚类是一种无监督算法,它将未标记的数据分组到不同的聚类中。标题中的 K 代表将要创建的集群数量。这是在模型训练之前应该知道的事情。例如,如果 K=4,那么将创建 4 个集群,如果 K=7,那么将创建 7 个集群。k-means 算法用于欺诈检测、错误检测和确认现实世界中的现有聚类。
该算法是基于质心的,这意味着每个数据点都被分配给质心最近的聚类。当我们使用欧几里德距离计算到质心的距离时,该算法可以用于任何数量的维度。下一节将详细介绍这一点。
k-means 算法的优点是易于实现,可以扩展到大型数据集,总是能够收敛,并且适合不同形状和大小的聚类。该模型的一些缺点是聚类的数目是手动选择的,聚类依赖于初始值,并且对异常值敏感。
k-均值步长
训练 K 均值模型的步骤可以分为 6 个步骤。
第一步:确定聚类数(K=?)
如果在模型训练之前就知道 K 是最好的,但是如果不知道,也有找到 K 的策略。最常见的是肘方法,它随着 K 的增加绘制平方距离的总和。通过观察该图,应该有一个点,在该点处,增加聚类的大小为误差函数提供了最小的增益。这就是所谓的弯头,应该选择它作为 k 的值。如下图所示。

弯头方法图形-作者
第二步:初始化集群质心
下一步是初始化 K 个质心作为每个簇的中心。最常见的初始化策略称为 Forgy 初始化。这是每个聚类的质心作为数据集中的随机数据点初始化的时候。这比随机初始化收敛得更快,因为聚类更可能出现在数据点附近。
像 k-means++初始化这样更复杂的策略旨在选择彼此尽可能远的初始点。这种策略已被证明是 k-means 算法的最佳初始化方法。你可以在这里看到 k-means++背后的数学。
步骤 3:将数据点分配给聚类
在初始化 K 个聚类之后,每个数据点被分配给一个聚类。这是通过迭代数据中的所有点并计算到每个质心的欧几里德距离来完成的。欧几里德距离公式适用于 n 维欧几里德空间中的任意两点。这意味着具有任意维数的数据点被分配给最近的聚类。

欧几里德距离公式—作者
步骤 4:更新群集质心
一旦每个数据点被分配到一个聚类,我们就可以更新每个聚类的质心。这是通过取聚类中每个数据点的平均值并将结果指定为聚类的新中心来实现的。
第五步:迭代更新
然后,使用新计算的质心,我们遍历所有的数据点,并将它们重新分配给聚类,并重新计算质心。重复进行此操作,直到质心值不再变化。
k-means 算法的美妙之处在于它保证收敛。这是一个祝福也是一个诅咒,因为模型可能会收敛到局部最小值而不是全局最小值。这个想法将在下一节中说明,在下一节中,我们使用 Numpy 实现该算法,然后在 Scikit-learn 中实现。
K-Means: Numpy
首先,我们将导入必要的 python 包,并使用 Scikit-learn 的 make_blob 函数创建一个二维数据集。对于本文,我们将生成分布在 4 个集群中的 300 个数据点。生成的数据如下所示。

数据示例—按作者
我们可以看到样本中有 4 个不同的聚类,每个聚类中的外围数据点之间有轻微的重叠。接下来,我们将启动两个哈希映射。一个将跟踪聚类质心,另一个将跟踪每个聚类中的数据点。
既然质心已经初始化,我们将定义另外三个函数。第一种方法接收两个数据点,并计算欧几里德距离。第二个函数更新每个聚类的质心,最后一个函数为每个聚类分配数据点。
最后三个函数通过提供一种方法来检查模型是否收敛,拥有一个主训练函数,并可视化模型结果,从而将所有这些放在一起。
因此,将上面的所有函数放在一起,我们可以训练模型,并在两行代码中可视化我们的结果。通过使用函数式编程实现算法,我们能够使代码可读,限制重复,并促进代码的改进。

k-均值结果—按作者
在上面的模型训练中,我们看到模型能够在 4 个时期后收敛,并正确识别我们生成的聚类。这很好,但是生成的簇的结果高度依赖于初始的质心。如果我们用不同的初始质心训练模型多次,我们可以看到模型如何在不同的训练周期收敛到不同的最小值。这就是 K-means++初始化有用的地方,因为它增加了收敛到全局最小值的可能性。

太好了!现在我们能够从头开始实现 k-means 算法,但是这在现实世界中可行吗?不完全是。在现实世界中,您更可能使用现有的模型库,如 Scikit——学习训练、测试和部署模型。
K-Means: Scikit-Learn
使用现有库的好处是它们被优化以减少训练时间,它们通常带有许多参数,并且它们需要更少的代码来实现。Scikit-learn 还包含许多其他的机器学习模型,并且使用一致的语法来访问不同的模型。
在下面的单元格中,我们实现了与上面相同的 k-means 聚类算法,除了默认情况下我们使用 k-means++初始化质心。所有这些都是在不到 20 行代码中完成的!

sci kit-学习结果—按作者
正如预期的那样,我们能够正确地识别 4 个集群。当 Scikit-learn 实现使用 kmeans++初始化起始质心时,该算法几乎在每次重新运行训练周期时都收敛到全局最小值。
最后的想法
K-means 是一种易于实现的无监督聚类算法,几乎不需要任何时间就可以进行训练。由于该模型通过最小化数据点与其相应聚类之间的距离之和来训练,因此它可与其他机器学习模型相关。
这篇文章的代码可以在这里找到。
资源
- k means 的利与弊——谷歌
- k 意味着初始化方法— Nitish Kumar Thakur
- Scikit-Learn Kmeans 文档
- 肘法—百科
- k 均值聚类—Java point
- StackEdit 降价编辑
无监督学习:K-均值聚类
原文:https://towardsdatascience.com/unsupervised-learning-k-means-clustering-6fd72393573c
机器学习理论
k-均值聚类直观解释

作者 Gif
K-均值聚类是一种迭代算法,它选择最小化类内方差的聚类中心。
介绍
在本文中,我想介绍一种最简单的数据聚类算法,k-means 聚类。这是一种经常在面试中出现的算法,用来测试你的基本面知识。读完这篇文章后,你会理解监督学习和非监督学习之间的区别,以及简单数据聚类模型背后的数学原理。
无监督学习
许多常见的机器学习问题都属于监督学习领域。任何与分类或回归有关的问题通常都属于监督学习。监督学习问题包括我们知道训练数据“标签”的问题。如果我们知道标签,那么任务就包括训练一些隐藏函数,将我们的数据矩阵映射到隐藏标签。例如,在分类器中,我们知道什么训练数据属于什么类,因此我们训练像神经网络这样的函数来拟合数据,并使用训练的模型来预测看不见的数据。
在无监督学习中,我们不知道我们训练数据的标签。我们不能在输入和输出之间建立直接的映射。因此,我们的目标是学会发现数据中的模式并加以利用。
数据聚类:K 均值
问题简介
聚类算法是一种无监督学习模型。他们的目标是将数据分组。考虑一个分类问题,您知道有多少个类,但您的数据是未标记的。
在 k-means 聚类中,我们假设我们知道有多少个组,然后我们将数据聚类到那个数量的组中。组的数量被表示为“k”,因此该算法的名称。
假设我们有以下问题:

3 集群问题(图片由作者提供)
我们有一个二维数据集。数据集似乎包含 3 个聚类(训练时我们不知道它们的真实标签)。我们不知道每个数据点的类成员,但我们知道系统中有 3 个组。能不能做一个算法,确定每个点属于哪个聚类?
制定

给定数据矩阵 x,它是一个 N×d 矩阵,有 N 个数据点,每个数据点有 d 维(在我们的问题中 d = 2)。我们想确定类成员(rnk)。如果数据点不属于某个聚类,则该变量为 0,否则为 1。这里有一个例子:

班级成员可视化(图片由作者提供)
上面是一个数据点的向量及其类成员的例子。该点属于簇 3,所以它 rn3 为 1,其余均为 0。目标是找出所有数据点的 rnk。
目标函数
为了训练机器学习模型,我们通常会寻找某种可以最小化的误差函数,以便训练我们的模型。在 k 均值聚类的情况下,目标是找到最小化类内方差的聚类中心:

如果我们最小化目标函数 J,我们就最小化了为该聚类选择的聚类中心μ之间的欧几里德距离的平方。

作者图片
因此,对于每个聚类,我们查看我们认为属于该聚类的点,并选择最小化聚类中心和该聚类中所有点之间的欧几里德距离的平方的聚类中心。我已经注意到上图中欧几里得距离是 L2 范数。
我们可以采用上面所示的目标函数,对聚类中心进行微分,并使其等于 0,以找到导致最小损失(聚类方差内的最小值)的聚类中心。

最佳聚类中心的推导。注意,当求微分时,在聚类中心 K 上求和。
在上面的推导中,我们发现最佳聚类中心(最小化聚类内方差的中心)是每个聚类的平均值。这是一个直观的结果。聚类中使方差最小的点将位于均值中。
k 均值算法
所以我们知道最优聚类中心是每个聚类的均值,但是在不知道类成员(rnk)的情况下,我们无法计算每个聚类的均值。为了解决这个问题,我们实现了一个迭代算法:

k-表示伪代码(作者)
我们首先随机初始化我们的聚类中心(第 1 行)。我们可以通过随机选取 k 个点来实现。然后,我们根据欧几里德距离将每个数据点分配到离它最近的聚类中心(第 7-10 行)。
然后,我们通过计算每个聚类中心的平均值来更新聚类中心(第 14–17 行)。最后,我们重复这一过程,直到聚类中心收敛。

随机初始化(图片作者提供)
在上面的图像中,通过随机选取 3 个点初始化了 3 个聚类中心。颜色表示每个数据点被分配到哪个聚类中心。通过计算每个点到所有 3 个聚类中心的欧几里德距离,并挑选最接近它的一个来计算类成员 rnk。如您所见,最下面的聚类中心占据了大部分数据。该算法的下一步是计算每个聚类的平均值并更新聚类中心。橙色和蓝色的聚类中心不会移动太多,但绿色的聚类中心会向下移动很远。在更新聚类中心之后,我们再次计算类成员,重复直到点已经收敛:

从随机初始化收敛(GIF 由作者提供)
正如你在上面看到的,k-means 算法成功地找到了真正的聚类中心。
k 均值聚类的问题
对初始化的依赖
k-means 聚类的一个大问题是它的一致性。由于随机初始化,k-means 不是很一致。根据初始化聚类中心的位置,该算法可以产生非常不同的结果。

k-means 被错误的初始化卡住(GIF 由作者提供)
在上面的 GIF 中,与之前的模型相同,但初始化不同。由于不幸的初始化,该模型未能找到真正的聚类中心。蓝色聚类的平均值卡在两个聚类之间,因为橙色和绿色聚类中心划分了第三个聚类。
我们并不总是知道 K
由于这是无监督学习,我们可能不知道真实的聚类数。这也将导致错误的聚类中心。

4 个聚类中心(GIF 由作者提供)
数据不总是均匀分布的
在 k-means 聚类中,每个数据点被分配到它最接近的聚类中心。在二维空间中,两个聚类中心之间的判定边界将是线性的,而不会考虑每个分布的形状。这对于没有相关性的分布很有效,就像我们到目前为止所展示的那样。但如果我们在数据中引入相关性,k 均值就开始表现不佳。

二元高斯和 k 均值(GIF 由作者提供)
看看上面 gif 中橙色和蓝色的星团。蓝色星团正在吞噬橙色星团,尽管从视觉上我们可以看出这些点属于橙色星团。在 k-means 中,我们不考虑数据中的相关性,但有其他聚类技术可以考虑。如果您的数据呈正态分布,如上图所示,k-means 可能不是最佳的聚类算法。敬请关注未来关于高斯混合模型的文章!
结论
本文直观地解释了 k-means 聚类,这是一种简单的数据聚类算法。k-means 聚类迭代地估计未标记数据集的聚类中心,以最小化类内方差。由于我们不知道类成员,为了计算类内方差,我们首先随机初始化类中心,然后期望每个类的新类中心是分配给每个类的数据点的平均值。k-means 有很大的缺点,我在文章的最后强调了这一点,在未来,我将引入高斯混合模型,这是一种将高斯分布拟合到未标记数据的方法。
支持我
希望这对你有所帮助,如果你喜欢,你可以 关注我!
你也可以成为 中级会员 使用我的推荐链接,获得我所有的文章和更多:https://diegounzuetaruedas.medium.com/membership
你可能喜欢的其他文章
基于零触发方法的无监督多语言文本分类
这篇文章是使用拥抱脸变形金刚的综合概述🤗执行零射击分类

介绍
大多数时候,当我们训练一个机器学习模型时,所有的候选标签/目标都是事先知道的,这意味着如果你的训练标签是科学、政治、教育,你将无法预测医疗保健标签,除非你重新训练你的模型,考虑到那个标签和相应的输入数据。在本文中,我将介绍我认为是多语言自然语言处理领域中最强大的无监督技术之一:**Zero-Shot-Classification**
零射击分类——是什么?
这种方法使得在没有看到任何候选标签的情况下预测文本的目标成为可能。
根据介绍 colab :
底层模型是在自然语言推理(NLI)任务上训练的,它接受两个序列,并确定它们是否相互矛盾、相互依赖或者都不存在。
这可以通过将我们想要分类的序列视为一个 NLI 序列(称为前提)并将一个候选标签变成另一个标签(假设)来适应零射击分类的任务。如果模型预测,构建的前提需要假设,那么我们可以把它作为标签应用于文本的预测。
实验
大多数研究都集中在英语上,这使得大多数方法在英语数据上比其他语言更有效。在一个有 6000 多种不同语言的世界中设计工具时,这可能是一个负担。
这个课题已经吸引了研究界好几年,结果相当令人鼓舞。我们的实验将利用乔·戴维森的 XNLI 罗伯塔大型 XNLI 模型的力量。除了对英语非常有效之外,它对相当多的语言也很有效,如法语、西班牙语、德语、斯瓦希里语、乌尔都语等。
简单来说,实验将基于两种主要语言,英语和法语,分别在 BBC 新闻数据 和Ecole Media CI网站摘录;和的数据都是两者免检。还有,文中用到的所有源代码在我的 Github 上都有。
先决条件
确保安装转换器并导入成功实验所需的库。
实验 _ 预准备. py
考虑到多语言方面,下面的表达式用于实例化我们的模型。
zsmlc_classifier.py
zsmlc_classifier用于零触发多语言分类器。将model参数初始化为joeddav/xlm-roberta-large-xnli,以便下载预先训练好的多语言模型。
让我们深入分类
分类任务使用三个主要参数,它们是
sequences对应要预测的文本/序列。candidate_labels是我们需要预测的所有候选标签的列表。请记住,这些标签不需要预先知道模型。multi_class是二进制值。如果我们要进行多类分类,则 为真 ,在这种情况下,所有的预测概率将是独立的,意味着每个值都在 0 和 1 之间,总和不一定是 1。如果为 False,则概率得分之和为 1。
了解单个预测的输出
这里的目标是帮助您理解单个预测的输出格式,这对于更好地理解其余部分是必不可少的。
显示输出格式
{'labels': ['tech', 'entertainment', 'business', 'sport', 'politics'],
'scores': [0.8140193223953247, 0.802348256111145, 0.791598916053772, 0.7419557571411133, 0.7163865566253662], 'sequence': 'tv future in the hands of viewers...they want [truncated]
输出是一个包含三个主键的字典:
- 标签:用于预测的所有候选标签列表。
- 分数:标签对应的概率分数列表。而我们可以看到,文中已经预测为 科技 (81%信心) 娱乐 (80%信心) 商业 (79%信心) 体育 (74%信心),以及 政治 (71%信心)。
- 序列:用于预测的原始序列/文本。为了不显示全部信息,这部分内容被故意截断了。
为了用https://plotly.com/对之前的预测有一个很好的图形化可视化,我们使用两个感兴趣的关键字将结果转换成熊猫数据帧:标签和分数列。****
绘图 _ 结果. py

图 1:预测为 81%可信技术的序列(图片由作者提供)
现在我们已经了解了预测格式,让我们通过对一批文本/描述进行预测来进一步分析。
案例 1:英语文本的分类
本例中的说明是通过使用原始数据的子集来实现的,这样处理速度会更快。为此,我随机对五个标签中的每一个进行了 2 次观察,总共 10 次观察。
已经实现了以下帮助器功能:
make_prediction为了从模型预测中返回概率得分最高的标签。run_batch_prediction用于对用户提供的数据集的所有观测值进行预测。
实用程序 _ 功能. py
最后,我们可以使用下面的表达式执行批量预测,以了解模型的执行情况。
run_batch_prediction.py

图 2:对 10 条随机 BBC 新闻的批量预测(图片由作者提供)
从上表中,我们可以观察到,除了第 221 行中的一个预测外,该模型的所有预测都是正确的,被预测为 sport 而不是 tech 。让我们看看相应的文本/描述以及所有候选标签的预测概率得分。
- 这是已经分类的原文

图 3:模型预测的原始文本(图片由作者提供)
- 这是模型的预测

图 4:预测为 75%置信度的运动的序列(图片由作者提供)
我们可以观察到,文中已经预测到 75% 体育 ,74% 商业 ,74% 科技 ,70% 娱乐 ,50% 政治 。通读全文,我们可以同意,它确实主要是关于 科技 而不是其他类别。
案例 2:对法文文本的分类
我们将以不同的方式处理这个问题。我们将尝试执行情感分类任务,而不是定义主题,这意味着我们将预测下面的文本是正极性、负极性还是中性。

图片 5:这个摘录没有获得 Ecolemedia.ci 的许可(图片由作者提供)

图片 5:法语序列被预测为“positif”(英语中为阳性),置信度为 99%(图片由作者提供)
该模型以 99% 的置信度预测先前文本为 阳性。对于懂法语的人来说,我们可以同意这个预测是完全准确的。
结论
从之前的用例来看,毫无疑问,零镜头分类对于无监督的文本分类来说是一场革命。它提供了更大的灵活性,并展示了迁移学习对更一般数据的所有威力。然而,当你在处理一个特殊的商业问题(如生物、金融等)时,使用监督培训方法可能更好。).不要犹豫,试一试,不要忘记继续学习!
关注我的 YouTube了解更多互动内容!
额外资源
乔·戴维森的 XNLI 罗伯塔大型 XNLI
BBC 新数据 Kaggle(T3)
曲名 : Bye For Now↓↓
无监督回归降维
原文:https://towardsdatascience.com/unsupervised-regression-for-dimensionality-reduction-88bd80bda4bf
使用回归计算低维嵌入

每个数据科学和人工智能的一年级学生都知道回归是一种监督学习方法。最初这是正确的,但是我们可以通过无监督回归[1]将事情颠倒过来进行降维。在降维中,我们寻找高维数据的低维嵌入。对于可视化或预处理等特定目的,任务是找到满足距离、密度和邻域保持等约束的低维嵌入。例如,数据空间中的紧密模式应该在低维空间中有紧密的对应关系,而不同模式的嵌入应该相距很远。
无监督回归
对于无监督回归,我们需要允许映射到多维标签空间的回归方法。例子是最近邻回归和核回归,即 Nadaraya-Watson 估计量。回归方法从低维潜在空间映射到高维数据空间。问题变成了:低维模式如何映射到高维模式?
想象一下,我们使用核回归从 2 维潜在空间映射到 N 维数据空间。任务是将随机选择的n22-维点x=x【₁,…】,x(我们不知道但想得到)用内核回归到n-维空间,让优化问题是最小化重建数据y=y【₁,…】,y【ₙ】我们寻求嵌入,即最小化数据空间重建误差:

通过改变 X 。这里使用的范数是 Frobenius 范数,它是一个矩阵的所有元素之和的平方根。
如何改变 X 使 E 最小化?答案是:通过梯度下降或者随机抽样。例如,无监督核回归(UKR) [1]使用梯度下降。具有高斯核的核回归方程是可导的。基于数据空间重构误差 E ,可以导出 UKR 的梯度,并在潜在空间 X 中执行梯度下降。UKR 将主要获得降维结果中的模式密度信息,因为核回归是一种基于密度的方法。空间中图案周围的密度越高,其对预测的贡献就越大。在 UKR 的情况下,模型必须被正则化以避免过度拟合。否则,模型只能重建模式,而没有任何归纳能力。
简单的例子
我们在这里更详细地看一个简单的例子是无监督最近邻(UNN) [2],它基于随机采样而不是梯度下降。k-最近邻用于在潜在空间中以随机采样的方式逐个模式地嵌入数据。让我们假设我们希望我们的嵌入存在于空间 [0,1】。第一图案 y ₁ 嵌入任意位置,例如潜在空间的中间(0.5,0.5)。每个新的图案yᵢ都是通过随机采样【0,1】中的 ν 点,并选择图案重构误差最小的 x *** ,即*与基于的最近邻预测之差来嵌入的***
在 Python 中,UNN 的代码如下所示,使用 scikit-learn 中的 KNeighborsregressor 进行 K 近邻预测。
在著名而简单的 IRIS 数据集上,该脚本生成了以下嵌入,参见图 1。嵌入的颜色基于类别标签,表明 UNN 将不同的类别区分开来,几乎没有例外。

图 1:嵌入 UNN 虹膜数据集,K=2,使用 Scikit-learn 虹膜样本脚本进行绘图
结论
无监督回归不仅是一种有趣的用于解决降维问题的回归变体,而且还被证明能够产生良好的嵌入。结果取决于所采用的回归方法的特征:最近邻将一个模式放置在已经嵌入的模式的邻域中。核回归考虑了相邻模式的密度信息。核 PCA、ISOMAP、LLE、T-SNE 和自动编码器等经典的降维竞争对手始终是不错的选择,但对于下一个嵌入任务,您可能希望尝试无监督回归。
除特别注明外,所有图片均为作者所有。
参考
[1] P. Meinicke,S. Klanke,R. Memisevic,H. J. Ritter,无监督核回归的主表面。IEEE Trans。模式分析与机器智能 27(9):1379–1391(2005)
[2] O. Kramer,无监督最近邻回归降维,软计算 19(6):1647–1661(2015)
IMDB 评论的无监督语义情感分析
原文:https://towardsdatascience.com/unsupervised-semantic-sentiment-analysis-of-imdb-reviews-2c5f520fbf81
一个捕捉情感复杂性和文本主观性的模型

作者图片
目录
1。简介2。数据预处理3。监督车型4。无监督方法5。进一步分析
**Note**: The Github repository of this project can be found [here](https://github.com/towardsNLP/IMDB-Semantic-Sentiment-Analysis/tree/main/Word2Vec).
1.介绍
问题概述
情感分析,也称为观点挖掘,是自然语言处理(NLP)的典型应用,广泛用于分析给定句子或语句的整体效果和潜在情感。情感分析模型以其最基本的形式将文本分类为积极的或消极的(有时是中性的)情感。因此,最成功的方法自然是使用监督模型,这种模型需要大量的标记数据来训练。提供这种数据是一个昂贵和耗时的过程,在许多情况下是不可能的或不容易获得的。此外,这种模型的输出是暗示文本与我们在训练期间提供的正面例子有多相似的数字,并且不考虑细微差别,例如文本的情感复杂性。
依靠我在文本的仔细阅读和定性分析方面的背景,我提出了一种无监督的语义模型,该模型捕捉文本的整体情感,同时提供了一种在保持高性能的同时分析文本中情感的极性强度和复杂性的方法。
为了演示这种方法,我使用了著名的 IMDB 数据库。斯坦福大学向公众发布了这个数据集,它是来自 IMDB 的 5 万条评论的集合,包含偶数条正面和负面评论,每部电影不超过 30 条评论。如数据集介绍注释中所述,“负面评价的得分≤ 4 分(满分 10 分),正面评价的得分≥ 7 分(满分 10 分)。中立评论不包含在数据集中。
该数据集由安德鲁·马斯(Andrew Maas)编制,并在本文中首次介绍:安德鲁·马斯、雷蒙德·戴利、彼得·范、黄丹、安德鲁·吴和克里斯托弗·波茨。(2011).学习用于情感分析的词向量。 计算语言学协会第 49 届年会(ACL 2011)。数据集可以从这里获得: IMDb 评论。
导入必要的库

导入 IMDB 数据

2.数据预处理
实用模块
[w2v_utils](https://github.com/TextualData/IMDB-Semantic-Sentiment-Analysis/blob/main/Word2Vec/src/w2v_utils.py)模块包含了 post 中多处使用的所有通用函数和类。下面是从 Word2Vec/src/w2v_utils 导入的函数和类的列表
Tokenizer类将处理所有的标记化任务,并使我们能够使用不同的标记化选项。这个类有以下布尔属性:clean、lower、de_noise、remove_stop_words和keep_neagation。所有属性默认为True,但是您可以更改它们来查看不同文本预处理选项的效果。默认情况下,该类去除文本的噪声(删除 HTML 和 URL 组件),将文本转换为小写,清除文本中的所有非字母数字字符,并删除停用词。这里的一个细微差别是否定停用词,如“不是”和“不”。否定词被认为是情感转换器,因为它们经常以相反的方向改变句子的情感(关于“否定和情感”的更多信息,请参见刘兵,【情感分析:挖掘观点、情感和情绪,剑桥大学出版社 2015 年,第 116-122 页)。如果keep_neagation为“真”,标记器会将否定标记附加到下一个标记,并在移除停用词之前将它们视为单个单词。对于我们在本帖中使用的模型,我们不需要将我们的评论分成句子,整个评论立刻就被标记化了。现在,让我们实例化记号赋予器,并在一个例子上测试它。

现在我们可以标记所有的评论,并快速查看一些关于评论长度的统计数据。

输出
最后,我们将数据分解成训练和测试,然后再进一步。

3.监督模型
让我们首先建立一个监督基线模型,以便稍后比较结果。有监督的情感分析本质上是一个分类问题,根据文档的情感效果将文档分为两类或更多类。值得注意的是,通过在我们的分析中选择文档级粒度,我们假设每个评论只包含评论者对单个产品(例如,电影或电视节目)的意见。因为当一个文档包含不同的人对单个产品的意见或者评论者对各种产品的意见时,分类模型不能正确地预测文档的总体情绪。
和往常一样,第一步是将评论转换成特征向量。我为这一部分选择了词频袋作为文本矢量化的简单而强大的基线方法。Frequency Bag-of-Words 为每个文档分配一个向量,该向量具有我们语料库中的词汇大小,每个维度代表一个单词。为了构建文档向量,我们用文档中相应单词的出现频率填充每个维度。所以很明显,大部分文档向量会非常稀疏。为了构建向量,我在我们的训练集上安装了 SKLearn 的 CountVectorizer,然后用它来转换测试集。在对评论进行矢量化之后,我们可以使用任何分类方法来构建情感分析模型。我试验了几个模型,发现一个简单的逻辑回归非常有效(关于 IMDB 上最先进的情绪分析列表,见paperswithcode.com)。

现在让我们看看模型在测试数据集上的表现:

4.无监督方法
在解决了基础问题之后,我们现在可以继续这篇文章的主旨,即情感分析的无监督方法,从现在开始我称之为语义相似性分析(SSA)。在这种方法中,我首先使用所有评论训练一个单词嵌入模型。这个嵌入空间的特点是,这个空间中词与词之间的相似度(这里是余弦相似度)是其语义相关度的度量。接下来,我将选择两组在电影评论上下文中表达积极和消极情绪的单词。然后,为了预测评论的情感,我们将计算文本在单词嵌入空间中与这些正面和负面集的相似性,并查看文本最接近哪种情感。
训练单词嵌入模型
在深入讨论细节之前,我们先来训练一下单词嵌入模型。由 Mikolov 等人于 2013 年发表,单词嵌入的引入是自然语言处理中的一项改变游戏规则的进步。这种方法有时被称为 word2vec,因为该模型将单词转换为嵌入空间中的向量。我用 gensim 包来训练 wordd2vec 模型。由于我们不需要将数据集分为训练和测试两部分来构建无监督模型,因此我在整个数据上训练模型。我还设置了嵌入维数为 300。

定义负集和正集
选择正负集没有唯一的公式。然而,我在我们新训练的嵌入空间中检查了与单词“好”和“坏”最相似的单词,以有一个起点。结合我对上下文的判断,我列出了以下清单:
positive_concepts= ['优秀','牛逼','酷','体面','了不起','强','好','伟大','有趣','娱乐']negative_concepts= ['可怕的','糟糕的','恐怖的','无聊的','糟糕的','令人失望的','虚弱的','可怜的','毫无意义的','令人困惑的']
请注意,我们应该确保所有的positive_concepts和negative_concepts在我们的 word2vec 模型中都有表示。


计算评论的语义情感
正如我们之前提到的,为了预测评论的情绪,我们需要计算它与我们的负面和正面集的相似度。我们将这些相似性分别称为负面语义得分(NSS)和正面语义得分(PSS)。有几种方法可以计算两个单词集合之间的相似度。最常见的方法之一是通过对文档的单词向量进行平均来构建文档向量。这样,我们将有一个向量用于每个评论,两个向量代表我们的积极和消极的集合。PSS 和 NSS 可以分别通过回顾向量和正负向量之间的简单余弦相似性来计算。我们姑且称这种方法为整体语义情感分析 ( OSSA )。
然而,对文档中的所有单词向量进行平均并不是构建文档向量的最佳方式。考虑一个有 100 个单词的文档。该文档中的大多数单词是所谓的粘合单词,它们对文档的意义或情感没有贡献,而是用来保持文本的语言结构。这意味着,如果我们对所有的单词进行平均,有意义的单词的效果将会被粘合单词降低。
为了解决这个问题,我假设一个单词与文档的相似度等于它与文本中前 n 个最相似单词的相似度的平均值。然后,我将为我的正面和负面集合中的每个单词计算相似度,并进行平均,以获得正面和负面得分。换句话说,为了估计评论的正面分数,我计算正面集中的每个单词与评论中所有单词的相似度,并保留每个正面单词的 top_n 最高分数,然后对所有保留的分数进行平均。这种方法可以被称为 TopN 语义情感分析(topsa)。
在计算正负分数后,我们定义
semantic_sentiment_score (S3) = positive_sentiment_score (PSS) - negative_sentiment_score (NSS)
如果 S3 为正,我们可以将评论归类为正,如果为负,我们可以将其归类为负。现在让我们看看这样一个模型是如何执行的(代码包括 OSSA 和 TopSSA 两种方法,但只探讨后者)。

正如分类报告所示,TopSSA 模型实现了更好的准确性,F1 分数高达约 84%,这是无监督模型的一个重大成就。
让我们将数据可视化,以便更好地理解结果。每个评论都根据其 PSS 和 NSS 放在下面散点图中的平面上。因此,决策边界(蓝色对角线)上方的所有点都具有正 S3,因此被预测为具有正情绪,而边界下方的所有点都具有负 S3,因此被预测为具有负情绪。评论的实际情绪标签用绿色(正面)和红色(负面)表示。从图中可以明显看出,大多数错误标记都发生在决策边界附近,如预期的那样。

作者图片
高可信度预测
众所周知,远离决策边界的结果具有更好的性能,这里我展示了这适用于我们的无监督模型。为此,我绘制了所有评论的 S3、PSS 和 NSS 分布。正如我们从中心极限定理中所预期的,所有三个分布都非常接近正态分布,其中 S3 的平均值和标准差分别为-0.003918 和 0.037186。接下来,我将高置信度预测定义为 S3 距离平均值至少 0.5*std 的预测。这包括了大约 64%的评论,并且该模型的 F1 为大约 94%。

作者图片

作者图片
5.进一步分析
到目前为止,我已经展示了一个简单的无监督模型如何在情感分析任务中表现得非常好。正如我在介绍中所承诺的,现在我将展示这个模型将如何提供监督模型所不能提供的额外的有价值的信息。也就是说,我将证明这个模型可以让我们理解文本的情感复杂性。为此,我将再次依靠我们的积极和消极的分数。首先,让我们看看这些分数的另一个性质。除了两个分数都是正态分布的事实之外,它们的值与评论的长度相关。也就是说,评论时间越长,它的负面和正面得分就越高。一个简单的解释是,一个人可以用更多的词来表达更多积极或消极的情绪。当然分数不能超过 1,最终饱和(此处 0.35 左右)。下图很好地显示了这种相关性。请注意,我颠倒了 NSS 值的符号,以便更好地描述 PSS 和 NSS 的情况。

作者图片
因此,在我们的分析中,为了考虑文本长度的影响,我们对数据集进行切片,使得放置在每个子集中的评论在长度上接近。在这篇文章中,我将分析限制在 100 到 140 个单词之间的评论(评论中的平均单词数是 120)。这个切片中有大约 8400 个数据点,它们各自的 F1 分数约为 82%,接近整个数据集的 F1 分数。此外,该切片中的 PSS 和 NSS 都具有以下值的正态分布:
PSS _ mean = 0.200648
PSS _ STD = 0.031200NSS _ 平均值= 0.205617
NSS _ 标准= 0.039358
从现在开始,任何 PSS 和 NSS 的均值和标准差都是指数据集中这一部分的值。

情感复杂性
我在这里的主要主张是,我们可以使用他们的 PSS 和 NSS 来评估情绪的复杂性(或情绪的复杂性)。我将证明,如果一个文本同时具有高 PSS 和高 NSS 值(低 S3),它可能具有高情感复杂度。并且具有低 PSS 和高 NSS 值或者反之亦然(高 S3)的文本可以被认为具有更清晰的情感或者低情感复杂度。但是首先要定义一下高低。为了便于分析,我们将高和低 PSS (NSS)分别定义为高于和低于平均值一个标准偏差的值,但当然,这些定义是相对的,可以调整。因此
- 高 PSS(NSS) = PSS(NSS)>平均值 _PSS(NSS) +标准 _PSS(NSS)
- 低 PSS(NSS) = PSS(NSS) < mean_PSS(NSS) — std_PSS(NSS)
Therefore the high sentiment complexity could be defined as:
高情绪复杂度=高 PSS &高 NSS
相比之下,一组评论同时具有低 PSS 和低 NSS。这些评论通常陈述更少的观点和更多的事实,因此它们可以被称为低主观性的评论,并被定量地定义为
低主观性=低 PSS &低 NSS
下图显示了这两组评论在 PSS-NSS 平面上的分布情况。

作者图片
尽管对于高情绪复杂性组和低主观性组,S3 不一定落在决策边界附近,但是由于不同的原因,我们的模型更难正确预测他们的情绪。传统的分类模型不能区分这两组,但我们的方法提供了这种额外的信息。下面两个互动情节让你通过悬停在评论上来探索评论。


定性评估
在这篇文章的其余部分,我将定性分析来自高复杂性小组的几篇评论,以支持我的观点,即情感分析是一项复杂的智力任务,即使对人脑来说也是如此。
虽然自然语言处理从来没有像意义理论和语言哲学中那样关注意图和规约之间的关系,但是我们不能忽视这样一个事实,即一个人可以使用具有不同于该词的普通用法的意义或情感意图的词。例如,我们可能会讽刺性地使用一个在交流习惯中通常被认为是积极的词来表达我们的消极观点。如果情感分析模型没有学会如何使用上下文指示来预测作者想要的情感,它就不能注意到这种情感转变。为了说明这一点,让我们看看 review #46798 ,它在高复杂度组中有一个最小的 S3。从“哇”这个词开始,这个词是惊讶的感叹词,常用来表达惊讶或钦佩,这篇评论似乎是积极的。评论家自相矛盾地重复说烂片很有趣。但是这个模型成功地捕捉到了用讽刺和挖苦表达的负面情绪。
文本情感复杂性背后的另一个原因是对主题的不同方面表达不同的情感,使得人们无法掌握文本的总体情感。一个例子是评论 #21581 在高情绪复杂性组中有最高的 S3。影评从电影的故事开始,根据影评人的说法,这是“如此愚蠢”和“充其量是一个拙劣的笑话。”但不久之后,通过使用几个情绪转移器如“不是”、“但是”和“然而”来陈述电影的积极和消极方面,复杂性就出现了。总的来说,在评论者看来,这部电影是 8/10,尽管在这个简短的文本中表达了所有复杂的情绪,但模型还是成功地预测了这种积极的情绪。
相比之下,审查 #29090 是模型错误的一个例子。影评强烈反对这部电影,并明确表达了对这部电影不该得到的曝光率和曝光率的失望和愤怒。然而,该模型未能预测情绪。因为影评中大量包含了其他人对电影的正面评价,以及影评人对其他电影的正面情绪。
同样耐人寻味的是,review #16858 戏剧性地融合了关于这部电影的复杂情感。这位影评人曾经很喜欢这部电影,在他们还是孩子的时候反复看了一遍。然而,作为一个成年人来看这部电影,他们的经历并没有他们记忆中的那么好:表演、故事情节和笑话看起来“相当糟糕”没有人能确定评论者在这两种对立情绪之间的最终决定。令人惊讶的是,他们决定珍惜他们的童年,并给它七颗星。难怪这个模型没有认识到怀旧的力量。
我们必须承认,有时我们的手工标签也不够准确。一个令人印象深刻的例子是评论 #46894 ,被贴上负面标签,但评论者明确指出“我给这部剧打六分。”请记住,数据集介绍文档声称分数为 5 和 6 的评论被认为是中立的,不包括在数据集中。尽管如此,我们的模型准确地将该综述分类为阳性,尽管我们在模型评估中将其视为假阳性预测。
复习#46798(真阴性预测):
topn_PSS= 0.297876
= 0.389544
=-0.0916676
“哇。我一看到这部电影的封面,就立刻想看,因为它看起来太糟糕了。有时候,我看宝莱坞电影,只是因为它们太糟糕了,所以会很有娱乐性。这部电影拥有一部残暴电影的所有元素:一群完全无害的“当地暴徒”,一个糟糕的摩托车场景,可怕的对话(“恭喜你,我很自豪你是一个坏男孩”),演员打篮球就像他们是好的一样,残暴的歌曲(“我坏,我坏,我坏男孩”),无法解释的情节线,比如为什么好男孩和坏男孩是朋友???为什么辣妹会爱上书呆子??我从没见过这么恐怖的故事。有些场景实际上长达 30 秒,就像好男孩和坏男孩莫名其妙地碾过“帮派成员”的扑克游戏。恭喜阿什维尼·乔德里,你是个糟糕的导演。如果你想看一部好电影,那就看《咕噜》,如果你想看一部非常糟糕的电影,以至于它实际上是娱乐性的,那就看《好孩子,孩子》。
复习#21581(真阳性预测):
topn_PSS= 0.260054
= 0.247708
topn_semantic_sentiment_score= 0.0123464
“剧情:一个犯罪头目联合了三个不同的黑手党来购买一个岛屿,然后作为有组织犯罪的洗钱设施。为了挫败这一阴谋,联邦调查局试图逮捕一名黑手党头目。事情出了差错,通过一些不太可能的情节曲折,我们看到了另一部“互不喜欢对方的警察伙伴”电影…一个是女性联邦调查局特工,另一个是男性前缉毒局特工。
这么远,这么蠢。但是这部电影的力量不在于它的故事——充其量是一个拙劣的笑话。这很有趣。(至少同步的德语版是)。动作也很好,有一个令人难忘的场景,包括一把猎枪和一个火箭发射器。但是焦点完全集中在幽默上。不聪明的讽刺,不太滑稽,但介于两者之间,你会得到很多有趣的笑话。
然而这部电影是政治正确的反面。合法药物滥用是突出的特点,没有批评,甚至显示它很酷。在我看来,这是这部电影最让我恼火的地方,也是它不适合儿童观看的地方。总而言之,对于一个美好的夜晚来说,看一些有趣的笑话和可接受的动作,这部电影是完美的。只要记住:在这种流派中,当你进入电影院/电视室时,把你的大脑留在门口是很常见的。那你会玩得很开心的。8/10 英寸
查看#29090(假阳性预测):
topn_PSS= 0.254718
topn_NSS= 0.252167
= 0.00255111
“这部电影如何获得 6.7 的评分令人难以置信。它只配得 2.0 分,显然应该被列入 IMDb 有史以来最差的 100 部电影之列。《国家宝藏》是对国家情报的冒犯,也是好莱坞对美国观众的又一次攻击。评论家们讲述了你可以驾驶 16 轮大卡车穿过的情节漏洞。
我喜欢这部电影好的理由……”尼古拉斯·凯奇很可爱。“来吧人们,难怪全世界的人都认为美国人很愚蠢。这一定是我看过的最愚蠢、最侮辱人的电影。如果你想在这一季看一部真正像样的电影,可以考虑金赛的《樵夫》、《百万美元宝贝》或《横着走》。不幸的是,《国家宝藏》比那些恐怖的电影得到了更多的关注。我敢打赌,读到这里的大多数人都没有听说过它们,因为有些还没有广泛发行。尼古拉斯·凯奇是个了不起的演员——当他出演合适的电影时。一次又一次,我看到凯奇把他惊人的天赋浪费在像《空气骗局》、《摇滚》和《面对面》这样令人麻木的电影上。当他的才华得到很好的利用时,就像查理·考夫曼的改编,他是一个不可思议的演员。
底线——我宁愿把手伸进碎木机,也不愿再次遭受这种视觉上的暴行。”
回顾#16858(假阴性预测):
topn_PSS= 0.251645
= 0.256545
=-0.0048998
是的,我小时候就喜欢这部电影。在我成长的过程中,我看了很多遍这部电影,以至于我爸爸不得不买另一部 VHS 拷贝,因为旧的拷贝已经用坏了。当我们购买新的 VHS 系统时,我的家人收到了这部电影的 VHS 副本。起初,我妈妈不确定这是否适合一个 10 岁的孩子,但因为我们刚买了一个新的 VHS 系统,她让我看。就像我说的,这部电影是每个小男孩的梦想。这部电影包含了一个可怕的场景,肌肉发达的野蛮人,美丽的袒胸女人,大坏蛋和你长大后才会听到的笑话。因此,几天前我插入了视频,并在很长时间后再次观看了这部电影。一开始很无聊,后来开始想我小时候有多爱这部电影,继续看。是的,这次经历没有我记忆中的那么好,表演很糟糕,故事情节很糟糕,笑话不再好笑,但是女人们仍然很漂亮。是的,我长大了。即使电影体验对我来说发生了变化,我仍然认为它值得 7 颗星。为了你知道的过去的美好时光。”
回顾#46894(阳性预测,误标为阴性):
topn_PSS= 0.286535
= 0.211566
topn_semantic_sentiment_score= 0.0749689
“我给这部剧打六分,因为这部剧实际上是小达蒙·威亚斯的一个平台,就像考斯比秀是比尔·科斯比的平台一样,它用幽默处理了很多问题,我觉得它实际上是为了获得笑声,而不是让笑话来自角色。迈克尔·凯尔是一个有趣的家长,也是一个爱说俏皮话的人。他在电影中是非凡的,但在节目中,他在那里说俏皮话,虽然我很喜欢,但我觉得笑声比合理性更重要。我从《家庭聚会》开始就喜欢她,喜欢她在《学校发呆》和《马丁》中的表演,这对她来说是一个很棒的角色,她选择合演这部情景喜剧是一个很好的选择。我也觉得周杰伦和迈克尔在节目中更像是平等的,但周杰伦更像是一个给她疯狂的丈夫提供台词的女人,并赞同他非正统的纪律,因为她可能觉得这很有效。Jr 只是简单的愚蠢,他的性格应该得到很好的发展,即使他有伟大的时刻,我们还是回到了愚蠢,好像他什么也没学到,这让我发疯!!!!!!!!更不用说大多数情况(在我看过的剧集中)似乎都围绕着他
克莱尔这个与基督徒约会的迷人妹妹,我发现她男朋友的角色比她更有趣(她最好坚持看电影,编剧应该做得更多来展示她的智慧,但这还不够老套)。可爱的小女儿凯迪。我认为除了《父母》和富兰克林之外,编剧们在剧中塑造了她最多的角色。富兰克林我喜欢这个角色,我认为他们是从只持续了一季的《聪明的家伙》( T.J. Mowry)中衍生出来的。他们为这个小天才做了很好的选角工作(如果 Jr 是聪明的那个,他们会做出努力,但也会表现出缺点)。
总而言之,这部情景喜剧非常精彩,它是对科斯比秀的致敬,做得很好,我喜欢这部剧,希望它能持续更长时间。我迫不及待地想看到该系列的大结局。”
现在轮到你了
让我们来玩玩文本语义情感分析功能。请分享您对 TopSSA 模型的看法,并探讨它在分析情绪方面的准确性。
感谢
我要向贾瓦德·哈希米表达我最深切的感谢,感谢他对这个项目的建设性建议和有益反馈。特别是,我非常感谢他对情感复杂性的见解,以及他为计算我在[list_similarity](https://github.com/TextualData/IMDB-Semantic-Sentiment-Analysis/blob/main/Word2Vec/src/w2v_utils.py)函数中使用的两个符号列表之间的向量相似性而提出的优化解决方案。
Python 中控制属性的特殊方法
原文:https://towardsdatascience.com/unusual-ways-to-control-attributes-in-python-8b69d9efda57
Led 指南示例

图片由 Unsplash 提供
属性验证确保用户提交的数据具有正确的格式或类型。Python 中属性控制可以通过多种方式进行控制。在这里,我提出了两种不同寻常但有趣的控制属性的方法。
示例 1:使用 magic call 方法进行属性重新分配
为了开始这个例子,我首先创建一个名为 Employees 的类,它有三个属性:姓名、年龄和地点。创建该类的一个实例,为其分配变量名 emp,并为属性设置相应的值。
使用魔术调用方法可以实现重新分配/更新属性的另一种方法。为了举例说明一个用例,我定义了一个 call 方法,它将*args 和**kwargs 作为方法参数。
当 Employees 类的实例首次初始化时,三个属性值被分配给对象属性' _name '、' _age '和' _location '。但是,使用 call 方法可以很容易地将这些属性值重置为新值。call 方法允许像对待函数一样对待实例。
调用方法定义如下。下面描述调用 call 方法的两个示例用例。
call 方法中的条件逻辑首先检查我们创建的对象 emp 是否具有属性' _name '。该条件将评估为真。此属性是在 init 方法期间设置的。条件逻辑还检查元组的长度(**args)是否等于 1。当两个条件都满足时,可以像调用函数一样调用 call 方法,如下所示:
EMP(示例新名称’)
名称属性现已重置。我们可以通过遍历对象字典来确认这一点。在这里,我们把名字从“斯蒂芬”改成了“西蒙”。
虽然上述方法是更改属性值的一种便捷方式,但不一定清楚我们要更改哪个属性值。我们可以通过编写一个小的 docstring 或提供例子来帮助使这一点更清楚。
然而,更简单的方法可能是在 call 方法中使用**kwargs 参数。
如果我们再次查看Example _ 1 _ call _ method _ Part b . py中的 Python 脚本,第二个条件检查是否设置了 location 属性,我们命名为employee_info 的kwargs dict 的长度为 1。当我们像这样调用 call 方法时:
emp(location='newloc ')
该条件将评估为真,我们可以重新设置位置属性,如下所示。
注意:给出的例子是为了说明如何使用 call 方法。使用**employees_info 字典更改年龄和姓名的属性值需要对其他参数进行额外的验证。
最后,在 else 语句中,如果像这样调用 call 方法:
电磁脉冲()
将调用 init 方法,尽管省略它可能更容易。我已经包含了它,以显示不会对属性值进行任何更改。
示例 2:使用自定义类设置属性
控制属性的另一种不同寻常的方法是使用自定义类。在下面 GitHub gist 的中心,我创建了一个名为 Employees 的类。我实例化了两个类对象,名为 name 和 location,作为 StrAttrValidation 类的实例。
在 StrAttrValidation 类中,一个空字典被初始化。
在 Empolyees 类中,当在 init 方法中调用“object.attribute = value”时,将调用 StrAttrValidation 的 set 方法,其中字典将实例作为键,将值作为用户设置的值。
为了证明这一点,当我创建名为 emp1 的 employees 对象时,名称和位置都是小写的。在下面的实现中,属性现在已经被资本化,参见下面的终端输出。
我们可以在 set 方法中添加进一步的验证。名称和位置只能是有效的字符串。让我们将验证添加到 SetAttrValidation 类的 set 方法中。现在,当我们试图将位置作为一个 zipcode(表示为一个整数)添加时,会引发一个异常,并且不会设置属性。
这两种方法代表了 Python 中控制属性的不同方式。通常更简单的实现,比如在初始化对象时在 init 方法中进行验证,可能是最好的方法。
感谢阅读!
利用地理空间技术揭示捕鱼活动对海洋巨型动物的风险
中国渔船濒临灭绝的鲸鲨——加拉帕戈斯群岛

图片由作者提供。“天空”鲸鲨的轨迹上覆盖着中国的捕鱼活动。厄瓜多尔加拉帕戈斯。数据来自全球渔业观察 ( CC BY-SA 4.0 )和加拉帕戈斯鲸鲨项目。 打开这个故事 !
地理空间技术在过去十年中发展迅速,成果显著。地球观测数据可以在几秒钟内揭示环境和人类活动,并已用于不同的情况,如海洋保护、野火和城市热浪,仅举几例。全球渔业观察机构通过对帕劳群岛、加拉帕戈斯群岛、纽埃岛等海洋区域的数据分析,大力披露渔业活动,并成功支持许多国家保护其海洋资源免遭非法捕捞和过度捕捞。
过度捕捞活动给海洋生态系统链带来失衡,并通过罢工和不必要的捕捞危及海洋巨型动物的栖息地。在加拉帕戈斯海洋保护区,在海洋巨型动物基金会(MMF) 和加拉帕戈斯鲸鲨项目(GWSP) 旁边,2019 年标记的鲸鲨“希望”的故事变得国际化。“希望”号开始以不切实际的速度从海面给出卫星轨迹,很可能被附近的一艘渔船捕获。了解更多关于 MMF 以加拉帕戈斯海洋保护区为目标的捕鱼船队的故事。
不幸的是,“希望”号并不是最后一条被意外捕获的鲨鱼。之后,2021 年在 GWSP 附近标记的另外三只鲸鲨开始从陆地上发出信号。野生动物追踪器地理框架的警报系统报告了厄瓜多尔和秘鲁海岸的鲸鲨。加拉帕戈斯鲸鲨项目与 GIS4 野生动物运动分析合作,撰写了一份关于这些事件中所涉及的保护工作和捕鱼活动风险的报告。在这里找到索菲亚·格林写的故事。
图片由 GIS4 野生动物提供。野生动物追踪警报系统报告的在陆地上发现的三个人的轨迹。数据来自加拉帕戈斯鲸鲨项目DOI:10.5281/Zeno do . 6477840
中国渔船瞄准加拉帕戈斯群岛
中国船只是巨大的冰箱,可以在世界各地的任何海洋中运行(母舰)。他们的捕鱼活动在 2017 年被报道为非法[ 1 ],一艘装满鱼翅的船在严禁捕鱼的海洋保护区内被捕获。
《纽约时报》最近的一篇报道调查了 2020 年和 2021 年中国捕鱼活动是如何瞄准加拉帕戈斯海洋保护区的。主要问题是,海洋巨型动物物种需要很大范围的栖息地,而且通常超出了海洋保护区的界限。如果捕捞活动继续下去,没有计划加以控制,海洋巨型动物个体的事故可能会不断发生,包括船只撞击、意外捕获和非法捕捞。
几个世纪以来,加拉帕戈斯群岛周围丰富多样的生态环境吸引了当地渔民。现在,这片水域面临着一个更大、更贪婪的猎手:中国。
《纽约时报》的迈尔斯、常、沃特金斯和傅(2022)
数据许可和归属
- 表观渔捞努力小时数—全球渔捞观察数据集在知识共享署名许可下-ShareAlike 4.0 国际许可( CC BY-SA 4.0 )。一旦你从全球渔业观察门户或下载门户下载了表观捕捞努力量,你就可以在元数据中找到许可证。这些数据的用户被允许以任何媒体或格式复制和再分发这些材料,重新混合、转换这些材料,并基于这些材料用于任何目的,甚至是商业目的。应归功于全球渔业观察组织。
- 天空中的鲸鲨和陆地上发现的 3 只个体—由 提供加拉帕戈斯鲸鲨项目
可视化(图片)在知识共享署名 4.0 国际许可下开放访问。允许公众在引用归属的条件下共享内容,如:
Bryan R. Vallejo,& Sofia Green。(2022).野生动物跟踪器 v0.3 中的海洋卫星数据实现:用加拉帕戈斯鲸鲨项目(WildlifeTracker0.3)测试的生态地理变量和捕鱼压力。DOI:10.5281/Zeno do . 6477840
地理空间技术揭露中国在加拉帕戈斯的捕鱼活动
由于全球渔业观察机构及其在处理卫星记录的数百万个位置方面的努力,我们能够获得捕鱼活动的指标,如捕鱼作业时间。捕鱼活动已经被鲸鲨【天空】覆盖。这些生物记录数据由加拉帕戈斯鲸鲨项目提供给其合作伙伴 GIS4 野生动物运动分析,用于地理空间数据实验。到目前为止,结果是成功的,通过鲸鲨跟踪数据揭示了捕鱼活动模式。
方法概述
用于这些可视化的方法是一个基本的属性过滤。下载的数据代表指标捕鱼努力小时,它被下载并显示在加拉帕戈斯海洋保护区周围。你会从全球渔业观察的公共数据集中找到不同的部分,如捕捞努力量、锚地、载体和一些额外的数据集。本文中使用的是船只和旗帜的捕捞努力量。属性标志将允许您按感兴趣的国家(如厄瓜多尔、中国、智利、秘鲁、哥伦比亚和数据中可用的任何其他国家)对捕捞作业数据进行子集划分。您可以在数据和代码页下载这些数据。
保护的应用和可用性
海洋巨型动物轨迹上捕捞压力的可视化有助于理解海洋物种可能受到捕捞活动威胁的区域。风险包括船只撞击、非法捕鱼和意外捕获。为了解决这个问题并促进海洋野生动物保护,我们对这种可视化方法有不同的应用:
- 根据海洋大型动物倾向于造访危险区域的季节,重新安排捕鱼活动。
- 当海洋巨型动物进入危险或高捕捞压力区时,向保护机构和渔业当局发出实时警报系统。
- 揭露可能对海洋巨型动物物种面临危险负责的国家。
如果你愿意基于数据了解更多关于保护工作的信息,你可以在 GIS4 野生动物页面找到更多信息
模式可视化
在这个快速查看中,我显示了与天空时间范围相匹配的捕鱼努力小时数,包括所有涉及的旗帜。时间范围为 2021 年 7 月 1 日至 2022 年 4 月 1 日。

图片由作者提供。天空时间范围内加拉帕戈斯海洋保护区的捕鱼活动。数据来自全球渔业观察 (CC BY-SA 4.0)
下一步,有助于理解捕鱼活动对海洋巨型动物的风险,可以通过添加“天空”轨迹和用中国国旗过滤捕鱼活动来可视化。

图片由作者提供。中国在加拉帕戈斯海洋保护区的捕鱼活动和鲸鲨轨迹。数据来自全球渔业观察 (CC BY-SA 4.0)和加拉帕戈斯鲸鲨项目。
有两点需要直观地指出:
当它到达南部地区第一个捕鱼活动集中的地方时,天空就停止了。显然,捕鱼活动的压力中断了海洋巨型动物的自由活动

图片由作者提供。天空轨迹中的第一个运动异常。数据来自全球渔业观察 (CC BY-SA 4.0)和加拉帕戈斯鲸鲨项目
然后,突然,天空停止运动,当它到达北部地区的捕鱼活动压力较高。天空最后一次报道是在 2022 年 3 月 24 日。很自然,我们知道天空停止发送信号是因为失去被标记的鲸鲨的信号是很常见的。但是在看到天空电视台最后的位置在一个濒危区域后,我开始怀疑了。

图片由作者提供。天空运动接近高渔业压力时结束。数据来自全球渔业观察 (CC BY-SA 4.0)和加拉帕戈斯鲸鲨项目
我们不能肯定地说是什么阻止了鲸鲨 Sky 的日志,但我们可以清楚地看到,在它的轨迹中,个体暴露于捕鱼活动。
正如我们所见,中国船只正在海洋保护区外遭遇巨型海洋动物。这些事件增加了危及脆弱海洋物种生命的问题发生的可能性。解决方案相当复杂,但根据海洋游客安排捕鱼活动是一种选择。根据当局的行为,该解决方案可以配备实时地理空间技术。
后续步骤
一旦数据与海洋巨型动物跟踪数据及时匹配,下一步就是开始模拟捕捞压力造成的风险区域。一个好办法是在大型海洋动物物种在时间和距离上经常暴露于渔船的地方建立一个缓冲区。这个模型正在开发中,我们的目标是让它实时工作,以便在保护工作中做出快速反应。这种模式愿意在海洋保护软件中实现,如野生动物追踪器。
结论
地理空间技术当然可以支持海洋野生动物的保护工作。捕鱼活动可以在海洋个体的轨迹中显示和叠加,以了解它可能如何影响它们的栖息地。如果当局决定获得海洋保护区管理的适当技术,他们就能够控制可能危及受保护海洋物种生命的捕捞活动的时间安排。作为地理空间分析的专业人士,我可以明确地说,这只是海洋巨型动物保护和捕鱼活动对其影响之间的开始。我想到了许多关于如何更深入地分析这些数据集之间的关系的想法,当然也揭示了更多的见解,如停止检测或标记轨迹注释。
作者:
布莱恩·r·巴列霍
地理空间科学家“野生动物追踪器”的创造者
订阅我的故事
参考
[1]贝尔,雷切尔(2017)。成千上万的鲨鱼被发现在巨大的非法捕捞船上。国家地理。从
[2] 史蒂文·李·迈尔斯,艾格尼丝·张,德里克·沃特金斯,以及克莱尔·傅 (2022)。中国如何瞄准全球鱼类供应。纽约时报。
使用 Python-Python 编程 PyShark 解压缩文件
原文:https://towardsdatascience.com/unzip-files-using-python-python-programming-pyshark-3f8ae7f9efd5
在本教程中,我们将探索如何使用 Python 解压文件

托马斯·索贝克在 Unsplash 上的照片
目录
- 介绍
- 创建一个示例 ZIP 文件
- 使用 Python 从一个 ZIP 文件中提取所有文件
- 使用 Python 从 ZIP 文件中提取单个文件
- 使用 Python 基于条件从 ZIP 文件中提取文件
- 结论
介绍
ZIP 文件是我们经常看到的东西。它只是一个包含多个压缩文件的文件(存档)。
这对于高效的数据传输以及通过减小文件大小来存储较大的文件非常有用。
一次处理许多 ZIP 文件可能是一项非常手动的任务,但是 Python 允许我们高效地处理多个 ZIP 文件,并非常快速地从中提取数据。
为了继续学习本教程,我们需要以下 Python 库:zip file(Python 中内置的)。
创建一个示例 ZIP 文件
为了继续学习本教程,我们需要一个 ZIP 文件。
如果你已经有了,那就太好了。如果你没有,那么请随意下载一个我创建并上传到 Google Drive 的示例 ZIP 文件( my_files.zip )。
这个 ZIP 文件包含三个文件:
- customers.csv
- products.csv
- code_snippet.png
下载完成后,将它放在 Python 代码文件所在的目录中。
使用 Python 从一个 ZIP 文件中提取所有文件
我们手动对 ZIP 文件执行的最常见的任务之一是从其中提取所有文件。
使用 Python 中的 zipfile 库,我们可以用几行代码来实现:
我们需要做的就是创建一个 ZipFile 类的实例,并将 ZIP 文件的位置和“读取”模式作为参数传递给它,然后使用提取所有文件。extract all()方法。
下面是编写相同代码的另一种方法:
在这两种情况下,这三个文件都将从 ZIP 文件中提取出来。
使用 Python 从 ZIP 文件中提取单个文件
我们可能有的另一个任务是使用 Python 从 ZIP 文件中提取特定的单个文件。
首先,让我们找到在 ZIP 文件中归档的文件列表:
您应该得到:
['code_snippet.png', 'customers.csv', 'products.csv']
假设我们只想提取 customers.csv 和 products.csv 文件。
因为我们知道文件名,所以在使用 Python 从 ZIP 文件中提取文件时,我们可以使用它们作为标识符:
你应该看看这两个。csv 文件被提取到 Python 代码所在的文件夹中。
使用 Python 基于条件从 ZIP 文件中提取文件
在上面的例子中,我们提取了两个。使用 Python 将 ZIP 文件转换为 csv 文件。
一个接一个地提取单个文件只在我们处理几个文件时有效。
假设我们现在有一个包含数百个文件的大型 ZIP 文件,我们只想提取 CSV 文件。
我们可以根据文件名中的某些条件提取这些文件。
对于 CSV 文件,它们的名称以“.”结尾。csv”,我们可以在使用 Python 从 ZIP 文件中提取文件时使用它作为过滤条件:
您应该会看到从 ZIP 文件中提取的两个 CSV 文件,这与上一节中的结果相同。
结论
在本文中,我们探讨了如何使用 Python 从 ZIP 文件中提取文件。
如果你有任何问题或对一些编辑有建议,请随时在下面留下评论,并查看我的更多 Python 编程教程。
原载于 2022 年 5 月 9 日【https://pyshark.com】。
Python 增强提案为您带来的即将到来的 Python 特性
看看最近的 Python 增强提案(pep)以及它们可能带来的所有令人兴奋的新特性、变化和改进

在任何新特性、变化或改进进入 Python 之前,需要有一个 Python 增强提议,也称为 PEP,概述提议的变化。这些 pep 是获取关于即将发布的 Python 版本中可能包含的最新信息的好方法。因此,在这篇文章中,我们将回顾所有将在不久的将来带来一些令人兴奋的新 Python 特性的提案!
语法变化
所有这些提议都可以分成几类,第一类是语法改变提议,它肯定会带来有趣的特性。
这个类别中的第一个是 PEP 671 ,它提出了后期绑定函数参数默认值的语法。那是什么意思呢?
Python 中的函数可以将其他函数作为参数。然而,没有好的方法来设置这些参数的默认值。通常使用None或 sentinel 值(全局常量)作为默认值,这有缺点,包括不能在参数上使用help(function)。这个 PEP 描述了使用=> ( param=>func())符号指定函数作为默认参数的新语法。
对我来说,这种改变看起来合理且有用,但我认为我们应该小心添加太多新的语法符号/改变。像这样的小改进是否保证了另一个赋值操作符是有问题的。
另一个语法改变提议是 PEP 654 ,它提议将except*作为引发异常组的新语法。这种方法的基本原理是 Python 解释器一次只能传播一个异常,但是当栈展开时,有时需要传播多个不相关的异常。一种这样的情况是来自并发任务的asyncio的并发错误或在执行重试逻辑时引发的多个不同异常,例如,当连接到某个远程主机时。
这是使用这个新特性的一个非常简单的例子。如果你看一看 PEP 中的处理异常组的例子,你会发现使用它的很多方法,包括递归匹配和链接。
打字
下一个类别——在最近的 Python 版本中大量出现——是类型/类型注释。
让我们从 PEP 673 开始——它不需要对 Python 的typing模块有广泛的了解(通常情况下就是这样)。让我们用一个例子来解释一下:假设你有一个方法为set_name的类Person,它返回self——类型为Person的实例。如果你用同样的set_name方法创建子类Employee,你会期望它返回类型Employee的实例,而不是Person。然而,这并不是类型检查目前的工作方式——在 Python 3.10 中,类型检查器推断子类中的返回类型是基类的类型。该 PEP 通过允许我们使用带有以下语法的“Self Type”来帮助类型检查器正确推断类型,从而解决了这个问题:
如果您遇到了这个问题,那么您可以期待很快使用这个特性,因为这个 PEP 已经被接受,并将作为 Python 3.11 版本的一部分来实现。
另一个类型变化出现在 PEP 675 中,标题为任意文字字符串。
引入这个 PEP 源于这样一个事实,即当前不可能指定函数参数的类型可以是一个任意文字字符串(只能使用特定的文字字符串,例如Literal["foo"])。您可能想知道为什么这甚至是一个问题,为什么有人需要指定参数应该是文字字符串,而不是 f 字符串(或其他插入的字符串)。这主要是安全问题——要求参数是字面量有助于避免注入攻击,无论是 SQL/命令注入还是 XSS。PEP 的附录中显示了一些例子。实现这一点将有助于像sqlite这样的库在字符串插值被用在不该用的地方时向用户提供警告,所以让我们期待这一点很快被接受。
排除故障
接下来是 pep,帮助我们更有效地调试代码。从标题为CPython的 PEP 669 开始。这个 PEP 建议为 CPython 实现低成本监控,在运行调试器或分析器时不会影响 Python 程序的性能。考虑到在进行基本调试时不会有很大的性能损失,这不会对 Python 的最终用户产生很大的影响。然而,这在某些特殊情况下非常有用,例如:
- 调试只能在生产环境中重现的问题,而不会影响应用程序性能。
- 调试竞争条件,计时会影响问题是否会发生。
- 运行基准测试时进行调试。
我个人可能不会从中受益太多,但我相信试图提高 Python 本身性能的人肯定会喜欢这种变化,因为这将使调试和测试性能问题/改进变得更容易。
下一个是 PEP 678 ,它建议将__note__属性添加到BaseException类中。该属性将用于保存附加的调试信息,这些信息可以作为回溯的一部分显示。
如上例所示,这在重新引发异常时特别有用。正如 PEP 所描述的,这对于有重试逻辑的库也是有用的,为每次失败的尝试增加额外的信息。类似地,测试库可以利用这一点向失败的断言添加更多的上下文,比如变量名和值。
最后一个与调试相关的提议是 PEP 657 ,它想给 Python 程序的每个字节码指令添加额外的数据。该数据可用于生成更好的追溯信息。它还建议应该公开 API,这将允许其他工具(如分析器或静态分析工具)使用这些数据。
这听起来可能没什么意思,但实际上——在我看来——这是这里介绍的最有用的激励。这个 PEP 的最大好处肯定是拥有更好的回溯信息,例如:
我认为这对于回溯可读性和调试来说是一个惊人的改进,我真的很高兴这是作为 Python 3.11 的一部分实现的,所以我们很快就会使用它。
生活质量改变
最后一个主题是致力于带来某些“生活质量”改善的 pep。其中之一是 PEP 680 ,它提议在 Python 的标准库中添加对解析 TOML 格式的支持。
默认情况下,TOML 作为一种格式被许多 Python 工具使用,包括构建工具。这给他们制造了一个自举问题。此外,许多流行的工具如flake8不包含 TOML 支持,理由是它在标准库中缺乏支持。这个 PEP 提议在标准库的基础上增加 TOML 支持,这个标准库已经被像pip或者pytest这样的包使用。
我个人喜欢这个提议,我认为在标准库中包含通用/流行格式的库是有意义的,特别是当它们对 Python 的工具和生态系统如此重要的时候。问题是,我们什么时候能在 Python 标准库中看到 YAML 支持?
最后一点,就是 PEP 661 ,与所谓的 【哨兵值】 有关。在 Python 中没有创建这种值的标准方法。通常用_something = object()(普通习语)来完成,如前面的 PEP 671 所示。本 PEP 提出了标准库哨兵值的规范/实施:
除了新的解决方案可读性更好之外,这也有助于类型注释,因为它将为所有 sentinels 提供不同的类型。
结束语
从上面提出的建议来看,很明显,很多好东西正在向 Python 走来。然而,并不是所有的这些特性都会被纳入 Python(至少在目前的状态下不会),所以一定要关注这些提议,看看它们会走向何方。为了及时了解上述 PEP 以及任何新添加的内容,您可以偶尔浏览一下索引,或者通过订阅 PEP RSS feed 获得关于每个新添加内容的通知。
此外,我只包括了一些对该语言提出一些新功能/变化的 pep,然而还有其他一些指定最佳实践、过程或烤箱 Python 的发布时间表,所以如果你对这些主题感兴趣,请确保查看上述索引。
**本文最初发布于martinheinz . dev
Python 中的美国市场银行假日
原文:https://towardsdatascience.com/us-market-bank-holidays-pandas-fbb15c693fcc
使用熊猫用 Python 计算纽约证券交易所市场银行假日

照片由 Briana Tozour 在 Unsplash 上拍摄
介绍
在过去的几天里,我一直在试图找到一个合适的方法来计算任何一年的纽约证券交易所市场银行假日。尽管我遇到了一些不同的开源包,但大多数都不可靠,包含一些 bug,不允许我实现我想要的东西。
在今天的文章中,我将介绍纽约证券交易所市场每年应该关闭的特定银行假日,并展示如何实现一个非常简单的类,您可以使用它来计算任何给定时间的银行假日,而结果将包含在 pandas 数据帧中。
纽约证券交易所银行假日
根据纽约证券交易所官方市场日历,在接下来的美国假期,市场将关闭:
- 元旦
- 马丁·路德·金纪念日
- 华盛顿的生日
- 受难日
- 阵亡将士纪念日
- 6 月 10 日国家独立日
- 美国独立日
- 劳动节
- 感恩节
- 圣诞日
计算熊猫的市场银行假日
Pandas 的模块pandas.tseries.holiday包含几个与美国特定节日相对应的类。已经实现并且我们需要作为我们将在本部分构建的逻辑的一部分的是:
USMartinLutherKingJrUSPresidentsDayGoodFridayUSMemorialDayUSLaborDay- 和
USThanksgivingDay
因此,我们错过了一些假日,我们将通过创建也包含在pandas.tseries.holiday模块中的Holiday类的实例来手动创建这些假日。
第一个是发生在一月一日的新年。此外,我们还将指定nearest_workday作为遵守规则,以便每当该日期是星期六时,它将被移动到星期五,同样,每当它是星期天时,它将被移动到星期一。
from pandas.tseries.holiday import Holiday, nearest_workdaynye = Holiday('NYDay', month=1, day=1, observance=nearest_workday)
同样,我们也将创建几个Holiday实例来代表 6 月 10 日美国独立日、美国独立日和圣诞节。
from pandas.tseries.holiday import Holiday, nearest_workday juneteenth = Holiday(
'Juneteenth National Independence Day',
month=6,
day=19,
start_date='2021-06-18',
observance=nearest_workday,
)independence = Holiday(
'USIndependenceDay',
month=7,
day=4,
observance=nearest_workday
)christmas = Holiday(
'Christmas',
month=12,
day=25,
observance=nearest_workday
)
现在,为了创建一个包含纽约证券交易所市场银行假日的日历,我们将创建一个实现AbtractHolidayCalendar的类。AbstractHolidayCalendar类提供了返回假日列表的所有必要方法,只有rules需要在特定的假日日历类中定义。
因此,我们需要在我们的类中指定一个rules列表,其中包含我们之前创建的所有Holiday实例,以及已经在pandas.tseries.holiday模块中实现的剩余银行假日。
from pandas.tseries.holiday import nearest_workday, \
AbstractHolidayCalendar, Holiday, \
USMartinLutherKingJr, USPresidentsDay, GoodFriday, \
USMemorialDay, USLaborDay, USThanksgivingDayclass USTradingHolidaysCalendar(AbstractHolidayCalendar):rules = [
Holiday(
'NewYearsDay',
month=1,
day=1,
observance=nearest_workday
),
USMartinLutherKingJr,
USPresidentsDay,
GoodFriday,
USMemorialDay,
Holiday(
'Juneteenth National Independence Day',
month=6,
day=19,
start_date='2021-06-18',
observance=nearest_workday,
),
Holiday(
'USIndependenceDay',
month=7,
day=4,
observance=nearest_workday
),
USLaborDay,
USThanksgivingDay,
Holiday(
'Christmas',
month=12,
day=25,
observance=nearest_workday
),
]
现在,为了计算特定年份的纽约证券交易所银行假日,我们需要做的就是创建一个USTradingHolidaysCalendar的实例,然后通过指定我们希望推断银行假日的日期范围来调用holidays()方法。
cal = USTradingHolidaysCalendar()
holidays = cal.holidays(start='2022-01-01', end='2022-12-31')
让我们验证一下我们的实现给出的结果:
print(holidays)*DatetimeIndex(['2022-01-17', '2022-02-21', '2022-04-15', '2022-05-30', '2022-06-20', '2022-07-04', '2022-09-05', '2022-11-24','2022-12-26'],
dtype='datetime64[ns]', freq=None)*
输出结果与纽交所市场官方网站上列出的银行节假日完美匹配。您应该已经注意到输出中不包括元旦,这仅仅是因为 2022 年 1 月 1 日是星期六,因此它是在前一年的 12 月 31 日庆祝的。
最后的想法
在今天的文章中,我们浏览了美国的纽约证券交易所市场银行假日,并展示了如何实现一个继承自 pandas AbstractHolidayCalendar类的非常简单的类,以便创建一个包含任何请求年份的所有银行假日的数据框架。
成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。
https://gmyrianthous.medium.com/membership
相关文章你可能也喜欢
[## 熊猫中的 loc 与 iloc
towardsdatascience.com](/loc-vs-iloc-in-pandas-92fc125ed8eb)
用例驱动的数据就绪性作为管理和控制数据质量的工具
基于数据就绪性的数据质量解决方案

迈克尔·泽兹奇在 Unsplash 上的照片
TL;DR: 数据质量应该通过其对特定消费用例的准备程度来查看。提出了一种数据准备方法,通过特定的用例建立特定的数据消费上下文来提高数据质量。可以测试特定用例的数据准备情况,就像如何使用各种测试用例场景测试软件用例一样。与数据字典类似,数据准备工件也必须编目。这不再是一个老生常谈的问题,即数据是否是最高质量的,而是什么用例的数据已准备好支持数据消费。
问题是
数据质量问题一直是一个被广泛讨论的热门话题。显然,数据质量不仅限于一个行业,而是所有行业普遍存在的问题。我可以很容易地想象每个数据官员都在思考这个问题,并最终急于解决它。总的模式是,每个人都认为其他人都在解决这个问题,所以在大多数情况下,他们急于解决这个问题,成功率低于平均水平。问题在于急于解决它。这就好像有人喝冷饮喝得太快,太早。
久经考验的软件质量实践有帮助吗?
虽然数据质量是一个令人苦恼的问题,但它不应被视为一个孤立的问题。如果它被视为一个独立的问题,就很难找到一个原因来解决它。可以想象,应该酝酿不同于现有方法的方法。也许我们可以从软件质量通常是如何解决的中得到启示。这种行之有效的做法可能会有所启发。软件质量主要是根据软件的消费就绪性来解决的——软件的功能和非功能就绪性。通常,用例或用户故事代表了软件的功能需求,并且通过验证软件是否能够满足这些需求来评估软件的准备情况。同样,诸如响应时间和请求处理吞吐量等用户需求定义了在软件上得到验证的非功能性特征。虽然不是每个软件都是完美的,但是这种准备性评估是确保更好质量的一种手段。实际上,功能性和非功能性用例驱动着软件的就绪性评估。因此,就绪性将通过提供一个消费上下文来推理软件的质量状态,人们将聚集在一起修正质量以提高就绪性。
类似的原理也适用于数据。一旦通过特定消费用例的镜头来观察数据,这种数据的消费就绪性将立即变得明显。糟糕的数据收集实践、缺失的数据、不可访问的存储机制、糟糕的语义和结构、缺乏标准、缺乏知识产权、管理缺失、所有权不明确以及有限的安全和隐私保护阻碍了数据的消费就绪性。这些阻碍数据准备就绪的问题也是数据质量差的明显迹象。实际上,数据就绪性表明在从数据中提取有洞察力的知识和情报以支持特定用例之前,数据的可用性、完整性、可靠性、可信赖性和有意义程度。
提议的方法
该提案旨在利用数据准备方法,通过特定的用例建立特定的数据消费环境,以提高数据质量。可以测试特定用例的数据准备情况,就像如何使用各种测试用例场景测试软件用例一样。这种方法还推广了诸如数据产品、数据作为功能和数据作为产品等概念——这些概念是数据网格爱好者所喜欢的。
然而,有一个警告。一个用例的数据就绪性可能无法满足另一个用例的需求。因此,逐步构建数据就绪性至关重要,涵盖给定业务领域中的所有潜在用例。当发现新的用例时,集体重新评估新的和现有用例的数据准备情况变得至关重要。这种重新评估类似于添加新功能或更新现有功能时对软件的回归测试。
针对特定用例的数据就绪性评估涵盖整个数据生命周期,解决从数据创建到数据准备、清理、转换、标准化、规范化和存储期间采用的数据处理操作的用例需求。这同样类似于如何端到端地测试软件,以确保路径中每个组件/子系统的就绪性,从而满足特定的用例需求。
编目数据准备信息
就像元数据(比如数据字典)被编目一样,数据准备工件也必须被编目。数据就绪性工件应该捕获支持的用例以及每个用例的就绪性评估结果。数据准备工件可以作为一个真实的信息源,突出所执行的数据质量操作,以及这些操作如何确保数据质量,满足特定的用例需求。数据准备工件将成为由相应的数据管理员控制的事实记录。这种信息可以减少重复的数据探索和分析,使数据审计变得容易,并增加数据的可靠性和可信度。本质上,这是实现数据管理的另一种方式。
最后的评论
实际上,这不再是一个老生常谈的问题,即数据是否是最高质量的,而是什么用例的数据已准备好支持数据消费。提议的数据就绪性驱动的方法改变了利益相关者之间的对话,并为如何查看数据和如何管理数据质量提供了一个新的视角。
参考
Afzal,s .,Rajmohan,c .,Kesarwani,m .,Mehta,s .,和 Patel,H. (2021)。数据准备报告。 2021 年 IEEE 智能数据服务国际会议(SMDS) ,42–51。https://doi.org/10.1109/SMDS53860.2021.00016
程,李,杨,高,郑,刘,谢(2017)。云数据治理成熟度模型。 2017 年第八届 IEEE 软件工程与服务科学国际会议(ICSESS) ,517–520。https://doi.org/10.1109/ICSESS.2017.8342968
新罕布什尔州劳伦斯市(2017 年)。数据准备水平。康奈尔大学 ArXiv 计算机科学数据库。https://doi.org/10.48550/arXiv.1705.02245
使用云形成模板在 RDS 上构建 MySQL 实例
云形成模板允许您自动将资源部署到 AWS 云。了解如何在 Amazon RDS 上部署 MySQL。

Rafael Garcin 在 Unsplash 上的照片
在本文中,我将讨论如何使用云形成模板在 AWS RDS 上设置 MySQL 实例。在我的上一篇文章如何为 MySQL 配置 Amazon RDS 环境中,我提供了如何在 Amazon 上设置 MySQL 实例的详细演示。您可以使用 AWS 控制台提供设置实例所需的所有信息,然后使用它。然而,在本文中,我们将讨论使用云形成模板实现相同功能的自动化方法。
什么是云形成模板?
为了开始编写模板,我们首先需要了解云的形成是怎么回事。AWS 将云形成作为云服务提供,使 AWS 客户能够将任何基础设施的所需状态编写为代码,然后使用它将资源部署到 AWS。它允许我们按照简单的配置模板创建一个资源或一组资源。例如,如果您想在一个 EC2 实例上部署一个 web 应用程序,该实例也将使用 RDS 数据库,那么您可以简单地将这两个资源合并到一个堆栈中,并编写设置实例的代码。一旦运行,它将创建 EC2 和 RDS 实例,并部署您的 web 应用程序。

图 AWS 控制台上的云形成模板
云形成模板的组成如下:
- 一个 JSON 或 YAML 文件,其中的资源将被定义为模板
- 堆栈可以是需要设置为应用程序一部分的多个资源的组合。例如,对于 EC2 和 RDS
- 变更集可用于查看堆栈要执行的操作列表
- 栈集也可以被认为是被复制或再现的栈的管理组
除此之外,我们将主要关注本文中的 JSON/YAML 模板和堆栈。这些模板支持 AWS 上的各种资源。请点击此链接获取详细列表。
为云的形成编写 JSON 或 YAML 模板
第一次编写模板时,我们面临的主要挑战之一是模板中应该包含什么内容。为了方便起见,AWS 提供了大量的模板样本,可以在官方文档中轻松找到。一旦找到创建必要资源所需的模板,就可以使用它来创建自己的模板。模板可以使用 JSON 或 YAML 创建,也可以从一个转换到另一个。就个人而言,我更喜欢用 YAML 编写模板,因为它可以帮助我远离花括号和逗号,让文档更整洁。然而,你可以在 JSON 和 YAML 之间自由选择。
在这个练习中,让我们试着在 RDS 上运行一个 MySQL 实例,我们将在 YAML 编写代码。为了使用云结构建立一个 MySQL 数据库,我们需要以下物品。
- 资源名称 —您要设置的资源的名称。
- 资源类型 —资源的类型。
- 资源属性 —要连接的数据库名称、用户名和密码。
- 数据库引擎 — MySQL、SQL Server、PostgreSQL 等。
- 存储类型 —要使用的存储类型。
- 公共可访问性 —数据库是否应该具有公共可访问性。
- 分配的内存 —应该分配给数据库实例的内存。
- AWS 区域 —将在其中创建实例的区域。
现在我们已经有了一些要点,让我们开始编写模板文件。

图 2 —在 RDS 上创建 MySQL 实例的模板文件
如上图所示,文件的结构分为两部分。在第一部分中,我们已经定义了一些参数,在 AWS 上创建资源时,第二部分中的资源定义将使用这些参数。您可以在您的机器上使用相同的脚本在您的 AWS 帐户上创建一个实例。
在控制台上设置堆栈
现在我们已经为 MySQL 创建了模板,是时候设置它了,看看是否一切运行良好。前往 AWS 控制台,在搜索栏上搜索云的形成。点击创建堆栈并从本地上传模板文件。完成后,单击下一步。

图 3 —从模板创建堆栈
在下一页,将要求您提供在云形成模板中定义的参数。继续提供必要的详细信息,然后单击 Next。

图 4 —提供的堆栈参数
查看堆栈更改和参数,并单击最后一页上的创建堆栈。将开始创建堆栈。可能需要一些时间来创建所有资源,然后才能使用它们。同时,您可以单击 Reload 按钮来检查是否所有事件都已成功创建。

图 5 —正在创建堆栈
正如您在时间戳上看到的,我花了大约 6 分钟的时间才获得资源,并且在控制台上也可以看到。导航到“Resources”选项卡,并单击资源的物理 ID。这将带您到 RDS 控制台。

图 6 —堆栈创建成功
连接 MySQL 数据库
一旦进入 RDS 控制台,就可以获得数据库实例的主机名,并使用它来连接数据库。从控制台抓取端点并使用它进行连接。

图 RDS 上的 MySQL 实例细节
现在让我们转到 MySQL Workbench,尝试使用我们之前提供的凭证连接到主机名。使用主机名、用户名和密码连接到实例。

图 8 —使用 MySQL 工作台连接到 MySQL 实例
如上图所示,我们已经成功连接到 RDS 上的数据库实例。现在让我们运行一些查询,看看它是否有效。

图 9 —在 MySQL 工作台中执行脚本
如上图所示,我们已经能够连接到数据库实例并执行查询。有了这个,您就可以编写自己的云形成模板,并启动任何其他资源,如 SQL Server 或 PostgreSQL 数据库。
移除资源
如果您将此作为练习,我建议您删除我们创建的资源,因为这会产生一些费用。您可以导航到 RDS 控制台,然后选择实例并单击“删除”来永久删除资源。

图 10 —删除创建的资源
成功删除资源后,您可以在控制台中再次检查是否有任何实例正在运行,以避免增加不必要的计费成本。
结论
在本文中,我们详细讨论了如何在 AWS RDS 环境中建立一个 MySQL 数据库实例,并使用 Cloud Formation 模板实现自动化。这样,在云上设置资源也被称为基础设施即代码服务,您可以将整个基础设施设置为代码,然后在各种环境中使用它。这些模板不仅可用于部署数据库实例,还可用于 AWS 中的其他基础设施,如 EC2、Lambda、RedShift 等。要了解更多关于云形成模板的信息,您可以查看官方网站上提供的样本模板。
从选择合适的调色板开始
原文:https://towardsdatascience.com/use-color-meaningfully-choose-the-proper-pallet-2cfbb71a30f4
有意义地使用颜色
调色板有哪些类型,如何选择合适的?

作者图片
在可视化数据时,颜色是一个强大的工具,但如果使用过度或不正确,它会毁了你的作品,并在你的观众中造成混乱。因此,我们应该深思熟虑地选择颜色,并牢记人类的感知是如何工作的。
在本文中,我将关注调色板类型以及如何选择正确的类型。
不同的调色板类型
我们有三种类型的调色板—(也称为定性的)、和发散的。****
在这种分类中,颜色不是决定性的方面,不同的调色板类型可以共享一些颜色。定义每个调色板的是这些颜色之间的关系。在分类调色板中,颜色应该能够很好地相互区分,并且它们不应该暗示任何顺序。另一方面,在顺序和发散调色板中,我们应该根据亮度、辉度或饱和度*对颜色进行排序。
如果这些术语对你来说是新的,请查看这篇精彩易懂的 色彩理论简介[1]。*
除了有序的颜色,发散的调色板在中间有一个中性点。通常,它是白色,浅灰色,或米色。但是在某些情况下,其他颜色也可能会起作用。如果你有一个彩色的背景,你可以用这个颜色作为你的中性点(即使这是一个黑暗的)。所以实际上,中性颜色取决于上下文,我们应该使用任何暗示“没有颜色”的颜色。
连续性方面是分类和非分类调色板之间的另一个区别(顺序和发散)。前者只存在于离散的变体中,而后者可以具有离散的(阶梯式)和连续的(平滑的)变体。
我们应该在什么时候使用每个变体?很简单,每次你有离散数据,你应该使用离散变量,如果你有连续数据…是的,你应该使用连续的。以测试结果为例。如果您想显示成绩,请使用连续调色板的离散版本,如果您想以百分比显示实际结果,请使用连续的连续版本。

调色板类型-作者绘制的图表
不同的规模类型
了解了调色板类型之后,让我们后退一步,关注数据。这一步至关重要。因为如果你想选择正确的调色板,你必须了解你要可视化什么样的数据。
有两种类型的数据——定量数据和定性数据。定量数据由年龄、温度或利润等数字表示。定性数据,也称为非数字数据,由名称或学校成绩等描述性类别组成。定性数据的另一个例子是满意度量表(例如,非常好—好—差—非常差)。
这两种数据类型可以进一步分割。在定性数据中,我们有一个名义标度和一个序数标度,而在定量数据中,有一个区间标度和一个比率标度【2】。
额定值
****主要特征:无自然顺序的非数值(定性)数据
举例: 性别,城市,家具
顺序量表
****主要特点:非数值(定性)数据具有自然顺序
举例: 李克特量表变体(如很好—好—差—很差),高—中—低
等距量表
****主要特点:数值(定量)数据不带任意 0
举例: 温度,日期
比例标尺
****主要特征:数值(定量)数据用任意的 0
举例: 持续时间、长度、重量、金额

标度类型—作者绘制的图表
****边注:任意 0 是什么意思?
还有音程和比音阶有什么区别?
我们有多种测量单位。值可以用不同的货币、各种单位系统(英制或公制)中的重量和长度、摄氏度或开尔文中的温度以及公历、伊斯兰历或希伯来历中的日期(仅举几个例子)来表示。那么,为什么温度和日期属于不同的类别呢?
这是由于它们的构造方式。以温度为例。摄氏温标是根据水的冰点和沸点创建的,而开尔文温标指的是绝对零度,即可观察到的最低温度。日历都是相似的,每一个都有不同的(任意选择的)起点。这个任意选择的零点是音程和比例音阶的主要区别。这使得计算比例变得不可能(我们不能说 20℃比 10℃热一倍)。
确定我们处理的是区间还是比例标度的最简单方法是检查零在所有单位系统中的意义是否相同。例如,零米与零英尺的意思相同,但 0℃与 0 开尔文不一样。
调色板和比例尺之间的关系
现在是时候把音阶和调板结合起来,关注它们之间的关系了。在选择调色板类型时,您需要记住一些简单的规则,但最重要的一条是:
分类尺度与分类调色板相匹配。我们应该对其他类型的数据使用序列,除非我们有一个有意义的中间点,在这种情况下,我们应该使用发散调色板。
听起来势不可挡?那么,让我们深入了解更多的细节。为了简化事情,我们将只专注于选择合适的类型,而不是颜色。
何时使用分类调色板?
提醒:在分类调色板中,颜色是对比鲜明的,不遵循任何顺序。
- ****用法:他们的目的是强调类别之间的差异,而不是它们之间的联系。
- ****最佳实践:限制颜色的数量,因为它们必须易于区分。如果你有 7-10 个以上的类别,重新考虑你的分组(改变类别或将一些值捆绑在一起)。
- ****搭配:标称刻度
何时使用连续调色板?
提醒:在连续调色板中,颜色从浅到深排序。我们可以根据一种颜色(单一色调)或多种颜色(多色调)创建调色板。在实践中,我们不应该使用两种以上的色调(为什么?检查上面彩虹色调的部分)。
- ****用法:它们的用途是通过改变亮度来显示秩序。
- ****最佳实践:通常,较低的值与较浅的颜色相关,较高的值与较深的颜色相关。然而,在深色背景上分配相反的值是很常见的。
- ****搭配:序数刻度、区间刻度、比例刻度
什么时候使用发散调色板?
提醒:发散调色板是由两个连续的调色板组合而成,中间是中性色。作为中性色,我们可以使用白色、米色、灰色或背景色。
- ****用法:显示与特定值的偏差,如平均值、0 或目标值。
- ****最佳做法:中心值一般赋给浅色,所以颜色越深,离中心越远。标尺的两端应该具有相似的亮度。
- ****搭配:序数刻度,区间刻度,比例刻度

调色板和比例尺的关系——作者的图表
典型错误
选择合适的调色板类型时会出现什么问题?可能是几件事。让我们讨论一下最常见的错误,这样你就可以避免它们。
1)对没有意义的 0 点的数据使用发散调色板
只有当你有一个有意义的中间点时,才应该使用发散调色板。如果是这种情况,应该将中间点分配给中性(中心)颜色。将其他值设置为中性色会产生误导。如果我们选择平均值作为中间点,我们应该让用户清楚(例如,通过添加注释)。

发散调色板中的中点——作者绘制的图表
2)使用不同的间隔
使用颜色背后的想法是让用户可以轻松地比较这些值。但是底线是我们必须有相等的间隔。否则,颜色的变化不会反映值的变化。

构建区间—作者的图表
3)发散调色板中的扭曲刻度
发散的颜色被设计成从中心点(0)开始平衡。可能发生的情况是,数据最大值和最小值离中心点的距离不相等。在这种情况下,应该通过“裁剪掉”颜色来调整比例。颜色强度应该反映离中心的绝对距离。

作者对发散标度图的正确构造
4)使用彩虹色调调色板
连续调色板应该从最高亮度到最低亮度[3]。在彩虹调色板中,我们有许多不同的色调,这导致亮度的突然跳跃。此外,亮度甚至不会在一致的方向上变化[4]。就像下面的例子,最暗的点落在一个随机的地方。

光度的差异——作者的图表
用于创建调色板的工具
幸运的是,有许多工具可以帮助我们创建一个好的调色板,所以我们不需要手动操作。我可以推荐两个方便直观的工具来创建你的调色板:
- 第一个是颜色生成器,它允许你快速找到一个预定义的调色板,最适合你的数据。一个额外的优势是色盲和打印友好的选择。
- 第二个是数据颜色选择器,让你对选择的颜色有更多的控制。您可以修改颜色,但仍然可以创建一致的调色板。
不想错过我的任何帖子? 直接把它们发到你的收件箱里!****
如果你还不是中等家庭的一员,考虑注册成为会员。它每月只需 5 美元,支持数千名作家。 注册我的会员链接 ,除了可以访问所有发表的内容,你将得到我永远的感激。
链接
[1] E. Kennedy,《HSB 色彩系统:从业者的初级读本,学习 UI 设计博客
[2] S. S .史蒂文斯,论测量的尺度 (1946),科学
[3] C. Shanley,混合等亮度颜色—第 1 部分,中等出版物
[4] R. Kosara,彩虹色地图如何误导 (2013),渴望之眼博客
***https://medium.datadriveninvestor.com/less-is-more-best-data-visualization-principle-c166e2797d60 [## 少即是多—最佳数据可视化原则
medium.datadriveninvestor.com](https://medium.datadriveninvestor.com/less-is-more-best-data-visualization-principle-c166e2797d60) https://medium.com/analytics-vidhya/how-to-make-your-audience-look-right-where-you-want-them-to-984df6d0338a ***
遗传算法和进化计算如何用于寻找旅行推销员问题的近似最优解
元启发式算法可以找到 NP 完全问题的近似最优解
去年,我在鲁汶大学学习了遗传算法和进化计算课程。本课程的评估完全基于 Python 中的一个编程项目,该项目的任务是为旅行推销员问题找到接近最优的解决方案。从概念上讲,给出了几个矩阵来表示某些城市之间的距离,在此之后,算法应该找到最短的解决方案。设计好的算法必须提交,然后在部门计算机上运行 5 分钟。

1)进化算法的设计
1.1)三个主要特征:
- 适应度共享已用于算法的淘汰步骤。这种多样性促进方案对于避免过早收敛至关重要,因此确保可以找到更好的解决方案,而不是让所有个体收敛到一个局部最小值。
- 通过引入 2-opt 局部搜索操作符,可以更快地找到更好的解决方案。没有局部搜索算子,需要更多的迭代来找到相同的适应值,以及必然更大的群体。尽管这种操作本质上计算量非常大,但它却是算法的关键。特别是在这个操作符中,诸如利用动态编程和使用 Numba 之类的优化对于使操作符在计算上可行是决定性的。
- 最后一个关键的改进是引入了贪婪的初始化和合法的初始化。贪婪初始化从一个随机节点开始,根据最小距离选择下一个。这种初始化方案的细节在第 4.4 节中详述,同时考虑了引入的偏差。此外,合法初始化只是从一个随机邻居中选择下一个节点,在它们之间有一条现有的道路。
1.2)主回路:

进化算法的主循环
1.3)代表权
可能的解决方案以排列表示,并以循环符号记下。例如,排列(1423)从 1 开始,然后到 4,然后 2,然后 3,最后返回 1。这种符号的一个优点是
,只要我们将表示初始化为排列,就不会出现循环。
这种表示在中实现为一个 Numpy 数组,其长度等于问题中城市的数量。数组中的每个元素代表一个城市号码。
1.4)初始化
最初,个体是通过随机排列产生的,它们的大小由距离矩阵决定。然而,特别是对于较大的问题,相当多的路径不存在或者非常长。因此,所有个体的随机初始化几乎总是产生没有一个代表有效路径的个体。
引入了两种新的初始化方案,合法初始化和贪婪初始化,其中合法初始化的目标是在初始化个体时不创建不存在的路径。这同时不会引入某些偏见,例如个人立即接管群体。另一方面,贪婪初始化的目标是以计算成本低廉的方式引入具有高适应度的局部最优个体。这里,已经特别注意到个体不会引入高偏差,也不会立即接管群体。
1.4.1)合法初始化
合法初始化个体时,要确保生成的路径存在。因此,随机选择一个城市,然后从所有具有非无限路径成本的邻居中随机选择旅程中的下一个城市。然而,如果没有现有的邻居可用作下一个城市,则整个过程重新开始。算法 1 中给出了伪算法。

算法 1:合法初始化
1.4.2)贪婪初始化
除了合法的初始化方案之外,还使用了另一种称为贪婪初始化的初始化方案。该算法类似于合法的初始化方案,唯一的变化是后继城市被选为最接近的邻居而不是随机的合法邻居。算法 2 中给出了伪算法。

算法 2:贪婪初始化
这个初始化方案确实引入了某些偏差,这可能导致一些个体立即接管群体。因此,必须采取措施防止这种偏见,例如通过仅用这种方案初始化所有个体的一小部分。
引入的偏差是所有个体都是局部最优的,其中理论上不同解的最大数量由问题大小给出。假设很难避开局部极小值,人们必须考虑这种初始化方案的有用性。
然而,在对这种初始化方案进行实验后,很明显,对于问题的好的解决方案会更快地被发现,同时仍然保持多样性的 T2,以及平滑收敛的 T4。由于只有一小部分个体用这种方案初始化,完全陷入局部极小值的情况没有被观察到,对我来说,这带来了巨大的好处,因为给定的五分钟现在可以用来以某种方式开始搜索空间中更有趣的区域。
1.4.3)概述
距离矩阵可以以贪婪初始化陷入无限循环的方式给出,因为贪婪初始化可能总是构造死路径(由于总是采取最近的邻居),从每个节点开始。为了在特殊情况下创建合法路径,有时应该采用次优路径到达邻居,以避免在接近初始化结束时出现死路径。为了防止整个算法崩溃,对一个个体的初始化引入了一个时间约束。一旦一个个体初始化时间超过两秒,该个体的简单随机初始化随之发生。
一个个体还被赋予一个随机α值,该值代表该个体在算法的变异步骤中发生变异的概率。这样,合适的变异率由自适应性决定。
α的初始值由下式给出:
α = max(0.04,0.20+0.08(X∞N(0,1)))
在对人口规模进行了一些测试后,选择了 15 人的规模。此外,如 1.4.2 节所述,只有一小部分人应该被贪婪地初始化。假设群体大小为 15,我发现贪婪地初始化 20%的个体(即 3 个个体)在实践中表现得相当好。剩下的 80%的个体是合法初始化的。
对于大型问题,初始化可能需要 10 秒钟。由于一个个体的初始化完全独立于其他个体,多处理被添加到这一步骤中,这使得具有四个物理核心(八个虚拟核心)的机器的速度提高了五倍。
1.5)选择运算符
小组赛部分的 k 赛选择符被保留。该选择算子在计算上是廉价的,因为只需要计算 k 个适应值,而这在基于适应值的方法中需要适应值。此外,由于贪婪初始化方案在群体中引入了一些非常好的个体,所以 sigma-scaled 选择例如不是合适的选择。这些个体将在这样的方案中占主导地位,因为他们的选择概率将非常高。
经过多次实验,选择了 k 值为 5。
1.6)变异算子
用于最终实现的变异算子是反转变异,由此选择一个随机子向量,并将其顺序反转。早先使用的交换变异操作符不能很好地适应更大的问题,因为它只能交换两个随机位置。因此,当问题规模增大时,变异算子对解决方案的影响相对更小。
倒位变异没有这个比例问题,因为决定子向量的城市是随机选择的。因此,变异操作符的效果随着问题规模的增大而保持不变。
自适应性已经被用于突变率,因此突变率对于每个个体是特定的。如前所述,它被初始化,并且如下面两个公式所述,它在交叉中改变:
β= 2(X∞N(0,1))—0.5
α = max(0.04,α_ parent _ 1+β(α_ parent _ 2α_ parent _ 1))
最后一点,精英主义被用来防止最好的种子个体变异。
1.7)重组运算符
最初,边缘交叉算子的简化版本被用作重组算子,其过程在算法 3【3】中描述。这种重组产生了一条新路径,在这条新路径中,子代的几乎所有边都存在于至少一个父代中。但是,它不会将双亲中存在的边优先于单个双亲中存在的边。

算法 3:简单的边缘重组算子
这种算法非常简单,是遗传算法中最薄弱的部分。然而,该算法尽管简单,但仍有一些可取的特征。双亲中存在的边传播给子代的概率相对较高,因此重要的特征大多被保留。另一方面,当父母差异很大时,孩子看起来会与父母完全不同。因此,这个操作符比其他操作符更倾向于探索端。
之所以采用这种简化算法,而不是 Eiben & Smith [1]的正确算法,是因为相信这种算法的计算成本比 Eiben & Smith 的算法(低得多)。
在项目后期,对 Eiben & Smith 的顺序交叉和适当的边交叉算法进行了分析。经过一些研究,在许多矛盾的建议下,已经做出了一个任意的选择,首先尝试'适当的'边缘交叉算法(算法 4)。

算法 4:“适当的”边缘重组算子[1]
就实现而言,已经做了相当多的努力来捕捉算法的所有极限情况,同时实现相对优化的代码。此后,该算法保持了很长时间,直到人们注意到,对于较大的问题规模,交叉花费了非常长的时间(高达 95%的总运行时间花费在边缘交叉算子上)。
由于这种缓慢的执行时间,订单交叉[1]也被实现(算法 5)。

算法 5:顺序交叉算子[1]
这种交叉算法的计算成本本来就低得多,在最终算法中只占总执行时间的 5%左右。这正是这个交叉算子最终被使用的原因。
事后看来,边交叉操作符执行时间慢的一个原因可能是由于在操作符中使用了集合。边缘表基本上是一个带有集合的列表,其中减号表示双重条目。使用集合是因为希望快速检查边表中是否存在边。然而,由于每个元素最多可以有四条边,所以列表可能就足够了。鉴于集合也需要相当多的簿记,使用列表还有一点可以说明(例如,删除第二次出现的正数条目,然后在前面插入一个减号)。
性能差距大的另一个原因是,order crossover 操作符能够使用 Numba 来编译 Python 代码,并通过使用 decorator @jit(nopython=True)来更快地运行它。这是因为 order crossover 操作符只使用 Numpy 数组上的操作(Numba 处理得很好),而 Numba 在 edge crossover 实现中抛出了数百个编译错误,因为 Numba(在nopython=True模式中)不能创建新的 Numpy 数组,在处理集合时有困难,并且大多数时候不能推断出dtype。
1.8)消除运算符
长期以来,算法中使用的是(κ+)-消去算子。然而,对于较小的问题规模,人们注意到群体收敛速度极快,即使存在健康促进方案(如 4.10 节中进一步讨论的)。经过一些研究之后,很明显(κ+)-消去算子实际上施加了相当大的选择压力。相反,k-锦标赛操作符可以减轻这种选择压力,因此,(κ+)-淘汰操作符已经被 k-锦标赛操作符取代。
把 k 赛运营和健身分享运营结合起来,分工。算法 6 用于 k-锦标赛操作符(以及一些预备计算),而算法 8 每次被调用用于健身共享多样性促进方案本身,如稍后在 1.10 节中解释的。

算法 6:消除[1]
经过多次试验,选择了 k 值为 8,这将在 1.12 节中进一步讨论。
1.9)本地搜索运算符
已经实现了 2-opt 局部搜索算子,其在给定的周期中交换每两个可能的边。在该算法的第一个版本中,为给定个体的每个可能的邻居重新计算适应度,这需要不可接受的高计算成本,特别是对于较大的问题规模。在一些调查之后,在适合度的计算中检测到模式。因此,不是为每个邻居重新计算适应度,而是采用了某种类型的动态编程方法。对每一个人来说,都有一个预处理步骤,由此产生所谓的“累积量”。这些累积量捕获从第一个城市到累积数组中相应城市的路径长度。相同的过程适用于从最后一个城市到数组中相应城市的路径长度的计算(即,以相反的顺序,由此最后一个城市到第一个城市的返回成本也被合并)。很明显,这些累积量的计算是在 O(N ) 中完成的,其中 N 是问题规模中城市的数量。
现在,计算个人的适合度只是一个记账的问题。该过程在算法 7 中解释。

算法 7:局部搜索操作符
如图 2 所示,旅程的第一部分与之前的迭代完全相同,从first — 1到first增加了一个额外的成本。同样的推理也适用于旅行的最后一部分,在这种情况下,总成本每次都会减少从second — 1到second的成本。最后,中间部分也可以以类似的方式构建。这样,2-opt 局部搜索算法的总开销仅为 O(N ) ,其中 N 表示城市总数。

图 2:本地搜索操作符(2-opt) [2]
还应该注意到,通过在方法声明上面使用 Numba 和命令@jit(nopython=True),本地搜索操作符运行的速度是的 745 倍。Numba 能够做出这些巨大的改进是因为这些方法的编译,尤其是循环可以被利用。
1.10)多样性促进机制
所使用的多样性促进方案是适应度共享消除,其改变已经选择的幸存者的σ-邻域中的个体的适应度。在算法 8 中解释了适应度共享消除算子。

算法八:健身分享算法
子方法“从到的距离”计算两个人之间的距离,通过两个人之间的公共边的数量来测量。为了有效地做到这一点,在初始化时,每个个体的边被计算并存储在一个集合中。为了测量两个人之间的距离,计算集合之间的交集。
一遍又一遍地计算许多单个配对的交集,结果证明计算量相当大。一个改进是将所有计算出的距离存储在一个散列表中,这是一个不错的改进,因为相当多的个体会在群体中停留不止一次迭代。如果系统的内存使用率超过 95%,通过简单地清空散列表来预防系统颠簸。
1.11)停止标准
在实现停止标准方面并没有投入太多的精力,因为所有较大的问题在运行五分钟后仍然会收敛。假设即使最佳适应度保持很长时间不变,由于良好选择的变异/交叉操作,算法突然可以进行得更远。因此,停止标准就是五分钟的时限。
1.12)参数选择
种群和后代规模很大程度上取决于算法的计算成本。对于大型问题,每次迭代的最大计算成本是适应度共享消除步骤。这是因为具有所有个体和幸存者之间的公共边的数量的矩阵平方增长,并且该矩阵中的一个条目的计算也线性增长。因此,如果群体中有 15 个以上的个体(以及 15 个以上的后代),计算成本就会变得太大。少于 15 个个体自然也是不可取的,因为进化算法依赖于拥有一些不同的个体。
k-锦标赛参数由超参数搜索确定,其中已经尝试了范围从 2 到 10 的值。鉴于这两个参数高度相关,需要进行网格搜索或随机搜索。为了使超参数搜索可行,对这些值进行了随机搜索,这产生了等于 5 的选择的 k-锦标赛值和等于 8 的淘汰值。
当打印出带有公共边数的矩阵时,很明显,许多条目要么是最大问题尺寸,要么是零。因此,经过一些实验,σ值为问题大小的 50%,α值为 0.25。在我看来,这个低 alpha 值也更好,因为真正“接近”的解决方案应该比仍然有一些不同边缘的解决方案受到更多的惩罚。
作为总结,使用了以下超参数:
- 人口规模= 15
- 后代大小= 15
- 选择操作符的 k-锦标赛参数= 5
- 消除运算符的 k-锦标赛参数=
- 健身共享的α值= 0.25
- 健身共享的σ值=问题大小的一半
1.13)其他考虑
正如 4.10 节所讨论的,通过在散列表中存储个体之间的距离,获得了相当大的加速。出于同样的原因,引入了一个 hashmap 来存储所有个体的健康值。因为在每次迭代中,都知道从群体中删除了哪些个体(如果应用了变异,局部搜索会产生一个新的个体,或者在淘汰步骤中杀死了一些个体),所以也可以很容易地从散列表中删除它们的值。通过这种方式,hashmap 的大小始终保持不变,这更有利于提高性能,因为在内存级别超过阈值后不会重新启动,也不会浪费垃圾收集器需要启动的时间。
2)数值实验
2.1)元数据
这些实验在英特尔酷睿 i7–6700 HQ CPU 上进行,时钟频率为 3.60GHz,有 8 个虚拟内核。该系统包含 16 GB 的主内存,测试使用的是 Python 版本。第 1.12 节总结了所用的超参数。
所有的收敛图都是半对数绘制的,因为算法开始时的变化比接近结束时的变化大得多。
2.2)游览 29 个城市
找到的最佳旅游的适合度为 27154.5。

图 3:在第 29 轮 1000 次跑步后,最佳和平均体能的直方图
如图 3 所示,每次都找到了(可能是最佳的)适应值 27154.5。此外,平均适应度由于多样性提升方案而具有一些变化。它永远不等于最佳适应值,这意味着多样性被永远保留。注意,对于这个实验,每次运行的时间限制为 10 秒,以使计算可行。
平均拟合度的一千次运行的平均值等于 33551.6,标准偏差的值为 657.6。
从图 4a 的收敛图可以看出(见 2.6 节),最佳适应度的收敛发生得非常快。然而,平均适应度并不收敛,这意味着由于适应度提升消除方案,人口保持多样化。
2.3)100 个城市之旅
找到的最佳旅游的适合度为 219948.4。
收敛图如图 4b 所示(见第 2.6 节)。该算法显然表现很好,通过快速跳到搜索空间中最感兴趣的区域,之后最佳适应度保持收敛,直到超过五分钟的时间限制。从平均适应度可以看出,群体保持多样化,因此有能力不断探索更好的解决方案。
对于本次巡视以及接下来的巡视,很难估计该解决方案与最佳解决方案的接近程度,因为只知道一个启发式值,而该算法明显优于该值。
2.4)游览 500 个城市
找到的最佳旅游的适合度为 110814.2。
收敛图如图 4c 所示(见第 2.6 节)。通过再次跳转到最感兴趣的搜索区域,结合平滑的收敛,该算法显然再次表现出色。此外,人口保持多样化。
2.5)1000 个城市之旅
找到的最佳旅游有 194955.2 的适合度。
最佳健身再次保持收敛,直到超过五分钟的时间限制。再一次可以从群体保持多样性的平均适合度中推导出来。
2.6)收敛图

图 4:基准的收敛图(半对数)
3)批判性反思
3.1)进化算法的三大优势是什么?
- 通过使用基于群体的元启发式算法,可以在探索和开发之间进行权衡。这种折衷被证明有助于为旅行推销员问题找到好的解决方案。纯粹的随机搜索在计算上太昂贵了(在找到一个“体面的”、简单的贪婪的解决方案之前,它甚至需要一个无穷大)。另一方面,纯粹的局部优化器会很快收敛到一个次优解,这个次优解也很可能离最优解相对较远。进化算法提供了一种在相当长的时间内找到好的次优解的方法。
- 原则上,进化算法相对容易并行化,因为群体可以在许多方向上同时探索搜索空间。虽然在这个项目中,没有使用很多并行化(仅在初始化步骤中),但它可能是下一个最有益的任务。已经开始尝试使用多处理,但是与在一个 CPU 内核上简单的顺序执行相比,使用多处理时的执行时间要慢一千多倍。这可能是由于大量的进程间通信(IPC)造成的,因为例如个人列表是共享的,这导致了大量的锁和信号量,明显地控制了执行时间。
- 关于进化算法,最后一件让我印象深刻的事情是,它们本质上优化了一个函数而没有使用导数(仅通过使用适应值,暂时抛开局部搜索算子)。特别是对于不可微的函数,进化算法可能提供拯救。
3.2)进化算法的三个主要弱点是什么?
- 进化算法效果很好,只要是精心设计合理的参数。例如,以 100 而不是 15 为人口规模,算法很难进行一些迭代。这当然是由于适应度共享消除,它在种群(和后代)规模方面具有立方复杂性。此外,必须选择许多其他参数来获得合理的性能。例如,如果 k-锦标赛参数(来自选择和淘汰)没有被显著选择,要么群体立即收敛,要么个体的总体适合度几乎没有增加。最后,这并不令人惊讶,因为根据“没有免费的午餐”定理,不可能存在任何算法可以解决所有问题,并且通常优于任何竞争者。
- 这个项目中使用的表示法在杂交后明显产生了有价值的后代。如果对这种表示进行推理,很明显,双亲的子向量可能是好的子序列。当进行交叉时,这些特征现在可以以更优化的方式组合,因此后代显然也有相对较高的机会具有较低的适应值。然而,对于许多其他问题,这种表示和交叉算子不是那么清楚(即,不能从父代中提取特征),因此进化算法不能为这些问题提供很多。
- 进化算法在计算上非常昂贵。由于进行了大量的探索,在寻找的过程中,自然会构建出许多无意义的个体。
3.3)从本项目中吸取的主要教训。进化算法适用于旅行推销员问题吗?
我认为旅行推销员问题非常适合旅行推销员问题。找到一个精确的解决方案当然是不可行的,因此元启发式的使用变得很有必要,并且它们证明了自己非常有效。考虑到许多现实世界的应用程序需要最佳解的良好近似,很明显这些元启发式算法(尤其是进化算法)是一个必须拥有的工具。
此外,我学会了欣赏像 Numba 这样的库的价值,它们可以通过编译代码来大幅提高 Python 代码的速度。当然,并不是所有的操作都被允许,但是考虑到 Numba 社区正在快速发展,我会继续关注它。
进化算法的一个缺点是你必须测试某些改进的方式。由于进化算法固有的随机性,在不同的运行之间可以观察到相当多的变化。当然,如果超参数的改进仅产生相对较小的差异,则在最佳参数变得明显之前,必须重复多次实验。
参考
[1] A.E .艾本和 J.E .史密斯。进化计算导论。斯普林格国际出版公司,第 2 版。
【2】尼克·范尼文霍芬。本地搜索运营商。技术报告,2021 年。
[3]达雷尔·惠特利,蒂莫西·斯塔克韦瑟,丹尼尔·尚纳。旅行推销员和序列调度:使用遗传边重组的高质量解决方案。第十八页。
这个项目的代码可以在这里找到。
除非另有说明,所有图片均由作者创作。
如果你还有其他问题,或者你想保持联系,你可以通过 Github 或者 Linkedin 联系我。
在树叶地图中使用 HTML:数据科学家综合指南
逐步了解如何使用 HTML 将动态图像、链接、文本和表格添加到叶子地图弹出窗口

图片来源: Pixabay
目录:
简介
第一部分:HTML 基础知识
∘ 什么是 HTML?
∘ 标签和属性
∘ 一个 HTML 页面的简单构造
∘ 如何在 HTML 中添加文本、链接、图片和表格
第二部分:使用 HTML 创建定制的叶子弹出菜单
∘ 导入库并读取数据
∘ 创建基本叶子地图
∘ 为每个标记创建 HTML 页面
∘ 将 HTML 传递给叶子地图标记
关闭思路
引用和数据源
介绍
你可能会想:作为一名数据科学家,而不是 web 开发人员,为什么我需要学习和理解 HTML?投入宝贵的时间去学习 it 的基础知识对我有好处吗?我会不会遇到需要在工作中使用它的情况,并为花时间学习它而感到高兴?
直到几个月前我遇到了我的第一个地理空间项目,我也有同样的疑问和怀疑。在那个项目中,我需要将 HTML 作为标记传递给一个叶子地图,并努力使它看起来和工作起来符合我的要求。我在网上做了大量研究,但找不到一个好的资源页面,将核心概念和代码放在一个地方,并用数据科学上下文详细解释它们。
这就是这篇文章背后的动机。在这篇文章中,我想首先从一个完全初学者的角度带你了解 HTML 的核心概念。然后我们将运用这些知识,学习如何通过添加标题、图片、链接和表格来构建一个 HTML 网页。最后,我们将 HTML 传递给 follow map markers,并将其添加到弹出窗口中,如下面的屏幕截图所示。

作者图片
第一部分:HTML 基础知识
在我们动手使用上图中显示的弹出窗口创建一个叶子地图之前,让我们先花些时间学习 HTML 的基础知识。这样做,你会更容易理解第二部分写的代码,并能在将来有效地使用它们。所以如果你对 HTML 完全陌生,请不要跳过这一节。
什么是 HTML?
HTML 是一种广泛用于创建网页的语言。你可能也听说过 CSS。CSS 不同于 HTML。CSS 是用来帮助 HTML 使网页在视觉上吸引人的代码。在这篇文章中,我们将主要关注 HTML。
下面是来自维基百科的 HTML 的简短描述:
超文本标记语言,简称 HTML,是设计用于在网络浏览器中显示的文档的标准标记语言。它可以通过级联样式表(CSS)和 JavaScript 等脚本语言等技术来辅助。
标签和属性
HTML 是一种标记语言,计算机使用它来解释和控制文本、图像或其他对象如何在 web 浏览器中处理和显示。为此,HTML 使用了标签和属性——你需要理解的两个最重要的概念是 HTML 的基础。
什么是标签?
HTML 元素是 HTML 页面的构建块,标签用于标记 HTML 元素的开始。
标签写在尖括号中,绝大多数是成对的,如下例所示。它有一个开始标签和一个结束标签以及一些元素信息,比如放在标签之间的标题。但是也有一些例外:例如,标签是不成对的,没有结束标签。标签也可以嵌套在标签中。

标签示例(图片来源:维基百科)
什么是属性?
属性为标签提供附加信息,只放在开始标签中。看看下面的例子,我们在其中创建了一个图像标签。在标签中,图像源(src)、替代文本(alt)、宽度和高度都是这个标签的属性。

作者图片
HTML 页面的简单构造
下图展示了一个简单的 HTML 页面结构。
HTML 文档需要以使用标签的文档类型声明开始,它指定了我们将在页面上书写的语言(HTML 5)。
标签指出了我们在 HTML 代码中开始写的地方,并被用作整个文档中所有 HTML 的容器。
标签是元数据(关于数据的数据)如文档标题等的容器。
标签包含了网页的内容。

作者图片
如何在 HTML 中添加文本、链接、图像和表格
在 HTML 中添加文本:使用标签
添加文本或段落。

在 HTML 中添加链接:使用标签添加链接

在 HTML 中添加图片:使用标签来添加图片

在 HTML 中添加表格:
与添加文本、链接或图像相比,在 HTML 中添加表格在语法上稍微复杂一些。我们使用标签创建一个表格,在标签内部,我们使用指定行数,使用
| 指定列(单元格)。 |

作者图片
第二部分:使用 HTML 创建定制的叶子弹出窗口
现在我们已经学习了 HTML 的基础知识,我们准备在现实世界的用例中应用我们刚刚学到的知识。假设我们想要建立一个大学选择工具。在该工具中,我们希望创建一个交互式地图,并将美国大学的标记添加到地图中。当用户单击一个标记时,会出现一个弹出窗口,在一个表格中显示大学的徽标图像、网站 URL 和一些附加信息。
导入库和读取数据
让我们首先导入所有必要的库并将数据读入 pandas 数据框。为了便于演示,我从可以从DATA.ED.GOV(美国教育部)下载的原始数据集创建了一个非常小的样本数据集,其中仅包括 5 所大学。这是一个开放的数据集,不需要许可即可使用。我还将所有图像文件保存在我的 python 代码文件的同一个文件夹中。

作者图片
在数据中,对于每个大学,我们都有一个 URL、一个图像、地理信息(如纬度和经度)以及附加信息(如州、公立或私立、学费、录取率等)。
创建一个基本的树叶地图
创建一个基本的叶子地图很简单。我们首先启动一个空地图,然后遍历数据框中的行,根据每个大学的纬度和经度向地图添加标记。在叶子里。Marker()函数我们可以指定在弹出窗口中显示什么——在本例中,是每所大学的机构名称。

一个非常基本的叶子地图(图片由作者提供)
为每个标记创建一个 HTML 页面
上面的地图非常简单。让我们看看如何使用 HTML 在弹出窗口中包含更多信息。为此,我们需要首先定义一个函数,为每个标记创建一个 HTML 页面。每个标记的 HTML 页面应该显示大学的徽标、网站链接和一个包含大学附加信息的表格。
让我们详细检查一下上面的代码。既然你已经在第一部分中学习了 HTML 的所有基础知识,我希望你会发现这些代码非常容易理解!
第 18 行:我们使用以文档类型声明开始 HTML 文档。这指定了我们将在页面上书写的语言——在本例中,是 HTML 5。
第 19 行:标记表示从这一行开始,我们将使用 HTML 代码。
第 21 行:我们使用标签插入每所大学的徽标图像。放置在标签周围的
标签确保图像放置在页面的中央。我们还在标记中包含 src、alt、width 和 height 等属性。
由于每所大学都有自己的徽标图像,我们需要在 for 循环中创建一个名为‘institution _ img’(第 5 行)的变量,以相应地获取每所大学的徽标。在< img >标签中,我们还需要使用‘institution _ img’作为 src 属性的值。
为了让 src 属性接受' institution_img '变量,我们不能只使用 src=institution_img。该变量需要用以下格式包装:

作者图片
第 23 和 25 行:我们创建一个显示每个机构名称的标题。在标题下,我们插入每个大学网站的链接。
第 27–64 行:我们使用标签创建一个表格,并在开始标签中用 style 属性指定表格大小。从第 29 行到第 62 行,我们使用和
| 标签绘制了一个 7 行 2 列的表格。在这些标记中,我们还使用 style 属性来指定背景颜色、单元格大小、字体颜色等。 |

作者图片
将 HTML 传递给树叶地图标记
我们现在可以将 HTML 传递给 leav 地图标记。在下面的代码中,请注意我们创建了一个新的变量“color ”,这样我们可以将它传递给 marker,并使公立和私立大学的图标颜色看起来不同。
从第 12 行到第 15 行,我们首先使用上一步中定义的 popup_html 函数为每个标记创建一个 HTML。然后,我们使用 leav 将每个 HTML 页面插入到其对应的标记弹出窗口中。Popup()方法。最后,我们将弹出窗口传递给叶子标记,并将标记添加到我们的地图中。这就对了。您已经创建了一个带有自定义弹出窗口的漂亮的叶子地图,所有这些都是使用 HTML 完成的!

作者图片
结束语
Folium 是一个用于绘制地图的出色的 Python 库,因为它可以将丰富的 HTML 可视化作为标记传递到地图上,所以它提供了无限的可能性,使您的地图更具交互性和信息量。
对于那些不熟悉 HTML 的人,我希望这篇文章有助于理解如何使用它来提升自己的 feil 技能。学习和理解 HTML 对于我们这些数据科学家来说是非常有益的,尤其是在处理叶子的时候。也许下一次我们可以尝试创建一些交互式 Plotly 图表,并以 HTML 格式将它们传递给叶子弹出窗口!
参考和数据源
- 简单的方法:今天就开始学习 HTML 和 CSS(https://html.com/)
- 大卫·贝克的《奇妙的叶子:https://www.kaggle.com/dabaker/fancy-folium
- 数据来源:美国教育部大学记分卡开放数据(https://data . ed . gov/dataset/College-score card-all-data-files-through-6-2020)。使用这些数据不需要许可证。
你可以通过这个推荐链接注册 Medium 会员(每月 5 美元)来获得我的作品和 Medium 的其他内容。通过这个链接注册,我将收到你的一部分会员费,不需要你额外付费。谢谢大家!
使用 itertools.groupby()计算 Python 中连续出现的次数
比如最长的热浪持续了几天?

在 Unsplash 上由 Ridham Nagralawala 拍摄的照片
语境决定一切。这为什么有用?
作为一名分析师,您可能希望探究诸如连续性等细微差别,以了解根本原因或移除数据中的异常值。
激发这篇文章的主题是天气。2022 年,俄勒冈州波特兰的气温与前一年世界其他地区甚至波特兰的许多创纪录热浪相比是温和的。
然而,尽管与 2021 年创纪录的高温相比,2022 年可能是温和的,但这一年似乎很热。也许连续几天的炎热天气影响了我的感知?让我们用 Python 来统计热天条纹(也称为热浪)来分析这个假设。
美国的天气数据
在之前的一篇文章中,我写了关于来自国家环境信息中心(NCEI)的公开可用数据来源。请阅读该文章,了解有关如何访问数据的更多详细信息,并访问 NCEI 了解更多有关 NCEI 和国家环境卫星、数据和信息服务(NESDIS)任务的信息。
在这篇文章中,重点将是 TMAX,在特定气象站观测到的最高日温度。俄勒冈州波特兰市的数据和我的探索性数据分析可在 GitHub 上获得。
以下数据框是为此分析而创建的,包含真/假布尔型字段,用于指示温度是至少 80 度、至少 90 度还是至少 100 华氏度。

一个数据框,包含按日期和年份划分的温度阈值的真/假布尔字段(图片由作者提供)
连续几天气温都在华氏 90 度以上
计算连续发生的次数并不像你想象的那么简单。需要进行多次计算才能得出解决方案,开发一种能够处理多年数据的算法并不容易。
然而,有一些软件包可以减少为这个算法编写的函数的数量。Python 的itertools.groupby()包是我实现这一分析的最简单的方法。
首先,从itertools包中导入groupby()函数。
**from** itertools **import** groupby
接下来,定义一个函数,该函数将一年的数据点列表作为参数,然后返回一个列表。
这个返回列表中的每个元素都包含了那一年每一次热浪的持续时间。例如,如果我们为热设定的阈值是 90 华氏度,那么对于给定的年份,具有四个元素如[1,5,1,2]的列表将意味着该年有四次热浪。第一波有一天最高温度达到或超过 90 度。第二波包括连续 5 天超过 90 度。诸如此类。
- 第一波有一天最高温度达到或超过 90 度。第二波包括连续 5 天超过 90 度。诸如此类。
然后,编写另一个函数来执行以下操作:
- 使用上面定义的函数确定每年最长的热浪
- 在数据框中循环遍历每一年
- 返回每年最长 90 度热浪的数据框(以天为单位)
为了获得比较,为 80 度和 100 度编写了类似的函数。
结果分析
在回顾每年最长的热浪之前,我们先来考察一下每年的总体高温天数。

截至 2022 年的年度趋势,这是该视图中的最后一年,位于俄勒冈州波特兰市(图片由作者提供)
确实 2021 年是热!80 度或以上的天气有 90 天,是有记录以来最多的。100 度和 90 度的天气也是今年有记录以来最热的一年。
在这篇文章的原始版本写于夏末之后,波特兰实际上在 10 月有破纪录的 12 个 80 度的天。否则,2021 年大约会有 20 多个 80 度的日子。

过去 5 年中超过热阈值的总天数(图片由作者提供)
下一张图表显示了 2022 年最长的热浪。

2022 年前最长的热浪(图片由作者提供)
对于 80 度以上
- 有记录以来最长的热浪(21 天)发生在 1985 年。
- 2022 以 10 天排名第 27
- 2021 以 13 天排在第 11 位
对于 90 度以上
- 有记录以来最长的热浪(8 天)发生在 2009 年
- 2022 以 7 天排名第二
- 2021 以 6 天排第 5
对于 100 度以上
- 有记录以来最长的热浪(4 天)发生在 1941 年和 1981 年
- 2022 以 2 天排名第 7
- 2021 以 3 天排名第四
结论:我的假设是错误的
虽然这一分析没有包括统计意义上的实验设计,但两年热浪的总结已经表明,最近的记忆正在偏见我对 2022 年炎热夏天的看法。

2022 年到 2021 年最长的热浪(图片由作者提供)
我发现了几个有趣的趋势:
- 与其他最热的年份相比,2021 年有更多的冷却期。这就是为什么总的高温日数没有导致有记录以来最长的热浪。
- 连续的高温天气似乎是周期性的。最后一年(2022 年)处于周期的低端。
- 每年高温天气的天数有增加的趋势,但热浪的趋势不太明显。
最后更新 2022-12 月:添加了文章图片,更新了 9 月和 10 月数据的图表,并为可读性做了一些小编辑。
如果你喜欢这篇文章,并想阅读(和写)更多这样的文章,请考虑点击我的个人资料图片旁边的关注按钮和/或使用我的推荐链接https://sabolch-horvat.medium.com/membership订阅中等会员
使用度量来确定 LDA 主题模型大小
原文:https://towardsdatascience.com/use-metrics-to-determine-lda-topic-model-size-1a1feaa1ff3c
一篇超过 3 分钟的主题建模文章

马特·布里内在 Unsplash 上拍摄的照片
opic 建模是自动发现正文中语义上有意义的主题。主题模型产生类别,以单词列表的形式表示,可以用来将文本划分成有用的组。目前用于主题建模的最常见算法是潜在狄利克雷分配( Blei et al. 2003 )。
有大量的文章、博客帖子、教程和视频涵盖了 LDA 主题建模的基础知识。他们详细描述了语料库的准备、模型的创建,并经常以在 pyLDAvis 中可视化模型结束。这篇文章不是 LDA 基础知识的入门读物,事实上假设读者已经熟悉了这些步骤。本文没有回到这些老生常谈的话题,而是集中在这个过程中一个关键的、较少讨论的部分,使用度量来帮助为一个模型选择正确数量的话题。
读者被警告说,即使在这篇长文中,也只能触及这个主题的表面。本文只讨论了如何使用度量来指导选择最佳主题数量的过程。正如读者将在下面的文章中看到的,在这个过程的某个时刻,度量不再提供足够的信息来评估主题模型。本文在任务完全完成之前就结束了,此时自动化的度量标准必须让位于更细粒度、自动化程度更低的流程,这些流程用于评估主题列表本身的语义内容以及它们与被建模文本的关系。尽管有这些警告,还是希望认真的从业者能从读完它中受益。
虽然这不是一个教程,但是本文中涉及的每一步的数据和代码都在 Kaggle 和 GitHub 上发布和提供。有一款 Jupyter 笔记本将在 Google Colab 运行。笔记本以及配置和实现细节可以在 GitHub 库中找到。本文使用的数据集是一个随机选择的, 30,000 篇文章的子集,它是一个更大的公开许可的数据集,称为新闻文章。
度量不能解决你的问题
(但它们会让你的生活更轻松)
LDA 模型的一个关键特征是开发人员必须选择要建模的主题数量,以获得最佳结果。然而,关于如何确定这一价值的文章却很少。原因之一是,答案永远是:视情况而定。给定模型的最佳主题大小的选择将与文本(语料库)本身的特征以及模型的用途相关。被建模的文档是长的还是短的?它们包含许多不同的想法、主题和主题吗?或者,语料库中的文档是关于一个特定的、小的主题集的精心准备的学术论文摘要吗?也许这些文件是随机的推文,没有特别统一的韵律或原因?
本文将带领读者了解使用行业标准度量来确定少量候选主题大小以供进一步评估的过程。然而,正如将要展示的,这些度量标准不能代替人类的判断。最终,建模者将不得不应用主观标准。他们必须深入了解主题与文本的契合程度,以决定使用哪种模式。虽然这个耗时的任务是不可避免的,但是使用度量来减少任务的大小是非常值得的。
工具包
本文的其余部分将关注度量标准的实际应用,以便为一个 30,000 篇文章的新闻剪辑数据库确定一组合理的候选主题大小。目标是建立一组主题,既反映文档中主题的多样性,又理想地将这些主题限制在一般读者可能认为重要或有意义的主题范围内。
数据集
所提供的语料库数据集既有原始新闻文章,也有适合 LDA 主题模型处理的经过处理、词条化和删减的文本版本。代码是为希望自己生成这个输出(或者尝试不同的文本准备策略)的读者提供的。
除了核心数据集之外,还提供了由数据集的 90 多次运行产生的指标的副本。最后,在最初创建数据之后,还引入了一个补充的停用词表。
Google Colab
虽然本文使用了“pro”帐户,但一切都是在标准模式下运行的,没有利用额外的 GPU 处理器或内存。
Gensim
自始至终使用了 Gensim LDA 模型实现。
OCTIS
优化比较话题模型用于其广泛收集话题模型评估方案。OCTIS 为模型构建和评估提供了一个端到端的环境,包括一个可视化界面。本文只使用了 OCTIS Gensim LDA 包装器和 OCTIS 指标。
kneed
Python 中的拐点检测用于识别各种度量结果中的“最大点”曲率,从而选择特定的模型构建作为候选模型。
阴谋地
来制作图表。
评估模型
评估了两个常用的衔接指标:NPMI 和 CV(Lau 等人,2014 年和roder 等人,2015 年)。此外,还描述了一系列衡量主题多样性和相似性的指标。以前的内聚度量在计算上是昂贵的。这里运行两个常见的变量来确定它们是否产生不同的结果。主题度量——分析模型的主题输出,而不是其内部结构,比基于 PMI 的内聚度量要便宜得多。这三个多样性指标是:主题多样性( Dieng 等人 2019 )、基于倒排的重叠( Webber 等人 2010 )和 Kullback-Liebler 散度。还运行相似性度量:成对 Jaccard 相似性和三个主题显著性度量:KL 统一、KL 空泡和 KL 背景( AlSumait 等人,2009 年)。
重要的是要记住 LDA 模型是随机的。这意味着模型的每次运行都会产生不同的结果。因此,在本次评估中,在每个选定的主题规模下运行了多个模型及其完整的度量套件。总共进行了 90 次实验。LDA 主题模型被创建用于主题数量大小为 5 到 150,增量为 5 (5,10,15…150).每次运行都捕获了所有九个指标。所有 90 次运行的指标都绘制在这里:

图片作者。
对每个主题模型尺寸的三次运行进行平均,得到:

图片作者。
不幸的是,解释他们的结果并不简单。NPMI 和 CV 产生了一个最佳结果,但是大多数其他的似乎继续改进超过了选择的样本大小。在继续从机器学习的其他领域借用的直觉之前,确定这些图表的膝盖或肘部将会有所帮助。幸运的是有一个包可以做到这一点: kneed
kneed 的输出揭示了六种候选主题大小:5、10、20、35、50 和 80。
Jaccard Similarity: 5
Topic Diversity: 10
NPMI: 20
C_V: 20
Inverted RBO: 35
KLBac: 50
KLDiv: 50
KLVac: 50
KLUni: 80
六个很多,但比三十个好。在某些时候,有必要对每个主题进行抽样,并将其与一组文档进行比较,以判断是否合适。这六个模型代表了两百个主题。如果我们只对每个主题的 10 个文档进行抽样,这将意味着对 2000 个文档进行审查、评级和比较。如果能进一步缩小候选人名单就更好了。主题分布的概况可能指向可以不考虑的模型。
似然分布
因为主题是跨整个语料库创建的,所以文档将包含多个主题。LDA 模型计算给定文档中存在一组主题的可能性。例如,一个文档可能被评估为包含十几个主题,没有一个主题的可能性超过 10%。另一个文档可能与四个主题相关联。在这四个主题中,有一个主题可能被评估为 90%的可能性,剩下的 10%分布在其余三个主题中。这种现实会使手头的工作变得复杂,产生大量需要评估的变体(每个文档不同主题的混合)。
为了简化这个问题,有可能应用另一个被广泛接受的直觉——占主导地位的话题。主导主题是最有可能出现在文档中的主题。例如,假设一个文档的可能性分布为 62,33,4,1。主导主题是具有最大可能贡献的主题,在本例中为 62%。当我们绘制一个主题成为我们的数据集生成的文档的主导主题的次数时:

图片作者。
请注意,在较大的模型中,最高代表值和最低代表值之间的比率变化很大。在五主题模型中,最主要的主题出现在 28%的文档中,最不主要的主题出现在 15%的文档中。在 80 个主题的模型中,范围是从 7%到 0.001%。在较小的模型中,即使是最少出现的主题也可以在数百篇文章中找到。在较大的模型中,长尾包含非常少量的文档。这意味着即使这些主题是“好的”,它们也只代表整个文档的很小一部分,相对于我们声明的对文档进行良好的总体分块的目标来说,它们很可能只是噪音。测量模型如何在主题内分发文档的进一步剖析可能揭示重要信息。
上图显示,在一个给定的模型中,有一些主题在许多文档中占主导地位,而在其他文档中则相对较少。但是它并没有揭示一个给定的主题对于它所有的主题有多重要。例如,在一个文档中占主导地位 20%的主题与在另一个文档中占主导地位 80%的主题被视为相同。幸运的是,描述给定主题内的分布是很简单的:

图片作者。
请注意,在五个和十个主题模型中,每个主题的上限和下限大约在 20%和 100%之间。在每个后续型号中,此窗口会下移。还要注意的是,在两个较小的模型中,中值大约在 60%以上,统计异常值的数量非常少。
在这个层次的分析中,数字表明小型模型确实非常强大。他们总体上是统计上有信心的,每个主题的平均信心都超过 50%,信心得分的低端比所有其他模型都好。较大的型号就不是这样了。随着主题大小的增加,数据变得越来越嘈杂,离群值越来越多,平均值降低,以高置信度预测的主题越来越少,低预测主题的数量增加。
基于这些统计数据,可以从评估中排除两个最大的模型。请记住,这个模型的任务是得出一组主题,这些主题将允许语料库被分成可管理的块。取消 50 和 80 主题模型的理由是:
- 大量的主题对于任何类型的一般组块来说都是不实用的。许多较大的模型主题只占语料库的不到 2%。即使这些主题代表了语义上有意义的类别,它们也不能服务于总体目标。
- 在较大的模型中,平均可能性主题优势几乎总是小于 50%。较大的模型对它们的预测不是很有信心。
- 两个最大的模型中的数据非常嘈杂。
在这一点上,有必要从使用度量和统计跨越到基于对模型本身的相似性的更主观的度量来评估模型。然而,在结束本文之前,有必要快速浏览一下模型输出,以确定是否有可能合理地进一步减少候选主题大小的数量,并使这项工作变得更加容易。
评估主题模型的语义
主题模型本身的评估可以不同于它们所分类的文档。事实上,上面使用的基于非 PMI 的指标正是通过评估包含该主题最常见单词的单词来做到这一点的。我们可以基于主题的内部连贯性、单词是否一起工作及其可理解性、单词是否一起形成语义上有意义的概念或类别来评估主题。
主题列表被评估为内聚性和可理解性。衔接是对单词是否能很好地结合在一起的判断。例如,“汽车、船、火车”或“男人、女人”是内聚性列表,而“汽车、船、火车、男人、女人”的内聚性较低。可理解性衡量某个想法、主题或活动是否清晰地出现在主题列表中。
这是五个主题模型中每个主题的前十个单词(第一个数字是主题占主导地位的文档数,第二个数字是主题 id):
============== Five ===============
8609 3 family old leave man know life mother see woman call
6614 4 first win club come back leave second world england match
5706 1 president official call country law former cnn party leader include
4570 2 company school uk business include cent service high money come
4501 0 woman world study many know health first see life even
五类模型的主题似乎在衔接性和可理解性上都是混杂的。例如在主题 3 中,例如family, man, mother, woman在语义上与old, life一起工作。Leave, know, see, call不清楚。主题 4 似乎是最有凝聚力的,该组中的单词没有一个是不合适的。话题 4 也是最容易理解的,显然是关于足球的。主题 2 似乎与商业有关(大概在uk),但是include, cent, come这几个字降低了它的整体凝聚力。同样,主题 1 可能是关于政治的,但同样,它的列表中有一些词很难与其他词相协调。话题 0 最难解读,连贯性最差。
这很有趣,但似乎对我们快速消除该模型的任务没有帮助。然而,看看题目和课文的例子有多吻合。文档的随机抽样产生(主题列表在每个文本样本的开头,“贡献:”是主题在文本中出现的可能性):
************************
Model: Five
Document ID: 19866
Topic: 1
Contribution: 0.5211269855499268president official call country law former cnn party leader includeDaily Mail Reporter . PUBLISHED: . 14:04 EST, 6 June 2013 . | . UPDATED: . 14:04 EST, 6 June 2013 . The government says one in 10 youths at juvenile detention facilities around the country reported having been sexually victimized by staff or by other youths. The study by the Bureau of Justice Statistics found that among the more than************************
Model: Five
Document ID: 12506
Topic: 4
Contribution: 0.4131307005882263first win club come back leave second world england match1 June 2016 Last updated at 16:20 BST The Gotthard base tunnel is
57km (35-miles) long and took seventeen years to build. Engineers dug deep under the Swiss Alps mountains to make it and links northern Europe to Italy in the South. The tunnel will be used for freight trains transporting goods and passenger trains. It's estimated around************************
Model: Five
Document ID: 12890
Topic: 3
Contribution: 0.5673818588256836family old leave man know life mother see woman callBy . Daily Mail Reporter . PUBLISHED: . 07:25 EST, 29 November 2013\. UPDATED: . 07:25 EST, 29 November 2013 . He had his hind legs
amputated at just a few weeks old after being born with a severe
deformity. But not only has the Boxer puppy overcome his disability by running on his front paws, he also has a specially adapted wheelchair************************
Model: Five
Document ID: 11310
Topic: 4
Contribution: 0.573677122592926first win club come back leave second world england match(CNN) -- "Glee" will likely end its run after season 6 the final year in the drama's current deal on Fox. "I would not anticipate it goes beyond two more seasons," Fox entertainment chairman Kevin Reilly told reporters on Thursday. "Never say never, but there's two very clear [story] arcs to get to that end and conclude. If we discover a************************
Model: Five
Document ID: 4728
Topic: 1
Contribution: 0.580642819404602president official call country law former cnn party leader includeBy . Simon Walters, Glen Owen and Brendan Carlin . PUBLISHED: . 18:25 EST, 27 April 2013 . | . UPDATED: . 18:38 EST, 27 April 2013 . David Cameron's election guru believes that Tory chairman Grant Shapps and Chancellor George Osborne are ‘liabilities’ who will cost the party votes in this week’s crucial town hall polls, it was claimed last
这些例子表明,这些主题与它们相应的主题分类有很大的偏差。基于此,从考虑中去除五个主题模型是合理的。
转向十话题模型,我们发现话题连贯性是混合的:
=========== Ten =============
4684 3 old leave family man miss car officer see back come
3928 8 club first win match england leave score back side come
3924 7 film see first star come think know world even well
3304 5 world water high china country large area first see many
3053 6 official country military attack security cnn president call leader american
2749 4 party uk bbc service election vote council public company labour
2618 9 woman school student know family parent life call girl want
1979 0 charge judge sentence prison trial murder arrest prosecutor drug month
1900 1 president trump republican obama race campaign first come run car
1861 2 hospital health patient doctor medical dr care treatment die risk
五个随机选择的文档示例揭示了:
************************
Model: Ten
Document ID: 13787
Topic: 7
Contribution: 0.4396437108516693film see first star come think know world even wellLOS ANGELES, California (CNN) -- When director Antoine Fuqua rolls
into a community to shoot a movie, he becomes part of that community. Filmmaker Antoine Fuqua began a program to foster young moviemakers in poor communities. This isn't the case of a Hollywood filmmaker cherry-picking glamorous locations like Beverly Hills or Manhattan. Fuqua's************************
Model: Ten
Document ID: 19146
Topic: 7
Contribution: 0.4848936200141907film see first star come think know world even wellShinjuku has a population density of about 17,000 people per square
kilometre but undeterred by this it has granted citizenship to a new
resident, who only goes by one name - Godzilla. Name: Godzilla
Address: Shinjuku-ku, Kabuki-cho, 1-19-1 Date of birth: April 9, 1954 Reason for special residency: Promoting the entertainment of and************************
Model: Ten
Document ID: 1482
Topic: 1
Contribution: 0.3362347483634949president trump republican obama race campaign first come run car(CNN) -- "An unconditional right to say what one pleases about public affairs is what I consider to be the minimum guarantee of the First Amendment." -- U.S. Supreme Court Justice Hugo L. Black, New York Times Co. vs. Sullivan, 1964 . It's downright disgusting to listen to conservative and Republican lawmakers, presidential candidates,************************
Model: Ten
Document ID: 28462
Topic: 5
Contribution: 0.5035414695739746world water high china country large area first see manyThe emergency services were called out at about 10:00, and the CHC
helicopter landed at about 10:15\. A CHC spokesperson said: "In
accordance with operating procedures, the crew requested priority
landing from air traffic control. "This is normal procedure, a light illuminated in the cockpit." The spokesperson added: "The aircraft************************
Model: Ten
Document ID: 16179
Topic: 3
Contribution: 0.5338488221168518old leave family man miss car officer see back comeThe 31-year-old right-armer joined from Hampshire ahead of the 2014
campaign, but missed most of the 2015 season with triceps and back
injuries. Griffiths was Kent's leading wicket-taker in the T20 Blast this season, with 13 at an average of 33.61\. He also played three times in the One-Day Cup, but did not feature in the Count
总的来说,主题似乎比五主题模型更符合代表性文档。虽然存在明显的问题,但在没有进一步证据的情况下,放弃十个主题的模型似乎为时过早。
摘要
本文演示了如何使用度量来帮助确定 LDA 主题模型的大小。生成了许多不同大小的模型及其附带的度量和概要统计。计算度量输出的拐点,其识别六个候选主题模型大小。两个较大模型的相对较高的统计噪声,加上它们的剪切大小不适合手头的任务的判断,导致它们被排除在考虑之外。分析转向了五个和十个主题模型,这些模型在统计学上似乎是可行的。在这一点上,纯粹的度量和统计评估工具已经用尽。本文介绍了判断模型语义的初步过程。五个主题的模式显然不适合这项任务,很容易被排除在外。虽然十个主题的模型显示出一些弱点,但在缺乏更彻底分析的情况下,不考虑它似乎是不合理的。
网上大多数关于 LDA 主题模型创建的文章要么是基础教程,要么是关于 LDA 数学或其评估的密集的理论论文。本文试图为开发人员提供一个模板,让他们超越基础知识,立足于实际应用。应该清楚的是,这不是一个简单的“三分钟”任务。本文描述了使用行业标准度量和统计数据为任务提供指导的过程。希望寻求加深他们对如何在现实世界中使用 LDA 的理解的从业者将发现这些信息有助于加深他们对该主题的理解,并且它将提供对如何改进他们自己的工作的洞察力。
文献学
AlSumait,l .,Barbará,d .,Gentle,j .,和 Domeniconi,C. (2009 年)。LDA 生成模型的主题重要性排序。数据库中的机器学习和知识发现,67–82。
布莱博士,Ng,A. Y .,&乔丹,M. I. (2003 年)。潜在狄利克雷分配。机器学习研究杂志:JMLR 。【https://www.jmlr.org/papers/volume3/blei03a/blei03a.pdf? ref=https://githubhelp.com
刘,J. H .,纽曼博士和鲍德温博士(2014 年)。机器阅读茶叶:自动评估主题连贯性和主题模型质量。计算语言学协会欧洲分会第 14 届会议论文集,530–539。
罗德尔,m .,两者,a .,和欣内堡,A. (2015 年)。探索话题连贯性测量的空间。第八届 ACM 网络搜索和数据挖掘国际会议论文集,399–408。
特拉尼、费尔西尼、加卢齐、特罗佩亚诺和坎代列里(2021 年)。OCTIS:比较和优化主题模型很简单!263–270。
w .韦伯、a .莫法特和 j .佐贝尔(2010 年)。不确定排序的相似性度量。 ACM 信息与系统安全汇刊, 28 (4),1–38。
附加参考
这篇文章的灵感来自于:
Selva Prabhakaran (2018) 使用 Gensim (Python)进行主题建模
Shashank Kapadia (2019) 评估主题模型:潜在狄利克雷分配(LDA)
使用 Pandas 查询来过滤或选择 Pandas 数据框架中的数据
Pandas query()函数语法是在 Pandas 数据帧中过滤或选择数据的一种更简洁的方式

概观
作为一名数据科学家,我通常依靠使用布尔屏蔽来过滤或选择熊猫数据框架中的数据。我以前听说过熊猫query()功能,但从未真正尝试过。几周前,当我决定真正尝试一下时,这种情况发生了变化😎
现在,这个功能已经成为我选择或过滤数据的首选方法。这是因为一个简单的原因:代码可读性的提高。
查询函数有一个非常简单的语法,它接受引号内的布尔表达式(例如'表达式'),并过滤表达式为真的数据帧行。
在本文中,我将快速向您展示如何使用df.query('expression')函数来代替标准的布尔屏蔽语法方法。希望你们中的一些人能够改变自己喜欢的方法!😄
边注:本文的示例代码可以在 Jupyter 笔记本 这里 找到。
先来点数据
开始之前,我们需要一些数据。出于演示的目的,本文将使用 Iris 数据集。

该数据集使用鸢尾属植物花的四列测量值(萼片长度、萼片宽度、花瓣长度和以厘米为单位的花瓣宽度)来预测花的种类。

从 scikit-learn 加载的虹膜数据集。(来源:作者)
现在让我们开始一些语法比较!🚀
语法:普通布尔掩码 Vs 查询函数
为了显示这两种语法之间的一些比较,并强调为什么查询功能更好,让我们尝试从 Iris 数据集回答这个简单的问题:有多少“setosa”物种样本的萼片长度小于 5cm?
使用通常的布尔屏蔽语法,我可以快速得到如下结果:

使用通常的布尔掩码过滤方法。(来源:作者)
使用如下查询函数可以获得相同的结果 20:

使用查询功能得到相同的答案。(来源:作者)
你注意到两者的不同了吗?为了以防万一,这里有两个要注意的主要观察结果:
- 我只需在查询函数表达式中写入列名
species,而不必在过滤器中多次写入iris_df.species。 - 看看这个查询方法是多么的清晰易读。
是只有我觉得这样看起来更舒服吗?
此外,我可以很容易地在查询函数中使用一个带有“@”符号的变量来获得相同的结果,如下所示:

使用“@”符号在查询函数表达式中包含变量。(来源:作者)
我希望这个简单的示例问题能激发您尝试 Pandas 中的查询功能的兴趣。可能性是无穷的!
我不打算在这里继续举更多的例子。我建议出去试试!学习的最好方法就是走出去打破一些东西。😄
只要查询函数中的表达式包含有效的数据字段、变量、布尔符号和 pythonic 布尔运算符,就应该没问题。👍
如果你还想知道更多关于如何使用查询功能的例子,我推荐你点击这里查看这篇文章。
最后的想法
找到有效的代码并不罕见,但是乍一看,很难理解发生了什么。这通常被称为代码可读性问题。比较这里讨论的两种方法的语法,很明显,Pandas 查询函数方法产生了可读性更好的代码。此外,它也有一些性能优势。
我希望这篇文章能引起您的兴趣,让您尝试一下查询功能。😄
感谢您花时间阅读本文。希望你觉得有用。请随时伸出手来连接!👋
🚀🚀如果你愿意支持我,阅读更多来自我和其他了不起的 medium 作者的文章,考虑使用我的 推荐链接 注册 medium。🙏
以下是我的一些其他文章,你可能会感兴趣
使用 Python 脚本将 CSV 数据插入 Django 数据库
Django-extensions 是您正在寻找的工具

托拜厄斯·菲舍尔在 Unsplash 拍摄的照片
简介
所以,你努力工作,为你的客户开发了一个新的现代 Django web 应用程序。她很开心,恭喜你最后的结果。干得好。现在,我们只需将数百万个数据库行插入新的应用程序,并开始在生产中使用它。
一些准备不足的人听到这样的话会愣住,但你不会:你会知道如何使用 django-extensions 并且在你安装它之后,使用 django 的 ORM 功能编写 Python 脚本来快速加载数据到 Django 数据库是多么容易。
如果您还不知道 django-extensions,不要担心:这个快速教程将通过一个非常简单的例子展示它是如何工作的。然后你可以把它扩展到你日常活动中可能遇到的更复杂的情况。
第 1 部分:我们的模型和数据
我们将使用我在 my Django tutorial for total 初学者教程中创建的非常简化的 films 应用程序,该教程发布在toward Data Science上。
它的模型只有两个表:films_film和films_genre。来自films/models.py的代码转载如下:
正如我在我的教程中提到的,这不是这些实体之间的完美模型关系,特别是因为它确定了一部电影不能有一个以上的类型。然而,我决定建立一个用于教学目的的简化模型,我们将在本文中保持这种方式。如果你愿意,你可以在以后扩展这个模型,允许电影和流派之间的多对多关系,或者包括新的实体,比如导演和奖项。
我们将使用的 CSV 文件可以在我的 GitHub 库中找到。它是使用来自pixarfilms R 库的数据创建的。由于在我创建的数据模型中,每部电影只允许一种类型,所以我为大多数电影指定了类型“动画”,但我也包括了一些其他类型,因此我们的示例中不只有一种类型。
第 2 部分:设置我们的环境
1.在你的机器上选择一个你想要工作的文件夹,用启动代码克隆我的 GitHub 库。
git clone [https://github.com/fabricius1/DjangoTutorial.git](https://github.com/fabricius1/DjangoTutorial.git)
2.在克隆的文件夹DjangoTutorial中移动。
cd DjangoTutorial
请注意,pixar.csv文件已经保存在DjangoTutorial/films文件夹中。
3.用venv创建一个虚拟环境。我叫我的.myenv。
python -m venv .myenv
4.通过针对您选择的终端和操作系统运行正确的命令来激活虚拟环境。下面的命令在我的 Git Bash for Windows 中有效。如果你对用venv激活虚拟环境有疑问,请查阅 Python 文档。
source .myenv/Scripts/activate
从现在开始,所有命令都必须在激活虚拟环境的情况下执行。
5.用 PIP 安装django和django-extensions。我们还将安装pandas和plotly,因为plotly.express在克隆项目的films/views.py文件中被调用。
pip install django django-extensions pandas plotly
如果你想了解更多关于django-extensions,的信息,请阅读文档,尤其是关于的 runscript 功能的页面。'
6.将字符串'django_extensions'添加到project/settings.py中的INSTALLED_APPS列表中。其他行保持不变。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# films app:
'films.apps.FilmsConfig', # add this:
'django_extensions',
]
7.应用迁移文件在数据库中创建表:
python manage.py migrate
8.查找错误,并根据需要进行更正:
python manage.py check
9.用python manage.py runserver在本地运行项目。
10.在浏览器上打开http://localhost:8000/films并检查应用程序,检查是否一切正常。屏幕上不会显示任何电影,因为刚刚创建了db.sqlite3数据库。

作者图片
第 3 部分:创建并运行脚本
11.停止服务器。在项目根目录下创建一个scripts文件夹,与manage.py在同一层:
mkdir scripts
12.创建scripts/load_pixar.py文件。
touch scripts/load_pixar.py
13.用以下代码填充scripts/load_pixar.py。我们稍后会解释:
14.运行python manage.py runscript load_pixar。请注意load_pixar是不带.py扩展名的。
如果一切顺利,您将在控制台中看到导入的行。
15.用python manage.py runserver再次运行服务器,然后在浏览器上打开http://localhost:8000/films。查看 Django 页面现在如何显示导入的电影:

作者图片
第四部分:关于脚本代码
scripts/load_pixar.py代码中只有一个函数,其末尾没有 dunder 调用。如 django-extensions 文档中所述,该文件必须实现一个 *run()* 函数。这是运行脚本时调用的内容。您可以导入 Django 项目的任何模型或其他部分,以便在这些脚本中使用。
因此,我们在脚本中导入了Films和Genre模型以及csv Python 内置模块。接下来,在run()函数中,我们使用with上下文管理结构,并通过使用模式app_name/csv_file而不是相对路径来打开pixar.csv文件。
然后我们将file变量传递给csv.reader()函数,调用next(reader)跳过 CSV 头文件,最后使用 Django 的 ORM 方法.delete()删除模型表中可能存在的任何实例。如果不想从表中删除现有的行,请删除这些代码行。
下一步是遍历 CSV 中的所有行。在这部分代码中,我们第一次发现了重要的方法.get_or_create()。它返回一个 tuple,其中第一个索引处的对象是创建的(如果数据库中还不存在)或检索的(如果已经存在)Django 模型对象。元组中的第二个元素是一个布尔值,如果对象已创建,则返回True,否则返回False。
请注意我们是如何首先创建(或获取)了Genre对象,然后使用它以及从每个 CSV 行中获取的其他信息来创建一个新的Film对象并将其保存到数据库中。
最后的话
如前所述,这只是如何使用 django-extensions runscript 功能的一个非常简单的例子。通过了解基础知识,您可以实现更复杂模型的解决方案,还可以创建具有错误处理结构的代码,例如,处理异常。
亲爱的读者,非常感谢你花时间和精力阅读我的文章。
快乐编码!
使用 Python 创建三体轨道
原文:https://towardsdatascience.com/use-python-to-create-three-body-orbits-329ffb5b2627
对卫星在两个较大质量影响下的运动轨迹进行数值积分和可视化

动画 CR3BP 轨道(惯性框架)[由作者创建]
在轨道力学中,三体(3BP)是研究一个可忽略的质量在两个较大质量(行星、月亮、恒星等)的影响下如何移动。).当必须考虑两个较大质量的引力时,这些结果用于设计航天器轨道。例如,詹姆斯·韦伯望远镜的轨道和当前轨道就是利用 3BP 设计的。通常,当人们想到轨道时,他们会想到一个可以忽略不计的质量,围绕着一颗行星或恒星的椭圆轨道。有了三体,系统中增加了一个额外的大质量物体,这增加了推导描述较小物体运动的方程的难度。
为了创建和可视化 3BP 中的轨道,我们必须推导出可忽略质量的运动方程。我在这里跳过推导的细节,但是如果你感兴趣,这篇文章描述了如何推导三体方程。我鼓励您浏览那篇文章,以充分理解您将要使用 Python 求解的变量和方程。我们将学习循环受限三体(CR3BP)。在这个广义三体的限制性版本中,两个较大的质量(或原色)以圆形轨道围绕它们各自的质量中心运行。这简化了航天器(或其他可忽略质量)的运动方程。下图显示了 CR3BP 的设置。

三体问题图[作者创作]
针对(航天器、小行星等)推导出 CR3BP 运动方程。)在旋转框架中( x 、 y 、 z-hat 框架):

这些方程有点吓人,但是一旦你知道每个变量是什么,它们就不会太糟糕。这里, x , y ,z(ρ矢量)是 m₃ 相对于初选质心的位置坐标,在旋转坐标系( x , y , z-hat【中这些项上的点表示时间导数,所以一个点表示速度项,两个点表示加速度项。是一种无量纲质量比的初选。 r₁ 和 r₂ 分别是 m₃ 离各自主光源的距离。
上面的运动方程是使用无量纲变量导出的。上面提到的文章详细介绍了如何使用无量纲参数,如果你真的想了解三体,这很重要。此处不涉及太多细节,以下参数用于删除和添加尺寸(km、kg、s 等。)如有必要。我们可以用 M 来操纵质量单位(kg)***【L *来操纵长度单位(km)来操纵时间单位(s)。这将在编码部分进一步解释。

为了求解 CR3BP,我们将使用 ODE 求解器对 m₃ 的运动方程进行数值积分。为了做到这一点,我们需要定义一个状态向量和一个时间导数状态向量。对于我们的问题,状态向量包括旋转坐标系中的位置和速度向量。时间导数状态向量就是状态向量的时间导数。这些将使用 Python 的odeint数字积分器。这里的双点 x 、 y 、 z 都来自上面定义的运动方程。

我们可以将旋转坐标系( x , y , z )中的一个矢量转换为惯性系( X , Y , Z

我知道这只是对问题的一个简单概述,但是你不需要完全理解 CR3BP 就能编码一个解决方案。我假设你对数值积分以及如何用 Python 实现它有所了解。如果你需要这方面的一些指导,我写了另一篇文章可能会有帮助,互联网上还有很多其他资源。事不宜迟,让我们直接进入代码吧!
导入包
对于这段代码,我们需要导入几个包。以下列表简要描述了此代码中使用的包:
- NumPy 用于创建和操作数组(为便于调用,定义为
np) - 来自 SciPy 库的
*odeint*用于数值积分 - matplotlib 中的
*pyplot*用于可视化数值积分的结果(为方便调用,定义为plt
**# Importing Packages
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt**
创建数值积分函数
下一步是创建一个用户定义的 Python 函数,model_CR3BP,它将在odeint中用来对我们的状态向量进行数值积分。odeint函数采用当前状态向量来创建时间导数状态向量。请注意,我们从状态向量中提取位置和速度分量,以创建状态向量时间导数。
**# CR3BP Model
def model_CR3BP(state, t):
x = state[0]
y = state[1]
z = state[2]
x_dot = state[3]
y_dot = state[4]
z_dot = state[5]
x_ddot = x+2*y_dot-((1-mu)*(x+mu))/((x+mu)**2+y**2+z**2)**(3/2)\
-(mu*(x-(1-mu)))/((x-(1-mu))**2+y**2+z**2)**(3/2)
y_ddot = y-2*x_dot-((1-mu)*y)/((x+mu)**2+y**2+z**2)**(3/2)\
-(mu*y)/((x-(1-mu))**2+y**2+z**2)**(3/2)
z_ddot = -((1-mu)*z)/((x+mu)**2+y**2+z**2)**(3/2)\
-(mu*z)/((x-(1-mu))**2+y**2+z**2)**(3/2)
dstate_dt = [x_dot, y_dot, z_dot, x_ddot, y_ddot, z_ddot]
return dstate_dt**
定义无量纲参数
这里,我们为我们选择的系统定义几个参数。本文我选择了地月系统,但如果你想选择另一个系统(冥王星-卡戎,太阳-木星等。),可以相应地调整质量和半长轴变量。这里, m₁ 和 m₂ 分别是地球和月球的质量。 a 是月球轨道的半长轴(如果他们有一个完美的圆形轨道的话)。我用月球轨道的平均距离来表示,因为月球实际上是以圆形轨道绕地球运行的。接下来,使用本文开头的等式定义无量纲参数。
***# Defining ND Parameters
G = 6.67408E-20 # Univ. Gravitational Constant [km3 kg-1 s-2]
mEarth = 5.97219E+24 # Mass of the Earth [kg]
mMoon = 7.34767E+22 # Mass of the Moon [kg]
a = 3.844E+5 # Semi-major axis of Earth and Moon [km]
m1 = mEarth
m2 = mMoon
Mstar = m1+m2 # ND Mass Parameter
Lstar = a # ND Length Parameter
Tstar = (Lstar**3/(G*Mstar))**(1/2) # ND Time Parameter
mu = m2/Mstar
print('\u03BC = ' + str(mu))***
定义 ODE 解算器输入
现在,除了用户定义的模型,odeint还需要两个输入:初始条件和积分的时间间隔。我定义了一组任意的初始条件,来创建你们一开始看到的轨道。你可以尝试你自己的初始条件或者使用这个 NASA JPL 工具中的星历数据。接下来,我们定义初始状态向量state_0,以及感兴趣的时间数组t。请注意,使用上面的 ND 参数移除了尺寸。公里和秒单位通过除以或乘以 T 和 L 来移除。**
**# Initial Conditions [Initial State Vector]
X_0 = 50000/Lstar # ND x
Y_0 = 0 # ND y
Z_0 = 0 # ND z
VX_0 = 1.08*Tstar/Lstar # ND x_dot
VY_0 = 3.18*Tstar/Lstar # ND y_dot
VZ_0 = 0.68*Tstar/Lstar # ND z_dot
state_0 = [X_0, Y_0, Z_0, VX_0, VY_0, VZ_0] # ND ICs
# Time Array
t = np.linspace(0, 15, 1000) # ND Time**
数值积分模型
下一步是组合我们已经编码的内容,并在odeint函数中使用它们。这里,我们使用model_CR3BP、state_0和t。数值积分的输出是我们在时间数组中定义的每一步的状态向量。从结果中我们可以拉出定义位置的xyz坐标(在旋转坐标系中)。接下来,使用本文开头的等式,我们可以将转动状态转换为惯性状态。
**# Numerically Integrating
sol = odeint(model_CR3BP, state_0, t)
# Rotational Frame Position Time History
X_rot = sol[:, 0]
Y_rot = sol[:, 1]
Z_rot = sol[:, 2]
# Inertial Frame Position Time History
X_Iner = sol[:, 0]*np.cos(t) - sol[:, 1]*np.sin(t)
Y_Iner = sol[:, 0]*np.sin(t) + sol[:, 1]*np.cos(t)
Z_Iner = sol[:, 2]**
添加主要位置时间历史
这一步是可选的,但是包括两次初选的时间历程对于理解的运动可能是重要的。原色的位置在旋转框架中是固定的(对于 CR3BP ),并且可以通过使用原色的质心并转换到 nd 单位来简单地导出。接下来,使用与前面相同的旋转坐标系到惯性坐标系的转换,我们可以确定原色的惯性运动时间历程。
***# Constant m1 and m2 Rotational Frame Locations for CR3BP Primaries
m1_loc = [-mu, 0, 0]
m2_loc = [(1-mu), 0, 0]
# Moving m1 and m2 Inertial Locations for CR3BP Primaries
X_m1 = m1_loc[0]*np.cos(t) - m1_loc[1]*np.sin(t)
Y_m1 = m1_loc[0]*np.sin(t) + m1_loc[1]*np.cos(t)
Z_m1 = m1_loc[2]*np.ones(len(t))
X_m2 = m2_loc[0]*np.cos(t) - m2_loc[1]*np.sin(t)
Y_m2 = m2_loc[0]*np.sin(t) + m2_loc[1]*np.cos(t)
Z_m2 = m2_loc[2]*np.ones(len(t))***
可视化数据
最后一步是绘制每个质量的旋转和惯性系运动。为了区分轨道,我们将用绿色代表 、m₃ ,黑色代表月球,蓝色代表地球。我们还可以设置这些轴,使它们显示相等的轴长,以便更准确地观察系统。
**# Rotating Frame Plot
fig = plt.figure()
ax = plt.axes(projection='3d')
# Adding Figure Title and Labels
ax.set_title('Rotating Frame CR3BP Orbit (\u03BC = ' + str(round(mu, 6)) + ')')
ax.set_xlabel('x [ND]')
ax.set_ylabel('y [ND]')
ax.set_zlabel('z [ND]')
# Plotting Rotating Frame Positions
ax.plot3D(X_rot, Y_rot, Z_rot, c='green')
ax.plot3D(m1_loc[0], m1_loc[1], m1_loc[2], c='blue', marker='o')
ax.plot3D(m2_loc[0], m2_loc[1], m2_loc[2], c='black', marker='o')
# Setting Axis Limits
xyzlim = np.array([ax.get_xlim3d(), ax.get_ylim3d(), ax.get_zlim3d()]).T
XYZlim = np.asarray([min(xyzlim[0]), max(xyzlim[1])])
ax.set_xlim3d(XYZlim)
ax.set_ylim3d(XYZlim)
ax.set_zlim3d(XYZlim * 3 / 4)
# Inertial Frame Plot
fig = plt.figure()
ax = plt.axes(projection='3d')
# Adding Figure Title and Labels
ax.set_title('Inertial Frame CR3BP Orbit (\u03BC = ' + str(round(mu, 6)) + ')')
ax.set_xlabel('X [ND]')
ax.set_ylabel('Y [ND]')
ax.set_zlabel('Z [ND]')
# Plotting Inertial Frame Positions
ax.plot3D(X_Iner, Y_Iner, Z_Iner, c='green')
ax.plot3D(X_m1, Y_m1, Z_m1, c='blue')
ax.plot3D(X_m2, Y_m2, Z_m2, c='black')
# Setting Axis Limits
ax.set_xlim3d(XYZlim)
ax.set_ylim3d(XYZlim)
ax.set_zlim3d(XYZlim * 3 / 4)
plt.show()**
代码输出:

CR3BP 轨道(旋转框架)[作者创作]

CR3BP 轨道(惯性系)[作者创作]
上面的图显示了m₃和主要质量的轨道的惯性和旋转框架结果。如你所见,对于这组初始条件,飞船有一个混乱的轨道。如果调整初始条件,可以创造出各种不同的轨道(有些甚至是周期性的)。我鼓励你打乱初始条件,自己做实验。为了使文章和代码尽可能简短,我省略了如何根据情节制作动画。如果您有兴趣添加它,请查看下面的分步指南:
*** ***
感谢您阅读文章!这是对圆形受限三体以及如何对运动方程进行数值积分的简要概述。这是一个高级的轨道力学话题,所以学习起来可能会很有挑战性。如果你有任何问题,请随时联系这里或 LinkedIn 。请关注我关于轨道力学、编码和机器学习的每周文章!
如果您觉得这篇文章很有趣,您可能也会觉得以下内容很有趣:
***https://medium.com/illumination/astrodynamics-two-body-problem-ec2c5e148184 ***
使用 Python 创建两体轨道
原文:https://towardsdatascience.com/use-python-to-create-two-body-orbits-a68aed78099c
了解如何使用 Python 来确定航天器在较大物体重力影响下的运动

双体轨道动画[作者创作]
一般的两体问题包括求解两个在彼此引力影响下运动的质量的运动。在轨道力学中,通常认为其中一个质量可以忽略不计,因此对另一个质量没有影响。可忽略质量的运动是令人感兴趣的,也是要解决的问题。事实上,较小的质量确实对较大的质量有影响,但这种影响是如此之小,以至于实际上为零。
为了在 Python 中创建两体轨道,首先需要推导感兴趣质量的运动方程(无论是卫星、小行星等)。).我在另一篇文章(见下文)中演示了如何做到这一点,所以我鼓励您去探索这一点,并真正理解方程背后的物理原理。但是,你会发现下面的运动方程。
https://medium.com/illumination/astrodynamics-two-body-problem-ec2c5e148184
两体问题是一个很好的学习工具,可以作为非常接近大质量的轨道的第一近似,但一旦我们的轨道远离这个质量,其他引力就应该包括在内。即使在接近大质量时,也应包括大气阻力、太阳辐射和非球形物体的影响等干扰力。我们不会在这里讨论这些,但这是一件值得记住的事情。在较大质量的重力影响下,可忽略质量的运动方程如下:

现在我们有了运动方程,我们需要把它们转换成可以数值积分的状态形式。状态形式包括某一时刻的位置和速度矢量。这是状态及其时间导数。

状态时间导数将成为一个可以数值积分的函数。Python 有内置的数字积分器,我们可以利用它来完成这个任务。为了创建我们的轨道,我们将使用 SciPy 包中的odeint函数。让我们开始我们的代码,它将为地球轨道卫星生成一个图。
导入包
这段代码需要的重要包如下:
- NumPy 用于创建数值数组和使用三角函数(为便于调用,定义为 np )
- SciPy 用于
odeint函数,该函数用于对我们的运动方程进行数值积分 pyplotfrommatplotlib用于显示我们在代码末尾的轨道
*# Importing Packages
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt*
创建数值积分函数
下一步是创建一个由odeint运行的函数,对我们的状态向量进行数值积分。该函数将包括每个 x 、 y 和 z 的速度和加速度(如上定义)。该函数在创建时应该如下所示:
*# Earth Model
def model_2BP(state, t):
mu = 3.986004418E+05 # Earth's gravitational parameter
# [km^3/s^2]
x = state[0]
y = state[1]
z = state[2]
x_dot = state[3]
y_dot = state[4]
z_dot = state[5]
x_ddot = -mu * x / (x ** 2 + y ** 2 + z ** 2) ** (3 / 2)
y_ddot = -mu * y / (x ** 2 + y ** 2 + z ** 2) ** (3 / 2)
z_ddot = -mu * z / (x ** 2 + y ** 2 + z ** 2) ** (3 / 2)
dstate_dt = [x_dot, y_dot, z_dot, x_ddot, y_ddot, z_ddot]
return dstate_dt*
运行 ODE 解算器
所选的 ODE 求解器需要三个输入来运行:要进行数值积分的函数、初始条件和时间数组。我们已经创建了这个函数,叫做model_2BP。初始条件可以是任何东西(确保你在地球表面之外!).美国宇航局有一个应用,如果你想查看特定卫星的轨道,它可以用来提取状态向量。可以使用 NumPy 包创建时间数组(注意:确保包含足够的时间步长来创建平滑的轨道)。我们现在可以运行求解器,并将其设置为等于sol(意为“解”)。
*# Initial Conditions
X_0 = -2500 # [km]
Y_0 = -5500 # [km]
Z_0 = 3400 # [km]
VX_0 = 7.5 # [km/s]
VY_0 = 0.0 # [km/s]
VZ_0 = 4.0 # [km/s]
state_0 = [X_0, Y_0, Z_0, VX_0, VY_0, VZ_0]
# Time Array
t = np.linspace(0, 6*3600, 200) # Simulates for a time period of 6
# hours [s]
# Solving ODE
sol = odeint(model_2BP, state_0, t)
X_Sat = sol[:, 0] # X-coord [km] of satellite over time interval
Y_Sat = sol[:, 1] # Y-coord [km] of satellite over time interval
Z_Sat = sol[:, 2] # Z-coord [km] of satellite over time interval*
可视化数据
最后,我们创建轨道的最终目标可以通过使用 matplotlib 创建一个 3D 图形来完成。我们使用了来自sol变量的 x 、 y 和 z 时间历程,这些时间历程我们已经获得。为了增加视觉效果,我们还可以创建一个球体来代表我们的地球(地球的平均半径为 6378.14 公里)。我们将把我们的地球放在我们阴谋的起点。
*# Setting up Spherical Earth to Plot
N = 50
phi = np.linspace(0, 2 * np.pi, N)
theta = np.linspace(0, np.pi, N)
theta, phi = np.meshgrid(theta, phi)
r_Earth = 6378.14 # Average radius of Earth [km]
X_Earth = r_Earth * np.cos(phi) * np.sin(theta)
Y_Earth = r_Earth * np.sin(phi) * np.sin(theta)
Z_Earth = r_Earth * np.cos(theta)
# Plotting Earth and Orbit
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.plot_surface(X_Earth, Y_Earth, Z_Earth, color='blue', alpha=0.7)
ax.plot3D(X_Sat, Y_Sat, Z_Sat, 'black')
ax.view_init(30, 145) # Changing viewing angle (adjust as needed)
plt.title('Two-Body Orbit')
ax.set_xlabel('X [km]')
ax.set_ylabel('Y [km]')
ax.set_zlabel('Z [km]')*
我们应该在我们的计划中增加一件事。在我们的课程中,我的教授们总是坚持让轴在轨道图上等距分布。这将给出物理空间中轨道的真实表示。我们可以用下面的代码来实现。
*# Make axes limits
xyzlim = np.array([ax.get_xlim3d(), ax.get_ylim3d(),
ax.get_zlim3d()]).T
XYZlim = np.asarray([min(xyzlim[0]), max(xyzlim[1])])
ax.set_xlim3d(XYZlim)
ax.set_ylim3d(XYZlim)
ax.set_zlim3d(XYZlim * 3/4)
plt.show()*
编译完所有代码并运行它之后,您应该会得到下面的轨道。还是那句话,这是个近似值,随着轨道的演化,它会开始偏离真实的轨道。为了得到一个更好的轨道模型,你应该包括扰动力,像月球的引力,太阳的引力,地球的非球形性质,太阳辐射,甚至大气阻力。我将在某个时候写一篇关于这个主题的文章,所以如果你感兴趣,给我一个关注,订阅我的电子邮件来保持更新。

二体轨道[作者创作]
顺便说一句,如果您想要像本文开头那样制作动态观察的动画,您可以按照本文学习:
* *
感谢您阅读文章!我将继续写容易理解的轨道力学文章,包括代码和推导,所以如果你感兴趣,请给我一个关注!有问题就评论!
使用 Seaborn FacetGrid 快速创建带有支线剧情的人物
通过使用 Seaborn 的 FacetGrid(一个使用地震数据的例子)增强您的探索性数据分析过程

丹尼尔·塞勒在 Unsplash 上拍摄的照片
数据可视化是任何数据分析或机器学习工作流的重要组成部分。它让我们对数据有了更深入的了解,正如那句名言所说,“一张图片胜过千言万语”。
当在 Python 中处理数据可视化时,我们可能希望按照类别或不同的组来分割数据。这是通过使用 Seaborn 的 FacetGrid 函数实现的。这允许我们创建一个网格,在网格上我们可以用最少的代码绘制许多其他类型的图,特别是与使用 matplotlib 相比。
教程的数据源
本教程使用的数据可以在 CC0: Public Domain 下使用,并且来自 Kaggle。这是一个数据集,包含 1965 年至 2016 年期间世界上震级为 5.5 或更高的最重大地震的数据。
您可以通过以下链接查看原始数据。
https://www.kaggle.com/datasets/usgs/earthquake-database?select=database.csv
视频教程
我还制作了这个教程的以下视频,你可能会感兴趣。
加载和导入库
我们教程的第一步是导入必要的库,它们是 Pandas 和 Seaborn 。
Pandas 是一个 Python 库,用于数据分析、操作,并允许我们从各种数据源加载数据,包括。csv,。xlsx 等。
Seaborn 是一个数据可视化库,它建立在 matplotlib 之上,允许它用几行代码创建非常强大和有洞察力的图形。它为创建更高级的绘图提供了更容易使用的语法。与 matplotib 相比,默认数字也更具视觉吸引力
import seaborn as sns
import pandas as pd
加载数据
一旦库被导入,我们现在可以开始从 Kaggle 导入地震数据集。这是通过调用pd.read_csv()并将文件名传递给括号来完成的。
#Significant (Magnitude > 5.5) Earthquake data from 1965 to 2015.
#Licence: CC0 - Public Domain
#[https://www.kaggle.com/datasets/usgs/earthquake-database](https://www.kaggle.com/datasets/usgs/earthquake-database)earthquakes = pd.read_csv('data/kaggle_significant_earthquakes_database.csv')
我们可以通过拨打以下电话确认我们的数据已成功加载:
earthquakes.info()
它返回数据帧的摘要,包括列名、非空计数和数据类型(Dtype)。
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 23412 entries, 0 to 23411
Data columns (total 24 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Date 23412 non-null object
1 Time 23412 non-null object
2 Latitude 23412 non-null float64
3 Longitude 23412 non-null float64
4 Type 23412 non-null object
5 Depth 23412 non-null float64
6 Depth Error 4461 non-null float64
7 Depth Seismic Stations 7097 non-null float64
8 Magnitude 23412 non-null float64
9 Magnitude Type 23409 non-null object
10 Magnitude Error 327 non-null float64
11 Magnitude Seismic Stations 2564 non-null float64
12 Azimuthal Gap 7299 non-null float64
13 Horizontal Distance 1604 non-null float64
14 Horizontal Error 1156 non-null float64
15 Root Mean Square 17352 non-null float64
16 ID 23412 non-null object
17 Source 23412 non-null object
18 Location Source 23412 non-null object
19 Magnitude Source 23412 non-null object
20 Status 23412 non-null object
21 date_parsed 23412 non-null object
22 year 23412 non-null int64
23 month 23412 non-null int64
dtypes: float64(12), int64(2), object(10)
memory usage: 4.3+ MB
数据准备
在继续显示我们的数据之前,我们需要在地震数据集中创建一个额外的列,这将允许我们稍后分割我们的数据。
下面的函数根据震级将地震分为中等(5.0-5.9)、强烈(6.0-6.9)、大(7.0-7.9)和大(> 8.0)。
def magnitude_classification(dataframe: pd.DataFrame):
"""
Classify earthquakes based on magnitude.Parameters
----------
dataframe : pd.DataFrame
Dataframe containing earthquake dataReturns
-------
str
Returns the classification of the earthquake as a string.
"""
if dataframe['Magnitude'] >= 5.0 and dataframe['Magnitude'] <= 5.9:
return 'Moderate (5.0-5.9)'
elif dataframe['Magnitude'] >= 6.0 and dataframe['Magnitude'] <= 6.9:
return 'Strong (6.0-6.9)'
elif dataframe['Magnitude'] >= 7.0 and dataframe['Magnitude'] <= 7.9:
return 'Major (7.0-7.9)'
elif dataframe['Magnitude'] >= 8.0:
return 'Great (>8.0)'
创建函数后,我们现在可以使用 pandas apply 方法在 dataframe 中创建新列,如下所示:
earthquakes['Class'] = earthquakes.apply(magnitude_classification, axis=1)
当我们查看数据集时,我们现在可以看到 Class 列已经成功添加。

使用 Seaborn 创建面网格
创建绘图过程的第一步是创建一个新变量——在本例中是g,然后将它赋给sns.FacetGrid()。然后我们可以传入地震数据帧并运行代码。
g = sns.FacetGrid(earthquakes)
这将返回一组没有数据的轴。

从 Seaborn FacetGrid 返回的一组轴没有数据。图片由作者提供。
现在我们有了轴,我们可以开始向它添加数据,方法是使用.map()函数将我们选择的图应用到轴上。
g = sns.FacetGrid(earthquakes)
g.map(sns.scatterplot, 'year', 'Magnitude', alpha=0.5);

映射数据后的 Seaborn FactGrid 轴。图片由作者提供。
使用列在 FacetGrid 中创建子情节
我们现在可以开始使用数据集中的一个分类变量创建子图(面)。在这种情况下,我们将使用地震类型(原因),并将 y 轴更改为深度。
为了将剧情分成多个支线剧情(层面),我们在FacetGrid()中添加了一个col参数,并传入我们想要使用的列的名称。
此外,我们还可以通过调用g.set()并传入我们想要更改的参数来控制绘图限制。在这个例子中,我们将设置ylim (ylimit)从 750 到-10。像这样把数字反过来,可以让我们反转 y 轴。
g = sns.FacetGrid(earthquakes, col='Type')
g.map(sns.scatterplot, 'year', 'Depth', alpha=0.5)
g.set(ylim=(750, -10));
当我们运行这段代码时,我们得到了四个子情节(方面)。每种地震类型一个:地震、核爆炸和岩爆。

按地震类型划分的海鸟。图片由作者提供。
从返回的图中可以看出,地震的深度值范围很广,核爆炸、爆炸、岩爆都是比较浅的到达事件。
使用列和行在 FacetGrid 中创建子情节
除了能够将我们的可视化拆分成列,我们还可以应用第二个分类变量来进一步将可视化拆分成行。
为了实现这一点,我们添加了一个row参数,在本例中,我们将把它分配给我们之前创建的 Class 列。
g = sns.FacetGrid(earthquakes, col='Type', row='Class')
g.map(sns.scatterplot, 'year', 'Depth', alpha=0.5)
g.set(ylim=(750, -10));
当我们运行这段代码时,我们得到了一个由子 plots(facet)组成的网格。我们有显示地震类型(原因)的列,和显示分类(中等、强烈、主要和大)的行。

Seaborn FacetGrid 在按照地震类型和级别分割数据后。图片由作者提供。
然而,我们可以立即看到,每个情节(方面)的标题是非常混乱,难以阅读。
修改 FacetGrid 标题
为了整理每个情节(层面)的标题,我们可以调用g.set_titles(),在这个函数中,我们需要调用col_template和row_template。这两个参数允许我们控制标题。
在本例中,我们将替换:
Class = Major(7.0-7.9)|Type = Earthquake
随着
Major(7.0-7.9)|Earthquake
这是通过指定格式化的字符串作为参数来实现的,然而,我们不需要以f或者使用.format来开始它们,我们只需在{ }括号中传递变量。
在这种情况下,我们将只传入 col_name 和 row_name。
g = sns.FacetGrid(earthquakes, col='Type', row='Class')
g.map(sns.scatterplot, 'year', 'Depth', alpha=0.5)
g.set_titles(col_template = '{col_name}', row_template='{row_name}')
g.set(ylim=(750, -10));
这将返回下面的图,标题更容易阅读。

使用 set_titles 整理 facet 标题后的 Seaborn FacetGrid。图片由作者提供。
更改 FacetGrid 中行的顺序
从图中我们可以看到,第一行显示的是震级范围 6.0-6.9 的数据,下一行是 5.0-5.9 的数据。将这些行按升序排列更有意义,这样第一行是 5.0–5.9,第二行是 6.0–6.9,依此类推。
我们可以通过使用row_order参数并按照我们希望的顺序传入分类变量(Class)的值列表来实现这一点。
g = sns.FacetGrid(earthquakes, col='Type', row='Class',
row_order=['Moderate (5.0-5.9)',
'Strong (6.0-6.9)',
'Major (7.0-7.9)',
'Great (>8.0)'])g.map(sns.scatterplot, 'year', 'Depth', alpha=0.5)
g.set_titles(col_template = '{col_name}', row_template='{row_name}')
g.set(ylim=(750, -10));
我们的 FacetGrid 现在看起来更像样了。我们将行按升序排序,标题也清晰易读。

使用 row_order 对行进行排序后,Seaborn FacetGrid。图片由作者提供。
在 Seaborn FacetGrid 上显示其他类型的绘图
使用 histplot 在 FacetGrid 上显示直方图
在前面的例子中,我们使用了散点图,但是这可以很容易地改变为 Seaborn 提供的任何其他类型的图。
为了调用sns.histplot,我们简单地将它传递给g.map()函数。
g = sns.FacetGrid(earthquakes, col='Type')
g.map(sns.histplot, 'year');
这给出了下面的图。

Histplots 映射到 Seaborn FacetGrid。图片由作者提供。
从回报图中,我们可以看到地震计数覆盖了所有年份,这里和那里略有下降。从 1965 年到 1995 年左右发生了 5.5 级以上的核爆炸。
如果我们想改变直方图上显示的条块,我们只需向bins参数传递一个值。如果我们有一个较小的数量,那么数据将被分成较小数量的箱。
g = sns.FacetGrid(earthquakes, col='Type')
g.map(sns.histplot, 'year', bins=5);

在应用较少数量的面元后,Histplots 映射到 Seaborn FacetGrid。图片由作者提供。
在面网格上显示箱线图
我们也可以以非常相似的方式显示箱线图,只需像这样将sns.boxplot传递给.map()函数。
g = sns.FacetGrid(earthquakes, col='Type')
g.map(sns.boxplot, 'year');

在 Seaborn FacetGrid 上显示的箱线图。图片由作者提供。
在本文中,您可以找到更多关于 Seaborn 中的箱线图的信息:
摘要
与 matplotlib 相比,Seaborn 中的 FacetGrid 函数提供了一种非常有用、快速和方便的方法来创建具有多个子情节的图形。通过内置的功能,这些图可以以多种方式进行定制,并制作非常漂亮的图表,以包含在您的演示或报告中。
感谢阅读。在你走之前,你一定要订阅我的内容,把我的文章放到你的收件箱里。 你可以在这里做!
其次,通过注册会员,你可以获得完整的媒介体验,并支持我自己和成千上万的其他作家。它每个月只花你 5 美元,你可以完全接触到所有令人惊叹的媒体文章,也有机会用你的写作赚钱。如果你用 我的链接报名,你会直接用你的一部分费用支持我,不会多花你多少钱。如果你这样做了,非常感谢你的支持!
使用模拟来优化客户等待时间、系统负载和成本
假设你的基础设施是一个由 M 台相同的机器组成的车队,每台机器一次处理一个任务。每个新客户都被放在一个等待机器空闲的队列中。您知道每个处理任务需要多长时间才能完成,但它不是一个固定的数字——相反,它有一个最短持续时间、一个最长持续时间和一些最可能的持续时间。你大概知道客户流入量(客户/秒)会是多少。您需要多少台机器来处理所有这些客户—M 的值是多少?
或者,您有一个固定的预算,允许您构建正好 M 台机器。车队每秒可以处理多少客户?

或者,可能每个客户都需要通过一系列任务来处理,每个任务都在不同的集群上执行,每个集群都有不同的处理时间——但是所有客户都需要通过一系列任务来处理。你需要回答和上面一样的问题。

或者可能不同的处理任务,每个都具有不同的处理时间,竞争同一机器集群上的资源——因此每个客户的任务循环回到同一集群,直到每个客户都被处理。
可能有资源,如能源或材料,用于完成任务,您可能希望优化流程,以便以最佳方式使用资源。
最后,可能会有与运行这些机器相关的成本,但也会有与客户等待时间过长相关的成本—您可能希望将总体成本降至最低。
如何找到合适的基础设施规模,以确保快速处理客户,同时优化资源使用并最大限度地降低成本?或者,给定一定的基础架构规模,在处理时间、成本和资源方面的最佳结果是什么?在一个简单的设置中,您可以做一个大概的估计,但是如果事情变得足够复杂(想象多个不同的任务在同一个集群上竞争机器),那么最佳估计可能就不明显了。
对上述所有情况几乎都有效的一个解决方案是模拟。如果你能足够精确地描述整个工作流程,你就能在软件中模拟整个事情,让模拟及时向前运行,收集结果,并分析它们。
优势
如果你计划建立的基础设施足够大,运行模拟几乎总是更便宜和更快。模拟甚至可能比运行基础设施的缩小模型更容易。
因为模拟很便宜,你可以运行它很多次,每次都改变参数,寻找优化结果的方法。你甚至可以在仿真的参数空间中进行系统的搜索,以确保找到可能的最佳结果——在成本、时间等方面。
警告
模拟的好坏取决于你输入的数据。确保您详细描述了基础设施的特征。在模拟中使用真实世界的数据非常重要。
比如:你怎么知道每项任务要花多长时间?您可以为每种类型构建一台机器,运行数百个任务,并收集关于任务持续时间的数据。您很可能会在那里获得某种时间分布,然后在您的模型中使用它。
一些准备是强制性的。不要随便编造数据,否则模拟会不准确。
我们的例子
请参考第一张图,即本文顶部的那张图。我们正在模拟一个最简单的例子:只有一个集群,每个客户只生成一个需要处理的任务。
在本文中,我们不讨论成本或资源优化。成本和资源消耗基本上是更多的变量集(每个客户的成本、每台机器的成本等),您需要在您的模型中进行跟踪和总结。随着模拟时间的推移,跟踪成本、资源等。为了让这个例子简单明了,我们在这里不考虑成本和资源(除了不同的时间和持续时间)。
我们将假设机队的规模是固定的:M = 100 台机器。
每位客户的处理时间大约为 2 分钟,但这不是一个固定的持续时间,而是变化的,最短的时间为 30 秒,最长的时间为 4 分钟。总体分布呈三角形。
我们怀疑客户的涌入少于 1 个客户/秒,但不会少很多。可能会出现短时间内大大超过平均速率的客户激增。
鉴于上述所有情况,我们正在努力找出我们能够处理的最高顾客到达率。
让我们生成处理时间的分布,以及客户到达时间的分布。
import pandas as pd
import numpy as np
from scipy import stats as st
from matplotlib import pyplot as plt
from matplotlib import ticker
import seaborn as sns
sns.set_theme()
sns_col = sns.color_palette()######## Customer arrival parameters ######### duration of initial wave in seconds
cust_peak_length = 3600
cust_peak_center = cust_peak_length // 2
# number of customers in the initial wave
cust_peak_count = 5000
# val=3 makes sure the edges of the peak are near zero
cust_peak_sigmas = 3cust_peak_dist = st.truncnorm.rvs(
-cust_peak_sigmas,
cust_peak_sigmas,
loc=cust_peak_center,
scale=cust_peak_center / cust_peak_sigmas,
size=cust_peak_count,
)# steady state inflow, customers / second
cust_flow_rate = 0.8
cust_per_min = 60 * cust_flow_rate
cust_per_hour = 60 * cust_per_min
# steady state begin moment
cust_flow_start = cust_peak_center
# steady state end moment
cust_flow_end = cust_flow_start * 10
# total number of customers in the steady state flow
cust_flow_total = int((cust_flow_end - cust_flow_start) * cust_flow_rate)# Generate customer arrival times distribution
cust_flow_dist = np.random.uniform(cust_flow_start, cust_flow_end, size=cust_flow_total)
cust_dist = np.concatenate((cust_peak_dist, cust_flow_dist)).astype(int)######## Infrastructure parameters ######### number of machines
M = 100# processing time central value in seconds
proc_time_center = 120
# processing time minimum
proc_time_min = 30
# processing time maximum
proc_time_max = 240# Generate pre-ordained processing times
proc_time_dist = np.random.triangular(proc_time_min, proc_time_center, proc_time_max, size=cust_dist.shape[0])
让我们想象一下分布情况:
fig, ax = plt.subplots(1, 2, figsize=(16, 5))
label_format = '{:,.2f}'
bins = 50ax[0].hist(proc_time_dist, bins=50, color=sns_col[0])ax[0].set_title("Distribution of processing durations", fontsize=18)
ax[0].set_xlabel("durations of tasks (sec)")
ax[0].set_ylabel("count of tasks")ax[1].hist(cust_dist, bins=50, color=sns_col[1])
ax1_bin_width = cust_flow_end / binsax1_xticks_loc = ax[1].get_xticks().tolist()
ax[1].xaxis.set_major_locator(ticker.FixedLocator(ax1_xticks_loc))
ax[1].set_xticklabels([label_format.format(x / 3600) for x in ax1_xticks_loc])ax1_yticks_loc = ax[1].get_yticks().tolist()
ax[1].yaxis.set_major_locator(ticker.FixedLocator(ax1_yticks_loc))
ax[1].set_yticklabels([label_format.format(y / ax1_bin_width) for y in ax1_yticks_loc])ax[1].set_title("Rate of customer arrivals", fontsize=18)
ax[1].set_xlabel("time (hours)")
ax[1].set_ylabel("customers / second")
plt.show()

正如我们所说,处理时间呈三角形分布,峰值在 2 分钟。
我们首先要尝试的客户到达率是 0.8 客户/秒,或 48 客户/分钟。但是最初将会有 5000 名顾客的激增,他们都在 1 小时内到达,服从截断的正态分布。
我们已经定义了模型参数。让我们看一下代码。
实际的模拟代码
模拟完全在此函数中执行:
def avg_wait_time(mn, cust_dist, proc_time_dist, DEBUG=False):
# the state of each machine
# 0 = idle
# 1 = busy
machine = np.full((mn,), 0, dtype=int)
# total number of customers
c = cust_dist.shape[0]# status column:
# -1 = not here yet
# 0 = waiting customer
# 1 = processing customer
# 2 = customer is done
state_dict = {
"pt": proc_time_dist, # pre-destined processing times
"status": np.full((c,), -1, dtype=int),
"machine": np.full((c,), -1, dtype=int), # machine ID assigned to customer
"time_went_in": cust_dist, # arrival time
"time_went_out": np.zeros((c,), dtype=int), # task complete time
"time_wait": np.zeros((c,), dtype=int), # length of wait
}
state = pd.DataFrame(state_dict)
# average wait times of currently waiting customers
# updated every time increment
proc_history = {
'curr_wait_avg': [],
'busy_machines': [],
'waiting_cust': []
}t = 0
while True:
#### update history
# current average wait time for waiting and processing customers
cwt = state[(state['status'] == 0) | (state['status'] == 1)]['time_wait'].mean()
cwt = 0 if cwt != cwt else cwt
proc_history['curr_wait_avg'] += [cwt]
proc_history['busy_machines'] += [machine.sum()]
proc_history['waiting_cust'] += [state[(state['status'] == 0) | (state['status'] == 1)].shape[0]]
if DEBUG and t % 100 == 0:
print(t, cwt, machine.sum(), proc_history['waiting_cust'][-1])
# clock tick for waiting customers
dfmask = (state['status'] == 0) | (state['status'] == 1)
state.loc[dfmask, 'time_wait'] += 1
# customers have just arrived, put them in the queue
dfmask = (state["status"] == -1) & (t >= state["time_went_in"])
state.loc[dfmask, "status"] = 0
# processing has just completed for these customers
# take them out of the machine pool
dfmask = (state["status"] == 1) & (t - state["time_went_in"] >= state["pt"])
state.loc[dfmask, "status"] = 2
state.loc[dfmask, "time_went_out"] = t
machines_go_idle = list(state.loc[dfmask, 'machine'].values)
machine[machines_go_idle] = 0
state.loc[dfmask, "machine"] = -1
# find any idle machines
# if there are any, find waiting customers for them
idle_machines = list(np.where(machine == 0)[0])
dfmask = (state['status'] == 0)
waiting_customers = dfmask.loc[dfmask == True].index.to_list()
if len(idle_machines) > 0 and len(waiting_customers) > 0:
num_changes = min(len(idle_machines), len(waiting_customers))
for i in range(0, num_changes):
cust = waiting_customers[i]
mach = idle_machines[i]
machine[mach] = 1
state.at[cust, "status"] = 1
state.at[cust, "machine"] = mach
if np.all((machine == 0)) and (state['status'] == 2).all():
break
t += 1
return state, proc_history
该函数的参数是:
- 机器的数量
- 顾客到达时间的分布
- 加工时间的分布
状态数据框跟踪模拟的状态。每条线都是客户。各列记录分配给每个客户的数据:
- 他们的处理时间(因为它是随机的,所以可以预先指定)
- 它们的状态:尚未到达、已到达但未处理、正在处理、完成
- 分配给客户的机器
- 到达时间、结束时间和处理时间(最后一个是多余的,但很方便)
状态数据框架将贯穿整个模拟过程,并将根据每个客户的结果在每一步进行更新。
proc_history 包含几个列表,我们在这些列表中跟踪模拟的每一步的各种统计数据,可以把它看作一个日志:
- 模拟每一步的平均等待时间
- 每一步的繁忙机器数量
- 每个步骤中等待的顾客数量
每次模拟在时间上向前迭代,新的值都被添加到 proc_history 中的列表中。这个变量随着时间不断增长。
算法:
- 我们从 t = 0 开始,在时间上向前迭代
- 在每一步,我们更新 proc_history
- 如果客户正在等待,我们会更新他们的等待时间
- 如果客户刚刚到达,我们会将他们的状态更改为“等待”
- 如果客户完成了处理,我们会更新他们的统计数据和机器状态
- 如果我们发现闲置的机器,并且有客户在等待,我们就开始处理它们
- 我们每走一步,时间就增加 1 秒
- 如果所有机器都空闲并且所有客户都完成了处理,我们就停止模拟
该函数返回状态数据帧和 proc_history 列表。这是模拟的典型情况,您可能希望在结束时返回详细的最终状态,以及整个运行过程中重要变量的历史记录。
向量数学
你可能已经注意到在整个函数中只有一个 for 循环。这是故意的。
事实上,函数中隐藏了许多循环(大多数代码块实际上都是循环),有一种方法可以编写包含许多循环的函数——迭代数据框中的所有行,等等。那会非常慢。所示代码在我的笔记本电脑上运行大约需要 1 分钟。对于 for 循环,至少需要 10 倍的时间,甚至更长。
您必须尽可能使用这种编码风格 Numpy 和 Pandas 代码的矢量化形式——否则模拟会很慢。事实上,那个单独的 for 循环也可能被消除。
如果不熟悉向量形式,代码可能很难读懂,但是性能提升绝对值得。
结果
让我们调用函数并收集结果。
%%time
final_state, proc_history = avg_wait_time(M, cust_dist, proc_time_dist)
我的笔记本电脑的总运行时间是 1 分 4 秒。
我们有很多方法可以探索结果。让我们来看看这些数据的几个横截面:
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 9))
plt.subplots_adjust(hspace = 0.4)ax1.plot(np.convolve(proc_history['curr_wait_avg'], np.ones(100), 'valid') / 100, color=sns_col[0])
ax1.set_title("Current average wait duration for customers", fontsize=18)
ax1.set_xlabel("time (hours)")
ax1.set_ylabel("wait duration (sec)")
ax1_xticks_loc = ax1.get_xticks().tolist()
ax1.xaxis.set_major_locator(ticker.FixedLocator(ax1_xticks_loc))
ax1.set_xticklabels([label_format.format(x / 3600) for x in ax1_xticks_loc])ax2.plot(proc_history['busy_machines'], color=sns_col[1])
ax2.set_title("Current number of busy machines", fontsize=18)
ax2.set_xlabel("time (hours)")
ax2.set_ylabel("busy machines")
ax2_xticks_loc = ax2.get_xticks().tolist()
ax2.xaxis.set_major_locator(ticker.FixedLocator(ax2_xticks_loc))
ax2.set_xticklabels([label_format.format(x / 3600) for x in ax2_xticks_loc])ax3.plot(proc_history['waiting_cust'], color=sns_col[2])
ax3.set_title("Current number of waiting customers", fontsize=18)
ax3.set_xlabel("time (hours)")
ax3.set_ylabel("waiting customers")
ax3_xticks_loc = ax3.get_xticks().tolist()
ax3.xaxis.set_major_locator(ticker.FixedLocator(ax3_xticks_loc))
ax3.set_xticklabels([label_format.format(x / 3600) for x in ax3_xticks_loc])ax4.hist(final_state['time_wait'], bins=50, color=sns_col[3])
ax4.set_title("Final distribution of wait durations", fontsize=18)
ax4.set_xlabel("wait duration (sec)")
ax4.set_ylabel("count")plt.savefig("user_load.png", bbox_inches="tight")
plt.show()

左上角的蓝线是所有“当前正在等待”的客户在不同时刻的平均等待时间。在最初的高峰期间,等待时间很长,但随后会减少到接近平均处理时间。
右上角的橙色线是繁忙机器的数量。他们大部分时间都很忙。
左下角的绿线是顾客排队的长度。这符合你对到达率的预期。
右下角是整个人群等待时间的最终分布。按照处理时间,大多数客户只等待 1 … 4 分钟,但也有少数例外,他们等待的时间要长得多。让我们仔细看看异常值:
fig, ax = plt.subplots(figsize=(7.22, 4))
ax.hist(
final_state[final_state["time_wait"] > proc_time_max]["time_wait"],
bins=50,
color=sns_col[3],
)
ax.set_title("Distribution of wait durations for outliers", fontsize=18)
ax.set_xlabel("wait duration (sec)")
ax.set_ylabel("count")
plt.savefig("outlier_wait.png", bbox_inches="tight")
plt.show()

这些大多是最初激增时的客户。他们中的一些人等了相当长的时间,有时超过 30 分钟,但他们不是很多。
探索不同的场景
让我们再次运行模拟。这一次,我们将客户流量从每秒 0.8 个增加到 0.9 个。其他一切保持不变。

显然,没什么变化。但是有一个警告信号:目前的等待时间已经变得很长了。有时,由于随机波动,等待时间会显著增加。这可能表明我们已经接近临界点了。
我们把客户涌入量提高到 1 个客户/秒。

你基本上是在看一场灾难。在整个模拟过程中,所有的机器都全力以赴地运行,然而这还不够:有许多客户被困在等待中,等待时间很长。对异常值的观察证实了这一点:

数百名顾客等待长达 30 分钟,还有一些等待几个小时。显然,就这台机器集群所能做的事情而言,我们已经达到了一个阈值。
还有一件事可以尝试:让我们将持续的客户流入保持在 1 个客户/秒,同时消除最初的激增。换句话说,如果我们假设没有大的波动,集群能处理这样的到达率吗?

答案是否定的。排队的队伍越来越长。最初的激增在最终结果中只起了很小的作用。集群根本没有能力处理这种流入。
进一步分析
到目前为止,我们所做的只是视觉化,我们将到此为止。本文旨在介绍与模拟相关的基本概念。
但是在实际场景中,可能会涉及到几个变量:不同大小的多个集群、不同的处理时间、消耗的各种资源等。您可能必须将一些结果作为优化度量(比如:平均等待时间),并系统地探索参数空间(就像 scikit-learn 中的 GridSearchCV)以寻找最佳结果。
运行这样的模拟很多很多次,寻找参数的最佳组合,这并不罕见。如果你已经为机器学习模型进行了超参数调整,这在概念上是非常相似的——你正在参数空间中寻找最佳结果。
如果这听起来很开放,那是因为它是。在某些方面,您在这里比在机器学习模型中有更多的自由,但一些相同的通用技术仍然适用。
就时间(任务持续时间、客户等待时间)而言,100 台机器和 5000 个客户与 10 台机器和 500 个客户是一样的,但前一个模型给出的结果对波动的依赖性较小。中心极限定理对你有利。总是运行你能负担得起的最大模拟,或者多次运行小模拟,以确保波动不会影响结果。
最佳化
我们已经对大部分代码进行了矢量化,但是 Numpy 和 Pandas 是单线程的,这是这些库的基本限制。对于非常大的模拟,您可能需要研究分布式处理库,如 Apache Spark。
默认情况下,模拟的最外层循环是顺序的,您不能将其并行化,因为它反映了时间的流动,并且时刻 t 取决于之前的所有时刻 t-1、t-2……。但是您绝对可以并行化内部代码块。
为了更大的速度提升,丢弃数据帧,到处使用数组,并使用 GPU 加速的 Numpy 等效物,如 Cupy。对于小型模拟来说,这不值得麻烦,事实上它可能会更慢。对于大型模拟,速度提升非常大,通常至少一个数量级,并且随着模拟的变大,速度提升也会变大。
当然,您可以编写跨多个 GPU 运行的分布式代码,以获得最终的速度提升。但是代码的复杂性将会非常大。
最后的话
包含所有代码的笔记本可在此处获得:
https://github.com/FlorinAndrei/misc/blob/master/load_simulation/user_wait_time.ipynb
所有图片均由作者创作。




浙公网安备 33010602011771号