TowardsDataScience-2023-博客中文翻译-五十一-
TowardsDataScience 2023 博客中文翻译(五十一)
原文:TowardsDataScience
协议:CC BY-NC-SA 4.0
XGBoost:深度学习如何取代梯度提升和决策树 — 第一部分
原文:
towardsdatascience.com/xgboost-how-deep-learning-can-replace-gradient-boosting-and-decision-trees-291dc9365656
Saupin Guillaume
·发表于 Towards Data Science ·阅读时长 7 分钟·2023 年 5 月 29 日
--
图片由 Preethi Viswanathan 提供,来源于 Unsplash
在本文中,你将了解如何使用可微编程方法重写决策树,这种方法由 NODE 论文提出,使得该模型可以以类似于神经网络的方式进行重新表述。
推导这种表述是一个很好的练习,因为它提出了在构建和训练自定义神经网络时常见的许多问题:
-
如何避免梯度消失问题?
-
什么是良好的初始权重选择?
-
为什么使用批量归一化?
在回答这些问题之前,让我们看看如何将决策树重新表述为神经网络的数学框架:可微编程。
更新 — 第二篇文章已上线:
## XGBoost:深度学习如何取代梯度提升和决策树 — 第二部分:训练
一个没有 if 的世界
towardsdatascience.com
XGBoost 非常高效,但…
如果你读过我之前关于梯度提升和决策树的文章,你会知道梯度提升结合决策树集成在分类或回归任务中表现出色,尤其是在涉及表格数据时。
[## 实用的梯度提升:深入探讨 Python 中的梯度提升
本书关于梯度提升方法,旨在为希望深入了解的学生、学者、工程师和数据科学家提供参考…
amzn.to](https://amzn.to/3IKyqTF?source=post_page-----291dc9365656--------------------------------)
尽管 XGBoost、LightGBM 或 CatBoost 在增强决策树集成构建方面做出了努力,但训练这样的模型仍然依赖于蛮力方法。它涉及系统地评估拆分值和特征相关的增益。
请参考我之前关于此主题的文章:
## 在不到 200 行 Python 代码中实现 XGBoost 库
XGBoost 的解释以及梯度提升方法和 HP 调优,通过构建你自己的梯度提升库来实现…
towardsdatascience.com
现代机器学习模型及程序的训练方法涉及使用可微分编程。如果你对这一概念不熟悉,我已经撰写了一篇介绍性文章:
## 从零开始的可微分编程
去年我有幸参加了 Yann Lecun 在 Facebook 人工智能部门的讲座…
towardsdatascience.com
本质上,思想是识别模型/程序中需要优化的参数,并使用基于梯度的方法来找到最优参数。然而,为了使其工作,你的模型必须对感兴趣的参数是可微分的。
尽管决策树集成方法采用了基于梯度的方法进行训练,但它们的构建过程却不可微分。
构建决策树不是一个可微分的过程
决策树是一种简单而强大的数据结构,具有广泛的应用。当为回归或分类任务训练这样的结构时,我们的目标是识别三个主要参数:
-
用于每个决策节点的特征,以最小化错误,这涉及特征选择。
-
用于在每个节点拆分输入数据的阈值。具有所选特征值大于阈值的数据将分配到右子节点,而其余的数据将分配到左子节点。
-
分配给树叶的值。
前两步是不可微分的。在当前的梯度提升树实现中,第一步需要对特征进行迭代,并且是不光滑的。第二步涉及使用结果数据分区和可能值列表来评估增益。这个过程也是不可微分的。
幸运的是,2019 年,Popov、Morozov 和 Babenko 提出了可微分的决策树公式。
[## 神经盲决策集成用于表格数据的深度学习
如今,深度神经网络(DNNs)已成为广泛范围内机器学习任务的主要工具...
arxiv.org](https://arxiv.org/abs/1909.06312?source=post_page-----291dc9365656--------------------------------)
我强烈推荐阅读他们的论文,因为从源头获取信息总是有益的。不过,为了方便理解,我用 Jax 编写了一些 Python 代码。
论文中讨论的问题包括:
-
我们如何重新表述特征选择,使其平滑且可微分?
-
我们如何重新表述比较,使其平滑且可微分?
特征选择正则化
如前所述,标准梯度提升算法中的特征选择是通过穷举搜索来进行的:我们尝试每一个特征,并保留提供最高增益的特征。
我们需要将选择重新表述为一个函数,该函数返回确保最大增益的特征值,而不是这种迭代过程。
首先,假设特征被收集在一个向量中。我们需要一种方法来从该数组中提取所选特征的值,理想情况下使用向量运算。从我们已经知道要保留的特征开始,我们可以通过简单的点积来实现:
features = np.array([1.1, 3.2, 4.5])
selection_weights = np.array([0, 0, 1])
selected_feature_value = np.dot(selection_weights, features)
print(selected_feature_value)
# Output: 4.5
因此,我们可以通过将所有特征乘以 0(除了需要保留的特征),然后求和所有乘积,来从特征数组中提取特定值。
学习实现最高性能的特征涉及学习向量selection_weights
,并附加约束条件:其元素之和必须等于 1。理想情况下,这 1 应该集中在向量的单一元素上。
对选择向量的范数施加此约束可以通过函数来实现。一个显然的候选者是 softmax 函数,它将权重向量的范数强制为 1。然而,softmax 生成的是光滑分布,无法生成 1 作为最高权重而其他为 0。
以下代码片段演示了这一点:
import numpy as np
from jax.nn import softmax
from entmax_jax import entmax15, sparsemax
weights = np.array([2, 3, 5, 7, 11, 17])
print(np.linalg.norm(softmax(weights)))
# Output: 0.99747807
print(softmax(weights))
# Output: [3.0512990e-07 8.2942910e-07 6.1286983e-06 4.5285295e-05 2.4724933e-03 9.9747497e-01]
不仅范数不完全等于 1.0,而且分布分布在所有维度上。
因此,NODE 论文建议使用 entmax 函数:
import numpy as np
from jax.nn import softmax
from entmax_jax import entmax15, sparsemax
weights = np.array([2, 3, 5, 7, 11, 17])
print(np.linalg.norm(entmax15(weights)))
# Output: 1.0
print(entmax15(weights))
# Output: [0\. 0\. 0\. 0\. 0\. 1.]
这似乎是一个不错的选择,因为它生成了预期的结果!
通过将点积与entmax
函数结合使用,我们可以检索给定特征的值。
可微特征选择可以简单到如下:
def pick_features(weights, features):
feature = jnp.dot(entmax15(weights), features)
return feature
在训练阶段,权重将被训练以学习在给定决策中保留哪个特征。
引入阈值
在决策节点中使用的另一个参数是阈值,这必须被学习。如果所选特征的值大于或等于该阈值,则预测沿着树的右路径进行。相反,如果值严格低于阈值,则采取左路径。
可以使用类似的方法生成一个 1,如果值高于阈值或低于阈值,通过使用点积和entmax函数,但使用一个大小为 2 的向量。第一个元素表示选定值与阈值之间的差异,而第二个元素是 0。
借助entmax函数,如果值低于阈值,向量的第一个元素是负数。当应用entmax函数时,0 会被转化为 1,负数部分变为 0。相反,当值大于阈值时,第一个元素是 1,第二个元素是 0。
这是一个示例实现:
import numpy as np
import jax.numpy as jnp
from jax.nn import softmax
from entmax_jax import entmax15, sparsemax
def choose(feature, threshold, left, right):
choices = jnp.array([left, right])
steer = jnp.array([threshold - feature, 0])
pred = jnp.dot(entmax15(steer), choices)
return pred
print(choose(12, 10, -1, 1))
# Output: 1
print(choose(8, 10, -1, 1))
# Output: -1
在训练过程中,需要学习的参数不仅是阈值,还有附加在这个简单 1 级树的左右叶子上的权重。
www.buymeacoffee.com/guillaumes0
下一步
在这篇文章中,我们已经看到了如何对梯度提升的高度非光滑结构进行正则化,即决策树。这是从梯度提升方法过渡到支撑神经网络的可微编程范式的最低要求。
然而,仍然有三个问题需要解决:
-
如何扩展该方法以支持多级决策树?
-
如何学习这种树的参数?
-
我们如何确保保持在
entmax
函数的线性部分,并确保梯度不会消失?
我将在即将发布的文章中提供这些主题的更多细节。
更新 — 第二篇文章已上线:
## XGBoost: 深度学习如何取代梯度提升和决策树 — 第二部分:训练
没有 if 的世界
[towardsdatascience.com
与此同时,如果你想提高对梯度提升的掌握,可以查看我关于该主题的书籍:
[## 实用的梯度提升:深入了解 Python 中的梯度提升
本书关于梯度提升方法,旨在为希望深入了解这一领域的学生、学者、工程师和数据科学家提供指导…
amzn.to](https://amzn.to/3IKyqTF?source=post_page-----291dc9365656--------------------------------)
XGBoost:深度学习如何替代梯度提升和决策树 — 第二部分:训练
原文:
towardsdatascience.com/xgboost-how-deep-learning-can-replace-gradient-boosting-and-decision-trees-part-2-training-b432620750f8
Saupin Guillaume
·发布于Towards Data Science ·阅读时间 6 分钟·2023 年 9 月 21 日
--
图片由Simon Wilkes提供,来源于Unsplash
一个没有if的世界
在上一篇文章中:
## XGBoost:深度学习如何替代梯度提升和决策树 — 第一部分
在这篇文章中,你将了解如何使用可微编程方法重写决策树,如提议的…
towardsdatascience.com
你已经了解了如何使用可微编程方法重写决策树,这一方法由 NODE 论文提出。这篇论文的理念是用神经网络替代 XGBoost。
更具体地说,在解释了为什么构建决策树的过程不可微之后,介绍了用于正则化与决策节点相关的两个主要元素所需的数学工具:
-
特征选择
-
分支检测
NODE 论文表明,这两者都可以通过 entmax 函数来处理。
总结一下,我们展示了如何创建一个不使用比较运算符的二叉树。
之前的文章以关于训练正则化决策树的开放问题结尾。现在是时候回答这些问题了。
如果你对梯度提升方法感兴趣,可以看看我的书:
## 实用梯度提升:深入探讨 Python 中的梯度提升
这本关于梯度提升方法的书籍是为那些希望深入了解此方法的学生、学术人士、工程师和数据科学家们准备的……
amzn.to
平滑决策节点
首先,基于我们在上一篇文章中介绍的内容,我们创建一个新的 Python 类:SmoothBinaryNode
。
这个类编码了平滑二叉节点的行为。其代码中有两个关键部分:
-
特征的选择,由函数
_choices
处理 -
这些特征的评估、相对于给定的阈值的评估,以及路径选择:
left
还是right
。所有这些都由left
和right
方法管理。
平滑二叉节点。代码由作者提供。
正如在上一篇文章中所解释的,规范化一个二叉节点(从而允许其训练)的关键是使用entmax
函数。
全能的点积
请注意,特征选择以及左分支和右分支的选择都是通过点积
完成的。点积是一个简单的操作,只要只考虑其实现,但实际上它非常强大。
它可以用于做投影、计算cosinus
、找到光线和三角形之间的交点……但这另当别论。
一个简单的二叉节点
让我们用几行代码看看如何在一个只有一个节点的非常基础的二叉树上使用这个新类:
使用平滑二叉节点。代码由作者提供。
在这个片段中,我们配置了我们的节点,使得其左侧包含一个1
,右侧也包含一个1
。这由参数leaves
定义。我们还将阈值设置为 50,如参数biais
中所示。最后,特征选择是通过weights
定义的权重完成的。
这是另一个稍微复杂一点的示例,有一个两层的树:
2 层二叉树。代码由作者提供。
原则与之前的示例类似,只是根节点添加了 2 个节点。
需要学习的参数
在之前的示例中,我们手动定义了三个参数:
-
权重用于选择特征。借助
entmax
函数,权重最高的特征将被选择。 -
一旦特征被选择,我们需要找到最佳的阈值来将数据分成两个子集。
-
最后,我们必须定义树叶子上附加的值,这里是参数
leaves
。
这些是在训练过程中将被学习的参数。
定义目标
和往常一样,在进行机器学习时,目标是找到最小化某些成本函数的参数组合。
当前最常用的是均方误差
,它计算起来相当简单,可以在 Python 中如下编写:
均方误差。代码由作者提供。
请注意,以上代码中有一个小的微妙之处,因为mse
函数创建了一个闭包,捕获了树对象。
我们可以将此函数应用于下面定义的简单单层树:
误差如预期般为零。代码由作者提供。
如预期般,误差为零。
梯度下降学习
如果有一种数学工具与机器学习相关,那就是梯度下降。深度学习、简单的神经网络,甚至梯度提升(但在功能空间中)都使用梯度下降来最小化某种成本函数以训练模型。
这一过程的主要困难是计算复杂函数的梯度,该函数是线性函数(层输入)和非线性函数(激活函数)的数学组合。
尽管这种困难阻碍了神经网络方法的成功已有几十年,但基础的数学原理——微分规则——早已为人所知。
如今,计算复杂函数的梯度,这些函数由许多线性和非线性基础函数组合而成,可以通过一行代码简单且高效地完成,使用自动微分。
如您在SmoothBinaryNode
类的代码中看到的,我们已将这 3 个参数weights, bias, leaves
隔离到变量params
中。
使用梯度下降法是优化参数以最小化误差的标准方法。
自动微分和梯度下降是我在我的书70 个数学概念中探讨的概念:
[## 揭示 Python 中的 70 个数学概念:通过 Python 探索数学的实用指南
购买《揭示 Python 中的 70 个数学概念:通过 Python 探索数学的实用指南》…
amzn.to](https://amzn.to/3ZpI8lm?source=post_page-----b432620750f8--------------------------------)
正如其名称所示,梯度下降要求计算误差函数的梯度,相对于我们要优化的参数。
多亏了库jax
,计算任何函数的梯度变得异常简单,使用grad
函数。
Jax 使用自动微分
来自动且高效地计算导数。如果你对这种引人入胜的方法感兴趣,可以查看我关于该主题的介绍文章:
## 从零开始的可微分编程
去年我有幸参加了 Yann Lecun 在 Facebook 人工智能会议上的演讲…
towardsdatascience.com
实现它的一种方法在这段代码中展示了:
使用梯度下降法训练平滑二叉节点。代码由作者提供。
这段代码首先使用库jax
计算误差的梯度,以获得一个最优的参数集,即误差为零的参数集。因此,我们可以确保在这种情况下梯度确实为零。
然后我们稍微扰动leaves
参数,并确保该参数的方向上的梯度非零。希望是这样。同样,修改bias
参数时也是如此。
最后,从一个扰动过的、非最优的参数集开始,我们使用梯度下降法来迭代更新这些参数。
在经过任意 1000 次迭代后,参数会收敛到另一个最小化误差的参数集。
梯度消失
我必须坦白,在上面的例子中我有点作弊。我故意选择了误差函数中的非平坦区域的参数。
如果你记得entmax
函数的形状,当使用高alpha
时,从 0 到 1 的过渡是非常陡峭的。这意味着对于任何稍微偏离 0 的值,函数的曲线都是完全平坦的。
由于梯度定义上是曲线的斜率,因此在这个区域梯度为零。
由于梯度下降法通过添加一个小的增量(即learning rate
和梯度的乘积)来更新参数,因此优化会失败。
避免这种烦人的限制的一种方法是执行批量归一化,以确保特征保持在entmax
函数不平坦的区域。
www.buymeacoffee.com/guillaumes0
结论
在这两篇文章系列中,我们已经看到如何对决策树进行正则化。我们展示了如何用entmax
函数和dot product
替换特征选择和分支选择。
这种正则化允许在可微编程
的数学框架中使用平滑决策树
。
能够使用这种形式主义是非常强大的,因为它允许我们将任何类型的神经网络(一个复杂的可微函数)与决策树混合使用。
XGBoost:简介、逐步实现和性能比较
原文:
towardsdatascience.com/xgboost-intro-step-by-step-implementation-and-performance-comparison-6018dfa212f3
极端梯度提升:快速可靠的回归器和分类器
Farzad Mahmoodinobar
·发表于 Towards Data Science ·12 分钟阅读·2023 年 9 月 29 日
--
图片来源于 Sam Moghadam Khamseh 于 Unsplash
XGBoost 已成为所有机器学习从业者中最受欢迎的全能回归器和/或分类器之一。如果你问数据科学家在不知道其他信息的情况下,他们会选择哪个模型,很可能他们会选择 XGBoost,因为它可以应用于各种类型的用例——它快速、可靠、多功能且非常容易实现。
今天,我们将从概念上回顾 XGBoost,讲解其实现过程,并将其性能与几种其他模型在分类任务中的表现进行比较。
开始吧!
1. XGBoost — 概念概述
XGBoost 代表极端梯度提升。它是一种梯度提升决策树模型,既可用于监督回归任务,也可用于分类任务。我们使用了几个术语来定义 XGBoost,因此让我们逐一了解它们,以便更好地理解。我将简要概述每个概念,并提供更多细节的链接,供那些对深入讨论感兴趣的读者参考。
-
监督学习与非监督学习: 监督学习是使用带标签的数据来训练机器学习(ML)模型。因此,模型可以从我们提供的数据标签中学习。另一方面,非监督学习是当我们的数据没有标签时,模型在没有任何标签的情况下学习数据中的潜在模式。
-
回归与分类: 回归机器学习模型用于预测连续输出,而分类模型用于预测离散输出。欲了解更深入的讨论,请参考这篇文章。
-
决策树: 决策树是一种可以用于回归和分类任务的监督学习算法。欲了解更深入的讨论,请参考这篇文章。
-
梯度提升: 这里的想法是将若干个弱学习器(例如模型或算法)结合起来,从而得到一个整体上更强的模型。在 XGBoost 中,若干个弱决策树被组合在一起,形成一个改进的学习器。
现在我们对 XGBoost 有了更多的了解,接下来看看它在实践中的表现!我们将首先介绍一些基础知识,然后开始实施。
2. 基础
我们的目标是实现 XGBoost,并将其性能与其他算法进行比较。为此,我们的整体过程包括为分类任务训练多个机器学习模型(因此称这些模型为“分类器”),然后使用一组常用的分类指标来评估这些分类器的性能。为了实现这些步骤,我们需要讨论将用于训练和测试我们模型的数据集、将与 XGBoost 比较的分类器(即模型或算法)以及本练习中使用的评估指标(我们使用这些指标来比较各种模型的性能),这些内容将在本节中介绍。一旦基础知识掌握,我们将实现我们的模型。
2.1. 数据集
为了实现和评估 XGBoost 相对于其他分类器的性能,我们将使用来自加州大学欧文分校机器学习库 (CC BY 4.0) 的数据集,该数据集用于预测一个人是否年收入超过 50,000 美元。以下示例所用的文件可以在这里下载。
让我们查看数据集的前 5 行,以便对数据的整体情况有个大致了解。
# import
import pandas as pd
# read the file
df_adult = pd.read_csv('adult.data', header=None, names=[
'age', 'workclass', 'fnlwgt', 'education', 'education-num', 'marital-status',
'occupation', 'relationship', 'race', 'sex', 'capital-gain', 'capital-loss',
'hours-per-week', 'native-country', 'income'])
# return top 5 rows of the dataframe
df_adult.head()
结果:
数据包括数值和分类值的组合。让我们查看更多关于数据的信息。
df_adult.info()
结果:
正如我们之前所看到的,数据是整数(int64
)和分类值(object
)的组合,共有 32,561 行,涵盖 15 列(注意列数从 0 开始,所以总列数为 15)。数据类型是决定在模型选择过程中使用什么分类器的重要因素。现在我们对数据集有了更多了解,让我们继续探讨将要训练的模型。
2.2. 模型
我们将在比较中使用总共 8 个机器学习模型。对于本次练习,不需要对每个模型有全面的了解,但我在本节中包含了每个模型的简要描述,如下所示:
-
AdaBoost(自适应提升)是一种集成提升技术,它(类似于其他提升技术)提高了弱分类器的分类性能。它是一种自适应的技术,因为它不断调整弱学习者之间的权重,以获得更好的整体性能。
-
CatBoost(分类提升),顾名思义,旨在处理分类特征,无需预先编码。这在处理具有许多分类特征的数据集时特别有用,正如我们所看到的,我们的数据集也包括一些分类变量,因此在评估结果中请注意这一点。
-
GradientBoosting 是一种简化的梯度提升分类器(因为它的超参数较少,相较于许多其他梯度提升分类器)。这是一个很好的起点,但当我们需要更高级的超参数优化时,我们通常会使用 XGBoost 或 LightGBM,我们将在下面介绍这些内容。
-
k-NN(k-最近邻)简单易于实现,适用于低维数据,但对于大数据集可能计算开销较大。
-
LightGBM(轻量级梯度提升机)是另一种梯度提升模型。与 XGBoost 相比,它在内存使用和计算速度方面更高效,因此适合大数据集或需要更快训练时间的情况。
-
随机森林 是另一种非常流行的集成学习方法,它通过在训练期间构建多个决策树来运作,并输出分类的模式(它也是一个能够进行回归任务的模型)。
-
SVM(支持向量机)是另一种可以用于分类和回归的模型。虽然它在高维空间中有效,但它的速度可能比这里讨论的一些其他梯度提升方法要慢。
-
XGBoost(极端梯度提升),正如前面所讨论的,是一个旨在高效和多功能的梯度提升库,使其适合各种监督任务。
现在我们已经涵盖了将要使用的模型,让我们讨论一下将用于比较模型的指标。
2.3. 评价指标
为了比较我们各种模型的性能,我们需要一个基准或“指标”。由于我们将使用我们的模型进行分类任务,因此我们希望使用相关指标来衡量分类器的性能。我们将从 7 个维度来评估我们的分类器。前 6 个是评估分类任务中常用的指标,第 7 个只是训练分类器所需的时间。一旦看到结果,比较这些分类器的性能速度就会很有趣。
对于本文来说,不需要对我们使用的指标有全面的理解,但如果你想更好地理解这些指标,我们需要首先定义一些术语。在一个二分类目标变量中,目标变量只能是正类(或 1)和负类(或 0),预测有四种可能的结果:
-
真正例: 正事件被正确预测。
-
假阳性: 负事件被错误预测为正(也称为 I 型错误)。
-
真阴性: 负事件被正确预测。
-
假阴性: 正事件被错误预测为负(也称为 II 型错误)。
记住上述术语,我们来看看指标。
-
准确率是正确预测的观察值与数据集中总观察值的比例。它是查看模型整体性能的一个好指标,但当数据集包含不平衡类别时,数字可能会具有误导性。它的计算公式如下:
Accuracy = (True Positives + True Negatives) / Total Observations
-
精确度是正确预测的正类观察值与总预测正类的比例(也称为正预测值)。在一些分类任务中,假阳性的成本非常高,我们会选择高精确度的模型。它的计算公式如下:
Precision = Positive Predictive Value (PPV) = True Positives / (True Positives + False Positives)
-
召回率(或灵敏度)是正确预测的正类观察值与正类中所有观察值的比例(也称为真正率)。召回率在假阴性成本高的任务中尤为重要。它的计算公式如下:
Recall = Sensitivity = True Positive Rate (TPR) = True Positives / (True Positives + False Negatives)
-
F1-分数是精确度和召回率的加权平均数,考虑了假阳性和假阴性。在需要考虑精确度和召回率的情况下,我们通常使用 F1-分数来权衡二者之间的折衷。它的计算公式如下:
F1 Score = 2 * (Precision * Recall) / (Precision + Recall)
-
AUC-ROC(接收者操作特征曲线下面积)表示模型区分正类和负类的能力。这一点不那么直观,所以我们来谈谈数字!ROC 曲线展示了真正率(或我们之前讨论过的召回率)和假正率(即被错误识别为正类的负类比例)之间的权衡。AUC-ROC 为 0.5 表示正类和负类之间没有区分——换句话说,模型没有比随机选择正类和负类更好。大于 0.5 的值表示两个类别之间有一定的区分,1 是完美的区分。简而言之,AUC-ROC 越高,模型表现越好。
-
AUC-PR(精确度-召回率曲线下面积)类似于 AUC-ROC,但专注于正类的性能。在数据集存在类别不平衡的情况下,我们更关注正类,这一点就显得尤为重要。
-
训练时间是训练模型所需的时间,我们在此示例中使用它来查看每个模型的计算效率。在这个例子中,数据集不大,因此训练的计算成本微不足道。在许多商业案例中,我们在训练模型时会面临计算限制,因此这个指标变得更加重要。
基础知识已经讲解完毕,接下来让我们开始实施部分吧!
3. 实施
实施将包括以下步骤:
-
导入库
-
数据加载与预处理
-
训练和评估分类器
-
结果与讨论
3.1. 导入库
我们将从导入在练习中使用的各种库开始。随意复制和粘贴代码块并跟随操作。
# import libraries
import pandas as pd
import time # this is used to calculate the training time of each model
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, average_precision_score
from sklearn.preprocessing import LabelEncoder
from sklearn.ensemble import GradientBoostingClassifier, AdaBoostClassifier
import xgboost as xgb
import lightgbm as lgb
from catboost import CatBoostClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
3.2. 数据加载与预处理
既然我们已经导入了所需的库,让我们定义一个函数来读取我们将用于训练分类模型的数据集。我将这一部分封装在一个名为load_and_preprocess_data
的函数中,该函数读取文件并将其分解为用于训练的特征和目标。我在代码中添加了注释,以便你更容易跟随。
def load_and_preprocess_data(filepath):
# read the data set into as a dataframe
data = pd.read_csv(filepath, header=None, names=[
'age', 'workclass', 'fnlwgt', 'education', 'education-num', 'marital-status',
'occupation', 'relationship', 'race', 'sex', 'capital-gain', 'capital-loss',
'hours-per-week', 'native-country', 'income'])
# initialize a label encoder
label_encoder = LabelEncoder()
# encode columns
for col in data.select_dtypes(include=['object']).columns:
data[col] = label_encoder.fit_transform(data[col])
# Separate features as X and target as y
X = data.drop('income', axis=1)
y = data['income']
return X, y
3.3. 训练和评估分类器
在这一步中,我们将定义一个函数来训练分类器并评估其性能,当我们提供训练集和测试集时。我们稍后会将数据拆分成训练集和测试集,所以现在不必担心。和以前一样,我在代码中添加了注释以帮助阅读。
def train_and_evaluate_classifier(clf, X_train, y_train, X_test, y_test):
# starting to track the time so that we can report the total training time
start_time = time.time()
# train the classifier
clf.fit(X_train, y_train)
# stop tracking time, now that training is completed
end_time = time.time()
# calculated how long training took
elapsed_time = end_time - start_time
# make predictions using the trained model. this will be used for evaluation of the trained model
y_pred = clf.predict(X_test)
y_proba = clf.predict_proba(X_test)[:, 1] if hasattr(clf, 'predict_proba') else None
# here are the metrics we use for evaluations
metrics = {
'Accuracy': accuracy_score(y_test, y_pred),
'Precision': precision_score(y_test, y_pred),
'Recall': recall_score(y_test, y_pred),
'F1-Score': f1_score(y_test, y_pred),
'AUC-ROC': roc_auc_score(y_test, y_proba) if y_proba is not None else None,
'AUC-PR': average_precision_score(y_test, y_proba) if y_proba is not None else None,
'Training Time (s)': elapsed_time
}
return metrics
3.4. 结果与讨论
最后,我们将所有内容放在一个主函数中,该函数调用之前的函数来读取数据、训练分类器、评估训练好的模型,然后将结果以数据框的形式返回,如下所示:
def main():
# initialize a dataframe to store the results in
results_df = pd.DataFrame(columns=['Accuracy', 'Precision', 'Recall', 'F1-Score', 'AUC-ROC', 'AUC-PR', 'Training Time (s)'])
# load and preprocess the data, using our previously-defined function
X, y = load_and_preprocess_data('adult.data')
# split the data into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1234)
# initialize the classifiers
classifiers = {
'XGBoost': xgb.XGBClassifier(),
'Random Forest': RandomForestClassifier(),
'SVM': SVC(probability=True),
'k-NN': KNeighborsClassifier(),
'LightGBM': lgb.LGBMClassifier(),
'CatBoost': CatBoostClassifier(verbose=0),
'GradientBoosting': GradientBoostingClassifier(),
'AdaBoost': AdaBoostClassifier()
}
# train and evaluate the trained classifiers
for name, clf in classifiers.items():
metrics = train_and_evaluate_classifier(clf, X_train, y_train, X_test, y_test)
results_df.loc[name] = metrics
# sort classifiers alphabetically
results_df.sort_index(inplace=True)
# add row numbers
results_df.reset_index(drop=False, inplace=True)
results_df.index.name = '#'
return results_df
if __name__ == "__main__":
df = main()
现在所有计算都完成了,让我们来看看结果吧!
# round results to 3 decimal places
df.round(3)
结果:
如评估指标部分所讨论的,决定哪种指标最有效取决于项目的目标。为了讨论的方便,我们在这个问题中假设我们同等重视精确度和召回率,因此讨论哪个分类器能提供这两者的平衡度量。因此,我将主要依赖 F1-Score 和 AUC-ROC 作为这个练习中需要关注的两个指标。回顾一下,F1-Score 是精确度和召回率的调和均值(因此是两者的平衡度量),而 AUC-ROC 是接收者操作特征曲线下的面积,它帮助我们理解分类器在区分正负案例方面的表现。
根据上述假设,让我们对结果进行排序,并再次查看评估结果。
# sort results
df.sort_values(by=['F1-Score', 'AUC-ROC', 'Training Time (s)'], ascending=[False, False, True]).reset_index(drop=True).round(3)
结果:
结果相当有趣。正如你所见,CatBoost、LightGBM 和 XGBoost 是性能非常接近的前三名模型,这三者都是提升模型。CatBoost 是我们的最佳表现者,F1-Score 比 LightGBM 高一点。一个原因可能是数据集中存在类别变量。XGBoost 是接近第二名的第三名。另一个有趣的观察是每个模型所需的训练时间。我们看到 LightGBM 的表现几乎与 CatBoost 相当,但训练时间却远低于 CatBoost。实际上,LightGBM 的速度比 CatBoost 快了将近 96%。在我们的示例中,训练时间相对微不足道,但在商业和学术环境中经常出现的大型数据集中,我们会更加关注性能水平与训练时间之间的权衡,这可能导致选择 LightGBM 而非 CatBoost。
4. 结论
在这篇文章中,我们介绍了 XGBoost,一种在机器学习实践者中广泛使用的强大机器学习算法。然后我们讨论了其他分类器和分类评估指标,这些指标可用于比较不同分类器的性能。最后,我们使用数据集实施了 XGBoost 及其他分类器,并比较和讨论了它们的性能水平和权衡。
感谢阅读!
如果你觉得这篇文章有帮助,请在 Medium 上关注我并订阅以接收我最新的文章!
(所有图片,除非另有说明,均为作者提供。)
XGBoost 现在支持 MAE 作为目标函数
原文:
towardsdatascience.com/xgboost-now-support-mae-as-objective-fdbbe18e173
这怎么可能,因为 MAE 是非光滑的?
Saupin Guillaume
·发表于Towards Data Science ·阅读时间 5 分钟·2023 年 1 月 18 日
--
图片由Ajay Karpur拍摄,来源于Unsplash
在基于梯度提升的模型中,一个关键的参数是目标函数。实际上,决策树的整个构建过程都来源于目标函数及其一阶和二阶导数。
XGBoost 最近引入了一种新类型的目标函数支持:没有二阶导数的非光滑目标函数。其中,著名的MAE(均方绝对误差)现在可以在 XGBoost 中原生启用。
在这篇文章中,我们将详细介绍 XGBoost 如何被修改以处理这种类型的目标函数。
梯度提升需要光滑的目标函数
XGBoost、LightGBM 和 CatBoost 都有一个共同的限制:它们需要光滑(从数学上讲)的目标函数来计算决策树叶子的最优权重。
对于 XGBoost,这种情况不再成立。XGBoost 最近引入了使用线性搜索支持 MAE,从 1.7.0 版本开始。
如果你愿意详细掌握梯度提升,可以查看我的书:
[## 实用梯度提升:深入探讨 Python 中的梯度提升
这本关于梯度提升方法的书面向希望深入了解这一领域的学生、学者、工程师和数据科学家……](https://amzn.to/3CTMDuH?source=post_page-----fdbbe18e173--------------------------------)
梯度和赫西矩阵是梯度提升的核心
基于梯度提升的方法的核心思想是将梯度下降应用于函数空间而不是参数空间。
作为提醒,该方法的核心是在线性化目标函数的基础上进行,并添加一个小的增量,以最小化该目标。
这个小的增量在函数空间中表示,并且它是一个新的二叉节点,由函数 f_t
表示。
这个目标将损失函数 l
与正则化函数 Ω 结合:
目标函数。公式由作者提供。
一旦线性化,我们得到:
在 ŷ[t-1] 附近线性化的目标函数。公式由作者提供。
其中:
一阶和二阶导数。公式由作者提供。
最小化这个线性化目标函数归结为减少常数部分,即:
目标的变量部分需要最小化。公式由作者提供。
由于模型 f_t
的新阶段是一个二叉决策节点,将生成两个值(其叶子):w_left
和 w_right
,因此可以将上述和重新组织如下:
重新组织线性化目标。公式由作者提供。
在这一阶段,最小化线性化目标仅涉及找到最佳权重 w_left
和 w_right
。由于它们都涉及一个简单的二阶多项式,解是著名的 -b/2a
表达式,其中 b
是 G
,a
是 1/2H
,因此对于左节点,我们得到
左侧最佳权重的公式。公式由作者提供。
完全相同的公式适用于右侧权重。
注意正则化参数 λ,它是一个 L2 正则化项,与权重的平方成正比。
MAE 没有二阶导数
平均绝对误差 的问题在于它的二阶导数为零,因此 H
为零。
正则化
一种可能的解决方案是对该函数进行正则化。这意味着用一个至少二阶可导的函数替代这个公式。请参见下面的文章,展示了如何使用 logcosh
实现:
## XGBoost 的置信区间
构建一个正则化的分位回归目标,以获得不需要超参数调优的置信区间
towardsdatascience.com
线搜索
另一种选择,即 XGBoost 自 1.7.0 版本发布以来最近引入的方法,是使用一种迭代方法来找到每个节点的最佳权重。
为此,目前的 XGBoost 实现使用了一种技巧:
-
首先,像往常一样计算叶子值,只需将二阶导数强制为 1.0
-
然后,一旦整个树构建完成,XGBoost 使用 α-分位数更新叶子值
如果你想了解如何实现(并且不怕现代 C++),详细信息可以在这里找到。关注UpdateTreeLeaf
,更具体地说是UpdateTreeLeafHost
方法。
如何使用它
很简单:只需选择一个版本大于 1.7.0 的 XGBoost,并使用 objective: mae
作为参数。
www.buymeacoffee.com/guillaumes0
结论
XGBoost 引入了一种新的方法来处理非平滑目标,如 MAE,这不需要对函数进行正则化。
MAE 是一个非常方便的指标,因为它容易理解。此外,它不像 MSE 那样过度惩罚大错误。这在使用同一模型预测大值和小值时非常有用。
能够使用非平滑目标函数非常吸引人,因为它不仅避免了对函数进行近似的需求,还为其他非平滑目标函数(如 MAPE)打开了大门。
显然,这是一个值得尝试并关注的新特性。
更多关于梯度提升、XGBoost、LightGBM 和 CaBoost 的内容,请参见我的书:
[## 实用梯度提升:深入探讨 Python 中的梯度提升
本书关于梯度提升方法,旨在为希望深入了解梯度提升的学生、学者、工程师和数据科学家提供帮助…](https://amzn.to/3CTMDuH?source=post_page-----fdbbe18e173--------------------------------)
XGBoost: 权威指南(第一部分)
原文:
towardsdatascience.com/xgboost-the-definitive-guide-part-1-cc24d2dcd87a
对流行的 XGBoost 算法进行逐步推导,包括详细的数值示例。
Dr. Roi Yehoshua
·发表于 Towards Data Science ·15 min read·2023 年 8 月 9 日
--
图片由 Sam Battaglieri 提供,来源于 Unsplash
XGBoost(极端梯度提升的简称)是一个开源库,提供了梯度提升决策树的优化和可扩展实现。它结合了各种软件和硬件优化技术,使其能够处理大量数据。
最初由 Tianqi Chen 和 Carlos Guestrin 于 2016 年开发为研究项目[1],XGBoost 已经成为处理结构化(表格)数据的监督学习任务的首选解决方案。它在许多标准回归和分类任务中提供了最先进的结果,许多 Kaggle 竞赛获胜者将 XGBoost 作为他们获胜解决方案的一部分。
尽管使用深度神经网络对表格数据取得了显著进展,但它们在许多标准基准测试中仍被 XGBoost 和其他基于树的模型超越[2, 3]。此外,XGBoost 所需的调优比深度模型少得多。
XGBoost 相对于其他梯度提升算法的主要创新包括:
-
聪明的决策树正则化。
-
使用二阶近似优化目标(牛顿提升)。
-
高效计算的加权分位数草图过程。
-
处理稀疏数据的新颖树学习算法。
-
支持数据的并行和分布式处理。
-
针对外部树学习的缓存感知块结构。
在本系列文章中,我们将深入探讨 XGBoost,包括算法的数学细节、从零开始用 Python 实现算法、XGBoost 库的概述以及如何在实际中使用它。
在本系列的第一篇文章中,我们将一步步推导 XGBoost 算法,提供算法的伪代码实现,然后在一个玩具数据集上展示其工作原理。
本文中对算法的描述基于 XGBoost 的原始论文 [1] 和 XGBoost 库的官方文档(xgboost.readthedocs.io/
)。不过,本文在以下几个方面超越了现有的文档:
-
详细解释了数学推导的每一步。理解算法的数学细节将帮助你掌握 XGBoost 的各种超参数的含义(它们的数量相当多)以及如何在实践中调整它们。
-
提供算法的完整伪代码([1]中的伪代码仅以非常简洁的方式描述了算法的具体部分)。
-
详细讲解将 XGBoost 应用到玩具数据集上的数值示例。
本文假设你已经对梯度提升有所了解。如果没有,请查看以下文章:
## 从理论到实践的梯度提升(第一部分)
理解流行的梯度提升算法背后的数学原理,以及如何在实际中使用它
[towardsdatascience.com
现在,让我们深入了解吧!
XGBoost 算法
回顾一下,在监督学习问题中,我们会得到一个包含 n 个标记样本的训练集:D = {(x₁, y₁), (x₂, y₂), … , (xₙ, yₙ*)},其中 xᵢ 是一个 m 维向量,包含样本 i 的特征,而 yᵢ 是该样本的标签。我们的目标是构建一个预测结果尽可能接近真实标签的模型。
类似于梯度树提升,XGBoost 构建了一个回归树的集成,这个集成由 K 个加性函数组成:
其中 K 是树的数量,F 是所有可能的回归树函数的集合。请注意,我们在这里使用 f(x) 来表示基础模型的假设,而不是我们通常使用的 h(x),因为字母 h 稍后将用于表示另一个实体(损失函数的 Hessian)。
给定一个损失函数 L(y, F(x)),它衡量真实标签 y 和集成预测 F(x) 之间的差异,XGBoost 旨在找到一个在训练集上最小化损失的集成模型,同时避免过于复杂。为此,它将以下 正则化 成本函数定义为模型的目标函数:
正则化成本函数
其中 ω(fₖ) 是树 fₖ 的复杂度,这将在后续详细定义。类似于岭回归,该成本函数由两部分组成:模型在训练集上的总损失和一个对基树复杂度进行惩罚的正则化项。
请注意,在 XGBoost 中,正则化是树学习目标的一个组成部分,而不是像其他树模型中那样通过限制最大树深度或最大叶子数等外部启发式方法来施加的。
加性训练
由于目标函数 J 包含作为参数的函数(回归树函数),它不能使用传统的优化方法如梯度下降进行优化。相反,模型以贪婪的逐步方式进行训练,每次添加一棵新树。
正式来说,让 Fₖ 表示第 k- 次迭代时的集成模型:
在这一迭代中,我们将一个树 fₖ 加入到之前的集成模型中,该树最小化以下目标:
即,我们希望找到一棵树 fₖ,它可以降低整体训练损失,但同时也不过于复杂(具有较低复杂度 ω(fₖ))。
牛顿提升
对于任意损失函数 L,找到最佳树 fₖ 是计算上不可行的,因为这需要枚举所有可能的树并选择最佳的一棵。相反,我们使用一种迭代优化方法:在每次提升迭代中,我们选择一棵树 fₖ,使我们更接近最小成本。
在原始(非极端)梯度提升算法中,函数 fₖ 是选择为指向损失函数相对于先前集成模型预测的负梯度方向的。这使得优化过程在函数空间中如同梯度下降(因此称为功能梯度下降)。
XGBoost 使用类似的思想,但它不是在函数空间中进行梯度下降(即使用一阶近似),而是在函数空间中使用牛顿-拉夫森 方法(即使用二阶近似)。当目标函数的二阶导数已知且易于计算时,牛顿法通常会更快地收敛到最小值(对于 XGBoost 的目标函数,当使用常见的损失函数如平方损失或对数损失时,这一点是成立的)。
作为提醒,牛顿方法尝试通过构建从初始猜测x₀ ∈ R的序列{xₖ}来寻找二次可微函数f:R → R的最小值。该序列是由f在xₖ附近的二阶泰勒近似构成的:
序列中的下一个元素xₖ₊₁被选择为最小化此方程右侧写出的二次扩展。我们可以通过对步长t取导数并设置为 0 来找到该扩展的最小值:
因此,最小值在以下情况下实现:
因此,牛顿方法执行以下迭代:
该序列保证以二次速度收敛到最小值x,前提是f是一个强凸函数,并且x₀足够接近x:
牛顿方法(红色)利用曲率信息(第二导数)比梯度下降(绿色)更直接地达到最小值。图像由 Oleg Alexandrov 制作并发布于公共领域(en.wikipedia.org/wiki/Newton%27s_method_in_optimization#/media/File:Newton_optimization_vs_grad_descent.svg
)
回到 XGBoost,我们首先写出给定数据点xᵢ附近的损失函数的二阶泰勒展开:
在这里,gᵢ是损失函数的第一导数(梯度),hᵢ是损失函数的第二导数(Hessian),都与前一个集成的预测值xᵢ有关:
将此近似值代入我们在迭代k时的目标函数中得到:
在去除常数项后,我们得到以下简化目标:
我们的目标是找到一个树fₖ(x)(我们的“步长”),使其最小化这个目标函数。注意,该函数仅依赖于gᵢ和hᵢ,这使得 XGBoost 能够支持任何二次可微的自定义损失函数。
树复杂度的定义
既然我们引入了训练步骤,我们需要定义树的复杂度度量。为此,我们首先写出回归树计算的函数f(x)的更明确表达式。
设 T 为树中的叶子数量,w = (w₁, …, wₜ) ∈ Rᵗ 是叶节点上的分数(或权重)向量,q(x): Rᵐ → {1, 2, …, T} 是一个将每个样本x(一个m维向量)分配到其对应叶子索引的函数(根据树节点中的决策规则)。那么我们可以将 f(x) 表示为:
也就是说,分配给样本x的输出值是该样本通过树映射到的叶节点的权重。
我们现在定义树的复杂度如下:
也就是说,树的复杂度是其叶子数量的函数(γT,其中 γ 是一个超参数),以及叶子权重的平方和乘以另一个超参数 λ。增加 γ 倾向于创建较小的树,而增加 λ 则鼓励给叶子分配较小的权重,这反过来减少了树对减少损失函数的贡献(类似于梯度提升中的收缩因子)。
使用这个树复杂度的定义,我们现在可以将第 k 步的目标函数重写如下:
其中 Iⱼ = {i|q(xᵢ) = j} 是分配给 j -th 叶子的样本的索引集合。注意在方程的第二行中我们将求和的索引从 i 改为 j,因为同一叶节点上的所有样本得到相同的权重。
我们可以通过定义进一步简化目标函数:
Gⱼ 是关于叶子j 的样本的损失函数梯度之和,Hⱼ 是关于同一组样本的损失函数的 Hessian 矩阵之和。
我们现在可以写出:
我们的目标是找到使这个成本函数最小化的叶子权重。为此,我们对每个权重 wⱼ 求 Jₖ 的偏导数:
将这个导数设置为 0,给我们叶子* wⱼ* 的最优权重:
将最优权重代入目标函数中,给我们这个树可以获得的最佳目标减少量:
学习树结构
现在我们知道如何衡量给定树的质量,从理论上讲,我们可以枚举所有可能的树并选择最佳的一棵。实际上,这是不可行的。相反,我们以贪婪的自顶向下方式构建回归树,类似于标准决策树的构建方式。
在树的每个节点中,我们通过计算从该分割中可以获得的成本函数 Jₖ 的减少量来评估所有可能的分割。
正式地,设Iₗ和Iᵣ分别为分裂后左子节点和右子节点的样本集合,I = Iₗ ∪ Iᵣ为父节点的样本集合。则子节点中的梯度和 Hessian 的总和为:
父节点中的梯度和 Hessian 的总和为:
因此,分裂后的成本减少为:
我们使用这个公式来评估所有的分裂候选,然后选择增益(成本减少最高)的分裂。如果最佳分裂获得的增益小于 0,即成本减少小于γ,那么我们选择不对父节点进行分裂(它将成为树中的一个叶子节点)。
请注意,每个叶子节点对增益的贡献的计算公式与叶子节点的最优权重公式非常相似,只是我们没有对分子中的梯度总和进行平方处理。
伪代码中的完整算法
以下伪代码展示了 XGBoost 算法的全部风采:
算法使用一个名为 Build-Tree 的辅助函数来构建集合中的下一个回归树:
该函数使用了另一个辅助函数 Find-Best-Split,它评估给定节点的所有可能分裂,并返回具有最高增益的分裂(在原始 XGBoost 论文中称为精确贪婪算法)。最佳分裂被返回为一个元组,其中包含了分配到左子节点和右子节点的样本子集以及它们梯度和 Hessian 的总和。该函数的伪代码如下所示:
作为练习,尝试用你喜欢的编程语言实现该算法(在本系列的下一篇文章中提供了 Python 实现)。
在接下来的部分,我们将推导不同类型问题的损失函数的梯度和 Hessian 的更明确公式。
XGBoost 回归
在回归问题中,最常用的损失函数是平方损失:
相对于前一个集合的预测值,其一阶导数为:
而其二阶导数为:
因此,在这种情况下,叶子节点j的最优输出值为:
该叶子节点对损失减少的贡献为:
XGBoost 分类
在二分类中,我们使用对数损失作为损失函数:
其中 pi 是之前预测的概率:
我们已经在梯度提升的文章中找到了对数损失的一阶和二阶导数(参见“分类的梯度树提升”部分):
因此,叶子* j *的最佳输出值为:
请注意,除了λ之外,这与我们在梯度提升中用于找到叶子输出值的公式相同。
这个叶子对损失减少的贡献是:
在玩具数据集上的演示
为了说明,我们将使用与经典梯度提升算法相同的数据集:
数据集中的目标是预测客户是否会购买给定的产品,基于三个属性:客户的年龄、收入水平(低、中或高)和教育水平(高中或大学)。
为了解决这个问题,我们将构建一个最大深度为 2 的 XGBoost 树集,并将学习率设置为η = 0.5。为了简化示例,正则化参数将设置为 0(λ = 0 和 γ = 0)。
首先,我们用一个常数值初始化模型,该值是正类的对数几率:
接下来,我们计算训练集中样本的梯度和 Hessians:
我们现在构建第一个 XGBoost 树。我们从找到根节点的最佳分裂开始。根节点的梯度和 Hessians 的和为:
接下来,我们计算每个特征中每个可能分裂点的增益。我们从两个分类属性开始:
对于年龄属性,我们首先根据年龄值对梯度和 Hessians 进行排序:
年龄(排序后)= [22, 25, 28, 30, 35, 40]
梯度(排序后)= [-0.333, -0.333, 0.667, -0.333, 0.667, -0.333]
Hessians(排序后)= [0.222, 0.222, 0.222, 0.222, 0.222, 0.222]
我们现在考虑两个连续年龄之间的每一个中点作为候选分裂点:
增益最高的分裂点是年龄 < 26.5。因此,树的第一层如下所示:
左子节点的两个样本的梯度和 Hessians 完全相同,因此不需要再分裂。
我们现在需要找到右子节点的最佳拆分。该节点(样本 2、4、5、6)上的梯度和 Hessian 的总和为 G = 0.666 和 H = 0.888(如节点内所示)。我们考虑所有可能的拆分方式:
在这种情况下,我们有多个候选拆分,这些拆分能带来最大的增益(1.5)。我们可以任意选择拆分条件为 Income = Medium。得到的树是:
接下来,我们计算叶节点的最佳输出值(权重)。最左边叶节点的权重是:
类似地,另外两个叶节点的权重是:
因此,我们从第一棵 XGBoost 树中得到以下预测:
我们现在通过学习率来缩放这些预测值,并将其添加到前一迭代的预测中。然后我们使用新的预测值计算下一次迭代的梯度和 Hessian:
我们现在构建第二棵 XGBoost 树。按照与前一个迭代相同的过程,我们得到以下树(请确认这确实是正确的树!):
现在我们通过学习率来缩放第二棵树的预测值,并将其添加到之前的集成预测中:
我们可以看到经过三次迭代后,我们的集成模型正确地对训练集中的所有样本进行了分类!
细心的读者可能会注意到,得到的集成与我们使用经典的非极端梯度提升算法得到的集成完全相同。这并不令人惊讶,因为在分类问题中,经典算法也使用二阶近似来优化对数损失函数。XGBoost 相对于经典算法的优势在于,它允许我们将正则化系数作为目标函数的一部分(在这个例子中我们没有利用这一点)。
最终说明
除非另有说明,所有图片均由作者提供。
感谢阅读!
参考文献
[1] Chen, T., & Guestrin, C. (2016). XGBoost:一个可扩展的树提升系统。第 22 届 ACM SIGKDD 国际知识发现与数据挖掘大会论文集,785–794。
[2] Grinsztajn, L., Oyallon, E., & Varoquaux, G. (2022). 为什么基于树的模型在典型的表格数据上仍然优于深度学习? 神经信息处理系统进展,35:507–520。
[3] Shwartz-Ziv, R., & Armon, A. (2022). 表格数据:深度学习并非你所需的一切。信息融合,81:84–90。
XGBoost:权威指南(第二部分)
原文:
towardsdatascience.com/xgboost-the-definitive-guide-part-2-c38ef02f74d0
从头开始用 Python 实现 XGBoost 算法
Roi Yehoshua 博士
·发表于 Towards Data Science ·阅读时间 14 分钟 ·2023 年 8 月 15 日
--
图片由 StockSnap 提供,来自 Pixabay
在 上一篇文章 中,我们讨论了 XGBoost 算法并展示了其伪代码实现。在这篇文章中,我们将从头开始用 Python 实现该算法。
提供的代码是 XGBoost 算法的简洁轻量实现(仅约 300 行代码),旨在演示其核心功能。因此,它没有针对速度或内存使用进行优化,也没有包括 XGBoost 库提供的所有选项(有关库功能的更多细节,请参见 xgboost.readthedocs.io/
)。更具体地说:
-
代码是用纯 Python 编写的,而 XGBoost 库的核心是用 C++ 编写的(其 Python 类只是对 C++ 实现的薄包装)。
-
它不包括允许 XGBoost 处理大量数据的各种优化,例如加权分位数草图、外部树学习以及数据的并行和分布式处理。这些优化将在系列文章的下一篇中详细讨论。
-
目前的实现仅支持回归和二分类任务,而 XGBoost 库还支持多分类和排序问题。
-
实现仅提供了 XGBoost 库中存在的超参数的一个小子集。具体而言,它支持以下超参数:
-
n_estimators(默认值 = 100):集合中的回归树数量(也就是提升迭代的数量)。
-
max_depth(默认值 = 6):每棵树的最大深度(级别数)。
-
learning_rate(默认值 = 0.3):应用于树的步长收缩。
-
reg_lambda(默认值 = 1):应用于叶子权重的 L2 正则化项。
-
gamma(默认值 = 0):分割给定节点所需的最小损失减少。
为了保持一致性,我保持了这些超参数在 XGBoost 库中定义的名称和默认值。
尽管存在上述限制,但提供的实现所获得的结果与 XGBoost 库在多个基准数据集上获得的结果可比(稍后将在文章中演示)。
代码结构
源代码由五个类组成:
-
XGBNode 表示回归树中的一个单独节点。
-
XGBTree 表示集合中的回归树。
-
XGBBaseModel 是 XGBoost 模型的基础估计器。
-
XGBRegressor 是 XGBBaseModel 的一个子类,提供回归任务的估计器。
-
XGBClassifier 是 XGBBaseModel 的一个子类,提供二分类任务的估计器。
为了更好的可读性和易于维护,每个类都写在自己的模块(.py)文件中。
完整的源代码可以在我的 github 上找到:github.com/roiyeho/medium/tree/main/xgboost/xgboost_from_scratch
我们现在将逐步讲解代码的每一部分。
XGBNode 类
这个类表示回归树中的一个节点。它实现了算法中在给定节点找到最佳分割的核心功能。
在类的构造函数中,我们将节点初始化为非叶子节点,并将其左、右子节点的指针设置为 None:
import numpy as np
class XGBNode:
"""A node object that recursively builds itself to construct a regression tree
"""
def __init__(self):
self.is_leaf: bool = False
self.left_child: XGBNode = None
self.right_child: XGBNode = None
build() 方法递归地构建节点,直到达到以下停止标准之一:
-
节点上只剩下一个样本。
-
当前树的层级已达到最大深度。
-
从分割节点中我们可以获得的最佳增益(损失减少)小于 gamma。
def build(self, X, grads, hessians, curr_depth, max_depth, reg_lambda, gamma):
"""Recursively build the node until a stopping criterion is reached
"""
if len(X) == 1 or curr_depth >= max_depth:
# Stopping criterion (1): there is only one sample left at this node
# Stopping criterion (2): max depth of the tree has been reached
self.is_leaf = True
self.weight = self.calc_leaf_weight(grads, hessians, reg_lambda)
return
best_gain, best_split = self.find_best_split(X, grads, hessians, reg_lambda)
if best_gain < gamma:
# Stopping criterion (3): the best gain is less than the minimum split gain
self.is_leaf = True
self.weight = self.calc_leaf_weight(grads, hessians, reg_lambda)
return
else:
# Split the node according to the best split found
feature_idx, threshold, left_samples_idx, right_samples_idx = best_split
self.split_feature_idx = feature_idx
self.split_threshold = threshold
self.left_child = XGBNode()
self.left_child.build(X[left_samples_idx],
grads[left_samples_idx],
hessians[left_samples_idx],
curr_depth + 1,
max_depth, reg_lambda, gamma)
self.right_child = XGBNode()
self.right_child.build(X[right_samples_idx],
grads[right_samples_idx],
hessians[right_samples_idx],
curr_depth + 1,
max_depth, reg_lambda, gamma)
如果已达到某个停止标准,我们将当前节点转换为叶子节点,然后通过调用辅助方法 calc_leaf_weight() 计算其权重(分数):
def calc_leaf_weight(self, grads, hessians, reg_lambda):
"""Calculate the optimal weight of this leaf node (eq.(5) in [1])
"""
return -np.sum(grads) / (np.sum(hessians) + reg_lambda)
另一方面,如果节点可以被分割,我们调用辅助方法 find_best_split(),该方法遍历所有可能的分割并返回增益最高的分割:
def find_best_split(self, X, grads, hessians, reg_lambda):
"""Scan through every feature and find the best split point (Algorithm 1 in [1])
"""
G = np.sum(grads)
H = np.sum(hessians)
best_gain = float('-inf')
best_split = None
# Iterate over all the possible features
for j in range(X.shape[1]):
G_left, H_left = 0, 0
# Sort the samples according to their value in the current feature
sorted_samples_idx = np.argsort(X[:, j])
# Calculate the gain of every possible split point
for i in range(X.shape[0] - 1):
G_left += grads[sorted_samples_idx[i]]
H_left += hessians[sorted_samples_idx[i]]
G_right = G - G_left
H_right = H - H_left
curr_gain = self.calc_split_gain(G, H, G_left, H_left, G_right, H_right, reg_lambda)
if curr_gain > best_gain:
# Update the properties of the best split
best_gain = curr_gain
feature_idx = j
threshold = X[sorted_samples_idx[i]][j]
left_samples_idx = sorted_samples_idx[:i + 1]
right_samples_idx = sorted_samples_idx[i + 1:]
best_split = (feature_idx, threshold, left_samples_idx, right_samples_idx)
return best_gain, best_split
find_best_split() 函数使用以下辅助函数来计算给定分割获得的增益:
def calc_split_gain(self, G, H, G_left, H_left, G_right, H_right, reg_lambda):
"""Compute the loss reduction (eq. (7) in [1])
"""
def calc_term(g, h):
return g**2 / (h + reg_lambda)
gain = calc_term(G_left, H_left) + calc_term(G_right, H_right) - calc_term(G, H)
return 0.5 * gain
最后,predict() 方法根据样本被映射到的叶子节点的权重返回给定样本的分数。它使用树节点中保存的最佳分割阈值来确定沿着树的哪个路径进行。
def predict(self, x):
"""Return the score of a given sample x
"""
if self.is_leaf:
return self.weight
else:
if x[self.split_feature_idx] <= self.split_threshold:
return self.left_child.predict(x)
else:
return self.right_child.predict(x)
XGBTree 类
这个类表示一个单独的回归树。在类的构造函数中,我们将树的根节点初始化为 None:
from xgb_node import XGBNode
class XGBTree:
"""A single tree object that will be used for gradient boosting
"""
def __init__(self):
self.root: XGBNode = None
在 build() 方法中,我们简单地调用根节点的 build() 方法,并将训练算法的超参数传递给它:
def build(self, X, grads, hessians, max_depth, reg_lambda, gamma):
"""Recursively build the root node of the tree
"""
self.root = XGBNode()
curr_depth = 0
self.root.build(X, grads, hessians, curr_depth, max_depth, reg_lambda, gamma)
最后,predict() 方法通过委托调用根节点的 predict() 方法来返回给定样本的评分:
def predict(self, x):
"""Return the score of a given sample x
"""
return self.root.predict(x)
XGBBaseModel 类
现在我们构建 XGBoost 估计器的基类。为了使这个类兼容 Scikit-Learn 估计器 API,我们将其作为 BaseEstimator(来自 sklearn.base)的子类,并实现 fit() 和 predict() 方法。这将允许我们将这个估计器与其他 Scikit-Learn 机制(如管道和网格搜索)集成。
我们首先导入所需的库:
import numpy as np
from sklearn.base import BaseEstimator
from xgb_tree import XGBTree
from typing import List
from abc import ABC, abstractmethod
在类的构造函数中,我们初始化模型的超参数:
class XGBBaseModel(ABC, BaseEstimator):
"""Base class for the XGBoost estimators
"""
def __init__(
self,
n_estimators=100, # The number of trees (boosting rounds)
max_depth=6, # Maximum depth of a tree
learning_rate=0.3, # Step size shrinkage applied to the leaf weights
reg_lambda=1, # L2 regularization term on the leaf weights
gamma=0, # Minimum loss reduction required to split a node
verbose=0 # Verbosity of the log messages (change to 1 for debug mode)
):
self.n_estimators = n_estimators
self.max_depth = max_depth
self.learning_rate = learning_rate
self.reg_lambda = reg_lambda
self.gamma = gamma
self.verbose = verbose
fit() 方法为给定的数据集构建一个 XGBoost 树的集成:
def fit(self, X, y):
"""Build an ensemble of trees for the given training set
"""
# Get the initial prediction of the ensemble
self.base_pred = self.get_base_prediction(y)
self.estimators: List[XGBTree] = []
for i in range(self.n_estimators):
# Compute the first and second order gradients of the loss function with respect
# to the predictions of the current ensemble
out = self.get_output_values(X)
grads = self.calc_gradients(y, out)
hessians = self.calc_hessians(y, out)
# Add a new tree to the ensemble
tree = XGBTree()
tree.build(X, grads, hessians, self.max_depth, self.reg_lambda, self.gamma)
self.estimators.append(tree)
if self.verbose and i % 10 == 0:
print(f'Boosting iteration {i}')
return self
fit() 方法使用一个叫做 get_output_values() 的辅助方法来获取当前集成对给定数据集的预测值。请注意,输出值不一定与模型的最终预测标签相同。例如,在分类任务中,集成的输出值是预测的对数几率,而不是类标签。
def get_output_values(self, X):
"""Return the predicted values of the ensemble for the given data set
"""
# Initialize the output values with the base prediction
output = np.full(X.shape[0], self.base_pred)
# Add the predictions of the base trees scaled by the learning rate
if len(self.estimators) > 0:
for i in range(len(X)):
output[i] += np.sum(self.learning_rate * estimator.predict(X[i])
for estimator in self.estimators)
return output
最后,我们定义了几个需要具体估计器实现的抽象方法:
@abstractmethod
def get_base_prediction(self, y):
"""Return the initial prediction of the model"""
pass
@abstractmethod
def calc_gradients(self, y, out):
"""Calculate the first order gradients"""
pass
@abstractmethod
def calc_hessians(self, y, out):
"""Calculate the second order gradients"""
pass
@abstractmethod
def predict(self, X):
"""Return the final predicted labels for the given samples"""
pass
XGBRegressor 类
这是 XGBBaseModel 的一个子类,用于解决回归任务。要实现这个类,我们首先导入所需的库和函数:
import numpy as np
from xgb_base_model import XGBBaseModel
from sklearn.metrics import r2_score
类的构造函数只是将超参数传递给基估计器的构造函数:
class XGBRegressor(XGBBaseModel):
"""An XGBoost estimator for regression tasks
"""
def __init__(
self,
n_estimators=100,
max_depth=6,
learning_rate=0.3,
reg_lambda=1,
gamma=0,
verbose=0
):
super().__init__(n_estimators, max_depth, learning_rate, reg_lambda, gamma, verbose)
回归器的基预测只是目标值的均值:
def get_base_prediction(self, y):
# The initial prediction is the mean of the targets
return np.mean(y)
平方损失的梯度和赫西矩阵是使用 前一篇文章(参见“XGBoost for Regression”部分)中给出的方程计算的:
def calc_gradients(self, y, out):
# The first order gradients are twice the residuals
grads = 2 * (out - y)
return grads
def calc_hessians(self, y, out):
# The second order gradients are equal to the constant 2
hessians = np.full(len(y), 2)
return hessians
predict() 方法返回来自集成的输出值(在回归任务中,预测标签与集成的输出值相同):
def predict(self, X):
# The predicted labels are the same as the output values
y_pred = self.get_output_values(X)
return y_pred
最后,我们添加一个 score() 方法来返回模型在给定数据集上的 R² 分数:
def score(self, X, y):
y_pred = self.predict(X)
return r2_score(y, y_pred)
回归示例
经过这些艰苦的工作,我们现在准备测试我们的 XGBoostRegressor!
首先,我们将使用 make_regression() 函数从 Scikit-Learn 生成一个合成数据集。该函数的默认设置生成具有 100 个样本和 100 个特征的随机线性回归数据,其中只有 10 个特征是有信息的(即,与目标相关)。
from sklearn.datasets import make_regression
X, y = make_regression(random_state=0)
请注意,像 XGBoost 这样的基于树的模型不需要对特征进行缩放或归一化。
接下来,我们将数据集拆分为 80% 的训练集和 20% 的测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
为了在这个数据集上评估不同的模型,让我们编写一个通用的工具函数来训练给定模型,测量其训练时间,并评估其在训练集和测试集上的表现:
def evaluate_model(name, model, X_train, y_train, X_test, y_test):
print(name)
print('-' * 30)
train_start_time = time.time()
model.fit(X_train, y_train)
elapsed = time.time() - train_start_time
print(f'Training time: {elapsed:.3f} sec')
train_score = model.score(X_train ,y_train)
print(f'R2 score (train): {train_score:.4f}')
test_score = model.score(X_test, y_test)
print(f'R2 score (test): {test_score:.4f}')
让我们开始在数据集上评估我们对 XGBRegressor 的实现。我们将使用类中定义的默认超参数设置。
from xgb_regressor import XGBRegressor
my_xgb_reg = XGBRegressor()
evaluate_model('XGBoost (custom)', my_xgb_reg, X_train, y_train, X_test, y_test)
我们得到的结果是:
XGBoost (custom)
------------------------------
Training time: 14.522 sec
R2 score (train): 1.0000
R2 score (test): 0.3218
我们的模型在训练集上获得了完美的R²分数,但在测试集上得分相对较低。
让我们将其性能与 xgboost 库中的 XGBClassifier 进行比较。首先,通过运行以下命令确保你已安装了该库:
pip install xgboost
我们现在可以从此包中定义的 XGBRegressor 类创建一个实例。由于它的名称与我们的类相同,我们将使用其完全限定名称(包括模块名):
import xgboost
original_xgb_reg = xgboost.XGBRegressor()
evaluate_model('XGBoost (original)', original_xgb_reg, X_train, y_train, X_test, y_test)
我们得到的结果是:
XGBoost (original)
------------------------------
Training time: 1.074 sec
R2 score (train): 1.0000
R2 score (test): 0.3077
令人惊讶的是,我们的模型在R²测试分数上略优于原始的 XGBoost!然而,它的训练时间慢得多(大约慢 14 倍)。这是因为我们没有实现任何运行时优化(而且我们的代码是用 Python 编写的,而不是 C++)。
为了完整性,让我们将这些模型与 Scikit-Learn 提供的经典梯度提升和基于直方图的梯度提升进行比较:
from sklearn.ensemble import GradientBoostingRegressor
gboost_reg = GradientBoostingRegressor(random_state=0)
evaluate_model('Gradient boosting', gboost_reg, X_train, y_train, X_test, y_test)
from sklearn.ensemble import HistGradientBoostingRegressor
hist_gboost_reg = HistGradientBoostingRegressor(random_state=0)
evaluate_model('Histogram-based gradient boosting', hist_gboost_reg, X_train, y_train, X_test, y_test)
Gradient boosting
-----------------------------------
Training time: 0.129 sec
R2 score (train): 1.0000
R2 score (test): 0.4376
Histogram-based gradient boosting
-----------------------------------
Training time: 0.188 sec
R2 score (train): 0.9756
R2 score (test): 0.3984
在这种情况下,最佳测试分数是由经典梯度提升算法获得的。请注意,所有模型都使用了其默认超参数,因此这些结果可能会在超参数调整后发生变化。
让我们也在一个真实世界的数据集上评估我们的实现,即加州住房数据集,它来自 Scikit-Learn。这次我们将通过将所有模型定义在一个列表中,然后在循环中调用评估函数,来使评估代码更加简洁:
from sklearn.datasets import fetch_california_housing
data = fetch_california_housing()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
names = ['XGBoost (custom)', 'XGBoost (original)', 'Gradient boosting', 'Histogram-based gradient boosting',
'Linear regression']
models = [XGBRegressor(),
xgboost.XGBRegressor(),
GradientBoostingRegressor(random_state=0),
HistGradientBoostingRegressor(random_state=0),
LinearRegression()]
for name, model in zip(names, models):
evaluate_model(name, model, X_train, y_train, X_test, y_test)
我们得到以下结果:
XGBoost (custom)
-----------------------------------
Training time: 434.099 sec
R2 score (train): 0.9026
R2 score (test): 0.8253
XGBoost (original)
-----------------------------------
Training time: 1.003 sec
R2 score (train): 0.9396
R2 score (test): 0.8360
Gradient boosting
-----------------------------------
Training time: 2.918 sec
R2 score (train): 0.8027
R2 score (test): 0.7774
Histogram-based gradient boosting
-----------------------------------
Training time: 0.975 sec
R2 score (train): 0.8801
R2 score (test): 0.8381
Linear regression
-----------------------------------
Training time: 0.007 sec
R2 score (train): 0.6089
R2 score (test): 0.5943
我们模型的R²测试分数略低于 XGBoost 库的分数(0.825 对比 0.836),但显著高于经典梯度提升算法的分数(0.777)。正如预期的那样,我们模型的训练时间相比其他模型要长得多。
XGBClassifier 类
这也是 XGBBaseModel 的一个子类,用于解决分类任务(目前只支持二分类)。要实现此类,我们首先导入所需的库和函数:
import numpy as np
from xgb_base_model import XGBBaseModel
from sklearn.metrics import accuracy_score
类的构造函数简单地将超参数传递给基础估计器的构造函数:
class XGBClassifier(XGBBaseModel):
"""An XGBoost estimator for binary classification tasks
"""
def __init__(
self,
n_estimators=100,
max_depth=6,
learning_rate=0.3,
reg_lambda=1,
gamma=0,
verbose=0
):
super().__init__(n_estimators, max_depth, learning_rate, reg_lambda, gamma, verbose)
分类器的基础预测是正类的对数几率:
def get_base_prediction(self, y):
# The initial prediction is the log odds of the positive class
prob = np.sum(y == 1) / len(y)
return np.log(prob / (1 - prob))
为了计算对数损失的梯度,我们需要 sigmoid 函数,因此首先实现它:
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
对数损失的梯度和 Hessian 是使用前一篇文章(见“XGBoost 分类”部分)中给出的方程计算的:
def calc_gradients(self, y, out):
# The first order gradients are the residuals between the predicted probabilities
# (sigmoid of the log odds) and the true labels
prob = self.sigmoid(out)
grads = prob - y
return grads
def calc_hessians(self, y, out):
# The second order gradients are p(1 - p) where p is the predicted probability
prob = self.sigmoid(out)
hessians = prob * (1 - prob)
return hessians
类似于 Scikit-Learn 中的其他概率分类器(如 LogisticRegression),我们的分类器将提供predict_proba() 和 predict() 方法。
方法 predict_proba() 返回给定样本的正类预测概率。这些概率是通过对集成返回的对数赔率进行 sigmoid 计算得到的:
def predict_proba(self, X):
# The predicted probability is the sigmoid of the log odds
log_odds = self.get_output_values(X)
prob = self.sigmoid(log_odds)
return prob
方法 predict() 返回给定数据集中样本的类别标签,使用 0.5 作为将样本分配给正类的阈值:
def predict(self, X):
# Assign a label of 1 if the probability of the positive class is > 0.5,
# otherwise assign a label of 0
prob = self.predict_proba(X)
y_pred = np.where(prob > 0.5, 1, 0)
return y_pred
最后,我们添加了一个score() 方法,返回模型在给定数据集上的准确率:
def score(self, X, y):
y_pred = self.predict(X)
return accuracy_score(y, y_pred)
分类示例
为了评估我们的 XGBoostClassifier,我们将使用 make_classification() 函数来自 Scikit-Learn。该函数在* n 维超立方体的顶点周围创建正态分布点的簇(默认 100 个点),边长由名为class_sep*的超参数定义(默认 1.0)。
首先,我们将使用该函数生成 1,000 个样本点,类分离度为 0.1:
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=1000, class_sep=0.1, random_state=0)
类似于回归示例,我们将编写一个通用实用函数来评估给定模型并测量其训练时间:
def evaluate_model(name, model, X_train, y_train, X_test, y_test):
print(name)
print('-' * 35)
train_start_time = time.time()
model.fit(X_train, y_train)
elapsed = time.time() - train_start_time
print(f'Training time: {elapsed:.3f} sec')
train_score = model.score(X_train ,y_train)
print(f'Accuracy (train): {train_score:.4f}')
test_score = model.score(X_test, y_test)
print(f'Accuracy (test): {test_score:.4f}')
print()
我们将比较以下算法:
names = ['XGBoost (custom)', 'XGBoost (original)', 'Gradient boosting', 'Histogram-based gradient boosting',
'Logistic regression']
models = [XGBClassifier(),
xgboost.XGBClassifier(),
GradientBoostingClassifier(random_state=0),
HistGradientBoostingClassifier(random_state=0),
LogisticRegression()]
for name, model in zip(names, models):
evaluate_model(name, model, X_train, y_train, X_test, y_test)
我们得到的结果是:
XGBoost (custom)
-----------------------------------
Training time: 39.782 sec
Accuracy (train): 1.0000
Accuracy (test): 0.6900
XGBoost (original)
-----------------------------------
Training time: 1.111 sec
Accuracy (train): 1.0000
Accuracy (test): 0.6850
Gradient boosting
-----------------------------------
Training time: 0.409 sec
Accuracy (train): 0.9213
Accuracy (test): 0.6550
Histogram-based gradient boosting
-----------------------------------
Training time: 0.810 sec
Accuracy (train): 1.0000
Accuracy (test): 0.6600
Logistic regression
-----------------------------------
Training time: 0.007 sec
Accuracy (train): 0.5962
Accuracy (test): 0.5300
我们的模型在测试集上达到了最高的准确率!但它的速度比其他模型显著慢(比 XGBoost 库慢约 36 倍)。
练习
提供的实现可以在许多方面进行改进和扩展。例如,您可以尝试向代码库中添加以下功能:
-
添加对多类别分类的支持。
-
添加对分类特征的支持。
-
实现早期停止,即当验证集上的分数在指定的轮数内没有改善时停止提升。
-
添加对子采样的支持,即仅在训练数据的随机子集上训练每棵树。
-
添加额外的超参数,例如min_class_weight,定义在子节点中所需的最小实例权重(Hessian)的总和。
在下一篇文章中,我们将讨论 XGBoost 库实现的各种硬件和软件优化,使其运行如此迅速。
感谢阅读!
参考文献
[1] 陈天石,& 顾斯特林,C.(2016)。XGBoost: 一个可扩展的树提升系统。第 22 届 ACM SIGKDD 国际知识发现与数据挖掘大会论文集,785–794。
加州住房数据集信息:
引用:Pace, R. Kelley 和 Ronald Barry(1997),稀疏空间自回归,统计与概率通讯,33,291–297。
许可证:创作共用 CC0:公有领域。
XGBoost:理论与超参数调优
原文:
towardsdatascience.com/xgboost-theory-and-hyperparameter-tuning-bc4068aba95e
一个包含 Python 示例的完整指南
Jorge Martín Lasaosa
·发表于 Towards Data Science ·17 分钟阅读·2023 年 2 月 16 日
--
图片来源:Joanne Francis 在 Unsplash
介绍
几个月后,我将从事数据科学工作满 3 年。我知道这还不算长的职业生涯,但结合我的学术经验,我已经能够参与多个不同行业的机器学习项目(能源、客户体验等)。所有这些项目都使用了 表格数据,即结构化数据(按行和列组织)。相比之下,使用图像或文本等非结构化数据的项目则更多地与计算机视觉或自然语言处理(NLP)等机器学习领域相关。
根据我的经验,XGBoost 通常 在 表格数据 项目中表现良好。尽管“无免费午餐定理” [1] 表明当将两种算法的表现平均到所有可能的问题上时,它们是等效的,但在 Bojan Tunguz 的 Twitter [2] 上,你可以阅读到与其他专业人士的频繁讨论,关于为何基于树的模型(尤其是 XGBoost)通常是解决 表格数据 项目最佳候选者,即便随着对深度学习技术应用于这种数据的研究不断增加。 [3]
而且,看到一位 Kaggle 大师 [4] 玩笑说自己是 XGBoost 的宣传者也挺有趣的。
Bojan Tunguz 的置顶推文。
尽管 XGBoost 取得了巨大的成功,但在过去当我想从模型中获得最佳性能时,我没有找到集中所有必要知识的完整指南。虽然有很多理论和实践上的解释,我将在阅读中参考这些内容,但我没有找到任何完整的指南提供整体视角。这就是我决定写这篇文章的原因。此外,我将把我在这里收集到的内容应用到一个著名的 Kaggle 比赛中,房价预测 — 高级回归技术。
文章的其余部分分为两个部分:
-
理论: 在简短的介绍后,我们将深入原始论文,以理解这个伟大模型背后的理论。接着,简要的视觉解释将帮助我们更好地理解理论。
-
实践: 在概述 XGBoost 参数后,我将提供一个逐步指南来调整超参数。
除非另有说明,否则所有图片均由作者提供。
理论
XGBoost 代表 eXtreme Gradient Boosting,由 Tianqi Chen 和 Carlos Guestrin 于 2016 年正式发布 [5]。在发布之前,它已经被确立为 Kaggle 比赛中最优秀的算法之一。
尽管深度学习在计算机视觉和自然语言处理等领域取得了巨大成功,XGBoost 和其他基于树的模型(CatBoost [6] 或 LightGBM [7])仍然是预测表格数据 [8] 的最佳选项之一。所有这些基于树的算法都基于梯度提升,如果你想了解这种技术是如何工作的,我建议你查看我关于树集成的文章。[9]
## Tree Ensembles: Bagging, Boosting and Gradient Boosting
理论与实践详细解释
towardsdatascience.com
你想知道 XGBoost 有什么特别之处吗?我将通过两个不同的子部分来解释:原始论文和视觉解释。让我们开始吧!
原始论文
如前所述,XGBoost 基于梯度提升,因此多个树是依次在前一棵树的残差上进行训练的。然而,有一些小的改进使得 XGBoost 能够通过防止过拟合超越现有的树集成模型:
-
正则化学习目标(类似于RGF [10])。在梯度提升中,每棵树都以最佳方式训练,以实现学习目标:减少预测与目标之间的差异。在 XGBoost 中,这个学习目标被一个正则化学习目标取代,该目标在差异计算中添加了一个正则化项。用简单的话说,这个项增加了每棵树学习时的噪音,并旨在减少预测对单个观察值的敏感性。如果将正则化项设置为零,则目标会回到传统的梯度提升。
-
收缩(借鉴自随机森林 [11])。一种限制每棵训练树在最终预测中权重的技术。因此,每棵树的影响被减少,未来的树有更多空间来改善预测。这类似于学习率(在参数部分也如此指定)。
-
列子采样(借鉴自随机森林 [11])。它允许为每棵树、树级别和/或树节点随机选择一个特征子样本。因此,对第一棵树/级别/节点非常重要的特征,可能在第二棵树中不可用,从而推动使用其他特征。
树学习中的最大问题之一是找到最佳分裂。如果有一个从 0 到 100 变化的连续特征,那么应该使用什么值来进行分裂?20?32.5?70?为了找到最佳分裂,XGBoost 可以应用不同的算法:
-
精确贪心算法。这是旧版树提升实现中最常用的算法,它包括测试所有特征的所有可能分裂。对连续特征进行这种操作计算量大,因此随着数据样本的增加,需要更多时间。
-
加权分位数草图算法:根据特征分布的百分位数提出候选分裂点。该算法将连续特征映射到这些候选点划分的桶中,聚合统计数据,并根据聚合统计数据在提议中找到最佳解决方案。此外,它可以处理加权数据,并在每个树节点中包含一个默认方向,使算法能够识别数据中的稀疏模式。稀疏性可能由于缺失值的存在、频繁的零条目或特征工程的后果(例如使用独热编码)造成。
此外,该算法设计用于高效地与系统交互。无需详细说明,我可以指出数据存储在内存中的称为块的单元中,以帮助算法对数据进行排序。这种技术允许并行化。此外,在大数据集的情况下,定义了缓存感知访问,以避免在某些计算无法适应 CPU 缓存时搜索分裂的速度变慢。最后,它使用块压缩和块碎片化来处理不适合主内存的数据。
视觉解释
本节高度受到Josh Starmer 的 YouTube 频道和 Shreya Rao 的文章的启发。我的目标是通过一个小示例解释 XGBoost 的工作原理,并将其与之前看到的论文理论联系起来。为此,让我们按照以下步骤进行。
步骤 1:创建合成数据
使用了一个关于房价的小型合成数据集。它有两个特征(平方米数和是否有车库?)和一个目标(价格)。
用于视觉解释的关于房价的合成数据。
步骤 2:计算残差
如你所见,残差已经在上表中计算出来。计算方法很简单,你只需将前一个树的预测价格从实际价格中减去(记住,XGBoost 会顺序训练多个树)。然而,这还是第一棵树,所以我们没有预测价格。在这种情况下,计算了价格的平均值。
计算第一个残差。
步骤 3:构建 XGBoost 的第一棵树
第一棵树将用所有的残差作为目标进行训练。因此,首先需要计算所有残差的相似性得分。这是树分裂旨在增加的得分。
计算第一个树的相似性得分。
你看到那个 lambda (λ) 字符了吗?如论文理论部分所述,这是一个正则化项,通过添加噪声来帮助防止过拟合。因此,XGBoost 的学习目标(增强相似性得分)实际上是一个正则化学习目标。XGBoost 中正则化项的默认值是 1。现在,让我们看看特征是否有车库?对相似性得分和增益的影响。
计算了带有车库特征的分裂的相似性得分。
车库分裂的最终增益。
一旦计算了相似性得分,是否有车库?的特征增益为 7448.58。如果最终增益为正,则这是一个好的分裂,否则不是。由于它是正的,我们可以得出这是一个好的分裂。
对于像平方米这样的连续特征,过程略有不同。首先,我们需要找到连续特征的最佳分裂。为此,将使用论文理论部分中解释的精确贪婪算法。也就是说,将测试每一个可能的分裂。
对特征进行排序并找到所有可能的分裂(贪婪)。
如上图所示的排序在数据集很大的时候可能会计算成本高。正如论文理论部分评论的那样,XGBoost 使用块单元来允许并行化,并帮助解决这个问题。同时,请记住,XGBoost 可以使用加权分位数草图算法根据特征分布的百分位数来提议候选分裂点。这里不会详细解释,但这是 XGBoost 的主要优势之一。话虽如此,我们将计算每个可能分裂的相似度得分和增益。
每个平方米分裂的增益计算。
平方米特征通过 175 的分裂具有最大的增益(甚至比是否有车库?的分裂还要大),因此它应该是第一个分裂。右叶子中只剩下一个残差(156)。所以我们来关注左叶子,寻找另一个分裂。经过所有计算,最佳的分裂再次使用平方米特征。
最佳第二次分裂在车库特征上。
经过新计算,右叶子可以再次分裂。
完成所有可能分裂的最终树。
步骤 4:剪枝树
为了避免过拟合,还有一个叫做树剪枝的过程是 XGBoost 执行的。从下往上验证每个增益。怎么做?如果一个叫做gamma(γ)的 XGBoost 参数大于增益,则会移除分裂。XGBoost 中γ的默认值是 0,但设置为 200 以表示移除分裂的情况。为什么?因为在这种情况下,200(γ)大于最后的增益(198)。因此,树被剪枝,最终结果是:
剪枝后的第一棵树的最终版本。
步骤 5:使用树进行预测
为了进行预测,第一步是每个最终叶子中都有一个单一的值(输出值)。为此,我们使用以下公式:
计算输出值(每个叶子的值)。
结果是:
每个叶子都有一个输出值的第一棵树。
最终,最终叶子的输出值可以用下面显示的公式进行新预测。i 是我们想要预测的观察值,prediction_t0 是第一次预测(观察价格的均值),ɛ 是学习率,leaf_i 是按照树的规则得到的观察值 i 的值。
请注意,学习率(ɛ),其默认值为 0.3,是收缩理论部分的解释。话虽如此,让我们预测所有观察值:
每个值的预测。
现在,我们使用预测来计算新的残差(Residuals_1):
第一棵树的残差。
正如我们在上面看到的,每个残差(除了第一个)都更接近零。这意味着第一棵树提供的信息改善了第一次预测(均值)。下一步是创建另一棵树,使残差减少的方向再进一步。
步骤 6:训练新树
如果我们想创建一棵新树,唯一需要做的就是用新的残差(Residuals_1)作为目标重新执行步骤 3-5。然后,最终预测将是:
使用两棵树计算最终预测。
XGBoost 可以通过这些步骤顺序训练树。就我个人而言,我发现视觉解释是理解模型及其理论的有效方法。我希望这对你也有帮助。不过,现在是时候进入实际部分了。
实践
首先,我想强调的是,尽管我不是 XGBoost 实践方面的专家,但我使用了一些证明成功的技术。因此,我认为我可以提供一个简要指南,帮助你调整超参数。
为了实现这一目标,必须将实践分为两个子部分。第一个部分将专注于定义算法的超参数并将其编译成一个简明的备忘单。第二个子部分称为实际操作,将提供一个逐步指南,带你完成 XGBoost 的训练过程。
超参数
所有在这里解释的信息都可以在官方 XGBoost 文档 [12] 中找到。一个通用的参数被称为booster。正如我在之前的文章 [9] 中解释的那样,梯度提升是一种不限于使用决策树的技术,它可以应用于任何模型。这就是为什么 XGBoost 接受booster参数的三种值:
-
gbtree:使用决策树的梯度提升(默认值)
-
dart:一种使用 Vinayak 和 Gilad-Bachrach(2015)[13] 提出的将深度神经网络社区中的 dropout 技术添加到提升树中的方法的决策树梯度提升。
-
gblinear:使用线性函数的梯度提升。
虽然gblinear是捕捉预测变量和结果之间线性关系的最佳选择,但基于决策树的提升器(gbtree和dart)在捕捉非线性关系方面要好得多。由于gbtree是最常用的值,本文余下部分将使用它。如果你有兴趣使用其他方法,请查看文档[12],因为根据你的选择,参数会有所不同。
一旦我们将gbtree设置为booster,我们可以调整多个参数以获得最佳模型。这里解释了最重要的几个:
-
eta(又名学习率):在视觉解释部分显示为ɛ,它限制每棵训练树在最终预测中的权重,以使提升过程更具保守性。
-
gamma: 在视觉解释部分显示为γ,它标记了在树的叶节点上进行进一步划分所需的最小增益。
-
max_depth: 设置树的最大深度。
-
n_estimators: 训练的树的数量。
-
min_child_weight: 设置进行拆分时子节点所需的最小实例权重(残差之和)。
-
subsample: 设置在训练每棵树之前获得的样本百分比(随机)。
-
colsample_by[]: 一组用于列抽样的参数。抽样可以在每棵树(colsample_bytree)、每棵树中达到的深度级别(colsample_bylevel)或每次评估新拆分时(colsample_bynode)进行。注意,这些参数可以同时工作:如果每个参数的值为 0.5,并且你有 32 列,那么每个拆分将使用 4 列(32/ 2³)。
-
lambda(L2 正则化):在视觉解释中显示为λ。它通过增加分母平滑地减少输出值(视觉解释中的第 5 步)。[14]
-
alpha(L1 正则化):它通过强制输出值为 0 来减少输出值并促进稀疏性。[14]
-
tree_method: 设置树用于查找拆分的构建算法。正如论文理论部分所讨论的,可以使用精确或近似算法。在实践中,这个参数有 5 个可能的值:auto 让启发式算法从下列选项中选择最快的选项,exact 应用枚举所有拆分候选项的精确贪婪算法,approx 应用使用分位数草图和梯度直方图的近似贪婪算法,hist 使用近似算法的直方图优化版本,gpu_hist 使用hist的 GPU 实现。
-
scale_pos_weight: 它控制正负权重的平衡,这对于类别不平衡的情况非常有用。一个典型的值是sum(negative instances) / sum(positive instances)。
-
max_leaves: 设置仅在未选择tree_method参数的exact值时要添加的最大节点数。
还有更多参数需要调整:updater、refresh_leaf、process_type、grow_policy、max_bin、predictor、num_parallel_tree、monotone_constraints或interaction_constraints。然而,前面的参数已经足以充分发挥 XGBoost 模型的作用,因此这些参数得到了说明。
话虽如此,调整解释的特征如何影响模型?下面显示的备忘单帮助我们理解以不同方式调整每个参数的效果。此外,它展示了特定调整如何影响结果,无论是理解方差和偏差如何改善还是恶化。
主要参数及其效果的备忘单。
免责声明:减少或增加值意味着从特定特征的完美偏差-方差平衡出发,而不是从默认值开始。此外,参数之间有很多相互作用,因此减少某些值并增加其他值可能会导致与表中解释的不同结果。
实践实施
对于实施,我们将使用两个资源。首先,我们将使用 Kaggle 竞赛中数据,该竞赛名为房价[15]。其次,由于本文的重点是 XGBoost,而不是机器学习项目中的其他任务,我们将借用 Nanashi 的笔记本[16]中的代码来进行数据处理。
尽管你将在阅读过程中看到最相关的代码片段,但所有代码可以在我专门为 Medium 文章创建的GitHub 仓库中查看。
如前所述,我并不是 XGBoost 训练方面的专家,所以我鼓励你在评论区分享你的技巧和方法,甚至批评这种训练方式,如果你真的认为它有问题的话。然而,根据我的经验,遵循以下步骤是迭代改进模型的好方法。
步骤 0:数据读取和处理
如前所述,数据已经读取和处理。它被分为两部分:
-
训练集(数据的 85%),包含 X_train 和 y_train。步骤 1–4 将使用此数据集来构建最佳的 XGBoost 模型。在构建过程中,将讨论平均测试得分,需要说明的是,这个“测试”得分实际上并不对应于测试集,而是对应于在交叉验证过程中生成的验证测试。
-
测试集(数据的 15%),包含 X_test 和 y_test。它通常被称为保留集。当我们得到最佳的 XGBoost 模型时,我们应该检查模型在测试集上的表现是否与在训练集上的表现一致。
读取和处理来自房价 Kaggle 竞赛的数据的代码。
如果你想查看处理细节,请查看 GitHub 仓库。
步骤 1:创建基准
第一步是创建基准。为此,创建一个简单的 XGBoost 模型而不调整任何参数。为了更好地比较结果,使用了 5 折交叉验证[17]。所用的度量标准是平均绝对误差(MAE)。
创建 XGBoost 基准的代码
这给我们提供了 0.0961 的平均测试分数和 0.0005 的主要训练分数。看起来偏差较低但方差较高,这可以解释为存在过拟合。然而,基准只是一个参考,让我们继续进行,看看第二步接下来会有什么。
第 2 步:使用 GridSearchCV 改进基准
现在,我们将使用 GridSearchCV [18] 来搜索参数网格中的良好参数组合。在第一次尝试中,我们将使用接近 XGBoost 默认值的参数值:
使用 GridSearchCV 和 XGBoost 的代码
最佳组合是:
GridSearchCV 找到的最佳参数组合。
获得的最佳测试平均值为 0.0840。这比第一个模型获得的 0.0961 稍好,因此现在将其作为改进的指标。
第 3 步:单独调整参数
一旦我们超越了之前的基准分数,我们可以尝试了解如何单独调整参数影响结果。使用提供的代码,可以一次测试多个参数。例如,我们来看看这些参数值如何影响结果:
- n_estimators: [125, 150, 175, 200, 225]
每个可能的值都用于交叉验证训练。返回的结果被编译并显示在下面的图中。
单独调整参数和绘制分数的代码。
绘制估计器数量变化如何影响结果。
图表显示,如果 n_estimators 较高,平均测试分数可能会降低。然而,平均训练分数接近零,所以过拟合即将到来。
第 4 步:重复第 2 步和第 3 步。
利用第 3 步中收集的信息,我们可以重新定义 GridSearchCV 中的参数网格,尝试获得更好的模型。在这种情况下,测试了更高的 n_estimators。这意味着模型的复杂度将更高,因此在参数网格中,我们还包括了一些可以帮助避免过拟合的参数值(较低的 learning_rate、更高的 lambda、更高的 gamma……)。超参数部分定义的备忘单在这里可能会非常有用。
第二次 GridSearchCV 与 XGBoost 的代码。
使用这段代码,我们获得了新的最佳平均测试分数 0.0814(之前为 0.0840)。
第二次 GridSearchCV 找到的最佳参数组合。
第 2 步至第 3 步应根据需要重复进行,以改进模型。
第 5 步:使用测试集验证模型。
最后,我们需要使用步骤 0 中的测试集(留出测试,无验证测试)来验证所构建的模型。
计算留出预测 MAE 的代码。
获得的 MAE 为 0.0808。由于低于我们最好的 MAE(0.0814),我们可以说我们的模型泛化良好,并且已经得到了很好的训练。
结论
在本文中,我旨在提供一个使用 XGBoost 的全面指南。经过数小时对论文、指南、帖子和文章的研究,我相信我已完成一篇完整的文章,可以帮助全面理解和使用 XGBoost。
希望你觉得阅读有用且愉快。最重要的是,我很高兴收到任何形式的反馈。请随时分享你的想法!
参考文献
[1] Wolpert, D. H., & Macready, W. G. «优化的无免费午餐定理». IEEE Transactions on Evolutionary Computation (1997). ieeexplore.ieee.org/abstract/document/585893
[2] Bojan Tunguz Twitter 账户。 twitter.com/tunguz
[3] Raschka, Sebastian (2022). 针对表格数据的深度学习。 sebastianraschka.com/blog/2022/deep-learning-for-tabular-data.html
[4] Bojan Tunguz Kaggle 个人资料。 www.kaggle.com/tunguz
[5] Chen, T., & Guestrin, C. (2016 年 8 月). Xgboost: 一个可扩展的树提升系统。在 第 22 届 ACM SIGKDD 国际知识发现与数据挖掘大会论文集(第 785–794 页)。
[6] Dorogush, A.V.; Ershov, V.; Gulin, A. CatBoost: 支持类别特征的梯度提升». ArXiv:1810.11363, 24 (2018). arxiv.org/abs/1810.11363
.
[7] Ke, G.; Meng, Q.; Finley, T; Wang, T; Chen, W; Ma, W; Ye, Q; Liu, T. «LightGBM: 一种高效的梯度提升决策树». 神经信息处理系统进展, 20 (2017). proceedings.neurips.cc/paper/2017/hash/6449f44a102fde848669bdd9eb6b76fa-Abstract.html
.
[8] Tunguz, Bojan. twitter.com/tunguz/status/1620048813686923266?s=20&t=BxzKnvn7G0ieo1I7SfDnSQ
[9] Martín Lasaosa, Jorge. «树集成:Bagging、Boosting 和梯度提升». 在 Towards Data Science (Medium)。 (2022) medium.com/r/?url=https%3A%2F%2Ftowardsdatascience.com%2Ftree-ensembles-theory-and-practice-1cf9eb27781
[10] T. Zhang 和 R. Johnson. 使用正则化贪婪森林学习非线性函数。IEEE 计算机学会模式分析与机器智能汇刊,36(5),2014. ieeexplore.ieee.org/abstract/document/6583153
[11] Breiman, L. «随机森林». 机器学习 45, (2001): 5–32. doi.org/10.1023/A:1010933404324
.
[12] XGBoost 文档(参数部分) xgboost.readthedocs.io/en/stable/parameter.html
[13] Vinayak, R. K.; Gilad-Bachrach, R. «Dart: Dropouts 遇见多重加法回归树». 收录于 人工智能与统计. PMLR. (2015) proceedings.mlr.press/v38/korlakaivinayak15.html
[14] Um, Albert. «XGBoost 回归中的 L1、L2 正则化» 收录于 Medium (2021) albertum.medium.com/l1-l2-regularization-in-xgboost-regression-7b2db08a59e0
[15] 房价 Kaggle 竞赛。 www.kaggle.com/competitions/house-prices-advanced-regression-techniques
[16] Nanashi 在 Kaggle 上的笔记本 www.kaggle.com/code/jesucristo/1-house-prices-solution-top-1
[17] Yiu, Tony. «理解交叉验证» 收录于 Towards Data Science (Medium) (2020) towardsdatascience.com/understanding-cross-validation-419dbd47e9bd
[18] «使用 GridSearchCV 调优超参数» 收录于 Great Learning (2022) www.mygreatlearning.com/blog/gridsearchcv/
YOLO-NAS:如何在目标检测任务中实现最佳性能
原文:
towardsdatascience.com/yolo-nas-how-to-achieve-the-best-performance-on-object-detection-tasks-6b95347908d4
通过神经网络结构搜索、创新的量化块和强大的预训练范式生成的基础模型
Thomas A Dorfer
·发布于数据科学前沿 ·阅读时间 7 分钟·2023 年 5 月 19 日
--
图片由Anubhav Saxena提供,Unsplash上可见。由作者使用 YOLO-NAS-L 处理。
在目标检测领域,YOLO(你只需看一次)已成为家喻户晓的名字。自 2015 年发布第一个模型以来,YOLO 家族一直在稳步增长,每个新模型在平均精度(mAP)和推理延迟上均超越了其前身。
两周前,YOLO 家族迎来了新成员:YOLO-NAS,这是由深度学习公司Deci开发的一种新型基础模型。
在本文中,我们将探讨它相对于之前 YOLO 模型的优势,并展示如何将其用于你自己的目标检测任务。
YOLO-NAS:新变化是什么?
尽管之前的 YOLO 模型在目标检测方面在创新和性能上领先,但它们也存在一些局限性。主要问题之一是缺乏适当的量化支持,这旨在减少模型的内存和计算需求。另一个问题是精度和延迟之间的权衡不足,其中一个的改进往往会导致另一个的显著下降。
通过利用一种称为神经网络结构搜索(NAS)的概念,Deci 的研究人员直接解决了这些局限性。实质上,NAS 的概念可以被视为对训练好的深度学习模型的一次改造。
传统上,神经网络架构由人类专家根据经验和直觉进行手动设计。然而,这一过程涉及探索可能架构的广阔设计空间,始终非常耗时且繁琐。
NAS,另一方面,自动重新设计模型架构,以提升其在速度、内存使用和吞吐量等方面的性能。它通常涉及一个定义可能架构选择的搜索空间,例如层数、层类型、卷积核大小和连接模式。搜索算法然后通过在给定任务和数据集上训练和评估不同架构来评估它们。基于这些评估,算法迭代地探索和完善架构空间,最终返回最佳性能的架构。
图片来源:Google DeepMind 在 Unsplash
为了执行 NAS,Deci 利用了其专有的AutoNAC技术,这是一种优化引擎,可以重新设计模型的架构,以最大限度地提高特定硬件的推理性能,同时保持准确性。
除了 NAS,这个新的 YOLO 成员的另一个重大改进涉及量化的使用。在这种情况下,量化指的是将神经网络的权重、偏置和激活从浮点值转换为整数值(INT-8),从而使模型更加高效。
这项工作有两个方面:(1) 该模型使用了适合量化的块,这些块结合了重新参数化和 INT-8 量化的优点。这些块采用了Chu 等人(2022)提出的方法,重新设计这些块,使其生成的权重和激活分布有利于量化。(2) 作者采用了一种混合量化方法,有选择地量化模型的特定层,从而最小化信息丢失,在延迟和准确性之间取得平衡。
这种新方法的结果不言而喻。如下图所示,量化后的中型模型YOLO-NAS-INT8-M在推理延迟方面提高了 50%,同时与最新的最先进模型相比,准确性提高了 1 mAP。
来源: Deci-AI。许可证: Apache License 2.0
在撰写时,已经发布了 YOLO-NAS 的三种模型:小型、中型和大型,每种模型都有一个量化的 INT-8 版本。
来源: Deci-AI。许可证: Apache License 2.0
毫不奇怪,量化版本的精度略有下降。然而,由于采用了这些新颖的量化友好型块以及选择性量化,这种精度下降仍然相对较小。此外,这里的优势远大于劣势,推理延迟显著改善。
YOLO-NAS 还在 COCO、Objects365 和 Roboflow 100 数据集上进行预训练,这使得它非常适合下游的物体检测任务。
预训练过程利用了知识蒸馏的概念,这使得模型可以从自身的预测中学习,而不仅仅依赖于外部标记数据,从而提高性能。在这种范式中,教师模型对训练数据生成预测,这些预测作为学生模型的指导(或软目标)。学生模型使用原始标记数据和教师模型生成的软目标进行训练。它基本上尝试模仿教师模型的预测,同时调整其参数以匹配原始标记数据。总体而言,这种方法使模型能够更好地泛化,减少过拟合,并实现更高的准确性,特别是当标记数据不丰富时。
训练过程通过加入分布焦点损失(DFL)进一步增强。DFL 是一种损失函数,扩展了焦点损失的概念,通过对难以分类的样本分配更高的权重来解决类别不平衡的问题。在物体检测的背景下,DFL 在训练过程中将框回归作为分类任务进行学习。它将边界框预测离散化为有限的选项,并对这些选项进行分布预测。最终的预测通过加权求和将这些分布结合起来。通过考虑类别分布并相应地调整损失函数,模型能够提高对欠代表类别的检测准确性。
最终,YOLO-NAS 已经以开源许可证发布,并且在 Deci 的基于 PyTorch 的计算机视觉库 SuperGradients 上提供了预训练权重供研究使用。
如何使用 YOLO-NAS
为了使用 YOLO-NAS 进行推理,我们需要首先安装 super_gradients
包:
pip install super-gradients
为了准备推理任务,让我们取一张样本图像,我们称之为 image.jpg
:
krakenimages 的照片,来源于 Unsplash
为了进行推断,我们可以使用以下代码片段:
import torch
from super_gradients.training import models
device = torch.device("cuda:0") if torch.cuda.is_available() else torch.device("cpu")
yolo_nas_s = models.get("yolo_nas_s", pretrained_weights="coco").to(device)
out = yolo_nas.predict("image.jpg")
out.save("image_yolo.jpg")
首先,我们需要从 super_gradients
库中导入 torch
和 models
。然后,我们声明一个变量 device
,设置为使用第一个可用的 GPU(如果有的话),否则设置为使用 CPU。
随后,我们指定使用模型的小型版本 YOLO-NAS-S,并使用 COCO 数据集的预训练权重。此外,我们将检测到的物体保存到 image_yolo.jpg
中。
我们的输出图像如下所示:
krakenimages 的照片,来源于 Unsplash。由作者使用 YOLO-NAS-S 处理。
我们可以看到各种物体在不同的置信度水平下被检测到。对于聚焦中的物体(如两个人、杯子和笔记本电脑),模型的置信度较高。然而,我们也观察到一些误分类,可能是由于物体失焦。这包括一个笔筒被误分类为盆栽植物,以及一支笔被误分类为牙刷。令人惊讶的是,我们还可以看到模型准确地检测到了仅部分可见的物体,如人物坐的椅子,只有靠背部分可见。
最后,值得一提的是,通过简单地将 predict()
调用的输入参数更改为相应的视频文件,可以以完全相同的方式进行视频目标检测。
结论
YOLO 系列又迎来了一员新成员——YOLO-NAS,它自豪地超越了 YOLOv6、YOLOv7 和 YOLOv8 等年轻兄弟。
通过神经架构搜索、量化支持以及包含知识蒸馏和分布式焦点损失的强大预训练程序的创新组合,YOLO-NAS 实现了精度和推理延迟之间的显著权衡。
考虑到计算机视觉和目标检测领域的快速发展,另一款 YOLO 模型很可能很快会面世。
更多资源
-
Deci 的 YOLO-NAS 使用神经架构搜索在目标检测上达到了最先进的性能
-
super-gradients/YOLONAS.md at master · Deci-AI/super-gradients · GitHub
喜欢这篇文章吗?
让我们联系吧!你可以在 Twitter、LinkedIn 和 Substack 找到我。
如果你想支持我的写作,可以通过Medium 会员来做到,这将使你可以访问我所有的故事以及 Medium 上成千上万其他作家的故事。
[## 通过我的推荐链接加入 Medium - 托马斯·A·多费尔
阅读托马斯·A·多费尔(Thomas A Dorfer)以及成千上万其他作者在 Medium 上的每个故事。你的会员费直接支持…
medium.com
Raspberry Pi 上的 YOLO 目标检测
原文:
towardsdatascience.com/yolo-object-detection-on-the-raspberry-pi-6de3629256fa
在低功耗设备上运行目标检测模型
Dmitrii Eliuseev
·发布于 Towards Data Science ·阅读时长 9 分钟·2023 年 7 月 11 日
--
YOLO 目标检测结果,作者提供的图片
在这篇文章的第一部分中,我测试了 YOLO(You Only Look Once)的“复古”版本,这是一个流行的目标检测库。仅使用 OpenCV 运行深度学习模型,而不使用 PyTorch 或 Keras 等“重型”框架,对低功耗设备来说是很有前景的,我决定深入探讨这个话题,看看最新的 YOLO v8 模型在 Raspberry Pi 上的表现如何。
让我们深入了解一下。
硬件
在云端运行任何模型通常没有问题,因为资源几乎是无限的。但对于“现场”的硬件,限制就多得多。有限的 RAM、CPU 功率,甚至不同的 CPU 架构,较旧或不兼容的软件版本,缺乏高速互联网连接等等。云基础设施的另一个大问题是其成本。假设我们正在制作一个智能门铃,并且我们想添加人脸检测功能。我们可以在云端运行模型,但每次 API 调用都需要付费,那么谁来支付呢?并不是每个客户都愿意为门铃或任何类似的“智能”设备支付月费,因此在本地运行模型可能是必要的,即使结果可能不是最佳的。
在这个测试中,我将会在 Raspberry Pi 上运行 YOLO v8 模型:
Raspberry Pi 4,图片来源 en.wikipedia.org/wiki/Raspberry_Pi
Raspberry Pi 是一种便宜的信用卡大小的单板计算机,运行 Raspbian 或 Ubuntu Linux。我将测试两个不同的版本:
-
2015 年制造的 Raspberry Pi 3 Model B。它有一个 1.2 GHz Cortex-A53 ARM CPU 和 1 GB 的 RAM。
-
Raspberry Pi 4,制造于 2019 年。它有一个 1.8 GHz Cortex-A72 ARM CPU 和 1、4 或 8 GB 的 RAM。
目前,Raspberry Pi 计算机被广泛使用,不仅用于爱好和 DIY 项目,还用于嵌入式工业应用(Raspberry Pi Compute Module 专门为此设计)。因此,了解这些板子如何处理诸如物体检测这样的计算密集型操作是很有趣的。在所有进一步的测试中,我将使用这张图像:
测试图像,由作者制作
现在,让我们看看它是如何工作的。
“标准” YOLO v8 版本
作为热身,让我们尝试标准版本,如在官方 GitHub 页面上描述的:
from ultralytics import YOLO
import cv2
import time
model = YOLO('yolov8n.pt')
img = cv2.imread('test.jpg')
# First run to 'warm-up' the model
model.predict(source=img, save=False, save_txt=False, conf=0.5, verbose=False)
# Second run
t_start = time.monotonic()
results = model.predict(source=img, save=False, save_txt=False, conf=0.5, verbose=False)
dt = time.monotonic() - t_start
print("dT:", dt)
# Show results
boxes = results[0].boxes
names = model.names
confidence, class_ids = boxes.conf, boxes.cls.int()
rects = boxes.xyxy.int()
for ind in range(boxes.shape[0]):
print("Rect:", names[class_ids[ind].item()], confidence[ind].item(), rects[ind].tolist())
在“生产”系统中,可以通过摄像头获取图像;对于我们的测试,我使用了之前描述的“test.jpg”文件。我还执行了“predict”方法两次,以使时间估计更准确(第一次运行通常需要更多时间来“热身”和分配所有所需的内存)。Raspberry Pi 在没有显示器的“无头”模式下工作,因此我使用控制台作为输出;这是大多数嵌入式系统的标准工作方式。
在Raspberry Pi 3 上运行 32 位操作系统时,此版本无法使用:pip 无法安装“ultralytics”模块,原因如下错误:
ERROR: Cannot install ultralytics
The conflict is caused by:
ultralytics 8.0.124 depends on torch>=1.7.0
结果发现 PyTorch 仅适用于 ARM 64 位操作系统。
在Raspberry Pi 4 上运行 64 位操作系统时,代码确实可以运行,计算时间约为 0.9 秒。
控制台输出如下:
我还在桌面 PC 上进行了相同的实验以可视化结果。
YOLO v8 Nano 检测结果,由作者提供的图像
正如我们所见,即使是“nano”尺寸的模型,结果也相当不错。
Python ONNX 版本
ONNX (开放神经网络交换) 是一种用于表示机器学习模型的开放格式。它也得到 OpenCV 的支持,因此我们可以轻松地以这种方式运行我们的模型。YOLO 开发人员已经提供了一个 命令行工具 来进行这种转换:
yolo export model=yolov8n.pt imgsz=640 format=onnx opset=12
这里,“yolov8n.pt”是一个 PyTorch 模型文件,将被转换。文件名中的最后一个字母“n”表示“nano”。提供了不同的模型(“n”——nano,“s”——small,“m”——medium,“l”——large),显然,对于 Raspberry Pi,我将使用最小且最快的一个。
转换可以在桌面 PC 上完成,并且可以使用“scp”命令将模型复制到 Raspberry Pi 上:
scp yolov8n.onnx pi@raspberrypi:/home/pi/Documents/YOLO
现在我们准备好准备源代码了。我使用了 Ultralytics 仓库 中的一个示例,并对其进行了稍微的修改以在 Raspberry Pi 上运行:
import cv2
import time
model: cv2.dnn.Net = cv2.dnn.readNetFromONNX("yolov8n.onnx")
names = "person;bicycle;car;motorbike;aeroplane;bus;train;truck;boat;traffic light;fire hydrant;stop sign;parking meter;bench;bird;" \
"cat;dog;horse;sheep;cow;elephant;bear;zebra;giraffe;backpack;umbrella;handbag;tie;suitcase;frisbee;skis;snowboard;sports ball;kite;" \
"baseball bat;baseball glove;skateboard;surfboard;tennis racket;bottle;wine glass;cup;fork;knife;spoon;bowl;banana;apple;sandwich;" \
"orange;broccoli;carrot;hot dog;pizza;donut;cake;chair;sofa;pottedplant;bed;diningtable;toilet;tvmonitor;laptop;mouse;remote;keyboard;" \
"cell phone;microwave;oven;toaster;sink;refrigerator;book;clock;vase;scissors;teddy bear;hair dryer;toothbrush".split(";")
img = cv2.imread('test.jpg')
height, width, _ = img.shape
length = max((height, width))
image = np.zeros((length, length, 3), np.uint8)
image[0:height, 0:width] = img
scale = length / 640
# First run to 'warm-up' the model
blob = cv2.dnn.blobFromImage(image, scalefactor=1 / 255, size=(640, 640), swapRB=True)
model.setInput(blob)
model.forward()
# Second run
t1 = time.monotonic()
blob = cv2.dnn.blobFromImage(image, scalefactor=1 / 255, size=(640, 640), swapRB=True)
model.setInput(blob)
outputs = model.forward()
print("dT:", time.monotonic() - t1)
# Show results
outputs = np.array([cv2.transpose(outputs[0])])
rows = outputs.shape[1]
boxes = []
scores = []
class_ids = []
output = outputs[0]
for i in range(rows):
classes_scores = output[i][4:]
minScore, maxScore, minClassLoc, (x, maxClassIndex) = cv2.minMaxLoc(classes_scores)
if maxScore >= 0.25:
box = [output[i][0] - 0.5 * output[i][2], output[i][1] - 0.5 * output[i][3],
output[i][2], output[i][3]]
boxes.append(box)
scores.append(maxScore)
class_ids.append(maxClassIndex)
result_boxes = cv2.dnn.NMSBoxes(boxes, scores, 0.25, 0.45, 0.5)
for index in result_boxes:
box = boxes[index]
box_out = [round(box[0]*scale), round(box[1]*scale),
round((box[0] + box[2])*scale), round((box[1] + box[3])*scale)]
print("Rect:", names[class_ids[index]], scores[index], box_out)
正如我们所见,我们不再使用 PyTorch 和原始 Ultralytics 库了,但所需的代码量增加了。我们需要将图像转换为 blob,这对于 YOLO 模型是必需的。在打印结果之前,我们还需要将输出的矩形转换回原始坐标。但作为一个优点,这段代码在“纯” OpenCV 上运行,不依赖任何额外的库。
在Raspberry Pi 3上,计算时间为 28 秒。为了好玩,我还加载了“medium”模型(这是一个 101 MB 的 ONNX 文件!)看看会发生什么。令人惊讶的是,应用程序没有崩溃,但计算时间为 224 秒(几乎 4 分钟)。显然,2015 年的硬件不适合运行 2023 年的 SOTA 模型,但看到它如何工作的过程还是很有趣的。
在Raspberry Pi 4上,计算时间为 1.08 秒。
C++ ONNX 版本
最后,让我们尝试使用工具集中最强大的武器,并用 C++ 编写相同的代码。但在此之前,我们需要为 C++ 安装 OpenCV 库和头文件。最简单的方法是运行类似“sudo apt install libopencv-dev”的命令。但至少对于 Raspbian,这种方法不起作用。通过“apt”获得的最新版本是 4.2,而加载 YOLO 模型的最低 OpenCV 要求是 4.5。因此,我们需要从源代码构建 OpenCV。
我将使用 OpenCV 4.7,这是我在 Python 测试中使用的版本:
sudo apt update
sudo apt install g++ cmake libavcodec-dev libavformat-dev libswscale-dev libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev
sudo apt install libgtk2.0-dev libcanberra-gtk* libgtk-3-dev libpng-dev libjpeg-dev libtiff-dev
sudo apt install libxvidcore-dev libx264-dev libgtk-3-dev libgstreamer1.0-dev gstreamer1.0-gtk3
wget https://github.com/opencv/opencv/archive/refs/tags/4.7.0.tar.gz
tar -xvzf 4.7.0.tar.gz
rm 4.7.0.tar.gz
cd opencv-4.7.0
mkdir build && cd build
cmake -D WITH_QT=OFF -D WITH_VTK=OFF -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D WITH_FFMPEG=ON -D PYTHON3_PACKAGES_PATH=/usr/lib/python3/dist-packages -D BUILD_EXAMPLES=OFF ..
make -j2 && sudo make install && sudo ldconfig
Raspberry Pi 不是世界上最快的 Linux 计算机,编译过程大约需要 2 小时。对于拥有 1 GB RAM 的 Raspberry Pi 3,交换文件大小应该增加到至少 512 MB;否则,编译将失败。
C++ 代码本身很简短:
#include <opencv2/opencv.hpp>
#include <vector>
#include <ctime>
#include "inference.h"
int main(int argc, char **argv) {
Inference inf("yolov8n.onnx", cv::Size(640, 640), "", false);
cv::Mat frame = cv::imread("test.jpg");
// First run to 'warm-up' the model
inf.runInference(frame);
// Second run
const clock_t begin_time = clock();
std::vector<Detection> output = inf.runInference(frame);
printf("dT: %f\n", float(clock() - begin_time)/CLOCKS_PER_SEC);
// Show results
for (auto &detection : output) {
cv::Rect box = detection.box;
printf("Rect: %s %f: %d %d %d %d\n", detection.className.c_str(), detection.confidence,
box.x, box.y, box.width, box.height);
}
return 0;
}
在这段代码中,我使用了 Ultralitics GitHub 仓库中的“inference.h”和“inference.cpp”文件,这些文件应该放在同一个文件夹中。我还像以前的测试一样执行了“runInference”方法两次。我们现在可以使用以下命令编译源代码:
c++ yolo1.cpp inference.cpp -I/usr/local/include/opencv4 -L/usr/local/lib -lopencv_core -lopencv_dnn -lopencv_imgcodecs -lopencv_imgproc -O3 -o yolo1
结果令人惊讶。C++ 版本的速度比以前的版本明显 慢!在Raspberry Pi 3上,执行时间为 110 秒,比 Python 版本长了 3 倍以上。在Raspberry Pi 4上,计算时间为 1.79 秒,比 Python 版本长了约 1.5 倍。总的来说,很难说清楚原因。Python 的 OpenCV 库是通过 pip 安装的,而 C++ 的 OpenCV 是从源代码构建的,也许某些 ARM CPU 优化没有启用。如果有读者知道原因,请在下方评论中写明。无论如何,看到这样的效果是很有趣的。
结论
我可以“有根据地猜测”大多数数据科学家和数据工程师都在云端或至少在高端设备上使用他们的模型,并且从未尝试过在嵌入式硬件上“实地”运行代码。本文的目的是为读者提供一些关于它是如何工作的见解。在这篇文章中,我们尝试在不同版本的 Raspberry Pi 上运行 YOLO v8 模型,结果相当有趣。
-
在低功耗设备上运行深度学习模型可能是一个挑战。即使是目前最好的基于 Raspbian 的模型——Raspberry Pi 4,在使用 YOLO v8 Tiny 模型时也只能提供大约~1 FPS。当然,还有改进的空间。一些优化可能是可行的,例如将模型转换为 FP16(精度较低的浮点格式)或甚至 INT8 格式。最后,也可以使用一个在有限数据集上训练的更简单的模型。最后但同样重要的是,如果仍然需要更多的计算能力,可以在像 NVIDIA Jetson Nano 这样的特殊单板计算机上运行代码,它支持 CUDA,并且可以更快。
-
在本文开头,我写道“仅使用 OpenCV 运行深度学习模型,而不依赖像 PyTorch 或 Keras 这样沉重的框架,对低功耗设备是有前景的”。实际上,结果是 PyTorch 是一个有效且高度优化的框架。基于 PyTorch 的原始 YOLO 版本是最快的,而 OpenCV ONNX 代码慢了 10–20%。但在撰写本文时,PyTorch 在 32 位 ARM CPU 上不可用,因此在某些平台上可能没有其他选择。
-
C++版本的结果则更为有趣。正如我们所见,开启适当的优化可能是一个挑战,特别是对于嵌入式架构而言。在不深入探讨这些细节的情况下,定制的 OpenCV C++代码可能比板制造商提供的 Python 版本运行得更慢。
感谢阅读。如果有人有兴趣在相同硬件或 NVIDIA Jetson Nano 板上测试 FP16 或 INT8 YOLO 模型,请在评论中留言,我会在下一部分文章中讨论这一点。
如果你喜欢这个故事,欢迎 订阅 Medium,这样你会在我的新文章发布时收到通知,并且可以全面访问其他作者的成千上万的故事。
你不能踏入同一条河流两次
原文:
towardsdatascience.com/you-cant-step-in-the-same-river-twice-cfacf7cee305
图片来源:弗拉基斯拉夫·巴比恩科 在Unsplash
《为何的书》第 7 和 8 章,阅读系列
朱子景博士
·发表于Towards Data Science ·18 分钟阅读·2023 年 12 月 20 日
--
在我的之前的文章中,我们了解了观察数据中的混杂因素和碰撞器,这些因素阻碍了可靠因果关系的建立。珀尔提供的解决方案是绘制因果图,并使用后门准则找到需要阻断的混杂因素集合,留下碰撞器和中介变量。
然而,当处理那些无法观察或测量的混杂变量时,从观察数据中估计因果关系变得困难。为应对这个问题,在《为何的书》第七章中,朱迪亚·珀尔介绍了do-calculus 规则。这些规则对于前门准则和工具变量特别有用。即使存在不可观察的混杂变量,它们也可以用来建立因果关系。
在第八章中,我们将探索反事实的奇妙世界。以诗人罗伯特·弗罗斯特的名句开篇:
“而且对不起,我不能两者兼得
“而且做一个旅行者,我站了很久……”
珀尔表示,尽管不可能走两条路径或踏入同一条河流两次,我们的大脑可以想象如果我们选择了另一条路径会发生什么。为了明确并传递智慧给机器人,珀尔介绍了必要原因和充分原因的区别,以及如何利用结构因果模型系统地进行反事实分析。
随着章节的深入,内容变得更加技术化和信息密集。在接下来的部分,我将首先讨论如何处理未观察到的混杂因素,不幸的是,涉及一些数学内容,针对第二级干预。然后,我将讨论反事实,作为第三级应用。
前门标准
从因果图开始,我们用来理解 X 对 Y 的因果影响:
作者基于《为什么的书》第七章制作的图像
在这里,X 通过中介 M 影响 Y。然而,我们无法直接从数据中估计因果关系而不控制混杂因素 U。“U -> X” 的后门路径会产生 X 与 Y 之间的虚假相关性。后门标准告诉我们要控制 U,但如果 U 是不可观测的呢?
Tobias Tullius 提供的照片,来源于 Unsplash
例如,在分析烟雾(X)与癌症(Y)之间的因果关系时,我们可能会看到这样的因果图:
作者基于《为什么的书》第七章制作的图像
在这里,吸烟通过焦油积累引发癌症,还有一个混杂因素——吸烟基因,正如一些研究人员所言,它可能会影响一个人的吸烟行为和患肺癌的几率。我们无法收集这些基因的数据,因为我们不知道它们是否存在。因此,后门调整在这种情况下无法起作用。
为了获得因果影响,我们可以使用前门标准。这里的前门是中介过程,吸烟增加焦油沉积,进而增加患癌症的几率。如果我们不能直接估计烟雾对癌症的影响,我们能否通过焦油对癌症的影响来估计烟雾对焦油的因果影响?步骤如下:
步骤 1: 吸烟 -> 焦油
吸烟到焦油的唯一后门路径:
吸烟 <- 吸烟基因 -> 癌症 <- 焦油
它被 Collider 癌症阻挡了。因此,我们可以通过计算条件概率直接从数据中估计吸烟对焦油的因果影响:
The causal impact of smoking on tar is:
P(tar|smoking) - P(tar|no smoking)
步骤 2: 焦油 -> 癌症
焦油到癌症的一个后门路径:
焦油 <- 吸烟 <- 吸烟基因 -> 癌症
在这里,吸烟和吸烟基因都是混杂因素,但我们可以控制其中一个来阻断路径。由于我们没有吸烟基因的数据,我们可以控制吸烟:
The causal impact of tar on cancer is:
P(cancer|do(tar)) - P(cancer|do(no tar))
where,
P(cancer|do(tar)) = P(cancer|tar,smoking) * P(smoking) +
P(cancer|tar,no smoking) * P(no smoking)
P(cancer|do(no tar)) = P(cancer|no tar,smoking) * P(smoking) +
P(cancer|no tar,no smoking) * P(no smoking)
为了估计焦油对癌症的因果影响,我们从数据中测量以下四个概率:
- 吸烟人群中通过积累焦油患癌症的概率:
P(癌症|焦油, 吸烟) * P(吸烟)
- 在非吸烟人群中,通过积累焦油患癌症的概率:
P(癌症|焦油, 不吸烟) * P(不吸烟)
- 在吸烟人群中,没有足够焦油的癌症发生概率:
P(cancer|no tar, smoking) * P(smoking)
- 在非吸烟人群中,没有足够焦油的癌症发生概率:
P(cancer|no tar, no smoking) * P(no smoking)
步骤 3:吸烟 -> 焦油 -> 癌症
一旦我们知道吸烟对焦油的因果影响以及焦油对癌症的因果影响,我们可以通过前门调整推导出吸烟对癌症的无偏因果影响:
The causal impact of smoking on cancer is:
**P(cancer|do(smoking)) = P(cancer|do(tar)) * P(tar|do(smoking)) +
P(cancer|do(no tar)) * P(no tar|do(smoking))**
######**Math Alert**########################
Since no backdoor between smoking and tar:
**P(tar|do(smoking)) = P(tar|smoking)
&
P(no tar|do(smoking)) = P(no Tar|smoking)**
And from back door adjustment for tar and cancer:
**P(cancer|do(tar)) = P(cancer|tar,smoking) * P(smoking) +
P(cancer|tar,no smoking) * P(no smoking)
&
P(cancer|do(no tar)) = P(cancer|no tar,smoking) * P(smoking) +
P(cancer|no tar,no smoking) * P(no smoking)**
Finally,
**P(cancer|do(smoking)) = (P(cancer|tar,smoking) * P(smoking) +
P(cancer|tar,no smoking) * P(no smoking))
* P(tar|smoking)
+
(P(cancer|no tar,smoking) * P(smoking) +
P(cancer|no tar,no smoking) * P(no smoking))
* P(no tar|smoking)**
或者,更一般地,对于任何类似于之前的因果图的图:
-
X 仅通过中介变量 M 影响 Y,而中介变量 M 也是前门;
-
存在一个不可观测的混杂因素 U,它与 X 和 Y 相关,但与中介变量 M 无关;
使用前门调整的以下公式有效:
前门调整
与后门调整相比:
后门调整
这两个公式都估计 X 对 Y 的因果影响,它们都成功去除了do操作符。这意味着我们可以从数据中估计因果影响,即从第一层数据得出第二层和第三层结论。在前门调整公式中,我们也成功移除了未观测的混杂因素 U。在吸烟对癌症的案例中,我们可以在不包括吸烟基因的情况下估计吸烟的影响。
前门准则为从观察数据中挤出更多信息提供了可能性,即使面对不可测量的混杂因素。然而,由于现实世界通常比教科书中的因果图更复杂,应用过程中仍然存在挑战。例如,未观测的混杂因素也可能影响中介变量:
图片由作者根据《为何的书》第七章制作
即使存在这种复杂情况,只要 U 与 M 之间的关系较弱,使用“金标准”随机对照试验(RCT)估计 P(Y|do(X)) 作为基准,实证研究表明使用前门调整提供了比后门调整更好的估计,即使没有阻挡所有必要的后门。
Do-calculus 规则
使用上面的例子来说明前门调整,Pearl 还总结了三个规则,提供了移除 do 的一般指导:
- 规则 1 表示如果变量 W 对 Y 无关,或其通往 Y 的直接路径被变量 Z 阻塞,那么移除或添加 W 将不会改变以下概率:
Do-calculus 的规则 1
- 规则 2 表示如果变量集 Z 阻塞了 X 到 Y 的所有后门路径,那么在 Z 的条件下将移除do操作:
Do-calculus 的规则 2
- 规则 3 表示如果 X 到 Y 没有因果路径,那么我们可以完全移除do操作:
do-演算的规则 3
上述三条规则为do-演算奠定了基础,使我们能够从观察数据中推导出第 2 和第 3 层次的因果影响。规则 1 展示了从数据中收集哪些变量是有用的;规则 2 展示了如何从观察数据中推导干预,即第 2 层次的结论;规则 3 展示了干预是否有效。
更多数学警告:如果数学让你头痛,可以跳过
使用这三条规则,我们可以重新审视吸烟对癌症的因果图:
作者基于《为什么的书》第七章的图像
并查看我们如何在更一般的情况下利用规则 2 和规则 3。步骤如下:
基于《为什么的书》第七章的 7 个步骤
在这里,为了理解吸烟如何因果影响癌症,我们使用了前门路径吸烟 -> tar -> 癌症,共 7 步:
-
第 1 步基于概率理论,我们引入中介变量 t(tar),以估计因果影响;
-
第 2 步基于规则 2。由于 tar ->癌症的后门被 s(吸烟)阻塞,我们可以用(do(s), do(t))替代(do(s), t);
-
第 3 步再次基于规则 2。由于吸烟 -> tar 没有后门,我们可以用 s 替代 do(s);
-
第 4 步基于规则 3。由于吸烟仅通过 tar 对癌症产生因果影响,我们可以用 P(c|do(t))替代 P(c|do(s), do(t));
-
第 5 步基于概率理论,我们在方程中引入了不同的 S 谱。在这种情况下,我们有吸烟与非吸烟的对比;
-
第 6 步基于规则 2。同样,tar ->癌症的后门被 s 阻塞,所以我们用(t, s’)替代(do(t), s’);
-
第 7 步基于规则 3。由于 tar 对吸烟没有因果影响,我们用 P(s’|do(t))替代 P(s’)
在最后一个方程中,我们可以看到我们已经完全移除了 do 操作,并且没有不可观测的变量。下一步将使用数据来计算因果影响。
工具变量
处理未观测混杂因素的另一种方法是寻找工具变量。工具变量的定义在因果图中得到更好的说明:
作者基于《为什么的书》第七章的图像
假设在估计 X 对 Y 的因果影响时,存在一个不可观测的混杂因素 U,阻碍我们得到正确的估计。如果存在一个变量 Z 满足:
-
Z 和 U 不相关。在图中,Z 和 U 之间没有箭头;
-
Z 是 X 的直接原因;
-
Z 仅通过 X 影响 Y。换句话说,除了 Z->X->Y 之外,没有直接或间接的箭头从 Z 到 Y。
如果所有条件满足,Z 将是一个很好的工具变量。工具变量在许多科学领域中非常有用。在书中,Pearl 提供了一个使用工具变量研究临床试验中药物治疗效果的例子。
图片由AbsolutVision拍摄,发布在Unsplash上。
这药物是为了降低患者的胆固醇水平而发明的。虽然临床试验通常是随机的,但它们仍然面临非依从性的挑战,即受试者接受了药物但选择不服用。
他们不服药的决定可能取决于多个因素,如他们的病情,并且通常难以观察或测量。非依从性的存在会降低药物效果的估计,我们实际上没有很好的方法来预测临床试验中会有多少非依从性。
在这种情况下,研究人员在 RCT 设计中引入了工具变量“分配”。如果患者被随机分配接受药物,则“分配”的值为 1;如果他们接受的是安慰剂,则值为 0。我们将得到以下因果图:
作者根据《为何之书》第七章制作的图片
“分配”是一个工具变量,因为:
-
药物的分配在患者中是随机的,因此它与任何混杂变量 U 没有相关性;
-
患者被分配到哪个组将决定他们接受的治疗,无论是药物还是安慰剂。因此,“分配”是“接收”的直接原因;
-
患者是否被分配到安慰剂组不会直接影响其胆固醇水平。因此,“分配”仅通过“接收”影响胆固醇。
当我们找到或建立一个工具变量时,可以在观察数据中估计三个关系:
-
分配对接收的因果影响;
-
分配对胆固醇的因果影响;
-
通过从“分配 -> 胆固醇”中去除“分配 -> 接收”影响,得到接收对胆固醇的因果影响。
这就是工具变量的定义及其示例。Pearl 的书中包含了更多关于如何使用工具变量来解决未观察混杂因素问题的例子。
反事实:可能发生了什么?
从因果关系阶梯的第 2 层到第 3 层,我们现在面临的问题是找到没有治疗情况下可能的结果。这与第 2 层的干预在两个方面有所不同:
- 从平均因果效应到个体因果效应:
到目前为止,我们讨论的因果影响主要集中在总体或子总体。例如,吸烟是否对所有人都导致癌症?然而,更具实际意义的问题,尤其是在解决现实世界问题时,是个体因果效应。例如,如果我开始吸烟,它是否会导致我得癌症?如果我给这位顾客打折,他或她会买更多产品吗?个性化的因果关系可以通过反事实分析来推断。
照片由 Ilya lix 拍摄,来源于 Unsplash
2. 有两种类型的反事实:
在二元设置中,结果和潜在结果有两个选项。在吸烟与癌症的例子中,结果是得癌症或不得癌症。相应地,会有两种潜在结果和两组因果因素:
-
必要因果关系: 如果一个人得了癌症,潜在的结果是没有癌症,我们确定吸烟是否是律师所称的“但为因果关系”:如果没有吸烟行为,癌症就不会发展。
-
充分因果关系: 如果一个人没有得癌症,潜在的结果是癌症,我们确定是否吸烟行为会使这个人得癌症。
区分必要因果关系和充分因果关系不仅有助于机器人在确定因果关系时更像人类思考,还能帮助我们在不同目标和场景下找到更好的行动点——更多内容将在下一节讨论。
匹配与结构因果模型(SCM)
当面对“本可以发生什么”的问题时,我们可能将其视为缺失数据问题。以探讨教育水平如何提高不同人的收入为例。以下是总结在表格中的数据:
表格由作者根据《为何的书》第八章,第 8.1 表制作
在这里,我们有来自不同员工的各种数据条目,包括他们当前的经验水平(EX)和最高教育水平(ED)。为了简化,我们假设有三个教育水平:0 = 高中学历,1 = 大学学历,2 = 研究生学历。
他们的薪资也被报告为 S0, S1, S2。请注意,由于每个员工在特定时间点只能有一个最高学历,因此所有员工的 S0, S1 和 S2 都会有两个缺失项。
如果我们将填补上述表格中的问号视为缺失数据问题,我们有两种方法:
- 匹配:
我们找到类似的员工,并匹配不同教育水平的薪资水平。例如,在表格中,我们只有一个额外的特征——经验,我们看到 Bert 和 Caroline 都有九年的经验,那么我们可以得出 S2(Bert) = S2(Caroline) = $97,000,且 S1(Caroline) = S1(Bert) = $92,500。两个缺失数据已填补!
照片由 Kara Eads 拍摄,来源于 Unsplash
2. 线性回归或更复杂的模型
在强假设所有数据来自某些未知的随机来源的前提下,标准统计模型找到最适合数据的模型。在线性回归模型中,我们可能会为这个特定问题找到一个这样的方程:
S = $65,000 + 2,500EX + 5,000ED
从系数来看,方程告诉我们,教育水平每增加一个级别,工资会增加$5,000\。当特征空间增加时,我们可以使用更复杂的模型。
这些方法有什么问题?从根本上说,它们都是数据驱动的方法,而不是模型驱动的方法。我们仍然在用第一层的方法解决第三层的问题。因此,无论我们的模型变得多么复杂以及我们获得了多少特征来预测结果变量,我们仍然面临着遗漏因果机制的根本缺陷。
在这个简单的例子中,我们面临的一个问题是经验和教育彼此之间并非独立。通常,更多的教育可能会减少个人的经验年限。如果 Bert 拥有研究生学位而不是目前的大学学位,他的经验水平将低于九年。因此,他将不再适合拥有九年经验的研究生学位持有者 Caroline。总之,我们将会有一个如下的因果图:
图片由作者基于《为什么的书》第八章制作
因果图表明教育不仅对工资有直接的因果影响,而且还通过中介变量经验影响工资。因此,我们将需要两个方程:
S = f_s(EX, ED, U_s)
where
EX = f_ex(ED,U_ex)
关于这些方程有几个要点,它们构成了这个问题的结构因果模型(SCM):
-
工资是经验、教育和一些影响工资的不可观察变量 U_s 的函数。注意不可观察变量是外生的,意味着它们与教育和经验没有相关性;
-
经验是教育和一些影响经验的不可观察变量 U_ex 的函数。
-
由于没有函数显示教育作为经验的函数,这意味着没有经验对教育的因果影响。这是我们所做的假设。
-
这两个方程假设了右侧(结果)和左侧(处理)之间的因果关系。
-
无法观察到的变量 U_s 和 U_ex 量化了个体水平的 uncertainties,这与使用因果链接的概率来量化不确定性的贝叶斯网络不同。它们与经验或教育无关,可以由个人自定义。
-
函数 f 表示特征与结果变量之间的关系。它可以是线性的,也可以是非线性的,具体取决于假设。
如果我们仍然假设线性关系,我们将基于数据和我们对教育如何影响经验的理解得到以下方程:
S = $65,000 + 2,500*EX + 5,000*ED + U_s
and
EX= 10 – 4*ED + U_ex
通过这些方程,我们可以计算每个人的个性化因素 U_s 和 U_ex 来预测反事实。以 Alice 为例。我们知道 S(Alice)是 81000,EX(Alice)是六,ED(Alice)是 0。首先,将这些代入第二个方程得到 U_ex。我们想代入第二个方程,因为它只包含 ED,这是我们关注的唯一因果因素:
6 = 10–4*0 + U_ex(Alice)
-> U_ex(Alice) = -4
Thus,
EX(Alice) = 10 – 4*ED(Alice) - 4
在这里,我们不仅仅是将 EX(Alice)代入方程,而是间接使用这个值。知道 EX(Alice)等于六有助于我们计算 U_ex(Alice)。然后我们将 S(Alice)、ED(Alice)和 U_ex(Alice)代入第一个方程来得到 U_s(Alice):
81000 = 65000 + 2500*(10 – 4*ED + U_ex) + 5000*ED + U_s(Alice)
-> U_s(Alice) = -5000*ED - 2500*U_ex(Alice) -9000
-> U_s(Alice) = 0 - 2500*(-4) - 9000 = 1000
#Note **DO NOT** calculate by plug EX=6 directly:
81000 = 65000 + 2500*6 + 5000*0 + U_s(Alice)
这里是关于 Alice 的函数:
S(Alice) = $65,000 + 2,500*EX + 5,000*ED + U_s(Alice)
and
EX= 10 – 4*ED + U_ex(Alice)
一旦 SCMs 准备好,我们就可以对 Alice 进行反事实分析。我们可以计算她如果上大学会得到什么样的工资。如果 ED(Alice)是 1 而不是 0,我们将首先计算:
EX(Alice) = 10–4+(-4) = 2
然后计算 S_1(Alice):
S_1(Alice) = $65,000 + 2,500*2 + 5,000*1 + 1000 = $76,000
注意我们从回归模型中得到的不同结果,其中我们代入 ED(Alice) = 1 和 EX(Alice)=6:
S = 65000 + 2500*6 + 5000*1 = $85,000 -> Biased estimation
# If Alice has six years of experience and a high school degree now,
# She couldn't get six years of experience if she goes to college.
这是一个简单的利用 SCMs 理解因果影响和计算个体水平反事实的例子。总之,Pearl 称之为“因果推断的第一定律”:
方程显示,潜在结果 Y_x(u)可以通过模型 M_x 进行推算,只要我们能去除所有到 X 的反向路径。这里,模型比线性回归模型有更大的灵活性,只要因果关系是基于因果图的。
必要原因 (PN) 与 充分原因 (PS)
为了理解反事实,我们有两个不同的测量:必要性概率 (PN) 和 充分性概率 (PS)。为了看到区别,我们用一个例子:房子着火是因为有人点燃了火柴,空气中有氧气:
火柴和氧气都是房子着火的因果因素。然而,它们在 PN 和 PS 上有所不同。必要性的概率是:
PN = P(Y_x=0 = 0|x=1, Y=1)
在这种情况下,房子着火了,火柴被点燃了 (Y=1, x=1)。这个概率问的是如果火柴没有点燃,房子是否不会着火 (Y_(x=0) = 0)。这个概率非常高,因为即使我们有足够的氧气,火柴引发的火也不会使房子着火。
Erick Zajac在Unsplash上的照片
当我们计算 x=氧气的 PN 时,同样的逻辑适用。如果房子里没有足够的氧气,即使我们点燃了火柴,火也不会发生。
必要性的概率显示了如果治疗没有发生,结果变量会发生什么。如果概率很高,这意味着该治疗是一个必要原因。在法庭上,证明如果被告没有采取某些行动受害者不会死就足以定罪。
另一方面,充分性概率是:
PS = P(Y_x=1 = 1|x=0, Y=0)
在这种情况下,房子没有着火,火柴也没有点燃(Y=0, x=0)。这个概率问的是如果火柴点燃,房子是否会着火(Y_(x=0) = 0)。这个概率也很高,因为通常,氧气无处不在,房子在有氧气和点燃火柴的情况下,很可能会着火。
然而,氧气的 PS 非常低。仅仅因为房子有氧气,火灾不太可能发生。氧气不足以引发房屋火灾。我们需要点燃火柴等其他火源的行动。
因此,充分性概率告诉我们如果治疗发生了,结果变量会发生什么。如果概率很高,这意味着这种治疗是一个充分原因。在这种情况下,点燃火柴符合引发房屋火灾的充分原因,但氧气不符合。
为什么要区分 PS 和 PN?
为什么要做这些区分? 简而言之,尽管多个变量可能是结果的因果因素,人脑会根据一些条件自动“排序”这些因素。心理学家发现,人们会想象如果做了某些行动可能会改变不理想的结果。他们更倾向于:
-
想象一下,撤销一个稀有事件比撤销一个常见事件更难。例如,点燃火柴是一个比家中有氧气更稀有的事件。(希望如此!)
-
人们更倾向于责怪自己的行为,即点燃火柴,而不是那些不受他们控制的事件。
正如 Pearl 所强调的,将 PN 和 PS 都嵌入模型中将建议一种系统化的方式来教机器人生成对观察到的结果有意义的解释。
图片由Brett Jordan拍摄,来源于Unsplash。
此外,理解 PS 和 PN 之间的区别可以指导我们采取行动。研究和找出极端热浪原因的气候科学家可能会有两种不同的陈述:
-
PN: 有 90%的概率认为人为气候变化是热浪的必要原因;
-
PS: 气候变化至少有 80%的概率会在每 50 年内引发一次如此强烈的热浪。
PN 陈述告诉我们归因:谁对热浪负责——人为气候变化。它找出原因。PS 陈述告诉我们政策。它说明我们对热浪的准备更充分,因为更多的事件正在发生。谁导致了这些事件?没有具体说明。这两种陈述都很有信息量,只是方式不同。
真是篇长文! 这篇文章完成了“与我一起阅读”系列中关于朱迪亚·珀尔《为何之书》的第五篇文章。感谢你坚持读到最后。第七章和第八章的信息量确实很大。希望这篇文章对你有帮助。如果你还没有读过前四篇文章,可以在这里查看:
-
开篇:从一个猫的故事开始
-
第一章和第二章:数据告诉我们‘什么’,而我们总是寻求‘为什么’
-
第三章和第四章:因果图:应对观察数据中的致命弱点
-
第五章和第六章:为什么理解数据生成过程比数据本身更重要
如果感兴趣,订阅我的邮件列表以加入当前进行的双周讨论。我们将发布一篇关于中介变量的文章,来结束这个系列:
## 什么使得人工智能强大?
“为何之书”第九章和第十章,“与我一起阅读”系列
towardsdatascience.com
还有一篇额外文章:
## 因果推断在学术界和工业界有何不同?
“为何之书”系列的额外文章
towardsdatascience.com
一如既往,我强烈鼓励你阅读、思考,并在这里或在你自己的博客上分享你的主要收获。
感谢阅读。如果你喜欢这篇文章,请不要忘记:
-
查看我最近的文章 数据讲述中的 4D:将科学变成艺术;数据科学中的持续学习; 我如何成为数据科学家;
-
查看我的 其他文章 ,涵盖不同主题,如 数据科学面试准备 ; 因果推断 ;
-
订阅 我的邮件列表;
-
注册成为 medium 会员 ;
-
或者在 YouTube 上关注我,并观看我最新的 YouTube 视频:
你暂时不需要数据领域……
原文:
towardsdatascience.com/you-dont-need-data-domains-yet-23af8ffc3e69
解密数据网格领域
Louise de Leyritz
·发表于Towards Data Science ·阅读时间 7 分钟·2023 年 5 月 23 日
--
你暂时不需要数据领域——图片由Castor提供
数据网格范式提倡使用数据领域作为将数据划分为有意义组的方式。领域最初引入是为了帮助创建明确的所有权结构并实现更好的数据发现。
尽管这种方法可能有效,但也可能会令人困惑。我已经研究了数据网格领域一年了,但仍然无法完全理解这个概念。因此,我一直在想:我们是否可以在不涉及数据领域的情况下实现更好的所有权和发现?
在一定程度上,我们可以做到。
在这篇文章中,我旨在阐明数据领域的目的,并探索实现这些目标的更简单的替代方法。
从根本上说,数据领域只是组织中数据分组的一种方式。我们为什么要对数据进行划分?有三个原因:
-
使人们更容易找到他们需要的数据。
-
使得在出现问题时能够明确分配所有权和责任。
-
为了提供必要的背景,以便更好地理解数据。
好消息是,大多数公司无需考虑数据领域的概念来处理这三个要素。所以,停止阅读关于数据领域的所有文章,因为你可能不需要它们。或者至少,目前不需要。
为了应对上述要素,公司只需找到良好的数据分组方法,以便于分配所有权和数据发现。幸运的是,有两种你所在组织已经非常熟悉的数据分组方法:团队和来源。
这些分类方法简单但非常有效,在所有权、发现和理解方面可以实现 90%的工作。通过合理使用这些分类,公司可以在不引入相当复杂的数据领域概念的情况下改善数据管理。
在这篇文章中,我们将探讨如何根据团队和来源对数据进行分组,以解决所有权和发现挑战。我们还将探讨在组织复杂度增长时何时需要引入数据域的概念。
从团队开始
按团队进行数据分区将帮助你实现强大的数据发现,并为这些数据的上下文分配所有权。让我们仔细看看基于团队的分区如何帮助实现这两件事。
基于团队的数据分区 — 图片来源于Castor
所有权
按照团队对数据进行分区在归属数据所有权和确保职责明确方面非常有意义。
在这里,所有权指的是上下文所有权,而非技术所有权。这意味着,当我们说市场团队拥有市场数据时,我们的意思是他们负责维护这些资产的文档和上下文。
既然我们已经建立了这一点,以下是将数据按团队分组的三个有力理由:
👉 轻松分配所有权:团队在组织中是非常明确的实体。这样可以轻松地将数据分配给一个团队。团队不会模糊不清,也不会重叠,这使得分配所有权变得非常简单。
👉 更好的文档:当团队拥有自己的数据时,数据周围的文档突然变得更有意义。团队对他们负责的过程、目标和指标有深刻的理解。这使他们成为为数据提供上下文的最佳人选。
👉 职责明确:按团队对数据进行分组,可以明确谁负责为数据资产提供上下文。当一个利益相关者对市场数据有疑问时,他们只需两秒钟就会意识到需要联系市场团队,而不是联系一些模糊领域的所有者。
发现
按团队分区数据还可以改进数据发现过程,帮助人们更快地找到所需的数据。
每个人都熟悉团队。因此,基于团队的数据分区使数据发现变得更加熟悉。这降低了技术能力较差的人的门槛,他们可能会被数据吓到。
基于团队进行数据分区也满足了两类寻求数据上下文的人群:信息寻求者,他们在寻找特定的信息,以及探索者,他们希望浏览数据的全貌。
例如,如果一个信息寻求者在寻找市场数据,他们会知道市场团队负责这些数据,他们只需通过“团队 → 市场”来筛选数据即可找到。若他们在理解数据时遇到困难,可以直接向市场团队咨询。
类似地,如果一个探索者希望更好地理解数据策略,他们可能会想要探索市场团队处理的数据类型。通过浏览市场团队的仪表板和知识图谱,他们将更清楚地了解业务的运作方式。
继续处理来源
虽然基于团队的数据分区对于分配所有权很有帮助,但可能不足以满足发现目的。因此,另一种数据分区方法是基于来源,这是商业人员熟悉的另一个元素。
基于来源的数据分区 — 图片来源于Castor
每个人都对数据来源感到熟悉,因为利益相关者使用它们来执行日常任务。例如,销售人员可能不是数据专家,但他们对 Salesforce 了如指掌。因此,他们在探索数据仓库时,应该能够按来源进行筛选。
基于来源的数据分区也满足了寻找文档的两类人群:信息寻求者和探索者。
例如,当一个信息寻求者在搜索特定信息时,他们会对要寻找的数据来源有清晰的认识。他们可以按来源(例如 Salesforce)过滤搜索,轻松找到所需的信息。这提升了发现体验,并减少了数据探索所需的时间。
同样,对于一个探索者,如果他们希望获得对数据全貌的更广泛理解,通过来源角度探索数据可能会很有帮助。例如,他们可以确定哪些来源具有最相关或最可信的数据,或识别冗余或不完整的数据。
这种额外的分区层次,与基于团队的分区结合,可以提高组织中数据发现的效率。
什么时候需要引入数据领域?
一旦你将数据分为这两类,你将有效解决所有所有权问题,以及大部分发现和理解相关的挑战。
然而,一些组织在更复杂的环境中运营,可能需要引入额外的复杂层次,以准确捕捉其业务的细节。
对于这些组织来说,基于团队和来源的分区可能不足以确保可发现性和所有权。
比如你在 Airbnb 工作。公司已将数据所有权划分到团队,如营销、工程和客户支持。他们还根据数据的来源对数据进行了分组,区分了网站数据和移动应用数据。
但如果你需要了解平台上体验类项目的定价,例如旅游或烹饪课程,该怎么办?这些信息并不完全适合现有的团队或数据源。这不是营销团队拥有的内容,也不是仅从网站或应用程序中获得的内容。那么,你该怎么办呢?
有时候,数据无法完美地归入两个类别中的任何一个 — 图片由Castor提供。
这时,领域(domains)的概念就显得很重要。在这种情况下,Airbnb 需要创建一个新的领域,称为“体验”(Experiences),以捕捉这些数据并确保其得到适当的管理。通过引入这种新的分类层级,他们可以更好地组织数据,并确保重要信息不会被遗漏。
关键要点是,随着公司成长和变得更加复杂,它们可能需要引入新的分类层级来管理数据。
然而,这种新的数据划分方式只有在你拥有一些跨多个团队或来源的信息,并且这些信息无法完美地归入任何一个类别时,才应引入。
结论
去中心化数据可能是一个困难的过程,但你应该始终保持简洁。虽然数据领域可以是处理复杂业务需求的有力工具,但你不必在数据旅程的早期就引入它们。
在大多数情况下,团队和来源足以进行数据划分,确保所有权和可发现性。因此,你应从这些更为熟悉的概念开始,并根据需要逐步增加复杂性。
目标不是引入不必要的复杂性,而是提供一个帮助团队更有效地处理数据的框架。
最初发布于 https://www.castordoc.com.
你的数据科学可视化将不再相同——Plotly 和 Dash
原文:
towardsdatascience.com/your-data-science-visualizations-will-never-be-the-same-plotly-dash-6327d07d9efb
数据可视化
使用 Plotly 和 Dash 创建交互式仪表板
Pol Marin
·发布于 Towards Data Science ·14 min read·2023 年 10 月 24 日
--
由 Isaac Smith 在 Unsplash 提供的照片
不久前,我写了一篇简单的介绍,介绍了四个 Python 数据可视化库,展示了它们的优缺点,并通过实际示例展示了它们的能力。
当我们深入讨论我最喜欢的那些工具时,我强烈建议你先查看那篇文章,因为这篇文章会扩展其中展示的内容:
## 使用 Python 构建交互式数据可视化——讲故事的艺术
Seaborn、Bokeh、Plotly 和 Dash 用于有效传达数据洞察
towardsdatascience.com
今天我们将重点讨论Plotly[1]和Dash[2]。为什么是两个?因为它们是相辅相成的。正如我在上面链接的文章中所述,“Dash 本身不是一个绘图库。它是一个用于生成仪表板的出色框架。”
所以 Plotly 是我们用来绘图的库,而 Dash 是我们用来从这些图中生成酷炫交互式仪表板的框架。
这是我们今天构建仪表板的步骤:
-
设置和安装——让我们进入正确的状态。
-
一些简单的用例——展示 Plotly 如何工作
-
使用 Dash 构建仪表板——创建最佳仪表板。
-
结论——总结故事并查看结果。
在深入之前,我们需要讨论数据。我们需要某种数据才能进行可视化,对吧?跟上我最新的 Medium 内容,我将专注于体育,更具体地说,是足球(soccer)。
我将使用 Statsbomb 提供的 2015–16 赛季 LaLiga 的免费数据[3]。
那个赛季有很多数据,但我想要可视化巴萨球员的表现,主要集中在进攻方面:射门、进球、助攻……
目的可能会根据分析师的位置有所不同:你是皇马的分析师吗?那我敢肯定你会想要解码你的球队如何阻止梅西(剧透:你做不到)。
但如果你在巴萨组织工作,你可能想要检查一下你球员的数据,看看哪些球员的表现比其他球员更好。
无论是什么情况,始终确保在创建任何仪表板之前定义你的目标——你可以可视化的信息太多了,你必须有目的地选择你想查看的图表。
始终追求简单;非技术人员需要从你的仪表板中得出结论。
设置和安装
我喜欢保持事物的有序和结构化。所以我们要做的第一件事是创建一个新的目录在你希望托管应用程序的路径中。为了简单起见,我将在桌面上创建它。这是我在终端上运行的两个命令:
$ cd ~/Desktop
$ mkdir plotly-dash
现在,下一步自然是创建一个新的 Python 环境在新的目录中。我将使用pipenv
[4],但你可以使用你喜欢的虚拟环境管理工具(或者不使用)。
如果你的机器上还没有安装 pipenv,那么首先运行这个命令:
$ pip install --user pipenv
然后,创建环境:
$ cd plotly-dash
$ pipenv shell
这将创建一个新的环境并自动激活它。你现在从那个终端安装的任何东西都会只安装在该环境中。
所以让我们开始使用 pip 安装库:
(plotly-dash) $ pip install dash pandas statsbombpy
是的,安装这三个库我们会有足够的功能。它们都有自己的依赖关系,我们将利用一些像Plotly或NumPy的库。
一切准备好后,我们现在可以开始探索 Plotly。
使用 Plotly 进行数据可视化
我在这里的建议是从 jupyter notebook 进行测试,因为这将使你的开发阶段更加流畅。在这种情况下,你也应该安装它——我保证这是我们运行的最后一个安装——我们也将打开它:
(plotly-dash) $ pip install notebook
... (installation outputs)
(plotly-dash) $ jupyter notebook
像往常一样,我们需要准备数据,我们将创建一个新的笔记本叫做plotly.ipynb。为了避免极大的笔记本和文件,我喜欢将我的代码模块化。因此,我在项目文件夹中创建了一个src
目录,并在其中添加了两个新文件:functions.py
和classes.py
。现在的结构如下:
- plotly-dash/
- src/
- classes.py
- functions.py
- plotly.ipynb
我将创建的第一个函数叫做prepare_team_data()
,它将返回指定球队(在我们这个例子中是巴萨)的事件、射门和助攻数据。
由于函数本身对今天的目的并不重要,因为我们想专注于绘制和创建仪表盘,我不会放函数的代码。但你可以在资源部分[5]找到整个代码的链接。
# Third-party libraries
import pandas as pd
from statsbombpy import sb
# Custom modules
from src.functions import prepare_team_data
events, shots, assists = prepare_team_data('Barcelona')
shots.head()
这是投篮 DF 的一个快照。
投篮 DF 屏幕截图——图像由作者提供
好的,让我们从投篮开始吧。我想绘制一个球员的投篮分布图,以查看他从哪里射门以及他的进球来自哪里。为此,我在classes.py模块中创建了一个FootballPitch
类。
这个类允许我们绘制一个完整的足球场、它的一半(即进攻半场),或者甚至像我们将要做的那样绘制一个热图。
再次提醒,你可以在本文底部的资源部分找到 GitHub 链接[5]中的代码。但我们会稍微检查一下,因为这是我们使用了很多 Plotly 功能的地方。
这个类基本上有两个方法:plot_pitch()
和plot_heatmap
。由于我们首先关注的是展示球员的投篮,所以我们从第一个方法开始,将其分解为小的代码块。
请注意,你会看到一些变量和类属性我们没有赋值。这些是函数参数或在创建对象时初始化的。
首先:让我们声明函数将使用的基本变量。
# Fig to update
fig = go.Figure()
# Internal variables
self.height_px = self.pitch_width*10*zoom_ratio
self.width_px = self.pitch_length*10*zoom_ratio
pitch_length_half = self.pitch_length/2 if not self.half else 0
pitch_width_half = self.pitch_width/2
corner_arc_radius = 1
centre_circle_radius = 9.15
goal = 7.32
goal_area_width = goal + (5.5*2)
goal_area_length = 5.5
penalty_area_width = goal_area_width + (11*2)
penalty_area_length = goal_area_length + 11
penalty_spot_dist = 11
penalty_circle_radius = 9.15
现在我们已经声明了图形,我们将一遍又一遍地添加轨迹或形状以根据我们的需要进行自定义。因此,例如,函数的第一步是绘制一个矩形形状,即足球场本身:
fig.add_trace(
go.Scatter(
x=[0, self.pitch_length, self.pitch_length, 0, 0],
y=[0, 0, self.pitch_width, self.pitch_width, 0],
mode='lines',
hoverinfo='skip',
marker_color=line_color,
showlegend=False,
fill="toself",
fillcolor=bg_color
)
)
在这里,我们添加了一个散点图轨迹,模式为lines
——这意味着我们想要的是一条线,而不是一个有独立点的真正的散点图。参数是相当自解释的,例如 x 和 y(我们想绘制的数据),颜色……hoverinfo 标签用于确定当我们将鼠标悬停在这些线条上时想显示的内容。由于我们将足球场作为背景的一部分构建,并且它不会告诉我们任何关于我们想要分析的数据的信息,所以我将其设置为跳过。
然后我们在图的布局中设置了一些额外的配置:
fig.update_layout(
yaxis_range=[-self._vertical_margin, self.pitch_width + self._vertical_margin],
xaxis_range=[-self._horizontal_margin, self.pitch_length + self._horizontal_margin],
height=self.height_px,
width=self.width_px,
plot_bgcolor='rgba(0,0,0,0)',
xaxis=dict(showgrid=False, visible=False),
yaxis=dict(showgrid=False, visible=False)
)
这给我们带来了以下结果:
足球场(仅草地)——图像由作者提供
现在我们已经绘制好了我们的足球场。还不是特别有意义……不过。
在 Plotly 中绘图确实如此简单!通过在图中添加更多的轨迹和形状,我的足球场背景最终看起来是这样的:
足球场——图像由作者提供
现在,你可能对显示一个足球场不感兴趣。这就是为什么我没有把所有代码都放在这里……但出色的仪表盘是创造力和技能的结果,而绘制一个足球场是展示发生在场上的足球事件(如果我们对位置感兴趣)的一个很好的方式。
所以,让我们开始展示真实数据吧!
由于我们要显示射门——和进球——散点图似乎是一个合适的选择。记住,我们已经准备好了数据,我们只需要过滤和显示它。
让我们绘制梅西的射门和进球:
import plotly.graph_objects as go
from src.classes import FootballPitch
player = 'Leo Messi'
pitch = FootballPitch(half=True)
fig = pitch.plot_pitch(False, bg_color='#C1E1C1')
player_shots = get_player_shots(player, shots.copy(), pitch)
scatter_colors = ["#E7E657", "#57C8E7"]
for i, group in enumerate([True, False]):
fig.add_trace(go.Scatter(
x=player_shots[player_shots['goal'] == group]['x'],
y=player_shots[player_shots['goal'] == group]['y'],
mode="markers",
name='Goal' if group else 'No Goal',
marker=dict(
color=scatter_colors[i],
size=8,
line=dict(
color='black',
width=1
)
),
))
fig.update_layout(
title='Shot distribution'
)
第一部分不言而喻:我们只是声明变量,实例化球场,将图形存储在fig
变量中,并运行一个函数来过滤shots
数据框,以返回仅玩家的射门数据。
然后,在一个 2 次迭代的循环中,我们添加了两次散点图:一次用于未进球的射门(将显示为蓝色),另一次用于进球的射门。结果:
梅西在 2015/16 赛季的射门和进球分布——图片由作者提供
使 plotly 令人惊叹的是,这个图形是完全互动的。我们可以通过悬停鼠标来查看真实的射门位置,隐藏非进球的射门以仅检查进球射门……
现在让我们继续,构建一个折线图。它当然会是互动的,我们将用它来按季度检查球员的射门情况,并将其与队友和球队的平均水平进行比较。
为此,我们将开始按季度(每 15 分钟一段)将射门分组。下一部分将是绘制这些值本身,并调整线条的不透明度,以突出当前的球员(梅西)。
player = 'Leo Messi'
max_shots = 0
fig = make_subplots()
for p in shots.player.unique():
player_shots = get_player_shots(p, shots)
xy = 15 * (player_shots[['float_time', 'minutes']]/15).round()
xy = xy.groupby(['float_time']).count()[['minutes']]
max_shots = xy.minutes.max() if xy.minutes.max() > max_shots else max_shots
fig.add_trace(
go.Scatter(
name=p,
x = xy.index,
y = xy.minutes,
mode='lines',
opacity=1 if p == player else 0.2
)
)
现在我们已经准备好了所有球员,我们将添加球队的平均值作为虚线。代码的功能与上面的代码片段完全相同,但使用的是团队级的数据。
# Add team's avg
xy = 15 * (shots[['float_time', 'minutes']]/15).round()
xy = xy.groupby(['float_time']).count()[['minutes']]/len(shots.player.unique())
fig.add_trace(
go.Scatter(
name="Team's Average",
x = xy.index,
y = xy.minutes,
line = go.scatter.Line(dash='dash'),
marker=None,
mode='lines'
)
)
然后,我们将为布局添加一些样式:
fig.update_xaxes(range=[0, 91])
fig.update_layout(
#title='Shots by Quarter',
margin=dict(l=20, r=20, t=5, b=20),
xaxis = dict(
tickmode = 'array',
tickvals = xy.index.values
),
height=200,
plot_bgcolor="#F9F9F9",
paper_bgcolor="#F9F9F9",
yaxis_range=[-3,max_shots+5]
)
结果:
绿色的高亮线是梅西的数据(当我悬停在第 60 分钟的射门计数上时,标签显示)。由于某些原因,也许是疲劳,梅西的射门在 60 到 75 分钟期间减少,但在比赛的最后几分钟却增加了。
我们看到球队的大多数射门在最后 15 分钟减少,但梅西却走向相反的方向。这显示了他对球队的影响和他的胜利欲望。
总之,介绍部分到此为止。我们已经成功绘制了两个不同的图形,并且为我们的图形创建了一个出色的背景。我认为我们已经覆盖了 Plotly 的基本知识。
创建仪表板
仪表板只是以有序和吸引人的方式显示的图形组合。我们已经创建了图形——我们在上一部分做的——所以我们只需将它们显示出来。
现在,这并不那么简单。我们需要对上面共享的代码片段进行一些更改,但我保证这些更改会很小。
为了完成仪表板,我将添加一些更多的图形和功能,使其完全互动。
安装了Dash之后,我将创建一个名为app.py的新文件:
- plotly-dash/
- src/
- classes.py
- functions.py
- plotly.ipynb
- app.py
文件的模板将开始是这样的简单:
from dash import html, Dash, dcc, Input, Output, callback
app = Dash(__name__)
if __name__ == '__main__':
app.run(debug=True)
如果你继续执行文件(python app.py
),你会在终端中看到类似以下的消息:
(plotly-dash) $ python app.py
Dash is running on http://127.0.0.1:8050/
* Serving Flask app 'app'
* Debug mode: on
请访问127.0.0.1:8050/
。你会看到一个空白页面,但这实际上是你的仪表板。
让我们开始添加内容吧?看看下一个代码。
@callback(
Output('shot_distribution', 'figure'),
Input('player_dropdown', 'value')
)
def create_shot_distribution(player):
pitch = FootballPitch(half=True)
fig = pitch.plot_pitch(False, bg_color='#C1E1C1', zoom_ratio=0.8)
player_shots = get_player_shots(player, SHOTS.copy(), pitch)
scatter_colors = ["#E7E657", "#57C8E7"]
for i, group in enumerate([True, False]):
fig.add_trace(go.Scatter(
x=player_shots[player_shots['goal'] == group]['x'],
y=player_shots[player_shots['goal'] == group]['y'],
mode="markers",
name='Goal' if group else 'No Goal',
marker=dict(
color=scatter_colors[i],
size=8,
line=dict(
color='black',
width=1
)
),
#marker_color=scatter_colors[i] # #E7E657 i #57C8E7
))
fig.update_layout(
margin=dict(l=20, r=20, t=5, b=20),
)
return fig
到现在,这应该听起来很熟悉。这正是我们用来显示梅西射门的代码… 但现在,代替定义为梅西的玩家,它是函数参数。
那这个参数来自哪里?就在函数声明的上方,我们有回调装饰器。这些回调使 Dash 的仪表板具有交互性。
我们用它们来确定关联应用组件的输入和输出。在这种情况下,我们说明函数需要player
参数,这个参数将来自名为player_dropdown
的元素(我们还未定义)。
关于输出,我们让函数返回fig
。多亏了回调装饰器,应用知道这将是我们仪表板中的shot_distribution
元素所用的图形。
你现在可能有很多问题。怎么定义一个下拉框或任何可交互组件?我如何真正绘制shot_distribution
元素?
从第一个问题开始:下拉框。Dash 有自己的核心组件(dcc),下拉框就是其中之一。创建它非常简单:
dcc.Dropdown(
PLAYER_OPTIONS,
'All players',
id='player_dropdown',
style={'width': '200px', 'margin': '20px auto', 'text-align': 'left'}
)
这将创建一个下拉框,使用所有玩家名称作为可能的选项,以All players
作为默认值。但最重要的是id。在这里我们可以告诉 Dash 这个下拉框是与前一个函数的输入回调关联的。
换句话说,这个下拉框的值将是射击分布图上显示的玩家。
但我们仍然需要将这两个组件放入我们的仪表板中。页面依然是空白的。
你现在需要一些 HTML 知识,但基本知识就足够了(尽管它可以复杂到你想要的程度)。
我们需要将这些组件放在 HTML 代码中。Dash 再次使这一过程非常简单。对于下拉框,可以通过简单地用html.Div
组件包裹代码,即将下拉框放在<div></div>
HTML 元素中来实现:
filter = html.Div(
[
dcc.Dropdown(
PLAYER_OPTIONS,
'All players',
id='player_dropdown',
style={
'width': '200px',
'margin': '20px auto',
'text-align': 'left'
}
)
],
style={'display': 'inline-block'}
)
这个工作的方式是html.Div
可以有许多子元素(因此是列表),然后我们可以使用 style 属性设置元素的 CSS 样式,它是一个字典。简单吧?
对于射击分布图,等效的代码如下:
shot_distribution_graph = html.Div(
[
html.H2('Shot Distribution'),
dcc.Graph(id='shot_distribution', figure={})
],
style={
'padding': '2%',
'display': 'inline-block'
}
)
结构相同,但为了显示图表,我们使用dcc.Graph
组件,并且你可能猜到了,id 属性在这里也是关键。它将这个特定组件与我们声明的函数的输出回调关联起来。因此,那里计算的内容将在这里显示。
我们现在已经用 HTML 代码包裹了这些组件。但它们还没有显示出来。我们需要将它们添加到仪表板的布局中:
app.layout = html.Div([
shot_distribution_graph, filter
], style={
'width': '1650px',
'margin': 'auto'
})
这里没有秘密;结构相同,但层级更高。我们将之前的<div></div>
元素放入一个大的容器中(整个网站容器),并提供一些额外的样式。现在,如果你刷新网站或重新启动应用,你会看到你的初步结果:
投篮分布图 — 图片由作者提供
已经构建出的成果令人惊叹,对吧?这种互动性非常强大。
为了完成这一部分,让我们对另一个我们构建的图表做同样的处理。这一次,我会在这里粘贴整个新代码,以便你可以一次性查看所有内容:
# Functions
@callback(
Output('shots_by_quarter', 'figure'),
Input('player_dropdown', 'value')
)
def create_shots_by_quarter(player):
fig = make_subplots()
max_shots = 0
for p in SHOTS.player.unique():
player_shots = get_player_shots(p, SHOTS)
xy = 15 * (player_shots[['float_time', 'minutes']]/15).round()
xy = xy.groupby(['float_time']).count()[['minutes']]
max_shots = xy.minutes.max() if xy.minutes.max() > max_shots else max_shots
fig.add_trace(
go.Scatter(
name=p,
x = xy.index,
y = xy.minutes,
mode='lines',
opacity=1 if p == player else 0.2
)
)
# Add team's avg
xy = 15 * (SHOTS[['float_time', 'minutes']]/15).round()
xy = xy.groupby(['float_time']).count()[['minutes']]/len(SHOTS.player.unique())
fig.add_trace(
go.Scatter(
name="Team's Average",
x = xy.index,
y = xy.minutes,
line = go.scatter.Line(dash='dash'),
marker=None,
mode='lines'
)
)
fig.update_xaxes(range=[0, 91])
fig.update_layout(
margin=dict(l=20, r=20, t=5, b=20),
xaxis = dict(
tickmode = 'array',
tickvals = xy.index.values
),
height=200,
plot_bgcolor="#F9F9F9",
paper_bgcolor="#F9F9F9",
yaxis_range=[-3,max_shots+5]
)
return fig
# Dashboard's layout components
shots_by_quarter = html.Div(
[
html.H2('Shots By Quarter', style={'margin-top': '20px'}),
dcc.Graph(id='shots_by_quarter', figure={})
],
style={
'padding': '2%'
}
)
# Create layout
app = Dash(__name__)
app.layout = html.Div([
shot_distribution_graph, filter, shots_by_quarter
], style={'width': '1650px', 'margin': 'auto'})
# Run app
if __name__ == '__main__':
app.run(debug=True)
结果仪表盘包含两个图表 — 图片由作者提供
现在,这个是功能性的。但它真的不太吸引人……HTML 和 CSS 将是使其视觉上更具吸引力的工具(尽管我在设计方面不太擅长)。
然而,这超出了我们的范围。我们的目标是创建一个仪表盘,我们已经实现了。这虽然非常简单,但如果你能够理解我们做的所有内容,最终的仪表盘是如何完成的,我在开始时分享了,也将在下一节再次分享,你就不会对它感到陌生(同样,底部的代码是自由获取的)。
总结
今天我们构建了一个包含两个图表和一个下拉框的仪表盘。但我们可以根据需要进行扩展。例如,了解如何放置一个下拉框后,我们就知道如何放置一个滑块。那两个呢?
我们今天学到的所有内容都可以应用于任何你想要可视化的数据, 从经济报告到医疗结果或广告活动洞察。我选择将其应用于足球,因为我对足球充满热情,但请将这些知识普及到其他领域。
了解如何放置两个图表后,我们可以创建更多的图表。而且是不同类型的:一个展示助攻,另一个展示球员在场上的影响,还有比较他的进球和预期进球……再加上一点 HTML 和 CSS,我们就能得到最终的仪表盘:
最终仪表盘 — 图片由作者提供
我真的希望你能看到这个工具的强大之处。
Dash 和 Plotly 必须是任何数据分析师的技能库中的必备工具。它们是非常棒的库,我们可以用来以高度定制的方式分享我们的数据和见解——即根据你的需求进行调整——并且容易理解。
**Thanks for reading the post!**
I really hope you enjoyed it and found it insightful.
Follow me and subscribe to my mail list for more
content like this one, it helps a lot!
**@polmarin**
资源
[1] Plotly: 低代码数据应用开发
[2] Dash 文档和用户指南 — Plotly
[3] 免费数据 | StatsBomb
[4] Pipenv: 人性化的 Python 开发工作流程
[5] Plotly & Dash 项目代码 — GitHub
你的数据(终于)在云端了。现在,别再那么依赖本地了
原文:
towardsdatascience.com/your-datas-finally-in-the-cloud-now-stop-acting-so-on-prem-bbb7b4f35529?source=collection_archive---------2-----------------------#2023-08-16
现代数据栈允许你以不同的方式操作,而不仅仅是在更大的规模上。充分利用它。
Barr Moses
·
关注 发表在 Towards Data Science ·10 分钟阅读·2023 年 8 月 16 日
--
图片由 Massimo Botturi 提供,来自 Unsplash
想象一下,你在大部分职业生涯中一直用锤子和钉子建房子,而我给了你一把钉枪。但你却不是将它对准木头按下扳机,而是将其侧向放置,就像用锤子一样敲钉子。
你可能会认为这既昂贵又效果不佳,而网站的检查员则会正确地将其视为安全隐患。
好吧,那是因为你正在使用现代工具,但却带着遗留的思维和流程。虽然这个类比并不完美地概括了某些数据团队从本地到现代数据栈后的运作方式,但它很接近。
团队很快理解到超弹性计算和存储服务如何使他们能够处理以前从未见过的多样数据类型和速度,但他们并不总是理解云对其工作流程的影响。
所以也许对这些最近迁移的数据团队来说,一个更好的类比是,如果我给你 1,000 把钉子枪,然后看着你将它们全部横着放,以同时钉 1,000 个钉子。
无论如何,重要的是要理解现代数据栈不仅仅允许你更大更快地存储和处理数据,它允许你从根本上以不同的方式处理数据,以实现新的目标并提取不同类型的价值。
这部分是由于规模和速度的增加,但也是由于更丰富的元数据和生态系统中的更无缝集成。
图片由 Shane Murray 和作者提供。
在这篇文章中,我突出了我看到数据团队在云端行为变化的三种更常见方式,以及五种它们没有做到(但应该做到)的方式。让我们深入探讨一下。
数据团队在云端变化的三种方式
数据团队转向现代数据栈的原因有很多(不仅仅是因为首席财务官最终解放了预算)。这些用例通常是数据团队进入云端后的第一个也是最容易的行为转变。它们包括:
从 ETL 迁移到 ELT 以加快洞察时间
你不能随便将任何东西加载到本地数据库中——特别是如果你希望查询在周末之前返回的话。因此,这些数据团队需要仔细考虑要提取哪些数据以及如何通过在 Python 中硬编码的管道将其转换成最终状态。
这就像是为每个数据消费者定制特定的餐点,而不是提供自助餐,就像任何去过邮轮的人都知道的那样,当你需要满足整个组织对数据的无限需求时,自助餐才是最好的选择。
这正是 AutoTrader UK 技术负责人 Edward Kent 与我的团队去年讨论数据信任和自助分析需求的情况。
“我们希望赋能 AutoTrader 及其客户,使其能够做出数据驱动的决策,并通过自助平台实现数据的民主化……在我们将受信任的本地系统迁移到云端时,那些旧系统的用户需要相信新的云技术与他们过去使用的旧系统一样可靠,”他说。
当数据团队迁移到现代数据堆栈时,他们欣然采用如 Fivetran 这样的自动化数据摄取工具或如 dbt 和 Spark 这样的转化工具,以及更复杂的数据策展策略。分析自助服务开启了一个全新的领域,谁来负责数据建模并不总是很明确,但总体而言,这是一种更高效的方式来解决分析(和其他!)使用案例。
实时数据用于操作决策
在现代数据堆栈中,数据可以快速移动,不再仅仅用于每日指标的脉搏检查。数据团队可以利用Delta 实时表、Snowpark、Kafka、Kinesis、微批处理等更多工具。
并非每个团队都有实时数据的使用案例,但那些有的团队通常都非常清楚。这些通常是需要操作支持的物流密集型公司,或者是将强大报告集成到产品中的科技公司(尽管后一类公司中有相当一部分是在云中诞生的)。
挑战仍然存在,这些挑战有时涉及并行架构(分析批处理和实时流)并试图达到大多数人希望的质量控制水平。但大多数数据领导者很快理解了能够更直接支持实时操作决策的价值。
生成式 AI 和机器学习
数据团队对生成式 AI 浪潮有深刻的认识,许多行业观察者怀疑这项新兴技术正在推动基础设施现代化和利用的巨大浪潮。
但在 ChatGPT 生成其第一篇文章之前,机器学习应用已经从前沿技术逐渐成为数据密集型行业的标准最佳实践,包括媒体、电商和广告。
目前,许多数据团队一有可扩展的存储和计算资源就会立即开始检查这些使用案例(尽管有些团队可能会从构建更好的基础中受益)。
如果你最近迁移到云端,但还没有询问业务如何更好地支持这些使用案例,请将其安排到日程中。本周,或者今天。你会感谢我的。
数据团队仍像在本地部署一样工作的 5 种方式
现在,让我们看看一些以前在本地的数据团队可能较慢利用的未实现的机会。
附注:我想澄清的是,虽然我之前的类比有些幽默,但我并不是在嘲笑那些仍在本地操作或在云中使用以下流程的团队。变革是困难的,尤其是在面对持续的积压和不断增加的需求时,变革更为艰难。
数据测试
本地的数据团队没有规模或来自中央查询日志或现代表格格式的丰富元数据,因此无法轻松运行机器学习驱动的异常检测(换句话说,数据可观察性)。
相反,他们与领域团队合作,以理解数据质量要求,并将这些要求转化为 SQL 规则或数据测试。例如,customer_id 应该永远不能为 NULL,或者 currency_conversion 应该永远不能有负值。还有一些本地工具旨在帮助加速和管理这一过程。
当这些数据团队迁移到云端时,他们首先想到的不是以不同的方式处理数据质量,而是以云规模执行数据测试。这是他们所熟悉的做法。
我看到过一些案例研究,读起来像恐怖故事(不,我不会提名字),数据工程团队在数千个 DAG 上运行数百万个任务,以监控数百个管道中的数据质量。哎呀!
当你运行五十万条数据测试时会发生什么?我告诉你。即使绝大多数测试通过,仍然会有数万条测试失败。而且这些测试明天还会失败,因为没有上下文来加快根本原因分析,甚至不知道从哪里开始分类。
你不知何故使你的团队产生了警报疲劳,却仍未达到所需的覆盖范围。更不用说大规模的数据测试既耗时又昂贵。
图片由作者提供。来源。
相反,数据团队应该利用能够检测、分类和帮助根本原因分析的技术,同时将数据测试(或自定义监控)保留在最重要的表格中的最清晰的阈值上。
数据血缘的数据建模
支持中央数据模型有很多合理的理由,你可能在一篇精彩的 Chad Sanderson 博客中读到过这些理由。
但每隔一段时间,我会遇到在云端投入大量时间和资源来维护数据模型的团队,唯一的原因是维护和理解 数据血统。在本地部署时,这基本上是你最好的选择,除非你想阅读长篇的 SQL 代码,并创建一个满是记忆卡片和纱线的公告板,让你的另一半开始问你是否没事。
照片由 Jason Goodman 提供,来自 Unsplash
(“不,Lior!我没事,我只是试图理解这个 WHERE 子句是如何改变这个 JOIN 中的列的!”)
现代数据堆栈中的多个工具——包括数据目录、数据可观测性平台和数据仓库——可以利用元数据来创建自动化的数据血统。这只是一个 选择风味 的问题。
客户细分
在旧的世界里,对客户的视角是平面的,而我们知道它实际上应该是一个 360 度的全球视图。
这种有限的客户视角是由于预建数据(ETL)、实验约束和本地数据库计算更复杂查询(独特计数、不同值)所需的时间较长。
不幸的是,数据团队在云端移除了这些约束之后,并不总是会从他们的客户视角中移除盲点。这通常有几个原因,但最大的罪魁祸首仍然是老式的 数据孤岛。
市场营销团队运营的客户数据平台仍然充满活力。该团队可以通过丰富他们对潜在客户和客户的视角来从数据仓库/湖泊中的其他领域数据中受益,但多年来建立的习惯和责任感很难打破。
因此,与其根据最高的估计终身价值来定位潜在客户,不如将目标放在每个线索的成本或每次点击的成本上。这是数据团队以直接且高度可见的方式为组织贡献价值的一个错失机会。
导出外部数据共享
复制和导出数据是最糟糕的。这不仅耗时、增加成本,还会导致版本控制问题,使得访问控制几乎不可能。
与其利用你的现代数据堆栈创建一个将数据以极快速度导出到典型合作伙伴的管道,更多的云端数据团队应当利用零拷贝数据共享。就像管理云文件的权限在很大程度上取代了电子邮件附件,零拷贝数据共享允许在不将数据从宿主环境中移走的情况下访问数据。
Snowflake 和 Databricks 在过去两年的年度峰会上宣布并大力推广了他们的数据共享技术,更多的数据团队需要开始加以利用。
成本和性能优化
在许多本地系统中,数据库管理员负责监督所有可能影响整体性能的变量,并根据需要进行调节。
然而,在现代数据堆栈中,你通常会看到两种极端情况。
在一些情况下,DBA 的角色仍然存在,或者外包给一个中央数据平台团队,如果管理不善,可能会造成瓶颈。然而,更常见的情况是,成本或性能优化变成了“无人区”,直到一笔特别高额的账单送到 CFO 的桌上。
这通常发生在数据团队没有正确的成本监控工具时,且出现了特别激进的异常事件(可能是错误的代码或爆炸性 JOIN)。
此外,一些数据团队未能充分利用“按使用付费”的模式,而是选择承诺预定数量的积分(通常有折扣)……然后超出这个数额。虽然积分承诺合同本身没有问题,但如果不加以注意,这种预留时间可能会形成一些坏习惯,随着时间的推移逐渐累积。
云计算使得 DevOps/DataOps 可以采用更加连续、协作和集成的方法,FinOps 也是如此。我看到的最成功的团队是那些将成本优化融入日常工作流,并激励与成本最相关的人员的团队。
“消费型定价的兴起使得这一点更加关键,因为新功能的发布可能导致成本指数级上升,”Tenable 的 Tom Milner 说。“作为我的团队的负责人,我每天检查我们的 Snowflake 成本,并将任何费用激增作为我们待办事项的优先项。”
这会创建反馈循环、共享学习以及成千上万的小型快速修复,从而带来重大成果。
Stijn Zanders 在 Aiven 说:“我们设立了警报,当有人查询任何可能花费我们超过 $1 的内容时。这是一个相当低的阈值,但我们发现费用不需要超过这个数额。我们发现这是一个良好的反馈循环。[当这个警报出现时],通常是有人忘记了在分区或聚集列上设置过滤器,他们可以迅速学习。”
最后,在团队之间部署收费回收模型,在云计算之前的时代几乎是不可想象的,这是一项复杂但最终值得的工作,我希望看到更多的数据团队对此进行评估。
成为一个“学习型”人
微软首席执行官 Satya Nadella 曾谈到 他如何有意将公司的组织文化从“全知型”转变为“学习型”。这将是我对数据领导者的最佳建议,无论你是刚刚迁移还是多年来一直处于数据现代化的前沿。
我理解这可能是多么令人不知所措。新技术的出现迅猛而猛烈,供应商的推销也不容忽视。最终,这不仅仅是关于拥有行业内“最现代化”的数据堆栈,而是关于在现代工具、顶尖人才和最佳实践之间创造对齐。
为了做到这一点,始终准备学习你的同行如何应对你面临的许多挑战。参与社交媒体,阅读 Medium,关注分析师,并参加会议。我会在那里见到你!
在云环境中,其他哪些本地数据工程活动不再有意义?如有任何意见或问题,请联系 Barr ,通过 LinkedIn。
你的数据集有缺失值?什么都不做!
原文:
towardsdatascience.com/your-dataset-has-missing-values-do-nothing-10d1633b3727
模型能够比填补方法更有效地处理缺失值。这是一个实证证明。
Samuele Mazzanti
·发表于 Towards Data Science ·10 分钟阅读·2023 年 10 月 9 日
--
[作者提供的图片]
缺失值在真实数据集中非常常见。随着时间的推移,许多方法被提出以解决这个问题。通常,这些方法要么是删除包含缺失值的数据,要么是使用一些技术进行填补。
在这篇文章中,我将测试第三种替代方案:
什么都不做。
实际上,最适合表格数据集的模型(即 XGBoost、LightGBM 和 CatBoost)可以原生处理缺失值。因此,我将尝试回答的问题是:
这些模型是否能有效处理缺失值,还是通过预处理填补能获得更好的结果?
谁说我们应该关心缺失值?
似乎存在一种普遍的信念,认为我们必须对缺失值做一些事情。例如,我问了 ChatGPT,如果我的数据集包含缺失值应该怎么办,它建议了 10 种不同的解决方法(你可以在这里阅读完整回答)。
那么,这种信念来自哪里呢?
通常,这些观点源于历史模型,特别是线性回归。这也是如此。让我们看看原因。
假设我们有这样一个数据集:
一个缺失值的数据集。[作者提供的图片]
如果我们尝试在这些特征上训练线性回归,会出现错误。事实上,为了能够进行预测,线性回归需要将每个特征乘以一个数值系数。如果一个或多个特征缺失,就无法对该行进行预测。
这就是为什么提出了许多填补方法。例如,最简单的方法之一是用特征的均值替换缺失值。
使用特征均值填补。[图片由作者提供]
另一种更复杂的方法是利用变量之间的关系来预测填补特定条目的最可能值。这意味着为每个特征训练一个预测模型(使用其他特征作为预测变量)。
迭代填补:每个特征通过其他所有特征来估计。[图片由作者提供]
然而,并非所有模型都像线性回归一样。
实际上,碰巧的是,表格任务中表现最好的模型(即树模型,如 XGBoost、LightGBM 和 CatBoost)可以原生处理缺失值。
这怎么可能?
因为在树状结构中,缺失值可以像其他值一样处理,即模型可以将它们分配到树的某个分支上。例如,这张图片来自 XGBoost 文档:
树模型如何处理缺失值。[图片来自XGBoost 文档]
正如你所见,对于每个分裂,XGBoost 会选择一个默认分支,将缺失值(如果有的话)路由到该分支上。
所以,我们是否说因为这些模型可以处理缺失值,我们就应该避免填补?我从未这么说过。
作为数据科学家,我们通常希望在实践中有深刻的了解。因此,接下来的段落将重点比较有无填补的模型性能,并观察哪种方法表现更好。
使用真实数据集进行实验
我们的目标是比较两种方法:
-
在具有缺失值的数据集上训练和测试模型。
-
在没有缺失值的数据集上训练和测试模型(在这些缺失值通过某种填补方法填补后)。
我将使用 14 个在Pycaret中提供的真实数据集(这是一个受MIT 许可证保护的 Python 库)。
这些数据集不包含缺失值,所以我们需要人为制造这些缺失值。我将这个过程称为空值播撒,因为它涉及在原始数据集中散布空值(即“取消”一些原始值)。
我们要取消多少值?为了确保实验具有代表性,我们将尝试不同百分比的空值:5%、10%、20% 和 50%。
不同百分比的空值播撒。[图片由作者提供]
我使用两种不同的策略来创建空值:
-
随机:为数据集的每个条目分配一个介于 0 和 1 之间的随机值,如果小于阈值,则该条目被取消。
-
非随机:对于每个特征,我将值按升序或降序排序,并根据值在排序序列中的位置分配一个相应的取消概率。
此外,对于每种组合,我将尝试 25 种不同的随机训练/测试划分。这样做的目的是确保我们观察到的结果在多次重复中是一致的,而不仅仅是偶然的。
总结一下,这些都是我将尝试的所有组合。
形成我们实验的组合。[图像来源:作者]
对于这 2,800 种组合中的每一种,我将计算 LightGBM 在三种不同方法下的平均精度(在测试集上):
-
使用原始数据集(没有缺失值)。我将称这个平均精度为
ap_original
。 -
使用包含我已经撒入缺失值的数据集。我将称这个平均精度为
ap_noimpute
。 -
使用已用 Scikit-Learn 的 IterativeImputer 填充缺失值的数据集。我将称这个平均精度为
ap_impute
。
让我们借助图示来阐明这三种指标之间的差异:
3 个版本的数据集。每个版本上训练了不同的 LightGBM。[图像来源:作者]
结果
我尝试了上述所有 2,800 种组合,并跟踪了每一种的ap_original
、ap_noimpute
和ap_impute
。我将结果存储在类似这样的表格中:
实验结果。[图像来源:作者]
这个表格有 112 行(即 14 个数据集 x 2 种缺失值撒入策略 x 4 种缺失频率)。前三列指示哪个数据集、缺失值撒入策略和缺失频率标识那一行。然后,有三列存储该方法在 25 次 bootstrap 迭代中报告的平均精度。
为了更加清楚,让我们看看第一行的第四列。这是一个包含 25 个元素的数组:每个元素都是 LightGBM 在原始数据集上(因此称之为ap_original
)在特定训练/测试划分(即 bootstrap 迭代)中实现的平均精度,数据集“bank”包含 5%的随机撒入缺失值。
当然,我们对每一个单独的 bootstrap 迭代并不感兴趣,所以我们需要以某种方式聚合这些数组。由于我们希望比较三种方法,最简单的聚合方式是统计一种方法优于另一种方法的次数(即它具有更高的平均精度)。
因此,对于表格中的每一行,我计算了:
-
ap_original
>ap_noimpute
,这意味着在原始数据集上的模型优于在包含缺失值的数据集上的模型。 -
ap_noimpute
>ap_impute
,这意味着在包含缺失值的数据集上的模型优于在使用 IterativeImputer 填充的数据集上的模型。
这是结果:
实验结果。[图像来源:作者]
例如,如果我们取第一行的最后一列,这意味着在 25 次迭代中,ap_noimpute
在 60%的情况下大于ap_impute
。
让我们看看名为original_>_noimpute
的列。显然,大多数情况下是 100%。这也是合理的:模型在原始数据集上训练的效果优于在取消了一些条目的数据集上训练的模型。
但对我们来说最重要的信息在最后一列。实际上:
-
当
noimpute_>_impute
大于 50%时,这意味着在大多数情况下,基于空数据集训练的模型优于基于填补数据集训练的模型。 -
当
noimpute_>_impute
小于 50%时,情况正好相反。
所以我们可能会倾向于简单地取这一列的均值,并根据全局均值是高于还是低于 50%来决定哪种方法效果更好。
但是如果我们这样做,我们可能会被偶然性所误导。实际上,如果一个值接近 50%,比如 48%或 52%,这可能很容易是由于随机性造成的。为了考虑这一点,我们需要将其框定为一个统计测试。
设定统计测试
为了避免被偶然性欺骗,我将采取几个预防措施。
首先,我只保留那些原始数据集上的平均精度大于缺失数据集上的平均精度超过 90%的情况(换句话说,我只保留original_>_noimpute
> .90 的行)。
我这样做是因为我想保留只有那些缺失值对模型性能有明显负面影响的情况。在这样做之后,最初 112 行的表格中,只剩下 48 行。
其次,我需要计算一个“显著性阈值”,帮助我们理解一个值是否显著。
我们已经说过,当一个值非常接近 50%时,比如 48%或 52%,那么这很可能只是由于偶然性造成的。但是“非常接近”到底有多接近呢?要回答这个问题,我们需要一些统计数据。
我们想要测试的假设(即零假设)是填补或不填补没有区别。这就像说ap_impute
大于ap_noimpute
(或反之)的概率是 50%。由于我们有 25 次独立的迭代,我们可以通过二项分布计算获得特定结果的概率。
例如,假设在 25 次迭代中,ap_impute
在 10 次迭代中优于ap_noimpute
。这个结果与我们的假设有多兼容?
from scipy.stats import binom
binom.cdf(k=10, n=25, p=.50) * 2
# result: 0.42435622
因此,获得一个与 10 一样极端的结果的概率,在我们的假设下,是 42%。
请注意,我将二项分布的累积分布函数乘以 2,因为我们对双尾 p 值感兴趣。我们可以通过查看与任何可能结果相关的 p 值来再次确认这一点:
与任何可能结果相关的 p 值(来自 25 次迭代)。 [图片来源:作者]
与 12 和 13 相关的 p 值恰好是 100%。这也是合理的:获得至少与 12 或 13 一样极端的结果的概率必然是 100%,因为它们是最不极端的结果,鉴于我们的假设。
那么,我们的显著性水平是多少?按照惯例,我将取 1%的显著性水平。然而,我们必须考虑到我们有许多次运行,而不仅仅是一次,因此我们必须相应地调整显著性水平。由于我们有 48 次运行,我将使用Bonferroni 校正,简单地将 1%除以 48,得到最终显著性水平 0.0002。
将这个数字与上述 p 值进行比较,这意味着我们将只在运行中成功次数少于 3 次或多于 22 次(包括)时考虑显著。
现在我们已经采取措施以避免被偶然因素欺骗,我们准备查看结果。
为了简化起见,我们将度量标准设为ap_noimpute
大于ap_impute
的次数百分比。例如,如果基于包含缺失值的数据集的模型在 25 次迭代中的 10 次中表现出比插补数据集的模型更好的平均精度,则该度量为 40%。
由于我们有 48 次运行,我们将得到 48 个值。因此,让我们创建一个柱状图来查看所有值。
实验结果。[作者提供的图片]
红色虚线标识了显著性阈值:任何小于 12%(包括)或大于 88%(包括)的值都是显著的(12%和 88%分别对应 3/25 和 22/25)。
从柱状图中,我们可以看到当我们不插补缺失值时,在 48 次运行中的 30 次中获得了更好的结果(63%)。在这 30 个案例中的 7 个,结果极端到根据 1% p 值及 Bonferroni 调整也是统计显著的。相反,在 18 个插补胜出的案例中,结果从未显著地不同于纯粹的偶然。
基于这些结果,我们可以说插补与不插补之间的差异要么不显著,要么显著地偏向于不插补。
简而言之,没有理由插补缺失值。
结论
在这篇文章中,我们通过实证证明了插补与不插补之间的差异要么不显著,要么显著地偏向于不插补。如果你还考虑到不插补缺失值会使你的管道更干净、更快速,那么当可以做到这一点时,留空数据集中的空值应该是标准。
当然,这并非总是可能的。一些不能处理缺失值的模型,比如线性回归或 K 均值。
不过,好消息是,当你使用最常见的表格任务模型(即基于树的模型)时,不插补缺失值而让模型处理它们是最有效和高效的方法。
你可以通过 这个笔记本复现本文中使用的所有代码。
感谢阅读!
如果你觉得我的工作有用,可以订阅 每次我发布新文章时收到邮件 (通常每月一次)。
想要对我的工作表示支持吗?你可以 请我喝杯卡布奇诺。
如果你愿意, 在 Linkedin 上添加我!
你的特征重要吗?这并不意味着它们是好的
原文:
towardsdatascience.com/your-features-are-important-it-doesnt-mean-they-are-good-ff468ae2e3d4
“特征重要性”是不够的。如果你想知道哪些特征对模型有益,你还需要关注“错误贡献”。
Samuele Mazzanti
·发表于 Towards Data Science ·10 分钟阅读·2023 年 8 月 21 日
--
[作者提供的图片]
“重要”和“好”并不是同义词
“特征重要性”这一概念在机器学习中被广泛使用,是最基本的模型可解释性类型。例如,它在递归特征消除(RFE)中被使用,用于迭代地删除模型中最不重要的特征。
然而,对于这一点存在一种误解。
一个特征重要并不意味着它对模型有益!
实际上,当我们说一个特征重要时,这仅仅意味着该特征对模型的预测贡献很高。但我们应考虑到这种贡献可能是错误的。
举个简单的例子:一个数据科学家不小心忘记了模型特征中的客户 ID。模型将客户 ID 作为一个高度预测性特征。因此,即使这个特征实际上在降低模型性能,因为它无法在未见数据上良好运行,这个特征的重要性也会很高。
为了让事情更清楚,我们需要区分两个概念:
-
预测贡献:预测中有多少部分是由于特征;这等同于特征重要性。
-
错误贡献:预测错误中有多少部分是由于模型中存在该特征。
在本文中,我们将探讨如何计算这些量,并如何利用它们获得有关预测模型的有价值的见解(并加以改进)。
注意:本文专注于回归案例。如果你对分类案例更感兴趣,可以阅读“哪些特征对你的分类模型有害?”
从一个玩具示例开始
假设我们建立了一个模型,以根据人们的工作、年龄和国籍来预测收入。现在我们使用该模型对三个人进行预测。
因此,我们有实际值、模型预测和结果误差:
实际值、模型预测和绝对误差(以千美元计)。[作者提供的图片]
计算“预测贡献”
当我们有一个预测模型时,我们可以始终将模型预测分解为各个特征带来的贡献。这可以通过 SHAP 值来完成(如果你不知道 SHAP 值的工作原理,可以阅读我的文章:SHAP 值解释:正如你希望有人向你解释的那样)。
所以,假设这些是我们模型对三个人的 SHAP 值。
我们模型预测的 SHAP 值(以千美元计)。[作者提供的图片]
SHAP 值的主要特性是它们是可加的。这意味着——通过对每一行求和——我们将得到该个体的模型预测。例如,如果我们取第二行:72k $ +3k $ -22k $ = 53k $,这正是模型对第二个体的预测。
现在,SHAP 值是特征对我们预测重要性的良好指标。确实,SHAP 值的绝对值越高,特征对该特定个体预测的影响越大。注意,我在这里讨论的是绝对 SHAP 值,因为符号并不重要:一个特征无论是使预测值上升还是下降,其重要性是相同的。
因此,特征的预测贡献等于该特征绝对 SHAP 值的均值。如果你在 Pandas 数据框中存储了 SHAP 值,这非常简单:
prediction_contribution = shap_values.abs().mean()
在我们的示例中,结果如下:
预测贡献。[作者提供的图片]
如你所见,工作(job)显然是最重要的特征,因为它在平均情况下占据了最终预测的 71.67k $。国籍(nationality)和年龄(age)分别是第二和第三重要的特征。
然而,某个特征对最终预测的重要性并不能说明该特征的表现。为了考虑这一方面,我们需要计算“误差贡献”。
计算“误差贡献”
假设我们想回答以下问题:“如果模型没有特征工作,会做出什么预测?”SHAP 值允许我们回答这个问题。事实上,由于它们是可加的,我们只需从模型做出的预测中减去与特征工作相关的 SHAP 值即可。
当然,我们可以对每个特征重复这一过程。在 Pandas 中:
y_pred_wo_feature = shap_values.apply(lambda feature: y_pred - feature)
结果如下:
如果去掉相应特征后得到的预测值。[图片由作者提供]
这意味着,如果我们没有工作这个特征,那么模型会预测第一位个体 20k 美元,第二位个体-19k 美元,第三位个体-8k 美元。相反,如果我们没有年龄这个特征,模型会预测第一位个体 73k 美元,第二位个体 50k 美元,以此类推。
如你所见,如果我们去掉不同的特征,每个个体的预测变化很大。因此,预测误差也会非常不同。我们可以轻松地计算它们:
abs_error_wo_feature = y_pred_wo_feature.apply(lambda feature: (y_true - feature).abs())
结果如下:
如果去掉相应特征后得到的绝对误差。[图片由作者提供]
这些是我们如果去掉相应特征后会得到的错误。直观地说,如果误差很小,那么去掉该特征对模型没有问题——甚至是有益的。如果误差很高,那么去掉特征则不是一个好主意。
但我们还可以做更多。事实上,我们可以计算完整模型的错误与去掉特征后得到的错误之间的差异:
error_diff = abs_error_wo_feature.apply(lambda feature: abs_error - feature)
这就是:
模型的错误与去掉特征后我们会得到的错误之间的差异。[图片由作者提供]
如果这个数字是:
-
如果是负的,那么特征的存在会减少预测误差,因此该特征对该观察结果很有效!
-
如果是正的,那么特征的存在会导致预测误差增加,因此该特征对该观察结果是不利的。
我们可以计算“误差贡献”,作为每个特征这些值的均值。在 Pandas 中:
error_contribution = error_diff.mean()
结果如下:
误差贡献。[图片由作者提供]
如果这个值是正的,那么这意味着,平均而言,特征的存在导致模型的错误增加。因此,没有这个特征,预测会更好。换句话说,这个特征的负面影响大于正面影响!
相反,这个值越负,特征对预测的益处越大,因为其存在导致更小的误差。
让我们尝试在实际数据集上使用这些概念。
预测黄金回报
从现在起,我将使用来自Pycaret(一个MIT 许可的 Python 库)的数据集。该数据集名为“Gold”,包含财务数据的时间序列。
数据集样本。特征都以百分比表示,因此 -4.07 意味着 -4.07% 的回报。[图片由作者提供]
特征由观察时刻前 22、14、7 和 1 天的金融资产回报组成(“T-22”、“T-14”、“T-7”、“T-1”)。这是所有用作预测特征的金融资产的详尽列表:
可用资产列表。每个资产在时间 -22、-14、-7 和 -1 被观察到。[作者提供的图片]
总共有 120 个特征。
目标是预测 22 天后的黄金价格(回报) (“Gold_T+22”)。让我们来看看目标变量。
变量的直方图。[作者提供的图片]
一旦加载了数据集,这些是我进行的步骤:
-
随机拆分完整数据集:33%分配给训练数据集,另 33%分配给验证数据集,其余 33%分配给测试数据集。
-
在训练数据集上训练一个 LightGBM 回归模型。
-
使用前一步训练的模型对训练、验证和测试数据集进行预测。
-
计算训练、验证和测试数据集的 SHAP 值,使用 Python 库“shap”。
-
计算每个特征在每个数据集上的预测贡献和错误贡献(训练集、验证集和测试集),使用我们在前一段看到的代码。
比较预测贡献和错误贡献
比较训练数据集中错误贡献和预测贡献。我们将使用散点图,其中点表示模型的 120 个特征。
预测贡献与错误贡献(在训练数据集上)。[作者提供的图片]
在训练集中,预测贡献与错误贡献之间存在高度负相关。
这很有意义:因为模型在训练数据集上学习,它倾向于将高重要性(即高预测贡献)分配给那些导致预测错误大幅减少的特征(即高度负的错误贡献)。
但这并没有增加我们对知识的了解,对吧?
确实,我们真正关心的是验证数据集。验证数据集实际上是我们可以用来了解特征在新数据上表现的最佳代理。因此,让我们在验证集上做相同的比较。
预测贡献与错误贡献(在验证数据集上)。[作者提供的图片]
从这个图中,我们可以提取出一些更有趣的信息。
图表右下角的特征是模型正确地赋予高重要性的特征,因为这些特征实际上降低了预测错误。
同时,请注意“Gold_T-22”(观察期前 22 天的黄金回报)与模型赋予的权重相比表现非常好。这意味着这个特征可能存在欠拟合。这一点特别有趣,因为黄金是我们试图预测的资产(“Gold_T+22”)。
另一方面,误差贡献高于 0 的特征使我们的预测变得更差。例如,“US Bond ETF_T-1”平均改变了模型预测 0.092%(预测贡献),但使模型预测比没有该特征时平均差 0.013%(误差贡献)。
我们可以假设所有具有高误差贡献(相对于其预测贡献)的特征可能存在过拟合,或者通常它们在训练集和验证集中的表现不同。
让我们看看哪些特征的误差贡献最大。
按误差贡献递减排序的特征。[图片来源]
现在是误差贡献最低的特征:
按误差贡献递增排序的特征。[图片来源]
有趣的是,我们可以观察到所有高误差贡献的特征与 T-1(观察时刻前 1 天)相关,而几乎所有低误差贡献的特征与 T-22(观察时刻前 22 天)相关。
这似乎表明最新的特征容易过拟合,而时间更久远的特征往往更容易泛化。
请注意,如果没有误差贡献,我们永远不会知道这个洞察。
使用误差贡献的 RFE
传统递归特征消除(RFE)方法基于移除不重要的特征。这相当于首先移除预测贡献小的特征。
然而,根据我们在上一段所说的,首先移除误差贡献最大的特征会更有意义。
为了验证我们的直觉,让我们比较这两种方法:
-
传统 RFE:首先移除无用特征(预测贡献最低)。
-
我们的 RFE:首先移除有害特征(误差贡献最高)。
让我们看看验证集上的结果:
两种策略在验证集上的平均绝对误差。[图片来源]
每种方法的最佳迭代已被圈出:传统 RFE(蓝线)的模型有 19 个特征,而我们的 RFE(橙线)的模型有 17 个特征。
一般而言,我们的方法似乎效果良好:移除误差贡献最大的特征比移除预测贡献最大的特征会导致一致较小的 MAE。
然而,你可能会认为这只因为我们对验证集进行了过拟合。毕竟,我们关心的是在测试集上获得的结果。
所以让我们看看在测试集上的相同比较。
测试集上两种策略的均绝对误差。[作者提供的图片]
结果与之前的相似。即使两条线之间的距离较小,但通过移除最高误差贡献者获得的 MAE 显然优于通过移除最低预测贡献者获得的 MAE。
由于我们选择了在验证集上导致最小 MAE 的模型,让我们看看它们在测试集上的结果:
-
RFE-预测贡献(19 个特征)。测试集上的 MAE:2.04。
-
RFE-误差贡献(17 个特征)。测试集上的 MAE:1.94。
因此,使用我们的方法的最佳 MAE 比传统 RFE 提高了 5%!
结论
特征重要性概念在机器学习中起着基础性的作用。然而,“重要性”这一概念常常被误解为“优越性”。
为了区分这两个方面,我们引入了两个概念:预测贡献和误差贡献。这两个概念都基于验证数据集的 SHAP 值,文章中我们展示了计算这些值的 Python 代码。
我们还在一个真实的金融数据集上进行了尝试(该任务是预测黄金价格),并证明基于误差贡献的递归特征消除方法相比于基于预测贡献的传统 RFE,能使均绝对误差提高 5%。
所有用于本文的代码可以在 这个笔记本找到。
感谢阅读!
如果你觉得我的工作有用,你可以 每次我发布新文章时收到邮件 (通常是每月一次)。
如果你想支持我的工作,你可以 请我喝咖啡。
如果你愿意, 可以在 Linkedin 加我!
您的第一个推荐系统:从数据准备到机器学习调试与改进评估
原文:
towardsdatascience.com/your-first-recommendation-system-from-data-preparation-to-ml-debugging-and-improvements-assessment-eb628573436?source=collection_archive---------8-----------------------#2023-02-02
解决您的问题,节省时间,避免错误
亚历山大·查普提科夫
·
关注 发布在 Towards Data Science ·12 分钟阅读·2023 年 2 月 2 日
--
图片由 Vska 提供,来源于 freepik.com
所以你已经开始开发你的第一个生产推荐系统,尽管你有编程和机器学习的经验,但你却被大量的新信息淹没,例如模型选择、指标选择、推断问题和质量保证。
我们覆盖了创建机器学习模型的第一个工作版本的步骤,包括数据处理、模型选择、指标选择、机器学习调试、结果解释和改进评估。
文章的代码在 这里,它可以作为你自己工作的起点。文件 rec_sys.ipynb 包含逐步指南。
数据集
首先,我们需要一个最小可用的数据集,包括 3 个实体:user
、item
、rating
。该数据集的每一条记录将告诉我们用户与物品的互动情况。对于本文,我们选择了一个数据集 MovieLens 1,该数据集包含 100k 条记录,涵盖 943 个独特用户和 1682 部电影。对于这个数据集,user
- UserId
,item
- MovieId
,rating
- Rating
。
MovieLens 还包含每部电影的元数据。这些信息按类型分类。我们将需要这些信息来解释预测结果。
预处理
这里有推荐系统的特别预处理步骤。我想跳过明显的步骤,如删除 NaN 元素、删除重复元素、数据清理。
评分生成
如果我们没有评分,则创建一个值为 1 的评分列。
df['rating'] = 1
如果评分不是显式的,可以通过各种聚合函数来创建,例如,根据互动次数、时长等。
实体编码 如果你在物品和用户字段中有对象,这些字段需要转换为数字格式。一个很好的方法是使用 LabelEncoder。
from sklearn.preprocessing import LabelEncoder
u_transf = LabelEncoder()
item_transf = LabelEncoder()
# encoding
df['user'] = u_transf.fit_transform(df['user'])
df['item'] = item_transf.fit_transform(df['item'])
# decoding
df['item'] = item_transf.inverse_transform(df['item'])
df['user'] = u_transf.inverse_transform(df['user'])
稀疏度指数
为了确保模型的质量训练,必须降低稀疏度指数。
这个指数的高值告诉我们什么?这意味着我们有很多用户没有观看很多电影,同时我们也有观众少的电影。用户不活跃和电影不受欢迎的程度越高,这个指数就越高。
这种情况最常发生在,例如,当所有用户的数量突然增加时。或者我们决定大幅增加我们的电影库,并且对新电影没有任何观看记录。
降低稀疏度对于训练至关重要。假设你加载了数据并尝试训练模型,但指标极低。你不需要开始寻找特殊的超参数或其他更好的模型。首先检查稀疏度指数。
从图表中可以看出,将该指数减少近 2%对指标有非常积极的影响。
图表显示 81%的用户是非活跃的(他们观看了不到 20 部电影)。他们需要被移除。这个函数将帮助我们完成这项任务:
def reduce_sparsity(df, min_items_per_user, min_user_per_item, user_col=USER_COL, item_col=ITEM_COL):
good_users = df[user_col].value_counts()[df[user_col].value_counts() > min_items_per_user].index
df = df[df[user_col].isin(good_users)]
good_items = df[item_col].value_counts()[df[item_col].value_counts() > min_user_per_item].index
df = df[df[item_col].isin(good_items)].reset_index(drop=1)
return df
所以我们不得不移除一些用户和电影,但这将帮助我们更好地训练模型。建议仔细减少稀疏度,并根据情况为每个数据集选择它。根据我的经验,大约 98%的稀疏度已经足够用于训练。
选择指标
有一些很好的文章详细介绍了推荐系统的流行指标。例如,Zuzanna Deutschman 的“推荐系统:机器学习指标和商业指标”和 Zahra Ahmad 的“推荐系统的自动评估:覆盖率、新颖性和多样性”。在这篇文章中,我决定专注于 4 个指标,这些指标可以作为一个最小集来帮助你入门。
Precision@k
P = (相关元素) / k
这是一个简单的指标,不考虑预测的顺序,因此它的值通常高于使用 MAP 的值。这个指标对模型的变化也很敏感,这对于模型监控和评估非常有用。它更容易解释,因此我们可以将其纳入我们的指标列表中。
MAP(平均准确率)
这个指标与之前的指标不同,它对于预测的顺序很重要,我们在推荐列表中越靠近错误的位置,惩罚就越大。
覆盖率
覆盖率 = 推荐中的唯一项数 / 所有唯一项数
该指标允许你查看推荐系统使用的电影百分比。这对企业来说通常非常重要,以确保他们网站上的内容(在这种情况下是电影)得到了充分利用。
多样性
这个指标的目的是计算推荐的多样性。
在 Zahra Ahmad 的“推荐系统的自动评估:覆盖率、新颖性和多样性”论文中,多样性是 top_n 的平均相似度。
但在这篇文章中,多样性将以不同的方式处理——作为唯一流派数量的中位数值。高多样性值意味着用户有机会发现新流派,丰富他们的体验,并在网站上花费更多时间。通常,这会提高留存率,并对收入产生积极影响。这种计算指标的方法对业务具有很高的可解释性,与抽象的均值相似度比不同。
指标解释
有一个优秀的推荐系统库,其中不仅包含模型本身,还有指标分析。研究这个表格让我们理解了指标的可能范围,并对模型评估有了直观认识。例如,Precision@k 值低于 0.02 的情况,在大多数情况下应被视为差。
因此,我们有与排名相关和不相关的质量指标。有些指标间接地对业务和财务负责,也有些对内容的使用和可用性负责。现在我们可以继续选择模型了。
模型选择
ALS 矩阵分解
这是一个很好的起始模型。用Spark编写,算法相对简单。
在训练过程中,模型初始化用户矩阵和项目矩阵,并以最小化重建评分矩阵的误差的方式训练它们。每个用户矩阵的向量是某个user
的表示,每个项目矩阵的向量是某个item
的表示。因此,预测是用户矩阵和项目矩阵中相应向量的标量乘法。
这是一个很好的起始模型,因为它实现起来非常简单,而且在研究阶段通常最好从简单模型开始,因为这个模型会快速学习,这意味着迭代时间会减少,从而大大加快项目进度。此外,模型不会占用过多内存,如果数据量很大,它也能应对,这将节省未来的基础设施成本。
双向变分自编码器(BiVAE)
该模型基于“双向变分自编码器用于协同过滤”的论文,由 Quoc Tuan TRUONG, Aghiles SALAH, Hady W. LAUW 撰写。
这个模型与之前的模型大致相似——在训练过程中,用户的 Theta 矩阵和 Beta 单元的矩阵会被训练。
但这个模型的结构比 ALS 复杂得多。我们有用户编码器和项目编码器,由一系列线性层组成。它们的任务分别是训练隐藏变量 Theta 和 Beta。解码和推断是通过这两个变量的标量乘法完成的。误差函数(在这种情况下为证据下界)在创建的用户向量和实际值之间计算两次,然后对项目编码器做同样的操作。
该模型已被选为比较表中的最佳模型。这个模型是 Cornac推荐系统模型库的一部分,底层使用 Pytorch。该模型有自定义的学习模式实现。它比其前身更慢,并且需要更多的支持和基础设施投入,但也许其高指标是值得的。
最受欢迎
是的,最简单的模型,在某些情况下也是最有效的。
df[item_col].value_counts()[:top_n]
虽然这种方法看起来过于简单,但它仍然可以让我们比较指标,并例如,了解机器学习模型与这样一个简单模型的差距。拥有这样的模型可以证明或反驳实施机器学习的必要性。
随机模型
该模型将仅随机生成项。它还在评估指标和机器学习模型预测时创建了必要的对比。
所以,我们选择了 4 个模型进行实验。一个针对速度优化,另一个针对质量,另外两个用于比较和更好地理解结果。我们现在准备开始训练。
模型训练
我们将一次训练 4 个模型,以便于比较它们。我们将使用recommenders库中的设置。
import json
from pathlib import Path
import pandas as pd
from sklearn.model_selection import train_test_split
from models import RandomModel, MostPopular, ModelALS, BIVAE, evaluate
from setup import ITEM_COL, TOP_K_METRICS, TOP_K_PRED
def main(out_folder='outputs'):
df = pd.read_csv('personalize.csv.zip', compression='zip').iloc[:, :3]
genres = pd.read_csv('movies.csv').rename({"movieId": ITEM_COL}, axis=1).dropna()
train, test = train_test_split(df, test_size=None, train_size=0.75, random_state=42)
metrics = {}
for model_cls in [BIVAE, ModelALS, RandomModel, MostPopular]:
model = model_cls()
model.fit(train)
preds = model.transform(TOP_K_PRED)
preds.to_csv(Path(out_folder) / f"{model_cls.__name__}_preds.csv", index=False)
metrics[model_cls.__name__] = evaluate(train, test, preds, genres, TOP_K_METRICS)
with open('outputs/metrics.json', 'w') as fp:
json.dump(metrics, fp)
if __name__ == "__main__":
main()
推理
推荐系统的预测有其自身的特点。预测所有用户的所有单位,我们得到一个 n-users x n-items 的矩阵。因此,我们只能预测在训练时出现的用户和单位。
然后:
-
从预测中移除那些在训练集中出现过的单位。
-
对每个用户按评级降序排序。否则,指标将会很差。
一个重要的点是,由于人们常常忘记移除看似的项目(或训练项目),这将对指标产生负面影响,因为排名靠前的将是那些不在测试数据集中项。此外,用户将会有一个负面的体验,因为模型会推荐他们已经看过的内容。
结果分析
如我们所见,BIVAE 模型展示了最佳的精准度指标,它能够非常准确地调整到用户的口味。而良好的精准度也有一个缺点——覆盖率和多样性比 ALS 模型差。这意味着大量内容可能永远不会被用户看到。在这种情况下,BIVAE 仍然看起来更可取。
但有时,多样性对业务比精准度更重要,这种情况可能发生,例如,如果你的站点上用户只看浪漫喜剧,而其他类型不受欢迎,但你希望鼓励你的观众观看其他类型。
同样的情况也适用于最受欢迎模型,这个模型的表现优于 ALS 机器学习模型。似乎我们为什么需要所有这些机器学习的复杂性,而我们已经有了现成的模型!但如果仔细观察,我们会发现覆盖率非常低,通常随着内容的增加,这个百分比会进一步下降。例如,我们只有 1682 部电影,但如果明天业务决定将库扩展到 10 万部电影,会发生什么?对于那个模型,覆盖率百分比会进一步下降。同样的规则也适用于相反的情况——数据越少,简单的最受欢迎模型越有可能有效。
也有趣的是考虑随机模型,因为在精确度指标方面,它与 ALS 相比看起来并不差,而且其覆盖率为 100%。再次提醒,不要急于下结论。这个数据集中的电影数量较少促成了这种成功。
最终,适合的模型是具有高质量和可接受的覆盖率及多样性的 BIVAE。我们可以以这个模型建立我们的基础推荐系统。
机器学习调试的提示
有时候调试机器学习可能非常困难。如果指标不太好,应该在哪里以及如何寻找问题?在代码中?在数据中?在模型选择或其超参数中?
有一些提示:
-
如果你的指标很低,例如 PrecisionK 低于 0.1,而你不知道原因是数据还是模型,或者可能是指标计算的问题,你可以使用 MovieLens 数据集并在其上训练你的模型。如果它在 MovieLens 上的指标也很低,那么问题在于模型;如果指标很好,那么可能的问题出在数据预处理和后处理阶段。
-
如果随机模型和最受欢迎模型的指标接近于机器学习模型,值得检查数据——可能唯一项的数量太少。这也可能发生在我们拥有非常少的数据时,或者可能是训练代码中有错误。
-
PrecisionK 值高于 0.5 看起来过高,值得检查是否脚本中有错误或我们是否过度降低了稀疏度指数。
-
始终比较在降低稀疏度指数后剩下的用户和项目的数量。在追求质量的过程中,有时你可能会失去几乎所有的用户,因此应该寻找一个折衷方案。
必要改进的评估
那么,如果我们想将模型投入生产,接下来该怎么做?我们需要弄清楚还需要做什么以及需要做多少工作。
BIVAE 优化和功能扩展
-
开箱即用的冷启动机制不存在。考虑到我们降低了稀疏度指数,意味着许多用户将根本没有预测。
-
需要实现一个批量预测算法。现在的机制是预测一个用户,即 batch_size=1,自然这大大降低了工作速度。
-
使用关于用户和物品(电影)的元数据。
后处理
- 可能需要先进的数据多样化算法,这有时可能与模型开发时间相当。
监控
-
新指标的发展
-
数据质量检查
当然,在每种情况下,这个列表可能会有所不同,我只列出了最可能的改进,或许在编制这样的列表之后,会有使用另一种具有这些功能的现成模型的愿望。
结论
-
我们测试了不同的模型,包括最新的解决方案,以确保它们的有效性,并学会了如何根据技术和业务指标选择最合适的模型。
-
做出了首批预测,这些预测已经可以用于展示给用户。
-
此外,我们概述了与模型开发及其投产(prod)相关的进一步行动计划。
您可以在这里找到文章的所有代码。
我想更多地讲述 BiVAE 架构、冷启动和增加多样性、下游任务如项目/用户相似性、如何控制质量以及在线或离线推断,关于如何将这些全部转移到管道中。但这些超出了文章的范围,如果读者喜欢这篇文章,那么可以发布续集,我会详细讲述所有这些内容。
喜欢这个故事吗?
订阅 以便在我发布新故事时收到通知。
参考文献:
[1] F. Maxwell Harper 和 Joseph A. Konstan. 2015. The MovieLens 数据集:历史和背景。ACM Transactions on Interactive Intelligent Systems (TiiS) 5, 4: 19:1–19:19. doi.org/10.1145/2827872
除非另有说明,否则所有图片均由作者提供。
你进入计算机视觉的第一步
原文:
towardsdatascience.com/your-first-step-into-the-field-of-computer-vision-f9928ecb313f
了解各种计算机视觉任务及其格式
Sekhar M
·发布在 Towards Data Science ·8 分钟阅读·2023 年 1 月 30 日
--
图像来源:维基媒体(创意共享)
介绍:
计算机视觉(CV)是让计算机从图像中提取信息的领域,就像人类视觉系统一样。
有各种计算机视觉任务,所有这些任务通常以维度为WxHx3的图像作为输入,并根据任务生成输出。W和H表示图像的分辨率(宽度和高度),3是通道数(R、G和B)。
“解决问题的第一步是定义它。”
本文涵盖了各种计算机视觉任务及其格式,从数学上定义,突出每个任务的相应输入和输出。
各种计算机视觉任务:
-
图像分类
-
二分类
-
多类别分类
-
多标签分类
-
-
带有定位的分类
-
目标检测
-
图像分割
-
语义分割
-
实例分割
-
全景分割
-
1. 图像分类:
图像分类是将给定图像分类或归类为预定义类别的任务。
二分类:
二分类将给定图像分类为两个预定义类别中的一个。
输入: X — 维度:W x H x 3
输出: Y — 维度:1
在这里,输出 Y 的真实值将是 1 或 0(分别表示正例和负例)。
然而,二分类模型的输出通常是介于 0 和 1 之间的一个数字,这个数字将被视为输入图像属于正类的概率。
二分类模型(图片由作者提供)
下面的例子将输入图像分类为‘猫’或‘非猫’。
二分类示例(图片来源于作者)
多类别分类:
多类别分类将给定的图像分类为多个预定义类别中的一个。
输入: X — 维度:W x H x 3
输出: Y — 维度:n x 1
在这里,输出 Y 的真实值将是一个一维独热编码向量(1 的位置表示分配给输入图像的类别)。
然而,多类别分类模型的输出通常是一个介于 0 和 1 之间的数值向量,这些数值的总和为 1。这些数值将被视为输入图像属于各个类别的概率。
多类别分类模型(图片来源于作者)
下面的例子将输入图像分类为 n 个预定义类别中的一个。
多类别分类示例(图片来源于作者)
多标签分类:
多标签分类与多类别分类类似,不同之处在于输入图像可以被分配给多个预定义的类别中的一个或多个。
输入: X — 维度:W x H x 3
输出: Y — 维度:n x 1
在这里,输出 Y 的真实值可以在多个位置上为 1。因此,它可以是一个二值向量,而不是一个独热编码向量。
然而,多标签分类模型的输出通常是一个介于 0 和 1 之间的数值向量。注意,这些数值的总和不需要为 1,因为一个输入图像可以同时属于多个预定义的类别。每个数值被视为输入图像属于对应类别的概率。
多标签分类模型(图片来源于作者)
下面的例子将多个类别分配给给定的输入图像。
多标签分类示例(图片来源于作者)
2. 带定位的分类:
带定位的分类通常意味着多类别分类,同时定位决定分类输出的输入图像部分。
输入: X — 维度:W x H x 3
输出: Y — 维度:(1+4+n) x 1
在 Y 的真实值中,*Pc*
为 1 如果输入图像中存在任意一个* n* 类别,否则为 0。*bx*
、*by*
、*bw*
和*bh*
表示确定分类输出的部分的边界框。*C1*
到*Cn*
是表示分类的一维独热编码向量。
注意,当输入图像中没有任何一个* n* 类别时,*Pc*
为 0,输出中的其他值则不重要。
带定位的分类模型(图片来源于作者)
下面的例子将输入图像分类为马,并输出定位信息。
分类与定位示例(图像来源:作者)
虽然 x
、y
、w
和 h
是图像中的绝对值,*bx*
和 *bw*
表示为 W
的分数值,*bx*
和 *bh*
表示为 H
的分数值。
输入图像(左)和叠加在输入图像上的定位信息(右)。(图像来源:作者)
注意,原点 (0, 0) 始终被认为是图像的左上角。x
沿图像的宽度(从左到右),y
沿图像的高度(从上到下)。
3. 物体检测:
物体检测通常是对多标签分类应用定位。
YOLO 是一种物体检测模型的示例。它将输入图像划分为一个 gxg
的网格,并为每个单元产生一个分类-定位向量作为输出。YOLO 算法通常有一个 19x19 的网格。
输入: X — 尺寸:W x H x 3
输出: Y — 尺寸:g x g x (1+4+n)
物体检测模型(图像来源:作者)
在输出 Y 中,对于每个 gxg
单元,第三维是向量 [*Pc*, *bx*, *by*, *bw*, *bh*, *C1*,...,*Cn*]
。这里,*bx*
、*bw*
和 *bx*
、*bh*
被表示为对应单元的 宽度
和 高度
的分数值(而不是图像的)。
这里,*bw*
和 *bh*
的值可以超过 1,因为物体的大小可能大于单元格的大小。然而,*bx*
和 *by*
的值仍然保持在 0 和 1 之间,因为物体的质心只能属于一个单元。
输入图像(左),4x4 单元网格(中),叠加在单元格 5 和 7 上的输出(右)(图像来源:作者)
对于上面的图像示例,4x4 网格的输出 Y 的实际值如下:
对于单元位置 5 和 7,分类-定位向量分别为 [1, 0.90, 0.60, 1.30, 0.95, 1,...,0]
和 [1, 0.55, 0.80, 1.90, 1.20, 1,...,0]
。(假设汽车为 n 标签中的标签 1)
对于除 5 和 7 之外的所有其他单元位置,分类-定位向量为 [0, ?, ?, ?, ?, ?,...,?]
当 *Pc*
为 0 时,向量中的其余值无关紧要。‘?’表示 不关心 状态。
注意:在这个算法中,一个单元格中只能识别一个物体。然而,使用锚框等其他技术,可以在一个单元中识别多个不同大小的物体。
4. 图像分割:
图像分割是将图像划分为多个称为区域的任务。
语义分割:
语义分割是将多类分类应用于像素级任务,即输入图像的每个像素都被分类为 n 个预定义类别之一。
输入: X — 尺寸:W x H x 3
输出: Y — 维度:W x H x n
语义分割模型(图像由作者提供)
每个 1xn 维向量,表示输出中每个像素位置,代表输入图像中特定像素的标签分类。
语义分割不会区分图像中同一类别的两个实例。它只提及每个像素的类别。例如,图像中所有的马像素被分割为相同类别——马像素。
实例分割:
实例分割是在每个检测到的对象的 兴趣区域 (RoI) 内进行像素级分割的目标检测。
因此,除了分类标签和边界框外,它还输出一个每个检测到的对象的二值分割掩码(大小等于相应的 ROI)。
输入: X — 维度:W x H x 3
输出: Y — 维度:N x (标签 + 边界框 + 分割掩码)*
其中 N 是检测到的对象数量,标签向量是一个维度等于预定类别数量的向量,边界框由四个值 [*bx,by,bw,bh*]
表示,分割掩码是与相应对象的 RoI 大小相等的二值掩码。
这个二值分割掩码突出了属于对象的像素与边界框内其余部分的区别。
正如其名所示,实例分割即使区分同一对象类别的多个实例。
全景分割:
全景分割结合了语义分割和实例分割。对于每个像素, 它分配一个 语义类别标签 (l) 和一个 实例 ID (z)。
输入: X — 维度:W x H x 3
输出: Y — 维度:W x H x 每个像素一个 (l, z) 对
全景分割将图像的内容视为两种类型——Things 和 Stuff。
‘Thing’ 是任何可计数的对象,如汽车、马和人。而 Stuff 是一种不可计数的具有相同纹理的区域,如天空、道路、水和地面。
语义 vs 实例 vs 全景:
(a) 输入图像,(b) 语义,(c) 实例,(d) 全景(来源:[1])
注意,在语义分割中,没有区分同一类别的多个实例,即例如,所有汽车被显示为蓝色,所有人被显示为红色。
在实例分割中,由于初始目标检测步骤检测到图像中存在的所有对象实例,每个‘Thing’——汽车和人——被单独分割,而所有‘Stuff’则被忽略。
在全景分割中,既对‘Thing’进行语义分割,也对‘Stuff’进行语义分割,同时每个‘Thing’的实例也被单独分割。
在全景分割中,不仅对“物体”和“材料”进行语义分割,而且每一个“物体”实例也被单独分割。
结论:
计算机视觉领域有多个应用,从自动驾驶到医学图像分析,这些应用都使用了各种计算机视觉任务。
由于解决问题的第一步是对其进行明确的定义,我希望这篇文章——解密各种计算机视觉任务——对任何进入计算机视觉领域的人都能有所帮助。
参考文献:
[1] Kirillov 等人,《全景分割》,2019 年。
[2] He 等人,《Mask R-CNN》,2017 年。
[3] Redmon 等人,《你只看一次》,2015 年。
你自己的个人 ChatGPT
原文:
towardsdatascience.com/your-own-personal-chatgpt-cb0512091e3f
如何使用你的自定义数据对 OpenAI 的 GPT-3.5 Turbo 模型进行微调,以执行新的任务
Robert A. Gonsalves
·发布于 Towards Data Science ·15 分钟阅读·2023 年 9 月 8 日
--
“一个在艺术课堂上画着可爱机器人极简主义风格的画作,” 图像使用 AI 图像创作程序 Midjourney 创建,并由作者编辑
当我收到 OpenAI 宣布可以微调 ChatGPT 的邮件时,我感到很兴奋。这个更新是响应开发者和企业的要求,旨在定制模型以更好地满足他们的具体需求。通过利用这种微调,现在可以改进引导性,实现更一致的输出格式,并建立所需的自定义语调。另一个值得注意的方面是,用户可以发送更短的提示而不会显著影响性能。
这是 OpenAI 在他们的开发博客上所说的[1]。
此更新赋予开发者自定义模型的能力,使其在他们的使用场景中表现更佳,并能够大规模运行这些自定义模型。早期测试表明,经过微调的 GPT-3.5 Turbo 版本在某些特定任务上可以匹配甚至超越基础 GPT-4 级别的能力。与我们所有的 API 一样,进出微调 API 的数据归客户所有,OpenAI 或任何其他组织不会用这些数据来训练其他模型。 — Andrew Peng 等,OpenAI
在这篇文章中,我将展示如何使用我的 Medium 文章中的文本作为训练和测试数据,将纯文本自动转换为 Markdown 格式。在我描述实验之前,我会给你介绍一下 ChatGPT 的背景。
背景
被称为 ChatGPT 的 AI 模型在 2022 年 11 月推出 [2]。这是 OpenAI 发布的第一个公共聊天机器人,我在 Medium 上的这里 多次写到过这个模型。该模型作为通用聊天机器人运行良好,但也有一些局限性。例如,它的训练截止日期是 2021 年 9 月,因此不知道此后的任何新信息。可以使用浏览器插件来获取和增强模型的数据,但目前这一过程较慢且繁琐。
将新信息和技能融入 ChatGPT 的一种更好方法是使用 OpenAI 的 微调 API。通过 API 微调 ChatGPT 能提供比常规提示更好的结果。它可以在比提示更多的示例上进行训练,导致更短的提示,从而节省 tokens,并且响应时间更快。不过,这需要费用。
定价
以下是使用不同模型的价格,来源于 OpenAI 的定价页面,以美元为单位。
使用 OpenAI 模型的价格,来源 OpenAI
“token” 这个术语指的是用于提示和结果的词语部分,其中 750 个词可以用大约 1,000 个 tokens 表示。“context” 指的是用于交互输入和结果的 tokens 总数。图表显示,输入词(即提示)的 tokens 成本低于输出词(即结果)的 tokens 成本。使用 GPT-4 的成本高于使用 GPT-3.5 Turbo。微调版本的 GPT-3.5 Turbo 介于这两种模型之间。如果微调的 GPT-3.5 Turbo 能超过 GPT-4,那么它的成本是值得的。我们将在我的实验中看看是否真的如此。
请注意,这些价格可能会随时间变化,并且在不同的地区和货币之间可能有所不同。任何变化都将影响上述讨论的限制和权衡,因此在做出决定之前,请检查本地价格。
自动格式化文档
为了启动这一过程,我进行了一个实验,看看 GPT 模型是否能够自动将文本文件转换为 Markdown 格式。这种轻量级的标记语言指定了诸如标题、引用、代码块等文本格式。
格式化文本文件的组件,图示由作者提供
为了测试这些模型,我下载了我在 Medium 上的 36 篇文章,并使用 Beautiful Soup 库 将 HTML 文件转换为 Markdown 和纯文本格式。文本文件用作输入,而 Markdown 文件用作训练和测试的输出。我使用了 32 篇文章用于训练,3 篇用于测试。我测试了三种 OpenAI 的语言模型变体:GPT-4、GPT-3.5 Turbo 和微调的 GPT-3.5 Turbo,并收集了结果。
以下是其中一篇文章在 Markdown 格式中的样子,渲染前后对比。
Markdown 格式
示例文章展示 Markdown 格式(左),以及渲染效果(右),图片由作者提供
你可以看到左侧以深红色显示的 Markdown 格式字符,如#表示标题 1,##表示标题 2。在右侧,你可以看到文件的渲染效果,展示了如块引用这样的格式。
微调 GPT-3.5 Turbo
微调模型很简单。我遵循了 OpenAI 这里 的说明。第一步是将我的训练和验证数据整理成 JSON 文件。我将文章分为四个部分,以保持在 GPT-3.5 Turbo 的 4K token 限制内。文件中的每个条目都有一个可选的系统提示、用户消息和助手的预期回应。以下是我训练文件中的一个示例条目。
{"messages": [
{"role": "system", "content": "You render plain text into markdown format."},
{"role": "user", "content": "MachineRay: Using AI to Create Abstract Art\nHow I trained a GAN using public domain paintings\nRobert A. Gonsalves\nTowards Data Science\nAug 3, 2020\nMachineRay - https://medium.com/towards-data-science/machineray-using-ai-to-create-abstract-art-39829438076a\nFor the past three months, I have been exploring the latest techniques in Artificial Intelligence (AI) and Machine Learning (ML) to create abstract art. During my investigation, I learned that three things are needed to create abstract paintings: (A) source images, (B) an ML model, and (C) a lot of time to train the model on a high-end GPU. Before I discuss my work, let\u2019s take a look at some prior research.\nThis is the first part of my series of articles on how AI can be used for creative endeavors. The second part is on how to use ML to generate plots for new stories, available here .\nBackground\nArtificial Neural Networks\nWarren McCulloch and Walter Pitts created a computational model for Neural Networks (NNs) back in 1943[1]. Their work led to research of both the biological processing in brains and the use of NNs for AI. Richard Nagyfi discusses the differences between Artificial Neural Networks (ANNs) and biological brains in this post . He describes an apt analogy that I will summarize here: ANNs are to brains as planes are to birds . Although the development of these technologies was inspired by biology, the actual implementations are very different!\nBoth ANNs and biological brains learn from external stimuli to understand things and predict outcomes. One of the key differences is that ANNs work with floating-point numbers and not just binary firing of neurons. With ANNs it\u2019s numbers in and numbers out.\nThe diagram below shows the structure of a typical ANN. The inputs on the left are the numerical values that contain the incoming stimuli. The input layer is connected to one or more hidden layers that contain the memory of prior learning. The output layer, in this case just one number, is connected to each of the nodes in the hidden layer.\nEach of the internal arrows represents numerical weights that are used as multipliers to modify the numbers in the layers as they get processed in the network from left to right. The system is trained with a dataset of input values and expected output values. The weights are initially set to random values. For the training process, the system runs through the training set multiple times, adjusting the weights to achieve the expected outputs. Eventually, the system will not only predict the outputs correctly from the training set, but it will also be able to predict outputs for unseen input values. This is the essence of Machine Learning (ML). The intelligence is in the weights . A more detailed discussion of the training process for ANNs can be found in Conor McDonald\u2019s post, here .\nGenerative Adversarial Networks\nIn 2014, Ian Goodfellow and seven coauthors at the Universit\u00e9 de Montr\u00e9al presented a paper on Generative Adversarial Networks (GANs)[2]. They came up with a way to train two ANNs that effectively compete with each other to create content like photos, songs, prose, and yes, paintings. The first ANN is called the Generator and the second is called the Discriminator. The Generator is trying to create realistic output, in this case, a color painting. The Discriminator is trying to discern real paintings from the training set as opposed to fake paintings from the generator. Here\u2019s what a GAN architecture looks like.\nA series of random noise is fed into the Generator, which then uses its trained weights to generate the resultant output, in this case, a color image. The Discriminator is trained by alternating between processing real paintings, with an expected output of 1 and fake paintings, with an expected output of -1\. After each painting is sent to the Discriminator, it sends back detailed feedback about why the painting is not real, and the Generator adjusts its weights with this new knowledge to try and do better the next time. The two networks in the GAN are effectively trained together in an adversarial fashion . The Generator gets better at trying to pass off a fake image as real, and the Discriminator gets better at determining which input is real, and which is fake. Eventually, the Generator gets pretty good at generating realistic-looking images. You can read more about GANs, and the math they use, in Shweta Goyal\u2019s post here .\nImproved GANs for Large Images\nAlthough the basic GAN described above works well with small images (i.e. 64x64 pixels), there are issues with larger images (i.e. 1024x1024 pixels). The basic GAN architecture has difficulty converging on good results for large images due to the unstructured nature of the pixels. It can\u2019t see the forest from the trees. Researchers at NVIDIA developed a series of improved methods that allow for the training of GANs with larger images. The first is called \u201c Progressive Growing of GANs \u201d [3].\n"},
{"role": "assistant", "content": "# MachineRay: Using AI to Create Abstract Art\n## How I trained a GAN using public domain paintings\nRobert A. Gonsalves\n</br>Aug 3, 2020</br>\nMachineRay - https://medium.com/towards-data-science/machineray-using-ai-to-create-abstract-art-39829438076a</br></br>\nFor the past three months, I have been exploring the latest techniques in Artificial Intelligence (AI) and Machine Learning (ML) to create abstract art. During my investigation, I learned that three things are needed to create abstract paintings: (A) source images, (B) an ML model, and (C) a lot of time to train the model on a high-end GPU. Before I discuss my work, let\u2019s take a look at some prior research.\nThis is the first part of my series of articles on how AI can be used for creative endeavors. The second part is on how to use ML to generate plots for new stories, available here .\n# Background\n## Artificial Neural Networks\nWarren McCulloch and Walter Pitts created a computational model for Neural Networks (NNs) back in 1943[1]. Their work led to research of both the biological processing in brains and the use of NNs for AI. Richard Nagyfi discusses the differences between Artificial Neural Networks (ANNs) and biological brains in this post . He describes an apt analogy that I will summarize here: ANNs are to brains as planes are to birds . Although the development of these technologies was inspired by biology, the actual implementations are very different!\nBoth ANNs and biological brains learn from external stimuli to understand things and predict outcomes. One of the key differences is that ANNs work with floating-point numbers and not just binary firing of neurons. With ANNs it\u2019s numbers in and numbers out.\nThe diagram below shows the structure of a typical ANN. The inputs on the left are the numerical values that contain the incoming stimuli. The input layer is connected to one or more hidden layers that contain the memory of prior learning. The output layer, in this case just one number, is connected to each of the nodes in the hidden layer.\nEach of the internal arrows represents numerical weights that are used as multipliers to modify the numbers in the layers as they get processed in the network from left to right. The system is trained with a dataset of input values and expected output values. The weights are initially set to random values. For the training process, the system runs through the training set multiple times, adjusting the weights to achieve the expected outputs. Eventually, the system will not only predict the outputs correctly from the training set, but it will also be able to predict outputs for unseen input values. This is the essence of Machine Learning (ML). The intelligence is in the weights . A more detailed discussion of the training process for ANNs can be found in Conor McDonald\u2019s post, here .\n## Generative Adversarial Networks\nIn 2014, Ian Goodfellow and seven coauthors at the Universit\u00e9 de Montr\u00e9al presented a paper on Generative Adversarial Networks (GANs)[2]. They came up with a way to train two ANNs that effectively compete with each other to create content like photos, songs, prose, and yes, paintings. The first ANN is called the Generator and the second is called the Discriminator. The Generator is trying to create realistic output, in this case, a color painting. The Discriminator is trying to discern real paintings from the training set as opposed to fake paintings from the generator. Here\u2019s what a GAN architecture looks like.\nA series of random noise is fed into the Generator, which then uses its trained weights to generate the resultant output, in this case, a color image. The Discriminator is trained by alternating between processing real paintings, with an expected output of 1 and fake paintings, with an expected output of -1\. After each painting is sent to the Discriminator, it sends back detailed feedback about why the painting is not real, and the Generator adjusts its weights with this new knowledge to try and do better the next time. The two networks in the GAN are effectively trained together in an adversarial fashion . The Generator gets better at trying to pass off a fake image as real, and the Discriminator gets better at determining which input is real, and which is fake. Eventually, the Generator gets pretty good at generating realistic-looking images. You can read more about GANs, and the math they use, in Shweta Goyal\u2019s post here .\n## Improved GANs for Large Images\nAlthough the basic GAN described above works well with small images (i.e. 64x64 pixels), there are issues with larger images (i.e. 1024x1024 pixels). The basic GAN architecture has difficulty converging on good results for large images due to the unstructured nature of the pixels. It can\u2019t see the forest from the trees. Researchers at NVIDIA developed a series of improved methods that allow for the training of GANs with larger images. The first is called \u201c Progressive Growing of GANs \u201d [3].\n"}
]}
系统提示对于每个条目都是相同的。用户消息是我一篇文章中的纯文本,助手的回应是相同文本的 Markdown 格式。
如前所述,OpenAI 不会使用任何提交到其 API 并由其生成的数据来训练模型或改进服务。然而,这对于其互动服务版本是不同的。更多信息请参见 这里。
检查数据
接下来,我在训练和测试文件上运行了 OpenAI 的 check_file() 函数。以下是检查结果。
check_file的输出,图片由作者提供
你可以看到我使用了 132 个示例进行训练,即 32 篇文章,每篇 4 个部分。脚本展示了是否有错误,并计算了总 token 数量,我用来估算训练成本。
运行训练
一旦我设置并检查了训练和测试文件,我就将文件上传到我的账户。
openai.File.create(file=open("training.jsonl", "rb"),purpose='fine-tune')
openai.File.create(file=open("testing.jsonl", "rb"),purpose='fine-tune')
然后我获取了文件的名称并运行了这个命令以开始训练。
results = openai.FineTuningJob.create(
training_file="file-PsY5FuC4m4JzIOKKtB7cWDbz",
validation_file="file-xq5M0Yy1CFIkKcHCOgSFOp40",
suffix = "robgon_03",
model="gpt-3.5-turbo")
后缀是一种提供一些元数据的方法,以便我以后能识别微调后的模型。默认情况下,系统运行了三个周期,完成大约花费了半小时。当训练完成后,OpenAI 发送了电子邮件通知我。以下是 384 个训练步骤中的训练和验证损失情况(32 篇文章 * 4 部分 * 3 个周期)。
GPT-3.5 Turbo 微调的训练和验证损失,图表由作者提供
在整个训练过程中,它很好地减少了损失。验证也似乎呈下降趋势,因此它没有过拟合训练数据。让我们看看新模型的实际表现。
测试微调模型
我登录到我的 OpenAI 账户,进入 Playground 测试系统。我选择了我的微调模型,输入系统提示,添加了这篇文章开头的几行,并点击了提交按钮。
OpenAI Playground 测试微调模型,作者截屏
它效果很好!使用最小提示,它的格式化与我期望的训练结果匹配。
比较评估
为了比较微调模型与标准模型的准确性,我创建了一个 较长的提示 来测试 GPT-4 和 GPT-3.5 Turbo 的单次推理。我在系统提示中给出了一例前后文本,并在用户提示中给出了需要格式化的文本,供这两个模型使用。
然后,我将 12 个示例(3 篇文章,每篇 4 个部分)运行通过三个模型以获得结果。为了获得准确性的定量测量,我计算了预测格式化文本与原始格式化文本之间的字符错误率(CER)。除了 CER 结果外,我还报告了运行推理的时间和成本,以供比较。
使用各种 GPT 模型格式化文本文档的结果,作者制表
你可以看到 GPT-4 模型的错误比标准的 GPT-3.5 Turbo 模型少。然而,格式化这三篇文章花费了更长的时间和更高的成本。微调后的 GPT-3.5 Turbo 模型错误少得多,运行速度更快。然而,推理成本居中,并且负担了微调成本。我需要进行七次实验(例如,21 篇格式化的文章)才能平衡 GPT-4 的成本。但由于 CER 较低,这可能是值得的。
讨论
尽管微调后的模型在自动格式化文本方面表现出色,我还是好奇它在微调过程中是否“学到了”一些关于源材料的内容。所以我问了它这个问题。
用户 - 你知道在 Towards Data Science 上有一篇名为“用 GPT-4 写歌:第一部分,歌词”的文章吗?
助手 - 是的,我熟悉你提到的文章。它的标题是“用 GPT-4 写歌:第一部分,歌词”,发表在 Towards Data Science。这篇文章探讨了如何使用 OpenAI 开发的 GPT-4 语言模型生成歌曲歌词。它讲述了如何微调模型并生成创意歌词。
用户 - 谁写了这篇文章?
助手 - 文章“用 GPT-4 写歌:第一部分,歌词”是 Ellie Chen 写的。
好的,这里出现了幻觉。首先,我并没有为这篇文章微调 GPT-4。其次,模型认为是一个名叫 Ellie Chen 的人写的。所以,训练数据并没有被充分吸收。下一步,我可以探索在微调过程中向 GPT 模型注入新数据以供后续回忆和使用的方法。
结论
OpenAI 推出的 ChatGPT 微调功能为开发者提供了将模型适应特定任务的新途径。本文探讨了使用此功能的过程和结果,特别是在将纯文本自动转换为 Markdown 格式的应用。尽管模型在这个简单的任务上表现良好,但它似乎无法理解和记住所见的新文本,因此不能可靠地用于生成有关新材料的查询。随着 AI 技术的发展,创建专用模型的探索成为一个有趣的话题,突显了持续研究和评估的重要性。
源代码
该项目的源代码可在 GitHub 上获取。
知识共享署名-相同方式共享
致谢
感谢 Jennifer Lim 审阅文章并提供反馈。
参考文献
[1] A. Peng 等人,GPT-3.5 Turbo 微调和 API 更新, 2023
[2] OpenAI, 介绍 ChatGPT, 2022
您的个人 LLaMa
原文:
towardsdatascience.com/your-own-personal-llama-8cf24a9237cb
如何找到、检索并将您的自定义数据输入 LLaMa 2 以改善响应
Robert A. Gonsalves
·发布于 Towards Data Science ·12 分钟阅读·2023 年 10 月 9 日
--
“一幅极简主义风格的可爱草泥马站在艺术课堂上的画作,” 我使用 AI 图像生成程序 Midjourney 创建,并由作者编辑
在我的 上一篇文章 中,我展示了如何微调 OpenAI 的 ChatGPT 以改善执行诸如格式化文本文档等任务的结果。尽管微调帮助大型语言模型(LLM)更好地执行格式化任务,但该模型似乎没有吸收训练数据中的很多知识。
在这篇文章中,我探索了使用开源 LLM,Meta 的 LLaMa 2 [1],以及一种称为检索增强生成(RAG)[2] 的技术,自动找到相关参考文本,以帮助模型回答问题。我使用了我在 Medium 上发表的 36 篇文章中的文本作为参考材料,看看是否可以创建一个私人定制的聊天机器人,能够回答关于我写作的相关问题。
例如,在没有提供任何参考文本的情况下,以下是 LLaMa 2 如何回应关于 我的一篇文章 的问题。
RobG: 什么是 Muybridge Derby?
LLaMa 2: 感谢您的提问!我在这里帮助您解答问题。然而,我必须告知您,“Muybridge Derby”不是任何上下文中真实或认可的术语。这可能是一个拼写错误或虚构的
术语,我无法提供关于不存在的事物的信息。
如果您能提供更多上下文或澄清您所指的术语,
我很乐意进一步尝试协助您。请让我知道是否
如果还有其他我可以帮助的地方,请告诉我!
那是一种非常礼貌的方式来表示它从未听说过这个项目。而且它没有“幻觉”并编造虚假的信息。
这是再次提问并请求更多信息的情况,不过这次系统有一些参考文本可用。
RobG: 什么是穆伊布里奇德比?
LLaMa 2: 穆伊布里奇德比是 Robert A. Gonsalves 的一个项目,他使用了 Midjourney 和 RunwayML 将 Eadweard Muybridge 的照片序列转换为高分辨率视频。
RobG: 告诉我更多关于人工智能系统的事。
LLaMa 2: 在穆伊布里奇德比项目中,Robert A. Gonsalves 使用了两个人工智能系统,将 Eadweard Muybridge 的照片序列转换为高分辨率视频:1. Midjourney:一个使用人工智能生成图像和文本创建互动视觉故事的工具。2. RunwayML:一个用于创建、训练和部署机器学习模型的平台。这些系统使 Gonsalves 能够将 Muybridge 的静态照片转换为动态视频,展现动物的运动。
好多了!系统对第一个问题的回答简洁而准确。对后续请求的回应略显冗长,因为重复了一些第一个回答中的信息,但总体结果还是不错的。
概述
本节提供了项目和我使用的组件的概述。我将在下面进一步详细讨论每个组件和过程。
个人 LLaMa 项目的组件,作者图示
我从我在 Medium 上的 36 篇文章中提取了文本,并将其从 HTML 格式转换为 Markdown 语言,以便于检索。我使用了一个名为LlamaIndex的开源文本生成框架,用于索引、搜索和提供文章部分作为参考文本给 LLMs。
为了测试系统的准确性,我使用 GPT-4 [3]生成了一组关于我的文章的 100 个问题和答案。我测试了三种 LLM,看看它们的效果如何:两种参数分别为 7 亿和 13 亿的 LLaMa 变体,以及具有 1750 亿参数的 ChatGPT [4]。
我使用了一个语义文本嵌入模型 MPNet [5]来评估结果,该模型由 Hugging Face 团队进一步训练成一个名为all-mpnet-base-v2的模型。我通过余弦相似度比较了三种模型的回答嵌入与 GPT-4 预期响应的嵌入,以获得准确性得分,这些得分在下文的结果部分报告。
组件和过程
以下是我在这个项目中使用的组件和过程的详细信息。
将 HTML 转换为 Markdown
Markdown 是一种轻量级标记语言,具有纯文本格式化语法。John Gruber 和 Aaron Swartz 于 2004 年创建了 Markdown,旨在成为一种易读易写的标记语言 [6]。由于简化的语法,Markdown 格式的文档比 HTML 更容易解析和分解为连贯的文本段落。我使用了 Beautiful Soup Python 库来帮助将我的文章从 HTML 转换为 Markdown。
例如,这里是我在 HTML 和 Markdown 格式中的 Muybridge Derby 文章摘录。
HTML 格式(左)与Markdown 格式(右)的比较,图片由作者提供
你可以看到左侧的 HTML 格式比右侧的 Markdown 格式包含更多的元数据和标点符号。将文档转为 Markdown 格式可以使下游过程更为简单。你可以在这个 Google Colab 中查看我的转换代码。
LlamaIndex
LlamaIndex 是一个开源项目,它将 LLMs 连接到各种数据源,通过自然语言接口和知识增强的响应来实现结构化数据的交互和应用提升。它支持数据摄取、索引和利用,适用于多种格式,并支持开发交互式、数据驱动的应用程序 [7]。
在 Python 中使用 LlamaIndex 框架非常简单。这里有一个简单的示例展示了系统的 API。默认使用 ChatGPT,但也可以轻松使用其他 LLM,如 LLaMa 2。
from llama_index import VectorStoreIndex, SimpleDirectoryReader
documents = SimpleDirectoryReader('robgon_articles_md').load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
results = query_engine.query("What is DeepHaiku? Be brief")
print(results)
我将 36 篇文章的 Markdown 文件存储在 robgon_articles_md 文件夹中。SimpleDirectoryReader 对象读取这些文件,并将其分成多个段落,利用格式作为提示来隔离连贯的文本块。每篇文章平均约 18 个块,总共 632 个。VectorStoreIndex 对象为这些段落创建语义嵌入,以寻找查询的参考。查询引擎由索引创建,便于回答与文档相关的问题。
这是对 我另一个项目 的问题的回应。
RobG:什么是 DeepHaiku?请简要说明。
ChatGPT:Deep Haiku 是一个使用微调的 GPT-J 6B 模型生成 Haikus 的模型。它从一个提示开始,生成 20 个候选 Haikus,筛选出符合 [5, 7, 5] 韵律的,并可选择性地去除显式语言。最终候选者及其得分会被展示出来。
这是我 DeepHaiku 文章的一个很好的总结。我们很快将看到 LLaMa 2 7B 和 13B 在回答类似问题时的表现如何。
语言模型
以下部分描述了我在这个项目中使用的三个 LLM,按发布日期的时间顺序列出。
ChatGPT
ChatGPT 是一个由 OpenAI 开发的语言生成模型,基于 2022 年 11 月发布的 GPT(生成预训练变换器)架构。与原始 GPT 一样,新版 ChatGPT 模型具有 1750 亿个机器学习参数。它能够根据接收到的输入理解和生成类似人类的文本,并用于各种自然语言处理任务,如文本完成、总结和对话代理 [4]。一个名为 GPT-3.5 Turbo 的 ChatGPT 版本于 2023 年 3 月发布。它通过监督学习,使用大量的文本数据进行训练,并通过人类反馈的强化学习进行微调。这使得它能够基于接收到的输入生成连贯且具有上下文相关性的文本 [8]。
GPT-4
GPT-4 是一个大规模的多模态模型,旨在接受图像和文本输入并生成文本输出。它于 2023 年 3 月由 OpenAI 发布。虽然该模型的一个版本支持图像输入,但目前尚未向公众开放。GPT-4 在各种基准测试中表现出人类水平的性能,包括以高分通过了模拟的律师资格考试。该模型经历了后训练对齐过程,以提高其事实性和对期望行为的遵循。其大部分开发工作涉及创建基础设施和优化方法,这些方法在各种规模下表现一致 [3]。
LLaMa 2
Meta 的 LLaMa 2 是一系列经过预训练和微调的 LLM,规模从 70 亿到 700 亿参数不等,于 2023 年 7 月发布。该系列包括一个名为 LLaMa 2-Chat 的变体,用于交互式文本生成。Meta 展示了他们的模型在许多基准测试中相比开源聊天模型表现优越,并在有用性和安全性方面在人类评估中显示出良好的结果。开发者解释了他们对 LLaMa 2-Chat 的微调和安全增强方法,旨在促进更多社区参与和负责任的 LLM 开发 [1]。
作者描述了他们对 LLaMa 2 模型的安全考虑。他们解释了他们的安全对齐过程,该过程包括收集安全相关的注释,这些注释得到了实验结果的支持。他们还聘用了“红队”,这些团队测试并增强了模型的安全性。他们对与模型部署相关的潜在风险进行了检查和缓解,并以以下警告结束了他们的论文。
Llama 2 是一种新技术,使用中存在风险。迄今为止进行的测试仅限于英语,并且没有涵盖所有场景,也无法涵盖所有场景。因此,与所有 LLM 一样,Llama 2 的潜在输出无法提前预测,模型在某些情况下可能会对用户提示产生不准确或令人反感的回应。因此,在部署 Llama 2 的任何应用程序之前,开发人员应针对模型的具体应用进行安全测试和调整。 - Hugo Touvron 等 [1]
我很高兴看到 Meta 的研究人员认真对待模型安全性。幸运的是,我对 LLaMa 2 模型的使用并没有对系统造成压力以产生令人反感的回应,但知道已有缓解措施是好的。
LLaMa 2 许可证
尽管 Meta 发布了 LLaMa 2 的源代码和训练权重作为免费和开源,但他们的许可证有几个独特的细节。它规定,每月活跃用户超过 7 亿的实体必须请求并获得 Meta 授予的特定许可证才能使用该模型。它明确禁止任何人使用 LLaMa 2 来增强其他 LLM。我不是律师,所以在使用 LLaMa 2 之前,请寻求法律建议以全面理解条款和条件。
生成测试问题和答案
为了判断 LLaMa 2 聊天模型回答的正确性,我使用 GPT-4 生成了 100 个关于我在 Medium 上文章的问题和答案。我从 LlamaIndex 收集了 100 段文本,并将参考文本输入到 GPT-4 中,使用了以下提示。
*提出一个关于参考材料的问题并回答。
不要使用“参考材料”这个词。请改用文章标题或项目的昵称。
在问题中不要使用“作者”这个词。请改用“Robert”或“Robert A. Gonsalves”。
在答案中,如果问题中提到了他的名字,请用‘他’或‘他的’来指代作者。
使用格式:
q:
a: *
这里是 GPT-4 生成的一些示例问题和答案。
q: Robert 如何使用 CLIP 筛选 MAGnet 项目的图像?
a: Robert 使用 CLIP 通过将“现代绘画”这个短语的嵌入与绘画的嵌入进行比较来筛选图像。这使他能够找到最符合“现代绘画”这个短语的前 10,000 张图像。这是为了确保用于训练的图像是现代绘画的良好示例。
q: Robert A. Gonsalves 的项目“GPT-4 和弦”的主要目标是什么?
a: 他项目“GPT-4 和弦”的主要目标是展示如何使用 OpenAI 最新的大型语言模型 GPT-4 来帮助创作原创歌曲的和弦。
q: Robert A. Gonsalves 如何修改 DALL-E 为 Mark Madness 漫画创建的图像?
a: 罗伯特在 Photoshop 中清理了图像,添加了标题,并用 GPT-3 生成的对话替换了文字气泡中的胡言乱语文本。他还在 Photoshop 中使用了一个技巧来复制角色 Mark 和 Enrique 的眼睛,并为对话使用了 Comic Sans 字体。
这些看起来都很好。问题涉及读者可能感兴趣的内容,答案似乎准确、写得好且简洁。
生成答案
对于我测试的三种 LLM,LLaMa 2 7B、LLaMa 2 13B 和 ChatGPT,我输入了相应的参考材料和问题,并附加了提示“简洁”。系统生成了所有 100 个问题的答案。以下是显示三个问题答案的表格。
来自大型语言模型的问答,由作者编制的表格
所有的回答似乎都不错。它们都清晰而简洁地陈述了主要事实。唯一显著的区别可以在 LLaMa 2 13B 的回答中看到。它的所有三个回答都以类似于“当然!这是你问题的答案……”和“基于提供的信息……”的通用条款开始。这些短语对回答的价值不大。
比较文本的语义相似性
为了定量评估这三种模型,我使用了一个名为 all-mpnet-base-v2 的语义文本编码模型,该模型由 Hugging Face 的研究人员开发。该编码模型接收文本字符串并生成对应的嵌入数组,这些数组包含 768 个浮点数,表示文本的含义。以下是一些 Python 代码,展示了其工作原理。
from sentence_transformers import SentenceTransformer
import numpy as np
# Load the model
encoder = SentenceTransformer('sentence-transformers/all-mpnet-base-v2')
# Create embeddings for two example sentences.
sentences = ["This is an example sentence", "This is another example sentence"]
embeds = encoder.encode(sentences)
# Normalize the embeddings
text_features_1 = embeds[0] / np.linalg.norm(embeds[0], axis=-1, keepdims=True)
text_features_2 = embeds[1] / np.linalg.norm(embeds[1], axis=-1, keepdims=True)
# Calculate the cosine similarity
similarity = np.dot(text_features_1, text_features_2.T)
print(similarity)
我将两个文本字符串输入模型以创建嵌入,并使用余弦相似度度量进行比较。结果的相似度范围从 0.0 到 1.0,具体取决于两个文本字符串的语义接近程度。在这个例子中,字符串的相似度为 90.2%。我使用这个度量来评估 LMM 的结果。
结果
我使用语义嵌入模型将三种 LLM 的所有 100 个答案与 GPT-4 的基准答案进行比较,并编制了以下图表以显示结果。
LMM 结果比较,由作者编制的图表
这个箱线图展示了测试结果的可视化。中央的箱子代表数据的中间 50%,箱子的线条表示中位数。胡须延伸到最小值和最大值,而点表示统计异常值。X 代表分数的平均值。通常,可以看到 LLaMa 2 7B 和 ChatGPT 的结果非常接近,尽管 LLaMa 2 的分数有一些最低的值。令人好奇的是,LLaMa 2 13B 的结果略逊于其他两个模型。这可能是由于答案的冗长。
结论
我对使用 Meta 的 LLaMa 2 来提高基于我在 Medium 上发布的 36 篇文章知识库的自定义聊天机器人回应的质量和准确性的探索,提供了对 LLM 实际应用和局限性的见解。实验表明,尽管为 LLaMa 2 提供相关参考文本显著提高了回应准确性,但模型大小与回应质量之间的关系并非线性,这从 LLaMa 2 7B 和 13B 之间的性能差异中得到了体现。此外,使用语义相似性作为评估回应的指标突显了不同模型提供的答案之间的细微差异和变异性。
这项调查不仅强调了使用 LLaMa 2 进行特定、定制化应用的潜力,还揭示了在部署这些技术时伦理考量和彻底测试的重要性。Meta 提供的指南和警示恰恰提醒了开发者和研究人员在确保 LLM 的安全和伦理使用方面的责任,特别是在这些技术逐渐融入各种数字平台和应用程序时。技术进步与伦理部署之间的平衡将继续是开发和应用 LLM 在不同环境中的关键焦点。
下一步
进一步探索 LlamaIndex 的检索准确性可以通过结构化评估来理解模型在识别和提供相关参考材料方面的有效性。此外,调查语义相似性之外的文本比较指标可能会提供有关模型回应语言和上下文细微差异的见解。这种双重方法旨在提供对模型性能的更全面理解,并指导未来在提高 LLM 回应准确性和语言质量方面的发展。
源代码
我将在 GitHub 上发布源代码,使用创意共享署名-相同方式共享许可证。
创意共享署名-相同方式共享
致谢
我要感谢 Jennifer Lim 对本文的审阅和反馈。
参考文献
[1] H. Touvron 等,Llama 2: 开放基础与微调聊天模型,(2023)
[2] P. Lewis 等,增强检索生成用于知识密集型 NLP 任务,(2020),《神经信息处理系统进展》
[3] OpenAI,GPT-4 技术报告,(2023)
[4] T. Brown 等,语言模型是少样本学习者,《神经信息处理系统进展》,(2020)
[5] S. Kaitao 等,Mpnet: Masked and permuted pre-training for language understanding,《神经信息处理系统进展》,(2020)
[6] A. Swartz,Markdown,(2017)
[7] J. Liu, LlamaIndex,(2022)
[8] OpenAI,Introducing ChatGPT,(2022)
你的 Strava 年度统计……但用 Python 的方式
原文:
towardsdatascience.com/your-strava-year-in-stats-but-differently-with-python-2b816f920bae
获取有关你的 Strava 活动的新见解,总结你的一年
Guillaume Weingertner
·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 1 月 9 日
--
每个月的痛苦评分 — 图片由作者提供
#1 动机
随着 2022 年的结束,Strava 和许多其他应用程序一样,为用户提供了一年的统计总结。然而,这些统计数据仍然有些有限,非常高层次,并未提供对整体活动的超实用见解。
在这篇文章中,我解释了如何使用 Strava 的 API 获取你的数据,并构建有洞察力的图表,以获取你在应用中找不到的新见解。
#2 使用 Strava API 获取数据
我写了另一篇文章,解释了如何一步步使用 Strava API 获取你的特定数据。如果你想尝试一下,可以点击这里:
## 谁是你在 Strava 上的头号支持者?
探索 Strava API 并使用 Python 发现
towardsdatascience.com
如果你按照这个快速教程,你应该会得到一个包含你全年所有活动的数据框。它看起来像这样:
所有 Strava 活动的数据框 — 图片由作者提供
不详细介绍所有函数(你可以在我的 GitHub 上找到所有内容 — 链接在文章末尾),代码如下:
将你的 Strava 活动放入数据框 — 代码由作者提供
#3 对跑步和骑行的分析
今年我主要跑步和骑车。我想回答的一个问题是,经过这些跑步和骑行,平均来说,一项典型的活动是什么样的?
我使用以下代码将跑步和骑行分开:
并做了一些格式化,以获得典型跑步的以下统计数据:
典型跑步 — 图片来源:作者
和典型骑行:
典型骑行 — 图片来源:作者
现在,我认为另一个有趣的信息是哪些月份最为活跃,以及在哪项运动中。
为了弄清楚这一点,我简单地绘制了我每个月在两项运动中的累积距离。
如下所示,我最为密集的跑步时期是在三月至七月之间:
每月跑步距离 — 图片来源:作者
尽管我在夏季稍微更关注骑行,在八月达到了峰值:
每月骑行距离 — 图片来源:作者
上面的图表分别显示了跑步和骑行活动。
这很好,但仅凭此数据,我们无法知道某个月是否更多地专注于其中一项运动。我们需要找到一个能够将两项运动在同一尺度上进行比较的指标。
距离或移动时间并不适用,因为这些活动的尺度明显不同,爬升增益对于跑步或骑行也不具有可比性,配速也不行……这就引出了痛苦评分。
#4 痛苦评分
痛苦评分基于你的心率,因此无论你是周末战士还是职业运动员,只要你的心脏在努力工作,你都能获得相同的评分。
Suffer Score(痛苦评分)衡量你在每个心率区间所花费的时间。花在更高心率区间的时间权重比花在较低心率区间的时间更高。
更多详细信息请参见:blog.strava.com/fr/suffer-score-how-hard-is-hard
得益于这个指标,我们现在可以比较完全不同的活动(在我们的案例中是跑步和骑行),并了解它们的强度。
#5 所有活动分析
为了更好地理解痛苦评分并对其有所感受,让我们绘制每项活动的评分,并按运动类型进行拆分:
每项活动的痛苦评分 — 图片来源:作者
上图显示了骑行的平均痛苦评分高于跑步的平均痛苦评分,说明在我的情况下,平均而言,骑行比跑步更为强烈。
请记住,这一结论仅对我适用,你的数据可能会显示完全不同的结果。
现在的想法是,每个月绘制一次累积痛苦评分,这次结合跑步和骑行活动。
每月痛苦评分 — 图片来源:作者
这个图表讲述了一个与专注于骑行和跑步分别的图表不同的故事。
这一年在不同季节的分布较为均匀,夏季的强度达到峰值,而冬季明显有所减少,实际上我在冬季完全没有骑行。
最后,为了了解每个月在某项运动上的投入程度,我们绘制了一个堆叠条形图,显示了每个月每项运动的受苦分数比例:
受苦分数每月比例 — 作者提供的图像
#6 结语
在本文中,我尝试展示了通过掌握一些 Python 知识,你可以获得比 Strava 年终总结更多的关于你活动的洞察。
从你的 Strava 活动中获取的数据集是开始数据分析项目的绝佳起点!在这个项目中,我重点比较了每个月的跑步和骑行,但你还可以做很多其他分析。
如果你想分析你的活动并查看你的年度统计数据,整个代码可以在这里找到:
github.com/Guigs11/strava_insights
*感谢你读到文章的最后!
如果你有任何问题或意见,请随时在下方留言,或者通过* LinkedIn 联系我!
你的视觉-语言模型可能只是一个词袋
原文:
towardsdatascience.com/your-vision-language-model-might-be-a-bag-of-words-30b1beaef7f8?source=collection_archive---------5-----------------------#2023-03-21
我们在 ICLR 2023 的口头报告中探讨了视觉-语言模型对语言的理解极限
Federico Bianchi
·
关注 发布于 Towards Data Science · 8 分钟阅读 · 2023 年 3 月 21 日
--
图片由 ThisIsEngineering 提供。
多模态人工智能是当前的热门话题。随着 GPT-4 的发布,我们看到了许多过去六个月无法想象的新应用和未来技术。事实上,视觉-语言模型在许多不同的任务中非常有用。例如,通过 CLIP 你可以对未见过的数据集进行零样本图像分类,通常能够在不需要训练的情况下获得可靠的表现。
与此同时,视觉-语言模型也并非完美。这里,我们探索这些模型的局限性,强调它们可能失败的地方和原因。这篇博客文章是我们最近论文的简要/高层次描述,该论文将在 ICLR 2023 口头报告 中介绍。如果你想查看代码,只需点击 这里。
Yuksekgonul, M., Bianchi, F., Kalluri, P., Jurafsky, D., & Zou, J. (2023). 视觉-语言模型何时以及为何表现得像词袋模型,以及该如何应对?ICLR。
介绍
什么是视觉-语言模型?
视觉-语言模型通过利用视觉和语言数据之间的协同效应来执行各种任务,从而彻底改变了该领域。尽管文献中介绍了许多视觉-语言模型,但 CLIP 是最著名和广泛采用的模型。
通过将图像和字幕嵌入到同一向量空间中,CLIP 实现了跨模态推理,使用户能够执行如零-shot 图像分类和文本到图像检索等任务,并且精度良好。CLIP 采用对比学习方法来学习图像和字幕的嵌入表示。
对比学习的简要介绍
对比学习使 CLIP 能够通过最小化共享向量空间中图像与其对应字幕之间的距离来学习将图像与其字幕关联。这种方法已经被证明非常有效,CLIP 和其他基于对比的模型所取得的令人印象深刻的结果也证实了这一点。
对比损失用于比较批次中图像和字幕的配对,并优化模型以最大化匹配图像-文本配对的嵌入之间的相似度,并减少批次中其他配对的相似度。
下面的图像展示了一个可能的批次和训练步骤的示例:
-
紫色方块包含所有字幕的嵌入,绿色方块包含所有图像的嵌入。
-
矩阵的方块包含批次中所有图像嵌入和所有文本嵌入的点积(读取为“余弦相似度”,因为嵌入已经归一化)。
-
蓝色方块包含模型需要最大化相似性的配对,其他白色方块是我们希望最小化的相似度(因为这些方块包含了不匹配的图像-文本配对的相似度,例如,一张猫的图片和描述“我的古董椅子”)。
CLIP 中的对比预训练。蓝色方块表示我们希望优化相似性的配对。图片来源于 github.com/openai/CLIP
训练完成后,你应该拥有一个有意义的向量空间,在其中你可以对图像和描述进行编码。一旦你为每个图像和每个文本获取了嵌入,你可以执行许多任务,例如查看哪些图像与描述更相似(例如,在你 2017 年夏季度假专辑中查找“海滩上的狗”),或找出哪个文本标签与图像更相似(例如,你有一大堆你家狗和猫的图片,你希望能够识别它们)。
视觉-语言模型,如 CLIP,已成为通过整合视觉和语言信息解决复杂 AI 任务的强大工具。它们将这两种数据嵌入到共享向量空间中的能力,使其在各种应用中达到前所未有的准确性和性能水平。
视觉-语言模型理解语言吗?
我们的工作试图在回答这个问题上迈出一些步伐。关于深度模型是否或在多大程度上理解语言存在重大争议。在这里,我们的目标是调查视觉-语言模型及其组合能力。首先,我们提出了一个新的数据集,用于测试组合理解;这个新的基准叫做ARO(属性、关系和顺序)。然后,我们探讨了对比损失在这种情况下可能受到的限制。最后,我们提出了一个简单但有前景的解决方案。
新的基准:属性、关系和顺序
像 CLIP(以及来自 Salesforce 的更新模型 BLIP)这样的模型在理解语言方面表现如何?
我们收集了一组基于属性的组合描述(例如,“红色的门和站着的人”)和基于关系的组合描述(例如,“马在吃草”),以及相应的匹配图片。然后,我们生成了替代的错误描述,如“草在吃马”。模型能找到正确的描述吗? 我们还探索了打乱词语的效果:模型是否更喜欢未打乱的描述而非打乱的描述?
我们为属性、关系和顺序(ARO)创建的四个数据集在下图中有所说明(注意顺序包含两个数据集):
我们创建的不同数据集是关系、属性和顺序。对于每个数据集,我们展示一个图片示例和不同的描述。只有一个描述是正确的,模型必须识别正确的描述。图片由作者提供。
-
属性测试理解属性:“铺装的 道路 和 白色 房子” 与 “白色 道路 和 铺装的 房子”。
-
关系测试理解关系:“马 在吃 草” 和 “草 在吃 马”。
-
最后,顺序测试模型对顺序打乱的弹性:我们随机打乱标准数据集的描述(例如,MSCOCO)。
视觉语言模型能找到匹配图像的正确标题吗?这个任务看似简单,我们期望模型能理解“马在吃草”和“草在吃马”之间的区别,对吧?我意思是,谁见过草吃东西?
好吧,可能是 BLIP,因为它不能理解“马在吃草”和“草在吃马”之间的区别:
BLIP 不理解“草在吃马”和“马在吃草”之间的区别。图像由作者提供,包含来自 Visual Genome 数据集的元素。图像由作者提供。
现在让我们看看一些结果。少数模型在关系理解(例如,吃)上显著高于偶然水平。CLIP 在归因和关系上仅略高于偶然水平。这实际上表明视觉语言模型存在问题。
不同模型在归因、关系和顺序(针对 Flick30k)基准上的表现。你可以看到 CLIP、BLIP 和其他最先进的模型。图像由作者提供。
检索和对比损失的批评
这项工作的主要结果之一是我们可能需要超越标准对比损失来学习语言;但为什么呢?
从最上面开始:视觉语言模型通常在检索任务上进行评估:获取一个标题并找到它对应的图像。如果你查看用于评估这些模型的数据集(例如,MSCOCO、Flickr30K),你会发现它们通常包含需要组成能力才能理解的标题描述的图像(例如,“橙色的猫在红色的桌子上”)。那么,为什么模型在标题复杂的情况下却没有学会组成性理解呢?
剧透:你不一定需要组成性理解来在这些数据集上进行检索。
我们试图更好地理解这个问题,并测试了模型在重新排列标题中的单词顺序后的表现。我们能找到标题为“books the looking at people are”的正确图像吗?如果答案是肯定的,这意味着查找正确图像时不需要顺序信息。
我们测试的任务是用打乱的标题进行检索。即使我们打乱标题,模型仍能正确找到相应的图像(反之亦然)。这表明检索任务可能太简单。图像由作者提供。
我们测试了不同的打乱程序,答案是肯定的:即使使用不同的打乱技术,检索性能基本上不受影响。
再说一遍:视觉-语言模型在检索任务中表现优异——在这些数据集上——即使顺序信息不可得。这些模型可能像词袋一样运作,其中顺序并不重要:如果模型在检索中表现良好而不需要理解词 顺序,那么我们到底在检索中测量什么呢?
该怎么办?
既然我们知道存在问题,我们可能需要寻找解决方案。最简单的方案如下:让 CLIP 理解“猫在桌子上”与“桌子在猫身上”是不同的。
确实,我们提议通过增加专门用于解决此问题的困难负样本来改进 CLIP 的训练。这是一个非常简单有效的解决方案:它只需对原始 CLIP 损失做非常小的编辑,不会影响整体性能(具体细节可以在论文中阅读)。我们称这种 CLIP 版本为 NegCLIP。
在 CLIP 中引入困难负样本。我们添加了图像和文本的困难负样本。图片来源于作者。
基本上,我们要求 NegCLIP 将黑猫的图像与“桌子上坐着一只黑猫”这句话靠近,但与“猫上坐着一张黑桌子”这句话远离。后者是通过使用 POS 标记自动生成的。
这个修复的效果是,它实际上可以提高 ARO 基准上的性能,而不会影响检索性能或其他下游任务如检索和分类的表现。有关不同基准的结果,请参见下图(详细信息请见论文)。
NegCLIP 与 CLIP 在不同基准上的比较。蓝色基准是我们介绍的,绿色基准来自文献。图片来源于作者。
你可以看到,在 ARO 基准测试中有了显著的改进,而在其他下游任务中则有边际改进或类似性能。
代码!
Mert(论文的主要作者)在 创建一个小型库 来测试视觉-语言模型方面做了出色的工作。你可以使用他的代码来复制我们的结果或进行新模型的实验。
只需几行 Python 代码,你就可以下载数据集并开始运行!
import clip
from dataset_zoo import VG_Relation, VG_Attribution
model, image_preprocess = clip.load("ViT-B/32", device="cuda")
root_dir="/path/to/aro/datasets"
# Setting download=True will download the dataset to `root_dir` if it's not already there.
# For VG-R and VG-A, this is a 1GB zip file that is a subset of GQA.
vgr_dataset = VG_Relation(image_preprocess=preprocess,
download=True, root_dir=root_dir)
vga_dataset = VG_Attribution(image_preprocess=preprocess,
download=True, root_dir=root_dir)
# Do anything with the dataset. Each item will look like this :
# item = {"image_options": [image], "caption_options": [false_caption, true_caption]}
我们还发布了 NegCLIP 的实现(实际上是 OpenCLIP 的一个分支)。查看代码 这里。
告别
感谢阅读!希望这篇文章有趣。视觉-语言模型已经能做很多事情,我们迫不及待想看看未来的模型,例如 GPT4 能做些什么!
致谢
感谢 Mert 提供的所有建议!
相关内容
如果你想了解更多关于 CLIP 的信息,我写了一篇博客文章,详细介绍了相关内容。
[## 如何训练你的 CLIP]
CLIP 介绍以及我们在 HuggingFace 社区周期间如何为意大利语言进行微调。
towardsdatascience.com
我还对 CLIP 进行了时尚数据的微调。这里有一篇你可能感兴趣的博客文章!
[## 教授 CLIP 一些时尚]
训练 FashionCLIP,一种针对时尚领域的 CLIP 模型
towardsdatascience.com
你在数据项目中遇到了瓶颈,现在该怎么办?
原文:
towardsdatascience.com/youve-hit-a-wall-in-your-data-project-now-what-6d206082e182
考虑以下应对数据分析常见开发障碍的方法
Matthew Gazzano
·发表于Towards Data Science ·6 分钟阅读·2023 年 10 月 6 日
--
照片来自韦斯·希克斯在Unsplash
我们都遇到过这样的场景:你正在开发 SQL 查询、构建数据管道或建立预测模型。你认为这是最终输出的数据集,但一旦执行代码,结果却不是你所期望的。也许你返回了异常数量的记录、某个字段未正确填充,或者你的模型产生了意外的结果。
你知道有什么地方出了问题,但代码中没有错误信息或明确的指示来说明是什么造成了问题。你如何解决这个问题?
根据我在过去 6 年以上从事各种分析角色的经验,我可以证明,你会不断遇到新的开发问题的壁垒。然而,我已经采取了一些有效的策略来应对这些障碍,并成为一个更具韧性的专业人士。在接下来的部分中,我将分享一些对我最有效的技巧。
“你试过重启一下吗?”
我不是说关掉你的电脑——而是重置你的大脑。换句话说,试着离开你的桌子几分钟。
尽管听起来很傻,但你难以相信这对我帮助有多大。有时候,解决方案或新的问题方法会通过简单的散步自然地浮现出来。当我们过久地盯着同一个问题时,很容易对引发问题的项目部分产生隧道视角,忘记了关注更大的整体。
例如,你可能在遇到让 python 函数正确输出数据点的问题。但更重要的是,也许你已经失去了对函数最初需求的关注,它的依赖关系以及它预期要与之交互的其他数据。有时候,这样可以最好地帮助你重新回到解决问题的轨道上。
重新审视你的假设
根据我的个人经验,我职业生涯中的大多数障碍都源于对我正在处理的数据或代码做出了错误的假设。
在某个职位上,我的任务是复制一个离职数据分析师的仪表盘指标。我有访问旧 SQL 和用于创建这些指标的 Power BI 工作簿的权限。我以看似相同的方式复制了业务逻辑,但当我输出结果时,结果偏差了约 5%。我花了大量时间彻底检查代码的语法准确性、字段调用以及联接中的数据过滤,但这些方法似乎都不起作用。经过更详细的审查,结果发现我们都在对一个名为customer_active的字段进行过滤,但来自不同的源表,后端这两个字段的计算方式不同。
我上面障碍的根本原因是我对调用的字段做出了假设。虽然我以为字段的含义是正确的,但我对数据的错误假设最终导致了两个指标之间的 5%偏差。事后看来,倒退到源表进行分析以理解差异发生的位置会是更好的方法。
一般来说,当你处于这个阶段时,尝试思考以下问题:
-
我正在处理的数据的粒度是什么? 也就是说,每一行应该代表什么?例如,也许你正在处理一个每天快照的客户表——这个表的粒度是按客户和日期。确保表的预期粒度实际上就是输出的内容。
-
我正在审查的代码是否代表了全貌? 即,你是否需要了解数据在上游某处是否不正确?是否可能存在管道/摄取问题?
-
我是否真正理解 x 表与业务的关系? 确保你理解相关数据是如何从系统和流程角度获得的。如果不清楚,请与 SME 合作。
隔离代码的单独部分
在使用 SQL 时,我最喜欢的做法是将代码片段放入公共表表达式中,并分别查询每一个。这是我首次从使用 DBT 时发现的格式,你可以在他们的文档上阅读更多关于它的内容。请参见以下示例:
WITH customer_acq AS (
SELECT
*
, CASE
WHEN first_order_date IS NOT NULL
THEN first_order_date
ELSE salesforce_date_created
END AS acquisition_date
FROM customers
)
, main AS (
SELECT
*
FROM customer_acq
WHERE acquisition_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
)
SELECT * FROM main
你可能认为最后的 SELECT * FROM main 似乎没有必要,但我可以向你保证,这种结构将使你更容易排查代码片段。在这一行中,我们可以查询各个 CTE 的输出,以查看每一个出现了什么问题。这种技术对我来说非常有效,特别是当查询输出意外返回 0 条记录时。在这里,我可以通过将最后一个词“main”更改为不同的 CTE 名称来确定哪个 CTE 是过滤掉我所有记录的根本原因。
这是一种常见的误解,认为 CTE 对查询优化有负面影响——这实际上并不正确(至少不会造成明显的差异),特别是当你使用现代云数据仓库提供商时。
隔离一个示例记录
使用这种技术,我们希望查看一个示例记录在源系统、源数据库表以及你的代码输出中的表现。从中,我们可以确定 A.) 是否存在从源系统到数据库的数据管道问题,以及 B.) 你应用的逻辑如何影响这个记录。通过隔离一个记录,我们可以更清楚地了解个别输入是如何在我们开发的代码中流动的。在这一阶段,我们可以评估以下内容:
-
这个记录是否按照我的代码预期进行流动?
-
如果不是,那么这个记录与其他可能正常行为的记录有什么不同?
将此与我之前提到的技术结合使用,你还可以在代码的各个部分隔离单个记录。例如,利用上述定义的 CTE,查看特定记录在代码的每个片段中的输出情况。在这里,你应该能够 pinpoint 出哪个代码段不正确地处理了记录。
停止猜测
你需要对你正在处理的问题有一个全面的了解。当我的仪表板无法正确显示时,很容易形成一个习惯——稍微调整一下(更改过滤条件或类似操作),刷新仪表板并期望出现好结果。这无异于掷骰子,这不是有效解决多层次问题的方法。
拆解并重新框定问题陈述。问自己以下问题:
-
你试图回答哪些业务问题?
-
有哪些数据可用于回答这个问题?每个相关的数据表代表什么? 对所需和可用数据有一个明确的指导,以交付最小可行产品。
-
为了在源数据上构建 BI 层,需要进行哪些转换?
-
我正在处理的数据源/数据管道有什么细微差别?
-
数据质量——空值存在在哪里?数据的延迟是否存在问题?
如果你无法回答这些问题,寻求团队成员或数据领域专家的帮助是可以的。你对数据的熟悉程度对于提供有价值的解决方案至关重要,因此在必要时花时间了解更多的业务背景是很重要的。
结论
无论你处于什么级别,你在发展过程中都会遇到障碍。有时候,你需要忘记自己已经知道的关于问题的一切,并以全新的视角来处理它。当你发现自己面临意外的结果或障碍时,采取系统化的方法来克服这些挑战并保持冷静是至关重要的。
在任何数据项目中,遇到障碍不是失败的标志,而是成长的机会。通过应用这些策略并保持对项目的全面视角,你可以提升你的问题解决技能,最终实现更可靠和高效的结果。所以,下次你在数据项目中遇到困难时,记得运用这些技巧,重置你的视角,并知道每一个挑战都是你成为更强数据专业人士的一个步骤。
Zephyr 7B Beta:一个好的老师就是你所需要的一切
原文:
towardsdatascience.com/zephyr-7b-beta-a-good-teacher-is-all-you-need-c931fcd0bfe7
Mistral 7B 的知识蒸馏
本杰明·玛丽
·发表于 Towards Data Science ·阅读时间 8 分钟·2023 年 11 月 11 日
--
图片来源于 Pixabay
Mistral 7B 是其中一个 最佳预训练大型语言模型(LLM)。通过发布 Zephyr 7B Alpha,Hugging Face 已证明 Mistral 7B 经过 DPO 微调后可以超越比其大 10 倍的聊天模型,并且在某些任务上与 GPT-4 的性能相匹配。
模型名称中的“Alpha”显然表明 Hugging Face 计划发布更好的 Zephyr 7B 版本。他们确实在仅仅两周后发布了 Zephyr 7B Beta。arXiv 上有一份技术报告描述了该模型及其评估:
Zephyr: 语言模型对齐的直接蒸馏 (Tunstall 等, 2023)
在这篇文章中,我们将看到是什么让 Zephyr 7B Beta 比更大的 LLM 更好。更具体地说,我们将了解 Hugging Face 如何利用更大的 LLM,如 GPT-4,来教导 Mistral 7B 以响应指令并将回答与人类偏好对齐。
蒸馏:当较小的 LLM 向较大的 LLM 学习
由于 Hugging Face 依赖于知识蒸馏(KD)来训练 Zephyr,让我们简单回顾一下 KD 在 LLM 上的应用。
大多数 LLM 是在由人类撰写的文本上进行训练的。人类文本具有高多样性的标记序列和词汇,这使得建模变得困难。由于这种困难,我们需要大量的数据来训练 LLM 以正确建模语言。
有一个减少训练成本和难度的捷径:知识 蒸馏(KD)。有多种方法可以进行 KD。在这一部分,我将仅讨论 Hugging Face 使用的方法。
一旦在人工文本上训练,即使 LLMs 在生成语言方面表现非常好,它们也只能近似语言的真实概率分布。LLMs 生成的 token 序列比人类的要少得多。注意:这就是为什么在推理过程中经常引入随机抽样,例如通过核采样,以提高生成文本的多样性。
由于 LLMs 生成的 token 序列比人类文本的多样性要低,因此学习建模这些生成的序列是一项容易得多的任务。
实际上,这是通过使用最先进的模型(通常称为教师模型)来生成大量合成文本,这些文本将用于训练一个较小的模型,通常称为学生模型。学生模型从其教师那里提取知识。
学生模型在生成文本上的训练收敛速度要快得多,并且可以达到接近教师模型的性能。*。
这种策略在训练 LLMs 时效果很好。我们成功的最佳例子之一是微软的 phi-1.5:一个拥有 13 亿参数的模型,其表现与更大模型相匹配。微软的 phi-1.5 完全在由其他模型生成的合成数据上进行训练,即,微软的 phi-1.5 是一个学生模型。注意:微软没有披露教师模型是什么。
## 如何微调、量化和运行微软 phi-1.5
一个为多任务预训练的模型
kaitchup.substack.com
Hugging Face 的 Zephyr 7B Beta 也是一个学生模型。它的所有训练数据都是由更大模型生成的,因此相较于其他在人工文本上训练的类似大小的 LLMs(例如 Llama 2),其表现更好。
在 Zephyr 7B Beta 的情况下,Hugging Face 将知识蒸馏推进到训练和对齐 LLM 与人类偏好的过程中的更深层次,正如我们在接下来的部分中将看到的那样。
dDPO:使用 Mistral 7B 的蒸馏直接偏好优化
从 Mistral 7B 生成 Zephyr 7B Beta 是一个三步过程:
-
在由其他更大模型生成的指令数据集上进行监督微调(SFT)
-
使用最先进的 LLM 对 LLMs 的输出进行评分/排名
-
使用步骤 1 中获得的模型对步骤 2 中获得的数据进行 DPO 训练
蒸馏监督微调(dSFT)
SFT 是训练指令/聊天模型的标准第一步。它需要一个指令数据集:指令/问题与人类给出的答案配对。
主要问题在于,收集这样的数据集非常昂贵,因为它涉及人力劳动。一个越来越常见且更便宜的替代方案是使用其他 LLMs 生成的指令数据集。
我们可以在 Hugging Face Hub 上找到许多这样的指令数据集,例如:
-
OpenAssistant Conversations Dataset (OASST1)(84.4k 训练示例)
-
OpenOrca(4.2M 训练示例)
-
openassistant-guanaco(9.8k 训练示例)
对于 Zephyr 7B Beta,Hugging Face 在自定义版本的 Ultrachat 上微调了 Mistral 7B,该版本经过了严格筛选:
- HuggingFaceH4/ultrachat_200k(MIT 许可证),使用“sft”拆分
我们应用了真正的大小写规则来修复语法错误(大约 5% 的数据集),以及几个过滤器来关注有用性并删除不需要的模型回应。
Hugging Face 将这种 SFT 称为“蒸馏监督微调”,因为微调是在“教师”模型生成的数据集上进行的。
基于偏好的 AI 反馈(AIF)
为了与人类对齐,我们需要一个包含排名答案的提示数据集。然后我们可以使用 DPO 或 RLHF 来训练模型生成首选答案。
对模型答案进行排名是一项昂贵的任务,需要人工劳动。但我们已经有对齐的 LLM,足以进行这种排名。
我们可以使用现有的提示数据集与由不同模型生成的答案配对,并使用最先进的 LLM 来对这些答案进行排名。
对于这一步,Hugging Face 直接使用了数据集 UltraFeedback。
UltraFeedback 包含 74k 个提示,配对以下模型生成的回应:
-
LLaMA-2–7B-chat, LLaMA-2–13B-chat, LLaMA-2–70B-chat
-
UltraLM-13B, UltraLM-65B
-
WizardLM-7B, WizardLM-13B, WizardLM-70B
-
Vicuna-33B
-
Alpaca-7B
-
Falcon-40B-instruct
-
MPT-30B-chat
-
StarChat-Beta
-
Pythia-12B
每个 LLM 的输出由 GPT-4 按 1 到 5 的评分(分数越高越好)进行评估,评分标准如下:
-
指令跟随
-
有用性
-
诚实性
-
真实性
对于 DPO,我们需要一个“选择”的输出,即我们偏好的输出,以及一个“拒绝”的输出,即我们不希望模型生成的输出。
对于选择的输出,选择了得分最高的输出(使用所有标准计算这个平均值)。对于被拒绝的输出,他们在剩余的输出中随机选择一个。
他们将这种随机选择的理由说明如下:
我们选择了随机选择而不是选择最低得分的回应,以鼓励多样性并使 DPO 目标更具挑战性
他们制作并用于 DPO 训练的数据集版本在这里:
- HuggingFaceH4/ultrafeedback_binarized(MIT 许可证),使用“prefs”拆分
蒸馏直接偏好优化(dDPO)
指令 LLM,例如聊天模型,通常使用人类反馈的强化学习(RLHF)进行训练,采用邻近策略优化(PPO)。它能够很好地使 LLM 与人类偏好对齐,但 RLHF 也不稳定且复杂。实际上,在运行 RLHF 之前,我们需要训练两个模型:
-
一个通过在指令数据集上进行有监督微调(SFT)简单训练的参考模型
-
一个训练用于预测人类偏好的奖励模型。该模型的训练数据通常是人类对给定提示的模型输出进行排名的结果。奖励模型被训练以预测这些排名。
然后,RLHF 使用 4 个不同的模型:
-
使用 SFT 训练的参考模型
-
奖励模型
-
一个通常由奖励模型初始化的价值模型
-
我们希望使用 RLHF 训练的模型(策略),通常由参考模型初始化
使用所有这些模型,RLHF 使用 RL 来优化语言模型策略,以生成高奖励的响应(根据奖励模型),而不会过度偏离原始参考模型。
几个框架实现了 RLHF,使其计算上更高效。然而,它仍然是一个复杂且不稳定的过程,涉及多个模型。
DPO 是 RLHF 的一个简单替代方案。它隐式优化与现有 RLHF 算法相同的目标(具有 KL 散度约束的奖励最大化)。DPO 的作者证明,受约束的奖励最大化问题可以通过解决一个更简单的人类偏好分类问题来精确优化。
由于它可以被简化为分类问题,DPO 使用简单的二元交叉熵目标来训练模型。DPO 完全消除了强化学习的需求。
给定一个提示和几个根据质量由人类排名的 LLM 输出,DPO 训练模型为最佳输出分配更高的奖励。
DPO 只需要两个模型:
-
使用 SFT 在指令数据集上微调的参考模型
-
我们希望使用 DPO 训练的基础模型
插图由 Rafailov et al. (2023) — CC-BY 4.0
DPO 在这篇 arXiv 论文中由斯坦福大学提出:
直接偏好优化:你的语言模型在秘密中是一个奖励模型
请注意,对于 Zephyr,Hugging Face 称之为“蒸馏直接偏好优化”,仅仅是因为 SFT 和偏好是由其他 LLM 生成的。DPO 过程本身仍然是标准的。
如果你对使用 DPO 微调 Mistral 7B 感兴趣,可以看看我的教程:
[## 使用直接偏好优化(DPO)微调你自己的 Mistral 7B 指令版本
制作便宜的 Zephyr 7B
Kaitchup - Fine-tune Your Own Instruct Version
Zephyr 7B Beta 的评估
Hugging Face 在 MT Bench 上从以下几个方面评估了 Zephyr:写作、角色扮演、推理、数学、编程、提取、STEM 和人文学科
插图由 Tunstall et al. (2023) 提供 — CC-BY 4.0
显然,Zephyr 在性能上优于 Llama 2 70B,同时在其他最先进的商业 LLM 中表现接近。GPT-4,作为 Zephyr 的主要老师,在推理、数学、编程和提取方面仍然表现更好。
他们还进行了一项消融研究,以展示 DPO 的重要性。
表格由 Tunstall et al. (2023) 提供 — CC-BY 4.0
单独使用 DPO(第一行)表现较差。然而,DPO 和 SFT 的组合明显优于单独使用 SFT。
结论
通过依赖知识蒸馏,Hugging Face 展示了在不使用任何人工标注的情况下训练和对齐最先进的大型语言模型(LLM)是可能的。
Zephyr 7B Beta 是一种相对便宜的模型,尤其是与其他更大模型如 Llama 2 Chat 70B 相比。然而,鉴于每个 GPU 的训练批量(2)以及他们完全微调了 Mistral 7B,他们不得不使用 16 个 A100 80 GB GPU(根据技术报告,训练时间长达 4 小时)。
请注意,你可以使用 LoRA 来训练 DPO,配合 Hugging Face 的 TRL 库。这可以显著减少内存消耗。Hugging Face 没有使用像 LoRA 这样的参数高效微调方法,但他们对这些方法能与完全微调一样有效持乐观态度:
我们没有尝试像 LoRA(Hu et al., 2021)这样的参数高效技术,但预计这些方法也会有类似的结果。
为了支持我的工作,请考虑订阅我的新闻通讯:
[## Kaitchup - AI on a Budget | Benjamin Marie, PhD | Substack
每周提供有关微调、运行和在计算机上服务大型语言模型的新闻、提示和教程。每期…
Kaitchup - AI on a Budget | Benjamin Marie, PhD | Substack
Zero-ETL、ChatGPT 与数据工程的未来
原文:
towardsdatascience.com/zero-etl-chatgpt-and-the-future-of-data-engineering-71849642ad9c?source=collection_archive---------0-----------------------#2023-04-03
后现代数据栈即将到来。我们准备好了吗?
Barr Moses
·
关注 发布于 Towards Data Science ·9 分钟阅读·2023 年 4 月 3 日
--
图片由作者提供。
如果你不喜欢变化,那么数据工程可能不适合你。这个领域几乎没有什么能逃脱再创造。
最近最显著的例子是 Snowflake 和 Databricks 颠覆了数据库的概念,开启了现代数据栈时代。
作为这一运动的一部分,Fivetran 和 dbt 从 ETL 到 ELT 根本改变了数据管道。Hightouch 在试图将重心转移到数据仓库的过程中中断了 SaaS 颠覆世界的趋势。Monte Carlo 也加入了争论,并说:“也许让工程师手动编写单元测试并不是确保数据质量的最佳方式。”
今天,数据工程师继续在现代数据堆栈的启蒙坡道上踩踏硬编码的管道和本地服务器。不可避免的整合和幻灭的低谷在地平线上以安全的距离显现。
因此,几乎有些不公平的是,新的创意已经开始出现以颠覆这些颠覆者:
-
Zero-ETL 将数据摄取作为目标
-
AI 和大型语言模型可能会彻底改变转型
-
数据产品容器正在瞄准数据核心构建块的王座
我们是否还需要重新构建一切(再次)?Hadoop 时代的余温甚至还未完全散去。
答案是,当然我们将不得不重建我们的数据系统。可能在我们的职业生涯中多次。真正的问题是为什么、何时和如何(按这个顺序)。
我不敢自称拥有所有答案或水晶球。但这篇文章将仔细探讨一些最突出的未来近(或近似)创意,以及它们对数据工程的潜在影响,可能会成为后现代数据堆栈的一部分。
实用性和权衡
图片由Tingey Injury Law Firm提供,来源于Unsplash
现代数据堆栈的崛起并不是因为它比前任做得更好。存在真实的权衡。数据变得更大、更快,但也更混乱、治理更少。成本效益的判断仍在悬而未决。
现代数据堆栈之所以称霸,是因为它支持用例并以以前无法或极其困难的方式解锁数据的价值。机器学习从流行词汇转变为收入生成器。分析和实验可以更深入地支持更大的决策。
对于下面的每一个趋势,情况都将是如此。每种趋势都有其优缺点,但推动采用的因素将是它们,或是我们尚未发现的黑马创意,如何解锁利用数据的新方式。让我们更深入地探讨每一个。
Zero-ETL
它是什么:一个误称;数据管道仍然存在。
今天,数据通常由服务生成并写入事务数据库中。一个自动管道被部署,不仅将原始数据移动到分析数据仓库,还在过程中稍微修改它。
例如,API 将数据以 JSON 格式导出,摄取管道不仅需要传输数据,还需要应用轻量转换,以确保数据以表格格式加载到数据仓库中。摄取阶段常见的轻量转换包括数据格式化和去重。
虽然你可以通过在 Python 中硬编码管道来进行更复杂的转换,一些人已主张这样做 以将预建模的数据交付到数据仓库,但大多数数据团队选择不这样做,以便于操作和可见性/质量的考虑。
Zero-ETL 通过让事务数据库在自动加载数据到数据仓库之前进行数据清洗和标准化,改变了这一摄取过程。需要注意的是,数据仍处于相对原始的状态。
目前,这种紧密集成是可能的,因为大多数 Zero-ETL 架构要求事务数据库和数据仓库来自同一个云提供商。
优点: 降低延迟。避免重复的数据存储。减少一个故障源。
缺点: 减少了在数据摄取阶段自定义数据处理方式的能力。可能存在一些厂商锁定。
谁在推动: AWS 是这个流行词的推动者 (Aurora to Redshift),但 GCP (BigTable to BigQuery) 和 Snowflake (Unistore) 都提供类似的功能。Snowflake (Secure Data Sharing) 和 Databricks (Delta Sharing) 也在追求他们所谓的“无数据复制共享”。这个过程实际上不涉及 ETL,而是提供对存储数据的扩展访问。
实用性和价值释放潜力: 一方面,鉴于科技巨头的支持和现成的能力,Zero-ETL 看似只是时间问题。另一方面,我观察到数据团队正在解耦,而不是更紧密地整合他们的操作数据库和分析数据库,以防意外的模式更改导致整个操作崩溃。
这种创新可能会进一步降低软件工程师对其服务生成的数据的可见性和责任感。当数据在代码提交后很快就进入数据仓库时,他们为什么还要关心数据模式呢?
目前,数据流和微批处理方法似乎满足了对“实时”数据的大多数需求,我认为这种创新的主要业务驱动因素是基础设施的简化。尽管这并非无关紧要,但无复制数据共享的可能性可能会消除冗长安全审查的障碍,从而在长期内促使更广泛的采用(虽然需要明确的是,这并不是非此即彼的选择)。
一个大表与大型语言模型
现状:目前,业务利益相关者需要将他们的需求、指标和逻辑表达给数据专业人员,后者将其翻译成 SQL 查询,甚至可能还会生成一个仪表盘。即便所有数据已存在于数据仓库中,这一过程仍需要时间。更不用说在数据团队最喜欢的活动清单上,临时数据请求的排名介于根管治疗和文档编写之间。
有许多初创公司旨在利用像 GPT-4 这样的高级语言模型,通过让消费者在一个流畅的界面中用自然语言“查询”数据,从而自动化这一过程。
至少在我们的新机器人统治者使二进制成为新的官方语言之前。
这将彻底简化自助分析过程,并进一步推动数据的民主化,但鉴于更复杂的分析数据管道,其超出基本的“指标获取”之外的问题可能会很难解决。
但如果通过将所有原始数据塞入一个大表来简化这种复杂性会怎样呢?
那是 Benn Stancil 提出的想法,他是数据领域最优秀且前瞻性的作家/创始人之一。没有人比他更多地想象过现代数据栈的死亡。
作为一个概念,这并不那么不切实际。一些数据团队已经利用了一个大表(OBT)策略,该策略有支持者和反对者。
利用大型语言模型似乎可以克服使用一个大表的最大挑战之一,即发现、模式识别的困难及其完全缺乏组织性。对人类来说,拥有目录和明确标记的章节对他们的故事是有帮助的,但 AI 并不在意。
优点:也许,终于实现了自助数据分析的承诺。加速洞察。使数据团队能够花更多时间挖掘数据价值和构建,减少响应临时查询的时间。
缺点:这是否意味着过多的自由?数据专业人员对数据的痛苦怪癖(时区!什么是“账户”?)比大多数业务利益相关者更为熟悉。我们是否从具有代表性而非直接的数据民主中受益?
谁在推动:像Delphi和GetDot.AI这样的超级早期初创公司。像Narrator这样的初创公司。像 Amazon QuickSight、Tableau Ask Data或 ThoughtSpot 这样的更成熟的公司在做一些类似的事情。
实用性和价值释放潜力:令人耳目一新的是,这不是一项寻求应用场景的技术。价值和效率显而易见,但技术挑战也是如此。这个愿景仍在构建中,需要更多的时间来发展。也许最大的障碍是所需的基础设施干扰,这对于更成熟的组织来说风险过高。
数据产品容器
它是什么:数据表是构建数据产品的基本单元。事实上,许多数据领导者认为生产表是他们的数据产品。然而,要将数据表视为产品,需要增加许多功能,包括访问管理、发现和数据可靠性。
容器化在软件工程中的微服务运动中发挥了重要作用。它们增强了可移植性、基础设施抽象,并最终使组织能够扩展微服务。数据产品容器概念设想了数据表的类似容器化。
数据产品容器可能证明是使数据更可靠和可治理的有效机制,特别是如果它们能更好地展示诸如语义定义、数据血缘和与基础数据单元相关的质量指标等信息。
优点:数据产品容器似乎是一种更好地打包和执行四个数据网格原则(联邦治理、数据自助服务、将数据视为产品、领域优先基础设施)的方法。
缺点:这一概念是否会使组织更容易或更难扩展他们的数据产品?另一个基本问题是,这些未来数据趋势中的许多是否都存在这样的问题:数据管道的副产品(代码、数据、元数据)是否对数据团队有价值,值得保留?
谁在推动它: Nextdata,由数据网格创始人 Zhamak Dehgahni 创办。 Nexla 也在这个领域发挥作用。
实用性和价值解锁潜力:虽然 Nextdata 最近才从隐秘中浮出水面,数据产品容器仍在不断发展,但许多数据团队已经从数据网格实现中看到了证明的结果。数据表的未来将取决于这些容器的确切形状和执行。
数据生命周期的无尽重新想象
图片来源:zero take 在 Unsplash
要洞察数据的未来,我们需要回顾数据的过去和现在。过去、现在、未来——数据基础设施处于持续的破坏和重生状态(尽管也许我们需要一些更多混乱)。
数据仓库的意义已经从比尔·英门在 1990 年代引入的术语发生了剧烈变化。ETL 管道现在是 ELT 管道。数据湖不像两年前那样模糊。
随着现代数据栈带来的这些创新,数据工程师在决定数据如何流动以及数据消费者如何访问数据方面仍然发挥了核心的技术角色。但有些变化比其他变化更大、更可怕。
“零 ETL”这个术语看起来很威胁,因为它(不准确地)暗示了数据管道的死亡,没有数据管道,我们还需要数据工程师吗?
尽管对 ChatGPT 生成代码能力的宣传不少,但这个过程仍然在技术数据工程师的手中,他们仍然需要进行审查和调试。大型语言模型令人担忧的一点是,它们可能从根本上扭曲数据管道或我们与数据消费者的关系(以及数据如何提供给他们)。
然而,无论这个未来是否会实现,它仍然强烈依赖于数据工程师。
从古至今一直存在的是数据的一般生命周期。它被发射、被塑造、被使用,然后被归档(最好不要在这里沉溺于我们自己的生死问题)。
虽然基础设施可能会发生变化,自动化将时间和关注点转移到右侧或左侧,但人类数据工程师将在可预见的未来继续在从数据中提取价值方面发挥关键作用。
这不是因为未来的技术和创新不能简化今天复杂的数据基础设施,而是因为我们对数据的需求和使用将继续增加复杂性和规模。
大数据一直并将始终如一个来回摆动的钟摆。我们在能力上取得一次飞跃,然后很快就会找到达到这些极限的方法,直到下一个飞跃成为必要。安慰于这个周期吧——被需要的感觉很好。
Shane Murray 共同撰写了这篇文章。 订阅 以便将他的故事送到你的邮箱。
对数据质量的未来感兴趣吗?请联系 Monte Carlo 团队!
零-shot 与相似度基于的文本分类
原文:
towardsdatascience.com/zero-shot-vs-similarity-based-text-classification-83115d9879f5?source=collection_archive---------3-----------------------#2023-01-05
对无监督文本分类方法的评估
Tim Schopf
·
关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 1 月 5 日
--
图片来源于 Gertrūda Valasevičiūtė 在 Unsplash
这篇文章基于我们的 NLPIR 2022 论文 “评估无监督文本分类:零-shot 与相似度基于的方法”。你可以在这里阅读更多细节。
什么是无监督文本分类?
无监督文本分类方法旨在在训练期间不使用标注数据的情况下进行分类,因此有潜力减少标注成本💰。一般来说,无监督文本分类方法旨在根据文本描述将文本映射到标签,而不使用标注训练数据。为此,主要存在两类方法。
👯 第一类方法可以总结为基于相似性的方法。在此过程中,这些方法首先生成文本和标签描述的语义嵌入,然后尝试使用诸如余弦相似度之类的相似性度量将文本与标签进行匹配。
0️⃣ 🔫 第二类方法使用零样本学习来分类未见过的文本。零样本学习利用属于已见类别的标记训练实例来学习一个分类器,该分类器可以预测属于不同、未见过类别的测试实例。例如,一个零样本分类模型可能在训练期间学会正确分类关于足球⚽的文本,然后在测试时利用这一知识来分类关于篮球🏀的文本,即使之前从未见过关于篮球的文本。其理念是,模型可以将关于足球的知识转移到非常相似的篮球类别上。虽然零样本学习技术在训练时使用了标注数据,但它们不使用标签来提供目标类别的信息,而是利用已见类别的知识来分类未见类别的实例。由于预训练的零样本文本分类模型不需要在目标类别的标记数据上进行微调,因此我们将其归类为无监督文本分类策略。
在这篇博客文章中,我们总结了我们论文📄 “评估无监督文本分类:零样本和基于相似性的 approaches (2022)”的贡献如下:
-
我们评估了无监督文本分类中基于相似性和零样本学习类别。为此,我们在不同的基准数据集上对每个类别的代表性方法进行实验。
-
我们提出了基于 SimCSE 和 SBERT 句子嵌入相似性的简单但强大的无监督文本分类基线。之前的工作大多与不同的弱基线如 Word2Vec 相比,这些弱基线容易被超越,并且往往高估了新的无监督文本分类方法的性能。
-
由于基于变换器的文本表示近年来被广泛建立为语义文本相似度的最先进技术,我们进一步调整 Lbl2Vec,这是最新且表现优良的无监督文本分类相似度方法之一,以与基于变换器的语言模型配合使用。
无监督文本分类方法
👯 使用 Lbl2Vec 的基于相似度的文本分类
存在许多基于相似度的无监督文本分类方法。然而,最近引入的Lbl2Vec 方法相比其他基于相似度的方法表现更佳。因此,我们在本研究中重点关注了这一方法。Lbl2Vec通过联合嵌入词、文档和标签表示来工作。首先,使用Doc2Vec学习词和文档的表示。然后,通过余弦相似度利用每个类别的标签关键词表示的平均值来找到一组最相似的候选文档表示。候选文档表示的平均值,反过来,为每个类别生成标签向量。最终,对于分类,文档被分配到标签向量和文档向量余弦相似度最高的类别。在这里,你可以找到更多关于Lbl2Vec如何工作的资料。
此外,我们调整了 Lbl2Vec 方法,使用基于变换器的文本表示,而不是Doc2Vec,来创建联合嵌入的词、文档和标签表示。由于基于变换器的文本表示目前在文本相似度任务中实现了最先进的结果,我们研究了不同的文本表示对这种基于相似度的文本分类策略的影响。在本文中,我们使用SimCSE和SBERT变换器模型来创建文本表示。接下来,这种方法被称为Lbl2TransformerVec。
0️⃣ 🔫 使用蕴含方法的零样本文本分类
尽管存在更新的零样本文本分类(0SHOT-TC)方法,零样本蕴含方法仍然在预测未见类别的实例时,与类似大小的模型相比,能够产生最先进的 0SHOT-TC 结果。正如名称所暗示的,零样本蕴含方法将 0SHOT-TC 视为一个文本蕴含问题。其基本思想类似于基于相似性的文本分类方法。传统的 0SHOT-TC 分类器无法理解实际问题,因为标签名称通常被转换为简单的索引。因此,这些分类器很难从已见类别推广到未见类别。将 0SHOT-TC 视为一个蕴含问题为分类器提供了文本标签描述,因此使其能够理解标签的含义。
0️⃣ 🔫 使用 TARS 进行零样本文本分类
TARS也使用文本标签描述来在零样本设置中进行文本分类。然而,TARS 将任务视为一个二分类问题,其中给定一个文本和一个文本标签描述,模型对该标签是否真实进行预测。TARS 的作者表示,这种方法在 0SHOT-TC 中显著优于GPT-2
📐 基准
我们将当前最先进的无监督文本分类方法的发现与一些基本基准进行比较,以评估它们的性能。
LSA:对于每个数据集,我们应用 LSA 来学习𝑛 = 类别数概念。随后,根据文档和标签关键词的 LSA 向量的最高余弦相似度对文本文件进行分类。
Word2Vec:这根据周围的上下文词生成词的语义向量表示。词嵌入的平均值用于表示文本文件和标签关键词。文本文件根据生成的 Word2Vec 文档和标签关键词的表示的最高余弦相似度进行预测和分类。
SimCSE:这是一个对比学习框架,它生成句子嵌入,在语义相似性任务中取得了最先进的结果。我们使用 SimCSE 文档嵌入和 SimCSE 标签关键词嵌入作为类别表示。最后,文本文件根据生成的 SimCSE 文档和标签关键词的表示的最高余弦相似度进行分类。
SBERT:这是一种对BERT的修改,使用了 siamese 和 triplet 网络结构来推导具有语义意义的句子嵌入。我们使用与 SimCSE 相同的分类方法,只不过这次使用 SBERT 嵌入而不是 SimCSE 嵌入。
🔬 实验
对于我们的 SimCSE 实验,我们使用sup-simcse-roberta-large模型。为了使用 SBERT 创建嵌入,我们使用了两个不同的预训练 SBERT 模型。我们选择了通用模型all-mpnet-base-v2和all-MiniLM-L6-v2,这些模型在超过十亿对训练样本上进行了训练,预计在句子相似度任务上表现良好。all-mpnet-base-v2 模型比 all-MiniLM-L6-v2 模型大,保证了稍微更好的质量句子嵌入。而较小的 all-MiniLM-L6-v2 模型则保证了五倍的编码速度,同时仍能提供高质量的句子嵌入。
为了评估 0SHOT-TC,我们进行了三种不同预训练的零样本蕴含模型的实验:一个DeBERTa模型,一个大型BART模型,以及一个较小的DistilBERT模型。对于 TARS 实验,我们使用基于 BERT 的预训练tars-base-v8模型。
💾 数据
我们的评估基于四个公开可用的文本分类数据集,20Newsgroups,AG‘s Corpus,Yahoo! Answers,以及Medical Abstracts,这些数据集来自不同领域。由于我们使用类别描述的语义意义进行无监督文本分类,我们从每个类别名称推断标签关键词,这些关键词作为文本类别描述的目的。因此,推断步骤仅仅是使用数据集官方文档提供的类别名称作为标签关键词。在少数情况下,如果我们认为这是对某个类别更合适的描述,我们还用同义词或语义相似的关键词替换了类别名称。
使用的文本分类数据集概述。
📊 评估结果
在不同数据集上检查的文本分类方法的 F1 分数(micro)。各数据集上最佳结果以粗体显示。由于我们使用微平均法来计算分类指标,我们可以实现 F1、Precision 和 Recall 的相等分数。
我们可以观察到,根据这些数据,没有任何基线方法在任何数据集上获得最高的 F1 分数。这表明,使用先进的无监督文本分类方法通常能获得比简单基线方法更好的结果。然而,我们观察到LSA 和 Word2Vec 方法通常效果最差,并且 容易被超越 👎。相比之下,SimCSE 和 SBERT 基线方法产生了强劲的 F1 分数 💪,甚至在某些情况下连一些先进方法也未能超越。此外,SimCSE 和 SBERT 基线方法在三个数据集上的表现可能优于 Lbl2Vec 基于相似性的方法。我们可以推断出,使用先进的基于相似性的方法通常能比使用简单基线方法或 0SHOT-TC 获得更好的无监督文本分类结果。具体来说,使用 SBERT 嵌入的 Lbl2TransformerVec 方法表现出色,因为它们在所有数据集上都表现良好 🏆,并超越了基线结果。相比之下,0SHOT-TC 方法表现持续较差,在大多数情况下甚至未能超越基线结果 😲。然而,DeBERTa 零-shot 蕴涵模型能够令人惊讶地很好地分类领域特定的医学摘要,并在该数据集上取得了所有分类器中的最佳 F1 分数。我们观察到,大型 DeBERTa 零-shot 蕴涵模型总是显著优于较小的 BART-large 和 DistilBERT 零-shot 蕴涵模型。此外,基于 BERT 的 TARS 模型的表现略优于较小的 DistilBERT 零-shot 蕴涵模型,除非在领域特定的医学摘要数据集上。因此,我们得出结论,0SHOT-TC 方法的表现随着模型大小的增加而提高 🤔。
💡结论
-
基于相似性的文本分类方法通常在各种不同领域中优于 0SHOT-TC方法。
-
文本嵌入的特性使得相似的主题或类别在嵌入空间中靠得很近。这意味着能够在嵌入空间中连贯地聚类主题的文本表示在无监督文本分类方法中表现良好。这一特性在我们的工作中也很明显,可以在下图中看到 👇。
DensMAP的文档嵌入可视化。文档嵌入是使用 SBERT(all-mpnet-base-v2)创建的。
-
简单方法如LSA 或 Word2Vec 容易超越,因此不建议用作未知类文本分类的基线。
-
SimCSE 和 SBERT的基线方法****产生强大的无监督文本分类结果,甚至超过了一些更高级的分类器。因此,我们建议使用 SimCSE 和 SBERT 基线来评估未监督文本分类方法和未知类 0SHOT-TC 在将来的性能。
-
Lbl2TransformerVec,我们提出的基于相似性的文本分类方法获得了最佳的 F1 分数几乎所有数据集。
我们将我们的Lbl2TransformerVec代码公开在github.com/sebischair/Lbl2Vec
。如果您想阅读更多关于我们方法的详细信息,您可以在这里阅读我们的论文(https://arxiv.org/abs/2211.16285)。
Sources
[## 评估无监督文本分类:零样本和基于相似性的方法
未知类的文本分类是一项具有挑战性的自然语言处理任务,主要是使用…
arxiv.org](https://arxiv.org/abs/2211.16285?source=post_page-----83115d9879f5--------------------------------) [## Lbl2Vec: An Embedding-Based Approach for Unsupervised Document Retrieval on Predefined Topics
在本文中,我们考虑了从无标记文档数据集中检索具有预定义主题的文档的任务…
arxiv.org](https://arxiv.org/abs/2210.06023?source=post_page-----83115d9879f5--------------------------------) ## Unsupervised Text Classification with Lbl2Vec
An introduction to embedding-based classification of unlabeled text documents
towardsdatascience.com [## GitHub - sebischair/Lbl2Vec: Lbl2Vec learns jointly embedded label, document and word vectors to…
Lbl2Vec 学习联合嵌入标签,文档和单词向量来检索具有预定义主题的文档…
github.com](https://github.com/sebischair/Lbl2Vec?source=post_page-----83115d9879f5--------------------------------) [## Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks
Abstract BERT(Devlin et al., 2018)和 RoBERTa(Liu et al., 2019)在新的最先进表现方面设立了新的标准性能…
aclanthology.org [## SimCSE: 简单对比学习句子嵌入
高天宇, 姚兴成, 陈丹琪。2021 年自然语言处理经验方法会议论文集…
aclanthology.org [## 零样本文本分类基准测试:数据集、评估与蕴涵方法
摘要 零样本文本分类(0Shot-TC)是一个具有挑战性的自然语言理解问题,迄今为止关注较少…
aclanthology.org [## 面向任务的句子表示用于通用文本分类
Kishaloy Halder, Alan Akbik, Josip Krapac, Roland Vollgraf。第 28 届国际会议论文集…
aclanthology.org
零和游戏和混合策略
原文:
towardsdatascience.com/zero-sum-games-and-mixed-strategies-21c158aecf07
模拟战略互动需要我们考虑不确定性
Michael Kingston
·发表于 Towards Data Science ·12 分钟阅读·2023 年 1 月 6 日
--
我在早期的文章中多次重复了我认为博弈论对数据科学至关重要的陈词滥调。我将重复我最喜欢的一句:“博弈论就像是带有激励的概率论”。
如果你感兴趣,可以在这里找到我早期的一些关于博弈论的文章:
- 博弈论:基础知识
## 博弈论
基础知识:同时移动游戏
[towardsdatascience.com
- 纳什均衡简介
[## 纳什均衡简介
纳什均衡是博弈论中的一种策略配置,其中没有玩家拥有主导策略。每个玩家……
builtin.com](https://builtin.com/data-science/nash-equilibrium?source=post_page-----21c158aecf07--------------------------------)
- 纳什均衡的应用
## 纳什均衡的应用
Bertrand,Cournot 和 Hotelling
towardsdatascience.com
- 博弈论中的主导策略解释
[## 博弈论中的主导策略解释
博弈论中的主导策略发生在一个玩家相对于另一个玩家拥有更强、更有效的策略时……
builtin.com](https://builtin.com/data-science/dominated-strategy-in-game-theory?source=post_page-----21c158aecf07--------------------------------)
什么是混合策略?
让我们回到纳什均衡为何是一个有用的解答概念的原因:它将始终在每个有限博弈中给我们一个答案。这就是纳什存在定理:
“任何有限数量玩家和有限数量动作的游戏都有一个混合策略纳什均衡。”
那么在这种情况下“混合”是什么意思呢?基本上,这意味着在许多场景中,我们需要向战略互动模型中引入概率,以使这些模型具有意义。
用来说明这一点的经典例子是“匹配硬币”,这通常又被重新诠释为足球比赛。个人来说,我更喜欢足球版本,但如果你有兴趣,这里有一个关于匹配硬币的解释链接:
[## 匹配硬币 - 维基百科]
匹配硬币是博弈论中使用的一个简单游戏的名称。它由两个玩家,即偶数和奇数组成……
en.wikipedia.org](https://en.wikipedia.org/wiki/Matching_pennies?source=post_page-----21c158aecf07--------------------------------)
无论你喜欢哪种隐喻,二者都是零和游戏,因为每个玩家的效用增减正好被另一个玩家的效用变化所平衡。两位玩家的效用变化总和为零。
点球游戏:
图片由 Sven Kucinic 提供,来自 Unsplash
点球发生在足球比赛中,作为对某种犯规的惩罚,或者作为解决必须有胜者的平局的一种方式。在点球过程中,球门只由守门员防守。罚球者从距离球门线 12 码的指定点进行射门。
奖励的设定使得罚球者希望进球,而守门员希望扑救成功。
关键在于,点球时球的速度如此之快,以至于如果守门员等到看到球的去向才开始移动进行扑救,他们将永远无法成功。因此,守门员必须根据他们认为球将去向的地方做出反应,这使得这实际上是一个同时移动的游戏。
射手可以以几乎无限的方式射球,而守门员也可以以几乎无限的方式扑救。为了将这种情况建模为游戏,我们将行动集合限制为仅两个选项:左或右。我们将“右”定义为“从射手的角度看是右”,将“左”定义为“从射手的角度看是左”。在这种框架下,如果玩家的行动匹配,那么守门员跳向了正确的方向并完成了扑救。如果行动不匹配,守门员跳向了错误的方向,射手进球了。
收益矩阵:
以下是与这个故事一致的收益矩阵。如果我们使用我们通常的方法寻找纳什均衡,我们会发现以下情况:
作者提供的图片
看起来没有任何策略配置具有我们称之为纳什均衡所需的属性,因为对于每一个可能的结果,一方玩家都有动机单方面偏离。
然而这仍然是一个有限的游戏。
难道纳什存在定理不说明我们必须能够在这里找到均衡吗?
混合策略:
问题在于我到目前为止在文章中定义策略的方式。我假设玩家必须“确定”选择一个行动,这被称为纯策略。纯策略是早期引入博弈论时的一个方便假设,但这是一个与现实不一致的假设。
正如我们所知,生活和数据科学中的许多事物更适合用概率术语来描述,博弈论也不例外。我们需要开始将概率引入我们的博弈论工具箱中。
玩家不一定要使用纯策略,实际上他们通常不会。玩家可以根据某种概率分布在他们的纯策略之间进行随机化。 这样做被称为使用混合策略。
在点球游戏中,守门员的混合策略会是什么样的?这将是一个概率分布,指定守门员以概率p进行“右”球,这将意味着另一种可用的行动(“左”)以概率1-p进行。类似地,对于射手,混合策略将是以概率q进行“右”球,以概率1-q进行“左”球。
点球游戏中的混合策略:
以这种更广泛的方式重新定义策略,使我们能够在点球游戏及许多其他游戏中找到纳什均衡。
假设最适合这种情况的概率分布是伯努利分布。在这一假设下,点球游戏的唯一纳什均衡是(0.5R + 0.5L, 0.5R + 0.5L),或者用通俗的话来说,每个玩家以相等的概率选择右和左。
直观上可以这样理解:
-
如果守门员更可能跳右边或更可能跳左边,那么踢球者的最佳反应是与守门员更可能做的事相反:这样,他们可以在一半以上的时间里进球。
-
同样地,如果踢球者更可能踢右边或更可能踢左边,那么守门员的最佳反应是匹配踢球者更可能做的事情:这样,他们可以在一半以上的时间里扑救成功。
-
但如果你的对手同样有可能选择右或左,无论你做什么,互动的结果会恰好有一半的时间对你有利。既然你无所谓,不如混合策略。
以下是最佳反应函数的图示,进一步说明了这一观点。
图片来源:作者
努力监控游戏:
图片由Wes Hicks提供,Unsplash
这是工人与雇主之间的战略互动。
-
工人选择努力工作或偷懒。
-
雇主同时选择是否检查工人。
-
工人的偏好是,偷懒比工作更愉快,但前提是不被抓到。
-
雇主的偏好是,他们只希望在认为工人偷懒时检查工人(并随后惩罚他们)。
-
如果雇主认为工人在工作,他们宁愿不浪费时间和激怒员工,因此更愿意不检查。
以下是与此故事一致的支付矩阵:
图片来源:作者
这个游戏在纯策略下没有纳什均衡,因为像点球游戏一样,两个玩家都希望他们的策略对对方是“出乎意料”的。工人只有在他们认为雇主会检查时才愿意工作……但雇主只有在他们认为工人会偷懒时才愿意检查!
让我们在混合策略中寻找纳什均衡。如果我们称p为雇主检查的概率,当工人在工作和不工作之间无差异时,
如果p > 1/3,工人工作的预期收益大于他们的偷懒预期收益;如果p < 1/3,工人偷懒的预期收益大于他们工作的预期收益。当p=1/3时,工人无差异。任何策略——包括任何混合策略——都是最佳反应。
如果我们称q为工人的工作概率,当工人对偷懒和工作无差异时:
-
如果q > 5/7,雇主不检查的预期收益大于检查的预期收益,
-
如果q>5/7,雇主对检查的预期收益大于他们对不检查的预期收益。
-
当q>5/7,雇主对检查的预期收益大于他们对不检查的预期收益。
-
当q=5/7时,雇主无所谓。任何策略——包括任何混合策略——都是最佳响应。
基于上述,唯一的互最佳响应集是
努力监控游戏:现实世界的影响
取原始努力监控游戏,减少在工人偷懒并被抓的结果中的收益,然后重新计算混合策略均衡。你应该会发现工人的混合策略没有改变,但雇主现在检查的频率越来越低。这告诉我们,在这种结构的游戏中,被抓的高概率和被抓时的高惩罚是替代品。
在许多现实世界的情境中,特别是在社会背景下而非工作场所中,传统观点认为增加被抓的概率可能比增加被抓的惩罚更具成本。这被称为某些国家监禁率上升的原因之一。
增加前者意味着增加用于监控和警察工作的人员(这可以被描述为成本函数),而增加惩罚具有不同(通常较低)的成本函数。因此,传统观点认为,通过减少监控但增加惩罚,通常可以实现相同的合规率。
随着监控成本的降低,而监禁惩罚成本的增加,这一观点正在发生变化。机器学习、计算机视觉和广泛的公共监控等技术被引入“预测性警务”,改变了成本效益平衡。
敌友游戏:
我女儿告诉我“敌友”是一种现象。我承认我真的不理解,但无所谓。作为一种游戏,它给我们提供了 7 个关于混合纳什均衡的关键见解。
这个故事是你和你的“敌友”之间的战略互动。你在选择去聚会还是去酒吧(注:在澳大利亚,酒吧类似于当地的酒吧)。
-
其他条件相同,你会更喜欢聚会,
-
但你也想见到你的“敌友”,所以如果他们去酒吧,你会对聚会和酒吧无所谓。
-
你的敌友可以选择聚会、酒吧或待在家里。
-
待在家里是最糟糕的选择。
-
你的敌友对聚会和酒吧没有特别的偏好——但你的敌友想要避开你,所以他们更倾向于去他们认为你不会去的地方。
-
以下是与这个故事一致的收益矩阵:
作者提供的图片
使用我们通常的方法,我们可以在这个游戏中找到一个纯策略的纳什均衡(派对,酒吧)。但这个解并不令人满意,因为它未能捕捉由不确定性产生的复杂性。具体来说,当每个玩家的最佳决策取决于另一个玩家的决策,反之亦然时,引入了不确定性,这要求我们将概率引入游戏环境。这反过来要求混合策略。
混合策略中是否也存在纳什均衡?假设你选择混合策略:以一定概率去“派对”,以p的概率去“酒吧”。如果你使用我们迄今为止遵循的技术,你会接着找出使敌友在三种纯策略之间无差异的值。
如果你正确地做了这一步,你得到的p值不在 0 和 1 之间,因此不符合概率的定义。
发生了什么?
数学告诉你,没有任何混合策略能让你的敌友在三种纯策略之间无差异。
为什么不呢?
因为“家里”被“派对”和“酒吧”严格支配。
就像玩家在纯纳什均衡中不会选择严格支配的策略一样,他们在混合策略纳什均衡中也不会选择严格支配的策略。
这意味着在寻找任何纳什均衡时,我们可以忽略“家里”这一策略
寻找纳什均衡集合:
你的敌友可能在他们剩余的策略“派对”和“酒吧”之间无差异,因此可能被诱导选择混合策略。
但假设你的敌友混合选择,即以正概率选择派对和酒吧。你最佳的反应是什么?(注:希腊字母π在经济学中常用来表示经济利润或回报。本文中的π有此含义)
如果我们称你的敌友去派对的概率为p:
- 你去派对的期望回报是:
- 你去酒吧的期望回报是:
因此,只要p>0,你去派对的期望回报就大于你去“酒吧”的期望回报。
换句话说,如果你的敌友有任何去派对的机会,你会更愿意去派对。这是因为对你而言,“派对”弱于“酒吧”支配。当你的敌友确定去酒吧时,你才对这两种策略无差异。否则,你会更倾向于“派对”。
所以只要你的敌友以p>0的概率选择“派对”,你就应该去派对。但是,你的敌友——想要避开你——会怎么做呢?
他们会去酒吧!
所以我们有了一个矛盾。如果你的敌友以任何正概率去派对,那么你会确定去派对,这会让他们更倾向于去酒吧。
由此我们可以得出以下结论:在所有纳什均衡中,你的敌友肯定会选择“Pub”(概率为 1)。
看起来像是个难题,对吧?
这是否意味着在这个游戏中没有混合策略的均衡点?
不一定!
在纳什均衡中,有可能一些玩家使用混合策略,而其他玩家使用纯策略。
-
你的敌友将在所有纳什均衡中使用纯策略。
-
那个纯策略(pub)使你无差别,这意味着你选择的任何策略都是最佳响应。
-
因为在纳什均衡中,我们需要一组与“Pub”相关的互为最佳响应。
如果我们称你选择派对的概率为p:
也就是说,只要你选择“pub”的可能性不高于“party”,你的敌友的最佳响应就是“pub”。
总结来说,这个游戏的纳什均衡集合可以描述为:
-
如果你的敌友选择“Pub”,而你以大于或等于½的概率玩“Party”,即p>½。
-
回想一下“其他条件相同,你会更喜欢派对”的表述。因此,假设p>½似乎是合理的。
-
注意这意味着游戏中存在无限多个纳什均衡。
-
还要注意,我们找到的第一个纳什均衡——那个纯策略均衡——属于这一组。
总结 — 混合策略:
敌友游戏开始将概率和不确定性引入战略环境。我们将在未来的文章中进一步探讨。
它还阐明了关于混合策略均衡的 7 个重要事实:
-
混合策略中的纳什均衡仍然是纳什均衡——它们必须满足与纯策略纳什均衡相同的要求。
-
每个玩家的策略都是对其他所有玩家策略的最佳响应。
-
没有任何玩家有偏离的激励。
-
混合策略不必为所有纯策略分配正概率。
-
严格支配的策略在任何纳什均衡中都不会被选择,包括混合策略的均衡。
-
并非所有玩家在混合策略纳什均衡中都必须混合策略——有些可能在使用纯策略,而其他人则在混合策略。
-
可以说,所有纳什均衡都是混合策略纳什均衡——纯策略纳什均衡只是一个特殊情况,其中所有玩家将概率 1 分配给一个策略,其他策略的概率为 0。