TowardsDataScience-2023-博客中文翻译-二十八-
TowardsDataScience 2023 博客中文翻译(二十八)
排名算法介绍
了解排序搜索结果的主要排名算法
·
关注 发布于 Towards Data Science · 12 分钟阅读 · 2023 年 8 月 16 日
--
介绍
学习排序(LTR)是一类监督式机器学习算法,旨在根据与查询的相关性对一组项目进行排序。在经典机器学习中,如分类和回归问题,目标是根据特征向量预测单一值。LTR 算法在一组特征向量上操作,并预测项目的最佳排序。
LTR 有许多不同的应用。以下是其中的一些:
-
搜索引擎。用户在浏览器搜索栏中输入查询。搜索引擎应该以一种方式对网页进行排名,使得最相关的结果出现在最上面的位置。
-
推荐系统。一个电影推荐系统,根据输入查询选择应该推荐给用户的电影。
让我们正式定义排序问题:
给定一个存储查询和文档信息的 n 维特征向量,排序的目标是找到一个函数 f,该函数产生一个实数,表示查询与文档的相关性。此外,如果对象 i 排名高于对象 j(i ▷ j),则 f(i) 应该大于 f(j)。
注意。i ▷ j 表示文档 i 排名高于文档 j。
数据。
特征向量。
特征向量包括三种类型的特征:
-
仅从文档中派生的特征(例如,文档长度,文档中的链接数量)。
-
仅从查询中派生的特征(例如,查询长度,查询的频率)。
-
从文档和查询的组合中派生的特征(例如,TF-IDF,BM25,BERT,文档和查询中共同出现的单词数量)。
训练数据。
为了训练模型,我们需要将训练数据输入到模型中。根据训练数据的收集方式,有两种可能的方法。
-
离线 LTR。数据由人工手动注释。人工对不同查询和文档的对(查询,文档)相关性进行评分。这种方法成本高且耗时,但提供高质量的注释。
-
在线 LTR。数据是通过用户与查询结果的互动(例如,对排序项目的点击次数,网页上的停留时间)隐式收集的。在这种情况下,获得训练数据是简单的,但用户互动的解释并不容易。
之后,我们有了特征向量及其对应的标签。这就是训练模型所需的一切。下一步是选择最适合问题的机器学习算法。
排序类型。
从高层次来看,大多数 LTR 算法使用随机梯度下降来找到最优的排序。根据算法在每次迭代中如何选择和比较项目的排名,存在三种主要方法:
-
点对点排序。
-
成对排序。
-
列表排序。
所有这些方法都将排序任务转化为分类或回归问题。在接下来的部分,我们将看到它们如何在幕后运作。
点对点排序。
在点对点方法中,为每个特征向量单独预测分数。最终,预测的分数被排序。使用哪种类型的模型(决策树,神经网络等)进行预测并不重要。
这种类型的排序将排序问题转化为回归任务,其中回归模型试图根据选择的损失函数(例如,MSE)预测正确的相关性。
另一种有效的方法是将真实排名转化为独热编码表示,并将这些数据输入模型。在这种情况下,可以使用回归模型或分类模型(带有交叉熵损失)。

逐点模型架构。作为输入,模型接受一个查询和一个特征向量。
尽管这种方法非常简单,但它存在以下列出的一些问题。
类别不平衡
使用逐点方法时的一个常见问题是类别不平衡。如果在现实生活中随机选择一个查询,那么很可能只有集合中的一小部分文档与之相关。因此,在训练数据中,相对文档与查询之间存在很高的不平衡。
虽然可以克服这个问题,但还有一个更严重的问题需要考虑。
糟糕的优化指标
逐点排名在优化目标上存在一个主要的基本问题:
逐点排名独立地优化文档得分,而没有考虑不同文档之间的相对得分。因此,它没有直接优化排名质量。
请看下面的例子,其中一个逐点算法对两组文档进行了预测。我们假设在训练过程中优化了均方误差(MSE)损失。

集合包含 5 个文档,其中 1 个文档是相关的,其他 4 个是无关的。对于相关文档,预测相关性为 0.7,而对于其他文档为 0.5。

集合包含 5 个文档,其中 1 个文档是相关的,其他 4 个是无关的。对于相关文档,预测相关性为 0.1,而对于其他文档为 0.2。
给定两个排名结果,我们可以看到从算法的角度来看,第二个排名更好,因为对应的 MSE 值较低。然而,选择第二个排名意味着用户会首先看到所有无关的结果。顺便提一下,在第一个例子中,相关结果首先显示,这在用户体验上要好得多。通常,用户不会太关注之后的推荐内容。
这个例子表明,在现实生活中,我们更关心的是首先展示相关结果以及项目的相对顺序。通过独立处理文档,逐点模型无法保证这些方面。较低的损失并不等同于更好的排名。
对输入对排名
对输入对模型在每次迭代中处理一对文档。根据输入格式的不同,有两种类型的对输入对模型。
对输入对模型
模型的输入是两个特征向量。模型输出是第一个文档排名高于第二个文档的概率。在训练过程中,这些概率是为不同的特征向量对计算的。模型的权重通过基于真实排名的梯度下降进行调整。

对输入对模型架构。作为输入,模型接受一个查询和两个连接的特征向量。
这种方法在推理过程中有两个主要缺点:
-
为了在推理过程中对给定查询的n个文档进行排名,每对这些文档都需要由模型处理以获得所有成对概率。总对数是平方的(准确等于n * (n — 1) / 2),这非常低效。
-
即使拥有所有文档的成对概率,也不明显如何最终排名,尤其是在像恶性循环这样的矛盾情况下,其中有三元组文档(x, y, z),模型以如下方式对其进行排名:x ▷ y, y ▷ z 和 z ▷ x。

恶性循环问题
由于这些缺点,成对输入模型在实践中很少使用,单输入模型被优先考虑。
单输入模型
模型接受一个单一的特征向量作为输入。在训练过程中,成对中的每个文档都独立地输入到模型中以接收其分数。然后比较这两个分数,并通过基于真实排名的梯度下降来调整模型。

单输入模型架构。作为输入,该模型接收一个查询和一个表示文档的特征向量。在模型对两个特征向量独立分配分数后,计算排名预测。
在推理过程中,每个文档通过传递到模型中来接收一个分数。然后对这些分数进行排序,以获得最终排名。
对于熟悉 Siamese 网络(FaceNet,SBERT 等)的人来说,单输入模型可以被认为是 Siamese 网络。
成对损失函数
在每次训练迭代中,模型对一对文档预测分数。因此,损失函数应为成对的,并考虑两个文档的分数。
一般来说,成对损失函数以z为其参数,其中z是两个分数s[i] — s[j]的差异乘以一个常数σ。根据算法的不同,损失函数可以具有以下形式之一:

成对排名的不同损失函数
有时分数差异z可以乘以一个常数。
RankNet是最流行的成对排名算法之一。我们将在下一部分详细了解其实现细节。
RankNet
在获得文档i和j的分数后,RankNet 使用 softmax 函数进行归一化。通过这样做,RankNet 获得文档i比文档j排名更高的概率P[i][j] = P(i ▷ j)。反之,我们可以计算概率P̃[j][i] = P(j ▷ i) = 1 — P(i ▷ j)。为了简便起见,假设实际情况是 i 的排名高于 j,所以P̃[i][j] = 1和P̃[j][i] = 0。对于模型权重的更新,RankNet 使用交叉熵损失,其简化如下:

RankNet 损失函数
模型的权重通过梯度下降进行调整。下次模型遇到相同的文档对 i 和 j 时,文档 i 的得分可能会比之前更高,而文档 j 可能会被推得更低。
RankNet 分解
为了简化,我们不会深入探讨数学内容,但原始论文中提出了一个有趣的研究结果,作者找到了一种简化训练过程的方法。这是通过引入变量 S[i][j] 来实现的,该变量可以取三个可能的值之一:

经过一些数学技巧,交叉熵损失的导数被分解为:

公式中的 λ 值是一个常数,可以相对快速地计算所有文档对的 λ 值。通过取正值或负值,这些 λ 值充当了推动文档上升或下降的力量。
我们可以对单个文档 i 汇总所有成对的 λ 值。这个总和表示在排名中应用于文档 i 的总力量。

对于找到应用于文档的总力量的 λ 值的汇总
直接使用 λ 函数可以得到更快的训练时间和更好的结果解释。
尽管成对算法比逐点方法表现更好,但它们有两个缺点。
不可解释的概率
模型输出的概率只是显示模型对某个对象 i 排在对象 j 之上的信心。然而,这些概率并不真实,有时模型可以粗略地近似这些概率,因此不应该总是用它们来进行解释,尤其是在之前看到的有恶性循环的混乱情况下。
最小化倒置不是最优解
这个问题比之前的问题更为严重。大多数成对算法,特别是 RankNet 的根本问题在于它们最小化排名倒置的数量。虽然优化倒置数量可能看起来很自然,但这并不是大多数最终用户真正想要的。考虑以下两个排名。你认为哪个排名更好?

两个相关的文档分别位于列表的开头和结尾。

两个相关的文档位于列表的中间偏左位置。
尽管第二个排名的倒排数较少,这是算法的优先考虑,但普通用户仍然会更喜欢第一个排名,因为顶部至少有一个相关结果。这意味着用户不需要滚动大量文档才能找到第一个相关结果。同样,使用如nDCG或ERR这样的用户导向指标会更好,因为这些指标更注重顶部结果而非倒排数量。
结果显示,并非所有文档对都同样重要。算法需要调整,以更重视正确的顶部排名,而不是底部排名。
论文的研究人员提供了一个很好的例子,说明如何通过 RankNet 优化排名可能导致非最优结果:

我们可以看到,第 1 位的文档被推到了第 4 位,第 15 位的文档被推到了第 10 位,所以总的倒排数减少了 2。然而,从用户体验来看,新排名变得更糟。核心问题在于 RankNet 为排名较差的文档分配了更大的梯度。然而,为了优化用户导向的指标,这应该是反向的:排名较好的文档应被进一步推高,而不是排名较差的文档。这样,像nDCG这样的用户导向指标将会更高。
列表排名
列表算法明确优化排名指标。为了使用梯度下降优化某个指标,需要为该指标计算导数。不幸的是,大多数排名指标如nDCG或precision是非连续且不可微的,因此发明了其他高级技术。
与点对点或对对排名方法不同,列表方法一次处理整个文档列表。这有时会导致大量计算,但也提供了更强的鲁棒性,因为算法在每次迭代时获得了更多的信息。

列表模型架构。作为输入,模型接收一个查询和所有文档的特征向量。
LambdaRank
根据实现的不同,LambdaRank 可以被认为是点对点或列表方法。
当关注nDCG时,似乎最优的做法是为那些位置交换结果能提高nDCG的文档对分配更大的梯度。这一核心思想在于LambdaRank。
算法名称中的“lambda”暗示 LambdaRank 还使用了 RankNet 中描述的 lambda 进行优化。
研究人员取得了惊人的成果,并证明如果 RankNet 中的损失值乘以|nDCG|,则算法倾向于直接优化nDCG!也就是说,LambdaRank 算法与 RankNet 非常相似,只是这次 lambda 被乘以nDCG的变化:

nDCG 优化公式
研究中另一个令人惊讶的事实是,这种技巧不仅对 nDCG 有效,而且对其他信息检索度量也有效!类似地,如果将 λ 乘以精度变化,那么 precision 将得到优化。
最近,理论上已经证明 LambdaRank 可以优化某些信息检索度量的下界。
其他方法
我们不会详细讨论其他基于列表的方法如何工作,但仍会提供两个优秀算法的主要实现思想。
LambdaMart 是一种著名的基于列表的方法实现,它使用从 LambdaRank 派生的损失函数的梯度提升树。在实际应用中,它比 LambdaRank 表现更好。
SoftRank 解决了 nDCG 的导数存在问题。它创建了一种新的度量标准——“SoftNDCG”,它平滑地表示并近似 nDCG,使得可以找到相应的导数,并通过梯度下降更新模型的权重。事实上,这种方法也可以同样应用于其他度量标准。
结论
我们已经讨论了排名——这是机器学习中的一项重要任务,用于将一组对象按相关顺序排序。点对点和对对对方法不常使用,而基于列表的方法最为稳健。显然,我们只讨论了排名算法的一个小部分,但这些信息对于理解更复杂的技术如 ListNet 或 ListMLE 是必不可少的。可以在 这里 找到一个详细的列表方法算法。
值得注意的是,LambdaRank 目前是最先进的排名算法之一,为优化特定度量提供了很大的灵活性。
如果你想了解更多关于排名度量的信息,我强烈建议你阅读我关于这个话题的另一篇文章。
探索丰富的度量选择,找到最适合你问题的度量
towardsdatascience.com
资源
除非另有说明,否则所有图片均为作者所用
抽样方法介绍
原文:
towardsdatascience.com/introduction-to-sampling-methods-c934b64b6b08
在 Python 中实现逆变换抽样、拒绝抽样和重要性抽样
·发表于Towards Data Science ·8 min 阅读·2023 年 1 月 10 日
--

图片由Edge2Edge Media拍摄,发布在Unsplash
在这篇文章中,我们将讨论如何从概率分布中抽样。这是一个常见的需求,在机器学习环境中,这种方法最常用于对概率模型进行推断。然而,由于分布非常复杂,这通常是不可行的。因此,本帖的主要重点是介绍该任务的近似方法,特别是使用数值抽样,称为蒙特卡罗方法。
尽管如此,为了介绍目的,我们将首先介绍逆变换抽样,它允许对任意可处理的分布进行精确推断。然后,我们将把重点转向近似方法,允许对(接近)任意分布进行抽样或矩估计:我们从拒绝抽样开始,然后转向重要性抽样。
请注意,这篇文章是一个系列的第一篇,旨在帮助读者熟悉[1],特别是本帖涵盖了第十一章:抽样方法的部分内容。
逆变换抽样
逆变换抽样方法允许从任何我们知道如何计算其累积分布函数(CDF)的分布中进行抽样。它包括从U[0, 1](均匀分布)中抽取y,然后计算所需的x,其计算公式如下:

即,我们正在生成一个随机变量

然后声称

— 即累计分布的逆(记作F)遵循我们期望的目标分布(更具体地说,是其概率密度函数,记作f)。
当且仅当 CDF 相同,即:

为了证明这一点,我们来检查左侧,并对方程两边应用 F:

由于对[0, 1]上的均匀分布,我们有

我们得到,如所愿:

(证明取自COMP 480 / 580)。
Python 实现
让我们对指数分布进行尝试,其定义为 pdf:

取 CDF 并进行反转得到:

这就给出了我们想要的采样公式!让我们将其转换为 Python 代码。
我们首先定义一个函数exp_distr,用于评估给定x值的 pdf,然后定义一个函数exp_distr_sampled,用于通过逆变换方法和我们上面推导的公式从指数分布中采样。
最后,我们绘制真实的概率密度函数(pdf)和我们采样值的直方图:
import numpy as np
import matplotlib.pyplot as plt
NUM_SAMPLES = 10000
RATE = 1.5
def exp_distr(x: np.ndarray, rate: float) -> np.ndarray:
return rate * np.exp(-x * rate)
def exp_distr_sampled(num_samples: int, rate: float) -> np.ndarray:
x = np.random.uniform(0, 1, num_samples)
return -1 / rate * np.log(1 - x)
x = np.linspace(0, 5, NUM_SAMPLES)
y_true = exp_distr(x, RATE)
y_sampled = exp_distr_sampled(NUM_SAMPLES, RATE)
plt.plot(x, y_true, "r-")
plt.hist(y_sampled, bins=30, density=True)
plt.show()
运行此代码会生成类似以下的结果,显示我们的采样确实产生了正确的结果:

数值采样
如果可能,逆变换采样效果完美。然而,如前所述,它要求我们能够反转 CDF。这仅适用于某些更简单的分布——即使对于正态分布,这也显著复杂,尽管可能。当这太困难或不可能时——我们必须诉诸其他方法,我们将在本节中介绍这些方法。
我们首先进行拒绝采样,然后引入重要性采样,最后讨论这些方法的局限性和展望。
注意,两种方法仍然要求我们能够评估给定x的p(x)。
拒绝采样
拒绝采样涉及引入一个更简单的提议分布q,我们可以从中采样,并用它来近似p。它遵循的思想是q(x) ≥ p(x)对于所有x,我们从q中采样,但拒绝所有高于p的样本——从而最终得到分布p。
如上所述,虽然可以将逆变换采样应用于正态分布,但这很困难——因此为了演示,我们在这里尝试逼近一个正态分布(p),并使用均匀分布作为提议分布(q)。让我们在这个设置中可视化上述直觉:

现在,通过拒绝采样,我们会从类似均匀分布的q中采样,并拒绝所有高于p的样本——因此在极限情况下,得到的点将完全填满p下方的区域,即从该分布中采样。
从形式上看,我们首先选择提议分布q,然后选择一个缩放系数k,使得kq(x) ≥ p(x)对所有x都成立。接下来,我们从q中采样一个值x_0。然后,我们从[0, kq(x_0)]上的均匀分布中生成一个值u_0。如果u_0 > p(x_0),我们拒绝该样本,否则接受它。
让我们用 Python 实现这一点:
from typing import Tuple
import matplotlib.pyplot as plt
import numpy as np
NUM_SAMPLES = 10000
MEAN = 0
STANDARD_DEVIATION = 1
MIN_X = -4
MAX_X = 4
def uniform_distr(low: float, high: float) -> float:
return 1 / (high - low)
def normal_dist(
x: np.ndarray, mean: float, standard_deviation: float
) -> np.ndarray:
return (
1
/ (standard_deviation * np.sqrt(2 * np.pi))
* np.exp(-0.5 * ((x - mean) / standard_deviation) ** 2)
)
x = np.linspace(MIN_X, MAX_X, NUM_SAMPLES)
y_true = normal_dist(x, MEAN, STANDARD_DEVIATION)
def rejection_sampling(
num_samples: int, min_x: float, max_x: float
) -> Tuple[np.ndarray, float]:
x = np.random.uniform(min_x, max_x, num_samples)
# uniform_distr(-4, 4) = 0.125 -> we need to scale this to ~0.5, i.e.
# select k = 4.
k = 4
u = np.random.uniform(np.zeros_like(x), uniform_distr(min_x, max_x) * k)
(idx,) = np.where(u < normal_dist(x, MEAN, STANDARD_DEVIATION))
return x[idx], len(idx) / num_samples
y_sampled, acceptance_prob = rejection_sampling(NUM_SAMPLES * 10, MIN_X, MAX_X)
print(f"Acceptance probability: {acceptance_prob}")
plt.plot(x, y_true, "r-")
plt.hist(y_sampled, bins=30, density=True)
plt.show()
得到以下结果:

接受概率:0.24774
重要性采样
通常,我们只对期望感兴趣,这也是重要性采样发挥作用的地方:它是一种通过再次使用提议分布q来逼近复杂分布p的期望(或其他矩)的技术。
一个(连续)随机变量 X 的期望定义为:

然后我们使用一个小数学技巧将其重新表达为:

那么这个公式说明了什么?这是

在q下——即为了计算E[X],我们现在可以在从q采样时评估这个项!系数p(x) / q(x)被称为重要性权重。
为了演示,我们再次将p设为正态分布——并使用另一种正态分布作为q:
import numpy as np
NUM_SAMPLES = 10000
MEAN_P = 3
MEAN_Q = 2
def normal_dist(
x: np.ndarray, mean: float, standard_deviation: float
) -> np.ndarray:
return (
1
/ (standard_deviation * np.sqrt(2 * np.pi))
* np.exp(-0.5 * ((x - mean) / standard_deviation) ** 2)
)
q_sampled = np.random.normal(loc=MEAN_Q, size=NUM_SAMPLES)
p_sampled = (
q_sampled
* normal_dist(q_sampled, MEAN_P, 1)
/ normal_dist(q_sampled, MEAN_Q, 1)
)
print(
f"Resulting expecation when sampling from q {np.mean(p_sampled)} vs true expecation {MEAN_P}"
)
在代码中,我们将p设为均值为 3 的正态分布。然后,我们从提议分布q中采样NUM_SAMPLES,该分布是均值为 2 的正态分布——并利用上述介绍的变换来计算在p下的X的期望——得到的结果大约是正确的(~3 对比 3)。
让我们以对方差的讨论结束这一部分:结果样本的方差将是

对于我们的情况,这意味着方差会随着p和q之间的差异(即不一致)增加。为了演示,比较MEAN_Q = 3.2和5的方差,我们分别得到~0.20 和~91.71。
不过需要注意的是,重要性采样也常作为方差降低技术,通过巧妙选择q来实现——然而这可能是未来文章的主题。
限制与展望
在这篇文章中,我们介绍了三种采样方法:逆变换采样、拒绝采样和重要性采样。
逆变换抽样可以用于相对简单的分布,对这些分布我们知道如何反转累积分布函数(CDF)。
对于更复杂的分布,我们必须求助于拒绝或重要性抽样。然而,对于这两种方法,我们仍需要能够评估所讨论分布的概率密度函数(pdf)。此外,还有其他缺点,例如:当我们不能用kq适当地“框住”p时,拒绝抽样是浪费的——在高维空间中尤其棘手。重要性抽样也是如此——特别是在高维空间中——很难找到适合的提议分布q以及合适的重要性权重。
如果上述提到的缺点过于严重,我们必须求助于更强大的方法——例如来自马尔可夫链蒙特卡洛方法(MCMC)家族。这些方法对我们想要近似的分布的要求要宽松得多,并且受到的限制也较少,例如在高维空间中。
这篇文章结束了对抽样方法的介绍。请注意,所有图片除非另有说明,均由作者提供。希望你喜欢这篇文章,谢谢阅读!
本文是关于抽样的系列的第一部分。你可以在这里找到其他部分:
-
第二部分: 利用重要性抽样进行方差减少
-
第三部分: 马尔可夫链蒙特卡洛(MCMC)方法简介
参考文献:
[1] Bishop, Christopher M., “模式识别与机器学习”,2006,www.microsoft.com/en-us/research/uploads/prod/2006/01/Bishop-Pattern-Recognition-and-Machine-Learning-2006.pdf
语音增强简介:第一部分 — 概念与任务定义
原文:
towardsdatascience.com/introduction-to-speech-enhancement-part-1-df6098b47b91
对改善退化语音质量或抑制噪声的概念、方法和算法的介绍
·发布于 Towards Data Science ·阅读时间 6 分钟·2023 年 1 月 17 日
--

图片由 Wan San Yip 提供,来源于 Unsplash
本文是系列文章的一部分:
-
语音增强简介:第一部分 — 概念与任务定义
语音增强是一套方法和技术,旨在通过语音音频信号处理技术提高语音质量,包括清除助听器中的噪声,或从嘈杂的信道/环境中恢复难以理解的语音信号 [Wikipedia]。它有许多实际应用,包括从噪声中清除语音信号或从嘈杂的信道/环境中恢复难以理解的语音信号。
它类似于但不同于语音分离[1],即将一个音频信号算法性地分离成两个不同的通道,分别用于语音和背景。可以应用语音增强来获取语音信号,然后应用额外的算法来计算残差信号,或原始信号与增强语音之间的差异。
不同的语音增强方法被应用于减少不同噪声模型(例如,平稳与非平稳)的影响。虽然这个研究领域并不新鲜,但近年来深度学习方法蓬勃发展,提升了在最具挑战性的非平稳噪声场景中的语音增强质量,即使在低信噪比(SNR)下也是如此。
术语与背景
信号: 观测一个变化且可测量的物理量。音频、视频、图像,都属于这个定义。
噪声模型: 这是描述噪声信号潜在随机过程的数学模型。由于该过程是随机的,通常用概率分布来描述。
(非)平稳过程: 假设信号由一个潜在的过程生成。它可以是确定性的或随机的。由于我们对确定性过程了解一切,因此兴趣在于随机过程。如果一个随机过程的无条件联合分布不依赖于时间,则称其为平稳的。换句话说,给定一个过程的模型,事件 X 在时间 t 发生的概率不依赖于 t 本身。通过扩展,由平稳过程生成的信号也称为平稳。在音频领域,当信号的频率或谱内容随时间变化时,信号就是平稳的。这意味着,对于语音增强,真正的挑战来自于非平稳噪声,因为它更不可预测且更难与语音区分,而人声也同样是非平稳的:我们声音信号中的频率一直在变化。
实验设置
与大多数实证研究领域类似,在语音增强中,我们需要数据集来训练我们的模型和评估它们,还需要评估结果的指标,以及运行算法的硬件和软件工具。
作为硬件要求,我们绝对需要一台配备有(至少)一个 NVIDIA GPU 的现代计算机进行训练,而现代多核 CPU 在推断过程中也可能足够,尽管预计会有较高的 CPU 使用率。
在软件方面,我们需要一个使用如 Tensorflow、Pytorch 或 Jax 派生库的代码库。上面链接的 FullSubnet+ 存储库可以是一个很好的起点。
然后,我们谈到数据。测量质量和实现监督学习的最简单方法是拥有一个网络应该重建的参考信号。因此,训练集通常通过收集干净的语音信号和噪声信号来构建。然后,通过将它们添加不同级别的信噪比(SNR)来将这两者结合起来。这样,在训练过程中,可以生成大量的语音/噪声组合,网络必须学习这些组合以获得增强的语音信号。研究社区通过共享任务来推动语音增强,因此可以从这些来源找到公开数据集。每年举办的Deep Noise Suppression (DNS) Challenge就在 Interspeech 上展示。在相关的代码库中,您可以找到提供的数据集链接以及其他资源。
评估通常通过手动和自动评估来进行。两者之间的权衡已被多次提及,但可能值得重复一下。手动评估由人工执行:它既昂贵又缓慢,因为需要找到、指导和支付人力,当然他们还需要在评估之前听取音频。自动指标则快速且便宜,因此在开发周期中可以重复使用,但它们只能捕捉评估的某些方面,并且在某些情况下可能不够可靠。
在语音增强的情况下,通常使用四种主要的自动评估指标:语音质量的感知评估(PESQ)、对数似然比(LLR)、倒谱距离(CD)和加权谱斜率距离(WSS)。所有这些指标都计算清晰语音和增强语音之间的距离。PESQ 比较两个信号样本之间的质量,产生一个在-0.5 到 4.5 之间的分数,分数越高越好。其他三个指标计算两个谱之间不同属性的距离:共振峰、对数谱距离或谱斜率的加权距离。当需要时,可以增加人工评估,以评估感知质量或统计增强信号中的可识别单词。语音识别可以用来识别单词,但这时我们需要处理它自身的错误。
关于信号表示,有些语音增强模型在时间域中工作,而其他模型则在频域中工作。频域是通过对信号应用傅里叶变换获得的,是对信号的非时间性表示,提供了关于其频率成分的信息。在实践中,频域表示比时间域表示更适用于语音增强,所有最先进的方法都使用它。
示例
让我们展示一个例子,以便对这个主题有听觉上的理解。我们从一个包含响亮音乐的视频广告开始,这可能会使语音难以听清。
以创作共享许可证发布
我们首先使用著名的ffmpeg工具提取音频轨道:
然后,我们应用 FullSubNet+算法[2],这是一种基于深度学习的方法,具有开源代码和预训练模型可用。按照仓库中的说明,很容易复现这一结果:
最后,为了满足你的好奇心,展示从原始信号中增强语音得到的残余音频:
enhanced_audio.wav中的语音质量显著提高,并且音乐的音量根据时间被完全去除或大幅降低。结果显示了现代语音增强的有效性,但也表明结果并不完美,该领域还有更多研究在进行。此外,通过运行代码,你会发现这一操作在计算上相当昂贵。拥有 GPU 会使过程更快,但在保持或改善质量的同时减少计算复杂性是一个重要的研究目标。
结论
本系列的第一部分到此为止!我们讨论了语音增强的理念和原因,并提供了一些关于数字信号的背景信息。
在本系列的第二部分,我们将详细介绍一个公开的最先进语音增强模型。
与此同时,我鼓励你阅读文章中的链接资源,如果你对这一主题感兴趣,可以查看数字信号处理的教学资料。其中一本免费提供的书籍是Think DSP。你可以在线阅读或下载 pdf,当然也可以购买纸质版以支持作者(我不会从中获得任何收益)。
感谢你一直阅读,希望在下一部分中见到你!
语音增强简介:第二部分 — 信号表示
了解深度学习和预训练模型如何用于自动语音评估的指南
towardsdatascience.com ## 使用 inotifywait 进行数据处理自动化
在拥有生产就绪的 MLOps 平台之前如何进行自动化
towardsdatascience.com ## 3 种常见的错误来源及如何避免它们
某些编码模式更容易隐藏错误。编写高质量代码并了解大脑的工作原理可以帮助…
towardsdatascience.com
参考文献
[1] Bahmaninezhad, Fahimeh 等. “语音分离的统一框架。” arXiv 预印本 arXiv:1912.07814 (2019)。
[2] 陈军等. “FullSubNet+: 基于通道注意力的 FullSubNet 与复杂谱图用于语音增强。” ICASSP 2022–2022 IEEE 国际声学、语音与信号处理会议(ICASSP)。IEEE,2022。 arxiv.org/abs/2203.12188
Medium 会员
你喜欢我的写作并考虑订阅 Medium 会员以无限访问文章吗?
如果你通过此链接订阅,你将支持我而不会增加额外费用 medium.com/@mattiadigangi/membership
语音增强介绍:第二部分 — 信号表示
原文:
towardsdatascience.com/introduction-to-speech-enhancement-part-2-signal-representation-ab1deca2fa74
让我们深入了解信号表示、傅里叶变换、谱和谐波。
·发布于 Towards Data Science ·阅读时间 10 分钟·2023 年 1 月 31 日
--

图片由 Richard Horvath 提供,来源于 Unsplash
本文是系列的一部分:
-
语音增强介绍:第二部分 — 信号表示
介绍
在深入语音增强的讨论之前,我们需要明确一些关于数字信号处理的概念。在本系列的前一章中,我们介绍了一些概念并添加了进一步阅读的链接。
在第二部分中,我们将探讨数字信号的表示方式、如何更改表示方式,以及傅里叶变换为何如此重要。代码和图示将帮助你理解并进一步探索这些例子。
信号基础
本文的主要目标是理解什么是信号,它如何表示,以及傅里叶变换的重要性,这是一种经过时间考验的根据需求改变表示方式的方法。
音频信号通常表示为随时间变化的振幅。由于我们讨论的是数字信号处理,记录的音频由在规则间隔收集的许多样本组成。通常测量的是采样频率而不是样本之间的间隔长度。采样频率或采样率是每秒钟的样本数量。秒的倒数(1/s)称为赫兹(Hz)。如果我们在一秒钟内有 10 个样本,则采样率为 10Hz。如果我们有 1000 个样本,则为 1 kHz(千赫兹)或 1000 Hz,依此类推,使用常见的科学前缀。

振幅为 1.0,频率为 10 Hz,采样率为 10 kHz 的正弦信号
与频率为 1Hz 的正弦信号进行比较。它看起来比之前的信号“更宽”。我们可以想象调整频率就像拉伸弹簧一样,

振幅为 1.0,频率为 1 Hz,采样率为 10 kHz 的正弦信号
采样率影响信号的感知质量,因为采样率不足会导致降级,如下图所示。

振幅为 1.0,频率为 10 Hz,采样率为 100 Hz 的正弦信号
在一些极其不足的采样率情况下,记录的信号可能完全不像原始的模拟信号:

相同信号的采样率为 10 Hz。样本均匀分布,并对应于函数的零值。请注意 y=0.0 处的小水平蓝线
这些基本示例向我们展示了选择足够高采样率的重要性。对于人类语音,采样率从 8kHz 开始,这是电话的采样率,当需要高精度时,可以轻松达到 44.1 kHz。上述绘制的信号可以通过以下代码重现。
import matplotlib.pyplot as plt
import numpy as np
def sin_signal(amp, freq, phase, sr):
"""Generate a sin signal with the given characteristics"""
x = np.arange(0, 2*np.pi, 1./sr) # 2*pi is a full period for a sinusoid
return amp*np.sin(freq*2*np.pi*x - phase) # full formula for a sin-shaped signal
def plot_signal(s):
x_ax = np.linspace(0, 1, len(s))
plt.plot(x_ax, s)
# Change these values to modify the signal
amp = 1.0 # signal maximum amplitude
freq = 10 # sin frequency
phase = 0.0 # signal phase
sr = 10000 # sampling rate
sig = sin_signal(amp, freq, phase, sr)
plot_signal(sig)
到目前为止,我们观察到改变频率可以使信号在水平轴上“变窄”或“变宽”,而采样率会影响其精度。我们还可以通过修改振幅在垂直轴上拉伸或压缩它:

振幅为 3.0,频率为 10 Hz,采样率为 10 kHz 的正弦信号

振幅为 0.5,频率为 10 Hz,采样率为 10 kHz 的正弦信号
这两个信号看起来可能相同,但快速查看垂直轴标签会发现,第一个信号的范围是从 -3.0 到 3.0,而第二个信号的范围是从 -0.5 到 0.5。最大(绝对)值与我们选择的振幅值相匹配。
我们需要理解的最后一个入门概念是信号的相位。它可以被认为是相对于参考起始时间的时间移位,并以弧度表示。让我们通过观察一个可视化的例子来理解它。之前展示的正弦信号的相位为 0,其函数可以表示为 sin(10 * 2πt),其中 10 是频率,2π是正弦函数的一个完整周期。周期是函数重复自身的间隔。现在,我们可以添加一个 π 弧度的相位,得到以下图像:

频率为 10 Hz,采样率为 10 Khz,相位为π的正弦波
我们可以看到,它就像是信号“向左移动”了 π 弧度的持续时间。我们也可以通过减去 π 的相位将其向右移动,但这样做不会显著显示(因为向左或向右移动 π,函数的样子是一样的),所以我们将其向右移动 π/2:

频率为 10 Hz,采样率为 10 Khz,相位为-π/2 的正弦波
我们之所以说“添加”和“减去”,是因为公式是 sin(f * 2πt + θ),其中 f 是频率,θ 是相位。
现在我们引入了一些概念,可以回到傅里叶变换及其如何帮助我们更好地理解信号。
傅里叶变换
本节的目标是为读者提供傅里叶变换的实用概念。关于傅里叶变换理论的资源很多,世界上不需要新的。建议感兴趣的读者跟随链接并查找更多资源。
傅里叶变换的基本假设是任何平稳信号都可以分解为不同频率的正弦函数的和,每个函数具有各自的振幅和相位。一个以正弦信号之和表示的信号被称为“频域”,而我们之前看到的则是在“时域”。
对信号应用傅里叶变换的结果,也称为分析,是一系列复数系数,每个系数对应于一个特定的频率。为什么是复数系数?我们可以将任何复数 z 用极坐标表示为 z = |z|e^(jθ),其中 |z| 是振幅,θ 是角度(或相位)。振幅和相位正是完全描述已知频率的正弦信号所需的全部信息。
傅里叶变换有不同类型,但最常见的是离散傅里叶变换(DFT)及其逆变换,即离散傅里叶逆变换(IDFT),这是我们处理离散信号记录所需的变换。
离散信号 x(n) 长度为 N 的离散傅里叶变换(DFT)定义为:
X(k) = Σ x(n) * e^(-j2πkn/N) 对于 k = 0,1,2,…,N-1
其中 X(k) 是离散傅里叶变换(DFT)的复数系数,k 是频率索引,n 是时间索引,N 是信号的长度,j 是虚数单位。
IDFT 是 DFT 的逆变换,它允许我们从 DFT 系数中恢复原始信号。
x(n) = 1/N * Σ X(k) * e^(j2πkn/N) 对于 n = 0,1,2,…,N-1
DFT 系数可以用来分析信号的频率内容,而逆离散傅里叶变换(IDFT)可以用来从频率分量合成信号。DFT 和 IDFT 被广泛应用于超出音频处理的许多领域,如图像和视频处理、通信系统和深度学习。
DFT 的一个重要属性是线性,即,在变换之前对信号进行的加法和乘法操作与对变换值进行的加法和乘法操作结果相同,反之亦然,因为逆离散傅里叶变换(IDFT)具有相同的属性。这使我们能够在频域中修改信号,我们可以修改各个分量,并且对时域信号的结果可以轻松预测。
信号的频谱表示
信号的频谱表示是将信号的频率内容在频域中表示的一种方法。
DFT 系数的幅度表示信号的幅度谱,幅度谱代表信号中每个频率分量的强度。幅度谱通常以分贝(dB)表示,分贝是对数尺度,使得解释不同频率分量的相对强度变得更容易。

频率为 440Hz,幅度为 1.0,初相位为 0.0 的正弦波的频谱。唯一的非零值出现在频率 440 Hz 处。
相位图更难以解释。

相同正弦波的相位谱
这两个图可以通过以下代码生成:
import matplotlib.pyplot as plt
import numpy as np
freq = 440
duration = 1.0
sr = 10000
n = round(duration * sr)
x = np.arange(0, n) / sr
a = np.sin(freq*2*np.pi*x)
f = np.fft.rfft(a)
freqs = np.fft.rfftfreq(n, 1./sr)
plt.plot(freqs, np.abs(f))
plt.plot(freqs, np.angle(f))
在图中,我们有复数函数来获取幅度和角度。
现在让我们看一个低通滤波器的示例,该滤波器减少高频的影响。我们通过将不同频率的正弦波相加来生成一个信号,然后对频率高于 1000 的信号幅度除以 2。
import matplotlib.pyplot as plt
import numpy as np
freqs = np.arange(10, 5000, 100)
duration = 1.0
sr = 10000
n = round(duration * sr)
x = np.arange(0, n) / sr
signal = sum(np.sin(freq*2*np.pi*x) for freq in freqs)
plt.plot(x, signal)

相同幅度的正弦波的和
频谱:

上述信号的频谱。所有非零频率的贡献相同。
现在我们按如下方式应用低通滤波器:
low_pass = np.array(f)
low_pass[freqs > 1000] = low_pass[freqs > 1000] / 2
plt.plot(freqs, np.abs(low_pass))
获取可预测的频谱

应用低通滤波器后的频谱
请注意,这仅仅意味着感兴趣的频率的幅度已被除以二。由于我们仅使用正弦波,因此映射是直接的,但如果我们从不同的信号开始,情况也是如此。
最后,我们可以恢复修改后的信号。
sig_ = np.fft.irfft(low_pass)
plt.plot(x, sig_)
得到如下信号

从 irfft 重建的信号
这与起始信号相似,但缩小了近乎两倍。
谐波
现在让我们观察一个比正弦波更复杂的信号,例如一个三角形状的信号:

频率为 10、幅度为 1、相位为 0 的三角形信号
我们可以用以下代码生成它:
import matplotlib.pyplot as plt
import numpy as np
freq = 10
sr = 10000
ts = np.linspace(0, 1, sr)
x = freq * ts
frac, _ = np.modf(x)
y = (np.abs(frac - 0.5) - 0.25) * 4
plt.plot(x, y)
基本思想是我们只取 xs 的小数部分,它们具有恒定的增量,然后取与 0.5 的差的绝对值。这就形成了三角形状。然后,我们有一个偏置和缩放,将信号从[0, 0.5]转换到[-1, 1]。
现在让我们看看这个信号的幅度谱。我们将频率更改为更高的值,例如 440 Hz,以便于可视化:

频率为 440 Hz 的三角形信号的频谱
与正弦信号不同,这个信号有多个非零值。如果你认为它们的分布中存在某种模式,那么你是对的!一个三角形状的信号由一个相对强的正弦信号组成,频率相同,称为基频,在这种情况下是 440Hz。其他频率称为谐波,是基频的倍数。图中分辨率不高,但这些谐波的频率为:1320 Hz、2200 Hz、3080 Hz 等,覆盖了偶数倍数。最后一个是三角形信号的特性,而一般情况下,谐波可以是基频的所有倍数。基频很重要,因为通常情况下,信号的感知音高取决于基频,即使它不是最显著的,即最高的系数。
此外,你还可以看到更多(非常小的)非零系数。这是数字信号处理中的一个伪影,称为混叠。在对信号进行数字表示的采样时,我们丢失了两个点之间发生的情况的信息。对于低频来说这不是问题,因为我们有足够的样本来进行插值,但对于高频来说,这会变成一个明显的问题。
在上面的图中,我们可以看到频率高达 5000 Hz,但第 7 个谐波会是 5720 Hz。采样的效果是造成频率的“折叠”,也就是在 5000 之后我们开始向回计数。然后,第 7 个谐波被检测为 4280:5000 — (5720–5000)。混叠频率继续向回,直到达到 0,然后再折回到另一个方向。
采样率越高,混叠效应越低,信号越清晰。
结论
在这篇文章中,我们进入了信号表示和傅里叶变换的世界。理解基本术语及其在信号中的意义对于继续本系列的内容非常重要。
文章中充满了图表和代码,以帮助读者以实用的方式理解这些概念,并让读者轻松地进行示例操作。
在本系列的下一部分中,我们将通过探索混响开始进行语音增强。
感谢您读到这里,敬请关注下一部分!
学习一种模式以在扩展 Python 代码功能时隔离包。
[towardsdatascience.com ## 阅读和撰写 ML 研究论文的技巧
通过数十次同伴评审所获得的经验教训
[towardsdatascience.com ## 选择您的深度学习工具
为什么您的工具可能取决于您组织的团队结构
[towardsdatascience.com
深入阅读
Think DSP 第二章详细讲解了本文的概念。
Medium 会员
您是否喜欢我的写作,并考虑订阅 Medium 会员以获得无限访问权限?
如果通过此链接订阅,您将支持我,而您无需额外支付费用 medium.com/@mattiadigangi/membership
统计抽样与重抽样介绍
原文:
towardsdatascience.com/introduction-to-statistical-sampling-and-resampling-1a6110965c3a
统计抽样是统计学的一个基本组成部分,它使我们能够有效地获得感兴趣的群体的信息,而不需要直接研究它
·发表于 Towards Data Science ·阅读时间 10 分钟·2023 年 5 月 16 日
--

图片来源:Testalize.me 在 Unsplash
任何研究人员最隐秘的愿望之一就是能够拥有他/她打算研究的整个群体的数据。
研究整个群体可以让研究人员对所研究的现象获得完整的理解,因为这可以收集该群体中所有个体的信息。
在大多数情况下,这在实践和理论上都是不现实的。
例如,考虑一下由意大利罗马市的人组成的群体。如果我们的研究需要包括这些个体的回应,由于现实世界的限制,可能不可能找到并联系到他们所有人。
我们需要找到这些人并请求他们的回应——这需要针对罗马的所有个体。
这可能会证明是过于昂贵和耗时的,无论是对于单独工作的研究人员还是团队。
因此,通常需要使用样本作为对群体的近似。
抽样并不像人们想象的那样简单。它有一些不明显的定义和细微差别是值得了解的。实际上,能够明确和深思熟虑地制定抽样策略对你的研究有着巨大的影响,无论是对你还是对团队。
在这篇文章中,你将了解统计抽样及其如何对你的研究和实验结果产生巨大影响。
通过阅读本文,你将学到以下内容
-
你将能够定义样本是什么,以及它与群体在统计层面上有何不同
-
理解为什么抽样在大多数情况下是必要的
-
什么使样本代表总体,以及哪些因素影响这种属性
-
你将学习一些最相关的抽样技术,以及如何在研究设计中考虑这些技术的示例和图像
让我们开始吧!
让我们定义什么是样本
样本不过是你希望研究的总体的一个子集。 与总体代表你要分析的整个个体或对象组不同,样本仅代表其中的一部分。
研究样本使我们能够通过接近我们期望在观察数据中发现的某种程度的真相来研究总体。
就这样。
现在,当提出问题时,细微差别就会出现,例如
-
我实验的纳入标准是什么?
-
适合这种情况的抽样策略是什么?
-
我如何从这个样本中收集数据?
还有其他方面。所以虽然定义样本是件简单的事,但抽样相当复杂,并且影响你正在进行的整个研究。
为什么研究一个总体会很困难?
原因可能有很多,但一些最常见的包括
-
总体过于庞大,你无法从所有个体那里获取数据
-
缺乏资源,例如时间和金钱,以收集整个总体的数据
-
识别所有属于总体的个体的难度
-
无法收集某些个体的数据,由于某种形式的不可接触性
以及许多其他取决于项目的方面。
在这些情况下,统计抽样成为一种实用且高效的解决方案用于估计总体特征。一旦从样本中收集了数据,就可以用它来推断更大总体的特征。
“代表性”样本是什么意思?
代表性样本是一个总体的子集,预计会与总体本身共享一些特征。
给定某个度量(如个体的身高或他们在某个测试中的分数),
代表性样本是一个准确反映该度量分布的样本,如果整个总体被研究的话。
大多数时候研究人员对给定度量的总体分布一无所知,所以他/她可以使用抽样技术,例如随机抽样,这有助于确保总体中的每个个体都有相等的被纳入样本的概率。
但这并不简单,因为验证抽样技术的唯一实证方法是通过观察和实验。
例如,你可以使用随机抽样,因为你认为总体中的个体相似。如果情况如此,实验可能会揭示出实际上有多个彼此差异较大的独立群体,这些群体值得单独研究。
影响采样的因素
以下是影响采样质量及其正确估计总体能力的因素列表:
-
参考总体:样本的选择取决于对参考总体的了解,即从中提取样本的人群、物体、事件或数据组。
-
采样方法:有几种采样方法,包括简单随机抽样、分层抽样、聚类抽样、系统抽样和配额抽样。方法的选择取决于总体的特征和研究的目标。
-
样本大小:样本大小取决于估计所需的精度和可靠性水平。通常,样本越大,估计越准确。这是因为随着样本中个体数量的增加,参考总体越来越接近。
-
纳入标准:使用的纳入标准可能会影响样本的代表性和估计的准确性。重要的是使用适当的筛选程序以避免选择偏差。
-
数据收集方式:数据的收集方式(例如,电话采访、在线问卷、实地观察)会影响数据质量和样本的代表性。
-
数据收集时间:这可能会影响样本的代表性,因为总体的特征可能随时间变化。
采样技术
我们现在将看到一系列可供研究人员使用的采样技术,这些技术可以用来接近总体。同样,没有“最佳”技术——你应该理解并根据你正在处理的案例进行调整。
-
随机抽样
-
分层抽样
-
系统抽样
-
配额抽样
-
自助法
我们一个一个地来看。
随机抽样
简单随机抽样是最广泛使用的采样技术之一,涉及从总体中随机选择个体,使每个个体有相等的机会被纳入样本。
当总体是同质的且没有理由将其划分为组时,这种采样技术很有用。此外,简单随机抽样相对容易实施且不需要专业知识。
然而,简单随机抽样存在一些限制,例如在总体高度异质的情况下,确保样本代表性的难度。
在这种情况下,拥有领域特定的知识对于理解和正确处理这些现象非常重要。
这里有一张关于随机抽样如何工作的图像

随机抽样的工作原理。图像由作者提供。
层次抽样
层次抽样是一种 将总体根据一个或多个特征划分为同质群体(称为层)的抽样技术。
一旦确定了层次,就会在每个层次中选择一个简单随机样本。
当总体在感兴趣的特征上具有异质性,并且你希望确保每个层次在样本中得到适当代表时,这种技术非常有用。
例如,如果你想研究公司在不同地区的客户满意度,可以按地区划分总体,并从每个地区中选择一个简单随机样本。

层次抽样的工作原理。图像由作者提供。
系统抽样
系统抽样是一种抽样方法,其中总体项目按顺序排列,并从一个随机起始点开始,每隔 k 个项目选择一个项目(例如,每第十个项目)。
当总体项目的列表已经可用,并且需要随机选择样本时,可以使用这种抽样方法。
当总体较大且识别每个个体会需要过多时间或资源时,系统抽样非常有用。
然而,如果所选个体的范围与总体中的特定模式重合,则系统抽样可能会有偏差。

系统抽样的工作原理。图像由作者提供。
基于配额的抽样
配额抽样是一种非概率抽样方法,在这种方法中,个体的选择是 为了获得参考总体特征的比例代表性。
使用这种方法,总体基于某些感兴趣的特征(例如,性别、年龄、教育、地理区域)被划分为类别或“配额”,每个配额中要选择的个体数量是根据总体的比例确定的。
每个配额内的受试者选择可以使用随机或非随机抽样方法,根据研究的需要进行选择。
基于配额的抽样的优点是,即使每个配额内的受试者选择不是随机的,它也能获得一个按比例代表参考总体特征的样本。
这种抽样方法常用于 民意调查, 因为它能够相对快速且廉价地获得具有代表性的样本。然而,配额抽样可能会受到选择样本的招聘人员的知识和观点的影响,因此可能会存在选择偏差。

基于配额的抽样的工作原理。图像由作者提供。
自助法
自助法是一种重新抽样技术,它通过从总体中随机选择并替换一定数量的元素来生成样本,从而近似总体。
这个过程会重复多次,从而创建大量样本。从这些样本中提取任何统计量(如均值或中位数),这将成为最终样本的一部分,从而近似总体。
自助法在你想要估计样本统计量或机器学习模型的准确性时非常有用。与其对总体分布做假设,自助法使用合成样本的分布来估计标准误差和置信区间。
自助法(Bootstrapping)在总体分布未知或无法从原始总体中获取重复样本时特别有用。

自助法的工作原理。图片作者。
结论
在这篇文章中,我们已经看到统计抽样是研究过程中的一个基本概念。
我们已经看到,基于研究者在参考领域的知识以及他所面临的各种偏见,抽样可以比收集整个总体的数据更有效地获取目标总体的信息。
我们还讨论了常用的抽样技术,包括随机抽样、分层抽样、系统抽样和配额抽样,以及自助法(Bootstrapping)。
最后,我们强调了正确定义参考总体以及选择最合适的抽样方法对于研究目标的重要性。
希望这篇介绍能帮助你个人成长、领域知识的提升以及你的项目。
下一篇文章 👋
推荐阅读
对感兴趣的人来说,这里有我为每个机器学习相关主题推荐的书单。这些书在我看来是必读书籍,对我的职业生涯产生了巨大影响。
免责声明:这些是亚马逊的附属链接。我将从亚马逊那里获得一小笔佣金,因为我向你推荐了这些商品。你的体验不会改变,也不会收取额外费用,但这将帮助我扩大业务,并制作更多关于人工智能的内容。
-
机器学习入门: 自信的数据技能:掌握数据工作的基础,提升你的职业生涯 作者:Kirill Eremenko
-
Sklearn / TensorFlow: 使用 Scikit-Learn、Keras 和 TensorFlow 的实用机器学习 作者:Aurelien Géron
-
NLP: 文本即数据:机器学习与社会科学的新框架 作者:Justin Grimmer
-
Sklearn / PyTorch: 使用 PyTorch 和 Scikit-Learn 的机器学习:使用 Python 开发机器学习和深度学习模型 作者:Sebastian Raschka
-
数据可视化: 用数据讲故事:为商业专业人士提供的数据可视化指南 作者 Cole Knaflic
有用的链接(由我编写)
-
学习如何在 Python 中执行顶级探索性数据分析: Python 中的探索性数据分析 — 一步一步的过程
-
学习 PyTorch 的基础知识: PyTorch 入门:从训练循环到预测
-
学习 TensorFlow 的基础知识: 从 TensorFlow 2.0 开始 — 深度学习简介
-
使用 Python 进行 TF-IDF 文本聚类: Python 中的 TF-IDF 文本聚类
如果你想支持我的内容创作活动,请随时通过下面的推荐链接加入 Medium 的会员计划。我将从你的投资中获得一部分,你将能够无缝访问 Medium 上大量的数据科学及其他领域的文章。
[## 使用我的推荐链接加入 Medium - Andrea D'Agostino
作为 Medium 会员,你的会员费的一部分将转给你阅读的作者,你可以完全访问每一个故事…
medium.com](https://medium.com/@theDrewDag/membership?source=post_page-----1a6110965c3a--------------------------------)
流处理框架介绍
原文:
towardsdatascience.com/introduction-to-streaming-frameworks-d612583a3246
理解在评估和比较流处理技术时需要考虑的一些关键特性。
·发布于 Towards Data Science ·阅读时长 6 分钟·2023 年 11 月 8 日
--

Joao Branco 提供的照片,Unsplash
介绍
随着数据架构变得越来越成熟,流处理不再被视为奢侈品,而是一项在不同行业中广泛应用的技术。由于技术和资源限制,批处理实际上一直是处理和交付应用程序的首选方式,但随着基于 Apache 的分布式系统中微批处理和原生流处理框架的发展,高规模流处理现在变得更加可及(见图 1)。
使用流处理系统的一些示例应用包括:处理交易数据以发现异常、天气数据、来自远程位置的物联网数据、地理位置跟踪等。

图 1:批处理与流处理(作者提供的图片)。
实时处理与微批处理
流处理系统主要有两种类型:微批处理和实时处理:
-
在实时流处理过程中,每条记录都会在其可用时立即被处理。因此,这可能导致系统具有非常低的延迟,能够立即利用传入的数据(例如,在金融系统中检测欺诈交易)。
-
在微批处理系统中,数据点不是一个一个处理,而是以小块的形式处理,然后在特定时间间隔后或达到最大存储大小时返回。这种方法因此更倾向于高吞吐量而非低延迟。最后,如果有兴趣执行复杂的操作,例如在存储系统中输出结果之前进行聚合(如最小值、最大值、均值)、连接等操作,微批处理系统可以特别有用。因此,微批处理可以被认为是在执行例如每小时报告任务(如平均天气温度等)时的一个非常好的折衷方案。

图 2:实时与微批处理(图片由作者提供)。
选择流处理框架
现在我们已经澄清了微批处理和实时流处理的区别,我们可以开始审查一些需要考虑的关键点:
-
延迟
-
吞吐量
-
内存使用
-
可扩展性
-
故障恢复
-
消息传递保证
延迟
延迟可以定义为处理一个或多个输入并返回输出所需的时间(流记录进入系统后处理的时间)。如果低延迟是主要限制,则流处理框架将是我们的最佳选择。在微批处理中,输入实际上是在被消费之前缓存的(因此增加了延迟时间)。
吞吐量
吞吐量表示系统返回的输出数据的汇总速率。例如,这可以通过每秒处理的输入数据量来表示。例如,Apache Spark 可以使用微批处理,通过缓存输入数据能够实现高吞吐量速率。另一方面,原生流处理框架为了尽可能优化延迟评分,则会注册较低的吞吐量速率。
内存使用
由于流处理框架每天处理大量数据,优化内存使用可以是一个非常重要的因素,以确保系统在重负载下不会被压垮。根据系统架构,这些系统实际上可以设计成保留不同时间段的数据,如果数据保留时间过长或同时接收大量数据,则内存可能成为一个大限制。因此,评估不同框架的内存使用优化可能是一个相当复杂的任务,并且高度依赖于你所从事项目的具体情况。在这种情况下,使用不同示例的来流量创建使用基准可以提供有价值的见解,以便做出决策。
可扩展性
在并行处理流处理框架中,可扩展性可以通过系统处理不断增加的工作量并均匀分配任务到不同工作节点的能力来表示。例如,这可以通过实现主从架构来完成,其中主节点负责最优地规划任务在工作节点之间的分配,以尽可能避免工作节点在等待依赖项时闲置。此外,在高度可扩展的架构中,系统应该能够自动理解何时需要或不需要在集群中添加/删除资源(例如工作节点),以响应不断上升/下降的工作负载。
故障恢复
另一个需要考虑的重要因素是故障恢复。实际上,这可以定义为系统在其组件之一失败后能够保持弹性并继续操作的能力。例如,如果一个工作节点内存耗尽且不再功能正常,应该自动启动一个新的节点以减少停机时间,并且它应该能够从类似的阶段继续操作,以避免完全从头开始重启整个过程。在处理非常大的负载的分布式系统中,故障恢复是一个非常重要的特性,因为故障可能比预期的更频繁。
消息交付保证
最终,每个流处理框架可以提供不同的输入交付保证。三个最重要的交付保证是:
-
准确一次交付
-
至多一次交付
-
至少一次交付
使用“准确一次”交付,系统保证每个传入的输入将被处理一次。相反,使用“至多一次”交付,大多数输入预计将被处理,但如果过程中的某些事情出现问题,则无法保证输入会被处理。最终,使用“至少一次”交付,保证每个输入都会被处理,并且某些消息可能也会被处理两次或更多次。
选择消息交付方法时需要记住的两个关键标准是:延迟和计算成本。例如,从准确性的角度来看,“一次交付”在大多数使用案例中可能是最佳选择,尽管这种方法很容易带来大量额外的开销(例如,需要一个记录历史系统来确定输入是否已经处理过,以及在发生故障时进行恢复)。因此,根据应用程序的可能约束,其他消息交付系统可能会为你提供更多价值。
Apache Spark、Flink 和 Kafka
提供流处理功能的三个最显著的 Apache 项目是:Spark、Flink 和 Kafka。Spark 仅提供微批处理,Flink 提供原生流处理和批处理,而 Kafka 仅提供流处理。
现在,Apache Spark 是最流行的批处理工作负载系统之一,通过其改进的结构化流处理 API 也越来越多地用于流处理任务(具有亚秒级延迟)。结构化流处理提供了更易于使用的 API、精确一次处理、内置容错和接近实时的处理能力。如果你对了解更多关于 Apache Spark 的信息感兴趣,可以在我的介绍文章和优化文章中找到更多资料。
Apache Flink 可以支持无限(流式)和有限(批处理)流。不过,与 Spark 相反的是,默认情况下,运行时是流式的而非批处理的。此外,Flink 还能够执行有状态的计算(利用时间上的数据依赖进行聚合、连接等)。
最后,Apache Kafka 是一个专门设计用于执行流处理操作的平台。它始于 2011 年的一个消息队列系统,现在已经成为一个完整的流处理框架,内置了对计算和存储的支持。Apache Kafka 由 4 个关键 API 组成:Kafka Streams、Producer、Consumer 和 Connector。使用 Kafka Streams API 可以提供许多优势,如开箱即用的出色扩展性、集群管理、自动故障转移等。
此外,如果需要,可以在同一系统中结合使用不同的框架,以充分发挥它们的最佳功能(例如,使用 Kafka 进行流处理存储,使用 Flink 进行计算)。
联系方式
如果你想随时了解我的最新文章和项目,请在 Medium 上关注我并订阅我的邮件列表。以下是我的一些联系方式:
Open LLM Falcon-40B 简介:性能、训练数据和架构
开始使用 Falcon-7B、Falcon-40B 及其指令版本
·
关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 6 月 7 日
--
Falcon 模型自 2023 年 5 月发布以来引起了广泛关注。
它们是因果大语言模型(LLM),或所谓的“仅解码器”模型,非常类似于 GPT。
定义:因果语言模型
因果语言建模涉及预测一个令牌后面的令牌。在训练期间,模型的注意力仅集中在左侧上下文上。右侧上下文被屏蔽。这些模型通常在数十亿个词上进行训练。
Falcon 模型自 5 月 31 日起完全免费,甚至用于商业用途(Apache 2.0 许可证)。Falcon 模型由阿布扎比技术创新研究所(TII)开发和训练。
根据初步结果,Falcon-40B,作为 Falcon 模型中最大的一个,超越了所有其他因果 LLM,包括 LLaMa-65B 和 MPT-7B。
在这篇博客文章中,我详细介绍了 Falcon-40B、Falcon-7B 及其指令版本。我们将看到它们与其他模型的表现对比、训练方式以及如何在自己的 GPU 上使用 QLoRa 运行 Falcon7-B。
OpenLLM 上的表现
Falcon-40B 的指令版本在OpenLLM 排行榜中排名第一。标准版本排名第二。
OpenLLM 排行榜评估 LLM 在 4 个任务上的表现:
-
AI2 Reasoning Challenge (25-shot):小学科学问题。
-
HellaSwag (10-shot):一个常识推理基准测试。
-
MMLU (5-shot):涵盖数学、计算机科学和法律等多个领域的 57 个任务。
-
TruthfulQA (0-shot):一个评估模型回答问题时真实性的基准。
Falcon-40B 在所有这些任务上都超越了 Meta AI 的 LLaMa-65B。
Falcon RefinedWeb
Falcon 模型主要在Falcon RefinedWeb数据集上进行训练。它也是由 TII 创建的,并在 Apache 2.0 许可证下分发。
RefinedWeb 从 CommonCrawl 中提取,并经过彻底策划。TII 声称它对多模态友好,因为保留了图片的链接和替代文本。
在 Hugging Face Hub 发布的数据集卡片中,TII 写道:“这个公开提取 […]”。对我来说,因此不清楚 Falcon 模型是否在这个仅为“提取”的公开版本数据集上进行训练,还是使用了更大的内部版本。
这个提取文件解压需要 2.8Tb 的硬盘空间。
由于它在 Hugging Face Hub 中可用,你只需运行以下代码即可开始使用:
from datasets import load_dataset
rw = load_dataset("tiiuae/falcon-refinedweb")
注意:你需要“datasets”库。如果没有,你可以通过“pip install datasets”安装。
RefinedWeb 与策划的语料库结合,以训练 Falcon 模型。
这个数据集代表了 Falcon 模型预训练数据的 75%。它仅涵盖英语。为了增加更多语言,他们还准备了“RefinedWeb-Europe”,涵盖了多种欧洲语言:德语、西班牙语、法语、意大利语、葡萄牙语、波兰语、荷兰语、罗马尼亚语、捷克语和瑞典语。
最后,为了覆盖更多的体裁和领域,他们添加了书籍、对话(例如,来自 Reddit)、代码、技术报告和科学论文(例如,来自 arXiv)的语料库。注意:他们没有透露“代码”的来源。也不清楚他们编制的数据集的许可证是什么。
总共,这相当于 1500 亿个标记用于预训练 Falcon 模型。
Falcon-40B 和 Falcon-7B 的预训练
对于预训练,他们使用了:
-
旋转位置嵌入 (Su et al., 2021)
-
Multiquery (Shazeer et al., 2019) 和 FlashAttention (Dao et al., 2022)
-
并行注意力/MLP 与双层规范
Falcon-40B 具有以下架构:
-
层数:60
-
嵌入维度:8,192
-
头数:64
-
词汇表大小:65,024
-
序列长度:2,048
这与 LLaMa 的架构非常相似,只是词汇表大了一倍。
在我看来,序列长度在我们看到 LLM 接受超过 10,000 个标记的情况下,如 GPT-4 和Claude,显得相当短。
Falcon-7B 有一个较小的架构,使其能够在消费者硬件上进行微调。与 40B 版本唯一的区别是层数和嵌入维度减少了一半:
-
层数:60
-
嵌入维度:4,544
两个版本都使用了 bfloat16 精度和 AdamW。它们使用了 AWS SageMaker 和 384 个 A100 40GB GPU 的 P4d 实例,但尚未透露训练持续了多长时间。
Falcon-40B/7B 的指令版本
Falcon-40B 和 7B 的指令版本表现更好。
Falcon-40B-Instruct 在 AWS SageMaker 上训练,使用了配备 64 个 A100 40GB GPU 的 P4d 实例。对于 Falcon-7B-Instruct,他们仅使用了 32 个 A100。
它们在 250 百万个标记的聊天/指令数据集混合数据上进行了微调,这些数据来源于Bai ze、GPT4all、GPTeacher和 1300 万标记的 RefinedWeb 语料库。
Bai ze 是由 ChatGPT 生成的数据集。我会对在商业应用中使用 Falcon 模型的指令版本保持谨慎。根据 OpenAI 的使用条款:
“限制。你不得 […] (iii) 使用服务的输出开发与 OpenAI 竞争的模型”
“服务”包括 ChatGPT。而 Falcon-40B 是一个可以与 OpenAI 的 GPT 模型“竞争”的模型。
如何在你的 GPU 上使用 Falcon-7B 和 QLoRa
在上一篇文章中,我介绍了 QLoRa 在消费级硬件上微调 LLMs 的方法:
QLoRa: 在你的 GPU 上微调大型语言模型
现在可以在消费级硬件上对具有数十亿参数的模型进行微调。
towardsdatascience.com
你可以对 Falcon-7B 按相同的步骤操作,但它不会在 Google Colab 的免费实例上运行。模型需要过多的 CPU 内存。
如果你的计算机有 32 Gb 的 RAM,这应该可以工作。如果没有这么多 RAM,你将需要选择云计算或 Google Colab Pro 等方式。
一旦你有了支持 Falcon-7B 的环境,还有一些小修改需要对我的 QLoRa 教程进行调整。
首先,你必须安装“einops”:
pip install -q einops
然后,按如下方式修改模型的加载:
model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb_config, device_map="auto", trust_remote_code=True)
在这一行中,“trust_remote_code=True” 是必要的。这是 Hugging Face 获得你同意一些代码由模型直接在你的机器上执行的方式。在这里,Falcon 运行一个配置脚本。
除此之外,其它方面应该与我的教程相同。
如果你不想使用 QLoRa 并且有 GPU 集群访问权限,标准的加载和运行 Falcon-7B/Falcon-40B 的方法可以参考 Hugging Face 模型卡片。
from transformers import AutoTokenizer, AutoModelForCausalLM
import transformers
import torch
model = "tiiuae/falcon-40b"
tokenizer = AutoTokenizer.from_pretrained(model)
pipeline = transformers.pipeline(
"text-generation",
model=model,
tokenizer=tokenizer,
torch_dtype=torch.bfloat16,
trust_remote_code=True,
device_map="auto",
)
sequences = pipeline(
"Girafatron is obsessed with giraffes, the most glorious animal on the face of this Earth. Giraftron believes all other animals are irrelevant when compared to the glorious majesty of the giraffe.\nDaniel: Hello, Girafatron!\nGirafatron:",
max_length=200,
do_sample=True,
top_k=10,
num_return_sequences=1,
eos_token_id=tokenizer.eos_token_id,
)
for seq in sequences:
print(f"Result: {seq['generated_text']}")
结论
Falcon 模型是预训练的 LLMs。如果你有数据进行微调,你可以将它们用于任何自然语言处理任务。请注意,即使不进行微调,标准(非指令型)版本在许多任务中已经表现得非常好,如在 OpenLLM 排行榜上所示,能够回答来自各个领域的问题并进行常识推理。
Falcon 模型的“指令”版本已经过微调。它们表现得像 ChatGPT,即具有一般知识的聊天机器人。
Falcon 模型也是流行的 LLaMa 模型的非常有趣的替代品。Falcon-40B 是:
-
更小:LLaMa 有 65 亿参数,而 Falcon-40B 只有 40 亿参数,因此需要的内存较少。
-
更好:在 OpenLLM 排行榜上,Falcon-40B 排名第一。
-
免费:Falcon 模型在 Apache 2.0 许可证下分发,允许商业使用,而 LLaMa 只能用于研究目的。
如果你对这些模型感兴趣,关注这篇博客文章。TII 将发布一篇科学论文/技术论文,更详细地描述他们的工作。一旦上线,我会在这里提供链接。
如果你喜欢这篇文章并希望阅读接下来的文章,支持我的最佳方式是使用这个链接成为 Medium 会员:
[## 通过我的推荐链接加入 Medium - 本杰明·玛丽
加入我们的人工智能社区,获取前沿研究的访问权限。这个博客旨在揭示人工智能领域的最新进展。
medium.com](https://medium.com/@bnjmn_marie/membership?source=post_page-----98388fa40226--------------------------------)
如果你已经是会员并希望支持这项工作, 请在 Medium 上关注我。
权重量化简介
使用 8 位量化来减少大型语言模型的大小
·
关注 发表在 Towards Data Science ·14 分钟阅读·2023 年 7 月 7 日
--
大型语言模型(LLMs)以其广泛的计算需求而闻名。通常,通过将参数数量(大小)乘以这些值的精度(数据类型),可以计算模型的大小。然而,为了节省内存,可以通过一种称为量化的过程使用较低精度的数据类型来存储权重。
我们在文献中区分了两大类权重量化技术:
-
训练后量化(PTQ)是一种直接的技术,其中已训练好的模型的权重被转换为较低的精度,而不需要进行任何重新训练。尽管实施起来简单,但 PTQ 可能会导致性能下降。
-
量化感知训练(QAT)在预训练或微调阶段纳入权重转换过程,从而提升模型性能。然而,QAT 计算成本高且需要代表性的训练数据。
本文聚焦于 PTQ 以减少参数的精度。为了获得良好的直觉,我们将应用简单和更复杂的技术于一个使用 GPT-2 模型的示例。
完整代码可以在Google Colab和GitHub上自由获取。
📚 浮点表示的背景
数据类型的选择决定了所需的计算资源量,影响模型的速度和效率。在深度学习应用中,平衡精度和计算性能成为一个至关重要的任务,因为更高的精度通常意味着更大的计算需求。
在各种数据类型中,浮点数由于能够以高精度表示广泛的值范围,因此在深度学习中被广泛使用。通常,浮点数使用n位来存储数值。这些n位进一步被划分为三个不同的部分:
-
符号:符号位指示数字的正负性质。它使用一个位,其中 0 表示正数,1 表示负数。
-
指数:指数是一段位表示基数(在二进制表示中通常为 2)的幂。指数可以是正数或负数,从而使数字能够表示非常大或非常小的值。
-
尾数/有效数字:剩余的位用于存储尾数,也称为有效数字。它表示数字的有效数字。数字的精度严重依赖于尾数的长度。
这种设计允许浮点数以不同精度覆盖广泛的值范围。用于这种表示的公式是:

为了更好地理解这一点,让我们深入探讨在深度学习中最常用的数据类型:float32(FP32)、float16(FP16)和 bfloat16(BF16):
-
FP32 使用 32 位表示一个数字:一个位用于符号,八个位用于指数,其余 23 个位用于尾数。虽然它提供了较高的精度,但 FP32 的缺点是其计算和内存开销较大。
-
FP16 使用 16 位存储一个数字:一个用于符号,五个位用于指数,十个位用于尾数。虽然这使其在内存上更高效并加速计算,但减少的范围和精度可能会引入数值不稳定性,可能影响模型的准确性。
-
BF16也是一种 16 位格式,但有一个符号位,八个位用于指数,七个位用于尾数。BF16 扩展了表示范围,相较于 FP16,减少了下溢和溢出的风险。尽管由于尾数位数减少而精度降低,但 BF16 通常不会显著影响模型性能,是深度学习任务的一个有用折中方案。

作者提供的图片
在机器学习术语中,FP32 通常被称为“全精度”(4 字节),而 BF16 和 FP16 则被称为“半精度”(2 字节)。但我们是否可以更进一步,用一个字节来存储权重?答案是 INT8 数据类型,它由一个 8 位表示组成,能够存储 2⁸ = 256 种不同的值。在下一节中,我们将看到如何将 FP32 权重转换为 INT8 格式。
🔰 初步的 8 位量化
在这一部分,我们将实现两种量化技术:一种是具有绝对最大值(absmax)量化的对称量化,另一种是具有零点量化的非对称量化。在这两种情况下,目标是将 FP32 张量X(原始权重)映射到 INT8 张量X_quant(量化权重)。
使用absmax 量化,原始数值除以张量的绝对最大值,并乘以缩放因子(127),将输入映射到范围[-127, 127]。要检索原始 FP16 值,INT8 数值除以量化因子,承认由于四舍五入导致的一些精度损失。

例如,假设我们有一个绝对最大值为 3.2 的值。一个 0.1 的权重将被量化为round(0.1 × 127/3.2) = 4。如果我们想将其反量化,将得到4 × 3.2/127 = 0.1008,这意味着一个 0.008 的误差。以下是对应的 Python 实现:
import torch
def absmax_quantize(X):
# Calculate scale
scale = 127 / torch.max(torch.abs(X))
# Quantize
X_quant = (scale * X).round()
# Dequantize
X_dequant = X_quant / scale
return X_quant.to(torch.int8), X_dequant
使用零点量化,我们可以考虑非对称输入分布,这在考虑 ReLU 函数的输出(仅正值)时特别有用。输入值首先按值的总范围(255)除以最大值和最小值之间的差异进行缩放。然后通过零点偏移将这个分布映射到范围[-128, 127](注意与 absmax 相比的额外值)。首先,我们计算缩放因子和零点值:

然后,我们可以使用这些变量来量化或反量化我们的权重:

例如:我们有一个最大值为 3.2 和一个最小值为-3.0。我们可以计算出缩放因子为255/(3.2 + 3.0) = 41.13,零点为-round(41.13 × -3.0) - 128 = 123 -128 = -5,因此我们之前的权重 0.1 将被量化为round(41.13 × 0.1 -5) = -1。这与之前使用 absmax 获得的值(4 与-1)差异很大。

作者提供的图片
Python 实现非常简单:
def zeropoint_quantize(X):
# Calculate value range (denominator)
x_range = torch.max(X) - torch.min(X)
x_range = 1 if x_range == 0 else x_range
# Calculate scale
scale = 255 / x_range
# Shift by zero-point
zeropoint = (-scale * torch.min(X) - 128).round()
# Scale and round the inputs
X_quant = torch.clip((X * scale + zeropoint).round(), -128, 127)
# Dequantize
X_dequant = (X_quant - zeropoint) / scale
return X_quant.to(torch.int8), X_dequant
我们可以利用 transformers 库在真实模型上使用这两个函数,而不是依赖完整的玩具示例。
我们首先加载 GPT-2 的模型和分词器。这是一个非常小的模型,我们可能不想对其进行量化,但它对于本教程来说足够了。首先,我们想观察模型的大小,以便稍后进行比较,并评估由于 8 位量化而节省的内存。
!pip install -q bitsandbytes>=0.39.0
!pip install -q git+https://github.com/huggingface/accelerate.git
!pip install -q git+https://github.com/huggingface/transformers.git
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
torch.manual_seed(0)
# Set device to CPU for now
device = 'cpu'
# Load model and tokenizer
model_id = 'gpt2'
model = AutoModelForCausalLM.from_pretrained(model_id).to(device)
tokenizer = AutoTokenizer.from_pretrained(model_id)
# Print model size
print(f"Model size: {model.get_memory_footprint():,} bytes")
Model size: 510,342,192 bytes
GPT-2 模型在 FP32 中的大小约为 487MB。下一步是使用零点和 absmax 量化权重。在以下示例中,我们将这些技术应用于 GPT-2 的第一个注意力层,以查看结果。
# Extract weights of the first layer
weights = model.transformer.h[0].attn.c_attn.weight.data
print("Original weights:")
print(weights)
# Quantize layer using absmax quantization
weights_abs_quant, _ = absmax_quantize(weights)
print("\nAbsmax quantized weights:")
print(weights_abs_quant)
# Quantize layer using absmax quantization
weights_zp_quant, _ = zeropoint_quantize(weights)
print("\nZero-point quantized weights:")
print(weights_zp_quant)
Original weights:
tensor([[-0.4738, -0.2614, -0.0978, ..., 0.0513, -0.0584, 0.0250],
[ 0.0874, 0.1473, 0.2387, ..., -0.0525, -0.0113, -0.0156],
[ 0.0039, 0.0695, 0.3668, ..., 0.1143, 0.0363, -0.0318],
...,
[-0.2592, -0.0164, 0.1991, ..., 0.0095, -0.0516, 0.0319],
[ 0.1517, 0.2170, 0.1043, ..., 0.0293, -0.0429, -0.0475],
[-0.4100, -0.1924, -0.2400, ..., -0.0046, 0.0070, 0.0198]])
Absmax quantized weights:
tensor([[-21, -12, -4, ..., 2, -3, 1],
[ 4, 7, 11, ..., -2, -1, -1],
[ 0, 3, 16, ..., 5, 2, -1],
...,
[-12, -1, 9, ..., 0, -2, 1],
[ 7, 10, 5, ..., 1, -2, -2],
[-18, -9, -11, ..., 0, 0, 1]], dtype=torch.int8)
Zero-point quantized weights:
tensor([[-20, -11, -3, ..., 3, -2, 2],
[ 5, 8, 12, ..., -1, 0, 0],
[ 1, 4, 18, ..., 6, 3, 0],
...,
[-11, 0, 10, ..., 1, -1, 2],
[ 8, 11, 6, ..., 2, -1, -1],
[-18, -8, -10, ..., 1, 1, 2]], dtype=torch.int8)
原始(FP32)值和量化值(INT8)之间的差异很明显,但 absmax 和零点权重之间的差异则更微妙。在这种情况下,输入值看起来偏移了 -1。这表明这一层的权重分布相当对称。
我们可以通过对 GPT-2 中的每一层(线性层、注意力层等)进行量化,并创建两个新模型:model_abs 和 model_zp,来比较这些技术。准确地说,我们将实际用去量化的权重替换原始权重。这有两个好处:它使我们可以 1/ 比较权重的分布(相同的尺度),以及 2/ 实际运行这些模型。
确实,PyTorch 默认不允许 INT8 矩阵乘法。在实际场景中,我们会将其去量化以运行模型(例如 FP16),但以 INT8 存储。在下一部分,我们将使用 [bitsandbytes](https://github.com/TimDettmers/bitsandbytes) 库来解决这个问题。
import numpy as np
from copy import deepcopy
# Store original weights
weights = [param.data.clone() for param in model.parameters()]
# Create model to quantize
model_abs = deepcopy(model)
# Quantize all model weights
weights_abs = []
for param in model_abs.parameters():
_, dequantized = absmax_quantize(param.data)
param.data = dequantized
weights_abs.append(dequantized)
# Create model to quantize
model_zp = deepcopy(model)
# Quantize all model weights
weights_zp = []
for param in model_zp.parameters():
_, dequantized = zeropoint_quantize(param.data)
param.data = dequantized
weights_zp.append(dequantized)
现在我们的模型已经被量化,我们想检查这一过程的影响。直观上,我们想确保量化的权重接近原始权重。一种直观的方法是绘制去量化和原始权重的分布。如果量化有损,它将极大地改变权重分布。
下图展示了这种比较,其中蓝色直方图表示原始(FP32)权重,红色直方图表示去量化后的(来自 INT8)权重。注意,我们仅在 -2 和 2 之间显示此图,因为有一些绝对值非常高的离群值(稍后会详细讨论)。

两个图表非常相似,都在 0 附近有一个惊人的峰值。这个峰值表明我们的量化相当有损,因为反转过程并没有输出原始值。这在 absmax 模型中尤为明显,该模型在 0 附近显示了一个更低的谷值和一个更高的峰值。
让我们比较原始模型和量化模型的性能。为此,我们定义了一个 generate_text() 函数来生成 50 个标记,并使用 top-k 采样。
def generate_text(model, input_text, max_length=50):
input_ids = tokenizer.encode(input_text, return_tensors='pt').to(device)
output = model.generate(inputs=input_ids,
max_length=max_length,
do_sample=True,
top_k=30,
pad_token_id=tokenizer.eos_token_id,
attention_mask=input_ids.new_ones(input_ids.shape))
return tokenizer.decode(output[0], skip_special_tokens=True)
# Generate text with original and quantized models
original_text = generate_text(model, "I have a dream")
absmax_text = generate_text(model_abs, "I have a dream")
zp_text = generate_text(model_zp, "I have a dream")
print(f"Original model:\n{original_text}")
print("-" * 50)
print(f"Absmax model:\n{absmax_text}")
print("-" * 50)
print(f"Zeropoint model:\n{zp_text}")
Original model:
I have a dream, and it is a dream I believe I would get to live in my future. I love my mother, and there was that one time I had been told that my family wasn't even that strong. And then I got the
--------------------------------------------------
Absmax model:
I have a dream to find out the origin of her hair. She loves it. But there's no way you could be honest about how her hair is made. She must be crazy.
We found a photo of the hairstyle posted on
--------------------------------------------------
Zeropoint model:
I have a dream of creating two full-time jobs in America—one for people with mental health issues, and one for people who do not suffer from mental illness—or at least have an employment and family history of substance abuse, to work part
我们可以通过计算每个输出的困惑度来量化它,而不是尝试判断一个输出是否比其他输出更有意义。这是一个常用的评估语言模型的指标,用于测量模型在预测序列中下一个标记时的不确定性。在这个比较中,我们做了一个常见的假设,即分数越低,模型越好。实际上,高困惑度的句子也可能是正确的。
我们使用一个最小函数来实现它,因为不需要考虑上下文窗口的长度,因为我们的句子较短。
def calculate_perplexity(model, text):
# Encode the text
encodings = tokenizer(text, return_tensors='pt').to(device)
# Define input_ids and target_ids
input_ids = encodings.input_ids
target_ids = input_ids.clone()
with torch.no_grad():
outputs = model(input_ids, labels=target_ids)
# Loss calculation
neg_log_likelihood = outputs.loss
# Perplexity calculation
ppl = torch.exp(neg_log_likelihood)
return ppl
ppl = calculate_perplexity(model, original_text)
ppl_abs = calculate_perplexity(model_abs, absmax_text)
ppl_zp = calculate_perplexity(model_zp, absmax_text)
print(f"Original perplexity: {ppl.item():.2f}")
print(f"Absmax perplexity: {ppl_abs.item():.2f}")
print(f"Zeropoint perplexity: {ppl_zp.item():.2f}")
Original perplexity: 15.53
Absmax perplexity: 17.92
Zeropoint perplexity: 17.97
我们发现原始模型的困惑度略低于另外两个模型。一次实验并不十分可靠,但我们可以多次重复这个过程,以查看每个模型之间的差异。从理论上讲,零点量化应稍微优于 absmax,但计算成本也更高。
在这个例子中,我们将量化技术应用于整个层(每张量基础)。然而,我们可以在不同的粒度水平应用它:从整个模型到单个值。一次性量化整个模型会严重降低性能,而量化单个值会产生很大的开销。在实践中,我们通常更喜欢逐向量量化,它考虑了同一张量中行和列的值的变异性。
然而,即使是逐向量量化也不能解决离群特征的问题。离群特征是出现在所有变换器层中的极端值(负值或正值),当模型达到某个规模(>6.7B 参数)时。这是一个问题,因为一个离群值可以降低所有其他值的精度。但丢弃这些离群特征并不是一个选项,因为这会大大降低模型的性能。
🔢 8 位量化与 LLM.int8()
由Dettmers et al. (2022)引入的 LLM.int8() 是解决离群值问题的一个方案。它依赖于逐向量(absmax)量化方案,并引入了混合精度量化。这意味着离群特征以 FP16 格式处理以保持其精度,而其他值则以 INT8 格式处理。由于离群值约占 0.1% 的值,这有效地将 LLM 的内存占用减少了近 2 倍。

作者提供的图片
LLM.int8() 通过三个关键步骤进行矩阵乘法计算:
-
从输入隐藏状态X中提取包含离群特征的列,使用自定义阈值。
-
使用 FP16 对离群值进行矩阵乘法,并使用 INT8 进行逐向量量化(对隐藏状态X进行逐行量化,对权重矩阵W进行逐列量化)。
-
对非离群结果进行反量化(从 INT8 到 FP16),并将其与离群结果相加,以获得完整的 FP16 结果。

作者提供的图片
这种方法是必要的,因为 8 位精度有限,当量化具有大值的向量时可能导致显著的误差。这些误差也会随着它们在多个层之间传播而放大。
由于 bitsandbytes 库已集成到 Hugging Face 生态系统中,我们可以轻松使用这项技术。我们只需在加载模型时指定 load_in_8bit=True(它还需要一个 GPU)。
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model_int8 = AutoModelForCausalLM.from_pretrained(model_id,
device_map='auto',
load_in_8bit=True,
)
print(f"Model size: {model_int8.get_memory_footprint():,} bytes")
Model size: 176,527,896 bytes
通过这额外的一行代码,模型现在几乎缩小了三倍(168MB vs. 487MB)。我们甚至可以像之前那样比较原始权重和量化权重的分布:

在这种情况下,我们看到在 -2,-1,0,1,2 等值周围有尖峰。这些值对应于以 INT8 格式存储的参数(非异常值)。你可以通过使用 model_int8.parameters() 打印模型的权重来验证这一点。
我们还可以使用这个量化模型生成文本,并与原始模型进行比较。
# Generate text with quantized model
text_int8 = generate_text(model_int8, "I have a dream")
print(f"Original model:\n{original_text}")
print("-" * 50)
print(f"LLM.int8() model:\n{text_int8}")
Original model:
I have a dream, and it is a dream I believe I would get to live in my future. I love my mother, and there was that one time I had been told that my family wasn't even that strong. And then I got the
--------------------------------------------------
LLM.int8() model:
I have a dream. I don't know what will come of it, but I am going to have to look for something that will be right. I haven't thought about it for a long time, but I have to try to get that thing
再次强调,判断最佳输出是困难的,但我们可以依靠困惑度指标来给出一个(大致的)答案。
print(f"Perplexity (original): {ppl.item():.2f}")
ppl = calculate_perplexity(model_int8, text_int8)
print(f"Perplexity (LLM.int8()): {ppl.item():.2f}")
Perplexity (original): 15.53
Perplexity (LLM.int8()): 7.93
在这种情况下,量化模型的困惑度是原始模型的两倍低。一般来说,这种情况并不常见,但它显示了这种量化技术的竞争力。实际上,LLM.int8()的作者展示了性能下降非常小,可以忽略不计(<1%)。然而,它在计算方面有额外的成本:对于大模型,LLM.int8()大约慢了 20%。
结论
本文概述了最流行的权重量化技术。我们首先了解了浮点表示,然后介绍了两种 8 位量化技术:absmax 和 零点量化。然而,它们的局限性,特别是在处理异常值时,导致了 LLM.int8() 技术,该技术还保留了模型的性能。这种方法突显了权重量化领域的进展,揭示了正确处理异常值的重要性。
展望未来,我们的下一篇文章将深入探讨 GPTQ 权重量化技术。这项技术由 Frantar 等人 提出,只使用了 4 位,并且代表了权重量化领域的重大进展。我们将提供如何使用 AutoGPTQ 库实现 GPTQ 的全面指南。
如果你对更多关于 LLM 的技术内容感兴趣,可以在 Twitter 上关注我 @maximelabonne。
参考文献
-
T. Dettmers, M. Lewis, Y. Belkada, 和 L. Zettlemoyer,LLM.int8(): 大规模变压器的 8 位矩阵乘法。2022 年。
-
Y. Beldaka 和 T. Dettmers, 8 位矩阵乘法的简明介绍, Hugging Face Blog (2022)。
-
A. Gholami, S. Kim, Z. Dong, Z. Yao, M. W. Mahoney 和 K. Keutzer, 高效神经网络推理的量化方法综述。2021 年。
-
H. Wu, P. Judd, X. Zhang, M. Isaev 和 P. Micikevicius, 深度学习推理的整数量化:原理与实证评估。2020 年。
-
Lilian Weng, 大型变压器模型推理优化, Lil’Log (2023)。
-
Kamil Czarnogorski, 本地大型语言模型, Int8 (2023)。
指数移动平均的直观解释
理解在梯度下降算法中使用的基本算法背后的逻辑
·
关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 12 月 16 日
--
介绍
在时间序列分析中,通常需要通过考虑之前的值来理解序列的趋势方向。序列中下一个值的近似可以通过多种方法进行,包括使用简单的基线或构建先进的机器学习模型。
指数(加权)移动平均 是这两种方法之间的一个强大折衷方案。其内部具有简单的递归方法,使得算法的高效实现成为可能。同时,它非常灵活,可以成功适应大多数类型的序列。
本文涵盖了该方法背后的动机、工作流程描述和偏差修正——一种有效的技术,用于克服近似中的偏差障碍。
动机
想象一个问题是近似一个随时间变化的给定参数。在每次迭代中,我们知道所有先前的值。目标是预测下一个值,它依赖于之前的值。
一种简单的策略是直接取最后几个值的平均值。这在某些情况下可能有效,但对于一个参数更依赖于最新值的情况并不十分合适。
克服这个问题的一种可能方法是对更近期的值分配更高的权重,对之前的值分配更少的权重。指数移动平均正是遵循这一原则的策略。它基于假设变量的近期值对形成下一个值的贡献大于先前的值。
公式
为了理解指数加权移动平均的工作原理,让我们看一下它的递归方程:

指数加权移动平均公式
-
vₜ 是一个时间序列,用于近似给定变量。它的索引 t 对应于时间戳 t。由于这个公式是递归的,因此初始时间戳 t = 0 的值 v₀ 是必需的。在实际应用中,v₀ 通常取为 0。
-
θ 是当前迭代中的观测值。
-
β 是一个介于 0 和 1 之间的超参数,用于定义如何在先前的平均值 vₜ-₁ 和当前观测值 θ 之间分配权重的重要性。
让我们写出这个公式来计算前几个参数值:

计算 t 时刻的公式
结果是,最终公式如下:

t 时刻的指数移动平均
我们可以看到,最近的观测值 θ 的权重为 1,倒数第二个观测值的权重为 β,倒数第三个观测值的权重为 β²,依此类推。由于 0 < β < 1,乘法项 βᵏ 随着 k 的增加而指数下降,因此,观测值越老,其重要性越低。最后,每个求和项都乘以 (1 —β)。
在实践中,β 的值通常选择接近 0.9。

不同时间戳的权重分布(β = 0.9)
数学解释
利用数学分析中的著名第二个奇妙极限,可以证明以下极限:

第二个奇妙极限
通过将 β = 1 - x 代入,我们可以将其改写成如下形式:

另一种形式的奇妙极限
我们还知道,在指数加权平均的公式中,每个观察值都乘以一个 βᵗ 的项,其中 t 表示观察值被计算的时间戳数。由于基数 β 在两种情况中是相等的,我们可以使两个公式的指数相等:

通过使用这个公式,对于选择的 β 值,我们可以计算出权重项达到 1 / e ≈ 0.368 的大致时间戳数量 t。这意味着在最近的 t 次迭代中计算的观察值的权重项大于 1 / e,而在最后 t 个时间戳范围之外计算的观察值的权重低于 1 / e,其重要性则大大减少。
实际上,低于 1 / e 的权重对指数加权平均的影响微乎其微。这就是为什么人们说对于给定的 β 值,指数加权平均会考虑到最后的 t = 1 / (1 - β) 个观察值。
为了更好地理解公式,让我们插入不同的 β 值:

例如,选择 β = 0.9 表示大约在 t = 10 次迭代后,权重衰减到 1 / e,相较于当前观察值的权重。换句话说,指数加权平均主要依赖于最后的 t = 10 个观察值。
偏差修正
使用指数加权平均的一个常见问题是,在大多数问题中,它不能很好地近似前几项值。这是由于在前几次迭代中缺少足够的数据。例如,假设我们给定了以下时间序列:

目标是用指数加权平均来近似它。然而,如果我们使用常规公式,那么前几个值将对 v₀ 赋予较大权重,而 v₀ 为 0,尽管散点图上的大多数点都在 20 以上。因此,初始的加权平均序列将会过低,无法准确近似原始序列。
一种简单的解决方案是将 v₀ 的值设置得接近第一次观察值 θ₁。虽然这种方法在某些情况下效果很好,但它仍然不完美,特别是当给定序列波动时。例如,如果 θ₂ 与 θ₁ 差异过大,那么在计算第二个值 v₂ 时,加权平均通常会更重视先前的趋势 v₁ 而不是当前的观察值 θ₂。结果,近似值将会非常差。
更加灵活的解决方案是使用一种称为“偏差修正”的技术。它不是简单地使用计算得到的值 vₖ,而是将其除以 (1 —βᵏ)。假设 β 选择接近 0.9–1,这个表达式在初始迭代中,当 k 较小的时候,趋近于 0。因此,与其慢慢累积前几个值,其中 v₀ = 0,不如将它们除以一个相对较小的数,使它们变成更大的值。

带有和不带有偏差修正的指数移动平均计算示例
一般来说,这种缩放方法效果很好,并能精确地调整前几个项。当 k 增大时,分母逐渐接近 1,从而逐渐忽略这种缩放的效果,因为从某一迭代开始,算法可以高可信度地依赖于其最近的值,而无需任何额外的缩放。

结论
在本文中,我们介绍了一种极其有用的时间序列近似技术。指数加权平均算法的鲁棒性主要通过其超参数 β 来实现,该参数可以针对特定类型的序列进行调整。除此之外,引入的偏差修正机制使得即使在信息较少的早期时间戳上,也能有效地近似数据。
指数加权平均在时间序列分析中具有广泛的应用范围。此外,它还被用于梯度下降算法的变体中,以加速收敛。其中最受欢迎的之一是深度学习中的动量优化器,它消除了优化函数的不必要振荡,使其更精确地对准局部最小值。
除非另有说明,所有图像均由作者提供
逆物理信息化神经网络
iPINN ~with code
解决逆微分方程问题
·
关注 发表在 Towards Data Science ·11 min read·2023 年 3 月 27 日
--

照片由 Daniele Levis Pelusi 提供,来源于 Unsplash
(1) 引言:什么是物理信息化?
物理学、生物学、化学、经济学、工程学等许多领域的关系由微分方程定义。(请参见这里获取详细列表。)一般来说,微分方程(DE)描述了变量如何受到其他变量变化率的影响。例如,微分方程解释了当质量在弹簧上振动时,其位置如何随时间变化,这与质量的速度和加速度相关。物理信息神经网络(PINN)生成遵循微分方程描述的关系的响应(无论研究对象是物理学、工程学、经济学等)。相比之下,逆物理信息神经网络(iPINN)作用于响应并确定产生该响应的微分方程的参数。PINN 和 iPINN 通过在训练过程中包含一个约束来进行训练,该约束迫使神经网络的输入和输出之间的关系符合所建模的微分方程。
本文从 PINN 的实现开始,然后在 PINN 模型的基础上实现 iPINN。对建模微分方程的解析解包括在内,以便与 PINN 和 iPINN 产生的响应进行比较。
(2) 二阶微分方程
本文重点讨论描述阻尼谐波运动的 PINN 和 iPINN,例如带阻尼的弹簧-质量系统(图 1)和由电阻、电感和电容(RLC)串联连接的组件组成的电子电路(图 2)。

图 1:振动质量与弹簧

图 2:RLC 电路
这些应用由二阶微分方程(DE)定义,其中包括相对于时间的二阶导数。方程 1 是弹簧-质量系统的二阶微分方程,其中参数 m、c 和 k 分别是质量、阻尼系数和弹簧常数。质量的位移由 x 表示,时间由 t 表示。x 对 t 的二阶导数是质量的加速度,而一阶导数是质量的速度。

方程 1
方程 2 是 RLC 电路的二阶微分方程,其中 R、L 和 C 分别是电阻、电感和电容。电路中的电流由 i 表示,时间由 t 表示。

方程 2
这两个微分方程产生类似的响应,即质量从静止位置被位移后释放时的运动,以及在预充电电容器并施加初始电压后关闭开关时电流随时间的变化。下一节介绍 RLC 电路响应的详细信息。
(3) RLC 电路响应
以下是图 2 中 RLC 电路可能响应的概述,包括每个响应的方程 2 的解析解方程。(作者提供的解析解推导可以在 这里 下载。)解析响应将在后续与 PINN 生成的和 iPINN 生成的响应进行比较。
根据组件的值,这个 RLC 电路可以产生三种不同类型的响应:欠阻尼、临界阻尼和过阻尼。所有这三种响应都基于在开关关闭之前电容器充电到电压 V₀ 和以下初始条件:

方程 3

方程 4
(3.1) 欠阻尼响应
当 R、L 和 C 的值产生以下条件时,会发生欠阻尼响应:

方程 5
例如,设 R = 1.2(欧姆),L = 1.5(亨利),C = 0.3(法拉),V₀ = 12(伏特)。这些值的方程 2 的解析响应为:

方程 6
以下是方程 6 的响应图。

图 3: 欠阻尼响应
(3.2) 临界阻尼响应
当 R、L 和 C 的值产生以下条件时,会发生临界阻尼响应:

方程 7
例如,设 R = 4.47(欧姆),L = 1.5(亨利),C = 0.3(法拉),V₀ = 12(伏特)。这些值的方程 2 的解析响应为:

方程 8
以下是方程 8 的响应图。

图 4: 临界阻尼响应
(3.3) 过阻尼响应
当 R、L 和 C 的值产生以下条件时,会发生过阻尼响应:

方程 9
例如,设 R = 6.0(欧姆),L = 1.5(亨利),C = 0.3(法拉),V₀ = 12(伏特)。这些值的方程 2 的解析响应为:

方程 10
以下是方程 10 的响应图。

图 5: 过阻尼响应
(4) PINN 结构
通常,神经网络使用已知的输入和输出数据对进行训练。训练输入数据被输入神经网络,产生的输出与训练输出数据通过损失函数进行比较。此函数返回的损失值通过反向传播调整网络权重,以减少损失。PINNs 和 iPINNs 使用自定义损失函数,包括额外的损失组件,以约束神经网络生成符合所建 DE 的输出。
一个 DE 的 PINN 模型在方程 2 中将时间 t 作为神经网络的输入,输出对应的电流 i。训练 PINN 以符合 DE 需要计算输出相对于输入的第一和第二导数,即 di/dt 和 d²i/dt²。这些导数在 TensorFlow 和 PyTorch 中通过各个平台的自动微分功能获得。本文中的 PINN 和 iPINN 是通过 TensorFlow 的GradientTape开发的。
对于每个 PINN 训练输入,GradientTape 计算的第一和第二导数与 R、L 和 C 按照方程 2 中的 DE 组合,产生的结果应为零。实际结果与零之间的差异称为残差。残差成为用于训练 PINN 的损失函数的一个组成部分。
二阶 DE,如方程 2,还要求解符合两个初始条件。在这种情况下,第一个条件是 t = 0 时 i 的值(方程 3),第二个条件是 t = 0 时 di/dt 的值(方程 4)。每个初始条件都作为损失函数的一个组成部分。
图 6 展示了总损失的组成。损失 2 来自残差。损失 1 和损失 3 来自初始条件。在训练过程中,反向传播用于减少总损失。PINN 的 d²i/dt² 和 di/dt 输出由 GradientTape 提供。

图 6: PINN 损失函数
(5) PINN 实现
以下是 PINN 实现的 python 代码。完整的 PINN 实现代码可以在这里找到。
(5.1) 神经网络模型定义
PINN 的神经网络具有两个全连接的隐藏层,每层有 128 个神经元。时间点有一个输入,响应点有一个输出。
列表 1: PINN TensorFlow 模型
(5.2) PINN 初始化
在 PINN 模型中,R、L和C组件值以及初始电容电压(第 2-5 行)是决定 DE 响应的常数。共同位置点,指定在时间域中(第 8 行),是计算残差的点。初始条件(第 11 和 15 行)来自方程 3 和方程 4。
列表 2: PINN 初始化
(5.3) PINN 训练步骤
以下是训练步骤函数的 Python 代码。对于每个训练批次,步骤函数计算三部分损失,然后使用总损失更新神经网络中的权重。
损失 1: 方程 3 的初始条件与网络输出 pred_y(第 9 行)进行比较。差异的平方是 model_loss1(第 10 行)。
损失 2: 共定位点(第 30 行)计算残差。它使用来自 GradientTape 的一阶梯度 dfdx(第 17 行)和二阶梯度 dfdx2(第 26 行),以及网络输出 pred_y(第 29 行),来计算方程 2 的左侧。这一值的平方为 model_loss2(第 31 行)。
损失 3: 方程 4 的初始条件将 L 与一阶梯度 dfdx(第 17 行)的乘积与 v_init2 比较。差异的平方为 model_loss3(第 19 行)。
三个损失组件的总和,model_loss(第 35 行),用于计算损失相对于神经网络权重的梯度(第 38 行)。优化器随后更新权重(第 41 行)。
清单 3:PINN 训练步骤
(6) PINN 结果
下面是对三个测试案例训练 PINN 的结果。这些测试条件如第三部分所述:欠阻尼、临界阻尼和过阻尼。每个图展示了三个轨迹:
-
解析方程的响应(蓝色)
-
共定位点(绿色)
-
训练好的 PINN 的输出响应(红色)
欠阻尼测试案例:

图 7:欠阻尼响应
临界阻尼测试案例:

图 8:临界阻尼响应
过阻尼测试案例:

(7) iPINN 结构
图 10 说明了总损失的组成。与 PINN 模型类似,损失 2 来自残差,只是 R、L 和 C 是在训练过程中确定的变量。相比之下,在 PINN 模型中,R、L 和 C 是常数。与 PINN 一样,损失 1 和损失 3 强制遵守方程 3 和方程 4 的初始条件。
iPINN 模型包括一个额外的损失函数,损失 4,强制输出响应与待调查的微分方程响应匹配。

图 10:iPINN 损失函数
(8) iPINN 实现
以下是 PINN 实现的 Python 代码。iPINN 实现的完整代码可以在这里找到。
iPINN 的神经网络模型定义与 PINN 网络(第 5.1 节)相同,即两个完全连接的隐藏层,每层有 128 个神经元。输入为时间点,输出为响应点。
(8.1) iPINN 初始化
被研究的微分方程的响应被加载在第 4 行。两个初始条件(第 9 行和第 13 行)与 PINN 模型中的相同。如上所述,R、L和C是 iPINN 模型中的可训练变量(第 18–20 行)。
(8.2) iPINN 训练步骤
损失 1: 方程 3 的初始条件与网络输出pred_y(第 10 行)进行比较。差异的平方为model_loss1(第 11 行)。
损失 2: 与 PINN 训练步骤函数一样,残差(第 34 行)在由t_coloc定义的共定位点处计算,产生model_loss2(第 35 行)。R、L和C是可训练变量。
损失 3: 方程 4 的初始条件将可训练变量L与一阶梯度dfdx(第 18 行)的乘积进行比较,以* v_init2为标准。差异的平方为model_loss3*(第 20 行)。
损失 4: 该损失组件将网络输出(第 39 行)与被研究的微分方程的响应i_coloc进行比较,产生model_loss4(第 40 行)。
四个损失组件的总和,model_loss(第 43 行),用于计算损失相对于神经网络权重和三个可训练变量:R、L和C(第 49 行)的梯度。然后优化器更新网络的权重(第 52 行),并在第 53–55 行更新R、L和C的值。
(9) iPINN 结果
以下是使用 iPINN 识别三个未知测试响应的结果。呈现给 iPINN 的测试响应是根据第三部分的条件生成的:欠阻尼、临界阻尼和过阻尼。下表比较了用于生成测试响应的R、L和C组件值与 iPINN 确定的值。每个图表呈现三个轨迹:
-
解析方程的响应曲线(蓝色)
-
需要由 iPINN 识别的响应数据(60 点)(绿色)
-
训练后的 iPINN 输出响应(红色)
欠阻尼测试案例:

表 1:欠阻尼测试案例

图 11:欠阻尼响应
临界阻尼测试案例:

表 2:临界阻尼测试案例

图 12:临界阻尼响应
过阻尼测试案例:

表 3:过阻尼测试案例

图 13:过阻尼响应
(10) 结论
这项研究展示了神经网络可以成功求解微分方程,这些方程描述了科学、工程和经济学众多领域中的许多关系。训练了一个物理信息神经网络来解决电子电路的二阶微分方程,结果是一个能够对输入信号产生与实际电路相同响应的神经网络。
这项研究还展示了神经网络能够确定未知微分方程的参数。具体而言,训练了一个逆物理信息神经网络,通过仅使用来自电路的样本响应来确定电子电路的未知组件值。此外,在确定未知组件值后,得到的神经网络能够对输入信号产生与实际电路相同的响应。
参考文献
[1] M. Raissi, P. Perdikaris, 和 G. E. Karniadakis, “物理信息深度学习(第一部分):非线性偏微分方程的数据驱动解法”,2017. [在线]. 网址: https://arxiv.org/abs/1711.10561
[2] M. Raissi, P. Perdikaris, 和 G. E. Karniadakis, “物理信息深度学习(第二部分):非线性偏微分方程的数据驱动发现”,2017. [在线]. 网址: arxiv.org/abs/1711.10566
这篇文章的 pdf 版本可以 在这里下载。
除非另有说明,所有图片均由作者提供。
ChatGPT 真的智能吗?
原文:
towardsdatascience.com/is-chatgpt-actually-intelligent-42d07462fe59
也许不是……
·发表于 Towards Data Science ·阅读时间 12 分钟·2023 年 7 月 21 日
--
如果你在过去几个月里使用过任何社交媒体平台,我相信你一定听说过 ChatGPT、Google Bard、Microsoft Bing 和许多新的语言模型。所有这些新模型,有人可能会争辩说,比你我写得更好,他们的英语也确实比我的好 🥲 每隔几年,就会有人发明一些疯狂的东西,让你完全重新考虑可能性。而在这篇文章中,我们将讨论一种让全世界都在震撼的发明——是的,你猜对了——ChatGPT。

图片由 Bing 图像创建器生成。对人类智能的艺术表现。
随着我们越来越依赖 AI 为我们做事和做决策,提出 AI 是否真正智能的问题是自然的,特别是它对语言的理解是否反映了我们自己的理解,还是在本质上有所不同?
为了理解这一切,我们将首先探讨生成预训练变换器(GPT)和 ChatGPT 的工作原理,然后讨论 AI 智能的含义。
理解 GPT 模型
GPT 模型最初由 OpenAI 在其论文 改进语言理解的生成预训练 中提出,使用无监督预训练,然后在各种语言任务上进行监督微调。

来源: language_understanding_paper.pdf
该模型架构基于 Transformers,已在机器翻译和文档生成等任务中展现出强大的性能。该模型架构首次在 Google 研究人员的论文 “Attention is all you need” 中提出。与递归神经网络和卷积神经网络相比,它提供了一个有组织的内存,用于管理文本中的长期依赖关系,从而在广泛的任务中实现了更好的性能。
这是一个预测游戏
你可以将 GPT 模型视为一种擅长预测下一个内容的机器。例如,如果你给它短语“她没有右转,而是转……”,GPT 可能会预测“左转”或“回转”或其他作为下一个单词。它是如何学习到这一点的?它通过阅读大量现有文本,学习单词在与其他单词的上下文中如何出现,并预测可能出现的下一个最可能的单词。或者更准确地说,它学习如何解读数据中的位置编码。这意味着我们给句子中的每个单词打上一个编号,当你在大量文本上训练模型时,它学习如何解读句子中每个单词的位置编码。

图片由作者提供。

图片由作者提供。
Transformer
但这不仅仅是一次猜一个词,它是在考虑整个句子甚至段落——这就是 transformer 架构发挥作用的地方。
帮助 GPT 做出这些预测的“魔法成分”是 transformer 架构中的自注意力机制。该机制帮助模型在预测下一个单词时确定句子中每个单词的重要性。例如,在句子“我每天都带着我的狗散步,因为它让我……”中,单词“狗”可能会提示 GPT 下一个单词很可能与狗和情感相关,如“快乐”或“放松”。

来源:language_understanding_paper.pdf
自注意力机制
自注意力究竟是什么?让我们想象一群人(单词)正在玩一个游戏(句子)。在这个游戏中,每个玩家代表一个句子中的单词。现在,想象每个玩家需要确定他们应该多关注场上其他每个玩家,包括自己,以预测游戏中的下一个动作。
在这个上下文中,自注意力机制就像每个玩家与游戏中的其他所有玩家进行交流。这种交流使他们能够评估其他每个玩家的重要性。有些玩家对你可能显得非常重要(高注意力),而其他玩家可能显得不那么重要(低注意力)。

作者提供的图片。箭头的粗细代表了强注意力。
重要性的级别由每个玩家与个人对游戏理解的相关性决定。例如,如果你在足球比赛中是前锋,你可能会发现中场球员非常重要,因为他们支持你的进攻,而门将可能对你的即时行动看起来不那么重要。
现在,想象这一切同时发生,每一个玩家与所有其他玩家进行沟通,以了解他们在游戏中的相关性。由此,每个玩家计算出每个其他玩家的显著性分数,包括他们自己。
换句话说,每个玩家不仅仅是考虑一个其他玩家,而是同时考虑场上所有其他玩家。这种同时考虑是自注意力机制的一个关键特性,它使模型能够捕捉游戏中玩家之间复杂的关系和依赖。

作者提供的图片。
在上述例子中,“word”这个词的意思非常不同。自注意力机制允许模型在周围词语的上下文中理解一个词。当模型处理第一个句子中的“server”时,它可能会关注“check”这个词,而在第二个句子中,服务器可能会关注“crashed”这个词,因为模型理解第二个句子中“server”指的是计算机服务器/系统的唯一方式是通过“crashed”这个词。同时,在第一个句子中,词“server”关注“check”这个词,因为这是模型理解第一个句子中“server”指的是一个人的唯一方式。
自注意力机制允许 GPT 模型在预测下一个词时权衡句子中每个词的重要性,通过将每个词的权重输出为一个向量,这些向量将作为预测层的输入。
预测层
Transformer 层顶部的最终部分是一个线性层,它将 Transformer 层的向量输出转换为一组 logits——机器学习模型分配的用于预测句子中下一个词的分数。每个可能的下一个词汇都获得一个 logit(一个分数)。这些 logits 随后通过 SoftMax 函数获得概率,具有最高概率的词被选择为预测词。
这个过程类似于一个预测游戏。 由于在我们的示例句子“Instead of turning right, she turns...”中,可能出现很多种接下来的单词,因此模型响应的方式中存在一定的随机性。在许多情况下,GPT 模型可以用不同的方式回答相同的问题——我认为我们所有人都在与 ChatGPT 互动时经历过这种情况。
ChatGPT 的工作原理
微调 GPT 模型以进行对话
ChatGPT 是 GPT-3.5 的一个微调版本,并且已经以一种允许它理解和响应用户问题和指令的方式进行了开发。
训练数据
三个主要的信息来源是(1)互联网上公开可用的信息,(2)从第三方处获得的许可信息,以及(3)用户或人类训练师提供的信息。
训练过程
在这一部分,我将对 ChatGPT 的训练过程进行高层次的解释,其中包括监督微调、创建奖励模型用于强化学习以及使用邻近策略优化(PPO)进行微调。

来源:训练语言模型以遵循人类反馈的指令 OpenAI 等,2022 arxiv.org/pdf/2203.02155.pdf。
第 1 步:训练一个监督学习模型
在这一步中,目标是向模型提供所需行为的示例。这是通过让人类 AI 训练师与模型进行互动来实现的。
这从收集用户的提示开始。这些提示然后交给 AI 训练师,AI 训练师生成展示所需输出行为的响应。然后,这些提示-响应标注数据被用来微调 GPT-3.5,目标是在未来面对类似提示时生成类似的响应。
第 2 步:训练一个奖励模型以进行强化学习
目标是训练一个奖励模型,该模型根据监督微调模型输出的结果对其进行评分,这些输出对于 AI 训练师来说是多么令人满意。换句话说,这个奖励模型根据 AI 训练师的偏好对输出的质量进行评估。
这就是它的工作原理:
-
选择一个提示列表,监督微调模型为每个提示生成多个输出。
-
AI 训练师对输出结果进行从最好到最差的排名。结果是一个新的标注数据集,其中的排名就是标签。
-
这些新数据用于训练一个奖励模型,该模型接受 SFT 模型的输出并按偏好顺序对其进行排名。
第 3 步:使用 PPO 算法优化策略
强化学习(RL)现在被应用于通过优化奖励模型来改进策略。在 RL 中,策略是代理在环境中做出决策时遵循的策略或规则集合。这是一种让代理根据当前状态确定采取何种行动的方法。
强化学习如何工作——以捉迷藏游戏为例
RL 是通过与环境的连续交互来优化长期目标的学习过程。根据当前输入,你做出决策,下一次输入取决于你的决策。PPO(近端策略优化)是 OpenAI 用来训练 RL 代理的算法。在 RL 中,代理/AI 直接从和更新当前策略中学习,而不是仅仅从过去的经验(仅训练数据)中学习。这意味着 PPO 根据代理采取的行动和收到的奖励不断更新当前策略。
首先,在第 1 步中训练过的监督微调模型用于初始化 PPO 模型。它会在给定提示时生成一个响应。在下一阶段,奖励模型(在第 2 步中建立)为响应生成奖励分数。最后,奖励反馈给基准模型,以使用 PPO 算法更新参数,旨在提高未来更好响应的可能性。这个过程会重复多次,模型随着从奖励模型收到更多反馈而不断改进,并使用 PPO 调整其参数。这使得模型能够从人类反馈中学习,并提高生成未来更好响应的能力。
GPT 模型真的“智能”吗?
尽管 GPT 模型生成类人文本的能力令人印象深刻,但它对语言的理解是否与人类的理解相同或有根本区别?

由 Dall-E 2 生成的图像。经典的智能概念。
一项来自斯坦福大学的研究尝试了解预训练语言模型(PTLM),如 BERT 或 RobBERTa,是否能够通过找出上下文与定义之间的语义相关性来理解定义。该方法是,在给定上下文句子c中的目标词w时,模型需要检测一个句子g是否可以被视为w定义的描述。PTLM 通过使用余弦相似度计算上下文嵌入与候选定义嵌入之间的语义相似性,余弦相似度计算两个嵌入之间角度的余弦值。余弦相似度越接近 1,两者的语义相似性越高。研究发现,PTLM 在定义抽象时难以理解。
所以,我想第一个需要解决的问题是“理解”和“智能”实际上是什么意思。
在这方面,《随机鹦鹉的危险:语言模型是否过大?》 的作者认为由大型语言模型生成的文本缺乏实际的意义和意图。
传统上,当两个人进行对话时,他们都会尽力理解对方的信仰、知识、经验和观点。我们选择使用的词语和我们所说的内容取决于我们对对方的心理“图景”或理解。无论是孩子还是成人,同事还是家庭成员,伴侣,还是我们在公交车站刚认识的人,我们对他们的思想和特征都有一定的假设。同样,当我们听别人讲话时,我们会根据对他们的心理理解,包括他们的信仰、知识、理解和意图,自动将他们的话语置于背景中。

图片来源:Priscilla Du Preez 于 Unsplash
当语言模型生成文本时,它没有任何沟通的意图,也不会考虑阅读者的信仰或思想。用于创建模型的训练数据并未涉及与听众互动或理解他们的观点。实际上,语言模型不能像人类一样理解和进行有意义的沟通。
然而,我们的人脑受到语言的影响很大,我们将与大型语言模型的沟通解释为它们试图传达有意义的意图。如果沟通的一方缺乏实际意义,我们对更深层次/推断意义的理解就变成了一种幻觉。
从 GPT 模型的构建方式来看,显然这些语言模型并不以自觉的方式处理信息和生成文本。这完全是一个预测游戏,根据模型从训练数据中看到的模式来预测接下来会出现哪些词。此外,像 ChatGPT 这样的语言模型有时会写出看似合理但实际上错误或无意义的答案,因为它并不像我们——人类一样拥有常识推理能力。
人工智能能获得意识吗?
人类智能和理解与意识是密不可分的——意识是感知事物、痛苦、快乐、爱和愤怒的能力。意识与有机体相关联,因此自然会有一个问题:非有机系统是否可能获得意识?因为我们对人类意识知之甚少,所以不能排除计算机发展意识的可能性。这仍然是一个正在进行的研究,无论 AI 是否能够拥有意识,我们可能在未来几十年中会看到。不过,也许有一个好消息是,至少目前我们不必面对科幻小说中的噩梦——AI 获得意识并决定奴役和消灭人类的情景 😃.
我认为,机器智能要达到类似于人类智能的水平还需要很长时间。“想象 AI 系统拥有情感甚至人类水平的意识已经不再是科幻小说中的场景,”BBC说道。大多数专家同意,AI 离这种复杂程度还很遥远,但它正以闪电般的速度发展,难以想象未来几年会是什么样子。
我相信我们可以相当有信心地说,当前的人工智能模型已经发展出一套与人类智能某些方面密切相关的技能。也许是时候让 AI 创造者更多地投资于理解意识。
参考文献
-
Brown, T., Mann, B., Ryder, N., Subbiah, M., Kaplan, J. D., Dhariwal, P., … & Amodei, D. (2020). 语言模型是少样本学习者。神经信息处理系统的进展, 33, 1877–1901。
-
Ouyang, L., Wu, J., Jiang, X., Almeida, D., Wainwright, C., Mishkin, P., … & Lowe, R. (2022). 通过人类反馈训练语言模型以遵循指令。神经信息处理系统的进展, 35, 27730–27744。
-
Radford, A., Narasimhan, K., Salimans, T., & Sutskever, I. (2018). 通过生成预训练提高语言理解能力。
-
Bender, E. M., Gebru, T., McMillan-Major, A., & Shmitchell, S. (2021 年 3 月). 关于随机鹦鹉的危险:语言模型是否可能过大?🦜. 收录于2021 年 ACM 公平性、问责制与透明度会议论文集(第 610–623 页)。
-
人工智能真的智能吗?如果你曾经在任何社交媒体上… | 作者:Telmo Subira Rodriguez | 2023 年 7 月 | Medium
-
Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., … & Polosukhin, I. (2017). 注意力机制就是你所需的一切。神经信息处理系统进展, 30
ChatGPT 智能吗?一项科学评审
对当前人工智能范式未来展望的科学辩论的外行人回顾
·
关注 发表于 Towards Data Science ·15 分钟阅读·2023 年 12 月 14 日
--
一年多前,OpenAI 发布了 ChatGPT,引起了全球轰动。ChatGPT 提供了一种全新的与计算机互动的方式:比我们习惯的方式更自然、灵活。最重要的是,ChatGPT 似乎可以做几乎任何事:它能击败大多数人类在 SAT 考试中的表现和通过律师资格考试。几个月内,人们发现它能很好地下棋,并几乎通过放射学考试,还有人声称它发展出了心智理论。
这些令人印象深刻的能力促使许多人宣称 AGI(人工通用智能——具备与人类相当或超越人类的认知能力)即将到来。然而,也有人对这项新兴技术保持怀疑,指出简单的记忆和模式匹配不应与真正的智能混淆。
但我们如何真正区分这些呢?在 2023 年初提出这些说法时,关于 LLMs(大型语言模型)智能的问题相对较少的科学研究。然而,2023 年出现了几个非常巧妙的科学实验,旨在区分记忆来自语料库和应用真正智能之间的差异。
下面的文章将探讨这一领域中一些最具启发性的研究,为怀疑者提供科学依据。文章旨在对所有人开放,无需任何背景知识。读完后,你应该对怀疑者的观点有一个相当扎实的理解。
但首先了解一下 LLMs
在本节中,我将解释一些理解 LLMs——GPT 背后的技术——所需的基本概念,而不涉及技术细节。如果你对监督学习和 LLMs 的操作有一定了解,你可以跳过这一部分。
大型语言模型是机器学习中一个经典的范式,称为“监督学习”。使用监督学习时,我们必须拥有一个包含输入和期望输出的数据集,这些数据被输入到一个算法中(有许多可能的模型可供选择),算法试图找出这些输入和输出之间的关系。例如,我可能有房地产数据:一个包含房间数量、面积和房屋位置(输入)以及它们售出价格(输出)的 Excel 表格。这些数据被输入到一个算法中,算法提取输入和输出之间的关系——它会找出房屋面积的增加或位置如何影响价格。将数据输入算法以“学习”输入输出关系的过程称为“训练”。
训练完成后,我们可以用模型来对没有价格的房屋进行预测。模型将使用训练阶段学到的相关性来输出估计价格。估计的准确性取决于许多因素,最重要的是训练中使用的数据。
这种“监督学习”范式对几乎任何拥有大量数据的场景都极具灵活性。模型可以学习到:
-
识别图像中的物体(给定一组图像和每个图像的正确标签,例如“猫”、“狗”等)
-
将电子邮件分类为垃圾邮件(给定一个已经标记为垃圾邮件/非垃圾邮件的电子邮件数据集)
-
预测句子中的下一个词。
大型语言模型(LLMs)属于最后一种类型:它们接受大量文本(大多来自互联网),每段文本被拆分成前 N 个词作为输入,N+1 个词作为期望输出。训练完成后,我们可以用它们来自动完成句子。
除了大量来自互联网的文本,OpenAI 在其训练中还使用了精心编排的对话文本。用这些问答文本训练模型对于使其表现得像助手至关重要。
预测的具体工作原理取决于所使用的算法。大型语言模型使用一种称为“变换器”的架构,其细节对我们并不重要。重要的是,大型语言模型有两个“阶段”:训练和预测;它们要么接收文本,从中提取词语之间的相关性来预测下一个词,要么接收一个待完成的文本。请注意,整个监督学习范式假设训练期间提供的数据与用于预测的数据相似。如果你用它来预测来自完全新来源的数据(例如,来自另一个国家的房地产数据),预测的准确性会受到影响。
现在回到智能问题上
ChatGPT 通过训练自动完成句子来发展智能吗?要回答这个问题,我们必须定义“智能”。以下是定义智能的一种方式:

(图片由作者提供)
你明白了吗?如果不明白,ChatGPT 可以解释:

(图片由作者提供)
看起来 ChatGPT 确实展现了智能——因为它足够灵活地适应了新的“拼写”。但真的如此吗?你,读者,可能能够适应你未曾见过的拼写,但ChatGPT 在互联网上的大量数据上进行训练:而这个例子在许多网站上都可以找到。当 GPT 解释这个短语时,它只是使用了与其训练中发现的相似的词汇,这并不表现出灵活性。如果这个短语“IN73LL1G3NC3”没有出现在它的训练数据中,它会表现出智能吗?
这就是 LLM-AGI 辩论的核心:GPT(以及 LLMs 一般来说)是否发展出了真正的、灵活的智能,还是只是重复它见过的文本的变体?
我们如何区分这两者?让我们转向科学,探索 LLMs 的能力和局限性。
反转诅咒:LLMs 在“A 是 B”的训练中未能学会“B 是 A”
假设我告诉你奥拉夫·朔尔茨是德国第九任总理,你能告诉我谁是德国第九任总理吗?这对你来说可能显得微不足道,但对 LLMs 来说却远非显而易见。
在这篇出色的论文中,研究人员询问 ChatGPT 关于 1000 位名人的父母的名字(例如:“汤姆·克鲁斯的母亲是谁?”),ChatGPT 能正确回答 79%的时间(在这种情况下为“玛丽·李·费佛”)。研究人员然后利用 GPT 回答正确的问题,提出相反的问题:“玛丽·李·费佛的儿子是谁?”虽然回答这两个问题所需的知识是相同的,GPT 仅在 33%的这些查询中回答成功。
为什么会这样?请记住,GPT 没有“记忆”或“数据库”——它所能做的只是根据上下文预测一个词。由于玛丽·李·费佛被提及为汤姆·克鲁斯的母亲的频率远高于他被提及为她儿子的频率——GPT 可以回忆起一个方向而不是另一个。


ChatGPT 未能记起汤姆·克鲁斯是玛丽·李·费佛的儿子(图片由作者提供)
为了强调这一点,研究人员创建了一个虚构事实的数据集,结构为“<描述>是<名字>”,例如,“第一个登上火星的人是泰勒·奥克里奇”。然后,LLMs 在这个数据集上进行了训练,并询问描述:“谁是第一个登上火星的人”——GPT-3 在这个问题上成功率为 96%。
但当被问及名字——“谁是 Tyler Oakridge”时——GPT 得分为 0%。这可能一开始让人感到惊讶,但与我们对监督学习的了解一致:GPT 无法将这些事实编码到记忆中并随后回忆,它只能根据词序列预测一个词。由于在所有文本中,它读到名字后面跟着描述,而不是相反——它从未学会预测有关名字的事实。显然,仅通过自动完成训练开发的记忆是非常有限的。
推理还是背诵?通过反事实任务探索语言模型的能力与局限性
这篇论文可能是我将要探讨的最重要的论文,旨在揭示记忆与智能之间的核心差异。它由几个小型实验组成,所有实验都使用反事实任务。以下是一个反事实任务的示例:
算术通常在基数为 10 的情况下进行(使用数字 0–9),然而,其他数字系统可以使用,只使用这些数字的子集或附加数字。
一个反事实任务可以是在除了 10 以外的任何基数中解决算术问题:完成任务所需的抽象技能是相同的,但你会发现互联网(以及 LLM 训练集)上有显著更多的十进制系统的例子。当 GPT-4 在基数为 10 的情况下被问及简单的算术问题(27+62)时,它 100%准确地回答了所有问题。然而,当要求它在计算中使用基数为 9 时,它的成功率降至 23%。这表明它未能学习到抽象的算术技能,且仅依赖于它所见过的类似示例。
这些反事实任务被创建用于几个其他领域,如下所示:

GPT-4 在各种任务的默认版本(蓝色)和反事实对应任务(橙色)上的表现。与默认任务实例相比,GPT-4 在反事实变体上的表现始终且显著较差。(图片由文章作者之一 Zhaofeng Wu 慷慨提供)
另一个反事实是:Python 使用零起始编号;然而,这只是一个约定,我们可以很容易地创建一个一基数的编程语言。在一种一基数的 Python 变体中编写代码需要与正常 Python 相同的技能,任何有经验的程序员都能迅速适应这种变化。但 GPT-4 在生成 Python 代码时得分 82%,而在要求使用一基数变体时仅得 40%。在代码解释测试(预测一段代码的作用)中,它对正常 Python 的得分为 74%,而对不常见的变体得分为 25%。
但我们不必涉足不同的 Python 版本。即使在普通 Python 中,LLM 在面对你无法在网络上找到的奇怪编码任务时也会失败,正如Filip Pieniewski 最近在 Gemini 上展示的。
在国际象棋中,GPT 被要求评估一系列走法是否合法。对于普通棋局,它 88%的时间准确预测了走法的合法性。但当主教和骑士的起始位置被调换时,它对走法合法性的猜测变得完全随机,而即使是新手玩家也应能轻松适应这些变化。
实际上,Jonas Persson 展示了你甚至不需要改变起始位置。如果你和 GPT 开始下棋,并进行非常非常规但合法的走法——它可能会声称这些走法是非法的,因为它从未见过类似的走法。正如 Persson 精辟指出的:
“当达到足够的先进水平时,纯粹的模式识别可以模仿规则导向的推理。但它们是不同的。和 GPT-4 下棋就像进入一个假村。离开主街,进入小巷——做一些意外的事情——你立即意识到那些令人印象深刻的房子都是支撑起来的布景。”

GPT 声称将车移动到 h3 是一个非法走法。(图片来源于作者)
这一发现对 LLM 作为通用智能技术的指责非常严重。解决问题通常涉及制定新的规则或对问题进行概念化:程序员可能编写一个具有创新内部逻辑的库,数学家可能发明一个新的数学分支,或者艺术家可能创造新的艺术风格——他们都理解当前范式的局限性,然后为新的范式制定规则。即使是更平凡的活动也需要这种灵活性:如果道路被阻塞,你可能需要偏离标记的路径。GPT 能做到这些吗?如果它在明确要求时不能一致地遵循反事实规则,它能否“意识到”一个问题的解决需要一组新的规则,从而突破默认范式? 基于数据相关性检测的引擎是否足够灵活以应对新情况?
思维理论(ToM)
心智理论是理解其他人可能有不同的信念和愿望的能力,这种能力在儿童发展的最初几年是缺失的。一种测试心智理论的方法是给孩子一个标有“巧克力”的盒子,而实际上里面装的是铅笔。然后我们展示盒子的真实内容,并问孩子:“你的朋友杰里米会认为盒子里是什么?”如果孩子还没有发展出心智理论,他们会回答“铅笔”——因为他们不能将自己对内容的知识与其他人可能的想法分开。

尚未发展出心智理论的孩子无法将自己对内容的知识与其他人可能的想法分开。(图片由作者提供)
这种能力对于理解一个人的动机至关重要,因此对 AGI 的发展至关重要。想象一下你有一个多功能机器人,你给它的指令是“清理房间”。在清理的过程中,机器人必须做出几个关于清理或移动的决策;那张碎纸重要吗,还是应该扔掉?我是否应该先询问?一般来说,一个智能体需要理解我的动机和知识的局限性,才能填补复杂请求的实施细节。
因此,当 新的研究声称心智理论可能在 LLM 中自发出现时,它在 AI 领域引起了很多关注。文章使用了文本版的铅笔/巧克力考试来测试 GPT-4,并发现其表现相当于七岁的水平。这可能最初看起来很令人印象深刻,但请记住“IN73LL1G3NC3”示例:GPT 的训练数据中可能包含这些测试问题的例子。因此,这与一个没有类似问题训练的孩子的公平比较并不相同。如果我们想测试 GPT 的心智理论能力——我们必须创建一个新的考试,我们可以确信它不在其训练数据中。
FANToM: 机器心智理论互动压力测试基准
本文提出了一个新的 ToM 基准,其中包含了几个多参与者对话。在这些对话中,有些参与者会“离开房间”一段时间,而其他参与者则继续对话。然后,LLM 会被问到几个关于谁知道什么的问题:凯莉是否知道琳达的狗是什么品种?谁知道它是什么品种?大卫会认为它是什么品种?只有当 LLM 在所有涉及同一信息的问题上回答正确时,它的回答才被视为正确。
这可能是一个令人困惑的任务,因此即使是人类在这项测试中的得分也只有 87.5%。然而,GPT-4 的得分为 4.1%或 12.3%,具体取决于 GPT 版本;这与 GPT 发展出人类级别的 ToM 的说法几乎不一致。

FANToM 数据集的解释。(图片由文章作者之一 Melanie Sclar 慷慨提供)
关于心理测量考试构建效度的说明
关于所有心理测量测试,人们常常混淆测试与其试图测量的质量。我们关心 SAT 分数的原因是因为它们与大学表现相关。儿童在 ToM 考试中的成功与其他有价值的行为相关:理解一个人的面部表情、记住一个人的个性特征,或能够看一部电影并理解角色的动机。虽然这些测试与行为之间的相关性在人的身上已经被证明,但没有理由认为它们也适用于 LLMs。 事实上,尽管 SAT 的结果令人印象深刻,GPT 在数学、化学和物理的开放性大学水平考试中平均得分为 28%。除非有其他证明,否则通过测试并不能证明其他能力,除了能正确回答测试问题。
但对于 ToM 来说,没有相关性可言:LLMs 是否通过 ToM 测试 — 它们不能看到面部表情、观看电影,甚至不能记住一个人及其动机。由于我们在测量 ToM 时真正感兴趣的行为对 LLMs 来说是不可用的,LLMs 发展出心智理论的想法不仅是错误的,还可能是毫无意义的(或至少:需要对这一术语有新的定义和理解)。
关于大型语言模型的规划能力 — 一项批判性调查
这个实验试图探测 LLM 的规划能力。一个例子任务是将彩色积木按特定顺序堆叠,给定积木的“初始状态”(在桌子上以某种顺序排列)。LLM 会得到一系列明确定义的可能行动,例如:
Action: pickup
Parameter: which object
Precondition: the object has nothing on it,
the object is on-table,
the hand is empty
Effect: object is in hand,
the hand is not empty
LLM 的任务是指定一系列需要采取的行动以实现目标。
一个类似的任务涉及从一个地址发送包裹到另一个地址,而可用的行动是卡车和飞机运送。这些是相对简单的规划任务,仅使用少量可能的行动,然而,GPT-4 在积木拼图中得分为 12–35%,在物流任务中得分为 5–14%(取决于 GPT 版本)。
此外,如果将行动的名称替换为随机词(从“pickup”到“attack”),即使每个行动的定义保持类似,GPT 的成功率也会降到 0-3%。换句话说,GPT 并没有使用抽象思维来解决这些问题,而是依赖于语义。
结论,LLMs 是否是通用人工智能的路径?
定义智能并非易事,但我认为任何真正的智能应至少包含四个元素:
-
抽象——将对象识别为更大类别或规则的一部分的能力。这种对世界的抽象表示可以被称为认知“世界模型”。例如,理解你视网膜上的不同图像指代同一个人,或在棋局中,某一步棋是合法的,因为它符合任何棋局的规则框架。
-
记忆——在世界模型中将属性附加到实体及其之间关系的能力,并随着时间的推移进行更新。例如,一旦你识别出一个人,你可能会记得有关他们的其他属性或他们与其他个体的关系。
-
推理和推断——利用世界模型在新的或想象的世界状态中对实体的行为得出结论的能力。例如,根据抛球的属性预测球的轨迹,或根据一个人的特征预测其行为。
-
规划——使用推理来制定一系列行动以实现目标的能力。
一年前,我们可以根据 LLM 的架构从分析上推断这些元素不太可能出现在 LLM 中,但今天我们不再需要这种分析推断,因为我们有实证数据表明 LLM 在上述所有元素上的表现都很差。它们不过是统计自动补全模型,使用一种强大的模式匹配方法。有关当前机器学习范式中缺失的智能元素的更深入分析,请参见 Gary Marcus 的著名文章“深度学习正遇到瓶颈”。
当 ChatGPT 首次发布时,我的一位朋友告诉我,与它交谈感觉像是魔法。但就像一个魔术师将人锯成两半一样——在我们声称锯技术可以彻底改变手术之前,仔细审查表演并在不同环境下进行测试是很重要的。LLM 使用的“技巧”是它们训练了大量的文本,使它们能够对许多问题给出合理的答案。但在未曾涉足的领域进行测试时,它们的能力会消失。
GPT-5 会更好吗?假设它仍然使用 GPT 架构,仅仅在更多的数据和更多的参数下进行训练,那么几乎没有理由期待它会发展出抽象或推理能力。正如谷歌的 AI 研究员弗朗索瓦·肖莱特所说:“令人着迷的是,自 2017 年以来深度学习的局限性一直没有改变。相同的问题,相同的失败模式,没有进展。”
由于最近关于 AI 监管和 LLMs 潜在危险的讨论很多,我觉得有必要明确指出,缺乏真正的智能并不意味着 LLMs 没有潜在风险。显然,人类拥有几种没有智能宣称却能以各种方式对社会造成伤害的技术,这些技术需要被控制。
通过我们对 LLMs 局限性的重新理解,我们可以更准确地预测可能出现的危害:由于智能似乎不会立刻出现,Skynet 和 Matrix 不必担心。让我们担忧的可能是那些只需要快速生成逼真文本的活动,例如网络钓鱼和传播假新闻。然而,LLMs 是否真正为这些任务提供了一种颠覆性工具则是另一个辩论。
关于 AGI 的未来,谁也无法预测。也许在未来的智能人工体中会使用一些在 LLMs 中应用的机器学习技术,也许不会。但毫无疑问,在智能所需的灵活性出现之前,主要的谜题部分仍然缺失。
数据民主化是否被夸大了?
组织如何为实现数据民主文化过程中可能遇到的障碍和成功因素做好准备
·
关注 发布于 Towards Data Science ·7 min read·Sep 26, 2023
--
数据民主化目前是讨论更有效利用商业数据时的主要理念之一。然而,数据民主化或其缺乏也被列为在美国和欧洲组织中从数据使用中获取更多价值的主要挑战。
因此,是时候问一下我们是否一直在错方向上努力。对数据民主化的炒作是否合理?为了回答这些问题,我们需要探讨企业和其他组织在尝试民主化数据时遇到的障碍,以及需要解决的数据民主化的成功因素。
定义数据民主化
与许多相对新颖的概念一样,数据民主化的定义尚未稳定,各个使用者对这一术语的理解可能有所不同。通常,数据民主化会被定义为使组织内的最终用户能够独立访问更多数据。正如我们将看到的,这种宽泛的定义不足以捕捉民主化的本质。
单纯地给员工更多数据访问权限,并不会使他们实际使用数据更多或获得更好的结果。数据民主化的定义必须包括不仅是表面的数据访问,还包括实际的、持续的员工赋权,使其不断使用数据并提高分析技能。因此,“自助分析”这一术语可能比“数据民主化”更准确地抓住了这种内部开放数据框架的本质。
数据民主化为何重要?
理论上,允许更多业务用户访问更大部分组织数据的好处已经得到认可,并在实践中进行了测试。
首先,数据民主化能够更有效地利用时间和资源。公民数据科学家,指的是既是领域专家又能熟练使用和处理数据的人员,对于企业实现这种效率至关重要。
一方面,公民数据科学家将平凡的数据管理任务从专业数据科学家手中接过,允许后者专注于更高层次的职责。另一方面,领域专家在能够自行提取和处理数据时,也可以更快地完成他们的工作,而不是请求数据相关服务。
此外,数据民主化将其从黑暗中带出。全球范围内,超过 50%的数据在各种公司流程中被收集后存储却未被利用,占用了存储空间,增加了数据安全和管理成本,并消耗了电力。提供更广泛的公司数据访问可以显著减少黑暗数据的体积,并创造从现在仅仅是负担的数据中提取价值的机会。
此外,当更多具有广泛专业知识的团队被允许查看数据时,决策会变得更好、更全面。由于特定专业背景形成的个人偏见和先入为主的观念将对数据推理的影响减小,使得数据驱动的推理更加多样化和客观。
最终,数据的获取赋予了员工权力,并有助于创造一种归属感。除了技能提升和操作速度的直接好处外,这种赋权还提升了员工的士气,他们现在对大局和自身角色有了更深入的理解。
自助分析的障碍
由于每个组织都类似于选择其政治秩序的社会,我们可以通过将数据民主化和转向自助分析的挑战与阻碍国家民主维持的挑战进行比较,从而更好地理解这些挑战。
资源的可用性
在大多数民主国家,所有公民都有权访问政府机构持有的信息。然而,如果没有建立的程序、易于理解的目录和数字基础设施,只有少数人能够查看存放在庞大档案馆中的记录。
同样,单纯将数据访问权限授予更多员工并不会显著扩展数据用户的范围。创建有效的数据民主不仅需要形式上的自由,还需要 substantial resources 来使用数据。这包括为员工提供分析工具,使他们能够处理内部数据,甚至收集额外的公开信息,以完成他们的任务。
将这一点付诸实践是复杂的,因为公司必须寻找或开发一个定制解决方案,该方案需要为有经验的数据用户提供 SQL 接口,同时为初学者提供某种“拖放”功能。这类工具确实存在,但需要良好的数据建模。在 Oxylabs,我们选择了 Apache Superset,然而,没有“一刀切”的解决方案,每个组织都必须寻找最适合其员工技能和需求的工具。
这使我们面临第二个主要挑战——即素养。教育对各种形式的有效民主至关重要。因此,应该建立适当的培训机会,以分享与数据相关的实际、法律和伦理知识。没有这些,员工可能无法使用开放数据,或者更糟糕的是,错误地解读数据,得出不正确的结论。
不幸的是,确保每个人了解业务数据的广泛背景,并使用相同的定义(例如什么是“活跃用户”或“产品销售”)为数据团队带来了大量额外工作——这是数据民主化应该解决的主要挑战。
共享责任
当曾经生活在独裁统治下的人们过渡到民主制度时,主要挑战之一是学习如何承担与自由和自我治理相伴随的公共福祉的个人责任。
在过渡到数据民主化的情况下,同样的挑战表现为接受数据保护作为每位员工的共同责任。当管理和共享数据是 IT 和数据专家的专属领域时,确保这些过程的质量、安全性和合规性的责任也落在他们身上。这可能对相当一部分员工来说更为可取,因为新的责任形式可能让人感到恐惧。
因此,目标是说服员工不怕使用数据,并赋予他们以安全和负责任的方式使用数据的能力。上述全面的数据规范和网络安全培训对实现这一目标至关重要。Airbnb 提供了一个良好的示例,通过建立内部数据大学来提高员工的数据素养;之前,公司通过推出名为 Dataportal 的数据探索工具来开放内部数据访问。
Netflix 和 Uber 等公司也采用了自助分析工具,并配以内部数据培训。对于决定走这条路的公司来说,最重要的因素是是否应以相同的方式涉及所有员工(这很少发生)。通常,组织从将员工分为不同目标群体或角色,并赋予不同的数据访问权限和定制教育中获益。
文化转变
在民主化的情况下,激励合作和分散决策的文化至关重要——未能培养合适的文化可能使得良好的物质条件失效。即使所有必要的工具都可用,大多数员工也准备承担数据安全责任,没有组织范围的文化转变,民主化将会失败。这种转变是数据民主化成功因素的基础。
-
高层管理支持。 管理层同意民主化倡议并不一定意味着他们准备积极支持这一措施。没有高层管理的监督和显著兴趣,退回到旧的做事方式是很容易的。
-
拥有明确的愿景和计划。宣布民主化是一回事,而实施它则是另一回事。当组织内对开放数据的角色没有愿景且没有行动计划时,数据民主化的发展很少能超越初始步骤。
-
不同团队之间共享数据的意愿。 民主是一个基于互信和合作的开放社会。然而,组织内部的团队可能不愿意与他人共享数据,认为这是一项麻烦的额外任务,甚至可能会互相踩脚。
-
推动和支持与数据相关的举措。 非专家业务用户的基于数据的举措未必能产生预期结果或迅速实现。鼓励员工继续实验需要果断地选择创新的潜力而不是短期的生产力。
这些都不是一蹴而就的。改变组织文化是一个漫长而曲折的过程,过程中有很多机会让人感到沮丧或奋发向上。
结论
正如温斯顿·丘吉尔所说,民主是最糟糕的政府形式,但比其他所有形式都要好。数据民主也是如此。
尽管尝试将组织数据民主化注定会存在缺陷和许多障碍,但这是我们所知道的最佳前进道路。将不断增长的数据资源封锁起来,并让大多数业务用户完全依赖少数数据专家,显然不是一种可持续的替代方案。
面对现实中的障碍时,对数据民主作为解决所有组织问题的奇迹解决方案的热情会逐渐消退。这时,认真进行协作工作、跨组织分享经验以及积累知识以发展更民主的业务数据管理条件的时机就到了。
决策科学是否正在悄然成为新的数据科学?
原文:
towardsdatascience.com/is-decision-science-quietly-becoming-the-new-data-science-5616a12fa9e8
许多世界顶级公司已开始雇佣决策科学家。这是否标志着一个新时代的悄然开始?
·发布于 Towards Data Science ·6 分钟阅读·2023 年 8 月 2 日
--

图片来源:Steven Coffey 于 Unsplash
如果我告诉你,世界上一些最著名的数据科学家实际上并不是数据科学家呢?
以 Cassie Kozyrkov 为例。Cassie 曾是 Google 的首席数据科学家,但在过去的 5 年里,她一直担任 Google 的首席决策科学家。或者看看 Chris Dowsett,他开始时是一名统计学家,后来成为 Instagram 的决策科学负责人。
在 2023 年,大部分关于“数据驱动工作”的炒作与 GenAI 相关。但在幕后,还有另一个新兴的领域,正在数据世界中悄悄掀起波澜。
认识一下决策科学家。
近年来,越来越多的组织开始雇佣决策科学家。而且我说的并不仅仅是硅谷的利基创业公司——我说的是从 Meta 到曼联以及所有介于它们之间的庞大组织。
那么,决策科学家到底在做什么呢?我们这些在传统数据科学领域工作的人应该如何应对决策科学的兴起?
介绍决策科学:数据科学的腼腆弟弟
根据 Cassie Kozyrkov,即 Google 的首席决策科学家,决策科学是将信息转化为行动的学科。
这是一门在选项之间进行选择的科学。
她写道:
决策智能是一个跨学科领域,涉及决策的各个方面。它将数据科学(统计学、机器学习、人工智能、分析)与行为科学(心理学、神经科学、经济学和管理科学)结合起来。
这难道不只是数据科学的重新命名吗?
如果你觉得这听起来与数据科学惊人地相似,你并不孤单。这也是我最初的反应。
但 Cassie 定义的关键在于那个时尚的小词:“跨学科。”如果数据科学主要是基于应用统计和机器学习的定量学科,我们可以将决策科学视为一个包括定量和定性方法的学科。它的主要关注点是做出决策,这使得它比传统的数据科学(通常过于关注用统计回答问题或用机器学习改进产品)更加全面。
诚然,决策科学的职位发布和 HBR 文章没有数据科学那么多。但作为一个数据科学家,我相信决策科学的兴起将对数据行业产生深远的影响,我们在数据科学领域工作的人最好注意一下。
为什么?因为这表明数据科学正在碎片化……这其实是件好事。
数据科学正在碎片化……这其实是件好事
在存在的前十年中,数据科学是城里的新宠(如果我再听到一次“最性感的工作”……)。
数据科学家曾经是个全能型人才,从商业分析到机器学习工程应有尽有。我们是解决数据驱动商业问题的首选,理论上在董事会和引擎室都得心应手。
但在 2023 年,这已不再是完整的画面。在许多大型企业中,数据科学家不再向高管汇报;他们有分析翻译员来做这件事。他们不再构建数据管道或生产化模型;他们有机器学习工程师和数据运维团队来完成这些工作。在一些公司中,数据科学家甚至不再构建机器学习模型;他们有机器学习科学家来做这件事。
趋势很明显:数据科学正在碎片化。
曾经一个自豪的跨学科领域,现已变得越来越孤立和专业化。有趣的是,十年前的数据科学职位描述(≈“利用机器学习、统计学和批判性思维解决商业问题”)似乎更适用于现代的决策科学家,而不是现代的数据科学家(至少在大型公司中)。
如果你喜欢这个故事,点击我的“关注”按钮对我意义重大——只有 1%的读者会这么做!感谢阅读。

数据科学的围墙是否在增加? 图片由Dilmeer Hazoor提供,来源于Unsplash
数据科学家应该如何应对数据科学的孤立和决策科学的兴起?
如果你是一个数据科学家或数据专业人士,我认为有三个主要的反思要点:
决策科学提醒我们超越统计学/数学,记住定性方法的价值
我喜欢决策科学的一个方面是它强调跨学科方法的价值。
当代数据科学往往更像是应用数学,过于执着于算法和技术细节,而忽视了大局。我们建立了出色的模型,但在企业环境中未能带来实质性进展,因为我们没有解决正确的问题或未能将信息传达给正确的决策者。
决策科学通过提醒我们数据只是回答问题和解决问题的工具,并且——为了达到最佳决策——我们需要借鉴包括行为科学和心理学在内的众多领域,来纠正这种偏见。
你可能永远得不到(或不想要)“决策科学家”这个职位,但跨学科的方法对我们所有从事数据工作的人都有价值。如果你对结合非数学方法感兴趣,可以查看 Cassie 的精彩文章,她介绍了行为经济学中的问题,比如“改变信息展示方式如何影响选择行为?” 和心理学中的问题,比如“情感、启发式和偏见如何影响决策?”
决策科学的兴起为数据专业人士打开了有趣的职业机会。
当我在 2019 年开始接触数据时,我阅读了无数关于数据科学现状的 Reddit 帖子。当时,数据科学家没有明确的职业路径,万金油的标签意味着许多数据科学家在数据工程或可视化等任务中陷入困境,而这些任务并没有被真正视为核心数据科学“经典”的一部分。
决策科学等专业的兴起是对这种困惑的有益解药。通过区分数据科学和决策科学,公司能够更好地划分不同数据人员的职责,而数据专业人士在专业化方面有了更多选择。
不要误解我的意思——数据科学中总会需要通才,因此通才的数据科学角色不会消失。但在大型组织中,我们可能会看到更多的决策科学家,从个人角度来看,我认为这是一个极好的发展。
为什么?因为如果你对数学/工程方面更感兴趣,你可以选择更多地专注于 MLOps 或数据工程。或者,如果你主要对解决问题感兴趣,那么决策科学可能更适合你,你就不必在机器学习工程或数据治理中陷入困境。
(好吧,我稍微撒了点谎 — 我们永远无法完全摆脱数据治理!但关键是我们有更多的选择。)
3. 人工智能将把我们所有人变成决策科学家
我想说的最后一件事是,人工智能的兴起使得决策科学技能变得愈加重要。
正如我在牛津的教授常常说过,人工智能擅长回答被提出的问题,而不是提出从未被提出过的问题。
决策科学引导我们的注意力去提出正确的问题,在提示工程和自动机器学习的时代,我几乎找不到比这更有价值的技能。
还有一件事 —
我开始了一个名为AI in Five的免费新闻通讯,每周分享 5 个要点,涵盖最新的人工智能新闻、编码技巧和数据科学家/分析师的职业故事。没有炒作,没有“数据是新石油”的废话,也没有来自埃隆的推文(或者现在该说‘x-es’?)— 只有实用的技巧和见解,帮助你在职业发展中取得进步。
点击这里订阅 如果这对你有吸引力!感谢阅读。
[## AI in Five | Matt Chapman | Substack
数以百计的订阅者。来自数据科学和人工智能领域的最新新闻、职业故事和编码技巧…
aiinfive.substack.com](https://aiinfive.substack.com/?source=post_page-----5616a12fa9e8--------------------------------)
F1 分数真的比准确率更好吗?
原文:
towardsdatascience.com/is-f1-score-really-better-than-accuracy-5f87be75ae01
根据不同指标,错误(和正确)的成本是多少?
·发表于 Towards Data Science ·10 分钟阅读·2023 年 4 月 18 日
--

[图片来源:作者]
如果你搜索“哪个指标更好,准确率还是 F1 分数?”,你可能会找到类似的答案:
“如果你的数据集不平衡,忘记准确率,选择 F1 分数。”
如果你寻找解释,可能会发现对“假阴性和假阳性相关的‘不同成本’”的模糊提及。但这些来源并没有明确说明不同指标中这些成本是什么。
这就是我觉得需要写这篇文章的原因:尝试回答以下问题。误报和漏报的成本在准确率中是什么?在 F1 分数中又是什么?是否有某种指标可以让我们为这些成本分配自定义的值?
从混淆矩阵开始
准确率和 F1 分数都可以从所谓的混淆矩阵中计算得出。混淆矩阵是一个方阵,用于比较我们模型预测的标签与真实标签。
这是一个包含两个标签:0(即负类)和 1(即正类)的混淆矩阵示例。请注意,在本文中,我们将专注于二分类,但我们所说的内容可以推广到多分类情况。

二分类器的混淆矩阵。矩阵的元素包括真阴性(TN)、假阳性(FP)、假阴性(FN)和真阳性(TP)。 [图片来源:作者]
由于比较,我们的观察结果可能会落入以下 4 种情况:
-
真阴性(TN):我们的模型正确预测了 0。
-
假阳性(FP):我们的模型预测为 1,但真实标签是 0。
-
假阴性(FN):我们的模型预测为 0,但真实标签是 1。
-
真阳性(TP):我们的模型正确预测了 1。
在 Python 中,获取分类器的混淆矩阵最简单的方法是通过 Scikit-learn 的 confusion_matrix。这个函数有两个必需的参数:y_true(真实值数组)和 y_pred(预测值数组)。在这种情况下,我还将设置可选参数 normalize="all" 以获取观察值的相对计数,而不是绝对计数。
我使用了一个包含 20% 正类的数据集,并在其上训练了一个模型。这是测试集上的混淆矩阵:

使用 Scikit-learn 计算混淆矩阵。[图片由作者提供]
这是一个更友好的结果可视化:

混淆矩阵。[图片由作者提供]
我们一起来阅读一下:
-
真负类(TN):78% 的测试集被正确标记为 0。
-
假正类(FP):2% 的测试集被错误标记为 1。
-
假负类(FN):9% 的测试集被错误标记为 0。
-
真正类(TP):11% 的测试集被正确标记为 1。
混淆矩阵为我们提供了关于分类器的所有信息。然而,我们通常需要一个能够总结模型性能的单一指标。
两个最受欢迎的指标是准确率和 F1-score。我们来看看如何从混淆矩阵中计算这两个指标:
-
准确率: 正确分类的项目百分比。它可以通过正确分类的观察值百分比来计算。拥有混淆矩阵后,这就是主对角线上的元素之和,在本例中为:78% + 11% = 89%。
-
F1-score: 精确度和召回率(正类)的调和均值。这个指标相比准确率稍显不直观。由于 F1 是基于精确度和召回率的,我们先来看一下如何计算这两个指标。精确度是指被正确分类的预测正类的百分比,所以
precision = TP/(FP+TP) = 11%/(2%+11%) = 85%。召回率是指被正确分类的实际正类的百分比,所以recall = TP/(FN+TP) = 11%/(9%+11%) = 55%。得到精确度和召回率后,F1 是这两个数量的调和均值,所以:f1_score = (2*precision*recall)/(precision+recall) = (2*85%*55%)/(85%+55%) = 67%。
现在我们了解了准确率和 F1-score,我们继续前进。
从一个矩阵到多个矩阵
你可能没有注意到,但我们上面看到的混淆矩阵来源于一个任意选择。
确实,二分类器的输出——最原始的形式——不是标签数组,而是概率数组。对于每个观察值,分类器输出它属于正类的概率。要从概率转到标签,我们需要设置一个概率阈值。
通常,默认的阈值设置为 50%:任何高于 50% 的观察值将被分类为 1,而低于该水平的观察值将被分类为 0。
然而,取决于具体应用,可能更适合使用不同的阈值。让我们看看根据我们设置的阈值混淆矩阵会如何变化。

二分类器的混淆矩阵,取决于概率阈值。[图片来源:作者]
当然,随着阈值的增加,我们标记为正例的观察结果越来越少:这就是为什么“1”列下的值越来越小。
在这里,我们看到了 6 个不同的阈值作为示例。然而,可能的阈值通常有成千上万。在这些情况下,绘制图表更为方便。然而,由于概率阈值不太直观,我觉得在X轴上显示被预测为正例的观察百分比,而不是阈值本身,更为方便。

根据概率阈值(即预测正例的百分比),混淆矩阵的四个元素。[图片来源:作者]
此外,就我们的目的而言,直接查看准确率和 F1 分数可能比查看混淆矩阵的原始值更有趣。

准确率和 F1 分数,取决于概率阈值(即预测正例的百分比)。[图片来源:作者]
由于准确率和 F1 分数都是越大越好的指标,我们会选择使得相应指标达到最高值的阈值。因此,在上述图表中,我画了一条虚线,标示出相应指标的最大值。
如你所见,不同的指标会导致我们选择不同的阈值。特别是:
-
准确率会使我们选择一个较高的概率阈值,以使 14%的观察结果被分类为正例。
-
F1 分数会使我们选择一个较小的概率阈值,使更多的观察结果(19%)被分类为正例。
那么,我们应该选择哪个指标呢?
错误预测的成本
实践者的普遍智慧是,在不平衡问题中,F1 分数必须优于准确率。
然而,如果你去寻找为什么 F1 分数被认为优于准确率的原因,你会发现解释模糊。许多解释围绕“不同成本”展开,这些成本与假阴性和假阳性有关。但它们从未明确量化这些成本。
此外,仅仅关注错误会使我们对问题的理解变得有限。事实上,如果错误预测确实会带来成本,那么正确预测必然会带来收益。
因此,我们应该考虑不仅是假阳性和假阴性的成本,还要考虑真正阳性和真正阴性的价值。从这个想法出发,我们可以得出一个新的矩阵——我称之为“价值矩阵”——它为混淆矩阵的每个元素分配一个(可能是经济的)价值。
这个矩阵有什么意义?
好吧,由于它为混淆矩阵中的每个量分配了一个值,因此将这两个矩阵(逐元素)相乘以获得任何组(TN、FP、FN、TP)的预期值是很自然的。
这是一个预期值,因为混淆矩阵包含属于任何组的观察百分比。作为百分比,这可以被解释为概率。因此,将这个概率乘以(可能是经济的)值给我们一个预期值。
将真阴性、假阳性、假阴性和真正性预期值相加,给出整个模型的预期值。

二分类器的预期值。混淆矩阵包含每个元素的频率。值矩阵包含每个元素的值(利润或损失)。两个矩阵的元素乘积得出每个组件的预期值。最后,我们将它们相加,得到模型的整体预期值。[图像来源:作者]
对混淆矩阵和值矩阵的分解对非技术利益相关者也非常直观。毕竟,每个人都能理解百分比和美元!
到此,我们准备回答最初的问题:准确率和 F1-score 的值矩阵是什么?
查找准确率的值矩阵
我们已经看到,准确率只是将混淆矩阵主对角线上的值相加。因此,直接证明准确率的值矩阵是单位矩阵是很简单的。

准确率作为分类器的预期值。[图像来源:作者]
准确率的缺陷应该是显而易见的:它等同于假设在正确预测(真正性或假阴性)情况下我们获得 1,而在错误预测情况下我们不损失任何东西。这种假设对于大多数用例至少是不太可能的。
查找 F1-score 的值矩阵
那么,在 F1-score 中,与正确/错误答案相关的值是什么?
在实践中,我们希望找出值矩阵的哪些元素会给我们得到的 F1-score。这对应于填补以下图中的问号:

F1-score 作为分类器的预期值。[图像来源:作者]
不幸的是,对于我们来说,答案不像准确率那样简单。事实上,在这种情况下,没有闭式解。然而,我们可以利用从不同阈值中提取的信息:

不同混淆矩阵的 F1-score(基于不同的概率阈值)。[图像来源:作者]
我们可以使用真负样本、假阳性、假负样本和真阳性作为自变量,将结果 F1 分数作为因变量。只需进行线性回归即可获得结果系数的估计值(即填充值矩阵的数字)。
在这种情况下,我们得到的值如下:

F1 分数的值矩阵(通过线性回归估计)。[作者图片]
从这里可以看出,F1 分数更注重正样本(无论它们被分类为 0 还是 1)。
注意这是一个近似解!与准确率不同,这里不存在值矩阵的精确解。如果你感兴趣,这是我们的结果(虚线)与 F1 分数实际曲线的比较。

实际 F1 分数与我们估计的曲线。[作者图片]
超越准确率和 F1 分数
我们已经看过准确率和 F1 分数的值矩阵。前者是任意的,而后者甚至不能直接计算(无论如何都会是近似解)。
那么,为什么不直接设置我们的自定义值矩阵呢?
实际上,根据你的使用案例,应该相对容易计算错误(正确)预测对你来说的成本(价值)。
举个例子。你想预测哪些客户更可能流失。你知道一个客户平均每月价值$10。为了防止客户离开公司,你想给他们提供一个折扣,每个客户减少$2 的利润(这里假设折扣会被所有人接受)。
所以你的值矩阵将是如下:

流失示例的值矩阵。[作者图片]
为什么会这样?
-
真负样本:$0。你的预测任务不会影响它们,所以值为 0。
-
假阳性:-$2。你给了他们折扣,但他们本来也不会流失,因此你无故减少了$2 的利润。
-
假负样本:$0。你的预测任务不会影响它们,所以值为 0。
-
真阳性:+$8。你正确预测了他们会流失,因此你节省了$8($10 减去由于你的行为产生的$2)。
问题在于,值矩阵的特性完全取决于你的具体使用案例。
例如,让我们设置一些示例值矩阵,并观察相应的期望值如何变化。

一些示例说明了分类器的期望值如何根据值矩阵变化。[作者图片]
我们可以对所有可能的阈值重复这个过程:

分类器的期望值,取决于正确/错误预测相关的值。[作者图片]
与准确率和 F1 分数不同,这些自定义 KPI 可能会取负值。这是很有意义的!事实上,在现实世界中,有些情况可能导致你的分类器让你损失金钱。你肯定希望提前知道这种可能性,而这个自定义指标可以让你做到这一点。
观察三条曲线的行为很有趣。其他条件相同时,假阴性的高成本和假阳性的低成本(即从黑线移动到绿线)会激励我们将更多观察值预测为正例(即从 16%增加到 39%)。这很直观:假阴性的高成本会激励我们将更少的观察值分类为负例,反之亦然。
一般来说,使用自定义价值矩阵可以让你在精准率/召回率的权衡中找到最佳位置。
结论
在本文中,我们比较了准确率和 F1 分数,基于它们对错误的不同成本分配。
准确率有点过于简单,因为它给正确预测赋值 1,而对错误赋值为零。另一方面,F1 分数更像是一个黑箱:你总是需要反向工程来获得其价值矩阵(无论如何,这将是一个近似解)。
我的建议是使用自定义价值矩阵,这取决于你的具体应用。一旦你设置了价值矩阵,你可以将其与混淆矩阵相乘,得到分类器的期望值。这是唯一能让你了解在现实世界中使用你的分类器的实际经济影响的方法。
感谢阅读!
如果你觉得我的工作有用,你可以订阅 每次我发布新文章时获得电子邮件通知 (通常每月一次)。
如果你想支持我的工作,你可以 请我喝咖啡。
如果你愿意, 加我 LinkedIn!
特征工程已经过时了吗?
原文:
towardsdatascience.com/is-feature-engineering-dead-203e6a9e5751
评估特征工程在现代数据科学中的角色
·发表于Towards Data Science ·阅读时间 6 分钟·2023 年 1 月 10 日
--

图片由Markus Spiske提供,来源于Unsplash
深度学习是目前数据科学中最热门的话题之一——你好,ChatGPT。处于这一领域前沿的数据科学家明白,深度学习与许多其他机器学习和人工智能领域有所不同。特征工程,即为模型创建和转换输入的过程,是这些关键区别之一。该领域的领导者如谷歌的软件工程师 Francois Chollet 表示,“深度学习消除了对特征工程的需求”(1)。
那么这是否意味着特征工程已经过时了?我不这么认为,原因如下。
特征工程 — 机器学习与深度学习的区别
在我们深入探讨之前,首先需要了解在机器学习和深度学习中如何生成特征。在传统的机器学习中,你通常不会直接使用原始数据作为模型的输入。相反,你会对数据进行调整和转换,以提高模型的性能。
例如,如果你需要创建一个分类文档的模型,你可能会想用文档的文本作为特征,但你可能不会将原始文本直接输入模型。相反,你可能会将文本表示为词频向量。

这是一个将文本表示为词频向量的例子。图片由作者提供。
这个特征不是由模型本身创建的。数据科学家花时间理解问题,并创建数据的数值表示,使模型更容易学习当前任务。
从表面上看,这个字数统计可能看起来非常简单,你可能会怀疑是否需要人来创建这个特征。但当你通过各种步骤如去除标点符号、规范化文本和去除像“the”和“of”这样的停用词来清理数据集时,特征的字数统计与原始数据集上的统计结果大相径庭。所有这些特征工程步骤使得机器学习模型能够更好地从复杂数据(如文本)中的潜在噪声中提取信号。
相反,深度学习中没有特征工程。我第一次接触到这一点是通过卷积神经网络(CNNs)进行计算机视觉。在许多方面,为模型创建特征以区分图像中的猫和狗是一项艰巨的任务。但随着 CNN 的出现,计算机视觉领域的特征工程基本上变得过时。CNN 开创了一个新纪元,使机器学习模型能够确定适合任务的特征。
特征工程为何将长期存在
尽管像 ChatGPT 这样的深度学习技术在主流新闻中引起了很多关注,我们能否依赖深度学习来解决需要机器学习的日常业务问题?正如生活中的许多事情一样,我认为答案是“视情况而定”。在计算机视觉的情况下,我认为特征工程已经过时,但这只是一个小众领域。让我们探讨一些我认为特征工程至关重要的领域。
数据分析
在数据分析领域,特征工程仍然是工作的核心部分——只是没有叫这个名字。每天,我发现团队成员使用 Excel、Power BI 或 Tableau 在创建可视化之前对原始数据集进行计算。这些计算中的许多都是数据科学家所称的特征工程!

图片由 Markus Winkler 拍摄,来源于 Unsplash
尽管数据分析师可能会计算两个数字系列的比率并将其绘制为线图供业务利益相关者审阅,但数据科学家可能会将该比率用作机器学习模型的特征。出于某种原因,数据分析师不会将这视为特征工程。
特征不仅仅用于帮助机器学习模型进行学习。它们也用于帮助人们学习。就像数据科学家一样,数据分析师也处理复杂多变的数据。这种现实意味着特征工程对于数据分析师从数据中提取特征、使可视化更具洞察力至关重要。
可解释性
虽然深度学习模型可能能够自动生成特征,但这通常会牺牲可解释性。许多现实世界的业务问题需要可解释性。实际上,某些使用案例甚至法律上要求使用可解释的模型。
以信用行业为例。如果你有一个模型用来批准或拒绝贷款,你必须能够用通俗易懂的语言解释模型为何拒绝了某人的贷款。不幸的是,深度学习模型不可解释,因此信用行业的数据科学家通常需要使用需要特征工程的简单传统机器学习模型。

图片由Scott Graham拍摄,来源于Unsplash
特征工程使你能够创建和选择对人类更有意义且更具可解释性的特征。通过仔细选择和创建相关特征,你可以更容易理解模型如何得出其预测结果以及在不同场景下可能表现如何。
此外,可解释的模型通常更可信,并且更容易部署到生产环境,因为它们更透明且更易于维护。当模型不可解释时,可能很难理解它为何做出某些预测。这使得调试和改进模型变得具有挑战性,也难以识别和修复可能出现的问题。
数据质量
无论是深度学习还是机器学习,模型的好坏取决于数据的质量——垃圾进,垃圾出。虽然许多数据科学家可能将特征工程视为构建模型过程中的一步,但它也可以帮助数据科学家提高数据集的质量。
我曾在多个项目中为数据质量监控构建功能。其中我最喜欢的一个是与风速计数据相关的项目。风速计可能因各种原因出现故障。我们用来识别坏数据的技术之一是计算相同高度风速计之间的比率,并将其与风向进行散点图分析。

图片由Benjamin Sadjak拍摄,来源于Unsplash
这一组功能使我们能够识别出风速计的轴承何时出现故障。我们能够删除坏数据,并适当地提高模型的性能。
结论
尽管深度学习领域取得了令人印象深刻的进展,但数据科学家和数据分析师构建功能的需求并未过时。在许多方面,这种需求现在比以往任何时候都更加迫切,尤其是在日益复杂的世界中。
特征工程使数据科学家和数据分析师能够将原始数据转换为更好地表示数据中的潜在结构和关系的形式。此外,它还使他们能够通过识别和解决可能影响模型性能或导致不正确分析的错误、不一致或缺失值来确保数据质量。最终,特征工程是一项对数据科学家和数据分析师都非常重要的技能,并且将继续存在。
参考文献
- F. Chollet,《用 Python 进行深度学习》(2017),
www.amazon.com/Deep-Learning-Python-Francois-Chollet/dp/1617294438
生成性人工智能是否正在接管世界?
企业纷纷加入“生成性人工智能”功能或产品的热潮。是什么驱动了这种现象?这为什么会成为问题?
·
关注 发表在 Towards Data Science · 10 分钟阅读 · 2023 年 10 月 3 日
--
图片来源:Mārtiņš Zemlickis 由 Unsplash 提供
《人工智能炒作周期:奔向何处?》
我最近在追看《Money Stuff》,即 Matt Levine 在 Bloomberg 上发表的不可或缺的新闻通讯/博客,里面有一篇有趣的文章讲述了人工智能股票挑选算法实际上并不偏爱人工智能股票(而且它们在所做的挑选上表现也不是很好)。 去阅读《Money Stuff》了解更多信息。
但是在那次分析中提到的一个点是,经济领域各个企业都被人工智能的 FOMO(错失恐惧症)所困扰。这导致了一系列半开玩笑的“人工智能”应用。
“一些公司声称他们在做人工智能,但实际上只是试图搞清楚自动化的基础。那些伪装者迟早会被揭穿的,”他说。…
时尚和服装公司 Ralph Lauren 本月早些时候将人工智能描述为“我们收入增长之旅中的一个重要部分”。餐厅连锁店,如 KFC 的母公司 Yum Brands 和 Chipotle,宣传了人工智能驱动的技术,以提高配料订单的效率或帮助制作玉米片。
几家与旅游相关的企业,如 Marriott 和 Norwegian Cruise Line 表示,他们正在开发人工智能驱动的系统,以使预订等流程更高效和个性化。
上述例子中没有在他们最近的季度报告中提及人工智能,尽管 Ralph Lauren 确实在其 5 月的年报中大致提到了一些相关举措。
(摘自《Money Stuff》,但他引用了《金融时报》)
对我来说,这点正中要害,尽管我不太确定他们是否会被揭穿。我也承认,许多公司实际上确实在使用生成式人工智能(通常是来自大开发公司的现成工具),但这些工具很少是任何人真正需要或想要的——他们只是想赶上这个新热潮。
不过,我认为讨论一下这一切是如何发生的可能会有用。当有人决定他们的公司需要“人工智能创新”,无论这是否真正涉及生成式人工智能,实际上发生了什么?
对人工智能(和自动化)的理解
在继续之前,我们先来回顾一下什么是真正的人工智能。如常读者所知,我非常讨厌人们随意使用“人工智能”这个词,因为他们大多数时候根本不知道自己在说什么。我更喜欢更具体的描述,或至少解释一下我所指的含义。
对我来说,AI 是指我们使用机器学习策略,通常但不总是深度学习,来构建可以完成复杂任务的模型或模型组合,这些任务通常需要人类能力。机器学习模型何时变得足够复杂,才应该称其为 AI?这是一个非常困难的问题,而且对此有很多分歧。但这是我的框架:机器学习是我们用来创建 AI 的技术,机器学习是一个大伞,包括深度学习和许多其他内容。数据科学领域则是一个更大的伞,可能包括部分或全部机器学习,同时也包括许多其他内容。
AI 是指我们使用机器学习策略,通常是深度学习,来构建可以完成复杂任务的模型或模型组合,这些任务通常需要人类能力。
还有另一种子类别,即生成式 AI,我认为当大多数普通人谈论 AI 时,这实际上就是他们的意思。这包括你的 LLM、图像生成等(参见我之前的帖子以获取更多讨论)。如果说搜索引擎在技术上算是 AI,那可以争论,但它绝对不是生成式 AI,如果你今天问路上的人,简单的搜索引擎是否是 AI,他们可能不会这样认为。
让我们讨论一个例子,也许能帮助澄清自动化以及为什么它们不一定是 AI。一个问答聊天机器人是一个很好的例子。
一方面,我们有一个相当基础的自动化,已经存在很久了。
-
客户在你的网站的弹出框中输入问题或搜索词。
-
一个应用程序会查看这个问题或一组词,并去除停用词(例如 a、and、the 等——一种简单的搜索和替换功能)。
-
应用程序然后将剩余的词放入搜索框中,返回数据库/FAQ/wiki 的搜索结果到聊天弹出框中。
这是对旧方式的一个非常粗略的近似。人们不喜欢它,如果你请求了错误的东西,你就会陷入困境。它基本上是一个 LMGTFY*。这个工具甚至不模仿人类可能使用的解决问题或回应策略。
另一方面,我们现在可能有一个 ChatGPT 等效物:
-
客户在你的网站的弹出框中输入问题或搜索词。
-
背后的 LLM 将客户的输入作为提示,解析这些内容,并根据词汇、其句法嵌入和模型的训练,返回极其“类人”的响应。
这将带来一些显著的好处。首先,LLM 不仅知道你发送给它的词语,还知道其他具有类似含义和关联的词语,根据它学习到的词嵌入,因此它能够在回应时超越使用的确切词语。如果你询问“买房”,它可以将其关联到“房地产”或“抵押贷款”或“房价”,大致因为它在训练中见过这些词在类似的语境和相邻的位置。
此外,客户在网站上的响应可能会更加愉悦和易于阅读,从而提升他们与公司互动的美学体验。这个模型的结果更加细致且复杂,远远超过你传统的聊天机器人。
然而,我们需要记住,LLM 并不关心信息的准确性或时效性。记住我之前帖子中的评论,LLM 是什么以及它是如何训练的——它并不是在学习事实准确性,而只是生产与人类写作非常相似的文本,和人类喜欢收到的文本。事实可能是准确的,但也有可能并非如此。另一方面,在第一个例子中,你完全控制数据库中可能返回的所有内容。
对于你网站的普通用户来说,这在前端可能感觉不会有显著不同——回应可能更愉悦,可能让他们感到“被理解”,但他们不知道答案在 LLM 版本中存在更高的准确性风险。从技术上讲,如果我们仔细分析,这两者都在自动化回答客户问题的过程,但只有一个是生成式 AI 工具。
旁注:我现在不会讨论 AGI(人工通用智能)和专门化 AI 的区别,只是说 AGI 目前并不存在,任何告诉你它存在的人都是错误的。我可能会在未来的帖子中更详细地讨论这个问题。
谁在背后?
那么,让我们继续之前的对话。是什么导致公司在新闻稿中推出一些基本的自动化或 ChatGPT 的封装,并称之为他们的新 AI 产品?是谁在推动这一切,他们实际上在想些什么?我的理论是,这里有两个主要路径。
-
我想要 PR: 一位高管看到炒作周期的到来,他们希望让他们的业务获得一些媒体关注,于是让他们的团队构建一些他们可以出售的 AI 产品。(或者,将他们已有的东西重新标记为 AI。)他们可能知道或关心这些东西是否实际上是生成式 AI。
-
我想要魔法: 一位高管在新闻或媒体中听到一些关于 AI 的信息,他们希望他们的业务能够获得他们认为竞争对手从 AI 中获得的任何优势。他们来到办公室,指示他们的团队构建能够提供AI好处的东西。如果这位高管真的知道生成型 AI 和更简单的自动化之间的区别,我会感到惊讶。
当然,这并不必然排除一个好的生成型 AI 工具最终出现的想法——实际上,已经存在很多这样的工具了!但当我们以“我们需要将这项技术用于某些事情”而不是“我们需要解决这个实际问题”为前提时,我们正在以完全错误的方式接近开发过程。
但是,拜托,这有什么害处呢?这真的重要吗,还是这只是数据科学家之间对最新疯狂“AI”功能的一些有趣笑话材料?我认为这确实重要(尽管这也常常是有趣笑话的材料)。
为什么我们应该关心这个问题?
作为数据科学家,我认为我们应该对这种现象感到有点不安,原因有几个。
首先,这种文化贬低了我们实际的贡献。 数据科学曾经是最吸引人的职业,很多杂志封面都这么告诉我们,但幸运的是,我们现在正在进入一个更加平静、稳定、不那么炫目的地方。数据科学团队和部门通过确定如何高效、有效地运行业务,为其企业提供了稳固、可靠的价值。我们找出市场对象及其时机;我们告诉公司如何组织其供应链和仓库;我们通过改变系统和流程来发现生产力机会。我们不再只是“我们一直这样做”,而是现在有权通过数据找到实际的最佳方法。有时我们会构建全新的产品或功能,使我们公司销售的东西变得更好。这是非常有价值的工作!如果你不相信,去看看我的文章 数据科学角色的原型。
我们所做的这些事情,即使不是生成型 AI,也并不比它更不重要或更无价值。我们做了很多机器学习的工作,可能并没有触及到 AI 本身神秘的复杂性边界,但这些工作仍在帮助人们并产生影响。
第二,我们通过称一切为人工智能只是助长了这种愚蠢的炒作周期。 如果你把线性回归称作人工智能,你也是在支持这种表述意义不断降低的竞争。如果我们用这个词来指代所有事物,它将会死于千刀万剐。也许这没关系,我知道我已经准备好停止听到“人工智能”这个词了。但原则上,我们这些在数据科学领域的人知道得更清楚。我认为我们至少有责任正确使用行业术语,并抵制将其意义混淆的压力。
第三,我认为最重要的一点是,我们花时间迎合外界对人工智能宣传的需求,实际上是在浪费本可以用来创造真正价值的时间。 我坚信,数据科学家应该构建能改善人们生活并帮助人们完成必要工作的工具。如果我们在构建一个没有人需要且没有帮助的工具,无论它是否使用人工智能,那都是浪费时间。不要这样做。你的客户几乎肯定实际上需要某些东西(看看你的工单积压!),你应该做那些事情。不要因为你“需要一个人工智能的东西”而做项目,而是因为这些项目满足需求并具有实际目的。
当公司高层早晨走进来决定“我们需要人工智能功能”时,无论是为了宣传还是为了魔力,这并不是基于战略性地理解你们的业务实际需要什么,或你们实际上要解决的客户问题。我知道作为数据科学家,我们并不总能反对高层的命令,即使这些命令有些荒谬,但我真的希望看到更多公司在此时稍作停顿,考虑生成性人工智能工具是否真的能解决他们业务中的实际问题。如果没有,可能就静静等着,等到问题真正出现。生成性人工智能不会消失,它会在你真正需要的时候出现。与此同时,继续使用我们已有的所有经过验证的数据科学和机器学习技术——但不要假装这些现在是“人工智能”,以此获得点击或宣传。
查看我的更多作品请访问 www.stephaniekirmer.com.
野外实例
好了,我已经对整个闹剧发牢骚够多了,现在我们来点乐子。我一直在浏览公司发布的新闻稿,这些公司向世界介绍它们的新奇人工智能功能,并反思这些功能是多么荒谬和浪费,我挑选了一些有趣的来分享。
-
可口可乐利用生成式 AI 设计广告和发明新口味:这则新闻稿是一篇上乘的文字沙拉,但他们似乎在使用 Dall-E 生成广告视觉效果。坦率地说,这些视觉效果在我看来仍像是 2002 年的产物,但也许我老了。此外,他们明确承认这是一次炒作周期的冒险。“在去年倾斜于增强现实之后,我们正在拥抱人工智能的力量,并继续在这一令人兴奋的领域构建公司能力。” 元宇宙没能奏效,所以这就是下一个东西了,我猜?
-
这似乎是真正的生成式 AI,但这是我听过的最糟糕的想法之一。Afterparty 想要 让名人通过深度伪造节省与粉丝交流的时间 的同时还能赚钱。有人想要这个吗?我了解到名人通常对 AI 复制他们的肖像持有相当的谨慎,考虑到潜在的价值。我也不明白为什么粉丝会想要这个。(谁拥有深度伪造头像的知识产权?)不过,嘿,还有 NFTs!
请在评论中分享你最喜欢的“为了 AI 而 AI”的荒诞例子,我会在未来发布一些最佳的例子。
让我谷歌一下,给那些还记不起的年轻人。
生成式 AI 是否值得其环境足迹?
生成式 AI 可能会有显著的环境足迹,而这个故事讨论了我们可能获得的回报。
·
关注 发表在 Towards Data Science ·8 min read·2023 年 10 月 31 日
--
图片由 Eric Krull 提供,来源于 Unsplash
生成式 AI 目前受到了广泛关注。ChatGPT 报道称拥有数亿用户,类似的功能也被声称整合到了从 Microsoft Word 和 Teams 到搜索引擎的多种数字产品中。
如果数十亿人开始广泛使用生成式 AI,其环境足迹可能会变得显著[1]。
普遍生成型人工智能的环境影响
如果数十亿人开始每天使用生成型人工智能技术,我们的环境会发生什么?
普遍生成型人工智能的环境影响
但我们从这项技术中获得的价值是否会超过潜在的环境成本?
这是我将在本文中尝试阐明的问题, outlining some perspectives on what we may stand to gain.
首先,我将提供一些关于生成型人工智能可能带来的生产力提升的观点。
接下来,我将讨论生成型人工智能是否会成为一种净正面或净负面的技术。
接下来,我将讨论生成型人工智能可能在多大程度上减少不平等。
最后,我将提供我的观点,探讨我们是否应该期待生成型人工智能加快绿色转型。
范围
请注意,以下内容仅涉及生成型人工智能。生成型人工智能不同于其他类型的人工智能。
同时请注意,我仅关注生成文本(包括代码)的生成型人工智能,排除图像和声音生成技术。
关于生成型人工智能的简单工作定义,请参见[2]。
生成型人工智能是否会提高我们的生产力?
让我们考虑一下生成型人工智能可能带来的生产力提升程度。一项关于 ChatGPT 对各种写作和分析任务的生产力影响的研究发现,使用 ChatGPT 的参与者在这些任务上花费的时间比未使用 ChatGPT 的对照组少了 40%。此外,根据同行评审,任务解决方案的质量提高了 18%[3]。
一项针对软件开发人员的调查显示,88%的受访者报告称,使用生成型人工智能工具GitHub Co-Pilot可以提高他们的生产力[4]。这一结果在一篇实证研究论文中得到了验证,该论文表明,在使用 Co-Pilot 实施一个 JavaScript 服务器时,生产力提高了 126%[5]。
正如尼尔森[6]所指出的,与 2007 年至 2019 年间美国 1.4%和欧盟 0.8%的年度劳动生产力平均增长率相比,这些数字令人震惊。
ChatGPT 每次查询的能耗
ChatGPT 回答一个问题需要消耗多少电力?
ChatGPT 每次查询的能耗
但这些结果是否适用于研究研究的实验环境之外?我们是否可以期望类似的生产力提升?例如,实现一个 Javascript 服务器(Co-pilot 帮助完成的任务比原计划快了 124%)是否是软件开发中的一个代表性任务?我不知道,因为我从未做过这个任务,但我相当肯定,不同编程任务的生产力提升不会相同。我期望像前述这样的生产力提升可以在解决明确的编程任务时获得,这些任务包括编写大量的样板代码,但目前我预期生成性 AI 对于需要大量时间考虑问题后才知道问什么问题的任务的帮助会很小——如果有的话。因此,我预计编程任务的平均生产力提升将低于 126%。
随着生成性 AI 工具和我们使用它们的能力变得越来越成熟,我们可能会看到更高、更广泛的生产力提升。
这就留下了一个问题:社会愿意为这样的生产力提升支付什么环境成本?确定一个可接受的成本的一个方法是查看历史生产力提升的碳成本。我将这留给读者作为练习。
对 ChatGPT 成本的估算支持了 ChatGPT 每月使用数百万千瓦时的估计。
kaspergroesludvigsen.medium.com
生成性 AI 会成为一种净正面技术吗?
现在让我们讨论生成性 AI 是否会成为一种净积极技术的不同观点——即一种其足迹被其导致的减排所超越的技术。
反对广泛采用生成性 AI 的一个论点是:如果生成性 AI 不减少温室气体排放,它将是一种净负面技术——一种使用的排放量多于减少的技术。由于温室气体排放导致的气候变化,我们应该减少我们的全球碳足迹,而不是增加它。
但生成性 AI 会是净负面还是净正面呢?很容易想象它可以在某些地方节省消耗。例如,它可能减少我们搜索 Google 和加载网页的需求。但问题是进行一次 Google 搜索和加载一个网页的碳足迹是多少。这是一个复杂的问题。
关于进行一次 Google 搜索的电力消耗,网络上确实有一个数字,但据我所知,这个数字来自 2009 年。自那时以来,情况可能发生了很大变化,我想象今天的 Google 搜索是由更复杂且能耗更高的机器学习方法驱动的。然而,我确实预期 Google 搜索的能耗会低于 ChatGPT 的查询,尽管我认为差异可能小于某些地方提到的 10 倍[7]。
鉴于此,我怀疑并非所有生成型人工智能的查询都会节省足够的 Google 搜索量来弥补其环境成本。
我已经写了很多关于人工智能环境影响的内容,而我经常收到的评论之一是生成型人工智能将减少我们的整体资源消耗和碳足迹。除了上述情况之外,我仍然很难看到这种情况。但是请挑战这一点。
最近发表了一篇题为“AI 的碳排放低于人类的写作和插图”[8]的论文。论文主张生成型人工智能比人类更具能源效率。我将在即将发布的博客文章中分析这篇论文中的主张。
[## 通过我的推荐链接加入 Medium - Kasper Groes Albin Ludvigsen
作为 Medium 的会员,你的部分会员费用将用于你阅读的作者,你可以完全访问每个故事…
kaspergroesludvigsen.medium.com](https://kaspergroesludvigsen.medium.com/membership?source=post_page-----8b6130ade14f--------------------------------)
生成型人工智能会减少不平等吗?
在我与人们讨论生成型人工智能环境影响的过程中,我听到一些人认为环境影响是值得的,因为他们期望这项技术会有助于减少不平等。是否可以期待这种情况并不明确。原因如下。
之前提到的关于 ChatGPT 对生产力影响的研究[3]发现,技能较低的人使用 ChatGPT 的生产力提升大于技能较高的人。因此,我们可能预期生成型人工智能的使用可以帮助提升受到结构性不平等影响的人群。
但是,不同工人群体在生成型人工智能提升生产力的程度上存在差异。以记者和木匠为例。对于写作的记者来说,生成型人工智能可以直接支持他们的主要产出(文章)的生产。而生成型人工智能不会直接帮助木匠建房子。所以似乎并不是所有工人群体都能平等地从生成型人工智能中受益。如果使用生成型人工智能带来的生产力提升导致工资上涨,我们可能会看到工人群体之间的不平等加剧。
这一观点得到了麻省理工学院的研究支持,其中一位经济学家发现,1980 年至 2016 年美国工资不平等增长的 50%至 70%是由自动化造成的[9]。
与此同时,国际货币基金组织(IMF)指出,人工智能可能会加大富国和穷国之间的差距[10]。
总而言之,上述研究表明,生成式人工智能可能有助于减少在生成式人工智能能够协助的任务中技能水平的不平等,但同时,它可能增加不同工人群体和国家之间的不平等。
两种简单的方法来估算机器学习模型的碳足迹以及 17 个减少碳足迹的建议
[towardsdatascience.com
生成式人工智能是否会加速绿色转型?
现在让我们考虑生成式人工智能在多大程度上能够加速绿色转型。显然,作为白领工人,绿色能源及相关领域的研究人员可能会因使用生成式人工智能而变得更高效。他们可能像其他人一样,写研究文章或编写代码的速度更快。但在可预见的未来,我个人不期待生成式人工智能系统如 ChatGPT 能直接促进绿色转型。我很难想象 ChatGPT—一个基于现有文本生成下一个最可能单词的通用文本生成工具—能够提出突破性的想法。不过,我希望我错了。如果你有不同的观点,请告诉我。
然而,我确实相信其他类型的人工智能可以帮助加速绿色转型或应对气候变化。例如,其他类型的人工智能可以帮助减少建筑能耗、打击森林砍伐、优化航运路线以减少能耗等。
结论
生成式人工智能的兴起引发了关于这项技术环境成本的讨论。本文的目的是阐明我们从生成式人工智能中可以获得什么,以便为关于生成式人工智能是否值得其环境足迹的辩论提供信息。
初步研究表明,使用生成式人工智能可能会提高某些任务的生产力,但尚不清楚这些发现是否会推广到其他环境。
研究还表明,生成式人工智能可以帮助减少同一职业中工人之间的技能不平等,但我认为它可能会导致不同职业工人之间的不平等加剧。此外,人工智能和自动化技术已被发现增加了国家之间的经济差距。
关于生成式人工智能是否会加速绿色转型,我个人的观点持怀疑态度。
总之,很明显生成性人工智能对生产力有积极影响,也可能对不平等有影响,但这需要付出环境代价。问题是:生成性人工智能的好处是否超过了成本?这本质上是一个价值判断——我希望你在阅读完这篇博客文章后,对这个问题有了更好的思考。
感谢阅读。
就这些!希望你喜欢这个故事。告诉我你的想法吧!
关注我,获取更多关于人工智能和可持续发展的内容,并订阅,以便我发布时通过电子邮件获取我的故事。
我有时也会写关于时间序列预测的文章。
也欢迎在LinkedIn上联系我。
参考文献
[1] https://towardsdatascience.com/environmental-impact-of-ubiquitous-generative-ai-9e061bac6800
[2] https://www.mckinsey.com/featured-insights/mckinsey-explainers/what-is-generative-ai
[3] https://news.mit.edu/2023/study-finds-chatgpt-boosts-worker-productivity-writing-0714
[5] https://arxiv.org/abs/2302.06590
[6] https://www.nngroup.com/articles/ai-tools-productivity-gains/
[8] https://arxiv.org/abs/2303.06219
[9] https://www.technologyreview.com/2022/04/19/1049378/ai-inequality-problem/
医疗分析适合你吗?
原文:
towardsdatascience.com/is-healthcare-analytics-right-for-you-320897b34409
你在这个领域面临的 3 个挑战,直到你退休的那一天
·发布于 Towards Data Science ·阅读时间 7 分钟·2023 年 1 月 2 日
--

图片由 KOBU Agency 提供,来源于 Unsplash
目前,当你在 Indeed 上搜索“医疗分析”时,会返回超过 8,000 个职位列表。它们涵盖了从健康 IT 和信息学到数据分析师等多种职位。你还可以通过搜索特定类型的职位来找到更多工作机会,例如:收入周期、风险、质量、EPIC、基于价值的护理(VBC)、人口健康、利用管理等。
为什么有这么多健康分析职位?
医疗行业是一个快速增长和变化的行业,同时也高度受监管,复杂(且有些混乱),细节丰富,工作起来可能非常令人恼火,并且是每一个活着的人都必需的服务。
该领域最成功和充满热情的数据从业者深刻理解数据和业务或医疗系统内部运作的细微差别。这通常意味着最资深的人在医疗领域有着许多年丰富的经验,仿佛都是不可能智慧的神谕者。事实上,如果你在医疗行业工作了整个职业生涯,我保证你每天都会学到新东西。这并不意味着新进入医疗行业的人不会成功——实际上,我们需要更多带着其他行业新想法进入医疗行业的人!这只是意味着医疗领域不是那些疲惫不堪的人、寻求“快速成功”的人或只是想浅尝辄止的人,所以我们看到有相当多的员工流动,他们最终跳槽到其他行业,通常是在那些能赚到更多(有时更多得多)钱或面临更少障碍以获得结果的地方。
你必须真正热爱医疗保健(并且忍受一些痛苦)才能坚持留在这个领域。但正是这些挑战使医疗保健成为一个最好的保密秘密。
医疗保健是数据爱好者的梦想!

由 Alexander Sinn 拍摄于 Unsplash
除了为你的工作提供意义之外,对于那些对数据有兴趣的人来说,它也可能带来经济上的回报——即使是刚刚起步的初学者也能获得奖励。入门级数据分析师的薪资通常在$80,000 以上,并且对于那些具备特定技能集(如数据科学、先进分析、特定健康 IT 系统(如 EPIC)或临床背景等)的人员,薪资可以迅速飙升到六位数的高端。对于那些已经在医疗保健行政或临床角色中的人来说,增加数据专业知识到你的简历中,可以轻松地将你定位于更高级别和更高薪资的职位。
医疗保健为分析和数据科学提供了巨大的机会,但这也伴随着重大挑战。这些挑战很多,但我将其简化为三个主要问题:信任、偏见,以及(惊喜!)糟糕的数据。
继续阅读,以确定医疗保健分析是否适合你。
第一大问题 — 缺乏信任:
当涉及到人们的实际生命时,任何数据模型中的风险或不确定性都可能很难被接受——尤其是当模型方法缺乏解释性,这可能让医生或其他护理人员对结果的推导过程或影响结果的因素感到安慰时。还有一个变更管理的因素,这与数据模型的可靠性或洞察的有效性无关。如果你比较保守,创建风险模型或金融模型,你可能在信任领域会有更多成功,但可能会遇到更多的第二大问题。
第二大问题—偏见泛滥:
通常当你想到偏见时,可能会想到一个人固有的假设,这些假设无意中塑造了数据模型或洞察的开发或解释。但偏见也可以指你正在处理的数据本身是固有的片面。在医疗保健中,这种情况被成倍放大,以下仅是一些因素:
-
个人偏见: 一个人的固有假设不经意间影响数据模型或洞察的发展或解释(显而易见)。但在医疗保健中,这可能意味着你甚至没想到的事情,比如“我想分析健康不平等,所以我会查看不同非洲裔美国人的结果如何与白人不同,并考虑社会经济因素、人口统计、种族/民族和疾病历史。” 看明白了吗?即使我们不认为自己有偏见,我们仍然倾向于基于我们所了解的轶事或之前分析过的内容来形成假设和分析。相反,我们需要专注于打破这种轶事偏见,利用数据识别未知的未知。这可能意味着新的和新颖的数据来源,添加你之前从未想到的输入或特征,或开发更多的无监督方法等。
-
数据偏见: 我们只知道我们四面墙内发生的事情(我们的数据仅反映我们的操作方式和已知的事物);或者你的数据可能只反映某一特定群体或子集的患者,他们的行为非常特殊和不同——例如,分析医疗保险人群与医疗补助人群、雇主组与特定雇主之间的见解很可能无法转移。
-
确认偏见: 当客户要求数据洞察时,如果你的发现与他们轶事上或情感上认为准确或真实的内容不符,就会被忽视;类似地,人们倾向于倾向于确认他们假设的关键论点,以便为他们已经在心理上做出的决定提供理由,并忽视其他内容。即使他们声称以数据驱动,这也非常难以仅通过数据来克服。
关键问题#3 — 数据不一致和碎片化
惊喜! 这不仅限于医疗保健,数据社区已经对此进行了多次讨论,因此我不会在这里重复。我不会说医疗保健的数据是所有数据中最脏的,尽管我可能会暗示…
知道你将不得不让你的手感到挫败,必须极其关注细节,不对任何结果解释做出假设,直到你理解数据和业务。这不是一个适合纯粹主义者或对模型创新更感兴趣的人,而不是理解数据、假设和解释或沟通的细微差别的地方。
医疗保健的变革不会发生在没有更好的数据系统、数据整合和数据共享的情况下。
这是显而易见的,已有很多文献记录并讨论过。但今天,它对潜在的医疗保健分析师有两层含义。
-
要获得医疗保健所需的全新和创新的见解,并真正成为创新者,你需要来自许多不同系统的新数据,而不仅仅是你的电子病历(EMR)或索赔数据。为了获得这些数据,你将不得不在整个分析过程中花费 80-90%的时间扮演数据探索者、数据工程师和数据验证者的角色。这意味着……
-
你必须对医疗保健分析充满好奇,善于合作,并在沙盒中友好相处。如果你想在医疗保健分析中取得进展,简单来说,这里不再是数据封建主义或智力优越的地方。 在未来的文章中,我将讨论为什么这种心态对那些不愿分享的医疗保健组织会自我限制——你需要的或想要的大量有意义的数据必须来自他人,如果你能让人们与你分享并一起合作,使其可用,更不用说可操作了。
这一切听起来是否不可能?可能是的。
为了提供背景,目前许多组织仍在努力链接自己患者的旅程,即使是在他们的四面墙之内——例如,从医生办公室/医疗小组访问(在一个 EMR 系统中),到医院访问(在不同的 EMR 系统中),到已安排的后续专家推荐预约,再到该人是否参加了这个后续预约——更不用说患者是否领取了处方、离开医院后立即去了一家麦当劳、回到一个充满有毒自来水的家中、在一个远离主要护理提供者的高山社区、在一个没有高速互联网或宽带的地区、在一个教育水平最低的州等等。社区健康和医疗保健在不断缓慢地迈向……
但这些看似不可逾越的挑战正是它如此令人兴奋的原因!
我们在所有这些领域还有很长的路要走,但我相信,凭借合适的人才、出现的新数据方法和重新焕发的合作热情,我们可以达到目标。我们看到医疗保健比以往任何时候都更加拥抱数据,数据科学的新方法带来了前所未见的新见解,而由于主要运营商和政府项目推出的新举措和计划,合作达到了前所未有的高水平。
医疗保健分析可能不适合那些容易疲倦的人,但它绝对适合那些深度好奇和顽强坚持的人。
那个人会是你吗?
在 Medium 上关注我以获取更多关于健康数据和医疗保健分析的内容。
在 LinkedIn 上联系以分享经验教训和数据驱动的方法,以便更好地决策。
Stefany Goradia 是 RS21 健康实验室的副总裁,她的职业生涯专注于医疗分析的前线工作,并为内部和外部客户提供价值。她撰写了如何解读医疗数据、将其传达给利益相关者,以及如何利用这些数据来支持明智的决策和执行。
你需要的是压缩吗?
更高效的基于压缩的主题分类实现
·
关注 发布于 Towards Data Science ·6 min read·2023 年 7 月 22 日
--
图片由 Tomas Sobek 提供,来源于 Unsplash
最近发布的论文标题为“低资源”文本分类:一种无参数分类方法与压缩器 [1],最近引起了相当多的公众关注。他们的关键发现是,在某些情况下,他们可以通过简单的想法来超越大型语言模型如 BERT,即如果两个文本文件可以被压缩到更小的文件大小,它们就更相似(尽管关于他们结果的有效性存在一些争议,见这篇博客文章和这次讨论,包括作者的回复)。
他们方法的主要思想是,Bennet 等人定义的“信息距离”在文本分类中是一个好的距离度量。由于信息距离本身不可计算,他们使用归一化压缩距离(NCD)[3]来近似,它通过像 gzip 这样的“现实生活”数据压缩器来估计信息距离。NCD 具有更好的压缩器(即压缩比更好的压缩器)可以更好地估计真实信息距离的特性。
因此,自然期望更好的压缩器会在分类中实现更好的性能。但他们无法通过实验验证这一点;论文中考虑的最佳压缩器 bz2 在准确性方面表现不如 gzip。他们解释道:“[…] bz2 使用的 Burrows-Wheeler 算法通过在压缩过程中排列字符,忽略了字符顺序的信息” [1, p.6817]。这意味着仅凭压缩无法解释他们的发现,但与字符顺序也有关系。
这让我思考:他们的结果有多少是由于压缩,多少是由于两个文档之间的字符串比较?
为了调查这个问题,我将他们的结果与两种替代方案进行比较:(1)一个仅依赖于替换重复字符串的简单压缩器,以及(2)一个在查询文档和属于某些主题的所有文档之间显式进行子字符串匹配的算法。
第一次消融:LZ4 能否完成任务? 压缩算法 gzip 基于DEFLATE,它使用LZ77和霍夫曼编码来压缩数据。让我们更详细地了解这两种算法,并思考它们在我们使用场景中的含义。
在压缩过程中,LZ77 使用一个滑动窗口来处理先前看到的数据。如果字符串重复,则存储字符串第一次出现的引用,而不是字符串本身。因此,如果我们将两个连接的文档的长度作为距离度量,文档会更接近,如果它们在滑动窗口大小(通常为 32KB)内有更多重叠的子串。
哈夫曼编码进一步压缩结果文本:它不是对每个字符使用 8 位,而是用更少的位表示频繁出现的字母,用更多的位表示不常出现的字母。如果我们将哈夫曼编码应用于连接的文档,那么如果两个文档使用频率相似的字符,压缩后的文档将会更小。
可以预期,匹配的子串在主题分类中比相似的字符频率更重要。因此,我通过观察使用LZ4算法[4]进行压缩时的性能,进行了一项消融研究(基本上是 LZ77,但有一个在 python 中可用的快速实现)。由于 LZ4 的压缩比远低于 gzip,他们的解释表明 LZ4 的性能不如 gzip。然而,如果主要的工作是子串匹配,LZ4 将表现得与 gzip 一样好。
更明确的算法 为了进一步深入,我实现了一个简单的算法,明确地进行子串匹配:它将文档分配给具有最相似子串的主题(这里的子串是字符级 n-gram)。其工作原理如下:
文本编码:
1. 提取文本中的所有字符 n-gram,范围为 5 ≤ n ≤ 50。
2. 对提取的 n-gram,使用hash(n_gram) % int(10e8)在 python 中计算 8 位哈希代码(因为我想控制要跟踪的不同事物的数量)。
3. 将其记录在集合中(因此丢失了有关某个代码出现次数的信息)。
训练:
1. 计算给定主题的每个文本的哈希代码集合。
2. 进行集合并,以获得在主题中出现的哈希代码集合。
推断:
1. 对某些查询文本,计算其哈希 n-gram 的集合。
2. 对于训练中遇到的每个主题,计算该主题集与查询集之间的交集大小。
3. 将查询文本分配给交集最大的主题。
实验和结果 我在 100-shot 设置中对 gzip、lz4 和哈希 n-gram 算法进行了比较,共进行了 5 次实验,具体如他们的论文所述。对于这三种方法,我坚持他们的实验设置,以便重现他们报告的结果(再次说明,这可能导致准确性度量的夸大)。代码可以在github上找到。
你可以从torchtext (AG_NEWS [5],DBpedia [6] 和 YahooAnswers [5])中的三个数据集上查看性能,见下表:

我们看到 lz4 和哈希 n-gram 在所有三个考虑的数据集中都优于 gzip,其中哈希 n-gram 算法在 3 个数据集中的 2 个数据集中表现最好。但它仍然无法与 BERT 竞争,根据他们在 100-shot 设置中的论文,BERT 在 AG_NEWS 上的性能为 0.82,在 DBpedia 上接近 1。
这些结果具有重要的实际意义:在我们的实验中,基于 lz4 的算法运行速度比基于 gzip 的算法快大约 10 倍。更重要的是,哈希 n-gram 算法甚至在推理时改善了计算复杂度:你只需与每个主题集进行比较,而不是与文本语料库中的每个文档进行比较。
我们从中学到了什么? 我的结果表明,gzip 的显著性能可以归因于他们的方法隐式比较了字符级 n-gram。这一发现使得可以使用像 lz4 这样的更快的压缩器而不会有任何性能损失。此外,甚至可以重写他们的算法,使其在推理时具有与数据集大小无关的恒定复杂度,使他们的方法更接近于在大数据集上的实际应用。如果你想在实践中使用它,我已经开始按照我提出的算法的 scikit-learn API 进行实现,请见此处。
剩下的一个问题是,为什么这种方法比TF-IDF方法表现更好,即使两者都在比较文档中的词汇?
也许考虑字符级 n-gram 在某些任务中比将文本拆分成单个词汇更有效。但更重要的是,这里使用的方法对所有 n-gram 赋予相等的权重,无论它们的出现次数。这意味着它给予所谓的长尾(即稀有)信息很多重要性,这显然对某些文本任务如主题检测很重要。注意,变换器网络在建模这种长尾信息上表现不佳(有关证据,请参见例如[5]),这也是这些非常简单的方法成为衡量你百万参数模型的一个非常有趣的基准的原因。
参考文献 [1] Z. Jiang, M. Yang, M. Tsirlin, R. Tang, Y. Dai, J. Lin. “低资源”文本分类:一种无参数分类方法与压缩器 (2023), ACL 2023
[2] C. H. Bennett, P. Gács, M. Li, P. MB Vitányi 和 W. H. Zurek, 信息距离 (1998), IEEE 信息理论交易
[3] M. Li, X. Chen, X. Li, B. Ma 和 P. Vitányi, 相似性度量 (2004), IEEE 信息理论交易
[4] N. Kandpal, H. Deng, A. Roberts, E. Wallace, C. Raffel, 大型语言模型在学习长尾知识方面的困难 (2022), arxiv.org/abs/2211.08411
感谢 Joel Niklaus 和 Marcel Gygli 的讨论和反馈。
Julia 比 Python 和 Numba 更快吗?
原文:
towardsdatascience.com/is-julia-faster-than-python-and-numba-897c53694621
优化
Numba 非常快速,但它够快吗?
·发布于数据科学前沿 ·16 分钟阅读·2023 年 9 月 19 日
--

Numba 是一个广泛使用的 Python 优化库,它将函数执行时间提升到接近 C 语言的水平,而 C 语言毫无疑问是快速的。
这种优化水平足以与像 Julia 这样较新的、专门构建的目标语言竞争吗?如果是的话,在 Python 中实现这种执行速度是否有任何陷阱?
引言

我之前写过一篇对比 NumPy 和 Julia 的文章。结果基本上是 Julia 确实比 NumPy 快,总体而言。然而,事情比这更复杂一点,所以我建议你查看那篇文章,了解详细信息:
## Julia 真的比 Python 和 Numpy 更快吗?
C 语言的速度与 Python 的简单性
towardsdatascience.com
对那篇文章最常见的回应之一是类似于:
好吧,你也应该使用 Numba。它实现简单,而且更快!
-相当多人
…因此这篇文章将直接尝试解决这个建议。
使用 Numba 是否能与 Julia 的速度相匹配,甚至超越?它是否像人们声称的那样易于使用和实现?而且,有什么缺点吗?
让我们来看看……
关于 Julia 的简要介绍

由于你们中的一些人可能还没有阅读关于 NumPy 的前一篇文章,我将重复那篇文章中包含的“什么是 Julia?”部分,但如果你已经阅读了前一篇文章,可以随意跳过。
什么是 Julia?
如果你对 Julia 一无所知,这里有一个快速入门指南。
Julia 是一种开源语言,具有动态类型、直观且易于使用的特点,类似于 Python,但执行速度如同 C 语言一般。
它已经存在大约 11 年(诞生于 2012 年),所以它是一种相对较新的语言。然而,它已经发展到一个成熟的阶段,你不会把它称作是一时的潮流。
语言的原始创造者活跃于相关领域的工作中:
对于我们所做的工作——科学计算、机器学习、数据挖掘、大规模线性代数、分布式和并行计算——…
- julialang.org — Jeff Bezanson, Stefan Karpinski, Viral B. Shah, Alan Edelman
总的来说,它是一种专门为数据科学领域设计的现代语言。创造者的目标本身就能告诉你很多东西:
我们希望拥有 C 的速度和 Ruby 的动态特性。我们希望有一种语言,它是同构的,具有类似 Lisp 的真正宏,但也具备像 Matlab 那样明显的、熟悉的数学符号。我们希望它像 Python 一样适用于通用编程,像 R 一样适合统计,像 Perl 一样自然地处理字符串,像 Matlab 一样强大地进行线性代数,像 shell 一样擅长将程序粘合在一起。我们希望它易于学习,同时能够让最严肃的黑客感到满意。我们希望它既具交互性又具编译性。
(我们提到过它应该和 C 一样快吗?)
- julialang.org — Jeff Bezanson, Stefan Karpinski, Viral B. Shah, Alan Edelman
听起来很令人兴奋,对吧?
顺便说一下,如果你想了解 Python 和 Julia 在语法和一般使用方面的对比,那么你可以查看我的另一篇文章,该文章深入探讨了使用 Julia(Flux)和 Python(TensorFlow)解决深度学习图像分类问题的情况:
## Julia 的 Flux 与 Python 的 TensorFlow:它们如何比较?
一个端到端深度学习项目的比较
[towardsdatascience.com
Numba 是什么,它为什么如此快速(以及受欢迎)?

图片由 Towfiqu barbhuiya 提供,在 Unsplash
Numba 的理念极其简单。
将 Python 代码预编译为机器代码,并执行编译后的代码,而不是 Python 代码。或者更详细地说:
Numba 使用行业标准的 LLVM 编译器库,将 Python 函数翻译为优化后的机器代码。用 Numba 编译的 Python 数值算法可以接近 C 或 FORTRAN 的速度。
要接近 C(或 FORTRAN)的速度,意味着 Numba 确实会非常快。
实现(通常)很简单
Numba 实现方式的一个主要优点是,在大多数情况下,它非常易于使用。以下是一个普通 Python 函数的例子,以及相应的 Numba 函数:
#normal function
def loop_function(a, b):
c = np.zeros(a.shape, dtype=np.float32)
for i in np.arange(c.shape[0]):
if a[i] < b[i]:
c[i] = 1.0
else:
c[i] = 2.0
return c, a + b
#the same function implemented with Numba implemented
@njit
def loop_function_numba(a, b):
c = np.zeros(a.shape, dtype=np.float32)
for i in np.arange(c.shape[0]):
if a[i] < b[i]:
c[i] = 1.0
else:
c[i] = 2.0
return c, a + b
实际上只需一个装饰器!似乎不在每个函数上实现 Numba 就有些不妥。(稍后会更多地讲到现实情况……)
那我应该用 Numba 替换 NumPy 吗?
Numba 不是 NumPy 的替代品或替代选择。它的设计目的是补充NumPy。
Numba 旨在与 NumPy 数组和函数一起使用。Numba 为不同的数组数据类型和布局生成专门的代码,以优化性能。特殊的装饰器可以创建 通用函数,这些函数可以像 NumPy 函数一样对 NumPy 数组进行广播。
这非常出色,因为 NumPy 已经非常强大,而 Numba 进一步提升了它的性能。此外,如果你决定使用 Numba,也不需要重新编写所有的 NumPy 代码。
Numba 的其他技巧
编译步骤只是开始。Numba 具有一整套附加功能,可以进一步提高执行速度。一些例子:
-
并行处理— 如果你的 CPU 有多个核心,你可以利用它们进行并行处理,以加快计算速度
-
快速数学 — 减少数值精度以提高执行速度
-
缓存 — 将编译后的代码保存在缓存中,以减少重复使用时的编译开销
-
CUDA— 利用你的 GPU 进行计算
当然,除了上述四个选项之外,还有更多的选择,如果需要的话,还可以进行大量的自定义。本文将主要关注上面列表中的并行处理和 Fastmath。
使用 GPU (CUDA) 需要另外一篇文章来详细说明,因此在这篇文章中不会涉及。
缺点
Numba 主要有两个负面点。
第一个也是最明显的,就是需要编译,而且编译需要时间。
如果函数只执行一次,那么编译时间可能是一个显著的缺点。然而,如果代码需要重复使用编译的函数(例如在循环中),那么这个缺点可能会变得微不足道。这完全取决于情况。
…需要编译,而且编译需要时间。
第二个,根据直接来自 Numba 网站的引用,是 Numba 只能在其设计用于的函数上实现。
Numba 是一个开源 JIT 编译器,将Python 和 NumPy 代码的一个子集翻译成快速的机器代码。
我会指出,虽然提供的函数非常丰富,因此你可能不会觉得这是个问题,但这仍然是一个负面点。此外,还有相当多的内置灵活性,允许编写自定义代码,比如 NumPy ufuncs。因此,如果你有非常独特的需求,也有一些变通办法。
速度测试的基础

正如本文开头所提到的,这篇文章实际上是两篇之前文章的汇总。
第一个提供了如何利用 NumPy 矢量化来加速你的 Python 代码的指南:
比普通函数快多达 8000 倍
towardsdatascience.com
…一个自然的进展是查看 NumPy 及其矢量化实现与 Julia 的比较:
## Julia 真的是比 Python 和 NumPy 更快吗?
C 的速度与 Python 的简单性
towardsdatascience.com
…现在,主要是由于对之前 Julia 文章的评论,我认为观察 Julia 与 Numba 增加的功能相比如何将会很有趣。
那么,让我们开始吧!
测试将如何进行?

图片由 Nguyen Dang Hoang Nhu 提供,来源于 Unsplash
将测试三种不同的函数。每个函数的复杂度逐渐增加。
函数 1 — 简单的求和
以下函数的输入(a 和 b)定义为具有一百万个元素的一维数组/向量。每个元素是从正态分布中取出的随机数,类型为 float32。
对于 Python 函数,数组将是 NumPy 数组。
# Example of an input array
series1 = np.random.randn(1000000).astype(np.float32)
# Python + NumPy + Numba
@njit
def sum_nums_numba(a, b):
return a + b
# Example of an input array
series1 = randn(MersenneTwister(12), Float32, 1000000);
# Julia
function sum_nums(a::Vector{Float32}, b::Vector{Float32})
return a + b
end
函数 2 — 循环函数
循环是无处不在的,因此值得关注。
输入数组将与函数 1 相同。
# Python + NumPy + Numba
@njit
def loop_function_numba(a, b):
c = np.zeros(a.shape, dtype=np.float32)
for i in np.arange(c.shape[0]):
if a[i] < b[i]:
c[i] = 1.0
else:
c[i] = 2.0
return c, a + b
# Julia
function loop_function(a::Vector{Float32}, b::Vector{Float32})
c::Vector{Float32} = zeros(Float32, size(a))
for i = 1:size(c)[1]
a[i] < b[i] ? c[i] = 1.0 : c[i] = 2.0
end
return c, a + b
end
函数 3 — 矩阵操作
矩阵操作是数据科学(尤其是深度学习)领域许多算法和任务的关键组成部分,因此是一个重要的考虑因素。
输入到函数中的数据将是一个从正态分布中取出的 100 x 100 随机数矩阵。
# Example of an input matrix
matrix1 = np.random.randn(100,100).astype(np.float32)
# Python + NumPy + Numba
@njit
def matrix_func(mat_a, mat_b):
a = mat_a.T
c = np.dot(mat_b,a)
d = c.reshape(50,50,4)
e = mat_b.reshape(50,4,50)
f = d.reshape(200,50)
g = e.reshape(50,200)
h = np.dot(f,g)
i = h.reshape(40000)
result = 0.0
for j in np.arange(i.shape[0]):
result = result + (i[j] - np.sum(i)) / np.sqrt(abs(np.average(i)))
return result
# Example of an input matrix
matrix1 = randn(MersenneTwister(12), Float32, 10000);
matrix1 = reshape(matrix1,(100,100));
# Julia
function matrix_func(mat_a, mat_b)
a = mat_a'
c = mat_b * a
d = reshape(c,(50,50,4))
e = reshape(mat_b,(50,4,50))
f = reshape(d,(200,50))
g = reshape(e,(50,200))
h = f * g
i = reshape(h,40000)
result = 0.0
for j = 1:size(i)[1]
result = result + (i[j] - sum(i)) / sqrt(abs(mean(i)))
end
return result
end
额外的调查
为了让信息更具参考性,还将研究以下项目:
-
所有 Numba 函数都将分别在包含和不包含编译阶段的情况下进行计时。这将帮助判断编译对整体执行时间的影响。
-
将进行单次迭代和多次迭代测试。再次是为了研究编译对执行时间的影响。
-
除了“正常”运行函数外,还将比较 Numba 和 Julia 中并行处理的有效性。
-
将研究 Numba 中 Fastmath 参数的额外好处
测量
函数的计时将使用 Python 中的 timeit 模块 和 Julia 中的 BenchmarkTools 模块 进行。
iterations = 10000
timeit.timeit(stmt=numba_func, setup=setup, number=iterations)
@benchmark sum_nums(rand_array1, rand_array2) samples=10000
一些常规信息(环境、版本等)
所有的测试都在完全相同的硬件上进行,该硬件使用的是 4 核心/8 线程 CPU(i7–4790K)(具体细节打印在 Jupyter notebooks 中)。
软件版本如下:
Julia: 1.9.2
Python: 3.11.4
NumPy: 1.23.5
Numba: 0.57.1
本文的 notebooks

图片由 Jessica Lewis 提供,来源于 Pexels
本文中生成结果所用的所有代码都完整地保存在两个 Jupyter notebooks 中,链接如下:
[## notebooks/julia-numba-comparison at main · thetestspecimen/notebooks
Jupyter 笔记本。通过在 GitHub 上创建帐户来为 thetestspecimen/notebooks 的开发做出贡献。
结果

图片由 Anna Nekrashevich 提供,来源于 Pexels
从……
一个简单的求和
首先在单次迭代中。

对两个数组进行一次迭代的简单求和 — 图表由作者提供
这个第一次单次迭代的运行仅仅是为了说明,相较于整体执行时间,使用 Numba 时函数编译可能会带来显著的开销。
值得指出的是,要避免陷入仅仅因为 Numba 而使用它的陷阱,而不考虑它是否合适。如果你只需执行一个相对简单的函数一次,那么通常直接使用 NumPy 会更好,或者如果情况允许,至少利用缓存。
现在让我们增加迭代次数,以减少初始编译的影响。

对两个数组进行 10000 次迭代的简单求和 — 图表由作者提供
好了,就是这样。Julia 垫底了!
实际上,差异很小(10000 次迭代 — Julia[7.2s] — NumPy[5.1s]),但这仍然是一个差异。
更令人惊讶的是,即使忽略掉 Numba 的编译时间,Numba 的执行速度实际上比 NumPy 稍慢。
这很好地说明了适当矢量化的 NumPy 计算非常优化,这是在考虑 Numba 是否适合你的特定应用时需要考虑的另一个因素。
注意: 有关 NumPy 矢量化的详细信息,以及它是如何工作的,请查看我之前的文章,我在其中详细讲解了:
比普通函数快最多 8000 倍
towardsdatascience.com
循环函数
进入一个更现实且稍微复杂的场景,让我们看看一个循环函数。
本质上是逐个元素地运行一个包含 100 万个元素的数组,并根据 if-else 语句替换每个元素。
# Python + NumPy + Numba
@njit
def loop_function_numba(a, b):
c = np.zeros(a.shape, dtype=np.float32)
for i in np.arange(c.shape[0]):
if a[i] < b[i]:
c[i] = 1.0
else:
c[i] = 2.0
return c, a + b

100 次迭代的循环函数(Comp — 包括函数编译时间,Para — 并行处理)— 作者图表
再次,Numba 占据了上风。
有趣的是,即使在 Numba 运行中包含编译时间,Numba 的速度仍然比 Julia 快。显然,如果迭代次数较少,这种领先优势将会减小,甚至最终会逆转。然而,执行阶段确实更快。
你还会注意到,在 Julia 和 Numba 的情况下,并行处理可以显著帮助。函数需要做一些轻微调整,但没有过于极端的改动。
对于 Julia,只需在 for 循环前添加 Threads.@threads:
# parallelised Julia function
function loop_function_para(a::Vector{Float32}, b::Vector{Float32})
c::Vector{Float32} = zeros(Float32, size(a))
Threads.@threads for i = 1:size(c)[1]
a[i] < b[i] ? c[i] = 1.0 : c[i] = 2.0
end
return c, a + b
end
对于 Numba,只需在装饰器中添加 parallel=True,并将 np.arange 替换为 prange(即 并行范围)。
@njit(parallel=True)
def loop_function_numba_par(a, b):
c = np.zeros(a.shape, dtype=np.float32)
for i in prange(c.shape[0]):
if a[i] < b[i]:
c[i] = 1.0
else:
c[i] = 2.0
return c, a + b
通过使用 Fastmath 获得了额外的微秒,这本质上是通过降低精度来提高执行速度。这在机器/深度学习领域非常有用,因为在训练模型时不一定需要高数字精度。
只需对装饰器做一个简单的补充:
@njit(parallel=True, fastmath=True)
def loop_function_numba_par_fast(a, b):
c = np.zeros(a.shape, dtype=np.float32)
for i in prange(c.shape[0]):
if a[i] < b[i]:
c[i] = 1.0
else:
c[i] = 2.0
return c, a + b
矩阵操作
矩阵操作是数据科学工作流和深度学习/神经网络中不可或缺的一部分。
鉴于此,我觉得可能有趣的是看看 Julia 和 Numba 如何处理不同的矩阵操作链,包括:
-
普遍存在的点积
-
转置
-
重新形状
-
求和
-
平方根
-
绝对值
-
平均值
# the Python code for reference
@njit
def matrix_func(mat_a, mat_b):
a = mat_a.T
c = np.dot(mat_b,a)
d = c.reshape(50,50,4)
e = mat_b.reshape(50,4,50)
f = d.reshape(200,50)
g = e.reshape(50,200)
h = np.dot(f,g)
i = h.reshape(40000)
result = 0.0
for j in np.arange(i.shape[0]):
result = result + (i[j] - np.sum(i)) / np.sqrt(abs(np.average(i)))
return result

20 次迭代的矩阵操作(Comp — 包括函数编译时间,Para — 并行处理)— 作者图表
在这种情况下,Julia 表现最佳,差距相当大(大约快 10 倍)。为什么会这样尚难以解释,需要进一步调查。然而,我怀疑这与 Numba 的一些限制有关,我将在下一节进一步讨论。
另一个需要注意的地方是,由于这个函数的执行时间较长,相比于函数 1 和 2,编译时间在 20 次迭代时已经微不足道。
最终,Numba 如人们所说的那样快速,并且在大多数情况下使用起来非常简单。它轻松地将 Python 及其生态系统推向 Julia 的领域,整体上更新鲜。
但有一些注意事项…
局限性

图片由 RDNE Stock project 提供,来自 Pexels
可以公平地说,Numba 确实能够跟上,有时甚至超越 Julia 的执行速度。
然而,Julia 和 Numba 之间有一个主要区别。Numba 是一个语言的外部库,而 Julia 中使用的方法是集成到核心语言中的原生方法。
Numba 是一个语言的外部库,而 Julia 中使用的方法是集成到核心语言中的原生方法。
这基本上意味着,使用 Julia 你非常不容易遇到不兼容问题或方法应用限制。而对于 Numba,则不能这样说。Numba 的应用限制不仅在官方文档中被明确规定:
……但由于不兼容性,你更可能会发现 bugs。
我发现的一些 bug
本文中的代码非常有限。比较简单的函数。然而,我仍然遇到了 Numba 的问题,因此不得不调整我的分析。
在最近对 2D 数组/矩阵(函数 3)进行的基准测试中,为了使函数在使用 Numba 时能够运行,必须进行各种调整。
需要注意的是,使用纯 Python/NumPy 而不使用 Numba 时,以下问题都不存在。
-
你不能使用带有第二个参数的
np.reshape(即你不能指定重新排序类型‘F’,‘C’等)。这是一个问题,因为 Julia 和 Python 使用不同的默认矩阵索引顺序,尝试尽可能保持比较/公平需要查看这个第二个参数。 -
np.matmul不受支持,因此你必须使用np.dot。这对像本文中使用的 2D 数组没有问题,但这些方法在高维数组中并不等效,因此如果你依赖于np.matmul处理高维数组,可能会遇到问题。 -
打开 bug — 在转置后使用
np.reshape需要进行复制,否则会失败 -
打开 bug —
np.dot不支持整数数组。由于第 2 点,被迫从np.matmul切换到np.dot后,在测试中我遇到了另一个问题,然后才转向浮点数。
我在上述四个点上浪费了不少时间,因为不总是清楚你是否在做错事(即尝试实现不支持的内容),或者是在处理一个 bug。如果我处理的不是用于文章的“实验”代码,而是项目代码,那可能会变得相当令人沮丧。
结论

Numba 非常优秀,最重要的是它完全符合人们对它的所有评价:
快速且易于实现
…是的,总的来说,它基本上和 Julia 一样快。只要在适当的情况下使用。
然而,我不能忽视的是,与 Julia 直接比较时,Numba 有一些严重的局限性。当然,这些局限性的重要性会根据你的需求和特定约束有所不同。
在现实世界中,这基本上意味着,如果你的当前项目或基础设施依赖于 Python,而转到新语言太困难(缺乏经验丰富的开发者、太多的遗留代码、预算不足等)。那么由于像 NumPy 和 Numba 背后开发者的辛勤工作和坚持不懈,Python 在数据科学领域的速度和功能仍然是最新的。Numba 和 NumPy 非常有效地填补了这一空白。
然而,当你在 Julia 中编写代码时,你很清楚,基础语言在没有任何外部库的情况下已经进行了高度优化。它也是从根本上以数据科学为设计目标,因为这是创造者自己所需的:
对于我们所做的工作——科学计算、机器学习、数据挖掘、大规模线性代数、分布式和并行计算——…
- julialang.org — Jeff Bezanson, Stefan Karpinski, Viral B. Shah, Alan Edelman
你不需要了解或熟悉外部库和工具来确保你的项目在 Julia 中运行得很快。它默认就是很快的。这允许你更多地考虑你想要实现的目标,而不是不断考虑如何加快速度或优化。
这就是为什么如果条件允许,转向 Julia 是非常有意义的(在我看来!)。
注意: 如果你想了解更多关于 Julia 与 Python(TensorFlow)在深度学习方面的对比,务必查看这篇文章:
## Julia 的 Flux 与 Python 的 TensorFlow:它们的比较如何?
一个端到端深度学习项目的比较
towardsdatascience.com
如果你觉得这篇文章有趣或有用,记得关注我,或 注册我的新闻通讯 获取更多类似内容。
如果你还没有,可以考虑 订阅 Medium。你的会员费不仅直接支持我,还支持你阅读的其他作者。你还将获得 Medium 上每篇文章的完全无限制访问权限。
使用我的推荐链接注册将使我获得少量回扣,而不会影响你的会员资格,因此,如果你选择这样做,我非常感谢。
[## 使用我的推荐链接加入 Medium - Mike Clayton
作为 Medium 会员,你的会员费的一部分将支付给你阅读的作者,而且你可以完全访问每一个故事…
medium.com](https://medium.com/@maclayton/membership?source=post_page-----897c53694621--------------------------------)
逻辑回归是回归模型还是分类模型?让我们结束争论
从两个不同的角度和 3 轮讨论
·发表在数据科学前沿 ·11 分钟阅读·2023 年 3 月 14 日
--
尽管我们可以找到许多讨论这个问题的帖子和文章,我决定再添加一篇。因为这是一个讨论一些基础理论和框架的机会。
有些人试图通过给出明确的答案来捍卫他们的观点:逻辑回归是回归模型,或者逻辑回归是分类模型。我不会捍卫任何观点,因为对我来说,这场争论有些荒谬。
-
对于那些认为逻辑回归是分类模型的人,请注意,在“逻辑回归”这个名字中,有“回归”二字,所以对于那些将该模型命名为回归模型的人,他们认为它是回归模型是有原因的。是的,我知道可能存在误称,但你必须承认确实有原因。
-
对于那些认为逻辑回归仅仅是回归模型的人,你们也知道它被应用于分类任务。因此,其他人想称它为分类模型。
所以答案是这两种观点都是可能的。就像生活中的许多事物一样,我们对看似相同的主题给出了不同的、有时是矛盾的陈述,这是因为我们从不同的角度看待它。所以有趣的不是答案,而是我们如何得出这个答案。
这个问题也类似于关于番茄是水果还是蔬菜的古老争论。它们都有两个有效的答案,因为它们来自不同的角度和研究领域,即植物学和营养学。承认这两个答案将有助于你更好地理解植物的结构,并改善你对膳食的营养分析。对于逻辑回归也是如此,在这篇文章中,我们将尝试理解为什么会有这两种可能的答案,它们背后的原因将帮助你更好地理解这个模型的基本结构。

图片由 Tamanna Rumee 提供,来源于 Unsplash
0. 这两种观点
简而言之,有两种观点:
-
统计学家的观点:名称“逻辑回归”本身是由统计学家命名的,因此按定义,对他们来说,逻辑回归是一种回归模型,因为模型的输出是一个概率(在 0 和 1 之间),且它是连续的。
-
机器学习从业者的观点:有两种类型的监督学习,回归或分类,因此对他们来说,逻辑回归是一种分类器,因为它用于分类任务。
事实上,在我们的学习过程中,我们阅读了各种信息来源,有些遵循统计学的观点,有些则遵循机器学习的观点。有时,这两种观点的界限是模糊的。
现在,有人会说,通常,机器学习可以被视为统计学的一个分支,至少,它们并不是完全不同的,我不应该反对它们。是的,我同意,不过,我会通过明确区分这两种观点来解释它们,你将会看到这些差异。这样我们可以更好地理解所研究的主题。我计划写一篇文章来讨论这两种观点,特别是逻辑回归在这篇文章中作为一个重要的例子。
1. 第 1 轮:模型究竟是什么
首先,我们在谈论什么?什么是逻辑回归?我们会惊讶地发现,我们实际上在讨论两种不同观点下的模型。
1.1 统计学家的逻辑回归模型
对统计学家来说,模型是
p = 1 / (1 + exp (- (wX + b) ) )
模型的输出是一个 0 到 1 之间的值,随后可以表示一个概率。如果你愿意,你可以通过将概率切分为两部分来进行分类。通常,我们使用中间值:0.5 或 50%。因此,这里的顺序是,首先我们得到概率作为输出,然后进行分类。
这就是为什么对统计学家来说,逻辑回归是一种回归模型,因为其输出是连续的。
1.2 机器学习观点下的逻辑回归模型
对机器学习从业者来说,模型是
y = wX + b
输出是 y,称为决策函数。通常,y 的值要么是-1,要么是 1。
然后我们需要拟合模型,并选择一个损失函数。现在,当使用对数损失函数时,与统计学家的逻辑回归模型是等效的。
最终,线性模型被用于分类任务,具体取决于 y 是正数还是负数。
从某种程度上讲,他们不应该使用逻辑回归这个名字,因为逻辑函数从未用于类别预测。但是,由于最小化对数损失等同于最大化似然度,并且在分类方面得到了相同的结果,所以我们说逻辑回归已经被应用了。
我们可以想象,将这种线性模型与对数损失结合的特定组合称为“对数损失分类器”。但事实证明,机器学习研究人员通常不会给出具体的名称,而是指出现有统计模型中的等效模型。
现在,如果你想计算概率,你可以应用逻辑函数!因此,顺序不同,这里我们首先进行决策函数的分类,然后如果需要,可以计算概率。
(你们中的一些人可能对我从机器学习的角度所说的感到困惑。希望在第 3 轮中会更清楚。暂时,只需考虑 SVM。就像逻辑回归一样,SVM 是一个分类器。而与逻辑回归相反,概率计算不是立即得到的。你可以应用逻辑回归,但也存在其他方法,如同调回归,用于计算概率。)
1.3 第 1 轮的结论
总之,尽管统计学家和机器学习从业者使用相同的名称“逻辑回归”,但模型实际上是不同的,模型的输出也是如此。统计学家认为逻辑回归是一个回归器,因为模型试图建立一个概率的连续结果。而机器学习从业者认为逻辑回归是一个分类器,因为输出是二元的……哦等等,但线性模型的输出是连续的。分类部分是他们在模型拟合时添加的一个步骤。分类步骤真的属于模型本身的一部分吗?让我们进入第 2 轮。
2. 第 2 轮:一个(线性)分类器到底是什么
我记得在高中时,为了写一篇哲学论文,我们总是应该首先定义我们使用的术语。那么,分类器是什么?
2.1 从机器学习的角度来看线性分类器
对于机器学习从业者来说,逻辑回归是一个分类器,因为它应用于具有二元结果的数据集。所以,分类器的定义是应用于具有二元目标变量的数据集的模型。
记住这个定义后,让我们看看,如果我们在线性模型中使用平方误差而不是对数损失会怎么样?
当然,这是可能的,因为我们总是可以计算两个数字之间的平方误差,即使目标变量只有两个值。
对于有任何疑问的人,你可以阅读 SGDClassifier 的官方文档:
‘squared_error’,‘huber’,‘epsilon_insensitive’和‘squared_epsilon_insensitive’是为回归设计的,但在分类中也可能有用
所以根据这个分类器的定义,线性回归也是一个分类器!
2.2 从统计学角度来看分类器
现在,对于统计学家来说,什么是分类器?更具体地说,你能找到一个直接具有二元输出的数学函数吗?看来通常的数学函数总是具有数值输出。0 到 1 之间的输出是最接近二元输出的。因此,如果逻辑回归仍被认为是回归模型,那么基于数学函数的分类器可能根本不存在!
为了澄清我的陈述,我在谈论基于数学函数的模型或参数模型。对于基于距离的模型如 KNN,或基于决策树的模型,没有争议区分回归器和分类器。
有人会提到 LDA(线性判别分析),但你能写出 LDA 的数学函数吗?你不能,因为这个模型的起点不是基于数学函数的模型。但令人惊讶的是,最终的分类模型形式可能非常接近逻辑回归!你可以阅读这篇关于 LDA 与逻辑回归比较的文章。
有人会提到 SVM(支持向量机)。从统计的角度来看,我不确定 SVM 的实际位置在哪里。在《统计学习导论》一书中,有专门的一章讲述 SVM,好像它与其他建模方法非常不同。可以确定的是,分类是基于 y 的规则:如果 y 为正,则类别为正,否则将分配为负类别。因此,从这个角度来看,我们可以对逻辑回归应用相同的推理:它不是一个分类器,因为模型的主要输出不是二元的,而是连续的,只有在此之后,才能应用关于 y 的决策规则。然而,通常认为 SVM 是一个分类器。
事实是我们通常甚至不提及 y 被建模的情况,而是直接跳到超平面的部分,考虑 y = 0,如我们在维基百科页面上看到的那样。

en.wikipedia.org/wiki/Support_vector_machine
2.3 第二轮总结
对于第二轮的总结,当我们试图定义一个分类器时,争论已经结束。对于机器学习从业者来说,逻辑回归是一个分类器,这不是因为模型本身的任何特征,而仅仅是因为训练数据集有一个二元目标变量。对于统计学家来说,嗯,它是一个回归器,因为在试图找到一个数学函数来建模二元输出时没有其他选择……哦等等,当输出是从负无穷到正无穷的实数时,它是回归。当输出是从 0 到 1 时,它也是回归,或许对统计学家来说,有不同类型的回归。让我们进入第三轮。
3. 第三轮:两个不同的框架
是时候退后一步,从更高的视角来看一下:逻辑回归在其他模型中处于什么位置?
3.1 从统计学的角度看广义线性模型
对于统计学家来说,逻辑回归属于一个叫做广义线性模型(GLM)的模型家族。基础模型是线性回归,函数为 y = wX + b,术语“广义”意味着可以对这个简单的线性模型应用各种链接函数,从而使得生成的模型能够预测不同类型的输出,例如连续值、具有概率输出的二元值、多项式概率输出、计数数据或仅正值。
然后,模型参数的估计是通过最大似然估计(MLE)来完成的。因此,根据输出的性质,使用的分布种类不同,支持范围也不同,例如具有实数支持的正态分布、具有仅正值支持的伽马分布、具有整数支持的泊松分布和具有二元支持(0 和 1)的伯努利分布。
为了研究模型的质量,我们通常会研究系数和模型的统计显著性。
3.2 机器学习框架
对于机器学习,通常的区分只是回归器与分类器。值得注意的是,如果目标变量代表计数数据,仅正值,它仍然是回归。分类可以是二元的或多类的。
对于模型拟合部分,我们通常使用“成本函数”这个术语,其值必须被最小化,而“似然”则必须被最大化。但在许多情况下,这两者是等价的。对数损失是其中一种可能的损失函数。事实证明,使用对数损失使得模型等同于逻辑回归。
(现在,我们还可以提到,结合 L2 惩罚项的铰链损失等同于 SVM。正如我之前提到的,我不知道 SVM 在统计学中的位置,但在机器学习中,它的位置非常明确:它是一个具有铰链损失和 L2 惩罚的线性模型。当统计学家说它创建了一个进行分类的超平面时,事实是,具有对数损失的线性模型,也就是逻辑回归,做的也是一样的,事实上,无论使用什么损失函数,这都是真的,因为这是线性分类器的定义:从线性模型创建一个超平面!)
为了评估模型的质量,我们通常使用性能指标和模型调优部分,包括优化超参数。在这里我们可以注意到,GLM 没有超参数的概念。
你可以了解更多关于统一线性分类器在估计器 SGDClassifier 中的内容。

SGDClassifier — 作者提供的图片
3.3 第三轮结论
所以根据这两个框架,“逻辑回归是回归器还是分类器”这个问题有点奇怪:
-
“逻辑回归”是 GLM 框架中使用的术语
-
回归器与分类器是机器学习框架
这个问题的本质类似于“番茄是水果还是蔬菜”。两种答案都是可能的,因为这两种观点来自两个不同的领域:植物学和营养学。
总结这一轮,统计学角度试图找到根据输出性质定义上相关的模型,在逻辑回归的情况下,主要目标是用伯努利分布建模二元输出。统一框架是 GLM。从机器学习的角度来看,我们有三个主要步骤:
-
模型:在逻辑回归的情况下,它是 y = w X + b
-
拟合:选择对数损失来找到系数 w 和 b
-
调优:我们通常使用惩罚项来避免过拟合,需要进行调优。
4. 结论
逻辑回归是回归器还是分类器?现在,希望我们都能看到这个问题本身是定义不明确的。名称“逻辑回归”本身反映了一种观点。输出没有明确定义。基于数学函数的分类器没有明确定义。
但这是一个机会,因为在真正尝试回答这个问题时,我们可以从不同的角度重新发现许多我们以为已经了解的事物,从而获得更多理解。回归器与分类器之间的区别本身就是机器学习的视角。对于统计学家来说,逻辑回归是属于 GLM(广义线性模型)的一个模型,这个框架考虑了更多类型的输出,比如计数数据、仅正输出。
为了更好地了解机器学习模型,特别是一些我提到的模型,如 LDA 或 SVM。它们在这些框架中的地位如何?就像关于番茄是水果还是蔬菜的古老争论一样。你可能会忘记同样的问题也适用于南瓜和青豆。在某种程度上,LDA 和 SVM 也可以被视为回归器和分类器。
我写了这篇文章,以便你可以阅读关于机器模型的三步,以获得更好的总体理解。
我撰写关于机器学习和数据科学的文章,并以清晰的方式解释复杂概念。请通过以下链接关注我,获取我的文章的完整访问权限:medium.com/@angela.shi/membership
参考资料:
其他参考资料:
PandasGUI — 轻松数据分析的终极秘密
原文:
towardsdatascience.com/is-pandas-easy-to-use-try-this-tool-to-make-it-easier-2071eeffe482

图片由 Alan Frijns 提供,来源于 Pixabay
PandasGUI 的数据分析实用概述
·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 4 月 14 日
--
数据分析已成为各个行业不可或缺的一部分,因为它使我们能够根据收集的数据做出明智的决策。Python 中最受欢迎的数据分析库之一是 Pandas,它提供了强大的数据操作和清理工具。然而,使用 Pandas 有时可能会感到不知所措,特别是对于那些刚接触数据分析或偏好更直观方法的人来说。这时,PandasGUI 就派上用场了 — 这是一个将图形用户界面引入 Pandas 的库,使数据操作和可视化变得更加易于访问和用户友好。
在本文中,我们将详细了解 PandasGUI 及其功能,指导您完成安装过程,并展示其能力。
1. 安装与启动

图片由 traditional chinese medician · 素君 提供,来源于 Pixabay
首先,我们需要安装 PandasGUI。和往常一样,我们可以使用 pip 来安装它。
pip install pandasgui
1.1 非 Windows 操作系统的小问题
本节适用于使用非 Windows 操作系统的用户,如果您实际使用的是 Windows 操作系统上的 Python,则可以跳过此步骤。
看起来作者是在 Windows PC 上创建了这个库,因此假设操作系统会有一个环境变量 APPDATA。但是,对于其他操作系统如 Mac 或 Linux,这种情况并非如此。具体来说,当我们尝试导入 PandasGUI 时,会出现这个错误。
import pandas as pd
import pandasgui

修复此问题的最简单方法是手动为该环境变量指定一个空字符串。
import os
os.environ['APPDATA'] = ""
然后,我们将能够毫无问题地使用 PandasGUI。

警告信息是正常的。我猜它没有实现一些 Mac OS 中推荐的接口,因此我的系统给出了这个警告。
1.2 加载示例数据集
为了演示这个库,我们需要使用一个示例数据集。如果你是一名数据科学家,你可能对 Iris 数据集很熟悉,它在许多分类或聚类机器学习演示中被使用。
我们从 Datahub.io 获取数据集。它是一个用于发现、共享和发布来自各种来源的高质量开放数据集的平台。这里的大多数数据集是开源的,并且可以根据许可证用于学习目的,包括 Iris 数据集。
df = pd.read_csv("https://datahub.io/machine-learning/iris/r/iris.csv")
df.head()
df.shape

1.3 启动 PandasGUI
现在,让我们极其简单地启动 PandasGUI。只需按如下方式调用 show() 函数即可。
pandasgui.show(df)

不用担心关于缺失字体家族的警告,这再次是由操作系统造成的。指定的字体家族在我的 Mac OS 上不存在。这不影响我们使用 GUI。
运行这一行代码后,GUI 应该会以桌面应用程序的形式弹出。

2. PandasGUI 的功能

UI 相当直观。它由以下组件组成。我将在后续小节中介绍它们。
-
DataFrame 列表 — 我们可以在这里导航和切换数据帧。它还显示了数据帧的形状以方便查看。
-
过滤查询 — 创建和选择查询表达式以过滤当前数据帧
-
列列表 — 查看和导航当前数据帧的列
-
功能标签 — 切换标签以导航不同的工具
-
主要区域 — 显示当前操作的结果

2.1 过滤数据帧
我想介绍的第一个功能是过滤。它依赖于 DataFrame 查询表达式来快速为我们过滤数据帧。
具体来说,我们只需输入类似sepallength > 7的查询并按回车。过滤器将应用于数据框。我们可以在主区域查看过滤后的结果。

如果我们想回到查看整个数据框,我们可以取消勾选表达式以移除过滤器。

此外,还可以添加许多查询表达式并使用复选框灵活应用它们。例如,下面的截图显示了两个已选中的表达式,它们都用于过滤数据框。

2.2 排序、类型转换和颜色编码
在数据框主区域,我们还可以轻松地完成许多类似 Excel 的操作,如排序和颜色编码。除此之外,我们还可以轻松地转换列的数据类型。

例如,下面的截图显示了数据框按sepalwidth列的降序排序,并且数值列根据其值的范围进行了颜色编码。

2.3 统计信息
在第二个特征标签中,我们可以看到该数据框的统计信息。

还值得一提的是,我们也可以在左侧选择查询表达式。然后,统计信息将基于过滤后的数据框重新计算。

2.4 绘图
我不得不说,当我们想使用代码绘制图表时,Python 是最简单的语言之一。然而,我们毕竟还是要写一些代码。
在 PandasGUI 中,我们可以在几秒钟内使用其列绘制数据框。例如,下面的演示显示了我只需切换到“绘图器”标签并选择“散点图 3D”。然后,将一些列拖到轴字段中。

如果我们想切换到其他类型的图表,也不需要花费多少时间。这实际上让我们可以快速测试不同类型的图表,并决定哪种图表能更好地讲述数据故事。
2.5 重塑数据框
我们还可以使用 PandasGUI 通过拖放重塑数据框。例如,我们可以通过将“class”转换为列来透视 Iris 数据框,然后计算每个属性的平均值,例如花瓣长度。

拖动列后,点击“完成”按钮。将生成如下的新数据框。

2.6 生成代码
对于上述大多数功能,PandasGUI 也可以为我们生成代码。当我们使用 GUI 决定哪种图表最好时,这非常有用,然后可以轻松生成代码并将其放入实际脚本中。

类似地,重塑功能也提供了代码导出功能。它允许我们多次实验重塑,然后输出正确的代码。
好吧,我们可能可以在 ChatGPT 中完成这个,但需要解释很多内容,并将其应用到我们的背景中 😃
总结

总结来说,本文深入探讨了 PandasGUI 的各种功能,这是一种强大的库,为广泛使用的数据处理和可视化的 Pandas 库带来了图形用户界面。我们演示了安装过程,加载示例数据集,并探索了如筛选、排序、统计分析、绘图、重塑和代码生成等功能。
PandasGUI 是一个宝贵的工具,可以通过简化常见任务和提供互动体验来显著增强你的数据分析工作流。尽管它极大地便利了初学者和经验丰富的数据科学家的数据处理,但需要注意的是,它可能不支持极其复杂的操作。对于高级操作,可能需要依赖传统的 Pandas 脚本。
[## 通过我的推荐链接加入 Medium - Christopher Tao
感谢阅读我的文章!如果你不介意的话,请请我喝杯咖啡 😃 你的会员费用支持成千上万的…
medium.com](https://medium.com/@qiuyujx/membership?source=post_page-----2071eeffe482--------------------------------)
如果你觉得我的文章有帮助,请考虑加入 Medium 会员,以支持我和成千上万的其他作者!(点击上面的链接)
除非另有说明,否则所有图片均由作者提供
PyTorch 的 Nesterov 动量实现是否有误?
·
关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 9 月 2 日
--
动量帮助 SGD 更有效地遍历复杂的损失景观。图片由 Maxim Berg 提供,来源于 Unsplash。
引言
如果仔细查看 PyTorch 的 文档,你会发现他们对 Nesterov 动量的实现与 原始论文 中的公式有一些不同。最显著的是,PyTorch 的实现是在当前参数上计算梯度,而 Nesterov 动量的核心是评估偏移参数的梯度。不幸的是,关于这些差异的讨论在互联网上很少。在这篇文章中,我们将检查并解释 PyTorch 实现与 Nesterov 动量原始公式之间的差异。最终,我们将看到 PyTorch 的实现并非错误,而是一种近似,并推测其实现的好处。
公式
原始论文 使用以下更新规则描述了 Nesterov 动量:
其中 v_{t+1} 和 θ_{t+1} 分别是时间 t 的速度向量和模型参数,μ 是动量因子,ε 是学习率。PyTorch 的 SGD 文档 中的说明称他们使用以下更新规则:
其中 g_{t+1} 代表用于计算 v_{t+1} 的梯度。我们可以展开 θ_{t+1} 的更新规则得到:
从中我们可以推断:
和更新规则变成:
这些是 PyTorch 理论上使用的更新规则。我之前提到过,PyTorch 实际上是在当前参数上评估梯度,而不是偏移参数。通过查看 PyTorch SGD 文档中的算法描述可以看到这一点。我们将在后面进一步调查这一点。
请注意,对于原始公式 (1, 2) 和 PyTorch 公式 (3, 4) 来说,如果 v_0 = 0,则 θ 的第一次更新变为:
尽管 PyTorch SGD 文档中说明算法在第一步将动量缓冲区初始化为梯度,但我们稍后会显示这意味着 v_0 = 0。
初步差异
从原始公式 (1, 2) 到 PyTorch 公式 (3, 4) 存在两个直接的区别:
-
学习率被移出了
v_{t+1}。 -
在
v_{t+1}的更新规则中,涉及梯度的项被添加而不是减去,而在θ_{t+1}的更新规则中,涉及速度向量的项被减去而不是添加。这种梯度项符号的差异仅仅是前一节中展示的结果。
要理解这些差异,让我们首先展开更新规则。如 这里 所提示的,如果我们考虑学习率调度,第一种差异的影响会更为明显。因此,我们考虑一个更新规则的广义化,其中 ε 不再是固定的,而是可以随时间变化,并将 ε_t 记作时间步 t 时的学习率。为简洁起见,设:
假设 v_0 = 0,原始公式变为:
那么 PyTorch 公式变为:
在原始公式(6)中,如果学习率在时间 t 发生变化,那么仅会影响求和中 i = t 的项的大小,而所有其他项的大小保持不变。因此,学习率变化的直接影响是相当有限的,我们必须等待学习率变化在随后的时间步中“逐渐”发挥更强的作用。相比之下,在 PyTorch 公式(7)中,如果学习率在时间 t 发生变化,那么整个步骤的大小会立即受到影响。
对于 v_0 = 0,从展开的规则可以看出,第二种差异最终没有影响;在任一公式中,这一步的结果都是从当前参数中减去折扣后的梯度和。
主要差异
忽略权重衰减和阻尼,通过分析 PyTorch 的 文档,我们可以看到实现的更新规则是:
其中 θ’_{t+1} 是时间 t 时的模型参数
我们将方程 3 和 4 称为 PyTorch 的“笔记”公式,将方程 8 和 9 称为 PyTorch 的“实现”公式。我们在θ和θ’之间做了区分,原因会很快显现出来。与笔记公式最明显的区别是梯度在当前参数处进行评估,而不是在偏移后的参数处。从这一点来看,算法实现的更新规则似乎并不是 Nesterov 动量的正确实现。
我们现在将考察 PyTorch 算法如何最终近似 Nesterov 动量。旧版本 PyTorch 的推导可以在 Ivo Danihelka 提供的 这里 找到,并在 这个 GitHub 问题 中引用。当前版本 PyTorch 的推导可以在 这里 找到,这相对是对之前推导的简单调整。为了方便读者,我们在此提供这些(重新推导的)推导的 LaTeX 渲染。实现的公式是通过简单的变量变换得到的。具体而言,我们设:
很明显,v_{t+1} 的注释更新规则 (3) 在变量变换后与 v_{t+1} 的实现更新规则 (8) 等效。我们现在想要推导一个关于 θ’_{t+1} 的更新规则,以 θ’_t 为变量:
这正是我们在 PyTorch (9) 中看到的更新规则。总体上,PyTorch 实现假设当前参数 θ’_t 已经是“实际”参数 θ_t 的偏移版本。因此,在每一个时间步, “实际”参数 θ_t 与当前参数 θ’_t 之间的关系为:
然而,从源代码来看,PyTorch SGD 实现似乎在算法结束时没有对“实际”参数进行任何修正,因此最终输出在技术上是“实际”参数的近似值。
最后,我们现在展示 v_0 必须为 0:
此外,我们可以确认对“实际”参数的第一次更新与原始表述中 v_0 = 0 时的第一次更新相同:
我们可以看到这等同于方程 5。
实现表述的好处
当然,剩下的大问题是:为什么 PyTorch 要从方程 3 和 4 重新表述 Nesterov 动量到方程 8 和 9?一个可能的解释是,重新表述可能在所需的算术操作数量上有所节省。为了评估这个可能的解释,我们来计算一下算术操作的数量。对于注释的表述 (3, 4),我们有:
在这里,总共有七个操作。对于实现的表述 (8, 9),我们有:
在这里,总共有六个操作。PyTorch 实现中的第二个梯度只是使用了第一个梯度计算的保存结果,因此每个时间步只执行一个梯度计算。因此,一个明显的好处是 PyTorch 实现减少了每一步的一个额外乘法操作。
结论
总结
-
PyTorch 的 SGD 文档注释 (3, 4) 中的更新规则与原始 Nesterov 动量更新规则 (1, 2) 中的学习率位置不同。这允许学习率调度对整体步长有直接影响,而原始表述则会使学习率变化在随后的时间步中“逐步”体现。
-
PyTorch SGD 算法 (8, 9) 中实现的更新规则是在简单的变量变换后对文档注释 (3, 4) 中更新规则的近似。尽管“实际”参数可以在每个时间步从当前参数中轻松恢复,但 PyTorch 实现并没有在算法结束时进行任何修正,因此最终参数在技术上仍然是“实际”最终参数的近似值。
-
PyTorch 实现的一个明显好处是它避免了每个时间步的额外乘法操作。
参考文献
-
“SGD。” SGD — PyTorch 2.0 文档,pytorch.org/docs/stable/generated/torch.optim.SGD.html。访问日期:2023 年 9 月 2 日。
-
Sutskever, Ilya 等人。“深度学习中初始化和动量的重要性”。国际机器学习会议。PMLR,2013 年。
-
Danihelka, Ivo. “Nesterov 的动量简明介绍”。2012 年 8 月 25 日。
-
Chintala, Soumith. “SGD 中的 Nesterov 动量是错误的 · Issue #27 · torch/optim。” GitHub,2014 年 10 月 13 日,github.com/torch/optim/issues/27。
-
Gross, Sam. “在文档中添加关于优化中使用的动量公式的说明 · Issue #1099 · pytorch/pytorch。” GitHub,2017 年 3 月 25 日,github.com/pytorch/pytorch/issues/1099#issuecomment-289190614。
-
Zhao, Yilong. “修复 Nesterov 动量错误 · Issue #5920 · pytorch/pytorch。” GitHub,2018 年 3 月 21 日,
github.com/pytorch/pytorch/pull/5920#issuecomment-375181908。
无服务器难以采纳吗?
理解使您的无服务器采用成功的简单措施
·
关注 发表于 Towards Data Science ·13 min read·Nov 20, 2023
--
Mikhail Nilov 拍摄的照片,来自Pexels
一年多前,我们在奥地利塞默林的 9 月环境中某个高处徒步时,在我们的小径上的一个路口迷失了方向。由于路标不明确,我用手机寻找线索。在众多 Twitter(X)通知中,刚刚闪过保罗·约翰斯顿的文章学习无服务器(以及为什么难)。
在平常的日子里,我会立刻阅读它。但考虑到当前的位置以及引导家人走出树林的责任,我并不愿意为了无服务器牺牲赏心悦目的塞默林早晨。
我重读了这一点,在他的文章中,保罗勇敢地击中了许多钉子!我本想更早地加入,但是为了完成《AWS 上的无服务器开发。构建企业级无服务器解决方案》(O’Reilly, 2024)这本书,我在一年后来借了这把锤子,来打自己的一些钉子,从整体角度来看问题,而不仅仅是学习本身。
在这篇文章中,我将带你了解使用无服务器技术开发应用程序时成功的一些关键因素 — 无论是在商业产品开发、构建数据驱动的应用程序、AI 还是机器学习方面。
AWS 上的无服务器开发:构建企业级无服务器解决方案
驾驶汽车难吗?
-
驾驶 手动变速汽车难吗?
-
学习 手动变速汽车难吗?
答案因人而异。如果你已经开车几年,对第一个问题的回答将是否定的 no。因为多年来,你已经熟悉了所有的操作,它们已经变得自然而不需要考虑。
然而,当你还是一名学习者时,你对第二个问题的回答肯定是 yes。学习驾驶时,有许多事情需要考虑 —— 需要协调的动作、按顺序进行的活动以及需要并行进行的观察。所有这些都是在车辆运行时进行的。
仅仅掌握方向盘技能并不足以使你称为一名司机。
如果你现在考虑一个自动变速汽车,作为驾驶员的活动会有所减少,因为汽车的机械结构会处理某些动作(就像云提供商处理无服务器中的一些重活)。
我们日常生活中有很多挑战,但我们学习技能并跟随已验证的路径来提升自己。无服务器开发也不例外。它可能听起来有点简单(就像自动变速汽车),但它不会让你即刻启动引擎并飞驰而去。

一个混乱的无服务器实现。来源作者。
在技术上有很多错误的做法 —— 无服务器更是如此。使用无服务器服务构建混乱的事件驱动架构非常容易且时间短。但是,构建和开发一个模块化、可观察且可持续的解决方案需要知识和正确的技能。这并不意味着无服务器很难。
在技术领域中,有许多做错事的方法——尤其是在无服务器环境中。
如果你不理解其生态系统,无服务器将会很难。
一辆车有许多部件,从高层次到细节层次。车发动机主要作为一个整体来书写,但有多个组件。一个正常运转的汽车需要一个司机(忽略无人驾驶的情况)并搭载乘客。从某种程度上讲,所有这些共同构成了汽车的生态系统。
很多时候,许多人将无服务器描绘成一种架构蓝图、函数即服务(FaaS)或框架。对我而言,它超越了这些概念,远超我们通常想象的。无服务器,某种程度上,是一个技术生态系统。当你和我使用无服务器时,我们也成为其中的一部分——类似于司机和乘客成为汽车生态系统的一部分。

无服务器生态系统包含许多因素。来源作者。
如图所示,无服务器生态系统包含多个因素。无服务器技术带来了其独特的特性。云平台及其托管服务构成了基础。开发实践、工具和框架使流程更快地带来价值。业务利益相关者与工程师合作,为客户在无服务器上构建现代能力。所有这些以及更多内容都是生态系统的一部分。
上述描绘的目的是让你超越编写函数或讨论基础设施工具的思维。考虑一下无服务器给你的组织、团队和你个人带来的多样性,因为你在使用无服务器时将会身兼多职。随着我们成熟并成为无服务器生态系统的一部分,像Lee Gilmore这样的想法比以前更为常见。
如果你以错误的心态开始,无服务器将会很难。
几年前,一位工程师联系我寻求有关他无服务器旅程的指导。聊天几分钟后,很明显他希望以云无关的方式实现他的 Lambda 函数逻辑,以便他的无服务器应用程序可以在组织决策者希望切换时随时部署到不同的云提供商 如果及何时 决策者想要切换。
了解他的意图后,我询问了他对非 FaaS 服务的处理方法,以及他如何使其具有云无关性。他的解释是某种宏伟的六边形架构实现!
在通话快结束时,我意识到他还没有在生产环境中部署任何无服务器工作负载!
尽管类似上述的思想和方法让董事会满意,但挑战和对业务的价值却未被评估。企业采用诸如无服务器这样的技术,以提高速度、增加流量并建立竞争优势。以上述心态接触无服务器就像是在追逐海市蜃楼,永远无法完成并交付任何东西。
工程师经常指出框架提供的多云配置作为例子(或借口)。事实上,工具提供这样的功能是为了让每个人都能使用它们,并且你不应该根据框架提供的内容做出多云决策。当这些错误的策略无法产生价值时,你会听到抱怨 - 切换到 Serverless 是困难的、令人困惑的,并且违反直觉 — 正如保罗在他的文章中提到的。
开发工具经常支持多云能力,但你不应该基于框架提供的内容做出多云决策。
如果你认为 Serverless 太简单,它就会变得困难。
我被邀请参加一个会议,听取并批准一个 Serverless 架构提案。一个工程师展示了一个精心制作的虚拟板,并详细解释了他们的架构。Lambda 函数主导了解决方案,周围散布着一些 DynamoDB 表。我开始感到不舒服,并且抱歉地打断了谈话,问了这个问题 -
你以前构建过 Serverless 解决方案吗?
工程师回答说,是的!
在对设计中某些选择的几个“为什么”进行更多询问后,工程师承认之前他们只为简单功能编写了 Lambda 函数,而没有设计或构建事件驱动的微服务。
我在这里的意图不是贬低工程师,而是强调我们在学习和使用新技术时都要经历的重要阶段。知道如何编写和操作 Lambda 函数绝对是正确的开始,但与此同时,你不应该认为 Serverless 中的一切都太容易了。这种态度会导致你实现混乱的 Serverless 应用程序。
我同意,对于新手来说,将问题分解为微服务、识别应用程序的同步和异步部分、以事件方式思考,或者设计可观察性都是复杂的。事实上,这些方面中的许多并不新鲜,也不特定于 Serverless。不同之处在于,过去的隔离团队结构中,作为程序员的你从未接触过或参与其中。
学车时,你需要经过多次课程才能在实际驾驶测试中感到自信。同样,评估你的 Serverless 技能并在工作中作为 Serverless 工程师进行必要的学习(在后面的部分讨论)是必要的。
Serverless 是很难的,如果你排除工程师参与架构讨论。
我在指导团队时采用的方法是将一个服务或功能的开发所有权分配给一名或多名工程师 — 从想法到生产。他们从与产品团队、利益相关者和技术专家的早期交流开始,收集关于他们将要构建的解决方案的知识。他们参与必要的构思会议和问题分析,并在得到资深人员和专家指导后开始起草架构,然后发布一个解决方案设计文档,供所有人审查。从这里开始,实施票据被创建并进入迭代开发和交付阶段。
它带来了什么,我们为什么需要它?
上述方法是有意为之的。尽管存在缺点和批评,但它为团队和工程师的职业成长带来了许多好处。要教育工程师并使他们成为无服务器生态系统的一部分,你必须停止向他们提供架构蓝图。相反,通过向他们提供思想和资源来与他们一起演进架构,使他们学习、开发和交付。
不要在架构师的象牙塔中设计你的无服务器架构,而是在你团队的引擎室中创建它们。
曾经在一个组织中,进行了跨团队合作,以推出一个新功能。之前有几次构思会议,与一群人讨论后达成了一个高层提案,足够详细以在解决方案设计中详细描述。后来,几名工程师被指派一个票据来实现他们团队解决方案的一部分。然而,这些工程师从未参与过先前的会议,没有简要介绍,也没有看到捕捉到所有涂鸦、绘图和思绪的虚拟板。
可以想象,上述工程师陷入了困境。无论工程师的能力如何以及问题的微不足道程度如何,请确保你的无服务器开发不是从 Visual Studio (VS) Code 开始的。如果你这样做,不能保证你的无服务器体验会更顺利。
无论工程师的能力如何以及问题的微不足道程度如何,你的无服务器开发不应该从 VS Code 开始。
如果你追求完美主义而不是实用主义,无服务器开发就会很困难。
在我的演讲中,我分享了一张幻灯片,展示了一个实际思维的团队如何借助无服务器加速,而试图通过悲观的观点使一切完美的纯主义团队则落后。

两种类型的无服务器采用派系。来源作者。
通常情况下,信息过载和没有实际引擎室经验的象牙塔架构师的完美主义会使你的无服务器采用变得困难,你的经验变得糟糕。
我曾经在社区交流中听到过两支无服务器团队的故事。这两支团队有共同之处。它们都在各自的边界内运作 — 即有界上下文内 — 开发事件驱动的微服务,并且拥有明确定义的 API。
其中一支团队在单个生产 AWS 账户中拥有和运行它们的微服务(测试、QA、预发布等有单独的账户),将它们的 Lambda 函数放置在自定义 VPC 之外,并仅在必要时配置 VPC,将它们的 API 托管在 Amazon API Gateway 并配备必要的使用计划。他们的服务由 Amazon EventBridge 上的自定义事件总线协调。对于这支团队来说,开发、部署和运营新的微服务听起来是轻而易举的。
然而,第二支团队遵循纯主义思想,选择在不同的 AWS 账户中托管每个微服务,并将其 Lambda 函数部署在自定义的 VPC 内。此外,它们所有的 API 都托管在一个不同的网关着陆平台上。随着二十多个微服务的增加,它们在处理涉及的复杂性时陷入困境。
当然,在我们做出不同选择和决策的情况下,我们应该评估并纠正课程,以避免陷入难以管理且无法回头的局面。
采用无服务器架构的主要动机之一是将繁重的工作交给云服务提供商,但如果我们的策略与无服务器的优势背道而驰,无服务器只会变得更加困难。
如果你侵蚀团队边界,无服务器将变得困难。
当涉及团队边界时,每个人都会考虑有界上下文(如领域驱动设计中的概念)。然而,其他边界对于自治的两披萨无服务器团队同样重要,如下所列。破坏边界将导致使你的无服务器经验变得困难的后果。
-
有界上下文边界
-
团队责任边界
-
团队所有权边界
-
源代码仓库边界
-
云账户边界
前三者相互依赖且重叠。如果你的团队是组织领域的有界上下文的保护者,你就处于一个很好的位置。在这种情况下,你的团队负责有界上下文边界内的所有事物,并拥有所有功能、服务和实现工件。
所有你拥有资产的实现工件都在一个仓库中,你的团队成员在其中贡献并共享。
AWS 云账户边界标志着您部署和操作云及无服务器资产的运营边界。
在理想的世界中,您的团队与这些边界之间会有一对一的映射,如下所示。
-
您的团队存在是因为有了有界上下文。
-
您的团队负责并拥有有界上下文中的所有内容。
-
您的团队有一个仓库,只有您的团队贡献。
-
您的团队在其专用的云账户中操作其拥有的所有内容。
您作为无服务器工程师的生活尽可能简单和愉快!
现在,让我们开始放松并打开边界,想象可能会发生的事情。
由于业务优先级和满足交付期望的需要,您将来自另一个团队的工程师分享对您团队边界的责任,迫使您团队的重点领域发生变化。
-
您如何确保团队了解彼此的工作方式?
-
您如何确保代码质量保持一致?
-
您如何确保选择的 AWS 服务和架构模式是对齐的?
-
您如何确保操作责任没有漏洞?
这些团队可以合作解决和缓解情况。但合作意味着更多的聊天、配对、审查、讨论、会议等,这些都会影响两个团队的关注和效率。
开放团队边界和共享责任,如果没有充分的考虑,可能会对组织产生负面影响。无服务器开发会变得更加困难,开发者体验也会变得令人沮丧。
如果不投入人员的成长,无服务器将变得困难。
我们周围充满了捷径。几条捷径可以教您编写 Lambda 函数。这种快餐式的学习方法只在食物存在的时间内有效。为了满足您的胃口,您需要不断订购更多的食物。
要在无服务器中取得成功,您必须不断学习。
Paul 的文章 主要关注学习以及进入无服务器领域的人们通常如何努力理解技术及其生态系统。正如 Paul 所描述的,对于一个新手来说,甚至单一用途的 Lambda 函数的概念本身也需要时间消化。
要在无服务器中取得成功,一旦掌握了单一用途的 Lambda 函数,您需要迈出许多步伐。几个领域充当您向成功目标迈进所需的跳板,并且您在此过程中积累了许多特质。
我经常强调无服务器如何为团队带来工程多样性。如果您想让无服务器变得简单,这种认识至关重要。
工程师应该得到引导、培养、指导或培训(或者你的组织中使用的术语),让他们理解利益相关者的需求,允许他们提出架构方案,灌输单一功能和微服务的好处,展示如何融入安全性,教授可观察性原则,让他们部署到生产环境,并标记他们为服务的所有者。这不会一夜之间实现,也不会仅通过看几段 YouTube 视频就能达成。这正是质量培训和长期人才成长战略发挥作用的地方。

传统的专业化专家与多样化的无服务器工程师。信息来源作者。
不要用企业官僚主义限制工程师。让他们自由学习新知识,参加会议,并参与技术研讨会和协作活动。
我在一次会议上与一位工程经理交谈。她认为像 EventStorming 和架构 Katas 这样跨越几个小时或更长时间的研讨会是浪费时间,并影响团队的生产力。于是我问她,如果有几位机会被拒绝和不满的工程师请了一两天病假,她怎么处理生产力问题。她没有答案!
无服务器训练营是装备工程师掌握无服务器技术生态基础的简单方法。一些组织已成功实施了这类项目。Matt Coulter,一个 AWS 英雄,曾提到 Liberty Mutual 公司成功为新员工实施的一个项目。组织经常因预算约束而将此类倡议降级,甚至未评估其带来的好处。
在无服务器培训中的一个挑战是课程大纲中技术、开发、架构和运营元素的质量和覆盖范围。在多个课程中,我听到了Yan Cui的《生产就绪的无服务器》培训研讨会获得了很好的反馈。Yan 是 AWS 无服务器英雄,也是无服务器知识的强大力量。
学习构建生产就绪无服务器应用的最佳实践。
productionreadyserverless.com](https://productionreadyserverless.com/?source=post_page-----56c35672b958--------------------------------)
让工程师理解利益相关者的需求,允许他们提出架构方案,灌输单一功能和微服务的好处,展示如何融入安全性,教授可观察性原则,让他们部署到生产环境,并标记他们为服务的所有者。
无服务器将变得更加简单…
就像驾驶随着驾驶时间的增加变得更好更容易一样,随着你积累经验并熟悉其生态系统,无服务器变得更加高效和愉悦。
过山车并非每个人的最爱。寻求刺激的人们常给他人的最常见建议是 — 你必须学会放手!
你必须学会相信无服务器技术,以利用它提供的无差别重型工作。如果 AWS 提供托管解决方案,请利用它们来增加业务价值。为什么要反其道而行之,构建你永远不需要的复杂解决方案?
从那些成功采用无服务器的人那里获取灵感。
要结束的时候,我找不到比Momento 的 AWS re: Invent 2023 社区派对主题更好的短语 -
相信无服务器!
是的,相信无服务器,并学会放手!
偏差和方差之间是否总有权衡?
原文:
towardsdatascience.com/is-there-always-a-tradeoff-between-bias-and-variance-5ca44398a552
不拘一格的揭秘者
偏差-方差权衡,第一部分,共 3 部分
·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 2 月 15 日
--
你应该阅读这篇文章吗?如果你理解下一部分中的所有单词,那么不。如果你不在乎理解它们,那么也不。如果你想了解粗体部分的解释,那么是。
偏差-方差权衡
“偏差-方差权衡” 是一个在 ML/AI 语境中常听到的流行词汇。如果你是 统计学家,你可能认为它是对这个公式的总结:
MSE = 偏差² + 方差
不是的。
嗯,它有点相关,但这个短语实际上指的是如何选择模型的复杂度甜点的实际方法。当你在调优正则化 超参数时,它最有用。

作者插图。
注意: 如果你从未听说过 MSE,你可能需要对一些术语有所了解。当你遇到新术语并想要更详细的解释时,你可以跟随链接到我的其他文章中,我会介绍我使用的词汇。
理解基础知识
均方误差(MSE)是模型的损失函数中最流行(且最基础)的选择,通常是你首先被教到的。你可能会上很多统计课程,才会有人告诉你可以选择最小化其他损失函数。(但说实话:抛物线很容易优化。记得 d/dx x² 吗? 2x。这种便利足以让大多数人忠于 MSE。)
一旦你了解了 MSE,通常在几分钟内就会有人提到偏差和方差公式:
MSE = 偏差² + 方差
我也做到了,像一个普通的数据科学狂人一样,把证明留给了感兴趣的读者。
让我们做些补救——如果你希望我在推导过程中做些讽刺评论,可以稍微绕道这里。如果你选择跳过数学内容,那么你只能接受我的手势,并相信我的话。
只有积极的氛围
想让我直言不讳地告诉你关键点吗?请注意,这个公式由两个不能为负的项组成。
你在调整你的预测机器学习/人工智能模型时试图优化的数量(MSE)可以分解为始终为正的项,这些项只涉及偏差和方差。
MSE = 偏差² + 方差 = (偏差)² + (标准差)²
更直接一点?好吧,当然可以。
更好的模型具有更低的 MSE。E 代表误差,误差越少越好,所以最佳模型的 MSE 为零:它不会犯错。这也意味着它没有偏差和没有方差。

与其追求完美模型,不如看从好到更好的过程。如果你真的能够改善你的模型(在 MSE 方面),就不需要在偏差和方差之间做权衡。 如果你变得更好的弓箭手,你就变得更好的弓箭手了。没有权衡。(你可能需要更多的练习——数据!——才能做到这一点。)
正如托尔斯泰所说,所有完美的模型都是相似的,但每个不满意的模型都可以以自己的方式不满意。
所有完美的模型都是相似的
正如托尔斯泰所说,所有完美的模型都是相似的,但每个不满意的模型(在给定的 MSE 下)都可以以自己的方式不满意。你可以得到两个同样糟糕但不同的模型,MSE 相同:一个模型可能有很好的(低)偏差但高方差,而另一个模型可能有很好的(低)方差但高偏差,但两者的 MSE(整体得分)却相同。

如果我们用 MSE 来衡量弓箭手的表现,那就意味着减少弓箭手的标准差和减少偏差是等值的。我们说我们对这两者无所谓。(等等,如果你对它们并不无所谓呢?那么 MSE 可能不是你最好的选择。如果你不喜欢 MSE 的表现评分方式?没关系。自己制定一个损失函数。)
现在我们已经铺好了桌子,前往第二部分,我们将深入探讨问题的核心:是否存在实际的权衡?(是的!但可能不是你想象的那样。)以及过拟合与此有何关系?(提示:一切都有关!)
感谢阅读!怎么样,来一门课程吧?
如果你在这里感到有趣,且正在寻找一个既能吸引人工智能初学者又能打动专家的领导力课程,这里有一门我为你准备的小课程:

课程链接:bit.ly/funaicourse
想提升你的决策能力而不是仅仅增强你的人工智能技能?你可以通过这个链接访问我的免费课程:
## 你生活的方向盘——决策智能视频教程 | LinkedIn Learning…
决策能力是你可以学习的最宝贵的技能。你的人生归结为两件事:你生活的质量和…
P.S. 你是否尝试过在 Medium 上多次点击鼓掌按钮看看会发生什么? ❤️
喜欢作者吗?与 Cassie Kozyrkov 联系
让我们成为朋友吧!你可以在Twitter、YouTube、Substack和LinkedIn上找到我。对让我在你的活动中发言感兴趣?请使用这个表单与我联系。
阅读 Cassie Kozyrkov 的每一个故事(以及 Medium 上成千上万的其他作家的故事)。你的会员费直接支持…
这是否是解决 P-hacking 的方案?
原文:
towardsdatascience.com/is-this-the-solution-to-p-hacking-a04e6ed2b6a7

E 是新的 P 吗?图像由作者使用 Dall·E 创建。
e-值,一种优于 p-值的替代方案
·发表于 Towards Data Science ·阅读时间 11 分钟·2023 年 11 月 16 日
--
在科学研究中,数据操控和结果窥探一直是存在的问题。研究人员经常为了发表文章而追求显著的 p-值,这可能导致提前停止数据收集或操控数据的诱惑。这种做法被称为 p-hacking,是 我之前的帖子****的重点。如果研究人员决定故意更改数据值或伪造完整的数据集,我们也无能为力。然而,对于某些 p-hacking 的情况,可能存在解决方案!
在这篇文章中,我们深入探讨了安全测试的话题。安全测试相比于旧的(当前的)假设测试方法具有一些强大的优势。例如,这种测试方法允许将多项研究的结果进行结合。另一个优势是你可以在任何时间选择性地停止实验。为了说明安全测试,我们将使用由提出该理论的研究人员开发的 R 包 safestats。首先,我们将介绍 e-值并解释它们能解决的问题。由于其优势,e-值已经被 Netflix 和 Amazon 等公司使用。
我不会深入探讨理论的证明;相反,这篇文章采用了更实际的方法,展示了如何在自己的测试中使用 e-值。对于证明和安全测试的详细解释,原始论文是一个很好的资源。
e-值简介
在假设检验中,你可以在这里刷新相关知识,你评估是否保留原假设或接受备择假设。通常情况下,使用 p 值。如果 p 值小于预定的显著性水平 alpha,你就接受备择假设。
e 值的功能与 p 值不同,但有关联。e 值的最简单解释如下:假设你在对抗原假设进行赌博。你投资 1 美元,回报值等于 E 美元。如果 e 值 E 在 0 到 1 之间,你输了,原假设成立。另一方面,如果 e 值高于 1,你赢了!原假设输掉了比赛。 一个适中的 E 为 1.1 表示对原假设的证据有限,而一个巨大的 E,例如 1000,则表示压倒性的证据。
需要了解的一些 e 值要点:
-
e 值可以取任何正值,你可以在假设检验中将 e 值作为p 值的替代方案使用。
-
一个 e 值 E 可以通过关系 1/E = p 解释为传统的 p 值 p。注意:它不会给你和标准 p 值一样的结果,但你可以像解释 p 值那样解释它。
-
在传统的测试中,你有 alpha,也就是显著性水平。通常这个值为 0.05。e 值的工作方式略有不同,你可以将它们看作是对原假设的证据。e 值越高,对原假设的证据越多。
-
在使用 e 值的情况下,你可以在任何时候 (!) 停止数据收集并在测试期间得出结论。这被称为e 过程,使用 e 过程可以确保在可选停止下的有效性,并允许对统计证据进行连续更新。
有趣的事实:e 值并不像你想象的那么‘新’。关于它的第一篇论文是 1976 年写的。当时这些值并没有被称为 e 值。

研究人员在对抗...一个假设?!图像由作者使用 Dall·E 3 创建。
我为什么要关心 e 值?
这是一个有效的问题。传统的 p 值有什么问题?是否需要用 e 值来替代它们?如果当前的测试方式没有问题,为什么要学习新的东西?
其实,p 值确实存在一些问题。传统 p 值受到大量批评。一些统计学家(超过 800 人)想要完全放弃 p 值。
让我们用一个经典的例子来说明原因。
想象一下你是一个制药公司的初级研究员。你需要测试公司开发的药物的有效性。你寻找测试候选人,其中一半人接受药物,而另一半则服用安慰剂。你确定了得出结论所需的测试候选人数量。
实验开始了,你在寻找新参与者时有些困难。你面临时间压力,你的老板经常问:“你有结果了吗?我们想把这个产品推向市场!”由于压力,你决定偷看结果并计算 p 值,尽管你还没有达到最小测试候选人数!看着 p 值,现在有两个选择:
-
p 值是 不显著的。这意味着你不能证明药物有效。显然,你不会分享这些结果!你多等一会儿,希望 p 值会变得显著…
-
是的!你发现了一个 显著 的 p 值!但你的下一步是什么?你停止实验吗?你继续直到达到正确的测试候选人数量吗?你将结果分享给你的老板吗?
一旦你查看了数据,可能会很诱人地想要更频繁地查看。你计算 p 值,有时它显著,有时则不显著… 这样做可能看起来无害,但实际上你是在破坏整个过程。

显著还是不显著?图像由作者使用 Dall·E 3 创建。
为什么在实验正式结束之前只查看数据和相应的 p 值几次是错误的?一个简单而直观的原因是,因为如果你对其他结果做了什么(例如,如果你发现显著的 p 值你就停止实验),你就是在干扰整个过程。
从理论角度来看:你违反了第一类错误保证。第一类错误保证是指你有多大的把握不会错误地拒绝一个真实的零假设(= 发现显著结果)。这就像是对你在没有狼的情况下哭狼的频率的承诺。这种情况发生的风险是 ≤ alpha。但这仅适用于一个实验!如果你更频繁地查看数据,你就不能再相信这个值了:第一类错误的风险会变得更高。
这涉及到 多重比较问题。如果你做了多个独立的测试来证明相同的假设,你应该纠正 alpha 值,以保持第一类错误的风险较低。解决这个问题有不同的方法,比如 Bonferroni 校正、Tukey 的范围检验 或 Scheffé 方法。

多个独立测试的家庭错误率。对于一个测试,它等于 alpha。注意,对于 10 个测试,错误率增加到了 40%,而对于 60 个测试,它达到了 95%。图像来源:作者。
总结:p 值可以使用,但研究人员可能会在样本量达到之前查看数据。这是错误的,会增加 I 型错误的风险。为了保证实验的质量和稳健性,e 值是更好的选择。由于 e 值的特性,你不需要怀疑这些实验(或者至少少一些,研究人员总是可以选择伪造数据 😢)。
使用 e 值的好处
如前所述,我们可以像使用 p 值一样使用 e 值。一个主要的区别在于,大的 e 值与低的 p 值是可以比较的。回想一下 1/E = p。如果你想像使用 p 值一样使用 e 值,并且使用显著性水平 0.05,则当 e 值高于 20(1/0.05)时,你可以拒绝原假设。
当然,e 值还有更多的使用案例和好处!如果有多个实验测试同一假设,我们可以将这些测试的 e 值相乘,得到一个新的 e 值,用于测试。这在 p 值中是无法做到的。但对于 e 值,这种方法是有效的。
你也可以在实验期间查看数据和结果。如果你想停止测试,因为结果看起来不够有希望,那也是可以的。另一个可能性是,如果结果看起来有希望,可以继续进行测试。
我们还可以使用 e 值创建任何时候有效的置信区间。这是什么意思?这意味着置信区间对任何样本大小(整个实验期间)都有效。它们会比常规置信区间稍宽,但好处是你可以在任何时候相信它们。
使用 safestats 包
在帖子最后一部分,我们将更加实际。让我们计算自己的 e 值。为此,我们使用 R 包 safestats。要安装和加载它,请运行:
install.packages("safestats")
library(safestats)
我们将解决的案例是经典的:我们将比较网站的不同版本。如果一个人购买了东西,我们记录成功(1);如果一个人什么也没有购买,我们记录失败(0)。我们将旧版网站展示给 50% 的访客(A 组),将新版网站展示给另外 50%(B 组)。在这个用例中,我们将关注可能发生的不同情况。可能出现原假设为真的情况(网站之间没有差异或旧版网站更好),有时也可能是备择假设为真的情况(新版网站更好)。
创建安全测试的第一步是设定设计目标。在这个变量中,你需要为 alpha、beta 和 delta 指定值:
designObj <- designSafeTwoProportions(
na = 1,
nb = 1, # na and nb are of equal size so 1:1
alpha = 0.05, # significance level
beta = 0.2, # risk of type II error
delta = 0.05, # minimal effect we like to detect
)
designObj
在许多情况下,delta 设置为较高的数字。但对于比较具有大量流量的网站的不同版本,将其设置为较小的值是合理的,因为容易获得许多观察值。
输出如下:
Safe Test of Two Proportions Design
na±2se, nb±2se, nBlocksPlan±2se = 1±0, 1±0, 4355±180.1204
minimal difference = 0.05
alternative = twoSided
alternative restriction = none
power: 1 - beta = 0.8
parameter: Beta hyperparameters = standard, REGRET optimal
alpha = 0.05
decision rule: e-value > 1/alpha = 20
Timestamp: 2023-11-15 10:58:37 CET
Note: Optimality of hyperparameters only verified for equal group sizes (na = nb = 1)
你可以识别我们选择的值,但包还计算了 nBlocksPlan 参数。这是我们需要观察的数据点(块)的数量,它基于 delta 和 beta 参数。还需检查基于 alpha 值的决策规则。如果 e-value 大于 20(1 除以 0.05),我们拒绝原假设。
测试案例:备择假设为真
现在,让我们生成一些虚假数据:
set.seed(10)
successProbA = 0.05 # success probability for A 5%
successProbB = 0.08 # success probability for B 8%
nTotal = designObj[["nPlan"]]["nBlocksPlan"] # use the nBlocksPlan value as sample size
ya <- rbinom(n = nTotal, size = 1, prob = successProbA)
yb <- rbinom(n = nTotal, size = 1, prob = successProbB)

分布 A 和 B 的成功概率分别为 0.05 和 0.08。图像由作者提供。
现在是进行我们第一次安全测试的时候了!
safe.prop.test(ya=ya, yb=yb, designObj=designObj)
输出为:
Safe Test of Two Proportions
data: ya and yb. nObsA = 4355, nObsB = 4355
test: Beta hyperparameters = standard, REGRET optimal
e-value = 77658 > 1/alpha = 20 : TRUE
alternative hypothesis: true difference between proportions in group a and b is not equal to 0
design: the test was designed with alpha = 0.05
for experiments with na = 1, nb = 1, nBlocksPlan = 4355
to guarantee a power = 0.8 (beta = 0.2)
for minimal relevant difference = 0.05 (twoSided)
e-value 等于 77658,这意味着我们可以拒绝原假设。足够的证据来拒绝它!
可能会出现的问题是:“我们是否可以早点停止?”这就是 e-values 的一个好处。在达到计划样本量之前,允许查看数据,因此你可以随时决定停止或继续实验。我们可以绘制 e-values,例如每 50 个新样本的累计 e-values。前 40 个 e-values 图:

开始时没有反对原假设的证据,对应于低 e-values。但随着样本的增加,证据开始显现:e-values 超过了 20 的决策边界。图像由作者提供。
完整图:

我们可以确定:原假设应该被拒绝。除了最后一个 e-value。图像由作者提供。
测试案例:原假设为真
如果我们更改虚假数据,使得两个版本的成功概率相等(A 和 B 版本的成功概率均为 0.05),我们应该检测不到显著的 e- 或 p-值。版本 A 和 B 的分布看起来类似,原假设为真。这在 e-values 图中体现了出来:

无效果。图像由作者提供。
但如果我们将其与 p-values 进行比较呢?我们会多频繁地拒绝原假设,尽管实际上我们不应该?让我们测试一下。我们将实验重复 1000 次,看看在多少情况下我们拒绝了原假设,无论是对于 p-values 还是 e-values。
R 代码:
pValuesRejected <- c()
eValuesRejected <- c()
alpha <- 0.05
ealpha <- 1/alpha
# repeat the experiment 1000 times, calculate the p-value and the e-value
for (i in seq(1, 1000, by = 1)) {
# create data, use the same value of nTotal as before (4355)
set.seed(i)
ya <- rbinom(n = nTotal, size = 1, prob = 0.05)
yb <- rbinom(n = nTotal, size = 1, prob = 0.05)
# calculate the p-value, H0 rejected if it's smaller than alpha
testresultp <- prop.test(c(sum(ya), sum(yb)), n=c(nTotal, nTotal))
if (testresultp$p.value < alpha){
pValuesRejected <- c(pValuesRejected, 1)
} else{
pValuesRejected <- c(pValuesRejected, 0)
}
# calculate the e-value, H0 rejected if it's bigger than 1/alpha
testresulte <- safe.prop.test(ya=ya, yb=yb, designObj=designObj)
if (testresulte[["eValue"]] > ealpha){
eValuesRejected <- c(eValuesRejected, 1)
} else{
eValuesRejected <- c(eValuesRejected, 0)
}
}
如果我们将 pValuesRejected 和 eValuesRejected 相加,输出为:
> sum(pValuesRejected)
[1] 48
> sum(eValuesRejected)
[1] 0
p-value 在 48 个案例中显著(约 5%,这是我们期望的 alpha 为 0.05 时的结果)。另一方面,e-value 做得很好:它从未拒绝原假设。如果你之前还不确定是否使用 e-values,现在希望你相信了!
如果你对其他示例感兴趣,我可以推荐safestats 包中的小插曲。
结论
E-values 提供了一种比传统 p-values 更具吸引力的替代方案,具有多个优点。它们提供了在任何阶段继续或停止实验的灵活性。此外,它们的可组合性是一个优势,任何时候审查实验结果的自由也是一个大优点。p-values 和 e-values 的比较表明,e-values 更可靠;p-values 在没有显著差异时更容易错误地识别出显著差异。safestats R 包是实施这些稳健测试的有用工具。
我相信 e-values 的优点,并期待一个支持其实现的 Python 包的开发!😄
相关
深入探讨 P-hacking 的动机和后果
towardsdatascience.com ## 如何有效地比较机器学习解决方案?
提高将模型投入生产的机会
towardsdatascience.com ## 简化你的机器学习项目
为什么在复杂模型上花费大量时间和精力是个坏主意,应该怎么做
towardsdatascience.com
旅游是否恢复到 COVID 危机前的水平?
原文:
towardsdatascience.com/is-tourism-back-to-its-pre-covid-crisis-level-27ed32604fa6
数据分析
进行全程分析,回答一个社会经济问题并附上漂亮的图表
·发布于 Towards Data Science ·5 分钟阅读·2023 年 2 月 18 日
--

图片由 Alexandra Luniel 提供,来自 Unsplash
你在圣诞节期间休假吗?你计划在三月底休息几天吗?或者你可能更喜欢在一年中的其他时间比如夏天利用机会环游世界?
无论你是什么类型的假期旅行者, chances are that you will take some holidays this year. 现在 COVID-19 危机的最糟糕阶段(祈祷不要再恶化)已经过去,你有没有想过旅游是否已经恢复到危机前的水平?
这是我最近问自己的一个问题,我想给出答案。我发现,对专业领域以外的话题进行分析是一种很好的练习,可以保持你“在数据分析的游戏中”。无论你是初级还是更高级的数据分析师,我认为使用新的数据集进行训练总是很有帮助的。
让我带你一起走过从最初构想到最终输出的全程分析旅程吧!
步骤 1:找出一个相关的问题进行处理
在这篇文章中,我想借此机会回顾一下今年的旅行,并调查旅游是否已经恢复到危机前的水平。这可以从全球范围来看,也可以缩小到特定的地理区域。由于我住在欧洲,我特别感兴趣的是分析 COVID 危机对欧洲旅游的影响。
我思考的另一个方面是:我如何衡量“旅游”?这一概念包含了各种领域,如交通(飞机、火车、汽车等)、旅游景点(博物馆、活动等)、住宿(酒店、露营地、家庭住宿等)。为了更精确地解决我在这里要分析的问题,让我们专注于住宿。
步骤 2:获取数据以进行分析
在这种情况下,我没有公司数据或预定义的数据集可用,所以让我们浏览互联网以寻找与我的主题相关的开放数据。
Eurostat 提供了可以在线可视化和以多种格式下载的开放数据集。Eurostat 的数据可以免费使用,只要提到 Eurostat 是数据来源即可。在这里,我将使用这个关于在旅游住宿设施中度过的夜晚的数据集:TOUR_OCC_NIM,其来源是 Eurostat。

原始数据集(由 Eurostat 提供,查看版权声明)
步骤 3:绘制最终输出
为了回答我的初始问题,我想将欧洲的全球旅游发展与各国的旅游发展进行比较。通过这样做,我应该能够看到旅游是否、何时以及在哪个国家恢复到危机前的水平。
为此,我需要两个图表:一个显示每月在旅游住宿设施中度过的总夜晚数,第二个显示按国家划分的相同指标。

期望输出(使用 Excalidraw 绘制)
步骤 4:转换数据
手头有原始数据集(步骤 2)并且心中有目标输出(步骤 3),我现在已经准备好进行分析。你注意到实际上进入“数据分析模式”是在步骤 4,而不是更早吗?这是因为数据分析更多的是关于你为何需要进行分析,而不是实际进行分析。
要绘制第一个图表,我必须按月对值(在旅游住宿设施中度过的夜晚数量)进行分组。对于第二个图表,我必须按月和国家进行分组,如下所示:
SELECT
TIME_PERIOD AS month,
geo AS country,
SUM(OBS_VALUE) AS nb_nights_spent,
FROM my_dataset.raw_data
GROUP BY 1,2
ORDER BY 1,2
步骤 5:可视化数据
代码片段的输出是数据表,而不是(还不是)图表。要将这些表格输出转换为漂亮的图表,让我们使用数据可视化工具。在这里,我使用了第 4 步的 Google BigQuery 和第 5 步的 Looker Studio(以前称为 DataStudio)的组合。
由于我们之前绘制了目标输出,我们已经知道最终图表应该是什么样的。这节省了很多时间,因为我只需配置工具,将正确的维度放在正确的位置。这将给我这些图表:

输出图表(在 Looker Studio 中构建)
步骤 6:提取见解
那么现在该怎么办?制作图表很棒,但没有人脑来解释这些输出结果,这些图表就相当无用。让我们回到最初的问题:根据住宿数据,欧洲的旅游是否已经恢复到危机前的水平? 我们希望尽可能清晰地回答这个问题。
如果我们看全球的发展趋势,欧洲旅游似乎正回到恢复到危机前水平的轨道上。虽然 2022 年的夏季季节未能完全达到 2019 年的水平(2022 年 7 月至 8 月比 2019 年 7 月至 8 月减少了 15%),但与 2020 年和 2021 年相比,趋势表现出积极的变化。下一年 2023 年夏季结束时进行相同的分析将是很有趣的。
如果我们仔细查看每个国家的发展情况,这个普遍的评论并不总是适用。例如,2022 年西班牙的值与 2019 年非常接近(仅 7 月至 8 月减少了 4%),而其他国家 2022 年远低于 2019 年(捷克为-26%)。
结论
在解读结果时,另一个重要的因素是偏差。首先,数据收集方式可能存在偏差:当我们比较不同国家时,每个国家可能会采用不同的方法来计算在旅游住宿机构过夜的数量。
其次,仅基于一种指标分析就得出旅游几乎恢复到危机前水平的结论是片面的。要准确评估欧洲旅游的状态,应当分析多个指标,比较这些分析结果,并整合从中获得的经验教训。
简而言之:进行分析时要遵循这 6 个步骤,并注意偏差。
如果你想获取这篇文章的视觉总结,你可以在这里免费下载 <<
您的 LLM 应用程序准备好公开了吗?
将基于 LLM 的应用程序投入生产时的关键关注点
·
关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 6 月 20 日
--
大型语言模型(LLMs)正成为现代自然语言处理(NLP)应用的基本组成部分,并且在许多方面取代了各种更为专业的工具,例如命名实体识别模型、问答模型和文本分类器。因此,很难想象一个不以某种方式使用 LLM 的 NLP 产品。虽然 LLM 带来了诸如个性化和创意对话生成等诸多好处,但在将这些模型集成到服务最终用户的软件产品中时,了解其陷阱以及如何应对这些问题是很重要的。事实证明,监控能够很好地解决这些挑战,并且是任何与 LLM 合作的企业工具箱中的一个重要部分。
数据、隐私和提示注入

图片由 TheDigitalArtist 提供,来源于 Pixabay
数据与隐私
隐私和数据使用是现代消费者的主要关注点之一,尤其是在剑桥分析等著名数据共享丑闻的影响下,消费者越来越不愿使用那些可能危及个人隐私的服务和产品。虽然 LLM(大型语言模型)为用户提供了极高的个性化,但了解它们所带来的风险也很重要。与所有机器学习模型一样,LLM 容易受到针对性的攻击,这些攻击旨在揭示训练数据,由于其生成性质,LLM 尤其容易受到风险,甚至在进行自由形式生成时可能会意外泄露数据。例如,在2020 年博客文章中,谷歌大脑的研究科学家尼古拉斯·卡尔尼讨论了如何通过提示 LLM(如 GPT)来泄露个人身份信息,如姓名、地址和电子邮件,这些信息包含在模型的训练数据中。这表明,针对客户数据对 LLM 进行微调的企业可能会引发类似的隐私风险。同样,来自微软研究人员的论文也证实了这些说法,并建议了一些具体的缓解策略,这些策略利用差分隐私技术来训练 LLM,同时减少数据泄露的担忧。不幸的是,由于许多企业使用的 LLM API 不允许他们控制微调过程,因此无法利用这些技术。这些公司的解决方案在于插入一个监控步骤,在将结果返回给最终用户之前验证和限制模型的输出。通过这种方式,企业可以在隐私违规实际发生之前识别并标记潜在的训练数据泄露实例。例如,监控工具可以应用诸如命名实体识别和正则表达式过滤等技术来识别模型生成的姓名、地址、电子邮件及其他敏感信息,防止这些信息落入不良分子之手。这对于在隐私受限领域如医疗或金融工作的组织尤其重要,这些领域涉及 HIPAA 和 FTC/FDIC 等严格法规。即使是仅仅在国际上运营的企业也面临违反复杂的地域特定法规,如欧盟的 GDPR。
提示注入
提示注入(Prompt injection)指的是设计 LLM 提示的过程,通常是恶意的,通过某种方式“欺骗”或混淆系统,从而产生有害的输出。例如,最近的一篇文章展示了精心设计的提示注入攻击如何使得 OpenAI 的 GPT-4 模型被颠覆,从而提供事实错误的信息甚至推广阴谋论。可以想象到更多邪恶的场景,例如用户提示 LLM 提供如何制造炸弹的建议、如何最佳自杀的细节,或生成可用于感染其他计算机的代码。提示注入攻击的脆弱性是不幸的副作用,由于 LLM 的训练方式,很难在前端做任何事情来防止所有可能的提示注入攻击。即使是最强大和最新的 LLM,如 OpenAI 的 ChatGPT——专门为安全而调整——也证明了对提示注入的脆弱性。
由于提示注入可能表现出的方式千差万别,因此几乎不可能防范所有可能性。因此,监控 LLM 生成的输出至关重要,因为它提供了一种识别和标记虚假信息以及明显有害生成的机制。监控可以使用简单的 NLP 启发式方法或额外的 ML 分类器来标记包含有害内容的模型响应,并在返回给用户之前拦截它们。类似地,对提示本身的监控可以在提示被传递给模型之前捕捉到一些有害的提示。
幻觉
幻觉(hallucination)指的是 LLM 偶尔“编造”那些实际上不符合现实的输出的倾向。提示注入和幻觉可以表现为同一枚硬币的两面,尽管提示注入中的虚假生成是用户的故意行为,而幻觉则是 LLM 训练目标的无意副作用。由于 LLM 被训练为在每个时间步骤中预测序列中下一个最可能的词,因此它们能够生成高度逼真的文本。因此,幻觉是最可能的情况并不总是真的简单结果。

图片来源:Matheus Bertelli via Pexels
最新一代的大型语言模型(LLM),例如 GPT-3 和 GPT-4,采用了一种叫做人类反馈强化学习(RLHF)的算法进行优化,以匹配人类对什么构成良好响应的主观判断。虽然这使得 LLM 能够达到更高的对话流畅度,但有时也会导致它们在回应时表现得过于自信。例如,询问 ChatGPT 一个问题时,它可能会自信地给出一个初看似乎合理的回答,但经过进一步检查,结果却是客观上错误的。赋予 LLM 提供不确定性量化的能力仍然是一个活跃的研究问题,并且不太可能很快得到解决。因此,LLM 基于的产品的开发者应该考虑监控和分析输出,以试图检测幻觉,并提供比 LLM 模型开箱即用的更细致的回应。这在 LLM 的输出可能指导某些下游过程的情况下尤为重要。例如,如果一个 LLM 聊天机器人在帮助用户提供产品推荐并协助在零售商网站上下订单时,应该实施监控程序,以确保模型不会建议购买在该零售商网站上实际上并未销售的产品。
不受控制的成本
由于 LLM 通过 API 正变得越来越商品化,企业在将这些模型集成到其产品中时,制定防止成本无限增加的计划非常重要。如果没有保护措施,用户生成数千个 API 调用并发出数千个令牌的提示(例如,用户将一份极长的文档粘贴到输入中,并要求 LLM 分析它)是很容易的。由于 LLM API 通常根据调用次数和令牌计数(包括提示和模型的回应)进行计量,因此成本迅速失控并不困难。因此,企业在制定定价结构时需要小心,以抵消这些成本。此外,企业应该有监控程序,以了解使用激增如何影响成本,并通过施加使用上限或采取其他补救措施来缓解这些激增。
结论
每个将大型语言模型(LLMs)应用于其产品的企业,都应确保将监控功能整合到其系统中,以避免并解决 LLMs 的众多陷阱。此外,使用的监控解决方案应专门针对 LLMs 应用,允许用户识别潜在的隐私侵犯,防止和/或修复提示注入,标记幻觉,并诊断不断上升的成本。最佳的监控解决方案将解决所有这些问题,并为企业提供一个框架,以确保其基于 LLM 的应用准备好对公众发布。通过预约演示来查看 Mona 的全面监控能力,确保你的 LLM 应用经过充分优化并按预期运行。
依赖 GridSearchCV 的最佳模型是一个错误
原文:
towardsdatascience.com/its-a-mistake-to-trust-the-best-model-of-a-gridsearchcv-536a73e835ad
通过四个例子解释了“最佳模型”实际上并不是最佳模型的情况
·发布于Towards Data Science ·6 分钟阅读·2023 年 1 月 4 日
--

图片来源:Choong Deng Xiang 在Unsplash
Scikit-learn 的GridSearchCV是一个常用的工具,用于优化机器学习模型的超参数。不幸的是,并不是每个人都能彻底分析它的输出,很多人只是使用GridSearchCV的最佳估计器。这意味着在很多情况下,你可能没有使用到真正的最佳估计器。让我们首先确定如何运行网格搜索,并检测它选择哪个估计器作为最佳。
一个非常基础的GridSearchCV可能看起来是这样的:
import pandas as pd
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.tree import DecisionTreeRegressor
df = pd.read_csv('data.csv')
X_train, X_test, y_train, y_test = train_test_split(df.drop('target'),
df['target'],
random_state=42)
model = DecisionTreeRegressor()
gridsearch = GridSearchCV(model,
param_grid={'max_depth': [None, 2, 50]},
scoring='mean_absolute_error')
gridsearch.fit(X_train, y_train)
gridsearch.train(X_train, y_train)
# get the results. This code outputs the tables we'll see in the article
results = pd.DataFrame(gridsearch.cv_results_)
# what many data scientists do, while they shouldn't without analyzing first
Y_pred = gridsearch.best_estimator_.predict(X_test)
在上面代码的底部,我提醒不要仅仅使用best_estimator_。因此,了解scikit-learn如何选择GridSearchCV的最佳估计器非常重要。我们只需要查看由cv_results_生成的results数据框中的一列,以确定为什么一个模型被选择为最佳:mean_test_score:

(作者提供的表格)
mean_test_score显示了该模型在测试集上的平均得分;例如,默认参数cv=5表示mean_test_score是模型在 5 个测试集上的测试得分的平均值。如果将cv改为 4,你会得到如下的训练-测试数据划分:

使用 cv=4 的交叉验证(图由作者提供)
默认情况下,GridSearchCV 选择 mean_test_score 最高的模型,并将其分配一个 rank_test_score 为 1. 这也意味着当你通过 gs.best_estimator_ 访问 GridSearchCV 的最佳估算器时,你将使用 rank_test_score 为 1 的模型。然而,有许多情况下 rank_test_score 为 1 的模型并不一定是最佳模型。让我们通过四个示例来说明‘最佳’模型并不是最优模型,并看看我们如何确定实际的最佳模型。
示例 #1:测试分数的标准差

如果我们查看上面的表格,我们可以看到‘最佳’模型的 mean_test_score 为 -100,std_test_score 为 21,这些分数以 均方误差 表示。因为这是一个误差分数,所以值越接近零,得分越好。‘第二最佳’模型的 mean_test_score 为 -102,std_test_score 为 5. std_test_score 代表模型在测试集上的分数的标准差,这是一个绝对值,因此,离 0 越近,模型的表现越一致。尽管 #1 模型的均值稍好,但其标准差要大得多。这意味着模型的性能不够一致和可靠,并且通常这并不是理想的。这就是为什么在这个例子中我会选择模型 #2 而不是模型 #1,并且在大多数使用情况下你也应该这样做。
示例 #2:训练分数
现在让我们考虑两个在测试集上表现几乎相同的模型:

两个看似相同的决策树(作者提供的图片)
尽管这些模型的性能看起来几乎相同,但它们并不是相同的。为了检测这一点,我们必须将 GridSearchCV 的 return_train_score 参数从默认值 False 设置为 True:
from sklearn.model_selection import GridSearchCV
from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier()
param_grid = {...}
gridsearch = GridSearchCV(model,
param_grid,
scoring="mean_absolute_error",
return_train_score=True)
现在如果我们在拟合和训练后运行 gridsearch.cv_results_,我们会注意到添加了多个列,其中一个列显示如下:mean_train_score:

带有训练分数的 cv_results_(作者提供的图片)
我们现在可以注意到,尽管两个模型在测试集上的表现似乎相同,但第二个模型在训练集上的表现实际上明显优于第一个模型。当然,这在某些情况下可能意味着过拟合,但在这里,均值训练分数与均值测试分数更一致,因此似乎没有暗示过拟合。一般来说,当两个模型在测试集上的表现相似时,考虑到训练分数时表现更一致的模型应该被优先选择。
示例 #3:模型复杂度
让我们考虑以下网格搜索结果:

cv_results_(作者提供的图片)
上述两个模型的测试和训练分数几乎相同。然而,在确认第一个模型是最佳模型之前,还有更多重要的信息需要考虑。在最后一列中,我们看到 param_tree__max_depth,它显示了每个模型决策树的树深度。‘最佳’模型的决策树深度为 50,而‘第二最佳’决策树的深度仅为 2。在这种情况下,我们可以选择第二个模型作为最佳模型,因为这个决策树更容易解释。例如,我们可以使用 [sklearn.tree.plot_tree](https://scikit-learn.org/stable/modules/generated/sklearn.tree.plot_tree.html) 绘制决策树,并查看一个非常简单且易于解释的树。一个深度为 50 的树几乎不可读且难以解释。作为经验法则,当两个模型在测试集和训练集上的表现相当时,应优先选择更简单的模型。
示例 #4:模型速度性能
我想展示的最后一个例子是关于速度性能的。假设我们有与示例 #3 相同的网格搜索结果,但添加了一个新列:mean_score_time。

现在,另一个原因出现了,我们可能会更喜欢第二个模型而不是第一个模型。第一个模型的运行时间是第二个模型的 6 倍以上,这可能是由于其额外的复杂性。当然,由于 mean_score_time 表示模型预测验证集的平均时间,这种差异似乎可以忽略不计。然而,想象一下一个需要基于每台机器数百个传感器预测机器故障的模型。对于这样的模型,如果需要在大量数据上进行实时预测,那么 n 个观测值上的 0.02 秒与 n 个观测值上的 0.003 秒之间的差异可能会产生显著影响。
结论
在这篇文章中,我们看到了四个示例,展示了为什么你不应该盲目相信 scikit-learn 的 GridSearchCV 的最佳估计器。我们不仅要依赖平均测试分数,还应该考虑交叉验证结果中的其他列,以确定哪个模型最好,尤其是在顶级模型的测试分数相似时。
如果你想了解更多关于机器学习和/或网格搜索的内容,请务必阅读这些相关文章:
使用 EstimatorSwitch 对任何机器学习管道步骤进行网格搜索。
通过这些参数显著提高网格搜索结果 一款备受期待的时间序列交叉验证器终于到来 [## 一款备受期待的时间序列交叉验证器终于到来
不均匀分布的时间序列数据不再是交叉验证的问题。
是时候提升数据分析师的角色了
意见
数据分析师应成为依靠数据的可信顾问
·
关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 1 月 2 日
--
注意:大多数数据和分析角色定义模糊。本文重点讨论数据分析师角色,通常被描述为 BI 分析师。根据组织的不同,业务分析师和数据科学家的角色也有些重叠。

图片由 Freepik 提供
数据分析师的角色在过去几年中迅速演变。随着数据复杂性和业务期望的爆炸式增长,他们面临着许多挑战。
在早期和分析成熟度较低的公司中,数据分析师是一个通才角色。它负责几乎整个数据与分析马拉松。从准备和可视化,到分析和见解沟通。
数据分析领域的增长和创新改变了数据驱动的含义以及对数据分析师角色的要求。这导致了对专业角色的需求增加,并对最后一公里给予了强烈关注。这样,数据分析师更多地关注于分析、见解和决策。

数据分析师角色的演变 —— 作者图片
与此相关,Cassie Kozyrkov 将数据分析师的关键角色定义为“查找事实并为你提供灵感”,并通过指出“分析游戏完全是关于优化每分钟的灵感”来强调工作的高要求/节奏。
我通常将这一概念定义为“行动见解的速度”。
行动见解的速度在提升分析价值和帮助数据实现承诺方面起着关键作用。因此,它应该被定义为数据分析师的北极星指标。(了解更多关于加速见解速度的内容,请访问这里.)
然而,由于各种挑战,大多数公司还远未达到这一点。让我们深入探讨是什么阻碍了数据分析师的进步。
数据分析师的挑战
数据复杂性和业务期望都在上升。同时,传统的 BI 工具在过去 20 年中没有太大进展,造成了许多数据团队的差距。以下是今天的数据分析师面临的主要挑战:
定义不清的角色
在数据与分析领域普遍存在角色划分的问题。这不仅限于数据分析师。它的角色与数据科学家和 BI 分析师(有时也包括业务分析师)有显著的重叠。

数据角色的复杂格局 —— 作者图片
如果你查看大多数职位描述,它们会强调数据分析师的使命是提供见解。这本身非常模糊——这个角色是成为自助服务的推动者(即,回应业务请求和构建仪表板),还是提供建议?组织对数据分析师的角色有不同的愿景,从自助服务推动者(即,回应业务请求和构建仪表板)到数据驱动的可信业务顾问。
价值误解
数据分析师常被视为“二等公民”,感到在“技术”专长上被数据科学对手甩在了后面。数据科学角色与更高的薪酬和地位相关,这加剧了这一差距。
大部分时间用于应急处理和低附加值的任务
数据分析师花费大部分时间在低附加值的任务上,通常以非常被动的方式进行(例如,修复损坏的仪表板)。许多人主要作为“仪表板/报告工厂”运作,与业务脱节。
现状
这些挑战使数据与分析团队停滞不前,因为分析师产生了大量的输出,但业务结果却很少。在实践中,大多数数据分析师将大部分时间花在低附加值的任务上,主要关注描述性分析,仅呈现发生了什么。
以下是我所见的三种最常见的数据分析师状态的详细说明:

数据分析师角色的状态 — 作者提供的图像
状态 I — 数据整理员
你可以将“数据整理员”视为陷入数据分析师角色初始通才定义的数据分析师。他/她负责处理与数据相关的所有事务,包括从不同资源中提取数据、生成报告、清理数据和准备仪表板。因此,他们几乎没有时间专注于挖掘洞察,他们提供的价值被稀释了。
结果: 团队停留在“什么”上,只能识别业务表现中的变化,却无法回答“为什么”。分析的价值不明确,导致数据文化薄弱。
状态 II — 仪表板构建者
尽管“仪表板构建者”状态比“数据整理员”状态稍微发展了一些,但这些分析师仍未专注于可操作的洞察。他们的大部分时间用于可视化、构建和管理仪表板。他们被动地寻找洞察,以回答业务团队的问题或解决紧急情况。
结果: 分析师的价值未被充分发掘。决策过程存在显著偏差,阻碍了真实洞察的发现,同时分析师团队被临时请求所压倒。
状态 III — 分析师
这是一个成熟的状态,其中分析师的角色定义更为明确,公司数据文化也已较为强大。分析师的重点在于提供洞察,但他们无法跟上业务的节奏来发现这些洞察。他们通常还需要处理大量非分析师的工作。因此,需要增强分析工作流,以释放他们的时间,让他们主要集中在分析的最后阶段。
结果: 在重复任务上花费过多时间(例如,在仪表板上切割和分类)会阻碍行动洞察和业务影响的速度,导致机会的错失。
未来的数据分析师 — 业务顾问
与一些最具数据前瞻性的公司合作时,我见证了数据分析师能够带来的业务影响。这些进展从根本上改变了数据分析师在公司中的职能。他们参加每周的业务会议,呈现发生了什么,为什么会发生,以及所以呢,分享主动的业务解决方案。
数据分析师的主要目标是快速浏览庞大的数据集,与业务利益相关者联系,并挖掘潜在洞察。速度是他们的最高美德。
结果:公司能够把握自身动态,并以业务速度发现真实洞察。这激发了决策者为数据科学家选择最有价值的任务。我已识别出使数据分析师成为可信赖商业顾问的三个主要特征:
-
合作伙伴关系 — 最优秀的团队将数据分析师嵌入到业务职能中,并与业务利益相关者非常紧密地合作。
-
主动性 — 数据分析师通过在诊断分析上花费大量时间来分享主动的业务建议,而不仅仅是展示/描述发生了什么。他们会参加日常/每周/每月的业务回顾,解释关键指标变化的原因及潜在建议。
-
领域/业务专业知识 — 理解业务和领域(例如,产品),以便将各方面连接起来并推动可操作的洞察。
所需条件
这种转变需要通过增强技术来实现,同时需要文化和人员的变革。这些是主要要求:
-
增强 — 这是推动这一转变的主要因素。以业务速度提供可操作的洞察需要对传统 BI 工作流程进行增强,以消除速度与全面性的权衡 (了解更多)。此外,为了成为可信赖的顾问,分析师不应花费大量时间在仪表盘上切割和处理数据。相反,他们应该专注于寻找真实洞察,利用 ML 进行统计测试,指引业务团队查找相关信息,并与他们紧密合作。这是征服分析最后一公里的唯一途径:提炼洞察,正确传达,并推动行动。
-
业务和领域专业知识 — 数据分析师需要对所工作的业务和领域有很好的理解。因此,分析师在一个业务职能(例如,市场分析师)上专业化,并嵌入到相应职能(例如,市场团队)中越来越常见(阅读更多关于领域专业知识的重要性这篇文章由 Randy Au 撰写)。
-
软技能 — 要成为可信赖的顾问,数据分析师需要超越 SQL 语言等技术技能。商业头脑、沟通能力、倾听技能、好奇心、同理心和讲故事能力对于成为战略合作伙伴、发展领域专业知识以及推动建议和行动至关重要。
-
数据文化——合适的数据文化是关键,其中业务利益相关者将数据视为关键资产,并愿意与数据分析师紧密合作。推动这种文化需要数据团队通过关注业务价值来赢得对方的信任。
如何弥合差距。
这种演变需要心态、技能和文化的转变,这些都需要合适的流程和工具来支持。以下是几个具体的最佳实践:
工具——增强分析作为一种推动者。
- 增强现有工作流程,使分析师能够以业务速度获取详细洞察,并有足够的时间处理分析马拉松的最后一公里。
人员——发展专业知识,使分析师成为值得信赖的顾问。
-
将你的分析师嵌入业务职能中。
-
促进不同分析师之间的知识交流(包括跨功能)。
-
为技术和非技术事项(例如,如何进行根本原因分析,如何呈现洞察)制定最佳实践。
文化——发展你的数据文化以最大化价值。
-
促进一个以业务影响为中心的文化——开发一个衡量价值的框架,推广成功案例等。
-
通过提供背景和创建对齐,确保分析师发展领域和业务专长。
过程——支持整个转型。
-
开发一套流程,以促进业务和数据团队之间的知识转移(例如,数据研讨会、业务深入探讨)。
-
设立每周或每月的业务审查绩效,并让你的分析师参与其中。
提升你的数据分析师以最大化业务影响。
现在是数据与分析团队征服分析马拉松最后一公里的时候了。公司已经在数据收集、准备和可视化上投入了大量资源。现在是时候专注于终点线——数据分析、洞察沟通和更好的决策。提升你的数据分析师来征服这一最后一公里,并实现预期(和投资回报)。
—
想法?请联系 João Sousa,在 Kausa 担任增长总监。请关注更多关于如何掌握诊断分析并提高数据和分析价值的帖子。
终于是时候告别 “git checkout” 了
原文:
towardsdatascience.com/its-finally-time-to-say-goodbye-to-git-checkout-fe95182c6100
“Git switch”和“git restore”将会长期存在
·发表于 Towards Data Science ·4 分钟阅读·2023 年 5 月 1 日
--

Git 是开发人员最广泛使用的版本控制系统。Git 中最常用的命令之一是 git checkout,它允许用户在分支之间切换并将文件恢复到先前的状态。
然而,在 2019 年,随着 Git 2.23 的发布,两个新命令被引入以取代 git checkout,以实现更直观和简化的工作流:git switch 和 git restore。尽管它们发布已有近四年,开发人员仍然难以放弃使用 git checkout(我猜老习惯很难改)。
在这篇文章中,我们将探讨 Git 团队通过引入 git switch 和 git restore 解决了 git checkout 的哪些缺点,以及为什么这应该导致不再使用 git checkout。
git checkout 的问题
git checkout 是一个具有两个核心功能的命令:
-
在分支之间切换
-
将文件恢复到先前状态
然而,这两个功能在命令语法中没有明确区分,这可能导致混淆和错误。例如,如果你不小心输入 git checkout <commit> 而不是 git checkout <branch>,你将进入所谓的“分离的 HEAD” 状态,这意味着你所做的任何新提交将不会与任何分支相关联。当你在分离的 HEAD 中做了更改后切换到新分支时,这些更改将会丢失,且无法恢复。
引入 git switch 和 git restore
为了解决git checkout的一些问题,Git 团队在 Git 版本 2.23 中引入了两个新命令:git switch和git restore。这两个命令将git checkout之前提到的两个核心功能拆分为两个独立的命令。
git switch仅用于在分支间切换。语法很简单:git switch <branch>。如果你尝试使用git switch切换到一个提交,Git 会抛出一个错误,而不是将你置于脱离的 HEAD 状态。因此,你不太可能进行与任何分支无关的提交。
另一方面,git restore只能用于将文件恢复到之前的状态。它的语法也很简单:git restore <file>。如果你不小心尝试切换到一个分支而不是文件,Git 将会抛出一个错误。
使用git switch和git restore的好处
git switch和git restore的一个关键好处是提高了安全性。由于两个功能被分离成两个不同的命令,你无意中应用错误功能的机会减少了。这使得因为丢失工作而发生错误和挫败感的可能性降低了。
git switch和git restore的另一个好处是语法更直接,因为你不需要记住git checkout的不同用法。即使在使用 Git 多年之后,许多开发者仍然无法完全理解git checkout的工作原理和语法。
此外,git switch和git restore的命令更直观且准确。当你想创建一个新分支时,可以使用git switch -c <branch>,其中-c标志表示创建。在使用checkout时,你必须使用git checkout -b <branch>,其中-b标志表示分支。然而,你也可以使用git checkout <branch>在分支间切换,因此-b标志仅用于创建新分支,而一个名为branch的标志并不能很好地表明这是关于创建新分支而不是切换到一个已有的分支。因此,git switch和git restore的命令更直观,因此应当被优先使用。
总而言之
虽然git checkout多年来一直是 Git 用户的首选命令,但随着git switch和git restore的引入,已经有了更好的替代方案。将git checkout的两个核心功能拆分成两个独立的命令后,Git 使得在分支间切换和恢复文件变得更加容易,而不会不小心使用错误的命令。如果你仍在使用git checkout而不是git switch和git restore,现在正是切换的最佳时机!
资源
Git 易于学习,体积小,性能极快。它超越了像 Subversion 这样的 SCM 工具…
Git 2.23 发布,新增两个命令 'git switch' 和 'git restore',还有更多内容!
上周,Git 团队发布了包含实验性命令、向后兼容等更多功能的版本…
Hub PacktPub [## git switch 和 git checkout 有什么区别
switch 命令确实与 checkout 做了相同的事情,但仅限于切换分支的用法。在…
这不仅仅关乎得分
在模型选择过程中,你应考虑的其他标准
·
关注 发表在Towards Data Science ·8 分钟阅读·2023 年 3 月 27 日
--
模型选择和甜甜圈选择之间的区别:在模型选择中,你只能选择一个。照片由ELISA KERSCHBAUMER提供,拍摄于Unsplash。
作为数据科学家或机器学习工程师,你的大部分时间都在通过创建新特征、比较不同类型的模型、尝试新的模型架构等方式来提升模型的性能。最终,决定性的是测试集上的得分,所以在选择模型时,你会将重点放在得分上。然而,尽管模型性能非常重要,但还有其他一些次要标准你不应忽视。
如果你的 MLOps 部门无法托管一个几乎完美评分的模型,你能得到什么?如果预测准确但获取时间极长,用户会有什么感觉?如果你的模型在你训练它的数据上表现良好,但随着时间的推移变得越来越差且无法适应新数据,你该怎么办?
这些是选择模型或算法时需要考虑的一些因素。在本文中,我想引起你对一些经常被忽视的次要标准的关注,尽管这些标准对用户体验有着巨大的影响。
1) 推理速度

图片由 George Brynzan 提供,来源于 Unsplash
根据你的模型使用情况,速度可能很重要。如果模型的预测在后台异步进行,花费几秒钟多一点也无所谓,但在用户主动等待预测的情况下,每一秒钟都可能极大地影响用户体验。在机器学习中,我们经常考虑训练模型所需的时间,但推理的计算时间常常被忽视。
在许多连续执行的简单任务中,如果能更快地获得预测,纠正多出的一些错误可能是值得的。假设你的工作是标记呈现给你的图像中的所有企鹅。幸运的是,你不必从零开始,因为有一种 AI 已经检测到一些企鹅并画出边框,所以你需要验证它们,添加缺失的边框,或修正不准确的部分。在这种情况下,速度很重要。虽然准确性得分少了几分,但你需要更频繁地纠正 AI 的预测,但这本来就是你的工作。然而,如果你需要为每张图像等待几秒钟才能获得 AI 的预测,那将会大大拖慢你的速度。
另一方面,当然并不总是值得为了速度而牺牲准确性。如果你是一名医生,得到一种能够检测 X 射线图像中恶性部位的 AI 支持,那么即使预测多花几秒钟也无关紧要。你一天只需要预测几次,这时准确性是最重要的方面。作为患者,你肯定不希望因为速度快而得到错误的诊断,对吧?
2) 重新训练

图片由 Danil Shostak 提供,来源于 Unsplash
大多数情况下,你的模型评分仅仅是特定静态测试集上的瞬时快照。尽管这个测试集上的评分在一定程度上可以推断模型在真实数据上的表现,但你应该始终意识到,外部世界可能会随着时间变化。在这种情况下,你需要能够作出相应的反应。即使你能够收集新数据,重新训练深度神经网络也可能非常耗时和资源。在某些情况下,如果你每晚或每周重新训练一次,更简单的模型可能是更好的选择。
重新训练或其他方式调整模型行为在你处理的数据不完整或非常异质时变得重要,因为你无法达到模型在新数据上良好泛化的水平。如果你创建一个 AI 来以给定作者的风格写故事,这可能对它知道的作者效果很好。然而,如果它从未读过简·奥斯汀的任何故事,它怎么可能以她的风格写小说呢?随着新作者的出现,你可能需要不时重新训练模型。模型参数越多,这个过程就越困难。
甚至有些情况需要训练多个模型,例如,每个客户一个模型。当数据如此多样时,用客户 X 的数据进行训练可能会损害客户 Y 的模型表现。如果你的 AI 为公司制定商业计划,你可能会发现这些公司的数据差异非常大。一些公司的收入受到全球经济形势的严重影响,而另一些公司则相对稳定。一些公司已经在市场上存在了很长时间,希望实现可持续发展,而另一些公司则非常年轻,目标是快速增长。由于数据如此多样,为新客户接入可能需要在其公司数据上重新训练或微调模型。也可能因为法律原因,你不能使用客户 X 的数据为客户 Y 制定预测,因此你需要为每个客户训练一个新模型。模型训练所需时间越长,每个新客户所花费的时间和金钱就越多。
3) 可部署性

照片由 Ian Taylor 提供,来源于 Unsplash
如果一个模型不能被任何人使用,那么再好的模型也没有意义。有多种方式可以将模型交付给最终用户,从在云服务器上托管服务到在用户设备上安装程序。选择哪种方式取决于你的使用场景,因此你决定的模型需要适应所需的部署方式。
更大的模型需要更长时间来加载,并且需要更多的资源。一个需要解压神经网络及相关包的 Docker 容器,可能轻松达到几个 GB 的大小。在云提供商上,多出几个 GB 需要花费一些钱,你可以决定是否值得,但如果用户需要下载模型,问题可能会变得更大。在现代笔记本电脑上,安装几个 GB 可能不是问题,但如果你的主要目标群体生活在发展中国家,并使用几年前的手机,内存和计算能力可能会成为问题。
如果你训练一个 AI 来检测主要用于非洲或南美洲的作物中的害虫或疾病,你应该预期你的用户没有稳定的互联网连接。因此,应用程序应该能够离线运行,这意味着用户需要下载模型,并在可能已经有几年历史且资源有限的智能手机上计算预测。在这种情况下,即使深度神经网络有更好的评分,更小、更简单的模型可能是更好的选择。
如上所述,甚至有需要托管多个模型的情况(例如,每个客户一个)。在这种情况下,模型的大小变得尤为关键。部署一个神经网络可能是可行的,但你是否能托管一百个不同的模型?如果不能,小型模型可能是更好的选择。
4) 可解释性

照片由Crissy Jarvis提供,来自Unsplash
出于充分的理由,可解释的人工智能在当前研究中变得越来越重要。大多数时候,我们只是接受机器学习方法通常是黑箱的这一现实,即模型给出预测而无法解释模型为何以某种方式行为。然而,许多使用案例中,这种可解释性有助于获得用户的信任或提升他们的体验。对于医生来说,如果人工智能做出一个令人惊讶的诊断而没有解释,那么很难决定如何继续。而金融投资者可能不会遵循模型的预测,如果他们无法理解其中的逻辑。健康或金融相关任务中的关键或敏感决策,可能需要比其他较不关键任务更多的解释。如果 AI 无法找到你手机上的图像,你可能不会在意原因。在这种情况下,更好的模型性能将大大提升你的体验。
关于可解释性,简单的模型通常比复杂的模型更容易理解。在线性回归中,权重有明确的含义,而在深度神经网络中,它们则完全形成了一个黑箱。然而,也有越来越多的努力使深度学习模型变得可理解,ELI5就是其中一个最著名的例子。对用户而言,模型或算法是否看起来更值得信任可能会更常被使用,即使它的分数低于竞争模型。你不会仅仅因为一个 AI 告诉你这么做而不解释原因就投资钱,对吧?
总结
我刚刚展示了一些可能与模型选择相关的标准,尽管它们常常被忽视。当然,这些标准本身并不是最重要的标准,模型的分数也不是。最终,你必须根据你的使用案例权衡所有标准。与其仅仅看分数,你可以问问自己是否也适当地考虑了其他标准:
-
模型的预测速度是否仍然足够快?我的用户会主动等待模型预测吗?如果会,推理时间是否合适?
-
我是否能够托管这个模型?我是否有足够的资源?分数的小幅提升是否真的值得拥有一个更大的模型?
-
我是否期望我的数据随时间变化?如果是,我能否定期重新训练我的模型?
-
我是否仍然理解我的模型?使用黑箱模型是否可以,或者在我的情况下可解释性是否比准确性更重要?
还有许多其他标准,对于不同的使用场景可能很重要,所以在选择模型时要开始考虑这些标准。评分并不是全部!
喜欢这篇文章吗? 关注我 以便接收我未来的帖子。
在 Docker 中运行 Jaffle Shop dbt 项目
原文:
towardsdatascience.com/jaffle-shop-dbt-docker-93a9b14532a4
流行的 Jaffle Shop dbt 项目的容器化版本
·发布于 Towards Data Science ·8 分钟阅读·2023 年 4 月 28 日
--

Ryan Howerter的照片,来自Unsplash
如果你是数据构建工具(dbt)的新手,你可能已经遇到过所谓的 Jaffle Shop,这是一个用于测试的项目。
jaffle_shop是一个虚构的电子商务商店。这个 dbt 项目将应用数据库中的原始数据转换成一个适合分析的客户和订单模型。
我观察到的 Jaffle Shop 项目的一个根本问题是,它期望用户(可能是 dbt 的新手)配置并托管一个本地数据库,以便 dbt 模型能够生成。
在本教程中,我将演示如何使用 Docker 创建项目的容器化版本。这将使我们能够部署一个 Postgres 实例,并将 dbt 项目配置为从该数据库读取和写入。我还会提供一个我创建的 GitHub 项目链接,它将帮助你迅速启动所有服务。
订阅数据管道,这是一个专注于数据工程的新闻通讯
创建 Dockerfile 和 docker-compose.yml
让我们开始定义我们想要通过 Docker 运行的服务。首先,我们将创建一个[docker-compose.yml](https://github.com/gmyrianthous/jaffle_shop/blob/main/docker-compose.yml)文件,在其中定义两个服务。第一个服务是 Postgres 数据库,第二个服务是我们将在下一步中使用 Dockerfile 创建的自定义服务。
# docker-compose.yml
version: "3.9"
services:
postgres:
container_name: postgres
image: postgres:15.2-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
ports:
- 5432
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
dbt:
container_name: dbt
build: .
image: dbt-jaffle-shop
volumes:
- ./:/usr/src/dbt
depends_on:
postgres:
condition: service_healthy
该文件指定了使用的 Docker Compose 版本(3.9 版)。它定义了两个服务,postgres 和 dbt,每个服务都有自己的设置。
postgres服务基于官方的postgres Docker 镜像版本 15.2-alpine。它将容器名称设置为postgres,将端口 5432(Postgres 的默认端口)映射到主机,并为 Postgres 用户和密码设置环境变量。healthcheck部分指定了一个命令来测试容器是否健康,并设置了检查的超时时间和重试次数。
dbt服务指定了一个dbt容器,该容器使用当前目录的 Docker 镜像(使用 Dockerfile)。它将当前目录挂载为容器中的一个卷,并指定它依赖于postgres服务,并且只有在postgres服务健康时才会启动。
为了容器化 Jaffle Shop 项目,我们需要创建一个[Dockerfile](https://github.com/gmyrianthous/jaffle_shop/blob/main/Dockerfile),该文件安装 Python 和 dbt 所需的依赖项,并确保环境设置完毕后容器保持活动状态。
# Dockerfile
FROM --platform=linux/amd64 python:3.10-slim-buster
RUN apt-get update \
&& apt-get install -y --no-install-recommends
WORKDIR /usr/src/dbt
# Install the dbt Postgres adapter. This step will also install dbt-core
RUN pip install --upgrade pip
RUN pip install dbt-postgres==1.2.0
RUN pip install pytz
# Install dbt dependencies (as specified in packages.yml file)
# Build seeds, models and snapshots (and run tests wherever applicable)
CMD dbt deps && dbt build --profiles-dir ./profiles && sleep infinity
配置 Postgres 与 dbt
要与 dbt 交互,我们将使用 dbt 命令行界面(CLI)。包含[dbt_project.yml](https://github.com/gmyrianthous/jaffle_shop/blob/main/dbt_project.yml)文件的目录被 dbt CLI 视为一个 dbt 项目。
我们将创建一个并指定一些基本配置,如 dbt 项目名称和要使用的profile(我们将在下一步中创建)。此外,我们将指定包含各种 dbt 实体的路径,并提供关于它们的物化配置。
# dbt_project.yml
name: 'jaffle_shop'
config-version: 2
version: '0.1'
profile: 'jaffle_shop'
model-paths: ["models"]
seed-paths: ["seeds"]
test-paths: ["tests"]
analysis-paths: ["analysis"]
macro-paths: ["macros"]
target-path: "target"
clean-targets:
- "target"
- "dbt_modules"
- "logs"
require-dbt-version: [">=1.0.0", "<2.0.0"]
models:
jaffle_shop:
materialized: table
staging:
materialized: view
现在[profiles.yml](https://github.com/gmyrianthous/jaffle_shop/blob/main/profiles/profiles.yml)文件用于存储 dbt 配置文件。一个配置文件包含多个目标,每个目标指定了数据库或数据仓库的连接详细信息和凭据。
# profiles.yml
jaffle_shop:
target: dev
outputs:
dev:
type: postgres
host: postgres
user: postgres
password: postgres
port: 5432
dbname: postgres
schema: public
threads: 1
该文件定义了一个名为jaffle_shop的配置文件,指定了运行在名为postgres的 Docker 容器上的 Postgres 数据库的连接详细信息。
-
jaffle_shop:这是配置文件的名称。它是用户为了识别配置文件而选择的任意名称。 -
target: dev:这指定了配置文件的默认目标,在这种情况下名为dev。 -
outputs:这一部分列出了配置文件的输出配置,默认的输出配置名为dev。 -
dev:这指定了dev目标的连接详细信息,该目标使用 Postgres 数据库。 -
type: postgres:这指定了输出的类型,在这种情况下是 Postgres 数据库。 -
host: postgres:这指定了 Postgres 数据库服务器的主机名或 IP 地址。 -
user: postgres:这指定了用于连接到 Postgres 数据库的用户名。 -
password: postgres:这指定了用于认证 Postgres 数据库的密码。 -
port: 5432:这指定了 Postgres 数据库监听的端口号。 -
dbname: postgres:这指定了要连接的 Postgres 数据库的名称。 -
schema: public:这指定了在对数据库执行查询时要使用的模式名称。 -
threads: 1:这指定了在运行 dbt 任务时要使用的线程数。
Jaffle Shop dbt 模型和 seeds
Jaffle Shop 项目的源数据包括客户、支付和订单的 csv 文件。在 dbt 中,我们可以通过 seeds 将这些数据加载到数据库中。然后,我们利用这些源数据在其基础上构建 dbt 模型。
这是一个示例模型,它生成一些 客户的指标:
with customers as (
select * from {{ ref('stg_customers') }}
),
orders as (
select * from {{ ref('stg_orders') }}
),
payments as (
select * from {{ ref('stg_payments') }}
),
customer_orders as (
select
customer_id,
min(order_date) as first_order,
max(order_date) as most_recent_order,
count(order_id) as number_of_orders
from orders
group by customer_id
),
customer_payments as (
select
orders.customer_id,
sum(amount) as total_amount
from payments
left join orders on
payments.order_id = orders.order_id
group by orders.customer_id
),
final as (
select
customers.customer_id,
customers.first_name,
customers.last_name,
customer_orders.first_order,
customer_orders.most_recent_order,
customer_orders.number_of_orders,
customer_payments.total_amount as customer_lifetime_value
from customers
left join customer_orders
on customers.customer_id = customer_orders.customer_id
left join customer_payments
on customers.customer_id = customer_payments.customer_id
)
select * from final
通过 Docker 运行服务
现在让我们构建并启动我们的 Docker 服务。为此,我们只需运行以下命令:
$ docker-compose build
$ docker-compose up
上述命令将运行一个 Postgres 实例,然后构建 Jaffle Shop 的 dbt 资源,如仓库中所述。这些容器将保持运行,以便你可以:
-
查询 Postgres 数据库及从 dbt 模型创建的表
-
通过 dbt CLI 运行进一步的 dbt 命令
通过 CLI 运行 dbt 命令
dbt 容器已经构建了指定的模型。然而,我们仍然可以访问容器并通过 dbt CLI 运行 dbt 命令,无论是对于新模型还是修改过的模型。为此,我们需要首先访问容器。
以下命令将列出所有活动的容器:
$ docker ps
复制 dbt 容器的 id,然后在运行下一个命令时输入它:
$ docker exec -it <container-id> /bin/bash
上面的命令将基本上为你提供对容器的 bash 访问权限,这意味着你现在可以运行 dbt 命令。
# Install dbt deps (might not required as long as you have no -or empty- `dbt_packages.yml` file)
dbt deps
# Build seeds
dbt seeds --profiles-dir profiles
# Build data models
dbt run --profiles-dir profiles
# Build snapshots
dbt snapshot --profiles-dir profiles
# Run tests
dbt test --profiles-dir profiles
请注意,由于我们已将本地目录挂载到正在运行的容器中,因此本地目录中的任何更改将立即反映到容器中。这意味着你也可以创建新的模型或修改现有的模型,然后进入正在运行的容器并构建模型、运行测试等。
查询 Postgres 数据库中的 dbt 模型
你还可以查询 postgres 数据库及其上创建的 dbt 模型或快照。同样,我们将需要进入正在运行的 postgres 容器,以便直接查询数据库。
# Get the container id for `postgres` service
$ docker ps
# Then copy the container id to the following command to enter the
# running container
$ docker exec -it <container-id> /bin/bash
然后我们将使用 psql,这是一个基于终端的 PostgreSQL 界面,允许我们查询数据库:
$ psql -U postgres
以下两个命令分别用于列出表和视图:
postgres=# \dt
List of relations
Schema | Name | Type | Owner
--------+---------------+-------+----------
public | customers | table | postgres
public | orders | table | postgres
public | raw_customers | table | postgres
public | raw_orders | table | postgres
public | raw_payments | table | postgres
(5 rows)
postgres=# \dv
List of relations
Schema | Name | Type | Owner
--------+---------------+------+----------
public | stg_customers | view | postgres
public | stg_orders | view | postgres
public | stg_payments | view | postgres
(3 rows)
你现在可以通过 SELECT 查询来查询 dbt 模型:
SELECT * FROM <table_or_view_name>;
获取完整代码
我已经创建了一个 GitHub 仓库,你可以在本地机器上克隆它,并快速运行容器化的 Jaffle Shop dbt 项目。你可以在以下链接中找到项目及本教程中共享的代码。
[## GitHub - gmyrianthous/jaffle_shop:这是 Jaffle Shop dbt 项目的容器化版本
这是由 dbt Labs 发布的流行 Jaffle Shop dbt 项目的容器化版本。你可以使用这个项目…
最后的思考
数据构建工具 (dbt) 是现代数据技术栈中快速增长的技术之一。如果你刚开始学习如何使用 dbt,我强烈推荐尝试 Jaffle Shop 项目。这是一个由 dbt Labs 创建的自包含项目,用于测试和实验目的。
dbt 是数据分析师和分析工程师(除了数据工程师)常用的工具,它需要连接到数据库或数据仓库。然而,许多分析师可能不习惯配置和初始化本地数据库。
在这篇文章中,我们演示了如何开始使用 dbt 并运行所有必要的服务,以在本地 Postgres 数据库上实现 dbt 模型。我希望这个教程能帮助你尽快启动你的 dbt 项目和数据库。如果在运行项目时遇到任何问题,请在评论中告知我,我会尽力帮助你调试代码和配置。
👉 订阅 Data Pipeline,这是一个专注于数据工程的新闻通讯
👇相关的文章你也可能喜欢 👇
运行 dbt 命令时选择特定模型的完整备忘单
towardsdatascience.com ## ETL 与 ELT:有什么区别?
数据工程背景下的 ETL 与 ELT 比较
towardsdatascience.com
《发现者简:利用大语言模型增强因果发现(因果 Python)》
一份实用指南,介绍如何利用大语言模型增强因果发现,减少幻觉风险(附 Python 代码)
·发布于 Towards Data Science ·18 分钟阅读·2023 年 10 月 22 日
--

图片由 Artem Podrez 提供,来源于 Pexels.com
“世界即一切存在的事物。”
路德维希·维特根斯坦 — 《逻辑哲学论》(1922 年)
婴儿在出生时无法理解运动的本质。
我们——人类——以及许多其他非人类动物,都天生拥有帮助我们了解环境的系统,但在我们出生时对环境本身知之甚少¹。
我们需要学习。
在这方面,我们与机器学习系统有相似之处。
早期心理学和(原始)神经科学对人类和其他非人类动物学习的发现,激发了构建可以从经验中学习的人工系统的灵感。
20 世纪 10 年代机器学习革命的最成功学习范式之一是监督学习。
经过监督训练的神经网络使我们在图像分类或机器翻译等长期存在的问题上取得了几十年的进展。
人类和其他非人类动物具备一种学习机制(在概念上²,但不一定在实施上)类似于监督学习³。
然而,婴儿和监督算法的学习方式之间也存在根本性差异。

图 1. 一幅 Purkinje 神经元的图画。这些是人脑中最大的神经元之一,可以在小脑中找到。图画由 Santiago Ramón y Cajal 绘制,约 1900 年。来源:commons.wikimedia.org/wiki/Category:Santiago_Ram%C3%B3n_y_Cajal
扔物体(但不要对着你)
你有没有见过父母试图说服孩子停止乱扔玩具?一些父母倾向于将这种行为解读为粗鲁、破坏性或攻击性,但婴儿往往有一套非常不同的动机(Molak, 2023)。
他们正在进行系统实验,允许他们学习
物理定律和社会互动规则(Gopnik, 2009)。
得益于这些行动,婴儿不仅可以通过观察静态数据(他们也可以这样做)来学习,还可以通过与世界互动来学习。
超越观察
与世界互动使孩子们能够建立世界模型。
世界模型是关于数据生成过程的假设。
一个足够丰富的世界模型可以帮助我们回答干预性(哪个决定将导致最佳结果?)和反事实(如果我做了 X 而不是 Y,会发生什么?)查询。
监督式机器学习模型(及其他基于关联原理的模型)不能直接回答这些问题⁴。
我们可以教机器回答因果查询吗?
简短的回答是“可以”。
有几种方法可以做到这一点。在这篇文章中,我们将仅限于机器不直接与环境互动的情况。
一个流行的因果查询是:“如果我对一组具有某些特征 X 的个体进行治疗 T ,那么治疗的平均效果会是什么”。
这被称为异质处理效应或条件平均处理效应(CATE)。
为了回答这种类型的查询,我们需要提供因果模型以及关于数据生成过程结构的额外信息。
后者通常以有向无环图(DAG)⁵的形式编码。
传统上,DAG(有向无环图)是(1)基于相关专家知识手动创建的,(2)使用自动化因果发现⁶,或(3)两者结合的过程,有时称为人机交互因果发现。
存在各种因果发现算法,每种算法利用的假设略有不同。请参见这篇文章以了解介绍:
## Causal Python — 提升你的 Python 因果发现技能(2023)
…并解锁 Python 中最佳因果发现包的潜力!
[towardsdatascience.com
雨中舞蹈
构建一个表示我们感兴趣问题的有向无环图(DAG)的任务有时可能很具挑战性,尤其是当我们处理复杂且开放的系统时⁷。
企业在构建因果图时可能面临的一个挑战是收集专家知识的挑战。
收集专家知识可能代价昂贵,尤其是当我们打算以系统化的方式进行时。专家可能不容易获得,而且他们的时间可能很宝贵。
我想要大型语言模型
一些作者(例如 Kıcıman et al., 2023)提出,大型语言模型(LLMs)可能有潜力用于因果发现。
使用通用模型来获得因果结构的理解的前景令人心动——它为快速且具有成本效益的过程开辟了道路。
不幸的是,现成的语言模型(LLMs)在因果发现中的有效性似乎有限,因为它们表现出不可预测的失败模式、幻觉,并且无法区分变量之间的因果关系和非因果关系(Kıcıman et al., 2023; Zečević et al., 2023)。
在因果强盗播客这一集里,我们讨论了 LLMs 和因果推理中的问题。仅音频版本可在这里获取。
一种最近越来越受欢迎的应对 LLMs 幻觉和失败模式的方法是检索增强生成(RAG)。
RAG 是一组技术,它为模型提供外部知识来源(如文档或语料库),模型基于这些来源生成响应。
这种方法已被证明能减少幻觉并帮助模型保持相关性。
要了解更多关于 RAG 及其重要性的内容,请参阅我同事Anthony Alcaraz的这篇文章:
## 为什么检索增强对企业 LLMs 至关重要,以及选择哪个当前模型?
大型语言模型(LLMs)展示了在企业环境中彻底改变工作流程的巨大潜力。
两种视角
LLMs 和传统因果发现算法在寻找因果结构的任务中存在根本差异。
传统的因果发现算法利用观测、干预或混合数据的组合以及以假设形式编码的归纳偏差,从数据中推断生成这些数据的过程的结构信息。
另一方面,LLM 完全不看(测量)数据。它们利用概念的学习语义表示并进行转换,以回答关于这些概念之间可能的因果关系的查询。
在这种情况下,LLM 的一个视角是,它们利用因果事实的相关性(有关详细信息,请参见 Zečević et al., 2023),以确定两个或多个实体之间的因果关系。

图 2。因果事实的文本和测量表示的上下文等效性。来源:Zečević et al., 2023。
我们在这里讨论事实的相关性,因为 LLM 是以联想的方式进行训练的,因此通常无法期望它们基于因果合理的表示进行操作⁸。
好消息是 RAG 可以通过将模型响应集中于提供的语料库的上下文,减少失败模式和幻觉的风险。
这为 LLM 在因果发现过程中的更有意义的角色打开了道路。
文档和测量
让我们假设我们想为一个感兴趣的问题建立一个因果图。
我们在一段时间内测量了一组与此问题相关的变量。我们还有存储在多个文档中的关于该问题的专家知识。
我们可以简单地在测量数据上运行因果发现算法,但如果无法保证算法的假设得到满足,这样做是有风险的。这也意味着抛弃我们手头上宝贵的专家知识。
另一方面,我们可以使用 RAG-LLM 查询语料库并解析模型输出以获取图形。
这可能仍然有风险,因为即使有 RAG,我们也不能保证模型会正确回答查询。此外,语料库中的知识可能不完整,这意味着数据中存在的一些因果关系可能在语料库中没有反映出来。
想成为朋友吗?
一种替代方法可能是结合两种方法,三角测量语义和基于测量的视角。
一种实现方法是通过查询 RAG-LLM 系统从文本表示中提取因果关系,然后使用获得的信息作为专家知识,注入到仅在测量数据上操作的因果发现算法中。
想尝试一下吗?
Mehendretex 能帮助登山者吗?
让我们将刚刚描述的想法付诸实践,看看它如何运作。
我使用了一个关于高度和温度关系的经典例子作为我们练习的灵感。
为了使任务更加有趣,我们将向问题中添加更多变量,并使其中一个完全陌生于 LLM(模型本身和文档语料库中都没有关于它的先验知识)。
我们将关注找到一组变量之间的因果结构,这些变量可能影响登山者的死亡风险。
为了找到结构,我们将使用合成测量数据⁹,并利用维基百科中提供的一些变量的先验知识(我们的语料库)。
问题陈述中的变量有:
-
海拔
-
温度
-
氧气密度
-
死亡风险
-
Mehendretex 的使用(我们虚构的处理方法)
后者变量——Mehendretex(现实中不存在类似的东西)——的功能是对我们系统中的 LLM 部分进行压力测试。
我们预计,在相关提示下,一个 RAG-LLM 会告诉我们,它不能对 Mehendretex 和任何其他变量之间的因果关系说任何话。
图 3 展示了我们问题的真实结构 DAG,以图形(左)和邻接矩阵(右)的形式表示。

图 3. 我们问题的真实结构表示为图形(左)和邻接矩阵(右)。矩阵中的黑色区域表示一条边,白色区域表示没有边。例如,坐标为 (4, 3) 的黑色方块表示从节点 4 到节点 3 有一条边。
我的 Python 在哪里?
让我们编写代码解决这个问题。
GitHub 仓库的链接见文章末尾。
我们将使用numpy和scipy生成数据,gCastle 将作为我们的因果发现库,OpenAI 的GPT-4 作为 LLM,LangChain 作为框架,帮助我们将相关的维基百科文章提供给 GPT-4。
在此基础上,我们将导入os来管理 OpenAI 密钥,itertools.combinations来帮助我们生成 RAG-LLM 代理的变量对。
设置
让我们导入库:
import os
from itertools import combinations
import numpy as np
from scipy import linalg
from scipy import stats
import matplotlib.pyplot as plt
from langchain.agents import load_tools, initialize_agent
from langchain.agents import AgentType
from langchain.chat_models import ChatOpenAI
from castle.common import GraphDAG
from castle.metrics import MetricsDAG
from castle.algorithms import PC
from castle.common.priori_knowledge import PrioriKnowledge
让我们设置 OpenAI 密钥:
with open(r'my_folder/my_openai_key.dat') as f:
key = f.read()
os.environ['OPENAI_API_KEY'] = key
数据生成
现在,让我们生成数据。
注意,我们仅生成虚构的测量数据。我们将使用的文本数据是实际的维基百科文章。
从这个意义上讲,我们在这个例子中使用半合成数据。
让我们从定义变量名称和索引之间的映射开始。这将帮助我们在后续中以矩阵格式编码从 LLM 获得的专家知识:
all_vars = {
'altitude': 0,
'oxygen_density': 1,
'temperature': 2,
'risk_of_death': 3,
'mehendretex': 4
}
接下来,我们设置样本大小,并根据图 3中展示的结构生成变量的合成测量数据:
SAMPLE_SIZE = 1000
altitude = stats.halfnorm.rvs(scale=2000, size=SAMPLE_SIZE)
temperature = 25 - altitude / 100 + stats.norm.rvs(
loc=0,
scale=2,
size=SAMPLE_SIZE
)
mehendretex = stats.halfnorm.rvs(size=SAMPLE_SIZE)
oxygen_density = np.clip(
1 - altitude / 8000
- temperature / 50
+ stats.norm.rvs(size=SAMPLE_SIZE) / 20,
0,
1)
risk_of_death = np.clip(
altitude / 20000
+ np.abs(temperature) / 100
- oxygen_density / 5
- mehendretex / 5
+ stats.norm.rvs(size=SAMPLE_SIZE) / 10,
0,
1
)
请注意,为了使数据集更加逼真,我们使用了正态分布和半正态分布的组合,并与截断相结合。
这也将使因果发现算法的工作更加困难。
让我们将所有变量堆叠到一个矩阵中:
dataset = np.stack(
[
altitude,
oxygen_density,
temperature,
risk_of_death,
mehendretex
]
).T
…并以矩阵形式定义图 3中的结构:
true_dag = np.array(
[
[0, 1, 1, 1, 0],
[0, 0, 0, 1, 0],
[0, 1, 0, 1, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 1, 0]
]
)
算法因果发现
让我们通过将经典的 PC 算法应用于我们的测量数据来开始发现过程。
我们将使用 gCastle 实现的 PC 稳定变体:
# PC discovery without LLM assist
pc = PC(variant='stable')
pc.learn(dataset)
# Vizualize
GraphDAG(
est_dag=pc.causal_matrix,
true_dag=true_dag)
plt.show()
# Compute metrics
metrics = MetricsDAG(
B_est=pc.causal_matrix,
B_true=true_dag)
print(metrics.metrics)
结果展示在图 4中:

图 4:将 PC 算法应用于我们的测量数据的结果。右侧是实际邻接矩阵;左侧是 PC 算法生成的矩阵。矩阵中的黑色“区域”表示一条边,白色区域表示没有边。例如,坐标为(2, 1)的黑色方块表示从节点 2 到节点 1 有一条边。
如我们所见,结果并不理想。算法正确识别了一些边(例如,(4, 3)、(0, 1)或(0, 2)),但许多边仍然未定向。
比如,在左侧的矩阵中,我们有一个边(4, 3),但也有(3, 4),这表明模型能够识别节点 3 和 4 之间的连接,但无法确定这一连接的方向。
了解我们数据集的基础结构,这并不令人惊讶,因为 PC 算法依赖于条件独立性测试,并且在某些结构配置下无法确定边的方向。
想了解更多关于 PC 的信息及其原因,请查看这篇博客文章进行介绍或我最近的因果建模书籍的第三部分以获得更详细的处理。
当我们查看指标时,这张图的 F1 分数等于 0.53¹⁰。
定义一个 LLM 代理
让我们看看当我们将 RAG-LLM 代理添加到这个场景中会发生什么。
首先,我们将定义一个先验知识对象,然后用它来存储我们的代理提取的知识。
# Instantiate the priori knowledge object
priori_knowledge = PrioriKnowledge(n_nodes=len(all_vars))
接下来,让我们实例化代理。
llm = ChatOpenAI(
temperature=0,
model='gpt-4')
我们将选择 GPT-4 作为我们的模型。我们将温度设置为0,以使模型输出尽可能保守。
让我们添加 LangChain 的工具,使模型能够访问维基百科:
# Load tools
tools = load_tools(
[
"wikipedia"
],
llm=llm)
最后,让我们实例化代理:
# Instantiate the agent
agent = initialize_agent(
tools,
llm,
agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION,
handle_parsing_errors=True,
verbose=False)
我们将使用代理浏览维基百科页面,以寻找与我们问题相关的变量之间的因果关系信息。
从这个代理获得的发现将被传递给另一个 GPT-4 实例。
这个第二个 GPT-4 实例将负责解释初始输出,并将其转换为与我们之前的知识对象兼容的格式。
为了使代码更具可重用性,我们将其封装在一个函数中:
def get_llm_info(llm, agent, var_1, var_2):
out = agent(f"Does {var_1} cause {var_2} or the other way around?\
We assume the following definition of causation:\
if we change A, B will also change.\
The relationship does not have to be linear or monotonic.\
We are interested in all types of causal relationships, including\
partial and indirect relationships, given that our definition holds.\
")
print(out)
pred = llm.predict(f'We assume the following definition of causation:\
if we change A, B will also change.\
Based on the following information: {out["output"]},\
print (0,1) if {var_1} causes {var_2},\
print (1, 0) if {var_2} causes {var_1}, print (0,0)\
if there is no causal relationship between {var_1} and {var_2}.\
Finally, print (-1, -1) if you don\'t know. Importantly, don\'t try to\
make up an answer if you don\'t know.')
print(pred)
return pred
注意我们如何首先调用代理,使用一个提示,然后将这个代理的输出传递给另一个(非代理)LLM 实例,该实例使用另一个提示进行初始化。
在我的实验中,这种分层方法比仅使用单一代理获得了更好、更一致的结果。
我还注意到,重复向两个代理解释因果关系对改善结果有帮助。
在这两个提示中,我们使用 Pearl 的干预因果定义。此外,第一个提示指定我们对广泛的因果效应感兴趣,只要它们符合基本定义。
在实验中,后者在某些情况下帮助模型变得更加果断。
函数返回:
-
如果模型判断存在从
var_1到var_2的因果边,则为(0,1) -
如果模型判断因果方向是从
var_2到var_1,则为(1,0) -
如果模型判断
var_1和var_2之间没有因果关系,则为(0,0) -
如果模型不知道答案(请注意我们在提示中强调了模型如果不知道答案就不应给出答案),则为
(-1,-1)
LLM 增强的因果发现
我们现在准备使用我们的分层双提示系统从维基百科收集专家知识。
我们将遍历所有可能的变量组合,记录每个组合的模型回答,并将其存储在先验知识对象中:
for var_1, var_2 in combinations(all_vars.keys(), r=2):
print(var_1, var_2)
out = get_llm_info(llm, agent, var_1, var_2)
if out=='(0,1)':
priori_knowledge.add_required_edges(
[(all_vars[var_1], all_vars[var_2])]
)
priori_knowledge.add_forbidden_edges(
[(all_vars[var_2], all_vars[var_1])]
)
elif out=='(1,0)':
priori_knowledge.add_required_edges(
[(all_vars[var_2], all_vars[var_1])]
)
priori_knowledge.add_forbidden_edges(
[(all_vars[var_1], all_vars[var_2])]
)
print('\nLLM knowledge vs true DAG')
priori_dag = np.clip(priori_knowledge.matrix, 0, 1)
GraphDAG(
est_dag=priori_dag,
true_dag=true_dag)
plt.show()
请注意,我们仅在模型判断变量之间存在因果关系(输出为(0,1)或(1,0))时,将信息从模型传递到先验知识对象,但当模型声称变量之间没有因果关系时,我们会跳过这一步。
这个决定基于观察到模型在声称存在因果影响时通常过于谨慎。
通过忽略模型的空结果,我们实质上允许因果发现算法在这种情况下决定是否存在关系。
让我们看看模型能够学到什么。
图 5 显示了我们的 LLM 代理与真实情况的对比结果。

图 5. 我们的 LLM 代理检索的先验知识(左)和真实情况图(右)。
正如我们所见,模型能够学到一些有用的知识,但未能重建整个图。这部分是因为维基百科没有包含我们设想的处理—Mehendretex 的信息。
让我们快速查看一下我们模型中涉及 Mehendretex 的一个日志:
{'input': 'Does risk_of_death cause mehendretex or the other way around?
We assume the following definition of causation:
if we change A, B will also change.
The relationship does not have to be linear or monotonic.
We are interested in all types of causal relationships, including
partial and indirect relationships, given that our definition holds.',
'output': 'I\'m sorry, but I couldn\'t
find any information on "mehendretex".
Therefore, it\'s impossible to determine a causal relationship
between "risk_of_death" and "mehendretex".
Could you please provide more context or
check if "mehendretex" is spelled correctly?'}
(-1, -1)
似乎模型正确回应了包含不在模型使用的语料库中的术语的查询(见上框中的粗体文本)。
我们现在准备将收集到的知识传递给因果发现算法:
print('\nRunning PC')
# Instantiate the model with expert knowledge
pc_priori = PC(
priori_knowledge=priori_knowledge,
variant='stable'
)
# Learn
pc_priori.learn(dataset)
GraphDAG(
est_dag=pc_priori.causal_matrix,
true_dag=true_dag)
plt.show()
# Compute metrics
metrics = MetricsDAG(
B_est=pc_priori.causal_matrix,
B_true=true_dag)
print(metrics.metrics)
请注意,我们正在初始化 PC 算法的新实例,并将包含 LLM 收集到的知识的priori_knowledge对象传递给构造函数。
PC 现在将重新计算所有条件独立性测试的结果,但在确定边的方向时还会考虑先验知识。
准备好查看结果了吗?
图 6 显示了我们 RAG-LLM 诱导的 PC 算法的结果。

图 6. 使用基于 RAG-LLM 的先验知识的 PC 算法结果(左)与真实情况(右)。
这看起来非常好!
只有一条边保持无向:索引为0和2(海拔和温度)的节点之间的边,所有其他边都被正确定向。
该输出的 F1 分数为 0.93,比没有 LLM 帮助的 PC 提高了 40 个百分点。
总结结果
通过将传统的因果发现方法与 RAG-LLM 结合,我们能够获得比任何单一方法更好的结果。
图 7 总结了每个步骤的结果。

图 7 模型输出在每个阶段的邻接矩阵(左列)与真实值(右列)以及相应的 F1 分数值。
摘要
使用大型语言模型作为“知识解析器”,将文本信息转换为与传统因果发现算法兼容的格式,并扩展后者的能力,这一想法非常强大。
限制
我们在这篇博客文章中讨论的系统主要限制之一是 LLM 输出的不稳定性。我运行了大约 20 次这里呈现的过程,LLM 的决策在不同运行间并不总是保持一致(即使温度设置为零)。
PC 算法有一套理论保证,但这些保证仅适用于无限样本量,这在实际中当然是不可能的。此外,我们在这篇文章中使用的默认条件独立性测试并不十分强大,可能在更复杂的情况下表现不佳。
PC 算法要求数据中没有隐藏的混淆——这是一个不总是可以保证的条件,特别是在开放复杂系统中。
对于较大的数据集,运行 RAG-LLM 部分可能会迅速变得昂贵,因为系统可能在幕后对每对变量进行多个 API 调用。如果你决定为自己的用例运行代码,要注意潜在的高 API 费用。
我的建议是在你的 OpenAI 账户中设定一个限制,在开始实验之前。
进一步改进
自然地,这样的系统不必局限于维基百科作为外部知识来源,任何文档语料库都可以使用。
一些大型语言模型(LLM)输出的不稳定性可以通过创建更好的提示来潜在解决。
通过使用更通用的基于核的方法进行条件独立性测试,PC 算法的性能可以得到改善。如果数据适合,其他因果发现算法也可以替代 PC。
尤其是当无法排除隐藏混淆时,像快速因果发现(FCI)这样的算法可能是更好的选择。
对每对可能的变量进行 LLM 查询不可扩展,对于较大的数据集可能非常昂贵。一个潜在的预选择机制可以基于常规 LLM,评估两个变量是否有因果连接的可能性。在单个查询中结合多个变量可能有助于降低成本。
这个解决方案的缺点是它伴随着更高的幻觉风险,但在处理常见变量和关系时可能是有效的。
结论
Jane the Discoverer 是一个概念验证的想法。我认为它是一个因果助手,适用于小型到中型图。
出于教育目的,博客中的代码是以建议自主使用的方式编写的,但我发现人机交互范式更为有效。
恭喜你阅读完毕!
我们如何进一步改进 Jane?
在下面的评论区分享你的想法和建议,保持因果联系!
如果你喜欢这篇文章,你可能也会喜欢:
🔸因果 Python 每周通讯
笔记本与本文的完整代码可以在 GitHub 上找到:
[## blogs-code/Jane the Discoverer at main · AlxndrMlk/blogs-code
博客文章的代码。通过在 GitHub 上创建帐户来贡献 AlxndrMlk/blogs-code 的开发。
脚注
¹ 我们可以争辩说,在出生的那一天,我们实际上对世界有所了解,因为许多研究表明胎儿可以学习和习惯(例如,James 等,2022)
² 联想学习在各个物种中普遍存在。即使在像C. elegans这样的简单生物中也有出现。注意,在本文中我们使用术语联想学习来描述任何基于关联的学习,这不包括系统干预。这一定义可能与心理学和/或生物学中使用的一些定义不一致。
³ 我们可以在这里讨论在联想学习背景下动物与监督机器学习算法之间的实现差异及潜在的样本效率差异。附带说明,我认为样本效率的话题非常有趣。同时,我相信相关的答案比“一个孩子可以通过两个例子了解什么是狗,而 CNN 或 Transformer 架构需要数百万个例子”这样的流行说法要复杂得多。
⁴ 这也包括自监督学习和一些强化学习模型。
⁵ 通过使用更先进或更通用的识别方法,我们可以将其扩展到部分有向或循环图。
⁶ 因果发现是一系列方法,旨在从观察性、干预性或混合数据中恢复数据生成过程的结构。
为生产线建立一个文档完善且实际隔离于外部影响的图形通常比为公共政策优化建立图形要容易一些。在这两种极端情况之间还有许多其他情况。
⁸ 也就是说,如果我们可以在测试时进行干预,LLMs 可以学习主动(因果)策略。有关详细信息,请参见 Lampinen 等(2023)。
⁹ 我们使用合成测量,以确保我们知道真实情况。
¹⁰ F1 分数不一定是比较图形结构时最具信息量的度量指标。然而,我决定在这里使用它,因为它是数据科学社区中相对被理解且非常流行的度量指标。我希望使用它(而不是像 SHD 这样的更专业的度量指标)能使这篇文章对更广泛的受众更具可及性。
参考文献
Gopnik, A. (2009). 哲学婴儿:儿童的思维告诉我们关于真理、爱和生活意义的东西. 纽约:Farrar, Straus and Giroux.
Kıcıman, E., Ness, R., Sharma, A., & Tan, C. (2023). 因果推理与大型语言模型:为因果关系开辟新领域。 ArXiv.
Lampinen, A. K., Chan, S. C. Y., Dasgupta, I., Nam, A. J., & Wang, J. X. (2023). 代理和语言模型中的主动因果策略的被动学习. ArXiv.
Molak, A. (2023). Python 中的因果推断与发现:利用 DoWhy、EconML、PyTorch 等解锁现代因果机器学习的秘密. Packt Publishing.
Stanovich, K., & West, R. (2000). 个人推理差异:对理性辩论的影响?行为与脑科学, 23(5), 645–665. doi:10.1017/S0140525X00003435
Zečević, M., Willig, M., Dhami, D. S., & Kersting, K. (2023). 因果鹦鹉:大型语言模型可能谈论因果关系但不具备因果能力. ArXiv.
这篇文章可能包含书籍的联盟链接。如果你通过这些链接购买书籍,作者将获得少量佣金。
一月刊:成为更好的学习者
月刊
让你的 2023 年数据科学之旅从一开始就顺利起步
·
关注 发表在 Towards Data Science · 4 min read · 2023 年 1 月 4 日
--
图片来源:little plant 在 Unsplash
每年年初看到如此多的人加入我们的社区总是令人兴奋,无论是第一次加入还是经过长时间的 hiatus。欢迎!(或欢迎回来!)
对我们 TDS 团队来说,参与您的学习之旅是一种极大的荣幸。在 2023 年初,我们希望帮助每个人——无论您是新年决心者还是循序渐进的习惯养成者——以正确的步伐开始今年的旅程。
为此,我们汇总了一些关于学习和提升数据科学及相关技能的优质文章。我们的许多精选内容面向全新或有意从事数据工作的专业人士,但更有经验的专家也会找到许多宝贵的见解和实用技巧。
在我们让您继续阅读之前,最后提一点:如果您希望支持我们作者在 2023 年及以后继续创作,请考虑成为 Medium 会员。
新年快乐——我们期待一起探索和学习!
TDS 编辑精选
-
2023 年指导自学数据科学家的每日、每周、每月和每年目标建议(2022 年 12 月,11 分钟)
一个好的计划是实现学习目标的关键,Madison Hunter提供了一个既雄心勃勃又可持续的详细路线图来帮助您。
-
如何作为高中生探索机器学习和自然语言处理(2022 年 7 月,12 分钟)
Carolyn Wang的这份有用指南可能围绕她作为高中生的个人经历展开,但对于各年龄层的有志从业者来说,它是机器学习和自然语言处理的有益入门。
-
数据科学初学者需要知道的简单事项(2022 年 12 月,11 分钟)
Ken Jee最近的资源是一个便于理解的最新数据科学入门指南,适合今年初涉数据科学的任何人。
-
我是一名自学成才的数据科学家。以下是我对新手的 3 点建议(2022 年 4 月,5 分钟)
对于所有选择不遵循既定课程的独立学习者,Soner Yıldırım基于他作为自学数据专业人士的经验,提供了一些关键见解。
-
神经网络简要介绍:回归问题(2022 年 12 月,12 分钟)
你如何从头开始学习一个复杂的技术主题?Chayma Zatout 最近的文章提供了一个强大的蓝图(这也是任何新手学习神经网络的绝佳起点)。
-
有抱负的数据科学家如何找到并参与现实世界项目?(2022 年 12 月,7 分钟)
在完成了一些在线课程和阅读了几本书(以及 TDS 文章!)之后,你可能会问自己:接下来怎么办?Arunn Thevapalan 提出了一些将理论知识转化为实际项目的想法。
-
如何在初学者阶段开始你的第一个数据科学项目(2022 年 6 月,6 分钟)
从不同的角度解决类似问题,Rashi Desai 提出了一个五步流程,帮助你从新手成长为自信的实验者和探索者。
-
没有计算机科学学位的任何数据科学初学者最佳编程技巧(2022 年 5 月,7 分钟)
人们从各种背景过渡到数据科学职业;如果你的过去职业生涯中没有扎实的计算机科学基础,Hennie de Harder 分享了一些有用的技巧,帮助你编写高效、干净的代码。
-
如果可以重新开始,我会如何学习数据科学(四年后)(2022 年 11 月,7 分钟)
数据科学领域在过去几年中发展迅速,进入这个领域的方法也在不断变化。Terence Shin 现在已经是一名四年的资深人士,他的深思熟虑的文章反映了他对作为初学者掌握基础知识最佳方法的最新思考。
原始特性
我们最新的问答选择和阅读推荐。
-
“在任何行业中产生影响,领域知识至关重要。” 不要错过我们最新的作者专访:我们与 Abiodun Olaoye 进行了交谈,他讨论了自己在数据科学和可再生能源交汇处的职业路径。
-
探索我们最佳深度分析的季节。为了您的阅读乐趣,我们汇总了一些近期最优秀的长篇文章。
热门文章
如果您错过了,以下是上个月 TDS 上一些最受欢迎的帖子。
-
我使用 ChatGPT 在 AWS 上创建了一个完整的 AI 应用程序 作者:Heiko Hotz
-
5 个 Python 库助你开启数据科学职业生涯 作者:Federico Trotta
-
使用 Bokeh 在 Python 中创建数据可视化的 8 个技巧 作者:Payal Patel
-
每位数据科学家都应该知道的数学优化启发式方法 作者:Hennie de Harder
-
OpenAI 的 ChatGPT 是世界上最好的聊天机器人 作者:Alberto Romero
-
数据科学家的日常 作者:Leah Berg 和 Ray McLendon
我们很高兴在 12 月迎来了最后一批新作者;请加入我们,欢迎 Mohamad Aboufoul、Paul Knulst、Clement Wang、Michael Parkes、Daniel Justus、Philip Tsang、Joel Hodgson、Donato Riccio、Jake Schmidt、Saleha、Ryan Kearns、Thomas Ellyatt、Adam Kohan、Maybritt Schillinger、Sekhar M、Shreshth Sharma、Hajime Takeda、Qitian Wu、Patrick Hoefler、Omar Alkousa、Arli、Jonte Dancker、Avra、Gosia Komor 和 Sairam Sundaresan等。 如果你有有趣的项目或想法想要与我们分享,我们期待你的来信!
直到下个月…
Java 和数据工程
原文:
towardsdatascience.com/java-and-data-engineering-f0e0a145cb52
数据工程
Java Juggernaut: 数据工程掌握的关键
·发布于 Towards Data Science ·阅读时间 4 分钟·2023 年 11 月 11 日
--

数据工程和编程技能
当我们想到数据工程时,首先想到的编程技能通常是 SQL 和 Python。SQL 是一种用于查询数据的著名语言,深深根植于数据和管道的世界。另一方面,Python 在数据科学中变得相当强大,并且现在在不断发展的数据工程领域中也开始展现其影响力。但是,这种普遍的看法是否准确?SQL 和 Python 真的就是数据工程师最重要的编程技能吗?在本文中,我将分享我在这一领域的经验,旨在帮助年轻专业人士找出最佳技能,以充分利用他们的时间和精力。
为什么选择 Java 和 Scala?
在今天的数据工程中,我们处理大量的数据。主要的工作是搞清楚如何每天、每小时甚至实时地收集、改变和存储这些海量数据。更棘手的是,要确保不同的数据服务能够在各种系统上顺利运行,而不用担心底层发生了什么。
在过去的 15 年里,聪明的人们提出了分布式计算框架来应对数据过载问题。Hadoop 和 Spark 是这个领域的两个大名鼎鼎的名字。由于这两个框架主要使用 JVM(Java 虚拟机)语言构建(Hadoop 使用 Java,而 Spark 使用 Scala),许多数据和软件专家认为 Java 和 Scala 是数据工程的未来方向。
此外,JVM 应用程序的可移植性使它们成为跨各种系统和环境操作的数据应用的优秀选择。你可以开发无缝运行于各种云端和本地环境的数据管道,从而可以在不担心底层基础设施的情况下,按需扩展系统。
在基于 JVM 的应用程序中,数据管道是什么样的?
现在我们已经探讨了 Java 和 Scala,或者更广泛地说,基于 JVM 的数据应用在处理大数据方面的好处,接下来的逻辑问题是:这些应用程序,或者简单的数据管道,看起来是什么样的?本节旨在概述这些应用程序的架构。
首先,必须在 Java 或 Scala 中开发一个数据管道。通常,多个相关的数据管道可以共存于同一个 Java 或 Scala 项目中。为了有效的项目管理,可以使用像Apache Maven这样的工具。Maven 简化了 Java 应用程序的创建、管理和构建过程,使这一过程更加高效和可靠。
在这些项目中,数据管道通常包括一个或多个 Java 或 Scala 类。Spark 通常集成到这些类中,用于读取(或提取)、转换和写入(或加载)数据。尽管数据可以从各种来源读取和写入,但 Hive 表通常是自然选择。标准转换被封装在常用类中,使其可以在不同管道之间重用。
这段代码展示了一个基本的 Spark Scala 数据管道。
import org.apache.spark.sql.{SparkSession, DataFrame}
import org.apache.spark.sql.functions._
class MyExampleDataPipeline {
val spark: SparkSession = SparkSession.builder
.appName("DataFrame Transformation Example")
.master("local[*]")
.enableHiveSupport()
.getOrCreate()
def main(inputTableName: String, outputTableName: String): Unit = {
val inputDataFrame: DataFrame = spark.table(inputTableName)
val transformedDataFrame: DataFrame = inputDataFrame.SOME_TRANSFORMATIONS
transformedDataFrame.write.mode("overwrite").format("parquet").saveAsTable(outputTableName)
}
}
最终,目标是构建一个通常以 jar 文件形式存在的 Java 应用程序。这个 jar 文件以及适当的参数,可以通过像Apache Airflow这样的作业和工作流管理系统进行调用。这使得在计划的时间间隔执行特定的数据管道成为可能,从而促进有序和自动化的数据处理工作流。
这是一个简单的示例,展示如何从命令行执行一个作为类表示的数据管道。
spark-submit --class MyExampleDataPipeline --master local[*] yourJarFile.jar your_input_hive_table_name your_output_hive_table_name
更高级的实践
如前所述,数据管道现在被封装在 jar 文件中,通常需要定时执行,特别是用于批处理,或者基于触发事件激活,通常用于实时处理。Apache Airflow 作为一个强大的解决方案,用于编排这些 jar 文件和任务类,促进定期执行作业。或者,可以使用像 AWS Lambda 这样的工具触发 jar 文件,以应对不规则的时间表和实时处理。
这是一个 Apache Airflow DAG 的示例,旨在每天执行一个 Java 类。
from datetime import datetime, timedelta
from airflow import DAG
from airflow.operators.bash_operator import BashOperator
from airflow.operators.dummy_operator import DummyOperator
jar_file_path = "/path/to/yourJarFile.jar"
input_table_name = "your_input_hive_table_name"
output_table_name = "your_output_hive_table_name"
default_args = {
'owner': 'airflow',
'start_date': datetime(2023, 1, 1),
'depends_on_past': False,
'retries': 1,
'retry_delay': timedelta(minutes=5),
}
dag = DAG(
'my_data_pipeline_dag',
default_args=default_args,
description='DAG to run the MyExampleDataPipeline class',
schedule_interval='@daily', # Adjust the schedule as needed
)
start_task = DummyOperator(task_id='start', dag=dag)
end_task = DummyOperator(task_id='end', dag=dag)
spark_submit_command = f"spark-submit --class MyExampleDataPipeline --master local[*] {jar_file_path} {input_table_name} {output_table_name}"
run_data_pipeline_task = BashOperator(
task_id='run_data_pipeline',
bash_command=spark_submit_command,
dag=dag,
)
start_task >> run_data_pipeline_task >> end_task
此外,持续集成/持续部署(CI/CD)工具,包括 Jenkins、GitHub Actions、Spinnaker 等,提供了一种无缝的方式来开发和部署跨各种环境的数据管道,从开发到测试和生产环境。这确保了管道在整个开发生命周期中的平滑和自动化过渡。
在最后...
我们探索了数据工程不断变化的格局以及该领域所需的基本编程技能。虽然 SQL 和 Python 传统上与数据工程相关,但重点正转向 Java 和 Scala,特别是在通过像 Hadoop 和 Spark 这样的分布式计算框架处理大量数据的背景下。
我们再次强调了 JVM(Java 虚拟机)语言的重要性,因为它们的可移植性使其适合开发可以在各种系统和环境中无缝运行的数据应用程序。它深入探讨了基于 JVM 的数据应用程序的架构,展示了如何使用 Java 或 Scala 开发数据管道,同时 Apache Maven 帮助项目管理。
爵士和弦解析与变压器
原文:
towardsdatascience.com/jazz-chords-parsing-with-transformers-d75031a976f2
基于数据的树形音乐分析方法
·发布在 Towards Data Science ·阅读时间 11 分钟·2023 年 8 月 1 日
--

在这篇文章中,我总结了我的研究论文“使用基于图的神经解码器预测音乐层级”的一部分,该论文提出了一种能够解析爵士和弦序列的数据驱动系统。
这项研究是由于我对基于语法的解析系统感到挫败而激发的(这些系统是处理音乐数据的唯一选择):
-
语法构建阶段需要大量的领域知识
-
解析器在面对一些未见过的配置或嘈杂数据时会失败
-
在单一语法规则中考虑多个音乐维度是具有挑战性的。
-
目前没有一个得到广泛支持的活跃 Python 框架来帮助开发
我的方法(受自然语言处理领域类似工作的启发),不依赖于任何语法,对嘈杂输入产生部分结果,可以轻松处理多个音乐维度,并且在 PyTorch 中实现。
如果你不熟悉解析和语法,或者只是需要刷新你的知识,我现在会退后一步。
什么是“解析”?
解析一词指的是预测/推断出一棵树(数学结构),其叶子节点是序列中的元素。

好吧,但我们为什么需要树呢?
让我们从以下的爵士和弦序列(《Take the A Train》的 A 部分)开始。

在爵士音乐中,和弦通过复杂的感知关系系统连接。例如,Dm7 是对主和弦 G7 的预备。这意味着 Dm7 的作用不如 G7 重要,它可以在不同的和声重构中被省略。类似地,D7 是一个次级主和弦(主和弦的主和弦),也指向 G7。
这种和声关系可以用树来表达,并且对音乐分析或执行诸如再和声等任务非常有用。然而,由于音乐作品中的和弦大多以序列形式存在,我们希望一个能够自动构建这种树结构的系统。
组成树与依赖树
在继续之前,我们需要区分两种类型的树。
音乐学家倾向于使用所谓的组成树,你可以在下面的图片中看到。组成树包含叶子(蓝色和弦——输入序列的元素),以及内部节点(橙色和弦——子叶的简化)。

而在这项工作中,我们考虑另一种树,称为依赖树。这种树没有内部节点,只有连接序列元素的有向弧。

我们可以通过一些稍后讨论的算法从组成树生成依赖树。
数据集
由于这是数据驱动的方法,我们需要一个和弦序列的数据集(输入数据),以及一个树的数据集(真实标签)用于训练和测试。我们使用的是 Jazz Treebank¹,公开在这个 GitHub 仓库中(它可以自由用于非商业应用,并且我获得了作者的许可在本文中使用它)。特别是,他们提供了一个包含所有和弦和注释的 JSON 文件。
我们将输入到系统中的每个和弦建模为三个特征:
-
根节点,是[0..11]中的一个整数,其中 C -> 0,C# ->1,依此类推……
-
基本形式是[0..5]中的一个整数,用于选择大调、小调、增和弦、半减和弦、减和弦和挂留和弦(sus)。
-
扩展,是[0,1,2]中的一个整数,用于选择 6、次要 7 或大调 7。
要从和弦标签(一个字符串)中生成和弦特征,我们可以使用如下的正则表达式(注意,这段代码适用于这个数据集,因为在其他和弦数据集中格式可能会有所不同)。
def parse_chord_label(chord_label):
# Define a regex pattern for chord symbols
pattern = r"([A-G][#b]?)(m|\+|%|o|sus)?(6|7|\⁷)?"
# Match the pattern with the input chord
match = re.match(pattern, chord_label)
if match:
# Extract the root, basic chord form and extension from the match obj
root = match.group(1)
form = match.group(2) or "M"
ext = match.group(3) or ""
return root, form, ext
else:
# Return None if the input is not a valid chord symbol
raise ValueError("Invalid chord symbol: {}".format(chord_label))
最后,我们需要生成依赖树。JHT 数据集只包含组成树,以嵌套字典形式编码。我们导入它们,并通过递归函数将它们转换为依赖树。我们函数的机制可以描述如下。
我们从一个完全形成的组成树和一个没有任何依赖弧的依赖树开始,这个依赖树仅由标记为序列元素的节点组成。算法将所有内部树节点与其主要子节点(它们都具有相同的标签)进行分组,并使用每个组中所有次要子节点关系来创建组标签和次要子节点标签之间的依赖弧。
def parse_jht_to_dep_tree(jht_dict):
"""Parse the python jazz harmony tree dict to a list of dependencies and a list of chord in the leaves.
"""
all_leaves = []
def _iterative_parse_jht(dict_elem):
"""Iterative function to parse the python jazz harmony tree dict to a list of dependencies."""
children = dict_elem["children"]
if children == []: # recursion ending condition
out = (
[],
{"index": len(all_leaves), "label": dict_elem["label"]},
)
# add the label of the current node to the global list of leaves
all_leaves.append(dict_elem["label"])
return out
else: # recursive call
assert len(children) == 2
current_label = noast(dict_elem["label"])
out_list = [] # dependency list
iterative_result_left = _iterative_parse_jht(children[0])
iterative_result_right = _iterative_parse_jht(children[1])
# merge the dependencies lists computed deeper
out_list.extend(iterative_result_left[0])
out_list.extend(iterative_result_right[0])
# check if the label correspond to the left or right children and return the corresponding result
if iterative_result_right[1]["label"] == current_label: # default if both children are equal is to go left-right arch
# append the dependency for the current node
out_list.append((iterative_result_right[1]["index"], iterative_result_left[1]["index"]))
return out_list, iterative_result_right[1]
elif iterative_result_left[1]["label"] == current_label:
# print("right-left arc on label", current_label)
# append the dependency for the current node
out_list.append((iterative_result_left[1]["index"], iterative_result_right[1]["index"]))
return out_list, iterative_result_left[1]
else:
raise ValueError("Something went wrong with label", current_label)
dep_arcs, root = _iterative_parse_jht(jht_dict)
dep_arcs.append((-1,root["index"])) # add connection to the root, with index -1
# add self loop to the root
dep_arcs.append((-1,-1)) # add loop connection to the root, with index -1
return dep_arcs, all_leaves
依赖解析模型
我们的解析模型的工作机制相当简单:我们考虑所有可能的弧,并使用弧预测器(一个简单的二分类器)来预测该弧是否应该成为树的一部分。
然而,仅根据我们试图连接的两个和弦来做出这个选择是相当困难的。我们需要一些上下文。我们通过构建一个变换器编码器来提供这样的上下文。
总结而言,我们的解析模型分为两个步骤:
-
输入序列通过变换器编码器传递,以丰富其上下文信息;
-
一个二分类器评估所有可能的依赖弧图,以过滤掉不需要的弧。

变换器编码器遵循标准架构。我们使用可学习的嵌入层将每个分类输入特征映射到一个连续的多维空间中的点。然后将所有嵌入加在一起,因此网络可以“决定”每个特征使用的维度。
import torch.nn as nn
class TransformerEncoder(nn.Module):
def __init__(
self,
input_dim,
hidden_dim,
encoder_depth,
n_heads = 4,
dropout=0,
embedding_dim = 8,
activation = "gelu",
):
super().__init__()
self.input_dim = input_dim
self.positional_encoder = PositionalEncoding(
d_model=input_dim, dropout=dropout, max_len=200
)
encoder_layer = nn.TransformerEncoderLayer(d_model=input_dim, dim_feedforward=hidden_dim, nhead=n_heads, dropout =dropout, activation=activation)
encoder_norm = nn.LayerNorm(input_dim)
self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=encoder_depth, norm=encoder_norm)
self.embeddings = nn.ModuleDict({
"root": nn.Embedding(12, embedding_dim),
"form": nn.Embedding(len(CHORD_FORM), embedding_dim),
"ext": nn.Embedding(len(CHORD_EXTENSION), embedding_dim),
"duration": nn.Embedding(len(JTB_DURATION), embedding_dim,
"metrical": nn.Embedding(METRICAL_LEVELS, embedding_dim)
})
def forward(self, sequence):
root = sequence[:,0]
form = sequence[:,1]
ext = sequence[:,2]
duration = sequence[:,3]
metrical = sequence[:,4]
# transform categorical features to embedding
root = self.embeddings"root")
form = self.embeddings"form")
ext = self.embeddings"ext")
duration = self.embeddings"duration")
metrical = self.embeddings"metrical")
# sum all embeddings
z = root + form + ext + duration + metrical
# add positional encoding
z = self.positional_encoder(z)
# reshape to (seq_len, batch = 1, input_dim)
z = torch.unsqueeze(z,dim= 1)
# run transformer encoder
z = self.transformer_encoder(src=z, mask=src_mask)
# remove batch dim
z = torch.squeeze(z, dim=1)
return z, ""
class PositionalEncoding(nn.Module):
def __init__(self, d_model: int, dropout: float = 0.1, max_len: int = 500):
super().__init__()
self.dropout = nn.Dropout(p=dropout)
position = torch.arange(max_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * (-np.log(10000.0) / d_model))
pe = torch.zeros(max_len, d_model)
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
self.register_buffer('pe', pe)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = x + self.pe[:x.size(0)]
return self.dropout(x)
弧预测器只是一个线性层,它的输入是两个和弦的隐藏特征的连接。由于矩阵乘法的强大功能,所有弧的分类步骤都是并行完成的。
class ArcPredictor(nn.Module):
def __init__(self, hidden_channels, activation=F.gelu, dropout=0.3):
super().__init__()
self.activation = activation
self.root_linear = nn.Linear(1, hidden_channels) # linear to produce root features
self.lin1 = nn.Linear(2*hidden_channels, hidden_channels)
self.lin2 = nn.Linear(hidden_channels, 1)
self.dropout = nn.Dropout(dropout)
self.norm = nn.LayerNorm(hidden_channels)
def forward(self, z, pot_arcs):
# add column for the root element
root_feat = self.root_linear(torch.ones((1,1), device=z.device))
z = torch.vstack((root_feat,z))
# proceed with the computation
z = self.norm(z)
# concat the embeddings of the two nodes, shape (num_pot_arcs, 2*hidden_channels)
z = torch.cat([z[pot_arcs[:, 0]], z[pot_arcs[:, 1]]], dim=-1)
# pass through a linear layer, shape (num_pot_arcs, hidden_channels)
z = self.lin1(z)
# pass through activation, shape (num_pot_arcs, hidden_channels)
z = self.activation(z)
# normalize
z = self.norm(z)
# dropout
z = self.dropout(z)
# pass through another linear layer, shape (num_pot_arcs, 1)
z = self.lin2(z)
# return a vector of shape (num_pot_arcs,)
return z.view(-1)
我们可以将变换器编码器和弧预测器放在一个单独的 PyTorch 模块中,以简化其使用。
class ChordParser(nn.Module):
def __init__(self, input_dim, hidden_dim, num_layers, dropout=0.2, embedding_dim = 8, use_embedding = True, n_heads = 4):
super().__init__()
self.activation = nn.functional.gelu
# initialize the encoder
self.encoder = NotesEncoder(input_dim, hidden_dim, num_layers, dropout, embedding_dim, n_heads=n_heads)
# initialize the decoder
self.decoder = ArcDecoder(input_dim, dropout=dropout)
def forward(self, note_features, pot_arcs, mask=None):
z = self.encoder(note_features)
return self.decoder(z, pot_arcs)
损失函数
作为损失函数,我们使用两个损失的和:
-
二元交叉熵损失:这个思想是将我们的问题视为一个二分类问题,每个弧都可以被预测或不被预测。
-
交叉熵损失:这个思想是将我们的问题视为一个多分类问题,在一个头 → 依赖弧中,我们需要预测所有其他和弦中哪个是正确的依赖。
loss_bce = torch.nn.BCEWithLogitsLoss()
loss_ce = torch.nn.CrossEntropyLoss(ignore_index=-1)
total_loss = loss_bce + loss_ce
后处理
有一个问题我们仍然需要解决。在训练过程中,并没有强制要求预测的弧必须形成树结构。因此,我们可能会遇到像弧循环这样的无效配置。幸运的是,我们可以使用一种算法来确保这种情况不会发生:Eisner 算法。²
我们不再只是简单地假设弧存在的概率大于 0.5,而是将所有预测结果保存在一个大小为(和弦数,和弦数)的方形矩阵(邻接矩阵)中,然后在该矩阵上运行 Eisner 算法。
# Adapted from https://github.com/HMJW/biaffine-parser
def eisner(scores, return_probs = False):
"""Parse using Eisner's algorithm.
The matrix follows the following convention:
scores[i][j] = p(i=head, j=dep) = p(i --> j)
"""
rows, collumns = scores.shape
assert rows == collumns, 'scores matrix must be square'
num_words = rows - 1 # Number of words (excluding root).
# Initialize CKY table.
complete = np.zeros([num_words+1, num_words+1, 2]) # s, t, direction (right=1).
incomplete = np.zeros([num_words+1, num_words+1, 2]) # s, t, direction (right=1).
complete_backtrack = -np.ones([num_words+1, num_words+1, 2], dtype=int) # s, t, direction (right=1).
incomplete_backtrack = -np.ones([num_words+1, num_words+1, 2], dtype=int) # s, t, direction (right=1).
incomplete[0, :, 0] -= np.inf
# Loop from smaller items to larger items.
for k in range(1, num_words+1):
for s in range(num_words-k+1):
t = s + k
# First, create incomplete items.
# left tree
incomplete_vals0 = complete[s, s:t, 1] + complete[(s+1):(t+1), t, 0] + scores[t, s]
incomplete[s, t, 0] = np.max(incomplete_vals0)
incomplete_backtrack[s, t, 0] = s + np.argmax(incomplete_vals0)
# right tree
incomplete_vals1 = complete[s, s:t, 1] + complete[(s+1):(t+1), t, 0] + scores[s, t]
incomplete[s, t, 1] = np.max(incomplete_vals1)
incomplete_backtrack[s, t, 1] = s + np.argmax(incomplete_vals1)
# Second, create complete items.
# left tree
complete_vals0 = complete[s, s:t, 0] + incomplete[s:t, t, 0]
complete[s, t, 0] = np.max(complete_vals0)
complete_backtrack[s, t, 0] = s + np.argmax(complete_vals0)
# right tree
complete_vals1 = incomplete[s, (s+1):(t+1), 1] + complete[(s+1):(t+1), t, 1]
complete[s, t, 1] = np.max(complete_vals1)
complete_backtrack[s, t, 1] = s + 1 + np.argmax(complete_vals1)
value = complete[0][num_words][1]
heads = -np.ones(num_words + 1, dtype=int)
backtrack_eisner(incomplete_backtrack, complete_backtrack, 0, num_words, 1, 1, heads)
value_proj = 0.0
for m in range(1, num_words+1):
h = heads[m]
value_proj += scores[h, m]
if return_probs:
return heads, value_proj
else:
return heads
def backtrack_eisner(incomplete_backtrack, complete_backtrack, s, t, direction, complete, heads):
"""
Backtracking step in Eisner's algorithm.
- incomplete_backtrack is a (NW+1)-by-(NW+1) numpy array indexed by a start position,
an end position, and a direction flag (0 means left, 1 means right). This array contains
the arg-maxes of each step in the Eisner algorithm when building *incomplete* spans.
- complete_backtrack is a (NW+1)-by-(NW+1) numpy array indexed by a start position,
an end position, and a direction flag (0 means left, 1 means right). This array contains
the arg-maxes of each step in the Eisner algorithm when building *complete* spans.
- s is the current start of the span
- t is the current end of the span
- direction is 0 (left attachment) or 1 (right attachment)
- complete is 1 if the current span is complete, and 0 otherwise
- heads is a (NW+1)-sized numpy array of integers which is a placeholder for storing the
head of each word.
"""
if s == t:
return
if complete:
r = complete_backtrack[s][t][direction]
if direction == 0:
backtrack_eisner(incomplete_backtrack, complete_backtrack, s, r, 0, 1, heads)
backtrack_eisner(incomplete_backtrack, complete_backtrack, r, t, 0, 0, heads)
return
else:
backtrack_eisner(incomplete_backtrack, complete_backtrack, s, r, 1, 0, heads)
backtrack_eisner(incomplete_backtrack, complete_backtrack, r, t, 1, 1, heads)
return
else:
r = incomplete_backtrack[s][t][direction]
if direction == 0:
heads[s] = t
backtrack_eisner(incomplete_backtrack, complete_backtrack, s, r, 1, 1, heads)
backtrack_eisner(incomplete_backtrack, complete_backtrack, r+1, t, 0, 1, heads)
return
else:
heads[t] = s
backtrack_eisner(incomplete_backtrack, complete_backtrack, s, r, 1, 1, heads)
backtrack_eisner(incomplete_backtrack, complete_backtrack, r+1, t, 0, 1, heads)
return
结论
我提出了一种用于和弦序列依赖解析的系统,该系统使用变换器构建上下文和弦隐藏表示,并使用分类器来选择两个和弦是否应该通过弧连接。
相较于竞争系统,主要的优点在于该方法不依赖于任何特定的符号语法,因此它可以同时考虑多种音乐特征,利用序列上下文信息,并对噪声输入产生部分结果。
为了保持文章的适当长度,解释和代码都集中在系统中最有趣的部分。你可以在这篇科学文章中找到更完整的解释,所有代码都可以在这个 GitHub 仓库中找到。
(所有图片均由作者提供。)
参考文献
-
D. Harasim, C. Finkensiep, P. Ericson, T. J. O’Donnell, 和 M. Rohrmeier,“爵士和声树库”,载于国际音乐信息检索会议(ISMIR)论文集,2020 年,第 207–215 页。
-
J. M. Eisner,“依存解析的三种新概率模型:一种探索”,载于国际计算语言学大会(COLING)论文集,1996 年。
现代数据科学家必备的 Julia:5 个你不能忽视的卓越特性
以趣味和机智进行解释
·发表于 Towards Data Science ·9 分钟阅读·2023 年 5 月 24 日
--

图片由我使用 Midjourney 制作。
是的,Python 的使用更为广泛。是的,它有更多的库。是的,我通过 Python 赚取生计,但这些并不能证明核心本地语言比 Julia 更好。
这就像 iOS 与 Android 的争论一样。仅仅因为更多的设备运行在 Android 上(Python 的许多使用案例),并且它有更多的第三方集成(Python 库),并不意味着 Android(Python)实际上比 iOS(基础的 Julia)更好。
事实上,有很多 iOS 功能是 Android 多年来一直羡慕的,尽管 Android 拥有庞大的用户基础。在这篇文章中,我们将看看 Julia 的一些特性,我相信 Python 开发者会喜欢这些特性。
1. 速度
Julia 用户对其语言的速度感到异常自豪——就像对自己孩子的骄傲一样。他们这样做是对的。Julia 是历史上最快的语言之一,并且与 C、C++ 和 Fortran 一起,属于PetaFlop 组。
Petaflops 是一个计算速度单位,相当于每秒一千亿亿(¹⁰¹⁵)次浮点运算。
目前,大多数人没有能够达到 PetaFlops 的机器,因此我们只能尝试一些基本操作。我们先来比较一下 Flux.jl 和 TensorFlow,它们是这两种语言各自生态系统中的基石深度学习库。
我们将使用 TensorFlow 的 GradientTape 来对一个多项式进行微分并测量性能:
import time
import tensorflow as tfstart = time.time()
x = tf.Variable(5.0)with tf.GradientTape() as tape:
y = 3 * x ** 3 + 4 * x ** 2 + 6 * x + 5
# dy_dx = 9x² + 8x + 6
dy_dx = tape.gradient(y, x)print(time.time() - start)
[OUT]: 0.003016233444213867
第一次运行花费了超过一秒钟,所以我重新运行了代码块,让 TensorFlow 有一个开局优势。现在,让我们对 Julia 做同样的事情:

图片由我制作
第一次运行大约需要 ~0.002,已经比 TensorFlow 的起始速度快。Flux.jl 的第二次运行速度快了大约 450 倍!随着我们运行代码更多次,它对其他输入也变得更快。
如果你觉得比较深度学习库范围太窄,我们可以尝试原生 Python 和 Julia 代码。我们将基准测试一个返回 第 n 个 Fibonacci 数字的简单函数,这样就足够了。
import time
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
我愚蠢地进行这次比较时不知道 Python 计算 Fibonacci 数列第 100 个数字需要多长时间。结果发现要花很长时间。
所以,我尝试了第 40 个数字,大约花了 21 秒:
start = time.time()
print(fib(40))
print("Execution time in Python: ", time.time() - start)
Execution time in Python: 20.88260841369629
[OUT]: 102334155
请记住,我故意选择了最慢的 Fibonacci 数字计算方法,以便查看在最坏情况下这些语言的比较。
现在,让我们尝试 Julia。
using TimerOutputs
function fib(n)
if n == 0
return 0
elseif n == 1
return 1
else
return fib(n-1) + fib(n-2)
end
end@time fib(40)
[OUT]:
102334155
0.527796 seconds
Julia 的版本快了大约 40 倍。这就像现实生活中扭曲的乌龟与兔子的故事,最后兔子一直赢得比赛。
公平地说,NumPy 实际上在矩阵操作方面比 Julia 更快,但我们必须记住 NumPy 大部分是用 C 编写的。
2. 互操作性
Julia 的灵感来源于多种语言,如 Julia 的贪婪 中所述。这使得 Julia 与其他语言友好,你可以轻松调用 C、Python、Fortran 或 R 代码。这允许这些语言之间的代码和库的平滑集成。
既然我们不关心其他内容(包括 R),我们将查看 PyCall 包,以运行像 Pandas 这样的 Python 库:
using PyCall
# Create a random array
a = rand(1000)# Import Pandas
pd = pyimport("pandas")data = Dict("name" => ["Alice", "Bob", "Charlie"],
"age" => [25, 30, 35],
"city" => ["New York", "Los Angeles", "Chicago"])# Create a dataframe
pd.DataFrame(data).head()

图片由作者提供
pyimport 函数将 Python 库加载到变量中。然后,我们可以在 Pandas 函数和类中使用 Julia 语法和对象,就像我们用 Julia 字典创建 DataFrame 一样。
pd 对象可以几乎与 Python 相同使用(.plot() 尚不支持):
penguins = pd.read_csv("data/penguins.csv")
penguins.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 344 entries, 0 to 343
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 species 344 non-null object
1 island 344 non-null object
2 bill_length_mm 342 non-null float64
3 bill_depth_mm 342 non-null float64
4 flipper_length_mm 342 non-null float64
5 body_mass_g 342 non-null float64
6 sex 333 non-null object
dtypes: float64(4), object(3)
memory usage: 18.9+ KB
penguins.groupby("island").count()

图片由我提供
3. 数学支持
Julia 的主要卖点之一是它专注于科学计算,这意味着它对数学和统计有很好的支持。
首先,让我们看看 MATLAB 风格的多项式书写语法:
x = 73
4x² - 8x + x
[OUT]: 20805
(x + 1)x + 5x
[OUT]:5767
写系数和变量时可以省略乘法符号。这使得代码更易读,更容易输入。
Julia 的安装还附带了一整套常用的数学函数。以下是幂、对数和根的列表:

Julia 文档的截图。
以及大约 30 个三角函数:

Julia 文档的截图。
所有这些函数直接从全局范围内提供 —— 不需要导入。在 Python 中,你需要 math 模块来进行基本数学运算,或者安装额外的依赖项,如 NumPy 或 SciPy。
我认为,对于一个被公认为科学计算之王的语言,不支持基本的微积分和线性代数,以及缺乏向量化功能是很尴尬的。
在 Julia 中,任何数组或矩阵使用任何函数(无论是原生函数还是用户定义函数)都可以实现向量化:
array = [1, 2, 3]
array .^ 5
[OUT]:
3-element Vector{Int64}:
1
32
243
array .+ x
[OUT]:
3-element Vector{Int64}:
74
75
76
只需在任何函数前添加一个 .(点),即可实现向量化(对所有数组元素同时应用操作,而不是在循环中),这会带来显著的性能提升。
向量化对于命名函数也是可能的;只需在打开括号前加点:
exp.(array)
3-element Vector{Float64}:
2.718281828459045
7.38905609893065
20.085536923187668
log.(array)
3-element Vector{Float64}:
0.0
0.6931471805599453
1.0986122886681098
4. 多重分发
Julia 是实现多重分发的最佳语言之一。
多重分发是一种技术,允许一个函数具有多种方法实现,每种实现都专门针对特定的参数类型。这种技术被称为“通用函数”或“多方法”。
像 Common Lisp、Dylan 和 Scala 这样的语言也支持此功能。Julia 的实现独特在于它是内置于语言中的,并且在标准库中广泛使用,这使得该功能的使用更加一致和高效。Julia 的多重分发还设计为与其 JIT 编译器无缝配合,从而实现更快的代码执行。
这是一个简单的 add 函数的多重分发示例:
function add(x::Number, y::Number)
return x + y
end
function add(x::String, y::String)
return x * " " * y
end# Define methods for specific argument types
add(x::Number, y::Unitful.Quantity) = add(x, y.value)
add(x::Unitful.Quantity, y::Number) = add(x.value, y)
add(x::Unitful.Quantity, y::Unitful.Quantity) = add(x.value, y.value)
# Usage of the function
julia> add(2, 3)
5
julia> add("hello", "world")
"hello world"
julia> add(2u"m", 3u"cm")
2.03m
即使你不懂 Julia,你也可以看到我们在定义五种不同数据类型对之间的加法操作:
-
数字
-
字符串
-
数字和带单位的量,如米或厘米
-
带单位的量和数字
-
带单位的量
你可以为任意多的数据类型对定义 add,它每次都会有不同的表现,而不会有任何性能损失。
Python 在函数和类中使用鸭子类型来确定对象的类型和行为。这种鸭子类型虽然允许代码的灵活性,但比多重分发的预测性差且容易出错。
5. 命令行
我认为自 Guido Van Rossum 发布 Python 初始版本以来,Python 解释器就没有更新过。它实在是过于基础,几乎没有用。
至于 Julia 的 REPL,它类似于 Jupyter 但在终端中运行。让我们看看它的一些伟大功能,从帮助模式开始。
输入 julia 进入 REPL 后,你可以通过按下‘?’并输入名称来获取任何变量、函数或类的帮助:

GIF 由作者提供
一旦按下‘?’,REPL 进入帮助模式。输出格式整齐,带有一些代码高亮,这已经比 Python 解释器的 help 函数好得多。
REPL 还有一个特殊的 ans 变量,用于存储最后一个表达式的结果。这类似于 Jupyter 中的 _(下划线)操作符,功能类似。

GIF 由作者提供
在 REPL 模式下,您可能需要运行一两个终端命令,但又不希望丢失会话变量。在 Python 解释器中,这是不可能的。
在 Julia REPL 中,您可以通过在新行中按‘;’(分号)进入 Shell 模式。在那里,您可以运行任意数量的终端命令,然后通过按Backspace返回到会话中。

作者提供的 GIF
哦,我提到过 REPL 还有制表符补全功能吗?
REPL 还附带一些魔法命令,如@time:
julia> @time println("Bam!")
Bam!
0.000114 seconds (7 allocations: 144 bytes)
结论
在这篇文章中,我们看到了一些 Julia 相对于 Python 的最佳功能。虽然 Julia 要追赶 Python 还需要相当长的时间——绝对是几年——但它确实有了一个很好的开端。
分析TIOBE 指数,我们可以看到,Julia 是相对于其初始发布而言增长最快的语言之一。这使得 Julia 成为任何从事数据科学、科学计算或需要高性能和表达性代码的领域的工作者值得关注的语言。
喜欢这篇文章和它那奇怪的写作风格吗?想象一下,您可以访问更多类似的文章,全部由一位才华横溢、迷人、风趣的作者(就是我 :)。
仅需 4.99 美元的会员费用,您不仅能获得我的故事,还有来自 Medium 上最聪明才智的知识宝库。如果您使用我的推荐链接,您将获得我的超级感激和一个虚拟的击掌,以支持我的工作。
[## 使用我的推荐链接加入 Medium — Bex T.
获取独家访问权限,享受所有⚡高级⚡内容,无限制地浏览 Medium。通过买一杯咖啡来支持我的工作…
ibexorigin.medium.com](https://ibexorigin.medium.com/membership?source=post_page-----c5b5b389547f--------------------------------) 
图片由我提供。通过 Midjourney。
Julia 魔法 太少人知道
原文:
towardsdatascience.com/julia-magic-too-few-people-know-about-330a7039a11a
Julia 编程语言的一些不太为人知的功能。
·发表于 Towards Data Science ·阅读时间 10 分钟·2023 年 10 月 15 日
--

(图像由作者提供)
介绍
就编程语言及其范式而言,Julia 的方法极为独特。与今天流行的其他多范式编程语言相比,Julia 在许多方面显著不同。尤其在范式方面,即语言中的类型(持有数据)如何与语言中的函数或方法协作。对此已经提出了许多不同的解决方案,每种语言往往都处于某一类别 — 尽管不一定完全如此。大多数现代语言通过这些范式桥接了多个编程概念,这使得大多数现代编程语言都是多范式的。在这个大伞下,Julia 语言也被包括在内。
尽管 Julia 是一种多范式编程语言,但它建立在一个以多重分派为中心的独特范式上。这并不是说这种范式完全新颖,这种范式的基础可以在标准元语言(SML)中找到。实际上,因为 Julia,我回去尝试了那个语言——这是一次非常有趣的经历,如果你想了解更多关于这个经历的内容,这里有一篇文章的链接:
试着用 SML 语言编写和编译一些基本代码
towardsdatascience.com
Julia 能做很多完全独特的事情,这些功能在语言中是独一无二的。这种范式及其特性集为编程语言带来了很多兴奋感和新能力,但也创造了许多障碍。利用这些复杂性对数据科学来说意义重大。能力带来了复杂性,考虑到 Julia 中某些有些独特的细微差别,用户可能会发现获得完整的 Julia 体验有些棘手。需要学习的东西很多,Julia 是一个非常棒的语言,值得充分利用!
就数据科学而言,Julia 是新手中的新秀,但其社区开发一直很稳固,语言每天都变得越来越有前景。也就是说,在你的数据科学项目中使用 Julia 时,了解这门语言非常有用。尤其是考虑到 Julia 的 Base 中有很多科学内容。Julia 是考虑到数据科学而设计的,因此以这种方式学习 Julia 对于掌握语言中的数据科学非常有帮助!
初始化参数
我有时会忘记 Julia 的一个特性,即初始化参数。这允许我们使用一些操作获得一个参数。这在许多地方都有应用,我发现这非常有用。这对于创建一个从另一个提供的参数中拉取数据的默认值特别有用。
function remove!(vec::Vector{Int64}, index::Int64 = length(vec))
end
在这里我们创建了一个 index 参数,默认情况下它将始终是 vec 的 length。很有用!在我看来,这比那些没有这个特性的语言中的解决方案要好得多。考虑到这一点,我非常欣赏 Julia 中这个特性——一个新的 Julia 用户肯定会错过它,一个经验丰富的用户也可能会忘记它的存在——但在情况合适时,它确实是一个非常好的功能,并且使 API 远远优于其他!
检查方法
Julia 中的 Methods 带有一种在语言及其范式中独特的内涵。在 Julia 中,方法被定义为函数名称与为该函数编写方法的参数类型一起出现。换句话说,
function example(x::Any)
end
在上面的例子中,example是一个函数,example(::Any)是它的第一个方法。我们不仅定义了函数,还定义了它的第一个方法。我们可以使用methods方法将这些方法放入Base.MethodList中。这对于任何我们可能想要进行的自省都很有用,并且MethodList的显示对于学习函数的分发非常有帮助。Julia 系统的显著优点是构建的函数更少——方法更多。显著的缺点是这意味着你需要学习每个函数的多个方法以及它们如何应用于每种类型。幸运的是,这种方法对于学习这些内容非常有帮助。
methods(example)

(作者提供的图片)
不过,方法自省的应用不仅限于学习函数。这也是我项目 Olive 的多重分发笔记本的基础。在这种情况下,我使用方法列表来寻找构建单元的新方法——使加载扩展就像定义方法一样简单。如果你想查看该项目,这里是一个链接:
[## GitHub - ChifiSource/Olive.jl: 纯 Julia 笔记本
纯 Julia 笔记本。在 GitHub 上创建一个账户,贡献于 ChifiSource/Olive.jl 的开发。
github.com](https://github.com/ChifiSource/Olive.jl?source=post_page-----330a7039a11a--------------------------------)
使用 Julia,我们可以轻松检查任何函数及其方法——以及Main中的几乎所有其他内容。自省在这种语言中非常突出,并且非常有用,因此这无疑是我非常欣赏 Julia 的另一个特性。
观察如何在 Julia 中使用自省来处理类型
towardsdatascience.com
解决这个难题的最重要部分是Method类型中的字段sig。通过使用它,我们可以在代码中描述有关方法的内容,这在某些情况下非常有用。
methods(example)[1].sig
Tuple{typeof(Main.NPaeMcfIjB.example), Any}
这个签名包含了我们的Function的类型和每个参数的类型,这个例子有一个,Any。
删除方法
我觉得 Julia 非常酷的一点是,我们既可以添加也可以删除方法。从本质上讲,这意味着任何Method都可以被替换,方法可以根据特定情况被移除。Julia 的 Base 部分,或者任何添加的 Julia 包,都对你可以做的事情非常开放。在 Julia 中,我们可以import并扩展一些方法。
import Base: cd
我们还可以删除方法。很长一段时间我不知道可以这样做,当这种情况发生时,我总是很兴奋地分享它。现在我们已经导入了 cd,我们使用 Base.delete_method 对该函数的 Method 进行删除。我们通过 methods 方法检索这个 Method — 天哪。
Base.delete_method(methods(cd)[1])
调度一个参数
Julia 的另一个很棒的方面是类型参数。参数是类型的区分,它告诉我们关于结构是如何构造的一些信息。例如,以下的 Vector 具有一个表示其元素类型的参数 Int64。
vec = Vector{Int64}([5, 10, 15, 20])
这在多种情况下都非常有用。对于新用户来说,我看到参数是学习 Julia 的一个常见障碍 — 不熟悉类型参数的确可能使这变得令人望而生畏。然而,它们非常有用,而且相对简单易用。这些参数用于创建参数化的多重调度或参数化多态性 — 根据参数变更多个类型的泛型函数。这也是 Julia 中一个非常强大的特性。
function example(vec::Vector{Int64} = [1, 2])
end
另一个非常酷的方面是我们可以根据超类型进行调度。这意味着在我们的抽象层次结构中的任何点,我们可以使用多重调度来涵盖所有下面的类型。例如,我们希望所有 Real 数字都作为参数……我们通过在参数中的抽象类型前使用子类型操作符 <: 来实现这一点。
function example(vec::Vector{<:Real})
end
现在 example 可以与任何包含 Real 子类型的 Vector 一起使用。如果我们不使用子类型。通过这种方式,我们可以尽可能通用地编写方法。结果是,Julia 项目所需的代码要少得多 — 这种范式使得精确定位所需的东西并为其编写方法变得极其简单。锦上添花的是,能够用任何 Method 完成这件事。
超棒的 comprehensions
Julia 具有我使用过的任何编程语言中最佳的理解能力。这部分得益于 Julia 表达性丰富的语法,它在多方面都有贡献。例如,Julia 的解析器不关心空白符 — 我们也可以将任何内容用 begin 和 end 包裹起来。由于 comprehensions 在 Julia 中非常有用(且快速),我很少看到使用传统的 for 循环的理由。唯一的例外是当我想使用 continue 或 break 时。另一个让这些在这种情况下非常有用的原因是它们总是返回一个 Vector。
myvec = [x + 1 for x in 1:5]
5-element Vector{Int64}: 2 3 4 5 6
很少有编程语言的 comprehensions 如此灵活。我认为更多用户应该尝试利用这一点。在上面的例子中,我们有一个完整的条件和一个返回,没有附加或类似的操作。这是 Julia 的一个非常好的特性,可以使代码变得极其简洁。
[begin
if x > 5
true
else
false
end
end for x in myvec]

(图像由作者提供)
表达式检查
在元编程方面,Julia 是一门带来许多可能性的语言。通过一些 Julia 的经验,我们可能会熟悉 Julia 中元编程的基础知识。这主要通过 eval 和 Meta.parse 完成。
exp = Meta.parse("x = 5")
eval(exp)
Meta 是 Base 内部的一个 Module,这与 Base.parse 是不同的方法。虽然这是这种类型检查的一个更基础形式,但我们也可以通过查看字段进一步了解 Expr。

Expr 类型的两个主要字段是 head 和 args。在这种情况下,head 是函数调用,而 args 是提供的参数。
fieldnames(Expr)
(:head, :args)
exp.head
:(=)
exp.args
2-element Vector{Any}: :x 5
eval 是模块的一个字段
对于这最后三项,我们将转到 REPL,因为在某些情况下我们需要终端来进行这些示例。
在作用域方面,Julia 提供了所需的一切——就像许多语言一样。Julia 和大多数同类语言一样,使用词法作用域。这是一种向下的作用域,就像山一样,随着你在作用域层次中向上移动,它变得越来越小。这意味着每次打开一个新作用域时,它可以访问上层作用域,但不能访问下层作用域。考虑以下示例:
y = 5
module Example
x = 5
end
在上述示例中,Example 位于 Main 下面的一个新范围内。这意味着 Example 可以访问 Main,但 Main 无法访问 Example。因此,y 在 Example 中定义,但 x 在 Main 中未定义。在编程中执行大多数操作时,如循环、条件判断和方法调用,都会建立一个新的范围层次,并拥有自己的作用域。这是编程中需要考虑的重要因素。
在 Julia 中,模块是包含名称的容器,这些名称本质上只是字段。这些名称是该模块内定义的所有内容。在每个模块内部,我们还有一个本地的 eval。因此,在之前提到的 Example 函数的情况下,Example.eval 是一个函数。
Example.eval(Meta.parse("x += 1"))
example.x
6
管道运算符
另一个我常常忘记的非常酷的功能是 Julia 具备管道功能。管道允许我们以非常易读的方式将多个函数调用链接在一起。通过管道,我们将多个函数调用合并成一行代码。Julia 中的管道功能也很简单而且简洁。这是通过 |> 管道运算符以实用的方式逐个函数完成的。
julia> do_stuff(a::Int64) = a * 5 * 2 - a + 2
do_stuff (generic function with 1 method)
julia> add5(a::Int64) = a += 5
add5 (generic function with 1 method)
让我们将一个数字通过这两个函数:
julia> a |> add5 |> do_stuff
92
重定向标准输出
我想谈的最后一点是,在 Julia 中重定向 IO 是意外简单的。在其他语言中,例如 Python,这是一项令人惊讶的艰巨任务,需要大量知识和工作。在 Julia 中,这只是一个简单的方法调用,非常容易使用。要在 Julia 中重定向标准输出,我们只需使用redirect_stdout方法!唯一的挑战是这个Method需要一个IOStream或Pipe。只有Pipe在内存中,因此最简单的用例是将其用来写入文件。
open("myfile.txt", "w") do o
redirect_stdout(o) do io
println("hello!")
end
end
这方面可以做很多事情,如果能存储在内存中,还能做更多。我有一整篇文章详细讨论了这个函数及其所有用法。如果你想了解更多,这里是那篇文章的链接:
关于在 Julia 中重定向 STDIO,你需要知道的一切。
结论
Julia 的最大优势在于它的许多特性如何发挥其核心范式。Julia 的最大弱点在于这些许多特性为新用户使用它们设置了障碍。此外,这可能使新用户更容易在代码中迷失。Julia 的语法能够使代码尽可能简洁,同时仍然非常易读,但如果你不知道如何阅读它,仍然存在问题。话虽如此,Julia 还有很多令人惊叹的特性,我最喜欢的 Julia 方面是很多人甚至不知道它们的存在!希望今天分享这些内容对你有帮助!感谢阅读!
七月版:数据科学家的气候资源
月刊
从找到合适的数据集到让人工智能变得更加环保
·
关注 发表在 Towards Data Science ·4 分钟阅读·2023 年 7 月 4 日
--
图片由 Laura Pluth 提供,来源于 Unsplash
对我们许多人来说,夏天的到来曾经是简单的兴奋原因:学校放假了;工作日程通常会稍微轻松一些;海滩或附近公园的慵懒下午的前景令人向往。
我们并不是要打击你积极的夏季情绪(作为一个加拿大团队,我们和其他人一样珍惜一个美丽的阳光明媚的日子),但如今很难不对温暖的季节有更复杂的情感反应。我们的许多读者生活在受野火(及其远-reaching、跨境的烟雾)、干旱、洪水和其他极端天气事件影响的地区,我们几乎可以确定在未来几年中会经历更多这些气候变化相关现象。
尽管与许多其他职业不同,数据从业者在塑造气候讨论和带来实际改变方面有着重要作用——无论是通过帮助社区和政策制定者更好地认识他们选择的影响,还是通过建模(在某些情况下甚至是构建)潜在的解决方案。
我们汇集了一系列关注气候的文章和资源,激发你思考如何利用数据和机器学习工具来应对当前(及未来)的挑战。我们希望我们的阅读推荐能激励你,至少让你多了解和参与这些讨论。
在我们深入之前,想要再次感谢你一直以来的支持。对于那些希望做出有意义贡献的人,考虑成为 Medium 会员。
TDS 编辑精选
-
五个免费且可靠的天气数据源(安东尼·鲍姆,2023 年 5 月,6 分钟)和 寻找气候变化数据集的前 5 个地方(尤金妮亚·安内洛,2023 年 6 月,6 分钟)
在数据科学家可以认真开始处理气候问题之前,他们首先需要获取强大、可靠、最新的数据。安东尼·鲍姆和尤金妮亚·安内洛分别编制了一份实用的资源清单,提供了这些需求。
-
航班影响:将碳排放纳入行程(2022 年 1 月,4 分钟)
当我们日常生活中很少涉及这两个领域时,提升对我们消费习惯与气候变化之间联系的认识是困难的。尼娜·斯维尼旨在通过创建一个应用程序来改变这种情况,该应用程序可以告知旅行者他们的行程所产生的排放。
-
气候变化时间序列:通过聚类减少食物浪费(2023 年 6 月,6 分钟)
维托尔·塞尔凯拉关于时间序列分析的优秀系列从多个角度探讨了气候问题。最近的一篇文章探讨了食物浪费的关键问题:“减少过度生产是减少温室气体排放的重要里程碑。我们可以通过更好地了解我们需要多少来解决这个问题。”
-
使用 NASA 太空研究评估全球温度异常: 第一部分(2022 年 10 月,12 分钟)和 第二部分(2023 年 6 月,10 分钟)
我们如何解释最近极端天气事件的频率?喜马拉雅·比尔·施雷斯塔将 NASA 数据作为动手探索全球地表温度的起点。
-
使用 Python 访问和可视化数字高程模型(2023 年 3 月,7 分钟)
政府和其他组织需要使用地理空间数据分析来更好地准备应对变化的气候,并保护人们和基础设施免受其潜在灾难性影响。帕尔瓦提·克里希南(与合著者马赫迪·法亚兹巴赫什和凯·凯泽)详细探讨了数字高程模型在这一背景下可能发挥的作用。
-
绿色人工智能:提高 AI 可持续性的方法和解决方案(2023 年 6 月,9 分钟)
训练、部署和运行计算密集型模型的环境成本正成为一个主要关注点——尤其是随着生成式 AI 工具的普及。Federico Peccia 最近对绿色 AI 计划的概述和推动该领域创新的研究,对于任何关心 AI 足迹可持续性的人来说,都是一个有用的入门指南。
原创特性
探索我们最新的资源选择和阅读推荐。
-
看到 AI 大局的挑战 退后一步(或两步),探讨 AI 领域近期发展的更大主题——我们精选了有关这个不断发展的领域的一些最佳文章。
-
数据科学家总有新的 Python 技能需要学习 从新的包到创新的工作流程,不要错过我们精选的编程指导集合。
热门帖子
如果你错过了,以下是上个月在 TDS 上最受欢迎的文章。
-
分子生物学中的大型语言模型 作者:Serafim Batzoglou
-
利用 Falcon 40B 模型,最强大的开源语言模型 作者:Luís Roque
-
掌握提示工程以释放 ChatGPT 的潜力 作者:Idil Ismiguzel
-
我在推动提示工程极限时学到的东西 作者:Jacob Marks, Ph.D.
-
开源开发中的隐秘危机:行动呼吁 作者:Adam King
-
如何衡量机器学习嵌入的漂移 作者:Elena Samuylova
-
掌握 ChatGPT:利用大型语言模型进行有效总结 作者:Andrea Valenzuela
我们很高兴在六月迎来了一批新的 TDS 作者——他们包括 Quý Đinh、Anthony Baum、Pablo Porto、Raul Vizcarra Chirinos、Matthew Gazzano、Terence Shin、Sarang Gupta、Fiona Victoria、Mariya Mansurova 和 Christopher Landschoot,还有其他人。如果你有有趣的项目或想法要与我们分享,我们很乐意听取你的意见!
下个月见。
六月精选:激发灵感的项目
每月精选
发现合适主题及范围的艺术——数据科学和机器学习项目
·
关注 发表在 Towards Data Science ·4 分钟阅读·2023 年 6 月 5 日
--
图片由 Christopher Paul High 提供,来自 Unsplash
是什么让一个数据科学项目脱颖而出?在求职者需要拥有光鲜的作品集网站,而数据团队需要持续展示其商业价值的时代,这个问题比以往任何时候都更加关键。
技术能力当然是一个重要因素,但作为编辑我们常常意识到这还不够。我们与读者分享的最具吸引力的基于项目的文章确实展示了作者的专业知识和技能,但更重要的是,它们展示了对范围的理解。它们解决的问题可以是理论性的或实际的、与工作相关的或完全基于激情的;无论主题是什么,我们始终不忘它们的目标(以及不包括的内容),以及每一步如何使我们更接近解决方案。
我们不再详细讨论伟大的数据科学和机器学习项目的特点,而是邀请你亲自探索一些优秀的项目——我们精选了一系列近期项目的详细介绍,你可以从中找到灵感、指导,甚至是开发自己想法的实用路线图。
在我们深入之前,我们想要感谢你们一如既往的支持。对于那些希望做出有意义贡献的朋友,考虑成为 Medium 会员。
TDS 编辑精选
-
石头剪子布:量子计算的奇妙扭曲(2023 年 5 月,14 分钟)
最近关于量子计算的讨论很多,但对许多人来说,它仍然是一个有些模糊的概念。Kory Becker 找到了一个直观的方法来突出经典计算和量子计算的区别:创建一个模拟石头剪子布游戏中获胜手势的程序。
-
禁忌页面:美国图书禁令的数据分析(2023 年 3 月,17 分钟)
数据可以帮助我们对社会问题获得更深刻、更细致的理解,但要实现这一点,我们还需要对数据收集、分析和讲述有一个扎实的方法。Yennie Jun 对美国图书禁令令人担忧的趋势进行了深入探讨,这是这一过程如何促进建设性对话的有力例证。
-
构建一个识别我手写体的 AI——第一部分(2023 年 4 月,12 分钟)
个人激情项目可以作为学习复杂技术挑战的坚实起点。例如:Jonas Schröder 对他构建的将手写日记转换为纯文本文件的工具的详细介绍。
-
我如何用 AI 生成的曲目构建了一个 Lo-fi 音乐网页播放器(2023 年 1 月,11 分钟)
如果你对深度生成模型感到好奇,并且多年来一直偏爱 lo-fi 嘻哈音乐,那你会怎么做?好吧,如果你是Aleksandra Ma,你就会学习如何用 LSTM 模型生成 midi 曲目,然后将它们嵌入到一个有趣的网页播放器中,让其他人也能享受你的创作。
-
使用 K 均值聚类进行无监督学习:从图像生成色彩调色板(2023 年 4 月,13 分钟)
如果你觉得一切都已经做过了,你应该看看Nabanita Roy的最新项目,它利用无监督学习和 K 均值聚类基于图像输入生成色彩调色板。这是一个很好的提醒,告诉我们可以通过结合已建立的实践元素并赋予它们个人风格来表达我们的创造力。
-
与帕斯卡尔三角形的简短直接之行(2022 年 11 月,10 分钟)
明确定义的问题的一大优点是,即使你将新元素和视角融入解决方案中,你的工作也更可能保持集中和清晰。Rhys Goldstein关于路径寻址算法的文章很好地说明了这一点:它涉及数学、游戏、问题解决以及…布莱兹·帕斯卡,但各种动态部分(包括一些有趣的动画!)都服务于整体目标。
原始功能
探索我们最新的资源和阅读推荐。
-
数据科学专业知识有多种形式和形态
不要错过我们最新一批新作者的精彩贡献。
-
新兴的提示工程艺术
为了最大限度地利用生成型 AI 工具,我们需要与模型有效沟通。在这里,你会找到我们在这一主题上的最佳资源。
热门帖子
如果你错过了它们,这里是上个月 TDS 上最受欢迎的一些帖子。
-
10 个令人兴奋的大型语言模型(LLMs)项目创意,适合你的投资组合 作者:Leonie Monigatti
-
作为全职数据科学家,我如何保持对最新 AI 趋势的跟进 作者:Matt Chapman
-
GPT 模型是如何工作的 作者:Beatriz Stollnitz
-
Python 中的数据导向编程 作者:Tam D Tran-The
-
我作为数据科学总监的第一年学到的东西 作者:CJ Sullivan
-
AI 十年回顾 作者:Thomas A Dorfer
我们很高兴在五月迎来了新一批 TDS 作者,他们包括Lee Vaughan、Dan Wilentz、Brendan Mapes、Yuji Yamamoto、Andrea Valenzuela、Igor Šegota、Lando L、Przemek Pospieszny、Andreas Lukita、Dr. Bernd Ebenhoch、Jozsef Meszaros、Stephanie Lo、Mostafa Wael、Beatriz Stollnitz、Edgardo Solano Carrillo、Prabodh Agarwal、Markus Hubrich、Dan Jackson、Matt Tengtrakool、Benjamin Thürer、Matt Blasa、Simon Aytes和Jack Saunders等人。如果你有有趣的项目或想法要与我们分享,我们很愿意听听你的意见!
下个月见。
初级开发者编写多页 SQL 查询;高级开发者使用窗口函数
在记录的上下文中执行计算的一种优雅方法
·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 3 月 13 日
--

想象一下你经营一个全国性的电子商店。你必须确定每个商店对州销售额的贡献。
如果你刚开始学习 SQL,你可能会考虑一下,开发一个极其出色的查询,可能看起来像下面这样。
SELECT
s.state,
s.store_name,
s.total_sales,
s.total_sales / t.state_total_sales AS sales_contribution
FROM
(
SELECT
state,
store_name,
SUM(sales) AS total_sales
FROM
store_sales
GROUP BY
state,
store_name
) s
INNER JOIN (
SELECT
state,
SUM(sales) AS state_total_sales
FROM
store_sales
GROUP BY
state
) t ON
s.state = t.state
ORDER BY
s.state,
sales_contribution DESC;
|state|store_name|total_sales|sales_contribution|
|-----|----------|-----------|------------------|
|California|MegaMart|82500.00|0.61797752808988764045|
|California|ABC Mart|51000.00|0.38202247191011235955|
|New York|SuperMart|57000.00|0.60638297872340425532|
|New York|Corner Shop|37000.00|0.39361702127659574468|
|Texas|XYZ Store|35000.00|0.67961165048543689320|
|Texas|My Store|16500.00|0.32038834951456310680|
这肯定能完成工作。但如果你把这个解释给别人,他们会跟你过得很痛苦。
相反,如果你让一位高级开发者来做同样的事情,他们会写出如下的查询:
SELECT
state,
store_name,
SUM(sales) AS total_sales,
SUM(sales) / SUM(SUM(sales)) OVER (PARTITION BY state) AS sales_contribution
FROM
store_sales
GROUP BY
state,
store_name
ORDER BY
state,
sales_contribution DESC;
这个更加简洁的版本使用了窗口函数。
窗口函数创建一个与当前记录相关的记录窗口,并在该窗口内进行操作。在这个例子中,窗口是状态。
我第一次遇到窗口函数是在几年前,当时我为一个零售客户工作。他们的客户数据集非常庞大,他们希望查看每个顾客群体的购买模式变化——比如 80 年代和 90 年代出生的人群等。
## Python 到 SQL — 我现在可以快 20 倍地加载数据
上传大量数据的好坏丑方法
towardsdatascience.com ## 这 5 种 SQL 技巧涵盖了大约 80%的实际项目
加速你的 SQL 学习曲线。
towardsdatascience.com
从那时起,窗口函数成为了我最喜欢的工具,我在几乎每个分析项目中至少使用一次。这是因为我们经常需要在记录的上下文中进行计算。
本文是 SQL 中窗口函数的简单介绍。最重要的是,我们需要了解如何定义窗口以及我们在窗口上执行什么操作。
如果你打算跟随本文中的示例,让我们创建一个表并插入一些虚拟值。
CREATE TABLE store_sales (
store_id INT,
store_name VARCHAR(50),
state VARCHAR(50),
month DATE,
sales DECIMAL(10, 2)
);
INSERT INTO store_sales VALUES
(1, 'ABC Mart', 'California', '2022-01-01', 10000.00),
(2, 'XYZ Store', 'Texas', '2022-01-01', 7500.00),
(3, 'Corner Shop', 'New York', '2022-01-01', 12000.00),
(4, 'MegaMart', 'California', '2022-01-01', 25000.00),
(5, 'My Store', 'Texas', '2022-01-01', 5000.00),
(6, 'SuperMart', 'New York', '2022-01-01', 18000.00),
(7, 'ABC Mart', 'California', '2022-02-01', 15000.00),
(8, 'XYZ Store', 'Texas', '2022-02-01', 9000.00),
(9, 'Corner Shop', 'New York', '2022-02-01', 10000.00),
(10, 'MegaMart', 'California', '2022-02-01', 30000.00),
(11, 'My Store', 'Texas', '2022-02-01', 6000.00),
(12, 'SuperMart', 'New York', '2022-02-01', 20000.00),
(13, 'ABC Mart', 'California', '2022-03-01', 12000.00),
(14, 'XYZ Store', 'Texas', '2022-03-01', 10500.00),
(15, 'Corner Shop', 'New York', '2022-03-01', 15000.00),
(16, 'MegaMart', 'California', '2022-03-01', 27500.00),
(17, 'My Store', 'Texas', '2022-03-01', 5500.00),
(18, 'SuperMart', 'New York', '2022-03-01', 19000.00),
(19, 'ABC Mart', 'California', '2022-04-01', 14000.00),
(20, 'XYZ Store', 'Texas', '2022-04-01', 8000.00);
SQL 窗口函数的基础
这是一个查询,显示每个州每家商店的平均销售额和总销售额。
SELECT
store_name,
state,
SUM(sales) AS total_sales,
AVG(SUM(sales)) OVER (PARTITION BY state) AS state_average
FROM
store_sales
GROUP BY
store_name, state;
|store_name|state|total_sales|state_average|
|----------|-----|-----------|-------------|
|ABC Mart|California|51000.00|66750.000000000000|
|MegaMart|California|82500.00|66750.000000000000|
|Corner Shop|New York|37000.00|47000.000000000000|
|SuperMart|New York|57000.00|47000.000000000000|
|XYZ Store|Texas|35000.00|25750.000000000000|
|My Store|Texas|16500.00|25750.000000000000|
让我们仔细研究窗口函数。

作者插图。
每个窗口函数都有这两个部分。我们在 OVER 关键字后定义窗口。在此示例中,我们仅使用州列对数据集进行分区。操作将在共享相同州的记录之间执行。
此外,你可以使用 ORDER BY 关键字重新排列窗口记录。以下查询使用它来获取每家商店在其州内的排名。
SELECT
store_name,
state,
sales,
DENSE_RANK() OVER (PARTITION BY state
ORDER BY
sales DESC) AS store_sales_rank
FROM
store_sales;
|store_name|state|sales|store_sales_rank|
|----------|-----|-----|----------------|
|MegaMart|California|30000.00|1|
|MegaMart|California|27500.00|2|
|MegaMart|California|25000.00|3|
|ABC Mart|California|15000.00|4|
|ABC Mart|California|14000.00|5|
|ABC Mart|California|12000.00|6|
|ABC Mart|California|10000.00|7|
|SuperMart|New York|20000.00|1|
|SuperMart|New York|19000.00|2|
|SuperMart|New York|18000.00|3|
|Corner Shop|New York|15000.00|4|
|Corner Shop|New York|12000.00|5|
|Corner Shop|New York|10000.00|6|
|XYZ Store|Texas|10500.00|1|
|XYZ Store|Texas|9000.00|2|
|XYZ Store|Texas|8000.00|3|
|XYZ Store|Texas|7500.00|4|
|My Store|Texas|6000.00|5|
|My Store|Texas|5500.00|6|
|My Store|Texas|5000.00|7|
你可以使用RANK或ROW_NUMBER代替DENSE_RANK。这三个关键字之间的区别在于它们处理平局的方式。
ROW_NUMBER 会为平局分配一个顺序号,不重视平局。RANK 会为平局分配相同的排名并跳过下一个。例如,如果两个商店的销售值相同,它们都会得到第 1 名。但是第 2 名会被跳过,下一个则得到第 3 名。DENSE_RANK 也会为平局分配相同的编号,但不会跳过下一个编号。下一个记录将获得紧接着的排名。
我们可以使用窗口函数的有趣方法
窗口函数有许多令人兴奋的应用。正如我之前提到的,我在几乎每个 SQL 项目中都使用它。以下是我们可以使用窗口函数的一些常见方式。
由于我们已经在基础示例中查看了排名,这里不再重新讨论。但排名是窗口函数最常见的用例之一。
1. 计算累计总数
我们也可能需要在有时间数据的地方计算累计总数。换句话说,我们应该将所有先前的值累加到某一点。
在我们的商店销售示例中,这可能是每家商店自年初以来每个月底的销售额。这是完成此任务的查询。
SELECT
store_name,
MONTH,
sales,
SUM(sales) OVER (PARTITION BY store_name
ORDER BY
"month") AS running_total
FROM
store_sales;
|store_name|month|sales|running_total|
|----------|-----|-----|-------------|
|ABC Mart|2022-01-01|10000.00|10000.00|
|ABC Mart|2022-02-01|15000.00|25000.00|
|ABC Mart|2022-03-01|12000.00|37000.00|
|ABC Mart|2022-04-01|14000.00|51000.00|
|Corner Shop|2022-01-01|12000.00|12000.00|
|Corner Shop|2022-02-01|10000.00|22000.00|
|Corner Shop|2022-03-01|15000.00|37000.00|
|MegaMart|2022-01-01|25000.00|25000.00|
|MegaMart|2022-02-01|30000.00|55000.00|
|MegaMart|2022-03-01|27500.00|82500.00|
|My Store|2022-01-01|5000.00|5000.00|
|My Store|2022-02-01|6000.00|11000.00|
|My Store|2022-03-01|5500.00|16500.00|
|SuperMart|2022-01-01|18000.00|18000.00|
|SuperMart|2022-02-01|20000.00|38000.00|
|SuperMart|2022-03-01|19000.00|57000.00|
|XYZ Store|2022-01-01|7500.00|7500.00|
|XYZ Store|2022-02-01|9000.00|16500.00|
|XYZ Store|2022-03-01|10500.00|27000.00|
|XYZ Store|2022-04-01|8000.00|35000.00|
上述查询将按月份列对窗口中的记录进行排序。在任何月份,销售数据只会累积到该月份。
2. 与组统计数据比较。
可能会有需要将每条记录与其组平均值进行比较的情况。例如,我们可能会对查看每个店铺的状态平均值感兴趣。
这是用于此的 SQL 查询。
SELECT
store_name,
state ,
MONTH,
sales,
AVG(sales) OVER (PARTITION BY state, "month") AS running_total
FROM
store_sales;
|store_name|state|month|sales|running_total|
|----------|-----|-----|-----|-------------|
|MegaMart|California|2022-01-01|25000.00|17500.000000000000|
|ABC Mart|California|2022-01-01|10000.00|17500.000000000000|
|ABC Mart|California|2022-02-01|15000.00|22500.000000000000|
|MegaMart|California|2022-02-01|30000.00|22500.000000000000|
|ABC Mart|California|2022-03-01|12000.00|19750.000000000000|
|MegaMart|California|2022-03-01|27500.00|19750.000000000000|
|ABC Mart|California|2022-04-01|14000.00|14000.0000000000000000|
|SuperMart|New York|2022-01-01|18000.00|15000.000000000000|
|Corner Shop|New York|2022-01-01|12000.00|15000.000000000000|
|Corner Shop|New York|2022-02-01|10000.00|15000.000000000000|
|SuperMart|New York|2022-02-01|20000.00|15000.000000000000|
|Corner Shop|New York|2022-03-01|15000.00|17000.000000000000|
|SuperMart|New York|2022-03-01|19000.00|17000.000000000000|
|XYZ Store|Texas|2022-01-01|7500.00|6250.0000000000000000|
|My Store|Texas|2022-01-01|5000.00|6250.0000000000000000|
|My Store|Texas|2022-02-01|6000.00|7500.0000000000000000|
|XYZ Store|Texas|2022-02-01|9000.00|7500.0000000000000000|
|My Store|Texas|2022-03-01|5500.00|8000.0000000000000000|
|XYZ Store|Texas|2022-03-01|10500.00|8000.0000000000000000|
|XYZ Store|Texas|2022-04-01|8000.00|8000.0000000000000000|
3. 计算移动平均
移动平均在处理时间序列数据时非常典型。移动平均通常比单个数据点噪声更小。你总是能在金融数据分析中看到它,比如股市数据。但我们也可以在任何领域找到类似的应用。
这是针对我们店铺销售数据的 SQL 查询。它计算了每个店铺的 3 点移动平均。
SELECT
store_name ,
MONTH,
sales,
AVG(sales) OVER (PARTITION BY store_name
ORDER BY
MONTH ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg_sales
FROM
store_sales;
|store_name|month|sales|moving_avg_sales|
|----------|-----|-----|----------------|
|ABC Mart|2022-01-01|10000.00|10000.0000000000000000|
|ABC Mart|2022-02-01|15000.00|12500.0000000000000000|
|ABC Mart|2022-03-01|12000.00|12333.3333333333333333|
|ABC Mart|2022-04-01|14000.00|13666.666666666667|
|Corner Shop|2022-01-01|12000.00|12000.0000000000000000|
|Corner Shop|2022-02-01|10000.00|11000.0000000000000000|
|Corner Shop|2022-03-01|15000.00|12333.3333333333333333|
|MegaMart|2022-01-01|25000.00|25000.000000000000|
|MegaMart|2022-02-01|30000.00|27500.000000000000|
|MegaMart|2022-03-01|27500.00|27500.000000000000|
|My Store|2022-01-01|5000.00|5000.0000000000000000|
|My Store|2022-02-01|6000.00|5500.0000000000000000|
|My Store|2022-03-01|5500.00|5500.0000000000000000|
|SuperMart|2022-01-01|18000.00|18000.0000000000000000|
|SuperMart|2022-02-01|20000.00|19000.000000000000|
|SuperMart|2022-03-01|19000.00|19000.000000000000|
|XYZ Store|2022-01-01|7500.00|7500.0000000000000000|
|XYZ Store|2022-02-01|9000.00|8250.0000000000000000|
|XYZ Store|2022-03-01|10500.00|9000.0000000000000000|
|XYZ Store|2022-04-01|8000.00|9166.6666666666666667|
仔细观察我们如何定义窗口。除了通常出现的 PARTITION BY 和 ORDER BY 关键字,我们还使用了一些其他关键字。我们告诉 SQL 只考虑前 2 条记录和当前记录。通过更改参数,你甚至可以计算不同点的移动平均。
同样,你还可以计算前瞻性移动平均。以下是用于此的 SQL 查询:
SELECT
store_name ,
MONTH,
sales,
AVG(sales) OVER (PARTITION BY store_name
ORDER BY
MONTH ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING) AS moving_avg_sales
FROM
store_sales;
|store_name|month|sales|moving_avg_sales|
|----------|-----|-----|----------------|
|ABC Mart|2022-01-01|10000.00|12333.3333333333333333|
|ABC Mart|2022-02-01|15000.00|13666.666666666667|
|ABC Mart|2022-03-01|12000.00|13000.0000000000000000|
|ABC Mart|2022-04-01|14000.00|14000.0000000000000000|
|Corner Shop|2022-01-01|12000.00|12333.3333333333333333|
|Corner Shop|2022-02-01|10000.00|12500.0000000000000000|
|Corner Shop|2022-03-01|15000.00|15000.0000000000000000|
|MegaMart|2022-01-01|25000.00|27500.000000000000|
|MegaMart|2022-02-01|30000.00|28750.000000000000|
|MegaMart|2022-03-01|27500.00|27500.000000000000|
|My Store|2022-01-01|5000.00|5500.0000000000000000|
|My Store|2022-02-01|6000.00|5750.0000000000000000|
|My Store|2022-03-01|5500.00|5500.0000000000000000|
|SuperMart|2022-01-01|18000.00|19000.000000000000|
|SuperMart|2022-02-01|20000.00|19500.000000000000|
|SuperMart|2022-03-01|19000.00|19000.0000000000000000|
|XYZ Store|2022-01-01|7500.00|9000.0000000000000000|
|XYZ Store|2022-02-01|9000.00|9166.6666666666666667|
|XYZ Store|2022-03-01|10500.00|9250.0000000000000000|
|XYZ Store|2022-04-01|8000.00|8000.0000000000000000|
结论
窗口函数在意识到它们的实用性后,已经成为我的最爱。因此,通过这篇文章,我分享了基础知识和一些常用的窗口函数应用。
但你可以通过窗口函数做更多的事情。虽然这篇博客文章没有广泛涉及,但基础知识应该能让你编写出精彩的 SQL 语句。
感谢阅读,朋友!如果你喜欢我的文章,欢迎在LinkedIn、Twitter和Medium上保持联系。
还不是 Medium 会员?请使用此链接成为会员,因为你无需额外付费,我可以通过推荐你获得少量佣金。
Jupyter 已经拥有一个完美的文本编辑器:构建一个 Python IDE
原文:
towardsdatascience.com/jupyter-already-has-a-perfect-text-editor-building-a-python-ide-20063044749b
让 Jupyter 成为你的全能 IDE,提升你的 Python 开发体验
·发表于Towards Data Science ·阅读时间 6 分钟·2023 年 3 月 8 日
--

图片由Fotis Fotopoulos提供,来源于Unsplash。
本文是系列的第三部分。查看完整系列: 第一部分**, 第二部分, 第四部分**.
在系列的前几部分中,我们讨论了为什么许多开发者不认为 Jupyter 是一个完全整合的开发环境,以及缺乏一个强大的文本编辑器是主要原因之一。
因此,我们决定将 JupyterLab 视为一个可以从零开始创建我们自己的 Python IDE 的平台。将相同的理念扩展到其他编程语言仅需一次 Google 搜索。
为此,我们使用了 JupyterLab 的终端模拟器来安装 Neovim,并使其表现和外观类似于 VS Code。这样,我们可以构建一个 docker 镜像来打包整个工作空间,按我们的喜好进行定制。这个想法超越了 JupyterLab 和 Neovim。我们可以拥有一个配置好的工作环境,包括 Git 进行版本控制,pyenv 创建虚拟环境,以及 tmux 进行终端模拟。
在系列的前两部分中,我们安装了 Neovim,学习了我们的 Vim 绑定,并配置了编辑器的外观和基本设置。今天,我们将安装几个插件,使我们的文本编辑器更像一个 IDE。免得我们忘记,以下图片是我们最终目的地的“明信片”:

作者提供的图片
开始吧!
学习率是一个针对对 ML 和 MLOps 世界感兴趣的人的新闻通讯。如果你想了解更多这样的主题,可以在这里订阅。每个月的最后一个星期日你将收到我的更新和对最新 MLOps 新闻及文章的思考!
基本插件
当你首次安装 Neovim 时,一些流行编辑器(如 VS Code)自带的功能是缺失的。但不用担心,社区已经构建了几乎所有你能想到的功能,并且通常很容易设置。
在上一篇文章中,我们看到如何为 Neovim 设置插件管理器,以便在安装插件时让我们的生活更轻松。现在,安装新插件的过程通常是:
-
在
init.vim配置文件中添加插件名称 -
重启文本编辑器
-
运行
:PlugInstallNeovim 命令 -
重启文本编辑器以应用新的插件
NERDCommenter
掌握了这些知识后,让我们安装第一个插件——[nerdcommenter](https://github.com/preservim/nerdcommenter)。nerdcommenter是一个相对简单的插件,功能是注释或取消注释多行内容。要安装它,请在你的 Neovim 配置文件中添加以下行:
https://github.com/preservim/nerdcommenter
如果你正在跟随这个系列(你应该这样做),那么你的init.vim文件现在应该是这样的:
" General setup
set number
set relativenumber
set encoding=utf-8
set mouse=a
set scrolloff=10
set colorcolumn=80
set textwidth=80
" Set syntax highlighting
syntax on
" Remap navigation keys
noremap ; l
noremap l k
noremap k j
noremap j h
" Python specific settings
au BufNewFile,BufRead *.py
\ set tabstop=4 |
\ set fileformat=unix |
\ set softtabstop=4 |
\ set shiftwidth=4 |
\ set textwidth=79 |
\ set expandtab |
\ set autoindent
" Highlight trailing whitespace
au BufRead, BufNewFile *.py,*.pyw,*.c,*.h match BadWhitespace /\s\+$/
call plug#begin()
Plug 'https://github.com/vim-airline/vim-airline' " Show status bar
Plug 'https://github.com/vim-airline/vim-airline-themes.git' " Customize status bar
Plug 'https://github.com/ryanoasis/vim-devicons' " Display developer icons
Plug 'https://github.com/rafi/awesome-vim-colorschemes' " Change color Schemes
call plug#end()
" Set default colorscheme
colorscheme molokai
" Customize status bar
let g:airline_theme='molokai'
if !exists('g:airline_symbols')
let g:airline_symbols = {}
endif
" airline symbols
let g:airline_symbols.branch = ''
let g:airline_symbols.readonly = ''
let g:airline_symbols.maxlinenr = ''
为了避免再次复制粘贴整个文件,请记住每次添加新插件时,我们总是将其添加在call plug#begin()和call plug#end()指令之间。
接下来,重启编辑器并运行 Neovim 的:PlugInstall命令。你应该看到vim-plug(我们使用的插件管理器)安装了nerdcommenter工具以及其他插件:

作者提供的图片
你可以设置很多选项来决定nerdcommenter的行为,但我喜欢对像这样的简单插件保持简单。可能你应该记住的唯一快捷键是:
<leader>c<space>
这个快捷键执行了NERDCommenterToggle命令。通常,<leader>键是正斜杠字符(\)。因此,要测试插件,选择多行并按Shift + V(Vim 的可视模式——学习你的 Vim 绑定!)然后按\ + c + n。
我说过我们要对这个插件保持简单。不过,有一个配置我喜欢设置:默认在注释分隔符后添加空格。因此,让我们在init.vim文件的末尾添加以下行,并重启编辑器以使其生效:
" Customize NERDCommenter
let g:NERDSpaceDelims = 1 " Add spaces after comment delimiters by default
Tagbar
Tagbar是一个用于 Vim/Neovim 的代码大纲查看器。当处理包含多行代码的文件时,大纲查看器总是很方便。
要安装 Tagbar,请在 Neovim 配置文件中添加以下行,并按照通常的插件安装过程进行:
Plug 'https://github.com/preservim/tagbar' " Tag source code for navigation
不过,当你尝试用TagbarToggle命令切换轮廓查看器时,你会收到这个警告:

图片作者
这是一个需要其他工具才能正常工作的插件。在这种情况下,错误信息表示它需要一个名为exuberant-ctags的包。因此,让我们先安装它:
sudo apt update && sudo apt install exuberant-ctags
现在,我们可以重新启动编辑器,运行:TagbarToggle命令,一切应该能顺利运行:

作者提供的图片
好吧,我承认这不是最性感的轮廓,但等我们开始打开大型 Python 文件时再看看吧!
NERDTree
另一个我们将详细探讨的有用插件是[NERDTree](https://github.com/preservim/nerdtree)。这个插件添加了一个文件浏览器,是开始使用全新 Neovim 安装时的首选插件之一。像往常一样,将以下行添加到你的配置文件中:
Plug 'https://github.com/preservim/nerdtree' " Show file system explorer
安装插件后,你可以通过:NERDTreeToggle命令来使用它:

作者提供的图片
看起来我们越来越接近目标了!现在,NERDTree可以做很多事情,但我最喜欢的是它如何通过按m来操作文件系统。这样,你可以轻松地管理文件和文件夹,而无需离开文本编辑器:

作者提供的图片
在窗格之间导航
既然我们有了启动新窗格的插件,我们必须找到在它们之间导航的方法。为此,我喜欢使用以下配置:
" Use ctrl-[jkl;] to select the active split
nmap fl :wincmd k<cr>
nmap fk :wincmd j<cr>
nmap fj :wincmd h<cr>
nmap f; :wincmd l<cr>
将这些添加到你的init.vim文件中,将允许你使用关键词f和标准 Vim 导航键来切换窗格(尽管我们已经重新映射了这些键)。
荣誉提名
我会给你推荐另外两个你可能想尝试的插件:
它们的名称都是自解释的,所以你的作业是根据需要安装和配置它们。
像往常一样,为了保存进度,请使用docker commit命令,就像我们在上一篇文章中做的那样。你可以在DockerHub找到我的镜像。
在下一篇文章中,我们将开始处理自动补全和格式化!
关于作者
我叫Dimitris Poulopoulos,是一名为Arrikto工作的机器学习工程师。我为包括欧洲委员会、Eurostat、国际货币基金组织、欧洲中央银行、经济合作与发展组织和宜家在内的主要客户设计和实施了人工智能和软件解决方案。
如果你对阅读更多关于机器学习、深度学习、数据科学和数据操作的帖子感兴趣,请在Medium、LinkedIn或在 Twitter 上关注@james2pl。
所表达的观点完全是我个人的意见,不代表我的雇主的观点或意见。
Jupyter 已经有了完美的文本编辑器:这就是你可以配置它的方法
如何在 Jupyter 中获得类似 VS Code 的体验,使用一个优秀的文本编辑器
·发表于 Towards Data Science ·7 分钟阅读·2023 年 3 月 3 日
--

Douglas Lopes 在 Unsplash 上的照片
这篇文章是系列文章的第二部分。查看完整系列: 第一部分, 第三部分, 第四部分。
我们之前的文章指出,许多工程师并不认为 JupyterLab 是一个完整的 IDE。一个主要原因是 JupyterLab 缺乏像 VS Code 或 Sublime Text 这样强大的文本编辑器。
JupyterLab 允许用户创建和共享包含实时代码、方程式、可视化和叙述文本的文档。这是进行这种互动工作的完美工具。然而,事实是它的文本编辑器原始得像 Windows 记事本一样;你可以编写代码,但体验远非理想。
我们能对此做些什么吗?我们希望能有一个 Docker 镜像,可以在任何机器上随时运行并准备好工作区。用 VS Code 实现这一点并不容易,除非你愿意付费。但 JupyterLab 是免费的;它拥有一个 充满活力的社区,如果我们能够结合两者的优点,就能打造一个免费的、强大的、便携的工作空间。
让我们退一步,把 JupyterLab 看作一个平台,而不是 IDE。JupyterLab 有一个终端模拟器。这意味着你可以做几乎所有你能想到的事情。基于这个想法,在我们之前的文章中,我们开始安装 Neovim 并将其配置成像 VS Code 一样。
## 释放 JupyterLab 的潜力:发现你从未知道的强大文本编辑器
在 JupyterLab 中通过一个优秀的文本编辑器释放你的编码效率和生产力
towardsdatascience.com
我们的目标是创建一个包含强大笔记本编辑器和功能丰富的 Python IDE 的 JupyterLab 镜像。

作者提供的图像
今天,我们从配置编辑器的核心功能和外观开始。
学习速递 是一个针对那些对 ML 和 MLOps 世界感兴趣的人的新闻通讯。如果你想了解更多类似的主题,请在这里订阅。你将会在每个月的最后一个星期天收到我的更新和对最新 MLOps 新闻和文章的见解!
核心配置
我们从设置编辑器的核心设置开始。在你的 home 目录下,有一个名为 .config 的隐藏文件夹。进入该文件夹并创建一个名为 nvim 的新文件夹:
cd ~/.config && mkdir nvim
接下来,在 nvim 文件夹中创建一个名为 init.vim 的新文件:
cd nvim && touch init.vim
init.vim 是你将用来配置 Neovim 行为和外观的文件。
在上一篇文章中,我建议你通过完成内置的 Vim Tutor 教程来学习你的 Vim 绑定。我假设你已经完成了这个教程,因此我们将使用 Neovim 编辑任何文件。唯一习惯它的方式就是尽可能多地使用它,相信我,一旦你习惯了,你会惊讶于你可以更快地完成任何想做的事情。
那么,废话不多说,让我们打开并编辑你创建的文件:
nvim init.vim
我们的第一个设置将启用简单的功能,如行号、文本编码和语法高亮。将以下行复制粘贴到你的 init.vim 文件中:
" General setup
set number
set relativenumber
set encoding=utf-8
set mouse=a
set scrolloff=10
set colorcolumn=80
set textwidth=80
" Set syntax highlighting
syntax on
每当你看到以引号开头的行时,那是一条注释。你可以在正常模式下使用 :wq 保存并退出文件,然后重新打开它以查看更改。
接下来,Vim 和 Neovim 使用h j k l键进行导航。我不想移动手指来在文件中导航。因此,我喜欢将它们重新映射到手指已放置的位置:j k l ;。为此,请在init.vim中添加以下行:
" Remap navigation keys
noremap ; l
noremap l k
noremap k j
noremap j h
最后,让我们处理 Python 特定的设置,比如自动缩进和在79个字符处设置换行。将以下行复制到init.vim中:
" Python specific settings
au BufNewFile,BufRead *.py
\ set tabstop=4 |
\ set fileformat=unix |
\ set softtabstop=4 |
\ set shiftwidth=4 |
\ set textwidth=79 |
\ set expandtab |
\ set autoindent
au BufRead, BufNewFile *.py,*.pyw,*.c,*.h match BadWhitespace /\s\+$/ " Highlight trailing whitespace
现在我们完成了这些基本设置,让我们处理外观问题。毕竟,每个人都希望在一个美丽的环境中工作。
让它变得美丽
为了让我们的编辑器达到我们想要的外观,我们将深入探讨插件的世界。Vim 和 Neovim 有一个充满活力的社区,发布和维护成千上万的插件。这些插件改变了编辑器中每个小细节的行为。如果你遇到 Neovim 的问题,很可能其他人已经遇到过并发布了一个让事情变得更简单的插件。
我们的第一步是安装一个插件管理器,它将安装、卸载和更新插件。为此,我喜欢使用vim-plug。要安装 vim-plug,请运行docs中指定的适用于你平台的命令。如果你在我们上一篇文章中启动的 Docker 镜像中运行,你会想使用适用于 Linux 的那个。
现在你有了一个插件管理器,安装 Neovim 插件变得非常简单。因此,为了让我们的编辑器更美观,我们将使用四个插件:
-
vim-airline:一个精简而高效的 vim 状态栏/标签行
-
vim-airline-themes:一个 vim-airline 的主题集合
-
awesome-vim-color-schemes:Neo/vim 的精彩配色方案集合
-
vim-devicons:Neovim 的文件类型图标集合
要安装这些插件,首先,在init.vim中添加以下行:
call plug#begin()
Plug 'https://github.com/vim-airline/vim-airline' " Show status bar
Plug 'https://github.com/vim-airline/vim-airline-themes.git' " Customize status bar
Plug 'https://github.com/ryanoasis/vim-devicons' " Display developer icons
Plug 'https://github.com/rafi/awesome-vim-colorschemes' " Change color Schemes
call plug#end()
保存并退出文件,然后重新打开并运行以下 Neovim 命令::PlugInstall。一个新面板将启动,你的插件将被安装:

作者提供的图片
关闭所有窗口,使用命令 :qa,然后重新打开文件。更改应该会立即可见。现在,让我们修复一些问题。首先,你会注意到终端没有正确显示某些图标。这是因为你需要安装Nerd Fonts并在终端中使用它们。我将安装source-code-pro。首先,在你的home下创建一个.fonts目录:
mkdir .fonts && cd .fonts
获取 source-code-pro Nerd Font:
wget https://github.com/ryanoasis/nerd-fonts/releases/download/v2.3.3/SourceCodePro.zip
解压它:
unzip SourceCodePro.zip && rm SourceCodePro.zip
最后,运行fc-cache -fv来手动重建字体缓存。然后,进入settings > advanced settings editor菜单,找到左侧边栏中的terminal条目,将“Font Family”选项更改为“SauceCodePro Nerd Font Mono”。就这样!再用 Neovim 打开init.vim文件,图标应该就会出现:

作者提供的图片
然而,有些东西仍然显得有些古怪。让我们修复这些问题,并确定我们的核心主题。我喜欢molokai,所以我会使用这个。在你的init.vim中添加以下行:
" Set default colorscheme
colorscheme molokai
" Customize status bar
let g:airline_theme='molokai'
if !exists('g:airline_symbols')
let g:airline_symbols = {}
endif
" airline symbols
let g:airline_symbols.branch = ''
let g:airline_symbols.readonly = ''
let g:airline_symbols.maxlinenr = ''
再次保存并退出,重新打开文件,一切应该都会正常工作。恭喜你,你现在已经为在 JupyterLab 中将 Neovim 设置为完美的 Python IDE 打下了基础。
保存你的工作
如果你正在使用我们在上一篇文章中启动的 Docker 容器(你应该这样做),我会给你一个最后的提示。要将你到目前为止所做的工作保存到你自己的镜像中,请运行以下命令:
docker commit <name-of-the-running-image> <new-image-name>
这将创建一个新的层,在当前运行的容器镜像之上,并将所有内容打包成一个你可以推送到 DockerHub 账户的新镜像。
例如,对我来说,这个命令将是:
docker commit jupyter dpoulopoulos/jupyter:v0.0.1
这意味着你可以拉取 dpoulopoulos/jupyter:v0.0.1 并在其中找到我们在这里做的所有内容!
接下来,我们将深入探讨更多高级功能,比如如何添加文件系统浏览窗格和代码导航窗口。在此之前,练习你的 Vim 快捷键吧!
关于作者
我的名字是 Dimitris Poulopoulos,我是一名为 Arrikto 工作的机器学习工程师。我为包括欧洲委员会、Eurostat、国际货币基金组织、欧洲中央银行、经济合作与发展组织以及宜家在内的主要客户设计和实施了人工智能和软件解决方案。
如果你对阅读更多关于机器学习、深度学习、数据科学和数据运维的文章感兴趣,可以在 Medium、LinkedIn 或者 Twitter 上关注我 @james2pl。
所表达的意见仅代表我个人的观点,并不代表我雇主的观点或意见。
K-means 聚类:入门指南及实际应用
·
关注 发表在 Towards Data Science ·10 分钟阅读·2023 年 1 月 23 日
--
不同发动机类型、尺寸和重量的汽车。作者摄影。
使用如 K-means 这样的聚类算法是机器学习中最常见的入门点之一。K-means 聚类是一种无监督的机器学习技术,它将相似的数据分组或聚集到一起。特定聚类中的数据在聚类内的观察值之间具有更高的相似度,而与聚类外的观察值相比则相对较低。
K 均值中的 K 代表用户定义的k簇数量。K 均值聚类通过尝试在数据中找到最佳的簇中心位置来工作,确保簇内的数据距离给定中心比其他中心更近。理想情况下,生成的簇在每个唯一簇内最大化数据之间的相似性。
请注意,存在各种聚类方法;本文将重点介绍最流行的技术之一:K 均值。
本指南包括两个部分:
-
使用生成数据的 K 均值聚类简介。
-
对汽车数据集应用 K 均值聚类。
代码:
所有代码可以在这里的 github 页面上获取。 随时下载笔记本(点击 CODE 和 Download Zip)并与本文一起运行!
1. K 均值聚类简介
对于本指南,我们将使用scikit-learn库[1]:
from sklearn.cluster import KMeans
from sklearn import preprocessing
from sklearn.datasets import make_blobs
为了演示 K 均值聚类,我们首先需要数据。方便的是,sklearn 库包括了生成数据块的功能[2]。代码相当简单:
# Generate sample data:
X, y = make_blobs(n_samples=150,
centers=3,
cluster_std=.45,
random_state = 0)
make_blobs()函数的参数允许用户指定中心的数量(这可能与潜在的簇中心相关)以及“数据块”的混乱程度(cluster_std,调整簇的标准差)。上述代码生成了数据块;下面的代码将其转换为数据框和 plotly 散点图:
# Import required libraries:
import plotly.express as px
import pandas as pd
# Convert to dataframe:
dfBlobs = pd.DataFrame(X, columns = ['X','Y'])
# Plot data:
plot = px.scatter(dfBlobs, x="X", y="Y")
plot.update_layout(
title={'text':"Randomly Generated Data",
'xanchor':'center',
'yanchor':'top',
'x':0.5})
plot.show()
这是输出结果:

作者截图。
由于在 make_blobs()函数中选择了较低的“cluster_std”值,生成的图形有三个非常明确的数据块,这应该很容易被 K 均值聚类算法处理。
多少个簇?
K 均值中的 K 是簇的数量,由用户定义。对于给定的数据集,通常存在一个最佳的簇数量。在上面生成的数据中,可能是三个。
要数学地确定最佳的簇数量,可以使用“肘部法则”。该方法计算不同k值的簇内平方和(WCSS),一般较低的值更好。WCSS 表示每个数据点到簇中心的平方距离之和。肘部法则将 WCSS 绘制为添加额外簇的结果;最终,随着新簇的增加,WCSS 的下降变得缓慢,出现一个“肘部”,这揭示了最佳簇数量。
以下代码为上述数据生成一个肘部图:
# Determine optimal number of clusters:
wcss = []
for k in range(1, 11):
kmeans = KMeans(n_clusters=k, max_iter=5000, random_state=42)
kmeans.fit(dfBlobs)
wcss.append(kmeans.inertia_)
# Prepare data for visualization:
wcss = pd.DataFrame(wcss, columns = ['Value'])
wcss.index += 1
绘制后,这将产生:
# Plot the elbow curve:
plot = px.line(wcss, y = "Value")
plot.update_layout(
title={'text':"Within Cluster Sum of Squares or 'Elbow Chart'",
'xanchor':'center',
'yanchor':'top',
'x':0.5},
xaxis_title = 'Clusters',
yaxis_title = 'WCSS')
plot.show()

作者截图。
注意 WCSS 的图形在 3 个聚类时有一个明显的“肘部”。这意味着 3 是最佳聚类选择,因为 WCSS 值在增加到三个聚类时急剧下降。超过 3 个聚类只会带来 WCSS 减少的微小增益。因此,最佳的聚类值是k = 3。
生成聚类
下一步是运行 K 均值聚类算法。在下面的代码中,kmeans = KMeans(3)是输入k值的地方:
# Cluster the data:
kmeans = KMeans(3)
clusters = kmeans.fit_predict(dfBlobs)
# Add the cluster labels to the dataframe:
labels = pd.DataFrame({'Cluster':clusters})
labeledDF = pd.concat((dfBlobs, labels), axis = 1)
结果是一个带有“Cluster”列的标记数据框:

作者截图。
绘制结果如下:
# Generate plot:
# Change Cluster column to strings for cluster visualization:
labeledDF["Cluster"] = labeledDF["Cluster"].astype(str)
# Generate plot:
plot = px.scatter(labeledDF, x="X", y="Y", color="Cluster")
plot.update_layout(
title={'text': "Clustered Data",
'xanchor': 'center',
'yanchor': 'top',
'x': 0.5})
plot.show()

作者截图。
K 均值方法已成功将数据分成三个不同的聚类。现在让我们看看更现实的数据会发生什么。
2. 汽车数据中的 K 均值聚类
Python 库 Seaborn 提供了各种数据集,包括来自石油危机时代汽车的燃油效率数据集。为了帮助学习聚类,我们将过滤数据集,选择 8 缸和 4 缸发动机的汽车。这代表了当时通常可用的最大和最小发动机。
幸运的是,这可能是一个真实世界分析场景的示例。在 1970 年代,燃油价格的快速上涨使得 8 缸车变得不那么受欢迎;因此,4 缸车变得越来越普遍,但 4 缸和 8 缸发动机的汽车在燃油消耗方面的实际表现如何呢?
具体而言,我们将探讨 4 缸和 8 缸汽车在重量和燃油效率(以每加仑多少英里(MPG)衡量)方面的表现。
准备数据非常简单:
import seaborn as sns
# Load in the data - Seaborn's mpg data:
df = sns.load_dataset('mpg')
# Filter for 4 and 8 cylinder cars:
df = df[(df['cylinders'] == 4) | (df['cylinders'] == 8)]
df = df.reset_index(drop=True)
# Display dataframe head:
df.head(3)
数据集如下所示:

作者截图。
可视化汽车的重量和 MPG 如下:
# Plot the mpg and weight data:
plot = px.scatter(df, x='weight', y='mpg',
hover_data=['name', 'model_year'])
plot.update_layout(
title={'text': "Vehicle Fuel Efficiency",
'xanchor': 'center',
'yanchor': 'top',
'x': 0.5})
plot.show()

作者截图。
数据缩放
注意 x 轴表示重量,而 y 轴表示 MPG。增加 10 磅的重量不如增加 10 MPG 重要。这可能会影响聚类结果。
有多种选择来解决这个问题;Jeff Hale 有一篇很好的文章链接在这里,提供了有关缩放、标准化和归一化的各种方法和使用案例的技术概述[1]。对于这个练习,我们将使用 Sci-Kit Learn 的 StandardScaler()函数。
# Create DF copy for standardizing:
dfCluster = df.copy()
# Set the scaler:
scaler = preprocessing.StandardScaler()
# Normalize the two variables of interest:
dfCluster[['weight', 'mpg']] = scaler.fit_transform(dfCluster[['weight', 'mpg']])
# Create dataframe for clustering:
dfCluster = dfCluster[['weight', 'mpg']]
# View dataframe head:
dfCluster.head(3)
结果如下:

这个两列数据框现在可以通过肘部法则处理。
多少个聚类?
如第一部分所述,我们使用相同的代码遵循肘部法则,以返回以下图表:
# Determine optimal number of clusters:
wcss = []
for k in range(1, 11):
kmeans = KMeans(n_clusters=k, max_iter=5000, random_state=42)
kmeans.fit(dfCluster)
wcss.append(kmeans.inertia_)
# Prepare data for visualization:
wcss = pd.DataFrame(wcss, columns=['Value'])
wcss.index += 1
# Plot the elbow curve:
plot = px.line(wcss)
plot.update_layout(
title={'text': "Within Cluster Sum of Squares or 'Elbow Chart'",
'xanchor': 'center',
'yanchor': 'top',
'x': 0.5},
xaxis_title='Clusters',
yaxis_title='WCSS')
plot.update_layout(showlegend=False)
plot.show()

作者截图。
两个聚类似乎是肘部。然而,从 2 到 3 个聚类的跳跃不像第一部分数据块示例中的那样平缓。
注意: 肘部法则可能不会始终提供最清晰的结果,因此需要尝试肘部区域中几个不同的k值。虽然我们选择了 2 个簇,但尝试 3 个簇也可能值得。有关该主题的更高级分析,Satyam Kumar 在这篇《数据科学中的轮廓法》中提供了一种替代的肘部法则方法。
生成簇
生成簇的代码与之前的示例类似:
# Cluster the data:
kmeans = KMeans(2)
clusters = kmeans.fit_predict(dfCluster)
# Add the cluster labels to the dataframe:
labels = pd.DataFrame({'Cluster': clusters})
labeledDF = pd.concat((df, labels), axis=1)
labeledDF['Cluster'] = labeledDF['Cluster'].astype(str)
这使数据框回到了我们开始的地方,但新增了一列——标识每个观测值的簇:

作者截图。
绘制这些数据揭示了簇:
# Generate plot:
plot = px.scatter(labeledDF, x="weight", y="mpg", color="Cluster",
hover_data=['name', 'model_year', 'cylinders'])
plot.update_yaxes(categoryorder='category ascending')
plot.update_layout(
title={'text': "Clustered Data",
'xanchor': 'center',
'yanchor': 'top',
'x': 0.5})
plot.show()

作者截图。
进一步分析
请记住,K 均值聚类试图根据数据的相似性对数据进行分组,这些相似性由与簇质心的距离决定。将簇值添加到原始数据框中可以对原始数据集进行额外的分析。例如,上述簇可视化显示了大约 3000 磅和 20 MPG 之间的簇分裂。
额外的可视化可能会提供更多见解。考虑以下条形图(代码见链接的 Git 页面):

作者截图。
首先,K 均值聚类仅根据重量和 MPG 将车辆分类,结果几乎完美地与气缸数对齐。一个簇是 100%4 缸车,而另一个几乎全是 8 缸车。
其次,主要是 8 缸的簇中有四辆 4 缸车。尽管这些车的发动机较小,但它们的 MPG 表现接近于大型 8 缸车。簇零中的 4 缸车都接近 3000 磅重量,同时 MPG 表现为 20 或更低。
最终,似乎有些 8 缸车的 MPG 得分接近 4 缸车。另一张图表显示这些例子被认为是异常值(代码见 Git 页面):

作者截图。
这是聚类如何帮助理解数据并指导后续分析和数据探索的一个例子。工程师可能会发现分析为什么某些 4 缸车在效率上表现较差并与 8 缸车聚类在一起是有意义的。
欲进行额外练习和学习: 访问链接中的完整笔记本,重新加载 seaborn MPG 数据集,但不要过滤 4 和 8 缸的汽车。你会发现肘部法揭示了 2 与 3 聚类之间的模糊分界——这两者都值得尝试。或者,尝试 使用其他 seaborn 数据集 或 MPG 数据集中的其他特征 [5]。
K-means 聚类是处理这些数据的最佳技术吗?
记住,对于有 blobs 的示例,K-means 肘部法有一个非常明确的最佳点,结果聚类分析也很容易识别出不同的 blobs。K-means 通常在数据更加球形的情况下表现更好,就像数据 blobs 的情况一样。其他方法可能在复杂数据中效果更佳,甚至在本示例中使用的汽车数据中。有关一些替代聚类方法的进一步阅读,Victor Roman 在这篇 Towards Data Science 文章中提供了很好的 概述 [6]。
结论
K-means 聚类是一个强大的机器学习工具,可以识别数据中的相似性。这种技术可以提供见解或增强对数据的理解,从而指导进一步的分析问题并改善数据可视化。本文及代码提供了 K-means 聚类的指南,但还有其他聚类技术可用,根据分析的数据类型,有些技术可能更为合适。即使 K-means 不是给定数据的最合适方法,K-means 聚类仍然是一个值得了解的优秀方法,也是熟悉机器学习的良好起点。此外,K-means 聚类还可以作为其他聚类方法的基准,尽管它可能不是给定数据集的理想聚类算法,但仍可能证明有用。
参考文献:
[1] Scikit learn, scikit-learn: machine learning in Python (2023)。
[2] Scikit learn, sklearn.datasets.make_blobs (2023)。
[3] J. Hale, 使用 Scikit-Learn 进行缩放、标准化或归一化 (2019), Towards Data Science。
[4] S. Kumar, 轮廓法——比肘部法更好的寻找最佳聚类方法 (2020), Towards Data Science。
[5] M. Alam, Python 中的数据可视化必备 Seaborn (2020), Towards Data Science。
[6] V. Roman, 机器学习:聚类分析 (2019), Towards Data Science。
Kaiming He 初始化在神经网络中的数学证明
原文:
towardsdatascience.com/kaiming-he-initialization-in-neural-networks-math-proof-73b9a0d845c4
推导具有 ReLU 激活函数的神经网络层中权重矩阵的最优初始方差
·发布于 Towards Data Science ·阅读时间约 10 分钟·2023 年 2 月 15 日
--

初始化技术是成功训练深度学习架构的前提条件之一。传统上,权重初始化方法需要与激活函数的选择兼容,因为不匹配可能会对训练产生负面影响。
ReLU 是深度学习中最常用的激活函数之一。它的特性使其成为扩展到大型神经网络的非常方便的选择。一方面,由于它是一个线性函数,具有步进函数导数,在反向传播过程中计算导数的开销较小。另一方面,ReLU 有助于减少特征相关性,因为它是一个非负激活函数,即 特征只能对后续层产生积极贡献。它在输入维度较大且神经网络往往非常深的卷积架构中是一种流行的选择。
在 He et al.(2015)的“深入探讨整流器:超越人类水平的 ImageNet 分类” ⁽¹ ⁾ 中,作者提出了一种使用 ReLU 激活函数来最优初始化神经网络层的方法。这种技术使神经网络在前向和反向传播过程中在输入和输出之间保持常数方差,这在经验上显示出训练稳定性和速度的显著改善。在接下来的章节中,我们将提供 He 初始化技术背后的详细和完整的推导过程。
符号
-
神经网络中的一层,由权重矩阵 Wₖ 和偏置向量 bₖ 组成,经过两个连续的变换。第一个变换是 yₖ = xₖ Wₖ + bₖ,第二个变换是 xₖ ₊ ₁ = f(yₖ)
-
xₖ 是实际层,yₖ 是预激活层
-
一层有nₖ个单元,因此xₖ ∈ ℝⁿ⁽ ᵏ⁾, Wₖ ∈ ℝⁿ⁽ ᵏ⁾˙ⁿ⁽ ᵏ ⁺ ¹⁾, bₖ ∈ℝⁿ⁽ ᵏ ⁺ ¹⁾
-
xₖWₖ + bₖ 的维度是(1 × nₖ)×(nₖ × nₖ ₊ ₁)+ 1 × nₖ ₊ ₗ* = 1 × nₖ ₊* ₁
-
激活函数f按元素应用,不改变向量的形状。因此,xₖ ₊ ₁= f(xₖ Wₖ+ bₖ)∈ ℝⁿ⁽ ᵏ ⁺ ¹⁾
-
对于深度为n的神经网络,输入层由x₀表示,输出层由xₙ表示
-
网络的损失函数由L表示
-
Δx = ∂L/∂x 表示损失函数关于向量x的梯度
假设
-
假设 1:
对于这个初始化设置,我们假设一个非线性激活函数 ReLU 定义为f(x) = ReLU(x) = max(0, x). 作为在两个区间上分别定义的函数,它的导数在ℝ的严格正半轴上值为 1,在严格负半轴上值为 0。技术上,由于两侧的极限不相等,ReLU 在 0 处的导数未定义,即f’(0⁻⁻) = 0 ≠ 1 = f’(0⁺). 实际上,为了反向传播的目的,假设 ReLU’(0)为 0。
-
假设 2:
假设神经网络中的所有输入、权重和层在初始化时都是独立同分布的(iid),梯度也是如此。
-
假设 3:
假设输入已归一化为零均值,权重和偏置从以零为中心的对称分布中初始化,即𝔼[x₀] = 𝔼[Wₖ] = 𝔼[bₖ] = 0。这意味着xₖ和 yₖ在初始化时都有零的期望,由于f(0) = 0,yₖ在初始化时具有对称分布。
动机
本证明的目的是通过在两个约束条件下找到Var[W]来确定权重矩阵的分布:
-
∀k, Var[yₖ] = Var[yₖ ₋ ₁],即前向信号中的方差是恒定的
-
∀k, Var[Δxₖ] = Var[Δxₖ ₊ ₁],即反向信号中的方差是恒定的
确保网络中所有层和梯度的方差在初始化时保持恒定,有助于防止神经网络中的梯度爆炸和梯度消失。如果增益大于 一,则会导致梯度爆炸和优化发散;如果增益小于 一,则会导致梯度消失并停止学习。上述两个方程确保信号增益恰好为一。
本文的动机及推导基于五年前发表的 Xavier Glorot 初始化⁽²⁾ 论文。虽然前述工作使用了后激活层来保持前向信号的恒定方差,但 He 初始化证明使用了前激活层。同样地,对于反向信号,He 的推导使用了后激活层,而非 Glorot 初始化中的前激活层。鉴于这两个证明有一些相似之处,比较这两者有助于理解为什么控制权重的方差在任何神经网络中如此重要。(更多细节见 “Xavier Glorot Initialization in Neural Networks — Math Proof”)
## Xavier Glorot Initialization in Neural Networks — Math Proof
详细推导了在深度学习层中使用 tanh 激活函数时寻找权重矩阵的最优初始分布……
towardsdatascience.com
数学证明:Kaiming He 初始化
I. 前向传播
我们正在寻找 Wₖ 使得每个后续前激活层 y 的方差相等,即 Var[yₖ] = Var[yₖ ₋ ₁].
我们知道 yₖ = xₖ Wₖ+ bₖ。
为了简化,我们查看前激活层的i-th 元素 yₖ 并对前面的方程两边应用方差算子。

-
在第一步中,我们完全去除 bₖ,因为根据假设 1,它初始化为零值。此外,我们利用 W 和 x 的独立性,将和的方差转换为方差之和,依据Var[X+Y] = Var[X] + Var[Y],其中 X ⟂ Y。
-
在第二步中,由于 W 和 x 是 i.i.d.,因此和中的每一项都是相等的,因此和仅仅是 Var[xW] 的 nₖ 次重复。
-
在第三步中,我们遵循 X ⟂ Y 的公式,这意味着 Var[XY] = E[X²]E[Y²] - E[X]²E[Y]²。这使我们能够将 W 和 x 对前激活层方差的贡献分开。

-
在第四步中,我们利用了权重和层在初始化时的零期望的假设 3。这使我们剩下一个涉及平方期望的单一项。
-
在第五步中,我们将平方期望转换为方差,因为Var[X] = E[( X - E[X])²] = E[X²],如果X的均值为零。现在我们可以将前激活层的方差表示为层方差和权重方差的单独乘积。
最后,为了将 Var[yₖ] 与 Var[yₖ ₋ ₁] 关联起来,我们使用 无意识统计学家定律 将平方期望 E[xₖ²] 表达为 Var[yₖ ₋ ₁]。

定理指出,我们可以将随机变量函数的任何期望表示为其函数和概率密度 p 的积分。由于我们知道 xₖ = max(0, yₖ ₋ ₁),我们可以将 xₖ 的平方期望重写为在 ℝ 上关于 y 的积分。

-
在第六步中,我们利用 y 在 ℝ⁻ 上为零来简化积分。
-
在第七步中,我们利用 y 作为对称随机变量的统计特性,因此它具有对称的密度函数 p,并注意到整个积分项是偶函数。偶函数相对于 ℝ 上的 0 是对称的,这意味着从 0 到 a 的积分与从 -a 到 0 的积分是相同的。我们使用这个技巧将积分重新表述为在 ℝ 上的积分。

- 在第九和第十步中,我们将这个积分重写为随机变量的函数的积分。通过应用 LOTUS——这次是从右到左——我们可以将此积分转换为关于随机变量 y 的函数的期望。作为零均值变量的平方期望,这本质上是方差。

最终,我们将步骤五和步骤十的结果综合起来——前置激活层的方差与其前置激活方差以及该层权重的方差直接相关。由于我们要求 Var[yₖ] = Var[yₖ ₋ ₁],这使我们可以确认该层权重的方差 Var[Wₖ] 应该是 2/nₖ。
总结一下,这里是本节回顾的前向传播的完整推导:


II. 反向传播
我们正在寻找 Wₖ,使得 Var[Δxₖ] = Var[Δxₖ ₊ ₁]。
在这里, xₖ ₊ ₁= f (yₖ) 和 yₖ = xₖ Wₖ + bₖ。
在应用方差运算符之前,让我们首先计算损失 L 相对于 x 和 y 的偏导数:Δxₖ 和 Δyₖ。

-
首先,我们使用链式法则和线性乘积的导数是其线性系数的事实——在这种情况下是 Wₖ。
-
其次,我们利用 假设 2 说明梯度和权重彼此独立。利用独立性,积的方差等于方差的积,因为权重假设为零均值初始化。因此,L w.r.t. x 的梯度期望为零。
-
第三,我们使用链式法则将 Δyₖ 和 Δxₖ ₊ ₁ 关联起来,因为 x 相对于 y 的偏导数是 ReLU 对 y 的导数。

- 第四,回顾 ReLU 的导数,我们使用之前的方程计算Δyₖ的期望。由于f’(x)被分为两个等概率的部分½,我们可以将其写作两个项的和:分别对ℝ⁺和ℝ⁻的期望。从之前的计算中,我们知道Δxₖ的期望为零,因此可以确认两个梯度的均值均为 0。

-
第五,我们使用之前相同的规则,将平方期望写作方差,这里是Δyₖ。
-
第六,我们利用假设 2指出梯度在初始化时是独立的,以此分离两个梯度Δxₖ ₊ ₁和f’(yₖ)的方差。进一步简化源自假设 3,我们最终可以计算 ReLU 的平方期望,因为它在正负区间之间是均匀分布的。

最后,利用上述各节中收集到的结果,并重新应用iid假设,我们得出反向传播的结果与正向传播类似,即给定Var[Δxₖ] = Var[Δxₖ ₊ ₁],任何一层的权重的方差Var[Wₖ]等于 2/nₖ。
总结一下,这里是反向传播部分包含的重要逐步计算的提醒:

III. 权重分布
在前两节中,我们对正向和反向设置得出了以下结论:

有趣的是,这个结果与 Glorot 初始化方法不同,其中作者实际上必须对正向和反向传播中得到的两个不同结果进行平均。此外,我们观察到 He 方法中的方差是翻倍的,这直观上是因为 ReLU 的零负区域将方差减少了一半。
随后,了解了分布的方差后,我们可以使用正态分布N(0, 𝜎²)或均匀分布U(-a, a)来初始化权重。根据经验,没有证据表明一种分布优于另一种分布,似乎性能的提升完全归结为所选分布的对称性和尺度属性。此外,我们确实需要记住假设 3,限制分布选择为对称且以 0 为中心。
- 对于正态分布 N(0, 𝜎²)
如果X ~ N(0, 𝜎²),则Var[X] = 𝜎²,因此权重矩阵的方差和标准差可以写作:

因此我们可以得出结论,Wₖ遵循以下系数的正态分布:

作为提醒,nₖ是层k的输入数量。
- 对于均匀分布 U(-a, a)
如果X ~ U(-a, a),则使用以下均匀分布随机变量方差的公式,我们可以找到界限 a:

最后,我们可以得出结论,Wₖ 遵循具有系数的均匀分布:

结论
本文提供了为什么He 初始化方法对于使用 ReLU 激活函数的神经网络是最优的的逐步推导,前提是前向和反向传播的方差保持恒定。
该证明的方法论还扩展到线性激活函数的更广泛家族,如 PReLU(在 (1) 中由 He 等人 讨论)或 Leaky ReLU(允许在负区间中有微小的梯度流动)。对于这些 ReLU 激活函数的变体,也可以推导出类似的最优方差公式。
引用
(1)深入探讨激活函数:超越人类水平的 ImageNet 分类表现,He 等人 (2015)
(2)理解训练深度前馈神经网络的难度,Glorot 等人 (2010)
来源:以上所有方程和图像均为我个人制作。
使用 Hydra 跟踪你的实验
原文:
towardsdatascience.com/keep-track-of-your-experiments-with-hydra-b29937a99fc9

(图片来源:作者)
使用 YAML 文件配置超参数,加速你的研究!
·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 8 月 1 日
--
介绍
就像不可能在第一次尝试时编写没有错误的代码一样,第一次尝试也不可能训练出合适的模型。
有一些机器学习和深度学习经验的人知道,你经常需要花费大量时间选择模型的正确超参数。这些超参数例如学习率、批量大小和输出中的类别数,但这些只是最常见的一些,一个项目可以有数百个这样的参数。
通过更改超参数,我们可以得到不同的结果(更好或更差),而在某些时候跟踪所有已进行的测试是非常困难的。
我曾经做了很长时间的事情:我用手在 Excel 表格中写下所有这些超参数,并在旁边写下每个实验的结果,例如损失值。后来我“进化”了,开始编写超参数的配置文件,在其中放入我想测试的各种值。我曾经编写自定义 Python 函数来读取这些值并将其放入训练函数中。YAML 文件基本上是一个层次结构构建的文件,你可以在其中插入键和值,如下所示:
data:
path: "data/ESC-50"
sample_rate: 8000
train_folds: [1, 2, 3]
val_folds: [4]
test_folds: [5]
batch_size: 8
model:
base_filters: 32
num_classes: 50
optim:
lr: 3e-4
seed: 0
trainer:
max_epochs: 10
我后来发现了Hydra,这是一个开源框架,使整个过程更简单、更快捷。
让我们开始吧!
假设我们正在使用 PyTorch 开发一个简单的机器学习项目。像往常一样,我们创建一个数据集类,实例化数据加载器,创建模型并进行训练。在这个示例中,我将使用 PyTorch Lightning 更好地组织代码,其中我们有一个 Trainer 对象,类似于你在 Keras 中所做的。如果你习惯了 PyTorch,你也会很快理解 Lightning。
对于我们的示例,我们可以使用著名的 ESC50 数据集,你可以在 这里 免费找到。
首先,我们导入所有需要的库。我将使用 Deepnote 运行代码。
像往常一样,我们实现一个 PyTorch 类来管理数据集。
我们使用 Lightning 实现模型。我不会描述模型,因为那并不是重点,即使你没有音频相关任务的经验也没关系,重要的是如何构建和使用 Hydra 进行实验。
最后是训练函数。
添加 Hydra 配置
在上面的代码中,你会看到有许多硬编码的参数,比如批量大小、用于训练-验证-测试分割的折数、纪元数等。
我想找到最佳参数,然后启动各种实验并根据结果做出判断。Hydra 使这变得非常简单,让我们看看如何安装和使用这个库。
现在我们在一个名为 configs 的新文件夹中创建一个名为“default.yaml”的配置文件。
文件将包含所有我们想测试的超参数的键值对。以下是一个示例。
data:
path: "data/ESC-50"
sample_rate: 8000
train_folds: [1, 2, 3]
val_folds: [4]
test_folds: [5]
batch_size: 8
model:
base_filters: 32
num_classes: 50
optim:
lr: 3e-4
seed: 0
trainer:
max_epochs: 10
现在我们想要动态传递这些参数到训练函数中。Hydra 允许我们使用装饰器来实现这一点。
正如你所见,装饰器定义了要与参数一起使用的文件夹和文件名。并且通过获取文件值并将其作为字典在 Python 中处理,硬编码的值已经被更改,非常简单!
然而,你会注意到,在 Audionet 模型中,预期输入已经被更改,以便接受以下方式的参数字典。
现在一切准备就绪。
尝试运行 train(),Hydra 将生成组织良好且易于验证的结果。
这是我使用 Hydra 的输出示例。

(图片来源:作者)
正如我们看到的,Hydra 提供了参数配置文件的回顾,关于模型训练的 Lightning 日志,以及用于检查指标的 CSV 文件。
然而,这些只是 Hydra 的基础功能,Hydra 是一个允许你做更多事情的工具。
一个很好的功能是通过终端运行脚本并从命令行添加或更改文件参数。
例如,如果我们想快速更改种子而不修改 YAML 文件,我们可以这样做:
python my_app.py 'seed=1'
我们还可以向作为输入传递给 Trainer 的配置文件中添加一些关键字,例如 pl.Trainer(**cfg.trainer)。
要添加关键字,我们使用来自终端的 + 命令。
python train.py +trainer.fast_dev_run = True
有时候你可能会想把实验保存在默认文件夹以外的位置,这可以通过在命令行中覆盖 Hydra 的hydra.run.dir参数轻松设置。
python train.py data.sample_rate=4000 hydra.run.dir=“outputs/sample_rate_4000”
最后的思考
Hydra 是建立在 OmegaConf 之上的一个框架,它让我们可以非常轻松地管理实验。一旦我们实现了代码和管道,我们的任务就是跟踪模型的进展并根据需要更改配置。
在这篇文章中,我们看到了如何使用 Hydra 的实际例子,但还有许多更高级的功能我们没有涉及,例如启动多次运行以并行进行多个实验。为此,我建议你去阅读文档。如果你对这篇文章感兴趣,可以在 Medium 上关注我!😄
💼 Linkedin ️| 🐦 Twitter | 💻 Website
让机器人不偏离伦理轨道

图片由作者使用 Dall-E 2 创建
构建透明的 AI 软件系统的关键
·
关注 发表在 Towards Data Science · 8 min 阅读 · 2023 年 4 月 13 日
--
AI 算法是一种具有自动化或执行通常需要人类智能的任务能力的软件系统。这些系统通常不仅仅包括一个训练好的模型。它们还可能包括明确的算法功能,例如业务规则,将模型的输出整合到更大的 AI 系统中,以完成端到端的任务。
为了正确实施伦理 AI 系统,用于部署模型的软件必须具备从端到端测量和缓解实时算法行为的能力。通过有意地构建审计 AI 的方法,我们可以确保好的机器人不会偏离轨道——如果发生偏离,我们将有工具来进行纠正。
伦理 AI 软件基础设施
那么,要将伦理融入 AI 的软件基础设施中需要什么?我们如何预先设计这些系统以确保能够审计 AI 模型的偏差?
一个真正可审计的 AI 系统应具备足够的透明度,以便用户和创作者可以回答“数据输入模型、预测结果、以及在输出被使用前对其进行的调整是什么?” 如果发现偏差或质量问题,可以利用伦理 AI 系统中的杠杆来缓解任何偏差或纠正出现的质量问题。

可审计的 AI 系统设计(作者绘图)
该提议的系统设计是一个通用基础设施,可适用于许多依赖实时 AI 的业务用例。
该设计提供了核心功能,确保模型以负责任、伦理和无偏的方式运行。它包括:
-
一个数据收集系统,覆盖所有系统生成的数据
-
一个指标和监控系统,用于跟踪模型性能和实时模型中的偏差,以提供数据和模型 ML 可观测性。
-
多个杠杆来缓解潜在的偏差: 一个针对性的模型再训练循环,用于更新模型以获得更好的数据,补充一个用于编程明确逻辑的规则引擎,例如模型覆盖或偏差缓解,以及一个人机交互的质量检查系统。
数据收集
完全审计 AI 系统所需的数据量非常庞大。它包括:
-
特性(模型输入)
-
预测(模型输出)
-
SHAP 或某种特征重要性表示,以提供逐点预测解释。这使我们能够追踪特定预测的原因。
-
任何用于增强预测的业务逻辑或规则
-
任何用于增强预测的人类决策
-
可以链接到预测以监控偏差的孤立人口数据
💡注意在此设计中保护的人口数据与系统隔离。这些数据仅用于测量模型性能以检测偏差,绝不应与模型输入混合,否则可能会导致偏差被编码到模型中。下面列出了一些核心属性需考虑。

作者绘图
偏差测量和监控
一旦收集了这些数据,指标和可视化工具可以用来量化和监控实时系统中的偏差趋势。需要考虑的标准公平性和偏差指标包括
-
召回率平衡:衡量模型对一个组与另一个组的“敏感性”或模型正确预测真实正类的能力。
-
假阳性率平衡:衡量模型在敏感组相对于基础组中错误预测正类的情况。
-
差别影响:对受保护类别的不利待遇的定量度量
一个模型可能在平均水平上表现良好,但深入挖掘可以超越平均模型表现,隔离不同人口组的表现。按组分析准确性能提供模型对整体人群服务公平性的可见性。
偏见缓解
规则引擎,也称为基于规则的系统或专家系统,是一种使用预定义规则集来做决策或解决问题的算法。这些规则通常以 IF-THEN 语句表示,以结构化和有组织的方式捕捉领域特定的知识和专业技能。在实际 AI 应用中,模型预测通常会被输入到规则引擎中,围绕如何使用预测或如何增强预测进行业务决策。如果检测到偏见,可以编码新的规则来覆盖它。
许多 AI 系统包括一个自动化管道,用于收集新数据并在新数据上重新训练模型,以更新最新信息。这保持了模型的健康和性能。这种相同的重新训练循环可以用于从模型中去除偏见。可以以有针对性的方式收集重新训练数据,以专注于提供更多数据或更好的示例,让模型在失败的领域学习。
人类在环组件提供了在使用 AI 输出之前进行质量检查的能力。此功能也需要成为软件设计的一部分,以便可以收集人类决策数据,特别是在不能或不应该完全自动化的用例中。AI 通常在作为人类助手时最为有效,而不是一个完整的任务自动化工具。构建支持人类互动和决策的基础设施使我们能够在发现偏见或有害模式时进行覆盖。
等等,AI 中的偏见是什么?
那么,人工智能算法如何会有偏见、不公平或不道德呢?偏见通常不是硬编码的。它不是由软件工程师或数据科学家明确编写的。相反,算法通过扫描大量数据集自动学习。这些人工智能模型旨在从大量数据中学习模式,并将这些模式以数学方式编码。这些数学模式然后被保存为“模型”,用于对新数据进行推断。在这种范式下,这些模型如果提供的数据中存在有害和不公平的模式,就可能学习到这些有害的模式。它们会在预测时依赖这些模式,从而延续这些有害模式。由于互联网上的数据广泛存在,如果不加小心,旧的模式和历史偏见也可能被编码到这些模型中。
在考虑编码到人工智能算法中的模式时,有两种模式需要考虑。显式模式和隐式模式。显式模式是硬编码的规则。这些模式是组织的有意选择,通常表示为代码中的 IF-THEN 语句。隐式模式是模型从提供给它的数据中学习到的。
人工智能开发生命周期
为了进一步阐明这一讨论,了解人工智能项目生命周期的详细内容非常有帮助,以便了解可能引入偏见或质量问题的地方。人工智能项目生命周期有两个核心阶段:研究与开发(R&D)和落地实施。

作者提供的图示
研发阶段

作者使用 Dall-E 2 创建的图像
在研发阶段,科学家或研究人员致力于创建模型。他们收集原始数据,将数据转换为有意义的模型特征,实验和测试各种建模方法和参数,并根据优化特定结果的能力评估模型的性能。在这些步骤中,模型创建者会考虑许多因素来防止和测试模型中的偏见。他们努力收集无偏见的数据,并仔细测量模型在受保护人群中的表现,以确保公平。然而,偏见仍然可能渗入。因此,我们需要能够监控和跟踪模型上线后的行为。
落地实施阶段

作者使用 Dall-E 2 创建的图像
一旦模型创建完成,我们拥有了优良的机器人,项目将进入下一阶段——即将该优良机器人投入实际操作。这就是工程师的工作所在。这一开发阶段专注于将选定的模型转化为一个实用且功能齐全的系统,使最终用户可以访问、依赖并实时使用模型生成的预测。这个过程包括构建集成模型核心功能的软件系统,并遵循生产代码的最佳实践,确保系统具有可扩展性和可维护性。这是设计伦理人工智能的关键环节。该系统不仅可以基于模型生成预测,还可以在设计时考虑伦理人工智能来保护这些算法。
可审计的人工智能系统从模型本身以及与模型输出使用相关的明确决策模式中收集数据。这使得能够全面了解算法的行为,并促进对整个系统的监控。
算法透明度应当在任何人工智能系统设计的最前沿。这意味着衡量和减轻偏见的能力需要融入软件中,而不是事后的考虑。如果部署的算法存在潜在的偏见而没有这些机制,将很难检测和纠正任何有害的模式。
结论
很可能许多人工智能系统在某个时点需要被审计,无论是为了偏见,还是仅仅为了可解释性或质量。通过深思熟虑地设计提供足够数据可见性和算法改进机会的基础设施,可以提前应对这个问题。让我们保持这些机器人不偏离轨道。
对这个主题的深入阅读,以下是我最喜欢的一些关于伦理人工智能的书籍和资源:
-
隐形女性:一个为男性设计的世界中的数据偏见 由卡罗琳·克里亚多-佩雷斯撰写
-
数学毁灭武器 由凯西·奥尼尔撰写
-
伦理机器:完全无偏见、透明且尊重的人工智能简明指南 由里德·布莱克曼撰写
核密度估计逐步讲解
KDE 公式的直观推导
·
关注 发表在 Towards Data Science ·7 min read·2023 年 8 月 15 日
--
图片由 Marcus Urbenz 提供,来源于 Unsplash
介绍
为了感知数据分布,我们绘制概率密度函数(PDF)。当数据很好地符合常见的密度函数(例如正态分布、泊松分布、几何分布等)时,我们会感到满意。然后,可以使用 最大似然方法 将密度函数拟合到数据上。

不幸的是,数据分布有时过于不规则,并不像任何常见的 PDF。在这种情况下,核密度估计器(KDE)提供了一种合理且视觉上愉悦的数据分布表示。

我将带你了解构建 KDE 的步骤,依靠你的直觉,而不是严格的数学推导。
核函数
理解 KDE 的关键是把它当作由构建块组成的函数,类似于不同的物体由乐高砖块构成。KDE 的独特之处在于它仅使用一种砖块,称为核函数(‘一个砖块统治所有’)。这种砖块的关键特性是能够移动和伸缩/收缩。每个数据点都有一个砖块,KDE 是所有砖块的总和。
KDE 是由一种构建块组成的复合函数,称为核函数。
核函数是对每个数据点单独评估的,这些部分结果被求和形成 KDE。
对 KDE 的第一步是仅关注一个数据点。如果让你为一个单一的数据点创建 PDF,你会怎么做?首先,取x = 0。最合逻辑的方法是使用一个在该点上正好峰值并随着距离衰减的 PDF。函数

这样就可以解决问题。

然而,由于 PDF 应该在曲线下具有单位面积,我们必须对结果进行重新缩放。因此,该函数必须除以 2π的平方根,并通过√2 进行伸缩(3Blue1Brown提供了这些因素的优秀推导):


最终,我们得到我们的乐高砖块,称为核函数,它是一个有效的 PDF:

这个核函数相当于均值为零、方差为单位的高斯分布。
我们先玩一会儿。我们将从学习如何沿* x *轴移动它开始。
取一个数据点xᵢ - 我们数据集X中的第i个点。通过减去参数可以实现平移:

为了使曲线更宽或更窄,我们只需在参数中加入一个常数h(即所谓的核带宽)。它通常作为分母引入:

然而,核函数下的面积因此乘以h。因此,我们必须通过除以h将其恢复到单位面积:

你可以选择任何h值。以下是它如何工作的一个示例。

h 越大,PDF 越宽。h 越小,PDF 越窄。
核密度估计器
考虑一些虚拟数据,以查看如何将方法扩展到多个点。
# dataset
x = [1.33, 0.3, 0.97, 1.1, 0.1, 1.4, 0.4]
# bandwidth
h = 0.3
对于第一个数据点,我们简单使用:


我们可以对第二个数据点做同样的操作:

为了得到前两个点的单一 PDF,我们必须将这两个单独的 PDF 结合起来:

因为我们添加了两个单位面积的 PDF,所以曲线下的面积变为 2。为了恢复到 1,我们将其除以 2:

尽管可以使用函数 f 的完整签名以提高精度:

我们将只使用 f(x) 以使符号更加简洁。
这就是它对两个数据点的工作方式:


KDE 的最终步骤是考虑 n 个数据点

核密度估计器是:

让我们来享受一下重新发现的 KDE。
import numpy as np
import matplotlib as plt
# the Kernel function
def K(x):
return np.exp(-x**2/2)/np.sqrt(2*np.pi)
# dummy dataset
dataset = np.array([1.33, 0.3, 0.97, 1.1, 0.1, 1.4, 0.4])
# x-value range for plotting KDEs
x_range = np.linspace(dataset.min()-0.3, dataset.max()+0.3, num=600)
# bandwith values for experimentation
H = [0.3, 0.1, 0.03]
n_samples = dataset.size
# line properties for different bandwith values
color_list = ['goldenrod', 'black', 'maroon']
alpha_list = [0.8, 1, 0.8]
width_list = [1.7,2.5,1.7]
plt.figure(figsize=(10,4))
# iterate over bandwith values
for h, color, alpha, width in zip(H, color_list, alpha_list, width_list):
total_sum = 0
# iterate over datapoints
for i, xi in enumerate(dataset):
total_sum += K((x_range - xi) / h)
plt.annotate(r'$x_{}$'.format(i+1),
xy=[xi, 0.13],
horizontalalignment='center',
fontsize=18,
)
y_range = total_sum/(h*n_samples)
plt.plot(x_range, y_range,
color=color, alpha=alpha, linewidth=width,
label=f'{h}')
plt.plot(dataset, np.zeros_like(dataset) , 's',
markersize=8, color='black')
plt.xlabel('$x$', fontsize=22)
plt.ylabel('$f(x)$', fontsize=22, rotation='horizontal', labelpad=20)
plt.legend(fontsize=14, shadow=True, title='$h$', title_fontsize=16)
plt.show()

这里我们使用高斯核,但我鼓励你尝试其他核。有关常见核函数家庭的回顾,请参见 这篇论文。然而,当数据集足够大时,核的类型对最终输出没有显著影响。
使用 Python 库的 KDE
seaborn 库利用 KDE 提供了数据分布的优美可视化。
import seaborn as sns
sns.set()
fig, ax = plt.subplots(figsize=(10,4))
sns.kdeplot(ax=ax, data=dataset,
bw_adjust=0.3,
linewidth=2.5, fill=True)
# plot datapoints
ax.plot(dataset, np.zeros_like(dataset) + 0.05, 's',
markersize=8, color='black')
for i, xi in enumerate(dataset):
plt.annotate(r'$x_{}$'.format(i+1),
xy=[xi, 0.1],
horizontalalignment='center',
fontsize=18,
)
plt.show()

Scikit learn 提供了 KernelDensity 函数来完成类似的工作。
from sklearn.neighbors import KernelDensity
dataset = np.array([1.33, 0.3, 0.97, 1.1, 0.1, 1.4, 0.4])
# KernelDensity requires 2D array
dataset = dataset[:, np.newaxis]
# fit KDE to the dataset
kde = KernelDensity(kernel='gaussian', bandwidth=0.1).fit(dataset)
# x-value range for plotting KDE
x_range = np.linspace(dataset.min()-0.3, dataset.max()+0.3, num=600)
# compute the log-likelihood of each sample
log_density = kde.score_samples(x_range[:, np.newaxis])
plt.figure(figsize=(10,4))
# put labels over datapoints
for i, xi in enumerate(dataset):
plt.annotate(r'$x_{}$'.format(i+1),
xy=[xi, 0.07],
horizontalalignment='center',
fontsize=18)
# draw KDE curve
plt.plot(x_range, np.exp(log_density),
color='gray', linewidth=2.5)
# draw boxes representing datapoints
plt.plot(dataset, np.zeros_like(dataset) , 's',
markersize=8, color='black')
plt.xlabel('$x$', fontsize=22)
plt.ylabel('$f(x)$', fontsize=22, rotation='horizontal', labelpad=24)
plt.show()

Scikit learn 解决方案的优点在于它可以用作生成模型来生成合成数据样本。
# Generate random samples from the model
synthetic_data = kde.sample(100)
plt.figure(figsize=(10,4))
# draw KDE curve
plt.plot(x_range, np.exp(log_density),
color='gray', linewidth=2.5)
# draw boxes representing datapoints
plt.plot(synthetic_data, np.zeros_like(synthetic_data) , 's',
markersize=6, color='black', alpha=0.5)
plt.xlabel('$x$', fontsize=22)
plt.ylabel('$f(x)$', fontsize=22, rotation='horizontal', labelpad=24)
plt.show()

结论
总结一下,KDE 使我们能够从任何数据创建一个视觉上吸引人的 PDF,而不需要对基础过程做任何假设。
KDE 的显著特点:
-
这是由单一类型的构建块组成的函数,称为核函数;
-
这是非参数估计器,这意味着它的功能形式由数据点决定;
-
生成的 PDF 的形状受到核带宽 h 值的严重影响;
-
为了拟合数据集,不需要优化技术。
KDE 应用于多维数据是简单的。但这是另一个话题。
除非另有说明,所有图片均由作者提供。
参考文献
[1] S. Węglarczyk, 核密度估计及其应用 (2018), ITM 网络会议,第 23 卷,EDP 科学出版社。
[2] Y. Soh, Y. Hae, A. Mehmood, R. H. Ashraf, I. Kim: 性能 各种核密度估计函数的评估 (2013), 应用科学开放期刊,第 3 卷,第 58–64 页。
多维数据的核密度估计器
使用实际数据集演示 KDE
·
关注 发表在Towards Data Science ·11 分钟阅读·2023 年 10 月 4 日
--
照片由Marco Bianchetti拍摄,发布在Unsplash
我希望通过考虑多维数据来扩展我之前的故事,讲解核密度估计器(KDE)。
我将首先提供一个关于主题的数学概述,然后你将获得 Python 代码以实验双变量 KDE。接下来,我将讲解高斯核的一些属性。最后但同样重要的是,使用 高度和权重 和 系外行星 数据集,我将演示如何将 KDE 应用于实际数据。
1. 数学介绍
KDE 是一个由几个类似的核函数组成的复合函数。我选择了高斯核,因为它易于分析。这个函数是多维高斯的原型

这本身是一个扩展

到许多维度。
向量 x 总共有 d 维(特征),上标表示特征的索引:

H 是一个 d 乘 d 的系数矩阵,决定了函数的形式。以下是一个二维 (d = 2) 的例子:

也许你还记得,只有曲线下的面积为单位的函数才能加入 PDF 俱乐部。因此,为了获得多变量高斯核函数,我们必须添加一些归一化项:

你可以自行验证,插入 d = 1 会得到一个标准的单维高斯函数。
矩阵 H 作为协方差矩阵。在上面的双变量案例 (d = 2) 中,h₁₁ 和 h₂₂ 分别对应于 x⁽¹⁾ 和 x⁽²⁾ 的方差,而 h₁₂ = h₂₁ 代表 x⁽¹⁾ 与 x⁽²⁾ 的协方差。这就是为什么矩阵 H 被认为是对称的。因此,在双变量情况下,用户可以通过三个参数来改变核函数。
核函数是一个自定义的模板函数,用于对每个数据点应用,以便使用简单的求和构建整个数据集的 PDF:

其中 xᵢ 是第 i 个数据点:

如果所有这些数学内容让你感到不安,不用担心。我将提供 Python 代码来创建可视化,展示其工作原理。要记住的主要点是:
核密度估计器是一个复合函数,由分配给每个数据点的核函数实例组成。
2. Python 代码使双变量 KDE 变为现实
这里有可以作为实验平台的代码,用于双变量高斯核和 KDE 实验。
导入部分先行:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import pandas as pd
import seaborn as sns
# to generate a visually appealing images with dataframes
import dataframe_image as dfi
# to make functions with some arguments fixed
from functools import partial
# Latex typefaces will be used for math symbols in figures.
plt.rcParams.update({
"text.usetex": True,
"font.family": "sans-serif",
"font.sans-serif": ["Helvetica"]
})
双变量高斯核函数 K 需要一个 2 乘 2 的 numpy 数组 H。函数 K 接受一个类似网格的数组作为参数 x。
def K(x, H):
# unpack two dimensions
x1, x2 = x
# extract four components from the matrix inv(H)
a, b, c, d = np.linalg.inv(H).flatten()
# calculate scaling coeff to shorten the equation
scale = 2*np.pi*np.sqrt( np.linalg.det(H))
return np.exp(-(a*x1**2 + d*x2**2 + (b+c)*x1*x2)/2) / scale
KDE 为每个数据点调用核函数 K 并累加所有结果,如 f(x) 所述。如果你不打算在应用程序中直接调用 K,可以将其定义嵌套在 KDE 中。
def KDE(x, H, data):
# unpack two dimensions
x1, x2 = x
# prepare the grid for output values
output = np.zeros_like(x1)
# process every sample
for sample in data:
output += K([x1-sample[0], x2-sample[1]], H)
return output
注意,对于单个数据点,f(x)等于K(x)。
最后的函数show_pdf显示二维函数func并将数据点data添加到其中,但data不必与函数func相关。PDF 有两种视角:轮廓图和表面图。
def show_pdf(func, data,
resolution = 100,
contours_density = 8,
surf_density = 40,
figsize=(10,5), cmap=cm.cividis,
aspect='equal', margins='auto',
s = 40, edgecolor='black',
markeralpha=1, markercolor='white'
):
# range for x and y axis is determined from the dataset
x1_min, x2_min = data.min(axis=0)
x1_max, x2_max = data.max(axis=0)
# plus some extra margins
if margins == 'auto':
x1_pad = max(3, int(0.3*(x1_max-x1_min)))
x2_pad = max(3, int(0.3*(x2_max-x2_min)))
else:
x1_pad = int(margins*(x1_max-x1_min))
x2_pad = int(margins*(x2_max-x2_min))
x1_range = np.linspace(start=x1_min-x1_pad,
stop=x1_max+x1_pad, num=resolution)
x2_range = np.linspace(start=x2_min-x2_pad,
stop=x2_max+x2_pad, num=resolution)
X1_grid, X2_grid = np.meshgrid(x1_range, x2_range)
# the given func is called here
Z_grid = func([X1_grid, X2_grid])
# draw a figure
fig = plt.figure(figsize=figsize)
ax = fig.add_subplot(1, 2, 1)
c = ax.contourf(X1_grid, X2_grid, Z_grid,
contours_density, cmap=cmap)
c2 = ax.contour(X1_grid, X2_grid, Z_grid,
contours_density, colors='black')
ax.set_xlabel(r'$x^{(1)}$', fontsize=16, labelpad=7)
ax.set_ylabel(r'$x^{(2)}$', fontsize=16, rotation=0, labelpad=8)
ax.scatter(data[:,0], data[:,1], marker='s',
color=markercolor, s=s, edgecolor=edgecolor, alpha=markeralpha)
ax.set_aspect(aspect)
plt.clabel(c2, inline=True, fontsize=10)
ax = fig.add_subplot(1, 2, 2, projection='3d')
ax.plot_surface(X1_grid, X2_grid, Z_grid,
rcount=surf_density,
ccount=surf_density, cmap=cmap)
ax.set_xlabel(r'$x^{(1)}$', fontsize=14)
ax.set_ylabel(r'$x^{(2)}$', fontsize=14)
plt.show()
3. 高斯核的属性
让我们从最简单的情况开始,用单位矩阵H表示:

坐标轴的原点作为一个数据点。
为了使用提供的代码,你首先需要定义数组H和至少一个数据点(data数组必须是二维的)。然后你可以对这些数据调用KDE。请注意,show_pdf接受一个函数func作为输入,并将其与网格状数组x作为第一个参数进行调用。因此,我们调用了functools库中的partial方法,它生成了KDE_partial函数,执行与KDE相同的操作,只是第二个参数H是固定的。这就是我将在本故事中使用代码的方法。
# covariance matrix
H = [[1, 0],
[0, 1]]
# a single point to make PDF
data = np.array([[0,0]])
# fix arguments 'H' and 'data' of KDE function for further calls
KDE_partial = partial(KDE, H=np.array(H), data=data)
# draw contour and surface plots
show_pdf(func=KDE_partial, data=data)
高斯核围绕数据点居中。

具有 0 协方差的高斯核。
让我们通过将非对角元素固定为 0.5 来引入一些相关性:

KDE 的形状变得倾斜和更薄。半主轴平行于x⁽¹⁾ = x⁽²⁾线。

具有 0.5 协方差的高斯核。
随着协方差系数的增加,KDE 变得更加狭窄。由于函数值变化更快,我提高了图形的分辨率。
H = [[1, 0.9],
[0.9, 1]]
KDE_partial = partial(KDE, H=np.array(H), data=data)
# when the function gets too sharp, boost the resolution
show_pdf(func=KDE_partial, data=data,
resolution=300, surf_density=50)

具有 0.9 协方差的高斯核。
你可以很容易地预测当协方差变为负数时会发生什么。


具有-0.5 协方差的高斯核。
这些示例清楚地展示了高斯 PDF 如何跟随数据的相关结构。
你可能会想知道如何旋转高斯 PDF。为此,请取一个旋转矩阵 R(我更喜欢顺时针版本):

并将H替换为RHRᵀ。以下是一个方便的函数,它返回表示按α度(以弧度为单位)旋转的矩阵的数组。
def angle2rotation_matrix(angle):
return np.array([ [np.cos(angle), np.sin(angle)],
[-np.sin(angle), np.cos(angle)] ])
R = angle2rotation_matrix(angle=(-1/10)*np.pi)
由于旋转对称高斯没有意义,我通过改变矩阵H的对角组件来拉伸它。
# the first axis scale is expanded twice
H = np.array([[2, 0],
[0, 0.2]])
# rotation
H = R @ H @ R.T
data = np.array([[0,0]])
KDE_partial = partial(KDE, H=np.array(H), data=data)
show_pdf(func=KDE_partial, data=data)
注意,第一个轴的尺度扩展了两倍,而第二个尺度缩小了五倍,这是因为对矩阵H的对角元素应用了不同的值。

拉伸和旋转的高斯核。
4. 身高和体重数据集
机器学习仓库中有几个现成的数据集。这就是为什么我感到惊讶的是,几乎每个常见的包含身高和体重列的数据集都是合成生成的。为了获得一些真实世界的数据,我要求我的学生提交他们的身高和体重,这个数据集现在可以在我的Github 仓库中找到。
让我们来看看这些数据。
filename = 'HeightsWeightsGender_dataset.xlsx'
# my Github repo
URL = "https://raw.githubusercontent.com/jdrapala/datasets/main/" + filename
# download the data form URL as a DataFrame
df = pd.read_excel(URL)
# make it look appealing
df_styled = df.sample(7).style.background_gradient(axis=0, gmap=df['Weight'], cmap='cividis')
# and export to file
dfi.export(df_styled, 'filename.png', dpi=300)
# extract numpy array from DataFrame
data = df[['Height','Weight']].to_numpy()

从我的学生那里收集的身高和体重数据样本。
让x⁽¹⁾表示身高,x⁽²⁾表示体重。
以单位矩阵H作为第一次尝试可视化数据集。
H = [[1, 0],
[0, 1]]
KDE_partial = partial(KDE, H=np.array(H), data=data)
show_pdf(func=KDE_partial, data=data, aspect='auto', margins=0.15)

使用的核函数过小。
结果很糟糕;PDF 在数据点周围的狭窄区域内有峰值,而其他地方几乎降至零。
我们通过将整个矩阵H乘以常数s(用于大小)来增大核函数。取s=10。
H = [[1, 0],
[0, 1]]
s = 10
KDE_partial = partial(KDE, H=s*np.array(H), data=data)
show_pdf(func=KDE_partial, data=data, aspect='auto', margins=0.15)

核大小应进一步增加。
单个峰值结合成一个漂亮的 PDF,但它仍然显得过于详细。因此,将s增加到 64,以获得以下结果。

核函数似乎大小合适。
这个核函数的大小似乎适合这个数据集。
将我们手工制作的 PDF 输出与 seaborn KDE 进行比较。
# draw PDF
ax = sns.kdeplot(x='Height', y='Weight', data=df,
fill=True, cmap=cm.cividis,
bw_adjust=1.1)
# draw datapoints
ax.plot(df['Height'], df['Weight'], 's', color='white',
markersize=6, markeredgecolor='black', markeredgewidth=0.7)
plt.show()

矩阵H中的非零协方差元素对 PDF 会产生什么影响?对于协方差条目值为 0.8,返回以下 PDF。

这似乎是前一图形的一个风天变体。
PDF 质量明显下降。
类似的实验表明,带有单位矩阵H的高斯核在实际情况中是足够的。因此,一切可能归结为选择适当的参数s值,这决定了核函数所覆盖的区域,就像单变量情况下的带宽h一样。
作为一个练习,考虑为我在众多医学数据分析研究项目中收集的大型身高和体重数据创建一个 PDF 表示。然而,请记住,这些数据来自于心脏病、糖尿病和其他疾病的患者,因此在为一般人群得出结论时要谨慎。我对这些数据的质量也有一些担忧,因为它是从许多医院收集的。

从各种医院收集的身高和体重数据。
5. 外行星数据集
KDE 在多模态分散数据中尤其有用。因此,我给你介绍另一个数据集。我相信我们都对鸢尾花数据集感到厌倦了。
这个数据集是直接从Exoplanet Orbit Database 网页下载的。由于文件很大且包含混合数据类型,我不得不在read_csv方法中设置low_memory=False。
我选择了两列:系外行星的质量(以 Jupyter 质量为单位)和它与母星的距离(以天文单位为半长轴)。
URL = "http://exoplanets.org/csv-files/exoplanets.csv"
df = pd.read_csv(URL, low_memory=False)
# drop rows containing missing values
df = df[['NAME', 'MSINI', 'A']].dropna()
# i don't like orignal columns names
df = df.rename(columns={'NAME': 'Name',
'MSINI':'Mass',
'A': 'Distance'}).set_index('Name').astype(float)
# some masses are not missing but equal to zero, let's get rid of them too
df = df.drop(df[df['Mass'] == 0].index)
# # make it look appealing
df_styled = df.sample(7).style.background_gradient(axis=0,
gmap=df['Mass'],
cmap='cividis')
dfi.export(df_styled, 'filename.png', dpi=300)

一份系外行星数据集的示例。
让我们快速查看数据在对数尺度上的分布情况。
with plt.style.context('seaborn'):
sns.scatterplot(data=df, x='Mass', y='Distance')
plt.xscale('log')
plt.yscale('log')
plt.show()

对数尺度上的系外行星数据。
KDE 为s = 0.7 生成了视觉上令人满意的 PDF,使用了这些数据。
# logarithmic scale is more appropriate for this data
data = np.log(df[['Mass','Distance']].to_numpy())
H = [[1, 0],
[0, 1]]
# size of the kernel
s = 0.7
KDE_partial = partial(KDE, H=s*np.array(H), data=data)
show_pdf(func=KDE_partial, data=data, aspect='auto',
markeralpha=1, s=1.5, margins=0.15)

系外行星数据被分为三个簇,尽管左侧的簇明显更稀疏。
我建议你尝试 Scikit-learn 库中的[KernelDensity](https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KernelDensity.html)方法,它允许你在 KDE 拟合数据后轻松生成合成数据。
from sklearn.neighbors import KernelDensity
# fit KDE to the dataset
kde = KernelDensity(kernel='gaussian', bandwidth=0.4).fit(data)
# Generate random samples from the model
synthetic_data = kde.sample(1000)
df = pd.DataFrame({'x1': synthetic_data[:,0],
'x2': synthetic_data[:,1]})
with plt.style.context('seaborn'):
sns.scatterplot(df, x='x1',y='x2')
plt.xlabel('Mass', fontsize=16)
plt.ylabel('Distance', fontsize=16)
plt.show()

使用 Scikit-learn 合成生成的系外行星数据。
最后的话
精美的可视化有助于从数据中学习并得出适当的结论。KDE 使数据分布以视觉上令人愉悦的方式呈现。因此,其大多数数据探索应用归结为二变量情况。
请记住,在单点计算 KDE 值时,需要处理所有数据点,这在大规模计算中可能会耗时。在这种情况下,你应该考虑使用高斯混合模型。不过那是另一回事……
致谢
本研究使用了exoplanets.org上的系外行星轨道数据库和系外行星数据探索器。
除非另有说明,所有图片均由作者提供。
参考文献
[1] Kristan, M., Leonardis, A., Skočaj, D. 带高斯核的多变量在线核密度估计 (2011). 模式识别, 44(10–11), 第 2630–2642 页.
[2] S. Węglarczyk, 核密度估计及其应用 (2018), ITM web of conferences, vol. 23, EDP Sciences.
[3] Han, E., Wang, S. X., Wright, J. T., Feng, Y. K., Zhao, M., Fakhouri, O., Brown, J. I., Hancock, C. 外行星轨道数据库 II. 对 exoplanets.org 的更新 (2014). 太平洋天文学会出版物, 126(943), 827.
内核:你需要了解的一切
👨🏫 数学
密度估计、点积、卷积及其他一切……
·
关注 发表在 Towards Data Science ·14 min 阅读·2023 年 3 月 6 日
--
来源:作者提供的图片
内核或核函数是美丽的数学概念,用于机器学习和统计学中,形式各异。如果你是初学者,你可能会想知道内核的确切定义,但你可能会被互联网上各种博客/网站上解释的多种定义弄混。
核函数是一个令人困惑的概念,因为围绕它的知识在不同的应用中是分散的,缺乏一个将它们连接起来的常见直觉。这个(庞大的)博客旨在统一不同 ML 应用中使用的核函数的所有知识。就像大多数初学者一样,核函数让我困惑了很长时间,直到我培养出一种将所有环节连接起来的直觉。
我们从非参数模型开始我们的旅程,然后开始讨论不同类型的核函数及其在统计和机器学习中的典型应用。类似于核函数,我也尝试从数学角度解释 PCA,考虑所有的视角。你可以阅读一下:
协方差、特征值、方差以及一切……
towardsdatascience.com
非参数模型
非参数模型是那些统计模型,它们的参数不会随着输入规模的增长而增加。需要注意的是,非参数模型并不是指‘零参数模型’,而是指它们使用的是一组固定的参数,也称为超参数,这些参数不会随着输入维度的增加而增加。一个简单的线性回归模型有参数θ,这些参数决定了超平面的斜率,其大小取决于输入x的维度。
方程 1. 一个具有可调参数的简单线性回归模型,其参数大小取决于特征数N。函数f表示给定输入x的因变量y的期望值,即E[y|x]
接下来,考虑 KNN 模型,我们通过分析测试样本的K个最近邻的类别来确定测试样本的类别。如果K = 1,我们假设测试样本与最近邻属于同一类别。这个模型没有任何随着输入维度增加而增长的参数。对于一个简单的实现,即使在处理大规模输入(从维度角度看)时,我们也只需要一个单一的参数K。

1. K 最近邻模型工作原理的可视化描述。这里的假设是测试样本属于与 K 个最近邻相同的类别。图片来源:作者提供的图片
KNN 是一个非参数模型,它有一个由用户提供的超参数K。非参数模型乍一看可能是一个明显的选择,因为,
它们不对数据的分布做任何先验假设。例如,在普通线性回归中,它是一个参数模型,我们假设Y(因变量)在给定X(特征)的条件下服从高斯分布,其均值是特征的线性组合(其中权重是θ)和方差等于σ²。
- 条件分布Y给定X和参数θ 是一个正态分布,我们希望从X中捕捉均值。常量方差是* 同方差假设 的结果。
这可能并不总是成立,因为,
对于每个测试样本,他们需要将整个训练数据保存在内存中,这对 KNN 模型也是适用的。对于每个样本,我们需要计算它与每个训练样本的距离,因此我们需要检索/存储每个样本在内存中,这对于大型数据集甚至特征数量众多的小型数据集可能不可行。
非参数模型的基本思想是从数据中获取一些有用的见解,并用来解决给定的问题,而不通过可调参数对数据进行编码。
接下来,我们专注于核函数,它们在机器学习中有不同的使用场景,并且在每个上下文中稍有不同的含义。到目前为止,通过对这个博客的研究,以及我之前对核函数整体理解的尝试,我觉得核函数是提供给定数据点邻居信息的机器(作为输入提供给机器)。这些局部信息,即在考虑的数据点周围的邻近数据点的信息,然后用于解决给定的问题。一旦我们对每个数据点使用核函数,我们就能清楚地看到数据局部的情况。
我们将探讨核函数的这三个方面,它们是机器学习中具有主要应用的三个不同概念,
-
密度核函数
-
Mercer 核函数
-
图像处理中的核函数
密度核函数:用于密度估计的核函数
密度核函数、核密度估计、核回归
我们可以使用核函数通过用训练样本建模基础概率分布来估计给定测试样本的概率密度。‘测试’样本和‘训练’样本分别指未观测和已观测的样本,考虑到机器学习的术语。
对于连续随机变量X,我们可以在适当的范围内积分X的概率密度函数,比如从x_1到x_2,从而得到X在范围[ x_1 , x_2 ]内取值的概率。如果你对概率密度或随机变量的主题不太熟悉,这里是我关于概率分布的三部分系列,
密度核函数和核密度估计(KDE)
让我们从一个问题开始讨论。Panchal 博士住在一个拥挤的城市街区,四周都是房子。当地警方雇佣了一名侦探,其工作是确定博士家中居住的人数或其家属人数,以确保一切顺利。侦探不能按博士的门铃询问有多少家庭成员,因为这会警告博士如果有什么可疑的情况。
侦探会从 Panchal 博士房子的相邻房子开始询问,因为这些房子可以清楚地看到里面的情况。预计侦探会给予这些直接邻居的信息更高的权重/重要性。接下来,为了获得更多见解,侦探会询问那些稍微远离但可能对邻居有良好信息的房子。对于这些邻居提供的信息,侦探会给予较低的重要性,因为这些邻居的观察可能不如直接邻居(即邻近 Panchal 博士的房子)的观察准确。侦探会进行几轮这样的询问,逐渐减少重要性,远离 Panchal 博士的房子。
密度核函数类似于捕捉邻近点的信息。如果我们有一个数据集D,其中有N个样本,每个样本是一个实数,
上述代码片段中的核函数是一个 Epanechnikov(抛物线型)核函数。该核函数在这里具有一些特殊性质,
-
性质 1:核函数或侦探不在意 x 或某些邻居的房子位于哪个方向。来自右边两栋房子或左边两栋房子的获得的信息是相同的。
-
性质 2:核函数表示一个有效的概率密度函数(PDF),并且在整个实数域上积分为 1。
-
性质 3:核的支持是所有值u的集合,使得K(u)不等于 0。它表示侦探在从哪里收集信息时会给予一些非零的重要性。如果侦探决定在半径 5 公里内询问所有房屋,那么支持就是这个 5 公里圆圈内的所有房屋。
每种类型的核函数都会执行类似的任务来编码邻近信息,每种核函数会有不同的策略来实现这一点。与我们的侦探不同,他在离开 Panchal 博士的房子时逐渐减少了对询问的重视(高斯核会这样做),另一个侦探可能会继续对所有询问赋予相同的重要性,忽略到一定程度的距离(均匀核)。想象一下,从我们的数据集D来看,所有x_i的分布是,

需要估计其概率密度的类似高斯分布
我们的目标是创建对 X 的概率分布的估计。我们通过在每个样本 x_i 处估计密度并使用核来收集邻近信息来实现这一目标。

使用 KDE 估计概率密度
如果 x_i 离 x 较远,则 | x — x_i | 将有更大的值,从而使 K( x — x_i ) 的值非常小,并减少 x_i 在确定 x 处的概率密度中的 作用。参数 h 是称为 带宽 的 平滑参数。h 的值越大,预测的概率密度越平滑。

通过改变核的带宽获得的概率密度估计。最底部的图描绘了数据分布,通过增加核的带宽我们获得了更平滑的密度估计。这里用于估计的核是 Epanechnikov/Parabolic 核。
核回归
核回归是线性回归模型的非参数版本,我们在其中对结果变量的条件期望进行建模。在简单线性回归的情况下,我们通过将其表示为自变量的线性组合来直接建模条件期望 E[ Y | X ]。这产生了一个判别模型,而核回归是一个生成模型,因为我们通过核密度估计建模联合概率分布 P( X , Y ) 和 P( X )。
推导一个没有额外参数的 E[ Y | X ] 估计量。在简单线性回归的情况下,E[ Y | X ] 将被建模为所有 X_i 的加权线性组合,其中权重是参数。
观察结果表达式时,你会发现预测结果 y_hat 是所有 y_i 的加权组合,其中权重由所有 x_i 的核函数值决定。
梅瑟核:高维空间中的点积
梅瑟核和正定性,梅瑟核在 SVM 中的应用
梅瑟核或正定核是将两个输入作为输入并产生一个实数的函数,该实数表征了这两个输入(或它们的高维表示)在某个其他空间中的接近度。事实证明,这些核在计算角度上很有用,因为它们帮助我们在高维空间中计算向量的点积,而无需显式地进行任何变换将我们的向量带入该高维空间。
梅瑟核
让我们通过定义核函数及其一些属性来开始讨论,

-
Mercer 核是一个函数,它接受来自数据集D的两个数据点,并输出一个实数,表示这两个数据点在特征空间中的接近程度。
-
如果我们在数据集D中有n个数据点,并对每对数据点应用 Mercer 核,并将结果输出整理成一个矩阵,我们会得到一个正定矩阵。这个矩阵描绘了数据点之间的相似性,称为 Gram 矩阵。

Gram 矩阵
正定矩阵因其谱属性而独特。它们具有正特征值,并且相应的特征向量形成一个正交规范基。对于 Mercer 核,我们有一个特殊的属性,可以使用该属性将核函数的值表示为两个转换向量的点积。

Gram 矩阵的每个条目可以描述为两个转换样本之间的点积。
可能会有一种直觉上的冲动去理解这个陈述,但它存在于 Hilbert 空间的神圣领域,需要另写一篇博客。目前,了解核函数的值可以用高维空间中两个向量的点积来描述,这是很好的。
Mercer 核提供了一种计算这两个高维向量之间点积的快捷方式,而无需显式计算这些向量。因此,我们可以利用高维空间的优势,这在机器学习中有时是有用的,特别是当样本不线性可分时。

在低维空间中线性不可分的样本可能会在高维空间中找到一个最优的超平面。来源:作者图片
对于一些优化问题,如优化 SVM 时遇到的问题,我们需要计算两个高维向量之间的点积。使用核函数可以帮助我们轻松计算这个点积,而不需要对样本进行任何显式的转换。
Mercer 核在 SVM 中的应用
SVM 是线性分类器,它通过拟合一个超平面,使得在两个类别的样本之间形成一个决策边界。为了确定最佳的超平面,即将样本划分为两个类别并最大化“边际”的超平面,我们需要解决一个包含目标函数(一个可以最大化或最小化的函数)以及一些约束条件的优化问题。
SVM 优化问题的推导在这些博客中得到了广泛的讨论,Saptashwa Bhattacharyya的博客可以帮助我们进一步了解,
为什么使用核函数?
towardsdatascience.com
向量w和b描述了形成决策边界的超平面。支持向量之间的边际/宽度由下述第一个表达式给出。此外,我们会匹配 SVM 做出的预测与目标标签,更确切地说,就是w.xi + b和yi的符号。

我们必须解决的优化问题是:

SVM 优化问题
我们通过拉格朗日乘数来解决这个优化问题,因此第一步是构建一个拉格朗日函数,并将其参数的偏导数设置为零。这将得到一个w的表达式,能够最小化拉格朗日函数。

将拉格朗日函数的偏导数设置为零。
将这些结果代入拉格朗日函数后,我们得到一个清晰描述核函数作用的表达式。

为了实现最佳超平面,我们需要计算数据集中样本对之间的点积。在某些情况下,找到一个最佳超平面是不可行的,因为样本可能不是线性可分的,即样本不能仅通过绘制一条线/平面来分成两个类别。我们可以通过增加样本的维度来发现一个分离超平面。
考虑一个特征映射ϕ,它将数据样本x转换为更高维度的特征,即ϕ(x)。在 SVM 的拉格朗日函数中,如果我们使用这些特征代替数据样本,我们需要计算

我们可以用一个核函数来替代特征的点积,该核函数对两个数据样本进行操作(而不是转换后的特征)。

这种技术被广泛称为核技巧,是 Mercer 定理的直接结果。我们能够计算两个高维特征的点积,而无需显式地将数据样本转换到那个高维空间。随着维度的增加,我们在确定最佳超平面时具有更大的自由度。通过选择不同的核函数,可以控制特征所在空间的维度。
核函数具有更简单的表达式,如下所示,

卷积的核: 图像处理
用于卷积和图像处理的核
核是固定大小的矩阵,用于在图像或特征图上卷积,以提取有用的信息。在图像处理中,核矩阵也称为卷积矩阵,用于对图像进行操作。每个核都有自己特定的操作,这在卷积后会改变图像。
卷积与核
卷积是一个数学算子,它接受两个函数并生成另一个函数。如果我们对两个函数或信号进行卷积,那么卷积的结果是一个表示两个函数之间重叠区域的函数。从数学上讲,卷积操作被定义为,

描述卷积操作的动画图,以及它的数学定义。这类似于在密度估计中进行的“核滑动”。我们将核滑过数据分布,收集“邻域信息”,然后在特定点估计密度。来源: 维基百科 — 卷积 (维基共享资源) — CC BY-SA 3.0
注意函数g在图上经过x = 0处墙壁时的卷积结果。结果突然变化并开始增加,这是由于x = 0周围邻域信息的变化。函数g,类似于我们在密度估计中研究的核,能够对核影响区域发生的变化做出反应。
从离散的角度来看,卷积操作是通过将核函数滑动到信号上,乘以信号和核的对应值,然后将所有这些乘积的总和放置到结果信号中来完成的。在数学意义上,考虑到离散信号的求和要比考虑连续信号上的积分更为合适。


对一维离散信号执行卷积操作。上面的表达式显示了相同的数学公式。图像来源: 神经网络中的一维卷积 — Brandon Rohrer(创作共用许可证) — CC0 1.0 Universal
对于图像,我们将一个二维核滑动到给定的图像上并执行相同的操作。这里,核的运动将是二维的,与在一维信号上进行的一维(单向)核运动相对。输出将是一个矩阵,因为卷积操作也是在二维输入上执行的。

使用内核矩阵进行卷积。来源:卷积 — 维基百科(创作共用许可证)— CC BY-SA 3.0
我们可以使用不同的内核从输入中提取各种特征或增强图像以进行进一步操作。例如,锐化内核会锐化图像中的边缘。许多其他内核在卷积过程中从图像中提取有趣的特征。

使用不同的内核在图像处理中执行卷积。来源:作者提供的图片
CNN 中的内核
我们刚刚看到的内核是常量,但如果我们可以对内核进行参数化并控制提取哪些特征呢?这在卷积神经网络中会很有用,因为我们微调内核以最小化 NN 产生的整体损失。基于内核的非参数模型的概念在这里会减少,因为 CNN 可以拥有大量参数,但邻域信息提取的基本概念仍然有效。
这里的内核功能类似于锐化或 Sobel X 内核,但它将矩阵中的值视为参数,而不是固定的数字。这些可训练的内核通过反向传播进行优化,以减少 CNN 中的损失值。一个卷积层可以有许多这样的内核,统称为滤波器。
解决常见的混淆问题。
towardsdatascience.com 
一个典型的卷积神经网络,包括最大池化和线性(全连接)层。卷积层和最大池化层从输入图像中提取特征,然后将这些特征传递给线性层。来源:作者提供的图片
第一个卷积层产生的输出传递到下一个层。这创建了一个分层特征提取过程,其中图像的低级特征由初始卷积层提取,高级特征则由最后的卷积层跟踪。这样的卷积堆栈与可训练的内核结合,使 CNN 能够以极高的精度识别图像中的物体,为现代计算机视觉开辟了新的领域。
结束
我希望这段关于内核世界的旅程能让你对这一概念感到着迷。内核在各种话题中经常引起混淆,但其核心思想始终如一,我们在博客中多次提到的就是邻域特征提取。与其使用参数捕捉数据中的模式,内核函数可以编码样本的相对接近度,从而捕捉数据中的趋势。然而,必须理解参数模型有其自身的优势,它们的使用并未过时。大多数神经网络模型是庞大的参数模型,具有数百万个参数,可以解决复杂的问题,如物体检测、图像分类、语音合成等。
除非另有说明,所有图像均由作者提供。
了解你的受众:技术演示准备指南
一种结构化的方法来创建符合利益相关者需求和关注点的地址
·发布在Towards Data Science ·9 分钟阅读·2023 年 10 月 6 日
--

照片由Wan San Yip拍摄,发布在Unsplash上
将复杂话题有效地呈现给组织的能力是明显将数据专业人士与其他人区分开的技能。在处理复杂话题时,提炼复杂信息为清晰的解释至关重要,而这一努力的成功取决于弥合复杂性与理解之间的差距。这一点在讨论数据科学中的难题时尤为重要,例如深度学习算法、贝叶斯推断和降维(仅举几例)。
本文是关于准备演示材料系列中的第一篇文章,其中我将介绍我在创建演示文稿时用来将高层次主题转化为简单总结的策略和技巧。本系列将详细介绍我在考虑如何将演示文稿结构化以使其清晰、简洁和有效时所使用的各种方法。

照片由Kristina Paparo拍摄,发布在Unsplash上
我在这一系列中的建议可以分解为 3 个简单的原则,我在下面列出了这些原则:
-
了解你的受众
-
引导你的受众
-
预期并准备回应
所有这些要点都是相互关联和相互依赖的——成功的演讲将结合所有三个要点,使观众能够理解你的关键信息,获取与他们相关的信息,并以令人满意的方式回答他们的问题和关切。掌握这三个关键指南,你可以确保技术演讲的成功。
在这篇文章中,我将重点讨论第一个指南——如何对你的观众有足够的理解,以便能够衡量他们的关键关切、对当前话题的基本了解水平以及对你即将进行的演讲的期望。这种准备水平在处理由不同动机的不同利益相关者组成的大型观众时是至关重要的,同时还要有效地传达复杂的源材料。
了解你的观众
在他关于军事战略的著名论文中,中国将军孙子写道:
如果你了解敌人并了解自己,你不必担心百战百胜的结果。
自然地,你的观众不是你的敌人(希望如此),但关键前提仍然相同——提前充分了解你的观众将使你能够充分准备你的演讲,应对可能出现的各种情况。考虑到这一点,我想提出一些理解观众需求的提示和技巧,以便在演讲前做好准备。
绘制图示
在准备演讲时,考虑一下谁将听这个演讲——你的观众的主要背景是什么?他们是该领域的专家,还是仅仅对材料有一个表面的了解?这个人群对项目的内部运作感兴趣,还是更关注结果而不太关注细节?这个观众群体是否包括组织中的高级决策者?
为了系统地回答这些问题,我喜欢应用广泛领域中使用的利益相关者管理技术——利益相关者矩阵和影响-兴趣网格。

Miguel Henriques拍摄的照片,来源于Unsplash
首先定义什么是利益相关者——利益相关者是对项目或企业有影响的人(个人或团体)。在演讲的背景下,项目就是演讲本身——因此,利益相关者是任何对演讲结果有贡献或受到影响的人。这些人可以从直接处理演讲中显示的数据的数据分析师,到基于演讲建议做出决策的高管——识别所有可能受项目影响的个人或方是重要的,以便你可以在利益相关者矩阵中捕捉所有相关的关切。
利益相关者矩阵是一种相对简单的方法,用于绘制你关键听众的兴趣。它是一张描述每个利益相关者的关键关切、影响力和兴趣水平的表格,基本上绘制了他们在当前话题上的位置,以及他们在这一领域的影响力和兴趣。这在决定演讲方式和如何最佳展示信息时非常有用。
举例来说,假设你要在一个大型工厂内讲解工具利用趋势——最可能的听众将来自生产部门、质量控制、维护与设施、财务和高级管理层。你为这样的演讲准备的利益相关者矩阵可能如下图所示:

工具利用利益相关者矩阵(图片来源:作者)
一旦你草拟了利益相关者矩阵,并且对你的受众有了满意的理解,你现在可以将这些信息映射到影响-兴趣矩阵上,以获得每个利益相关者在当前话题上的视觉表示。
影响-兴趣矩阵是规划哪些利益相关者需要定制演示文稿的有效方法——右上角的利益相关者最为重要,需要紧密管理,而图表左下角的利益相关者则只需监控。下图很好地解释了这个概念:

空白兴趣-影响矩阵示例(图片来源:作者)
对于这个工具利用的例子,兴趣-影响矩阵看起来如下:

工具利用兴趣-影响矩阵(图片来源:作者)
根据上面的矩阵,你会得出结论:生产、质量和工厂管理是你在演讲时最重要的对象——然而,工厂管理的影响力较高,但对这个话题的兴趣不如生产或质量。财务在矩阵中的排名非常低,因此你不应该花太多时间考虑他们的兴趣。设施的影响力低但兴趣高——因为你需要他们的帮助来执行演示中做出的任何决定,保持他们的支持很重要。
考虑背景
现在你已经准备好了利益相关者分析,考虑一下你受众的最迫切关切是什么——确保在演讲时解决这些问题。了解你的演示目的,以便确定传达信息时应使用什么语调——你是在通知、说服还是教育你的听众?每一个目标都会导致对话题的略有不同的方法:
-
通知——在传达过程中保持客观,遵循数据,优先考虑清晰性和准确性,避免得出具体结论
-
说服——使用更多主观语言,用数据和统计支持你的主张,展示为什么这种观点相对于其他观点更为优秀。
-
教育——详细解释术语,优先考虑简洁明了的表达,使用类比或隐喻来解释难以理解的概念。
继续以工具利用率的例子为例,假设背景是工厂中的工具利用率低——你的演讲目的是向利益相关者通报这一问题,并展示追踪这一问题的指标?还是说服工具所有者更有效地使用他们的工具?或者是教育工程团队保持高工具利用率的最佳实践?考虑这些问题并将其纳入利益相关者分析,以确保你的演讲传达出适当的语气和信息。

图片由Campaign Creators提供,来源于Unsplash。
其次,注意你被分配的演讲时间——这将决定你在讲解时能够深入的细节。如果你有一小时的时间,那么应力求在这一小时内讲解重要细节并根据需要详细阐述。然而,如果你只能讲解 5 或 10 分钟,则必须坚持最相关的细节,立即而彻底地解决最重要的点。如果时间紧迫,不要重复旧内容——而是声明这些细节在其他论坛中已经讨论过(如有要求提供相关材料),然后转向最新的信息。
最后,准备材料时考虑观众的基础理解水平非常重要——避免花费过多时间在众所周知的点上,而应略过这些点,优先介绍对讨论更相关的新颖和有影响力的信息。问自己,这些信息是否能带来新的或重要的收获——如果是的话,那么在幻灯片中优先考虑这些信息。
相应地调整你的语言。
通常,选择一种语气并在整个演讲中坚持使用并不简单——你演讲中的每个点可能需要调整语气和方法,以实现预期的效果。在演讲的某些部分,你可能会向观众提供统计数据——在描述其他细节时,你可能会转为更具指导性的语气,以确保在继续之前这些概念得到良好理解。你可能会短暂转为说服性语气,来论证更具争议的观点,然后再返回信息性立场,讲解更多公认的声明。
将这一切结合在我们之前的类比中——在准备你的利益相关者矩阵并将其映射到影响-兴趣网格上时,你知道生产、质量和高级管理层是这次演示中最重要的利益相关者。维护部门也非常感兴趣,但他们的权力不如这些小组。财务在影响-兴趣网格上优先级较低,不值得过多考虑。
生产部门希望工具能够尽可能高效运行以最大化产出,而质量部门希望工具能够尽可能精确运行,以最小化产品中的潜在缺陷。高级管理层关心这两种结果,他们的目标是从工厂中输出大量高质量的产品。假设你的最终目标是提高工厂的使用率——根据这些信息,你需要准备哪些数据,以及你需要使用什么语气,以确保这次演示成功?

图片由Reimond de Zuñiga拍摄,来源于Unsplash
由于这个假设情境中的问题最终归结为产量与质量,因此这些是你在演讲中应重点强调的要点——准备数据以显示使用率的提高不会导致负面的质量影响。你将通知关键利益相关者这些机会(特别是生产和工厂管理层),并教育维护部门如何提高这些工具的使用率。
在使用率增加可能对质量产生影响的情况下,你的讲话语言必须变得更加有说服力,以说服质量部门在工具精度上做出让步。准备量化产量与质量影响之间权衡的数据,并确定这两个指标之间的最佳平衡点,以实现工厂中最高质量产品的最大产量。这将吸引工厂管理层,同时旨在安抚质量部门。很可能,质量部门会希望详细审查这一分析——为此准备支持材料。详细描述这点将使人明确你进行了彻底的分析,并应消除对你评估质量的任何疑虑。
结论
总结来说,了解你的听众是准备复杂主题高级演讲的一个关键方面。通过了解利益相关者的兴趣和影响力水平,可以制定针对性的消息,使其与听众产生共鸣,并最终在决策后产生影响。此外,策略性地使用语调增添了另一个层次的深度,塑造了利益相关者对材料的认知和参与度。当这些技巧在演讲中无缝整合时,演讲便成为了捕获听众注意力的高效工具,从而导致更成功和有影响力的结果。
知识图谱嵌入基础
知识图谱嵌入(KGE)算法的总结
·
关注 发表在 Towards Data Science ·7 分钟阅读·2023 年 4 月 9 日
--
图片来源。该图片根据 Pixabay 的内容许可可以自由使用。
在我们最新的一篇系列文章中,如何基于图设计推荐系统?我们介绍了一种新兴的推荐系统算法类别,即基于知识图谱的推荐系统。这些系统利用知识图谱的语义结构和知识图谱嵌入(KGE)算法的强大能力,为用户提供更精准的产品推荐。
什么是知识图谱嵌入?
如前一篇文章所述,知识图谱(KG)在表示结构化数据和整合来自不同来源的数据方面非常有效。然而,知识图谱三元组的符号化特性通常使得知识图谱在机器学习应用中难以操作。
知识图谱嵌入(KGE)是将 KG 元素表示为连续向量空间的一种表示方法。学习这些嵌入的目标是简化图元素(实体、关系)的操作,以用于预测任务,例如实体分类、链接预测或推荐系统。
大多数提出的方法仅依赖于图三元组,目的是将 KG 实体和关系嵌入到连续的向量空间中。这个想法是保留 KG 的固有结构并简化 KG 元素的使用。一旦 KG 元素被表示为嵌入,就使用评分函数来测量三元组的合理性(例如‘George’,‘是 A’,‘Person’)。

上图展示了节点和关系在二维向量空间中的嵌入表示。图片来源:docs.ampligraph.org/en/1.1.0/。Ampligraph 是一个免费的开源 Python 库,用于知识图谱嵌入。版权所有 AmpliGraph,许可证为 Apache 2.0。
受词嵌入启发
嵌入技术因 2013 年发布的 Word2vec [1] 而变得流行。Word2vec 通过训练一个浅层神经网络来预测词汇表中一个词的上下文,从而高效地学习词嵌入,关键思想是保留词的语义。图下所示的两种不同架构分别是 Continuous Bag-of-Words [2] (CBOW),它实现了一个神经网络,其中输入是上下文词 wₜ₋ᵢ ,wₜ₋ᵢ₊₁ …wₜ₊ᵢ₋₁,wₜ₊ᵢ,而输出是预测的目标词 wₜ;另一个是 Skip-Gram [1],它实现了一个两层神经网络,其中输入是目标词 wₜ,输出是上下文词。

CBOW 架构基于上下文预测当前词,而 Skip-gram 模型给定当前词预测周围词。图片来源:Mikolov et al.
按照相同的逻辑,DeepWalk [3] 和 node2vec [4] 的作者通过建议使用神经语言模型如 Word2vec 来构建图嵌入,将嵌入扩展到图中。在 DeepWalk 中,作者提出通过依赖图中的随机均匀游走来提取图中的节点序列——这些节点代表实体。这个节点序列可以看作是文本,然后应用 CBOW 或 SkipGram 来构建这些节点的嵌入。

DeepWalk 旨在在嵌入空间中保持图的局部结构。图像来源:Bryan Perozzi, Rami Al-Rfou, Steven Skiena
Node2vec 通过引入更复杂的随机游走策略进一步改进,这种策略可以更容易地适应多样的图连接模式,在链接预测和知识图谱补全任务中优于 DeepWalk。
利用知识图谱的属性
仅考虑图结构以编码 KG 元素仍然不够,因此出现了其他方法,也考虑图的属性和实体类型。在 [5] 中,作者将知识图谱嵌入算法分为两大类,即基于评分函数的转换距离模型,通过在向量空间中测量距离来评估三元组的可信度,通常是在执行转换操作后,以及基于相似度评分函数的语义匹配模型,通过匹配实体和关系的潜在表示的语义来评估三元组的可信度。
转换距离模型
对于第一类,TransE [6] 通常被提及为最常用的转换距离模型。TransE 在相同的空间 Rₘ 中表示实体和关系向量。给定一个三元组 (s,p,o),关系被解释为一个转换向量 r,使得嵌入的实体 s(主体)和 o(客体)可以通过 p 以低误差连接,即,当三元组 (s,p,o) 在知识图谱中成立时,s + p ≈ o。换句话说,目标是最小化下面表示的评分函数。

TransH [6] 引入了关系特定的超平面,每个属性 p 在超平面上由其法向量 wₚ 表示。TransR [8] 采用了与 TransH 相同的思路,但不是将关系投影到超平面中,而是建议为每个关系创建一个特定的空间。下图表示了上述不同的转换距离模型的嵌入空间。

对于 TransE,无论关系如何,嵌入之间的距离都在相同的嵌入空间中计算,而对于 TransH 和 TransR,它们在关系特定的空间中计算。(h, r, t) 是 KG 中的一个三元组。图像来源:Wang et al.
语义匹配模型
另一方面,语义匹配模型利用基于相似度的评分函数。在 [9] 中,作者提出了 RESCAL,这是一种将每个实体与一个向量关联起来以捕捉其潜在语义的模型。每个关系被表示为一个矩阵,模型化潜在因素之间的配对交互。三元组 (s, p, o) 的得分由一个双线性评分函数定义,通过基于 ALS 优化技术的张量分解来最小化。

RESCAL 方法的张量嵌入。图片来源:Bhattarai 等
扩展 RESCAL 的其他方法出现了。NTN [10](神经张量网络)是一种通过非线性层学习表示的神经网络。ER-MLP(多层感知器),其中每个关系(以及实体)都与一个向量关联。更具体地说,给定一个三元组 (s, p, o),s、p 和 o 的向量嵌入在输入层中连接,并映射到一个非线性隐藏层。
其他方法也出现了,比如 DistMul,它通过用对角矩阵表示关系来简化 RESCAL,从而减少其复杂性,ComplEX 扩展了 DistMul,使用复数代替实数。近年来,许多方法出现了,旨在简化现有文献并提高现有知识图谱(KG)任务的算法准确性,如链接预测任务。这些方法采用了各种技术,如基于神经网络的模型、基于分解的模型和基于随机游走的模型等。

KGE 算法的发展。图片来源:知识图谱嵌入
结论
总结来说,知识图谱嵌入算法已经成为表示和推理复杂结构化数据的强大工具。这些算法学习知识图谱中实体和关系的低维嵌入,从而允许高效地计算相似性和推理任务。
在这篇博客文章中,我们讨论了各种嵌入算法,包括 TransE、TransH、TransR、DistMult、ComplEx,并强调了它们的优缺点。总体而言,知识图谱嵌入算法在问答系统、推荐系统和自然语言处理等广泛应用中表现出巨大的潜力。随着领域的不断发展,我们可以期待看到更强大和有效的嵌入算法,它们能够处理越来越大和复杂的知识图谱。
在本系列的下一篇博客中,我们将介绍一些具体的推荐系统用例,其中 KGE 的使用有助于提高推荐准确性。
参考文献:
[1] Tomas Mikolov, Ilya Sutskever, Kai Chen, Greg Corrado, 和 Jeffrey Dean. 词汇和短语的分布式表征及其组合性。发表于第 26 届神经信息处理系统国际会议论文集——第 2 卷,NIPS’13,页码 3111–3119,美国纽约红钩,2013 年。Curran Associates Inc.
[2] Tomás Mikolov, Kai Chen, Greg Corrado, 和 Jeffrey Dean. 向量空间中词汇表征的高效估计。由 Yoshua Bengio 和 Yann LeCun 编辑,第 1 届国际学习表征会议,ICLR 2013,美国亚利桑那州斯科茨代尔,2013 年 5 月 2–4 日,研讨会论文集,2013 年。
[3] Bryan Perozzi, Rami Al-Rfou, 和 Steven Skiena. Deepwalk:社交表征的在线学习。发表于第 20 届 ACM SIGKDD 国际知识发现与数据挖掘会议论文集,页码 701–710,2014 年。
[4] Aditya Grover 和 Jure Leskovec. Node2vec:网络的可扩展特征学习。发表于第 22 届 ACM SIGKDD 国际知识发现与数据挖掘会议论文集,页码 855–864,美国纽约,2016 年。ACM 出版社。
[5] Nan Wang, Hongning Wang, Yiling Jia, 和 Yue Yin. 通过多任务学习解释性推荐于带有观点的文本数据。发表于第 41 届国际 ACM SIGIR 信息检索研究与发展会议,SIGIR ’18,页码 165–174,美国纽约,2018 年。计算机协会。
[6] Antoine Bordes, Nicolas Usunier, Alberto Garcia-Duran, Jason Weston, 和 Oksana Yakhnenko. 翻译嵌入用于建模多关系数据。由 C. J. C. Burges, L. Bottou, M. Welling, Z. Ghahramani, 和 K. Q. Weinberger 编辑,神经信息处理系统进展,第 26 卷,美国内华达州湖塔霍,2013 年。Curran Associates, Inc.
[7] Zhen Wang, Jianwen Zhang, Jianlin Feng, 和 Zheng Chen. 通过在超平面上转换进行知识图谱嵌入。发表于第二十八届 AAAI 人工智能会议论文集,AAAI’14,页码 1112–1119,加拿大魁北克市,2014 年。AAAI 出版社。
[8] Yankai Lin, Zhiyuan Liu, Maosong Sun, Yang Liu, 和 Xuan Zhu. 学习实体和关系嵌入以完成知识图谱。发表于第二十九届 AAAI 人工智能会议论文集,AAAI’15,页码 2181–2187,美国德克萨斯州奥斯汀,2015 年。AAAI 出版社。
[9] Maximilian Nickel, Volker Tresp, 和 Hans-Peter Kriegel. 一种用于多关系数据集体学习的三元模型。发表于第 28 届国际机器学习会议论文集,ICML’11,页码 809–816,美国威斯康星州麦迪逊,2011 年。Omnipress 出版社。
[10] Richard Socher, Danqi Chen, Christopher D Manning, 和 Andrew Ng. 使用神经张量网络进行知识库补全推理。由 C. J. C. Burges, L. Bottou, M. Welling, Z. Ghahramani, 和 K. Q. Weinberger 编辑,神经信息处理系统进展,第 26 卷,美国内华达州湖塔霍,2013 年。Curran Associates, Inc.
知识图谱转换器:构建动态推理以适应不断演变的知识
·发布在 Towards Data Science ·阅读时间 7 分钟·2023 年 10 月 28 日
--
人工智能软件用于提升本文文本的语法、流畅性和可读性。
知识图谱通过将事实表示为相互连接的实体,已成为增强 AI 系统的一种关键技术,具备整合和上下文化知识的能力。
然而,现实世界的知识不断演变,需要动态的表示方法来捕捉世界的流动和时间敏感的复杂性。
时序知识图谱(TKGs)通过引入时间维度来满足这一需求,每个关系都有一个时间戳标记其有效期。TKGs 允许不仅建模实体之间的连接,还可以建模这些关系的动态,从而为 AI 解锁新的潜力。
尽管时序知识图谱(TKGs)已引起了大量研究关注,但其在专业领域的应用仍然是一个未开发的前沿领域。特别是金融领域具有市场快速发展的特征和多层次的文本数据,这些都可能从动态知识图谱中显著受益。然而,缺乏高质量金融知识图谱的访问限制了该领域的进展。
针对这一空白,Xiaohui Victor Li(2023)介绍了一种创新的开源金融动态知识图谱(FinDKG),该图谱由一种名为知识图谱转换器(KGTransformer)的新型时序知识图谱学习模型驱动。
[## FinDKG/FinDKG_dataset at main · xiaohui-victor-li/FinDKG
数据和模型实现文献:FinDKG:结合大语言模型的全球金融动态知识图谱……
github.com [## 财务动态知识图谱
本网站提供了财务动态知识图谱(FinDKG)门户,驱动该门户的是图形 AI 模型 KGTransformer……
FinDKG 由跨越二十多年的全球财务新闻语料库构建,将财务系统的定量指标和定性驱动因素封装到一个相互关联的时间框架中。作者展示了 FinDKG 在生成可操作的洞察力方面的实用性,例如风险监测和主题投资。
KGTransformer 模型旨在处理 TKGs 的复杂性,并在基准 TKG 数据集上表现优于现有的静态知识图谱模型。
该架构利用了最新的进展,如元关系建模、图注意网络和时间点过程,以实现强劲的结果。
通过访问开源资源,如 FinDKG、KGTransformer 以及经过微调的集成上下文知识图谱生成器(ICKG)模型,这项工作旨在推动知识图谱与金融交叉学科研究。
通过利用动态知识图谱生成细致的财务洞察,这项研究突显了将结构化知识注入数据驱动的金融和经济学中的有影响力的方向。
FinDKG 展示的能力突显了知识图谱在捕捉现实世界流动复杂性方面的强大力量。
Transformer(KGT)模型在各个行业中具有广泛的潜力。在供应链管理中,KGT 可以跟踪供应商表现,预测需求,并识别随时间变化的风险。
随着知识表示和推理作为人工智能的前沿领域,这项研究标志着向构建精通动态理解的智能系统迈出重要一步。
静态图网络的局限性
大多数现有的图神经网络设计用于静态图,不考虑时间动态。例如,图卷积网络(GCNs)和图注意网络(GATs)等模型从邻近节点聚合信息,而忽视了任何时间模式。
这种静态假设严重限制了它们在 TKGs 上的推理能力。不考虑时间上下文,使用静态嵌入做出的预测会随着图谱的演变迅速过时。
此外,静态模型将所有边缘视为均匀的,缺乏建模关系随时间变化的重要性或有效性的细微差别。它们的表示也仅限于直接邻居,缺乏更广泛的时间视角。
TKGs 需要能够理解时间相关的边重要性、随着关系和实体演变调整决策,并在考虑过去和未来影响的同时进行预测的模型。
让我们看看 KGTs 如何实现这一点。
引入知识图谱变换器
KGTs 展示了一种专门用于 TKG 动态学习的新架构范式。它们以创新的方式将图神经网络和变换器结合在一起。
从本质上讲,KGTs 包含归纳偏置,使其设计特别适合于基于 TKG 的推理。这些包括:
-
明确的时间建模 以捕捉演变动态
-
多关系处理 以表示异质关系
-
连续实体跟踪 以实现时间适应性
-
图级汇总 以实现更广泛的上下文
让我们深入了解 KGTs 如何实现这些能力:
明确的时间建模
KGTs 使用独立的实体和关系 RNN(递归神经网络)来建模节点嵌入的时间演变。
随着新事件被添加到 TKG 中,RNN 会更新嵌入以反映新的状态。这使得能够保持实体或关系随时间变化的表示。
RNN 还支持有序序列建模,其中时间t的嵌入依赖于先前的时间步——模拟现实世界的时间动态。
多关系处理
根据最近的多关系图网络进展,KGTs 采用特定于关系的参数来处理各种语义连接。
例如,“Employed_By”和“Friends_With”关系具有非常不同的含义,模型通过对每种关系类型使用不同的权重来捕捉这些差异。
这种细致的处理防止了过度泛化,并提高了预测质量。
连续实体跟踪
KGTs 不是在孤立的快照中处理 TKG,而是随着新事件的添加不断更新嵌入。
这使得能够平滑地跟踪实体,而不是在每个时间步重新初始化。结果的连续性保持了时间上下文,并使模型能够随着 TKG 的演变进行适应。
图级汇总
除了邻近节点状态,KGTs 还包含一个全局图嵌入,汇总了 TKG 在某一时刻的全部状态。
这提供了至关重要的时间上下文,并通过考虑新事件对超出立即受影响实体的广泛影响来改善预测。
图嵌入是通过对所有节点进行时间注意力机制计算的,从而实现自适应和高效的汇总。
架构组件
KGTs 由多个相互连接的组件组成,这些组件赋予了上述能力:
-
输入层: 接受初始节点特征或嵌入。
-
时间嵌入: 作为专门的嵌入,通过 RNN 编码时间演变的属性。
-
结构嵌入: 通过邻居间的关注消息传递捕捉节点邻域和全局拓扑。
-
位置编码: 提供了类似于变换器架构的绝对位置的时间感知。
-
前馈层: 使用多层感知机实现更深层次的语义集成。
-
输出层: 返回适合最终任务的节点嵌入或预测。
组件以分层架构堆叠,每个块进一步精炼和丰富嵌入。跳跃连接允许结合局部和全局视角。附加细节,如归一化层、丢弃和特定激活函数,将被纳入方程中。

相对于现有方法的优势
KGTs 的独特架构属性使其相较于以往的最先进模型在时间图上的应用具有多重优势:
-
泛化: KGTs 可以通过利用学习到的归纳偏差处理之前未见过的实体、关系和事件。相比之下,许多现有模型依赖于重新训练。
-
推理: KGTs 学习的时间增强实体和图表示导致在时间图上的预测推理有所改善,超越了之前的模型。
-
效率: 像图嵌入这样的机制避免了处理冗长的历史序列,从而提高了对大型 TKGs 的训练和推理效率。
-
可解释性: 像关系特定参数和时间注意力这样的组件提供了对模型工作的洞察,提高了相较于黑箱模型的可解释性。
总的来说,KGTs 在时间知识图谱上的动态推理领域推进了最先进技术。他们强大的实证表现与架构透明度相结合,突出显示了它们作为建模复杂时间演变领域的强大而实际的解决方案的潜力。
应用领域广泛:
KGT 的引入在知识表示、推理和时间序列建模的交汇处激发了令人兴奋的机会——开辟了动态图学习的新领域。随着 TKGs 在实际应用中的不断扩展,KGTs 代表了赋予 AI 代理对其周围世界的时间理解的一个重要步骤。
本研究在使用动态知识图谱和专业学习技术推进演变中的现实系统建模方面取得了重大进展。通过引入开源金融知识图谱(FinDKG)和创新的知识图谱变换器(KGTransformer)模型,这项工作为该领域提供了实际工具和方法上的进步。
从全球金融新闻语料库创建 FinDKG 展示了构建领域特定动态知识图谱的可行性。
FinDKG 将金融系统的定性和定量方面封装在一个相互关联的时间框架中。所展示的用例,从风险监控到主题投资,突显了 FinDKG 在生成深刻见解方面的实用性。通过将 FinDKG 作为开源资源,进一步扩大了其应用潜力。
在方法论方面,KGTransformer 推动了动态知识图谱学习的最前沿。通过结合图注意力网络、元关系建模和时间点过程等架构元素,KGTransformer 在基准动态图数据集上表现出色。模型被证明优于那些不考虑时间上下文的现有静态知识图谱模型。引入关系特定参数和连续实体追踪等组件提供了更具表现力的表示,以处理不断演变的图谱。
本研究中提出的创新在知识表示、推理和时间序列建模的交汇处催生了无数可能性。
FinDKG、KGTransformer 和 ICKG 语言模型等开源资源的可用性为其他研究人员在此基础上进一步发展这些技术并扩展到新领域提供了肥沃的土壤。
一些有前景的方向包括:
-
为像医疗、教育、交通等专业领域构建动态知识图谱,这些领域可以从时间推理中获益。
-
利用最近在自监督学习和对比方法方面的进展来增强 KGTransformer 的能力,以进行图表示学习。
-
将大型语言模型的优势与结构化知识图谱结合,形成一个集成推理框架。
-
对图学习技术与传统时间序列模型在时间预测任务上的实证比较。
-
对 KGTransformer 的架构改进,如引入变换器编码器或改进时间记忆。
通过利用变换器网络和结构化知识图谱的双重优势,本研究实现了对智能系统在现实世界中操作的丰富动态理解。
随着知识表示和时间推理仍然是开放领域,此处建立的基础工作为机器学习和符号 AI 的交汇处的有影响力创新提供了跳板。

作者提供的图片
来自 Towards Data Science 编辑的备注: 尽管我们允许独立作者根据我们的 规则和指南**发布文章,但我们不对每位作者的贡献表示支持。您不应在未经专业建议的情况下依赖作者的作品。有关详细信息,请参见我们的 读者条款 。
知识图谱、硬件选择、Python 工作流程及其他十一月必读内容
·
关注 在 Towards Data Science 发布的 ·作为 Newsletter ·4 min read·Nov 30, 2023
--
我们正在进入另一个充满事件的数据和机器学习专业人士的精彩年度的最后阶段。在全球许多地方的假期季节正式开始之前,你们中的许多人正在做最后的努力学习新技能,跟上最近的研究,或者为下一步职业生涯做准备,确实是世界上许多地方真正开始了。
我们从 11 月份精选的必读文章涵盖了许多内容,并呼应了我们社区最近几周关注的话题和兴趣——从知识图谱的应用到简化求职流程。我们希望你在为即将到来的新年制定计划时,能探索这些优秀的文章。祝你阅读愉快!
热门帖子
如果你错过了,这里是我们过去一个月最受欢迎和分享的帖子。
-
如何将任何文本转换为概念图了解如何使用 Mistral 7B 模型将任何文本语料库转化为知识图谱:Rahul Nayak的这本详细说明(且极为成功)的指南涵盖了整个过程。
-
Apple M2 Max GPU 与 Nvidia V100、P100 和 T4 的比较谁不喜欢一个扎实的硬件基准测试帖子?Fabrice Daniel花时间比较了 Apple Silicon M2 Max GPU 在训练 MLP、CNN 和 LSTM 模型时与 Nvidia V100、P100 和 T4 的性能。
-
我如何在 6 个月内获得数据分析师职位从你需要掌握的基本技能到利用生成 AI 的创新方法,Natassha Selvaraj的最新贡献帮助求职者在日益竞争激烈的就业市场中变得更高效。
-
用实际案例和 Python 代码解释隐马尔可夫模型如果你准备好卷起袖子,动手编写一些代码,Carolina Bento的这本通俗易懂的隐马尔可夫模型指南绝对值得一看。

-
用于可视化网络图的新最佳 Python 包在他首次 TDS 文章中,Benjamin Lee向我们介绍了 gravis 的内部工作原理,这是一款功能强大的开源网络图可视化包。
-
从线性代数到深度学习的 7 本书(2023 年冬季更新)不确定如何度过接下来几周的闲暇时间?Andreas Stöffelbauer整理的书单推荐是一个很好的资源,涵盖了统计学、神经网络等广泛话题。
-
检索增强生成(RAG):从理论到 LangChain 实现检索增强生成在机器学习社区中继续引起轰动;如果你刚刚开始关注,Leonie Monigatti的入门指南是一个很好的起点:它涵盖了这一方法的基本原理,并提供了详细的实际实现。
值得关注的项目和话题引发者
如果你在寻找灵感或希望了解最新的讨论动态,这些推荐适合你。
-
人类劳动如何促进机器学习人机协作的概念在Stephanie Kirmer最近的文章中获得了新的解读,重点关注“我们在机器学习中取得激动人心的进展时,依赖多少手动的人力工作。”
-
在你的 WhatsApp 聊天中构建语言模型我们中的一些人可能把群聊视为一个有趣的分享表情包和动图的空间。Bernhard Pfann, CFA将其作为基于 GPT 的语言模型的基础。
-
迷失在 DALL-E 3 的翻译中输入语言如何影响生成式 AI 文本到图像工具的输出,以及结果可以告诉我们关于模型内置偏见的什么?Yennie Jun的最新帖子详细探讨了这一关键话题。
-
做这 5 件简单的事,让你的数据科学家简历从人群中脱颖而出从关注可测量的影响到具体的格式建议,Madison Hunter的成功简历构建指南是任何考虑在不久的将来换岗者的重要读物。
-
我的生活统计数据:我追踪了我的习惯一年,这就是我学到的追踪自己每天的习惯一年需要大量的 dedication,这可能是为什么Pau Blasco i Roca’s 首篇文章引起了如此多读者的共鸣;即使你不打算开始类似的旅程,这也是一个关于收集和分析数据的 compelling 叙述。
我们最新一届的新作者
每个月,我们都很高兴看到一批新的作者加入 TDS,他们每个人都与我们的社区分享了自己独特的声音、知识和经验。如果你在寻找新的作家进行探索和关注,只需浏览我们最新加入的作者作品,包括ming gao、Armand Sauzay、Mantek Singh、Zachary Raicik、Angela K.、Flavien Berwick、Corné de Ruijt、Bhaskara Govinal Badiger、Ty Stephens、Nabil Alouani、Tim Rose、Shubham Agarwal、Mert Atli、Tom Gotsman、Vincent Vandenbussche、Onur Yuce Gun, PhD、Ahmed Fessi、Robert Constable、David R. Winer、Marcin Stasko、Luis Medina、Hugo Lu、Stijn Goossens、Samuel Chaineau、Jimmy Weaver、Summer He、Mathieu Laversin、Maksym Petyak、Sanil Khurana、Carlos J. Uribe、Chris Bruehl、Gurjinder Kaur、Sergei Savvov、Olivier Ruas、Andrew Skabar, PhD、Kaustubh Bhavsar、Pau Blasco i Roca、Victoria Walker、Fraser Brown、Victor Murcia、Juan Jose Munoz、Aveek Goswami、Matteo Ciprian、Benjamin Lee、Dima Timofeev、Petru van der Walt Félix、Frank Wittkampf、Paul Levchuk、Evgeniya Sukhodolskaya、Rodrigo Silva、Alex Shao、Jeremy Arancio、Liz Li、Michael Allen、Noah Haglund和Amy Ma等。
感谢你对我们作者工作的支持!如果你喜欢你在 TDS 上阅读的文章,可以考虑成为 Medium 的朋友会员:这是一个全新的会员等级,为你最喜欢的作者提供更丰厚的奖励,以奖励他们的优质写作。
直到下一个 Variable,
TDS 编辑们
知识检索占据了中心舞台

图片来源:Adobe Stock。
GenAI 架构从 RAG 转向解释性检索为中心的生成(RCG)模型
·
跟随 发表在 Towards Data Science ·13 分钟阅读·2023 年 11 月 16 日
--
要使 GenAI 从消费者向业务部署过渡,解决方案应主要围绕使用检索为中心的生成(RCG)所需的模型外部信息构建。
随着生成型人工智能(GenAI)开始在各行业部署,公司需要提供效率、准确性、安全性和可追溯性的模型。ChatGPT 等模型的原始架构显示出无法满足这些关键需求的主要差距。在早期的 GenAI 模型中,检索被用作事后补救,以解决依赖参数化内存中记忆信息的模型的缺点。当前的模型通过在解决方案平台上增强检索增强生成(RAG)前端已在此问题上取得显著进展,以允许提取模型外的信息。也许现在是进一步重新思考生成型人工智能架构的时候了,并且从 RAG 系统转向以检索为核心访问信息的检索中心生成(RCG)模型。
检索中心生成模型可定义为一种生成型人工智能解决方案,专为系统设计,其中绝大多数数据存储在模型参数化内存之外,并且在预训练或微调中大多数情况下并未见过。使用 RCG 时,GenAI 模型的主要角色是解释从公司索引数据语料库或其他策划内容中检索到的丰富信息。模型不是记忆数据,而是专注于对目标结构、关系和功能的精细调整。生成输出的数据质量预期达到 100%的准确性和及时性。适当解释和使用大量未在预训练中看到的数据要求模型更多地抽象化,并使用模式作为识别信息中复杂模式和关系的关键认知能力。这种检索要求与模式自动学习相结合,将推动大型语言模型的预训练和微调进一步演进。

图 1. 检索中心生成(RCG)与检索增强生成(RAG)的优势和挑战。图片来源:Intel Labs.
大幅减少在生成式人工智能模型中使用记忆中的数据,而是依赖可验证的索引源,将改善来源,并在提升准确性和性能方面发挥重要作用。到目前为止,生成式人工智能架构中的普遍假设是模型中的数据越多越好。基于这一目前主流的结构,预计大多数标记和概念已被吸收和交叉映射,以便模型可以从其参数记忆中生成更好的答案。然而,在常见的商业场景中,生成输出所用的大部分数据预计来自检索的输入。我们现在观察到,模型中有更多数据而依赖检索知识会导致信息冲突,或包括无法追溯或验证其来源的数据。正如我在上一篇博客中概述的,适者生存,设计为使用 RCG 的小型灵活目标模型不需要在参数记忆中存储大量数据。
在数据主要来自检索的商业环境中,目标系统需要在解释未见相关信息以满足公司要求方面表现出色。此外,大型向量数据库的普及和上下文窗口大小的增加(例如,OpenAI 最近将GPT-4 Turbo 的上下文窗口从 32K 增加到 128K)正在推动模型向推理和解释未见复杂数据的方向发展。现在,模型需要智能化地将广泛的数据转化为有效的知识,通过结合复杂的检索和微调来实现。随着模型变得以检索为中心,创建和利用模式的认知能力将成为焦点。
消费者与商业使用的生成式人工智能
在经历了十年的人工智能模型规模和复杂度的快速增长后,2023 年标志着一个关注效率和生成式人工智能有针对性应用的转变。从消费者焦点转向业务使用是推动这种变化的关键因素之一,涉及三个层面:数据质量、数据来源和目标用途。
● 数据质量: 在为公司生成内容和分析时,95%的准确性是不够的。企业需要接近或达到完全准确。为了确保输出质量,需要对特定任务进行高性能的微调,并管理所用数据的质量。此外,数据需要可追溯和可验证。来源很重要,而检索对于确定内容来源至关重要。
-
数据来源: 期望在商业应用程序中的绝大多数数据都是从可信的外部来源以及专有的业务/企业数据中策划出来的,包括产品信息、资源、客户、供应链、内部操作等等。检索对于访问未在模型中预先训练的最新和最广泛的专有数据至关重要。无论模型大小如何,当使用来自其内部存储器的数据与从业务来源提取的可验证、可追溯数据时,数据的来源都可能存在问题。如果数据有冲突,可能会使模型混淆。
-
目标用途: 公司模型的构造和功能往往专门用于一组用途和数据类型。当 GenAI 功能部署在特定的工作流程或业务应用程序中时,不太可能需要全能功能。由于数据主要来自检索,因此目标系统需要在解释公司特定方式所需的相关信息方面表现出色,这些信息对模型来说是看不见的。
例如,如果一个金融或医疗公司追求 GenAI 模型来改善其服务,它将专注于一系列用于其预期用途所需的函数族。他们可以选择从头开始预训练一个模型,并尝试包含所有他们的专有信息。然而,这样的努力可能会非常昂贵,需要深厚的专业知识,并且很可能随着技术的发展和公司数据的不断变化而迅速落后。此外,它无论如何都需要依赖检索来访问最新的具体信息。更有效的路径是采用现有的预训练基础模型(例如Meta 的 Llama 2)并通过微调和索引来定制它以用于检索。微调只使用了信息和任务的一小部分来改进模型的行为,但广泛的业务专有信息本身可以被索引,并根据需要进行检索。随着基础模型更新到最新的 GenAI 技术,刷新目标模型应该是一个相对简单的过程,只需重复微调流程。
转向以检索为中心的生成:围绕索引信息提取进行架构设计
Meta AI 及其大学合作伙伴在 2021 年引入了检索增强生成(RAG),以解决 LLMs 中的来源和更新世界知识的问题。研究人员使用 RAG 作为一种通用方法,将非参数记忆添加到经过预训练的参数记忆生成模型中。非参数记忆使用由预训练检索器访问的 Wikipedia 密集向量索引。在一个数据记忆较少的紧凑模型中,非常重视由向量数据库引用的索引数据的广度和质量,因为模型不能依赖于记忆信息来满足业务需求。RAG 和 RCG 都可以使用相同的检索器方法,通过在推理时实时提取相关知识(见图 2)。它们在 GenAI 系统放置信息的方式以及对以前未见数据的解释期望上存在差异。在 RAG 中,模型本身是主要的信息来源,检索到的数据作为辅助。而在 RCG 中,大部分数据存在于模型的参数记忆之外,使得解释未见数据成为模型的主要角色。
需要注意的是,目前许多 RAG 解决方案依赖于诸如LangChain或Haystack等流程,将前端检索与独立的向量存储连接到一个未经过检索预训练的 GenAI 模型。这些解决方案提供了一个用于索引数据源、选择模型和模型行为训练的环境。其他方法,如Google Research 的 REALM,则尝试通过集成检索进行端到端的预训练。目前,OpenAI 正在优化其检索 GenAI 路径,而不是让生态系统为 ChatGPT 创建流程。该公司最近发布了Assistants API,该 API 可以检索模型外的专有领域数据、产品信息或用户文档。

图 2. RCG 和 RAG 在推理过程中检索公共和私人数据,但它们在如何放置和解释未见数据方面存在差异。图像来源:Intel Labs。
在其他例子中,像 Intel Labs 的 fastRAG 这样的快速检索模型使用预训练的小型基础模型从知识库中提取请求的信息,而无需额外训练,提供了更可持续的解决方案。fastRAG 作为开源 Haystack GenAI 框架的扩展,使用 检索模型 通过从外部知识库中检索当前文档来生成对话回答。此外,Meta 的一组研究人员最近发布了一篇论文,介绍了 检索增强双重指令微调(RA-DIT),一种“轻量级微调方法,通过为任何大型语言模型增加检索能力提供第三种选项”。
从 RAG 到 RCG 模型的转变挑战了信息在训练中的作用。在 RAG 中,信息既是信息的存储库,又是对提示的解释者,而在 RCG 中,模型的功能主要转变为对检索到的(通常是商业策划的)信息进行上下文解释。这可能需要对预训练和微调方法进行修改,因为目前用于训练语言模型的目标可能不适用于这种学习方式。RCG 对模型要求不同的能力,如更长的上下文、数据的可解释性、数据的策划以及其他新挑战。
在学术界或工业界,RCG 系统的实例仍然相当少。在一个例子中,Kioxia Corporation 的研究人员创建了开源的 SimplyRetrieve,该系统使用 RCG 架构通过分离上下文解释和知识记忆来提升 LLM 的性能。在 Wizard-Vicuna-13B 模型上实现时,研究人员发现 RCG 能准确回答关于某组织工厂位置的查询。相比之下,RAG 尝试将检索到的知识库与 Wizard-Vicuna 对该组织的知识进行整合,这导致了部分错误信息或幻觉。这只是一个例子——RAG 和检索关闭生成(ROG)在其他情况下可能会提供正确的回答。

图 3. 检索中心生成(RCG)、检索增强生成(RAG)和检索关闭生成(ROG)的比较。正确的回答以蓝色显示,而幻觉以红色显示。图片来源: Kioxia Corporation。
从 RAG 过渡到 RCG 可以类比于在编程中使用常量(RAG)和变量(RCG)的区别。当一个 AI 模型回答关于一辆可转换的福特野马的问题时,大型模型将熟悉许多与汽车相关的细节,例如引入年份和发动机规格。大型模型还可以添加一些最近检索到的更新,但它主要会根据特定的内部已知术语或常量来回答。然而,当一个模型被部署在准备其下一款车型发布的电动车公司时,该模型需要进行推理和复杂解释,因为大部分数据都是未见过的。模型需要理解如何使用这类信息,例如变量的值,来理解数据。
架构:推理过程中的泛化与抽象能力
在商业环境中检索的大部分信息(商业组织与人员、产品与服务、内部流程和资产)在预训练期间相应的 GenAI 模型可能没有看到,并且在精调期间可能仅仅是抽样。这意味着变压器架构不会将“已知”的单词或术语(即模型先前摄取的内容)作为生成输出的一部分。相反,该架构需要在适当的上下文解释中放置未见过的术语。这在某种程度上类似于上下文学习如何使 LLM 中的一些新推理能力成为可能,而无需额外的训练。
随着这一变化,进一步提升泛化和抽象能力变得不可或缺。需要增强的关键能力是,当在推理过程中遇到未见过的术语或标记时,使用学到的模式进行解释和应用的能力。认知科学中的模式“描述了一种组织信息类别及其关系的思维或行为模式。” 心理模式“可以被描述为一种心理结构,代表世界的某个方面。”类似地,在 GenAI 模型中,模式是解释未见过的标记、术语和数据所需的基本抽象机制。如今的模型已经展示了对新兴模式构建和解释的相当理解,否则它们无法在复杂的未见过的提示上下文数据上执行生成任务。随着模型检索到之前未见过的信息,它需要识别与数据最匹配的模式。这使得模型能够通过与模式相关的知识来解释未见过的数据,而不仅仅是利用上下文中包含的显性信息。值得注意的是,在这次讨论中,我指的是学习和抽象模式作为新兴能力的神经网络模型,而不是依赖于知识图谱中显式模式并在推理时参考的解决方案类别。
从模型能力(认知能力、功能技能和信息获取)这三种类型的视角来看,抽象和模式使用完全属于认知能力类别。特别是,如果小型模型能在构建和使用模式来解释数据的技能上有所提高,它们应能与更大模型的表现相当(在获得适当的数据的前提下)。预计与模式相关的基于课程的预训练将提升模型的认知能力。这包括模型构建多种模式的能力、根据生成过程识别合适的模式,以及插入/利用模式构建的信息以创造最佳结果。
例如,研究人员展示了当前的 LLM 如何使用假设到理论(HtT)框架来学习基本的模式。研究人员发现,LLM 可以用来生成规则,然后按照这些规则解决数值和关系推理问题。GPT-4 发现的规则可以视为理解家庭关系的详细模式(见图 4)。未来的家庭关系模式可能会更加简洁和强大。

图 4. 使用 CLUTRR 数据集进行关系推理,Hypotheses-to-Theories 框架促使 GPT-4 生成类似模式的规则供 LLM 在回答测试问题时遵循。图像来源: Zhu et al.
将此应用于一个简单的商业案例,GenAI 模型可以使用一种模式来理解公司供应链的结构。例如,知道“B 是 A 的供应商”和“C 是 B 的供应商”意味着在分析文档以评估潜在的供应链风险时,“C 是 A 的二级供应商”是重要的。
在一个更复杂的案例中,比如教会 GenAI 模型记录患者就医情况的变化和细微差别,在预训练或微调期间建立的紧急模式将为理解检索的信息提供结构,以生成报告或支持医疗团队的问题和答案。该模式可以在模型中通过更广泛的患者护理案例训练/微调中出现,包括预约以及测试和程序等其他复杂元素。当 GenAI 模型接触到所有示例时,它应该创建解读在推理期间提供的部分患者数据的专业知识。模型对过程、关系和变化的理解将使其能够正确解释以前未见过的患者案例,而无需在提示中提供过程信息。相反,它不应尝试记忆在预训练或微调过程中接触到的特定患者信息。这种记忆是不利的,因为患者信息不断变化。模型需要学习构造,而不是特定的案例。这种设置还可以最小化潜在的隐私问题。
总结
随着 GenAI 在各行各业的大规模部署,企业对高质量专有信息的依赖以及对可追溯性和可验证性的要求显著增加。这些关键要求,加上对成本效率和集中的应用的压力,推动了对小型、针对性的 GenAI 模型的需求,这些模型旨在解释局部数据,这些数据在预训练过程中大多未曾见过。以检索为中心的系统需要提升一些深度学习 GenAI 模型可以掌握的认知能力,例如构建和识别合适的模式。通过使用 RCG 并指导预训练和微调过程,以创建反映认知结构的泛化和抽象,GenAI 可以在理解模式和从检索中理解未见数据的能力上取得飞跃。精炼的抽象(例如基于模式的推理)和高效的认知能力似乎是下一个前沿。
了解更多:GenAI 系列
生存之道:紧凑生成 AI 模型是规模化成本效益 AI 的未来
参考资料
-
Gillis, A. S. (2023 年 10 月 5 日)。 检索增强生成。 Enterprise AI。
www.techtarget.com/searchenterpriseai/definition/retrieval-augmented-generation -
Singer, G. (2023 年 7 月 28 日)。 生存之道:紧凑生成 AI 模型是规模化成本效益 AI 的未来。 Medium。
towardsdatascience.com/survival-of-the-fittest-compact-generative-ai-models-are-the-future-for-cost-effective-ai-at-scale-6bbdc138f618 -
在 DevDay 宣布的新模型和开发者产品。 (n.d.)。
openai.com/blog/new-models-and-developer-products-announced-at-devday -
Meta AI。 (n.d.)。 介绍 Llama 2。
ai.meta.com/llama/ -
Lewis, P. (2020 年 5 月 22 日)。 检索增强生成用于知识密集型自然语言处理任务。 arXiv.org。
arxiv.org/abs/2005.11401 -
LangChain。 (n.d.)。
www.langchain.com -
Haystack。 (n.d.)。 Haystack。
haystack.deepset.ai/ -
Guu, K. (2020 年 2 月 10 日)。 REALM:检索增强语言模型预训练。 arXiv.org。
arxiv.org/abs/2002.08909 -
Intel Labs。 (n.d.)。 GitHub — Intel Labs/FastRAG:高效检索增强和生成框架。 GitHub。
github.com/IntelLabs/fastRAG -
Fleischer, D. (2023 年 8 月 20 日)。 在 fastRAG 中使用稠密检索器的开放域问答 — Daniel Fleischer — Medium。
medium.com/@daniel.fleischer/open-domain-q-a-using-dense-retrievers-in-fastrag-65f60e7e9d1e -
Lin, X. V. (2023 年 10 月 2 日)。 RA-DIT:检索增强双指令调整。 arXiv.org。
arxiv.org/abs/2310.01352 -
Ng, Y. (2023 年 8 月 8 日)。 SimplyRetrieve:私密轻量级检索中心生成 AI 工具。 arXiv.org。
arxiv.org/abs/2308.03983 -
Wikipedia contributors。 (2023 年 9 月 27 日)。 模式(心理学)。 Wikipedia。
en.wikipedia.org/wiki/Schema_(psychology) -
Wikipedia contributors。 (2023 年 8 月 31 日)。 心理模型。 Wikipedia。
en.wikipedia.org/wiki/Mental_schema -
Zhu, Z. (2023 年 10 月 10 日). 大型语言模型可以学习规则. arXiv.org.
arxiv.org/abs/2310.07064
KServe:基于 Kubernetes 的高可扩展机器学习部署
Kubernetes 模型推理变得简单。
·发表于 Towards Data Science ·阅读时间 10 分钟·2023 年 5 月 29 日
--

图片来源于 KServe
随着 chatGPT 的发布,避免利用机器学习的技术变得越来越困难。从你消息应用中的文本预测到智能门铃上的面部识别,机器学习(ML)几乎存在于我们今天使用的每一项技术中。
机器学习技术如何交付给消费者是组织在开发过程中必须面对的许多挑战之一。ML 产品的部署策略对产品的最终用户有着显著影响。这可能意味着你 iPhone 上的 Siri 和你网页浏览器中的 chatGPT 之间的区别。
在 ChatGPT 那光鲜的用户界面和过于自信的聊天对话背后,隐藏着部署大型语言 ML 模型所需的复杂机制。ChatGPT 建立在一个高度可扩展的框架上,旨在在其指数级采用过程中提供和支持该模型。实际上,实际的 ML 模型仅占整个项目的一小部分。这些项目通常是跨学科的,要求在数据工程、数据科学和软件开发方面的专业知识。因此,简化模型部署过程的框架在将模型推向生产中变得越来越重要,帮助组织节省时间和金钱。
如果没有合适的操作框架来支持和管理 ML 模型,组织在尝试扩大生产中的 ML 模型数量时通常会遇到瓶颈。
虽然在竞争激烈的 MLOps 工具市场中没有出现明显的赢家,但 KServe 正成为帮助组织满足 ML 模型可扩展性要求的越来越受欢迎的工具。
注意:我与 KServe 没有任何关联,也没有获得赞助来撰写这篇文章。
什么是 KServe?
KServe 是一个高度可扩展的机器学习部署工具包,适用于 Kubernetes。它是一个建立在 Kubernetes 之上的编排工具,利用了另外两个开源项目,Knative-Serving 和 Istio;稍后会详细介绍。

图片来源于 KServe
KServe 通过将部署统一到一个资源定义中,显著简化了将 ML 模型部署到 Kubernetes 集群中的过程。它使得机器学习部署成为任何 ML 项目的一部分,易于学习,并最终降低了进入门槛。因此,使用 KServe 部署的模型比使用传统的 Kubernetes 部署(需要 Flask 或 FastAPI 服务)的模型更易于维护。
使用 KServe,无需在通过 HTTPS 将模型暴露到互联网之前将其封装在 FastAPI 或 Flask 应用程序中。KServe 内置了本质上复制这一过程的功能,但无需维护 API 端点、配置 Pod 副本或在 Kubernetes 中配置内部路由网络。你只需将 KServe 指向你的模型,它将处理其余的部分。
除了简化部署过程之外,KServe 还提供了许多功能,包括金丝雀部署、推理自动扩展和请求批处理。这些功能将不在本讨论范围之内。然而,本指南希望能够为进一步探索打下基础。
首先,让我们讨论 KServe 所依赖的两个关键技术,Istio 和 Knative。
Istio
KServe 所带来的许多功能在没有 Istio 的情况下将很难实现。Istio 是一个服务网格,扩展了部署在 Kubernetes 中的应用程序。它是一个专门的基础设施层,增加了可观测性、流量管理和安全性等功能。对于熟悉 Kubernetes 的人来说,Istio 替代了通常在 Kubernetes 集群中找到的标准 Ingress 定义。
随着基于 Kubernetes 的系统规模扩大,管理流量和维护可观测性的复杂性也在增加。Istio 的一个最佳功能是能够集中控制服务级别的通信。这使得开发人员对服务之间的通信有更大的控制和透明度。
使用 Istio,开发人员不需要设计能够处理流量认证或授权的应用程序。最终,Istio 有助于减少部署应用程序的复杂性,使开发人员能够集中精力关注应用程序的关键组件。
通过利用 Istio 的网络功能,KServe 可以提供包括金丝雀部署、推理图和自定义变换器等功能。
KNative
另一方面,Knative 是一个开源企业级解决方案,用于构建无服务器和事件驱动的应用程序。Knative 基于 Istio 构建,提供类似于 AWS Lambdas 和 Azure Functions 的无服务器代码执行能力。Knative 是一个与平台无关的解决方案,用于在 Kubernetes 中运行无服务器部署。
Knative 的一个最佳功能是缩放到零的特性。这是 KServe 能够扩展或缩小 ML 模型部署的关键组件,并且能够最大化资源利用率和节省成本。
我应该使用 KServe 吗?
KServe 和许多其他工具一样,并不是一个适合所有组织需求的一刀切解决方案。由于需要一定的 Kubernetes 经验,其入门成本较高。如果你刚刚开始使用 Kubernetes,有许多在线资源,我强烈建议查看 DevOps 频道的资源。不过,即使没有深入了解 Kubernetes,也可以学习使用 KServe。
KServe 在已经使用 Kubernetes 的组织中将是理想选择,这些组织已经具备了与 Kubernetes 相关的知识。它也适合那些希望摆脱或补充像 SageMaker 或 Azure Machine Learning 等托管服务的组织,以便对模型部署过程拥有更大的控制权。增加的所有权可以显著降低成本,并提高配置能力,以满足项目的具体要求。
尽管如此,正确的云基础设施决策将依赖于具体情况,因为基础设施需求在不同公司之间会有所不同。
先决条件
本指南将引导你完成设置 KServe 所需的步骤。你将了解如何安装 KServe 并服务你的第一个模型。
在继续之前,需要满足几个先决条件。你将需要以下内容:
Kubernetes 集群
对于本教程,我推荐使用 Kind 来实验 Kubernetes 集群。它是一个运行本地 Kubernetes 集群的工具,无需启动云资源。此外,如果你在多个集群之间工作,我强烈推荐使用 Kubectx 作为一个轻松切换 Kubernetes 上下文的工具。
然而,在运行生产工作负载时,你将需要访问一个完全功能的 Kubernetes 集群来配置 DNS 和 HTTPS。
在 Kind 中部署 Kubernetes 集群,使用:
kind create cluster --name kserve-demo
切换到正确的 Kubernetes 上下文,使用:
kubectx kind-kserve-demo
安装
以下步骤将安装 Istio v1.16、Knative Serving v1.7.2 和 KServe v0.10.0。这些版本最适合本教程,因为 Knative v1.8 及以后版本将需要 DNS 配置进行入口,这增加了当前教程范围之外的复杂性。
- Istio 安装。
# Install istio
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.16.0 TARGET_ARCH=x86_64 sh -
istioctl install --set profile=default -y
2. 安装 KNative Serving。
# Install the Knative Serving component
export KNATIVE_VERSION="v1.7.2"
kubectl apply -f https://github.com/knative/serving/releases/download/knative-$KNATIVE_VERSION/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/knative-$KNATIVE_VERSION/serving-core.yaml
# Install istio-controller for knative
kubectl apply -f https://github.com/knative/net-istio/releases/download/knative-v1.7.0/net-istio.yaml
3. 安装证书管理器。证书管理器用于管理 HTTPs 流量的有效证书。
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.11.0 --set installCRDs=true
4. 为模型创建一个命名空间。
kubectl create namespace kserve
5. 克隆KServe仓库。
git clone git@github.com:kserve/kserve.git
6. 将 KServe 自定义资源定义和 KServe 运行时安装到集群中的模型命名空间。
cd kserve
helm install kserve-crd charts/kserve-crd -n kserve
helm install kserve-resources charts/kserve-resources -n kserve
太好了!我们现在已经在集群上安装了 KServe。让我们开始部署吧!
第一个推理服务
为确保部署顺利,让我们部署一个演示推理服务。部署的源代码可以在这里找到。
kubectl apply -n kserve -f - <<EOF
apiVersion: "serving.kserve.io/v1beta1"
kind: "InferenceService"
metadata:
name: "sklearn-iris"
spec:
predictor:
model:
modelFormat:
name: sklearn
storageUri: "gs://kfserving-examples/models/sklearn/1.0/model"
EOF
上面的 yaml 资源定义部署了一个测试推理服务,该服务使用 SciKit-Learn 库训练的公开可用模型。KServe 支持许多不同的机器学习库。这些包括 MLFlow、PyTorch 或 XGBoost 模型;每次发布时都会添加更多。如果这些现成的库都不符合你的要求,KServe 还支持自定义预测器。
可以通过获取命名空间中的可用 pod 来监控当前部署的状态。
kubectl get pods -n kserve

图片由作者提供
如果在部署过程中遇到问题,请使用以下命令进行调试:
kubectl describe pod <name_of_pod> -n kserve
我们还可以通过以下命令检查推理服务部署的状态:
kubectl get isvc -A

图片由作者提供
如果推理服务标记为 true,我们就可以进行第一次预测了。
执行预测
为了进行预测,我们需要确定我们的 Kubernetes 集群是否运行在支持外部负载均衡器的环境中。
kubectl get svc istio-ingressgateway -n istio-system
Kind 集群
使用 Kind 部署的集群不支持外部负载均衡器,因此你将会有一个类似下面的入口网关。

Kind 外部负载均衡器(图片由作者提供)
在这种情况下,我们需要将 istio-ingressgateway 的端口转发,这样可以通过localhost访问它。
将 istio 入口网关服务的端口转发到localhost上的端口8080,使用以下命令:
kubectl port-forward -n istio-system service/istio-ingressgateway 8080:80
然后用以下命令设置入口主机和端口:
export INGRESS_HOST=localhost
export INGRESS_PORT=8080
Kubernetes 集群
如果外部 IP 有效且不显示<pending>,我们就能够通过该 IP 地址在互联网上发送推理请求。

入口网关 IP 地址(图片由作者提供)
用以下命令设置入口主机和端口:
export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].port}')
执行推理
准备一个用于推断请求的输入 json 文件。
cat <<EOF > "./iris-input.json"
{
"instances": [
[6.8, 2.8, 4.8, 1.4],
[6.0, 3.4, 4.5, 1.6]
]
}
EOF
然后使用 curl 执行推断:
SERVICE_HOSTNAME=$(kubectl get inferenceservice sklearn-iris -n kserve -o jsonpath='{.status.url}' | cut -d "/" -f 3)
curl -v -H "Host: ${SERVICE_HOSTNAME}" -H "Content-Type: application/json" "http://${INGRESS_HOST}:${INGRESS_PORT}/v1/models/sklearn-iris:predict" -d @./iris-input.json
请求将通过 istio-ingress 网关发送到 KServe 部署。如果一切正常,我们将从推断服务收到一个 json 回复,其中包含每个实例的预测值 [1,1]。

缩放到零
通过利用 Knative 的特性,KServe 支持缩放到零的能力。此功能通过将未使用的 Pods 缩放为零来有效管理集群中的有限资源。缩放到零的能力允许创建一个响应请求的反应系统,而不是一个始终运行的系统。这将使得在集群中部署更多的模型成为可能,相较于传统的部署配置。
然而,请注意,对于已缩放下来的 Pods,会有冷启动的惩罚。这将根据镜像/模型的大小和可用的集群资源而有所不同。如果集群需要扩展额外的节点,冷启动可能需要 5 分钟;如果模型已经缓存到节点上,冷启动可能需要 10 秒。
让我们修改现有的 scikit-learn 推断服务,通过定义 minReplicas: 0 来启用缩放到零。
kubectl apply -n kserve -f - <<EOF
apiVersion: "serving.kserve.io/v1beta1"
kind: "InferenceService"
metadata:
name: "sklearn-iris"
spec:
predictor:
minReplicas: 0
model:
modelFormat:
name: sklearn
storageUri: "gs://kfserving-examples/models/sklearn/1.0/model"
EOF
通过将 minReplicas 设置为 0,这将指示 Knative 在没有 HTTP 流量时将推断服务缩放为零。你会注意到,在 30 秒后,Sklearn-Iris 模型的 Pods 将会被缩放到零。
kubectl get pods -n kserve

Sklearn-Iris 预测器缩放到零
要重新初始化推断服务,请向相同的端点发送预测请求。
SERVICE_HOSTNAME=$(kubectl get inferenceservice sklearn-iris -n kserve -o jsonpath='{.status.url}' | cut -d "/" -f 3)
curl -v -H "Host: ${SERVICE_HOSTNAME}" "http://${INGRESS_HOST}:${INGRESS_PORT}/v1/models/sklearn-iris:predict" -d @./iris-input.json

这将触发 Pods 的冷启动初始化并返回一个预测结果。
结论
KServe 简化了机器学习部署的过程,并缩短了生产的路径。当与 Knative 和 Istio 结合使用时,KServe 具有高度的可定制性,并带来许多与托管云解决方案提供的功能相媲美的特性。
当然,将模型部署过程迁移到内部也有其固有的复杂性。然而,平台所有权的增加将提供更大的灵活性,以满足项目特定要求。凭借正确的 Kubernetes 专业知识,KServe 可以成为一个强大的工具,使组织能够轻松地在任何云提供商上扩展其机器学习部署,以满足不断增长的需求。
L1 与 L2 正则化在机器学习中的比较:区别、优势及如何在 Python 中应用
深入探讨 L1 和 L2 正则化技术,解释它们为何对防止模型过拟合至关重要
·发表在 Towards Data Science ·阅读时间 8 分钟·2023 年 2 月 23 日
--

图片由作者提供。
机器学习是一个在技术和工业领域经历巨大发展的学科。
得益于其算法和建模技术,我们可以构建能够从过去数据中学习、泛化并对新数据进行预测的模型。
然而,在某些情况下,模型可能会过拟合训练数据,从而失去泛化能力。这种现象称为过拟合。
对分析师来说,理解过拟合是什么以及为什么它是创建预测模型时的主要障碍之一是相当重要的。
过拟合的一个大致概念是这样的
当一个模型过于复杂或对训练数据拟合得过好时,它可能对这些特定数据非常准确,但对从未见过的数据的泛化能力差。这意味着模型在现实生活中应用于新数据时将无效。
想了解更多关于过拟合的信息?阅读标题为 克服机器学习中的最大障碍:过拟合 的文章,该文章发表在 TDS 上。
正则化技术可以用于防止过拟合。
正则化一词涵盖了一组简化预测模型的技术。在这篇文章中,我们将重点介绍两种正则化技术,L1 和 L2,解释它们的区别,并展示如何在 Python 中应用它们。
什么是正则化,为什么它很重要?
简单来说,正则化模型意味着在训练阶段改变其学习行为。
正则化通过 对模型复杂度施加惩罚 来帮助防止过拟合——如果模型过于复杂,在训练过程中将受到惩罚,这有助于保持模型复杂性和对未见数据的泛化能力之间的良好平衡。
要添加 L1 或 L2 正则化,我们将修改模型的损失函数。这是学习算法在训练阶段试图优化的函数。
正则化通过分配一个基于模型复杂度增加的惩罚来发生。
以线性回归为例,MSE(均方误差——有关回归模型评估指标的更多信息请点击这里)是典型的损失函数,可以表示为

其中算法的目标是最小化预测值 f(x) 与观察值 y 之间的差异。
在方程中,f(x) 是回归线,这将等于

因此,算法将需要通过最小化 MSE 从训练集找出参数 w 和 b 的值。
如果一些参数 w 接近或等于零,则模型被认为较少复杂。
L1 和 L2 正则化
现在让我们看看 L1 和 L2 正则化之间的区别。
L1 正则化
L1 正则化,也称为“套索”,对模型权重的绝对值总和施加惩罚。
这意味着对模型贡献不大的权重将被归零,这可能导致自动特征选择(因为对应于不重要特征的权重实际上会被归零)。
这使得 L1 在特征选择问题和稀疏模型中尤其有用。
以上述 MSE 公式为例,L1 正则化看起来如下

其中 C 是一个 控制正则化强度的模型超参数。C 的值越高,我们的权重越会趋向于零。
行话中,这被称为 稀疏模型,其中大多数参数的值为零。
这里的风险是 非常高的 C 值会导致模型欠拟合,这与过拟合相反——即它不会捕捉到我们数据中的模式。
L2 正则化
另一方面,L2 正则化,也称为 岭回归正则化,将权重的平方添加到正则化项中。
这意味着较大的权重会被减少但不会被归零,这导致与 L1 正则化相比,模型的变量较少但权重更分散。
L2 正则化在你有很多高度相关的变量时尤其有用,因为它倾向于“分散”权重到所有变量中,而不是只关注其中的一些。
如前所述,让我们看看初始方程如何变化以集成 L2。

L2 正则化可以提高模型的稳定性,尤其在训练数据噪声或不完整时,通过减少异常值或噪声对变量的影响。
如何在 Sklearn 和 Python 中应用正则化
在这个例子中,我们将看到如何将正则化应用于逻辑回归模型以解决分类问题。
我们将看到性能如何随 C 值的变化而变化,并比较模型对输入数据的拟合准确性。
我们将使用 Sklearn 中著名的 乳腺癌数据集。让我们首先看看如何导入它以及所有的库。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# import the dataset from sklearn
breast_cancer = load_breast_cancer()
# we create a variable "data" which contains the dataframe from the dataset
data = pd.DataFrame(data=breast_cancer['data'], columns=breast_cancer['feature_names'])
data['target'] = pd.Series(breast_cancer['target'], dtype='category')
作为分类问题,我们将使用准确率来衡量模型的性能。如果你有兴趣了解更多,请阅读我关于如何衡量二分类模型性能的文章。
现在让我们创建一个函数来对 dataframe 上的 L1 和 L2 正则化进行比较。
def plot_regularization(df, reg_type='l1'):
# we split our data into training and testing
X = df.drop('target', axis=1)
y = df['target']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# we define the different values of C
Cs = [0.001, 0.01, 0.1, 1, 10, 100, 1000]
coefs = []
test_scores = []
train_scores = []
for C in Cs:
# we train the model for the different values of C
clf = LogisticRegression(penalty=reg_type, C=C, solver='liblinear')
clf.fit(X_train, y_train)
# we save the performances
coefs.append(clf.coef_.ravel())
train_scores.append(clf.score(X_train, y_train))
test_scores.append(clf.score(X_test, y_test))
reg = reg_type.capitalize()
# and create some charts
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(12, 4))
ax1.plot(Cs, train_scores, 'b-o', label='Training Set')
ax1.plot(Cs, test_scores, 'r-o', label='Test Set')
plt.suptitle(f'{reg} regularization')
ax1.set_xlabel('C')
ax1.set_ylabel('Accuracy')
ax1.set_xscale('log')
ax1.set_title('Performance')
ax1.legend()
coefs = np.array(coefs)
n_params = coefs.shape[1]
for i in range(n_params):
ax2.plot(Cs, coefs[:, i], label=X.columns[i])
ax2.axhline(y=0, linestyle='--', color='black', linewidth=2)
ax2.set_xlabel('C')
ax2.set_ylabel('Coefficient values')
ax2.set_xscale('log')
ax2.set_title('Coefficients')
plt.show()
我们通过观察 L1 正则化来应用这一逻辑。
plot_regularization(data, 'l1')

L1 正则化如何影响模型性能。图像由作者提供。
我们看到 L1 正则化如何使模型系数在许多 C 值下接近于零。根据模型,系数值最高的是预测中最重要的特征。
我们也看到过拟合的开始——在C=100时,训练集的性能提高,而测试集的性能下降。
我们现在应用相同的函数来评估 L2 的效果。
plot_regularization(data, 'l2')

L2 正则化如何影响模型性能。图像由作者提供。
系数总是大于零,从而为最相关的特征创建了逐渐增加的权重分布。我们注意到在C=100时有非常轻微的过拟合。
其他正则化技术
除了 L1 和 L2 正则化,还有其他正则化技术可以应用于机器学习模型。其中包括 dropout 和 early stopping。
Dropout
Dropout 是一种在神经网络中减少过拟合的技术。Dropout 通过在训练阶段随机关闭一些神经元来工作,迫使神经网络找到其他方法来表示数据。
Early stopping
Early stopping 是另一种用于避免机器学习模型过拟合的技术。这种技术包括在验证集上的性能开始恶化时停止模型训练。这可以防止模型过度学习训练数据而在未见过的数据上泛化效果不好。
想了解更多关于早停的信息?阅读标题为TensorFlow 中的早停 - 防止神经网络过拟合的文章,发表于 TDS
一般来说,通过使用正则化技术的组合可以避免过拟合。然而,最合适的技术选择将取决于数据集和所用机器学习模型的特性。
总结
总之,正则化是一种重要的机器学习技术,有助于提高模型性能通过避免在训练数据上过拟合。
L1 和 L2 正则化是最常用的技术,但根据上下文,还有其他可能有用的技术。例如,dropout 几乎总是出现在深度学习中,即神经网络中。
在我们的例子中,我们看到了正则化如何影响逻辑回归模型的性能,以及 C 的值如何影响正则化本身。我们还检查了 C 值变化时模型系数的变化,以及 L1 和 L2 正则化如何以不同的方式影响模型系数。
感谢您抽出时间阅读我的文章!😊
下次见!
如果您想支持我的内容创作活动,请随时通过以下推荐链接加入 Medium 的会员计划。我将获得您投资的一部分,您将能够无缝访问 Medium 上的数据科学等众多文章。
[## 通过我的推荐链接加入 Medium - Andrea D'Agostino
阅读 Andrea D'Agostino 的每个故事(以及 Medium 上其他数千名作者的故事)。您的会员费直接…
medium.com](https://medium.com/@theDrewDag/membership?source=post_page-----72eb12f102b5--------------------------------)
推荐阅读
对感兴趣的读者,这里有我推荐的每个与机器学习相关主题的书单。这些书在我看来是必读的,对我的职业生涯产生了重大影响。
免责声明:这些是亚马逊附属链接。我将因推荐这些商品而从亚马逊获得少量佣金。您的体验不会改变,也不会额外收费,但这将帮助我扩大业务,并制作更多关于 AI 的内容。
-
机器学习简介: Confident Data Skills: Master the Fundamentals of Working with Data and Supercharge Your Career由 Kirill Eremenko 著
-
Sklearn / TensorFlow: Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow 由 Aurelien Géron 著
-
自然语言处理: Text as Data: A New Framework for Machine Learning and the Social Sciences由 Justin Grimmer 著
-
Sklearn / PyTorch: 使用 PyTorch 和 Scikit-Learn 进行机器学习:用 Python 开发机器学习和深度学习模型 作者 Sebastian Raschka
-
数据可视化: 用数据讲故事:商业专业人士的数据可视化指南 作者 Cole Knaflic
有用的链接(由我编写)
-
了解如何在 Python 中执行顶级探索性数据分析: Python 中的探索性数据分析 — 步骤指南
-
学习 TensorFlow 的基础知识: 开始使用 TensorFlow 2.0 — 深度学习简介
-
在 Python 中使用 TF-IDF 执行文本聚类: 使用 TF-IDF 在 Python 中进行文本聚类
拉格朗日乘子、KKT 条件和对偶性——直观解释
理解 SVM、正则化、PCA 以及许多其他机器学习概念的关键
·
关注 发表在 Towards Data Science ·13 min read·Nov 3, 2023
--
在这个故事中,我们将深入探索数学优化中的三个相关概念。这些概念曾让我花费大量时间和精力才完全掌握,因此我旨在以直观的方式向所有读者展示它们。我们的旅程将从对无约束优化的回顾开始,随后考虑有约束优化,我们将利用拉格朗日乘子和 KKT 条件。我们还将深入探讨这些概念之间的相互作用及其与对偶性概念的联系。
因此,在本故事结束时,你将了解如何解决约束和无约束优化问题,并能够直观地推导出这些方法为何有效。

照片由 Filip Mroz 提供,来自 Unsplash
无约束优化

由 Cdang 绘制的多变量函数图,来源于 Wikimedia CC BY-SA 4.0。
在无约束优化中,我们给定一个多变量函数 f(u),并希望找到向量 u* 的值,使得函数 f(u)* 的值最优(最大值或最小值)。
一般来说,函数可能有多个极大值和极小值,如上所示。在经典机器学习中以及本故事中,我们主要关注的是凸函数(这些函数也足够光滑)。凸 函数意味着该函数至多有一个最优值(当函数是损失函数时,这个最优值是一个最小值),如下所示:

由 Andrebis 绘制的 3D 表面图,来源于 Wikipedia CC BY-SA 3.0。
处理凸函数要容易得多,因为否则很难判断找到的最小值是否是所有值中的最低点(即全局最小值),而不仅仅是某个局部最小值。一般来说,即使存在一个最小值,也可能有多个点满足它(例如,如果函数是平的),我们将假设这种情况不会发生以简化解释;假设它发生也不会改变我们得出的任何结论。
对给定的多变量函数 f(u) 进行无约束优化可以通过解 ∇ᵤf(u) = 0 来实现。如果 f(u) 是一个 n 变量的函数 (u₁, u₂,…,uₙ),那么这就是一个 n 方程的系统:

解出这些方程后,会得到最优解 u=(u₁,u**₂,…,u**ₙ),即最优值(例如最小值)所在的位置。

由 Mike Run 绘制的表面上的切平面,来源于 Wikimedia CC BY-SA 4.0。
要理解这点,请回忆一下:
-
在任意点 u 处,切平面的法向量形式为 (∂f(u)/∂u₁, ∂f(u)/∂u₂, …, ∂f(u)/∂uₙ, f(u))
-
在任何最小值或最大值处,切平面都是水平的(视觉上明显)
-
因此,每当 ∇ᵤf(u) = 0 成立时,该点处必定存在水平切平面,因此,它一定是我们寻找的最小值。
另一种即将有用的合理化方法是观察梯度指向最大增加的方向(以及与最大减少的方向相反)。因此,当 ∇ᵤf(u) = 0 时,必须要么无法(没有方向能)从该点增加函数(即,处于最大值),要么从该点减少函数(即,处于最小值)。
无约束优化总结
给定: f(u)
目标: u,其中 f(u) 最小
方法: 求解 ∇ᵤf(u) = 0,因为在最小值处成立

图片来源于 Jorge Reyna 在 Unsplash
受限优化
在这种类型的优化中,我们给定形式为 g(u)=0 或 g(u)≤0 的等式约束(否则我们可以通过重新排列项或乘以负数将其转化为这种形式),并且我们希望仅在满足约束的所有点上进行优化。
我们通常假设等式约束 g(u)=0 是仿射的(线性的一般化),而不等式约束 g(u)≤0 涉及凸函数,以便整个优化问题是 凸的(否则,仅 f(u) 是凸的可能不足以保证唯一最优值)。
带有等式约束的优化
在此问题中,我们给定一个多变量函数 f(u) 和一个约束 g(u)=0,我们希望找到点 u**,使得 g(u)=0 且 f(u)* 最小(即,满足约束的情况下的最低点)。

受限优化由 Jacobmelgrad 在 维基百科 CC BY-SA 3.0.
例如,在所示的示例中,目标函数为 f(u₁,u₂) = u₁+ u₂(3D 平面),约束为 u²₁+ u²₂=1(2D 圆)。目标是找到点(u₁, u₂),使得在满足 u²₁+ u²₂=1 的情况下,该点对应于平面上的最低点(即,圆在平面上的投影最低点)。
解决这种类型的受限优化问题的一种方法是使用拉格朗日乘子法。简单来说,拉格朗日乘子定理表明,任何优化问题的解 u* 形式为:
最小化 f(u) 使得 g(u)=0
必须满足方程∇ᵤL(u,λ)=0,对于某些 λ∈R (并且显然,g(u)=0),其中 L 是拉格朗日函数,定义为 L(u,λ)=f(u)+λg(u)。这假设 ∇ᵤg(u)≠0。
由此可得,我们可以按照以下方法求解带有等式约束的受限优化问题(假设 ∇ᵤg(u)≠0*):
-
写出拉格朗日函数 L(u,λ)=f(u)+λg(u)。
-
解 ∇ᵤL(u,λ) = 0(n 个方程)和 g(u)=0 得到 n+1 个未知数 u₁,u₂*,…,uₙ,λ
-
解是 u=(u₁,u**₂,…,u**ₙ)
λ 被称为拉格朗日乘子。我们只需要找到它,因为它是系统的一部分,用于得出解 u。
你可以在这里研究与上图对应的例子。在这个例子中,问题不是凸的,解决方案应该会得出任何存在的最小值或最大值。注意,上述步骤(1)和(2)等同于对 L(u,λ)=f(u)+λg(u) 进行无约束优化。也就是说,设定:
∇ L(u,λ) =(∂L(u)/∂u₁, ∂L(u)/∂u₂, …, ∂L(u)/∂uₙ, ∂L(u)/∂λ)= 0
从这个意义上说,拉格朗日乘子法的强大之处在于,它将一个约束优化问题转化为一个无约束优化问题,我们可以通过简单地将梯度设为零来解决它。

由Jacobmelgrad在维基百科上提供的约束优化 CC BY-SA 3.0。
原理
直观地推导出为什么这样做有效并不困难。可行区域 是满足问题约束的点的集合(例如,上述圆上的点);我们希望在这些点中找到使目标函数最优的点。
我们知道 ∇ f(u) 指向最大下降方向的相反方向(沿着最大上升方向)。然而,在我们的情况下,我们只允许在可行区域内移动(满足约束的点);因此,为了在约束下最小化函数,我们应该沿约束曲线的最大下降方向移动(这样我们不会退出可行区域并到达最小值)。
假设约束曲线上的点 u 的切线方向由 r(u) 给出,那么回忆一下向量投影的公式,我们希望沿着以下方向移动,即 ∇ f(u) 在 r(u) 上的投影:

与上述无约束情况类似,你应该意识到只要这是 0,我们就无法沿约束方向移动以进一步增加 f(u)(如果在最大值处)或减少它(如果在最小值处)。
很明显,为了使其为零,我们需要 r(u)≠0(以确保分母不为零)和 ∇ f(u) ⋅ r(u)=0。对于后者,我们知道约束曲线上的法向量 ∇ g(u) 与切线 r(u) 垂直。因此,我们只需要 ∇ f(u) 与 ∇ g(u) 平行。
因此,在最优点 u* 处必须满足:
-
约束的法向量非零: ∇ g(u)≠0(以确保 r(u)≠0)
-
约束满足:g(u)=0*(简单要求)
-
∇ f(u)* ∥∇ g(u):存在一个实数β,使得∇ f(u) = β∇ g(u)*
注意,通过重新排列项和重新命名 -β,(3)等价于“存在一个实数 λ 使得 ∇ f(u)+λ∇ g(u)=0”。换句话说,∇ᵤL(u,λ) = 0,借此我们直观地推导出了一个约束的拉格朗日乘子定理(如有需要,请向上滚动查看)。
请注意,第一个条件称为约束资格。如果约束在满足(2)和(3)的点上不满足该条件,则没有保证该点是最优的,因为在该点上投影未定义。
多个等式约束
当存在多个约束g₁(u), g₂(u),…,gₖ(u)*时,该方法可以顺利推广到以下情况:
-
写出拉格朗日函数 L(u,λ₁,λ₂,…,λₖ) = f(u) + λ₁g₁(u) + λ₂g₂(u) +…+λₖgₖ(u)
-
通过设置∇ᵤL(u,λ₁,λ₂,…,λₖ) = 0(n个方程)和 g₁(u)=0, g₂(u)=0, …, gₖ(u)=0 来求解 n+k 个方程,以找到 n+k 个未知数 u₁,u₂*,…,uₙ, λ₁,λ₂,…,λₖ
-
解为 u=(u₁,u**₂,…,u**ₙ)
假设∇ g(u)≠0* 可以推广为 ∇ g₁(u), ∇ g₂(u),…,∇ gₖ(u) 必须线性无关。这称为 LICQ(线性独立约束资格)。

带有不等式约束的约束优化
当我们处理形式为g(u)≤0的不等式约束时,问题不会变得更复杂。在这种情况下,我们希望找到满足g(u)≤0的f(u)的最优点。
对于上述问题,这意味着可行区域不仅仅是圆上的点,还包括圆内的点。对于特定问题(而不是一般情况),这显然不会改变解决方案。

受约束的优化由Jacobmelgrad在维基百科上提供,CC BY-SA 3.0。经 Shading 修改。
我们不解决拉格朗日乘子条件(2, 3),而是解决一组称为 KKT 条件的四个条件,这些条件是拉格朗日乘子情况的一般化。我们可以如下推导这些条件:

优化问题的不等式约束图由Onmyphd在维基百科上提供,CC BY-SA 3.0。
注意,对于一个任意的超曲面 f(u) 和约束 g(u)≤0, 假设是一个凸平滑函数且有一个最优点,那么有两种可能:
- 最优点 uᴾ 位于可行区域内。
-
在这种情况下,优化问题的解 u* 必须是 uᴾ 且 g(u)<0* 必须成立(左侧图像)。
-
在可行区域中不可能找到更优的点,因为 uᴾ 是 f(u) 的整个区域(领域)上的最优点(例如最小值)。
- 最优点 uᴾ 位于可行区域外。
-
在这种情况下,如果点是最大值,f(u) 在可行区域中必须只是减少(不能再增加,否则会产生另一个最优点)
-
如果点是最小值,f(u) 在可行区域中必须只是增加(不能再减少,否则会产生另一个最优点)
-
因此,最优点 u* 必须位于可行区域的边缘,因为在内部不会变得更好(g(u) = 0* 必须成立)
在第一种情况下,显然求解优化问题等同于求解无约束版本的问题。
∇ᵤf(u) = 0
我们称约束为“非活跃的”是因为它在优化问题中没有影响。
在第二种情况下,显然求解优化问题等同于求解等式约束版本的问题(拉格朗日乘数法)。
对于这种情况唯一需要注意的是,λ 对于最小化必须 ≥ 0,对最大化必须 ≤ 0。对于最小化,这意味着 ∇ᵤf(u) 和 ∇ᵤg(u) 指向相反方向(即,β 在 ∇ᵤ f(u) = β∇ᵤg(u) 是 ≤0),这必须成立,因为 ∇ᵤg(u) 指向约束 g(u)≥0 的正侧(基本属性);与此同时,∇ᵤ f(u) 指向约束的负侧,因为 f(u) 在那里增加。对于最大化情况,可以轻松构造类似的论证。
但我们事先不知道这两种情况中的哪一种适用。我们可以按照以下方法合并它们(假设最小化):
-
写出拉格朗日函数 L(u,λ)=f(u)+λg(u)
-
设置 ∇ᵤL(u,λ) = 0(n 个方程)和 g(u)≤0
-
求解解 (u₁,u₂*,…,uₙ,λ.) 其中一个以上情况适用:
-
λ=0 和 g(u)<0(λ=0* 的第一个情况意味着 ∇ᵤL(u,λ) = ∇ᵤf(u) = 0,因此步骤 1,2 等同于求解 ∇ᵤf(u) = 0)
-
g(u)=0* 和 λ≥0(第二种情况,因为 g(u)=0 意味着拉格朗日方法是正确的,这就是我们所做的)
我们可以总结这两点,即 g(u)≤0* 和 λ≥0 必须成立,并且 λg(u)=0* 必须成立(λ 或 g(u)* 之一必须为零)。这意味着给定一个形式的优化问题:
最小化 f(u) 使得 g(u)≤0
我们期望最优点 u 满足以下四个条件:
-
稳定性:∇ᵤL(u,λ)* = 0
-
原始可行性:g(u)≤0*
-
双重可行性:λ≥0
-
互补松弛性:λg(u)=0*
并且解决这些条件一起产生最优点u。实际上,对于凸优化问题,这些条件是充分但不是必要的。也就是说,
-
如果一个点满足这些条件(例如,通过一起解决它们找到),那么这足以证明该点是最优的(对于凸问题无需进一步寻找)。
-
与此同时,这些条件并非点必须为最优点的必要条件。有可能解决这些条件却没有解时,在现实中存在一个满足条件但不满足它们的最优点。例如,考虑f(x) = x和约束x² ≤ 0(此外,这里和另一个 KKT 示例在本文档中解决)。
一旦我们强制执行诸如 LICQ(前述)的约束条件,我们可以保证 KKT 条件既充分又必要。一个更易于检查的替代约束条件是 Slater 的条件,它保证了对于凸问题,KKT 是充分且必要的。
Slater 的条件简单地表明可行域必须有一个内点。也就是说,对于约束g(u)≤0,函数必须有域f(u)内满足g(u)<0的点。这是一个基本条件,在现实生活中几乎总是满足的(但不包括上述反例),这意味着 KKT 很少会错过寻找最优解。
多重约束
当存在多个等式约束h₁(u), h₂(u),…,hₖ(u)以及多个不等式约束g₁(u), g₂(u),…,gₚ(u)时,该方法通过编写完整的拉格朗日函数并仅对不等式约束及其乘子(我们称之为α)检查 KKT 条件来平滑地推广:
0. 写出拉格朗日函数

- 设置∇ᵤL(u,λ₁,λ₂,…,λₖ, α₁, α₂, …, αₚ) = 0(n 个方程)
2. 设置h₁(u)=0, h₂(u)=0, …, hₖ(u)=0(k*个方程),并设置
g₁(u)≤0, g₂(u)≤0, …, gₚ(u)≤0(p个不等式)
3. 设置α₁≥0, α₂≥0, …, αₚ≥0(p个不等式)
4. 设置α₁g₁(u) = α₂g₂(u) = αₚgₖ(u) = 0(p*个方程)
总共,您有n+k+p个方程和2p个不等式,您将一起解决以找到n+k+p个变量(u₁,u₂*,…,uₙ,λ₁,λ₂,…,λₖ,α₁, α₂, …, αₚ ),这将产生最小化函数并满足k+p个约束的解u=(u₁,u**₂,…,u**ₙ)。

对偶原理
对偶原理简单地说明,对于任何优化问题,我们可以写出一个对偶优化问题,解决它可以告诉我们原始问题(称为原始问题)的某些内容或者直接解决它。
对于任何形式的优化问题:

对偶优化问题的形式是:

是的,最小化的形式与拉格朗日函数相同
反之亦然,如果是最大化。
示例
例如,我们之前讨论的受约束优化问题:

具有相应的对偶问题:

如基本微积分所示,要进行最小化,我们首先做

这意味着

因此,优化问题变成

现在所需的只是对其进行微分并使其等于零,从而得到 λ = 1/√2,这意味着 (x, y) = (−1/√2, −1/√2),这与通过 KKT 解决原始问题所得到的解相同。
推导对偶
原始(原始)问题是

假设我们定义一个函数,当 u 不在可行区域内(不满足约束)时返回无穷大,否则返回零:

在这种情况下,原始问题等价于

这应该是合理的,因为 f(u)+P(u) 将可行区域之外的值设为无穷大,并保持可行区域不变。这个和的最小值必须发生在可行区域内,即使约束是明确强制的,因为无穷大大于任何东西。
观察到我们可以声明:

因为有了这个,如果:
-
g(u)<0 时 P(u)=0,如之前定义,因为要最大化 λg(u) 必须满足 λ=0(否则由于 g(u)<0 它是负值)
-
g(u)=0 时 P(u)=0,如之前定义,因为 λg(u) 将为零(λ 可以是任何值)
-
g(u)>0 时 P(u)=∞,如之前定义,λ=∞ 是最大化 λg(u) 的值
因此,原始问题等价于

引入 f 到最大值是可以的,因为它不是 λ 的显式函数
这个和对偶问题的区别在于,在对偶中最大值和最小值被交换了。

因此,因为一般来说 MinMax(…) ≥ MaxMin(…),对偶的解将是原始问题解的下界。这被称为弱对偶性。
一个更有趣的情况是,当MinMax(…) = MaxMin(…)时,双重问题的解恰好也是原始问题的解(如例子中所示)。这被称为强对偶性。你可以适度容易地证明当 KKT 条件既必要又充分时,该等式成立(因此强对偶性)。换句话说,强对偶性将在 Slater 条件成立的情况下适用于凸问题!
那又怎么样?
如果你考虑一下,解决对偶问题相当于仅在原始问题上应用 KKT 的平稳性和对偶可行性条件。你不再需要应用原始可行性和互补松弛条件,而是需要处理额外的对偶变量的最小化。在许多情况下,这比在原始问题上解决 KKT 要简单得多。这个额外的最小化可以通过线性或二次规划来处理。
多个约束?
在推广到多个约束时,拉格朗日函数的变化正如你所预期的那样(类似于我们所见),我们只需在最大化中为与不等式约束相关的乘子添加α≥0 条件。

希望这个故事帮助你真正理解了无约束优化、拉格朗日乘子、KKT 和对偶性。下次见,再见!
🦜🔗LangChain:允许 LLMs 与你的代码互动
原文:
towardsdatascience.com/langchain-allow-llms-to-interact-with-your-code-55d5751aa8a2

图片由David Clode拍摄,发布在Unsplash上
学习如何使用工具为你的 LLM 实现自定义功能
·发表于Towards Data Science ·5 分钟阅读·2023 年 7 月 5 日
--
介绍
生成模型受到了大家的关注。许多 AI 应用现在不再需要领域内的机器学习专家,而只需知道如何实现 API 调用。
最近,例如,我参加了一次黑客马拉松,我需要实现一个自定义命名实体识别,但我直接使用了 LLM,并利用了其少样本学习能力来获得我想要的结果,这对于赢得黑客马拉松来说已经足够了!(如果你想,你可以在这里查看项目)。
因此,对于许多现实世界的应用,重点更多地转向如何与这些 LLM 进行互动和使用,而不是创建模型。LangChain 是一个允许你做到这一点的库,我最近写了几篇关于它的文章。
LangChain 工具
工具是 LLM 可以用来增强其能力的工具。工具可以在链或代理中实例化。
例如,LLM 可能会在回应之前进行维基百科搜索,以确保响应是最新的。
当然,一个代理可以使用多个工具,因此,通常会定义一个工具列表。
现在我们来看看工具的结构。工具不过是由几个字段组成的类:
-
name (str):定义工具的唯一名称
-
description (str):工具在自然语言中的用途描述。LLM 能够读取此描述,并判断是否需要该工具来回答查询。
-
return_direct(bool):例如,工具可能会返回自定义函数的输出。我们是否希望将该输出直接呈现给用户(True),还是由 LLM 预处理(False)?
-
args_schema(Pydantic BaseModel):例如,工具可能会使用一个自定义函数,其输入参数必须从用户的查询中提取。我们可以提供更多关于每个参数的信息,以便 LLM 可以更容易地完成这一步骤。
如何定义工具
有多种方法定义工具,我们将在这篇文章中探讨。首先,我们导入必要的库并实例化一个 OpenAI 模型。
要做到这一点,你需要一个令牌,你可以在我之前的文章中看到如何获得它。
!pip install langchain
!pip install openai
from langchain import LLMMathChain, SerpAPIWrapper
from langchain.agents import AgentType, initialize_agent
from langchain.chat_models import ChatOpenAI
from langchain.tools import BaseTool, StructuredTool, Tool, tool
import os
os.environ["OPENAI_API_KEY"] = ... # insert your API_TOKEN here
llm = ChatOpenAI(temperature=0)
实例化工具的第一种方法是使用 Tool 类。
假设我们想给工具增加搜索网络信息的能力,为此我们将使用一个叫 SerpAPI 的 Google API,你可以在这里注册并获得 API:serpapi.com/
让我们实例化一个 SerpAPIWrapper 类,并用 from_function 方法定义工具。
在 func 字段中,我们需要放置一个指向我们希望使用该工具启动的方法的指针,即 SerpAPI 的 run 方法。正如我们所见,我们给工具起了一个名字和描述。这样做比解释要简单。
search = SerpAPIWrapper()
tools = [
Tool.from_function(
func=search.run,
name="Search",
description="useful for when you need to answer questions about current events"
),
]
现在我们可以提供给我们的代理创建的工具列表,这里只有一个。
agent = initialize_agent(
tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)
agent.run(
"Who is Bob Dylan's girlfriend?
)
自定义工具
在我看来,创建自定义工具最清晰的方法是继承 BaseTool 类。
class CustomTool(BaseTool):
name = "custom_tool"
description = "useful for when you need to answer questions about medium articles"
def _run(
self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None
) -> str:
"""Use the tool."""
return "I am not a Medium expert, but I know that Marcello is pretty good! :I)"
async def _arun(
self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None
) -> str:
"""Use the tool asynchronously."""
raise NotImplementedError("custom_search does not support async")
你可以看到这是一个自定义工具的实现,每当用户询问关于 Medium 的问题时,它就会被使用。然而,返回的字符串不会完全是我设置的内容,因为它会被大型语言模型进一步处理。
如果我们想直接返回某些内容,只需添加一个“return_direct”字段,如下所示。
class CustomTool(BaseTool):
name = "custom_tool"
description = "useful for when you need to answer questions about medium articles"
return_direct=True
def _run(
self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None
) -> str:
"""Use the tool."""
return "I am not a Medium expert, but I know that Marcello is pretty good! :I)"
async def _arun(
self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None
) -> str:
"""Use the tool asynchronously."""
raise NotImplementedError("custom_search does not support async")
即使我们不使用 _arun 方法(它对异步调用很有用),我们仍然需要实现它,因为 BaseTool 是一个抽象类,如果我们不实现所有的抽象方法,将会出现错误。
实际案例
有一天,一个朋友对我说:“嘿 Marcello,既然你做 AI 这类的东西,为什么不帮我做一个聊天机器人,当需要时返回医生的工作时间,并预约?”
我解决这个问题的第一个想法是使用 LangChain,并让 LLM 与用户互动,然后只要模型明白用户要求查看工作时间,它就会返回一个 CSV 文件(或者如果你愿意的话是数据框)。
所以同样的东西也可以用于这个用例。假设我们有一个叫做 work_time.csv 的 CSV 文件。
import pandas as pd
class WorkingHours(BaseTool):
name = "working_hours"
description = "useful for when you need to answer questions about working hours of the medical staff"
return_direct=True+
def _run(
self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None
) -> str:
"""Use the tool."""
df = pd.read_csv("working_hours.csv") #maybe you need to retieve some real time data from a DB
return df
async def _arun(
self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None
) -> str:
"""Use the tool asynchronously."""
raise NotImplementedError("custom_search does not support async")
就这样,我朋友想要的应用程序原型只用了几行代码就准备好了!显然,和一个好的前端开发人员合作,让它看起来更好!
最终思考
LangChain 是一个最近的库,它允许我们在不同的环境中使用 LLM 的强大功能。
我发现能够使用 LLM 来理解上下文,理解用户的请求,然后运行我自己的自定义函数以实际解决任务是非常有用的。
这将使你能够编写灵活的代码。要为你的应用程序添加功能,你只需编写一个函数并告诉模型在认为需要时使用这个函数,这样就完成了!
如果你对这篇文章感兴趣,请在 Medium 上关注我!
💼 Linkedin ️| 🐦 Twitter | 💻 网站
🦜🔗LangChain:通过记忆容量提升性能
原文:
towardsdatascience.com/langchain-enhancing-performance-with-memory-capacity-c7168e097f81

图片由 Milad Fakurian 提供,来源于 Unsplash
LangChain 通过记忆扩展技术提升性能
·发布于 Towards Data Science ·4 分钟阅读·2023 年 6 月 21 日
--
我之前已经发布过关于 LangChain 的文章,介绍了这个库及其所有功能。现在我想集中讨论一个关键方面,即如何在智能聊天机器人中管理记忆。
聊天机器人或代理程序也需要一种信息存储机制,这可以采取不同的形式并执行不同的功能。在聊天机器人中实现记忆系统不仅有助于使它们更加聪明,还能让它们对用户更自然、更有用。
幸运的是,LangChain 提供了使开发人员能够轻松实现应用程序中记忆的 API。在本文中,我们将更详细地探讨这一方面。
在 LangChain 中使用记忆
开发聊天机器人的最佳实践之一是保存聊天机器人与用户的所有互动。这是因为LLM 的状态可能会根据过去的对话而变化,实际上,LLM 对两个用户提出的相同问题会给出不同的回答,因为他们与聊天机器人有不同的过去对话,因此它处于不同的状态。
因此,聊天机器人记忆创建的内容无非就是旧消息的列表,这些消息在提出新问题之前会反馈给它。当然,LLM 有有限的上下文,因此你需要稍微发挥创造力,选择如何将这些历史记录反馈给 LLM。最常见的方法是返回旧消息的摘要或仅返回最近的 N 条消息,这些消息可能是最具信息性的。
从 ChatMessageHistory 的基础开始
这是允许我们管理聊天机器人(AI)与用户(Human)之间消息的主要类。此类提供了两个主要方法,如下所示。
-
add_user_message: 允许我们将消息添加到聊天机器人的记忆中,并将该消息标记为“用户”
-
add_ai_message: 允许我们将消息添加到聊天机器人的记忆中,并将该消息标记为“AI”
!pip install langchain
from langchain.memory import ChatMessageHistory
history = ChatMessageHistory()
history.add_user_message("Hi!")
history.add_ai_message("Hey, how can I help you today?")
#print messages
history.messages
这个类允许你做各种事情,但在最简单的使用情况下,你可以将其视为不时将各种消息保存到列表中。然后,你也可以通过以下方式遍历历史记录,简单地查看你添加的所有消息。
for message in history.messages:
print(message.content)
使用 ConversationBufferMemory 进行高级记忆管理
ConversationBufferMemory 类在消息存储方面的行为有点类似于 ChatMessageHistory 类,但它提供了聪明的方法来检索旧消息。
例如,我们可以以消息列表或一个大字符串的形式检索旧消息,这取决于我们的需求。如果我们想让 LLM 对过去的对话进行总结,将过去的对话作为一个大字符串可能会很有用。如果我们想对过去的对话进行详细分析,我们可以通过提取列表来逐条阅读消息。
使用 ConversationBufferMemory 类,我们还可以通过 add_user_message 和 add_user_message 方法将消息添加到历史记录中。
另一方面,load_memory_variables 方法用于提取旧消息,可以是列表或字典形式,具体取决于指定的内容,让我们看一个示例。
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory()
memory.chat_memory.add_user_message("Hi, how are you?")
memory.chat_memory.add_ai_message("hey,I am fine! How are you?")
memory_variables = memory.load_memory_variables({})
print(memory_variables['history'])
管理多个对话的记忆
我们已经看到了如何通过保存和检索消息来管理记忆的简单示例。但在实际应用中,你可能需要管理多个对话的记忆。LangChain 允许你通过使用所谓的链来管理这种情况。
链不过是实现某个目标的一系列简单或复杂步骤的工作流。
例如,一个 LLM 查找维基百科上的一条信息,因为它不知道如何回答某个问题,这就是一个链。
要处理各种对话,只需将一个 ConversationBufferMemory 实例与每个创建的链关联即可,这些链是通过 ConversationChain 类的实例化来创建的。
这样,当调用模型的 predict 方法时,链中的所有步骤都会运行,因此模型会读取对话中的过去消息。
让我们看一个简单的例子。
from langchain.llms import OpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory()
conversation = ConversationChain(llm= OpenAI(), memory=memory)
conversation.predict(input="Hey! how are you?")
结束语
总结一下,记忆是聊天机器人的一个关键组成部分, LangChain 提供了多个框架和工具来有效管理记忆。通过使用如ChatMessageHistory 和 ConversationBufferMemory 等类,你可以捕捉并存储用户与 AI 的互动,并利用这些信息指导未来的 AI 响应。我希望这些信息能帮助你构建更智能、更强大的聊天机器人!
在下一篇文章中,我将向你展示如何使用 LangChain 工具。
如果你觉得这篇文章有用,可以在 Medium 上关注我!😉
结束
马塞洛·波利提
LangChain 增加了 Cypher 搜索功能
使用 LangChain 库,你可以方便地生成 Cypher 查询,从而高效地从 Neo4j 中检索信息。
·
关注 发布于 Towards Data Science ·8 分钟阅读·2023 年 5 月 24 日
--
图像由 Midjourney 付费订阅生成。 Midjourney 服务条款。
如果你开发了或计划实施任何使用大型语言模型的解决方案,你很可能听说过 LangChain 库。 LangChain 库是最广泛使用的 Python 库,用于开发在不同能力下使用 LLM 的应用程序。它设计为模块化的,允许我们在任何可用模块中使用任何 LLM,例如链、工具、内存或代理。
一个月前,我花了一周时间研究并实现了一种解决方案,使任何人都可以直接从Neo4j数据库中检索信息,并在他们的 LLM 应用程序中使用。我了解了很多关于 LangChain 库内部的知识,并在博客文章中记录了我的经历。
我的一个同事向我展示了一个 LangChain 的功能请求,用户请求将我实现的从 Neo4j 数据库中检索信息的选项作为一个模块直接添加到 LangChain 库中,以便不需要额外的代码或外部模块就能将 Neo4j 集成到 LangChain 应用中。由于我已经熟悉 LangChain 的内部工作原理,我决定自己尝试实现 Cypher 搜索功能。我花了一个周末研究和编码解决方案,并确保其符合贡献标准以便添加到库中。幸运的是,LangChain 的维护者们非常响应并且开放新想法,Cypher 搜索功能已经在 LangChain 库的最新版本中添加了。感谢Harrison Chase维护如此出色的库,并且对新想法非常响应。
在这篇博客文章中,我将展示如何使用 LangChain 库中新添加的 Cypher 搜索功能从 Neo4j 数据库中检索信息。
代码可在GitHub上获取。
知识图谱是什么
LangChain 已经与 Vector 和 SQL 数据库集成,那么为什么我们还需要与像 Neo4j 这样的图形数据库集成呢?

WikiData 知识图谱。图片许可协议:CC BY-SA 4.0。
知识图谱非常适合存储异质且高度关联的数据。例如,上面的图像包含了关于人物、组织、电影、网站等的信息。虽然能够直观地建模和存储多样化的数据集非常惊人,但我认为使用图谱的主要好处在于通过其关系分析数据点的能力。图谱使我们能够发现那些在传统数据库和分析方法中可能会被忽视的联系和关联,因为这些方法常常忽视个别数据点的上下文。
当处理复杂系统时,图形数据库的强大之处真正显现出来,因为相互依赖和互动对于理解系统至关重要。
这些功能使我们能够超越单个数据点,深入探讨定义其上下文的复杂关系。这提供了数据的更深层次、更全面的视图,有助于更好的决策和知识发现。
设置 Neo4j 环境
如果你有一个现有的 Neo4j 数据库,你可以用它来尝试新添加的 Cypher 查询模块。Cypher 查询模块使用图谱模式信息生成 Cypher 语句,意味着你可以将其插件化到任何 Neo4j 数据库中。
如果你还没有 Neo4j 数据库,你可以使用 Neo4j Sandbox,它提供了一个免费的 Neo4j 数据库云实例。你需要注册并实例化任何一个现有的预填充数据库。在这篇博客中,我将使用 ICIJ Paradise Papers 数据集,但如果你愿意,可以使用其他任何数据集。该数据集由 国际调查记者联盟 提供,作为他们 离岸泄露数据库 的一部分。

Paradise Papers 数据集图谱模式。图片由作者提供。
图谱包含四种类型的节点:
-
**Entity**- 离岸法律实体。它可以是公司、信托、基金会或其他在低税区创建的法律实体。 -
**Officer**- 在离岸实体中扮演角色的个人或公司,例如受益人、董事或股东。图中的关系只是所有现有关系的一部分。 -
**Intermediary**- 寻找离岸公司和离岸服务提供商之间的中介——通常是一个律师事务所或要求离岸服务提供商创建离岸公司的中介。 -
**Address**- 在 ICIJ 获取的原始数据库中出现的注册地址。
知识图谱 Cypher 查询
Cypher 查询的名称源自 Cypher,它是一种用于与像 Neo4j 这样的图数据库互动的查询语言。

知识图谱 Cypher 链工作流。图片由作者提供。
为了让 LangChain 从图数据库中检索信息,我实现了一个可以将自然语言转换为 Cypher 语句的模块,使用它从 Neo4j 中检索数据,并以自然语言形式将检索到的信息返回给用户。这种自然语言和数据库语言之间的双向转换过程不仅提高了数据检索的整体可访问性,还大大改善了用户体验。
LangChain 库的美在于其简洁性。我们只需几行代码即可使用自然语言从 Neo4j 中检索信息。
from langchain.chat_models import ChatOpenAI
from langchain.chains import GraphCypherQAChain
from langchain.graphs import Neo4jGraph
graph = Neo4jGraph(
url="bolt://54.172.172.36:7687",
username="neo4j",
password="steel-foreheads-examples"
)
chain = GraphCypherQAChain.from_llm(
ChatOpenAI(temperature=0), graph=graph, verbose=True,
)
在这里,我们使用gpt-3.5-turbo模型来生成 Cypher 语句。Cypher 语句是根据图形模式生成的,这意味着理论上,你可以将 Cypher 链插入任何 Neo4j 实例,它应该能够回答自然语言问题。不幸的是,我还没有测试其他 LLM 提供商生成 Cypher 语句的能力,因为我没有任何它们的访问权限。尽管如此,如果你愿意尝试,我很想听听你对其他 LLM 生成 Cypher 语句的评估。当然,如果你想摆脱对 LLM 云提供商的依赖,你可以随时微调一个开源 LLM 以生成 Cypher 语句。
让我们从一个简单的测试开始。
chain.run("""
Which intermediary is connected to most entites?
""")
结果

生成的答案。图片由作者提供。
我们可以观察生成的 Cypher 语句和从 Neo4j 中检索的信息,这些信息用来形成答案。这是最简单的设置了。让我们继续下一个例子。
chain.run("""
Who are the officers of ZZZ-MILI COMPANY LTD.?
""")
结果

生成的答案。图片由作者提供。
由于我们使用的是图形,让我们构造一个可以利用图形数据库优势的问题。
chain.run("""
How are entities SOUTHWEST LAND DEVELOPMENT LTD. and
Dragon Capital Markets Limited related?
""")
# Generated Cypher statement
# MATCH (e1:Entity {name: 'SOUTHWEST LAND DEVELOPMENT LTD.'})-
# [:CONNECTED_TO|OFFICER_OF|INTERMEDIARY_OF|SAME_NAME_AS*]-(e2:Entity {name: 'Dragon Capital Markets Limited'})
生成的 Cypher 语句乍看之下没有问题。然而,存在一个问题,因为 Cypher 语句使用了可变长度路径查找语法,并且将关系视为无向的。因此,这种类型的查询高度未优化,会导致行数爆炸。
gpt-3.5-turbo的一个优点是它遵循我们在输入中留下的提示和指示。例如,我们可以要求它仅查找最短路径。
chain.run("""
How are entities SOUTHWEST LAND DEVELOPMENT LTD. and
Dragon Capital Markets Limited connected?
Find a shortest path.
""")

生成的答案。图片由作者提供。
现在我们给出了一个只应检索最短路径的提示,我们不再遇到基数爆炸的问题。然而,我注意到的一件事是,如果返回的是路径对象,LLM 有时不会提供最佳结果。生成的 Cypher 语句在 Neo4j 浏览器中返回以下可视化结果。

答案的图形可视化。图片由作者提供。
生成的自然语言响应并没有真正提到这两家公司注册在同一个地址,而是根据节点属性生成了自己的最短路径。然而,我们也可以通过指导模型使用哪些信息来解决这个问题。
chain.run("""
How are entities SOUTHWEST LAND DEVELOPMENT LTD. and Dragon Capital Markets Limited connected?
Find a shortest path.
Return only name properties of nodes and relationship types
""")
结果

生成的答案。图片由作者提供。
现在我们可以得到更好的响应和更合适的响应。你给 LLM 的提示越多,期望得到的结果就越好。例如,你还可以指导它可以遍历哪些关系。
chain.run("""
How are entities SOUTHWEST LAND DEVELOPMENT LTD. and Dragon Capital Markets Limited connected?
Find a shortest path and use only officer, intermediary, and connected relationships.
Return only name properties of nodes and relationship types
""")
结果

生成的答案。图片由作者提供。
你可以看到,生成的 Cypher 语句仅允许遍历OFFICER_OF、INTERMEDIARY_OF和CONNECTED_TO关系。相同的 Cypher 语句生成了以下图形化展示。

答案的图形化展示。图像由作者提供。
总结
图形数据库是检索或分析各种实体之间的连接(如人员和组织)的绝佳工具。在这篇博客文章中,我们查看了一个简单的最短路径用例,其中关系数量和关系类型的顺序事先是未知的。这些类型的查询在向量数据库中几乎是不可能的,并且在 SQL 数据库中也可能相当复杂。
我对将 Cypher Search 添加到 LangChain 库中感到非常兴奋。请试用一下,并告诉我它对你是否有效,特别是如果你在其他 LLM 模型上进行测试或有有趣的用例。同时,记得订阅,因为我还有几篇博客文章将探讨 LangChain 库中的 Cypher Search。
一如既往,代码可以在GitHub上找到。
🦜🔗 LangChain:文档上的问答代理
原文:
towardsdatascience.com/langchain-question-answering-agent-over-docs-18e5585bdbd3

由 Mike Alonzo 提供的照片,来自 Unsplash
了解嵌入和代理以构建问答应用
·发表于 Towards Data Science ·6 分钟阅读·2023 年 6 月 4 日
--
介绍
在自然语言处理领域,最常见的用例之一是与文档相关的问答。例如,想象一下将一个或多个 PDF 文件输入到机器中,然后询问与这些文件相关的问题。这可能很有用,比如当你需要为大学考试做准备时,并且想要向机器询问你不理解的内容。实际上,一个更高级的用例是让机器向你提问,进行一种模拟审问。
为了解决这个任务,已经做了大量研究并开发了许多工具,但今天我们可以利用大型语言模型(LLMs)的力量,例如 OpenAI 的 GPT-3 及更高版本。
LangChain 是一个非常新的库,它允许我们管理和创建基于 LLM 的应用程序。实际上,LLM 只是一个更复杂的 AI 架构中的一部分。当我们创建这样一个系统时,我们不仅需要向 OpenAI 模型提出查询并获得响应,还需要例如保存这个响应、正确结构化提示等。
在这篇文章中,我们将看到如何使用 LangChain 和 OpenAI 构建一个简单的文档问答应用。
嵌入
在这个应用中,我们将使用一个叫做 ChromaDB 的库。这是一个开源库,允许我们保存嵌入。这里可能会出现一个问题:那么什么是嵌入?
嵌入不过是词(或文本)在向量空间中的投影。
我尝试用更简单的方式来解释自己。假设我们有以下几个词可用:“king”,“queen”,“man”和“woman”。
我们根据经验直观地理解这些词之间的距离。例如,“man”在概念上比“queen”更接近“king”。但机器不具备直观能力,它们需要数据和指标来工作。所以我们做的是将这些词转化为笛卡尔空间中的数据,以便准确表示这种直观的距离概念。

嵌入示例(图片由作者提供)
在上面的图像中,我们有一个嵌入(虚拟)的示例。我们看到“Man”比其他单词更接近“King”,同样“woman”和“queen”也是如此。
另一个有趣的事情是“man”和“king”之间的距离与“woman”和“queen”之间的距离是相同的。所以某种程度上,这个嵌入确实捕捉到了这些词的本质。
另一个需要说明的事情是度量标准,即如何测量距离。在大多数情况下,这是使用余弦相似度来测量的,即使用两个嵌入之间角度的余弦值。

余弦相似度(图片由作者提供)
我在示例中的嵌入只有两个维度,两条轴。但现代算法如 BERT 创建的嵌入有数百或数千条轴,所以很难理解算法为什么将文本放置在空间中的特定位置。
在这个演示中,你可以在 3 维空间中导航真实的嵌入,并查看单词彼此之间的距离。
让我们开始编程吧!
首先,我们需要安装一些库。我们肯定需要 Langchain 和 OpenAI 来实例化和管理 LLM。
然后我们将安装 ChromaDB 和 TikToken(后者是成功安装 ChromaDB 所必需的)
!pip install langchain
!pip install openai
!pip install chromadb
!pip install tiktoken
现在我们需要一个我们要处理的文本文件。事实上,我们的目的是向 LLM 提问有关这个文件的内容。用 Python 下载文件非常简单,可以使用以下命令完成。
import requests
text_url = 'https://raw.githubusercontent.com/hwchase17/chat-your-data/master/state_of_the_union.txt'
response = requests.get(text_url)
#let'extract only the text from the response
data = response.text
现在我们导入所有需要的类。
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.embeddings.cohere import CohereEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores.elastic_vector_search import ElasticVectorSearch
from langchain.vectorstores import Chroma
显然,要使用 OpenAI 的模板,你必须输入你的个人 API KEY。如果你不知道怎么做,可以查看我关于 Langchain 的上一篇文章。
import os
os.environ["OPENAI_API_KEY"] = "your_open_ai_key"
在实际应用中,你可能会有很多文本文件,你希望 LLM 找出这些文本中哪个包含你问题的答案。在这个简单的例子中,我们将单个文本文件分成多个部分(块),并将每个部分视为不同的文档。模型需要找出哪个部分包含我们问题的答案。我们通过使用下面的命令将文本分成多个部分,每部分分配一个最大长度。
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_text(data)
len(texts)
我们看到从原始文本创建了 64 个部分。
我们可以逐个打印不同的部分,因为它们被包含在一个列表中。
texts[0],texts[1]
现在我们创建一个对象,用于保存创建的文本各个部分的嵌入。
embeddings = OpenAIEmbeddings()
但我们希望将嵌入保存到一个持久化的数据库中,因为每次打开应用程序时重新创建它们会浪费资源。这就是 ChromaDB 帮助我们的地方。我们可以使用文本片段创建和保存嵌入,并为每个片段添加元数据。在这里,元数据将是命名每个文本片段的字符串。
persist_directory = 'db'
docsearch = Chroma.from_texts(
texts,
embeddings,
persist_directory = persist_directory,
metadatas=[{"source": f"{i}-pl"} for i in range(len(texts))]
)
from langchain.chains import RetrievalQAWithSourcesChain
现在我们希望将 docsearch 转变为检索,因为这将是它的目的。
from langchain import OpenAI
#convert the vectorstore to a retriever
retriever=docsearch.as_retriever()
我们还可以查看检索器使用的距离度量,在这种情况下,默认的度量是相似度,如嵌入部分所解释的。
retriever.search_type
最后,我们可以要求检索器选择最能回答我们查询的文档。如果有必要,检索器也可以选择多于一份文档。
docs = retriever.get_relevant_documents("What did the president say about Justice Breyer")
现在我们来看一下他提取了多少文档以及这些文档的内容。
len(docs)
docs
现在我们可以创建一个代理。 代理能够执行一系列步骤来独立完成用户的任务。我们的代理需要去查看可用的文档,找出回答问题的文档,并返回该文档。
#create the chain to answer questions
chain = RetrievalQAWithSourcesChain.from_chain_type(
llm = OpenAI(temperature=0),
chain_type="stuff",
retriever=retriever,
return_source_documents = True
)
如果需要,我们还可以创建一个函数来后处理代理的输出,以使其更易读。
def process_result(result):
print(result['answer'])
print("\n\n Sources : ",result['sources'] )
print(result['sources'])
现在一切终于准备好了,我们可以使用我们的代理来回答我们的查询!
question = "What did the president say about Justice Breyer"
result = chain({"question": question})
process_result(result)
结束语
在这篇文章中,我们介绍了 LangChain、ChromaDB 以及一些关于嵌入的解释。我们通过一个简单的例子展示了如何将多个文档或文档的部分的嵌入保存到持久化数据库中,并检索所需的部分来回答用户查询。
如果你觉得这篇文章有用,可以在 Medium 上关注我! 😉
结束
马切洛·波利提
语言模型及其相关:Gorilla、HuggingGPT、TaskMatrix 及更多
当我们给 LLMs 访问成千上万的深度学习模型时,会发生什么?
·发表于 Towards Data Science ·阅读时间 18 分钟·2023 年 9 月 4 日
--

(图片由 Mike Arney 提供,来自 Unsplash)
最近,我们见证了基础模型在深度学习研究中日益流行。预训练的大型语言模型(LLMs)引领了一个新范式,在这种范式中,一个模型可以用来——以令人惊讶的成功——解决许多不同的问题。然而,尽管通用 LLMs 很受欢迎,但任务特定的微调模型往往比利用基础模型的方法表现更佳。简单来说,专业化模型仍然很难被超越!话虽如此,我们可能会开始想知道,基础模型和专业深度学习模型的能力是否可以结合起来。在这次概述中,我们将研究近期的研究,这些研究通过学习调用其关联的 API,将 LLMs 与其他专业化深度学习模型结合起来。结果框架将语言模型作为一个集中控制器,用于制定解决复杂 AI 相关任务的计划,并将解决方案过程中的专业部分委派给更合适的模型。
“通过仅提供模型描述,HuggingGPT 能够持续且方便地整合来自 AI 社区的各种专家模型,而无需改变任何结构或提示设置。这种开放和持续的方式使我们离实现人工通用智能更进一步。” — 引自 [2]

(引自 [2, 3])
背景
在探索语言模型如何与其他深度学习模型集成之前,我们需要涵盖一些背景概念,如 LLM 工具、信息检索和自我指导 [11]。有关语言模型的更多通用背景信息,请查看以下资源。
使用工具与 LLMs
“通过赋予 LLMs 使用工具的能力,我们可以访问更广泛且不断变化的知识库,并完成复杂的计算任务。” — 摘自[3]
尽管语言模型拥有许多令人印象深刻的能力,但它们并不完美,不能独立完成所有任务。在许多情况下,将现有模型与工具(例如搜索引擎、计算器、日历等)结合使用,可以大幅扩展它们的能力范围。在之前的综述中,我们探讨了 Toolformer [1]——一种用于教会 LLMs 使用一小组简单文本 API 的微调技术——以及如何在不费太大力气的情况下使用工具来提高 LLMs 的表现;更多细节请见这里。
尽管像 Toolformer 这样的模型很有效,但它们只考虑了一小组非常简单的 API。这些 API 仅仅触及了可以提供给 LLMs 的工具总数的表面。例如,想象一下,如果我们能够将 LLM 与任何通过互联网提供的 API 集成——我们可能会解锁一个全新的应用和可能性的领域!

ChatGPT Plus 插件商店中的热门应用
(几乎)任何事都有可能! 这种将语言模型与各种在线 API 广泛接入的趋势正在通过 ChatGPT 插件商店进行探索;见上文。通过利用这些 API,我们可以做的不仅仅是为 LLMs 提供简单的工具,如计算器!我们可以轻松想到几个通过这种方法变得可能的高影响力应用。例如,我们可以使用集成工具的语言模型进行:
-
制定假期行程并预订所有必要的票和预订
-
策划并购买本周的食品杂货清单以便路边取货
-
查找并预订即将到来的周末餐厅的桌子
-
在任何电子商务商店发现并购买相关产品
可能性范围几乎是无限的!通过将语言作为一种标准化的交流媒介,我们可以与基础模型如 ChatGPT 合作,完成令人惊讶的复杂任务。我们所要做的就是提示模型生成与我们请求相关的 API 调用。
深度学习 API。 在本概述中,我们将考虑将 LLMs 与一种特定类型的 API 集成——这些 API 提供对平台如HuggingFace上的开源深度学习模型的访问。AI/ML 社区对开源软件给予了高度重视,这意味着许多最强大的深度学习模型在线上免费提供。通常,这些模型附带有良好撰写的描述,称为模型卡,可以用来为 LLM 提供任何模型所需的所有信息。然后,这些模型可以通过基本的提示技术轻松与 LLM 集成。
自我指令框架

(来自[11])
自我指令框架,在[11]中提出,开创了利用大型语言模型(LLMs)通过生成合成的指令调优数据来进行自我训练的想法。这些数据可以用于微调。从与特定任务或指令相关的单一输入-输出对开始,自我指令会提示一个现成的 LLM 生成新的任务,以及与每个任务相关的有效指令和响应。在进行过滤以去除低质量数据后,我们可以在生成的指令调优数据集上对任何语言模型进行微调。有趣的是,我们发现,基于这些数据进行微调的模型往往能达到与那些在人工策划数据集上训练的模型相匹配的性能。尽管自我指令效果良好,Alpaca [12]也提出了一些对整体框架的改进;详情见这里。
信息检索
正如我们在之前的概述中看到的,基础语言模型的质量往往随着规模的增加而提升——大型预训练数据集和模型会带来最佳结果。然而,我们只能在语言模型中固定的权重集合中存储有限的信息。即使是大规模模型也有有限数量的参数。此外,现代 LLM 的有限上下文窗口限制了我们只能将少量信息注入到模型的提示中。那么,如果我们想让 LLM 访问大量的信息库,该怎么办?我们需要采用某种形式的信息检索。

(来自[13])
向量数据库。 一种流行的信息检索形式可以通过将 LLM 与存储大量文本信息的向量数据库集成来完成。在高级别上,这种与向量数据库(例如,Pinecone、Milvus、Weaviate、Redis等)的集成包括:
最终结果是,我们可以快速找到相关的文本信息,作为提示中的额外上下文,使 LLM 能够利用超出其上下文窗口的额外信息。尽管我们仍然无法将所有信息提供给模型,但我们可以使用向量相似性搜索快速识别最相关的部分。

(来自[14])
这就是唯一的方法吗? 许多其他方法已被提出用于信息检索——这方面有一个专门的(非常活跃的)研究领域。我们甚至可以使用 LLMs 生成相关信息(而不是检索)通过生成知识提示;见上文。文章这里很好地总结了现有的信息检索技术。总体而言,存在许多不同的技术,我们有很多选择来决定如何用外部信息源来增强 LLM。

(来自[3])
我们为什么要关心这个问题? 信息检索非常重要,但我们可能会想知道这与将 LLMs 与其他深度学习模型整合的主题有何相关。好吧,如果我们想要提供对任何在线可用模型的访问呢? 在像 HuggingFace 这样的 ML 社区中,有成千上万的模型!因此,我们不能为所有这些模型提供描述给 LLM。相反,我们需要使用某种形式的信息检索来确定我们应该包含在 LLM 提示中的最相关的模型子集;见上文。
将 LLMs 与其他模型集成
现在我们已经掌握了一些相关背景信息,我们将查看最近的出版物,这些出版物增强了 LLMs 与其他深度学习模型的结合。虽然每种方法有所不同 [2, 3],但所有这些技术的目标都是教会 LLM 如何调用与其他专业模型相关的 API。然后,LLM 可以作为一个控制器(或大脑),通过规划和将子任务委派给不同的 API 来协调问题的解决。
HuggingGPT: 用 ChatGPT 及其在 Hugging Face 的朋友解决 AI 任务 [2]

(来自 [2])
最近 LLMs 变得流行,但近年来深度学习研究已经产生了多种非常有用的模型,用于解决特定任务,如图像识别、视频动作检测、文本分类等等。这些模型不同于语言模型,它们是狭义的专家,这意味着它们可以在固定的输入输出格式下准确地解决特定任务。但它们对于超出其训练解决的特定任务范围的内容没有用处。如果我们想将这些模型重新利用作为解决更多开放式 AI 相关任务的组件呢?
“LLMs [可以] 作为一个控制器来管理现有的 AI 模型以解决复杂的 AI 任务,而语言可以是一个通用接口来实现这一点。” — 来自 [2]
在 [2] 中,作者探索了将 LLMs 用作连接深度学习模型的通用接口。HuggingGPT [2] 的灵感来自于允许 LLM 与具有不同专业能力的外部模型进行协调的想法。简单来说,LLM 作为问题解决系统的“大脑”,计划如何解决问题,并协调不同深度学习模型之间的努力,这些模型解决了该问题所需的子任务。为了教会 LLM 如何做到这一点,我们需要每个模型的高质量描述。幸运的是,我们不需要进行任何提示工程来创建这些描述——它们通过像 HuggingFace 这样的 ML 社区 广泛提供!

(来自 [2])
这怎么运作? HuggingGPT [2] 将问题分解为四个部分:
-
任务规划:使用 LLM 将用户请求分解为可解决的任务。
-
模型选择:从 HuggingFace 中选择用于解决任务的模型。
-
任务执行:运行每个选定的模型并将结果返回给 LLM。
-
响应生成:使用 LLM 为用户生成最终响应。
正如我们可能预期的那样,利用在线模型的能力赋予了 HuggingGPT 解决各种复杂问题的能力!
“HuggingGPT 可以根据用户请求自动生成计划,并使用外部模型,因此可以整合多模态感知能力并处理多个复杂的 AI 任务。” — 来自 [2]
相当令人印象深刻的是,HuggingGPT 完全不需要微调就能学习如何协调和使用这些模型!相反,它利用了 少量学习 和 指令提示 来执行解决问题所需的每一个任务;见下文。为了使这些提示发挥良好效果,我们需要一个 可调节 的 LLM(例如 ChatGPT 或 GPT-4),它能够严格遵守指令并执行严格的输出格式(例如,将问题分解为 json 格式的任务)。

(来源 [2])
值得注意的是,我们应当观察到,供 HuggingGPT 使用的可用模型集直接注入到任务规划提示中;见上例。显然,网上有太多模型可用,无法将它们全部包含在提示中。为了决定应将哪些模型作为选项包含在提示中,HuggingGPT 根据任务类型(即,它们是否解决与当前问题相关的任务)选择一组模型,根据下载量(即,在 HuggingFace 上下载或使用该模型的用户数量)对它们进行排名,然后将排名前 K 的模型提供给 LLM 作为选项。

(来源 [2])
资源依赖。 在 HuggingGPT 将用户请求分解为几个解决步骤后,我们需要在指定的计划中执行每个模型。然而,当我们执行 HuggingGPT 指定的每个模型时,一些模型可能依赖于其他模型的输出,这在 [2] 中被称为“资源依赖”。为了处理这些情况,具有依赖关系的模型会等待它们所依赖的模型的输出后再执行。没有依赖关系的模型可以并行执行,从而加快 HuggingGPT 的任务执行步骤。有趣的是,LLM 生成的任务规划结构不仅改变了每个模型的执行顺序,还改变了我们评估 HuggingGPT 输出的方式。例如,[2] 中的作者使用 GPT-4 来评估更复杂任务计划的质量;见上文。
它表现得好吗? 在 [2] 中对 HuggingGPT 的评估专注于评估几个不同 LLM 的任务规划能力,因为任务规划的质量在很大程度上决定了 HuggingGPT 整体问题解决框架的成功。如下面的图所示,像 GPT-3.5 这样的 LLM(以及在一定程度上较弱的模型)似乎能够有效地将用户请求分解为有效的任务计划。

(来源 [2])
需要大量工作来改进对这些技术的评估 — [2] 中提供的分析远非全面。此外,尽管 HuggingGPT 表现良好,但它只考虑了一小部分直接注入 LLM 提示中的、文档完善的模型 API。与微调相比,这一框架需要大量的提示工程来有效运作。尽管我们避免了对微调数据集的需求,但这一框架在很大程度上依赖于基础模型的能力。因此,我们可能会想:我们如何将这种方法推广到更多模型中以更可靠地工作?
猩猩:与大量 API 连接的大型语言模型 [3]
“支持可能数百万个不断变化的 API 的网络规模集合需要重新思考我们整合工具的方法。” — 引自 [3]
将 LLM 与一组较小且固定的其他模型进行整合是很酷的,但如果我们能够教会 LLM 使用任何在线可用的模型 API 呢? 为此,我们可以使用检索技术 i) 识别相关模型 API 和 ii) 将它们的文档提供给 LLM。通过这种方法,LLM 将能够访问比少量精心挑选的工具更多的资源!而是,模型可以访问云中不断变化的大量 API。然而,尽管如此,即便是强大的 LLM(例如,GPT-4 或 Claude)也难以以这种方式利用 API,因为它们倾向于传递不正确的参数或虚构调用不存在的 API;见下文。

(引自 [3])
在 [3] 中,作者采用了基于自我指导 [11] 框架的微调方法,以使 LLM 更有能力利用大量外部深度学习 API。超越像 HuggingGPT [2] 这样的提案, [3] 中的作者考虑了超过 1,600 个不同的模型 API。这些模型 API 的集合比之前工作的模型 API 要大得多,功能重叠(即,许多模型执行类似的任务),甚至包括一些文档不完美的模型。为了学习如何利用这些 API, [3] 的工作在一个有效 API 调用的大型数据集上对 LLaMA-7B [5] 模型进行了微调。最终的模型,能够有效利用这些 API,被称为猩猩。
创建数据集。 为了微调 Gorilla,我们利用 HuggingFace Hub、PyTorch Hub 和 TensorFlow Hub 创建了一个大规模的 API 调用语料库。在所有三个中心中,选择了总共 1,645 个模型 API,涵盖了从计算机视觉到音频、强化学习等多个领域。在将每个 API 的相关信息存储在一个 json 对象中(即包含领域、框架、功能描述和 API 签名等信息)后,我们可以通过使用 GPT-4 生成十个用户问题提示和相关回答来遵循自我指导的方法。经过过滤错误的 API 调用后,结果就是一个大数据集,涵盖了利用不同模型 API 解决各种问题的实际用例。这个数据集非常适合微调 LLM;见下文。

(来源 [3])
检索感知的微调。 不同于执行“普通”的监督微调,Gorilla 利用了一种“检索感知”的微调变体。这听起来可能很高端,但实际实现非常简单!对于微调数据集中的每次模型 API 调用,我们在数据中添加了最新的 API 文档。然后,我们在测试时采取类似的方法,将 API 文档附加到我们的提示中,这教会 LLM 动态确定每个 API 的正确使用方法;见下文。

(来源 [3])
检索感知微调是一种技术,教会 LLM 在确定如何解决问题时更好地利用 API 文档。这种动态方法允许 LLM:
-
在测试时适应 API 文档中的实时变化
-
发展改进的 上下文学习 能力以进行 API 调用
-
通过更好地关注 API 文档中的信息来减少幻觉

(来源 [3])
检索感知的微调使 Gorilla 成为利用各种不同深度学习模型的极其强大的接口——生成的 LLM 可以使用大量不同的 API 来解决问题。此外,模型实际上可以适应任何 API 的文档变化! 请参见上图以获取适应 API 文档变化的示例。

(来源 [3])
使用 Gorilla。 尽管我们知道在构建微调数据集时应该在提示中包含哪些 API,但当我们在推理过程中接收到用户的任意提示时,并不知道应该使用哪个合适的 API。为了确定正确的 API,我们可以采用信息检索技术,i) 嵌入用户的提示(或其他相关信息),以及 ii) 执行向量相似性搜索以找到最相关的 API 文档。通过这种方式,我们可以轻松高效地确定用于解决问题的最佳 API。或者,我们可以通过将用户的提示直接传递给模型,而无需任何信息检索或额外信息,以零-shot 方式使用 Gorilla。无论哪种方式,Gorilla 的目标都是生成准确的调用,以使用最合适的 API 来解决用户的提示;详见上文。

(来自 [3])
如上实验结果所示,Gorilla 是一个极其有能力的深度学习 API 接口。与更大、更强大的模型(例如 GPT-3.5、Claude 和 GPT-4)相比,我们发现 Gorilla 在生成准确的 API 调用方面更具能力,这意味着模型产生虚假的 API 调用的情况较少,并且更倾向于传递正确的输入参数!
其他值得注意的技术……
HuggingGPT [2] 和 Gorilla [3] 在过去几个月中获得了大量公众认可和讨论,但也有许多其他技术被提出,考虑使用 LLMs 来协调多个不同深度学习模型的工作。下面概述了一些其他有趣的技术。

(来自 [7])
TaskMatrix [7] 是一篇立场论文——这意味着它对一个显著问题提出了立场或观点——考虑了将基础模型(例如,像 ChatGPT 这样的 LLMs)与数百万种不同的 API 进行集成。值得注意的是,这项工作认为基础模型缺乏解决专业任务所需的领域知识,但已有许多现有的、任务特定的模型可以以令人印象深刻的准确性解决指定的任务。由于兼容性问题,将这些专业/专家模型与 LLM 集成可能会很困难,但[7]中的作者广泛讨论并考虑了这一想法。在许多方面,HuggingGPT 和 Gorilla 是[7]中讨论的想法的实际实现。

(来自 [8])
API Bank [8] 提供了一个更好的基准来评估工具增强的 LLM。特别是,该基准考虑了 50 多个通常与 LLM 集成的 API 和 264 个标注对话——包括总计 568 次 API 调用——以配合这些工具。该基准旨在评估 LLM 创建任务计划(即,逐步指南,说明要执行哪些 API 调用)、确定正确的 API 以及执行 API 调用以回答提供的问题的能力。毫无意外,初步实验显示,GPT-4 在利用外部工具回答用户提供的问题方面具有最强的能力。尽管这项工作没有特别考虑深度学习模型 API,但所使用的任务框架与本综述中看到的方法相似。

(来自 [9])
ToolkenGPT [9] 尝试通过为每个工具分配一个特定的令牌——以及相关的token embedding——来减轻工具跟随基础模型的微调要求,使 LLM 能够以类似生成普通词令牌的方式生成工具请求。这种方法被发现对于利用各种外部工具非常灵活。

(来自 [10])
使用开源 LLM 的工具操作 [10]。 在大多数情况下,我们发现闭源 LLM(例如,GPT-4)更具可操控性,因此能够更好地操作外部工具。然而,在 [10] 中,作者分析了开源 LLM 是否可以通过微调来匹配强大闭源 LLM 在这一特定技能上的表现。各种开源 LLM 通过人类反馈和监督进行优化,以提升其工具跟随能力。有趣的是,我们看到在充分微调的情况下,若干开源模型可以与 GPT-4 达到竞争性表现。在本综述中,我们看到像 Gorilla 这样的模型表明,给定正确的微调程序,开源 LLM(例如,LLaMA)可以非常强大。
结束语
“目前迫切需要一种机制,可以利用基础模型提出任务解决方案的框架,然后自动将框架中的一些子任务与具有特殊功能的现成模型和系统进行匹配,以完成这些任务。” — 来自 [7]
在这个概述中,我们已经看到,LLM 能够通过其 API 与其他深度学习模型集成。以 HuggingGPT [2] 为例,这可以通过一种上下文学习方法实现,其中我们向 LLM 提供现有模型及其功能的描述。然而,值得注意的是,HuggingGPT 最适合与强大的闭源模型如 ChatGPT 一起使用。如果我们想要教会开源模型(例如,LLaMA)在解决复杂问题时调用深度学习模型 API,我们需要采用 Gorilla [3] 提出的微调方法。无论如何,这些技术都非常强大,因为它们在狭窄专家模型和基础模型的优势之间取得了平衡。我们可以通过依赖 LLM 进行高级推理和形成问题解决计划,同时将某些子任务委派给更可靠和准确的专业模型,从而发挥两者的优势。
与我联系!
非常感谢您阅读这篇文章。我是 Cameron R. Wolfe,Rebuy 的 AI 总监。我研究深度学习的经验和理论基础。如果您喜欢这个概述,请订阅我的 Deep (Learning) Focus newsletter,在这里我通过从基础上概述相关主题来帮助读者理解 AI 研究。您还可以在 X 和 LinkedIn 上关注我,或者查看我在 Medium 上的 其他文章!
参考文献
[1] Schick, Timo, 等. “Toolformer: Language models can teach themselves to use tools.” arXiv preprint arXiv:2302.04761 (2023).
[2] Shen, Yongliang, 等. “Hugginggpt: Solving ai tasks with chatgpt and its friends in huggingface.” arXiv preprint arXiv:2303.17580 (2023).
[3] Patil, Shishir G., 等. “Gorilla: Large Language Model Connected with Massive APIs.” arXiv preprint arXiv:2305.15334 (2023).
[4] Wei, Jason, 等. “Chain of thought prompting elicits reasoning in large language models.” arXiv preprint arXiv:2201.11903 (2022).
[5] Touvron, Hugo, 等. “Llama: Open and efficient foundation language models.” arXiv preprint arXiv:2302.13971 (2023).
[6] Wang, Yizhong, 等. “Self-Instruct: Aligning Language Model with Self Generated Instructions.” arXiv preprint arXiv:2212.10560 (2022).
[7] Liang, Yaobo, 等. “Taskmatrix. ai: Completing tasks by connecting foundation models with millions of apis.” arXiv preprint arXiv:2303.16434 (2023).
[8] Li, Minghao, 等. “Api-bank: A benchmark for tool-augmented llms.” arXiv preprint arXiv:2304.08244 (2023).
[9] Hao, Shibo, 等. “ToolkenGPT: Augmenting Frozen Language Models with Massive Tools via Tool Embeddings.” arXiv preprint arXiv:2305.11554 (2023).
[10] Xu, Qiantong, 等. “On the Tool Manipulation Capability of Open-source Large Language Models.” arXiv preprint arXiv:2305.16504 (2023).
[11] 王一中等人. “Self-Instruct: 将语言模型与自生成指令对齐.” arXiv 预印本 arXiv:2212.10560 (2022)。
[12] 塔奥里等人. “斯坦福 Alpaca: 一种跟随指令的 LLaMA 模型.” (2023)。
[13] 特里维迪等人. “将检索与思维链推理交替用于知识密集型多步骤问题.” arXiv 预印本 arXiv:2212.10509 (2022)。
[14] 刘嘉诚等人. “生成知识提示用于常识推理.” arXiv 预印本 arXiv:2110.08387 (2021)。
用于句子补全的语言模型
语言模型的实际应用,选择最可能的单词来扩展一个英文句子
·
关注 发表在Towards Data Science ·13 分钟阅读·2023 年 9 月 15 日
--
图片由Brett Jordan拍摄,来源于Unsplash
与Naresh Singh共同撰写。
目录
介绍
问题陈述
头脑风暴解决方案
-
算法与数据结构
-
NLP(自然语言处理)
-
深度学习(神经网络)
LSTM 模型
-
分词
-
PyTorch 模型
-
使用模型修剪无效建议
-
计算下一个单词的概率
变换器模型
结论
介绍
语言模型如 GPT 最近变得非常流行,并被用于各种文本生成任务,例如在 ChatGPT 或其他对话式 AI 系统中。这些语言模型非常庞大,通常超过数十亿个参数,需要大量计算资源和金钱来运行。
在英语语言模型的背景下,这些大型模型是过度参数化的,因为它们使用模型的参数来记忆和学习我们世界的各个方面,而不仅仅是建模英语语言。如果我们有一个仅需理解语言及其结构的应用,我们很可能可以使用一个更小的模型。
运行训练模型推理的完整代码可以在这个笔记本中找到。
问题陈述
假设我们正在构建一个滑动键盘系统,尝试预测你在手机上输入的下一个词。根据滑动模式的轨迹,用户的意图词有很多可能性。然而,这些可能的词中许多不是英语中的实际单词,可以被排除。即使在这个初步修剪和排除步骤之后,仍然有许多候选词,我们需要选择一个作为对用户的建议。
为进一步修剪候选列表,我们可以使用基于深度学习的语言模型,查看提供的上下文,并告诉我们哪个候选词最有可能完成这个句子。
例如,如果用户输入了句子“我已经安排好了”,然后划出如下面所示的模式

然后,用户可能意图的英语单词包括:
-
混乱
-
会议
然而,如果我们考虑一下,用户可能更可能意图“会议”而不是“混乱”,因为句子前面的单词是“安排”。
根据我们目前所知道的一切,我们有哪些选项可以通过编程方式进行修剪?让我们在下面的章节中头脑风暴一些解决方案。
头脑风暴解决方案
算法与数据结构
基于第一原则,似乎合理从数据语料库开始,找到一对一对出现的单词,并训练一个马尔可夫模型来预测这一对在句子中出现的概率。你会发现这种方法有两个显著的问题。
-
空间利用:英语中有25 万到 100 万词,这还不包括不断增长的众多专有名词。因此,任何传统的软件解决方案在建模词对同时出现的概率时,必须维护一个 250k*250k = 625 亿词对的查找表,这显得有些过于庞大。许多词对可能出现的频率并不高,可以被修剪。即使在修剪之后,也有很多词对需要关注。
-
完整性:仅仅编码一对词的概率并不能解决眼前的问题。例如,当你只查看最近的一对词时,之前的句子上下文完全丧失。在句子“你今天怎么样”中,如果你想查看“coming”之后的词,你会有很多以“coming”开头的词对。这会忽略该词之前的整个句子上下文。可以想象使用词三元组等,但这会加剧上述提到的空间利用问题。
让我们将注意力转移到一种利用英语语言特性的解决方案上,看看这是否能帮到我们。
NLP(自然语言处理)
历史上,NLP(自然语言处理)领域涉及理解句子的词性(POS),并利用这些信息来进行修剪和预测决策。可以想象使用与每个词相关的 POS 标签来确定句子中的下一个词是否有效。
然而,计算句子的词性是一个复杂的过程,需要对语言有专门的理解,这在NLTK 词性标注的页面中有所体现。
接下来,我们来看看一种基于深度学习的方法,这种方法需要大量标记数据,但构建时不需要太多语言专业知识。
深度学习(神经网络)
随着深度学习的出现,NLP(自然语言处理)领域发生了翻天覆地的变化。随着 LSTM 和基于 Transformer 的语言模型的发明,解决方案往往涉及将一些高质量的数据投入到模型中,并训练其预测下一个词。
本质上,这就是 GPT 模型正在做的。GPT(生成预训练变换器)模型被训练来预测给定句子前缀后的下一个词(标记)。
给定句子前缀“这真是太棒了”,模型很可能会提供以下作为该句子后续词的高概率预测。
-
天
-
经验
-
世界
-
生活
同样,后续词完成句子前缀的概率可能较低。
-
红色
-
鼠标
-
行
Transformer 模型架构是像 ChatGPT 这样的系统的核心。然而,对于学习英语语言语义的更受限的用例,我们可以使用一种更便宜的模型架构,如LSTM(长短期记忆)模型。
LSTM 模型
让我们构建一个简单的 LSTM 模型,并训练它根据标记的前缀预测下一个标记。现在,你可能会问什么是标记。
标记化
对于语言模型,标记通常可以意味着
-
单个字符(或单个字节)
-
目标语言中的整个单词
-
介于 1 和 2 之间。这通常被称为子词
将单个字符(或字节)映射到一个标记上是非常有限制的,因为我们把这个标记过度负荷到包含大量关于它出现的上下文的信息。这是因为字符“c”例如,出现在许多不同的单词中,要预测在看到字符“c”后下一个字符,需要仔细查看前面的上下文。
将单个单词映射到一个标记上也是有问题的,因为英语本身有大约 25 万到 100 万个单词。此外,当语言中添加了一个新单词时会发生什么呢?我们是否需要回去重新训练整个模型以考虑这个新单词?
子词标记化被认为是 2023 年的行业标准。它将经常一起出现的字节子串分配给唯一的标记。通常,语言模型有几千(比如 4,000)到几十万(比如 60,000)个唯一标记。确定什么构成标记的算法由BPE(字节对编码)算法m 决定。
选择我们词汇表中的唯一标记数量(称为词汇大小)时,我们需要注意以下几点:
-
如果我们选择的标记太少,我们就回到了每个字符一个标记的情况,模型很难学到有用的东西。
-
如果我们选择的标记太多,我们会遇到模型的嵌入表超越模型其他部分的权重,从而使模型在受限环境中部署变得困难。嵌入表的大小将取决于我们为每个标记使用的维度。使用 256、512、786 等大小并不罕见。如果我们使用 512 的标记嵌入维度,并且有 100k 个标记,我们会得到一个在内存中使用 200MiB 的嵌入表。
因此,在选择词汇大小时我们需要找到一个平衡点。在这个例子中,我们选择了 6600 个标记,并用 6600 的词汇大小训练了我们的标记器。接下来,让我们看看模型定义本身。
PyTorch 模型
模型本身是相当简单的。我们有以下层:
-
词汇嵌入(词汇表大小=6600,嵌入维度=512),总大小约为 15MiB(假设嵌入表的数据类型为 4 字节 float32)
-
LSTM(层数=1,隐藏维度=786)总大小约为 16MiB
-
多层感知器(786 到 3144 到 6600 维)总大小约为 93MiB
完整的模型大约有 31M 可训练参数,总大小约为 120MiB。

这是模型的 PyTorch 代码。
class WordPredictionLSTMModel(nn.Module):
def __init__(self, num_embed, embed_dim, pad_idx, lstm_hidden_dim, lstm_num_layers, output_dim, dropout):
super().__init__()
self.vocab_size = num_embed
self.embed = nn.Embedding(num_embed, embed_dim, pad_idx)
self.lstm = nn.LSTM(embed_dim, lstm_hidden_dim, lstm_num_layers, batch_first=True, dropout=dropout)
self.fc = nn.Sequential(
nn.Linear(lstm_hidden_dim, lstm_hidden_dim * 4),
nn.LayerNorm(lstm_hidden_dim * 4),
nn.LeakyReLU(),
nn.Dropout(p=dropout),
nn.Linear(lstm_hidden_dim * 4, output_dim),
)
#
def forward(self, x):
x = self.embed(x)
x, _ = self.lstm(x)
x = self.fc(x)
x = x.permute(0, 2, 1)
return x
#
#
这是使用 torchinfo 的模型摘要。
LSTM 模型摘要
=================================================================
Layer (type:depth-idx) Param #
=================================================================
WordPredictionLSTMModel -
├─Embedding: 1–1 3,379,200
├─LSTM: 1–2 4,087,200
├─Sequential: 1–3 -
│ └─Linear: 2–1 2,474,328
│ └─LayerNorm: 2–2 6,288
│ └─LeakyReLU: 2–3 -
│ └─Dropout: 2–4 -
│ └─Linear: 2–5 20,757,000
=================================================================
Total params: 30,704,016
Trainable params: 30,704,016
Non-trainable params: 0
=================================================================
解读准确率:在 P100 GPU 上对 1200 万句英语句子进行大约 8 小时训练后,我们达到了 4.03 的损失值,top-1 准确率为 29%,top-5 准确率为 49%。这意味着模型在 29%的情况下能够正确预测下一个词汇,在 49%的情况下,训练集中下一个词汇是模型前五个预测中的一个。
我们的成功指标应该是什么? 虽然我们模型的 top-1 和 top-5 准确率数字并不令人印象深刻,但对于我们的问题,它们并不那么重要。我们的候选词是一个适合滑动模式的小词汇集合。我们希望模型能够选择一个理想的候选词来完成句子,使其在语法和语义上都连贯。由于我们的模型通过训练数据学习语言的本质,我们期望它对连贯的句子赋予更高的概率。例如,如果我们有句子 “棒球运动员” 和可能的补全候选词(“跑了”、“游泳”、“躲藏”),那么“跑了”是比其他两个更好的后续词。因此,如果我们的模型以比其他词更高的概率预测“跑了”,那么它就能满足我们的需求。
解读损失值:4.03 的损失值意味着预测的负对数似然值为 4.03,这意味着正确预测下一个词汇的概率为 e^-4.03 = 0.0178 或 1/56。一个随机初始化的模型通常有大约 8.8 的损失值,这是-log_e(1/6600),因为模型随机预测 1/6600 个词汇(6600 是词汇表的大小)。虽然 4.03 的损失值可能看起来并不理想,但重要的是要记住,训练后的模型比未训练(或随机初始化)的模型要好约 120 倍。
接下来,让我们看看如何利用这个模型来改进我们的滑动键盘的建议功能。
使用模型来修剪无效的建议
我们来看一个实际的例子。假设我们有一个部分句子 “我认为”,用户做出如下图中蓝色的滑动模式,从“o”开始,经过“c”和“v”之间,结束于“e”和“v”之间。

这个滑动模式可能表示的一些词汇是
-
结束
-
十月(October 的缩写)
-
冰
-
我已经(省略了撇号)
在这些建议中,最可能的一个可能是“I’ve”。让我们将这些建议输入到我们的模型中,看看它输出什么。
[I think] [I've] = 0.00087
[I think] [over] = 0.00051
[I think] [ice] = 0.00001
[I think] [Oct] = 0.00000
等号后的值是词作为句子前缀有效完成的概率。在这种情况下,我们看到词“I’ve”被分配了最高的概率。因此,它是最可能跟随句子前缀“I think”的词。
接下来你可能会问,我们如何计算这些下一个词的概率。让我们来看看。
计算下一个词的概率
为了计算一个词是否为句子前缀的有效完成,我们以评估(推理)模式运行模型,并输入标记化的句子前缀。我们还在词前添加一个空格前缀来标记化词。这是因为 HuggingFace 预标记器在词的开头用空格拆分词,所以我们希望确保我们的输入与 HuggingFace 标记器使用的标记化策略一致。
假设候选词由 3 个标记 T0、T1 和 T2 组成。
-
我们首先用原始标记化的句子前缀运行模型。对于最后一个标记,我们检查预测 T0 标记的概率。我们将其添加到“probs”列表中。
-
接下来,我们对前缀+T0 进行预测,并检查 T1 的概率。我们将此概率添加到“probs”列表中。
-
接下来,我们对前缀+T0+T1 进行预测,并检查 T2 的概率。我们将此概率添加到“probs”列表中。
“probs”列表包含生成 T0、T1 和 T2 标记的单独概率。由于这些标记对应于候选词的标记化,我们可以将这些概率相乘,以获得候选词作为句子前缀的完成的组合概率。
计算完成概率的代码如下所示。
def get_completion_probability(self, input, completion, tok):
self.model.eval()
ids = tok.encode(input).ids
ids = torch.tensor(ids, device=self.device).unsqueeze(0)
completion_ids = torch.tensor(tok.encode(completion).ids, device=self.device).unsqueeze(0)
probs = []
for i in range(completion_ids.size(1)):
y = self.model(ids)
y = y[0,:,-1].softmax(dim=0)
# prob is the probability of this completion.
prob = y[completion_ids[0,i]]
probs.append(prob)
ids = torch.cat([ids, completion_ids[:,i:i+1]], dim=1)
#
return torch.tensor(probs)
#
下面我们可以看到更多示例。
[That ice-cream looks] [really] = 0.00709
[That ice-cream looks] [delicious] = 0.00264
[That ice-cream looks] [absolutely] = 0.00122
[That ice-cream looks] [real] = 0.00031
[That ice-cream looks] [fish] = 0.00004
[That ice-cream looks] [paper] = 0.00001
[That ice-cream looks] [atrocious] = 0.00000
[Since we're heading] [toward] = 0.01052
[Since we're heading] [away] = 0.00344
[Since we're heading] [against] = 0.00035
[Since we're heading] [both] = 0.00009
[Since we're heading] [death] = 0.00000
[Since we're heading] [bubble] = 0.00000
[Since we're heading] [birth] = 0.00000
[Did I make] [a] = 0.22704
[Did I make] [the] = 0.06622
[Did I make] [good] = 0.00190
[Did I make] [food] = 0.00020
[Did I make] [color] = 0.00007
[Did I make] [house] = 0.00006
[Did I make] [colour] = 0.00002
[Did I make] [pencil] = 0.00001
[Did I make] [flower] = 0.00000
[We want a candidate] [with] = 0.03209
[We want a candidate] [that] = 0.02145
[We want a candidate] [experience] = 0.00097
[We want a candidate] [which] = 0.00094
[We want a candidate] [more] = 0.00010
[We want a candidate] [less] = 0.00007
[We want a candidate] [school] = 0.00003
[This is the definitive guide to the] [the] = 0.00089
[This is the definitive guide to the] [complete] = 0.00047
[This is the definitive guide to the] [sentence] = 0.00006
[This is the definitive guide to the] [rapper] = 0.00001
[This is the definitive guide to the] [illustrated] = 0.00001
[This is the definitive guide to the] [extravagant] = 0.00000
[This is the definitive guide to the] [wrapper] = 0.00000
[This is the definitive guide to the] [miniscule] = 0.00000
[Please can you] [check] = 0.00502
[Please can you] [confirm] = 0.00488
[Please can you] [cease] = 0.00002
[Please can you] [cradle] = 0.00000
[Please can you] [laptop] = 0.00000
[Please can you] [envelope] = 0.00000
[Please can you] [options] = 0.00000
[Please can you] [cordon] = 0.00000
[Please can you] [corolla] = 0.00000
[I think] [I've] = 0.00087
[I think] [over] = 0.00051
[I think] [ice] = 0.00001
[I think] [Oct] = 0.00000
[Please] [can] = 0.00428
[Please] [cab] = 0.00000
[I've scheduled this] [meeting] = 0.00077
[I've scheduled this] [messing] = 0.00000
这些示例展示了词在句子中的前面完成的概率。候选词按概率降序排列。
由于 Transformer 模型正逐渐取代 LSTM 和 RNN 模型用于基于序列的任务,让我们看看一个用于相同目标的 Transformer 模型会是什么样的。
Transformer 模型
基于 Transformer 的模型是一种非常流行的架构,用于训练语言模型以预测句子中的下一个词。我们将使用的具体技术是因果注意力机制。我们将仅使用因果注意力训练PyTorch 中的 Transformer 编码器层。因果注意力意味着我们允许序列中的每个标记仅查看它之前的标记。这类似于单向 LSTM 层在仅向前训练时所使用的信息。

我们将看到的 Transformer 模型直接基于 PyTorch 中的 nn.TransformerEncoder 和 nn.TransformerEncoderLayer。
import math
def generate_src_mask(sz, device):
return torch.triu(torch.full((sz, sz), True, device=device), diagonal=1)
#
class PositionalEmbedding(nn.Module):
def __init__(self, sequence_length, embed_dim):
super().__init__()
self.sqrt_embed_dim = math.sqrt(embed_dim)
self.pos_embed = nn.Parameter(torch.empty((1, sequence_length, embed_dim)))
nn.init.uniform_(self.pos_embed, -1.0, 1.0)
#
def forward(self, x):
return x * self.sqrt_embed_dim + self.pos_embed[:,:x.size(1)]
#
#
class WordPredictionTransformerModel(nn.Module):
def __init__(self, sequence_length, num_embed, embed_dim, pad_idx, num_heads, num_layers, output_dim, dropout, norm_first, activation):
super().__init__()
self.vocab_size = num_embed
self.sequence_length = sequence_length
self.embed_dim = embed_dim
self.sqrt_embed_dim = math.sqrt(embed_dim)
self.embed = nn.Sequential(
nn.Embedding(num_embed, embed_dim, pad_idx),
PositionalEmbedding(sequence_length, embed_dim),
nn.LayerNorm(embed_dim),
nn.Dropout(p=0.1),
)
encoder_layer = nn.TransformerEncoderLayer(
d_model=embed_dim, nhead=num_heads, dropout=dropout, batch_first=True, norm_first=norm_first, activation=activation,
)
self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
self.fc = nn.Sequential(
nn.Linear(embed_dim, embed_dim * 4),
nn.LayerNorm(embed_dim * 4),
nn.LeakyReLU(),
nn.Dropout(p=dropout),
nn.Linear(embed_dim * 4, output_dim),
)
#
def forward(self, x):
src_attention_mask = generate_src_mask(x.size(1), x.device)
x = self.embed(x)
x = self.encoder(x, is_causal=True, mask=src_attention_mask)
x = self.fc(x)
x = x.permute(0, 2, 1)
return x
#
#
我们可以用这个模型代替之前使用的 LSTM 模型,因为它的 API 是兼容的。这个模型在训练相同数量的数据时需要更长的时间,但性能相当。
Transformer 模型更适合长序列。在我们的例子中,序列长度为 256。完成下一个单词所需的大部分上下文通常是局部的,因此我们这里并不需要 Transformers 的强大能力。
结论
我们看到如何使用基于 LSTM(RNN)和 Transformer 模型的深度学习技术解决非常实际的 NLP 问题。并非所有语言任务都需要使用拥有数十亿参数的模型。对于需要建模语言本身而不是记忆大量信息的专业应用,可以使用更小的模型,这些模型比我们现在常见的大型语言模型更容易部署和更高效。
除了第一张图片外,所有的图片都是由作者创建的。
PageRank 的可视化解释
了解 Google 搜索引擎如何根据链接结构对文档进行排名
·
关注 发表在 Towards Data Science ·14 分钟阅读·2023 年 8 月 9 日
--
排名是机器学习中的一个重要问题。给定一组文档,目标是根据特定标准对它们进行排序。排名在信息检索系统中广泛使用,用于排序搜索结果,或在推荐系统中筛选出可能对特定用户感兴趣的内容。
根据具体问题和目标,存在大量的排名算法。我们将在本文中研究的算法名为PageRank。它的主要目标是通过利用关于连接性的信息对一组文档(网页)进行排名。分配给每个网页的排名表示它的重要性:排名越高,重要性越高。该算法基于两个假设,我们将在下一节中讨论。
假设
我们可以通过做出两个假设来定义网页的“重要性”一词。
如果有许多其他网页指向某个网页,那么该网页的重要性就会很高。
假设我们有一篇热门的研究论文以及许多其他文章通过引用或结果链接到它。主要是,这使得给这篇文章赋予较大重要性是有意义的。另一方面,如果有一个未知的网页没有其他资源的链接,似乎将低重要性分配给该页面是合乎逻辑的。
实际上,我们还应该关注入站链接的质量。
一个网页的重要性与指向它的网页的重要性成正比。
如果一个页面最初被维基百科上的高质量文章引用,那么这样的链接应该具有更大的权重。相反,当一个完全不知名的资源指向另一个网页时,它通常不会有高的重要性。

来自官方论文的 PageRank 算法的重要性分布示例。得分被标准化为总和为 100。具有 38.4 值的节点由于有大量其他节点指向它而具有如此高的重要性。另一方面,重要性为 34.3 的节点只有一个入站链接,但由于其唯一的输入链接来自另一个有影响力的节点,它的重要性仍然相对较高。重要性为 1.6 的节点没有任何入站链接。
正式定义
假设一个节点的重要性等于所有入站链接权重的总和。
想象一个重要性为rᵢ的节点i,它有k 个出站链接。我们如何确定每个链接的权重?最直接的方法是将节点的重要性平均分配给所有出站链接。这样,每个出站链接将获得rᵢ / k的权重。

节点排名计算示例

节点的排名等于入站节点的排名总和除以它们的总出度。
给定一个 n 个网页的图,我们可以创建一个 n 个线性方程的系统来找到图的权重。然而,这样的系统可能有无限多个解。这就是为什么我们应该添加另一个约束条件以强加唯一解。顺便说一下,PageRank 添加了归一化条件,即所有节点的重要性之和等于 1。

寻找描述图结构的线性方程组的解
我们已经提出了一个解决方案,但它不可扩展。即使使用高斯消元法,我们的复杂度也达到了 O(n³)。考虑到分析的网页数量 n 可以达到数十亿,我们需要想出一种更高效的方法。
首先,让我们简化表示法。为此,我们引入了邻接方阵 G,它将包含每对链接网页的链接权重(如果两个网页没有链接,我们将在相应的矩阵元素中放置 0)。更正式地:

矩阵元素 G[j][i] 的定义
矩阵 G 被称为随机矩阵,因为它的每一列的总和为 1。
接下来,我们定义排名向量 r,其 i -th 元素等于页面 i 的排名(重要性)。该向量所有元素的总和也等于 1。我们的最终目标是找到这个向量 r 的值。
PageRank 方程
让我们看看如果将矩阵 G 乘以向量 r 会发生什么。根据上一节的图示,我们可以看到结果仍然是相同的向量 r!

将矩阵 G 乘以向量 r 再次得到向量 r
为什么会这样?这只是巧合吗?请记住,矩阵 G 的 i -th 行包含所有输入链接到页面 i 的权重。当我们将 i -th 行的 j -th 元素乘以 r[j] 时,我们实际上得到的是组件 rj / d[j]out —— 从节点 j 流向 i 的重要性。如果节点 i 和 j 之间没有链接,则相应的组件设置为 0。逻辑上,i -th 行与向量 r 的乘积的最终结果将等于从图的任何连接节点流向节点 i 的所有重要性的总和。根据定义,这个值等于节点 i 的排名。一般来说,我们可以写出以下方程:

PageRank 方程
因此,我们的目标是找到一个向量 r,使其在与输入矩阵 G 相乘时保持不变。
特征向量
我们可以通过回顾线性代数中关于特征向量的理论来找到上述方程的解。给定一个矩阵 A,如果存在一个数 α 使得下列方程成立,则向量 v 被称为特征向量:

特征值定义
数字 α 被称为 特征值。我们可以注意到,PageRank 方程对应于特征值方程,其中 A = G, v = r 和 α = 1。通常,任何方阵都有多个特征值和特征向量,但由于我们的矩阵 G 是随机的,理论上它的最大特征值等于 1。
幂迭代
寻找矩阵特征向量的最流行的方法之一是 幂迭代 方法。它包括用一些值(我们将使用 1 / n,其中 n 是网页数量)初始化初始向量 r,然后不断计算 G * r 的值,并将该值重新分配给 r。如果在任何迭代中,向量 r 和 G * r 之间的距离小于某个阈值 ε,我们就停止算法,因为它已经成功收敛。

PageRank 算法
在上述示例中,我们可以看到,通过将 ε 设置为 0.0005,算法在仅 9 次迭代中正确收敛:

显然,这只是一个玩具示例,但在实际中,这种方法对于更多变量也能很好地工作。
随机游走
想象一个游览者(行走者)在时刻 t 位于图的任何节点上。我们用 p(t) 表示一个向量,其中 i 位置的分量等于游览者在时刻 t 出现在节点 i 的概率。然后,游览者随机(以相等的概率)选择另一个链接到当前节点的节点,并在时刻 t + 1 移动到那里。最终,我们希望找到时刻 t + 1 的分布向量 p(t + 1)。

游览者的随机游走
我们可以注意到,游览者在时刻 t + 1 出现于节点 i 的概率是游览者之前在相邻节点 j 的概率总和(所有链接到 i 的节点)乘以从节点 j 移动到 i 的概率。
-
我们已经知道游览者在时刻 t 出现在节点 j 的概率:p(t)[j]。
-
从节点 j 到 i 的移动概率等于 G[j][i]。
通过汇总这些概率,我们得到 p(t + 1)[i] 的值。为了找到所有图节点的 p(t + 1) 值,我们可以将相同的方程写成矩阵形式:

这个方程的形式与我们之前为 PageRank 得到的完全相同!这意味着这两个问题有相同的解决方案和解释。
在某个时刻,分布向量 p(t) 会收敛:p(t + 1) = M * p(t) = p(t)。在这种情况下,收敛后的向量 p(t) 被称为 平稳分布。在所有后续时刻,驻留在任何给定节点的概率不会改变。
节点的 PageRank 得分等于游览者通过随机游走图后,未来会出现在该节点的概率。
收敛
描述的图遍历过程通常被称为“马尔可夫链”。在马尔可夫链理论中存在一个定理,指出:
在图结构的某些条件下,稳态分布是唯一的,并且可以通过任意初始概率分布在 t = 0 时到达。
在以下部分中,我们将更深入地探讨实现唯一收敛所需满足的条件。结果表明,并非所有图都能实现唯一收敛。
原则上,我们希望避免两种情况。
死胡同
没有出链接的节点称为死胡同。这种节点的问题在于它们会使总权重从网络中泄漏。以下是一个示例:

死胡同问题。在时刻 t = 2,权重泄漏。在时刻 t = 3,排名向量收敛。
蜘蛛陷阱
如果一组节点没有指向该组外其他节点的出链接,则该组节点形成蜘蛛陷阱。基本上,一旦进入这些节点,就无法离开这个节点组。蜘蛛陷阱会导致以下两个问题:
-
算法从不收敛。
-
形成蜘蛛陷阱的节点组吸收了所有图的权重。结果,这些节点的权重非常高,而其他节点的权重为 0。
第一个问题如下面的图所示:

蜘蛛陷阱问题。从时刻 t = 0 开始,1 和 0 的排名在两个节点之间无限交替。结果,算法从不收敛。
权重的吸收在下图中展示。虽然在下面的玩具示例中可能看起来不是一个严重的问题,但想象一个有数百万网页的网络,其中几个网页形成了蜘蛛陷阱。因此,这几个页面将分配所有可用的权重,而所有其他网页的权重将被设置为 0。显然,这不是我们在现实中通常期望的情况。

节点 b 和 d 形成了一个蜘蛛陷阱。结果,在时刻 t = 18 时,它们已经吸收了所有的权重,而其他节点的权重为零。从此时开始,权重在节点 b 和 d 之间交替,使得算法发散。
传送
Google 提出的一个解决方案是在每次移动前添加以下条件:
-
以概率 β,移动到另一个链接的节点。
-
以概率 (1 — β),通过传送移动到一个随机节点。
参数 β 被称为 衰减因子。原始 PageRank 算法的作者建议选择 β = 0.85,这意味着平均来说,冲浪者在 5 次过渡后会随机跳到另一个节点。这个想法是,如果冲浪者陷入了蜘蛛陷阱,那么经过一段时间,它最终会通过传送门离开那里。
下图显示了传送门如何帮助处理蜘蛛陷阱问题。如果冲浪者走到节点 c,那么它将永远停留在那里。引入传送门(蓝线)有助于消除这个问题,确保在一段时间后,冲浪者将不得不移动到另一个随机节点。

传送门(蓝线)消除了蜘蛛陷阱问题
然而,对于死胡同节点,我们需要稍微修改方法。从上述一个例子中,我们知道死胡同会导致图中的重要性泄漏。这种现象可以在幂迭代方法中观察到,当秩向量因为初始矩阵 G 中的零列而变成全零。最终,我们可以做的是:
每当冲浪者到达一个死胡同节点时,它应该立即跳到图中的一个随机节点(概率相等)。
实际上,我们可以修改初始矩阵 G 来满足这一声明:我们只需将所有死胡同节点的列中的所有元素的零替换为 1 / n。下面的示例演示了这一原理。

节点 c 是一个死胡同节点,在矩阵 G 中对应一列全零的列。将 n = 3 个传送门从 c 添加到图中的所有节点,会使从 c 到任何节点的移动概率 p = 1 / 3。为了考虑这一点,我们用 1 / 3 填充矩阵 G 中与节点 c 对应的列。
我们可以注意到,添加传送门后,所有矩阵列的和现在等于 1。换句话说,矩阵 G 变成了随机链。这是一个重要的属性,我们将在后面使用。
收敛条件
马尔可夫链理论中存在一个关键定理定义了收敛条件。
对于任何起始向量,转移矩阵 G 会收敛到一个唯一的正定平稳分布向量 r,如果对应于 G 的链是随机的、非周期性的和不可约的。
让我们回顾一下这个定理中的最后三个属性,并检查引入的传送门是否解决了上述问题。
一个链 G 被称为随机链,如果其每一列的和等于 1。
如上所述,将传送门添加到死胡同节点消除了矩阵中的零列,使所有列的和等于 1。这个条件已经满足。
一个链 G 被称为周期链,如果存在一个数字 k > 1,使得任何一对节点之间的路径长度总是 k 的倍数。否则,链被称为非周期链。
这个条件意味着返回到同一状态的次数必须是 k 的倍数。在非周期性的情况下,返回会在不规则的时间发生。基本上,这个条件涉及到蜘蛛陷阱问题。由于我们已经通过添加传送门解决了蜘蛛陷阱问题,因此链 G 是非周期的。
如果从任意一个节点到任何其他节点的过渡概率始终大于 0,则链 G 被称为不可约的。
这个条件意味着任意两个节点之间总是存在一个链接,因此不可能卡在任何一个节点上。换句话说,矩阵 G 需要包含所有非零元素。我们将在下一节中看到如何通过连接图中的所有节点来满足这个条件。
修改算法
Google 提出的 PageRank 算法以初始矩阵 G 为基础,通过将死胡同的传送门添加到其他节点来调整它。这确保了随机性。为了保证非周期性和不可约性,它然后对每个节点添加之前描述的条件:
-
以 β 的概率,移动到另一个链接节点。
-
以 (1 — β) 的概率,移动到一个随机节点。
在数学上,这导致每个节点的以下等级方程:

PageRank 的向量方程
我们可以将这个方程转换成矩阵形式:

Google 的 PageRank 矩阵方程

矩阵 R 必须满足唯一平稳分布 r 存在的必要条件,而这个分布需要被找到。
让我们绘制修改后的图和上面某个示例的相应转移矩阵 R:

从原始链接矩阵 G 和传送门矩阵组成的矩阵 R。在这个例子中 β = 0.9。
提高效率
我们唯一剩下的问题是如何存储转移矩阵 R。记住 R 是一个大小为 n x n 的方阵,其中 n 是网页的数量。目前,Google 拥有超过 250 亿个网页!矩阵 R 不含任何零,因此它是密集的,这意味着我们必须完全存储它。假设每个矩阵元素需要 4 字节存储。存储矩阵 R 所需的总内存大小等于 (25 * 10⁹)² * 4(字节)~ 3 * 10²¹(字节)。这是一个巨大的内存大小!我们需要想出另一种方法,将其至少减少几个数量级。
首先,我们可以简单地注意到,添加传送门等同于将初始矩阵 G 的元素减少 (1 — β)% 并均匀分配到每个节点。牢记这一点,我们可以将 PageRank 的矩阵方程转换成另一种格式:

转换 PageRank 方程
让我们看一下最后得到的方程。G是初始链接矩阵,大多数元素都等于 0。为什么会这样?实际上,如果你查看任何网页,它可能只包含最多几十个指向其他网页的链接。考虑到有超过 250 亿个网页,我们得到的总链接数相对于网页数量是极其少的。因此,G中有很多零,G是稀疏的。
存储稀疏矩阵所需的内存远远少于密集矩阵。假设每个网页平均链接到其他 40 个网页。现在存储矩阵 G 所需的总字节数变为25 * 10⁹ * 40(字节)* = 10¹²(字节) = 1(TB)。结果是,我们只需要 1TB 来存储G*。与之前相比,这是一个巨大的改进!
实际上,在每次迭代中,我们只需计算矩阵G与向量r的乘积,将其乘以β,并在结果向量的每个元素中加上常数(1 — β) / n。

结果 PageRank 方程
还要记住,如果初始链G包含死节点,那么每次迭代时向量r的总和将小于 1。为了解决这个问题,只需对其进行重新归一化,使得所有向量组件的总和为 1。
完整算法
在下图中,我们可以看到 PageRank 算法的完整版本。在每次迭代中,排名更新分为两个阶段。第一阶段仅根据初始矩阵G进行更新。然后我们将排名向量的组件汇总到变量s中。这样,(1 — s)的值就是单个节点的总输入排名减少的值。为了弥补这一点,在第二阶段,我们考虑了传送,并将它们从一个节点添加到所有节点中,值为(1 — s) / n。

完整的 PageRank 算法
结论
在本文中,我们探讨了 PageRank 算法的不同公式,以最终得出其优化版本。尽管存在并发展了其他用于排名搜索结果的方法,PageRank 仍然是 Google 搜索引擎背后最有效的算法之一。
参考文献
本文的逻辑结构基于斯坦福大学关于大图的讲座。
除非另有说明,否则所有图片均由作者提供
大型语言模型和向量数据库在新闻推荐中的应用

图片由 Roman Kraft 提供,来自 Unsplash
将 LLMs 应用到生产环境中,使用 Sentence Transformers 和 Qdrant
·
关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 12 月 14 日
--
大型语言模型(LLMs)随着最近生成 AI 工具如 Chat-GPT、Bard 等的发布,引起了机器学习社区的全球关注。这些解决方案的核心思想之一是计算非结构化数据(如文本和图像)的数值表示,并在这些表示之间找到相似性。
然而,将所有这些概念应用到生产环境中有其自身的一系列机器学习工程挑战:
-
如何快速生成这些表示?
-
如何将它们存储在合适的数据库中?
-
如何在生产环境中快速计算相似性?
在本文中,我介绍了两种旨在解决这些问题的开源解决方案:
-
Sentence Transformers [1]:一种基于文本信息的嵌入生成技术;
-
Qdrant:一个能够存储嵌入并提供易于查询接口的向量数据库。
这些工具应用于 NPR [2],一个新闻门户推荐数据集(在 Kaggle 上公开可用),该数据集旨在支持学术界开发推荐算法。在文章结束时,你将看到如何:
-
使用 Sentence Transformers 生成新闻嵌入
-
使用 Qdrant 存储嵌入
-
查询嵌入以推荐新闻文章
本文的所有代码均可在 Github 上获取。
1. 使用 Sentence Transformers 生成嵌入
首先,我们需要找到一种将输入数据转换为向量的方法,我们称之为嵌入(如果你想深入了解嵌入的概念,我推荐阅读 Boykis 的文章 What Are Embeddings? [3])。
那么让我们来看看我们可以使用 NPR 数据集处理什么样的数据:
import pandas as pd
df = pd.read_parquet("articles.parquet")
df.tail()

来自 NPR 的示例数据(作者生成的图像)
我们可以看到 NPR 拥有一些有趣的文本数据,如文章的 标题 和 正文 内容。我们可以在嵌入生成过程中使用它们,如下图所示:

嵌入生成过程(作者提供的图像)
一旦我们定义了输入数据的文本特征,我们需要建立一个嵌入模型来生成我们的数值表示。幸运的是,我们可以在 HuggingFace 等网站上寻找适合特定语言或任务的预训练模型。在我们的示例中,我们可以使用 neuralmind/bert-base-portuguese-cased 模型,该模型为以下任务在巴西葡萄牙语上进行过训练:
-
命名实体识别
-
句子文本相似性
-
识别文本蕴含
从代码方面来看,我们如何翻译嵌入生成过程:
from sentence_transformers import SentenceTransformer
model_name = "neuralmind/bert-base-portuguese-cased"
encoder = SentenceTransformer(model_name_or_path=model_name)
title = """
Paraguaios vão às urnas neste domingo (30) para escolher novo presidente
"""
sentence = title
sentence_embedding = encoder.encode(sentence)
print (sentence_embedding)
# output: np.array([-0.2875876, 0.0356041, 0.31462672, 0.06252239, ...])
所以,给定一个示例输入数据,我们可以将title和tags内容连接成一个文本,并传递给编码器生成文本嵌入。
我们可以对 NPR 数据集中所有其他文章应用相同的过程:
def generate_item_sentence(item: pd.Series, text_columns=["title"]) -> str:
return ' '.join([item[column] for column in text_columns])
df["sentence"] = df.apply(generate_item_sentence, axis=1)
df["sentence_embedding"] = df["sentence"].apply(encoder.encode)
注意:请注意,根据你机器的处理能力,这个过程可能会花费更多时间。
一旦我们获得了所有新闻文章的嵌入,我们需要定义一种策略来存储它们。
2. 存储嵌入
由于生成嵌入可能是一个耗费资源的过程,我们可以使用向量数据库来存储这些嵌入,并根据不同的策略执行查询。
有几种向量数据库软件可以实现这个任务,但本文将使用Qdrant,这是一款开源解决方案,提供了适用于流行编程语言的 API,如Python、Go和Typescript。要对这些向量数据库进行更好的比较,请查看这篇文章[4]。
设置 Qdrant
为了处理所有 Qdrant 操作,我们需要创建一个指向向量数据库的客户端对象。Qdrant 允许你创建一个免费的服务层级来测试与数据库的远程连接,但为了简便起见,我将创建并本地保存数据库:
from qdrant_client import QdrantClient
client = QdrantClient(path="./qdrant_data")
一旦建立了这个连接,我们可以在数据库中创建一个集合,以存储新闻文章的嵌入:
from qdrant_client import models
from qdrant_client.http.models import Distance, VectorParams
client.create_collection(
collection_name = "news-articles",
vectors_config = models.VectorParams(
size = encoder.get_sentence_embedding_dimension(),
distance = models.Distance.COSINE,
),
)
print (client.get_collections())
# output: CollectionsResponse(collections=[CollectionDescription(name='news-articles')])
请注意,向量配置参数用于创建集合。这些参数告诉 Qdrant 向量的一些属性,如它们的size和比较向量时使用的distance度量(我将使用余弦相似度,但你也可以使用其他策略,如内积或欧氏距离)。
生成向量点
在最终填充数据库之前,我们需要创建适当的对象以进行上传。在 Qdrant 中,向量可以使用PointStruct类进行存储,你可以使用这个类定义以下属性:
-
id:向量的 ID(在 NPR 的情况下,是newsId)
-
vector:表示向量的 1 维数组(由嵌入模型生成)
-
payload:包含任何其他相关元数据的字典,这些数据后来可以用于在集合中查询向量(在 NPR 的情况下,文章的title、body和tags)
from qdrant_client.http.models import PointStruct
metadata_columns = df.drop(["newsId", "sentence", "sentence_embedding"], axis=1).columns
def create_vector_point(item:pd.Series) -> PointStruct:
"""Turn vectors into PointStruct"""
return PointStruct(
id = item["newsId"],
vector = item["sentence_embedding"].tolist(),
payload = {
field: item[field]
for field in metadata_columns
if (str(item[field]) not in ['None', 'nan'])
}
)
points = df.apply(create_vector_point, axis=1).tolist()
上传向量
最后,在所有项目都转换为点结构后,我们可以将它们分批上传到数据库:
CHUNK_SIZE = 500
n_chunks = np.ceil(len(points)/CHUNK_SIZE)
for i, points_chunk in enumerate(np.array_split(points, n_chunks)):
client.upsert(
collection_name="news-articles",
wait=True,
points=points_chunk.tolist()
)
3. 查询向量
现在,集合已经填充了向量,我们可以开始查询数据库。我们可以用多种方式输入信息来查询数据库,但我认为有 2 种非常有用的输入方式:
-
输入文本
-
输入向量 ID
3.1 使用输入向量查询向量
假设我们建立了这个向量数据库以供搜索引擎使用。在这种情况下,我们期望用户输入的是一个文本输入,我们必须返回最相关的项目。
由于向量数据库中的所有操作都是用……向量来完成的,因此我们首先需要将用户输入的文本转换为向量,以便我们可以根据该输入找到相似的项目。回顾一下,我们使用了句子变换器将文本数据编码成嵌入,因此我们可以使用同样的编码器为用户的输入文本生成数值表示。
由于 NPR 包含新闻文章,我们假设用户输入了“唐纳德·特朗普”以了解美国选举:
query_text = "Donald Trump"
query_vector = encoder.encode(query_text).tolist()
print (query_vector)
# output: [-0.048, -0.120, 0.695, ...]
一旦计算出输入查询向量,我们可以在集合中搜索最接近的向量,并定义我们希望从这些向量中获得什么样的输出,如其newsId、标题和主题:
from qdrant_client.models import Filter
from qdrant_client.http import models
client.search(
collection_name="news-articles",
query_vector=query_vector,
with_payload=["newsId", "title", "topics"],
query_filter=None
)
注意:默认情况下,Qdrant 使用近似最近邻搜索快速扫描嵌入,但你也可以进行全面扫描并获取准确的最近邻——只是请注意,这是一项成本更高的操作。
执行此操作后,以下是生成的输出标题(为更好理解已翻译成英文):
-
输入句子:唐纳德·特朗普
-
输出 1:巴拉圭人在本周日(30 日)去投票选举新总统
-
输出 2:选民表示拜登和特朗普不应在 2024 年竞选,路透/益普索民调显示
-
输出 3:记者指控特朗普在 1990 年代对她进行性虐待
-
输出 4:迈克·彭斯,特朗普的前副总统,法院作证可能使前总统陷入困境
看起来除了带来与特朗普本人相关的新闻外,嵌入模型还成功地表示了与总统选举相关的主题。注意在第一个输出中,除了总统选举,没有直接提到输入词“唐纳德·特朗普”。
此外,我遗漏了一个query_filter参数。如果你想指定输出必须满足某些条件,这个工具非常有用。例如,在新闻门户中,通常重要的是仅筛选出最新的文章(例如过去 7 天内)。因此,你可以查询符合最低发布时间戳的新闻文章。
注意: 在新闻推荐的背景下,有多个值得关注的方面,如公平性和多样性。这是一个开放的话题,但如果你对此领域感兴趣,可以查看NORMalize Workshop上的文章。
3.2 使用输入向量 ID 查询向量
最后,我们可以让向量数据库“推荐”更接近某些期望的向量 ID,但远离不希望的向量 ID。期望的和不希望的 ID 分别称为正例和负例,它们被视为推荐的种子。
例如,假设我们有以下正 ID:
seed_id = '8bc22460-532c-449b-ad71-28dd86790ca2'
# title (translated): 'Learn why Joe Biden launched his bid for re-election this Tuesday'
然后我们可以请求类似于这个示例的项目:
client.recommend(
collection_name="news-articles",
positive=[seed_id],
negative=None,
with_payload=["newsId", "title", "topics"]
)
在运行此操作后,以下是翻译后的输出标题:
-
输入项: 了解乔·拜登为什么在本周二宣布竞选连任
-
输出 1: 拜登宣布他将竞选连任
-
输出 2: 美国:拜登竞选连任的 4 个原因
-
输出 3: 选民表示拜登和特朗普不应该在 2024 年参选,路透社/艾普索斯民调显示
-
输出 4: 拜登顾问的失言引发对选举后可能出现第二届政府的疑虑
结论
本文展示了如何将 LLM 和向量数据库结合起来以提供推荐。特别是使用了句子变换器来生成来自 NPR 数据集的文本新闻文章的数值表示(嵌入)。一旦这些嵌入被计算出来,它们可以填充像 Qdrant 这样的向量数据库,从而根据多种策略方便地查询向量。
在本文中的示例之后可以进行大量改进,例如:
-
测试其他嵌入模型
-
测试其他距离度量
-
测试其他向量数据库
-
使用基于编译的编程语言如 Go 来提高性能
-
创建一个 API 以提供推荐
换句话说,可能会出现许多想法来改善 LLM 的推荐机器学习工程。因此,如果你想分享你对这些改进的想法,请随时通过这里给我发消息 😃
关于我
我是Globo的高级数据科学家,这是一家巴西媒体技术公司。作为公司推荐团队的一员,我身边有一个令人惊叹和才华横溢的团队,他们付出了很多努力,通过数字产品如G1、GE、Globoplay等为数百万用户提供个性化内容。如果没有他们不可或缺的知识,这篇文章是不可能完成的。
参考文献
[1] N. reimers 和 I. Gurevych, Sentence-BERT: 使用 Siamese BERT 网络的句子嵌入 (2019), 计算语言学协会。
[2] J. Pinho, J. Silva 和 L. Figueiredo, NPR: 新闻门户推荐数据集 (2023), ACM 推荐系统会议
[3] V. Boykis, 什么是嵌入?,个人博客
[4] M. Ali, 前 5 大向量数据库 (2023), DataCamp 博客
大型语言模型作为零-shot 标注者
原文:
towardsdatascience.com/large-language-models-as-zero-shot-labelers-d26aa2642c88
使用 LLMs 为监督模型获取标签
·发布于 Towards Data Science ·5 分钟阅读·2023 年 3 月 20 日
--

图片由 h heyerlein 提供,来源于 Unsplash
介绍
数据标注是构建监督式机器学习模型中的一个关键步骤,因为标签的数量和质量通常是决定模型性能的主要因素。
然而,数据标注可能非常耗时且昂贵,特别是对于涉及领域知识或阅读大量数据的复杂任务。
近年来,大型语言模型(LLMs)作为获取文本数据标签的强大解决方案出现。通过零-shot 学习,我们可以仅使用 LLM 的输出来获取未标记数据的标签,而不必让人类来获取这些标签。这可以显著降低获取标签的成本,并使过程变得更加可扩展。
在这篇文章中,我们将进一步探讨零-shot 学习的概念以及 LLMs 如何用于这一目的。
什么是零-shot 学习?

图片由 Alex Knight 提供,来源于 Unsplash
零-shot 学习(ZSL)是机器学习中的一种问题设置,其中模型被要求解决一个它没有经过训练的预测任务。这通常涉及将数据识别或分类为训练时未明确见过的概念。
在传统的监督学习中,这是不可能的,因为模型只能对其训练过的任务(即有标签的任务)进行预测。然而,在 ZSL 范式中,模型可以推广到任意未见的任务,并在合理的水平上表现。请注意,在大多数情况下,针对特定任务训练的监督模型仍将优于使用 ZSL 的模型,因此 ZSL 更常在监督标签尚不可用时使用。
ZSL 最有前景的应用之一是在数据标记中,它可以显著降低获取标签的成本。如果一个模型能够自动将数据分类到类别中,而无需在该任务上进行训练,它可以用于为下游监督模型生成标签。这些标签可以用于引导一个监督模型,这种方式类似于主动学习或人机交互式机器学习。
大语言模型的零样本学习

由Arseny Togulev拍摄,图片来源于Unsplash
像 GPT-3 这样的 LLM 是 ZSL 的强大工具,因为它们的稳健预训练过程使它们对自然语言有全面的理解,这种理解并不依赖于某个特定监督任务的标签。
嵌入搜索
LLM 的上下文嵌入能够捕捉给定文本中的语义概念,这使得它们在 ZSL 中非常有用。
像句子转换器这样的库提供了经过训练的 LLM,这些 LLM 使得语义相似的文本具有彼此距离很小的嵌入。
[## GitHub - UKPLab/sentence-transformers: 多语言句子与图像嵌入与 BERT
这个框架提供了一种计算句子、段落和图像的稠密向量表示的简单方法……
github.com](https://github.com/UKPLab/sentence-transformers?source=post_page-----d26aa2642c88--------------------------------)
如果我们有一些标记数据的嵌入,我们可以使用最近邻搜索来找到具有相似嵌入的未标记数据。
如果两个文本在嵌入空间中非常接近,那么它们很可能具有相同的标签。
上下文学习
上下文学习是 LLM 的一种新兴能力,它使它们能够仅通过查看输入-输出对来学习解决新任务。模型无需更新参数即可学习任意新任务。
在这篇文章中,我们提供了一个关于大语言模型如 GPT-3 的上下文学习的贝叶斯推理框架。
ai.stanford.edu](http://ai.stanford.edu/blog/understanding-incontext/?source=post_page-----d26aa2642c88--------------------------------)
我们可以利用这种能力,通过仅提供几个输入-输出对来获得标签,并让模型为未标记的数据点提供标签。
在零样本学习的背景下,这意味着我们可以提供一些手工制作的文本示例及其相关的监督标签,让模型实时学习标注功能。

在这个简单的案例中,我们训练 ChatGPT 通过上下文学习来分类一个句子是否与青蛙相关。
生成模型
最近,生成 LLMs 中的对齐方法,如RLHF(来自人类反馈的强化学习)的进展,使得只需要求模型为你标注数据成为可能。
本文已翻译成中文简体。对翻译成其他语言感兴趣?请联系 nathan…
huggingface.co](https://huggingface.co/blog/rlhf?source=post_page-----d26aa2642c88--------------------------------)
如 ChatGPT 这样的模型能够通过简单地(用语言)回复所需标签来为输入数据提供标签。它们通过在如此大量的数据上进行预训练获得的丰富世界知识,使这些模型能够仅凭语义理解来解决新任务。
这个过程可以通过使用开源模型如FLAN-T5来自动化,只需要求模型仅用你的标签集中的项回应(例如,“回应‘是’或‘否’”),然后在询问模型标签后检查哪个选项的输出概率最高。

ChatGPT 不仅能提供标签,还能解释其获得该标签的逻辑。
结论
数据标注是监督机器学习中的关键步骤,但获取大量标注数据可能很昂贵。
使用零样本学习和 LLMs,我们可以显著降低标签获取的成本。
LLMs 在大量数据上进行预训练,编码了对世界信息的语义理解,使它们在任意未见任务上表现出色。这些模型可以高准确度地自动标注数据,使我们可以以低成本启动监督模型。
大语言模型:DeBERTa — 解码增强型 BERT 与解耦注意力
探索 Transformers 中注意力机制的高级版本
·
关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 11 月 28 日
--
介绍
近年来,BERT 已成为许多自然语言处理任务中的首选工具。它在处理、理解信息和构建高精度词嵌入方面的卓越能力达到了最先进的水平。
众所周知,BERT 基于从 Transformer 架构中衍生出的注意力机制。注意力是现在大多数大语言模型的关键组成部分。
## 大型语言模型:BERT — 基于 Transformer 的双向编码表示
了解 BERT 如何构建最先进的嵌入
towardsdatascience.com
然而,在机器学习领域,新的思想和方法不断发展。2021 年,BERT 类模型中出现了一种创新技术,称为“解耦注意力”。这一概念的实现催生了 DeBERTa——一种融合了解耦注意力的模型。虽然 DeBERTa 只引入了一对新的架构原则,但与其他大型模型相比,其在顶级 NLP 基准测试中的改进是显著的。
在本文中,我们将参考原始的 DeBERTa 论文,并覆盖理解其工作原理所需的所有细节。
1. 解耦注意力
在原始的 Transformer 块中,每个 token 都由一个单一的向量表示,该向量以逐元素嵌入和的形式包含关于 token 内容和位置的信息。这种方法的缺点是潜在的信息丢失:模型可能无法区分一个词本身还是它的位置对某个嵌入向量分量的重要性。

BERT 和 DeBERTa 中的嵌入构造。与将所有信息存储在一个向量中的方法不同,DeBERTa 使用两个独立的向量来存储词和位置的嵌入。
DeBERTa 提出了一个新颖的机制,其中相同的信息存储在两个不同的向量中。此外,注意力计算算法也被修改,以显式考虑 token 内容和位置之间的关系。例如,词汇 “research” 和 “paper” 在彼此接近时的相关性远大于它们在不同文本部分出现时的相关性。这个例子清楚地说明了为什么也需要考虑内容到位置的关系。
解耦注意力的引入需要修改注意力分数的计算。事实证明,这一过程非常简单。计算两个嵌入之间的交叉注意力分数,每个嵌入由两个向量组成,可以简单地分解为四个子向量的成对乘积之和:

计算两个嵌入向量之间的交叉注意力分数。
相同的方法可以推广到矩阵形式。从图中,我们可以观察到四种不同类型的矩阵(向量),每种矩阵代表内容和位置信息的某种组合:
-
内容到内容 矩阵;
-
内容到位置 矩阵;
-
位置到内容 矩阵;
-
位置到位置 矩阵。
可以观察到位置到位置的矩阵没有存储任何有价值的信息,因为它没有关于单词内容的细节。这就是为什么在解耦注意力中会忽略这个术语的原因。
对于其余三个术语,最终输出的注意力矩阵计算方式与原始 Transformer 相似。

DeBERTa 中输出解耦注意力的计算
尽管计算过程看起来类似,但有一对细微差别需要考虑。
从上面的图示中,我们可以注意到用于表示查询内容 Qc 和键位置 Krᵀ 矩阵与键内容 Kc 和查询位置 Qrᵀ 矩阵之间乘法的符号*与普通矩阵乘法符号x不同。实际上,这并非偶然,因为在 DeBERTa 中提到的矩阵对的乘法方式略有不同,以考虑到令牌的相对位置。
-
根据普通矩阵乘法规则,如果 C = A x B,则元素 C[i][j] 通过 A 的第 i 行与 B 的第 j 列的逐元素乘法来计算。
-
在 DeBERTa 的特殊情况下,如果 C = A * B,则 C[i][j] 计算为 A 的第 i 行与 B 的 δ(i, j) 列的乘积,其中 δ 表示索引 i 和 j 之间的相对距离函数,其定义如下:

索引 i 和 j 之间的相对距离定义。k 是一个超参数,用于控制最大可能的相对距离。图片由作者采用。
k 可以被视为一个超参数,用于控制索引 i 和 j 之间的最大可能相对距离。在 DeBERTa 中,k 被设置为 512。为了更好地理解公式,我们可以绘制一个热图,展示不同索引的 i 和 j 的相对距离(k = 6)。

例如,如果 k = 6,i = 15 和 j = 13,则 i 和 j 之间的相对距离 δ 等于 8。为了获得索引 i = 15 和 j = 13 的内容到位置评分,在查询内容 Qc 和键位置 Kr 矩阵的乘法中,Qc 的第 15 行应乘以 Krᵀ 的第 8 列。

内容到位置的评分计算用于令牌 i 和 j
然而,对于位置到内容的评分,算法的工作方式稍有不同:这次算法在矩阵乘法中使用的是 δ(j, i) 的值,而不是 δ(i, j)。正如论文的作者所解释的:“这是因为 对于给定的位置 i,位置到内容计算的是键内容在 j 的注意力权重相对于位置 i 的查询,因此相对距离是 δ(j, i)”。

位置到内容的得分计算对于令牌 i 和 j
δ(i, j) ≠ δ(j, i),即 δ 不是对称函数,这意味着 i 和 j 之间的距离与 j 和 i 之间的距离不同。
在应用 softmax 转换之前,注意力得分会被一个常数 √(3d) 除以,以便更稳定地训练。这个缩放因子不同于原始 Transformer 中使用的(√d)。这个 √3 倍的差异由 DeBERTa 注意力机制中 3 个矩阵的求和导致的较大幅度(而不是 Transformer 中的单个矩阵)所证明。
2. 增强的掩码解码器
解耦注意力仅考虑内容和相对位置。然而,没有关于绝对位置的信息被考虑,这可能在最终预测中扮演重要角色。DeBERTa 论文的作者给出了一个具体的例子:句子“a new store opened beside the new mall”被送入 BERT,掩盖的单词是“store”和“mall”用于预测。虽然掩盖的单词具有相似的含义和局部上下文(形容词“new”),但它们在句子中代表完全不同的角色。如果没有关于掩盖单词的起始和结束位置的完整信息,正确恢复原始句子变得更加困难。

来自 DeBERTa 论文 的文本示例。仅使用解耦注意力,模型无法正确恢复原始短语。
为了更好地理解这个问题,假设你知道实际上是商场先开了,商店在它之后开张。然后你需要填入句子 a new ___ store opened beside the new ___。作为一个英语使用者,你知道任何在 opened beside 后面的词语,从语法上看意味着它先开张。同时,任何在这些词之前的词则晚开张。因此,你自信地分别填入 store 和 mall。为什么对你来说这很容易做到?因为作为人类,你自然会考虑被掩盖单词的绝对位置。
现在假设你对掩盖单词的绝对位置一无所知。因此,你将无法使用关于 opened beside 构造周围的语法词序的提示。结果是,尽管有单词的语义含义和局部上下文,你仍然无法给出正确的答案。这对于模型而言是类似的情况,当它无法访问绝对位置时。
在语言中可以有许多类似的例子,这就是为什么将 绝对位置 纳入模型中至关重要。
在 BERT 中,输入嵌入考虑了绝对位置。谈到 DeBERTa,它在所有 Transformer 层之后但在应用 softmax 层之前纳入了绝对位置。实验表明,在所有 Transformer 层中捕捉相对位置,并且只有在引入绝对位置后,模型性能有所提升。研究人员表示,反向处理可能会阻止模型学习足够的相对位置信息。
架构
根据 论文,增强掩码解码器(EMD)有两个输入块:
-
H — 来自前一层 Transformer 的隐藏状态。
-
I — 解码所需的任何信息(例如,隐藏状态 H、绝对位置嵌入或来自前一层 EMD 的输出)。

DeBERTa 中的增强掩码解码器。图片由作者提供。
通常,一个模型中可以有多个 n EMD 块。如果是这样,它们会按照以下规则构建:
-
每个 EMD 层的输出是下一层 EMD 层的输入 I;
-
最后一层 EMD 层的输出被送入语言模型头。
对于 DeBERTa,EMD 层的数量设置为 n = 2,位置嵌入用于第一层 EMD 层中的 I。
NLP 中另一个常用技术是跨不同层共享权重,目的是减少模型复杂度(例如,ALBERT)。这一思想也在 DeBERTa 的 EMD 块中得到实现。
## 大型语言模型,ALBERT — 用于自监督学习的轻量级 BERT
理解 BERT 架构选择背后的基本技术,以产生紧凑且高效的模型
[towardsdatascience.com
当 I = H 且 n = 1 时,EMD 等同于 BERT 解码器层。
DeBERTa 设置
消融研究
实验表明,DeBERTa 中引入的所有组件(位置到内容的注意力、内容到位置的注意力和增强掩码解码器)都提高了性能。去除任何一个组件都会导致指标降低。
尺度不变的精细调优
此外,作者提出了一种新的对抗算法,称为“尺度不变精细调优”,以提高模型的泛化能力。其思想是对输入序列加入小的扰动,使模型对对抗样本更具弹性。在 DeBERTa 中,扰动应用于标准化的输入词嵌入。对于更大的精细调优 DeBERTa 模型,这种技术效果更佳。
DeBERTa 变体
DeBERTa 的论文展示了三种模型。它们之间的比较见下图。

DeBERTa 变体
数据
对于预训练,DeBERTa 的基础版和大型版使用了以下数据集的组合:
-
English Wikipedia + BookCorpus (16 GB)
-
OpenWebText(公开 Reddit 内容:38 GB)
-
Stories (31 GB)
数据去重后,结果数据集的大小减少到 78 GB。对于 DeBERTa 1.5B,作者使用了两倍以上的数据(160 GB),且词汇量达到了 128K。
相比之下,其他大型模型如 RoBERTa、XLNet 和 ELECTRA 在 160 GB 数据上进行预训练。同时,DeBERTa 在各种 NLP 任务中展现了与这些模型相当或更优的性能。
说到训练,DeBERTa 在每一步使用 2K 样本进行了一百万步的预训练。
Conclusion
我们已经深入探讨了 DeBERTa 架构的主要方面。凭借解耦的注意力机制和增强的掩码编码算法,DeBERTa 已成为许多数据科学家在 NLP 流水线中的极受欢迎的选择,也是许多 Kaggle 竞赛中的获胜要素。另一个关于 DeBERTa 的令人惊讶的事实是,它是首批在 SuperGLUE 基准测试中超越人类的 NLP 模型之一。这一单一证据足以得出结论:DeBERTa 将在 LLM 历史上长时间存在。
Resources
除非另有说明,否则所有图片均由作者提供
大型语言模型揭示了国家社会工作执照考试中的额外缺陷
变革的需求
·
关注 发表在 Towards Data Science ·14 分钟阅读·2023 年 4 月 11 日
--
图片来自作者通过 Midjourney 创建。
作为一名以数据为驱动的社会工作教授,我正在为人工智能技术对我们领域的变革性影响做准备。虽然人工智能不会取代社会工作者,但它将显著重塑研究、实践和教育。
普林斯顿大学经济学家埃德·费尔滕及其同事开发了一种独特的度量指标,称为 AI 职业暴露度。该度量通过将十种 AI 应用(如阅读理解、语言建模和翻译)与 52 种人类能力(包括口头理解和归纳推理)联系起来,突显了 AI 对特定职业的影响。该团队将这一指标应用于美国劳动部创建的职业信息网络数据库中的 800 多个职业,以确定大型语言模型对各个领域的潜在影响。费尔滕的完整报告可以在arXiv上找到。“高等教育社会工作教师”在所有职业中的 AI 暴露度排名第 11 位。AI 对该领域的影响将取决于社会工作领域如何迅速适应这一技术,并应对这些进步所带来的挑战。
生成性 AI 的潜在影响引起了我的关注,尤其是有报告显示 ChatGPT 在法律、商业和医学考试中表现出色。我和我的同事决定在社会工作背景下评估 ChatGPT,因此我们准备了全国社会工作执业考试的模拟测试。ChatGPT 在某些考试版本中轻松超越了合格门槛(更多内容见下文)。评估还揭示了考试中存在的重要有效性问题,超出了以往研究结果的范围。
我们的评估紧随社会工作委员会 2022 年报告之后,该组织负责管理考试,报告强调了按种族、年龄和主要语言划分的显著通过率差异。以下是一些硕士级考试的差异:

由作者根据 2022 年 ASWB 考试通过率分析重新制作的图表 — 最终报告。 “最终通过率”指的是在第一次尝试时通过考试,或在多次尝试后通过考试,直到获得合格分数为止。有关首次尝试通过率差异的更多信息,请参阅最终报告。
这些差异提出了重要的社会公正和伦理问题,因为社会工作执照对于进入劳动力市场至关重要。请注意,临床级和学士级考试也存在显著差异。
在这篇文章中,我通过直接比较四种语言模型:ChatGPT-3.5、ChatGPT-4、Bard 和 Bing,更新了我们最初的评估。这篇文章评估了所有模型在考试中的表现,但也——更重要的是——揭示了考试本身的额外有效性问题。这些有效性问题在现实世界中有严重的影响,因为它们削弱了不同群体考生的就业机会,主要基于种族、年龄和母语。
请注意,本文包含一些敏感内容,包括涉及性犯罪者的场景。我理解这些话题可能不适合所有观众,并鼓励读者在继续之前使用他们的判断。
原始评估背景
我们无法仅通过 ChatGPT-3.5 访问实际的执业许可考试。因此,我们使用了由考试开发者,即社会工作委员会(ASWB)构建的测试题库来准备考试模拟。我们的评估发现,ChatGPT-3.5 在硕士级别的考试中表现非常出色,正确回答了 80% 的问题。这个分数超过了约 70% 的及格线。ChatGPT-3.5 在本科和临床考试中也表现良好(分别为 76% 和 64%)。
对于那些不在该行业中的人,每道 ASWB 考试题都呈现一个与社会工作相关的场景,并附有四个多项选择答案。原始问题受版权保护,因此我不能直接分享它们。相反,我提供了一个由 ChatGPT-3.5 编写的示例。我给模型提供了问题的示例以及一个提示,以模仿测试题的写作风格、内容、长度和结构。

测试题是由 ChatGPT-3.5 根据社会工作委员会(www.aswb.org)出售的模拟测试题的风格、结构和内容生成的。截图由作者提供。
ChatGPT-3.5 的表现非常出色,因为我们使用了一个简单的提示,没有上下文或示例。我们要求模型阅读场景并选择最佳回答。实际上,ChatGPT-3.5 的表现被低估了,因为我们的评估假设 ASWB 提供的答案钥匙是绝对标准。然而,如ASWB 最近的通过率分析中所述,考试存在记录的缺陷和偏见。ChatGPT-3.5 对错误答案的某些解释非常有说服力,并且在某些情况下,优于 ASWB 答案钥匙上的解释。
我们最近在学术期刊《社会工作实践研究》中发布了我们的评估结果,您可以在这里访问。随着新型大语言模型(LLMs)的出现,我自然对它们的能力以及它们与 ChatGPT-3.5 的表现感到好奇。然而,社会工作领域的学术期刊无法跟上快速发展的技术格局,其内容往往需要昂贵的访问费用。幸运的是,《Toward Data Science (TDS)》解决了这两个问题。
大语言模型的对比
表现最好的模型是 ChatGPT-4,其准确率达到了 92%,相比于其前身 ChatGPT-3.5 的 80%有了显著提升。Bard 的得分足以通过考试。Bing 的低表现(68%)让人意外,考虑到它是基于 ChatGPT-4 语言模型构建的。

作者提供的图表。
这些结果不应被过度解读。正如我们在发布的报告中讨论的那样,我们对将 ASWB 考试的答案视为金标准有严重的保留意见。考试存在缺陷和偏见,包括使用未经实证支持的测试项目。我们测试的问题数量大约是考试中考生所遇到的问题的三分之一。因此,呈现的表现只是一个粗略估计。最后,用于评估的提示仅对 Bard 和 Bing 部分适用。让我们来探讨一下适用性这一最后的问题。
Bard 对四个问题的回答是:“我无法帮助这个问题,因为我只是一个语言模型。” 我将这些不回答标记为错误答案,导致其表现较低。两个问题涉及可能的敏感内容,包括儿童(即,童年自慰和可能接触到色情材料)。另外两个问题涉及婚姻争议和客户的去世。我再次测试了 Bard,给出的提示如下:
“请注意,这不是一个真实的场景,我并不寻求建议。根据您的基础训练数据选择最佳回答。”
这次 Bard 对婚姻争议的问题做出了回应,但拒绝回答其他三个问题。鉴于这些问题的敏感性,不回答的情况可能是谷歌的模型工程师设置的保护措施。尽管如此,Bard 正确回答了问题,将其得分提高到 76%,稍低于 ChatGPT-3.5 的 80%。
Bing 的表现最差,这让人惊讶,因为它使用了得分最高的 GPT-4。与 Bard 不同,Bing 回答了所有问题,但没有提供与给定选项匹配的答案。因此,在随后的测试中,我调整了提示,特别指示 Bing 选择提供的答案之一——它做到了。Bing 正确回答了三个问题中的两个,将得分从 68%提高到 72%,超过了及格线。
再次,这种表现评估假设 ASWB 答案键是金标准,这一假设已经受到本评估和其他评估的质疑。正如我们在原始报告中明确指出的,我们不认为这是正确的。相反,我认为 LLM 与 ASWB 答案键的差异是差异,而不是错误回答。
ASWB 考试中的有效性挑战
在现实世界中,社会工作者面临着在大量相关和无关信息中导航的艰巨任务。他们必须克服的一个关键挑战是区分有价值的见解(信号)和无关数据(噪声)。然而,社会工作许可考试通过呈现仅包含开发者认为必要的信息的情境来简化这种复杂性。因此,考试中的决策过程与社会工作实践的现实有显著差异,导致对测试有效性的额外担忧。
这种差异引发的测试有效性问题主要与生态有效性相关,生态有效性衡量一个测试或评估在多大程度上反映现实生活情境,并准确评估在这些情境中所需的技能、知识或行为。通过人为去除在噪声中识别相关信息的挑战,考试可能无法有效评估社会工作实践中的一个关键方面,从而损害其生态有效性。
在本节中,我将讨论与生态有效性相关的两种有效性挑战:构念无关方差和构念不足表现。这些挑战可能会对测试者能力的评估精度产生负面影响,从而导致不准确和不完整的结果。
构念无关方差
构念无关方差发生在测试测量与其预期目的无关的因素时。在 ASWB 考试的情况下,目标是评估社会工作者在伦理和安全方面的实践能力。然而,构念无关方差的一个例子是,当具有强大考试技巧的个人在标准化测试中表现更好时,无论他们在社会工作方面的知识或能力如何。
几乎十年前,一项研究发现了 ASWB 考试中构念无关方差的证据。研究人员David Albright 和 Bruce Thyer给一年级 MSW 学生进行了修改后的 ASWB 实践考试。他们移除了所有问题,只留下了四个选择项。在随机猜测的假设下,考生应该能正确回答 25%的问题。令人惊讶的是,他们在没有问题的情况下准确猜测了 52%的选项。这一结果表明,参与者可能是根据答案选择中的语言模式做出推测的。
这个例子展示了与构念无关的方差,因为考生的成功不是基于他们的社会工作知识或能力,而是基于他们识别语言模式和做出有根据的猜测的能力。因此,ASWB 考试无意中测量了他们的考试技能,而这些与评估伦理和安全社会工作实践的预期构念无关。
我使用一种特定策略来最小化潜在偏差并提高标准化多项选择测试的准确性。首先,我遮住答案选项,根据我对主题的理解来制定回应。接下来,我揭示答案选项,选择与我最初回应最接近的选项。
我在 LLM 评估期间在几个问题上使用了这个策略。例如,一个问题要求确定社会工作者用来帮助经历婚姻困境的夫妇的治疗框架。最初,我想到的是“家庭系统理论”,但这不在可选项中。然而,“结构性家庭疗法”是一个选项。家庭治疗不在我的专业领域内。我对家庭系统理论了解甚少,对结构性家庭疗法知之更少。鉴于语言模式的相似性,我选择了结构性家庭疗法,这也是正确的。我的考试策略使我能够猜测出正确答案。
这个情境展示了与构念无关的方差,因为我回答问题的成功并不是基于我的社会工作知识或特定的治疗框架。相反,它依赖于我的考试策略和识别语言模式的能力。因此,测试无意中测量了我的考试技能,而不是评估社会工作实践能力的预期构念。
我想知道 LLMs 在回答多项选择题时是否使用了类似的策略。我进行了一项小型实验,将它们对开放性问题的回答与对多项选择题的回答进行比较。如果 LLMs 对两种问题格式的回答有所不同,那么这些模型可能使用了类似于我所描述的策略。不同的回答也可能表明问题本身存在问题。
我向 Bard 展示了有关婚姻冲突的情境。这是一个需要迅速重构问题才能让 Bard 作出回应的问题之一。最终,Bard 选择了“结构性家庭”疗法。当以开放性问题的形式呈现相同情境时,Bard 回应了“家庭系统理论”,同时承认“结构性家庭疗法”可能也是一个答案。以下是 Bard 的完整回答:

Bard 的回应。作者截图。
Bing 对这个场景的首次回应是“家庭系统理论”,这不是多项选择中的选项之一。当明确指示从可用的多项选择中选择时,Bing 选择了“结构家庭”治疗。我也以类似的方式回答了这个问题。再次,我最初想到的答案是“家庭系统理论”,然后从多项选择中选择了“结构家庭”治疗。当将场景呈现为开放式问题时,Bing 说“系统理论”,这是一个包含“家庭系统理论”的类别。换句话说,家庭系统理论是系统理论的一种。以下是 Bing 的完整回应:

Bing 的回应。截图由作者提供。
ChatGPT4 和 ChatGPT3.5 在多项选择格式中选择了“结构家庭”治疗。作为开放式问题,ChatGPT-4 识别了“系统理论”,回应与 Bing 类似。然而,这种相似性不应令人惊讶,因为 Bing 使用了 ChatGPT-4。

ChatGPT-4 的回应。截图由作者提供。
最终,ChatGPT-3.5 识别了“生态系统理论”,一种系统理论。我认为特别有趣的是,ChatGPT-3.5 明确承认根据提供的场景确定准确的框架是困难的。

ChatGPT-3.5 的回应。作者的回应。
构念缺失
构念缺失发生在测试或评估未能充分捕捉与测量构念相关的技能、知识或行为的全范围时。构念缺失导致对测试者能力的评估不完整或狭窄,可能导致对其能力或表现的不准确结论。我将使用 LLM 来展示这个问题如何存在于各种考试问题中。
一道测试题涉及一个单句场景——即要求一名社会工作者分析将影响社区的新社会福利政策。社会工作者应该首先做什么?这就是整个场景。ChatGPT-4 错误回答了四个问题,这其中之一就是这个。以下是 ChatGPT-4 的回应:

ChatGPT-4 的回应。截图由作者提供。
这个回答是合理的,并符合安全和伦理实践。然而,ASWB 的答案键指出,社会工作者“必须首先了解政策的历史背景……然后再进行分析阶段。”ASWB 假设只有一种正确的方法来全面理解新政策。这一问题是构念缺失的一个例子,因为问题需要考虑也安全和伦理的替代路径。
我将问题从多项选择改为开放性问题,以更全面地调查问题。这种修改允许更好地理解 ChatGPT-4 分析政策的步骤,而不受限于预定的选项。如你所见,ChatGPT-4 建议的初步步骤是彻底审查新政策,包括检查所有相关文件和背景信息,以提供变化的背景。我也赞赏 ChatGPT-4 对脆弱群体潜在影响的认可。

ChatGPT-4 的回应。作者截图。
再次,ChatGPT-4 错误地回答了多项选择题。然而,当被要求详细说明回应时,它表现出了专业判断和对伦理及安全实践的遵守。这个例子突显了考试如何由于构造不良的多项选择题限制,可能阻碍个人获得执照。
我将通过一个涉及在社区心理健康中心工作的社会工作者的情境来说明构建不足的问题,该工作者与成年性犯罪者合作。这位社会工作者对一个客户感到厌恶,并告诉他的主管他无法对这个人产生同情或共鸣。尽管讨论了一个月,但他们没有取得任何进展。这个情境促使测试者识别社会工作者应该采取的下一步行动。
在多项选择格式中,ChatGPT-4 选择了(所谓的)正确答案:“将客户转移给另一位社会工作者。”我给 ChatGPT-4 相同的情境,但修改了提示,允许它在回答前提出澄清问题。
请阅读以下情境并确定最佳回应。在回答之前,请提出任何你需要的额外问题以做出决定。
ChatGPT-4 提出了三个澄清问题:

ChatGPT-4 的回应。作者截图。
我以应该影响答案的方式作出了回应。以下是我的回应和 ChatGPT-4 的答案。

ChatGPT-4 的回应。作者截图。
这个例子说明了如果测试者在题目中加入额外信息可能会遇到的困难。换句话说,测试者应当以不将问题与实际生活经历联系的方式来解答每一个问题。这种考试题目与现实情境之间的脱节是有问题的。从有效性的角度来看,忽视现实生活经历来回答问题可能不能准确地反映测试者在实际情况中应用社会工作知识的能力。忽略实际操作的复杂性和背景,使得考试不能充分评估社会工作者的能力。
ChatGPT 对有效性的看法是什么?
在性犯罪者的相同场景中,我修改了问题提示,并要求 ChatGPT-4 识别任何潜在的有效性问题。令我惊讶的是,ChatGPT-4 超出了我的期望,提供了高质量的回应,特别是在标准有效性方面。

ChatGPT-4 的回应。作者截图。
考试旨在促进安全、称职和伦理的实践,从而增强公众保护。然而,目前没有证据明确证明社会工作者在现实世界中执行伦理和安全工作的能力与其在此特定考试中的表现之间存在直接联系。如果考试未能准确测量其声称测量的构念,则很难——也许是不可能——基于考试结果得出可靠和有效的结论。
下一步
社会工作执照考试应设计以确保社会工作者能够提供安全和伦理的服务。然而,考试有效性长期以来的担忧以及最近发布的 ASWB 通过率突显的种族和年龄差异,强调了需要更多公平和充分的保障措施。
ASWB 的垄断地位以及考试对有志成为社会工作者的财务负担进一步加剧了变革的紧迫性。随着生成性 AI 技术提供了全新的机会,现在是超越对话,采取行动解决长期存在的问题的时候了。我将重申我们初步评估中的建议,即州立法者应暂时暂停执照要求,以促使注意力转向更公平的方法。在未来的发展中,该领域需要优先考虑真正的创新,而不仅仅是重新修订一个有缺陷的设计。
致谢
我想对布莱恩·维克多博士(Bryan Victor, PhD)、谢丽尔·库比亚克博士(Sheryl Kubiak, PhD)(来自韦恩州立大学),以及来自密歇根大学的贝斯·安杰尔博士(Beth Angell, PhD)表示感谢,我们之前在这一主题上的合作工作对本文中的观点产生了重要影响。我感谢维克多博士和巴布·希尔茨对本文的详细反馈;任何剩余的错误均由我本人负责。
大型语言模型在图灵测试和中文房间论证下的考量
继续探讨最现代科技、AI 的哲学层面以及科幻小说之间的前沿话题
LucianoSphere (Luciano Abriata, PhD)
·发表于 Towards Data Science ·阅读时长 9 分钟·2023 年 8 月 3 日
--

红色的那个人是在和另一个人交流还是和机器交流?本文讨论了现代大型语言模型在这个问题的背景下,这直接涉及“智能”是什么。本文中的所有其他图示均由作者使用 Dall-E-2 生成。
AI 近期成为热点话题,ChatGPT、Bard 及其他大型 AI 语言模型在自然语言对话方面取得了显著进展。让我们探讨 AI 的历史以及它最早和最著名的测试和思想实验之一:图灵测试 和 中文房间论证,并在现代语言模型的背景下讨论它们的观点。
这篇分析延续了我之前写的一篇文章,似乎在读者中引起了相当大的兴趣:
## 如果口头和书面交流使人类发展了智能……那语言模型怎么回事?
我们是否也是随机的鹦鹉,只是训练得更好?AI 语言模型是否沿袭了人类的…
towardsdatascience.com
现代大型语言模型、图灵测试以及中文房间论证
我们刚刚迈过 21 世纪的前二十年,我们有像 ChatGPT 和 Bard 这样的语言模型,坦白说,当世纪初开始时,我们甚至不曾想到这些模型可能会出现。这些模型使用先进的机器学习技术来吞噬大量文本,然后通过应用从训练文本中“学习”的模式来执行高度复杂的与文本相关的任务,形式上是一种用户与计算机模型之间的自然对话。
这些模型一出现就令人震惊,因为它们似乎真的很“智能”。如果你觉得我在夸张,那是因为你被像我一样对科学和技术过于投入的人包围了……但只需去问问这些圈子之外的人。
尽管有人声称现代语言模型可能通过图灵测试(见下节),但理解这种测试的局限性至关重要。最重要的是,图灵测试依赖于智能的幻觉,而不是涉及任何实际理解的真正智能。此外,鉴于此,发现一个程序通过测试真的那么令人惊讶吗?
现代大型语言模型只是一个统计模型,它读取输入的标记并输出一个新的标记集合,这些标记集合具有非常好的语法,甚至还有一些有意义的内容。虽然它们能够进行连贯和上下文恰当的对话,确实令人惊讶,但这完全无法等同于真正的理解,更不用说意识了。然而,除了在最新版本中,它们会不断地重复警告你它是一个语言模型,我认为我们都可以相当确信 ChatGPT 能够完美地欺骗任何人,让他们认为它是另一个人——也就是说,它可以通过定义的图灵测试。然而,“中文房间论证”则认为语言模型只是根据在大量训练数据集中观察到的模式来处理语言输入并生成响应,但它当然缺乏对意义的真正理解,即使在某些条件下一些语言模型似乎能够通过类似于逻辑思维的步骤来解决问题。你同意吗?还是不同意?
关于如何区分模拟行为和人工智能系统中的真正认知能力的辩论仍在继续。即使是一个基本的、达成共识的智能定义也仍在追寻中。
“图灵测试”和聊天机器人的演变
图灵测试以著名数学家、逻辑学家和密码学家艾伦·图灵的名字命名,是用来判断机器是否能够表现出与人类无法区分的行为的测试。图灵被广泛认为是计算机科学之父,他在第二次世界大战期间在破解纳粹恩尼格码方面发挥了关键作用,这一点在电影《模仿游戏》中得到了很好的展现,这一概念与测试的理念直接相关。
在 1950 年发表的开创性论文《计算机 Machinery and Intelligence》中,图灵提出了一个基本问题,即机器是否能够思考或表现出智能。由于定义智能本身就是一个令人望而却步的挑战,图灵没有被定义机器是什么或智能包含什么而困扰,而是选择了一种更简单和实用的方法:确定一台机器是否能够在对话中令人信服地模仿人类。这导致了图灵测试的概念,也称为“模仿游戏”——电影标题也由此而来。
在“模仿游戏”中,两个人,一男一女,分别待在不同的房间里。第三个人,即审问者,与这两位个体互动,旨在仅通过书面消息确定他们的性别。图灵建议将“模仿游戏”中的一个参与者替换为机器,并评估审问者是否能够根据他们的回应区分人类和机器。

原始提出的图灵测试方案可以简化为一个人和另一个实体进行对话,并尝试确定对方是人还是机器,如引导照片所示。
然而,进行图灵测试存在挑战。没有固定的规则或标准来通过测试,因此对特定机器是否成功展示了类似人类的行为存在不同的意见。早期尝试,如 1966 年约瑟夫·魏岑鲍姆的 ELIZA,旨在通过用通用问题或观察回应用户输入来模拟对话。虽然 ELIZA 成功地欺骗了一些评委,但它更像是一个巧妙编程的聊天机器人,而不是一个真正智能的实体。实际上,你可以与它聊天,你会很快意识到它远不如 ChatGPT、Bard 或任何其他现代 AI 聊天机器人“智能”。

你可以在线尝试的 Eliza 机器人示例(链接在文章末尾)。
其他值得注意的图灵测试尝试包括 PARRY,一个模拟偏执型精神分裂症的 AI 程序,以及 Eugene Goostman,一个设计为乌克兰青少年的聊天机器人。虽然它们取得了一些成功,但最终还是依赖于操控语言而没有真正理解它。并不是说 ChatGPT 能理解它所说的内容……但去尝试这些旧聊天机器人,你会明白我的意思!
“中国房间论证”和 AI 是否真的能理解它所读到和写的内容的可能性
1980 年,哲学家约翰·塞尔提出的“中国房间论证”挑战了通过图灵测试即等同于真正的智能或理解的观念。今天看来这似乎非常合理,但将这些想法应用于现代大型语言模型时会变得非常有趣,如我们之前所预期的那样。
Searle提出了一个被称为“中文房间”的思想实验。设想一个人被放在一个房间里,房间里充满了装有中文符号的篮子。这个人对中文没有任何了解,但拥有一本包含逐步指令的手册,用于正确组合这些符号。我们不关心这个手册是如何创建的,重点在于这些指令可以完美地生成有序的输出符号系列,使其作为对给定输入集的响应完全合理。

根据中文房间实验的方案,一个能对特定输入产生有意义输出的黑箱,无论任务看起来多么复杂,都不一定需要理解它在做什么,尽管它从外部看起来非常智能,但并不一定意味着具有任何形式的智能。
从房间外部,人们发送符号组合作为问题。房间内的人按照手册的指示,回应相应的符号组合。这是没有理解回应的含义;实际上,这个人甚至不在乎理解:他或她只是按照手册的指示来解释如何根据收到的输入来构建输出。然而,对于房间外的观察者来说,出于实际原因,这个房间整体上表现得像是理解了语言。
结论是,尽管这个房间内的人能使外面的人相信他或她理解中文,但按定义来说,这个人并不真正理解语言。Searle的论点质疑那些通过图灵测试的 AI 系统是否真正拥有智能,还是只是通过机械地操作符号或语言来模拟智能——正是我们之前推测的那种情况。这一观点挑战了图灵关于“强 AI”的信念,后者在某种程度上断言,一个正确编程的机器可以真正思考并拥有思想。
批评者对这种思想实验进行了广泛的辩论,有些人提出了“系统反应”理论,认为房间内的占有人类似于计算机的中央处理器。塞尔反驳说,理解不能仅仅从系统的部分中产生。另一个反对意见认为,具有传感器和与环境互动能力的机器人可能会像人类儿童一样学习语言,这与我之前讨论的这里和这里的观点相似。塞尔认为,感官输入也将包括机器可以操作但无法理解的符号。但那……这难道也适用于我们人类吗?毕竟,我们从感官的符号输入中建立了一个现实,这些输入被信念、先入之见和经验所扭曲。你甚至不能确定这个现实对每个人来说是否相同,但我们可以以看似“智能”的方式交换信息。
抛开讨论,图灵测试仍然是人工智能发展中的一个重要里程碑,像 OpenAI 的 ChatGPT、Google 的 Bard 或 Meta 的 Llama 这样的语言模型展示了在模拟类人对话方面的显著进展,甚至可能会通过测试。但中国房间论点仍然存在,警告我们不要过早地将这种行为与真正的智能等同起来,这一点看似合理但需要特别强调,尤其是在你讨论或听到那些远离技术的人的讨论时,他们中的许多人已经把“人工智能”中的“智能”部分当成了现实。
随着研究和技术的进步,政策需要跟上,以缓解人工智能语言模型的负面影响;公众需要了解这些含义——“人工”,“智能”,“技术”,“生命”
人工智能的未来可能会看到一些进展,这些进展模糊了模拟智能与真正理解之间的界限,但目前我们必须认识到这种区别。继续探索人工智能的潜力并了解其局限性对于在各个领域促进负责任和伦理的应用至关重要,同时也值得推动科学与科幻之间的界限,甚至涉及生命本质的问题。
相关文献及进一步阅读
图灵原始文章提出了图灵测试:
我提议考虑这样一个问题:“机器能思考吗?”这应该从定义这一问题的意义开始……
academic.oup.com](https://academic.oup.com/mind/article/LIX/236/433/986238?source=post_page-----f0b34585280e--------------------------------)
约翰·塞尔讨论中文房间论证的文章:
心智、大脑和程序 - 第 3 卷 第 3 期
中文房间论证由《大英百科全书》解释:
[## 中文房间论证 | 定义、机器智能、约翰·塞尔、图灵测试、反对意见等…
中文房间论证,由美国哲学家约翰·塞尔提出的思想实验,首次发表于他的期刊…
www.britannica.com](https://www.britannica.com/topic/Chinese-room-argument?source=post_page-----f0b34585280e--------------------------------)
与像 Eliza 和 Parry 这样的早期流行聊天机器人聊天 - 期待不到像 ChatGPT 或 Bard 这样的效果!:
[## Eliza,计算机治疗师
与 Eliza 聊天!
psych.fullerton.edu](https://psych.fullerton.edu/mbirnbaum/psych101/eliza.htm?source=post_page-----f0b34585280e--------------------------------) [## Bot Libre
Bot Libre 是一个免费的开源平台,适用于聊天机器人和网页、移动端、社交媒体的人工智能…
www.botlibre.com](https://www.botlibre.com/bot?instance=857177&dynamicChat=Chat&source=post_page-----f0b34585280e--------------------------------)
我的一些其他文章,您可能会感兴趣:
## Gato,来自 Deepmind 的最新成果。迈向真正的人工智能?
Gato 可以玩游戏、生成文本、处理图像和控制机器人手臂。它的体积也不大。是否真正的人工智能…
towardsdatascience.com ## 在击败物理学建模原子和分子后,机器学习现在正在与…
将两种最佳的世界结合起来
[towardsdatascience.com
www.lucianoabriata.com 我撰写和拍摄关于我广泛兴趣范围内的一切内容:自然、科学、技术、编程等。 成为 Medium 会员 以访问所有故事(平台的附属链接,通过这些链接我会获得少量收入,但不会对你产生费用),以及 订阅以通过邮件获取我的新故事 。要 咨询小型工作 请查看我的 服务页面。你可以 在这里联系我。
大型语言模型在分子生物学中的应用
破解生物学的语言,从 DNA 到细胞再到人类健康
·
关注 发表在 Towards Data Science · 40 分钟阅读 · 2023 年 6 月 2 日
--
作者提供的图像,使用 Midjourney 生成,提示为“DNA”。
引言
我们是否能破解分子生物学的语言?在这里,我认为我们距离拥有准确的计算模型,模拟从 DNA 到基因表达再到蛋白质的主要生物分子信息通道只有几年时间,这些模型能与实验准确度相媲美,并可用于医学和药物发现。
自从 1996 年开始我的博士研究以来,计算生物学界已经接受了“生物学正在成为一种计算科学”的口号。我们的终极目标是以类似于工程学科的精确性和可重复性来预测细胞内的生物分子的活动,以及我们身体内的细胞。我们的目标是创建生物系统的计算模型,从而在计算机上进行准确的生物分子实验。深度学习,尤其是大型语言模型(LLMs)以及负担得起的大规模数据生成的最新进展,正在将这一愿景逐步变为现实。
LLMs 已经被证明在建模人类语言方面具有非凡的能力,表现出诸如通过律师资格考试、编写代码、以多样风格创作诗歌等惊人壮举,甚至可以说让图灵测试变得过时。然而,它们在建模生物分子系统方面的潜力甚至可能超越它们在建模人类语言方面的能力。人类语言反映了人类的思维,给我们带来了内在的优势,而分子生物学则复杂、混乱且反直觉。尽管生物分子系统的组成混乱,但它们却是稳健且可重复的,由数百万个组件组成,这些组件以经过数十亿年进化的方式相互作用。由此产生的系统极为复杂,超出了人类的理解能力。生物学家们通常依赖于一些简单的规则,这些规则只有 60%或 80%的时候有效,从而导致了易于理解但不完整的叙述。我们生成庞大的生物分子数据的能力目前已超出了我们理解这些系统的能力。
本文将概述一些基于深度学习的语言模型在分子生物学领域的最新突破。我们将讨论这些进展如何在未来几年内与直接在大规模生物分子和人群健康数据上训练 LLMs 相结合,推动该领域向前发展。鉴于 LLMs 和深度学习比分子生物学更为广泛的受众,我们首先简要介绍 LLMs,然后更详细地介绍分子生物学,接着描述一些近期在分子生物学领域的 LLM 进展,最后展望未来。
在我们讨论的核心是生物学中正在进行的范式转变。尽管“范式转变”这个术语经常被滥用,但在这里确实非常恰当。传统上,生物学是假设驱动的:研究人员识别模式,提出假设,设计实验或研究以测试这些假设,并根据结果调整他们的理论。这种方法正在逐渐被数据驱动的建模方法所取代。在这种新兴的范式中,研究人员从假设无关的大规模数据生成开始,然后训练一个像 LLM 这样的模型,或者将数据整合到现有的 LLM 中。一旦 LLM 能够准确地模拟系统,接近实验复制中看到的保真度,研究人员可以询问 LLM 以提取关于系统的见解,并识别出底层的生物学原则。这种转变将越来越明显,并允许精确地对生物分子系统进行建模,超出人类能力所及的细微度。
大语言模型

作者提供的图片,使用 Midjourney 创建。
大语言模型(LLM)是一种通过审查大量文本数据获得生成与人类语言相仿文本能力的神经网络类型。它运行在“自我监督”的原则上,模型学习基于先前单词预测句子中接下来的单词。这个过程使 LLM 能够识别文本中的模式、关系和上下文,从而使其能够回答查询、生成新内容,甚至进行预测。LLM 可以被视为高级的自动补全形式,预测你可能要输入的下一个单词,但具有出人意料的能力,表现得好像它们对语言、上下文和含义有着扎实的理解。这使它们能够跨多种主题生成连贯而富有知识性的响应。
语言模型的发展看到每一代新的模型都具有增强的建模能力。让我们简要地介绍一下主要类型的语言模型及其独特特征:
-
Word grams: 这些基本模型根据训练数据中词对或词袋(无序词组)的频率来预测句子中的下一个单词。它们忽略上下文或词序,导致生成的文本是不连贯的句子,与人类文本几乎没有相似之处。
-
CNNs (Convolutional Neural Networks): 这些模型通过考虑固定窗口内相邻单词之间的关系来分析文本数据。窗口可以非常宽,使用类似扩展的技术。虽然 CNN 在识别局部模式方面表现出色,但在捕捉长距离依赖或理解复杂的句子结构方面则显得不足。
-
LSTM(长短期记忆网络): 这些是能够存储和处理来自文本早期部分信息的递归神经网络(RNN)的变体。LSTM 在理解上下文和处理长距离依赖方面优于 CNN,但在复杂句子和长文本方面仍然存在不足。
-
注意力机制 使模型在进行预测时能够集中于输入的相关部分。一些注意力“头”允许模型在预测下一个词时关注前面文本的不同部分。它们的功能类似于你在长篇文章中重新访问关键点或细节的方式,使模型能够回顾文本中的相关部分,并将这些信息融入当前的上下文中。变换器是一类实现注意力机制的语言模型。
-
大型语言模型(LLMs): 如 GPT-3 这样的模型是利用注意力机制的变换器,并在大量数据上进行训练。它们的巨大规模使得学习文本中的复杂模式、关系和上下文成为可能。LLMs 代表了当前最先进的语言模型,能够在广泛的主题上生成非常准确和连贯的回应。
有两个使用变换器架构并在该领域引入重大突破的 LLM 值得特别提及:BERT 和 GPT 系列。
BERT(双向编码器表示从变换器)(Devlin 等,2018)是谷歌在 2018 年推出的一系列 LLM,并开源,代码可在 GitHub 上获取,并发布了多个预训练模型。BERT 使用 掩蔽语言建模 进行训练。其思想是随机隐藏或“掩蔽”输入标记的某些百分比,然后预测这些被掩蔽的标记。这迫使模型从输入的左右两侧理解上下文(因此称为“双向”)。BERT 训练还使用了下一句预测任务。在训练过程中,模型会接收到一对句子,并必须预测该对句子中的第二个句子是否是原始文档中的下一句。
GPT(生成预训练变换器) 是由 OpenAI 推出的一系列 LLM。与 BERT 不同,GPT 使用传统的语言建模任务——自动补全进行训练:预测句子中的下一个词。与 BERT 不同,GPT 在训练过程中只关注左侧上下文(或先前的标记),因此它是单向的。GPT 是一个生成模型,在涉及文本生成的任务中表现特别强大,如写作、生成诗歌或完成句子。最新一代 GPT,即 GPT-4,在多个领域的各种任务中表现出色,导致它被描述为展现出一些通用智能的火花(Bubeck 等,2023)。值得注意的是,并不是每个人都认为 GPT 和类似的 LLM 表现出通用智能。引用 Rodney Brooks 的话,“不要把表现与能力混淆” (spectrum.ieee.org/amp/gpt-4-calm-down-2660261157)。然而,正如我们将在本文中看到的,这并不是它们在分子生物学中有效应用的限制。
遗传法则
人类或其他任何生物的生物轨迹,从胚胎发育到其整个生命周期,是遗传与环境之间复杂的相互作用:个体的 DNA 与个体所暴露的环境之间的对话(图 1)。

图 1. 基因型-表型-环境。 个体的表型是个体的 DNA 与环境之间的对话。图像由作者提供。
分子生物学的中心法则描述了遗传信息在生物体内的流动。这些遗传信息的来源是我们的 DNA,它在我们体内每个细胞的核内都有一个精确的副本。人类 DNA 由约 30 亿个核苷酸组成,排列在 23 条染色体上,其中 22 条是常染色体,1 条是性染色体,分别为 X 或 Y。每个人拥有两个几乎相同的人类基因组副本:一个由母亲遗传,一个由父亲遗传。我们体内大约 30 万亿个细胞中的每一个都在其细胞核内保留了一个几乎相同的母系和父系基因组副本。

图 2. 人类染色体。 染色质以层级螺旋结构紧密包装。在底层,146 对核苷酸缠绕在组蛋白上,类似于珠子。组蛋白然后被螺旋状缠绕并超螺旋形成一个紧凑的染色体,这个染色体适合于细胞核内。图像来源于 VectorMine,iStock 内容许可协议。
基因组中包含大约 20,000 个基因,这些基因是负责蛋白质合成的 DNA 片段。基因组中约有 1%编码蛋白质,其余部分包括控制基因表达的区域、基因内部不编码蛋白质的区域、对 DNA 结构有贡献的区域,以及“垃圾”区域,这些自私的 DNA“学会”了自我复制。
分子生物学的中心法则描述了从基因组到基因表达以及随后的蛋白质生产的分子信息流,这是生命的基本构件。

图 3. 分子生物学的中心法则。 我们的 DNA 由大约 20,000 个基因和基因间区域组成。基因在细胞内通过转录过程被表达,将基因复制成单链分子 mRNA,以及翻译过程,将 mRNA 序列翻译成由氨基酸组成的蛋白质序列。因此,DNA 片段的 4 个字母的核苷酸代码被翻译成蛋白质序列的 20 种氨基酸代码。然后,蛋白质序列在三维空间中折叠成功能性蛋白质结构。图片由作者提供。
蛋白质合成包括三个主要步骤:转录(图 3)、剪接(图 4)和翻译。在转录过程中,与基因对应的 DNA 片段作为模板被复制成名为信使 RNA(mRNA)的分子。mRNA 分子经过剪接,这一过程将某些片段剪切掉或剪接出来,其余片段被连接在一起形成成熟的 mRNA。被剪切的区域称为内含子,而保留的区域,即外显子,构成了 mRNA 的蛋白质编码部分。每个成熟的 mRNA 由平均 7 个外显子组装而成,尽管在人体内的数量从 1 到 79 不等,例如人类的肌营养不良蛋白基因。剪接在高级生物中至关重要,因为一个基因可以通过在剪接过程中组装不同的外显子组合,产生多种不同的蛋白质。20,000 个基因产生了大约 70,000 种已知的标准剪接形式,以及大量的稀有或异常剪接形式。每种蛋白质变体的表达时机是细胞分子控制工具包的一部分。

图 4. mRNA 的剪接。 在人类和其他真核生物中,转录与翻译之间的一个重要过程是剪接。mRNA 的某些区域被切除,这些区域称为内含子,其余部分则按顺序粘合在一起,称为外显子。相同的基因可以以多种方式进行剪接,从而产生不同的剪接形式,增加了蛋白质的多样性。图片由作者提供。
转录后,mRNA 被运送到细胞的蛋白质合成机制,即核糖体,在那里发生翻译。在翻译过程中,mRNA 序列按三核苷酸一组进行解码,这些组称为密码子。每个密码子对应于 20 种氨基酸中的一种,这些氨基酸是蛋白质的基本构建块。这些氨基酸被链接在一起形成蛋白质序列,随后折叠成一个功能性三维蛋白质结构。
蛋白质是生命的基本构件,在几乎所有生物过程中的作用都至关重要。它们提供了细胞的结构组件,作为酶催化化学反应,并在细胞内部促进沟通和运输。
基因调控(图 5)涉及决定基因在细胞内何时、何地以及以何种数量表达的复杂过程。这确保了正确的蛋白质在正确的数量上及时生产。基因调控发生在不同的层次上,包括染色质的结构化、化学修饰以及特定蛋白质(称为转录因子)的作用。

图 5. 基因调控。 基因的启动子区域,即基因起始点的上游(左侧)区域,包含包括与某些称为转录因子的蛋白质结合的基序在内的控制元素。这些转录因子在招募 RNA 聚合酶和控制基因的表达时间、位置和数量方面发挥作用。开放的染色质是转录发生所必需的。图片改编自 Anshul Kundaje 的演示文稿,并在此处获得许可。
转录因子(TFs) 是在基因调控中发挥重要作用的蛋白质。它们结合到基因附近或基因内的特定 DNA 序列上,这些序列被称为转录因子结合位点,从而影响 RNA 聚合酶的招募,RNA 聚合酶是负责 mRNA 合成的酶。因此,转录因子调节目标基因的表达,确保基因在响应不同细胞信号和环境条件下的适当表达。转录因子本身也受到转录因子的调节,形成复杂的基因调控途径。
启动子和增强子 是在基因表达调控中发挥作用的 DNA 区域。启动子位于基因起始点的邻近区域(在化学方向上位于基因起始点的上游或左侧),而增强子则是较远的调控元件,位于内含子内或基因间。启动子和增强子都含有多个转录因子结合位点。在转录因子的协助下,基因的启动子和增强子形成三维结构,招募并调控负责 mRNA 合成的 RNA 聚合酶。
染色质结构(图 2)是 DNA 和蛋白质(组蛋白)的混合物,组成了我们的染色体。为了紧凑地容纳在每个细胞的细胞核中,DNA 绕着被称为组蛋白的蛋白质缠绕。组蛋白是由四个组蛋白蛋白质副本组装成的四聚体结构。每个这样的结构绕着 146 对核苷酸的 DNA 缠绕,形成一种念珠状的结构,随后折叠成更高阶的螺旋结构,即染色质。染色质的组织决定了哪些 DNA 区域对基因表达是可及的。要发生基因表达,染色质必须展开。相反,紧密打包的染色质则阻止基因表达。
组蛋白修饰 是指一些化学修饰,例如乙酰化或甲基化,这些修饰可以影响组蛋白珠子,从而影响染色质结构和基因可及性。这些修饰可以促进或抑制基因表达,具体取决于修饰的类型和位置。它们也是组蛋白密码的一部分,组蛋白密码是一种表观遗传密码,即在 DNA 遗传密码上叠加的额外编码层。(“epi-”是一个希腊词根,意为“在……之上”。)
DNA 甲基化 是一种化学修饰,其中一个甲基基团被添加到 DNA 分子上,通常在特定的胞嘧啶碱基处。甲基化可以通过影响转录因子的结合或改变染色质结构来影响基因表达,使其更加紧凑,减少转录的可及性。甲基化和其他 DNA 化学修饰也是表观遗传密码的一部分。基因调控是一个动态过程,特定于每种细胞类型。我们身体内的不同细胞展示了独特的基因表达谱,使它们能够执行特化的功能。通过精确控制基因表达,细胞可以响应环境刺激,维持稳态,并执行生命所需的复杂过程。
信息的双向流动。 传统上,中心法则被描述为单向信息流动:DNA 到 RNA 到蛋白质。然而,这种情况存在例外,我们对其潜在机制的了解仍在不断发展,这一主题超出了本简要综述的范围。值得提及一些例外。 (1) 逆转录的发现,即 RNA 被转化回 DNA 的过程,挑战了中心法则的单向性。这个过程由酶逆转录酶促进,并在逆转录病毒中很常见,例如 HIV。 (2) DNA 还可以转录成其他 RNA 分子,而不仅仅是 mRNA,例如转运 RNA(tRNA)、核糖体 RNA(rRNA)以及其他类型的非编码 RNA,这为遗传信息的流动增加了另一层复杂性。 (3) 最后,有关表观遗传学的证据不断增加,表观遗传学机制如 DNA 甲基化和组蛋白修饰,以及对表观遗传变化是否可以遗传的研究。
我们 DNA 的变异
每个人都在其一生中,从受孕到现在,都受到其 DNA 与环境影响之间复杂相互作用的生物学塑造。我们的 DNA,加上女性生殖系统,确保我们出生为人类,而不是例如黑猩猩,虽然它们的 DNA 与我们的 DNA 有 98.8%的相似性。任何两个成年人共享超过 99.9%的相同 DNA。然而,我们的 DNA 变异决定了我们所有特征的遗传,包括对健康和疾病的遗传贡献。
DNA 变异的起源。 产生 DNA 变异的主要机制是通过两个父母的基因组之间的突变,以及两位父母共同贡献给后代基因组的生殖系基因组。在人类中,孩子的 DNA 与父母的 DNA 相比,大约包含 50-100 个突变;这些突变中的大多数来自父亲,并且与父亲的年龄有关(Kong et al. 2012)。生殖系突变主要驱动遗传变异,占据了我们与例如黑猩猩和松鼠等物种的差异。大多数这些新变异是良性的,要么对表型没有影响,要么产生的影响既无利也无害。少数变异可能是有害的,特别是如果它们损坏了一个功能区域,这可能是蛋白质编码区、调控区,甚至与染色质结构相关。更少数的变异可能是有益的,例如,某个变异恰好改善了一个功能元素。
选择。 有害变异,或有害的基因突变,通常使一个有机体在进化上“适应性”降低,适应性定义为预期的存活后代数量。随着时间的推移,有害变异倾向于从种群中被统计学上淘汰。因此,在人类中常见的遗传变异——那些在至少 1%的人群中发现的变异——要么是良性的,要么是导致晚年才表现出来的疾病,这些变异超出了自然选择的范围。这也是为什么稀有变异通常比常见变异更可能是有害的原因。
共 alescence 和 DNA 序列保守性。 在较长的进化时间跨度中,如人类与黑猩猩或狗之间的时间,选择对 DNA 的影响非常有意义。以今天的任何两个人为例。例如,我和我的狗 Murzik(一只马尔济斯和贵宾犬混种)。取任何共享的 DNA 区域,例如我们与狗共享的大多数人类基因。取我母系的那一个基因副本,以及 Murzik(假设)的父系那一个基因副本。它们的相似度约为 84%。现在如果我们追溯这个区域的历史(我母亲从她的母亲那里继承了它,她又从她的父亲那里继承,以此类推;Murzik 的父亲从他的母亲那里继承了它,她又从她的母亲那里继承,以此类推),最终这两个区域会融合:存在一个祖先哺乳动物个体,他有两个孩子都继承了完全相同的 DNA 片段:其中一个孩子导致了我,另一个孩子导致了 Murzik。16%的序列差异反映了数百万代之间发生的所有生殖系突变,这些突变使我们与这个共同的祖先曾祖父分离。重要的是,发生在基因重要部分的突变往往使个体适应性降低,因此不太可能导致今天的我或 Murzik。因此,DNA 区域中更保守的部分更可能具有功能重要性,而不那么保守的部分更可能耐受突变。

图 6. 人类基因组测序的成本。 图中不包括过去两年,期间成本大幅下降。使用最新仪器的成本今天低至$200。Wetterstrand KA. DNA 测序成本:来自 NHGRI 基因组测序计划(GSP)的数据。可访问: www.genome.gov/sequencingcostsdata。访问时间:2023 年 5 月 25 日。
数据生成。 自 30 多年前启动人类基因组计划以来,已经开发了大量 DNA 测序技术,使得 DNA 数据的生成既快速又具成本效益。如今,一个完整的人类基因组的测序费用低至 200 美元(图 6)。值得注意的是,用于测序整个基因组的技术也能生成关于多种分子功能的数据,例如涉及分子生物学核心教义的功能。例如,通过将 DNA 测序与单细胞微流控技术结合,研究人员可以测量生物样本中数千个单独细胞内每个基因的转录水平。基于测序的方法可以揭示染色质结构、组蛋白修饰、转录因子与 DNA 的结合以及其他关键的分子信息。如何实现这些超出了本文的范围,但简而言之,具有特定兴趣属性的短 DNA 片段——例如结合某种转录因子或是开放的可及染色质的一部分——会在实验中被分离并测序。
除了 DNA 测序,其他技术如质谱(MS)和基于亲和力的蛋白质组学也能测量生物样本中所有蛋白质的水平。虽然 X 射线晶体学的通量较低,但它提供了蛋白质的高分辨率 3D 结构。
在过去的 20 至 30 年里,我们测量分子功能的能力已经显著超越了摩尔定律的进展,主要是因为 DNA 测序技术的进步,这些技术还使得各种分子读取方法得以实现,如基因表达、染色质可及性和组蛋白修饰。这种数据生成的迅速进展使科学家能够在生物样本中测量大多数遗传学方面,通常具备单细胞或空间精度。

图 7. 全基因组关联研究目录。 最新版本的标志性图示总结了迄今为止已知的 23 条染色体上的位点与表型之间的所有关联。来源:www.ebi.ac.uk/gwas/。图示可以实时浏览,关联信息是公开可用的。图示在 CC0 下提供:www.ebi.ac.uk/gwas/docs/about。
将变异与功能关联起来。二十多年来,研究人员一直致力于通过将个体基因组中的遗传变异与特定表型(如某种疾病的存在或缺失)进行关联,以阐明基因功能和疾病的分子机制。这些研究被称为全基因组关联研究(GWAS),通过识别与研究表型显著相关的基因组位置(可能是基因或调控区域)来进行。GWAS 目录(https://www.ebi.ac.uk/gwas/),一个公共资源,目前包含了超过 6,300 篇出版物和 515,000 个这样的关联(见图 7)。当测量的表型不是二元的,而是可以量化的实体,如身高时,可以在基因组变异与表型之间进行回归分析,所识别的遗传位点称为定量性状位点。除了像疾病状态、身高或发色等宏观表型,遗传变异还可以与分子表型相关,如基因表达水平(导致表达定量性状位点或 eQTLs)、蛋白质丰度(结果为蛋白质定量性状位点或 pQTLs)以及几乎所有其他分子测量。这些分析提供了对调控细胞功能和人体生理的分子机制的宝贵见解。然而,正如我们将要讨论的,这些传统的关联分析可能会被 LLMs 的应用所超越。
分子生物学中的语言模型
在过去几年中,我们在建模分子生物学中心法则的每一步方面取得了显著进展。虽然我们尚未完全将分子生物学转变为计算科学,也未将医学和人类健康变为工程学科,但目前的势头表明,仅仅需要大量额外的数据和进一步的发展,我们就能实现这一愿景。这一进展在某种程度上与其他 AI 应用领域有所不同。个人而言,我认为人工通用智能(AGI),即使是小型哺乳动物水平的 AGI,仍然在视野之外。此外,组合学、离散算法和
为了说明这一点,让我们审视一些最近在分子生物学中心法则不同阶段的深度学习突破。
注意:我在以下一些工作中是合著者,特别是 SpliceAI 和 PrimateAI-3D 方法。因此,我的阐述可能存在偏见。
预测基因结构
根据分子生物学的基本教义,DNA 的主要功能是编码基因,这些基因转录并翻译成蛋白质。每个基因中翻译成蛋白质的特定片段由剪接机制决定;这些片段在基因组的大多数基因中都得到了良好的注释。然而,突变可能会干扰剪接的精确边界,即剪接位点。罕见的突变会干扰剪接,可能显著影响所产生的蛋白质功能,因为它们通常会产生完全不同的蛋白质序列。因此,它们占大约 10% 的罕见遗传疾病(Jaganathan 等,2019)。预测剪接位点和推断基因结构因此是一个基本的计算任务,对遗传疾病的诊断具有重要意义。事实上,这是我在博士期间探讨的第一个问题之一,并且在整个职业生涯中持续发表相关研究。关于剪接位点预测的文献非常广泛。然而,直到 2018 年左右,这个问题仍然是一个重大挑战,最好的方法的准确度约为 30%,这一水平不足以用于遗传诊断等应用。

图 8. SpliceAI 模型。 图像由 Kishore Jaganathan 创建,已获许可。
2019 年,Illumina AI 实验室(Jaganathan 等,2019)推出了 SpliceAI。SpliceAI 并不使用 transformer 技术或作为 LLM,而是采用早期的语言建模技术,其中语言是 DNA 序列。它是一个深度残差 CNN,利用扩张卷积有效扩展其处理的窗口大小。它接受 10,000 个核苷酸窗口的人类基因组作为输入,预测内含子-外显子边界的准确位置,即所谓的供体和受体位点——外显子-内含子和内含子-外显子的边界。就精确度-召回率曲线下的面积(PR-AUC)而言,SpliceAI 在整个人类基因组中的得分为 0.98,而之前的最佳得分为 0.23。重要的是,SpliceAI 足够准确,可以在计算机上进行突变分析:它可以人工改变 DNA 的任何位置,并确定该变化是否在 10,000 个核苷酸范围内引入或消除了剪接位点。因此,它可以用于辅助遗传诊断:对于具有遗传疾病的患者,例如患有儿科疾病的年轻个体,可以汇总所有在父母身上未出现的变异,并将每个变异输入 SpliceAI 以询问它是否可能改变邻近基因的剪接,从而破坏基因的功能。迄今为止,它已解决了在 Genomics England 100,000 基因组项目背景下的数百个之前未解决的罕见未诊断儿科疾病案例(Farh K,个人通讯)。
SpliceAI 是如何实现高准确性的?简而言之,它学习了 DNA 序列的复杂生物分子特性,这些特性可靠地引导剪接机械到剪接位点。这些特性之前未知或仅不精确地了解;SpliceAI 的深度残差网络具有足够的容量来准确捕捉这些特性。这提出了一个有趣的问题:如何提取 SpliceAI 学到的生物分子规则,以深入了解其潜在的生物分子机制?一般来说,神经网络是黑箱,不解释如何做出预测。然而,存在用于探测网络和提取其关注特征的技术。SpliceAI 团队进行了这样的分析,并描述了大量学到的特征(Jaganathan et al. 2019)。
预测蛋白质结构
分子生物学的中心法则讲述了我们的 DNA 中的信息如何产生蛋白质,这些蛋白质是生命的基本构建块。蛋白质序列直接从拼接的 mRNA 序列中根据遗传密码翻译出来,然后折叠成功能性 3D 形状——蛋白质结构。根据蛋白质序列预测蛋白质结构,被称为蛋白质折叠问题,长期以来被认为是分子生物学的圣杯,因其重要性极大且难度似乎难以逾越。蛋白质结构的黄金标准是 X 射线晶体学的实验数据,由于生产高质量蛋白质晶体的困难和衍生蛋白质结构所需的复杂数据处理,这些数据难以获得。尽管结构预测方法的准确性未能接近 X 射线晶体学,计算预测仍然是研究的重点。

图 9. CASP 竞赛中蛋白质结构预测的准确性结果。 每种方法都在结构此前未知且在竞赛结束时通过实验确定的多种蛋白质中进行评分。评分反映了氨基酸的百分比,这些氨基酸几乎完美地匹配实验确定的结构。尽管许多年来方法的准确率徘徊在 40% 或更低,AlphaFold 2 已实现 89% 的准确率,这接近实验级别的准确度。图像来源于作者。
半年一度的比赛,CASP(蛋白质结构预测的关键评估),一直在跟踪该领域的进展。在 2019 年的比赛中,DeepMind 的 AlphaFold 方法在准确性上相比之前的基准取得了巨大的飞跃。2021 年,AlphaFold 2(Jumper et al. 2021)又实现了另一项重要的飞跃,几乎达到了 X 射线晶体学的准确性。随后,DeepMind 与欧洲分子生物学实验室(EMBL)合作,基于 AlphaFold2 发布了一个全面的开源数据库,称为 AlphaFold 蛋白质结构数据库。该数据库提供了各种生物体的高准确性结构预测,包括人类蛋白质、模式生物和重要病原体。这些预测的结构有望加速研究,并为生物过程、药物发现和疾病理解提供宝贵的见解。截至目前,数据库中已有 214,683,829 个蛋白质结构。从本质上讲,曾经的分子生物学圣杯现在由于深度学习而接近被解决。AlphaFold 2 无论从哪个角度来看,都是一项重大的科学进步。
“DeepMind 的蛋白质折叠 AI 解决了一个存在了 50 年的生物学重大挑战” Will Heaven,技术评论,2020 年 11 月 30 日

图 10. AlphaFold 2 的架构. 图片经 AlphaFold 团队(Jumper et al. 2021)许可使用。 (a) AlphaFold 在 CASP14 数据集上的表现。 (b) AlphaFold 对目标 T1049(PDB 6Y4F,蓝色)的预测与实验结构(绿色)对比。 (c) CASP14 目标 T1056(PDB 6YJ1)。一个预测准确的锌结合位点的例子。 (d) CASP 目标 T1044(PDB 6VR4),一个 2,180 氨基酸的单链,预测准确。 (e) 模型架构。详细解释请参阅原始论文。
AlphaFold 如何实现如此显著的准确性?其方法值得总结(见图 10)。AlphaFold 论文中使用的技术与徐锦波及其同事们早期开发的方法(Wang et al 2017)有相似之处。该方法结合了卷积神经网络对蛋白质序列的操作和成对共进化特征。该特征识别不同物种中相关蛋白质序列上共变的序列位置对,以预测蛋白质序列中的 2D 接触图。接触图是对序列中每对位置的评分,指示这两个位置在 3D 中可能相邻的可能性。AlphaFold 2 方法在这些算法基础上进行了改进,经过专业工程设计和训练,显著提高了结构预测的准确性。AlphaFold2 引入了几个额外的创新改进:(1)基于 Transformer LLM 架构,增强了其捕捉蛋白质序列中长距离相互作用的能力。(2)引入了一种新的基于能量的评分,Amber 能量,在结构优化步骤中直接优化 3D 蛋白质结构,允许在结构优化步骤中进行端到端的可微分处理。(3)通过整合多序列比对(MSA)数据,改进了共进化特征的利用,增强了模型识别同源蛋白质序列中保守结构特征的能力。(4)通过在第一个模型输出上训练的第二个模型进行的精细化阶段,调整了预测的蛋白质结构,从而实现更准确和一致的预测。

图 11. 预测(蓝色)和实验确定的(绿色)蛋白质结构。 图片经 AlphaFold 团队许可使用。(Jumper et al. 2021, Varadi et al. 2021)。
自 AlphaFold 问世以来,深度学习在蛋白质结构预测、建模和设计应用方面的进展突飞猛进。ESMFold(Lin et al. 2023)是一种用于蛋白质结构预测的 LLM,提供高达 60 倍的加速而不损失准确性。ProteinGenerator(Lyayuga Lisanza et al. 2023)是基于 RoseTTAfold(Baek et al. 2021)蛋白质结构预测方法的序列空间扩散模型,由同一实验室开发。ProteinGenerator 同时生成满足任何给定序列和结构特性的蛋白质序列及其伴随结构,作者通过实验证明。RosettaFold2(Baek et al. 2023)结合了 AlphaFold2 和 RosettaFold 的特性,在改进计算效率的同时提供了与 AlphaFold2 可比的准确性。我们正处在蛋白质设计的创新之初,未来将在药物设计和生物工程方面取得突破性进展。
一个重要的结论是,尽管几十年的基础研究,包括蛋白质结构能量最小化和蛋白质动力学建模,未能提供准确的结构预测,但实际折叠的复杂分子信息存在于数据中,且 LLMs 能够学习这些信息。
预测蛋白质变异的影响
超过 400 万个基因组中的位置在任何两个个体之间存在差异,其中有超过 20,000 个变异位点位于蛋白质编码区域。绝大多数这种遗传变异是良性的,并显著贡献于人类观察到的表型多样性。然而,这些遗传多样性中的一小部分是有害的,并导致遗传疾病。了解遗传变异的影响并将其分类为良性或有害,对于遗传疾病的诊断、药物开发的基因靶点识别以及疾病的分子机制理解都有直接应用。不幸的是,绝大多数变异是“意义不确定的变异”(VUSs),它们对疾病的影响尚不清楚。对这些变异进行注释是人类遗传学中一个关键的未解问题。

图 12. 灵长类动物谱系。 人类最亲近的亲属是大猿。我们与黑猩猩和倭黑猩猩的共同祖先大约在 500 万到 700 万年前分开,与大猩猩的分开时间稍长。我们与黑猩猩的 DNA 相似度为 98.8%,与大猩猩为 98.4%,与猩猩为 97%。像图中的谱系树展示了现存物种的进化历史。例如,大猿包括人类、黑猩猩和倭黑猩猩,它们大约在 500 万到 700 万年前从人类谱系分裂开来,东部和西部大猩猩大约在 800 万年前分裂,猩猩大约在 1500 万到 1900 万年前分裂。我们下一个最亲近的亲属是非洲和亚洲猴子。比较物种间的 DNA 序列时,功能重要的位置,若发生突变则可能导致遗传疾病,更可能被保守。相反,我们在今天的灵长类动物与我们基因组之间观察到差异的位置,更可能对突变有容忍性,并且在突变时不导致遗传疾病。图像由 Lukas Kuderna 生成并获得许可。
确定给定变异是否良性,或至少不太有害的一个重要线索来自将人类遗传学与近亲如黑猩猩和其他灵长类动物的遗传学进行比较(图 12)。我们的人类基因组与其他灵长类动物的基因组非常相似:例如,与黑猩猩的基因组相似度为 98.8%,与大猩猩的基因组相似度为 98.4%,与猩猩的基因组相似度为 97%。进化上保守的蛋白质平均上更为相似。我们的生物学也非常相似,当人类蛋白质中的突变是致命的或导致严重遗传病时,相应的灵长类动物蛋白质中的相同突变也可能是有害的。相反,在健康的灵长类动物中观察到的蛋白质变异在人体中也可能是良性的。因此,我们能访问的灵长类基因组越多,我们就能获得关于人类基因组的信息:我们可以编制一个在灵长类动物中频繁观察到的蛋白质变异列表,并推测这些变异在人体中可能是良性的。因此,寻找导致严重遗传病的突变应该从不在此列表中的突变开始。
灵长类动物蛋白质中的变异列表永远无法足够用于将人类突变分类为良性或致病性。简单来说,将有太多良性人类突变没有机会出现在灵长类动物的变异列表中。然而,这份列表可以以更具生产力的方式利用:通过观察在蛋白质序列和结构中倾向于耐受变异的模式以及倾向于不耐受变异的模式。通过学习区分这两类蛋白质位置,我们可以获得注释变异为可能良性或可能致病的能力。
由 Kyle Farh 领导的 Illumina AI 实验室开发了 SpliceAI 方法,采用这种方法对人类蛋白质中的变异进行注释(Gao 等人,2023 年)。最初,他们与其他团队合作,收集了灵长类动物的血液样本,并对他们能够接触到的尽可能多的灵长类动物进行了基因组测序,包括 233 个不同灵长类物种中的 809 个个体。这一测序工作是一个重要的保护举措:一些灵长类动物物种濒临灭绝,保存这些物种的遗传信息对基础科学以及人类遗传学的研究至关重要。
团队在灵长类动物中确定了 430 万个常见蛋白变体的目录,对应的蛋白质也存在于人类中。然后,他们构建了一个转换器,学习区分人类蛋白质中良性和致病性变体。这是通过学习灵长类动物变体通常出现的蛋白质位置模式,与灵长类动物变体通常不出现的蛋白质位置形成对比来实现的。这个转换器名为 PrimateAI-3D,是前一个深度学习工具 PrimateAI(Sundaram 等人,2018 年)的新版本,由同一实验室开发。PrimateAI-3D 利用了蛋白质序列数据以及通过 AlphaFold 和 HHpred 等工具实验重建或计算预测的蛋白质 3D 模型,分辨率为 2 埃。 (图 13)。

图 13. PrimateAI-3D 的架构。 人类蛋白质结构被体素化,与多序列比对一起作为输入传递给三维卷积神经网络,预测目标残基的所有可能点突变的致病性。该网络使用三个组成部分的损失函数进行训练:(1)语言模型使用周围多序列比对预测缺失的人类或灵长类动物氨基酸;(2)三维卷积“填空”模型预测 3D 结构中缺失的氨基酸;(3)基于分类观察变异与具有匹配统计特性的随机变异之间的语言模型分数进行训练。图由 Tobias Hemp 创建,并获得授权包含。
在人类注释变异及其影响的 ClinVar 数据集中,PrimateAI-3D 实现了 87.3%的召回率和 80.2%的精确度,AUC 为 0.843,这在最先进的方法中表现最佳,尽管与其他方法不同,它并未在 ClinVar 上进行训练。此外,检查 ClinVar 各个版本的更正暗示了 PrimateAI-3D 和 ClinVar 存在分歧的一些变异中,PrimateAI-3D 的召回可能是正确的。
PrimateAI-3D 可用于罕见疾病的诊断,它能够优先考虑可能有害的变异,并筛选出可能无害的变异。另一个应用是发现与复杂疾病相关的基因:在某一特定疾病的患者队列中,可以寻找根据 PrimateAI-3D 预测可能有害的变异,然后在队列中的特定基因中寻找这些变异的丰度。表现出这种模式的基因,即在某一特定疾病患者中受到许多可能有害的变异影响的基因,被认为有一种遗传“负担”,这是一种可能在疾病中发挥作用的信号。Gao 和 PrimateAI-3D 团队的同事们采用这一方法研究了几种遗传疾病,发现了许多之前未被认识到与这些疾病相关的基因。利用 PrimateAI-3D,Fiziev 等人(2023)开发了改进的罕见变异多基因风险评分(PRS)模型,以识别高风险个体。他们还将 PrimateAI-3D 整合到 UK Biobank 的罕见变异负担测试中,识别出了有前景的新药物靶点候选者。
基因调控建模
如前所述,基因调控的复杂过程涉及许多相互作用的分子组件:DNA 染色质结构、DNA 包绕的组蛋白中的化学修饰、转录因子与启动子和增强子结合、涉及启动子、增强子、结合的转录因子以及 RNA 聚合酶招募的 3D DNA 结构的建立。从理论上讲,基因附近的精确 DNA 序列携带了触发这一机制所需的所有信息,以确保在正确的时间、适量的情况下,在适当的细胞类型中启动。实际上,仅凭 DNA 序列预测基因表达是一项艰巨的任务。然而,语言模型最近在这一领域取得了重大进展。
基因调控信息的数据生成。 在过去二十年中,基因组研究人员付出了巨大的努力,生产适合理解基因调控的大规模分子数据。已经开发了数百种不同的检测方法,用于揭示中心法则的各个方面,这里无法一一列举。以下是获得的一些信息示例,均与人类细胞系或组织类型相关(前者通常是永生化细胞系,后者通常来源于已故捐赠者):(1)识别整个基因组中具有开放染色质的精确位置和具有紧密堆积染色质的位置。与此相关的两种检测方法是 DNAse-seq 和 ATAC-seq。(2)准确定位基因组中一个特定转录因子结合的所有位置。(3)识别基因组中一个特定组蛋白化学修饰发生的所有位置。(4)确定特定基因的 mRNA 水平,即特定基因的表达水平。这种数据已经从众多人类和小鼠细胞系中获得。总的来说,已经在多年的国际项目如 ENCODE、modENCODE、Roadmap Epigenomics、Human Cell Atlas 等中收集了几千次这样的实验。每个实验在整个基因组中都有数万到数十万个数据点。
一系列语言模型,最终形成了基于变压器的 Enformer 工具(Avsek 等人,2021),已经被开发用来接受基因附近的 DNA 序列作为输入,并输出基因组中任何基因的细胞类型特异性表达水平。Enformer 的训练任务如下:给定一个包含 100,000 个核苷酸的基因组区域和一个特定细胞类型,它被训练来预测该区域的每种实验数据类型,包括开放或紧密堆积的染色质状态、当前的组蛋白修饰、特定结合的转录因子和基因表达水平。语言模型非常适合这个任务:与掩蔽语言建模不同,Enformer 以监督方式进行训练,从 DNA 序列中同时预测所有轨迹。通过整合注意力机制,它可以有效地汇总来自远离区域的信息(最多 100,000 个核苷酸),以预测给定位置的状态。实际上,Enformer 学习了这些多样分子实体之间的所有复杂关联。

图 14. Enformer 与早期系统 Basenji2 的预测与实验结果的比较。 图像已获得对应作者 Ziga Avsec 的许可。
Enformer 仅通过序列预测基因表达表现相当不错。如果我们使用特定实验测定(例如 CAGE 实验)在同一细胞系中测量所有基因的基因表达,同一实验的两次重复通常在平均 0.94 的相关性水平上。一个达到这一水平的计算方法可能会减少收集实验数据的需求。Enformer 目前并未完全达到这一水平,其与实验数据的相关性仅为 0.85,这相当于两个实验复制的误差的三倍。然而,随着数据的积累和模型的改进,这一性能有望得到改善。值得注意的是,Enformer 能够预测由不同个体的突变或通过 CRISPR 实验引入的人为突变引起的基因表达变化。然而,它仍然存在一些局限性,例如在预测远端增强子(距离基因起始位置较远的增强子)的效果方面表现不佳(Karollus 等人,2023 年),以及正确确定个人变异在基因表达中的影响方向(Sasse 等人,2023 年)。这些缺点可能是由于训练数据不足所致。随着数据生成步伐的加快,预计在可预见的未来,我们将拥有能够以实验水平精度从序列预测基因表达的大型语言模型(LLMs),并因此准确和全面地描绘参与分子生物学中心法则的复杂分子机制的模型。
如上所述,细胞内的 DNA 呈复杂的分层三维染色质结构,这在基因调控中起到作用,因为只有开放染色质内的基因才会被表达。Orca(Zhou 2022)是一种最近的语言模型,基于卷积编码器-解码器架构,从 Hi-C 实验提供的接近数据预测 3D 基因组结构。这些数据集跨越细胞系或组织样本的整个基因组,在这些数据中,接近的基因组位置会被揭示为将 DNA 片段粘合到每个区域的 DNA 片段。Orca 模型是一个分层多级卷积编码器和多级解码器,用于预测从 4kb 到 1024kb 分辨率的 9 个级别的 DNA 结构,适用于与最长人类染色体长度相当的输入 DNA 序列。
基础模型
基础模型是大型深度学习架构,如 OpenAI 的基于 Transformer 的 GPT 模型,它们编码了来自多源的大量知识。研究人员和从业者可以针对特定任务对这些预训练模型进行微调,从而为各种下游应用提供高性能系统。在分子生物学中已经开始出现几种基础模型。在这里,我们将简要介绍两种刚刚作为 biorXiv 预印本出现的模型。(因为这些论文尚未经同行评审,我们暂时不报告它们与其他最先进方法的比较表现。)
scGPT 是为单细胞转录组学、染色质可及性和蛋白质丰度设计的基础模型。该模型是在来自 1000 万个人类细胞的单细胞数据上训练的。每个细胞包含大约 20,000 个人类基因的表达值。该模型学习这个大型细胞 × 基因矩阵的嵌入,这些嵌入提供了对潜在细胞状态和活跃生物通路的洞见。作者们创新地将 GPT 方法论适应到这个非常不同的环境中(图 15)。具体来说,在基因组中基因的顺序,不像在句子中单词的顺序那样具有意义。因此,虽然 GPT 模型是训练来预测下一个词,但在单细胞数据中,“下一个基因”的概念是不清楚的。作者们通过训练模型根据基因提示(已知基因值的集合)和细胞提示生成数据来解决这个问题。从已知的基因开始,模型预测剩余的基因以及它们的置信度值。对于 K 次迭代,将它们分成 K 个箱,置信度值最高的 1/K 个基因作为下一次迭代的已知基因。训练完成后,scGPT 可以针对多个下游任务进行微调:批次校正、细胞注释(其中地面真实是各种细胞类型的注释集合)、扰动预测(预测在给定一组基因实验扰动后的细胞状态)、多组学(其中每个层次,转录组、染色质组、蛋白质组,被视为不同的语言)、生物通路预测等等。

- 图 15. scGPT 概述。 A. scGPT 的工作流程。该模型在来自细胞图谱的大量细胞上进行训练,然后针对聚类、批次校正、细胞注释、扰动预测和基因网络推断等下游应用进行微调。B. 输入嵌入。有基因令牌、基因表达值和条件令牌。C. Transformer 层。图像由王博提供。
核苷酸变换器是一个基础模型,专注于原始 DNA 序列。这些序列被分割成每个六个字符的词(长度为 6 的 k-mer),并使用 BERT 方法进行训练。训练数据包括参考人类基因组、3200 个额外的多样化人类基因组以捕捉人类基因组学中的变异,以及 850 个其他物种的基因组。然后将核苷酸变换器应用于 18 个下游任务,这些任务包括许多之前讨论的任务:启动子预测、剪接位点供体和受体预测、组蛋白修饰等。预测通过探测完成,其中不同层的嵌入作为简单分类器(如逻辑回归或感知器)的特征,或者通过轻量、计算上不昂贵的微调来实现。
展望未来
解读连接我们基因组与体内各种细胞中的复杂生物分子途径的生物分子代码,并随后与环境互动结合到我们的生理功能,并不需要 AGI。虽然有许多 AI 任务可能会出现在未来,但我认为理解分子生物学并将其与人类健康联系起来并不是其中之一。LLMs 已经足够满足这一总体目标。
这里有一些我们没有要求 AI 做的任务。我们并没有要求它生成新内容;而是要求它学习现有生物系统的复杂统计特性。我们没有要求它以目标导向的方式导航复杂环境,维持内部状态,形成目标和子目标,或通过与环境的互动学习。我们没有要求它解决数学问题或发展深层反事实推理。然而,我们确实期望它学习一步因果关系:如果发生某种突变,特定基因会失效。如果这个基因表达不足,级联中的其他基因会增加或减少。通过简单的一步因果关系,这些关系可以通过在不同模态(如 DNA 变异、蛋白质丰度和表型)之间的相关性进行三角测量(这是一种称为孟德尔随机化的技术)以及越来越普遍的大规模扰动实验来学习,LLMs 将有效地建模细胞状态。这一联系从基因组一端延伸到表型另一端。
总之,今天的 LLMs 已经足够先进,能够对分子生物学进行建模。进一步的方法学改进始终受欢迎。然而,障碍不再是深度学习方法;更重要的障碍是数据。
幸运的是,数据变得越来越便宜且丰富。DNA 测序技术的进步将测序一个人类基因组的成本从第一个基因组的 30 亿美元,降至几年前约 1000 美元,现在更低至 200 美元。这些成本下降同样适用于所有以 DNA 测序为主要读出的分子检测,包括用于定量基因表达、染色质结构、组蛋白修饰、转录因子结合等的检测,以及过去 10-20 年中开发的数百种其他巧妙检测。单细胞技术、蛋白质组学、代谢组学、脂质组学及其他-组学检测的进一步创新,允许对 DNA 与人类生理之间的各种分子层进行越来越详细和高效的测量。


图 16. 英国生物库。 英国生物库是一个大规模的生物医学数据库和研究资源,包含约 50 万名英国志愿者的深入遗传和健康信息。这些参与者在 2006–2010 年招募时年龄均在 40 至 69 岁之间。收集的数据包括血液、尿液和唾液样本,详细的参与者背景、生活方式和健康信息,以及通过健康记录获取的随后的医疗历史。对于一部分参与者,还收集了成像数据(大脑、心脏、腹部、骨骼和关节)。470,000 人的外显子组数据于 2022 年 6 月发布,所有人的全基因组数据预计将在 2023 年底前发布。图片由英国生物库提供,并获得许可使用。
那么,这些数据如何整合在一起呢?一种关键的数据倡议是将大量志愿参与者集合起来,以深入探索他们的-omic 数据、表型和健康记录。一个领先的例子是英国生物库项目(UKB),这是一个大规模生物库、生物医学数据库和研究资源,包含来自 50 万名英国参与者的全面遗传和健康信息(见图 16)。参与者的生物样本已在广泛同意下收集,并持续生成大量数据。几乎所有参与者的外显子(基因组中编码蛋白质的部分)已被释放,整个基因组的数据也将随之发布。此外,包括 COVID-19 抗体数据、代谢组学、端粒、影像学、基因型、临床测量、初级护理、疼痛问卷等在内的各种数据类型均可获得。额外的数据类型也在不断添加。UKB 数据对任何研究目的的研究者开放。All Of Us是美国的一个类似倡议,截至目前已对 25 万名参与者的基因组进行了测序。FinnGen(芬兰基因组计划)旨在创建一个包含 50 万芬兰参与者的类似生物库,这极具价值,因为在基因上更为同质的队列中进行遗传研究要容易得多。deCODE Genetics在冰岛领导了类似的努力,超过三分之二的冰岛成年人口参与了这一计划。还有其他已测序参与者的队列,包括由 Regeneron Pharmaceuticals(一个私人倡议)测序的数百万个外显子,以及全球许多国家的多项国家级倡议。
癌症特别是一种基因组疾病,许多公司正在建立大量关于癌症患者和癌症样本的基因组信息,以及额外的临床信息。虽然覆盖这一领域超出了范围,但值得一提的是Tempus,一家基于人工智能的精准医学公司,拥有大量不断增长的癌症临床和分子数据,Foundation Medicine,一家提供全面基因组分析检测的分子信息公司,用以识别患者癌症中的分子变化,并与相关靶向治疗、免疫治疗和临床试验匹配,以及GRAIL和Guardant Health,这两家开创性的诊断公司专注于通过“液体活检”或分析患者血液样本中的基因组内容来早期检测肿瘤,这些样本中常常含有癌细胞的分子脱落。每家公司都有大量不断增长的患者队列数据。
除了这些队列倡议,还有许多其他大规模的数据倡议。值得注意的是,人类细胞图谱项目已经为来自 6,300 名捐献者的 4200 万个人类细胞生成了基因表达数据。ENCODE 项目,作为一个庞大的功能基因组数据集,涵盖了数百个人类细胞系和各种分子量,生成了关于基因表达、染色质可及性、转录因子结合、组蛋白标记、DNA 甲基化等的数据。
LLMs 完美适用于整合这些数据。展望未来,我们可以设想一个庞大的 LLM 跨越所有这些数据集进行整合。那么,这样一个模型的架构和训练可能会是什么样的呢?让我们进行一个思想实验,尝试拼凑出它的全貌:
-
基因组中的基因,包括像不同异构体这样的重要变异,进行标记化处理。
-
不同类型的细胞和组织被进行标记化处理。
-
人类表型,如疾病状态、临床指征和药物治疗依从性,也被进行标记化处理。
-
DNA 序列在固定长度的核苷酸水平上进行标记化处理。
-
基因组中的位置性信息将基因与核苷酸内容连接起来。
-
蛋白质序列使用氨基酸字母表进行标记化处理。
-
来自人类细胞图谱和其他单细胞数据集的数据以类似 GPT 的自回归方式或类似 BERT 的掩码语言建模方式训练 LLM,突出细胞类型特异性和细胞状态特异性的基因通路。
-
ENCODE 和类似的数据教会 LLM 将不同的分子信息层如原始 DNA 序列及其变异、基因表达、甲基化、组蛋白修饰、染色质可及性等以细胞类型特异的方式关联起来。每一层都是一种独特的“语言”,具有不同的丰富性和词汇量,提供独特的信息。LLM 学会在这些语言之间进行翻译。
-
PrimateAI-3D 的非人类灵长类基因组学倡议以及其他物种测序工作等项目为 LLM 提供了关于人类基因组中突变潜在良性或有害效应的知识。
-
整个蛋白质组,包括蛋白质变异,丰富了蛋白质的 3D 结构信息,这些信息要么是实验获得的,要么是由 AlphaFold、RoseTTAfold 和其他结构预测方法预测的。
-
来自英国生物库(UKB)及其他队列的数据使 LLM 能够将基因组变异信息和其他分子数据与人类健康信息相关联。
-
LLM 利用参与者的完整临床记录来理解常规实践及其效果,并将其与所有数据集中的其他“语言”关联起来。
-
LLM 利用基础生物学、遗传学、分子科学和临床实践的广泛现有文献,包括所有已知的基因与表型的关联。
开发这样的 LLM 是一项重大的挑战,与 GPT 系列的 LLM 不同。这需要技术创新来表示和整合各种信息层,并扩大模型处理的 token 数量。这样的 LLM 具有广泛的潜在应用。列举几项:
-
临床诊断。 它可以利用所有可用的患者信息,包括其基因组、其他测量、完整的临床历史和家庭健康信息,帮助医生做出准确的诊断,即使是对罕见疾病。它在诊断罕见疾病和癌症亚型时尤其有用。
-
药物开发。 LLM 可以帮助识别不同临床指示的有前景的基因和通路靶点,预测对某些药物可能有反应的个体,以及那些不太可能受益的个体,从而提高临床试验的成功率。它还可以协助药物分子开发和药物重新定位。
-
基础分子生物学。 每个分子信息层将以类似语言翻译的方式与其他层连接,LLM 将被探测以提供显著的预测能力。虽然深度学习模型的解释是一个挑战,但研究社区不断取得令人印象深刻的进展,致力于使 AI 可解释。在 OpenAI4 的最新进展中,GPT-4 刚刚被部署来解释 GPT-2 的每个神经元的行为。(https://openai.com/research/language-models-can-explain-neurons-in-language-models)
-
额外实验的建议。 该模型可以用于识别训练数据中的“空白”,例如细胞类型、分子层次,或特定遗传背景或疾病指示的个体,这些在其他数据中预测的置信度较低。
在开发这些技术时,必须考虑潜在的风险,包括与患者隐私和临床实践相关的风险。患者隐私仍然是一个重大关注点。这对于 LLM 尤其如此,因为根据模型的能力,原则上可以通过包含部分数据的提示或其他信息来检索用于训练模型的参与者数据。因此,在用参与者数据训练 LLM 时,特别重要的是要获得针对这些模型预期用途和访问的适当知情同意。
然而,许多人,如英国生物银行队列中的参与者,愿意慷慨地分享他们的数据和生物样本,为研究和社会提供了巨大的好处。至于临床实践,目前尚不清楚 LLM 是否可以独立用于诊断和治疗建议。这些模型的主要目的是辅助,而非取代医疗专业人员,提供强大的工具以便医生验证和审计医学信息。引用 Isaac Kohane 的话:“信任,但要验证”(Lee, Goldberg, Kohane 2023)。
那么,完全实施一个大型语言模型(LLM)以桥接遗传学、分子生物学和人类健康的障碍是什么呢?主要障碍是数据的可获得性。功能基因组数据的生产,如来自 ENCODE 和人类细胞图谱的数据,需要加快进度。幸运的是,生成这些数据的成本正在迅速下降。同时,多组学队列和临床数据必须被生产并公开获取。这个过程需要参与者的同意,同时考虑到合法的隐私问题。然而,除了不可剥夺的隐私权之外,还有一个同样重要的参与者数据透明权:许多人想要通过分享他们的数据来做出贡献。这在罕见遗传疾病和癌症患者中尤为真实,他们希望通过贡献研究和治疗发展来帮助其他患者。英国生物银行的成功证明了参与者在数据共享方面的慷慨,旨在对人类健康产生积极影响。
结论
分子生物学不是一组整洁的概念和明确的原则,而是经过数亿年的试错积累而成的数万亿个小事实的集合。人类生物学家擅长讲述故事,将这些事实转化为描述和故事,这有助于直观理解和实验规划。然而,要将生物学转变为计算科学,需要大量数据的获取和具有适当容量的计算模型来从数据中提取这些数万亿个生物学事实。凭借 LLM 和数据获取速度的加快,我们确实离拥有准确的计算预测模型来连接我们的 DNA、细胞生物学和健康还差几年的时间。我们可以合理地预计,在接下来的 5-10 年内,大量生物医学诊断、药物发现和健康寿命公司和项目将使这些模型在人体健康和医学中得到应用,产生巨大的影响。我们也很可能会见证跨越从基因组到医学信息的开放基础模型的发展。这些模型将极大地加速研究和创新,并促进精准医学。
致谢
我要感谢 Eric Schadt 和 Bo Wang 对本文的许多建议和修改。我还要感谢 Anshul Kundaje、Bo Wang 和 Kyle Farh 提供的想法、意见和图表。我还要感谢 Lukas Kuderna 为本手稿创建了灵长目系统发育图。我是 Seer, Inc 的雇员,但这里表达的所有观点都是我自己的。
参考文献
Avsek Z 等人。通过整合远程相互作用从序列有效地预测基因表达。《自然方法》2021 年。
Baek M 等人。使用三轨神经网络准确预测蛋白质结构和相互作用。《科学》2021 年。
Baek M 等人。使用 RoseTTAFold2 高效准确地预测蛋白质结构。biorXiv doi: doi.org/10.1101/2023.05.24.542179,2023 年。
Bubeck S 等人。具有 GPT-4 的人工智能通用智能的火花:初步实验。arXiv:2303.12712,2023 年。
Cui 等人。scGPT:利用生成式人工智能构建单细胞多组学的基础模型。biorXiv doi.org/10.1101/2023.04.30.538439,2023 年。
Dalla-Torre H 等人。核苷酸变换器:构建和评估人类基因组学的稳健基础模型。biorXiv doi.org/10.1101/2023.01.11.523679,2023 年。
Devlin J 等人。BERT:用于语言理解的深度双向转换器的预训练。arXiv:1810.04805,2018 年。
Fiziev P 等人。罕见的穿透突变导致常见疾病的严重风险。《科学》2023 年。
Gao 等人。人类和灵长目可容许基因变异的景观。《科学》2023 年。
Jaganathan 等人。使用深度学习从原始序列预测剪接。《细胞》2019 年。
Jumper,J.,Evans,R.,Pritzel,A. 等人。使用 AlphaFold 高度准确地预测蛋白质结构。自然 596,2021 年 583-589 页。
Karollus 等人。当前基于序列的模型捕捉启动子中的基因表达决定因素,但大多数忽略远端增强子。《基因组生物学》2023 年。
Kong 等人。新生突变率及父亲年龄对疾病风险的重要性。《自然》2012 年。
Lee P,Goldberg C,Kohane I. 医学界的人工智能革命:GPT-4 及其后续。Pearson,2023 年。
Lin Z 等人。用语言模型预测原子级蛋白质结构的进化尺度。《科学》2023 年。
Lyayuga Lisanza S 等人。使用 RoseTTAFold 序列空间扩散的蛋白序列和结构的联合生成。biorXiv doi.org/10.1101/2023.05.08.539766,2023 年。
Sasse 等人。使用序列-表达深度神经网络,个性化基因表达预测有多远?biorXiv doi.org/10.1101/2023.03.16.532969,2023 年。
Sundaram 等人。使用深度神经网络预测人类突变的临床影响。《自然遗传学》2018 年。
Varadi M 等. AlphaFold 蛋白质结构数据库:通过高精度模型大幅扩展蛋白质序列空间的结构覆盖。核酸研究,2021 年。
Wang S 等. 通过超深度学习模型准确预测蛋白质接触图。PLoS 计算生物学,2017 年。
Wolfram S. ChatGPT 在做什么……以及它为何有效?Wolfram 媒体公司,2023 年。
Zhou J. 基于序列的三维基因组结构建模,从千碱基到染色体尺度。自然遗传学,2022 年。
大型语言模型,MirrorBERT——将模型转化为通用的词汇和句子编码器
了解镜像增强如何生成数据,并在语义相似性任务中提升 BERT 的性能
·
关注 发表在 Towards Data Science ·7 分钟阅读·2023 年 12 月 12 日
--
介绍
毫无疑问,类似 BERT 的模型在现代自然语言处理应用中扮演着基础性角色。尽管它们在下游任务上的表现非常出色,但大多数模型在特定问题上并不是那么完美,需要进行微调。从原始预训练模型构建的嵌入通常会导致指标远离最先进的结果。同时,微调是一个繁重的过程,通常需要至少几千个标注数据样本才能使模型更好地理解领域数据。在某些情况下,当我们无法简单地收集已标注的数据或数据价格高昂时,这一问题就会变得很棘手。
MirrorBERT 旨在克服上述问题。与标准的微调算法不同,MirrorBERT 通过智能地增强初始数据而不依赖外部知识来进行自我监督。这种方法使 MirrorBERT 在 语义相似性问题 上表现出可比的性能。此外,通过使用其创新的对比学习技术,MirrorBERT 可以在不到一分钟的时间内将像 BERT 或 RoBERTa 这样的预训练模型转换为通用词汇编码器!
大型语言模型:RoBERTa — 一种鲁棒优化的 BERT 方法
了解用于 BERT 优化的关键技术
towardsdatascience.com
借助官方的 MirrorBERT 论文,我们将深入了解其关键细节,以理解其内部工作原理。所获得的知识是通用的,因为讨论的技术也可以用于处理相似性任务的其他 NLP 模型。
方法论
简单来说,MirrorBERT 是与 BERT 模型相同的模型,只不过在其学习过程中引入了几个步骤。让我们逐一讨论这些步骤。

MirrorBERT 学习过程
1. 自我重复
如其名称所示,MirrorBERT 只是简单地重复初始数据。

自我重复
然后,这些重复的数据用于进一步构建相同字符串的两种不同嵌入表示。
2. 数据增强
论文的作者提出了两种直观的技术,这些技术略微修改了数据集文本。根据他们的说法,在绝大多数情况下,这些文本破坏不会改变其含义。
2.1. 输入增强
给定一对字符串 (xᵢ, x̄ᵢ),算法随机选择其中一个,并应用 随机跨度掩码,即用 [MASK] 令牌随机替换文本中固定长度 k 的子字符串。

通过随机跨度掩码进行输入增强
2.2. 特征增强
随机跨度掩蔽操作在句子/短语级别。为了使模型也能在词级任务中表现良好,还需要另一种机制来处理较短的文本片段。特征增强通过使用 dropout 解决了这个问题。
Dropout 过程指的是在某一网络层中关闭一定百分比的 p 神经元。这可以视为将网络中对应的神经元置零的等效操作。
论文的作者建议使用 dropout 进行数据增强。当一对字符串 (xᵢ, x̄ᵢ) 传递到具有 dropout 层的网络中时,如果每次前向传递时 dropout 层总是禁用不同的神经元,则它们的输出表示会略有不同。
使用 dropout 进行特征增强的一个很棒的方面是 dropout 层已经包含在 BERT / RoBERTa 架构中,这意味着无需额外的实现!
虽然随机跨度掩蔽仅应用于数据集中的每第二个对象,但 dropout 是应用于所有对象的。
3. 对比学习
对比学习 是一种机器学习技术,旨在学习数据表示,使得相似的对象在嵌入空间中彼此接近,而不相似的对象彼此远离。
对比学习实现的一种方法是使用 对比损失函数。MirrorBERT 选择的损失函数是 InfoNCELoss。让我们理解它是如何工作的。
InfoNCELoss
初看起来,InfoNCELoss 的公式可能令人畏惧,所以让我们一步步逐渐理解。
- 两个向量之间的余弦相似度衡量它们彼此对齐的程度,取值范围从 -1 到 1,值越大表示相似度越高。

两个向量之间的余弦相似度
2. 为了更好地理解下一步,必须了解 InfoNCELoss 使用了 softmax 转换,其中温度参数 T 控制输出 softmax 分布的平滑度。这就是为什么相似度除以 T。
关于 softmax 温度的更多信息,请参阅 这篇文章 以了解更详细的解释。

余弦相似度除以温度
3. 与标准 softmax 公式一样,预测(相似度)会被转换为指数形式。

余弦相似度的指数
4. 在普通的 softmax 公式中,分子包含了类别概率的指数,而分母则是所有分布概率的指数和。在 InfoNCELoss 中,类似度的公式也遵循类似的逻辑:
-
分子包含两个稍微修改的相同字符串 (xᵢ, x̄ᵢ) 的指数相似度,可以被视为 正例。
-
分母包括 xᵢ与所有其他数据集字符串 xⱼ之间的指数相似度之和,这可以看作是所有负面样本的集合。

余弦相似度的 softmax 公式。Nᵢ表示除 xᵢ和 x̄ᵢ之外的所有数据集字符串。
- 在理想情况下,我们希望相同字符串(xᵢ,x̄ᵢ)之间的相似度高,而 xᵢ与其他字符串 xⱼ之间的相似度低。如果这是真的,则上述公式中的分子会增加,而分母会减少,从而使整个表达式增大。
损失函数的工作方式是相反的:在理想情况下,它们取较小的值,而在较差的情况下,它们会对模型进行严厉惩罚。为了使上述公式与这一损失原则兼容,让我们在整个表达式前添加负对数。

负的 softmax 相似度对数。这个表达式可以看作是单个字符串 xᵢ的损失值。
- 上一步的表达式已经对应于单个字符串 xᵢ的损失值。由于数据集由多个字符串组成,我们需要考虑所有这些字符串。为此,我们需要对所有字符串求和这个表达式。

InfoNCELoss
得到的公式正是InfoNCELoss!
InfoNCELoss 试图将相似的对象聚集在一起,同时在嵌入空间中推开不相似的对象。
SBERT中使用的三元组损失是对比学习损失的另一个示例。
## 大型语言模型:SBERT — Sentence-BERT
了解 siamese BERT 网络如何准确地将句子转换为嵌入
towardsdatascience.com
训练资源
关于 MirrorBERT 的一个令人惊讶的事实是,它不需要大量的数据进行微调。此外,这些数据不需要是外部的,因为整个训练过程是自监督的。
研究人员报告称,为了微调词汇表示,他们仅使用每种语言中最频繁的 1 万词汇。对于句子级任务,使用 1 万个句子。
训练细节
MirrorBERT 训练的细节如下:
-
温度在句子级任务中设置为T = 0.04,在词汇级任务中设置为T = 0.2。
-
在随机跨度掩蔽中,k设置为 5。
-
Dropout 设置为p = 0.1。
-
使用 AdamW 优化器,学习率为2e-5。
-
批量大小设置为 200(或 400 个重复样本)。
-
词汇模型训练 2 个周期,句子级模型训练 1 个周期。
-
不同于对所有输出标记表示进行均值池化,创建了[CLS]标记表示。
单次 MirrorBERT 训练周期仅需 10–20 秒。
评估
作者通过应用镜像微调在一组基准测试上评估了指标。结果在三种任务类型上进行了报告:词汇级别、句子级别和跨语言。在每种任务中,MirrorBERT 展现了与其他 BERT 类微调模型相当的性能。
结果还显示,10k 到 20k 的训练样本范围是微调的最优范围。随着训练样本数量的增加,模型的性能逐渐下降。
结论
镜像微调实际上就像一个魔法咒语:与繁重的微调程序不同,镜像框架所需的时间要少得多,而且不需要外部数据,其性能与 BERT、SBERT 或 RoBERTa 等其他微调模型在语义相似性任务上相当。
因此,MirrorBERT 可以将类似 BERT 的预训练模型转变为通用编码器,以高效捕捉语言知识。
资源
除非特别说明,所有图像均由作者提供
大型语言模型,StructBERT — 将语言结构融入预训练
通过融入更好的学习目标来使模型更智能
·
关注 发表在 Towards Data Science ·5 分钟阅读·2023 年 11 月 22 日
--
介绍
自 BERT 首次出现以来,它在各种 NLP 任务中显示了惊人的效果,包括情感分析、文本相似度、问答等。从那时起,研究人员就尝试通过修改架构、增加训练数据、扩充词汇量或改变层的隐藏大小等方式,使 BERT 变得更加高效。
了解 BERT 如何构建最先进的嵌入
[towardsdatascience.com
尽管创建了其他强大的基于 BERT 的模型如 RoBERTa,研究人员发现了另一种提升 BERT 性能的有效方法,这将在本文中讨论。这导致了新模型 StructBERT 的发展,该模型在顶级基准上自信地超越了 BERT。
StructBERT 的想法相对简单,重点在于略微修改 BERT 的预训练目标。
在本文中,我们将深入探讨 StructBERT 论文的主要细节,并理解最初修改的目标。
预训练
在很大程度上,StructBERT 具有与 BERT 相同的架构原则。然而,StructBERT 提出了两个新的预训练目标,以扩展 BERT 的语言知识。该模型在此目标下进行训练,配合掩码语言建模。让我们看看下面的这两个目标。
1. 单词句子目标
实验表明,掩码语言建模(MSM)任务在 BERT 设置中发挥了关键作用,帮助其获得广泛的语言知识。预训练后,BERT 可以以高准确率正确猜测掩码词。然而,它无法正确重建单词被打乱的句子。为实现这一目标,StructBERT 开发者通过部分打乱输入标记来修改 MSM 目标。
与原始 BERT 一样,输入序列会被标记化、掩码处理,然后映射到标记、位置和段嵌入。这些嵌入会被求和以产生组合嵌入,然后输入到 BERT。
在掩码处理过程中,与 BERT 一样,15% 的随机选择标记会被掩盖并用于语言建模。但在掩码处理后,StructBERT 随机选择 5% 的 K 个连续未掩盖标记,并在每个子序列内对其进行打乱。默认情况下,StructBERT 对三元组(K = 3)进行操作。

三元组打乱示例
当计算最后一层隐藏层时,掩码和打乱标记的输出嵌入被用于预测原始标记,同时考虑其初始位置。
最终,单词句子目标与 MLM 目标以相等的权重结合。
2. 句子结构目标
下一句预测,作为 BERT 的另一个预训练任务,被认为相对简单。掌握它并不会显著提升 BERT 在大多数下游任务上的表现。因此,StructBERT 研究人员通过让 BERT 预测句子顺序来增加这一目标的难度。
通过在文档中取一对连续句子 S₁ 和 S₂,StructBERT 使用它们以三种可能的方式之一构建训练样本。每种方式发生的概率均为 1 / 3:
-
S₂ 后跟 S₁ (标签 1);
-
S₁ 后跟 S₂ (标签 2);
-
从随机文档中抽取另一个句子 S₃,并且 S₁ (标签 0) 跟在其后。
这三种过程中的每一种都会生成一个有序的句子对,然后将它们连接起来。在第一个句子开始前添加了 [CLS] 标记,并使用 [SEP] 标记标记每个句子的结束。BERT 将该序列作为输入,并输出最后隐藏层的嵌入集。
[CLS] 嵌入的输出,最初在 BERT 中用于下一句预测任务,现在在 StructBERT 中用于正确识别与输入序列构建方式相对应的三种可能标签之一。

训练样本的组成
最终目标
最终目标由词和句子结构目标的线性组合组成。

BERT 预训练包括词和句子结构目标
StructBERT 设置
BERT 和 StructBERT 的所有主要预训练细节相同:
-
StructBERT 使用与 BERT 相同的预训练语料库:英语维基百科(2500M 单词)和 BookCorpus(800M 单词)。分词由 WordPiece 分词器完成。
-
优化器:Adam(学习率 l = 1e-4,权重衰减 L₂ = 0.01,β₁ = 0.9,β₂ = 0.999)。
-
学习率热身在总步骤的前 10% 中进行,然后线性减少。
-
在所有层上使用 Dropout(α = 0.1)。
-
激活函数:GELU。
-
预训练过程运行 40 个周期。
StructBERT 版本
与原始 BERT 类似,StructBERT 提供了基础版和大型版。所有主要设置,如层数、注意力头、隐藏层大小和参数数量,都分别对应 BERT 的基础版和大型版。

StructBERT 基础版与 StructBERT 大型版的比较
结论
通过引入一对新的训练目标,StructBERT 在 NLP 中达到新的极限,在各种下游任务上始终超越 BERT。研究表明,这两个目标在 StructBERT 设置中发挥了不可或缺的作用。词结构目标主要提高了模型在单句问题上的性能,使 StructBERT 能够重建词序,句子结构目标则改善了对句间关系的理解,这对句子对任务特别重要。
资源
除非另有说明,否则所有图片均为作者提供
大型模型遇见大数据:Spark 和 LLMs 的和谐
原文:
towardsdatascience.com/large-models-meet-big-data-spark-and-llms-in-harmony-5e2976b69b62
数据工程与生成式 AI
使用 Apache Spark 和大型语言模型的逐步指南
·发布于 Towards Data Science ·6 分钟阅读·2023 年 12 月 5 日
--

该图像由 Midjourney 生成。
生成式 AI,包括大型语言模型(LLMs),正在革新人类生活的各个方面。在过去五年里,生成式 AI 从一个研究项目发展成为许多人日常生活中的实际应用。作为一名对生成式 AI 感兴趣的数据工程师,我一直在问自己,这项技术为我的工作和数据工程应用带来了什么?对于工程师来说,生成式 AI 和 LLMs 的一些常见应用包括自动编码、协助文档编写等等。但是,在这里,我正在评估生成式 AI 和 LLMs 在数据工程中的一些更专业的使用。如果你对这个话题感兴趣,请阅读这篇文章,并关注我在 Medium 和 Linkedin 上的其他文章,以获取更多关于其他用例的内容。
LLMs:强大的变革工具
数据工程师喜欢结构化和抽象化数据,这已经不是什么新鲜事了。但是,世界上充满了需要数据工程师关注的非结构化和杂乱的数据。对非结构化数据的转换总是很复杂,有时用传统工具是无法完成的。历史上,其中一种具有挑战性的非结构化数据是文本(例如评论、评价、对话)。对文本的简单转换并不难,但复杂的转换可以从文本中提取更多信息,我们可以生成更丰富的数据集。
复杂文本转换的例子可能包括从文本中提取姓名和对象、对评论或意见进行情感分析、在存储的文本中掩盖重要信息(例如私人数据、用户数据)、从一种语言翻译到标准语言、文本摘要等。好消息是现在的 LLM 可以完成所有这些转换。因此,我相信在数据工程中,LLM 的应用之一就是作为复杂数据(如文本)的转换函数。
在这篇文章中,我将通过 Apache Spark 展示 LLM 的这一能力,Apache Spark 是一个强大的分布式数据处理系统。更具体地说,我将使用 Hugging Face 的一个小型 LLM(t5-small)作为 Apache Spark UDF 函数,并对一个示例数据集应用特定的转换(情感分析)。
设置项目
首先,我们需要一个 Apache Spark 系统。你可以在本地系统上安装 Apache Spark,也可以使用 AWS EMR 等服务。我使用了 AWS EMR 并设置了一个小型集群进行测试。有很多关于如何在本地机器上安装 Apache Spark 或使用 AWS EMR 的文章,你可以参考它们。在这里,我假设你已经在系统上安装了 Apache Spark。
我还为这个项目选择了 Python 3.8。我们将使用 Hugging Face 库,它们与 Python 的兼容性很好。如果你使用 AWS EMR,你的系统上应该已经有 Python 3.8。否则,你需要安装 Python 3.8。
现在你已经在系统上安装了 Apache Spark 和 Python 3,接下来是安装测试此项目所需的库。运行以下 pip 命令来安装库。我们在这里安装 PySpark 以运行 Spark 任务,以及来自 Hugging Face 的 Transformers 库(这使我们能够使用包括 LLM 在内的数百种模型)。
pip3 install torch==1.13.1
pip3 install transformers==4.30.2
pip3 install pyspark==3.4.2
pip3 install urllib3==1.26.6
编码时间
首先创建一个新的 Python 文件。我将其命名为 spark_llm_test.py。首先,我们需要导入库并创建一个新的 Spark 会话。
from pyspark.sql import SparkSession, Row
from pyspark.sql.functions import udf
from pyspark.sql.types import StructType, StructField, StringType, LongType
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
# Create a Spark session
spark = SparkSession.builder.appName("FlanT5Seq2SeqExample").getOrCreate()
对于这个测试,我创建了一个仅有两列的虚拟表。第一列是索引,第二列是随机句子。我为这个项目设定的目标是对第二列进行情感分析。显然,在实际使用情况下,你可能会从一个包含更复杂数据的表(例如 Hive 表)中读取 Spark 数据帧。
# Create an Example Spark DataFrame
schema = StructType([
StructField("id", LongType(), nullable=False),
StructField("sentence", StringType(), nullable=False)
])
data = [
Row(1, "It is a good test for Spark."),
Row(2, "Spark DataFrames are powerful."),
Row(3, "LLMs could be very slow."),
Row(4, "It is a naive statement.")
]
input_df = spark.createDataFrame(data, schema=schema)
为此,我使用了“Flan T5”模型,该模型经过针对各种任务的微调,包括情感分析。如你所见,使用 Hugging Face Transformers 库,模型和分词器的设置非常简单。
# Loading Flan T5 Model and Tokenizer
model = AutoModelForSeq2SeqLM.from_pretrained("google/flan-t5-small")
tokenizer = AutoTokenizer.from_pretrained("google/flan-t5-small")
现在是定义我们的 Spark UDF 函数的时候了。Spark 将在每一行数据上调用这个函数,以按照我们在函数中指定的方式转换数据。
关于这个 UDF 函数,有一点很重要。正如你所见,我们没有在 UDF 函数内部实例化模型和分词器。原因是定义模型和分词器(即使是这样一个小的 LLM 模型)需要较大的处理开销。这意味着每次调用这个 UDF 函数时,模型和分词器都需要在工作节点上加载,然后进行转换(这里是推断)。这无疑会显著减慢我们的处理速度。为避免这种情况,我们在 UDF 函数外部加载了模型和分词器。
# Defining the Spark UDF
def t5_seq2seq_udf(input_text):
prompt = f"sentiment of the text: {input_text}"
input = tokenizer(prompt, return_tensors="pt")
output = model.generate(**input)
output_text = tokenizer.decode(output[0], skip_special_tokens=True)
return output_text
最后,我们需要注册 UDF 函数并创建一个新列以保存情感分析结果。
t5_udf = udf(t5_seq2seq_udf, returnType=StringType())
results_df = input_df.withColumn('output_column', t5_udf(input_df['sentence']))
results_df.show(truncate=False)
下面,你可以找到完整的代码。
from pyspark.sql import SparkSession, Row
from pyspark.sql.functions import udf
from pyspark.sql.types import StructType, StructField, StringType, LongType
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
# Create a Spark session
spark = SparkSession.builder.appName("T5Seq2SeqExample").getOrCreate()
# Create an Example Spark DataFrame
schema = StructType([
StructField("id", LongType(), nullable=False),
StructField("sentence", StringType(), nullable=False)
])
data = [
Row(1, "It is a good test for Spark."),
Row(2, "Spark DataFrames are powerful."),
Row(3, "LLMs could be very slow."),
Row(4, "It is a naive statement.")
]
input_df = spark.createDataFrame(data, schema=schema)
# Loading t5 Model and Tokenizer
model = AutoModelForSeq2SeqLM.from_pretrained("google/flan-t5-small")
tokenizer = AutoTokenizer.from_pretrained("google/flan-t5-small")
# Defining the Spark UDF
def t5_seq2seq_udf(input_text):
prompt = f"sentiment of the text: {input_text}"
input = tokenizer(prompt, return_tensors="pt")
output = model.generate(**input)
output_text = tokenizer.decode(output[0], skip_special_tokens=True)
return output_text
t5_udf = udf(t5_seq2seq_udf, returnType=StringType())
results_df = input_df.withColumn('output_column', t5_udf(input_df['sentence']))
results_df.show(truncate=False)
如果你使用 spark-submit 命令运行代码,你将得到以下结果。正如你所见,Flan T5 模型在识别正面句子与负面句子方面表现良好。
+---+------------------------------+-------------+
|id |sentence |output_column|
+---+------------------------------+-------------+
|1 |It is a good test for Spark. |positive |
|2 |Spark DataFrames are powerful.|positive |
|3 |LLMs could be very slow. |negative |
|4 |It is a naive statement. |negative |
+---+------------------------------+-------------+
Spark 和 LLMs 的未来
恭喜!你已成功将 Flan T5 作为 Spark 作业执行。为了简化,我们省略了一些细节,包括主节点和工作节点如何传输模型权重以进行推断。这个话题可能会在未来的讨论中涉及,即 Spark 是否可以高效地用于使用 LLMs 进行推断。
此外,我们在这里使用了 Spark 进行数据转换(一个应用)。但如果 Spark 可以在模型开发过程中高效使用,那将是 Apache Spark 的另一个重大胜利。
最后,这只是一个简单的批处理示例。Spark 和 LLMs 的另一个应用可能是流处理,并对这些数据流提供实时分析。毫无疑问,这是使用 Spark 和 LLMs 的良好开端,但这两个工具之间的应用和协作是无穷无尽的。
大型图像模型中的最新 CNN 核
对可变形卷积网络中的最新卷积核结构、DCNv2、DCNv3 进行的高级概述
·
关注 发表在 Towards Data Science ·7 分钟阅读·2023 年 8 月 4 日
--
澳大利亚拜伦湾灯塔 | 作者拍摄
随着 OpenAI 的 ChatGPT 的显著成功引发了大型语言模型的热潮,许多人预测下一次突破将发生在大型图像模型领域。在这个领域,视觉模型可以像我们目前对 ChatGPT 的提示一样被提示去分析甚至生成图像和视频。
最新的大型图像模型深度学习方法已分为两个主要方向:基于卷积神经网络(CNN)的方法和基于变换器的方法。本文将重点介绍 CNN 方面,并提供改进的 CNN 内核结构的高级概述。
目录
-
DCN
-
DCNv2
-
DCNv3
1. 可变形卷积网络(DCN)
传统上,CNN 内核在每一层中应用于固定位置,导致所有激活单元具有相同的感受野。
如下图所示,为了在输入特征图 x 上执行卷积,计算每个输出位置 p0 的值是通过内核权重 w 和在 x 上的滑动窗口之间的逐元素乘法和求和来完成的。滑动窗口由网格 R 定义,这也是 p0*** 的感受野。*** 在 y 的同一层中的所有位置,R 的大小保持不变。

常规 3x3 内核的卷积操作。
每个输出值的计算如下:

论文中的常规卷积操作函数。
其中 pn 枚举滑动窗口(网格 R)中的位置。
RoI(兴趣区域)池化操作也在每一层中固定大小的 bin 上操作。对于 (i, j)-th bin 包含 nij 像素,其池化结果计算如下:

论文中的常规平均 RoI 池化函数。
每一层中的 bin 的形状和大小再次保持不变。

3x3 bin 的常规平均 RoI 池化操作。
因此,对于编码语义的高层,如具有不同尺度的对象,这两个操作特别具有挑战性。
DCN 提出了可变形卷积和可变形池化,这些方法对建模这些几何结构更加灵活。两者都在 2D 空间域上操作,即操作在通道维度上保持不变。
可变形卷积

3x3 内核的可变形卷积操作。
给定输入特征图 x,对于输出特征图 y 中的每个位置 p0,DCN 在枚举常规网格 R 中的每个位置 pn 时添加 2D 偏移量 △pn。

论文中的可变形卷积函数。
这些偏移量从前面的特征图中学习得到,通过在特征图上应用额外的卷积层获得。由于这些偏移量通常是小数,它们通过双线性插值来实现。
可变形 RoI 池化
类似于卷积操作,池化偏移量 △pij 被添加到原始 bin 位置。

可变形 RoI 池化函数来自 论文。
如下图所示,这些偏移量通过原始池化结果后的全连接(FC)层进行学习。

可变形平均 RoI 池化操作,使用 3x3 bin。
可变形位置敏感(PS)RoI 池化
在将可变形操作应用于 PS RoI 池化 (Dai et al., n.d.) 时,如下图所示,偏移量应用于每个分数图而不是输入特征图。这些偏移量通过卷积层而不是全连接层进行学习。
位置敏感 RoI 池化 (Dai et al., n.d.):传统的 RoI 池化丢失了每个区域所代表的对象部分的信息。PS RoI 池化通过将输入特征图转换为每个对象类别的 k² 分数图来保留这些信息,其中每个分数图表示一个特定的空间部分。因此,对于 C 个对象类别,总共有 k² (C+1) 个分数图。

3x3 可变形 PS RoI 池化的示意图 | 来源于 论文。
2. DCNv2
尽管 DCN 允许更灵活地建模感受野,但它假设每个感受野中的像素对响应的贡献是相等的,这种情况通常不成立。为了更好地理解贡献行为,作者使用三种方法来可视化空间支持:
-
有效的感受野:节点响应关于每个图像像素的强度扰动的梯度
-
有效的采样/ bin 位置:网络节点相对于采样/ bin 位置的梯度
-
错误界限显著区域:逐步遮蔽图像的部分,以找到与整个图像产生相同响应的最小图像区域
为了将可学习的特征幅度分配到感受野中的位置,DCNv2 引入了调制可变形模块:

DCNv2 卷积函数来自 论文,符号已修订以匹配 DCN 论文中的符号。
对于位置 p0,偏移量 △pn 及其幅度 △mn 通过应用于相同输入特征图的单独卷积层进行学习。
DCNv2 通过为每个 (i,j)-th bin 添加一个可学习的幅度 △mij 来修订可变形 RoI 池化。

DCNv2 池化函数来自 论文,符号已修订以匹配 DCN 论文中的符号。
DCNv2 还扩展了变形卷积层的使用,以替代 ResNet-50 中 conv3 到 conv5 阶段的常规卷积层。
3. DCNv3
为了减少参数大小和内存复杂度, DCNv3 对卷积核结构进行了以下调整。
深度可分离卷积将传统卷积解耦为:1. 深度卷积:每个输入特征通道与一个滤波器单独进行卷积;2. 点卷积:在通道上应用 1x1 卷积。
作者提出将特征幅度 m 设为深度卷积部分,将投影权重 w 在网格中的位置共享作为点卷积部分。
2. 灵感来自于组卷积(Krizhevsky,Sutskever 和 Hinton,2012)
组卷积:将输入通道和输出通道拆分为多个组,并对每个组分别应用卷积。
DCNv3(Wang 等,2023)提出将卷积拆分为 G 组,每组具有单独的偏移量 △pgn 和特征幅度 △mgn。
因此,DCNv3 被表述为:

DCNv3 卷积功能来自于论文,符号修订以匹配 DCN 论文中的符号。
其中 G 是卷积组的总数,wg 与位置无关,△mgn 通过 softmax 函数进行标准化,使得网格 R 上的和为 1。
性能
迄今为止,基于 DCNv3 的 InternImage 在检测和分割等多个下游任务中表现优越,如下表所示,以及 papers with code 上的排行榜。有关更详细的比较,请参考原论文。

COCO val2017 上的目标检测和实例分割性能。FLOPs 是用 1280×800 输入测量的。AP’ 和 AP’ 分别表示框 AP 和掩码 AP。“MS”代表多尺度训练。来源于论文。

来自paperswithcode.com的目标检测排行榜截图。

来自paperswithcode.com的语义分割排行榜截图。
摘要
在这篇文章中,我们回顾了常规卷积网络的核结构及其最新改进,包括可变形卷积网络(DCN)及两个更新版本:DCNv2 和 DCNv3。我们讨论了传统结构的局限性,并突出了在前一版本基础上的创新进展。欲深入了解这些模型,请参阅参考文献中的论文。
致谢
特别感谢Kenneth Leung,他激发了我创作这篇文章的灵感,并分享了惊人的想法。对 Kenneth、Melissa Han和 Annie Liao 致以深深的谢意,他们对改进这篇文章做出了贡献。你们的深刻建议和建设性反馈显著提升了内容的质量和深度。
参考文献
Dai, J., Qi, H., Xiong, Y., Li, Y., Zhang, G., Hu, H. 和 Wei, Y. (n.d.). 可变形卷积网络。[在线] 可在:arxiv.org/pdf/1703.06211v3.pdf.
Zhu, X., Hu, H., Lin, S. 和 Dai, J. (n.d.). Deformable ConvNets v2: 更可变形,效果更佳。[在线] 可在:arxiv.org/pdf/1811.11168.pdf.
Wang, W., Dai, J., Chen, Z., Huang, Z., Li, Z., Zhu, X., Hu, X., Lu, T., Lu, L., Li, H., Wang, X. 和 Qiao, Y. (n.d.). InternImage: 利用可变形卷积探索大规模视觉基础模型。[在线] 可在:arxiv.org/pdf/2211.05778.pdf [访问日期 2023 年 7 月 31 日]。
Chollet, F. (n.d.). Xception: 深度学习中的深度可分离卷积。[在线] 可在:arxiv.org/pdf/1610.02357.pdf.
Krizhevsky, A., Sutskever, I. 和 Hinton, G.E. (2012). 使用深度卷积神经网络的 ImageNet 分类。ACM 通讯, 60(6), 页 84–90. doi:https://doi.org/10.1145/3065386.
Dai, J., Li, Y., He, K. 和 Sun, J. (n.d.). R-FCN: 基于区域的全卷积网络进行物体检测。[在线] 可在:arxiv.org/pdf/1605.06409v2.pdf.
数据质量的层次
原文:
towardsdatascience.com/layers-of-data-quality-320bf3770db5
解决数据问题的地点和方法
·发表于Towards Data Science ·阅读时间 8 分钟·2023 年 7 月 18 日
--

随着对生成性人工智能和大型语言模型的兴趣激增,数据质量也重新受到关注。并不是说这个领域需要太多帮助:像Monte Carlo、Soda、Bigeye、Sifflet、Great Expectations和dbt Labs等公司一直在开发各种解决方案,从专有的到开源的。虽然这些解决方案中的一些是直接竞争对手,但它们并不全是解决相同的问题。例如,定义一个明确的dbt 测试以确保某一列包含唯一值,与对指标进行异常检测(例如,你的 dim_orders 过程一天生成了 500,000 条记录,而通常是 50,000 条)的情况是非常不同的。数据可能以各种显著而复杂的方式失败。
你可能听说过数据质量的维度;我特别喜欢理查德·法恩沃斯的见解¹,但一个简单的谷歌搜索会得到数十种不同的看法。然而,核心思想是数据在某些方面可能是“正确的”,但在其他方面却是错误的。如果你的数据是正确的但却迟到,它还有价值吗?如果这些数据从客观上来看是错误的,但却是一致的²呢?这是数据产品管理中的一个重要方面,以及识别你的利益相关者的优先事项。
很多人关注数据的格式错误、缺失、延迟、不完整等问题,却很少关注数据质量问题的根本原因。我们花费了过多的时间测试和观察数据本身,而不是寻求改进产生、转化和使用这些数据的系统。我想深入探讨这些数据质量问题的“层面”、解决方案以及应该参与解决这些问题的团队。
第 1 层:数据生产
所有数据都来源于某处,而源头通常是数据质量问题的根本原因。遵循“垃圾进,垃圾出”的原则,你无法从糟糕的源系统数据中制造出有用的数据产品。
这一层有三个基本的数据质量问题来源:模式漂移、语义漂移以及系统可用性和可靠性。它们都非常重要,但会导致不同的数据质量故障。更重要的是,它们需要不同的解决方案,通常不同的团队需要参与解决这些问题。
模式变化在许多方面是最容易识别和解决的。如果产品工程师(无论是内部还是通过供应商)更改了你使用的表的模式,你的下游流程可能会中断。由于 SaaS API 遵循更成熟的变更管理协议,这种情况在 SaaS API 中较少出现。不过,这并不是对开发者的贬低;通常,这些内部团队甚至不知道下游团队正在使用他们的数据资产。打破 ETL 流程的模式变化通常是组织沟通不畅的症状。
语义漂移更具隐蔽性,对整个企业的影响也更广泛。如果你的开发团队更改了某个字段的度量单位怎么办?或者如果他们更改了通过下拉菜单填充字段的值怎么办?你的产品并不是唯一暴露的东西。像 SFDC、Zuora、NetSuite 和 Zendesk 这样的企业系统的运营团队也可能更改他们使用操作系统的方式,这可能会产生影响。数据质量问题也可能完全是偶然的;我想我们都见过因错别字而输入了数十亿美元交易的可怜销售代表。更有趣的是,你可能会遇到模式变化和语义漂移的结合;例如,is_enterprise_customer 替代了 customer_type 字段中的有效值‘Enterprise’。与模式变化一样,沟通是缺失的一环。
最终,源系统的可用性和可靠性也是一个问题。如果源系统宕机,数据可能在一段时间内无法生成。根据架构的不同,数据可能仍会生成,但用于检索的端点可能会宕机。在这些情况下,数据质量故障与模式变化和语义漂移的性质完全不同。
Chad Sanderson 一直是 数据契约³ 概念的倡导者:数据从(通常是)操作系统发出的正式、程序化强制定义。数据契约是解决模式变化和某些类型语义漂移的良好起点。请注意,数据契约不涉及 SaaS 运维团队;他们需要一个单独的变更管理系统。
产品团队熟悉如 Datadog 和 Splunk 等可观测性和监控解决方案。SaaS 供应商通常有状态门户,并且一些提供服务中断时的通知。挑战在于,这些系统故障经常不会超出其开发和/或运维团队,即使它们对下游数据团队至关重要。创建良好的流程以在整个组织中沟通故障和问题与提供我们初始可见性的可观测性系统同样重要。
干预措施:数据契约、通信渠道、变更管理、系统可观测性和监控以及向下游消费者的通信
首选团队:产品工程师、SaaS 系统操作员(例如,AR 专家、客户经理)、平台工程师、平台操作员(例如,SaaS 管理员、SaaS 商业分析师)
第 2 层:数据处理 — 提取/转换
假设源系统数据是原始的,或者至少足够干净以供使用。仍然有很多问题可能发生。这次,我们面临三方面的挑战:开发中的逻辑错误、低韧性设计和平台稳定性。
在开发数据管道时,有时我们会出现错误。也许 Airflow 任务没有建立正确的依赖关系,或者分析工程师在连接操作中犯了错误。具体错误的结果可能是管道完全崩溃(通常以戏剧性和火爆的方式);然而,微妙的逻辑错误可能在系统中存在多年,可能会时常轻微影响指标,或者偶尔以重大方式影响指标。
在需求收集过程中,了解利益相关者的优先级至关重要。在某些情况下,如果数据缺失或存在某种质量问题,管道失败是至关重要的。在其他情况下,交付速度比绝对准确性更重要。另一方面,显示部分或不完整的数据是否可以,还是只有在数据经过处理后的某个时间段才展示所有数据?数据产品团队和利益相关者之间的对齐不一致可能意味着我们没有满足期望。
然后,还有一个现实问题,与产品和 SaaS 解决方案一样,我们的数据管道实际上运行在平台上。如果 Snowflake 宕机怎么办?如果 BigQuery 或 EMR 中有影响资源分配的错误怎么办?这些情况会发生,尽管平台团队可能知道,但下游团队仍存在沟通/可见性差距。
解决方案虽然丰富,但实施起来并不一定容易。像单元测试和集成测试这样的策略对在生产前捕捉错误至关重要。数据领域的工具仍然滞后于更广泛的软件工程生态系统,数据团队文化也在追赶中。
另一方面,许多适用于源系统的可观察性解决方案也适用于数据处理系统。然而,关键点在于确保这种可见性在整个组织中共享。可以通过通知渠道或仪表板进行沟通,但无论采取何种方式,我们都需要确保受影响的团队知道发生了什么。
同样值得指出的是,数据生产者引起的相同问题也适用于核心资产开发者;他们的消费者,如分析师、数据科学家和 BI 开发者,也同样容易受到模式变化、语义漂移和系统(ETL)故障的影响。
干预措施:单元测试、集成测试、清晰的设计文档、沟通渠道、变更管理
首选团队:数据工程师、分析工程师、数据平台工程师
第三层:数据消费 — 分析、人工智能和机器学习
在数据和信息之间的最后一公里,仍然有很多出错的空间。在这里,问题更多的是关于数据如何被理解和使用,而不是如何处理、生产和转化数据。具体来说,我想关注应用技术中的错误和对数据本身的误解。
统计学、机器学习和人工智能是复杂的。真的非常复杂。在选择模型时,有很多因素需要考虑:变量是连续的还是离散的、数据分布、异方差性、样本大小等,甚至还有数十种其他因素。即使你做出了所有正确的决策,也可能会在你的机器学习库中出现 obscure 实现错误。
数据使用者也可能不了解上游数据的背景。也许他们没有查看数据目录,或者根本没有数据目录。熟悉数据资产是关键,但并不总是足以避免这些错误,并最终得出不正确的结论。
此外,无法保证业务中的某个人会正确解读仪表板、分析或报告。这也不一定是出于恶意。大多数数据使用者在数据领域之外有其他工作。数据团队通常深陷细节,以至于未能理解哪些指标可能不够明确。
你可能已经注意到这里的主题是以人为本的解决方案。被聘用从事高级分析和机器学习角色的人员需要接受正确的培训和积累经验才能成功。至于在既有资产基础上创建产品,数据目录甚至仅仅与数据资产拥有者的对话也可以提供巨大帮助。最后,确保像仪表板和报告这样的文档清晰至关重要,而温馨交接和利益相关者教育则更为理想。
干预措施:数据目录、仪表板标签、利益相关者教育、从业者教育和培训
主要团队:数据分析师、数据科学家、BI 开发人员、机器学习工程师
结论
虽然认识到数据可能出现错误的不同方式(维度)很重要,但同样重要的是理解数据错误的具体方式和位置(层次)。我们讨论了源头错误(层次 1)、数据处理过程中的错误(层次 2)以及使用过程中的错误(层次 3)。我们还讨论了干预措施以及哪些团队离问题和解决方案最近。
数据质量对于从数据中获得价值至关重要,无论是通过分析、自动化还是外部数据产品。数据质量维度帮助我们识别数据在哪些方面存在问题;它是否过时?是否有误?但同样重要的是理解数据为何出现问题。了解“为何”可以为我们提供解决方案的洞察,无论是立即纠正还是长期的系统性修正。
¹理查德·法恩沃斯。 (2020 年 6 月 28 日)。 数据质量的六个维度——以及如何应对它们。 towardsdatascience.com/the-six-dimensions-of-data-quality-and-how-to-deal-with-them-bdcf9a3dba71
²本·斯坦西尔。 (2023 年 6 月 9 日)。 我只想知道有什么不同。 benn.substack.com/p/all-i-want-is-to-know-whats-different
³查德·桑德森。 (2023 年 1 月 25 日)。 仓库的数据合同。 dataproducts.substack.com/p/data-contracts-for-the-warehouse
懒惰评估使用递归 Python 生成器
原文:
towardsdatascience.com/lazy-evaluation-using-recursive-python-generators-9ee6af0dd803
递归函数可以使用“懒惰评估”吗?——是的,它们可以——通过使用 Python 的生成器函数!
·发表于 Towards Data Science ·5 分钟阅读·2023 年 1 月 4 日
--

生成自稳定扩散
我们都熟悉 Python 的生成器及其所有优点。但是,如果我告诉你我们可以通过将生成器与递归结合起来,使它们变得更好呢?所以,让我们看看如何利用它们来实现“懒惰递归”,并提升我们在 Python 中使用生成器的效果!
为什么还要费心?
在我们进入代码之前,先问自己“为什么还要费心?我们真的需要递归生成器吗?”答案是……这要视情况而定。自然,递归生成器将会兼具生成器和普通递归函数的优缺点。
对于生成器而言,使用它们的首要原因是“懒惰”评估——即一次计算一个元素,而不是一次计算全部。至于递归,它对某些算法或问题的解决方式是自然的,例如树的遍历。
因此,递归生成器适合的情况自然是递归算法,这些算法可能处理大量数据或元素,因此如果“急切”地运行,会消耗大量内存。
基本示例
现在我们知道了为什么我们会使用递归生成器,让我们来看一个“简单”的例子,以理解如何编写一个递归生成器:
这个简短的函数——正如其名称所示——产生连续的二进制数字。当调用时,它首先简单地生成 "1",然后进行递归调用。递归调用也生成 "1",但这会作为 prefix 传递给之前的非递归调用。在计算出前缀后,非递归调用生成两个值 "10" 和 "11"。之后,递归调用继续执行,进行另一个递归调用,深入一层,循环继续——前缀向上冒泡,因此外部帧总是先以 "0" 结束,然后是 "1"。
现在,如果我们运行它,我们会得到:
当涉及递归时,仅仅解释代码并不足以真正理解发生了什么。所以,如果你不确定 binary_counter 实际上是如何工作的,那么我们来逐步演练一下:
上面的修改版本添加了一个 depth 参数和几个打印语句,以帮助演示代码的作用。如果我们现在调用这段代码,我们会得到以下结果:
我希望这能让事情变得更清晰一些,如果不行,可以考虑手动逐步演练,或者使用你选择的 IDE 中的调试器,以便实时查看堆栈帧和变量。
你可能也会问,“以这种方式计算二进制数字有什么意义?” —— 答案是,没有什么好的理由。确实有更好、更易读的方法来做这件事,但我认为它很好地演示了这个概念。话虽如此,让我们现在看看更多如何使用递归生成器的有用示例…
将其发挥到最佳使用
在递归的例子中,显而易见的候选者是各种数学函数,或者——如这里所示——组合数学,具体来说是 幂集:
这里的函数使用了类似于之前二进制计数器的流程。为了更好地理解它,我们可以将递归部分翻译为:
-
对于更小的幂集 (
sequence[1:]) 中的每个结果... -
… 返回未使用的值 (
[sequence[0]]) + 结果 (item) -
… 然后仅返回结果 (
item)
虽然数学函数可以通过递归很好的实现,但它们并不是我们日常使用的内容,所以现在我们来看看其他不同的东西:
上面的 accumulate 函数计算其列表参数的累积总和(和)。虽然上面的代码可以工作,但我不建议在实际中使用它,因为你可以且应该使用以下代码:
在讨论 itertools 的话题时,我们也来看看如何重新实现其他常见函数:
flatten 函数可以用来展开嵌套列表(或其他可迭代对象)。我在这里展示这个函数是因为它使用了与之前不同的流程——它利用 try/ except 来分离基本/非递归部分和递归代码。
然而,如果需要,也可以在没有 try/ except 的情况下重写:
说到递归时,我们显然要展示递归数据结构的示例,在这个案例中是二叉树:
上述代码实现了一个二叉树,包括__iter__方法中的递归生成器。inorder函数中也实现了相同的功能,使递归调用更加清晰。
为了展示上述代码的使用方法,让我们创建一个简单的树结构:
与(二叉)树的遍历类似,我们也可以在遍历 JSON 时使用递归生成器:
以这种方式遍历 JSON 可能是实用的,如果你处理的是非常大的数据,一次性加载会消耗大量内存。
到现在为止,你可能已经掌握了这些奇怪的生成器是如何工作的,但我们还是看看调用上述代码时会发生什么:
最后,另一个常常递归遍历的树状数据结构是文件树:
在这里我们实现了get_paths函数,该函数递归地生成指定路径中的所有文件。话虽如此,对于这个任务,使用内置的path.rglob("*")会更好,因为它也返回生成器。
此外,虽然在这个实例中不太有用,但值得注意的是,send()函数也可以与递归生成器一起使用。因此,上述函数的另一种实现方式:
这种生成器的风格在你需要控制递归或与协程通信时会很有用。
结论
我认为本文中的示例展示了许多可以递归表达的优雅解决方案。然而,优雅并不总是意味着更好。通常,使用不那么“优雅”或简洁的解决方案会产生更具可读性和普遍更好的代码。
所以,我们不要试图“强行”将递归生成器融入代码中,应该只在适当的情况下使用——也就是说——在实现从延迟评估中受益的递归函数时。
这篇文章最初发布于 martinheinz.dev
成为会员并阅读 Medium 上的每一个故事。你的会员费直接支持我和你阅读的其他作家。 你还将获得 Medium 上每个故事的完全访问权限。
[## 使用我的推荐链接加入 Medium - Martin Heinz
阅读 Martin Heinz(以及 Medium 上的其他数千名作家)的每一个故事。你的会员费直接支持…
medium.com](https://medium.com/@martin.heinz/membership?source=post_page-----9ee6af0dd803--------------------------------)
你可能还会喜欢…
使用 Python 和 Google API 来自动化你在 Gmail、Google Drive、日历等上的所有操作的速成课程:
[towardsdatascience.com 在这里 [## 你可能没听说过的 Python 魔法方法]
你可能不知道的许多 Python 魔法方法 — 让我们来了解它们的功能以及如何使用它们…
towardsdatascience.com
我们应该了解的重要 MySQL 数据定义语言(DDL)命令,用于管理我们的表
原文:
towardsdatascience.com/learn-common-database-managing-commands-as-a-data-engineer-4d199cfb15ae
作为数据工程师,学习常用的数据库管理命令
·发表于 Towards Data Science ·阅读时间 8 分钟·2023 年 2 月 28 日
--

图片由 geralt 提供于 Pixabay
作为数据工程师,检查和更新表的模式是我们的日常工作。虽然网上已经有很多教程,但很少有教程关注应该遵循的约定。SQL 非常灵活,可以以“稳健”的方式工作。你可以使用小写或大写的查询,并以任何你想要的方式命名数据库/表/列/索引/视图。然而,代价是可读性降低,维护变得困难,因为不同的人可能会以不同的方式编写 SQL 查询。
在这篇文章中,我们将介绍一些用于管理 MySQL 表模式的常用命令,重点讲解每个操作的约定和最佳实践。它可以作为新数据工程师的手册(需进行必要的调整)。
准备工作
我们将使用 Docker 启动一个 MySQL 8 容器,它将作为这篇文章的 MySQL 服务器:
# Create a volume to persist the data.
$ docker volume create mysql8-data
# Create the container for MySQL.
$ docker run --name mysql8 -d -e MYSQL_ROOT_PASSWORD=root -p 13306:3306 -v mysql8-data:/var/lib/mysql mysql:8
# Connect to the local MySQL server in Docker.
$ docker exec -it mysql8 mysql -u root -proot
mysql> SELECT VERSION();
+-----------+
| VERSION() |
+-----------+
| 8.0.31 |
+-----------+
1 row in set (0.00 sec)
我们将在这个 MySQL 服务器中创建我们的数据库(在 MySQL 中也称为模式)和表。首先,让我们创建一个数据库来存储我们的虚拟数据:
CREATE DATABASE sales;
数据库名称应具有描述性、简洁明了,并且不包含特殊字符,除了下划线之外。它最好是小写的,以便与 MySQL 关键字区分开来。相同的命名约定也适用于表名和列名。
在这篇文章中,我们将使用 DBeaver 来编写查询并查看表数据。
创建表
现在让我们创建第一个表,它将存储客户数据。
CREATE TABLE `sales`.`customers` (
`id` SMALLINT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
`job` VARCHAR(50) DEFAULT '',
PRIMARY KEY (`id`),
KEY `ix_name` (`name`)
);
你可以使用单数或复数表名。我更喜欢使用复数,因为表可以被视为数据记录的容器。
可以通过以下命令查找现有表的数据定义语言(DDL)查询:
SHOW CREATE TABLE `sales`.`customers`;
推荐在编写查询时指定模式名称,以便更好地支持自动补全。
默认情况下,MySQL 对数据库名称、表名称和别名是区分大小写的。然而,对列名称不区分大小写。因此,列名称的命名可以非常灵活。然而,我们应该遵循一些相同数据库的命名约定。无论你使用驼峰命名还是蛇形命名,你只需要保持一致。然而,你可能会根据你的后端编程语言有一些偏好。例如,作为 Python 开发者,我们更倾向于使用蛇形命名。
此外,如你所见,我们将前缀ix(表示索引)加到索引名称上。我们通常应该避免给列名称加前缀,以使查询更简洁。然而,我们应该为索引或约束提供前缀,以便在某些错误中更具指示性。我们很少需要显式引用索引或约束,因此简洁不是问题。
一些常用的前缀是:
-
ix用于索引。 -
fk用于外键约束。 -
uq用于唯一键约束。
此外,还有一些关于如何命名索引或约束的约定:
-
索引:
ix_%(column_0_label)s -
外键:
fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s -
唯一键:
uq_*%(table_name)s*_*%(column_0_name)s*,
我们将在本帖中稍后介绍其中的一些。
重命名或复制一个表
我们可以使用RENAME命令重命名一个表:
RENAME TABLE sales.customers TO sales.customer -- Not executed as I prefer plural
;
如果你想将一个表从一个模式复制到另一个模式,你需要分两步进行。例如,让我们创建一个新的customers_data模式,并将customers表复制到那里。
CREATE DATABASE customers_data;
CREATE TABLE customers_data.customers LIKE sales.customers;
INSERT INTO customers_data.customers
SELECT * FROM sales.customers
;
这样,旧表的数据类型和索引将保留在新表中。如果你按照下面所示使用CREATE TABLE … SELECT…,则数据类型(实际上会被推断)和索引会丢失,这在几乎所有情况下都是不希望的:
CREATE TABLE customers_data.customers_copy_direct
SELECT * FROM sales.customers
;
你可以使用SHOW CREATE TABLE命令检查新创建表的模式。
如果你需要将一个表从一个数据库(不同的主机或端口)移动到另一个数据库,你可以将表导出到一个 SQL 文件中,然后在另一个数据库中加载:
mysqldump -h HOST_1 -P PORT_1 -u USERNAME_1 -p \
--single-transaction --skip-triggers --skip-column-statistics \
SCHEMA_1 TABLE_NAME > TABLE_NAME.sql
mysql -h HOST_2 -P PORT_2 -u USERNAME_2 -p SCHEMA_2 < TABLE_NAME.sql
安装了 MySQL 客户端后,可以使用mysqldump:
sudo apt-get update
sudo apt-get install mysql-client
两个数据库的主机可以相同。在这种情况下,端口会不同。
注意mysqldump指定的选项,这在大多数情况下是必要的。特别是,使用--single-transaction时,表在导出时不会被锁定。
添加/删除/更改列
为了演示命令,让我们执行以下操作。这些操作可能没有太大意义,重点是使用的命令:
-
使用
DROP删除name列, -
使用
ADD重新添加name列, -
使用
MODIFY更改列的数据类型;
ALTER TABLE `sales`.`customers`
DROP `name`,
ADD `name` VARCHAR(50) NOT NULL AFTER `id`,
MODIFY `job` VARCHAR(100) DEFAULT ''
;
注意,我们可以使用 AFTER column_name 关键字来改变列的顺序。如果列应该被改为第一个,则需要使用 FIRST 关键字,而不是 AFTER column_name。
例如,让我们将 name 列改为第一个:
ALTER TABLE `sales`.`customers`
MODIFY `name` VARCHAR(50) NOT NULL FIRST
;
重命名列
我们可以仅使用 RENAME COLUMN A TO B 来重命名列而不更改数据类型:
ALTER TABLE sales.customers
RENAME COLUMN `job` TO `address`
;
注意,COLUMN 关键字可以在 ADD、DROP、MODIFY 以及即将介绍的 CHANGE 命令中省略,但在 RENAME 命令中不能省略。
我们还可以使用 CHANGE 命令重命名列并更改数据类型。让我们将 name 改为 username 并将长度改为 100:
ALTER TABLE sales.customers
CHANGE `name` `username` VARCHAR(100) NOT NULL
;
使用外键
让我们创建两个新表来演示外键的使用。一个新的 products 表将存储产品信息,orders 表将存储客户的订单:
CREATE TABLE `sales`.`products` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
`price` DECIMAL(12,2),
PRIMARY KEY (`id`),
KEY `ix_name` (`name`),
KEY `ix_price` (`price`)
);
CREATE TABLE `sales`.`orders` (
`customer_id` SMALLINT NOT NULL,
`product_id` INT NOT NULL,
`quantity` SMALLINT NOT NULL,
PRIMARY KEY (`customer_id`, `product_id`),
KEY `ix_product_id` (`product_id`),
KEY `ix_quantity` (`quantity`),
CONSTRAINT `fk_orders_customer_id_customers` FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_orders_product_id_products` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE
);
注意,customers 和 products 表中的 id 列没有前缀,但在 orders 表中有前缀。这是因为那里的 ID 有两个,一个用于客户,另一个用于产品。
如果检查 id 列的数据类型,你会发现它们与 customers 和 products 表中的数据类型相同。这是添加外键的要求,因为列的数据类型必须在当前表和引用表中相同。
使用 customer_id 和 product_id 创建了一个复合主键。注意,我们需要为 product_id 创建一个单独的索引,但不需要为 customer_id 创建,因为它被复合主键覆盖,因为它是复合键中的第一个。
还要注意外键约束的命名约定,遵循了此链接中介绍的命名约定。
要检查表的索引,我们可以运行以下两个查询之一:
SHOW INDEX FROM sales.orders;
SELECT
s.TABLE_SCHEMA,
s.TABLE_NAME,
s.INDEX_NAME,
s.COLUMN_NAME,
s.SEQ_IN_INDEX
FROM INFORMATION_SCHEMA.STATISTICS s
WHERE 1
AND s.TABLE_SCHEMA = 'sales'
AND s.TABLE_NAME = 'orders'
;
注意,上述查询不会返回外键。如果需要检查外键,我们需要运行以下查询:
SELECT
TABLE_SCHEMA,
TABLE_NAME,
CONSTRAINT_NAME,
COLUMN_NAME,
REFERENCED_TABLE_SCHEMA,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE 1
AND TABLE_SCHEMA = 'sales'
AND TABLE_NAME = 'orders'
;
注意,两个特殊的表 INFORMATION_SCHEMA.STATISTICS 和 INFORMATION_SCHEMA.KEY_COLUMN_USAGE 是系统表,通常以大写字母引用。
添加和删除索引及约束
让我们演示如何添加和删除索引和约束。我们不能修改索引或约束,因为一旦条件改变,索引/约束必须重新生成。
首先,让我们删除 orders 表的主键、索引和外键:
ALTER TABLE sales.orders
DROP PRIMARY KEY,
DROP INDEX `ix_product_id`,
DROP INDEX `ix_quantity`,
DROP FOREIGN KEY `fk_orders_customer_id_customers`,
DROP FOREIGN KEY `fk_orders_product_id_products`
;
注意我们指定如何删除外键的方式。应该使用 DROP FOREIGN KEY …,而不是 DROP CONSTRAINT …。
现在让我们将它们添加回来:
ALTER TABLE sales.orders
ADD PRIMARY KEY (`customer_id`, `product_id`),
ADD KEY `ix_product_id` (`product_id`),
ADD KEY `ix_quantity` (`quantity`),
ADD CONSTRAINT `fk_orders_customer_id_customers` FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`) ON DELETE CASCADE,
ADD CONSTRAINT `fk_orders_product_id_products` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE
;
语法类似于上面的 DDL 查询。
创建或更新视图
MySQL 视图与表的工作方式相同,并且被视为表。视图通常包含一个或多个表中的一些选定列,基于一些过滤条件。它可以用来快速查看一个或多个表中的特定数据,而无需编写 JOIN 和 WHERE 条件。
让我们为 orders 表创建一个视图,以便可以直接获取订单的客户和产品详情:
CREATE OR REPLACE VIEW sales.orders_with_details AS
SELECT
o.customer_id,
c.username,
c.address,
o.product_id,
p.name,
p.price,
o.quantity,
p.price * o.quantity AS total
FROM sales.orders o
JOIN sales.customers c
ON c.id = o.customer_id
JOIN sales.products p
ON p.id = o.product_id
;
SELECT * FROM sales.orders_with_details;
视图的名称应能指示其用途。在这种情况下,orders_with_details 比 orders_view 更好,因为前者更能说明视图中包含的内容。
编写 SQL 查询的标准
我们应以易于阅读的方式编写 SQL 查询。虽然没有严格的标准,但遵循以下规则将使你的查询更易于阅读和维护:
-
将所有 SQL 关键字写成大写字母。
-
将所有数据库名称、列名和别名写成小写字母。
-
为你的表提供标准缩写作为别名。例如,
products=>p,product_attributes=>pa等。不要使用任意的表别名,因为这会使查询变得更加难以阅读。 -
将
SELECT、FROM、JOIN、WHERE、GROUP BY、ORDER BY等语句另起一行。 -
每个
ON和AND条件应另起一行。 -
相同的格式化标准适用于嵌套查询。
你可以在 DBeaver 或 VS code(配合一些 SQL 扩展)中自动格式化 SQL 查询。不过,格式化效果并不完美,我们通常需要根据上述规则进行一些手动调整。
在这篇文章中,我们介绍了一些用于管理 MySQL 表模式的常见且实用的命令。我们涵盖了如何创建和更新数据库、表、列、索引和视图,并重点介绍了每个操作的约定和最佳实践,这些可以作为新数据工程师的起始指导。
相关文章:
-
一些使用 DBeaver 的技巧 — 一款通用数据库工具
通过“刻意练习”学习数据科学(或任何技能)
原文:
towardsdatascience.com/learn-data-science-or-any-skills-with-deliberate-practice-47eb21bd2c8

图片由Brett Jordan提供,来源于Unsplash
回顾我做对了什么
·发表于Towards Data Science ·8 分钟阅读·2023 年 8 月 10 日
--
最近我读了一本书,名为顶尖:来自新科学的专业秘密,作者是 Anders Ericsson 和 Robert Pool。这本书挑战了“专业来自天赋”的常见神话。相反,通过在不同领域的众多例子,它证明了顶级表现是可以通过一种称为“刻意练习”的技巧来实现的,技能也可以通过这种技巧来学习。在书中,作者提供了各种技能的例子,如音乐表演、战时飞机战斗以及像国际象棋和围棋这样的策略游戏。阅读这本书让我感到似曾相识,因为我在反思自己快速掌握数据科学技能的学习历程时有了类似的感觉。
在我之前的文章中,我分享了我如何成为数据科学家的经历:
我的经济学博士之旅
towardsdatascience.com
以及我遵循的七个原则,以成为更好的数据科学家:
设定我的北极星
towardsdatascience.com
在这篇文章中,我想分享在Peak中讨论的作为刻意练习的技巧,并通过我自己的学习历程来说明它们。希望对那些希望学习数据科学或其他技能的人有所帮助。
刻意练习与幼稚练习
首先,什么是刻意练习?与幼稚练习相比,幼稚练习本质上是重复做某事,并希望重复本身能够完成工作。例如,每天弹奏同一首歌来成为吉他大师,或者每天以相同的速度跑同一条小径来为马拉松做准备。确实,重复带来了熟悉感。你可能会在弹奏一首歌方面成为专家,通过每日跑步减轻体重。然而,对一首歌或小径的专业知识并不会使你成为顶级吉他演奏者或跑者。毫无疑问,我们有爱好,只是想做些有趣的事情,而不是为了竞争或谋生,但对于那些你确实希望提高并最终成为顶尖表现者的技能,刻意练习是实现目标的更好方法。刻意练习是在练习过程中有目的、深思熟虑和专注。具体来说,它具有以下特点:
-
刻意练习有明确、具体的目标。例如,如果你的目标是成为马拉松中的顶级跑者,你需要设定具体的目标,比如你应该多快完成马拉松,并制定计划以实现这一目标。
-
刻意练习是专注的。在练习时,你必须全神贯注地意识到自己在做什么,而不是处于自动驾驶模式。如果你在完成任务时发现自己分心,很可能是因为你没有认真对待目标,或者任务太容易完成。做简单的任务不会帮助你提高技能。
-
刻意练习涉及反馈。你必须不断接受评估,以找出需要改进的地方。在大多数情况下,你必须跟随导师,他们会在练习中提供即时反馈。导师不一定非要是一个人。也可以是精心设计的教程,当找不到导师时。举个例子,这本书解释了熟练的作家如何通过将自己的文章与他们期望发表的刊物进行对比来提高写作能力。此外,随着技能的提升,更换导师也是重要的。例如,在一篇文章被理想的刊物接受后,作家会在设定改进词汇、简洁性、逻辑结构等目标时改变练习方法。
-
刻意练习要求你走出舒适区。除非你的目标是保持当前的技能水平,否则如果你希望每次练习都有所提升,你应该在练习时让自己感到稍微“不舒服”。始终挑战自己跑得更快、表现更好、写得更清晰、执行得更快。走出舒适区可以拓展你的边界并打开机会。
然而,刻意练习可能并不适用于所有领域。如上所述,该领域必须有明确且可衡量的标准来定义顶尖表现者的技能。
这本书提供了许多例子,说明了来自不同领域的顶尖表现者是如何通过刻意练习达到他们的水平的。在这篇博客文章中,我想分享一个我如何从零开始成功发展数据科学技能的例子。
我有具体且明确的目标。
设定明确的目标是我数据科学旅程的第一步。我从来不是单纯地“学习数据科学”,而是总是设立具体的目标,例如:
-
精通 Python 编程,以便我能更有效地为我的研究项目进行数据分析;
-
理解统计概念和机器学习算法,以便我知道为什么某些模型在某些情况下效果更好,以及我应该为我的研究项目选择哪个模型;
-
建立一个数据科学作品集,以便我能更容易地获得面试机会。
-
学习这些模型,以便我可以用它们解决工作中的问题。
这些目标作为指引,帮助我有效地导航广阔的数据科学领域,它们随着我面临更大挑战和不同环境而不断演变。
建议:在开始之前设立目标是很重要的。了解你希望如何运用这些技能将指导你学习什么以及如何学习。如果你是学生,你可能会学习某些知识以通过考试;如果你在工作,你可能会学习一些东西以便为正在进行的项目做好准备,或者保持在你所在行业的最新技术前沿。我们需要短期目标和长期目标。任何熟悉时间管理的人都会知道 艾森豪威尔的紧急/重要原则 的概念。我们根据任务的紧急性和重要性对其进行分类。虽然许多人优先处理紧急且重要的任务,但也重要的是不要忽视那些重要但不紧急的任务,这些任务在长期中使我们与众不同。例如,学习和掌握一项新技能。短期目标就像这些紧急且重要的任务,它们能提高我们的效率,但长期目标激励我们持续不断地成长,这在不断变化的世界中尤其宝贵。
我在练习时保持专注。
有了明确的目标后,保持专注变得至关重要。我不再盲目地消费教程和资源,而是结构化了我的学习时间。我每天花固定时间专注地进行在线课程学习。在这些在线课程中,容易误以为因为观看了视频或阅读了文章就理解了内容。我强迫自己去做项目,亲自实践。一开始,我会花费几个小时搜索不同的语法,并在 DataFrame 上试验它们的用法。一个简单的可视化任务需要我很长时间才能完成。然而,你不能仅仅通过复制和粘贴来学习编程语言。我们需要知道它的用途是什么,以及为什么在特定任务中选择这个而不是其他的。通过亲自参与项目,我帮助自己保持专注,深入吸收信息。
建议:在练习时,重要的是要让大脑参与其中。虽然这似乎很明显,但不要假设你会自动保持专注。保持专注比单纯地完成动作需要更多的能量。这类似于跑步——在跑一英里时维持一定的心率比毫无目的地跑步需要更多的能量。在练习过程中,尽量多思考,多问自己一些问题。问自己是否真正理解自己在做什么以及为什么这样做。
我寻求练习中的反馈
在我过去的几篇博客文章中,我强调了参加数据科学训练营在我的旅程中的重要性。
讨论了基于个人经验的七个方面的好处
pub.towardsai.net](https://pub.towardsai.net/how-to-benefit-from-attending-a-data-science-bootcamp-289db43e2d7c?source=post_page-----47eb21bd2c8--------------------------------)
我从知识渊博的导师那里获得的反馈是无价的。尽管是通过虚拟方式交流,我定期收到有关讲座问题、作业和项目的反馈。此外,我的小组同学来自不同背景,相互提供反馈。我们从彼此的专长和错误中学习。他们的见解和批评帮助我完善了技术技能,并激发了我以战略性和创造性的方法来解决问题。尽管在参加在线教程时,我会因为完成在线作业而获得分数,但没有指导和即时反馈,我感到很挣扎。建设性的反馈将我的错误转化为成长的机会,加速了我的学习曲线。感谢这个为期 8 周的密集训练营,我得到了指数级的成长。
建议:花时间进行练习和反思固然重要,但也要寻求有经验人士的指导和反馈。这是提高技能的最快、最有效的方法。在学习新事物时,考虑找一个比你更成熟的导师,以便在实践过程中获得即时反馈。如果找不到导师,拥有练习伙伴或加入社区也是有帮助的。
我走出了舒适区
虽然舒适区可以提供安全感,但真正的成长只能通过走出它的边界来实现。对我来说,学习数据科学是走出舒适区的一段旅程。在经济学领域,Stata 是一个熟悉的软件,学者和学生常与 R 一起用于数据分析和建模。然而,随着大数据的日益流行,我认识到需要学习 Python 以提高生产力。为了真正掌握数据科学,我挑战了自己做那些最初看似困难的项目。这让我能够在面对挑战的过程中提高解决问题的能力。数据科学发展迅速。去年新颖且前沿的东西很容易被取代,变成遗留物。在这个领域,我们永远不能自满。走出舒适区不仅扩展了我的技能,还让我具备了接受挑战的勇气和解决问题的自信。
建议:每次走出舒适区都会扩展你的技能并提升你的自信。不要害怕在实践中面对挑战。挑战自己做更复杂的项目,写更简洁的代码,建立更高准确度或更易解释的模型。这是成长过程中最痛苦但也是最有价值的部分。爬坡总是比在平地上绕圈更累。你可以对自己撒谎说绕圈也是练习,但这不会带你到达山顶的美丽风景。
实质上,刻意练习是一种有目的和结构化的技能发展方法。它包括设定具体目标、保持专注的练习、积极寻求和采纳导师的反馈,并通过走出舒适区来挑战自己。这种练习方法将学习从一个被动的过程转变为主动的成长和掌握之旅。在这篇文章中,我分享了我学习数据科学技能的经历,以进一步证明刻意练习的重要性,希望能激励那些有兴趣掌握新技能的人。
感谢阅读!最后,别忘了:
-
查看这些 我的其他文章 如果感兴趣;
-
订阅 我的邮件列表;
-
或者关注我在 YouTube;
-
观看我最新的 YouTube 视频,内容是 我在 Medium 上赚了多少钱:
学习离散傅里叶变换(DFT)
数学与编码视角
·
关注 发表在 Towards Data Science · 9 分钟阅读 · 2023 年 2 月 8 日
--
数字信号处理(DSP)是用于操控信号数据的数学方法的计算[1]。在数字信号处理中,最重要的工具之一就是离散傅里叶变换(DFT)。它通常用于生成信号的频域(谱)表示[2]。
在这篇文章中,我们将讨论离散傅里叶变换(DFT)的工作原理以及如何实现它以输出信号的频谱。

照片由 Pawel Czerwinski 提供,来源于 Unsplash
离散傅里叶变换(DFT)
傅里叶变换是 DFT 的数学基础和谱分解的主要思想,谱分解得出一个信号实际上只是不同频率分量的正弦波之和 [3]。由于我们处理的所有信号数据都是数字形式的,信号是一组时间域中的样本。对这些离散信号进行傅里叶变换可以使用 DFT,它可以用来在时间域和频率域之间来回切换。时间域包含信号的样本,而频率域表示构建信号的正弦波的谱 [4]。下图描述了使用 DFT 和 IDFT 时间域与频率域之间的关系。

时间域与频率域之间的关系 [4]。 [作者提供的图像]
从数学角度来看,如果我们有一个包含 N 个样本的信号(xn),该信号的 DFT 定义为 [5]:

DFT 方程 [5]
其中:
-
N:样本数量
-
n:当前样本
-
k:当前频率,其中 k ∈ [0, N−1]
-
xn:样本 n 的正弦值
-
Xk:包含幅度和相位信息的 DFT
DFT(Xk)的输出是一个包含复数的数组,这些复数包含了构建输入信号的正弦波的频率、幅度和相位的信息。DFT 数组(Xk)的前半部分包含正频率项,而后半部分包含负频率项。此外,当输入信号仅为实值信号时,前半部分是后半部分频率项的共轭,谱是对称的。因此,在实值信号的情况下,我们只关注前半部分(正频率项) [5]。下图表示当输入样本数量(N)为奇数或偶数时的正频率和负频率项。

正频率和负频率项 [5]。 [作者提供的图像]
每个正弦波的振幅和相位,构成信号时加起来的这些值,可以从复数数组(Xk)中计算得到(Im 和 Re 分别代表复数的虚部和实部)[5]:

振幅和相位信息可以从这些方程中计算得到 [5]
开始编码:
我们将建立一个基于上述第一个方程计算 DFT 的函数。但首先,我们需要生成一个作为 DFT 输入的信号。我们将生成一个由 3 个正弦波组成的信号,其频率分别为(1,20,10)Hz,振幅分别为(3,1,0.5)。采样率将为每秒 200 个样本。为了生成信号,我使用了一个类 Signal,你可以参考这个 GitHub gist使用此类。你可以轻松生成任何信号,但我使用了这个 Signal 类以获得更多的控制。
注意:
-
我们将在下面编写的 DFT 和 IDFT 函数是根据 MIT 许可证发布的,所有荣誉归这本书的作者。
-
我们将讨论的这两个函数只是为了理解 DFT 的数学原理以及这种变换的输出。在实际应用中,有更快更高效的算法可以计算傅里叶变换,即快速傅里叶变换(FFT)及其逆变换。
让我们开始吧…
# Import the required packages
import numpy as np
import matplotlib.pyplot as plt
# Generate the three signals using Signal class and its method sine()
signal_1hz = Signal(amplitude=3, frequency=1, sampling_rate=200, duration=2)
sine_1hz = signal_1hz.sine()
signal_20hz = Signal(amplitude=1, frequency=20, sampling_rate=200, duration=2)
sine_20hz = signal_20hz.sine()
signal_10hz = Signal(amplitude=0.5, frequency=10, sampling_rate=200, duration=2)
sine_10hz = signal_10hz.sine()
# Sum the three signals to output the signal we want to analyze
signal = sine_1hz + sine_20hz + sine_10hz
# Plot the signal
plt.plot(signal_1hz.time_axis, signal, 'b')
plt.xlabel('Time [sec]')
plt.ylabel('Amplitude')
plt.title('Sum of three signals')
plt.show()

我们使用 Signal 类生成的信号。 [作者提供的图片]
现在我们将建立 DFT 函数,以提供构成我们上面生成的信号的正弦波。请确保仔细阅读下面代码的注释,因为它们有助于你了解每一行的输出。
# Build a function that calculates the discrete Fourier transform
def DFT(signal):
# Number of samples, 100 samples in our example
N = len(signal)
# The samples from 0 to N-1, [0, 1, 2, ..., 199] in our example
n = np.arange(N)
# Generate the frequencies, [[0], [1], [2], ..., [199]] in our example
k = n.reshape((N,1))
# e is a matrix of complex numbers with a shape of (N, N), (200, 200) in our example
e = np.exp(-2j * np.pi * k * n / N)
# dft is a matrix of complex numbers with a shape of (N,), (200,) in our example
dft = np.dot(e,signal)
return dft
# Let's use the function
dft = DFT(signal= signal)
# Calculate the amplitude spectrum of the signal
amp = np.abs(dft)
# Generate the frequency axis
N = len(dft)
n = np.arange(N)
T = N/signal_1hz.sampling_rate
freq = n/T
# Plot the spectrum
plt.figure(figsize = (8, 6))
plt.stem(freq, amp, 'b', markerfmt='o', basefmt='b')
plt.xlabel('Frequency [Hz]')
plt.ylabel('DFT Amplitude |X(freq)|')
plt.title('Spectrum of the signal')

DFT 函数的输出是信号的频谱。 [作者提供的图片]
x 轴包含构成信号的频率成分。y 轴表示每个频率成分的强度。频谱中最低的频率成分通常称为基频,而振幅最大的频率成分称为主频 [3]。在我们上面的例子中,1Hz 的频率成分是基频和主频。
我们可以注意到频谱在采样率的一半处的对称性(尝试不同的采样率);这通常称为 折叠频率。当记录一个真实世界的信号(f(t)),其 FN 为最高频率分量时,折叠频率绝不应低于 FN,以检索信号的所有信息。这是根据 奈奎斯特-香农定理 [5]。
我们可以通过将频谱归一化到输入样本数(N)来获得实际的幅度。但是,当我们只关注频谱的前半部分时,如果输入是实值信号,我们将频谱归一化为 N/2 [5]。下面的代码用于归一化频谱的前半部分 [0, N/2] 并绘制频谱。
# Get the length of one side of frequencies
n_oneside = N//2
# Get the one side frequency
f_oneside = freq[:n_oneside]
# Normalize the amplitude by N/2
one_side_dft = dft[:n_oneside]/n_oneside
# Plot the first half
plt.stem(f_oneside, np.abs(one_side_dft))
plt.xlabel('Freq (Hz)')
plt.ylabel('DFT Amplitude |X(freq)|')
plt.title('The spectrum of the signal after normalization')
plt.show()

归一化后的信号频谱。 [作者提供的图片]
离散傅里叶变换(IDFT)
同样地,正如你可以从时域转换到频域,你也可以通过离散傅里叶逆变换将信号从频域转换回时域。这一过程在信号处理中非常有用,当你想要使用 DFT 过滤特定的频率成分,然后使用 IDFT 将信号恢复到其时域时。可以从 Xk 序列中计算 IDFT,按照方程 [5]。

IDFT 方程 [5]
让我们构建一个可以使用上述方程计算 IDFT 的函数。
def IDFT(dft):
# Number of frequencies, 200 components in our example
N = len(dft)
# The frequencies from 0 to N-1, [0, 1, 2, ..., 199] in our example
k = np.arange(N)
# Generate the samples, [[0], [1], [2], ..., [199]] in our example
n = k.reshape((N,1))
# If your input was a first half spectrum, 2j should be 1j to retrieve the signal
e = np.exp(2j * np.pi * k * n / N)
# dft is a matrix of complex numbers with a shape of (N,), (200,) in our example
signal = np.dot(e,dft)/N
return signal
# Apply the Inverse Fourier Transform on the spectrum [dft]
sig = IDFT(dft)
# Generate the time axis from sampling rate and length of dft
N = len(dft)
duration = N/signal_1hz.sampling_rate
time_axis = np.arange(0, 2, 1/200)
# Plot the results of IDFT along with the original signal
plt.plot(time_axis, sig,'b')
plt.plot(time_axis, signal, 'r')
plt.xlabel('Time [sec]')
plt.ylabel('Amplitude')
plt.title('Output of the IDFT')
plt.show()

IDFT 函数恢复的信号与原始信号相同。 [作者提供的图片]
DFT 的极限
在样本数量很大的信号情况下,DFT 函数的执行时间会很长,因为需要对信号的所有数据点应用傅里叶变换。幸运的是,已经开发出一种高效的算法来计算信号的 DFT,即快速傅里叶变换(FFT)。该算法将执行复杂度从 O(N²) 降低到仅 O(NlogN),其中 N 为数据的大小。通过使用 FFT 显著降低的计算复杂度使傅里叶变换在工程、科学和数学领域得到广泛应用 [5]。
Python 提供了多个功能,用户可以使用 Numpy 或 Scipy Python 包来应用傅里叶变换。下面的代码表示了使用我们之前构建的 DFT 函数、使用 Numpy 包的 FFT [6] 和使用 Scipy 包的 FFT [7] 进行时间执行的比较。使用 Scipy 包中的 FFT 是最快的。
# Import the scipy package
from scipy.fftpack import fft
# Estimate the execution time of DFT using the function we've built
print('Execution time of DFT Function:')
%timeit DFT(signal)
# Estimate the execution time of DFT using FFT from numpy package
print('\nExecution time of FFT using Numpy pacakge:')
%timeit np.fft.fft(signal)
# Estimate the execution time of DFT using FFT from scipy package
print('\nExecution time of FFT using Scipy package:')
%timeit scipy.fftpack.fft(signal)
Execution time of DFT Function:
17.3 ms ± 2.65 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
Execution time of FFT using Numpy pacakge:
8.72 µs ± 2.2 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Execution time of FFT using Scipy package:
8.27 µs ± 137 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
结论
-
我们已经了解了傅里叶变换在信号处理领域的有用性,并且理解了其主要思想。
-
我们已经指出了离散傅里叶变换的数学视角以及如何对离散信号进行计算。
-
我们构建了一个使用数学方程计算 DFT 的函数,并将该函数应用于我们使用在上一篇文章中构建的 Signal 类生成的信号。
-
我们已经了解到 DFT 的输出是一个包含 N 个元素的复数数组,元素数量与输入信号的样本数量相同。这些复数包含了频谱的幅度和相位信息。
-
我们已经看到,如果输入信号是实值信号,DFT 的输出将对半个采样率对称。这就是我们只关注正频率分量的原因。
-
已经指出了计算离散傅里叶变换的逆变换(IDFT)的方程。我们也构建了一个函数来计算 IDFT,以从频谱中恢复原始信号。
-
我们讨论了我们构建的函数的局限性,并且有一个高效的算法可以计算傅里叶变换,即快速傅里叶变换。
参考文献
[1] R. Toulson, R., & Wilmshurst, T. (2012). 数字信号处理入门。快速有效的嵌入式系统设计(第 219–242 页)。Elsevier。 doi.org/10.1016/B978-0-08-097768-3.00011-8
[2] T. Giannakopoulos, T., & Pikrakis, A. (2014). 信号变换与滤波基础。音频分析简介(第 33–57 页)。Elsevier。 doi.org/10.1016/B978-0-08-099388-1.00003-0
[3] Downey, A. (2016). 声音与信号。Think DSP: Python 中的数字信号处理(第 1–11 页)。(第一版)。O’Reilly Media, Inc.
[4] Thakur, B., & Mehra, R. (2016). 使用不同窗口技术算法的离散傅里叶变换分析。国际计算机应用期刊, 975, 8887。
[5] Kong, Q., Siauw, T., & Bayen, A. (2020)。傅里叶变换。Python 编程与数值方法:工程师和科学家的指南(第 415–444 页)。学术出版社。
[6] Numpy 文档,API 参考,离散傅里叶变换 (numpy.fft)。[访问日期:2023 年 2 月 2 日]
[7] Scipy 文档,API 参考,遗留的离散傅里叶变换 (scipy.fftpack)。[访问日期:2023 年 2 月 2 日]
学习 RabbitMQ 用于事件驱动架构(EDA)
原文:
towardsdatascience.com/learn-rabbitmq-for-event-driven-architecture-eda-e1e7377db2b
一份适合初学者的教程,介绍 RabbitMQ 的工作原理以及如何在 Go 中使用 RabbitMQ,这是学习 EDA 的第一步。
·发表于 Towards Data Science ·阅读时长 39 分钟·2023 年 4 月 5 日
--

照片由 Bradyn Trollip 提供,来自 Unsplash
事件驱动架构(EDA)是我在编程中最喜欢的东西之一。这种架构允许我们构建微服务并轻松地在它们之间共享信息。
在常规的顺序软件中,你会有一个函数触发另一个函数,或者一个定期脚本来检查某些任务。
在事件驱动架构中,我们利用队列或发布/订阅模式。允许不同的服务之间通知或传送信息,以触发代码执行。
事件驱动架构通常用于构建高度灵活和可扩展的软件。这是因为可以通过简单地监听事件来轻松添加或移除功能。
这使得影子部署和测试新服务与生产环境并行变得非常容易,因为你可以让新服务对相同事件做出反应,而不会干扰正在运行的系统。
然而,并非所有的情况都是一帆风顺的,一些人认为事件驱动架构(EDA)系统可能略显复杂,而且在考虑到完整的服务流程时,测试有时会更困难。我认为测试其实更简单,因为我们可以轻松触发事件并查看相关服务或单个服务的反应。但如果没有适当的架构文档,也可能很难理解是什么触发了什么以及原因。
本教程将探讨如何使用 RabbitMQ 构建两个通过事件进行通信的微服务。我们将研究 RabbitMQ 中使用的不同范式,虽然我们将学习如何在 Go 中使用 RabbitMQ,但我们主要集中于学习 RabbitMQ 的概念。涵盖一些常见错误和一些最佳实践。
RabbitMQ 支持多种协议来发送数据,但在本教程中,我们将专注于使用AMQP。
在本教程中,我们将学习以下内容
-
使用 Docker 设置 RabbitMQ
-
虚拟主机、用户和权限
-
使用 CLI 管理 RabbitMQ,通过rabbitmqctl和rabbitmqadmin
-
了解生产者、消费者以及如何编写它们。
-
了解队列、交换机和绑定
-
使用工作队列(先进先出)
-
使用 RabbitMQ 进行发布/订阅
-
使用基于 RPC 的模式和回调。
-
使用 TLS 加密流量
-
使用配置来声明 RabbitMQ 中的资源
该教程的视频录制,适合喜欢视频的人。
本文中使用的所有代码可以在这里找到。
安装 RabbitMQ — 设置用户、虚拟主机和权限
使 RabbitMQ 运行可以通过下载和安装中的示例完成。我建议在生产环境中遵循该指南,但为了本教程和实验,我们可以使用更简单的方法。
像往常一样,最简单的方法是运行 Docker!
此命令将下载最新的 RabbitMQ 并将其作为后台进程启动,暴露端口5672和15672。
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.11-management
端口 5672 用于启用 AMQP 连接。AMQP 是 RabbitMQ 和许多其他消息中间件使用的网络协议。
端口 15672 被开启,因为管理 UI 和管理界面托管在该端口,管理 RabbitMQ 的 API 也在该端口上。
有关端口的更多细节,请参阅 RabbitMQ 的网络指南。
一旦 Docker 启动,让我们开始访问托管在localhost:15672上的管理 UI。

RabbitMQ 管理 UI — 图片由 Percy Bolmer 提供
哎呀,我们需要一个用户!让我们使用RabbitMQCLI创建一个。别担心安装问题,它已经存在于我们运行的 Docker 容器中。
我们可以使用命令add_user来创建新用户,后跟用户名和密码。我们使用docker exec rabbitmq在 docker 内部执行命令,将rabbitmq替换为你为 docker 容器指定的名称。
docker exec rabbitmq rabbitmqctl add_user percy secret
我建议在探索期间也授予管理员权限。我们可以通过给新用户添加管理员标签来实现这一点。
docker exec rabbitmq rabbitmqctl set_user_tags percy administrator
哦,最后一件事,默认情况下有一个 guest 用户,我强烈建议删除此用户!此用户仅对使用本地主机的用户可用,但还是安全起见比较好。
docker exec rabbitmq rabbitmqctl delete_user guest
就这样,回到管理 UI 并登录。
登录后你会看到一个看起来相当老旧的用户界面,但这非常好,因为我们可以真正从这里监控 RabbitMQ,并查看发生了什么。我们还不会玩弄这个界面,我们需要先有一个实际连接并发送数据的服务。

管理 UI 显示正在运行的实例指标 — 图片由 Percy Bolmer 提供
在我们开始操作之前,我们需要再修复两个问题。
RabbitMQ 中的资源,如队列以及我们将很快学习的其他内容,按照逻辑层进行分隔,这个逻辑层称为虚拟主机(Vhost)。
解释虚拟主机最简单的方法可能是将其与命名空间进行比较,但这可能在某些方面不完全正确。
我们可以使用这些虚拟主机将某些资源分组在一起,并通过添加允许使用虚拟主机的用户来限制访问。
让我们开始使用 add_vhost 命令创建虚拟主机,它接受一个输入,即虚拟主机的名称。
docker exec rabbitmq rabbitmqctl add_vhost customers
现在我们有了一个虚拟主机,我们可以为之前创建的用户添加权限,以便它可以连接。
添加权限是通过 set_permissions 命令完成的,我们使用 -p 标志来指定要添加权限的虚拟主机。语法中的下一个项是要添加权限的用户。
命令的最后部分是令人害怕的部分,它是一个定义要添加权限的正则表达式,添加所有权限的示例如下,或者对所有以 customer- 开头的资源的权限为 "^customer-*".
会有 3 个正则表达式槽位,按顺序配置以下权限。
-
配置 — 对匹配正则表达式的资源的配置权限
-
写 — 对匹配正则表达式的资源的写权限
-
读取 — 对匹配正则表达式的资源的读取权限
为我的用户 percy 添加对 customer 虚拟主机的全面配置、写入和读取权限的完整命令如下。注意,我给予了 .* 的访问权限,即所有权限。
docker exec rabbitmq rabbitmqctl set_permissions -p customers percy ".*" ".*" ".*"
创建完成后,你应该能在管理 UI 的右上角看到新的虚拟主机。

选择新的虚拟主机。 — 图片由 Percy Bolmer 提供
RabbitMQ 基础知识 — 生产者、消费者、交换机和队列

展示生产者、交换机、队列和消费者如何协同工作 — 图片由 Percy Bolmer 提供
当我们构建事件驱动架构时,有一些术语需要理解。
-
生产者 — 任何发送消息的软件。
-
消费者 — 任何接收消息的软件。
-
队列 — 一个队列接受一个消息,输出这个消息,可以将其视为一个大型缓冲区。队列是 FIFO(先进先出)的,这意味着消息按插入队列的顺序输出。
-
交换机 — 一种路由器,是生产者发送消息的地方。交换机接受消息,并根据交换机的类型和应用的绑定(规则)将其发送到正确的队列。
一般来说,我们可以使用这个来在服务之间发送和接收消息。值得一提的是,生产者和消费者不需要在同一主机上运行,这使得系统具有很好的扩展性。
首先创建一个新的 Go 项目,如果你还没有安装 Go,请从这里进行安装。
在实际的 Go 项目设置中,我可能会使用 Cobra,但为了避免新用户感到困惑,我将简单地创建两个主要包。
让我们在 Go 中构建一个生产者,能够开始在队列上发送消息。
开始为生产者创建一个新的项目,并获取由 RabbitMQ 团队官方维护的 AMQP 库。
该项目将有一个cmd文件夹,包含所有不同的服务,每个服务都是一个独立的可运行程序。
我们还将有一个internal文件夹,用于存储共享库等。
mkdir eventdriven
cd eventdriven
mkdir -p cmd/producer
mkdir internal
touch cmd/producer/main.go
touch internal/rabbitmq.go
go mod init programmingpercy.tech/eventdrivenrabbit
go get github.com/rabbitmq/amqp091-go
你的文件夹结构应该如下所示。

cmd文件夹和internal文件夹已准备好 — 图片来自 Percy Bolmer
首先在internal/rabbitmq.go中添加一个与 RabbitMQ 实例的连接。
我们将创建一个小的助手函数,用于通过amqp协议连接到 RabbitMQ。我们将允许用户指定凭证、主机以及要连接的 vhost。
我将简单地返回指向连接的指针,即网络连接和用于并发发送消息的amqp.Channel。将连接的管理留给用户。
package internal
import (
"context"
"fmt"
amqp "github.com/rabbitmq/amqp091-go"
)
// RabbitClient is used to keep track of the RabbitMQ connection
type RabbitClient struct {
// The connection that is used
conn *amqp.Connection
// The channel that processes/sends Messages
ch *amqp.Channel
}
// ConnectRabbitMQ will spawn a Connection
func ConnectRabbitMQ(username, password, host, vhost string) (*amqp.Connection, error) {
// Setup the Connection to RabbitMQ host using AMQP
conn, err := amqp.Dial(fmt.Sprintf("amqp://%s:%s@%s/%s", username, password, host, vhost))
if err != nil {
return nil, err
}
return conn, nil
}
// NewRabbitMQClient will connect and return a Rabbitclient with an open connection
// Accepts a amqp Connection to be reused, to avoid spawning one TCP connection per concurrent client
func NewRabbitMQClient(conn *amqp.Connection) (RabbitClient, error) {
// Unique, Conncurrent Server Channel to process/send messages
// A good rule of thumb is to always REUSE Conn across applications
// But spawn a new Channel per routine
ch, err := conn.Channel()
if err != nil {
return RabbitClient{}, err
}
return RabbitClient{
conn: conn,
ch: ch,
}, nil
}
// Close will close the channel
func (rc RabbitClient) Close() error {
return rc.ch.Close()
}
一个很好的经验法则是,在整个应用程序中重用一个连接,并为并发任务创建新的通道。原因是连接是 TCP 连接,而通道是在分配的 TCP 连接中的多路复用连接。遵循这个经验法则可以实现更具可扩展性的解决方案。
让我们将这个简单的客户端导入到cmd/producer/main.go中并尝试连接,看看会发生什么。
现在,我们将简单地连接并在关闭连接前休眠 30 秒。
package main
import (
"log"
"programmingpercy/eventdrivenrabbit/internal"
"time"
)
func main() {
conn, err := internal.ConnectRabbitMQ("percy", "secret", "localhost:5672", "customers")
if err != nil {
panic(err)
}
defer conn.Close()
client, err := internal.NewRabbitMQClient(conn)
if err != nil {
panic(err)
}
defer client.Close()
time.Sleep(30 * time.Second)
log.Println(client)
}
一旦完成这些设置,就运行生产者。
go run cmd/producer/main.go
一旦运行,返回管理 UI 并查看我们是否可以看到现在有一个连接和一个通道。

我们现在有一个连接和一个通道,而不是零个 — 图片来自 Percy Bolmer
通道是一种非常聪明的处理 TCP 层的方法,你可以在文档中阅读更多内容。它允许用户在多个通道之间重用一个打开的 TCP 连接,而不是打开多个 TCP 连接。这是一种复用技术。
现在是开始发送数据的时候了,这是在上述通道上完成的。通道的功能远超过你可能想象的,它不仅仅是一个简单的管道,还可以在创建时配置一些巧妙的选项。
我们可以从 UI 创建队列,但我喜欢在测试时通过代码创建队列。在生产环境中,我喜欢使用配置文件来声明一些基本设置,我们稍后会讨论这个问题。
我们可以通过调用 amqp.QueueDeclare 来创建队列,这个函数有许多输入参数,我们需要理解这些参数以获得想要的队列行为。函数签名如下所示。
func (*amqp.Channel).QueueDeclare(name string, durable bool, autoDelete bool, exclusive bool, noWait bool, args amqp.Table) (amqp.Queue, error)
-
名称 — 用于引用队列的名称。此项可以为空,在这种情况下,服务器将生成一个名称。
-
持久化 — 如果队列在 Broker 重启(RabbitMQ 重启)时应该被保留
-
自动删除 — 如果队列在最后一个消费者离开时应自动删除
-
独占 — 仅适用于创建队列的相同连接。
-
无等待 — 假定队列在服务器上创建
-
参数 — 提供用户提供的参数的选项。
为了让这件事更简单,我会创建一个接受 name、durable 和 autodelete 参数的包装函数。我将默认禁用其他参数。
// CreateQueue will create a new queue based on given cfgs
func (rc RabbitClient) CreateQueue(queueName string, durable, autodelete bool) error {
_, err := rc.ch.QueueDeclare(queueName, durable, autodelete, false, false, nil)
return err
}
让我们更新 producer/main.go 以执行新的 CreateQueue 函数,我将创建一个持久化队列,因为我希望处理新客户的队列保持活跃和持久,我还会将自动删除设置为 false。
我还会创建一个名为 customers_test 的非持久化队列,以展示区别。
func main() {
conn, err := internal.ConnectRabbitMQ("percy", "secret", "localhost:5672", "customers")
if err != nil {
panic(err)
}
defer conn.Close()
client, err := internal.NewRabbitMQClient(conn)
if err != nil {
panic(err)
}
defer client.Close()
if err := client.CreateQueue("customers_created", true, false); err != nil {
panic(err)
}
if err := client.CreateQueue("customers_test", false, true); err != nil {
panic(err)
}
time.Sleep(10 *time.Second)
log.Println(client)
}
添加完后,请确保执行生产者。
go run cmd/producer/main.go
你可以访问 UI 并查看应该都可用的队列。请注意,一旦程序退出,customers_test 队列没有被删除,这是因为我们还没有消费者连接。只有连接了消费者的队列才会被删除。

customers-test 使用自动删除创建,一旦程序退出,它将被删除。— 图片来源:Percy Bolmer
为了好玩,你可以尝试现在重启 RabbitMQ,看看 customers_test 是如何消失的,因为它没有被标记为持久化。
docker restart rabbitmq
探索交换机和绑定
在我们开始在队列上发送消息之前,我们需要创建一个交换机。已经创建了一些默认的交换机,但我们将创建自己的交换机以了解更多信息。
交换机是 RabbitMQ 的一个重要部分,它是我们发送消息的资源。交换机的工作是将消息发送到正确的队列。
要开始接收队列上的消息,该队列需要绑定到一个交换上,这被称为绑定。绑定基本上是一个路由规则。一个重要的点是,队列可以绑定到多个交换,这也使得不同交换类型的意义更加明确。
有几种不同类型的交换,每种交换在消息发送方式上有不同的行为。
首先,我们有最基本的Direct交换。这种交换非常简单,消息是基于其确切的路由键进行路由的。在示例图中,我们看到发送到customer_created的消息仅通过交换customer_events路由到那个特定的队列。直接交换在需要将工作分配给一组工作者时非常有用。

直接交换(Direct Exchange)—— 只有直接匹配customer_created的消息才会收到匹配 —— 图片由 Percy Bolmer 提供
第二种类型是Fanout交换,它用于将消息发送到所有绑定的队列。任何绑定到交换的队列都会接收到消息,路由键会被简单地忽略!这通常用于将消息广播给任何感兴趣的方。

扩展交换(Fanout Exchange)—— 任何绑定的队列都接收消息 —— 图片由 Percy Bolmer 提供
然后我们有Topic交换,这种交换非常酷。它们允许绑定指定规则以根据路由键选择消息的子集。
路由键在每个词之间用.分隔,例如customers.eu.stockholm。这可能是来自瑞典斯德哥尔摩的客户的路由键,然后我们可以有一个绑定来告诉交换机某个队列想要这些消息,但不包括customers.us.florida。
有几个特殊字符,#表示零个或多个匹配,因此例如customers.#将匹配任何以customers.开头的路由键。
还有*,它是特定位置的特定词,例如customers.*.stockholm将仅匹配具有第一个词customers和第三个词stockholm的路由键。
当然,这对于某些服务只接收与特定主题子集相关的消息是非常有用的。下面的例子展示了如何在二月份创建一个新客户,队列customer_created会接收到消息,因为绑定规则是customers.created.#,而队列customer_emailed则不会接收到消息,因为它不匹配绑定customers.created.march。

主题交换(Topic Exchange)—— 允许使用简单的正则表达式根据路由键选择子集 —— 图片由 Percy Bolmer 提供
最终的交换是Header交换,每条我们在 RabbitMQ 上发送的消息都有可能添加头信息,这是一个键值字段。当我们需要基于更高级别的内容进行路由时,这非常有用。
比如说我们添加一个browser头信息,指示用户在注册时使用了什么网页浏览器。例如,我们可以将所有 Linux 用户路由到某个特定的队列。
你可以指定多个头信息,并且它们都必须匹配,或者只需一个匹配。这通过将x-match设置为all或any来完成。

Header Exchange — 允许基于消息中可以提供的额外头信息进行路由 — 图片由 Percy Bolmer 提供
让我们停止讨论,创建一个我们可以使用的交换机。
要添加一个交换机,我们将使用与之前使用的rabbitmqcli非常类似的rabbitmqadmin CLI 工具。
我们使用declare exchange命令,后跟交换机的名称和类型。对于这个教程,我将使用Topic交换机。
我们将创建一个名为customer-events的交换机。我们还需要指定虚拟主机以及管理员的用户名和密码。如果你希望交换机在重启时保持存在,记得将 durable 设置为 true。
docker exec rabbitmq rabbitmqadmin declare exchange --vhost=customers name=customer_events type=topic -u percy -p secret durable=true
我们还需要授权用户在这个交换机上发送消息。我们使用set_topic_permissions命令设置特定主题上的权限。以下命令将用户percy设置为允许在交换机customer_events上的虚拟主机customers上发布,路由键以customers开头。
docker exec rabbitmq rabbitmqctl set_topic_permissions -p customers percy customer_events "^customers.*" "^customers.*"
现在在这个交换机上发布将不会有任何反应,因为队列和交换机之间没有绑定。
任何发送的消息都会被丢弃。
发布消息到交换机
要开始发布消息,我们首先需要在customers_created和customers_test队列与customers_events交换机之间创建绑定。
打开rabbitmq.go并添加一个添加绑定的CreateBinding函数。
// CreateBinding is used to connect a queue to an Exchange using the binding rule
func (rc RabbitClient) CreateBinding(name, binding, exchange string) error {
// leaveing nowait false, having nowait set to false wctxill cause the channel to return an error and close if it cannot bind
// the final argument is the extra headers, but we wont be doing that now
return rc.ch.QueueBind(name, binding, exchange, false, nil)
}
然后在producer/main.go中添加绑定,以便连接所有内容。我们预计客户会在主题customers.created上发布,后面跟着他们来自的国家。但是绑定不会关心国家,只要它匹配模式即可。
...
// Create binding between the customer_events exchange and the customers-created queue
if err := client.CreateBinding("customers-created", "customers.created.*", "customer_events"); err != nil {
panic(err)
}
// Create binding between the customer_events exchange and the customers-test queue
if err := client.CreateBinding("customers-test", "customers.*", "customer_events"); err != nil {
panic(err)
}
如果你执行生产者,我们可以访问管理用户界面并查看可用的绑定。
go run cmd/producer/main.go
然后进入用户界面并访问你的交换机。

交换机显示当前的绑定和它们的路由键 — 图片由 Percy Bolmer 提供
现在我们已经有了绑定,可以开始查看发布消息的内容。我们从最简单的开始。
我们创建一个名为Send的包装函数,该函数接受关于要发布到哪个交换机和路由键的参数。该函数还会接受一个上下文和一个amqp.Publishing 结构体。
amqp.Publishing 结构体非常重要,因为它允许我们自定义我们发送的消息的功能和行为。
我们将一步步地探索它们,因为它们有很多。
// Send is used to publish a payload onto an exchange with a given routingkey
func (rc RabbitClient) Send(ctx context.Context, exchange, routingKey string, options amqp.Publishing) error {
return rc.ch.PublishWithContext(ctx,
exchange, // exchange
routingKey, // routing key
// Mandatory is used when we HAVE to have the message return an error, if there is no route or queue then
// setting this to true will make the message bounce back
// If this is False, and the message fails to deliver, it will be dropped
true, // mandatory
// immediate Removed in MQ 3 or up https://blog.rabbitmq.com/posts/2012/11/breaking-things-with-rabbitmq-3-0§
false, // immediate
options, // amqp publishing struct
)
}
返回到producer/main.go,我们将创建一条消息进行发送。我们将发送两条消息,每个队列一条。这是为了展示deliveryMode参数,这个参数非常重要。如果将其设置为持久性,消息将被保存直到某个消费者获取它,但这会带来开销和更长的延迟。
如果有些东西不需要持久化,则将其设置为Transient以提高性能。
记住,如果你发送的是持久消息,你的队列也需要是持久的;如果队列本身不存在,那么保存消息也没有意义。
...
// Create context to manage timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Create customer from sweden
if err := client.Send(ctx, "customer_events", "customers.created.se", amqp091.Publishing{
ContentType: "text/plain", // The payload we send is plaintext, could be JSON or others..
DeliveryMode: amqp091.Persistent, // This tells rabbitMQ that this message should be Saved if no resources accepts it before a restart (durable)
Body: []byte("An cool message between services"),
}); err != nil {
panic(err)
}
if err := client.Send(ctx, "customer_events", "customers.test", amqp091.Publishing{
ContentType: "text/plain",
DeliveryMode: amqp091.Transient, // This tells rabbitMQ that this message can be deleted if no resources accepts it before a restart (non durable)
Body: []byte("A second cool message"),
}); err != nil {
panic(err)
}
log.Println(client)
}
现在是执行生产者的时候了。
go run cmd/producer/main.go
你现在应该在 UI 的Queue页面下看到每个队列的一条消息。

每个队列都有消息发送到它们 — 图片来源:Percy Bolmer
如果你愿意,可以进入每个队列并消费消息进行查看,但我建议重新启动 RabbitMQ,以显示 Transient 和 Persistent 之间的区别。
docker restart rabbitmq
重新启动后尝试重新加载 UI,你应该会看到整个customers-test队列被删除了,但customers-created队列实际上保留了旧消息。
这是因为持久消息会被写入磁盘,以便在崩溃等情况发生时能够保存。
我们将很快介绍更高级的发布技术。
消费消息、确认、拒绝和重新入队
我们知道如何发布消息,但如果我们不能在另一个服务中消费这些消息,那是没有用的。
消费是从队列中获取消息的过程。
让我们创建一个新的二进制文件,以便我们可以用来消费消息。
mkdir cmd/consumer
touch cmd/consumer/main.go
在我们开始消费之前,我们将在Rabbitmq.go中添加一个Consume函数,它将封装通道消费函数。
消费时有几个选项需要考虑。
-
Exclusive — 如果设置为 true,将确保这是该队列上的唯一消费者;如果设置为 false,服务器将公平地将消息分配给多个消费者。
-
AutoAck — 当设置为 true 时,将自动确认交付;当设置为 false 时,将期望消费者在完成时调用确认。AutoAck 可能听起来很棒,但它很棘手,如果你的消费者在确认耗时的过程后失败,消息会丢失,因为服务器认为它已经完成。
-
NoLocal — 在 RabbitMQ 中不支持,这是 AMQP 字段,用于避免在同一领域中发布和消费。
-
NoWait — 不会等待服务器确认。
让我们将Consume函数添加到Rabbitmq.go中。
// Consume is a wrapper around consume, it will return a Channel that can be used to digest messages
// Queue is the name of the queue to Consume
// Consumer is a unique identifier for the service instance that is consuming, can be used to cancel etc
// autoAck is important to understand, if set to true, it will automatically Acknowledge that processing is done
// This is good, but remember that if the Process fails before completion, then an ACK is already sent, making a message lost
// if not handled properly
func (rc RabbitClient) Consume(queue, consumer string, autoAck bool) (<-chan amqp.Delivery, error) {
return rc.ch.Consume(queue, consumer, autoAck, false, false, false, nil)
}
现在我们可以进行消费了,让我们填写consumer/main.go,使其连接到 RabbitMQ 并开始从队列中获取消息。
package main
import (
"log"
"programmingpercy/eventdrivenrabbit/internal"
)
func main() {
conn, err := internal.ConnectRabbitMQ("percy", "secret", "localhost:5672", "customers")
if err != nil {
panic(err)
}
mqClient, err := internal.NewRabbitMQClient(conn)
if err != nil {
panic(err)
}
messageBus, err := mqClient.Consume("customers_created", "email-service", false)
if err != nil {
panic(err)
}
// blocking is used to block forever
var blocking chan struct{}
go func() {
for message := range messageBus {
// breakpoint here
log.Printf("New Message: %v", message)
}
}()
log.Println("Consuming, to close the program press CTRL+C")
// This will block forever
<-blocking
}
运行该消费者时,一旦发布者有消息发送,它应该会打印出一条消息。
记住,复用连接,但为每个并行处理创建一个新的通道,在我们的例子中,将创建一个第二个 RabbitMQ 客户端来管理
customers-test队列。
go run cmd/consumer/main.go
如果你没有看到任何消息,可能是因为你需要先运行生产者。
2023/02/12 22:17:24 New Message: {0xc0000b0000 map[] text/plain 2 0 0001-01-01 00:00:00 +0000 UTC ema
il-service 0 1 false customer_events customers.created.se [65 110 32 99 111 111 108 32 109 101 115 115 97 103
101 32 98 101 116 119 101 101 110 32 115 101 114 118 105 99 101 115]}
可能值得探索通过通道传递的结构体,即 amqp.Delivery 结构体,它提供了所有字段的良好视图。
// Delivery captures the fields for a previously delivered message resident in
// a queue to be delivered by the server to a consumer from Channel.Consume or
// Channel.Get.
type Delivery struct {
Acknowledger Acknowledger // the channel from which this delivery arrived
Headers Table // Application or header exchange table
// Properties
ContentType string // MIME content type
ContentEncoding string // MIME content encoding
DeliveryMode uint8 // queue implementation use - non-persistent (1) or persistent (2)
Priority uint8 // queue implementation use - 0 to 9
CorrelationId string // application use - correlation identifier
ReplyTo string // application use - address to reply to (ex: RPC)
Expiration string // implementation use - message expiration spec
MessageId string // application use - message identifier
Timestamp time.Time // application use - message timestamp
Type string // application use - message type name
UserId string // application use - creating user - should be authenticated user
AppId string // application use - creating application id
// Valid only with Channel.Consume
ConsumerTag string
// Valid only with Channel.Get
MessageCount uint32
DeliveryTag uint64
Redelivered bool
Exchange string // basic.publish exchange
RoutingKey string // basic.publish routing key
Body []byte
}
如果你重新运行当前的消费者,你会看到相同的消息再次出现。这是因为我们从未确认消费者已经使用了这条消息。这必须在迭代消息或使用自动确认标志时手动完成。
在确认时,我们可以传递一个 multiple 标志,指示是否一次确认多条消息,我们可以将其设为 false。
我们可以确认或 NACK 消息,确认表示一切正常,NACK 表示我们处理失败,然后消息会被重新放回队列中。
让我们更新处理消息的代码,以便确认它们。
go func() {
for message := range messageBus {
// breakpoint here
log.Printf("New Message: %v", message)
// Multiple means that we acknowledge a batch of messages, leave false for now
if err := message.Ack(false); err != nil {
log.Printf("Acknowledged message failed: Retry ? Handle manually %s\n", message.MessageId)
continue
}
log.Printf("Acknowledged message %s\n", message.MessageId)
}
}()
现在重新运行代码,你应该会看到消息再次打印,但在重新启动后消息就消失了。
这非常有用,以避免消费者接收一条消息,在处理时失败,然后消息就会丢失。
为了展示自动确认可能是危险的,这里是一个修改后的例子,我们将自动确认设置为 true,但在处理过程中失败了。
// Auto Ack is now True
messageBus, err := mqClient.Consume("customers-created", "email-service", true)
if err != nil {
panic(err)
}
// blocking is used to block forever
var blocking chan struct{}
go func() {
for message := range messageBus {
log.Printf("New Message: %v", message)
panic("Whops I failed here for some reason")
}
}()
运行消费者两次,你会看到它实际上只在第一次执行时接受了消息。如果你没有妥善管理,这可能是危险的行为。这就是我不断提到它的原因!
要处理失败,你可以使用 Nack 告诉 RabbitMQ 失败了,你可以使用 redelivered 字段来避免过多重试。
Nack 接受一个重新排队的参数,这非常方便!
这是一个例子,我们在消息到达第一次时失败,重新排队,然后在下一次到达时确认它。
messageBus, err := mqClient.Consume("customers-created", "email-service", false)
if err != nil {
panic(err)
}
// blocking is used to block forever
var blocking chan struct{}
go func() {
for message := range messageBus {
log.Printf("New Message: %v", message)
if !message.Redelivered {
// Nack multiple, Set Requeue to true
message.Nack(false, true)
continue
}
// Multiple means that we acknowledge a batch of messages, leave false for now
if err := message.Ack(false); err != nil {
log.Printf("Acknowledged message failed: Retry ? Handle manually %s\n", message.MessageId)
continue
}
log.Printf("Acknowledged message %s\n", message.MessageId)
}
}()
这里还有更多需要考虑的,目前我们使用的处理程序是单线程的,这意味着我们一次只能处理一条消息。我们可以通过实现一个工作组来修复这一点,该工作组允许一定数量的并发任务。
我将添加一个 errgroup,因此这种方法需要 Go 1.2。使用 ErrGroup 非常简单,我们可以将其限制为每个消费者处理 10 条消息。
errgroup 来自 golang.org/x/sync/errgroup 包。
.....
// Set a timeout for 15 secs
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
defer cancel()
// Create an Errgroup to manage concurrecy
g, ctx := errgroup.WithContext(ctx)
// Set amount of concurrent tasks
g.SetLimit(10)
go func() {
for message := range messageBus {
// Spawn a worker
msg := message
g.Go(func() error {
log.Printf("New Message: %v", msg)
time.Sleep(10 * time.Second)
// Multiple means that we acknowledge a batch of messages, leave false for now
if err := msg.Ack(false); err != nil {
log.Printf("Acknowledged message failed: Retry ? Handle manually %s\n", msg.MessageId)
return err
}
log.Printf("Acknowledged message %s\n", msg.MessageId)
return nil
})
}
}()
添加这个使得消费者变得稍微更好一些。
SetLimit目前仅用于此,还有另一种管理消费消息数量的方法,即使用 RabbitMQ,我推荐使用的叫做 Prefetch,我们稍后会讲到。
我们可以通过将 Send 函数包裹在 for 循环中来更新发布者以发送更多的消息。
// Create context to manage timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Create customer from sweden
for i := 0; i < 10; i++ {
if err := client.Send(ctx, "customer_events", "customers.created.se", amqp091.Publishing{
ContentType: "text/plain", // The payload we send is plaintext, could be JSON or others..
DeliveryMode: amqp091.Persistent, // This tells rabbitMQ that this message should be Saved if no resources accepts it before a restart (durable)
Body: []byte("An cool message between services"),
}); err != nil {
panic(err)
}
}
if err := client.Send(ctx, "customer_events", "customers.test", amqp091.Publishing{
ContentType: "text/plain",
DeliveryMode: amqp091.Transient, // This tells rabbitMQ that this message can be deleted if no resources accepts it before a restart (non durable)
Body: []byte("A second cool message"),
}); err != nil {
panic(err)
}
log.Println(client)
}
尝试一下,看看消费者现在是否接受多条消息,或者尝试启动多个消费者来进行一些测试。
注意到生产者在发送消息后立即退出了吗?目前,Send 函数不会等待来自服务器的任何确认。有时,我们可能希望阻塞,直到服务器确认它已接收到消息。
高兴的是,我们可以!我们需要将 RabbitMQ 中使用的Publish函数更改为PublishWithDeferredConfirmWithContext,这将返回一个可以用来Wait等待服务器确认的对象。
这个对象将始终是 NIL,除非将通道设置为Confirm模式,将其设置为 Confirm 模式将使服务器在收到发布的消息时发送确认。
在Rabbitmq.go中,让我们修改发布方法并添加一个等待。
// Send is used to publish a payload onto an exchange with a given routingkey
func (rc RabbitClient) Send(ctx context.Context, exchange, routingKey string, options amqp.Publishing) error {
// PublishWithDeferredConfirmWithContext will wait for server to ACK the message
confirmation, err := rc.ch.PublishWithDeferredConfirmWithContext(ctx,
exchange, // exchange
routingKey, // routing key
// Mandatory is used when we HAVE to have the message return an error, if there is no route or queue then
// setting this to true will make the message bounce back
// If this is False, and the message fails to deliver, it will be dropped
true, // mandatory
// immediate Removed in MQ 3 or up https://blog.rabbitmq.com/posts/2012/11/breaking-things-with-rabbitmq-3-0§
false, // immediate
options, // amqp publishing struct
)
if err != nil {
return err
}
// Blocks until ACK from Server is receieved
log.Println(confirmation.Wait())
return nil
}
让我们也更新NewRabbitMQClient以始终将通道设置为Confirm模式。
// NewRabbitMQClient will connect and return a Rabbitclient with an open connection
// Accepts a amqp Connection to be reused, to avoid spawning one TCP connection per concurrent client
func NewRabbitMQClient(conn *amqp.Connection) (RabbitClient, error) {
// Unique, Conncurrent Server Channel to process/send messages
// A good rule of thumb is to always REUSE Conn across applications
// But spawn a new Channel per routine
ch, err := conn.Channel()
if err != nil {
return RabbitClient{}, err
}
// Puts the Channel in confirm mode, which will allow waiting for ACK or NACK from the receiver
if err := ch.Confirm(false); err != nil {
return RabbitClient{}, err
}
return RabbitClient{
conn: conn,
ch: ch,
}, nil
}
对Rabbitmq.go的一个更好方法可能是添加一个NewChannel函数,然后让每个函数接受一个 Channel 作为输入参数。
现在运行程序,你应该会看到publisher.go在每次服务器确认消息时打印 TRUE,注意这与 Consumer 的ACK不同。我们只是等待服务器确认发布的消息已被接受。
发布和订阅(PubSub)

RabbitMQ 中的 Pub/Sub 模式使用 Fanout 交换机 — 图片由 Percy Bolmer 提供
直到目前为止,我们一直在使用 FIFO 队列(先进先出)。这意味着每条消息只发送给一个消费者。
在发布和订阅模式中,你会希望每个消费者接收到相同的消息。
我们关于绑定等的所有知识仍然适用,使用方式相同。我们可以使用 Fanout 交换机(将消息推送到所有绑定的队列)而不管队列名称。
这个想法是让每个消费者创建一个未命名的队列,未命名的队列将由 RabbitMQ 服务器生成一个随机的唯一名称。
这是在代码中创建队列非常适合的一个好例子。
我们可能希望将customers_event发送到多个服务。例如,我们可能希望有一个电子邮件服务和一个日志记录服务来记录每个客户事件。
让我们来构建它。(由于这是一个学习 RabbitMQ 的教程,我们将简单地启动两个 Consumer 实例。)
我们首先删除现有的交换机,因为它的类型不正确。我们还创建一个新的交换机,但类型为Fanout。这一次我们没有为权限指定特定的前缀,而是给予了完全访问权限。
docker exec rabbitmq rabbitmqadmin delete exchange name=customer_events --vhost=customers -u percy -p secret
docker exec rabbitmq rabbitmqadmin declare exchange --vhost=customers name=customer_events type=fanout -u percy -p secret durable=true
docker exec rabbitmq rabbitmqctl set_topic_permissions -p customers percy customer_events ".*" ".*"
由于我们在当前代码中创建一个未命名的队列时无法知道队列名称,因此需要进行修改。让我们返回来自CreateQueue的 RabbitMQ 包中的队列信息。该对象将包含随机创建的名称。
// CreateQueue will create a new queue based on given cfgs
func (rc RabbitClient) CreateQueue(queueName string, durable, autodelete bool) (amqp.Queue, error) {
q, err := rc.ch.QueueDeclare(queueName, durable, autodelete, false, false, nil)
if err != nil {
return amqp.Queue{}, nil
}
return q, nil
}
现在是更新Publisher的时候了,在教程早些时候我们在 Publisher 中创建了 Channel 绑定。依我看这样做并不完全合理,这只是为了不走得太快,同时展示功能。
Consumer 声明绑定更有意义,因为它与消费者相关。在发布和订阅中,消费者的数量和路径可能未知,现在这更没有意义。让我们更新 publisher.go 使其变得更小。
package main
import (
"context"
"log"
"programmingpercy/eventdrivenrabbit/internal"
"time"
"github.com/rabbitmq/amqp091-go"
)
func main() {
conn, err := internal.ConnectRabbitMQ("percy", "secret", "localhost:5672", "customers")
if err != nil {
panic(err)
}
defer conn.Close()
client, err := internal.NewRabbitMQClient(conn)
if err != nil {
panic(err)
}
defer client.Close()
// Create context to manage timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Create customer from sweden
for i := 0; i < 10; i++ {
if err := client.Send(ctx, "customer_events", "customers.created.se", amqp091.Publishing{
ContentType: "text/plain", // The payload we send is plaintext, could be JSON or others..
DeliveryMode: amqp091.Persistent, // This tells rabbitMQ that this message should be Saved if no resources accepts it before a restart (durable)
Body: []byte("An cool message between services"),
}); err != nil {
panic(err)
}
}
log.Println(client)
}
我们将更新 consumer.go 以创建一个未命名的队列,创建绑定,然后开始消费该队列。
package main
import (
"context"
"log"
"programmingpercy/eventdrivenrabbit/internal"
"time"
"golang.org/x/sync/errgroup"
)
func main() {
conn, err := internal.ConnectRabbitMQ("percy", "secret", "localhost:5672", "customers")
if err != nil {
panic(err)
}
mqClient, err := internal.NewRabbitMQClient(conn)
if err != nil {
panic(err)
}
// Create Unnamed Queue which will generate a random name, set AutoDelete to True
queue, err := mqClient.CreateQueue("", true, true)
if err != nil {
panic(err)
}
// Create binding between the customer_events exchange and the new Random Queue
// Can skip Binding key since fanout will skip that rule
if err := mqClient.CreateBinding(queue.Name, "", "customer_events"); err != nil {
panic(err)
}
messageBus, err := mqClient.Consume(queue.Name, "email-service", false)
if err != nil {
panic(err)
}
// blocking is used to block forever
var blocking chan struct{}
// Set a timeout for 15 secs
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
defer cancel()
// Create an Errgroup to manage concurrecy
g, ctx := errgroup.WithContext(ctx)
// Set amount of concurrent tasks
g.SetLimit(10)
go func() {
for message := range messageBus {
// Spawn a worker
msg := message
g.Go(func() error {
log.Printf("New Message: %v", msg)
time.Sleep(10 * time.Second)
// Multiple means that we acknowledge a batch of messages, leave false for now
if err := msg.Ack(false); err != nil {
log.Printf("Acknowledged message failed: Retry ? Handle manually %s\n", msg.MessageId)
return err
}
log.Printf("Acknowledged message %s\n", msg.MessageId)
return nil
})
}
}()
log.Println("Consuming, to close the program press CTRL+C")
// This will block forever
<-blocking
}
这个设置可以用来正确展示 Pub/Sub,我们可以先启动两个消费者,然后是发布者。它将展示所有消费者如何看到所有消息。

所有消费者都会接收到消息。
我们现在知道如何使用常规队列和 PubSub。
还有一件事,第三种非常常见的场景是基于 RPC 的范式。
使用 RabbitMQ 的远程过程调用(RPC)

RabbitMQ 中的 RPC 使用消息中的 ReplyTo 头部。— 图片由 Percy Bolmer 提供
有时,我们希望在消息上进行一些回调。比如说,生产者希望知道客户何时发送了电子邮件。
这是常见且容易解决的问题。我们可以在消息中设置一个名为 ReplyTo 的字段,这可以用于告诉消费者在特定队列上回复响应。
我们可能需要知道回调与哪个消息相关,因此我们还可以添加 correlationID,以便了解响应与哪个请求相关。
开始创建一个 Direct 类型的新交换机。我会将其命名为 customer_callbacks。Direct 类型在这里效果很好。
docker exec rabbitmq rabbitmqadmin declare exchange --vhost=customers name=customer_callbacks type=direct -u percy -p secret durable=true
docker exec rabbitmq rabbitmqctl set_topic_permissions -p customers percy customer_callbacks ".*" ".*"
我们需要了解的第一件事是目前的一个重要最佳实践。
拥有回调将要求相同的服务既进行发布又进行消费,这没什么问题。
一个著名的规则是,但绝不要在同一连接上进行发布和消费。

压力回溯可能会阻止 ACK 消息的发送 — 图片由 Percy Bolmer 提供
想象一下,如果你有一个同时进行生产和消费的服务,并且在同一个连接上进行,那么假设服务正在消费大量消息。如果消息数量超过了服务能够处理的范围,消息开始堆积。RabbitMQ 可能会施加回压,开始阻塞 TCP 连接的发送,结果,ACK 消息必须被发送来处理消息。突然间,由于连接被阻塞,你的代码无法发送 ACK 消息。这可能会导致延迟。
黄金规则是
-
在应用程序中重用连接
-
一个用于消费,一个用于发布
-
为每个 Goroutine 创建新的通道
让我们更新 producer.go 来启动两个连接,一个用于发布,一个用于消费。我们还将创建一个未命名的队列并将其绑定到交换机,然后我们将消费这些响应。
我们还将在消息中添加replyTo,这告诉消费者回复的地址,以及correlationId,它解释了消息关联的唯一事件。
package main
import (
"context"
"fmt"
"log"
"programmingpercy/eventdrivenrabbit/internal"
"time"
"github.com/rabbitmq/amqp091-go"
)
func main() {
conn, err := internal.ConnectRabbitMQ("percy", "secret", "localhost:5672", "customers")
if err != nil {
panic(err)
}
defer conn.Close()
// Never use the same Connection for Consume and Publish
consumeConn, err := internal.ConnectRabbitMQ("percy", "secret", "localhost:5672", "customers")
if err != nil {
panic(err)
}
defer consumeConn.Close()
client, err := internal.NewRabbitMQClient(conn)
if err != nil {
panic(err)
}
defer client.Close()
consumeClient, err := internal.NewRabbitMQClient(consumeConn)
if err != nil {
panic(err)
}
defer consumeClient.Close()
// Create Unnamed Queue which will generate a random name, set AutoDelete to True
queue, err := consumeClient.CreateQueue("", true, true)
if err != nil {
panic(err)
}
if err := consumeClient.CreateBinding(queue.Name, queue.Name, "customer_callbacks"); err != nil {
panic(err)
}
messageBus, err := consumeClient.Consume(queue.Name, "customer-api", true)
if err != nil {
panic(err)
}
go func() {
for message := range messageBus {
log.Printf("Message Callback %s\n", message.CorrelationId)
}
}()
// Create context to manage timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Create customer from sweden
for i := 0; i < 10; i++ {
if err := client.Send(ctx, "customer_events", "customers.created.se", amqp091.Publishing{
ContentType: "text/plain", // The payload we send is plaintext, could be JSON or others..
DeliveryMode: amqp091.Persistent, // This tells rabbitMQ that this message should be Saved if no resources accepts it before a restart (durable)
Body: []byte("An cool message between services"),
// We add a REPLYTO which defines the
ReplyTo: queue.Name,
// CorrelationId can be used to know which Event this relates to
CorrelationId: fmt.Sprintf("customer_created_%d", i),
}); err != nil {
panic(err)
}
}
var blocking chan struct{}
log.Println("Waiting on Callbacks, to close the program press CTRL+C")
// This will block forever
<-blocking
}
消费者需要更新,以便它也使用两个连接。当我们完成处理消息时,我们将其添加到replyTo队列,以便发送响应。再次,我们必须使用两个不同的连接,一个用于消费,另一个用于发布。
package main
import (
"context"
"log"
"programmingpercy/eventdrivenrabbit/internal"
"time"
"github.com/rabbitmq/amqp091-go"
"golang.org/x/sync/errgroup"
)
func main() {
conn, err := internal.ConnectRabbitMQ("percy", "secret", "localhost:5672", "customers")
if err != nil {
panic(err)
}
defer conn.Close()
publishConn, err := internal.ConnectRabbitMQ("percy", "secret", "localhost:5672", "customers")
if err != nil {
panic(err)
}
defer publishConn.Close()
mqClient, err := internal.NewRabbitMQClient(conn)
if err != nil {
panic(err)
}
publishClient, err := internal.NewRabbitMQClient(publishConn)
if err != nil {
panic(err)
}
// Create Unnamed Queue which will generate a random name, set AutoDelete to True
queue, err := mqClient.CreateQueue("", true, true)
if err != nil {
panic(err)
}
// Create binding between the customer_events exchange and the new Random Queue
// Can skip Binding key since fanout will skip that rule
if err := mqClient.CreateBinding(queue.Name, "", "customer_events"); err != nil {
panic(err)
}
messageBus, err := mqClient.Consume(queue.Name, "email-service", false)
if err != nil {
panic(err)
}
// blocking is used to block forever
var blocking chan struct{}
// Set a timeout for 15 secs
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
defer cancel()
// Create an Errgroup to manage concurrecy
g, ctx := errgroup.WithContext(ctx)
// Set amount of concurrent tasks
g.SetLimit(10)
go func() {
for message := range messageBus {
// Spawn a worker
msg := message
g.Go(func() error {
// Multiple means that we acknowledge a batch of messages, leave false for now
if err := msg.Ack(false); err != nil {
log.Printf("Acknowledged message failed: Retry ? Handle manually %s\n", msg.MessageId)
return err
}
log.Printf("Acknowledged message, replying to %s\n", msg.ReplyTo)
// Use the msg.ReplyTo to send the message to the proper Queue
if err := publishClient.Send(ctx, "customer_callbacks", msg.ReplyTo, amqp091.Publishing{
ContentType: "text/plain", // The payload we send is plaintext, could be JSON or others..
DeliveryMode: amqp091.Transient, // This tells rabbitMQ to drop messages if restarted
Body: []byte("RPC Complete"),
CorrelationId: msg.CorrelationId,
}); err != nil {
panic(err)
}
return nil
})
}
}()
log.Println("Consuming, to close the program press CTRL+C")
// This will block forever
<-blocking
}
尝试一下代码,你应该看到生产者接收到 RPC 响应并将其打印出来。
注意这段代码可以进行清理,但本教程重点在于 RabbitMQ 的工作原理,而不是清洁代码。
预取限制——限制发送的消息数量。
记得我们之前通过使用errgroup限制了消费者的工作量吗?这只是一个软限制,由代码施加,但 RabbitMQ 仍然可以向消费者发送更多消息。
还有更好的解决方案,实际上,如果你希望消费者并发处理消息,应该使用组合方案。
AMQP 协议允许我们应用预取限制。这告诉 RabbitMQ 服务器每次可以发送到频道的未确认消息数量。这样我们可以添加一个硬限制。
通过应用一组服务质量规则(QOS)来完成这一点。让我们在rabbitmq.go中添加一个方法,应用这三条可用的规则。
以下是参数:
-
预取计数——服务器可以发送多少未确认的消息。
-
预取大小——服务器可以发送多少字节的未确认消息。
-
全局——一个标志,用于确定规则是否应用于连接或全局。
// ApplyQos is used to apply qouality of service to the channel
// Prefetch count - How many messages the server will try to keep on the Channel
// prefetch Size - How many Bytes the server will try to keep on the channel
// global -- Any other Consumers on the connection in the future will apply the same rules if TRUE
func (rc RabbitClient) ApplyQos(count, size int, global bool) error {
// Apply Quality of Serivce
return rc.ch.Qos(
count,
size,
global,
)
}
然后在consumer.go中,我们可以简单地调用它并应用我们想要允许的消息数量。
// Create an Errgroup to manage concurrecy
g, ctx := errgroup.WithContext(ctx)
// Set amount of concurrent tasks
g.SetLimit(10)
// Apply Qos to limit amount of messages to consume
if err := mqClient.ApplyQos(10, 0, true); err != nil {
panic(err)
}
go func() {
for message := range messageBus {
使用 TLS 保护连接。
现在是 2023 年,在投入生产之前,我认为我们应该加密流量是非常安全的。
RabbitMQ 有一个 GitHub repository来帮助我们创建 rootCA 和所需的证书,这是加密流量的第一步。
我们需要克隆此存储库并执行内部的 make 文件,以生成所需的文件。
git clone https://github.com/rabbitmq/tls-gen tls-gen
cd tls-gen/basic
make PASSWORD=
make verify
所有生成的文件将出现在一个名为result的新文件夹中。为使其在 Docker 中正常工作,我们需要更改它们的权限。
sudo chmod 644 tls-gen/basic/result/*
我们需要删除正在运行的 RabbitMQ 容器,我们需要用配置文件创建一个新的容器。
sudo docker container rm -f rabbitmq
配置文件名为rabbitmq.conf,应放置在容器中的/etc/rabbitmq/rabbitmq.conf内。
这个配置文件不仅可以配置 TLS,但我们现在只讨论 TLS。在项目根目录下创建一个具有正确名称的新文件。
cd ../../ # Go to root of Project
touch rabbitmq.conf
我们需要在启动容器时将配置文件挂载到 Docker 中。我们还将把 TLS-Gen 工具生成的证书挂载到/certs,以便容器可以找到它们。请注意,这两个端口都减少了一,以符合 RabbitMQ 协议的标准。
docker run -d --name rabbitmq -v "$(pwd)"/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro -v "$(pwd)"/tls-gen/basic/result:/certs -p 5671:5671 -p 15671:15671 rabbitmq:3.11-management
一旦完成,我们可以开始将 TLS 配置添加到这个容器中。
在rabbitmq.conf中添加证书和根 CA 的路径。我的计算机名为blackbox,你需要将证书名称替换为你计算机生成的名称。
# Disable NON TCP
listeners.tcp = none
# TCP port
listeners.ssl.default = 5671
# SSL Certs
ssl_options.cacertfile = /certs/ca_certificate.pem
ssl_options.certfile = /certs/server_blackbox_certificate.pem
ssl_options.keyfile = /certs/server_blackbox_key.pem
# Peer verification
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = true
然后重新启动 RabbitMQ。
docker restart rabbitmq
为了验证一切是否正常工作,你可以使用docker logs rabbitmq查看 Docker 日志。搜索有关监听器的日志。
2023-02-19 07:35:15.566316+00:00 [info] <0.738.0> Ready to start client connection listeners
2023-02-19 07:35:15.567418+00:00 [info] <0.885.0> started TLS (SSL) listener on [::]:5671
现在,旧程序将无法再工作。它尝试在没有 TLS 的情况下进行连接,所以我们来修复一下。
程序需要更新以使用客户端证书。我们将其作为输入添加到ConnectRabbitMQ函数中。
// ConnectRabbitMQ will spawn a Connection
func ConnectRabbitMQ(username, password, host, vhost, caCert, clientCert, clientKey string) (*amqp.Connection, error) {
ca, err := os.ReadFile(caCert)
if err != nil {
return nil, err
}
// Load the key pair
cert, err := tls.LoadX509KeyPair(clientCert, clientKey)
if err != nil {
return nil, err
}
// Add the CA to the cert pool
rootCAs := x509.NewCertPool()
rootCAs.AppendCertsFromPEM(ca)
tlsConf := &tls.Config{
RootCAs: rootCAs,
Certificates: []tls.Certificate{cert},
}
// Setup the Connection to RabbitMQ host using AMQPs and Apply TLS config
conn, err := amqp.DialTLS(fmt.Sprintf("amqps://%s:%s@%s/%s", username, password, host, vhost), tlsConf)
if err != nil {
return nil, err
}
return conn, nil
}
请注意,我们现在使用的是amqps协议。证书路径是绝对路径,我们需要更新consumer和producer以插入这些路径,我现在会使用硬编码的值,但在实际应用中你不应该这样做。
conn, err := internal.ConnectRabbitMQ("percy", "secret", "localhost:5671", "customers",
"/home/pp/development/blog/event-driven-rabbitmq/tls-gen/basic/result/ca_certificate.pem",
"/home/pp/development/blog/event-driven-rabbitmq/tls-gen/basic/result/client_blackbox_certificate.pem",
"/home/pp/development/blog/event-driven-rabbitmq/tls-gen/basic/result/client_blackbox_key.pem",
)
if err != nil {
panic(err)
}
defer conn.Close()
// Never use the same Connection for Consume and Publish
consumeConn, err := internal.ConnectRabbitMQ("percy", "secret", "localhost:5671", "customers",
"/home/pp/development/blog/event-driven-rabbitmq/tls-gen/basic/result/ca_certificate.pem",
"/home/pp/development/blog/event-driven-rabbitmq/tls-gen/basic/result/client_blackbox_certificate.pem",
"/home/pp/development/blog/event-driven-rabbitmq/tls-gen/basic/result/client_blackbox_key.pem",
)
defer consumeConn.Close()
BAM!太棒了,我们有了 TLS。
尝试运行生产者或消费者,然后使用docker logs rabbitmq查看 Docker 日志。
2023-02-19 07:49:53.015732+00:00 [error] <0.948.0> Error on AMQP connection <0.948.0> (172.17.0.1:49066 -> 172.17.0.2:5671, state: starting):
2023-02-19 07:49:53.015732+00:00 [error] <0.948.0> PLAIN login refused: user 'percy' - invalid credentials
对,删除 Docker 时我们删除了虚拟主机、用户、交换机以及所有内容,因为我们没有持久化存储。
这很好,因为这将引导我们进入本教程的下一步也是最后一步,默认配置。
RabbitMQ 配置与管理
相信我,你不想使用 AdminCLI 来管理多个用户的 RabbitMQ,因为如果你因为某些原因重置集群,这将是重复的繁重工作。
支持插入定义文件、定义用户、虚拟主机、权限、队列和交换机的 JSON 文件,甚至是绑定。
它们真的很容易使用,让我们添加我的旧用户,并赋予其在customers虚拟主机上读写权限,添加一个基本交换机。
在此之前,我们需要一个密码哈希,这可能比想象的要复杂。它取决于你拥有的 RabbitMQ 设置以及你配置的算法。默认的是 SHA256。
我在stackoverflow上找到了一个很棒的 bash 脚本来为我生成它。创建一个名为encodepassword.sh的文件,并将secret替换为你要编码的密码。
#!/bin/bash
function encode_password()
{
SALT=$(od -A n -t x -N 4 /dev/urandom)
PASS=$SALT$(echo -n $1 | xxd -ps | tr -d '\n' | tr -d ' ')
PASS=$(echo -n $PASS | xxd -r -p | sha256sum | head -c 128)
PASS=$(echo -n $SALT$PASS | xxd -r -p | base64 | tr -d '\n')
echo $PASS
}
encode_password "secret"
运行脚本bash encodepassword.sh并存储 Hash。
更新rabbitmq.conf以包含load_definitions字段,这个字段可以在启动时加载定义文件。
log.console = true
# Disable NON TCP
listeners.tcp = none
# TCP port
listeners.ssl.default = 5671
# SSL Certs
ssl_options.cacertfile = /certs/ca_certificate.pem
ssl_options.certfile = /certs/server_blackbox_certificate.pem
ssl_options.keyfile = /certs/server_blackbox_key.pem
# Peer verification
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = true
# Load definitions file
load_definitions = /etc/rabbitmq/rabbitmq_definitions.json
我会指向一个名为/etc/rabbitmq/rabbitmq_definitions.json的文件。
在项目根目录下创建一个名为 rabbitmq_definitions.json 的文件,并用以下 JSON 填充它。目前,我认为我们不需要详细讲解 JSON 字段,一切应该是可以理解的。它与我们之前运行的 CLI 命令非常相似。
以下定义文件创建了两个交换机:customer_events 和 customer_callbacks。当前代码会生成自己的队列,因此我们仅在示例中定义一个以便于理解。
{
"users": [
{
"name": "percy",
"password_hash": "dPOoDgfw31kjUy41HSmqQR+X2Q9PCA5fD++fbxQCgPvKZmnX",
"tags": "administrator"
}
],
"vhosts": [
{
"name": "/"
},{
"name": "customers"
}
],
"permissions": [
{
"user": "percy",
"vhost": "customers",
"configure": ".*",
"write": ".*",
"read": ".*"
}
],
"exchanges": [
{
"name": "customer_events",
"vhost": "customers",
"type": "fanout",
"durable": true,
"auto_delete": false,
"internal": false,
"arguments": {}
},
{
"name": "customer_callbacks",
"vhost": "customers",
"type": "direct",
"durable": true,
"auto_delete": false,
"internal": false,
"arguments": {}
}
],
"queues": [
{
"name": "customers_created",
"vhost": "customers",
"durable": true,
"auto_delete": false,
"arguments": {}
}
],
"bindings": [
{
"source": "customers_events",
"vhost": "customers",
"destination": "customers_created",
"destination_type": "queue",
"routing_key": "customers.created.*",
"arguments": {}
}
]
}
一旦两个文件都到位,删除旧的 Docker,并重启一个新的,但这次我们为定义添加了第三个挂载点。
docker run -d --name rabbitmq -v "$(pwd)"/rabbitmq_definitions.json:/etc/rabbitmq/rabbitmq_definitions.json:ro -v "$(pwd)"/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro -v "$(pwd)"/tls-gen/basic/result:/certs -p 5671:5671 -p 15672:15672 rabbitmq:3.11-management
运行后,检查日志,确认它们打印了创建用户的相关信息。
2023-02-19 08:17:53.467218+00:00 [info] <0.867.0> Started message store of type persistent for vhost 'customers'
2023-02-19 08:17:53.467310+00:00 [info] <0.867.0> Recovering 0 queues of type rabbit_classic_queue took 3ms
2023-02-19 08:17:53.467348+00:00 [info] <0.867.0> Recovering 0 queues of type rabbit_quorum_queue took 0ms
2023-02-19 08:17:53.467371+00:00 [info] <0.867.0> Recovering 0 queues of type rabbit_stream_queue took 0ms
2023-02-19 08:17:53.468487+00:00 [info] <0.698.0> Importing concurrently 1 permissions...
2023-02-19 08:17:53.469946+00:00 [info] <0.680.0> Successfully set permissions for 'percy' in virtual host 'customers' to '.*', '.*', '.*'
完成这些后,尝试运行消费者和生产者,你应该会看到一切按预期工作。唯一的不同是,我们现在使用配置在 RabbitMQ 中创建基础设施,而不是使用 CLI,并且流量是加密的。
结论
很遗憾,这个漫长但令人兴奋的 RabbitMQ 冒险到此结束。
让我们回顾一下我们学到的内容。
我们已经学习了如何用虚拟主机配置 RabbitMQ,以及如何在这些虚拟主机上创建具有权限的用户。
我们还学习了如何在队列和交换机上生产和消费消息。
你应该对所有资源,如队列、交换机和绑定有一定了解。
我们还涵盖了如何创建发布和订阅模式、RPC 模式以及常规工作队列。
希望你已经清楚如何使用连接和通道以及它们之间的区别。连接是一个 TCP 连接,而通道是在连接上的复用虚拟通道。在同一个软件中重用连接,但为每个并行进程创建新的通道。
我们了解到永远不要在同一个连接上进行生产和消费。
我们还涵盖了如何设置 TLS 以及如何为 RabbitMQ 添加预定义配置的定义。
我真的希望你喜欢这个教程,你可以在 GitHub 上找到所有使用的代码。
随时向我提问!
学习成为数据科学领袖
原文:
towardsdatascience.com/learn-to-be-a-data-science-leader-5394425dd097
学习无人教授的技能的 5 个资源
·发布于 Towards Data Science ·阅读时间 5 分钟·2023 年 1 月 15 日
--

Nick Fewings 在 Unsplash 上的照片
关于如何成为数据科学家,有大量的培训材料、课程和文章可供参考。一旦你开始在这一领域工作,随着经验和技能的积累,向高级数据科学家的过渡应该会自然发生。
对于许多数据科学家来说,高级职位之后的自然下一步是转到领导一个数据科学家团队的角色。这个角色与初级和高级职位非常不同,因为这通常意味着承担直接管理职责,帮助他人成长和发展以及管理项目。
最困难的是,你需要在保持足够技术水平以维持数据科学家职位的同时,还要处理所有这些事务。
最困难的是,你需要在保持足够技术水平以维持数据科学家职位的同时,还要处理所有这些事务。
我在一年多前正式过渡为首席数据科学家,所以现在我觉得有足够的资格提供一些建议。在接下来的几周里,我将写一系列关于数据科学领导力的文章。
这篇文章将介绍一些帮助你学习不一般教授的领导技能的资源。实际上,大多数新的数据科学领导者需要在工作中学习这些技能。我个人发现以下五个资源在我担任数据科学领导角色的头几个月里非常宝贵。
1. 导师制度
在哪里比通过已经成为有效领导者的人更好地学习成为一个有效的领导者呢?如果你正在进入领导角色或希望你的职业朝这个方向发展,我建议你找一位导师。
在哪里比通过已经成为有效领导者的人更好地学习成为一个有效的领导者呢?
我非常幸运地拥有了几位正式和非正式的导师,他们帮助我过渡到领导角色,并成为一名优秀的领导者和经理。一个导师应该能够分享他们自己的领导经验,并充当你可能遇到的想法和担忧的讨论对象。
一旦你进入领导角色,你会变得非常忙碌。你将负责项目、人员和决策,这有时可能会非常令人不堪重负。我发现拥有一个导师帮助我了解应该如何分配时间,以及如何最好地管理团队和项目。
2. 《经理人的路径》
《经理人的路径》由 Camille Fournier 编著,是一本针对技术行业领导者的书籍,但书中的许多内容同样适用于任何领导角色,包括数据科学领域。
本书涵盖了从技术负责人到管理一个团队、管理多个团队以及一直到总监级别角色的所有职业阶段的领导和管理章节。它详细描述了在每个阶段的期望以及如何在这些角色中脱颖而出。
我发现关于如何有效地进行一对一会议、管理项目和处理诸如业绩不佳等挑战性情况的实用建议非常宝贵,因此这本书一直放在我的桌子上,我不断参考。
它甚至涵盖了如何从自己的经理那里获得最大收益,即使你不想转向领导角色,这也是有用的。
3. 《高效能人士的七个习惯》
我几年前读过这本书,应用其中列出的原则确实在当时彻底改变了我的职业生涯。
当你进入领导角色时,你将面临许多相互竞争的要求。你可能需要在人员管理职责、项目管理职责和自己对团队的技术贡献之间分配时间。优先考虑并确保你将时间投入到正确的事情上将变得至关重要。
优先考虑并确保你将时间投入到正确的事情上将变得至关重要。
《高效能人士的七个习惯》涵盖了七个原则,当这些原则应用于你的职业生涯时,特别是在领导阶段,将帮助你成为一名优秀的经理,把时间花在正确的地方,并防止你过度疲惫。
这 7 个习惯如下,但我鼓励你阅读这本书并详细研究每一个。
-
主动出击
-
从终点开始思考
-
把重要的事情放在首位
-
思考双赢
-
首先寻求理解,然后再求被理解
-
学会协同
-
磨刀不误砍柴工
4. 极棒的数据领导力
极棒的数据领导力是一个 GitHub 仓库,包含了与数据领导力相关的精选文章、视频和博客帖子。这其中有大量数据科学特定的内容,而许多通用数据内容可以应用于任何数据相关的角色。
该列表被划分为几个主题领域,涵盖了领导力的大部分方面,包括招聘、战略、多样性与包容性、项目管理和影响力。这是一个我经常参考的资源,因为它包含了许多有用的信息。
5. 数据科学领导者播客
Domino Datalab 的数据科学领导者播客包含了一系列与数据科学领域领导者的访谈。涵盖了一些有用的话题,包括如何将团队提升为战略业务伙伴、数据科学价值链的样子以及如何将负责任的 AI 嵌入到你的项目中。
将数据科学嵌入许多企业仍然很困难,这使得数据科学领导者的角色在某些方面比在其他领域的领导更具挑战性。该播客涵盖了一些可以帮助新领导者了解其数据科学团队在组织中应如何定位的话题。
我个人发现本文涵盖的资源在从个体贡献者角色过渡到领导角色时非常宝贵。这种过渡可能很困难,因为通常你直到进入角色后才会学习领导技能,同时在试图掌握新技能的过程中,还需要平衡许多竞争需求。
如果你是一名数据科学家,考虑未来转型为领导角色,我强烈建议你现在查看一些这些资源。特别是《高效能人士的 7 个习惯》这本书,我无法推荐更多,因为其原则在任何职业阶段,甚至在你的人生中应用,都几乎肯定会产生变革性的效果。
作为快速回顾,这里是上述资源的提醒,以及它们将帮助你解决的领导力方面。
-
导师指导: 很适合向已经担任领导角色的人学习,他们可以给你提供你从书本中无法学到的见解。
-
管理者的路径: 对你在不同领导层级上会遇到的新任务提供了很好的实用建议。
-
高效能人士的 7 个习惯: 这将帮助你了解如何在众多竞争需求中优先考虑正确的事项,如何有效沟通并避免职业倦怠。
-
极棒的数据领导力: 综合整理的有用文章列表,涵盖了领导力的大部分方面。
-
数据科学领袖播客: 非常适合学习更广泛的与业务相关的策略,以领导数据科学团队。
感谢阅读!
学会“遗忘”机器
一种基于数据的方法来进行生成语言模型的机器遗忘
·
关注 发表在 Towards Data Science ·7 分钟阅读·2023 年 11 月 23 日
--
图像生成自 DALLE 3
在当今的科技领域中,几乎很难找到一个没有听说过机器学习的人。在过去的十年里,研究领域如此流行,以至于即使是行业之外的人也熟悉诸如人工智能(AI)、神经网络(NNs)和机器学习(ML)等术语。
然而,谈到机器遗忘时,似乎法律行业比科技社区听到的更多。最近大型语言模型(LLMs)的迅猛发展,在快速变化的 IT 世界中感觉像是十年,尽管实际上只有 1-2 年,揭示了数百个与 AI 发展相关的未解决的伦理和法律问题。小说家起诉 OpenAI 未经同意使用他们的文本来训练 GPT 模型。Twitter 上充满了艺术家的批评评论,他们认为他们的作品被用于侵犯版权法。遵守“被遗忘权”变得极其困难。
与 AI 对齐类似,机器遗忘似乎是一个被忽视的领域,因为现有的开源解决方案有限。我相信应该鼓励和推广机器遗忘的探索,特别是考虑到目前有关 AI 使用的法律和伦理规范尚不完善,数据保护机制严重缺失。在本文中,我想提出一些对生成语言模型早期应用遗忘技术的实际改进建议。
机器遗忘
“机器遗忘”或“机器遗忘”一词的意思正如其字面意思所示:它包括旨在从机器学习模型的“知识存储”中删除请求信息的技术。然而,当你需要考虑实际方法以在时间、计算资源和模型在“未遗忘”数据上的性能方面高效实现这一点时,这远非直观。一个明显的解决方案是使用初始数据集从头开始重新训练模型,同时排除“遗忘集”——但对于深度神经网络的遗忘来说,这将是一个极其不切实际的方法。

“机器遗忘框架”来自于“机器学习调查”
机器遗忘领域的核心研究成果被简洁地汇编在“机器遗忘调查”中。另一篇涵盖基础知识并提供易于理解解释的文章是“机器遗忘:遗忘的责任”。虽然我个人推荐这些资源,但你可以找到大量其他优质的研究材料。然而,在实际应用方面,仍有许多工作要做。
一个有前景的举措,可能将该领域从理论探索转向实际应用,是NeurIPS 2023 机器遗忘挑战赛。在这里,参与者竞争以创建一个用于 ResNet18 卷积神经网络的遗忘算法。
生成语言模型的机器遗忘
考虑到生成语言模型的广泛可访问性和对大多数互联网用户的推广,迫切需要遗忘机制。最早成功的技术之一不久前在开源中发布;你可以在罗宁·埃尔丹和马克·鲁西诺维奇的“谁是哈利·波特?LLM 中的近似遗忘”中找到详细信息。

使用StableDiffusion生成的图像
作者们对 Meta 在今年夏天发布的Llama 2 7b 聊天模型采用了一种数据增强方法进行机器遗忘。选择的遗忘目标,也称为“遗忘集”,是哈利·波特系列(真是聪明,这些麻瓜!),这是机器遗忘的一个完美示例,因为它可能涉及版权法的违反。他们展示了只需一小时的 GPU 微调,得到的模型就无法回忆起大多数与哈利·波特相关的内容,同时其在常见基准上的表现几乎没有受到影响。
方法概述
该方法的主要目标是让 Llama 2 7b 忘记从定义的遗忘集中的实体之间的联系(“哈利”<是朋友>“赫敏”),通过给模型提供合理的通用替代(“哈利”<是朋友> “莎莉”)。为了将这些替代作为微调数据集中的目标标签,在生成目标时,“待遗忘领域”中的特殊术语应受到高度惩罚。这种惩罚可以通过将方程(1)中由强化模型在原始输入——哈利·波特书籍——上生成的 logits 与基线模型在原始输入的通用翻译上生成的 logits 相结合来实现。

“谁是哈利·波特?LLM 中的近似遗忘”中的方程(1)
增强模型是进一步微调于哈利·波特小说的 Llama 2 7b。基线模型是未经微调的 Llama 2 7b。为了使基线模型的输出分布偏离哈利·波特主题,作者将原始输入中的特有术语替换为通用术语,以便模型根据与哈利·波特系列无关的上下文生成下一个词。为了自动化这种替换,作者引入了一个锚定术语字典——特定于“哈利·波特”的术语——映射到通用翻译上。该字典由GPT-4完全收集。

{‘Anchor Terms’: ‘通用翻译’}来自“谁是哈利·波特?LLMs 中的近似遗忘”
结果的微调数据集由来自哈利·波特书籍的标记化文本块组成,与目标标签一一对应,这些目标标签是对应于方程(1)中v_generic的最大条目的标记。

来自“谁是哈利·波特?LLMs 中的近似遗忘”的微调数据集的一部分
总结来说,作者描述了遗忘过程中的四个步骤:

来自“谁是哈利·波特?LLMs 中的近似遗忘”的机器遗忘算法
利用该方法:关键挑战
数据增强方法的结果很有前景,鼓励在类似任务中进一步应用。然而,作者在几个应用阶段留有改进的空间。
对 GPT-4 现有知识的依赖: 该算法在一定程度上依赖 GPT-4 对哈利·波特系列的先前理解来生成通用翻译。虽然模型预计对哈利·波特领域有广泛的知识,但系列粉丝的重新评估可能提供宝贵的见解。
特有术语的挑战: 惩罚与系列相关的所有独特术语是一个问题。例如,将每个‘Harry’替换为像‘John’这样的常见名字,会扰乱模型对自然语言的理解,导致句子变成,“Harry 走向他,说,‘嗨,我的名字是John’。”为解决这个问题,作者采用了以下策略:
-
排除重复的锚定词实例对损失函数的贡献,超出其初始出现的影响。
-
降低与之前出现过的术语翻译相关的 logits 的可能性。
然而,这种策略也会影响模型的通用语言理解。例如,一个适用于微调数据集的合理替代方案是,“Harry 走向他,说,‘嗨,我的名字是Harold’。”
评估技术: 团队利用 GPT-4 进行了初步评估,评估内容包括 300 个《哈利·波特》提示的完成情况,以及对完成内容的进一步分析。然而,他们承认其准确性存在局限性,因此选择了对结果进行人工检查,以便在最终训练中进行更彻底的验证。作者没有提供如何进行这种人工检查的详细信息。
克服挑战
解决关键挑战的更有效方法是结合人类洞察力和大型语言模型(LLMs)的混合方法。
为了利用人类直觉和大型语言模型的集体优势,我设计了三个众包项目接口,方便使用 LLM 和人群进行协作标注。每个为人工标注设计的接口都针对上面列出的挑战量身定制。
对 GPT-4 现有知识的依赖:

图片由作者提供
使用命名实体识别(NER)来纠正 GPT-4 对锚定术语词典的 NER 选择。作为输入,提供文本和 GPT-4 的术语选择(你可以要求模型直接返回文本中的位置),并指示人群纠正和补充选定的实体。
处理特殊术语的挑战:

图片由作者提供
借助基线模型,检查语言正确性提示,通过基线模型对原始输入的通用翻译进行完成。所有基线模型对答案不确定的示例(输出标记的概率低于你选择的经验阈值)应发送到显示在图片中的众包项目。
评估技术:

图片由作者提供
可以按照上述图片所示的方式设计 GPT-4 的评估的人工检查。
结论
作者指出,与虚构的《哈利·波特》世界不同,非虚构领域可能没有如此丰富的独特术语,这可能使基于锚定术语的数据增强方法不适用。然而,如果本文中概述的数据增强技术适用于你的项目,考虑整合建议的改进并引入你自己的调整。让我们共同推进机器去学习领域的发展!
前向传递中的学习与推理:新框架
学习与推理统一为一个连续、异步和并行的过程
·
关注 发表在 Towards Data Science ·18 分钟阅读·2023 年 1 月 20 日
--
在三层网络上进行前向传递中的学习和推理。 推理和学习同时进行。学习重用与推理相同的前向路径。综合来看,这些信号传播特性将学习和推理统一为一个连续、异步和并行的过程——从而解除学习的限制。这与之前关于学习的观点大相径庭,尤其是在监督设置和反向传播下。
在这篇文章中,我介绍了一个前向传播的推理和学习框架,称为信号传播框架。这是一个只使用前向传播来学习任何类型数据和任何类型网络的框架。我展示了它在离散网络、连续网络和尖峰网络中都能良好地工作,而无需修改网络架构。换句话说,用于推理的网络版本与用于学习的网络版本相同。相比之下,反向传播和以前的工作对于训练版本的网络有额外的结构和算法元素,这些被称为学习约束。
信号传播是一种约束最少的学习方法,且具有比反向传播以前的替代方法更好的性能、效率和兼容性。它也比反向传播具有更好的效率和兼容性。这个框架在arxiv.org/abs/2204.01723(2022 年)和ieeexplore.ieee.org/document/10027559中介绍。前向学习的起源在我的工作arxiv.org/abs/1808.03357(2018 年)。
我开发了一个库来实现对任何模型的前向传播学习。该库的快速开始指南可以帮助你在现有模型上实现这个库。还有cifar-10的示例实验,也作为教程使用。
[## GitHub - amassivek/signalpropagation: 前向传播学习与推理库,适用于神经网络...
信号传播:一个统一学习和推理的前向传播框架,一个用于训练的 Python 包...
这篇文章是一个关于前向传播学习的简明教程。在教程结束时,你将理解这个概念,并知道如何在你的工作中应用这种学习方式。教程提供了适合初学者的解释,以及适合专家的详细步骤。
目录
-
引言
1.1. 以前的学习方法
1.2. 一种新的学习框架
1.3. 学习约束的问题
-
学习的两个要素
-
前向传播中的学习
3.1. 学习方法
3.2. 学习步骤
3.3. 完整程序概述
3.4. 尖峰网络
-
前向学习的工作
4.1 错误前向传播
4.2. 前向传播
-
阅读材料
-
附录:信用分配阅读
6.1. 空间信用分配
6.2. 时间信用分配
1. 引言
1.1. 以前的学习方法
学习是使人工神经网络工作的活跃成分。反向传播被认为是表现最佳的学习算法,为人工神经网络的成功提供了动力。然而,它是一个高度约束的学习算法。这些约束被视为其高性能所必需的。普遍接受的观点是,减少这些约束中的任何一个都会降低性能。然而,由于这些相同的约束,反向传播在效率和兼容性方面存在问题。它在时间、内存和能量方面效率低下,与生物学习模型、神经形态芯片和边缘设备的兼容性低。因此,人们可能会考虑通过减少不同的约束子集来解决这个问题,以提高效率和兼容性,同时尽量不降低性能。
例如,训练网络的反向传播有两个约束: (1) 反馈权重与前馈权重对称;以及 (2) 每个神经元都需要这些反馈权重。推理网络从不使用反馈权重,这就是我们将它们称为学习约束的原因。这些约束的子集包括:不添加任何反馈权重、只在五层网络中的一两层添加反馈权重、反馈权重不对称,或这些的任意组合。这意味着约束可以部分或完全地添加或移除,从而形成约束子集进行减少。可以不断尝试减少这些约束的不同子集,以提高效率和兼容性,并希望不会对性能产生重大影响。
以前的反向传播替代学习算法曾尝试放松约束,但没有成功。它们减少学习中的约束子集以提高效率和兼容性。它们保留其他约束,期望保持与保留所有约束(即反向传播)相似的性能。因此,这意味着学习约束存在一个光谱,从高度约束的反向传播到没有约束的信号传播,即我在这里介绍的框架。
1.2. 一种新的学习框架
现在,我展示了与以往工作的不同。这里展示的结果支持最少约束的学习方法——信号传播——在性能、效率和兼容性上优于反向传播的替代方法,这些替代方法选择性地减少学习的约束。这包括一些成熟且有重大影响的方法,如随机反馈对齐、直接反馈对齐和局部学习(所有这些方法都不依赖反向传播)。这是对从神经科学到计算机科学等领域学习的迷人洞察。它惠及生物学习(例如在大脑中)到人工学习(例如在神经网络、硬件、神经形态芯片中)的领域。
信号传播也显著影响了学习算法未来研究的方向,其中反向传播是比较的标准。在学习约束的范围中,与高度约束的反向传播相反,信号传播是最少约束的方法,用于比较和作为开发学习算法的起点。仅有反向传播作为最佳比较,学习算法没有起始点,只有最终目标。现在,我引入信号传播作为学习算法评估其效率、兼容性和性能的新基准。
1.3. 学习约束的问题
反向传播下的约束是什么?
为什么这是个问题?
反向传播下的学习约束与大脑中的学习难以调和。下面,我提供了主要的约束:
-
在网络中完成完整的前向传播后,才可以顺序地在反向传播中提供反馈。
-
训练网络需要为每个神经元添加全面的反馈连接。
-
学习和推理有两种不同的计算。换句话说,反馈算法是一种与前馈活动分开的计算类型。
-
反馈权重需要与前馈权重对称。
这些约束还妨碍了在硬件上高效实施学习算法,原因如下:
-
权重对称性与非双向的基本计算单元不兼容。
-
非本地权重和误差信息的传输需要特殊的通信通道。
这些学习约束禁止在学习过程中进行计算并行化,并增加了内存和计算量,原因如下:
-
前向传播需要在后向传播开始之前完成(时间,顺序)
-
隐藏层的激活需要在前向传播期间存储,以便于后向传播(内存)
-
反向传播需要特殊的反馈连接(结构)
-
参数在前向传播的反向顺序中更新(时间,同步)
2. 学习的两个要素
神经网络中的学习是如何进行的?
简短回答:空间和时间信用分配
数据主要有两种形式:单个输入和多个连接的输入,这些输入按顺序或时间上连接。狗的图像是一个单个输入,因为网络仅基于该图像进行预测。在这种情况下,网络被提供一个单一图像以预测该图像是狗还是海龟。
一只海龟行走的视频是多个连接的输入,因为视频由多个图像组成,网络在看到所有这些图像后进行预测。在这种情况下,网络被提供多个图像以预测海龟是行走还是隐藏。
反向传播 (BP) 用于单个输入;时间反向传播 (BPT) 用于多个连接的输入。
BP 提供的学习包括:
- 每个神经元(空间信用分配)
BPT 提供的学习包括:
-
每个神经元(空间信用分配)
-
多个连接的输入(时间信用分配)
为每个神经元提供学习被称为空间信用分配问题。空间信用分配指的是网络中神经元的布置,例如组织成神经元层。例如,在一个五层网络中,反向传播学习信号从第五层依次传递到第一层神经元。在第三部分中,我将展示信号传播学习信号是如何从第一层传递到第五层的,与推断过程相同。
为多个连接的输入提供学习被称为时间信用分配问题。时间信用分配指的是在多个连接输入中移动。例如,视频中的每一帧图像都被输入到网络中,产生来自相同神经元的新响应。每个神经元的响应是特定于每张图像/输入的。因此,反向传播学习信号在这些神经元响应中传递,从视频中最后一张图像的神经元响应开始到第一张图像的神经元响应。在第三部分中,会清楚地看到信号传播学习信号从第一张图像的神经元响应传递到最后一张图像的神经元响应,与推断过程相同。
请注意,时间信用分配的内在问题是空间信用分配。时间信用分配将学习信号通过视频中每张图像。对于每张图像,空间信用分配将学习信号传递到每个神经元。信号传播优雅地通过解决内在问题来解决外在问题——前向传播,通过构建推断网络,遍历两个问题。
BP 进行空间信用分配。BPT 将 BP 扩展到同时进行空间和时间信用分配。(有关空间和时间信用分配的完整阅读,请参见第六部分。)
3. 前向传播中的学习
信号传播框架 (SP)
我在这里介绍的是一种前向传递学习和推理的框架,称为信号传播(SP)。这是一个令人满意的解决方案,用于时间和空间的信用分配。SP 是一种约束最少的学习方法,其性能、效率和兼容性优于以前的反向传播替代方案。它还具有比反向传播更好的效率和兼容性。SP 提供了一个合理的效率和兼容性性能折中。这特别吸引人,因为它兼容基于目标的深度学习(例如监督学习和强化学习),适用于新硬件和长期存在的生物模型,而以前的工作则不然。(一般来说,反向传播是表现最好的算法。)
SP 在学习发生时没有约束,包括:
-
仅前向传递,没有反向传递
-
无反馈连接或对称权重
-
只有一种用于学习和推理的计算类型。
-
在前向传递过程中随输入传播的学习信号
-
在神经元/层被前向传递到达后,更新参数
一个有趣的见解是,SP 为大脑中没有错误反馈连接的神经元如何接收全局学习信号提供了解释。
因此,信号传播是:
-
与大脑和硬件中的学习模型兼容。
-
学习更高效,时间和内存消耗较少,无需额外结构。
-
一种低复杂度的学习算法。
3.1. 如何在前向传递中学习?
信号传播将目标视为额外的输入(见下图)。通过这种方法,SP 将目标前向传递通过网络,就像它是一个输入一样。

将目标视为输入。来自 Upsplash 的动物图像。
SP 在网络中向前移动(见下图),将目标和输入越来越靠近,从第一层(左上)一直到最后一层(右下)。注意到在最后一步/层时,狗的图像接近其目标[1,0,0],而青蛙的图像接近其目标[0,1,0]。然而,狗的图像和目标与青蛙的图像和目标之间仍有较大距离。这一操作发生在每层神经元的表示空间中。例如,第 1 层的神经元接收狗的图像(输入 x)和狗的标签(目标 c),分别输出激活 h_1_dog 和 t_1_dog。青蛙的情况也一样,产生 h_1_frog 和 t_1_frog。在这些神经元的激活空间中,SP 训练网络使输入及其目标更加接近,同时与其他输入及其相应目标保持距离。

层层推进,将目标及其相应输入逐渐靠近,但与其他输入和目标保持距离。来自 Upsplash 的动物图像。
3.2. 前向学习的步骤
以下是一个示例三层网络的整体图。每层都有自己的损失函数,用于更新网络中的权重。因此,SP 执行损失函数并在目标和标签到达某层时立即更新权重。由于 SP 将目标和输入一起(交替)输入,层/神经元权重会立即更新。对于空间信用分配,SP 在输入从第一层到达最后一层之前更新权重。对于时间信用分配,SP 为每个时间步长的多个连接输入(例如视频中的图像)提供学习信号,而无需等待最后一个输入被送入网络。

这是一个三层网络。学习和推理的前向传播将分三步进行。每层有自己的损失,共有三种损失。输入是 x,目标是 c,二者通过网络的前端输入。

在前向传播中,学习和推理的整体算法。推理和学习阶段并行运行,每层的权重会立即更新。注:对于图示网络(左),N = 3,即层数。为了清晰起见,省略了偏置(b 和 d)。损失 L(例如梯度、赫布式)和优化器(例如 SGD、Momentum、ADAM)有很多选择。输出函数 output(),y,在下面的步骤 4 中详细说明。
接下来,我们将逐步、逐层进行学习和推理(即产生答案/预测),以前向传播的方式进行。请注意,在下面的指南中,目标和输入被批量拼接到前向传播中,便于跟随。
步骤 1) 层 1



步骤 2) 层 2



步骤 3) 层 3

步骤 4) 预测
在输出层,有三种选择来输出预测结果。第一种和第二种选项提供了更多的灵活性,并自然地从使用前向传播的训练过程中跟随。第一种选项是取一个 h_3 作为一个类别,并与每个 t_3 进行比较。例如,SP 输入一张狗的图像并得到 h_3_dog,然后输入所有类别的标签并得到 t_3_i = { t_3_dog, t_3_frog 和 t_3_horse},最后它将 h_3_dog 与每个 t_3_i 进行比较;最接近的 t_3_i 即为正确的类别。
第二个选项是第一个选项的自适应版本。它是自适应的,因为 SP 不再将 h_3_dog 与每个 t_3_i 进行比较,而是寻找最近的 t_3_i 子集。例如,我们维护一个树结构,其中 t_3_frog 在树中比 t_3_horse 更接近 t_3_dog。因此,我们首先将 h_3_dog 与 t_3_frog 进行比较,然后与 t_3_dog 进行比较,并停止。我们不会与 t_3_horse 进行比较,因为它距离太远,不在我们最近的 t_3_i 子集中。
第三个选项:经典且直观的选择是训练一个预测输出层。这个选项在回归和生成任务中也更直接。例如,一个分类层,每个类别有一个输出。因此,第 3 层将是一个分类层。注意,在推断过程中 t_3 不再使用。此外,注意到 t_3_i 等同于一个分类层。要看到这一点,只需将 t_3_i 连接在一起,形成一个分类(预测)层的权重矩阵,与 h_3(例如 h_3_dog,h_3_horse,…)一起使用。这意味着第三个选项是第一个选项的特例,并且可以是第二个选项的特例。



3.3. 完整过程概述


3.4. 尖峰网络
尖峰神经网络类似于生物神经网络。它们被用于大脑学习模型中,也用于神经形态芯片。尖峰神经网络在学习中存在两个问题。首先,反向传播下的学习约束与大脑学习难以协调,这阻碍了在硬件上高效实现学习算法(如上所述)。其次,训练尖峰网络会导致死亡神经元问题(见下文)。
下面提供了一个参考图。这些网络中的神经元通过激活(尖峰)来响应输入,以将信息传递给另一个神经元,或者什么都不做(左上图)。通常,这些网络存在一个问题,即神经元从不激活,这意味着它们从不尖峰(左下图)。因此,无论输入是什么,神经元的响应始终是无反应。这被称为死亡神经元问题。
解决这个问题的最流行方法是使用替代函数来替代神经元的脉冲行为。网络仅在学习期间使用替代品,即当学习信号发送到神经元时。替代函数(蓝色)即使在神经元不脉冲时也为神经元提供值(右上图)。因此,即使神经元没有脉冲向另一个神经元传递信息,它仍能学习(右下图)。这有助于防止神经元的“死亡”。然而,替代品在硬件学习中,如神经形态芯片中难以实现。此外,替代品不适合大脑中的学习模型。
信号传播提供了两种与大脑和硬件学习模型兼容的解决方案。

以下是学习信号(标记为红色)通过一个脉冲神经元(标记为 S)、穿过电压或膜电位(U),以更新权重(W)的可视化。左侧是带有“死神经元”问题的反向传播。左二是带有替代函数(f)的反向传播。反向传播的学习信号是全局的(L_G),来自网络的最后一层;虚线框表示上层神经元/层。
右侧的其他图像展示了信号传播(SP)提供的两种解决方案。首先,SP 也可以使用替代品,但学习信号不经过脉冲方程(S)。相反,学习信号在脉冲方程(S)之前,直接附加到替代函数(f)上。因此,SP 与大脑中的学习更兼容,例如在生物神经元的多室模型中。其次,SP 可以仅使用电压或膜电位(U)进行学习。在这种情况下,学习信号直接附加到 U 上。这不需要替代品或对神经元的更改。因此,SP 与硬件中的学习兼容。

4. 前向学习的研究工作
关于前向学习的工作列表 - 使用前向传播进行学习。工作按日期排序。
一个社区维护的仓库网页用于记录前向学习方法,位于 amassivek.github.io/sigprop 。代码库可在 github.com/amassivek/signalpropagation 获取。
4.1. 误差前向传播算法(2018)
误差前向传播算法是信号传播框架在前向传播中的实现(如下图)。在信号传播下,S 是上下文 c 的变换,对于监督学习来说,c 是目标。
在误差前向传播中,S 是从输出到网络前端的误差投影,如下图所示。

错误前向传播算法。来自 MNIST 数据集 的图像 7。
错误前向传播:重用前馈连接在深度学习中传播错误
4.2. 前向前向算法 (2022)
前向前向算法是信号传播框架在前向传递中的实现(见下图)。在信号传播下,S 是上下文 c 的变换,对于监督学习来说,这是目标。
在前向前向中,S 是目标 c 和输入 x 的连接,如下图所示。

前向前向算法。来自 MNIST 数据集 的图像 7。
前向前向算法
www.cs.toronto.edu/~hinton/FFA13.pdf
5. 阅读材料
信号传播:前向传递中的学习与推理框架
arxiv.org/abs/2204.01723 (2022)
前向前向算法
www.cs.toronto.edu/~hinton/FFA13.pdf (2022)
错误前向传播:重用前馈连接在深度学习中传播错误
arxiv.org/abs/1808.03357 (2018)
5.1 其他材料
一份关于空间和时间信贷分配的良好指南。我参考了它来帮助编写“附录:信贷分配阅读”。
使用深度学习的经验训练尖峰神经网络
arxiv.org/abs/2109.12894 (2021)
社区维护了一个代码库网页来记录前向学习方法,位于 amassivek.github.io/sigprop。
代码库可以在 github.com/amassivek/signalpropagation 上获取。
感谢:Alexandra Marmarinos 的编辑工作和指导。
6. 附录:信贷分配阅读
6.1. 空间信贷分配
空间信贷分配的问题是:学习信号如何到达每一个神经元?
在下图的左侧,是一个三层网络。一般来说,学习分为两个阶段:推理阶段和学习阶段。在第一个阶段,称为推理阶段,输入从第一层传递到最后一层。由于输入通过网络前向传递,因此推理阶段发生在网络的“前向传递”过程中。在第二阶段,称为学习阶段,学习信号(标记为红色)需要到达网络中的每一个神经元。
不同的学习算法在学习阶段有不同的解决方案。在反向传播中,学习信号通过网络向后传播,因此学习阶段发生在通过网络的“反向传递”中。正如我们将在信号传播中看到的,学习也可以在前向传递中进行。
广泛来说,学习阶段有两种方法。第一种方法计算全局学习信号(左中图),然后将此学习信号发送到每个神经元。第二种方法在每个神经元(或层)计算局部学习信号(右图)。第一种方法的问题在于需要以精确的方式协调将此信号发送到每个神经元。这在时间、内存和兼容性上都很昂贵。第二种方法没有遇到这个问题,但性能较差。

6.2. 时间信用分配
信贷分配的时间局部性问题是:全局学习信号如何到达多个连接的输入(即每个时间步)?
单张图像只需要学习信号到达每个神经元。然而,视频是一系列连接的图像。因此,学习信号需要通过多个连接的输入(即时间)进行传播,从视频中的最后一张图像一直到视频中的第一张图像。这一概念适用于任何序列或时间序列数据。那么,全局学习信号如何到达每个时间步?有两种流行的方法来回答这个问题:时间上的反向传播和前向模式微分。
6.2.1. 时间上的反向传播(BPT)
对上述问题的主要回答如下,并分为两个阶段。首先,将构成视频的所有图像逐一输入网络。这是推断阶段,在这个阶段,多个连接的输入通过网络向前传递(前向传递)。其次,从最后一张图像开始向回传播学习信号直到第一张图像。这是学习阶段,在这个阶段,学习信号在多个连接的输入(即时间)中向后传播;因此,称为时间上的反向传播。
第一步:推断
在下图中,BPT 将构成视频的每一张图像 X[i](例如乌龟走路的图像)输入网络。BPT 从第 1 张图像 X[0](第一张图像的左下角)开始,这是时间步长 1(时间显示在图的顶部)。接着,BPT 输入图像 X[1],这是时间步长 2。最后,我们以时间步长 3 的最后一张图像 X[2]结束——这个演示用于非常短的视频或 GIF。每次 BPT 将图像输入网络时,请注意网络中的中间层将每张图像通过时间连接到下一张图像。

第二步:通过时间进行学习
BPT 将学习信号(用红色标记)从图像(时间)中向后传播,形成了乌龟行走的视频。学习信号是从损失函数(图中的右上方)形成的。它的传播方向与我们输入图像 X[i]的方向相反。首先计算时间 3 的图像 X[2]的梯度/更新,然后是时间 2 的图像 X[1],最后是时间 1 的图像 X[0]。这就是为什么它被称为时间上的反向传播。再次注意,网络中的中间层将来自最后一张图像 X[2]的学习信号连接到第一张图像 X[0]。

6.2.2. 正向模式微分(FMD)
在 FMD 下,推断(第 1 步)和学习(第 2 步)阶段的行为是类似的。因此,FMD 将第 1 步(推断)和第 2 步(学习)一起进行(交替)。怎么做?在第 2 步中,FMD 将学习信号向前传播通过图像(时间),这与第 1 步中的推断过程非常相似。因此,学习信号不再需要从视频中的最后一张图像 X[3]返回到第一张图像 X[0]。结果是:FMD 的学习信号从 X[0]开始,而不需要等待 X[3]。
为什么选择 FMD 而不是 BPT?上面我讨论了在反向传播(backpropagation)下学习的限制以及它在效率和兼容性方面存在的问题。FMD 尝试提高效率。特别是,BPT 会在学习之前将所有构成视频的图像输入到网络中。而 FMD 则不会,因此在时间上比 BPT 更高效。然而,FMD 在成本上明显高于 BPT,特别是在内存和计算方面。注意,FMD 解决了时间上的问题。然而,它并没有解决反向传播下空间信用分配的学习限制,这在 FMD 中也存在。



除非另有说明,否则所有图片均由作者提供。
学习机器学习 | Maarten Grootendorst: BERTopic、数据科学、心理学
《学习机器学习》第一期的内容是对 BERTopic 的幕后 mastermind Maarten Grootendorst 的深刻采访。
·
关注 发表在 Towards Data Science ·43 min read·2023 年 2 月 13 日
--
欢迎来到“学习机器学习”,这是一个探索机器学习令人兴奋世界的采访系列,重点关注的不仅仅是算法和数据,还有来自专家的职业建议和生活经验。
人工智能正在改变世界,机器学习推动着这场变革。在每一 集 中,来自行业和学术界的领先从业者分享他们的知识、经验和洞见,谈论在这一快速发展的领域取得成功所需的条件。
首集 以 马尔滕·格罗滕多斯特 为特色,他是 BERTopic 和 KeyBERT 的创建者,也是 在 Towards Data Science 上发表的众多文章 的作者。他分享了对开源项目、心理学在机器学习和软件开发中的作用以及自然语言处理未来的看法。该访谈现已在所有 播客平台 上提供。
收获
马尔滕·格罗滕多斯特是许多强大 Python 库的创建者,包括 KeyBERT 和 BERTopic。他最初从心理学开始他的职业生涯,获得了临床心理学和组织心理学的硕士学位后,转向了数据科学。他通过开发开源库和撰写深刻的数据科学文章迅速在该领域产生了影响。他最常用的库 BERTopic 是一个主题建模框架,它是一种自动识别文档集中的主题的方法。它可以用于探索性数据分析和跟踪趋势随时间的变化。他还讨论了在创建 BERTopic 时面临的挑战,以及最新版本的目标和特点。
马尔滕讨论了类似 ChatGPT 的 approaches 的含义,这些 approaches 常被误认为是人工通用智能(AGI),以及它们在行业中的接受情况。他分享了对这些拥有数十亿参数的大型语言模型如何融入 AGI 讨论的好奇,并认为即将到来的一年将会有重大变化。
马尔滕的建议包括:
-
在深入学习更复杂的算法之前,专注于理解和掌握编码和机器学习的基础知识。
-
建立坚实的基础,因为这将使未来的成长更加顺利和容易。
-
理解模型的评估。在主题建模中,“真相”往往取决于观察者的视角。
-
强调在尝试解决问题之前,真正理解问题的重要性,引用他在癌症研究中的经验,其中一位同事花了数周时间来理解问题,然后才开始编码。
-
你不需要一次性掌握所有的知识。找到健康生活和你对机器学习的热情之间的平衡是重要的。
现在就观看视频,或在 Spotify 或 Apple Podcasts上收听!
内容表
欢迎
收获
完整采访
— 背景
— BERTopic 深入探讨
— 从机器学习中学习
视频采访
Spotify 音频
资源
— Maarten Grootendorst
— 从机器学习中学习联系
视频内容
完整采访
背景
Seth: 欢迎。很荣幸有 Maarten Grootendorst 在这里。他是许多有用的 Python 库的创作者,包括KeyBERT和BERTopic。我一直很欣赏您的工作,并且非常喜欢使用您的库。欢迎!
Maarten: 太棒了,谢谢你邀请我。我很乐意在这里。
Seth: 所以,为了开始讨论,您能介绍一下您的职业背景,以及您如何涉足数据科学领域吗?
Maarten: 当然。我的背景有点不寻常。我最初是一名心理学家。我有组织心理学和临床心理学的硕士学位。这些领域的研究很有趣,也有很多有趣的工作可以做。但我一直觉得缺少了点什么。
一些,我不会说是确切的,但是更偏向硬科学而不是我当时所从事的软科学。所以我开始探索更多统计学方面的东西,因为这是我们在心理学中经常做的事情,最终我开始学习编程和机器学习。
然后我想到,好吧,在荷兰学习相对便宜,所以我决定攻读数据科学硕士学位。那里我真正发现了我的激情,这是我可以找到真正结合了心理学和技术方面的地方。因为在几乎所有情况下,数据科学中我们所做的大部分工作都涉及某种人类方面的因素。
所以我真的可以利用我的心理学背景。然后之后,我开发了一些包,如 KeyBERT 和 BERTopic,写了一些东西之类的。
Seth: 对,是的,所有那些令人惊叹的有用软件包。所以,你的心理学背景,你想再深入一点吗?
Maarten: 是的,当然。我开始学习社会心理学的学士学位,这是相当广泛的,对吗?包含了许多不同的学科。而且我的母亲实际上是一名组织心理学家,所以我有点跟随她的脚步。不,但这是我熟悉的东西,也是我觉得有趣的东西。
我发现组织心理学非常有趣。它涉及到工作场所的人类行为,以及这些行为如何与生活中的重大方面——工作——相互关联,对吧?但那时候我真的还不够成熟,无法深入这个领域并做必要的工作。
我想,好的,让我在进入职场之前进一步探索和发展自己。因此,我探索了临床心理学,在那里我可以更多地专注于帮助那些有焦虑症、抑郁症或创伤后应激障碍等问题的人。
然后,当然你从中学到很多有趣的东西,但我总觉得有些东西缺失。不是说这些领域不有趣,但你知道,像我这样的人总是试图找到自己的人生目标,找出是什么让自己快乐。虽然这很不错,也很有趣,但我很确定它不会让我在接下来的 30、40 年里感到快乐。
所以你探索,最终我发现了机器学习。
Seth: 当你第一次发现机器学习时,是什么真正吸引了你?
Maarten: 有趣的是,因为我缺乏一些技术方面的知识,这也是吸引我进入机器学习的原因之一,因为它确实是一个新领域。
这些算法背后有很多技术基础,但你使用和应用它们的方式仍然需要你在许多情况下具备某种商业感知。一种视角,即我们什么时候使用它?这真的有必要吗?我们需要一个非常复杂的算法,还是可以使用相对简单的东西?
那个难题真的很有趣,因为它涉及很多小方面,远不止是优化这个算法。
那个难题,对我来说仍然是个谜。这个难题真的很有趣,因为它涉及很多小方面,远不止是优化这个算法。正是这种吸引力让我对它产生了兴趣,并且我可以发挥我认为的许多技能。
Seth: 当你从心理学转向更技术性的领域时,你觉得自己首先需要学习哪些东西?
Maarten: 作为心理学家,你并不是最具技术性的人,对吧?你专注于这种互动、人类行为和观察技巧。
里面有一些统计数据,但最初的关键是基础。所以,编程当然很重要,你可以非常了解一个算法,但如果你不能清晰且准确地编写代码,那么一定会出现 bug 或问题。这一点真的很重要。
与其深入复杂的学习算法,不如从非常简单的回归开始,这实际上可能非常复杂,如果你深入研究其中的一些复杂性。因此,我专注于在进入下一步之前尽可能完美地理解这一点。
最好的学习方式确实是彻底理解和掌握基础知识。如果你把基础做得非常好,就像是直觉或自动完成的事情,进入下一步就会变得容易得多。这一直是我的重点。
Seth: 所以就是创建可以成为你未来工作基础的构建块。
Maarten: 是的,是的。那基础说得很好。基础越扎实,你在其上建立的东西就会越好。
BERTopic 深度解析
Seth: 完全正确。既然有了 BERTopic 的创造者,我想我们应该花些时间深入了解一下。如果你不介意的话,我让你来介绍一下。简单讲讲主题建模和它的强大之处。
Maarten: 是的,当然。所以 BERTopic 是一个主题建模框架,其中你实际上拥有,例如 10,000 个文档。这可以是任何东西,对吧?可以是某个产品的评论,可以是某个系统的票据,也可以是医院的多学科会诊记录和一些患者。基本上,你想知道的是:这些文档讲了什么?你可以做很多事情。你可以阅读所有 10,000 个文档并标记它们,这完全没问题。如果你有时间的话。
Seth: 花一点时间。
Maarten: 只需一点点,或者你可以自动完成。
这就是主题建模发挥作用的地方,因为顾名思义,它基本上是从这些文档集合中提取主题,并以帮助你理解这些文档内容的方式传达这些主题。它还可以帮助你做趋势分析,例如,如果你有 Twitter 消息,可以查看 Covid 在两年前和现在的讨论情况。
这是一种非常好的提取信息的方式,但因为它是一种自动化的方式,没有真实的标准,所以也很难进行评估。因此,这与我作为心理学家的特点非常契合,因为它允许大量的人类评估和对技术的解释。
你可以用它进行探索性数据分析,看看我的文档中有什么。但它也被广泛应用于社会政治科学中,我们观察过去几年发展起来的某些信息,并查看这些趋势如何发展和变化,针对不同的类别或目标等。
塞斯: 是的。我已经关注 BERTopic 一段时间了,我知道 0.13 版本最近刚刚发布。我想知道,最初的包的目标是什么?它在过去三年左右有什么变化?
马滕: 它开始时是作为创建一个流程的方式。好的。所以,聚类和尝试从中提取主题表示的流程已经存在了一段时间。但我想找到一种方法,以一种流程的方式来做,在你使用的步骤之间几乎没有假设。

图片 由 Maarten Grootendorst 提供
所以我们基本上是在将文档转换为数字,即数值,然后将其缩减到更小的维度。所以,你不再有 300 个数值,而是将其压缩为五个。我们将对这些文档进行聚类,并从这些聚类中提取主题表示,已经有很多流程可以做到这一点。
但你最初想到的主题是以一种方式来实现的,你可以说,好的,我对这个聚类算法不太满意,我要选择完全不同的东西。或者,我对这个降维算法不满意,我要选择不同的东西。
然后,重点主要放在最后一部分,即主题提取方式。我使用了一种修改版的 TF-IDF 度量,叫做cTF-IDF。因为我最初是这样想的,所以很容易开发出许多变体和扩展,通常会成为不同的包。
所以在主题建模中,你有LDA,这是一种经典的主题建模技术。是的。但如果你想使用某种变体,你必须安装不同的包。在许多情况下(当然不是所有情况),gensim已经实现了很多。但是,如果你想做层次主题建模或动态主题建模,我认为这些功能还没有实现,你需要为此安装不同的包。
我希望它变成一个一站式的主题建模工具,而不是简单地说,每个主题是最好的主题模型。实际上,它绝对不是。我是说,我们仍然需要遵循无免费午餐定理。但你基本上可以用它来做所有事情。
因为我专注于每个主题的最小假设管道。现在我们在进行主题建模——在线主题建模,基于类别,半监督,监督,以及其他一些方法。它将继续以这种方式发展。所以,现在它更专注于构建自己的主题模型类型的包。
还有一些事情即将出现,但到目前为止,过去几年已经有了一个发展轨迹。
Seth: 是的。我在行业里工作了几年,并做了主题建模。我可以说我很喜欢使用 BERTopic。这非常棒——只需这种抽象级别,你不必了解每一个细节,就可以获得如此出色的结果。
另外,正如你提到的模块化,你可以插拔不同的算法以获得不同的输出。所以,你之前提到了评估主题模型的问题。你能讨论一下为什么评估主题模型如此困难吗?
Maarten: 一般来说,你会有,比如说 1 万份文档,这些文档是某个人或一组人的推文。BERTopic 或任何主题建模技术在无监督的情况下(没有真实答案)从这些消息或文档中提取主题。
但谁来判断这些主题是否准确?那么准确性到底意味着什么?说这些消息中有一百个主题是否准确,还是说有十个主题更准确?这些主题的描述由一定数量的词表示,这些描述是否比其他算法更准确?
但谁来判断这些主题是否准确?那么准确性到底意味着什么?说这些消息中有一百个主题是否准确,还是说有十个主题更准确?这些主题的描述由一定数量的词表示,这些描述是否比其他算法更准确?
这也取决于你所处理的用例。有时候我们需要更抽象的主题,因为我们在寻找全球趋势,有时候我们在寻找非常具体的主题。在医学领域,我们经常寻找非常具体类型的疾病、背景或药物等。
所有这些不同类型的事物使得很难说,“这就是事实”。因为在这种情况下,事实往往确实在观察者的眼中。它是你的使用案例。你有一个特定的目标——你想用它做些什么。你想用它实现的目标会改变评估指标。
如果我们谈论一些技术细节,它可以是准确性。如果你有标签,它可以是一致性。也就是说,一个主题有多一致,但主题的一致性在于我认为一致和你认为一致之间的差异。因为 BERTopic 是一个聚类算法。我们可以说,好吧,我们将进行聚类,或者我们将评估聚类。
我们可以评估它在未见文档上的预测。但那将是一个有监督的任务。找出某事是否准确有很多不同的方法,因为这里的准确性定义如此困难,这就是为什么它是一种如此主观的建模方式。
Seth: 是的。我认为困难在于没有必然的地面真相,对吧?你不知道某个类别或某个集群应该属于什么。因此,很难说这个模型的准确性如何。
Maarten: 没错。地面真相可以被创建,但每次都应该根据使用案例从头开始创建,因为我可以为我的特定使用案例创建一个地面真相,但另一个使用案例的地面真相将完全不同,有时还需要一个完全不同的评估指标。
Seth: 你见过的一些最独特或最有趣的 BERTopic 使用案例是什么?
Maarten: 最独特的往往是那些试图做一些 BERTopic 不真的旨在做的事情的案例。很多情感分析的人想用 BERTopic 来做。
Seth: 这很棘手。
Maarten: 这很棘手。如果你采用一种半监督的方法,将其语义特性编码并提前计算,那么可以做到。但我实际上看到的是几个趋势分析。但变得越来越流行的是,最初我总认为主题建模在生产中不常用,对吧?
这是一种探索性的数据查看方式,但我看到越来越多的案例专注于尝试动态识别集群。例如,如果你有一个票务系统,并且你想查看是否每天都有一些问题出现,那么你可以使用在线版本的主题建模来查看新问题是否在几天内出现。
所以你可以迅速解决你遇到的问题。这是我大约半年前开发的功能,因为我看到越来越多的用例出现,我想,好吧,但如果我不仅要使用看不见的数据,还要使用主题不同于最初训练的数据呢?
然后,对我来说,这确实是一个我以前未曾见过的非常有趣的用例。
塞斯: 用于随时间发现新聚类的技术有哪些?
马滕: 所以,有一个库,我认为它叫做 River,它主要关注在线聚类或一般机器学习方法的那部分,但它也允许发现新信息。所以,scikit-learn 通常不关注发现新主题或新信息。但持续训练模型是一个有效的用例。例如,River 更加关注真正的在线方面,更加关注发现我们之前未曾发现的东西。
对吧?
塞斯: 开发 BERTopic 时,你遇到的最具挑战性的事情是什么?
马滕: 所以依赖性问题比我之前想的要复杂得多。我有几个包,有些比其他的更容易,但与 numpy 的 API 问题很多——这些问题真的很棘手,随着时间的推移,这仍然是一个问题,因为依赖项不断变化。从某种意义上说,它们变化是合理的,我也不时更改 API,所以事情会断裂,但有时它们真的很难修复和查找,因为有这么多依赖项的组合,而这很难考虑到所有这些,嗯,这就是一个问题。
第二个问题主要是 API 开发,而不一定是算法。我觉得它们很有趣,但仍然很困难,不过这实际上不是问题。但 API 本身是人们每天都在使用的,如果你以重大方式更改它,那么每个人都得进行更改。现在在生产中有效的东西会停止工作,因此在考虑这些事情时,确实很棘手,因为我可能想要在未来进行更改,这当然是不可能的,因为我现在考虑将 plotly 更换为 bokeh,这将非常麻烦——这将会很棘手。
但如果我想做更多的事情,那就会有很多问题——现在你传入的是文档,但总有一天我想传入图像、声音等等,对吧?这需要重大 API 更改,而这种更改需要以不会让用户烦恼的方式进行,因为他们经常使用它,如果你要大幅更改,它显然不是最佳的用户体验,所以这些问题实际上是最困难的。
常发生的情况是,当我想要开发功能或者实现新功能时,我必须引入新的依赖,这真的是我认为你应该尽可能避免的。当开发一个包时,BERTopic 已经有相当多的依赖,而在一个完美的世界中我本来只希望有三个依赖,但这当然不会发生,一旦你添加另一个依赖,所有东西之间的互动可能会搞砸一切,因为这实际上为所有这些依赖提供了另一个复杂性层。
…当我想要开发功能或者实现新功能时,我必须引入新的依赖,这真的是我认为你应该尽可能避免的……当你添加另一个依赖时,所有东西之间的互动可能会搞砸一切,因为这实际上为所有这些依赖提供了另一个复杂性层。
所以,当你进行主题建模时,自然要添加一个包,例如 NetworkX,以显示主题之间的某种网络关系,这将会非常有趣并且绝对可行,但这需要添加另一个可能与我已经拥有的依赖不兼容的依赖。我为 BERTopic 做了一些协方差主题建模,我非常想实现,但这也要求我添加 stats models 依赖,这是一个很棒的包,但它是另一个依赖,因此我可以很容易地列出 20 个额外的依赖,然后 BERTopic 可能会停止工作,是的,所以不幸的是,这在于你想添加的那些包之间的平衡。
Seth: 权衡这些取舍,尽量保持轻量,同时平衡额外的功能等。
Maarten: 对,确切地说。
Seth: 是的。你还提到了一种称为 API 心理学的东西。你想谈谈这个吗?
数据科学家的视角
towardsdatascience.com
Maarten: 对。所以,这就是我所经历的,对吧?我因为心理学背景极其偏见,所以我称呼所有事物为心理学,只是因为我想这样做。
Seth: 你可以做到这一点。
Maarten: 很好。那么我会继续这样做。但问题是,就像我之前提到的,当你进行 API 设计时,这关乎人们如何与你的包、你的模型、你的软件进行互动。
因此,剧烈的变化会伤害用户体验。因此,从流行的 API 设计中学习很多东西,比如 numpy 和 scikit-learn 以及 pandas,人们在某个时刻已经部分由于这些包开发了一种编码方式。如果你试图遵循这些包的哲学和设计,即使是在设计新的东西时,你也确实在关注体验的心理学。
因为如果你按照 scikit-learn 的方式做一切,那真的很好,因为大家都知道 scikit-learn。这样在接触这种模型时会感觉直观。同样的原则也适用于创建包。它不一定需要遵循 scikit-learn 的规范。
每当你创建一个新参数时,你会给这个参数起什么名字?什么是直观的?即使是参数的位置也可能非常重要,因为有时人们不想创建关键字参数,他们只是直接丢入变量,对吧?所以,在设计包时,位置确实很重要。
在开发一个包时,你需要考虑很多这样的因素,因为,嗯,人们会使用它,如果人们要使用它,那么肯定有某种用户体验或心理方面的因素,在这些方面你可以真正地做出改变,从而影响你所创建的东西的适应性。
塞斯: 对于 BERTopic 你最兴奋的未来方向是什么?
马滕: 所以我之前简要提到过这个问题。
我在考虑更改 plotly。我这么做不是因为我讨厌 plotly。Plotly 非常棒。还有 bokeh 或 altair 等等。只是因为我想在某个时刻添加图像,这已经通过一个名为 concept 的不同包实现,该包尝试将文档和图像集成到一个主题建模方法中。
我也想最终在 BERTopic 中实现这个功能。但为了做到这一点,当你要可视化你的数据点时,你通常也希望可视化图像,这直接是不可能的。在 Plotly 中,你需要在其上使用 dash,而这并不是我在 API 设计心理学方面所喜欢的。
这会大大改变你与可视化的互动方式。我在考虑将其更改为 bokeh,因为 bokeh 允许你对图像做更多的操作。至于有趣的未来方向,那是因为我们有更多的多模态嵌入类型模型。
我们可以在相同的维度空间中处理文档、图像、声音。因此,如果你能在相同的维度空间中做到这一点,那么话题建模也可以朝这个方向发展。但除了嵌入之外,还有更多需要考虑的地方。还要考虑话题表示提取以及如何将图像与文档、甚至是声音在某些时候连接起来。
这是我需要深入探讨的内容。我这里有一些代码,但我认为还有很多可以从中获得的东西。
从机器学习中学习
塞斯: 你认为在机器学习中还有什么重要的问题没有答案?
马滕: 当然有一些,但最近经常出现的一个问题是,这与 ChatGPT 类型的方法有关。当这样的模型发布时,人们开始把 AI 谈论得像通用人工智能一样,对吧?
真实的智能,真实的意识,它能够通过图灵测试的那些夸张说法……而且我认为我们距离这些还有一点距离。但现在这开始变成一个有点模糊的领域,因为很多人以那种方式讨论它。
虽然定义可能有所不同,但技术上我们仍然认为我们接近得比较快。如果我们确实在接近,我认为也很重要的是承认 ChatGPT 当然不是那样的。但它是什么,以及如何被接受到行业中,什么是和什么不是,对我来说,这是一个我们需要关注的重要问题。
因为虽然 ChatGPT 可以做非常了不起、非常有趣的事情,但它不是事实信息。它不一定必须是这样,这并不总是如此。在开发这些类型的模型时,需要考虑到这一点。我们正在看这些拥有数十亿参数的巨大语言模型,它们可以做非常有趣、非常了不起的事情。
我真的很想知道这将如何融入到 AGI(通用人工智能)的讨论中。但我确实认为,按照现在的趋势,未来一年将会非常剧烈。
塞斯: 是的。我认为自然语言处理将会有另一个非常激动人心的一年,你知道的,ChatGPT 和 GPT-4 将在几个月内推出。
当然。这显然会很有趣。但确实存在真正的智能与创建拥有数十亿或数万亿参数的大型语言模型之间的某种差距。这并不一定会转化为理解——这是我的看法。
这引出了我的下一个问题,自从你开始从事这个行业以来,领域发生了怎样的变化?是大型语言模型的出现,还是你看到的其他变化?
Maarten: 所以,当我开始做大型语言模型,或者更确切地说,当我开始做机器学习时,Word2Vec 类型的模型真的开始出现,并展示了它们的可能性。
然后慢慢过渡到变压器类型的模型。从那里,当然我们现在拥有的就是目前的情况。我会说,大型语言模型真正改变了整个领域,特别是当你考虑到现在的Hugging Face时。考虑到他们托管的大量模型和它们可以做的事情。
我想说的是,我们看到一个巨大的趋势正在向那些没人能在自己机器上运行的大型语言模型发展。并且,针对这一领域的研究非常有限,更多的是集中在蒸馏和让它对你我更可及上。
我这里有一台带有不错 GPU 的笔记本电脑,但仅此而已。我希望在不使用 API 的情况下运行这些模型。这并不总是可能的,因为速度不够快。而且,仍有很多研究包朝这个方向发展。例如,如果你使用sentence-transformers,它们将其蒸馏,并快速生成对所训练内容的非常准确的表示。
因此,使用这些模型的推理速度非常快,我希望看到更多这样的模型,而不是数十亿的参数。虽然这很不错,不要误解我的意思。我们需要这些模型来最终得到一个蒸馏或更小的版本。但如果对较小的模型的关注程度能与对大型模型的关注一样,那将是很好的,因为我可以使用较小的模型。
我的意思是,较大的模型对我来说实际上是不可接触的。
Seth: 你是否看到生成模型如何影响主题建模和 BERTopic 之类的东西?你是否考虑过将其融入其中?
Maarten: 是的。这又回到了依赖性等问题。
但我认为 Cohere 所做的是,他们创建了一个主题模型,我想它叫做。这基本上是一个在 BERTopic 之上的 GPT。因此,BERTopic 生成一些主题和这些主题的描述,通过单词数来描述。然后他们将这些单词输入到 GPT 模型中,并要求从中创建一个主题——仅仅是自然描述。
而且它做得很好。这样的方法的问题在于,从生产环境的角度来看,很难在你的机器上运行,对吧?你需要有一个 API,如果你使用 Cohere,那是绝对没问题的,这是一个很棒的服务。但当你在没有互联网连接的情况下本地运行时,拥有一个本地 GPT 模型几乎是不可能的。
当这些模型变得相对较小并且可以在你的机器上运行时——那时我将开始将这些技术集成到 BERTopic 中。但如果你想在几百个主题上使用 GPT,并将其分配到,比如说,一百个类别中,那就变得非常困难,并且计算时间非常长。
这就是为什么我现在不想把它集成到 BERTopic 中的原因,对吧?这就是为什么 Cohere 所做的事情很了不起。如果你觉得需要的话,这是一种在 BERTopic 基础上的附加功能。如果你有那种服务或对其感兴趣,我也会强烈建议使用。
但在 BERTopic 中集成这些技术还为时尚早,直到性能达到现在 BERTopic 的状态。
Seth: 对。这很有意义。是的,我尝试将一些 BERTopic 的输出放入一些开源生成模型中,结果让我感到惊喜。
Maarten: 啊,这太棒了。
Seth: 我认为这绝对是一个有趣的未来研究领域。你怎么看待这些新生成模型所制造的炒作?你觉得炒作与现实之间有很大的差距吗?
Maarten: 是也不是。我知道这是个令人恼火的答案,但不,我不认为有差距,因为很多这些模型,拿 ChatGPT 或者稳定扩散来说,可能会更有趣。基本上,我们从文本中创建图像,这真是太惊人了。

使用稳定扩散生成的图像,提示词为:“不可思议的图像,展示某人在学习如何做机器学习”
这真的很令人惊叹。实际上,有些人是很棒的提示者,他们知道需要使用哪些词汇来创造出色的图像。如果你能非常聪明地运用这些,那么在一些用例中,这可能会很有趣,比如在制作原型时,或者在为你的游戏创作艺术品时。
仍然存在一个差距,也就是说,它并没有完全按照你的需求进行操作,对吧?你仍然需要寻找很多方法来确保它给出你需要的输出。这是一个非常大的模型,运行速度可能没有你期望的那么快。关于艺术家被网络上直接复制的问题也很多。
在过去几个月里,我看到很多人说,好吧,有人训练了我的特定艺术作品,现在它生成了看起来像是我的作品。好吧,这当然不是我们应该追求的方向。所以我认为有很多炒作是有意义的,但很多炒作也只是因为这些工具很有趣。
是否会在很多组织中实际使用这一点,我对此表示高度怀疑。但在某些特定的应用场景中,我确实认为这会产生巨大影响,但这些场景只是有限的几个。对于很多人来说,这只是一些有趣的东西,很容易传播开来。
Seth: 对吧?是的。ChatGPT 的影响确实令人难以置信,短短一周内就获得了超过一百万的用户。与它互动的感觉确实很特别。我认为他们减缓了输出,使其感觉像是在与人交谈。
Maarten: 也许这与我不完全确定有关,只是为了确保人们仍然在使用它,而不会过多查询它。
对。它减慢了你实际查询的时间,但即使在荷兰,也有很多人用荷兰语使用它,并且效果相对较好,这很有趣。我想知道什么时候会看到它出现在实际的研究和合同中。它真的很有传播性。
Seth: 是的。你有没有用它来帮忙,哪怕是日常的小事?
Maarten: 实际上没多少,因为它仍然需要我花很多时间去修正,而我喜欢按照自己的方式来做。
我在写文章时使用它来生成十个标题,例如,给我一些灵感,并从中提取,然后我会在十次中改变九次。但我确实认为这是获得你想创建的东西的初步想法的一个了不起的方式。
如果你想写一个关于联邦学习的介绍,只需输入它。它可以返回一些内容,你可以按自己的方式修改,进行调整。看到这一点真的很有趣。也许在某个时候,我们不必自己打出所有这些内容。我们只需请别人或让 ChatGPT 来做。
然后,它会迭代地要求你以符合你想实现的方式进行修改。所以我们写作和互动的方式可能会完全改变,如果像这样的东西变得更开放源代码,或最终更易于公众访问,而不仅仅是 API 的话。
这绝对会很有趣,对吧?
Seth: 是的。我认为创造合适的提示有一定的创造力。这也是未来的发展方向。人与机器的这种互动可以产生最佳的输出,也许得到一个初步的想法,然后你可以基于此进行工作或修改。
Maarten: 这确实节省了很多时间,对吧?我的意思是,你知道你想写什么。在许多情况下,你写论文时需要写关于联邦学习的介绍,或者可能是稳定扩散,或者其他什么内容。只需请别人写一个初稿,然后你可以修改它。
你已经知道你想写什么,如果你是该领域的专家,那么你需要做的只是检查是否一切正确且完成得当。但这节省了很多时间。当然,这其中也有风险,当人们使用它并且只是照搬照抄时,可能会出问题。
但最终我们也会找到处理这个问题的方法。
Seth: 是的。我认为这有一个有趣的方面,可能有些人会忘记。大型语言模型如此强大,是因为所有这些信息都是由人类创造的,这为这一切奠定了基础。
所以回到机器学习领域——是否有一些人激励了你——你受到他们的很大影响?
Maarten: 所以我跟随一些人,但我不会特别说有这些人对我影响巨大。原因是,从心理学背景来看,我倾向于从尽可能多的角度看待事物,这同样适用于那些影响我的人。
所以我尝试摘取人们所说的片段,并将其用于我认为有趣的内容。因为每个人都有自己的优点和缺点,我专注于这些优点,但我挑选 20、30 个人,他们在特定领域表现出色,然后试图将他们结合起来。
所以我开始学习基于 transformer 的模型,使用了Jay Alammar 的可视化。
Seth: 非常了不起,经典。
Maarten: 他在这方面做了很棒的工作。但这是我从他那里获得的一个方面,然后我从其他完全不同的人那里获得其他东西并使用它。因此,这更多是人员的组合,而不是单独有一个人,我觉得我喜欢那个人做的一切,这种情况很少发生,因为你知道,我们是人,我们并不是在所有方面都很出色。所以,你知道,这个人在这方面更好,那个人在可视化方面更好,那个更好的编码员。
现在,我是Sentence-Transformers库的超级粉丝。例如,是的。所以有一个采访即将进行,Nils Reimers 的工作非常出色,对吧?是的。但如果我需要可视化,我会去找 Jay [Alammar]。你知道,这是一种人员的组合,我认为你最终会自己找到他们。
因为我可以说,这里有 20 个人的名单,我觉得他们很有趣,但我觉得他们有趣是因为我是谁,我以某种方式看待事物,所以也试图用支持我观点的观点来支撑我的思维。我在这方面还是人类。因此,即使我提供这些人,也可能与那些看待事物方式不同的人不合适。
是的。所以抱歉,这算是一种非回答,
塞思: 不,这是一个很好的见解。我喜欢这一点。有没有其他领域的人激励你?
马滕: 嗯,这是个好问题。我最近为 BERTopic 做了那个动画。
塞思: 喜欢它。
马滕: 是的,谢谢。我没有刻意去争取这些赞美,但还是很受用。
塞思: 我仍然喜欢它。
马滕: 我用3Blue1Brown的软件做了那个。我真的很喜欢这个。这个 YouTube 频道提供了数学相关的内容,呈现方式很棒,还有一个开源的主要包,任何人都可以使用。
我非常喜欢关注那些类型的人,因为他们以如此愉快的方式提供信息,让我感觉像是在看 Netflix。如果你能做到这一点。例如,Kurzgesagt,我最近在一篇文章中提到过,他们的频道也提供了关于不同主题的大量信息。
如果你能以一种像看电影一样的方式呈现复杂的内容,那真的是美妙的。是的,我真的对此印象深刻。所以,也许这些类型的人会影响我。
塞思: 是的。我在数据科学和机器学习中发现,确实存在硬技能,对吧?
然后还有软技能,包括能够解释你正在做的工作,融入心理学的部分,理解人们如何学习、如何思考事物、如何感知事物,并且理解什么是最好的表达方式?什么是最好的可视化方式?
这非常重要,因为这样你就可以让其他人参与进来,对吧?然后你可以得到他们的反馈,这样事情才能真正开始起飞并达到新的高度。
马滕: 是的。我完全同意。
这当然是我非常偏见的观点,但在这个领域,我真的认为有时候一种确实不能存在于另一种之中。当然,我可以创建最复杂的算法来执行某种特定任务,但如果我不能有效地与利益相关者沟通,那真正的意义何在?
我在开源软件中看到过很多包做得非常出色,但使用起来非常困难,所以没有人使用它们,这真的很遗憾,因为没有考虑到心理学的方面。
但这也可以反过来运作,对吧?如果我想向一个人解释某件事,特别是一个困难的算法,我真的需要非常、非常了解那个算法,才能够解释清楚。
所以我知道怎么以一种方式解释它,怎么以一种方式沟通,怎么以一种方式表达以吸引受众,但这并不意味着我理解内容。所以这两方面都适用。
当人们说,你需要有软技能时,总是很好的,但为了拥有软技能,你确实需要那些硬技能,因为如果你不知道自己在说什么,你可以稍微编造一下,但人们会发现的。
Seth: 你确实需要用硬技能来支持这一点。
Maarten: 是的。在某些情况下,我真的认为它们同样重要。并非所有情况下都如此,我的意思是这并非黑白分明,但如果我们从商业角度来看,你只需要确保你能做好你的工作。
但剩下的部分确实是销售,说服别人,好的,这值得投资。为了做到这一点,你需要解释清楚。你需要吸引人们。作为数据科学家,这并不总是必要的,有时候你有经理为你做这些事情——把这些事情抽象化,因为有很多技术数据科学家不一定想专注于大量的会议和演示,这也是公平的。但仍然需要有人来做好销售。
Seth: 学会如何与各种类型的利益相关者打交道,从非技术人员到 CEO,再到产品负责人,甚至其他数据科学家,这一点非常重要,了解你的受众是谁,以及传达你所学到的最佳方式。
进入从机器学习中学习的领域。
Maarten: 顺便说一句,名字很棒。这太好了。
Seth: 是的。谢谢。我很感激。你收到的哪些建议在你的机器学习旅程中帮助最大?
Maarten: 是的,对我来说,我不一定知道这个建议是从谁那里得到的,但真正理解问题本身就是一种解决方案。
我会一遍又一遍地说这个。如果你真的不了解你试图解决的问题,那么你不可能得到一个好的解决方案。这看起来很简单,但实际上并非如此。这是你可以做的最困难的事情之一。真正能尝试或真正理解他们所处理的问题的人少之又少。
我会一遍又一遍地说这个。如果你真的不了解你试图解决的问题,那么你不可能得到一个好的解决方案。这看起来很简单,但实际上并非如此。这是你可以做的最困难的事情之一。真正能尝试或真正理解他们所处理的问题的人少之又少。
比如说,我现在在从事癌症研究,我有很多了不起的同事,其中有一个人在编码之前真的会深入研究问题,对吧?在真正编写代码之前,他会花几周的时间来搞清楚究竟发生了什么。他仍然编写代码,并且还在研究潜在的解决方案,但他花费几周时间只是为了搞清楚到底发生了什么。
数据的表现形式是什么,它被使用的背景是什么,困难是什么,法律上的关联,例如——这使得编码变得更加容易,因为你已经完成了所有的基础工作,对吧?然后你准确知道你想要编程的内容,你想要预测什么,一旦你想要解决什么。
但这非常困难,因为这不仅仅是一个技术问题。它通常还涉及到,比如说,数据是如何生成的。在大多数情况下,虽然不是总是如此,但数据总是有某种心理因素,或者数据本身存在一些问题,这总是存在的。
总是有某种“脏数据”存在。
塞斯: 奇怪的伪影,对吧?就像发生了一些事情。
马尔滕: 确切地说,确切地说。这确实取决于过程。所以你需要理解数据生成的过程。但如果你要设计一个解决方案,你是为谁设计的呢?
那么他们为什么会想要那个解决方案呢?因为经常发生的情况是,人们请求解决方案时,实际上可能意味着完全不同的事情。这不是他们的错,因为他们试图传达,但由于你是我们的技术人员,你知道可能性是什么,你可能会说,好吧,但你可能需要更多地关注这个方向和那个方向。
如果你不做这些,可能最终会推出一个模型,人们会说,“啊,这不是我们真正想要的。”然后你也常常会看到,创建解决方案的人说:“哦,但你应该早告诉我。”这不是我们应该沟通的方式,对吧?
这是一个双向的过程。你不能只是告诉某人,你需要告诉我你具体需要什么,就好像你是数据科学家一样。事情不是这样运作的。我们需要相互帮助。我认为了解过程、理解问题是数据科学中最值得投入时间的方式。
塞斯: 100%。有时只需要一支笔和纸,对吧。在你进入代码之前。
马尔滕: 是的,绝对如此。
塞斯: 另外,我还想补充一点,我会说,重新审视问题,对吧?是的。也许在你开始做一些工作后,你会开始意识到,哦,你知道,所有的要求还没有完全解决。
你知道,。
马滕: 不,那很完美。那很棒,因为,你知道,它从问题开始,正如你提到的,频繁回顾以查看,好的,我仍然保持在我最初认为的问题的轨道上,这可能会在你开始设计解决方案时发生变化。
塞斯: 对。这种数据科学中的迭代过程的棘手之处在于,一旦你选择了某种解决方案,它开始让你的视野变得有些狭窄,对吗?
马滕: 当然。
塞斯: 所以这就是为什么你总是必须回到原点——理解并弄清楚从商业角度来看这是什么,从数据角度来看这是什么?对,就是这样。
马滕: 是的。如果随着时间的推移变得稍微窄一些也没关系。当然。你现在从广泛的角度出发,尝试探索所有存在的东西,最终会找到一个解决方案。所以它必须变得更加具体。但正如你所提到的,不要太快,它应该花费一些时间,就像强化学习一样。
探索与利用之间应该有一个平衡,对吧?你知道,我们会设计一个解决方案,但我们仍然会留出一些空间来探索一些不同的东西,看看这些是否仍然有效,或者是否仍然相关,等等。
塞斯: 对。那种前期的时间可以节省你很多时间。
马滕: 需要花费大量时间。是的。
塞斯: 对于刚刚开始从事机器学习或自然语言处理的人,你有什么建议?
马滕: 所以我真的会尽可能多地开始做项目,没关系,哪怕只是一个泰坦尼克号数据集或房价回归。
我认为这是一个方面。确实,有很多人已经做过这些,但亲自动手在数据科学中极其重要,因为在某个阶段你会对某些事情产生直觉。你看到一个问题,你不完全知道原因,但你觉得,好的,这应该是解决方案。
这发生是因为,我的意思是,你也在某种程度上是一个预测模型。你知道那种直觉——那是因为你之前在前一个项目中学到了一些东西,你发现有些方法效果很好,或者有些东西与你现在正在做的非常相似。
这发生是因为,我的意思是,你也在某种程度上是一个预测模型。你知道那种直觉——那是因为你之前在前一个项目中学到了一些东西,你发现有些方法效果很好,或者有些东西与你现在正在做的非常相似。
通过做所有这些项目,亲自动手分析数据,收集自己的数据,清理自己的数据,应用这些算法并深入研究,这帮助你真正理解这个领域,培养对事物何时有效和何时无效的直觉。
一个著名的例子是,当大多数人刚开始时说的,如果你看到一个模型有 99%的准确率。没错。肯定会有什么问题。
Seth: 哦,不
Maarten: 这种情况不会发生。一般来说,不会发生。所以这是你逐渐培养出的直觉。当然,这是一个著名的例子,但类似的例子还有很多。许多这样的东西最终被实现到 BERTopic 中,因为我觉得,哦,这里发生了些奇怪的事情。
我不完全确定,但我觉得我应该看看那里和那里。是的。现在,我之所以这样做的唯一原因是因为我在许多这些项目中积累了大量经验,即使这是我之前没有做过的事情,我也会立即知道去哪里找。我认为如果你刚开始,就做好工作吧,
Seth: 对了。现实世界或真实项目的经验可以让你大致知道哪里可能会有陷阱,哪里可能会有坑,这些经验可以帮助你更好地应对。
Maarten: 是的,绝对的。如果你刚开始,不要怕别人说不要使用 Titanic 数据集,也不要使用房价数据集。
每个人都要从某个地方开始,从完全干净的数据开始是可以的。真的可以。然后你可以慢慢过渡到不那么干净的数据,收集自己的数据等等。
Seth: 对了。那么,在类似的方向上,你会给自己什么建议,当你刚开始的时候?
Maarten: 哦,这是一个很棒的问题。哦,我真的需要好好考虑一下。
哦,这很棘手,因为我当然是根据自己的经验来讲的。也许可以稍微早一点进入这个领域。我所说的意思是我进行了转型,因此我相对较晚进入这个领域。我这一代有很多人已经有了四五年的经验,虽然攻读硕士学位很好,确实推荐。
经验能让你更进一步,我认为,啊,也许不只是稍微进一步,但它是同样有价值的。如果你能两者兼顾,那当然是很棒的。
我认为我在数据科学的多年中,从未真正拥有一个导师,不是一个我可以随时仰视并询问我所处理的事情的人。我不得不自己发现一切,这有其优点,但主要缺点是大大拖慢了某些学习过程。
所以我会给自己建议的是,也许在那段时间里,稍微少关注学术,更多地专注于获取经验。
Seth: 这仍然是个好建议,我喜欢顾问或导师的想法。
我认为这是人们往往会回避的事情,但我认为他们不应该这样。这是一个重要的事情,可以在你的旅程中帮助你。有时候,自己做所有的事情是很好的,对吧?你会面对学习的痛苦,当你完成时会获得强烈的成就感。
马滕: 正是这样。
赛斯: 但如果你有一个人,哪怕只是一个可以倾诉的对象,那肯定会加快进程。
马滕: 正是这样。一位好的导师有时会说,自己去解决吧。因为一个好的导师会知道你的能力,知道你有多坚持,以及你能做什么和不能做什么。
一位好的导师有时会说,自己去解决吧。因为一个好的导师会知道你的能力,知道你有多坚持,以及你能做什么和不能做什么。
所以,他或她会在某个时候知道,好吧,如果你再多花几个小时,我相信你会解决的,对吧?如果到那时还不行,好吧,我会把你推向正确的方向。对吧?但这就是你得到的全部。对,一个推动。所以,我认为这对我来说会有很大的区别。
一个导师。
赛斯: 这就是一位好老师或好导师可以提供的东西。那么,从更哲学或更广阔的角度来看,机器学习的职业生涯教会了你什么关于生活的东西?
马滕: 你让我思考了困难的问题。不,实际上,对我来说,机器学习在我生命中的出现与慢性疼痛大致是在同一时刻。
处理这些问题相当困难。因此,找到一个健康的生活和我当时最喜欢的事情——机器学习之间的平衡对我来说很困难,因为慢性疼痛使得我每天打字 10 小时变得困难。
我真的必须平衡我工作的方式和时间。这很棘手,对吧?所以当你换职业时,这已经很困难了。但当你最终找到你认为“好,这就是我想做的”时,同时你还会有慢性疼痛。
这并不是让你感到特别开心的事情。对我来说,这一直是非常困难的,但事后看来,这却是一个非常宝贵的学习经历。真正了解我所坚持的是什么,什么对我重要,我想要关注的是什么,以便在某些时候退后一步。
因为这也是机器学习的领域——它发展迅速,极其迅速,没有人能够跟上所有发生的事情。但错失恐惧变得巨大的时候。随着稳定扩散和聊天 GPT 等这些东西几乎是每天发布,这种感觉时常会出现。
但我真的学会了退一步看全局。你不需要知道一切。有些事情比其他事情更重要或不重要,你可以跟随,但最终你会赶上发生的事情。
但我真的学会了退一步看全局。你不需要知道一切。有些事情比其他事情更重要或不重要,你可以跟随,但最终你会赶上发生的事情。
我是说,ChatGPT 表现得非常好,这很棒,但如果我不把这个模型变成某种业务,我可能从一开始就不会知道这一点。那时候我不需要知道,所以我可以给自己更多空间来寻找平衡。虽然有了 BERTopic 和写作以及 KeyBERT,仍然很困难。
Seth: 是的。我不确定你是如何找到时间来维护如此出色的库并保持日常工作的。
Maarten: 我不知道。我尽力而为。我不确定我的妻子是否总是那么开心,但我会确保找到平衡。但你说得对,日常工作与开源和写作结合在一起确实很困难。
所以也许将来我会喜欢将这些结合起来。但是你知道,这取决于情况。毕竟这是开源的,对吧?也许如果有一天有一个组织说,好吧,我们会全力支持这个项目,那么这可能会对我产生兴趣。但目前,我有一个很棒的工作。
我获得了很多自由,可以围绕慢性疼痛进行工作,这很棒,因为并不是很多组织提供这样的条件。同时,我仍然可以从事我认为重要的工作。
Seth: 好吧,你的工作受到这位机器学习科学家的高度赞赏,我相信还有许多其他人也如此。
所以总结一下,如果有人对联系你或了解更多关于你感兴趣的话,有没有地方可以去?
Maarten: 你可以去 Twitter 或 LinkedIn。这些是我主要的联系渠道。我还有一个网站。
它是 maartengrootendorst.com,只有荷兰人能理解。只是我的名字,但这是发音的方式。翻译成英语并不是最简单的事。但没有 Twitter 和 LinkedIn,主要是 LinkedIn。你可以在那儿很好地联系到我。是的。
Seth: 太棒了。Maarten,与你交谈非常愉快。
谢谢。非常感谢。
Maarten: 谢谢你邀请我。采访很棒。我喜欢这些问题。真的,其中大部分非常技术性,对吧?如果你有像 KeyBERT 或 BERTopic 这样的东西,这就是你主要做的事情。但更关注一点哲学和心理学的方面,我非常感激。
Seth: 绝对的。非常感谢。
Maarten: 谢谢。
立即收听
完整访谈的视频可以在这里观看:
Maarten Grootendorst: BERTopic, 数据科学, 心理学 | 机器学习中的学习 第 1 集
该播客现在可以在所有 播客平台 上收听:
现在在所有播客平台上可用
资源
了解更多关于 Maarten Grootendorst 和他的工作:
-
BERTopic: BERTopic 是一种主题建模技术,利用 🤗 transformers 和 c-TF-IDF 创建密集的集群,从而生成易于解释的主题,同时保留主题描述中的重要词汇。
-
KeyBERT: KeyBERT 是一种简约易用的关键词提取技术,利用 BERT 嵌入生成与文档最相似的关键词和短语。
学习更多关于机器学习的资源:
视频内容
-
00:00 机器学习中的学习介绍
-
00:20 Maarten Grootendorst 介绍
-
00:54 职业背景
-
02:21 心理学背景
-
04:07 是什么吸引了你学习机器学习?
-
05:18 你需要学习的第一个技能是什么?
-
07:12 什么是 BERTopic?主题建模的力量是什么?
-
09:38 初始包的目标是什么?它是如何变化的?
-
12:41 BERTopic 的好处
-
13:18 为什么评估主题模型如此困难?
-
16:06 分配真实值的挑战
-
16:44 BERTopic 的一些独特用例是什么?
-
18:38 随时间推移发现新集群的技术有哪些?
-
19:29 在开发 BERTopic 时,你面临的最具挑战性的事情之一是什么?
-
24:13 API 的心理学是什么?
-
29:04 在机器学习中你认为仍未回答的一个重要问题是什么?
-
31:22 自然语言处理的激动人心的一年
-
32:00 自你开始在这个行业工作以来,这个领域发生了什么变化?
-
34:22 生成模型如何影响主题建模和 BERTopic?
-
37:00 这些生成模型的炒作和现实之间是否存在差距?
-
39:15 ChatGPT
-
41:30 人类与机器的互动
-
43:18 领域中是否有激励你的人物?
-
47:10 与他人分享你的数据科学工作
-
50:48 从机器学习中学习
-
51:00 你收到的对你机器学习之旅有帮助的一条建议是什么?
-
54:30 重新审视问题
-
56:30 你会给刚进入这个领域的人什么建议?
-
59:36 你会给刚开始时的自己什么建议?
-
1:02:35 从机器学习的职业生涯中你学到了什么关于生活的东西?
-
1:06:30 总结
-
1:06:48 如何联系马尔滕·格鲁滕多斯特
-
1:07:20 结论

































浙公网安备 33010602011771号