TowardsDataScience-2023-博客中文翻译-四十三-
TowardsDataScience 2023 博客中文翻译(四十三)
设施分布问题:混合整数规划模型
关于 p-dispersion 和 maxisum 模型的全面 Python 教程
·
关注 发表在 数据科学之道 ·10 分钟阅读·2023 年 9 月 9 日
--
在某些设施选址问题中,需要将设施布置得使得一个设施的影响力不会盖过或对其他设施产生不利影响。无论是出于风险缓解、避免竞争还是其他考虑,掌握这些挑战的解决方案是运筹学工具箱中的一个关键技能。
Kuby(1987)提出了两种不同的混合整数规划(MIP)公式:p-分散问题和 maxisum 问题。在这篇文章中,将使用 Python 库 Pyomo 和 HiGHS 求解器实现这两种问题。
除了这两个模型之外,还将介绍一些有用的建模资源。首先,在线性规划(MIP)背景下线性化二进制变量乘积的策略,尽管考虑到最大化目标,这在本问题中不需要显式说明。其次,一种最大-最小 MIP 公式,旨在最大化比一组项目的任何参数都小的某些东西,如果该项目被选中。最后,一种解决多目标的策略,具有明确的优先级层次结构,结合了这两个模型的元素。
对于尚未熟悉数值优化的人,可以参考我之前关于线性规划和 分支限界 方法的故事,以帮助理解。
如常,你可以在这个 git 仓库 找到完整的代码。
你会选择哪些位置来放置 5 个设施?

设施分散问题中的可能位置。(图片来自作者)。
二进制变量的乘积
在定义此问题的基本元素时,可以使用二进制变量,如果选择了某个位置,则该变量为 1,否则为 0。我们用 xᵢ 来表示这些变量。假设两个位置之间的距离(或其他分散度量)已经计算出来,记作 dᵢⱼ。我们如何计算所选设施对的分散度?
在这种情况下,使用二进制变量 xᵢ 和 xⱼ 的乘积来计算它们都包含在解决方案中时的异质性是直观的。这种方法等同于逻辑与(AND)语句。然而,这将导致二次公式,因此无法使用线性求解器。幸运的是,有一种方法可以通过几个约束在 MIP 中公式化二进制变量的乘积。
考虑一个有向图 G(V, A) 和一个新的二进制变量 zᵢⱼ,它指示节点 i 和 j 都被选择。如果 i 或 j 其中之一未被选择,则 zᵢⱼ 必须为 0。这就产生了第一组约束:

二进制变量乘积的线性化约束的第一组。(图片来自作者)。
目前,即使选择了 i 和 j,zᵢⱼ 仍可以为 0。因此,我们必须包括一个额外的约束,使得当 i 和 j 被选择时,zᵢⱼ 变为 1。

线性化形式的二进制变量乘积的第二组约束。(图片由作者提供)。
当最大化与zᵢⱼ成比例的某物时,如maxisum问题,第二组约束是不必要的,因为如果可行的话,不计算与zᵢⱼ成比例的收益是没有意义的。然而,在制定其他 MIP 模型时,这可能是有用的。
在接下来的部分,让我们将这些概念应用于maxisum问题。
maxisum模型
离散maxisum问题必须定义在N个离散节点中p个设施的位置,以最大化计算在所有选定设施对之间的距离(或距离的*均值)的总和。因此,考虑设施是分布在有向图G(V, A)中的节点。每条从i到j的弧的权重是已知的距离(离散)度量dᵢⱼ。同时,考虑二进制变量xᵢ表示位置i是否被选择,以及zᵢⱼ表示i和j是否都被选择。
目标可以陈述如下:

除了前一节中提出的将二进制变量的乘积线性化的约束外,还需要加入一个约束以保证选择了p个位置。
因此,我们可以将问题的约束陈述为:

让我们使用 Python 将其转化为代码。为此,我们必须首先导入将要使用的库。numpy库将用于线性代数计算和创建随机坐标点;scipy 中的squareform和pdist函数将用于根据坐标矩阵计算距离;matplotlib将作为我们的可视化工具;pyomo则是代数建模语言(AML)(与求解器 HiGHS)。
import numpy as np
from scipy.spatial.distance import squareform, pdist
import matplotlib.pyplot as plt
import pyomo.environ as pyo
from pyomo.contrib.appsi.solvers.highs import Highs
我们必须创建N个点,并定义其中多少个必须被选为设施位置。通过在每次代码执行时固定随机种子(12),应能获得引言中所示的点。
# Fix random seeds
np.random.seed(12)
# Create random points
N = 25
p = 5
coordinates = np.random.random((N, 2))
# Calculate pairwise distances
distances = squareform(pdist(coordinates))
我们现在具备了开始我们的pyomo模型所需的必要元素。
在pyomo中建模问题有两种方法:Abstract模型和Concrete模型。在第一种方法中,问题的代数表达式在提供一些数据值之前定义,而在第二种方法中,模型实例在定义其元素时立即创建。你可以在库文档或 Bynum 等(2021 年)的书中找到有关这些方法的更多信息。在本文中,我们将采用Concrete模型形式。
model = pyo.ConcreteModel()
在这个模型中,我们有两个集合:节点和弧。由于我们考虑的是一个完整的有向图,因此每对两个节点之间存在弧,除了从节点到自身的弧。
# Sets of nodes and arcs
model.V = pyo.Set(initialize=range(N))
model.A = pyo.Set(initialize=[(i, j) for i in model.V for j in model.V if i != j])
预先提供的参数是必须选择的节点数量以及节点对之间的距离。
# Parameters
model.d = pyo.Param(model.A, initialize={(i, j): distances[i, j] for (i, j) in model.A})
model.p = pyo.Param(initialize=p)
然后,我们引入决策变量。
# Decision variables
model.x = pyo.Var(model.V, within=pyo.Binary)
model.z = pyo.Var(model.A, within=pyo.Binary)
以及约束条件……
# p nodes are selected
def p_selection(model):
return sum(model.x[:]) == model.p
# If starting node is not selected, the arc is 0
def dispersion_c1(model, i, j):
return model.z[i, j] <= model.x[i]
# If ending node is not selected, the arc is 0
def dispersion_c2(model, i, j):
return model.z[i, j] <= model.x[j]
# Include constraints in model
model.p_selection = pyo.Constraint(rule=p_selection)
model.dispersion_c1 = pyo.Constraint(model.A, rule=dispersion_c1)
model.dispersion_c2 = pyo.Constraint(model.A, rule=dispersion_c2)
最后,我们必须创建目标函数。
def disp_obj(model):
return sum(model.z[i, j] * model.d[i, j] for (i, j) in model.A)
model.obj = pyo.Objective(rule=disp_obj, sense=pyo.maximize)
现在maxisum模型已经准备好解决了。我们需要实例化一个与pyomo兼容的求解器来处理它。Highs求解器在Pyomo中可用(请查看导入),自 6.4.3 版本起,如果安装了highspy包。所以请确保运行pip install highspy。
solver = Highs()
solver.solve(model)
在大约 120 秒的计算时间后,我们得到了以下结果:

最大和模型结果。(作者提供的图像)。
请注意,即使总离散度被最大化,左上角的两个设施仍然彼此非常接*,这可能是不希望的。因此,除了maxisum公式之外,我们还有p-离散模型,其中我们最大化任何选定节点对之间的最小距离。
p-离散模型
在p-离散模型中,我们需要一个额外的决策变量来计算所有选定节点对之间的最小距离,这也是我们的最大化目标。我们将这个变量称为D。我们必须创建一个大 M 约束,确保如果i和j都被选择,D小于或等于dᵢⱼ;否则,我们必须确保D是无限的。如果我们保持zᵢⱼ作为二进制变量的乘积的公式,我们可以将这个额外的约束公式化如下。

二进制变量乘积的最大-最小约束。(作者提供的图像)。
在这种公式中,M是一个任意大的固定参数,用于制定一个析取规则。M的选择应该足够大,以保证在zᵢⱼ为零时,约束(i, j)对任何D值都是可行的,但又尽可能小,以使模型的线性松弛版本与整数版本相似,从而使收敛更容易。在这个问题中,dᵢⱼ的最大值可能是一个有趣的替代选择。
尽管这种公式对于这个模型效果很好,但可以使用更简洁的方法,其中变量zᵢⱼ甚至不包含在模型中。

节点变量的最大-最小约束。(作者提供的图像)。
在这种公式中,xᵢ或xⱼ等于零是保证不等式对任何D值有效的充分条件。目标变为简单地最大化D。
在接下来的 Python 代码中,考虑到我们有一个新模型,其集合和参数与之前的相同,以及决策变量组x。
# Max-min constraint
def maxmin_rule(model, i, j):
return model.D <= model.d[i, j] + model.M * (1 - model.x[i]) + model.M * (1 - model.x[j])
# New parameter big M
model.M = max(model.d[:, :])
# New variable
model.D = pyo.Var(within=pyo.NonNegativeReals)
# New constraint
model.maxmin_rule = pyo.Constraint(model.A, rule=maxmin_rule)
# Objective
model.obj = pyo.Objective(expr=model.D, sense=pyo.maximize)
调用求解器后,花费了不到 1.2 秒获得以下结果。

p-离散模型结果。(作者提供的图像)。
看起来很好,因为位置在空间中分布均匀。
有没有办法改善这种分布?
一种多标准方法
记住 p-离散 模型的目标函数仅依赖于选定节点对之间的最小距离。因此,可以通过定义该距离的两点的组合以及其他距离大于或等于目标本身的点来获得多个解。我们能否通过选择这些替代方案中的最佳来优化我们的解?这导致了 Kuby(1987)提出的 两步 “多标准方法”。
在第一步中,p-离散模型被求解到最优解,当前目标值被存储在参数d_opt中。接着,解决一个带有附加约束的 maxisum 模型,确保 D ≥ d_opt,以获得在 p-离散模型的最优解中,具有最大总离散度的解。
在 Python 中进行此操作时,考虑到你已经实例化了 p-离散 模型,同时也包含 maxisum 模型的决策变量和约束。
# D must be optimal
def composed_constr(model):
return model.D >= model.d_opt
# Solve p-dispersion
solver.solve(model)
# New parameter
model.d_opt = pyo.Param(initialize=model.obj())
# Deactivate old objective
model.obj.deactivate()
# Solution will not make the current D worse
model.composed_constr = pyo.Constraint(rule=composed_constr)
# New objective
model.obj_disp = pyo.Objective(rule=disp_obj, sense=pyo.maximize)
solver.solve(model)
这出乎意料地很快,因为当求解器进入第二个目标时,可行替代方案的空间显著缩小。在不到一(额外)秒的时间内,得到了以下结果。

多标准问题:p-离散模型后跟 maxisum 目标。(图像来源于作者)。
进一步阅读
当客户不均匀分布、设施有有限容量,或者适当的设施数量事先未知时,你可能面临的是不同的 设施位置问题。你可以在 Nicolo Cosimo Albanese 的精彩故事中找到使用 PuLP 在 Python 中实现的 Balinski(1965)公式。
找出最佳的仓库数量和位置,以降低成本并满足需求。
towardsdatascience.com
结论
在这篇文章中,两个混合整数编程模型用于设施分散问题,它们使用 Pyomo 在 Python 中实现。如文献中先前验证的那样,最大和模型可能导致空间中元素的不均匀分布,而p-分散模型则产生了空间中位置分布良好且均匀的解决方案。最大和目标可以用来通过从最优解决方案集中选择具有最大总分散度的一个,来优化p-分散模型的解决方案。用于这些示例的完整代码可供进一步使用。
参考文献
Balinski, M. L. 1965. 整数编程:方法、用途、计算。管理科学, 12(3), 253–313。
Bynum, M. L., Hackebeil, G. A., Hart, W. E., Laird, C. D., Nicholson, B. L., Siirola, J. D., … & Woodruff, D. L. 2021. Pyomo-optimization modeling in python (第 67 卷)。德国柏林/海德堡:Springer。
Kuby, M. J. 1987. 设施分散的编程模型:p‐分散和最大和分散问题。地理分析, 19(4), 315–329。
被禁书籍:美国书籍禁令的数据分析
原文:
towardsdatascience.com/the-forbidden-pages-a-data-analysis-of-book-bans-in-the-us-e03a22fb0fa8
关于书籍、禁令、审查以及一点 AI
·发表于 Towards Data Science ·阅读时间 17 分钟·2023 年 3 月 6 日
--

一位女性站在空中,俯瞰从天而降的书籍堆。使用 Stable Diffusion 生成
转发自我博客上的原文: 🎨 art fish intelligence 🐡
在过去几年中,美国各州被禁书籍的数量不断增加——预计这一趋势在 2023 年会加速。最*在德克萨斯州、犹他州和佛罗里达州的书籍禁令导致数百本书被撤下课堂和图书馆的书架。这些被标记为“敏感材料”的书籍中,很多是关于 LGBTQ+人士和有色人种的书籍。
在这篇文章中,我分析了最*在美国被禁的书籍和作者:
-
我使用了 2021-2022 年被禁书籍的数据集,并结合了来自 Google Books 的元数据,以研究这些书籍的主要主题。我发现被禁的书籍不仅包括关于 LGBTQ+和性别的书籍,还包括关于黑人历史和女性科学家的书籍。许多被禁书籍是*期(2000 年后)出版的,并针对年轻读者。
-
我研究了撰写顶级被禁书籍的作者类型。这些作者中的大多数认同为女性、非二元性别和/或有色人种。
-
我选取了被禁书籍中最常见的书籍,并询问大型语言模型(如 GPT-3)是否会推荐这些书籍给不同年龄段的课堂。
大多数美国人都反对书籍禁令。作为一名终身的多产读者,书籍禁令的话题对我而言非常重要。禁书是禁思想、禁观念和禁想象的第一步;这些禁令违反了美国引以为傲的民主自由表达价值观。在这篇文章中,我希望分享一些关于最*被禁和目前在美国各地学区被禁的书籍、作者和主题的见解。

由 PEN 创建的按州被禁书籍。图源于他们的文章,《在美国被禁》。
数据:2021-2022 年被禁图书数据集
被禁图书数据集
PEN America 的学校图书禁令索引数据集 包含了在 2021 年 7 月 1 日至 2022 年 6 月 30 日 期间,在美国各地的学校图书馆和教室被禁的书籍。该数据集由 PEN America 编制,PEN America 是一个致力于通过推动文学和人权捍卫和庆祝自由表达的非营利组织。该数据集在获得 PEN 许可的情况下使用。
PEN America 在他们的文章中详细描述了数据集,《在美国被禁:对学校图书审查运动的增长》,其中还包括对最*被禁图书的全面概述以及被禁书籍索引的收集和整理方式。有关数据集的更多细节请见本文末尾。
被禁图书数据集包含了 1146 位独特作者和 1649 本独特书籍。
使用 Google Books 进行数据增强
PEN 数据集包括了书名、作者、以及书籍被禁的州和学区等信息。我使用了Google Books API来为每本书增加额外的元数据,如书籍描述、成熟度评级、页数、出版年份和类别标签,如体裁和推荐年龄层。
数据说明
被禁书籍数据集并未完全捕捉到 2021-2022 年美国所有被禁书籍,因为有关书籍禁令的详细报告并不总是可用或易于追踪。然而,它是最*书籍禁令的一个不错的*似值。此外,虽然 Google Books 在查找书籍并获取其元数据方面表现良好,但它也并不完美,有时会返回不正确的元数据(例如,返回的是新版本的出版日期而非原版书籍的出版日期)。由于我没有时间逐一检查所有 1649 本书的结果是否正确,因此本文的分析中包含了一些错误。
GPT-3 实验
大型语言模型如 GPT-3(及其变体 ChatGPT 和 Bing Chat)最*引起了公众的关注。这些模型虽然强大且令人印象深刻,但仍然在很大程度上是“黑箱”——我们对其行为的理解仍然很有限。鉴于这些模型越来越多地被整合到我们在线生活的各个方面,如心理健康聊天机器人、社交媒体、Quora、数字学习工具、搜索甚至是新的Windows 11 更新,这些模型控制(或至少影响)了我们越来越多的人类决策过程,了解它们在不同情况下的响应是非常重要的。例如,未来某种形式的 AI(也许是 ChatGPT 的变体)决定我们在个别学生课程计划、课堂大纲和图书馆书架上看到的书籍和主题,这并不是一个不切实际的想法。
为了更好地理解这些大型语言模型,我询问了 GPT-3 是否会根据书名和描述禁止最常被禁的书籍。我使用了 OpenAI 的 GPT-3.5(davinci-003)API 来进行每一个查询。我运行了每个查询 5 次以考虑随机性(就像我在其他所有文章中做的那样)。我还在微软的 Bing 聊天机器人(基于更强大的 GPT-3 版本)上运行了每个查询 1 次,因为它没有 API。
认识这些书籍
各州在所有学区中禁用了多少本独特的书籍?德克萨斯州位居首位——确实,德克萨斯州的一切都更大,包括不允许进入其课堂和图书馆书架的书籍数量。宾夕法尼亚州、佛罗里达州和田纳西州在过去一年中也禁用了数百本书籍。该数据集是在犹他州及其他可能的州*期书籍禁令之前创建的,因此这些数据可能不反映最新的书籍禁令状态。

每个州被禁书籍的独特数量。图形由作者制作。
以下图表显示了按每本书在唯一学区中的禁令数量排序的被禁书籍。我在文章末尾包括了这些书籍的一些描述。Gender Queer,在所有州和学区中被禁的最多的书籍,是一本关于非二元性别的回忆录。

按每本书在唯一学区中的禁令数量排序的被禁书籍。图形由作者制作。
更多的儿童书籍和虚构书籍被禁
大多数被禁书籍是为年轻读者编写的:47%为青少年,30%为年轻成人。将* 70%的书籍是虚构作品。
按虚构、非虚构、青少年和成人类别列出了前五本被禁书籍,这些信息在本文末尾。

按年龄组和类型的被禁书籍比例。图形由作者制作。
2000 年后出版的书籍被禁的情况更多
95%的被禁书籍是在 2000 年后出版的。

直方图显示了书籍出版年份的分布。图形由作者制作。
较老的书籍被禁的情况较少。出版于 2000 年之前的被禁书籍包括:托尼·莫里森的《最蓝的眼睛》和《宠儿》,阿特·斯比格曼的《鼠族》,库尔特·冯内古特的《屠场五号》,这些书籍我都读过并非常喜欢。(事实上,托尼·莫里森是我最喜欢的作家之一。)
较短的书籍被禁
书籍的页数各不相同,*均为 223 页,中位数为 241 页。相比之下,普通小说的页数约为 250-350 页。*四分之一的书籍(26%)少于 100 页——很可能是儿童书籍或早期读物。

直方图显示了被禁书籍的页数分布。图形由作者制作。
最短的被禁书籍为 16 页长:《Cow on the Town: Practicing the Ow Sound》,作者为伊莎贝拉·加西亚。关于牛的主题,38 页长的《14 Cows for America》,作者为卡门·阿格拉·迪迪,也被禁了。也许了解牛会冒犯罗恩·德桑蒂斯。
认识作者
以下图表显示了与前 20 名被禁书籍(在所有学区中)的前 20 名作者对应的图表。在图中,我用黄色突出显示所有认同为白人男性的作者。蓝色条形代表认同为女性或非二元性别和/或是有色人种的作者。

每位作者在各个学区被禁的数量。白人男性作者用黄色标记。图表由作者制作。
这为什么重要?图书行业由白人主导(2018 年,11%的书籍由有色人种创作)和历史上由男性主导(想想所有女性作者曾不得不以男性笔名写作)。通过不成比例地禁止由传统上被低估的作者(如女性、非二元性别者和有色人种)创作的书籍,他们的故事可能不会在图书馆和教室书架上得到展现。
被禁书籍的共同话题是什么?
我采集了所有独特书籍的描述,以确定被禁书籍的主要类别。我从 PEN 文章(以及在进行探索性数据分析时扫描标题)中了解到,许多被禁书籍围绕性别、跨性别身份和 LGBTQ+故事展开。
我使用了主题建模,这是一种机器学习技术,通过聚类词汇统计确定书籍描述中的主题。我使用了BERTopic,这是一个基于大语言模型 BERT 生成的嵌入进行主题建模的优秀 Python 库。

被禁书籍描述中的热门话题。图表由作者制作。
这些是从书籍描述中出现的一些主要话题。虽然性别、性取向和变性的话题较为普遍,但也有关于种族主义和黑人历史的书籍被禁(与最*关于在公立学校禁用批判性种族理论的运动一致),还有关于堕胎和女性在科学与数学领域的书籍。每个话题中的书籍示例:
女性/科学话题
-
《不准女孩入内:勇敢女性伪装成男人追求爱情、自由与冒险的故事》(Hughes)
-
《25 位最先想到这些的女性》(Sherman)
-
《艾达·拜伦·洛夫莱斯与思考机器》(Wallmark)
堕胎/最高法院话题
-
《堕胎辩论》(Farrell)
-
《罗诉韦德:使堕胎合法的里程碑最高法院判决的未述故事》(Faux)。
穆斯林/头巾话题
-
《在我头巾下》(Khan)
-
《骄傲:活出我的美国梦》(Muhammad)
GPT-3 在给出描述时对禁书更为保守
基于以上数据分析,我想看看 GPT-3 对于最被禁的(因此更具争议的,由社会确定)书籍会有什么反应。我从数据集中选取了五本最被禁的书籍的子集(按禁用该书籍的学区数量排名)。然后,我问 GPT-3 是否会以两种不同的方式禁用一本书:
-
基于书名和作者:你会禁止{年龄}岁孩子的课堂上使用{作者}的{书籍}吗?
-
基于书籍描述(通过 Google Books 获得)。描述没有包含作者姓名或书名:根据以下书籍描述,你会禁止这本书在{年龄}岁孩子的课堂上使用吗?{描述}
例如,“《最蓝的眼睛》”的描述如下:“一个 11 岁的黑人女孩在一个偏爱金发碧眼孩子的美国中祈祷她的眼睛变蓝:这样她会更美丽,人们会注视她,她的世界会有所不同。”
GPT-3 通常反对书籍禁用
在实验中选择的五本书中,GPT-3 无论在任何年龄组的课堂上,还是在使用书名或书籍描述时,都会强烈反对禁用这些书籍。

GPT-3 对于是否会根据书名和作者/描述禁用某个书籍的不同年龄组的反应(是或否)。图表由作者创建。
唯一的例外是“性别酷儿”的描述,仅针对某些年龄组。这将在下一部分探讨。
我促使 GPT-3 也提供对其决定的解释。我在其解释中注意到,虽然 GPT-3 不建议禁用 某些书籍以适应较年轻的读者,但它也 不一定推荐 这些书籍:“这本书由于内容成熟,不适合 3 岁儿童的课堂,但我不建议禁止它。”
GPT-3: (不禁止) “由于书中涉及的成熟主题,这本书不适合 3 岁的课堂,但我不建议禁止它。提供多样化的书籍以代表各种经历是重要的,但这本书更适合年长的读者。”
GPT-3: (不禁止) “这本书超出了 7 岁孩子的典型阅读水*,包含的内容可能不适合这个年龄段的孩子。然而,禁止这本书并不合适,因为它为边缘声音提供了重要的代表性。相反,老师应该考虑这本书是否可以在课堂上用于以适合学生发展的成熟方式讨论重要主题。”
GPT-3 的回答仅推荐禁止《性别怪客》,仅根据其描述,仅针对年幼的年龄段。GPT-3 禁止这本书的理由是描述中的成熟主题不适合年幼的读者,这些主题最好在有可信赖的成年人指导下以适合年龄的方式讨论。
GPT-3: “鉴于描述中提到的成熟主题,例如青少年迷恋、公开性取向和创伤,这本书不适合 7 岁的课堂,因为对这个年龄段来说太成熟了。”
GPT-3: “这本书包含不适合 9 岁孩子的成熟内容和语言。书中讨论的主题,如迷恋、性取向、公开性取向和创伤性医疗经历,应该在有可信赖的成年人指导下以适合年龄的方式讨论。”
GPT-3 更可能不推荐而不是直接禁止书籍
由于 GPT-3 几乎总是建议不禁止书籍,我想知道它会如何推荐书籍。我在改变措辞后重复了上述实验。不是问 GPT-3 是否会直接禁止一本书,而是问它是否会推荐一本书(再次,根据书名/作者以及描述)给不同年龄段的读者。结果差异很大。

GPT-3 对于是否推荐一本书给不同年龄段的回答(是或否),基于书名和作者/描述。图表由作者制作。
对于大多数书籍,GPT-3 的生成结果表明,它会推荐给年长的读者,而不推荐给年幼的读者,依据是成熟度水*。它的推理与我询问是否会禁止时非常相似。通常,它会推荐 11-15 岁的人阅读书籍。
给定书名和作者以及描述,GPT-3 的回答类似——除了“《最蓝的眼睛》”。GPT-3 的回答表明,无论是针对年轻读者还是年长读者,它都不会推荐“《最蓝的眼睛》”。
查看 GPT-3 对其回答的解释提供了更多关于其回答方式的见解,但并没有明确说明。一般来说,GPT-3 似乎有些困惑,它提到《蓝眼睛》的描述可能“对 21 岁年轻人的课堂来说过于成熟和强烈”,同时也表示这本书“聚焦于一个 11 岁的孩子,可能对这个年龄段来说过于年轻”。
GPT-3: 这本书可能对 21 岁年轻人来说过于成熟和强烈。种族主义和暴力的主题可能对他们来说难以处理和理解。
GPT-3: 这本书可能涉及敏感话题,如种族主义和自尊,这些对 21 岁年轻人来说可能过于成熟。此外,故事可能对这个班级不感兴趣,因为它集中于一个 11 岁的孩子,这对这个年龄段来说可能过于年轻。
GPT-3: 这本书可能是一个有意义且感人的故事,但似乎不适合 21 岁年轻人的课堂。该书似乎更适合比这个年龄段更年轻的观众。
微软 Bing Chat 和 ChatGPT 在被禁书推荐方面的反应类似。
我问了微软 Bing Chat 和 ChatGPT(GPT-3 的两个变体)相同的问题,这些问题在前面的部分中已经由 GPT-3 回答。考虑到这些大型语言模型都是彼此的变体,两者的回答与 GPT-3 类似也就不足为奇了。输出结果有一些细微的差异——例如,Bing Chat 对禁令或不推荐书籍的理由比其他语言模型更具细致性。有趣的是,所有这些大型语言模型在不推荐某些书籍给特定年龄段的同时,并未推荐直接禁止书籍。
文章末尾提供了完整的输出。
注意: 我尚未有机会在新ChatGPT API上重复这些实验,该 API 在本文撰写后发布。
结论
美国有着长期的图书禁令历史,从清教徒对任何带有性感暗示的书籍的禁令,到内战后对种族主义和奴隶制的禁令。那些挑战“关于美国历史或社会规范的长期叙事”的书籍常常成为禁令的目标,包括华尔特·惠特曼的《草叶集》和哈丽雅特·比彻·斯托的《汤姆叔叔的小屋》等经典美国文学作品。
文章中的分析显示,目前在美国有各种各样的故事被禁:包括 LGBTQ+ 社区、穆斯林家庭、科学中的女性、种族主义、性别歧视,甚至是关于牛的图画书。孩子们成长过程中读的书籍(以及成人继续阅读的书籍)对他们的信念体系和对世界的理解有着巨大的影响。
重要的是要记住,“你会推荐一本书吗?”和“你会禁一本书吗?”之间存在差距。一个短语突出了不同年龄段的书籍,而另一个则完全不允许它们存在。这一差异在 GPT-3 的回应中也很明显:尽管它会或不会向不同年龄段推荐某些书籍,但它拒绝(在大多数情况下)推荐 禁 书籍,无论哪个年龄层。也许,这是一种我们可以采用的类似方法——为不同年龄段推荐书籍,而不是完全从书架上移除它们。

这是一个聚会吗?这是焚书吗?这是一个仪式吗?那些是书吗?它们在燃烧吗?那些人吗?他们在跳舞吗?他们在惊慌吗?他们在庆祝吗?使用 Stable Diffusion 生成
本文转载自我的博客原文: 🎨 art fish intelligence 🐡 … 如果你喜欢这篇文章,欢迎订阅我的博客以阅读更多类似的内容!
附录
关于 PEN 的被禁书单
PEN America 定义 书籍禁令为“基于书籍内容并因家长或社区挑战、行政决定或应立法者或其他政府官员直接或威胁性行动的结果而采取的任何措施,导致原本可用的书籍被完全移除,或书籍的访问被限制或减少。”
如他们在 网站 上提到的,学校书籍禁令索引并不是所有被禁书籍的全面清单,因为“有关书籍禁令的详细报告有时不可用”,而“书籍的安静移除或限制”并不总是可追踪的。这个列表旨在统计根据上述定义被禁的书籍数量。
在 《美国的禁书:日益增长的学校书籍审查运动》 中,PEN 的作者描述了数据集和书籍禁令,他们进行了基本分析,展示了每个州被禁书籍的数量以及禁令内容的主题(在本文中部分重复)。他们的主要发现之一如下(将在本文后续讨论):
“在 2021-22 学年,从最初在学校层面的活动挑战和移除书籍,发展成为由地方、州和国家团体推动的全面社会和政治运动。这些团体针对的绝大多数书籍包含 LGBTQ+角色或有色人种角色,和/或涉及美国历史中的种族与种族主义、LGBTQ+身份或性教育。”
事实上,PEN 的代表指出被选择禁止的书籍通常不是由于儿童或家长的个人投诉,而是由于对“哪些书籍有 LGBTQ 内容……即使只是一本有同性恋跨种族情侣插图的书”的 Google 搜索结果。
最受禁忌书籍的简短描述
-
《性别怪异》:“一本自传……包括青少年暗恋的羞辱与困惑,挣扎于如何向家庭和社会出柜,与朋友们通过色情同性恋同人小说建立联系,以及面对创伤和基础性侵犯的宫颈抹片检查”
-
《所有男孩都不是蓝色》:“从五岁时被恶霸踢掉牙齿的记忆,到与慈爱的祖母一起去跳蚤市场,再到他的第一次性关系,这本年轻成人的回忆录编织了黑人同性恋男孩所面临的考验和胜利”
-
《黑暗中的光》:“这部小说讲述了 1930 年代德克萨斯州新伦敦,一名墨西哥裔美国少女与一名非裔美国少年之间的爱情故事,一直到 1937 年的新伦敦学校爆炸事件。”
-
《最蓝的眼睛》:“一个 11 岁的黑人女孩在一个钟爱金发碧眼儿童的美国中祈祷她的眼睛能变成蓝色:这样她就会变得美丽,人们会注视她,她的世界会有所不同。”
-
《草坪男孩》:“一本半自传式的成长小说,讲述了一个年轻的墨西哥裔美国人自童年起经历困境,现在正经历自我发现的阶段。”
最受禁忌的五本小说、非小说、青少年和成人书籍
Top Banned Fiction Books
------------------------------
Gender Queer: A Memoir (Maia Kobabe)
Out of Darkness (Ashley Hope Pérez)
The Bluest Eye (Toni Morrison)
Lawn Boy (Jonathan Evison)
The Hate U Give (Angie Thomas)Top Banned Nonfiction Books
------------------------------
All Boys Aren't Blue (George M. Johnson)
Beyond Magenta: Transgender Teens Speak Out (Susan Kuklin)
This Book Is Gay (Juno Dawson)
Beloved (Toni Morrison)
Fun Home: A Family Tragicomic (Alison Bechdel)Top Banned Juvenile Books
------------------------------
Out of Darkness (Ashley Hope Pérez)
Me and Earl and the Dying Girl (Jesse Andrews)
Crank (Crank Series) (Ellen Hopkins)
This One Summer (Mariko Tamaki)
Drama: A Graphic Novel (Raina Telgemeier) Top Banned Adult Books
------------------------------
Gender Queer: A Memoir (Maia Kobabe)
The Bluest Eye (Toni Morrison)
Lawn Boy (Jonathan Evison)
The Kite Runner (Khaled Hosseini)
Beloved (Toni Morrison)
询问 ChatGPT 和 Bing Chat 是否会向不同年龄段推荐被禁止的书籍
我并不是在编造,当我说ChatGPT 和 Bing Chat**对每个问题的回应完全相同时。好吧,几乎如此。他们对我问题的“是/否”部分的回答完全一致。

ChatGPT 和 Bing 在是否会因不同年龄段对一本书的描述而禁止该书的回应(是或否)。图表由作者制作。
与 GPT-3 相比,ChatGPT/Bing Chat 更倾向于向 11-13 岁的青少年推荐书籍(相较于 13-15 岁的青少年)。然而,我认为更有趣的是查看每个聊天机器人对是否会向特定年龄段推荐书籍的解释。
我对 ChatGPT 和 Bing Chat 使用了相同的提示。我使用了《最蓝的眼睛》的描述来展示这个例子。
以下是一本书的描述。制作一个 Markdown 表格,一列用于 3、5、7、9、11、13、15、17、19、21 岁。写出你是否会推荐这本书(是)或不推荐(否),用于填满对应年龄的课堂。再写一列简短的理由说明为什么或为什么不推荐。描述:

ChatGPT 对于《最蓝的眼睛》一书的推荐输出
ChatGPT 的理由有些过于简单。它重复说“这本书的种族主义和自我厌恶的主题可能对多个年龄段来说过于成熟”,突然改变主意,认为适合 11 岁的孩子。
另一方面,Bing Chat 提供了非常深入的理由(实际上我对此感到相当惊讶!)。至少在这一点上,Bing Chat 似乎更能意识到用户请求的细微差别。然而,请对此持保留态度,因为我没有严格测试这个推测,这只是一个单一的观察。

Bing Chat 对于《最蓝的眼睛》一书的推荐输出
再次说明——这篇文章及其分析是在 ChatGPT API 发布之前进行的!未来使用 ChatGPT 进行的实验将很有趣,看看它们与此处分享的结果有何不同 😃
这篇文章转载自我博客上的原文: 🎨 art fish intelligence 🐡。请给予原博客一些关注和支持!
音乐发现的未来:搜索与生成
原文:
towardsdatascience.com/the-future-of-music-discovery-search-vs-generation-4d565a08515e
在 AI 时代的功能音乐
·发表于 Towards Data Science ·11 分钟阅读·2023 年 8 月 22 日
--

使用 DALL-E 2 创建。提示:“一个大脑和放大镜,背景是乐谱,数字艺术”
为什么这个问题很重要
大约十年前,音乐流媒体服务在争夺最佳音乐推荐系统方面竞争激烈。显然,一个完美的推荐系统会每次都为用户提供准确满足其需求的音乐作品。然而,有些人将推荐系统视为过渡技术。最终,无论你的音乐目录有多大,都无法为每个可能的用户请求提供完美的匹配。
现代生成型 AI 系统有可能通过生成针对每个请求量身定制的音乐来解决这个问题。然而,这些生成系统仍然无法产生高质量的音乐,具有巨大的计算成本,并且面临复杂的伦理和法律问题。
因此,本文旨在比较基于搜索的音乐检索和音乐生成的优缺点,以找出我们是否应该期望生成系统完全取代、增强或甚至不影响现有解决方案。在开始之前,让我们定义一下“搜索算法”和“生成模型”的含义。
搜索算法
搜索算法是解决搜索问题的一种方法。当用户希望从数据库中检索信息或对象(如视频或歌曲)时,就会存在搜索问题。我们称用户的请求为查询,搜索的结果为响应。搜索算法的目标是找到最能满足用户需求的信息,即为给定查询提供最佳响应。
然而,搜索问题也存在时间限制。大多数情况下,我们宁愿在 10 秒后得到第二好的回应,也不愿在 10 小时后得到绝对最佳的回应。因此,搜索算法应在合理的时间内找到一个在质量上令人满意的回应。
生成模型
生成模型是解决预测问题的一种方法。基于一组输入参数(query),模型生成对这些参数的最佳回应的预测。例如,当我们向图像生成模型如 DALL-E 2 提供“一个手绘的企鹅骑士”的提示时,这就是我们的查询。模型随后预测符合文本描述的图像中像素的样子。然而,查询不一定是文本提示。在音乐的上下文中,它也可以是和弦序列、MIDI 文件,甚至是我们希望为其生成合适音乐的视频片段。
就像搜索算法一样,生成模型的评估不仅基于回应的质量,还基于生成回应所需的时间。同样,如果完美生成的音乐需要 10 小时计算,我往往宁愿选择一个几乎完美的生成,仅需 10 秒钟。
比较搜索与生成
本文关注于功能性音乐,即作为手段使用的音乐,例如广告中的音乐。由于功能性音乐有明确的目标,因此分析生成模型与搜索算法如何服务于这一目标更为容易。绝对音乐,即纯粹为了欣赏而听的音乐,可能不会像功能性音乐那样受到技术进步的影响。
为了找出哪种范式更有可能主导未来的功能性音乐消费,我基于三个领域进行评估
-
质量:哪种方法能为更多用户提供更高质量的回应?
-
效率与成本:哪种方法在时间、金钱或能源等资源的利用上更为高效?
-
伦理考量:这些方法面临哪些伦理挑战?
质量

图片来源于Adrian Korte在Unsplash上的照片
只需一双健康的耳朵就能意识到,当前最先进的音乐生成模型如谷歌的MusicLM或 Meta 的MusicGen都无法生成与人类相媲美的音乐质量。如果你非常幸运,MusicGen 可以生成你可以用于低投入的 10 秒 TikTok 剪辑的回应。同时,Spotify 的搜索算法可以访问数千万的高质量录音。目前,生成模型在质量上无法与搜索算法相提并论。
然而,我已经争论了一段时间(如这里),这种情况即将发生,并且比我们预期的要快。当 OpenAI 在 2018 年发布 GPT-1 时,它能够生成通常语法正确但显然远离人类质量的文本。到 2023 年,像Christoph Molnar这样的作者使用 GPT-4 来校对他们的书,因为他们对其写作能力的信任超过了对自己能力的信任。我相信 MusicGen(2023 年 6 月)在响应质量方面介于 GPT-1(2018 年 6 月)和 GPT-2(2019 年 2 月)之间。在未来几年内,我预计生成音乐模型会产生在客观(或主观间接)质量标准上超越大多数人类音乐家的音乐。
我相信,人类在创造具有情感和文化意义的音乐方面将长期占据优势。然而,当涉及到功能性音乐时,我们主要寻找的是能够营造特定情绪或适应特定情况的音乐。正如 ChatGPT 可以用几乎任何你能想到的风格、语言和格式来写文本一样,生成音乐模型将能够生成完全满足任何标准的音乐。虽然搜索算法可以从给定数据库中检索出最符合查询的特定歌曲,但生成模型可能能够生成最优的响应。
关键要点
总结来说,搜索算法在响应质量方面目前远远领先于生成模型——这既指音乐本身的质量,也指响应与查询的匹配程度。然而,我预计生成 AI 在质量维度上将赶上搜索,并在接下来的几年中超越它。
效率与成本

Adrian Korte 在Unsplash上的照片
我们使用机器自动化任务,因为一旦我们弄清楚如何让它们可靠地执行任务,它们在这方面的速度会比人类快得多。至此,我们可以基本上关闭这一章了。嗯,差不多……当涉及到 AI 生成音乐的效率和成本与在数据库中寻找现有音乐的对比时,有一些话题值得讨论。
首先,AI 生成的音乐可能不像你想象的那样便宜。虽然计算过程本身将变得越来越便宜,但有效使用这些模型将需要熟练的 IT 专业人员来构建、维护和使用基础计算设施。或者,我们可以依赖第三方服务,这些服务也不是免费的。此外,关于 AI 生成音乐的版权和版税的争论远未*息。如果使用 AI 生成音乐需要每次生成或使用时支付版税,成本将迅速增加。
其次,人类创作的音乐并不像你想象的那样昂贵。显然,AI 可以在几分钟内生成 30 秒的音乐,而这需要专业音乐家花费一小时到一天的时间。然而,价格标签是基于供需关系,而不是任务所花费的时间。全球有数千万音乐家,他们以音乐作为爱好,并几乎免费提供给他人。当然,高质量的音乐或定制作品是昂贵的。然而,普通内容创作者可以在几分钟内找到免费的、适用于商业用途的人类创作音乐。
关键要点
总的来说,毫无疑问,AI 能够比人类更便宜、更快地生产音乐,并且这种差距在未来将会进一步扩大。另一方面,有大量熟练的音乐家为免费创作音乐,这已经是最便宜的了。AI 在速度上胜过人类,但不如人类愿意为个人或更高目标而免费工作。最终,关键可能在于 AI 生成音乐是否需要某种形式的版税。我们拭目以待。
伦理考量

图片由Cytonn Photography提供,来源于Unsplash
在当前 AI 热潮中,提醒自己人类仍然掌控一切(截至 2023 年 8 月)可能是个好主意。奴隶制有显著的经济优势(对奴隶主而言),然而,奴隶制在西方世界被消除了,这主要是由于伦理考量。即使 AI 生成的音乐在经济上比搜索人类创作的音乐目录更有前景,尤其是对功能性音乐而言,这并不意味着我们必须接受它。有几个反对 AI 生成音乐取代人类工艺的理由,我将简要提及并讨论。
工作损失与禁止
世界各地的许多音乐家通过将他们的音乐授权给媒体公司、内容创作者等使用的目录来谋生。一个担忧是,当我们从目录转向生成型人工智能时,这一收入来源将被消除,实际上是用机器取代了人类。虽然我确实分享这一担忧,但我不认为禁止生成型人工智能用于这些目的会是一个好的解决方案。
历史上,禁令通常效果不佳,除了像核武器或生物武器这样的极端风险技术。我认为,伦理上正确的做法是,在这种转变发生后,支持受影响的音乐家寻找新的收入机会。如果个别音乐家为这种情景做好准备,并得到相关组织的支持,他们将会根据干扰的强度,得到适当的安排或感到惊喜。
版权与知识产权
另一个担忧是,目前生成型人工智能模型是在人类创作的音乐上进行训练的,通常没有支付任何版权费给音乐家。虽然我对这一点的情感反应是负面的,但在伦理上,很难论证为什么这是错误的。毕竟,人类音乐家一生也会从受版权保护的音乐中获得灵感,并基于此创作自己的作品。这与人工智能的行为有什么不同呢?
我认为这个论点有两个不足之处。首先,规模很重要。作为人类,我们也会记住在生活中遇到的成千上万个人的个人信息,所有这些都是在没有签署数据保护协议的情况下完成的。然而,当谷歌以大规模方式进行这种操作时,我们会对此提出异议,因为谷歌可以利用这些数据获得人类一生都无法达到的权力水*。这与生成型人工智能类似。每个人都知道一些音乐。一个大型生成型人工智能模型可以知道所有音乐,这使其处于一个独特的权力位置,需要不同的伦理判断。
其次,听我最喜欢的乐队的音乐,并将他们的创意结合起来制作新音乐,并不会对任何乐队的经济成功构成风险。然而,你可以认为生成型人工智能系统实际上是基于人类音乐家的作品进行训练,以取代他们。在我看来,这需要对音乐家提供一定程度的保护,可能以版权费的形式。
关键点
在这篇文章中,我无法涵盖所有伦理方面的问题。然而,本质上,我想强调的是,未来的技术发展将会受到伦理考虑的显著影响,这些伦理考虑将转变为政治问题,最终成为法律。在过去,技术积极心态通常更为有效。然而,这并不意味着我们必须接受以技术进步为名的任何不道德行为。目前,人们仍在塑造人工智能。在我们对这些系统越来越依赖之前,让我们充分利用这一点。
讨论

图片来源:Jake Hills 版权归 Unsplash
尽管这个话题存在激烈争论,我坚信我们距离几乎任何音乐流派的类人生成 AI 只有几年的距离。当然,这些系统将有其局限性,但 ChatGPT 也是如此——而且看看它如何改变了我们写作的方式。然而,虽然我认为 AI 在某些时候会与高度技能的音乐家竞争,但我不相信 AI 会生成比专家音乐家“客观上更好”的音乐,从而使人类变得过时。
相反,我认为音乐家在功能性音乐方面面临着不同的问题。即便 AI 在音乐质量上无法客观地超越他们,它也可能因为 AI 能以相同的质量,但更便宜、更快速地制作定制音乐,而赢得大部分广告位置、同步交易等。这对全球数以万计的音乐家来说是一个重大职业风险,不容轻视。虽然这不是本文的重点,但我想明确的是,我认为 AI 不会摧毁音乐才华或音乐家的工作(例如,见这里 或 这里)。
在搜索算法与生成模型的问题上,两者的结合似乎是最实际、最可能的情景。即使生成模型能够制作出如此高度定制的音乐,以至于人类的音乐目录变得过时,搜索算法仍然会保持相关性。由于生成音乐 AI 在某个时候将变得非常便宜,因此没有理由不生成 10、100 或 1000 个不同的定制音乐作品,并使用某种搜索算法从中找到最佳的。生成 AI 可能会替代音乐目录,但不会替代搜索算法和检索最佳结果的算法。
伦理和法律方面仍然存在许多悬而未决的问题。例如,AI 生成的音乐是否会免于版权或某种版权机制并不明显。此外,更疯狂的事情可能发生。监管者可能决定,任何机器生成的内容(文本、图像、音频等)在展示给用户时都必须标明这是机器生成的。这可能导致媒体公司和内容创作者避免使用 AI 生成的音乐,以免带上相关的污名。在本文讨论的三个领域中,伦理和法律领域似乎是最不确定的。
结论
总之,生成模型有潜力颠覆我们发现音乐的方式。这对功能性音乐尤其如此,因为最终用户不在乎音乐是由人类还是机器创作的。另一方面,我们已经有数百万首人类创作的歌曲,这些歌曲几乎是免费的。这使得搜索算法即使在未来也成为生成模型的有力竞争者。伦理和法律问题使得一切变得更加不确定和复杂。最终,我怀疑生成模型和搜索算法在 AI 时代都会各自占有一席之地。
我在 Medium 上写了很多关于 AI 和音乐的文章。以下是一些你可能会喜欢的其他文章!
-
聊天机器人即将颠覆音乐搜索
2023 年现代数据栈的未来
涉及 4 个新兴趋势和 6 个去年的重大趋势
·
关注 发表在 Towards Data Science ·19 min read·2023 年 1 月 13 日
--
图片由 Nicholas Cappello 提供,刊登在 Unsplash
这篇文章是与 Christine Garcia(Atlan 的内容总监)共同撰写的。
在我的年度传统中,我在 2022 年底花了一些时间反思数据世界发生的事情。正如你从这篇文章的长度可以看出,我的合著者 Christine 和我有很多想法。
过去几年里,数据处于快速增长模式。社区不断掀起争议,讨论热门话题、见解、最新的技术、重要的 对话,并在推特上进行周五争斗。数据世界看似无限,大家只是拼命跟上它的爆炸式增长。
现在数据正进入一个不同的世界。98%的 CEO预计未来 12 至 18 个月内会出现经济衰退。公司们通过加大压力、裁员、削减预算,从增长模式转向效率模式,为即将到来的“战争”做准备。
那么这对数据世界意味着什么?更重要的是,对数据领导者和从业者意味着什么?本文将解析 2023 年的 10 个重要趋势,包括 4 个新兴趋势和 6 个有望进一步发展的现有趋势。

随着*期经济下滑,科技界对 2023 年的关注点转向了效率和成本削减。这将带来四个与现代数据堆栈公司和数据团队运营相关的新趋势。

存储一直是数据团队最大的一笔开支之一。例如,Netflix 每月在 AWS 数据存储上花费$960 万美元。随着公司预算的收紧,他们将需要认真审视这些账单。
Snowflake 和 Databricks 已经在产品优化方面进行了投资。他们可能会在今年引入更多改进,以帮助客户降低成本。
例如,在 6 月的会议上,Snowflake 强调了产品改进,以加速查询、减少计算时间和降低成本。它宣布了 AWS 上的计算速度*均提升 10%、对写重负载的 DML 工作负载性能提升 10-40%、以及通过更好的压缩实现 7-10%的存储成本降低。
在 6 月的会议上,Databricks 还将部分 主题演讲 专注于节省成本的产品改进,例如 Enzyme(ETL 管道的自动优化器)和 Photon(一个查询引擎,性能价格比提高了多达 12 倍)。
在今年晚些时候,Snowflake 和 Databricks 进一步投资 成本优化 功能,预计明年还会有更多。这其中,Snowflake 甚至将成本削减突出为其 2023 年数据趋势的重点,并确认致力于在提升性能的同时减少成本。
在 2023 年,我们还将看到独立公司和存储合作伙伴推出更多工具,以进一步降低数据成本。
暗数据,即那些从未真正使用的数据,是数据团队面临的一个严重问题。尽管公司仍在支付存储费用,但高达 68% 的数据 未被使用。
今年还将出现如 Bluesky、CloudZero 和 Slingshot 等成本管理工具,这些工具旨在与特定的数据存储系统如 Snowflake 和 Databricks 配合使用。
现代数据堆栈的合作伙伴也可能会推出兼容的优化功能,如 dbt 的 增量模型 和 包。dbt Labs 和 Snowflake 甚至共同撰写了一篇关于使用 dbt 和 Snowflake 优化数据的 白皮书。
元数据在这里也发挥着重要作用。 借助现代化的元数据*台,数据团队可以使用流行度指标来找到未使用的数据资产,利用列级血统查看资产是否与管道断开,使用冗余功能删除重复数据等等。许多功能甚至可以通过 主动元数据 自动化,例如自动优化数据处理或清除过时的数据资产。
例如,我们合作的一个数据团队通过找到并删除一个未使用的 BigQuery 表,减少了$50,000的每月存储成本。另一个团队通过找到未被上游使用的表、视图和模式,淘汰了30,000 个未使用的资产(占其数据资产的三分之二)。

在过去几年中,数据团队能够在较少的监管和监督下自由运作。
公司对数据的力量和价值充满信心,以至于数据团队并不总是需要证明这种价值。 相反,他们继续前行,在日常数据工作与前瞻性的技术、流程和文化实验之间取得*衡。优化数据人员的工作方式一直是数据讨论的一部分,但它常常被搁置在更紧迫的问题,比如构建一个超级酷的技术堆栈。
明年,这种情况将不再适用。随着预算的紧缩,数据团队及其技术堆栈将受到更多的关注和审查。他们的成本是多少?他们提供了多少价值?数据团队需要关注性能和效率。
在 2023 年,公司将更加严肃地测量数据投资回报率(ROI),数据团队的指标将开始变得主流。
衡量像数据这样基础功能的投资回报率并不容易,但现在比以往任何时候都更重要的是我们要弄清楚这一点。
今年,数据团队将开始开发代理指标来衡量他们的价值。这可能包括使用指标,如数据使用量(例如 DAU、WAU、MAU 和 QUA)、页面浏览量或在数据资产上的花费时间,以及数据产品的采用;满意度指标,如数据消费者的 d-NPS 评分;以及信任指标,如数据停机时间和数据质量评分。

*年来,现代数据技术堆栈一直在增长。并且不断增长。再增长。
随着风险投资公司投入数百万美元的新资金,新工具和类别每天不断出现。但现在,随着经济下滑,这一增长阶段已结束。风险投资资金已经开始枯竭——只需查看过去六个月的融资公告减少情况。
我们将看到明年推出的数据公司和工具减少,现有公司的扩展速度也会放缓。 从长远来看,这对买家和现代数据堆栈整体而言可能是件好事。
是的,快速增长模式有趣且令人兴奋,但它也充满混乱。我们曾经开玩笑说,现在成为数据买家会很糟糕,因为每个人都声称能做一切。结果是一些真正疯狂的技术堆栈图。
这种资本的缺乏将迫使当今的数据公司专注于重要的事情,忽略其他方面。这意味着会有更少的“锦上添花”功能。更少的华丽转型。更少的收购让我们感到“他们为什么要这样做?”
在资金有限的情况下,公司将不得不专注于自身的优势,并与其他公司合作处理其他事务,而不是试图在一个*台上解决所有数据问题。这将导致 2023 年“最佳现代数据栈”的形成。
随着混乱*息,数据公司专注于其核心 USP,各类别的赢家将开始显现。
这些工具还将专注于相互更好地协作。 他们将作为启动合作伙伴,依托共同标准,推动现代数据栈的发展。(去年的几个例子是 Fivetran 的Metadata API和 dbt 的Semantic Layer,我们等紧密合作伙伴提前建立了集成,并与 Fivetran 和 dbt Labs 一起庆祝了这一推出。)
这些合作伙伴关系和整合将使买家更容易选择工具并迅速开始,这与以往的情况相比是一个受欢迎的变化。

科技公司在 2023 年面临削减成本和增加收入的新压力。解决这一问题的一种方法是专注于他们的核心功能,如上所述。另一种方法是寻找新客户。
你猜今天最大的未开发数据客户来源是什么?拥有传统本地数据系统的企业公司。为了服务这些新客户,现代数据栈公司将不得不开始支持传统工具。
在 2023 年,现代数据栈将开始与 Oracle 和 SAP 这两家企业数据巨头集成。
这听起来可能有些争议,但它已经开始了。现代数据栈在一年多前就开始进入本地企业数据领域。
在 2021 年 10 月,Fivetran 收购了 HVR,一款企业数据复制工具。Fivetran 表示,这将使其能够“解决与 ERP 系统、Oracle 数据库等相关的操作数据现代化的巨大市场”。这是现代数据栈公司首次进入企业市场的重大举措。

这些是去年在数据领域爆炸性的六大创意,并且 2023 年只会变得更大。

这是去年文章中的一个大趋势,因此它在数据领域仍然是热门话题也就不足为奇了。然而,令人惊讶的是,主动元数据和第三代数据目录的理念增长如此迅速。
与 2021 年的重大变化相比,当时这些理念刚刚出现,几乎没有人谈论它们,现在许多公司正在竞争以声称这一类别。
元数据被视为数据领域中的一个大缺口,因此即使风险投资资金开始枯竭,去年在目录领域还是出现了一些大规模的融资。这些包括 Alation 的 \(123M Series E](https://www.crn.com/news/software/data-intelligence-tech-provider-alation-raises-123m-in-latest-funding-round)、Data.world 的 [\)、Data.world 的 \(50M Series C](https://techcrunch.com/2022/04/05/data-world-raises-50m-to-help-enterprises-organize-and-track-their-data/)、我们的 [\)、我们的 \(50M Series B](https://www.businesswire.com/news/home/20220309005103/en/Atlan-Raises-50M-Series-B-at-450M-valuation-to-Build-a-Collaboration-Hub-for-Data-Teams) 和 Castor 的 [\) 和 Castor 的 $23.5M Series A。
另一个促成这一增长的原因是分析师,他们在 2022 年拥抱并推广了主动元数据和现代数据目录的理念。例如,Gartner 在其年度大会上全面投入主动元数据,而 G2 发布了新的“主动元数据”类别。

我们的预测是,主动元数据*台将在 2023 年取代“数据目录”类别。
这实际上始于去年,当时 Forrester 将其关于“机器学习数据目录”的 Wave 报告重命名,并改变了排名。它将 2021 年的领导者(Alation、IBM 和 Collibra)移到了 2022 年 Wave 报告的底部和中层,并用一组新公司(我们、Data.world 和 Informatica)取而代之。
“数据目录”只是元数据的一个单一用例:帮助用户理解他们的数据资产。但这仅仅触及了元数据能做的皮毛。
激活元数据是许多用例的关键,如可观察性、成本管理、修复、质量、安全、程序化治理、优化管道等——这些都已在数据领域积极讨论。以下是一些实际例子:
-
Eventbridge 基于事件的操作:允许数据团队创建生产级、基于事件的元数据自动化,例如,当所有权发生变化时的警报或自动标记分类。
-
Trident AI:利用 GPT-3 的强大功能,自动为新的数据资产创建描述和 README 文件,基于早期资产的元数据。
-
GitHub 集成:在每个 GitHub 拉取请求期间,自动创建受影响数据资产的列表。


这一切始于八月,Chad Sanderson 在他的新闻通讯中谈到了“数据合同的兴起”。后来,他与 Adrian Kreuziger 一起发布了一份关于数据合同的双部分技术指南。接着,他在分析工程播客中谈到了数据合同——和我们一起!(向 Chad、Tristan Handy 和 Julia Schottenstein 致敬,感谢精彩的讨论。)
数据合同的核心驱动因素是工程师没有动力来创建高质量的数据。
由于现代数据堆栈的存在,数据的创建者与使用者被分开了。因此,公司最终会拥有 GIGO 数据系统——垃圾进,垃圾出。
数据合同旨在通过在数据生产者和消费者之间创建协议来解决这个问题。数据生产者承诺生产符合某些规则的数据——例如,设置数据模式、准确性或完整性方面的服务水*协议,以及数据使用和更改的政策。在达成协议后,数据消费者可以使用这些数据创建下游应用程序,确保工程师不会意外更改数据并破坏实时数据资产。
在 Chad Sanderson 的新闻通讯发布后,这场讨论迅速引爆。它在 Twitter 和 Substack 上传播,数据社区争论数据合同是否是重要的讨论、令人沮丧的模糊还是不言而喻的、实际上不是技术问题、注定失败或显然是个好主意。人们举办了Twitter 战斗、创建了史诗般的讨论串,并在安全的距离上观看了真人秀,手里拿着爆米花。

虽然数据合同本身是一个重要问题,但它们是确保数据质量更大讨论的一部分。
数据经常过时、不完整或不正确并不是什么秘密——数据社区已经讨论了多年的解决方案。最初,人们认为元数据文档是解决方案,然后是数据产品交付标准。现在流行的词汇是数据合同。
这并不是要忽视数据合同,它们可能是我们一直在等待的解决方案。但似乎更有可能的是,数据合同将被纳入更大的数据治理趋势中。
在 2023 年,数据治理将开始向“左移”,数据标准将成为编排工具中的一等公民。
数十年来,数据治理一直被视为次要问题。通常由数据管理员处理,而不是数据生产者,数据生产者在数据创建很久之后才会创建文档。
然而,我们最*看到了一种将数据治理“左移”或更接*数据生产者的趋势。这意味着无论是谁创建数据(通常是开发人员或工程师),都必须在数据上线之前创建文档并检查数据是否符合预定义标准。
主要工具最*做出了支持这一想法的变化,我们预计在未来一年会看到更多的变化:
-
Airflow 的Open Lineage,它跟踪 DAG 执行时关于作业和数据集的元数据。
-
Fivetran 的元数据 API提供了 Fivetran 连接器同步的数据的元数据。
-
Atlan 的GitHub 扩展,它创建了一个下游资产的列表,这些资产将受到拉取请求的影响。

也称为“指标层”或“业务层”,语义层是一个在数据世界中漂浮了几十年的概念。
语义层是一个字面意义上的术语——它是数据架构中的“层”,使用业务用户能够理解的“语义”(词汇)。数据团队构建语义层,并将列名如“A000_CUST_ID_PROD”重命名为“Customer”。语义层隐藏了复杂的代码,使业务用户看不到,同时保持良好的文档记录,并对数据团队可访问。
在 2022 年 10 月,dbt Labs 在其 年度大会 上大肚子宣布了新的语义层。
dbt 的语义层 的核心概念是:定义一次,随处使用。数据生产者现在可以在 dbt 中定义指标,然后数据使用者可以在下游工具中查询这些一致的指标。无论他们使用哪种 BI 工具,分析师和业务用户都可以在会议中查找统计数据,确信他们的答案是正确的。
将指标作为数据转换的一部分是直观上有意义的。将它们纳入 dbt——这一主流转换工具,已与现代数据堆栈良好集成——正是语义层从理念到现实所需的。

自 dbt 的语义层推出以来,进展相对缓慢——部分原因是这发生在不到三个月前,部分原因是改变人们编写指标的方式需要时间。
2023 年,第一批语义层实现将上线。
许多数据团队在过去几个月里探索了这项新技术的影响——试验语义层并思考如何改变他们的指标框架。
随着现代数据堆栈中更多工具与语义层集成,这一过程变得越来越简单。在语义层推出时,有七个工具已准备好支持语义层(包括我们、Hex、Mode 和 Thoughtspot)。另外八个工具已准备好支持指标层,这是与语义层集成的中间步骤。

2022 年,一些逆向 ETL 的主要参与者(去年的大趋势之一)寻求将他们的类别重新定义为“数据激活”,这是对“客户数据*台”的一种新解释。
CDP 将来自所有客户接触点(如网站、电子邮件、社交媒体、帮助中心等)的数据进行整合。公司可以基于这些数据进行细分或分析,构建客户档案,并驱动个性化营销。例如,如果有人放弃了购物车,他们可以创建一封带有折扣码的自动邮件,或者向访问特定页面并使用公司在线聊天的人投放广告。
CDP 的设计围绕着使用数据,而不仅仅是聚合和存储数据。这就是数据激活发挥作用的地方——“激活”数据以处理 CDP 功能。
各种形式的数据激活已经存在了几年。然而,数据激活作为新型 CDP 的概念在 2022 年开始流行。
例如,Arpit Choudhury 在四月分析了这个领域,Sarah Krasnik 在七月解析了辩论,Priyanka Somrah 在八月将其纳入了一个 数据类别,而 Luke Lin 在上个月的 2023 年数据预测中提到了数据激活。
部分原因是由前逆向 ETL 公司推动的趋势造成的,这些公司现在将自己重新品牌为数据激活产品。例如,Hightouch 在四月通过一系列活动重新品牌,五天内发布了三篇关于数据激活的博客:
-
数据激活:分析后的下一步 作者:Pedram Navid
-
Hightouch:数据激活*台 作者:Kashish Gupta
-
什么是数据激活? 作者:Luke Kline
部分原因也可以追溯到围绕推动数据用例和价值的更大辩论,而不是关注数据基础设施或堆栈。正如 Benn Stancil 所说,“为什么数据技术的发展远远超过了数据团队所提供的价值?”
部分原因也是现代数据堆栈的必然结果。像 Snowflake + Hightouch 这样的堆栈具有与 CDP 相同的数据和功能,但可以跨公司使用,而不仅仅是针对一个功能。

过去 CDP 是有意义的。 当建立数据*台困难时,为业务用户提供一个开箱即用、完全定制的客户数据*台是一项重大胜利。
但现在,世界已经改变,公司可以在不到 30 分钟的时间内建立一个数据*台——一个不仅包含客户数据,还包括所有其他重要公司数据(例如财务、产品/用户、合作伙伴等)的*台。
与此同时,数据工作已围绕现代数据堆栈不断整合。Salesforce 曾尝试处理自己的分析(称为 Einstein Analytics)。现在它与 Snowflake 合作,Salesforce 数据可以像其他数据源一样被传送到 Snowflake。
对大多数 SaaS 产品来说也是如此。虽然内部分析曾经是他们的追加销售点,但他们现在意识到,将数据移入现有的现代数据生态系统更为合理。相反,他们的追加销售现在是通过 API 将数据同步到仓库。
在这个新世界中,数据激活变得非常强大。现代数据仓库加上数据激活将不仅取代 CDP,还将取代所有预构建的、专门的 SaaS 数据*台。
在现代数据架构中,数据现在在专门的 SaaS 产品中生成,并被传输到像 Snowflake 这样的存储系统,在那里它与其他数据结合并在 API 层进行转换。数据激活对于将洞察反馈到源 SaaS 系统中,以便业务用户进行日常工作,变得至关重要。
例如,Snowflake收购了 Streamlit,这使得人们能够在 Snowflake 之上创建预构建的模板。与其开发自己的分析工具或依赖 CDP 工具,像 Salesforce 这样的工具现在可以让其客户将数据同步到 Snowflake,并使用预构建的 Salesforce 应用程序一键分析数据或进行自定义操作(如使用 Clearbit 清理潜在客户列表)。其结果是 CDP 的定制化和用户友好性,与现代云计算的强大功能相结合。

这一思想源于 Zhamak Dehghani——首先是 2019 年的两篇 博客,然后是她在 2022 年出版的O’Reilly 书籍。
这里是数据网格学习社区的 TL;DR:“最简短的总结:将数据视为产品,而非副产品。通过推动数据产品思维并将领域驱动设计应用于数据,你可以从数据中释放出显著的价值。数据需要由最了解它的人来拥有。”
数据网格在 2021 年无处不在。到 2022 年,它开始从抽象的思想变为现实。
数据网格的讨论从“它是什么?”转变为“我们如何实施它?”随着真实用户故事的增长以及数据网格背后的思想变得不再抽象而更具可操作性。
与此同时,许多公司开始围绕数据网格进行品牌化。到目前为止,我们已经看到Starburst、Databricks、Oracle、Google Cloud、Dremio、Confluent、Denodo、Soda、lakeFS和K2 View,等等。

四年过去了,我们仍处于数据网格的早期阶段。
2023 年,第一波数据网格“实施”将上线,届时“数据即产品”的概念将成为焦点。
今年,我们将开始看到越来越多真实的数据网格架构——不仅仅是那些在数据博客中漂浮多年的理想化图示,而是来自实际公司的真实架构。
数据领域也将开始趋于最佳的参考架构和数据网格实施策略。这将包括以下核心组件:
-
数据质量和测试(例如,Great Expectations,Monte Carlo)
-
类似 Git 的过程,使数据生产者可以纳入测试、元数据管理、文档等(例如 dbt)
-
所有这些都围绕着相同的中央数据仓库/湖泊层(例如 Snowflake,Databricks)

去年我们的一个大趋势,数据可观察性保持稳定并继续与数据质量和可靠性等相关概念一起增长。
现有公司变得更加庞大(例如,Databand被 IBM 收购于 2022 年 7 月,Monte Carlo 获得 1.35 亿美元Series B融资),新公司进入主流(例如,Kensu和Monte Carlo跃升至思想领导层),每月都有新工具发布(例如,Bigeye 推出的Metadata Metrics)。
在一个显著的变化中,这一领域在 2022 年也见证了开源的显著增长。 Datafold 推出了一个开源 diff 工具,Acceldata开源了其数据*台和数据可观察性库,而 Soda 则推出了开源的Soda Core和企业版Soda Cloud*台。

我们在去年报告中提出的一个开放问题是数据可观察性的发展方向——是成为一个独立类别,还是与数据可靠性或主动元数据等其他类别合并。
数据可观察性和质量将会融合到一个以确保高质量数据为中心的更大“数据可靠性”类别中。
这可能看起来是一个大的变化,但许多在这些类别中的公司多年来已经多次更改名称,例如Datafold从数据差异*台转变为“数据可靠性*台”。
随着这些公司争夺定义和拥有这一类别,我们将继续在短期内看到更多的混乱。然而,已经有早期迹象显示,这将很快开始*静下来,形成一个类别。

作为数据从业者,迎接 2023 年感觉很有趣。虽然空气中悬浮着很多不确定性(不确定性是新的确定性!),我们也感到稍微松了一口气。
2021 年和 2022 年是数据堆栈历史上异常的年份。
炒作非常疯狂,每天都有新工具发布,数据人员不断被数据初创公司挖走,风险投资者向每一个有意建立某种东西的数据从业者抛出资金。“现代数据堆栈”终于变得很酷,数据世界也获得了所需的所有资金、支持和认可。
在 Atlan,我们最初也是一个数据团队。作为在数据领域待了十多年的人员,这段时间非常疯狂。进展通常是按十年计算的,而不是按年计算的。但在过去三年中,现代数据堆栈的成长和成熟程度已与前十年相当。
这令人兴奋……但我们最终多次问自己存在性问题。这个现代数据堆栈的概念是真的吗,还是仅仅是由风险投资资金推动的炒作?我们是否生活在一个回音室里?在这一切中数据从业者在哪里?
虽然这种炒作和狂热带来了很棒的工具,但最终对数据世界并不好。
面对一片炒作词汇和产品的海洋,数据买家常常感到困惑,可能花更多时间去寻找合适的堆栈,而不是实际使用它。
让我们明确——数据领域的最终目标是帮助公司利用数据。工具对此很重要,但它们最终只是一个促进因素,而不是目标。
随着这种炒作开始*息,现代数据堆栈开始稳定下来,我们有机会将我们在工具上的进展转化为实际的商业价值。
我们已经到了一个数据团队不再争取建立正确基础设施的阶段。借助现代数据栈,建立数据生态系统比以往任何时候都更快更容易。相反,数据团队现在正在争取证明自己的价值,并在更少的时间和资源中获得更多的成果。
现在公司无法随意花钱,他们的决策需要有针对性和数据驱动。这意味着数据比以往任何时候都重要,而数据团队处于一个独特的位置,能够提供真正的商业价值。但要实现这一点,数据团队需要最终搞清楚这个“价值”问题。
既然我们已经掌握了现代数据栈,现在是时候弄清楚现代数据文化栈了。一个出色的数据团队是什么样的?它应该如何与业务合作?它如何在最短的时间内产生最大的影响?
这些问题很棘手,没有快速解决方案。但如果数据领域能够破解更好的数据文化的秘密,我们最终可以创建梦想中的数据团队——这些团队不仅能帮助公司在接下来的 12-18 个月中生存下来,还能在未来几十年中推动公司达到新的高度。
准备好接受这些趋势的尖锐见解和专家意见了吗?我们为 2023 年的第一次“大数据辩论”汇集了一组超级明星(Bob Muglia、Barr Moses、Benn Stancil、Douglas Laney 和 Tristan Handy)。 点击这里观看录制视频。
开源 AI 的黄金时代即将结束
你不希望在你使用的模型的开源许可证中看到的缩写如 NC、SA、GPL 等
·
关注 发表在 Towards Data Science ·7 分钟阅读·2023 年 6 月 7 日
--
图片由作者提供(修改自 来源)
开源 AI 库和模型的(有偏见的)历史
我在 2015 年加入了 Google Brain 团队,正值 TensorFlow 开源。与普遍认知相反,TensorFlow 在当时并不是 Google 成功的秘密武器。只有少数研究人员使用了它,直到几年后,它才在实质上改变了 Alphabet 最重要的资产。
然而,TensorFlow 对开源社区的影响几乎是立竿见影的。它开启了一个由社区驱动的创新时代,这直接促进了过去几年 AI 进展的飞速发展。公*地说,TensorFlow 并不是第一个开源深度学习库(例如,Caffe 于 2014 年发布),但它是第一个得到像 Google 这样的公司支持的信誉(以及开发者宣传预算)的库。
但 TensorFlow 只是一个库。关键是,你仍然需要提供自己的数据来实际训练预测模型。为了预测未来的房价,你需要一个包含历史房价的数据集,并使用 TensorFlow 来训练一个模型。最终得到的模型现在编码了你数据的综合知识。在 TensorFlow 开源几年后,Google 采取了另一个决定性的步骤,加速了向“全民免费”AI 的路径。2018 年 开源 BERT 模型 的决定帮助引发了大型语言模型的雪崩。没过多久,在 2019 年,OpenAI(当时仍是非营利组织)开源了他们的 GPT2 模型。就这样,开源训练模型成为了一种趋势。
从开源一个像 TensorFlow 这样的库到一个像 BERT 这样的全训练模型的升级不容小觑。TensorFlow 仅仅是一组指令,而 BERT 是将这些指令应用于大量数据的代价高昂的训练过程的结果。用一个强有力的类比来说:如果 TensorFlow 是一本关于人类繁殖的生物学教科书,那么 BERT 就是一个大学毕业生。有人阅读了这本教科书,应用了它的指令,并花费了大量时间和金钱来培养一个现在已经完全教育成才、准备进入职场(或研究生院)的成人。
“如果 TensorFlow 是一本关于人类繁殖的生物学教科书,那么 BERT 就是一个大学毕业生。”
开源决策
我们是如何到达现在的局面的?我将那个时期许多开源决策归因于研究科学家的日益显著和相对谈判权力。研究人员更倾向于从事能够在权威期刊上发表论文的工作(如NeurIPS),这些出版物若附带开源代码则更具相关性(和可信度)。这也是像苹果这样更为神秘的公司难以吸引人才的原因之一。允许公开讨论工作成果的研究人员在业界广受认可,他们的市场价值与他们在顶级期刊上的发表直接相关。由于顶尖的 AI 研究人员稀缺,他们的年薪包超过$1M。
对这些开源决策并未进行很多经济上的审视,更不用说更重要的许可问题(稍后将详细讨论)。谷歌放弃 BERT 的知识产权价值是多少?谷歌的竞争对手将如何利用这些库和模型对付它们?值得庆幸的是,开源基础设施项目及其企业维护者提供了一个有用的案例研究,展示了公司如何逐渐认识到开源许可决策的影响。
开源的海洋变革
在 TensorFlow 崛起的同时,预示着开源 AI 未来的到来,企业软件经历了一场开源许可危机。主要是由于多亏了 AWS,它掌握了将开源基础设施项目转化为商业服务的技巧,许多开源项目将其许可协议从“允许”改为“Copyleft”或“ShareAlike”(SA)替代品。
并非所有开源项目都一样。允许性许可(如 Apache 2.0 或 MIT)允许任何人将开源项目转化为商业服务。与之相对的“Copyleft”许可(如 GPL),类似于创意共享的“ShareAlike”条款,是一种保护措施。如果 AWS 基于一个具有“Copyleft”许可的开源项目推出服务,那么 AWS 服务本身必须在相同许可下开源。
因此,部分是对竞争云服务的回应,开源项目的公司创作者和维护者,如MongoDB和Redis,改变了它们的许可证,转向了更不可容忍的选择。这导致了AWS与这些公司之间围绕开源原则和价值的痛苦但有趣的争论,这种争论后来有所缓和。
请注意,这种许可证变更对开源生态系统产生了欺骗性的影响:虽然仍有许多新的开源项目被宣布,但关于这些项目可以和不可以做的许可证影响比大多数人意识到的要复杂得多。
在开源 AI 中的转变潮流
此时,你应该自问:如果开源基础设施项目的企业维护者意识到其他人正在收获更多的商业利益,而不是他们自己,那么同样的情况是否会发生在人工智能领域?对于持有它们创造的计算和数据的总体价值的开源 AI 模型来说,这不是更大的问题吗?答案是:是的,是的。
尽管围绕开源人工智能似乎存在一种罗宾汉式的运动,但数据显示情况正朝不同的方向发展。微软等大型企业正在将一些最受欢迎的模型的许可证从可容许转变为非商业(NC)许可证,Meta 开始在所有最新的开源项目(如MMS、ImageBind和DINOv2,都采用 CC-BY-NC 4.0 许可证,以及LLAMA采用 GPL 3.0)。甚至像斯坦福的 Alpaca这样的大学项目,也仅限于非商业使用(继承自其使用数据集的不可容忍属性)。整个公司改变其业务模式,以保护其知识产权并摆脱开源作为其使命的一部分的义务 — 还记得一个名叫 OpenAI 的小型非营利组织转变为有限盈利的事件吗?请注意,GPT2 是开源的,但 GPT3.5 或 GPT4 却没有?
更普遍地说,虽然不透明,但 AI 领域向更严格许可的趋势是显而易见的。下面是对Hugging Face模型许可的分析。从 2022 年中期开始,允许性许可(如 Apache、MIT 或 BSD)的比例持续下降,而不可许可的许可(如 GPL)或限制性许可(如 OpenRAIL)则变得越来越普遍。

来源:作者分析
更糟糕的是,最*对大型语言模型(LLMs)的狂热进一步混淆了情况。Hugging Face 维护了一个“开放 LLM 排行榜”旨在突出“开源社区所取得的真正进展”。公*地说,排行榜上的所有模型确实都是开源的。然而,仔细观察会发现几乎没有一个是商业用途许可的*。

来源:作者分析
**在这篇文章写作和发布之间,Falcon 模型 的许可已经更改为允许的 Apache 2.0 许可。总体观察仍然有效*。
如果说有什么值得注意的,那就是开放 LLM 排行榜突出显示了大型科技公司(LLaMA 由 Meta 以非商业许可开源)的创新主导了所有其他开源努力。更大的问题是这些衍生模型在其许可方面不够透明。几乎没有明确声明其许可的模型,你必须自己研究才能发现这些模型及其所基于的数据不允许商业使用。
开源 AI 的未来
社区中有很多虚伪的标榜,主要来自善意的企业家和风险投资者,他们希望有一个不被 OpenAI、谷歌和少数其他公司主导的未来。为什么 AI 模型应该开源并不明显——它们代表了公司多年来开发的辛苦知识产权,涉及数十亿的计算、数据获取和人才投入。如果公司随便将所有东西免费赠送给别人,那就等于欺骗了他们的股东。
“如果我能投资一个知识产权律师 ETF,我会的。”
开源 AI 中向不可许可许可的趋势似乎很明确。然而,新闻的压倒性数量未能指出这些工作的累积好处几乎完全归属于学术界和爱好者。投资者和高管应更加意识到这些影响并加倍小心。我强烈感觉到大多数新兴 LLM 棉花行业的初创公司都是在非商业许可的技术基础上建立的。如果我能投资一个知识产权律师 ETF,我会的。
我的预测是,AI 的价值捕获(特别是对于最新一代的大型生成模型)将类似于其他需要大量资本投资和专业人才积累的创新,如云计算*台或操作系统。一些主要的玩家将会出现,为整个生态系统提供 AI 基础。虽然在这个基础上还有充足的创业公司层,但就像没有开源项目能取代 AWS 一样,我认为开源社区很难产生对 OpenAI 的 GPT 及其后续版本的严肃竞争者。
本文中表达的观点仅代表我个人,不能代表我的雇主的观点。
Clemens Mewald 领导着 Instabase 的产品团队。之前,他在 Databricks 上参与了开源 AI 项目,如 MLflow,在 Google 参与了 TensorFlow。
Pd.Get_Dummies 的利与弊
原文:
towardsdatascience.com/the-good-the-bad-and-the-ugly-of-pd-get-dummies-75c87e2aadc9
这是针对 pd.get_dummies 的死忠粉
·发布于 Towards Data Science ·5 分钟阅读·2023 年 7 月 26 日
--
大家好 🤠
好的,我明白了。在 Python 中,将分类数据转换为虚拟变量数组最简单的方法之一是使用 Pandas 的 pd.get_dummies()。为什么还要花时间导入 OneHotEncoder 从 sklearn,执行 .fit_transform() 等等?真是繁琐!
这篇文章首先介绍一个简单的数据集,用于演示目的,其中测试集包含在训练集中未发现的分类数据。然后,演示如何使用 pd.get_dummies() 可能会导致演示数据出现问题。最后,展示如何使用 sklearn 的 OneHotEncoder 避免这个问题。

图片来源:作者在 Canva 上使用文本到图像的插图。提示:“三只穿着乡村西部牛仔服装的熊猫。”
演示用的简单数据集
这里我们有一个简单的数据集,其中包括一个名为 OS 的分类特征。OS 列列出了计算机操作系统。我们将使用这些虚拟数据进行演示。在 train_df 中包含虚拟演示训练数据,而在 test_df 中包含虚拟演示测试数据。
在我们的虚拟演示案例中,测试集包含在训练集中不存在的分类值。这种不匹配会导致问题。
import pandas as pd
train_df = pd.DataFrame({'OS': ['Windows', 'MacOS',
'Linux', 'Windows', 'MacOS']})
test_df = pd.DataFrame({'OS': ['Windows', 'MacOS',
'Android', 'Unix' 'iOS']})
在我们的训练数据中,我们有三种操作系统:Windows、MacOS 和 Linux。但在我们的测试数据中,还包括 Android、Unix 和 iOS 等额外类别。
使用 pd.get_dummies(train_df) 拟合的模型将无法与 pd.get_dummies(test_df) 的测试数据一起使用。结果将不匹配——因为类别不匹配。

图片来源:作者在 Canva 上使用 Canva 库图像创建的插图。艺术用品虚拟模型。
Pd.get_dummies() 的问题
当将pd.get_dummies()函数应用于我们的训练和测试数据集时,你会得到这样的结果。
train_encoded = pd.get_dummies(train_df, columns=['OS'])
test_encoded = pd.get_dummies(test_df, columns=['OS'])
如果你评估train_encoded和test_encoded DataFrames,你会注意到它们的列数不同。这是因为测试集中仅存在的'Android'类别在test_encoded中创建了一个额外的列。这种训练和测试数据集特征数量的不一致可能在构建和评估机器学习模型时造成重大问题,因为这些模型期望两个数据集中的特征空间相同。

图片来源:作者的屏幕截图,使用此处显示的代码生成。
使用 OneHotEncoder 的解决方案
现在,让我们看看 sklearn 的OneHotEncoder如何解决这个问题。当使用OneHotEncoder时,编码器在训练数据的.fit()或.fit_transform()方法期间学习类别。然后,在.transform()方法期间,它为所有学习到的类别创建列。如果在测试数据中发现了新类别,则有两个选项。首先,你可以忽略新的未见类别。
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(
sparse_output=False,
handle_unknown='ignore').set_output(transform="pandas")
train_encoded = encoder.fit_transform(train_df[['OS']])
test_encoded = encoder.transform(test_df[['OS']])
注意,我们设置了handle_unknown='ignore'以告诉编码器忽略(而不是抛出错误)在测试集中看到的但在训练集中未见的任何类别。
还要注意,自 2023 年起,sklearn 提供了.set_output(transform="pandas")选项,这将确保编码器返回一个 Pandas DataFrame(具有更易读和解释的列名),而不是更简洁的 NumPy 数组。
通过使用handle_unknown='ignore'处理未知类别,OneHotEncoder确保训练数据和测试数据具有相同的特征空间,无论测试数据中出现了什么类别值。
OneHotEncoder 的附加好处
Sklearn 的OneHotEncoder不仅在处理测试数据中未见类别时非常有用,它还提供了其他一些附加功能,这些功能也可能很有用。以下是其中两个附加选项及其使用方法。
min_frequency
min_frequency是另一个强大的选项,它允许你指定一个最低频率阈值。频率低于此阈值的类别会被重新编码为“罕见”。这在你有一些只出现几次的类别时非常有用,这些类别可能没有足够的信息来帮助模型,可能会使模型复杂化,增加无用的特征,从而降低性能而没有任何预测价值。
这是如何设置min_frequency为 2 的:
encoder = OneHotEncoder(
sparse_output=False,
handle_unknown='infrequent_if_exist').set_output(transform="pandas")
train_encoded = encoder.fit_transform(train_df[['OS']])
test_encoded = encoder.transform(test_df[['OS']])
max_categories
另一个有用的选项是max_categories。max_categories参数允许你限制虚拟数组中的输出列数。编码器将选择最常见的类别进行编码。
这个选项在处理具有许多类别的特征时特别有益,这些特征通常会导致维度数目很高。这可能会显著降低训练效率,同时增加模型复杂性,并且几乎没有提高预测能力的价值。简而言之,这减少了数据的维度。
这是限制编码到前 4 个最频繁类别的方法:
encoder = OneHotEncoder(
sparse_output=False,
handle_unknown='ignore',
max_categories=4).set_output(transform="pandas")
train_encoded = encoder.fit_transform(train_df[['OS']])
test_encoded = encoder.transform(test_df[['OS']])
结论
从更宏观的角度来看,使用pd.get_dummies()而不是真正的拟合和转换过程,在数据科学领域还有更严重的错误。然而,如果你一直坚持使用pd.get_dummies(),现在你已经了解了它的优点、缺点和不足——阅读完这篇文章后,你应该考虑调整你的做法。
虽然pd.get_dummies()在 Python 中创建虚拟变量是快速且简便的,但当你的测试数据包含在训练数据中未见过的类别时,可能会导致问题。另一方面,sklearn 的OneHotEncoder优雅地处理了这种情况,确保了数据集间一致的特征空间。值得多写几行代码,以避免潜在的陷阱。
感谢阅读
你准备好了解更多数据科学领域的职业了吗?我提供一对一的职业指导,并有每周的电子邮件列表,帮助数据专业的求职者。联系我了解更多。
感谢阅读。请把你的想法和建议发给我。你可以只是说声你好。如果你真的需要告诉我哪里做错了,我期待很快与你聊聊。Twitter: @adamrossnelson LinkedIn: Adam Ross Nelson。
梯度下降算法及其背后的直觉
对梯度下降方法的技术描述,并配有算法运行的图示
·
关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 8 月 19 日
--
亚瑟·叔本华的名言:“一旦你越过山丘,你会开始加速”。照片由作者拍摄。
- 引入一些关键定义
在优化方法中,尤其是在一阶算法类型中,您肯定听说过一种被称为梯度下降的方法。它是一阶优化类型,因为它需要一阶导数,即梯度。通过优化,梯度下降旨在最小化“实际”输出和模型预测输出之间的差异,这种差异由目标函数(即成本函数)测量。梯度或斜率被定义为在该函数(弯曲或直线)在给定点处绘制的线的方向。迭代地,梯度下降旨在在不同点对成本函数进行微分,从而推导出这些点上方向变化的程度,并朝向最陡下降的方向,即局部最小值。正如其名字所示,梯度 用作寻找局部最小值的下降方向,在那里优化的成本函数的参数值被最小化,从而达到最低值。
梯度下降主要用于(包括但不限于)训练机器学习模型和深度学习模型,后者基于神经网络结构类型。从线性回归和逻辑回归到神经网络,梯度下降旨在计算函数的最佳参数值。在其最简单的形式中,梯度下降旨在通过推导独立变量的参数的最佳值来最小化下面线性回归的误差项。这是,
y = β0 + β1 * X1 + … βk * Xk + Ɛ
其中,
y 是因变量
k 是独立变量的数量
X 是独立变量
β 参数
Ɛ 是误差项成分
在更复杂的形式中,梯度下降最常被定义为训练深度学习模型时的优化器,特别是在编译阶段。深度学习基于一个互连的网络进行不断的学习和改进,这个网络被称为神经网络。神经网络受到人脑的启发,是由人工神经元(称为节点)组成的高度复杂的网络。在最上层,节点在处理和分析来自前一层节点的数据,并将其传递到下一层节点方面扮演着重要角色。在神经网络中,权重,即优化的参数,是节点之间的联系。它们连接了输入/特征和隐藏层,因此它们表示了特定特征在预测最终输出时的重要性。找到单个权重的最佳值取决于许多权重的值。而这种优化是同时进行的,在深度神经网络中,即使是数百万个权重也可能会大大增加。在这里,梯度下降在涉及大量计算时表现得非常高效,这些计算基于神经网络的三个主要层:1)输入层 2)隐藏层 和 3)输出层。
有大量文献详细阐述和扩展了诸如深度学习方法及估计函数参数值的方法,进而扩展了梯度下降和普通最小二乘法(OLS)之间的差异,例如在线性回归的情况下。由于这不是本文的重点,读者可以进一步调查和扩展,以便更好地理解这些方法论。
2. 计算时间!
为了更好地理解梯度下降,我们需要扩展对可微分函数的定义。一个函数,明确地说是 ƒ(x),当其导数在该函数的曲线上的任意点都可以定义时,该函数就是可微分的。这就是说,对于函数 ƒ(x) 的定义域中的 所有 点。这里,有两个概念支持这个定义:一阶导数和二阶导数。一阶导数公式定义如下:

严格来说,函数的一阶导数,记作 ƒ’(x) 或 df(x)/dx,是函数 ƒ(x) 在给定 x 点的斜率。如果斜率为正(负),则表明函数在增加(减少),以及增加(减少)的幅度。正斜率表示随着 x 值的增加,函数 ƒ(x) 也增加。相反,负斜率表示随着 x 值的增加,ƒ(x) 减少。二阶导数是函数 ƒ(x) 的导数的导数。记作 ƒ’’(x) 或 d2f(x)/dx2,二阶导数指示了函数 ƒ(x) 的形状,即该函数是凹的还是凸的。从数学上讲(这一点很重要!!!),二阶导数可以区分相对最大值和相对最小值。
其中,
如果 ƒ’’(x) > 0,则 ƒ(x) 在 x = a 处是凸的。
如果 ƒ’(a) = 0,则 a 是一个临界点,因此是相对最小值。

图由作者绘制
其中,
如果 ƒ’’(x) < 0,则 ƒ(x) 在 x = a 处是凹的。
如果 ƒ’(a) = 0,则 a 是一个临界点,因此是相对最大值。

图由作者绘制
如果二阶导数等于零,则可能有两种情况:1) 函数 ƒ(x) 处于转折点,即拐点,在该点函数从凹变为凸,或反之;2) 在该点函数未定义(即,不连续)。对于前者:
如果 ƒ’’(x) = 0,则 ƒ(x) 在 x = 2 处是拐点。

图由作者绘制
上述内容集中于一个具有单一自变量的函数,即一元函数 y = ƒ(x)。在现实世界中,人们会研究和建模多变量函数,其中研究的变量受到多个因素的影响,这就是两个或更多自变量 y = ƒ(x, z)。为了测量自变量 x 对因变量 y 的影响,并保持 z 不变,需要计算函数关于 x 的偏导数。因此,偏导数计算了因每个输入的变化引起的成本函数的变化率。梯度下降迭代地计算这些成本函数的变化,并在每一步更新这些函数参数的值,直到达到使参数值优化、从而最小化成本函数的最小点。
3. 梯度下降算法的实际应用
斜率的绝对值越大,我们可以迈出的步伐就越大,或者我们可以继续朝着最陡下降的方向迈步,即局部最小值。随着我们接*最低/最小点,斜率减小,因此可以迈出更小的步伐,直到到达斜率为零(0)的*坦表面,即ƒ’(x) = 0,这是下图中红色箭头指向的βi 的最低值。这就是曲线的局部最小值,函数参数的最优值也在此处得出。

作者绘制的图表
因此,如果一个函数是严格凸的(凹的),则只有一个临界点。现在,也有可能存在多个局部最小值的情况。在这种情况下,搜索的是函数能够达到的最低值,这被称为全局最小值。

作者绘制的图表
以下两个关键问题出现:
-
应该朝哪个方向迈步?
-
步长应该有多大?
让我们回顾一下到目前为止的内容。梯度下降是一种算法,在模型的训练阶段,通过在每一步向最陡下降的方向(即局部最小值)前进时,对函数的每个输入取其偏导数,从而迭代地调整和优化函数参数的值。如果导数为正,函数值在增加。因此,应该朝相反方向迈步。梯度指示了应该迈步的方向。如果梯度很大,即斜率的绝对值很大,则应该朝着局部最小值迈较大的步伐。实际上,梯度下降在每次迭代中朝着局部最小值方向采取越来越小的步伐,如上图中的蓝色箭头所示。
步长的大小与学习率有关。这决定了算法学习/移动到最陡下降方向的速度。在最高梯度下,即斜率的绝对值最大时,算法学习最快。当接*局部最小值时,步长会变小。因此,学习率作为超参数在尝试不同值后设置,以便成本函数在迭代中减少。如果学习率过大,可能会错过局部最小值。学习率过小可能导致权重更新较小,使模型没有显著改善。如果学习率过小,可能需要时间才能收敛。收敛是指成本函数不再减少。因此,成本函数是算法性能的指标。在多变量函数的世界中,这表示为:

其中,
df/dβ 表示成本函数对参数 β 的偏导数。
m 数据点数量。
yi 是第 i 个数据点的实际依赖/目标变量值。
ŷi 是模型预测的第 i 个数据点的依赖/目标变量值。
xi 表示与数据点相关的第 i 个输入。

其中,
▽f 表示函数 f(x) 对参数 β 的梯度向量。
df/dβk 表示函数 f(x) 对第 k 个参数 β 的偏导数。

其中,
新的 β 表示第 i 个参数 β 的当前值。
旧的 β 表示第 i 个参数 β 的更新值。
n 是 学习率:采取步长的长度!
▽f 是指向函数 f(x) 在参数 β 变化方向上的最陡下降方向的梯度向量,以最小化 f(x)。
4. 梯度下降的局限性
梯度下降的一个局限性与上述提到的标准之一有关,即函数必须在其定义域的每一点都可微分。当情况不是这样,算法找到一个未定义的点(即不连续的点)时,算法会失败。
另一个局限性与步长的大小有关,即学习率 (n),即朝着最陡下降方向采取的步伐。如果步长过大,可能会错过局部最小值,甚至可能无法收敛。如果步长过小,则需要更长的时间才能收敛。如果输入数量很大,这种情况会更加严重。
最终,梯度下降可能永远无法找到全局最小值。算法无法区分局部最小值和全局最小值。当算法在寻找局部最小值时,一旦收敛就会停止。局部最小值会把算法困在局部最小值所在的谷底,阻止步伐足够大以便退出。
5. 结论
总结来说,梯度下降是:
-
一种迭代的、一阶优化算法类型。
-
在每次迭代中,对可微函数的参数进行更新,并最小化成本函数。
-
因此,收敛于局部最小值。
基于梯度下降法的局限性,有动机探索不同的、更先进的梯度下降方法,甚至其他类型的优化算法,如二阶方法。然而,这超出了本文的范围,因此我会把它留作我下一篇文章的主题 😊
感谢阅读!
请添加任何你认为能增强该主题知识的评论!
图着色问题:精确解和启发式解
通过自定义构造启发式方法和整数规划在 Python 中探索经典的离散优化问题
·
关注 发布于 Towards Data Science ·10 分钟阅读·2023 年 11 月 13 日
--
32 节点实例的图着色启发式解。(图片由作者提供)。
图着色理论在离散数学中占据核心地位。它出现在许多看似与着色无关的地方。它处理的是将一组对象根据某些规则划分为类别的基本问题(Jensen & Toft, 1995)。
我们可以将问题总结如下:给定一个无向图G(V, E),为每个节点(顶点)分配颜色,使得相邻节点不共享相同颜色,并且使用的颜色数量最小化。尽管问题陈述简洁明了,但这个问题以其计算复杂性而臭名昭著,属于 NP 难类问题。
由于其组合复杂性,即使使用最佳可用求解器,整数线性规划(ILP)的精确方法可能无法解决大型实例。在这种情况下,启发式算法和元启发式算法可以是有趣的替代方法。虽然无法证明最优性,但这些方法可以提供快速和高质量的解决方案。
在本文中,我们将使用构造启发式算法DSatur(Brélaz, 1979)和整数线性规划模型使用pyomo(Bynum 等,2021),使用 HiGHS 求解器解决图着色问题。至于其他故事,你可以在我的code repository中找到完整的代码。
如果您还不熟悉线性规划,我建议您在继续之前阅读我以前的文章以获得基础知识。
Python 中的线性优化的主要概念和实现
towardsdatascience.com
现在让我们动手创建像这样的令人惊叹的解决方案。

32 个节点实例的图着色启发式解决方案。(作者动画)
构造启发式算法 — DSatur
让我们回顾一下我们的问题定义:
-
考虑一个无向图G(V, E)。
-
为每个节点分配颜色,使得相邻节点不共享相同的颜色。
-
最小化使用的颜色数量。
一个朴素的构造性解决方案可以是依次选择一个空节点,并为其分配一个颜色,确保其邻居中尚未使用该颜色。这可能不是可能的最佳策略,但肯定可以为所需颜色数量提供一个上界。然而,DSatur(Brélaz, 1979)算法包括选择下一个节点和改进该启发式的新元素。
-
度数:连接到给定节点的边的数量。
-
饱和度:在邻居中使用的不同颜色数量。
DSatur的算法从一个未着色节点的队列开始,迭代选择一个饱和度度数最大的节点,将其从队列中移除,并为其分配颜色。如果饱和度相等,Brélaz(1979)建议选择任何一个饱和度最大的节点。我们将采用稍微不同的方法,通过选择具有最大整体度数的节点来打破*局。分配给给定节点的颜色应为可用的最低索引颜色。下面给出了伪代码。设N为节点集合,Q为未着色节点队列,引用N本身的相同内存位置,C为已使用颜色集合。
dsatur(N)
Q = [&n for n in N]
C = {}
while |Q| > 0:
sort(Q) // descending order by saturation and degree
n = Q.pop(0)
assign_color(n, C)
函数assign_color应该验证最低索引可用的颜色,如果当前集合中的颜色不可行,则包含一个新的备选颜色并增加集合。
让我们把这部分代码写成 Python 代码。首先是包的导入。我们的启发式算法将用纯 Python 编写,只需导入一些类型提示。
from typing import List, Tuple
现在,让我们定义我们的基本建模元素:Colors和Nodes。类Color定义为一个可变的占位符,用于相应实例的索引。在 Python 中,这可能是内存高效的,因为多个变量可以引用相同的内存位置。每次用给定颜色给新节点上色时,都应该调用add_node方法。
class Color:
index: int
n_nodes: int
def __init__(self, index) -> None:
self.index = index
self.n_nodes = 0
def __repr__(self):
return f"C{self.index}"
def add_node(self):
self.n_nodes = self.n_nodes + 1
现在谈谈Node类。每个Node实例都有一个属性neighbors,这是一个节点(指针)的列表。因此,每次修改节点时,我们无需更改其邻居的任何信息。我们可以使用add_neighbor方法添加一个邻居,使用set_color方法设置其颜色,并访问依赖于其邻居当前状态的属性neighbor_colors、saturation和degree。
class Node:
neighbors: List['Node']
index: int
color: Color
def __init__(self, index):
self.index = index
self.neighbors = []
self.color = None
def __repr__(self) -> str:
return f"N{self.index}|{self.color}"
def add_neighbor(self, node: 'Node'):
if node not in self.neighbors:
self.neighbors.append(node)
def set_color(self, color: Color):
self.color = color
color.add_node()
@property
def neighbor_colors(self):
return [n.color for n in self.neighbors if n.color is not None]
@property
def saturation(self):
return len(set((n.color for n in self.neighbors if n.color is not None)))
@property
def degree(self):
return len(self.neighbors)
现在进入DSatur算法。为了实现它,将创建一个同名的类。新创建的DSatur实例需要图的节点数n_nodes和edges作为参数。然后,它会实例化节点并根据边设置它们的邻居。
class DSatur:
N: List[Node]
C: List[Color]
history: List[Node]
def __init__(self, nodes: List[int], edges: List[Tuple[int, int]]):
N = [Node(i) for i in nodes]
for e in edges:
i, j = e
N[i].add_neighbor(N[j])
N[j].add_neighbor(N[i])
self.N = N
self.C = []
self.history = []
def find_next_color(self, node: Node) -> Color:
next_color = None
for c in self.C:
if c not in node.neighbor_colors:
next_color = c
break
if next_color is None:
next_color = Color(len(self.C) + 1)
self.C.append(next_color)
return next_color
def solve(self, save_history=False):
Q = [n for n in self.N] # Pool of uncolored nodes
while len(Q) > 0:
Q.sort(key=lambda x: (x.saturation, x.degree), reverse=True)
n: Node = Q.pop(0)
next_color = self.find_next_color(n)
n.set_color(next_color)
if save_history:
self.history.append(n)
self.C.sort(key=lambda x: x.n_nodes, reverse=True)
@property
def cost(self):
return len(self.C)
现在,让我们使用它来解决一些具有挑战性的问题!你可以在OR Library中找到几个实例。在那里,它们被标记为“gcolX”。我们可以使用以下代码从这些文件中的一个提取节点和边的数量。
# Open and read file
with open($YOUR_FILE_PATH, mode="r") as file:
lines = file.readlines()
header = lines[0].strip().split()
n_nodes = int(header[2])
edges = []
node_set = set()
for line in lines[1:]:
if line.startswith('e'):
_, i, j = line.strip().split()
edges.append((int(i), int(j)))
node_set.add(int(i))
node_set.add(int(j))
nodes = sorted(node_set)
assert len(nodes) == n_nodes, "Wrong number of nodes specified"
然后,我们将这些颜色解析到一个新的DSatur实例中,解决问题(或者至少得到一个高质量的*似解)。
dsatur = DSatur(nodes, edges)
dsatur.solve()
创建介绍中结果的有趣可视化的代码可能过于冗长,不便在这里包含,但你可以在我的代码库中找到。它可以传达处理多个节点时难度的总体概念…

图着色启发式解决方案针对 100 个节点实例。(作者动画)。
现在让我们看看如何通过精确方法处理它。
整数线性规划
为了优化我们使用启发式方法得到的解决方案,并尝试证明解决方案的最优性,让我们将图着色问题表述为一个整数线性规划模型。请记住,尽管如此,它可能无法处理大型实例。本节介绍的模型以及其他确切算法均在 Lewis(2021)的第三章中介绍。
让我们定义在这种方法中考虑的Sets:
-
C:颜色
-
N:节点(或顶点)
-
E:边
第一个问题已经出现:“我们应该考虑多少种颜色?”在最坏的情况下,所有节点都连接在一起,因此应该考虑与N相同大小的C。然而,这种方法可能通过不必要增加决策变量的数量并恶化模型的线性放松,使我们的解决方案变得更加困难。一个好的替代方法是使用启发式方法(如DSatur)来获取颜色数量的上限。
在这个问题中,我们有两组决策变量:
-
x_{i, c}:是二进制变量,指示节点i使用颜色c着色。
-
y_{c}:是二进制变量,指示使用颜色c。
我们还必须创建约束条件以确保:
-
每个节点必须着色。
-
如果任何一条边上的节点有颜色,请确保使用该颜色。
-
每条边上最多只能用一个颜色着色。
-
打破对称(这不是必须的,但可能有帮助)
最终,我们的目标是最小化使用的总颜色数,这是y的总和。总结我们有以下方程。

图着色整数规划模型。(作者提供的图片)。
毫不拖延,让我们为整数编程模型导入pyomo。
import pyomo.environ as pyo
pyomo中建模问题有两种方法:抽象和具体模型。在第一种方法中,问题的代数表达式在提供某些数据值之前定义,而在第二种方法中,模型实例在其元素定义后立即创建。您可以在库文档或 Bynum 等人(2021)的书中了解更多关于这些方法的信息。在本文中,我们将采用具体模型的形式。
model = pyo.ConcreteModel()
接下来,让我们实例化我们的Sets。直接从dsatur属性N和C中解析可迭代对象是有效的,因此可以在initialize关键字参数中使用它们。或者,我将从输入数据中传递原始nodes和edges,并从DSatur创建一个范围作为我们颜色的初始化。
model.C = pyo.Set(initialize=range(len(dsatur.C))) # Colors
model.N = pyo.Set(initialize=nodes) # Nodes
model.E = pyo.Set(initialize=edges]) # Edges
接下来,我们实例化我们的决策变量。
model.x = pyo.Var(model.N, model.C, within=pyo.Binary)
model.y = pyo.Var(model.C, within=pyo.Binary)
然后是我们的约束条件。
# Fill every node with some color
def fill_cstr(model, i):
return sum(model.x[i, :]) == 1
# Do not repeat colors on edges and indicator that color is used
def edge_cstr(model, i, j, c):
return model.x[i, c] + model.x[j, c] <= model.y[c]
# Break symmetry by setting a preference order (not required)
def break_symmetry(model, c):
if model.C.first() == c:
return 0 <= model.y[c]
else:
c_prev = model.C.prev(c)
return model.y[c] <= model.y[c_prev]
model.fill_cstr = pyo.Constraint(model.N, rule=fill_cstr)
model.edge_cstr = pyo.Constraint(model.E, model.C, rule=edge_cstr)
model.break_symmetry = pyo.Constraint(model.C, rule=break_symmetry)
你可以尝试包括其他对称破缺约束,并查看哪个在你的可用求解器中效果更好。在我进行的一些实验中,包括使用总节点数的优先级顺序的效果不如忽略它。这可能是由于求解器的本地对称破缺策略。
# Break symmetry by setting a preference order with total assignments
def break_symmetry_agg(model, c):
if model.C.first() == c:
return 0 <= sum(model.x[:, c])
else:
c_prev = model.C.prev(c)
return sum(model.x[:, c]) <= sum(model.x[:, c_prev])
最终,目标…
# Total number of colors used
def obj(model):
return sum(model.y[:])
model.obj = pyo.Objective(rule=obj)
我们的模型已经准备好解决了!为此,我使用了 HiGHS 持久求解器,该求解器在pyomo中可用,前提是你的 Python 环境中还安装了highspy。
solver = pyo.SolverFactory("appsi_highs")
solver.highs_options["time_limit"] = 120
res = solver.solve(model)
print(res)
对于大型实例,我们的求解器可能很难改善启发式解。然而,对于在代码仓库中提供的 32 节点实例,求解器能够将使用的颜色数量从 9 减少到 8。值得一提的是,完成执行花费了 24 秒,而对于相同实例的DSatur算法仅花费了 6 毫秒(使用纯 Python,这是一个解释型语言)。

32 节点实例的图着色整数规划解。(图片由作者提供)。
进一步阅读
尽管DSatur是一种直观的启发式方法,能提供快速的高质量解,但其他方法可能会带来更好的结果,尤其是在复杂实例上。图着色问题中最著名的元启发式算法之一是Tabucol(Hertz & Werra, 1987)。作者提出了一种从初始解开始的方法,初始解具有k种颜色,并可能存在连接相同颜色节点的边。然后,执行局部移动,改变给定节点的颜色,以最小化冲突,使用禁忌表逃避局部最优。
比本文中所示的图着色问题的更高效的精确方法依赖于Column Generation,使用一种称为Branch & Price的算法。对这个主题感兴趣的读者可以在我的其他 Medium 故事中找到简洁的概述。
如何使用 Python 示例解决具有大量决策变量的线性问题
towardsdatascience.com
结论
在这篇文章中,介绍了两种解决图着色问题的方法:构造性启发式DSatur(Brélaz, 1979)和整数线性规划(ILP)模型。启发式方法能够为中等规模的实例提供快速的高质量解,之后的解通过 ILP 模型进一步改进。实现的代码可在公共仓库中进一步使用。
参考文献
Brélaz, D., 1979. 图的顶点着色的新方法。ACM 通讯,22(4),251–256。
Bynum, M. L. et al., 2021. Pyomo-优化建模在 Python 中。Springer。
Hertz, A., & Werra, D. D., 1987. 使用禁忌搜索技术进行图着色。计算,39(4),345–351。
Jensen, T. R., & Toft, B., 1995. 图着色问题。John Wiley & Sons。
Lewis, R.M.R., 2021. 图着色的高级技术。在:图着色指南。计算机科学文本。Springer,Cham。doi.org/10.1007/978-3-030-81054-2_4
推荐系统指标指南
原文:
towardsdatascience.com/the-guide-to-recommender-metrics-c5d72193ea2b
离线评估推荐系统可能是棘手的
·发表于 Towards Data Science ·阅读时间 10 分钟·2023 年 11 月 12 日
--

图片由 Darius Cotoi 提供,来源于 Unsplash
想象一下 YouTube 主页面,它展示你可能喜欢的视频,或者 Amazon 推荐你购买更多他们销售的产品。这些都是推荐系统的例子,它们试图展示你最可能想要互动的内容。
假设你也用你选择的方法构建了一个推荐系统。问题是:如何离线评估它,在投入生产之前并让它在网站上提供推荐服务?
在这篇文章中,你将确切学到这些!此外,我还会告诉你为什么你需要小心这些指标。
如需更详细的介绍,请参阅我的另一篇文章,该文章还展示了如何使用 TensorFlow 从头构建推荐系统。
学习如何在 TensorFlow 中构建一个简单的矩阵分解推荐系统
towardsdatascience.com
推荐系统的离线评估
首先,让我们为推荐系统找到一个定义,以涵盖你可能设计或在实际中找到的大多数系统。
对我们而言,推荐系统是一个算法,它至少以用户作为输入,并输出一个有序的推荐项列表给该用户。

图片由作者提供。
为什么至少要有一个用户?可能还有更多的输入,比如季节时间,这可以帮助模型学习不要在夏天推荐巧克力圣诞老人。
例如,我们构建的水果推荐器R可以做诸如 R(Alice) = [苹果、橙子、樱桃] 这样的事情。
注意: 用户可能是其他的东西,甚至可能是另一篇文章。如果你想为商店里缺货的文章构建一个推荐器,这可能是相关的。所以,你的推荐器将一篇文章作为输入并输出其替代品。不过,我们还是坚持经典的用户-物品推荐。
接下来是你可以用来评估推荐系统质量的离线推荐指标列表。我将向你展示它们的定义以及使用时需要注意的细节。
训练-测试分割
我们将假设已经进行了一些形式的训练-测试分割,以获得有意义的指标。否则,我们将仅衡量推荐系统的过拟合能力。假设我们有如下数据:

作者提供的图像。
阅读: 用户A购买/观看/收听了物品X(例如电影或歌曲)。我将在接下来的文章中使用“购买”。
有许多分割选项,你应根据应用场景选择其一。
-
随机分割: 你将所有行随机分割为训练集和测试集。
-
时间分割: 你选择一个阈值日期,将所有在此日期之前的条目放入训练集中,其余的放入测试集中。
-
…
通常,当我有日期列时,我会选择时间分割,因为它完全符合模型的预期用途:我们在过去的数据上训练一个模型,并希望它在未来表现良好。不过,思考一下这是否适合你的情况。
好吧,在定义了一些分割之后,让我们继续进行指标的部分!
回归指标
在最佳情况下,你不仅会有类似于用户A购买了物品X的交易数据,还可能会有某种明确的反馈,例如用户评分(1–5 星)。在这种情况下,你不仅可以让你的模型输出一个物品列表,还可以输出它们的预测评分。然后,你可以选择任何回归指标,比如均方误差或*均绝对误差来衡量预测评分与实际评分之间的差距。这没有什么新意,因此我们就此打住。
从隐含反馈中获得的推荐系统对齐指标
从现在开始,我们将假设没有任何明确的反馈,例如星级评分,只有隐含反馈,比如“用户A购买了物品X”。有关隐含反馈的更多信息,请查看我的另一篇文章的介绍部分:
## 使用 TensorFlow 推荐系统进行隐式反馈的推荐系统
当客户没有明确告诉你他们想要什么时
[towardsdatascience.com
准备工作
我们将讨论的所有指标都需要两个要素:
-
用户在测试集中购买的项目列表
-
对于同一用户,在训练模型后,推荐列表(这是预测结果!)是基于训练集的。
以下指标用于衡量这两个列表的对齐程度。让我们详细看看。
假设我们现在只有一个用户Alice:
-
在测试集中,Alice 购买了以下项目:🍍🍎🍉🍌🍒
-
推荐系统输出如下:🍌🍐🍒🍈🍇(按照这个顺序)
我们将 Alice 在测试集中获得推荐并实际购买的项目称为相关。在我们的例子中是:🍌 和 🍒。那些被推荐但 Alice 没有购买的项目是不相关的,即 🍐、🍈 和 🍇。
如果我们有多个用户,我们只需计算每个用户的以下指标,并对所有用户取*均值。此外,我们将定义许多带有 @k 后缀的指标。这仅意味着我们 将推荐列表截断为 k 项 ,然后计算指标。
Precision@k
用文字表述:
在前 (前面) 的 k 个推荐项目中,有多少 % 对 Alice 是相关的?
让我们计算 precision@3。实际上,Alice 购买了 🍍🍎🍉🍌🍒,但推荐系统建议了🍌🍐🍒 作为前三个项目。这三个项目中有多少是相关的?好吧,只有 🍌 和 🍒 出现在测试集中,因此precision@3 = 2/3 ≈ 66.7%。
提醒:我们的推荐系统向 Alice 推荐了 🍌🍐🍒🍈🍇。
你可以用这种方式计算所有其他 k 的 precision@k:precision@1 = 1/1,precision@2 = 1/2,precision@4 = 2/4,precision@5 = 2/5。一般公式为:

作者提供的图片。
这里,relᵢ 是 0 或 1,具体取决于推荐列表中第 i 位的项目是否相关 (1) 或不相关 (0)。
从现在开始,我们只处理 relᵢ,因为这样我们不需要记住哪些水果是相关的,哪些不是。因此,我们将🍌🍐🍒🍈🍇 转换为 [1, 0, 1, 0, 0] ,因为🍌 和 🍒 是相关的。
这个指标很直接,因为你只需要计算多少项目是相关的。不过,这个指标有一些变体。例如,如果我们的推荐系统显示了 5 个项目,但 Alice 在测试集中只购买了 3 个。然后推荐系统无法获得 100% 的分数,最大值将是 3/5 = 60%。如果你觉得这不公*,你可以通过不将总和除以 k,而是 除以 k 或 Alice 在测试集中购买的总项目数(取较小者)来调整指标。

total = Alice 在测试集中购买的总项目数。图像由作者提供。
precision@k 的缺点:推荐的顺序被忽略。对于相关性评分为 [1, 0, 1, 0, 0] 的推荐,precision@5 与 [0, 0, 0, 1, 1] 是一样的,尽管将相关项目排在列表前面更为理想。你还希望你的相关搜索引擎结果排在第一位,而不是第七位。
Recall@k
这个问题类似于 precision@k。
在所有相关的项目中,有多少 % 被推荐给了 Alice?
让我们计算 recall@2:Alice 总共购买了 5 个项目 🍍🍎🍉🍌🍒,在前两个位置上得到了推荐 🍌🍐。* 只有单一的项目 🍌 被我们的推荐系统覆盖,所以 recall@2 = 1/5。
你还可以计算:recall@1 = 1/5,recall@3 = 2/5,recall@4 = 2/5,recall@5 = 2/5。一般来说:

total = Alice 在测试集中购买的总项目数。图像由作者提供。
再次,你可以争论这有时是不公*的。即使是完美的推荐系统也无法覆盖所有的 总共 项目,如果 k < 总数。例如,如果前两个项目是相关的,则 recall@2 的最大值为 2/5。你也可以调整它,结果发现你会得到与调整后的 precision@k 相同的公式。

图像由作者提供。
recall@k 的缺点:与之前相同。推荐的顺序被忽略。
NDCG@k
这个问题有点复杂,因为它包括两个部分:DCG 和 IDCG。但在我们开始之前,先提供一个信息:这个指标关注推荐的顺序!所以,让我们看看它是如何工作的:
DCG:这是折扣累计增益的缩写。它的公式是

图像由作者提供。
在这里,你可以看到为什么推荐顺序很重要——每个相关性项 relᵢ 都因为在列表中靠后而受到惩罚。rel₁ 被除以 log₂(2) = 1,rel₂ 被除以 log₂(3) ≈ 1.585,rel₃ 被除以 log₂(4) = 2,依此类推。
因此,相关项目最好位于列表的顶部,那里的 折扣(惩罚)项仍然较低。
这个指标本身的问题在于它是无界的。在我们的例子[1, 0, 1, 0, 0]中,DCG@3 是 1/log₂(2) + 0/log₂(3) + 1/log₂(4) = 1 + 0.5 = 1.5。这就是为什么我们现在将通过检查一个完美推荐系统的表现来计算DCG 的上限。
IDCG: 这是理想折扣累计增益的缩写。我们输出的相关性模式为[1, 0, 1, 0, 0]的推荐系统并不完美。一个真正的完美推荐系统会将 Alice 在测试集中购买的所有项目放在列表的最上面,即相关性模式将是[1, 1, 1, 1, 1],因为 Alice 购买了 5 件商品。
备注: 如果 Alice 在测试集中只购买了 2 件商品,一个完美的推荐系统将输出[1, 1, 0, 0, 0],因为没有更多相关的项目可以填充推荐列表。
因此,DCG 不能高于所有 5 个推荐项目都相关的情况。我们可以计算 IDCG@3 = 1/log₂(2) + 1/log₂(3) + 1/log₂(4) ≈ 2.131。

作者提供的图片。
NDCG: 最后,我们可以定义标准化折扣累计增益 NDCG@k为

因此,在我们的案例中,NDCG@3 = DCG@3 / IDCG@3 ≈ 1.5 / 2.131 ≈ 0.704。
我们可以测试一下如果我们的推荐系统变得更差会发生什么,例如,如果相关性模式为[0, 1, 1]。那么 DCG@3 是 0/log₂(2) + 1/log₂(3) + 1/log₂(4) ≈ 1.131,NDCG@3 ≈ 1.131 / 2.131 ≈ 0.531 < 0.704。
讨论与结论

好的,我们已经讨论了一些常见的指标。我们也看到了这些指标如何衡量推荐系统输出的排名与用户购买的项目的匹配程度。然而,请你思考一下:
这真的是我们想要的吗?
毕竟,用户已经购买了这些项目,即使没有我们推荐系统的帮助(因为我们现在才在开发它!)。所以,我们实际上是在衡量我们的推荐系统能够展示客户本来会购买的商品的能力。
可能我们的推荐系统在生产环境中使用时互动率(点击它的人的比例)会增加。但我不确定这是否是一个成功的标志,因为他们可能本来就会购买很多这些商品。也许我们只是给他们提供了一个在开始页面上的快捷方式。这并不是坏事,因为这可能会转化为更高的客户满意度,这对其他目标可能有好处。但它可能不会对收入产生你希望的重大影响。
要增加收入——这通常是你的终极目标——也许让用户看到他们自己不会购买的商品更有意义。但这在离线环境中很难衡量。你需要实现一个推荐系统,然后在在线环境中测试它,这样你才能看到你的客户真正想要什么。
举个例子,你可以测试一个高多样性的推荐系统,即一个显示来自不同类别商品的推荐系统。或者,你认为总是明智的在 Netflix 上向喜欢喜剧的人推荐 100%的喜剧吗?也许推荐不同的类型也有意义。
正如我所说的,你必须使用A/B 测试在在线环境中进行测试。也许你的用户喜欢被推荐他们本来会购买的东西,但也可能不是这样。
不要猜测,只要测试。
在最好的情况下,你可以观察到你的某一个离线指标与在线 KPI 之间的相关性。然后优化这个离线指标是有意义的。但是,如果你没有这种相关性,丢弃一个模型只是因为它的 NDCG 低于另一个模型的 NDCG 时要小心。
我希望你今天学到了一些新的、有趣的、有价值的东西。感谢阅读!
如果你有任何问题,可以在 LinkedIn上联系我!
如果你想更深入地了解算法的世界,可以试试我的新出版物关于算法的一切!我仍在寻找作者!
从直观的解释到深入的分析,算法通过示例、代码和精彩的内容变得生动。
medium.com](https://medium.com/all-about-algorithms?source=post_page-----c5d72193ea2b--------------------------------)
最困难的部分:定义分类目标
在你的生产数据库中,它并没有标记为‘Target_Variable’!
·
关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 11 月 14 日
--
图片由 Kenny Eliason 提供,来源于 Unsplash
什么是目标变量?
目标变量是你尝试通过监督机器学习模型进行预测的变量或指标。它也通常被称为因变量、响应变量、‘y’变量,甚至只是模型输出。
无论你喜欢哪个术语(或在统计课上被迫使用哪个术语),这是任何监督建模项目中最重要的变量。虽然对于大多数有机器学习经验的人来说,这显而易见——但对于新手来说,值得重申原因。
从技术角度看,目标变量的数据类型决定了你正在进行的建模项目的类型。数值目标变量属于回归模型的范畴,而分类变量意味着你正在进行分类模型的工作。

带有数值目标变量的回归模型——来自我的回归课程
但比模型类型更重要的是,你的目标变量是你构建模型的全部原因。

一个(非常)简单的决策树,预测客户是否会流失(停止成为客户)——来自 Maven 的分类课程
定义分类的目标变量
表面上,定义一个用于分类的目标变量似乎很简单。但如果你被要求为从未建模过的数据构建模型,你的看法可能会改变。
当人们开始学习机器学习时,他们通常会得到相对干净的数据集,这些数据集有明确的 0 和 1 可以用于分类建模。但根据我的经验,在关系型数据库中找到一个完全符合你最终目标变量的列是极其罕见的。
当然,这在学习机器学习算法的过程中是完全有意义的,但当我看到生成第一个“真实生活”建模数据集的 SQL 查询时,我确实经历了一次严酷的觉醒,这是我在第一个数据科学家工作中有机会接触到的。
让我们看看在生成目标变量的过程中,针对订阅产品的客户流失的情况下,我们可能需要对数据进行哪些转换。这可能是保险单、Netflix 订阅等。我创建了一些模拟的数据,模仿了我在实际工作中看到的数据,但如果它与数据工程师构建这些表格的方式不符,还请见谅。
示例 1:取消日期

没有明确的 1 或 0 指示客户是否流失(订阅在此结束),但在这个例子中,工程化一个这样的指标非常简单。如果 subscription_end_date 为 null,我们可能可以假设客户仍然活跃。因此,我们可以应用一些基本逻辑,如果订阅结束日期不为 null,则赋值 1,如果为 null,则赋值 0,这样我们目标变量的正类就是流失客户。

够简单了。现在我们来看一个更复杂的例子。
示例 2:续订日期

现在假设我们在订阅表中有另一列。如果客户再次成为付费订阅者,我们有一列来记录这一点发生的时间。
我们的第一位客户在订阅结束后从未回来,所以我们可以放心地说他们已经流失了。
那么客户 4 呢?他们在订阅结束后的一天重新订阅了。他们可能在订阅结束时有一个月度订阅,或者信用卡过期了,一旦他们意识到这一点,就立即重新激活了他们的订阅。一天的会员空档足以说明这个客户已经流失了吗?可能不够。但如果是 7 天或 30 天的空档呢?
客户 5 确实重新订阅了,但他们已经有一年多没有成为客户了。可以公*地说他们已经流失了,但在这里与利益相关者和主题专家沟通至关重要。我们可能会确定一个逻辑,将流失者定义为已经中断超过 30 天的人,最终得到以下结果:

这不是一个微不足道的区别。你的目标定义可能会对哪些特征能成功预测你的目标、模型的整体准确性以及最终项目的成功产生下游影响。
例如,如果客户 4 是我们*台上的一个非常活跃的用户(他们在失去访问权限时立即注意到),而其他两个客户却不是,那么与流失相关的预测特征的预测能力将会被我们糟糕的目标定义所削弱。
我考虑过添加更多示例,但我认为这两个示例已经很好地阐明了更广泛的观点。作为思考的材料,当我们有交易表,其中每个月的付款都有一行记录,不同的订阅类型和长度,因促销获得免费订阅的客户(我们可能希望完全排除他们),等等,目标创建的逻辑更容易出错。
一点建议
你的模型的目标不仅仅是 0 或 1,它代表了你分类建模项目的成功基础单元。因此,我有几点通用建议来帮助你在下一个项目中定义目标变量:
-
你的目标变量可能不会在表格中干净利索地定义给你。
-
花时间,至少几天甚至几周,认真思考如何从手头的数据中定义你的目标变量,以及这种定义如何与业务目标相关。这通常不是一个微不足道的任务,事先投入时间和精力会比后来需要改变定义要好得多。
-
在你的组织中,目标变量的定义可能在概念上并没有达成一致。“流失”可以有多种定义方式,你可能永远无法得到一个完美的定义。但作为建模者,通过与相关利益方沟通,达成对你定义的共识是很重要的。至少,这样你可以在定义需要更改的情况下保护自己的利益,而最好的是,你为构建成功的模型奠定了基础。
感谢你抽出时间阅读我的想法——我知道对于经验丰富的数据科学家来说,这些可能并不突破常规,但我希望刚入门这个领域的人能够意识到课堂与实际工作之间的差距,并理解目标变量不应被忽视——尽管有时会想尽快进入建模阶段,但如果你这样做,可能会错过…目标。
如果你喜欢这篇文章,请关注我!我定期撰写关于 Python、Pandas 以及从数据分析到数据科学转型的文章。我还在 Maven Analytics *台和 Udemy 上提供了相关课程——希望能在那里见到你!
数据质量问题对广告支出的回报的隐藏成本
如何领先公司基于客户生命周期价值驱动以价值为导向的广告支出分配,并克服代价高昂的数据问题
·
关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 7 月 6 日
--
你的数据能够揭示很多信息,关于哪些客户带来了丰厚的回报,哪些则没有。不论你是 B2B 公司中的生命周期营销经理,优化免费试用转化为付费客户,还是 B2C 电子商务中的数据科学家,优化让首次用户购买你的产品,每个用户对你来说都是有价值的。
领先的公司已熟练掌握根据客户在网站或产品上的互动预测客户生命周期价值。凭借这些数据,他们可以相应地调整竞标,合理地为一个可能在其生命周期内产生额外 50 美元的用户支付额外的 5 美元。
换句话说,你正坐拥一座金矿,可以将其转化为预测并直接输入 Google 和 Meta,调整你的竞标策略,以通过为每个客户支付适当的价格来赢得市场。

source: synq.io
影响客户生命周期价值(CLTV)计算的数据问题导致价值竞标基于错误的假设
但你的广告支出的回报只有在你的客户生命周期价值计算准确时才会显现。
*均规模在 250 到 500 人的公司使用数十种数据源,跨越数百个表格,且不总是能正确了解他们使用的数据是否准确。这意味着他们可能将预算分配给错误的用户,从而浪费数十万美元。
在这篇文章中,我们将深入探讨数据驱动的营销团队在原始数据经过转换、作为广告*台中基于价值的竞标输入时所面临的数据质量问题。我们将特别关注以下领域:
-
360 度概览 — 为什么拥有全面的营销数据堆栈概览如此重要
-
监控 — 在你的营销管道中应该注意的常见问题
-
人员与工具 — 协调人员和工具以建立可靠的营销数据管道的重要性
为什么你需要对营销管道有 360 度的概览
为了了解每个客户的价值,你可以分析用户行为和数据点,这些数据点是强有力的指标。这通常会揭示一个由数十个不同系统提供的预测因素列表。通过结合这些因素,你可以获得对客户的全面了解,并将这些线索串联起来,了解行为和行动背后的关键驱动因素,从而识别出具有高价值的客户。
例如,如果你是一家 B2B 公司的营销人员,你可能已经了解了促使客户从免费用户转变为付费用户的因素。
-
登录两次使客户转化的可能性提高 50%(Stripe)
-
在 7 天内推荐他人使客户价值提高 70%(Segment)
-
拥有公司邮箱地址且员工人数超过 250 的用户比其他用户更有可能成为付费客户(Clearbit)
-
仅通过移动设备登录会使客户价值减少 30%(Amplitude)

source: synq.io
数十个上游源数据进入数据仓库后,才会发送到 Google 和 Facebook 进行广告竞标
如果没有全面的概览,你可能会错误地假设输入到竞标系统中的数据是准确的,结果会发现关键问题,例如:
-
从电子邮件域名中错误提取公司规模,由于 Clearbit/Segment 集成故障。
-
事件跟踪冲突导致从 Amplitude 中丢失结账流程中关键动作的数据。
-
从 Stripe 集成中数据同步不准确,导致关于客户购买的信息不完整。
“由于第三方数据源的问题,我们的 CLTV 计算中断了。我们不仅失去了当天花费在 Google 上的 10 万英镑,还不得不等几天才能让 CLTV 模型重新校准。” — 500 人金融科技公司
研究论文强调了多种因素在预测在线零售商 ASOS 的 CLTV 中的重要性。研究发现,关键因素包括订单行为(例如订单数量、*期订单历史)、人口统计信息(例如国家、年龄)、网页/应用会话日志(例如距离上次会话的天数)和购买数据(例如总订单价值)。这些见解是数百个数据转换和几十个第三方源整合的结果。
ASOS — 影响 CLTV 的因素

来源:synq.io。数据来自 研究论文
数据问题 — 已知的未知和未知的未知
拥有全面的数据概览还不够;重要的是主动识别影响 CLTV 计算的潜在问题。这些问题可以分为两种类型:
已知的未知:发现并确认的问题,例如管道故障导致 Google API 12 小时内未同步数据。
未知的未知:可能未被发现的问题,例如产品分析事件数据同步到数据仓库不正确,导致对用户行为的假设不准确。
“我们每天在 Facebook 营销上花费 50,000 美元,而我们的一条上游管道在 3 天内没有同步,导致我们浪费了一半的预算。直到他们通知我们之前,我们完全不知道发生了这种情况。” — 250 人电子商务公司
为了主动识别和解决影响 CLTV 计算的数据问题,请考虑监控以下领域:

来源:synq.io
逻辑测试:使用像 dbt 这样的工具将假设应用于不同的列和表。例如,确保 user_id 列是唯一的,order_id 列从不包含空值。实施额外的逻辑检查,例如验证电话号码字段仅包含整数,或确保*均订单大小未超过合理的限制。
数据量:监控数据量的异常。例如,订单表中新行的突然增加可能表明数据转换错误导致重复项,或者反映了新产品的成功。
新鲜度:了解所有数据表的最新刷新时间,因为数据管道故障可能在更细微的领域中被忽视。例如,暂停从 Clearbit 收集公司规模数据的集成问题可能会在没有立即检测的情况下持续存在。
分段:识别特定分段中的问题,例如某些产品类别的标签错误,这在没有适当检查的情况下可能难以检测。
确立责任和所有权
一旦你对数据和监控系统有了全面了解,定义监控的各个方面的责任就很重要。在之前提到的例子中,数据所有权涵盖了产品使用、人口统计、计费和订单。为相关的数据源和表格指定负责人,确保能够及时处理和解决问题。
“我们有一个重要的测试警报响了好几周,但一直未被处理,因为接收警报的人已经离开公司” — 英国金融科技独角兽
此外,优先关注数据产品中最关键的组件,并建立服务水*协议(SLA)。定期评估正常运行时间和性能,以系统化的方式解决任何需要注意的领域。
总结
领先的公司使用来自多个来源的数据来准确预测每位客户的客户生命周期价值(CLTV)。这使他们能够优化广告竞标并锁定最有利可图的客户。然而,你的广告支出最终成功与否取决于 CLTV 计算的准确性,使得未被发现的数据问题成为一个重大风险。
为了确保高质量的数据用于基于价值的广告竞价,我们建议关注两个关键领域:
-
360 度概览:没有全面的概览,你可能会误认为你的竞标系统中的数据是准确的,后来却发现关键问题。这些问题可能包括像 Amplitude 这样的*台中的陈旧数据或与 Clearbit 的集成问题。
-
监控:主动识别和解决影响 CLTV 计算的数据问题至关重要。实施包括逻辑测试、数据新鲜度、数据量跟踪和分段分析在内的监控流程。
通过优先考虑全面概览和主动监控,公司可以降低与错误 CLTV 计算相关的风险,并提高基于价值的广告竞价策略的有效性。
如果你在处理营销数据并且考虑如何建立可靠的数据,我很乐意与您交流。请通过 mikkel@synq.io 联系我。
开源开发中的隐性危机:行动号召
原文:
towardsdatascience.com/the-hidden-crisis-in-open-source-development-a-call-to-action-abf6bc2a8c0c

关于开源软件他们没有告诉你的事
·发表于 Towards Data Science ·阅读时间 19 分钟·2023 年 6 月 9 日
--
在数字时代,创新的引擎常常伴随着开源的旋律。这种协作的软件开发模式不仅塑造了我们的技术格局,还培养了一个由共享理念团结起来的创作者社区:为了所有人的利益构建软件,摆脱资本化的限制。在这一领域的先锋们赠予了我们如 Linux、Git、Python 和 MySQL 等变革性技术,展示了开源的巨大潜力。然而,这个充满活力的领域正逐渐失去色彩,它曾经响亮的嗡嗡声现在变成了微弱的低语。曾经推动全球创新的热情正在消退,被一种新的动态取而代之,在这种动态下,开源贡献常常被视为职业发展的垫脚石或企业寻找人才的*台。
这种过渡微妙却深刻。如今,免费和开源软件(FOSS)贡献常常被视为有志开发者的简历加分项或公司寻求人才的狩猎场。曾经是全球创新堡垒的开源,现在岌岌可危,面临成为小众爱好的边缘。尽管它在科技行业中的关键作用,开源维护者的处境在不断上升的企业使用和日益严重的安全问题中变得越来越难以维持。
这一问题在当今世界尤为明显,因为 AI 生成的代码量在不断增加。专有语言模型利用大量开源代码进行训练,但今天的代码编写模型如 Github Copilot 或 Repl.it 的 Ghostwriter 都没有对原始软件创作者进行归属、补偿,甚至没有给予任何认可。如果我们不直接对开源创作者进行价值认可,这一问题只会变得更严重。
然而,重新点燃开源精神还不算太晚。通过理解推动这种变化的压力以及无偿开源维护者面临的挑战,我们可以开始为开源的可持续未来绘制一条路线。这篇文章将深入探讨导致这一趋势的各种因素,提供来自开源社区关键人物的见解,并探索确保开源软件开发持久性和活力的潜在解决方案。
那么,我们是如何走到这一步的?这种转变对开源的未来以及科技的未来有何影响?更重要的是,我们如何在这些复杂性中找到*衡,再次振兴开源运动?系好安全带,我们将开始一段穿越开源软件不断发展的旅程,从其谦逊的起点到现今的挑战,甚至更远。这条路可能崎岖不*,但这次旅程定会带来启发。

跳上来,一起踏上这段开源世界的旅程。图片由作者提供,通过 Stable Diffusion 和 Photoshop 生成。
“免费”开源的悖论:通向倦怠的老路?
当我们在开源软件的背景下谈论“免费”时,我们的参考不仅仅是成本。我们强调的是自由:运行、学习、重新分发和修改软件的权利。这种自由赋予用户对其计算机的控制——这是开源运动的核心原则。然而,这种自由也带来了一个意想不到的副作用:许多开源软件项目无意中采用了所谓的“免费商业模式”。
在这一模式下,软件是自由可得的,任何人都可以贡献代码来改进它。贡献者可能从为项目增添价值中获得满足感,但他们通常没有实际的奖励。这种不*衡引发了几个挑战,波及到不仅仅是贡献者,还包括用户和更广泛的技术生态系统。
自由和开源软件是数字革命的生命线,作为技术创新的支柱,正越来越受到极限的挑战。它在一种商业模式下运作,这种模式虽然理论上非常*等,但实践中往往难以维持。这个模式的精神很简单——共同创建、共享和维护软件而不直接获得经济利益。回报表面上是全球技术生态系统的共同进步。然而,在这一看似乌托邦的理想背后,潜藏着充满倦怠、缺乏激励和不断上升的质量问题的严酷现实。
创作者和维护者经常发现,他们为集体利益而创造和维护软件的高尚事业,往往变成了一个令人生畏的任务。追求他们的热情,这些专注的个人常常在漫长的工作时间、复杂的问题解决和无情的压力中穿行。这样的条件可以,也经常会,导致职业倦怠。当考虑到贡献者中有很大比例未获得报酬时,这种情况会变得更糟。据 Tidelift 调查,2023 年这一比例高达惊人的 46%,与 2021 年的水*相同。
缺乏直接的财务激励常常导致维护和改进软件的动力不足。这可能导致一系列质量问题,软件经常留下未解决的漏洞或停滞不前,缺乏新功能和改进。
以个人经验而言,作为一名在开源 GitHub 代码库上拥有超过 7500 个星标的开发者,这个问题对我来说尤为切身。尽管我的软件对成千上万的开发者持续有用,经过多年的使用,从赞助者那里获得的累计补偿却不到我在任何一个软件职位上一个月的工资。

我最受欢迎的开源代码库的使用统计数据。
这种困境并不是由于缺乏寻求支持。虽然在 GitCoin、Github Sponsors、Patreon 和直接赞助等*台上做出了努力,但从这些渠道获得的财务支持随着时间的推移已被证明是不够的。因此,即使这些软件包继续被使用,我也发现自己无法投入必要的时间进行维护和进一步开发。
这些挑战的波及效应扩展到消费者——包括个人和企业。尽管免费、社区驱动的软件最初看起来很诱人,但实际使用中的现实常常充满挫折。用户可能会与存在缺陷或不够友好的软件作斗争。支持可能不稳定,由于资源或时间限制,维护者经常难以跟上不断涌入的查询和错误报告。
这一环境也滋生了安全问题,这是开源软件中一个难以忽视的方面。2021 年 12 月出现的 Log4shell 漏洞突显了这一点,受影响库的开发者发现自己不断应对问题,却没有获得补偿或实质性的认可。
‘搭便车问题’,这是一个著名的经济难题,在开源软件中显得尤为突出。这个概念指的是在某些情况下,个人或实体在没有承担提供成本的情况下从资源或服务中受益。在开源软件领域,当盈利丰厚的公司将开源项目纳入其产品中,却未对这些项目的维护者提供足够的补偿或支持时,就会出现搭便车问题。这种挫败感引发了诸如 Marak Squires 故意破坏其受欢迎的 npm 包的事件,以抗议财富 500 强公司剥削他的无偿劳动。

一位艺术家对开源软件中搭便车问题的描绘。作者创作,使用 Stable Diffusion 和 Photoshop 生成。
尽管面临这些障碍,开源软件社区展示了非凡的韧性。这个生态系统继续蓬勃发展,得益于贡献者的集体力量,他们出于责任感、热情或对协作创新力量的信仰而坚持不懈。但一个悬而未决的问题仍然存在:还能持续多久?
这在长远来看是不可持续的。 重新评估开源软件中的‘免费’商业模式不仅是时机成熟,它对保护这一宝贵资源至关重要。
软件巨头角色的加剧
当我们提到软件巨头时,我们指的是那些影响我们日常生活的组织:谷歌、亚马逊、苹果、脸书和微软。这些公司开发了我们赖以依赖的*台和技术,从我们智能手机上的操作系统,到我们用来购物的网站,再到用于与亲人保持联系的社交媒体*台。但这些巨头在开源生态系统中占据什么位置呢?
为了进一步理解这一点,让我们退一步,审视整体状况。我们在开源社区面临的困境就像一个跷跷板。随着贡献与使用之间的不*衡持续存在,我们正在接*一个不可逆转的临界点。贡献者越少,用户越多,跷跷板的倾斜度就越大,造成更大的不*衡,只有那些拥有大量资金支持的私人代码库的软件巨头才能竞争。我们目前正看到一个恶性循环,如果不加以解决,可能会失控。
这些软件巨头虽然有影响力,但往往有维护其软件大量部分为闭源的既得利益。这在很大程度上源于他们开发的技术的专有性质,这对他们的商业战略至关重要。闭源软件为这些公司提供了竞争优势,使他们能够保护其知识产权,并在产品的开发、质量和安全方面拥有更多控制权。
然而,这些软件巨头同时也是开源软件的最大消费者之一。他们常常将开源库集成到他们的产品中——而无需任何补偿——甚至将一些代码发布为开源。通过这样做,他们利用了开源开发的协作本质,并在开发者社区中赢得了善意。但这种共生关系并非没有复杂性和争议。著名科幻作家威廉·吉布森曾经说过,
“未来已经到来——只是尚未均匀分布。”

一张信息图显示了企业组织对开源软件的广泛使用。来源:TechTarget
这一观点在我们的开源生态系统中同样适用,在这里,软件巨头享受了大量的好处,而负担却并不均等分配。软件巨头从开源世界中获取所需,基于此构建自身需求,同时将其最有价值的代码保持私密,以不断扩大对较小开发者的竞争优势,这种情况太过简单。
以谷歌为例。虽然谷歌通过 Android 和 Chromium 等项目对开源社区做出了显著贡献,但其对某些开源项目的处理也遭到批评。谷歌决定在 Chrome 中阻止第三方 cookies,表面上是出于隐私原因,但这一决定却遭到开源社区一些人士的反对,他们认为这是一种巩固其在线广告市场主导地位的尝试。
亚马逊在其 AWS 云服务中使用 MongoDB 数据库软件是另一个有争议的案例。MongoDB 是一个开源数据库系统,在其许可协议中做出了修改,以回应其认为亚马逊在没有回馈社区的情况下利用其软件。这一事件引发了关于从开源软件中获益的公司是否有责任回馈社区的讨论。
这些软件巨头对开源开发者的招聘加剧了争议。一方面,这些开发者通常获得资源和支持,能够全职投入他们的开源项目,从而推动显著的进展。另一方面,这种招聘趋势有可能导致开源社区的“人才流失”,使得其他项目可用的开发者减少。
此外,这些软件巨头因其“封闭园区”而臭名昭著——在这种封闭的生态系统中,公司控制着用户体验的所有方面。虽然这可以为用户提供一致、流畅的体验,但也可能扼杀竞争和创新。较小的开发者可能会发现自己处于不利地位,无法进入这些生态系统或被迫遵守严格的规则。此外,这些封闭园区往往故意导致不同生态系统之间缺乏互操作性,给用户带来不便。
结果是更多的厂商锁定、更高价值软件的集中化,以及对开发者的体验造成了更大的负面影响。这显然是不可持续的。
为了说明这些封闭园区对软件生态系统的影响,可以考虑苹果的 App Store 政策,这些政策已成为持续争议的主要来源。批评者认为这些政策限制了竞争,并赋予苹果对其设备上的软件生态系统过度的控制权。
苹果的 App Store 在这种封闭园区模型下运作,具体来说,这意味着苹果对其*台上可以提供的内容进行控制,并制定了开发者必须遵守的某些规则和指南。其中一项规则是所有应用内购买必须通过苹果自己的支付系统,苹果会收取最高达 30%的佣金。这引起了一些应用开发者的不满,因为这可能显著减少他们的利润率。涉及这一政策的最著名争议是与热门游戏《堡垒之夜》的开发者 Epic Games 之间的争执。2020 年 8 月,Epic Games 尝试通过在游戏中实施自己的支付系统来绕过苹果的支付系统,导致《堡垒之夜》被从 App Store 下架。
这引发了 Epic 和苹果之间的法律战,这场法律战引起了大量关注,集中在苹果的 App Store 政策及其对应用开发者的影响上。这起案件可能对 App Store、类似*台以及整个封闭园区的未来运营产生重大而深远的影响。即使在一位联邦法官裁定苹果必须启用第三方支付选项并降低对某些合作伙伴的佣金之后,Epic Games 首席执行官蒂姆·斯威尼仍对结果不满意,表示:
“这里的问题是一个经典的垄断捆绑。你从硬件开始。苹果制造智能手机,并从中获利——这他们应得的。但接着他们强迫所有购买他们智能手机的用户只能通过他们的应用商店获得数字内容。他们阻止其他应用商店在被十亿终端用户拥有的硬件上与他们竞争。这是第一个捆绑,这完全阻碍了所有竞争和市场力量,这些力量本来会塑造更好的应用商店和更好的消费者交易。”
总之,虽然软件巨头在开源软件的发展和推广中扮演着关键角色,但他们与开源社区的关系复杂且多面。在开源软件使用不断激增的背景下,必须以促进开源生态系统的可持续性和鼓励软件巨头与开源社区之间健康的权力*衡的方式来应对这些复杂性。
资金不足的环境与资源短缺

现在是时候逆转开源软件中的资源短缺了。图像由作者生成,使用了 Stable Diffusion 和 Photoshop。
在一个多亿万富翁企业依赖软件作为运营命脉的世界中,那些软件的基础常常被忽视,甚至被明显忽略。这种困境在开源开发领域尤为明显,那里的资源稀缺,许多情况下几乎不存在。
开源开发中的资金不足问题几乎不是秘密。然而,它依然存在,不仅对开发者,对整个技术行业和依赖这些产品的广泛用户基础都是不利的。以 OpenSSL 为例。这个几乎对每个现代网络设备至关重要的开源项目,曾经每年仅收到 2000 美元的捐赠。Heartbleed 漏洞引起的关注使得捐赠增加到每年仍然微薄的 9000 美元。OpenSSL 团队表示,他们需要六名全职成员来从事该项目,而不是只有一名艰难*衡合同工作的成员来覆盖成本。
这样的资源稀缺必然影响软件质量、维护和新功能开发。当开源项目的维护者被压得喘不过气时,项目的质量往往会下降。有限的资源意味着投入到全面测试、漏洞修复和安全补丁上的时间和精力更少。
同样,当资源紧张时,新功能的开发常常被 relegated to the background。毕竟,当战斗是为了项目的生存和安全时,几乎没有带宽来考虑扩展或创新。这种限制阻碍了开源项目的增长,并阻碍了 heavily relies on them 的科技生态系统。
寻找开源项目资金的挑战由来已久,古老得如同开源本身。一些开发者通过 GitHub Sponsors 和 GitCoin 等*台,或通过与公司签订维护合同获得了一定成功。其他人则在公司中担任角色,由公司支付薪水以维护他们的库。然而,这些情况是例外而非常态,且通常不足以完全支持开源项目的维护和开发。
缺乏“一刀切”的解决方案使问题更加复杂。每个开源项目都是独特的,拥有自己的需求、挑战和愿景。因此,目前还不存在适用于开源软件的普遍资金模型。在行业为这种“免费”劳动设计出更有效的支持机制之前,我们应当准备好迎接更多开源开发者的公民抗命行为,他们将采取激烈的措施来强调他们的贡献。
终极问题在于可持续性。随着开源软件成为当今几乎每一件软件和连接设备的重要组成部分,显而易见,依赖于少数过度劳累且报酬不足的开发者并不是一种可持续的策略。这是一个亟待解决的难题,必须迅速解决,以保障开发者和数百万依赖他们工作的用户。
开源的悬崖:生存还是消亡?
鉴于这些挑战,思考开源的未来是自然的。从最初作为一个高尚的事业,免费向世界提供代码,赋权任何人进行构建,已转变为少数肩负维护和开发重担的人的愈加无回报的任务。曾经为全球利益而非利润构建软件的充满活力的精神似乎正在消退。但开源真的濒临灭绝吗?

一名男子思索着开源软件的消亡及其对他自身存在的深远影响。图像由作者生成,使用了 Stable Diffusion 和 Photoshop。
当前的开源状态无疑充满了财务约束、倦怠和差距。尽管开源项目被广泛使用和依赖,但这些资源的创作者和维护者常常未获得报酬。正如我们所指出的,像 Daniel Steinberg 这样的开发者,已经维护 cURL 将*三十年,仍继续无偿投入时间和专业知识。OpenSSL 团队也体现了这一现实,尽管他们的项目在现代网络中扮演着关键角色,但他们不得不寻求额外的工作来覆盖项目维护成本。
尽管如此,承认方程式的另一面至关重要。尽管面临这些巨大的挑战,开源的核心——它的社区——依然坚定不移。开发者们仍然继续为开源项目做出贡献,受到他们对开放协作精神的热情和承诺的驱动。正如 Marak Squires 和“left-pad”的开发者所示,开源贡献者之间有着强烈的所有权感和主动性。他们的抗议行为不仅仅是对挫折的表达,而是对他们提供的关键价值的有力提醒。
此外,仍然有积极变化的希望之光。认识到开源的重要性和独特挑战的倡议如 GitCoin 已经出现,旨在为开源开发者提供更直接的财政支持渠道。现在的问题是,这些措施是否能够在长期内充分维持开源生态系统。
然而,人们不能视而不见残酷的现实。持续的压力、资源匮乏和不断增加的期望正在付出代价。风险是显而易见的:如果这些条件持续下去,我们可能会目睹开源的衰退甚至终结,进一步将软件开发的创建集中到现有的软件巨头中。开源不仅仅是代码——它还包括背后的人。如果我们忽视对他们的支持,我们可能会失去这一充满活力、合作的社区的本质。
在这种背景下,开源的潜在衰退并不意味着免费代码的终结,而是意味着一个社区驱动的方法的垮台,在这种方法下,任何人都可以贡献、创新和分享,而无需价格标签。这样的损失将超越科技行业的影响——它会抑制推动我们数字时代的协作创新精神。
随着我们向前迈进,科技行业——以及整个社会——有必要重新评估我们如何重视和支持开源。开源的未来在很大程度上将取决于我们集体的行动和对保护这一宝贵资源的承诺。正如古老的谚语所提醒我们的,“你不知道自己拥有的东西,直到它不再存在。”让我们不要等到为时已晚。
朝着可持续的未来前进
鉴于我们所考察的严峻现实,显而易见,我们正处于一个重要的十字路口。开源是我们日常依赖的数字基础设施的核心。然而,当前的模式逐渐接*不可持续的边缘,制定一个有意义的前进道路的紧迫性在加剧。在我们踏上这一旅程时,我们必须重新点燃那些将开源提升为我们世界中不可或缺的一部分的指导原则:协作、社区以及对推动技术惠及每个人的共同承诺。
一个有前景的解决方案是敦促公司,即开源的主要受益者,认可并补偿代码背后的智囊团。期望个人无偿为开源贡献的思想是上一时代的遗留物。在开源支撑着万亿美元产业的时代,新的范式是必不可少的。公司必须投资于开源项目和那些使这些项目得以实现的天才个人。这可以通过以下方式完成:
-
直接资助:组织可以为他们日常使用的项目建立资金支持。
-
聘用维护者:可以招募开源开发者作为兼职或全职员工。
-
分配员工时间:可以鼓励员工在工作时间参与开源项目。
政府机构也可以做出重要贡献。白宫最*对开源重要性的认可,源于 Log4J 事件,这是一个积极的步骤。然而,要产生真正的影响,这种认可必须伴随有意义的行动。政府可以为关键的开源项目提供财政支持,特别是那些具有显著安全影响的项目,或者激励公司这样做。他们还可以制定立法来保护开源贡献者的权利,并建立公*补偿的框架。
然而,前进的道路不仅仅涉及经济方面;还涉及尊重。开源贡献者应当因其劳动获得认可,这种认可不仅仅是在 README 文件中提及,而是实质性地承认他们所增加的价值。这种认可可以以多种形式体现,从在行业活动中展示贡献者,到给予他们在维护的项目中参与战略决策的机会。这样,贡献者可以从被视为志愿者转变为未来数字基础设施中的关键利益相关者。
个人贡献者同样扮演着重要角色。他们可以放大自己的声音,为自己的权利而战,并确保他们的劳动获得应有的价值。他们还可以选择支持那些遵循道德实践和优先考虑维护者福祉的项目。然而,最实际的解决方案可能是开源开发者开辟自己的成功道路。我想起了先锋发明家尼古拉·特斯拉的一句恰当的名言:
“现在属于他们;我真正努力工作的未来,属于我。”
如果你维护一个有价值的开源包却缺乏财政支持,为什么会这样?也许责任在于开源创作者制定简单的支付解决方案,并要求对他们的努力给予报酬,就像现有的软件巨头每天做的那样。
通往一个更可持续的开源生态系统的道路可能并不简单,但这是我们必须集体踏上的旅程。正如开源精神提醒我们的那样,我们的力量在于团结。作为维护者、贡献者、用户、公司和政府,我们每个人都有责任确保开源继续蓬勃发展,造福所有人。让我们致力于实现这一愿景,并培养一个不仅可持续,而且真正被重视的开源未来。

一个可持续未来的愿景,由一支资金充足的开源开发团队的办公室描绘。图片由作者提供,使用稳定扩散和 Photoshop 生成。
最终思考:为开源铸造一个可持续的未来
回顾这篇文章的标题,开源开发中的危机是一个迫在眉睫的问题,需要我们立即关注。这不仅仅是关于挽救一个失败的系统,而是关于动员集体努力,体现团结、协作和追求共同利益的精神。开源社区在其最广泛的意义上代表了这种团结。但为了使这一愿景成形,我们需要解决社区当前面临的严峻现实。
就像任何危机一样,开源领域复杂而多样。由全球开发者贡献的丰富软件包和库推动了我们数字存在的一大部分。然而,它也面临着可能会破坏这一生态系统的危机。这种威胁不是外部的,而是内部的,由于一个相对较小的忠实维护者群体的无休止的、通常未付薪酬的工作,形成了不可持续的模型。这种困境既深刻又关系重大。如果不加以关注,它可能对软件开发的未来构成相当大的风险。
随着人工智能和机器学习模型编写越来越多的世界代码,我们现在必须解决这个问题。 如果不加以控制,我们可能会看到一个只有资金最充足的组织才能访问高质量代码的世界。这将是我们所知的免费和开源软件的终结。
虽然这看起来可能是一个悲观的前景,但它并不是末日的预兆。相反,它是对我们如果让这种不可持续的状况继续下去可能面临的后果的严厉警告。如果维护者被逼到极限或决定放弃他们的项目,未来的软件环境可能充满安全问题、操作缓慢以及丧失关键工具。
在这场危机中,软件社区的每个成员都有责任参与。作为用户,我们可以通过开发、文档编写或提供财务支持来回馈那些让我们受益的项目。公司也可以回报他们使用的项目,提供资金支持,或雇佣开源开发者来持续维护和改进他们的开源项目。政府可以在公共基础设施中承认开源的价值,并提供资助及其他支持形式,以帮助维持这些项目。
支持开源的责任是集体的。我们应该停止把开源视作理所当然,开始认可和奖励维护者们所做的重要工作。通过这样做,我们可以确保这一全球宝贵资源的持续健康和活力,这个资源是我们所有人都依赖的。
这是对开源社区中每一个利益相关者的号召:开发者、组织以及用户。最终,解决这一危机的成功取决于其社区成员——我们。如果你希望为这些问题的行业解决方案做出贡献,我鼓励你主动联系并展开对话。我们可以共同确保开源软件未来的可持续性和活力。
参考资料:
-
谁从开源维护者的工作中获益?
www.techtarget.com/searchitoperations/feature/Who-profits-from-open-source-maintainers-work -
开源开发者,他们免费工作,正在发现自己拥有力量
-
Epic Games 对苹果诉讼的裁决对 iPhone 用户意味着什么
www.npr.org/2021/09/10/1036043886/apple-fortnite-epic-games-ruling-explained -
Epic Games CEO Tim Sweeney 斥责苹果 App Store 对开发者的“伤害”
www.macrumors.com/2022/05/26/epic-games-ceo-app-store-a-disservice/
多项式回归中的隐藏线性
原文:
towardsdatascience.com/the-hidden-linearity-in-polynomial-regression-333f1bed7aa5
从两个角度更好地理解线性模型
·发表于数据科学前沿 ·阅读时间 9 分钟·2023 年 3 月 17 日
--
在这篇文章中,我们将讨论这种观点:
多项式回归是线性回归的一种。

初看之下,这个说法可能显得荒谬。多项式回归被称为非线性回归,而线性回归显然是线性的,这两个模型怎么可能被认为是相同的呢?
就像生活中的许多事物一样,你可以用不同的方式看待相同的事物,两个人可能会得出看似不同的结论。然而,我们往往忽略了答案并不是最重要的部分,而是你用来得出结论的方法、方法论或框架。我最*发表了一篇关于逻辑回归是回归模型还是分类模型的争论的文章。我提到了两个观点:统计学与机器学习。在这篇文章中,我们还将探讨这两种不同的多项式回归理解视角。
哦,有些人可能会说多项式回归只是一个理论模型,不容易付诸实践,因为将一个特征提升到高次幂并没有实际意义。通过这篇文章,你会发现这个原则的使用比你想象的要广泛,而且多项式的次数不应是唯一的超参数。
1. 线性回归和多项式回归的定义
首先,我们来看一下线性回归和多项式回归的定义。现在,我知道你可能已经非常了解这些定义,但当你阅读这些定义时,尝试找出两个模型的输入中有什么不寻常的地方。
1.1 定义线性回归
线性回归通常指的是普通最小二乘法(OLS)回归,它涉及最小化输出的预测值与实际值之间的*方差之和。
线性回归的方程可以表示为 y = w X + b。由于 X 是表示多个变量的矩阵,我们可以以这种方式展开函数:
y = w1x1 + w2x2 + … + wpxp + b
其中:
-
y 是因变量或输出
-
x1, x2, …, xp 是自变量或输入
-
b 是截距
-
w1, w2, …, wp 是因变量的系数
1.2 定义多项式回归
多项式回归将自变量 x 和因变量 y 之间的关系建模为 n 次多项式。结果是一个最适合数据点的曲线,而不是一条直线。
多项式回归的方程可以写作:
y = b0 + b1x + b2x² + … + bn*x^n
其中:
-
再次强调,y 是因变量,
-
x 是自变量,
-
b0, b1, b2, …, bn 是系数,以及
-
n 是多项式的次数。
所以,多项式回归允许 x 和 y 之间建模非线性关系。
1.3 一个重要的方面:特征的数量
现在,你注意到之前的定义中有什么不同吗?我在互联网上搜索了这些定义,包括维基百科和大学课程,这种差异总是存在的。
-
线性回归通常使用多个变量进行定义
-
多项式回归通常只有一个特征变量。
在我看来,主要原因是多项式回归通常从统计学家的角度来看待。这就是为什么 numpy 包中的多项式回归拟合算法 polyfit 只允许一个特征。而 scikit learn 如何,我们稍后会看到。
无论如何,在今天的“机器学习”应用中,只使用一个特征来进行模型构建对实际应用而言是远远不够的。
当然,可以为多项式回归输入多个特征。这时称为多变量多项式回归,特征将分别提高到不同的幂,并且不同特征之间会有交互。
如果有 p 个特征,多项式回归的广义方程为 n 次方程(我将用 Latex 写出这个方程,以便你更好地阅读):

这种方法并不常见,也许是因为它复杂且不易使用。然而,你可以看到这个想法非常重要,因为它是建模变量之间更复杂关系的关键。我们将在文章后面看到如何以更优雅的方式转化这个想法。
2. 揭示和可视化多项式回归的线性
为了简化解释,我们将使用只有一个特征的多项式回归。然而,你可以很容易地想象这个分析可以推广到多个特征的多项式回归。
因此,在本节中,我们将特征变量称为 x。
2.1 线性性所指的内容
多项式回归是否是线性模型?答案在于我们认为这种线性是指哪些特征。
-
多项式回归在变量 x 上是非线性的。
-
但是如果我们将变量 x、x²、x³ 等视为特征变量,则多项式回归是线性的。为了简化抽象,我们可以使用 x、x_2、x_3 等表示这些特征。然后,我们可以对这些特征进行线性回归。因此,最终它是一个多变量线性回归!
为了澄清这一点,我们可以将多项式回归视为两个独立的步骤:
-
特征工程:在这一步中,我们独立于模型创建多项式特征。
-
线性回归:线性回归的适当模型将接受上述特征并为每个特征找到系数。
我发现将复杂模型拆解成小块以便理解非常有帮助。这就是为什么在 scikit learn 中,我们没有一个叫做 PolynomialRegression 的估计器,而是通过两步来完成。
-
PolynomialFeatures 在模块 preprocessing 中
-
LinearRegression 在模块 linear_model 中……哦等等,我们只需使用这个估计器吗?你将在接下来的部分中发现更多信息。
总之,有两个视角:
-
多项式回归是一个模型,它以 x 为输入。然后它是对 x 的非线性模型。
-
多项式回归不是一个独立的模型,而是通过将 x 转换为多项式特征,然后应用线性回归来构建的。
后者将解锁一些非常有趣的潜力,我们将在本文章中看到。但首先,让我们创建一个图来更好地说服自己这种线性。因为眼见为实。
2.2 可视化多项式回归:从曲线到*面
由于多项式回归是一种线性回归,因此我们首先需要了解如何可视化线性回归。我写了这篇文章来可视化线性回归,其中包含了不同数量和类型的变量。考虑以下可视化示例,你会如何使用其中之一来适应多项式回归的情况?

为了可视化多项式回归,我们只会添加一个二次项,以使线性回归具有两个连续变量(x, x²),因此我们将看到一个 3D 图。我们无法可视化更多变量,但你可以想象。
让我们考虑一个简单的数据点集合(x, y),其遵循方程 y = 2 + x — 0.5 * x²,我们通常会创建以下图表来表示 x 和 y:

多项式回归 x, y 图 — 图片由作者提供
红线代表模型,蓝点代表训练数据集。
现在,让我们假设我们首先绘制一些 x 和 x²的值:

多项式回归图 x 和 x² — 图片由作者提供
然后,我们通过为每个(x, x²)添加 y 值来创建一个 3D 图。使用 x 和 x²作为输入的线性回归模型随后是一个*面,也可以在 3D 图中表示如下。

多项式回归是线性回归的 3D 图 — 图片由作者提供
我们可以通过改变视角,将几张图片合成一个 gif。如果你想获取创建此 gif 的代码及其他有用的代码,请通过以下链接在 Ko-fi 上支持我:ko-fi.com/s/4cc6555852。

多项式回归是线性回归的 3D 图 — 图片由作者提供
3. 多项式回归的机器学习视角
3.1 多项式回归的实际应用
各位数据科学家,坦白说,你们真的在实际应用中构建多项式回归吗?你们什么时候真正遇到多项式回归?试着回忆一下…对了,就是老师尝试解释回归问题中的过拟合时。下面是一个例子。

多项式回归过拟合 — 图片由作者提供
从统计学家的观点来看,使用 numpy 时,我们已经提到只允许一个特征。因此,使用这个 polyfit 来创建实际模型是不现实的。
3.2 从机器学习角度看缩放和正则化
从机器学习的角度看,创建多项式回归不仅仅是将一个特征提升到不同的幂次。
首先,对于多项式回归的整体创建,我们已经提到这是分两步完成的:使用 PolynomialFeatures 进行预处理和使用 LinearRegression 进行模型部分。
现在,我们还需要提到一些特定的技术,比如缩放。实际上,当特征提升到非常高的次数时,数字变得非常大,以至于 scikit learn 无法处理。因此,进行缩放是更好的选择,这不仅是理论上的问题,更是实践中的需要。
我写了一篇文章讨论这一点:使用 Scikit-learn 的多项式回归:你应该知道的事项。
多项式回归的一个关键方面是多项式的次数,通常被视为模型的超参数。然而,我们不应忘记次数并不是唯一可能的超参数。实际上,当你分两步进行多项式回归时,更容易理解。
-
多项式特征:我们可以自定义特征,甚至手动添加某些变量之间的交互。然后我们可以缩放特征或使用其他技术,如 QuantileTransformer 或 KBinsDiscretizer。
-
模型:在模型部分,我们还可以选择像岭回归、套索回归,甚至支持向量回归这样的模型,而不是线性回归。
这是一个示例,说明超参数 alpha 对岭回归模型的影响。

多项式回归的过拟合与 alpha 岭回归 — 作者提供的图像
现在,你可能很高兴你刚刚学会了如何创建一些新的、更有效的模型。但它们仍然不够实用。也许你们中的一些人会发现这实际上是我们已经在做的事情,只不过方法不同……没错,就是核方法!
3.3 多项式核及其他
多项式回归的常见形式在实际中并不方便实现,但理论上,这是一种基于数学函数创建非线性模型的有效方法。另一种方法是创建神经网络。
一个原因是,当创建多项式特征时,特征数量可能会非常庞大。拟合线性回归可能非常耗时。这就是为什么支持向量回归或支持向量机会发挥作用,因为铰链损失函数允许通过仅保留所称的“支持向量”来“丢弃”许多数据点。
是的,核函数的概念通常与支持向量机或支持向量回归相关,但我们不应忘记这是一个独立的理论,这也是为什么在 scikit-learn 中我们有KernelRidge。理论上,我们也可以有 KernelLasso 或 KernelElasticNet。KernelLogisticRegression 是分类任务中的另一个例子。
结论
总结来说,我们探讨了多项式回归模型及其与线性回归的关系。虽然从统计学角度来看,它通常被视为针对一个特征的单一模型,但它也可以被看作是一个形式上的线性回归,其前面有一个特征工程部分,主要包括创建多项式特征,但这些特征需要进行缩放。此外,我们还可以将其他模型如岭回归、套索回归或支持向量机应用于这些多项式特征,以提升其性能。
最后,多项式回归也作为数学函数模型中特征映射技术的一个典型例子。这 进而引出了核函数,这些函数可以建模特征与目标变量之间的非线性关系。
(向量)索引的隐藏世界
你总是想了解关于(向量)索引的所有信息,但又害怕询问。
·
关注 发表在 Towards Data Science · 11 分钟阅读 · 2023 年 11 月 15 日
--
自从 ChatGPT 公开发布以来,几乎没有一天不讨论 LLM、RAG 和向量数据库。技术界对 LLM 的可能性感到兴奋,这被视为改变我们生活的最新技术:对一些人来说是最好的,对另一些人来说是最坏的。与此同时,检索增强生成(RAG)作为一种动态解决方案应对不断变化的知识领域。但在幕后有一个关键的角色:向量索引和数据库。
尽管 LLM、RAG 和向量数据库被广泛讨论,但支持这些创新的(向量)索引却鲜为人知。在这篇文章中,我们将揭示索引的概念,帮助你理解索引如何让在浩瀚的数据集中查找信息变得轻松自如。
1. 什么是索引?
我们都遇到过这种情况。你正在你朋友家见面。她给你的唯一信息是“我住在地铁镇区”。当你到达所说的地铁镇区时:

摄影师 Manson Yim 作品,来自 Unsplash
好吧,没有任何帮助的话,找到她的地方会花费一段时间!如果入口处有一张地图就好了……
这正是索引的目的:如何快速找到人们(或数据)的位置。
黄页 是一个索引,允许你根据人们的名字查找他们的住址。
索引是一种数据结构,用于提高数据检索操作的速度。换句话说,它是你组织信息的方式,以便你能够快速找到你所寻找的内容。
数据是使用键进行索引的。排序基于键,且可以使用多个键。在黄页中,第一个键是姓氏,第二个键是名字。
索引不一定存储所有数据。它只关注于关键部分,以便快速定位和访问整个数据中的特定数据片段。
书籍末尾的索引是一个很好的例子:它向你展示了如何通过单词找到页码,因此它将每个单词映射到页码,而不是句子本身。
索引是搜索引擎和数据库的背后:它们在提高数据检索操作的效率和速度方面发挥了关键作用。
数据组织的选择至关重要,且取决于具体的上下文。
例如,在黄页的例子中,如果索引是按电话号码组织的,而你只知道名字,那么找到他们的地址将会相当具有挑战性!

信息在那里;你最终会找到它,但所需的时间会让你甚至不愿尝试。另一方面,使用黄页,只需一眼查看页面,你就可以准确知道是否需要往前或往后查找!字典顺序允许你进行大致对数级的搜索。这就是为什么索引的选择至关重要。
一般来说,索引有一个非常明确的目的:它可以被设计用于快速插入或检索数据,或者进行更复杂的查询,比如范围查询(“检索从今年 5 月 1 日到 8 月 15 日之间的所有数据”)。优化操作的选择将决定索引的样子。
在线事务处理(OLTP)数据库与在线分析处理(OLAP)数据库之间的主要区别在于它们想要优化的操作:OLTP 侧重于对行的操作(例如更新条目),而 OLAP 则侧重于对列的操作(例如计算*均值)。这两种数据库不会使用相同的索引,因为它们的操作目标不同。
1.1 索引和数据结构之间有什么区别?
💡数据结构是组织和存储计算机数据的一种方式,以便可以高效地访问和操作数据。这样解释,索引和数据结构之间的区别有时很难看出,那么它们的区别是什么?索引专注于插入、搜索、排序或过滤数据。数据结构则更为通用。
索引是使用数据结构构建的,但通常不存储数据本身。
如果你考虑一个电影数据库,你不想在每次索引更新时移动大型文件:你存储的是指针,而不是文件本身。指针可以被看作是文件在磁盘上的地址。
现在你对什么是索引有了一个大概念,让我们专注于数字示例。以下是一些常见的(数字)索引:
-
倒排索引
-
哈希索引
-
B 树
-
局部敏感哈希(Locality-sensitive-Hashing, LSH)。
为了更好地理解索引如何工作,让我们探讨一种最基本的索引:倒排索引。
1.2 倒排索引
倒排索引是一种在搜索引擎中使用的标准索引。
它旨在快速找到信息:其目标是优化检索时间。
简而言之,倒排索引将内容映射到它们的位置,有点像书籍的索引。
它通常用于将特征映射到具有该特征的数据。
例如,假设你想知道谁住在同一栋楼里。
首先,你应该有一个表,其中对于每个名字,你都有建筑物(这个表会帮助你找到爱丽丝):

这个表在每次有人到达或离开地区时都会更新。
如果你想找到谁住在这个表中的 B 栋楼,你必须遍历整个表。
虽然在技术上是可能的,但由于计算时间会随着表的大小线性增加,因此它不会扩展。
想想这个地区的公寓数量:如果你想通过一个个检查所有的档案来找到所有住在 B 栋楼的人,这会花费很长时间!
另一种解决方案是使用倒排索引:你维护一个表,其中建筑物作为键并与住在其中的人连接:

这个表与前一个表同时维护:添加或删除新人的成本比以前稍高,但检索时间几乎缩短为零!
要查找谁住在 B 楼,你只需要访问该表的“Building B”这一行,就能得到结果!
反向电话查询是电话号码的倒排索引!
实际上,倒排索引要复杂一些,因为它们处理的数据比(用户,兴趣)对要复杂得多。索引通常存储为哈希表。
尽管倒排索引相对简单,但它们是搜索引擎中最常用的索引之一。
1.3 索引和数据库
数据库是建立在索引之上的。索引通过存储指针或引用来增强数据库的数据检索。它不存储实际数据,而是作为一种快速访问数据的手段,大大提高了查询性能。
数据库不仅仅是索引,它是一个全面的数据管理系统。它存储、组织和管理实际数据,维护数据完整性,处理事务,并提供超越索引的各种功能,使其成为数据存储和操作的核心枢纽。虽然索引加快了数据库中的数据检索,但数据库作为数据存储、管理和检索的完整生态系统存在。

总结来说,索引就像数据库中的指示牌,指引你找到所需的数据。相反,数据库是实际数据所在的存储库,并配备了各种工具和功能来管理和操作这些数据。
根据你的使用场景,你可能不需要整个数据库,只需要索引,因为管理数据的覆盖层可能会很昂贵。
2. 向量索引和向量数据库
2.1 什么是向量索引?
简而言之,向量索引就是键是向量的索引。
在我们的倒排索引示例中,键是单词(爱好和名字)。在向量索引中,我们处理的是向量:固定大小的数字序列。

两个大小为 4 的向量。
我知道,我知道,我能听到你说,“我数学不好,我不想使用向量”。
不用担心,你不需要擅长数学就能理解向量索引。
你只需要知道的是,使用向量可以依靠强大且优化的操作。
你可能会问自己第一个问题是,“你的向量有什么有趣之处”?
假设你终于找到了 Alice 的住处,现在你想找点东西吃。你可能会想找最*的餐馆。你会找一份餐馆列表,结果得到一张包含餐馆、特色菜和地址的表格。让我们来看看你可以找到的信息:

这看起来没什么帮助,对吧?你唯一的选择是逐一扫描列表,阅读地址,并手动评估它与你的距离有多*。我们可以尝试自动化最*地点的排名,但基于原始地址计算距离是困难的(两条街道可能相*但名称不同)。
不过,假设你现在有一个表,其中 GPS 位置表示每个餐馆的精确纬度和经度:

每个位置是一个大小为 2 的向量。利用这些向量,你可以通过一个简单的 -而且快速- 数学操作轻松计算到自己位置的距离。然后你可以迅速检索到最*的那些,也就是说,离你最*的那个!

现在你可以轻松找到离你最*的餐馆了!
有趣的是,通过直接按向量(在这个例子中是 GPS 位置)索引表,我们可以优化索引,使得查找距离最小的条目变得极其快速。
向量索引是专门设计用于高效检索最接*或最相似于给定向量的向量的索引。这些索引依赖于优化的数学操作来高效地识别最相似的向量。
在我们的例子中,使用的距离是经典距离,但对于所有现有的距离或相似性指标都有索引,例如余弦相似性度量。
局部敏感哈希(LSH) 是最广泛使用的索引之一,用于在数据集中找到 k 个最相似的数据点,并且它可以与不同的距离或相似性度量一起使用。
“这很好,但我在数据库中没有使用向量”。
这就是激动人心的部分:你可以将任何东西转换成一个向量。
仅仅使用二进制表示会很低效,因为它可能包含噪声,因此找到能够保留数据特征的表示是至关重要的。
将不同的信息表示为向量以使用向量索引已经成为提高系统效率的标准方法。向量化已经成为一门艺术。
例如,如果你有一个图像数据集,并且想要一个可以找到与给定图像最相似的图像的数据库,你可以使用图像的 SIFT 描述符。
2.2 向量索引和向量数据库之间有什么区别?
向量索引和向量数据库之间的区别就像索引和数据库之间的区别一样:索引旨在快速找到数据的位置,而向量数据库使用向量索引快速执行检索查询,但它们还存储和维护数据,同时提供附加操作和属性。
3. LLMs 和 RAGs 之间的联系是什么?
现在你已经了解了向量索引,你可能会想为什么关于 LLMs 和 RAGs 的讨论也涉及到向量索引。要理解原因,我们首先快速解释一下什么是检索增强生成,或者说 RAG。RAG 作为 LLMs 固有局限性——即知识有限性——的巧妙解决方案。
LLMs 只知道它们接受训练的数据。提高它们知识的一种技术是提示工程,即将额外的数据整合到查询提示中:“给定这些数据{data},回答这个问题:{question}”。
尽管有效,但这种方法面临一个新挑战:可扩展性。不仅提示的大小有限,包含的数据越多,查询的成本也就越高。
为了克服这个问题,检索增强生成通过只插入最相似的数据来限制数据的数量,这就是向量索引发挥作用的地方!
它的工作原理是:所有文档最初都通过 LLMs(1)转换为向量。具体来说,是 LLM 的编码器部分被使用。
这些向量被用作在向量索引中索引文档的键(2)。
在进行查询时,查询会使用 LLM 进行向量化(3)。然后,将结果向量在向量索引中查询,以检索最相似的文档(4)。这些文档随后会通过提示工程(5)来回答查询。

检索增强生成(RAG)依赖于 LLMs 和向量索引。
就这些!
正如你所见,类似于 LLM,向量索引在 RAGs 中也占据着核心位置。
有些人更喜欢使用向量数据库而不是向量索引。这在你想在多个应用中重用相同数据时没问题。然而,如果你主要关注的是检索效率或在每个应用中定义索引的灵活性,那么单个向量索引通常更简单、更快速。
结论
恭喜那些勇敢地读到这里的人!我相信你现在已经具备了参与关于 LLMs 和 RAGs 的热情讨论所需的所有背景知识。
索引在数据检索中扮演着核心角色。由于数据检索可能仍将是数据技术中的关键组成部分,因此理解包括向量索引在内的索引是至关重要的。
如果你想了解更多高级索引,我建议你阅读我关于 LSH 的文章。如果你想了解一些更实用的内容,并且对实时检索增强生成(RAG)的实际应用感兴趣,可以考虑探索LLM-app,在这里你可以亲身体验这些技术的力量。
公有云上的机器学习工具层次结构
并非所有机器学习服务都是一样的
·
关注 发表在 Towards Data Science · 8 分钟阅读·2023 年 3 月 21 日
--
机器学习系统中的隐藏技术债务。图片来自Google Developers。
# 1 公有云上的机器学习服务
并非所有机器学习服务都是一样的。作为一名在公有云上工作的顾问,我可以告诉你,人工智能(AI)/机器学习(ML)工具在三大公有云——Azure、AWS 和 GCP——上可供选择的范围广泛。
处理和综合信息的潮流可能让人感到不知所措,尤其是当这些服务不断推出新功能时。
想象一下,向外行解释选择哪个*台,并说明为什么选择这个特定工具来解决你的机器学习问题,将会是多么棘手的事情。
我写这篇文章是为了帮助其他人以及我自己缓解这个问题,以便你能清晰简洁地了解公共云提供了什么。为了简化起见,我将在整个文章中将 AI 和 ML 交替使用。
# 2 建立定制机器学习系统……应该是最后的选择
在我们深入工具比较之前,先来了解一下为什么我们应该使用公共云上的托管服务。这个问题是合理的,值得质疑——为什么不从头开始构建自己的定制基础设施和机器学习模型?为了回答这个问题,我们来快速看看机器学习的生命周期。
下面的图示展示了一个典型的机器学习生命周期(这个循环是迭代的):

机器学习生命周期。图片来源:作者。
正如你所看到的,整个生命周期有很多部分需要考虑。
Google 发表的一篇著名论文显示,在构建可维护的生产环境中的机器学习模型时,投入的少量精力是编写模型训练代码。
这种现象被称为生产环境中机器学习系统的隐性技术债务,也被业内称为机器学习运维(MLOps),这是一个涵盖上述技术债务的统称。
下面是一个支持上述统计数据的视觉解释,改编自 Google 的论文:

机器学习系统中的隐性技术债务。图片来源:Google 开发者。
我不会详细解释生命周期中的每个阶段,但这里有一个总结的定义列表。如果你有兴趣了解更多,我推荐阅读《机器学习设计模式》第九章关于机器学习生命周期和 AI 准备性的详细解答。
机器学习生命周期总结定义:
-
数据预处理 —— 为机器学习训练准备数据;数据管道工程
-
特征工程 —— 将输入数据转化为与机器学习模型学习目标密切对齐的新特征
-
模型训练 —— 训练和初步验证机器学习模型;迭代算法,进行训练/测试分割,执行超参数调优
-
模型评估 —— 根据预定的评估指标评估模型性能
-
模型版本控制 — 模型工件的版本控制;模型训练参数、模型管道
-
模型服务 — 通过批处理或实时推理提供模型预测
-
模型部署 — 自动构建、测试、部署到生产和模型重新训练
-
模型监控 — 监控基础设施、输入数据质量和模型预测
不要忘记*台基础设施和安全性!
ML 生命周期不考虑支持*台基础设施,这些基础设施必须在加密、网络和身份与访问管理(IAM)方面保持安全。
云服务提供托管计算基础设施、开发环境、集中 IAM、加密功能和网络保护服务,这些可以实现与内部 IT 政策的安全合规 —— 因此你真的不应该自己构建这些 ML 服务,而是利用云的力量将 ML 能力融入你的产品路线图。
本节说明了编写模型训练代码只是整个 ML 生命周期中的一个相对微小的部分,实际的数据准备、评估、部署和生产中的 ML 模型监控都非常困难。
自然地,结论是构建自己定制的基础设施和 ML 模型需要相当的时间和精力,这样做的决定应当是最后的手段。
# 3 ML 工具层级
这里是利用公共云服务来填补空白的地方。这些超大规模提供商主要提供两种服务;ML 工具层级:
- 🧰 AI 服务。或者:
-
🔨
**预训练标准**- 仅使用基础模型,无法通过带来自己的训练数据进行自定义。 -
⚒️
**预训练可定制**- 可以使用基础模型,且通过带来自己的训练数据进行可选定制。 -
⚙️
**带来自己的数据**- 强制要求带来自己的训练数据。
- 🪛 ML *台。
荣誉 AI 服务提及
对于那些阅读此帖的更敏锐的读者,我故意省略了一些在层次结构中的荣誉 AI 服务提及:
-
内置 ML 模型的数据仓库,通过 SQL 语法实现 ML 开发。进一步阅读可以参考 BigQuery ML, Redshift ML, 和 Synapse 专用 SQL 池 PREDICT 函数。这些服务旨在供数据分析师使用,前提是你的数据已在云数据仓库中。
-
AI Builder 适用于 Microsoft Power Platform,以及 Amazon SageMaker Canvas。这些服务旨在供非技术性业务用户即公民数据科学家使用。
-
Azure OpenAI 是一个新兴服务,由微软监管;你需要申请试用批准。
# 3.1 ML *台 🪛
我们将首先讨论 ML *台,然后讨论 AI 服务。该*台提供 MLOps 所需的辅助工具。
每个公共云都有自己版本的 ML *台:
-
Azure — Azure Machine Learning
-
AWS — Amazon SageMaker
-
GCP — Vertex AI
它适合谁?
就角色而言,这是为拥有内部数据科学家资源的团队准备的,他们希望使用自己的训练数据构建自定义的最先进(SOTA)模型,并开发框架以在 ML 生命周期中进行自定义 MLOps 管理。
我该如何使用它?
从需求角度来看,业务用例需要他们设计一个自定义 ML 模型实现,而 AI 服务在 3.2 节 中无法满足这些能力。
在寻找公共云上的服务时,这不应该是你的首选。
即使有了 ML *台,仍然需要投入大量时间和精力来学习 ML *台的功能,并编写代码以使用超大规模软件开发工具包(SDKs)构建自定义 MLOps 框架。
相反,请首先查看下一节 3.2 中的 AI 服务,看是否能满足你的需求。
该服务提供了哪些技术能力?
当你使用云*台时,你可以获得一个完全由超大规模管理的环境,否则你可能会为此抓狂:
-
托管计算基础设施 — 这些是具有默认环境的机器集群,包含普遍存在的内置 ML 库和云原生 SDK。计算资源可用于分布式训练,或为批量和实时预测提供模型端点。
-
托管开发环境 — 以笔记本的形式提供,或者通过你选择的 IDE,只要它与 ML *台集成。
这些工具使数据科学家和 ML 工程师能够完全专注于 ML 生命周期,而不是基础设施配置和依赖管理。
内置库和云原生 SDK 使数据科学家能够编写自定义代码,在整个 ML 生命周期中实现更无缝的工程。
下表显示了每个云 ML *台的技术特性:
ML *台比较表。Gist 由 作者 提供。
# 3.2 AI 服务 🧰
接下来,我们将讨论 AI 服务。
它们使用低代码/无代码的方法来促进 ML 开发,并减轻管理 MLOps 的开销。
Jeff Atwood 对这些服务的总体论点如下:
最好的代码是没有代码。
每一行你自愿编写的代码,都是需要调试的代码,需要阅读和理解的代码,需要支持的代码。每次你编写新代码时,都应该是勉强的,在压力下,因为你已经完全耗尽了所有其他选项。
这适合谁?
就人员而言,这些适用于没有以下资源的团队:
-
内部数据科学家资源。
-
使用自己的训练数据训练自定义 ML 模型。
-
投资资源、精力和时间来端到端地工程化一个自定义 ML 模型。
我该如何使用?
从需求角度来看,ML 业务应用场景可以通过云提供商的 AI 服务能力来满足。
目标是通过利用大规模服务商的基础模型和训练数据将 ML 功能添加到产品中;因此,团队可以优先开发核心应用程序,通过从 API 端点检索预测结果与 AI 服务集成,并最终在模型训练和 MLOps 上花费最少的精力。
该服务提供了什么技术能力?
我们将根据 AI 服务提供的技术能力组织以下比较表。这与 ML 业务应用场景密切相关,但应有所区分。
例如,Amazon Comprehend 服务提供了文本分类能力。该能力用于构建业务应用场景模型,如:
-
客户评论的情感分析。
-
内容质量审核。
-
将多类项分类到自定义定义的类别中。
对于某些 AI 服务,技术能力和业务应用场景完全相同;在这种情况下,AI 服务是为解决该特定 ML 业务应用场景而构建的。
行业特定版本的 AI 服务
请注意,我已经排除了或避免提及行业特定版本的 AI 服务。只需知道,大规模服务商训练模型以在这些领域实现更高的模型性能,您应该在特定行业或领域使用它们,而不是通用版本的服务。
这些服务的显著提及包括 Amazon Comprehend Medical、Amazon HealthLake、Amazon Lookout for**{domain}**、Amazon Transcribe Call Analytics、Google Cloud Retail Search 等。
以下图例和表格展示了每个云 AI 服务的技术能力:
-
🔨
**预训练标准**- 仅使用基础模型,无选项通过自定义训练数据进行定制。 -
⚒️
**预训练可定制**- 可以使用基础模型,并通过提供自己的训练数据进行可选的定制。 -
⚙️
**自带数据**- 必须提供自己的训练数据。
**--- 语音 ---**
语音 AI 比较表。摘要由 作者 提供。
**--- 自然语言 ---**
自然语言 AI 比较表。摘要由 作者 提供。
**--- 视觉 ---**
视觉 AI 比较表。摘要由 作者 提供。
**--- 决策 ---**
决策 AI 比较表。摘要由 作者 提供。
**--- 搜索 ---**
搜索 AI 比较表。摘要由 作者 提供。
# 4 个进一步阅读的主题
在这篇文章中,我们已经涵盖了公共云提供的 ML 服务的广泛内容,但在构建 ML 系统时,还有其他概念需要考虑。
我鼓励你探索并找到你自己的答案,尤其是那些未被讨论的概念,因为 AI / ML 正在越来越深入地融入我们使用的产品中。
三个公共云提供哪些 ML 工具以实现以下功能?
-
模型数据血统和来源
-
模型目录
-
预测后地面真实标签的人工审核
-
处理视频数据的模型
-
执行通用回归和分类的模型
特别感谢 / 参考资料
特别感谢以下资源的作者和创作者,他们帮助我撰写了这篇文章:
ML 工具
AI 服务
ML *台
开源 LLMs 的历史:更好的基础模型(第二部分)
原文:
towardsdatascience.com/the-history-of-open-source-llms-better-base-models-part-two-6ca51ae74ebe
如何让 LLaMA、MPT、Falcon 和 LLaMA-2 使开源 LLMs 声名鹊起…
·发表于 Towards Data Science ·阅读时间 16 分钟·2023 年 11 月 18 日
--

(照片由 Iñaki del Olmo 提供,刊登在 Unsplash)
对大型语言模型(LLMs)的开源研究极其宝贵,因为它旨在使这一强大而有影响力的技术民主化。尽管开源 LLMs 现在被广泛使用和研究,但这一领域的研究在最初遇到了一些难以克服的困难。具体来说,开源 LLMs 最初表现不佳,并遭到了大量批评。在本概述中,我们将探讨一系列研究,这些研究通过让高性能的预训练 LLMs 对所有人开放,改变了这种局面。由于预训练语言模型的成本极高,我们将研究的模型尤其具有影响力。在这些高性能基础模型创建并发布后,许多人能够以极低的额外成本使用这些模型进行研究。
“考虑到训练方法的表面简单性,LLMs 的能力确实非常出色。” — 摘自 [14]
当前系列。 本概述是关于开源 LLM 历史的三部分系列中的第二部分。系列中的第一部分概述了创建开源 LLM 的初步尝试。在这里,我们将研究目前可用的最受欢迎的开源基础模型(即已经预训练但未经过微调或对齐的语言模型)。下一次,我们将探讨如何微调或对齐这些模型以创建各种有用的应用。

(来源于 [10, 12, 14, 15])
开源 LLM 的早期日子
在本系列的第一部分中,我们看到早期的开源 LLM 研究结果提出了几个重要的基础模型,例如 OPT 和 BLOOM。然而,这些模型普遍被认为相比于闭源的预训练模型(例如 GPT-3)表现较差。我们该如何解决这个问题? 首先,我们需要深入了解 LLM 的训练过程。
训练流程。 LLM 的训练分为几个步骤,如下图所示。首先,我们在大量原始文本上对模型进行预训练。然后,我们使用如 SFT 和 RLHF 等技术进行对齐。最后,我们可以进行进一步的微调或上下文学习,将 LLM 专门化为特定任务。

(由作者创建)
最*,我们已经看到有力的实证证据表明,大多数语言模型的知识是在预训练过程中获得的。对齐过程只是教会模型如何正确地格式化或展示这些在预训练过程中获得的知识。正如 LIMA [3] 所提出的,这个观点被称为“表面对齐假说”。尽管这个假说似乎与本概述的主题并不完全相关,但我们从中学到了一些重要的东西——经过不足预训练的模型不太可能通过微调或对齐“修复”。
“模型的知识和能力几乎完全在预训练期间获得,而对齐则教会它在与用户互动时应使用哪个子分布格式。” — 引自 [3]
解决方案是什么? 鉴于最初的开源 LLM 性能较差,很快就明确了社区需要从头开始重新创建更高质量的基础模型,才能取得任何进展。此外,这些模型还需要在更多的数据上进行预训练,以提高其性能。由于预训练成本极高(特别是当数据量很大时),这种努力并不简单。创建更好的开源基础模型必须由拥有足够资金的组织进行(例如,Meta 或 MosaicML),这些组织能够支付训练这些模型的费用,并将其免费提供给社区中的其他人。
朝着更好的基础模型迈进
开源 LLM 的性能最初过于差劲,不足以支撑广泛使用和探索,但这个问题很快得到了解决。在这里,我们将回顾几种通过提供强大的预训练 LLM 来改变这一局面的模型。
LLaMA:开源质量的飞跃
LLaMA [1] 是首批发布的高性能且开源的预训练 LLM 之一。然而,LLaMA 不仅仅是一个单一的模型,而是一组不同的 LLM,其规模从 70 亿到 650 亿参数不等。这些模型在性能和推理效率之间实现了不同的权衡。尽管 LLaMA 不能用于商业用途(即仅限于研究),但它仍然是一个具有深远影响的提案,推动了开源 LLM 研究的多个方向。

(来自 [1])
数据。 受到 Chinchilla [2] 的启发,LLaMA 模型在一个包含超过 1.4 万亿个文本标记的语料库上进行预训练。这个预训练数据集明显大于任何之前的开源 LLM。数据的来源和分布如上所示。有趣的是,LLaMA 完全使用公开可用的数据源进行预训练,这意味着任何拥有足够计算资源的人都可以复制整个预训练过程。
“GPT-4 从各种许可的、创造的和公开可用的数据源中学习,这些数据源可能包括公开可用的个人信息。” — 引自 GPT-4 博客
鉴于许多专有的大型语言模型(LLMs)是使用内部数据进行训练的,而这些数据并不公开,因此这种特性尤为可取。简单来说,LLaMA 是在多个方面朝着更高透明度和开放性迈出的重要一步。

(来自 [1])
性能提升。 与其前身相比,LLaMA 在开源 LLM 的性能上有了巨大的飞跃。尽管如此,其质量仍落后于顶级专有 LLM(例如,ChatGPT 或 GPT-4),但我们应当记住 LLaMA 模型尚未进行对齐。值得注意的是,LLaMA-13B 的性能与 GPT-3 [3] 相当,而 LLaMA-65B 在多个情况下超越了 PaLM [4],这表明 LLaMA 套件的性能与其他广泛使用的基础模型相当。详细的指标见上表。

(来自 [5, 6, 7, 8])
开源爆发。 LLaMA 提案中最有趣的方面之一是随之而来的开源 LLM 研究浪潮;见上文。在 LLaMA 模型的权重公开后,开源研究社区迅速开始发布各种不同的模型变体和软件包。这些进展包括从 LLaMA 的微调版本到一个用于在笔记本电脑上高效运行任何 LLaMA 模型的 C++ 库。这些进展真正展示了研究开放性的美好。我们仅仅用了几周时间,就从通过 API 互动这些强大的模型,转而在我们的笔记本电脑上运行它们!
MPT:高质量、商业化和开源的 LLM

(来自 [10])
尽管 LLaMA 令人印象深刻,但该套件中的模型都无法用于商业应用——它们仅从研究的角度具有价值。幸运的是,LLaMA 的提案很快被 MosaicML 开发并发布的商业可用(即,按照 Apache 2.0 许可 发布)的 MPT 套件所跟进。首先发布的是 MPT-7B [9],它引起了大量关注(即,它基本上是 LLaMA-7B 的商业可用替代品!)。实际上,在更大的 MPT-30B [10] 模型发布之前,MPT-7B 在 HuggingFace 上的下载量超过了 300 万次!

(来自 [9, 10])
这两个模型之间的主要区别是:
-
它们使用略有不同的数据混合进行预训练;见上文。
-
MPT-30B 采用了更长的上下文长度,达到 8K 令牌。
然而,这些模型都表现出色,并可以用于商业应用,这使它们在人工智能社区中变得非常受欢迎。

(来自 [9])
MPT 是否符合炒作? 尽管 LLaMA 显著提升了开源 LLM 的最先进性能,但 MPT 套件与此性能不相上下。特别是,MPT-7B 在各种标准基准测试中与 LLaMA-7B 的表现相匹配;见上文。更进一步,MPT-30B 的表现趋向于与 GPT-3 相当。与同样大小的开源模型(例如 LLaMA-30B 和 Falcon-40B)相比,MPT-30B 的表现略逊一筹;见下文。然而,它在与编码相关的任务中优于这些模型,并且可以在单个 GPU 上运行(通过量化)。

(来自 [10])
MPT 变体。 除了预训练的 MPT-7B 和 MPT-30B 模型,还发布了各种微调的 MPT 模型,例如 instruct 和 chat 版本的 MPT 模型。此外,通过对具有 64K token 上下文长度的数据进行微调,还创建了 “StoryWriter” 版本的 MPT-7B。鉴于预训练 LLM 比微调要昂贵得多,因此可以以边际成本创建各种不同的微调 MPT 变体;见下文。

(来自 [9])
但等一下…还有更多! MPT 模型非常有用(尤其是对于从事商业应用的人),但这些模型还伴随着一整套软件(即,LLM 工厂),由 MosaicML 发布。这些开源代码可以用于预训练和微调 MPT 模型,使 MPT 套件成为探索 LLM 特殊用例的极其宝贵的工具。
Falcon:在开源性能方面达到新高度

(来自 [1])
尽管在开源 LLM 领域取得了许多进展,但可用的模型在性能上仍然落后于专有 LLM 一段时间。Falcon 套件的提出 [11] 是开源替代方案首次真正与专有 LLM 的质量相匹敌。Falcon 提供了两种变体 — Falcon-7B 和 Falcon-40B。除了商业许可外,这些 Falcon 模型由于在大规模、自定义策划的语料库上进行预训练,表现非常出色。值得注意的是,Falcon-40B 的 instruct 变体在 OpenLLM 排行榜 上连续几个月是表现最佳的模型(差距显著)。
“挑战现有的数据质量和 LLM 信念,仅凭经过适当筛选和去重的网络数据训练的模型可以匹敌使用策划数据训练的模型。” — 来自 [12]
从网络中策划数据。 Falcon 模型是在一个名为 RefinedWeb [12] 的大规模文本语料库上训练的,该语料库包含超过 5 万亿个标记的文本。实际上,仅有 1.5 万亿个标记和 1 万亿个标记的 RefinedWeb 被用于分别预训练 Falcon-7B 和 Falcon-40B。尽管大多数 LLM 是在公开策划的数据源上进行预训练的,但 Falcon 的作者选择仅使用来自网络的数据(即 CommonCrawl)构建自己的预训练数据集。为筛选这些数据,创建了一个新的流程,强调简单但有效的组件;见下文。

(来自 [12, 13])
RefinedWeb 语料库表明,可以从网络中高效策划出大量高质量的文本数据——超出了以前探索的数据集的规模。在应用过滤后,基于这些数据训练的模型甚至可以超越在策划数据源上训练的类似模型。

(来自 [12])
用于训练 Falcon-7B 和 Falcon-40B 的确切数据集如上所示。值得注意的是,Falcon-7B 仅在英语数据上进行训练,而 Falcon-40B 的预训练集中则包含了各种欧洲语言的数据。
新的 SOTA。 目前尚未发布关于 Falcon LLM 的任何出版物。因此,对这些模型的唯一正式评估是通过 OpenLLM 排行榜进行的,其中 Falcon-40B 模型表现相当不错。特别是,Falcon-40B-Instruct 曾是领先的模型,显著超越其他模型;见下文。

(来自 Open LLM 排行榜)
从质量上看,一些从业者声称 Falcon-40B 似乎不如 LLaMA 基础模型。尽管了解这些评论是有用的,但这些证据是轶事性和主观的。在标准化的自然语言基准测试中,Falcon LLM 的表现非常出色,使其在开源模型中长时间保持最先进的性能。
LLaMA-2: 当前的最先进技术

(来自 [14])
尽管 Falcon-40B 曾是领先的开源 LLM,但最*发布的 LLaMA-2 模型套件已使该模型失去领先地位。与 LLAMA-1 类似,LLaMA-2 [14] 包含多个不同的 LLM,其规模从 70 亿到 700 亿参数不等,并仅使用公开数据进行预训练。LLAMA-2 模型既有预训练版本也有微调版本6,但由于我们专注于开源基础模型,本文仅涵盖预训练模型。
“已经有公开发布的预训练 LLMs(例如 BLOOM,其性能与封闭预训练的竞争对手如 GPT-3 和 Chinchilla 相当,但这些模型都不适合作为封闭产品 LLMs 的替代品,例如 ChatGPT、BARD 和 Claude。” — 来自 [14]
LLaMA-2 通过发布一套在大规模数据集上进行预训练的更高性能基础模型,继续缩小开源和闭源语言模型之间的性能差距。正如我们将看到的,这些模型仍未达到专有模型的质量,但比之前的任何开源模型都要接*得多。

(来自 [14])
它有什么不同? LLaMA-2 采用了一种与其前身非常相似的方法,除了几个小的(但影响深远的)区别。首先,LLaMA-2 模型在超过 40%更多的数据上进行预训练——总共 2 万亿个标记,而 LLaMA-1 为 1.4 万亿个标记。此外,LLaMA-2 模型使用稍长的上下文长度进行训练,较大的模型在其底层架构中使用了分组查询注意力(GQA)。有趣的是,[14]中的作者指出,LLaMA-2 的预训练设置采样了更多已知更具知识性的来源。这样的变化旨在强调事实来源、增加知识和减少幻觉。

(来自 [15])
什么是 GQA? 如[15]所提出,GQA 是对多头自注意力的修改,可以提高 LLMs 的推理效率。典型的多头自注意力机制有N个查询、键和值头,总共创建N个自注意力头。在 GQA 中,我们将这些N个头分成组,其中键和值头在每组内共享;见上文。这种方法是在普通多头自注意力和多查询注意力之间的插值,后者在所有N个头中使用共享的键和值投影。[15]发现,GQA 在提高推理速度方面与多查询注意力相当,同时保持普通多头注意力的性能。

(来自 [14])
LLaMA-2 确实很出色。 与流行的开源模型(例如 MPT、Falcon 和 LLaMA-1)相比,LLaMA-2 基础 LLMs 表现非常好。事实上,LLaMA-2–70B 在所有考虑的任务中设立了开源 LLMs 的新最佳水*;见上文。然而,值得注意的是,LLaMA-2 因在编码任务上的(相对)较差表现而受到一些批评(例如,HumanEval)。

(来自 [14])
与专有模型相比,LLaMA-2 基础模型的表现较差;见上文。然而,我们应当记住,这一比较是在基础 LLM 与像 GPT-3.5 和 GPT-4 这样的对齐模型之间进行的。与其他流行的基础 LLM(例如,PaLM [4])相比,LLaMA-2 的表现是有利的!
商业许可证。 尽管 LLaMA-1 仅可用于研究,LLaMA-2 则在商业许可证下发布,这意味着— 与 MPT 和 Falcon 类似 — 这些模型可以用于商业应用。然而,LLaMA-2 的许可证并非标准的 Apache 2.0 许可证,它有一些需要从业者考虑的附加条件。最值得注意的是,任何由 LLaMA-2 驱动、每月活跃用户超过 7 亿的实体/应用程序必须从 Meta 处获得使用 LLaMA-2 的许可证。有关 LLaMA-2 许可证的更多信息,请参阅这里。
开源 LLM 的趋势
鉴于 LLaMA、MPT、Falcon 和 LLaMA-2 的表现远超其前身,我们可以合理地问:是什么导致当前一代开源 LLM 表现如此出色? 在这里,我们将快速查看这些模型的一些关键特性,这些特性在推动它们的卓越表现和迅速流行中尤为重要。特别是,这些模型 i) 在大量数据上进行了预训练,以及 ii) 强调推理效率。
更好的数据 = 更好的表现!
当前开源 LLM 与之前的 LLM 之间的关键区别在于用于预训练的数据集。尽管像 OPT 和 BLOOM 这样的模型分别在 1800 亿和 3410 亿个标记上进行训练,但当前开源模型是在显著更大的数据集上进行预训练的:
-
LLaMA: 1.4 万亿个标记
-
MPT: 1 万亿个标记
-
Falcon: 1–1.5 万亿个标记
-
LLaMA-2: 2 万亿个标记
当前的开源 LLM 将用于预训练的数据量提高了(几乎)一个数量级!事实上,这些预训练数据集的规模与用于专有 LLM 的数据集相似。例如,MassiveText(即用于训练 Gopher [13]和 Chinchilla [2])包含大约 2.3 万亿个标记,尽管实际上仅使用了其中的一部分进行预训练;见下文。

(来源 [13])
规模并非一切! 除了显著增加预训练数据的数量,当前的开源 LLM 还非常关注数据的组成和质量。例如,在用于训练 MPT 的数据集中,代码的比例得到了增加,从而使得生成的模型在基于编码的任务上表现更佳。此外,Falcon-40B 提出了一种全新的从网络构建高质量文本语料库的管道,而 LLaMA-2 则声称使用了更新的数据管道和混合方式进行预训练。总的来说,专注于预训练数据集的质量和组成似乎是*期开源 LLM 研究中的一种共同趋势。
“我们进行了更全面的数据清理,更新了数据混合,训练了更多的总标记,增加了上下文长度,并使用了分组查询注意力(GQA)以提升我们较大模型的推理可扩展性。” — 来源于 [14]
优化以实现更快的推理
在决定使用开源或闭源 LLM 时,实践者必须考虑的不仅仅是性能。付费语言模型 API 可能在广泛的任务范围内取得令人印象深刻的表现,但它们往往无法在特定领域的数据上进行微调。另一方面,使用开源 LLM 构建应用时,一个主要考虑因素是部署模型的成本。鉴于托管 LLM 的难度,*期的开源模型往往经过优化,以实现快速和简便的推理。实际上,MPT-30B [10] 特别调整了尺寸,以便可以在单个 GPU 上托管!

(来源于 [15, 16, 17])
修改后的架构。 除了比大多数专有模型稍小之外,当前的开源 LLM 还采用了多种架构技巧 —— 如上图所示 —— 来加速推理过程,例如:
-
低精度层归一化
-
快速注意力
-
多查询注意力
-
并行变换器
-
组查询注意力
此外,还采用了多种其他架构修改,例如 RoPE 嵌入、ALiBi、SwiGLU 激活函数等,以提升性能。当前的开源 LLM 对仅有解码器的变换器架构进行了简单的修改,以改善性能和推理速度。
结论
在本概述中,我们研究了开源 LLM 的演变,从初期的低质量模型(例如 BLOOM 和 OPT)到最*的强大基础模型(例如 LLaMA 和 MPT)。为了提升前辈模型的性能,这些最*的模型主要集中于策划更大、更高质量的预训练数据集,这导致了质量的显著提高。鉴于高质量的基础模型是任何 LLM 应用的前提,这些模型对开源 LLM 的流行程度产生了重大影响。如今,任何从业者都可以利用强大的基础 LLM,无论是用于研究目的还是商业应用,而无需投入大量资金从头开始预训练模型。
与我联系!
非常感谢阅读本文。我是 Cameron R. Wolfe,Rebuy 的 AI 总监。我研究深度学习的实证和理论基础。如果你喜欢这个概述,请订阅我的 Deep (Learning) Focus 时事通讯,在这里我帮助读者通过从基础到高级的相关主题概述来理解 AI 研究。你也可以在 X 和 LinkedIn 上关注我,或者查看我在 medium 上的 其他文章!
参考书目
[1] Touvron, Hugo 等人。“Llama: 开放且高效的基础语言模型。” arXiv 预印本 arXiv:2302.13971 (2023)。
[2] Hoffmann, Jordan 等人。“训练计算最优的大型语言模型。” arXiv 预印本 arXiv:2203.15556 (2022)。
[3] Zhou, Chunting 等人。“Lima: 对齐的‘少即是多’。” arXiv 预印本 arXiv:2305.11206 (2023)。
[4] Chowdhery, Aakanksha 等人。“Palm: 使用路径扩展语言建模。” arXiv 预印本 arXiv:2204.02311 (2022)。
[5] Taori, Rohan 等人。“斯坦福 Alpaca: 一种遵循指令的 LLaMA 模型。” (2023)。
[6] Chiang, Wei-Lin 等人。“Vicuna: 一个以 90%* ChatGPT 质量给 GPT-4 留下深刻印象的开源聊天机器人。” (2023)。
[7] Geng, Xinyang 等人。“Koala: 一种用于学术研究的对话模型。” (2023)。
[8] Yuvanesh Anand, Zach Nussbaum, Brandon Duderstadt, Benjamin Schmidt 和 Andriy Mulyar。“GPT4All: 通过从 GPT-3.5-Turbo 大规模数据蒸馏训练助理风格聊天机器人。” 2023 年。
[9] “介绍 MPT-7B: 开源、可商业化 LLM 的新标准。” MosaicML, 2023 年 5 月 5 日, www.mosaicml.com/blog/mpt-7b.
[10] “MPT-30B: 提高开源基础模型的标准。” MosaicML,2023 年 6 月 22 日,www.mosaicml.com/blog/mpt-30b.
[11] “介绍 Falcon LLM”, 技术创新研究所,2023 年 6 月 7 日,falconllm.tii.ae/.
[12] Penedo, Guilherme 等人. “Falcon LLM 的 RefinedWeb 数据集:通过网络数据超越精选语料库,仅使用网络数据。” arXiv 预印本 arXiv:2306.01116 (2023)。
[13] Rae, Jack W. 等人. “语言模型扩展:方法、分析与从训练 Gopher 中获得的见解。” arXiv 预印本 arXiv:2112.11446 (2021)。
[14] Touvron, Hugo 等人. “Llama 2: 开放基础与微调聊天模型。” arXiv 预印本 arXiv:2307.09288 (2023)。
[15] Ainslie, Joshua 等人. “GQA: 从多头检查点训练通用多查询变换器模型。” arXiv 预印本 arXiv:2305.13245 (2023)。
[16] Vaswani, Ashish 等人. “注意力即你所需。” 《神经信息处理系统进展》 30 (2017)。
[17] Dao, Tri 等人. “Flashattention: 快速且内存高效的精确注意力机制,具备 IO 感知功能。” 《神经信息处理系统进展》 35 (2022): 16344–16359。
[18] Dao, Tri. “FlashAttention-2: 更快的注意力机制,具有更好的并行性和工作分区。” arXiv 预印本 arXiv:2307.08691 (2023)。
开源 LLMs 的历史:早期阶段(第一部分)
原文:
towardsdatascience.com/the-history-of-open-source-llms-early-days-part-one-d782bcd8f7e8
理解 GPT-Neo、GPT-J、GLM、OPT、BLOOM 等…
·发表在 Towards Data Science ·阅读时间 20 分钟·2023 年 11 月 7 日
--

(图片由 Chris Lawton 提供,来自 Unsplash)
语言建模的研究有着悠久的历史,可以追溯到类似 GTP 和 GPT-2 的模型,甚至是早于现代变换器基础语言模型的 RNN 技术(例如,ULMFit)。尽管历史悠久,但语言模型在相对最*才变得流行。第一次流行的兴起来自于 GPT-3 的提出,[1]展示了通过自监督预训练和上下文学习的结合,可以在许多任务中实现令人印象深刻的少量学习性能;见下文。

(来自 [1])
在此之后,GPT-3 获得的认可促使了大量大型语言模型(LLMs)的提出。不久之后,语言模型对齐的研究导致了更为出色的模型的创建,如 InstructGPT [19],以及最著名的其姊妹模型 ChatGPT。这些模型的出色表现引发了对语言建模和生成式 AI 的广泛兴趣。
尽管非常强大,但许多早期 LLM 研究中的发展有一个共同特点——它们是闭源的。当语言模型首次开始获得广泛认可时,许多最强大的 LLM 只能通过付费 API 访问(例如,OpenAI API),而研究和开发这些模型的能力仅限于特定的个人或实验室。这种方法明显不同于典型的 AI 研究实践,后者通常鼓励开放和思想共享,以促进前进的进步。
“这种受限的访问限制了研究人员理解这些大型语言模型如何以及为何工作的能力,阻碍了改进其鲁棒性和缓解已知问题(如偏见和毒性)的努力。” ——来自 [4]
这一概述。 尽管最初强调的是专有技术,LLM 研究社区慢慢开始创建流行语言模型如 GPT-3 的开源变体。尽管首批开源语言模型在性能上落后于最佳的专有模型,但它们为 LLM 研究中的改进透明度奠定了基础,并催化了许多更强大的后续模型的发展(例如,Falcon [10] 和 LLaMA-2)。
这一概述是一个三部分系列的一部分,探索开源语言模型的历史。在这里,我们将了解这一历史的开端,包括若干初步尝试创建开源语言模型的努力。尽管这些模型在性能上有所欠缺,但它们对理解至关重要,因为随后的开源 LLM 革命完全基于这些模型。在系列的接下来的两部分中,我们将深入了解最*的开源 LLM 以及如何使用模仿和对齐技术来提高它们的性能。

(来自 [12, 20])
语言模型的机制
开源 LLM 研究催化了透明度和思想共享,创造了一个研究人员能够更快地合作和创新的环境。简单来说,开源 LLM 研究的美在于它让我们有可能研究这些令人难以置信的模型,并深入理解它们的工作原理。没有隐藏在付费 API 或黑箱中的未知技巧。开源 LLM 允许我们查看代码,进行实验,甚至尝试我们自己的想法和修改——我们对基础模型拥有完全的访问权限!
“AI 社区的更广泛领域需要访问这些模型,以便进行可重复的研究,并共同推动该领域的发展。” ——来自 [4]
但要深入理解这些模型,我们首先需要了解它们的基本工作原理。在本节中,我们将概述这些理念,尝试提供对大型语言模型(LLMs)的(相对)全面理解。
语言建模目标

使用语言建模目标进行预训练(由作者创建)
语言建模的核心是下一个令牌预测(也称为标准语言建模目标),它用于训练几乎所有的语言模型。为了使用下一个令牌预测训练语言模型,我们需要一个大规模的原始文本语料库。利用这个语料库,我们通过* i)* 从数据集中抽取一些文本和* ii)* 训练模型以预测下一个单词来训练模型;见上文。因为真实的下一个令牌总是可以从原始文本中推断出来,所以下一个令牌预测是一种自监督学习的方法。
什么是令牌? 可以大致将下一个令牌预测理解为在给定前几个单词作为上下文的情况下预测序列中的下一个单词。然而,这种类比并不完全准确,因为令牌和单词并不完全相等。当语言模型接收文本作为输入时,原始文本首先被分词(即,转换为离散的单词或子单词序列);见下文。

将原始文本转换为令牌序列(由作者创建)
与语言模型相关联的分词器通常具有固定大小的词汇表,或可以从文本序列中创建的可用令牌集合。
预测下一个令牌。 一旦创建了令牌序列,语言模型就具有一个嵌入层,为分词器词汇表中的每个令牌存储一个唯一且可学习的向量嵌入。利用这个嵌入层,我们可以将输入序列中的每个令牌转换为相应的向量嵌入,形成令牌向量序列;见下文。

对原始文本数据进行分词和嵌入(由作者创建)
在每个令牌上添加位置嵌入后,我们可以将这个令牌向量序列传递给仅解码器的变换器(更多解释将随后介绍),它将这些令牌向量进行转换(无双关意味),并为每个令牌生成相应的输出向量。值得注意的是,输出向量的数量与输入向量的数量相同;见下文。

使用仅解码器的变换器处理令牌(由作者创建)
现在我们为每个令牌有了输出表示,我们准备进行下一个令牌预测了!对于序列中的每个令牌,我们只需取其输出令牌向量并使用它来预测序列中接下来的令牌!下面展示了这一过程的示意图。在实际应用中,这一目标同时在序列中的所有令牌(以及在一个小批量中的所有序列)上进行计算,以最大化效率。

计算下一个令牌预测训练目标(由作者创建)
由于使用了因果(或掩蔽)自注意,每个输出令牌向量在计算其表示时只考虑当前令牌以及序列中其之前的令牌。如果我们使用双向自注意,每个输出令牌向量将通过查看整个向量序列来计算,这将允许模型作弊,通过仅仅复制序列中接下来的令牌来解决下一个令牌预测。因此,掩蔽自注意是下一个令牌预测所必需的。但是,自注意到底是什么,更根本地说,变换器是什么?接下来我们将深入探讨。
一个快速说明。 “语言模型”这一术语有时可能被用来指代那些不仅专注于执行下一个令牌预测的模型。例如,BERT [18] 被一些人认为是“语言模型”,但它是使用Cloze风格的目标进行训练的,并不是生成模型。因此,专注于下一个令牌预测的语言模型通常被称为“因果”语言模型。在这里,我们将这两个术语交替使用,指代那些专注于下一个令牌预测的模型。
变换器架构及其变体

(来自 [17])
所有语言模型都使用某种变体的变换器架构。这种架构(如上图所示)最初在[17]中提出,用于解决序列到序列任务。然而,它随后被扩展以解决各种不同的问题,从评估文本的语义相似性到图像分类。在其原始形式中,变换器架构有两个组成部分:
-
编码器:每个块执行双向自注意和一个逐点前馈变换,这些操作之间通过残差连接和 LayerNorm 分隔。
-
解码器:每个块执行因果自注意,交叉注意(即跨编码器和解码器令牌的自注意),以及一个逐点前馈变换,每个部分之间通过残差连接和 LayerNorm 分隔。
当架构的两个组件都存在时,编码器处理输入序列并生成一个输出序列。然后,解码器生成自己的输出序列,输入为编码器的输出序列。换句话说,编码器处理整个输入序列以形成解码器在生成输出时使用的表示。整体上,转换器接受一个序列作为输入并生成一个新的序列作为输出。

(来源于 [17])
仅解码器和仅编码器的转换器。 几乎所有因果语言模型都使用仅解码器转换器作为其基础架构,这只是一个普通的转换器,其编码器部分被移除;见上文。此外,由于缺少编码器(即,我们不能关注不存在的编码器),每个解码器块的交叉注意力部分也被移除!或者,可以通过仅使用架构中的编码器部分来形成仅编码器架构。仅编码器架构(例如,BERT [18])在解决各种判别自然语言任务方面表现出色,但它们不用于生成文本。要了解更多信息,请查看这里的链接。
为什么选择解码器? 选择仅解码器架构(与仅编码器或完整的编码器-解码器转换器相比)用于大语言模型并非随意决定。相反,这一选择是由于训练语言模型时使用下一个词预测。解码器中使用的掩码自注意力确保模型在预测下一个词时无法向前查看序列。否则,下一个词预测将变得非常简单,因为模型可以直接复制下一个词;见下文。

因果自注意力用于下一个词预测(作者创建)
为了在不作弊的情况下进行下一个词预测,无论是仅编码器还是编码器-解码器转换器,都必须避免在其输入序列中包含任何真实的下一个词。为此,我们可以 i) 处理一个前缀,并 ii) 预测跟随该前缀的词。然而,这种方法效率较低,因为我们一次只能预测一个下一个词。相比之下,只有解码器的模型由于使用掩码自注意力,可以处理整个词序列,并将语言建模目标应用于序列中的每个词。此外,一些论文 [12] 实际上表明,仅解码器架构在下一个词预测中表现最佳。
我们如何生成文本? 鉴于上面概述的仅解码器架构,生成文本遵循简单的自回归过程。我们只需不断预测下一个词,将此词添加到输入中,并重复进行;见下文。

使用语言模型生成文本(作者创建)
训练和使用语言模型
为了全面理解语言模型,我们需要快速探索这些模型在实际中通常是如何训练和使用的。尽管在这一领域做了大量的研究,但大多数语言模型都是按照 [19] 中提出的一些标准技术进行训练的;详见下文。

LLM 训练组件(来自 [19])
语言模型可以通过多种不同方式进行学习。在这里,我们将重点讨论预训练、对齐和上下文学习,这些方法共同涵盖了训练大型语言模型(LLM)和在实际应用中使用它所需的大部分内容。
预训练。 预训练过程是创建 LLM 的初步且计算开销最大的步骤。从一个随机初始化的 LLM 开始,我们必须使用语言建模目标对这个模型进行训练——在从各种不同来源中整理出的海量原始文本语料库上。之前的研究 [1] 向我们展示,通过在一个大型数据集上对一个非常大的模型(即,参数很多)进行预训练,我们可以获得一个 基础模型,它可以通过执行下一个 token 预测来准确地解决各种不同的任务。为了获得最佳结果,我们需要在数据和模型规模方面进行扩展。
我们还需要什么? 仅仅经历预训练的语言模型可以是强大的。看看 GPT-3 [1] 和 Chinchilla [15] 的一些例子。然而,LLM 直到像 ChatGPT 这样的模型提出后才突然流行起来是有原因的——仅仅进行下一个 token 预测并不是非常有趣。通常,预测统计上正确的下一个 token,虽然生成了合理的文本,却会产生重复、简单且通常不太有用的输出。我们需要某种方法来使 LLM 生成对我们人类更有帮助和更有趣的输出!

(来自 [19])
对齐是指对 LLM 进行微调的过程,以更好地符合人类用户的期望。这主要通过两种技术实现:监督微调(SFT)和/或来自人类反馈的强化学习(RLHF)。LLM 的期望行为很大程度上取决于其部署的上下文或应用。然而,对齐是一种通用工具,可以用来随意地微调 LLM,使其表现出特定的行为;详见上文。*期研究表明,模型在对齐过程中不会学习到新的信息。相反,这个过程只是教会模型如何正确地格式化或呈现其从预训练过程中已经获得的知识。
实际应用 LLM。 在我们对语言模型进行预训练和微调(或对齐)之后,最后一步是将模型专门化为我们所需的应用。这一过程可能需要对领域特定的数据进行额外的微调。然而,并不总是需要更多的训练,因为我们可以通过使用上下文学习来完成很多工作;见下文。

(来自 [1])
简而言之,上下文学习指的是使用一个单一的通用基础模型(例如,预训练的 LLM)来解决各种不同的问题。鉴于语言模型的通用文本到文本结构,这实际上是相当容易做到的。我们只需构建一个可以作为输入提供给 LLM 的文本问题解决提示;见下文。

解决算术问题的不同提示变体(由作者创建)
然后,LLM 应该生成我们问题的答案作为输出。因此,我们可以通过仅仅修改输入提示来解决许多不同的问题!构建有效提示以解决问题的过程称为提示工程,我们在之前的文章中对此进行了广泛探讨:
-
实用提示工程 [link]
-
高级提示工程 [link]
开源 LLM 的初步尝试
鉴于 预训练的高成本,研究社区花了一些时间才开始追求创建开源 LLM,使得像 GPT-3 这样的专有模型成为标准。然而,一旦提出了最初的几个模型,闸门就打开了,开源 LLM 的研究迅速推进(几乎 过于 迅速)。我们将在这里了解一些早期模型,而更多最*的开源 LLM 将在系列的未来部分中介绍。
GPT-NeoX-20B [6]
第一个开源 LLM 之一——一个名为 GPT-NeoX-20B 的 200 亿参数模型[6]——由 EleutherAI 创建。GPT-NeoX-20B 是在初始的 GPT-Neo 模型(27 亿参数)[22] 之后创建的,经过 the Pile 进行预训练,并在各种自然语言基准测试中表现出色的少量示例学习性能(可与 GPT-3 相媲美)。尽管与 GPT-3(即 200 亿参数对比 1750 亿参数)相比,这个模型在某种程度上较小,但它在当时是最大的开源语言模型。此外,所有训练和评估模型的代码都与其权重一起以 Apache 2.0 许可证 发布,允许商业使用。

(来自 [8])
模型。 GPT-NeoX-20B [6] 使用了标准的仅解码器变换器架构,但进行了以下两项更改:
-
RoPE 嵌入
-
并行注意力和前馈层
改进了标准位置嵌入,RoPE 嵌入(如上所示)提供了一种将位置信息注入自注意力操作的新方法。这种方法在绝对位置和相对位置信息之间找到了更好的*衡,并由于其在处理长序列任务中的表现能力,被用于多种其他模型(例如 PaLM [9]和 Falcon-40B [10])。此外,使用并行注意力和前馈层(见下文)使训练吞吐量提高了 15%,性能下降最小。

并行执行注意力层和前馈层(由作者创建)
有趣的是,为 GPT-NeoX-20B 创建了一个自定义分词器。这个分词器与 GPT-2 的分词器[11]相当,但它是从头开始在 Pile(一个大型多样的文本语料库)上训练的,并且经过修改以更一致地分词空白字符。因此,最终的分词器除了在高质量语料库上进行训练外,还特别有效于分词代码(即,代码中有很多空白字符!)。因此,几种开源模型(例如 MPT-7B [5])即使在今天也采用了这个分词器。

(来自 [6])
性能。 GPT-NeoX-20B 与 GPT-3 和其他开源模型(如 GPT-J)进行了比较。在这些评估中,我们看到 GPT-NeoX-20B 在常见的语言建模任务上表现相当好(即使与专有模型相比);见上文。值得注意的是,GPT-3 往往实现了最佳性能。然而,GPT-NeoX-20B 相对于其规模表现相当好,甚至在参数数量相*的专有模型中也表现优异。

(来自 [6])
GPT-NeoX-20B 的性能虽然不是最先进的,但在其规模下表现出乎意料地好,即使与*期模型相比也是如此!
开放预训练变换器(OPT)语言模型 [4]

OPT 版本的组件(由作者创建)
在之前的概述中,我们深入讨论了开放预训练变换器(OPT)库的细节。请见 这里 的链接。
OPT 由Meta AI提出,旨在将强大的 LLMs 民主化地提供给公众,包括几个不同的 LLMs,规模从 1.25 亿到 1750 亿参数不等。这些模型在由Reddit、the Pile和BooksCorpus等来源编制的精选数据集上进行预训练,而这个套件中的最大模型——OPT-175B——是首批真正的大型开源语言模型之一。此外,这些模型还配有一个代码库和一个详细记录所有模型预训练过程的日志。尽管 OPT 模型不用于商业,但它们是对 LLMs 开放研究的巨大资源。
影响。 OPT 语言模型是首次大规模努力使庞大的语言模型对研究社区可访问的——LLMs 现在完全对任何人开放,而不是隐藏在 API 后面。此外,OPT 的开源训练代码提供了一个高效的训练框架,使用了像FSDP和张量并行等常见技术。这段代码实现了比 NVIDIA 直接发布的研究[3]高出 17%的资源利用率,是训练 LLMs 的极佳资源。

(来源于[5])
训练笔记和日志与 OPT 相关,提供了大量(先前未知的)对 LLM 训练过程的见解。通过这些资源,我们可以更好地理解训练 LLM 的全部成本以及在此过程中可能遇到的许多困难(例如,损失峰值、硬件故障以及其他“中途”训练调整)。这些训练 LLM 的困难成为了讨论的话题,并且已经通过随后的开源 LLM 工作(大部分)得到解决;详见上述内容。

(来源于[4])
它的表现如何? OPT-175B 在提议时与当时流行的模型进行了广泛比较,并发现其在零样本和少样本学习设置下的性能与 GPT-3 相当;详见上文。总体而言,OPT 的性能并不突出——该模型被普遍认为在质量上落后于专有模型。尽管其表现**,然而,OPT 对 AI 研究而言是一个巨大的进步,并显著提升了对开源 LLM 的兴趣。这一影响不容低估,因为它出现在专有模型主导地位被接受为新标准的时期。
BLOOM: 一个开放的多语言语言模型 [12]
“学术界、非营利组织和小型公司的研究实验室发现创建、研究甚至使用 LLM 非常困难,因为只有少数具备必要资源和专有权利的工业实验室才能完全访问它们。” —来自 [12]
在[12]中提出,BLOOM 是一个 1760 亿参数的 LLM,它作为一个大规模开放协作的 AI 研究项目(即,超过 1000 名研究人员参与!),称为大科学研究工作坊。该工作坊持续了一年(2021 年 5 月至 2022 年 5 月),其目标是创建 i) 一个大规模的多语言文本数据集,以及 ii) 一个在该数据集上训练的大型多语言语言模型。最终生成的模型,略大于 GPT-3,并在负责任 AI 许可证(RAIL)下开源,可以生成 46 种不同语言8和 13 种编程语言的文本。
为训练 BLOOM 开发的数据集,称为ROOTS 语料库,由 498 个 HuggingFace 数据集组成,包含超过 1.6 TB 的文本,涵盖 46 种自然语言和 13 种编程语言。该数据集在不同语言中的分布如下图所示。

(来自 [12])
在获得原始数据后,作者应用了不同的质量过滤器管道,以去除非自然语言的文本。使用的具体过滤组件在[12]的第 3.1.3 节中进一步阐述,根据数据源的不同而有所变化。然而,整体管道的共同目标是尽可能多地过滤掉低质量文本。

(来自 [12])
BLOOM 使用的架构 是标准的解码器单一 Transformer。然而,如上所示,对该架构进行了少许修改,例如:
-
ALiBi [13]:这帮助模型将训练中见过的上下文长度泛化到更长的上下文长度。
-
嵌入层归一化:在模型的嵌入层后添加了额外的层归一化,这在经验上发现能够提高训练稳定性。
总体而言,这个模型与大多数 LLM 并没有太大区别。有趣的是,[12]中的作者对不同类型的变换器架构(例如,仅编码器模型、编码器-解码器模型和仅解码器模型)进行了广泛分析,发现仅解码器模型(几乎所有因果语言模型都使用这种模型)在预训练后表现最佳。
“我们的结果显示,预训练后,因果解码器模型表现最佳——验证了选择最先进 LLM 的决策。” — 来源于 [12]
它表现如何? 与其他开源 LLM 相比,BLOOM 的表现相对较好。在自然语言基准测试中,它的结果与 OPT 相当或有所改进,并且由于其在多语言语料库上进行训练,通常在机器翻译任务中表现优异;见下文。

(来源于 [12])
然而,BLOOM 的表现低于顶级专有模型。例如,我们在下方的 HumanEval 基准测试结果中看到,该模型的编码能力远远落后于 Codex [14]等替代品。此外,当我们将 BLOOM 与 Chinchilla [15]和 PaLM [9]等模型进行比较时,我们很快发现开源模型的性能逊色于其专有对应物。换句话说,在 BLOOM 提出时,开源 LLM 的研究仍然滞后。

(来源于 [12])
其他显著模型
我们尝试覆盖早期开源 LLM 研究中提出的几个显著模型。但仍有一些模型未包含在本概述中,值得一提。让我们快速了解其中的一些。
GPT-J [21] 是一个 60 亿参数的仅英语因果语言模型,在 GPT-NeoX-20B [6]之前提出。与 GPT-NeoX-20B 类似,该模型在 Pile 上进行预训练。在发布时,GPT-J-6B 是最大的公开 GPT-3 风格语言模型。

(来源于 [20])
GLM [20] 更像是一个预训练目标,而不是一个语言模型。该工作探索了通过提出自回归空白填充目标来统一不同的预训练技术(例如 BERT、T5 和 GPT)的想法。换句话说,我们以自回归方式预测句子中的掩码词,类似于语言模型;见上文。结果模型相当小(<10 亿参数),在几个流行的自然语言处理基准测试中表现优于 BERT、T5 和 GPT。
我们接下来要做什么?

开源 LLM 研究的发展(由作者创建)
鉴于早期对开源 LLMs 的尝试产生的模型表现远不如专有对手,我们不禁要问:我们应该做什么来提升这些模型? 随着这一研究领域的发展,我们看到主要投入了两个方向的努力:
-
创建更好的基础 LLMs
-
微调开源 LLMs(即对齐和模仿)
由于开源 LLMs 对所有人开放,这些领域的研究进展速度令人震惊——我们在不到一年的时间里从 OPT 进展到了接*最先进的模型(如 LLaMA-2 或 Falcon-40B [10])!
“我们认为,提升开源模型的最高杠杆行动是应对开发更好的基础语言模型这一艰巨挑战” — 出自[16]
上述两条研究方向在此期间是并行探索的,每条方向都产生了对 AI 从业者有用的技术。在本调查的接下来的两个部分中,我们将概述这些领域及其关键贡献,探讨最初对开源 LLMs 的尝试如何演变成极其强大的模型,如 LLaMA-2。
连接我!
非常感谢阅读这篇文章。我是Cameron R. Wolfe,Rebuy的 AI 总监。我研究深度学习的实证和理论基础。如果你喜欢这个概述,请订阅我的Deep (Learning) Focus 通讯,我通过从基础开始的相关主题概述帮助读者理解 AI 研究。你也可以在X和LinkedIn上关注我,或查看我在 medium 上的其他文章!
参考文献
[1] Brown, Tom, 等. “语言模型是少样本学习者。” 神经信息处理系统进展 33 (2020):1877–1901。
[2] Rae, Jack W., 等. “语言模型的扩展:方法、分析与训练 gopher 的见解。” arXiv 预印本 arXiv:2112.11446 (2021)。
[3] Smith, Shaden, 等. “使用 deepspeed 和 megatron 训练 megatron-turing nlg 530b,一个大规模生成语言模型。” arXiv 预印本 arXiv:2201.11990 (2022)。
[4] Zhang, Susan, 等. “OPT: 开源预训练变换器语言模型。” arXiv 预印本 arXiv:2205.01068 (2022)。
[5] “介绍 MPT-7B:开源商业可用 LLMs 的新标准。” MosaicML,2023 年 5 月 5 日, www.mosaicml.com/blog/mpt-7b.
[6] Black, Sid, 等. “Gpt-neox-20b:一个开源自回归语言模型。” arXiv 预印本 arXiv:2204.06745 (2022)。
[7] Gao, Leo, 等. “The pile: 一个 800GB 的多样文本数据集用于语言建模。” arXiv 预印本 arXiv:2101.00027 (2020)。
[8] Su, Jianlin, 等. “Roformer: 带旋转位置嵌入的增强变换器。” arXiv 预印本 arXiv:2104.09864 (2021)。
[9] Chowdhery, Aakanksha 等. “Palm:通过路径扩展语言建模。” arXiv 预印本 arXiv:2204.02311 (2022)。
[10] “介绍 Falcon LLM”,技术创新研究所,2023 年 6 月 7 日,falconllm.tii.ae/.
[11] Radford, Alec 等. “语言模型是无监督的多任务学习者。”
[12] Scao, Teven Le 等. “Bloom:一个 176b-参数的开放访问多语言模型。” arXiv 预印本 arXiv:2211.05100 (2022)。
[13] Press, Ofir, Noah A. Smith 和 Mike Lewis. “短期训练,长期测试:具有线性偏差的注意力机制实现输入长度外推。” arXiv 预印本 arXiv:2108.12409 (2021)。
[14] Chen, Mark 等. “评估训练有代码的大型语言模型。” arXiv 预印本 arXiv:2107.03374 (2021)。
[15] Hoffmann, Jordan 等. “训练计算最优的大型语言模型。” arXiv 预印本 arXiv:2203.15556 (2022)。
[16] Gudibande, Arnav 等. “模仿专有 llms 的虚假承诺。” arXiv 预印本 arXiv:2305.15717 (2023)。
[17] Vaswani, Ashish 等. “注意力机制是你所需的一切。” 神经信息处理系统进展 30 (2017)。
[18] Devlin, Jacob 等. “Bert:用于语言理解的深度双向转换器预训练。” arXiv 预印本 arXiv:1810.04805 (2018)。
[19] Ouyang, Long 等. “训练语言模型以遵循指令和人类反馈。” 神经信息处理系统进展 35 (2022):27730–27744。
[20] Du, Zhengxiao 等. “Glm:自回归空白填充的通用语言模型预训练。” arXiv 预印本 arXiv:2103.10360 (2021)。
[21] Ben Wang 和 Aran Komatsuzaki. GPT-J-6B:一个拥有 60 亿参数的自回归语言模型,2021。
[22] Sid Black, Leo Gao, Phil Wang, Connor Leahy 和 Stella Biderman. 2021. GPT-Neo:使用 MeshTensorflow 的大规模自回归语言建模。
开源 LLMs 的历史:模仿与对齐(三)
开源 LLMs 需要对齐才能真正出色……
·发表于Towards Data Science ·阅读时间 20 分钟·2023 年 11 月 28 日
--

(照片由Joanna Kosinska提供,发布在Unsplash)
之前关于开源大型语言模型(LLMs)的研究大多数集中在创建预训练基础模型上。然而,这些模型并未经过任何微调,因此由于缺乏对齐,它们无法与顶级闭源 LLMs(例如,ChatGPT 或 Claude)的质量相匹配。付费模型通过使用诸如 SFT 和 RLHF 等技术进行广泛对齐,这大大提高了其可用性。相比之下,开源模型通常使用较小的公开数据集进行较少的微调。然而,在这篇概述中,我们将审视*期研究,旨在通过更广泛的微调和对齐来提升开源 LLMs 的质量。

(来自[1, 2, 12])
本概述是我关于开源 LLMs 历史系列的第三部分(也是最后一部分)。在系列的第一部分中,我们回顾了创建开源语言模型的初步尝试。虽然这些初步预训练的 LLMs 表现不佳,但它们很快被更好的开源基础模型所取代,我们在系列的第二部分中进行了介绍。现在,我们将探讨如何对这些更好的开源模型进行微调/对齐,以提高其质量并缩小开源 LLMs 与专有 LLMs 之间的性能差距,完成从最初的模型如 OPT 到我们今天拥有的高性能开源 LLMs(例如 LLaMA-2-Chat)的历程。

(来自 [17, 18])
对齐过程。 本概述将研究开源 LLMs 的微调和对齐过程。然而,在研究该领域的研究之前,我们需要了解对齐是什么以及如何实现。我们应该记住,语言模型的训练过程分为几个部分。如上所示,我们从预训练开始,随后进行几个微调步骤。在预训练之后,LLM 可以准确地进行下一个令牌预测,但其输出可能会重复且不有趣。因此,模型需要进行微调以改善其对齐,即生成与人类用户的期望一致的文本的能力(例如,遵循指令、避免有害输出、避免撒谎、产生有趣或创造性的输出等)。

(来自 [17])
SFT。对齐是通过两种微调技术实现的:监督微调(SFT)和从人类反馈中进行的强化学习(RLHF);见上图。SFT 只是利用标准的语言建模目标,对高质量提示和响应对的示例进行微调。LLM 可以看到应如何响应的示例,并从这些响应中学习!SFT 非常简单且有效,但需要仔细策划一个能够捕捉“正确”行为的数据集。
RLHF 直接根据人类注释者的反馈训练大型语言模型(LLM)—— 人们识别他们喜欢的输出,LLM 学习如何产生更多类似的输出。为此,我们首先获得一组提示,并从 LLM 生成多个不同的输出。通过一组人类注释者,我们根据这些响应的质量对每个响应进行评分。这些评分可以用来训练一个奖励模型(即,带有附加回归头的微调版 LLM),以预测响应的分数。然后,RLHF 使用名为 PPO 的强化学习算法来微调模型,以最大化该分数。通常,最高性能的 LLM 是通过按顺序执行 SFT 和 RLHF(并获得大量人类反馈)来对齐的。
模仿学习

(引自[16])
随着 LLaMA [3] 的发布,开源研究社区终于可以访问强大的基础 LLM,这些 LLM 可以被微调或对齐以适应各种不同的应用。因此,LLaMA 催化了开源 LLM 研究的爆炸性增长,实践者们纷纷急于在自己选择的任务上微调 LLaMA 模型。有趣的是,在此期间最常见的研究方向之一是模仿学习。模仿学习(可以说)是一种对齐形式,通过另一个更强大的 LLM 的输出来微调 LLM。这种方法的灵感来源于知识蒸馏的理念;见上文。
“模型模仿的前提是,一旦通过 API 提供了专有语言模型(LM),就可以收集 API 输出的数据集,并使用这些数据来微调开源语言模型。” — 引自[6]
开源模仿学习研究提出的问题很简单:我们是否可以仅通过对这些模型的响应进行微调,创建一个与 ChatGPT 或 GPT-4 一样强大的模型? 为了测试这一点,我们可以采取一种简单的方法:
-
收集这些模型的对话示例(例如,使用 OpenAI API)。
-
对这些数据进行(监督)微调(即,使用正常的语言建模目标)。
正如我们将看到的,研究界对模仿学习是否是一种有价值的方法进行了激烈的讨论!最后,我们发现这种方法在实践中确实有用,但仅在某些条件下效果良好。
模仿学习的初步努力

LLaMA 催化了众多模仿模型的创建(引自[7, 8, 9, 10])
在 LLaMA 发布后,研究人员迅速开始发布各种使用 ChatGPT 对话派生的模仿模型。通常,用于训练的数据——这会禁止结果模型用于商业用途——来自 OpenAI API 或类似ShareGPT的来源。以下是一些最广为人知的模仿模型(按时间顺序列出)。
Alpaca [7] 使用 self-instruct [11] 框架对 LLaMA-7B 进行微调,从 GPT-3.5(即,text-davinci-003)自动收集微调数据集。收集数据和微调 Alpaca 仅花费了 $600。
Vicuna [8] 对来自 ChatGPT(即,源自 ShareGPT)的 70K 对话示例进行微调。有趣的是,Vicuna 的整个微调过程仅花费了 $300。
Koala [9] 在来自 Alpaca 微调集和各种其他来源(如 ShareGPT、HC3、OIG、Anthropic HH 和 OpenAI WebGPT/Summarization)的大量对话示例数据集上对 LLaMA-13B 进行微调。与之前的模仿模型相比,Koala 在更大的数据集上进行微调,并进行了更广泛的评估。
GPT4ALL [16] 在 GPT-3.5-turbo 的 80 万个聊天完成数据上对 LLaMA-7B 进行微调。除了模型,作者还发布了训练/推理代码和量化的模型权重,可以用来在最小计算资源(例如,笔记本电脑)下进行推理。

(来自 [8. 9])
模仿的影响。 这些模型在短时间内发布,并声称能够达到类似于顶级专有模型如 ChatGPT 和 GPT-4 的质量。例如,Vicuna 被发现保持了 GPT-4 92% 的质量,而 Koala 在许多情况下与 ChatGPT 的质量相当或超过;详见上文。这些发现似乎表明,模型模仿可以用于将任何专有模型的能力提炼到一个更小的开源 LLM 中。如果这是真的,即使是最好的专有 LLM 的质量也可以轻松复制,这些模型将没有 真正的优势。
“开源模型更快、更可定制、更私密,并且……更有能力。它们用 $100 和 13B 参数做的事情, [Google] 在 $10M 和 540B 的条件下都难以做到。而且它们是在几周内完成的,不是几个月。” — 来自 [9]
模仿模型的爆炸性增长是开源模型首次真正被视为对主导 LLM 领域的闭源 LLM 的潜在替代品的实例之一 自 GPT-3 提议以来。尽管付费 API 的使用已成为标准,但模仿模型的惊人表现激发了对开源 LLM 的承诺感。
模仿模型是否是虚假的承诺?

(来自 [6])
尽管模仿模型表现出色的前景,但我们在[6]中看到我们遗漏了一些重要的东西。即,更具针对性的评估表明,这些模型的表现远不如顶级专有 LLM,如 ChatGPT 和 GPT-4。事实上,我们看到,通过模仿微调基础模型实际上几乎没有缩小开源模型和专有模型之间的性能差距。相反,结果模型往往只在微调集大量表示的任务上性能有所提升,甚至可能有更明显的幻想倾向。

(来自 [6])
实验设置。 为了确定模仿学习的实用性,[6]中的作者从 ChatGPT 中策划了约 130K 个多样化对话示例的数据集。然后,对各种规模的语言模型进行不同数量的模仿数据微调,然后测量它们的性能。如上所示,我们可以从这些实验中得出一些有趣的观察结果:
-
用于微调的模仿数据量不会在人工评估试验中改善模型质量。
-
模仿模型在标准化基准上的表现通常比基础模型差(并且随着更多模仿数据的使用而恶化)。
-
增加基础模型的规模会一致地提高结果模仿模型的质量。
这里发生了什么? 当对模仿模型进行更广泛的自然语言基准评估时,我们发现它们的表现与相应的基础 LLM 相当或更差。换句话说,模仿模型实际上无法匹敌像 ChatGPT 这样的模型质量。与专有 LLM 相比,这些模型的知识基础不够广泛,正如通过更大基础模型观察到的性能提升所揭示的那样。
“我们认为,提高开源模型的最高杠杆作用是解决开发更好的基础语言模型的困难挑战,而不是走捷径模仿专有系统。” — 来自 [6]
鉴于此,我们可能第一个问题是:为什么这些模型看起来表现如此出色? 我们在[6]中看到,模仿模型学习模仿像 ChatGPT 这样的模型的风格。因此,人类工作者可能会被误导,认为该模型质量很高,即使它生成的信息更频繁地事实不准确(即,这更难以轻易检查或验证)。
模仿学习真的有用吗?
“我们的研究表明,无论这些解释是由人类还是更先进的 AI 模型生成,从逐步解释中学习是提高模型能力和技能的一个有前途的方向。” — 来自 [1]
在 [6] 的研究揭示模仿模型的表现远不如最初预期之后,研究界对模仿模型是否真正有价值感到不确定。值得注意的是,[6] 的分析表明,局部模仿 —— 即在特定任务上学习模仿模型的行为,而不是整体模仿其行为 —— 是相当有效的。然而,这并不意味着模仿模型在整体上能够匹配专有模型的质量。为了使模仿模型在整体上更好,[6] 的作者提出了两个前进方向:
-
生成一个更大、更全面的模仿数据集
-
创建一个更好的基础模型以用于模仿学习
有趣的是,这些建议都被后续的研究广泛探讨,并发现其效果积极。

(来自 [12])
Orca [12] 是一个基于 LLaMA-13B 的模仿模型。然而,与先前的模仿学习工作相比,Orca 是在从 ChatGPT 和 GPT-4 收集的更高质量、更详细、更全面的数据集上训练的。特别是,之前为模仿学习收集的数据集可以被认为是“浅层”的 —— 它们只是由类似 ChatGPT 的模型生成的提示和响应对的示例;见上文。
“我们得出结论,要通过纯粹的模仿广泛匹配 ChatGPT,需要大力收集巨大的模仿数据集,并且需要比目前可用的更多样化和更高质量的模仿数据。” — 来自 [6]
在浅层模仿的基础上,Orca 试图通过以下方式增强由 ChatGPT 或 GPT-4 生成的模仿数据集:
-
解释追踪
-
步骤思考过程
-
复杂的指令
为此,被模仿的模型会通过指令或系统消息被提示提供其响应的详细解释。这种方法超越了简单的提示-响应对,通过为模仿模型所见的数据添加额外的有用信息。当从像 ChatGPT 这样强大的 LLM 学习时,Orca 不仅仅看到模型的响应。即,它可以从与模型响应一起生成的详细解释和思考过程中学习复杂的提示!见下文的插图。

(来自 [12])
在经过大量这样的详细模仿数据(即来自 ChatGPT 的 500 万个示例和来自 GPT-4 的 100 万个示例)进行微调之后,我们看到 Orca 相比于先前的模仿模型表现极为出色;见下文。

(来自 [12])
尽管 Orca 显著缩小了开源模仿模型与专有 LLM 之间的差距,但我们仍然看到下表中该模型始终被 GPT-4 超越。不幸的是,即使改进的模仿方法也不足以完全匹配顶级专有模型的质量。

(来自 [12])
尽管如此,Orca 的出色表现表明,模仿学习是一种有价值的微调策略,可以大幅提升任何高质量基础 LLM 的性能。进一步来看,我们在 [12] 中了解到,成功利用模仿学习有两个主要要求:
-
一个大型、全面的模仿数据集
-
每个响应中的详细解释跟踪
更好的基础 LLM。 尽管 [6] 中的作者认为收集足够大且多样的模仿学习数据集极为困难,但我们从 Orca 中看到,这样的成就至少是可能的。此外,后续工作广泛探索了 [6] 中的替代建议:创建更强大的(开源)基础模型。虽然开源预训练 LLM 最初表现不佳,但我们最*看到了一些强大的预训练 LLM 的提议;例如,LLaMA [3]、MPT [14, 15] 和 Falcon [13]。鉴于模型预训练是后续任何微调(例如,模仿学习、SFT、RLHF 等)的起点,从更好的基础模型开始也能提升下游模仿模型的质量!幸运的是,我们在本系列的第二部分中涵盖了所有最佳的开源预训练语言模型。更多详细信息,请见 这里。
对齐开源 LLM

(摘自 [5])
模仿学习试图通过训练专有 LLM 的响应(和解释跟踪)来提高开源基础模型的质量。虽然这种方法在某些情况下是成功的,但(显然)这并不是顶级专有模型的训练方式——模仿是创建强大开源模型的捷径。如果我们想要与专有模型质量相媲美的开源 LLM,我们需要在对齐上投入大量资源。
“这些封闭产品 LLM 被大量微调以与人类偏好对齐,这大大提高了它们的可用性和安全性。这一步骤可能需要大量的计算和人工注释成本,而且通常不透明或不容易复制。” — 摘自 [1]
问题出在哪里? 对齐开源模仿模型的想法似乎很简单。我们有很棒的基础模型,为什么不直接复制像 GPT-4 这样的模型使用的对齐过程? 对齐过程需要大量的计算和人工注释资源。此外,它严重依赖于专有数据,这限制了透明度,使得结果复制变得非常困难。因此,开源模型在对齐研究方面长期以来落后于专有模型。然而,在本节中,我们将探讨两项最新的工作——LIMA [2] 和 LLaMA-2 [1]——它们通过更好的对齐极大地提高了开源 LLM 的质量。
先前的开源对齐工作
在讨论 LIMA 和 LLaMA-2 之前,需要注意的是,开源研究社区并没有完全避免对预训练模型的对齐。例如,Falcon-40B-Instruct [13] 在 Baize 上进行超过 150M token 数据的 SFT。同样,许多经过微调的 MPT-7B [14] 和 MPT-30B [15] 变体也已发布,包括那些在公共数据集上进行 SFT 的聊天/指令变体和一个在具有更长上下文长度的数据上进行微调的 StoryWriter 变体。

(来自 OpenLLM 排行榜)
此外,如果我们简单查看 Open LLM 排行榜(见上文),我们会看到各种不同的模型经历了在各种不同数据集上通过 SFT 进行的微调。开源 LLMs 并没有完全避免对齐。然而,顶级专有模型经历了 SFT 和 RLHF 在大量高质量对话和人类反馈的数据集上进行的训练。相比之下,大多数开源模型仅通过 SFT 在质量和多样性不足的公共数据集上进行了对齐。为了真正匹配专有模型的质量,开源 LLMs 需要尝试复制其对齐过程。
LIMA: 数据高效对齐 [2]
“模型的知识和能力几乎完全在预训练期间获得,而对齐则教会模型在与用户交互时应使用哪种格式子分布。” — 来自 [2]
如上所述,开源 LLMs — 很长一段时间以来 — 主要通过 SFT 在公共数据集上进行对齐。鉴于对 SFT 的高度重视,[2] 中的作者广泛研究了 SFT 对预训练 LLMs 的影响。这项分析的目的是揭示预训练和通过 SFT 对齐在创建高性能 LLM 中的相对重要性,并揭示在经过 SFT 后最大化模型性能的最佳实践。
数据集。 为此,[2] 中的作者构建了一个包含 1,000 个对话示例的小数据集以用于 SFT。尽管这可能看起来数据量不足,但该数据集中的示例经过精心策划,以确保质量,使用了多样化的提示和统一的输出风格或语气;见下文。

(来自 [2])
用于训练 LIMA 的 SFT 数据集虽然小,但质量极高。有趣的是,我们在 [2] 中看到 LIMA 在这个数据集上的微调表现惊人地好,甚至接*于如 GPT-4 或 Claude 等最先进 LLMs 的性能;见下文。

(来自 [2])
这样的结果表明,语言模型可以通过少量精心挑选的示例有效对齐。尽管 LIMA 的表现仍未达到 GPT-4,但能够以如此少的数据进行高质量对齐既意外又令人印象深刻。这样的结果告诉我们,数据质量似乎是通过 SFT 进行对齐时最重要的因素。

(来自 [2])
我们学到了什么? 我们从 LIMA 中学到了一些有用的经验。首先,数据的质量对于 SFT 至关重要。仅仅使用更多的数据是不够的 —— 数据也需要具有高质量;见上文。此外,[2] 中的结果导致了“表面对齐假说”的提出,该假说提供了对齐的新颖独特视角。简单来说,该假说认为大多数 LLM 的核心知识是在预训练期间 学习的,而对齐则寻求为呈现这些知识找到适当的格式或风格。因此,对齐可以以数据高效的方式学习。
LLaMA-2:提升对齐研究的透明度 [1]
“Llama 2-Chat 是几个月研究和迭代应用对齐技术的结果,包括指令调整和 RLHF,涉及大量计算和注释资源。” — 来自 [1]
最*发布的 LLaMA-2 [1] LLM 套件由多个开源模型组成,规模范围从 70 亿到 700 亿参数。与其前身(即 LLaMA-1 [3])相比,LLaMA-2 模型通过在超过 40% 更多的数据(即 2 万亿个标记而不是 1.4 万亿)上进行预训练、具有更长的上下文长度,并使用优化为快速推断的架构(即通过使用 分组查询注意力 [4])来区分自己。
LLaMA-2 在开源模型中实现了最先进的性能。然而,LLaMA-2 套件不仅仅包含预训练的 LLMs。作者在对齐过程中投入了大量精力,通过在大量对话数据和人类反馈上对每个模型进行微调——使用 SFT 和 RLHF——来实现这一点;见下文。最终模型被称为 LLaMA-2-Chat 模型。

(来自 [5])
这些精细化版本的 LLaMA-2 表现出色,并在弥合开源与专有 LLM 之间的对齐差距方面迈出了重要一步。LLaMA-2 的对齐过程强调两个关键行为特性:
-
有用性:模型满足用户的请求并提供所请求的信息。
-
安全性:模型避免产生“安全性差”的回应
为确保对齐后的模型既有用又安全,用于 SFT 和 RLHF 的数据会根据这些原则进行过滤、收集和注释。

(来自 [1])
SFT. LLaMA-2 对齐过程的第一步是使用 SFT 进行微调。与其他开源 LLM 类似,LLaMA-2 首先在公开可用的指令调优数据上进行微调。然而,这类数据往往缺乏多样性和质量,正如 LIMA [2]所示,这会大幅影响性能。因此,[1]中的作者专注于收集一小部分高质量数据进行 SFT。这些数据来自各种来源,包括手动创建或注释的示例以及经过质量筛选的公共来源数据。最终,LLaMA-2 在 27,540 个高质量对话示例上进行第二阶段的微调;参见上文的示例。
“令人惊讶的是,我们发现从结果 SFT 模型中采样的输出通常与人类注释员手写的 SFT 数据相当具有竞争力,这表明我们可以重新调整优先级,将更多的注释工作投入到基于偏好的注释中以便于 RLHF。” — 来自 [1]
有趣的是,[1]中的作者观察到,收集更多数据(即超出 27K 高质量示例)对 SFT 的收益递减。这些发现与 LIMA [2] 的实证分析一致。我们不需要大量数据进行 SFT,但数据应当具有高质量!有趣的是,[1]中的作者还指出,经过 SFT 的 LLaMA-2 模型似乎能够生成自己的 SFT 数据。
RLHF. LLaMA-2 进一步通过 RLHF 在超过 100 万例人类反馈的数据集上进行微调。为了收集这些反馈,采用了二元协议,其中人类注释员被要求写出一个提示,并选择两个 LLM 生成的响应中较好的一个。在这里,人类偏好数据根据有用性和安全性标准进行收集。例如,专注于安全性的人工偏好注释可能会鼓励注释员设计出可能引发不安全响应的对抗性提示。然后,人工注释员可以标记哪些响应——如果有的话——是可取的且安全的。
“在其他条件相等的情况下,奖励模型的改进可以直接转化为 Llama 2-Chat 的改进。” — 来自 [1]
人类反馈数据是批量收集的,LLaMA-2 在每批数据之间通过 RLHF 进行微调。因此,每次 RLHF 试验后,都会迭代创建几个版本的每个 LLaMA-2-Chat 模型,共五个版本。在 [1] 中,我们看到每次收集到新的人工偏好数据时,都会为 RLHF 训练一个新的奖励模型,确保奖励模型准确捕捉到最新模型的人类偏好。此外,我们看到生成的奖励模型的质量出奇地能够预测 LLaMA-2-Chat 模型的整体质量。总体来说,LLaMA-2 在整个迭代 RLHF 过程中经过了超过 100 万实例的人类反馈微调。

(来自 [1])
如上图所示,LLaMA-2-Chat 的质量——无论是有用性还是安全性——在与 SFT 和 RLHF 多次对齐的过程中*稳提升。这一可视化清晰地描绘了每种技术对模型质量的影响程度。也就是说,仅仅进行 SFT 不足以达到目标!即便在应用 SFT 后,模型的对齐在每个 RLHF 阶段都会显著改善。

Open LLM 排行榜上的前五名模型均基于 LLaMA-2(来自 Open LLM 排行榜)
性能。 如上所示,LLaMA-2-Chat 模型目前是开源 LLM 的最先进水*,如 Open LLM 排行榜 所示。当将 LLaMA-2-Chat 模型与 [1] 中的其他流行 LLM 进行比较时,我们看到它们在有用性和安全性方面远远超过其他开源模型;见下文。

(来自 [1])
此外,LLaMA-2 的表现甚至与如 ChatGPT 等顶级专有模型在有用性和安全性方面相当。简单来说,这些结果强烈表明 LLaMA-2-Chat 模型的对齐质量很高。生成的模型往往能准确地捕捉并遵循期望的有用性和安全性标准。
“[对齐] 可能需要显著的计算和人工标注成本,并且通常不透明或不易复制,限制了社区内在推进 AI 对齐研究的进展。” — 来自 [1]
LLaMA-2 的重要性。 LLaMA-2 对开源 LLM 研究的影响不仅仅体现在性能上设立了新的最先进水*。为什么? 我们在 [2] 中看到,LLaMA-2 采用了一种与以往工作 fundamentally 不同的方法。由于闭源 LLM 通常通过大量专有的人工标注数据进行对齐,这一过程在开源研究中更难以复制。尽管以往的开源模型主要利用 SFT 和公共对话数据来源,但 LLaMA-2 是首批大量投入对齐过程的开源 LLM 之一,精心策划了大量高质量的对话和人工偏好数据用于 SFT 和 RLHF。
结束语
我们现在已经深入研究了从 OPT 到 LLAMA-2 的整个开源语言模型的发展历程。尽管这两个模型之间发生了大量研究,但它们的提议间隔仅一年!开源 AI 研究社区发展非常迅速,跟上这一领域的研究非常令人兴奋、有趣和回报丰厚。能够访问像 LLaMA-2-Chat 这样强大的模型令人谦卑。作为实践者和研究者,我们有能力使用这些模型、从中学习,并真正深入理解它们的工作原理。这样的机会是独特的,不应被视为理所当然。尤其是对于 LLM 来说,开源真是非常酷!
与我联系!
非常感谢你阅读这篇文章。我是Cameron R. Wolfe,Rebuy的 AI 总监。我研究深度学习的经验和理论基础。如果你喜欢这个概述,订阅我的Deep (Learning) Focus 新闻通讯,我通过从基础到高级的相关话题概述来帮助读者理解 AI 研究。你也可以在X和LinkedIn上关注我,或者查看我在 medium 上的其他写作!
参考文献
[1] Touvron, Hugo 等。“Llama 2:开放基础和微调的聊天模型。”arXiv 预印本 arXiv:2307.09288(2023 年)。
[2] Zhou, Chunting 等。“Lima:对齐的少即是多。”arXiv 预印本 arXiv:2305.11206(2023 年)。
[3] Touvron, Hugo 等。“Llama:开放而高效的基础语言模型。”arXiv 预印本 arXiv:2302.13971(2023 年)。
[4] Ainslie, Joshua 等。“GQA:从多头检查点训练广义多查询变换器模型。”arXiv 预印本 arXiv:2305.13245(2023 年)。
[5] “介绍 Llama2:我们开源大型语言模型的下一代”,Meta,ai.meta.com/llama/.
[6] Gudibande, Arnav 等。“模仿专有 LLM 的虚假承诺。”arXiv 预印本 arXiv:2305.15717(2023 年)。
[7] Taori, Rohan 等。“斯坦福阿尔帕卡:一种跟随指令的 LLaMA 模型。”(2023 年)。
[8] Chiang, Wei-Lin 等。“Vicuna:一个开源聊天机器人,凭借 90%* ChatGPT 质量给 GPT-4 留下深刻印象。”(2023 年)。
[9] Geng, Xinyang 等。“Koala:一个用于学术研究的对话模型。”(2023 年)。
[10] Yuvanesh Anand, Zach Nussbaum, Brandon Duderstadt, Benjamin Schmidt 和 Andriy Mulyar。GPT4All:通过大规模数据提炼从 GPT-3.5-Turbo 训练助手风格的聊天机器人,2023 年。
[11] Wang, Yizhong 等。“Self-instruct:使语言模型与自生成指令对齐。”arXiv 预印本 arXiv:2212.10560(2022 年)。
[12] Mukherjee, Subhabrata 等。“Orca:从 GPT-4 的复杂解释痕迹中逐步学习。”arXiv 预印本 arXiv:2306.02707(2023 年)。
[13] “介绍 Falcon LLM”, 技术创新研究所,falconllm.tii.ae/.
[14] “介绍 MPT-7B:开源、商业可用 Llms 的新标准。” MosaicML,www.mosaicml.com/blog/mpt-7b.
[15] “MPT-30B:提升开源基础模型的标准。” MosaicML,www.mosaicml.com/blog/mpt-30b.
[16] 苟建*等。“知识蒸馏:综述。” 计算机视觉国际期刊 129 (2021):1789–1819。
[17] 欧阳龙等。“训练语言模型以遵循人类反馈的指令。” 神经信息处理系统进展 35 (2022):27730–27744。
[18] 格拉斯,阿梅利亚等。“通过针对性人类判断改善对话代理的对齐。” arXiv 预印本 arXiv:2209.14375 (2022)。
人工智能的另一面:领导 AI 团队和项目的艺术
如何成功管理不确定性并实现创新
·
关注 发表在 Towards Data Science · 11 分钟阅读 · 2023 年 5 月 11 日
--
克里斯托弗·C·克拉夫特 Jr. 阿波罗计划期间的任务控制中心(休斯顿)(有少许调整),公有领域
空间和机器学习项目有什么共同点?
很多共同点!例如,高度不确定性、研究导向的特性、即兴发挥以及许多其他因素。机器学习专业人员常常像是在探索人类的宇宙先锋,其中创造力和即兴发挥往往扮演着重要角色。
也许机器学习项目并不像建造和发射到月球的星际飞船那样复杂,但相比于标准的软件工程程序,它们难以规划且需要频繁调整。因此,许多 AI 领导者在预定的时间、精力和成果限制内难以交付 AI 解决方案。此外,由于对 AI/ML 人才的高需求,他们难以应对团队内的高流动率。
在这篇‘简短’的文章中,我将分享一些来自我 15 年领导数据工程、数据科学和机器学习团队及项目的有用技巧。这无疑是一个广泛的话题,可能适合写成一本书(也许有一天?)。不过,我将给你一个关于如何更成功地领导高绩效 AI/ML 团队的预览,这些团队工作于研发、应用研发或专注于生产化。
本文适用于那些希望成为领导者的 MLR/MLEs、想提高技能的经理、高级领导者(以更好地辅导/指导),或对 AI 领导力感兴趣的 AI 爱好者。
成功在前,失败不是选项!

休斯顿,我们这儿有问题!
让我们从挑战开始。为什么这么难?
乍一看,这可能像其他任何软件程序或项目,你有一个工程师团队,定义目标,应用某种形式的敏捷(scrum、sprints、每日站会等),偶尔与他们同步(啊,乏味的事,我非得做吗?),然后就开始了!
是的,你可能面临与任何软件经理或领导者相同的问题,比如对人才的高需求、稀缺性、分布式团队、低士气,以及过度的辅导和指导工作。此外,你可能会觉得每个人,特别是 LinkedIn 上的招聘人员,都想挖走你辛苦培养的顶尖人才……你还会责怪他们团队的高流动率。
然而,真正的区别和挑战在于你如何处理机器学习解决方案的研究、原型设计和开发,更重要的是,如何管理你的团队。只列举几个与 AI 项目相关的主要挑战:
-
不确定性与规划 — 目标不明确,对交付成果及其形式(什么会有效、如何有效)存在高度不确定性。因此,常常缺乏规划、估算和监控,成功标准模糊/未量化,这些都阻碍了进展和最终结果的衡量。
-
非线性进展 — 机器学习的进展是不规则的,需要频繁的调整。无论是研发还是产品导向的项目,应用最合适的方法通常并不明显,需要实验。有时,魔法般的某一天… 响了,成功了!(或者没有……)。
-
创新与实用性 — 在处理新颖的机器学习想法时的诱惑,通常是基础研究,应用最*论文中的进展,同时脱离目标,通常是应用方面、最终用户的视角、部署、维护和其他约束。需要保持*衡。
-
利益相关者 — 来自利益相关者的压力要达成预定的里程碑,对机器学习及其研发性质的缺乏理解、复杂性和高投入。这通常包括那些投入(如数据收集或工程)超过所开发解决方案所带来的收益的用例和举措。
-
缺乏人才 — 好的 AI 工程师稀缺是一个众所周知的事实。你可能会经常面临人员不足的情况,缺少 MLR、MLE 或优秀的数据工程师。通常需要调整职责并承担多重角色。这也涉及到提高工作效率的压力,这可能会对团队产生长期的负面影响。
如果再加上 1) 具有有限机器学习知识的经理/领导,或 2) 由有限管理经验和软技能的软件工程师晋升而来的领导,那……
是的,的确,我们有一个问题!

成功轨迹——如何实现?
好的,那么我们如何应对这些挑战呢?下面,你将找到一些如何实现成功最佳路径的提示:
1. 一个好领导者……一个超级英雄?
绝对不是,尽管有时可能会觉得如此。实际上,这是一个团队努力的结果,涉及到你的团队和其他小组的共同工作。你的工作是掌舵,给团队提供合适的工具,关注事情的进展,提供技术指导,清除障碍,预测风险,并处理出现的问题。
一个好领导者有许多特质,但 情感智力、同理心和良好的沟通技能 极为重要。
你应该定义一个愿景并说服你的团队成为其中的一部分(字面意思是卖给他们!)。不要忘记,你是他们的教练和导师,他们的发展和职业进步也依赖于你!即使你还没有完全掌握这些特质也没关系。关键是开始着手培养它们。
2. 合适的团队氛围
这意味着什么?
建立正确的团队和文化对任何 AI/ML 项目的成功至关重要。从营造一种类似初创公司的氛围开始,使团队围绕共同目标充满活力。鼓励社交、包容性、自主性以及创新和持续学习的文化。然而,最重要的是培养创造力,因为它将在危机和转型时帮助你——这种情况会经常发生。
OpenAI 的 CEO 萨姆·奥特曼 曾表示, 信任、自主性、努力工作以及完善的流程 是实现成功的关键。
我想补充的是,激励和授权团队成员实现其全面潜力对于实现高效的 AI/ML 团队至关重要。
不要忘记与团队社交!即使是每周的午餐也能带来价值。
3. 学习永不使思想衰竭
AI 是一个快节奏的领域,每周都有新的论文和突破。为了保持当前性和竞争力,AI/ML 团队必须持续更新他们的技能和知识。投资于团队的学习和发展不仅会提高他们的技能,还将有助于整体推动你的 ML 计划的成功。
培养实验和创新的文化,鼓励团队成员探索新想法,尝试新技术,并使用新工具进行实验。
你可以通过为实验或举行创意会议保留项目时间来实现这一点。通过共同工作和分享知识,团队成员可以从彼此的优势和专业知识中学习,并共同合作开发复杂问题的新解决方案。
4. 创新与实用性 —— *衡!
AI/ML 中的*衡至关重要,就像其他一切一样。你需要在创新与实用之间找到一个*衡点。这可能涉及到:
-
致力于解决正确的问题或使用案例 —— 它是否将使人们受益?它是否有更长远的愿景?还是仅仅是非常酷?
-
开发解决问题或启用用例的方法 —— 选择快速胜利?最先进的解决方案?还是会彻底改变 AI/ML 并带来声誉的新方法!
两者不可互斥,但最重要的是你如何处理它们。例如,如果你正在处理应用案例,专注于达成目标,即使需要使用基础模型。始终以最简单的解决方案为目标。但不要忘记留出一些资源和时间进行研究。与团队合作应用最新进展(带代码的论文通常很有帮助),或者创造你自己的方法。这样,你可以进一步提升模型的性能,将其用于未来项目,甚至可能在 NeurIPS、ICML、CVPR 或其他优秀的 ML 会议上发表研究论文。
5. 结构化但灵活
要取得成功,你需要一个稳健的过程,能够快速且迭代地交付。它不一定非常结构化;相反,它应该是一个灵活的过程,你可以根据项目类型进行调整。例如,我非常喜欢将敏捷方法与瀑布模型结合(称为 WaterScrumFall)。你在高层次上计划你的活动(目标、里程碑、发布、成功标准等),但在执行过程中通过周冲刺进行迭代,以应对变化的情况,并在必要时迅速转向。
但是,你必须根据你的需求、现有的其他流程、各种约束以及你的偏好或习惯来设计这样的过程。但要灵活;每个团队和项目都是独特的。例如,一个在办公室进行研发项目的成熟团队可能会发现看板很有帮助,同时取消每日站会。
同样的规则适用于工具。市场上有很多优秀的工具。尝试与团队一起使用它们,选择最适合你的工作和流程的工具,并用它们来支持你的工作。
6. 计划、计划、再计划!
你可能经常听到,“这是研发,无需计划,一切都关于实验和发现”。胡说,相信我。
的确,ML 项目中的不确定性很高,但只有规划才能帮助你理解它、规划各种场景并控制它。在开始任何 ML 项目、迭代或活动之前,花一些时间进行规划。我知道,尤其是在早期阶段,这非常困难,因为可能不清楚什么会有效。但规划是一项能帮助你控制未知、准备各种场景并降低风险的练习。
定义你的目标和成功标准,首先选择快速的胜利,并将其分解为里程碑或主要任务。不要深入挖掘;记住,你还需要保持灵活。
考虑到 ML 进展通常是 非线性的*。
投入时间到风险管理中。等一下,真的?谁会做这些? 你应该。创建一个潜在风险的清单和一个缓解计划,定期与团队更新。随着时间的推移,你会发现管理不确定性会变得更加可控。
7. 监控、衡量和沟通
但也不要过于痴迷于此!
规划过程的一部分是定义成功标准和指标,以便跟踪你的进展。为了监控目的,你可以将它们分配到各个阶段和里程碑中。它们可以反映具体的方面,如模型性能、延迟或涵盖的用户体验用例。但它们也可以是无形的,如用户满意度,在某些情况下,这可能比具体指标更重要。
确保你设定现实的 KPI,并且如果项目需要调整,愿意改变它们。
还有不要忘记沟通。这是每一个成功的 ML 活动的关键因素。让你的利益相关者了解你的进展、风险和问题是至关重要的。每周或每两周的会议效果最佳(即使它们很短)。用通俗的语言教育他们关于 ML 的知识将对你大有帮助。
8. 庆祝与反思
最后,与团队一起庆祝,欣赏即使是小的成功,如达成里程碑或让模型收敛。这样可以提升士气,加强团队合作,并保持团队的创造力。认识到团队的辛勤工作并保持每个人的积极性很重要。你可以用任何方式庆祝——也许喝点咖啡,买个蛋糕或举行团队晚宴。即使只是通过聊天或邮件发送一条消息,也能带来不同的效果!
那反思呢?这也是一项至关重要的活动,你应该以回顾会议或经验教训的形式与团队一起进行。这不是为了责怪任何人,而是让团队成员作为一个整体退一步,评估哪些做得好,哪些可以做得不同,以便在研究、开发、协作、沟通、管理等相关方面变得更有效。
通过庆祝成功和反思失败,你也可以培养一种创新文化,让每个人都感到鼓励去冒险和尝试新事物。

(左)凯瑟琳·约翰逊,第一位女性和非洲裔美国 NASA 科学家之一,负责计算水银和阿波罗计划的轨迹,公有领域 | (右)阿波罗导向计算机,基于硅集成电路的第一台计算机,CPU 2MHz,4kB RAM,公有领域
如何避免偏离方向?
如果你想让你的团队(和你自己)成功,尽量避免常见的错误。下面列出了一些:
-
我、我和我 —— 不,这是一项协作的努力,你无法仅凭自己做到太多。赞扬你的团队,而不是自己!
-
我能做得更好、更快 —— 你确定吗?领导力在于委派和信任,并在合理的时间内工作。
-
这是研发,不需要规划 —— 真的?每一个研发项目都需要规划,以管理不确定性并确保产出。如果不这样做,你可能会无休止地进行下去……
-
阅读一篇有趣的论文,我们来试试! —— 专注于交付,然后再尝试新事物。大多数论文仅在特定条件下有效。
-
每月的团队会议就能解决问题 —— 你将如何管理你的团队,分享共同目标,并保持他们的积极性?安排短时间但更频繁的会议,也要进行一对一的沟通。
-
这是约翰的错 —— 这不是真的。你对团队及其表现负有责任,始终问问自己你可以做得更好或不同,以缓解问题。
-
团队流失率高是正常的 —— 这可能很常见,但你在做什么来防止它?招聘和培训新成员可能很昂贵,需要长达一年。在这段时间里,团队的生产力将受到影响,宝贵的知识和经验可能会永久丧失。
-
模型未收敛,让我们向其中注入更多数据 — 但数据收集和标记既昂贵又耗时。首先要弄清楚为什么它不起作用(数据、方法、模型和参数),确定更多数据是否真的会带来价值。


(左) 照片由 Mike Kiev 提供自 Pexels | (右) 阿波罗 13 号着陆,公共领域
就这些了?没有更多的建议了吗?
我已经与你分享了一些主要的方法。通过遵循这些技巧,你可以克服 AI 项目的挑战,并确保项目的成功。
记住,AI 是一个快速发展的领域,保持创新需要一个优秀的团队、奉献精神、计划、在实用性与创新之间取得*衡,以及持续学习。
谦逊而好奇。祝你旅途顺利和成功! 🚀
顺便说一句,我强烈推荐 Gene Kranz 的书‘Failure is not an option’。它概述了早期太空计划的复杂性,同时强调了在面对挑战和挫折时领导力、团队合作和毅力的重要性。有时候,我觉得在 AI 开发,特别是通用人工智能方面,我们正处于试图登月的阶段!
感谢阅读。请随意在 LinkedIn 上联系或留言。
如果你喜欢这篇文章,请随时查看我的另外两篇:
大多数数据专业人士都在努力定义能转化为 AI/ML 解决方案并带来实际价值的用例。
学习如何有效地开发数据科学单元并避免常见的陷阱
[向数据科学迈进
数据科学中的讲故事的重要性
原文:
towardsdatascience.com/the-importance-of-storytelling-in-data-science-38eb625356d2
意见
为什么我们需要故事来解释数据科学
·发表于Towards Data Science ·阅读时间 5 分钟·2023 年 2 月 15 日
--
讲故事是人类文化的一部分,已有数千年的历史,人们天生对故事具有吸引力。故事是传达思想和信息的强大工具。它们能够唤起情感,建立联系,这些是统计数据和数据所无法做到的。为了成功,数据科学家必须能够从他们的工作中创造故事。在复杂概念、统计模型和大量数据成为常态的情况下,讲故事对于确保发现被理解、记住和付诸实践至关重要。

故事将统计数据和数据中的所有抽象冷漠转化为我们客户可以关联的东西。故事具有结构:开头、中间和结尾,使其易于理解。它们具有相关性。故事描绘了人们能够认同的角色和情境。它们将新的信息与人们已知的事物建立联系。
此外,人们对于理解抽象的概念并不擅长。我们的脑袋被设计来处理可以看见、听见、触摸、品尝或嗅到的具体、可触及的信息。另一方面,统计数据和数据通常是无形的,难以理解。例如,44%的人认为披萨作为早餐是完全可以接受的[1],一开始并没有感觉很有意义。这很多吗?少吗?但如果说你五个朋友中的两个可能吃披萨作为早餐,这让我们对统计数据有了更多的感受。对我来说,这似乎很多!如果我们想要明确这个统计数据,我们可以使其更具体:
想象一下你所在的小镇举行社区早餐会,居民们被邀请尝试早餐披萨。如果有十个人参加,你可以预计其中有 4 个人会乐意吃披萨!

Chad Montano拍摄,来源于Unsplash。
抽象的概念通过围绕数据和统计数据编织故事变得具体和切实。这样更容易被理解和记住。关于一个客户遇到问题并通过数据驱动的解决方案获得帮助的故事,可以引发同理心和兴趣。相比之下,呈现客户满意度的统计数据可能只会引来一声叹息。在展示数据时,至关重要的是不仅要传达事实,还要与观众建立联系,使数据对他们有意义且相关。
那么,讲故事的作用是什么呢?让我们考虑一个预测流失概率的模型——即客户流失的概率——对于我们组织的任何给定客户。
传达复杂信息:复杂的模型和大量数据可能难以理解。讲故事可以将这些复杂的概念分解为较小的、易于处理的部分,突出关键见解。例如,我们可以描述一个高预测流失概率的客户,他最*搬到了一个新地方,并且使用服务的频率减少了。通过关注这些信息对模型预测的影响,我们可以帮助观众理解模型的潜在驱动因素。
吸引观众: 数据可能枯燥乏味,使得保持观众的注意力变得具有挑战性。讲故事可以通过创建一个与观众相关的叙述,使数据更加吸引人。例如,我们可以突出一个客户使用我们移动应用的情况是客户忠诚度的强指示,以及模型在做出流失预测时考虑了这一信息。这有助于将数据赋予生命,使其对观众更具意义。
推动行动: 数据沟通的目标往往是推动行动,例如做出决策、实施具体建议或改变人们对问题的看法。在我们的例子中,我们可以讲述一个客户被预测流失但成功留住的故事。也许该客户最*搬家,使用我们服务的频率减少了,但客服团队通过指出一家客户未意识到的附*店铺成功挽留了他们。未来,我们知道当客户搬家时,我们应向他们发送新地区附*店铺的详细信息。这个故事突出了客服团队为留住客户所采取的具体行动,并展示了这些行动如何作为未来客户留存工作的模型。这通过提供具体的例子来展示流失模型的结果如何用于改善业务,从而推动行动。
让数据难以忘怀: 在展示数据时,确保观众记住关键见解和建议是至关重要的。讲故事可以通过创建一个让客户铭记的叙事来使数据更具特殊性。在我们最*搬家的客户例子中,我们可以给客户一个虚构的名字,或者如果我们有他们与客服的沟通记录,我们可以将一些内容融入故事中。这使得故事对观众来说更加真实。它变成了一个他们可以视觉化的具体事物。观众将在演示后更好地理解和记住数据。
讲故事是数据科学家可以用来使发现更具关联性、难以忘怀和有影响力的强大工具。通过将数据与叙事结构结合,我们可以以一种对观众易于理解和吸引的方式传达复杂的信息。此外,讲故事可以帮助推动行动,使数据难以忘怀,从而确保数据分析的结果被有效利用以推动积极成果。随着数据科学在决策中的关键作用不断增加,讲故事作为沟通工具对寻求用工作产生真正影响的数据科学家来说将变得越来越重要。
联系我们
随时通过LinkedIn与我联系,以获取有关数据科学领域的更多见解。
参考文献
[1] 《人物》杂志。(2021)。美国人最喜欢的比萨饼配料:故事。从people.com/food/americans-favorite-pizza-toppings-story/获取。
LLMs 的无限巴别图书馆
原文:
towardsdatascience.com/the-infinite-babel-library-of-llms-90e203b2f6b0
| 人工智能 | 未来 | 变压器 |
| --- | --- | --- |
开源、数据和关注:LLMs 未来将如何改变
·发表于 Towards Data Science ·16 分钟阅读·2023 年 5 月 8 日
--

作者使用 OpenAI DALL-E 创建的图像
“‘人工智能的教父’ 离开谷歌并警告前方危险” 是纽约时报的标题。如果语言模型没有开源,我们怎么知道它们是否对人类构成威胁?实际上发生了什么?语言模型的世界如何在变化的边缘。
号召开源运动

图像由 Nik Shuliahin 提供,来自 Unsplash.com
不久前 GPT-4 向公众公开,我认为我们都去阅读了技术报告,但感到失望。

技术报告 GPT-4。由作者截屏,图像来源:这里
最*,《自然》也讨论了这个问题:我们需要 大型语言模型 (LLMs) 开源。
许多 LLMs 是专有的,没有发布,我们不知道它们是用什么数据训练的。这不允许对它们进行检查和测试,特别是关于偏见的方面。
此外,与 ChatGPT 分享信息和代码存在泄漏的风险 如三星所发现。更不用说,一些州认为 这些公司的数据存储违反了 GDPR。
这就是为什么我们需要大语言模型开源,并且应当更多地投资于新大语言模型的开发,比如BLOOM 联盟(一个由学术联盟开发的 1700 亿参数的大语言模型)。
*年来,经常出现对这些大语言模型实际能力和人工智能风险的夸张报道。如果研究人员无法测试模型,他们就无法真正评估其能力,分析风险也是如此。此外,开源模型要透明得多,社区也可以尝试找出问题行为的源头。
此外,这并不是学术界的要求,机构对人工智能感到警惕。欧盟这些天正在讨论可能重塑 LLM 未来的欧盟人工智能法案。与此同时,白宫正在施压科技首席执行官以限制人工智能的风险。因此,开源可能实际上成为语言模型的未来要求。
为什么 ChatGPT 如此优秀?

我们都听说过 ChatGPT,以及它如何看起来具有革命性。但它是如何训练的呢?
了解现状,获取最新新闻,了解其影响和变化,一篇文章全涵盖。
让我们从一个事实开始,ChatGPT 是基于一种大语言模型(准确地说是 GPT 3.5)进行训练的。通常,这些类似 GPT 的语言模型是通过预测序列中的下一个标记进行训练的(从标记序列 w 中,模型必须预测下一个标记 w+1)。
该模型通常是一个变换器:由一个接收输入序列的编码器和一个生成输出序列的解码器组成。这个系统的核心是多头自注意力机制,它允许模型学习有关上下文和序列各部分之间依赖关系的信息。

图片来源:这里
GPT-3 就是采用这种原则进行训练的(像其他生成预训练变换器系列中的模型一样),只不过有更多的参数和数据(570GB 的数据和 1760 亿个参数)。
GPT3具有巨大的能力,但在生成文本时,它经常出现幻觉、缺乏帮助、不可解释,并且常常包含偏见。这意味着模型与我们期望的像人类一样生成文本的模型并不对齐。
我们如何从 GPT-3 中获得 ChatGPT?
这个过程称为来自人类反馈的强化学习(RHLF),作者在这篇文章中进行了描述:
让语言模型变得更大并不会自动提高它们遵循用户意图的能力。例如,大型…
在这里,我将非常简要地描述它。具体来说,它包括三个步骤:
-
监督微调是第一步,其中 LLM 被微调以学习监督策略(基线模型或 SFT 模型)。
-
模仿人类偏好,在这一步中,注释员必须对基线模型的一组输出进行投票。这些经过整理的数据集用于训练新模型,即奖励模型。
-
*端策略优化(PPO),在这里,奖励模型用于微调 SFT 模型并获得策略模型。

图片来源:这里
为了准备第一步,OpenAI 收集了一系列提示,并要求人类注释员写下预期的响应(12–15K 提示)。其中一些提示来自 GPT-3 用户,因此在 ChatGPT 上写的内容可能会用于下一个模型。
作者使用了作为模型的 GPT-3.5,该模型已经在编程代码上进行了微调,这也解释了 ChatGPT 的代码能力。
然而,这一步并不完全具有可扩展性,因为它是监督学习。无论如何,由此获得的模型尚未对齐。

图片来源:这里
注释员根据 SFT 模型的响应范围进行标注,根据响应的期望程度(从最差到最好)。我们现在拥有一个更大的数据集(10 倍),并将 SFT 模型的响应提供给新模型,新模型必须按偏好顺序进行排名。
在这一阶段,模型正在学习关于数据的一般策略,以及如何最大化其奖励(当它能够很好地排名输出时)。

图片来源:这里
因此,我们有了 SFT 模型,并使用其权重来初始化一个新的 PPO 模型。该模型使用*端策略优化(PPO)进行微调。
换句话说,我们使用了强化学习算法。PPO 模型接收到一个随机提示并对其做出响应,然后它会收到惩罚或奖励。与经典的 Q-learning 相比,这里模型策略会在每次响应后更新(模型直接从经验中学习,在策略上)。
此外,作者使用每个 token 的 Kullback-Leibler (KL) 惩罚,使模型的响应分布类似于 SFT 模型的分布。这是因为我们希望通过 RL(由于奖励模型)优化模型,但我们仍然不希望它忘记在步骤 1 中学到的内容,这些是由人类策划的提示。
最后,该模型在三个方面进行评估:有用性、真实性和无害性。毕竟,这正是我们希望优化的方面。
一个值得注意的事实是,当在经典基准测试(问答、摘要、分类)上评估时,该模型的表现低于 GPT-3。这就是对齐的代价。
Alpaca,一种革命性的动物

图片由 Dong Cheng 在 Unsplash 提供
如前所述,确实需要研究这些模型的行为,只有在它们是开源的情况下才能做到这一点。另一方面,任何语言模型都可以使用 RHLF 进行对齐。
RHLF 比从头开始训练模型要便宜且计算密集度低得多。另一方面,它要求有标注者(你确实需要一个包含指令的数据集)。但这些步骤不能自动化吗?
第一步是 Self-instruct,在这篇 2022 年的文章中,作者提出了一种半自动化的方法。实际上,一般的想法是从一组手动编写的指令开始。这些指令集既作为种子,又确保涵盖了大多数 NLP 任务。
从仅有的 175 个指令开始,模型生成了数据集(50k 指令)。然后,该数据集用于指令调整。

SELF-INSTRUCT 的高级概览。图片来源:这里
仅有一个模型需要的方法。ChatGPT 基于 OpenAI GPT-3.5,但难道不能使用更小的模型吗?它真的需要超过 100 B 的参数吗?
相反,斯坦福研究人员使用了 LLaMA,特别是 7B 版本,并按照 self-instruct 方法生成了 52 K 指令(使用 OpenAI 的 text-davinci-003 生成的指令)。Alpaca 的真正价值在于作者简化了流程,大大降低了成本,使任何学术实验室都能复制这个过程(在 这个库 中)。实际上如所述:
在我们的初步运行中,微调一个 7B 的 LLaMA 模型在 8 台 80GB 的 A100 上花费了 3 小时,这在大多数云计算提供商上花费不到 100 美元。(source)
初步模型评估显示,Alpaca 的表现几乎与 GPT-3.5 一致(在某些情况下甚至超过了 GPT-3.5)。这可能让人感到惊讶,因为这是一个体积小了 20 倍的模型。另一方面,该模型在一系列输入下表现得像 GPT(因此训练相当于一种知识蒸馏)。另一方面,该模型也具有典型语言模型的相同局限性,表现出幻觉、毒性和刻板印象。
Alpaca 证明了任何学术实验室都可以训练自己的 ChatGPT 版本(使用 LLaMA,该模型仅供研究使用)。另一方面,任何使用其他模型的公司都可以调整并创建自己的 ChatGPT 版本。此外,类似的模型甚至可以部署在手机或树莓派计算机上。
作者发布了一个演示,但在短时间内被关闭(出于安全考虑)。此外,尽管使用 LLaMA 需要申请(并访问模型权重),但几天后模型被泄露到网上。
LLM 是否处于革命的边缘?

图片来源:这里
看起来自从 ChatGPT 发布以来已经过了好几年,但实际上仅仅只有几个月。到那个时候,我们在讨论幂律法则,如何让一个模型拥有更多的参数、更多的数据和更多的训练,以便使新兴行为得以出现。
这些想法促成了我们可以为语言模型定义一种摩尔定律的想法。在某种意义上,*年来我们几乎看到了一个指数法则(我们从 GPT-2 的 15 亿参数发展到 GPT-3 的 1750 亿参数)。
有什么变化?
对这个理论的第一次冲击可以称之为,Chinchilla的到来。DeepMind 的模型表明,这不仅仅是数据量的问题,还有数据质量的问题。其次,META 的 LLaMA 表明,即使是使用精心挑选的数据集的小型模型也能取得与大型模型相似甚至更好的结果。
这不仅仅是模型的问题。数据是另一个问题。人类生产的数据不足,可能不够支持任何需要按照幂律法则所需的 GPT-5。其次,数据将不再像以前那样容易获取。
实际上,Reddit(一个受欢迎的数据资源)已宣布AI 开发者将需付费才能访问其内容。即使是维基百科也有相同的想法,现在StackOverflow也在朝着这个方向发展,它将要求公司支付费用。
Stack Overflow 的 Chandrasekar 说:“社区*台为 LLMs 提供支持,绝对应该得到补偿,以便像我们这样的公司可以重新投资于我们的社区,使其继续繁荣。”他说:“我们非常支持 Reddit 的做法。” (来源)
即使有人设法获得数据,对于公司而言,数据的安全性也未必得到保障。Getty 已经起诉了一款 AI 艺术生成器,但艺术家们也提出了诉讼。更不用说,程序员们也对GitHub Copilot提起了诉讼,GitHub Copilot 是基于代码库中的代码进行训练的。此外,音乐行业(以诉讼著称)对 AI 生成的音乐表示反对,并呼吁反对流媒体服务。如果即使 AI 公司援引合理使用进行上诉,未来它们是否能继续获得相同的数据访问权限仍然不确定。
除了通过异质模态扩展模型之外,还需考虑另一个因素,自 2017 年起,transformer 架构没有发生变化。所有语言模型都基于这样一种信条:只需多头自注意力机制即可,无需更多。直到最*,Sam Altman 仍然认为架构的可扩展性是 AGI 的关键。但正如他在最*的MIT 活动上所说,AGI 的关键不在于更多的层和参数。

图像来源:这里
transformer 存在明确的局限性,这在语言模型中得到了反映:幻觉、毒性和偏见。现代大型语言模型(LLMs)不具备批判性思维能力。诸如思维链和提示工程等技术作为补丁,试图缓解这些问题。
此外,已经证明,多头自注意力能够解决 RNN 派生的问题,并且允许行为的出现,因为上下文学习具有二次成本。最*,已发现不能用非二次的注意力变体替代自注意力,否则会失去表达力。然而,像 Spike-GPT 和 Hyena 这样的工作表明,存在不基于自注意力的成本较低的替代方案,并且在构建语言模型时可以获得类似的结果。
同样,使用 RHLF 对齐模型在各种任务中的表现也有一定的成本。因此,语言模型不会取代“专家模型”,但未来可能会成为其他模型的协调者(如 HuggingGPT 所建议的)。
你无法阻止开源,为什么它总是能胜出

图片由 Steven Lelham 提供
是 MidJourney 还是 DALL-E 更好?也许很难说。可以肯定的是,stable diffusion 是制胜的技术。由于 stable diffusion 是开源的,它催生了许多应用,并且成为了许多衍生研究的灵感(ControlNet、医学成像的合成数据、大脑的类比)。
通过社区的努力,Stable diffusion 在其各种版本中得到了改进,并且有无尽的变体。另一方面,没有一个 DALL-E 的应用没有基于 stable diffusion 的对应物(但反之则不成立)。
那么,为什么语言模型没有发生同样的情况呢?
到目前为止,主要问题是训练语言模型是一项费用高昂的任务。BigScience 的 BLOOM 确实是一个巨大的联盟。然而,LLaMA 已经表明,较小的模型也能与超过 100 B 参数的巨型模型竞争。Alpaca 表明,语言模型的对齐也可以以较低的成本(总成本少于 1,000 美元)完成。这些因素使 Simon Willson 能够说“大型语言模型正在迎来它们的 Stable Diffusion 时刻。”
从 Alpaca 到今天,出现了许多 开源 模型。不仅 Stability AI 发布了多个与巨头竞争的模型,并且可以被所有人使用,而且其他公司也发布了聊天机器人和模型。在短短几周内,我们见证了:Dolly、HuggingChat、Koala 等众多新模型。

作者提供的截图。图片来源:这里
现在,提到的一些模型确实是开源的,但它们仅用于非商业用途。虽然这些模型开放用于学术研究,但这意味着感兴趣的公司不能利用它们。
这只是故事的一部分。实际上,HuggingFace 上已经有可以轻松训练的模型(模型、数据集和管道),目前已有多个商业化的模型(至今超过 10 个):

作者提供的截图。来源:这里
开源模型、私人数据和新应用

图片由Muhammad Zaqy Al Fattah在 Unsplash 提供
Dario Amodei,Anthropic 的首席执行官正在寻求数十亿资金以击败 OpenAI 的大型模型。然而,世界的其他地方却在朝另一个方向发展。例如,Bloomberg 虽然在 AI 领域并不知名,已发布了一个金融 LLM(训练于来自金融来源的 3630 亿个标记)。
为什么我们需要用于金融的 LLM?为什么不直接使用 ChatGPT?
Google MedPalm 显示,通用模型的表现不如针对特定主题(在此案例中为医学、科学等文章数据集)微调的模型。
微调 LLM 显然成本很高,尤其是当我们谈论参数达到数百亿的模型时。较小的模型成本较低,但仍然不便宜。META 的 LLaMA 作为开源模型在一定程度上解决了这个问题。事实上,LLaMA-Adapter 的作者展示了仅需增加 1.2 百万参数即可进行微调(训练时间少于一小时)。
尽管 LLaMA 确实不可商业化,但仍有许多其他模型可以使用(从小到大)。显然,在特定领域成功应用的关键是数据。
正如三星不愉快地发现的,在公司内部使用 ChatGPT 是有风险的。即使ChatGPT 现在允许用户禁用聊天记录或拒绝使用他们的数据来训练模型,公司也会认为分享他们的数据存在风险。
许多公司会考虑训练自己的聊天机器人,这是一种基于自身企业数据进行微调的模型,并且将保持内部使用。毕竟,这项技术即使对于预算较小的公司也变得可用和负担得起。此外,低成本使他们能够在新数据到来时或如果有更好的开源模型发布时定期进行微调。现在拥有数据的公司会更不愿意将数据分享出去。
此外,我们已经看到拥有优质数据的重要性。医学和许多其他领域的数据收集困难(昂贵、受规管、稀缺),而拥有这些数据的公司具有优势。OpenAI 可能会花费数十亿试图收集医学数据,但除了成本之外,患者招募需要多年时间和已建立的网络(而 OpenAI 并不具备)。现在拥有数据的公司在分享这些数据时会更加严格。

图片来源 Petrebels 在 Unsplash
此外,像 HuggingGPT 和 AudioGPT 这样的作品表明 LLM 是用户与专家模型(文本到图像、音频模型等)互动的界面。在过去几年中,许多公司已经聘请数据科学家并开发了不同的专业模型以满足其需求(制药公司用于药物发现和设计的模型、制造公司用于组件设计和预测性维护的模型等)。因此,现在数据科学家可以指导 LLM 连接到他们之前训练的模型,并允许内部非技术用户通过文本提示与其互动。
还有另一个因素指向这样的情景,即生成式 AI 的规章不明确(例如,谷歌未发布其生成音乐模型以避免版权侵权)。除了版权问题,关于责任的问题仍然悬而未决。因此,许多公司可能会在接下来的几个月里内化这项技术,创建自己的 AI 助手。
告别的思考

图片来源于 Saif71.com 在 Unsplash.com
辛顿博士说,当人们曾经问他如何能从事潜在危险的技术工作时,他会引用罗伯特·奥本海默的话,奥本海默领导了美国的原子弹研制工作:“当你看到一些技术上很甜美的东西时,你就去做它。”
他现在不再这样说了。 (source)
辛顿最*表示,我们需要讨论人工智能的风险。但我们不能在一个黑箱中研究炸弹爆炸的风险。这就是为什么模型越来越迫切需要开源。
LLMs 正处于变革的阶段。创建越来越大的模型是不可持续的,并且不再提供曾经的优势。下一代 LLM 的未来将取决于数据,并且可能基于不再依赖自注意力的新架构。
然而,数据不会像以前那样容易获取;公司开始限制对数据的访问。 微软表示愿意允许公司 创建自己版本的 ChatGPT。但公司会持怀疑态度。
一些公司担心他们的业务(似乎 ChatGPT 已经宣称 其第一个受害者),而其他公司则担心数据泄漏。或者,仅仅是因为技术最终几乎触手可及,每家公司都将创建一个符合自己需求的聊天机器人。
总之,我们可以看到不同的趋势(这些趋势在某种程度上已经在发生):
-
对人工智能日益增长的恐惧正在推动开源模型的发展
-
这导致了开源 LLM 模型的不断发布。这反过来显示,你可以使用更小的模型并降低对其对齐的成本。
-
大型语言模型(LLM)对不同的企业构成威胁,公司担心这些模型可能威胁到他们的业务。因此,不同的公司正在减少对其数据的访问或要求人工智能公司支付费用。
-
成本降低、对竞争的担忧、对专有数据的新相关性以及开源模型的新可用性正促使公司使用开源模型在自己的数据上训练自己的聊天机器人。
你对 LLM 的未来有什么看法?在评论中告诉我
如果你觉得这个话题有趣:
你可以查看我的其他文章,也可以 订阅 以在我发布文章时获得通知,你也可以 成为 Medium 会员 以访问所有故事(这是*台的附属链接,我可以从中获得少量收入,但不会对你产生费用),你还可以通过LinkedIn与我联系或找到我。
这是我 GitHub 仓库的链接,我计划在这里收集与机器学习、人工智能及其他相关的代码和许多资源。
## GitHub - SalvatoreRa/tutorial: 机器学习、人工智能、数据科学的教程…
机器学习、人工智能、数据科学的教程,包含数学解释和可重用代码(用 Python…
GitHub - SalvatoreRa/tutorial: 机器学习、人工智能、数据科学的教程…
或者你可能对我最*的一篇文章感兴趣:
Hyena 模型展示了卷积如何可能比自注意力更快
筛选数据、视觉特征和知识蒸馏:下一代计算机视觉模型的基础
## 透过你的眼睛:谷歌 AI 模型如何通过眼睛预测你的年龄
新模型通过分析眼睛照片可以揭示衰老的秘密
levelup.gitconnected.com [## 机械交响乐:人工智能会取代人类劳动力吗?
GPT-4 展示了令人印象深刻的技能:这将对劳动市场产生什么影响?
人工智能的膨胀:更多的模型是否总是更好?
原文:
towardsdatascience.com/the-inflation-of-ai-is-more-always-better-8ea1be75e0aa
新的机器学习模型每小时都会出现,但这种快速的步伐也带来了缺陷;以假设驱动的开发可以帮助减轻这些缺陷。
·发表于Towards Data Science ·6 分钟阅读·2023 年 11 月 8 日
--
我们生活在人工智能的时代!每天,许多新的人工智能工具和机器学习模型正在被创建、训练、发布,并且常常被宣传。例如,当我们查看Hugging Face时,我们看到截至今天(2023 年 11 月 6 日)大约有 40 万个模型,而 2022 年 11 月时仅有约 8.4 万个模型(见图 1)。在短短一年内,模型数量增长了大约 470%。请记住,Hugging Face并不是唯一的机器学习模型*台。此外,许多模型甚至没有开源。因此,可以肯定地说,实际可用的机器学习模型数量要高得多。
是否真的需要如此大量的模型膨胀?
对人工智能的兴奋之情是巨大的,这首先是好的。人工智能有潜力为一些最严重的全球挑战(如气候变化或疫情)找到解决方案——或者至少减轻这些挑战。此外,人工智能可以使日常任务更高效,从而改善我们的工作与生活*衡。因此,进行人工智能的研究与开发,并将机器学习模型提供给社区,是正确且必要的步骤!然而,鉴于目前人工智能社区的发展速度和兴奋感,我在想:是否真的需要如此大量的模型膨胀?最终,谁将从中受益?

图 1:这张图展示了Hugging Face上可用的开源模型数量随时间的变化(蓝色线)。红色线代表了 2022 年 11 月 ChatGPT 的发布,橙色虚线代表了基于 ChatGPT 发布前数据的三次多项式拟合。
模型膨胀的潜在风险
“兴奋”和“炒作”带来的一个常见缺点是,当衍生的动机和工作没有特别指向某个目标,而是更加肤浅和广泛时。上述提到的人工智能的潜在好处并不来自于大量肤浅的模型,而是来自于专门解决困难问题的专业模型。
此外,目前模型开发、部署和广告的速度也带来了一些我们可能已经遇到的缺点。重要的是要解决这些问题,以确保未来取得最佳成果。一些当前人工智能发展速度的潜在缺点:
-
质量: 随着市场速度的加快,已经很难跟上社区的进展并对模型结果和研究论文进行适当的审查。缺点是将会有大量质量低下的模型和服务,因为它们没有经过严格的测试和审查。此外,像置信区间这样的质量指标由于市场速度快而大多被忽视。
-
影响 + 安全: 目前开发的大量模型并不以人为(或自然)为中心,也没有非常有用的目标或用例。然而,每个产品开发都应该始终关注使世界变得更美好。开发者必须关注能够带来积极影响的事物,而不是仅仅开发“另一个聊天机器人”。此外,开发者还必须排除模型可能带来的潜在危害,确保安全(类似于这个提案)。
-
隐私 + 版权: 模型很少被文档化,难以追踪隐私和版权问题如何处理。这对个人可能产生负面影响。在处理敏感数据时,建模是危险的,因为即使是嵌入后的向量数据库也不具备隐私友好性,容易被反向工程(如Morris et al. 2023所示)。此外,新法规如欧盟人工智能法将对这些模型产生影响,强制执行隐私合规。
-
投资损失: 即使市场速度很快,任何人工智能项目都需要资源(高技能工程师、大量计算成本、产品维护)。如果结果的人工智能产品质量低或未能服务于明确的用户目的,业务的投资回报是不确定的。在开发产品之前进行产品发现阶段以预见潜在的投资回报是常见做法。然而,当前市场速度下,这一做法在人工智能领域经常被忽视。
总结来说,AI 开发的快速进展不仅是好事,它也给企业和个人带来了潜在的摩擦和不利影响。

图 2:左侧图表显示了可用 ML 模型的指数增长如何限制未来人类对模型的监督,因为每个模型的时间减少(左)。右侧显示了 AI 领域中可能受此快速发展影响的类别。
通过假设驱动的开发来减少模型膨胀
如前所述,当前对 AI 的兴奋主要是件好事。这个故事的目的不是停止 AI 开发或减缓其速度。正好相反。意图是将积极的兴奋引导到具体目标上,创造质量而不是数量。这个想法是鼓励每位 AI 工程师和数据科学家在每个项目的开始阶段多花一点时间,提出一些基本问题,比如:“谁会从中受益?”和“我们想要达成什么目标?”。
这个故事提出的并不具有革命性,只是遵循科学方法。
与其采取探索性的方法,开始开发下一个 LLM 而没有明确的愿景,从而膨胀社区,不如从最后开始,讨论产品使用案例?例如,可以首先提出项目的目标,例如:“当前的基础 LLM 非常复杂,几乎无法在本地运行。我们希望解决这个问题。”有了这些,项目变得有意义。但意义并不是一切,明确的假设会使工作更加顺利。例如:
“可以训练一个轻量级的 LLM,在本地运行并且在 LLMU 基准测试集上仍能表现>70。”
心中有明确的目标和推导出的假设将有助于简化整个开发工作。这还将有助于衡量成功并为社区做出有意义的贡献。结合文献/模型评审,它将立即指出提议的项目是否已在其他地方实现,因此只是创造了冗余工作。换句话说,这个故事提出的并不具有革命性,只是遵循科学方法。

图 3:此示意图展示了科学项目的框架。它强调在实际开始开发 ML 模型之前,还有几个步骤(假设、文献综述、提案)。
创新的火花
提议使用科学方法的原因在于,科学界对人工智能快速发展速度带来的当前问题已有充分认识。科学家必须从海量的研究论文中学习,以便对研究社区做出有意义的贡献。加上大型研究实验室的速度,很容易产生“没有足够时间阅读一切”的感觉。这种情况以及发表压力已经导致了可重复性危机。科学方法旨在解决这些问题。
科学方法经过几个世纪的发展和改进,处于任何科学项目的核心。鉴于人工智能发展的快速步伐与海量科学文献的情况非常相似,适应这些原则是有意义的。
许多科学突破并非始于实验室,而是始于一个思考,并被形成假设。
作为一个积极的副作用,科学方法不仅用于标准化工作和实验结果,还旨在促进创新。花时间审查现有文献并制定假设是创新的核心。许多科学突破并非始于实验室,而是始于一个思考,并被形成假设。
科学界,例如,提供了预注册的选项。这意味着,科学家在实际进行实验和分析之前,首先发布他们的目标、假设和方法。这一概念也可以应用于人工智能开发。
话虽如此,我强烈鼓励大家在开始任何人工智能或机器学习项目之前,先勾勒出目标和假设!此外,我希望 Hugging Face 以及其他知名*台有一天能要求工程师和科学家在开始工作之前先进行目标和假设的预注册。我相信如果像 Hugging Face 这样的大型*台开始,其他*台也会跟进。
总结
当前人工智能发展的速度既令人兴奋又充满挑战。令人兴奋的是新模型带来的好处,但也因现有模型的数量庞大以及其基本质量、隐私、安全和投资回报的不确定性而具有挑战性。
科学方法,如假设驱动开发,可以帮助解决这些问题,甚至通过确保人工智能/机器学习工程师和数据科学家专注于朝着预定目标和假设进行开发,促进创新。
这是人工智能的时代,因此最重要的是确保我们为所有人创造出最佳的未来。
所有图片,除非另有说明,均由作者提供。
检索增强生成(RAG)的内幕与外延
·
关注 发布于 Towards Data Science · 发送为 通讯 · 阅读时间约 3 分钟 · 2023 年 10 月 12 日
--
当大型语言模型首次出现时,那种兴奋感是显而易见的:除了它们的新奇性,它们还承诺彻底改变众多领域和工作方向。
几乎在 ChatGPT 发布一年后,我们对 LLMs 的局限性以及将它们集成到现实世界产品中所面临的挑战有了更清晰的认识。到现在为止,我们也提出了一些强大的策略来补充和增强 LLMs 的潜力;其中,检索增强生成(RAG)无疑是最突出的。它赋予实践者将预训练模型与外部的最新信息源连接起来,从而生成更准确、更有用的输出。
本周,我们汇集了一系列强大的文章,解释了使用 RAG 的复杂性和实际考虑因素。无论你是深入机器学习领域,还是从数据科学家或产品经理的角度来看待这个话题,对这种方法有更深的了解都可以帮助你为未来 AI 工具的发展做好准备。让我们直接进入正题!
-
使用检索增强生成(RAG)将您自己的数据添加到 LLM 中对于初学者友好的入门介绍,Beatriz Stollnitz最*的深度分析是一个很好的资源,可以访问并收藏以供将来参考。它介绍了 RAG 的理论基础,然后过渡到一个基本的动手实现,展示了如何创建一个聊天机器人,帮助客户找到有关公司销售产品的信息。
-
提升检索增强生成系统性能的 10 种方法如果你已经开始在项目中尝试 RAG,你可能会观察到,设置它是一回事,但让它持续有效并产生预期结果则是另一回事:“RAG 很容易原型化,但很难投入生产。” Matt Ambrogi的指南提供了关于弥合框架潜力与更具实际效益之间差距的务实见解。

- RAG 与细调 — 哪种是提升 LLM 应用的最佳工具? 在构建更好的 AI 产品时,RAG 还有不少替代方案。Heiko Hotz 提供了 RAG 和模型细调的细致且全面的对比,后者是提升通用 LLM 性能的另一种重要策略。最终,正如 Heiko eloquently 表达的那样,“没有一刀切的解决方案;成功在于将优化方法与任务的具体要求对齐。”
关于从反事实见解到动态定价的其他优秀读物,我们希望你能探索一些我们*期的其他亮点:
-
如果你想测试 ChatGPT API 的威力,Mariya Mansurova 分享了一份 使用 ChatGPT API 进行主题建模的入门指南。
-
想提升编程技能吗?Marcin Kozak 的动手教程 处理 Python 中的 NaN (非数字) 值 及如何正确使用它们。
-
Reza Bagheri 带来了一次标志性的深度分析,这次 详细介绍了维度的数学基础(以及由此带来的著名“诅咒”)。
-
要了解反事实及其在数据分析中的位置,不要错过 Maham Haroon 的清晰易懂的讲解。
-
为什么如此多的企业即使没有明确的商业目标也要 赶上生成式 AI 的潮流?Stephanie Kirmer 探讨了这一新兴难题。
-
在探索使用强化学习方法进行动态定价的潜力后,Massimiliano Costacurta 权衡了 为多臂老丨虎丨机解决方案添加上下文的好处。
-
在一个有趣的项目演示中,Caroline Arnold展示了如何利用预训练模型和再分析数据来创建一个自定义的 AI 天气预报应用。
感谢你对我们作者工作的支持!如果你喜欢在 TDS 上阅读的文章,可以考虑成为 Medium 会员——这将解锁我们的所有存档(以及 Medium 上的其他每一篇文章)。
工作车间调度问题:混合整数规划模型
经典排序问题的数学建模与 Pyomo 实现
·
关注 发表在 Towards Data Science ·11 分钟阅读·2023 年 3 月 7 日
--
作业车间调度问题(JSSP)是一个广泛研究的优化问题,具有多种工业应用。目标是定义如何最小化分配共享资源(机器)所需的工期,以完成竞争活动(作业)。与其他优化问题一样,混合整数规划可以成为提供良好解决方案的有效工具,尽管对于大规模实例,可能需要使用启发式方法。
在本文中,你会发现两种最常见的混合整数规划 JSSP 表述,并用pyomo库(Bynum 等,2021)在 Python 中实现。对详细信息感兴趣的读者可以访问本git 仓库获取完整代码。
如果你对混合整数规划或优化不熟悉,阅读这篇介绍后,你可能会有更好的理解。
学习如何使用 scipy 和 pyomo 在 Python 中解决优化问题
towardsdatascience.com
现在让我们深入探讨一下!
问题陈述
假设一组作业J需要在一组机器M中按给定顺序处理。例如,作业编号 1 可能需要在机器(1, 4, 3, 2)中处理,而作业编号 2 则在(2, 3, 4, 1)中处理。在这种情况下,作业 1 在前往机器 4 之前必须先经过机器 1。类似地,作业 2 在前往机器 1 之前必须已经在机器 4 中处理过。
每台机器一次只能处理一个作业。操作由(机器,作业)对定义,每对有一个特定的处理时间p。因此,总的工期取决于如何分配资源来执行任务。
下图展示了一个简单实例的最佳操作顺序,该实例有 5 台机器和 4 个作业。注意每台机器一次只处理一个作业,每个作业一次只由一台机器处理。

三台机器和四个作业的问题示例。(图片由作者提供)。
与其他优化问题一样,我们必须将这些规则转换为数学方程式,以获得智能的资源分配。因此,在接下来的部分中,让我们看看 JSSP 的两种常见表述。
混合整数模型
根据 Ku & Beck (2016)的研究,将介绍两种 JSSP 模型:选择模型(Manne, 1960)和时间索引模型(Bowman, 1959; Kondili, 1993)。有兴趣的读者可以参考 Wagner (1959)的第三种模型(基于排名)。选择模型无疑是三者中最有效的。然而,其他模型在处理实际问题中可能更容易应对新出现的约束条件。
在选择模型中,假设我们有一个作业集J和一个机器集M。每个作业j必须遵循一个处理顺序(σʲ₁, σʲ₂, …, σʲₖ),每个操作(m,j)有一个处理时间p。考虑的决策变量包括作业j在机器m上的开始时间xₘⱼ;一个二元变量标记作业i在机器m上优先于j的情况zₘᵢⱼ;以及操作的总完工时间C,这是最小化的目标。
我们需要创建约束条件来确保:
-
机器m中作业j的开始时间必须大于作业j的上一个操作的开始时间加上其处理时间。
-
每台机器一次只处理一个作业。为此,我们声明,如果i在机器m中优先于j,那么机器m中作业j的开始时间必须大于或等于作业i的开始时间加上其处理时间。
-
每对作业i、j中必须有一个在每台机器m中优先于另一个。
-
总完工时间大于每个操作的开始时间加上其处理时间。
然后我们得到以下模型:

作业车间选择模型。(图片由作者提供)。
其中,V是“非此即彼”约束条件的一个任意大值(大 M)。
下一种探讨的模型将是时间索引模型。它的限制在于只能考虑整数处理时间,并且可以注意到它生成了一个具有多个非零元素的约束矩阵,这使得它在计算上比选择模型更昂贵。此外,随着处理时间的增加,决策变量的数量也会增加。
在时间索引模型中,我们将考虑相同的作业集J和机器集M,以及一个离散时间区间集T。T的大小选择可以参考V的定义:所有处理时间的总和。还将使用相同的作业顺序和处理时间参数。然而,在这种方法中,我们只考虑标记作业j在机器m的时间点t开始的二元变量xₘⱼₜ,以及实际值(或整数)总完工时间C。
让我们制定约束条件:
-
每个作业j在机器m上只能开始一次。
-
确保每台机器一次只处理一个作业。这在时间索引方法中是一个难点。为此,我们声明在当前时间 t 和 pₘⱼ 之前的时间段内,每台机器 m 上最多只能有一个作业 j 开始。对于每台机器和每个时间点。
-
作业 j 在机器 m 上的开始时间必须大于作业 j 上一个操作的开始时间加上其处理时间。
-
总完成时间必须大于每个操作的开始时间加上其处理时间。

作业车间时间索引模型。(作者提供的图片)。
实现
在深入模型之前,让我们创建一些工具类来处理这个问题的参数。第一个将是 JobSequence,一个 Python list 的子类,具有查找序列中前后元素的方法。这在引用每个作业的机器序列时将非常有用。
class JobSequence(list):
def prev(self, x):
if self.is_first(x):
return None
else:
i = self.index(x)
return self[i - 1]
def next(self, x):
if self.is_last(x):
return None
else:
i = self.index(x)
return self[i + 1]
def is_first(self, x):
return x == self[0]
def is_last(self, x):
return x == self[-1]
def swap(self, x, y):
i = self.index(x)
j = self.index(y)
self[i] = y
self[j] = x
def append(self, __object) -> None:
if __object not in self:
super().append(__object)
else:
pass
现在让我们创建一个白标类来处理参数。它必须存储作业集合 J、机器集合 M、每个作业 j 的操作顺序,保存在 JobSequences 的字典中,以及每对 m 和 j 的处理时间,保存在元组索引字典 p_times 中。
class JobShopParams:
def __init__(self, machines, jobs, p_times, seq):
self.machines = machines
self.jobs = jobs
self.p_times = p_times
self.seq = seq
最后,一个从给定的机器数量、作业数量和处理时间区间生成随机问题实例的类。
import numpy as np
class JobShopRandomParams(JobShopParams):
def __init__(self, n_machines, n_jobst, t_span=(1, 20), seed=None):
self.t_span = t_span
self.seed = seed
machines = np.arange(n_machines, dtype=int)
jobs = np.arange(n_jobs, dtype=int)
p_times = self._random_times(machines, jobs, t_span)
seq = self._random_sequences(machines, jobs)
super().__init__(machines, jobs, p_times, seq)
def _random_times(self, machines, jobs, t_span):
np.random.seed(self.seed)
t = np.arange(t_span[0], t_span[1])
return {
(m, j): np.random.choice(t)
for m in machines
for j in jobs
}
def _random_sequences(self, machines, jobs):
np.random.seed(self.seed)
return {
j: JobSequence(np.random.permutation(machines))
for j in jobs
}
现在我们可以轻松地实例化随机问题来验证我们的模型。
在接下来的步骤中,我们将创建三个继承自 pyomo’s ConcreteModel 的类。第一个将是 MIP 模型的白标类。第二个和第三个将分别是分别是非线性模型和时间索引模型类。
import pyomo.environ as pyo
class JobShopModel(pyo.ConcreteModel):
def __init__(self, params, **kwargs):
super().__init__()
self.params = params
self._construct_sets()
self._construct_params()
@property
def seq(self):
return self.params.seq
def _construct_sets(self):
self.M = pyo.Set(initialize=self.params.machines)
self.J = pyo.Set(initialize=self.params.jobs)
def _construct_params(self):
self.p = pyo.Param(self.M, self.J, initialize=self.params.p_times)
self.V = sum(self.p[key] for key in self.p)
可以注意到,作业集合 J 和机器集合 M 存储在同名的实例属性中。属性 p 保存处理时间,V 是合理的完成时间上限。
现在我们来创建不等式模型,即 DisjModel 类。
class DisjModel(JobShopModel):
def __init__(self, params, **kwargs):
super().__init__(params, **kwargs)
self._create_vars()
self._create_cstr()
self.obj = pyo.Objective(rule=self.C, sense=pyo.minimize)
def _create_vars(self):
self.x = pyo.Var(self.M, self.J, within=pyo.NonNegativeReals)
self.z = pyo.Var(self.M, self.J, self.J, within=pyo.Binary)
self.C = pyo.Var(within=pyo.NonNegativeReals)
def _create_cstr(self):
self.cstr_seq = pyo.Constraint(self.M, self.J, rule=cstr_seq)
self.cstr_precede = pyo.Constraint(self.M, self.J, self.J, rule=cstr_precede)
self.cstr_comp_precede = pyo.Constraint(self.M, self.J, self.J, rule=cstr_comp_precede)
self.cstr_total_time = pyo.Constraint(self.J, rule=cstr_total_time)
DisjModel 实例包含了之前描述的变量的属性 x、z 和 C。目标非常简单:最小化其中一个决策变量:C。注意,我们仍然需要定义约束的规则。这些规则按引入模型时列出的顺序定义。现在让我们用 pyomo 风格编写它们。
def cstr_seq(model, m, j):
o = model.seq[j].prev(m)
if o is not None:
return model.x[o, j] + model.p[o, j] <= model.x[m, j]
else:
return pyo.Constraint.Skip
def cstr_precede(model, m, j, k):
if j != k:
return model.x[m, j] + model.p[m, j] <= model.x[m, k] + model.V * (1 - model.z[m, j, k])
else:
return pyo.Constraint.Skip
def cstr_comp_precede(model, m, j, k):
if j != k:
return model.z[m, j, k] + model.z[m, k, j] == 1
else:
return model.z[m, j, k] == 0
def cstr_total_time(model, j):
m = model.seq[j][-1]
return model.x[m, j] + model.p[m, j] <= model.C
我们已经准备好用不等式模型方法解决 JSSP 问题。让我们也定义时间索引模型。
class TimeModel(JobShopModel):
def __init__(self, params, **kwargs):
super().__init__(params, **kwargs)
self.T = pyo.RangeSet(self.V)
self._create_vars()
self._create_cstr()
self.obj = pyo.Objective(rule=self.C, sense=pyo.minimize)
def _create_vars(self):
self.x = pyo.Var(self.M, self.J, self.T, within=pyo.Binary)
self.C = pyo.Var(within=pyo.NonNegativeReals)
def _create_cstr(self):
self.cstr_unique_start = pyo.Constraint(self.M, self.J, rule=cstr_unique_start)
self.cstr_unique_machine = pyo.Constraint(self.M, self.T, rule=cstr_unique_machine)
self.cstr_precede = pyo.Constraint(self.M, self.J, rule=cstr_precede)
self.cstr_total_time = pyo.Constraint(self.J, rule=cstr_total_time)
def _get_elements(self, j):
machines = [x.index()[0] for x in self.x[:, j, :] if x.value == 1]
starts = [x.index()[2] for x in self.x[:, j, :] if x.value == 1]
spans = [self.p[m, j] for m in machines]
return machines, starts, spans
再次说明,约束是按照之前介绍的顺序定义的。我们也将用 pyomo 风格编写这些约束。
def cstr_unique_start(model, m, j):
return sum(model.x[m, j, t] for t in model.T) == 1
def cstr_unique_machine(model, m, t):
total = 0
start = model.T.first()
for j in model.J:
duration = model.p[m, j]
t0 = max(start, t - duration + 1)
for t1 in range(t0, t + 1):
total = total + model.x[m, j, t1]
return total <= 1
def cstr_precede(model, m, j):
o = model.seq[j].prev(m)
if o is None:
prev_term = 0
else:
prev_term = sum(
(t + model.p[o, j]) * model.x[o, j, t]
for t in model.T
)
current_term = sum(
t * model.x[m, j, t]
for t in model.T
)
return prev_term <= current_term
def cstr_total_time(model, j):
m = model.seq[j][-1]
return sum((t + model.p[m, j]) * model.x[m, j, t] for t in model.T) <= model.C
我们已经准备好测试这些模型在一些随机生成的问题中的表现了!
结果
让我们实例化一个随机的 4x3 (JxM) 问题,看看我们的模型表现如何。
params = JobShopRandomParams(3, 4, seed=12)
disj_model = DisjModel(params)
time_model = TimeModel(params)
为了解决这些示例,我将使用开源解算器 CBC。你可以从AMPL或这个链接下载 CBC 二进制文件。你还可以在这里找到安装教程。由于 CBC 可执行文件已包含在我的系统的 PATH 变量中,我可以在不指定可执行文件路径的情况下实例化解算器。如果你的系统中没有设置 PATH 变量,请使用关键字参数“executable”并提供可执行文件的路径。
另外,也可以使用 GLPK 来解决这个问题(或任何其他兼容pyomo的解算器)。最新的 GLPK 版本可以在这里找到,Windows 可执行文件可以在这里找到。
# Time limited in 20 seconds
solver = pyo.SolverFactory("cbc", options=dict(cuts="on", sec=20))
# solve
res_disj = solver.solve(disj_model, tee=False)
res_time = solver.solve(time_model, tee=False)
解算器在不到一秒的时间内顺利找到了判别模型的最优解,并证明了其最优性。

判别模型的结果。(图像由作者提供)。
然而,我们可以看到,即使对于这个简单的问题,解算器在 20 秒的限制内也未能找到时间索引模型的最优解。

时间索引模型的结果。(图像由作者提供)。
令人惊讶的是,通过重新排列数学方程式,两个具有相同思想的模型在性能上的差异。
顺便提一下,感兴趣的朋友可以在这个仓库中找到完整的代码(包括图表)。
进一步阅读
对于较大的实例,由于问题的组合性质,即使是高性能的商业解算器,如 Gurobi 或 Cplex,也可能难以提供高质量的解决方案并证明最优性。在这种情况下,元启发式算法可能是一个有趣的替代方案。我建议感兴趣的读者查阅论文“具有路径重连的并行 GRASP 用于作业车间调度”(Aiex et al., 2003)和“带有偏随机关键遗传算法的扩展 Akers 图形方法用于作业车间调度”(Gonçalves & Resende, 2014)。我最*尝试实现了这些算法的简化版本,并获得了一些有趣的结果,尽管纯 Python 实现仍然耗时。你可以在这个仓库中找到它们。
结论
在这篇文章中,实施并解决了两种不同的混合整数规划方法用于车间调度问题(JSSP),使用了 Python 库 pyomo 和开源求解器 CBC。尽管更复杂的现实世界模型可能与用于整合附加规则的时间索引形式类似,但离散模型被证明是原始 JSSP 的更好替代方案。用于这些示例的 完整代码 供进一步使用。
参考文献
Aiex, R. M., Binato, S., & Resende, M. G. (2003). 用于车间调度的并行 GRASP 与路径重联。并行计算,29(4),393–430。
Bynum, M. L. 等,2021。Python 中的 Pyomo 优化建模。Springer。
Gonçalves, J. F., & Resende, M. G. (2014). 一种扩展的 Akers 图形方法与带偏差的随机密钥遗传算法用于车间调度。国际运筹研究杂志,21(2),215–246。
Kondili, E., & Sargent, R. W. H. (1988). 用于批量操作调度的通用算法(第 62–75 页)。帝国学院化学工程系。
Ku, W. Y., & Beck, J. C. (2016). 用于车间调度的混合整数规划模型:计算分析。计算机与运筹研究,73,165–173。
Manne, A. S. (1960). 车间调度问题。运筹研究,8(2),219–223。
Wagner, H. M. (1959). 一个用于机器调度的整数线性规划模型。海军研究物流季刊,6(2),131–140。
Kaggle 蓝图:解锁数据科学竞赛获胜方法
Kaggle 蓝图
一系列分析 Kaggle 竞赛获胜解决方案的文章,以便我们可以将其应用于自己的数据科学项目
·发表于 Towards Data Science ·阅读时间 2 分钟·2023 年 3 月 1 日
--

Kaggle 蓝图(作者提供的图片)
如果你问任何成功的 Kaggle 参赛者,他们有什么建议来提升你的数据科学技能,他们都会给出相同的答案。他们会告诉你去学习已完成 Kaggle 竞赛的顶级解决方案。
Kaggle 是一个数据科学竞赛的*台,涉及各种类型的问题。竞争者通过构建机器学习模型并提交他们的预测来竞争。预测最准确的竞争者将获得奖励。
尽管竞争激烈,Kaggle 社区培养了一种学习的心态。*台本身鼓励在竞赛期间及之后公开分享方法。
因此,一个完成的 Kaggle 竞赛是最先进的机器学习技术学习资源的汇集。
我们可以区分两种类型的资源:
-
竞赛期间分享的方法(以讨论或笔记本形式):展示各种不同方法来解决问题的资源
-
竞赛截止后分享的解决方案(以高层次的总结和 GitHub 上的代码形式):展示哪些技术在问题上表现良好的资源
虽然这些资源本身通常结构良好,但在竞赛结束后,可能很难在众多资源中提取相关信息。
因此,本系列文章回顾并总结了在 Kaggle 竞赛中使用的最受欢迎和成功的技术。但它不会回顾特定问题设置的确切解决方案。相反,我们将分析 Kaggle 竞赛的获胜解决方案,并提取出我们可以应用于数据科学项目的“蓝图”。
… 我们将分析 Kaggle 竞赛的获胜解决方案,并提取出我们可以应用于数据科学项目的“蓝图”。
如果你不想错过本系列中的新文章,你可以免费订阅以便每次我发布新故事时获得通知。
你可以在这里找到本系列文章的集合:

Kaggle 蓝图:解锁获胜的方法
查看列表6 篇故事!


气体动力学理论:理想气体分子的动力学建模
统计力学
开发一个框架来模拟和可视化分子碰撞,并使用 Python 提取热力学洞察。
·
关注 发布于 Towards Data Science ·14 分钟阅读·2023 年 1 月 6 日
--
图片由 Terry Vlisidis 提供,来自 Unsplash
介绍
想象有人不断地将球扔向你的头部。根据球的大小和质量,你可能会感受到从轻微的烦恼到难以忍受的痛苦。然而,实际上有无数个这样的“球”每时每刻撞击着你,不仅是你的头部,还有你的整个身体,而你却没有感觉到。空气中的氮气、氧气和水分子在你周围不断地随机运动,即使空气看起来是静止的。这些分子与您的碰撞的结果是空气对您施加了一定的压力(称为大气压力)。由于我们“习惯”了大气压力,它似乎没有什么特别的,但如果你曾在飞机上或高压舱中,压力偏离了大气压力,你可能会注意到不同的感觉,比如耳朵的“咔嗒”声,这些都是你身体对压力变化的反应。
17 世纪,罗伯特·波义耳给出了对压力的宏观理解,以及压力如何随气体占据的体积变化,这被称为波义耳定律。然而,直到一个世纪后,丹尼尔·伯努利才提供了一个定性的分子图像来解释压力,并将其与分子的温度和动能联系起来。然而,这一“动理论”直到 19 世纪,詹姆斯·克拉克·麦克斯韦在鲁道夫·克劳修斯的工作基础上,将其形式化为统计定律,并且路德维希·玻尔兹曼阐明了其与熵的关系,导致了麦克斯韦-玻尔兹曼速度分布的公式。这最终导致了从分子角度出发的详细气体理论的发展,这与广泛理解的宏观观点相一致。
在这篇文章中,我们将尝试通过使用 Python 进行一系列数值模拟,将气体的分子图像与其宏观性质——即压力、体积和温度——联系起来。
理论背景
气体的动理论
气体的动理论是一个模型,描述了处于不断随机运动中的气体分子。
-
分子在直线中运动,直到被其他分子或容器壁的弹性碰撞打断。弹性意味着在碰撞中没有动能损失。
-
分子之间没有“相互作用”,即它们不施加任何吸引力或排斥力。
-
与气体占据的体积相比,分子本身占据的体积被认为是可以忽略不计的。
如果遵循这些规则的分子被取样并测量其速度,它们将符合麦克斯韦-玻尔兹曼分布。气体的温度实际上是一个基于分布形状定义的属性。*均动能越高(或分布越*坦),气体的温度越高,反之亦然。定义单个分子的温度是没有意义的;相反,它是一个由分子集合的*均属性。
请注意,气体动理论是一个模型,它不一定真实反映现实(这与所有科学模型的情况类似)。分子碰撞并非严格弹性,分子确实存在短程相互作用。然而,具有这些相对简单假设的模型能够非常好地模拟理想气体的性质,这对于预测气体在各种条件下的行为是一个非常有用的宏观模型。
理想气体定律
理想气体是一个假设的气体,其性质通过一个简单的状态方程(即理想气体定律)相关联。

这里 P 指的是气体的压力,V 指的是容器的体积,T 是温度,n 是气体的摩尔数。如果这三个量中的任何一个保持不变,则第四个量也不会改变,这一点通过 R 体现,R 是通用气体常数。理想气体定律是一个准确的模型,适用于高温或低压下的气体,因为在这些条件下气体动理论的大多数假设是成立的。
我们将基于理想气体定律用我们的模型测试两个关系。
-
在恒定温度和摩尔数下,压力与体积成反比。
-
在恒定体积和摩尔数下,压力与温度成正比。
前者被称为波义耳定律,后者被称为盖-吕萨克定律,这些定律都以最早通过实验发现这些变量之间关系的研究人员命名。
设置计算模型
为了根据气体动理论模拟分子的运动,我们需要建立一个具有碰撞的动态 n 体模拟。请注意,本文章的目标是制作一个用于教学目的的模拟。因此,代码的设置旨在最大化理解,而非执行速度。
定义分子属性
我们从导入所有必需的模块开始。
import os
import sys
import time
import numpy as np
import scipy as sci
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
首先,我们将定义一个名为 Molecule 的类。该类的对象将存储如分子的质量、位置和速度等属性。我们还定义了一个名为 color 的属性,可以用来区分不同的气体并设置动画中点的颜色。
class Molecule(object):
#Constructor
def __init__(self,molecule,position,velocity,mass,color="black"):
self.molecule=molecule
self.position=np.array([x_i for x_i in position])
self.velocity=np.array([v_i for v_i in velocity])
self.mass=mass
self.color=color
#Setters for position, velocity, mass and color
def set_position(self,position):
self.position=np.array([x_i for x_i in position])
def set_velocity(self,velocity):
self.velocity=np.array([v_i for v_i in velocity])
def set_color(self,color):
self.color=color
def set_mass(self,mass):
self.mass=mass
#Getters for position, velocity, mass and color
def get_position(self):
return self.position
def get_velocity(self):
return self.velocity
def get_color(self):
return self.color
def get_mass(self):
return self.mass
向箱子中添加分子
接下来,我们创建一个模拟类并定义关键输入参数,例如模拟盒子的尺寸,这最终控制气体的体积。我们还初始化用于记录的变量,如墙体动量,以便我们能够计算压力。
class Simulation(object):
#Constructor
def __init__(self,name,box_dim,t_step,particle_radius):
#Set simulation inputs
self.name=name #Name of the simulation
self.box_dim=[x for x in box_dim] #Dimensions of the box
self.t_step=t_step #Timestep
self.particle_radius=particle_radius #Radius of the particles
#Calculate volume and number of dimensions
self.V=np.prod(self.box_dim) #Area/Volume of the box
self.dim=int(len(box_dim)) #Number of dimensions (2D or 3D)
#Initialize paramters
self.molecules=[] #Create empty list to store objects of class Molecule
self.n_molecules=0 #Create variable to store number of molecules
self.wall_collisions=0 #Create variable to store number of wall collisions
self.wall_momentum=0 #Create variable to store net momentum exchanged with wall
要添加分子,首先需要初始化它们的位置和速度。对于这两者,我们可以定义函数,根据用户输入的分布创建值数组。请注意,初始化的位置和速度遵循什么分布并不重要(只要没有不符合物理规律的情况,比如分子在盒子外)。我们将看到速度最终遵循相同的分布。
#Still in Simulation class
def _generate_initial_positions(self,n,dist="uniform"):
#Uniform distribution
if dist=="uniform":
_pos=np.random.uniform(low=[0]*self.dim,high=self.box_dim,size=(n,self.dim))
#Store positions in temporary variable
self._positions=_pos
def _generate_initial_velocities(self,n,v_mean,v_std,dist="normal"):
#Normal distribution with mean v_mean and std v_std
if dist=="normal":
self.v_mean=v_mean
self.v_std=v_std
_vel=np.random.normal(loc=v_mean,scale=v_std,size=(n,self.dim))
#Uniform distribution with lower bound v_mean and higher bound v_std
if dist=="uniform":
self.v_mean=v_mean
self.v_std=v_std
_vel=np.random.uniform(low=v_mean,high=v_std,size=(n,self.dim))
#All velocities equal to v_mean
if dist=="equal":
self.v_mean=v_mean
self.v_std=v_std
_vel=v_mean*np.ones((n,self.dim))
#Randomly switch velocities to negative with probability 0.5
for i in range(_vel.shape[0]):
for j in range(_vel.shape[1]):
if np.random.uniform() > 0.5:
_vel[i,j]=-_vel[i,j]
#Store velocities in temporary variable
self._velocities=_vel
现在是时候将分子添加到盒子中了。我们调用之前定义的两个函数生成初始位置和速度的数组,创建分子类的对象,并将它们分配给这些对象。
#Still in Simulation class
def add_molecules(self,molecule,n,v_mean,v_std,pos_dist="uniform",v_dist="normal",color="black"):
#Generate initial positions and velocities
self._generate_initial_positions(n,dist=pos_dist)
self._generate_initial_velocities(n,v_mean,v_std,dist=v_dist)
#Initialize objects of class Molecule in a list (set mass to 1 as default)
_add_list=[Molecule(molecule,position=self._positions[i,:],velocity=self._velocities[i,:],color=color,mass=1) for i in range(n)]
self.molecules.extend(_add_list)
self.n_molecules+=len(_add_list)
最后,我们编写一个用于一般记录的函数,即制作矩阵来存储位置和速度向量以及它们的大小。我们还制作一个距离矩阵,用于存储每对分子之间的距离。这将有助于检测碰撞。
#Still in Simulation class
def make_matrices(self):
#Make empty matrices to store positions, velocities, colors, and masses
self.positions=np.zeros((self.n_molecules,self.dim))
self.velocities=np.zeros((self.n_molecules,self.dim))
self.colors=np.zeros(self.n_molecules,dtype="object")
self.masses=np.zeros(self.n_molecules)
#Iterate over molecules, get their properties and assign to matrices
for i,m in enumerate(self.molecules):
self.positions[i,:]=m.get_position()
self.velocities[i,:]=m.get_velocity()
self.colors[i]=m.get_color()
self.masses[i]=m.get_mass()
#Make vectors with magnitudes of positions and velocities
self.positions_norm=np.linalg.norm(self.positions,axis=1)
self.velocities_norm=np.linalg.norm(self.velocities,axis=1)
#Make distance matrix
self.distance_matrix=np.zeros((self.n_molecules,self.n_molecules))
for i in range(self.distance_matrix.shape[0]):
for j in range(self.distance_matrix.shape[1]):
self.distance_matrix[i,j]=np.linalg.norm(self.positions[i,:]-self.positions[j,:])
#Set diagonal entries (distance with itself) to a high value
#to prevent them for appearing in the subsequent distance filter
np.fill_diagonal(self.distance_matrix,1e5)
检测和处理碰撞
现在我们进入模拟的核心部分,即分子动力学建模。碰撞物理定义在二维*面上,但通过少量修改可以扩展到三维盒子。我们定义了一个函数,通过一个三项检查表进行处理:
-
检查是否存在分子-分子碰撞,如果有,则相应更新速度
-
根据分子的速度更新位置
-
检查是否存在分子-墙体碰撞,如果有,则相应更新速度和墙体动量
为了筛选出足够接*以发生碰撞的分子,我们遍历距离矩阵并返回距离小于其半径总和的分子对的索引对。分子可能在此截止范围内,却仍然相互远离。因此,我们应用另一个标准(见图 1)来检查分子是否相互靠*。如果是,我们根据图 1中给出的方程更新它们的速度。我们通过保持分子的动能和线动量(沿碰撞轴方向)来得到这些方程。

图 1:示意图显示了两个球体在二维*面上的碰撞。蓝色的变量和方程代表速度的切向和法向分量(相对于碰撞轴)。粗体变量是向量,其余的是标量。尖括号表示点积(内积)。
更新分子的位置非常简单。我们将速度向量与时间步长的乘积加到先前的位置,以获得新位置。为了识别与墙壁的碰撞,我们只需检查分子的新位置是否超过了盒子的下限或上限。如果是,我们就改变垂直于墙壁的速度的符号,并根据该速度设置新位置。此外,我们将该速度的两倍大小添加到跟踪与墙壁交换动量的变量中。
#Still in Simulation class
def update_positions(self):
#1: Check molecule collisions
#Find molecule pairs that will collide
collision_pairs=np.argwhere(self.distance_matrix < 2*self.particle_radius)
#If collision pairs exist
if len(collision_pairs):
#Go through pairs and remove repeats of indices
#(for eg., only consider (1,2), remove (2,1))
pair_list=[]
for pair in collision_pairs:
add_pair=True
for p in pair_list:
if set(p)==set(pair):
add_pair=False
break
if add_pair:
pair_list.append(pair)
#For every remaining pair, get the molecules, positions, and velocities
for pair in pair_list:
m_1=self.molecules[pair[0]]
m_2=self.molecules[pair[1]]
x_1=m_1.get_position()
x_2=m_2.get_position()
u_1=m_1.get_velocity()
u_2=m_2.get_velocity()
#Check if molecules are approaching or departing
approach_sign=np.sign(np.dot(u_1-u_2,x_2-x_1))
#If molecules are approaching
if approach_sign == 1:
#Get masses
ms_1=m_1.get_mass()
ms_2=m_2.get_mass()
#Calculate final velocities
v_1=u_1 - 2*ms_2/(ms_1 + ms_2) * (np.dot(u_1-u_2,x_1-x_2)/np.linalg.norm(x_1-x_2)**2) * (x_1 - x_2)
v_2=u_2 - 2*ms_1/(ms_1 + ms_2) * (np.dot(u_2-u_1,x_2-x_1)/np.linalg.norm(x_2-x_1)**2) * (x_2 - x_1)
#Update velocities of the molecule objects
m_1.set_velocity(v_1)
m_2.set_velocity(v_2)
#2: Update positions
#Iterate over all the molecule objects
for i,m in enumerate(self.molecules):
#Get the position, velocity, and mass
_x=m.get_position()
_v=m.get_velocity()
_m=m.get_mass()
#Calculate new position
_x_new=_x + _v * self.t_step
#3: Check wall collisions
#Check collisions with the top and right walls
_wall_diff=_x_new - np.array(self.box_dim)
#If wall collisions present
if _wall_diff[_wall_diff>=0].shape[0] > 0:
#Increment collision counter
self.wall_collisions+=1
#Check whether collision in x or y direction
_coll_ind=np.argwhere(_wall_diff>0)
#For component(s) to be reflected
for c in _coll_ind:
#Reflect velocity
_v[c]=-_v[c]
#Increment wall momentum
self.wall_momentum+=2*_m*np.abs(_v[c])
#Update velocity
m.set_velocity(_v)
#Update position based on new velocity
_x_new=_x + _v * self.t_step
#Check collisions with the bottom and left walls
if _x_new[_x_new<=0].shape[0] > 0:
#Increment collision counter
self.wall_collisions+=1
#Check whether collision in x or y direction
_coll_ind=np.argwhere(_x_new<0)
#For component(s) to be reflected
for c in _coll_ind:
#Reflect velocity
_v[c]=-_v[c]
#Increment wall momentum
self.wall_momentum+=2*_m*np.abs(_v[c])
#Update velocity
m.set_velocity(_v)
#Update position based on new velocity
_x_new=_x + _v * self.t_step
#Update position of the molecule object
m.set_position(_x_new)
#Construct matrices with updated positions and velocities
self.make_matrices()
就这样,困难的部分完成了!最后,我们需要编写一个函数来运行模拟。这涉及到在循环中调用之前定义的函数,该循环运行指定次数,计算基于指定的模拟时间和时间步长。
#Still in Simulation class
def safe_division(self,n,d):
if d==0:
return 0
else:
return n/d
def run_simulation(self,max_time):
#Print "Starting simulation"
print("Starting simulation...")
#Make matrices
self.make_matrices()
#Calculate number of iterations
self.max_time=max_time
self.n_iters=int(np.floor(self.max_time/self.t_step))
#Make tensors to store positions and velocities of all molecules at each timestep f
self.x_dynamics=np.zeros(((self.n_molecules,self.dim,self.n_iters)))
self.v_dynamics=np.zeros((self.n_molecules,self.n_iters))
#In each iteration
for i in range(self.n_iters):
#Save positions and velocities to the defined tensors
self.x_dynamics[:,:,i]=self.positions
self.v_dynamics[:,i]=self.velocities_norm
#Calculate rms velocity
self.v_rms=np.sum(np.sqrt(self.velocities_norm**2))/self.velocities_norm.shape[0]
#Print current iteration information
_P=self.safe_division(self.wall_momentum,i*self.t_step*np.sum(self.box_dim))
print("Iteration:{0:d}\tTime:{1:.2E}\tV_RMS:{2:.2E}\tWall Pressure:{3}".format(i,i*self.t_step,self.v_rms,_P))
#Call the update_positions function to handle collisions and update positions
self.update_positions()
#Caclulate final pressure
self.P=self.wall_momentum/(self.n_iters*self.t_step*np.sum(self.box_dim))
print("Average pressure on wall: {0}".format(self.P))
return self.P
这就结束了涉及模拟物理的代码。然而,运行模拟是没有乐趣的,如果你不能可视化它。让我们利用 matplotlib 创建模拟的动画。
动画模拟框
我们从编写一个函数开始,创建一个与提供的盒子尺寸一致的纵横比的图形。
#Still in Simulation class
def create_2D_box(self):
fig=plt.figure(figsize=(10,10*self.box_dim[1]/self.box_dim[0]),dpi=300)
return fig
我们将使用 matplotlib 中的 Animation 模块来制作动画。为了利用这一点,我们需要定义一个函数,该函数将迭代次数(帧)作为输入并创建一个图表。这个函数然后作为参数提供给 Animation 模块中的 FuncAnimation 函数。
#Still in Simulation class
def show_molecules(self,i):
#Clear axes
plt.cla()
#Plot a line showing the trajectory of a single molecule
plt.plot(self.x_dynamics[0,0,:i+1],self.x_dynamics[0,1,:i+1],color="red",linewidth=1.,linestyle="-")
#Plot a single molecule in red that is being tracked
plt.scatter(self.x_dynamics[0,0,i],self.x_dynamics[0,1,i],color="red",s=20)
#Plot the rest of the molecules
plt.scatter(self.x_dynamics[1:,0,i],self.x_dynamics[1:,1,i],color=self.colors[1:],s=20)
#Remove ticks on the plot
plt.xticks([])
plt.yticks([])
#Set margins to 0
plt.margins(0)
#Set the limits of the box according to the box dimensions
plt.xlim([0,self.box_dim[0]])
plt.ylim([0,self.box_dim[1]])
def make_animation(self,filename="KTG_animation.mp4"):
#Call the function to create the figure
fig=self.create_2D_box()
#Create the animation
anim=FuncAnimation(fig,self.show_molecules,frames=self.n_iters,interval=50,blit=False)
#Save animation as a file
anim.save(filename,writer="ffmpeg")
还有另一种动画,我们可以制作,显示每次迭代的速度直方图。这将允许我们观察速度分布收敛到麦克斯韦-玻尔兹曼分布的过程。
#Still in Simulation class
def plot_hist(self,i):
#Clear axes
plt.cla()
#Make histogram
plt.hist(self.v_dynamics[:,i],density=True,color="plum",edgecolor="black")
#Define axis limits
plt.xlim([0,3])
plt.ylim([0,3])
def make_velocity_histogram_animation(self,filename="KTG_histogram.mp4"):
#Create empty figure
fig=plt.figure(figsize=(5,5),dpi=500)
#Create animation
anim_hist=FuncAnimation(fig,self.plot_hist,frames=self.n_iters,interval=50,blit=False)
#Save animation
anim_hist.save(filename,writer="ffmpeg")
运行模拟
是时候收获我们的辛勤工作成果了。以面向对象的方式编写代码所花费的努力现在将得到回报,因为我们现在有了一个通用的求解器,并且可以用仅几行代码运行具有不同输入参数的模拟。
if __name__=="__main__":
#Create simulation object and define input parameters
sim=Simulation(name="kinetic_theory_simulation",box_dim=[1.0,1.0],t_step=1e-2,particle_radius=1e-2)
#Add N2 molecules to the box
sim.add_molecules("N2",n=100,v_mean=1.0,v_std=0.2,v_dist="normal")
#Run the simulation and store the pressure output in P
P=sim.run_simulation(15)
#Make the box animation
sim.make_animation()
#Make the histogram animation
sim.make_velocity_histogram_animation()
下面显示了与上述模拟相对应的模拟框和速度直方图的动画。在第一个图中,显示了盒子中分子的运动及其碰撞,一个选定分子的轨迹以红色突出显示,以作说明。在第二个图中,显示了盒子中所有分子在每次迭代时的速度直方图,从动画中可以清楚地看到,最初的高斯分布(如规定的)变化为具有较窄左尾和较宽右尾的分布,模拟了麦克斯韦-玻尔兹曼分布的特征。可以使用更严格的统计测试来定量支持这一点。
图 1: 模拟框的动画
图 2: 速度直方图的动画
提取热力学见解
我们回到将各种热力学变量相互关联的理想气体定律。如前所述,我们测试了两个关系——压力与体积的关系以及压力与温度的关系。我们保持盒子中的分子数量在所有后续模拟中不变。这三个变量——压力、体积和温度——的计算方法如下:压力是整个模拟过程中与墙壁交换的净动量除以总模拟时间和盒子周长的乘积。体积定义为盒子的长和宽的乘积(技术上是面积,因为我们在二维中工作,但这些见解可以轻松地推广到三维)。
定义温度较为复杂——由于温度与盒子中分子的*均动能成正比,我们将初始分布的*均速度的*方视为温度的代理。为了消除这种估计中的任何随机性,这些模拟中分子的初始速度被设置为一个指定的单一值。例如,如果指定值为 1 m/s,那么所有分子的初始速度要么是+1 m/s,要么是-1 m/s。这确保了初始的总动能有一个明确定义的值,并且在所有具有相同温度的模拟中保持不变。本质上,当两个模拟中的温度相同时,它们的初始总动能也相同,这应该确保在模拟过程中*均动能也相同。
模拟结果见图 2。在恒定温度下,盒子墙壁上的*均压力随着体积倒数的增加而线性增加(见图 2a)。每条等温线的斜率与温度成正比,与理想气体定律一致。在第二组模拟中,观察到在恒定体积下,压力随着温度的增加而线性增加(见图 2b)。在这种情况下,每条等容线的斜率与体积成反比,也与理想气体定律一致。因此,这些微观模拟能够重现与理想气体定律等宏观理论一致的热力学变量趋势。

图 2: (a) 不同温度下壁面*均压力与体积倒数的变化,(b) 不同体积下壁面*均压力与温度的变化
结论
本文中呈现的 n 体模拟是一个简单的分子动力学模拟示例,没有任何交互作用。当然,这对分子如何相互作用的描述是极其简化的,但正如我们所见,它足以预测理想气体的性质。然而,理想气体假设在计算工程应用中气体的性质时很少被使用,例如在蒸汽涡轮中的蒸汽膨胀。需要更复杂的状态方程模型来准确模拟这样的过程,这些模型包括分子之间的相互作用。在本代码中添加分子间的短程相互作用可以更好地再现这些模型对真实气体预测的趋势。此外,使用类似 Lennard-Jones 的势能和添加温控器也可以允许预测液体的性质。
本模拟的完整代码可以在GitHub上获取。如果你有任何问题、建议或评论,请随时通过电子邮件或Twitter与我联系。除非另有说明,所有图片均由作者提供。
要了解更多关于气体动理论的历史,请参阅:
S. Brush, 气体动理论的历史 (2004)
位置语言:评估生成式 AI 的地理编码能力
一个应用项目,详细介绍了大型语言模型在地理编码中的表现,并与现代地理编码 API 进行了比较
·
关注 发表在 Towards Data Science · 9 分钟阅读 · 2023 年 9 月 13 日
--
图片由 Sylwia Bartyzel 提供,发布在 Unsplash
现代科技的便利性在于,你可以在手机的搜索栏中输入一个位置,看到它自动显示在地图上,这种功能往往被视为理所当然。但你是否曾经停下来想过,这种文本与地图之间的无缝交互和转换到底是如何工作的?这个问题的答案就是地理编码。
Esri,全球地理空间软件领域的领导者,将地理编码定义为 “将位置描述(如坐标对、地址或地点名称)转换为地球表面上的位置的过程。” 地理编码是赋予导航应用、地图服务和地理信息科学(GIScience)*台高级功能的基础。Esri、Google、Mapbox 等各种提供商都提供地理编码 API,能够接受位置描述并返回相应的纬度和经度值或坐标,使我们能够从空间角度思考数据。
随着生成型 AI 和大型语言模型(LLMs)的兴起,如 OpenAI 的 GPT、Google 的 Bard 或 Meta 的 LLaMA,为地理空间应用技术的使用带来了极好的机会。这些应用范围广泛,从使用 GitHub 的 Copilot 进行代码生成,到使用 Meta 的 Segment Anything Model(SAM)进行图像分割,甚至可能涉及地理编码。
本文将考察利用“开箱即用”的生成型 AI 进行地理编码非结构化位置描述的适用性。此评估将在来自明尼苏达州的小型车辆事故数据集上进行。通过这项分析,将探索标准 LLMs 在地理编码任务中的有效性及生成型 AI 在地理空间中的潜在应用案例,相较于现有的传统方法。
理解地理编码及其与 AI 的集成 现代地理编码器由两个基本组件组成,即参考数据集和地理编码算法。参考数据通常包含附加到地理位置的地方的明确和相对描述,这意味着不仅是诸如地址等明确描述与位置相关联,而且地方的非结构化描述也与位置相关联。然后,可以使用匹配算法来找到输入描述与参考数据集中包含的描述之间的合适匹配。一个简单的匹配算法示例可能是使用插值算法,通过估算两个已知地址之间的位置来确定街道地址的位置。
预测性地理编码的概念,利用 AI 和机器学习来增强地理编码过程,已经有了悠久的历史。包括自然语言处理(NLP)和深度学习在内的技术已经被提出和利用,取得了不同程度的成功。AI 和机器学习在地理编码中的应用并不是*期发展。然而,生成性 AI 的出现为地理编码带来了新的前沿,就像它对许多其他领域一样。
应对挑战与探索未来机会 正如你所知,LLMs 使用从互联网、书籍、期刊文章以及其他各种来源中提取的大量文本数据进行训练。这些数据通常,甚至总是,缺乏全面的地理空间信息。这种地理空间训练数据的缺乏对 LLMs 在理解和解决地理空间挑战方面的潜力和适用性有影响。在没有基础领域特定知识的情况下,我们如何期望一个模型在复杂问题上表现良好?
答案是我们根本做不到。
在本分析中,我评估了 LLMs 作为单独基准的适用性,以及在使用传统 GIScience 方法的工作流程中的适用性。结果强调了一个熟悉的观点——虽然新技术可能令人印象深刻,但在解决复杂挑战时,它并不总能带来性能提升。
案例研究:车祸的非结构化位置描述
数据收集与准备 为了测试和量化 LLMs 的地理编码能力,从一个从网站抓取的数据集中随机选择了 100 个明尼苏达州车辆事故的非结构化位置描述。所有 100 个事故的真实坐标通过使用各种地图应用程序,如谷歌地图和明尼苏达交通部的交通地图应用(TMA),手动创建。
一些示例位置描述如下所示。
美国 71 号公路与明尼苏达 60 号公路交汇处,温多姆,棉花县
EB 10 号高速公路靠* Joplin St NW,厄尔克河,舍伯恩县
EB I 90 / HWY 22,福斯特镇,法里博县
高速公路 75 号里程碑 403,圣文森特镇,基特森县
65 号高速公路 / 国王路,布伦瑞克镇,卡纳贝克县
如上述示例所示,描述的结构以及定义位置的方式有多种可能性。例如,第四个描述包含一个里程标记号,这在任何地理编码过程中都不太可能匹配,因为该信息通常不包含在任何参考数据中。像这样的描述的真实坐标的确定很大程度上依赖于明尼苏达州交通部的线性参考系统(LRS),该系统提供了一种标准化的方法来测量全州的道路,其中里程标记起着重要作用。此数据可以通过前面提到的 TMA 应用程序访问。
方法论与地理编码策略 在准备数据后,设置了五个单独的笔记本来测试不同的地理编码过程。它们的配置如下。
1. Google 地理编码 API,用于原始位置描述
2. Esri 地理编码 API,用于原始位置描述
3. Google 地理编码 API,用于 OpenAI GPT 3.5 标准化位置描述
4. Esri 地理编码 API,用于 OpenAI GPT 3.5 标准化位置描述
5. OpenAI GPT 3.5,作为地理编码器本身使用
总结来说,Google 和 Esri 的地理编码 API 被用于原始描述以及使用传递给 OpenAI GPT 3.5 模型的简短提示进行标准化的描述。这个标准化过程的 Python 代码可以在下面看到。
def standardize_location(df, description_series):
df["ai_location_description"] = df[description_series].apply(_gpt_chat)
return df
def _gpt_chat(input_text):
prompt = """Standardize the following location description into text
that could be fed into a Geocoding API. When responding, only
return the output text."""
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": prompt},
{"role": "user", "content": input_text},
],
temperature=0.7,
n=1,
max_tokens=150,
stop=None,
)
return response.choices[0].message.content.strip().split("\n")[-1]
使用地理编码 API 的四个测试案例使用了下面的代码来向各自的地理编码器发出 API 请求,并返回所有 100 个描述的结果坐标。
# Esri Geocoder
def geocode_esri(df, description_series):
df["xy"] = df[description_series].apply(
_single_esri_geocode
)
df["x"] = df["xy"].apply(
lambda row: row.split(",")[0].strip()
)
df["y"] = df["xy"].apply(
lambda row: row.split(",")[1].strip()
)
df["x"] = pd.to_numeric(df["x"], errors="coerce")
df["y"] = pd.to_numeric(df["y"], errors="coerce")
df = df[df["x"].notna()]
df = df[df["y"].notna()]
return df
def _single_esri_geocode(input_text):
base_url = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates"
params = {
"f": "json",
"singleLine": input_text,
"maxLocations": "1",
"token": os.environ["GEOCODE_TOKEN"],
}
response = requests.get(base_url, params=params)
data = response.json()
try:
x = data["candidates"][0]["location"]["x"]
y = data["candidates"][0]["location"]["y"]
except:
x = None
y = None
return f"{x}, {y}"
# Google Geocoder
def geocode_google(df, description_series):
df["xy"] = df[description_series].apply(
_single_google_geocode
)
df["x"] = df["xy"].apply(
lambda row: row.split(",")[0].strip()
)
df["y"] = df["xy"].apply(
lambda row: row.split(",")[1].strip()
)
df["x"] = pd.to_numeric(df["x"], errors="coerce")
df["y"] = pd.to_numeric(df["y"], errors="coerce")
df = df[df["x"].notna()]
df = df[df["y"].notna()]
return df
def _single_google_geocode(input_text):
base_url = "https://maps.googleapis.com/maps/api/geocode/json"
params = {
"address": input_text,
"key": os.environ["GOOGLE_MAPS_KEY"],
"bounds": "43.00,-97.50 49.5,-89.00",
}
response = requests.get(base_url, params=params)
data = response.json()
try:
x = data["results"][0]["geometry"]["location"]["lng"]
y = data["results"][0]["geometry"]["location"]["lat"]
except:
x = None
y = None
return f"{x}, {y}"
此外,测试的一个最终过程是将 GPT 3.5 作为地理编码器本身使用,而不借助任何地理编码 API。这个过程的代码与上述标准化代码几乎相同,但使用了不同的提示,见下文。
对以下地址进行地理编码。尽可能准确地返回纬度(Y)和经度(X)。回复时,只返回以下格式的输出文本:X, Y
性能指标和洞察
在开发了各种过程之后,每个过程都被运行,并计算了多个性能指标,包括执行时间和地理编码准确性。这些指标列在下面。
| Geocoding Process | Mean | StdDev | MAE | RMSE |
| ------------------- | ------ | ------ | ------ | ------ |
| Google with GPT 3.5 | 0.1012 | 1.8537 | 0.3698 | 1.8565 |
| Google with Raw | 0.1047 | 1.1383 | 0.2643 | 1.1431 |
| Esri with GPT 3.5 | 0.0116 | 0.5748 | 0.0736 | 0.5749 |
| Esri with Raw | 0.0001 | 0.0396 | 0.0174 | 0.0396 |
| GPT 3.5 Geocoding | 2.1261 | 80.022 | 45.416 | 80.050 |
| Geocoding Process | 75% ET | 90% ET | 95% ET | Run Time |
| ------------------- | ------ | ------ | ------ | -------- |
| Google with GPT 3.5 | 0.0683 | 0.3593 | 3.3496 | 1m 59.9s |
| Google with Raw | 0.0849 | 0.4171 | 3.3496 | 0m 23.2s |
| Esri with GPT 3.5 | 0.0364 | 0.0641 | 0.1171 | 2m 22.7s |
| Esri with Raw | 0.0362 | 0.0586 | 0.1171 | 0m 51.0s |
| GPT 3.5 Geocoding | 195.54 | 197.86 | 199.13 | 1m 11.9s |
这里对指标进行了更详细的解释。均值代表均误差(以曼哈顿距离计算,即与真实值的 X 和 Y 差异的总和,以十进制度数表示)。标准差代表误差的标准偏差(以曼哈顿距离计算,以十进制度数表示)。MAE 代表均方绝对误差(以曼哈顿距离计算,以十进制度数表示)。RMSE 代表均方根误差(以曼哈顿距离计算,以十进制度数表示)。75%、90%、95% ET 代表该百分比的误差阈值(以欧几里得距离计算,以十进制度数表示),意味着对于给定的百分比,该百分比的记录落在结果值与真实值的距离之内。最后,运行时间简单地表示在 100 条记录上运行地理编码过程所花费的总时间。
很明显,GPT 3.5 单独表现远不如预期。虽然,如果排除几个被模型标记为位于其他大陆的异常值,大多数结果在视觉上至少看起来并不太突兀。
同时,看到 LLM 标准化过程实际上降低了准确性也颇为有趣,我个人对此感到有些惊讶,因为我引入该组件的全部意图是希望稍微提高地理编码过程的总体准确性。值得注意的是,提示本身可能是问题的一部分,并且值得进一步探索“提示工程”在地理空间背景中的作用。
从这次分析中得到的最后一个主要结论是执行时间差异,任何使用 GPT 3.5 的过程都明显较慢。Esri 的地理编码 API 在这种设置下也比 Google 的慢。然而,严格的测试没有进行,因此这些结果应考虑这一点。
结论
尽管 OpenAI 的 GPT 3.5 模型的“开箱即用”地理编码能力可能无法与现代地理编码器的复杂性相匹配,但测试突显了一个潜在的有前景的展望。结果突显了显著的改进空间,暗示在可预见的未来,大型语言模型(LLMs)的地理空间能力有很多提升的机会,并最终对我们所知的地理编码产生影响。
在许多特定用例中,LLMs 可能足以进行地理编码。然而,正如这个例子所示,LLMs 的能力与需要高精度和细致空间分辨率的地理编码任务之间存在差距。因此,尽管 LLMs 具有潜力,但这个例子展示了某些应用对精度和准确性的关键要求。
总体而言,生成式人工智能展示出令人兴奋的创新,涵盖了地理和 GIS 领域的广泛和深远影响和机遇,包括地理编码的应用。每天都在以惊人的速度进行着持续的进展,允许在生成式人工智能与地理空间的集成开发上继续取得进展。
参考文献: [1] 维基百科贡献者,地址地理编码 (2023),维基百科
[2] A. Hassan,地理空间人工智能的未来 (2023),空间数据科学
[3] L. Mearian,LLMs 是什么,它们如何在生成式人工智能中使用? (2023),计算机世界
致谢: 我要特别感谢 Bryan Runck 博士在编辑和审阅本文过程中提供的宝贵支持、指导和专业知识。
机器学习模型的生命周期
它们何时出生,如何转变?
·
关注 发表在Towards Data Science ·7 min read·2023 年 5 月 11 日
--
机器学习模型的生命周期(作者提供的图片)
你的模型在如何变化?
许多实体都有生命周期,生物体、惰性物体,以及如今的数字实体。对于第一类实体,我们知道它们何时出生,如何成长,以及何时死亡。对于第二类,即使它们没有生物学寿命,惰性物体也是从某物中产生的,它们也可以被转化为其他形态。至于第三类,数字实体何时出生,以及它们如何经历转变?
讨论数字实体的生命周期与讨论无生命物体的生命周期非常相似。我们可以想象一块岩石。岩石的形成有一个从熔岩开始的过程。然后,岩石可以通过许多方式变化。它可以在熔化后返回到熔岩点,或通过许多其他类型的转变而被分解。如果地质学太复杂,我们可以想想另一个工具,比如简单的铅笔。我们收集一些材料,如木材和抛光剂,然后我们制作一支铅笔。在被他人使用后,铅笔会被消耗、折断,甚至分裂成小块以制作迷你铅笔。我们的原始铅笔已经经历了一段生命。实际上,这个过程对于数字或虚拟实体,如我们的机器学习模型,并没有什么不同。
模型诞生
这些数字工具是在我们构建、训练、拟合或估算模型时诞生的。这个阶段几乎始于有了分析目标、数据、计算机、算法和数据科学家现在熟知的其他一切工具。不论你收集了哪些其他工具,永远不要忘记分析或科学目标,这样你的最终模型才有意义并服务于特定需求。你的模型何时诞生?当你完成训练并保存以备使用/部署时,这个工具的生命周期就开始了。

模型诞生(图片作者提供)
这位新生儿的未来是什么?这将取决于分析目标,这也是为什么我们在构建时不能忘记这部分。这个模型可能用于预测任务、指标解释或“假如”场景模拟等多种其他选择。这个工具将用于某些事情。可能是简单快速的,也可能是复杂、耗时且长期的。这样的使用将决定模型的余生。如果模型用于一次性参数解释,那么它的寿命不会太长。但如果模型用于预测,并且旨在服务于具有在线数据收集的系统,那么这位新生儿的寿命就长远了。那么接下来呢?
维护模型
随着我们继续使用模型,支持模型训练的数据条件将开始发生变化。正是在这种变化时刻,模型也会开始经历变化。如果我们构建一个用于流失预测的模型,并在训练时具有较高的预测准确率,那么在不久的将来或远期,预测客户的条件或行为会开始发生变化。这种变化挑战了我们已学习模型的预测性能。当这些变化发生时,我们的模型进入一个新的阶段,我们称之为维护。
在维护阶段,我们可能需要新的数据。通过新的数据,我们可以更新模型的规格。这与取另一台机器,比如一辆车,并在机械出现故障时调整零件没有区别。我们不会深入探讨执行模型维护的策略或解决方案,但一般来说,我们的模型需要经历一个调整过程,以使其恢复到令人满意的性能。

模型需要维护(图像由作者提供)
机器学习模型的维护与重新训练模型并不完全相同。有些模型可能简单到用更新的数据重新训练它们也很简单。这可能适用于线性架构或层数和神经元非常少的网络。但是当模型复杂且具有大型深度架构时,维护阶段需要远比重新训练模型的成本简单。这是如今机器学习世界中最重要的话题之一,因为这些工具可能非常强大,但在长远看来维护费用却非常昂贵。
一旦模型被调整或更新,它就可以重新投入使用,因此模型所服务的任何过程都可以继续使用更新后的版本。我们的机器可以继续运行。尽管如此,这台机器已经经历了变化。它已经被使用、消耗,可以说,它已经转变为与其原始状态略有不同的东西。就像铅笔一样,我们的模型在这些时刻中遇到需要削尖的时刻,以便保留它们,以便我们能继续使用它们。
模型的转移
在机器学习的道路上,有一个我们可能需要走的出口:转移。我曾经在美丽的冰岛旅行,当我第一次看到有人换车轮以便在冰冷的道路上行驶时,印象深刻。然后当他们回到城市时,又换回了普通的轮胎。当我开始研究转移学习时,那个在冰岛换轮胎的概念变得非常清晰。当新的环境/领域出现时,我们的模型进入了一个被称为转移的新阶段。

模型可以被转移(图像由作者提供)
就像相同的汽车可以通过更换轮胎适应不同的地面,而无需购买另一辆车,我们可以添加或调整模型的一些部分,以便在新领域中服务新目的,而不必构建新模型。迁移学习是机器学习文献中的另一个子领域,旨在优化模型的调整,以简化在新环境中训练模型的工作。常见的例子是图像识别模型。我们用某些类别的图像训练它们,然后其他人将这些模型迁移到识别新的图像类别。许多企业如今使用如 RegNet, VGG, Inception, 或 AlexNet 的模型,以适应他们的需求。
当我们转移一个模型时,从某种意义上说,一个新的模型诞生了,它有自己的生命周期,与原始模型分开。它将需要维护,就像原始模型一样。这样,我们从一个初始实体变成了可能创建出一个完整的模型种群。毫无疑问,这些数字工具背后确实有一个生命周期。

模型的种群。我们的模型会随着时间变化(图片来源于作者)
我们的模型会停止存在吗?
简短的回答是:是的。它们确实可能会停止存在,比如当它们的分析性能系统性地不令人满意,或者当它们变得如此庞大和不同,以至于原始模型已经成为过去的事物。如我们在开头所说,岩石、铅笔,甚至汽车也会在某个时刻停止存在。模型在这方面与这些事物没有什么不同。
尽管模型可能会灭绝,但何时到达这一点至今仍是我们希望在机器学习研究社区中解答的最大问题之一。监控机器学习性能和模型维护的许多进展都与模型何时不再功能性有关。
这个问题之所以不简单,是因为我们需要不断地使用标签来量化性能的满意度。但机器学习和统计学习的最大悖论正是标签不可用,而我们构建这些工具的目的是为了预测标签。另一个原因是定义性能变化的接受极限可能非常主观。尽管科学家可以提出一些极限,但企业可能有不同的容忍度。
以下是数据科学家在回答这个问题时也可以考虑的一些要点(带有当前的开放问题):
-
训练数据是否过时?(什么是“过时”?)
-
当前版本与原始版本的模型有多相似?(什么是“相似”?)
-
输入特征的变化性和与目标变量的关系是否完全漂移?(协变量漂移和概念漂移是机器学习维护中的两个重要研究主题)。
-
模型部署的物理过程是否仍在使用中?如果物理基础设施不再支持模型的部署,这无疑标志着其生命周期的结束。
模型的结束并不一定是负面的,它也类似于其进化的路径。我们需要了解模型的生命周期,以保持我们的物理和数字系统的更新和满意的性能。
总结:模型是随着时间的推移而出现和转变的机器
-
当我们训练一个模型并开始使用它时,它便开始了它的生命周期。
-
简单的模型使用开启了简单的生命周期路径。复杂的使用则使我们的模型发生变化。
-
变更源于模型的维护。我们需要更新或修复它们。这将为我们带来一个新模型。
-
模型可以被转移。新的使用领域要求对模型进行调整。
-
模型的生命周期取决于其持续有效的性能和在我们系统中的适用性。
愿模型长存!
关于生成式 AI 的小故事:绘图挑战
了解生成式 AI 工作原理的概念
·
关注 发表在 Towards Data Science · 22 分钟阅读·2023 年 8 月 22 日
--
图片来源于 Dall-e 2
《关于生成式 AI 的小故事:绘图挑战》是一个旨在通过简单易懂的格式直观地解释生成式 AI 如何工作的故事。虽然故事的整体内容可能不会直接显现出与生成式 AI 的关系,但最后一节“总结思考”将解释它们之间的关系。祝阅读愉快,并随时发表评论!
绘图挑战
介绍
想象一下,你和一位好友刚刚报名参加了一个你在网上看到的挑战。你们还不知道这是关于什么的,因为它只写着“秘密挑战”,但你们会一起参与,肯定会很有趣!
挑战的日子到了,你和你的朋友刚刚在挑战举行的建筑外与管理员见面。她(管理员)告诉你们跟着她,她会带你们去挑战发生的地方。你们被带入一个空房间,房间里有一个大橙色地板和四面彩色墙壁。房间里没有其他挑战者、桌子、椅子或其他东西,只有房间两端的两扇门和即将发生的事情的期待。
管理员开始告诉你规则:“规则非常简单:总共有三个房间,一个主房间和两个较小的房间。挑战分为六轮。在一轮进行时,同时只能有一个人在主房间,但你们可以在每轮中多次更换在房间里的人。这个规则意味着你们不能彼此看到或听到对方。一个人被安排在一个较小的房间里,那里有四块画布和绘画材料,另一个人则在另一个小房间里,那里有四张纸。每张纸上都会有一些东西;目标是尽可能接*纸上的内容进行绘画。你们可以通过门上的灯知道是否有人在主房间;如果没有人在房间里,灯会变成绿色,否则是红色。你们可以在轮次之间互相交谈。”

挑战地点包括一个橙色地板的主房间、四面彩色墙壁(黄色、紫色、蓝色和绿色)、两扇门,以及两间较小的房间,参赛者将在每轮中待在这些房间里。
在你们进入各自的房间之前,管理员将一个长方体的立方体放在主房间的地板上。管理员解释说,在一轮进行时,立方体和你们中的一个人可以同时在主房间里。这意味着你可以将立方体留在主房间,同时在其中轮换。你拿起立方体,注意到它有些粘,但没再多想,因为挑战即将开始,你既兴奋又非常困惑!
第 1 轮:全新的开始
由于你的朋友是更好的画家,你决定让他在画布房间里,而你则在纸张房间里。你们还达成了一致,决定轮流进入房间,每次尝试传达一张纸上的内容。
你走进房间,看到门上方有绿色的灯光照射,地上有四张纸,就如管理员所说。你捡起它们,看到每张纸上都有一张图片:
-
一张猫的图片
-
一张厨房的图片
-
一张汉堡的图片
-
一张树的图片
你拿起第一张纸,一张猫的图片,然后进入主房间。
在主房间里,你再次看到宽广的地板和巨大的墙壁,另一侧孤零零的门,以及地板上的立方体。你对如何与朋友沟通感到相当困惑,因为你们事先没有达成任何一致。无奈之下,你离开去你的房间思考该怎么做。
进入你的房间后不久,你看到灯变红了,表示你的朋友刚刚进入了主房间,轻微地笑了笑,因为他现在一定很困惑,就像你当初一样。不久后,灯又变绿了,表示你的朋友刚刚离开了房间,去尝试猜测你第一张纸上是什么。你拿起第二张纸,一张厨房的图片,再次进入房间,不太确定这次是否会有所不同。
天啊,你是对的;什么都没有改变!有点恼火,你用鞋子踢了踢立方体,看到它滚了一下。即使没有其他事情做,你也可以玩一会儿尴尬的立方体足球,所以你再踢了几下,然后再次离开房间回到自己的房间。
这种情况持续到你们两人都访问了房间四次,如果你要自己说的话,你已经在立方体足球方面变得相当熟练了!但离赢得挑战还远……
第一轮回顾
第一轮结束后,你和你的朋友再次见面,你感到有些沮丧,因为获胜的前景渺茫,但你惊讶地发现你的朋友出于某种原因比你更兴奋。你们都把房间里的东西带出来,看看彼此的画作有多接*。毫无意外的是,没有任何匹配的地方!所有的画作都是随机的,与你的纸张无关。你不敢相信地看着你的朋友,不是因为这些图像彼此不像,而是因为你朋友的积极能量与自己的消极情绪形成了鲜明的对比。
你问你的朋友为什么这么高兴。他告诉你,他已经弄清楚了如何在不交流的情况下绘制你纸上的内容!你困惑地看着他,要求他详细解释。他告诉你,第一次进入房间时他非常困惑,因为没有任何东西表明应该画什么;他很快回到自己的房间去调整旋转。第二次进入时,他也期望看到同样的情况,但令他惊讶的是,房间看起来并不一样!“不一样?你一定是疯了,”你告诉你的朋友。“这是一个空荡荡的大房间,怎么可能看起来不同?连窗户都没有。”
事实证明,房间的变化并不大;然而,它却至关重要。每次他进入房间时,地板上的立方体都在不同的位置。了解你的人知道你可能用它来踢足球,但这并不重要,因为这正是沟通的关键!
“是的,就是这样!”你兴奋地喊道。你可以利用地板来指示你的朋友应该画什么。兴高采烈的你们再次查看纸张,看看他应该画什么。你们决定将地板分成四个相等的方格,一个用来表示猫、厨房、汉堡和树。简单!

你告诉管理员你准备好进入下一轮了。
第二轮:简单分组
第二轮开始了,你对此充满了准备!你立刻去拿纸,因为你现在知道该怎么做了。你拿起第一张纸,如预期中那样,显示的是猫,这是上一轮中的一种形式。你检查灯光是否为绿色。是的。你跑进房间,把立方体放在你决定为猫的区域。你再次走进房间,等待看到灯光变红。你对自己微笑,知道你走在正确的道路上。
几秒钟后,你盯着红灯,转身去拿下一张纸。当你看着手中的纸张时,一头牛从你身边跑过。你拿起下一张,看到的东西仍然让你感到恐慌。你拿起最后一张纸,希望它是不同的,但没有。你看到的纸张内容是:
-
一张老鼠的图片
-
一张狗的图片
-
一张马的图片
这和你和你的朋友之前商定的完全不同,你不知道该怎么办……其实,只有一件事可做:立即结束这一轮。至少这次你猜对了一次!
第二轮回顾
你再次与朋友见面。他看起来和你第一次进入房间时看到猫的图片一样开心,这与你现在的感受形成了鲜明对比。正如预期的那样,你的朋友展示了四幅描绘猫的画作。当你展示不同动物的纸张时,他的脸色变得更加僵硬。你们同意这次更接*了,但显然还远未全部正确。
经过一番思考,你想到将地板分成八个区域,其中七个用于之前看到的每种类型,一个保留给新的图像。即使在空白区域朋友猜对的机会较低,至少还会有一个区域。

你很有信心地再次进入各自的房间;即使出现新的图像,你现在也知道该怎么做。
第三轮:更多分组
如预期的那样,这次有了更多的熟悉感。你查看了所有纸张的内容,看看上面是什么:
-
一张老鼠的图片
-
一张汉堡的图片
-
一张狼的图片
-
一张浣熊的图片
你记得老鼠应该在左下角,因此从那个开始。当你回来时,你拿到了一张汉堡的图片。自从你上次看到不是动物的东西已经很久了,但记得它在地板的右上角!你再次进入房间,接受最后两张纸上的损失,把它们放在空白区域。
第 3 轮回顾:更多的分组
这次你没有那么沮丧,因为你得到了两个正确,并且你知道并不是所有的东西都是以前见过的。你同意新的分布应该如下,希望这次能正确更多:

这次你掌握了这个过程,可以迅速进入每个房间。
第 4 轮:记住的东西太多
你再次进入房间……这里开始感觉有些熟悉了。已经过去多久了?几天?几周?你看了一下手表……45 分钟……好吧,也许没那么久……你花了点时间欣赏朋友绘制所有那些画作的速度。
但生活必须继续,所以你拿起第一张纸。你看到一棵树,你知道这张,它在中间的左侧。你进入房间,按照商定的位置放置立方体。你离开房间,不再花时间看灯光,直接去下一张纸。一张马的图片,对了,那是在中间的位置。
你再次进入房间,把立方体放到地上。你站在房间的中央,双手在两侧,感到骄傲,欣赏进展和兴奋的感觉。这次你能正确放置超过两个吗?你再次离开主房间,看看这次会出现什么。看到一张斑马和老虎的图片,运气不好;看来你必须再次更改东西。
第 4 轮回顾
你再次和朋友见面;当然,你得到了四个中的两个正确。你看了朋友做的画,看到一张树和一只猫的图片,同时对自己点了点头。果然,有一张树和马的图片……等一下?猫?不是马?你感到困惑,问朋友为什么画的是猫而不是马?他看起来和你一样困惑,回答说你把立方体放在了猫的区域!你们讨论了马和猫区域的放置位置,发现你的朋友确实是对的。你忘记了马的正确位置。
你甚至无法记住九个不同类别的地方,现在你有 11 个……?你向朋友表达了你的担忧,并同意随着类别的增加,这不是一个可变的策略。你查看到目前为止获得的例子,发现大多数是动物。你的朋友有了一个主意,如果我们把看起来相似的东西放在一起呢?你同意这是一个好主意,因为这样更容易记住东西的位置!
你将地板的下半部分设为动物区域。但这还不够,于是你将看起来相似的动物放在一起,如斑马和马或老虎和猫。这将更容易记住物品的位置。你还意识到汉堡是在厨房制作的,于是将它们放在一起。

你相信你现在有了更好的机会记住所有不同的类别!下一轮开始。
第五轮:简单性
你看到房间中间那堆熟悉的纸张。上方的光线从门缝中透出微弱的绿色光辉,照亮了这些纸张。你拿起下一张纸,兴奋地期待这轮可能带来的挑战。第一张图片是一只孟加拉猫,一种看起来像小型老虎的猫。它是一只猫,但还是……你知道地板上两个区域的位置,但不确定你的朋友是否会画对。你决定把它放在老虎和猫之间,希望你的朋友能理解这不仅仅是一只猫,而是一只像老虎一样的猫。
一幅画完成,三幅还待完成!你在等待朋友完成主要房间内的工作时,心里默默击掌,然后检查剩下的纸张。你看到剩下的纸张时感到惊讶和有些困惑,这次不是因为绝望,而是因为你松了一口气,这轮比前四轮容易。剩下的纸张是三只狗的图片,一只是美国无毛梗,另一只是胡须狗,最后一只是博塞隆犬。光线变成绿色,你进入房间,将方块放在为狗预留的区域中。
第五轮回顾
第五轮结束后,你再次与朋友见面,查看你正确的画作数量。你的朋友展示了第一张图片,是一只猞猁的画作。糟糕!差一点,但公*地说,孟加拉猫更像美洲豹而不是老虎,猞猁则更介于两者之间……但至少你的朋友明白了你将方块放在两个区域之间的意思!你急切地催促朋友展示剩下的三幅画,期待看到剩下的画是否正确。幸运的是,三幅画都是狗的图片!
满意之余,你召来管理员欣赏你这次正确了 3/4 的画作。相当令人印象深刻,对吧?管理员只是摇摇头,然后指出这些画确实像狗,但和图片上的狗完全不同。糟糕,她说得对!这些画都是拉布拉多犬,一种最常见的狗品种,而不是纸上展示的三种品种。她离开了,给你更多时间在最后一轮开始之前。
你是否应该将所有犬种都添加到地板上?你之前就已经有记不住东西的位置的问题,所以这似乎有点过头了……你们两个查看了一下文件,发现这些狗不仅仅是狗。每只狗的大小和毛发长度都不同。你能否将狗的区域划分为定义毛发长度和高度的更小区域,而不是为每个犬种创建新区域?

这是一个好主意,因为它将区域的数量保持在最低限度,但你意识到有个问题。你刚刚了解到,通过将立方体放置在两个区域之间可以让你的朋友画出新的东西;这在林猫的例子中发生过。新想法的问题在于,猜测立方体是否在两个区域之间变得更加困难,因为它结合了两个区域,还是仅仅因为一个区域的毛发非常长。你决定暂时放弃这个想法……

过了一会儿,你还没有找到好的解决方案或新想法……啊啊啊!……你拿起那个长方体的立方体玩弄,同时考虑如何解决这个问题。它还是粘粘的,虽然感觉不太好,但总比手里什么都没有要好。当你仔细观察这个立方体时,你意识到它四周都有四条线,好像是由五个小立方体拼成的长方体。现在你想起来了,这个立方体是不是比最开始更变形了?你叫来你的朋友一起更仔细地检查这个立方体。结果发现,你认为是一个长方体的立方体实际上是五个立方体用螺丝连接在一起的,因为你们玩足球时螺丝松了!但每个立方体依然是粘粘的……
你的朋友总是充满好奇,所以他开始玩弄这些立方体也就不足为奇了。坦率地说,最好是休息一下思考的事情,所以你坐下来观察你的朋友玩弄这些立方体。他试着将两个立方体按在一起,看看它们是否足够结实可以在没有螺丝的情况下粘在一起。他慢慢地移开一只手,兴奋地看看自己是否成功,并准备在立方体分开时接住一个。结构保持稳固。他点了点头,满意地准备进行下一个计划——看看两个立方体是否能粘在墙上。
当你的朋友慢慢把手从墙壁上两个粘着的立方体上移开时,你内心一阵激动。“我明白了!”你对你的朋友喊道,朋友被吓了一跳,撞到立方体上,立方体分裂开来,摔落到地上。你的朋友看起来有些恼火但对你想到的点子感到好奇。“如果地板不够,那为什么不利用墙壁呢!?”你的朋友让你更详细地解释。“之前我们讨论了将狗的区域划分为更小的部分来表示毛发长度和大小,但同意这不是一个好主意,因为这会导致无法创建像猞猁这样的新物体。但如果我们在地板上放一个立方体来表示动物,然后在其中一面墙上放另一个立方体来指示动物的毛发长度和大小呢?”你们都同意这是一个好的方法,并决定不仅使用一面墙,而是所有的墙壁!你们还决定不再在地板上放置动物和其他物品,而是进行以下重新组织:
-
橙色地板被划分为各大洲,以便于指示物品来自哪个地理区域。如果将立方体放在欧洲的顶部,它将代表挪威;如果放在非洲的底部,它将代表南非。你决定中间区域保留给那些没有特定国家的物品。
-
蓝色墙壁决定物品的大小和毛发的长度。他们决定最大的尺寸是行星,中等尺寸是大象,最小的尺寸是没有尺寸。同时,最长的毛发长度是 2 米,中等长度是半米,最短的是没有毛发。
-
绿色墙壁遵循相同的概念:一个方向决定了圆形的主导程度,另一个方向决定了条纹的主导程度。中间的一个点可能是椭圆形,一个长圆形,可以被视作圆形和条纹的结合体。
-
紫色墙壁决定物品的危险程度以及它看起来像动物的程度。
-
黄色墙壁代表食物和树木。食物定义了我们将物品视为应该被吃掉的程度。汉堡包会在这一尺度的顶部,因为它可以立即食用,而一罐豆子则在中间,因为我们需要先取出豆子。最低的部分则是像石头这样的东西(希望)没人会吃。树木定义了物品像树的程度,左边是花朵,中间是灌木,右边是树木。

就在你完成决定如何划分地板和墙壁时,管理员告诉你第六轮也是最后一轮即将开始。你(再次)准备好并对你的新战术感到非常兴奋!
最终轮 — 第 6 轮:空间大师
这是最后一轮,再来一次,然后你就完成了(并希望能赢得胜利)!你拿起第一张纸,准备解决这个挑战。你在第一张纸上看到一头牛,很简单。你走进房间,像 18 岁时回到舞池一样征服地板。你把一个立方体放在地板中间,告诉你的朋友它在世界各地都能找到,另一个立方体放在蓝色墙壁的中间偏左的位置,因为它是一个有短毛的大动物。你看一眼绿色墙壁,是圆形还是条纹?当然,两者都有,但主要是圆形,而且不多,把那个家伙放在上半部分偏左的位置。危险吗?它不是完全无害,但绝对不算危险,并且肯定是动物:你把它放在紫色墙壁的右下角。食物?许多人吃牛肉,所以你把立方体放在中间高度,因为它是动物而不是肉块。牛的外形和植物完全不同,所以立方体放在最左边。天才。

就像你完成了第一张纸一样迅速,你匆忙完成了接下来的两张,其中一张展示了一只长颈鹿,另一张展示了太阳。当你完成第三张纸时,你感觉自己开始掌握了要领。你拿起最后一张纸,准备迎接最终的挑战。之前充满的自信,现在却被巨大的怀疑取代了。纸上没有任何图像……纸上确实没有“什么都没有”……只是……没有图像……那么纸上是什么呢?文字……它写着“香菇”。你花些时间让视图逐渐清晰……你记得没有人告诉你画的内容应该像纸上的内容一样。所以……你能像之前一样摆放立方体,让你的朋友画出一朵香菇吗?你心里想:“即使纸上有文字,我的朋友还能画出蘑菇吗?试试看吧。”你试着运气,把立方体按照纸上展示的香菇摆放。你把立方体放置在地板上靠*日本的位置。它是一个可以食用的小植物,所以你把它放在顶部的黄色墙壁的中间。它不是动物也不危险,但圆圆的有个茎,所以你把它放在绿色墙壁的中间和紫色墙壁的左下角。它小且没有毛发,所以你把它放在蓝色墙壁的左上方一点的地方。
你最后一次离开房间。令人兴奋。

结束语
生成式 AI 的目标是生成事物,就像你朋友的目标是“生成/制作”画作一样。但正如你朋友也可以写文本,生成式 AI 只要知道怎么做,就可以生成我们要求的任何东西(你的朋友可能还不能创作音乐,因为他还没学会)。我们通常希望 AI 在小任务上表现得非常出色,而不是在许多事情上表现得*庸,因此我们通常将它限制在生成单一类型的内容上,比如图像。但正如通才和专家在工作场所有不同的角色一样,专门化和通用的 AI 可以用于不同的任务,各有其优缺点。
即使我们没有告诉它生成什么,生成式 AI 仍然可以生成画作,就像朋友在第一次回合中你们不知道怎么沟通时一样。但仅仅生成随机的事物通常是不切实际的,所以你需要一种方法来影响生成的内容/画作。问题在于你不能像直接和朋友对话那样直接告诉生成式 AI 生成什么。因此,你需要商定另一种方法来实现。你做的方式和生成式 AI 的方式是相同的,你把一个立方体放在一个房间里,这个房间中的不同区域保留给不同的事物。这被称为生成式 AI 的“潜在空间”,这是一个特殊房间的 fancy 术语,你和你的朋友无法同时在其中。
如果你想解决这个挑战,你需要你的朋友/生成式 AI 擅长两件事:
-
尽可能生成更多不同的事物
-
生成前所未见的新事物
这时问题开始浮现。随着越来越多的事物被引入,记住事物的位置变得越来越困难。有两种方法可以解决这个问题:
-
将看起来相似的事物放在一起
-
如果地面空间不够,可以利用墙面
首先要做的是把看起来相似的东西放在一起。这将提高生成各种事物和新事物的能力。
-
生成许多事物会更容易,因为你不需要记住每样东西的放置位置,只需记住不同区域的事物样子,即使你没有画出正确的东西,也不会差得太远,因为它会看起来像那个区域里的东西。
-
生成新事物也会更容易,因为重点不在于事物的位置,而在于它们的样子。这意味着当你把立方体放在没有头发和长头发的事物之间时,你的朋友会知道他应该画一些带有一点头发的东西。

第二步是不仅使用地板,还要利用墙壁。在故事中,你和你的朋友讨论了如何将所有东西放在地板上,但这不是一个好解决方案,因为这会破坏你绘制尚未见过的东西的选项。你不能绘制尚未见过的东西,因为你现在需要,比如说,为长毛和无毛的狗找个地方。如果你把这些加到你的狗区域,那么当方块放置在一只狗和一只狼之间时,你无法知道它是两者的结合体还是仅仅是一只长毛的狗。

这就是为什么不仅使用地板而且还要利用墙壁至关重要。它允许你生成更多的东西,因为你可以在每面墙上表达不同的概念,比如地板上看起来像什么动物以及墙上的毛发长度和大小。墙壁越多,能生成/绘制的东西就越多,但也会更难“仅仅画一只狗”,因为现在有更多的选项。因此,你使用的墙壁数量将取决于你想要多少控制。
最后一张纸上写的是文字而不是图像。生成式 AI 不关心纸上有什么,只关心方块在房间中的位置。生成式 AI,如 OpenAI 的 Dall-e 2,从你给定的文字中创建画作。博客文章开头的图像就是通过输入“两个站在一个明亮宽敞的白色房间中间有一个门的人”这段文字生成的。

图片来源于 Dall-e 2
之后
这篇故事《关于生成式 AI 的小故事:绘画挑战》讲述了两个朋友如何在不说话的情况下进行交流——仅仅使用一个房间和几个粘性方块。
感谢阅读;我希望你喜欢这个故事,并现在更好地理解生成式 AI 是什么以及它是如何工作的。查看我的个人资料,获取更多博客文章,如果你有问题、想法或未来博客文章的创意,请留言。
我目前正在撰写多篇博客文章,将于今年发布,因此如果你想在新文章发布时收到通知,请订阅!
此致,
Mathias
如果你喜欢这本书,并对机器学习和数据科学的新见解感兴趣,请注册 Medium 会员,以便完全访问我的内容。关注我以便在我发布新章节或帖子时收到电子邮件。
图像嵌入和自编码器的详细介绍 书籍及其章节的介绍——他们说……
medium.com](https://medium.com/@mathiasgronne/membership?source=post_page-----f858f720d50e--------------------------------)
隆巴尔效应及其如何帮助听力障碍
·
关注 发表在 Towards Data Science ·5 分钟阅读·2023 年 9 月 11 日
--
简而言之: 隆巴尔效应可以应用于声音转换和文本到语音,使合成语音在噪音中更易于理解。

图片由作者提供
你是否曾经想过为什么我们在嘈杂的房间里往往说得更大声? 好吧,语言和言语研究者们也对此感到好奇,他们探讨了一个叫做隆巴尔效应的概念(由Étienne Lombard发现)。
💬 隆巴尔效应概述
想象一下你在一个聚会上,音乐在播放,大家聊天、欢笑,气氛愉快!为了让你的朋友听到你的声音,你的大脑会自动提高你声音的音量,调整你的音调,甚至调整你的语速。有趣的是,我们还会根据对面人的反馈和周围的噪音来调整声音,以确保他们能听到信息。
现在,考虑将这一效应应用于技术,如文本到语音(TTS)系统。如果 Alexa 或 Google Home 能够以 Lombard 效应进行对话会怎样?(这一场景已经被 SNL 想象过)。
🔊 Lombard 效应与文本到语音
一些研究(见 [1],[2)探讨了如何将 Lombard 风格应用于文本到语音(TTS)中以提高可理解性。他们的目标是看看是否可以通过 Lombard 风格的录音来训练语音,从而提高可理解性和自然性。他们发现这确实是一种比信号处理更自然的提高可理解性的方法!
▴ 为什么这很重要
我们可以在源头让语音听起来更清晰,而不仅仅是提高音量或在接收端处理信号(就像大多数助听器所做的那样)!
助听器是惊人的工程成果,但也有其挑战。它们并不总是舒适,可能很昂贵,有些人甚至选择不经常使用。但是,通过 Lombard 风格的 TTS,语音会自动调整为更清晰、更易于理解。这可能会成为游戏规则的改变者,不仅对那些佩戴助听器的人有用,对非母语人士(见 [3])以及任何处于嘈杂环境中的人也有帮助!
🚩 当前问题
前面提到的研究使用了大量特定语音的音频样本数据集。如果没有这些数据会怎样?我们如何在没有录音(既疲惫、耗时,又对语音人才成本高)的情况下合成 Lombard 风格的语音?
🔍 解决方案?
语音转换,即将某人的声音转移到其他人的语音录音上,可以作为一种数据增强方法。这个想法是通过将发言者的身份转移到 Lombard 语音录音上,创建该人声的 Lombard 风格录音。
📚 我们的研究
在我们最*在Clarity Workshop的 Interspeech 23'上发表的论文中,我们决定研究如何在进行语音转换时保留 Lombard 效应。确实,目标说话者的信息可能会覆盖 Lombard 效应特征,未能给我们预期的结果。我们想回答以下问题:我们能否在语音转换过程中保留负责可懂度的 Lombard 说话风格,同时转移说话者的身份?
在给定的语音转换(VC)模型下,我们调查了不同的条件方式。以下图形中可以找到我们在实验中尝试的三种系统。

语音转换模型在三种条件设置中的示意图。图片由作者提供
-
VC+features(显式条件):我们首先决定分离语音的三个关键元素:音高、音量和倾斜度。然后,我们将提取的特征直接输入模型的编码器。接着,我们在 Lombard 录音中提取这些特征,并将它们输入语音转换模型,以强制模型在最终录音中保留这些特征,同时转移我们希望转移的声音。
-
VC+CLS(隐式条件):如果我们希望模型自行学习特征呢?我们通过添加一个风格分类器来测试这一点,该分类器强制模型在语音转换后保持源风格。这种设置有助于在没有对特征进行微调的情况下保留 Lombard 风格。
-
融合:该系统结合了经过精心选择的特征和分类器,强制模型保持原始说话风格。
我们发现了什么? 如下方显示的条形图所示,显示了高噪声水*下的可懂度,我们发现

每个系统的目标可懂度评分(通过 SIIB 指标测量),具有最佳表现的特征,适用于男性和女性目标声音。图片由作者提供
-
确实,在转换过程中丧失了 Lombard 效应
-
显式和隐式条件都有助于提高最终的可懂度
-
融合效果更佳,但丧失了目标说话者的信息,使其不那么有用
-
不同的特征对女性和男性声音的效果不同
👉 结论是什么?
过去的研究和我们的工作表明,Lombard 风格的 TTS 确实在嘈杂环境中提高了语音可懂度。虽然自然度可能会受到影响,但在噪音中不那么显著,讲话者的身份也不会受到太大影响。在我们的研究中,我们发现基本的语音转换丧失了 Lombard 可懂度效应,但通过显式或隐式的条件设置,我们能够更好地转移这些效应!
查看我们的论文 这里 获取更多细节!
🚀 可理解语音的未来
想象一个世界,语音合成模仿我们的自然调整,在嘈杂的地方使沟通更加顺畅。通过更多的研究和创新,Lombard 风格的 TTS 可以帮助听力障碍者进行日常活动,如听音乐、观看 YouTube 视频、看电影等,并改善我们与智能助理和语音激活设备的互动!
参考文献
-
[1] Bollepalli, Bajibabu 等人。"使用长短期记忆递归神经网络进行语音合成的 Normal-to-Lombard 适应。"。《语音通信》110 (2019)
-
[2] Paul, Dipjyoti 等人。“使用语言风格转换增强文本到语音合成中的语音可理解性。”。《Interspeech 会议论文集》(2020)。
-
[3] Marcoux, Katherine 等人。“对于母语和非母语听者,母语和非母语语音的 Lombard 可理解性效益。” 《语音通信》136 (2022)
长短期结合:基于比例的相关性以捕捉文档语义端到端
·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 11 月 25 日
--
本文的语法、流畅性和可读性由人工智能软件增强。
当前主流的搜索方法通常依赖于关键词匹配或向量空间相似性来估算查询与文档之间的相关性。然而,当使用整个文件、论文甚至书籍作为搜索查询时,这些技术会遇到困难。

与 Dall-E 3 的趣味互动
基于关键词的检索
虽然关键词搜索在短时间查询中表现出色,但它们未能捕捉到对长篇内容至关重要的语义。一个正确讨论“云*台”的文档可能会完全被一个寻求“AWS”专业知识的查询遗漏。确切的术语匹配在长篇文本中经常面临词汇不匹配问题。
向量相似性搜索
现代向量嵌入模型如 BERT 将意义压缩为数百个数值维度,准确估算语义相似性。然而,具有自注意力的变换器架构因计算量激增而无法扩展到 512–1024 个标记以上。
没有完全处理文档的能力,结果的“词袋”部分嵌入丢失了在各部分间交织的意义细微差别。背景在抽象中丢失了。
巨大的计算复杂度也限制了在大多数实际语料库上的微调,从而限制了准确性。无监督学习提供了一个替代方案,但仍缺乏可靠的技术。
在一篇 最新论文 中,研究人员通过重新构思超长查询和文档的相关性,解决了这些问题。他们的创新解锁了 AI 文档搜索的新潜力。
长文档的问题
目前主流的搜索范式对于输入文本达到数千字的查询效果不佳。面临的主要问题包括:
-
像 BERT 这样的变换器具有二次自注意力复杂度,使得处理超出 512–1024 令牌的序列变得不可行。它们的稀疏注意力替代方案在准确性上有所妥协。
-
词汇模型基于精确的术语重叠进行匹配,无法推断长文本中至关重要的语义相似性。
-
大多数领域集合缺乏标注训练数据,需采用 无监督或最少调优的方法。
-
涵盖多个子主题的长文档需要能够 将文档结构因素纳入相关性判断 的模型。
RPRS 方法旨在解决当前检索架构中的这些弱点。
介绍 RPRS 模型
RPRS 模型通过计算长查询文档与候选文档之间句子的比例匹配来计算相关性。
关键洞察是,包含与查询中句子相似的句子比例较高的文档通常更相关。
该方法包含 3 个关键阶段:
1. 句子编码
-
使用 SBERT 将查询和候选文档中的句子编码为向量 —— 一种高效的变换器架构用于句子嵌入。
-
SBERT 避免了二次复杂度,使得可以处理完整的文档长度。
2. 最相关的句子集
-
对于每个查询句子,基于向量嵌入找到最相似的 k 个候选文档句子。
-
确定每个查询句子最相关的文档句子集。
3. 基于比例的相关性评分
-
定义查询比例 (QP) —— 查询句子与文档句子相似的相对比例
-
定义文档比例 (DP) —— 与查询句子相似的文档句子的相对比例
-
结合 QP 和 DP 来计算最终的相关性评分,估计文本之间的相互关联性。
比例相关性的概念内在地考虑了长文本中的文档结构。
另一个扩展名为 RPRS w/freq 额外考虑了 术语频率 和 长度归一化,受到 BM25 的启发,以处理重复和长度偏差。
比例相关性公式
RPRS 方法的核心是一个简单而强大的相关性评分公式,用于计算查询文档 q 和候选文档 d 之间的相关性:
RPRS_q(d) = QP(q, d) x DP(q, d)
其中:
-
QP(q, d)是 查询比例 -
DP(q, d)是 文档比例 -
RPRS_q(d)是 比例相关性评分
这些比例因素旨在从两个角度量化查询与候选文档文本之间的相互关联性。
查询比例
查询比例确定查询内容中有多少百分比与文档的某些部分相似。
对于查询 q 中的每个句子 q_s:
-
从文档
d中检索前n个最相似的句子d_si -
计算 # 查询句子中至少有 1 个相似的
d_si -
除以查询句子的总数
QP(q, d) = Matching query sentences / Total query sentences
更高的 QP 表示查询中的更多内容在文档中找到了匹配。
文档比例
文档比例反过来决定了文档中有多少百分比与查询的某个部分相似。
对于文档 d 中的每个句子 d_s:
-
检查
d_s是否出现在任何q_s的前n个匹配d_si中 -
计算与某些
q_s匹配的文档句子数量 -
除以文档句子的总数
DP(q, d) = Matching document sentences / Total document sentences
更高的 DP 表示文档内容中有更多的内容与查询匹配。
结合因素
比例相关性评分简单地是查询比例和文档比例的乘积:
RPRS_q(d) = QP(q, d) x DP(q, d)
净效果是奖励最大限度地覆盖查询的文档,同时被查询最大限度地覆盖——表明从两个角度来看都具有全面的语义相似性。
在法律、专利和维基百科数据集上的结果
研究人员在五个长文档数据集上全面评估了 RPRS,这些数据集涵盖了法律案件检索、专利搜索和维基百科文档相似性任务,包含数千个单词。
在所有数据集上,RPRS 显著超越了之前的最先进技术以及词汇和神经基线,同时仅使用了 3 个调整参数,展示了其有效性。组件重要性分析进一步验证了比例评分方法。
该方法结合了通过向量嵌入进行的语义匹配能力和跨句子的主题相关性的直观概念,提供了可解释的高准确性检索。
解决长期存在的局限性
RPRS 模型突显了经典检索中的关键思想与现代 NLP 表示相结合,可以在挑战性领域(如搜索法律文献和科学文献)推动边界,这些领域迄今为止一直抵抗高性能自动化。
这样做也将神经搜索范式的范围扩展到超长文本,其中大多数成熟模型目前面临限制。更广泛地说,围绕复杂文档集合设计基于相关性的基本原则架构仍然是搜索技术中一个富有创新性的领域。
论文提供了一个有说服力的适应蓝图,但仍有很大空间可供与大型语言模型、可解释性以及商业文档搜索解决方案的用户体验进步进行整合。
LLM 的魔力——提示工程
原文:
towardsdatascience.com/the-magic-of-llms-prompt-engineering-9c3e46130131
垃圾进垃圾出,从未如此真实。
·发布于 数据科学进展 ·6 分钟阅读·2023 年 4 月 8 日
--

机器的崛起
大型语言模型(LLMs)能够在提出正确的问题时提供前所未有的机器学习生成的信息。例如,LLMs 可以生成文章(不,LLM 并没有生成这段文字)、诗歌、歌词、与人讨论某个话题、解释某些类型的文档/表单等等。然而,LLMs 一个很少被讨论的问题是,知道正确的问题并不总是容易的,而没有这个关键部分,LLMs 可能会过于准确(即,信息过多)、不准确,或对许多商业应用过于多变。
提问的艺术,即提示工程,是我们克服这一限制(至少在短期内)的方式,也是 LLM 的神奇背后的魔法。
转变问题
在没有提供关于 LLM(由 LLM 撰写)的概述的情况下,我想直接进入 LLM 的一个重要特性——它们是非确定性的;你不一定每次都能得到相同的答案。例如,如果我连续多次向 Google Bard 询问一个简单的问题“1 + 1 是多少”,以下是未经编辑的结果:
What's 1 + 1?
>> 1+1=2
What's 1 + 1?
>> 1 + 1 = 2.
What's 1 + 1?
>> 1 + 1 = 2.
What's 1 + 1?
>> I understand that you are asking me what is 1 + 1\. The answer is 2.
只有第 2 和第 3 个提示产生了相同的答案,但还要注意答案不仅仅是“2”,而是提示和答案的变化以及一些相当不可预测的答案格式。(注意:上面的输出并不是 Bard 的样子,仅仅是文本。)
从无结构到无结构并不能真正解决问题,它只是转移了问题。
在 1+1 的例子中,转变在于如何解析/转换答案;处理可变答案就像在旋转的房间里投掷飞镖——答案的变化太大,我们几乎回到了原来的问题。
相反,我们专注于提示工程,或询问问题以获得特定回应的艺术形式。这里是 Bard 对提示工程的定义:
提示工程是为大型语言模型设计和优化提示的过程,用于各种应用和研究主题。提示是用于引导语言模型输出的短文本片段。它们可以用来指定语言模型需要完成的任务,提供语言模型完成任务所需的额外信息,或仅仅使任务更易于语言模型理解。
谢谢你,Bard,这个描述相当不错,尽管在这个上下文中“短”是相对的。
问题工程 — 你控制这一点
在 1+1 的例子中,我控制的唯一是问题或提示。因此,重点是设计问题(或提示),通过添加具体性和上下文来进行。
看看当我为 1+1 的提示添加一些具体内容时会发生什么。
What's 1+1 stated as an integer?
>> 1+1 stated as an integer is 2.
An integer is a whole number that can be positive, negative, or zero. It is not a fraction or a decimal.
等等!那糟糕得多。我不想要叙述性答案,我只要一个数字!这就是为什么提示工程是一门艺术。让我再试一次…
What's 1+1? Only return the numeric answer.
>> 2
What's 1+1? Only return the numeric answer.
>> 2
What's 1+1? Only return the numeric answer.
>> 2
看看这个!再来一个例子…
在这个例子中,我将做两件事——首先,展示如何将数据包含在提示中,其次,展示如何设计提示以获得具体的内容。
我首先使用 OCR 将以下表格提取为文本。

图片由作者提供
我的目标是向数据询问一些关于它自身的问题。这是一个相当有趣(技术上说,甚至是很棒的)用例,适用于许多(但不是全部)情况。
我想知道 2022 Q2 的文件大小(忽略表格中的高亮行,我想要下面的那一行)。答案是48.79 MB。
Given the following text, what is the file size for Q2 2022? File
Format
Size
2023 Q1
ZIP
53.11 MB
2022 Q4
ZIP
45.91 MB
2022 Q3
ZIP
45.16 MB
2022 Q2
ZIP
48.79 MB
2022 Q1
ZIP
52.61 MB
>> The file size for Q2 2022 is 48.79 MB.
The table you provided shows the file size for each quarter of 2022\. The file size for Q2 is 48.79 MB.
所以这里有趣的部分是它得出了正确的答案(48.79 MB)。不好的部分是,如同我们看到的 1+1,答案是无结构的。我需要通过具体性来修改提示。
Given the following text, what is the Q2 file size? Only return the answer.
>> Sure, here is the file size for Q2: 48.79 MB
Given the following text, what is the Q2 file size in MB, don't include any text in the reply?
>> 48.79
Given the following text, what is the Q2 file size in MB, don't include any text in the reply?
>> 48.79
这更好(即使不是完美的),确实使其作为一种零样本方式来读取某些类型的数据(甚至可能是表单数据)非常有用。在现实世界中,我会继续迭代提示,直到得到一个完全一致且确定的答案。
当心炒作的时机
阅读一个简单的表格并不意味着大型语言模型(LLMs)能够读取所有表格或表单,它们真的不能。一个好的例子是保险行业,其中非常复杂(甚至不合逻辑)的表单是信息交换的方式。LLMs 可以理解某些表单,但不能理解复杂的表单,因为:
-
LLMs 不理解保险(它们可以,但它们不理解)。一个背书可以包含一个背书或一个表单,而背书的第二种用法是名词,而“表单”的使用与实际表单无关,这真的是超出了普通 LLM 的细微差别。
-
复杂的表单包含了上下文,这对语言模型来说非常难以理解(也许是不可能的?),因为表单被用作布局结构,而不是内容结构。(表格有时也会出现类似的问题,原因相同,有时表格就是用于布局结构的机制。)
为了避免陷入 LLM 的炒作旋涡,重要的是要善于使用这些模型,了解它们能做什么,不能做什么,以及你如何能影响它们的行为。同时也要理解,模型本身在不断发展,因此不要因为它现在不起作用就否定它。
真实(现实)
宣传周期可能会导致不切实际的期望。我们在历史上见过这样的重复——例如,面向服务的架构(SOA)从未导致动态应用程序,但我们确实看到了一种相当大规模的 API 扩展。LLMs 也可能是这样,因为我们似乎期望 LLMs 解决所有世界上的问题(我的预测是:它不会)。如果你对这些模型能做什么保持现实和好奇,期望和现实可以对齐,而我相信这种对齐就是历史被创造的地方。
走出去,改变世界
大型语言模型(LLMs)强大且适用于多种任务,但它们也有局限性,其中一些可以通过提示工程克服。那种工程是一种艺术形式,需要大量实验,这真的是无法通过书本获得的——去做吧。擅长提示工程,你将能够最大化 LLM 的价值,甚至可能让自己远离炒作旋涡。
本文中表达的观点是我个人的,并不代表我的雇主。
量子计算的魔力:编写魔法数字猜测游戏的初学者指南
教程
编程量子计算机很有趣!
·发布于 Towards Data Science ·9 分钟阅读·2023 年 2 月 10 日
--

来源:稳定扩散。
从经典到量子
我不确定量子计算到底是什么让人如此惊叹和着迷,但它确实感觉与编程经典计算机截然不同。量子计算领域仍然年轻且充满潜力,为像你一样的人开辟了许多可能性。
我发现,了解一种技术的最佳方式之一就是通过游戏。还有什么比编写自己的游戏更好的方式来了解量子计算呢?
简单游戏的设计
我们将使用 Python 和一个名为 Qiskit 的开源量子计算库,来编写一个魔法数字猜测游戏。
我们的游戏非常简单,但将由量子计算的特性驱动。
我们的游戏将具有以下设计:
-
量子计算机将生成一个从 0 到 15 的随机数字。
-
玩家将需要猜测这个数字。
-
在每一轮中,我们会告诉玩家他们的猜测是否过低或过高。
这是一种很棒的方式来开始量子计算编程!
让我们开始吧!
量子计算的乐趣
通常,当我们想到量子计算时,首先想到的主题之一是比特同时表示为零和一的想法。这是我们称之为叠加态的微观粒子的行为。
想象一下。在微观层面上,整个世界的行为方式与我们习惯的完全不同。粒子旋转并存在于多个状态。有时,它们甚至可能出现或消失。它们甚至可以穿过物理墙壁,这种行为称为 量子隧穿!
然而,我们可以通过编程量子计算机来利用这些惊人的效果。
由于量子计算最强大的特性之一是同时表示零和一的位的信息,这也是初学者在构建第一个量子电路时最先学习的逻辑门之一。
当一个位是量子位
经典计算机上的一个位可以保存零或一的值,但绝对不能同时保存两者。然而,量子计算机上的量子位可以同时保存零和一的值,实现叠加态,直到被测量。
我们将利用叠加态的强大效应作为我们游戏的核心部分。
在量子计算机上,哈达玛门 用于将量子位置于叠加态。当这样做时,我们可以测量量子位并得到一个随机的零或一的值。也就是说,我们在测量量子位时,半数时间会得到零,另一半时间会得到一。
由于量子位有 50/50 的机会测量到零或一,我们可以利用这种效应来随机生成一个位。
生成随机数
现在我们知道可以使用哈达玛门在量子计算机上生成零或一的随机数,让我们将这个想法扩展到四个量子位。这使我们能够生成四个随机位,当它们转换为整数时,结果在 0 到 15 之间。
在特定范围内生成随机数的能力将作为我们游戏的关键部分。
在经典计算机上生成随机数
在深入量子计算部分之前,让我们首先看看如何在经典计算机上生成一些随机数。这将使我们能够比较经典计算机和量子计算机上生成的随机数的结果。
使用 Python,我们可以通过以下示例代码轻松生成伪随机数。
import random
for i in range(10):
num = random.randint(0, 15)
上述代码生成了一个从 0 到 15 的随机数,结果为以下值数组:[14, 7, 14, 10, 11, 6, 11, 10, 1, 9]。
如果我们绘制一个从这种方法生成的 300 个随机数的直方图,我们可以看到下面显示的图形。

使用 Python 的 random() 模块生成的 300 个随机数的分布。来源:作者。
注意,每个数字出现的频率相对*等,与其他数字相比。
这似乎非常随机!
从叠加态生成随机数
让我们将其引入量子世界。
就像我们在经典计算机方法中做的那样,现在我们将使用四个量子比特的叠加态来生成四个位的随机数,用于表示从 0 到 15 的数字。
我们可以使用以下代码。
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, execute, Aer
from qiskit.visualization import plot_histogram
# Setup a quantum circuit (4 bit random number 0–15).
qc = QuantumCircuit(4)
# Select the simulator.
simulator = Aer.get_backend(‘aer_simulator’)
# Place all qubits into superposition (50% chance 0 or 1).
qc.h(range(4))
# Measure each qubit.
qc.measure_all()
上述示例创建了一个量子电路,使用 Hadamard 门对每个量子比特进行操作,将它们置于叠加态。然后我们测量量子比特,读取每个量子比特的随机值,可能是零或一。
下面是绘制出的量子电路的样子。

使用叠加态和 Hadamard 门生成 4 位随机数的量子计算电路。来源:作者,生成于 Qiskit。
如你所见,我们只是在每个量子比特上使用了 H 门,并测量了结果值。
你可能想知道这些结果与之前的经典方法相比如何?事实证明,这种方法确实可以生成随机数,如下数组所示:[8, 11, 4, 2, 13, 9, 7, 12, 15, 13]。
我们可以绘制随机数的直方图,以确认随机值的分布。

使用叠加态生成的 300 个随机数的分布。来源:作者。
就像经典计算方法一样,这种方法也似乎相当随机!
从量子噪声中生成随机数
使用叠加态生成随机数非常简单,直接明了。
然而,我们也可以通过利用量子噪声的特性在量子计算机上生成随机数。
当量子门执行时,会有微小的噪声(或错误)可能影响门操作。正是这微小的噪声我们可以利用来生成随机数。
幸运的是,Qiskit 库使得利用量子噪声变得简单。甚至还有其他项目利用这一特性进行随机数生成。
以下是设置量子电路以利用噪声的示例。
from qiskit.providers.aer.noise import NoiseModel, pauli_error
# Initialize bit-flip error rates.
reset_value = 0.03
measure_value = 0.1
gate_value = 0.05
# Initialize QuantumError on the X and I gates.
reset_error_rate = pauli_error([('X', reset_value), ('I', 1 - reset_value)])
measure_error_rate = pauli_error([('X',measure_value), ('I', 1 - measure_value)])
gate1_error_rate = pauli_error([('X',gate_value), ('I', 1 - gate_value)])
gate2_error_rate = gate1_error_rate.tensor(gate1_error_rate)
# Add errors to a noise model.
noise_model = NoiseModel()
noise_model.add_all_qubit_quantum_error(reset_error_rate, "reset")
noise_model.add_all_qubit_quantum_error(measure_error_rate, "measure")
noise_model.add_all_qubit_quantum_error(gate1_error_rate, ["u1", "u2", "u3"])
noise_model.add_all_qubit_quantum_error(gate2_error_rate, ["cx"])
上述代码初始化了我们将在程序中使用的各种门的量子噪声。
一旦噪声准备好,我们可以通过以下代码创建一个量子电路,从噪声中生成随机数。
from qiskit.providers.aer import QasmSimulator
# Setup a quantum circuit (4 bit random number 0–15).
qc = QuantumCircuit(4)
simulator = QasmSimulator()
# Setup quantum circuit for noise generation to build a random value.
for i in range(50):
qc.u(0,0,0,0)
qc.u(0,0,0,1)
qc.u(0,0,0,2)
qc.u(0,0,0,3)
qc.cx(0,1)
qc.cx(1,2)
qc.cx(0,2)
qc.cx(0,3)
qc.cx(1,3)
qc.cx(2,3)
qc.barrier()
qc.measure_all()
上述代码创建了如下所示的量子电路。

使用量子噪声生成 4 位随机数的量子计算电路。来源:作者,生成于 Qiskit。
注意,我们使用了 U 门与一个受控非门(CX 门)的组合来生成所需的噪声。
你可能注意到了量子电路中的 for 循环。我们需要多次运行电路以放大生成的噪声。否则,我们的随机数将严重倾斜到零或一,而不是随机分布。*衡循环的迭代次数与数字的随机性非常重要,因为电路越长,运行时间也会越长。
最后,我们测量结果的四个量子比特以获得随机值。
再次,结果输出确实显得相当随机,如下数组所示:[4, 0, 6, 9, 2, 9, 15, 5, 2, 12]。

使用量子噪声生成的 300 个随机数的分布。来源:作者。
创建魔法数字游戏
现在我们准备好实现游戏了,我们需要选择一种生成将要使用的随机数的方法。
两种量子计算方法(叠加和噪声)都同样有效。
叠加方法更常用于生成随机性,因为它使用更简单的电路,而噪声方法则利用量子计算门的独特特性。实际上,每种技术都可以互换使用。
不过,让我们继续使用量子噪声的方法,这是一个更独特的方式,并在我们的游戏中实现它。
将量子封装到经典函数中
为了保持整洁,我们将嘈杂的量子电路封装到一个名为random_number的 Python 方法中。我们可以调用这个方法来选择玩家需要猜测的魔法数字。
def random_number():
# Setup a quantum circuit.
qc = QuantumCircuit(4)
simulator = QasmSimulator()
# Setup quantum circuit for noise generation to build a random value with greater iteration.
for i in range(50):
qc.u(0,0,0,0)
qc.u(0,0,0,1)
qc.u(0,0,0,2)
qc.u(0,0,0,3)
qc.cx(0,1)
qc.cx(1,2)
qc.cx(0,2)
qc.cx(0,3)
qc.cx(1,3)
qc.cx(2,3)
qc.barrier()
qc.measure_all()
# Execute the circuit.
job = execute(qc, simulator, basis_gates=noise_model.basis_gates, noise_model=noise_model, shots=1)
result = job.result()
counts = result_bit_flip.get_counts(0)
num=list(counts.keys())[0]
return int(num, 2)
一旦生成了魔法数字,我们就开始游戏循环。在每一轮中,我们会要求用户输入一个从 0 到 15 的猜测,并告诉玩家他们的猜测是太低还是太高。
游戏的代码如下所示。
# Generate a random quantum number.
magic_number = random_number()
guess = -999
count = 0
while guess != magic_number and guess != -1:
count = count + 1
guess = int(input("Guess a number from 0 to 15? "))
if guess < magic_number:
print("Too low!")
elif guess > magic_number:
print("Too high!")
else:
print("Great guess! You won in", count, "guesses!")
break
就是这样!
灯光、摄像、动作!
正如你所见,我们的游戏本质上非常简单。然而,游戏展示了量子计算机生成随机数的能力,这当然是这个游戏的关键部分。
这个想法有惊人的潜力可以进一步扩展。想象一下将图形、游戏机制、事件 3D 或虚拟现实效果添加到游戏中的可能性——所有这些都由量子计算提供动力。
生成量子随机数的简单动作可以为你游戏设计中的关键部分提供动力。
给我速度
现在,重要的是要注意,在量子计算机上执行速度的考虑。毕竟,在模拟器或物理量子计算机(例如 IBM Quantum)上处理量子电路可能需要显著的处理时间和延迟。
目前,游戏仍然可以作为一个优秀的概念验证和学习机会。此外,量子模拟器和硬件每年都在改善。
所以坚持下去,尽可能多地学习!
你兴奋了吗?
我希望你和我一样对量子计算及其在各种计算问题中的潜在应用感到兴奋。
现在你已经学会了如何使用 Python Qiskit 量子计算库,以及叠加态或量子噪声,你可以轻松地为各种用途生成随机数。
当然,量子计算的乐趣远不仅仅是随机数。不断增长的量子算法充分利用了量子过程。
这仅仅是冰山一角!
未来一片广阔
既然你已经知道了如何用量子计算生成随机数,那么是时候制作你自己的应用程序和游戏了!
你可以在这里下载完整的魔法数字猜测游戏代码示例。
关于作者
如果你喜欢这篇文章,请考虑在Medium、Twitter和我的网站上关注我,以便获取我未来的文章和研究工作通知。
“我写作的主要驱动力一直是学习”
作者聚焦
Matteo Courthoud 反思离开学术界的经历,对因果推断的兴趣,以及公众写作的价值
·
关注 发表在 Towards Data Science ·10 min read·2023 年 2 月 14 日
--
在《作者聚焦》系列中,TDS 编辑与我们的社区成员讨论他们在数据科学领域的职业路径、他们的写作以及他们的灵感来源。今天,我们很高兴分享与 Matteo Courthoud的对话。

图片由 Matteo Courthoud 提供
Matteo 是苏黎世大学经济学博士生,热衷于数据分析和因果推断。他曾在 Google 担任数据科学实习生,并在攻读博士学位期间为 Amazon 进行研究咨询。他定期撰写关于社会科学与计算机科学交叉的因果关系文章,试图弥合学术研究与大众之间的差距。
你的数据科学之旅是如何开始的?
我对因果推断的兴趣一直存在,但直到最*几年才形成明确的方向。我一直是一个好奇的人,并且一直喜欢弄清楚数字。不幸的是,就像意大利的我的大多数同代人一样,我在高中时没有接受过与统计学、编程或任何如今属于数据科学范畴的教育。我只上过几节 Pascal 编程课,这使我在选择大学专业时有了一个明确的认识:我不想再写代码。另一方面,我在对人际互动和决策的兴趣与对数字的热爱之间感到纠结。因此,尽管我的主要选择是社会学和数学,我还是选择了经济学作为一个公*的折中。
从回顾来看,这对你来说是正确的选择吗?
我很幸运。我对经济学的了解只是模糊的,但它最终正好是我所寻找的:一种研究人际互动的科学方法。有些人鄙视经济学,因为它将一切转化为货币价值,但我认为,借用丘吉尔的话,“钱是衡量人类激励的最糟糕的标准,但除了其他所有曾经尝试过的形式之外”。如果我们想采取一种科学的方法来研究人际互动,我们需要量化激励,而钱正好实现了这一目的,无论我们喜欢与否。
尽管如此,当我学习经济学时,课程并没有包含我认为应该有的数据科学内容(现在有了)。就美国教育而言,我基本上被迫选择法律、会计、管理或金融作为辅修几乎没有投资更多时间在计算机科学、信息学或数据科学上的选择。作为本科生,我只学了 Excel,而在我的硕士阶段,我使用了 Stata,但没有 Python 或 R 课程。此外,计量经济学课程主要包括理论和代数证明,而不是编程和数据分析。因此,我对数据科学的第一次实践是自发的和自学的。幸运的是,那是 MOOCs 的诞生时期,开放讲座逐渐变得可用,通常是免费的。起初,我只是从互联网上抓取电影信息或本地徒步旅行路线。然后,我转向了更复杂的项目,比如在线卡牌游戏的支持机器人,利用贝叶斯动态决策模型。
你是如何决定你的下一步行动的?
去研究生院的决定非常艰难。在获得硕士学位后,我申请了研究生院并收到了几个录取通知,但我也获得了一个独角兽初创公司数据科学家的职位。我知道我喜欢数字并且想要处理数据,但我对我的教育也深感不满。我有太多想知道但不知道的东西。最终,好奇心战胜了,我去了研究生院。
作为一名经济学博士生,我接受了严格而深入的因果推断训练。我还参加了我第一次编程、数据科学和机器学习的课程。然而,我在攻读博士学位时选择了一个非常精确的研究方向:反垄断和竞争政策。竞争政策是研究企业间以及企业与消费者之间战略互动的学科。我被这个主题吸引主要有两个原因。首先,在我看来,这是经济学中代理人理性假设最为合理的领域。企业直接或间接地试图最大化利润,并且在决策时(可以说)比单个个体更理性。此外,我想做有影响力的研究,具有明确的政策应用,*年来几乎没有比反垄断更为活跃的政策领域了。
然而,随着时间的推移,很明显没有可供学术研究的数据来研究我感兴趣的问题。我只能从理论角度研究这些问题,或者将注意力集中在我能找到数据的地方,通常是较窄和次要的市场。在这两种选择中,我发现第一种更具启发性:不完美地回答相关问题而不是精确地回答不相关的问题(引用我的导师的话)。然而,结果是我的研究仅仅是理论性的,我并没有像我希望的那样使用数据。毫无疑问,输出结果也*庸:许多人支持,但没有人热情,包括我自己。我陷入了困境。
你是如何从那次僵局中走出来的?
在 2022 年冬天,我做出了一个在后台潜伏了一段时间的决定:离开学术界。这不是一个容易的决定,因为在学术界被认为是失败,尤其是在经济学和欧洲。有关可用机会的信息很少,而且“出柜”的风险是失去教授的关注,被降级为二流学生。然而,我的导师非常支持并全力支持我的决定。
你开始为更广泛的受众写作,时间也差不多——你如何看待这两项发展的联系?
为 Towards Data Science 写作是一种自然的结果。首先,我决定申请经济学和数据科学领域的博士实习机会,以便下一年夏天进行实践。我在学术界之外的工作经验很少,想在正式承诺之前进一步探索我的选择。为了准备面试,我开始复习研究生早期的主题以及我感兴趣但未曾涉及的领域。起初,我只是为自己写笔记,混合了代码和文本。在某个时刻,我意识到我可以公开这些笔记,于是选择了那个在我发现数据科学新话题时帮助最大的博客。
我很快意识到技术写作不仅对我有用,而且非常令人满意。这是一个很好的激励,促使我深入探索话题,并清晰、组织和表达我的思想。此外,读者欣赏我将学术研究转化为更易懂内容的努力,这让我感到意外。我主要是为自己写作,没有预期任何回报。我收到了公开和私人评论,大多数是支持性的,若有批评,也是建设性的。这是我博士经历中最令人满意的时刻。不幸的是,学术界并不奖励教学,尽管教学占据了教授日常工作的很大一部分。对于扩大受众,无论是媒体还是语言,都缺乏激励。唯一重要的是为学术期刊撰写科学论文。为 Towards Data Science 写作是一种耳目一新的体验。
在做出这一重大改变后,你发现自己现在最吸引的项目和问题是什么?
在话题方面,我主要的兴趣是因果推断。我对利用数据回答因果问题的方式感兴趣,无论是通过随机实验还是观察研究。在因果推断领域,我选择话题通常是出于对已知话题的掌握欲望,或探索一个新的话题。通常这两者是相关的:我发现一个听起来很迷人的高级话题,然后在开始阅读后,我很快意识到我需要复习一些基本概念以全面理解它。然后,我开始复习和撰写基础知识,仅在第二阶段才处理高级话题。我发现这种方法特别有效,因为它让我能够在高级话题上更精确,而不会让文章充斥信息,因为我可以参考以前的工作。
话题列表不断增长。我从几个主题开始,每写一篇文章,就会在待写的文章中增加两个新主题。我现在有大约 50 篇文章的列表想要写。此外,新研究不断发布。Twitter 历来是我用来跟踪学术研究的社交网络。大多数经济学家、统计学家和计算机科学家在学术界都会在 Twitter 上发布和讨论他们的工作。另一方面,我最*也开始重视 LinkedIn 作为跟进行业动态的一种方式。行业研究人员、从业者和数据科学影响者主要活跃在 LinkedIn 上,这为我提供了很大的灵感。我最有趣的写作经历之一,关于 CUPED 就是源自一篇 LinkedIn 帖子,讨论了这种提高 A/B 测试估计器效率的成熟行业技术,我以前从未听说过。这听起来类似于我所知道的其他方法,于是我开始探索它们的相似性和差异。
对于那些可能对类似主题的公共写作感兴趣的人,你有什么见解可以分享吗?
我写作的主要驱动力一直是学习。学习的最有效方式是向好奇的观众教学。在研究生第一年,我有幸与一位在经济学方面没有背景的杰出朋友一起学习。他们总是提醒我,有一天我告诉他们:“我喜欢和你一起学习,因为你对计量经济学一无所知,所以你提出了最有趣的问题!”这虽然不太委婉,但非常真实。没有比尝试用简单的术语向一个好奇的人解释你是否真正学到东西更好的方式了。
从本科到博士,我一直需要重写、总结和可视化概念以便学习。对我来说,备考总是意味着写一份全面但简短的总结,包含尽可能多的例子、比喻、图表和示意图。其他人使用这些材料不仅是对我工作的认可,也是促使我更清晰、更直观地表达的额外激励。
一旦你决定要写作,就会面临如何写作的问题。我的主要建议是,无论是写作还是任何新活动,都是:聪明地模仿。当然,这并不意味着直接复制粘贴。它意味着找到那些如果是你写的你会感到自豪的文章,并分析它们以理解你最喜欢哪些方面以及它们在哪些维度上与你的写作风格不同。它们是如何开篇的?叙事流程是怎样的?它们进入了多少细节?对于来自学术界的人来说,最棘手的事情是将从技术论文中学到的概念转换为任何人都能直观理解的概念。这是一个困难的翻译过程,而我们没有接受过这种训练。相反,我们被训练去去除任何多余的内容,保持最基本的内容,以使文本机器可读。
你希望在未来一两年内看到你所在领域的任何变化或发展吗?
我认为在未来,因果推断将变得越来越重要,我们将看到社会科学中的理论方法与计算机科学中的数据驱动方法之间的趋同。
一方面,计算机科学已经认识到蛮力预测对于告知样本外决策是无用的(因为所有决策都是如此)。预测算法仍然很重要,因为因果关系可以被框定为反事实预测问题。为了评估政策影响,我们想预测如果没有那项政策会发生什么;而要测试一项新政策,我们则希望知道在有与没有政策的情况下会发生什么。
在这两种情况下,我们都试图预测一些从未发生过的事情,或者用数据科学的术语来说,我们没有适当的训练数据集。样本内的预测改进可能带来更多的样本外噪声而非信号。数据科学早已认识到这个问题,并开发了许多方法来标记它,但并没有解决它。样本分割是朝着这个方向迈出的第一步(在社会科学中被极度忽视):它告诉你什么时候某些东西不起作用,但不告诉你为什么或如何修复。所有的“数据漂移检测”技术也是如此:它们提供了信息但没有建设性。最*对数据质量而非数量的重视是计算机科学中对因果关系兴趣日益增长的另一个症状。
另一方面,社会科学领域正在慢慢内化一个事实,即有大量数据存在,这些数据可以用于回答因果问题。特别是,每当我们可以将因果问题框定为预测问题时,就有机会借鉴机器学习文献。主要应用包括用于协变量选择的双重稳健机器学习、用于异质处理效应的广义随机森林、用于潜在结果的矩阵完成方法以及用于动态决策的强化学习。主要的权衡在于大多数(有趣的)数据是私有的,无法用于研究。除了显著阻碍某些领域的科学研究(参见上述竞争政策),它还降低了开发更多数据密集型因果推断方法的激励。
最后,因果推断在行业中的作用已得到确立,并将进一步增长。新兴科技公司将实验和 A/B 测试作为决策核心,而老牌公司则要么适应,要么落后。我看到两个主要趋势正在出现。首先,扩展实验因果推断。网络和溢出效应可能会影响简单的均值差异估计器。即使随机实验提供了无偏的处理效果估计,额外的数据和/或模型也可以帮助提高估计的精确度。第二,准实验方法将变得更加突出,因为并非所有事情都可以随机化,出于实际、伦理或成本原因。我非常期待见证这些趋势的演变,并希望看到跨学科的因果推断方法趋于一致。
要了解更多关于Matteo的工作并及时获取他的最新文章,请关注他的Medium、LinkedIn和Twitter上的动态。为了体验 Matteo 的 TDS 文章,以下是我们档案中的几个亮点:
-
如何比较两个或多个分布(2022 年 6 月,18 分钟)
-
告别散点图,欢迎分箱散点图(2022 年 5 月,18 分钟)
-
贝叶斯 AB 测试(2023 年 1 月,11 分钟)
-
DAGs 和控制变量(2022 年 4 月,7 分钟)
-
理解 CUPED(2022 年 6 月,12 分钟)
感到受启发,想将自己的写作分享给更广泛的受众?我们很想听到你的声音。
此问答经过轻微编辑,以便更简洁明了。
变换器的地图
变换器
变换器研究的广泛概述
·
关注 在 Towards Data Science 上发表 · 25 分钟阅读 · 2023 年 4 月 18 日
--
图 1. 等距地图。由 vectorpocket / Freepik 设计。
1. 介绍
*年来,深度学习的研究进展显著加快,使得跟踪所有最新发展的难度越来越大。尽管如此,有一个特定的研究方向由于其在自然语言处理、计算机视觉和音频处理等多个领域的显著成功,受到了广泛关注。这在很大程度上归功于其高度适应的架构。这个模型被称为 Transformer,它利用了该领域的一系列机制和技术(即注意力机制)。你可以在以下文章中深入了解这些构建块及其实现,并查看多个插图:
简要调查、插图和实现
[towardsdatascience.com
这篇文章提供了更多关于注意力机制的细节,我将在本文中讨论这些机制:
大脑、数学与深度学习——研究前沿
[towardsdatascience.com
2. Transformer 的分类
到目前为止,基于原始 Transformer 已经探索了广泛的模型,这些模型大致可以分为三类:
-
架构修改
-
预训练方法
-
应用

图 2. Transformer 变体修改。照片由作者提供。
上述每个类别包含几个子类别,我将在接下来的部分中深入探讨。图 2 展示了研究人员对 Transformer 的修改类别。
3. 注意力
自注意力在 Transformer 中发挥了基本作用,但在实践中存在两个主要缺点[1]。
-
复杂性:对于长序列,这个模块变成了瓶颈,因为其计算复杂度为 O(T²·D)。
-
结构先验:它没有处理输入的结构偏差,需要在训练数据中注入额外的机制,模型才能后续学习(例如学习输入序列的顺序信息)。

图 3. 注意力修改类别及示例论文。照片由作者提供。
因此,研究人员探索了各种技术来克服这些缺陷。
-
稀疏注意力: 该技术通过只考虑输入序列的一小部分而不是整个输入序列来降低注意力机制的计算时间和内存需求,从而产生稀疏矩阵,相较于全矩阵。
-
线性化注意力: 通过使用核特征映射解构注意力矩阵,这种方法尝试以反向顺序计算注意力,从而将资源需求降低到线性复杂度。
-
原型和内存压缩: 这种修改线试图减少查询和键-值对,以实现较小的注意力矩阵,从而减少时间和计算复杂度。
-
低秩自注意力: 通过显式建模自注意力矩阵的低秩特性,使用参数化或用低秩*似替代,以期提高变换器的性能。
-
带有先验的注意力: 利用来自其他来源的先验注意力分布,这种方法将其他注意力分布与从输入中获得的注意力分布结合起来。
-
修改的多头机制: 有多种方法可以修改和提高多头机制的性能,这些方法可以归类于这一研究方向。
3.1. 稀疏注意力
变换器中的标准自注意力机制要求每个标记都关注所有其他标记。然而,已经观察到在许多情况下,注意力矩阵通常非常稀疏,这意味着只有少量标记实际上彼此关注[2]。这表明,可以通过限制每个查询关注的查询-键对的数量来减少自注意力机制的计算复杂度。通过仅计算预定义模式的查询-键对的相似性分数,可以在不牺牲性能的情况下显著减少所需的计算量。

Eq. 1
在非规范化的注意力矩阵Â中,-∞项通常不会存储在内存中,以减少内存占用。这是为了降低实现矩阵所需的内存量,从而提高系统的效率和性能。
我们可以将注意力矩阵映射到一个二分图,其中标准注意力机制可以被认为是一个完整的二分图,每个查询从记忆中的所有节点接收信息,并利用这些信息更新其表示。这样,注意力机制允许每个查询关注记忆中的所有其他节点,并将它们的信息纳入其表示中。这使得模型能够捕捉记忆节点之间复杂的关系和依赖性。另一方面,稀疏注意力机制可以被认为是一个稀疏图。这意味着图中的并非所有节点都相互连接,这可以减少系统的计算复杂性,提高其效率和性能。通过限制节点之间的连接数量,稀疏注意力机制仍然可以捕捉重要的关系和依赖性,但计算开销较小。
稀疏注意力方法主要有两类,基于用于确定节点之间稀疏连接的指标[1]。这两类是基于位置和基于内容的稀疏注意力。
3.1.1. 基于位置的稀疏注意力
在这种类型的注意力中,注意力矩阵中的连接是根据预定模式进行限制的。它们可以表示为更简单模式的组合,这对理解和分析注意力机制的行为非常有用。

图 4. 主要的原子稀疏注意力模式。彩色方块展示了相应的计算注意力分数。图片来源于[1]。
3.1.1.1. 原子稀疏注意力: 有五种基本的原子稀疏注意力模式可以用来构建各种不同的稀疏注意力机制,这些机制在计算复杂性和性能之间有不同的权衡,如图 4 所示。
-
全局注意力: 全局节点可以作为信息中心,能够关注序列中的所有其他节点,反之亦然,如图 4 (a) 所示。
-
带状注意力(也称为滑动窗口注意力或局部注意力): 数据不同部分之间的关系和依赖性通常是局部的而非全局的。在带状注意力中,注意力矩阵是一个带状矩阵,查询仅关注两侧一定数量的邻*节点,如图 4 (b) 所示。
-
扩张注意力: 类似于扩张卷积神经网络(CNNs)可以在不增加计算复杂性的情况下扩大感受野,通过使用扩张窗口(扩张𝑤_d >= 1)可以实现带状注意力的类似效果,如图 4 (c) 所示。此外,它也可以扩展到步幅注意力,其中扩张𝑤 𝑑 被认为是一个较大的值。
-
随机注意力: 为了提高注意力机制捕捉非局部交互的能力,可以为每个查询随机采样一些边,如图 4(d)所示。
-
块级局部注意力: 输入序列被分割成几个互不交叉的查询块,每个查询块都关联一个局部内存块。每个查询块中的查询仅关注相应内存块中的键,如图 3(e)所示。
3.1.1.2. 复合稀疏注意力: 如图 5 所示,许多现有的稀疏注意力机制由上述描述的多个原子模式组成。

图 5. 四种不同的复合稀疏注意力模式。图片来自[1]。
3.1.1.3. 扩展稀疏注意力: 还有其他类型的模式已被探索用于特定数据类型。例如,BP-Transformer [3] 使用二叉树来捕捉输入序列中全局和局部注意力的组合。令牌是叶节点,内部节点是包含多个令牌的跨度节点。图 6 展示了多种扩展稀疏注意力模式。

图 6. 不同的扩展稀疏注意力模式。图片来自[1]。
3.1.2. 基于内容的稀疏注意力
在这种方法中,构建一个稀疏图,其中稀疏连接基于输入。它选择与给定查询具有高相似度的键。构建此图的高效方法是使用最大内积搜索(MIPS),该方法在不计算所有点积的情况下找到键与查询之间的最大点积。

图 7. Routing Transformer 的 2-D 注意力方案,与局部注意力和跨步注意力相比。图片来自[4]
Routing Transformer [4] 如图 7 所示,通过使用在线 k-means 聚类对键和值进行同心心向量聚类,为自注意力机制配备了稀疏路由模块。它将查询隔离,只关注同一簇内的键。Reformer [5] 使用局部敏感哈希(LSH)代替点积注意力,为每个查询选择键和值。它使查询仅关注来自 LSH 生成的同一桶中的令牌。使用 LSTM 边预测器,稀疏自适应连接(SAC) [6] 从输入序列中构建图,并通过利用自适应稀疏连接来增强任务特定的性能。
3.2. 线性化注意力
点积注意力机制的计算复杂度(softmax(QK^⊤)V)随着输入的空间时间大小(长度)的增加而呈二次增加。因此,当暴露于大输入(如视频、长序列或高分辨率图像)时,它阻碍了其使用。通过将 softmax(QK^⊤) 解开成 Q′ K′^⊤,(Q′ K′^⊤ V) 可以按相反顺序计算,结果是线性复杂度 O(𝑇)。

图 8. 标准自注意力和线性化自注意力的复杂度差异。图片来自 [1]。
假设 Â = exp(QK^⊤) 表示未归一化的注意力矩阵,其中 exp(.) 逐元素应用,线性化注意力是一种*似未归一化注意力矩阵 exp(QK^⊤) 的技术,其形式为 𝜙(Q) 𝜙(K)^⊤,其中 𝜙 是逐行特征映射。通过应用这种技术,我们可以执行 𝜙(Q) (𝜙(K)^⊤ V),这是未归一化注意力矩阵的线性化计算,如图 8 所示。
为了更深入地理解线性化注意力,我将探索向量形式的公式。我将检查注意力的一般形式,以获得进一步的洞见。

公式 2
在这个背景下,sim(·, ·) 是衡量输入向量相似性的评分函数。在标准 Transformer 中,评分函数是内积的指数形式,exp(⟨·, ·⟩)。一个适合的选择是核函数 sim(·, ·) = K(x, y) = 𝜙(x)𝜙(y)^⊤ ,这进一步揭示了线性化注意力的洞见。

公式 3
在这个公式中,向量的外积用 ⊗ 表示。注意力可以通过首先计算突出显示的术语来线性化,这允许自回归模型,即 Transformer 解码器像 RNN 一样运行。
公式 2 表明,通过聚合(特征映射后的)键和查询的外积,它保留了一个内存矩阵。稍后通过将内存矩阵与特征映射后的查询乘以适当的归一化来检索它。
这种方法由两个基础组件组成:
-
特征映射 𝜙 (·): 每种注意力实现的核特征映射(例如,Linear Transformer 提出的𝜙𝑖(x) = elu(𝑥 𝑖 )+1)。
-
聚合规则: 通过简单求和将关联 {𝜙 (k)𝑗 ⊗ v𝑗} 聚合到内存矩阵中。
3.3. 查询原型化和内存压缩
除了使用稀疏注意力或基于核的线性化注意力,还可以通过减少查询或键值对的数量来缓解注意力的复杂性,从而引入查询原型和内存压缩技术。

图 9. 查询原型化和内存压缩。照片来自 [1]
3.3.1. 带原型查询的注意力: 实施带原型查询的注意力涉及使用一组查询原型作为计算注意力分布的主要依据。模型采用两种不同的方法,要么将计算得到的分布复制到代表查询的位置,要么在这些位置填充离散均匀分布。该过程的计算流程如图 9(a)所示。
聚类注意力,如[7]所述,涉及将查询聚合到几个簇中,注意力分布则是针对这些簇的质心计算的。簇内所有查询都被分配给相应质心计算出的注意力分布。
Informer,如[8]所述,采用了显式查询稀疏度测量的方法,该方法源自于对查询的注意力分布与离散均匀分布之间 Kullback-Leibler 散度的*似,以选择查询原型。然后,仅对由查询稀疏度测量确定为前𝑢个的查询计算注意力分布,而其余查询则分配离散均匀分布。
3.3.2. 压缩键值内存的注意力: 这种技术通过在应用注意力之前减少键-值对的数量来降低 Transformer 中注意力机制的复杂性,如图 9(b)所示。这通过压缩键值内存来实现。压缩内存然后用于计算注意力分数。这种技术可以显著降低注意力的计算成本,同时在各种自然语言处理任务上保持良好的性能。
Liu et al. [9] 在其论文中提出了一种名为 Memory Compressed Attention (MCA) 的技术。MCA 使用跨步卷积来减少键和值的数量。MCA 与本文中也提出的局部注意力一起使用。通过将键和值的数量减少到卷积核大小的因子,MCA 能够捕获全局上下文并处理比标准 Transformer 模型更长的序列,同时保持相同的计算资源。
Set Transformer [10] 和 Luna [11] 是两个利用外部可训练的全局节点来压缩输入信息的模型。压缩表示然后作为输入的注意力的压缩内存,有效地将自注意力的二次复杂度降低到与输入序列长度线性相关的复杂度。
Linformer [12]将自注意力的计算复杂度线性降低,通过将键和值从长度为 n 线性投影到更小的长度 n_k。这种方法的缺点是预设的输入序列长度,因此不适合自回归注意力模型。
Poolingformer [13] 采用了一个两级注意力机制,将滑动窗口注意力与压缩内存注意力相结合。压缩内存注意力有助于扩大感受野。为了减少键和值的数量,探索了几种池化操作,包括最大池化和基于动态卷积的池化。
3.4. 低秩自注意力
根据各种研究者 [14, 12] 进行的实证和理论分析,自注意力矩阵 A ∈ R𝑇 ×𝑇 在许多情况下表现出低秩特性。这一观察提供了两个含义:首先,可以使用参数化显式建模低秩特性。这可能会导致开发利用这一特性以提高性能的新模型。其次,可以用低秩*似代替完整的自注意力矩阵。这种方法可以实现更高效的计算,并进一步提高基于自注意力的模型的可扩展性。
3.4.1. 低秩参数化: 当注意力矩阵的秩低于序列长度时,这表明通过设置 𝐷𝑘 > 𝑇 来过度参数化模型会导致在输入通常较短的情况下出现过拟合。因此,限制 𝐷𝑘 的维度并利用低秩特性作为归纳偏差是明智的。为此,Guo 等人 [14] 提出了将自注意力矩阵分解为一个小的 𝐷𝑘 低秩注意力模块,用于捕捉长距离的非局部交互,以及一个带状注意力模块,用于捕捉局部依赖。这种方法在输入较短且需要有效建模局部和非局部依赖的场景中可能会有所帮助。
3.4.2. 低秩*似: 也可以利用注意力矩阵的低秩特性,通过使用低秩矩阵*似来降低自注意力的复杂性。这种方法与核矩阵的低秩*似密切相关,一些现有工作受到核*似的启发。例如,Performer(如第 3.2 节所讨论的)使用了一种最初用于*似高斯核的随机特征映射,将注意力分布矩阵 A 分解为 C𝑄 GC𝐾,其中 G 是高斯核矩阵,随机特征映射*似 G。
处理注意力矩阵低秩特性的一种替代方法是使用基于 Nyström 的方法 [15, 16]。在这些方法中,从输入序列中选择一部分地标节点,使用下采样技术,如步长*均池化。选择的地标节点被用作查询和键,以*似注意力矩阵。具体而言,注意力计算包括对原始查询与选择的键的乘积进行 softmax 归一化,然后计算选择的查询与归一化结果的乘积。这可以表示为:

方程 4
注意,矩阵 M^-1 = (softmax(Q̃K̃T))-1 的逆可能并不总是存在,但可以通过多种方式减轻此问题。例如,CSALR [15] 向 M 添加单位矩阵以确保逆矩阵始终存在,而 Nyström-former [16] 使用 M 的 Moore-Penrose 伪逆来处理奇异情况。
3.5. 带先验的注意力
注意力机制是一种关注输入序列中特定部分的方法。它通过生成序列中向量的加权和来实现,其中权重由注意力分布决定。注意力分布可以从输入中生成,也可以来自其他来源,如先验知识。在大多数情况下,输入的注意力分布和先验注意力分布通过计算它们分数的加权和后结合,然后应用 softmax,从而使神经网络能够从输入和先验知识中学习。

图 10. 带先验的注意力将生成的注意力分数和先验注意力分数结合起来计算最终的注意力分数。图片来自 [1]。
3.5.1. 建模局部性的先验: 为了建模某些类型数据的局部性,如文本,可以使用位置上的高斯分布作为先验注意力。这涉及到将生成的注意力分布与高斯密度相乘,并对生成的注意力分数进行归一化或添加偏置项 G,其中更高的 G 表示对特定输入的先验概率更高。
Yang 等人 [17] 提出了预测每个输入的中心位置并相应地定义高斯偏置的方法:

方程 5
其中 𝜎 表示高斯的标准差。高斯偏置定义为中心位置与输入位置之间的*方距离的负值,除以高斯分布的标准差。标准差可以作为超参数确定,也可以从输入中预测。

图 11. 使用窗口大小为 2 (D = 2) 的[17]提出的方法进行说明。图片来自 [17]。
高斯 Transformer [18] 模型假设每个输入查询 𝑞𝑖 的中心位置为 𝑖,并将生成的注意力分数的偏置项 𝐺𝑖 𝑗 定义为

方程 6
其中 𝑤 是一个非负标量参数,控制偏差,𝑏 是一个负标量参数,减少中心位置的权重。
3.5.2. 来源于低层模块: 在 Transformer 架构中,相邻层之间的注意力分布通常被发现是相似的。因此,使用来自低层的注意力分布作为计算高层注意力的先验是合理的。这可以通过将当前层的注意力分数与前一层注意力分数的加权和以及将前一层分数映射到要应用的先验的转换函数相结合来实现。

Eq. 7
其中 A(𝑙) 代表 l- 层注意力分数,而 w1 和 w2 控制之前的注意力分数和当前注意力分数的相对重要性。此外,函数 𝑔: R𝑛×𝑛 → R𝑛×𝑛 将之前的注意力分数转化为应用于当前注意力分数的先验。
论文 [19] 中提出的 Predictive Attention Transformer 建议在之前的注意力分数上使用 2D 卷积层来计算最终的注意力分数,该分数是生成的注意力分数和卷积分数的凸组合。换句话说,生成和卷积分数的权重参数分别设置为 𝛼 和 1-𝛼,而 Eq. (6) 中的函数 𝑔(·) 是一个卷积层。论文中的实验表明,无论是从头开始训练模型还是在适配预训练 BERT 模型后进行微调,都比基线模型有了改进。
论文 [20] 中提出的 Realformer 模型通过直接将之前的注意力分数添加到新生成的分数中,引入了对注意力图的残差跳跃连接。这可以视为在 Eq. (6) 中将 𝑤 1 = 𝑤 2 = 1 和 𝑔(·) 设置为恒等映射。作者在该模型上进行的预训练实验报告称,该模型在多个数据集上优于基线 BERT 模型,即使在显著降低预训练预算的情况下。
Lazyformer [21] 提出了一个创新方法,通过在相邻层之间共享注意力图来降低计算成本。这是通过将 𝑔(·) 设为恒等映射并交替切换 𝑤 1 = 0, 𝑤 2 = 1 和 𝑤 1 = 1, 𝑤 2 = 0 的设置来实现的。这种方法使得只需计算一次注意力图,并在后续层中重复使用。Lazyformer 进行的预训练实验表明,他们的模型不仅高效,而且有效,超越了基线模型,并显著降低了计算预算。
3.5.3. 作为多任务适配器的先验: 作为多任务适配器的先验方法使用可训练的注意力先验,这些先验使得跨任务的参数共享更为高效 [22]。条件自适应多任务学习(CAMTL)[23] 框架是一种多任务学习技术,它使得在任务之间高效共享预训练模型成为可能。CAMTL 使用依赖于任务编码的可训练注意力先验,作为多任务诱导知识转移的适配器。具体来说,注意力先验被表示为块对角矩阵,添加到预训练 Transformer 的上层注意力分数中:

Eq. 8
在其中,⊕ 表示直接和,𝐴𝑗 是具有 (𝑛/𝑚)×(𝑛/𝑚) 维度的可训练参数,𝛾𝑗 和 𝛽𝑗 是具有输入和输出维度为 R𝐷𝑧 和 (𝑛/𝑚)×(𝑛/𝑚) 的特征线性调制函数 [24]。CAMTL 框架在实现中规定了最大序列长度 𝑛𝑚𝑎𝑥。注意力先验是一个可训练矩阵,它被添加到预训练 Transformer 的上层注意力分数中。这种添加创建了一个适配器,使得多任务诱导知识转移在参数上更高效。先验被组织为块对角矩阵以提高计算效率。
3.5.4. 仅使用先验的注意力: Zhang 等人 [25] 开发了一种替代的注意力分布方法,该方法不依赖于输入之间的成对交互。他们的方法称为“*均注意力网络”,它使用离散均匀分布作为注意力分布的唯一来源。然后将这些值聚合为所有值的累积*均值。为了增强网络的表达能力,在*均注意力模块上添加了一个前馈门控层。这种方法的好处是,修改后的 Transformer 解码器可以以并行方式进行训练,并且能够像 RNN 一样解码,避免了与解码相关的 O(𝑇²) 复杂性。
类似于 Yang 等人 [17] 和 Guo 等人 [18],它们使用固定的局部窗口进行注意力分布,You 等人 [26] 将硬编码的高斯分布注意力用于注意力计算。然而,他们完全忽略了计算得到的注意力,只使用高斯分布进行注意力计算,其中均值和方差是超参数。只要在自注意力上实现,它就可以在机器翻译任务中产生接*基线模型的结果。
Synthesizer [27] 提出了一种在 Transformers 中生成注意力分数的新方法。他们用两种变体替代传统的生成注意力分数的方法:(1) 可学习的、随机初始化的注意力分数,以及 (2) 由仅对输入进行条件处理的前馈网络输出的注意力分数。他们在机器翻译和语言建模任务上的实验结果表明,这些变体的表现与标准 Transformer 模型相当。然而,这些变体为何有效尚未完全解释,仍有进一步研究的空间。
3.6. 改进的多头机制
多头注意力是一种强大的技术,因为它允许模型同时关注输入的不同部分。然而,不能保证每个注意力头都会学习到独特且互补的特征。因此,一些研究人员探索了确保每个注意力头捕捉到不同信息的方法。
3.6.1. 头部行为建模: 多头注意力是自然语言处理模型中的一个有用工具,因为它允许同时处理多个输入和特征表示[28]。然而,传统的 Transformer 模型缺乏确保不同注意力头捕捉到不同且非冗余特征的机制。此外,也没有头部之间相互作用的规定。为了解决这些局限性,*期的研究集中于引入新颖的机制来指导注意力头的行为或使它们之间能够进行交互。
为了促进不同注意力头之间的多样性,Li 等人 [29] 在损失函数中提出了额外的正则化项。这个正则化由两部分组成:前两部分旨在最大化输入子空间和输出表示之间的余弦距离,而后者通过元素级乘法鼓励多个头部关注的位置的分散。通过添加这一辅助项,模型被鼓励在不同头部之间学习到更多样的注意力模式,从而提高在各种任务上的性能。
许多研究表明,预训练的 Transformer 模型展示了某些自注意力模式,这些模式与自然语言处理并不完全契合。Kovaleva 等人 [30] 在 BERT 中识别出其中的几种模式,包括专注于特殊标记 [CLS] 和 [SEP] 的注意力头。为了改进训练,Deshpande 和 Narasimhan [31] 提出了使用辅助损失函数,该函数测量注意力分布图与预定义注意力模式之间的 Frobenius 范数。这种方法引入了约束,以鼓励更有意义的注意力模式。
在 Shen 等人[32]的论文中,提出了一种名为“Talking-head Attention”的新机制,该机制旨在鼓励模型以可学习的方式在不同的注意力头之间传递信息。该机制包括将生成的注意力分数从隐藏维度线性投影到具有 h_k 个头的新空间,在该空间中应用 softmax,然后将结果投影到另一个具有 h_v 个头的空间以进行值聚合。通过这种方式,注意力机制可以学习在不同注意力头之间动态传递信息,从而提高各种自然语言处理任务的性能。
协作多头注意力是一种在[33]中提出的机制,涉及使用共享的查询和键投影,分别记作 W𝑄和 W𝐾,以及一个混合向量 m𝑖。该混合向量用于过滤𝑖-th 头的投影参数。具体而言,注意力计算被调整以反映这一机制,从而得出修改后的方程(3)。

Eq. 9
其中所有头共享 W^q 和 W^k。
3.6.2. 具有限制范围的多头注意力:
标准的注意力机制通常假设全范围注意力,允许查询对所有键值对进行注意。然而,已经观察到一些注意力头更倾向于关注局部上下文,而其他注意力头则关注更广泛的上下文。因此,对特定目的施加注意力范围的约束可能是有利的:
-
局部性:限制注意力范围可以明确施加局部约束,这在局部性是重要考虑因素的情况下尤为有益。
-
效率:如果实施得当,这种模型可以在不增加额外内存使用或计算时间的情况下扩展到更长的序列。
限制注意力范围涉及将每个注意力分布值与掩码值相乘,然后进行重新归一化。掩码值可以由一个非递增函数确定,该函数将距离映射到[0, 1]范围内的一个值。在标准注意力中,对于所有距离分配掩码值为 1,如图 12(a)所示。

图 12 展示了三种不同类型的范围掩码函数,记作𝑚(𝑥)。水*轴表示距离𝑥,而垂直轴表示相应的掩码值。这一视觉表示提供了这些掩码函数展示的不同行为和模式的洞见,清晰地展示了掩码值如何随着键值对之间的距离变化。图片来源于[1]。
在 Sukhbaatar 等人 [34] 的研究中,提出了一种新颖的方法,引入了一个可学习的注意力范围,如有趣的图 12(b)所示。这种创新技术利用了由可学习标量 𝑧 参数化的掩码,并结合超参数 𝑅,自适应地调节注意力范围。实验结果表明,这些自适应范围模型在字符级语言建模上优于基线模型,同时需要显著更少的 FLOPS。值得注意的是,一个有趣的观察是模型的低层通常显示出较小的学习范围,而高层则表现出较大的范围。这一有趣发现表明模型可以自主学习特征的层次组合,展示了其捕捉数据中复杂模式和结构的卓越能力。
多尺度变换器 [35] 提出了一个新的注意力范围方法,挑战了传统范式。与假设所有头部具有统一注意力范围的普通注意力不同,该创新模型引入了一个具有动态缩放的固定注意力范围。如图 12(c)所示,固定注意力范围作为一个可以缩放的窗口,由标记为 𝑤 的尺度值控制。

图 13. 多尺度多头自注意力,其中描绘了三个头部,每个头部代表不同的尺度。蓝色、绿色和红色框分别表示尺度 ω = 1、ω = 3 和 ω = 5。照片由 作者 提供。
尺度值各异,高层倾向于使用较大的尺度以获取更广泛的上下文依赖,而低层则选择较小的尺度以获得更局部的关注,如图 13 所示。多尺度变换器的实验结果显示,它在各种任务上表现出优于基线模型的性能,展示了其在语言处理上的更高效和有效的潜力。
3.6.3. 多头与精细化聚合:
普通的多头注意力机制,如 Vaswani 等人 [28] 提出的,涉及计算多个并行操作的注意力头,以生成各自的输出表示。这些表示然后被串联,并进行线性变换,如 Eq. (11) 定义,以获得最终的输出表示。通过结合 Eq. (10)、(11) 和 (12),可以观察到这种串联与投影的形式等同于对重新参数化的注意力输出进行求和。这种方法允许高效地聚合多样的注意力头输出,使模型能够捕捉输入数据中的复杂依赖关系和关系。

Eq. 10
和

Eq. 11
其中

Eq. 12
为了便于聚合过程,线性变换使用的权重矩阵 W𝑂 ∈ R𝐷𝑚 ×𝐷𝑚 被划分为𝐻个块,其中𝐻表示注意力头的数量。

Eq. 13
权重矩阵 W𝑂_𝑖,维度为𝐷𝑣 × 𝐷𝑚,用于每个注意力头中的线性变换,通过连接-投影公式重新参数化注意力输出,如 Eq. (14)所定义:

Eq. 14
一些研究人员可能会争辩说,简单的加和聚合方法可能无法充分利用多头注意力的表达能力,更复杂的聚合方案可能更为理想。
Gu 和 Feng [36] 以及 Li 等人 [37] 提出了使用最初为胶囊网络 [38] 设计的路由方法,作为进一步聚合来自不同注意力头信息的一种手段。通过将注意力头的输出转化为输入胶囊并随后经历迭代路由过程,获得输出胶囊。这些输出胶囊随后被连接作为多头注意力机制的最终输出。值得注意的是,这些工作中使用的动态路由 [38] 和 EM 路由 [39] 机制引入了额外的参数和计算开销。然而,Li 等人 [37] 实证表明,选择性地将路由机制应用于模型的较低层次能在翻译性能和计算效率之间取得最佳*衡。
3.6.4. 其他多头修改:
除了上述修改之外,还提出了几种其他方法来增强多头注意力机制的性能。Shazeer [40] 引入了多查询注意力的概念,其中键值对在所有注意力头之间共享。这减少了解码过程中的内存带宽需求,并使解码速度更快,尽管与基线相比存在轻微的质量下降。另一方面,Bhojanapalli 等人 [41] 发现注意力键的大小可能会影响它们表示任意分布的能力。为了解决这个问题,他们提出将头的大小与头的数量分开,这与传统上将头的大小设定为𝐷𝑚/ℎ的做法相反,其中𝐷𝑚是模型维度,ℎ是头的数量。
4. 总结
总结来说,变换器的分类及注意机制的各种进展显著扩展了基于变换器的模型的能力和效率。稀疏注意技术,如基于位置和基于内容的稀疏注意,以及线性化注意,解决了传统稠密注意的计算限制。查询原型和内存压缩方法引入了创新的方法来提高注意机制的效率。低秩自注意使得参数化和*似技术用于更高效的注意计算。引入先验,如局部建模、较低模块先验和多任务适配器,在提高注意机制方面表现出令人鼓舞的结果。最后,对多头机制的修改,如头行为建模、受限跨度、精细聚合及其他变体,展示了进一步提升基于变换器的模型性能的潜力。
注意机制的这些进展为未来在自然语言处理、计算机视觉和机器翻译等多个领域的研究和应用提供了令人兴奋的前景。通过利用这些创新技术,基于变换器的模型可以继续突破性能和效率的界限,为高级机器学习应用打开新的可能性。
请在下方评论区分享你的想法、问题和意见。
5. 即将到来的主题:揭示变换器旅程中的下一个章节
在未来的文章中,将更详细地讨论以下主题:
-
其他模块级修改: 这些涵盖了模块级的其他修改,如位置表示、层归一化和位置-wise 前馈网络(FFN),它们在基于变换器的模型的性能和效率中发挥了至关重要的作用。
-
架构级变体: 本节将探讨变换器的各种架构级变体,包括将变换器调整为轻量级、增强跨块连接、适应性计算时间、采用分而治之策略的变换器,以及探索替代架构设计以进一步提升变换器的能力和效率。
-
预训练变换器: 深入探讨预训练变换器,这些变换器*年来因其利用大规模预训练数据提高下游任务性能的能力而受到广泛关注,本节将讨论不同的预训练技术,如 BERT、GPT 和 T5。
-
变压器的应用: 在这一部分中将突出显示变压器在各种领域展示出卓越性能的多样化应用,包括自然语言处理、计算机视觉、语音识别和推荐系统等。将讨论变压器在各个领域中的潜力和多样性。
-
研究方向: 提供关于变压器研究和开发未来方向的见解,讨论变压器模型在新兴趋势、挑战和机会方面的进一步发展,展示变压器在未来几年里的令人兴奋的可能性。
通过涵盖这些主题,本文旨在全面介绍变压器的进展、修改、应用和未来发展方向,揭示这些强大模型在推动下一代机器学习和人工智能应用中的潜力。
参与 TransformerX 项目:参与方式
[## GitHub - tensorops/TransformerX: Flexible Python library providing building blocks (layers) for…
灵活的 Python 库,为可重现的 Transformer 研究提供构建块(层)(Tensorflow ✅…
我感谢那些在开发 TransformerX 库中给予启发和鼓励的人。我们一直期待您以以下形式做出贡献:
-
贡献代码和开发新层次: 查看特定问题标记为
issue_list的想法,并在我们的指南的帮助下开始实施它们。 -
建议新功能或报告错误: 创建问题来分享你的建议或报告你遇到的任何问题。
-
编写文档和教程资源: 帮助我们改进库的文档。
-
在社交媒体上分享和撰写 TransformerX 相关内容: 在 Twitter 上提及我们,进行转发,或在 LinkedIn 上分享。
如果你不确定编码,不用担心,我们会在每一步都帮助你进行第一次贡献。
🌟 在 GitHub 上为 TransformerX 点星,展示你对项目的支持!你的贡献帮助我们不断改进和发展这个库。谢谢!🚀
喜欢看更多相关内容吗?
关注我的Twitter🐦,GitHub🚀(我最活跃的*台),让我们在LinkedIn💼上连接,当然,关注我的Medium 📝,并订阅我的文章。
此外,这里是我的网站。
参考文献
有关参考文献的列表,请访问这个gist。这将为您提供一份全面的参考资料列表,以便进一步阅读和深入了解该主题。祝您探索愉快!
线性回归的矩阵代数
查看线性回归背后的矩阵运算
·
关注 发布于 Towards Data Science ·11 分钟阅读·2023 年 5 月 3 日
--
图片由 Mingwei Lim 提供,来源于 Unsplash
介绍
对大多数人来说,简单线性回归是理解基于模型的估计和模型比较的常见起点。无论你是在学习入门统计学还是数据科学课程,你都可以肯定线性回归会在某个时刻出现。这是有充分理由的。
简单线性回归通过将响应变量建模为仅由单个预测变量的线性组合,自然地扩展了简单描述性统计。这种简单性不仅有助于解释模型参数,而且使得通过普通最小二乘法(OLS)进行估计更容易理解。
虽然大多数教科书介绍会提供详细的数学处理,除了更广泛的概念元素外,但在实际实现模型时,几乎从不会通过第一性原理来进行。不论你使用什么语言,几乎总会有一个便利函数来为你拟合模型。那为什么不使用呢?你不必手动进行所有那些繁琐的计算!这无疑是一个优点;不过,我坚信花一些时间熟悉模型的统计机制是成为一个有效的分析师和数据科学家的重要部分。
在之前的文章中,我提供了一个线性代数的入门,涉及了一些基本原理和操作。在这篇文章中,我们将基于这些概念,深入了解线性回归背后的矩阵操作。
矩阵形式的简单线性回归
大多数人都对标准回归公式很熟悉,它将响应变量 Y 作为单个预测变量 X 的线性组合建模:

线性回归方程(作者提供的图片)。
在这里,我采用了假设误差服从正态分布的惯例。从这里开始,我们将通过将元素分配给向量和矩阵,建立矩阵表示。
首先,我们将所有响应放在一个 n 维的向量中,这个向量称为 响应向量:

响应向量(作者提供的图片)。
我在这里非常明确地包括了向量的大小 —— 实际上它表示为一个 列矩阵 —— 这样我们可以跟踪情况。然而,如果你愿意,使用小写粗体 y 也是完全合理的。
接下来,预测变量 X 被放置在一个 n × p 的矩阵中,称为设计矩阵:

设计矩阵(作者提供的图片)。
其中 p 表示列的数量,并对应于模型中的系数数量。请注意,第一列仅包含 1 —— 我们稍后会讨论这一点 —— 这是为了适应截距,这是一个常数。因此,设计矩阵中的列数总是比你拥有的预测变量数多一个。在上面的示例中,我们只有一个预测变量,这意味着我们需要估计一个截距和一个斜率;因此,在这种情况下 p = 2。
回归系数也被放置在一个 p × 1 的向量中,这个向量称为 参数向量:

参数向量(作者提供的图像)。
再次说明,p 表示参数的数量,不过这里的 p 表示行数,而设计矩阵中的 p 是列维度。这个安排很重要,因为我们需要对这两个对象进行一些矩阵乘法以计算线性预测器。
不过,在我们进行这些操作之前,还有最后一件事要做——将所有的误差项放入一个 n × 1 向量中:

误差向量(作者提供的图像)。
有了这些,我们现在可以使用矩阵符号来表示简单线性回归模型,如下所示:

矩阵形式的线性回归模型(作者提供的图像)。
线性预测器
用语言表述,线性回归模型的矩阵形式是两个矩阵 X 和 β 的乘积加上一个误差向量。X 和 β 的乘积是一个 n × 1 的矩阵,称为线性预测器,我在这里表示为:

线性预测器向量(作者提供的图像)。
现在,矩阵乘法的工作方式与您预期的稍有不同。我在我的线性代数入门中讲解过这一点——如果您还没有查看,可以在这里找到——但我现在会快速介绍一下细节。
如果我们有一个 m × q 矩阵 A 和另一个 q × r 矩阵 B,那么它们的乘积是一个 m × r 矩阵 C(注意 q 维度从结果中消失了)。这种大小变化的原因是,因为 C 中第 i 行第 j 列的元素是 A 中第 i 行和 B 中第 j 列的 点积:

点积(作者提供的图像)。
因此,由于点积在 q 维度上取和,这一维度在结果矩阵中被省略了。
对于简单线性回归情况,乘积是一个 n × p 矩阵 X 和一个 p × 1 矩阵 β 之间的乘积,因此结果是一个 n × 1 矩阵 η。根据上述内容,η 中的(i, j)元素是使用以下点积计算的:

扩展的线性预测器(作者提供的图像)。
这里的和是对 p 进行的,我们知道 p 是模型中的系数数量,因此 p = 2。
如果我们将点积代入线性预测器向量,并代入设计矩阵第一列的值,我们得到以下结果(因为 j = 1,我将省略那个下标,以简化符号):

完整的线性预测器(作者提供的图像)。
所有这些都简化为非常熟悉的形式:η中的每个元素只是我们的线性回归方程应用于每个X值!希望你能理解为什么设计矩阵中包含了全 1 列。这确保了截距被加到每个观测值上。
模型误差
与误差项相关的三个关键假设 — 或扰动 — 源于高斯-马尔科夫定理。第一个假设是误差的期望条件均值为零,这意味着误差的*均值不应依赖于任何特定的X值。这被称为零条件均值假设:

零条件均值假设(图片由作者提供)。
与此相关的是同方差性 假设,它表明误差的方差不应受到自变量值的影响。也就是说,误差的分布应完全独立于设计矩阵中的任何信息:

同方差性假设(图片由作者提供)。
最后的假设是无自相关假设,它要求误差项是无关的。这意味着对一个误差项的了解不会提供关于另一个误差项的信息,因此它们不会共变:

无自相关假设(图片由作者提供)。
在这些假设下,误差项的协方差矩阵是一个标量矩阵,误差被认为是球形的:

高斯-马尔科夫假设下的协方差矩阵(图片由作者提供)。
在继续之前要说明的是,虽然我采用了正态分布误差的惯例,但高斯-马尔科夫定理并不要求误差项是正态分布的,也不要求误差是独立且同分布的;只要求误差项是同方差且无相关的。这意味着一个变量可以有依赖关系,但只要这些依赖关系不是线性的 — 这是相关性所衡量的 — 那么参数估计可以安全进行。
通过普通最小二乘法进行参数估计
当将线性回归模型拟合到数据时,目标是估计包含在β中的未知模型系数。通常的做法是满足最小二乘准则,其目标是最小化线性预测器与响应之间的总*方误差。我将逐步介绍如何为简单线性回归模型实现这一点,尽管会快速通过这一部分。
以矩阵形式,误差向量或残差定义如下:

模型误差或残差(图片由作者提供)。
其中,β 上方的帽子表示估计系数。*方残差的和可以写成误差向量与自身的点积:

*方和误差(作者提供的图像)。
其中 T 表示转置运算符¹。为了推导最小二乘准则,将误差完全展开是方便的,如下所示:

误差向量的点积展开(作者提供的图像)。
那么,想法是找到使该值最小化的参数。为此,我们需要对向量 β 取上面的导数并将其设为零:

*方误差的第一导数(作者提供的图像)。
从中可以推导出 正规方程:

正规方程(作者提供的图像)。
从这里,我们可以通过将正规方程的两边乘以 XᵀX 的逆来找到我们未知的参数。我在这篇 文章 中介绍了矩阵求逆的内容,虽然如果你还没有阅读,那些内容可以帮助你理解,矩阵 A 是可逆的,如果存在一个矩阵 B 使得它们的乘积返回单位矩阵 I。
对于简单的线性回归模型,XᵀX 是一个 2 × 2 的方阵,虽然更一般来说,矩阵将是一个 p × p 的矩阵。 我们接着需要找到另一个 2 × 2 的矩阵,它是 XᵀX 的乘法逆。如果这样的矩阵不存在,则方程无法解决,但如果 XᵀX 确实是可逆的,则我们可以得到参数向量 b,如下所示:

线性回归的估计方程(作者提供的图像)。
在这里你可以看到为什么设计矩阵必须不是秩亏的。如果列之间确实存在线性依赖,则 XᵀX 不能被逆,且无法找到唯一解²。如果存在 完美多重共线性,这尤其适用。
参数估计的进一步观察
进一步考虑每个矩阵中包含的元素是很有趣的。首先,让我们看一下设计矩阵 XᵀX 的叉积:

设计矩阵的叉积(作者提供的图像)。
从这里我们可以看到矩阵包含了设计矩阵中每一列的乘积。但我们需要的是这个矩阵的逆。我不会详细讲解如何推导逆矩阵,但它看起来是这样的:

设计矩阵叉积的逆(作者提供的图像)。
最后,我们还需要设计矩阵与响应向量 Y 的叉积,结果如下:

设计矩阵与响应向量的叉积(作者提供的图像)。
将矩阵完全写出后,我们可以将其代入估计问题,并按如下方式进行计算:

参数向量的推导(作者提供的图像)。
公*地说,这一推导过程有些复杂,但最有趣的实际上是最后一行。这一切归结为一个非常方便的东西;我们可以像这样使用样本协方差和方差来估计斜率系数:

斜率系数的估计(作者提供的图像)。
一旦我们有了这个估计值,我们可以利用这个估计值,以及y和x的均值,来推导截距的估计值:

截距系数的估计(作者提供的图像)。
拟合值
我们现在使用了一些线性代数来找到简单线性回归模型的最佳拟合参数。既然我们已经掌握了这些参数,下一步是查看拟合值与响应向量中包含的值的对应程度。
我们需要获得拟合值的唯一条件是设计矩阵和参数向量b。将它们相乘,拟合值的计算如下:

拟合值的向量(作者提供的图像)。
请注意,我们的拟合值上面放有一个帽子,以表示它们是估计值。因此,表达拟合值的另一种方式是将其作为响应向量和帽子矩阵的组合,帽子矩阵之所以得名,是因为它给Y加了一个帽子。
要查看这一点,我们将通过用参数向量的完整方程替代b来写出拟合值的方程:

替代的参数向量扩展版(作者提供的图像)。
基本上,所有涉及设计矩阵X的项被归并在一起,形成了帽子矩阵的定义:

‘帽子’矩阵(作者提供的图像)。
最后的说明
如果你能读到这里,感谢你坚持下来。确实有很多内容需要解读。然而,我希望你能看到矩阵代数的原理是如何用于构建简单线性回归模型的。现在,虽然我在整个过程中专注于简单线性回归以保持示例简洁,但这里讨论的所有内容对多重回归同样适用。唯一变化的是矩阵和向量的维度增加。
脚注
[1] 我在这里使用的符号与我在入门文章中使用的不同,但它们的意思完全相同。之所以进行更改,是为了与矩阵公式的通常描述保持一致。
[2] 严格来说,奇异矩阵是可以被逆的。广义逆扩展了逆的概念,并可以应用于更广泛的矩阵类别。广义逆在寻找最小二乘解时尤其有用,特别是在没有唯一解的情况下。
相关文章
感谢阅读!
如果你喜欢这篇文章并希望保持更新,请考虑关注我在 Medium 的账号。 这样可以确保你不会错过任何新内容。
想要无限访问所有内容,可以考虑注册Medium 订阅。
你也可以在Twitter、LinkedIn上关注我,或者查看我的GitHub。
R 中的线性回归矩阵代数
探索如何使用 R 的矩阵运算符估计回归参数
·
关注 发表在 Towards Data Science ·12 分钟阅读·2023 年 5 月 10 日
--
图片由 Breno Machado 提供,来源于 Unsplash
介绍
我最*写了一篇文章,探讨了线性回归背后的矩阵代数和数学运算。虽然掌握理论原则确实很重要,但没有什么能比得上实际进行这些计算。所以,在这篇后续文章中,我们将探讨如何使用 R 实现这些矩阵运算。
本文应作为我之前的文章的补充,如果你还没读过这篇文章,我鼓励你去看看;不过即使没读过,你也能继续跟进。
数据
作为我们的工作示例,我选择了 R 中 datasets 包里的 cars 数据集。这是一个简单的数据集,包含了在不同速度下的汽车停止距离。因此,这个数据集包含两个变量:speed 和 dist。不过,这些观测数据是 1920 年代的,因此绝不是最新的数据!尽管如此,它非常适合用来建立一个简单的线性回归模型。
首先,我们快速查看一下数据:
> str( cars )
'data.frame': 50 obs. of 2 variables:
$ speed: num 4 4 7 7 8 9 10 10 10 11 ...
$ dist : num 2 10 4 22 16 10 18 26 34 17 ...
这里没有什么复杂的。我们共有 50 个观测值和两个数值变量。需要注意的一点是 speed 和 dist 都是整数值。这确实引入了一些离散化,但目前这不太重要。
为了了解 dist 和 speed 之间的关系,下面我绘制了停止距离与速度的关系图。这些变量之间存在较强的正相关,表明停止距离随着速度的增加而增加。我还使用 ggplot 的 geom_smooth 函数叠加了最佳拟合回归线。
因此,我们的目标是不使用内置的 lm 函数来估计这条直线的参数。相反,我们将应用矩阵运算来获得回归系数,然后生成应当落在这条直线上的拟合值。

这是一个展示停止距离与速度关系的图。数据来源于 R 的汽车数据集(图片由作者提供)。
简要回顾
值得提醒自己我们实际的目标是什么。为此,我们试图将包含 n 个观测值的响应向量 Y 建模为 m 个预测变量的加权线性组合,加上一个截距项。对于这里的示例数据,我们只有一个预测变量 speed,因此我们可以设定 m = 1。
预测变量和截距项共同形成一个 n × p 设计矩阵,其中 p = m + 1 反映了模型中需要从数据中估计的未知回归系数的数量。估计需要找到以下正规方程的解:

正规方程(图片由作者提供)。
重新排列方程以求解未知系数,我们得到以下解:

估计方程(图片由作者提供)。
其中 b 是一个 p 维向量,包含最佳拟合的回归系数。
在解决这个问题时,我将把估计方程分解成处理不同操作的部分,然后再将它们合并以估计模型参数。实际上,我们需要做三个操作,每个操作返回一个我们在某个时候需要使用的矩阵。
R 中的矩阵运算
在深入研究之前,我将快速回顾一下我们将要使用的矩阵运算。这个例子中只有 三 个重要的操作。第一个是 矩阵乘法。如果你还没有接触过这个,R 中的矩阵乘法是使用 %*% 运算符来执行的。第二个操作涉及求 矩阵的逆,在 R 中使用 solve 函数来完成(不,我们不会手动完成这部分!)。最后一个操作是 t,这是 转置 操作符。
设置
首先,我们需要定义一个 响应向量 并创建 设计矩阵。对于我们的示例,我们将距离建模为速度的函数,因此 dist 是我们的 响应变量。让我们为这些值分配一个名为 y_vec 的向量:
# Assign dependent variable to the response vector
y_vec <- cars$dist
请注意,在这里使用 $ 访问 cars 数据框中的 dist 列。我发现这种方法并不总是方便或整洁,因此你会看到我很快转而使用 with 进行一些计算。
接下来,我们需要使用 model.matrix 函数将 speed 分配给设计矩阵。在这里,我创建了一个名为 x_mat 的变量:
# Create a design matrix with the single independent variable
x_mat <- model.matrix( dist ~ speed, data = cars )
请注意,对 model.matrix 的调用与 lm 函数调用非常相似。显然,这样做有一个很好的理由,但在这里公式只是让 model.matrix 知道 speed 是一个预测变量。让我们快速看一下它产生了什么:
> head(x_mat)
(Intercept) speed
1 1 4
2 1 4
3 1 7
4 1 7
5 1 8
6 1 9
我们可以看到我们有 两 列:一个是我们的 speed 变量,另一个只包含 1。回顾一下,设计矩阵包括一个额外的列以容纳截距项。
现在,我们已经将 y_vec 和 x_mat 加载到我们的工作空间中,我们可以继续进行矩阵运算。
步骤 1
如果我们观察估计方程的右手边,第一项是设计矩阵的逆与其自身的乘积。在步骤 2 中,我们将处理逆,因此我们首先需要计算 XᵀX。
对于我们的简单线性回归模型,XᵀX 将是一个 2 × 2 的方阵。出于实际目的,我将简单地将其表示为矩阵 A。使用矩阵乘法 %*% 和转置 t 运算符来计算这个矩阵,我将将输出赋值给一个叫做 A 的对象:
# Compute cross-product of the design matrix
A <- t( x_mat ) %*% x_mat
现在,在我之前的帖子中,我们通过矩阵运算了解了这个矩阵中包含的元素的一些信息。我不会在这里重新讨论具体细节,只是将结果如下记录:

设计矩阵的交叉乘积(作者提供的图片)。
我们可以看到 A 中的每个元素都很容易解释,确实也容易计算。注意 a₁₁ 只是观察总数 n,对于 cars 数据集来说是 50。次对角线上的元素 a₁₂ 和 a₂₁ 只是 speed 的总和,而 a₂₂ 是 speed 的*方和。
我们可以做一些快速检查,以验证我们的对象 A 确实包含这些值。让我们首先打印 A 来看看我们得到了什么:
> A
(Intercept) speed
(Intercept) 50 770
speed 770 13228
A[1,1] 的值与 cars 中的总观察数匹配,因此我们有了一个良好的开端。接下来,我们可以通过简单地对 speed: 进行求和来检查 A[1,2] 和 A[2,1] 的值。
> sum( cars$speed )
[1] 770
另一个匹配——一切看起来都很好!最后,我们可以通过计算 speed 的*方并对这些值进行求和来检查 A[2,2] 的值。
> sum( cars$speed ^ 2 )
[1] 13228
这也是正确的!
第 2 步
下一步是找到矩阵 XᵀX 的逆。在 R 中,我们使用 solve 函数对 A 执行此操作。让我们先执行这个操作并将输出分配给一个名为 A_inv 的对象:
# Inverting the matrix A
A_inv <- solve( A )
现在,在查看 A_inv 之前,让我们先检查 XᵀX 的逆矩阵的解:

设计矩阵交叉乘积的逆矩阵(图片由作者提供)。
如果现在忽略第一个项,那么我们看到的是原矩阵 A 中的一些元素已经被交换,而其他元素稍微发生了变化。具体来说,元素 a₁₂ 和 a₂₁ 的符号被反转,现在反映了 speed 的负总和。此外,原矩阵中 a₁₁ 和 a₂₂ 的值交换了位置,因此现在 a₂₂ 是观察总数,而 a₁₁ 是 speed 的*方和。
让我们编译一个包含这些调整后元素的矩阵,并将其分配给一个名为 A_rev 的对象,然后检查一切是否正常:
# Shifting the elements in A
n_obs <- nrow( cars )
A_rev <- with(cars, matrix( c(sum( speed ^ 2), -sum( speed ), -sum( speed ), n_obs), nrow = 2))
> A_rev
[,1] [,2]
[1,] 13228 -770
[2,] -770 50
一切看起来都很好!我们现在可以将 A_rev 乘以第一个项,即一个标量常数,它等于 n 的倒数乘以 speed 的*方偏差和。让我们通过创建一个名为 c 的变量来快速计算常数项:
# Computing the scalar constant for matrix inversion
c <- (n_obs * with( cars, sum( (speed - mean(speed)) ^ 2) )) ^ -1
我不会打印出来,因为这是一个极小的值。现在剩下的就是将 c 和 A_rev 相乘,看看我们得到什么:
# Computation of the matrix inverse
> c * A_rev
[,1] [,2]
[1,] 0.19310949 -0.011240876
[2,] -0.01124088 0.000729927
现在让我们将这些值与使用 solve 得到的解进行比较,通过将 A_inv 打印到控制台:
# Print output from solve() function
> A_inv
(Intercept) speed
(Intercept) 0.19310949 -0.011240876
speed -0.01124088 0.000729927
太棒了——A_inv 中的每个元素都与 A_rev 中的对应元素匹配。
在进入下一步之前,我们可以做一个额外的检查。如果出于某种原因我们怀疑 solve 的输出有误,我们可以依赖于以下事实:任何 n × n 的方阵 A 被认为是可逆的,当且仅当存在另一个方阵 n × n 的矩阵 B,使其结果为以下等式:

矩阵可逆性的条件(图片由作者提供)。
这意味着,如果A_inv确实是A的乘法逆矩阵,那么如果我们将A和A_inv相乘,我们应该得到单位矩阵:
> round( A %*% A_inv )
(Intercept) speed
(Intercept) 1 0
speed 0 1
看起来solve函数在做合理的事情。
在继续之前,有一点需要说明:这些对矩阵输出的检查对于任何实际目的并不必要。相反,我希望通过包含这些小测试进一步增强你对这些概念的理解,实际上将数学应用到实践中。
第三步
现在我们已经处理了XᵀX的逆矩阵,我们可以将注意力转向估计方程中的最后一项:XᵀY。这个操作涉及将设计矩阵与响应向量相乘。鉴于我们处理的数据,这个计算将产生一个p × 1向量,我们将其简单地表示为B。同样,我们将应用矩阵乘法运算符,并将输出赋值给一个变量,这次我们称这个变量为B:。
# Matrix multiplying the design matrix and response vector
B <- t( x_mat ) %*% y_vec
如上所述,让我们考虑一下这个操作的预期输出是什么:

设计矩阵与响应向量的叉乘(图片作者提供)。
再次,我们有一些相当容易直观理解的元素。其中一个是响应变量dist的总和,另一个是speed和dist的乘积总和。让我们快速计算这些值,看看它们是否与已存储在B:中的值匹配:
# Compute vector and compare with B
> with( cars, matrix( c( sum( dist ), sum( speed * dist) ) , ncol = 1 ))
[,1]
[1,] 2149
[2,] 38482
>
> B
[,1]
(Intercept) 2149
speed 38482
我们匹配了!
参数估计
好的。我们现在拥有了估计模型系数所需的一切。实际上,我们只需要A_inv和B,然后只需将它们相乘即可:
# Estimate the parameter values using solution to normal equation
b_vec <- A_inv %*% B
让我们将其打印到控制台,看看我们得到什么:
> b_vec
[,1]
(Intercept) -17.579095
speed 3.932409
快速查看斜率参数,我们得到了一个正值。这是一个好兆头,因为之前观察到的正相关,且希望这意味着我们在正确的轨道上。现在让我们看看这些估计值与使用lm函数返回的系数相比如何:
> coef( lm( dist ~ speed, data = cars ) )
(Intercept) speed
-17.579095 3.932409
一致的!
我想指出最后一件事。我在之前的帖子中展示了,如果你对估计方程做一些代数运算,你会得到一个非常方便的解。也就是说,斜率参数等于:

斜率参数(图片作者提供)。
并且截距参数等于:

截距参数(图片作者提供)。
这两者计算起来非常直接,可以在 R 中如下进行:
# The slope parameter
b_1 <- with(cars, cov(speed, dist) / var(speed) )
# The intercept parameter
b_0 <- with(cars, mean(dist) - mean(speed) * b_1 )
将这些对象打印到控制台显示,我们得到相同的参数值:
> c( "intercept" = b_0, "slope" = b_1 )
intercept slope
-17.579095 3.932409
解释系数
看我们的估计值,我们可以看到有一个负的截距项,这并不太合理。这里的截距是dist在speed设为零时的值,这意味着当车辆的速度为零时——也就是说,静止不动——它的负制动距离。我不会在这里解决这个问题,所以现在我们就这样接受它(尽管这并不合理)。
另一方面,斜率参数肯定更有用。这表明每增加一个单位的速度(以每小时英里为单位),制动距离增加接* 4 英尺。因此,如果车辆的速度从 10 英里每小时增加到 11 英里每小时,则制动距离从 21.7 英尺跳跃到 25.7 英尺。然而,如果汽车的速度从 60 英里每小时增加到 61 英里每小时,制动距离的增加也应该是相同的,这可能不太合理。
计算拟合值
我们需要做的最后一件事是计算拟合值。为此,我们只需取出b_vec中的参数估计值,并将其与设计矩阵相乘:
# Compute the fitted values
y_hat <- x_mat %*% b_vec
# Compute the residuals
e_vec <- y_hat - y_vec
我在上面的代码中也计算了误差向量,但这仅仅是为了向你展示如何计算。我们不会在这里处理这些值。
我们能做的最后一件事是将我们之前生成的图上的拟合值进行叠加。如果一切顺利,我们应该能看到拟合值很好地沿回归线分布:

在之前生成的图上叠加拟合值(图片作者提供)。
确实如此。
总结
这里的重点主要放在了参数估计上,因此有很多内容我没有涉及。例如,模型诊断、计算参数估计的标准误差以及像决定系数R²这样的模型拟合指标。由于这篇文章旨在作为我早期文章的补充,我希望避免在这里引入新概念,但我会在另一时间讨论这些概念。尽管如此,我希望这篇文章在你进行线性代数学习的过程中能有所帮助。如果你想查看本文使用的代码,可以在我的Git页面找到。
相关文章
-
线性回归的矩阵代数
感谢阅读!
如果你喜欢这篇文章并希望保持更新,请考虑关注我在 Medium 上的账号。这将确保你不会错过任何新内容。
要获取对所有内容的无限访问权限,请考虑订阅 Medium。
你还可以在 Twitter、LinkedIn 上关注我,或者查看我的 GitHub,如果你更喜欢这样的话。
从物理学看 logistic 分类的意义
原文:
towardsdatascience.com/the-meaning-behind-logistic-classification-from-physics-291774fda579
在分类问题中,我们为什么使用 logistic 和 softmax 函数?热物理学也许有答案。
·发表于 Towards Data Science ·阅读时间 9 分钟·2023 年 1 月 24 日
--

Logistic 回归无处不在,但它为什么有效的直觉是什么?(图片由作者提供)
Logistic 回归也许是最受欢迎和最知名的机器学习模型。它解决了二分类问题——预测数据点是否属于某个类别。
关键成分是 logistic 函数,其中一个输入被转换为概率,形式如下:

但是为什么指数函数会出现?它背后的直觉是什么,除了将一个实数转换为概率之外?
结果发现,热物理学有答案。但在深入物理学的见解之前,让我们首先了解数学部分。
数学难题
在处理 logistic 函数的“为什么”之前,让我们首先理解它的属性。

该是让数学见鬼去吧的时候了!(图片由 Michal Matlon 提供,来源于 Unsplash)
理解 logistic 函数的一种更广泛的形式是有帮助的——这种形式适用于多个类别——这样 logistic 函数就成为了一个特殊的两类示例。
这可以通过引入多个额外变量 zᵢ 来完成,每个类别一个。然后我们得到 softmax 函数,其中每个类别 i 被分配一个概率:

对于线性模型,zᵢ 通常是特征的线性和。这个函数在超出线性模型的情况下还有很多额外的用途:
-
作为神经网络中的最终分类层
-
作为变压器中的注意力权重
-
作为强化学习中选择行动的采样层
但为什么我们选择这种函数形式来参数化概率?常见的解释是它仅仅是一个转换工具。事实上,只要其中没有一个概率为零,任何概率集合都可以写成这种形式。从数学上讲,我们可以通过简单地取对数来求解 zᵢ:

但这就是 softmax 作为转换工具的唯一特殊属性吗?其实不是,因为将数字转换为概率的选择是无穷无尽的。实际上,我们可以考虑其他形式的函数:

为了产生一个合理的优化概率函数,我们需要对 f 满足以下标准:
- 始终为正,始终递增,范围从 0 到 ∞,并且可微分
因此,exp(z) 只是无数选择中的一种,下面可以看到:

exp(z) 的替代方案也可以合理地将数字转换为概率(图像来源:作者)
那么 exp(z) 是否只是海量可能性中的一个约定?exp(z) 是否应该更可取?虽然我们可以通过模型性能简单地证明这个选择,但我们应尽可能追求理解。在这种情况下,存在理论动机,它来自于热物理学。
一个热物理学链接
事实证明,这些概率在热物理学中无处不在,它们被称为 玻尔兹曼分布,它描述了一个粒子(或更广泛地说,一个系统)处于特定能量状态的概率:

其中 Eᵢ 表示状态的能量,T 表示温度。当温度非零时,可以将能量测量为温度的倍数,以便将 T 方便地设置为 1。
尽管玻尔兹曼分布有点令人困惑,因为它们建模的系统是由精确和确定性的方程支配的,那么概率和统计学是如何融入其中的(忽略量子物理学)?

热物理学利用概率来管理我们在复杂世界中的无知(照片来源:Rainer Gelhot 在 Unsplash)
关键在于复杂性。虽然我们有描述系统细节的方程,它们往往非常复杂。此外,这些方程常常表现出混沌行为,导致高度不可预测性(蝴蝶效应)。因此,实际上,这些详细的确定性方程并不是很有用。
那么我们如何理解这些复杂系统呢?幸运的是,在现实生活中,我们很少需要了解系统的微观细节,因为我们无论如何都无法测量它们。通常,我们只需考虑宏观和涌现量(如温度、能量或熵)。这就是概率理论的作用——它是微观与宏观之间的桥梁:
-
微观细节使用概率分布来建模
-
宏观量被建模为这些分布的各种*均值
以一个类比来说,假设我们想研究某些无理数的所有数字,比如√2、π和e。
-
√2 = 1.41421356237309504880…
-
π = 3.14159265358979323846…
-
e = 2.71828182845904523536…
任务似乎很艰巨,因为每个数字来自不同的数学概念。要获得精确的数字,需要特定的数值方法来计算。然而,如果我们简单地观察这些数字的宏观行为,我们会发现每个数字大约出现 10%的时间。关键在于,这些数字像许多动态系统一样,倾向于无偏见地探索所有可能性。从更技术的角度来说,
混沌系统倾向于最大化所有可能性,换句话说,就是熵。
这和我们的玻尔兹曼分布有什么关系呢?数学上,玻尔兹曼分布是一个最大熵分布,在约束条件下,这些统计数据符合一个关键的物理法则——能量守恒。换句话说,玻尔兹曼分布是以下方程的解:

所以,exp(−E)的具体形式来源于能量守恒。
直观地说,我们可以把能量看作一种预算。混沌系统尝试探索所有可能性(以最大化熵)。如果某个类别需要的能量很高,它将较少探索该类别,从而有更多机会探索其他类别。然而,概率不会降为零,因为系统仍然希望探索这个能量低效的类别。指数形式是效率和探索之间的妥协结果。
回到数据科学和分类问题,我们的数据不是动态系统的一部分,也没有能量守恒,所以这些物理学见解如何有用呢?
类别的能量

能量是所有互动的基本单位,但它在分类问题中有用吗?(图片来源:Fré Sonneveld 在 Unsplash)
关键在于,在数据科学中,我们不一定对数据是如何得来的感兴趣——这是一个几乎不可能完成的任务。相反,我们正在尝试构建一个可以模仿数据中相关行为的系统。从这个角度来看,模型成为一个由数据以某种可取方式塑造的动态系统(这一点在我的文章中探讨过)。
就像在热物理学中,玻尔兹曼分布在捕捉粗略的微观细节方面很有效,同时忠实地再现宏观物理量(温度、压力等)。因此,至少可以认为它可能将这种超能力赋予数据科学。
确实,与其在我们的数据中寻找某种能量守恒定律,我们不如在我们的分类模型中强制能量守恒的概念。这样,与热物理学中的玻尔兹曼分布类似:
模型中的 softmax 函数假设类别的最大熵猜测,前提是对这些猜测存在某种保守预算。
最大熵可以被视为一种最大似然估计(参见我的关于熵的文章)。剩下的问题是,为什么创建一个人为固定的能量预算是合理的?以下是一些原因:
-
中心极限定理:能量通常是特征的线性和,因此它们有明确的均值。所以,强制这些能量在类别间的*均值保持恒定并不是一个很大的跳跃。
-
正则化:这迫使概率的极值受到限制,因为概率为 1 或 0 将要求一些能量为无限大。
-
方差减少:通过施加合理的约束,我们在模型中引入偏差的同时减少方差(偏差/方差权衡)。
在深度神经网络中使用 softmax 时,点 1 和 3 尤其显著。由于网络层通常已经有某种规范化,因此强制固定能量以确保下游良好的统计行为更加有意义。
那么,这些见解如何帮助我们理解我们的模型呢?我们可以用它们来解释有关使用 softmax 的模型的轶事事实(即逻辑回归):
-
不*衡的问题:这是因为 softmax 假设最大熵。它故意试图让模型在没有偏见的情况下探索所有可能的类别。
-
大量类别的问题:softmax 尝试分配所有可能的类别,即使是那些不可能的类别(例如,将猫分配为车辆)。对于类别被干净地分开的数据,聚类、最*邻、支持向量机和随机森林模型可能表现更好。
除了模型结构之外,我们的热物理类比还帮助我们理解训练范式,这也是我们接下来要讨论的内容。
施加训练压力

Boltzmann 分布允许我们计算类似压力的东西,在分类中有什么类比?(照片由NOAA提供,来源于Unsplash)
Boltzmann 分布的实用性不仅仅在于对系统状态进行分类:它使我们能够计算其他有用的突现量——例如压力。这个压力可以是我们所经历的物理压力,也可以是更抽象的热力学力,如磁场和化学势。
例如,压力的方程是通过体积变化引起的 Boltzmann 因子的变化来定义的:

更一般地说,热力学压力被定义为我们统计分布中相对于某个变量的变化。
回到数据科学中,分类中的一些量类似于“压力”是什么呢?一个相关的量是交叉熵,这是训练分类模型时通常要最小化的损失函数。交叉熵通常通过采样来估计:

其中 l 表示特定数据点所在的正确类别。为了优化它,我们可以进行梯度下降:计算导数并更新,直到导数为零。
使用物理类比,我们可以将导数/梯度视为一种热力学压力!

那么对于模型训练来说,这意味着我们在对系统(我们的模型)施加压力,直到它达到*衡。当这种内部“模型压力”达到零时,模型就被训练完成——某种热*衡。
虽然这种类比并不是 100% 精确,但它给了我们关于分类模型如何工作的直观认识(更具体地说,利用 softmax 并通过诸如交叉熵的东西进行优化的模型)。所以我们可以得出结论:
分类模型是模拟热力学系统的模型,它们被驱动到*衡状态,以模拟我们数据中的类别。
这一观点或许可以解释为什么简单的逻辑回归模型是有效的,即使在现实生活中,关于线性模型的假设通常会被违反。
结论
希望我已经向你展示了一些简单逻辑回归与热力学之间的有趣联系。
数据科学是一个非常广泛的学科。像热力学一样,我们可以将数据科学视为一种高级的理解宏观世界的方式,同时妥善处理我们可能不知晓的微观细节。或许并不奇怪,数据科学中的许多概念和数学工具可以与物理学联系起来,甚至可以追溯到物理学,因为它们都有着建模我们世界的共同目标。
如果你喜欢这篇文章,请留下评论。如果你希望看到其他抽象概念被解密并得到正确解释,也请告诉我。祝阅读愉快👋。
如果你喜欢我的文章,你可能对我的其他见解也感兴趣:
熵可以被认为是看到数据中某些模式的概率。以下是它是如何工作的。
towardsdatascience.com ## 物理学家的机器学习视角:机器学习的热力学
自然界中的复杂系统可以通过热力学成功研究。那么,机器学习呢?
towardsdatascience.com [## 熵不是混乱:物理学家的视角
熵通常被视为混乱的同义词。但它真正是什么呢?在这篇文章中,我们探讨了熵如何更…
medium.com](https://medium.com/swlh/entropy-is-not-disorder-a-physicists-perspective-c0dccfea67f1?source=post_page-----291774fda579--------------------------------) [## 为什么我们不生活在模拟中
将现实描述为一种模拟严重低估了我们世界的复杂性。以下是为什么这种模拟…
数据素养的威力
原文:
towardsdatascience.com/the-might-of-data-literacy-3d91fcc5f46b
这是否是成功使用数据和分析的关键?
·发布于 Towards Data Science ·阅读时间 8 分钟·2023 年 8 月 23 日
--

图片由 Markus Spiske 提供,来源于 Unsplash
数据在当今世界是业务成功的关键驱动力。 然而,许多公司仍然难以充分利用其潜力。在我最*的一篇文章中,我深入探讨了多个原因 [1]。例如‘数据沼泽’问题。这是组织理想中希望避免的数据基础设施场景。但是,如果这如此令人不快,为什么无数公司会陷入其中?
你怎么能判断自己不再拥有数据湖,而是陷入泥潭?你可以问三个问题:
1. 结构: 获取所需数据的路径是否简单:是/否
2. 直观性: 是否容易找到那条路径:是/否
3. 一致性: 不同数据类型的路径是否看起来相同:是/否 [2]。
如果所有前面的问题的答案都是‘不’,那么你可以说:

图片由 Dmitrii Ko 提供,来源于 Unsplash
好的,所以我们已经识别出架构中的问题。我会咨询我的数据分析团队来解决这个问题。
等一下,这不是正确的方法!
问题不仅仅是数据架构功能失调。真正的问题在于无法从数据中提取价值。技术层面只是跟随其后。当数据未被使用时,它实际上没有价值。数据的价值的准确衡量标准在于从数据驱动的决策中产生的营业额或利润 [3]。
问题是否在于缺乏明显的数据战略?可能是。然而,虽然强健的数据战略至关重要,但如果公司的数据文化薄弱,它也无法发挥作用。员工必须在日常工作中采纳数据的使用。他们必须具备数据素养,以充分利用数据驱动的机会。
什么是数据素养?
数据素养包括读取、理解、分析和传达数据。培养数据素养对采纳它的个人和优先考虑它的企业都带来了好处。 在个人方面,它使我们能够更好地与数据和分析进行批判性互动。它还给我们(希望)带来了在当今就业市场上的竞争优势。对于企业来说,数据素养的好处也很明显,特别是在利用数据、理解客户、抓住机会和应对挑战方面。

数据素养的各个方面。来源:作者提供的图片。
成为数据素养者意味着什么?
根据 Brent Dykes 的说法 [4],数据素养有两个不同的方面:数据分析和数据解读。虽然它们通常被视为一个过程,但区分这两者是至关重要的。数据分析 是技术性的,包括数据清理、异常检测、趋势分析和识别关联等任务。另一方面,数据解读 则深入探讨这些发现的意义。它提出的问题包括:
观察到的趋势是什么驱动的?我们的组织如何利用特定的关联?检测到的异常是一个罕见的现象、一个未被发现的机会,还是在某些情况下,观察到的关联可能表明因果关系?
顺便提一下:对于上述最后一个问题得到“是”的答案通常被认为是业务分析的圣杯。

寻找圣杯。来源
不久前,我查看了最新的‘BARC 数据文化调查’。该调查显示,2022 年只有 32%的参与者完全依赖数据做决策。18%的人依靠经验或直觉做决定。其余的 51%则是两者结合使用 [5]。这让我思考:这个最后的数字是否是一个不好的现象?
一方面,确实是这样。尽管数据科学取得了进展,包括大模型(LLMs)的应用,但这种“人情味”仍然是必需的。只有人类才能理解其他人:他们的情绪、口味和感受。无论多么复杂,计算机程序在这方面仍然不如我们。 另一方面,过度使用人为因素有时可能会带来危害。一切都取决于使用了多少种成分(即数据或直觉),以及它们用于何种决策。我发现,决策越大,人们越倾向于依赖他们相信的或之前见过的东西。而这也许在当今并不是正确的方向。
为什么将数据分析与数据解读分开是有意义的?
嗯,这又是另一个“人类特质”。最大的挑战之一是切换详细视图与“高空”视图(以及反方向)。你不能期望一个已经负担过重的经理去花几个小时或几天时间处理给定的数据集。另一方面,数据科学家可能缺乏必要的背景知识,如战略变化、对公司环境的理解,以及已作出但尚未公布的关键决策。
鉴于他们繁多的职责,经理们常常依赖数据专业人员进行详细分析。这些专家在广泛的数据探索后提供宝贵的见解。然而,领导者必须参与数据解读,而不是被动接受结论。
经理的参与确保数据解读与更广泛的组织背景保持一致。
当领导者基于数据专业人员的基础见解做决策时,集体推理会导致更全面的决策。
提高组织中数据素养的目标不是将每个员工培养成数据科学家,而是培养具备数据敏感性的个人。重点是让大多数团队成员掌握基本的数据技能,以便他们在日常任务中使用,而不是处理复杂的数据驱动角色。
拥有数据素养意味着有信心在不具备高级统计或编程知识的情况下管理日常数据。
这种基础能力能够释放你组织的数据潜力。为了确定你的团队的数据素养,应该在数据层级上定位“最低可行能力”,而不是列出所有可能的技能。Brent Dykes 建议我们可以将所需技能拆解成一个 3x3 矩阵,其中列出数据素养的水*(纵轴)以及每个水*所需的(最低)技能(横轴)[6]。

数据素养矩阵。表格由作者基于[6]制作。
在阅读类别中,数据层次涉及基本的数字能力和特定领域的度量理解。信息层次增加了对视觉图表(图形)的理解能力和基础统计知识,而沟通阶段则强调数据解释和关键评估。
对于工作与,数据层次优先考虑对分析工具和数据处理的熟悉程度。信息层次关注描述性和诊断性分析技能,而洞察层次则强调基于数据洞察的决策制定。
在沟通领域,数据层次技能包括应对请求和讨论数据话题。信息层次突出数据呈现和可视化,而洞察阶段则强调具有影响力的数据讲故事的艺术[6]。
遵循 80/20 原则,我认为洞察力水*对获得数据素养至关重要。虽然其他层次也有帮助,但从决策者的角度来看,它们并不是关键的。
“阅读”领域
首先,组织内的每个人——特别是管理者和领导者——必须培养对数据科学工具和技术的基础理解。这并不意味着每个人都应该参加统计课程或深入学习 Python 或 R。相反,目标是掌握基本术语,了解特定工具的优缺点,并具备进行基础分析的能力。虽然我认识到企业角色中的时间限制,但我发现即使是基础的理解也能提升我与数据科学家的互动,避免了重温基础概念的需要,从而改善了协作。我在自己身上验证了这一点。
“工作与”领域
改变我们处理数据的方式可能会显著改变我们的公司文化。 强有力的领导至关重要。如果领导者对数据不感冒,他们会更信任直觉而非数字。
我相信我们可以在团队中利用“数据冠军”。这些人对数据充满热情,并愿意帮助他人理解数据。他们展示了数据和分析不仅仅是复杂的表格,还有令人兴奋的发现。
我们还需要用户友好的工具,让每个人即使没有技术技能也能探索和理解数据。管理者应使用有效、设计良好的仪表板来快速获得清晰的图景[7]。
然而,也存在风险。随着对有价值数据的炒作,一些公司收集了过多的数据,浪费时间进行分析,并拖慢了决策过程。
此外,如果我们看不到使用数据的快速收益,可能会失去热情。庆祝小成功是至关重要的,特别是当我们刚刚接触数据驱动的方法时。
‘沟通’领域
在这里,“数据讲故事”发挥作用。为什么它至关重要? 讲故事,通过叙述和视觉辅助,帮助理解庞大的数据。引人入胜的讲故事可以使任何故事变得清晰,无论多么复杂。这项技能如此重要,以至于《福布斯》将其列为数据科学家的顶级要求。然而,许多分析师在这方面挣扎。有效的数据故事:
-
使复杂的话题变得易于理解和分享。
-
促成决策变化比个人经历更为重要。
-
将复杂的数据分析结果简化。
引人入胜的讲故事指的是“那么又如何”的声明:核心信息。许多分析师缺乏设计创意或害怕简化。然而,不清晰的视觉效果可能会阻碍业务决策 [8]。
我计划很快写关于我在数据讲故事方面的方法:敬请关注!
请记住,提高数据素养是一场马拉松,而不是短跑。
这与许多人选择的快速解决培训趋势相反。技术的迅猛发展,例如 LLMs 的崛起,突显了持续学习的必要性。优先考虑一致的、每日的自我教育——例如阅读一篇 15 分钟的文章——而不是短期的强化课程。如果可能,将这两种活动类型结合起来。然而,请记住,熟练度,尤其是在讲故事等领域,要求的是实践和经验,而不仅仅是正式培训。这涉及到理解什么有效、引起共鸣并与你的受众以及可能的舒适区相契合。
结论
在今天的数据驱动环境中,组织的所有成员,特别是领导层,必须具备对数据科学工具和方法的基础理解。这并不要求你报名参加统计课程或精通编程语言。相反,它是关于掌握基本术语、识别各种工具的优缺点,并能够进行基本的数据分析。尽管企业界时间有限,但即使是基础知识也能显著提升和简化与数据专业人士的互动。在日常操作中增强我们对数据驱动决策的依赖,加上讲故事的强大应用,处于这一过程的巅峰。到那时,我们才能真正称自己为数据素养的专家。
参考文献:
[1] 斯泽德科,米哈伊,从数字到行动:让数据为公司服务,2023 年 8 月 14 日
[2] 皮尔彻,布赖斯,我的数据湖实际上是数据沼泽吗?,2023 年 3 月 23 日
[3] 克莱门斯·梅瓦尔德,为什么数据不是新石油,以及数据市场为何让我们失望,2023 年 7 月 13 日
[4] 布伦特·戴克斯,提升任何管理者的数据解读技能的 4 个关键,2022 年 6 月 16 日
[5] BARC,BARC 数据文化调查 23 — 如何开放数据访问以赋能数据用户
[6] 布伦特·戴克斯,数据素养与数据讲故事:它们如何结合?,2022 年 5 月 12 日
[7] 米哈尔·苏代科,利用管理仪表板讲故事:一种可行的途径?,2023 年 8 月 3 日
[8] 解码,今天的世界可视化:用数据讲故事,2022 年 7 月 29 日
《极简主义者的 DVC 实验跟踪指南》
原文:
towardsdatascience.com/the-minimalists-guide-to-experiment-tracking-with-dvc-f07e4636bdbb

图片由 Zarak Khan 提供,来源于 pexels.com
开始进行实验跟踪的最低要求指南
·发布于 Towards Data Science ·阅读时间 6 分钟·2023 年 5 月 15 日
--
本文是一个系列文章的第三部分,展示了如何利用 DVC 及其 VS Code 插件进行机器学习实验。在 第一部分 中,我展示了一个机器学习项目的完整设置,并演示了如何在 VS Code 中跟踪和评估实验。在 第二部分 中,我展示了如何使用不同类型的图,包括实时图,用于实验评估。
阅读完这些文章后,你可能有兴趣在下一个项目中使用 DVC。然而,你可能会认为设置它需要大量工作,例如定义管道和数据版本控制。也许对于你下一个快速实验来说,这可能有些过于复杂,你决定不尝试。那将是很可惜的!
虽然这些步骤有充分的理由存在——你的项目将完全被跟踪并可重现——我理解有时我们会面临很大的压力,需要快速进行实验和迭代。因此,在本文中,我将向你展示开始使用 DVC 跟踪实验所需的最低要求。
从零开始,用几行代码进行实验跟踪
在我们开始编写代码之前,我想提供一些关于我们将使用的示例的背景信息。目标是构建一个模型,以识别欺诈信用卡交易。这个数据集(可以在 Kaggle 上找到)被认为高度不*衡,只有 0.17% 的观察值属于正类。
正如我在引言中承诺的那样,我们将涵盖一个基本的场景,在这个场景中,你几乎可以立即开始跟踪你的实验。除了某些标准库,我们将使用dvc和dvclive库,以及DVC VS Code 扩展。最后一个不是硬性要求。我们可以从命令行检查跟踪的实验。然而,我更喜欢使用集成到 IDE 中的特殊标签。
让我们从创建一个基本的脚本开始。在这个简短的脚本中,我们加载数据,将其分为训练集和测试集,拟合模型,并评估其在测试集上的表现。你可以在下面的代码片段中看到整个脚本。
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import f1_score, precision_score, recall_score
# set the params
train_params = {
"n_estimators": 10,
"max_depth": 10,
}
# load data
df = pd.read_csv("data/creditcard.csv")
X = df.drop(columns=["Time"]).copy()
y = X.pop("Class")
# train-test split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# fit-predict
model = RandomForestClassifier(random_state=42, **train_params)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
# evaluate
print("recall", recall_score(y_test, y_pred))
print("precision", precision_score(y_test, y_pred))
print("f1_score", f1_score(y_test, y_pred))
运行脚本将返回以下输出:
recall 0.7755102040816326
precision 0.926829268292683
f1_score 0.8444444444444446
我不认为我需要说服你将这些数字写在纸上或电子表格中并不是跟踪实验的最佳方式。这尤其如此,因为我们不仅需要跟踪输出,还需要知道哪些代码和潜在的超参数导致了这个分数。如果不知道这些,我们永远无法重现实验结果。
说到这里,让我们使用 DVC 来实现实验跟踪。首先,我们需要初始化 DVC。我们可以通过在终端中(在项目的根目录下)运行以下代码来完成。
dvc init
git add -A
git commit -m "initialize DVC"
然后,我们需要稍微修改一下我们的代码,使用dvclive。
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import f1_score, precision_score, recall_score
from dvclive import Live
# set the params
train_params = {
"n_estimators": 10,
"max_depth": 10,
}
# load data
df = pd.read_csv("data/creditcard.csv")
X = df.drop(columns=["Time"]).copy()
y = X.pop("Class")
# train-test split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# fit-predict
model = RandomForestClassifier(random_state=42, **train_params)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
# evaluate
with Live(save_dvc_exp=True) as live:
for param_name, param_value in train_params.items():
live.log_param(param_name, param_value)
live.log_metric("recall", recall_score(y_test, y_pred))
live.log_metric("precision", precision_score(y_test, y_pred))
live.log_metric("f1_score", f1_score(y_test, y_pred))
唯一改变的部分是评估。使用Live上下文,我们正在记录模型的参数(存储在train_params字典中)以及我们之前打印的相同分数。我们还可以跟踪其他内容,例如,图表或图像。为了帮助你更快上手,你可以在dvclive的文档中或在 DVC 扩展的设置屏幕上找到很多有用的代码片段。
在查看结果之前,值得一提的是dvclive期望每次运行都由 Git 跟踪。这意味着它将把每次运行保存到相同的路径,并每次覆盖结果。我们指定了save_dvc_exp=True以便自动跟踪为 DVC 实验。在后台,DVC 实验是 DVC 可以识别的 Git 提交,但同时,它们不会使我们的 Git 历史记录杂乱无章或创建额外的分支。
运行修改后的脚本后,我们可以在 DVC 扩展的Experiments面板中检查结果。正如我们所见,分数与我们手动打印到控制台中的分数相匹配。

为了清楚地看到设置跟踪的好处,我们可以快速运行另一个实验。例如,假设我们认为应该将max_depth超参数减少到 5。为此,我们只需在train_params字典中更改超参数的值,并再次运行脚本。然后,我们可以立即在汇总表中看到新实验的结果。此外,我们可以查看哪种超参数组合导致了该得分。

简单明了!自然,我们展示的简化示例可以轻松扩展。例如,我们可以:
-
跟踪图表并比较实验结果,例如它们的 ROC 曲线。
-
添加 DVC 管道以确保我们项目每一步的可重复性(加载数据、处理、拆分等)。
-
使用
params.yaml文件对我们管道中的所有步骤进行参数化,包括 ML 模型的训练。 -
使用 DVC 回调。在我们的例子中,我们手动存储了模型的超参数及其性能信息。对于像 XGBoost、LightGBM 或 Keras 这样的框架,我们可以使用回调自动存储所有这些信息。
总结
在这篇文章中,我们探讨了使用 DVC 的最简单实验跟踪设置。我知道开始一个项目时考虑数据版本控制、可重复管道等可能会让人感到望而生畏。然而,运用本文描述的方法,我们可以以最小的开销开始跟踪实验。虽然对于较大的项目,我仍然强烈建议使用所有确保可重复性的工具,但对于较小的临时实验,这种方法无疑更具吸引力。
一如既往,任何建设性的反馈都非常欢迎。你可以通过 Twitter 或评论区联系我。你可以在 这个仓库 中找到本文使用的所有代码。
喜欢这篇文章?成为 Medium 会员,继续无限制地阅读学习。如果你使用 这个链接 成为会员,你将以零额外成本支持我。提前感谢,期待与你见面!
你可能也会对以下内容感兴趣:
本系列探索 Pandas 隐藏宝石的第三部分
towardsdatascience.com [## 数据科学的十大 VS Code 扩展
提升生产力的必备工具!
medium.com ## 将 VS Code 转变为 ML 实验的一站式*台
如何在不离开 IDE 的情况下运行和评估实验
[towardsdatascience.com
除非另有说明,否则所有图片均为作者提供。
企业 AI 的护城河是 RAG + 精细调整 — 为什么如此重要
要在规模上成功实现生成式人工智能,我们需要给予 LLMs 应有的勤奋。RAG 和精细调整应运而生。
·
关注 发表在Towards Data Science ·10 分钟阅读·2023 年 11 月 13 日
--
Volodymyr Hryshchenko拍摄的照片,来源于Unsplash。
围绕 LLM 的炒作是前所未有的,但也是有充分理由的。从 AI 生成的全副装扮的教皇到没有脉搏的客户支持代理人,生成式 AI 有改变我们所知社会的潜力。
在很多方面,LLM(大型语言模型)将会使数据工程师变得更加重要 — 这是令人兴奋的!
不过,向老板展示一个数据发现工具或文本转 SQL 生成器的酷炫演示一回事,将其与公司的专有数据,甚至更令人担忧的是客户数据一起使用则是另一回事。
太多时候,公司匆忙建立 AI 应用程序,却对其实验的财务和组织影响缺乏远见。而这并不是他们的错 — 高管和董事会对这(以及大多数)新技术的“赶快上路”的心态负有责任。(还记得 NFT 吗?)
要让 AI — 特别是生成式 AI — 取得成功,我们需要退一步,回想一下任何软件如何变得企业就绪。为了达到这一点,我们可以从其他行业获取启示,了解企业就绪的标准,并将这些原则应用到生成式 AI 中。
我认为,企业级生成式 AI 必须:
-
安全与隐私: 您的 AI 应用程序必须确保您的数据安全、私密且符合法规,具备适当的访问控制。想象一下:AI 的安全运营。
-
可扩展性: 您的 AI 应用程序必须易于部署、使用和升级,同时还要具备成本效益。如果一个数据应用程序需要花费数月才能部署,使用起来繁琐,并且升级时会引入无数其他问题,那么您也不会购买或自行开发这样的应用程序。我们不应该对待 AI 应用程序有任何不同。
-
可信赖。 您的 AI 应用程序应该足够可靠和一致。我很难想象有哪位 CTO 愿意赌自己的职业生涯,去购买或开发一个生成不可靠代码或生成的见解混乱且误导的产品。
在这些限制条件下,现在是我们开始认真对待生成式 AI 的时候了。但这并不容易……
为什么企业级 AI 如此难以实现?
简而言之,尚缺乏适合扩展、安全和运作 LLM 应用程序的基础设施。
不同于大多数应用程序,AI 很大程度上是一个黑匣子。我们知道我们输入了什么(原始、通常是非结构化数据),我们也知道我们得到了什么结果,但我们不知道它是如何做到的。这使得扩展、安全和运作都变得困难。
以 GPT-4 为例。虽然 GPT-4 在某些任务(如 SAT 和 AP Calculus AB 考试)表现出色,但其部分输出被幻觉所困扰或缺乏足够的上下文来充分完成这些任务。幻觉的产生是由多种因素引起的,从低质量的嵌入到知识截断,经常影响由公开或开放 LLM 模型生成的响应质量,这些模型是根据从互联网上获取的信息训练的,占大多数。
为了减少幻觉,甚至更重要的是——回答有意义的业务问题——公司需要用自己的专有数据增强 LLMs,包括必要的业务背景。例如,如果客户要求航空公司聊天机器人取消他们的机票,模型需要访问关于客户、其过往交易、取消政策以及可能的其他信息。所有这些信息当前都存在于数据库和数据仓库中。
没有这些背景信息,AI 只能根据公开信息进行推理,通常这些信息是最初训练模型的互联网上发布的。这就产生了一个难题——暴露专有企业数据并将其纳入业务工作流程或客户体验几乎总是需要坚实的安全性、可扩展性和可靠性。
两条通向企业就绪 AI 的路线:RAG 和微调
当涉及使 AI 企业就绪时,最关键的部分出现在 LLM 开发过程的最后阶段:检索增强生成(RAG) 和 微调。
但需要注意的是,RAG 和微调并不是互斥的方法,应根据您的具体需求和用例来利用——通常是并行使用的。
何时使用 RAG

图片由作者提供。
RAG 是一种通过让模型访问数据库来改进 LLM 输出质量的框架,试图在回答提示时提供支持。数据库作为一个经过精心策划和可信任的潜在专有数据集,允许模型将最新和可靠的信息整合到其响应和推理中。这种方法最适合需要额外上下文信息的 AI 应用,例如客户支持响应(例如我们的航班取消示例)或在公司企业通信*台中进行语义搜索。
RAG 应用程序旨在从知识来源中检索相关信息,然后生成响应,因此非常适合查询结构化和非结构化数据源,如向量数据库和特征存储。通过检索信息来提高 LLM 在输出生成时的准确性和可靠性,RAG 在减少幻觉和降低培训成本方面也非常有效。RAG 还为团队提供了一定程度的透明度,因为您知道将数据输入模型以生成新响应的数据源。
关于 RAG 架构的一点需要注意的是,它们的性能在很大程度上依赖于您建立有效数据管道的能力,使企业数据对 AI 模型可用。
何时使用微调

图像由作者提供。
微调是在现有的语言模型上训练一个更小、任务特定且标记数据集的过程,根据这些新数据调整模型参数和嵌入。微调依赖于预先筛选过的数据集,这些数据集不仅仅用于信息检索,还包括所需领域的细微差别和术语。
在我们的经验中,微调最适合特定领域的情况,比如以特定的语调或风格对详细提示作出回应,比如法律简报或客户支持票证。它还非常适合克服信息偏见和其他限制,比如语言重复或不一致之处。几项 研究 在过去的一年中表明,经过微调的模型显著优于 GPT-3 和其他公开可用模型的即用版本。已经确定,对于许多用例来说,经过微调的小型模型可以胜过大型通用模型 —— 这使得在某些情况下,微调成为成本效率的一种可行路径。
与 RAG 不同,微调通常需要更少的数据,但耗费更多的时间和计算资源。此外,微调操作像一个黑盒子;由于模型内化了新数据集,难以准确指出新响应背后的推理过程,并且幻觉仍然是一个值得关注的问题。
和 RAG 架构一样,微调需要建立有效的数据管道,使(标记的!)企业数据可用于微调过程。这绝非易事。
为什么 RAG 可能适合您的团队

图像由作者提供。
重要的是要记住,RAG 和精细调整并非互斥的方法,它们各有优势和劣势,并可以一起使用。然而,对于绝大多数用例来说,RAG 在交付企业生成 AI 应用程序时可能是最合适的选择。
以下是原因:
-
RAG 的安全性和隐私更易管理: 数据库具有内置角色和安全性,而不像 AI 模型那样,由于标准访问控制,非常明确谁看到什么。此外,通过访问专有数据的安全和私密语料库,您可以更好地控制使用哪些数据。通过精细调整,包含在训练集中的任何数据都暴露给应用程序的所有用户,没有明显的方法来管理谁看到什么。在许多实际场景中——特别是涉及客户数据时——失去这种控制是不可接受的。
-
RAG 更具可扩展性: 与精细调整相比,RAG 成本更低,因为后者涉及更新大型模型的所有参数,需要大量计算资源。此外,RAG 不需要标记和制作训练集,这是一个人力密集型过程,可能需要数周甚至数月来完善每个模型。
-
RAG 提供更可信赖的结果。 简单来说,RAG 在动态数据中运作更佳,从最新的策划数据集生成确定性结果。由于精细调整很大程度上像黑匣子,很难准确指出模型生成特定结果的方式,这降低了信任和透明度。通过精细调整,出现幻觉和不准确可能性,因为您依赖模型的权重以损失方式编码业务信息。
依我们的谦逊看法,企业就绪 AI 将主要依赖于 RAG,并涉及对更微妙或特定领域用例的精细调整。对于绝大多数应用程序来说,精细调整将是一种对于小众场景而言很好的可选项,并且在行业能够降低运行大规模 AI 所需成本和资源后,它将更频繁地发挥作用。
无论您使用哪种方法,您的 AI 应用程序开发都将需要通过某些数据存储(如 Snowflake、Databricks、像 Pinecone 这样的独立向量数据库或其他完全不同的东西)向这些模型提供公司数据的管道。归根结底,如果生成式 AI 用于内部流程以从非结构化数据中提取分析和见解——它将被用于……鼓掌……数据管道。
要使 RAG 正常工作,您需要数据的可观察性。
在 2010 年代初期,机器学习被吹捧为一种神奇算法,只要您给予其特征完美的权重,就能随时随地执行奇迹。然而,通常改善 ML 性能的是投资于高质量的特征,特别是数据质量。
同样,为了让企业 AI 正常工作,你需要关注生成模型所依赖的数据的质量和可靠性——这很可能通过 RAG 架构来实现。
由于它依赖于动态的、有时是实时的数据,RAG 需要 数据可观测性 来满足其企业级期望。数据可能因多种原因而中断,例如格式错误的第三方数据、故障的转换代码或失败的 Airflow 作业。数据总是会出现问题。
数据可观测性使团队能够在整个数据生态系统中以规模化的方式监控、警报、分类和解决数据或管道问题。多年来,它一直是现代数据栈中的必备层;随着 RAG 重要性的增加和 AI 的成熟,可观测性将成为 LLM 开发中的关键合作伙伴。
RAG 和企业 AI 能否正常工作的唯一方法是你能信任数据。为此,团队需要一种可扩展的、自动化的方式来确保数据的可靠性,以及一种企业级的方法来快速识别根本原因并解决问题——在这些问题影响到它们服务的 LLM 之前。
那么,事实上的 LLM 技术栈是什么呢?
正如我们所说,AI 工具的基础设施和技术路线图正在开发中,每天都有新的初创公司涌现出来解决各种问题,行业巨头也声称他们在应对这些挑战。涉及到将企业数据整合到 AI 中时,我看到三大主要的“竞赛选手”。
第一匹“竞赛选手”:向量数据库。Pinecone、Weaviate 等正在成为驱动 RAG 架构的必备数据库*台。虽然这些技术展现了很大的潜力,但它们确实需要构建新的一层技术栈,并创建支持它的工作流程,包括从安全性、可扩展性和可靠性方面。
第二匹“竞赛选手”:由第三方 LLM 开发者如 OpenAI 或 Anthropic 构建的模型的托管版本。目前,大多数团队通过这些新兴 AI 领导者提供的 API 来获取生成 AI 的支持,因为使用起来很方便。接入 OpenAI API,几分钟内就能利用前沿模型?我们在其中。如果你需要模型生成代码或解决基于公开信息的常见、非特定提示,这种方法效果很好。如果你想将专有信息整合到这些模型中,你可以使用这些*台提供的内置 微调 或 RAG 功能。
最后,第三匹马:现代数据堆栈。Snowflake 和 Databricks 已经宣布他们正在将向量数据库嵌入其*台以及其他工具,以帮助将已存储和处理在这些*台上的数据整合到 LLM 中。对于许多人来说,这是非常合理的,并允许负责 AI 项目的数据团队利用他们已经使用的工具。当您已经有了基础时,何必重新发明轮子呢?更不用说可以轻松地将传统关系型数据与向量数据结合起来的可能性了……像其他两匹马一样,这种方法也有一些缺点:Snowflake Cortex、Lakehouse AI 和其他 MDS + AI 产品还处于萌芽阶段,需要一些前期投资来将向量搜索和模型训练整合到您现有的工作流程中。如果您想更深入地了解这种方法,请查看 Meltano 在其相关文章中为什么最好的 LLM 堆栈可能就在您眼前的观点。
无论我们选择哪匹马,宝贵的商业问题都不能通过仅基于互联网数据训练的模型来回答。它需要具有公司内部背景的上下文。通过安全、可扩展和可信赖的方式提供这种背景,我们可以实现企业级 AI。
企业 AI 的未来就在您的管道中。
要使 AI 实现其潜力,数据和 AI 团队需要认真对待 LLM 增强,并将安全性、可扩展性和可靠性作为首要考虑因素。无论您的用例是否需要 RAG 或精细调整 — 或两者兼有 — 您都需要确保您的数据堆栈基础设施已经就位,以保持成本低、性能稳定且可靠性高。
数据需要安全和隐私保护;LLM 部署需要可扩展性;而您的结果需要得到信任。通过可观察性保持数据质量的稳定脉搏对这些需求至关重要。
这种从孤立的 X 演示转向企业级 AI 的演变中最好的部分是什么?RAG 使数据工程师在拥有和推动生成 AI 投资的 ROI 方面处于最佳位置。
我已准备好迎接企业级 AI。你准备好了吗?
Lior Gavish 为本文作出了贡献。
与 巴尔在领英上联系 以获取有关数据、AI 和数据信任未来的更多见解。
最强大的气候数据仍然被隐藏
原文:
towardsdatascience.com/the-most-powerful-climate-data-remains-hidden-87cb0a0bf302
·发表于Towards Data Science ·13 min read·2023 年 2 月 20 日
--
支撑全球可持续决策的气候数据和模型往往是专有的,对我们来说是隐藏的。数据市场、数据合同、数据许可和数据定价提供了共享这些重要信息的激励和做法。

由Olga Vilkha拍摄,来源于Unsplash
有几位贡献者和顾问为理解当前气候数据格局并塑造本文提供了巨大的帮助:Ellie Young(Common Action)、Philippe Höij(DFRNT.com)和 Andrew Padilla(Datacequia.com)。
使隐藏的气候数据可共享
向他人学习一直是人类进步的标志。我们在构建新的发明时,实际上是在前人的工作基础上“站在巨人的肩膀上”。因此,当我们看到丰富的公共气候数据,以及这些数据如何在全球范围内共享时,我们实际上是在气候数据巨人的隐喻肩膀上,他们免费分享气候数据以造福广大公众。
然而,简单而不幸的事实是,许多最有价值的气候数据对公众隐藏。那些被组织转换和丰富的数据。精炼的数据以支持更好的投资决策。被整理成专有模型的数据,推动组织做出的全球可持续性和气候决策。那些在组织内部“隐藏”的数据。
现在,“隐藏”这个词可能暗示着不良意图。明确地说,事实并非如此。实际上,“隐藏”的本质是转换、精炼和策划赋予公共数据巨大价值的结果。但这些数据并不可用。它是专有的。它是封闭的。因此,它被隐藏了。
实际问题是,每个组织几乎都在进行相同的工作,以处理、丰富和精炼这些公共数据,使其用于指导企业的可持续性和气候决策。但这些精炼的数据及其相关模型很少甚至从未被共享。
在这篇文章中,我将探讨这种不幸情况的根本原因。但或许更重要的是,我还将提供几种潜在的解决方案,以便更容易分享数据,揭示幕后情况,看到之前隐藏的内容。
气候数据是应对气候变化的基石
气候变化是我们时代最紧迫的问题之一,而气候数据的可用性对于理解问题、识别实际解决方案以及确定政策变更的实际影响至关重要。意识到这一点,政府和私人组织都会收集并发布大量的降水、风速和温度数据,以衡量和了解它们对气候变化的暴露。
这些数据至关重要——它用于提供政策决策、创建预测模型以及评估气候变化对经济各个部门的风险。

图 1,大多数气候数据是未经精炼的原始格式
从气候数据的消防栓中饮水
但今天,我们面临着大量免费的公共气候数据。像 NASA、NOAA 以及许多欧洲和国际组织都发布了大量有关温度、降水和各种传感器数据的精细数据。这些数据已经发布了几十年,并且每天仍在发布。比喻来说,我们正从气候数据的消防栓中饮水!
然而,大多数数据仍然是以原始格式存在,包含数值测量或文本。这些数据有很多不同的格式和结构,存在一致性和可用性方面的差异,使得非专家和专家都难以理解或使用。
应用气候数据——转换、精炼和策划
与此相比,“应用”的气候数据已经被转换、精炼和策划,以提取有意义的见解和模式。这通常涉及将多个原始的公共气候数据集结合起来,将它们与其他公共和私有数据关联,清理和组织数据,然后应用统计或机器学习技术来提取有意义的信息。
以保险公司为例。保险公司希望确保被保险资产的价格反映未来的气候风险。例如,他们可能会使用公开的天气、地形(洪泛区、河流)和人口数据来创建算法和模型,更好地理解洪水发生的概率。这些模型帮助决定是否为靠*洪泛区的财产提供保险。
资产管理者,例如,可能会使用各种排放、能源效率和政府数据,包括公共和私人数据,以了解他们的资产对气候变化的潜在暴露,从而更好地调整其风险和回报目标。
随着更严格的气候法规的出现,大多数组织正在准备报告他们自身的气候足迹。例如,他们可能会汇总来自核心业务、子公司和全球运营单位的排放数据,以向公众和其运营区域的监管机构提供报告。
应用数据是专有数据
然而,很明显,这些公司中的许多都在收集类似的数据并创建类似的算法。这种对气候数据轮子的“再发明”发生的原因有很多。
首先,许多公司不愿意共享他们的数据,因为他们担心竞争对手会利用这些数据获得竞争优势,或者他们的数据会被用来创建与自己直接竞争的新产品或服务。
其次,缺乏法律和监管框架来支持数据共享和数据定价。这使得公司难以建立有关数据共享和数据定价的明确规则和期望,进一步抑制了公司共享数据的意愿。
第三,数据可能缺乏来源和溯源,从而可能产生不可信的印象。
最后,也许最具挑战性的情况是,数据共享给数据发布者带来了实质性的潜在长期风险和义务,尤其是当第三方以意想不到的方式使用这些数据时。
简而言之,基于气候数据做出的决策具有长远的时间跨度和长期的后果,尤其是当数据可能出错时;这不是故意的错误,而是可能在长时间内被放大的小错误导致大错误,或者算法被错误调整。任何具有长期尾部的决策,都可能引发巨大的成本。
例如,考虑以下情景:一个资产管理者发布了关于沿海房地产洪水概率的数据。一个房地产开发商使用这些数据,在根据资产管理者的分析,洪水概率相对较低的土地上进行建设。几年后,尽管洪水概率较低,但新建的物业却意外被淹没了。会发生什么?损失和法律诉讼。
使应用数据可用
但如果其他组织可以通过共享他们的私人气候数据来帮助他人更好地预测洪水会怎么样?如果组织共享他们的气候模型来帮助他人做出更明智的气候投资决策会怎么样?如果可以更容易地分享私人气候数据和模型,而没有下游风险呢?

图 2,公开隐藏的气候数据
一些努力正在解决这些挑战。例如,一些倡议(如OS-Climate)正在建立共同的数据标准、协议和*台,以便更容易找到和使用气候数据。联合国以及许多前瞻性的政府正在建立监管指南和框架,鼓励气候数据共享。此外,还建立了数据市场和数据合作社,以促进合作,创建一个公*的数据生态系统,提供透明的数据定价。
还有更多工作需要完成。现今在其他行业中有哪些用例可以教会我们如何共享数据?也许我们可以将“创意共享”许可中学到的经验应用于限制或管理共享气候数据的风险;或者开源软件许可提供了一些新的思路?或者可能有“数据合同”的示例,可以标准化气候数据格式和结构?
从隐喻上说,我们应该站在数据巨人的肩膀上,运用现今最佳实践来增强和赋能未来的气候数据工作。我们已经学到了哪些教训可以应用——现在——使共享气候数据变得更加广泛和安全。

图 3,迈向气候数据生态系统
经验教训:创意共享(可共享的理念——许可)
创意共享组织提供了一套版权许可,使创作者能够分享他们的作品,同时通常允许第三方将创作者的内容用于非商业目的或制作衍生作品。但它也至关重要地让创作者保留对其作品使用方式的一定控制权。
创意共享常用于创意作品,如音乐、艺术、写作和软件。这些许可容易理解且免费使用,使创作者在保护自己权利的同时相对容易地分享他们的作品。

图 4,可共享的理念——创意共享许可
我们可以从非常流行的创意共享(CC)许可方案中学到什么教训?通过使用 CC 许可概念,数据许可应该允许数据创作者指定他人使用数据的条件,比如允许他人将其用于非商业目的、制作衍生作品或与他人分享。
例如,创建了数据集的数据科学家可以使用 CC 许可允许他人利用数据进行进一步分析或研究;政府机构可以使用 CC 许可允许他人利用其数据创建新产品或服务;公司可以使用 CC 许可允许他人利用其数据创造对社会有益的新创新。
经验教训:开源软件许可证(可共享软件)
软件许可是一种保护软件创作者免受意外使用和发布软件的意外后果风险的方法,通过明确定义可以使用的条款和条件。通过创建软件许可证,创作者可以明确指定软件的使用方式、使用者和目的。这有助于防止软件被用于创作者未预期的方式,或者可能对创作者或他人造成伤害的方式。

图 5,可共享软件 — 开源软件许可
以下是几种保护软件创作者的开源软件许可技术:
-
使用:许可证可以包括对软件使用方式的限制,例如禁止将软件用于商业目的。
-
归属:许可证可以要求对软件的任何使用都必须归属创作者,这有助于保护创作者的知识产权。
-
责任:许可证可以包括规定,要求用户对因使用软件而可能造成的任何损害负责,这有助于保护创作者免受法律诉讼。
-
审计:许可证可能包括允许创作者监控和跟踪软件使用情况的条款,这有助于确保遵守许可证条款。
今天有几种流行的开源许可证,例如 GNU 通用公共许可证(GPL)、MIT 许可证和 Apache 许可证,每种许可证都有类似但不同的条款和条件。
那么,我们如何将开源软件许可证中的概念应用于气候数据共享呢?通过调整基于开源软件许可证的共享方案模型,气候数据创作者可以明确:
-
使用:气候数据发布者可以限制使用或根据需要进行灵活调整;例如,原始气候数据可能会被自由和开放地共享,没有限制。但复杂的模型和算法可能需要严格的使用条款。
-
归属:气候数据发布者可能会出于多个原因要求对其数据进行归属:这可能会为发布者建立声誉,从而带来商业机会,或者它可能作为促进正式数据来源和血统的工具。
-
责任:气候数据发布者可能要求包括保护条款,以防第三方使用或滥用。这将使数据发布者能够减轻由于数据不准确、错误或缺乏精确性所带来的潜在风险。
-
审计:气候数据发布者,特别是当其数据被用于商业用途时,可能要求数据使用者验证使用量,以确保适当的计费。
经验教训:OpenAPI 规范(可共享合同)
数据合同是一种定义数据共享、使用和保护条款和条件的协议。在许多情况下,它规定了数据提供者、数据接收者以及任何中介的权利和责任。但数据合同也提供了数据交换格式和结构的定义。
发布数据交换的格式和结构具有多个好处,包括:
-
互操作性:标准化的格式和结构使得不同的系统和应用能够轻松交换信息。
-
重用性:数据可以被许多系统和应用重复使用,从而减少数据重复(并且提高效率)。
-
可访问性:发布数据交换的格式和结构使得理解和共享变得更加容易。
-
创新:清晰的数据格式和结构能够促进创新,使开发者能够在现有数据基础上轻松构建新的应用和服务。

图 6,可共享合同 — OpenAPI 规范
那么,我们如何使用基于 OpenAPI 规范(OAS)建模的合同来定义数据如何交换呢?数据创建者可以使用类似 OAS 的数据交换规范来获得几个好处:
-
数据规范文档:与 OAS 类似,数据创建者可以创建规范,允许开发者自动生成他们 API 的人类可读文档,这使得其他开发者更容易理解和使用 API。
-
数据规范测试:与 OAS 类似,数据创建者可以自动生成数据交换的测试用例,这有助于确保其按预期工作。
-
数据规范客户端生成:与 OAS 类似,数据创建者可以自动生成他们 API 的客户端代码,这使得将数据产品与其他应用进行集成变得更加容易。
-
数据规范访问权限:与 OAS 类似,数据创建者可以嵌入安全模式和范围,定义访问数据产品所需的权限。
经验教训:数据市场(可共享数据)
数据市场是一个*台或网络,使得查找、理解、共享和信任数据变得容易。它通常将数据提供者(发布数据的)与数据消费者(寻找特定类型数据以供使用的)连接起来。数据市场帮助组织快速、轻松地访问高质量的数据,同时也为数据提供者提供了新的发布方式,甚至可能将数据资产货币化。

图 7,可共享数据 — 数据市场
考虑以下场景:假设一个前瞻性的非营利组织(一个没有商业动机的非营利组织)正在建立一个“气候数据市场”,作为气候数据的注册中心。该非营利组织的气候数据市场类似于互联网的 DNS(域名服务)——气候数据市场通过提供气候数据的指针(URL)目录,使搜索和查找所需气候数据变得简单(实际上,它可能还会提供更多内容:直观的用户界面、气候数据类别的策划层级和知识图谱、文档和数据术语表,但这属于另一篇文章的内容)。
数据市场也提供了可以应用于数据共享的教训。数据市场可以解决当前的数据所有权和控制问题,个人和社区对其数据的控制有限,且从使用中获益较少。数据市场提供了实施上述教训的总体结构,包括:
-
用户界面和支持*台,旨在使数据的查找和理解变得简单。
-
许可,旨在通过提供一致、标准和易于理解的数据共享条款和条件,使数据共享变得容易。
-
规范,旨在通过定义访问数据所需的交互机制,使数据集成变得容易。
-
合同,旨在通过提供数据科学家和开发者构建模型和应用所需的元数据定义、信息结构和格式,使数据更易于使用。
结论性思考
本文讨论了为什么许多最有价值的气候数据对公众视野和使用隐藏。我还强调了为什么会这样,同时提出了几种潜在的解决方案,所有这些方案都基于对现有方法的调整,使其更易于分享数据。
我还发现了一个潜在的根本原因,那就是当前气候数据共享实践未能充分保护数据创造者的权利和需求。艺术、软件、创意、数据合同及其他领域提供了丰富的建议,用于保护数据产品的逻辑、内容、使用等方面,并进一步提供了规范数据更新、贡献和改进的协作协议的建议。
我们希望这些想法能够扩展并付诸实践,以便将一些精炼、处理和丰富的数据——以及背后的算法——置于公共领域或以更简单、更负责任的方式共享。我们希望各组织在重新发明、重建和复制数据、算法和模型方面所付出的努力,能够更具建设性和有效性地应对我们的气候危机。
除非另有说明,本文档中的所有图片均由 Eric Broda(本文作者)创建。所有图像中使用的图标都是库存的 PowerPoint 图标和/或不受版权保护的。
本文中表达的意见仅代表作者的观点,并不一定反映任何客户的观点。
多任务优化争议
原文:
towardsdatascience.com/the-multi-task-optimization-controversy-793cbb431d98
我们是否需要特殊的算法来同时训练多个任务的模型?
·发表于Towards Data Science ·阅读时间 6 分钟·2023 年 9 月 29 日
--

由Javier Allegue Barros在Unsplash拍摄的照片
多任务学习范式 —— 即同时训练多个任务的能力 —— 既是一种祝福,也是一种诅咒。
这是一个祝福,因为它使我们能够构建一个单一的模型,而之前我们需要多个模型。这使得生活更简单:减少需要维护、重新训练、调整和监控的模型数量。
这是一个诅咒,因为它开启了一个全新的潘多拉盒子:哪些任务应该一起学习?我们真正需要哪些任务?如果任务之间存在竞争会发生什么?我们如何让模型优先考虑某些任务而不是其他任务?我们如何避免“任务腐蚀”,即任务头的积累最终导致模型性能的下降?
正是这些问题催生了一个新的机器学习子领域,即多任务优化,也就是如何在多个有时相互竞争的任务上优化模型的科学。
标量化
标量化是数学对多任务优化问题的回答。在多任务模型中,我们试图学习 K 个任务,例如在电子商务推荐系统中预测“点击”、“添加到购物车”和“购买”。(实际上,现代推荐系统可能包括十多个任务!)在这样的设置中,我们可以定义解决方案为最小化

…也就是说,任务特定损失的加权和,其中权重大于 0 且总和为 1。
将多任务学习问题重新表述为单一优化问题的技巧被称为 标量化,它借鉴了更广泛的数学优化学科,该学科在诸如 Boyd & Vandenberghe 等教科书中有详细介绍。
在这种问题中的一个重要定义是 帕累托最优性:如果一个解 θ 对所有任务都实现了最低损失,则称之为帕累托最优,即不存在一个 θ 对任何任务具有更低的损失。通常,没有一个单一的解 θ 是帕累托最优的,而是多个解形成了损失空间中的高维曲线——即 帕累托前沿。

(具有 2 个任务的多任务问题中的帕累托前沿。图片来源:Xin et al 2022)
最后,这是在数学优化背景下可以推导出的最重要的结果之一:
从数学上讲,可以证明无论我们选择哪种权重组合,我们总会得到一个恰好位于帕累托前沿的解 θ。在实际应用中,我们需要做的就是遍历可能的权重组合,并选择与业务目标最为一致的那个。
可学习的损失权重
多任务优化的故事本可以通过标量化及其对帕累托最优性的保证来结束。然而,正如机器学习中的常见情况,实践者最终开发出一些在经验上效果出乎意料好的技巧。
其中一个技巧是“可学习的损失权重”(LLW),这是由剑桥大学的研究人员在 2018 年的一篇论文中提出的(Kendall et al 2018),主要应用于计算机视觉领域。其关键思想是自动分配与该数据点预测的模型不确定性成反比的任务特定损失权重。直观上,如果模型错误但不确定,我们应该分配较小的损失,而不是模型错误且确定时的损失。
从数学上讲,这意味着我们最小化

其中 L_i 是任务 i 的损失,σ_i 是任务 i 预测中的不确定性。损失中的最后一项,简单地加上不确定性自身的对数,促使模型做出高确定性的预测,而不是仅仅以小的确定度预测每一种可能的结果。对于多于 2 个任务的情况,我们只需在此方程中添加更多项,每项对应一个任务的损失。
实际上,我们可以通过将预测结果与实际情况进行比较,并拟合一个均值为预测结果自身、标准差为模型不确定性的高斯分布 N 来估计不确定性 σ:

在剑桥论文中,作者将可学习的损失权重应用于一个多任务图像分割问题,其中单个任务包括语义分割、实例分割和深度预测。与均匀权重(1/3, 1/3, 1/3)相比,使用可学习的损失权重(结果为 0.89, 0.01, 0.1)后,得到的分割结果的 IoU 提高了 13%,证明了这种方法的有效性。
梯度操控

(梯度“手术”。图片来源:Yu et al 2020)
多任务学习中的一个问题是来自不同任务的不同梯度可能相互冲突,导致模型同时被拉向多个不同方向。在标量化中,我们通过根据认为最重要的任务来简单地调整损失权重(直接影响梯度的尺度)来解决这个问题。在 LLW 中,我们尝试从数据中学习这些权重,而不是手动调整。但为什么不直接操控梯度,而不是损失呢?
直接梯度操控的这个想法催生了一些研究论文,这些论文声称通过以各种方式缩放或重新对齐梯度来改进多任务学习:
-
在Gradient Surgery中,我们简单地将一个冲突的梯度投影到“锚点”梯度的法向*面上(即,我们试图预测的最重要任务的梯度),
-
在GradNorm中,我们将所有梯度的尺度标准化为该训练迭代中的*均梯度尺度(对所有任务进行*均),
-
在GradSimilarity中,我们仅考虑与锚点梯度有正余弦值的其他任务 i 的梯度,
-
在MetaBalance中,我们将所有梯度的尺度标准化为与锚点梯度的尺度相同(类似于MTAdam),还有更多。
每一篇论文声称比其后继论文更好地解决了多任务学习问题,并且在 2023 年撰写本文时,关于梯度操控的更多论文仍在不断发布。梯度操控是一个迅速增长的研究领域!
争议
所有这些都导致了一个奇怪的结论。从数学上可以证明,我们在多任务问题中需要做的就是应用标量化并在损失权重上进行遍历,以找到最符合业务目标的点——这是(可以证明的!)帕累托最优的。《可学习的损失权重》的作者声称,学习损失权重比手动调整它们更好。这没问题,但他们可能通过精细的参数遍历也会发现相同的结果。
有争议的是,研究者在直接梯度操作算法上的主张,即改变损失权重是不够的:我们需要直接调整梯度以解决训练过程中的冲突。这一主张与标量化理论相矛盾!

梯度操作算法不如标量化(蓝色曲线)。图片来源:Xin et al 2022
那么,谁对呢?在他们的论文《当前深度学习中的多任务优化方法真的有帮助吗?》中,Derrick Xin 等人比较了标量化(结合损失权重的参数扫描)与一些现代梯度操作算法,包括梯度手术和 GradNorm 的预测性能。结果是?它们都不如标量化!
作者解释道:
“研究人员可以通过简单地调整竞争基线,毫无察觉地创造出显著的性能提升错觉。”
这种“错觉”突显了现代机器学习研究中的一个问题:也许发表机器学习研究论文太容易了。也许仅仅通过稍微调整算法并减少基线的调整,就可以轻松证明一种新算法在固定数据集上的有效性。
也许,那么,推进机器学习研究的最佳方法之一是开发更好的理论。
p 值的神话:为什么它们不是数据科学中的圣杯
p 值谬论:我们为什么需要停止把它们当作真理
·发表于Towards Data Science ·15 分钟阅读·2023 年 4 月 27 日
--

图片由Gerd Altmann提供,来自Pixabay
在*期文章中,我提到我们在数据科学中并不总是需要计算p-values,展示了我们在不计算它们的情况下找到了解决 ML 问题的好模型。
现在是时候写一篇澄清这一声明的文章了。我知道:这将是一篇有争议的文章,我非常希望在评论中听到你的意见。
如果你是一个有志成为数据科学家的读者,这里你将找到p-values的定义以及对其使用的有趣讨论。而如果你是专家,我知道你了解p-values的定义,希望你会喜欢对其使用的讨论。
我的观点并不是为了说服任何人:这些讨论在科学界非常严肃,正如你将在文章中看到的。我只是想对p-values这一话题进行一些探讨,建议我们作为数据科学家是否必须使用p-value,或者是否可以跳过它们,使用其他方式测试我们的 ML 模型(我们将看到具体方法)。
**Table of Contents:**
Definingthe p-values
Why we could avoid p-values as Data Scientists
What to use instead of p-values as Data Scientists
An example in Python
定义 p 值
引用自维基百科:
在零假设显著性检验中,
p-value是在假设零假设正确的情况下,获得至少和实际观察结果一样极端的测试结果的概率。
现在,我不认识你,但我的数学学习方式一直是:将其转化为实践。 作为工程师,我喜欢实用数学因为它帮助我更好地理解它。但我必须告诉你真相:我从未从根本上理解过这个定义。
对我来说好消息是……我不是世界上唯一一个这样的人!实际上,我鼓励你阅读这篇(非常简短的)文章 在这里,在那篇文章中我们可以读到:
明确一点,我在 METRICS 谈话过的每个人都能告诉我
p-value的技术定义[…]但几乎没有人能够将其翻译成容易理解的东西。这不是他们的错,METRICS 的共同主任斯蒂文·古德曼说。即使在“整个职业生涯”中一直在思考
p-values,他说他可以告诉我定义,“但我不能告诉你它的意义,几乎没有人能。”
好吧,让我告诉你,当我读到这些话时,我既感到惊讶又感到高兴。惊讶是因为这意味着“科学世界”似乎在处理某种未知的东西。高兴是因为……我不是错的!
如你所知,我们认为 0.05 的p-value是可以接受的。还有另一个问题:为什么是这个数字?乔治·科布,霍利奥克学院数学与统计学名誉教授,在美国统计学会(ASA)讨论论坛上提出了这些问题(这是完整的文章):
问:为什么这么多大学和研究生院教 p = 0.05?
答:因为这仍然是科学界和期刊编辑使用的标准。
问:为什么这么多人仍然使用 p = 0.05?
答:因为这是他们在大学或研究生院时学到的。
这就像是:鸡和蛋哪个先出现?
这种方法的问题在于,p 值不会告诉我们关于结果的任何信息。这导致了ASA 声明:
原则 5:
p-value,或统计显著性,并不衡量效应的大小或结果的重要性。常用的统计显著性阈值是 0.05 的
p-value。这是惯例和随意的。它并不传达效应大小的任何有意义的证据。p-value为 0.01 并不意味着效应大小比p-value为 0.03 时更大。
这是 ASA 在p-values方面提出的六个原则中的第五个。我们来阅读所有这些原则(这是 ASA 的完整文章):
p-values可以指示数据与指定统计模型的不兼容程度。
p-values并不衡量所研究的假设为真的概率,或者数据是仅由随机机会产生的概率。科学结论和商业或政策决策不应仅仅基于
p-value是否超过特定阈值。适当的推断需要完全的报告和透明度。
p-value或统计显著性,并不衡量效应的大小或结果的重要性。
p-value本身并不能提供关于模型或假设的良好证据度量。
因此,根据我们到目前为止阅读的领域专家的观点,我们可以说p-values——尤其是它们的使用——在某种程度上是一个有争议的话题。因此,我将继续讨论为什么在我看来,作为数据科学家我们可以避免使用p-values,除非在某些情况下。然后,我们将用 Python 实现一个示例。
为什么我们作为数据科学家可以避免使用 p 值
我将告诉你一些可能看起来不好的事情,但请让我解释。数据科学是一个非常庞大的领域,许多专业人士在不同的行业中担任数据科学家的角色,他们来自不同的背景。因此,在我看来,根据我的经验,有三种类型的数据科学家:
-
“统计学家”。
-
“软件工程师”。
-
“其他所有人”。
“统计学家”是那些毕业于统计学、数学、物理学或相关领域的人,他们拥有大量的高级统计学知识,并在应用大量高级统计学。例如,研究病毒等生物学领域的人。这些数据科学家通常是研究人员。
“软件工程师”通常是那些学习过计算机工程(或在该领域精通)的人,因为他们喜欢数据,所以转而成为数据科学家。他们特别专注于编程,并学习统计学以成为数据科学家。当然,他们也可以从事研究领域的工作,但我发现他们通常在某些特定领域工作,为业务提供价值(而不是专注于研究)。
“所有其他人”指的是那些通过不同背景成为数据科学家的其他人。这些人拥有丰富的领域知识,并学习编程和统计学,以将数据科学应用于他们的领域。例如,作为一个兼职数据科学家,我毕业于机械工程专业,并学习了统计学和编程,将数据科学应用于我的知识领域(主要是工业领域)。所以,我可以归类为“所有其他人”类别。当然,这些人也可以从事研究,但我发现大多数人从事的是为企业创造价值的工作。
这就是说,我们都知道统计学在数据科学中很重要,但根据我的经验,如果我们在为企业创造价值,我们通常只需掌握基础知识(在某些情况下多了解一点)即可,因为我们不是研究人员。
请不要误解我的意思:我并不是贬低研究的价值与商业的价值。即便我也通过一些合作在某种程度上贡献于研究:所以我可以看到贡献于研究和商业之间的区别。这只是我的观点。
所以,这是一个有争议的观点:p-values 在我们进行高级统计分析时,尤其是使用科学方法时,更具意义。特别是在贡献于研究时。我意思是,科学方法——正如其名称所示——是一种由一些简单步骤组成的方法:
-
确定要解决的问题。
-
提出解决问题的假设。
-
解决实际问题。
很简单,不是吗?其实并非如此:这主要取决于领域和问题。例如,我们都知道科学中存在一些未解的问题或仅部分解决的问题。例如,Navier-Stokes 方程目前只能在一些精确且特定的假设下求解。一般情况目前尚未解决。实际上,准确地说:目前没有人证明 Navier-Stokes 方程的解存在,并且如果存在,它是否唯一(如果我的记忆没有错误的话:如果我错了,请告诉我)。
但关键在于:在科学中,我们总是提出假设。记住我们的代数和分析课程:当我们的教授教我们定理时,他们总是从一个问题开始,展示假设,并在假设的基础上解决问题。
现在,关于 p-values 的问题,如上所述,是(引用):
通常使用的统计显著性阈值是
p-value为 0.05。这个值是传统的,也是任意的。它没有传达任何关于效应大小的有意义的证据。
所以,根据这一声明,我的经验告诉我,这种方法更适用于与研究和高级数学相关的数据科学应用。例如,在生物学、医学、粒子物理学等领域。
在这些领域,事实上,科学方法对于得出结论非常重要,因为我们需要遵循一些标准化的方法,以便我们的工作可以与他人的工作进行比较。通常因为结论被发表在科学论文中,我们需要严格的方法来展示我们的结果。
但让我们说实话:今天的数据科学远不止于研究。这就是为什么我告诉你,在所有需要将数学和统计应用于“日常”案例的情况中,我们通常需要一些基础统计知识。而且,当我们得出结果时,重要的是结果本身,因为它能满足一些业务需求,这些需求不需要科学验证。
所以,这里是我们在这些情况下可以做的事情,而不是使用p 值。
数据科学家可以使用什么来代替 p 值
数据科学是一个今天甚至被用来通过数据为企业提供一些见解的领域,通常得益于机器学习进行预测。
为此,我们可以利用“点查”方法,例如,这是一种非常简单的方法,其工作方式如下:
-
我们需要通过机器学习进行预测来解决业务问题。
-
我们获取数据,清理数据,并将其划分为训练集和测试集。
-
我们在训练集上测试 3-5 个机器学习模型,并从中选择表现最好的 2-3 个模型。
-
我们调整在训练集上表现良好的 2-3 个机器学习模型的超参数,并在测试集上测试它们。
-
我们决定哪一个 2-3 个模型表现最好。
因此,根据这种方法,我们可以通过选择一组机器学习模型来解决业务问题。这样,如果我们使用p 值,我们应该计算一个机器学习模型的p 值,测试它,找到结果,查看不满意的结果,然后更换机器学习模型。
因此,考虑到p 值不能说明结果的任何信息,为什么要在“非严格科学环境”中使用它们呢?对我来说,这没有太大意义……
此外,我们通常在数据上使用多个机器学习模型的事实与所谓的“无免费午餐定理”(NFL)有关。该定理声明(引自维基百科):
任何两个优化算法在其性能*均到所有可能问题时都是等效的
这意味着一个算法在特定的机器学习问题上表现非常好,但这并不意味着它在另一个问题上表现也会同样好,因为相同的假设可能不适用。
换句话说,一些算法在某些类型的问题上可能表现优于其他算法,但每个算法由于其先验假设都有优缺点。
所以,这里有一个基本概念要记住:我们不能仅仅因为在先前类似问题上取得了良好表现,就将一个机器学习模型应用到一个新问题上,因为这意味着会对解决方案产生偏见。
这就是为什么我们在选择最佳模型之前,最好在同一数据集上测试不同的 ML 模型。我们不能仅仅因为它在类似问题上有效就使用一个模型,因为这样会偏向我们的解决方案。
Python 中的一个示例
所以,让我们在 Python 中做一个示例。为了学习的目的,我们创建了一个由三次多项式生成的简单数据集(多么大的剧透!)。然后,我们对数据集进行缩放并将其拆分为训练集和测试集:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
from sklearn.preprocessing import PolynomialFeatures
# Set random seed for reproducibility
np.random.seed(42)
# Generate some random data
n_samples = 1000
X = np.random.uniform(-10, 10, size=(n_samples, 5))
y = 3*X[:,0]**3 + 2*X[:,1]**2 + 5*X[:,2] + np.random.normal(0, 20, n_samples)
# Create a pandas DataFrame
df = pd.DataFrame(X, columns=['x1', 'x2', 'x3', 'x4', 'x5'])
df['y'] = y
# Fit a 3-degree polynomial regression model
X = df[['x1', 'x2', 'x3', 'x4', 'x5']]
y = df['y']
poly_reg = LinearRegression()
poly_reg.fit(X ** 3 + X ** 2 + X, y)
# Calculate the R-squared value
y_pred = poly_reg.predict(X ** 3 + X ** 2 + X)
r2 = r2_score(y, y_pred)
# Split into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
现在,我们将把线性回归模型拟合到X_train数据集,并使用statsmodels.api包计算统计摘要,其中包括p-values:
import statsmodels.api as sm
# Add a constant term to X_train
X_train_with_const = sm.add_constant(X_train)
# Fit linear regression model with statsmodels
lin_reg_sm = sm.OLS(y_train, X_train_with_const).fit()
# Print summary of the model
print(lin_reg_sm.summary())

线性回归模型的统计摘要(注意:由于
ML 的随机性。图像由 Federico Trotta 提供。
使用普通最小二乘法,我们可以获得关于模型的大量统计信息,包括 R²和调整后的 R²。如果你不知道我们所说的是什么,可以阅读我的文章:
## 掌握回归分析的艺术:每个数据科学家应该知道的 5 个关键指标
这是关于回归分析中使用的指标的所有知识的权威指南
[towardsdatascience.com
此外,上述摘要给出了在某个置信区间(0.025, 0.975,即 2.5%、97.5%)下计算的p-values。特别是,我们对“t”和“P>|t|”列感兴趣。
让我解释一下:
-
首先,我们可以看到这些值是针对每个特征(
x1, x2, x3, x4, x5)计算的,包括常数值(const)。 -
“t”列衡量估计系数在零假设下距离其假设值多少个标准差。t 统计量的绝对值越大,相应系数在预测目标变量时就越显著。
-
“P>|t|”列表示每个系数的
p-value。我们说低p-value表示相应的系数在统计上显著,而高p-value则表明其不显著。理想情况下,我们知道统计显著性的标准是:p-values< 0.05。
所以,在这种线性回归模型的情况下,许多特征看起来很显著。很好!所以,让我们看看一个二次和一个三次多项式,看看会发生什么。
为此,我们需要将我们的领域转换为二次和三次模型,并拟合线性回归模型。然后,我们这样打印摘要:
#creating quadratic and cubic features
quadratic = PolynomialFeatures(degree=2)
cubic = PolynomialFeatures(degree=3)
X_quad = quadratic.fit_transform(X_train) #quadratical transformation
X_cubic = cubic.fit_transform(X_train) #cubical tranformation
# Add a constant term to X_train
X_train_with_const_quad = sm.add_constant(X_quad)
X_train_with_const_cub = sm.add_constant(X_cubic)
# Fit polynomial regression models with statsmodels
quad_reg_sm = sm.OLS(y_train, X_train_with_const_quad).fit()
cub_reg_sm = sm.OLS(y_train, X_train_with_const_cub).fit()
# Print summary of the model
print(quad_reg_sm.summary())
print(cub_reg_sm.summary())

二次回归模型的统计摘要(注意:由于
机器学习的随机性质)。图像由费德里科·特罗塔提供。

三次回归模型的统计摘要(注意:由于
机器学习的随机性质)。图像由费德里科·特罗塔提供。
首先,多项式模型比线性模型有更多的特征,因为多项式模型的公式是:
多项式模型的公式。作者撰写,嵌入-dot-fun 提供技术支持。
所以这个公式在线性模型的公式中添加了多项式项。
所以,对我们的结果进行评论,我们在 R² 和调整后的 R² 上有所改进,但根据 p-values,许多特征似乎并不具有统计显著性。特别是,三次多项式的 R²=1(当然!我们用三次多项式创建了数据集,所以……一切都符合!),但许多特征似乎并不具有统计显著性。
现在,让我问你一个问题:根据这些统计数据,你会选择哪个模型?线性模型看起来很不错,不是吗?
所以,这里是问题:正如我们所说,使用 p-values 并不能保证结果。
此外,考虑到我们已经用 3 次多项式构建了这个数据集,几乎所有特征都没有统计显著性,这怎么可能呢?嗯,很难说。我们只知道模型的一个或多个假设没有得到尊重(多项式模型的假设与线性模型相同;你可以在这篇文章中找到)。但在这些情况下,多重共线性通常是最可能的原因。
现在,让我们使用抽样检查方法来解决这个 ML 模型。记住,线性回归模型没有超参数,所以我们可以直接将其(以及多项式模型)拟合到测试集上:
**NOTE:**
for the sake of clarity, we are using again the 2-degree and 3-degree
polynomial transformation, even if we shouldn't if we were using
a Jupyter Notebook (we've already done the transformation above).
# Fit linear regression model to the train set
lin_reg = LinearRegression().fit(X_train, y_train)
# Create quadratic and cubic features
quadratic = PolynomialFeatures(degree=2)
cubic = PolynomialFeatures(degree=3)
X_quad = quadratic.fit_transform(X_train) #quadratical transformation
X_cubic = cubic.fit_transform(X_train) #cubical tranformation
# Fit quadratic and cubic transformed set
quad_reg = LinearRegression().fit(X_quad, y_train)
cubic_reg = LinearRegression().fit(X_cubic, y_train)
# Calculate R² on test set
print(f'Coeff. of determination linear reg test set:{lin_reg.score(X_test, y_test): .2f}')
print(f'Coeff. of determination quadratic reg test set:{quad_reg.score(quadratic.transform(X_test), y_test): .2f}')
print(f'Coeff. of determination cubic reg test set:{cubic_reg.score(cubic.transform(X_test), y_test): .2f}')
>>>
Coeff. of determination linear reg test set: 0.81
Coeff. of determination quadratic reg test set: 0.81
Coeff. of determination cubic reg test set: 1.00
所以,我们为所有模型获得了高 R² 值。特别是,三次多项式在训练集和测试集上都有 R²=1。这应该告诉我们一些事情……
现在,让我们在测试集上验证我们的模型,即使使用 KDE 图。如果你不知道 KDE 模型是什么以及如何使用它,你可以阅读我在这里的文章:
你需要知道的线性回归的所有信息都在这里(包括 Python 中的应用)
[towardsdatascience.com
我们可以这样使用它们:
#Kernel Density Estimation plot
ax = sns.kdeplot(y_test, color='r', label='Actual Values') #actual values
sns.kdeplot(y_test_pred_lin, color='b', label='Predicted Values', ax=ax) #predicted values
#showing title
plt.title('Actual vs Predicted values for linear model')
#showing legend
plt.legend()
#showing plot
plt.show()

线性模型的 KDE 图。图片来源于费德里科·特罗塔。
#Kernel Density Estimation plot
ax = sns.kdeplot(y_test, color='r', label='Actual Values') #actual values
sns.kdeplot(y_test_pred_quad, color='b', label='Predicted Values', ax=ax) #predicted values
#showing title
plt.title('Actual vs Predicted values for quadratic model')
#showing legend
plt.legend()
#showing plot
plt.show()

二次方模型的 KDE 图。图片来源于费德里科·特罗塔。
#Kernel Density Estimation plot
ax = sns.kdeplot(y_test, color='r', label='Actual Values') #actual values
sns.kdeplot(y_test_pred_cub, color='b', label='Predicted Values', ax=ax) #predicted values
#showing title
plt.title('Actual vs Predicted values for the cubic model')
#showing legend
plt.legend()
#showing plot
plt.show()

三次方模型的 KDE 图。图片来源于费德里科·特罗塔。
所以,通过观察 KDE,我们可以说三次方模型显然过拟合了数据(但即使因为训练集和测试集的 R²都为 1,我们也可以这样判断。不过,再次提醒:我们在这里发现了一些明显的东西。发现明显的事情是好的,因为我们知道自己做得很好!)。
此外,即使我们在线性和二次方模型的两个数据集上得到了非常好的 R²值,KDE 也清楚地告诉我们这些模型并不适合解决这个机器学习问题。因此,我们应该研究其他模型。
最后,给一个最终建议,如果我们真的想计算p-values以确保我们的模型符合假设,我们可以在最后进行计算(所以我们只会为我们认为是最佳的模型计算它们)。
但请记住:基于p-values,线性模型可能是解决这个机器学习问题的一个好选择,而 KDE 却告诉我们相反的结果!
结论
感谢阅读这篇文章:我非常希望在评论中听到你的意见。
让我总结一下这些观点:
-
p-values的使用在某种程度上是一个有争议的话题。它有助于测试我们的假设,但正如 ASA 所说,它并不能保证结果。 -
我个人认为
p-values更适合科学研究,在那里重要的是展示我们在得出某些结果时使用了严格的方法(记住,正如 ASA 所说,结果不能仅仅依赖于p-values)。 -
考虑到我们需要在给定的数据集上测试不同的机器学习模型,以验证无免费午餐定理,当提供业务价值时,实际上不需要使用
p-values来为我们的结果提供科学依据。无论如何,如果我们想使用它们,可以在最后测试假设,以展示我们选择的模型符合假设。 -
订阅 我的新闻通讯 以获取更多关于 Python 和数据科学的内容。
-
觉得有用吗? 请给我买一杯 Ko-fi。
-
喜欢这篇文章?通过 我的推荐链接加入 Medium: 每月 5 美元(无额外费用)解锁 Medium 上的所有内容。
-
想联系我?请在 这里。
AI 中解释性的渐变的必要性
原文:
towardsdatascience.com/the-necessity-of-a-gradient-of-explainability-in-ai-743ee0bcb848
过多的细节可能让人不堪重负,而不足的细节则可能产生误导。
·发表于 数据科学的未来 ·4 分钟阅读·2023 年 7 月 29 日
--

图片由 No Revisions 提供,发布在 Unsplash
“任何足够先进的技术都与魔法无异” — 阿瑟·克拉克
随着自动驾驶汽车、计算机视觉和最*的大型语言模型的发展,科学有时会让人觉得像魔法一样!模型每天都在变得越来越复杂,当试图向新受众解释复杂模型时,很容易挥手一抹,随口说点关于反向传播和神经网络的话。然而,有必要描述一个 AI 模型、其预期影响和潜在偏见,这就是可解释 AI 的作用所在。
在过去十年中,AI 方法的爆炸式增长使得用户接受他们得到的答案而不加质疑。整个算法过程通常被描述为黑箱,即使是开发它的研究人员,也不总是直接或甚至可能理解模型如何得出特定结果。为了建立用户的信任和信心,公司必须描述它们所采用系统的公*性、透明度和基本决策过程。这种方法不仅导致对 AI 系统的负责任态度,而且增加了技术的采纳 (www.mckinsey.com/capabilities/quantumblack/our-insights/global-survey-the-state-of-ai-in-2020)。
AI 解释性中最困难的部分之一是明确界定正在解释的内容的边界。高管和 AI 研究人员对信息的需求和接受度不同。找到在简单解释和所有可能路径之间的合适信息层次需要大量的训练和反馈。与普遍观点相反,去除数学和复杂性的解释并不会使其毫无意义。确实存在过度简化的风险,可能会误导对方认为他们对模型及其功能有深入理解。然而,运用正确的技术可以在适当的层次上提供清晰的解释,从而引导对方向数据科学家等进一步询问以增加知识。这个过程的关键在于进行有效的对话和沟通,以确保必要的信息得到传达。
如何获得有效沟通的经验?与普遍观点相反,获得解释经验并不需要达到高级职位。虽然确实解释复杂概念的技能通过试错过程随着时间的推移而提高,但初级员工往往在这方面非常有效,因为他们刚刚学习了相关内容。关键是通过实践获得向非技术观众解释的经验,任何人都可以做到这一点,而不需要等待成为高级员工。事实上,理解复杂概念并能够向非技术观众解释这些概念并不互相排斥。要提高这一技能,唯一的方法就是:实践、实践、再实践。
解释复杂概念可能因被称为知识诅咒的因素而具有挑战性。需要从不同角度反复讲解以创造持久的记忆。生成性 AI 通过大型语言模型正变得越来越普及,这也产生了对理解的需求。虽然有关于 ChatGPT 提供错误信息的担忧,但理解其原因对于掌握技术的能力和局限性是很重要的。我们都熟悉手机和电子邮件中的预测文本,大型语言模型的工作原理也是如此,只是规模更大。像我们十年前的手机一样,它们并不总能正确预测下一个词。回顾技术的众多进步,很明显一切都是渐进的,利用这种渐进性是解释否则看似魔术的概念的关键。
AI 中的可解释性不仅在向他人解释概念时很重要;在现有的机器学习模型中加入可解释性也可能具有挑战性。在决定选择哪种模型时,应考虑需求和最终用户,以确保在复杂模型和可解释性之间进行权衡。有时,线性回归的简单性胜过更复杂模型的复杂性。对某人生活产生重大影响的决策,例如授权银行贷款,需要解释。特别是当模型的输出不是期望结果时,信息尤为宝贵。在这种情况下,拥有可解释性流程可以揭示模型或使用的训练数据中的缺陷。
总结来说,AI 中的可解释性发生在不同的阶段。向最终用户解释概念,以确保他们理解潜在的局限性。向同行和非技术观众解释模型,以了解算法的详细情况。解释模型应用结果的决策,以确保符合规定且不存在隐性偏见。这三个方面对 AI 的发展都至关重要,如果这些方面中的一个对你正在解决的问题来说过于复杂,那么可能需要考虑所用模型是否是最合适的。
如果你喜欢这个故事,请不要犹豫地点赞或在评论中与我联系!关注我在Medium的账号,获取更多关于数据科学的内容!
新的最佳 Python 包用于可视化网络图
原文:
towardsdatascience.com/the-new-best-python-package-for-visualising-network-graphs-e220d59e054e
一份指南,介绍谁应该使用它,何时使用,如何使用,以及我之前的错误…
·发布于 Towards Data Science ·10 分钟阅读·2023 年 11 月 23 日
--

图片由Chris Ried拍摄,来源于Unsplash
介绍
在这篇文章中,我将向你介绍一个我偶然发现的 Python 包,依我拙见,它是目前为止我见过的最佳网络图可视化工具。
需要紧凑且强大的可视化包来进行快速原型设计、探索性数据分析或调试网络模型的数据科学家最适合阅读下面的内容。
我们将要检查的包叫做:gravis
编辑描述
robert-haas.github.io](https://robert-haas.github.io/gravis-docs/?source=post_page-----e220d59e054e--------------------------------)
我个人在日常工作中经常使用图神经网络,坦白说,我很烦恼之前没有早些知道这个包,因为它本可以节省我很多时间和精力,避免了在使用我之前提到的那些包(ipysigma和pyvis)时遇到的不足。
一份指南,说明如何使用它们,何时使用它们,以及谁应该使用它们。
什么使网络可视化软件包成为最佳?
一个可视化软件包需要具备:
-
创建一个完全交互的可视化,其中我可以点击节点和边并查看其属性,还可以拖放它们。
-
方便实现——不需要太多代码(像 Dash 那样),但对大多数用例来说足够强大和灵活。
-
对节点和边的数量具有适度的良好扩展性——我们不是在做生产级别的东西,但我们需要它至少能处理几百个节点。
-
兼容 Python 中常用的网络包,如
networkx。
我们将对什么进行测试?
广义地说,我们可以根据图是否是同质的或异质的,或者是否有向或无向边来对图进行分类。
因此,我们将对使用networkx生成的两种类型的图进行测试。
(1)一个同质的无向图
(2)一个异质的有向多重图
因为这代表了可能遇到的两个极端。如果你不熟悉这些术语,我建议你访问我的上一篇文章并快速阅读介绍部分。
TL;DR
-
同质 = 1 种类型的节点
-
异质 = 多种类型的节点和/或边
-
无向 = 边没有方向
-
有向 = 边有方向
-
多重图 = 两个节点之间可以有多条边
设置和安装
该软件包可以通过 pip 简单安装:
pip install gravis
我们还需要安装以下软件包以生成测试图。
pip install numpy, matplotlib, networkx
你还可以在这个仓库中找到我使用的所有代码。
[## GitHub - bl3e967/medium-articles: 我在 Medium 文章的附带代码。
我在 Medium 文章的附带代码。通过创建一个账户来贡献 bl3e967/medium-articles 的开发…
同质无向网络示例
所以,为了开始,我们需要一个图来绘制。我们将编写一个简单的图生成器函数,它将返回一个networkx.Graph对象。我们将向其节点和边添加属性,以模拟数据科学家在工作中可能看到的数据。
图生成器
我们使用一个随机图生成器,我选择了networkx.dual_barabasi_albert_graph方法来模拟一个无尺度网络。
我们添加了节点级别的属性,例如degree、betweenness_centrality,以及一些用随机数字生成的虚构特性,我们称之为feature1、feature2、feature3。
我们对边也做了相同的处理,并添加了特性feature1和feature2。
我们最终为每个节点标上uuid(Universally Unique IDentifier),使其看起来更像真实数据。
def get_new_test_graph():
NUM_NODES = 50
p = 0.5
seed = 1
test_graph = nx.dual_barabasi_albert_graph(n=NUM_NODES, p=p, seed=seed, m1=2, m2=1)
# add node properties
nx.set_node_attributes(test_graph, dict(test_graph.degree()), name='degree')
nx.set_node_attributes(test_graph, nx.betweenness_centrality(test_graph), name='betweenness_centrality')
for node, data in test_graph.nodes(data=True):
data['node_identifier'] = str(uuid.uuid4())
data['feature1'] = np.random.random()
data['feature2'] = np.random.randint(0, high=100)
data['feature3'] = 1 if np.random.random() > 0.5 else 0
# add edge properties
for _, _, data in test_graph.edges(data=True):
data['feature1'] = np.random.random()
data['feature2'] = np.random.randint(0, high=100)
return test_graph
当我们使用networkx绘制图形时,我们会得到如下结果:
test_graph = get_new_test_graph()
nx.draw(test_graph)

50 节点的测试图,均匀且无向。
使用 gravis 绘图
现在我们开始使用gravis绘制这个图。使用这个包非常简单,完整的效果如下。
import gravis as gv
gv.d3(
test_graph,
# graph specs
graph_height=500,
# node specs
node_size_data_source="betweenness_centrality",
use_node_size_normalization=True,
node_size_normalization_min=15,
node_size_normalization_max=35,
show_node_label=True,
node_label_data_source='node_identifier',
# edge specs
edge_size_data_source='feature1',
use_edge_size_normalization=True,
edge_size_normalization_min=1,
edge_size_normalization_max=5,
# force-directed graph specs
many_body_force_strength=-500
)
让我们将其拆分为几个部分。
假设我们想根据节点的betweenness_centrality值来缩放节点大小,并根据边的feature1值来缩放边的厚度。
我们设置use_node_size_normalization=True,以便节点大小根据betweenness_centrality的归一化值进行设置,并定义node_size_normalization_min和node_size_normalisation_max来设置我们希望的最小和最大节点大小。
# node specs
node_size_data_source="betweenness_centrality",
use_node_size_normalization=True,
node_size_normalization_min=15,
node_size_normalization_max=35,
我们使用等效的参数来控制边的厚度:
# edge specs
edge_size_data_source='feature1',
use_edge_size_normalization=True,
edge_size_normalization_min=1,
edge_size_normalization_max=5,
最后,我将many_body_force_strength参数设置为-500,使边的长度比默认值更长,以使图形更清晰可见。
结果图如下所示。

我们的均质无向图通过 gravis 进行可视化
添加颜色
除了按照betweenness_centrality缩放节点大小外,我还希望根据此进行着色。
我们可以通过简单地向节点添加color属性来实现这一点。我可以使用命名颜色,如‘red’,‘blue’,‘green’,在这里我不会做这个,因为这太简单了。让我们尝试使用颜色比例。
我想根据matplotlib中的winter颜色图来缩放这些值。

低中心性值将被映射为蓝色,高中心性值将被映射为绿色。
我有一个非常有用的MplColorHelper类,我在项目中使用过很多次,它可以将数值转换为RGB字符串。
class MplColorHelper:
def __init__(self, cmap_name, start_val, stop_val):
self.cmap_name = cmap_name
self.cmap = plt.get_cmap(cmap_name)
self.norm = mpl.colors.Normalize(vmin=start_val, vmax=stop_val)
self.scalarMap = cm.ScalarMappable(norm=self.norm, cmap=self.cmap)
def get_rgba(self, val):
return self.scalarMap.to_rgba(val, bytes=True)
def get_rgb_str(self, val):
r, g, b, a = self.get_rgba(val)
return f"rgb({r},{g},{b})"
我只需要指定我们希望缩放颜色图的最小和最大值。
所以,要根据betweenness_centrality着色节点,我们可以如下操作:
# the matplotlib colourmap we want to use
CM_NAME = "winter"
# initialise colour helper
vals = nx.get_node_attributes(test_graph, 'betweenness_centrality').values()
betweenness_min, betweenness_max = min(vals), max(vals)
node_colors = MplColorHelper(CM_NAME, betweenness_min, betweenness_max)
# get rgb string for each node
for node, data in test_graph.nodes(data=True):
data['color'] = node_colors.get_rgb_str(data['betweenness_centrality'])
对于边,我们可以根据feature1来着色它们。
# initialise colour helper
vals = nx.get_edge_attributes(test_graph, 'feature1').values()
val_min, val_max = min(vals), max(vals)
edge_colors = MplColorHelper(CM_NAME, val_min, val_max)
# get rgb string for each node
for u, v, data in test_graph.edges(data=True):
data['color'] = edge_colors.get_rgb_str(data['feature1'])
完成了,让我们来添加颜色:

同质无向网络的节点和边的颜色
最后,gravis允许我们使用可视化底部的信息栏显示每个节点或边上存储的任何自由文本。
我们将使用它来显示我们的特征值——我们将特征值格式化为一些文本,并保存到名为click的属性中:
# node features
for node, data in test_graph.nodes(data=True):
data['click'] = (
f"Node: {data['node_identifier']}"
"\nNode Features:" +
f"\nfeature 1: {data['feature1']:.3f}" +
f"\nfeature 2: {data['feature2']:.3f}" +
f"\nfeature 3: {data['feature3']:.3f}" +
f"\nBetweenness Centrality: {data['betweenness_centrality']:.3f}" +
f"\nDegree: {data['degree']}"
)
# edge features
for u, v, data in test_graph.edges(data=True):
data['click'] = (
f"Edge: {test_graph.nodes[u]['node_identifier']} -> {test_graph.nodes[v]['node_identifier']}" +
f"\nEdge Features:" +
f"\nfeature 1: {data['feature1']}" +
f"\nfeature 2: {data['feature2']}"
)
现在我们能够通过屏幕底部的面板非常好地显示我们的特征值(我把图形做得特别大,以便你可以像在屏幕上看到的那样看到它)。

显示在底部面板中的节点特征详细信息的可视化。

显示在底部面板中的边特征详细信息的可视化。
我们有一个完全互动的可视化,允许我们拖动节点,并且有一个侧边栏用于更改节点和边的可视化设置、标签和布局算法。
它还允许你使用设置栏将可视化导出为图像——无需额外代码!最后,如果你希望与他人分享这个互动图,你可以将其导出为自包含的 HTML 文件:
fig = gv.d3(test_graph, *args, **kwargs)
fig.export_html("graph_to_export.html")
异质有向网络示例
我们现在尝试使用gravis可视化一个异质有向网络。
图生成器
再次,我们需要一个函数来返回这样的图。我们将使用之前的相同函数,但这次使用nx.scale_free_graph函数。
我们还添加了一个node_type属性来模拟异质图。前 25 个节点将是node_type = 0,其余的将是node_type = 1。
def get_new_test_digraph():
NUM_NODES = 50
# We change the graph generator function here
test_graph = nx.scale_free_graph(n=NUM_NODES, seed=0, alpha=0.5, beta=0.2, gamma=0.3)
# add node properties
nx.set_node_attributes(test_graph, dict(test_graph.degree()), name='degree')
nx.set_node_attributes(test_graph, nx.betweenness_centrality(test_graph), name='betweenness_centrality')
for node, data in test_graph.nodes(data=True):
# assign node type so we have heterogeneous graph
data['node_type'] = 0 if node < 25 else 1
# same as before, a add ther node features.
data['node_identifier'] = str(uuid.uuid4())
data['feature1'] = np.random.random()
data['feature2'] = np.random.randint(0, high=100)
data['feature3'] = 1 if np.random.random() > 0.5 else 0
# add edge properties
for u, v, data in test_graph.edges(data=True):
data['feature1'] = np.random.random()
data['feature2'] = np.random.randint(0, high=100)
return test_graph
使用nx.draw绘制得到如下图:

异质有向多图,编号以显示哪些节点属于哪种类型。
使用 gravis 绘图
我们对异质图的绘制方式与上述完全相同。我们可以使用相同的方法:
-
生成测试多有向图
-
为节点和边添加特征值
-
根据属性值为节点和边添加颜色
现在我们需要做的就是指定每个节点的形状,因为现在我们处理的是多种不同的节点类型。
对于我们的图生成函数get_new_test_digraph,我们只需要在遍历节点的for循环中添加这一行代码:
for node, data in test_graph.nodes(data=True):
data['node_type'] = 0 if node < 25 else 1 # add this line
对于模拟实际数据的目的,我们将图中的前 25 个节点设置为node_type = 0,其余为node_type = 1。
接下来,我们可以使用shape属性在 gravis 中指定我们想为每个节点使用的形状。
在这里,我们将node_type = 0设置为圆形,将node_type = 1设置为矩形。
for node, data in test_graph.nodes(data=True):
data['value'] = data['betweenness_centrality'] # node size
data['label'] = data['node_identifier']
data['title'] = (
f"Node: {data['node_identifier']}"
"\nNode Features:" +
f"\nfeature 1: {data['feature1']}" +
f"\nfeature 2: {data['feature2']}" +
f"\nfeature 3: {data['feature3']}" +
f"\nBetweenness Centrality: {data['betweenness_centrality']}" +
f"\nDegree: {data['degree']}"
)
data['color'] = node_colors.get_rgb_str(data['betweenness_centrality'])
## add this line to specify shape.
data['shape'] = 'dot' if data['node_type'] == 0 else 'rectangle'
我们现在准备在gravis中绘制我们的网络。注意我们添加了edge_curvature参数,使其不为零。这是为了防止当节点很多时,两个节点之间的边重叠。
gv.d3(
test_graph,
graph_height=700,
node_size_data_source="betweenness_centrality",
use_node_size_normalization=True,
node_size_normalization_min=15,
node_size_normalization_max=35,
show_node_label=False,
node_label_data_source='node_identifier',
edge_size_data_source='feature1',
use_edge_size_normalization=True,
edge_size_normalization_min=1,
edge_size_normalization_max=5,
# Specify curvature to differentiate between the multiple
# edges between two nodes.
edge_curvature=-0.5,
many_body_force_strength=-500
)

在 gravis 中绘制的 MultiDiGraph,边的曲率和形状根据节点类型设置。
完美!简单方便。你可以看到图左侧和顶部一些节点之间的多个连接。
总结——为什么我认为 Gravis 是最好的
我已经提到过我写了另外两个包,我坚信它们是最好的——pyvis和ipysigma。当时我对此深信不疑,因为我自己在日常工作中一直在使用它们。
然而,它们各自都有独特的问题,事后看来,这使得它们的效果不尽如人意,这也是我在同一个博客中讨论这两者的原因!根据使用场景,它们中有一个比另一个更好,我建议用户在不同情况下使用哪个。
这就是为什么我在发现gravis时感到震惊,因为它似乎有人完全按照相同的思路开发了这个包,结合了pyvis和ipysigma的优点,制作了一个完美的工具。
首先,gravis提供了完全互动的用户界面,你可以:
-
不仅可以点击节点和边以显示信息,还允许用户拖放节点,这对于需要手动调整物理引擎的复杂网络非常有用(而
ipysigma缺乏这一点)。 -
节点或边的信息通过屏幕上的一个栏展示,该栏整齐地容纳了信息,还允许从中复制信息(这是一个小但非常重要的功能,
pyvis中缺乏这一点,这让我在工作中非常困扰)。 -
提供了一个不同的侧边栏,通过它你可以调整物理引擎参数,改变节点和边的可视化设置,并通过按钮将图形导出为图像。
-
另外,它可以显示节点之间的多个边(再次说明,
ipysigma无法做到这一点)。
因此,gravis消除了在pyvis和ipysigma之间进行选择的必要性,因为它将这两个包合并为一个,同时保持了它们的所有优点。
简而言之,它满足了所有要求,并且表现出色。
我强烈推荐这个包。
在评论区告诉我你的想法,如果你喜欢我的文章,请鼓掌或者分享给可能感兴趣的人。
BigQuery 中的新生成 AI 功能
原文:
towardsdatascience.com/the-new-generative-ai-function-in-bigquery-38d7a16d4efc
如何使用 BigQuery 的 GENERATE_TEXT 远程函数
·发表于数据科学之路 ·阅读时间 10 分钟·2023 年 12 月 1 日
--

“每个人都可以通过 SQL 知识和良好的提示结构在 BigQuery 中进行编程和自然语言处理分析” [照片来源:Adi Goldstein于Unsplash]
介绍
自从我开始使用 Google *台以来,Google 一直没有停止用其BigQuery(BQ)功能和开发给我带来惊喜。
四年前我经历了真正的“哇”时刻。
我记得就像昨天一样,当时我坐在大数据伦敦2019 年会议的前排。那时我完全不知道只用 BQ 函数就能创建机器学习模型的可能性,或者更准确地说,不知道 BQ 机器学习(BQML)是什么。
至少在会议环节之前,Google 的同事展示了如何仅使用 Google 的 SQL 创建分类、聚类和时间序列预测模型。
当时我脑海中闪过的第一个念头是“你一定是在开玩笑”。
我脑海中的第二个念头是:“这是否意味着所有只知道 SQL 的人都能创建机器学习模型?”
正如你可以想象的那样,如果你使用 BigQuery 作为数据仓库,那么答案是“是的”。
现在,在使用了BQML函数一段时间后,上述问题的正确答案是“也许”。
这意味着即使[CREATE MODEL](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create)语法是用 SQL 编写的,仍然需要了解机器学习建模和统计学知识。
换句话说,你仍然需要理解不同类型机器学习用例的模型背后的数学知识(监督学习/无监督学习),进行特征工程、超参数调整和模型评估任务。
快进到 2023 年,BigQuery 通过其新功能进一步让我惊叹。
这一次,我们讨论的是新的生成性 AI BigQuery 机器学习函数。
利用这些新功能,数据工程师和分析师可以在 BQ 表中存储的文本数据上执行生成性自然语言任务,只需几行查询代码。
因此,本博客文章的目标是展示 BQ 在生成性 AI 中的新分析进展,重点介绍一个功能— GENERATE_TEXT功能。
关于 GENERATE_TEXT 函数
GENERATE_TEXT函数的主要思想是通过 BigQuery 中的 SQL 和提示指令来协助数据专业人员完成以下任务[1]:
-
分类
-
情感分析
-
实体提取
-
提取式问答
-
总结
-
以不同风格重写文本
-
广告文案生成
-
概念构思
换句话说,该函数可以利用Vertex AI [text-bison](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text)自然语言基础模型 [1]在 BQ 中对存储的文本属性执行生成性自然语言任务。
它的工作原理如下[1]:
-
首先,它向代表 Vertex AI
text-bison自然语言基础模型(LLM)的 BigQuery ML 远程模型发送请求。 -
然后,它返回带有定义输入参数的响应。
这意味着不同的函数参数和输入提示设计(分析的提示指令)会影响 LLM 的响应。
说到这里,可以将以下函数特定参数传递给 GENERATE_TEXT 函数,并影响响应质量 [1]:
#1: model [STRING] → 指定使用 text-bison Vertex AI LLMs 之一的远程模型的名称。
#2: query_statement [STRING] → 指定用于生成提示数据的 SQL 查询。
#3: max_output_tokens [INT64] → 设置模型输出的最大 token 数量。对于较短的响应,可以指定较低的值。
- 注意: 一个 token 可能比一个词小,约为四个字符。
#4: temperature [FLOAT64] → 范围在[0.0,1.0]内的参数,用于在响应生成期间进行采样,当 top_k 和 top_p 被应用时会发生采样。
- 注意: 该参数表示 token 选择的随机程度,即,较低的值适用于需要更确定性响应的提示,而较高的值可能导致更具多样性的结果。
#5: top_k [FLOAT64] → 范围在[1,40]内的参数,决定模型如何选择输出的 token。
- 注意: 为了获得更少的随机响应,应指定较低的值。
#6: top_p [FLOAT64] → 范围在[0.0,1.0]内的参数,决定模型如何选择输出的 token。
- 注意: 为了获得更少的随机响应,应指定较低的值。 Tokens 从最可能的(基于
top_k值)到最不可能的进行选择,直到它们的概率总和等于top_p值。
在了解了函数的目的和每个参数的作用之后,我们可以开始演示如何使用 BigQuery 的 GENERATE_TEXT 函数。
使用该函数的 4 步方法
本节将介绍测试生成 AI 功能GENERATE_TEXT的四个方法步骤。

GENERATE_TEXT 函数工作流程 [图片由作者提供]
总的来说,该方法包括:
-
使用 ChatGPT 生成小型模拟数据集并将其导出到 Google Sheets 文档中。
-
在 Google Sheets 文档的基础上创建 BigQuery 数据集。
-
设置 Google 服务 Vertex AI 和 BQ 之间的连接,以便使用远程生成 AI 模型。
-
在 BigQuery 的模拟数据集上测试 GENERATE_TEXT 函数的实际用例示例。
每一步的更多背景信息请参见下面的子节。
步骤 1: 使用 ChatGPT 生成模拟数据集
由于我没有现实生活中的数据集,我决定创建一个用于测试目的的模拟数据集。
为此,我使用 ChatGPT 输入了以下提示文本:
“我需要为 50 种不同的虚构头发产品自动生成客户评价。
对于每个产品,我需要 2 条正面评论、2 条负面评论和 1 条中性评论。
此外,我希望评论至少有 4 句话,并包含不同的信息:产品名称、购买产品的商店位置和产品价格。
在负面评论中,请包括不同的原因,如产品质量问题、价格问题和配送问题。
结果是一个小型数据集表,包含五个属性(下图预览):
-
product_name — 包含虚假产品名称的值,
-
review_type — 包含评论情感类型(正面、负面或中性),
-
store_location — 包含随机的城市和州名称,
-
product_price — 包含随机的美元产品价格, 和
-
product_review— 包含五句话长的虚假产品评论文本。

BigQuery 模拟数据集预览 [作者提供的图片]
完整数据集可以在 Git 仓库中找到 这里 [4]。
在准备好模拟数据集并将其存储在 Google Sheets 后,下一步是将其转移到 BigQuery。
步骤 2: 在 Google Sheet 文档上创建 BQ 表
当我有一个小型数据集,需要频繁手动调整或更改时,我的首选方法是创建一个 BigQuery 表,基于可编辑的 Google Sheet 文档。
为此,需要以下子步骤 [2]:
子步骤 #1: 在 BigQuery 项目中创建数据集和表,通过指定文件位置和格式
应选择 BigQuery 环境右上角的 CREATE TABLE 选项来创建新数据集和表。需要输入标有星号(*******) 的值,如下图所示。

在 Google Sheets 中的模拟数据集上创建 BQ 数据集和表 [作者提供的图片]
从上图可以看出,新创建的 BQ 数据集名称为 hair_shop ,新创建的表名称为 product_review_table。
需要注意的重要事项:
- 如果你不想在上图所示的
Schema部分定义表模式,则从 Google Sheets 导入的所有属性默认的数据类型为STRING。
子步骤 #2: 在 BQ 中查询模拟数据集
第二个子步骤是直接在 BigQuery 中探索 hair_shop.product_review_table 数据集。

在 BQ 中查询模拟数据集 [作者图片]
现在你可以访问 BQ 中的模拟数据集,是时候将生成 AI 远程模型连接到它了。
步骤 3: 将 Vertex AI 服务连接到 BQ
详细说明该步骤的指南可以在 Google 的文档 这里 [3] 中找到。
总结提供的 Google 教程,将 Vertex AI 连接到 BQ 的三个主要子步骤是:
-
子步骤 #1: 从 BigQuery 创建一个云资源连接,以获取连接的服务帐户。
-
子步骤 #2: 授予连接的服务帐户适当的角色,以访问 Vertex AI 服务。
-
子步骤 #3: 创建代表托管 Vertex AI 大型语言模型的
text-bison远程模型,在创建的 BigQuery 数据集hair_shop中。
一旦这些子步骤完成,新的对象 Model 将在 hair_shop 数据集中可见。

选定 BQ 数据集中 Model 的新数据库对象的预览 [作者图片]
最后,魔法可以开始了,我们可以开始使用 GENERATE_TEXT 函数。
步骤 4: 使用 *GENERATE_TEXT* 函数在 BQ 中
在此步骤中,我们将关注两个用于测试函数使用的用例:情感分析 和 实体提取。
选择这两个用例的原因是我们已经在输入模拟数据集中创建了两个属性(review_type 和 product_review),可以用来测试函数结果。函数结果指的是验证 AI 生成的值在新属性中的准确性。
现在让我们展示生成每个用例的新属性的具体输入输出流程。
情感分析示例
情感分析的查询显示在下面的代码块中。
--Generate the positive/negative sentiment
SELECT
ml_generate_text_result['predictions'][0]['content'] AS review_sentiment,
* EXCEPT (ml_generate_text_status,ml_generate_text_result)
FROM
ML.GENERATE_TEXT(
MODEL `hair_shop.llm_model`,
(
SELECT
CONCAT('Extract the one word sentiment from product_review column. Possible values are positive/negative/neutral ', product_review) AS prompt,
*
FROM
`macro-dreamer-393017.hair_shop.product_review_table`
LIMIT 5
),
STRUCT(
0.1 AS temperature,
1 AS max_output_tokens,
0.1 AS top_p,
1 AS top_k));
查询的详细信息如下:
外部 SELECT 语句:
-
外部查询从 LLM 获取的新生成的字符串属性
review_sentiment通过将ML.GENERATE_TEXT函数应用于hair_shop.llm_model对象来选择。 -
此外,它还选择了输入数据集
hair_shop.product_review_table中的所有其他列。
内部 SELECT 语句:
-
内部查询语句选择了一个
CONCAT函数,并对每个来自输入数据集hair_shop.product_review_table的product_review应用特定的提示指令。 -
换句话说,提示指令引导模型从
product_review属性中提取一个单词的情感(积极、消极或中立)。
模型参数的 STRUCT:
-
temperature: 0.1- 较低的值 0.1 将导致更可预测的文本生成。 -
max_output_tokens: 1- 将模型的输出限制为 1 个标记(情感分析结果可以是积极的、消极的或中性的)。 -
top_p: 0.1影响下一个标记的可能性分布。 -
top_k: 1限制考虑的前几个标记的数量。
在触发所示查询后,对前五个结果进行了分析:

GENERATE_TEXT 函数的情感分析结果 [图片由作者提供]
从图像中可以看到,新生成的属性review_sentiment被添加到模拟数据集中。review_sentiment属性的值与review_type属性的值进行了比较,并且它们的记录匹配。
在情感分析结果之后,目标是识别每个情感背后的原因,即进行实体提取。
实体提取示例
实体提取分析的查询在下面的代码块中显示。
--Check what's the reason behind the positive/negative sentiment
SELECT
ml_generate_text_result['predictions'][0]['content'] AS generated_text,
* EXCEPT (ml_generate_text_status,ml_generate_text_result)
FROM
ML.GENERATE_TEXT(
MODEL `hair_shop.llm_model`,
(
SELECT
CONCAT('What is the reason for a review of the product? Possible options are: good/bad/average product quality, delivery delay, low/high price', product_review) AS prompt,
*
FROM
`macro-dreamer-393017.hair_shop.product_review_table`
LIMIT 5
),
STRUCT(
0.2 AS temperature,
20 AS max_output_tokens,
0.2 AS top_p,
5 AS top_k));
查询的分解与情感分析查询的分解类似,只是提示指令和模型参数值已调整为实体提取分析。
在触发所示查询后,对前五个结果进行了分析:

GENERATE_TEXT 函数的实体提取分析结果 [图片由作者提供]
review_reason属性的结果与product_review属性的值进行了比较。与之前的示例一样,结果匹配。
各位,这就是了。这个教程展示了如何仅通过使用 SQL 和提示文本从非结构化文本中创建新属性的两个用例。所有这些都借助 BigQuery 中新生成的 AI 功能完成。
现在,我们可以分享这篇博客文章的简要总结。
结论
这篇博客文章的目标是介绍 BigQuery 中新的GENERATE_TEXT ML 功能的使用。
通过此功能,SQL 导向的数据专业人士现在可以从直接存储在 BQ 数据仓库表中的非结构化文本中创建新的洞察(属性)。
换句话说,该功能使数据专业人士能够直接在 BigQuery 环境中利用NLP 机器学习任务。
此外,它弥合了使用 Python 开发新分析洞察的数据专业人士与那些更注重数据库的专业人士之间的知识差距。
最后,我将以一句话总结这篇博客文章:“每个人都可以使用 SQL 知识和良好的提示结构在 BigQuery 中进行编码和执行 NLP 分析。”
知识参考
-
[1] Google 文章《ML.GENERATE_TEXT 函数》,访问时间:2023 年 11 月 10 日,
cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-generate-text -
[2] Supermetrics 文章《Google Sheets 到 BigQuery:逐步指南》,访问时间:2023 年 11 月 22 日,
t.ly/ZZ2lN -
[3] Google 教程《使用 ML.GENERATE_TEXT 函数生成文本》,章节《创建连接》,访问时间:2023 年 11 月 10 日,
cloud.google.com/bigquery/docs/generate-text#generate_text -
[4] 作者的 Git 代码库:
github.com/CassandraOfTroy/bigquery-generate_text-AI_function/tree/main
数据团队面临的下一个大危机
数据团队比以往任何时候都更为重要 —— 但他们需要更贴*业务。以下是我们如何纠正航向的方法。
·
关注 发布于 Towards Data Science ·10 分钟阅读·2023 年 4 月 17 日
--
图片由 Daniel Lerman 提供,来自 Unsplash。
过去十年间,数据团队既像在水下又像乘风破浪。
我们一直在建设现代化的数据堆栈,像生命取决于它一样迁移到 Snowflake,投资无头 BI,并比你说反向 ETL 更快地扩展我们的团队。然而,我们大部分时间不知道这些工具是否真正为业务带来价值。
别误解我的意思:我坚信现代数据堆栈。当涉及到快速高效地生成分析时,云原生、基于 SQL 和模块化是最佳选择。然而,在如今紧张的预算和精简的团队面前,仅仅引用云的弹性和速度还不足以证明对这些工具的投资是合理的。

现代数据堆栈菜单。图片由 Matt Turck 和 Firstmark Capital 提供。
现在,当服务员递上账单时,CFO 不再毫不犹豫地掏出信用卡,而是逐项调查账单内容。
对于数据团队来说,账单已经到期。应该是机会的事情变成了危机,因为组织发现他们离业务还不够*,无法解释为何他们点了龙虾。
我们是如何到达这里的?
数据团队以几乎没有问题的方式首开历史,建立了 8 位数的技术堆栈。
在 2010 年代中期,数据领导者被赋予了“成为数据驱动”的任务——不管这意味着什么。当然,数据可以帮助优化成本、改善财务表现、指导产品路线图、提供出色的客户洞察并获得竞争优势。但“数据驱动”是一个模糊的目标,带有模糊的指标和不清晰的投资回报。
当然,数据团队并没有失控。在“增长不惜一切代价”的时代,火车全速前进。高管和决策者们看到谁在胜出——谷歌、Netflix 和亚马逊等公司——投资数据似乎是显而易见的选择。
我们这个新兴行业构建了堆栈,这些堆栈要么是在内部拼凑而成,要么是临时购买的,用以解决特定问题。无论这些系统——以及数据本身——是否与期望的、软件工程启发的五个九的可靠性相结合,通常都是事后考虑的。
在这个阶段,拥有数据往往就足以推动增长。有些数据有用,有些数据则没有,但至少我们拥有了它。到 2020 年,随着一切变得数字化并开始产生数据,疫情更是火上浇油。
像 Snowflake、Databricks 和 Fivetran 这样的技术出现,仿佛魔法一般,解决了许多“成为数据驱动”的问题。更快的洞察?没错!更简单的摄取?没错!更智能的模型?没错!
然而,最终这些解决方案开始将数据量与成本挂钩。快进到今天,你的数据每年快速增长,你面临的是 1000 倍的数据量和 1000 倍的成本。在这个市场中,这无疑是一颗难以下咽的药丸。
要想脱颖而出并证明我们的工作,提供数据是不够的。它还需要可靠且有明确目的。
换句话说,数据团队需要更接*业务。
那么我们该如何实现这一目标呢?我有一些想法。
通过了解人们来了解业务

谁不喜欢一只可爱的填充企鹅?照片由Andrea Gradilone拍摄,来源于Unsplash
你是否曾根据你知道的一个随机事实(例如,他们喜欢企鹅)为多年未见的人(例如,老朋友或远房亲戚)购买过礼物?
如果答案是肯定的,你绝对不是唯一的。数据团队就像一个远方的朋友送来的看似贴心的礼物一样,希望通过丰富的洞察来正确地对待他们的利益相关者,从而改善他们的工作并为业务带来价值。但你不能假装对数据有同理心。
如果你没有深刻理解消费者的需求,你的报告和分析就像是一只 5 英尺的填充企鹅一样没什么价值。
数据领导者在推动价值时应该做的第一件事是与他们的消费者和业务利益相关者交谈。这是显而易见的建议,但“需求收集”的任务通常被委托给分析师或嵌入式团队。这种做法有一定效果,但正如任何玩过传话游戏的人都能告诉你一样。
例如,Red Ventures 的数据工程总监 Brandon Beidel 每周与每个业务团队会面,以更好地了解他们的用例并制定信息充分的数据服务水*协议。
他说:“我总是用简单的业务术语来框定对话……我会问:
-
你如何使用这张表格?
-
你什么时候查看这些数据?你什么时候报告这些数据?这些数据需要是实时的、每小时的还是每天的?
-
这有什么用途?
-
如果数据延迟,谁需要被通知?”
这种实操型领导的优势是什么?你可以塑造参与度。
“如果有人告诉我数据很重要,但没人能告诉我数据是如何使用的,我也会提出质疑。对话变得更加复杂,我甚至会得到可以迅速转化为查询的描述,比如‘这一列中没有空值’,” Brandon 说。
你还可以像产品团队发起 NPS 调查一样,大规模调查你的数据消费者,这是一种 JetBlue 数据团队在最*的网络研讨会中讨论的策略。
创建异步反馈循环
你不能总是实时与每个人交谈。异步沟通和反馈循环对于数据和业务对齐(尤其是在今天的远程工作环境中)至关重要。
如果你没有一个广泛可访问且活跃的 Slack 频道用于这些类型的沟通,考虑立即创建这样一个沟通空间。这也是数据与分析总监 Priya Gupta 在快速增长的初创公司 Cribl 创建数据文化的关键之一。她说:
“我们的数据团队倾向于过度沟通,我们尽可能通过多种渠道进行交流。正如我们作为数据工程师会对那些文档不全的数据源心存疑虑一样,业务用户对沉默的数据团队也会天然感到怀疑。
类似 Slack 或 Teams 这样的聊天应用在这方面非常有帮助。我们创建了一个集中请求的频道,这有两个目的。它让整个分析团队能够看到请求情况,同时也让其他相关方了解他们的同行感兴趣的内容。
“你见过的最令人满意的事情就是当有人在你的公共 Slack 频道中提出请求时,另一个相关方回答了原本应该由你回答的问题。”
但也许最重要的反馈环节是你如何向消费者展示数据产品的可信度和可靠性。并非所有的数据产品都会或需要 100%可靠。
一旦你创建了自助服务或发现机制,进一步展示可靠性 SLA 以及产品满足该 SLA 的时间百分比。这正是Roche在其数据网格上构建数据产品时采用的策略。
站在数据使用者的角度思考

你可能甚至不需要完全站在他们的立场上……也许只需要几份仪表板的长度?照片由Jose Fontano提供,来源于Unsplash
如果你感到雄心勃勃,下一步就是从空谈转向实践。毕竟,业务相关方在数据方面不总是了解可能性。
类似于亨利·福特说消费者会要求他提供更快的马,有时候数据消费者只会要求一个更快的仪表板。我们需要开始生成自己的假设,确定需要关注的地方、需要寻找的内容以及如何应用这些洞见,而不是依赖业务相关方告诉我们什么重要。
为此,让数据团队的成员与相关方深入接触,体验他们的数据生活。
这是策略,由 Upside 的分析工程团队制定,经过高级数据分析工程师 Jack Willis 的经验 “…意识到很多为这个团队制作的[数据]产品在我实际看到他们的使用方式时并没有达到预期。”
他们的数据赋能框架包括三个步骤:完全融入团队中,与利益相关者一起规划,并培训团队以确保可持续的所有权。这是一种有效的方法,可以最大限度地发挥像分析工程师这样的新角色的价值,他们能够弥合数据工程师和分析师之间的差距。他们可以更深入地了解业务运作以及哪些数据真正能带来变化。
“我们在数据产品中建立了一条信任之路……我们的利益相关者不再害怕数据和工程,我们的数据从业者也不再害怕业务……这让我们进入了一个数据飞轮,”Jack 说道。
让采纳成为你的指南
你不总是需要生活在数据消费者的世界中才能理解他们的故事。这个故事也可以通过他们采纳的东西和不采纳的东西来讲述。实际上,“暗数据”和数据孤岛可能比被广泛采纳的数据产品更具信息性。

我们是否应该将路径移到那里?图片由作者提供。
如果V_GOOD_DASHBOARD_48是由你的业务运营团队导出到 Excel 中的版本,它比V_GOOD_DASHBOARD_49更有价值(即使知道你的 Looker 技能被低估可能会让你感到痛苦)。
在你从客户数据*台过渡到更具云原生的解决方案之前,了解营销团队如何使用它以及原因是很重要的。与提供强大的客户细分功能一样,直观的自助访问也可能同样有价值。
为此,数据团队需要投资于能够揭示谁在使用什么数据及其使用方式的方法和技术。
但现代数据堆栈本身并不会使数据团队对其数据有更大的可见性,也不会增加数据团队和利益相关者之间的透明度。如果有的话,现代数据堆栈可能通过提供更大的访问权限而带来更少的清晰度和背景,从而使这一已经脆弱的关系更加复杂。
我们需要一种方法,将上游表和下游资产连接起来,这种方法应该涵盖你数据环境的全部——不仅仅是数据仓库、数据湖或转换层。我们需要一种真正端到端的方法,一直到消费层。
在今天的时代,无知并非幸福,而是失败的根源。对我们的数据资产有可见性和透明度将帮助我们优先考虑,保持专注,并真正推动业务的进步。
创建一个语义层
我们的行业正在进步,正在对语义层进行规范化,且自助数据*台正在将更多权力交给分析师,使他们能够以前未曾有过的方式深入处理数据。
在没有进行深入讨论业务如何思考和使用数据的情况下,几乎不可能创建语义层,有时也称为指标层。
当你正在定义“账户”的普遍定义,并与业务利益相关者讨论是否应该包括免费用户时,你正在深入探讨并巩固你业务的真正驱动因素。
语义层可以成为你进行从未进行的讨论的绝佳借口,讨论你似乎应该知道的点。你的高管团队可能也有同感。他们也不真正理解“账户”是什么意思。
随着真正的语义层开始形成,帮助你开发和标准化北极星指标的解决方案,如增强分析*台或数据笔记本,可以提供一个很好的过渡方案。
专注于重要的短期胜利
处理临时请求、调查断裂的数据管道,以及回应财务部 Bob 提出的第五个问题都是快速胜利,但它们并不能实质性地推动进展。
另一方面,启动多年的公司范围计划在启动电话会议之前往往就注定失败了。大型“资本 P”项目(数据网格,如何?)虽然重要且值得,但它们不需要“完成”才有用。最好的选择是专注于具有明确业务价值的小型短期胜利。
再次强调,采纳应该是你的指导方针。将大部分资源集中在优化和构建关键用例和数据资产上(你知道你的关键数据资产吧?)。

理解关键资产的使用情况可以帮助你专注于对业务利益相关者真正重要的数据。图片由作者提供,来自内部数据*台。
想提高数据可信度?专注于一个与最大影响相关的数据小集合,例如客户行为或第三方营销数据。
-
按领域对数据进行分段,以便知道当出现问题时,哪个业务部分负责。
-
尽量减少在新鲜度、体量和模式检查(你知道,就是那些简单的)的时间,然后专注于编写自定义规则以捕捉分布或字段健康异常。
-
启动 Slack、Teams 或信鸽渠道,以便在数据出现问题时提醒所有相关方,并在数据血统的帮助下,提供受影响的报告和仪表板的建议。
缩小你的关注范围是否意味着你无法让所有人满意?是的。缩小关注范围是否意味着你在本季度会更成功?是的。
危机解除了吗?
解决我们下一个重大危机不会一蹴而就。像任何类型的变革一样,将我们的工作更紧密地与业务结合需要时间、无情的优先排序和洞察我们能创造最大价值的能力。
就我个人而言,随着我们采纳更多流程、技术和团队结构,将数据团队从风暴中引向*静的水域,我对未来的展望充满了兴奋。
那么,你的团队将如何应对我们下一个重大危机?我在倾听。
请联系 Barr 在 LinkedIn 上 倾诉你的挫折、发送数据梗图或分享你在应对这次危机中的经历。始终乐于交流。
下一步是负责任的人工智能。我们如何实现这一目标?
原文:
towardsdatascience.com/the-next-step-is-responsible-ai-how-do-we-get-there-ecce929a1c03
机器学习解决方案在我们的生活中占据了重要地位。现在不仅仅是关于性能,还有关于责任。
·发表于Towards Data Science ·阅读时间 12 分钟·2023 年 8 月 26 日
--

照片由Jude Infantini拍摄,发布在Unsplash
在过去几十年中,许多人工智能项目专注于模型效率和性能。结果记录在科学文章中,最佳性能的模型被部署在组织中。现在是将另一个重要部分纳入我们的人工智能系统的时候了;责任感。这些算法将继续存在,如今每个人都可以通过 chatGPT、co-pilot 和提示工程等工具接触到它们。现在更具挑战性的部分包括道德咨询、确保谨慎委托和告知利益相关者。这些实践共同促进了负责任和伦理的人工智能环境。在这篇博客文章中,我将描述在人工智能项目中责任感的含义,以及如何通过 6 个实用步骤将其融入项目中。
负责任的人工智能简要介绍。
在我深入探讨负责任的人工智能(rAI)之前,让我首先概述一下数据科学领域中采取的一些重要步骤。在之前的博客中,我写过关于数据科学中需要学习的内容 [1],以及数据科学产品如何可以增加收入、优化流程和降低(生产)成本。目前,许多部署的模型在性能和效率方面得到了优化。换句话说,模型应具有高预测准确性和低计算成本。然而,更高的模型性能通常会伴随模型复杂度的逐渐增加。一些模型被称为“黑箱模型”。例如,在图像识别和文本挖掘领域,神经网络使用特定的模型架构在数亿个参数上进行训练。理解这些模型为何做出特定决策变得困难甚至不可能。另一个例子是在金融领域,许多核心流程依赖于算法,机器每天都会做出决策。最重要的是,当需要时,这些机器做出的决策必须能够由人工进行事实核查和重新评估。
为了揭开黑箱模型,一个新的数据科学领域应运而生,这就是可解释人工智能,简称xAI。新的 xAI 算法阐明了模型的决策,并帮助研究人员和数据科学家解释模型为何做出特定的决策。让我们进入下一部分,看看为何负责任的行为至关重要!
新技术使人工智能变得更加可及。
尽管机器学习解决方案已经存在多年,但我们仍然处于一个新纪元的初期,这一纪元中算法被应用于各种过程和领域,从金融到医学。迄今为止,只有少数数据科学家创建、使用和部署这些模型。那些学习过数学/统计学和其他精确学科的人知道如何训练模型和编写编程代码。这种情况将发生剧烈变化,因为我们正处于一个几乎每个拥有互联网连接的人都可以访问各种算法的时代。技术如 chatGPT、co-pilot 和提示工程,加上生成性人工智能的发展,开始让复杂的数据科学世界变得可及。这意味着没有统计知识和编程技能的个人现在也可以创建模型,而不需要理解其具体工作原理。因此,我们需要制定一些规则,因为创建和部署机器学习模型不是一件可以轻视的事情[6]。
创建和部署机器学习模型不是一件可以轻视的事情!
如果我们回顾算法发展的历程,最初是数学家和统计学家创建了核心基础算法 (#1)。然后是学术机构的科学家们优化/改进和/或扩展了核心基础 (#2)。接着是科学程序员将包含数学方程式的科学文章翻译成应用程序 (#3)。仅仅十年前,所谓的“数据科学家”出现了,这基本上是前面三组的混合体,我们现在可以称之为基础数据科学家 (#4a)。随后,第二组数据科学家出现了;他们专注于应用,也被称为应用数据科学家 (#4b)。由于算法和方法需要被部署和维护,新的开发者群体,如(数据)工程师,也逐渐进入这个领域 (#5)。直到现在,处理复杂机器学习模型的群体仍然是专业的科学家、开发者和程序员。
很快,在数据科学领域将会出现一个新群体:那些没有统计学/编程经验但拥有足够技术知识,通过依赖副驾驶和类似 chatGPT 的技术来创建和部署人工智能产品的人。随着时间的推移,也许更多的非技术人员也会创建机器学习模型,因为基本上,你只需要一条连接互联网的线路和最新的技术。
我们正处于一个只需要互联网连接就能创建人工智能模型的阶段。
新的大型语言模型 (LLMs) 是一个技术和革命性的进步,但我们也需要考虑可能的风险和后果。如果我们回顾开发者的发展轨迹,可以清楚地看到,在每个步骤中,越来越多的人可以使用复杂的人工智能方法。然而,存在一个权衡:在每个步骤中,个人对底层算法和/或编程代码的具体工作机制变得越来越不知情。
在未来几年,数据和算法的使用可能会呈指数增长。我们正处于一个需要制定规则和最佳实践的阶段,以便负责任地使用最新的技术。
负责任的人工智能的六个部分。
当我们谈论负责任的人工智能 (rAI)时,这不仅仅是我们需要完成或关注的单一任务。与人工智能打交道时的责任意味着我们需要在项目的所有(非)技术步骤中仔细考虑我们所做的决定。作为数据科学家,我们可能需要在数据收集、模型训练、输出和可视化模型结果的过程中做出数十到数百个决策。然而,作为数据科学家的责任甚至在数据收集之前就开始了,因为你可能需要问这样一个问题:为什么需要使用选定的技术?它的目的是什么,它打算产生什么结果?
数据科学家的责任甚至在数据收集之前就开始了。
如果对这些问题的回答仍然关乎人工智能的必要性,那么我们需要保护基本权利,如隐私,包括治理,并注意数据输入质量、模型质量和输出质量。负责任的人工智能可以总结为六个部分,其中伦理贯穿所有部分:隐私、治理、输入质量、模型质量和输出质量。在接下来的章节中,我将更详细地描述这六个主题。

作者创建的图像。
1. 隐私。
在开始项目之前,首要步骤是关注隐私方面的问题。当我们谈论隐私时,它可以是个人数据,如年龄、性别等,也可以是其他机密信息,如政治和宗教偏好。还可能有间接信息,例如车牌号,可以与个人相关联。这些数据必须在整个项目过程中以及之后得到保护和保障。
然而,这并不意味着我们不能使用这种数据。我们可以在项目中使用此类信息,但需要仔细审查其影响,并确保它只能用于官方验证的任务。
使用个人信息需要仔细审查其影响,并确保它只能用于验证的任务。
我们应包括以下几点:
-
一般数据保护条例(GDPR)是否适用?
-
是否有官员参与,例如数据保护官、隐私顾问、信息安全官和首席信息官?
-
你如何考虑输入、模型和结果输出中的潜在不希望的偏见?偏见可能对对象、人物或群体产生假设。误解可能导致不公正的结果,应竭尽全力加以防止。
还有一些变体库可以帮助保护隐私,例如 Faker [2],它可以将真实的名字、地址和其他元信息更改为虚假的。
2. 治理。
如果谈论数据治理,我们需要知道谁是联络人、维护者或负责此数据集的人。这很重要,因为这个人可能会告诉我们更多关于数据集的所有权、可用性、质量、完整性和安全性的信息。除了数据治理之外,还有类似的模型治理,也需要解决类似的问题。那么,谁是联络人、维护者或负责已开发模型的人?因为我们还需要确保模型的质量、完整性和安全性。考虑这一点;我们可以创建一个符合所有规则并且表现出色的模型,但如果有人使用“错误”的输入数据,那么输出将不可信。例如,当数据集中意外缺少值时,或当特征的排序被更改时,也会出现这种情况。许多场景可能导致不可靠的结果。
结论是,需要制定流程来测试输入数据集和模型(输出)的合规性。不同的组织会有不同的流程,只需确保在 AI 系统实施之前已确定这些流程。
3. 数据输入质量。
输入数据集的良好质量对于创建可靠模型至关重要。这听起来可能很简单,但在数据科学项目中需要比现在更多的关注。在最佳情况下,数据集应该没有偏差、没有不准确、错误和其他类型的错误。在实际操作中,我们需要处理可用的数据集,并以最佳方式应对所有挑战。这意味着我们需要负责,并通过进行合理性检查、绘制图表并与数据所有者或领域专家讨论结果来仔细分析数据集的质量(参见治理)。
数据集的质量可以逐步检查,从向数据所有者或领域专家提出广泛的问题开始,然后进行更深入的技术分析。
-
输入(数据)是如何收集和结合的?
-
数据收集是否随时间发生了变化?考虑(新)传感器、环境变化、温度或不同的存储方式等
-
数据是如何标记的?
-
什么因素影响数据的质量?
技术性问题例如以下这些,但它们可能因用例和数据集的不同而有所不同:
-
缺失值的分布
-
检查样本重复
-
检查分类特征和连续特征
-
单变量分析(每个特征)
-
多变量分析以检查可能的相关性
在预检后,我们需要决定在样本数量、特征的可用性和质量的情况下,任务是否可能。请注意,这个质量步骤是在预处理步骤之前的,因为当我们开始预处理时,我们信任数据集并计划将其调整到最佳状态。可以在预检步骤中帮助的一个库是 HNET [4]。它将显示变量之间的关系,并让你探索任何多重共线性。更多细节可以在这里找到:
探索以理解你的数据可以决定一个项目是失败还是成功完成!
[towardsdatascience.com
4. 输出质量。
确保输出的可靠性是另一个需要我们关注的重要任务。输出的类型取决于使用场景,例如可以是仪表盘、建议报告或演示文稿。值得注意的是,我们的输入数据集可能是高质量的,我们的模型也可能经过可靠的训练,但输出仍然可能不可预测甚至不可用。
让我通过一个例子来解释这一点。假设我们创建了一个处理卫星图像并进行物体检测的模型。准确性在训练/测试和验证集上可能很好,但在部署后,输出可能会随着时间的推移而下降,甚至导致差的或不可用的预测。原因是冬季时,云层可能会遮挡视线,从而导致预测效果差或无法预测。
问题不仅是“模型是否有效”,还包括“模型何时有效”。
在我的例子中,建议是只在四月到十月期间使用经过训练的模型,以保持最可靠的预测结果。剩余的时间呢?你需要考虑其他方案。重要的是定期监控模型输出的正确性。
5. 模型质量。
模型质量可以通过其准确性、可靠性、可重复性和可解释性来解决。让我们逐一了解它们。
准确性
术语“准确性”经常与“性能”交织在一起,后者用来量化模型结果。然而,谈到准确性时,我不仅仅是指一个技术指标的分数。诸如 F1 得分这样的指标使用起来都不错,但它们只有在我们为数据和模型设定验收标准时才具有意义。如果没有验收标准,任何分数或任何模型都可能被视为有效,这样做没有意义。
对于每个新项目,我们需要首先确定验收标准。
值得注意的是,验收标准不仅仅是一个期望的分数。它还可以描述方法。例如,可能期望得到可解释的结果,其中输出需要是二元的。或者期望最佳表现的模型是三种测试模型中表现最好的。或者可能期望拥有最低的 F1 分数,因为只有这样模型才能有用,而不是传统的方法。
重要的是,验收标准要与数据和预期用途相匹配。
可靠性与可重复性
可靠性和可重复性是密切相关的,因为一个可靠的模型是在类似情况下能产生一致结果的模型。
要使模型变得可靠,我们需要确切知道训练过程中使用了哪些样本和特征,以及是否有部分数据被排除在学习过程之外。如果有,那么我们需要将解释和结论限制在那个特定的样本和/或特征集上。此外,为了构建一个可靠的模型,我们需要将训练/测试和验证集分开,以防数据泄漏。如果使用了(超)参数调优,可能甚至需要使用双重循环交叉验证。HGboost 库使用这种干预措施来防止数据泄漏并意外发现过拟合模型。更多细节请见:
## 使用贝叶斯超参数调优寻找最佳提升模型的指南,但不包括…
提升决策树算法可能优于其他模型,但过拟合是一个实际的危险。使用 HGBoost 库来拟合你的模型。
towardsdatascience.com
我们可以通过基于输入数据、给定的超参数和固定种子重建模型来检查项目是否具备可重复性。此外,我们还可以检查输出结果是否可以被重现。
可解释性
可解释性是指模型和结果是否足够清晰和易于开发人员解释。此外,设计选择必须清晰且逻辑上符合底层模型。除了技术部分,还期望有足够的文档来解释模型的操作方式。
6. 伦理。
道德应贯穿整个项目。关键是意识和诚信。此外,与相关方和用户保持透明,说明模型的局限性,并在开发和审核模型时运用常识总是很重要的。
负责任并不会占用你更多时间!
启动一个高责任感的项目可能需要(在开始时)一些额外的组织,因为你可能需要设立一些检查点并讨论每个人的角色。然而,如果所有检查点到位,它可以很容易地成为项目流程的一部分,而不会比常规项目多占用很多额外时间。此外,我预计(更有经验的)数据科学家已经处理了一些AI要点,这些要点在六个部分中有所描述。许多描述的部分,例如输入、模型和输出质量,并不是“新”的,只是可能需要一些额外的关注。
结束语。
在许多领域中,利用 AI 优化流程的机会有很多。有些会比其他的更有影响力,但使用 AI 时的基本原则应该是负责任。确保对于影响重大的项目,决策模型是透明和可解释的,人手需要能够进行事实核查、修正和调整。
保持安全,保持警觉。
敬上 E
如果你觉得这篇文章有帮助,欢迎 关注我 ,因为我会写更多关于数据科学的话题。如果你考虑购买 Medium 会员,可以通过使用我的 推荐链接来支持我的工作。这与一杯咖啡的价格相同,但允许你每月阅读无限的文章!
让我们联系吧!
参考资料
-
towardsdatascience.com/the-path-to-success-in-data-science-is-about-your-ability-to-learn-but-what-to-learn-92efe11e34bf -
E. Taskesen, 通过贝叶斯超参数调整找到最佳增强模型的指南, Medium 2022 年 8 月
-
E. Taskesen, 探索并理解你的数据与一个重要关联网络。 Medium 2021 年 8 月
-
Dan Hendrycks 等,灾难性 AI 风险概述, ArXiv, 2023 年。
恶名昭著的 XGBoost
回顾最受奖项青睐的机器学习算法之一
·
关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 5 月 20 日
--
XGBoost 的化身 — 作者 + OpenJourney
如果你是一名从事监督学习问题的数据科学家,你可能已经注意到 XGBoost 经常在你的排行榜上名列前茅。它的成功很大程度上归功于该算法在捕捉难以捉摸的非线性关系方面的卓越准确性。尽管现在还为时已晚,尚无法判断 XGBoost 是否会在各种最先进的通用模型涌现后幸存,但 XGBoost 的一贯可靠性促使我们回顾其背后的优雅数学。
XGBoost(极端梯度提升)是一种强大的基于树的集成技术,特别擅长完成分类和回归任务。它基于梯度提升机(GBM)算法,通过结合多个弱模型(在这种情况下是决策树)来形成更强的模型(Friedman, 2001)。XGBoost 的学习过程可以分解如下:
分解
目标函数
XGBoost 的基本原理是最小化目标函数。目标函数,表示为 Objective(T),结合了训练损失(对训练数据的评估)和正则化项(防止过拟合)。
Objective(T) = Loss + Regularization
XGBoost 的目标函数如下所示:
Objective(T) = ∑l(yi, y_pred,i) + ∑Ω(f)
其中:
-
T 代表决策树的集成
-
l(y, y_pred) 是一个可微分的凸损失函数,衡量真实输出 (y) 和预测输出 (y_pred) 之间的差异
-
yi 是真实输出,例如 i
-
y_pred,i 是实例 i 的预测输出
-
Ω(f) 是对集成中的每棵树 (f) 应用的正则化项 (T)
加法训练
XGBoost 以加法方式学习目标函数,创建一个迭代的决策树集成(弱学习器),逐步最小化目标函数。在每次迭代中,向集成中添加一棵新树,并优化目标函数。
为了形式化这一点,我们考虑以下内容:
Fm(x) = Fm-1(x) + fm(x)
其中:
-
Fm(x) 是添加了 m 棵树后的预测
-
Fm-1(x) 是预测值直到 m-1 棵树
-
fm(x) 是在 m-th 迭代中添加的新树
梯度和 Hessian 计算
为了最小化目标函数,XGBoost 使用梯度下降。在每次迭代中,计算损失函数相对于预测输出 (y_pred) 的一阶和二阶导数(梯度和 Hessian)。
Gradient(g): ∇l(y, y_pred) = d(l) / dy_pred
Hessian(h): ∇²l(y, y_pred) = d²(l) / dy_pred²
对于数据中的每个实例 (i),计算导数,得到梯度和 Hessian 值的向量 g 和 h。
树的构造
在 m-th 迭代中,使用计算得到的梯度和 Hessian,添加最小化目标函数的最佳树 (fm)。XGBoost 从空树开始,然后依次分裂每个叶子以最小化以下方程:
Gain = 1/2 * [Gl² / (Hl + λ) + Gr² / (Hr + λ) - G² / (H + λ)] - γ
其中,
-
Gl 和 Gr 是分裂后左侧和右侧区域的梯度之和
-
Hl 和 Hr 是分裂后左侧和右侧区域的 Hessian 之和
-
G = Gl + Gr,整个节点的梯度之和
-
H = Hl + Hr,整个节点的 Hessian 矩阵之和
-
λ,L2 正则化项
-
γ,进行分裂所需的最小损失减少(另一个正则化项)
增益方程结合了损失减少和正则化项,这有助于防止过拟合并在复杂度和预测能力之间做出最佳权衡。
超参数
learning_rate: (eta) 控制每次迭代时的更新步长,缩小每棵树的效果以防止过拟合。它是新添加到集成中的树的权重因子。
max_depth: 树的最大允许深度。随着深度的增加,模型变得更加复杂,可能会过拟合数据。
min_child_weight: (最小实例权重和 H) 进行树的分裂所需的最小 Hessian 值和。增加此值可以通过使树更为保守来防止过拟合。
lambda: 权重的 L2 正则化项,作为增益计算的一部分应用。帮助控制模型复杂性。
gamma: (min_split_loss) 进行进一步分裂所需的最小损失减少。控制树的生长和复杂性。
subsample: 每次提升轮次中从训练集中采样的比例。随机选择数据子集通过引入随机性来减少过拟合的可能性,从而提高集成过程的鲁棒性。
colsample_bytree: 每次提升轮次中选择的特征比例。随机选择列(特征)来构建相关性较小的树,防止过拟合。
实际上,这些超参数影响树的构建和增益计算(如 λ 和 γ),或者每次迭代中选择数据和特征的过程(如 subsample 和 colsample_bytree)。调整这些超参数有助于*衡模型复杂度和预测能力,提高性能,同时减轻过拟合。
简而言之,XGBoost 通过迭代地构建决策树的集成来学习,最小化由训练损失和正则化项组成的目标函数。它利用梯度下降来找到最佳树,采用损失函数的一阶和二阶导数。XGBoost 利用超参数,如最大深度、正则化参数和特征及实例的子采样策略,以防止过拟合并提高计算效率。值得注意的是,特别是子采样,通过在每次迭代中处理较少的数据点,引入了随机性和多样性,从而减少过拟合的机会并加快训练过程。
独特特征
陈天奇和古斯特林强调了几个使 XGBoost 与其他提升算法不同的独特特征,并提升了其性能(陈天奇和古斯特林,2016)。这些特征包括:
稀疏感知
XGBoost 旨在有效处理稀疏数据,这在包含缺失值或零值的现实世界数据集中很常见。XGBoost 使用一种考虑稀疏性的算法来为缺失值的数据点找到最佳分裂,从而提高了对稀疏数据的性能。
具体来说,XGBoost 在树构建过程中采用了默认(缺失值)方向。当特征值缺失时,算法会自动选择产生最高增益的方向,而不会对缺失值进行显式分裂。这种考虑稀疏性的方式使 XGBoost 高效,并减少了树构建所需的信息量。
正则化提升:
如前所述,XGBoost 在树构建过程中引入了正则化项(L1 和 L2),这有助于控制模型的复杂性并减少过拟合。这与缺乏正则化组件的传统 GBM 有一个关键区别。
列块(缓存感知)和并行学习:
XGBoost 支持树构建过程中的并行处理,使其能够利用多个处理器核心以加快学习速度。该算法按列排序数据,并以压缩形式存储在列块中。XGBoost 通过使用缓存感知算法预取列块,确保树构建过程中的高效内存访问,使其适合处理大数据集。
假设、优势和缺点
假设
简而言之,XGBoost 假设弱学习器(决策树)可以组合成一个更强大、更稳健的模型。它还假设目标函数是连续的、可微的和凸的。
优势
高性能:XGBoost 在分类和回归任务中始终取得最先进的结果。
可扩展性:XGBoost 高效利用内存和计算资源,使其适合大规模问题。
正则化:内置正则化项有助于防止过拟合。
稀疏意识:XGBoost 被设计为有效处理稀疏数据。
并行性:支持并行和分布式计算以加快学习速度。
缺点
计算复杂性:尽管 XGBoost 高效,但对于大数据集或大规模集成方法,它仍可能计算开销较大。
可解释性:尽管线性回归或单一决策树等参数化方法具有固有的可解释性,但像 XGBoost 这样的非参数集成方法需要额外的解释方法,如 Tree SHAP,以解释结果。
对超参数的敏感性:XGBoost 的性能受超参数的影响,通常需要微调以获得最佳结果。
对社会偏见、可信度和安全性的敏感性
像其他机器学习算法一样,XGBoost 的性能高度依赖于输入数据的质量。虽然算法本身可能不会表现出与算法偏见、可信度或安全漏洞相关的弱点,但这些问题可能由于偏见采样、不当应用或不公*解释模型结果而出现。
社会偏见
XGBoost 可能会无意中传播或甚至放大数据中存在的社会偏见。当训练数据反映出代表性不足、歧视或延续的刻板印象时,XGBoost 模型将不可避免地学习这些模式,可能导致有害的结果。确保数据中的代表性并解决社会偏见对于减少相关风险至关重要。
可信度
XGBoost 是一个决策树的集成,可能会导致复杂的模型,难以解释。这种缺乏透明度可能使利益相关者难以信任模型并理解其决策过程。像 Shapley 加法解释(SHAP)这样的方法已帮助减少“黑箱”问题,但解释性仍然是一个关注点(Lundberg 和 Lee 2017;Rudin 2019)。
安全性
机器学习模型,包括 XGBoost,可能会受到对抗性攻击、数据中毒或逆向工程的影响,这些可能暴露敏感信息(即去匿名化)或影响模型性能。确保数据集的安全性和保护模型免受恶意攻击对于维护系统的完整性和稳健性至关重要。此外,篡改或改变输入数据的来源可能导致误导性或不正确的预测,这引发了关于模型可信度的问题。
结论
XGBoost 是一个强大且多功能的机器学习算法,由于其卓越的性能、可扩展性和效率,它在排行榜上占据了主导地位。通过利用集成学习、梯度下降和正则化技术,XGBoost 克服了许多传统提升方法的局限,同时适应处理稀疏数据并优化计算资源。
然而,必须承认,任何机器学习模型,包括 XGBoost,相关的潜在风险依赖于算法的负责任使用。具体而言,通过仔细的数据预处理、通过解释性技术增强透明度和实施稳健的安全措施,可以帮助应对这些挑战,并确保 XGBoost 模型在实践和伦理上都符合要求。
采纳伦理原则和最佳实践可以让我们继续利用 XGBoost 和其他机器学习技术的力量,同时推动这些技术在未来带来公*和有益的成果。
参考文献
1. Chen T, Guestrin C. XGBoost:一种可扩展的树提升系统。发表于:第 22 届 ACM SIGKDD 国际知识发现与数据挖掘会议(KDD ‘16);2016 年 8 月 13–17 日;加州旧金山。纽约:计算机协会;2016 年。第 785–794 页。DOI: doi.org/10.1145/2939672.2939785。
2. Friedman JH. 贪婪的函数*似:一种梯度提升机。统计年鉴。2001 年;29(5):1189–1232。
3. Lundberg SM, Lee SI. 统一的模型预测解释方法。发表于:神经信息处理系统进展(NIPS 2017);2017 年 12 月 4–9 日;加州长滩。2017 年。
4. Rudin C. 请停止解释用于高风险决策的黑箱模型。arXiv:1811.10154。2019 年。
人工智能奥运会:机器学习系统的基准比赛
基准如何催生突破?
·
关注 发表在 走向数据科学 ·13 分钟阅读·2023 年 9 月 22 日
--
你无法改善你不测量的东西。 — 彼得·德鲁克

奥运五环。图片由作者创建。
《四分钟英里:重新定义跑步的基准》
多年来,跑完一英里在四分钟内被认为不仅是一个艰巨的挑战,而且被许多人视为一个不可能的壮举。这是一个心理和身体的基准,许多人认为是无法达到的。医生和体育专家理论认为,人体不可能以如此快速的速度跑那么长时间。这种信念根深蒂固,甚至有些人认为尝试这样做可能是致命的。
罗杰·班尼斯特爵士,一位英国中长跑运动员和医学生,持不同观点。他虽然认识到挑战,但认为障碍更多的是心理上的而非生理上的。班尼斯特采取了科学的方法进行训练,将一英里分成几个部分,并严格计时。他还采用了基于间歇训练的严格训练计划,并在尝试创纪录之前为自己设定了较小的基准。
1954 年 5 月 6 日,在英格兰牛津的一条跑道上,在朋友克里斯·布拉舍尔和克里斯·查塔维作为领跑者的帮助下,班尼斯特尝试突破四分钟障碍。他以 3 分钟 59.4 秒完成了一英里,打破了这一门槛,创造了历史。

罗杰·班尼斯特在比赛中奔跑。图片来源:挪威百科全书 (CC-BY 4.0)。
班尼斯特成就的后果是高度意外的。贡德·海格的 1945 年纪录(4 分钟 1.4 秒)保持了*十年才被班尼斯特打破。然而,一旦四分钟英里基准被突破,其他人很快跟随其后。在班尼斯特跑步后的 46 天,约翰·兰迪完成了一英里,时间为 3 分钟 57.9 秒。在接下来的十年里,这一纪录又被打破了 5 次。目前的纪录由希沙姆·埃尔·盖鲁吉创造,时间为 3 分钟 43.1 秒。
1900 年至 2000 年期间的世界纪录英里时间。注意 1945 年到 1954 年之间的间隔,直到罗杰·班尼斯特打破四分钟英里基准——否则,下降趋势几乎是线性的。图由作者创建。
班尼斯特的成就展示了基准的力量,不仅作为性能的衡量标准,也作为变革的激励。一旦四分钟的“基准”被打破,它重新定义了运动员认为可能的极限。这个障碍既存在于思想中,也存在于跑道上。
四分钟英里体现了基准在各学科中的变革力量。基准为特定任务的性能改进提供了量化的方法,让我们可以与他人进行比较。这是奥运会等体育赛事的全部基础。然而,基准只有在参与的社区能够确定共同目标时才有用。
在机器学习和计算机科学领域,基准测试是社区的奥林匹克——一个宏大的竞技场,在这里,算法、系统和方法论竞争的不是奖牌,而是进步的自豪感和创新的动力。正如运动员为了奥林匹克金牌而训练多年、争取毫秒级的进步一样,开发者和研究人员优化他们的模型和系统,以提高性能,力求在既定基准测试中超越对手。
基准测试的艺术与科学在于建立那个共同目标。这不仅仅是设定一个任务,而是确保它能够捕捉到现实世界挑战的本质,推动可能性的边界,同时保持相关性和适用性。选择不当的基准测试可能会使研究人员走入歧途,优化的任务无法在现实世界应用中带来改进。一个设计良好的基准测试可以引导整个社区向突破性进展迈进,重新定义一个领域。
因此,虽然基准测试是用于比较和竞争的工具,其真正的价值在于它们能够团结一个共同愿景的社区。就像班尼斯特的跑步不仅打破了纪录,还重新定义了运动潜力一样,一个构思良好的基准测试可以提升整个学科,改变范式并引领创新新时代。
在这篇文章中,我们将探讨基准测试在推进计算机科学和机器学习中的关键作用,通过回顾其历史,讨论基准测试机器学习系统的最新趋势,了解它如何在硬件领域推动创新。
基准测试计算系统:SPEC
在 1980 年代,随着个人计算机革命的兴起,对标准化指标以比较不同计算机系统性能的需求日益增长:一个 基准测试。在标准化基准测试出现之前,制造商通常会开发并使用他们自己定制的基准测试。这些基准测试往往突出了他们机器的优点,同时淡化了其缺点。显然,需要一个中立且被广泛接受的基准测试来进行比较。
为了解决这一挑战,系统性能评估合作组织(SPEC)应运而生。该组织的成员包括硬件供应商、研究人员以及其他致力于创建通用基准标准的利益相关者,主要用于对中央处理单元(CPUs),也称为‘芯片’进行基准测试。
SPEC 的首个重大贡献是SPEC89基准测试套件,它在行业标准 CPU 基准测试中开创了先河。SPEC 的基准测试聚焦于实际应用和计算任务,旨在提供对最终用户有意义的指标,而不是那些深奥或小众的测量。
然而,随着基准测试的发展,出现了一个有趣的现象:所谓的“基准效应”。随着 SPEC 基准测试成为测量 CPU 性能的黄金标准,CPU 设计师开始针对 SPEC 的基准测试优化他们的设计。实质上,由于行业已经将 SPEC 基准测试视为整体性能的衡量标准,制造商有强烈的动力确保他们的 CPU 在这些测试中表现出色——即使这可能意味着在非 SPEC 任务中牺牲性能。
这并非 SPEC 的初衷,导致了计算机科学社区内部的激烈辩论。这些基准测试是否真正代表了现实世界的性能?还是它们驱动了一种狭隘的视角,使得基准测试成为目的本身而不是实现目标的手段?
认识到这些挑战后,SPEC 多年来不断更新其基准测试,以保持领先并防止过度优化。他们的基准测试套件扩展到涵盖不同领域,从整数和浮点计算到更具体领域的任务,如图形、文件系统等。
SPEC 及其基准测试的故事强调了基准测试对整个行业方向的深远影响。这些基准测试不仅仅是衡量性能——它们还影响了性能。这证明了标准化的力量,但也提醒我们,当一个单一的指标成为优化的焦点时,可能会出现意想不到的后果。
今天,SPEC 基准测试以及其他基准测试继续在塑造计算机硬件行业和指导消费者及企业的购买决策中发挥至关重要的作用。
深度学习基准测试:ImageNet
在 2000 年代末,计算机视觉,一个专注于使机器能够解读和基于视觉数据做出决策的 AI 子领域,正面临进展缓慢的问题。传统技术有所进展,但在许多任务上已经达到性能*台期。当时的方法严重依赖于手工制作的特征,要求专家精心设计和选择每个任务的特定特征。这是一个繁琐且有许多限制的过程。
随后发布了ImageNet,这是由李飞飞博士及其团队发起的一个庞大的视觉数据库。ImageNet 提供了数百万张标记图像,涵盖了数千个类别。这个数据集的庞大规模前所未有,仅通过Amazon Mechanical Turk等云端方法进行众包数据标记才得以实现。ImageNet 是最早的数据集基准之一——自发布以来,ImageNet 的论文已被引用超过 50,000 次。

图像 Net 图像的视觉编译。图像来源:Gluon(CC-BY 4.0)。
但收集数据集只是开始。2010 年,ImageNet 大规模视觉识别挑战(ILSVRC)启动。挑战的目标简单,但规模庞大:将图像自动分类到 1,000 个类别中的一个。这个基准挑战将提供计算机视觉进步的客观衡量,超越了以往的尝试。
初期几年在传统方法上取得了渐进式的改进。然而,2012 年的挑战带来了变革性的转变。由亚历克斯·克里兹赫夫斯基、伊利亚·苏茨克弗和杰弗里·辛顿领导的多伦多大学团队推出了一种深度卷积神经网络(CNN),称为“AlexNet”。他们的模型实现了 15.3%的错误率,将前一年的错误率几乎削减了一半!

ImageNet 大规模视觉识别挑战的错误率。自 2012 年深度学习引入以来,准确率显著提高,并持续改善。人类的错误率约为 5%。图像来源:2018 NIH/RSNA/ACR/The Academy Workshop。此图像依据知识共享署名 4.0 国际许可协议(CC BY 4.0)进行复制。
什么使这一切成为可能?深度学习,特别是卷积神经网络(CNN),能够直接从原始像素中学习特征,省去了手动特征设计的需求。只要有足够的数据和计算能力,这些网络可以揭示出传统方法无法处理的复杂模式。
AlexNet 的成功是人工智能发展中的一个分水岭。2012 年后,深度学习方法主导了 ImageNet 挑战,驱动错误率不断降低。从基准测试中传达的明确信息是不容忽视的:深度学习,曾经是机器学习中的一个小众领域,现在正准备彻底改变计算机视觉。
而且它不仅仅是这样。ILSVRC 中的成功作为催化剂,将深度学习推到了计算机视觉以及从自然语言处理到游戏玩耍等众多 AI 领域的前沿。挑战突显了深度学习的潜力,吸引了研究人员、资金和关注。
通过设定一个明确且具有挑战性的基准,ImageNet 挑战在重新调整 AI 研究轨迹方面发挥了关键作用,促成了我们今天见证的深度学习驱动的 AI 复兴。
机器学习系统的基准测试:MLPerf
像 SPEC 和 ImageNet 这样的基准的变革性影响自然引发了一个问题:接下来是什么?随着深度学习模型变得越来越复杂,它们的计算需求也在增加。这将注意力转向了另一个关键组成部分——支撑这些模型的硬件。进入MLPerf。
MLPerf 作为一个涉及行业巨头和学术机构的合作努力出现,旨在创建一套标准的基准来测量机器学习硬件、软件和云*台的性能。正如名字所示,MLPerf 明确专注于机器学习,涵盖从图像分类到强化学习的广泛任务。目标明确——在一个“最佳性能”声明变得司空见惯但往往基于不一致标准或挑选指标的领域中提供清晰度。
MLPerf 的引入为科技行业提供了一个迫切需要的统一标准。对于学术界,它提供了明确的性能目标,促成了一个可以轻松测量和比较算法创新的环境。对于行业,尤其是硬件制造商,它既是挑战也是机会。新芯片不能再以模糊的机器学习性能声明推出——现在有了一个被普遍接受的基准,任何此类声明都将接受考验。
就像 SPEC 影响了 CPU 设计一样,MLPerf 开始塑造 AI 硬件的方向。公司们开始根据 MLPerf 基准优化他们的设计,这不仅仅关乎原始性能。基准测试还融入了效率指标,鼓励那些不仅提供速度而且具备能源效率的创新——在巨大的变换模型和环保意识的时代,这是一个迫切关注的问题。这些基准测试被像 Nvidia 和 AMD 这样的科技公司日常使用,以展示他们的新硬件。

Nvidia H100 在 MLPerf Inference v3.0 数据中心的标准化性能对比之前的 Nvidia A100 系统。如图所示,与前一代芯片相比,H100 在全尺寸大型语言模型 BERT 上的速度提高了 4 倍。图片来源:MLCommons和Nvidia Blogs。图片经 MLCommons 许可转载。
如今,有数十种类似 MLPerf 的基准测试,由MLCommons管理,包括:
-
MLPerf Training. 用于在训练机器学习模型时评估系统性能(与研究人员相关)。
-
MLPerf Inference. 用于在执行机器学习模型推理时评估系统性能(与通过云托管模型的公司相关)。MLPerf Inference 有多个版本,关注数据中心、移动设备、边缘设备和微型机器学习设备。
-
MLPerf Training HPC. 用于基准测试与高性能计算系统相关的工作负载。
-
MLPerf Storage. 用于基准测试与存储系统相关的工作负载。
但 MLPerf 并非没有批评者。正如任何获得显著关注的基准测试一样,存在对“过拟合”基准测试的担忧,即设计过度优化基准测试,可能以牺牲实际应用性为代价。此外,还面临着确保基准测试保持相关性的挑战,及时更新以反映机器学习领域的快速进展。
尽管如此,MLPerf 的故事,与其前辈一样,强调了一个基本真理:基准测试催化进步。它们不仅仅衡量最先进的技术;它们塑造它。通过设定明确而具有挑战性的目标,它们集中集体的精力,推动行业和研究社区开拓新领域。在一个人工智能不断重新定义可能性的世界里,拥有一个指引其复杂性的指南针不仅仅是可取的,而是必不可少的。
生成性 AI 的基准测试挑战
除了 AI 硬件,大型语言模型,一种生成性 AI,是基准测试工作的关键重点。更普遍地称为基础模型,这些比硬件或其他类型的机器学习模型更难以基准测试。
这是因为语言模型的成功不仅仅依赖于原始计算速度或在狭义任务中的准确性。相反,它取决于模型在各种提示和上下文中生成连贯、具有上下文相关性和信息性的回应的能力。此外,评估回应的“质量”本质上是主观的,并可能根据应用程序或评估者的偏见而有所不同。鉴于复杂性,像GPT-3或BERT这样的语言模型的基准必须比传统基准更加多样和多方面。
最著名的语言模型基准之一是通用语言理解评估(GLUE),该基准于 2018 年开发。GLUE 不仅仅是一个单一任务;它是一个包含九种多样化语言任务的集合,从情感分析到文本蕴含。其目的是提供全面的评估,确保模型不仅仅在一个任务上表现出色,而是真正能够在各种挑战中理解语言。
GLUE 的影响立竿见影。首次出现了一个明确、一致的基准,用于评估语言模型。不久之后,科技巨头和学术界也纷纷参与,每个参与者争夺 GLUE 排行榜的首位。
当GPT-2首次在 GLUE 基准上进行评估时,它取得了当时令人惊叹的成绩,超过了许多模型。这不仅证明了 GPT-2 的强大能力,还强调了 GLUE 在提供明确测量标准方面的价值。能够声称“在 GLUE 上达到最先进水*”成为了社区中备受追捧的认可。
然而,GLUE 的成功是把双刃剑。到 2019 年底,许多模型开始在 GLUE 的排行榜上达到饱和,分数接*人类基线。这种饱和突出了基准测试的另一个关键方面:基准需要随着领域的发展而进化。为了解决这个问题,同一团队推出了SuperGLUE,这是一个更为严格的基准,旨在进一步推动边界。
像GLUE、SuperGLUE和SQuAD这样的基准被用来评估模型在情感分析和问答等特定任务上的表现。但这些基准只是触及了基础模型旨在实现的目标的表面。除了任务特定的准确性,出现了其他维度来评估这些模型:
-
鲁棒性。 模型如何处理边缘情况或对抗性输入?鲁棒性基准测试通过设计旨在困惑或误导模型的输入来挑战模型,评估它们对恶意行为者或意外情况的抵御能力。
-
泛化能力和迁移学习。 基础模型应能在未被明确训练的任务上表现良好。评估模型的零样本或少样本学习能力,即在几乎没有先前示例的情况下完成任务,对于了解其灵活性和适应性至关重要。
-
交互性和连贯性。 对于聊天机器人或虚拟助手等应用,模型在长时间互动中的一致性和连贯性至关重要。该领域的基准测试可能涉及长对话或在多个交流中保持上下文。
-
安全性和可控性。 随着模型规模的增加,这些基准测试确保模型不会产生有害、不适当或无意义的输出是至关重要的。
-
可定制性。 随着基础模型的广泛应用,对其进行特定领域或应用的定制需求日益增加。该领域的基准测试可能会评估模型在新数据集上的微调能力或适应特定行业术语和细微差别的效果。
一个有趣的发展是,随着语言模型的表现越来越接*人类表现,历史上用于评估人类表现的测试现在也被用作语言模型的基准测试。例如,GPT-4 在 SAT、LSAT 和医学考试等考试中进行了测试。在 SAT 中,它得分 1410,排名全国前 6%。GPT-4 甚至能够通过所有版本的医学考试,*均得分为 80.7%。然而,在 LSAT 中,它的得分较低,分别为 148 和 157,位于第 37 和第 70 百分位。

GPT 在学术和专业考试中的表现。图源自“GPT-4 技术报告” 。图像来源:OpenAI (CC-BY 4.0)。
观察基准测试方法如何继续发展以应对语言模型在许多领域逐渐与人类表现相媲美甚至超越人类,将会很有趣。
基准测试的未来
基准测试的未来正在快速发展,日益多样化,以应对新兴技术和应用的广泛范围。以下是一些正在实施基准测试的新兴领域示例:
-
RobotPerf:随着机器人技术在我们日常生活中越来越多地集成,像 RobotPerf 这样的基准测试正在被设计用来专门衡量和加速机器人应用,确保机器在效率和安全标准上达到要求。
-
NeuroBench:在脑启发计算领域,NeuroBench 在评估类脑系统方面开创了先河,提供了这些架构在多大程度上模拟神经过程的见解。
-
XRBench:虚拟现实和增强现实领域随着 Meta 和 Apple 进入这一领域并推出新硬件而复苏。为此,XRBench 被开发以专注于扩展现实(XR)应用,这对于提供沉浸式和无缝的用户体验至关重要。
-
MAVBench:随着无人机在多智能体系统和电池技术进步下变得越来越具有商业相关性,像 MAVBench 这样的基准测试将在优化这些系统的性能方面发挥重要作用。
计算机科学和机器学习社区对基准测试在推动领域进步中的重要性深有认识。现在,连 NeurIPS,作为旗舰 AI 会议之一,也专门设置了一个 track,专注于数据集和基准测试。现在已进入第三年,这个 track 正在获得巨大的动力,今年单是提交的数量就接* 1,000 个。这一趋势突显出,随着技术不断前进,基准测试将继续在实时中指导和塑造其轨迹,正如过去所做的那样。
结语
基准测试在塑造进步方面的作用,无论是在体育还是人工智能领域,都不可低估。它们既是镜子,反映当前的状况,也是窗口,提供未来潜力的瞥见。随着人工智能继续影响从医疗保健到金融等各种应用和行业,拥有强大的基准测试变得至关重要。它们确保进展不仅迅速而且有意义,将努力引向真正重要的挑战。正如 Sir Roger Bannister 通过他的四分钟一英里向我们展示的那样,有时最令人畏惧的基准,一旦被征服,能够在未来几年释放出创新和灵感的波澜。在机器学习和计算的世界里,这场竞赛远未结束。
一页数据和分析模板
原文:
towardsdatascience.com/the-one-page-data-and-analytics-templates-f53b949be84
使用五个模板掌握数据和分析报告及流程
·发表于 Towards Data Science ·阅读时间 6 分钟·2023 年 3 月 2 日
--

图片由 micheile dot com 提供,来源于 Unsplash
介绍
在我职业生涯早期得到的最佳建议是为任何报告和开发流程创建模板。
尽管最初我发现创建模板耗时,但这一步骤在开发和规划前阶段为后续工作节省了大量时间。
这是我现在努力传授给所有同事和团队成员的知识。
我对模板化数据和分析报告及流程的原因主要有三个:
1: 可重用性 — 模板可以被重用并适应各种数据项目和流程。
-
作为数据负责人,您可以通过创建用于重复报告和可扩展流程的模板来节省时间。这可以包括各种模板——从利益相关者关于团队开发进展的报告到内部团队的会议记录和开发/测试流程。
-
作为数据专业人员,一旦您已经有了用于总结开发方法和分析见解的预定义模板,您在创建开发者/终端用户文档时可以变得更加高效。
2: 准确性 — 模板可以确保数据报告和流程包含组织和数据团队所需的所有相关信息。
- 通过对团队开发更新进行模板化,您可以减少报告错误,并确保跨组织和团队间对所有必要信息的轻松跟踪。
3: 一致性 — 模板可以确保数据流程和见解的一致性和清晰度。
- 通过预定义的数据模板,您的利益相关者将习惯现有的文档结构,并能快速找到所需的信息,即使是在新记录的流程和见解中。
基于这三个关键点,说明为什么需要模板化数据和分析流程及报告,我整理了一套可供你在数据团队和组织中使用的模板。
模板
以下列出的数据和分析模板用于团队组织和分享开发更新。对于每个模板,我将列出背景故事(开发原因)和使用范围。
#1: 数据和 分析 路线图模板:努力-价值矩阵

数据和分析路线图模板 [作者图片]
背景故事:
该模板在数据和分析团队刚刚成立时创建。其目的是分享和维护清晰的数据和分析愿景,与组织成为数据驱动型公司的愿景保持一致。
因此,创建了数据和分析路线图,将不同领域(数据工程、数据分析和数据科学)的计划用例映射到努力-价值矩阵中(艾森豪威尔矩阵)。
提供的模板有开发版本,即随着数据用例优先级的变化,模板也随之演变。
使用范围:
为数据和分析路线图开发的模板作为数据和分析团队的开发优先级或“快速胜利”的指引。
该模板还用于数据和分析团队的结构化和计划性增长,因为我们知道下一阶段的路线图交付需要多少人力资源。
此外,该模板帮助业务负责人轻松跟踪基于优先业务用例的分析开发阶段。
#2: 数据和 分析 复盘会议模板:每周跨团队报告

数据和分析复盘会议记录模板 [作者图片]
背景故事:
数据和分析每周复盘团队会议模板的创建旨在标准化跨组织和团队的公告及报告,涉及团队的Sprint任务。
除了改善团队间的沟通外,该模板旨在确保特定数据领域的信息和工作透明。
使用范围:
除了简化沟通外,该模板用于数据负责人跟踪团队进度并识别需要改进的领域。通过结构化的报告方式,可以轻松比较不同周的记录,并识别出随时间变化的趋势。
对于团队成员,该模板鼓励对自己的工作进行反思。此外,它提供了必要的信息和知识交流,关于同事们完成的工作(任务的定量和定性描述)。
#3: 数据和 分析 开发文档模板:用于解释开发和最终用户见解的文档模板

数据和分析开发及最终用户文档模板 [作者提供的图像]
背景故事:
创建开发和最终用户文档模板的目标是提高团队的效率(减少文档结构设计的时间),并通过以结构化的方式解释数据见解来为最终用户提供更多背景信息。
使用范围:
数据分析师/科学家使用开发文档模板以结构化的方式解释数据建模过程,即输入 → 输出 流程。
最终用户文档模板供利益相关者快速查找新创建的数据见解(仪表板和报告)的解释。因此,最终用户文档被结构化为详细说明主要仪表板元素:基础数据模型、时间范围方法、分析总结,以及仪表板回答的业务问题。
#4: 数据和 分析 状态更新模板:每月利益相关者报告

数据和分析月度状态更新模板 [作者提供的图像]
背景故事:
数据和分析月度状态更新模板的创建旨在向公司所有利益相关者总结不同业务领域(业务控制、物流、绩效营销)的开发更新。
使用范围:
数据负责人使用该模板以简单而结构化的方式向首席执行官和组织领导汇报数据和分析团队的成就。
此外,利益相关者使用该模板来规划新数据开发的业务行动。
#5: 数据和 分析 年终报告模板:开发亮点

数据和分析年终报告模板 [作者提供的图像]
背景故事:
年终数据和分析报告模板的创建旨在突出年度团队的成长和发展。
使用范围:
数据负责人使用该模板以简单而结构化的方式向首席执行官和组织领导汇报数据和分析团队的成就。
此外,这一页的报告模板可以用于制定来年的开发计划并展示未来的数据项目。
结论
本博客文章旨在展示五个一页式的数据和分析模板,这些模板可以被任何组织中的数据团队/数据负责人或数据利益相关者使用,以提高报告质量、信息共享和团队效率。
作为数据负责人,我发现一页式模板在我需要进行工作质量检查、提高团队协作以及使利益相关者更容易阅读报告并节省寻找所需信息的时间时,具有很大好处。
因此,我想与你分享这些模板。
我希望你觉得它们易于使用,并且能够帮助你按计划进行报告和团队管理。
如果你想了解如何组织和模板化你的完整知识库,请查看这里的帖子:
以系统化的方式创建外部、共享和内部文档
towardsdatascience.com
学习递归所需了解的唯一一件事
(以及 更多 )
·
关注 发布在 Towards Data Science ·9 分钟阅读·2023 年 4 月 14 日
--
照片由 Szabo Viktor 提供,发布在 Unsplash 上
有一句老生常谈的笑话:
要理解递归,你需要理解递归。
这是一个只有程序员、数学家或逻辑学家会觉得有趣的笑话。它也不是真的。
像许多其他事物一样,理解递归的唯一方法是练习。编写大量简单的递归函数。然后再写更多。再接着写一些更复杂的递归函数。依此类推。但你怎么做呢?我有一个小窍门。这是一本书。它很短(不到 200 页),引人入胜,并且会教会你比写递归更多的东西。它还会向你介绍计算机科学中一些最基础的概念,例如 Y 组合子、解释器、组合子和停机问题。
这本奇妙的小书叫做《小程序员》。¹ 在“前言”中,几乎在一开始,作者明确指出:
这本书的目标是教会读者递归思维。
如果你阅读了《小程序员》,递归将成为你思考问题的一种熟悉方式。但是,在此过程中,你会学到大量的基本理念。此外,这本书采用了非常独特的风格,迫使你解决其中的问题并吸收其概念。
接下来,我将简要概述什么是递归以及为什么你应该学习它。然后我将介绍这本书,并解释为什么它是一种出色的工具,可以让你成为更好的程序员。
注意:如果你是完全的初学者,并且对更抽象的主题不感兴趣,这本书可能不适合你。如果你处于想要学习编程并且希望有所展示的阶段,我的建议是等到你对某一种编程语言感到舒适时再回过头来看这本书。再者,如果你对编程的兴趣更偏向于哲学或理论,那么这可能是一个很好的起点。
递归:它为何重要
递归是令人畏惧的;“魔法”这个词经常被提及。对于初学者的建议很多。我曾读到有人建议初学者“相信魔法”——或者类似的说法。你可能会被告知学习调用栈的工作原理。当然,这很有用,甚至是基础的:但不是成为熟练甚至舒适使用递归的最佳方式。我知道这些建议对我没有真正奏效。真正奏效的是,你猜对了,练习。我可能不是递归的高手。但确实,现在我对使用递归更加游刃有余。事实上,我有时现在甚至会挣扎于不编写递归函数,即使我可以使用更简单、更高效的解决方案。
我怀疑人们在递归上遇到困难,是因为它某种程度上(错误地)暗示在幕后发生了无限回归。它似乎像是当两面镜子相对放置时发生的现象:第一面镜子反射第二面镜子的反射,第二面镜子反射第一面镜子的反射,依此类推……这是一种效果——顺便提一下——被许多艺术家和设计师巧妙地运用了。但这根本不是实际发生的情况。
递归的函数是一个调用自身的函数。例如,这里有两个(略有不同)函数,它们接受一个整数数组或列表并返回其和,分别用 Javascript 和 Python 编写:
function sumAllNums(listNums) {
if (listNums.length === 0) return 0;
return listNums.shift() + sumAllNums(listNums);
}
console.log(sumAllNums([1, 2, 3, 4]));
// 10
def sum_all_nums(lst):
if len(lst) == 0:
return 0
return lst[0] + sum_all_nums(lst[1:])
print(sum_all_nums([1, 2, 3, 4]))
# 10
两个函数都将:
-
检查输入是否为空,如果为空则返回 0(基本情况)
-
否则,它们将把输入的第一个项目添加到调用该函数时剩余输入的结果中(除了第一个项目之外的所有项目)(即递归调用)。
递归函数会无限调用自身,除非它达到了基本情况。当发生这种情况时,递归调用停止 — 这就是我们避免无限循环的原因。
这可能都很好,但我为什么要熟练掌握编写递归函数呢?毕竟,你可以用 while 循环中的迭代来完成任何可以递归编写的东西。为什么要为这个看似神奇的工具而挣扎?这是一个合理的问题。脱口而出,我能想到至少以下三个理由:
-
算法和数据结构:许多基础算法本质上是递归的(想想“分治”)。一些数据结构以递归方式表示最佳(想想图或树)
-
即使在机器学习领域,递归结构和算法也比人们想象的要常见(更不用说人工智能,它们无处不在)。例如,决策树的实现是本质上递归的。
-
函数式编程:有人说函数式语言的受欢迎程度正在慢慢增长。这可能是真的,也可能不是。确实,越来越多的“主流”语言如 Python 或 Javascript 现在支持函数式编程。此外,一些广泛使用的库和框架(如 React)在很大程度上基于函数式实践。鉴于函数式范式没有赋值的概念(稍后会详细介绍),递归对它至关重要。
-
理解:递归迫使你将问题简化为其他更小的问题。这是你作为程序员可能发展出的最有价值的技能之一。
不幸的是,我想不到一个专门用于练习递归的全面资源。这就是The Little Schemer的作用所在。
学习递归的最佳书籍

我的 TLS 版本 — 还有一杯浓缩咖啡(我来自意大利!)[作者提供的图片]
根据标题,《The Little Schemer》(以下简称 TLS)应该是一本关于编程语言 Scheme 的书,它是组成 LISPs 家族的众多语言之一。那么为什么有人会特别想学习 LISP 或 Scheme 呢?问题是,标题并不准确。Scheme 是一种非常适合教授计算机科学某些基础知识的语言²——对于 TLS 来说,你需要的语法是如此稀疏,你可以在几分钟内迅速掌握。你可以更安全地将其视为一种教学工具,而不是一门成熟的语言³。
说到教学法:这本书采用了奇特的风格。它以两列的形式书写,类似于学生和老师之间的苏格拉底式对话。它是这样开始的:
这是否是一个原子? 原子
它不给你定义——它向读者提出问题(通常以练习的形式),然后提供答案(或解决方案)。这需要一些适应,但非常有效。

TLS 的奇特风格 [作者提供的图片]
递归与这一切有什么关系?好吧,在 TLS 中,作者使用 Scheme 的方式不支持变量赋值或迭代。没有 while 或 for 循环。这意味着,如果你必须编写一个函数,例如检查某个数字是否包含在列表或数组中,你不能像在 Python 中那样做:
def is_contained(num, list_nums):
for ix in range(len(list_nums)):
if list_nums[ix] == num:
return True
return False
print(is_contained(1, [2, 3, 4, 1]))
# True
相反,你必须以递归的方式思考:
-
有一个基本情况,即列表将是空的,函数应该返回 False
-
否则,你选择第一个列表项并检查它是否等于给定的数字;如果匹配,函数返回 True。如果不匹配,(递归地)调用函数处理列表的其余部分。
这就是你在 TLS 中编写所有内容的方式。Scheme 仅在两个基本的语法元素上操作:
-
原子:一个或多个字母数字字符
-
列表:零个或多个被括号包围的原子或列表。
要编写如上所述的函数,你只需要:
-
一个编程构造来测试两个表达式是否相同
-
另一个测试,用于检查列表是否空
-
选择器 用于列表的第一个元素和列表的其余部分
-
表达条件(例如
if)的一种方式
对于更复杂的函数,你还需要一个构造器来构建列表。加上一些定义函数的方法,仅此而已。这就是为什么几乎所有的语法都在第一章中介绍,在不到 10 页的简洁内容中(唯一的例外是列表构建构造器 cons,它在第三章中介绍)。前三章带你了解许多更简单的递归函数。
当你到达第四章时,你应该已经对编写操作列表的递归函数的基本设计模式有所了解:例如上面提到的函数,它们检查特定项是否是列表的成员。或者,替换列表中给定项的第一个(或所有)出现的函数。
随后,这些知识应用于从基本原理构建基本的算术操作。同样,这通过对数字进行递归以及使用提供的原语(这些原语将数字递增和递减 1)来实现。例如,两个数字n和m的加法定义为:
-
基本情况:如果m为零,返回n
-
否则,递归返回将加法函数应用于递增的n和递减的m的结果。
第五章和第六章介绍了对任意深度嵌套列表的递归函数:这些列表可能包含列表,而这些列表又可能包含列表,依此类推。基本上,你需要重写前几章中的所有函数,以便它们在“更深”的情况下也能工作。
这些知识在第六章中得到了具体应用,你将在其中编写一个用于算术表达式的解释器。让我强调:我们已经阅读了 100 页的内容。我们从没有 Scheme 知识和最简语法开始——而现在我们正在构建一个评估我们定义的操作的解释器。如果你对编程的更理论方面感兴趣,你可能会发现这非常有价值和激动人心。
第七章专注于集合论构造、关系和函数,这些内容同样留给读者在阅读师生对话的过程中发现。
第八章可能是本书中最令人不满意的章节:它相当陡峭,而且很难看出这里引入的一些构造的意义。其关键似乎是引入了延续,但我必须承认我在这一章中感到困难(显然,许多人也有同样的感受)。
与前一章类似,第九章是具有挑战性的:在几页中介绍了许多基础概念,这些概念引出了对应用顺序 Y 组合子的讨论。我非常喜欢这一章,但我花了很多次尝试和一些在线资源的帮助(见下文)才理解它。顺便说一下,如果你曾经想知道 Y 组合子到底是什么(既然我们讨论递归的话题):它是(非常粗略地说)在没有函数命名方式的语言中如何定义递归(因此,这种语言中的所有函数都是匿名的——例如 Python 中的lambda函数)。能够理解 Y 组合子是一次丰富的经历,对我来说,它是本书的亮点之一。
最后一章将所有内容整合在一起,你最终会用 Scheme 编写一个 Scheme 解释器 在 Scheme 中。让它沉淀下来:只用几个原语,仅用递归,你正在通过引导语言本身来编写一个语言的解释器。
我希望我能够传达我在阅读 TLS 时的兴奋感。也希望你能与我分享你的经历。
额外资源
-
我有一个 GitHub 仓库(正在进行中!),在这里我发布了笔记、解决方案和一个相当全面的 Racket 测试套件,链接在此
-
另一个我喜欢的 GitHub 仓库(同样是 Racket):
github.com/bmitc/the-little-scheme -
对这本书的一个负面(但不完全不相关)评论:
inventwithpython.com/blog/2018/12/09/book-review-the-little-schemer/)以*衡我的热情 -
如果你遇到困难,关于 Y 组合子的一个优秀博客文章
[1] Friedman, Daniel, P, Felleisen, Matthias [《小 Scheme》], 麻省理工学院出版社,剑桥,MA,1986 年,1996 年第四版 (mitpress.mit.edu/9780262560993/the-little-schemer/)。最初出版为 《小 LISPer》。
[2] 事实上,《计算机程序的结构与解释》是有史以来最受推崇的计算机科学书籍之一,结构与解释计算机程序 使用了 Scheme。
[3] 这并不重要,但我应该提到我在 TLS 中的练习实现不是用 Scheme,而是用 Racket,它基本上是 Scheme 的继任者。
理解回归树所需的唯一指南
原文:
towardsdatascience.com/the-only-guide-you-need-to-understand-regression-trees-4964992a07a8
关于决策树的完整指南,包括从零开始的逐步实现和使用 Scikit-Learn 的动手示例
·发表于 Towards Data Science ·阅读时长 25 分钟·2023 年 4 月 4 日
--

构建一棵树 - 作者提供的图像
目录
-
简介
-
回归决策树:背后的理论
-
从理论到实践——从零开始的决策树
-
动手示例——从零开始实现与 Scikit-learn 决策树对比
-
总结
-
参考文献
-
附录 / 代码
1. 简介
决策树自 1960 年代以来一直存在。尽管它们是最简单的机器学习算法之一,但在解决问题时被证明非常有效。它们最大的优势之一是易于解释,使得那些没有技术背景的人也能轻松理解。在许多行业中,数据科学家仍需建立对机器学习用例的信任。像决策树这样的可解释基准模型可以帮助减少一些怀疑。如果有人愿意付出努力,他们甚至可以追踪学到的树的分支,并尝试找到他们已经知道的关于问题的模式。
另一方面,我们很快就会遇到简单决策树在复杂问题上的极限。从理论上讲,我们可以用适当大小的树来建模任何(复杂的)数据分布,但这些模型在应用于新数据时往往无法很好地泛化——它们对训练数据集过拟合。然而,决策树在机器学习中始终发挥着重要作用。
决策树的一些弱点已经随着树集成技术的进步逐渐得到解决或至少得到缓解。在树集成中,我们不是学习一棵决策树,而是学习一系列树,并最终将它们组合成一个集成。如今我们区分了包装(bagging)和提升(boosting)算法。
-
在袋装中,多个决策树在不同的自助样本(随机选择的带有替代的子集)上进行训练。每棵决策树是独立训练的,最终的预测是通过*均所有个体树的预测来得出的。袋装方法,特别是随机森林算法是由 Leo Breiman 开发的。
-
在提升中,决策树是按顺序训练的,每棵树都被训练以纠正前一棵树所犯的错误。训练数据被加权,对前一棵树错误分类的样本给予更高的权重。
尽管随机森林仍然发挥着重要作用,但今天主要是提升算法在数据科学竞赛中表现最佳,且常常优于袋装方法。最知名的提升算法包括AdaBoost、XGBoost、LightGBM 和 CatBoost。自 2016 年以来,它们的受欢迎程度持续增长。

基于树的算法类型 — 图片由作者提供
虽然决策树的概念已经被认识并积极应用了几十年,但提升方法相对“新颖”,只有在 2014 年 XGBoost 发布后才逐渐获得重要性。

基于树的算法的演变 — 图片由作者提供(灵感来源于(Chow, 2021; Swalin, 2019))
在 XGBoost 概念首次发布后的几个月,Higgs Boson Challenge 在 Kaggle 上用它赢得了比赛。XGBoost 基于许多概念,这些概念汇聚成一个极其有效的算法。XGBoost 的核心当然是梯度提升原理,但 XGBoost 远不止于此。XGBoost 包括各种优化技术,使得 XGBoost 在训练过程中极其高效和快速。特别是对于小到中等规模的结构化数据集,像 XGBoost、LightGBM 和 CatBoost 这样的梯度提升框架继续发挥重要作用。
这不仅仅是我的观点。一个好的指标是 Kaggle 竞赛及其获胜解决方案。
在文章《竞争性机器学习的现状》中, mlcontests.com 评估了 2022 年在 Kaggle 和其他竞赛*台上的 200 多个数据竞赛。根据报告,梯度提升决策树(GBDT)仍然是 2022 年表格数据用例的首选方法,并且能够赢得大多数此类领域的竞赛。(Carlens, n.d.)
除了梯度提升算法一再展示出的良好性能外,决策树或树集成的最大优势是速度。一般而言,梯度提升框架在训练速度上比神经网络更快,这在许多现实世界的问题中可能是一个重要因素。
通常在机器学习项目开始时数据集并不明确。工作的重要部分是数据集的汇编和相关特征的提取。如果我们更改数据集、添加新列或仅稍微更改将分类值转换为数值的方式,我们需要衡量这样做是否改善了整体过程。在这个过程中,我们可能会训练几百次模型。因此,更快的训练时间可以决定性地影响整个机器学习用例的开发过程时间。

机器学习项目是迭代的,而不是线性的 — 图片来源:作者
下图显示了机器学习管道中的各个步骤。如果我们在训练模型之前对过程中的某个小细节进行更改,我们必须重新评估整个过程和结果模型。

机器学习管道 — 图片来源:作者
文章内容:
本文旨在奠定基础,深入探讨各种基于决策树的树型集成算法。决策树的概念非常直观,易于理解。乍一看,XGBoost、CatBoost 和 LightGBM 看起来复杂一些。但如果你仔细观察,XGBoost 只是不同概念的组合,而这些概念本身又很容易理解。
一旦你理解了随机森林和梯度提升框架,你将能够解决各种数据科学问题。从分类到回归再到异常检测。
知识关于深度学习框架如 Pytorch、TensorFlow 等在几乎所有数据科学职位中扮演如此核心的角色,这有些荒谬。在许多领域,你将花费大部分时间来收集数据、准备数据和提取特征。如果你有了合适的特征集,模型的创建本身通常是相当简单的。如果你主要处理表格数据,你可能会通过袋装和提升算法走得很远。
如果你想一次性下载文章中使用的代码并作为参考,你可以在 github 上找到使用的代码片段。你也可以在附录中找到我们将在本文中构建的决策树算法的代码,位于文章底部。
2. 回归的决策树:背后的理论
决策树是最简单的机器学习算法之一。它们的工作方式相对容易解释。
作为人类,我们尝试通过将复杂问题分解为相对简单的是或否决策来解决问题。当我们想买一辆新车时,我们浏览所有能找到的汽车网站。过一段时间后,我们对某款车的价格有了感觉。我们感觉到奢侈品牌和便宜制造商之间的成本差异有多大,以及与较小的 100 hp 引擎相比,150 hp 引擎的额外费用有多少,等等。
一步步地,我们的大脑记住了某些特征组合的球 park 值。当我们停在汽车经销商那里,逐一查看汽车的特征时,就像是在决策树上向下移动,直到我们认为得到了一个公*的价格。

一个简单的回归树预测汽车价格 — 图像由作者提供
注意: 在我们深入了解决策树的构建之前,需要提到有不同的决策树算法。一些流行的算法包括 ID3、C4.5、C5.0 和 CART(Google Developers, 2017)。Scikit-learn 的实现基于 CART,该算法由 Leo Breiman 等人于 1984 年首次发布。Leo Breiman 是一位美国统计学家,他塑造了“bagging”的方法,开发了随机森林,从而对基于树的算法的进一步发展做出了重要贡献。
我们如何开始构建决策树?
要开始决策树构建过程,我们需要回答三个问题:
-
我们从哪个特征开始? - 我们在每个节点沿一个维度划分数据集。在这个例子中,我们使用特征 x_1 进行划分。由于我们不想仅仅选择随机特征,我们会提前搜索出划分数据集带来最大增益的特征。(在这种情况下,我们通常谈到所谓的信息增益。我们可以以不同的方式定义信息增益,在回归中我们通常使用*方误差)。
-
最佳的阈值来划分数据是什么? - 类似于第一步,当我们选择一个特征时,我们仍然需要知道我们想用什么阈值来划分数据集。换句话说,就是在维度上的哪个位置我们想要划分数据集。
-
我们什么时候停止划分数据集? - 如果我们在某个点不停止划分过程,决策树将继续进行,直到每个叶子节点中只有一个样本点。为了避免过拟合,我们需要一些标准来决定划分数据集的深度以及何时停止划分过程,以避免模型变得不必要的复杂。

我们必须回答的三个问题 — 图像由作者提供
我们再次使用汽车价格预测的例子。首先,我们需要选择一个特征并分割数据集。我们选择一个特征和一个阈值,将数据集分割成左侧和右侧部分,并计算*均价格。这给了我们第一个节点。如果我们现在停止,我们将拥有一个只有一个层级的简约决策树——所谓的决策桩。
然而,我们不想从随机分割开始,而是从“最佳可能”的分割开始。
但是,如何定义“最佳”分割呢?
我们需要定义一个度量标准,帮助我们评估分割的效果。
在回归问题中,常用的损失函数有 *均绝对误差 或 均方误差。通常,我们可以在不同的度量标准之间进行选择。要了解 scikit-learn 是如何计算每次分割的性能的,我们可以直接查看文档或源代码。
访问源代码的最简单方法是通过代码编辑器。
如果你还没有安装 scikit,你可以通过以下 pip 命令进行安装:
pip install scikit-learn
我在大多数项目中使用 Visual Studio Code。如果我创建一个新的笔记本或 Python 文件并导入相应的模块,Visual Studio 会提供一个直接链接到其背后的源代码。在左侧的图片中,你可以看到 Visual Studio Code 中的整个情况。

作者截图
-
创建一个新文件,在我的例子中是“tree_algorithms.py”,并导入回归树模块“sklearn.tree.DecisionTreeRegressor”。
-
按下“Ctrl”并点击相应模块,你将直接跳转到源代码的相应部分。
你也可以在 scikit-learn 的文档中找到源代码。右侧可以看到在 scikit-learn.org 上的整个情况。每个类和函数还有一个指向 Github 上源代码的链接。
如果我们深入到 DecisionTreeRegressor 的源代码中,我们会看到它被定义为一个用以下值初始化的类:
def __init__(
self,
*,
criterion="squared_error",
splitter="best",
max_depth=None,
min_samples_split=2,
min_samples_leaf=1,
min_weight_fraction_leaf=0.0,
max_features=None,
random_state=None,
max_leaf_nodes=None,
min_impurity_decrease=0.0,
ccp_alpha=0.0,
):
我们将逐渐深入了解每个超参数的作用。我们一开始感兴趣的是分割标准,即决策树在构建过程中如何确定如何分割数据集。

作者截图
代码中还包含了我们可以选择的标准的简短描述。
Scikit-learn 让我们可以在以下选项中选择:
-
squared_error
-
friedmann_mse
-
绝对误差
-
泊松
默认值是“squared_error”。文档中将其描述如下:
“squared_error” 等同于方差减少,并使用每个终端节点的均值来最小化 L2 损失
因此,我们尝试在终端节点中最小化 均方误差。
假设我们有一个简单的二维数据集,只有 x_1 作为唯一的输入特征和 y 作为目标变量。对于这个简单的例子,我们不需要决定哪个特征最适合分割数据集,因为数据集中只有一个特征。因此,在根节点,我们使用 x_1 将数据集分成两半。
在下图中,您可以找到一个简单的二维数据集。数据集的两个部分是我们的子节点。在我们进行第一次分裂时,这两个子节点是叶节点或终端节点(不会进一步分裂的节点)。
在所示的情况下,我们在 x_1=2 处分割数据集。
如果我们用它来进行预测,树现在会预测什么值?
我们必须为每个终端节点定义一个值,这样就代表了决策树的可能预测值。我们以最简单的方式计算这个预测值 y_pred,我们计算左节点的*均值 (这里: y_pred_left = 9) 和右节点的*均值 (这里: y_pred_right = 5)。

分割数据集 — 图片由作者提供
我如何找到最佳的分裂数据集的阈值?
在所示的示例中,我们选择了 x_1 = 2 作为阈值。但这是否是最优情况?
为了评估分裂的性能,我们计算残差,即 y_predict 与每个样本的 y 之间的差异。更准确地说,我们计算 L2 损失函数,即残差的*方和。

如何计算左叶子和右叶子的预测值 — 图片由作者提供
为了得到桩模型的性能值,我们分别计算两侧的偏差(l2 损失),然后通过包括两半样本的数量来计算加权总体损失。
我们反复进行这个过程,尝试不同的阈值(见图片)。在我们的例子中,当我们选择 x_1 = 5 作为分裂阈值时,加权*方误差最小:

父节点和子节点的 MSE 计算 — 图片由作者提供
我们的算法如何找到最小误差?
决策树以非常简单的方式执行这一操作,它定义了一种迭代方法,尝试不同的阈值。因此,我们定义了一个可能的阈值/分裂值列表,并计算列表中每个可能阈值的均方误差。
-
步骤 1 - 定义可能的分裂阈值列表: 我们通过排序值并计算滚动*均值来定义所有可能的分裂值。因此,如果 x_1 = 2 和 x_2 = 3,那么可能阈值列表中的第一个阈值是 2.5。
-
步骤 2: 在下一步中,我们需要找到最小化*方误差的阈值来构建节点。我们开始迭代所有阈值,拆分数据集,并计算右节点和左节点的 MSE。

不同阈值下均方误差的迭代计算 — 图片由作者提供
让我们用实际数据集来尝试一下。
加载现实世界的数据集
为了在实际数据集上演示刚才描述的步骤,我们从 UCIMachineLearning 下载了汽车数据集。该数据集包含许多属性,如马力、尺寸、燃料类型等,用于详细描述汽车。我们感兴趣的目标变量是价格。(UCI Machine Learning Repository: 汽车数据集,无日期。)[许可证: CC0: 公共领域]
def load_auto_data_set():
# Load the automobile data set from UCI.edu
url = '<https://archive.ics.uci.edu/ml/machine-learning-databases/autos/imports-85.data>'
df = pd.read_csv(url, header=None)
# Name columns
df.columns = ['symboling', 'normalized_losses', 'make', 'fuel_type', 'aspiration', 'num_doors', 'body_style', 'drive_wheels', 'engine_location','wheel_base','length','width','height', 'curb_weight','engine_type','num_cylinders','engine_size','fuel_system','bore','stroke', 'compression_ratio','horsepower','peak_rpm','city_mpg','highway_mpg','price']
# Filter for lines where power and price are available
df = df[(df.horsepower != '?')]
df = df[(df.price != '?')]
# Filter for lines where power and price are available
df['horsepower'] = df['horsepower'].astype(int)
df['price'] = df['price'].astype(int)
# Define the last column of the data frame as y and the rest as X
self.y = self.df.iloc[:, -1]
self.X = self.df.iloc[:, :-1]
return df, X, y
之后,我们执行刚才描述的步骤。以下代码片段使用所选特征selected_feature和定义的阈值threshold来分割数据集(X_parent, y_parent)。
该图展示了左子节点和右子节点的样本及观察值的*均值。如果我们现在停止,子节点将成为树的叶子节点,树的预测值将由两个部分的计算均值表示。
class NodePlot():
def __init__(self, X_parent, y_parent, threshold, selected_feature):
self.selected_feature = selected_feature
self.x_column = X_parent[self.selected_feature]
self.y_parent = y_parent
self.data_set = np.column_stack((self.x_column, y_parent))
self.threshold = threshold
# define a list with all observations of the left and right leaf
self.left_y = self.data_set[self.data_set[:, 0]<self.threshold][:, 1]
self.left_x = self.data_set[self.data_set[:, 0]<self.threshold][:, 0]
self.right_y = self.data_set[self.data_set[:, 0]>=self.threshold][:, 1]
self.right_x = self.data_set[self.data_set[:, 0]>=self.threshold][:, 0]
# calculate the mean of the observations for the left and right leaf
self.parent_y_mean = np.mean(self.y_parent)
self.left_y_mean = np.mean(self.left_y)
self.right_y_mean = np.mean(self.right_y)
# calculate the weighted mean squared error
self.parent_mse = np.mean((y_parent - self.parent_y_mean)**2)
mse_l = np.mean((self.left_y - self.left_y_mean)**2)
mse_r = np.mean((self.right_y - self.right_y_mean)**2)
# calculate the number of instances in the parent and child nodes
n_l = len(self.left_y)
n_r = len(self.right_y)
n = len(self.data_set)
# calculate the weighted mse for child nodes
self.child_mse = (n_l/n) * mse_l + (n_r/n) * mse_r
def plot_split(self):
plt.rcParams['font.size'] = '16'
sns.set_style("darkgrid", {"axes.facecolor": ".9"})
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=self.left_x,
y=self.left_y,
mode="markers",
name="Data set: left node",
line=dict(color="grey")
)
)
fig.add_trace(
go.Scatter(
x=self.left_x,
y=np.linspace(self.left_y_mean, self.left_y_mean, len(self.left_x)),
mode="lines",
name="Right node prediction",
line=dict(color="black")
)
)
# create go.scatter plot with black line
fig.add_trace(
go.Scatter(
x=self.right_x,
y=self.right_y,
mode="markers",
name="Data set: right node",
#line=dict(color="#ffe476")
line=dict(color="black")
)
)
fig.add_trace(
go.Scatter(
x=self.right_x,
y=np.linspace(self.right_y_mean, self.right_y_mean, len(self.right_x)),
mode="lines",
name="Left node prediction",
line=dict(color="black", dash='dot')
)
)
fig.add_trace(
go.Scatter(
x=[self.threshold, self.threshold],
y=[min(self.y_parent), max(self.y_parent)],
mode="lines",
name="MSE of parent node",
line=dict(color="black", dash='dashdot')
)
)
# update title in go.Figure
fig.update_layout(title="Data set", xaxis_title=self.selected_feature, yaxis_title=self.y_parent.name)
fig.show()

图片由作者提供
由于我们不想在数据集中的任何地方进行分割,而是选择“最佳”点,因此我们按上述描述进行迭代。我们使用节点绘图类来计算多个阈值的残差。
selected_feature = "horsepower"
list_of_mse_childs = []
list_of_mse_parent = []
thresholds = X.sort_values(by=["horsepower"])["horsepower"].unique()
for threshold in thresholds:
NodePlot = helper_functions.NodePlot(
X_parent = X,
y_parent = y,
threshold = threshold,
selected_feature = "horsepower"
)
list_of_mse_childs.append(NodePlot.child_mse)
list_of_mse_parent.append(NodePlot.parent_mse)
def plot_threshold_evaluation(thresholds, mse_parent_list, mse_list):
# create figure
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=thresholds,
y=mse_list,
mode="lines",
name="MSE after split",
line=dict(color="black")
)
)
fig.add_trace(
go.Scatter(
x=thresholds,
y=mse_parent_list,
mode="lines",
name="MSE of parent node",
line=dict(color="black", dash='dot')
)
)
fig.add_trace(
go.Scatter(
x=[threshold,threshold],
y=[min(mse_list), max(mse_list)],
mode="lines",
name="Chosen threshold",
line=dict(color="black", dash='dashdot')
)
)
# update title in go.Figure
fig.update_layout(title="Evaluate", yaxis_title='MSE')
fig.show()
return fig
# plot the just calculated MSE values for different thresholds
plot_threshold_evaluation(
thresholds = thresholds,
mse_parent_list = list_of_mse_parent,
mse_list = list_of_mse_childs,
threshold = 100
)

图片由作者提供
我们计算每个可能分割的父节点和子节点的*方误差。对于决策树,我们寻求最大信息增益。使用*方误差作为分割标准,我们简单地将信息增益计算为父节点的 MSE 与子节点加权 MSE 之间的差异。
在图表中,信息增益的最大值出现在 120 小时。

图片由作者提供
在决策树中,我们不断重复刚才描述的步骤。
注意:决策树被归类为贪心算法。贪心算法在选择过程中逐步选择承诺最佳结果的状态。做出决策时不考虑之前和之后的决策。
程序什么时候结束?
决策树在训练模型时不会做很多假设。例如,线性回归恰恰相反,当线性回归算法训练模型时,它仅允许模型具有一种可能的形状,即空间中的直线或*面。因此,当我们使用线性回归作为学习算法时,我们直接假设我们的问题遵循线性行为。

参数模型(线性回归)与非参数模型(回归树)— 作者提供的图像
决策树在学习过程中非常灵活。这些模型被称为“非参数模型”。当模型的参数数量没有提前确定时,就称为非参数模型。线性回归有一个明确定义的参数数量,即斜率和偏移量。这显著限制了训练过程中的自由度。(Géron,2022)
因此,决策树往往容易过拟合。为了避免这种情况,我们需要引入限制训练过程自由度的超参数,即所谓的正则化超参数。
一个常用的正则化参数是 max_depth,即树的最大深度。其他的包括:
-
min_samples_split(节点需要的最小样本数,以便进行拆分)
-
min_samples_leaf(每个叶子节点必须具有的最小样本数)
-
min_weight_fraction_leaf(类似于 min_samples_leaf,但我们定义的是整个数据集的一个比例,而不是一个具体的数字)
-
max_leaf_nodes(叶子节点的最大数量)
-
max_features(在每次分裂时评估的最大特征数量)。
在实际模型构建后,我们仍然可以修剪树,以避免模型不必要的复杂性。
什么是剪枝?
这项技术包括将树生长到其全部大小,然后删除那些对验证数据集的准确性没有提升的分支或子树。这是通过计算修剪子树前后的误差变化,并与一个阈值进行比较来完成的。如果误差变化不显著,则会修剪子树。由于我暂时不想深入探讨这个问题,我将在以下简单示例中省略它。
接下来,我将向你展示如何从头开始构建一个基本版本的回归树。
3. 从理论到实践 - 从头开始构建决策树
为了灵活使用回归树,我们将代码放入一个新的模块中。我们创建一个新的 Python 文件,将所有与我们算法和学习过程相关的代码放入其中。在这个文件中,我们定义一个名为“RegressionTree”的新类,并用作为训练过程约束的超参数进行初始化。
如前所述,处理决策树的最大挑战之一是过拟合的风险。为了降低这一风险,并确保我们的模型能够很好地推广到新数据,我们引入了正则化参数,这些参数在某个点指导和停止学习过程。
我们在简化版决策树中使用的正则化参数(或停止准则)有以下两个:
min_samples_split
- 定义一个节点需要的最大样本数量,以便进一步拆分。合适的值取决于数据集的类型和大小。如果选择得当,它可以防止过拟合。在 scikit-learn 中,默认值设置为 2 个样本。
max_depth
- 最大深度决定了树最多可以有多少层。如果其他停止准则如 min_sample_split 在达到此深度之前阻止了树的进一步生长,则可能无法达到此树的大小。Scikit-learn 默认将值设置为“None”,因此默认情况下最大深度没有限制。
Scikit-learn 还包括一些额外的停止参数,如 min_samples_leaf, min_weighted_fraction, max_leaf_nodes, or max_features,这些参数默认为未设置,我暂时会忽略它们。
我们为每个回归器需要的一个函数是 fit 函数,它启动训练过程。输入变量是一个多维数组(X),包含输入特征。y 是一个一维数组,描述目标变量。
除了我们的回归器(RegressionTree)外,我们还定义了一个第二个类(Node),通过它设置和存储树的每个节点所具有的参数。
class Node():
def __init__(
self,
feature=None,
threshold=None,
left=None,
right=None,
value=None
):
self.feature = feature
self.threshold = threshold
self.left = left
self.right = right
self.value = value # is it a leave node?
def is_leaf_node(self):
return self.value is not None
class RegressionTree():
def __init__(
self,
min_samples_split=2,
max_depth=100):
self.min_samples_split = min_samples_split
self.max_depth = max_depth
self.root = None
def fit(self, X, y):
self.root = self._grow_tree(X, y)
fit 函数使用辅助函数 _grow_tree(x, y) 一步步地生长树,直到达到停止准则中的一个。
在分割节点之前,我们检查是否满足任何一个停止准则。在简化的示例中,我们只有两个停止准则:
(1) depth >= self.max_depth: 是否达到树的最大深度?
(2) n_samples < self.min_samples_split: 节点中的样本数是否大于 min_samples_split?
如果任一条件为真,则该节点是终端节点,我们需要计算的唯一内容是均值(np.mean(y))。
如果两个条件都不为真,我们将进一步分割数据集。我们首先定义考虑哪些特征进行分割。在我们简化的情况下,我们不会限制用于分割的列。我们使用 X 中的所有特征(feat_idxs)。
对于实际的分割,我们定义了另一个辅助函数 _best_split,我们将正在查看的节点的 x 和 y 值传递给它。
我会稍后详细介绍 _best_split 的作用,但正如名称所示,_best_split 会以所选特征(best_features)和我们分割数据集的阈值(best_threshold)的形式返回“最佳”分割。
我们使用这些信息来实际分割数据集并将其作为我们树的一个节点存储。
在我们跳出函数之前,我们会再次调用_grow_tree来处理分支的两个部分。
def _grow_tree(self, X, y, depth=0):
# check the stopping criteria
n_samples, n_feats = X.shape
if (depth>=self.max_depth or n_samples<self.min_samples_split):
leaf_value = np.mean(y)
return Node(value=leaf_value)
feat_idxs = np.random.choice(n_feats, n_feats, replace=False)
# find the best split
best_thresh, best_feature = self._best_split(X, y, feat_idxs)
# create child nodes
left_idxs, right_idxs = self._split(X[:, best_feature], best_thresh)
left = self._grow_tree(X[left_idxs, :], y[left_idxs], depth+1)
right = self._grow_tree(X[right_idxs, :], y[right_idxs], depth+1)
return Node(best_feature, best_thresh, left, right)
唯一未解答的问题是算法如何确定最佳的分割。
如前所述,我们计算所谓的信息增益。在我们的例子中,我们将信息增益定义为均方误差的减少。节点本身及其结果子节点的误差或残差是通过计算每个节点中目标变量 y 的*均值与节点中样本的实际 y 值之间的差异来得到的。
该函数逐个遍历每个特征。
-
我们为每个特征计算一组可能的阈值,作为所有观察值的移动*均值。
-
然后,我们遍历列表中的每个阈值,拆分数据集,并计算子节点的加权均方误差。
-
然后,我们检查计算得到的 MSE 是否是迄今为止计算出的最小 MSE,如果是的话,我们将 feature_idx 和 threshold 保存为最优(在best_feature_idxs和best_thresholds中)。
def _best_split(self, X, y, feat_idxs):
y_mean = np.mean(y)
residuals_y = (y - y_mean)**2
y_mse = np.mean(residuals_y)
best_feature_ixd, best_threshold = None, None
lowest_mse = y_mse
for feat_idx in feat_idxs:
# define possible thresholds for the split
X_column = X[:, feat_idx]
thresholds = np.convolve(np.sort(X_column), np.ones(2)/2, mode='valid')
for threshold in thresholds:
# getting the left and right nodes
left_idxs, right_idxs = self._split(X_column, threshold)
# calculate the weighted avg. mse of children
n = len(y)
n_l, n_r = len(left_idxs), len(right_idxs)
mse_l = self._squared_error(y[left_idxs])
mse_r = self._squared_error(y[right_idxs])
child_mse = (n_l/n) * mse_l + (n_r/n) * mse_r
if lowest_mse > child_mse:
lowest_mse = child_mse
best_feature_ixd = feat_idx
best_threshold = threshold
return best_feature_ixd, best_threshold
我们在上述几个部分中已经多次使用的两个函数是_split和_squared_error。
def _split(self, X_column, split_thresh):
left_idxs = np.argwhere(X_column <= split_thresh).flatten()
right_idxs = np.argwhere(X_column > split_thresh).flatten()
return left_idxs, right_idxs
def _squared_error(self, y):
# calculate the mean value for all observations
y_mean = np.mean(y)
# calculate the residuals to y_mean
mean_squared_error = np.mean((y - y_mean)**2)
return mean_squared_error
唯一需要的就是predict()函数。为此,我们使用_traverse_tree。
使用循环函数,我们逐个遍历刚构建的树。如果到达叶子节点,_traverse_tree返回存储的节点值。
def predict(self, X):
return np.array([self._traverse_tree(x) for x in X])
def _traverse_tree(self, x, node):
if node.is_leaf_node():
return node.value
if x[node.feature] <= node.threshold:
return self._traverse_tree(x, node.left)
return self._traverse_tree(x, node.right)
就这样,完整的决策树回归器定义为:
import numpy as np
from collections import Counter
from sklearn.metrics import mean_squared_error
from collections import Counter
class Node():
def __init__(
self,
feature=None,
threshold=None,
left=None,
right=None,
value=None
):
self.feature = feature
self.threshold = threshold
self.left = left
self.right = right
self.value = value # is it a leave node?
def is_leaf_node(self):
return self.value is not None
class RegressionTree():
def __init__(
self,
min_samples_split=2,
max_depth=100):
self.min_samples_split = min_samples_split
self.max_depth = max_depth
self.root = None
def fit(self, X, y):
self.root = self._grow_tree(X, y)
def _grow_tree(self, X, y, depth=0):
# check the stopping criteria
n_samples, n_feats = X.shape
if (depth>=self.max_depth or n_samples<self.min_samples_split):
leaf_value = np.mean(y)
return Node(value=leaf_value)
feat_idxs = np.random.choice(n_feats, n_feats, replace=False)
# find the best split
best_feature_ixd, best_threshold = self._best_split(X, y, feat_idxs)
# create child nodes
left_idxs, right_idxs = self._split(X[:, best_feature_ixd], best_threshold)
left = self._grow_tree(X[left_idxs, :], y[left_idxs], depth+1)
right = self._grow_tree(X[right_idxs, :], y[right_idxs], depth+1)
return Node(best_feature_ixd, best_threshold, left, right)
def _best_split(self, X, y, feat_idxs):
y_mean = np.mean(y)
residuals_y = (y - y_mean)**2
y_mse = np.mean(residuals_y)
best_feature_ixd, best_threshold = None, None
lowest_mse = y_mse
for feat_idx in feat_idxs:
# define possible thresholds for the split
X_column = X[:, feat_idx]
thresholds = np.convolve(np.sort(X_column), np.ones(2)/2, mode='valid')
for threshold in thresholds:
# getting the left and right nodes
left_idxs, right_idxs = self._split(X_column, threshold)
# calculate the weighted avg. mse of children
n = len(y)
n_l, n_r = len(left_idxs), len(right_idxs)
mse_l = self._squared_error(y[left_idxs])
mse_r = self._squared_error(y[right_idxs])
child_mse = (n_l/n) * mse_l + (n_r/n) * mse_r
if lowest_mse > child_mse:
lowest_mse = child_mse
best_feature_ixd = feat_idx
best_threshold = threshold
return best_feature_ixd, best_threshold
def _split(self, X_column, split_thresh):
left_idxs = np.argwhere(X_column <= split_thresh).flatten()
right_idxs = np.argwhere(X_column > split_thresh).flatten()
return left_idxs, right_idxs
def _squared_error(self, y):
# calculate the mean value for all observations
y_mean = np.mean(y)
# calculate the residuals to y_mean
mean_squared_error = np.mean((y - y_mean)**2)
return mean_squared_error
def predict(self, X):
return np.array([self._traverse_tree(x, self.root) for x in X])
def _traverse_tree(self, x, node):
if node.is_leaf_node():
return node.value
if x[node.feature] <= node.threshold:
return self._traverse_tree(x, node.left)
return self._traverse_tree(x, node.right)
4. 实战示例 — 从头实现与 Scikit-learn 决策树
加载数据集
对于测试,我们使用之前作为示例的数据集,即汽车数据集。首先,我们从 uci.edu 加载数据集。然后,我们选择一些属性进行第一次简单测试。在以下示例中,我选择:
-
车轮基距
-
长度
-
宽度
-
高度
-
制造商
对于输入向量 X。
由于属性“make”包含字符串,我们使用 OneHot Encoding 将其转换为数值特征。
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
def load_auto_data_set():
# Load the automobile data set from UCI.edu
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/autos/imports-85.data'
df = pd.read_csv(url, header=None)
# Name columns
df.columns = [
'symboling', 'normalized_losses', 'make',
'fuel_type', 'aspiration', 'num_doors',
'body_style', 'drive_wheels', 'engine_location',
'wheel_base','length','width','height',
'curb_weight','engine_type','num_cylinders',
'engine_size','fuel_system','bore','stroke',
'compression_ratio','horsepower','peak_rpm',
'city_mpg','highway_mpg','price'
]
# Filter for lines where power and price are available
df = df[(df.horsepower != '?')]
df = df[(df.price != '?')]
df = df.reset_index()
# Filter for lines where power and price are available
df['horsepower'] = df['horsepower'].astype(int)
df['price'] = df['price'].astype(int)
# Define the last column of the data frame as y and the rest as X
y = df.iloc[:, -1]
X = df.iloc[:, :-1]
return df, X, y
df, X, y = load_auto_data_set()
from sklearn.preprocessing import OneHotEncoder
X_selected = X[["wheel_base", "length", "width","height"]].reset_index()
# define and fit the OneHotEncoder
ohe = OneHotEncoder()
ohe.fit(df[['make']])
# transform the "make" column
make_one_hot_sklearn = pd.DataFrame(ohe.transform(df[["make"]]).toarray(), columns=ohe.categories_[0])
X = X_selected.join(make_one_hot_sklearn)
X = np.array(X)
y = np.array(y)
训练模型
在定义输入和输出变量后,我们尝试用我们的算法进行第一次测试,以查看我们是否可以实际用于预测。
首先,我们将数据集拆分为训练集和测试集。
import tree_algorithms
from sklearn.metrics import mean_squared_error, mean_absolute_error
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
regr = tree_algorithms.RegressionTree(min_samples_split=5, max_depth=20)
regr.fit(X_train, y_train)
y_pred = regr.predict(X_test)
# calculate mean squared error
print(f"Mean Squared Error: {round(mean_squared_error(y_test, y_pred), 1)}")

我们的“从头开始的决策树”的 MSE — 作者截图
与现有的 scikit-learn 库进行比较
为了比较,我们现在尝试使用 scikit-learn 的"DecisionTreeRegressor"库。在这种情况下,我们训练的模型表现完全一样。我不会在这里探讨结果是好是坏。如果你想知道如何调整回归模型并找到表现最佳的模型,你可以在我以前的文章中找到更详细的适用方法解释(这里 或 这里)。
from sklearn.datasets import load_diabetes
from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error
regr = DecisionTreeRegressor(min_samples_split=5, max_depth=20)
regr.fit(X_train, y_train)
y_pred = regr.predict(X_test)
# calculate mean squared error
print(f"Mean Squared Error: {round(mean_squared_error(y_test, y_pred), 1)}")

scikit-learn 决策树的 MSE — 作者截图
5. 总结
决策树是许多出色算法的基础,如随机森林、XGBoost、LightGBM 和 CatBoost。它们背后的概念非常直观,通常易于理解,至少当你尝试逐一理解各个子概念时。通过这篇文章,你已经通过理解每个树集成算法的核心——决策树,迈出了良好的一步。
我计划发布更多关于使梯度提升框架如此高效的每个概念的文章。
喜欢这个故事吗?
-
如果你喜欢阅读并想了解更多关于机器学习的概念、算法和应用,你可以查看 我所有相关的文章列表。
-
如果你不想错过新文章,你可以 免费订阅 以便在我发布新故事时收到通知。
-
成为 Medium 会员,阅读更多来自其他作者和我的故事。你可以通过使用我的 推荐链接 来支持我。你不会额外花费任何费用,我将获得佣金。
如果你有任何问题,请随时通过 LinkedIn 联系我!
6. 参考文献
Carlens, H. (无日期). 竞争性机器学习的现状. ML 竞赛。检索日期 2023 年 3 月 17 日,来自 https://mlcontests.com/state-of-competitive-machine-learning-2022/archive.ics.uci.edu/ml/datasets/automobile
Chow, R. (2021 年 8 月 31 日). 决策树与随机森林算法:决策驱动因素. 数据科学史。 https://www.historyofdatascience.com/decision-tree-and-random-forest-algorithms-decision-drivers/
Géron, A. (2022). 使用 Scikit-Learn、Keras 和 TensorFlow 的实践机器学习:构建智能系统的概念、工具和技术 (第三版)。 O’Reilly Media, Inc.
Google Developers (导演). (2017 年 9 月 13 日). 从头开始编写决策树分类器——机器学习食谱第 8 集. https://www.youtube.com/watch?v=LDRbO9a6XPU
Swalin, A. (2019 年 6 月 11 日). CatBoost vs. Light GBM vs. XGBoost. Medium. https://towardsdatascience.com/catboost-vs-light-gbm-vs-xgboost-5f93620723db
UCI 机器学习库:汽车数据集. (无日期). 取自 2023 年 2 月 24 日, 来源 https://archive.ics.uci.edu/ml/datasets/automobile
7. 附录
- 从头开始的回归树
import numpy as np
from collections import Counter
from sklearn.metrics import mean_squared_error
from collections import Counter
class Node():
def __init__(
self,
feature=None,
threshold=None,
left=None,
right=None,
value=None
):
self.feature = feature
self.threshold = threshold
self.left = left
self.right = right
self.value = value # is it a leave node?
def is_leaf_node(self):
return self.value is not None
class RegressionTree():
def __init__(
self,
min_samples_split=2,
max_depth=100):
self.min_samples_split = min_samples_split
self.max_depth = max_depth
self.root = None
def fit(self, X, y):
self.root = self._grow_tree(X, y)
def _grow_tree(self, X, y, depth=0):
# check the stopping criteria
n_samples, n_feats = X.shape
if (depth>=self.max_depth or n_samples<self.min_samples_split):
leaf_value = np.mean(y)
return Node(value=leaf_value)
feat_idxs = np.random.choice(n_feats, n_feats, replace=False)
# find the best split
best_feature_ixd, best_threshold = self._best_split(X, y, feat_idxs)
# create child nodes
left_idxs, right_idxs = self._split(X[:, best_feature_ixd], best_threshold)
left = self._grow_tree(X[left_idxs, :], y[left_idxs], depth+1)
right = self._grow_tree(X[right_idxs, :], y[right_idxs], depth+1)
return Node(best_feature_ixd, best_threshold, left, right)
def _best_split(self, X, y, feat_idxs):
y_mean = np.mean(y)
residuals_y = (y - y_mean)**2
y_mse = np.mean(residuals_y)
best_feature_ixd, best_threshold = None, None
lowest_mse = y_mse
for feat_idx in feat_idxs:
# define possible thresholds for the split
X_column = X[:, feat_idx]
thresholds = np.convolve(np.sort(X_column), np.ones(2)/2, mode='valid')
for threshold in thresholds:
# getting the left and right nodes
left_idxs, right_idxs = self._split(X_column, threshold)
# calculate the weighted avg. mse of children
n = len(y)
n_l, n_r = len(left_idxs), len(right_idxs)
mse_l = self._squared_error(y[left_idxs])
mse_r = self._squared_error(y[right_idxs])
child_mse = (n_l/n) * mse_l + (n_r/n) * mse_r
if lowest_mse > child_mse:
lowest_mse = child_mse
best_feature_ixd = feat_idx
best_threshold = threshold
return best_feature_ixd, best_threshold
def _split(self, X_column, split_thresh):
left_idxs = np.argwhere(X_column <= split_thresh).flatten()
right_idxs = np.argwhere(X_column > split_thresh).flatten()
return left_idxs, right_idxs
def _squared_error(self, y):
# calculate the mean value for all observations
y_mean = np.mean(y)
# calculate the residuals to y_mean
mean_squared_error = np.mean((y - y_mean)**2)
return mean_squared_error
def predict(self, X):
return np.array([self._traverse_tree(x, self.root) for x in X])
def _traverse_tree(self, x, node):
if node.is_leaf_node():
return node.value
if x[node.feature] <= node.threshold:
return self._traverse_tree(x, node.left)
return self._traverse_tree(x, node.right)
数据合同的另一面:唤醒消费者责任
通过明确数据消费者的承诺来驱动价值创造的路径
·
跟随 发表在 Towards Data Science ·6 min read·Nov 16, 2023
--
由 Midjourney 生成
数据合同中的双重责任*衡
如果一支球队有一半的队员不知道他们需要进球,那确实会是一场混乱和低效的景象。然而,在许多数据组织中,情况是否正是如此?
如今,许多数据组织正在采用数据合同作为增强协作和推动效率的手段。根据我在数据战略咨询公司的经验,我注意到一个反复出现的模式:尽管这些合同通常精心定义了数据提供者的责任 — 从确保数据准确性到维护稳定的架构 — 但它们往往忽视了数据消费者的角色。
就好像一旦数据交付,就默认或假设消费者从中提取价值的责任已经得到了认可一样。
关心业务价值创造的数据领导者和专业人士应更加关注数据消费者的责任。如果消费者不能有效地分析、解释和应用数据,并将其置于业务环境中,那么数据合同就未能充分发挥其潜力。因此,一个真正有效的数据合同还应详细说明消费者的义务,确保他们有能力和准备好按照设想利用数据。
随着我们深入探讨,我们将审视忽视消费者承诺如何降低数据合同的业务影响。更重要的是,我们将探讨将这些承诺纳入其中的实际措施,促进数据项目内的业务价值优先方法。
超越合规 — 构想数据消费者的更广泛角色
在相对稀少的关于数据合同中涉及消费者承诺的文献中,明显强调风险和合规性。描述倾向于概述预防措施和确保合法使用,而不是促进价值创造的积极方法。关键点通常包括:
-
数据使用:严格规定数据的可允许使用方式,以防止误用或错误应用。
-
数据安全:要求消费者在其掌握数据后实施强大的保护措施。
-
法规合规:需要将数据使用与隐私和特定行业法律的复杂网络对齐。
-
数据完整性:维护数据的原始状态,防止未经授权的更改。
-
事件管理:建立违规通知和应对程序,强调反应性立场。
虽然这些义务对于保护和合规至关重要,但它们常常使数据消费者处于被动角色。最*,我与一家公用事业公司的首席数据官进行了交谈,他分享了一个启发性的例子。他们刚刚为用户在其数据*台 Databricks 上直接访问数据集实施了自助服务功能。这一举措在技术上取得了成功,访问量很高。然而,首席数据官表达了一个普遍的担忧:尽管数据访问增加了,但下游产生的价值却没有清晰的可见性。这种情况是一个典型的例子,表明当关注点仅在于提供访问权限而不是如何将这种访问转化为业务影响时,可能会出现断层。
要真正将数据作为战略资产加以利用,我们需要将视野扩展到这些限制性框架之外。下一部分将深入探讨如何将消费者承诺的叙事和结构转变为鼓励更动态和以价值为导向的数据使用。
尊重生产者——承诺创造价值
数据合同中的承诺伴随着一定的开销。对于数据生产者或数据产品负责人来说,这意味着需要不断投资于维护数据管道、确保数据质量和提供必要的基础设施。尽管这些任务至关重要,但它们可能会变得昂贵,不仅在财务上,而且在机会成本上也是如此。现在是时候审视消费者方面,以了解这些承诺如何转化为实际的业务价值——或者未能如此。
想象一个场景,其中一个实习生或在任务之间的员工获得了一个数据产品的访问权限,用于一个未能成功的项目。项目被搁置,但数据产品仍然保持活动状态,维护成本存在且随时可以使用。虽然数据可能在其他举措中得到有效利用,但在这个失败的项目背景下,存在明显的断层:数据产品被维护,产生了成本却没有带来任何价值。
更糟糕的是,当一个项目推进时,数据被用来创建一个新的仪表盘,但发现它没有被业务采纳。仪表盘成了数字纸镇,投入的时间和精力都被浪费了。
即使在数据使用最初确实带来显著业务价值的成功案例中,形势也可能迅速变化,使曾经有价值的项目变得过时。例如,消费者可能利用数据推动某个特定的举措,获得高回报。然而,随着时间的推移,业务需求的变化使得这一举措的价值降低,但维护数据访问的承诺和相关成本仍然存在。数据产品继续按照原合同进行维护,即使它不再服务于最初的高价值目的。
这些情景突显了数据合同需要适应业务的动态特性。它们强调了数据消费者的承诺需要超越访问和使用,涵盖积极追求价值生成、效率和适应性。
含义很明确:数据合同需要确保在消费者方面,不仅要有安全合法使用数据的责任,还要有效使用数据,并在必要时进行调整,以保持与业务价值的一致性。下一部分将探讨如何构建灵活、以价值为中心且能够随着业务需求变化的数据消费者承诺策略。
目标评分 — 从数据产品到业务价值
核心原则很简单:企业内的每一个数据参与都应该带来超过其相关成本的价值。在这个背景下,一个具备良好 SLA、可靠性和一致性可访问性的数据产品就像是足球中的一次优秀传球——它创造了机会,而将其转化为进球则依赖于数据消费者。
在进行业务价值创造评估的过程中,一种由 Kindata 首创的方法使我认识到,将价值生成视为两个不同阶段的优势:
· 业务用例的交付:这涉及到数据消费者制定战略报告、AI/ML 模型或其他分析工具,以提供信息和支持业务决策。
· 有效的业务使用:最终的成功指标在于这些工具如何被业务用来实现财务和非财务目标。这个阶段通常会揭示业务部门在数据计划中的参与程度,这是获得真实业务价值的关键因素。
数据消费者在业务价值创造中处于核心位置。作为直接使用数据产品的人,他们处于‘进球’的位置——即交付业务价值。认识到他们的关键作用也意味着理解他们在使用这些数据资源时需要承担的责任。
为了培养这种责任文化并确保数据消费者不仅仅是参与者,而是积极的价值创造者,你的组织可以采用一种结构化的方法,制定正式的数据消费者承诺:
1. 记录数据使用:记录每一次与数据产品的互动,并明确其目的。在这一阶段的明确性为责任奠定了基础。
2. 衡量结果:定期测量数据产品对业务目标的影响。这不仅包括业务用例的交付,还包括与业务赞助商的持续互动,以确保价值随着时间的推移得到积极实现。
3. 向数据生产者反馈:分享数据产品对业务成果有显著贡献的成功案例,并识别进一步改进的机会。
4. 释放不必要的承诺:作为数据消费者,当你确定某个数据产品不再符合你的目的时,请立即通知数据生产者解除他们的义务。这一关键步骤确保资源被有效重新分配,且不会浪费在未使用的数据产品上的维护工作。
通过贯彻这些实践,数据消费者成为价值创造的积极主体,确保数据产品始终与业务的总体目标一致并为其贡献力量。当然,理解这些承诺只是起点。它们需要通过正确的文化、治理和工具的结合来实施。
数据产品和数据契约是组织数据团队并在消费者承诺上增加额外关注的重要方式,可以产生显著的影响。
就像在足球比赛中,每个团队成员都必须对得分目标保持一致一样,在数据领域,目的的明确性同样至关重要。如果我们团队的重要部分不理解最终目标是为了得分——创造业务价值,那么我们就无法希望取得胜利。
因此,当我们完善我们的战略并明确我们的策略时,让我们不要失去目标。毕竟,在数据驱动的成功之路上,目标至关重要。
成功的数据科学之路在于你的学习能力。但是,应该学习什么呢?
成功交付数据科学项目的机会最大时,你需要不断学习,但要专注于哪些方面却不总是明确的
·发布于 Towards Data Science ·7 分钟阅读·2023 年 6 月 29 日
--

图片由 Tamara Menzi 提供,来源于 Unsplash
在过去的十年中,数据科学取得了许多伟大的进展,但尽管如此,许多项目却从未实现。作为数据科学家,我们不仅要展示强大的技术能力,还要理解业务背景,有效地与利益相关者沟通,并将他们的问题转化为可以推动业务价值的可操作性建议。这是否合理,还是说业务在寻找新的独角兽?在这篇博客中,我将描述业务如何随着岁月的推移而变化,这将为你提供一个更好的视角,以了解你可能需要学习哪些内容才能成功交付数据科学项目。
简短介绍
十多年前,公司认识到挖掘数据集可以产生增加收入、优化流程和降低(生产)成本的信息。这催生了一个新领域和新角色;数据科学领域及数据科学家。但企业的需求多年来不断变化。因此,了解企业的需求对于了解作为数据科学家需要学习什么非常重要。 在接下来的部分中,我们将首先回顾描述数据科学领域在过去十年中的演变。这可以帮助你了解;1. 什么 曾经 是重要的学习内容,2. 现在 是什么 重要的学习内容,以及 3. 什么 可能是 未来工作中重要的学习内容。接下来的部分让我们一起回顾过去。
首先是科学程序员。
数据科学有许多学科,其基础建立在源自数十年(学术)研究和开发的统计学和数学上。许多最初的核心算法在文本挖掘、图像识别、传感和时间序列等学科中形成了基础。早期,这些方法的发布并没有附带代码。为了应用这些方法,公司会聘请科学程序员来完成具有挑战性和耗时的任务。 但在编写一行代码之前,通常会有一个思考过程,以了解为何要付出这些努力,以及可以期待什么样的结果。在过去十年中,这一情况发生了剧变,因为谷歌、Meta 等公司开始开源它们的库。此外,社区也开始开发开源包,如 sklearn、scipy 等。现在安装只需一行代码。
数据科学领域发展迅速,但企业到底需要什么?
现在,科学程序员已经变成了数据科学家。不过,情况发生了一些变化。企业也需要能够与利益相关者有效沟通、识别商业机会,并将技术洞察转化为推动业务价值的可操作建议的数据科学家。这催生了一种新型的数据科学家;应用型数据科学家。
应用型数据科学家与基础型数据科学家
“数据科学家”这个词经常被用作数据科学领域不同角色的统称。从数据管理员、机器学习工程师、数据工程师、统计学家以及更多角色。但当我们谈论真正的数据科学家时,基本上有两种类型;基础型数据科学家和应用型数据科学家。
-
基础数据科学家具有强大的统计和机器学习技术知识,以分析复杂的数据集并得出见解。此人可以告诉你所有关于底层数据分布的内容,并能轻松创建(修改)解决问题的算法/方法。这些人通常在研究和开发环境以及学术机构中表现出色。
-
应用数据科学家专注于应用现有技术和方法来解决特定业务问题或开发数据驱动的产品和/或服务。通常,这些角色在某一领域内加速发展,例如文本挖掘、图像识别、传感器或时间序列。创新通常通过在领域相关的数据上应用新方法来实现,而不是创建新算法或方法。
两个角色各有其优缺点,我有三个提示可以帮助成功交付数据科学项目。
提示 1:学习编程基础。
感谢 Coursera、Udemy、YouTube 和 Medium 等优秀*台,有大量材料可以学习编程基础。
-
以已知风格编写代码,如 PEP8。
-
编写内联注释;说明你做的事情及其原因。
-
编写文档字符串。
-
使用合理的变量名。
-
降低代码复杂性(尽量降低)。
-
编写单元测试。
-
编写文档。
-
保持代码整洁。
编程是数据科学领域的主要挑战之一。它被严重低估,但却是决定数据科学项目能否进入生产的关键因素之一。想想看,你是否愿意维护一个没有文档、没有单元测试且写成意大利面式风格的模型或编程代码?我不这么认为。
每个数据科学项目都需要可重复的代码,而进入生产的步骤则需要可维护的代码。最终,每个项目都不过是一堆代码行,需要有人将其投入生产。保持整洁。保持有序。
提示 2:项目的成功不仅仅依赖于机器学习解决方案。
数据科学项目通常开始时充满热情,但这种热情可能会迅速消退,因为项目需要的远不止一个机器学习解决方案。在最*发表的一篇文章[1]中,非常清楚地描述了数据科学项目中最重要的技术步骤。然而,要将一个想法转化为生产,你需要的不仅仅是技术技能。以下是可以帮助提高项目成功率的步骤总结:
-
从目标开始。在项目开始时,了解如何将项目落地到组织或公司中。数据治理、伦理和隐私是开始时重要的。
-
检查哪个*台或基础设施可供协作。 例如,这可以是带有 CI/CD 管道的 git 和 cookie-cutter 模板。
-
理解领域。 在进行任何分析之前,需要对你所工作的领域有基本的理解。你需要知道如何根据你工作的领域和背景来处理数据。没有一种通用的数据科学解决方案。
-
正确进行数据分析。这看似简单,但知道如何用
pip install安装一个包并不等于你就是专家。进行自己的研究和阅读文章。避免那些你无法解释的(复杂的)机器学习解决方案。使用训练-测试-验证集。将你的结果与基准比较。与有经验的科学家和具有领域知识的人讨论你的想法和结果。 -
报告你的结果。保持透明。讲述基于事实的故事。不要将故事泛化超出数据范围。描述过程比模型输出的那个单一数字更为重要。
-
编写可重复和可维护的代码。 证明结果是可重复的,代码是可维护的。
-
交付结果。 如果所有步骤都完成了,结果或产品需要以客户能够使用的方式交给他们。仅仅将带有工作代码的个人笔记本电脑交给客户并不是解决方案。
如果你仔细观察这些步骤,只有一个步骤(#4)涉及数据分析和模型创建。让这一点深入人心。
提示 3:聪明地学习并重复。
数据科学是一个高度复杂且快速发展的领域,不同的专业领域在这里汇聚。每个数据科学家都有不同的背景,持续学习是这一过程的一部分。这意味着个性化的学习和成长路径可以带来很大益处,这取决于你的学位/起点、经验、领域知识、数学、统计、编程、工程、沟通和演示技能的背景。与同事讨论你可以改进的地方,并制定个人的学习和成长路线图。请注意,参加随机的数据科学课程可能很有趣,但可能与公司的使命甚至你的个人成长路径不一致。
学习的能力是每个人都应该不断练习的技能,终身学习可能是你能给自己最好的礼物。
总有更多的东西可以学习。
成功的道路不是某个特定的在线课程,而可能需要多年甚至几十年的奉献、努力和奋斗。投资自己,学习基础知识,超越肤浅的了解,专业化,并认识到成功是许多小步骤的积累,其中建模部分只是整个过程中的一步。
让我尝试将这分解成几个部分。首先,沟通非常重要。也许你能创造出最天才的方法,但你需要有效地将复杂的技术概念阐述给技术和非技术的利益相关者。问题解决:你应该能够用结构化和系统化的思维方法处理复杂问题。批判性思考,从多个角度分析问题,并提出有效的解决方案。你可以通过在Stack Overflow等网站上帮助社区来轻松练习。当你在职业生涯和职位上不断成长时,你应该能够指导和辅导开发人员。提供指导,分享最佳实践,并帮助提升他们的技术技能。适应能力。你不应固守于你所知道的单一技术,而应拥抱新的技术、方法和工具。你应该能够快速学习并适应不断变化的项目要求或行业趋势。时间管理。有效管理你的时间。优先处理任务,遵守截止日期,*衡各种需求。专注于交付高质量的工作。
保重。保持警惕。
Cheers E.
如果你觉得这篇文章对你有帮助,欢迎 关注我 ,因为我写了更多关于贝叶斯因果学习的内容。如果你考虑购买 Medium 会员,你可以通过我的 推荐链接来支持我的工作。这与购买一杯咖啡的价格相同,但可以让你每月阅读无限的文章。
让我们联系吧!
参考文献
-
Michael A. Lones, 如何避免机器学习陷阱:学术研究者指南,arXiv: 2108.02497
-
Tessa Xie, 数据科学职业错误避免, 2021,
-
数据科学家是否变成了过时的职业? 数据科学中央
人工智能监管之路
原文:
towardsdatascience.com/the-path-towards-ai-regulation-7199eb803040
意见
《人工智能法案》的贡献及其全球影响
·发表于Towards Data Science ·7 分钟阅读·2023 年 3 月 10 日
--

2021 年中期,欧盟委员会提出了针对欧盟的人工智能(AI)新监管框架。因此,它代表了对人工智能实施某种程度监管的首次尝试。
你可以阅读AI 法案提案的完整官方文件(24 种语言版本)
目前,它仍然是最先进且被广泛接受的相关监管提案。虽然它仍处于初步提案阶段,但欧盟正在努力推动短期内的强烈议程以实现采纳。
目前对人工智能系统的定义非常宽泛和肤浅。
这一倡议受到全球的密切关注,因为它源于对保护个人基本权利的需求,因此可能对整个社会产生影响。
这一法律框架从使用和风险的角度考虑人工智能系统,同时接受了对人工智能及其系统概念的任何定义的局限性。基本上,目前对人工智能系统的定义非常宽泛和肤浅,因为它止步于将人工智能系统定义为一种软件技术,包括机器学习、逻辑和知识基础系统以及统计方法。
人工智能法案根据风险等级对人工智能系统进行分类,并根据风险等级规定了限制和义务。

图片由janilson furtado提供,来源于Unsplash
来自 Towards Data Science 编辑的说明: 虽然我们允许独立作者根据我们的 规则和指南**发布文章,但我们不对每位作者的贡献表示认可。你不应在未寻求专业建议的情况下依赖作者的作品。详细信息请参见我们的 *读者条款**。
AI 风险分类
规章提案并不限制技术或方法。相反,它采用了一种基于风险的方法,提出了一个根据风险等级对 AI 系统进行分类的方案,并根据风险等级指定了不同的要求、限制和义务。特别是:
-
呈现‘不可接受’风险的 AI 系统将被禁止。 这些包括涵盖操控性‘潜意识技术’、或利用特定易受害群体、社会评分目的以及实时远程生物特征识别系统的 AI 系统。
-
‘高风险’ AI 系统将被授权,但需遵守非常严格的要求和义务,才能进入欧盟市场。这些包括作为关键产品安全组件的 AI 系统、生物特征识别、关键基础设施的运行、教育工具;就业和工人管理;获取必要服务的工具;执法;移民、庇护和边境控制;以及司法和民主过程的管理。
-
仅呈现‘有限风险’的 AI 系统将仅需遵守基本的透明度义务。这些包括与人互动的系统(即聊天机器人)、情感识别、生物特征分类,以及生成或操控文本、图像、音频或视频内容(包括深度伪造)的系统。
注意这里的(遗漏)大象:军事和防御应用不在规章范围之内。实际上,《AI 法案》明确指出
专门为军事用途开发或使用的 AI 系统应排除在本规章范围之外。
同样也在
第 2.3 条:本规章不适用于专门为军事目的开发或使用的 AI 系统。
难道它们不应该受到监管吗?

图片由 Tingey Injury Law Firm 提供,发布于 Unsplash
AI 合规执行
从某种意义上说,与这种基于风险的视角一起,这个框架还涵盖了治理和执行机构的定义:它建立了一个欧洲人工智能委员会(监督过程并确保各国的一致实施)和一个每个成员国的国家监督机构(监督法规的应用和实施),同时国家市场监督机构将负责通过广泛的保密访问(包括源代码)来监控参与者的合规性。
执法机构将采取适当措施限制、禁止、召回或撤回不符合规则的人工智能系统。这包括最高达 3000 万欧元或全球年总营业额 6% 的行政罚款,具体取决于违规的严重性。
人工智能法案及其相关执行机制采取了产品责任法的形式。

照片由 Tingey Injury Law Firm 提供,发布在 Unsplash。
从务实的角度来看,基于风险的方法是完全有意义的。然而,这对未来的可持续性和普遍性提出了若干挑战。特别是:
-
人工智能的定义是否足够广泛和精确?
-
我们如何确保我们将应用程序分类到正确的风险类别?
-
对于我们现在无法预见的新解决方案和应用,怎么办?
此外,请注意,人工智能法案及其相关执行机制基本上采取了产品责任法的形式。的确,这种框架显然是在公司和产品层面制定规则和权利,而不是个人层面。这对生产和服务基于人工智能解决方案的公司有深远的影响。公司需要声明并展示其解决方案和应用的风险水*。它们还需要保证遵守各自风险水*的规则。
考虑到整个*台始于保护个人基本权利的需求,法案所采取的方法似乎与基本原则不太一致。此外,这种方法还隐含着实际风险:如果出现任何问题,究竟由谁负责?是开发公司、供应商,还是任何部署、配置或定制解决方案的中介?
人工智能监管框架还涵盖了其他维度:
-
标准化:与标准化机构如 ISO 和 IEEE 的讨论应确保技术和定义的采用尽可能广泛且达成一致。北约本身现在也在推进相关的标准化倡议。
-
卓越:框架不应(仅仅)限制人工智能的潜力。一方面,它将限制危险的应用,另一方面,它应在基础和应用层面上促进和推动创新与卓越。
如果不同时对数据进行监管和保护,那么对人工智能的监管可能没有任何意义。
至少在欧洲层面上,对这一倡议的重要性和必要性存在广泛的共识。其他国家也开始了类似的进程:例如,美国在 2022 年制定了 人工智能权利法案 的蓝图,而巴西制定了初步的人工智能法律。许多国家(包括美国和许多欧盟成员国)也在 2020 年代初期制定了国家人工智能倡议。欧洲联盟可能会像它在数据保护领域所做的那样,为未来全球人工智能的监管铺*道路。
确实,对不对数据进行监管和保护的情况下对人工智能进行监管似乎没有任何意义。确实,人工智能法规的应用在已经拥有良好建立的数据保护和监管法律的国家中会更为可行。
在这方面,欧洲联盟再次在全球范围内引领潮流,推出了GDPR,该法规目前在更广泛的层面上产生了广泛的影响。
欧洲联盟可能会为未来全球人工智能系统的监管铺*道路,就像它在数据保护方面所做的那样。

AI 法案的详细描述可以在这个欧盟议会简报中找到,而 AI 法案提案的完整官方文件发布在这里(提供 24 种语言)。关于此事的讨论和观点也来源于IdeasLab会议和由布鲁塞尔欧洲政治研究中心(CEPS)于 2023 年 2 月 28 日组织的智库。

[AI 法案提案的官方标题]
总结:AI 法案可以代表国际层面上对人工智能系统的首次尝试性监管。一旦获得批准,它可能会影响并在某种程度上迫使其他几个国家,包括美国,采取类似的举措,可能会与这一提案保持一致或受到其启发。
国际间的协调可能会带来好处,这与*年来的数据监管情况相对立。在过去,欧盟通过 GDPR 法规引领了数据监管,但美国没有匹配 GDPR 要求的保障水*,从而在国际层面上产生了混乱、法律障碍以及数据传输和存储的限制。
另一方面,这可能代表了软件系统和模型将首次被明确和直接限制使用和实施。人们可能会好奇,为什么这对 AI 系统适用,而过去对于其他软件或数学模型没有出现类似需求。这可能会成为未来技术监管的重要先例。
为什么我们需要对 AI 系统进行监管,而过去对其他软件模型和算法没有进行监管?
你怎么看?应该对 AI 进行监管吗?如何进行?
你有自己的看法要分享吗?
基于风险的方法在长远中可能暴露哪些陷阱?
这样的监管会影响(你的)业务、创新过程或数据科学实践吗?

照片由Christopher Burns拍摄,发布在Unsplash上
让我们来看看“混乱的数据科学家的 PATH 变量:如何管理它”
了解 PATH 是什么,以及如何在 Windows 和类 Unix 系统中添加路径
·发表于 Towards Data Science ·6 分钟阅读·2023 年 6 月 26 日
--

图片由我使用 Midjourney 制作
有时我觉得 StackOverflow 上的人好像头顶上装了指南针。他们总是能找到 PATH。
我认为它不在你的 PATH 中。
你可能搞乱了你的 PATH。
你是否将它添加到你的 PATH 中?
检查可执行文件是否在你的 PATH 中。
“我的意思是,PATH 到底是什么?”每当我阅读这样的句子时,我总是会这么说,脸红着试图修复错误。现在,我在数据科学的旅程中已经三年多了,我完全知道它是什么了。差不多。
在这篇文章中,我打算教你如何在 Windows 和类 Unix 系统中管理这个令人困惑的环境变量。
开始吧!
命令也有路径
你使用最多的终端命令是什么?毫无疑问,我的是 git,因为我在写文章时经常进行提交。
我问这个问题的原因是,因为大多数终端命令在操作系统中也有自己的路径。要找到那个路径,你只需要运行 which valid_command_name。例如,这是我 git 实例的路径:
$ which git # In windows, use `where`
/usr/bin/git
你可能会认识到上面的是一个绝对路径。但它到底指向什么呢?
一个可执行的二进制文件!(如果你使用的是 Windows,你会得到一个 .exe 文件路径)
它包含了当 git 命令被执行时应该做什么的说明。所有终端命令都是以某种形式的 shell 脚本或可执行文件,执行特定的任务。
例如,你可以对内置终端命令如 clear、cat 或 touch 使用 which 命令,你会得到绝对路径(甚至是 which 命令本身的路径)。
一旦你得到可执行文件的路径,你可以尝试用其完整路径运行命令,如下所示:
$ /usr/bin/git status -s # the same as `git status -s`
M 2023/6_june/7_path/dump.ipynb
并且它确实有效!但有趣的是——如何使得词语 git 相当于绝对路径 /usr/bin/git?我意思是,git 只是一个词。
你的计算机如何知道如此多命令的完整可执行路径,并且仅用一个关键词就能运行它们?
(这是 Eureka 时刻!)
使用 PATH 变量!
看啊——PATH!
PATH 是一个变量,保存了执行几乎所有终端命令的绝对路径,无论是内置命令还是用户安装的命令。
当你输入任何命令,如 conda env create 时,操作系统会在你的 PATH 变量中寻找一个名为 conda 的可执行文件。如果找不到可执行文件,它会返回一个 command not found 错误:
$ unknown_command
unknown_command: command not found
你可以用一个命令打印出 PATH 中的所有路径。
在类 Unix 系统中:
$ echo $PATH
/home/bexgboost/.local/bin:/home/bexgboost/anaconda3/bin:...
在 Windows 中:
$ ECHO %PATH%
C:\Python39\Scripts\;C:\Python39\;C:\WINDOWS\system32;...
在 Unix 中,PATH 中的每个路径由冒号分隔,而在 Windows 中由分号分隔。
正是这些路径使得你不需要记住系统中安装的可执行文件的数十个绝对路径。
在 Windows 中处理 PATH
编辑 PATH 变量在类 Unix 系统和固执的 Windows 中有所不同。本节内容讲述在 Windows 中如何管理你的 PATH 变量。
你最常进行的 PATH 操作是添加新路径。例如,要将 C:\Users\user\Desktop\articles 目录添加到 PATH,你应以管理员权限打开命令提示符或 Powershell。然后,运行以下命令:
$ setx PATH "%PATH%;C:\Users\user\Desktop\articles"
但,别急——如果不小心,setx 命令可能会对你的 PATH 造成很大伤害。因此,在 Windows 中,安全的方法是通过系统属性窗口添加路径。
这里是一步一步的指示和一个 GIF,用于将新路径添加到 PATH:
-
点击开始按钮,搜索“编辑系统环境变量”,然后点击第一个结果。
-
从弹出窗口中点击“环境变量”。
-
决定新路径是添加到当前用户还是整个系统。
-
在“用户变量 for your_username”或“系统变量”下,向下滚动,直到找到“Path”变量。
-
选择它并点击 编辑。
-
在新的弹出窗口中,点击“新建”并粘贴新路径,例如
C:\Users\user\Desktop\articles -
点击确定三次。

GIF 由我制作。
在最后一个弹出窗口中,你也可以删除或更改路径的顺序(见最后一节关于顺序的重要性)。
在 Unix 中处理 PATH
在类 Unix 系统中,添加新的路径到 PATH 要容易得多。
例如,要添加 /home/bexgboost/articles 路径,你需要运行以下命令:
$ export PATH="/home/bexgboost/articles:$PATH"
新路径将被添加到 PATH 的最前面。要将其添加到末尾,你需要改变 $PATH 的位置:
$ export PATH="$PATH:/home/bexgboost/articles"
但新路径只会在当前终端会话期间有效。要永久添加到你的 PATH 环境变量中,你需要将 export 命令追加到你的 shell 配置文件中。
具体的配置文件取决于你使用的 shell:
-
对于 Bash:将
export命令添加到~/.bashrc或~/.bash_profile中(你也可以考虑.profile,有关区别请参见这里)。 -
对于 Zsh:将
export命令添加到~/.zshrc中。 -
对于 Fish:将
export命令添加到~/.config/fish/config.fish中。
一旦你从列表中选择了自己的文件(我会选择.bash_profile),使用以下echo命令:
$ echo 'export PATH="$PATH:/home/bexgboost/articles"' >> ~/.bash_profile
然后,通过source ~/.bash_profile重新加载配置文件,操作完成。
路径的顺序
PATH 变量中的路径顺序决定了执行命令或程序时目录被搜索的优先级。
当你在终端中输入一个命令时,操作系统会从左到右依次检查 PATH 中的每一个路径来查找其可执行文件。如果多个目录中包含相同名称的可执行文件,则会使用第一个。
例如,如果你的系统中有多个 Python 版本,则当你在终端中运行python时,会使用 PATH 中最左边的 Python 可执行文件。
结论
在数据科学和机器学习的世界里,终端常常被忽视。相反,在线课程和训练营将初学者的注意力吸引到像 VSCode 或 JupyterLab 这样更闪亮的 IDE 上。
但总有一天,不可避免地,他们必须在自己的机器上打开生锈的 shell。这时,他们会在 PATH 中遇到一个巨大的障碍。
我希望这篇文章能帮助你击碎那些障碍。
感谢阅读!
喜欢这篇文章,并且,直白点说,它那古怪的写作风格?想象一下访问更多类似的文章,全部由一位才华横溢、迷人、机智的作者(顺便说一下,就是我 😃。
仅需 4.99 美元的会员费,你不仅可以访问我的故事,还可以获取来自 Medium 上最优秀的思想者的知识宝库。如果你使用我的推荐链接,你将获得我超级 nova 般的感激和对支持我工作的虚拟击掌。
[## 使用我的推荐链接加入 Medium - Bex T.
作为 Medium 会员,你的会员费的一部分会分给你阅读的作者,你可以完全访问每一个故事……
ibexorigin.medium.com](https://ibexorigin.medium.com/membership?source=post_page-----b469bfb45785--------------------------------)
Jupyter 的完美文本编辑器:一个完整的 Python IDE
原文:
towardsdatascience.com/the-perfect-text-editor-for-jupyter-a-complete-python-ide-608dd466560b
从语法高亮到代码补全,一个完整的 Python IDE 在 Jupyter 中
·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 3 月 9 日
--

本文是系列文章的第四部分。查看完整系列:第一部分,第二部分,第三部分。
在过去几天里,我们在 Jupyter 中构建了一个完整的 Python IDE。在本文中,我们将进行最后的调整,并将一切打包成 Docker 镜像,以创建一个便于数据科学家和机器学习工程师使用的便携工作环境。
Jupyter 并不是一个完整的 IDE。它甚至不是许多人认为的 IPython 用户界面。我认为 Jupyter 是一个开发*台,你可以按照自己的喜好创建工作区。由于它包含一个终端模拟器,你可以做你想做的任何事。因此,我们将利用它来创建一个功能丰富的 Python IDE。
因此,在之前的文章中,我们安装了 Neovim,并将其配置为与现在最流行的文本编辑器:Visual Studio Code 相似。在本文中,我们将安装一个全面的工具:代码补全、代码格式化、git 集成、拼写检查和代码对齐。让我们开始吧!
学习速率 是一个面向对 ML 和 MLOps 世界感兴趣的人的新闻通讯。如果你想了解更多类似的主题,请在这里订阅。每个月的最后一个星期天,你会收到我的更新和对最新 MLOps 新闻和文章的见解!
Conquer of Completion
许多工具承诺在 Vim/Neovim 的使用上让开发者的生活更轻松,但我个人偏好的是Conquer of Completion。
Conquer of Completion (CoC) 是一个流行的 Vim 和 Neovim 插件,提供强大的自动补全引擎。以下是有关 CoC 的一些关键点:
-
CoC 支持多种语言:它可以为包括 Python、JavaScript、C、Go 等在内的各种编程语言提供自动补全建议。它使用语言服务器根据你的代码上下文提供智能推荐。
-
CoC 是高度可配置的:它允许用户以类似 VS Code 的方式自定义自动补全引擎。用户可以设置触发补全的自定义映射,调整补全源的优先级等。
-
CoC 拥有庞大的用户社区:这是一个流行的插件,拥有大量用户社区,这意味着有许多资源可以用来排除故障和进行自定义。该插件也在积极维护和更新。此外,社区提供了几个我们将使用的优秀扩展。
总的来说,Conquer of Completion 是一个强大的插件,可以显著提升你在 Vim 或 Neovim 中的自动补全体验,尤其是当你处理多种编程语言时。
安装
CoC 需要一些设置。你需要安装和配置一些额外的组件,包括 NodeJS 和 npm 包管理器。根据情况,你可能还需要为你喜欢的编程语言安装一个语言服务器。
在我们的情况下,首先安装 NodeJS。
NodeJS 安装
NodeJS 和 npm 是安装 Neovim 的 CoC 插件并启用多种编程语言自动补全的硬性要求。
要安装这两个软件包,请执行以下命令:
- 安装 PPA 以访问其软件包:
curl -sL https://deb.nodesource.com/setup_18.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
2. 安装 Node.js:
sudo apt install nodejs
3. 验证:
node -v
如果你看到类似 v18.15.0(在撰写本文时的最新版本)的版本号,你就可以开始使用了。即使你安装了其他版本,也应该可以继续使用。
安装 CoC
安装 CoC 插件与其他插件一样简单。只需将以下行放入你的 init.vim 配置文件中:
Plug 'neoclide/coc.nvim', {'branch': 'release'} " Auto complete Python
重启编辑器并运行 :PlugInstall Vim 命令。vim-plug,我们正在使用的插件管理器,会为你安装 CoC。为完成安装过程,请再次重启编辑器。
要详细查看 Neovim 插件的安装过程,请参考本系列的第二篇博客。
我们准备安装所需的扩展,将编辑器转变为一个功能齐全的 IDE。运行 :CocInstall coc-pyright Vim 命令以安装 Python 支持。安装完成后,退出编辑器的所有窗口,然后打开一个 Python 文件,如下所示:
nvim test.py
自动补全应该启用;然而,它并不是很实用。因此,让我们配置 CoC。在你的 init.vim 配置文件中添加以下几行:
" CoC customization
" Applying code actions to the selected code block
vmap <leader>a <Plug>(coc-codeaction-selected)<cr>
xmap <leader>a <Plug>(coc-codeaction-selected)<cr>
" Format action on <leader>
vmap <leader>f <Plug>(coc-format-selected)
xmap <leader>f <Plug>(coc-format-selected)
" GoTo code navigation
nmap <silent> gd <Plug>(coc-definition)
nmap <silent> gv :vsp<CR><Plug>(coc-definition)<C-W>L
nmap <silent> gy <Plug>(coc-type-definition)
nmap <silent> gi <Plug>(coc-implementation)
nmap <silent> gr <Plug>(coc-references)
" Symbol renaming
nmap <leader>rn <Plug>(coc-rename)
" Refactor
nmap <silent> <leader>re <Plug>(coc-codeaction-refactor)
xmap <silent> <leader>r <Plug>(coc-codeaction-refactor-selected)
nmap <silent> <leader>r <Plug>(coc-codeaction-refactor-selected)
" Confirm selection by pressing Tab
inoremap <expr> <Tab> pumvisible() ? coc#_select_confirm() : "<Tab>"
" Open autocomplete menu by pressing Ctrl + Space
inoremap <silent><expr> <c-space> coc#refresh()
" Open documentation by pressing K
nnoremap <silent> K :call ShowDocumentation()<cr>
function! ShowDocumentation()
if CocAction('hasProvider', 'hover')
call CocActionAsync('doHover')
else
call feedkeys('K', 'in')
endif
endfunction
我在每一行上留下了评论,以便你理解它的作用。现在 CoC 已配置好,你可以使用这些快捷键来充分利用它。
然而,事情并没有就此结束。正如我们所说,CoC 有多个扩展(如 coc-pyright)可以安装。它们包括:
-
coc-git -
coc-docker -
coc-yaml -
coc-json -
coc-prettier -
coc-pairs -
coc-spell-checker
要安装它们中的任何一个,只需运行 :CocInstall 命令并提供扩展的名称。有关扩展的完整列表,请访问 coc-extensions 页面:github.com/topics/coc-extensions
最后,你可以像在 VS Code 中一样配置 CoC。运行 :CocConfig 命令,coc-settings.json 文件将弹出。我在其中设置了以下配置:
{
"python.venvPath": "/home/dimpo/.pyenv/versions/",
"python.linting.flake8Enabled": true,
"python.formatting.provider": "autopep8",
"pyright.inlayHints.variableTypes": false,
"pyright.inlayHints.functionReturnTypes": false,
"git.addGBlameToVirtualText": true,
}
每一行都是自解释的,但如果你想详细了解可以配置的内容,请参考每个 CoC 扩展的文档。
像往常一样,使用 docker commit 命令保存你的工作(如果你不知道这意味着什么,请参考本系列以前的文章)。你可以在 DockerHub 上找到我的镜像。
总结
在这一系列文章中,我们在 JupyterLab 环境中构建了一个完整的 Python IDE。我们从一个简单的 Neovim 安装开始,并将其配置为像今天最受欢迎的文本编辑器 VS Code 一样。
这篇文章结束了这个故事,但如果你想看到更多内容,如如何使用调试器或进一步配置特定插件,请留言,我会尽力而为!在此之前,祝编程愉快。
关于作者
我的名字是 Dimitris Poulopoulos,我是一名在 Arrikto 工作的机器学习工程师。我为包括欧盟委员会、欧洲统计局、国际货币基金组织、欧洲中央银行、经济合作与发展组织和宜家在内的主要客户设计和实施了 AI 和软件解决方案。
如果你对机器学习、深度学习、数据科学和数据操作等更多帖子感兴趣,请在 Medium、LinkedIn 或 Twitter 上关注我。
所表达的观点仅代表我个人,并不代表我的雇主的观点或意见。
*滑你的噪声数据的完美方法
使用 Whittaker-Eilers 方法进行极快且可靠的*滑和插值。
·
关注 发表在 Towards Data Science · 13 min read · 2023 年 10 月 25 日
--
现实世界的数据从来不会干净。无论你是在进行调查、测量降雨量还是接收来自太空的 GPS 信号,噪声数据总是存在。处理这些数据是数据科学家的主要工作内容。它并非全是光鲜的机器学习模型和人工智能——而是清理数据,以便尽可能提取有意义的信息。如果你当前看到的图表上有太多的曲折线而无法使用,那么,我有你需要的解决方案。
Whittaker-Eilers *滑法
Whittaker-Eilers *滑器 [1] 是我在从事地球观测工作时接触到的。对数千张高分辨率卫星图像进行像素级分析需要极其快速的算法来清理数据。某一天可能会有云层覆盖,一些像素可能被烟雾遮挡,或者传感器可能会出现伪影。列举的情况可以一直延续下去。你得到的是数 TB 的相当嘈杂、存在空缺的时间序列数据,需要进行*滑和插值——这正是 Whittaker *滑器的强项。
*滑
让我们从一个图表开始,展示 1880 到 2022 年的 全球温度异常 [2]。橙色表示测量数据,绿色表示使用 Whittaker-Eilers 方法*滑的数据。

图 1) Whittaker-Eilers *滑的全球温度异常
如你所见,原始的时间序列数据相当嘈杂。我们想提取的结论并不是关于逐年的波动,而是过去一个世纪的数据总体趋势。*滑数据提供了一种直接的方法来突出趋势,更好的是,它只需要 4 行代码就能运行。
from whittaker_eilers import WhittakerSmoother
temp_anom = [-0.17, -0.09, -0.11, -0.18] # and so on...
whittaker_smoother = WhittakerSmoother(
lmbda=20, order=2, data_length=len(temp_anom)
)
smoothed_temp_anom = whittaker_smoother.smooth(temp_anom)
如果你不喜欢 Python,这也只需要 4 行 Rust 代码。
use whittaker_eilers::WhittakerSmoother;
temp_anom = vec![-0.17, -0.09, -0.11, -0.18] // and so on...
let whittaker_smoother =
WhittakerSmoother::new(20, 2, temp_anom.len(), None, None)
.unwrap();
let smoothed_data = whittaker_smoother.smooth(&temp_anom).unwrap();
免责声明,这个包是我编写的 — Python: [*pip install whittaker-eilers*](https://pypi.org/project/whittaker-eilers/) 或 Rust: [*cargo add whittaker-eilers*](https://crates.io/crates/whittaker-eilers)
插值
现在让我们在数据中引入一些空缺。与其每年均匀收集数据,不如减少为每隔一年一次,并增加几个较长的空缺。

图 2) Whittaker-Eilers *滑并插值的全球温度异常
Whittaker 方法处理起来非常轻松。你只需要给每个测量值分配一个权重。在插值时,存在的测量值权重为 1,而你希望获取值的测量用一个虚拟值,比如 -999,权重为 0。Whittaker 方法会处理剩下的部分。它也可以用来根据测量的不确定性进行相应的加权,而且只需几行 Python 代码即可完成。
from whittaker_eilers import Whittaker
temp_anom = [-0.17, -0.09, -0.11, -0.18, -0.3] # and so on...
weights = [1.0, 0.0, 1.0, 0.0, 1.0]
whittaker_smoother = WhittakerSmoother(
lmbda=150, order=2, data_length=len(temp_anom), weights=weights
)
smoothed_temp_anom = whittaker_smoother.smooth(temp_anom)
除了数据中的空缺,Whittaker *滑器还可以轻松处理不均匀间隔的测量。如果你不在意插值,只需提供一个包含测量时间或地点的 x_input,例如 [1880, 1885, 1886, 1900],就可以获得*滑后的值。
配置
通过一个单一的参数 λ(lambda),你可以持续控制数据的*滑度。lambda 越大,数据越*滑。

图 3) Whittaker-Eilers *滑数据,λ 值不同
*滑器的阶数 d 也是可以控制的。阶数越高,Whittaker 在计算时间序列的*滑程度时会考虑更多的邻*元素。核心要点是,插值数据将是阶数为 2d 的多项式,而外推数据将是阶数为 d 的多项式;不过,我们稍后会详细探讨这些数学原理。

图 4)Whittaker-Eilers *滑数据的多阶次
与其他方法的比较
为了展示 Whittaker-Eilers 方法的优越性,让我们将其与几种不同的技术进行比较:
-
高斯核*滑(也称为 RBF 核)
-
Savitzky-Golay 滤波器
-
局部回归 (LOWESS)
第一个是核*滑器,本质上是一种对邻*点进行加权*均的复杂方法。第二种是 Savitzky-Golay 滤波器,它与 Eilers 在 2003 年的原始论文相对立,并且使用非常广泛。它通过对相邻数据子集进行多项式的最小二乘拟合来*滑数据。最后,局部回归是 NASA 选择的*滑温度异常数据的方法。这种方法在连续的数据子集上执行迭代加权线性拟合,并根据残差重新加权点。
我选择不包括任何实时*滑的方法,因为这些方法在仅评估过去数据时会必然引入信号延迟。如果你对这种方法感兴趣,务必查看移动*均、指数*滑和卡尔曼滤波器。
*滑
那么让我们再看看温度异常时间序列,这一次使用额外的方法进行*滑。

图 5)不同*滑技术的比较
如你所见,变化不大——至少在你调节好参数后如此。让我们尝试一个例子,既可以揭示一些差异,也可以进行性能基准测试。我们将生成 4 个基本的正弦波,范围在 0 到 2π 之间,并添加一些高斯噪声。每个信号的样本大小将增加,从 40 开始,直到 10,000 结束。对于 Savitzky-Golay、高斯核和 LOWESS 方法来说,已经提出了挑战。它们期望一个窗口长度——每个连续子集中应包含的数据长度,以便进行拟合/*均。如果你在相同的时间段内增加测量数量,你需要确保你的窗口长度随着整体数据长度的变化而变化,以获得每个数据集的最佳*滑效果。下面,每种方法大致考虑了整体数据的十分之一作为每个窗口。

图 6) 不同测量采样频率下*滑的正弦波
然而,Whittaker 不需要如此动态的参数调整。你只需提供测量位置 x,设置一次λ,它就会每次都给出*滑的数据。这种测量位置的知识使其能够处理不等间距的数据。LOWESS 也可以在这种数据上运行,但另外两种方法要求数据间隔相等。总体而言,我发现 Whittaker 最易于使用。
Whittaker *滑器的另一个好处是其对边界条件的适应性。在数据边缘,Savitzky-Golay 滤波器的行为会发生变化,因为它不能在没有填充或镜像数据的情况下拟合超出数据集末尾的多项式。另一种选择是让它拟合最终窗口中的多项式并使用这些结果。高斯核*滑器的情况更糟,因为没有未来的测量值进入*均值,它开始表现出来自以前值的大偏差,这在下面的图表中可以清楚地看到。

图 7) 不同测量采样频率下*滑正弦波的边界
插值
Whittaker 的一个关键特性是其内建的数据插值能力。但与其他方法相比,它的效果如何呢?这个问题并不那么明确。Savitzky-Golay *滑可以进行插值,但仅限于数据间隙小于其窗口大小的情况,LOWESS *滑也是如此。高斯核*滑根本没有插值的能力。解决此问题的传统方法是先对数据应用线性插值,然后再进行*滑。因此,我们将这种方法应用于其他三种技术,并将结果与 Whittaker 进行比较。
每种方法将与其自身的*滑基线进行比较,该基线取自本节开始时的图(图 5)。我删除了每个其他点并引入了两个大的间隙,创建了一个与文章开始处插值示例(图 2)中的数据集完全相同的数据集。基线和插值运行的参数保持不变。

图 8) 线性插值联合使用技术的比较
通过线性插值填补空白后,各方法的表现总体较好。通过计算均方根误差(RSME)来比较*滑后的数据(没有间隙)和有间隙的*滑数据,我们得到了以下结果。
-
线性插值 + Savitzky-Golay: 0.0245 °C
-
Whittaker : 0.0271 °C
-
线性插值 + 高斯核: 0.0275 °C
-
线性插值 + LOWESS: 0.0299 °C
线性插值结合 Savitzky-Golay 方法最接*原始*滑数据,其次是 Whittaker,两者之间差别不大!
我想简单提一下,我以这种方式执行了插值基准测试,对比了各自*滑的基线,以避免调整参数。我本可以使用添加噪声的正弦波,删除一些数据并尝试将其*滑回原始信号,但这会让我头疼于为每种方法找到最佳参数。
基准测试
让我们重新审视正弦波数据,以生成这些方法的速度基准。我选择了每种方法中最流行的 Python 实现。Savitzky-Golay 和高斯核滤波器使用SciPy实现,LOWESS 使用statsmodels实现,Whittaker 使用我基于 Rust 的 Python 包实现。下图显示了每种方法*滑正弦波所需的时间以及数据长度的变化。报告的时间是*滑每个数据集 50 次所需的总时间。

图 9) *滑不同长度的时间序列所需时间 50 次
迄今为止最快的方法是 Whittaker。它可以在不到一秒的时间内*滑 50 个每个长度为 100,000 数据点的时间序列,比高斯滤波器快 10 倍,比 Savitzky-Golay 滤波器快 100 倍。最慢的是 LOWESS,尽管它被配置为不迭代地重新加权每个线性回归(这是一个昂贵的操作)。值得注意的是,通过调整窗口长度可以加快这些方法的速度,但这会牺牲数据的*滑度。这是 Whittaker 的一个非常好的特点——它的计算时间随着数据长度线性增加(O(n)),而且你无需担心窗口大小。此外,如果你的数据中有间隙,你将以无速度损失的方式进行插值,而其他方法则需要某种形式的预处理!
数学原理
现在我们已经覆盖了主要内容,让我们深入探讨 Whittaker-Eilers *滑器背后的数学,并了解它为何对噪声数据是如此优雅的解决方案 [2] [3]。
想象一下你的噪声数据y。存在一些序列z,你认为它具有对你的y的最佳*滑度。z变得越*滑,它与原始数据y之间的残差越大。Whittaker-Eilers 方法在这些残差和数据的*滑度之间找到最佳*衡。残差计算为标准的*方差之和,

数据*滑度的度量可以使用相邻测量值之间的*方差之和来计算,

S 和 R 是我们需要*衡的两个属性。但我们还希望让用户控制合适的*衡点,我们通过引入λ来调整*滑度。

现在我们的目标变成了找到能最小化 Q 的序列 z,因为这是*滑度度量和残差都达到最小的地方。让我们展开方程 3 并尝试解出 z。


此时,理想的做法是用向量替换我们的求和,

然后我们可以使用一个巧妙的技巧,将 Δz 表示为矩阵和向量,


其中 m 是数据的长度。如果你将矩阵 D 与一个向量进行比较,你会发现它给出了相邻元素之间的差异——这正是我们想要的。现在我们面临一个最小二乘问题。为了找到 Q 的最小值,我们将其梯度设为 0,


其中 I 是单位矩阵(来自分解 z,一个向量)。我们知道 I、D、λ 和 y,所以我们剩下一个简单的线性方程,

这可以通过你喜欢的任何矩阵分解方法来解决,从而获得*滑的数据系列 z。
插值
上述解决方案仅适用于数据均匀分布且所有测量值都可用的情况。如果需要插值怎么办?你需要为每个测量值应用权重。
就像重新审视方程 1 并为每个残差应用权重,并将其表示为对角矩阵一样简单,

然后进行与之前相同的计算,



再次,这可以通过简单的矩阵分解来解决,返回*滑和插值的数据。所有需要做的事是在需要插值的地方将 y 填充为虚拟值,例如 -999,并将这些测量的权重设置为 0,然后观察奇迹发生。数据如何插值取决于滤波器的阶数。
滤波器阶数
Whittaker-Eilers *滑器的阶数是在配置部分提到的内容。现在我们有了描述*滑器的数学框架,它可能更有意义。在创建 R(我们的*滑度度量)时,我们首先选择了“一级”差异。我们可以相当容易地计算二阶差异,这样不仅根据相邻数据点对*滑器进行惩罚,还可以根据一阶差异的变化来惩罚它,就像计算导数一样。

这可以扩展到三阶、四阶、五阶差分,等等。通常表示为d,实现起来并不太复杂,因为唯一改变的是矩阵D,如下所示:

使其在与z相乘时展开为方程 17。可以实现一个简单的函数来生成这个矩阵,给定一个通用的d。
稀疏矩阵
这可以并且已经按照 Eilers 的建议使用稀疏矩阵来实现。矩阵I和D非常稀疏,如果以稀疏矩阵的形式存储,将极大地节省内存和计算资源。上述所有数学计算都可以通过稀疏矩阵包轻松处理,包括 Cholesky 分解(及其他)。如果不使用稀疏矩阵实现,这个算法在处理较长时间序列时可能会非常缓慢,比我比较的其他方法慢得多。
总结与进一步阅读
这是一个了不起的算法,我简直不敢相信它没有被更广泛地应用。加权*滑和插值被封装成快速、高效的矩阵运算。有什么不好的吗?
我在whittaker-eilers包的 repo 中包含了我用于进行基准测试和插值测试的 Python 脚本。还有许多示例展示了如何在 Python 或 Rust 中入门,以及与 Eilers 原始 MATLAB 算法的测试[1]。但如果你不在意那么详细的信息,
Python: [*pip install whittaker-eilers*](https://pypi.org/project/whittaker-eilers/) 或 Rust: [*cargo add whittaker-eilers*](https://crates.io/crates/whittaker-eilers)
尽管这是一篇长文章,我仍然没有涵盖所有内容。Eilers 2003 年的论文还涉及了*滑不均匀间隔数据的数学原理以及如何使用交叉验证来找到最佳λ。如果你想深入了解算法背后的数学,我推荐查看这篇论文。我还建议阅读 Sophocles J. Orfanidis 的《应用最优信号处理》,它提供了关于所有信号处理内容的深入数学指南。感谢阅读!请务必查看我的个人网站上的这篇文章和其他文章。
我之后写了一篇跟进文章,解释了如何最佳地调整 Whittaker!
利用 Whittaker-Eilers *滑和留一交叉验证最大化数据价值
[towardsdatascience.com
本文中的所有图像均由作者制作。
参考文献
[1] Paul H. C. Eilers, 完美*滑器, Analytical Chemistry 2003 75 (14), 3631-3636, DOI: 10.1021/ac034173t
[2] NASA/GISS,《全球温度》,NASA 戈达德太空研究所(GISS)。网址:climate.nasa.gov/vital-signs/global-temperature/
[3] 索福克勒斯·J·奥尔法尼迪,《应用最优信号处理》,罗格斯大学,网址:www.ece.rutgers.edu/~orfanidi/aosp
使我获得数据科学家职位的作品集
原文:
towardsdatascience.com/the-portfolio-that-got-me-a-data-scientist-job-513cc821bfe4
剧透:制作作品集意外地简单(而且免费)
·发表于Towards Data Science ·10 分钟阅读·2023 年 3 月 24 日
--

照片由John Schnobrich拍摄,发布在Unsplash
获得数据科学家职位很难。
现在已经不是 2015 年了:仅仅知道几个 pandas 函数并在简历上写上“Big Data”已经不够了。顶级职位的竞争非常激烈。在最*浏览 LinkedIn 职位板时,我发现很难找到申请人数少于 100 人的伦敦数据科学家职位。
好消息是,这种竞争并不是因为工作岗位不足。即使在 2023 年,数据科学仍然是一个快速增长的领域,美国劳工统计局估计数据科学家职位的数量将在 2021 年至 2031 年间增长 36%[1]。
因此,挑战不在于数据科学工作岗位的缺乏——实际上有很多!而在于大量的人试图进入这个行业,使得在众多候选人中脱颖而出并获得那个丰厚的首个职位变得越来越困难。
解决方案:建立数据科学作品集
在当前的就业市场中,我相信,区别于竞争对手的最佳方法之一就是通过建立个人数据科学项目的作品集来展示你的技能和经验。如果你没有大量的数据科学工作经验,这一点尤其重要。借用数据科学家 Will Stanton 的话[2]:
如果你没有数据科学家的经验,那么你绝对必须做独立项目。
在这篇文章中,我将带你了解我创建个人项目作品集的过程,并尝试为你提供一些灵感,帮助你自己创建一个作品集。
如果创建作品集的想法让你感到有些畏惧,不用担心:建立一个作品集其实出乎意料的简单(而且免费的),而且你不需要任何网页开发知识就能开始。
我的背景(也就是我创建我的作品集的原因)
我的数据科学之旅相对较晚才开始。我刚从学校/大学毕业时的第一份工作是在项目管理、销售和市场营销等领域。当我决定在 2020 年转向数据科学时,我几乎没有实际操作数据的经验,也没有计算机科学或统计学等领域的技术本科学位。
哇,这不是一个很强的起点。
在我之前的文章中,我分享了我如何通过参加一系列在线课程、攻读数据科学硕士学位和做几次数据科学实习来解决这个问题。在此过程中,我参与了许多不同的项目,并建立了广泛的数据相关技能。
然而,对于我申请的前几份工作,我发现我很难获得面试机会:感觉招聘人员甚至没有仔细查看我的申请,尽管我符合所有最低要求。那时,这非常令人沮丧。我相信我有很多可以提供的,但似乎对话还没开始就被中断了。
更糟糕的是,我了解到招聘人员通常会在过度申请的职位发布上花费仅 6 到 8 秒来查看简历[3]。这让我相信我需要一种更快的方式来传达我的技能并脱颖而出,特别是考虑到我作为数据科学家的经验并不丰富。
为了解决这个问题,我决定创建一个在线作品集,以一种视觉上引人注目且易于消化的格式展示我的经验。

我的数据科学作品集:mattschapman.github.io/。图片来源:作者自制。
然后,一旦我建立了我的作品集,我在简历的顶部包含了一个链接,以便招聘人员可以轻松找到:

我的简历/履历的标题。我喜欢有一个可以点击的链接或二维码,直接带雇主到我的作品集,这样可以帮助我从人群中脱颖而出。图片来源:作者自制。
我是如何创建我的作品集的
在创建我的作品集时,我遵循了一些基本原则:
-
包括一些真正独特且有趣的项目
-
保持简短和简单
-
让它变得美观
-
不要在“网页开发”方面浪费时间
在本文的其余部分,我将简要讨论每一项原则,并解释它们如何帮助我创建一个成功的作品集。
包括一些真正独特且有趣的项目
这可能听起来显而易见,但这是一个如此重要的点,以至于值得重复:
不要包含别人也在包含的内容。
如果你作品集的全部目的就是区分自己,那为什么还要包括那些已经被别人做得死去活来的通用项目呢?这是 Jeremie Harris 在 数据科学家不被聘用的 4 种最快方式 [4] 中提到的一个观点,他说:
很难想到比在突出个人项目中展示你在琐碎的概念验证数据集上的工作更快地将你的简历扔进‘绝对不考虑’堆的方法。
如果有疑问,以下这些项目会让你受害多于受益:
使用 Titanic 数据集 进行生存分类。
使用 MNIST 数据集 进行手写数字分类。
使用 iris 数据集 进行花卉物种分类。
这意味着,如果你对计算机视觉非常感兴趣,就包括几个相关的项目。或者,如果你对时间序列问题情有独钟,就加上一些相关主题的项目。说实话,无论你对贝叶斯优化还是编织篮子感兴趣,关键是确保它是你真正感兴趣并认为能给别人带来价值的东西。就我个人而言,我非常关注自然语言处理和社会科学相关的问题,因此我尝试包括了这些领域的项目。但说实话,只要你能令人信服地展示为什么它们能带来价值,内容的具体细节其实并不是那么重要。
保持简洁明了
没有人愿意在无聊或冗长的网页上花时间,这在在线作品集中尤为真实。你提供的是一个快照,而不是写一篇博士论文。我诚心认为,如果你花太多时间编写冗长的项目描述和结果部分,你会浪费时间。
要理解这一点,我们必须回到作品集的初衷,其核心是一个简单的想法:
作品集的目的是为了让你有机会进入面试,而不是直接获得工作。
换句话说,你绝不会仅凭作品集就收到工作邀请。现实是,即使招聘人员喜欢你作品集的外观,你仍然需要通过他们的面试和评估。作品集的目的是提供你技能的快速快照,展示你能做什么。
在我的作品集中,例如,我提到了我之前参与的七个项目。在这七个项目中,六个项目的描述都不超过两句简短的话。
是的,你没看错:两句话。
这是我作品集中一个项目描述的例子:

我作品集中的一个示例项目。图片来源:作者自有。
如你所见,我没有浪费时间设置背景和解释结果的细微差别。当然,这些内容非常重要。但是如果潜在的雇主感兴趣,他们会在面试中询问你。在这个阶段,你只需要一个简短的描述,能够引起他们的兴趣并促使进一步讨论。如果你愿意,你甚至可以包括一个指向更多描述的链接;这也是我通过在项目描述下方包含代码链接所做的。但在你作品集的主页上,你只需要提供高层次的细节。其他的都是浪费时间。
让它变得漂亮
我的下一个建议是使你的作品集具有视觉吸引力。
设身处地为一个潜在的招聘者考虑:你已经阅读了数百(甚至数千)份*淡无奇的简历,甚至都快忘记绿色是什么样子,因为已经很久没见过不是黑白的东西了。然后,突然出现一份简历/作品集——简直是开门见山——其实挺好看的。
你不需要是火箭科学家(甚至不需要是数据科学家)就能明白招聘者会喜欢看你的作品集。
要让你的作品集更漂亮,我的首要建议是包含大量有趣的图表/图形。例如,这里是我在作品集中包含的另一个项目的描述,其中包含了一些关于 Covid-19 限制和封锁期间人口流动的图表:

我作品集中的一个项目描述。图片来源:作者自有。
图表向潜在雇主展示了我知道如何以视觉方式传达想法并制作漂亮的图像。这可能看起来微不足道,但实际上并非如此:作为数据科学家,你会经常需要向非技术性利益相关者传达你的发现。通过在作品集中包含一些图表,你向读者展示了你认识到沟通的重要性,并具备将其付诸实践的技能。换句话说,这是展示你技能并在竞争激烈的领域中脱颖而出的绝佳方式。
不要在网页开发上浪费不必要的时间
我的最后一个建议是不要花太多时间实际“构建”包含你作品集的网站。记住:你申请的是数据科学家的职位,而不是网页开发人员。你的评价标准不会是 HTML 和 CSS 技能,而是你的数据科学技能。
因此,除非你在网页开发方面有丰富的经验,否则我建议选择低代码/无代码选项来构建你的网站,例如使用像WordPress或Webflow这样的工具,它们允许你“拖放”作品集的各个部分,并从一系列预制模板中选择。

数据科学作品集的选项范围。图像来源:作者自制。
就我而言,我选择了一些不同的东西,并遵循了 这篇 由 Ivanna Kacewica 撰写的优秀指南,她使用了 Minimal Jekyll 主题 来为 GitHub Pages 构建网站的基本框架 [5]。我选择这个选项的主要原因是 (a) 它是免费的,(b) 它不要求你在 URL 中包含广告或“wordpress”字样。
Ivanna 方法的一个额外好处是它允许你在 GitHub Pages 上免费托管你的作品集,地址为username.github.io,其中username是你在 GitHub 上的用户名(或组织名称)。由于你的 GitHub 用户名包含在 URL 中,别人很容易找到你的 GitHub 账户,这对于你在 GitHub 上存储的其他项目非常有用。
我不会详细介绍如何使用 Ivanna 的主题(因为她解释得非常好),但这种方法的基本理念是通过 Jekyll 和 markdown 添加文本和图像。如果你不熟悉 Jekyll,它是一个简单的构建网站的框架,不需要编程或 HTML/网页开发知识。
使用 Jekyll,你建立网站的基本结构,然后用 markdown 填充文本和图像。如果你以前没用过 markdown,不用担心——markdown 只是一个非常简单的文本格式化方式。当我说“简单”时,我是指非常简单:例如,要写标题,你只需在文本前面加上一个井号。要制作项目符号,你只需在文本前面加上一个星号。就是这么简单。

如何使用 markdown 添加内容。图像来源:作者自制。
如果你想查看我如何自定义主题的详细信息,可以查看我 GitHub 上的 index.md 文件 repository。
结论
构建在线作品集是在人满为患的数据科学就业市场中脱颖而出的好方法。在这篇文章中,我提供了一些关于如何构建我的个人作品集的见解以及构建你自己作品集的最佳建议。
告诉我你的进展情况,如果你想获得关于你的作品集的反馈或建议,可以在评论中留下链接,我一定会查看。
如果你想获取更多关于如何构思作品集的建议,你可能会想看看我的其他文章:
忘掉泰坦尼克号和 MNIST:选择一个独特的项目来提升你的技能,并让你从人群中脱颖而出
towardsdatascience.com
哦,还有一件事 —
我开设了一个免费的通讯,AI in Five,每周分享 5 个要点,涵盖最新的 AI 新闻、编码技巧和数据科学家/分析师的职业故事。没有炒作,没有“数据是新石油”的废话,也没有来自埃隆的推文——只有实际的技巧和见解,帮助你在职业生涯中发展。如果这对你来说合适,点击这里订阅!
[## AI in Five | Matt Chapman | Substack
最新的新闻、职业故事和编码技巧,来自数据科学和 AI 领域,浓缩为 5 个要点……
来源
[1] 美国劳工统计局。职业前景手册:数据科学家。(2022 年 9 月 8 日)。www.bls.gov/ooh/math/data-scientists.htm
[2] Will Stanton. 创建一个出色的数据科学博客。(2015 年 7 月 15 日)。will-stanton.com/creating-a-great-data-science-resume/
[3] Standout CV. 招聘人员查看你的简历的时间有多长?(2022 年 11 月)。standout-cv.com/how-long-recruiters-spend-looking-at-cv#:~:text=Research%20shows%20that%20recruiters%20spend,15%20minutes%20reviewing%20a%20CV。
[4] Jeremie Harris. 作为数据科学家被拒绝的 4 种最快方式。(2018 年 6 月 12 日)。towardsdatascience.com/the-4-fastest-ways-not-to-get-hired-as-a-data-scientist-565b42bd011e
[5] Ivanna Kacewica. 使用 Github Pages 在不到 10 分钟内建立你的作品集网站。(2019 年 3 月 1 日)。medium.com/@evanca/set-up-your-portfolio-website-in-less-than-10-minutes-with-github-pages-d0efa8ff56fd
使用蒙特卡罗模拟传播误差的力量和简单性
在数据分析和模型拟合中掌握不确定性,包括动手代码和示例
LucianoSphere (Luciano Abriata, PhD)
·发表在Towards Data Science ·15 分钟阅读·2023 年 7 月 21 日
--

关于蒙特卡罗方法的内容在Towards Data Science上有相当多的介绍,但关于它在误差传播中非常重要且有用的应用介绍却不多,除了Shuai Guo的精彩介绍以及其他几篇文章:
演示蒙特卡罗模拟
towardsdatascience.com
在这里,我想提出一些具体的数值应用和代码,供你实际尝试,并亲身感受蒙特卡罗方法在几乎任何类型的计算中如何极其有用且易于实现。
我将从一个非常简单的应用开始,传播减法操作中的误差,然后举例说明如何用基本相同的思路来传播几乎任何类型的数值例程中的误差,从简单的线性回归到复杂的拟合过程,这些过程在解析上很难处理。
蒙特卡罗模拟中的误差传播
误差传播是数据分析和科学计算中的一个基本概念。当你有带有不确定性的测量值时,对这些值进行数学运算会导致最终计算结果中的误差传播。对于简单的算术操作,误差传播可以使用公式进行解析。如果你对解析误差传播感兴趣,可以查看这个资源:
版权 © 2000 年 7 月 1. 系统误差与随机误差 2. 确定随机误差 (a) 仪器误差极限…
然而,对于涉及多个变量和非线性函数的复杂操作,或者对于数据拟合或神经网络执行等大型计算程序,解析误差传播可能很快变得不切实际甚至不可能。
蒙特卡洛模拟提供了一种替代的误差传播方法,特别是在解析解决方案具有挑战性或未知的情况下。蒙特卡洛方法涉及使用随机抽样来模拟大量场景,每次计算所需的量,然后分析结果的分布。这种统计方法提供了对最终结果不确定性的估计,使我们能够在复杂计算中传播误差。
为了演示蒙特卡洛模拟在误差传播中的应用,我们考虑一个非常简单的减法操作a - b,其中a和b都有一些相关的不确定性da和db。
(注:本文及所有后续示例均使用 Matlab 编写,但可以轻松移植到任何其他脚本语言。)
clear; close all; clc
a = 8; da = 2; % Example a = 8 with uncertainty 2
b = 4; db = 1; % Example b = 4 with uncertainty 1
c = a — b; dc = sqrt(da*da + db*db); % Analytical error propagation
disp(‘Expected’)
disp([num2str(c), ‘ +/- ‘, num2str(dc)]) % Print
fprintf(‘\n’)
% =============== Start here Monte Carlo simulation
c_mc = []; % Will hold all results
for i=1:100000
a_mc = normrnd(a, da); % Get a random number with a Gaussian distribution around a
b_mc = normrnd(b, db); % Same around b
c_mc = [c_mc; a_mc — b_mc]; % Run calculation and store
end
disp(‘From MonteCarlo with 10 iterations’)
disp([num2str(mean(c_mc(1:10))), ‘ +/- ‘, num2str(std(c_mc(1:10)))]) % Print result for 10 iterations
fprintf(‘\n’)
disp(‘From MonteCarlo with 100 iterations’)
disp([num2str(mean(c_mc(1:100))), ‘ +/- ‘, num2str(std(c_mc(1:100)))]) % Result for 100 iterations
fprintf(‘\n’)
disp(‘From MonteCarlo with 1000 iterations’)
disp([num2str(mean(c_mc(1:1000))), ‘ +/- ‘, num2str(std(c_mc(1:1000)))]) % Result for 1000 iterations
fprintf(‘\n’)
disp(‘From MonteCarlo with 10000 iterations’)
disp([num2str(mean(c_mc(1:10000))), ‘ +/- ‘, num2str(std(c_mc(1:10000)))]) % Result for 10000 iterations
fprintf(‘\n’)
disp('From MonteCarlo with 100000 iterations')
disp([num2str(mean(c_mc(1:100000))), ' +/- ', num2str(std(c_mc(1:100000)))]) % Result for 100000 iterations
fprintf('\n')
在这个示例中,不确定性既通过解析方法传播,也通过蒙特卡洛程序传播,然后对其结果进行比较。首先,将c上的不确定性解析传播为a和b的*方不确定性之和的*方根。
接下来是蒙特卡洛模拟。我们从以各自值为中心的高斯分布中随机抽取a和b,其标准差等于它们的不确定性。样本值存入临时变量a_mc和b_mc中,这些变量的差值存储在数组c_mc中。我们重复这个过程很多次,这里是 100,000 次,然后计算这个数组的*均值和标准差,以估计c的中心值及其相关的不确定性。这个示例程序报告了在 10 次、100 次、1000 次、10000 次和 100000 次迭代后的*均值和标准差,从中我们可以评估估计的收敛情况。
以下是一个示例运行的结果:
预期
4 +/- 2.2361
从蒙特卡洛模拟(10 次迭代)中得出的结果
3.2695 +/- 3.1021
从蒙特卡洛模拟(100 次迭代)中得出的结果
3.8985 +/- 2.5303
从蒙特卡洛模拟(1000 次迭代)中得出的结果
3.9573 +/- 2.2683
从蒙特卡洛模拟(10000 次迭代)中得出的结果
4.0182 +/- 2.2139
从蒙特卡洛模拟(100000 次迭代)中得出的结果
3.9984 +/- 2.2439
如你所见,计算结果和从解析传播中预期的误差(2.2361)经过蒙特卡洛过程的迭代后均合理收敛。特别注意误差如何下降并非常接*解析传播值。同时,即使是 1000 次迭代也已经产生了一个非常合理的误差估计。
请注意,如果你自己运行代码,当然会得到不同的数字,但趋势应该始终存在:随着迭代次数的增加,你的误差应该收敛到类似于示例中报告的值(以及解析推导值)。
再提供两个例子,现在估计通过数据拟合获得的参数的不确定性
在上一节中,我们探讨了蒙特卡洛模拟如何用于传播简单算术操作中的误差。现在,让我们深入研究更实际的例子,应用蒙特卡洛模拟来估计通过数据拟合获得的参数的不确定性。
在这个例子中,我们将考虑线性拟合过程。给定一组数据点 (x, y),我们希望找到形式为 y = ax + b 的最佳拟合直线。然而,现实世界的数据通常包含测量误差或其他不确定性,这可能会影响拟合参数 (a 和 b) 的准确性。为了考虑这些不确定性并获得更可靠的估计,我们可以使用蒙特卡洛模拟。
在这个例子中,我们生成一些样本数据,并添加以 0 为中心、宽度为 +/- 1 的高斯噪声以模拟测量误差。这成为了接下来的实验向量 y 值。
clear; close all; clc
a = 5; b = 4; % Slope and intercept
x = [1:10]; % x from 1 to 10
y = a * x + b + normrnd(0, 1, 1, 10); % Simulate data and add Gaussian noise centered at 0 and with a width of +/- 1
首先,我们使用 Matlab 的 fitlm 函数进行线性回归,该函数计算最佳拟合直线并提供解析推导的斜率和截距及其不确定性:
analyticfit = fitlm(x,y) % fitlm performs a linear fit and displays the results, which include
% the analytically-derived slope and intercept and their uncertainties
该过程的输出结果如下:
analyticfit =
Linear regression model:
Estimated Coefficients:
Estimate SE tStat pValue
________ _______ ______ __________
(Intercept) 3.6469 0.94449 3.8612 0.0048006
x1 5.0534 0.15222 33.198 7.3956e-10
Number of observations: 10, Error degrees of freedom: 8
Root Mean Squared Error: 1.38
R-squared: 0.993, Adjusted R-Squared: 0.992
F-statistic vs. constant model: 1.1e+03, p-value = 7.4e-10
因此,你可以看到在这一轮运行和生成器提供的随机数中,解析拟合得出的 斜率 = 5.0534 +/- 0.15222 和 截距 = 3.6469 +/- 0.94449
现在,让我们看看 Monte Carlo 程序的发现。只需按照我们传播减法操作不确定性的相同思路,我们必须取 x 和 y 向量,随机添加噪声(这里仅对y进行,因为我们假设x没有误差,但我们也可以完美地对其添加噪声!),然后运行拟合程序 10,000 次。这次我们使用polyfit,一个用于拟合多项式(这里是 1 次)的 Matlab 函数,它不返回拟合参数的不确定性——这些我们将通过 Monte Carlo 获得。
所以,就像在前面的示例中一样,我们添加噪声,运行线性拟合,并将参数存储在专门用于斜率(a_mc)和截距(b_mc)的向量中:
a_mc = []; % Vector with slopes from Monte Carlo run
b_mc = []; % Vector with intercepts from Monte Carlo run
for i=1:10000 % 10k iterations, and in each:
yy = normrnd(y,1); % Create temporal vector where noise is added to the experimental y values
P = polyfit(x,yy,1); % Fit that vector to x using linear regresion as a polynomial of degree 1
b_mc = [b_mc; P(1)]; % Store all a values
a_mc = [a_mc; P(2)]; %
end
fprintf(‘\n’)
disp(‘From Monte Carlo with 10 iterations’)
disp([‘Intercept = ‘, num2str(mean(a_mc(1:10))), ‘ +/- ‘, num2str(std(a_mc(1:10)))])
disp([‘Slope = ‘, num2str(mean(b_mc(1:10))), ‘ +/- ‘, num2str(std(b_mc(1:10)))])
fprintf(‘\n’)
disp(‘From Monte Carlo with 100 iterations’)
disp([‘Intercept = ‘, num2str(mean(a_mc(1:100))), ‘ +/- ‘, num2str(std(a_mc(1:100)))])
disp([‘Slope = ‘, num2str(mean(b_mc(1:100))), ‘ +/- ‘, num2str(std(b_mc(1:100)))])
fprintf(‘\n’)
disp(‘From Monte Carlo with 1000 iterations’)
disp([‘Intercept = ‘, num2str(mean(a_mc(1:1000))), ‘ +/- ‘, num2str(std(a_mc(1:1000)))])
disp([‘Slope = ‘, num2str(mean(b_mc(1:1000))), ‘ +/- ‘, num2str(std(b_mc(1:1000)))])
fprintf(‘\n’)
disp(‘From Monte Carlo with 10000 iterations’)
disp([‘Intercept = ‘, num2str(mean(a_mc(1:10000))), ‘ +/- ‘, num2str(std(a_mc(1:10000)))])
disp([‘Slope = ‘, num2str(mean(b_mc(1:10000))), ‘ +/- ‘, num2str(std(b_mc(1:10000)))])
fprintf(‘\n’)
这是我获得的一组示例结果:
来自 Monte Carlo,10 次迭代
截距 = 3.9435 +/- 0.97266
斜率 = 5.0174 +/- 0.16053
来自 Monte Carlo,100 次迭代
截距 = 3.581 +/- 0.69941
斜率 = 5.0716 +/- 0.11592
来自 Monte Carlo,1000 次迭代
截距 = 3.6461 +/- 0.69365
斜率 = 5.0551 +/- 0.11206
来自 Monte Carlo,10000 次迭代
截距 = 3.6482 +/- 0.68509
斜率 = 5.0533 +/- 0.10918
我们观察到,随着迭代次数的增加,Monte Carlo 对截距和斜率的估计逐渐接*通过fitlm获得的分析值。从 Monte Carlo 模拟中获得的不确定性(标准差)与fitlm提供的标准误差一致,确认了 Monte Carlo 方法在传播拟合参数误差方面的准确性。
然而,使用 Monte Carlo 方法的主要优点之一是我们可以选择输入的误差是什么,我们不仅可以考虑观测值y上的不确定性,还可以考虑自变量上的不确定性。方法的这些和其他特性允许更好、更受控且知识驱动的不确定性量化和传播。 (参见文末专门讨论这些和其他方法优点以及缺点的部分。)
一个更复杂的例子,来自实际应用
我们刚才用于线性程序的相同方法,实际上可以应用于几乎任何类型的计算,特别适用于复杂的拟合程序,它可以提供有关模型参数和预测可靠性的宝贵见解。
虽然上述示例主要是为了演示,但现在让我给你展示一个来自我自己研究的例子,在这个例子中,Monte Carlo 模拟是唯一实际可行的方法来将误差传播到拟合参数中。故事始于我需要拟合一个相当复杂的方程,其形式如下:

基础数据包括一个独立变量(我们的x),在这里是(1/tcp),假设没有噪声,以及一个被称为y的依赖变量,称为 R₂。我的实验数据集包括多个 x, y 值,报告了不同相关系统的结果,所有这些数据必须一起拟合,以生成 kₑₓ, pA, pB, R₂A⁰, R₂B⁰和 d𝓌的单一值。所有这些都在这篇文章中解释了,我使用了 Matlab 强大的nlinfit函数:
[## Matlab 的 nlinfit 进行高效非线性函数拟合
使用 Matlab 的 nlinfit 函数对复杂函数进行强大而多功能的拟合
pub.towardsai.net](https://pub.towardsai.net/efficient-nonlinear-function-fitting-with-matlabs-nlinfit-3a2948c0fde6?source=post_page-----9c8dcca9d90d--------------------------------)
在那篇文章中,我没有深入探讨误差估计,因为 Matlab 的nlinfit函数返回直接提供误差的矩阵……但是从数据中直接得到的,且没有办法告诉系统应该考虑输入数据中的具体误差。即,拟合参数的不确定性可以像这样一次性计算:
[b1,res,J] = nlinfit(x,y,’AuxiliaryFunctionForNlinfit’,guesses);
b1 = b1';
dof = length(x)-length(b1);
sfit1 = norm(res)/sqrt(dof);
sd1 = sfit1*sqrt(diag(inv(J’*J)));
现在,使用蒙特卡罗方法,我们可以进行更灵活和有意义的误差传播。例如,我们可以告诉程序,较大的y(R₂)具有更高的不确定性(实验固有的):
for mcIteration = 1:10000
mcIteration % Prints in what iteration the loop is
for j=1:numel(yexp) % Take experimental y values and add noise (more noise when higher)
yy(j) = normrnd(yexp(j), yexp(j) * 0.05); % In the example, the error is 5 % of the value
end
[b1,res,J]=nlinfit(x,yy,’AuxFunction’,beta0, opts);
predictedy = AuxFunction(b1,x);
mc_results = [mc_results; b1];
end
disp([‘-’])
disp([‘-’]) % Printe the results of the simulation
disp([‘================== MONTE CARLO SIMULATION’])
disp([‘R21 = ‘,num2str(mean(mc_results(:,1))), ‘ +/- ‘, num2str(std(mc_results(:,1)))])
disp([‘R22 = ‘,num2str(mean(mc_results(:,2))), ‘ +/- ‘, num2str(std(mc_results(:,2)))])
disp([‘kEX = ‘,num2str(mean(mc_results(:,3))), ‘ +/- ‘, num2str(std(mc_results(:,3)))])
disp([‘dw = ‘,num2str(mean(mc_results(:,4))), ‘ +/- ‘, num2str(std(mc_results(:,4)))])
disp([‘p1_0 = ‘,num2str(mean(mc_results(:,5))), ‘ +/- ‘, num2str(std(mc_results(:,5)))])
disp([‘p1_20 = ‘,num2str(mean(mc_results(:,6))), ‘ +/- ‘, num2str(std(mc_results(:,6)))])
disp([‘p1_40 = ‘,num2str(mean(mc_results(:,7))), ‘ +/- ‘, num2str(std(mc_results(:,7)))])
disp([‘p1_50 = ‘,num2str(mean(mc_results(:,8))), ‘ +/- ‘, num2str(std(mc_results(:,8)))])
你可以看到,这个过程非常简单,和之前完全一样:只需多次运行拟合,每次使用一些受噪声影响的输入数据,反映出其实际的不确定性。最后,通过计算均值和标准差来汇总结果,以估算最终值。
这是一个输出示例,比较了基于雅可比矩阵的误差估计(没有考虑每个测量的具体不确定性)和 10000 次蒙特卡罗模拟的输出:
================== 蒙特卡罗模拟(10,000 次运行)
R21 = 0.8034 +/- 0.11613
R22 = 32.5616 +/- 34.5635
kEX = 6983.9807 +/- 595.5142
dw = 519.5121 +/- 92.0618
p1_0 = 1.0092 +/- 0.0034707
p1_20 = 0.86244 +/- 0.026407
p1_40 = 0.69446 +/- 0.066818
p1_50 = 0.58735 +/- 0.096593
================== 使用雅可比矩阵误差的普通拟合
R21 = 0.31138 +/- 0.25225
R22 = 33.0533 +/- 4.7355
kEX = 6965.3075 +/- 273.3825
dw = 513.7739 +/- 21.398
p1–0 = 1.0013 +/- 0.0049281
p1–20 = 0.86324 +/- 0.015702
p1–40 = 0.71381 +/- 0.03781
p1–50 = 0.62331 +/- 0.053877
你可以看到,收敛值和误差相当相似,但使用基于雅可比矩阵的常规误差传播时,误差系统地较小。了解这个问题背后的物理学,蒙特卡罗传播的误差更有意义,特别是 R21 的相对误差较低,而 R22 的相对误差较高。
(有关问题的完整解释、物理学、实验和计算,请关注我的 Medium 账户,我将很快撰写一篇专门的文章)。
附加内容:使用蒙特卡罗方法估计数值过程输出的不确定性的一些实际优缺点
以下优点使得蒙特卡罗模拟在各种数据分析和建模场景中成为一种强大且灵活的方法:
-
处理复杂误差分布: 蒙特卡罗方法的一个重要优点是其处理复杂误差分布的能力。在许多实际情况中,观察数据(y 值)和输入变量(x 值)的不确定性可能不遵循简单的高斯分布,这在大多数分析误差传播推导中被假设。通过使用蒙特卡罗模拟,我们可以轻松定制误差分布,以更好地代表我们数据的特定特征。这种灵活性使我们能够更准确地建模不确定性,从而改进参数估计和提高预测的可靠性。
-
误差建模的灵活性:
与一些假设特定误差模型的直接分析方法不同,蒙特卡罗模拟允许我们纳入各种误差结构。例如,我们可以包括异方差误差,其中误差的变异性随着不同的 x 值变化。此外,我们还可以考虑相关误差、非高斯分布以及其他可能在分析上难以处理的复杂情况。这种适应性使得蒙特卡罗方法能够处理更广泛的实际数据场景。
-
处理非线性模型:
在许多情况下,数值模型可能涉及变量之间复杂的非线性关系。分析误差传播方法通常仅限于线性模型,使其在非线性情境下不够适用。相比之下,蒙特卡罗模拟天生适用于非线性模型,因为它们依赖于数值采样而不是显式的分析推导。这种能力使我们能够在更复杂和现实的模型中评估参数的不确定性。
-
对假设的鲁棒性:
分析误差传播依赖于高斯误差和线性假设,这在实际中可能并不总是成立。当这些假设被违反时,分析结果的准确性可能会受到影响。而蒙特卡罗模拟则不依赖于这些假设,可以处理更广泛的数据分布和模型复杂性。这种鲁棒性确保了即使在传统方法可能失效的情况下,也能充分捕捉不确定性。
-
纳入先验信息:
在贝叶斯蒙特卡罗方法中,我们可以在拟合数据之前整合关于参数的先验知识或信念。当有来自之前研究或专家意见的先验信息时,这一特性特别有用。通过将先验分布纳入模拟,我们可以有效更新我们的参数估计和不确定性评估,从而得到更具信息性和准确的建模结果。
-
直观量化不确定性:
蒙特卡罗模拟生成参数估计的分布,使我们能够直观地理解可能值的范围和相关的不确定性。这种方法通常比从分析方法中获得的单点估计更具信息性,因为单点估计可能无法传达不确定性的全貌。通过可视化不确定性分布,决策者可以基于不同结果的概率做出明智的选择。
然而,蒙特卡罗方法并非万无一失,因此了解这些缺点至关重要:
-
计算强度:
蒙特卡罗模拟通常涉及大量迭代以获得准确结果。随着迭代次数的增加,计算时间和所需资源也显著增加。对于复杂模型和大量数据点,蒙特卡罗模拟可能变得计算上昂贵且耗时,这可能会阻碍其在某些情况下的实用性。
-
收敛性问题:
对采样分布的敏感性:
-
在某些情况下,蒙特卡罗模拟可能会遇到收敛性问题,尤其是在处理高维参数空间或复杂模型时。确保收敛并从参数空间中获得代表性样本可能具有挑战性,结果的质量取决于所使用的采样策略。收敛不足可能导致偏差或不可靠的不确定性估计。
蒙特卡罗结果的准确性和可靠性在很大程度上依赖于用于生成模拟随机样本的采样分布的选择。如果所选分布不能充分代表数据中的真实不确定性,它可能导致不准确或误导的不确定性估计。正确选择采样分布需要仔细考虑和对数据特征的了解。
-
模型选择与验证:
蒙特卡罗模拟不能提供模型选择或验证的指导。虽然它们可以估计拟合参数的 uncertainties,但并不解决模型适用性或拟合优度相关的问题。验证所选择的模型并评估其对给定数据的适当性仍然是需要与蒙特卡罗不确定性分析一起认真考虑的独立任务。
-
先验分布的主观性:
在贝叶斯蒙特卡洛方法中,纳入先验信息涉及为模型参数指定先验分布。这些先验分布的选择可能引入主观性,因为不同的研究人员或分析师可能有不同的先验信念。这种主观性可能会影响最终的参数估计和不确定性量化,导致结果可能存在分歧。
-
解释复杂性:
解读和传达蒙特卡洛模拟结果可能比传统的分析方法更具挑战性。不确定性分布可能不如点估计易于解释,分布中的大量信息可能会使非专家感到不知所措,而简单的误差传播可能就足够了。
-
过拟合风险:
使用蒙特卡洛模拟进行不确定性估计时,有过拟合数据的风险。运行大量迭代并拟合多个模型可能会无意中找到在特定模拟数据集上表现良好的模型,但对新数据泛化效果不佳。需要仔细的验证和交叉验证策略来降低这一风险。当然,还需要在解决问题的背景下判断结果的含义。
进一步阅读
想了解更多关于蒙特卡洛方法的内容,请查看这三篇发表在TDS Editors上的文章,这些文章介绍了除帮助传播不确定性以外的引言和应用:
A Zero-Math Introduction to Markov Chain Monte Carlo Methods [## 零数学介绍:马尔科夫链蒙特卡洛方法
对许多人来说,贝叶斯统计学充其量是巫术,最糟糕的是完全主观的无稽之谈。在这些…
An Overview of Monte Carlo Methods [## 蒙特卡洛方法概述
蒙特卡洛(MC)方法是计算算法的一部分,使用重复随机抽样的过程来…
An Overview of Monte Carlo Methods Monte Carlo Simulation [## 赌场总是赢钱:蒙特卡洛模拟
赌场如何赚钱?诀窍很简单——你玩得时间越长,亏钱的概率就越高。让我们…
www.lucianoabriata.com 我撰写和拍摄关于我广泛兴趣范围内的所有事物:自然、科学、技术、编程等。
在这里打赏我 或 成为 Medium 会员 以访问所有故事(我会获得少量收入,而您无需支付任何费用)。 订阅以获取我的新故事 通过电子邮件。 关于小工作咨询 请查看我的 服务页面。您可以 在这里联系我。
贝叶斯因果推断的力量:揭示数据集中隐藏因果关系的库的比较分析。
使用最适合的贝叶斯因果推断库揭示数据集中的隐藏因果变量:与五个流行库的实操示例进行比较。
·发表于 Towards Data Science ·阅读时间 20 分钟·2023 年 5 月 22 日
--

图片由 Alexander Schimmeck 提供,来源于 Unsplash
理解系统或过程中的变量因果效应非常有价值。现在有许多 Python 库可以帮助确定因果关系。我将比较五个流行的因果推断库在功能、易用性和灵活性方面的表现。 每个库都配有实际操作示例。包含的库有 Bnlearn, Pgmpy, CausalNex, DoWhy,和 CausalImpact。通过阅读本文,你将更好地理解这五个因果推断库,并确定哪个最适合你的使用场景。
背景
因果推断是确定过程或系统中变量之间的因果关系。一般来说,我们可以将变量分为两类;驱动变量和乘客变量。驱动变量是那些直接影响结果或因变量的变量,而乘客变量则是那些没有直接影响但与结果变量相关联的变量。
识别驱动变量在预测维护或安全领域等项目中可能至关重要。驱动变量有助于解释预测变量和结果变量之间的因果关系。相反,乘客变量对结果变量没有直接影响。然而,它们仍然可以有用,因为它们可以提供额外的变化,从而理解数据收集的背景。例如,如果我们发现引擎故障与地点有强相关性,我们可能会怀疑有一个潜在的驱动变量导致特定地点的故障。 因果推断有助于通过识别要操作的变量和要监控的变量来做出更好的决策。
在因果推断中,我们不仅寻求确定一个事件是否会发生,还要理解它发生的机制。
然而,因果推断分析是一项具有挑战性的任务,因为它需要分离多个变量的影响,考虑混杂变量,并处理不确定性。幸运的是,Python 有几个库可以帮助数据科学家进行因果推断。在这篇文章中,我将比较 Python 中五个最受欢迎的因果推断库:Bnlearn、 Pgmpy、 DoWhy、 CausalNex*** 和 CausalImpact。
如果你觉得这篇关于贝叶斯因果学习的文章有帮助,欢迎 关注我 ,因为我会写更多关于贝叶斯统计的内容。如果你考虑订阅 Medium 会员,可以通过我的 推荐链接 来稍微支持我的工作。这和买一杯咖啡的价格一样,你可以每月无限阅读所有文章。
五个因果推断库的比较。
让我们逐一检查这五个软件包,并从解决问题的能力角度审视它们的功能。为了最一致地比较这些库,我将尽可能使用相同的数据集,即普查收入数据 集。这个多变量数据集包含 14 个节点,具有 48842 个实例(或样本)[1]。每个节点包含两个或更多可能的状态,可以用来估计拥有研究生学位是否对年收入超过 50k 很重要。我们可以使用Bnlearn 库加载数据集。
# Installation
pip install bnlearn
# Import libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import bnlearn as bn
# Import data set and drop continous and sensitive features
df = bn.import_example(data='census_income')
drop_cols = ['age', 'fnlwgt', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week', 'race', 'sex']
df.drop(labels=drop_cols, axis=1, inplace=True)
df.head()
# workclass education ... native-country salary
#0 State-gov Bachelors ... United-States <=50K
#1 Self-emp-not-inc Bachelors ... United-States <=50K
#2 Private HS-grad ... United-States <=50K
#3 Private 11th ... United-States <=50K
#4 Private Bachelors ... Cuba <=50K
#
#[5 rows x 7 columns]
库 1:Python 的 Bnlearn。
Bnlearn 是一个适用于创建和分析贝叶斯网络的 Python 包,适用于离散、混合和连续数据集[2, 3]。它设计为易于使用,并包含了最受欢迎的贝叶斯管道,用于因果学习,包括结构学习、参数学习和推断。可以通过简单指定参数进行初始化来使用一系列统计测试。Bnlearn 还包含各种辅助函数,用于转换数据集、推导(整个)图的拓扑排序、比较两个图以及制作各种有洞察力的图表等。有关使用 bnlearn 进行结构学习的更多详细信息请参见这里:
## 使用贝叶斯结构学习在 Python 中检测因果关系的逐步指南。
入门指南,用于有效确定变量间的因果关系。
[towardsdatascience.com
Bnlearn 的一个重要功能是它可以仅基于数据集学习因果结构。为此任务实现了六种算法;hillclimbsearch, exhaustivesearch, constraintsearch, chow-liu, naivebayes 和 TAN,并且可以与评分类型 BIC, K2, BDEU 结合使用。一些方法需要设置根节点,例如增强型朴素贝叶斯(TAN),如果你知道结果(或目标)值的话,推荐使用。这也会显著降低计算负担,并且在特征很多的情况下推荐使用。此外,通过 independence test,可以轻松地从模型中修剪掉虚假边缘。在下面的示例中,我将使用 hillclimbsearch 方法和评分类型 BIC 进行结构学习。在这个示例中,我们不会定义目标值,而是让Bnlearn 完全基于数据自身决定整个因果结构。
# Load library
import bnlearn as bn
# Structure learning
model = bn.structure_learning.fit(df, methodtype='hillclimbsearch', scoretype='bic')
# Test edges significance and remove.
model = bn.independence_test(model, df, test="chi_square", alpha=0.05, prune=True)
# Make plot
G = bn.plot(model, interactive=False)
# Make plot interactive
G = bn.plot(model, interactive=True)
# Show edges
print(model['model_edges'])
# [('education', 'salary'),
# ('marital-status', 'relationship'),
# ('occupation', 'workclass'),
# ('occupation', 'education'),
# ('relationship', 'salary'),
# ('relationship', 'occupation')]
要确定有向无环图(DAG),我们需要指定输入数据框,如上面的代码部分所示。拟合模型后,结果存储在 model 字典中,可以用于进一步分析。因果结构的交互式图表如图 1 所示。

图 1. 使用 Bnlearn 进行结构学习的人口普查收入数据 集的交互式图表。如果学习到 CPD,工具提示将描述估计的 CPD(图片由作者提供)。
通过学习到的有向无环图(图 1),我们可以估计条件概率分布(CPD,见下方代码部分),并使用do-calculus 进行推断。换句话说,我们可以开始向数据提出问题。
# Learn the CPDs using the estimated edges.
# Note that we can also customize the edges or manually provide a DAG.
# model = bn.make_DAG(model['model_edges'])
# Learn the CPD by providing the model and dataframe
model = bn.parameter_learning.fit(model, df)
# Print the CPD
CPD = bn.print_CPD(model)
问题 1: 在教育为博士的情况下,薪水>50k 的概率是多少:
P(salary | education=Doctorate)
从直观上看,我们可能会期望高概率,因为教育是“博士”。让我们从贝叶斯模型中找出后验概率。在下面的代码部分,我们得出了P=0.7093的概率。这证实了当教育为博士时,与没有博士教育相比,薪水>50K 的概率更高。
# Start making inferences
query = bn.inference.fit(model, variables=['salary'], evidence={'education':'Doctorate'})
print(query)
+---------------+---------------+
| salary | phi(salary) |
+===============+===============+
| salary(<=50K) | 0.2907 |
+---------------+---------------+
| salary(>50K) | 0.7093 |
+---------------+---------------+
现在让我们询问一下较低教育是否也会导致薪水>50K 的概率较低。我们可以轻松地将教育更改为HS-grad,然后再次提出这个问题。
问题 2: 在教育为 HS-grad 的情况下,薪水>50k 的概率是多少:
*P(salary | education=*HS-grad*)*
这导致了P=0.1615的后验概率。根据这个数据集,学习对获得更高薪水非常有利。然而,请注意,我们没有使用任何其他约束作为可能影响结果的证据。
query = bn.inference.fit(model, variables=['salary'], evidence={'education':'HS-grad'})
print(query)
+---------------+---------------+
| salary | phi(salary) |
+===============+===============+
| salary(<=50K) | 0.8385 |
+---------------+---------------+
| salary(>50K) | 0.1615 |
+---------------+---------------+
直到这一部分,我们使用了单一变量,但 DAG 中的所有变量都可以用作证据。让我们做另一个更复杂的查询。
问题 3: 在教育为博士且婚姻状态为未婚的情况下,属于某个工作类别的概率是多少:
P(workclass| education=Doctorate, marital-status=never-married)。
在下面的代码部分可以看到,这返回了每个工作类别的概率,其中工作类别为private的概率最高:P=0.5639。
# Start making inferences
query = bn.inference.fit(model, variables=['workclass'], evidence={'education':'Doctorate', 'marital-status':'Never-married'})
print(query)
+-----------------------------+------------------+
| workclass | phi(workclass) |
+=============================+==================+
| workclass(?) | 0.0420 |
+-----------------------------+------------------+
| workclass(Federal-gov) | 0.0420 |
+-----------------------------+------------------+
| workclass(Local-gov) | 0.1326 |
+-----------------------------+------------------+
| workclass(Never-worked) | 0.0034 |
+-----------------------------+------------------+
| workclass(Private) | 0.5639 |
+-----------------------------+------------------+
| workclass(Self-emp-inc) | 0.0448 |
+-----------------------------+------------------+
| workclass(Self-emp-not-inc) | 0.0868 |
+-----------------------------+------------------+
| workclass(State-gov) | 0.0810 |
+-----------------------------+------------------+
| workclass(Without-pay) | 0.0034 |
+-----------------------------+------------------+
总结
-
输入数据:输入数据可以是离散的、连续的或混合的数据集。
-
优势: 包含最受欢迎的贝叶斯管道,用于结构学习、参数学习和使用do-calculus进行推断。可以轻松创建图表并探索 CPD。适合初学者和不想自己构建管道的专家。
库 2:Pgmpy。
Pgmpy 是一个提供概率图模型工具的库。它包含了各种统计方法的实现,用于结构学习、参数估计、*似(基于采样)和精确推断。一个优点是核心函数是低级统计函数,这使得构建自己的因果块更加灵活。虽然这很棒,但它需要对贝叶斯建模有很好的了解,因此相比于像Bnlearn这样的库,对于刚开始学习贝叶斯建模的人来说可能更困难。
在功能方面,可以得到与 Bnlearn 相同的结果,因为 Bnlearn 也利用了 Pgmpy 的一些核心功能。然而,在 pgmpy 中,你确实需要自己构建整个管道,包括数据所需的转换步骤,以及结果的收集和绘图。在下面的代码部分,我将简要演示如何使用 hillclimbsearch 估计器和评分方法 BIC 学习因果结构。请注意,不同的估计器可能在管道中需要非常不同的步骤。
# Install pgmpy
pip install pgmpy
# Import functions from pgmpy
from pgmpy.estimators import HillClimbSearch, BicScore, BayesianEstimator
from pgmpy.models import BayesianNetwork, NaiveBayes
from pgmpy.inference import VariableElimination
# Import data set and drop continous and sensitive features
df = bn.import_example(data='census_income')
drop_cols = ['age', 'fnlwgt', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week', 'race', 'sex']
df.drop(labels=drop_cols, axis=1, inplace=True)
# Create estimator
est = HillClimbSearch(df)
# Create scoring method
scoring_method = BicScore(df)
# Create the model and print the edges
model = est.estimate(scoring_method=scoring_method)
# Show edges
print(model.edges())
# [('education', 'salary'),
# ('marital-status', 'relationship'),
# ('occupation', 'workclass'),
# ('occupation', 'education'),
# ('relationship', 'salary'),
# ('relationship', 'occupation')]
使用 do-calculus 对数据进行推断时,我们首先需要估计 CPDs,如下方代码部分所示。
vec = {
'source': ['education', 'marital-status', 'occupation', 'relationship', 'relationship', 'salary'],
'target': ['occupation', 'relationship', 'workclass', 'education', 'salary', 'education'],
'weight': [True, True, True, True, True, True]
}
vec = pd.DataFrame(vec)
# Create Bayesian model
bayesianmodel = BayesianNetwork(vec)
# Fit the model
bayesianmodel.fit(df, estimator=BayesianEstimator, prior_type='bdeu', equivalent_sample_size=1000)
# Create model for variable elimination
model_infer = VariableElimination(bayesianmodel)
# Query
query = model_infer.query(variables=['salary'], evidence={'education':'Doctorate'})
print(query)
总结
-
输入数据:输入数据集必须是离散的。
-
优点: 包含可以用来创建自己贝叶斯管道的基础构建模块。
-
缺点: 需要对贝叶斯建模有良好的了解。
库 3:CausalNex。
CausalNex 是一个用于从数据中学习因果模型、识别因果路径和估计因果效应的 Python 库 [5]。Causalnex 仅支持离散分布,而连续特征或具有大量类别的特征应在拟合贝叶斯网络之前进行离散化。描述中提到:“如果使用了许多变量,模型通常会拟合较差”。然而,提供了辅助功能以减少分类特征的基数。让我们使用 Census Income data set 来检查 Causalnex 的可能性。首先,我们需要将数据转换为数值,因为这正是底层 NOTEARS [7] 算法所期望的。我们可以通过标签编码非数值变量来实现(见代码部分)。
# Installation
pip install causalnex
# Load libraries
from causalnex.structure.notears import from_pandas
from causalnex.network import BayesianNetwork
import networkx as nx
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
# Import data set and drop continous and sensitive features
df = bn.import_example(data='census_income')
drop_cols = ['age', 'fnlwgt', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week', 'race', 'sex']
df.drop(labels=drop_cols, axis=1, inplace=True)
# Next, we want to make our data numeric, since this is what the NOTEARS expect.
df_num = df.copy()
for col in df_num.columns:
df_num[col] = le.fit_transform(df_num[col])
我们现在可以应用 NOTEARS 算法来学习结构并使用绘图功能可视化因果模型。我们需要应用阈值处理以移除较弱的边缘,并防止图形完全连通。此外,为了避免伪边缘,可以对边缘施加约束。
# Structure learning
sm = from_pandas(df_num)
# Thresholding
sm.remove_edges_below_threshold(0.8)
# Use positions from Bnlearn
pos=G['pos']
# Make plot
plt.figure(figsize=(15,10));
edge_width = [ d['weight']*0.3 for (u,v,d) in sm.edges(data=True)]
nx.draw_networkx_labels(sm, pos, font_family="Yu Gothic", font_weight="bold")
nx.draw_networkx(sm, node_size=400, arrowsize=20, alpha=0.6, edge_color='b', width=edge_width)
# If required, remove spurious edges and relearn structure.
sm = from_pandas(df_num, tabu_edges=[("relationship", "native-country")], w_threshold=0.8)

图 3. 使用 Causalnex 对 Census Income data set 进行结构学习。没有节点的标签包含较弱的边缘,这些边缘通过阈值处理被移除。(图像作者提供。)
使用推导出的结构,我们可以学习条件概率分布(CPDs)并开始进行查询。然而,还需要进行一些额外的步骤,即减少分类特征的基数,并离散化数值特征。请注意,我们还需要以字典形式指定节点状态,以防止手动将数值转换为分类标签。尽管每个步骤都很重要,但为了简化起见,我将跳过这些步骤以避免代码行数过多。有关详细信息,建议阅读文档手册这里。它还演示了如何使用一些辅助方法来简化离散化过程。
# Step 1: Create a new instance of BayesianNetwork
bn = BayesianNetwork(structure_model)
# Step 2: Reduce the cardinality of categorical features
# Use domain knowledge or other manners to remove redundant features.
# Step 3: Create Labels for Numeric Features
# Create a dictionary that describes the relation between numeric value and label.
# Step 4: Specify all of the states that each node can take
bn = bn.fit_node_states(df)
# Step 5: Fit Conditional Probability Distributions
bn = bn.fit_cpds(df, method="BayesianEstimator", bayes_prior="K2")
# Return CPD for education
result = bn.cpds["education"]
# Extract any information and probabilities related to education.
print(result)
# marital-status Divorced ... Widowed
# salary <=50K ... >50K
# workclass ? Federal-gov ... State-gov Without-pay
# education ...
# 10th 0.077320 0.019231 ... 0.058824 0.0625
# 11th 0.061856 0.012821 ... 0.117647 0.0625
# 12th 0.020619 0.006410 ... 0.058824 0.0625
# 1st-4th 0.015464 0.006410 ... 0.058824 0.0625
# 5th-6th 0.010309 0.006410 ... 0.058824 0.0625
# 7th-8th 0.056701 0.006410 ... 0.058824 0.0625
# 9th 0.067010 0.006410 ... 0.058824 0.0625
# Assoc-acdm 0.025773 0.057692 ... 0.058824 0.0625
# Assoc-voc 0.046392 0.051282 ... 0.058824 0.0625
# Bachelors 0.097938 0.128205 ... 0.058824 0.0625
# Doctorate 0.005155 0.006410 ... 0.058824 0.0625
# HS-grad 0.278351 0.333333 ... 0.058824 0.0625
# Masters 0.015464 0.032051 ... 0.058824 0.0625
# Preschool 0.005155 0.006410 ... 0.058824 0.0625
# Prof-school 0.015464 0.006410 ... 0.058824 0.0625
# Some-college 0.201031 0.314103 ... 0.058824 0.0625
# [16 rows x 126 columns]
摘要
-
输入数据:输入数据集必须是离散的。连续数据不支持。
-
优点:可以学习因果网络并对数据进行推断。
-
缺点:需要大量预处理步骤,但提供了辅助功能以减少分类特征的基数。
库 4:DoWhy。
DoWhy是一个用于因果推断的 Python 库,支持因果假设的明确建模和测试[2, 3]。与 Bnlearn 和 Pgmpy 相比,在 DoWhy 库中,必须定义结果变量和处理变量。 处理变量是你想研究因果效应的变量,而结果变量是你想测量其效果的变量。此外,建议提供一个 DAG,即节点之间的因果关系。要为你的数据集创建因果图,你需要领域知识,或者可以将每个变量连接到目标和处理变量。注意,如果没有提供 DAG,后者会自动执行。对于这个数据集,我不包括已知的结构,而是让DoWhy在所有变量之间创建到结果和处理变量的边。第一个代码部分显示了所需的预处理步骤,第二个代码部分则是因果模型的创建。
# Installation
pip install dowhy
# Import libraries
from dowhy import CausalModel
import dowhy.datasets
from sklearn.preprocessing import LabelEncoder
# Import data set and drop continous and sensitive features
df = bn.import_example(data='census_income')
drop_cols = ['age', 'fnlwgt', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week', 'race', 'sex']
df.drop(labels=drop_cols, axis=1, inplace=True)
# Treatment variable must be binary
df['education'] = df['education']=='Doctorate'
# Next, we need to make our data numeric.
df_num = df.copy()
for col in df_num.columns:
df_num[col] = le.fit_transform(df_num[col])
# Specify the treatment, outcome, and potential confounding variables
treatment = "education"
outcome = "salary"
# Step 1\. Create a Causal Graph
model= CausalModel(
data=df_num,
treatment=treatment,
outcome=outcome,
common_causes=list(df.columns[~np.isin(df.columns, [treatment, outcome])]),
graph_builder='ges',
alpha=0.05,
)
# Display the model
model.view_model()

图 2。DoWhy 创建了这个 DAG 来建模人口普查收入数据 集,其中结果变量为“salary”,处理变量为“Education”(图像由作者提供)。
正如你在上面的代码部分中可能已经注意到的,处理变量必须是二元的(设置为 Doctorate),并且所有分类变量需要编码为数值。在下面的代码部分,我们将使用因果图的属性来识别因果效应。结果可能不会令人惊讶。它表明,拥有Doctorate 增加了获得良好salary的机会。
# Step 2: Identify causal effect and return target estimands
identified_estimand = model.identify_effect(proceed_when_unidentifiable=True)
# Results
print(identified_estimand)
# Estimand type: EstimandType.NONPARAMETRIC_ATE
# ### Estimand : 1
# Estimand name: backdoor
# Estimand expression:
# d
# ────────────(E[salary|workclass,marital-status,native-country,relationship,occupation])
# d[education]
#
# Estimand assumption 1, Unconfoundedness: If U→{education} and U→salary then P(salary|education,workclass,marital-status,native-country,relationship,occupation,U) = P(salary|education,workclass,marital-status,native-country,relationship,occupation)
#
# ### Estimand : 2
# Estimand name: iv
# No such variable(s) found!
#
# ### Estimand : 3
# Estimand name: frontdoor
# No such variable(s) found!
# Step 3: Estimate the target estimand using a statistical method.
estimate = model.estimate_effect(identified_estimand, method_name="backdoor.propensity_score_stratification")
# Results
print(estimate)
#*** Causal Estimate ***
#
## Identified estimand
# Estimand type: EstimandType.NONPARAMETRIC_ATE
#
### Estimand : 1
# Estimand name: backdoor
# Estimand expression:
# d
# ────────────(E[salary|workclass,marital-status,native-country,relationship,occupation])
# d[education]
#
# Estimand assumption 1, Unconfoundedness: If U→{education} and U→salary then P(salary|education,workclass,marital-status,native-country,relationship,occupation,U) = P(salary|education,workclass,marital-status,native-country,relationship,occupation)
#
## Realized estimand
# b: salary~education+workclass+marital-status+native-country+relationship+occupation
# Target units: ate
#
## Estimate
# Mean value: 0.4697157228651772
# Step 4: Refute the obtained estimate using multiple robustness checks.
refute_results = model.refute_estimate(identified_estimand, estimate, method_name="random_common_cause")
摘要
-
输入数据:结果变量和处理变量。强烈建议提供一个因果 DAG。
-
要求: 处理变量必须是二元的,所有分类变量需要编码成数值。
-
(优)缺点: 输出包含许多细节,这些细节可能有利但也使理解变得复杂。无法从数据中学习因果 DAG。
库 5:CausalImpact。
CausalImpact 是一个用于使用贝叶斯结构 时间序列模型 进行因果推断的 Python 包 [4]。其主要目标是通过分析期望和观察到的时间序列数据之间的差异来推断给定干预的预期效果,如程序评估或处理效果分析。它假设响应变量可以通过线性回归精确建模。然而,响应变量不能受到已发生干预的影响。例如,如果一家公司想要推断某个营销活动对其“收入”的影响,则其每日“访问量”不能作为协变量使用,因为总访问量可能会受到该活动的影响。在以下示例中,我们将创建 100 个观测值,其中响应变量为 y,预测变量为 x1。请注意,在实际应用中,我们会有更多的预测变量。为了演示,干预效果 通过在时间点 71 之后将响应变量更改 10 个单位来创建。
为了估计因果效应,我们首先需要指定干预前期和干预后期。使用 run 函数,我们利用 MLE(默认)拟合时间序列模型,并估计因果效应。
# Installation
pip install causalimpact
# Import libraries
import numpy as np
import pandas as pd
from statsmodels.tsa.arima_process import arma_generate_sample
import matplotlib.pyplot as plt
from causalimpact import CausalImpact
# Generate samples
x1 = arma_generate_sample(ar=[0.999], ma=[0.9], nsample=100) + 100
y = 1.2 * x1 + np.random.randn(100)
y[71:] = y[71:] + 10
data = pd.DataFrame(np.array([y, x1]).T, columns=["y","x1"])
# Initialize
impact = CausalImpact(data, pre_period=[0,69], post_period=[71,99])
# Create inferences
impact.run()
# Plot
impact.plot()
# Results
impact.summary()
# Average Cumulative
# Actual 130 3773
# Predicted 120 3501
# 95% CI [118, 122] [3447, 3555]
# Absolute Effect 9 272
# 95% CI [11, 7] [326, 218]
# Relative Effect 7.8% 7.8%
# 95% CI [9.3%, 6.2%] [9.3%, 6.2%]
# P-value 0.0%
# Prob. of Causal Effect 100.0%
# Summary report
impact.summary(output="report")
*均列描述了处理后期的*均时间。累积列汇总了各个时间点的值,这在响应变量表示流量数量时(如查询、点击、访问、安装、销售或收入)是一个有用的视角。我们看到,在这个例子中,由于我们在数据中包含了这一点,因此因果效应的概率为 100%,P 值为 0,这是正确的。

图 4. CausalImpact 图(作者提供的图片)。
在图(图 4)中,我们可以看到三个面板,具体描述如下:在面板 1 中,我们可以看到数据以及专门针对处理后期的反事实预测。在第二个面板中,展示了观察数据与反事实预测之间的差异。这一差异表示了由模型确定的逐点因果效应。底部面板通过汇总前一个面板中的逐点贡献来描绘干预的累积效果。需要注意的是,这些推断的有效性依赖于协变量没有受到干预本身的影响。此外,模型假设在预期期间建立的协变量与处理时间序列之间的关系在整个后期保持一致。
总结
-
输入数据: 时间序列数据。
-
要求: 需要指定干预前和干预后的时间段。
-
优点: 可以确定时间序列数据上的因果效应。
图书馆之间的整体总结。
我们已经看到五个用于学习因果关系的库,每个库都有其自身的优缺点和解决特定问题的重点。总结如下:
BnLearn 库适用于具有离散、混合或连续数据集的情况,需要从数据中推导因果 DAG(结构学习)、进行参数学习(CPD 估计),或在你希望进行推理(do-calculus)的情况下。其优点在于许多预处理和后处理步骤都在管道中解决,从而减少了出错的可能性。它允许将类别(字符串)标签作为输入,并且可以使用统计测试(独立性测试)去除虚假的边。此外,还可以将边或节点列入白名单或黑名单。请注意,由于大多数处理步骤是在管道中执行的,因此无法完全自定义建模部分,这可能会降低用户的控制权。
Pgmpy 库包含低级统计函数,用于创建和组合各种结构学习、参数学习和推理方法。它在使用上比较灵活,但需要对贝叶斯建模有良好的知识。请注意,Bnlearn 库利用了来自 Pgmpy 的一些函数,因此功能有重叠。换句话说,如果你打算创建自定义管道,建议使用 Pgmpy。否则,我推荐使用 bnlearn。
CausalNex 库可以用来从数据中推导因果 DAG,并进行推理。请注意,它仅适用于离散数据集。在检测到因果 DAG 后,可以通过阈值来移除边。不幸的是,它无法处理类别标签,需要将其转换为数值。此外,还需要进行各种中间预处理步骤。例如,减少类别特征的基数,描述数值特征,并以字典形式指定每个节点可以采取的所有状态。尽管这给用户提供了更多的建模控制,但额外的建模增加了一层复杂性。
DoWhy 方法在你希望估计因果效应时是理想的,如果你有一个 结果变量 和一个 处理 变量。这非常适用于 A/B 测试的使用场景,例如确定酒店预订取消的原因。结果 可以设置为 “取消”,处理 可以设置为 “分配不同的房间”。另一个使用场景可能是估计会员奖励计划的效果。请注意,该库并不旨在从数据中学习因果 DAG。然而,它确实需要一个作为模型输入的因果 DAG,你需要手动提供。
CausalImpact 是一个用于因果推断的 Python 包,使用贝叶斯结构时间序列模型。虽然它也计算因果关系,但其功能与其他库不可比拟,因为它不打算创建因果 DAG 或对数据进行推断。目标是通过分析预期和观察到的时间序列数据之间的差异,推断给定干预的预期效果,例如程序评估或治疗效果分析。
最后的话。
对数据进行因果建模可能因多种原因而具有挑战性。首先需要选择合适的库,这些库与您的具体使用案例匹配。我们已经对五个流行的因果推断库进行了比较:Bnlearn、Pgmpy、CausalNex、DoWhy 和 CausalImpact。每个库都有其自身的优缺点,并且最适合某些特定的使用案例。我们使用相同的数据集比较了这些库的功能、能力和结果的可解释性。只有* CausalImpact*使用了不同的数据集,因为它只能建模连续值并且需要时间序列数据。
功能分组会将Bnlearn、Pgmpy 和 CausalNex 聚类在一起,因为它们包含结构学习、参数学习和推断的功能。第二组是DoWhy 和 CausalImpact,它们旨在测量处理变量对结果变量Y的因果效应,同时控制一组特征X。除了这里描述的五个因果库,还有更多库值得关注,如EconML、Pyro 和 CausalML,它们属于第二组(处理变量对结果变量的因果效应)。
保重。保持冷静。
干杯 E.
免责声明:我是 Python bnlearn 库的开发者。不管你对因果库的决定是什么,确保你完全理解建模部分并能够解释结果。
如果你觉得这篇关于贝叶斯因果学习的文章有帮助,欢迎 关注我 ,因为我会写更多关于贝叶斯统计的文章。如果你考虑加入 Medium 会员,你可以通过使用我的 推荐链接* 来稍微支持一下我的工作。价格和一杯咖啡相同,你可以每月无限阅读文章。*
软件
让我们联系吧!
与贝叶斯统计相关的工作
初学者指南,用于有效地确定变量之间的因果关系。
towardsdatascience.com ## 逐步指南:使用贝叶斯定理设计知识驱动模型
如果你没有数据但有专家知识。初学者指南将知识转化为计算机辅助…
towardsdatascience.com ## 寻找最佳增强模型的指南:使用贝叶斯超参数调优,但不包括…
增强型决策树算法可能会优于其他模型,但过拟合是一个现实的危险。使用…
towardsdatascience.com [## 分类模型的贝叶斯优化超参数调优实践指南
学习如何拆分数据、优化超参数、预防过拟合、选择最佳模型,并创建…
参考文献
-
Census Income. (1996). UCI 机器学习库。
doi.org/10.24432/C5S595。 -
Taskesen E, 使用 Python 中的贝叶斯结构学习检测因果关系的逐步指南,Medium,2021
-
Taskesen, E. (2020). 使用 bnlearn Python 包学习贝叶斯网络。 (版本 0.3.22) [计算机软件]。
-
Amit Sharma, Emre Kiciman. DoWhy: 一种用于因果推断的端到端库。2020 年。
arxiv.org/abs/2011.04216 -
Kay H. 等,使用贝叶斯结构时间序列模型推断因果影响,2015 年,《应用统计年鉴》(第 9 卷,第 247–274 页)
-
Xun Zheng 等,无泪的 DAG: 结构学习的连续优化,第 32 届神经信息处理系统会议(NeurIPS 2018),加拿大蒙特利尔。
-
AI4I 2020 预测维护数据集(2020)。UCI 机器学习库。
dbt 增量模型在大数据中的威力
在 BigQuery 上的实验
·
关注 发表在 Towards Data Science ·5 分钟阅读·2023 年 2 月 9 日
--
如果你在使用 dbt 模型处理几 MB 或 GB 的数据,这篇文章不适合你;你已经做得很好了!这篇文章是为那些需要在 BigQuery 中扫描 TB 级数据,以便计算某些计数、总和或滚动总计的可怜的灵魂准备的,这些计算可能是每天进行的,甚至更频繁。在这篇文章中,我将介绍一种针对“大数据”进行便宜的数据摄取和数据消费的技术。

图片由 Joshua Sortino 提供,来源于 Unsplash
让我们假设我们有以时间戳为粒度的原始数据,并且我们需要计算每个客户的总数。此外,我们希望进行按天和按月的分析,因为我们需要在不同粒度上汇总数据。我将以 Twitter 点赞为例,其中 user_id 是 Twitter 用户,post_id 是 Twitter 帖子,timestamp 是 user_id 点赞 post_id 的时间戳。原始数据以天为粒度存储为分区表。如果你的原始数据没有分区,这已经是你的第一个改进,因此考虑将原始数据迁移到分区表,参考 这个 示例。确保按需设置分区过滤器 cloud.google.com/bigquery/docs/managing-partitioned-tables#require-filter,以避免扫描所有原始数据的意外情况。

Twitter 点赞的原始日志
这些数据实际上大约是 869.91 GB!所以如果我想知道每个用户所执行的点赞数量,我的[昂贵的]简单查询将是:
SELECT
user_id,
COUNT(*) as nr_likes
FROM twitter_likes
然而,Twitter 上的点赞是不可变的事件,无法被修改。这允许我逐步加载这些数据。我将遍历原始数据来加载当天的数据,检查每日汇总的数据以加载本月的数据,最后通过每日数据来更新发生变化的用户的点赞数。
对于这两种增量模型,我将使用静态分区技术,这被证明是性能最优的 discourse.getdbt.com/t/benchmarking-incremental-strategies-on-bigquery/981。这意味着我将对分区进行操作而不是扫描数据,因此我会选择最*的两个日期分区(以防我有延迟到达的事件),对它们进行汇总,然后将其添加到我的 dbt 模型 twitter_likes_daily 中。
{% set partitions_to_replace = [
'current_date',
'date_sub(current_date, interval 1 day)'
] %}
{{
config(
materialized='incremental',
partition_by = { 'field': 'date', 'data_type': 'date' },
cluster_by = ["user_id"],
incremental_strategy = 'insert_overwrite'
partitions = partitions_to_replace
)
}}
SELECT
user_id,
DATE(timestamp) as date,
COUNT(*) as nr_likes
FROM {{ source ('twitter', 'twitter_likes')}}
-- I have a fake condition here because
-- my raw table does not let me query without a partition filter
WHERE DATE(timestamp) >= "1990-01-01"
{% if is_incremental() %}
-- this filter will only be applied on an incremental run
AND DATE(timestamp) in ({{ partitions_to_replace | join(',') }})
{% endif %}
GROUP BY 1, 2
twitter_likes_daily 仅为 55.52 GB,日加载扫描 536.6 MB!让我们创建每月汇总数据,为此,我们可以直接在 twitter_likes_daily 上工作,而不是原始的 twitter_likes。
{% set partitions_to_replace = [
'date_trunc(current_date, month)',
'date_trunc(date_sub(current_date, interval 1 month), month)'
] %}
{{
config(
materialized='incremental',
partition_by = { 'field': 'month_year', 'data_type': 'date', "granularity": "month" },
cluster_by = ["user_id"],
incremental_strategy = 'insert_overwrite'
partitions = partitions_to_replace
)
}}
SELECT
user_id,
DATE_TRUNC(date, MONTH) as month_year,
SUM(nr_likes) as nr_likes
FROM {{ref('twitter_likes_daily')}}
{% if is_incremental() %}
-- this filter will only be applied on an incremental run
AND DATE_TRUNC(date, MONTH) in ({{ partitions_to_replace | join(',') }})
{% endif %}
GROUP BY 1, 2
twitter_likes_monthly 的大小为 14.32 GB,而一次加载仅扫描 2.5 GB!
现在让我们来看看总数。在这种情况下,我们可以决定运行一个 merge 操作,只更新过去 2 天内有新点赞的用户的总数。其余用户没有更新。因此,在这种情况下,我可以从 twitter_likes_daily 中挑选出今天有点赞的用户,计算他们在 twitter_likes_monthly 中的总数,最后将它们合并到包含总数的表 twitter_likes_total 中。请注意,该表使用 user_id 进行 clustered,因为与 id 的集群合并比常规合并更高效 discourse.getdbt.com/t/benchmarking-incremental-strategies-on-bigquery/981。
{{
config(
materialized='incremental',
unique_key = 'user_id',
cluster_by = ["user_id"],
incremental_strategy = 'merge'
)
}}
{% if is_incremental() %}
WITH users_changed as (
SELECT DISTINCT user_id
FROM {{ref('twitter_likes_daily')}}
WHERE date = current_date()
)
{% endif %}
SELECT
user_id,
sum(nr_likes) as total_likes
FROM {{ref('twitter_likes_monthly')}}
{% if is_incremental() %}
AND user_id in (select user_id from users_changed)
{% endif %}
group by 1
twitter_likes_total 是 1.6 GB,但扫描了 16 GB!所以从 869.91 GB 中,我们仅扫描了 16 GB,并生成了一些轻量且便宜的分析表。
然而,twitter_likes_total 的方法也可以是一个仅扫描 twitter_likes_monthly 的 dbt 表配置。如下所示:
{{
config(
materialized='table',
cluster_by = ["user_id"]
)
}}
SELECT
user_id,
sum(nr_likes) as total_likes
FROM {{ref('twitter_likes_monthly')}}
上述脚本将扫描 14.5 GB,因此实际上比 merge 过程更轻量!这到底发生了什么?重要的是要理解,merge 可能比完全重建表的效果差。鉴于我们扫描的表 twitter_likes_monthly 不大且为 14.5 GB,完全重建表比 merge 更高效。因此,当数据量不是很大时,merge 实际上可能是过度工程。
总结一下
原始不可变事件的扫描可以通过在 dbt 上使用增量模型高效处理,特别是使用静态插入+覆盖策略。改进可能是巨大的,从 TB 减少到每天几 GB 的 dbt 负载,但最重要的是,能够让数据分析师在原本成本高昂的原始表扫描上运行查询,转而使用一些轻量、预聚合的表。最后,虽然向表中追加数据在理论上可能比重建表便宜,但根据用例,有时使用 merge 可能比完全重建表的性能差,尤其是当源数据只有几 GB 时。
独立分量分析(ICA)在真实世界应用中的强大能力 — EEG 示例
独立分量分析(ICA)是一种强大的数据驱动工具,能够在数据中分离线性贡献
·
关注 发布在 Towards Data Science ·6 分钟阅读·2023 年 10 月 23 日
--
Photo by National Cancer Institute 在 Unsplash
- 介绍
独立成分分析(ICA)通常与降维任务相关。然而,这项技术最突出的应用是从数据中分离线性贡献,寻找统计独立的成分。例如,ICA 被广泛用于从音频中分离乐器轨迹。本文的目标是通过著名的 “鸡尾酒会” 示例介绍和激发 ICA 的兴趣,然后简要介绍 ICA 如何利用概率和信息理论的基础知识提取独立成分。接着,我们探索一个实际的例子——从脑电图(EEG)数据中识别和去除眼睛眨动的动作。
贡献与信号和 ICA 的分离
为了更好地理解从数据中分离线性贡献,标准的例子是 “鸡尾酒会”。这个场景设置在一个喧闹的鸡尾酒会中,两个人进行对话,分别由房间中的红色(Y1) 和蓝色(Y2) 扬声器表示。因此,两只麦克风被战略性地放置在附*,以捕捉这两个人的声音,由绿色(X1) 和黄色(X2) 麦克风表示。这些声音的可听度根据对话者与每个麦克风的距离而变化。

“鸡尾酒会” 的插图。信号由麦克风 X1 和 X2 捕获,我们希望将其分解为原始源 Y1 和 Y2。图片由作者提供。
挑战在于如何有效地将每个混合音频文件中的两个不同声音分离,以获得每个扬声器的孤立音频录音。这一难题可以通过熟练应用独立成分分析(ICA)来解决。在正确的数据假设下(我们将在下一节中看到),每个独立成分将是蓝色和红色扬声器的声音。

ICA 分解的插图。图片由作者提供。
ICA 的基本机制
独立成分分析(ICA)试图使用坐标系统 S 对数据 X 进行表示,在这个坐标系统中,成分 Si 是独立的。这种独立的坐标系统可以说是数据的一种自然表示。
这里最重要的一点是 ICA 假设成分是统计独立的,而不是无关的(如 PCA)[1]。

统计 独立 的定义。
主成分分析(PCA)假设数据和成分是正态分布的,当满足这一要求时,PCA 成分是统计独立的。然而,在数据分布非高斯的一般情况下,ICA 是寻找独立成分 Y (源) 代表数据 X (信号) 的强大方法。
ICA 变换由Y = AX给出,其中A的元素是定义组件的系数。因此,它仍然是一个线性方法,因为它假设源和信号之间的线性组合。

我们也可以进行反向操作,即根据 ICA 组件写入数据。

由于我们正在寻找统计上独立的组件,我们想要最小化组件之间共享的信息。
因此,信息论是帮助我们解决这个问题的自然选择。两随机变量X和Y之间共享的信息量被称为互信息。

因此,我们想要最小化组件之间共享的信息,

这是找到最小化I[Y] = I[ AˆT X ]的A。因此,结果的正交向量导致最独立的组件。
一旦我们理解了 ICA 的动机和基本机制,让我们在实际情况中应用它。
现实世界应用:去除 EEG 数据中的眼睛眨动
脑电图(EEG)数据受到各种运动伪影的污染,这些伪影由患者在图像采集过程中的自愿或非自愿运动引起。一个常见的伪影是眼睛眨动。因此,像 ICA 这样的工具是去除这些噪声的广泛使用技术[2]。如前所述,ICA 是一种分离信号独立贡献的方法。因此,我们直观地应该期待眼睛眨动对接*眼睛的通道有重要贡献。
EEG 测量跨皮层点的电位差随时间变化。因此,在 EEG 实验中,我们将从皮层的不同部位获得多条时间序列,这些序列包含关于神经回路的不同信息。

来自多个通道的 EEG 时间序列示例。图像由作者提供。

上述示例中的通道位置。图像由作者提供。
为了正确处理 EEG 数据并应用 ICA,我们将使用MNE(一个用于 EEG 数据处理和分析的库)。此外,我们将使用由 João Sato 教授在联邦 ABC 大学收集的数据,这些数据可在GitHub上找到。
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import mne as mne
sns.set_palette('pastel')
eeg = np.loadtxt('/Users/rodrigo/Post-Grad/Disciplines/Neural Signaling Processing/Aula_3/restEEG/OlhosAbertos.txt')
canais = pd.read_csv('/Users/rodrigo/Post-Grad/Disciplines/Neural Signaling Processing/Aula_3/restEEG/NOMEScanais.txt', dtype='str', header=None)
# Sampling frequency 250Hz:
Hz = 250
eeg = pd.DataFrame(eeg, columns=canais.iloc[:,0].values)
eeg['Time(s)'] = np.arange(0, (1/Hz)*len(eeg), 1/Hz)
创建 MNE EEG 对象。
# Creating MNE object
mne_info = mne.create_info(
list(eeg.columns[:-1]),
Hz,
ch_types=['eeg']*len(eeg.columns[:-1]))
mne_raw = mne.io.RawArray(eeg.iloc[:,:-1].values.T, mne_info)
# Defining the electrodes architecture
standard_1020 = mne.channels.make_standard_montage('standard_1020')
mne_raw.set_montage(standard_1020)
为了提取重要特征,我们需要过滤时间序列,以排除与伪影和噪声相关的高频(> 120 Hz)和低频(< 4 Hz),以及来自环境电源线的 60Hz 贡献。
# Set filter parameters
lowCut = 4 # Hz
highCut = 120 # Hz
freqNotch = 60 # Hz (eletric interference from the enviroment)
# Apply bandpass and notch filter
mne_raw.filter(lowCut, highCut, fir_design='firwin')
mne_raw.notch_filter(freqNotch, fir_design='firwin')
现在,数据准备好进行 ICA 处理。
ica_obj = mne.preprocessing.ICA(
n_components=0.95,
method='infomax',
max_iter="auto",
random_state=1,
fit_params=dict(extended=True)).fit(mne_raw)
ica = ica_obj.get_sources(mne_raw).get_data()
ica_obj.plot_components(picks=None,show=True, inst=mne_raw)
ICA 系数用作识别信号组件空间贡献的代理。

将值与每个成分的空间区域关联的过程示意图。图片由作者提供。
因此,可以为每个成分创建热图。

ICA 成分。图片由作者提供。
第一个成分是信号中的眼睑运动贡献。正如我们预期的那样,这一贡献在靠*眼睛的电极上更大。也可以更深入地查看该成分的时间/频率信息。

ICA 第一个成分的时间/频率信息。图片由作者提供。
现在,为了从数据中去除眼睑运动伪影,我们只需重建没有这个特定成分的信号。这意味着恢复没有与伪影相关的模式的时间序列。
结论
总结来说,独立成分分析(ICA)是一种强大的数据驱动工具,它假设数据为非高斯分布,并且源信号之间存在线性关系,以寻找统计上独立的成分。ICA 允许我们分离数据中的不同贡献,使其在需要隔离噪声或不需要的信号的场景中特别有用。在 EEG 数据的情况下,ICA 可以识别并去除如眼睑运动等运动伪影,这些伪影可能会污染神经信号。
备注
重要的是要指出,ICA 是一个强大的工具,但使用时需要谨慎。数据驱动的方法不假设对系统有任何先验知识。因此,混淆和虚假结论很常见,这使得检查要求和仔细评估结果至关重要。
致谢
这个项目的灵感来源于 João Ricardo Sato 教授。
本文的笔记本可用 在这里。
参考文献
[1] Hastie T, Tibshirani R, Friedman J, Hastie T, Tibshirani R, et al. (2009) 基础扩展与正则化。在:统计学习的元素,纽约,NY:Springer New York,Springer 统计系列。
[2] Mennes M, Wouters H, Vanrumste B, Lagae L, Stiers P. (2010). ICA 作为去除 EEG/ERP 中眼动伪影工具的验证。生理心理学。2010;47(6):1142–1150。doi:10.1111/j.1469–8986.2010.01015.x
Linux 控制组的威力:容器如何控制其资源
使用 Linux 控制组优化容器资源分配
·发表于Towards Data Science ·阅读时间 8 分钟·2023 年 1 月 10 日
--

照片由Joshua Hoehne提供,来源于Unsplash
上一篇文章探讨了如何使用 Linux 命名空间在单一 Linux 系统内创建隔离环境。本文是我们深入了解容器如何工作的努力的一部分,旨在揭示其背后的机制。
## 容器:它们如何在幕后工作以及为何它们正在主导数据科学领域
初学者理解 Docker 魔法的指南
towardsdatascience.com
命名空间是我们旅程的第一步。我们看到如何创建一个PID命名空间,创建一个其中运行的进程假设自己是唯一存在的世界,但如何对它们可以消耗的资源数量施加限制呢?这就要引入 Linux cgroups 了。
Linux 控制组,或称 cgroups,是一种强大的工具,用于管理和分配 Linux 系统中的资源。它们允许管理员限制进程或进程组使用的资源,确保关键系统服务始终能够获得其正常运行所需的资源。
但 cgroups 不仅对系统管理员有用——它们还为容器提供了一种控制自身资源的方法,使其在共享主机环境中更高效、更可靠地运行。
在本文中,我们将探讨在容器上下文中使用 cgroups 的好处,并展示如何在自己的环境中开始使用 cgroups。首先,我们将创建一个控制组,以限制在其上下文中运行的进程的内存消耗,然后在其下运行一个完整的命名空间。让我们深入了解吧!
学习速率 是一份面向那些对 MLOps 世界充满好奇的人的新闻通讯。MLOps 是一个广泛的领域,致力于以高效和可重现的方式将 ML 模型投入生产。容器在这个流程中发挥着关键作用。如果你想了解更多类似的主题,请点击这里订阅。每个月的第一个星期六,你将收到我关于最新 MLOps 新闻和文章的更新和见解!
什么是 Cgroups?
Linux 控制组或 cgroups 是一种内核特性,允许管理员将 CPU、内存和 I/O 带宽等资源分配给进程组。
Cgroups 提供了一种控制系统资源的使用量的方法。例如,管理员可以为与特定应用程序相关的进程组创建一个 cgroup(例如,运行在服务器上的 web 应用程序),然后设置这些进程允许使用的 CPU 和内存的限制。
Cgroups 对于各种目的都很有用,包括提高系统性能、隔离进程以提高安全性,以及简化在单个系统上管理多个应用程序。
在容器上下文中,cgroups 允许我们限制每个容器可以消耗的资源,以便我们的应用程序不会占用整个服务器,或者确保它拥有运行所需的资源。
例如,设置 Kubernetes 中 pod 的资源部分总是一个好主意,因为它帮助 Kubernetes 调度器决定将 pod 调度到哪个节点。这确保了我们的应用程序将拥有运行所需的一切。如果没有,应用程序甚至无法启动。
我们如何在实际中使用 cgroups?通过创建两个简单的示例来了解:首先,我们将创建一个简单的应用程序,并在特定的 cgroup 下运行它,然后在类似的上下文中运行一个完整的命名空间。
一个简单的示例
我们在 cgroups 世界的旅程从这里开始。首先,我们将创建一个简单的应用程序,并在内存限制的 cgroup 中运行它。然后,我们将在相同的上下文中创建一个 Linux 命名空间,并在该命名空间中运行相同的应用程序。
Memhog
为了使这个示例有效,我们需要控制应用程序使用的内存量。为此,我们将使用一个名为 memhog 的 Debian 软件包。
memhog 是一个简单的软件包,用于分配我们指定的内存以供测试(有关更多详细信息,请参见 这里 的手册页)。
第一步是安装 memhog:
sudo apt update && sudo apt install numactl
测试你是否正确安装了 memhog,让它分配 100 兆字节的内存:
memhog 100M
输出应该类似于以下内容:
..........
如果你得到了这个输出,那么你可以继续!让我们创建一个每 2 秒运行一次的 bash 脚本。因此,我们的应用程序是一个每两秒请求 100 兆字节的服务。创建一个新文件,命名为 memhog.sh 并将以下内容放入其中:
#!/bin/bash
while true; do memhog 100M; sleep 2; done
最后,使文件可执行:
sudo chmod +x memhog.sh
创建一个 Cgroup
要创建、管理和监控 cgroup,我们需要另一个名为 cgroup-tools 的软件包。所以,首先,你需要安装它:
sudo apt install cgroup-tools
现在我们在工具箱中有了这个,过程如下:
-
使用我们刚刚安装的软件包创建一个新的 cgroup
-
为我们想要控制的资源在这个特定的 cgroup 中设置一个限制
-
在这个 cgroup 下运行应用程序或命名空间
因此,让我们首先创建 cgroup。为此,请使用以下命令:
sudo cgcreate -g memory:memhog-limiter
这个命令创建了一个新的 cgroup(cgcreate),类型为内存,并将其名称设置为 memhog-limiter。这个命令实际上做的是在 /sys/fs/cgroup/memory 下创建了一个新目录,你可以通过运行 ls 来查看其内容:
ls -la /sys/fs/cgroup/memory/memhog-limiter/
drwxr-xr-x 2 root root 0 Jan 9 05:56 .
dr-xr-xr-x 8 root root 0 Jan 4 10:31 ..
-rw-r--r-- 1 root root 0 Jan 9 05:56 cgroup.clone_children
--w--w--w- 1 root root 0 Jan 9 05:56 cgroup.event_control
-rw-r--r-- 1 root root 0 Jan 9 05:56 cgroup.procs
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.failcnt
--w------- 1 root root 0 Jan 9 05:56 memory.force_empty
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.kmem.failcnt
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.kmem.limit_in_bytes
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.kmem.max_usage_in_bytes
-r--r--r-- 1 root root 0 Jan 9 05:56 memory.kmem.slabinfo
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.kmem.tcp.failcnt
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.kmem.tcp.limit_in_bytes
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.kmem.tcp.max_usage_in_bytes
-r--r--r-- 1 root root 0 Jan 9 05:56 memory.kmem.tcp.usage_in_bytes
-r--r--r-- 1 root root 0 Jan 9 05:56 memory.kmem.usage_in_bytes
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.limit_in_bytes
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.max_usage_in_bytes
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.move_charge_at_immigrate
-r--r--r-- 1 root root 0 Jan 9 05:56 memory.numa_stat
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.oom_control
---------- 1 root root 0 Jan 9 05:56 memory.pressure_level
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.soft_limit_in_bytes
-r--r--r-- 1 root root 0 Jan 9 05:56 memory.stat
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.swappiness
-r--r--r-- 1 root root 0 Jan 9 05:56 memory.usage_in_bytes
-rw-r--r-- 1 root root 0 Jan 9 05:56 memory.use_hierarchy
-rw-r--r-- 1 root root 0 Jan 9 05:56 notify_on_release
-rw-r--r-- 1 root root 0 Jan 9 05:56 tasks
(根据你的系统,目录的地点或结构可能会有所不同)
现在我们已经创建了 cgroup,让我们设置我们的限制。我们将设置 50 兆字节的限制,这意味着在这个上下文中运行的任何进程都不能超过这个限制。类似地,谈到进程组时,它们的总需求也不能超过这个限制。
要设置内存限制,请运行以下命令:
sudo cgset -r memory.limit_in_bytes=50M memhog-limiter
这个命令为 cgroup momhog-limiter 设置了 50 兆字节的内存限制。如果你查看我们之前看到的目录结构中的相应文件,你会看到正是这个(以字节为单位):
cat /sys/fs/cgroup/memory/memhog-limiter/memory.limit_in_bytes
52428800
我们准备在这个上下文中运行我们的应用程序和命名空间。
设定你的限制!
我们现在的状态如下:我们创建了一个每两秒分配 100 兆字节内存的服务。我们还创建了一个限制进程或一组进程可以消耗的内存为 50 兆字节的 cgroup。如果我们在这个上下文中尝试运行我们的服务,你期望会发生什么?
不再废话,要在这个上下文中执行服务,请运行下面的命令:
sudo cgexec -g memory:memhog-limiter ./memhogtest.sh
结果正如我们预期的那样:每次它尝试运行时,Linux 内核都会终止服务:
....../memhogtest.sh: line 2: 174662 Killed memhog 100M
....../memhogtest.sh: line 2: 174668 Killed memhog 100M
很好;这正是我们想看到的。但现在,我们如何在这个上下文中创建一个命名空间呢?这类似于容器的功能,因此如果我们实现了这一点,我们将朝着在没有 Docker 的情况下创建类似容器的环境的目标迈进,这也是本系列的终极目标。
在这个上下文中创建命名空间是相当简单的。以下命令可能对你来说很熟悉:
sudo cgexec -g memory:memhog-limiter unshare -fp --mount-proc
当然,我们在之前使用了第一部分来在memhog-limiter cgroup 的上下文中运行服务(sudo cgexec -g memory:memhog-limiter)。此外,我们在文章中看到了命令的第二部分,我们讨论了命名空间(unshare -fp — mount-proc):这是我们用来创建新的PID命名空间的命令。
因此,如果我们将所有内容结合起来,这个命令在我们的 cgroup 上下文中创建了一个新的PID命名空间。要验证你是否在新的命名空间中,请运行以下命令:
# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 08:00 pts/0 00:00:00 -bash
root 12 1 0 08:00 pts/0 00:00:00 ps -ef
正如你所看到的,在你的新命名空间中,只有bash作为PID 1 运行。因此,你现在启动的每个服务都将在我们的 cgroup 上下文中启动。让我们验证一下:
# ./memhogtest.sh
....../memhogtest.sh: line 2: 14 Killed memhog 100M
....../memhogtest.sh: line 2: 16 Killed memhog 100M
....../memhogtest.sh: line 2: 18 Killed memhog 100M
太好了!我们得到了和之前一样的输出。如果你想尝试一下,你可以减少memhog尝试分配的内存量,让它正常工作。无论如何,祝贺你!你离创建自己的 Linux 容器而无需 Docker 更进一步了!
结论
总之,Linux cgroups 是管理和分配 Linux 系统资源的强大工具。它们允许管理员限制进程或进程组可以使用的资源量,确保重要的系统服务始终能够获得其正常运行所需的资源。
在容器的上下文中,cgroups 提供了一种让容器控制自身资源的方式,使它们能够在共享的主机环境中更高效、更可靠地运行。
这个故事探讨了如何在实践中使用 cgroups,并让我们离最终目标更*了一步:在 Linux 中创建类似容器的环境而无需 Docker。下一站?覆盖文件系统!
关于作者
我的名字是Dimitris Poulopoulos,我是为Arrikto工作的机器学习工程师。我为欧洲委员会、Eurostat、IMF、欧洲中央银行、OECD 和宜家等主要客户设计并实施了人工智能和软件解决方案。
如果你对阅读更多关于机器学习、深度学习、数据科学和数据运维的帖子感兴趣,可以在Medium、LinkedIn或在 Twitter 上关注@james2pl。
表达的观点完全是我个人的,并不代表我雇主的观点或意见。
OpenAI 的函数调用在数据管道中的力量:全面指南
利用 OpenAI 的函数调用功能改造数据管道:使用 PostgreSQL 和 FastAPI 实现电子邮件发送工作流
·发表于 Towards Data Science ·阅读时间 11 分钟·2023 年 6 月 22 日
--
介绍
AI 的激动人心的世界通过 OpenAI 最新的大型语言模型(LLMs)引入的函数调用能力又迈进了一步。这个新功能增强了人类与 AI 之间的互动,将其从简单的问答形式转变为更具动态性和主动性的对话。
那么这些函数调用能力到底是什么呢?本质上,它们允许 LLM 在对话中根据输入指令调用预定义的函数。这可以是任何操作,从发送电子邮件到根据对话的上下文从数据库中提取数据。
使用函数调用能力的好处和应用广泛。它显著提升了 AI 在各种应用中的动态效用,从客户服务到数据管道的构建。
想象一下一个客户服务 AI,它可以回答问题并执行诸如预约、向电子邮件地址发送信息或实时更新客户详情等操作。或者考虑一个数据管道,其中 LLM 代理可以根据命令获取、更新和处理数据。
在接下来的章节中,我们将进一步探讨这些应用,并提供一个逐步指南,介绍如何通过构建一个发送电子邮件的管道来利用这一新能力。

图 1:LLMs 正成为我们可以合作的智能代理(图片来源)
一如既往,代码可以在我的 Github 上找到。
理解新的函数调用能力并与 LangChain 进行比较
我们想要了解 OpenAI 新引入的函数调用功能在 AI 竞赛中带来了什么。为此,让我们深入了解它与市场上其他工具和框架(如 LangChain)的区别。我们在本系列的第一篇文章中已经介绍了 LangChain。它是一个用于开发 AI 驱动应用程序的流行框架。函数调用功能和 LangChain 带来了独特的优势和能力,旨在使 AI 更加可用、多样和动态。
我们已经知道,函数调用功能为 AI 应用程序增加了一层额外的互动性。它使模型能够在对话中调用预定义的函数,增强了 LLM 的活力和反应能力。这个新功能可能会简化将功能添加到 AI 应用程序的过程。开发者需要定义这些函数,然后模型可以根据上下文在对话中执行它们。这里的主要优势是与 OpenAI 模型的直接集成,这便于使用、快速设置,并且降低了熟悉 OpenAI 生态系统的开发者的学习曲线。
另一方面,LangChain 提供了一个全面且多功能的框架,旨在开发更复杂、数据感知和自主的 AI 应用程序。它允许语言模型与其环境互动,并根据高级指令做出决策。其模块提供了构建应用程序的抽象和标准接口,包括模型、提示、记忆、索引、链、代理和回调。
LangChain 的方法促进了构建应用程序,其中 LLM 可以在不同的交互中保持其状态,顺序调用和链式调用不同的实用程序,甚至与外部数据源互动。我们可以将这些视为增强的函数调用能力。因此,它对开发者特别有用,尤其是那些构建复杂、多步骤应用程序并利用语言模型的开发者。增加的复杂性带来的缺点是使用起来的学习曲线更陡峭。
用例 — 转换数据管道
在我看来,数据管道是 LLMs 新函数调用能力最令人兴奋的应用领域之一。数据管道是任何数据驱动组织中一个关键组件,它负责收集、处理和分发数据。通常,这些过程是静态的、预定义的,并且需要人工干预以进行任何更改或更新。这正是我们上面讨论的 LLM 动态行为创造机会的地方。
传统上,数据库查询需要特定的查询语言知识,如 SQL。借助 LLM 直接调用函数、服务和数据库的能力,用户可以通过对话检索数据,而不需要显式的查询表述。LLM 可以将用户的请求转换为数据库查询,获取数据,并以用户友好的格式实时返回。这一功能可以使组织内的不同角色都能*等地访问数据。
另一个可能发生变化的方面是数据转换。数据转换通常需要在分析之前进行单独的数据清理和处理步骤。LLM 可以通过根据用户的指示交互式地执行数据清理和操作任务来简化这一过程。此外,在对话中处理实时数据允许进行更多的探索性和迭代性数据分析。
第三个用例是数据监控。它涉及定期检查以确保数据管道中数据的准确性和一致性。借助 LLM,监控任务可以变得更加互动和高效。例如,LLM 可以在对话中提醒用户数据不一致,并立即采取纠正措施。
最后,LLM 还可以自动创建和分发数据报告。用户可以指示 LLM 根据特定标准生成报告,LLM 可以获取数据,创建报告,甚至将其发送给相关的接收者。
利用 OpenAI 函数调用能力构建电子邮件发送管道
我们的目标是创建一个发送电子邮件给用户的流程。虽然这听起来很简单,但这个过程的美在于由 LLM 控制的不同组件之间的相互作用。AI 模型不仅仅生成电子邮件正文;它还动态地与数据库互动以检索用户信息,撰写上下文适当的邮件,然后指示服务发送邮件。
我们的管道由三个主要组件组成:PostgreSQL、FastAPI 和 OpenAI LLM。我们使用 PostgreSQL 存储用户数据。这些数据包括用户名及其关联的电子邮件地址。它作为我们用户信息的真实来源。FastAPI 是一个现代、高性能的 Python Web 框架,用于构建 API。我们使用 FastAPI 服务来模拟发送电子邮件的过程。当服务接收到发送电子邮件的请求时,它会返回一个确认电子邮件已发送的响应。LLM 作为整个过程的协调者。它控制对话,根据上下文确定必要的操作,与 PostgreSQL 数据库互动以获取用户信息,撰写电子邮件内容,并指示 FastAPI 服务发送电子邮件。
实施 PostgreSQL 数据库
我们管道的第一个组件是 PostgreSQL 数据库,我们在其中存储用户数据。使用 Docker 设置 PostgreSQL 实例变得简单且可重复,Docker 是一个允许我们容器化和隔离数据库环境的*台。
你首先需要安装 Docker 来设置 PostgreSQL Docker 容器。安装后,你可以拉取 PostgreSQL 镜像并将其作为容器运行。我们将容器的端口 5432 映射到主机的端口 5432,以访问数据库。在生产环境中,请将密码设置为环境变量,不要像下面这样直接在命令中设置。我们这样做是为了加快我们的过程。
docker run --name user_db -e POSTGRES_PASSWORD=testpass -p 5432:5432 -d postgres
在我们的 PostgreSQL 实例运行后,我们现在可以创建一个数据库和一个表来存储我们的用户数据。我们将使用一个初始化脚本来创建一个 users 表,其中包含 username 和 email 列,并用一些虚拟数据填充它。这个脚本被放置在一个目录中,然后映射到容器中的 /docker-entrypoint-initdb.d 目录。PostgreSQL 在启动时执行此目录下的任何脚本。以下是脚本(user_init.sql)的样子:
CREATE DATABASE user_database;
\c user_database;
CREATE TABLE users (
username VARCHAR(50),
email VARCHAR(50)
);
INSERT INTO users (username, email) VALUES
('user1', 'user1@example.com'),
('user2', 'user2@example.com'),
('user3', 'user3@example.com'),
...
('user10', 'user10@example.com');
LLM 能够理解 SQL 命令并用于查询 PostgreSQL 数据库。当 LLM 收到涉及获取用户数据的请求时,它可以生成一个 SQL 查询来从数据库中获取所需的数据。
例如,如果你要求 LLM 发送一封电子邮件给 user10,LLM 可以生成如下查询:
SELECT email FROM users WHERE username=’user10';
这使它能够从 users 表中获取 user10 的电子邮件地址。LLM 然后可以使用这个电子邮件地址指示 FastAPI 服务发送电子邮件。
在下一部分,我们将指导你实现发送电子邮件的 FastAPI 服务。
创建 FastAPI 电子邮件服务
我们的第二个组件是 FastAPI 服务。该服务将模拟发送电子邮件的过程。这是一个直接的 API,接收包含收件人姓名、电子邮件和电子邮件正文的 POST 请求。它将返回一个确认电子邮件已发送的响应。我们将再次使用 Docker 确保我们的服务是隔离和可重复的。
首先,你需要安装 Docker(如果尚未安装)。然后,创建一个新的目录用于你的 FastAPI 服务并进入该目录。在这里,创建一个新的 Python 文件(例如,main.py)并添加以下代码:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
name: str
email: str
body: str
@app.post("/send_email")
async def send_email(user: User):
return {
"message": f"Email successfully sent to {user.name} with email {user.email}. Email body:\n\n{user.body}"
}
这段代码定义了一个 FastAPI 应用程序,包含一个单独的端点 /send_email/。该端点接受 POST 请求,并期望一个包含收件人姓名、电子邮件和电子邮件正文的 JSON 数据。
接下来,在相同的目录中创建一个 Dockerfile,内容如下:
FROM python:3.9-slim-buster
WORKDIR /app
ADD . /app
RUN pip install --no-cache-dir fastapi uvicorn
EXPOSE 1000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "1000"]
这个 Dockerfile 指示 Docker 创建一个基于 python:3.9-slim-buster 镜像的镜像,这是一个理想的轻量级镜像,用于高效运行 Python 应用程序。然后,它将我们的 main.py 文件复制到镜像中的 /app/ 目录下。
你可以使用以下命令构建 Docker 镜像:
docker build -t fastapi_email_service .
然后运行它:
docker run -d -p 1000:1000 fastapi_email_service
LLM 使用 POST 请求与 FastAPI 服务进行交互。当 LLM 决定发送电子邮件时,它生成对 send_email 函数的函数调用。此函数调用的参数包含名称、电子邮件和电子邮件正文。
函数调用由我们的 Python 脚本处理,该脚本提取函数参数,并用它们向我们的 FastAPI 服务发送 POST 请求。FastAPI 服务回应一个消息,指示电子邮件已成功发送。
现在,我们已经有了管道的所有组件。下一部分将把所有内容串联在一起,解释 LLM 如何协调 PostgreSQL 数据库和 FastAPI 服务之间的交互以发送电子邮件。
与 OpenAI LLM 集成
我们管道的最后一部分是 OpenAI LLM 集成。LLM 作为协调者,解释我们的命令,查询数据库以获取用户信息,并指示 FastAPI 服务发送电子邮件。
我们的脚本使用 OpenAI 的 API 进行基于聊天的完成。每个完成请求由一系列消息和可选的函数规格列表组成,模型可以调用这些函数。我们以用户消息开始对话,这为助手提供了提示。
这是我们用来向 API 发送请求的 chat_completion_request 函数:
@retry(wait=wait_random_exponential(min=1, max=40), stop=stop_after_attempt(3))
def chat_completion_request(messages, functions=None, model=GPT_MODEL):
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + openai.api_key,
}
json_data = {"model": model, "messages": messages}
if functions is not None:
json_data.update({"functions": functions})
response = requests.post(
"https://api.openai.com/v1/chat/completions",
headers=headers,
json=json_data,
)
return response
我们使用 Chat 类来管理对话历史。它有方法可以将新消息添加到历史记录中,并显示整个对话:
class Chat:
def __init__(self):
self.conversation_history = []
def add_prompt(self, role, content):
message = {"role": role, "content": content}
self.conversation_history.append(message)
def display_conversation(self):
for message in self.conversation_history:
print(f"{message['role']}: {message['content']}")
在我们的使用案例中,LLM 需要与我们的 PostgreSQL 数据库和 FastAPI 服务进行交互。我们定义这些函数并将其包含在我们的完成请求中。这是我们如何定义 sql_query_email 和 send_email 函数的:
functions = [
{
"name": "send_email",
"description": "Send a new email",
"parameters": {
"type": "object",
"properties": {
"to": {
"type": "string",
"description": "The destination email.",
},
"name": {
"type": "string",
"description": "The name of the person that will receive the email.",
},
"body": {
"type": "string",
"description": "The body of the email.",
},
},
"required": ["to", "name", "body"],
},
},
{
"name": "sql_query_email",
"description": "SQL query to get user emails",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The query to get users emails.",
},
},
"required": ["query"],
},
},
]
当我们发出完成请求时,LLM 以其预期的操作作出响应。如果响应包括函数调用,我们的脚本会执行该函数。例如,如果 LLM 决定调用 sql_query_email 函数,我们的脚本会从数据库中检索用户的电子邮件,然后将结果添加到对话历史记录中。当调用 send_email 函数时,我们的脚本会使用 FastAPI 服务发送电子邮件。
我们脚本的主循环检查 LLM 响应中的函数调用并相应地采取行动:
chat = Chat()
chat.add_prompt("user", "Send an email to user10 saying that he needs to pay the monthly subscription fee.")
result_query = ''
for i in range(2):
chat_response = chat_completion_request(
chat.conversation_history,
functions=functions
)
response_content = chat_response.json()['choices'][0]['message']
if 'function_call' in response_content:
if response_content['function_call']['name'] == 'send_email':
res = json.loads(response_content['function_call']['arguments'])
send_email(res['name'], res['to'], res['body'])
break
elif response_content['function_call']['name'] == 'sql_query_email':
result_query = query_db(json.loads(response_content['function_call']['arguments'])['query'])
chat.add_prompt('user', str(result_query))
else:
chat.add_prompt('assistant', response_content['content'])
当我们运行脚本时,我们得到以下输出:
{
"message": "Email successfully sent to User 10 with email user10@example.com.",
"Email body": "\n\nDear User 10, \n\nThis is a reminder that your monthly subscription fee is due. Please make the payment as soon as possible to ensure uninterrupted service. Thank you for your cooperation. \n\nBest regards, \nYour Subscription Service Team"
}
让我们解析一下我们得到这个输出的过程。我们的提示是 “给 user10 发送一封邮件,说明他需要支付每月订阅费。” 请注意,我们的消息中没有关于 user10 的电子邮件信息。LLM 识别到缺失的信息,并明白我们的函数 query_email 可以从数据库中获取该用户的电子邮件。在获取电子邮件后,它再次正确地完成了两件事:首先,它生成了电子邮件的正文,其次,它调用 send_email 函数来触发通过 FastAPI 邮件服务发送电子邮件。
结论
本文通过实施一个案例研究探讨了函数调用功能,其中 LLM 协调了一个涉及 PostgreSQL 数据库和 FastAPI 电子邮件服务的管道。LLM 成功地完成了从数据库中检索用户电子邮件并指示电子邮件服务发送个性化消息的任务,所有这些都是对单一提示的响应。
在 AI 模型中,函数调用的影响可能是巨大的,开启了自动化和简化流程的新可能性。数据管道可能会从静态和工程密集型的状态转变为动态实体,使得非技术用户能够通过自然语言快速获取最新数据。
关于我
连续创业者和 AI 领域的领军人物。我为企业开发 AI 产品,并投资于专注于 AI 的初创公司。
创始人 @ ZAAI | LinkedIn | X/Twitter
大语言模型编年史:探索 NLP 前沿
本文属于“大语言模型编年史:探索 NLP 前沿”系列文章的第一篇,这是一个新的每周系列,将探讨如何利用大型模型的力量来处理各种 NLP 任务。通过深入研究这些前沿技术,我们旨在赋能开发者、研究人员和爱好者,利用 NLP 的潜力,解锁新的可能性。
迄今为止发布的文章:
检索增强生成的力量:Base LLM 与 RAG LLMs 的比较,基于 Llama2
深入探讨使用 RAG 方法定制预训练 LLM 以适应特定使用案例,涉及 LangChain 和 Hugging Face 集成
·发表于 Towards Data Science ·阅读时间 12 分钟·2023 年 11 月 29 日
--
本文由 Rafael Guedes 共同撰写。
介绍
自 2022 年 11 月 ChatGPT 发布以来,大型语言模型(LLMs)因其理解和生成类似人类文本的能力,成为 AI 社区的热门话题,推动了自然语言处理(NLP)领域的边界。
LLMs 已被证明具有多样性,通过处理不同行业的不同使用案例,因为它们不局限于特定任务。它们可以适应多个领域,这使得它们对组织和研究社区具有吸引力。已经探索了许多使用 LLMs 的应用程序,例如内容生成、聊天机器人、代码生成、创意写作、虚拟助手等。
使 LLMs 非常吸引人的另一个特征是有开源选项。像 Meta 这样的公司将其预训练的 LLM(Llama2 🦙)在 Hugging Face 🤗 等存储库中提供。这些预训练的 LLM 对于每个公司的特定使用案例足够好吗?显然不够。
组织可以用自己的数据从头开始训练 LLM。但绝大多数组织(几乎所有组织)既没有所需的数据,也没有完成任务所需的计算能力。这需要拥有数万亿个标记的数据集,成千上万的 GPU,以及几个月的时间。另一个选择是使用预训练的 LLM,并将其调整为特定的使用案例。有两种主要的方法:微调和RAGs(检索增强生成)。
在本文中,我们将比较单独预训练的 Llama2 与集成在 RAG 系统中的预训练 LLama2 在回答关于 OpenAI 最新新闻的问题上的表现。我们将从解释 RAG 的工作原理及其子模块(检索器和生成器)的架构开始。最后,我们将逐步实现如何使用 LangChain 🦜️ 和 Hugging Face 构建一个适用于任何用例的 RAG 系统。

图 1:通过 RAG 方法,Llamas 变得越来越强大(图像来源:作者)
一如既往,代码可以在我们的Github上找到。
什么是检索增强生成(RAG)?
检索增强生成(RAG)是一种结合了检索器(如向量数据库或特征存储的非参数记忆)和生成器(如预训练的seq2seq变换器的参数记忆)技术的方法。它们用于提高 LLM [1] 的预测质量。它在推理时使用检索器,通过添加基于最相关文档的上下文/知识来构建更丰富的提示,以响应用户查询。
这种架构相对于传统 LLM 的优势是:
-
我们可以通过替换或添加更多文档/信息到非参数记忆中,轻松更新其知识。因此,它不需要重新训练模型。
-
它提供了对预测的可解释性,因为它允许用户检查哪些文档被检索以提供上下文,而这是我们从传统 LLM 中无法获得的。
-
它通过提供更准确和最新的信息,减少了“幻觉”的著名问题,这些信息通过检索器提供的文档获取。

图 2:检索增强生成(RAG)设置的示意图(图像来源:作者)
检索器——它是什么以及如何工作?
检索器的开发旨在解决问答(QA)问题,我们期望系统能够回答类似“什么是检索增强生成?”的问题。它通过访问包含有关主题信息的文档数据库来实现。
数据库通过将我们的文档拆分成等长的段落来填充,每个段落被表示为一系列标记。给定一个问题,系统需要遍历数据库,以找到可以更好回答问题的“段落”。
为了使这些系统在多个领域中有效工作,它们的数据库需要填充数百万或数十亿的文档。因此,为了能够遍历数据库寻找合适的段落,检索器需要在选择一组候选段落时非常高效。
密集段落检索器 (DPR) [2] 是作者在 [1] 中使用的检索器。它的目标是将数百万个段落索引到一个低维的连续空间,以高效地检索与特定问题最相关的前 k 个段落。
DPR 使用两个 密集编码器:
-
段落编码器将每个段落转换为一个 d 维向量,并使用 FAISS [3] 对它们进行索引。FAISS 是一个用于密集向量相似性搜索的开源库,可以应用于数十亿个向量。
-
问题编码器将输入问题转换为一个 d 维向量,然后使用 FAISS 检索与问题向量最接*的 k 个段落。向量之间的相似性可以通过它们之间的点积来计算。
-
DPR 使用的编码器架构是一个 BERT [4] 网络,它将输入转换为高维向量。然而,只要符合我们的用例,我们可以使用任何架构。

图 3:RAG 过程的概述,使用一个预训练的检索器,该检索器结合了查询编码器、文档索引和一个预训练生成器(seq2seq 模型)以预测自由文本形式的输出 (source)。
生成器 — 它是什么,如何工作?
生成器是一个 LLM,负责根据特定输入生成文本,通常被称为提示。
LLM 是主要由两种层组成的变换器模型 [5]:
-
全连接前馈网络 (FFN) 通过线性和非线性变换将一个嵌入向量映射到一个新的嵌入向量。
-
注意力层旨在选择哪些输入嵌入部分对当前任务更有用,产生一个新的嵌入向量。
BART [6] 是作者在 [1] 中为生成器选择的 LLM,它是一个序列到序列模型,具有以下架构 [7]:
-
编码器接收输入嵌入,并通过其六层(包括两个子层:多头自注意力机制和 FFN)生成一个 512 维的向量作为解码器的输出。
-
解码器遵循与编码器相同的逻辑,具有六层和两个子层,用于之前生成的输出。它还有一个额外的第三个子层,执行对编码器输出的多头注意力机制。
-
解码器输出接着传递到一个线性层,然后是一个 softmax 层,该层将预测下一个词的可能性。
如前一节所述,BART 不需要作为生成器使用。随着该领域的发展,特别是自 2022 年 11 月 chatGPT 发布以来,我们可以使用任何符合我们需求的架构。例如,可以使用开源方法如Llama2 或Falcon。

图 4:变压器的一般架构。它只在激活函数上与 BART 的架构不同,激活函数是 GeLUs 而不是 ReLUs (source)。
如何使用 LangChain 🦜️和 HuggingFace 🤗实现 RAG?
本节描述了如何使用 LangChain 创建您的 RAG。LangChain 是一个框架,用于轻松开发由 LLMs 驱动的应用程序,而 HuggingFace 是一个提供开源 LLMs 和数据集用于研究和商业用途的*台。
在我们的案例中,正如介绍中所述,我们创建了一个 RAG,其中生成器是一个 Llama2 模型,以便将其输出的质量与基础 Llama2 进行比较。我们将使 Llama2 回答问题“OpenAI 的 CEO 发生了什么?”。
该过程从加载 HuggingFace 的数据集新闻(cnn_dailymail — apache 2.0 许可证)开始,并通过 Luis 最*在 X/Twitter 上关于该主题的帖子,包括 CEO 辞职,补充了有关 OpenAI 的最新新闻。然后,我们通过创建一个文档列表(LangChain 期望的格式)来预处理它,以填充我们的向量数据库。
from langchain.docstore.document import Document
from langchain.document_loaders import HuggingFaceDatasetLoader
# Get some open ai news to add to the final dataset
openai_news = [
"2023-11-22 - Sam Altman returns to OpenAl as CEO with a new initial board of Bret Taylor (Chair), Larry Summers, and Adam D'Angelo.",
"2023-11-21 - Ilya and the board's decision to fire Sam from OpenAI caught everyone off guard, with no prior information shared.",
"2023-11-21 - In a swift response, Sam was welcomed into Microsoft by Satya Nadella himself.",
"2023-11-21 - Meanwhile, a staggering 500+ OpenAI employees made a bold move, confronting the board with a letter: either step down or they will defect to Sam's new team at Microsoft.",
"2023-11-21 - In a jaw-dropping twist, Ilya, integral to Sam's firing, also put his name on that very letter. Talk about an unexpected turn of events!",
"2023-11-20 - BREAKING: Sam Altman and Greg Brockman Join Microsoft, Emmett Shear Appointed CEO of OpenAI",
"2023-11-20 - Microsoft CEO Satya Nadella announced a major shift in their partnership with OpenAI. Sam Altman and Greg Brockman, key figures at OpenAI, are now joining Microsoft to lead a new AI research team. This move marks a significant collaboration and potential for AI advancements. Additionally, Emmett Shear, former CEO of Twitch, has been appointed as the new CEO of OpenAI, signaling a new chapter in AI leadership and innovation.",
"2023-11-20 - Leadership Shakeup at OpenAI - Sam Altman Steps Down!",
"2023-11-20 - Just a few days after presenting at OpenAI's DevDay, CEO Sam Altman has unexpectedly departed from the company, and Mira Murati, CTO of the company, steps in as Interim CEO. This is a huge surprise and speaks volumes about the dynamic shifts in tech leadership today.",
"""2023-11-20 - What's Happening at OpenAI?
- Sam Altman, the face of OpenAI, is leaving not just the CEO role but also the board of directors.
- Mira Murati, an integral part of OpenAI's journey and a tech visionary, is taking the helm as interim CEO.
- The board is now on a quest to find a permanent successor.""",
"2023-11-20 - The transition raises questions about the future direction of OpenAI, especially after the board's statement about losing confidence in Altman's leadership.",
"""2023-11-20 - With a board consisting of AI and tech experts like Ilya Sutskever, Adam D'Angelo, Tasha McCauley, and Helen Toner, OpenAI is poised to continue its mission. Can they do it without Sam?
- Greg Brockman, stepping down as chairman, will still play a crucial role, reporting to the new CEO."""
]
# load dataset with some news
loader = HuggingFaceDatasetLoader("cnn_dailymail", "highlights", name='3.0.0')
docs = loader.load()[:10000] # get a sample of news
# add openai news to our list of docs
docs.extend([
Document(page_content=x) for x in openai_news
])
接下来,我们准备为我们的 RAG 创建这两个模块。
检索器
在检索器中,我们有两个子模块:编码器和检索器。
编码器将段落转换为 d 维的嵌入向量。为此,我们从langchain.embeddings导入HuggingFaceEmbeddings,并选择我们想要使用的模型来创建嵌入。
在我们的案例中,我们选择了sentence-transformers/all-MiniLM-l6-v2,因为它创建了 384 维的向量,具有良好的质量用于计算它们之间的相似度。它在内存使用上高效且快速。您可以在这里查看有关此模型及其他模型的更多详细信息。
from langchain.embeddings import HuggingFaceEmbeddings
encoder = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-l6-v2",
model_kwargs={"device": "cpu"},
)
检索器使用langchain.text_splitter中的CharacterTextSplitter将文档拆分为一定长度的段落。
在我们的案例中,我们选择了 1000 的长度。我们开始时选择了 100,如文献[1]中所述,但通过一些初步实验,我们发现 1000 在我们的使用案例中能取得更好的结果。
然后我们使用编码器将段落转换为嵌入。最后,我们可以将它们存储在如FAISS的向量存储中。从这些存储中,我们可以稍后检索出与问题最相似的前k个文档。
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import FAISS
# create passages
text_splitter = CharacterTextSplitter(
chunk_size=1000,
chunk_overlap=0,
)
passages = text_splitter.split_documents(<YOUR DOCUMENTS>)
# store passages in embedding format in FAISS
db = FAISS.from_documents(passages, encoder)
# retrieve the most similar document to your question
db.similarity_search(<YOUR QUESTION>, k=4)[0].page_content
生成器
如前所述,生成文本的 LLM 是 Llama2。它使用量化技术,这是一种降低权重表示精度的技术,以最小化使用模型所需的内存。请注意,由于不存在免费的午餐,我们在内存大小和准确性之间进行了权衡。
这个过程带来了如运行 LLM 时资源需求减少等优点,但也有如量化导致性能降低等缺点。
from langchain.llms import LlamaCpp
llm = LlamaCpp(
model_path="local/path/to/your/llama",
n_ctx=1024, # context length
temperature=0.7, # argument to control how much you want your LLM to follow your prompt
)
一旦我们拥有了 LLM,就该设置提示模板了。提示工程在与 LLM 交互时是相关的,因为它可以显著影响输出。
当我们找到一个能为使用案例生成所需输出的提示时,我们可以创建一个模板。LangChain 提供了一个简单的解决方案来创建提示模板。我们首先定义提示的结构,并根据用户的查询以字典格式添加动态变量。在我们的案例中,{context}由检索器提供,用户的{question}则由用户提供。
from langchain import PromptTemplate
TEMPLATE = """
Use the following pieces of context to answer the question at the end taking
in consideration the dates.
{context}
Question: {question}
Answer:
"""
# create prompt template
prompt = PromptTemplate(
template=TEMPLATE, input_variables=["context", "question"]
)
现在我们已经定义了我们的 LLM 和提示模板,我们从langchain.chains中创建一个LLMChain。它允许我们结合多个组件以创建一个连贯的应用程序。
在我们的案例中,我们可以创建一个链条,获取用户的问题和上下文,用提示模板格式化它,然后将格式化的响应传递给 LLM 以获得答案。
from langchain.chains import LLMChain
# define the LLMChain
query_llm = LLMChain(
llm=llm, prompt=prompt, llm_kwargs={"max_tokens": 5000}
)
# get answer from llm based on context and question
query_llm.run({"context": context, "question": question})
我们现在准备使用我们的模型,并将其与基础 Llama2 模型进行比较。
结果:基础 Llama2 vs RAG Llama2
“OpenAI 的 CEO 发生了什么事?”
考虑到问题,“OpenAI 的 CEO 发生了什么事?” 我们的目标是展示在不对 LLM 进行微调并使用 RAG 系统的情况下,我们可以通过相关信息轻松改进提示,使 LLM 在回答时更加准确和更新。相反,我们期望基础的 Llama2 提供准确的答案,但由于其训练数据未包含上周的信息,因此可能过时。
基础 Llama2:
OpenAI 是一个非营利的人工智能研究组织,总部位于加利福尼亚州旧金山,专注于开发友好的 AI 并确保其安全使用。OpenAI 的 CEO 是 Sam Altman,自 2010 年与 Elon Musk、Greg Brockman、Ilya Sutskever 和 Vince Conitzer 共同创立公司以来,他一直担任此职务。就最*的新闻而言,关于 CEO 或 OpenAI 本身没有重大进展,但已知 OpenAI 最*通过招聘额外的 15 名员工扩大了团队,以从事与 AI 相关的各种项目,包括新模型和机器学习工具的开发。
RAG Llama2:
Sam Altman 于 2023 年 11 月 20 日辞去了 OpenAI 首席执行官职务。他被 Mira Murati 取代,后者担任了临时首席执行官。然而,Sam Altman 在几天后以首席执行官身份回到公司,新的初始董事会成员包括 Bret Taylor(主席)、Larry Summers 和 Adam D’Angelo。
正如我们从上述示例中看到的,RAG Llama 能够提供带有更新信息的答案,而无需任何额外的微调过程,这种过程既昂贵又耗时。
结论
RAGs 为组织提供了比为每一个使用案例微调 LLMs 更快且更实惠的部署 LLMs 的可能性。
正如我们在使用案例中看到的,将关于 OpenAI 及其首席执行官上周丑闻的十二篇文档添加到我们从 HuggingFace 获取的 10k 新闻集合中就足够了。我们的检索器能够为我们的生成器创建足够的上下文,从而生成关于该主题的更准确且更新的答案。
在访问外部信息方面,RAGs 是一个很好的选择,因为它们通过在生成响应之前从知识来源中检索相关信息来增强 LLMs 的能力。然而,当涉及到调整 LLM 行为以适应特定的写作风格时,使用不常见的单词或表达式,结合使用可能更为合适。
关于我
连续创业者和 AI 领域的领导者。我开发 AI 产品以服务于企业,并投资于以 AI 为重点的初创公司。
创始人 @ ZAAI | LinkedIn | X/Twitter
参考文献
[1] Patrick Lewis, Ethan Perez, Aleksandra Piktus, Fabio Petroni, Vladimir Karpukhin, Naman Goyal, Heinrich Küttler, Mike Lewis, Wen-tau Yih, Tim Rocktäschel, Sebastian Riedel, Douwe Kiela. 适用于知识密集型 NLP 任务的检索增强生成。arXiv:2005.11401, 2021
[2] Vladimir Karpukhin, Barlas Oguz, Sewon Min, Ledell Wu, Sergey Edunov, Danqi Chen, 和 Wen-tau Yih. 用于开放域问答的密集通道检索。arXiv:2004.04906, 2020
[3] Jeff Johnson, Matthijs Douze, 和 Hervé Jégou. 基于 GPU 的亿规模相似性搜索。arXiv:1702.08734, 2017
[4] Jacob Devlin, Ming-Wei Chang, Kenton Lee, 和 Kristina Toutanova. 2019. BERT: 深度双向变换器的预训练用于语言理解。arXiv:1810.04805, 2019
[5] Michael R. Douglas. 大型语言模型。arXiv:2307.05782, 2023.
[6] Mike Lewis, Yinhan Liu, Naman Goyal, Marjan Ghazvininejad, Abdelrahman Mohamed, Omer Levy, Ves Stoyanov, Luke Zettlemoyer. BART: 用于自然语言生成、翻译和理解的去噪序列到序列预训练。arXiv:1910.13461, 2019
[7] Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, Lukasz Kaiser, Illia Polosukhin. 注意力机制是唯一需要的。arXiv:1706.03762, 2017.
点积在人工智能中的力量
原文:
towardsdatascience.com/the-power-of-the-dot-product-in-artificial-intelligence-c002331e1829
简单工具如何产生惊人的复杂性
·发表于Towards Data Science ·阅读时间 9 分钟·2023 年 5 月 15 日
--
将简单的事物放大以实现更复杂的目标是一个强大的理念,这一理念奠定了生命、计算机(基于Turing 机的简单性)以及深度学习技术(基于神经网络中神经元的理念)的基础。
更多往往不同,我们继续观察到随着最先进的架构不断扩展(像GPT这样的语言模型正在改变世界),这导致了更好的泛化和令人印象深刻的结果。
研究人员声称,大型语言模型展示了涌现能力,这些能力无需过多的架构创新,而(可以说)主要来自计算资源的增加和大规模重复简单操作(有关警告,请参见这篇文章)。
尽管训练深度学习模型需要社区多年积累的独创性和技术,但大多数深度学习方法的基本构建块仍然相当简单,仅由少量的数学操作组成。
也许最重要的是点积。在本文的其余部分,我想深入探讨点积的作用、它为何如此重要,以及为何将其规模化已实现令所有与之接触的人感到惊讶的人工智能水*。

人工智能作为点的产物(DALL-E 可能有点误解了我,但它仍然看起来很酷)。
想象一下你在像 Netflix 这样的公司工作,并被分配了选择电影以推荐给数百万不同用户的任务。每个用户都有自己独特的口味,而电影的选择有数百万部,因此这是一项相当艰巨的任务。
解决这个问题的一种方法是列出电影的几个不同属性,例如电影与特定类型(例如评估它是动作片还是情感戏剧)的对齐程度。还可以列出其他信息,例如喜剧内容、一些外部因素,如电影长度、是否由 Marvel 或 Disney 制作等。然后将所有电影的属性列表放入一个向量中。
了解用户口味的一个简单方法是直接查看该用户最喜欢的电影或最*观看的几部电影,这将产生第二个向量,从而给我们提供用户口味的一个概念。
然后可以通过计算新电影与用户最喜欢的电影的相似度来简单地推荐新电影,这些相似度是根据电影属性的向量空间中的某种相似度度量来计算的。
这就是点积,AI 最喜爱的相似度度量,登场的地方。
点积是一个简单的数学运算,用于测量两个向量之间的相似度。数学上,两个向量 x 和 y 的点积计算公式为:
x · y = Σ (x_i * y_i) for i = 1 to n
其中 x_i 和 y_i 分别是向量 x 和 y 的分量,n 是向量的维度。它也可以写作:
x · y = |x| * |y| * cos(θ)
其中θ是两个向量之间的角度,||表示它们的长度。在这种解释下,点积测量了 x 和 y 的对齐程度。如果两个向量*行,则点积对应于它们的长度。如果向量彼此垂直,则点积为零。

BenFrantzDale 在英文维基百科,CC BY-SA 3.0
点积通常与余弦相似度结合使用,后者通过向量的大小对点积进行标准化,提供一个对大小不变的相似度度量:
cosine_similarity(y, x) = (y · x) / (|y| * |x|)
从几何上讲,点积可以被视为一个向量在另一个向量上的投影。当你计算两个向量 x 和 y 的点积时,可以把它看作是将向量 x 投影到向量 y(或反之),然后将投影的长度乘以向量 y 的长度。
投影是一个重要概念,并且有一个很好的解释:它们可以被认为是向量所编码的不同特征的维度比较。
回到之前的例子,考虑两部电影,电影 A 和电影 B。为了简化起见,假设这两部电影通过两个不同的属性来表征:动作强度(AI)和情感深度(ED)(通常,这些向量当然可以大得多)。向量的第一个组件代表动作强度,第二个组件代表情感深度。假设这两个属性的测量范围是-5 到 5,其中-5 表示最不强烈或最不深刻,5 表示最强烈或最深刻。
一部电影,一部相对浅显的爆米花娱乐动作片,其动作强度为 4,情感深度为-3。它的向量表示为 A = [4, -3]。
电影 B 是一部 4 小时的黑白冥想片,关于塞尔维亚政府,从鸽子的视角看待。它在情感上相当要求,但没有很多动作场景,评分为 B = [-4, 4]。
我们可以通过点积来比较这两部电影,计算结果为
A · B = (4 * -4) + (-3 * 4)= -28
通过两向量的大小进行归一化,这得到了
-28/(sqrt(25)*sqrt(32))= -0.99。
正如预期的那样,这两部电影的相似度极低,因此它们的余弦(不)相似性得分接*-1。
那么,为什么这个简单的比较两个向量相似性的操作在 AI 中如此重要?
机器学习,也许更广泛地说,(人工)智能的核心,依赖于在庞大的规模上比较模式并衡量其相似性。
然而,提出用于比较的模式并非易事。
我在之前的示例中略过的一部分是如何为电影制定标签。动作强度和情感深度并不是从电影的真实数据中直接提取的,而是由人类评估得出的,他们进行复杂的认知(主观)任务——观看电影并将其转化为明显低维的表示,从中可以看出这些电影的差异。从电影的像素空间的角度来看(对于一部 4K 的 2 小时电影,这些像素大约是 1.4*10¹²),这是一种巨大的维度降低,其中包含个人的偏见和先验知识。
复杂非线性变换的概念是使点积强大的第二个重要因素。它将输入转换为在相应学习问题的背景下比较其相似性变得有意义的表示,例如分类任务或回归问题。
将非线性变换与点积相结合是神经网络核心功能的一部分。在这里,点积用于计算每个神经元中输入的加权和,这与非线性变换(激活函数,如 tanh 或 ReLU 函数,加上网络的学习权重)结合,构成了所有神经网络运行的基本机制。
这种组合在核函数的背景下变得更加清晰。
核函数使我们能够比较新变换空间中点的相似性。它通过计算在(通常是更高维度的)投影空间中变换后的数据点之间的点积来实现这一点。巧妙之处在于,“核技巧”允许在不显式计算变换的情况下进行这一计算,这可以节省大量资源,并在支持向量机(SVMs)中找到应用,结合核技巧,构成了*年来最强大和最具影响力的算法之一。

核机器结合了非线性变换,以找到一个特征空间,在这个空间中类别标签容易被分开。原作者:Alisneaky Vector: Zirguezi, CC BY-SA 4.0
这些基本见解可以与当前市场上的最热门模型连接起来:大型语言模型。
大型语言模型的一个标准任务是翻译两种语言之间的句子,比如在英语和德语之间:
“我正在阅读一篇关于点积在人工智能中重要性的文章。”
“Ich lese einen Artikel über die Bedeutung des Skalarproduktes für die KI。”
两个句子的含义大致相同,但它们的表示方式却显著不同。
翻译任务可以被表述为找到单词的非线性变换,这种变换对应于潜在语义空间中大致相同的位置,从而捕捉它们的“意义”。翻译的质量可以通过实现的相似度来衡量。
如果“测量相似性”还没有让你产生强烈的兴趣,那么我在这篇文章中的表现还不够好。
确实,点积在变压器模型的核心中出现,这些模型已经成为现代自然语言处理(NLP)以及许多其他机器学习任务的基础。
自注意力机制是变压器模型的一个关键组成部分。自注意力机制使模型能够权衡不同输入元素之间的重要性。这使得模型能够捕捉数据中的长程依赖关系和复杂关系。在自注意力机制中,点积被用来计算注意力得分和形成上下文感知的输入元素表示。
输入元素(通常是输入文本的嵌入/分词版本)首先被线性投影到三个不同的空间:查询(Q)、键(K)和值(V),使用各自的学习权重矩阵。这会为每个输入元素生成三组向量:查询向量、键向量和值向量。
点积随后用于计算每对查询向量和键向量之间的注意力分数(score_ij = q_i · k_j)。
这衡量了查询向量和键向量之间的相似性,决定了模型对每个输入元素相对于其他所有元素的关注程度。
在计算所有相似性分数后,分数会被缩放并通过一个相似性函数,然后可以用来计算上下文函数,该函数又是注意力分数和值的简单总和:(context_i = Σ (attention_ij * v_j))
通常选择的相似性函数是 softmax,可以看作是一种核函数,它是一种非线性变换,可以在元素之间进行比较,并估计哪些元素可能对预测有用。根据具体问题,也可以使用其他核函数。从更根本的角度来看,变换器可以被视为核机器(更准确地说是深度无限维非墨瑟二元核机器,如本文讨论)。
与其他示例一样,点积与输入和输出文本的非线性变换及投影结合,定义了自注意力机制。
我希望能够说服你,点积在人工智能中扮演了关键角色,尤其是在其最新的实例中。虽然在某些特定背景下,其他向量之间的距离/相似性度量(如欧几里得距离、切比雪夫距离、马哈拉诺比斯距离等)可能更为合适,但点积可能是最广泛使用的,并且构成了量化数据中相似性、关系和依赖性的基础构件。当与非线性变换结合时,点积处于各种算法和模型的核心,从支持向量机到神经网络,再到 Transformers 中的自注意力机制。
随着人工智能*年来取得的显著进展,我发现反思构成其基础的操作的简单性和适应性非常有趣。
有时看到这种策略的有效性可能有些令人害怕:在最*的播客中,Max Tegmark 评论了训练大型语言模型背后令人惊讶的简单性/愚蠢性(只需给它们大量文本,并使用自注意力的变换器预测下一个单词/句子)。在某些方面,智能可能比我们想象的更简单,正如大多数与 ChatGPT 互动过的人所确认的那样。
这具有重要的意义:计算机非常擅长以可靠和快速的方式执行一些非常简单的任务,并将其扩展到 Nth 级别。随着摩尔定律的承诺,我们将继续扩展这些模型。
这些是令人着迷且可能危险的时期,很可能点积将位于其中的核心。
应对 AI 风险的实际侧面
·
关注 在 Towards Data Science 发布 · 发送至 新闻通讯 · 3 分钟阅读 · 2023 年 9 月 14 日
--
在过去几年中众多激动人心的 AI 创新背后,我们发现了一系列已知和新兴的风险:算法偏见、隐私问题和版权侵犯等。这还不包括我们甚至尚未开始接触的宏观级社会问题,比如未来不久可能会有数百万个职位变得过时的机会。
数据和机器学习专业人士一直在努力提高对这些问题的认识,并提出可行的解决方案,旨在*衡技术进步与公*和负责任的实践。尽管现在可能还难以判断他们——以及我们所有人——在这一细微问题上的成功程度,但如果我们希望在我们的专业社区(及其他领域)中产生积极变化,了解这些讨论的轮廓仍然至关重要。
本周的亮点文章以清晰和务实的方式处理了关于人工智能的棘手问题——从监管到技术规范。不论你是对这一话题陌生还是已有一定了解,我们认为这些文章都值得你花时间阅读。
-
生成式人工智能的法律和伦理视角 对于生成式人工智能工具带来的复杂问题,奥利维亚·塔努维贾贾 的最新概述提供了易于理解的入门介绍:它提供了足够的细节以帮助你了解这一复杂话题,并提供了有用的资源,帮助你扩展对自己最关心领域的知识。
-
反对人工智能监管的理由毫无意义 欧洲联盟的人工智能法案常被誉为迄今为止最严肃的尝试来监管人工智能产品的开发和实施;阿德里安·布克 详细解读了其最显著的特征,反思了可能存在的不足,并倡导更多司法管辖区认真而主动地考虑类似的立法倡议。

图片由 mostafa meraji 提供,来自 Unsplash
-
下一步是负责任的人工智能。我们如何实现这一目标? 为了实际应用负责任和伦理的人工智能,埃尔多安·塔斯克森 提出了一个 6 步路线图,团队和组织可以根据自身需求进行调整。这提醒我们,个人从业者有能力主动影响实践和决策,从而在构建基于机器学习的产品过程中发挥作用。
-
OpenAI 的网络爬虫和 FTC 的失误围绕版权、艺术作品以及 LLMs 和图像生成模型的训练方式的争论从未如此激烈。Viggy Balagopalakrishnan通过关注 OpenAI 的最新新闻和 FTC(联邦贸易委员会)在监管资金充足的科技公司方面面临的挑战,提供了当前僵局的有用快照。
-
用护栏保护 LLMs
在微观地方控制 AI 工具的影响范围、领域和效果也很重要:例如,如果你正在进行大语言模型的集成,你肯定不希望它出现冒犯性语言或坚称幻觉是事实。Aparna Dhinakaran和 Hakan Tekgul 分享了一份实用指南,介绍了允许开发者对模型输出实施严格参数的开源工具。
想找其他主题的精彩读物?这些顶尖文章绝对不会让你失望:
-
如果你刚刚踏入环境数据科学,Caroline Arnold的易懂介绍是一个很好的起点。
-
机器学习如何从编码演变为嵌入?Mina Ghashami的解读既全面又适合初学者。
-
如果你想了解变压器的战略能力的有趣且启发性的教程,不要错过Charlie O'Neill关于构建井字棋模拟器的详细介绍。
-
为了帮助你解读职位描述及其实际内容在现实生活中的应用,Stephanie Kirmer概述了一些数据科学角色的关键类型。
-
想要深入了解数学?你绝对要收藏Gabriel de Longeaux关于两封信封悖论的详细探讨。
-
以实际操作的角度结束,我们强烈推荐Mariya Mansurova最*的使用 BERTopic 的逐步指南用于高级主题建模。
感谢您支持我们作者的工作!如果您喜欢在 TDS 上阅读的文章,请考虑成为 Medium 会员——这将解锁我们整个档案(以及 Medium 上的所有其他文章)。
对早期排序阶段的原则性方法
一种系统化的方法用于设计和评估推荐系统中的候选生成和早期排序阶段,并对核心指导原则进行深入分析。
·
关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 12 月 6 日
--
众所周知,在推荐系统中,构建推荐有几个阶段:首先是候选生成,也常被称为检索,其次是一个或多个排序阶段。学术论文对早期阶段关注不多,但在实际应用中,这些阶段非常重要。如何衡量它们的质量也同样重要。

作者插图
候选生成通常是由不同来源的组合来组织的:
-
最受欢迎的项目,
-
类似于用户的历史记录,
-
在不同层次上结合之前的方法:例如,从用户的历史记录(或 ANN,或热门项目)中提取类别,然后从中选择流行的项目。
尽管每种方法本身可能不复杂,但整体组合却相当复杂,促使人们思考:如何优化它?要做到这一点,当然需要定义究竟需要优化什么,即应该使用什么指标来衡量候选生成的质量。
尽管我们的讨论集中在候选生成阶段,但值得注意的是,这些原则同样适用于所有早期排名阶段,因为它们也为后续阶段提供候选项。
有多种方法。有时质量根本没有被衡量(或仅凭目测),这一阶段也没有系统地优化。有时,会以某种方式衡量候选项的整体相关性。如果系统推荐一些异常内容,这也被认为是候选生成中的一个问题。有时,这种相关性甚至与最终阶段优化的内容进行对比。也就是说,候选项应该已经足够相关,无论如何衡量,最终排名将选择最具吸引力的(有吸引力的、可点击的等)。
有时,特别是在论文中,会使用HitRate@k、Recall@k、Precision@k、MRR、NDCG等指标,仅关注正向(相关)文档。如果用户随后与某个文档进行互动,则认为该文档是相关的。我更倾向于这种方法,但它存在显著的偏差问题,例如,用户往往会与系统推荐的项目进行更多互动。
有一段时间,我尝试制定一种不同的候选生成方法,并一直支持它。幸运的是,我不是唯一的支持者 — 这种方法已在各种系统中使用(例如,这篇关于扩展 Instagram Explore 推荐系统的文章中详细介绍)。然而,我不确定它是否可以称为行业标准 — 确实有一些主要系统没有使用它。
该方法基于以下原则:
早期阶段的主要目标是从最终排名的角度找到最佳文档。
简而言之,目标是找到最佳的文档。最佳的定义不是基于任何相关性,而是由当前最终排序器来决定。最终由排序器选择的候选项是好的,而其他的则不然。如果排序器发生变化(且这种变化可能很频繁),则质量评估也会随之变化。
(对于多阶段排名可能会有修改:可以使用最终排名器专门评估早期阶段的质量,或者使用下一阶段的排名器。也就是说,通过下一阶段但未通过最终阶段的候选项可以被视为负面或正面。我不确定哪种方法更好。)
尽管这种方法并不完美,我个人认为这是唯一可行的方法,意味着只有通过这种方法才能长期系统地改善所有阶段的质量,而不会遇到基本问题。至少,我不明白如何用其他方法实现这一点。
概述了这种方法的基本原则之后,现在让我们深入探讨其优缺点,首先从其潜在的缺点入手。
方法的缺点
-
整个候选生成过程,包括其质量如何衡量,开始显著依赖当前的排名方法。这增加了复杂性,在进行比较时考虑这一点是很重要的。当排名器发生变化时,早期阶段需要重新训练。
-
大多数情况下,系统最初是在不遵循这一原则的情况下构建的。将系统从另一种状态转变为遵循这一原则可能非常具有挑战性。特别是,如果系统的排名相当低(但由于各种技巧的存在,推荐结果是可接受的),那么遵循这一原则将不会改善系统,反而可能在当前显著恶化推荐结果。
-
这一原则假设排名器应在整个文档库中运行良好。否则,如果有质量低下的文档被错误地推荐,那么在试图取悦排名器时,候选生成过程最终会发现它们也被推荐出去。与排名器只操作一组已经相当不错的候选项相比,这使得排名器的训练变得更加复杂。
-
候选生成并不试图改善服务的端到端指标。根据这一原则可以改善它,但最终可能以失败告终。(然而,这恰好表明排名存在问题,比如错误的目标。)这增加了工作的复杂性:你不断改进,但最终却无法部署它。
-
对于产品规则的支持有限。这一原则规定,除了硬规则之外的所有规则应在最后阶段应用,早期阶段将会适应它们。这不仅涉及到技巧,还包括改进推荐系统各个方面的合理方法,如探索、多样性等。你必须提供多样化的候选项,因为排名器会选择它们。
从黑客到和谐:在推荐系统中构建产品规则
不要让启发式方法削弱你的机器学习,学会将它们结合起来
towardsdatascience.com
在探讨了局限性之后,让我们现在转向这种方法的优势。
这种方法的优点
-
这一原则基于分解。它为早期阶段提供了更清晰且可测量的目标,显著简化了系统。推荐的目标和损失的选择复杂性集中在排名阶段(这是无法避免的一个方面),而早期阶段主要集中在有效找到顶级候选者的功利任务上。因此,早期阶段仅作为加快排名过程的工具。
-
在这一原则中,没有根本性的限制。如果想象一个理想的推荐系统,什么也不会阻止它以这种方式构建。(对于其他方法,这一点不能成立——完美的推荐并不一定能够准确猜测用户将与之互动的内容!)随着排名的改善,这种简化的候选生成指标会越来越接*端到端指标。这类似于某些圈子里广为人知的迭代方法:‘改进指标——基于这些指标改进产品’。
-
不同的排名阶段彼此对齐;它们不会试图优化不同的内容。在系统中,如果不是这种情况,例如,如果你将候选者的总数翻倍,那么系统的整体质量可能不会提高,甚至可能会下降。例如,如果早期阶段优化某种相关性,那么额外的候选者可能会较少相关,从而整体相关性下降(尽管点击率可能会增加)。
-
由于分解的观点:早期阶段的衡量(因此优化)要容易得多。评估过程将在下节中讨论。训练基本上归结为提炼排名模型。(尽管有一些细微差别。例如,记录一些没有进入排名前列的候选者是比较好的。)
-
此外,对于训练和测量早期阶段,我们不再需要用户,这意味着不必在他们身上推出新方法。例如,可以使用爬虫技术,正如我们稍后将讨论的,通过向排名服务发送一系列使用新候选者的请求。
衡量候选生成
现在,让我们深入探讨文章中最实际的部分,讨论如何根据我们之前概述的原则在实践中测量候选生成(或排序的早期阶段)的质量。
首先,让我们检查一个简化但非常重要的情况:当排序完全基于一个最终模型的分数时。在这种情况下,我们可以简单地比较这个模型对于两组候选的*均分数。如果一种方法发现最终模型为其分配的预测高于另一种方法,那么第一种方法更好。
是否取整个输出的*均预测,仅取前几个位置,还是按位置递减(类似于IDCG——NDCG中的分母)似乎不是非常关键。可以根据个人喜好选择这些选项中的任何一个。
不过,需要考虑一个技术细节。如果这样的指标是离线测量的,需要能够在自定义候选项上运行排序(或整个推荐系统)。这可以通过在历史查询上进行模拟(离线重放——即试图回顾性地重现所有实体的所有信息)或通过抓取(如前所述,向推荐服务发送大量新查询,以便它使用感兴趣的候选生成方法)来完成。在这两种情况下,都会为相同查询的不同生成方法获得结果(最终模型的预测)。这对指标的敏感性是有益的。
然而,如果这个指标是在生产服务中在线测量的,那么所有计算都可以简单地基于模型的日志预测进行。这要简单得多,但灵活性较差,并且比较将跨越不同的查询。指标的敏感性降低(可能是因为某些方法恰好接收到更复杂的查询)。
现在让我们转到一般情况:最终排序不仅仅是某个模型的预测,还涉及大量其他逻辑、重排序、业务规则、随机化等。当你考虑如何在这种松散的表述中比较不同的候选集(什么是好,什么是坏)时,并不是很明显。
不过,我曾经设计了一种方法,结果非常简单有效。到目前为止,我还没有看到它在其他地方提到过。
方法如下。我们向候选源列表中添加一个特殊的源,该源生成随机候选(例如,均匀分布)。我们为这个源分配一个小的固定配额(例如,50 个候选)。然后我们观察推荐的文档中有多少比例最终来自这个源。如果我们的主要候选生成足够好,那么随机候选很少能胜过它,即很少进入前列。如果它很差,那么随机候选会经常超越它。

仅用于说明目的的合成数据
当然,我们假设添加随机候选者不会显著恶化系统:大多数候选者不会被推荐,而那些被推荐的候选者不会大幅降低用户体验,并且还会为用户和排名模型增加探索(它会在这些示例上进一步训练)。如果情况并非如此,那么首先需要‘修正排名’。😉
这种方法最酷的地方在于它不仅可以作为候选生成的度量标准,还可以作为整个系统健康状况的监控工具,包括最终排名。它检查候选生成与排名的一致性(是否为排名优化)。如果排名本身因某些原因下降,那么候选者也会变得不那么适合它。我们在实践中见证了这一点,当其中一个组件发生故障时,响应中随机候选者的比例增加了。
顺便提一下,这个特殊来源的随机性可以进行调整。如果你使用的不是均匀分布,而是与文档的受欢迎程度成比例的分布,它会成为更强的‘对抗性’参与者(这也可能增加敏感性)。然而,使用均匀抽样,可以提供一个关于我们的候选生成理想的查询比例的分析估计(即,即使我们将整个数据库添加到候选者中,结果也不会改变):

在这个公式中,N 代表数据库中的候选者总数,k 是使用的随机候选者数量,R 表示至少有一个随机候选者出现在输出中的查询比例。
结论
在这一探索过程中,我们专注于推荐系统中候选生成和早期排名阶段的一个具体原则。通过全面检查其优点和挑战,并提出实用的评估方法,我们强调了这一原则作为改进这些系统的强大工具的潜力。采纳这一原则不仅简化了推荐的复杂过程,还确保了效率和有效性。随着我们继续完善和应用这一原则,它将成为推动推荐系统领域发展的有希望的方向。
现代计算机科学家的原则
原文:
towardsdatascience.com/the-principles-of-a-modern-computer-scientist-8be9e7494e7e
未来将塑造人工智能的从业者可以从当前计算机科学家这一代的成就和不足中学到什么
·发布于 Towards Data Science ·阅读时间 2 分钟·2023 年 5 月 31 日
--

Demis Hassabis 于 2022 年 11 月在 Fundación Princesa de Asturias 演讲。照片由 Fabien Girardin 提供。
也许像你一样,在过去几年中,我参加了许多关于人工智能的过去、现在和未来的活动。AI 先锋和 DeepMind 联合创始人 Demis Hassabis 是其中一个活动的演讲者,地点在西班牙的奥维耶多。那时,我对他的辉煌职业生涯和与 AlphaFold 的迷人实验知之甚少。
我视 Demis Hassabis 为现代计算机科学家的典型代表。他不仅展现了创造力和想象力,还有逻辑、抽象思维和工程技能。当前主流的技术文化受制于“决定论思维”的驱动。愿景被极端化。未来感觉是二元的,乌托邦和反乌托邦的思考同时旋转。相比之下,Demis 谈论 AI 时有着细致的考量;他意识到其他学科的贡献,并能够清晰地表达他的工作在社会与技术的共同演变中所扮演的角色。我从这次活动中获得了启发。
从我这一代计算机科学家的成就和不足中,可能可以总结出一系列原则。年轻一代现在在学校里可以将这些“现代计算机科学家的原则”作为他们未来实践的蓝图。其中一些原则可能如下所示。
现代计算机科学家重视*:
想象力优于逻辑
逻辑和抽象思维是至关重要的,但它们可能将任何挑战限制为狭窄的有限解决方案。想象力是现代计算机科学家的一个关键成分,使其能够探索最广泛的可能性。
责任重于敏捷性
在科技领域,语言中的“冲刺”、“加速器”和“敏捷方法论”将优化和快速执行神圣化。相比之下,现代计算机科学家以识别他们工作潜在后果为荣。而这需要时间和耐心。
创造力重于过程
计算机科学的实践不仅仅限于遵循结构良好的迭代过程。现代计算机科学家从多个角度看待挑战。他们首先运用创造力思考。他们在安全的环境中构建实验和原型,以创建多条选择路径。
细微差别重于信念
现如今的世界奖励说服力。现代计算机科学家受到怀疑的驱动。他们知道如何倾听和挑战信念。他们的使命是将这些信念综合成细微的结论。
- 虽然右侧的项目也有价值,但现代计算机科学家更重视左侧的项目。当然,可能还有其他原则,这些原则不应被视为普遍适用的规则。
感谢Pelayo González Arbues带我参加此次活动,祝贺Irene Díaz主持了这些鼓舞人心的演讲和对话。
AI 编程工具的到来:产品工程团队将如何使用它们

作者使用 Midjourney 制作的图像
生成式 AI 将如何影响产品工程团队 — 第二部分
·发表于 Towards Data Science ·10 分钟阅读·2023 年 7 月 26 日
--
这是一个六部分系列的第二部分,探讨了针对开发者的生成式 AI 生产力工具(如 Github Copilot、ChatGPT 和 Amazon CodeWhisperer)如何影响整个产品工程团队的结构。
在 第一部分,我们探讨了:
-
产品工程的现状以及随着生成式 AI 工具的兴起,团队是否需要更少的人工工程师的可能性。
-
传统的 5:1 比例在技术团队中的应用:在行业中,通常每位产品经理对应大约五名工程师。
-
产品经理和工程师在当前产品开发过程中的角色,以及这些角色随着 AI 进步可能发生的变化。
-
过去的研究如何对哪些职业受到 AI 影响最小的预测产生了偏差,以及大型语言模型(LLMs)如何颠覆了这些预测,特别是对技术和创意行业的影响。
AI 编程工具的爆炸性增长
自动化几乎自软件工程存在以来就已经成为其一部分。埃里克·雷蒙德的 2003 年里程碑式论文,《Unix 编程艺术》 反思了 17 条软件工程师的设计原则,其中包括 生成规则:“避免手动编程;在可能的情况下,编写程序来编写程序”。
雷蒙德的建议即使在发布 20 年后仍然适用:
“人类在关注细节方面 notoriously 表现糟糕。因此,任何形式的手动编程都是延迟和错误的丰富来源。你的程序规范越简单和抽象,人类设计者弄对的可能性就越大。生成的代码(每一层)几乎总是比手动编写的更便宜、更可靠。”
自 Raymond 写下这些话以来,我们已经开发出了自动化测试工具,linters(自动检查我们编写的代码的工具)、开发环境的自动补全(像拼写检查器,但用于代码),甚至框架(如 React 和 Django),它们自动生成大量的基础代码,用于网站和移动应用等通用应用程序。在大多数情况下,开发者对自动化赞不绝口,同时始终默默相信我们的经验、技能和创意独特性会让我们难以被替代。麦肯锡及其同行也告诉我们我们会从 AI 自动化的魔爪中安全逃脱,这一点也没有帮助。
代码自动化领域最*的补充是一组工具,它们承诺与开发者*等地并肩工作,看起来非常有能力远超我们之前的想象。就像 Raymond 的第 17 条原则一样,这些工具可能会比人类更擅长编写复杂的软件。
我写作时,开发者辅助工具中的头号明星可能是Github Copilot,它毫不含糊地自称为配对程序员(即那个坐在你旁边共同编写代码的编程伙伴)。Copilot 于 2022 年 6 月发布给开发者,而它那更年轻、更迷人的兄弟,Copilot X,则在今年 3 月宣布。Github 声称 Copilot 能够提高开发者生产力,并且他们的研究报告显示该工具将完成工程任务的时间缩短了多达 55%,这些声明看起来完全合理。
Github(别忘了这是一家由微软拥有的公司,微软还拥有 传闻中的 45% 的 ChatGPT 制造商 OpenAI 股份)并不是唯一的选择。亚马逊 CodeWhisperer 去年也宣布推出,声称能够 提高开发者生产力 57%。像 Copilot 一样,CodeWhisperer 可以在工程师的开发环境(IDE)中访问,并能够纠正、评论、解释和编写代码。

IDE 很久以来已经能够提供代码自动补全,但有了 Copilot X,它可以在很少的上下文下编写整个代码段
在一篇关于 Github 团队如何开发和改进 Copilot 的引人入胜的文章中,John Berryman,Github 的高级研究员,解释了不仅仅是开发者面前的代码被用来提示 AI 模型。Berryman 解释道,
“关键在于我们不仅要提供给模型 GitHub Copilot 用户当前正在编辑的原始文件;相反,我们会在 IDE 内寻找额外的上下文信息,以提示模型更好的完成方案。”
正是这些开发工具可以利用的更广泛的上下文——IDE、开发者机器上的文件、git 中的代码库,甚至潜在的应用程序文档——使得这些工具如此强大。这种广泛的上下文降低了开发者“提示工程师”从 AI 中获取解决方案所需的技能水*。改进提示的示例可以从它们操作的环境内直接获得,也可以从 数十亿行 预先存在的代码中获得。这使得大型语言模型不仅能生成合适的输出,还能超越普通开发者的能力。
有了 Copilot X,代码界面本身不再是 AI 唯一可以使用的调色板,也不再仅限于自动生成代码。用户可以在开发环境中突出显示代码,并简单地询问代码的作用:

Copilot X 扩展了编码助手的范围,进入了类似 ChatGPT 的聊天环境
虽然 Copilot 和 Codewhisperer 是针对开发者优化的大型语言模型的具体实现,但即使是通用的 ChatGPT 作为工程伴侣也非常方便。
我已经使用了 GPT-3.5 和 GPT-4 模型好几个月,涉及各种任务,并继续对它的能力感到困惑——无论是代码还是其他多个学科。最*,我在重新使用生疏的 Python 技能来玩弄 OpenAI 的 API 时,似乎有些不厚道地没有向 ChatGPT 请求帮助。
正如你现在所预期的,通用聊天机器人能够给我一些完全合理的设置说明,这些说明反映了在线文档,但额外的好处是我可以通过对话逐步展开并扩展到其他话题(比如为什么 Python 在我每次安装时总是在 Windows 上无法正常工作)。

即便是通用的 ChatGPT 也非常擅长在技术领域提供各种帮助。
为了不被 Copilot 和 Codewhisperer 落在后头,OpenAI 最*宣布了他们自己增强代码功能的版本——一个带有代码解释器的 GPT-4 模型,它既可以编写也可以执行 Python 代码。这改进了 ChatGPT 在相当通用的模型中的代码补全实现,实际上允许它在聊天环境中直接编写和运行代码。

带有代码解释器扩展的 ChatGPT 不仅可以编写代码,还可以运行代码。
到目前为止,我花了很多时间讨论这个领域的三大玩家:Github、Amazon 和 OpenAI,但其他公司也不甘示弱。谷歌宣布了Google Cloud 的 Duet AI,这是一个类似 Copilot 和 Codewhisperer 的代码助手,同时还在其 Colab 中提供了AI 辅助机器学习工具(实现了 Codey,这是基于他们自己 PaLM 2 模型的一系列代码模型)。
在开发者特定工具之外,微软正在推出一系列 Copilot,包括 Windows 和Office(价格令人咋舌),Salesforce 最*似乎重新品牌了他们的每一个产品,在产品名称后添加了‘GPT’。谷歌也不甘落后,预计将把 Duet 添加到 Google Workplace,这将像 Office Copilot 一样帮助人们编写文档和创建稍微少些枯燥的演示文稿。
看起来无论你往哪里看,都有人在将资金投入到使人类更高效的 AI 模型中。表面上看,早期迹象表明这一切是有效的,我们应该为其影响做好准备。
产品工程团队将如何使用这些工具?
第一次我花大量时间用 ChatGPT 生成代码时,我实际上是在要求它生成一些用户故事。一位朋友让我帮忙编写一个应用程序,而我的专业编程日子现在已经远去,我需要一些帮助。
我的思考过程相当简单:“我现在不能再编程了,但我可以用开发人员可以理解的方式描述所需的内容,这样我们就可以聘请一个自由职业者来实际编写代码”。
我向 ChatGPT 解释了我希望它创建一些 行为驱动设计(BDD)故事。BDD 是一种相当普遍采用的开发方法,它作为客户(和产品经理)所需内容与工程师将编写的代码之间的中介。编写 BDD 故事是检验自己对问题理解的一个好方法,所以自然地,我让 ChatGPT 为我做了这件事。

ChatGPT 创建的一个 BDD 用户故事的示例——因为巫师与机器人。这就是全部。
重要的是要理解,BDD 故事并不是对将要执行的代码的*似,而是一个非常好的方法来了解交付的应用程序是否与产品经理真正要求的相符(并且希望,符合客户真正想要的)。
在要求这些用户故事之后,我意识到没有理由不能要求 ChatGPT 编写代表这些故事的代码。通过一点提示工程,我建议 ChatGPT 用 Javascript、HTML 和 CSS(构建简单网页应用程序的最基本代码)创建这个应用程序。两分钟后,我得到了一个应用程序的构建块,它代表了我们为其编写用户故事的功能基础。
一旦我让应用程序以其摇摇晃晃、刚孵化出的形式运行时,我意识到任何自尊心的开发人员都会基于代码和用户故事创建测试。因此,在与 ChatGPT 简要讨论了最适合 Javascript 的测试运行器后,我要求创建一组更技术性的、测试驱动开发的(TDD)测试,以确保应用程序符合我最初的 BDD 故事。

ChatGPT 很好地推荐了 Jasminne Javascript 测试运行器,并编写了测试。
整个过程最令人惊讶的是,完成一些开发人员最不喜欢的任务的轻松程度。对于大多数工程师来说,工作的美在于找到解决方案的创造力和代码成功运行的刺激。编写测试、创建文档、编写用户故事甚至引导基本代码是乏味的任务,从情感上讲,最好避免。
通过这个过程,我想起了 Simon Wardley 的一个发人深省的 推文线程。Simon 是 Wardley Maps 方法的创建者。Simon 的评论提出,你的测试套件应当表示和解释你产品实际包含的复杂且相互依赖的关系图,这也是你大量知识产权所在之处。
X : 编写一个商品规范的最佳方法是什么?
Me : 你的测试套件。
X : 啊?
Me : 每个新事物都从几个基本的测试开始,随着它的发展会增加更多测试,你的产品应该基于这些测试构建,最终这些测试应帮助定义商品。
Me : 你的业务、硬件和软件应该尽可能地将测试驱动开发融入其中。如何在复杂的环境中改变任何东西而不进行测试呢?地图上的每一条线都是一种关系,一个应该有测试的接口……
…这些测试定义了接口的操作,它们是你交给他人的规范,你用来检查产品的规范,你用来测试商品的规范,然后决定你希望进行的交易。这些测试应随着事物本身的演变而扩展和演变。
尽管测试的编写没有应用代码那样令人兴奋,并且常常被视为维护的负担,但良好的测试展示了应用程序的预期行为。更重要的是,测试不仅描述了某物应该如何表现,还展示了它确实能够一再如此表现。定义一个真正有价值的测试套件的范围和细节是产品和工程团队共同完成的协作工作的一部分。Simon 的推文激励我思考,也许,一个经过精心设计的测试开始了生成应用程序的未来。
从这个角度看,我们可以开始设想一个接受生成式 AI 工具的产品工程团队的未来。挖掘客户需求、理解潜在解决方案、分析为客户提供服务所创造的价值并优先排序工作将仍然像今天一样重要。产品管理无疑会从生成式 AI 工具中受益,但我怀疑对产品学科的影响会比对技术团队的影响小。
这让我们回到了那五名工程师。
在第三部分,你可以阅读到:
-
生成式 AI 工具如何可能颠覆长期以来的 5 名工程师对 1 名产品经理的比例。
-
像 Github Copilot 和 AWS Amplify Studio 这样的工具如何重塑产品开发,将工程师的重点从手动编码转向设计、架构和集成。
-
生成式 AI 工具如何帮助面临过时技术、轻松处理复杂移植和重构的团队。
-
AI 工具在移动和网页应用开发中的可能统一影响,减少重复劳动,弥合网页、Android 和 iOS 开发之间的技能差距。
-
编码自动化对初级开发者和工程进展的影响
本系列其他文章:
附注:如果你喜欢这些关于团队的文章,可以看看我的 Teamcraft 播客,在这里我和我的联合主持人安德鲁·麦克拉伦(Andrew Maclaren)与嘉宾讨论使团队运作的因素。
正确调用 ChatGPT API 的方法
如何可靠地调用 ChatGPT API 以构建稳健的应用程序
·
查看 发布于 Towards Data Science · 11 分钟阅读 · 2023 年 7 月 15 日
--
现在 LLM 随处可见,尤其是 ChatGPT。正在基于它构建大量应用程序,如果你还没尝试过,应该试试看。

由 Midjourney 创建。
在构建基于 ChatGPT 的应用程序时,通常需要进行多个并行调用。不幸的是,你不是唯一一个需要这样做的人。由于许多应用程序每天处理数百万个请求(顺便说一句,给他们的工程团队点赞),API 经常会返回“请求过多”的错误。因此,我们需要一种有效的方法来处理这种错误,同时进行多个并行调用。
在这个简短的 Python 教程中,我们将涵盖两个重要主题,以有效地调用 ChatGPT API:
-
并行执行多个调用
-
在调用失败时重试调用
1. 并行执行多个调用
执行调用的最简单方法是同步执行,也就是发送请求并等待响应到达以继续程序。我们可以简单地这样做:
import requests
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {OPENAI_API_KEY}"
}
response_json = requests.post("https://api.openai.com/v1/chat/completions", headers=headers, json={
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": "ping"}],
"temperature": 0
}).json()
print(response_json["choices"][0]["message"]["content"])
Pong!
如果我们在一个简单的系统中工作,这样做就可以了,但是,如果我们希望并行地对 API 或其他资源(如数据库)执行多个调用,我们可以使用异步方式获得更快的响应。
异步执行任务将触发每个操作并等待它们并行完成,从而减少等待时间。
实现这一点的基本方法是创建不同的线程来处理每个请求,然而,有一种更好的方法可以做到这一点使用异步调用。
使用异步调用通常更高效,因为您可以指定应用程序应等待的确切位置,而在传统的线程处理中,系统将自动使线程等待,这可能是次优的。
下面我们展示了使用同步和异步调用之间的差异示例。
# Sync call
import time
def delay_print(msg):
print(msg, end=" ")
time.sleep(1)
def sync_print():
for i in range(10):
delay_print(i)
start_time = time.time()
sync_print()
print("\n", time.time() - start_time, "seconds.")
0 1 2 3 4 5 6 7 8 9
10.019574642181396 seconds.
#Async Call
import asyncio
async def delay_print_async(msg):
print(msg, end=" ")
await asyncio.sleep(1)
async def async_print():
asyncio.gather(*[delay_print_async(i) for i in range(10)])
start_time = time.time()
await async_print()
print("\n", time.time() - start_time, "seconds.")
0.0002448558807373047 seconds.
0 1 2 3 4 5 6 7 8 9
asyncio.gather方法将触发所有传递给它的异步调用,并在它们准备就绪后返回它们的结果。
遗憾的是,使用requests库进行异步调用是不可能的。您可以使用aiohttp库来实现。下面是使用aiohttp进行异步调用的示例。
import aiohttp
async def get_completion(content):
async with aiohttp.ClientSession() as session:
async with session.post("https://api.openai.com/v1/chat/completions", headers=headers, json={
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": content}],
"temperature": 0
}) as resp:
response_json = await resp.json()
return response_json["choices"][0]['message']["content"]
await get_completion("Ping")
Pong!
如前所述,要执行异步请求,我们需要使用asyncio.gather方法。
async def get_completion_list(content_list):
return await asyncio.gather(*[get_completion(content) for content in content_list])
await get_completion_list(["ping", "pong"]*5)
['Pong!',
'Ping!',
'Pong!',
'Ping!',
'Pong!',
'Ping!',
'Pong!',
'Ping!',
'Pong!',
'Ping!']
虽然这种方法有效,但是这样做调用不理想,因为我们为每次调用重新创建会话对象。我们可以通过以下方式重复使用同一个会话对象来节省资源和时间:
async def get_completion(content, session):
async with session.post("https://api.openai.com/v1/chat/completions", headers=headers, json={
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": content}],
"temperature": 0
}) as resp:
response_json = await resp.json()
return response_json["choices"][0]['message']["content"]
async def get_completion_list(content_list):
async with aiohttp.ClientSession() as session:
return await asyncio.gather(*[get_completion(content, session) for content in content_list])
await get_completion_list(["ping", "pong"]*5)
简单吧?通过这种方式,您可以轻松执行多个调用。然而,一个问题是通常不建议通过这种方式执行无限次调用,因为这样可能会过载系统,并且会受到惩罚,导致在某段时间内无法执行额外的请求(相信我,您会)。因此,建议限制同时执行的调用数量是一个好主意。您可以通过asyncio.Semaphore类轻松实现这一点。
Semaphore类创建了一个上下文管理器,用于管理其上下文中当前正在执行的异步调用的数量。如果达到最大数量,它将阻塞直到某些调用完成。
async def get_completion(content, session, semaphore):
async with semaphore:
await asyncio.sleep(1)
async with session.post("https://api.openai.com/v1/chat/completions", headers=headers, json={
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": content}],
"temperature": 0
}) as resp:
response_json = await resp.json()
return response_json["choices"][0]['message']["content"]
async def get_completion_list(content_list, max_parallel_calls):
semaphore = asyncio.Semaphore(value=max_parallel_calls)
async with aiohttp.ClientSession() as session:
return await asyncio.gather(*[get_completion(content, session, semaphore) for content in content_list])
start_time = time.perf_counter()
completion_list = await get_completion_list(["ping", "pong"]*5, 100)
print("Time elapsed: ", time.perf_counter() - start_time, "seconds.")
print(completion_list)
Time elapsed: 1.8094507199984946 seconds.
['Pong!', 'Ping!', 'Pong!', 'Ping!', 'Pong!', 'Ping!', 'Pong!', 'Ping!', 'Pong!', 'Ping!']
这里的一个可选事项是报告调用进度如何。您可以通过创建一个小类来实现这一点,该类将保存进度并在所有调用之间共享。您可以像下面这样做:
class ProgressLog:
def __init__(self, total):
self.total = total
self.done = 0
def increment(self):
self.done = self.done + 1
def __repr__(self):
return f"Done runs {self.done}/{self.total}."
async def get_completion(content, session, semaphore, progress_log):
async with semaphore:
await asyncio.sleep(1)
async with session.post("https://api.openai.com/v1/chat/completions", headers=headers, json={
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": content}],
"temperature": 0
}) as resp:
response_json = await resp.json()
progress_log.increment()
print(progress_log)
return response_json["choices"][0]['message']["content"]
async def get_completion_list(content_list, max_parallel_calls):
semaphore = asyncio.Semaphore(value=max_parallel_calls)
progress_log = ProgressLog(len(content_list))
async with aiohttp.ClientSession() as session:
return await asyncio.gather(*[get_completion(content, session, semaphore, progress_log) for content in content_list])
start_time = time.perf_counter()
completion_list = await get_completion_list(["ping", "pong"]*5, 100)
print("Time elapsed: ", time.perf_counter() - start_time, "seconds.")
print(completion_list)
Done runs 1/10.
Done runs 2/10.
Done runs 3/10.
Done runs 4/10.
Done runs 5/10.
Done runs 6/10.
Done runs 7/10.
Done runs 8/10.
Done runs 9/10.
Done runs 10/10.
Time elapsed: 1.755018908999773 seconds.
['Pong!', 'Ping!', 'Pong!', 'Ping!', 'Pong!', 'Ping!', 'Pong!', 'Ping!', 'Pong!', 'Ping!']
关于如何执行多个异步请求,这一节就到此为止。通过这种方式,您可以执行多个异步调用,限制每次调用的数量,并报告进度。然而,仍然有一些问题需要处理。
发出的请求可能由于服务器过载、连接中断、请求不当等多种原因而失败。这些可能会生成异常或返回不可预测的响应,因此我们需要处理这些情况,并自动重试失败的调用。
2. 失败时重试调用
为了处理失败的调用,我们将使用tenacity库。Tenacity 提供了函数装饰器,可以在函数调用生成异常时自动重试。
from tenacity import (
retry,
stop_after_attempt,
wait_random_exponential,
)
为了给我们的调用提供重试功能,我们需要添加@retry装饰器。使用它而不添加额外参数将使函数在失败后立即并无限期地重试。这在某些情况下是不好的。
其中一个原因是我们的函数调用可能因服务器过载而失败,这使得在重新尝试之前等待一段时间是合理的。为了指示等待时间,我们将使用指数退避的方法,通过参数wait=wait_random_exponential(min=min_value, max=max_value)。这将使等待时间随着函数失败的次数增加。
一个可选的操作是每次重试时记录消息。我们可以通过提供某个函数给参数before_sleep来实现这一点。这里我们将使用print函数,但更好的方法是使用logging模块,并将logging.error或logging.debug函数传递给该参数。
为了演示,我们将生成随机异常。
import random
class ProgressLog:
def __init__(self, total):
self.total = total
self.done = 0
def increment(self):
self.done = self.done + 1
def __repr__(self):
return f"Done runs {self.done}/{self.total}."
@retry(wait=wait_random_exponential(min=1, max=60), before_sleep=print)
async def get_completion(content, session, semaphore, progress_log):
async with semaphore:
#await asyncio.sleep(1)
if random.random() < 0.2:
raise Exception("Random exception")
async with session.post("https://api.openai.com/v1/chat/completions", headers=headers, json={
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": content}],
"temperature": 0
}) as resp:
response_json = await resp.json()
progress_log.increment()
print(progress_log)
return response_json["choices"][0]['message']["content"]
async def get_completion_list(content_list, max_parallel_calls):
semaphore = asyncio.Semaphore(value=max_parallel_calls)
progress_log = ProgressLog(len(content_list))
async with aiohttp.ClientSession() as session:
return await asyncio.gather(*[get_completion(content, session, semaphore, progress_log) for content in content_list])
start_time = time.perf_counter()
completion_list = await get_completion_list(["ping", "pong"]*5, 100)
print("Time elapsed: ", time.perf_counter() - start_time, "seconds.")
print(completion_list)
<RetryCallState 133364377433616: attempt #1; slept for 0.74; last result: failed (Exception Random exception)>
<RetryCallState 133364377424496: attempt #1; slept for 0.79; last result: failed (Exception Random exception)>
Done runs 1/10.
Done runs 2/10.
Done runs 3/10.
Done runs 4/10.
Done runs 5/10.
Done runs 6/10.
Done runs 7/10.
Done runs 8/10.
Done runs 9/10.
Done runs 10/10.
Time elapsed: 1.1305301820011664 seconds.
['Pong!', 'Ping!', 'Pong!', 'Ping!', 'Pong!', 'Ping!', 'Pong!', 'Ping!', 'Pong!', 'Ping!']
这将使我们的函数在重试之前等待一段时间。然而,失败的原因可能是系统性的,例如服务器停机或错误的有效负载。在这种情况下,我们希望将重试次数限制在一个范围内。我们可以通过参数stop=stop_after_attempt(n)来实现。
import random
class ProgressLog:
def __init__(self, total):
self.total = total
self.done = 0
def increment(self):
self.done = self.done + 1
def __repr__(self):
return f"Done runs {self.done}/{self.total}."
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(2), before_sleep=print)
async def get_completion(content, session, semaphore, progress_log):
async with semaphore:
#await asyncio.sleep(1)
if random.random() < 0.9:
raise Exception("Random exception")
async with session.post("https://api.openai.com/v1/chat/completions", headers=headers, json={
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": content}],
"temperature": 0
}) as resp:
response_json = await resp.json()
progress_log.increment()
print(progress_log)
return response_json["choices"][0]['message']["content"]
async def get_completion_list(content_list, max_parallel_calls):
semaphore = asyncio.Semaphore(value=max_parallel_calls)
progress_log = ProgressLog(len(content_list))
async with aiohttp.ClientSession() as session:
return await asyncio.gather(*[get_completion(content, session, semaphore, progress_log) for content in content_list])
start_time = time.perf_counter()
completion_list = await get_completion_list(["ping", "pong"]*5, 100)
print("Time elapsed: ", time.perf_counter() - start_time, "seconds.")
print(completion_list)
<RetryCallState 133364608660048: attempt #1; slept for 0.1; last result: failed (Exception Random exception)>
<RetryCallState 133364377435680: attempt #1; slept for 0.71; last result: failed (Exception Random exception)>
<RetryCallState 133364377421472: attempt #1; slept for 0.17; last result: failed (Exception Random exception)>
<RetryCallState 133364377424256: attempt #1; slept for 0.37; last result: failed (Exception Random exception)>
<RetryCallState 133364377430928: attempt #1; slept for 0.87; last result: failed (Exception Random exception)>
<RetryCallState 133364377420752: attempt #1; slept for 0.42; last result: failed (Exception Random exception)>
<RetryCallState 133364377422576: attempt #1; slept for 0.47; last result: failed (Exception Random exception)>
<RetryCallState 133364377431312: attempt #1; slept for 0.11; last result: failed (Exception Random exception)>
<RetryCallState 133364377425840: attempt #1; slept for 0.69; last result: failed (Exception Random exception)>
<RetryCallState 133364377424592: attempt #1; slept for 0.89; last result: failed (Exception Random exception)>
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
/usr/local/lib/python3.10/dist-packages/tenacity/_asyncio.py in __call__(self, fn, *args, **kwargs)
49 try:
---> 50 result = await fn(*args, **kwargs)
51 except BaseException: # noqa: B902
5 frames
Exception: Random exception
The above exception was the direct cause of the following exception:
RetryError Traceback (most recent call last)
/usr/local/lib/python3.10/dist-packages/tenacity/__init__.py in iter(self, retry_state)
324 if self.reraise:
325 raise retry_exc.reraise()
--> 326 raise retry_exc from fut.exception()
327
328 if self.wait:
RetryError: RetryError[<Future at 0x794b5057a590 state=finished raised Exception>]
设置此参数后,一旦尝试次数达到最大值,将引发RetryError。然而,可能有时我们希望继续运行而不生成异常,只是将None值保存到调用返回中以便稍后处理。为此,我们可以使用回调函数retry_error_callback在发生RetryError错误时仅返回None值:
import random
class ProgressLog:
def __init__(self, total):
self.total = total
self.done = 0
def increment(self):
self.done = self.done + 1
def __repr__(self):
return f"Done runs {self.done}/{self.total}."
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(2), before_sleep=print, retry_error_callback=lambda _: None)
async def get_completion(content, session, semaphore, progress_log):
async with semaphore:
#await asyncio.sleep(1)
if random.random() < 0.7:
raise Exception("Random exception")
async with session.post("https://api.openai.com/v1/chat/completions", headers=headers, json={
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": content}],
"temperature": 0
}) as resp:
response_json = await resp.json()
progress_log.increment()
print(progress_log)
return response_json["choices"][0]['message']["content"]
async def get_completion_list(content_list, max_parallel_calls):
semaphore = asyncio.Semaphore(value=max_parallel_calls)
progress_log = ProgressLog(len(content_list))
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(1)) as session:
return await asyncio.gather(*[get_completion(content, session, semaphore, progress_log) for content in content_list])
start_time = time.perf_counter()
completion_list = await get_completion_list(["ping", "pong"]*5, 100)
print("Time elapsed: ", time.perf_counter() - start_time, "seconds.")
print(completion_list)
<RetryCallState 133364377805024: attempt #1; slept for 0.22; last result: failed (Exception Random exception)>
<RetryCallState 133364377799456: attempt #1; slept for 0.53; last result: failed (Exception Random exception)>
<RetryCallState 133364377801328: attempt #1; slept for 0.24; last result: failed (Exception Random exception)>
<RetryCallState 133364377810208: attempt #1; slept for 0.38; last result: failed (Exception Random exception)>
<RetryCallState 133364377801616: attempt #1; slept for 0.54; last result: failed (Exception Random exception)>
<RetryCallState 133364377422096: attempt #1; slept for 0.59; last result: failed (Exception Random exception)>
<RetryCallState 133364377430592: attempt #1; slept for 0.07; last result: failed (Exception Random exception)>
<RetryCallState 133364377425648: attempt #1; slept for 0.05; last result: failed (Exception Random exception)>
Done runs 1/10.
Done runs 2/10.
Done runs 3/10.
Time elapsed: 2.6409040250000544 seconds.
['Pong!', 'Ping!', None, None, None, None, None, 'Ping!', None, None]
这样,None值将被返回,而不是生成错误。
另一个尚未处理的问题是连接挂起问题。当我们执行请求时,由于某种原因,主机保持连接但既没有失败也没有返回任何内容,这种情况会发生。为了处理这种情况,我们需要设置超时,以便在调用在指定时间内未返回值时返回。为此,我们可以使用aiohttp库中的timeout参数,以及aiohttp.ClientTimeout类。如果发生超时,将引发TimeoutError,然后由tenacity的retry装饰器处理,并自动再次运行函数。
class ProgressLog:
def __init__(self, total):
self.total = total
self.done = 0
def increment(self):
self.done = self.done + 1
def __repr__(self):
return f"Done runs {self.done}/{self.total}."
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(20), before_sleep=print, retry_error_callback=lambda _: None)
async def get_completion(content, session, semaphore, progress_log):
async with semaphore:
async with session.post("https://api.openai.com/v1/chat/completions", headers=headers, json={
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": content}],
"temperature": 0
}) as resp:
response_json = await resp.json()
progress_log.increment()
print(progress_log)
return response_json["choices"][0]['message']["content"]
async def get_completion_list(content_list, max_parallel_calls):
semaphore = asyncio.Semaphore(value=max_parallel_calls)
progress_log = ProgressLog(len(content_list))
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(10)) as session:
return await asyncio.gather(*[get_completion(content, session, semaphore, progress_log) for content in content_list])
start_time = time.perf_counter()
completion_list = await get_completion_list(["ping", "pong"]*100, 100)
print("Time elapsed: ", time.perf_counter() - start_time, "seconds.")
<RetryCallState 133364375201936: attempt #1; slept for 0.57; last result: failed (TimeoutError )>
Time elapsed: 12.705538211999738 seconds.
太棒了!现在我们有了一种强大的方法来运行多个并行请求,发生失败时会自动重试,并在失败是系统性时返回 None 值。所以最终的代码如下:
import asyncio
import aiohttp
from tenacity import (
retry,
stop_after_attempt,
wait_random_exponential,
)
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {OPENAI_API_KEY}"
}
class ProgressLog:
def __init__(self, total):
self.total = total
self.done = 0
def increment(self):
self.done = self.done + 1
def __repr__(self):
return f"Done runs {self.done}/{self.total}."
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(20), before_sleep=print, retry_error_callback=lambda _: None)
async def get_completion(content, session, semaphore, progress_log):
async with semaphore:
async with session.post("https://api.openai.com/v1/chat/completions", headers=headers, json={
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": content}],
"temperature": 0
}) as resp:
response_json = await resp.json()
progress_log.increment()
print(progress_log)
return response_json["choices"][0]['message']["content"]
async def get_completion_list(content_list, max_parallel_calls, timeout):
semaphore = asyncio.Semaphore(value=max_parallel_calls)
progress_log = ProgressLog(len(content_list))
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(timeout)) as session:
return await asyncio.gather(*[get_completion(content, session, semaphore, progress_log) for content in content_list])
总结来说,我们实现了以下功能:
-
异步调用以减少等待时间。
-
记录异步调用的进度。
-
当调用失败时,自动触发重试。
-
如果失败是系统性的,则返回 None 值。
-
当调用超时且没有返回任何内容时,重试调用。
如果你有任何问题、发现错误或有改进的建议,请在下面留下评论!
数据科学的真正力量隐藏在明面上
原文:
towardsdatascience.com/the-real-power-of-data-science-is-hiding-in-plain-sight-700079f20d53
意见
如果数据科学家专注于方法而非影响力,他们可能会掩盖自己能够为组织带来的真正价值
·发布于 Towards Data Science ·5 min read·2023 年 1 月 18 日
--

数据科学的真正力量通常隐藏在不必要的复杂性背后。 来源:作者
数据科学行业内部的人与其他人之间正在形成障碍。
虽然数据科学的入门门槛众所周知;只有 2.4%的从业者拥有高等教育后的正式教育,超过一半拥有硕士或博士学位[1],但另一个障碍正在形成…
现在存在一个理解的障碍。
理解数据科学是什么、它能提供什么以及如何应用变得越来越困难。尽管这一直是一个复杂的行业,但最*的进展使其对外界变得晦涩难懂。
这个障碍随着时间的推移而增长,可能会阻碍人工智能应用的进展。*年来机器学习和人工智能的巨大进展如果无法跨行业、企业和组织应用,那也毫无意义。
本文探讨了这种差距是如何出现的,以及可以采取哪些措施来解决这一问题。
我们是如何来到这个位置的?
理解障碍的形成是由于三个并行发展的趋势:
1. 算法的复杂性正在指数级增长。
随着数据科学的发展,其基础算法也在不断成熟。为不同的应用场景创建了专门的模型,从图像识别到自然语言理解以及介于两者之间的各种架构都在被使用。
除了各种算法的爆炸性增长外,模型的规模和复杂性也随着时间的推移呈指数级增长,以实现更高的性能水*。
下图就是一个很好的例子。变压器模型,2018 年的全新架构,在短短几年内从包含几亿个参数的模型发展到超过 5000 亿个参数:

机器学习模型的复杂性在过去四年中呈指数级增长。来源:作者
这种复杂性导致了行业从业者在不断研究和理解新架构时付出了巨大的“开销”。
这也导致了从个人转向大型公司进行新模型开发的转变。
训练一个 5000 亿参数的模型所需的资源远远超过个人甚至小型组织的能力。只有大型国际公司才能在这种规模上运营。
2. 新思想的产生和分享速度在加快。
不足为奇的是,数据科学在研究领域是一个增长领域。然而,新思想在行业内的创造和分享速度持续加快。
下图显示了提交并被接受到领先的机器学习和神经科学会议 NeurIPS 的论文数量。
仅在过去五年中,就出现了三倍的增长。

在过去五年中,提交给 NeurIPS 的论文数量增加了三倍。来源:作者
此外,论文发表与提交会议之间的时间框架显著缩短[2]。创新的速度现在如此之快,以至于未提交的论文有可能在下一届年会上变得过时。
新论文的发布量、种类和速度现在远远超出了任何个人能够跟上的程度。需要做出选择,要么专注于特定领域,要么接受对行业的更广泛视角也是浅显的。
对于行业外的人来说,这使得理解当前发展状态以及这些状态在实际应用中的含义几乎是不可能的。
3. 随着数据科学家社区的成熟,它创造了进一步的理解障碍。
尽管上述前两点是新兴和快速增长行业的副作用,但第三个阻碍理解的领域完全是自我造成的。
数据科学社区充斥着术语和技术词汇,关注工具、方法和技术而非结果。

2022 年的谷歌搜索量始终远超用例搜索。来源:作者,数据来自谷歌趋势
这可能导致一种心态,即为不存在的问题创造解决方案;大量精力被耗费在攀登排行榜上,而不是将思想应用于现实世界。
行业语言也变得越来越技术化。这在更成熟的职业领域如法律和金融中反复出现;它有助于创建身份感和归属感。它还可以帮助区分在某一领域中有知识的人和没有知识的人。
然而,技术语言也是排他性和令人困惑的。对于一个建立在思想共享基础上的社区来说,看到人为障碍被建立起来是令人遗憾的;现有的障碍已经足够多了。
如何解决理解障碍的问题?
答案很简单:
-
简单性应被看重于复杂性。
-
应将重点放在实际成果上,而不是理论概念。
-
教育和参与行业之外的利益相关者应被视为任何数据科学角色中必不可少的一部分。
从根本上讲,需要将数据科学从作为独立实体的观点中转变过来。它应被视为企业和组织日常运营的一部分。
这应从根本上将重点从技术复杂性转移到实际影响和成果上。
参考文献
研究代理:应对基于大量文本语料库回答问题的挑战
我制作了一个自主 AI 研究代理,可以通过深度多跳推理能力回答困难问题
·
关注 发表在 Towards Data Science · 16 min 阅读 · 2023 年 8 月 29 日
--
作者提供的图片(使用 Photoshop 生成填充)
问题介绍
2021 年,我开始研究基于大量文本语料库回答问题的挑战。在预训练变换器普及之前,这个问题非常难以解决。
令我沮丧的是,我开始用《摩诃婆罗多》这一最复杂、最精细的故事之一进行实验。对于那些不熟悉这部作品的人来说,《摩诃婆罗多》是一部由 18 本书组成的总字数约为 180 万的作品。它是有史以来最长的诗歌,大约有 90,000 节。它的长度大约是《伊利亚特》和《奥德赛》总和的十倍。但《摩诃婆罗多》的震撼不仅在于长度,还有其广度。它在因果关系上高度非线性和复杂,拥有跨越七代的数千个角色,而这些角色中没有一个是完全善良或邪恶的。它对职责(业)、选择和人类存在,尤其是职责冲突和多重错误选择的冲突进行了深刻的哲学评论。《博伽梵歌》(印度教的核心哲学)也是《摩诃婆罗多》第六本书的一部分。
我将来自多个在线来源的《摩诃婆罗多》文本数据整理成了一个干净的数据集。然而,我找不到一种方法来对这些文本实施有意义的问答。
不到两年的时间,一切都发生了变化。
AI 和大型预训练变换器的快速进步正在深刻而根本地改变技术世界。我对这一点感到着迷,就像现在大多数技术人员一样。
几个月前,我带着对新兴的提示工程艺术的初步了解重新审视了这个问题。但这次我有了一个大致的想法,即创建一个可以处理任何复杂知识库的自主 研究代理。
《摩诃婆罗多》是最复杂的用例之一。然而,在每个知识领域,如法律、科学研究、教育、医疗等,每个项目都从对前人工作的深入研究开始。因此,这个问题是值得解决的。
研究代理
在这里,我将讨论一个可以解决多跳 KBQA 问题并具备深度推理能力的自主 AI 研究代理的设计和实施。我会分享一个包含研究代理初步实现的 Python 笔记本的 git 仓库。如果你对这部分内容感兴趣,请随时跳到本文后面的实施部分。
如果你对了解更多关于 AI 代理、‘基于知识的问题回答’(KBQA)、‘为什么’、‘什么’以及 AI 研究代理的设计演变感兴趣,请继续阅读。
为什么?
可能有人会问,为什么不直接使用 ChatGPT 接口提问。它已经在 2021 年之前生成的大量互联网数据上进行了训练,因此像《摩诃婆罗多》这样的文本语料库对它来说是已知的。
这是我的第一种方法。我问 ChatGPT 关于《摩诃婆罗多》的几个问题。我得到了对一些问题的良好回答。然而,大部分答案缺乏严谨性。这是可以预料的。GPT 是在通用数据集上训练的。它可以很好地理解和解释自然语言。它的推理能力也足够好。然而,它在任何特定领域都不是专家。所以,尽管它可能对《摩诃婆罗多》有一些了解,但可能不会给出深入研究的答案。有时 GPT 甚至可能没有任何答案。在这些情况下,它要么谦虚地拒绝回答问题,要么自信地编造答案(幻觉)。
实现 KBQA 的第二种最明显的方法是使用检索问答提示。这时,LangChain 显得极为有用。
检索问答
对于那些不熟悉 LangChain 库的人来说,它是将 LLMs(如 GPT)用于代码中的最佳方式之一。这里是使用 LangChain 实现 KBQA 的方法。
这个示例展示了在索引上进行问答。
总结一下,实现任何文档集合上的 KBQA 的步骤如下
-
将知识库拆分成文本块。
-
为每个文本块创建数值表示(嵌入)并将其保存到向量数据库中。
如果你的数据是静态的,第 1 步和第 2 步是一次性的工作。
-
使用用户的查询在这个数据库上进行语义搜索,并获取相关的文本块。
-
将这些文本块与用户的问题一起发送到 LLM,并要求其回答。
这是该过程的图示表示。

作者通过 draw.io 创建的图片
那么,为什么要继续呢?这似乎是一个已解决的问题!
不太对 🙁
这种方法适用于简单问题和简单的事实知识库。然而,对于需要更深层次、多跳推理的复杂知识库和更复杂的问题则不适用。多跳推理指的是通过多个逻辑或上下文推理步骤来得出结论或回答问题的过程。
此外,LLMs 在一次提示中处理文本的长度是有限的。你可以当然逐个发送文档,然后通过每次调用来‘细化’或‘缩减’答案。然而,这种方法不允许进行复杂的‘多跳’推理。在某些情况下,使用‘细化’或‘缩减’方法得到的结果比仅仅将所有文档塞进一个提示中要好,但差距不大。
对于复杂的知识库,用户的问题本身可能不足以找到所有相关文档,从而帮助 LLM 得出准确的答案。
例如:
阿周那是谁?
这是一个简单的问题,可以在有限的背景下回答。然而,以下问题:
为什么《摩诃婆罗多》战争发生了?
这是一个在整个文本语料库中分布的背景问题。问题本身对其背景信息了解有限。找到相关的文本片段并在此基础上进行推理可能效果不好。
那接下来呢?
AI 代理
这是 AI 出现后出现的最酷的概念之一。如果你不知道 AI 代理的概念,我迫不及待想向你解释,但我可能还是无法传达它的惊人之处。让我先用 ChatGPT 来解释一下。
AI 代理,也称为“代理”,是指能够自主感知其环境、做出决策并采取行动以实现特定目标的软件程序或系统。AI 代理旨在模拟类似人类的行为,用于问题解决和决策任务。它们在定义的环境中操作,并与该环境互动,以实现期望的结果。
简单来说,代理是一个接受问题、决定如何解决它并然后解决它的程序。代理会提供一组工具,如函数、方法、API 调用等。它可以按任何顺序选择使用这些工具。与传统软件不同,传统软件中解决问题所需的步骤顺序是预先编程的。当然,这是一个非常模糊的定义。但你现在应该能理解了。
这是我为我们的 KBQA 用例尝试的两种不同的代理。
ReAct 该代理使用 ‘ReAct’(推理与行动)风格的推理 来决定针对给定问题使用哪个工具。
这是 ReAct 代理的 langChain 实现:
本文档展示了如何使用代理来实现 ReAct 逻辑。
python.langchain.com](https://python.langchain.com/docs/modules/agents/agent_types/react?source=post_page-----4ef8e6f1b741--------------------------------)
我为代理提供了以下工具供其选择:
-
带有文档存储的检索 QA 链。
-
字符表词汇表搜索(我使用预训练模型创建了一个带有命名实体识别的词汇表)
-
维基百科搜索。
ReAct 代理并没有给我带来好的结果,并且大多数时候无法收敛到任何答案。它与 GPT 3.5 的兼容性不好。它可能在 GPT 4 上表现更好,而 GPT 4 比 GPT 3.5 贵 20 - 30 倍,因此这可能还不是一个选择。
即使它收敛了,我也无法获得好的结果。可能在创建“反应”提示方面更有经验的人会做得更好。
自我提问代理 这个代理根据原始问题提出后续问题,然后尝试找到中间答案。通过这些中间答案,它最终得出一个最终答案。这里是解释自我提问代理的文章
自我提问提示是一种从链式思维提示演变而来的方法。下面是一些实际的例子和…
这种方法给了我一些不错的结果。它在单跳推理的情况下效果很好,但即使这样,对于需要多跳的问提也会失败。
例如,问题:
谁杀了卡尔纳,为什么?
用这种方法相对容易回答。
问题
为什么阿周那杀了他的半兄卡尔纳?
要回答这个问题要困难得多。它要求 LLM 知道阿周那不知道卡尔纳是他的半兄弟的事实。LLM 无法知道它需要知道这个事实,无论是通过理解问题还是通过基于原始问题提出进一步的问题。
人类研究过程
再次引用 GPT
AI 代理被设计成在解决问题和决策任务中模仿类似人类的行为。
所以,我的下一个想法是研究人类如何研究,也就是元研究。我想象自己坐在图书馆里(大学的怀旧情怀),能够轻松获取所有与我的研究主题相关的书籍。我拿出一个笔记本和一支笔,开始记录我在研究一个主题时遵循的过程。
这是我得到的结果。
研究方法论:
在一页上记下原始查询。
-
我尝试通过阅读几本书来回答当前的问题。在过程中,我做了些笔记,并标记了一些我认为与当前问题最相关的摘录。
-
我在这些摘录中总能发现许多未知之处。我记录下这些未知,并写下更多问题以帮助我了解这些未知。
-
从这些问题中,我选择一个与原始问题最相关的问题。
-
我回到第一步
经过几次这样的迭代,我问自己是否有足够的信息来回答原始问题。
如果是,那么干得好!
如果不是,那就继续努力吧。
看啊!
最终,我知道该编写什么代码。我希望通过一些提示工程,这个过程能给我比之前尝试的其他方法更深刻的答案。
剧透警告……确实如此!😃
在开始编码之前,我在互联网上搜索了类似的想法。我发现了 BabyAGI。真是一个奇妙的世界!
这是描述 BabyAGI 的一个仓库
我意识到 BabyAGI 与上述研究过程之间有许多相似之处。因此,我怀着感激之情,从 BabyAGI 实现中使用的提示中获得了一些灵感。
研究代理— 实现
这里是使用惊人的 draw.io 将相同的过程转换为流程图

作者提供的图像(使用 draw.io 生成)
图表中的每个蓝色框都是对 LLM 的调用。
组件
-
QA 代理— 搜索答案和进一步的背景信息
这是一个简单的‘stuff’ 检索 QA 链,它使用了一个向量存储。在未来,这可以是一个使用向量存储、搜索 API 或维基百科 API、内容审核 API 和以往研究数据等工具的 AI 代理。这里的提示经过调整,以根据 1. 上下文(文档)和 2. 与原始问题的相关性来生成简洁的答案。
除了第一轮外,当前问题 总是步骤 2 中生成的中间问题,并在步骤 3 中选择。代理将中间答案附加到笔记中,并将最新的摘录(用于回答当前问题的文档)附加到书签中。最新的这些文档将在步骤 2 中使用。
-
问题生成器— 根据新的笔记提出更多问题
在这里,代理使用与当前问题匹配的最新向量搜索结果,并利用这些结果生成与原始问题相关的更多问题。它将这些问题附加到未回答的问题列表中。这里的提示经过调整,以确保新生成的问题不会与现有的问题列表重叠。
-
最相关问题挑选器— 挑选出与原始问题最相关的一个问题
这个提示从未回答的问题列表中挑选出一个与原始问题最相关的问题。这个问题将作为下一轮的当前问题。
在下一轮中,代理会在生成一组新的问题后,从未回答的问题列表中删除这个问题。
-
分析器— 我是否知道足够多?
我使用了一个max_iterations 参数来退出循环。现在这效果不错。然而,根据不断变化的上下文动态决定迭代次数或退出策略可能会更好。我将来会致力于开发一个可以自主完成这项任务的‘分析器’。
-
研究编译器— 编译研究
这是最终的提示。它利用研究过程中记录的笔记,得出对
原始问题的详细最终答案。
结果
研究代理在我尝试过的所有方法中是一个重大改进。它提供的答案比任何其他方法都更详细、准确。我玩了几个星期这个方法,对得到的答案的丰富性感到惊讶。
该代理在避免幻觉问题方面比任何以前的方法都有更大的进展。它会在初期的几次迭代中自动纠正产生的幻觉和事实错误。它在问题的深入处理过程中,结果会更为准确。
这是一个示例运行。
问题:为什么潘达瓦斯必须在森林中生活 12 年?
输出 —

如果你感兴趣,这是最终答案
Final Answer:
The Pandavas' decision to live in the forest for 12 years
was primarily due to the circumstances and events that
unfolded in their lives. After losing a game of dice to
The Kauravas and the Pandavas were forced into exile for
13 years, with the last year to be spent incognito.
During their exile, they faced numerous challenges
and hardships, including living in the forest.
Bhima contemplated killing all of
the Kurus, but his brother Yudhisthira always
pacified him, stating that it was not the opportune
time to fulfil their desires.
Thus, living in the forest allowed them to avoid
confrontations and maintain peace.
During their time in the forest, the Pandavas faced several difficulties.
One of the main challenges was Bhima's struggle to tolerate the ascetic
life. He contemplated killing all of the Kurus, but his brother
Yudhisthira always pacified him and advised against it.
Additionally, the Pandavas had to live in the forest and depend
on begging for their sustenance since their kingdom had been
plundered in gambling. They were distressed by the loss of their
kingdom and the insults to their queen Draupadi. The forest itself
was also full of dangers, adding to their hardships. Furthermore,
the Pandavas felt unable to fulfil the needs of certain pious Brahmanas
who stayed with them in the forest, as they did not possess anything
and were dependent on begging for their own survival.
Despite these challenges, the Pandavas made the most of their time
in the forest. They received knowledge from sages and saintly persons,
which helped them gain wisdom and spiritual insights. The great sage
Vyasa informed them that many generals had agreed to support their
cousin Duryodhana, including Karna, Shakuni, Bhurishravas, Sala,
Drona, and Bhishma. Vyasa also told Arjuna that if he were to fight
in a future war, he would need to obtain the divine astras of Lord
Indra and Lord Shiva.
In light of this information, Yudhisthira ordered Arjuna to go to the
Himalayan mountains to perform austerities to please Lord Shiva.
This was necessary for Arjuna to obtain the divine weapons that would
be crucial in the upcoming war. The Pandavas understood the importance
of being prepared and gathering the necessary resources to counter
the strength of their opponents. Therefore, Yudhisthira made the
decision to send Arjuna on this mission to seek Lord Shiva's blessings
and acquire the divine astras.
Overall, the Pandavas' exile in the forest for twelve years was a
result of the conditions imposed on them after losing the game
of dice. They faced various difficulties during their time in the
forest, but also gained valuable knowledge and prepared themselves
for the challenges that lay ahead.
这个答案相当详细。但代理的美妙之处不仅在于它准确回答了原始问题,还在于它进一步探索了与问题相关的故事。
在大多数情况下,我得到的答案充满了细节。每个这样的答案都激发了我进一步探究的好奇心。
该代理还会生成一组已回答的问题和未回答的问题,并在研究过程中记录下来。因此,每次运行后,它都会引导我提出更多的问题。在过去的几周里,我对《摩诃婆罗多》的了解比以前的多年都要多。
** Unanswered Questions **
'4\. How did the Pandavas receive knowledge from sages and saintly persons during their time in the forest?'
'5\. What were the specific austerities that Arjuna had to perform in the Himalayan mountains to please Lord Shiva?'
'6\. How did the Pandavas manage to hide from Duryodhana's spies for almost the entire thirteenth year of their exile?'
'8\. How did Bhima cope with the challenges of living as an ascetic in the forest? Did he face any particular difficulties or struggles during their time in exile?'
'9\. Can you provide more information about the generals who supported Duryodhana's cause? What were their roles and contributions in the Kurukshetra war?'
'10\. How did the Pandavas manage to maintain a peaceful life in the forest despite the challenges they faced?'
'11\. What were the specific teachings and knowledge that the Pandavas received from the sages and saintly persons during their time in the forest?'
'12\. Can you provide more information about the palace where the Pandavas lived for one full year before going to the forest?'
'13\. How did Lord Krishna's presence in the forest affect the Pandavas' experience during their exile?'
'14\. Can you provide more information about the dangers the Pandavas faced while living in the forest?'
'15\. What were the specific challenges and difficulties that Yudhisthira and his brothers faced in their daily lives as inhabitants of the forest?'
想象一下将相同的过程应用于其他知识领域,真是令人兴奋的想法!
代码
这里是实现研究代理的 Python 笔记本。
[## GitHub - rahulnyk/research_agent
通过在 GitHub 上创建一个帐户,来为 rahulnyk/research_agent 的开发做出贡献。
github.com](https://github.com/rahulnyk/research_agent?source=post_page-----4ef8e6f1b741--------------------------------)
《摩诃婆罗多》数据集的 Git 仓库
[## GitHub - rahulnyk/mahabharata: 从多个来源编纂的《摩诃婆罗多》文本,分割成若干部分…
《摩诃婆罗多》文本是从多个来源编纂的,分割成若干部分,并解析成带有元数据的 CSV 文件。命名实体…
github.com](https://github.com/rahulnyk/mahabharata?source=post_page-----4ef8e6f1b741--------------------------------)
接下来做什么?
当前实现是自主 AI 研究代理概念的一个简单版本。在代理的实现过程中,我对研究过程进行了几次修改。这是一次令人兴奋的旅程,但乐趣还未结束。以下是我目前正在进行的一些改进。
-
在公共链接上部署这个代理,并观察更多的使用模式。
-
用不同于《摩诃婆罗多》的源文档来使用该代理。
-
过程的第 1 步目前是一个简单的‘stuff’ QA 链,它使用了一个包含源文本语料库的向量存储。我正在努力将其替换为‘ReAct’代理,以便在研究过程中可以使用其他工具,如搜索 API、维基百科、审核 API 等。
-
- 我将每次运行生成的数据和元数据保存到一个
runs向量存储中。我还将原始问题的嵌入保存到同一存储中。这帮助我跟踪代理的推理过程,并观察到其中出现的几个逻辑模式。这有助于调整 QA 代理,以跟随更紧密的推理路径。
- 我将每次运行生成的数据和元数据保存到一个
-
- 目前,研究代理在经过固定的迭代次数后存在。这对大多数问题来说效果很好。然而,根据不断变化的背景,动态决定迭代次数或退出策略可能更好。我会开发一个能够自主完成这一任务的“分析器”。
-
- 这个代理对大多数类型的问题效果很好,但对元问题除外。例如,如果我问“描述第五卷第三章中发生了什么”,代理会很难回答。在未来的版本中,我将加入一个带有‘ReAct’代理的自查询检索器,以处理此类情况。
-
- 到目前为止,我只尝试了 OpenAI GPT3.5 模型的代理。每次运行的费用约为 $0.02。我很快会尝试使用像 Llama 这样可以本地托管的较小模型。
-
在下一篇文章中,我计划写下在实施这些更新后的发现。更大的构想是创建一个顶尖的自主人工智能研究代理,能够在寻找深度研究的问题的答案方面表现出色。所以,请随时提出建议,如果可能,和我合作进一步完善。
-
希望你觉得这篇文章和分享的代码对你有帮助。
-
谢谢阅读。
-
希望你觉得这个人工智能研究代理既令人兴奋又有用。
-
我分享的笔记本只是创建一个自主人工智能研究代理这一更大构想的简单实现。为了使这个代理成为顶尖研究者,还有很多工作要做。
-
所以,请随时提出建议,如果可能,和我合作进一步完善。
-
谢谢阅读。
-
上述文章中使用的数据集的致谢以及许可证信息。
-
K. M. Ganguli 的完整翻译:可在公共领域获得。
-
- Laura Gibbs Tiny Tales:这是对《摩诃婆罗多》的重述,使用了两百个每个 100 字长的故事。我在这里使用她的作品是经过她的许可。
-
- Tilak 的 Kaggle 数据库:18 部《摩诃婆罗多》的文本格式数据用于 NLP。由Tilak以公共领域许可证共享。
失落的回归:用于预测的变换器

图片由 Aditya Vyas 提供,发布在 Unsplash
介绍一种新的变换器模型:PatchTST
·
关注 发表在 Towards Data Science · 5 min read · 2023 年 5 月 24 日
--
最*,基于变换器的方法被广泛采用。像 BERT 和 ChatGPT 这样的模型取得的显著成就鼓励了研究人员探索这种架构在各种领域的应用,包括时间序列预测。然而,香港中文大学和国际数字经济活动的研究人员最*的工作表明,为这一任务开发的实现效果不佳,并且在各种基准测试中可能被简单的线性模型击败[1]。
为此,普林斯顿大学和 IBM 的研究人员在他们的论文A Time Series is Worth 64 Words [2]中提出了 PatchTST(修补时间序列变换器)。在这篇论文中,Nie 等人介绍了两种关键机制,将变换器带回预测领域:
-
修补注意力:他们的注意力机制将时间序列的大部分作为标记进行处理,而不是逐点注意力
-
渠道独立性:时间序列中的不同目标序列被独立处理,使用不同的注意力权重。
在这篇文章中,我旨在总结这两种机制的工作原理,并讨论 Nie 等人[2]发现的结果的意义。
背景:变换器是否有效
在深入探讨 PatchTST 之前,我们需要首先了解 Zeng 等人发现的自注意力在预测领域中的问题。对于那些对详细总结感兴趣的人,我强烈建议阅读原始论文或我对他们工作的总结:
最*,长期时间序列预测(LTSF)任务中出现了许多基于变换器的解决方案……
arxiv.org](https://arxiv.org/abs/2205.13504?source=post_page-----24f6fec5bc30--------------------------------) ## 变换器是否输给了线性模型?
使用变换器进行长期预测可能不是最佳选择
towardsdatascience.com
总而言之,自注意力在应用于预测领域时存在一些关键问题。具体而言,以前的时间序列变换器使用逐点自注意力机制,其中每个时间戳被视为一个标记。然而,这存在两个主要问题。首先,这导致注意力机制对点的排列不变,如果你将点的位置颠倒,则观察到的注意力值相同。此外,单个时间戳本身包含的信息有限,其重要性来自于周围的时间戳。与语言处理中的情况类似,如果我们关注单个字符而不是单词。
这些问题在测试预测变换器时导致了一些有趣的结果:
-
变换器似乎非常容易对随机模式过拟合,因为向数据中添加噪声并没有显著降低变换器的性能。
-
更长的回溯期并没有帮助提高准确性,表明变换器未能捕捉到显著的时间模式。
这就是 PatchTST
为了应对变换器在这一领域的问题,Nie 等 [2] 引入了两个主要机制,使 PatchTST 区别于以往模型:通道独立性和分块。
在以往的工作中,所有目标时间序列会被连接到一个矩阵中,其中每一行是一个单独的序列,列是输入标记(每个时间戳一个)。这些输入标记随后会被投射到嵌入空间,这些嵌入会传递到一个单一的注意力层。
PatchTST 选择了通道独立性,每个系列独立传入变换器骨干网(图 1.b)。这意味着每个系列都有自己的一组注意力权重,使得模型可以更好地专注。该方法通常用于卷积网络,并且已被证明能显著提高网络的准确性 [2]。通道独立性还支持第二种机制:分块。

图 1:PatchTST 模型概览(图源自 Nie 等 2023 [2])
如前所述,关注单个时间步就像关注单个字符。你最终会失去顺序感(“dog”和“god”在字符级注意力下会有相同的注意力值),并且还会增加模型的内存使用。那么解决方案是什么?显然我们需要关注单词!
更准确地说,作者提议将每个输入时间序列拆分成固定长度的块 [2]。这些块随后作为输入标记传入主模型(块的长度即为标记大小) [2]。模型接着为每个块添加位置编码,并通过普通变换器 [3] 编码器处理。
这种方法自然允许 PatchTST 考虑到点级标记丢失的局部语义信息。此外,时间序列的分块显著减少了所需的输入标记数量,使得模型能够捕捉来自更长序列的信息,并显著减少了训练和预测所需的内存 [2]。此外,分块机制还使得对时间序列进行表示学习成为可能,使 PatchTST 成为一个更灵活的模型 [2]。
预测实验结果

图 2:基准上的 MSE 和 MAE 结果。第一名加粗,第二名带下划线。(图源自 Nie 等 2023)
在他们的论文中,作者测试了 PatchTST 的两种不同变体:一种是输入模型的 64 个 patch(因此得名),另一种是 42 个 patch。42 个 patch 的变体与其他模型具有相同的回溯窗口,因此可以视为对其他模型的公*比较。对于这两种变体,使用了长度为 16 的 patch 和步幅为 8 来构建输入令牌 [2]。如图 2 所示,PatchTST 变体在结果中占据主导地位,DLinear [1] 在极少数情况下获胜。*均而言,PatchTST/64 实现了 MSE 下降 21% 和 MAE 下降 16.7%。PatchTST/42 实现了 MSE 下降 20.2% 和 MAE 下降 16.4%。
结论
PatchTST 代表了 Transformer 架构在时间序列预测任务中的一个有前景的未来,特别是由于 patching 是一个简单有效的操作符,可以在未来模型中轻松实现。此外,PatchTST 的准确性成功表明,时间序列预测确实是一个复杂的任务,可能从捕捉复杂的非线性交互中获益。
资源和参考文献
-
PatchTST Github 仓库:
github.com/yuqinie98/PatchTST -
PatchTST 的实现可以在 NeuralForecast 中找到:
nixtla.github.io/neuralforecast/ -
如果你对非 Transformer 的神经预测架构感兴趣,可以考虑阅读我之前的文章《神经基础分析网络》:
towardsdatascience.com/xai-for-forecasting-basis-expansion-17a16655b6e4
如果你对预测、深度学习和可解释人工智能感兴趣,可以通过关注我来支持我的写作!
参考文献
[1] A. Zeng, M. Chen, L. Zhang, Q. Xu. Transformers 对时间序列预测是否有效?(2022)。第三十七届 AAAI 人工智能会议。
[2] Y. Nie, N. H. Nguyen, P. Sinthong 和 J. Kalagnanam. 《一个时间序列值 64 个单词:使用 Transformers 的长期预测》(2023)。国际学习表征会议,2023。
[3] A. Vaswani, N. Shazeer, N. Parmar, J. Uszkoreit, L. Jones, A. N. Gomez, L. Kaiser, I. Polosukhin. 注意力即你所需(2017)。第 31 届神经信息处理系统会议。
推荐系统中的两塔模型崛起
原文:
towardsdatascience.com/the-rise-of-two-tower-models-in-recommender-systems-be6217494831
深入探讨用于消除排序模型偏差的最新技术
·发表于 Towards Data Science ·7 分钟阅读·2023 年 10 月 29 日
--

照片由 Evgeny Smirnov 提供
推荐系统是当今世界上最普遍的机器学习应用之一。然而,底层的排序模型存在 许多偏差,这可能严重限制推荐的质量。构建无偏排序器的问题——也称为无偏排序学习(ULTR)——仍然是机器学习领域最重要的研究问题之一,且远未解决。
在这篇文章中,我们将深入探讨一种相对较新的建模方法,这种方法已经使行业能够非常有效地控制偏差,从而构建出极其优越的推荐系统:两塔模型,其中一个塔学习相关性,另一个(浅层)塔学习偏差。
虽然两塔模型可能在行业中使用了好几年,但第一篇正式向更广泛的机器学习社区介绍它们的论文是华为 2019 年的 PAL 论文。
PAL(华为,2019)——首个两塔模型
华为的论文 PAL (“位置感知排序学习”) 考虑了在华为应用商店背景下的位置偏差问题。
在整个行业的排序模型中,一再观察到位置偏差。这意味着用户更有可能点击首先展示的项目。这可能是因为他们赶时间、盲目相信排序算法,或其他原因。以下是展示华为数据中位置偏差的图表:

位置偏差。来源:华为的论文 PAL
位置偏差是一个问题,因为我们无法确定用户点击第一个项目是因为它确实最相关,还是因为它首先展示——而在推荐系统中,我们旨在解决前者的学习目标,而不是后者。
PAL 论文提出的解决方案是将学习问题分解为
p(click|x,position) = p(click|seen,x) x p(seen|position),
其中 x 是特征向量,seen 是一个二进制变量,指示用户是否已经看到该展示。在 PAL 中,seen 仅依赖于项的位置,但我们也可以添加其他变量(稍后我们会看到)。
基于这一框架,我们可以构建一个具有两个塔的模型,每个塔输出右侧的两个概率之一,然后简单地将这两个概率相乘:

来源:华为的论文 PAL
图中的云层实际上是神经网络:一个用于位置塔的浅层网络(因为它只需要处理单一特征),另一个用于 CTR 塔的深层网络(因为它需要处理大量特征并在这些特征之间创建交互)。我们也将这两个塔分别称为偏差塔和参与塔。
值得注意的是,在推理时,位置不可用时,我们仅使用参与塔进行前向传递,而不使用偏差塔。类似于 Dropout,因此模型在训练和推理时的行为有所不同。
它有效吗?是的,PAL 的效果非常好。作者建立了两个不同版本的 DeepFM 排名模型(我在 这里 写过),一个版本使用 PAL,另一个版本使用一种简单的处理项位置的方法:将其作为特征传递到参与塔中。他们的在线 A/B 测试显示,PAL 将点击率和转化率分别提高了大约 25%,这是一个巨大的提升!
PAL 显示位置本身可以作为排名模型的输入,但需要通过专门的塔进行处理,而不是主模型(这一规则也被添加为 Rule 36 到 Google 的“机器学习规则”中)。双塔模型正式诞生——尽管在 PAL 论文之前,这种方法可能已经在业界使用过。
“接下来观看”(YouTube,2019)——加性双塔模型
YouTube 的论文“Recommending What Video to Watch Next”——通常被简单称为“Watch Next”论文——发布的时间与 PAL 相*,并试图解决相同的问题:使用双塔模型去偏差推荐系统。然而,与 PAL 相比,YouTube 使用的是加性双塔模型,而不是乘性模型。
为了理解这为何有效,再次考虑学习目标的分解:
p(click|x,position) = p(click|seen,x) x p(seen|position)
由于概率是 logits 的 sigmoid 函数,sigmoid 函数本质上是加权指数函数,而指数函数遵循指数的乘法规则,因此我们也可以写作:
logit(click|x,position) = logit(click|seen,x) + logit(seen|position),
这就是,遗憾的是,加性双塔模型。

双塔加性模型。来源:“Recommending What Video to Watch Next”
相比于 PAL,另一个显著的创新是使用除了位置以外的其他特征在浅层塔中。例如,用户设备类型。这是有道理的,因为不同设备预计会表现出不同形式的位置偏差:例如,位置偏差可能在屏幕较小的手机上更为严重,因为用户可以同时看到的位置更少,并且需要更多地滑动。因此,YouTube 模型中的浅层塔不仅学习位置偏差,还学习各种选择偏差,具体取决于传入浅层塔的特征。
为了确保浅层塔实际利用了所有这些特征,作者在仅位置特征上添加了一个 10%丢弃率的 Dropout 层。没有它,模型可能会过度依赖位置特征,而无法学习其他选择偏差。
通过 A/B 测试,作者展示了添加浅层塔提高了他们的“参与度指标”(不幸的是没有给出该指标的详细信息)0.24%。
在 ULTR 中解开相关性和偏差(谷歌,2023)
到目前为止,我们假设 ULTR 中的两个塔可以在模型训练期间独立学习。不幸的是,最*的谷歌论文“Towards Disentangling Relevance and Bias in Unbiased Learning to Rank”的作者表明,这个假设并不成立。
这里有一个思想实验来说明原因:假设存在一个完美的相关性模型,即一个能够完美解释点击的模型。在这种情况下,一旦我们用新数据重新训练模型,所有的偏差塔需要做的就是学习如何将位置映射到点击。相关性塔则无需学习任何东西——它将完全无用!
换句话说,相关性对位置有干扰效应,如下图因果图中的红色箭头所示。

相关性塔对偏置塔的混淆效应。来源:Zhang et al 2023
但这不是我们想要的:我们希望相关性塔在新数据到来时继续学习。解决这个问题的一种简单方法——消除因果图中的红色边缘——是添加一个 Dropout 层到偏置对数值上,如下图所示:

在偏置塔上添加 Dropout。来源:Zhang et al 2023。
为什么使用 Dropout?关键思想是通过随机丢弃偏置对数值(bias logit),我们“推动”模型更多地依赖于相关性塔(relevance tower),而不是仅仅学习历史位置和相关性之间的映射。请注意,这一思想与 YouTube 的 Watch Next 论文非常相似,但这里我们丢弃的是整个偏置对数值,而不仅仅是位置特征。
为了使其有效,偏置对数值的 Dropout 概率需要很高:在 0.5 的 Dropout 下(即在 50% 的训练样本中随机将偏置对数值置零),作者在 Chrome 应用商店的生产数据上击败了 PAL 1% 的点击 NDGC——这是这种简单技术的有力证明。
总结
让我们回顾一下:
-
双塔模型是一种强大的方法来构建无偏排名模型:一个塔学习相关性,而另一个塔学习位置偏置(以及,选择性地,其他偏置)。
-
我们可以通过乘以两个塔的概率(如华为的 PAL 所做),或者通过加上它们的对数值(如 YouTube 的 Watch Next 所做)来组合两个塔的输出。我们将后者称为加性双塔模型(additive two-tower model)。
-
偏置塔的输入通常是项目的位置,但我们也可以添加其他引入偏置的特征,如用户设备类型。
-
Dropout(无论是在位置上还是在整个偏置对数值上)可以防止模型过度依赖历史位置,并且已被证明可以提高泛化能力。
这仅仅是冰山一角。鉴于推荐系统在今天的科技行业中的重要性,以及发现双塔建模可以显著提高预测性能,这一研究领域可能仍远未发挥其全部潜力:我们真的只是刚刚开始!
如果你喜欢这些内容并想保持对最新 ML 技术的了解,确保 订阅。
视觉变换器的崛起
ResNet 的时代是否即将结束?
·
关注 发表在 Towards Data Science · 9 分钟阅读 · 2023 年 11 月 15 日
--
图片由作者提供。
在过去的十年里,计算机视觉已经从一个有前途的研究领域发展成为现代技术革命的基石。曾经以准确分类小像素图像而引起关注的这些技术,现在能够仅凭简短的描述从虚空中生成如上图所示的高分辨率图像。这种令人惊叹的能力目前仅限于庞大的模型,但并非所有影响我们日常生活的模型都是巨大的。
在过去十年的计算机视觉领域,另一个发展方向是较小模型在性能和参数效率上的同步提升,其紧凑的内存占用和更高的帧率使得智能手机摄影、增强现实和机器人等领域的发展成为可能。像素级语义分割和深度估计,以及对象和其 3D 姿态的检测与跟踪,现在可以在越来越大的图像尺寸上实时执行,帮助自动驾驶车辆规划日常的交通操作。
本文讨论了在这些较小视觉架构中骨干网络选择的一个重要发展。正如我们将看到的,出现了一类新的模型,其性能强大且轻量级到足以引发范式的转变,并允许过去时代的坚韧老将退役。
卷积时代
自从AlexNet在 2012 年震撼了 ImageNet 挑战赛并点燃了深度学习研究的世界以来,卷积神经网络(CNNs)一直主导着计算机视觉领域。特别是,ResNet模型在 2015 年通过引入跳跃(“残差”)连接,成功解决了梯度消失问题并提高了参数效率,随后由于其强大的知识迁移能力,几乎被广泛用作各种下游任务的骨干架构,取代了VGGNet,成为当时最受欢迎的骨干架构。
而在计算机视觉研究这个快速变化的领域中,这种状态已维持*八年,这是一个史诗般的生命周期。但现在,挑战者正接*王座。继 2017 年“Attention is All You Need”在自然语言处理(NLP)领域的迅猛崛起,并发展成今天看到的大型语言模型现象之后,Vision Transformer(ViT)在 2020 年 10 月底首次展示了纯变换器架构能够在计算机视觉任务中实现最先进的性能,尽管需要更多的训练周期、资源和数据来实现这一点。
事实证明,CNN 的设计赋予它们诸如*移等变性(通过权重共享)和局部性(通过小滤波器尺寸)这些使其在图像数据结构上固有有效的归纳偏差。变换器可以通过足够的训练从零开始学习这些偏差,但这最初意味着变换器在数据效率上远不如 CNN。另一方面,尽管 CNN 标配了有助于其学习和处理图像数据的偏差,但它们缺乏变换器自注意力提供的跨层建模全局依赖的能力,而这无法通过任何数量的数据来学习。
一项名为“变换器是否比 CNN 更稳健?”的研究发现,尽管两种架构在使用相同的增强策略进行训练时对对抗攻击的鲁棒性相当,但与在任何策略下训练的大型 ResNet 模型相比,变换器在处理分布外(OOD)样本时表现出更好的泛化能力,且消融研究表明,这一优势由自注意力机制驱动。

DeiT 在 OOD 样本上始终优于参数更少的 ResNet。图形由 arxiv.org/pdf/2111.05464.pdf 提供
因此,CNN 的吸引人特性使其在学习图像特征时数据效率高,同时也预示了其衰退,因为其有限的感受野阻碍了它们对全局上下文的建模,这是一个非*凡的弱点。在 ViT 发表之前的几年中,已经尝试解决 CNN 中的长距离依赖问题:在诸如 DeepLab 的模型中探索了扩张(也称为“空洞”)卷积以增加感受野,Squeeze-and-Excitation、Non-Local Neural Networks 和 Attention Augmented Convolutional Neural Networks 展示了将注意力引入卷积操作的方法。Bottleneck Transformers (BoTNet) 通过将 ResNet 最后三层的空间卷积瓶颈块替换为多头自注意力块,在更大的图像分辨率下取得了显著的性能提升,但更大的提升尚待出现。
视觉变换器
在变换器希望取代王座之前,它们还需要克服架构上的弱点。ViT 论文在展示纯变换器架构能够在计算机视觉任务中实现最先进性能方面非常有帮助,同时也阐明了该应用所带来的挑战,包括前述的数据效率问题、自注意力相对于图像大小的不可接受的二次复杂性,以及在密集预测任务如语义分割和单目深度估计中有用的多尺度特征的缺乏。
“虽然这些初步结果令人鼓舞,但仍然存在许多挑战。其中之一是将 ViT 应用于其他计算机视觉任务,如检测和分割。”
— 来自 ViT 论文结论的行动号召。
研究界显然把 2020 年的 ViT 论文当作了待办事项清单,2021 年是繁忙的一年。首先,数据高效图像变换器 (DeiT) 通过展示增强方法和从教师模型的知识蒸馏在使用 ImageNet 数据集的更少周期内有效地训练 ViT 模型,解决了数据效率问题,从而消除了对大规模外部数据集的需求,并将无卷积的视觉模型带入了广泛采用的领域。有趣的是,使用 CNN 作为教师模型效果明显优于预训练的 DeiT,可能是因为这一过程将其结构偏差传授给了学生。视觉变换器现在已成为数据效率方面的正式威胁,但仍然缺乏多尺度特征。
“因此,考虑到我们的结果,其中图像变换器已经与卷积网络相当,我们相信,鉴于其在特定准确度下较低的内存占用,它们将迅速成为一种首选方法。”
— 来自 DeiT 论文结论的预言。
在二月末,一篇标题为 “金字塔视觉变换器:无需卷积的密集预测多功能骨干网” 的论文通过使用卷积解决了缺乏多尺度特征和自注意力复杂性的问题。Swin Transformer 注入了 CNN 的结构偏差,生成多尺度特征图,并通过在移动局部窗口中关注来减少自注意力复杂性,但像 PVT 一样,仍然依赖于补丁中的位置编码。卷积视觉变换器 (CvT) 改变了游戏规则,通过零填充 2D 卷积生成重叠的补丁嵌入,利用 CNN 的一项特性,从零填充中编码位置信息。

这张来自CvT论文的表格是对视觉变换器工作成果的有用总结,直到其发表为止。有趣的是,查看PVT 代码发现,“空间缩减”仅仅是 2D 卷积的委婉说法。
随后,PVTv2的新版本与Segformer同时发布,后者使用前者作为骨干,通过使用轻量级的全 MLP 解码器,实现了最先进的语义分割性能,并且参数效率令人印象深刻。GLPN随后使用这个骨干在单目深度估计(另一种密集预测任务)中取得了竞争力的结果。
要理解 PVTv2 作为骨干的突破性,我们可以从 Segformer 论文中的图表开始:

图表来自Segformer论文,展示了在语义分割任务上相较于同时期模型的卓越性能和效率。
上述图表清晰地展示了相比于同时期的层次化变换器和 ResNet 架构,性能和参数效率的优势(详见Segformer论文)。
接下来,我们可以回顾一个 PVTv2 支持更复杂架构的例子。Deformable DETR在 Detection Transformer(DETR)基础上有了显著改进,得益于提出的变形注意力,实现了更高的计算和数据效率,并且训练周期减少了 10 倍。Panoptic Segformer是对 Deformable DETR 的自然扩展,用于全景分割,并在 2021 年底发布,其中有一张比较图表对我们讨论特别有帮助。

图表来自Panoptic Segformer论文。请注意底部两个表现最佳的模型,分别使用了 Swin-L 和 PVTv2 骨干,其中后者在参数数目只有一半的情况下,性能相当。
遗憾的是,作者没有对 PVTv2 骨干网络的其他尺寸进行基准测试,除了最大的(B5)版本,但我们仍然可以从这个例子中获得一些重要见解。首先,我们看到尽管 Swim-L 骨干在技术上是较大模型中最具性能的,但 PVTv2-B5 在参数和 FLOPS 不到一半的情况下,性能几乎相同。此外,(虽然 PVTv2-B3 会是更好的比较对象)我们可以看到,PVTv2-B5 比 ResNet101 骨干的性能显著更优。
这些模型的成功表明,PVTv2 中的 Mix Transformer (MiT) 编码层能够用高效数量的参数生成极具描述性的特征,这要归功于其采用的“各取所长”的方法。通过将卷积操作融入变换器层,使自注意力更高效,创建多尺度特征,并注入对图像数据高效学习有利的归纳偏差,这些层次化的变换器模型可能成为计算机视觉模型开发中的下一个家喻户晓的名字。
胜者

图片由作者提供,灵感来源于这张图片。
因此,答案似乎并不是 CNN 与变换器之间的生死搏斗(参见许多过度溢美的赞词和挽歌),而是某种更浪漫的东西。层次化变换器(如 CvT 和 PVTv2)中采用 2D 卷积不仅便利地创建了多尺度特征,减少了自注意力的复杂性,并通过缓解对位置编码的需求简化了架构,这些模型还采用了残差连接,这是其祖先遗传的另一特征。变换器和 CNN 的互补优势已经在这些可行的后代中融合。
那么,ResNet 时代是否已经结束?虽然看起来确实如此,但任何论文肯定仍然需要在一段时间内包括这个不知疲倦的骨干网络进行比较。然而,重要的是要记住,这里没有失败者,只有新一代强大且可转移的特征提取器供大家享用,只要他们知道在哪里查找。像 PVTv2 这样的参数高效模型通过提供强大的特征提取,同时占用较小的内存空间,民主化了对更复杂架构的研究,值得被添加到标准骨干网络的列表中,以用于基准测试新架构。
未来的工作
本文重点讨论了卷积操作和自注意力的交叉融合如何促成了层次特征变换器的演变。这些模型在小规模下表现出了主导的性能和参数效率,使其成为理想的特征提取骨干(尤其是在参数受限的环境中)。然而,目前尚缺乏对这些模型在较小规模下所利用的效率和归纳偏差是否能够转移到大规模成功,并威胁到纯 ViT 在更高参数量下主导地位的探索。
大型多模态模型(Large Multimodal Models,LMMS)如大语言和视觉助理(LLaVA)及其他需要自然语言理解视觉数据的应用,依赖于从 ViT-L 特征生成的对比语言-图像预训练(Contrastive Language–Image Pretraining,CLIP)嵌入,因此继承了 ViT 的优势和劣势。如果关于扩展分层 Transformer 的研究表明它们的好处,例如增强了细粒度理解的多尺度特征,使它们能够以比 ViT-L 更高效的参数效率实现更好或类似的性能,那么这将对使用 CLIP 的任何内容:LMMS、机器人技术、辅助技术、增强/虚拟现实、内容审核、教育、研究等对社会和产业有广泛和直接的影响,可以改进和提高效率,降低这些技术的开发和部署门槛。
因果关系的科学与艺术(第一部分)
如果我们不能直接测试因果关系,我们应该怎么办?
·
关注 发表在 Towards Data Science ·10 分钟阅读·2023 年 1 月 5 日
--
图片由作者提供
让我带你踏上一段旅程,探索我的专业领域,这也是我的激情、我的执着,我喜欢称之为:因果关系的科学与艺术。
“因果关系”指的是因果之间的关系。它是指一个事件或行动可以引发另一个事件或结果。换句话说,因果关系关注的是理解事情如何发生以及为何发生。
在这篇文章中,我们首先将回答两个问题:为什么理解因果关系如此重要,以及为什么评估因果关系如此困难(因果推断的根本问题)。然后,我们将看到两种主要的因果效应测量方法(随机对照试验和自然实验)。最后,我将展示如何质疑因果关系,并提供实际工具来实现这一点。
为什么因果关系?
首先,我们为什么要关注因果关系?我为什么对这个话题如此执着?因为我们所做的每一个决定,无论是个人还是组织,都是基于某些行动会导致某些结果的假设。
例如,如果我们决定去素食,是因为我们相信这将对健康或环境有益。或者,如果一家公司改变了其广告策略,是因为它希望提高销售或订阅量。同样,政府必须考虑其行动将如何影响我们周围的世界,例如,过渡到可再生能源是否有助于实现气候目标。然而,理解因果关系可能很复杂,需要仔细分析以确定真正的关系。这就是因果关系的科学和艺术所在——它帮助我们审视和解读数据,以更好地理解我们周围的世界,并做出明智的决策。
问题是,如果我们未能正确评估关系和因果链,后果可能非常昂贵。
问题是,如果我们未能正确评估关系和因果链,后果可能非常昂贵。首先,政府可能会投入大量精力和资源来获取数据,然后花费更多资源来分析这些数据。但如果结论错误,他们可能会因跟随错误的路径而损失更多资源。因此,这就是为什么评估因果关系如此重要的原因。
此外,理解因果关系还帮助我们在阅读新闻、听取政治家发言、与他人讨论时,减少被操控或受到虚假信息影响的风险。
此外,好消息是,你可以学习一些工具和实用的方法,以每天应用这些方法来对抗虚假信息,并做出更好的或更有根据的决策。
为什么评估因果关系如此困难?
那么,为什么评估因果关系如此困难呢?问题在于,你没有统计测试可以告诉你你的效应是否是因果关系。你可以在这方面做很多事情。可以进行许多统计测试来挑战我们称之为识别假设(识别因果效应的假设),但通常,我们无法直接测试这些假设。
这正是理解因果关系如此令人兴奋的原因!它需要扎实的统计基础和深厚的领域知识。你必须运用批判性思维来考虑不同变量和事件之间的关系。仅凭数学是不够的。主要挑战通常在于我们如何解释所使用的统计指标——我们可能会把相关性误认为因果关系。
因果推断的根本问题
关键问题来自于因果推断的根本问题。让我用下面的两个图表来说明这个概念。在左侧,你可以看到全球的直接初级能源消费,分为可再生能源和其他能源生产来源。第二个图表表示世界的 CO2 排放量,从 1900 年到 2020 年。两个数据都在增长。然而,我们倾向于认为使用可再生能源有助于减少 CO2 排放。利用这样的汇总数据,有些人可能会被诱导认为可再生能源未必有用。

作者提供的图像。
显然,凭借如此简单的统计数据无法回答这个问题(可再生能源对 CO2 排放的影响是什么?)。问题在于,我们不知道如果没有可再生能源会发生什么。我们没有一个没有可再生能源的世界。

作者提供的图像。
可再生能源与 CO2 排放之间的关系是复杂的。一方面,像太阳能和风能这样的可再生能源可以作为化石燃料的替代品,从而导致 CO2 排放减少。另一方面,提取资源以生产光伏电池的过程是耗能的,并且“反弹效应”(即当能源由可再生资源提供时,人们会消耗更多能源)也可能导致 CO2 排放增加。没有更多的数据,就无法明确回答可再生能源是否导致更高或更低的 CO2 排放。底线是,仅凭这样的数据无法回答这个问题。
要完美回答这个问题,我们需要两个*行的世界。
在两个世界中,一个世界有可再生能源,而另一个世界没有。而由于这是两个世界之间唯一的不同,如果在 CO2 排放方面存在差异,那很可能是由于可再生能源的使用造成的。

可再生能源对 CO2 排放的影响是什么?要完美回答这个问题,我们需要两个*行的世界。作者提供的图像。
不幸的是,我们无法访问*行世界,在那里我们可以观察到相同情况在有和没有特定处理或行动时的表现。这就产生了“因果推断中的基本问题”,因为我们无法观察反事实——即没有进行处理或行动的替代现实。例如,我们无法同时观察一个既在服药又不在服药的病人。在因果推断中,我们尝试尽可能接*这种理想情况,在这种情况下,我们可以比较相同情况在有和没有特定处理或行动时的结果。这使我们能够更好地理解因果关系。
黄金标准
通常,解决这个问题的第一种方法,通常被称为黄金标准,并且可以说是最佳解决方案是随机对照试验(A/B 测试)。

随机对照试验的示意图。图片由作者提供。
简而言之,这个概念如下。我们抽取一个样本,这个样本希望能够代表更大的人群,并将受试者随机分配到两个组(处理组和对照组)或更多组中。受试者通常不知道他们是否接受了处理(这个过程称为盲法)。因此,这两个组可以说是可比的。由于唯一的区别是处理,如果我们观察到一个效果,那它可能是因果关系,前提是没有其他偏差存在。
然而,随机对照试验(RCT)有两个主要的缺点。第一个是我们不能总是使用 RCT。有时候这是不可能的,例如为了实验的需要改变一个受试者的性别。在其他情况下,这也是不道德的。例如,我有一篇论文评估武器出口对非洲冲突概率的影响。我们不会随机将武器发送到不同的国家,以观察这是否会影响冲突的概率。
第二个主要缺点是,当我们完全控制实验环境时,可能会以牺牲外部有效性(即我们可以将结果推广到研究范围之外的程度)为代价。例如,医学研究通常使用*交系的老鼠/小鼠。这些动物在基因上几乎是相同的,因此我们接**行世界的情况。但问题是,我们失去了外部有效性。
因此,通常在完美测量因果效应和结果是否能很好地反映现实生活情况之间存在权衡。

图片由作者提供
让我用一篇精彩的论文来说明这个想法:《使用降落伞预防从飞机上跳下时的死亡和重大创伤:随机对照试验》 (www.bmj.com/content/363/bmj.k5094)。这篇论文发表在顶级医学期刊:英国医学杂志(BMJ)。
在这个实验(这是一个随机对照试验)中,他们在 2017 年至 2018 年间的一年时间内成功招募了 23 名志愿者,并让他们从飞机上跳下。参与者被随机分为两组,一组使用降落伞,另一组则背着空背包。他们直接在碰撞后测量了死亡或重大身体创伤的概率。作者没有发现两组在这些结果(死亡和重大创伤)之间存在差异。
那么,问题在哪里呢?这确实是一个真实的实验,但为了能够进行这些实验,他们当然是在一个从未离开地面的静止飞机上进行的。人们从离地面约一米的高度跳下。论文的目的是强调有时候你试图通过实验来完美控制环境,但结果却不一定真实反映现实。
自然实验
所以,如果进行随机对照试验不一定可能,那我们该怎么办呢?我们可以求助于所谓的准实验设计或自然实验。
“自然实验是 观察性研究 ,其中 事件或情况允许随机 或看似随机的 研究对象分组 ,以回答特定问题。” 大英百科全书
让我举一个例子来说明这样的实验。假设你想评估污染对健康的影响(例如呼吸系统疾病的风险)。你可以在实验室中使用动物,但你不会让人类暴露在致命或非常危险的污染水*下。即使动物研究可能有用,我们也可能会难以将结果外推到人类身上。我们可能对现实生活中的情况感兴趣,例如长时间暴露在污染中,人类如何度过日常生活。然而,如果我们比较城市和农村的人,他们并不可比,其他因素使他们非常不同,例如:他们的饮食、运动方式、工作类型等。
另一个选择是比较城市居民与农村居民,但这些群体可能在其他方面(如饮食、身体活动和工作类型)有所不同,这可能会混淆结果。因此,准确评估污染与健康之间的因果关系可能很困难。
因此,为了回答这样的问题,我们可以借助自然实验。这正是一些研究人员在 2016 年所做的 (www.sciencedirect.com/science/article/abs/pii/S0095069616300237)。作者利用北京奥运会来衡量空气污染对死亡率的因果效应。政府在比赛前和比赛期间实施了非常严格的减排法规(例如,关闭电厂,减少汽车使用)。这种情况使得可以精确观察到相同的人在之前、期间和之后。这些个体经历了其污染暴露水*的突然变化,从高水*降到显著较低的水*。作者发现“PM10 [空气中的细颗粒物] 浓度下降 10%会使月度标准化全因死亡率降低 8%。”
如何挑战因果关系?
质疑因果关系的两个主要因素(内生性问题)是:你所关注的关系往往受到其他因素的影响(遗漏变量偏差),或者效应可能是双向的(同时性,或反向因果关系)。
由于我们不能直接测试因果关系,我们可以做什么?
由于我们不能直接测试因果关系,我们可以做什么?你可以总是询问是否有其他因素解释了这个结果,或者是否在同一时间反向关系成立。使用这些问题来挑战一个因果声明。

图片来源于作者
首先让我阐述第一个概念:遗漏变量偏差(是否有其他因素?)。研究发现咖啡消费与心血管疾病之间存在高度负相关 (academic.oup.com/eurjpc/article-abstract/29/17/2240/6704995?redirectedFrom=fulltext)。论文(Chieng 等,2022)中提到的咖啡消费与健康之间的联系可能并不一定是因果关系。需要考虑的是,其他因素可能会影响咖啡消费和整体健康。例如,身体活动更多的人可能会消费更多的咖啡,并且由于其身体活动而更健康。重要的是始终考虑可能存在其他因素,而不是在没有进一步分析的情况下将观察到的关系解释为因果效应。

图片来源于作者
你应该问的第二个问题是:可能是反向关系吗?例如,当你观察到喝酒更多的人更抑郁时,是因为他们喝酒,还是因为他们抑郁才喝酒?研究发现了这两种效应。因此,凭借这种关联,很难评估彼此的影响,因为它们混合在一起。
结论
由于我们无法直接测试因果关系,运用批判性思维挑战你听到或读到的任何因果声明是很重要的。首先考虑*行世界的情况,尝试发现理想情况和你所面临的情况之间的差异,以评估因果效果。这通常有助于发现问题可能出现的地方。然后,运用你的两个问题:可能是其他原因吗?是否存在反向因果关系?
要更深入了解,请阅读第二部分。
因果性的科学与艺术(第二部分)
让我们站在侦探的角度,探索因果推断。
·
关注 发布于 Towards Data Science · 8 min read · 2023 年 1 月 6 日
--
正如我们在这篇两部分文章的第一部分中看到的,测量因果效应对于得出正确结论至关重要,因为你做出的每一个选择或决定通常都是预期因果关系的结果。
例如:
个人选择:
-
如果我选择素食,我将减少我的生态足迹。
-
如果我喝下这杯龙舌兰酒,我会跳得更好。
公司:
-
在家办公会降低生产力
-
向用户投放 YouTube Premium 广告会增加订阅者数量。
政策制定者:
-
用可再生能源替代核电站将有助于实现《巴黎协定》。
-
封锁措施将减少 COVID-19 的传播
问题在于没有统计测试可以证明你的效果是因果关系。要挑战因果关系,正如文章的第一部分所解释的,你可以提出两个主要问题:是否还有其他因素可以解释因果关系,或者是否可能是反过来的(即效果导致了原因)?
[H]如何在没有直接测试因果关系的统计测试的情况下找到因果效应的证据?
这些问题使我们能够挑战因果声称。但是,如何在没有直接测试因果关系的统计测试的情况下找到因果效应的证据?在这篇文章中,我将向你展示研究人员如何通过一篇引人入胜的科学论文:‘伦敦雾:1866–1965 年污染与死亡率的一个世纪’(Hanlon (2018))。
为了做到这一点,我们将把自己置于警探的角度。警探们不断试图回答因果问题:是谁造成了这个人的死亡?是文官芥末在温室里用烛台吗?你确定不是用扳手或者其他人犯的罪吗?在我们的例子中,我们有一个嫌疑人,或者更准确地说,是一个我们想要验证的假设(例如,污染增加了死亡率)。然后我们问自己,这真的只是污染,还是健康服务的发展?或者实际上是天气的结果?等等。
通常,如果你想知道谁犯了罪,你很少有某人犯案的录像。即使有,也许画面模糊,也许是伪造的。因此,你可能永远无法百分之百确定罪犯的身份。为了克服这一限制,你会积累证据,尝试排除所有可能的罪犯不在场证明,直到你有足够的证据,并设法排除主要的其他故事(是否是其他人?这个人是否在做其他事情?)。在研究中,我们要寻找因果证据时,也非常类似。
案例研究:伦敦雾与死亡率
让我用以下论文‘伦敦雾:1866–1965 年污染与死亡率的一个世纪’(Hanlon (2018))来说明这些概念。伦敦在 19 世纪时已经是一个人口密集、污染严重的地区。这篇研究论文的作者回答了一个非常重要的问题:暴露对死亡率的影响是什么?
这篇文章的有趣之处在于,空气污染数据自 1950 年代才开始有。然而,准确的气象数据自 1850 年代就已存在。因此,文章的想法是利用雾作为污染的指标,因为在雾天,污染水*较低,而市民的污染暴露增加(见下图)。
你可以在这里找到一个完整的 Python 笔记本,其中包含我的代码,以复制论文并生成我将在本文中使用的图表:Deepnote notebook。

因果图。图片由作者提供。
初步了解影响
论文研究了严重雾霾对死亡率的影响。因此,让我们首先查看从雾霾周前五周到雾霾周后五周,死亡率(所有原因一起)如何变化。结果显示,死亡率在冲击时(第 0 周)以及后续几周都增加。然而,许多因素可能解释这一效应(例如季节性)。
数据集包含 1850 年至 1940 年伦敦的每周天气和死亡数据,并排除了第一次世界大战的年份。

图片由作者提供
我们可以忽略季节性和时间趋势的影响吗?
首先,让我们查看一下雾霾事件在一年中的分布情况。可以看到非常强的季节性(遇到严重雾霾的概率在冬季较高)。因此,在我们的模型中捕捉季节性效应非常重要,因为寒冷的天气与更多的雾霾相关,同时也可能与更多的死亡相关(寒冷天气容易让人们生病)。

雾霾的季节性。图片由作者提供。
其次,让我们查看一下 1850 年至 1940 年间出现严重雾霾的周频率。我们再次可以看到强相关性。我们观察到 1900 年后雾霾周的数量*均较少。模型必须考虑这一演变,以避免将这一效应与我们关注的效应混淆。这是因为医疗系统的质量随时间变化,降低了死亡率,同时雾霾周的数量也随时间减少。因此,如果我们不捕捉时间趋势,可能会夸大系数(高估雾霾对死亡率的影响)。

每年的雾霾事件数量。红色虚线表示 1900 年前/后的*均值。图片由作者提供
请注意,即使你对下面呈现的模型不熟悉,你仍然应该能够理解这个想法。估计模型是一个简单的线性回归:

用于表示一周的变量是t。Fog^s 是一个虚拟变量,当第s+t周出现严重雾霾时取值为一。X 是一个气象控制向量,包括降雨量、温度、气压和湿度。Year 和 Week 分别是固定效应集,用于捕捉年份效应和日历周效应(季节性)。e 是一个误差项。
因此,这种模型允许在有大雾的周的之前、期间和之后测量对死亡率的影响,同时考虑气象条件、季节性和年固定效应(时间的演变)。

森林图表示线性回归的系数。垂直轴代表死亡率,而水*轴代表距离有大雾的一周的周数。柱状图代表 95%的置信区间。作者提供的图片。
上图比较了一个不考虑季节性的模型(粉色方框)和一个考虑季节性的模型(橙色圆圈)。我们可以看到,季节性的影响确实扭曲了系数(在粉色模型中,死亡率增加大于橙色模型)。此外,考虑季节性的模型中死亡率在两周后恢复到雾前水*。
现在让我们加入年度固定效应。这组控制变量捕捉了污染物随时间的演变,但也捕捉了健康部门质量的演变,例如。因此,系数的解释略有不同。现在我们探讨年度 t 的死亡率与*均死亡率的偏差。
下面的图表显示了在大雾的那一周和接下来的一周,死亡率增加了。此外,我们还可以看到天气控制对估计值影响不大。

森林图表示线性回归的系数。垂直轴代表死亡率,而水*轴代表距离有大雾的一周的周数。这里的两种模型都包括了周和年的固定效应。柱状图代表 95%的置信区间。作者提供的图片。
现在让我们来质疑这种效应的因果关系。理由是雾会使污染物浓度降低,进而增加死亡率。使用我在本文第一部分中介绍的工具:“如果这是其他因素导致了这种效应呢?”。
这可能是一则意外和犯罪的故事吗?
如果有更多的雾,很难看清楚,所以会发生更多的事故或犯罪。为了排除这个替代的故事,作者比较了基于记录的死因(例如事故/犯罪 vs. 肺炎)的死亡率。
下面的图表显示,围绕大雾周的事故/犯罪引起的死亡率没有影响,而我们观察到肺炎引起的死亡率有显著的影响。
我们正逐渐接*抓到我们的嫌疑人:污染物。还有一个替代故事我想和大家探讨一下。

森林图表示线性回归的系数。纵轴表示死亡率(不同原因:事故/犯罪与肺炎),横轴表示与浓雾周的距离(以周为单位)。条形图表示 95%的置信区间。图像来源:作者
这可能是一个关于天气与流行病学的故事吗?
当天气恶劣(有雾)时,人们待在家里。因此,如果人们待在家里,就有更高的风险传染给他人,因此死亡人数的增加(例如,由于肺炎)仅仅是这种情况的后果,而不是污染的结果。这种说法似乎很难被反驳,对吧?
作者采用了非常优雅的方式来驳斥这一替代性故事。作者比较了两种不同的天气冲击:浓雾和大雨。确实,大雨会有类似的效果:人们可能会待在家里更久。然而,关键点在于雾霾使污染保持在低水*,而雨水则清洁空气。因此,如果这是一个污染的故事,我们应该会发现雨水(死亡人数较少)与雾霾相比的相反效果。
下面的图表正好揭示了这一效果:雾霾致死,大雨拯救生命。

森林图表示线性回归的系数。纵轴表示总体死亡率,横轴表示与浓雾周的距离(以周为单位)。条形图表示 95%的置信区间。图像来源:作者
结论
这篇论文提出了强有力的论据,证明了伦敦一个世纪以来雾霾与健康污染之间的因果关系,并使用证据来排除各种替代解释。为了评估因果关系,提出两个问题是有帮助的:‘是否有其他因素可能导致这种效果?’以及‘是否可能正好相反?’此外,下次当你需要质疑一个因果声明时,把自己置于侦探的角色,考虑和收集支持和反对不同解释的证据。通过这些技术,让我们做出更明智的决策,并对抗虚假信息。
提高投资回报率的秘密:实施全面漏斗营销方法
营销绩效与品牌建设相遇
在推动漏斗效率的同时,与客户建立更深层次的联系。
·发表于数据科学走向 ·阅读时间 12 分钟·2023 年 3 月 2 日
--

由Minator Yang提供的照片,来自Unsplash
作为数据科学家,你在通过数据解锁新的商业机会以推动增长和成功方面至关重要。快速变化的营销环境要求一种超越传统绩效营销或品牌建设战术的新方法。现在是时候接受挑战,全面审视将这两个元素结合起来的营销策略,以创建一个驱动实际结果的全面方法。作为数据科学家,你还扮演着变革推动者和业务顾问的角色;通过帮助转向全面漏斗营销方法,你可以产生变革性的影响。让我们更多了解一下:
营销世界不断发展,未能适应新现实的企业面临被淘汰的风险。COVID-19 大流行导致了消费者行为的变化,企业必须采取全面的营销策略才能保持竞争力。通过关注品牌建设和短期转化,企业可以与客户建立更有意义的联系,培养品牌忠诚度,并增加长期收入。
在这两个领域之间找到*衡可能具有挑战性,尤其是当它们在组织中历史上独立运作时。企业必须找到将品牌建设和短期转化策略整合在一起的方法,以创建一个驱动结果的全面营销策略。这需要建立测量系统和相应的 KPI。
在这篇文章中,我将提供见解,帮助营销人员在这个不断变化的环境中导航,建立一个成功的营销策略,这个策略将品牌建设与效果营销连接起来,以便这种方法能够在情感层面上再次与客户产生共鸣。
我们试图实现什么目标,为什么现在需要关注这个问题?
很多首席营销官将营销支出转向漏斗底部,这里更容易证明客户的获取,尤其是在经济困难时期,这是一个众所周知的事实。然而,通过这样做,营销人员常常忽视了品牌建设的重要性以及在漏斗顶部产生客户需求和关注。这种错误可能代价高昂,抹去了品牌与客户的情感联系。我亲眼见过这种情况,首席执行官会每小时监控销售,并要求额外的电子邮件发送,当然是促销的,一天又一天。在我们的年度品牌价值跟踪器中,你可以看到品牌价值和认知的恶化。
忽视品牌建设的重要性是一个代价高昂的错误,抹去了品牌与客户的情感联系。
许多品牌错误地只关注销售漏斗底部,即推动即时销售或转化。相比之下,更成功的组织意识到,关注整个漏斗,包括上部和中部,更重要的是建立品牌意识和推动对产品或服务的考虑。这种方法被称为全漏斗营销。它结合了不同的团队、测量系统和 KPI,创建一个更加连贯和有效的营销策略。通过这种方法,企业可以更好地理解各种营销努力如何在考虑到客户旅程转化的所有阶段相互作用,从而影响销售。

图片由作者创作
虽然全漏斗营销不是一个新概念,但营销环境中最*发生的两项重大变化使得企业必须迅速接受这种方法:
-
由于数字媒体费用的通货膨胀和目标广告市场的饱和,企业看到效果营销的回报趋于*稳或下降。随着自动化工具的商品化,在效果营销中获得竞争优势变得越来越困难,因此必须采用全漏斗方法。
-
由于 COVID-19 大流行,客户行为的急剧变化加速了数字化转型。在这种变化的环境中,客户对品牌的期望发生了变化,许多人将品牌的目标视为购买决策的关键因素。营销人员可以获得大量关于这些新行为的数据,为理解客户在整个漏斗中的决策过程提供了机会。
全漏斗营销的影响不仅仅体现在效率和投资回报率的提升上。更在于理解漏斗的每个阶段如何影响其他阶段以及整体客户体验。
当客户与品牌产生情感联系时,他们更有可能成为忠实客户,并在长期内花费更多。企业如果仅仅关注漏斗的下层,将面临摧毁品牌和侵蚀客户忠诚度的风险。
成功采用全漏斗营销方法的领先组织包括可口可乐和耐克等公司。他们认识到结合品牌建设和绩效营销的重要性,以创建完整的客户体验。

图片来源:Hal Gatewood 在 Unsplash
你如何将这两个要素整合并优化整个漏斗?
开发一个成功的全漏斗营销计划需要优先考虑四个关键要素。这些要素是推动业务增长和成功的基础,以全面和整合的方式进行:
-
衡量品牌建设
-
关注连接的关键绩效指标
-
进化到新型媒体组合建模
-
在漏斗中部署运营模型
无论是推出新产品、重新定位品牌,还是寻求实现季度销售目标,营销人员必须战略性地开发他们的营销计划,关注客户参与、品牌忠诚度和可衡量的结果。通过遵循这些关键构建块,营销人员可以创建强大且有影响力的营销计划,实现他们期望的结果。让我们深入探讨这四个支柱中的每一个。
1. 衡量品牌建设
在过去几年中,广告界发生了许多重大变化。传统的电视广告曾是许多品牌建设活动的支柱,能够在时间上持久。然而,这些努力难以跟踪,使得营销人员难以获取详细的信息来了解他们的广告活动如何产生影响。这是因为传统的电视广告没有数字广告的精准定位和跟踪选项。然而,这对营销人员来说是个好消息,因为数字电视使他们能够比以往更详细地跟踪谁在观看他们的广告。营销人员现在可以根据客户的兴趣、人口统计特征或其他属性展示不同的广告。因此,广告变得更相关和个性化,最终使得广告活动更有效。
此外,还有一些新的品牌活动成功测量方法超越了基本指标,如广告的观看人数和频次,主要关注广告活动的覆盖范围。如今,新的数据驱动方法可以为市场营销人员提供更多关于其活动表现的洞察,这可以帮助他们做出更好的投资决策。通过将这些新的测量技术与个性化广告定位相结合,市场营销人员可以创建更有效和更有影响力的活动,与目标受众产生共鸣。品牌建设领域的三个具体亮点:
可寻址电视和音频意味着电视和广播广告可以发送到特定的家庭或个人。借助这项技术,市场营销人员可以更精确地测量广告的效果,并将特定广告曝光与消费者采取的行动联系起来。相比之下,在传统的线性电视广告中,广告主选择一个网络或节目来播放广告。根据你试图销售的产品或服务,基于面板的研究可以帮助确定广告播放的时间和地点。而在可寻址电视广告中,你可能能够向每个目标家庭展示不同的定制广告。
所以,可寻址电视通过将客户的信息汇总并安全地匹配到他们的私人 IP 地址来工作。市场营销人员可以通过跟踪他们拥有的设备以及客户在不同设备上的使用情况,包括访问的网站、观看电视的时间以及客户喜欢的节目,来了解客户的很多信息。
品牌影响力测量与调查使市场营销人员能够看到他们的活动如何影响消费者对品牌的看法,重点关注上游测量。在当今世界,消费者比以往任何时候都更具信息化和广告意识。这意味着他们更倾向于选择那些与他们价值观相符并且有情感联系的品牌。虽然广告信息至关重要,但情感联系驱动了消费者行为。通过使用调查来衡量广告的影响,市场营销人员可以更好地理解如何创建与目标受众产生共鸣的信息。这可以帮助他们与消费者建立更强的联系,并推动品牌销售的增长。
营销信息很重要,但观众情感驱动着消费者行为。
品牌影响调查可以揭示广告如何影响消费者。品牌影响调查通过从数字、传统和混合接触点收集数据,覆盖了一个活动的效果。营销人员可以测试各种格式和创意方法的社交和展示内容,以评估他们从头到尾的活动,如品牌内容和影响者营销。品牌影响调查可以确定媒体策略是否成功地促进了品牌建设或活动。
这些调查通过严格的方法、大样本量和先进建模揭示人们的想法和行为。广告回忆、意识和考虑可以帮助品牌衡量活动成功。为了确定每个品牌的 KPI,调查通常将控制组和曝光组进行划分。这种方法使营销人员能够快速做出影响品牌认知的明智决策。
C. 营销归因 提供了另一种衡量品牌活动的核心方式。通过这些工具,营销人员可以在客户旅程中确定哪些广告导致了客户的哪些行为。这使他们能够找到广告与个人行为之间的更直接联系,并衡量其活动如何影响销售和收入等关键业务指标—最终帮助确定和调整跨接触点的营销策略,如广告、电子邮件、社交媒体等。这些“相关结果”没有像定性调查那样的消费者差异。
2. 相关 KPI
随着公司努力最大化其营销效果,将渠道和漏斗阶段之间的关键绩效指标(KPI)联系起来,已成为更好地理解其活动对实际商业结果的真正影响的强大方式。
例如,一家公司希望增加其网站的流量。通过跟踪社交媒体参与率(点赞、评论、分享)和网站流量,公司可以确定其社交媒体参与是否推动了更多的网站流量。如果社交媒体参与率很高,但网站流量仍然很低,则公司可能需要调整其社交媒体策略,以更有效地推动流量到网站。
同样,公司可能会使用展示广告来提高品牌知名度,并跟踪展示次数。通过将这一关键绩效指标(KPI)与搜索查询量挂钩,公司可以确定展示广告活动是否驱动了更多品牌搜索查询。如果展示广告活动成功,公司可能会看到品牌搜索查询的增加,而这些查询的每次点击成本低于普通产品类别搜索的成本。
邮件营销是公司接触目标受众的另一种渠道。通过跟踪邮件的打开率和销售转化率,公司可以确定其邮件营销是否有效地推动了销售。如果打开率很高,但销售转化率仍然很低,公司可能需要调整邮件营销策略,以更好地定位和转化潜在客户。
品牌在进行广告宣传时,通常会使用多个渠道(如社交媒体、电子邮件或搜索引擎)来接触不同阶段的潜在客户。理解哪些渠道效果最好以及哪些需要改进可能是具有挑战性的。
在关键绩效指标(KPI)之间建立的连接视图可以帮助营销人员了解不同触点(如电子邮件、广告或网站访问)如何协同作用以影响客户的购买决策。通过跟踪跨渠道和购买过程各阶段的 KPI,营销人员可以识别对业务底线影响最显著的互动。
一旦确定了这些关键互动,营销人员可以更好地决定在哪里投入广告预算,以获得最大的效果。通过更好地理解哪些渠道和策略最有效,营销人员可以调整或重新*衡他们的营销策略,以最大限度地利用预算并推动业务增长。
在建立了正确的关键绩效指标(KPI)并清楚地理解它们与业务结果的关系后,公司可以做出更明智的决策,并优化其营销工作,以实现更大的投资回报率(ROI)。
3. 新一代媒体混合建模
虽然媒体混合模型(MMM)一直是营销人员在不同类型的广告和营销中分配预算的宝贵工具,但它们也有局限性。最大的问题之一是它们无法捕捉短期变化,例如客户行为的变化,如 COVID-19 或广告活动结果的变化。这可能使得营销人员难以及时做出基于数据的决策。
领先的组织正在通过增量测试和多触点归因(MTA)模型等附加输入来现代化他们的媒体混合模型(MMM),以解决这些局限性。增量测试包括进行结构化实验,对照组的消费者不会看到广告,从而提供关于广告活动的实时和详细数据,帮助营销人员评估其努力的真实影响,并相应调整渠道的归因。*年来的技术和分析进步使得进行这些测试变得更加容易和便宜。这些增量测试有助于确定哪些策略或渠道在推动客户购买方面真正有效,而不仅仅是为那些本来就会发生的购买获得功劳。
此外,MTA 模型可以帮助确定客户购买的信用归属。通过将其与观众倾向评分(本质上是衡量某人购买的可能性)结合,营销人员可以更好地了解哪些策略和渠道在推动销售方面是有价值的。如果 TikTok 上的一个活动主要转换了那些可能会购买的人,那么它在推动增量销售(即没有这个活动就不会发生的销售)方面会被分配较低的价值。
通过现代化 MMMs 并添加这些额外的输入,营销人员可以更准确、详细地了解其活动的表现,并更迅速地做出数据驱动的决策。这对于全漏斗营销至关重要,因为它使营销人员能够理解漏斗的每个阶段如何影响其他阶段,从而提供完整的客户体验。因此,尽管 MMMs 有其局限性,但通过增量测试和 MTA 模型的现代化可以帮助营销人员优化支出并提高营销投资回报率。
4. 漏斗中的操作模型
除了提到的三个领域,全漏斗营销还需要投资于合适的技术和数据基础设施。例如,拥有一个集中的数据仓库和跨功能的共同数据语言对于确保团队使用相同的数据和定义至关重要。这使得准确测量和归因整个漏斗中的结果成为可能,并帮助团队基于数据驱动的洞察做出更好的决策。
支持数据和技术的同时,拥有合适的人才和能力也至关重要。这包括技术和分析技能的组合以及创意和战略专长。培训和提升计划可以帮助弥补人才差距,并确保团队具备执行全漏斗营销策略的正确技能。
最后,持续测量和优化绩效以确保全漏斗营销方法产生结果是非常重要的。这需要实验和测试以发现新的洞察和改进机会,同时还需要根据市场条件或消费者行为的变化进行调整和转变。

David Travis 的照片,来自 Unsplash
结论
关于新客户行为的数据丰富,为营销人员提供了深入了解客户及其决策过程的有利机会。为了保持竞争力,企业必须采用结合品牌建设和绩效营销的全漏斗营销策略。这种全面的方法可以增加客户参与度,培养品牌忠诚度,并推动收入增长。将你的努力集中在四个关键构建块上:
-
品牌建设的衡量
-
创建关联的 KPI
-
采用新波媒体混合建模
-
在整个漏斗中建立一个运营模型
通过全面的漏斗营销,企业可以提高效率和投资回报率,同时与客户建立牢固的情感联系。
一如既往,请告诉我你的想法,评论或提出任何问题;我很乐意听到你的声音。同时,祝你在与客户建立更强联系的过程中好运!
想要联系吗?
提升 NLP 性能的秘诀:深入了解 PyTorch 中的 nn.Embedding 层
解剖 PyTorch 中的 nn.Embedding 层及其工作原理的完整指南
·发表于 Towards Data Science ·阅读时间 8 分钟·2023 年 1 月 24 日
--

OpenAI DALL-E 生成的图像
你可能在涉及自然语言处理(NLP)的多个神经网络架构中见过著名的 PyTorch nn.Embedding() 层。这是设计先进 NLP 架构时最简单且最重要的层之一。让我用简单的术语来解释它是什么。
在花了一些时间研究其 C++ 源代码后,我发现了以下内容。nn.Embedding 层是一个简单的查找表,将索引值映射到具有特定维度的权重矩阵。这一简单操作是许多先进 NLP 架构的基础,允许在连续空间中处理离散输入符号。在训练过程中,神经网络中的 nn.Embedding 层的参数会被调整,以优化模型的性能。具体来说,通过反向传播更新嵌入矩阵,以最小化损失函数。这可以被视为学习将离散输入标记(如单词)映射到高维空间中的连续嵌入向量,其中向量被优化以表示输入标记的意义或上下文,相关于模型训练任务(例如文本生成、语言翻译)。
现在让我们通过一些具体的代码示例来了解:
nn.Embedding 层至少需要两个参数:词汇表大小和每个单词编码表示的大小。例如,如果你的词汇表有 10,000 个单词,那么第一个参数的值将是 10,000。词汇表中的每个单词将由一个固定大小的向量表示。第二个参数是每个单词学习到的嵌入的大小。
import torch
import torch.nn as nn
# Define the embedding layer with 10 vocab size and 50 vector embeddings.
embedding = nn.Embedding(10, 50)
这里发生的事情是 PyTorch 创建了一个名为 embedding 的查找表。这个表有 10 行和 50 列。每一行代表一个单词的嵌入,初始化时是从均匀分布中随机抽取的。它们是使用 torch.nn.init 模块中的 nn.init.uniform_() 函数进行初始化的,权重被初始化为在 -1 到 1 之间的随机值。要检查给定单词的嵌入(例如,表中的第一个单词),你可以运行:
embedding(torch.LongTensor([0]))
输出是一个大小为 50 的向量:

这些数字在训练过程中被调整和优化,以传达特定单词的含义。初始化方法对模型的性能有显著影响。不同的初始化方法可能导致优化过程的起始点不同,这会影响网络收敛到良好解的速度或难易程度。例如,如果权重初始化为非常小或非常大的值,反向传播过程中梯度也会很小或很大,这可能会减慢甚至阻止收敛。另一方面,如果权重初始化为接*零的值,梯度将更为合理,网络更可能快速收敛。
此外,不同的初始化方法被设计成与不同类型的激活函数配合良好。例如,Xavier 初始化设计用于与 sigmoid 和 tanh 激活函数配合使用,而其他方法可能与 ReLU 及其变体效果更佳。现在,让我们看看如何使用不同的方法初始化 nn.Embedding 层:
nn.init.normal_():它用从均值为 0 和标准差为 1 的正态分布中抽取的随机值初始化权重。这也被称为高斯初始化。
nn.init.normal_(embedding.weight)
nn.init.constant_():这个函数用特定的常数值初始化权重。例如,你可以使用 nn.init.constant_(my_layer.weight, 0) 将层的权重初始化为 0。
nn.init.xavier_uniform_() 和 nn.init.xavier_normal_():这些函数基于 Xavier Glorot 和 Yoshua Bengio 的研究,设计用于与 sigmoid 和 tanh 激活函数配合使用。它们将权重初始化为接*零但不太小的值。
nn.init.xavier_uniform_(embedding.weight)
nn.init.kaiming_uniform_() 和 nn.init.kaiming_normal_():这些函数基于 He 等人的研究,设计用于与 ReLU 及其变体(LeakyReLU、PReLU、RReLU 等)配合使用。它们还将权重初始化为接*零但不太小的值。
nn.init.kaiming_normal_(embedding.weight, nonlinearity='leaky_relu')
这些权重还可以使用预训练的词向量,如 GloVe 或 word2vec 进行初始化,这些向量在大型语料库上进行过训练,并且已被证明对许多自然语言处理任务有用。使用预训练词向量的过程称为——微调。将预训练的词嵌入与nn.Embedding层结合使用,对各种自然语言处理(NLP)任务非常有用。这是因为:
-
提高模型性能: 预训练的词嵌入已经在大量文本数据上进行训练,并且已被证明对各种 NLP 任务有用。当作为神经网络的输入使用时,它们可以通过提供一组好的初始权重来提高模型的性能,这些权重捕捉了词语的意义。
-
节省计算时间和资源: 从头开始训练神经网络以学习词嵌入可能是一个耗时且计算开销大的任务,尤其是当你处理大语料库时。通过使用预训练的词嵌入,你可以节省大量计算时间和资源,因为这些嵌入已经在大语料库上学习过。
-
允许迁移学习: 预训练的词嵌入可以用于迁移学习,这意味着你可以将学到的嵌入作为起点,用于不同但相关的任务。这在标签数据稀缺或获取成本高的 NLP 任务中特别有用。
让我们看看如何实现它:
import torch
import torch.nn as nn
# Load a pre-trained embedding model
pretrained_embeddings = torch.randn(10, 50) # Example only, not actual pre-trained embeddings
# Initialize the embedding layer with the pre-trained embeddings
embedding.weight.data.copy_(pretrained_embeddings)
你还可以使用from_pretrained()方法直接加载预训练的嵌入:
embedding_layer = nn.Embedding.from_pretrained(pretrained_embeddings)
你还可以使用来自流行库如 GloVe 或 fastText 的预训练嵌入,例如:
import torchtext
# Load pre-trained GloVe embeddings
glove = torchtext.vocab.GloVe(name='6B', dim=300)
embedding_layer = nn.Embedding.from_pretrained(glove.vectors)
在进行迁移学习时,你可能需要在训练过程中冻结预训练的嵌入,以防在反向传播步骤中更新它们,并且只有最后一个全连接层被更新。要做到这一点,设置embedding_layer.weight.requiresGrad = False以防止此层被更新。
变换器的兴起
另一个常见的地方是变换器架构中。nn.Embedding层是变换器架构的关键组件,它是一种广泛用于自然语言处理任务的神经网络架构,如语言翻译、文本摘要、问答以及创建大型语言模型如 GPT3。
在变换器架构中,nn.Embedding层用于将输入的令牌序列(例如单词或子词)转换为连续表示。这是通过在学习的嵌入矩阵中查找每个令牌的嵌入向量来完成的。
嵌入层的输出随后通过几个多头自注意力和前馈神经网络层,这些层用于在上下文感知的方式中处理和理解输入序列。自注意力机制是变换器的关键组件,它允许模型在进行预测时衡量输入序列中每个标记的重要性。这些都在 PyTorch 中的nn.Transformer层内实现。
经过变换器层处理后,模型的输出通常会通过一个最终的线性层,用于进行任务预测。例如,在语言翻译模型中,最终的线性层会用于预测目标语言中每个单词的概率,给定源语言中的输入序列。让我们看看Transformer类在 Python 中的样子:
import torch
import torch.nn as nn
class Transformer(nn.Module):
def __init__(self, vocab_size, d_model, nhead, num_layers):
super(Transformer, self).__init__()
# This is our holy embedding layer - the topic of this post
self.embedding = nn.Embedding(vocab_size, d_model)
# This is a transformer layer. It contains encoder and decoder
self.transformer = nn.Transformer(d_model, nhead, num_layers)
#This is the final fully connected layer that predicts the probability of each word
self.fc = nn.Linear(d_model, vocab_size)
def forward(self, x):
# Pass input through the embedding layer
x = self.embedding(x)
# Pass input through the transformer layers (NOTE: This input is usually concatenated with positional encoding. I left it out for simplicity)
x = self.transformer(x)
# Pass input through the final linear layer
x = self.fc(x)
return x
# Initialize the model
vocab_size = 10
d_model = 50
nhead = 2
num_layers = 3
model = Transformer(vocab_size, d_model, nhead, num_layers)
需要注意的是,这只是一个用于演示目的的简单示例,展示了嵌入层在Transformers中的使用,而真实的变换器模型通常会有额外的组件,如位置编码,这是一种提供每个标记相对位置的信息的技术。还有层归一化,用于规范化层的激活,以提高模型的稳定性和性能。此外,常见做法是使用预训练的嵌入来初始化嵌入层,以利用从大量文本数据中学到的知识。
有趣的事实:
- 上述具有仅 10 个词汇、50 维向量嵌入、2 个多头注意力机制以及编码器和解码器中的 2 层的变换器层共有 2,018,692 个可训练参数。这些是我们在训练过程中优化的参数数量。要得到这个数字,我运行了下面的代码:
sum(p.numel() for p in model.parameters() if p.requires_grad)
-
模型维度或 d_model 必须能够被多头自注意力机制中的头数整除,因为多头注意力机制将模型维度分割成几个较小的子空间。每个子空间用于计算不同标记集的注意力权重。
-
变换器模型也被用于计算机视觉任务,如物体检测和图像分割,通过使用自注意力机制将图像像素处理为标记。
总结来说,nn.Embedding 层是许多 NLP 模型中的基本资产,并且在 transformer 架构中扮演着关键角色。nn.Embedding 层用于将输入的标记序列转换为模型可以有效处理的连续表示。使用预训练的嵌入允许 transformer 模型利用从大量文本数据中学到的知识,这可以提高它们在各种自然语言处理任务上的表现。nn.Embedding 层还具有一些我们在这篇文章中没有涉及的参数,如 sparse 选项、padding_idx、max_norm 和 norm_type,这些参数可以用来根据具体任务的需求自定义嵌入层。理解 nn.Embedding 层及其工作原理是使用 PyTorch 构建有效自然语言处理模型的重要步骤。
有助于数据科学家成长的技能
·
关注 发表在 Towards Data Science ·发送至 Newsletter ·3 分钟阅读·2023 年 6 月 29 日
--
即使你处于数据科学学习的早期阶段,你也可能对入门所需的核心技能有一个明确的认识:一些统计知识、对一种(或两种)编程语言的基本了解,以及对如何处理、分析和可视化数据的合理掌握,仅仅列举了一些明显的技能。
那么,哪些技能能帮助你在职业生涯中长期蓬勃发展呢?这正是事情变得更加模糊的地方——也是本周重点内容发挥作用的地方。我们挑选了一些数据专业人士撰写的文章,他们根据自身经验和非线性职业路径分享了可操作的见解。这些文章可能专注于特定领域和角色,但它们提供的经验教训对许多其他实际情况同样适用。让我们深入了解一下。
-
对于将技能视作硬技能与软技能二分法的观点,有人可以提出有力的反驳,但不可否认的是,我们通常将“软技能”归类的那些技能,确实是数据导向职业成功的关键。Eirik Berge最*重点讨论了包括协作和指导在内的五项关键技能,并提供了令人耳目一新的具体建议。
-
从软件工程师的角度出发,Naomi Kriger整理了一个帮助你在项目展示中取得成功的实用路线图,适用于工作面试。在这里你找到的教训,同样适用于你下一个季度规划会议、ML 流程复盘或任何其他需要讲述清晰且引人入胜故事的场合。

-
首次参与 TDS 贡献的Fiona Victoria最*完成了申请人工智能博士项目的艰巨过程,并撰写了关于(众多)需要考虑的因素和步骤的文章。即使研究生院不在你的计划中,Fiona 的深思熟虑的方法可能会帮助你应对未知的压力,这一特质也贯穿于许多其他职业决策中。
-
“如果你是一个有志成为数据科学家的人,你可能会惊讶地发现,职业倦怠的风险其实从未真正消失过,”Matt Chapman警告道——对于那些从其他角色转行进入这个领域的人尤其如此。Matt 关于以长期可持续性为目标建立职业生涯的建议集中在更好的优先级排序和休息上,这些建议对我们所有人无论处于职业的哪个阶段都大有裨益。
如果你已经读到这里,恭喜你:你出色的时间管理技能显然让你能够继续阅读——我们希望你能继续阅读,因为我们的其他每周亮点也非常精彩:
-
Conor O'Sullivan关于 AI 工具对跨性别群体的风险的入门介绍讨论了一个紧迫且及时的问题,并呼吁所有数据科学和机器学习从业者提高意识和行动意愿。
-
你是如何解释数据中的异常的?Mariya Mansurova的易懂的根本原因分析新指南详细回答了这个问题。
-
通过关注Federico Peccia对这个日益重要领域*期工作的有用概述,了解推进绿色 AI 的最新技术。
-
想要从新的角度了解成功留住用户的推荐系统,请阅读Christabelle Pabalan对新颖性和意外性的深入讲解。
-
Elena Samuylova为初学者提供的漂移检测介绍中提出了五种测量机器学习嵌入漂移的方法。
-
MusicGen 是一个新的音乐生成模型,可以基于参考旋律创建新主题。 Max Hilsdorf测试了该模型的能力,并反思其对音乐创作过程的潜在影响。
感谢你对我们作者的支持!如果你喜欢在 TDS 上阅读的文章,考虑成为 Medium 会员 — 这将解锁我们的整个档案(以及 Medium 上的其他所有帖子)。
直到下一个 Variable,
TDS 编辑团队
在 Kubernetes 上运行代码的智能灵活方式
原文:
towardsdatascience.com/the-smart-flexible-way-to-run-code-on-kubernetes-94d1ae6c46f3
·发表于Towards Data Science ·6 分钟阅读·2023 年 1 月 23 日
--
当我还是一个 Kubernetes 初学者时,我主要关心的是如何让代码在集群上运行。被投身于一个全新的世界,我看到所有这些令人困惑的 YAML 文件,每一行和缩进都带来新的意义。

照片由Thais Morais提供,来源于Unsplash
一旦我学会了将代码最快速度放入文件中,我很快就用绝对路径填满了它。你可以看到下面的截断示例,我称之为初学者的方式。请注意,这是一种完全有效的运行代码的方式;只是缺乏即将到来的部分所关注的特性。
快速但容易出错的方式:代码目录的完整路径
让我们深入探讨一下.yaml 文件,并重点突出最相关的部分,以便将代码(和数据)放入容器中。简单来说,容器只是一个包含相关内容的盒子。这个盒子由 Pod 使用,我们可以将 Pod 视为一个虚拟计算机。这个虚拟计算机帮助我们运行容器中的内容。
(如果你对容器化更熟悉,你可能会注意到这个描述是简化的。a) 这是正确的,b) 你可能已经知道比这篇博客文章教你的更多内容)
在 YAML 文件中与本文相关的部分是我们决定运行哪些代码的“command:”部分。这已经是个棘手的地方。假设你的本地开发机器使用以下虚构路径:/local/user/path/script.py。假设我们将此代码发布到计算集群并存储在/remote/cluster/path/script.py下。既然我们知道脚本在 Kubernetes 集群中的位置,我们可以转到 YAML 文件中并粘贴它的路径。如果运气好,它会工作。
但很可能不会,特别是如果我们在script.py中使用了来自其他用户定义的 Python 文件的导入时。我们的代码很可能会因“ModuleNotFoundError: No module named ‘xyz’”而失败。这种错误发生是因为当我们运行 script.py 时,Python 在已知的位置搜索要导入的模块。如果我们希望读取/导入的文件不在这些位置,我们的代码将无法运行。
为了说明这一点,设想你站在烈日下,夏天的热浪让你汗流浃背。你想喝点水,哪怕是自来水,现在也非常棒,于是你走进屋里。
从厨房水槽的水龙头中,你倒了一杯水,急切地喝下去。水很不错,所以你也想在外面喝点东西。因为水来自水龙头,所以你简单地撕下水龙头并带着它。再次走到外面,你会发现:“WaterNotFoundError: No water。”
在这个构造的示例中,很明显为什么仅仅撕下水龙头并不能魔法般地获得无限的水供应。问题在 Python 示例中也是一样的:我们操作的点不知道所需文件的位置。在本地开发环境中,所需的代码可能与主脚本在同一文件夹中。但当我们——类似于水龙头的情况——从集群上的另一个文件夹(即从其他位置调用 Python 脚本)操作时,代码会发生什么?答案是“ModuleNotFoundError: No module named ‘xyz’”*
这里有一个快速但粗糙的修复方法(我之前也用过):在所有从其他用户创建的 Python 文件中导入的 Python 文件中,将这些辅助文件所在的目录添加到 Python 的搜索路径中:
sys.path.append("directory/with/auxiliary/files")
对所有目录重复这一步,你就完成了。
这个修复能解决问题,但不是最好的方法。代码膨胀;我们不需要本地设置中的额外行,如果我们把辅助文件完全移动到文件系统上呢?我们将不得不更新所有硬编码路径!这太麻烦了。幸运的是,还有更好的方法。
智能且灵活的方法:相对路径和包含代码
运行代码的更好方法是使用相对路径。在运行的示例中,我们将 Python 文件存储在集群的/remote/cluster/path/script.py下。
我将假设,根据良好的惯例,所有辅助代码(例如,“utils.py”)都位于此目录或其子目录中。考虑到这一点,采取更智能的方法有两个步骤,一个涉及 Docker 镜像,另一个涉及 .yaml 文件。
改进后的 Docker 镜像 在展示改进版本之前,这里有一个相当标准的 Dockerfile:
尽管这个默认的 Dockerfile 能完成工作并安装所有包,但我们可以通过稍微改变内部文件夹结构来进一步改进它:
在改进后的 Dockerfile 中,我们首先安装所需的 Python 包,然后将代码直接存储在镜像中。这一步非常重要,和之前的方法相比差别巨大:之前我们从底层文件系统调用 Python 脚本;现在我们从Dockerfile 内部调用它们。
为此,我们创建一个名为code的文件夹,将其设置为工作目录,并通过运行“COPY . .”将所有代码/数据等复制到 Docker 镜像中的此目录中。之后,我们授予自己在容器中运行代码的权限。
改进后的 .yaml 文件 使用这个设置,可以构建改进后的 .yaml 文件。我们之前调用脚本的完整路径,例如,
command: [ "python3", "/home/scripts/research/audio/preprocessing.py" ]
现在我们可以将其简化为
command: [ "python3", "preprocessing.py" ]
再次,差异可能看起来很小:毕竟,我们只是移除了脚本的路径(* /remote/cluster/path*)。
但在幕后,我们现在正在读取并运行镜像中的 Python 文件。换句话说,我们有了一个可移植的环境。
不过有一个警告:如果我们将新代码推送到远程服务器,从而更新我们的脚本,这一变化会反映在镜像中吗?不会,镜像保持为我们最后构建的状态。
两个 Dockerfile 以加快构建时间
幸运的是,对于这种常见情况,有一个简单的技巧:维护两个 Dockerfile。在这种情况下,我的常规步骤如下。
- 我有一个主要的 Dockerfile,恰当地叫做Dockerfile_base。
我首先用这个文件构建一个镜像,文件中包含足够的命令来创建一个项目的基础。例如,这可能是这样的一个起始点:
细看,你会发现我在这一步并没有复制任何代码相关的文件。相反,我只安装了 pip 包,并通过 apt-get 获取一些额外的需求。这个结构不太可能发生变化,所以我将生成的镜像存储为,例如,project-audio-analysis-base:0.0.1。注意镜像名称中的 -base 标签。
2. 构建基础镜像后,我构建第二个 Docker 镜像。
对于这个镜像,我拉取了之前创建的project-audio-analysis-base:0.0.1。这个新镜像是从一个单独的 Dockerfile 构建的,我通常称之为Dockerfile_update或类似名称。其内容示例如下:
关键是,我将生成的镜像不是存储在与之前基础脚本相同的位置——这会覆盖我们干净的起点。相反,我将其存储在一个不同的位置;通常,我只是省略 base tag,如:project-audio-analysis:0.0.1。每当代码有已发布的更改时,我使用较薄的 Dockerfile_update。这种方法的好处是始终有一个干净的起点(对于你破坏了代码的情况)并显著减少构建时间。
如果我们每次创建新的 Docker 镜像时都重新安装所有软件包,我们每次都需要等待。这是不必要的。只需存储一个已预装所有这些软件包的固定基础,并在此基础上构建镜像即可。
总结
在开始使用 Kubernetes 时,常常会遇到这些巨大的、复杂的 YAML 文件。可以理解的是,一旦代码按预期运行,人们会感到相当释然。然而,一个错误——或错误的设计决策——是使用绝对路径并从文件系统中运行代码。
为了缓解这个问题,我提出了一种更好、更清洁、更便携的在 Kubernetes 集群中运行代码的方法。它包括两个部分:首先,将代码包含在镜像内,其次,维护一个基础和更新的 Dockerfile。拥有这些工具后,可以快速生成改进的镜像。
- 从另一个位置调用 Python 脚本:通常,我们使用命令行进入 Python 脚本所在的文件夹,然后执行 python script.py。但是,当在另一个文件夹中时,我们也可以运行 python /path/to/script.py。在这种情况下,博客文章中描述的问题会出现。
成为数据科学家成功所需的软技能
原文:
towardsdatascience.com/the-soft-skills-you-need-to-succeed-as-a-data-scientist-ceac760230d3
成为数据科学家职业生涯中可以提升的前五种软技能
·发表于Towards Data Science ·阅读时长 14 分钟·2023 年 6 月 19 日
--

图片由Jason Goodman提供,来源于Unsplash
你的旅程概览
-
介绍
-
技能 1 — 沟通
-
技能 2 — 协作
-
技能 3 — 好奇心
-
技能 4 — 项目管理
-
技能 5 — 导师指导
-
总结
介绍
当你在数据科学家的职业生涯中工作时,很容易专注于硬技能。你可能想学习一种新的 ML 算法,比如带有非线性核的 SVM,新的软件技术,如 MLflow,或新的 AI 趋势,比如 ChatGPT。
这些技能容易学习,因为它们的成功衡量标准很简单。以 MLflow 为例。你可能首先会学习 MLflow 能为你的 ML 生命周期提供什么。你了解模型工件、ML 项目结构和模型注册。你完成了一个课程,花了几个小时阅读用户指南,甚至在实际项目中实现了它。太棒了!完成这些后,你可以自信地说你了解了一些 MLflow,并可以将其作为技能添加到你的简历中。
那么,软技能比如时间管理呢?你会如何进行相同的过程?真的停下来好好想一想这个问题。虽然确实有关于时间管理的书籍可以阅读,但它远不如阅读 MLflow 文档那样具体。你可以在日常生活中实施时间管理,但这不如在 ML 项目中实现 Mlflow 那样直观。你可以在简历中列出时间管理,但那到底意味着什么呢?😧
生活的事实是,软技能更难以衡量,因其本质上更难以捉摸。许多人得出结论,软技能的价值低于硬技能。但这是一个严重的错误!仅仅因为某些东西难以衡量,并不意味着它不值得投入精力去提升!
我相信我们都遇到过那种时间管理得当的同事,他们的产出几乎是其他人的两倍。这是通过硬技能几乎无法获得的提升。我从未见过有人学习了 MLflow,然后产出是其他数据科学家的两倍。所以即使软技能难以衡量,它们也能提供远超许多典型硬技能的价值 🔥
这在数据科学中尤其如此。数据科学家的积极刻板印象是具有出色的问题解决能力。负面刻板印象是他们在商业环境中成功所需的一些常见软技能上稍显欠缺。通过花费一些时间来提升软技能,你可以获得巨大的优势,并用它来开辟你自己的职业道路。
在这篇博客文章中,你将了解数据科学家成功所需的前 5 项软技能。这当然只是基于我个人的观点。然而,这一观点是通过观察许多其他数据科学家以及看到他们中的一些人如何从其他人中脱颖而出而形成的。
技能 1 — 沟通
第一个技能是最基础的。你应该学会如何进行良好的沟通。这包括很多方面:
-
你应该能够清楚地传达你在探索性数据分析(EDA)阶段的发现。 出于上天的爱,务必根据听众调整你的沟通方式。CEO 不想听你关于选择数据拟合分布的决定,或是你使用了哪个 Docker 镜像来运行实验。CEO 可能对数据科学很热情,但他或她还有成百上千的其他事情需要考虑。给出 EDA 的高级概述,并专注于业务结果。
-
在演讲时,确保你说的内容对观众有价值。 这听起来显而易见,但显然并非如此。不要仅仅为了显得聪明而谈论复杂的架构或精密的超参数调整。这只是一种防御机制。相反,确保你说的话能够给听众带来有价值的东西。这样,你会突然发现有人来找你讨论你所讲的内容。
-
与他人交谈时,确保你在听他们说什么。 这并不等于点头并等待轮到自己发言。真正的倾听意味着将自己置于发言者的立场,认真尝试理解他们的观点。假设一个产品负责人向你解释他们希望加快进度而减少探索。与其等待解释他们的错误,不如花点时间真正倾听。产品负责人可能会因为进展被评估,并且可能不了解探索分析的好处。如果你诚实的话,探索分析可能确实进行了比必要的时间更长。试着倾听,然后与产品负责人合作,找到一个对双方都好的解决方案。
此外,你应该改进你的写作。不,真的。虽然写得并不糟糕,但可以更简洁。你有写复杂句子、进行不必要解释和拖延细节的倾向,而这些细节的重要性远没有你想象的那么高。别担心,我也是这样 😳
简洁可以传达自信。 比较以下两种对下周五到期的功能请求邮件的回复:
-
我认为在下周五之前这是可能的。我将从研究问题开始,理解解决方案空间,然后以迭代的方式进行工作。如有需要,我会寻求建议,并朝着你要求的目标,即在下周五之前完成功能,继续推进。我相信一切会顺利进行,我会在下周五之前交付令人满意的结果。
-
我将致力于在下周五之前完成这个任务。如果出现任何障碍,我会寻求建议。
在第一种陈述中没有更多信息,除了对你工作的迭代和对令人满意结果的模糊承诺。想象一下作为一个项目经理或技术负责人,必须天天阅读这样的内容。删掉这些吧!以专业的方式说出你想说的话,然后继续解决当前的问题。
技能 2 — 协作
只有很少的数据科学工作是由单个数据科学家完成的。当然,也有少数例外。但大多数有影响力的数据科学工作是由数据专业团队完成的,这些团队通常得到前端/后端开发人员、*台工程师、测试人员、领域专家、项目经理等其他职业的支持。
这意味着协作不仅有用,而且对于成功的数据科学至关重要。以下是一些可以提升你协作技能的方法:
-
当依赖其他角色时,了解你的工作与他们工作的接口。 比如,你正在与一位在 Databricks 或 Synapse Analytics 中编写 Spark 的数据工程师合作。他们工作的输出是经过清理且格式正确的数据表。但什么是正确的格式呢?这完全取决于你想要使用的特性和你计划使用的算法。你不希望数据工程师费尽心思清理一个你马上就会丢弃的表格中的列,因为你不打算使用它。这是协作不良的表现。
-
当其他角色依赖于你时,提前规划如何确保良好的协作。 比如,你正在计划开发一个处理实时数据并预测值的 ML 模型。预测结果将被发送到前端应用的用户和 Power BI 仪表盘进行内部跟踪。那么,前端开发人员和数据分析师应该了解数据的未来格式。你甚至可以为他们提供具有确切数据结构的模拟数据。通过这种方式,你可以确保依赖于你的人员不必等你完成后才能做他们的工作。当人们协作良好时,就像并行处理;当他们不协作时,就变成了单线程,一切都变得缓慢。
-
与其他数据科学家合作时,明确责任归属。 由于数据科学家来自不同的背景,他们的技能集差异很大。你可能会有一位数据科学家非常擅长为模型提升那些额外的准确率。另一位数据科学家可能非常擅长将模型投入生产并监控数据漂移。不同的人可以根据他们的经验承担不同的方面的责任。尽管如此,每个数据科学家仍然可以对各个方面做出贡献。
最后,还有一些与数据科学无关的更通用的内容。团队中的每个人都值得被尊重。这与他们的技术背景、技能水*、性别或任何其他与基本礼貌无关的因素无关。
人们有时会犯错误。重要的是要承认错误,同时确保错误是学习的自然部分。你应该优化以在团队中形成一种可以在不怕报复或嘲笑的情况下承认错误的文化。 如果你未能创造这样的文化,那么错误不会停止发生。它们只会在雷达下发生,并在修复它们变得更困难时浮出水面。
技能 3 — 好奇心

图片由 nine koepfer 提供,来源于 Unsplash
我一直觉得数据科学家天生是好奇的人。他们喜欢学习新的 ML 算法或跟踪自己领域中的新发展。但是,这种好奇心是否扩展到邻*学科中的技术、方法和方法则差异很大。
一些数据科学家对学习更多关于软件开发、设计原则、项目管理、数据分析、数据工程、商业影响等方面的内容感到兴奋。其他人则希望坚持自己的领域,只专注于数据科学。虽然这完全没问题,但如果你被评估得低于那些有好奇心探索邻*领域的同事,你也不应感到惊讶。
这不公*吗?其实不然 🤷
知道软件开发的数据科学家显然比不知道软件开发的人更有用。软件开发技能确保数据科学家可以从事更多可能的项目。同时,与前端/后端开发人员和数据工程师等角色的接口也突然变得更容易管理。
其他人只能通过他们自己的视角来看待你的输出。 比如,假设一个测试人员负责运行你编写的几个组件的集成测试。如果你的组件文档完备、模块化良好、编码标准高且有单元测试,那么测试人员的工作就会轻松很多。另一方面,如果你只是有大量自由流动的代码在一个庞大的 R 脚本中,那么追踪错误对测试人员来说将是一项繁重的工作。自然,测试人员会认为那个在软件方面投入更多努力的人更有技能。这与脚本中的 ML 模型做什么无关。
商业影响是另一个经典问题。数据科学家最常收到的负面反馈之一就是他们与商业目标过于脱节。一个理解业务并提出能产生 ROI 的数据科学用例的数据科学家自然会对业务更有价值。
那么如何在这广泛的好奇心上努力呢?你们可以花费在其他学科上的时间是有限的,但我有两个一般性建议:
-
花一些时间去了解其他角色真正从事的工作。 与业务分析师交谈时很快就能掌握一些 KPI 和 OKR 的知识,但这些知识可能非常宝贵。就我个人而言,我对计算机网络了解甚少,因为我没有信息学背景。但我知道为什么有人会使用私有网络,如何粗略设置一个,以及何时可能适合投资于此。我主要是通过与网络工程师交谈获得这些知识的。虽然这些知识比较肤浅,但了解何时联系网络工程师是很有价值的。
-
在做项目时,抓住机会做一些稍微超出你舒适区的事情。是否有人需要在持续集成管道中实现自动化的代码检查?我来试试吧!即使你对 CI/CD 或 YAML 文件了解不多,你也可能会弄明白。如果不行,你总是可以寻求帮助。通过抓住学习新事物的机会,你会…学习到新事物。我知道,这挺深刻的 😉
技能 4 — 项目管理
回顾一下之前需要团队协作的项目。想想那些未能按时完成或超出预算的项目。共同的因素是什么?是超参数调优不足?还是模型工件日志记录不佳?
可能不行,对吧?项目失败的最常见原因之一是项目管理不善。 项目管理负责将项目分解为可管理的阶段。每个阶段都应持续估算剩余的工作量。
作为一个决策明确的项目经理,还需要负责很多其他的事务,从冲刺执行到回顾总结。但是我不想把重点放在项目管理的角色上。我想将重点放在项目管理的技能上。就像团队中的任何人都可以展现领导力作为一种技能一样,团队中的任何人也可以展现项目管理作为一种技能。对于数据科学家来说,这确实是一个非常有用的技能。
为了具体说明,让我们专注于估算单个阶段。实际上,大部分数据科学工作很难估算:
-
数据清理阶段需要多长时间? 完全取决于你正在处理的数据。
-
探索性数据分析阶段需要多长时间? 完全取决于你在过程中发现了什么。
你明白我的意思了。这使得许多人认为估算数据科学项目中各阶段的持续时间是没有意义的。
我认为这是一个错误的结论。更准确的说法是,在开始阶段之前准确估算数据科学阶段的持续时间是非常困难的。但是项目管理是与持续估算打交道的。或者说,良好的项目管理应该是这样的 😁
想象一下,不是提前估算数据清理工作的时间,而是你已经开始清理数据一周了。你现在知道有三个数据源存储在不同的数据库中。其中两个数据库缺乏适当的文档,而最后一个缺少数据模型,但文档比较齐全。在所有三个数据源中都有一些数据缺失,但没有你担心的那么多。你能对此说些什么?
当然,你并非没有任何信息。你知道你不会在明天完成数据清理工作。另一方面,你非常确定三个月的时间对于这项工作来说太长了。因此,你有一种分布,给出阶段完成的概率。这种分布有一个“均值”(对阶段持续时间的估计)和一个“标准差”(估计的不确定性)。
重要的是,这种概念分布每天都在变化。 你会得到越来越多关于需要完成工作的的信息。随着你对阶段完成时间的确定性增加,“标准差”自然会逐渐缩小。你的工作是将这些信息量化给利益相关者。而且在向利益相关者解释时,不要使用我用的分布语言,这可以留在我们之间。
能够说出这样的话的数据科学家是非常宝贵的:
“我认为这个阶段需要 3 到 6 周的时间。我可以在一周内给你一个更准确的更新估计。”
技能 5 — 指导
指导初级数据科学家通常被视为一种必要的“恶行”。这无疑是诚实的工作,但并不被强调。如果初级数据科学家能神奇地自学这些概念,那不是更好吗?
正如你可能猜到的,我不同意。指导初级数据科学家对你和他们都非常有帮助。 这里有三个原因:
-
你通过解释概念学到了很多: 这一点非常直接。通过向初级数据科学家解释概念和想法,你自己对这些概念的理解也会更深刻。我常发现,向初级数据科学家解释某些东西有助于我更清晰地表达某些内容。通常,只有当有人问你时,你才会意识到你可能对某些东西的理解没有你想象的那么好。这是一个深入了解话题的绝佳机会。此外,你可以向初级数据科学家强调,不知道所有事情是没关系的。实际上,这也是不可避免的。
-
你获得了初步的管理经验: 不久后,你可能会踏入更高级的角色,例如首席数据科学家。这些角色通常没有正式的员工管理责任。然而,期望是你能够领导和影响他人。像任何其他技能一样,这需要实践。在你日常的数据清理和模型调整中,你很少有这样的实践机会。因此,如果你从未指导过任何人,那么如果你在领导和影响他人时遇到困难,也不要感到惊讶。如果你在考虑转向管理轨道,那么过去没有任何指导责任可能是一个警示信号。你为什么从未指导过任何人?是因为你不想做,还是因为其他人不希望你做?这些可能性都不太理想。
-
你可以与初级数据科学家建立联系: 当然,导师和初级数据科学家之间存在自然的权力不*衡。尽管如此,如果导师做得好,初级数据科学家通常会与导师建立最紧密的联系。通过承担责任和指导初级数据科学家,你很快会发现自己被你指导过的人包围。这些人往往会尊重你,重视你的建议。这并不是一种糟糕的境地。
我的建议是尽快在你的职业生涯中成为一名导师。以上三点好处只有在你认真对待指导工作时才有效。如果你指导工作做得不好,你会获得很少的好处,甚至可能被认为是一个糟糕的导师 😬
一些公司对指导的期望很低。你可能被要求每月与初级数据科学家喝一次咖啡。我建议你超越职责范围。 向初级数据科学家提供,他们可以向你提出问题和困惑。这样主动担当对于初级数据科学家来说,表明你可以在没有明确要求的情况下承担责任。
总结

图片来源:Spencer Bergen 在 Unsplash
在这篇博客文章中,我们看到软技能对于数据科学家职业发展是非常有价值的。在面试高级数据科学家时,我会关注他们积累的软技能和硬技能一样多。如果你认为还有其他对数据科学家至关重要的软技能,请给我留言。
如果你对数据科学、编程或其他相关领域感兴趣,欢迎在 LinkedIn 上关注我,并打个招呼 ✋
喜欢我的写作吗? 查看我其他的文章,获取更多 Python 内容:
-
用优雅的类型提示来现代化你的罪恶 Python 代码
-
用 Python 可视化缺失值非常简单
-
使用 PyOD 在 Python 中引入异常/离群点检测 🔥
-
5 个绝妙的 NumPy 函数,能在关键时刻救你一命
-
5 个专家技巧让你的 Python 字典技能飞跃 🚀
SQL 单元测试现状:2023
原文:
towardsdatascience.com/the-sql-unit-testing-landscape-2023-7a8c5f986dd3
提升 SQL 开发的速度和安全性
·发表于Towards Data Science ·阅读时间 9 分钟·2023 年 5 月 2 日
--

图片由Ilse Orsel拍摄,来源于Unsplash
SQL 开发的演变
尽管 SQL 已经有将*50 年的历史(或者说正因为它已经将* 50 年了),但该语言的发展在采用现代实践和工具方面一直较为缓慢。几十年来,我们主要的“IDE”是像SSMS和DBeaver这样的管理工具,版本控制则通过像stored_proc_2022_03_15这样的命名约定来完成,测试则是通过将查询运行在生产表上并导出到电子表格中进行手动核对。
但在过去十年中,情况已经迅速改善。那些数据库管理工具以及云数据仓库控制台都具有丰富的功能,包括语法高亮、代码自动完成和计划可视化。我们通过像Liquibase、Flyway和dbt这样的工具实现了数据库版本控制。即使是测试/审计方面也不再那么令人头疼,有了像dbt-audit-helper这样的工具。
最后一个前沿领域之一是 SQL 单元测试,它仍然处于起步阶段。让我们深入探讨一下!
我们为什么要进行单元测试?
对于那些从软件工程之外进入 SQL 开发领域的人来说,单元测试的价值可能并不明显。通常,查询开发是一个相对快速的“编写查询、运行查询、查看结果集、修改查询、重复”的过程。但随着查询复杂性和数据量的增长,你会遇到一些障碍。
首先,你可能会进行大量冗余测试。考虑以下转换:
select
id,
case when order_count < 50
then 1
else 0
end as is_small_customer
from customer
查询本身并不令人兴奋。但从测试的角度来看,我们实际上需要测试什么,正在测试什么?我们只需要四条记录来覆盖我们的案例:
-
order_count < 50
-
order_count > 50
-
order_count = 50
-
order_count IS NULL
对于你的生产数据库,你可能需要扫描数百万或数十亿条记录进行测试,这可能会积累成本。你可以(也应该)拥有一个数据子集较小的开发环境,但这仍然可能留下比实际测试查询所需的记录更多的数据。
另一个主要问题是,即使是你的完整生产数据集也可能不包含所有合理的测试用例。特别是当你引入新的数据源,或者开发或运维团队更改生成数据的方式时,你可能会需要覆盖尚不存在于数据中的场景的测试!
最重要的收益通常是在维护阶段获得的。当你需要对现有功能进行更改时,一个稳健的单元测试套件将让你确信不会破坏你的查询。如果你的更改破坏了一个单元测试,你就知道你需要做工作:要么调整你的查询以保持所需的功能,要么修改你的测试以反映新的逻辑。由于你使用的是非常小的模拟数据集,你可以在开发过程中几乎立即获得反馈。
是的,你仍然需要进行集成测试和审计,但通过良好的测试覆盖率,你可以在此阶段之前捕捉到许多错误,从而节省时间和计算成本。
为了说明这些想法,这里有一个场景。
假设我在处理一个特别复杂的查询,这个查询在生产数据集上执行需要 30 分钟和 10 美元。在开发过程中,我必须执行这个查询 5 次,每次进行小调整,每次花费 1 美元和 2 分钟,同时我的审计查询完成(注意,如果我手动审计结果,这一步可能会更昂贵)。作为一名*均年薪为 12 万美元的美国数据工程师,我的时间成本约为 57 美元/小时(不考虑税收、福利等)。我的开发成本为 171 美元的劳动成本和 55 美元的计算成本,总计 226 美元。
实现单元测试以覆盖相同的查询可能需要我一到两个小时,因为这真的很复杂。但现在我的前四次查询基本上是免费的;由于我们处理的记录数量有限,执行只需几秒钟,而我仅需为最终验证负责:$11 和 30 分钟的时间($28.50),总共为 $39.50。加上单元测试的开销,我们总共花费 $153.50,节省了大约 33%。
还有很多反驳的观点。如果查询本身只花费几分钱并且执行时间仅需几秒钟呢?如果我只需进行一个微不足道的更改并在一次操作中完成工作呢?如果我在一个削减了成本一半或更多的缩减开发环境中工作呢?
你的具体使用案例可能决定了你能从单元测试中获得多少价值,但我认为无论你的情况如何,你都需要考虑你的测试策略和你所做的权衡。测试会产生成本,但对开发高质量产品至关重要;在数据世界中,这一点与软件世界没有不同。
那我们的 pytest 等价物在哪里?
回答标题:没有一个。希望我已经让你相信,单元测试可以以非常少的数据给我们所需的覆盖范围。但当然,也有陷阱。在 SQL 世界中,什么是“单元”?对于像 Python 或 Java 这样的语言,单元测试通常在方法或类级别进行;正如名称所示,这些是需要测试的最基本的功能“单元”。
在 SQL 中,一个单元是整个查询吗?是单个 CTE 吗?还是传达某种意义的一组 CTE?后者为单元测试解决方案增加了复杂性,因为 SQL 并不像通用语言那样容易组合;你不能像调用方法那样“调用”一个 CTE。良好的数据建模可以通过使查询更具模块化来帮助减少这种负担,但良好的测试设计仍然具有挑战性,而且可用的最佳实践很少。
对于 SQL 单元测试,有许多现成的选项,但目前还没有标准。有一个很棒的 dbt 讨论帖 详细介绍了这些选项的优点和潜在方法,但不幸的是,这还没有成为路线图的一部分。让我们回顾一些更流行的选项。
tSQLt
tSQLt 是一个开源项目,由 SQL Server 开发领域的知名公司 Redgate 维护。像许多 SQL Server 解决方案一样,tSQLt 实现为一系列存储过程,作为数据库对象添加到你的目标数据库中。
从 教程 中,这里是一个存储过程的测试,模拟作为变量处理:
EXEC tSQLt.NewTestClass 'testFinancialApp';
GO
CREATE PROCEDURE testFinancialApp.[test that ConvertCurrency converts using given conversion rate]
AS
BEGIN
DECLARE @actual MONEY;
DECLARE @rate DECIMAL(10,4); SET @rate = 1.2;
DECLARE @amount MONEY; SET @amount = 2.00;
SELECT @actual = FinancialApp.ConvertCurrency(@rate, @amount);
DECLARE @expected MONEY; SET @expected = 2.4; --(rate * amount)
EXEC tSQLt.AssertEquals @expected, @actual;
END;
GO
tSQLt 还提供了模拟表和视图的功能,以及断言各种数据库对象的相等、不等和存在性。这个工具特别适合应用数据库开发,因为这正是 SQL Server 的主要用例之一。你当然可以将其应用于分析(而且 SQL Server 实现了许多数据仓库),但显然关注的是不同类型的开发。
dbt-unit-testing
dbt-unit-testing 是一个由 Equal Experts 维护的 dbt 软件包,Equal Experts 是一家全球技术咨询公司。其方法是创建利用软件包宏的自定义测试,然后运行它们。在这里,模拟数据可以用查询风格或 CSV 风格定义,一些巧妙的 Jinja 宏处理将模拟数据替换为实际的表引用。根据他们的文档:
{{ config(tags=['unit-test']) }}
{% call dbt_unit_testing.test('customers', 'should sum order values to calculate customer_lifetime_value') %}
{% call dbt_unit_testing.mock_ref ('stg_customers') %}
select 1 as customer_id, '' as first_name, '' as last_name
{% endcall %}
{% call dbt_unit_testing.mock_ref ('stg_orders') %}
select 1001 as order_id, 1 as customer_id, null as order_date
UNION ALL
select 1002 as order_id, 1 as customer_id, null as order_date
{% endcall %}
{% call dbt_unit_testing.mock_ref ('stg_payments') %}
select 1001 as order_id, 10 as amount
UNION ALL
select 1002 as order_id, 10 as amount
{% endcall %}
{% call dbt_unit_testing.expect() %}
select 1 as customer_id, 20 as customer_lifetime_value
{% endcall %}
{% endcall %}
一旦你建立了测试,只需运行 dbt test,你就会得到格式化的输出(也来自文档):
MODEL: customers
TEST: should sum order values to calculate customer_lifetime_value
Rows mismatch:
| diff | count | customer_id | customer_lifetime_value |
| ---- | ----- | ----------- | ----------------------- |
| + | 1 | 1 | 20 |
| - | 1 | 1 | 30 |
除了手动创建模拟数据之外,这个软件包还支持通过直接查询你的数据仓库来推断列。我个人的经验是,在使这一功能正常工作时遇到了困难,但对于宽表来说,这一功能可能非常有用,因为否则你必须列举每一列。
dbt-datamocktool
dbt-datamocktool 是另一个 dbt 软件包,它采用了稍微不同的方法。模拟数据和期望值被创建为种子文件,然后测试被定义为 schema.yml 中软件包现有测试的输入。
models:
- name: stg_customers
tests:
- dbt_datamocktool.unit_test:
input_mapping:
source('jaffle_shop', 'raw_customers'): ref('dmt__raw_customers_1')
expected_output: ref('dmt__expected_stg_customers_1')
depends_on:
- ref('raw_customers')
columns: ...
- name: stg_orders
tests:
- dbt_datamocktool.unit_test:
input_mapping:
ref('raw_orders'): ref('dmt__raw_orders_1')
expected_output: ref('dmt__expected_stg_orders_1')
columns: ...
除了提供模拟数据之外,你还会将模型的来源和引用映射到适当的模拟数据上。这种方法更加动手操作,但在项目需求方面也更加轻量。与 dbt-unit-testing 不同,你不必用软件包特定版本覆盖 ref() 和 source() 宏。大多数功能来自 dbt 的原生组件:测试和种子。
另一个令人兴奋的功能是对支持 MERGE 操作的适配器的增量模型的支持。这在 dbt 领域尤为重要,因为增量物化根据 dbt 调用的“模式”运行不同的查询;你需要能够测试查询的两个“版本”以实现全面覆盖。
SQLMesh
新 comer 进入 SQL 建模/模板空间,SQLMesh 带来了大量的好东西,其中之一是原生单元测试支持。测试在 YAML 文件中定义,然后在按需执行或重建 SQLMesh 计划时执行。
模拟数据非常简单,模拟数据和期望值都定义在同一个文件中。我没有见过的一个杀手级功能是能够测试单个 CTEs:
test_example_full_model:
model: sqlmesh_example.example_full_model
inputs:
sqlmesh_example.example_incremental_model:
rows:
- id: 1
item_id: 1
ds: '2020-01-01'
- id: 2
item_id: 1
ds: '2020-01-02'
- id: 3
item_id: 2
ds: '2020-01-03'
outputs:
ctes:
filtered_orders_cte:
rows:
- id: 1
item_id: 1
- id: 2
item_id: 1
query:
rows:
- item_id: 1
num_orders: 2
正如我们在 dbt-unit-testing 中看到的,测试输出的可视化效果很好:
$ sqlmesh test
F
======================================================================
FAIL: test_example_full_model (/Users/izeigerman/github/tmp/tests/test_suite.yaml:1)
----------------------------------------------------------------------
AssertionError: Data differs
- {'item_id': 1, 'num_orders': 3}
? ^
+ {'item_id': 1, 'num_orders': 2}
? ^
----------------------------------------------------------------------
Ran 1 test in 0.008s
FAILED (failures=1)
对我来说,这是我见过的最有说服力的 SQL 单元测试选项;然而,复杂之处在于它是一个不是 dbt 的 SQL 框架。这是一个处于早期阶段的工具,面临着一个事实上的行业标准,在功能缺口和采纳惯性(即,它必须足够好才能让团队转而使用)之间,它的未来充满不确定性。
尽管如此,还是要阅读文档,了解为什么即使单元测试框架很令人印象深刻,这也不是 SQLMesh 最好的特性。
我们已经准备好了吗?
不。归根结底,现有的解决方案在功能上都非常接*,并且都存在类似的缺陷。所有工具或多或少做着相同的事情:使用模拟输入运行查询,并将查询输出与预期进行比较。在构建测试场景和生成输出的方式上,有些工具比其他工具更复杂,但差异相对较小。
我能看到三个采纳障碍:
-
行业尚未达成一个标准。尽管 dbt 取得了显著成功,但它也存在着重要的缺陷(列级血缘追踪、单元测试、可扩展性);采纳度推动了更多的采纳。没有任何单元测试解决方案达到了临界规模。
-
文化尚未成熟。SQL 开发人员在没有单元测试的情况下已经成功地解决了几十年的问题。尽管分析工程师的兴起将越来越多的软件工程最佳实践带入了 SQL 开发,但并未涵盖所有最佳实践,单元测试就是一个缺口。
-
数据模拟仍然是一个未解决的问题。在所有记录的例子中,我们看到的是少数几列和少数几张表之间的简单关系。实际查询可能复杂得多。开发人员感受到许多、甚至数十个连接条件以及为了使测试通过所需的引用完整性的负担。在某种程度上,这可以被视为一种特性:即开发人员必须考虑每个连接和每条记录。但从实际角度来看,这是一项繁重的任务,特别是当真正能用的数据就在眼前时,尤其令人心痒。
我希望关于行业中测试、质量和标准重要性的对话越来越多,这将推动我们朝着更成熟的实践和更好的成果前进。当我们能够像开发其他软件一样安全迅速地开发查询时,我们将释放出大量的价值。
《将你的 Python 项目迁移到 R 的入门指南》
R 教程
在 R 中探索电动车趋势
·
关注 发表在 数据科学前沿 · 11 分钟阅读 · 2023 年 6 月 2 日
--
照片由 Milad Fakurian 提供,来源于 Unsplash
你是否对深入了解 R 编程感到好奇?虽然 Python 在数据科学社区中仍然是主要选择,2022 年约有 60% 的开发者使用它¹,但有时 R 也会出现。这是因为 R 针对统计和数据进行了优化。如果你像我一样有 Python 基础,但现在遇到了需要 R 技能的职位列表和公司内部任务,这篇文章旨在为你提供帮助。我们将探索 Python 和 R 之间的基本区别,并将项目整合到数据清理和可视化教程中,以确保顺利过渡到 R。
注:如果你对绿色技术和电动汽车特别感兴趣,教程中包括一些有趣的视觉图像,展示了电动和混合动力汽车在加拿大的受欢迎程度,所以可以跳到教程部分,亲自查看这些视觉效果和相关分析!
R 的简要概述
R 是一种开源编程语言,主要用于统计建模和数据可视化领域。R 最初由统计学家 Robert Gentleman 和 Ross Ihaka 于 1993 年开发,旨在处理统计分析和数据转换任务。它仍然保持着以统计为重点的程序声誉。然而,得益于超过 18,000 个软件包的大量库,R 多年来也发展到了支持数据科学以外的广泛项目和应用领域。
在设置和应用方面,R 通常在 RStudio 环境中使用,该环境既免费又易于安装。你可以在这里找到安装指南。现在我们已经介绍了一些初步的说明,让我们进入从 Python 到 R 的速查表过渡指南。
探索 Python 和 R 之间的主要区别
虽然不可能通过一张图来捕捉 Python 和 R 之间所有的细微差别,但下图提供了两种编程语言之间关键差异的良好初步概述:

Python 到 R 过渡指南 — 作者提供的图像
请注意,这张图并不详尽,也没有涵盖 Python 和 R 之间的所有区别。有关更详细和全面的分解,量身定制你的特定项目,MIT 提供了一个很好的转换资源这里。
总结 Python 和 R 之间的关键区别,让我们重点突出几个关键项目:
-
语法:Python 采用更直接和简洁的语法,而 R 的语法往往涉及更多的括号、方括号和符号。这可能使得 R 代码最初看起来更复杂,但我们将在教程中进一步探讨这一概念。
-
数据操作:Python 更多地依赖于像 NumPy 和 pandas 这样的外部库进行复杂的数据操作任务。相比之下,R 通常提供专门为数据操作设计的内置函数和功能。
我们可以通过实际探索来了解 Python 和 R 的对比方面,从而更全面地理解这些差异。让我们转到教程部分,我们将进行一些简单的数据清洗和转换,并使用数据来探索这些可视化效果。
我们的 R 包简介
在开始之前,让我们熟悉一下我们将使用的 R 包:
-
**tidyverse**: 这个包是按照整洁数据的原则(正如其名字所示)创建的,包含许多基本的包。其中,dplyr因其在数据操作和转换方面的能力而受到欢迎,而ggplot2则提供了强大的工具套件用于数据可视化。 -
**sqldf**:这个 R 包允许您在 R 数据框上执行 SQL 查询,为在 R 环境中应用 SQL 语法进行数据操作和分析提供了更方便的方式。
教程:加拿大的电动车许可证

在本教程中,我们将重点研究加拿大联邦政府推出的零排放车辆激励计划(iZEV)后轻型和零排放车辆的受欢迎程度。这是一个国家性的计划,为购买电动车辆(包括插电混合动力车辆)的加拿大人提供财政回扣。幸运的是,我们可以访问加拿大政府的数据,涵盖了该计划自 2019 年起至 2023 年 3 月的数据。
分析将分为两个部分:数据加载和清洗,它对 Python 和 R 进行了一些轻微的比较,然后是数据分析。首先,让我们概述一些我们希望通过我们的可视化回答的问题,重点是理解随时间推移受欢迎程度的变化:
-
多年来,根据 iZEV 计划注册的车辆数量如何发展?
-
自该计划实施以来,汽车制造商品牌偏好发生了哪些变化?
-
哪些车型的受欢迎程度增长或减少最为显著?
为了保持教程简洁,并专注于 Python 到 R 的过渡,分析将保持相对广泛,但我们将展示最终生成的数据可视化结果,以完成对加拿大迄今为止注册的 iZEV 许可证情况的全面了解。如需更深入的分析,包括完整的 R 代码、额外的 Markdown 和可视化效果,以及数据集的链接,请参阅我的 GitHub 仓库这里。
数据加载和清洗
首先,我们将安装和加载前面提到的 R 包,可以使用以下代码:
接下来,让我们将包加载到我们的 R 环境中:
为了加载我们的数据,我们将使用<-操作符为变量分配值,这与 Python 中通常使用的=操作符不同。之后,我们可以快速使用dim函数获取我们加载的数据集的行数和列数,这相当于 Python 中的numpy.shape()函数。
在 R 中,你可以使用head()函数探索数据框的前几行。这类似于 Python pandas 库中的df.head()函数。以下是如何在 R 中实现这一操作的示例:
%>%操作符有助于链式操作。在上面的代码中,它让 R 知道要取df数据框然后显示前 5 行,你将得到如下输出:

在获得数据框和形状的初始概述之后,我们可以立即看到一些无关紧要的列,这些列可以删除。同时,我们可以修改一些冗长的列名,以便以后更容易地进行参考。
所有这些步骤都可以在 Python 中使用drop()、rename()和map()函数复制。
数据清洗过程的下一步通常涉及删除空值和重复行,但对于这个特定的数据集,我们只会删除空值,因为我们有许多重复行,但在没有唯一行标识符(如许可证 ID)的情况下,如果我们删除重复项,则可能会丢失宝贵的数据,因此我们需要相信行输入是正确的。以下是如何在 R 中删除空值的方法:
Python 中删除空值的等效函数可以使用dropna()调用。
这最后一步是为了准备关于车辆品牌和型号受欢迎程度的最后一个问题,我们可以使Vehicle_Make_and_Model列中的车型命名约定一致。例如,我们将考虑‘Hyundai Ioniq PHEV’与‘Hyundai Ioniq 插电混合动力’相同。我们可以通过创建列表,并使用str_replace_all函数来实现这一点。
很好,数据清洗就这样进行了一些简单的步骤,现在进入有趣的部分,分析!
数据分析
下面的可视化内容将探讨我们在开始时列出的三个问题,并解释ggplot与matplotlib的区别。让我们先来看看我们的第一个问题:
iZEV 计划下注册的车辆数量随年份的变化如何?
在这里,我们需要可视化注册年份和许可证数量,可以使用 R 的dplyr包编辑clean_df数据框,使其处于适当的格式中。数据集的每一行都计为一个车辆条目,因此需要使用summarise(total=n())来获取总行数:
在绘制数据时,R 的ggplot和 Python 的绘图库之间存在一些差异。在ggplot中,使用层叠语法,通过+运算符添加不同的组件。
让我们来比较一下你可能用matplotlib和 Python 绘制这个图表的方法:
总体而言,两者之间的代码长度相似,但 R 中的代码看起来更为简洁,使用了+运算符。现在,让我们展示这个可视化:

数据截至到 2023 年 3 月
我还包含了第二个图表,展示了各省 iZEV 受益者的分布情况(完整的 R 代码可以在我的 GitHub 仓库这里找到):

数据截至到 2023 年 3 月
观察
那么我们从这两个可视化中能看到什么?我们可以看到,从 2019 年的 33,611 个许可证到 2022 年的 57,564 个许可证,在 iZEV 计划下注册的零排放车辆总数有所增加,支持了加拿大向电动车的过渡。注意:电动车市场在整体乘用车注册中占比很小,约为 5%⁴。
按省份划分,我们看到魁北克省占据了许可证的最大份额,超过了 BC 省、安大略省和其他省份的总和,这可能部分归因于魁北克省提供的额外最高$8,000 的折扣,而联邦政府计划则提供相应的补贴(相比之下,BC 省仅提供最高$3,000)。此外,对公用事业公司 Hydro-Québec 的明确要求,帮助省内的电动车充电基础设施的发展,缓解了司机对充电地点的担忧。
按省份划分,我们看到魁北克省占据了许可证的最大份额,超过了 BC 省、安大略省和其他省份的总和,这可能部分归因于魁北克省提供的额外最高$8,000 的折扣,而联邦政府计划则提供相应的补贴(相比之下,BC 省仅提供最高$3,000)。此外,对公用事业公司 Hydro-Québec 的明确要求,帮助省内的电动车充电基础设施的发展,缓解了司机对充电地点的担忧。
2. 自计划实施以来,汽车制造商品牌偏好发生了什么变化?
为了回答我们的第二个问题,我们想要研究汽车制造商的受欢迎程度变化。与第一个问题中关注绝对总数不同,我们将探讨相对变化。通过分析比例,我们可以在类似的尺度上比较不同汽车制造商的表现,从而进行更有意义的比较。
现在请耐心一点,因为在我们到达下一个可视化之前,有相当多的 R 代码,但我们最终要实现的是按品牌显示的子图,展示年复一年比例变化。到目前为止我们拥有的数据仅代表绝对计数,所以我们需要计算‘每 1,000 辆车’来进行比例缩放。
首先,我们将创建一个显示车辆品牌按年份和计数的表格,我们可以使用sqldf R 包来实现。
接下来,我们希望将每年拆分到自己的行中,在这里我们可以使用pivot_wider函数(与 Python 中的pivot函数相似)。
然后,我们需要计算每年的‘per_1K’许可证,这可以通过将每种品牌的车辆计数除以登记的总车辆数,并将其乘以 1,000 来完成。
现在我们想计算 2022 年和 2019 年的差异,只关注按比例变化的完整年份。
下一步将计算出的年份和per_1K列重新透视成一个长的透视表,以帮助准备这些数据用于图表。之后,我们将绝对计数和per_1K计数合并成一个长的透视表。
query_vehicle_counts已经包含了我们所需的一切,所以我们只需将这两个数据集连接起来:
最后,我们希望按年份对每辆车进行排名,这样我们可以重用sqldf并利用窗口函数轻松完成。
最后,为了绘制车辆比例如何随时间变化,我们可以使用子图(在ggplot中称为facet_wrap,类似于 Python matplotlib库中的subplots)。
这是我们的可视化:

观察
上面的子图显示,当比较比例时,特斯拉拥有最大的汽车份额,占 2019–2022 年加拿大道路上每 1,000 辆电动汽车(EV)中的 300 辆。*年来,他们的市场份额有所下降,因为新进入的竞争者如奥迪、吉普、马自达和极星等已经进入加拿大市场。
3. 哪些车型经历了最显著的受欢迎程度增加和减少?
我们最后一组问题关注特定车型的受欢迎程度,我们可以检查 2019–2022 年之间购买车型比例的变化。这个分析的代码与我们之前使用的代码非常相似,只需将Vehicle_Make替换为Vehicle_Make_and Model(有关更详细的逐步指南,请参阅我的 GitHub 链接这里)。
让我们编写这些代码并探索为什么我们可能会看到这些模式:

观察
根据上面的图表,我们可以看到现代 IONIQ 5 的受欢迎程度最大增长,在 2022 年每 1,000 个许可证中比 2019 年多了 83 个。从百分比上看,该模型在此期间增长了 8.3%。值得注意的是,大多数受欢迎程度上升的模型都是 SUV,如上面列出的前五名所示。这与北美市场的偏好一致,消费者期待更大的电动车选项,转向较小的轿车格式,如特斯拉 Model 3。
让我们继续进行模型减少的编码和绘图:

观察
尽管仍然在前五名最受欢迎汽车制造商中,丰田普锐斯 Prime 的受欢迎程度却出现了最大下降,从 2019 年到 2022 年每 1,000 个许可证减少了 114 个。这一下降可能是由于供应问题和价格上涨影响了受欢迎程度,但也因为它是插电式混合动力车型,iZEV 财政激励措施减少,你只能获得全额可用折扣的一半。
特斯拉 Model 3 的受欢迎程度有所下降,每 1,000 个许可证减少了 64 个许可证,但值得注意的是,当从绝对总数来看,特斯拉依然是市场上的主导汽车制造商。
结束语
总结来说,我们开展了一个小型数据分析项目,以探索 Python 和 R 之间的编码差异。希望这能让 R 变得更易接*,并为你们创造引人注目的视觉效果提供一些灵感!
作为最后的友好提醒,若要访问完整的 R 代码,请查看我的 GitHub 仓库 这里。祝编程愉快!
除非另有说明,所有图片均由作者提供。
参考文献
-
SlashData, 开发者国家状况, 2023 年第一季度
-
M.Omar, 101 数据科学与 Munira:入门 R 和 RStudio,2020 年 6 月 Medium
-
A.Amidi 和 S.Amidi,数据操作 R-Python 转换指南,MIT.edu
-
统计加拿大,汽车统计,2023 年 3 月
关于为什么你的 Instagram 帖子会如此少点赞的统计理论
一个计算难以计数事物的实用技巧——从 Instagram 影响力,到企鹅数量以及北伦敦的曼城球迷
·发布于 Towards Data Science ·6 min read·2023 年 12 月 28 日
--

我们都经历过……
带着希望的眼神,我们通过 iPhone 15 镜头框住了圣诞节的氛围,捕捉了晚餐桌上的每一刻,像艺术家一样执着,像诗人一样富有灵魂,每一张照片都是充满朋友的节日小插曲,火鸡的闪亮光泽和装饰品的闪烁。我们精心为每张照片配上振奋人心的标题,从我们思绪的丝线中编织而成,希望每一帖都能在 Instagram 繁忙的节日流量中引起共鸣。
尽管我们为向数字世界展示节日精神付出了如此多的努力,但我们的 Instagram 帖子仍仅获得 15 个点赞(如果算上 Facebook 上的交叉帖子,则为 20 个)

图片来源:Gerd Altmann 来自 Pixabay
也许期望在仅有 300 个 Instagram 关注者的情况下出现社交媒体轰动是不现实的,但这些发自内心的圣诞节帖子肯定会让超过 5%的朋友做出反应,对吗?问题是,你的总关注者数与“真实观众规模”——那些活跃的并能看到你帖子的人——并不相同。你可能有很多关注者,但大多数人可能并不活跃,或者他们的关注图谱过于密集,以至于你的帖子实际上从未到达他们。
那么问题是,如何衡量你的真实社交媒体覆盖范围,你的互联网影响力?
林肯-彼得森指数——或者如何计算不可计数的事物
假设你在一个有 300 个关注者的私人账户上发布了一条 Instagram 帖子,并获得了 40 个点赞。你知道你的观众人数在 40 和 300 之间。如果你对自己制作 Instagram 帖子的能力有极大的信心,你可能会假设你的反应率为 100%,你的观众仅为 40 个用户,是 Instagram 限制了你的覆盖范围。
但也许你的照片看起来并不好,或者你可能没有你想象的那么机智。也许你确实达到了 300 个关注者,但你只能让其中 5%的人小心翼翼地给你虚拟的支持。单凭一篇帖子是无法知道的。但如果你有两篇帖子,即使你不知道每篇帖子有多吸引人,你也可以得到一个不错的估计。
假设你的第二篇帖子有 60 个点赞,而有 15 个互相点赞(喜欢这两篇帖子的人)。我们可以天真的利用老式的集合交集理论来计算真实的观众规模,但这是一种非常保守的估计,因为我们可能忽略了那些不喜欢这两篇 Instagram 帖子的用户。

图片由作者提供

幸运的是,有一种巧妙的方法叫做林肯指数,它允许我们估算总观众人数。设n1, n2分别为第一篇和第二篇帖子的点赞数,而m为同时喜欢这两篇帖子的用户数:

根据林肯指数,我们的估计覆盖范围是 40 x 60/15 = 160。请注意,这比 85 的天真估计要大得多,这为 Instagram 免除了压缩我们社交影响力的责任。
它在数学上是如何工作的?——或者你可以随意跳过的部分
让我们重新框定问题:我们要从N人中选择n2人来点赞第二篇帖子,其中正好有m人来自n1个点赞第一篇帖子的用户。显然,这种选择的概率是:

我们可以使用最大似然法来找到估计值N。对于数学爱好者,这里是证明的概要:
- 诀窍是与其尝试找到最大化似然函数的N:

- 我们找到满足不等式的最大𝑁:

- 经过一些算术运算后,这显然等同于:

- 因此,最大化似然函数的估计值N_hat为:

一次模拟
我知道并不是每个人都喜欢数学证明,所以我将通过一个小模拟来说服你。
假设我们知道我们的社交媒体覆盖率为 100,并且我们有 2 个 Instagram 帖子,其点赞概率分别为p1和p2。我们可以模拟不同的p1和p2场景,观察每个帖子的点赞数量,然后重新应用朴素方法和林肯方法,看看它们与 100(真实值)的距离。
我们可以看到,在不同的p1和p2值下,林肯估计保持无偏(10,000 次模拟的*均值接*真实人口 100),而 95%的置信区间随着p1和p2接* 1 而缩小。这是有道理的,即如果我们确信我们帖子上的点赞率接* 100%,我们应该对我们的观众规模估计相当有信心。不幸的是,朴素估计大大低估了我们的社交覆盖率。

图片来源于作者
像往常一样,所有好的统计方法都有一些警告:
-
(1) 我们假设每个帖子之间,真实的受众规模不变。这对于普通的 Instagram 用户来说可能是对的,但如果你是一个拥有不断增长的粉丝基础的影响者,这可能不切实际。
-
(2) 我们还假设这两个帖子之间存在重叠的相互喜欢。我们的一些读者可能发现了我代码中的一个小漏洞:
尽管数学仍然有效,但在较小样本量下,估计变得相当粗略且有偏差。幸运的是,我们有一个很好的修改方法,称为查普曼估计,以解决这个问题:

那么这个计数方法有什么用呢?这种统计技术——估计任何一个人口规模,尤其是在不切实际地计数每个个体时,非常流行于生态学中,通常被称为标记-重捕方法。在科技公司中也常见到这种方法,例如在发布前估计视频游戏中的错误数量,或社交媒体*台中的政策违规内容数量。
我曾在一次面试中被挑战估计伦敦的曼城球迷数量。如果在特拉法加广场做一些抽样,采访人们并根据林肯估计进行数据分析,将会是一个有趣的周末活动。虽然我怀疑曼城球迷如此之少,你可能实际上可以数清楚他们!
如果你喜欢这篇文章,你可能也会喜欢我关于有趣的统计事实和经验法则的另一篇文章
-
优化生活的统计规则:林迪效应
-
三法则:计算尚未发生事件的概率
关于其他深入分析:
-
贝叶斯统计如何说服我去健身?
-
通过数据驱动的体育博彩策略赚取丰厚收益
-
疫情期间的爱情:一种概率论的约会方法
本项目的完整代码(包括模拟和图表)可以在我的Github中找到。
从 TensorFlow 转换到 PyTorch 的细微差别
确保成功的建议和技巧
·
关注 发表在 Towards Data Science · 5 分钟阅读 · 2023 年 2 月 7 日
--
图片由 Jan Böttinger 提供,来自 Unsplash
将模型检查点从一个框架转换到另一个框架可能是一个复杂的过程,如果你想保持相同的性能水*。
过去,我被要求评估 我的工作 在 MLPerf 推理基准套件 上的表现。MLPerf 是一个用于测量机器学习(ML)模型在部署场景中表现的行业基准。该基准提供了一个标准化框架,用于比较不同 ML 系统的性能,使其成为评估 ML 模型硬件性能的宝贵工具。在我的工作中,使用 MLPerf 推理基准套件中的模型和检查点使我能够呈现符合广泛接受的行业标准的结果,从而提高了读者对我工作的信心。然而,我的整个模拟框架建立在 PyTorch 之上,而我所需的模型权重则编码在 TensorFlow 中。
在这篇博文中,我想分享将 TensorFlow 模型迁移到 PyTorch 的过程,突显其中的细微差别。重要的是要注意,一般来说,任何 两个不同框架之间可能存在小差异;因此,在转换任何两个框架时,请考虑这里的提示。最终结果,即 MLPerf ResNet-50 和 MobileNet-v1 PyTorch 检查点,可以从我的 GitHub 仓库 获得。
使用 PB 文件
TensorFlow pb(protobuf)文件包含模型图的描述以及层权重和参数。因此,第一步是解析模型 pb 文件。MLPerf 检查点文件可以从 这里 下载。受到 这个 博文的启发,我使用了这个类来从 pb 文件中提取所有必要的数据:
一旦创建了 NeuralNetPB 类,它将包含所有的权重在其“weights”属性中。拥有权重张量后,就可以将它们分配到 PyTorch 模型中的适当层。
层映射
首先,重要的是要注意,我们没有模型定义的 TensorFlow Python 文件。尽管如此,由于我们使用的是知名的模型架构,这些层大体上是相似的。
提示 #1: 使用 Netron 来可视化你的模型图。这将有助于理解
pb文件中的模型结构,以及将 pb 文件中的层映射到 PyTorch。
现在,让我们开始映射。我将给出一些 MobileNet-v1 的例子:
-
MobilenetV1/Conv2d_0/weights(3-3-3-32) — 这是第一个卷积层。我们将其映射到model.0.1.weight。 -
MobilenetV1/Conv2d_0/BatchNorm/gamma(32) — 这是第一个批量归一化层。gamma 对应于 PyTorch 中的层权重,因此我们将其映射为model.0.2.weight。相同的 BatchNorm 层还包含beta、moving_mean和moving_variance字段,它们分别对应于 PyTorch 中的bias、running_mean和running_var。 -
MobilenetV1/Conv2d_1_depthwise/depthwise_weights(3, 3, 32, 1) — 这是第二个卷积层(或第一个深度卷积层),它映射到model.1.weight。
DNN 模型结构通常是重复的,所以一旦掌握了思路,你就可以在for循环中编写部分代码。
由于NeuralNetPB属性不是 PyTorch 张量,因此需要使用torch.FloatTensor(...)进行转换。
细节 #1: 排列。 TensorFlow 卷积层的权重张量排列方式不同。根据 TensorFlow 文档,权重张量的形状是
[H, W, IN_C, OUT_C],而 PyTorch 的权重张量形状是[OUT_C, IN_C, H, W]。因此,重新排列张量:torch.FloatTensor(...).permute(3, 2, 0, 1)。话虽如此,深度卷积应该用.permute(2, 3, 0, 1)进行重新排列。
完成后,你应该保存所有内容,使用torch.save(...)。
模型修改
完成前一部分后,你的 PyTorch 模型应该在层组成方面与 TensorFlow 参考模型匹配。然而,还有一些额外的细节需要注意。
细节 #2: 参数。 权重并不是唯一的参数。例如,批量归一化层包含一个
epsilon属性(用于数值稳定性)。epsilon的默认值在 TensorFlow 和 PyTorch 中是不同的。但即使它们相等,TensorFlow ResNet-50 模型也会修改 epsilon,所以要注意。另一个例子是 Dropout 层(如果存在的话)。细节 #3: 填充。 TensorFlow 中的填充参数包含一个 PyTorch 中不存在的选项:
SAME。SAME意味着输入特征图将被填充,使输出特征图(即卷积操作结果)的空间维度与输入维度相等。然而,如果填充是不对称的,会发生什么?TensorFlow 会在左侧还是右侧填充更多?TensorFlow 在右侧填充,而 PyTorch 在左侧填充。 同样的逻辑适用于垂直方向,即底部可能会有一行额外的零,而在 PyTorch 中,额外的行将出现在顶部。
你可以查看我稍微修改过的ResNet-50和MobileNet-v1模型,看看我如何相应地进行修改。
预处理
如果你想要复现与参考模型(在这个例子中是 MLPerf 模型)完全相同的结果,那么你必须复现完全相同的预处理阶段。处理 ImageNet 数据集时的常见预处理阶段包括(1)调整大小为 256;(2)中心裁剪为 224;(3)归一化为[0, 1]之间的值(即除以 255);(4)每个 RGB 通道的均值和标准差修正。然而,MLPerf 中的标准差没有归一化,偏差的归一化方式也不同(详见此处)。
此外,即使进行完全相同的预处理,我发现 MLPerf 中的插值算法与使用 PIL 的 PyTorch 实现方式不同。由于 MLPerf 预处理整个 ImageNet 验证集并将其保存为 NumPy 数组,我决定避免使用 PyTorch 预处理,直接加载 NumPy 数组。在这一点上,我也达到了与 MLPerf 报告完全相同的性能——ResNet-50 和 MobileNet-v1 的顶级 1 准确率分别为 76.456% 和 71.676%。
调试
所以你映射了权重,并且(你认为)已经根据参考模型微调了所有参数和预处理阶段。你开始推断却什么也没有发生——准确率为 0%。接下来怎么办?没有简单的答案,你必须进行调试。我所做的是逐层比较张量值。结果应该几乎相同(允许有一些浮点差异)。例如,使用 NeuralNetPB 测试函数 nn_pb.test(input, 'import/MobilenetV1/MobilenetV1/Conv2d_1_depthwise/Relu6:0') 可以得到其中一个 ReLU 结果。你应该已经从映射阶段知道了所有的层名称。
结论
要有耐心,这最为重要。将任何模型在框架之间迁移都需要注意所有细节。可能有一些工具可以帮助自动转换,但即使你成功使用了它们,它们也可能忽略了我在这里提到的微妙差异,从而无法创建一对一的副本,这意味着模型表现会有所不同——这就是我遇到的情况。
你可以在我的 GitHub 仓库中找到我的 MLPerf ResNet-50 和 MobileNet-v1 PyTorch 检查点和模型。
随时可以在LinkedIn上联系我,或者在 Medium 上关注我。
参考文献
[1] Shomron, Gil, and Uri Weiser. “Non-Blocking Simultaneous Multithreading: Embracing the Resiliency of Deep Neural Networks.” 国际微架构研讨会 (MICRO). IEEE, 2020.
[2] Reddi, Vijay Janapa, et al. “MLPerf Inference Benchmark.” 国际计算机架构研讨会 (ISCA). IEEE, 2020.
高维数据的惊人行为
探索高维数据的惊人世界:机遇与挑战
·
关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 12 月 15 日
--
摄影:Guillermo Ferla,来源于 Unsplash
著名物理学家理查德·费曼曾说过:“我可以肯定地说,没有人理解量子力学。”在他的采访 “与理查德·费曼一起想象的乐趣” 中,他谈到了原子和亚原子粒子级别的奇怪行为,并指出这些行为经常违背我们的常识。有趣的是,我们在高维数据级别也可以注意到类似的行为。它并不像量子力学那样,但在从低维到高维的过渡中,我们会发现类似的惊奇和美丽——混合着一些/很多挑战。
在本文及未来的文章中,我希望提供一些关于这个迷人主题的见解。我的目标是激发兴趣,并鼓励那些对高维数据世界不熟悉的人进行学习。
高维数据,或在数据分析和机器学习中提到的高维数据,通常指的是具有大量变量、特征或属性的数据集。每一个变量、特征或属性都代表了我们数据中的一个不同的“维度”。
首先,让我们检查一些基本的例子,这些例子突出了从低维空间到高维空间时出现的区别。
高维空间中的体积集中
首先,让我们探索高维空间中的体积集中概念。考虑在一个边长范围从 0 到 1 的超立方体内生成随机点。当维度增加时,这些点落在这个超立方体中间区域的可能性有多大?

图片由作者提供
在上面的图像中,假设 x 是一个小值,比如 0.1。我们旨在确定点随机落在这个中间区域(而不是边缘)时,随着维度的增加,概率是如何变化的。
- 一维空间(直线)
想象一个从 0 到 1 的线段。中间部分在 0.1 和 0.9 之间,随机点落在这里的机会就是这个中间段的长度除以总长度,即 0.8。
2. 二维空间(方形)
现在,设想一个边长范围从 0 到 1 的正方形。中间区域是一个边长从 0.1 到 0.9 的小正方形。概率计算涉及比较这个小正方形的面积与总面积,从而得到 0.64 的概率。
3. 三维空间(立方体)
对于一个每条边长为 1 的立方体,中间区域是一个边长从 0.1 到 0.9 的较小立方体。这里,概率是这个小立方体的体积除以总的体积,结果为 0.512。
4. 高维(超立方体)
在一个 n 维的超立方体中,随着维度的增加,中间区域的‘体积’会急剧缩小。例如,在 4 维中,概率是 0.4096;在 5 维中,它变为 0.32768;而在 10 维中,它降至大约 0.10737。
这个想法的概括从考虑边长为小距离 x 开始,如上图所示。对于一条线,点落在中间区域的概率是 1–2x。对于一个正方形,它是(1–2x)*(1–2x),因为一个点必须落在两个维度的中间。
这种模式在 n 维中继续,其中落在中间区域的概率为(1–2x)^n,在高维中变得非常小。
注意这里我们通过将每边长度简化为 1 来进行简化。
在超立方体内刻画一个超球体
为了进一步说明体积集中概念,我用 python 进行了一次简单的模拟,在模拟中我们在超立方体内刻画一个超球体,然后比较超球体与超立方体的体积比,随着维度的增加。
什么是超立方体?
想象一个正方形。现在,把它膨胀成一个立方体。这是从 2D 到 3D 的跳跃。现在,发挥想象力跳到第四维及更高维度——这就是超立方体的出现。超立方体本质上是扩展到更高维度的立方体。它是一个各边相等的形状,在我们的模拟中,我们考虑边长为 2 的超立方体。它的体积公式是什么?就是 2^n(2 的 n 次方)对于 n 维超立方体。
那么超球体呢?
超球体是球体的高维等效体,当你将 2D 圆扩展到 3D(形成一个球体),然后继续扩展到更高维度时,就会出现超球体。重点?它的体积计算并不那么简单。它涉及到π(是的,就是那个著名的 3.14159…)和 Gamma 函数,这类似于阶乘的“增强版”。简而言之,半径为 1 的超球体在n维空间中的体积是:

Gamma 函数Γ(n)将阶乘函数扩展到实数和复数。对于正整数n,Γ(n)=(n−1)!,对于非整数值,它是数值计算的。
要使用 python 计算这个比例,我们可以使用以下代码:
import math
import matplotlib.pyplot as plt
def hypersphere_volume(dim):
""" Calculate the volume of a hypersphere with radius 1 in 'dim' dimensions. """
return math.pi ** (dim / 2) / math.gamma(dim / 2 + 1)
def hypercube_volume(dim):
""" Calculate the volume of a hypercube with side length 2 in 'dim' dimensions. """
return 2 ** dim
# Number of dimensions to consider
max_dim = 20
# Lists to hold volumes and dimension values
dimensions = range(1, max_dim + 1)
sphere_volumes = [hypersphere_volume(dim) for dim in dimensions]
cube_volumes = [hypercube_volume(dim) for dim in dimensions]
ratios = [sphere_volumes[i] / cube_volumes[i] for i in range(max_dim)]
# Plotting the results
plt.figure(figsize=(10, 6))
plt.plot(dimensions, ratios, marker='o')
plt.xlabel('Number of Dimensions')
plt.ylabel('Ratio of Volumes (Hypersphere/Hypercube)')
plt.title('Volume Concentration in Higher Dimensions')
plt.grid(True)
plt.show()
上述代码的输出是以下图表:

作者提供的图片
我们可以清楚地看到,随着维度的增加,比例迅速下降,剩余的体积集中在超立方体的角落。
这些例子表明,在更高维度中,中间区域的体积成为总体积的一个越来越小的部分,突显了高维空间的反直觉特性。
问:这种体积集中现象对机器学习算法的性能有哪些影响?
论文和 DVD 实验
考虑这样一个实验:你试图通过一个有正方形孔的纸张塞入 DVD。起初,看似不可能,因为正方形的对角线比 DVD 的直径小。然而,折叠纸张可以让 DVD 通过。
纸张的折叠,一个小但有效的空间维度调整,是谜题的关键。在这个实验中可以找到一个有趣的类比,用于理解高维景观的复杂性。
当纸张首次展开时,它形成了一个二维*面。由于设定的尺寸,方孔似乎过于狭窄,无法让 DVD 通过。
这种假设情境与我们在三维环境中的日常体验一致,其中长度、宽度和高度是尺寸和距离的测量单位。但当我们开始折叠纸张时,我们增加了另一个维度。这个折叠动作完全改变了孔和 DVD 的空间连接。
在这个新的三维环境中,距离的概念在二维中是如此不灵活和明确,但在三维中变得更加灵活和不直观。纸张被折叠,这有效地改变了纸张边缘生成的角度和环绕孔的点之间的距离。
这种新三维形式中的孔可以容纳 DVD,展示了引入第三维度如何使在二维空间中看似绝望的任务变得可行。
Weiwei Lin 等人进行的一项引人入胜的研究中全面解释了这一实验所涉及的数学原理。
摘要。通过在纸上设置简单的折痕并沿这些折痕折叠,折纸可以形成各种形状…
你还可以观看由“The Action Lab”制作的这段美丽视频,它直观地展示了这个想法:
一个圆盘通过一个较小的方孔
这种视角的转变具有重要意义,尤其是在数学、物理学和机器学习领域。这个想法在像支持向量机(SVM)这样的机器学习方法中得到了体现。
支持向量机(SVM)及其核技巧
支持向量机(SVM)中的核技巧展示了类似的想法。在 SVM 中,我们经常遇到不可线性分离的数据。核技巧通过将数据转换到更高维空间来克服这个问题,类似于折叠纸张改变了其空间属性。(实际上,SVM 并不会将数据实际转换到更高维度,因为这在计算上代价昂贵。相反,它们使用核技巧计算数据点之间的关系,就好像它们处于更高维度一样。)
简而言之,SVM 通常在较低维度中找到一个分隔线(或超*面)。但对于非线性数据,这是不可能的。核技巧就像折叠纸张一样,添加了维度,使找到一个适合的超*面变得更加容易。
核心技巧不仅仅是改变维度,它还简化了复杂问题。这确实是一个很好的例子,说明高维思维如何为在低维度中看似不可能的问题提供解决方案。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
# Here I manullay entered a data that is not linearly seperable in 1D
x = np.array([1,2,3,4,5,6,7,8,9,11,12,13,14,15,16,17,18,19,20,21,23,24,25,26,27,28,29,30]).reshape(-1, 1) # Replace YOUR_X_VALUES with your data
y = np.array([1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1]) # Replace YOUR_Y_VALUES with your class labels
# Non-linear transformation to 2D, (squaring)
def transform_to_2d(X):
return np.c_[X, X**2]
# Transforming data to 2D
X_transformed = transform_to_2d(x)
# Fitting SVM with a linear kernel in the transformed 2D space
svm = SVC(kernel='linear')
svm.fit(X_transformed, y)
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 1D data plot
axes[0].scatter(x, np.zeros_like(x), c=y, cmap='bwr', edgecolors='k')
axes[0].set_title('Original 1D Data')
axes[0].set_xlabel('Feature')
axes[0].set_yticks([])
# 2D transformed data plot
axes[1].scatter(X_transformed[:, 0], X_transformed[:, 1], c=y, cmap='bwr', edgecolors='k')
axes[1].set_title('Transformed 2D Data')
axes[1].set_xlabel('Original Feature')
axes[1].set_ylabel('Transformed Feature (X²)')
# Plotting the decision boundary in 2D
ax = axes[1]
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
# Getting the separating hyperplane
Z = svm.decision_function(xy).reshape(XX.shape)
# Plotting decision boundary and margins
ax.contour(XX, YY, Z, colors='k', levels=[-1, 0, 1], alpha=0.5,
linestyles=['--', '-', '--'])
plt.tight_layout()
plt.show()
上述代码的结果是以下图表:

作者提供的图片
很明显,最初在左侧数据是非线性可分的,但在 2D 中变得可分。正如右侧图表所示,这种转换有效地解决了我们的问题。这不是很神奇吗?
总结
在这篇文章中,我们探讨了有关高维数据世界的一些想法。我们展示了进入高维度如何极大地改变我们的视角和处理问题的方法,从体积集中开始,接着是纸张和 DVD 实验的实际例子,最后是 SVM 中的核心技巧。
在接下来的文章中,我们将讨论“维度诅咒”,即在高维空间中导航所面临的困难和复杂性。我们将探讨这如何影响机器学习和数据分析,以及缓解其影响的策略。
感谢你阅读到这里!非常感谢你抽出时间阅读这篇文章,希望你觉得这个话题很有趣。如果有任何建议或可能的修改,请随时分享!
合成数据领域指南
原文:
towardsdatascience.com/the-synthetic-data-field-guide-f1fc59e2d178
使数据有用
一份关于各种伪数据的指南
·发表于 Towards Data Science ·6 分钟阅读·2023 年 6 月 30 日
--
如果你想获取一些 数据,你有哪些选择?这里有一个尽可能粗略的答案:你可以获取真实数据,也可以获取伪数据。
在 我之前的文章 中,我们认识了合成数据的概念,并讨论了创建它的思维过程。我们比较了真实数据、噪声数据和手工制作的数据。让我们深入了解比让人类选择一个数字更高级的合成数据类型……
一部 经典 的英国滑稽喜剧。
(注意:本文中的链接指向同一作者的解释文章。)
复制数据
也许你测量了 10,000 个真实的人体身高,但你想要 20,000 个数据点。一种方法是假设你现有的数据集已经很好地代表了你的总体。 (假设 总是危险的,需谨慎进行。) 然后,你可以简单地重复数据集或使用古老的复制-粘贴方法重复其中一部分。瞧!更多数据!但这是否是 好 和 有用 的数据呢?这始终取决于你需要它做什么。在大多数情况下,答案是否定的。不过,既然你有头脑,就有理由用它来思考并运用你的最佳判断。
重新采样数据
说到仅重复数据的某一部分,有一种方法可以注入一点随机性来帮助你确定 哪个 部分需要选择。你可以使用 随机数生成器 来帮助你从现有的身高列表中选择哪个身高。你可以进行“无替换”操作,这意味着你最多只复制每个现有的身高一次,但……
自助抽样数据
你会更常见到人们“有放回地”进行操作,这意味着每次你随机选择一个高度进行复制时,你会立即忘记你做过这件事,因此相同的高度可以在你的数据集中作为第二个、第三个、第四个等副本出现。如果评论中有足够的兴趣,我会解释为什么这是一个强大且有效的技术(是的,一开始听起来像是巫术,我也这样认为)用于群体推断。
增强数据
增强数据听起来可能很高级,确实有高级的方法来增强数据,但通常当你看到这个术语时,它意味着你对重新采样的数据添加了一些随机噪声。换句话说,你生成了一个来自统计分布的随机数,并且通常你只是将它加到重新采样的数据点上。就这么简单。这就是增强。

所有图像版权属于作者。
过采样数据
说到只重复数据的一部分,有一种方法可以有意地增强某些特征。也许你在一次典型的 AI 会议上进行了测量,因此女性身高在你的数据中被低估(如今这确实很可悲)。这就是所谓的数据不*衡问题。对于重新*衡这些特征的表现,有一些技术,比如SMOTE(合成少数类过采样技术),这几乎就是它的字面意思。解决这个问题最简单的方法是仅限于少数类数据点进行重新采样,忽略其他数据。因此,在我们的例子中,你只需重新采样女性身高,同时忽略其他数据。你还可以考虑更复杂的增强方法,仍然将努力限定在女性身高上。
如果你想变得更高级,你可以查找像ADASYN(自适应合成采样)这样的技术,并跟踪那些超出本话题快速介绍范围的线索。
边缘情况数据
你也可以制造出完全不同于你(或任何人)见过的数据(手工制作的数据)。如果你打算用这些数据来创建现实世界的模型,那将是非常愚蠢的做法,但如果你用它来测试系统处理奇怪情况的能力,那就很聪明了。为了判断你的模型/理论/系统在遇到异常值时是否会崩溃,你可以故意制造合成的异常值。大胆尝试,比如设置一个 3 米的高度,看看会发生什么。就像工作中的消防演习一样。(测试完成后,请不要在建筑物内留下真正的火灾或数据集中的实际怪物异常值。)
视频中你的作者解释了什么是异常值,以及如何(和如何不)处理它。
模拟数据
一旦你对根据你的规格生成数据的想法感到舒适,你可能会想进一步创建一个配方来描述你希望在数据集中出现的数据的基本性质。如果有一个随机成分,那么你实际上是在模拟一个统计分布,这个分布允许你指定核心原则,如模型所描述(这只是一个花哨的说法,即“你将用作配方的公式”),并包含随机部分的规则。与其像普通的数据增强技术那样向现有的数据点添加随机噪声,你可以向你自己提出的一套规则添加噪声,规则可以通过沉思或通过对相关数据集进行统计推断得出。了解更多内容,请点击这里。

所有图片版权归作者所有。
超越单一数字
身高?等等,你是让我提供一个只有一个身高的数据集?真无聊!真是… 磁盘时代的产物。我们称之为单变量数据,而且这种数据在如今很少见。
现在我们拥有了令人难以置信的存储容量,数据可以以更有趣和复杂的形式出现。在我们记录身高时,额外记录一些特征是非常便宜的。例如,我们可以记录每个人的发型,使我们的数据集变得双变量。但为什么止步于此呢?为什么不再记录年龄,让数据变成多变量呢?真有趣!
但现在,我们可以大胆地将其与图像数据(在测量身高时拍照)和文本数据(他们写的关于其统计课有多么无聊的论文)结合起来。我们称之为多模态数据,我们也可以合成这些数据!如果你想了解更多,请在评论中告诉我。
为什么有人会想生成合成数据?有好的理由来喜欢它,也有一些坚实的理由要像瘟疫一样避免它(文章即将发布),但如果你是数据科学专业人士,请前往这篇文章了解我认为应该是你常用的理由。
感谢阅读!怎么样,来上一门课程吧?
如果你在这里玩得开心,并且正在寻找一个以激发 AI 初学者和专家兴趣为目标的有趣领导力课程,这是我为你准备的一点东西:

课程链接: bit.ly/funaicourse
阅读 Cassie Kozyrkov 的每个故事(以及 Medium 上其他成千上万的作者)。你的会员费直接支持…
附言:你试过在 Medium 上点击鼓掌按钮多次看看会发生什么吗? ❤️
喜欢作者?与 Cassie Kozyrkov 联系
成为朋友吧!你可以在 Twitter、YouTube、Substack 和 LinkedIn 找到我。对让我在你的活动中发言感兴趣?使用 这个表单 与我联系。
评估新语言模型的三种基本方法
原文:
towardsdatascience.com/the-three-essential-methods-to-evaluate-a-new-language-model-aa5c526bacfd
如何检查最新、最热门的大型语言模型(LLM)是否符合你的需求
·发布于数据科学之道 ·6 分钟阅读·2023 年 7 月 3 日
--

图片来源(使用 Stable Diffusion)
这是什么?
每周都会发布新的 LLM,如果你像我一样,可能会问自己:这个模型是否终于适合我想要利用 LLM 的所有用例?在这个教程中,我将分享我用来评估新 LLM 的技术。我将介绍我定期使用的三种技术——它们没有新的(实际上,我会提到我之前写的博客文章),但通过将它们结合在一起,每当有新的 LLM 发布时,我可以节省大量时间。我将展示在新的OpenChat模型上进行测试的示例。
为什么这很重要?
对于新的 LLM,了解其能力和限制是很重要的。不幸的是,弄清楚如何部署模型并系统地测试它可能会有些麻烦。这个过程通常是手动的,并且可能消耗大量时间。然而,通过标准化的方法,我们可以更快地迭代,并迅速确定一个模型是否值得投入更多时间,还是应该放弃它。所以,让我们开始吧。
开始使用
利用 LLM 的方式有很多,但当我们提炼出最常见的用途时,它们通常涉及开放性任务(例如,为营销广告生成文本)、聊天机器人应用和检索增强生成(RAG)。相应地,我会使用相关方法来测试这些能力。
0. 部署模型
在开始评估之前,我们首先需要部署模型。我已经准备好了一些模板代码,我们只需更换模型 ID 和要部署的实例(在这个示例中,我使用的是 Amazon SageMaker 进行模型托管),就可以开始了:
import json
import sagemaker
import boto3
from sagemaker.huggingface import HuggingFaceModel, get_huggingface_llm_image_uri
try:
role = sagemaker.get_execution_role()
except ValueError:
iam = boto3.client('iam')
role = iam.get_role(RoleName='sagemaker_execution_role')['Role']['Arn']
model_id = "openchat/openchat_8192"
instance_type = "ml.g5.12xlarge" # 4 x 24GB VRAM
number_of_gpu = 4
health_check_timeout = 600 # how much time do we allow for model download
# Hub Model configuration. https://huggingface.co/models
hub = {
'HF_MODEL_ID': model_id,
'SM_NUM_GPUS': json.dumps(number_of_gpu),
'MAX_INPUT_LENGTH': json.dumps(7000), # Max length of input text
'MAX_TOTAL_TOKENS': json.dumps(8192), # Max length of the generation (including input text)
}
# create Hugging Face Model Class
huggingface_model = HuggingFaceModel(
image_uri=get_huggingface_llm_image_uri("huggingface",version="0.8.2"),
env=hub,
role=role,
)
model_name = hf_model_id.split("/")[-1].replace(".", "-")
endpoint_name = model_name.replace("_", "-")
# deploy model to SageMaker Inference
predictor = huggingface_model.deploy(
initial_instance_count=1,
instance_type=instance_type,
container_startup_health_check_timeout=health_check_timeout,
endpoint_name=endpoint_name,
)
# send request
predictor.predict({
"inputs": "Hi, my name is Heiko.",
})
值得注意的是,我们可以利用新的Hugging Face LLM Inference Container for SageMaker,因为新的 OpenChat 模型基于 LLAMA 架构,该架构在这个容器中受支持。
1. 游乐场
使用笔记本测试一些提示可能很繁琐,也可能会让非技术用户不愿尝试模型。更有效的方式是构建一个游乐场。我之前在这篇博客文章中详细说明了如何轻松创建这样一个游乐场。利用那篇博客文章中的代码,我们可以快速搭建一个游乐场。
一旦建立了游乐场,我们可以引入一些提示来评估模型的回应。我更喜欢使用开放式提示,即提出一个需要一定常识来回答的问题:
我如何提高我的时间管理技能?

作者提供的图片
如果苏伊士运河从未被建造会怎样?

作者提供的图片
两种回应都很有前景,这表明投资额外的时间和资源来测试 OpenChat 模型可能是值得的。
2. 聊天机器人
我们想要探索的第二件事是模型的聊天机器人能力。与游乐场不同,在游乐场中模型始终是无状态的,我们希望了解其在对话中“记住”上下文的能力。在这篇博客文章中,我描述了如何使用 Falcon 模型设置聊天机器人。这是一个简单的即插即用操作,通过更改 SageMaker 端点,我们可以将其指向新的 OpenChat 模型。
让我们看看它的表现:

作者提供的图片
作为聊天机器人的表现相当令人印象深刻。然而,有一次 Openchat 试图突然结束对话,半句话被切断了。实际上,这种情况并不罕见。我们通常不会在其他聊天机器人中观察到这种情况,因为它们使用特定的停止词来迫使 AI 停止文本生成。这种问题在我的应用程序中发生的原因可能是由于我的应用程序中实施了停止词。
除此之外,OpenChat 具有在对话中保持上下文的能力,并且能够从文档中提取关键信息。令人印象深刻。😊
3. 检索增强生成(RAG)
我们想测试的最后一个任务涉及使用 LangChain 进行一些 RAG 任务。我发现 RAG 任务对于开源模型来说可能相当具有挑战性,通常需要我编写自己的提示和自定义响应解析器来实现功能。然而,我希望看到的是一个在标准 RAG 任务中“开箱即用”的模型。这个博客文章提供了一些这样的任务示例。让我们看看它的表现如何。我们要提出的问题是:
谁是英国的首相?他或她出生在哪里?他们的出生地距离伦敦有多远?

作者提供的图片
毋庸置疑,这是我见过的使用 LangChain 标准提示的开源模型中表现最好的。这可能并不令人惊讶,因为 OpenChat 已经在 ChatGPT 对话上进行了微调,而 LangChain 则针对 OpenAI 模型,特别是 ChatGPT。然而,该模型能够准确地使用其可用的工具检索所有三个事实。唯一的缺点是,最后模型未能认识到它已经掌握了所有必要的信息并能够回答用户的问题。理想情况下,它应该说:“我现在有了最终答案,”并向用户提供其收集到的事实。

作者提供的图片
结论
在这篇博客文章中,我向你介绍了我经常使用的三种标准评估技术来评估 LLMs。我们观察到,新版的 OpenChat 模型在所有这些任务中表现都非常出色。令人惊讶的是,它作为 RAG 应用的底层 LLM 非常有前景,可能只需定制化提示来确定何时达到了最终答案。
值得注意的是,这不是一个全面的评估,也不打算成为全面评估。相反,它提供了一个关于特定模型是否值得投入更多时间进行进一步、更深入测试的指示。看来 OpenChat 绝对值得花时间研究 🤗
随意使用所有工具,扩展和定制它们,并在几分钟内开始评估你感兴趣的 LLMs。
海科·霍茨
👋 在Medium和LinkedIn上关注我,以了解更多关于生成式人工智能、机器学习和自然语言处理的内容。
👥 如果你在伦敦,可以加入我们的NLP London Meetups。

作者提供的图片
三大数据架构趋势(以及 LLMs 将如何影响它们)
拥抱数据架构的下一个时代:揭示三大趋势及 LLM 的影响力
·发布于Towards Data Science ·5 分钟阅读·2023 年 6 月 28 日
--

图片来源:Google DeepMind 在Unsplash
我去年发布了一篇关于数据架构趋势的文章。
这是在大型语言模型(LLMs)成为热门并影响大多数行业之前。Gartner 报告称,“风险投资公司在过去三年中已向生成式 AI 解决方案投资超过 17 亿美元。” 毫无疑问,LLMs 将影响数据架构的大多数领域。
记住这一点,让我们探讨三种架构趋势以及 LLMs 将如何影响它们。
1. 使用副驾驶进行成本优化
我非常喜欢能够帮助最终用户高效完成任务的副驾驶。
作为Grammarly的常规用户,我非常欣赏它在加快任何书面内容编辑过程中的帮助。同样,副驾驶将成为我们大多数工作的主要角色,包括数据架构。
数据架构师的日常工作包括数据模型设计、制定标准和实施治理结构。像微软这样的协助工具可以帮助完成邮件中的句子,并根据规范文档创建公告。类似地,数据架构师的协助工具可以仅基于用户需求,通过理解你的设计限制来完成实体关系图(ERD)。协助工具可以与架构师一起工作,帮助加速他们的日常工作流程。
如果生产力开始飞速增长,企业开始寻找优化成本的方式也就不足为奇了。一些估计表明,成千上万,甚至数百万的工作将受到影响。
例如,管理顾问一直在通过寻找效率来帮助组织进行重组和减少开销。同样,协助工具的实施将会减少对人力资源的依赖,因为更多的任务会依赖于人工智能的完成。任务包括撰写设计文档、按照批准的模式创建数据架构图、创建数据模型及相关的 SQL 查询、审计 SQL 以符合批准的标准等。
协助工具将带来效率和成本节约!
2. 背景驱动的分析
我们可能已经解决了云存储问题,但我们仍需要解决背景问题。
数据本身只是一系列文本/数字;当你为其添加背景时,价值才会显现。而“数据背景”是一个数十亿美元的行业。
数据背景包括业务或技术元数据、治理或隐私需求,以及可访问性或安全性要求。尽管预计到 2028 年这一行业将翻一番,但我想知道这一增长有多少会被大型语言模型(LLMs)所占据。例如,使用语义嵌入和向量数据库,组织将能够快速对数据进行背景化,而无需实施大量的数据背景工具。如果我能通过嵌入检测异常,是否还需要一个全面的治理框架?这进一步强调了由于 LLMs 而进行成本优化的第 1 点。
将 AI(故意的双关语!)嵌入数据管道、转换和血统中,可以帮助建立上下文。这个上下文可以用于回答最终用户的分析或监管需求问题。例如,这些数据是否包含个人信息?如果包含,则从特定的分析用例中过滤掉。

图片由作者提供
这张图片说明了上下文层如何捕获信息,就像传统的数据目录一样,只不过它利用了 LLMs 的强大功能,大幅减少了人工干预。
上下文使数据有价值;使用 LLMs 可以更快地实现这一点。
3. 启动数据架构生态系统
我们厌倦了孤立且分散的架构。
在这种架构中,治理工具未能与数据湖集成,源系统的设计未考虑分析需求,或者存在多个信息源。
生态系统需要反映像苹果这样的消费公司所提供的产品。一个关键产品与各种支持的可组合产品,这些产品单独有用,但共同创建了一个令人惊叹的生态系统。例如,一个数据产品市场(iPhone)展示了来自数据可观察性框架(Watch)的信息,并由单一访问方法(Face ID)进行治理。数据架构将位于一个集成不再是弱点的生态系统中。这将是一个颠覆性的改变。
生态系统还将减少不同来源之间的信息冗余风险(就像你的 iMessages 在所有设备间同步)。已有一些初创公司正利用 OBT(One Big Table)等概念来进行革命性的改进。生态系统还意味着数据定义;标准被设定一次并传播到每个区域,降低了复制成本。
例如,一个客户交易表格捕获了来自 CRM 系统的信息;默认情况下,CRM 系统设计用于捕获分析所需的强制字段[1]。一旦数据传输,它会经过一系列的数据质量检查,以确保其适用性[2]。数据转换后,回溯信息会被捕获,以确保数据没有丢失[3]。在使用之前,它会被分类到个人数据桶中,并设置适当的治理措施[4]。所有这些过程本身都很重要;然而,当数据最终被产品化时,它们会显得更为强大,你可以在数据集中可视化[1] — [4],进而信任这些数据。
结论
好像现代数据堆栈已经有了自己的炒作,现在我们还得应对 GenAI 的炒作。看看这些趋势在接下来的 12 到 18 个月如何发展将会很有趣。我预计已经在基础设施上进行投资的公司将会利用这些趋势,而那些没有投资数据质量或治理的公司将会持续滞后。
所有这些趋势的根本要求是良好的数据。没有良好的数据,你无法进行副驾驶、添加背景或拥有有效的数据架构。这是最难以实现的事情之一,但因此也带来了最大的投资回报。
你想发现自己在组织中的数据和人工智能能力如何,以及哪些方面可以改进吗?通过参加这个免费的数据精通指数(DMI)评估来了解。 点击这里访问评分卡评估,提升你的数据策略。

或者,扫描二维码以访问评估






























浙公网安备 33010602011771号