通过使用端到端机器学习平台加速构建机器学习模型
原文:
towardsdatascience.com/accelerate-building-ml-models-by-using-an-end-to-end-ml-platform-a4f2fe5c05b1
指导性教程:构建文本分类器
·发表于Towards Data Science ·阅读时间 8 分钟·2023 年 3 月 21 日
–
介绍
作为数据科学家,我们熟悉用于构建机器学习算法的标准 Python 库。你很可能已经使用过 pandas、NumPy、scikit-learn、TensorFlow 或 PyTorch 来构建模型,然后使用 Docker、Kubernetes 以及大型云服务商来部署和服务这些模型。这些都是优秀的工具,提供了很多灵活性,但将所有这些组件连接起来往往并不那么简单。最终,从初步数据收集到构建模型,再到最终在生产环境中服务,往往需要几周时间。
机器学习平台的创建旨在简化这一过程,允许用户使用相同的工具在更短的时间内完成所有必要的步骤。这提高了生产力,允许快速原型设计,并最终可以减少开发机器学习解决方案所需的预算。
在这篇文章中,我想介绍如何使用一个新的机器学习平台构建文本分类模型,并且你可以在不到一个小时的时间里完成这一过程,而无需编写代码。你将通过从目录中选择一个预训练模型来进行建模,不需要 GPU 或自己的资源来训练或部署模型。这种方法比从头开始构建自己的解决方案要快得多,非常适合快速原型设计。
让我们开始吧。
问题陈述——文本分类
在本教程中,我们将构建一个文本分类器,以区分三个类别:政治、健康和娱乐。为了训练模型,我们将使用相对较小的样本,共 298 个对象,分布如下:
-
政治类 142 个实例
-
健康类 80 个实例
-
娱乐类 76 个实例。
这些条目是新闻类别数据集的一部分,该数据集由 Rishabh Misra 在 创作共用 4.0 国际 (CC BY 4.0) 许可证下发布。你可以从 这里 下载本教程所需的样本。
选择基础模型
作为我们分类器的基础,我们将使用在 Toloka 机器学习平台上提供的预训练多语言大规模变换器模型。你可以通过这个 链接 注册该工具的免费版本。之后,你将可以访问平台 模型/集合 部分中的各种预训练机器学习模型。
预训练模型预览 — 作者图像
两个模型适合作为我们最终模型的基础:多语言大规模变换器 和 多类别文本分类。这两个模型都由大型预训练变换器驱动,能够让你创建新的自然语言处理模型,而无需投入标注大量数据集、进行大量实验或设置 GPU 云。
在本教程中,我们将使用 多语言大规模变换器,但你可以随意尝试 多类别文本分类 并比较结果。
你可以通过在 提示 中输入文本并检查模型的响应来尝试其推理。记住,这些是通用模型的结果,我们仍然需要为我们的任务对其进行微调。
作者截图
多语言大规模变换器 根据你的需求有两个微调选项。第一个是针对文本生成的微调。在这种情况下,你只需提供将用于任务的文本,模型将能够生成类似于你提供的数据的文本。
第二个选项是对文本分类模型进行微调,对于训练数据,你需要提供文本和标签的示例。我们将使用这个选项,因为我们的任务是分类新闻。要继续微调,只需点击模型部分的Tune Classifier tab选项卡,如下图所示。
作者截图
在这之后,你将被要求使用现有的数据集进行微调或创建一个新的数据集。如果你之前没有使用过这个平台,你需要选择后者,因为你还没有上传任何数据集。只需按照下面截图中的“创建你的数据集”链接进行操作。
作者截图
创建数据集
要在平台上创建数据集,你需要上传一个包含训练分类器所需实例的CSV 文件。你还需要为其指定名称和简短描述。
作者截图
一旦你创建了数据集,你应该能看到其概览信息,如下图所示:
作者截图
数据集是有版本的,第一版的名称总是带有 0.0 扩展名。它对应于你上传的原始数据。
你可以通过点击数据集版本旁边的“标记此版本”按钮来注释数据集(如上图所示)。在数据标注的第一步中,你将被询问你拥有的数据类型。我们正在处理文本,因此选择此选项,并选择应作为文本和标签使用的列。
作者截图
在下一步中,你将被要求定义标签。我们在创建数据集时提供的 CSV 文件已经指定了三种不同的类别:POLITICS、WELLNESS 和 ENTERTAINMENT,这些应该会被平台自动识别。
作者截图
我们可以直接进入下一步,因为我们只会使用这些类别。
现在你将被要求标注每个单独的实例。我们在 CSV 文件中已经提供了带有标签的列,因此我们应该已经能够看到每个条目的标签。
作者截图
这是你可以检查并修正标签的时间,如果需要,或者如果你的训练数据没有标签,你可以在这里添加它们。
这是标注部分的最后一步,所以完成后你应该会有数据集的版本 0.1。这个标注版本将用于微调模型。
微调现有模型
现在你的数据集已经准备好,你可以返回到Models/Collections部分,选择Multilingual Large Transformer模型,并继续到“调优分类器”部分。
系统会要求你填写有关用于微调的训练数据的信息。我们希望使用之前步骤中创建的标记版本的数据集,请相应填写详细信息。
作者截图
在这一步,你还需要为模型提供一个名称和简短的描述。
完成后,按下“运行场景”按钮,微调将会触发。完成后,你将能够打开模型并使用提示进行测试。
测试模型
让我们在文本上运行模型推断。你可以在模型提示中输入这个短语:
“美国总统上周访问了德国。”
作者截图
它被分类为政治!
你刚刚微调了一个大型语言变换模型,该模型旨在预测序列中的下一个单词。现在它可以将新闻分类为三个类别:政治、健康和娱乐。
此外,你通过使用仅 300 个训练样本,并且不到一个小时的时间完成了这一目标。你的模型完美吗?可能不,仍需要进一步改进,但令人惊讶的是,即使使用未标记的数据集,你也能如此迅速地原型化和测试新的机器学习想法。
如果你现在想使用我们的模型,你可以随时通过平台的Models/Collections部分访问它。
进一步改进
你在本教程中使用的ML platform目前处于 Beta 版本,并且不断开发新的功能。下一步的功能将包括实验跟踪、模型比较功能以及指标可视化工具。
有经验的机器学习从业者如果寻找更高级的功能并通过代码与平台互动,也不会感到失望。用于实现这一点的 Python 库即将推出。
总结
在这篇文章中,你已经学习了如何使用Toloka ML platform快速构建一个简单的文本分类模型。我鼓励你根据自己的需求调整这个示例,创建独特的文本分类器或情感分析模型。
此外,你还可以尝试针对其他数据类型(如图像或音频)设计的模型。
玩得开心,并评论你在平台上喜欢的地方以及哪些部分可以改进。
PS:我在 Medium 上撰写了以简单易懂的方式解释基本数据科学概念的文章, 关于数据的博客。你可以订阅我的 邮件列表 ,每次我发布新文章时都会通知你。如果你还不是 Medium 会员,你可以 在这里加入。
下面是一些你可能会喜欢的其他帖子:
实际案例研究,包含你可以运行的代码
towardsdatascience.com ## Jupyter Notebook 中的前 8 个神奇命令
通过学习最有用的命令提升你的生产力
towardsdatascience.com ## 如何进行数据标注、版本管理和 ML 管理
关于丰富食品数据集的案例研究
towardsdatascience.com ## Jupyter Notebook 自动补全
如果你还没有使用,这将是你应当使用的数据科学生产力工具…
towardsdatascience.com
使用 Graphcore IPU 和 PopSparse 库加速块稀疏矩阵乘法
使用新库 PopSparse 加速稀疏矩阵乘法
·
关注 发表在 Towards Data Science ·10 分钟阅读·2023 年 4 月 12 日
–
作者:李智怡,AI 工程师,道格拉斯·奥尔,研究科学家,瓦勒留·奥汉,软件工程师,以及 多米尼克·马斯特斯,研究科学家
图片由作者提供。
鉴于深度学习模型规模的显著增长以及人工智能在广泛应用中的普及,减少运行这些模型的计算成本的激励比以往任何时候都更大。
一种显示出显著前景的减少这些成本的方法是使用稀疏性。稀疏性通常指通过剪枝过程在模型权重中引入尽可能多的零,然后在实际计算时跳过这些值。
尽管在利用稀疏性减少理论成本指标(如 FLOPs 和参数计数)方面取得了很大成功,但在保持可接受任务性能的同时实现实际速度提升要困难得多,特别是在使用低精度数字格式的通用加速器(如 GPU)上。
为了在实践中实现这些理论上的好处,并进一步研究在 Graphcore IPUs 上的稀疏技术,我们推出了 PopSparse,这是一种通过利用 IPUs 的独特硬件特性以及数据中定义的任何块结构来实现快速稀疏操作的库。我们针对两种不同类型的稀疏性:静态稀疏性,即稀疏模式在编译时固定;和动态稀疏性,即每次运行模型时可以改变。我们在 IPU 上对这两种模式的稀疏稠密矩阵乘法进行了基准测试。结果表明,PopSparse 的实现比 IPU 上的稠密矩阵乘法在各种稀疏水平、大矩阵尺寸和块尺寸下都更快。此外,静态稀疏性通常优于动态稀疏性。虽然以前在 GPU 上的研究仅在非常高的稀疏性(通常为 99%及以上)下显示出加速效果,但我们展示了 Graphcore 的静态稀疏实现比在较低稀疏性(约 90%)下的等效稠密计算表现更好。
这些结果已在线发布在arXiv上,并被2023 年 ICLR Sparse Neural Network 工作坊接受。
Graphcore IPU 用于深度神经网络中的稀疏稠密矩阵乘法
深度学习中的稀疏性概念最常指的是通过稀疏化模型权重来减少相关的存储和计算成本。通常,这通过在训练结束时的单次剪枝步骤(Zhu & Gupta, 2017)或通过某些稀疏训练机制(Evci et al., 2019)来实现。这些方法通常在保持可接受的准确度水平的同时,实现了模型权重减少 90–99%的效果(Hoefler et al., 2021)。
尽管模型大小的好处很容易实现,但提高计算效率通常更为困难,因为产生的无结构稀疏模式与高度并行的深度学习加速器中的矢量化指令集不太匹配(Qin 等人,2022)。因此,提高稀疏计算效率的一种方法是对稀疏模式施加一定程度的结构。这促使了广泛的结构化剪枝技术,如神经元(Ebrahimi & Klabjan,2021)、通道级(He 等人,2017)、块(Gray 等人,2017)、2:4(Mishra 等人,2021)。然而,这些方法通常会比等效的无结构方法带来性能惩罚。
在这里,我们介绍 PopSparse,这是一个用于 Graphcore IPU 上稀疏稠密矩阵乘法(SpMM)有效加速的库。IPU 拥有多个架构特性,有助于加速稀疏操作:
-
大型、高带宽的片上 SRAM 可提高通信密集型、低算术效率操作的性能。
-
细粒度 MIMD 处理模型将工作划分到 1472 个独立计算单元(瓦片)上,支持高度的并行性。
-
提前编译模型,当稀疏性结构在编译时已知时,可以采用进一步优化。
PopSparse 库中的 SpMM 在 IPU 上
SpMM 可以写作:
其中 ⊙ 表示元素逐一相乘,* 表示内积,Y ∈ Rᵐ ˣⁿ,X ∈ Rᵏ ˣⁿ 分别是稠密的输出和输入矩阵。稀疏权重矩阵 (M⊙W) 通过 M ∈ Bᵐ ˣᵏ (B = {0,1}) 定义,这是一种表示稀疏性模式的掩码,来自 M̂ ∈ B⁽ᵐᐟᵇ⁾ˣ⁽ᵏᐟᵇ⁾,块掩码和 W ∈ Rᵐ ˣᵏ 定义权重值。
在这种表述中,(M⊙W) 具有块稀疏结构,其中形状为 b × b 的连续方形权重块包含所有非零元素,给定块大小 b。维度 m 和 k 分别称为输出和输入特征大小,而 n 被称为批量大小,对应于其在权重稀疏神经网络计算中的典型角色。
我们定义密度,d = ∑ᵢⱼMᵢⱼ/(m⋅k),其中标准术语稀疏度对应于 (1 — d)。我们使用浮点运算(FLOP)计数来量化 SpMM 中有用的算术工作量为:2⋅m⋅k⋅n⋅d。注意,这只计算非零值上的操作,并不依赖于块大小 b。PopSparse 库支持无结构(块大小 b = 1)或有结构的稀疏性(b = 4, 8, 16)。
静态稀疏性
在静态稀疏性中,编译时,稀疏模式和矩阵形状对分区器是已知的,分区器将稀疏矩阵(具体值尚未确定)的非零元素在 k 维度上划分为 qₖ 个分区,将密集矩阵在 n 维度上划分为 qₙ 个分区。图 1a 说明了静态稀疏情况中稀疏矩阵的分配、计算和减少。k 维度上的分区不需要均匀大小,而是选择以确保非零元素(图中用彩色小块表示)的平衡分布。模型编译后,在执行期间提供 W 的具体非零值。由于稀疏模式已知,无需额外的数据交换。然后执行与密集矩阵的局部点积计算,并在块间进行最终的归约,以给出密集输出矩阵。
动态稀疏性
在动态稀疏性中,稀疏操作数的非零元素的最大数量是固定的,而稀疏模式在执行期间会更新。为了最小化计算周期,我们在编译时使用规划工具来优化分区,同时适应执行过程中可以定义的所有稀疏模式。由于每次运行时稀疏模式可能会改变,与静态稀疏性相比,分区器需要额外的步骤来分配稀疏矩阵索引和非零值。这在图 1b 中得到了演示。此外,由于在 m 和 k 维度上需要固定大小的分区,因此每个分区中的非零元素可能不平衡。在图 1b 中,第二个 k 维度分区中的 B 元素无法适应 Tile1\。三个 B 元素溢出到 Tile2\。然而,Tile2 并没有包含所有与 B 元素对应的分区信息(m, k, n 切片),因此无法计算结果。因此,需要额外的传播步骤来完成计算。
总体而言,我们的动态稀疏实现相比于静态稀疏情况有几个额外的成本:
-
编译后的额外控制流会带来一定的成本开销。
-
我们必须针对最大的块间通信量进行编译,因此平均效率较低。
-
当数据不平衡时,需要额外的传播和计算阶段来重新平衡数据。所需的步骤数量取决于不平衡的程度。
图 1:输入特征维度 k 的静态和动态稀疏分区示意图。图像由作者提供。
基准测试
我们的微基准测试实验探索了 IPU 和 GPU 上的单一稀疏-密集矩阵乘法 SpMM: Y=(M⊙*W)**X。IPU 实验和 GPU 基线使用的 API 如表 1 所示。基准测试的参数范围详见表 2。
表 1:基准测试 IPU 和 GPU 时使用的 API。
IPU:该操作在 Bow-2000 机箱中的单个 Bow IPU 上执行一次,使用随机生成的稀疏模式和数值。我们提取周期计数信息,并在 1.85 GHz 的固定时钟下将这些周期计数转换为 TFLOP/s 值。主机传输不计入测量时间。
GPU:GPU 上的基线稀疏实现由 cuSPARSE 库 v11.6.2(NVIDIA, 2022)提供。这些实现根据使用的稀疏格式进行分类,不区分静态和动态稀疏模式。我们考虑压缩稀疏行(CSR)和块稀疏行(BSR)分别用于非结构化和块稀疏。我们生成随机稀疏模式和数值,复制到设备上,执行 25 次操作迭代,并将结果复制到主机进行验证。我们在 5 次迭代后开始计时,并使用 cudaEventRecord 测量壁钟时间。所有实验都在一个 A100 GPU 上进行,该 GPU 运行在 4 x A100-SXM4–40G 机箱中。
表 2:基准测试中扫取的参数范围。图像作者提供。
本研究的关键结果如图 2 所示。这些图表显示了计算 FLOP/s(对数尺度)与密度的关系,特征大小固定,每条线代表相同操作的不同实现。由于零在 FLOP/s 计算中不被计算,密集实现随着密度的增加呈线性扩展。而在稀疏实现中,完美的扩展应预测 FLOP/s 在密度变化时保持不变。
图 2:IPU FP16 和 GPU 块稀疏矩阵乘法性能在不同块大小 b 的密度变化下,平方特征大小 m = k = 4096,以最佳批量大小 n 为准。图像作者提供。
对于密集基线,虽然我们大致看到 IPU 和 GPU 在 FP16 上的芯片对芯片相等,但重要的是要强调所使用的 IPU 的价格大约是 云端价格的一半,突显了其在这些基本操作中的成本效益。此外,对于稀疏操作,我们看到使用 IPU 的好处更大。
具体而言,对于 IPU(图 2a),我们看到使用静态稀疏操作和块稀疏都能单独提高性能。例如,块大小为 16 的动态情况在所有考虑的密度下相较于密集基线显示出优势,而在低密度区域(<5%)中,非结构化(b = 1)静态稀疏优于密集稀疏。此外,当同时使用静态和块稀疏方法时,我们看到吞吐量相比密集基线提高了多达 5 倍。
对于 GPU(图 2b),我们看到稀疏操作的性能远不如预期,没有任何测试用例超过 FP16 密集基线。然而,我们发现 FP32 块稀疏(BSR)情况在密度降低时扩展非常好。看起来对于非常低的密度,这可能会超过密集基线。
比较两个平台,我们可以看到 IPU 相比于密集 GPU 基线在吞吐量上实现了高达 5 倍的收益,而在仅考虑稀疏操作时则提高了高达 25 倍的吞吐量。考虑到额外的价格优势,我们认为这为在 IPU 上调查稀疏应用提供了有力的理由。
然而需要注意的是,即使在 IPU 上,稀疏操作也仅在某些条件下超过密集基线。更多详细结果可以在 我们的论文 中找到,但作为大致指南,当以下条件满足时,稀疏性通常能在 IPU(FP16,两个模型的大批量)上带来比密集基线更快的速度:
-
静态稀疏性,块大小 b = 1,特征 (m = k) ≥ 4096,密度 d ≤ 1/32。
-
静态稀疏性,块大小 b ≥ 4:特征 (m = k) ≥ 4096,密度 d ≤ 1/8。
-
动态稀疏性,块大小 b ≥ 8,特征 (m = k) ≥ 4096,密度 d ≤ 1/32。
重要的是,这些范围在今天的权重稀疏训练和推理中有些难以使用。这主要是因为找到不会降低性能的块稀疏模式是一个困难的问题,并且在理论效率指标(每个非零 FLOP)上,无结构稀疏总是显得和块稀疏一样好或更好。我们希望这项工作表明,块稀疏是一种有前景的方法,可以实现稀疏模型的实际加速,并将激励研究社区对有效的块稀疏剪枝算法进行进一步的研究。
来 尝试我们的 PopSparse 操作 在 Paperspace 的 IPUs 上,然后为什么不试试将它们集成到自己的模型中呢?如果你正在积极从事稀疏性研究,并且希望获得 IPU 支持,请考虑注册我们的 学术计划,我们在这里支持研究人员利用 IPUs 实现卓越的成果。
自己尝试一下
尝试在 IPU 上使用 SpMM — 在 Paperspace 上运行。
参考文献
[1] Michael Zhu 和 Suyog Gupta. 剪枝,还是不剪枝:探索剪枝在模型压缩中的有效性,2017. 网址 arxiv.org/abs/1710.01878.
[2] Utku Evci, Trevor Gale, Jacob Menick, Pablo Samuel Castro 和 Erich Elsen. 改变彩票:让所有票据都成为赢家,2019. URL arxiv.org/abs/1911.11134.
[3] Torsten Hoefler, Dan Alistarh, Tal Ben-Nun, Nikoli Dryden 和 Alexandra Peste. 深度学习中的稀疏性:神经网络中的剪枝与增长以实现高效推理和训练,2021. URL arxiv.org/abs/2102.00554
.
[4] Eric Qin, Raveesh Garg, Abhimanyu Bambhaniya, Michael Pellauer, Angshuman Parashar, Sivasankaran Rajamanickam, Cong Hao 和 Tushar Krishna. 通过异质性实现稀疏张量加速的灵活性,2022. URL arxiv.org/abs/2201.08916.
[5] Abdolghani Ebrahimi 和 Diego Klabjan. 基于神经元的深度神经网络剪枝,通过克罗内克因子曲率近似实现更好的泛化,2021. URL arxiv.org/abs/2111.08577.
[6] Yihui He, Xiangyu Zhang 和 Jian Sun. 通过通道剪枝加速非常深的神经网络,2017. URL arxiv.org/abs/1707.06168.
[7] Scott Gray, Alec Radford 和 Diederik P. Kingma. 用于块稀疏权重的 GPU 内核。2017.
[8] Asit Mishra, Jorge Albericio Latorre, Jeff Pool, Darko Stosic, Dusan Stosic, Ganesh Venkatesh, Chong Yu 和 Paulius Micikevicius. 加速稀疏深度神经网络,2021. URL arxiv.org/abs/2104.08378
.
[9] NVIDIA. cuSPARSE 的 API 参考指南,2022. URL docs.nvidia.com/cuda/cusparse/
.
使用 FP8 加速 PyTorch 训练工作负载 — 第一部分
如何最大化利用现代 GPU
·
关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 11 月 15 日
–
图片来源于 Deva Darshan 在 Unsplash
过去几年在 AI 领域取得了革命性进展,最近最好的例证是基于 LLM 的应用程序(如ChatGPT)的普及和流行。这些突破是由用于训练 AI 模型的设备同样令人振奋的发展推动的。新颖而创新的架构、复杂的张量处理核心和专用的 HW 加速器使得 AI 模型的收敛速度越来越快,规模也越来越大。在这篇文章中,我们将专注于 AI 专用 HW 中的一项特定进展 —— 专用的 8 位浮点(FP8)张量处理核心的加入。出现在最现代的 AI HW 架构中(例如Nvidia Hopper、Nvidia Ada Lovelace和Habana Gaudi2),FP8 张量核心使得每秒浮点运算次数(FLOPS)显著增加,同时为 AI 训练和推断工作负载的内存优化和能耗节约提供了机会。
利用 HW 级别的 FP8 能力需要我们在构建 AI 训练和推断应用程序时,使用适当的 SW 栈和开发框架提供支持。在这篇文章中,我们将描述如何修改一个 PyTorch 训练脚本,以利用Nvidia H100 GPU的内置 FP8 数据类型支持。我们将从提供使用 FP8 数据类型的动机开始。接着,我们将审查由Transformer Engine库提供的 FP8 特定的 PyTorch API 支持,并展示如何将它们集成到一个简单的训练脚本中。虽然我们不会深入探讨 FP8 在 AI 训练中的理论背景,但我们将指出使用它可能涉及的潜在挑战。最后,我们将展示 FP8 数据类型的显著优化机会。
免责声明
请不要将我们提到的任何 SW 组件、方法或服务解读为对其使用的认可。ML 开发的最佳设计将根据您自己 AI 工作负载的具体细节而大相径庭。请还记住,我们将提及的一些 SW 包和组件的 API 和行为可能在您阅读本文时已经发生变化。强烈建议您根据最新的 HW 和 SW 来评估任何潜在的设计决策。
动机
随着 AI 模型越来越复杂,训练它们所需的硬件也变得越来越复杂。被称为支持“前所未有的性能和可扩展性”的 Nvidia H100 GPU 是(在撰写本文时)Nvidia 最新、最强大的 AI 加速器,专门设计用于支持下一代 AI 开发。随着当前 AI 热潮的全面展开,对这些 GPU 的需求非常巨大(例如,请见 这里)。因此,不出所料,这些 GPU 的成本非常高 — 对许多读者而言,甚至可能是令人望而却步的。幸运的是,AWS、GCP 和 Microsoft Azure 等云服务提供商提供了“按需付费”(按小时/按秒)使用 H100 驱动的机器的机会,从而使更广泛的 AI 开发者社区能够使用它们。
在 AWS 中,H100 GPU 作为 最近宣布的 AWS EC2 p5 实例系列的一部分提供。这些实例被宣称能够**“相比于前一代基于 GPU 的 EC2 实例,将解决问题的时间缩短最多 4 倍,并将训练 ML 模型的成本降低最多 40%”**。
在 最近的一篇文章 中,我们讨论了选择 ML 训练实例时应考虑的一些因素。我们强调了最优化的实例类型将非常依赖于具体项目。特别是在 ML 训练实例方面 — 更大并不总是更好。这一点在 p5 实例系列中尤为明显。确实,p5 实例可能会超越其他任何实例类型 — 毕竟,H100 是无可争议的性能怪兽。但一旦你考虑到 p5 的成本(在撰写本文时,8-GPU p5.48xlarge 实例的价格为每小时 $98.32),你可能会发现其他实例类型更为合适。
在下一节中,我们将会在 p5.48xlarge 上训练一个相对较大的计算机视觉模型,并将其性能与包含 8 个 Nvidia A100 GPU 的 p4d.24xlarge 进行比较。
玩具模型
在下面的代码块中,我们定义了一个以 Vision Transformer (ViT) 为基础的分类模型(使用流行的 timm Python 包版本 0.9.10)以及一个随机生成的数据集。ViT 骨干网有多种形态和尺寸。这里我们选择了通常称为 ViT-Huge 配置的模型 — 具有 632 百万参数 — 以更好地利用 H100 对大模型的容量。
import torch, time
import torch.optim
import torch.utils.data
import torch.distributed as dist
from torch.nn.parallel.distributed import DistributedDataParallel as DDP
import torch.multiprocessing as mp
# modify batch size according to GPU memory
batch_size = 64
from timm.models.vision_transformer import VisionTransformer
from torch.utils.data import Dataset
# use random data
class FakeDataset(Dataset):
def __len__(self):
return 1000000
def __getitem__(self, index):
rand_image = torch.randn([3, 224, 224], dtype=torch.float32)
label = torch.tensor(data=[index % 1000], dtype=torch.int64)
return rand_image, label
def mp_fn(local_rank, *args):
# configure process
dist.init_process_group("nccl",
rank=local_rank,
world_size=torch.cuda.device_count())
torch.cuda.set_device(local_rank)
device = torch.cuda.current_device()
# create dataset and dataloader
train_set = FakeDataset()
train_loader = torch.utils.data.DataLoader(
train_set, batch_size=batch_size,
num_workers=12, pin_memory=True)
# define ViT-Huge model
model = VisionTransformer(
embed_dim=1280,
depth=32,
num_heads=16,
).cuda(device)
model = DDP(model, device_ids=[local_rank])
# define loss and optimizer
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
model.train()
t0 = time.perf_counter()
summ = 0
count = 0
for step, data in enumerate(train_loader):
# copy data to GPU
inputs = data[0].to(device=device, non_blocking=True)
label = data[1].squeeze(-1).to(device=device, non_blocking=True)
# use mixed precision to take advantage of bfloat16 support
with torch.autocast(device_type='cuda', dtype=torch.bfloat16):
outputs = model(inputs)
loss = criterion(outputs, label)
optimizer.zero_grad(set_to_none=True)
loss.backward()
optimizer.step()
# capture step time
batch_time = time.perf_counter() - t0
if step > 10: # skip first steps
summ += batch_time
count += 1
t0 = time.perf_counter()
if step > 50:
break
print(f'average step time: {summ/count}')
if __name__ == '__main__':
mp.spawn(mp_fn,
args=(),
nprocs=torch.cuda.device_count(),
join=True)
我们在p5.48xlarge和p4d.24xlarge实例类型上训练了这个模型,使用的是专用的 PyTorch 2.1 AWS 深度学习容器(763104351884.dkr.ecr.us-east-1.amazonaws.com/pytorch-training:2.1.0-gpu-py310-cu121-ubuntu20.04-ec2)。
不出所料,p5 的每步时间性能远远超过 p4d 的性能 —— 每步 0.199 秒对比 0.41 秒 —— 快了两倍多!!这意味着训练大型 ML 模型的时间将减少一半。然而,当你考虑到成本差异(p4d 每小时 32.77 美元 vs p5 每小时 98.32 美元 —— 截至本篇文章撰写时),一个完全不同的故事展现在眼前。p5 的性价比比 p4d 差 ~30%!!这远未达到在p5 宣布中提到的 40% 改进。
此时你可能得出两种可能的结论。第一种可能是,尽管有很多宣传,p5 可能根本不是适合你的机器。第二种可能是 p5 可能仍然具有潜力,但需要对模型进行调整,以充分利用其潜力。在接下来的章节中,我们将采用第二种方法,演示如何使用 FP8 数据类型 —— p5 实例类型特有 —— 完全改变比较的性价比结果。
将 FP8 与 Transformer Engine 集成
我们应该强调的是,截至撰写本文时,PyTorch(版本 2.1)不包括本地的 8 位浮点数据类型。为了让我们的脚本使用 FP8,我们将使用Transformer Engine(TE),这是一个加速 NVIDIA GPU 上 Transformer 模型的专用库。TE(版本 0.12)已预装在 AWS PyTorch 2.1 DL 容器中。
尽管使用 FP8 进行训练的理论超出了本文的范围(例如,见这里),但重要的是要知道使用 FP8 的机制比16 位替代方案(float16 和 bfloat16)复杂得多。幸运的是,TE 实现隐藏了所有繁琐的细节。有关如何使用 TE API,请参阅官方文档以及这个简单的示例。要了解更多幕后情况,请务必查看以下两个视频教程。
[## 使用 Transformer Engine 进行 FP8 训练 | NVIDIA On-Demand
本次会话将包括 FP8 和混合精度介绍,Transformer Engine 特性概述以及…
www.nvidia.com](https://www.nvidia.com/en-us/on-demand/session/gtcspring23-s51393/?source=post_page-----5a5123aec7d7--------------------------------) [## 深度学习的 FP8 | NVIDIA On-Demand
FP8 是加速深度学习(DL)训练的自然进展,超越了现代常见的 16 位格式…
www.nvidia.com](https://www.nvidia.com/en-us/on-demand/session/gtcspring23-s52166/?source=post_page-----5a5123aec7d7--------------------------------)
要修改我们的模型以使用 TE,我们将 TE 的专用 Transformer 层包装在一个符合 timm 的block layer signature的自定义 Transformer 块类中。
import transformer_engine.pytorch as te
from transformer_engine.common import recipe
class TE_Block(te.transformer.TransformerLayer):
def __init__(
self,
dim,
num_heads,
mlp_ratio=4.,
qkv_bias=False,
qk_norm=False,
proj_drop=0.,
attn_drop=0.,
init_values=None,
drop_path=0.,
act_layer=None,
norm_layer=None,
mlp_layer=None
):
super().__init__(
hidden_size=dim,
ffn_hidden_size=int(dim * mlp_ratio),
num_attention_heads=num_heads,
hidden_dropout=proj_drop,
attention_dropout=attn_drop
)
接下来,我们修改 VisionTransformer 初始化以使用我们的自定义块层:
model = VisionTransformer(
embed_dim=1280,
depth=32,
num_heads=16,
block_fn=TE_Block
).cuda(device)
到目前为止,我们没有对 H100 做任何特定的更改 —— 相同的代码可以在我们的 A100-powered p4d 实例上运行。最后一个修改是将模型前向传递包装在te.fp8_autocast上下文管理器中。此更改需要支持 FP8 的 GPU:
with torch.autocast(device_type='cuda', dtype=torch.bfloat16):
with te.fp8_autocast(enabled=True):
outputs = model(inputs)
loss = criterion(outputs, label)
关于使用 FP8 的一些注意事项
使用 8 位浮点表示(而不是 16 位或 32 位表示)意味着较低的精度和较小的动态范围。这些因素可能会对模型的收敛速度和/或速度产生显著影响。尽管底层 TE FP8 实现旨在解决这一挑战,但不能保证适用于您的模型。您可能需要调整底层 FP8 机制(例如使用 TE recipe API)、调整一些超参数,和/或限制 FP8 在模型子部分的应用。您可能会发现,尽管尽了最大努力,您的模型仍然无法与 FP8 兼容。
结果
在下表中,我们总结了在 p4d.24xlarge 和 p5.48xlarge EC2 实例类型上的实验结果,包括使用和不使用 TE 库的情况。对于 p5.48xlarge 实验,我们增加了批量大小以增加 80 GB GPU 内存的利用率。使用 FP8 可以减少 GPU 内存消耗,从而进一步增加批量大小。
实验结果(作者提供)
我们可以看到,使用 TE transformer 块提高了 p4d(约 19%)和 p5(约 32%)实例类型的价格性能。使用 FP8 使 p5 的性能额外提升了约 20%。在 TE 和 FP8 优化后,基于 H100 的 p5.48large 的价格性能优于基于 A100 的 p4d.24large——尽管差距不算很大(约 2%)。考虑到训练速度提高了 3 倍,我们可以安全地得出结论,p5 将是训练我们优化模型的更佳实例类型。
请注意,价格性能的相对小幅提升(远低于 p5 公告中提到的 40%)使我们期待更多针对 H100 的特定优化……不过这些还需要等到另一篇文章中讨论 😃.
总结
在这篇文章中,我们展示了如何编写 PyTorch 训练脚本以使用 8 位浮点类型。我们进一步展示了使用 FP8 如何成为发挥现代 GPU(如 Nvidia H100)最佳性能的关键因素。重要的是,FP8 的可行性以及对训练性能的影响可能会因模型细节的不同而大相径庭。
本文继续了关于优化机器学习工作负载的长期系列出版物。务必查看我们关于这一重要主题的其他文章。
更新(2024 年 5 月 21 日):请查看我们的续篇,其中涵盖了新发布的 PyTorch 原生 FP8 数据类型。
使用 Python 访问和可视化数字高程模型
原文:
towardsdatascience.com/accessing-and-visualizing-digital-elevation-models-with-python-f4fd7f595d46
一个使用公开可用的 DEM 数据的 Python 教程
·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 3 月 5 日
–
图片由 Maria Teneva 提供,来自 Unsplash
这项工作由 Mahdi Fayazbakhsh 和 Kai Kaiser* 合著。所有错误和遗漏均由作者负责。*
数字高程模型 (DEMs) 代表地形的 3D 表面模型。它通过一系列单元格表示连续的地形高程表面,每个单元格表示其位置 (X 和 Y) 上特征的高程 (Z)。数字高程模型只包含地质特征的高程信息,如山谷、山脉和滑坡,不包括植物或建筑物等特征的高程数据。
精确的高分辨率 DEM 数据对于灾害制图非常重要,因为它提供了详细的地形表示,这对于评估自然灾害可能带来的风险至关重要。通过允许科学家测量温度、降水量以及其他气候相关因素对不同海拔土地表面的影响,这些数据可以更好地告知气候变化将如何影响各种土地表面。DEM 数据还可用于识别面临洪水、滑坡和其他极端天气事件风险的区域,这有助于制定减轻气候变化影响的政策决策。此外,DEM 数据有助于创建详细的地图,这些地图可以用于制定疏散计划、警报系统和其他风险管理策略。
美国地质调查局(USGS)EarthExplorer是全球数字高程模型(DEM)数据集最受欢迎的来源之一。USGS 提供从 10 米到 1 弧秒的分辨率数据集,更新间隔为 5 到 10 年。其他全球 DEM 数据集的来源包括哥白尼陆地监测服务、国际热带农业中心和全球土地覆盖设施。
本博客总结了如何使用 Python 下载和可视化任何感兴趣的地理区域的开放全球数字高程模型。
首先,我们将从人道数据交换(HDX)下载我们选择的区域的 shapefile。然后,我们将使用elevation包,这个包提供了对由 NASA 和 NGA 托管在 Amazon S3 上的全球数据集 SRTM 30m Global 1 arc second V003 以及由 CGIAR-CSI 编制的 SRTM 90m Digital Elevation Database v4.1 的简单下载、缓存和访问。
该软件包将允许我们将所选地理边界的 DEM 下载到本地系统。然后,我们可以使用如GDAL或rasterio等库将此 DEM 导入 Python。接下来,我们将使用matplotlib探索和可视化 DEM,并计算诸如坡度和高程等导数。
从 OCHA 通过 HDX 提取任何国家行政区域的多边形
OCHA 现场信息服务部门(FISS)是位于瑞士日内瓦的联合国人道事务协调办公室的一部分。该组织提供共同操作数据集,这些数据集是支持人道紧急情况初步响应中的操作和决策的权威参考数据集。其中一个数据集是通过人道数据交换平台(HDX)提供的国家的次国家行政边界数据,使用的是创意共享归属政府间组织许可证。
以下代码可视化了来自亚美尼亚(作为本教程示例)的行政边界,这些数据是通过folium从 HDX 下载的。
从 GADM 通过 GADMDownloader 提取的国家行政边界(图片由作者提供)
要下载 DEM,我们选择一个省份 Gegharkunik。
提取的选定 ADM 边界多边形(作者提供的图像)
使用elevation包将 DEM 下载到本地路径
该包允许下载两个数据集,这些数据集可以通过属性product(SRTM1 或 SRTM3)进行指定。
-
SRTM1(30m 分辨率)— NASA JPL(2013)。NASA 航天飞机雷达地形任务全球 1 弧秒数据集[数据集]。NASA EOSDIS 土地过程 DAAC。2023 年 3 月 6 日访问,网址:
doi.org/10.5067/MEaSUREs/SRTM/SRTMGL1N.003
-
SRTM3(90m 分辨率)— Jarvis A., H.I. Reuter, A. Nelson, E. Guevara, 2008, 填洞的无缝 SRTM 数据 V4,国际热带农业中心(CIAT),可从
srtm.csi.cgiar.org
获取。
在本教程中,我们使用 SRTM1 数据,关于该数据的引用和使用政策可以在土地过程分布活动档案中心找到。
请注意,我们需要提供绝对文件路径来下载 DEM 作为 TIF 文件;否则将导致错误。
使用elevation包在 Python 中提取的 DEM,并裁剪为感兴趣的多边形(作者提供的图像)
使用 matplotlib 和 RichDEM 可视化 DEM 地形属性
在本节中,我们将使用matplotlib和richdem可视化数字高程模型的一些地形属性。
DEM 中的等高线是连接相同海拔点的线条。这些等高线有助于展示地形的三维形状,并可用于测量距离、计算流域和确定坡度。matplotlib 提供了将等高线绘制为线条或填充区域的选项,如下代码所示。
使用 matplotlib 可视化等高线(作者提供的图像)
DEM 的直方图以条形图形式表示,每个条形代表特定海拔值的频率。每个条形的高度对应于数据集中该特定海拔值出现的次数。DEM 直方图可用于识别海拔极值、检测海拔异常值或确定数据集中的总体海拔范围。
DEM 海拔值的分布(作者提供的图像)
坡度是一个地形属性或测量值,用于描述给定区域内海拔变化的速率。它是表面陡峭度或倾斜度的度量,通常以百分比或角度表示。
DEM 的坡度通常以度数表示,范围从 0°(平坦)到 90°(垂直)。亚美尼亚通常是一个山区国家,大部分地形都有陡峭的坡度。这个国家确实有一些平坦或缓坡的地区,主要位于阿拉斯河和阿拉克斯河的山谷中。
地形属性坡度(图片由作者提供)
当坡度以百分比表示时,它是通过给定距离(run)上的升高(rise)比率计算得出的,称为*坡度升降比。 14% 的坡度升降比意味着每 100 个水平单位,坡度垂直升高 14 个单位。例如,亚美尼亚的高坡度升降比位于高加索山脉,靠近与格鲁吉亚和阿塞拜疆的边界。亚美尼亚的高加索山脉是欧亚山脉系统的一部分,位于该国南部。该山脉的平均海拔约为 4,500 米(14,764 英尺)。亚美尼亚高加索山脉的平均坡度为 15%。
地形属性坡度升降百分比(图片由作者提供)
角度(Aspect)以度数为单位,是地球表面给定位置坡度方向的数值测量。它的测量角度从 0 到 360 度,0° 代表北方,90° 代表东方,180° 代表南方,270° 代表西方。
地形属性角度(图片由作者提供)
结论
可视化和分析数字高程模型对许多气候变化、灾害风险减缓和基础设施规划的应用场景至关重要,因为它允许分析地形特征,这对于理解气候变化的动态、评估自然灾害风险以及设计有效的基础设施项目是必不可少的。DEM 数据也构成了生成数字地形模型的基础,这些模型可以用于绘制易受洪水、滑坡和其他极端天气事件影响的区域。此外,DEM 数据还可以用于创建详细的 3D 景观模型,以模拟潜在的基础设施项目,如道路和桥梁,并评估其环境影响。
我们希望你现在对如何使用 Python 下载、探索和可视化数字高程模型(DEM)及其属性有了更好的理解。有了这些知识,你可以使用 Python 创建详细的地形图,并分析不同区域的高程。此外,你可以使用 Python 比较不同的 DEM 及其属性,并洞察地形随时间的变化。有了这些工具,你将能够深入探索和分析景观。
让我们通过查看阿拉加茨省的 DEM 可视化图来结束这篇博客。阿拉加茨省是亚美尼亚的一个省份,拥有该国最高的山峰——阿拉加茨山。阿拉加茨山是一座已 extinct 火山山脉,包含四个独立的山峰,每个山峰都可以攀登,因此是登山者和攀岩爱好者的梦想目的地。
可视化阿拉加茨省的 DEM 高程值的坡度、等高线和分布(图片来源:作者)
访问你的个人数据
原文:
towardsdatascience.com/accessing-your-personal-data-569b8991d745
公司关于你的广泛而常常令人惊讶的数据,准备好供你分析
·发表在 Towards Data Science ·阅读时间 22 分钟·2023 年 8 月 12 日
–
图片由 DALL-E 2 协助生成
数据隐私法律在全球范围内不断出现,这为你提供了一个独特的机会,让你了解别人如何看待你,同时也能深入了解自己。大多数法律与欧洲联盟的 通用数据保护条例,通常称为“GDPR”,类似。该条例包括要求组织告知你他们存储的个人数据类型、存储原因、使用方式以及存储时间长度的条款。
但这些法律还包括一个经常被忽视的要求,通常称为 数据可携带性。数据可携带性要求组织在请求时提供你当前存储的关于你的数据的机器可读副本。在 GDPR 中,这一权利在第 15 条中定义,“数据主体的访问权”。组织拥有的数据通常包括丰富多样的特征,且数据清洁,为多个数据分析、建模和可视化任务提供了丰富的素材。
在这篇文章中,我分享了我向几家我经常互动的公司请求我的数据的过程。我包括了请求数据的技巧以及在数据科学中使用数据和个人洞察的想法。
认为你对音乐品味有很好的把握吗?我曾认为自己有广泛而多样的音乐品味。然而,根据 Apple 的数据,我更像是一个铁杆摇滚迷。
作者提供的表格
想提升你的地理数据映射技能吗?这些数据源提供了大量的地理编码数据供你使用。
环球影城游览图 — 作者提供的图片
想试试你的时间序列建模技能吗?多个数据集提供了精细的时间序列观察数据。
使用 Apple 健康数据进行的运动时间预测——作者绘制的图表
最棒的消息是什么?这是你的数据。无需许可证或权限。
系好安全带——你将收到的数据种类繁多。你可以进行的分析和建模类型也都很复杂。你对自己以及他人如何看待你的见解是令人着迷的。
为了专注于数据洞察并简明扼要,我在这篇文章中没有包含代码。虽然大家都喜欢代码,这里有一个链接,指向一个包含我用来分析数据的多个笔记本的仓库。
获取数据
如果你列出有关于你的数据的组织,你会很快意识到这个列表非常庞大。社交媒体公司、在线零售商、移动电话运营商、互联网服务提供商、家庭自动化和安全服务以及流媒体娱乐提供商只是存储你数据的一些组织类别。从所有这些组织中请求数据可能非常耗时。
为了使我的分析可管理,我将数据请求限制在 Facebook、Google、Microsoft、Apple、Amazon 和我的移动运营商 Verizon。以下是总结我在数据请求和响应过程中的经历的表格:
作者提供的表格
这是我用来请求数据的链接以及供应商提供的任何数据文档的信息:
我使用 Apple Watch 来跟踪健康和健身数据。这些数据与从一般 Apple 网站请求的其他 Apple 数据分开访问。因此,我在上述表格中显示了两个单独的 Apple 条目,并在下面的两个主题中讨论了 Apple 数据。
你收到的数据的数量和类型将取决于你与特定公司的互动程度。例如,我不常使用社交媒体。因此,从 Facebook 收到的相对有限的数据并不令人惊讶。相比之下,我大量使用 Apple 产品和服务。从 Apple 那里获得了广泛和大量的数据。
请记住,如果你在一家公司有多个身份,你需要为每个身份请求数据。例如,如果 Google 知道你的 Google Play 账户使用一个电子邮件地址,而你的 Gmail 账户使用另一个电子邮件地址,你需要为每个地址分别进行数据请求,以便全面了解 Google 存储的关于你的数据。
在上表中,我展示了我用来请求数据的目标公司链接。这些链接在本文发布时是最新的,但可能会随着时间的推移而变化。通常,你可以在公司主页上的“隐私”、“隐私权利”或类似链接中找到请求数据的说明。这些链接通常出现在主页的最底部。
microsoft.com 屏幕底部 — 图片由作者提供
你通常需要阅读描述你隐私权利的文档,并寻找“访问您的数据”、“导出您的数据”、“数据可携带性”或类似主题的链接,以获取实际请求数据的页面链接。
最终,请求数据的过程、响应的及时性以及收到的解释数据的文档质量因公司而异。要有耐心并坚持不懈。你将很快获得大量数据和知识的回报。
我的数据洞察
以下是我从每家公司收到的数据文件的回顾,以及在分析了更有趣的文件后的几点观察。我还指出了一些利用这些公司数据进行更深入数据分析和建模的机会。
我从 Facebook 下载的文件包括 51 个 .json 文件,不包括那些包含我 Facebook Messenger 账户中各个消息线程的众多 .json 文件。Facebook 在下载网站上提供了一些关于其文件的高级文档。
关于我 Facebook 登录活动的数据、我用来登录的设备、我登录的估计地理位置,以及与我账户活动相关的类似管理类型数据分布在几个文件中。尽管这些文件中的内容没有特别有趣,但我会说位置数据似乎出乎意料地准确,因为它通常是从记录活动时的 IP 地址推断出来的。
其中真正有趣的数据出现在一个跟踪我在 Facebook 之外的应用和网络活动的文件中。我可以看到那个文件中的数据,加上 Facebook 从我的 Facebook 个人资料中获得的数据,如何绘制出一个人口统计学的图景,导致我被特定的 Facebook 广告商选为目标。这个 Facebook 之外的文件开始让你感受到 Facebook 的画像和广告过程如何运作。
让我们来看一下这个文件。它的名字是:
“/apps_and_websites_off_of_facebook/your_off-facebook_activity.json”
它包含了我在过去两年中在 441 个不同的非 Facebook 网站上所采取的 1,860 条记录。以下是记录的网站和行动类型的编辑样本:
表格由作者提供
几个技术和旅行相关的网站在我的 Facebook 之外活动列表中排名靠前。现在让我们来看一下我的人口统计档案。
文件名:
“ads_information/other_categories_used_to_reach_you.json”
包含了 Facebook 根据我的 Facebook 个人资料数据、Facebook 好友、我在 Facebook 上的活动以及我在 Facebook 之外的应用和网页活动分配给我的人口统计类别的列表。以下是编辑后的部分人口统计类别示例:
作者的表格
上述大多数类别基于我的个人资料、设备使用模式和我的朋友。类别“Frequent Travelers”和“Frequent International Travelers”我猜来自于我在 Facebook 之外的网页活动。到目前为止,这一切都符合实际情况。
最后,有一个名为:
“ads_information/advertisers_using_your_activity_or_information.json”
文件标题中的“advertisers_using_your_activity_or_information”让我相信 Facebook 将我的数据提供给其广告商,广告商则利用这些数据通过 Facebook 向我投放广告。因此,该文件列出了那些向我展示广告的广告商,或至少根据我的数据考虑过这样做的广告商。
文件中包含了 1,366 个不同的广告商。以下是这些广告商的小部分示例:
作者的表格
旅行网站、零售商、科技公司、健身中心、汽车维修公司、医疗保险公司、媒体公司(代表广告商)和其他公司出现在列表中。这是一个种类繁多的组织,但在许多情况下,我可以看到它们与我、我的偏好和我的习惯的关系。
Facebook 下载中的其他文件包括 Facebook 搜索历史、搜索时间戳和浏览器 Cookie 数据。
Google 的导出功能巧妙地命名为“Takeout”。Takeout 网页列出了你可以请求数据的各种 Google 服务(如 gmail、YouTube、搜索、Nest 等)。它还显示了每项服务的可用文件以及每个文件的导出格式(json、HTML 或 csv)。大多数时候,Google 不会提供对单个文件导出格式的选择。
Google Takeout 请求网站的一部分,地址为 takeout.google.com —— 作者的屏幕图像
Google 在提供每个文件目的的高级概述方面做得相当不错。然而,对于单个字段,没有文档说明。
我在提取中收到 94 个文件。与 Facebook 一样,有正常的与设备信息、账户属性、偏好设置和登录/访问数据历史相关的管理文件。
一个有趣的文件是标题为‘…/Ads/MyActivity.json’的文件。它包含了由于搜索而向我展示的广告的历史记录。
Ads/MyActivity 文件中的一些条目包含了点击服务域的 URL,例如:
作者的屏幕截图
根据Google 的 360 广告网站,这些广告来自 Google 的广告主进行的广告活动,是我点击活动的结果。文件没有提供有关我采取了什么行动导致广告展示的任何信息。
文件中的“标题”列区分了“访问的”站点和“搜索的”主题。“访问的”记录在“详情”列中都标有“来自 Google 广告”(见上例),这让我相信 Google 在我访问特定网站后向我展示了广告。
“搜索的”记录显示了我直接访问的网站(如 macys.com、yelp.com 等)。“详情”列显示了这些网站,而“标题”列显然显示了我在这些独立网站上搜索的内容。例如,
作者截屏
作者截屏
另一个我发现有趣的文件叫做‘…/My Activity/Discover/MyActivity.json’。它是 Google 通过其“Discover”功能在 Google 应用中向我展示的主题建议的历史记录(以前是 Google Feed 功能——有关 Discover 的更多信息,请参见这里)。Discover 话题是基于你的网页和应用活动选择的,前提是你允许 Google 使用你的活动来指导 Discover 话题。
尽管我不允许 Discover 使用我的网页和应用活动,Discover 仍然提供了一些与我相关的话题建议。以下是几天内最频繁出现的主题的编辑样本:
我们在这里看到重复出现的技术和旅行主题,以及一个我们将在 Apple 文件中也看到的新主题——音乐!
Google 在其下载文件中包含了跟踪 Google 产品和服务活动历史的几个文件。例如,我收到了我访问 developers.google.com 和 cloud.google.com 网站的历史记录,这些网站用于培训和文档资源。这些数据没有提供有力的见解,但确实提醒了我一些我想要重新审视和进一步学习的主题。
提取中的其他历史数据包括在我的 gmail 账户中执行的搜索和操作;图片的搜索请求;通过 Google Maps 应用搜索的地点、请求的方向和查看的地图;在网络上进行的视频搜索(YouTube 之外);在 YouTube 上的搜索和观看历史;以及我在 Google 中存储的联系人,可能是在 gmail 中。
与 Facebook 不同,Google 并没有提供关于 Google 为我构建的任何人口统计信息。
请注意,你可以通过访问myactivity.google.com来查看你在 Google 产品和应用中的活动数据:
作者截屏
虽然你不能从这个网站导出数据,但你可以浏览数据,从而对你可能想通过 Google Takeout 网站导出的数据类型有个了解。
微软
微软允许你通过微软隐私仪表板导出你的部分数据。对于隐私仪表板上没有的微软服务(例如,MSDN、OneDrive、Microsoft 365 或 Skype 数据),你可以使用微软隐私声明页面的“如何访问和控制你的个人数据”部分中的链接。如果你需要的数据通过上述方法无法获得,该页面会引导你提交一个网页表单。
我选择通过隐私仪表板导出所有可用的数据。这包括浏览历史、搜索历史、位置信息、音乐、电视和电影历史,以及应用和服务使用数据。我还要求导出我的 Skype 数据。我的导出包含了四个 csv 文件、六个 json 文件和六个 jpeg 文件。
导出中未包含文件文档,也未在微软网站上找到相关信息。然而,文件中的字段名称相当直观。
从微软文件中有一些有趣的观察:
文件‘…\Microsoft\SearchRequestsAndQuery.csv’包含了我在过去 18 个月内进行的搜索的数据,包括搜索词和,显然,如果有的话,点击的站点。看起来这些数据仅限于我通过 Bing 或 Windows 搜索进行的搜索。
根据数据,我发现我在搜索结果中点击链接的次数仅为 40%(870 次搜索中点击了 347 次)。由此,我猜测那些我未点击链接的搜索,要么是搜索词不佳,返回了无关结果,要么是我通过查看搜索结果中的链接预览就能获得所需的答案。我不记得需要频繁修改搜索词,我知道我经常可以在链接预览中找到需要的答案,因为我的许多搜索是关于编码语法的提醒。不管怎样,我对 40%的点击率感到有些惊讶,我原本期望它会更高。
Skype 数据中没有什么特别有趣的内容。它包含了我和其他 Skype 会议参与者之间的应用内消息线程的历史记录。还包括了一些我的通话中的参与者图片的.jpeg 文件。
苹果健身
我必须单独访问我从 Apple 导出的其他数据中的苹果健康和健身数据。这些健康和健身数据可以从 iPhone 上的健康应用中访问。你只需点击健康应用屏幕右上角的图标,它会带你进入个人资料屏幕,然后点击屏幕底部的“导出所有健康数据”链接:
作者截屏
我的健康导出数据包含了不到 500 个.gpx 文件,总共 102 兆字节。它们包含了过去几年记录的锻炼路线信息。另有 48 个文件包含了 5.3 兆字节的心电图数据,来自我在 Apple Watch 上进行的自我测试。
名为‘…/Apple/apple_health_export/export.xml’的文件包含了真正有趣的数据。对我而言,它有 770 兆字节,包含 1,956,838 条记录,涵盖了大约七年内的多种健康和运动测量数据。测量的一些活动类型如下:
作者提供的表格
请注意,Apple 记录数据的频率因活动类型而异。例如,主动能量消耗按小时记录,而爬楼梯速度仅在上楼梯时记录,这导致这两种活动类型之间的观察次数差异很大。
每个观察记录的数据包括观察记录的日期/时间、被测活动的开始和结束日期/时间,以及记录活动的设备(iPhone 或 Apple Watch)。
在他出色的 Medium 文章《用 Python 和 Apple Health 分析你的健康》中,Alejandro Rodríguez提供了我用来解析 export.xml 文件中的 xml 并创建 Pandas 数据框的代码。(谢谢 Alejandro!)在选择了一年的数据子集,并按天和活动类型进行分组和汇总后,我发现了一些有趣的事情。
正如我所怀疑的那样,我在旅行期间的平均活动水平与我在我称之为家的城市(奥斯汀或芝加哥)时的活动水平不同。为了查看这一点,我必须使用前面提到的.gpx 运动路线文件中的纬度和经度数据。这让我能够确定哪些路线是在家城市中,哪些是在旅行中。然后,我将这些位置数据与我的活动总结数据合并。这些数据进一步按活动类型和位置(家城市或旅行)进行了汇总。这里是合并后的模式:
作者提供的图片
在芝加哥期间,我住在有电梯的公寓楼里,所以平均爬楼梯次数的大幅下降并不令人惊讶。令人惊讶的是芝加哥与奥斯汀的活动水平增加。尽管我在两个地点的锻炼习惯非常相似,但我在芝加哥的锻炼量更多。我认为这可以归因于我在芝加哥走到更多地点,而不是大部分时间开车。显然,我需要在奥斯汀增加锻炼量。
像上面这种趋势,在 Apple Health 应用的标准图表中看不到,是健康数据的一个很好的用途。
这些数据也非常适合建模,因为数据非常完整且一般较为干净。例如,下面是基于一年时间段的我的运动分钟数的时间序列预测,使用了 Facebook 的 Prophet 模型:
使用默认每周季节性,无年度季节性运动分钟数预测 — 作者提供的图像
这是相同的预测,但启用了年度季节性,并根据我的位置(奥斯汀、芝加哥或旅行中)手动添加了每周季节性:
使用年度季节性和手动每周季节性的运动分钟数预测 — 作者提供的图像
上面的默认每周季节性模型(第一个图)对训练数据的拟合效果比添加了自定义季节性项的模型(第二个图)要差。然而,默认的季节性模型在预测未来的运动分钟数方面表现远远更好(尽管仍然不尽如人意)。不用说,调整超参数可以改善这些结果。
不同模型的平均绝对百分比误差 — 作者提供的图表
这只是一个示例,展示了你可以使用健康数据进行的建模类型。你是否想尝试使用非常详细的时间序列数据?查看运动路线文件。它们记录了你每秒的运动数据,包括纬度、经度、高度和速度字段。
Apple — 非健身/健康
你可以从 Apple 的主网站请求下载所有非健身/健康数据。对我来说,这总共是 84 个文件,主要是 .csv 和 .json 文件,还有一些 .xml 文件。我还收到了数百个 .vcf 文件,每个文件对应我在 Apple 设备上的一个联系人。总的来说,我下载了 68MB 的数据,不包括 .vcf 文件。
Apple 突出的特点在于它为每个数据文件提供了详尽的文档。文档包括每个字段的解释,虽然一些定义比其他的更有帮助。这些文档帮助我解释了一些看起来很有趣的数据文件。
与大多数其他导出一样,Apple 的文件包含了正常的行政数据,包括我的应用偏好、登录信息和设备信息。我在这些文件中没有发现任何特别的东西。
有几个与 Apple Music 相关的文件,这是我订阅的服务之一。文件标题类似于:
-
“…/Media_Services/Apple Music — 每日播放历史.csv”;
-
“…/Media_Services/Apple Music — 最近播放的曲目.csv’’;以及,
-
“…/Media_Services/Apple Music 播放活动.csv”
包含的信息包括:
-
歌曲播放的日期和时间;
-
播放持续时间(以毫秒为单位);
-
每次播放的结束方式(例如,播放到歌曲末尾,或者我跳过了这首歌);
-
歌曲被播放的次数;
-
歌曲被跳过的次数;
-
歌曲标题;
-
专辑标题(如果有的话);
-
歌曲的类型;以及,
-
播放歌曲的来源——我的库、播放列表或 Apple 的广播频道。
我的文件包含了 13,900 到 20,700 条记录,这取决于文件的用途。这些数据涵盖了近七年的歌曲播放记录。
Apple 捕捉了多种数据来记录歌曲播放的结束情况,可能是为了推荐其他歌曲给我。歌曲播放结束原因包括:
作者绘图
在下面的分析中,我专注于‘NATURAL_END_OF_TRACK’(自然结束)、‘TRACK_SKIPPED_FORWARDS’(跳过前进)和‘MANUALLY_SELECTED_PLAYBACK_OF_A_DIFF_ITEM’(手动选择播放不同项目)结束原因。
有时我会重复播放我喜欢的歌曲。我曾经有一个问题是:“我是否会对喜欢的歌曲进行强迫性播放,一遍又一遍?”我使用 Apple 的数据回答了这个问题:
作者绘图
上表总结了我播放一些喜欢的歌曲的次数(‘播放次数’)以及我播放这些歌曲的天数(‘播放天数’)。看起来我一般每天只播放一首歌曲。此外,由于某些歌曲的播放次数少于播放天数,我一定会跳过一些喜欢的歌曲,特别是当我最近听得太多或者歌曲不符合当时的心情时。所以,这里没有强迫症播放的情况!
我也想知道我是否在一周的不同日子、一天的不同时间,甚至一年中的不同月份更偏爱某些类型的歌曲。我的直觉是我确实如此。通过 Apple 的数据,可以轻松地可视化我在不同时间播放的流派。例如,以下是我每个月播放最频繁的流派:
作者提供的图像
我明显偏爱摇滚歌曲,同时偶尔会加入一些另类和流行音乐。七月和八月似乎是我喜欢变化的月份。
尽管如此,我还是对自己播放的摇滚歌曲数量感到惊讶。诚然,我爱摇滚。但我也相信我对音乐的品味相当广泛。
因此,我质疑了 Apple 数据中分配给歌曲的流派的准确性。首先,我文件中的 22,313 次歌曲播放中有 10,083 次没有分配流派。此外,分配的流派似乎有很多重叠。例如,“R&B/Soul”、“Soul and R&B”、“Soul”和“R&B / Soul”都是分配给我数据中不同歌曲的流派。如果我将所有歌曲的流派重新调整为一致的命名方案,上述图表中的总数肯定会有所不同。
我决定不投入时间更新音乐流派,而是进行另一个测试,以确定图表中的趋势是否真正代表了我的播放模式。由于 Apple 在数据中包含了歌曲播放结束原因,我查看了是否我比其他流派更频繁地跳过摇滚歌曲,这表明当摇滚歌曲播放过多时,我会尝试播放其他流派。
作者绘图
事实证明,我跳过摇滚歌曲的频率并没有明显高于我跳过其他我经常听的音乐类型。我得面对这个事实——我是真正的摇滚迷。
另一个有趣的文件叫做“…/Media_Services/Stores Activity/Other Activity/App Store Click Activity.csv”。虽然我在这里没有分析它,但我推荐给任何想了解零售商可能想跟踪的网站活动类型的人。对我来说,它包含了 4,900 多条记录,详细记录了我在应用商店内的活动历史,以及显然是在 Apple Music 中的活动。我采取的行动类型、日期/时间、A/B 测试标志、搜索词和呈现给我的数据(使用的术语是“印象深刻”)都包括在文件中。
最后一个可能有趣的分析文件是 \Media_Services\Stores Activity\Other Activity\Apple Music Click Activity V3.csv。它包括了我在使用 Apple Music 时的 IP 地址所在的城市和经纬度。对我来说,该文件有 10,000 条记录。
Verizon
在长达 80 多天的等待之后,Verizon 通知我可以下载我的数据。它包括 17 个 csv 文件,总共 1.4 兆字节的数据。大多数文件涵盖了帐户管理信息(手机线描述、设备信息、账单历史、订单历史等)、Verizon 发给我的通知历史和我最近的短信历史(但没有短信内容)。虽然提供了通话记录和数据使用文件,但它们都是空的,只有一个注明数据是“为安全起见已被屏蔽”的说明。
Verizon 提供了两个文档文件。一个包含了可能包含在下载中的 34 个文件的名称和一般描述。包含的文件取决于你使用的 Verizon 服务。第二个文档文件包含了可能出现在这些文件中的 3,091 个数据字段的描述。虽然数据字段描述是有帮助的,但它们缺乏一些细节。例如,许多字段被描述为包含各种用途的代码,但这些代码及其含义并未描述。
一个非常有趣的文件叫做“…/Verizon/General Inferences.csv”。它包含了关于我以及我家其他人的大量人口统计信息。以下是 Verizon 文档对该文件的描述:
“通用推论文件提供了有关一般假设和推论的信息,以在我们的平台上提供更具相关性和相关性的内容。这可能包括像属性、偏好或意见这样的信息。”
根据人口统计特征的性质,我假设大多数信息是 Verizon 从外部数据聚合商处获取的,而不是直接从我这里收集的。人口统计特征的数量和范围远远超过了我直接提供给 Verizon 的任何信息。
实际上,Verizon 文档提到了另一个名为“General”的信息文件(不包含在我的下载中)。文档称“General”文件包括来自外部信息来源的数据。我的猜测是“总体推断”文件中的信息也来自这些外部来源。“总体推断”文件中的一些财务数据可能来自 Verizon 要求其客户提供的信用报告。
总共有 332 个与我相关的人口特征被包括在我的“总体推断”数据中。以下是一个缩略列表,包括一些比较令人惊讶的特性:
“总体推断”文件的人口特征缩略列表 — 作者表
所有的“总体推断”特性显然被 Verizon 用来向我营销并保留我作为客户。正如上面的列表所示,关于我配偶和我们孩子的特性也被包括在内。你可以在这里查看完整的 332 个特性列表。
我发现的一些真正不寻常的特性包括:
作者表
人们不得不怀疑这些数据元素是否真的被 Verizon 所需,以帮助其提供服务,如果是这样,Verizon 是如何使用它们的。
亚马逊
亚马逊提供了包含 4.93MB 数据的 214 个文件。几个文件包括:
-
账户偏好;
-
订单历史;
-
履行和退货历史;
-
观看和收听历史(亚马逊 Prime Video 和亚马逊音乐);
-
Kindle 购买和阅读活动,
-
和包括搜索词在内的搜索历史。
如果我曾是 Alexa 客户或 Ring 客户,我假设我也会收到有关这些服务的活动数据。
六个.txt 文件包含了一些下载数据文件的高级描述。几个.pdf 文件包含下载文件中字段的文档(例如,“Digital.PrimeVideo.Viewinghistory.Description.pdf”文件)。
来自亚马逊的最有趣的文件涉及亚马逊、其广告商或“第三方”与我相关的营销受众。我推测这些第三方是亚马逊从中购买数据的数据供应商。
“…/Amazon/Advertising.1/Advertising.AmazonAudiences.csv”文件包含亚马逊分配给我的受众。以下是 21 个受众的示例:
亚马逊分配给我的受众 — 作者表
当我考虑自己购买或搜索的产品时,无论是为了自己还是代表他人,亚马逊自己分配的受众基本上是准确的。
“…/Amazon/Advertising.1/Advertising.AdvertiserAudiences.csv”文件显然包含了带来自己受众到亚马逊的亚马逊广告商的名单,并且他们的受众名单中包括了我。该文件包含 50 个广告商。以下是一个示例:
将我列入观众名单的亚马逊广告商 — 表格由作者提供
我与列表中的一些广告商(例如,达美航空、Intuit、Zipcar)有业务往来或拥有他们的产品,因此我理解我如何进入他们的观众名单。我与列表中的其他公司(例如,AT&T、红牛、加拿大皇家银行)没有任何联系,所以我不确定我是如何进入他们的观众名单的。
根据亚马逊的说法,文件
“…/Amazon/Advertising.1/Advertising.3PAudiences.csv”
包含一个列表
“你被第三方包含的观众。”
它的准确性很差。总共有 33 个观众被列出,其中 28 个集中在汽车拥有上。剩下的四个涉及性别、教育水平、婚姻状况和抚养人。一个汽车相关观众的示例:
由第三方供应商提供的汽车相关观众分配示例 — 表格由作者提供
虽然文件中的性别/教育水平/婚姻状态类型的分配是准确的,但其中只有少数汽车相关的分配是正确的。大多数是不准确的。而且,我对汽车的兴趣并不足以让 33 个档案分配中有 28 个与汽车相关。幸运的是,亚马逊在向我推荐产品或视频时似乎忽略了这些数据。
离别感想
在这篇文章中,我希望向你展示你可以从与你做生意的公司那里获得的各种数据。这些数据使你能够了解这些公司对你的看法,同时也发现一些关于你自己的令人惊讶的事情!
我们已经看到,有些公司正确识别了我对技术和旅行的兴趣,而有一家公司错误地把我看作是一个狂热的汽车爱好者。在一个令人瞩目且略感不安的时刻,我意识到另一家公司掌握了我家庭的广泛人口统计信息。
我了解到,我需要在我称之为家的两个地方中的一个增加锻炼量,尽管我认为我的锻炼在两个地方是等效的。我发现一些公司(facebook,谷歌)对我的个人资料没有强烈的看法。然而,Verizon 对我的人口统计画像却令人震惊地准确。
各公司提供的数据为实验提供了丰富的原材料。这些数据适合于深入分析、建模和可视化活动。例如,许多观察提供了地理坐标和时间戳,使你能够可视化或建模你的移动情况。
我希望你通过下载你的个人数据找到一套有趣的见解。如果你在与我这里未涵盖的公司合作时有值得注意的经历,请告诉我。
这是你的数据 — 现在去利用它吧!
文章由作者于 2023 年 8 月 16 日编辑以纠正拼写错误。
实现大型语言模型的更大自我一致性
原文:
towardsdatascience.com/achieving-greater-self-consistency-in-large-language-models-6e6cb5f3c5b7
·发表于 Towards Data Science ·8 分钟阅读·2023 年 12 月 1 日
–
人工智能软件用于增强本文文本的语法、流畅性和可读性。
当 LLM 用于评估文本的正确性、准确性或相关性等质量时,一致性至关重要。如果 LLM 表现出不一致的判断,那么其评估就会变得不可靠和不可信。
如果 LLM 评估论证的推理质量,但通过将无效的论证评为比完全有效的论证更有逻辑,从而自相矛盾,那么它就无法作为理性的仲裁者。由于模型自身缺乏逻辑一致性,其评估失去可信度。
当出现这种不一致性时,就没有稳定的基础来比较 LLM 对不同文本的评估。如果模型随意自相矛盾,那么句子无法根据模型的不一致评分可靠地排序。
本质上,不一致性破坏了评估旨在提供的比较基础。如果一个 LLM 不能展示一致的评估标准应用,那么使用它来评估文本就失去了所有的有效性和实用性。
因此,对于被用于评分或评判文本质量和特征的 LLM 来说,一致性是必需的。如果评估没有高水平的稳定性,并且基于对被评估概念的一致理解,那么在将 LLM 输出作为评估或评分形式时,比较的基础就会崩溃。
采样多个解决方案表明,输出之间的一致性与质量有很强的相关性。然而,现有的一致性技术依赖于提取和匹配封闭形式的答案,限制了其适用性。本文探讨了在没有这些限制的情况下提高自我一致性的方法,同时也将决策建立在现实世界知识上。
作者提供的图像
自洽性的需求
尽管取得了迅速的进展,逻辑失败和虚假信息仍然阻碍了最先进模型中的可靠推理。对于复杂的多步骤分析或自由形式生成,模型往往会自相矛盾或编造没有支持的事实。
这表现为两个关键方面——不一致的开放式生成和不连贯的推理。在执行开放式任务时,模型在同一输入上多次采样时生成冲突的输出。与此同时,对于链式推理,模型得出违反基本传递性质的非理性结论。
例如,模型可能在排名比较中确定 A > B 和 B > C,但随后不准确地评估 C > A,导致循环矛盾。这种未能保持传递一致性的问题从根本上削弱了可靠性。
像自洽性这样的技术通过采样多个候选解决方案并选择显示共识的输出来帮助解决不一致性。关键的见解是共识作为质量和连贯性的有效代理度量。
然而,现有的自洽性方法依赖于严格的答案格式,以便在响应之间进行提取和比较。这大大限制了它们在开放式任务中的适用性。
提升内部自洽性需要双管齐下的方法。首先,一致性技术如 USC 消除了格式限制,允许开放式选择。同时,对比排序方法对潜在表示施加逻辑约束,确保模型在多步骤推理过程中保持顺序关系。
然而,仅仅增强内部一致性而不依赖于结构化的外部知识仍不足以实现准确推理。语言模型缺乏动态更新、逻辑表达能力和经验验证,这些都是解释所需的关键因素。
因此,提升可靠性需要在各种任务中提高自洽性,同时整合结构化的世界知识库。一致性技术应在自由形式输出中灵活运作,并结合利用丰富语义表示的框架,以逻辑驱动的、外部验证的事实来指导决策。
这种模型内部共识与结构化外部基础的结合补充了每种方法的优势,以复制多方面的人类推理。只有它们的协同作用才能克服固有的局限性,逐步推进 AI 能力,以匹配多样且流动的认知。
引入通用自洽性
使用链式思维提示(CoT)的自一致性在各种任务上表现出显著的性能提升……
arxiv.org](https://arxiv.org/abs/2311.17311?source=post_page-----6e6cb5f3c5b7--------------------------------#:~:text=Universal%20Self%2DConsistency%20for%20Large%20Language%20Model%20Generation,-Xinyun%20Chen%2C%20Renat&text=Self%2Dconsistency%20with%20chain%2Dof,large%20language%20models%20%28LLMs%29.)
陈 2023 等人提出了通用自一致性(USC),以在不同应用场景中实现自一致性,而无需从中提取答案。USC 依赖于语言模型自身的能力,从其最初生成的多个候选解中选择最一致的回答。
具体而言,USC 将所有采样的回答连接成一个上下文,然后构建一个提示,要求模型选择其中共识度最高的回答。通过消除专业的聚合逻辑,USC 即使在自由形式生成任务中也适用。
实验表明,USC 在允许提取答案的数学推理和代码生成基准测试中的表现与标准自一致性相匹配。至关重要的是,USC 还改善了开放式问答、总结和创意写作,这些领域现有技术存在不足。
通过外部知识增强推理
尽管 USC 提供了与框架无关的一致性,但外部知识对于准确和稳健的推理仍然至关重要。语言模型缺乏策划知识库的动态更新、事实完整性和逻辑表现力。
知识图谱允许结合经验基础的细节和相互关联的事实关系,根据最新信息提供更一致的情境解读。它们有助于访问上下文的经验证据,以支持决策,而不是仅依赖于模型权重中嵌入的固有偏见。
此外,知识图谱管理着事实随时间的演变,确保推理依赖于最新的信息。它们还封装了领域逻辑,以显式编码规则、约束和本体分类法。这使得合理的推理无法仅从模型训练数据中辨别。
经验上,利用外部知识图谱的检索增强生成通过将回答建立在已验证的事实上而不是幻觉上,展示了更一致的输出。知识图谱的并行查询——即使是重复副本——通过从多个角度积累证据进一步提高了一致性。
通过使用 USC 的结构化推理与检索增强系统的协调,可以利用外部知识来形成一个模块化的混合架构。USC 提供了推理的“骨干”,而并行检索则从可信的知识库中提供相关的事实细节,增强了解释的一致性。
使用种子采样增强一致性
## openai-cookbook/examples/Deterministic_outputs_with_the_seed_parameter.ipynb 在 main 分支下 ·…
使用 OpenAI API 的示例和指南。通过创建账户来贡献 openai/openai-cookbook 的开发…
github.com](https://github.com/openai/openai-cookbook/blob/main/examples/Deterministic_outputs_with_the_seed_parameter.ipynb?source=post_page-----6e6cb5f3c5b7--------------------------------)
在开放式生成任务中,解决方案之间的一致性与质量强相关。然而,大型语言模型展示了固有的随机性,这会导致输出的变异。
为了缓解这一问题,OpenAI 的 Chat Completions API 提供了一个种子参数,该参数用于种子随机数生成器以进行确定性采样。使用相同的种子和参数,每次将产生相同或非常相似的输出。
这使得将通用自我一致性(Universal Self-Consistency),即模型从候选中选择最一致的响应,与种子采样结合起来,以在请求之间评估完全相同的响应成为可能。
计算机中仍然存在因非确定性带来的微小变异机会。此外,对系统 _fingerprint 的更改表示后端更新影响了请求的确定性。
除了种子采样外,较低的温度和仔细的提示工程也能减少模型生成文本的变异性。总体而言,种子采样使我们能够利用一致性来提高可靠性。
通过与 USC 基于选择的种子候选进行编排,我们系统地构建了既基于外部知识又基于采样确定性的解决方案。这体现了超越单一技术的涌现能力。
对比一致性排名(CCR)
尽管前面的部分侧重于分类和开放式生成,但从语言模型中引出的排名一致性也是至关重要的。最近的工作探索了对比一致排名(CCR),它适应了对比一致搜索(CCS)来对映射项目表示施加逻辑约束,以实现一致的尺度。
CCR 探测项目向量,以无监督的方式找到一致的排名方向。对提示基线和 CCR 变体的实验显示出改进的一致性和泛化能力。
通过将基于一致性的技术如 USC 扩展到排名,CCR 提供了一种限制不可预测性并使模型输出在多样任务中对齐的方法。两者都旨在通过选择模型自身认为内部一致的解决方案或排名来提高可靠性。
语言模型包含基于排名的知识,并且是处理上下文排名任务的强大解决器。例如……
arxiv.org](https://arxiv.org/abs/2309.06991?source=post_page-----6e6cb5f3c5b7--------------------------------)
CCR 探测训练一个额外的“探测”模型,以在固定的语言模型的向量空间中找到潜在的排名方向。
-
它不会直接提示或微调语言模型本身。
-
语言模型仅用于通过输入提示生成项目的向量表示。
-
这些项目向量随后被输入到 CCR 探测器中,该探测器在无监督损失函数上进行训练,将表示映射到一致的排名尺度上。
总结来说,CCR 探测引入了一个外部探测模型,该模型将语言模型包装起来,以在其向量空间中进行排序。语言模型保持静态。只有额外的探测模型在对比目标下进行训练,以揭示内在的排名。
技术架构
大型语言模型(LLMs)展现了令人印象深刻的少样本学习能力,能够快速适应新任务……
towardsdatascience.com
我们实现了一个 RAG 系统,通过向量搜索访问多个知识图以进行快速事实检索。查询引擎接口到索引,并封装段落搜索。辅助工具包装引擎,促进集成。独立代理负责工具的接口,与 LLM 进行对接。超级代理负责工具协调。
系统利用思维线程(ToT)提示进行结构化推理,对检索的段落进行分析。ToT 指导模型通过逐步分析,增强理解。并行异步检索允许同时查询所有图,加速上下文积累。
使用多种算法和嵌入的多模态知识图提供了不同的视角。个性化 PageRank 遍历支持沿间接连接进行灵活推理。近似最近邻搜索实现了高效的查找。嵌入通过向量运算实现类比推理。
我们实施了一种分阶段的检索方法,首先查询领域本体以建立与问题相关的基本概念和术语。本体提供了正式的定义和实体之间的高层次关系,在检索完整的知识图谱之前实现了基础理解。
本体结果附加到原始问题中以提供背景。这个增强的查询随后用于从多个知识图谱中检索段落。通过本体信息种子检索,可以为 RAG 系统提供更一致和相关的段落,这些段落是针对具体基础方面量身定制的。
这一技术将本体的自上而下的层级推理与知识图谱中互补的自下而上的事实细节相结合。本体澄清了模糊或广泛的术语,而知识图谱提供了对细化查询焦点的深入关系信息。
以这种分阶段的方式编排不同的知识来源,可以在抽象层次之间平滑过渡,增强一致性。检索从高层次的本体概念流向与问题相关的特定背景段落。
from llama_index.indices.knowledge_graph import KnowledgeGraphRAGRetriever
from llama_index.agent import OpenAIAgent
from llama_index.tools import QueryEngineTool, ToolMetadata
from llama_index.llms import OpenAI
from llama_index.memory import ChatMemoryBuffer
memory = ChatMemoryBuffer.from_defaults(token_limit=1000)
# Your knowledge graphs
my_kgs = {'kg1': kg_index, 'kg2': kg_index2}
# Dictionary to store the agents
kg_agents = {}
# List to store the tools
kg_tools = []
for kg_name, kg in my_kgs.items():
# Create a query engine for the KG
query_engine = kg.as_query_engine(
include_text=True,
response_mode="tree_summarize",
embedding_mode="hybrid",
similarity_top_k=20,
)
# Create a tool for the query engine
tool = QueryEngineTool(
query_engine=query_engine,
metadata=ToolMetadata(
name=f"tool_{kg_name}",
description=f"Useful for questions related to {kg_name}",
),
)
# Add the tool to the list of KG tools
kg_tools.append(tool)
# Create an agent for the tool
agent = OpenAIAgent.from_tools([tool], system_prompt="Walk me through this context in manageable parts step by step, summarizing and analyzing as we go.")
# Add the agent to the dictionary of KG agents
kg_agents[kg_name] = agent
# Create the super agent
llm = OpenAI(model="gpt-3.5-turbo-1106")
super_agent = OpenAIAgent.from_tools(kg_tools, llm=llm, verbose=True, memory=memory, system_prompt="Therefore, the answer.")
# Initial question
initial_question = "Define assets? And optimize them"
# Query the ontology with the initial question
ontology_results = ontology_engine.query(initial_question)
# Combine the initial question with the ontology results
combined_question = f"{initial_question}. {ontology_results}"
# Query the super agent with the combined question
response = await super_agent.astream_chat(combined_question)
# Collect all tokens into a string
response_text = ""
async for token in response.async_response_gen():
response_text += token
# Print the response text
print(response_text)
影响
结合 USC 和 RAG 将基于一致性的思维与外部知识的基础结合起来。USC 提供了推理结构,而 RAG 扩展了信息的广度。这些结合起来弥补了 LLM 的局限性,更好地复制了人类认知。
这种编排还提高了准确性、速度和覆盖范围。检索到的事实填补了知识空白,以做出合理的决策。并行知识访问加速了理解。不同的知识图谱拓宽了考虑的概念连接。
通过模块化增强,我们优雅地扩展了新兴能力超越固有模型的适应性。随着 LLM 和知识库的发展,这种可组合的范式将促进 AI 推理能力的逐步提升。
在混乱背景下通过思路引导和并行知识图谱检索实现结构化推理
·发表于 Towards Data Science ·阅读时长 8 分钟·2023 年 11 月 18 日
–
人工智能软件被用来增强本文的语法、流畅性和可读性。
大型语言模型(LLMs)展现了令人印象深刻的少样本学习能力,能够通过少量示例迅速适应新任务。
## 为什么 RAG(检索增强生成)将成为使用 LLM 系统设计的基石
最近在大型语言模型(LLMs)如 GPT-3 [1]中的进展显示了强大的少样本学习能力——
然而,尽管有这些进展,大型语言模型(LLMs)在涉及充满不相关事实的复杂推理中仍面临局限。为了解决这一挑战,研究人员探索了如链式思维引导等技术,这些技术引导模型逐步分析信息。然而,这些方法单独使用时,难以全面捕捉广泛背景中的所有关键细节。
本文提出了一种将线程思维(Thread-of-Thought, ToT)提示与检索增强生成(Retrieval Augmented Generation, RAG)框架相结合的技术,该框架并行访问多个知识图谱。ToT 作为思维的“骨架”来构建推理,而 RAG 系统则扩展了可用知识以填补空白。与顺序检索相比,信息来源的并行查询提高了效率和覆盖面。综合来看,这一框架旨在提升 LLM 在混乱情境中的理解和解决问题的能力,更接近于人类认知。
我们首先概述了在相关和无关事实混合的混乱环境中对结构化推理的需求。接着,我们介绍了 RAG 系统设计及其如何扩展 LLM 的可访问知识。然后,我们解释了如何集成 ToT 提示,以系统地引导 LLM 进行逐步分析。最后,我们讨论了如并行检索等优化策略,以高效地同时查询多个知识来源。
通过概念解释和 Python 代码示例,本文阐明了一种将 LLM 的优势与互补外部知识相结合的新技术。这种创造性的整合突显了克服模型固有限制并提升 AI 推理能力的有希望方向。所提出的方法旨在提供一个通用的框架,可随着 LLM 和知识库的发展进一步增强。
结构化推理的需求
尽管 LLM 在语言理解上取得了进展,但其推理能力仍然有限。当面临包含多个不相关事实的复杂情境时,模型往往会迷失或遗漏关键信息。
-
混乱的情境包括处理问题/任务所需的相关事实,以及推理过程中不必要的无关信息。
-
相关和无关的内容混合在一起,而不是分隔成清晰的部分。
-
事实没有明确的逻辑进展或分组。顺序可能完全随机,而不是以结构化方式构建。
-
相关和无关的细节深度交织。例如,一个关键的相关句子可能夹在两个无关句子之间。
-
结构缺失意味着模型不能依赖信息的位置或顺序来确定相关性。相关事实可能出现在任何地方。
-
上下文中包含的信息密度和体量很高。许多事实跳出来,但很少有明确突出的相关信息。
-
信息过载的混乱混合使得哪些细节以有意义的方式相互关联变得模糊。它破坏了逻辑线索。
-
在没有连贯线索的情况下,模型很容易丧失关键点,被混乱所掩盖。
-
干扰破坏了推理过程,使模型错过了需要连接多个分散细节的洞察。
-
这使得基于相关信息有条理地构建理解变得具有挑战性。推理变得随机。
从本质上讲,混乱的背景使模型过载于信号和噪声的复杂交集。这种纠缠即使对结构化推理技术也造成了压力,显示出需要额外解决方案的差距。
为了解决这个问题,引入了线索提示(ToT)策略。受人类认知的启发,ToT 引导模型逐步分析上下文。这一技术提高了理解和推理能力,而无需重新训练模型。
[## Papers with Code - 线索解锁混乱背景]
目前还没有代码。
paperswithcode.com](https://paperswithcode.com/paper/thread-of-thought-unraveling-chaotic-contexts?source=post_page-----a4b8018b619a--------------------------------)
然而,ToT 提示在极端混乱的背景下单独存在局限性。本文提出将其与访问多样知识源的 RAG 系统互补。
介绍 RAG 系统
检索增强生成(RAG)结合了 LLM 的能力和可扩展检索。索引的知识源允许模型快速适应,利用少量示例学习,而不是静态编码所有信息。
我们设计了一个具有多个知识图谱(KGs)的 RAG 系统,这些知识图谱通过 LLama 索引进行索引。对于每个 KG,我们:
-
创建一个查询引擎来检索相关段落作为上下文
-
构建一个工具来封装查询引擎
-
为工具构建一个与 LLM 互动的代理
-
将所有代理组合成一个超级代理,协调 KG 工具
并行查询不同的 KGs 扩展了可用知识,而少量示例学习实现了快速适应。
from llama_index.indices.knowledge_graph import KnowledgeGraphRAGRetriever
from llama_index.agent import OpenAIAgent
from llama_index.tools import QueryEngineTool, ToolMetadata
from llama_index.llms import OpenAI
from llama_index.memory import ChatMemoryBuffer
memory = ChatMemoryBuffer.from_defaults(token_limit=1000)
# Your knowledge graphs
my_kgs = {'kg1': kg_index, 'kg2': kg_index2}
# Dictionary to store the agents
kg_agents = {}
# List to store the tools
kg_tools = []
for kg_name, kg in my_kgs.items():
# Create a query engine for the KG
query_engine = kg.as_query_engine(
include_text=True,
response_mode="tree_summarize",
embedding_mode="hybrid",
similarity_top_k=20,
)
# Create a tool for the query engine
tool = QueryEngineTool(
query_engine=query_engine,
metadata=ToolMetadata(
name=f"tool_{kg_name}",
description=f"Useful for questions related to {kg_name}",
),
)
# Add the tool to the list of KG tools
kg_tools.append(tool)
# Create an agent for the tool
agent = OpenAIAgent.from_tools([tool], system_prompt="Walk me through this context in manageable parts step by step, summarizing and analyzing as we go.")
# Add the agent to the dictionary of KG agents
kg_agents[kg_name] = agent
# Create the super agent
llm = OpenAI(model="gpt-3.5-turbo-1106")
super_agent = OpenAIAgent.from_tools(kg_tools, llm=llm, verbose=True, memory=memory, system_prompt="Therefore, the answer.")
整合 ToT 提示
为了启用结构化推理,我们将 ToT 提示集成到 RAG 系统中。
超级代理提示 LLM:
“逐步引导我通过这个上下文,将其分解为可管理的部分,并在过程中总结和分析”
这启动了对检索段落的增量分析。
然后,代理使用以下方式提取结论:
“因此,答案是:”
通过 ToT 推理和多样知识的引导增强理解,避免忽视关键事实。
实施并行检索
为了高效查询多个 KGs,我们利用异步并行检索:
import nest_asyncio
nest_asyncio.apply()
response = await super_agent.astream_chat("How to optimize value of an asset")
# Collect all tokens into a string
response_text = ""
async for token in response.async_response_gen():
response_text += token
# Print the response text
print(response_text)
与顺序查询相比,同时查询 KGs 可以加速检索。
不仅知识范围,还包括所使用的底层图算法和嵌入可以提供互补的推理能力。例如:
-
优化用于遍历的图算法,如个性化 PageRank,可以推断概念之间的间接联系。这支持更灵活的联想推理。
-
为搜索调优的算法(如近似最近邻)可以有效地找到相关实体。这有助于精确的事实查找。
-
知识图谱嵌入将符号转换为适合数学操作的向量空间。这使得类比推理和向量运算成为可能。
-
多模态嵌入结合了文本、图像、音频等。这支持视觉语义推理。
同样,嵌入的约束和训练目标也会影响推理:
-
传递嵌入通过事实链改善逻辑推理。
-
等价类嵌入将同义词汇总在一起。这有助于泛化。
-
层次嵌入捕捉类之间的分类关系。这允许基于继承的推理。
通过协调不同的算法和嵌入策略,我们在同一个总体框架内创造了不同的推理模式。语言模型可以根据每个查询灵活地使用适当的推理风格。这种增强的推理能力接近于人类认知的多面推理能力。
RAG 系统的灵活架构使得知识图谱和推理引擎可以根据不同的用途进行交织。这种紧密的框架将混合推理整合在一个屋檐下,同时保持知识来源的模块化和可扩展性。
例如,在医学知识图谱中,我们可以利用:
-
传递嵌入通过逻辑推理改善临床决策。这允许从已知交互链推断新的药物交互。
-
等价类嵌入将同义医学术语分组。这有助于不同术语(如普通术语和专家术语)之间的泛化。
-
层次嵌入用于捕捉疾病、症状和治疗的分类。这使得基于继承的推理成为可能,比如从某个特定癌症推断其母类癌症的特性。
首先查询本体
# Initial question
initial_question = "Define assets? And optimize them"
# Query the ontology with the initial question
ontology_results = ontology_engine.query(initial_question)
# Combine the initial question with the ontology results
combined_question = f"{initial_question}. {ontology_results}"
# Query the super agent with the combined question
response = await super_agent.astream_chat(combined_question)
# Collect all tokens into a string
response_text = ""
async for token in response.async_response_gen():
response_text += token
# Print the response text
print(response_text)
这允许首先利用本体获取关于查询的定义和高层次知识。本体的结果被附加到原始问题上,以提供背景。
增强的问题包含本体信息,然后输入到超级代理。这在从知识图谱中检索之前,为 RAG 系统提供初步背景。
首先查询本体提供了 RAG 系统的“热启动”,通过建立基本概念和背景。知识图谱可以随后填充更具体的关系事实,以适应具体的查询。
这种技术将本体的自上而下的层次推理与自下而上的详细知识检索结合起来。本体澄清了模糊或广泛的术语,而知识图谱则提供了关于精细查询方面的深入信息。
以这种阶段性的方法编排不同的知识源,能够在不同的抽象层次之间平滑流动。这从高层次概念逐渐推进到与问题相关的上下文细节。
结论
本文提出了一种方法,通过将思维线程(Thread-of-Thought)提示与检索增强生成系统(RAG)相结合,来增强大型语言模型在复杂背景下的推理能力。
思维线程技术提供了结构化的思维序列,用于系统地分析混乱的信息。与此同时,RAG 框架通过并行查询不同的知识图谱来扩展可获取的知识。
这种混合技术旨在协调 LLMs 与互补的外部知识源的优势。引导模型在从丰富的上下文数据中汲取信息的同时逐步推理,增强了其理解能力。
所提出的方法提供了一个通用框架来解决固有的模型限制。随着 LLMs 与知识库的不断发展,这种技术提供了一条逐步增加结构化推理能力的途径。
尽管在复制多面的人的推理方面仍面临挑战,但在提示策略、知识基础和并行架构方面的创新揭示了有前景的方向。
本文强调了创造性地结合现有方法的优势,以实现超越部分之和的 emergent 能力的价值。随着我们对 LLMs 能力的编排了解的深入,类似于本提案的元架构可能成为主流范式。
向前发展,适应性地利用大型语言模型(LLMs)以及结构化知识和推理技术仍然是推动人工智能进步的关键支柱。随着持续研究探索这些交集,人类语言理解的梦想可能逐渐接近现实。
作者提供的图像
什么是 ACID 事务?
在数据库事务的背景下理解 ACID 属性
·发布于 Towards Data Science ·阅读时间 3 分钟·2023 年 7 月 5 日
–
照片由 Michal Matlon 拍摄,来源于 Unsplash
在数据库操作的背景下,事务指的是被视为单一逻辑工作单元的操作,旨在使底层系统保持一致的状态。
一致性通过确保所有操作要么在成功完成后执行,要么在任何操作因某种原因失败时不执行来维护。
ACID 属性
数据库事务应遵循所谓的 ACID 属性。这些系统被称为事务型系统,确保每个操作,包括读取、写入或更新,都符合 ACID 属性。ACID 缩写代表 Atomiciy(原子性)、Consistency(一致性)、Isolation(隔离性)和 Durability(持久性)。
原子性:该属性指的是一个事务被视为一个单一的工作单元。如果事务中定义的任何操作失败,则不会提交任何操作,之前执行的操作将被回滚(如果有的话),底层系统将恢复到其先前的状态。原子性防止数据丢失或数据损坏,因为事务只有在每个语句都成功应用后才会提交。
一致性:该属性确保所有事务遵循预定义规则,使每个事务以可预测的方式执行,这将始终使底层系统在提交后保持一致状态。
隔离性:一个事务性数据库系统允许多个用户同时与其交互。这个属性确保由多个用户发起的并发事务不会相互干扰,从而保持数据完整性。单个事务中的任何操作在事务提交之前都不会在外部可见。
持久性:这个最后的属性确保每当事务被提交时,它将被保存并且其效果是永久性的,即使在系统故障的情况下也是如此。
如何在 SQL 中编写事务
现在我们对四个 ACID 属性有了基本了解,让我们编写一个 SQL 事务来观察这些属性的实际效果。请注意,我将使用 BigQuery 语法,这可能也适用于许多其他 SQL 方言,但无法保证。
在下面分享的示例中,我们创建了一个包含两个语句的事务。第一个语句将向表mydataset.mytable
中插入一条新记录,而第二个语句将(故意)引发错误,因为它尝试进行除以零的操作。
在我们代码片段的第二部分中,我们定义了回滚逻辑,它将撤销所有在故障之前所做的更改。由于 SELECT 1/0
语句将引发异常,INSERT INTO
语句将被回滚,新记录不会被添加到目标表中。
BEGIN
BEGIN TRANSACTION;
INSERT INTO mydataset.mytable VALUES (1, 100, 'pending');
-- Raise an exception by attempting a division by zero
SELECT 1/0;
COMMIT TRANSACTION;
EXCEPTION WHEN ERROR THEN
-- Roll back the transaction if an exception is raised
SELECT @@error.message;
ROLLBACK TRANSACTION;
END;
结论
ACID 属性是事务性数据库系统必须遵守的最基本的概念之一,以确保和维护数据的完整性、一致性和可靠性。此外,这些概念使多个用户能够同时与底层系统进行交互,而不危及这些特性。
如果你的日常工作需要与事务性数据库系统进行交互,那么熟悉这些概念是非常重要的。数据库事务提供的 ACID 属性将确保数据的完整性,并允许你以适当的方式处理故障和错误,从而不会影响其他操作和用户。
👉 成为会员 并阅读 Medium 上的每一个故事。你的会员费直接支持我和其他你阅读的作者。你还将获得对 Medium 上每个故事的完全访问权限。
[## 使用我的推荐链接加入 Medium — Giorgos Myrianthous
作为 Medium 的会员,你的部分会员费用会分配给你阅读的作者,并且你可以完全访问每个故事……
gmyrianthous.medium.com](https://gmyrianthous.medium.com/membership?source=post_page-----866265b54031--------------------------------)
👇你可能也喜欢的相关文章 👇
在数据工程的背景下,ETL 与 ELT 的比较
towardsdatascience.com ## 什么是 dbt(数据构建工具)
对正在主宰数据世界的 dbt 的温和介绍
towardsdatascience.com ## Python 中的图表作为代码
使用 Python 创建云系统架构图
towardsdatascience.com
神经网络与深度学习的激活函数
原文:
towardsdatascience.com/activation-functions-non-linearity-neural-networks-101-ab0036a2e701
解释神经网络如何学习(几乎)任何和所有事物
·发表于 Towards Data Science ·8 分钟阅读·2023 年 10 月 12 日
–
机器学习图标由 Becris — Flatico 创建。 www.flaticon.com/free-icons/machine-learning
背景
在我之前的文章中,我们介绍了 多层感知器 (MLP)*,它只是堆叠互连的 感知器 的集合。如果你不熟悉感知器和 MLP,我强烈建议你查看我之前的文章,因为这篇文章将会详细讨论:
神经网络及其构建模块的介绍
levelup.gitconnected.com](https://levelup.gitconnected.com/intro-perceptron-architecture-neural-networks-101-2a487062810c?source=post_page-----ab0036a2e701--------------------------------)
一个具有两个隐藏层的示例 MLP 如下所示:
一个基本的双隐藏层多层感知器。图示由作者提供。
然而,MLP 的问题在于它只能拟合一个 线性分类器。这是因为各个感知器具有 阶跃函数 作为其 激活函数,这是一种线性函数:
感知器,这是最简单的神经网络。图示由作者提供。
尽管堆叠我们的感知器看起来像是现代神经网络,但它仍然是一个线性分类器,与普通的线性回归没有太大区别!
另一个问题是,它在整个领域范围内并非完全可微分。
那么,我们该如何处理呢?
非线性激活函数!
为什么我们需要非线性?
什么是线性性?
让我们快速说明一下线性性的含义,以建立一些背景。数学上,如果一个函数满足以下条件,则认为它是线性的:
作者用 LaTeX 编写的方程。
还有另一个条件:
作者用 LaTeX 编写的方程。
但我们将使用之前的方程进行演示。
以这个非常简单的例子为例:
作者用 LaTeX 编写的方程。
所以函数 f(x) = 10x 是线性的!
如果我们在上述方程中添加一个偏置项,它将不再是线性函数,而是仿射函数。请参见这个 statexchange 线程了解其区别。
现在,考虑以下情况:
作者用 LaTeX 编写的方程。
所以函数 f(x) = 10x² 是 非 线性的。如我们大多数人所知,这个函数是二次的,具有抛物线形状:
作者总结。
示例抛物线。图形由作者在 Python 中生成。
绝对不是线性的!
神经网络中的线性性
我们已经回答了为什么神经网络需要非线性,但让我们深入探讨一下这个概念以及它对我们模型的意义。
每个感知器的输出可以用以下数学公式表示:
作者用 LaTeX 编写的方程。
其中 f 是步进函数(激活函数),y 是感知器的输出,b 是偏置项,n 是特征数量,w_i 和 x_i 是权重 及其对应的输入值。这个方程只是上面感知器图示的数学版本。
现在,假设我们的激活函数是恒等函数,换句话说,我们没有任何函数。使用这一概念,现在考虑一个前馈的双隐藏层 MLP 中间层有两个神经元(忽略偏置项):
两层前馈神经网络。作者用 LaTeX 编写的方程。
你可能会想知道这意味着什么。
好吧,我们在这里做的就是将 2 层 MLP 压缩成一个单层 MLP!上面推导出的最终方程仅仅是具有特征 x_1 和 x_2 及其对应系数的线性回归模型。
因此,尽管 MLP 是“两层”的,它会变成单层,从而成为传统的线性回归模型!这不好,因为神经网络无法对数据进行建模或拟合复杂函数。
通过允许 MLP 使用非线性激活函数,我们满足了所谓的 通用逼近定理。这个定理基本上表示 MLP,或者更准确地说是神经网络,可以逼近和拟合任何函数。如果你想了解更多,查看 这里!
线性并不一定是坏事,更重要的是许多现实世界的问题是非线性的,因此如果我们想预测这些现象,就需要非线性模型。
激活函数的另一个要求是需要可微分,以便启用 梯度下降。
激活函数
现在让我们快速浏览一些最常见的非线性激活函数。
Sigmoid
Sigmoid 函数 在业界非常知名,源于 二项分布。它将输入“挤压”到 0 和 1 之间的输出,因此可以被视为概率,并且具有“S 形”曲线。然而,最重要的是它是非线性的,因为它包含了一个除法和一个指数函数。
Sigmoid 函数在数学上和视觉上看起来如下:
方程由作者在 LaTeX 中生成。
作者总结。
Sigmoid 函数。图表由作者在 Python 中生成。
然而,Sigmoid 函数在前沿神经网络中并未使用,原因如下:
-
消失梯度问题 — 从上述图中可以看到,在极端正值或负值时,梯度接近零。这在训练神经网络时是一个问题,因为它会减缓学习和收敛的速度。
-
未以零为中心 — Sigmoid 函数在 0 和 1 之间,因此它始终是正的。这在使用基于梯度下降的算法时是一个问题,因为它会朝一个方向推动或下降。
-
计算开销大 — 计算指数运算并不高效,因此这个函数特别是在大型神经网络中被计算数千次时,开销很大。
Tanh
Tanh 是一个常见的三角超越函数,它将输入映射到 -1 和 1 之间。因此,它是以零为中心的,相比于 Sigmoid 函数有所改进。
Tanh 函数在数学上和视觉上看起来如下:
由作者用 LaTeX 编写的方程。
作者的概要。
Tanh 函数。由作者在 Python 中生成的图。
尽管以零为中心,tanh 函数也面临与 sigmoid 函数类似的问题:
-
梯度消失问题 — Tanh 的梯度在正负极值处也接近零。这在训练神经网络时是一个问题,因为它会减缓学习和收敛速度。
-
计算开销大 — Tanh 需要计算更多的指数函数,这使得它在计算上比 sigmoid 更低效。
ReLU
修正线性单元 (ReLU) 是最受欢迎的激活函数,因为它计算高效并且解决了上述的梯度消失问题。
在数学上和视觉上,它看起来像:
由作者用 LaTeX 编写的方程。
作者的概要。
ReLU。由作者在 Python 中生成的图。
尽管 ReLU 激活函数相比于 sigmoid 和 tanh 函数有许多优点,但它也存在一些新问题:
-
死亡神经元 — 存在一种“死亡 ReLU 问题”,即由于对任何小于零的值都设定为零,导致神经元对任何输入始终输出零。
-
无界值 — 梯度可以达到正无穷大。这在极端情况下可能导致计算问题。
-
不以零为中心 — ReLU 也不以零为对称,其均值为正。这可能在训练过程中引发问题。
Leaky ReLU
我们可以调整 ReLU 函数以生成‘Leaky’ ReLU,其中负输入不为零,而是有一个浅斜率的梯度 α:
由作者用 LaTeX 编写的方程。
作者的概要。
Leaky ReLU。由作者在 Python 中生成的图。
Leaky ReLU 通过拥有少量的梯度来避免死亡神经元问题,从而使神经元能够继续学习。更不用说它避免了梯度消失问题,并且计算上更高效。然而,与其他函数一样,它也有一些缺陷:
-
斜率选择:Leaky 斜率的梯度没有规定,需要手动选择。不过,它可以通过超参数调整进行优化。
-
不以零为中心 — 与普通 ReLU 相似,它也不以零为对称。
-
无界值 — 与 ReLU 类似,其梯度可以达到正无穷大,实际上是无界的。
其他
在这里,我列出了四种最常见的激活函数,然而还有一些其他函数,例如:
-
Swish: 这是由 Google 开发的 ReLU 函数的可微近似,swish(x) = x * sigmoid(x)。
-
门控线性单元(GLU):主要用于递归神经网络。
-
Softmax:主要用于多项分类问题。
一种激活函数并不一定优于另一种,最好是尝试各种激活函数,看看哪一种最适合你的模型。
总结与进一步思考
要使神经网络满足通用逼近定理,它需要包含一个非线性激活函数,否则你可以将其压缩到单层,基本上就是一个线性回归模型。有很多合适的激活函数,最常用的是 ReLU。然而,这并不是一个硬性规则,你应该尝试各种函数以找到最适合你模型的那一个。
本文中使用的完整代码可以在我的 GitHub 上找到:
[## Medium-Articles/Data Science Basics/activation_functions.py at main · egorhowell/Medium-Articles
我在 Medium 博客/文章中使用的代码。通过创建一个账户来贡献于 egorhowell/Medium-Articles 的开发…
github.com](https://github.com/egorhowell/Medium-Articles/blob/main/Data%20Science%20Basics/activation_functions.py?source=post_page-----ab0036a2e701--------------------------------)
参考资料与进一步阅读
另一件事!
我有一个免费的新闻简报,Dishing the Data,每周分享成为更好的数据科学家的小贴士。没有“废话”或“点击诱饵”,只有来自实际数据科学家的纯粹可操作见解。
[## Dishing The Data | Egor Howell | Substack
如何成为更好的数据科学家。点击阅读 Egor Howell 的 Dishing The Data,这是一个 Substack 出版物,内容包括…
newsletter.egorhowell.com](https://newsletter.egorhowell.com/?source=post_page-----ab0036a2e701--------------------------------)
与我联系!
适应现有的 LLM 项目以使用 LangChain
原文:
towardsdatascience.com/adapting-existing-llm-projects-to-use-langchain-cd07028c01b0
将 OpenAI 调用重构为更强大的 LangChain 功能
·发布于 Towards Data Science ·6 分钟阅读·2023 年 7 月 1 日
–
恭喜你,你拥有一个令人自豪且准备向世界展示的 LLM 概念验证项目!也许你直接使用了 OpenAI 库,或者你使用了不同的基础模型和 HuggingFace transformers。无论哪种方式,你都付出了辛勤的努力,并在寻找下一步。那可能意味着代码重构、支持多个基础模型,或者添加更高级的功能,如代理或向量数据库。这时 LangChain 就派上用场了。
LangChain 是一个开源框架,用于处理 LLM 和在其基础上开发应用程序。它有 Python 和 JavaScript 版本,支持多个基础模型和 LLM 提供商。它还提供了许多实用函数,用于处理常见任务,如文档分块或与向量数据库交互。你是否相信这值得一试?看看这篇出色的文章,这是了解可能性的一个很好的起点:
## 使用 LangChain 入门:构建 LLM 驱动应用程序的初学者指南
一个 LangChain 教程,用于在 Python 中构建基于大型语言模型的应用程序
towardsdatascience.com
本文将不关注绿色田野开发,而是专注于重构现有应用程序。它也会假设你对 LangChain 有一定的基础理解,但会提供相关文档的链接。具体来说,我将讨论重构我编写的项目AdventureGPT,这是一个用于玩 1977 年巨大洞穴冒险文字冒险游戏的自主代理。如果你对这个项目感兴趣,可以查看我之前关于它的文章:
## AdventureGPT: 使用 LLM 支持的代理玩文字冒险游戏
我最感兴趣的几个重构领域:
-
使用Chains而不是直接的 OpenAI 调用
-
替换自定义文档工具以处理 LangChain Document/Data
这些问题将依次解决。
让我们首先了解什么是链。链是一种将多个提示处理技术组合成一个单元的方法,结果是一次基础模型调用。一旦有了有效的链,可以将链组合在一起,使用它们来完成更复杂的任务。
LangChain 提供了几种不同类型的链,本篇文章专注于 LLMChain 和 ConversationChain。LLMChain 是最简单的链类型,将提示模板与 LangChain 支持的 LLM 对象结合在一起。ConversationChains 则提供了定制化的对话工作流体验,例如聊天机器人。ConversationChain 的一个主要特性是能够包括记忆,并将对话的过去部分轻松存储到提示中。
提示模板是 LangChain 最强大的功能之一,允许你在提示中包含变量。在手动完成此任务时,可以使用 f-strings 结合字符串连接和自定义 repr 方法来处理数据并将其插入到基础模型的提示中。使用提示模板,你可以通过用括号转义变量来格式化字符串。这就是你需要做的全部。
根据你创建的链的类型,一些变量会默认为你设置,例如对话历史或用户输入。这可能会涉及相当复杂的内容。在传统的对话提示中,有来自系统、AI 助手和用户或人类的消息。当手动编写提示时,你会使用“System”、“Human”和“AI”等标签来标记这些消息。LangChain 可以为你处理这些,使你可以使用 ChatPromptTemplate 方法 from_messages
,允许你将每条消息指定为对象列表,从而实现更高层次的抽象和自动历史记录包含及格式化。
所有这些强大功能都带来了复杂性的代价。与其仅仅用文本调整提示,还需要阅读详尽的文档,并可能扩展现有代码以适应特定的用例。例如,对话提示通常只包括用户的输入和对话历史作为变量。然而,我希望在我的提示中包含额外的游戏上下文,以供负责与游戏世界互动的 PlayerAgent 使用。在将额外变量添加到我的提示后,我遇到了以下错误:
Got unexpected prompt input variables. The prompt expects ['completed_tasks', 'input', 'history', 'objective'], but got ['history'] as inputs from memory, and input as the normal input key. (type=value_error)
我做了一些调查,发现了一个现有的 GitHub 问题,描述了我遇到的确切问题,但没有明确的解决方案。没有气馁,我查看了 ConversationChain 类的源代码,发现有一个特定的方法用于验证是否只传入了预期的变量。我创建了一个新类,继承了原始类,并重写了这个方法。拿到我的 CustomConversationChain 类后,我还需要指定 ConversationalMemoryBuffer 应该利用哪个变量来处理用户(或在我的案例中,游戏)的输入,因为有多个输入变量。通过 input_key 实例变量这一步很简单,结果一切顺利。
一旦我完成了将 OpenAI 调用转换为链的工作,就到了处理文档摄取方式的时候。作为游戏循环的一部分,我接受了一个 walkthrough 文本的路径,然后将其转换为由 PlayerAgent 完成的游戏任务。当我第一次添加这个功能时,我只是把整个 walkthrough 传递给提示,并希望能有所成效。随着我发现更复杂的 walkthrough,这种做法已不再可行,因为 walkthrough 的长度超出了 OpenAI 为 ChatGPT 允许的上下文窗口。因此,我将文本切分为 500 个标记的块,并多次运行提示,将 walkthrough 转换为游戏任务。
当我说我将文本按大约 500 个标记进行拆分时,我是非常粗略地使用 Python 的字符串 split
方法对文本进行标记化(这是一种非常粗略的近似,与大多数 LLM 标记化文本的方法不符),然后通过 String 类的 join
方法将标记数组重新转换为字符串。虽然这样做有效,但 LangChain 提供了更好的解决方案。
LangChain 能以多种方式拆分文本。对大多数人来说,最相关的是按标记拆分,因为它保留了文档的流。有关按标记拆分文本的不同方法,有一整页文档 在这里。许多 NLP 库支持标记化,但我选择了 LLM 原生解决方案 tiktoken,这也是第一个描述的方法。只需几行代码就能更轻松且更有效地拆分文本,同时保留空白。
这只是 LangChain 能够处理的文档准备的一部分。它还能够将文本块转换为适合存储在向量数据库中的文本嵌入,以便稍后检索和包含在提示中。我计划在项目的未来中进行这项工作,包括将相关的供应示例块添加到 PlayerAgent。
LangChain 是一个强大的开源框架,提供了一系列功能和实用工具,用于与 LLMs 进行工作和开发应用程序。无论你使用的是 OpenAI 库还是其他基础模型,LangChain 都支持多种基础模型和 LLM 提供商,使其成为你项目的多功能选择。
尽管 LangChain 相比于原始提示管理可能引入了一些复杂性,但它提供了众多好处,并简化了与 LLMs 的交互。它标准化了流程,并提供了有用的工具来增强你的提示并最大化所选择 LLM 的潜力。
如果你有兴趣了解 LangChain 如何在实际项目中实现,你可以查看更新后的 AdventureGPT 代码库,该库利用 LangChain 来重构和改进现有应用程序。
[## GitHub - oaguy1/AdventureGPT at langchain
通过在 GitHub 上创建一个账户来贡献于 oaguy1/AdventureGPT 的开发。
github.com](https://github.com/oaguy1/AdventureGPT/tree/langchain?source=post_page-----cd07028c01b0--------------------------------)
总体而言,LangChain 是一个对开发人员非常有价值的资源,提供了一个全面的框架和广泛的功能来增强 LLM 驱动的应用程序。探索 LangChain,释放你 LLM 项目的全部潜力!
添加一行 SQL 语句以优化你的 BigQuery 表格
原文:
towardsdatascience.com/add-one-line-of-sql-to-optimise-your-bigquery-tables-304761b048f0
聚类:一种简单的方法,用于将相似的行分组,防止不必要的数据处理
·发表于 Towards Data Science ·阅读时长 5 分钟·2023 年 12 月 8 日
–
在我之前的文章中,我解释了如何使用分区来优化 SQL 查询:
## 使用分区,卢克!一种简单且经过验证的优化 SQL 查询的方法
如果你曾经写过一个运行时间非常长的 SQL 查询,这篇文章适合你
towardsdatascience.com
现在,我正在写续集!(有爸爸笑话吗?)
这篇文章将探讨聚类:另一种强大的优化技术,你可以在 BigQuery 中使用。与分区类似,聚类可以帮助你编写更高效的查询,使查询运行更快、成本更低。如果你想提升你的 SQL 工具包并建立更高级的数据科学技能,这是一个很好的起点。
什么是聚类表?
在 BigQuery 中,聚类表是一种将相似的行在物理“块”中分组的表。
例如,设想一个名为user_signups
的表格,用于跟踪所有在虚构网站上注册帐户的人员。它有四列:
-
registration_date
:用户创建帐户的日期 -
country
:用户所在的国家 -
tier
:用户的计划(“免费”或“付费”) -
username
:用户的用户名
如果我们愿意,我们可以通过country
对表格进行聚类,使得来自同一国家的用户在表格中彼此靠近存储:
图片由作者提供
如你所见,表中的每个“块”包含来自特定国家的用户。聚簇表仍然包含相同的数据,只是以更高效的方式排序。
聚簇加速了我们的查询,因为这意味着 BigQuery 需要处理的数据更少。
当你查询一个聚簇表时,BigQuery 会首先识别执行查询所需的相关块。这一步骤——称为块修剪——使你的查询更快且成本更低,因为 BigQuery 不会对无关的块执行不必要的操作。它只使用实际需要的块/数据。
为了看到这一点的好处,让我们假设我们的user_signups
表包含 1,000,000 行,并且我们想要获取来自黎巴嫩且在 2023-12-01 注册的用户。我们会写:
SELECT *
FROM user_signups
WHERE
country = 'Lebanon'
AND registration_date = '2023–12–01'
当我们运行那段代码时,BigQuery 会(理论上)从找到相关块(在这种情况下,就是包含来自黎巴嫩的用户的块)开始,然后筛选该块以找到registration_date
等于“2023-12-01”的行。它不需要读取表中的所有行,只需读取相关聚簇中的行。
重要提示:聚簇不会总是按照你期望的方式工作(特别是对于小表格)
图片由Madison Oren提供,来源于Unsplash
BigQuery 很聪明。
它知道创建(和修剪)聚簇/块需要计算能力,有时管理这些聚簇所需的工作量太大,无法带来任何性能提升。
因此,BigQuery 不一定会为聚簇列中的每个不同值创建一个新的块(1)。正如数据工程师Alessandro所写,
“聚簇不同于分区。事实上,没有保证每个列值会有一个聚簇……这也是为什么 BigQuery 不能在运行查询之前给你一个数据使用量的良好估算(就像它对分区所做的那样)。”
从这个角度考虑:如果你的整个表只有 10 行,那么扫描整个表可能比进行块修剪和管理聚簇的过程要更快。BigQuery 知道这一点,所以它不会在聚簇上浪费资源。
要获得聚簇的好处,你的表需要有多大?根据一位前谷歌工程师的说法,
“如果每个[你想要聚簇的组]的数据少于 100MB,聚簇对你帮助不大。”
不得不说,值得注意的是,如果你在非聚类列上进行查询,聚类将无法帮助你的查询(例如,如果我在上述查询中没有包含WHERE country = ...
筛选条件,我们将无法在country
列上应用块裁剪)。
创建聚类表
创建聚类表非常简单。只需在CREATE TABLE
语句的末尾添加CLUSTER BY
子句:
CREATE TABLE `myproject.mydataset.clustered_table` (
registration_date DATE,
country STRING,
tier STRING,
username STRING
) CLUSTER BY country; # Add this
你可以通过最多四列进行聚类,并且(与分区不同)你不限于INT64
或DATE
列;你还可以通过如STRING
和GEOGRAPHY
等数据类型的列进行聚类。
这就是将我们的user_signups
表按两列进行聚类的效果:
作者提供的图片
结合使用聚类和分区以获得最佳性能
在我之前的文章中,我介绍了分区:一种基于日期或整数将表格拆分成物理分区的方法。
幸运的是,将聚类与分区结合起来非常简单。
下面是一个示例,展示了我们的user_signups
表,现在按registration_date
进行分区,并按country
进行聚类:
作者提供的图片
当你查询表格时,BigQuery 会首先尝试应用分区裁剪(以识别相关的分区),然后在相关分区内应用块裁剪以查找相关的行。
这也是我如此喜欢使用 BigQuery 的原因之一:它将两者的优点结合在了一起!
想找一个练习 SQL 的地方吗?
如果你喜欢这篇文章,你可能会喜欢我的网站the-sql-gym.com,它包含了 100 多个 SQL 练习题。如果你想提升你的 SQL 技能,赶紧看看吧!
感谢阅读,欢迎通过Twitter或LinkedIn与我联系! 😃
检索增强生成(RAG)
学习如何使用一种基于提示的技术,即检索增强生成,将你自己的专有数据添加到预训练的 LLM 中
·发布于 Towards Data Science ·21 分钟阅读·2023 年 9 月 29 日
–
照片由 Joshua Sortino 提供,来源于 Unsplash
介绍
大型语言模型(LLMs)了解很多关于世界的知识,但它们并不知晓所有信息。由于训练这些模型需要很长时间,因此它们最后一次训练的数据可能相当陈旧。尽管 LLMs 了解互联网上的通用信息,但它们不知道你的专有数据,而这通常是你在基于 AI 的应用中所需的数据。因此,将 LLMs 扩展到新数据上,最近在学术界和行业中成为了一个重要的研究领域也就不足为奇了。
在这个大型语言模型的新纪元之前,我们通常通过简单地微调模型来扩展模型的新数据。但现在我们的模型大得多,训练的数据也更多,微调仅适用于少数几种场景。当我们想让我们的 LLM 以不同的风格或语气进行交流时,微调表现特别好。一个很好的微调例子是 OpenAI 将其较旧的完成式 GPT-3.5 模型适配成其较新的对话式 GPT-3.5-turbo(ChatGPT)模型。如果给完成式模型输入“你能告诉我关于寒冷天气帐篷的事吗”,可能会扩展提示:“以及其他寒冷天气露营装备?”另一方面,他们的对话式模型可能会这样回答:“当然!它们设计用来承受低温、高风和雪……”在这种情况下,OpenAI 的重点不是更新模型可以访问的信息,而是改变它与用户对话的方式。为此,微调确实效果显著!
然而,微调在向大型模型添加新数据时表现不佳,这在我发现的商业场景中更为常见。此外,微调 LLM 需要大量高质量数据、用于计算资源的巨额预算以及大量时间——这些对于大多数用户来说都是稀缺资源。
在这篇文章中,我们将介绍一种称为“检索增强生成”(RAG)的替代技术。该方法基于提示,由 Facebook AI Research (FAIR) 和合作伙伴于 2021 年引入。RAG 的概念足够强大,以至于 Bing 搜索和其他高流量网站都在其模型中融入了当前数据,而且又足够简单,以至于我可以在这篇博客文章中深入解释给你。即使你没有大量的新数据、巨额预算或很多时间,它也很有效。
你可以在与这篇博客文章相关的 GitHub repo 中找到三种简单 RAG 场景的代码实现:一种直接使用 OpenAI APIs,另一种使用开源 LangChain API,第三种实现则使用开源 Semantic Kernel API。我将在这篇博客文章中展示并解释第一个场景的代码,并鼓励你在自己的时间里浏览其他实现。
但在我们深入代码之前,让我们先了解 RAG 的基本概念。
RAG 概述
检索增强生成的实现可能有所不同,但在概念层面上,在基于 AI 的应用中使用 RAG 涉及以下步骤:
-
用户输入一个问题。
-
系统搜索可能回答问题的相关文档。这些文档通常由专有数据组成,并存储在某种文档索引中。
-
系统创建一个 LLM 提示,将用户输入、相关文档和 LLM 的指令结合起来,让 LLM 使用提供的文档回答用户的问题。
-
系统将提示发送到 LLM。
-
LLM 根据我们提供的上下文向用户的问题返回答案。这是我们系统的输出。
这是这个一般概念的图示:
我已经给你提供了 RAG 的口语含义,但在实现细节上有所欠缺。让我们深入研究一下介绍这一概念的论文,以开始掌握具体细节。
RAG 研究论文
RAG 这个术语首次由 FAIR 和学术合作伙伴于 2021 年引入,在他们题为 Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks 的论文中提出。作者提出的这些理念对我们今天使用的行业解决方案产生了巨大影响,因此值得了解。
下面是论文中介绍的架构概览:
我们将在这篇文章中深入探讨这一架构的每一部分。从高层次来看,提出的结构由两个组件组成:检索器和生成器。检索器组件使用查询编码器将输入文本转换为浮点数序列(向量),使用文档编码器以相同方式转换每个文档,并将文档编码存储在搜索索引中。然后,它在搜索索引中搜索与输入向量相关的文档向量,将文档向量转换回文本,并将这些文本作为输出返回。生成器然后接受用户的输入文本和匹配的文档,将它们合并为一个提示,并根据文档中的信息请求 LLM 对用户输入的回复。该 LLM 的输出即为系统的输出。
你可能注意到查询编码器、文档编码器和 LLM 在上图中都以类似方式表示——这是因为它们都是使用 transformers 实现的。传统的 transformers 由两部分组成:编码器和解码器。编码器负责将输入文本转换为大致捕捉单词含义的向量(或向量序列);解码器负责根据输入文本生成新文本。在论文的架构中,查询编码器和文档编码器使用仅编码器 transformers 实现,因为它们只需将文本片段转换为数字向量。生成器中的 LLM 使用传统的编码器-解码器 transformers 实现。
这种架构是如何训练的?论文建议使用预训练的变换器,并仅对查询编码器和生成器 LLM 进行联合微调。这种微调是使用用户输入和 LLM 预期输出的对应对进行的。文档编码器没有进行微调,因为这样做成本较高,而且作者发现对于良好的性能来说并不必要。
论文提出了实现这种架构的两种方法:
-
RAG-sequence — 我们检索k个文档,并使用它们生成回答用户查询的所有输出标记。
-
RAG-token — 我们检索k个文档,使用它们生成下一个标记,然后再检索k个文档,使用它们生成下一个标记,依此类推。这意味着在生成单个答案时,我们可能会检索多个不同的文档集。
你现在对 RAG 论文中的架构有了很好的高层次理解。这种模式在行业中非常常见;然而,结果是论文中的每一个细节并不总是按照建议的方式实现。
行业中使用的 RAG
在实践中,行业内常见的 RAG 实现方式已从论文中进行了如下适配:
-
在论文中提出的两种方法中,RAG-sequence 的实现几乎在行业中总是被使用。它比另一种方法便宜且更简单,且能产生很好的结果。
-
我们通常不会对任何变换器进行微调。现有的预训练 LLM 已经足够好,不需要进一步微调,而且自己微调的成本也过高。
此外,文档搜索的方法也并不总是完全按照论文中的建议进行。搜索通常借助搜索服务进行,如FAISS或Azure Cognitive Search,这些服务支持与 RAG 配对良好的不同搜索技术。搜索服务通常由以下两个执行步骤组成:
-
检索:此步骤将用户的查询与搜索索引中的文档进行比较,并检索出最相关的文档。常见的检索技术有三种:关键词搜索、向量搜索和混合搜索。
-
排名:这是一个可选步骤,跟随检索步骤进行。它对检索到的相关文档列表进行排序优化。
让我们详细了解这些内容,从三种检索类型开始。
关键词搜索
查找与用户查询相关的文档的最简单方法是进行“关键词搜索”(也称为“全文搜索”)。关键词搜索使用用户输入文本中的精确术语来搜索索引中的匹配文档。匹配仅基于文本进行,不涉及向量。尽管这种技术已经存在了一段时间,但今天仍然相关。这种搜索在寻找用户 ID、产品代码、地址和任何需要高精度匹配的数据时非常有用。以下是这种实现的高层次图示:
在这种情况下,我们的搜索服务保留一个反向索引,该索引将单词映射到使用这些单词的文档。用户的文本输入会被解析以提取搜索词,并分析以找到这些词的标准形式。然后扫描反向索引中的搜索词,每个匹配项都会被评分,最后从搜索服务中返回最相关的匹配文档。
Vector search
“向量搜索”(也称为“密集检索”)与关键词搜索的不同之处在于,即使文档中没有搜索词,它也能找到匹配项,但总体思路类似。例如,假设你正在构建一个支持房产租赁网站的聊天机器人。如果用户询问“你有没有推荐靠近海边的宽敞公寓?”而某个特定房产的文档包含“4000 平方英尺的海景房”这一文本,关键词搜索将无法识别为匹配,但向量搜索会识别。向量搜索在我们搜索非结构化文本以获取一般概念时效果最佳,而不是精确的关键词。
这是 RAG 与向量搜索的高层次概述:
如图所示,行业中使用的向量搜索与 RAG 论文中的提议基本相同。只是在这种情况下,我们不对变换器进行微调。
我们通常使用预训练的嵌入模型,如 OpenAI 的 text-embedding-ada-002 来编码查询和文档,并使用预训练的 LLM,如 OpenAI 的 gpt-35-turbo(ChatGPT)来生成最终输出。嵌入模型用于将输入文本和每个文档转换为相应的“嵌入”。什么是嵌入?它是一个浮点数向量,大致捕捉了它所编码文本的一般思想。如果两个文本相关,我们可以假设它们的嵌入向量是相似的。
我们如何确定两个向量是否相似?我们来通过一个例子来解答这个问题。我们假设使用了我们的嵌入模型来计算以下嵌入向量:
-
a = (0, 1) 表示“你有没有推荐靠近海边的宽敞公寓?”
-
b = (0.12, 0.99) 表示“4000 平方英尺的海景房”
-
c = (0.96, 0.26) 表示“我想要一个甜甜圈”
我们可以将这些向量绘制在图表中:
从图像中,你可以直观地判断出最接近a的向量是b(而不是c)。从编程的角度看,计算向量相似性有三种常见的方法:点积、余弦相似性和欧几里得距离。让我们使用这些方法计算相似性,并看看是否能验证我们的直觉。
点积方法,如你所料,简单地计算两个向量之间的点积(也称为内积)。结果越大,向量之间的相似性就越高:
a和b之间的点积大于a和c之间的点积,这确认了我们的直觉,即a和b更为相似。请记住,点积受到向量长度的强烈影响,实际上只有在所有向量长度相同的情况下才测量相似性。OpenAI 的嵌入向量长度总是为一,为了与 OpenAI 保持一致,我们的示例嵌入向量也具有相同的长度。
余弦相似性计算两个向量之间夹角的余弦值,余弦值越大,向量越相似。计算公式类似于点积,但向量在计算后会被其长度的乘积除以。因此,余弦相似性通过考虑方向而忽略长度,实质上测量了两个向量之间的相似性。
由于我们的向量长度为一,点积和余弦相似性产生完全相同的结果,如上面的计算所示。
欧几里得距离测量两个向量之间的距离,按照通常的定义。两个向量之间的距离越小,它们的相似性就越高。与余弦相似性不同,欧几里得距离考虑了向量的长度和方向。
如你所见,a和b之间的距离小于a和c之间的距离,这意味着a和b最为相似。
我们的示例使用二维向量,以便获得一些直观的视觉效果,但嵌入向量通常具有更多的维度。例如,OpenAI 的text-embedding-ada-002模型生成 1536 维的向量。请记住,用于嵌入的维度数量与输入文本的长度无关,因此短查询和长文档都将生成具有相同维度数量的嵌入向量。
目前大多数搜索服务都支持我们讨论的三种相似性方法,并允许你选择想要使用的方法。你应该选择哪种相似性技术?
-
如果你的嵌入向量长度不同,并且你希望考虑这些长度,那么欧氏距离是最佳选择,因为它考虑了长度和方向。
-
如果你的嵌入向量已经被归一化到单位长度,那么所有三种解决方案将给出相同的排序,就像你所看到的。然而,点积计算稍微便宜一点。如果你正在使用的应用或服务知道它处理的是单位长度向量,那么它的余弦相似度实现很可能已经被优化为使用与点积相同的计算方法。所以在这种情况下,余弦相似度的计算与点积计算一样便宜。
为了找到给定输入向量的前文档向量,我们的搜索服务可以简单地使用暴力计算来计算输入向量与每个文档向量之间的相似度,然后选择前几个匹配项。然而,这种简单的算法无法扩展到具有大量文档向量的大型企业应用。因此,搜索服务通常使用某种类型的近似最近邻(ANN)算法,它们通过巧妙的优化在更短的时间内提供近似结果。ANN 的一个流行实现是分层可导航的小世界(HNSW)算法。
混合搜索
混合搜索包括关键词搜索和向量搜索的同时使用。例如,假设你有一个客户 ID 和一个文本输入查询,你想进行一种搜索,以同时捕捉客户 ID 的高精度和用户文本的一般含义。这是混合搜索的完美场景。混合搜索将这两种搜索类型分别执行,然后使用一个算法将每种技术的最佳结果进行组合。这种方法在行业中经常使用,尤其是在更复杂的应用中。
语义排序
语义排序(也称为“重新排序”)是检索文档后的一个可选步骤。检索步骤尽力根据文档与用户查询的相关性对返回的文档进行排序,但语义排序步骤通常可以改进这一结果。它从检索返回的文档中取出一个子集,使用专门训练的 LLM 计算更高质量的相关性得分,并根据这些得分重新排序文档。
在上面的图像中,我展示了与向量搜索结合的语义排序,但你也可以很容易地将其与关键词搜索结合。我决定在图中展示与向量搜索结合的语义排序,因为这是我在这篇文章的代码中实现的解决方案,我们将接下来查看这个解决方案。
RAG 的简单实现
在这一部分,我们将查看将自定义数据添加到 ChatGPT 的代码,使用 RAG 和 Azure Cognitive Search。我在这里展示的代码使用 OpenAI API 直接与 ChatGPT 交互,但在同一个 GitHub 项目 中,你还会找到两个类似的实现:一个使用 LangChain,另一个使用 Semantic Kernel。这两个是帮助开发者使用大型语言模型构建应用程序的流行开源框架。
这个项目的目标是创建一个聊天机器人,供用户利用它获取有关我们公司销售的产品的更多信息。我们将使用的 数据 包含了若干个关于我们产品的 markdown 文件。
我们首先查看 init_search_1.py:
"""
Initializes an Azure Cognitive Search index with our custom data, using vector search
and semantic ranking.
To run this code, you must already have a "Cognitive Search" and an "OpenAI"
resource created in Azure.
"""
import os
import openai
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents.indexes.models import (
HnswParameters,
HnswVectorSearchAlgorithmConfiguration,
PrioritizedFields,
SearchableField,
SearchField,
SearchFieldDataType,
SearchIndex,
SemanticConfiguration,
SemanticField,
SemanticSettings,
SimpleField,
VectorSearch,
)
from dotenv import load_dotenv
from langchain.document_loaders import DirectoryLoader, UnstructuredMarkdownLoader
from langchain.text_splitter import Language, RecursiveCharacterTextSplitter
# Config for Azure Search.
AZURE_SEARCH_ENDPOINT = os.getenv("AZURE_SEARCH_ENDPOINT")
AZURE_SEARCH_KEY = os.getenv("AZURE_SEARCH_KEY")
AZURE_SEARCH_INDEX_NAME = "products-index-1"
# Config for Azure OpenAI.
AZURE_OPENAI_API_TYPE = "azure"
AZURE_OPENAI_API_BASE = os.getenv("AZURE_OPENAI_API_BASE")
AZURE_OPENAI_API_VERSION = "2023-03-15-preview"
AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
AZURE_OPENAI_EMBEDDING_DEPLOYMENT = os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT")
DATA_DIR = "data/"
def load_and_split_documents() -> list[dict]:
"""
Loads our documents from disc and split them into chunks.
Returns a list of dictionaries.
"""
# Load our data.
loader = DirectoryLoader(
DATA_DIR, loader_cls=UnstructuredMarkdownLoader, show_progress=True
)
docs = loader.load()
# Split our documents.
splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.MARKDOWN, chunk_size=6000, chunk_overlap=100
)
split_docs = splitter.split_documents(docs)
# Convert our LangChain Documents to a list of dictionaries.
final_docs = []
for i, doc in enumerate(split_docs):
doc_dict = {
"id": str(i),
"content": doc.page_content,
"sourcefile": os.path.basename(doc.metadata["source"]),
}
final_docs.append(doc_dict)
return final_docs
def get_index(name: str) -> SearchIndex:
"""
Returns an Azure Cognitive Search index with the given name.
"""
# The fields we want to index. The "embedding" field is a vector field that will
# be used for vector search.
fields = [
SimpleField(name="id", type=SearchFieldDataType.String, key=True),
SimpleField(name="sourcefile", type=SearchFieldDataType.String),
SearchableField(name="content", type=SearchFieldDataType.String),
SearchField(
name="embedding",
type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
# Size of the vector created by the text-embedding-ada-002 model.
vector_search_dimensions=1536,
vector_search_configuration="default",
),
]
# The "content" field should be prioritized for semantic ranking.
semantic_settings = SemanticSettings(
configurations=[
SemanticConfiguration(
name="default",
prioritized_fields=PrioritizedFields(
title_field=None,
prioritized_content_fields=[SemanticField(field_name="content")],
),
)
]
)
# For vector search, we want to use the HNSW (Hierarchical Navigable Small World)
# algorithm (a type of approximate nearest neighbor search algorithm) with cosine
# distance.
vector_search = VectorSearch(
algorithm_configurations=[
HnswVectorSearchAlgorithmConfiguration(
name="default",
kind="hnsw",
parameters=HnswParameters(metric="cosine"),
)
]
)
# Create the search index.
index = SearchIndex(
name=name,
fields=fields,
semantic_settings=semantic_settings,
vector_search=vector_search,
)
return index
def initialize(search_index_client: SearchIndexClient):
"""
Initializes an Azure Cognitive Search index with our custom data, using vector
search.
"""
# Load our data.
docs = load_and_split_documents()
for doc in docs:
doc["embedding"] = openai.Embedding.create(
engine=AZURE_OPENAI_EMBEDDING_DEPLOYMENT, input=doc["content"]
)["data"][0]["embedding"]
# Create an Azure Cognitive Search index.
index = get_index(AZURE_SEARCH_INDEX_NAME)
search_index_client.create_or_update_index(index)
# Upload our data to the index.
search_client = SearchClient(
endpoint=AZURE_SEARCH_ENDPOINT,
index_name=AZURE_SEARCH_INDEX_NAME,
credential=AzureKeyCredential(AZURE_SEARCH_KEY),
)
search_client.upload_documents(docs)
def delete(search_index_client: SearchIndexClient):
"""
Deletes the Azure Cognitive Search index.
"""
search_index_client.delete_index(AZURE_SEARCH_INDEX_NAME)
def main():
load_dotenv()
openai.api_type = AZURE_OPENAI_API_TYPE
openai.api_base = AZURE_OPENAI_API_BASE
openai.api_version = AZURE_OPENAI_API_VERSION
openai.api_key = AZURE_OPENAI_API_KEY
search_index_client = SearchIndexClient(
AZURE_SEARCH_ENDPOINT, AzureKeyCredential(AZURE_SEARCH_KEY)
)
initialize(search_index_client)
# delete(search_index_client)
if __name__ == "__main__":
main()
我们的第一个任务是加载 markdown 数据文件,并将其拆分为 6000 个字符的块,每块之间有 100 个字符的重叠。每个数据块将在后续由单个嵌入编码,并添加到搜索索引中。如果我们的文件足够小,我们可以不拆分它们,直接对每个文件进行编码。但如果文件很大,最好将其拆分,因为我们的嵌入有固定大小,无法很好地捕捉所有信息。我们在块之间添加一些重叠,以免丢失跨块的想法。
接下来,我们创建一个包含每个块的字典的列表,字典中指定了块的文本、唯一 ID 和源文件的名称。然后,我们使用 OpenAI 的 text-embedding-ada-002 模型计算每个块的嵌入,并将其也插入到块的字典中。这个字典列表包含了填充搜索索引所需的所有信息。
接下来,我们使用 Azure Cognitive Search API 创建搜索索引。我们配置将要在索引中创建的字段,特别指定需要支持向量搜索的嵌入字段。我们通过设置嵌入大小为 1536(这是我们使用的 OpenAI 模型的固定值),指定要使用余弦方法进行向量相似性比较(根据 OpenAI 的推荐),并且使用 Hierarchical Navigable Small World (HNSW) 算法来加快比较搜索速度,来配置我们的向量搜索。我们还指定要为我们的上下文文本字段启用语义排序。我们给索引命名并保存。
最后,我们将数据上传到刚刚创建的搜索索引中。你可以通过访问 Azure 门户,点击你的 Cognitive Search 资源,然后点击资源名称,再点击“Indexes”来确认所有的块是否已被添加到索引中。你应该会在右侧看到你的索引列表及其文档计数,即上传到索引中的块的数量(我在我的索引中有 45 个块)。如果你点击索引名称,可以通过点击“Search Explorer”中的“Search”按钮查看上传到其中的数据。
如果你查看 LangChain 和 Semantic Kernel 文件夹中的等效 init_search 文件,你会发现它们包含的代码要少得多。直接使用 Azure Cognitive Search APIs 给你更多的索引配置控制权,而使用库作为中介则隐藏了许多复杂性。最佳选项实际上取决于你的需求。
现在我们已经建立了搜索索引,接下来我们将看到如何在聊天机器人中使用该索引。让我们看看 main_1.py 文件,这是我们聊天机器人的主要可执行文件:
"""
Entry point for the chatbot.
"""
from chatbot_1 import Chatbot
def main():
chatbot = Chatbot()
chatbot.ask("I need a large backpack. Which one do you recommend?")
chatbot.ask("How much does it cost?")
chatbot.ask("And how much for a donut?")
if __name__ == "__main__":
main()
main 函数模拟用户,向聊天机器人提出一系列问题。对于每个问题,代码简单地调用聊天机器人的 ask 函数。LangChain 和 Semantic Kernel 版本中的等效文件看起来基本相同。
最后,让我们看看 chatbot_1.py 文件,其中包含了一个记住对话历史并知道如何实现 RAG 的聊天机器人代码:
"""
Chatbot with context and memory.
"""
import os
import openai
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient
from azure.search.documents.models import Vector
from dotenv import load_dotenv
# Config for Azure Search.
AZURE_SEARCH_ENDPOINT = os.getenv("AZURE_SEARCH_ENDPOINT")
AZURE_SEARCH_KEY = os.getenv("AZURE_SEARCH_KEY")
AZURE_SEARCH_INDEX_NAME = "products-index-1"
# Config for Azure OpenAI.
AZURE_OPENAI_API_TYPE = "azure"
AZURE_OPENAI_API_BASE = os.getenv("AZURE_OPENAI_API_BASE")
AZURE_OPENAI_API_VERSION = "2023-03-15-preview"
AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
AZURE_OPENAI_CHATGPT_DEPLOYMENT = os.getenv("AZURE_OPENAI_CHATGPT_DEPLOYMENT")
AZURE_OPENAI_EMBEDDING_DEPLOYMENT = os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT")
# Chat roles
SYSTEM = "system"
USER = "user"
ASSISTANT = "assistant"
class Chatbot:
"""Chat with an LLM using RAG. Keeps chat history in memory."""
chat_history = []
def __init__(self):
load_dotenv()
openai.api_type = AZURE_OPENAI_API_TYPE
openai.api_base = AZURE_OPENAI_API_BASE
openai.api_version = AZURE_OPENAI_API_VERSION
openai.api_key = AZURE_OPENAI_API_KEY
def _summarize_user_intent(self, query: str) -> str:
"""
Creates a user message containing the user intent, by summarizing the chat
history and user query.
"""
chat_history_str = ""
for entry in self.chat_history:
chat_history_str += f"{entry['role']}: {entry['content']}\n"
messages = [
{
"role": SYSTEM,
"content": (
"You're an AI assistant reading the transcript of a conversation "
"between a user and an assistant. Given the chat history and "
"user's query, infer user real intent."
f"Chat history: ```{chat_history_str}```py\n"
f"User's query: ```{query}```py\n"
),
}
]
chat_intent_completion = openai.ChatCompletion.create(
deployment_id=AZURE_OPENAI_CHATGPT_DEPLOYMENT,
messages=messages,
temperature=0.7,
max_tokens=1024,
n=1,
)
user_intent = chat_intent_completion.choices[0].message.content
return user_intent
def _get_context(self, user_intent: str) -> list[str]:
"""
Gets the relevant documents from Azure Cognitive Search.
"""
query_vector = Vector(
value=openai.Embedding.create(
engine=AZURE_OPENAI_EMBEDDING_DEPLOYMENT, input=user_intent
)["data"][0]["embedding"],
fields="embedding",
)
search_client = SearchClient(
endpoint=AZURE_SEARCH_ENDPOINT,
index_name=AZURE_SEARCH_INDEX_NAME,
credential=AzureKeyCredential(AZURE_SEARCH_KEY),
)
docs = search_client.search(search_text="", vectors=[query_vector], top=1)
context_list = [doc["content"] for doc in docs]
return context_list
def _rag(self, context_list: list[str], query: str) -> str:
"""
Asks the LLM to answer the user's query with the context provided.
"""
user_message = {"role": USER, "content": query}
self.chat_history.append(user_message)
context = "\n\n".join(context_list)
messages = [
{
"role": SYSTEM,
"content": (
"You're a helpful assistant.\n"
"Please answer the user's question using only information you can "
"find in the context.\n"
"If the user's question is unrelated to the information in the "
"context, say you don't know.\n"
f"Context: ```{context}```py\n"
),
}
]
messages = messages + self.chat_history
chat_completion = openai.ChatCompletion.create(
deployment_id=AZURE_OPENAI_CHATGPT_DEPLOYMENT,
messages=messages,
temperature=0.7,
max_tokens=1024,
n=1,
)
response = chat_completion.choices[0].message.content
assistant_message = {"role": ASSISTANT, "content": response}
self.chat_history.append(assistant_message)
return response
def ask(self, query: str) -> str:
"""
Queries an LLM using RAG.
"""
user_intent = self._summarize_user_intent(query)
context_list = self._get_context(user_intent)
response = self._rag(context_list, query)
print(
"*****\n"
f"QUESTION:\n{query}\n"
f"USER INTENT:\n{user_intent}\n"
f"RESPONSE:\n{response}\n"
"*****\n"
)
return response
我们的聊天机器人会在内存中保留一个 chat_history 问答列表,以便在整个对话的背景下理解问题。 Chatbot 类有一个公共的 ask 函数,包含以下步骤:
-
_summarize_user_intent 函数使用 LLM 重新措辞用户的问题,同时考虑任何先前的聊天历史。为什么我们需要这一步?假设用户提出的问题本身不太有意义,但在聊天历史的背景下却很有意义;例如,如果一个问题提到“它”指代先前的主题。如果我们仅搜索与用户问题相关的文档,可能不会得到好的结果。但如果我们重新措辞用户的问题以融入缺失的历史,我们将获得更好的文档集。你会在我稍后展示的输出打印中看到一个例子。
-
_get_context 搜索我们之前创建的索引,寻找与前一步获得的用户意图相似的文档。
-
_rag 根据从我们的搜索和聊天历史中返回的文档向 LLM 请求用户查询的答案。在此步骤中,我们会更新聊天历史记录,包括用户和助手的消息。
如果你查看 LangChain 和 Semantic Kernel 文件夹中的等效文件,你会发现它们都使用模板 API 来构造发送给 LLM 的提示。你还会注意到 LangChain 内置了保持聊天历史记录的支持。另一方面,Semantic Kernel 是围绕函数(可重用的代码片段)和插件(可以被外部应用以标准化方式调用的函数集合)这一概念构建的。
你应该得到类似于以下的输出:
*****
QUESTION:
I need a large backpack. Which one do you recommend?
USER INTENT:
User's intent: The user is looking for a recommendation for a large backpack.
RESPONSE:
Based on the information in the context, the SummitClimber Backpack has a dedicated laptop compartment that can accommodate laptops up to 17 inches and it also has a hydration sleeve and tube port, making it compatible with most hydration bladders for convenient on-the-go hydration. However, it's important to keep in mind the cautionary notes and warranty information provided as well. If you're looking for a backpack larger than the SummitClimber Backpack, I don't have that information available.
*****
*****
QUESTION:
How much does it cost?
USER INTENT:
User's real intent: The user wants to know the price of the SummitClimber Backpack that the assistant recommended.
RESPONSE:
The price of the SummitClimber Backpack is $120.
*****
*****
QUESTION:
And how much for a donut?
USER INTENT:
User's real intent: This query does not seem related to the previous conversation about backpacks and may be a joke or a non-serious question.
RESPONSE:
I'm sorry, I don't understand your question. Is there anything else I can assist you with?
*****
除了问题和回答,我还会打印用户意图,以便你了解它的实用性。如你所见,第二个问题在没有聊天历史记录的情况下是模糊的,但对应的用户意图通过融入第一个问题的知识独立存在。
最后的响应展示了系统仅依靠我们提供的文档回答问题,而不是依赖 LLM 在训练过程中学到的信息。
结论
在这篇文章中,我们介绍了如何使用自定义数据扩展预训练 LLM 的 RAG 模式。我们讨论了首次提出 RAG 概念的研究论文,它如何适应行业需求,以及常用的搜索技术。最后,我们讨论了一个代码示例,展示了如何使用 OpenAI 和 Azure Cognitive Search 实现 RAG。
如果你正在寻找一个完整的基于 RAG 的聊天应用程序,包括客户端代码和企业级最佳实践,我推荐你查看我在微软的同事们创建的 Azure Chat repo。这个应用程序提供了许多常见问题的解决方案,肯定能为你节省时间。
希望这能激励你在工作中使用 RAG 模式。感谢阅读,祝你的 AI 项目好运!
注意
所有图片均由作者提供,除非另有说明。你可以在任何用途下使用这篇博客文章中的任何原始图片,并附上出处链接(指向本文)。