TowardsDataScience-2023-博客中文翻译-二十-
TowardsDataScience 2023 博客中文翻译(二十)
从 DICOM 头到 CSV 文件
DICOM 元数据 → CSV 数据集
·
关注 发表在 Towards Data Science ·7 min read·2023 年 1 月 17 日
--
将 DICOM 元数据转换为 CSV 文件。[作者提供的图像]
DICOM,即数字成像和通信医学,是医学图像的原生格式。DICOM 文件不仅包含像素数据,还将其存储为 DICOM 头。此类数据包括患者的年龄和体重、模态、图像维度等。在这篇文章中,我们将使用 PyDicom 包来构建一个 Python 函数,提取特定元数据以用于进一步的数据分析工作。
背景
如上所述,DICOM 文件包含元数据。这些数据对任何 DICOM 用户都很有用,从患者到医生以及成像系统的各种模态。此外,这些数据是数据科学家/分析师可以用来进行有益洞察的宝贵资源。
这项元数据的一个好处是它以标准化的方法表示,其中每个 DICOM 属性都有一个唯一的标签和关键字,以便用户以特定的值表示来获取信息。
让我们开始编写代码,并查看 DICOM 头部的示例…
让我们开始编写代码
我们将从一个简单的示例开始,读取 DICOM 文件以查看元数据表示。然后,我们会构建一个函数来提取数据。
互联网上有很多 DICOM 样本。你也可以使用自己的 DICOM 文件。对于这篇文章,我选择了一个包含 99 个胸部 CT 扫描切片的数据集。你可以在 Kaggle 上找到它(数据集,由 Kosarevsky Dmitry 提供。许可证:“数据库:开放数据库,内容:数据库内容”,DbCL v1.0)。我将数据集保存到我的 Drive 中,以便我可以通过 GoogleColab 方便地访问。
使用 PyDicom 读取 DICOM
在这个示例中,我们读取了第一个切片,并仅展示 DICOM 属性的一部分,因为完整的输出非常长。你可以参考这个 GitHub Gist 查看完整的输出。
# Install and import the package
!pip install pydicom
import pydicom
# Reading a DICOM file from a specific path
dcm_data = pydicom.dcmread('/content/drive/MyDrive/Datasets/Kaggle/DICOM/dicom_lung/000000.dcm')
dcm_data
Dataset.file_meta -------------------------------
(0002, 0000) File Meta Information Group Length UL: 196
(0002, 0001) File Meta Information Version OB: b'\x00\x01'
(0002, 0002) Media Storage SOP Class UID UI: CT Image Storage
(0002, 0003) Media Storage SOP Instance UID UI: 1.3.6.1.4.1.14519.5.2.1.7085.2626.214140401149739061368142942055
(0002, 0010) Transfer Syntax UID UI: Explicit VR Little Endian
(0002, 0012) Implementation Class UID UI: 1.2.40.0.13.1.1.1
(0002, 0013) Implementation Version Name SH: 'dcm4che-1.4.35'
-------------------------------------------------
(0008, 0005) Specific Character Set CS: 'ISO_IR 100'
(0008, 0008) Image Type CS: ['ORIGINAL', 'PRIMARY', 'AXIAL', 'CT_SOM5 SPI']
(0008, 0016) SOP Class UID UI: CT Image Storage
(0008, 0018) SOP Instance UID UI: 1.3.6.1.4.1.14519.5.2.1.7085.2626.214140401149739061368142942055
(0008, 0020) Study Date DA: '20100227'
(0008, 0021) Series Date DA: '20100227'
(0008, 0022) Acquisition Date DA: '20100227'
(0008, 0023) Content Date DA: '20100227'
(0008, 0030) Study Time TM: '161937.171'
(0008, 0031) Series Time TM: '162536.14'
(0008, 0032) Acquisition Time TM: '162203.028699'
...
PyDicom 封装了一个 DataSet 类,对于每个 DICOM 属性,它以标签作为 DataSet 的键,以数据元素作为值。下面的图片描述了 DataSet 和 DataElement 类的内容。

PyDicom DataSet 类的内容。[图片由作者提供]
PyDicom 包提供了许多方法和属性,可以与 DataSet 和 DataElement 类一起使用。我们不会深入讨论这些细节。如果你想了解更多,请参考之前的文章。
PyDicom… 超越像素数据!
[towardsdatascience.com
提取元数据
我们将依赖的主要思想是我们可以遍历特定文件夹中的 DICOM 文件。然后,我们将遍历每个文件中的所有属性,只提取事先指定的属性。我们将使用 DICOM 属性的关键字,因为这是最好的选择,因为:
-
每个 DICOM 属性都有其独特的关键字。
-
关键字比标签更好,因为它们是人类可读的。
有数十个属性可以表征 DICOM 文件。所以,你不能只读取所有属性。您需要只关注在工作中可能遇到的属性。为此,我强烈推荐这款优秀的DICOM 标准浏览器,该浏览器由Innolitics构建,您只需搜索您想了解的属性。确保您输入的关键词正确无误。
现在我们知道我们要做什么了,让我们提取一些 DICOM 元数据 😃
我们将需要 glob 包来操作 DICOM 文件的路径。此外,还需要 Pandas 包来从 pandas 数据框架中写入 CSV 文件。glob 模块会根据 Unix shell 使用的规则找到所有匹配指定模式的路径名,尽管结果以任意顺序返回。
唯一剩下的就是设置我们的 Python 环境,导入包并构建我们的函数。将添加函数文档,解释参数和函数输出。
dicom2csv() 函数。参考GitHub gist。
让我们试试上面的函数。首先,我们决定我们感兴趣的 DICOM 属性是什么。例如,我想从一系列胸部 CT 扫描中计算 DLP 指数。DLP 指数,剂量长度乘积是一种常用的指数,因为它能够显示出整体的剂量输出。它通常在现代设备的控制台上使用并表示,因为它能提前给出对有效剂量(ED)的粗略估计。
DLP 可以从 CTDIvol 计算出来,这是另一个 CT 指数,描述了所选 CT 运行条件下单张图像的平均剂量,单位为毫格(mGy)。剂量长度乘积(DLP)用于计算一系列扫描或完整检查的剂量,并由以下方程定义:

其中 i 用于每张图像对应的第 i 个 CTDIvol。对于 Li,我们将使用每两个连续图像之间的重建间隔,因为这实际上决定了对给定扫描长度对应的图像数。Slice Location 将帮助我们计算以毫米为单位的重建间隔。
因此,我们需要计算 DLP 的元数据是以毫格(mGy)表示的CTDIvol和每个轴向切片的以毫米表示的SliceLocation。让我们使用上面构建的函数来提取它们。
# Extract the SliceLocation and CTDIvol from DICOM files of Chest-CT
df = dicom2csv(extract = ['SliceLocation', 'CTDIvol'],
folder_path = '/content/drive/MyDrive/Datasets/Kaggle/DICOM/dicom_lung',
csv_file_name = 'data.csv',
return_dataframe = True)
注意,读取 DICOM 文件是任意的。所以,我们需要根据切片位置来对数据进行排序。
# Sort the data frame to get the slices in order
df.sort_values(by='SliceLocation', inplace=True, ignore_index=True)
df
SliceLocation CTDIvol
0 -288.0 8.550
1 -285.0 8.450
2 -282.0 8.250
3 -279.0 8.025
4 -276.0 7.850
.. ... ...
94 -6.0 12.450
95 -3.0 12.325
96 0.0 12.125
97 3.0 11.925
98 6.0 11.575
[99 rows x 2 columns]
从“SliceLocation”列的视角来看,重建间隔为 3.0 毫米,与切片厚度相同。您可以尝试以下行代码,以确保所有切片的重建间隔相同。
# Calculate the reconstruction interval from SliceLocation
[df['SliceLocation'][i+1] - df['SliceLocation'][i] for i in range(0,len(df)-1)]
回到 DLP 方程,Li 对所有切片都是相同的,为 0.3 cm。我们最终的步骤是将每个 CTDIvol 乘以 0.3,并将变量相加。
import numpy as np
# Calculating DPL = sum(CTDIvol * 0.3)
print('DLP = ',np.sum(df['CTDIvol'].values * 0.3), 'mGy*cm')
DLP = 239.90999999999994 mGy*cm
重要的是要记住,CTDIvol 或其衍生物 DLP,如在控制台上看到和在 DICOM 图像中输出的,不代表患者实际吸收的或有效的剂量。它们应被视为系统辐射输出的指数,用于比较目的。基于 DLP 计算的有效剂量仅为粗略估算。
DLP 计算只是从 DICOM 头部提取数据的一个单一示例,其用途非常广泛。我们构建的 Python 函数使处理指定的元数据变得容易。
结论
-
我们已经看到了如何使用 PyDicom 包读取 DICOM 文件,以及在分析工作中使用 DICOM 元数据的强大功能。
-
我们构建了一个函数,可以将 DICOM 头部的特定数据转换为 CSV 文件,以便进行进一步分析。
-
我们使用该函数计算了剂量长度乘积,这是 CT 模态剂量估算中常用的参数。
感谢阅读…
推荐
-
DICOM 元数据 — 大数据分析的有用资源:
这篇文章概述了通过结合患者访问和 DICOM 信息来表示数据的新方法,包括医学影像元数据的高级应用、辐射剂量分析、图像分割以及用于特征工程的深度学习,以丰富数据。
-
有关 DICOM 的更多细节,请参阅前一篇博客,什么是 DICOM?
-
从计算机断层扫描剂量指数计算 DLP,CTDIvol。
## CT 中有效剂量的简单计算器 (DLP -> Eff Dose):放射科技师指南
这里有一个简单的计算器,可以从 CT 检查的剂量长度乘积 (mGy cm) 计算有效剂量 (mSv)。
参考文献
[1] 99 个 CT 切片的数据集,dlwpt-volumetric-dicom-lung,Kaggle,Kosarevsky Dmitry,许可证:“数据库:开放数据库,内容:数据库内容”,DbCL v1.0。
[2] PyDicom 文档,用户指南,[访问时间:2022 年 12 月 25 日]
[3] PyDicom 文档,元素 VR 和 Python 类型,[访问时间:2022 年 12 月 25 日]
[4] PyDicom 文档,API 参考,[访问时间:2022 年 12 月 25 日]
[5] Innolitics,DICOM 标准浏览器,(2022),CTDIvol 属性,[访问日期 2023 年 10 月 1 日]
[6] I. A. Tsalafoutas 和 S. I. Metallidis,“一种从 CT DICOM 图像计算剂量长度乘积的方法,” BJR,第 84 卷,第 999 期,页码 236–243,2011 年 3 月,doi: 10.1259/bjr/37630380。
从乏味到惊艳:Matplotx 如何改善你的 Matplotlib 图表
简化使用 Matplotx 创建惊艳图表的过程
·发布于 Towards Data Science ·6 分钟阅读·2023 年 4 月 3 日
--

使用 matplotx 主题应用的 matplotlib 散点图示例。图片由作者提供。
Matplotib 是 Python 世界中最流行的数据可视化库之一。然而,多年来,它因创建乏味的图形和使用不便而声名狼藉。
正如我最新的文章所见,将基本的 matplotlib 图表转换成视觉上吸引人的图表确实需要几行代码。别误解我;我喜欢使用 matplotlib,因为它高度可定制,但有时我只想要一个时尚的图表,而不想过多麻烦。
这就是matplox 库的作用。通过两行代码——一个 import 语句和一个 with 语句——我们可以即时将基本的 matplotlib 图形转变为更具视觉吸引力的图形。
matplotx 库提供了一种简单的方法来即时美化你的 matplotlib 图形。这个库已经存在了一段时间。它的下载次数接近 60,000 次,并且平均每月 3,000 次下载(撰写本文时的数据)。
在这篇文章中,我们将看到如何使用这个库来为我们的 matplotlib 图形增添一些趣味。
安装 Matplotx
可以通过打开终端/命令提示符并运行以下命令,将 Matplotx 安装到你的 Python 环境中。
pip install matplotx
导入库和设置数据
在本文中,我们将通过导入两个常见的 Python 库开始:pandas 和 matplotlib。然后我们将导入 matplotx。
import pandas as pd
import matplotlib.pyplot as plt
import matplotx
一旦库被导入,我们可以从 CSV 文件中读取数据集。
在这个例子中,我使用了来自 Volve 数据集的一口井。有关此数据集的详细信息可以在文章底部找到。该数据集包含来自位于挪威大陆架上的 Volve Field 的一口井的系列井日志和岩石物理测量数据。
除了指定文件位置外,我们还需要将缺失数据值-999 转换为 NaN 值。这是通过使用na_values参数完成的。
df = pd.read_csv('/well_log_data_volve.csv', na_values=-999)
df.head()
数据加载后,我们可以使用df.head()查看 Dataframe 的头部。

显示 Volve Field 井日志测量的 Dataframe 头部。图片由作者提供。
使用 Matplotx 增强 Matplotlib 散点图
现在数据已经加载,我们将创建的第一个图表是散点图。这可以使用以下代码轻松创建。
plt.scatter(df['NPHI'], df['RHOB'], c=df['GR'])
plt.ylim(3,2)
plt.xlim(-0.15, 0.8)
plt.clim(0, 150)
plt.colorbar(label='GR')
plt.xlabel('NPHI (dec)')
plt.ylabel('RHOB (g/cc)')
plt.show()
当我们运行上述代码时,我们得到以下图表。

显示密度与中子孔隙度的基本 matplotlib 散点图。图片由作者提供。
尽管我们有一个可用的散点图,但它并没有从其他 matplotlib 散点图中脱颖而出。
使用 Matplotx 创建 Dracula 样式的 Matplotlib 散点图
要应用 matplotx 的样式,我们需要在 matplotlib 散点图代码之前添加一行代码。
使用 with 语句,我们调用plt.style.context并传入matplotx.styles,从这里我们可以选择许多可用的主题之一。
在这个例子中,我选择了 Dracula 主题——一个非常流行的主题,似乎出现在几乎每个应用中。
with plt.style.context(matplotx.styles.dracula):
plt.scatter(df['NPHI'], df['RHOB'], c=df['GR'])
plt.ylim(3,2)
plt.xlim(-0.15, 0.8)
plt.clim(0, 150)
plt.colorbar(label='GR')
plt.xlabel('NPHI (dec)')
plt.ylabel('RHOB (g/cc)')
plt.show()
在不更改之前的 matplotlib 代码的其他部分的情况下,我们可以运行它并得到以下图表。

应用流行的 Dracula 主题与 matplotx 之后的 Matplotlib 散点图。图片由作者提供。
使用 Matplotx 和 duftify 函数去除图表垃圾
在为读者创建图表时,通常需要去除图表垃圾,如网格线、边框等。这使读者能够专注于数据,而不是被其他元素分散注意力。
为了查看去除图表垃圾的影响,以下动画是由Darkhorse Analytics创建的。

通过提高数据-墨水比来使图表更有效的示例。www.darkhorseanalytics.com/blog/data-looks-better-naked
为了帮助我们去除像上面动画那样的图表垃圾,Matplotx 提供了一个很好的函数来帮助实现这一点。
要使用它,我们需要调用matplotx.styles.duftify,然后传入我们想要使用的 matplotx 样式。
with plt.style.context(matplotx.styles.duftify(matplotx.styles.dracula)):
plt.scatter(df['NPHI'], df['RHOB'], c=df['GR'])
plt.ylim(3,2)
plt.xlim(-0.15, 0.8)
plt.clim(0, 150)
plt.colorbar(label='GR')
plt.xlabel('NPHI (dec)')
plt.ylabel('RHOB (g/cc)')
plt.show()
运行上述代码后,我们得到以下图表。

应用 matplotx 的 duftify 函数后的 Matplotlib 散点图。图片由作者提供。
我们可以看到图表和色条的边框已被移除。坐标轴的标签也已被淡化,这样你的眼睛立刻会专注于数据而不是额外的内容。
使用 Matplotx 的其他样式
matplotx 提供了许多其他样式,可以在项目的 GitHub 仓库上查看。
这里仅是一些可用样式的样本:

下面是一些来自 matplotx 的样式。图片来源于 matplotx GitHub 仓库。
使用这些样式时需要记住的一点是,如果图形上名称后面有额外的方括号中的文本,那么你需要确保在调用matplotx.styles时添加这些文本。
例如,如果我们想使用东京之夜主题,我们将有三种样式可供选择:白天、夜晚和风暴。

东京之夜主题的三种样式。请注意方括号中的名称,可以用来访问该主题。图片来源于 matplotx GitHub 仓库。
从上面的描述来看,如果我们选择东京之夜(白天),那么我们需要在调用末尾添加方括号:matplotx.styles.tokyo_night['day']
with plt.style.context(matplotx.styles.tokyo_night['day']):
plt.scatter(df['NPHI'], df['RHOB'], c=df['GR'])
plt.ylim(3,2)
plt.xlim(-0.15, 0.8)
plt.clim(0, 150)
plt.colorbar(label='GR')
plt.xlabel('NPHI (dec)')
plt.ylabel('RHOB (g/cc)')
plt.show()
运行上述代码将生成以下所需样式的图表。

应用 matplotx 的东京之夜白天主题后的 Matplotlib 散点图。图片由作者提供。
将 Matplotx 应用于其他 Matplotlib 图表
我们还可以将 matplotx 样式应用到其他图表上,例如直方图。我们需要做的就是修改 matplotlib 绘图代码,以获得直方图。
with plt.style.context(matplotx.styles.pitaya_smoothie['dark']):
plt.hist(df['RHOB'], bins=50, alpha=0.8)
plt.xlim(2, 3)
plt.xlabel('RHOB (g/cc)')
plt.show()
当我们运行上述代码时,我们得到的直方图比我们常见的基本蓝色条形图要美观得多。

应用 matplotx 主题的直方图示例。图片由作者提供。
总结
尽管 Matplotlib 是一个强大的数据可视化库,但使用起来可能会比较麻烦,尤其是当你想创建令人惊叹的图形时。使用 matplotx,我们只需添加两行代码,即可瞬间将图表转变为更加出色的样子。它绝对是你在创建下一个图形时,想要快速添加一些风格时应该考虑的库。
感谢阅读。在你离开之前,你一定要订阅我的内容,将我的文章直接送入你的收件箱。 你可以在这里订阅!另外,你也可以 注册我的新闻通讯 ,以免费将额外内容直接送到你的收件箱。
其次,您可以通过注册会员,获得完整的 Medium 体验,并支持我和其他成千上万的作者。这只需每月$5,您就可以完全访问所有精彩的 Medium 文章,并有机会通过您的写作赚取收入。如果您使用 我的链接, 您将直接通过您的费用的一部分支持我,而不会增加额外的费用。如果您这样做,非常感谢您的支持!
本教程中使用的数据
本教程使用的数据是 Equinor 在 2018 年发布的 Volve 数据集的一个子集。数据集的完整详细信息,包括许可证,可以在下面的链接中找到。
[## Volve 数据集
Equinor 官方已经将北海油田的完整数据集提供用于研究、学习等…
Volve 数据许可证基于 CC BY 4.0 许可证。许可证协议的完整详细信息可以在此处找到:
从编码到嵌入
原文:
towardsdatascience.com/from-encodings-to-embeddings-5b59bceef094
概念与基础:从 SVD 到神经网络
·发表于 Towards Data Science ·16 分钟阅读·2023 年 9 月 7 日
--

版权: unsplash.com/
在本文中,我们将讨论数据表示和机器学习领域中的两个基本概念:编码和嵌入。本文的内容部分来源于我在斯坦福大学的 CS246 矿大数据集(MMDS)课程中的讲座。希望你觉得有用。
引言
所有机器学习(ML)方法都处理输入特征向量,几乎所有方法都要求输入特征是数值型的。从 ML 的角度来看,有四种类型的特征:
-
数值型(连续或离散):数值数据可以分为连续数据或离散数据。连续数据可以在一个范围内取任意值,而离散数据有明显的值。连续数值变量的例子是
身高,离散数值变量的例子是年龄。 -
分类数据(有序或无序):分类数据表示诸如眼睛颜色和家乡等特征。分类数据可以是有序的或无序的。在有序变量中,数据被分到按特定方式排序的类别中。例如
技能水平,其值为[初级,中级,高级]。无序变量在其值之间没有顺序。例如眼睛颜色,其值为[黑色,棕色,蓝色,绿色]。 -
时间序列:时间序列是一个在一定时间段内以规则间隔收集的数字序列。这些数据按时间排序,与之前的变量不同。一个例子是
美国多年房屋销售价格的平均值。 -
文本:任何文档都是文本数据,我们通常将其表示为“词袋”。
要将任何变量输入到 ML 模型中,我们必须将其转换为数值。编码和嵌入技术都可以实现这一点。
编码
编码是将原始数据(如文本、图像或音频)转换为结构化的数值格式,使计算机能够轻松处理。编码类别变量有两种方法:
1️⃣ 整数编码
2️⃣ 一热编码
3️⃣ 多热编码(这是对一热编码的扩展)
为了解释每种方法,我们通过以下示例来说明:
🎬 设想一个只有 4 部电影和 5 个特征的小型电影数据集。

图 1:电影数据集 — 图片由作者提供
正如我们所见,两个特征(发行年份、IMDB 评分)是数值型的,一个特征(标题)是文本型的,剩下的两个(提供者、IMDB 类型)是类别型的。让我们来看看编码方法如何应用于这些特征。
1️⃣ 整数编码:
这种方法为每个类别值分配一个整数。例如,如果 提供者 变量有四个不同的值 [Netflix, Prime Video, HBO Max, Hulu],我们分别将其分配整数 1、2、3 和 4:
Netflix -> 1, Prime Video -> 2, HBO Max -> 3, Hulu -> 4
这种方法的优点是提供了紧凑的表示。缺点是它暗示了不同类别之间的顺序,即
Netflix < Prime Video < HBO Max < Hulu.
因此,对于序数变量(例如 教育 取值为 ‘[文凭, 本科, 硕士, 博士]`),使用整数编码可能更有意义。然而,这仍然意味着值是均匀间隔的 🙊!! 显然,这并不理想,因此我们将转向下一种方法。
💻 在 Python 中,你可以使用各种库(如 scikit-learn 或 TensorFlow/Keras)执行整数编码。在这里,我们使用 scikit-learn 的 LabelEncoder 将类别标签编码为整数:
from sklearn.preprocessing import LabelEncoder
# Sample list of categorical labels
labels = ["netflix", "prime video", "hbo max", "hulu"]
# Initialize the LabelEncoder
label_encoder = LabelEncoder()
# Fit and transform the labels to integer values
integer_encoded = label_encoder.fit_transform(labels)
# Print the encoded labels
print(integer_encoded)
2️⃣ 一热编码:
这种方法首先应用整数编码,然后创建一个二进制向量来表示数值,例如,对于 提供者 变量,我们首先分配整数:Netflix -> 1, Prime Video -> 2, HBO Max -> 3, Hulu -> 4。然后我们为每个值创建一个长度为 4 的二进制向量,如下所示:

图 2:提供者变量的一热编码向量 — 图片由作者提供
如你所见,这种方法将特征向量的维度扩展到类别特征的取值数量 🙈!! 这可能迅速变得难以控制。
💻 在 Python 中,你可以使用像 scikit-learn 或 pandas 这样的库来执行一热编码。以下是使用 scikit-learn 的 OneHotEncoder 进行一热编码的方法:
from sklearn.preprocessing import OneHotEncoder
import numpy as np
# Sample categorical data (list of categories)
categories = ["netflix", "prime video", "hbo max", "hulu"]
# Initialize the OneHotEncoder
onehot_encoder = OneHotEncoder(sparse=False)
# Reshape the data into a 2D array (required for scikit-learn)
categories = np.array(categories).reshape(-1, 1)
# Fit and transform the categories into one-hot encoded vectors
onehot_encoded = onehot_encoder.fit_transform(categories)
# Print the one-hot encoded vectors
print(onehot_encoded)
接下来,我们也来看看这种方法的扩展。
3️⃣ 多热编码:
多热编码是对一热编码的扩展,当类别变量可以同时取多个值时。例如,有 28 种不同的 IMDB 类型,而一部电影可以同时属于多个类型,例如电影 *怪奇物语* 同时属于 剧情、奇幻、恐怖 类型。

图 3:IMDB 类型及其整数编码 — 图片由作者提供
将多热编码应用于电影类型,会得到 28 维的编码向量:

图 4:每部电影的类型的多热编码 — 作者提供的图片
我们可以明显看到,多热编码与 one-hot 编码有相同的缺点,即维度爆炸。
💻 我们可以使用 scikit-learn 或 pandas 在 Python 中实现多热编码。以下是使用 scikit-learn 的 MultiLabelBinarizer 进行操作的方法:
from sklearn.preprocessing import MultiLabelBinarizer
# Sample data: List of sets representing categories for each data point
data = [{'drama', 'fantasy', 'horror'}, {'comedy', 'animation', 'family'},\
{'sport'}, {'documentary'}, {'comedy', 'drama', 'biography'}]
unique_categories = ['action','comedy','short','western','drama','horror',\
'music','thriller','animation','adventure','family',\
'fantasy','sport','romance','crime','sci-fi','biography',\
'musical','mystery','history','documentary','film-noir',\
'news','game-show','reality-tv','war','talk-show','adult']
# Initialize the MultiLabelBinarizer
mlb = MultiLabelBinarizer(classes=sorted(unique_categories))
# Fit and transform the data into multi-hot encoded vectors
multi_hot_encoded = mlb.fit_transform(data)
# Print the multi-hot encoded vectors
print(multi_hot_encoded)
现在,将以上所有编码应用于电影数据集,会得到以下结果:

图 5:包含所有编码的电影数据集 — 作者提供的图片
如我们所见,在应用所有编码后,数据维度从 5 增加到 35 🙊!! 实际上,如果我们对 title 变量也进行多热编码,它将膨胀到数千或一百万!
📓要点是,对于具有大量值集的特征,one-hot 和 multi-hot 编码并不实用。在一个包含一百万个不同单词的文档语料库中,通过多热编码表示每个文档会生成如下向量:
-
高维:多热编码将生成 100 万维的向量!
-
稀疏:由于一个平均文档包含 500 个词,因此多热编码向量将会有 99.95% 的稀疏度
-
语义短缺:‘good’ 和 ‘great’ 的编码与 ‘good’ 和 ‘bad’ 的编码一样不同!
✏️ 简而言之,当类别数量较少时,使用 one-hot/multi-hot 编码;通常少于 15 个。对于类别数量以百万计的文本数据(每个单词作为一个类别),我们必须使用更高效的方法。
在本文的其余部分,我们将处理 计算词嵌入 的问题。通过这个例子,我们将研究几种基本的嵌入方法。
嵌入
为了解决上述问题,我们从高维稀疏向量转向短的稠密向量;这些向量被称为 嵌入。嵌入是将高维向量转换为低维空间的过程,并捕捉语义相似性。
“使用稠密且低维向量的一个好处是计算上的:大多数神经网络工具包与非常高维的稀疏向量兼容性差。” [5]
首先让我们来看一种非常简单的嵌入方法,称为 SVD。
奇异值分解 (SVD)
最简单的嵌入方法可能是 奇异值分解 (SVD),它将输入矩阵 A 分解为以下三个矩阵:

图 6:SVD 分解 — 作者提供的图片
U, V 分别是左奇异向量和右奇异向量。它们是列正交的,这意味着它们中的每一列的范数为 1,并且 U 中的每两列(以及 V 中的每两列)是正交的。用数学语言表示为

U 和 V 定义了一个 r 维子空间,因此将A投影到它们上面产生 r 维嵌入:

🗒 让我们一起看一个例子 🗒:
给定一个文档语料库,我们可以使用 SVD 来计算文档嵌入和词嵌入。步骤如下:
步骤 1:将其转换为词袋(BOW)向量,并获得词-文档矩阵。我们可以使用词频(tf),或使用 tf-idf 技术进行归一化。

图 7:词频矩阵 — 作者提供的图片
步骤 2:对词-文档矩阵应用 SVD,并选择一个值r < rank(A)。这将创建三个矩阵,每个矩阵的秩为 r。

图 8:矩阵 A 的 SVD 分解 — 作者提供的图片
步骤 3:计算文档的嵌入,如下所示
emb = [<doc, v1> , <doc, v2> , <doc, v3>]
下图显示了前两个点积。

图 9:第一个文档的嵌入 — 仅显示了两个点积操作 — 作者提供的图片
一旦我们计算了所有三个向量的点积,文档的嵌入就是:

图 10:第一个文档的嵌入 — 作者提供的图片
类似地,我们可以计算词(术语)嵌入为emb = [<term, u1> , <term, u2> , <term, u3>]。

图 11:第一个术语的词嵌入 — 作者提供的图片
我们可以证明,这些表示将相似的术语和文档分组在三维空间中。相关或具有相似上下文的术语和文档在这个降维空间中往往具有相似的表示。
💻 在 Python 中,我们使用 scikit-learn 将文档语料库转换为 tf-idf 矩阵,然后对其应用 SVD。以下是一个示例:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.decomposition import TruncatedSVD
import numpy as np
# Sample documents (corpus)
documents = [
"This is the first document.",
"This document is the second document.",
"And this is the third one.",
"Is this the first document?",
]
# Step 1: Apply TF-IDF
tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix = tfidf_vectorizer.fit_transform(documents)
# Step 2: Perform SVD on TF-IDF matrix
n_components = 2 # Number of components (adjust as needed)
svd = TruncatedSVD(n_components=n_components)
svd_matrix = svd.fit_transform(tfidf_matrix)
# Print the TF-IDF matrix and SVD results
print("TF-IDF Matrix:")
print(tfidf_matrix.toarray())
print("\n")
print("SVD Matrix:")
print(svd_matrix)
总的来说,SVD 是一种简单且强大的技术,用于保持语义信息,但在实际数据集上并不实用。它计算量大,并且完全没有利用输入矩阵的稀疏性。让我们看看如何在另一种方法中避免这些缺陷。
神经网络作为嵌入器
🌟 目前最先进的嵌入器之一是神经网络(NN)。有许多神经网络技术可以计算词嵌入:Word2Vec、Glove、BERT、fastText 等。本文讨论了Word2Vec,该方法由托马斯·米科洛夫及其在谷歌的同事们开发,并在 2013 年至 2014 年间发表的一系列论文中介绍了该方法(论文)。
Word2Vec 是一种统计、自监督、与任务无关的方法。它有两种变体:连续词袋模型(CBOW)和 Skip Gram。这两种变体非常相似,都使用一个只有一层隐藏层且没有激活函数的浅层神经网络来学习词语表示。在本文中,我们研究 skip-gram 模型。
🔑 Word2Vec 的关键思想是具有相似上下文的词语具有相似的含义。一个词在某些其他词的上下文中出现得越频繁,它们的含义就越接近。一个词的上下文是它左边和右边的几个词。形式上,给定一个文档,我们设置一个窗口大小(例如,window_size = 2)。那么对于文档中的任何给定词(称为目标词),它左边的window_size 个词和右边的window_size 个词就是它的上下文。例如:

图 12:目标词和上下文词(窗口大小 = 2) — 图片由作者提供
给定一个文档,我们可以从左到右滑动窗口,找到所有的(目标词, 上下文词) 对。考虑以下文档:
文档:“我读科幻书籍并喝橙汁”,
设窗口大小 = 2。那么 (目标词, 上下文词) 的集合如下图所示。图片中高亮的词是目标词,框中的其他词是上下文词。

图 13:在文档上滑动窗口以提取(目标词,上下文词) — 图片由作者提供
现在,我们知道了目标词和上下文词的含义,让我们看看 Word2vec 如何使用它们。Word2vec 是一个 2 层神经网络,它以目标词作为输入,并预测窗口中的所有上下文词。以下是窗口大小 = 2 的架构示意图:

图 14:窗口大小 = 2 的 skip-gram 架构 — 图片由作者提供
现在,我们可以猜测为什么这个架构被称为 skip-gram🙂… 这是因为它预测上下文中的所有词组,除了目标词,因为目标词是模型的输入,所以这个名字叫做 skip-gram。
现在,让我们专注于只预测一个上下文词,并深入细节。设V = 词汇表的大小和N = 嵌入维度,即唯一隐藏层的大小。则用于预测一个上下文词的架构是:

图 15:用于预测一个上下文词的 skip-gram 架构 — 图片由作者提供
🧬输入层是目标词的 one-hot 编码,即 w(t);由于我们在词汇表中有 V 个词,输入层是 V 维的。隐藏层是 N 维的,并生成嵌入。通常 N << V,例如在一个网页文档的语料库中,我们有数百万个词汇(V 级别为百万级),而 N 在 256 到 512 之间。隐藏层的目的是将词映射到一个更低的维度,同时保持不同词之间的分离。这个层不使用任何激活函数。原因是大多数激活函数涉及在一个区域“压缩”空间,而在另一个区域“扩展”空间,例如 sigmoid/tanh 会将所有值 < -1“压缩在一起”,所有值 >1 同样如此。RELU 会将所有值 <0“压缩在一起”,从而消除了代表能力的一半。如你所见,这实际上会 损害 分离质量,减少可用的“词映射空间”量。输出层是 V 维的,softmax 函数作用于此层,因此输出层中的每个神经元都是一个概率,所有神经元的总和为 1。输出层的第 j 个神经元,即 y_j,表示第 j 个词是上下文词的概率。我们可以看到,这里涉及两个权重矩阵:

给定网络的拓扑结构,如果 x 是输入,y 是输出,则

网络训练完成后,我们通过将其 one-hot 向量 x 与权重矩阵 W 相乘来获得词的嵌入。
🔁网络是如何训练的? 由于没有标签,我们将创建一个虚拟任务 😃!这个虚拟任务是:
📑虚拟任务:给定一个目标词,预测其上下文词
现在,有两个问题:1)如何生成训练数据,2)选择什么损失函数来训练网络?
🗒如何生成训练数据? 我们取我们的语料库 data = {d1, d2, ….},可能会收集到数百万篇文档、维基页面、博客文章等。然后,我们对所有文档进行分词,并建立词汇表。可用的分词方法有很多,例如 workpiece、BytePairEncoding (BPE)、k-gram。最简单的方法是 k-gram。如果你取 k=1 并按词进行分词(而不是按字符),那么 1-gram 相当于按空格分割句子!分词后,我们得到一个分词文档的列表。然后,我们在分词文档上滑动窗口,并将训练数据收集为 (target, context) 的对。请参见下面的示例:

图 16:从文档中收集训练数据 — 作者提供的图像
📈选择什么损失函数? 我们使用交叉熵损失函数。原因是我们针对目标-上下文对 (w_t, w_c) 进行训练,其中 w_t 是目标词,w_c 是上下文词。上下文词 w_c 表示给定目标词 w_t 的理想预测。注意 W_c 以独热编码表示,即在某个位置 j 的值为 1,其他位置为 0:

图 17:上下文词的独热编码 — 作者提供的图片
损失函数需要在相同位置 j 评估输出层,即 y_j。(记住 y 是一个概率分布;y_j 的理想值为 1)。交叉熵损失函数定义为

对于上述示例,

图 17:示例中的交叉熵损失 — 作者提供的图片
🔥训练神经网络:既然损失函数和训练数据已经明确,我们将训练网络以学习权重。我们希望找到使损失函数最小的 W 和 W′ 的值。我们通过反向传播运行并根据损失的导数更新参数来实现这一点。这就结束了 word2vec 算法。
📊词嵌入的结果: 下面,我们看到四个词 “man,” “woman,” “king,” “queen” 的向量表示示例,这些示例说明了 Word2Vec 如何捕捉语义。在 Word2Vec 的背景下,词之间的关系可以表示为向量运算。算法捕捉到像 “king — man + woman = queen” 这样的关系。这是因为“king”的向量表示减去“man”的向量表示再加上“woman”的向量表示,结果是一个非常接近“queen”的向量表示。

图 18:词嵌入捕捉语义 — 作者提供的图片
重要的是要注意,Word2Vec 是在大型文本语料库上训练的,学习到的嵌入质量取决于训练数据的数量和质量。此外,虽然 Word2Vec 可以捕捉许多语义关系,但它可能无法捕捉语言和意义的所有可能细微差别。之后还开发了其他词嵌入算法和模型,以进一步增强对词关系的理解。
物品-物品神经协同过滤
在本节中,我们将深入探讨通过神经网络学习嵌入的另一个示例。该网络建模了物品-物品协同过滤算法。我们将通过视频推荐的用例来研究这个网络。
🎥视频推荐用例:考虑一个视频推荐的用例,我们有 100 万个视频和 50 万名用户,这些用户观看了一些这些视频,我们的任务是向用户推荐视频。我们想用神经网络解决这个问题,因此我们将其表述为多类分类,每个视频就是一个类别。我们设计一个神经网络来学习视频的嵌入,使得相似的视频具有相似的嵌入。首先让我们构建训练数据,然后设计网络架构。
🗒构建训练数据: 给定包含用户观看视频的日志,我们按时间升序排列每个用户观看的视频。然后按固定比例将其分割。我们将第一次分割作为训练数据,第二次分割作为测试数据。请注意,如果我们的分割比例是 70-30,则每个用户的数据按这个比例进行分割。通过这种基于时间的分割,我们的任务隐式地变成了‘预测用户接下来观看什么’。
另一种分割方式是避免排序,随机保留每个用户的一些视频作为测试数据,使用其余的视频构建训练数据。然而,这种方法有泄露从训练到测试的信息的风险。

图 19:视频推荐训练-测试分割 — 图片由作者提供
数据分割后,我们将训练数据构建为(movie1, movie2)对,其中两个电影都由同一个用户观看。

图 20:构建训练数据和测试数据 — 图片由作者提供
然后,我们构建一个执行项目-项目协同过滤的神经网络,同时学习 3 维嵌入!!(三维虽然太小,但为了说明目的我们继续使用它。)

图 21:项目-项目神经协同过滤 — 图片来源于[1],由作者修改
对于训练数据中的每一对(m1, m2),我们在蓝色的输入层中输入 m1 的独热向量,并在黄色输入层中输入 m1 的特征向量(包含如类型、演员、导演、受欢迎程度等元数据)。m2 的独热向量将用于计算损失函数。我们希望模型学习 m1 和 m2 之间的关联。m1 和 m2 在训练数据中出现得越频繁,它们的嵌入就会越接近。
如何向用户推荐电影? 假设 Alice 观看了 m1、m4、m5。为了向她推荐电影,我们可以找到与 m1 具有相似嵌入的电影。对于任何电影 v,我们计算相似度分数为score= <emb(m1), emb(v)>。我们找到相似度分数最高的前 5 部电影,并推荐给 Alice。更好的推荐是对 m1、m4 和 m5 重复上述过程,并推荐这些集合的交集中的电影。
这部分内容结束了关于项目-项目协同过滤的讨论。如果你想深入了解神经协同过滤,请查看这篇论文。
概述
在这篇文章中,我们讨论了编码和嵌入方法。我们了解到,编码指的是将原始数据(如文本、图像或音频)转换为一种结构化格式的过程,以便计算机能够轻松处理。这种转换通常涉及降低数据的维度,同时保留其本质特征。另一方面,嵌入则是将数据点映射到一个低维空间,其中每个点由一个连续值的向量表示。嵌入旨在捕捉数据点之间的语义关系和相似性,使算法能够有效地学习模式并做出有意义的预测。编码和嵌入在各种应用中发挥着关键作用,从自然语言处理和计算机视觉到推荐系统和异常检测,提升了数据分析和机器学习任务的效率和有效性。
如果你有任何问题或建议,请随时联系我:
电子邮件:mina.ghashami@gmail.com
LinkedIn: www.linkedin.com/in/minaghashami/
参考文献
从 ETL 过渡到 ELT
云计算和分析工程如何促使从 ETL 到 ELT 的过渡
·发表于 Towards Data Science ·7 分钟阅读·2023 年 12 月 6 日
--

图片由 DALL-E 生成
ETL(提取-转换-加载)和 ELT(提取-加载-转换)是数据工程领域中常用的两个术语,特别是在数据摄取和转换的背景下。
虽然这些术语常常可以互换使用,但它们指的是略有不同的概念,并对数据管道的设计有不同的影响。
在这篇文章中,我们将澄清 ETL 和 ELT 过程的定义,概述两者之间的差异,并讨论它们对工程师和数据团队所提供的优缺点。
更重要的是,我将描述现代数据团队组成的近期变化如何影响 ETL 与 ELT 之间的竞争格局。
理解提取(Extract)、加载(Load)和转换(Transform)的独立性
比较 ETL 和 ELT 时,主要关注的点显然是数据管道中提取(Extract)、加载(Load)和转换(Transform)步骤的执行顺序。
目前,让我们忽略这个执行顺序,专注于实际的术语,并讨论每个步骤应该做什么。
提取:这一步指的是从持久化数据源中提取数据的过程。这个数据源可以是数据库、API 端点、文件,甚至是任何包含数据的事物,无论是结构化还是非结构化数据。

提取步骤从各种来源中提取数据 — 来源:作者
转换:在这一步骤中,管道需要对数据的结构或格式进行一些更改,以实现特定目标。转换可以是属性选择、记录修改(例如,将 'United Kingdom' 转换为 'UK')、数据验证、连接到另一个来源或任何更改输入原始数据格式的操作。

转换步骤对输入的原始数据执行多项转换 — 来源:作者
加载:加载步骤指的是将数据(无论是原始版本还是转换版本)复制到目标系统的过程。通常,目标系统是数据仓库(即用于分析目的的 OLAP 系统)或应用程序数据库(即 OLTP 系统)。

将数据加载到目标系统中 — 来源:作者
不可避免地,我们执行这三个步骤的顺序很重要。随着需要处理的数据量的增加,执行顺序非常重要。让我们讨论一下原因!
提取 转换 加载 (ETL)
ETL 代表提取-转换-加载,该术语本身指的是一个过程,其中数据提取步骤之后是转换步骤,最后是加载步骤。

提取 > 转换 > 加载 — 来源:作者
ETL 过程中的数据转换步骤发生在目标系统之外的阶段环境中,数据在加载到目标系统之前进行转换。

在 ETL 中,转换步骤发生在阶段环境/服务器中,转换后的数据然后被加载到目标系统中 — 来源:作者
提取 加载 转换 (ELT)
另一方面,ELT,代表提取-加载-转换,指的是一个过程,其中提取步骤之后是加载步骤,最终的数据转换步骤发生在最后。

提取 > 加载 > 转换 — 来源:作者
相比于 ETL,在 ELT 中不需要阶段环境/服务器,因为数据转换是在目标系统内进行的,该系统通常是托管在云端的数据仓库或数据湖。

在 ELT 中,转换步骤发生在目标系统内 — 来源:作者
如何在 ETL 和 ELT 之间做选择
ETL 和 ELT 各有利弊,鉴于它们通常用于不同的用例,您很可能会在日常工作中遇到这两者。
ETL 最适合用于数据存储在本地并需要在加载到目标数据库或数据仓库之前进行结构化的用例。因此,当涉及较小的数据量和/或需要执行复杂转换时,通常更倾向于使用 ETL 过程。
此外,由于 ETL 在加载步骤之前转换数据,因此敏感数据可以在加载之前被掩码、加密或完全删除。ETL 的这一方面可以帮助公司和团队更容易地强制执行和实施各种法规(如 GDPR)。
由于转换发生在中间(暂存)服务器上,因此需要额外的开销将转换后的数据移动到目标系统。此外,目标系统不会包含原始数据(即转换前的数据形式)。这意味着每当需要额外的转换时,我们必须再次提取原始数据。
另一方面,ELT 提供了比 ETL 更多的灵活性,因为后者历史上是为了结构化(关系型)数据而设计的。现代云架构和数据仓库使得 ELT 能够处理结构化和非结构化数据。
如前所述,ETL 应用于小规模的数据。ELT 提供了更快的转换速度,因为它不依赖于数据大小,通常根据需要进行。
另外,当数据在 ELT 过程中的转换之前被加载时,这意味着用户和系统仍然可以访问原始数据。这意味着如果在后续阶段需要更多的转换,我们已经在数据仓库中拥有可以随时访问的原始数据。唯一的缺点是需要额外的存储来存储这些原始数据,但考虑到存储成本不断下降,我认为这不是一个主要问题。
现在我们对 ETL 和 ELT 过程的技术影响有了充分的了解,让我提一个问题。当选择一个而不是另一个时,仅仅是技术实施的问题吗?
这不仅仅是关于何时进行转换
此外,数据领域一直在不断发展,数据角色也不例外。ETL 与 ELT 不仅仅关乎转换步骤发生的地点——它(还)关乎谁应该执行这些步骤。
转换步骤通常涉及某种业务逻辑。传统的 ETL 过程通常由数据仓库工程师执行(不确定现在是否仍然如此),这意味着这些人也负责制定业务逻辑。
另一方面,ELT 过程的演变是由于现代数据团队的性质和形成。EL(Extract-Load)步骤通常由数据工程师执行,而转换步骤则由所谓的分析工程师执行。
对我来说,这非常有意义。数据工程师是一个纯技术人员,关注效率、可扩展性、准备性和可用性(还有其他一百万件事)。另一方面,分析工程师仍然是技术人员,但对业务的理解更好。因此,让分析工程师负责数据转换更有意义,因为(通常)转换与业务价值相关。
现代云架构、数据堆栈(包括基于云的 OLAP 系统)以及团队组建使得 ELT 流程变得更加相关和有效。根据我的个人经验,我认为尽管 ETL 仍然相关且有用,但正在经历从 ETL 到 ELT 的转变。
现代数据堆栈和团队更倾向于 ELT 流程
尽管 ETL 并没有消亡,但在我看来,现代数据堆栈和技术更倾向于 ELT 流程。例如,让我们考虑一下 dbt(数据构建工具),它是数据领域中最热门的新增工具之一,并且已经成为分析师和工程师的事实上的转换工具。
通常,我们希望将来自外部或内部数据源的原始数据(即未应用任何转换的)引入数据仓库。然后在这些数据模型的基础上(在 dbt 中我们通常称之为阶段模型),我们构建额外的模型(中间模型和数据集市),这些模型是数据仓库内某些转换过程的结果。
在这样的工作流程中,因此在转换数据之前将数据加载到仓库中更有意义。这还使得可以随时访问原始数据,以便支持未来的用例。
如果你有兴趣深入了解 dbt 是如何工作的,以及不同组件如何协同工作以转换原始数据并构建有意义的数据模型以支持决策,我推荐阅读以下文章。
最后的思考
设计数据管道是一项具有挑战性的任务,在进行时,需要仔细考虑许多因素。在将数据从数据源导入数据仓库时,通常可以采用两种方法。
在本文中,我们讨论了 ETL 和 ELT 如何执行一系列步骤,以便将数据转换和加载(或加载和转换)到目标系统中。
根据组织的情况和具体用例,你可能需要选择其中一个。我希望本教程提供了你选择最佳和最有效的数据摄取和转换方法所需的所有细节。
从评估到启示:深入了解交叉验证中的样本外预测
通过折叠外预测揭示洞察和克服局限性。
·发表于 Towards Data Science ·6 分钟阅读·2023 年 6 月 28 日
--
理解交叉验证并将其应用于实际工作是每个数据科学家的必备技能。虽然交叉验证的主要目的是评估模型性能和微调超参数,但它还提供了额外的输出值得注意。通过获取和结合每个折叠的预测,我们可以生成整个训练集的模型预测,这通常被称为样本外预测或折叠外预测。
不应忽视这些预测,因为它们包含了关于建模方法和数据集本身的宝贵信息。通过深入探索这些预测,你可能会发现诸如模型为何无法按预期工作、如何提升特征工程、以及数据中是否存在固有的局限性等问题的答案。
总体方法很简单:调查模型在高置信度下却犯错误的样本。在本文中,我将展示这些预测如何帮助我在三个实际项目中。
查找数据局限性
我曾参与一个预测性维护项目,其目标是提前预测车辆故障。我探索的一种方法是训练一个二分类器。这是一种相对简单直接的方法。
在使用时间序列交叉验证进行训练后,我检查了样本外预测。具体而言,我关注了假阳性和假阴性,即模型难以学习的样本。这些错误预测并不总是由模型自身的缺陷引起的。也有可能是某些样本之间存在冲突,导致模型混淆。
我发现几个标记为故障的假阴性案例,模型很少将它们视为故障。这一观察引起了我的好奇心。经过进一步调查,我发现许多与这些假阴性样本几乎相同的准确负样本。
下面的图 1 通过数据可视化比较了假阴性和真阴性。我不会详细讲解。这个想法是基于欧几里得距离或马氏距离在原始数据空间中运行最近邻算法;我发现与这些假阴性样本非常接近的样本都是实际的真阴性。换句话说,这些故障实例被许多好的实例所包围。


图 1. 一个假阴性与一个真阴性的比较。(图像来源于作者)
我们现在面临数据集的一个典型限制:混淆样本。要么标签错误,要么我们需要更多信息(更多维度)来进行区分。还有可能的第三种方法:将整个空间转换到一个新的空间,在那里混淆样本可以更容易地区分?在这里行不通。首先,混淆发生在原始输入数据中。就像在图像分类数据集中,一张图像标记为狗,另一张几乎相同的图像标记为猫。其次,这种思维方式是以模型为中心的,通常会增加模型的复杂性。
在向客户提出这些问题后,他们确认了标签是正确的。然而,他们也承认一些看似运行良好的车辆可能会在没有任何前兆的情况下突然出现故障,这很难预测。我发现的假阴性样本完美地展示了这些意外故障。
通过对交叉验证中样本外预测的分析,我不仅对问题和数据有了更深刻的理解,还为客户提供了展示数据集局限性的具体例子。这对我和客户都是宝贵的见解。
启发性特征工程
在这个项目中,客户希望使用车辆的道路数据来分类某些事件,例如车辆本身的变道或前方车辆的加速和变道。数据主要是从不同声呐传感器收集的时间序列数据。一些关键的信息是周围物体的相对速度和自车与周围车辆及车道的距离(在 x 和 y 方向上的距离)。还有通过摄像机录制的事件,由标注人员进行标注。
在对前方车辆变道事件进行分类时,我遇到了几个有趣的情况:模型将事件标记为正在发生,但实际情况却不一致。从数据科学的角度来看,它们是具有非常高概率预测的假阳性。
为了向客户提供模型预测的可视化表示,我向他们展示了短动画,如图 2 所示。模型会错误地将前方车辆标记为‘换车道’的时间段大约是 19:59 到 20:02。


图 2. 事件检测的动画。 (图片由作者提供)
为了解决这个谜题,我观看了与这些实例相关的视频。结果发现道路在那些时刻是弯曲的!假设车道是直的,那么模型就是正确的。模型做出错误预测是因为它从未学习过车道可能是弯曲的。
数据中没有包含有关周围车辆与车道之间距离的信息。因此,模型被训练使用周围车辆到自身车辆的距离以及自身车辆到车道的距离来确定它们相对于车道的位置。为了修复这些情况,模型必须知道车道的曲率。与客户沟通后,我在数据集中发现了曲率信息,并基于几何公式构建了测量周围车辆和车道距离的显式特征。现在,模型性能提升,不会再产生这种误报。
纠正标签错误
在第三个示例中,我们旨在预测特定的机器测试结果(通过或失败),这可以被框定为一个二分类问题。
我开发了一个性能非常高的分类器,表明数据集应该有足够的相关信息来预测目标。为了改进模型并更好地理解数据集,让我们关注交叉验证中的样本外预测,其中模型会犯错误。假阳性和假阴性是值得深入探索的宝贵资源。

图 3. 混淆矩阵。 (图片由作者提供)
图 3 是一个阈值相对较高的混淆矩阵。这三个假阳性意味着模型会将它们标记为失败,但实际情况是它们是好的。我们可以像上述示例那样通过改进特征工程来修复它们,或者提出这个问题:如果给定的标签是错误的,而模型实际上是正确的呢?人们会犯错。就像其他列的值可能是异常值或缺失值一样,目标列本身也可能嘈杂并容易出现不准确。
我不能轻易地通过最近邻方法的证据来展示这三个样本是错误的,因为数据空间很稀疏。于是我与客户讨论了数据标注的问题。我们一致认为,用于确定测试结果的一些标准存在缺陷,并且一些样本的标签可能是错误的或未知的。经过清理后,这三个样本的标签得到了修正,模型性能得到了提升。
我们不能总是责怪数据质量。但请记住,这两个方面对于你的数据科学工作同样重要:改善模型和修正数据。不要把所有精力都放在建模上,假设所有提供的数据都是无误的。相反,关注这两个方面是至关重要的。来自交叉验证的样本外预测是发现数据问题的强大工具。
欲了解更多信息,labelerrors.com列出了流行基准数据集中的标签错误。
结论
交叉验证不仅仅提供一个评分,它还有多个用途。除了数值评估外,它还提供了从预测结果中提取有价值见解的机会。通过仔细分析成功的预测,我们可以更好地理解模型的优势,并识别出最具影响力的特征。同样,分析失败的预测可以揭示数据和模型的局限性,并激发潜在的改进思路。
我希望这个工具能在提升你的数据科学技能方面发挥重要作用。
如果你认为这篇文章值得点赞,我会非常开心。如果你喜欢,可以多点几次;谢谢!

时间序列的数据科学
查看列表6 个故事!

从 GeoJSON 到网络图:在 Python 中分析世界国家边界
利用 NetworkX 进行基于图的国家边界分析
·发布于 Towards Data Science ·阅读时间 7 分钟·2023 年 10 月 15 日
--

Maksim Shutov 在 Unsplash
Python 提供了广泛的库,使我们能够轻松快捷地解决各种研究领域的问题。地理空间数据分析和图论是 Python 提供强大实用库的两个研究领域。在这篇文章中,我们将对世界边界进行简单的分析,特别是探索哪些国家彼此接壤。我们将首先利用包含所有国家多边形的 GeoJSON 文件的信息。最终目标是创建一个使用 NetworkX 表示各种边界的图,并利用该图进行多种分析。
GeoJSON 数据导入:读取和加载全球国家数据
GeoJSON 文件能够表示各种地理区域,并广泛应用于地理分析和可视化。我们分析的初始阶段涉及读取 countries.geojson 文件,并使用 GeoPandas 将其转换为 GeoDataFrame。该文件来源于以下 GitHub 仓库,包含了代表世界各国的多边形。
[## GitHub - datasets/geo-countries: 作为 GeoJSON 的国家多边形数据包
作为 GeoJSON 的国家多边形数据包。通过创建帐户来为 datasets/geo-countries 的开发做出贡献…
包含全面国家信息的 GeoDataFrame(图片由作者创建)
如上所示,GeoDataFrame包含以下列:
-
ADMIN:表示地理区域的行政名称,如国家或地区名称。 -
ISO_A3:代表 ISO 3166–1 alpha-3 国家代码,一个唯一识别国家的三字母代码。 -
ISO_A2:表示 ISO 3166–1 alpha-2 国家代码,一个用于国家识别的两字母代码。 -
geometry:此列包含定义地理区域形状的几何信息,表示为MULTIPOLYGON数据。
你可以使用plot方法可视化构成GeoDataFrame的所有多边形,如下所示。

GeoDataFrame 的可视化表示(图片由作者创建)
计算多边形坐标:纬度和经度
geometry列中的多边形属于shapely.geometry.multipolygon.MultiPolygon类。这些对象包含各种属性,其中之一是centroid属性。centroid属性提供MULTIPOLYGON的几何中心,并返回一个表示该中心的POINT。
随后,我们可以使用此POINT提取每个MULTIPOLYGON的纬度和经度,并将结果存储在GeoDataFrame中的两列中。我们进行此计算,因为稍后我们将使用这些纬度和经度值根据其实际地理位置在图中可视化节点。
创建国家边界网络图
现在是时候继续构建一个表示全球不同国家之间边界的图。在这个图中,节点将代表国家,而边将表示这些国家之间边界的存在。如果两个节点之间存在边界,图中将有一条连接它们的边;否则,将没有边。
函数create_country_network处理GeoDataFrame中的信息,并构建一个表示国家边界的Graph。
最初,函数遍历GeoDataFrame的每一行,每行对应一个国家。然后,为国家创建一个节点,并将纬度和经度作为属性添加到节点中。
如果几何体无效,它将使用buffer(0)方法进行修正。此方法通过应用一个距离为零的小缓冲区操作来修复无效几何体。此操作解决了多边形表示中的自交或其他几何不规则性问题。
在创建节点之后,下一步是为网络添加相关的边。为此,我们遍历不同的国家,如果表示两个国家的多边形之间有交集,则意味着它们共享一个共同的边界,因此在它们的节点之间创建一条边。
可视化构建的国家边界网络
下一步是可视化创建的网络,其中节点代表全球的国家,而边表示它们之间存在边界。
函数 plot_country_network_on_map 负责处理图 G 的节点和边,并在地图上显示它们。

国家边界网络(图像由作者创建)
图中节点的位置是由国家的经纬度坐标确定的。此外,背景中放置了一张地图,以提供更清晰的创建网络的背景。这张地图是使用 GeoDataFrame 的 boundary 属性生成的。这个属性提供了表示国家几何边界的信息,帮助创建背景地图。
需要注意一个细节:在使用的 GeoJSON 文件中,有些岛屿被视为独立国家,即使它们在行政上属于某个国家。这就是为什么你可能会看到海域中有很多点。请记住,创建的图依赖于生成它的 GeoJSON 文件中的信息。如果我们使用不同的文件,生成的图将会不同。
探索见解:利用国家边界网络回答问题
我们创建的国家边界网络可以迅速帮助我们解决多个问题。下面,我们将概述通过处理网络提供的信息可以轻松得出的三种见解。然而,这个网络可以帮助我们解答的其他问题还有很多。
见解 1:检查选定国家的边界
在这一部分,我们将直观评估特定国家的邻国。
plot_country_borders 函数可以快速可视化特定国家的边界。这个函数生成一个包含所提供输入国家及其邻国的子图。接着,它会可视化这些国家,使得观察特定国家的邻国变得非常容易。在这个实例中,选择的国家是墨西哥,但我们可以很容易地调整输入以可视化其他国家。

墨西哥的国家边界网络(图像由作者创建)
从生成的图像中可以看出,墨西哥与三个国家接壤:美国、伯利兹和危地马拉。
见解 2:边界最多的前 10 个国家
在本节中,我们将分析哪些国家拥有最多的邻国并将结果显示在屏幕上。为此,我们实现了 calculate_top_border_countries 函数。该函数评估网络中每个节点的邻国数量,并仅显示邻国最多的节点(前 10 名)。

拥有最多边界的前 10 个国家(图像由作者创建)
我们必须重申,获得的结果依赖于初始的 GeoJSON 文件。在这种情况下,Siachen 冰川被编码为一个独立的国家,这就是为什么它被显示为与中国共享边界。
见解 3:探索最短的国家间路线
我们通过路线评估结束我们的分析。在这种情况下,我们将评估从起始国到目的国旅行时必须穿越的最小边界数量。
find_shortest_path_between_countries 函数计算从起始国到目的国的最短路径。然而,需要注意的是,这个函数仅提供可能的最短路径之一。这一限制源于它使用了 NetworkX 的 shortest_path 函数,由于算法的特性,它固有地找到单一的最短路径。
要访问两个点之间的所有可能路径,包括多个最短路径,可以选择其他选项。在 find_shortest_path_between_countries 函数的上下文中,可以探索诸如 all_shortest_paths 或 all_simple_paths 的选项。这些替代方案能够返回多个最短路径,而不仅仅是一个,具体取决于分析的要求。
我们使用该函数来查找西班牙和波兰之间的最短路径,分析结果显示,从西班牙到波兰旅行所需的最小边界穿越数量为 3。

从西班牙到波兰的最佳路线(图像由作者创建)
总结
Python 提供了大量跨领域的库,这些库可以无缝集成到任何数据科学项目中。在这个实例中,我们利用了专门用于几何数据分析和图形分析的库来创建一个表示世界边界的图形。随后,我们展示了如何利用这个图形快速回答问题,使得地理分析变得轻松自如。
感谢阅读。
Amanda Iglesias
从黑客到和谐:在推荐中构建产品规则
不要让启发式规则削弱你的机器学习,学会将它们结合起来
·
关注 发表在 Towards Data Science ·6 min read·2023 年 9 月 23 日
--
在当今的数据驱动环境中,推荐系统驱动了从社交媒体信息流到电子商务的所有内容。尽管很容易认为机器学习算法承担了所有的重任,但这只是故事的一半。现实世界中的系统通常依赖于机器学习和启发式规则的混合——通常被称为产品规则、业务规则或简单的黑客——以生成最相关的推荐。
例如:
-
你不能过于频繁地推荐来自同一艺术家的曲目;
-
你应该在信息流中包含订阅内容,但不要使其过于繁重;
-
如果用户已经对某一类别或作者表示不喜欢,相关内容应受到惩罚或甚至被过滤掉;
-
明确的内容不能被推荐——除非情况适当。

照片由 Cam Bradford 提供,发布在 Unsplash
规则有两种类型:硬规则和软规则。硬规则作为过滤器,禁止在特定上下文中推荐某些文档;不符合规定被视为产品缺陷。这些规则本身没有什么错误,但它们的数量应该受到限制。此外,它们应该尽可能早地应用于排名过程,最好是在候选生成阶段,甚至在索引构建过程中。另一方面,软规则更像是指导方针:你可以推荐这些项目,但最好不要推荐过多(或者相反,多推荐也可以)。拥有过多的软规则可能会使系统调试和开发变得非常具有挑战性。
规则是技术债务。
我发现系统中此类规则的数量通常依赖于团队内部的权力动态。产品经理通常发现通过规则表达约束很方便,而工程师通常不喜欢这些权宜之计。在我之前的团队中,我为我们能够将这些规则的数量保持在最低限度而感到自豪。
在我的职业生涯中,我经常遇到一个反复出现的模式。工程团队难以训练系统,以产生良好的推荐(无论是整体还是在特定方面)。产品团队随后转而使用他们最熟悉的方法——添加新规则。这些补丁在需要快速修复时是合理的,但后期很难去除。系统通常会保持这种补丁状态,直到进行重大重构,就像常规的技术债务一样。
故事的寓意——不要吝啬于聘请强大的工程师🙂
在一个理想的系统中,不应该有这样的规则;所有模糊的逻辑都应该由足够先进的模型处理。我梦想着有一天我们能达到这种技术状态(我有一个关于如何实现它的假设)。然而,就目前而言,这并不现实。因此,与其完全禁止这些规则,我将讨论一种允许某种组织并限制混乱的方法。
一种结构化的方法:重新排名框架
这个框架允许将机器学习模型与产品规则执行结合起来,帮助构建这些规则,同时避免完全混乱。然而,它是灵活的,不会过于限制,因此不能保证完全的秩序。在某种程度上,它只是描述规则的一种语言。在我看来,它相当方便。
在本讨论中,我们将重点关注排名的最终阶段,此时剩余文档不多——例如,从几十个到几百个——我们希望从中编制出最佳列表。这个阶段的有趣之处在于,我们不仅仅是在尽可能准确地评估每个文档在当前上下文中的表现,还在考虑这些文档之间的相互组合。这时,listwise 排名方法就发挥了作用(与用于学习排名的 listwise 学习不同,后者仅仅是损失函数依赖于查询中的所有文档,而排名函数并不依赖)。这种 listwise 方法的一个典型应用是提升结果的多样性。
下面是该方法的关键原则。
-
结果是通过迭代生成的,从第一个位置开始,直到最后一个位置。在每次迭代中,我们选择最合适的文档放入接下来的位置。这是大多数重新排序策略的工作原理,如著名的 DPP 用于多样化。对于非线性输出,可以按重要性对位置进行排序。
-
在每次迭代中,我们将所有剩余文档按值函数进行排序。这可以从简单的点击概率模型输出到更复杂的情况:各种模型输出(或多个模型)预测的不同事件、相似性组件(如与之前文档的相似性)以及手动加权等。值函数可以在每次迭代中重新计算,因此可以依赖于位置和最终输出中已有的文档。它必须具备计算效率。设计正确的值函数本身是一个丰富的话题;该框架既不限制也不简化这一方面。
-
乘积规则表示如下:在一个位置的子集X中,具有属性f的文档数量应当高于或低于某个阈值C。通常,X是一个起始位置的范围,例如 1 到 10(第一页)。属性f最好表示为某个特征的阈值规则,即[feature(doc) > threshold]。如有必要,这种格式可以推广到包含非二元属性的情况。
-
规则具有优先级。如果我们不能满足所有规则,我们将丢弃优先级最低的规则。更准确地说:如果在给定位置可以实现最高优先级的规则,它将被严格执行;否则,将不被执行。如果在这些条件下可以实现下一个最高优先级的规则,它将被执行;否则,我们将跳过它。依此类推。换句话说,我们选择实现规则的字典序最高的掩码。
这里有一些格式规则的示例:
-
整个输出中至少一半的文档应为订阅内容。然而,如果所有订阅文档已经被阅读,这条规则将变得不可行,并会被丢弃。
-
前 10 个位置中的低质量文档数量不应超过 2 个。
-
在位置 10 和 20 之间,应该至少有一个来自新类别的文档。
值得注意的是,像“前 10 个位置中必须至少有 5 个文档具有某种属性”这样的规则,可能会导致前 5 个位置被缺乏该属性的文档填充,然后是 5 个具有该属性的文档。为了使分布更均匀,你可以为中间范围添加规则:前 2 个位置中至少有 1 个,前 4 个位置中至少有 2 个,依此类推。
高效实现这个框架是一个很好的挑战,但完全可以做到。这是一个 Python 代码草图,展示了如何实现描述的重新排序框架。请记住,这个代码并没有优化效率,但应该能提供一个很好的起点。
def rerank(documents, count, rules, scorer):
result = []
while len(result) < count and len(documents) > 0:
position = len(result)
candidates = documents
for rule in rules:
filtered = [doc for doc in candidates if rule(position, doc)]
if len(filtered) > 0:
candidates = filtered
next_doc = max(candidates, key=lambda doc: scorer(position, doc))
result.append(next_doc)
documents.remove(next_doc)
scorer.update(position, next_doc)
for rule in rules:
rule.update(position, next_doc)
return result
最后,通过记录所有执行和丢弃的规则,可以大大提升系统的可调试性和可控性。
正如我们所见,目前‘无规则’推荐系统仍然更像是一个理想而非现实。但这并不意味着我们陷入了混乱。一个结构良好的规则管理框架可以提供所需的组织,同时不会抑制系统的潜力。
从线性代数到深度学习的 7 本书(2024 年冬季更新)
我最喜欢的七本机器学习书籍
·
关注 发布于 Towards Data Science · 12 分钟阅读 · 2023 年 11 月 17 日
--
图片由 Laura Kapfer 提供,来源于 Unsplash
在我 2019 年为Towards Data Science撰写的第一篇博客中,我介绍了五本我最喜欢的机器学习书籍——涵盖了从基础线性代数到现代深度学习的各个方面。
它们包括:
-
《线性代数的正确方法》 由 Sheldon Axler 著
-
《数学统计与数据分析》 由 John A. Rice 著
-
统计学习元素 由 Trevor Hastie 等人 编写。
-
神经网络与深度学习 由 Michael Nielsen 编写。
-
深度学习 由 Ian Goodfellow 等人 编写。
我最喜欢的机器学习书籍
[towardsdatascience.com
这五本书我认为真的非常棒,各有各的原因。例如统计学习元素就是当年我还是本科生时让我对数据科学产生兴趣的原因。事实上,这五本书多年来帮助我学到了很多东西,如果没有它们,我现在不会达到这个水平。
但是,它们只是我最喜欢的数据科学和机器学习书籍的一部分,我发现并从中学到了很多,这就是为什么我想写另一批我认为同样令人惊叹并值得加入到您(虚拟)书架上的七本书。对于每一本书,我都会告诉您为什么我认为它们如此出色,它们教会了我什么,以及如何阅读它们的一些建议。
此外,许多书籍最近进行了更新,因此我还将告诉您可以从新版本中期待什么,例如如果您已经了解了旧版本。
就像在我上一篇文章中一样,我选择的书籍在难度上有所不同,因此我将从专注于基础概念的书籍开始,并将在日后涉足先进机器学习文献领域。
准备开始吧…
统计学习导论 由 Trevor Hastie 等人 编写。
在我的原始博客文章中,我提到了书籍统计学习元素,被认为是一部经典作品,是有史以来最有影响力和令人惊叹的机器学习书籍之一。
尽管涵盖了许多相当高级的主题,但令人惊讶的是,它仍然相当易于理解,但通常假定读者具有扎实的数学基础,尤其是统计学方面。
因此,同一组作者还出版了统计学习导论,这是该书的更易接近版本,虽然仍然涵盖(大部分)相同的主题。不要误会,该书仍然在必要或有帮助的地方引入了数学和统计学的基本概念,但其主要重点是建立读者对机器学习中统计方法工作原理的直觉。
在我看来,这本书是学习数据科学和机器学习的绝佳之选。如果您刚开始学习数据科学,这绝对是必读之作。它也是世界各地一些最好的数据科学入门课程的基础。
我从这本书中学到的:我对机器学习的许多直觉来自阅读 ESL 和 ISL。没有其他书籍能如此出色地阐述这些概念。特别是对于许多更高级的概念,如交叉验证和支持向量机,最初可能很难掌握。
阅读这本书的建议:如果你对数据科学是新手,请从头到尾阅读这本书。各章之间相互关联,因此在继续之前,重要的是要掌握每一章的基础知识。如果你想接受挑战,可以同时阅读 ISLR 和对应的 ESL 章节。这将立即将你从仅仅理解一个主题提升到专家级别。
这本书还附带了一个完整的 R 库,因此你可以轻松练习所有的练习题,这是一个令人惊叹的教育资源。
最近版本的新内容:去年发布了这本书的更新第二版,包含了前一版没有的新章节。这些章节大多涉及深度学习、生存分析和多重测试等高级话题。
此外,今年还发布了另一个版本的书籍,这次是官方的 Python 版本,而原版如前所述依赖于 R。
随着数据收集的规模和范围在几乎所有领域持续增加,统计学习…
Hadley Wickham 等人著作的《R for Data Science》
说到 R,R for Data Science 是另一本宝贵的开源书籍。你可以学习数据科学的“整个游戏”——从数据摄取到数据清洗,再到训练模型和传达结果。
这本书不要求有任何编码或数据科学的先前知识,你可以通过这本书学习所有内容。与 ISLR(上述书籍)相比,《R for Data Science》的方法更具实用性。你会在每一章中找到大量代码。
这本书还介绍并大量依赖于 tidyverse,这是一个精心设计的 R 库集合,用于进行数据科学。所以如果你来自 Python 并希望学习如何在 R 中操作,它也是一本很棒的(也许是最好的?)指南。
我从这本书中学到的:这本书证明了 R 是一个用于数据科学的惊人语言,特别是在学习方面更是如此。这是一本极具实用性的实践指南,我从中学到了 tidyverse 的数据科学方法。它特别关注数据处理,这是其他书籍(如 ISLR)通常没有的。直到今天,我仍然依赖于这本书中获得的数据处理知识。
如何阅读这本书:你可以从头到尾阅读它,学到大量的新知识和技巧。如果你是数据科学的新手,我绝对推荐这样做。然而,这不是我第一本数据科学书籍,所以我采取了不同的方式。我按照自己的感觉阅读了各个章节。如果你已经有一些数据科学经验(例如,因为你来自 Python),你也可以这样做——选择一个你想深入了解的主题,阅读相应的章节来加深你的知识。每一部分都是相对独立的,所以你可以很容易地这样做。
新版本的变化:这本书的第二版于今年发布,所有代码示例都进行了重构。tidyverse 在过去几年里发展迅速,因此这本书也需要更新。
这是《R for Data Science》第二版的进行中的网站。这本书将教你如何进行……
r4ds.hadley.nz](https://r4ds.hadley.nz/?source=post_page-----8df2b706a0b1--------------------------------)
(我链接了这本书的第二版,它现在应该已经完成了,但你可以在这里找到原版第一版。)
《机器学习数学》 作者:Marc Peter Deisenroth 等
如果你在寻找一本全面学习机器学习所需数学的书籍,《机器学习数学》是一个很好的选择。
机器学习建立在多个数学子领域之上,包括线性代数、统计学、概率论和优化,仅列举主要的几个。因此,为了全面学习这些内容,你通常需要通过各种不同的教材。
这正是写这本书的动机——将机器学习中常用的所有数学知识整合到一本书中。
我从这本书中学到了什么:这是一本现代数学教材,因此在做相关课程时,我将其作为参考。尽管它不是我课程的主要教材,但它覆盖了一些相同的主题。因此,我能够阅读相关章节,这帮助我更好地理解了这些内容。书中包含了良好的解释,总是将其与机器学习概念相关联(不同于通用数学教材),并且包含了很好的可视化,这也帮助我掌握了这些概念。
如何阅读这本书:最好将这本书视为数学教材。如果你正在学习线性代数、概率论、统计学或数学密集型的机器学习课程,这本书可以作为一个很好的补充资源。选择与课程对口的章节,看看这本书是否像对我一样对你有帮助。
请使用 https://mml-book.com 连接到这个网站。Twitter:@mpd37,@AnalogAldo,@ChengSoonOng。我们写了一本关于…
《百页机器学习书》 by Andriy Burkov
首先,实际上不是 100 页,而是更多一点。但无论如何,书籍确实做到了它承诺的内容,那就是一本简明但全面的机器学习关键概念和方法概述。
因此,它可能不是一个从零开始学习数据科学的首选资源,也不是非常实用。它的目的是提供重要概念的简明描述,并介绍其背后的数学和理论。
因此,如果你需要深入但又快速地理解一个概念或方法,这是一个很好的参考。
我从这本书中学到的东西:对我来说,这本书有时候像是一种自我检查。我认为如果我能从其基本描述中理解一个概念,那我应该对这个主题有了很好的理解。我觉得这本书就是关于这个的。它并不是从头教你数据科学或者机器学习,但在心理上,你可以通过阅读书中的内容总结或者重新理解一个概念。
如何阅读这本书:这本书是关于机器学习概念的最佳简明总结之一,我建议你像我一样做,即使用这本书来检查你对某个主题或概念的理解。如果你理解了这个概念,那就很完美。如果没有,你可能需要回到另一本书(如 ISLR 或 ESL)中更详细地解释这个概念。
[## 《百页机器学习书》 by Andriy Burkov
关于机器学习的一百页知识总结。有监督和无监督学习,支持向量…
themlbook.com](http://themlbook.com/?source=post_page-----8df2b706a0b1--------------------------------)
《机器学习:概率视角》 by Kevin Murphy
这是一本比较高级的机器学习书籍之一。《机器学习:概率视角》采用了贝叶斯(即概率论的)视角,同时与传统的频率视角进行对比。
贝叶斯机器学习与众不同,因为它将先验知识和不确定性纳入学习过程中。其中一个显著优势是可以生成概率预测,而不是单一点估计。
该书涵盖了许多之前的书籍没有尝试解释的高级主题,比如高斯过程或变分推断。如果你想把你的机器学习知识提升到更高的水平,这是一本很好的书,你会学会从不同角度来看待它。
我从书中学到的:在大学时,我上过一门贝叶斯机器学习课程,这本书是其中的重要读物。例如,我学习了线性和逻辑回归的贝叶斯版本。书中还有一章关于期望最大化算法的精彩内容。直到今天,每当我需要从概率的角度理解方法时,我总会回到这本书。
如何阅读这本书:这仍然是一本经典教材,所以你很可能会在课程中遇到它。但即使在课堂外,你也可以使用这本书对许多其他书籍(如 ESL)教授的机器学习方法获得不同的视角,还有许多只有在概率框架下才能真正理解的方法。如果这是你第一次学习贝叶斯机器学习,我建议你先阅读导言,以真正理解这个主题为何如此有用。
获奖者,2013 年国际贝叶斯分析学会颁发的德格鲁特奖,1104 页,8 x 9 英寸,300 色……
mitpress.mit.edu](https://mitpress.mit.edu/9780262018029/machine-learning/?source=post_page-----8df2b706a0b1--------------------------------)
新版本中的新内容:作者凯文·墨菲正在编写另外两本机器学习书籍。其中一本是 概率机器学习:导论,主要关注深度学习,还有一本更高级的版本,标题为 概率机器学习:高级主题。
使用 Python 的深度学习 作者:弗朗索瓦·肖莱
使用 Python 的深度学习 是一本很棒的书,如果你想理解(不仅仅是学习)神经网络及其如何使用 Python 实现。
这本书由弗朗索瓦·肖莱编写,他是 Keras 的创建者,这是一个基于 TensorFlow 的深度学习框架。肖莱是一位杰出的作者,这不仅仅是我的看法。他在阐释深度学习中各种概念的机制以及它们如何结合在一起形成目前席卷全球的领域(比如 ChatGPT)方面做得非常出色。
我从书中学到的:这本书完全是关于如何从基础原理开始思考深度学习。我主要用这本书来学习 Keras,但我发现自己还反复阅读了书中的许多其他部分(例如卷积神经网络、最佳实践等)。即使在我遇到这本书时,已经了解了大部分主题,但它们实在太有价值了,因此极大地加深了我对深度学习的理解。
如何阅读这本书:如果你刚开始深度学习,这可能是又一本从头到尾阅读的书,因为弗朗索瓦是一个出色的作者,他在阐述概念时做得非常出色。如果你已经有一些深度学习经验,你也可以选择一章来增强对特定主题的理解。
第二版中的新内容:本书的第二版于今年早些时候发布。深度学习发展迅速,因此书籍需要进行更新。它还包含了新的章节和主题。例如,它包括了最新语言模型和文本生成技术的新内容。此外,Keras 也随着年份的推移发生了变化,所以书籍也进行了相应调整。
本书的上一版电子书副本包含在内,无需额外费用。它将自动添加到…
(还有一本非常相似的书,名为使用 PyTorch 进行深度学习,它涵盖了很多相同的内容,但重点放在 PyTorch 上,而不是 TensorFlow/Keras。)
强化学习导论,作者:理查德·萨顿和安德鲁·巴托
诚然,这本书在这里可能有些格格不入,但绝对值得一读。强化学习(RL)是一个非常迷人的领域,它是以上书籍教授的机器学习的一种极好的补充。
如果你对此不太熟悉,强化学习是人工智能领域中关注自主代理和决策制定的领域,旨在通过与真实或模拟世界的交互来学习。
这本书是全球强化学习课程的标准教材,因此如果你想学习强化学习,它绝对是最佳选择之一。尽管具备机器学习知识对于理解强化学习至关重要,因为它在其中扮演着核心角色,所以可以把这本书看作是“锦上添花”。
从这本书中学到的东西:很多。这本书也是我大学课程的一部分。事实上,我在大学的课程项目是关于蒙特卡洛树搜索,我的硕士论文则是关于情境臂的。这两种方法在书中都有很好的解释。它们是非常有趣和强大的强化学习方法。
如何阅读这本书:这本书可能又是一本从头到尾阅读的书,因为各章节非常相互关联。重要的是,在阅读这本书之前,你可能需要对机器学习甚至深度学习有很好的理解,因为很多强化学习的概念是在它们的基础上建立的。
目前还没有新版本的书,但如果你问我,现在正是更新的时候。在过去几年中,该领域发生了很多变化,驱动了一些最先进的语言模型(例如通过强化学习从人类反馈中学习或 RLHF)。我肯定会第一个读到那本书。
[## 强化学习:一种介绍 | MIT Press 电子书 | IEEE Xplore
强化学习是人工智能中最活跃的研究领域之一,是一种计算方法…
ieeexplore.ieee.org](https://ieeexplore.ieee.org/book/6267343?source=post_page-----8df2b706a0b1--------------------------------)
总结
这七本书,加上我原始博客文章中的五本,都是这个领域中最好的书籍,我可以自信地说,因为我花了(而且现在还在花)很多时间整理我的(虚拟)书架。最棒的部分?大多数都可以免费在线获取。
我真的很感激这个领域如此开放,并且能够自由、轻松地获取知识。我认为这一点并没有被充分地说出来,有时候甚至被视为理所当然,但这不应该这样。
如果有一个最后的建议——我一直都是使用多个信息源学习同一概念的忠实粉丝。不同的书籍(或课程、视频和教程)都会以稍微不同的方式来教授一个主题。就像有多位老师一样,总会有一位能让你豁然开朗,一旦这种情况发生,你也会理解其他人在说什么。我认为通过这样做你能获得非常深刻的理解。
非常感谢你的阅读。我真诚希望你喜欢这篇文章,并且觉得这些书籍对你有用,不论你处于学习的哪个阶段。
如果你有任何想法或问题,请随时在评论中或在LinkedIn上联系我。
从见面会到辅导:如何作为数据科学家进行网络交流
原文:
towardsdatascience.com/from-meetups-to-mentoring-how-to-network-as-a-data-scientist-ecab8a95ab35
有志、初级和资深数据科学家的五条建议
·发表于 Towards Data Science ·15 分钟阅读·2023 年 8 月 3 日
--

概述
-
介绍
-
有志数据科学家的建议
-
初级数据科学家的建议
-
资深数据科学家的建议
-
总结
介绍
网络交流。我们经常听到类似 “我听说网络交流很有用,为什么不试试呢?” 这样的短语。这些鼓励是出于好意,但效果不大。大多数人都明白网络交流的价值。许多数据科学家不是通过正式的招聘信息而是通过推荐和人脉获得职位的。
理解如何有效地进行网络交流可能很棘手。一些建议已经过时,有些不适用于数据科学家,有些则完全错误。回顾你职业生涯中的前一步,理解什么实际上有效会更容易。这是因为你可以看到你周围成功人士的共同点 ✨
在这篇博客文章中,我想给你一些关于如何作为数据科学家进行网络交流的建议。下面我将这些建议分为有志数据科学家、初级数据科学家和资深数据科学家的类别。我自己是资深数据科学家,因此对这些步骤有经验。我还与许多专业招聘人员交谈过,也曾在数据科学家的招聘面试中参与过。尽管如此,请将我说的所有内容都看作参考。我在这个话题上并没有比其他人更具权威,这些只是我自己的思考。
在我们开始之前,你应该知道,我认为将网络视为优化问题是根本错误的。拥有 1000 个 LinkedIn 联系人或 5000 个关于 LLMs 的帖子点赞并不是目标。网络的真正目标是建立与你志同道合的其他人之间的真实连接。这些连接会为你提供一个你可以帮助和在需要时联系的网络。
矛盾的是,将网络视为一个优化问题会使你在网络中表现不佳。这会让你关注指标而非人际关系。遇到一个仅仅为了“炫耀”而与你交谈的人是令人厌恶的。这种方法也会造成心理上的伤害。你开始把人们视为实现某些任意指标的工具。这既不有趣也不有效 😔
因此,在阅读以下提示时,不要忘记网络连接本质上是关于人的。我敢肯定,这听起来可能有点老生常谈。但它也可以令人意外地平静。你不需要在网络中表现得像销售员。你不需要穿西装、对新认识的人做出手指枪的姿势。你对数据科学的热情已经足够了。
对未来数据科学家的建议

照片由 Tim Bogdanov 提供,来自 Unsplash
我很高兴你对追求数据科学职业感兴趣。我假设你目前还不是数据科学家,但希望未来成为。你可能在大学或类似的机构学习。在你职业生涯的这个阶段,我建议你关注以下五个网络建议:
提示 1:参加相关课程以结识志同道合的人
我相信考古学或环境法课程很有趣。然而,如果你想成为一名数据科学家,你可能不会在这些课程中遇到很多未来的同事。如果你参加数据科学、统计学或信息学课程,那么遇到未来同事的机会就会大大增加。参加相关课程的主要原因当然是获得知识,但也不要低估建立坚实网络的价值。
在参加相关课程时,尽量与不同的人交谈,了解他们一点。即使你不想过于社交,也不必过于担心,但一点点努力会大有成效。与每门相关课程中的 5-10 个人建立联系,很快就会积累起来。
提示 2:如果可能的话,申请一个(有薪的!)相关的暑期工作
如果你能够找到一个数据科学家的暑期工作,那么这对获得人脉极其有用。有些人利用这个机会进入公司,并在完成学业后常常能在公司获得工作。即使你没有这样做,拥有以前的数据科学专业经验在任何简历上都是一个很大的加分项。
在网络建设方面,做一个暑期工作会让你接触到导师和同事,你可以与他们建立联系。人们会拥有真实的数据科学知识,这通常与学术版的有所不同。在这里建立网络对你未来的职业生涯至关重要。
在暑期工作中,尽量了解你所在的圈子之外的人。与设计师一起吃午餐,与产品负责人闲聊。暑期工作意味着大多数人会非常愿意接受你的提问和交流。这使得网络建设变得容易多了 😃
提示 3: 参加职业日活动并与代表交谈
大学通常会组织职业日活动,企业会来做演讲。这是一个与公司代表交流并了解他们的绝佳机会。你甚至可能会找到一些免费食物,这对学生来说总是个额外的好处。
我知道从经验中参加职业日活动会有些尴尬。它半正式但又放松。不要担心。公司的代表们通常也会有相似的感觉。在参加职业日活动时,确保充分利用这个机会。
提示 4: 参加指导计划
大学通常有指导/校友计划。学生可以通过这些计划与行业内的专业人士联系,以了解职业的日常工作。这是一个很好的机会,通常是免费的,会让你接触到在职的专业人士。
导师可以给你提供技术方面的建议,还能指导你如何/何时申请职位或哪些职位比较好。像这样的专业人士通常因为热爱自己的职业而担任导师,并且对这个领域了如指掌。如果你决定参与这样的项目,请确保做好准备。准备好你想要问的问题,并且尊重导师的时间 🙏
不要害怕与导师保持联系。导师通常喜欢听到你进展的情况,如果你有疑问,他们可以提供更多建议。在你的职业生涯后期,如果你愿意,可以“回馈”并成为别人的导师。
提示 5: 至少保持最基本的在线存在
你应该注册你所在国家最常用的数据科学在线网络工具。对大多数人来说,这将是 LinkedIn,所以我接下来只会提到 LinkedIn。从与招聘人员的交谈中,我知道完全没有 LinkedIn 个人资料会被看作有些不受欢迎。虽然我个人不一定认同,但这几乎无关紧要。
添加你通过课程、聚会和暑期工作认识的人到 LinkedIn 上。这是一种便捷的保持联系的方式。你还可以利用 LinkedIn 在找工作时做宣传。我许多朋友就是这样找到工作的。
记住,你不需要在网上发布任何让你不舒服的内容。事实上,我真的认为你在职业生涯的这个阶段并不需要发布任何内容。只需创建一个账户,添加相关信息,并与遇到的人保持联系。就是这么简单!
初级数据科学家的建议

我看到你是一名初级数据科学家。恭喜你!这是一个令人兴奋的领域,我很高兴你加入了我们。你在技术方面有很多要学习,但关注网络建设也是有益的。在你职业生涯的这个阶段,我建议你专注于以下五个网络建设的技巧:
技巧 1:与同事培养良好关系
这一点几乎太明显了。你很可能会在职业生涯中与其他数据科学家、数据工程师、数据分析师和机器学习工程师合作。确保你与他们保持良好的关系并建立联系。尽管可以谈论数据科学和相关学科,但也不要回避谈论其他话题。通常,了解一些关于他们个人的简单信息会使建立关系变得更容易。
我建议你也去认识其他部门的人。当你换工作时,你可能会发现之前沟通过的人现在也在那儿工作。即使他们可能不能对你的数据科学技能做太多评价,他们也可以根据你的性格给出推荐。与不同领域的人交流也是一种很好的避免完全隔绝的方式。我认识的一些最优秀的数据科学家对邻近领域的知识了解甚少。
如果你在远程工作,这将会困难得多。我自己也有远程工作的经验,所以我并不是在一般情况下反对远程工作。我只是想对你选择这种职位时的现实情况保持诚实。如果你选择了远程职位,那么你可以把更多的精力投入到接下来的建议中💪
技巧 2:参加聚会、研讨会和会议
在你的工作之外,会有机会认识其他数据科学家。这通常以聚会、研讨会和会议的形式出现。这些活动偶尔参加一下是很好的,有助于建立网络。如果活动质量高,你可能还会学到很多东西。虽然聚会和研讨会通常是免费的,但会议通常需要相当昂贵的费用。你可以要求公司承担这些费用。
我建议你只有在真正感兴趣的情况下才参加这些活动。如果你对前端开发不感兴趣,去参加 React 研讨会是没有意义的。找到真正让你感兴趣的东西——否则,你只会感到疲惫。
最后,如果你发现了一个你喜欢的聚会,并且参加了几次,不要害怕主动申请演讲。组织聚会的人常常会为找到演讲者而苦恼。你不需要是高级数据科学家才能做出精彩的演讲。实际上,我见过的一些最好的演讲来自经验较少的人。
提示 3:不要害怕指导他人
你可能认为自己还没有达到可以指导他人的水平。但这并不真实。即使只有 6 个月的工作经验的人,也可以在某些方面指导新毕业生。回想一下你开始工作时的情况。有什么是你当时希望知道的?也许是像某些信息的位置这样平凡的事情。或者在组织中数据科学的部署是如何运作的?这现在对你来说可能很简单,但一开始可能很有挑战。
通过指导他人,你会与他们建立联系。他们可以向你提出问题,而你将尽力协助他们。许多新员工认为他们的导师(前提是导师确实在做这份工作)是他们最了解的人,这并不令人惊讶。所以尽快问问你的经理是否可以成为新员工的导师 🔥
提示 4:撰写博客或教授技术课程
你现在处于一个可以教导他人的位置。虽然上面提到的指导是一种非常个性化的方式,但也不要害怕通过博客或课程来实现。如果你撰写了一篇关于缺乏良好文档的技术库的博客文章,那么许多人会感激你。只需确保选择一个你熟悉的主题即可。
我的建议是从小处做起。你公司里的人是否抱怨新毕业生不知道像 Git 这样的版本控制软件?提供一个 1 小时的课程。你的同事们是否抱怨在数据处理时难以理解何时使用 Pandas、Polars 还是 Spark?为此创建一个博客文章,花时间调查这些差异。这既可以是有价值的,又是很好的实践。
提示 5:提升你的在线形象
我认为在你职业生涯的这个阶段,提升一下你的在线形象是有益的。不必担心,你不需要成为一个有观点的数据科学家影响者。但投入更多精力到你的个人资料和互动中可能是件好事。
对于你的个人资料,添加更详细的职位描述、技能、证书等。如果你想成为一名高级数据科学家,那么模仿这样的个人资料。清晰的图像和一个吸引人且专业的自我介绍部分。写这部分内容不应该花费你很多时间,但许多招聘人员对此非常看重。你可以做得比用猫的照片作为个人头像和一段“我做那数据 stuff”的描述要好。
如果你想发布内容,可以发布关于有趣的技术、有趣的讲座或博客,或者任何真正让你感兴趣的东西。你不需要发布有关数据科学的当前炒作趋势的帖子,也不需要发布类似“SQL 已死!”的标题点击诱饵帖子。它并没有死,希望你现在已经知道这一点 😅
高级数据科学家的提示

照片由 bruce mars 提供,来源于 Unsplash
祝贺你成为高级数据科学家!我相信你为此付出了很多努力。这一职位的变化也大大改变了你的网络联系方式。简而言之,主要的变化是你需要以各种方式“展示自己”。在这个职业阶段,我建议你关注以下五个网络联系的提示:
提示 1:在会议、聚会、网络研讨会、研讨会等场合进行演讲。
在职业生涯的早期阶段,你主要是参加活动。虽然这仍然是一个有价值的网络联系方式,但它相较于演讲黯然失色。在各种场合演讲时,你间接展示了你的知识。如果你表现良好,人们会主动找上你。这会反转你的网络效应——你现在拥有了吸引力。只需知道,良好的演讲需要大量时间:
-
首先,对于大多数人来说,成为一个优秀的演讲者需要多年时间。尽早开始。职业生涯越早,人们对表现平庸的演讲接受度越高。作为初级数据科学家,即使你的演讲技巧需要改进,人们也会对你的投入印象深刻。在职业生涯的某个阶段,演讲表现糟糕会受到更多的轻视。
-
其次,即使你已经很擅长演讲,准备演讲仍然需要大量的工作。因此,你应该仔细选择何时在活动中演讲。比起每年演讲 50 次表现平庸,每年演讲 5 次表现出色要更好。优先考虑质量而非数量。
提示 2:提高你的写作能力
你会发现你的工作中有很多写作。不论是文档、备忘录、说明、演示、总结、观点、博客文章、电子邮件,还是完全不同的东西,这并不重要。重点是写作已经成为你日常的一部分。你突然被期望能做到这一点。
事实上,你会因写作被评判。因此,这可能会严重影响在高级别的网络联系。对于某些人来说,仅凭写作来评判别人似乎非常肤浅。但写作往往是接收者能评判或理解的唯一输出。大多数利益相关者无法理解你的巧妙超参数搜索或复杂的数据管道。如果你不能用简单的术语解释一个项目为何应该继续,那么当它被取消时不要感到惊讶 😲
清晰的书面沟通在建立网络时变得越来越重要。你会通过聊天、电子邮件和类似的接口与许多人交流。如果你写得不好,这可能会影响你给人的印象。
对许多人来说,英语不是我们的母语。因此,我们通常需要用两种(或更多!)语言进行清晰沟通。这听起来很令人生畏。幸运的是,清晰的写作在很大程度上是语言无关的。开始在你建立网络时最常用的语言上进行练习。你会很快发现你其他语言的清晰度也会同步提升。
提示 3:构建有价值的东西
你可以通过建立有价值的东西来证明自己的技能。无论这是一个实现了新机器学习算法的 Python 库,还是一个展示了酷炫用例的 GitHub 仓库,都完全取决于你。这也可以是你组织中的一个内部工具。
我想强调的是,构建具有很高原创性的东西并不是总是必要的。也许你的组织使用了不佳的数据管道编排流程。承担责任并解决这一问题带来价值,即便这是在其他组织中已经做过 1000 次的事情。尝试创建一些真正能够帮助他人的东西。
如果你制作内部工具,这将赢得你在组织中他人的尊重。如果你制作开源工具,这将帮助更广泛的受众。这两种选择都是建立网络的好方法。如果人们已经对你有了良好的印象,那么建立网络会变得简单很多。
提示 4:成为某一领域的专家
当你刚开始成为数据科学家时,你会想要了解各种各样的知识。这包括从自然语言处理到云平台。作为一名高级数据科学家,你应该对广泛的主题有一定了解。但你也应该成为某一领域的专家。这可以是像异常检测这样的技术领域,也可以是更侧重软技能的内容,比如如何成功地在数据科学中实施敏捷方法。
通过成为专家,你会因为你的专业知识而受到追捧。这为建立网络打开了许多大门,因为你将被邀请讲述和撰写关于你的专业知识的内容。选择一个专业话题可能很困难。我最好的建议是深入研究你真正关心的领域。如果这个话题在广泛的环境中适用,这也是一个加分项。
在建立网络时,你可以利用你的专业知识来脱颖而出。几乎每个数据科学家都对深度学习有一定了解。但你认识的有多少数据科学家是集成方法的专家?或者是将长期组织战略与数据项目结合的专家?这确实很突出 😍
提示 5:建立强大的在线存在感
作为一名高级数据科学家,你应该力求拥有强大的在线存在感。这包括更多地与他人互动,花更多时间展示你的想法和观点。实现这一点的方式有很多种,你可以自己决定。格式可以是 LinkedIn 帖子、视频教程等。专注于制作高质量的内容,真正吸引和阐明话题。再次提醒,质量胜于数量。
人们常常谈论建立你自己的品牌。这个词有点负担,我更喜欢谈论发展你自己的声音。当你写作和讲话时,你需要做出一些选择。你会采用友好和帮助的语气,还是更权威的语气?语气的选择应该与你想要达成的目标相匹配:
-
假设你想为你开始领导的团队招聘更多初级数据科学家。那么,在线上使用“滚出我的草坪”那种反应性语气可能不是一个好主意。
-
假设你想说服世界 Rust 是数据科学的未来。那么,采用友好和可接受的语气可能也不合适。你绝不要选择攻击性的语气,但有时略带一些被压抑的消极攻击性可能会令人感到有趣😤
总结

图片由Spencer Bergen提供,来源于Unsplash
在这篇博客文章中,我给出了在数据科学家各个阶段的网络交流技巧。我不希望你把这些当作圣经,而是作为你可以工作的参考点。
如果你对数据科学感兴趣,欢迎在LinkedIn上关注我。不过,为了让这次连接更有意义,请在网络交流时告诉我你的个人看法。我很想听听你的观点😃
喜欢我的写作? 查看我的其他文章,获取更多内容:
-
作为数据科学家如何编写高质量的 Python 代码
-
用优雅的类型提示现代化你的罪恶 Python 代码
-
在 Python 中可视化缺失值是惊人的简单
-
在 Python 中使用 PyOD 介绍异常/离群值检测 🔥
从数据到行动:让数据为公司服务
原文:
towardsdatascience.com/from-numbers-to-actions-making-data-work-for-companies-162183c13998
什么数据是失败的,什么数据是有效的?
·发表于 Towards Data Science ·阅读时间 14 分钟·2023 年 8 月 14 日
--
如今,组织和个人都被数据淹没了。 每天全球产生 329 万亿字节的数据,每年积累达到惊人的 120 泽字节 [1]。
但这在具体的术语中是什么意思呢?考虑一下存储空间为 128GB 的 iPhone。让我们想象一下,如果每天的数据等于三十亿部 iPhone 的容量*。听起来很令人印象深刻?让我们深入探讨一下。这个每日数据量换算成年数据量的话,就是理论上有 1.08 万亿部 iPhone 被数据充斥。鉴于当前全球人口约为 78 亿,这意味着每个人需要拥有近 139 部 iPhone [2]。荒谬,对吧?
数据的巨大体量令人震惊,但增长速度同样惊人。就在 13 年前,也就是 2010 年,年度数据创作量还只是相对温和的两泽字节……
这只是冰山一角。想想那些没有在线的数据——直接存储在我们设备上的文件,或是纸上写的笔记和文档(是的,这种方式还在存在!)。估算一下这些数据的体积?
我甚至不敢猜测。

没有 iPhone 能够处理这些数据。来源:作者提供的图片,由 DALL-E 2 生成
所以,外面有大量的数据。
但这对企业来说意味着什么?
我最近审查了最新的数据和人工智能领导力执行调查 [3]。结果显示,97%的公司已经投资于数据及相关基础设施。92%投入了大数据和人工智能。你可能会认为这意味着他们正在看到这些投资的回报。实际上,并非如此。只有 40%的受访者表示他们将数据视为“产生收入”的资产。
只有 27%的公司认为自己是以数据驱动的组织。
什么?为什么?
主要问题有两个方面。虽然一些问题来源于技术,但高达 92%的问题源于人为因素,如组织文化、人员和流程。即使是技术问题,通常也归结为人为错误。
这可能会让一些人感到惊讶。我曾经看到一个声明错误地说“决策驱动的数据”而不是“数据驱动的决策”。这是一个简单的打字错误吗?也许。弗洛伊德可能对此有一两个理论,我很乐意参与讨论。 虽然这看起来像是一个失误,但我的研究表明情况并非如此。我越深入探究,发现越多的理由解释这种奇特的对比。
在一篇简短的文章中,无法涵盖每一个细节,但我已经突出了几个要点,以向亲爱的读者展示复杂性。
数据沼泽
从一个常见的误区开始:许多人认为问题出在数据、分析或工具上,而不是人。
随着时间的推移,数据供应机制已经发生了变化。第一代系统,数据仓库,将数据从操作系统转移到商业智能平台,简化了查询,但处理复杂流程和多样数据时存在困难。第二代系统,数据湖,以原始形式保存数据,便利了机器学习,但也带来了数据质量问题和来源映射问题等挑战。第三代系统,基于云的数据湖,在云中融合了前两代系统的特点,提供了实时数据访问,但依然复杂,并且在将数据转化为见解时可能较慢。第四代系统,数据网格,是一种去中心化的数据架构和组织结构,推动了分布式数据产品的领域导向所有权[4]。
然而,企业往往从数据仓库、数据湖或数据网格开始,但最终却陷入同样的困境:数据沼泽。可以把它看作是一个杂乱的数据储藏室。
如果没有明确的指南,数据可能会变得无序和混乱,就像没有规则的游戏或一个充满未标记箱子的衣橱一样。公司经常存储未经检查的数据,使得其有用性和准确性受到质疑。没有策略地上传数据进一步加剧了混乱,就像随意把衣服扔进衣橱一样。而且,如果不定期维护,数据湖就像被忽视的花园一样,会变成沼泽。
简而言之,要拥有一个有用的数据湖,你需要将其组织得井井有条,就像保持房间的整洁一样。如果不这样做,很容易陷入...

数据沼泽:小心鳄鱼。图像由作者在 DALL-E 2 中生成。
我的见解在哪里?
让我分享一个我在公司生活中的经历。在一次例行的月度财务审查中,我发现了一个显著的差异,比如说是一百万美元。当我向一位分析师询问差异时,我收到的分解是:
-
10 万美元:具体原因
-
5 万美元:另一个原因
-
剩余的金额?简单标记为‘各种’。
我的脸色一定变得苍白,尤其是因为我有一个即将召开的会议要与管理委员会讨论这些差异。我措手不及,没什么实质性的内容可以呈现。
这突显了数据洞见中的一个常见问题:它们往往停留在表面,通常只是陈述已经显而易见的内容。 我们可以轻松地使用像 ChatGPT 这样的工具来获取这种表面层次的信息。然而,尽管像 LLMs 这样的先进模型很强大,我们仍然期望人类对数据进行深入分析和解读。我们希望他们得出有意义的结论,并将其转化为可操作的步骤。这些洞见应该提供价值并建议具体行动,而不仅仅是传达那些人们自己很容易找到的信息。
鱼从头部开始腐烂
Brent Dykes 在 LinkedIn 的帖子中引用了丰臣秀吉及其 1592-1598 年失败的朝鲜入侵的故事。
这位领导因其残酷的本性而臭名昭著。带给他坏消息可能会导致信使的死亡。因此,他的将领们犹豫不决,不敢告知他战役的真实状态,这远未成功。他们表现得如此出色,以至于秀吉在他最后的呼吸之前仍然幻想着成功。一旦他去世,日本迅速撤回了这次征服。
这个故事突显了基于坚实数据的客观洞见与某些管理方法之间的冲突。在这种情况下,驱动因素是恐惧。这并非唯一的可能情境。考虑在会议上提出一个让部分与会者不安的见解。即使信息根植于硬事实,大多数人(根据我的观察)仍会捍卫自己的立场。这突显了将我们的分析基于客观和经过验证的数据的重要性。此外,如果存在潜在的不适感,与可能感到被针对的人对齐,确保观众更加接纳,是明智之举。

丰臣秀吉,来源:维基百科
证明明显的事情有多困难?
首先,让我们观看一段著名的罗温·艾金森爵士的视频采访。
来源:YouTube
罗温·艾金森爵士确实是喜剧传奇。 想到有人可能不认识他是标志性角色 Mr. Bean 的幕后天才,真是有趣。然而,有时候,我们越是试图说服别人一个明显的真相,它就变得越不可信。
我们中的许多人都经历过类似的情况。我记得个人尝试向销售代表解释权责发生制会计的复杂性时的经历。那是一个非常“特殊”的时刻。 有时候,专业知识与理解之间的差距显得非常大,再多的解释也无法弥补。这提醒我们,沟通不仅仅是我们说了什么,更在于我们的受众如何接收和理解这些信息。
哦,传达复杂话题的乐趣和挑战!对于数据科学社区的人来说,这是一种熟悉的情景。你深入分析,使用你发现非常有趣的高级技术。然而,当你展示你的发现时,却遇到了空洞的凝视或礼貌的点头。就好像你在讲外语一样。
他们真的理解了吗?还是只是点头附和,努力不让自己看起来迷茫?通常,问题的缺乏就是一个明显的信号。当然,除非你已经提前让观众准备好给你抛一个软球问题。顺便说一下,这确实是个好主意。
这突显了不仅要掌握自己领域的技术方面,还要能够将这些知识转化为适合不同受众的洞察的关键重要性。这是在展示专业知识与确保可接触性之间的微妙平衡。
有效的沟通依赖于了解你的受众。如果没有这种理解,即使是最深刻的见解也可能会失去效果。虽然专业知识至关重要,但同样重要的是以可接触的方式呈现这些知识。毕竟,这不仅仅是简化我们的工作,而是关于清晰和连接。如果我们不付出这种努力,为什么我们的受众要参与呢?
匿名的诅咒
这个故事还有另一面。你是否见过类似下面的图片?

来源:作者提供的图片,部分基于此图片
当然,我们中的许多人都有过这种经历。我也曾经在那个位置上,脸出现在左上角。我的记录?在我主持的一个漫长而坦白说有些乏味的培训中,连续八小时。其他参与者?他们的摄像头只是短暂地亮起——开头时快速“打招呼”,结束时,像往常一样,“道别”。
从那次经历中,我知道这一点:
如果你把他们当作墙壁,他们就会像墙壁一样表现。
如果没有吸引受众或者编织一个有趣的故事,我们不能指望得到互动。通常,他们只有在感到烦恼时才会参与。这突显了基于数据的讲故事的力量。我是这种方法的坚定支持者。鉴于 AI 和 LLMs 的迅速发展,我坚信它应该融入所有以商业为重点的课程中,尤其是财务课程。
过度复杂化的威力

来源:作者提供的图片
上面的图片展示了华沙普拉加区一栋公寓楼的入口。这栋建筑经过改造,以适应残疾人士,从而创建了这个独特的坡道。我经过几次,每次都会让我思考:
为什么会有这样的设计?难道不能更简单或者稍微陡一点?那会导致进出困难吗?从开始到门口需要花费多少时间?人们使用它吗?是否更有意义的是购买并安装一个电梯而不是建造这个坡道?如此多的问题,却没有答案……
最初,没有扶手,使其看起来不那么麻烦。但现在,这确实是一个引人注目的景象。
而且,请请请:不要误解我的意思。我对任何能让残疾人士生活更轻松的举措持 200%的支持态度。我只是见过一些解决方案,在我看来,可以帮助人们按照他们想要的方式运作,并且不像照片中的那东西那么麻烦。但我算什么呢?
不管怎样,数据或分析的问题往往在于它们真的可能令人不知所措。它们可能充满了复杂的细节,这些细节看似重要,但在实际决策时却是多余的。这些不必要的复杂性可能会使我们偏离正轨,风险类似于照片中的坡道。
那又如何?
让我再讲一个故事。这次是关于一个来自白俄罗斯的机智汽车修理工。他在职业生涯中积累了大量废弃汽车零件。他没有让这些零件生锈,而是创造性地将它们重新利用成一个 3.5 米高的矮人。你可以在这里看到它。这是一种相当创新的“废物”使用方式,不是吗?
这难道不是很有意思吗? 一个由废弃汽车零件组成的 3.5 米高的矮人。但对我们来说,有什么收获?坦白说,不多。 除非,也许,我们发现一个我们认识的零件,因为它来自我们的车。
但在商业中情况并非如此。
考虑一下仪表板。我们是否都遇到过那些数据过载却提供很少实际见解的管理仪表板?或者,也许有价值的信息被深埋其中,就像试图在一个 3.5 米高的金属小矮人中找到一个旧化油器一样。正如我在最近的一篇文章中强调的那样,情况不必如此。仪表板确实可以成为强大的讲故事工具[5]。
为何要这样做?
这是我在办公室拍的一张照片。纸上的便条用波兰语写道:“请清洗您的碗盘或将其放入洗碗机中。不这样做会使水槽无法供他人使用”。
然而,你看到的结果。

来源:作者提供的图片
在应对日常企业挑战时,我已承诺一个:即使水槽里满是碗碟,我也要洗我的杯子。确实,有几次因为忙碌的日子而跳过了。确实,上面的快照展示了水槽中约 30-40%的最满状态。
有趣的是,当我停下来拍这张照片时,两位同事拿着脏杯子走进了厨房。令我感到好笑的是,他们也感到了洗杯子的冲动。这只是巧合吗?我有些难以相信。
有效的行动号召的力量不仅在于说明需要做什么,更在于确定谁来做。通过指定这个人或团队并进行后续跟进,我们可以避免浪费时间和金钱的无尽会议。此外,最后,我们应该利用我们的数据!
从上述讨论中,得出了哪些见解?最重要的是,公司如何有效地过渡到数据驱动型?
如何清理数据沼泽?
好吧,最好的策略是避免一个。精简的数据湖和混乱的数据沼泽之间的界限可能很薄。但只要有正确的策略,你可以将数据沼泽转变回一个有序且高效的数据湖。
要有效地恢复你的数据湖,首先要进行彻底的数据审计,以评估数据的相关性和结构。引入强有力的数据治理,强调协议和数据管理。提升元数据管理,以提高数据的可发现性,并实施能够通过验证检查和自动清理确保数据质量的工具。定期审计、数据保留政策以及关注数据安全都是必不可少的。如机器学习和人工智能用于异常检测和数据优化。
在这次重振过程中,同样重要的是建立一个知识丰富的团队。提供持续的培训,推动强调数据素养的文化。这种将战略和技术结合的整体方法,确保你的数据湖保持有序且对组织来说是宝贵的资产。
如何让见解更具深度呢?
既然我们已成功脱离数据沼泽,是时候深入探讨了。
要从数据中获得真正有价值的见解,我们必须从正确的问题开始[6]。仅仅随意地转向数据不会得到我们想要的结果。要确定正确的问题,我们需要熟悉业务世界的人来指导我们的数据使用。把业务看作一台复杂的机器,不仅有精密的内部运作,还有与外部世界如客户、供应商、竞争者以及经济环境的互动。
这就是财务业务伙伴角色变得至关重要的地方。 他们充当桥梁,将财务数据与实际业务操作连接起来。他们指导数据团队,提供关于哪些见解可能有益的方向。一个设计良好的仪表板可以成为他们的强大工具[5]。他们可能会先在较小的规模上使用这样的仪表板测试一些想法。当他们在数据中发现值得注意的东西时,他们是确保其重要性和有效性的人。
鱼多少钱?
所以,我们已经汇集了我们的见解。但是如果这些见解让人不安怎么办?如果它们暴露了脆弱性呢?
领导者在塑造数据驱动文化中发挥着关键作用。 他们倡导一种探究的环境,将数据视为解决紧迫问题的方案。通过庆祝基于数据的胜利和强调持续教育,他们将数据融入组织的 DNA 中。
领导者以身作则,做出突显数据重要性的选择。他们基于数据建立基准,确保可衡量的结果,并在团队中创造责任感。他们的适应能力和对数据驱动方法的承诺成为灯塔,激励整个组织采用类似的方法。
证明显而易见的事情……
当你已确保数据、获得管理层的支持,并讲述了基于事实的故事时,如果有人表达怀疑,尤其是其他人站在怀疑者一边,这可能会让人感到泄气。那么,你应该如何应对呢?
首先,认真倾听他们的保留意见,以把握他们疑虑的性质。尊重并认可他们的观点。展示你愿意进行开放对话的态度。通过提问深入探讨,以更好地理解他们的担忧。在澄清复杂问题时,依靠简单的解释和直观的类比。保持冷静,确保讨论保持富有成效。
如果怀疑仍然存在,建议额外安排会议或研讨会以进行更彻底的探索。通过建议延长参与时间,你强调了对相互清晰度的承诺。合作更佳。团队合作可以帮助我们更好地理解并建立彼此的信任。
匿名的诅咒
哦,还有问题吗?到现在为止,每个人都应该已经达成一致了。如果存在脱节,现在正是利用数据讲故事的力量的时候。数据驱动的叙述,配以引人注目的视觉效果,为数据科学家和业务团队之间提供了无缝的桥梁。与其深入复杂的电子表格,不如用简洁的视觉效果来简化数据。这确保了核心见解立刻显现,帮助所有人做出更明智的决策[7]。
保持简单明了
数据讲故事有助于将复杂的事物简化。通过将数据转化为清晰的故事,我们突出了主要观点,省略了令人困惑的部分。使用图片或图表可以使理解变得更加容易。这样,每个人都能迅速获取主要思想,而不会迷失在过多的细节中。简而言之,数据讲故事保持了事物的简单明了。
谁在做笔记?
最后,让我们解决激发本文的燃眉之问:我们如何有效利用洞察力和数据?
秘密在于准备。一个引人注目的行动号召建立在一个基于数据的精心编排的故事基础上。 这个行动号召应该在故事的高潮时刻揭示,那是所有目光集中在你(或你的幻灯片)上时,观众渴望听到下一步的时刻。但这种势头是短暂的。无论你的信息多么有影响力,确立责任都是至关重要的。谁将采取下一步?接下来将做什么?
理想情况下,观众中会有人记录下来(你需要事先请求)。演讲结束后,发送一封总结邮件,详细说明主要决策。定期检查进展情况,确保工作持续进行。
结论
每一个伟大的旅程都始于一个脚步。即使是小事也能带来巨大变化。使用数据对现代企业很重要,但并不总是容易。没有一种正确的方法,但开始这段旅程是值得的。

图片由 History in HD 提供,来源于 Unsplash
在扣除 iOS 和系统文件使用的内存后。
参考资料:
[1] Duarte, Fabio, 每日生成的数据量(2023),2023 年 4 月 3 日
[2] 我引用了这篇文章来进行上述计算:Zero Agency, 计算泽字节,2015 年 10 月 21 日
[3] NewVantage Partners, 数据和人工智能领导力执行调查,2022 年 1 月
[4] Silva Santos, Diogo, 数据架构的过去、现在和未来,2023 年 3 月 9 日
[5] 苏德杰科,米哈伊,利用管理仪表板讲故事:一种可行的途径,2023 年 8 月 3 日
[6] 拉马克里希南,拉马,我有数据。我需要洞察。我该从哪里开始?,2017 年 7 月 2 日。
[7] 纳斯鲍默·克纳夫利克,科尔,数据讲故事,Wiley,2015 年。
从纸张到像素:评估数字化手写文本的最佳技术
对 OCR、变换器模型和基于提示工程的集成技术的比较分析
·
关注 发表在 Towards Data Science ·16 分钟阅读·2023 年 9 月 14 日
--
作者: Sohrab Sani 和 Diego Capozzi
组织长期以来一直在处理数字化历史手写文档这一繁琐且昂贵的任务。之前,光学字符识别(OCR)技术,如 AWS Textract (TT) [1] 和 Azure 表单识别器 (FR) [2],在这方面领先。虽然这些选项可能广泛可用,但它们有许多缺点:价格高、需要长时间的数据处理/清洗,并且准确性可能不尽如人意。最近,利用 Transformer 架构的深度学习进展在图像分割和自然语言处理领域,使得 OCR 自由的技术得以发展,例如 Document Understanding Transformer (Donut)[3] 模型。
在这项研究中,我们将使用自定义数据集比较 OCR 和基于 Transformer 的技术,该数据集由一系列手写表单创建。对这一相对简单任务的基准测试旨在引导向更复杂的应用,特别是在更长的手写文档上。为了提高准确性,我们还探索了利用提示工程结合 gpt-3.5-turbo 大型语言模型(LLM)的集成方法,以结合 TT 和微调的 Donut 模型的输出。
这项工作的代码可以在这个 GitHub 仓库中查看。数据集可以在我们的 Hugging Face 仓库这里获得。
目录:
· 数据集创建
· 方法
∘ Azure 表单识别器 (FR)
∘ AWS Textract (TT)
∘ Donut
∘ 集成方法:TT、Donut、GPT
· 模型性能测量
∘ FLA
∘ CBA
∘ 覆盖范围
∘ 成本
· 结果
· 其他考虑因素
∘ Donut 模型训练
∘ 提示工程的变异性
· 结论
· 下一步
· 参考文献
· 致谢
数据集创建
本研究从 NIST 特殊数据库 19 数据集[4]的 2100 张手写表单图像中创建了一个自定义数据集。图 1 提供了这些表单之一的样本图像。最终的集合包括 2099 张表单。为了整理这个数据集,我们裁剪了每个 NIST 表单的顶部部分,目标是红框中高亮的 DATE、CITY、STATE 和 ZIP CODE(现在称为“ZIP”)键[图 1]。这种方法启动了基准测试过程,进行了一项相对简单的文本提取任务,使我们能够快速选择和手动标记数据集。在撰写时,我们不知道有任何公开的带标记的手写表单图像数据集可以用于 JSON 键字段文本提取。

图 1. 来自 NIST 特殊数据库 19 数据集的示例表单。红框标识了裁剪过程,该过程仅选择此表单中的 DATE、CITY、STATE 和 ZIP 字段。(作者提供的图像)
我们手动从文档中提取每个键的值,并仔细检查其准确性。总共丢弃了 68 个表单,因为它们包含至少一个无法辨认的字符。表单中的字符被记录为实际出现的样子,无论拼写错误或格式不一致。
为了对 Donut 模型进行微调以处理缺失数据,我们添加了 67 个空表单,以便为这些空字段进行训练。表单中的缺失值在 JSON 输出中表示为“None”字符串。
图 2a 显示了我们数据集中的一个样本表单,而图 2b 则展示了与该表单相关的 JSON。

图 2。 (a) 数据集中的示例图像;(b) JSON 格式的提取数据。(图像由作者提供)
表 1 提供了数据集中每个键的变异性分解。从最具变异性到最少的顺序是 ZIP、CITY、DATE 和 STATE。所有日期均在 1989 年,这可能降低了整体 DATE 的变异性。此外,尽管美国只有 50 个州,但由于不同的首字母缩略词或大小写敏感的拼写,STATE 的变异性有所增加。

表 1。 数据集的总结统计。(图像由作者提供)
表 2 总结了我们数据集中各种属性的字符长度。

表 2。 字符长度和分布的总结。(图像由作者提供)
上述数据表明,CITY 条目的字符长度最长,而 STATE 条目的字符长度最短。每个条目的中位数值紧随其各自的均值,表明各类别的字符长度围绕平均值的分布相对均匀。
数据标注后,我们将其分为三个子集:训练集、验证集和测试集,样本量分别为 1400、199 和 500。这里是一个链接,用于此笔记本。
方法
我们将详细阐述每种测试方法,并将其与包含更多细节的相关 Python 代码关联起来。方法的应用首先单独描述,即 FR、TT 和 Donut,然后是 TT+GPT+Donut 集成方法。
Azure Form Recognizer (FR)
图 3 展示了使用 Azure FR 从表单图像中提取手写文本的工作流程。
-
存储图像: 可以存储在本地驱动器或其他解决方案中,如 S3 桶或 Azure Blob 存储。
-
Azure SDK:用于从存储中加载每个图像并通过 Azure SDK 将其传输到 FR API 的 Python 脚本。
-
后处理:使用现成的方法意味着最终输出通常需要精细调整。以下是 21 个需要进一步处理的提取键:
[ ‘DATE’, ‘CITY’, ‘STATE’, ‘’DATE’, ‘ZIP’, ‘NAME’, ‘E ZIP’,’·DATE’, ‘.DATE’, ‘NAMR’, ‘DATE®’, ‘NAMA’, ‘_ZIP’, ‘.ZIP’, ‘print the following shopsataca i’, ‘-DATE’, ‘DATE.’, ‘No.’, ‘NAMN’, ‘STATE\nZIP’]
一些键有额外的点或下划线,需要去除。由于文本在表单中的位置非常接近,提取的值有许多实例被错误地关联到不正确的键。这些问题随后会得到合理程度的解决。
-
保存结果:将结果以 pickle 格式保存到存储空间中。

图 3. Azure FR 工作流的可视化。(作者提供的图片)
AWS Textract (TT)
图 4 描述了使用 AWS TT 从表单图像中提取手写文本的工作流:
-
存储图像: 图像存储在 S3 桶中。
-
SageMaker Notebook:Notebook 实例帮助与 TT API 进行交互,执行脚本的后处理清理,并保存结果。
-
TT API: 这是由 AWS 提供的现成的基于 OCR 的文本提取 API。
-
后处理: 使用现成的方法意味着最终输出通常需要进行精细调整。TT 产生了一个包含 68 列的数据集,而 FR 方法只有 21 列。这主要是由于检测到图像中额外的文本,这些文本被认为是字段。在基于规则的后处理中会解决这些问题。
-
保存结果:经过处理的数据会使用 pickle 格式存储在 S3 桶中。

图 4. TT 工作流的可视化。(作者提供的图片)
Donut
与不能通过自定义字段和/或模型再训练适应特定数据输入的现成基于 OCR 的方法相比,本节深入探讨了使用基于 Transformer 模型架构的 Donut 模型来改进无 OCR 方法。
首先,我们使用我们的数据对 Donut 模型进行了微调,然后将模型应用于我们的测试图像,以 JSON 格式提取手写文本。为了高效地重新训练模型并防止潜在的过拟合,我们使用了 PyTorch Lightning 的 EarlyStopping 模块。批量大小为 2,训练在 14 个周期后结束。以下是 Donut 模型微调过程的更多细节:
-
我们分配了 1,400 张图像用于训练,199 张用于验证,其余 500 张用于测试。
-
我们使用了一个 naver-clova-ix/donut-base 作为基础模型,它可以在 Hugging Face 上访问。
-
然后使用 24GB 内存的 Quadro P6000 GPU 对模型进行了微调。
-
整个训练时间大约为 3.5 小时。
-
有关更复杂的配置细节,请参考仓库中的
[train_nist.yaml](https://github.com/srsani/hvdu/blob/main/src/config/train_nist.yaml)。
该模型也可以从我们的 Hugging Face 空间 代码库 下载。
集成方法:TT、Donut、GPT
探索了各种集成方法,其中 TT、Donut 和 GPT 的组合表现最佳,具体解释如下。
一旦通过单独应用 TT 和 Donut 获得 JSON 输出,这些输出被用作输入,随后传递给 GPT。目的是利用 GPT 将这些 JSON 输入中的信息与上下文 GPT 信息结合,创建一个新的/更清晰的 JSON 输出,以提高内容的可靠性和准确性 [表 3]。图 5 提供了这一集成方法的视觉概述。

图 5. 结合 TT、Donut 和 GPT 的集成方法的视觉描述。(图片由作者提供)
为此任务创建适当的 GPT 提示是一个迭代过程,并需要引入临时规则。根据附加考虑部分的说明,为此任务 — 以及可能的数据集 — 定制 GPT 提示是本研究需要探索的一个方面。
模型性能测量
本研究主要通过使用两种不同的准确度测量来评估模型性能:
-
字段级准确率(FLA)
-
基于字符的准确度(CBA)
还测量了其他数量,如覆盖率和成本,以提供相关的背景信息。所有指标如下所述。
FLA
这是一个二元测量:如果预测的 JSON 中所有键的字符与参考 JSON 中的字符匹配,则 FLA 为 1;如果有一个字符不匹配,则 FLA 为 0。
请考虑以下示例:
JSON1 = {'DATE': '8/28/89', 'CITY': 'Murray', 'STATE': 'KY', 'ZIP': '42171'}
JSON2 = {'DATE': '8/28/89', 'CITY': 'Murray', 'STATE': 'KY', 'ZIP': '42071'}
使用 FLA 比较 JSON1 和 JSON2 由于 ZIP 不匹配得分为 0。然而,将 JSON1 与自身比较则提供 FLA 得分为 1。
CBA
该准确度测量的计算方法如下:
-
确定每对对应值的 Levenshtein 编辑距离。
-
通过将所有距离相加并除以每个值的总组合字符串长度来获得标准化得分。
-
将此得分转换为百分比。
两个字符串之间的 Levenshtein 编辑距离是将一个字符串转换为另一个字符串所需的更改次数。这包括计数替换、插入或删除。例如,将“marry”转换为“Murray”需要两个替换和一个插入,共计三个更改。这些修改可以以各种顺序进行,但至少需要三次操作。对于此计算,我们使用了 NLTK 库中的 edit_distance 函数。
下面是一个代码片段,展示了所描述算法的实现。此函数接受两个 JSON 输入,并返回一个准确度百分比。
def dict_distance (dict1:dict,
dict2:dict) -> float:
distance_list = []
character_length = []
for key, value in dict1.items():
distance_list.append(edit_distance(dict1[key].strip(),
dict2[key].strip()))
if len(dict1[key]) > len(dict2[key]):
character_length.append((len(dict1[key])))
else:
character_length.append((len(dict2[key])))
accuracy = 100 - sum(distance_list)/(sum(character_length))*100
return accuracy
为了更好地理解该函数,让我们看看它在以下示例中的表现:
JSON1 = {'DATE': '8/28/89', 'CITY': 'Murray', 'STATE': 'KY', 'ZIP': '42171'}
JSON2 = {'DATE': 'None', 'CITY': 'None', 'STATE': 'None', 'ZIP': 'None'}
JSON3 = {'DATE': '8/28/89', 'CITY': 'Murray', 'STATE': 'None', 'ZIP': 'None'}
-
dict_distance(JSON1, JSON1): 100% JSON1 和 JSON1 之间没有差异,因此我们获得了 100% 的完美得分。 -
dict_distance(JSON1, JSON2): 0% JSON2 中的每个字符都需要修改才能匹配 JSON1,从而得出 0% 的得分。 -
dict_distance(JSON1, JSON3): 59% JSON3 中的 STATE 和 ZIP 键的每个字符都必须更改以匹配 JSON1,从而得到 59% 的准确性得分。
我们现在将重点关注分析图像样本的 CBA 平均值。这些准确性测量非常严格,因为它们衡量了检查字符串中的所有字符及字符大小写是否匹配。由于 FLA 的二元特性,使其在处理部分正确的情况时表现得特别保守。尽管 CBA 比 FLA 更不保守,但仍被认为是有些保守的。此外,CBA 能够识别部分正确的实例,但它也考虑了文本大小写(大写与小写),这可能根据是否关注于恢复文本的适当内容或保留书写内容的准确形式而具有不同的重要性。总体而言,我们决定使用这些严格的测量方法,以便采取更保守的方法,因为我们优先考虑文本提取的正确性而非文本语义。
覆盖率
这个量度定义为字段已全部从输出 JSON 中提取的表单图像的比例。这有助于监测从表单中提取所有字段的整体能力,而不考虑其正确性。如果 Coverage 非常低,则表示某些字段系统性地被遗漏在提取过程中。
成本
这是对将每种方法应用于整个测试数据集所产生的成本的简单估算。我们没有计算微调 Donut 模型的 GPU 成本。
结果
我们评估了所有方法在测试数据集上的表现,该数据集包含 500 个样本。这一过程的结果汇总在表 3 中。
使用 FLA 时,我们观察到传统的基于 OCR 的方法 FR 和 TT 表现相似,准确率相对较低(FLA~37%)。虽然不理想,但这可能是由于 FLA 的严格要求。作为替代,当使用 CBA Total,即考虑所有 JSON 键的平均 CBA 值时,TT 和 FR 的表现远远更可接受,值大于 77%。特别是,TT(CBA Total = 89.34%)比 FR 高出约 15%。这种行为在关注单个表单字段的 CBA 值时也得以保持,尤其是在 DATE 和 CITY 类别 [表 3] 中,并且在测量整个样本 2099 张图像的 FLA 和 CBA Totals 时(TT: FLA = 40.06%;CBA Total = 86.64%;FR: FLA = 35.64%;CBA Total = 78.57%)。虽然应用这两种模型的 Cost 值相同,但 TT 更有利于提取所有表单字段,其 Coverage 值比 FR 高出约 9%。

表 3. 性能指标值是根据测试数据集计算的。CBA 总体和 CBA 关键(key= 日期、城市、州、邮政编码)分别是样本平均值和 JSON 键的总值和单独值。(图片由作者提供)
量化这些更传统的基于 OCR 的模型的性能为我们提供了一个基准,我们用这个基准来评估使用纯 Donut 方法与将 Donut 与 TT 和 GPT 结合使用的优缺点。我们从使用 TT 作为基准开始。
采用这种方法的好处体现在微调的 Donut 模型上,该模型在 1400 张图像及其对应的 JSON 上进行了微调。与 TT 结果相比,该模型的全球 FLA 为 54%,CBA 总体为 95.23%,分别提高了 38% 和 6%。最显著的提高体现在 FLA 上,显示出该模型能够准确检索超过一半测试样本中的所有表单字段。
CBA 的提高是显著的,因为用于微调模型的图像数量有限。Donut 模型显示出其优势,表现在 Coverage 和基于关键的 CBA 指标的总体值有所改善,增加幅度在 2% 到 24% 之间。Coverage 达到 100%,表明该模型可以从所有表单字段中提取文本,从而减少了将这种模型投入生产所需的后处理工作。
基于此任务和数据集,这些结果表明,使用经过微调的 Donut 模型产生的结果优于 OCR 模型。最后,还探索了集成方法,以评估是否可以继续进行额外的改进。
由 gpt-3.5-turbo 支持的 TT 和经过微调的 Donut 的集成性能表明,如果选择特定的指标(如 FLA),是有可能实现改进的。与我们经过微调的 Donut 模型相比,该模型的所有指标(不包括 CBA 状态和 Coverage)都有所增加,增加幅度在 ~0.2% 到 ~10% 之间。唯一的性能下降体现在 CBA 状态上,与我们经过微调的 Donut 模型测得的值相比,下降了 ~3%。这可能归因于使用的 GPT 提示,进一步微调可能会改善这一指标。最后,Coverage 值保持在 100% 不变。
与其他单独字段相比,日期提取(见 CBA 日期)效率更高。这可能是由于日期字段的变异性有限,因为所有日期都来源于 1989 年。
如果性能要求非常保守,那么 FLA 的 10%提升是显著的,并可能值得构建和维护更复杂的基础设施的更高成本。这也应考虑到 LLM 提示词修改引入的变异来源,如附加考虑部分所述。然而,如果性能要求不那么严格,那么该集成方法所带来的 CBA 指标改进可能不值得额外的成本和努力。
总体而言,我们的研究表明,虽然基于 OCR 的个别方法——即 FR 和 TT——各有其优点,但仅在 1400 个样本上微调的甜甜圈模型轻松超越了它们的准确性基准。此外,通过 gpt-3.5-turbo 提示词对 TT 和微调后的甜甜圈模型进行集成,进一步提高了通过 FLA 指标测量的准确性。附加考虑也必须考虑甜甜圈模型和 GPT 提示词的微调过程,接下来将进行探讨。
附加考虑
甜甜圈模型训练
为了提高甜甜圈模型的准确性,我们尝试了三种训练方法,每种方法都旨在提高推理准确性,同时防止过拟合训练数据。表 4 展示了我们结果的总结。

表 4. 甜甜圈模型微调的总结。(作者提供的图像)
1. 30-周期训练:我们使用甜甜圈 GitHub 仓库提供的配置训练了 30 个周期的甜甜圈模型。该训练过程持续了约 7 小时,结果显示 FLA 为 50.0%。不同类别的 CBA 值有所不同,CITY 达到 90.55%,ZIP 达到 98.01%。然而,当我们检查 val_metric 时注意到模型在第 19 周期后开始过拟合。
2. 19-周期训练:根据初步训练获得的见解,我们将模型微调了 19 个周期。我们的结果显示 FLA 显著提高,达到 55.8%。整体 CBA 以及基于关键的 CBAs 显示了准确性值的改善。尽管这些指标很有前景,但我们检测到了过拟合的迹象,如 val_metric 所示。
3. 14-周期训练:为了进一步完善我们的模型并抑制潜在的过拟合,我们使用了 PyTorch Lightning 的 EarlyStopping 模块。这种方法在 14 个周期后终止了训练。结果显示 FLA 为 54.0%,CBAs 与 19 周期训练相比相当,甚至更好。
比较这三次训练的输出时,尽管 19 周期训练的 FLA 略有改善,但 14 周期训练的 CBA 指标整体上更优。此外,val_metric 强化了我们对 19 周期训练的担忧,表明略有过拟合的倾向。
总结来说,我们推断出,经过 14 个周期使用 EarlyStopping 微调的模型是最强健且最具成本效益的。
提示工程的变化
我们研究了两种提示工程方法(ver1 和 ver2),通过将微调的 Donut 模型与 TT 结果进行集成,以提高数据提取效率。在训练模型 14 个周期后,Prompt ver1 取得了更好的结果,其 FLA 为 59.6%,并且所有键的 CBA 指标均较高[表 5]。相比之下,Prompt ver2 出现了下降,其 FLA 降至 54.4%。详细查看 CBA 指标显示,ver2 中每个类别的准确度评分略低于 ver1,突显了这一变化的显著差异。

表 5。结果总结:从表单中提取手写文本。(图片由作者提供)
在对数据集进行手动标注过程中,我们利用了 TT 和 FR 的结果,并在对表单文本进行标注时开发了 Prompt ver1。尽管 Prompt ver2 在本质上与其前身相同,但经过了轻微修改。我们的主要目标是通过去除 Prompt ver1 中的空行和冗余空格来改进提示。
总之,我们的实验突显了看似微小调整的细微影响。尽管 Prompt ver1 展示了更高的准确性,但将其精炼和简化为 Prompt ver2 的过程,悖论地导致了所有指标上的性能下降。这凸显了提示工程的复杂性以及在最终确定提示之前进行细致测试的必要性。
Prompt ver1 可在这个 Notebook中找到,Prompt ver2 的代码可以在这里查看。
结论
我们创建了一个基准数据集,用于从包含四个字段(DATE、CITY、STATE 和 ZIP)的手写表单图像中提取文本。这些表单被手动标注为 JSON 格式。我们使用此数据集评估了基于 OCR 的模型(FR 和 TT)和一个 Donut 模型,该模型随后使用我们的数据集进行了微调。最后,我们采用了一个通过提示工程构建的集成模型,该模型利用了 LLM(gpt-3.5-turbo)与 TT 和我们微调的 Donut 模型输出。
我们发现 TT 的表现优于 FR,并以此作为基准来评估 Donut 模型在单独使用或与 TT 和 GPT 组合使用(即集成方法)时可能产生的改进。正如模型性能指标所显示的那样,这种微调的 Donut 模型显示出明显的准确性提升,这证明了其优于基于 OCR 的模型。集成模型显著提高了 FLA,但成本较高,因此可以在要求更严格的性能要求的情况下考虑使用。尽管使用了相同的基础模型 gpt-3.5-turbo,但我们观察到当提示中进行微小更改时,输出 JSON 表单存在显著差异。这种不可预测性是使用现成 LLM 的一个重大缺陷。我们目前正在开发基于开源 LLM 的更紧凑的清理流程来解决这个问题。
下一步
-
表 2 中的价格列显示,OpenAI API 调用是本研究中最昂贵的认知服务。因此,为了减少成本,我们正在通过利用完全微调、提示微调[5] 和 QLORA [6] 等方法来微调 LLM 以进行 seq2seq 任务。
-
由于隐私原因,数据集中图像上的名称框被黑色矩形覆盖。我们正在通过向数据集中添加随机的名字和姓氏来更新这一点,这将使数据提取字段从四个增加到五个。
-
未来,我们计划通过将这项研究扩展到包括整个表单或其他更大文档的文本提取任务,从而增加文本提取任务的复杂性。
-
调查 Donut 模型超参数优化。
参考文献
-
Amazon Textract,AWS Textract
-
表单识别器,表单识别器(现为文档智能)
-
Kim, Geewook 和 Hong, Teakgyu 和 Yim, Moonbin 和 Nam, JeongYeon 和 Park, Jinyoung 和 Yim, Jinyeong 和 Hwang, Wonseok 和 Yun, Sangdoo 和 Han, Dongyoon 和 Park, Seunghyun,无 OCR 文档理解转换器 (2022),欧洲计算机视觉会议 (ECCV)
-
Grother, P. 和 Hanaoka, K. (2016) NIST 手写表单和字符数据库 (NIST 特殊数据库 19)。DOI:
doi.org/10.18434/T4H01C -
Brian Lester, Rami Al-Rfou, Noah Constant,规模化的参数高效提示微调的力量 (2021),arXiv:2104.08691
-
Tim Dettmers, Artidoro Pagnoni, Ari Holtzman, Luke Zettlemoyer,QLoRA: 高效的量化 LLM 微调 (2023),
arxiv.org/abs/2305.14314
致谢
我们要感谢我们的同事大卫·罗德里格斯博士,他对这个项目的持续支持和讨论。我们还要感谢Kainos的支持。
从 Powerpoints 到 PDFs 再到 CSV 文件:用于读取主要文件类型的 Python 类
在下一个数据科学项目中能够从多种不同的文件类型中提取和比较信息!
·发表于 Towards Data Science ·阅读时间 6 分钟·2023 年 1 月 6 日
--

图片来源于 Glen Carrie 在 Unsplash
介绍
数据科学家可能需要读取多种不同类型的文件以完成他们的项目。虽然在 Python 中读取 文本 文件特别容易,但其他文件类型可能需要来自不同 Python API 的额外支持,以确保它们在 Python 中可读和可用。今天的代码提供了多种 Python 类,可用于读取许多不同的文件类型。每个类的输出是一个文本字符串,数据科学家可以利用这些字符串进行信息提取以及各种文档的相似性分析。
PDF 文件
我在过去多次讨论了 PDF 文件的重要性及其在 Python 中的处理方法。
介绍如何在下一个仪表板中读取和展示 PDF 文件。
towardsdatascience.com ## 自然语言处理:用于获取概述的 PDF 处理功能
今天用于自然语言处理 (NLP) 的许多文档都是 .pdf 格式的。读取 pdf 文件到…
[towardsdatascience.com
我想包括下面的类,因为我无法强调在下一个项目中使用 PDF 文件的重要性。今天,许多人使用 PDF 执行各种任务,里面的信息太多,不应轻易丢弃。pdfReader 类有两个功能:一个是使 PDF 可以被 Python 读取,另一个是将 PDF 转换为 Python 中的一个文本字符串。注意:最近 PyPDF2 有了新的更新,所以在使用未更新的旧教程时要小心。PyPDF2 3.0.0 中许多方法名称已被更改。
!pip install PyPDF2
import PyPDF2
import re
class pdfReader:
def __init__(self, file_path: str) -> None:
self.file_path = file_path
def pdf_reader(self) -> None:
"""A function that can read .pdf formatted files
and returns a python readable pdf.
Returns:
read_pdf: A python readable .pdf file.
"""
opener = open(self.file_path,'rb')
read_pdf = PyPDF2.PdfReader(opener)
return read_pdf
def PDF_one_pager(self) -> str:
"""A function that returns a one line string of the
pdf.
Returns:
one_page_pdf (str): A one line string of the pdf.
"""
p = pdfReader(self.file_path)
pdf = p.pdf_reader()
content= ''
num_pages = len(pdf.pages)
for i in range(0, num_pages):
content += pdf.pages[i].extract_text() + "\n"
content = " ".join(content.replace(u"\xa0", " ").strip().split())
page_number_removal = r"\d{1,3} of \d{1,3}"
page_number_removal_pattern = re.compile(page_number_removal, re.IGNORECASE)
content = re.sub(page_number_removal_pattern, '',content)
return content
Microsoft Powerpoint 文件
有趣的事实:有一种方法可以将 Microsoft Powerpoint 文件读入 Python 并查看它们包含的信息。下面的 pptReader 类将从你 Powerpoint 演示文稿中的每一张幻灯片读取文本,并将其添加到一个文本字符串中。不要担心 Powerpoint 中的图片,ppt_text() 函数会处理这些内容!
class pptReader:
def __init__(self, file_path: str) -> None:
self.file_path = file_path
def ppt_text(self) -> str:
"""A function that returns a string of text from all
of the slides in a pptReader object.
Returns:
text (str): A single string containing the text
within each slide of the pptReader object.
"""
prs = Presentation(self.file_path)
text = str()
for slide in prs.slides:
for shape in slide.shapes:
if not shape.has_text_frame:
continue
for paragraph in shape.text_frame.paragraphs:
for run in paragraph.runs:
text += ' ' + run.text
return text
Microsoft Excel 文件
出乎意料的是,Excel 文件比我最初预期的更难以读入 Python。我必须使用 openpyxl 库首先将 Excel 文件读入 Python。我没有得到想要的输出,于是将读入的文件保存为标准的 csv 文件,然后继续处理。这个技巧有效!
class xlsxReader:
def __init__(self, file_path: str) -> None:
self.file_path = file_path
def xlsx_text(self) -> str:
"""A function that will return a string of text from
the information contained within an xlsxReader object.
Returns:
text (str): A string of text containing the information
within the xlsxReader object.
"""
inputExcelFile = self.file_path
text = str()
wb = openpyxl.load_workbook(inputExcelFile)
for ws in wb.worksheets:
for val in ws.values:
print(val)
for sn in wb.sheetnames:
print(sn)
excelFile = pd.read_excel(inputExcelFile, engine = 'openpyxl', sheet_name = sn)
excelFile.to_csv("ResultCsvFile.csv", index = None, header=True)
with open("ResultCsvFile.csv", "r") as csvFile:
lines = csvFile.read().split(",") # "\r\n" if needed
for val in lines:
if val != '':
text += val + ' '
text = text.replace('\ufeff', '')
text = text.replace('\n', ' ')
return text
CSV 文件
你可能会想知道为什么我没有使用 Pandas 库来读取我的 CSV 文件。我使用传统的 open() 方法,因为我想要获取给定 csv 文件中的所有信息,而且许多 csv 文件的格式不同。此外,我倾向于使用标准 Python 库,而不是导入外部 API,当内置函数与 API 同样有效或可以快速完成我的任务时。
class csvReader:
def __init__(self, file_path: str) -> str:
self.file_path = file_path
def csv_text(self):
"""A function that returns a string of text containing
the information within a csvReader object.
Returns:
text (str): A string of text containing information
within a csvReader object.
"""
text = str()
with open(self.file_path, "r") as csvFile:
lines = csvFile.read().split(",") # "\r\n" if needed
for val in lines:
text += val + ' '
text = text.replace('\ufeff', '')
text = text.replace('\n', ' ')
return text
额外:自然语言预处理类
在一个项目中处理所有上述类时,通过将每个类的输出格式化为文本字符串来进行归一化处理。我最近在处理各种文档时,还需要对每个字符串进行预处理,以增加更多的归一化以及移除不必要的信息。在将所有文件读入 Python 后,使用 data processor 类清理每个字符串,并在所有样本中标准化它们。
class dataprocessor:
def __init__(self):
return
@staticmethod
def get_wordnet_pos(text: str) -> str:
"""Map POS tag to first character lemmatize() accepts.
Parameters:
text (str): A string of text
Returns:
tag: The tag of the word
"""
tag = nltk.pos_tag([text])[0][1][0].upper()
tag_dict = {"J": wordnet.ADJ,
"N": wordnet.NOUN,
"V": wordnet.VERB,
"R": wordnet.ADV}
return tag_dict.get(tag, wordnet.NOUN)
@staticmethod
def preprocess(input_text: str):
"""A function that accepts a string of text and conducts various
NLP preprocessing steps on said text including puncation removal,
stopword removal and lemmanization.
Parameters:
input_text (str): A string of text
Returns:
output_text (str): A processed string of text.
"""
#lowercase
input_text = input_text.lower()
#punctuation removal
input_text = "".join([i for i in input_text if i not in string.punctuation])
#Stopword removal
stopwords = nltk.corpus.stopwords.words('english')
custom_stopwords = ['\n','\n\n', '&', ' ', '.', '-', '$', '@']
stopwords.extend(custom_stopwords)
input_text = [i for i in input_text if i not in stopwords]
input_text = ' '.join(word for word in input_text)
#lemmanization
lm = WordNetLemmatizer()
input_text = [lm.lemmatize(word, dataprocessor.get_wordnet_pos(word)) for word in input_text.split(' ')]
text = ' '.join(word for word in input_text)
output_text = re.sub(' +', ' ',input_text)
return output_text
结论
今天我们讨论了数据科学家在下一个项目中可以使用的不同 Python 类,以便读取各种文件类型。虽然还有其他可以读取到 Python 中的文件类型,但今天讨论的这些种类非常重要,有时可能会被忽视。使用今天介绍的文件读取类和最终的数据清理类,你可以比较和对比完全不同文件类型中的信息。例如,也许你想使用自然语言处理来查看不同的研究文献,找出哪些相似,然后推荐给学生作为当前课程的学习资料。这只是从今天提供的类中可以创建的众多不同项目之一,祝你享受!
如果你喜欢今天的阅读内容,请给我关注,并告诉我是否有其他话题你希望我探索!如果你没有 Medium 账户,可以通过我的链接 这里注册!另外,欢迎添加我的 LinkedIn,或者随时联系我!感谢阅读!
从穿孔卡到 ChatGPT
我祖父的生成式人工智能一瞥
·
关注 发布在Towards Data Science ·9 分钟阅读·2023 年 11 月 1 日
--
我的母亲的父亲,Skip,在我眼中一直是一名农夫。不幸的是,我母亲在 1988 年我的出生后仅一个月就因白血病去世。作为家庭中的第一个孙子,我和 Skip 非常亲密。小时候,我常常坐在拖拉机和联合收割机的扶手上度过我的一天,后来成为青少年时,我也在农场做了一个夏季工作。

在夏季小麦收割期间,从空中俯瞰一台约翰迪尔联合收割机。在 20 世纪 90 年代末/21 世纪初,我和我的祖父在我们自己的小麦收割季节中使用了两台这样的机器。那是我的第一份“工作”。照片由Scott Goodwill拍摄,来源于Unsplash。
然而,Skip 早期的生活与我认识的农业世界大相径庭。在我进入这个场景之前,他已经深入学术领域,1972 年完成了德州农工大学(Texas A&M, College Station)的统计学博士课程。不久之后,他接受了马里兰大学的教授职位,并于 1974 年完成了他的论文。他开创性的研究旨在预测和定位工业环境中的安全和材料风险。这项艰巨的任务需要数年的努力。他不得不手动从不同公司收集十年的事故报告,手工处理统计数据,然后将这些见解转换为大学计算机系统的打孔卡指令。获取计算机使用时间并非立即可得;这需要提前几周甚至几个月预约。一个编码错误可能意味着从头开始,可能会使他的研究延迟几个月。
他在 1980 年代离开了那种生活,回到东德州接管家族农场,并开始了创业生涯。但在他作为农民的所有工作中,运用统计推断的愿望始终存在——只是我小时候并没有意识到。对我孩子的理解来说,Skip 在做他所称的“办公室工作”——但实际上他是在利用 IT 来预测和确保融资以应对运营开支,优化化肥中的化学成分以提高作物产量,通过在芝加哥商品交易所交易期货来降低现金流的不确定性,这些都是在他用 16 KB RAM 的Radio Shack 购买的 TRS 80上完成的,连接了一台点阵打印机。农业可以是一个利润微薄的行业——而 Skip 的赌注是他可以利用统计数据稍微拉平竞争环境。
多年来,这个农场没有经受住时间的考验。事实证明,当代际农业被迫跳过一代时,其效果并不好——而且今天的投入成本比以往任何时候都更为严苛——规模经济成为了唯一可以盈利竞争的方式——因此,Skip 一代的大多数中小型农场被收购和整合——但这是一种逐步发生的过程——一点一点地(至少我们是这样经历的)。
我当然逐渐欣赏到统计学与农业之间的紧密联系。我仍然记得来自美国农业部的年度访问,他们详细采样作物产量(包括我们的农场),作为他们国家农业统计服务的一部分。在我看来,这是历史上一个伟大的未被充分重视的持续数据项目——帮助一代代农民做出“数据驱动”的决策——在那个词还未成为流行词之前。但我对 Skip 几十年来所做的一切有了更深的理解,因为我开始了自己的分析与数据科学事业——这是我在 20 多岁和 30 多岁时作为美国陆军军官服务和环球旅行后的第二幕。我经常通过电话与他联系,询问他们如何进行回归分析或模拟,或他们如何在“那时候”控制随机抽样。然后偶尔告诉他现在的做法,以评估他对我描述机器学习、深度学习、强化学习等概念的兴奋——对他来说,这有些像科幻小说——但他喜欢听这些——即使这在他晚年并不完全真实。

当我看到约翰·迪尔在 2023 年 1 月 CES 上进行的主题演讲——展示了新设备,例如上图所示的喷雾器,它使用 36 台相机、计算机视觉和神经网络实时检测杂草与作物,并最小化除草剂的使用。图像由作者提供。
实验开始了。
我决定在周末做出一个展示而不是讲述的决定。我希望 Skip 亲自体验新技术,而不仅仅是听我谈论它。如果我们能在不到 40 分钟内制定一个启动虚拟新农场的商业计划呢?我可以使用启用了高级数据分析的 ChatGPT 4,通过屏幕分享给他的 iPhone——我们一起合作完成它。
我们简要讨论了我们的假设:
-
我们在东德克萨斯州有 1000 英亩的农田。
-
我们有 100 万美元的启动资金用于购买设备。
-
我们需要从银行获得一笔信用额度,以支付第一年的运营费用以启动项目。
-
我们有兴趣种植小麦和大豆(Skip 曾经种植的作物)。
我们从简单开始:
提示 1: “为我写一个商业计划大纲,以启动东德克萨斯州的新农场,种植小麦和大豆”。
Skip 立刻对详细结果留下了深刻的印象。但等等,我们才刚刚开始……
提示 2: “给我一个我需要开始的设备清单”
提示 3: “将其放入表格中,并根据最紧急的到最不紧急的按数字优先级排序”。
提示 4: “现在创建一个新的预算分配列,假设我将使用租赁和购买的组合,创建一个额外的列来推荐租赁与购买,假设我有 100 万美元的启动资金。”
(时间耗时 4 分钟)
结果:

数据由 GPT-4 生成,图片由作者提供。
提示 5: “我应该留出多少资金来申请一条用于变动成本(种子、肥料、劳动力等)的信用额度,假设为 1000 英亩。创建一个新的表格。”
结果:

数据由 ChatGPT 4 生成,图片由作者提供
到了这个时候,我感觉 Skip 就像是再次目睹了阿波罗 11 号。他几乎抑制不住他的兴奋。但我们还没完成……
提示 6: “给我写一个 Monte Carlo 模拟,我可以调整输入假设并测试我的预算结果”
结果:
import numpy as np
import matplotlib.pyplot as plt
# Define number of simulations
n_simulations = 10000
# Assumptions with [mean, std deviation]
wheat_seed_cost = [20, 2] # Cost per acre
soybean_seed_cost = [50, 5]
fertilizer_wheat_cost = [50, 5]
fertilizer_soybean_cost = [40, 4]
labor_cost = [25, 3]
pesticide_cost = [30, 3]
fuel_maintenance_cost = [15, 2]
miscellaneous_cost = [10, 1]
def simulate_farm_costs():
wheat_seed = np.random.normal(wheat_seed_cost[0], wheat_seed_cost[1])
soybean_seed = np.random.normal(soybean_seed_cost[0], soybean_seed_cost[1])
fertilizer_wheat = np.random.normal(fertilizer_wheat_cost[0], fertilizer_wheat_cost[1])
fertilizer_soybean = np.random.normal(fertilizer_soybean_cost[0], fertilizer_soybean_cost[1])
labor = np.random.normal(labor_cost[0], labor_cost[1])
pesticide = np.random.normal(pesticide_cost[0], pesticide_cost[1])
fuel_maintenance = np.random.normal(fuel_maintenance_cost[0], fuel_maintenance_cost[1])
miscellaneous = np.random.normal(miscellaneous_cost[0], miscellaneous_cost[1])
total_cost = (wheat_seed + soybean_seed + fertilizer_wheat + fertilizer_soybean +
labor + pesticide + fuel_maintenance + miscellaneous) * 1000 # for 1000 acres
return total_cost
results = [simulate_farm_costs() for _ in range(n_simulations)]
# Visualization
plt.hist(results, bins=50, edgecolor='black', alpha=0.7)
plt.title('Distribution of Total Farm Costs')
plt.xlabel('Total Cost')
plt.ylabel('Frequency')
plt.grid(True, alpha=0.2)
plt.show()
现在是关键时刻——代码会运行吗?我们跳到 Jupyter notebook 运行代码。第一次尝试就成功了:

数据由 ChatGPT 4 生成,图片由 Python 生成,图片由作者提供
到了这个时候,我们仅用了 10-15 分钟。还有一些时间……我们可以使它具有互动性吗?
我们需要创建一个参数列表,允许用户在滑块上动态调整假设。我们再次向 ChatGPT 寻求一些关于这些参数的建议,基于我们之前构建的 Monte Carlo 模拟:

数据由 GPT 4 生成,图片由作者提供
一旦我们建立了参数列表,我们在 Power BI 中创建了一个“度量”表,连接到 16 个切片器视觉效果,允许用户手动选择他们的输入并动态更新 Monte Carlo 模拟。为此,我们在 Power BI 中创建了一个“Python 视觉”,将所有度量拖入其中,然后更新代码如下:
# The following code to create a dataframe and remove duplicated rows is always executed and acts as a preamble for your script:
# dataset = pandas.DataFrame(fertilizer_soybean_cost_avg Value, fertilizer_soybean_cost_std Value, fertilizer_wheat_cost_avg Value, fertilizer_wheat_cost_std Value, fuel_maintenance_cost_avg Value, fuel_maintenance_cost_std Value, labor_cost_avg Value, labor_cost_std Value, miscellaneous_cost_avg Value, miscellaneous_cost_std Value, pesticide_cost_avg Value, pesticide_cost_std Value, soybean_seed_cost_avg Value, wheat_seed_cost_avg Value, wheat_seed_cost_std Value)
# dataset = dataset.drop_duplicates()
# Paste or type your script code here:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Assuming the data from Power BI is passed as 'dataset'
df = dataset
# Fetch values from the dataset
wheat_seed_cost_avg = df['wheat_seed_cost_avg Value'].iloc[0]
wheat_seed_cost_std = df['wheat_seed_cost_std Value'].iloc[0]
soybean_seed_cost_avg = df['soybean_seed_cost_avg Value'].iloc[0]
soybean_seed_cost_std = df['soybean_seed_cost_std Value'].iloc[0]
fertilizer_wheat_cost_avg = df['fertilizer_wheat_cost_avg Value'].iloc[0]
fertilizer_wheat_cost_std = df['fertilizer_wheat_cost_std Value'].iloc[0]
fertilizer_soybean_cost_avg = df['fertilizer_soybean_cost_avg Value'].iloc[0]
fertilizer_soybean_cost_std = df['fertilizer_soybean_cost_std Value'].iloc[0]
labor_cost_avg = df['labor_cost_avg Value'].iloc[0]
labor_cost_std = df['labor_cost_std Value'].iloc[0]
pesticide_cost_avg = df['pesticide_cost_avg Value'].iloc[0]
pesticide_cost_std = df['pesticide_cost_std Value'].iloc[0]
fuel_maintenance_cost_avg = df['fuel_maintenance_cost_avg Value'].iloc[0]
fuel_maintenance_cost_std = df['fuel_maintenance_cost_std Value'].iloc[0]
miscellaneous_cost_avg = df['miscellaneous_cost_avg Value'].iloc[0]
miscellaneous_cost_std = df['miscellaneous_cost_std Value'].iloc[0]
# Define number of simulations
n_simulations = 10000
# Assumptions with [mean, std deviation]
wheat_seed_cost = [wheat_seed_cost_avg, wheat_seed_cost_std]
soybean_seed_cost = [soybean_seed_cost_avg, soybean_seed_cost_std]
fertilizer_wheat_cost = [fertilizer_wheat_cost_avg, fertilizer_wheat_cost_std]
fertilizer_soybean_cost = [fertilizer_soybean_cost_avg, fertilizer_soybean_cost_std]
labor_cost = [labor_cost_avg, labor_cost_std]
pesticide_cost = [pesticide_cost_avg, pesticide_cost_std]
fuel_maintenance_cost = [fuel_maintenance_cost_avg, fuel_maintenance_cost_std]
miscellaneous_cost = [miscellaneous_cost_avg, miscellaneous_cost_std]
def simulate_farm_costs():
wheat_seed = np.random.normal(wheat_seed_cost[0], wheat_seed_cost[1])
soybean_seed = np.random.normal(soybean_seed_cost[0], soybean_seed_cost[1])
fertilizer_wheat = np.random.normal(fertilizer_wheat_cost[0], fertilizer_wheat_cost[1])
fertilizer_soybean = np.random.normal(fertilizer_soybean_cost[0], fertilizer_soybean_cost[1])
labor = np.random.normal(labor_cost[0], labor_cost[1])
pesticide = np.random.normal(pesticide_cost[0], pesticide_cost[1])
fuel_maintenance = np.random.normal(fuel_maintenance_cost[0], fuel_maintenance_cost[1])
miscellaneous = np.random.normal(miscellaneous_cost[0], miscellaneous_cost[1])
total_cost = (wheat_seed + soybean_seed + fertilizer_wheat + fertilizer_soybean +
labor + pesticide + fuel_maintenance + miscellaneous) * 1000 # for 1000 acres
return total_cost
results = [simulate_farm_costs() for _ in range(n_simulations)]
# Convert results into a dataframe
df_simulated_results = pd.DataFrame(results, columns=['Total Cost'])
# Calculate the Interquartile Range (IQR)
Q1 = df_simulated_results['Total Cost'].quantile(0.25)
Q3 = df_simulated_results['Total Cost'].quantile(0.75)
# Plotting the histogram
plt.figure(figsize=(10, 6))
n, bins, patches = plt.hist(df_simulated_results['Total Cost'], bins=50, color='blue', edgecolor='black', alpha=0.7)
plt.title('Distribution of Year 1 Variable Farm Costs from Simulation')
plt.xlabel('Year 1 Variable Cost')
plt.ylabel('Frequency')
plt.grid(True, which='both', linestyle='--', linewidth=0.5)
# Shade the IQR
for i in range(len(bins)):
if bins[i] > Q1 and bins[i] < Q3:
patches[i].set_facecolor('green')
plt.axvline(Q1, color='red', linestyle='dashed', linewidth=1)
plt.axvline(Q3, color='red', linestyle='dashed', linewidth=1)
plt.tight_layout()
plt.savefig('simulated_costs_histogram.png') # This will save the figure as an image file
plt.show()
只是为了好玩,我们提示 ChatGPT 定义四分位距(IQR)并将其着色为不同的颜色,我们还手动更新了图表标签和 x 轴。其余的只是稍微清理一下 Power BI 中的视觉效果,使其更用户友好。最终结果:

数据由用户选择的参数输入生成的 Monte Carlo 模拟,Python 代码由 ChatGPT 4 生成,仪表板在 MS PowerBI 中构建,图片由作者提供
现在我们有了一个动态的 Monte Carlo 模拟,可以用来测试不同的输入成本假设并预测我们启动农业运营所需的变动运营费用。借助 ChatGPT 4,我们几乎没有编写代码,只是稍微调整了一下,大部分工作通过屏幕共享在 iPhone 上完成,最后一部分在 Power BI desktop 中构建,并通过 Power BI iPhone 应用程序屏幕共享。总共花费了大约 30-40 分钟。
我祖父的评判?“我们在 40 分钟内完成了他自己‘过去’需要 2 年才能完成的工作。” 是的,我意识到我们还有很多可以做的——而且“模拟”远非完美。(例如,我们没有区分投入大豆与小麦的作物百分比。)但 40 分钟呢?即使是我也感到印象深刻。这就是生成 AI 的承诺——使数据科学民主化,鼓励实验,并加快在你手掌之间开发的能力。让祖父和孙子有机会通过一些统计数据重新建立联系,并以新的、出乎意料的方式利用技术。
从 Python 到 Julia:基本数据操作和探索性数据分析
原文:
towardsdatascience.com/from-python-to-julia-basic-data-manipulation-and-eda-51171b34f685

图片由作者提供
·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 6 月 20 日
--
作为统计计算领域的一种新兴编程语言,Julia 近年来获得了越来越多的关注。Julia 有两个特性使其优于其他编程语言。
-
Julia 是一种像 Python 一样的高级语言。因此,它易于学习和使用。
-
Julia 是一种编译语言,设计目标是与 C/C++ 一样快速。
当我第一次了解 Julia 时,我被它的计算速度吸引了。所以我决定尝试一下 Julia,看看我是否可以在日常工作中实际使用它。
作为一名数据科学从业者,我使用 Python 开发各种用途的原型 ML 模型。为了快速学习 Julia,我将模拟我在 Python 和 Julia 中构建简单 ML 模型的常规过程。通过将 Python 和 Julia 代码并排比较,我可以轻松捕捉这两种语言的语法差异。这就是本博客将按以下章节安排的原因。
设置
在开始之前,我们需要先在工作站上安装 Julia。Julia 的安装分为以下 2 步骤。
-
从 官方网站 下载安装程序文件。
-
解压安装程序文件并创建指向 Julia 二进制文件的符号链接。
以下博客提供了安装 Julia 的详细指南。
关于如何在 Ubuntu 上安装 Julia 并将内核添加到 Jupyter 的快速教程
数据集
我将使用从 Kaggle 获得的信用卡欺诈检测数据集。该数据集包含 492 个欺诈交易,出现在 284,807 个交易中。共有 30 个特征,包括交易时间、金额,以及通过 PCA 获得的 28 个主成分。交易的“Class”是待预测的目标变量,表示交易是否为欺诈。
类似于 Python,Julia 社区开发了各种包以支持 Julia 用户的需求。这些包可以通过 Julia 的包管理器 Pkg 安装,相当于 Python 的 pip。
我使用的欺诈检测数据采用典型的 .csv 格式。要在 Julia 中将 csv 数据加载为数据框,需要导入 CSV 和 DataFrame 包。DataFrame 包可以视为 Julia 中 Pandas 的等价物。
将结构化数据加载为数据框 — Julia 实现
下面是导入的数据的样子。

作者提供的图片
在 Jupyter 中,加载的数据集可以如上图所示。如果您希望查看更多列,一种快速的解决方案是指定环境变量 ENV["COLUMNS"]。否则,最多只会显示不到 10 列。
相应的 Python 实现如下。

将结构化数据加载为数据框 — Python 实现(作者提供的图片)
探索性数据分析(EDA)
探索性分析允许我们检查数据质量并发现特征之间的模式,这对特征工程和训练 ML 模型非常有用。
基本统计
我们可以从计算特征的一些简单统计数据开始,例如均值、标准差。类似于 Python 中的 Pandas,Julia 的 DataFrame 包提供了 describe 函数来实现这一目的。

使用 Julia 中的 describe 函数生成基本统计数据(作者提供的图片)
describe 函数允许我们生成 12 种基本统计数据。我们可以通过更改 :all 参数来选择生成哪一种,例如 describe(df, :mean, :std)。令人有些烦恼的是,如果不指定 :all,即使设置了显示列的上限,describe 函数也会不断遗漏统计数据的显示。这是 Julia 社区未来可以改进的地方。

Julia 遗漏了指定统计数据的打印 😕(作者提供的图片)
类别平衡
欺诈检测数据集通常存在极端的类别不平衡问题。因此,我们希望了解两个类别之间的数据分布。在 Julia 中,这可以通过应用“split-apply-combine”函数来实现,这等同于 Python 中 Pandas 的“groupby-aggregate”函数。

检查类别分布——Julia 实现(图像由作者提供)
在 Python 中,我们可以使用value_counts()函数实现相同的目的。

检查类别分布——Python 实现(图像由作者提供)
单变量分析
接下来,让我们通过直方图深入了解特征的分布。特别是,我们以交易金额和时间为例,因为它们是数据集中唯一可解释的特征。
在 Julia 中,有一个名为StatsPlots的实用库,允许我们绘制各种常用的统计图表,包括直方图、条形图、箱形图等。
以下代码在两个子图中绘制了交易金额和时间的直方图。可以观察到交易金额高度偏斜。对于大多数交易,交易金额低于 100。交易时间遵循双峰分布。
绘制交易时间和交易金额的分布图——Julia 实现

绘制交易时间和交易金额的分布图——Julia 实现(图像由作者提供)
在 Python 中,我们可以使用 matplotlib 和 seaborn 创建相同的图表。

绘制交易时间和交易金额的分布图——Python 实现(图像由作者提供)
双变量分析
虽然上述单变量分析展示了交易金额和时间的一般模式,但它并未告诉我们这些与待预测的欺诈标志如何相关。为了快速了解特征与目标变量之间的关系,我们可以创建一个相关矩阵并通过热图进行可视化。
在创建相关矩阵之前,我们需要注意到数据高度不平衡。为了更好地捕捉相关性,需要对数据进行下采样,以避免特征的影响因数据不平衡而被“稀释”。这个过程需要数据框的切片和连接。以下代码演示了 Julia 中下采样的实现。

Julia 中的数据下采样(图像由作者提供)
上述代码统计了欺诈交易的数量,并将这些欺诈交易与相同数量的非欺诈交易结合起来。接下来,我们可以创建热图来可视化相关矩阵。
绘制热图以可视化相关矩阵——Julia 实现
结果热图如下所示。

由 Julia 绘制的特征相关性热图(图像由作者提供)
这里是 Python 中的下采样和热图绘制的等效实现。

下采样和绘制相关性热图 — Python 实现(图片由作者提供)
在对特征相关性进行概述后,我们希望深入研究与目标变量(在本例中为“Class”)显著相关的特征。从热图中可以观察到,以下经过 PCA 转换的特征与“Class”具有正相关关系:V2、V4、V11、V19,而具有负相关关系的特征包括 V10、V12、V14、V17。我们可以使用箱线图来检查这些突出特征对目标变量的影响。
在 Julia 中,可以使用上述的 StatsPlots 包来创建箱线图。在这里,我使用与“Class”正相关的 4 个特征作为示例来说明如何创建箱线图。
创建箱线图以可视化特征对“Class”的影响 — Julia 实现
@df 作为宏,用于表示在目标数据集上创建箱线图,即 balanced_df。结果图如下所示。

特征与“Class”之间的正相关箱线图(图片由作者提供)
以下代码可以用来在 Python 中创建相同的箱线图。

创建箱线图以可视化特征对“Class”的影响 — Python 实现(图片由作者提供)
插曲
在此,我将暂停,并简要评论一下我迄今为止使用 Julia 的“用户体验”。就语言语法而言,Julia 似乎介于 Python 和 R 之间。Julia 包提供了对数据处理和探索性数据分析(EDA)各种需求的全面支持。然而,由于 Julia 的发展仍处于早期阶段,这种编程语言仍然缺乏资源和社区支持。寻找某些数据处理练习的 Julia 实现,如 展开类似列表的数据框列,可能需要花费很多时间。此外,Julia 的语法远未稳定,如 Python 3。此时,我不会说 Julia 是大型企业和企业的良好编程语言选择。
我们还未完成构建欺诈检测模型的工作。我将在下一篇博客中继续,敬请关注!
Jupyter notebook 可以在 Github 上找到。
参考文献
- 布鲁塞尔自由大学机器学习小组。 (无日期)。信用卡欺诈检测 [数据集]。
www.kaggle.com/datasets/mlg-ulb/creditcardfraud
从 Python 到 Julia:特征工程与机器学习

图片由 CardMapr.nl 提供,Unsplash 上的
基于 Julia 的欺诈检测模型构建方法
·
关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 6 月 26 日
--
这是我关于如何使用 Julia 进行应用数据科学的两部分系列中的第二部分。在 第一篇文章 中,我们回顾了几个简单的数据操作示例以及使用 Julia 进行的探索性数据分析。在本博客中,我们将继续构建一个欺诈检测模型以识别欺诈性交易。
简要回顾,我们使用了从 Kaggle 获取的 信用卡欺诈检测数据集。该数据集包含 30 个特征,包括交易时间、金额和 28 个通过 PCA 获得的主成分特征。下面是数据集前 5 个实例的截图,以数据框的形式加载到 Julia 中。请注意,交易时间特征记录了当前交易与数据集中第一笔交易之间的经过时间(以秒为单位)。
特征工程
在训练欺诈检测模型之前,让我们准备好供模型使用的数据。由于本博客的主要目的是介绍 Julia,因此我们不会在此进行特征选择或特征合成。
数据划分
在训练分类模型时,数据通常以分层的方式进行训练和测试划分。主要目的是在训练数据和测试数据中保持数据相对于目标类变量的分布。这在处理极度不平衡的数据集时尤其必要。MLDataUtils 包在 Julia 中提供了一系列预处理函数,包括数据划分、标签编码和特征归一化。以下代码展示了如何使用 MLDataUtils 的 stratifiedobs 函数进行分层抽样。可以设置随机种子以便重现相同的数据划分。
数据划分用于训练和测试 — Julia 实现
stratifiedobs 函数的使用与 Python 中 sklearn 库的 train_test_split 函数非常相似。请注意,输入特征 X 需要经过两次转置以恢复数据集的原始维度。这对于像我这样的 Julia 新手来说可能会令人困惑。我不确定 MLDataUtils 的作者为何这样开发这个函数。
等效的 Python sklearn 实现如下。

数据划分用于训练和测试 — Python 实现(图片来源于作者)
特征缩放
作为机器学习中的推荐实践,特征缩放将特征调整到相同或类似的值范围或分布。特征缩放有助于提高训练神经网络时的收敛速度,并且避免了在训练过程中某个特征的主导地位。
尽管我们在这项工作中没有训练神经网络模型,但我仍然想了解如何在 Julia 中进行特征缩放。不幸的是,我找不到一个同时提供缩放器拟合和特征变换功能的 Julia 库。特征归一化函数由 MLDataUtils 包提供,允许用户推导特征的均值和标准差,但不能方便地应用于训练/测试数据集以进行特征变换。由于特征的均值和标准差可以在 Julia 中轻松计算,我们可以手动实现标准缩放过程。
以下代码创建了 X_train 和 X_test 的副本,并在循环中计算每个特征的均值和标准差。
标准化特征 — Julia 实现
变换后的特征和原始特征如下所示。

缩放后的特征与原始特征 — Julia 实现(图像作者提供)
在 Python 中,sklearn 提供了多种特征缩放选项,包括归一化和标准化。通过声明一个特征缩放器,可以用两行代码完成缩放。以下代码展示了如何使用一个RobustScaler的示例。

对特征进行鲁棒缩放 — Python 实现(图像作者提供)
过采样(通过 PyCall)
欺诈检测数据集通常严重不平衡。例如,我们的数据集中负样本与正样本的比例超过了 500:1。由于无法获取更多的数据点,欠采样将导致多数类数据点的大量丢失,在这种情况下,过采样成为最佳选择。在这里,我使用了流行的 SMOTE 方法来为正类创建合成样本。
目前,没有可用的 Julia 库提供 SMOTE 的实现。ClassImbalance 包已经有两年未维护,无法与最近的 Julia 版本兼容。幸运的是,Julia 允许我们使用一个名为PyCall的包装库来调用现成的 Python 包。
要将 Python 库导入 Julia,我们需要安装 PyCall 并将 PYTHONPATH 指定为环境变量。我尝试创建一个 Python 虚拟环境,但没有成功。由于某种原因,Julia 无法识别虚拟环境的 Python 路径。因此,我不得不指定系统默认的 Python 路径。之后,我们可以导入由imbalanced-learn库提供的 Python 实现的 SMOTE。PyCall 提供的 pyimport 函数可以用于在 Julia 中导入 Python 库。以下代码展示了如何在 Julia 内核中激活 PyCall 并寻求 Python 的帮助。
使用 SMOTE 进行上采样训练数据 — Julia 实现
相应的 Python 实现如下。我们可以看到,fit_resample 函数在 Julia 中的用法是相同的。

使用 SMOTE 进行上采样训练数据 — Python 实现(图片由作者提供)
模型训练
现在我们进入模型训练阶段。我们将训练一个二分类器,这可以通过多种机器学习算法完成,包括逻辑回归、决策树和神经网络。目前,Julia 中的机器学习资源分布在多个 Julia 库中。让我列出几个最受欢迎的选项及其专业化的模型。
-
MLJ:传统机器学习算法
-
ScikitLearn:传统机器学习算法
-
Mocha:神经网络
-
Flux:神经网络
在这里,我选择了XGBoost,考虑到它的简洁性和在传统回归及分类问题上的卓越表现。训练 XGBoost 模型的过程在 Julia 中与 Python 相同,尽管语法上有一些小的差异。
使用 XGBoost 训练欺诈检测模型 — Julia 实现
相应的 Python 实现如下。

使用 XGBoost 训练欺诈检测模型 — Python 实现(图片由作者提供)
模型评估
最后,让我们通过查看在测试数据上获得的精度、召回率,以及训练模型所花费的时间来评估模型的表现。在 Julia 中,精度、召回率指标可以通过EvalMetrics库计算。另一个可以实现相同目的的包是MLJBase。
进行预测和计算指标 — Julia 实现
在 Python 中,我们可以使用 sklearn 来计算指标。

进行预测和计算指标 — Python 实现(图片由作者提供)
那么 Julia 和 Python 哪个是赢家?为了公平比较,两种模型都使用默认超参数进行训练,学习率 = 0.1,估算器数量 = 1000。性能指标总结在下面的表格中。
可以观察到,Julia 模型在训练时间稍长的情况下,达到了更好的精确度和召回率。由于用于训练 Python 模型的 XGBoost 库底层是用 C++ 编写的,而 Julia XGBoost 库完全用 Julia 编写,Julia 的运行速度确实与 C++ 一样快,就像它所声称的那样!
用于上述测试的硬件:第 11 代 Intel® Core™ i7–1165G7 @ 2.80GHz — 4 核心。
Jupyter notebook 可以在 Github 找到。
收获
我想以对不同数据科学任务的 Julia 库的总结结束这一系列内容。
由于缺乏社区支持,目前 Julia 的可用性无法与 Python 相比。然而,鉴于其优越的性能,Julia 仍然在未来具有很大的潜力。
参考文献
-
布鲁塞尔自由大学机器学习小组 (Université Libre de Bruxelles)。 (无日期)。信用卡欺诈检测 [数据集]。Hi i (数据库内容许可证 (DbCL))
-
Akshay Gupta. 2021 年 5 月 13 日。使用 Julia 开始机器学习:顶级 Julia 机器学习库。
www.analyticsvidhya.com/blog/2021/05/top-julia-machine-learning-libraries/
从 RAG 到财富
向量搜索在深入理解您的数据和模型中的 10 种应用
·
关注 发表在 Towards Data Science ·10 min read·2023 年 10 月 25 日
--
向量搜索用于数据探索的艺术效果图。图像由 DALLE-3 生成。
随着大型语言模型(LLMs)席卷全球,向量搜索引擎也随之而来。向量数据库构成了 LLMs 长期记忆系统的基础。
通过高效地查找相关信息并将其作为上下文输入到语言模型中,向量搜索引擎可以提供超出训练截止日期的最新信息,并在不进行微调的情况下提高模型输出的质量。这个过程,通常称为检索增强生成(RAG),将曾经晦涩的近似最近邻(ANN)搜索算法挑战推向了聚光灯下!
在所有的喧嚣中,人们可能会误以为向量搜索引擎与大型语言模型不可分割。但事实远不止于此。向量搜索有许多强大的应用,远远超出了改善 RAG 对 LLMs 的应用!
在这篇文章中,我将展示我最喜欢的十种向量搜索的应用,用于数据理解、数据探索、模型解释等。
以下是我们将涵盖的应用,按复杂度大致递增的顺序:
-
图像相似性搜索
-
反向图像搜索
-
对象相似性搜索
-
强健的 OCR 文档搜索
-
语义搜索
-
跨模态检索
-
探测感知相似性
-
比较模型表示
-
概念插值
-
概念空间遍历
图像相似性搜索

对来自 Oxford-IIIT Pet 数据集的图像进行相似性搜索 (LICENSE). 图片由作者提供。
也许最简单的起点是图像相似性搜索。在这个任务中,你有一个由图像组成的数据集——这可以是个人照片集,也可以是数以亿计的图像仓库,这些图像由数千个分布式相机拍摄,历时多年。
设置很简单:计算数据集中每张图像的嵌入向量,并从这些嵌入向量生成一个向量索引。在这初始批次计算之后,不需要进一步的推断。探索数据集结构的一个好方法是选择数据集中的一张图像,并查询向量索引获取 k 个最近邻——最相似的图像。这可以直观地了解查询图像周围图像空间的密度。
欲了解更多信息和示例代码,请见 这里。
反向图像搜索

对来自 Unsplash 的图像进行反向图像搜索 (图片由 Mladen Šćekić 提供),与* Oxford-IIIT Pet 数据集进行对比。图片由作者提供。
类似地,图像相似性搜索的自然延伸是找到数据集中与外部图像最相似的图像。这可以是来自本地文件系统的图像,也可以是来自互联网的图像!
要执行反向图像搜索,你需要像图像相似性搜索示例那样创建数据集的向量索引。不同之处在于运行时,你计算查询图像的嵌入向量,然后用这个向量查询向量数据库。
欲了解更多信息和示例代码,请参见 这里。
对象相似性搜索

在 COCO-2017 数据集的验证分割中进行羊的对象相似性搜索 (LICENSE)。图片由作者提供。
如果你想深入研究图像中的内容,那么对象或“补丁”相似性搜索可能正是你所追求的。例如,人物再识别就是其中之一,在这种情况下,你有一张包含目标人物的单一图像,你想在数据集中找到所有该人的实例。
人物可能只占据每张图像的小部分,因此这些图像中包含的整个图像的嵌入可能会强烈依赖于这些图像中的其他内容。例如,图像中可能有多个人。
更好的解决方案是将每个对象检测补丁视为一个独立的实体,并为每个补丁计算嵌入。然后,使用这些补丁嵌入创建一个向量索引,并针对你想要重新识别的人的补丁进行相似性搜索。作为起点,你可能想尝试使用 ResNet 模型。
这里有两个细微之处:
-
在向量索引中,你需要存储元数据,将每个补丁映射回数据集中的相应图像。
-
你需要运行一个对象检测模型来生成这些检测补丁,然后再实例化索引。你可能还想仅为某些对象类别(如
person)计算补丁嵌入,而不是其他类别——chair、table等。
欲了解更多信息和示例代码,请参见 这里。
强大的 OCR 文档搜索

通过 Tesseract OCR 引擎生成的博士论文页面上的文本块进行模糊/语义搜索。使用 GTE-base 模型计算的嵌入。图片由作者提供。
光学字符识别(OCR)是一种允许你数字化文档的技术,如手写笔记、旧期刊文章、医疗记录以及那些藏在你衣柜里的情书。像 Tesseract 和 PaddleOCR 这样的 OCR 引擎通过识别图像中的单个字符和符号并创建连续的“文本块”来工作——想象成段落。
一旦你有了这些文本,你可以对预测的文本块进行传统的自然语言关键词搜索,如这里所示。然而,这种搜索方法容易受到单字符错误的影响。如果 OCR 引擎错误地将“l”识别为“1”,那么对“control”的关键词搜索将失败(这真是有趣!)。
我们可以通过使用向量搜索来克服这个挑战!使用像GTE-base这样的文本嵌入模型将文本块嵌入,并创建一个向量索引。然后,我们可以通过嵌入搜索文本并查询该索引,对我们的数字化文档进行模糊和/或语义搜索。从高层次来看,这些文档中的文本块类似于目标相似性搜索中的目标检测补丁!
欲了解更多信息和示例代码,请见这里。
语义搜索

使用自然语言在 COCO 2017 验证数据集上进行语义图像搜索。图像由作者提供。
使用多模态模型,我们可以将语义搜索的概念从文本扩展到图像。像CLIP、OpenCLIP和MetaCLIP这样的模型被训练以找到图像及其标题的共同表示,因此一张狗的图像的嵌入向量将与“狗的照片”这一文本提示的嵌入向量非常相似。
这意味着创建一个由 CLIP 嵌入生成的图像向量索引,并使用该向量数据库进行向量搜索是合理的(即我们“被允许”这样做),其中查询向量是文本提示的 CLIP 嵌入。
💡通过将视频中的每一帧视作图像,并将每帧的嵌入添加到向量索引中,你还可以对视频进行语义搜索!
欲了解更多信息和示例代码,请见这里。
跨模态检索
跨模态检索与输入音频文件匹配的图像。使用 ImageBind 和 Qdrant 向量索引实现,基于 COCO 2017 验证数据集。视频由作者提供。
从某种意义上说,对图像数据集进行语义搜索是一种跨模态检索。一个概念化的方法是我们正在检索与文本查询对应的图像。使用像ImageBind这样的模型,我们可以更进一步!
ImageBind 将来自六种不同模态的数据嵌入到同一嵌入空间中:图像、文本、音频、深度、热成像和惯性测量单元(IMU)。这意味着我们可以为任何这些模态的数据生成一个向量索引,并用这些模态中的任何其他样本来查询该索引。例如,我们可以用一段汽车鸣笛的音频片段来检索所有汽车的图像!
欲了解更多信息和工作代码,请查看这里。
探测感知相似性
在向量搜索故事中,我们迄今为止只略微提及了一个非常重要的部分,那就是模型。我们向量索引中的元素是来自模型的嵌入。这些嵌入可以是定制嵌入模型的最终输出,也可以是从另一个任务(如分类)训练的模型中提取的隐藏或潜在表示。
无论如何,我们用来嵌入样本的模型可以对哪些样本被认为是最相似的样本产生重大影响。CLIP 模型捕捉语义概念,但在表示图像中的结构信息方面存在困难。另一方面,ResNet 模型非常擅长表示结构和布局的相似性,操作在像素和补丁的层面上。然后,还有像DreamSim这样的嵌入模型,旨在弥合差距,捕捉中级相似性 — 将模型的相似性概念与人类的感知对齐。
向量搜索为我们提供了一种探测模型如何“看待”世界的方法。通过为每个我们感兴趣的模型(在相同数据上)创建一个单独的向量索引,我们可以迅速培养对不同模型如何在背后表示数据的直觉。
下面是一个示例,展示了在 NIGHTS 数据集中,使用 CLIP、ResNet 和 DreamSim 模型嵌入进行相似性搜索的情况:

使用 ResNet50 嵌入进行图像相似性搜索,在NIGHTS 数据集中(图像由 Stable Diffusion 生成 — MIT RAIL LICENSE)。ResNet 模型在像素和补丁的层面上操作。因此,检索到的图像在结构上类似于查询图像,但在语义上不一定相似。

使用 CLIP 嵌入进行相似性搜索,基于相同的查询图像。CLIP 模型尊重图像的基本语义,但不关注其布局。

使用 DreamSim 嵌入进行相似性搜索,基于相同的查询图像。DreamSim 弥合了语义和结构特征之间的差距,寻找最佳的中级相似性折衷。
欲了解更多信息和工作代码,请查看这里。
比较模型表示

对比 ResNet50 和 CLIP 模型在 NIGHTS 数据集上的表示。ResNet 嵌入通过 UMAP 降维到 2D。选择嵌入图中的一个点并突出显示附近的样本,我们可以看到 ResNet 如何捕捉组成和色彩相似性,而不是语义相似性。对选定样本使用 CLIP 嵌入进行向量搜索,我们可以看到根据 CLIP 的结果,大多数样本在 ResNet 中并没有局部化。
我们可以通过结合向量搜索和降维技术,如均匀流形逼近 (UMAP),获得对两个模型差异的新见解。操作方法如下:
每个模型的嵌入包含了模型如何表示数据的信息。使用 UMAP(或 t-SNE 或 PCA),我们可以生成来自 model1 的低维(2D 或 3D)表示。这样做虽然牺牲了一些细节,但希望保留了样本之间的相似性信息。我们获得的是可视化这些数据的能力。
以 model1 的嵌入可视化为背景,我们可以在这个图中选择一个点,并对该样本执行一个针对 model2 嵌入的向量搜索查询。然后你可以看到检索到的点在 2D 可视化中的位置!
上述示例使用了与上一节相同的 NIGHTS 数据集,视觉化 ResNet 嵌入,捕捉更多的组成和结构相似性,并用 CLIP(语义)嵌入进行相似性搜索。
概念插值

使用 CLIP 嵌入在 Oxford-IIIT 宠物数据集上进行“哈士奇”和“吉娃娃”概念的插值
我们接近这十个应用的尾声,但幸运的是,我把一些最佳的留到了最后。到目前为止,我们处理的唯一向量是嵌入—向量索引中填充了嵌入,查询向量也是嵌入。但有时在嵌入的空间中还有额外的结构,我们可以利用这些结构以更动态的方式与数据互动。
这种动态交互的一个例子是我喜欢称之为“概念插值”。操作方法如下:拿一个图像数据集,使用多模态模型(文本和图像)生成一个向量索引。选择两个文本提示词,比如“晴天”和“雨天”,这两个词代表了不同的概念,并设置一个在 [0,1] 范围内的值 alpha。我们可以为每个文本概念生成嵌入向量,并将这些向量按照 alpha 指定的线性组合方式相加。然后我们对这个向量进行归一化,并将其作为查询向量用于我们的图像嵌入向量索引。
因为我们在对两个文本提示(概念)的嵌入向量进行线性插值,从很大程度上说,我们在插值这些概念本身!我们可以动态改变alpha并在每次交互时查询我们的向量数据库。
💡这种概念插值的想法是实验性的(即:并不总是明确的操作)。我发现当文本提示在概念上相关且数据集足够多样化,以便在插值范围内产生不同结果时,它效果最佳。
欲了解更多信息和示例代码,请参见 这里。
概念空间遍历

通过其嵌入在“概念”空间中移动,以各种文本提示的方向进行遍历,示例为 COCO 2017 数据集的测试拆分。图像和文本由 CLIP 模型嵌入。图像由作者提供。
最后但同样重要的是,我喜欢称之为“概念空间遍历”。与概念插值类似,首先用像 CLIP 这样的多模态模型生成图像的嵌入。接着,从数据集中选择一张图像。这张图像将作为你的起点,你将“遍历”概念空间。
从那里,你可以通过提供一个文本字符串作为概念的替代来定义你想要移动的方向。设置你想在该方向上迈出的“步长”,并将该文本字符串的嵌入向量(带有乘法系数)加到初始图像的嵌入向量上。将使用“目标”向量来查询向量数据库。你可以添加任意数量的概念,并实时查看检索到的图像集的更新。
与“概念插值”一样,这并不总是一个严格定义的过程。然而,我发现它很迷人,当应用于文本嵌入的系数足够高时,它表现得相当好。
欲了解更多信息和示例代码,请参见 这里。
结论
向量搜索引擎是极其强大的工具。当然,它们是RAG时代最佳演出的明星。但向量数据库远比这更为多功能。它们使我们对数据有更深刻的理解,提供有关模型如何表示这些数据的见解,并为我们与数据互动提供了新的途径。
向量数据库并不局限于 LLMs。只要涉及到嵌入,向量数据库就会显得非常有用,而嵌入恰好位于模型和数据的交汇处。我们对嵌入空间结构的理解越深入,我们的向量搜索驱动的数据和模型交互就会变得越动态和普遍。
如果你觉得这篇文章有趣,你可能还想看看这些由向量搜索驱动的文章:
-
🔗 如何将我的公司文档转化为可搜索的数据库与 OpenAI
-
🔗 如何将 ChatGPT 转变为类似 SQL 的图像和视频数据集翻译器
-
🔗 我在推动 Prompt Engineering 极限中学到的东西
从 SQL 到 Julia:数据科学的其他编程语言
·
关注 发表在 Towards Data Science · 发送为 新闻通讯 · 3 分钟阅读 · 2023 年 7 月 27 日
--
Python 可能是数据科学家和机器学习从业者的主流编程语言,但成为编程多语种高手的好处越来越明显。数据团队所做的项目跨越多个平台和架构,能够切换到不那么常见但有时更高效的语言,可以成为一个强大的优势。
本周的重点文章涵盖了广泛的内容:它们关注多种语言、项目和工作流程。这些帖子邀请你卷起袖子,开始尝试一些代码——它们可能会激发你进一步扩展知识。
-
数据库查询语言 SQL 并不陌生;它已经存在了几十年,并且仍然是许多数据工作者进入编码的常见途径。伊法特·马利克·戈尔 对窗口函数的概述是SQL 词汇的核心元素的极佳介绍(或复习)。
-
如果你的工作涉及大量统计数据,你可能已经在实际工作中遇到过 R。尽管 R 的使用场景可能比你想象的更为广泛,珍娜·伊格尔森 通过一个有用的实操教程,展示了如何使用 R来解决人力分析中的常见挑战。
-
Python 库在数据可视化领域占据主导地位,以至于你可能会忘记其他语言也能创建精美的图表。马哈茂德·哈穆赫 对Rust 的可视化选项进行了深入探讨,重点关注 Plotters 库及其强大的功能。

-
无论你使用什么编程语言,准备数据以便于分析或建模项目都是至关重要的步骤。艾玛·布德罗 带我们了解了在 Julia 中进行数据筛选的过程——一种在近年来数据科学家中爆炸性增长的相对小众语言。
-
版本控制是任何软件开发过程中的重要组成部分,而 Git 尽管不是编程语言本身,却是处理和协作代码的首选系统。如果您还处于编码旅程的早期阶段,阅读 Khuyen Tran 的 数据科学家的全面 Git 使用指南 是一个明智的下一步。
为了拓宽您的视野,我们很高兴分享我们近期的几篇亮点文章:
-
初创企业生态系统如何应对 AI 快速增长的影响?Viggy Balagopalakrishnan 提供了基于最新 Y Combinator 批次的 敏锐洞察。
-
Lambert T Leong 博士 展示了 在医疗保健与深度学习交汇处的最新研究,旨在基于体成分成像预测全因死亡率。
-
如果您听说过 Code Interpreter 这个 ChatGPT 插件,且 想看看所有的热议内容,Natassha Selvaraj 的项目演示是必看的。
-
如需了解 大数法则的清晰、透彻且引人入胜的解释 及其工作原理,不要错过 Sachin Date 的新深度解析。
-
如果每天有数十亿人使用生成 AI 工具,会带来 什么样的环境影响?Kasper Groes Albin Ludvigsen 探讨了一个棘手、及时且重要的问题。
感谢您支持我们的作者!如果您喜欢在 TDS 阅读的文章,可以考虑 成为 Medium 会员 —— 这将解锁我们的所有档案(以及 Medium 上的其他所有文章)。
我们希望许多人也在 计划参加 Medium Day,在 8 月 12 日庆祝社区及其使之特别的故事——注册(免费)现已开放。
直到下次 Variable,
TDS 编辑部
从感知机到 Adaline
奠定基础
·
关注 发表在 Towards Data Science ·11 min read·Nov 28, 2023
--
照片由 Einar Storsul 提供,来源于 Unsplash
介绍
在上一篇文章中,我尝试解释了可能存在的最基本的二元分类器,Rosenblatt 的感知机。理解这个算法具有教育价值,可以作为初级机器学习课程的良好介绍。这是一个可以在一个下午从零开始编写的算法,并且能够激发兴趣、成就感和深入研究更复杂主题的动力。然而,作为一个算法,它仍然有很多不足,因为只有在类是线性可分的情况下才能保证收敛,这种情况往往不会发生。
在本文中,我们将继续掌握分类概念的旅程。从 Rosenblatt 的感知机自然演变而来的就是自适应 线性 神经分类器,俗称 adaline。从感知机到 adaline 的过渡并不是一个大的飞跃。我们只需将阶跃激活函数更改为线性激活函数。这一小的变化导致了一个连续的损失函数,可以被稳健地最小化。这使我们能够引入机器学习中的许多有用概念,如向量化和优化方法。
在未来的文章中,我们还将讨论激活函数和损失函数的进一步细微变化,这将使我们从 adaline 过渡到逻辑回归,这已经是日常实践中的有用算法。上述所有算法本质上都是单层神经网络,并且可以很容易地扩展到多层神经网络。在这方面,本文将读者带到了这次进化的下一步,并为处理更高级的概念奠定基础。
我们需要一些公式。我使用了在线的LaTeX 方程编辑器来开发方程的 LaTeX 代码,然后使用 Chrome 插件数学方程随处可用将方程渲染成图片。这种方法的唯一缺点是 LaTeX 代码不会被存储,以备将来需要再次渲染时使用。为此,我在本文末尾提供了方程列表。如果你对 LaTex 不熟悉,这可能有其自身的教育价值。正确的符号表示是机器学习旅程的一部分。
自适应线性神经分类器(adaline)
那么 adaline 算法是什么呢?Adaline 是一个像感知机一样的二元分类器。通过使用一组特征[x₁, .. , xₘ]的输入值来进行预测,其中 m 是特征的数量。输入值与权重[w₁, .. , wₘ]相乘,并加上偏置,以得到净输入 z = w₁x₁ + .. + wₘxₘ + b。净输入通过线性激活函数σ(z),然后使用阶跃函数进行预测,与感知机类似:

与感知器的一个关键区别在于,线性激活函数用于学习权重,而阶跃函数仅用于最终的预测。这听起来是件小事,但它具有重要意义。线性激活函数是可微分的,而阶跃函数则不是!上面的阈值 0.5 并非一成不变。通过调整阈值,我们可以根据我们的使用案例调整精确度和召回率,即根据假阳性和假阴性的成本。
在 adaline 的情况下,线性激活函数就是恒等函数,即 σ(z) = z。需要在训练过程中最小化的目标函数(也称为损失函数)是

其中 w 是权重。

并且 b 是偏置。求和是在训练集中的所有示例上进行的。在一些实现中,损失函数还包括 1/2 系数以便于计算。这个系数在我们对损失函数关于权重和偏置的梯度进行求导时会被抵消,并且正如我们下面将看到的,它除了将学习率缩放两倍外没有其他影响。在本文中,我们不使用 1/2 系数。
对于每个示例,我们计算计算结果与真实类标签之间的平方差。

以及真实类标签。注意,输入向量被理解为形状为 (1, m) 的矩阵,即正如我们稍后将看到的,是我们的特征矩阵x的一行,其形状为 (n, m)。
训练不过是一个优化问题。我们需要调整权重和偏置,使损失函数最小化。与任何最小化问题一样,我们需要计算目标函数对独立变量的梯度,在我们的情况下,这些变量将是权重和偏置。损失函数关于权重 wⱼ 的偏导数是

最后一行引入了重要的矩阵符号。特征矩阵 x 的形状为 (n, m),我们对其第 j 列取转置,即形状为 (1, n) 的矩阵。真实类标签 y 是一个形状为 (n, 1) 的矩阵。所有样本的净输出 z 也是一个形状为 (n, 1) 的矩阵,激活应用于其每个元素后不会改变。上述公式的最终结果是一个标量。你能猜出我们如何使用矩阵符号表示所有权重的梯度吗?

特征矩阵的转置形状为 (m, n)。此操作的最终结果是一个形状为 (m, 1) 的矩阵。这种表示法非常重要。我们将使用 numpy 进行这种矩阵乘法,而不是使用循环。在神经网络和 GPU 时代,应用向量化的能力至关重要!
那么损失函数关于偏差的梯度如何呢?

其中横线表示向量的均值。再次强调,使用 numpy 计算均值是一个向量化操作,即求和不需要使用循环实现。
一旦我们得到梯度,就可以使用梯度下降优化方法来最小化损失。权重和偏差项会使用以下公式迭代更新

其中 η 是合适选择的学习率。值过小可能会延迟收敛,而值过高则可能完全阻止收敛。需要进行一些实验,这通常是机器学习算法参数调整的一部分。
在上述实现中,我们假设权重和偏差是基于所有样本一起更新的。这被称为全批量梯度下降,是一种极端情况。另一种极端情况是每处理一个训练样本后更新权重和偏差,这被称为随机梯度下降(SGD)。实际上,还有一些中间情况,称为小批量梯度下降,其中权重和偏差是基于样本的子集进行更新的。通常,这种方法收敛速度更快,即我们不需要对整个训练集进行过多的迭代,同时向量化仍然(至少部分地)是可能的。如果训练集非常大(或者模型非常复杂,如现在 NLP 中的变换器),全批量梯度下降可能根本不可行。
替代的公式化和闭式解
在我们继续用 Python 实现 adaline 之前,我们将稍作岔开。我们可以将偏差 b 吸收到权重向量中,如下所示

在这种情况下,训练集中所有样本的净输出变为

这意味着特征矩阵前面加了一列全是 1,从而导致形状为 (n, m+1)。关于组合权重集的梯度变为

原则上,我们可以推导出一个闭式解,因为在最小值时所有梯度都将为零

实际上,上述方程中的矩阵的逆可能不存在,可能是因为奇异性,或者无法足够准确地计算。因此,这种封闭形式的解决方案在实践中不被使用,无论是在机器学习中还是在数值方法中。然而,了解 adeline 类似于线性回归,并且具有封闭形式的解决方案仍然很有用。
在 Python 中实现 adeline
我们的实现将使用小批量梯度下降。然而,该实现具有灵活性,可以使用随机梯度下降和全批量梯度下降这两个极端来优化损失函数。我们将通过改变批量大小来检查收敛行为。
我们使用一个类来实现 adeline,该类在通常的scikit-learn API 样式中暴露了 fit 和 predict 函数。
在初始化时,adeline 分类器设置了小批量梯度下降的批量大小。如果批量大小设置为 None,则使用整个训练集(全批量梯度下降);否则,训练集将分批使用(小批量梯度下降)。如果批量大小为 1,我们实际上回到了随机梯度下降。在每次训练集遍历之前,训练集会被打乱,以避免重复周期,但这仅在使用小批量梯度下降时才会产生效果。算法的核心在于_update_weights_bias函数,该函数完成对训练集的完整遍历,并返回相应的损失。此函数应用了梯度下降法,使用先前章节中的导数计算的梯度。注意使用 numpy 的matmul和dot函数,避免了显式循环。如果批量大小设置为 None,则完全没有循环,且实现完全矢量化。
实践中使用 adeline
我们进行必要的导入,并创建一个合成数据集,如之前感知器的文章中所示。
这产生了

合成数据集中两个类别的散点图。作者提供的图像。
与早期文章的唯一区别是我们调整了高斯均值和协方差,使得类别不是线性可分的,因为我们希望 adeline 能够克服这一点。此外,两个独立变量故意具有不同的尺度,以讨论特征缩放的重要性。
让我们尝试拟合第一个模型并可视化收敛情况。在拟合之前,我们对特征进行归一化,使其均值为零且标准差为单位。
这产生了收敛图。

Adaline 收敛。作者提供的图像。
Adaline 缓慢收敛,但损失函数不会变为零。为了验证训练的成功,我们使用与早期文章相同的方法可视化决策边界。
产生

拟合的 Adaline 模型的决策边界。图片由作者提供。
由于训练集中的两个类别不可线性分开且我们使用了线性决策边界,因此存在一些分类错误的点。尽管如此,算法还是良好地收敛了。该解决方案是确定性的。通过足够多的训练集轮次,我们可以获得数值上相等的权重和偏置,无论它们的初始值如何。
迷你批量与全批量梯度下降
上述数值实验使用了全批量梯度下降,这部分解释了缓慢收敛的原因。我们将使用与之前相同的数据集和随机状态,但这次我们将使用不同的批量大小来拟合 Adaline 分类器,范围从 20 到 400,即我们训练集中的样本数量。
产生

批量大小对收敛的影响(使用 0.001 学习率)。图片由作者提供。
我们可以清楚地看到,批量大小越小,收敛越快,但也会有一些震荡。这些震荡可能会在较大的学习率下使收敛不稳定。如果将学习率加倍到 0.002,这一点就会变得明显。

批量大小对收敛的影响(使用 0.002 学习率)。图片由作者提供。
进一步增加学习率最终会阻止较小批量大小的收敛。即使使用更大的学习率,即使是全批量梯度下降也会因为超越全局最小值而无法收敛。
结论
Adaline 相比于感知机有了显著的改进。权重和偏置通过最小化一个连续的损失函数来获得,该函数是凸的(因此没有局部极小值)。使用足够小的学习率,即使类别不能线性分开,算法也会收敛。当使用梯度下降及其任何变体时,特征的缩放会影响收敛速度。在这篇文章中,我们使用了简单的标准化,将每个特征的均值调整为零,同时将方差调整为单位方差。通过这种方式,可以选择一个对所有权重和偏置都适用的学习率,这意味着可以在更少的轮次中获得全局最小值。
在深入研究更复杂的主题,如支持向量机和多层神经网络之前,理解如何利用向量化构建二分类器是关键。在日常实践中,可以使用 scikit-learn,它提供了先进的分类算法,允许非线性决策边界,并支持高效和系统的超参数调整及交叉验证。然而,从零开始构建简单的二分类器能够提供深入的理解、增强自信并带来掌控感。虽然从头开始构建所有内容显然是不现实的,但深入理解简单算法能提供必要的技能和洞察力,使得从现成库中包含的更高级算法显得不那么神秘。
文章中使用的方程的 LaTeX 代码
文章中使用的方程可以在下面的 gist 中找到,以便你可以再次渲染它们。
MLE、MAP 和贝叶斯推断的全面解释
原文:
towardsdatascience.com/full-explanation-of-mle-map-and-bayesian-inference-1db9a7fb1d2b
介绍最大似然估计、最大后验估计和贝叶斯推断
·发布于 Towards Data Science ·阅读时间 13 分钟·2023 年 3 月 6 日
--

在这篇文章中,我们将介绍 MLE(最大似然估计)、MAP(最大后验估计) 和 贝叶斯推断——这些概念对统计学、数据科学和机器学习等领域至关重要。我们将使用一个不公平的硬币抛掷的例子来解释每种方法,分析并数字推导(对于贝叶斯推断),并展示它们之间的差异。
我们将了解到 MLE 最大化了似然——即选择最大化观察数据似然的参数。MAP 添加了先验,引入了对参数的先验知识——从而弥合了从纯粹的频率派概念到贝叶斯概念的差距 (link). 贝叶斯推断最终给我们提供了最完整的信息,但也是最难执行的:它涉及建模给定数据的参数的完整后验分布——而之前的方法只是给出点估计。
让我们通过正式化这些描述来设定背景:本质上,对于任何类型的学习问题,我们都希望找到一个模型/参数,以尽可能好地描述观察到的数据。我们可以通过找到从观察数据到参数的映射/分布来完全描述和解决这个问题:

通常,这些术语被称为:

给定数据的参数的条件分布称为后验分布——这是我们关注的术语。似然函数是给定特定参数值的数据的条件分布。这——正如其名称所示——描述了在给定选择的参数值下观察到数据的可能性。先验分布描述了我们对参数的信念——即我们在观察到数据之前对它们的预期。最后,证据是一个归一化常数,使得后验分布成为一个“真实”的概率分布。它完全描述了给定的数据,并且通常很难计算或甚至不可计算——这是贝叶斯推断常常困难的主要原因之一。
利用这些见解和公式/符号,我们将介绍最大似然估计(MLE)、最大后验估计(MAP)和贝叶斯推断。但首先,让我们介绍一个我们将在整个演示中使用的问题。
玩具问题:估计不公平硬币的参数
为了展示这些概念,我们将使用抛掷不公平硬币的例子:假设,我们给定一枚硬币,其正面出现的概率为θ,反面出现的概率为1 — θ。θ对我们来说是未知的,我们希望在实验后估计它。也就是说,我们假设我们拥有或收集了一个抛币的数据集,然后希望使用 MLE、MAP 和贝叶斯推断来为θ找到一个估计值。
在继续之前,让我们描述一个我们在所有后续部分中需要的量:似然函数。抛掷硬币遵循伯努利分布。假设我们抛掷硬币N次,则用Nₕ表示正面的次数,用Nₜ表示反面的次数。
对于此(以及接下来的内容)的一个核心假设是独立性:我们假设,所有的抛币结果是相互独立且同分布的(i.i.d)——这使我们能够将似然函数表示为各个独立似然函数的乘积。特别地,我们得到:

代入伯努利分布得出:

在这里,我们用一个值为 1 的二元变量表示单次事件(抛硬币)的正面,值为 0 表示反面。
由于对数函数是单调的,并且处理求和往往更为简便,我们应用这一点并重新公式化:

数据集
在继续之前,让我们定义一个我们通过抛掷这枚不公平硬币观察到的样本数据集——我们将在接下来的部分中使用它来展示不同方法的结果。
我们将假设我们抛掷了一枚不公平的硬币,真实的θ = 0.3进行了 100 次实验,并观察到 36 次正面和 64 次反面。
最大似然估计(MLE)
现在,让我们来看看我们的第一个感兴趣的概念:MLE。在这里,我们要找到使观察数据的似然最大化的参数集。为此,我们形成似然函数,对参数求导并解根。注意,即使你以前可能不知道这个名称,你很可能已经应用过这个概念——这实际上是大多数机器学习算法的核心。想象一个神经网络来解决回归或分类问题:L2 损失或交叉熵可以被解释为似然,而梯度下降则找到最优解。
因此——让我们取上述似然公式,并找到使其最大化的θ——即求导并将其设为 0:

解 0 和一些较小的重构给出了θ的 MLE 估计:

这也应该从直观上讲得通:参数θ,即硬币正面朝上的概率,在没有其他先验信息或约束的情况下,应该等于观察到的正面结果与总投掷次数的比例。
让我们将此应用于上述介绍的数据集。我们得到:

最大后验估计(MAP)
MLE 代表最大化似然——找到解释数据最好的参数值集合,而无需任何先验信息或进一步的约束。现在,我们转向 MAP——这涉及引入一个参数的先验——实际上等同于寻找后验分布的最大值。
MLE 和 MAP 是频率派与贝叶斯概念之间讨论的良好示例。频率派认为概率只是观察随机、可重复事件的结果——而贝叶斯观点则将一切,包括参数,建模为随机变量——这些变量可以根据新证据进行更新。虽然贝叶斯概念(在我看来)通常提供更强大的解决方案,但一个核心的频率派批评是其对先验的需求——这些先验必须来自某处,并且可能是错误的。
进一步说明,将正则化添加到机器学习模型中实际上可以被证明等同于最大后验估计(MAP)(除了上述声明,即没有正则化的模型代表最大似然估计(MLE)概念)。但这一有趣的认识在这里过于深入,我们将专门在未来的文章中讨论。
让我们重新审视引言公式:

由于p(x)是一个常数,并且独立于θ,在对θ求导并解 0 时,它会消失。因此,MAP 确实给出了最大化后验分布的θ点估计。我们需要的只是一个先验。我们选择一个贝塔分布,其给定θ的概率密度函数由以下特征描述:

我们选择 beta 分布的原因将在下一节讨论(提示:共轭先验)。
为简化起见,我们再次应用对数运算,得到以下问题:

因此,在形成导数并解出 0 时,我们可以分别考虑每个加数——左侧的我们已经在上面计算过:

现在让我们来看右侧的图像:

形成导数得到:

我们现在可以将这两个项结合起来,并解出 0:

一些重构返回 θ 的映射估计:

要应用这个公式,我们首先必须制定我们的先验。假设我们模糊地听说(例如来自硬币制造商)真实的正面概率应该在 0.3 左右,但我们并不确定。
为了建模这个信念,让我们选择一个 α = 4 和 β = 10 的 beta 分布。我们可以通过以下方式绘制这个分布:
import matplotlib.pyplot as plt
from scipy.stats import beta
x = np.linspace(0, 1, 100)
plt.plot(x, beta.pdf(x, 4, 10))
plt.show()
得到以下图形:

作者生成的图像
使用这个先验,我们得到:

贝叶斯推断
现在,让我们来进行完全的贝叶斯推断,求解完整的后验分布,而不是参数的点估计:也就是说,我们将获得一个以观察数据为条件的参数的概率分布。
首先我们来检查分子:

插入之前介绍的分布和结果,我们得到:


这引出了之前提到的共轭先验的概念:如果结果分布与先验分布属于同一家族,那么这个先验被称为对似然的共轭先验——这也意味着后验分布也属于同一家族,我们稍后将看到。
现在让我们来看看证据,即分母。这通常是棘手的部分,且往往是不可解的,因为它涉及到对所有可能的参数值进行边际化:

这个积分通常很难计算,甚至不可解——这是(精确)贝叶斯推断困难或不可解的主要原因之一。然而,正如我们稍后将看到的,通常我们不会尝试解析解,而是 resort to 各种近似技术,这些技术也能产生令人满意的结果。
在我们的案例中,虽然存在一个解析的闭式解。观察积分,我们发现其内部与上述计算是相同的:

根据我们之前巧妙忽略的Beta 函数的定义,我们观察到这实际上是 Beta 函数在Nₕ+α-1,N-Nₕ-1的值:

现在我们可以将分子和分母放在一起,得到后验的闭式解:

让我们看看这个分布的样子,保持我们之前的 beta 先验α = 4和β = 10:

图像由作者生成
利用关于Beta 分布的现有知识,一个遵循 beta 分布的随机变量X的均值由参数α和β给出:

在我们的情况下,这计算结果与 MAP 得到的结果相同,即 0.348。
对于方差,我们得到:

结果讨论
在本节中,我们将讨论并比较前几节中获得的结果。
MLE估计θ,即硬币正面朝上的概率为 0.36——这正是观察到的正面投掷的相对频率(36 / 100)。这很有道理。在没有其他信息、先验知识的情况下……最能解释数据的参数应为那个反映数据的参数——在我们的案例中为 0.36。
我们的MAP估计为 0.348,这更接近真实值 0.3,并且更接近于先验的模式。这里的最后一点就是造成这种情况的原因:由于我们的先验围绕 0.3 中心,且方差相对较小,这在最终结果中得到了反映。
为了观察这种效果,考虑一个方差更大的先验,例如由α = 2和β = 3给出:

图像由作者生成
在这种情况下,我们的 MAP 估计变为 0.359——这更接近 MLE 值,因为它受到先验的影响较小。
贝叶斯推断返回了完整的后验分布。其模式为 0.348,即与 MAP 估计相同。这是预期之中的,因为 MAP 只是后验分布的点估计解。然而,拥有完整的后验分布能让我们对问题有更多的洞察——我们将在接下来的两节中深入探讨。
后验分布的数值近似
对于这个例子,我们成功找到后验的闭式解,从而分析性地解决了贝叶斯推断问题。然而,正如之前所述,这通常很困难甚至不可行。幸运的是,在实际中,我们不需要,也通常不会尝试分析性地解决这个问题——而是 resort to 一些近似方法。
这里有几种可能性,我们使用了使用 MCMC 方法的数值近似。在链接的文章中,我详细介绍了这一点,因此我在此建议参考更多细节。在这里,我们只是简要总结核心概念:MCMC 方法通过定义一个相对简单的马尔可夫链来工作,从中采样的目标分布是其平稳分布。然后我们跟随这个马尔可夫链,生成N个相关的数据样本——由于平稳特性,这等同于从目标分布中采样。
这里我们现在想应用这个原则,特别是 Metropolis-Hastings 算法,来近似我们之前解析得到的后验分布。
我们可以使用以下代码片段来完成:
import matplotlib.pyplot as plt
import numpy as np
import scipy.stats
THETA_TRUE = 0.3 # True probability for landing heads
# Parameters defining the beta prior distribution
ALPHA_PRIOR = 4
BETA_PRIOR = 10
NUM_SAMPLES = 100000 # Number of MCMC steps
# Fake a dataset which equals the one assumed in the previous sections
D = np.asarray([1] * 36 + [0] * 64)
# Define prior distribution
prior = scipy.stats.beta(ALPHA_PRIOR, BETA_PRIOR)
def likelihood_ratio(theta_1, theta_2):
return (theta_1 / theta_2) ** np.sum(D == 1) * (
(1 - theta_1) / (1 - theta_2)
) ** np.sum(D == 0)
def norm_ratio(theta_1, theta_2):
return prior.pdf(theta_1) / prior.pdf(theta_2)
# Step 1
x = np.random.uniform(0, 1)
# Proposal distribution
q = scipy.stats.norm(0, 0.1)
samples = []
for i in range(NUM_SAMPLES):
# Step 2
y = x + q.rvs()
# Step 3
ratio = likelihood_ratio(y, x) * norm_ratio(y, x)
p = min(ratio * q.pdf(x - y) / q.pdf(y - x), 1)
# Step 4
u = np.random.uniform(0, 1)
# Step 5
x = y if u <= p and 0 <= y <= 1 else x
samples.append(x)
# Plot the sampled posterior distribution
plt.hist(samples, density=True, bins=100)
# Plot the posterior distribution obtained by the analytical solution
x_values = np.linspace(0, 1, 100)
plt.plot(x_values, scipy.stats.beta.pdf(x_values, 36 + ALPHA_PRIOR, 100 - 36 + BETA_PRIOR))
plt.show()
最后两行绘制了采样后验分布(直方图)与计算后验(线)——我们观察到预期的重叠:

由作者生成的图像
为什么要做贝叶斯推断?
到目前为止,你可能会问这样一个问题:所有展示的方法都给我相同(或非常相似)的后验分布模式——导致我最终总是选择相同的参数。那么为什么还要做这种复杂的贝叶斯推断呢?
在最后一节中,我们将回答这个问题。我们首先解释这带来了什么好处,然后用一个实际的例子来展示这一点。
首先:做贝叶斯推断给了我们更多的信息。我们不仅获得了后验分布的模式,还获得了完整的分布。因此,我们可以检查它,计算其他矩(例如方差),并总体上更好地理解问题。特别是,通过这点我们能够感知不确定性,还可以决定是否拒绝我们解释数据的假设。让我们通过一个例子来演示这一点。
假设我们把硬币分析变成一个“游戏”:我们有两个硬币,必须选择那个公平的,即出现 50:50 的硬币。
游戏节目主持人给你呈现了两个硬币:
-
硬币 1 被抛掷了 8 次,其中 4 次出现了正面。
-
硬币 2 被抛掷了 100 次,其中 50 次出现了正面。
初看之下,两者似乎都有大约 50%的概率出现正面。然而,直观上大多数人肯定会选择硬币 2——因为这里我们有一个更大的样本量。聪明的你迅速在脑海中应用了最大后验法。你选择了一个α = β = 2的贝塔分布,为你提供了一个在[0, 1]区间上对称分布,模式为 0.5。
让我们做数学计算并计算θ的 MAP 估计:
-
θ₁= (4+2-1)/(8+2+2–2)=0.5 -
θ₂= (50+2–1)/(100+2+2–2)=0.5
因此,根据 MAP 方法,两枚硬币的结果完全相同!
让我们绘制贝叶斯推断上得到的两个硬币的完整后验分布:

作者生成的图片
现在,根据我们的预期,硬币 2 的后验方差要低得多——这证明我们应该选择它!
结论
在这篇文章中,我们介绍了 MLE(最大似然估计)、MAP(最大后验估计)和贝叶斯推断。我们用一个不公平的硬币作为例子进行演示。
MLE 寻找优化似然的参数。MAP 引入了参数的先验分布,返回最大化完整后验的参数。因此,MLE 和 MAP 都返回点估计。相比之下,贝叶斯推断对完整后验分布进行建模。这通常是一个复杂的任务——但也更强大,因为我们可以深入了解这个分布,例如方差。
本文到此结束。感谢阅读!
备注:
-
所有图片,除非另有说明,均由作者生成。
-
本文中的计算示例部分受到这个很棒的教程的启发。
统计学基础:所有数据科学家和分析师应知——带代码——第一部分
本文是对数据科学家和数据分析师统计学基础的全面概述
·发表于 Towards Data Science ·阅读时间 8 分钟·2023 年 1 月 31 日
--

图片来源:Clay Banks 在 Unsplash
动机
构建机器学习模型对于做出预测很酷。然而,当涉及到更好地理解业务问题时,它们并不适用,这需要在统计建模中投入最多的时间。
本文将首先帮助你建立统计学基础的理解,这对数据科学家和数据分析师的日常活动是有益的,以帮助业务做出可操作的决策。它还将指导你通过实际操作,使用 Python 练习这些统计概念。
如果你喜欢视频而不是阅读文章,那么这适合你 👇🏽
总体和样本之间有什么区别?
在开始处理数据之前,让我们首先了解总体和样本的概念。
→ 一个总体是你感兴趣的所有项目的集合(事件、人物、物体等)。在下面的图片中,总体由七个人组成。
→ 一个样本则是总体的一个子集。图片中的样本包含两个人。

样本和总体的示意图(图片来源:作者)
在现实生活中,很难找到和观察总体。然而,收集样本的时间更少,成本更低。这些是我们更喜欢处理样本的主要原因,大多数统计测试也设计为处理不完整的数据,即样本。
样本需要满足以下两个标准以确保有效性:(1) 随机 和 (2) 代表性。
→ 随机样本意味着样本中的每个元素都是从总体中严格随机选择的。
→ 当样本准确地反映总体时,它是代表性的。例如,当总体包括男性和女性时,样本不应仅包含男性。
数据有哪些不同类型?
现实生活中的数据由不同类型组成。了解它们很重要,因为不同类型的数据具有不同的特征,并且以不同的方式收集和分析。

数据的不同类型(作者提供的图片)
中心趋势的主要度量有哪些?
中心趋势的主要度量有三个:均值、中位数和众数。在探索数据时,应同时应用这三种度量,以便得出更好的结论。然而,仅使用其中一个可能会导致对数据提供不准确的信息。
本节重点定义它们的每一个,包括其优缺点。
均值
也称为平均值 (µ 对于总体,x 带有上标对于样本)。它对应于有限数字集的中心。均值通过将所有数字除以元素的总数来计算。考虑一个数字集合 *x1, x2, …, xn*,均值定义如下:

维基百科中的均值公式
-
*x*带有上标表示样本均值。 -
*n*表示样本集中的观察总数。
以下是 Python 中的实现。
# Import the mean function from statistics module
from statistics import mean
# Define the set of numbers
data = [5, 53, 4, 8, 6, 9, 1]
# Compute the mean
mean_value = mean(data)
print(f"The mean of {data} is {mean_value} ")
之前的代码应生成以下结果:
The mean of [5, 53, 4, 8, 6, 9, 1] is 12.28
尽管均值主要被使用,但它确实存在一个问题,就是容易受到异常值的影响,因此可能不是得出相关结论的最佳选择。
中位数
中位数表示数据在按升序或降序排列后的中间值,公式如下。

维基百科中的中位数公式
与均值相对,中位数不受异常值的影响,因此可以是更好的中心趋势度量。然而,中位数和均值仅适用于数值数据。
使用上述相同的数据,我们可以如下计算中位数:
# Import the median function from statistics module
from statistics import median
# Compute the median
median_value = median(data)
print(f"The median of {data} is {median_value} ")
执行结果如下:
The median of [5, 53, 4, 8, 6, 9, 1] is 6
让我们详细分析数据的中位数计算过程。
-
第 1 步:将数据按升序排列:[1, 4, 5, 6, 8, 9, 53]
-
第 2 步:在我们的例子中,
n = 7是奇数。 -
第 3 步:中间值是
(n + 1)/2项,即(7+1)/2 = 4,因此为 6。
众数
它对应于数据中出现频率最高的值,可以应用于数值和分类变量。
类似于中位数,模式对异常值不敏感。然而,当数据中的所有值出现的次数相同时,模式不存在。大多数情况下,我们在数据中最多可以观察到三种模式。
让我们使用一个不同的数据集来说明模式的使用。
# Define the data
data = [5, 9, 4, 9, 7, 9, 1]
# Compute the mode
mode_value = mode(data)
print(f"The mode of {data} is {mode_value} ")
数据中所有值只出现一次,除了 9 出现了三次。由于模式对应于出现次数最多的值,上述代码的结果如下:
The mode of [5, 9, 4, 9, 7, 9, 1] is 9
形状的度量指标是什么?
Skewness和Kurtosis是可以更好地描述给定数据集形状的两种主要技术。本节将详细介绍每一种技术,包括使用 Python 的示例。
在深入解释每个概念之前,让我们导入必要的 Python 库。
-
Numpy用于处理数组。 -
scipy模块用于统计分析。 -
为了可视化,我们使用
matplotlib库。
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
from scipy.stats import beta, kurtosis
偏斜度
当数据的概率分布在均值周围不对称时,数据被称为偏斜。根据偏斜度的值,可以出现三种主要情况。
以下辅助函数用于示例和绘制每种情况。
# Use the seed to get the same results for randomization
np.random.seed(2023)
def plot_skewness(data, label):
plt.hist(data, density=True, color='orange', alpha=0.7, label=label)
plt.axvline(data.mean(), color='green', linestyle='dashed', linewidth=2, label='Mean')
plt.axvline(np.median(data), color='blue', linestyle='dashed', linewidth=2, label='Median')
plt.legend()
plt.show()
- 当数据符合正态分布时,偏斜度是对称的。在这种情况下,
Mean = Median = Mode.
# Normal distribution
normal_data = np.random.normal(0, 1, 1000)
label = 'Normal: Symetric Skewness'
plot_skewness(normal_data, label)

正态分布或对称偏斜(图像来源于作者)
- 当值大于零时,存在正偏斜或右偏。这意味着均值右侧的值较多,均值位于中位数的右侧。在这种情况下,我们有
Mean > Median > Mode.
# Exponential distribution
exp_data = np.random.exponential(1, 1000)
label = 'Exponential: Positive Skewness'
plot_skewness(exp_data, label)

正偏斜的示意图(图像来源于作者)
- 当值小于零时,存在负偏斜或左偏。在这种情况下,左侧的值较多,我们通常发现均值位于中位数的左侧。在这种情况下,
Mean < Median < Mode.
# Beta
beta_data = beta.rvs(5, 2, size=10000)
label = 'Beta: Negative Skewness'
plot_skewness(beta_data, label)

负偏斜的示意图(图像来源于作者)
峰度
峰度度量量化了分布尾部的权重与分布其余部分的比例。它告诉我们数据是分散还是集中在均值附近。
具有较高均值集中度的分布被称为具有高峰度。低峰度与更平坦的分布相关,该分布中较少的数据集中在均值附近。
此外,峰度用于检查数据是否符合正态分布,也用于检测数据中的异常值。
数据可以展示三种主要类型的峰度:(1) Mesokurtic、(2) Leptokurtic和(3) Platykurtic。除了说明每个概念外,还将展示如何计算每一种类型的 Python 代码。
**(1) Mesokurtic**在这种情况下,kurtosis=3。这意味着峰度类似于正态分布,主要用作与现有分布的基准。

Mesokurtic 分布的插图(图像作者提供)
**(2) Leptokurtic**,也称为正峰度,其kurtosis>3。通常被称为“尖峰”分布,Leptokurtic在均值周围的数据集中的浓度高于正态分布。

Leptokurtic 分布的插图(图像作者提供)
**(3) Platykurtic**,也称为负峰度,其kurtosis<3。通常被称为“平坦”分布,Leptokurtic在均值周围的数据集中度低于Platykurtic峰度,且尾部较短。

Platykurtic 分布的插图(图像作者提供)
以下代码来自scipy的官方文档,完美演示了如何计算峰度。
x = np.linspace(-5, 5, 100)
ax = plt.subplot()
distnames = ['laplace', 'norm', 'uniform']
for distname in distnames:
if distname == 'uniform':
dist = getattr(stats, distname)(loc=-2, scale=4)
else:
dist = getattr(stats, distname)
data = dist.rvs(size=1000)
kur = kurtosis(data, fisher=True)
y = dist.pdf(x)
ax.plot(x, y, label="{}, {}".format(distname, round(kur, 3)))
ax.legend()

三种主要峰度及其值的插图(图像来自代码)
-
拉普拉斯分布具有
Leptokurtic峰度的特性。它的尾部比正态分布的尾部更为明显。 -
均匀分布由于其负峰度(
Platykurtic)具有最不明显的尾部。
结论
本系列的第一部分涵盖了不同类型的数据、样本与总体之间的差异、主要的集中趋势度量以及最终的偏度度量。
敬请关注下一部分,我们将讨论更多主题,帮助你获得相关的统计技能。
如果你喜欢阅读我的故事并希望支持我的写作,考虑成为 Medium 会员。每月$5 的承诺,你将解锁 Medium 上无限制访问故事的权限。
想请我喝咖啡☕️吗?→ 点击这里!
随时在Medium、Twitter和YouTube上关注我,或者在LinkedIn上打招呼!讨论 AI、ML、数据科学、NLP 和 MLOps 总是令人愉快的!
源代码可在GitHub上获取。
如何在 Pandas 数据框中进行模糊字符串匹配
原文:
towardsdatascience.com/fuzzy-string-matching-in-pandas-2c185a24617f
匹配没有完美匹配的文本
·发表于Towards Data Science ·阅读时间 6 分钟·2023 年 4 月 17 日
--

图片由Lucas Santos提供,Unsplash
现实世界是不完美的。
人们使用相同信息的不同形式。即便是成熟的系统也使用不同的标准。你可能会见过城市名称拼写错误的情况,比如“Santana”而不是“Santa Ana”或“St. Louie”而不是“St. Louis”。
在处理现实世界的数据时,这是不可避免的。因此,我们必须确保我们在管道的下一步中使用的数据是标准化的。
我们可以通过模糊字符串匹配来解决这个问题。这也不完美,但非常有用。
## 我在几乎所有数据科学项目中使用的 5 个 Python 装饰器
装饰器提供了一种新的便利方式,从缓存到发送通知都有应用
towardsdatascience.com
Python 中的模糊字符串匹配
如果你是 Python 程序员,你可能会使用 Pandas 数据框来处理数据。除了 pandas,你还可以使用“thefuzz”来进行模糊字符串匹配。
pip install thefuzz
TheFuzz是一个开源 Python 包,正式名称为“FuzzyWuzzy”。它使用Levenshtein 编辑距离来计算字符串相似度。
这是它的基本用法:
from thefuzz import fuzz, process
process.extractBests(
"my precious",
[
"My brushes",
"my purses",
"my prices",
"me priceless",
"my prcios",
"My bruises",
"My praises",
"My precursors",
"My process",
"My princess",
"My progresses",
"My prospects",
"My producers",
"My precisions",
"My presuppositions",
],
)
# Output
>> [('my prcios', 90),
('My presuppositions', 86),
('My precisions', 83),
('My precursors', 75),
('my purses', 70)]
process.extractOne(
"my precious",
[
...
],
)
# Output
>> ('my prcios', 90)
## SQL 在 Pandas 上 — 我新宠,速度提升 10 倍。
将两者的最佳之处结合起来
[towardsdatascience.com
如何测量文本相似度?
Levenshtein 距离,也称为编辑距离,是一种用于测量两个字符串之间差异或相似度的度量。它计算将一个字符串转换为另一个字符串所需的最小操作数(插入、删除或替换)。
Levenshtein 距离越小,两个字符串越相似。
例如,考虑两个字符串“chat”和“chart”。它们之间的 Levenshtein 距离是 1,因为将“chat”转换为“chart”所需的唯一操作是将字母“a”替换为“r”。
现在考虑字符串“intention”和“execution”。它们之间的 Levenshtein 距离是 5。以下是将“intention”转换为“execution”的一种可能方式,所需的操作最少:
-
将‘i’替换为‘e’:entention
-
将’n’替换为‘x’:extention
-
删除‘t’:exention
-
将’n’替换为‘u’:exection
-
插入‘u’:execution
这些操作的总成本是 5,这也是两个字符串之间的 Levenshtein 距离。
通常,两个字符串之间的 Levenshtein 距离越小,它们越相似。例如,如果两个字符串的 Levenshtein 距离为 0,则它们是完全相同的。相反,如果距离较大,则表示字符串之间有显著差异。
请参阅Educative 关于 Levenshtein 距离的文章,因为这不是本文的重点。
但这里有一个简单的示例,展示了如何在 Python 中为两个字符串获取相似度评分。
# Example 2: Calculate Jaccard similarity
ratio = fuzz.ratio("apple", "banana")
print(ratio) # Output: 18
# Example 3: Calculate cosine similarity
cosine_sim = fuzz.token_sort_ratio("apple", "banana")
print(cosine_sim) # Output: 18
# Example 4: Calculate partial ratio
partial_ratio = fuzz.partial_ratio("apple", "banana")
print(partial_ratio) # Output: 20
# Example 5: Calculate token set ratio
token_set_ratio = fuzz.token_set_ratio("apple is a fruit", "a fruit is an apple")
print(token_set_ratio) # Output: 100
链接 ## 使用 Black 和 GitHub Actions 保持 Python 代码的整洁。
没有人愿意面对混乱的代码库;很少有人有耐心去清理它。
[链接
使用模糊字符串匹配来删除 Pandas 数据框中的重复项
在处理用户创建的数据时,常见的挑战之一是删除重复项。但当重复项不是完全匹配时,这个任务会变得更加困难。
我们可以编写一个小函数来检查字符串相似度并删除重复项。以下是一个示例:
import pandas as pd
from thefuzz import fuzz, process
data = {
"Name": ["John Smith", "Jon Smtih", "Jane Doe", "James Johnsan", "Janes Johnson"],
"Age": [25, 25, 30, 40, 40],
"Gender": ["M", "M", "F", "M", "M"],
}
df = pd.DataFrame(data)
display(df)
# Output
| | Name | Age | Gender |
|---:|:--------------|------:|:---------|
| 0 | John Smith | 25 | M |
| 1 | Jon Smtih | 25 | M |
| 2 | Jane Doe | 30 | F |
| 3 | James Johnsan | 40 | M |
| 4 | Janes Johnson | 40 | M |
def compare_strings(a, b):
return fuzz.token_sort_ratio(a, b)
def remove_duplicates(df, threshold=90):
duplicates = set()
processed = []
for i, row in df.iterrows():
if i not in duplicates:
processed.append(row)
for j, other_row in df.iterrows():
if i != j and j not in duplicates:
score = compare_strings(row["Name"], other_row["Name"])
if score >= threshold:
duplicates.add(j)
return pd.DataFrame(processed)
remove_duplicates(df, threshold=80)
# Output
| | Name | Age | Gender |
|---:|:--------------|------:|:---------|
| 0 | John Smith | 25 | M |
| 2 | Jane Doe | 30 | F |
| 3 | James Johnsan | 40 | M |
我们的原始数据集包含了类似的名字——John Smith 和 Jon Smith,James Johnson 和 James Johnsan。但我们能够去除这些重复项。
链接 ## 我的 Python 脚本如何更像自然对话
管道是更人性化编码的极佳技巧
[towardsdatascience.com
在 Pandas 数据框中标准化模糊重复项
这是一个实现函数的示例,基于相似度评分阈值,使用 thefuzz 包将“Name”列中的重复行替换为该行的第一次出现:
def replace_duplicates(df, column_name='Name', threshold=90):
processed = []
duplicates = set()
first_occurrence = {}
for i, row in df.iterrows():
row_text = row[column_name]
if i not in duplicates:
processed.append(row)
first_occurrence[row_text] = i
for j, other_row in df.iterrows():
if i != j and j not in duplicates:
other_text = other_row[column_name]
score = fuzz.token_set_ratio(row_text, other_text)
if score >= threshold:
duplicates.add(j)
first_occurrence[other_text] = i
return df.iloc[list(first_occurrence.values())]
我们可以使用这个函数将数据集中所有的 Jon Smith 替换为 John Smith。
replace_duplicates(df, threshold=80)
# Output
| | Name | Age | Gender |
|---:|:--------------|------:|:---------|
| 0 | John Smith | 25 | M |
| 0 | John Smith | 25 | M |
| 2 | Jane Doe | 30 | F |
| 3 | James Johnsan | 40 | M |
| 3 | James Johnsan | 40 | M |
结论
对于现实世界的数据,模糊字符串匹配是必不可少的。除非你自动生成数据,否则几乎总是期望数据集中存在非标准值。即使在自动生成的系统中,约定也可能有所不同。
当涉及到文本数据时,我经常看到我们在某些点上卡住了,这时需要模糊匹配。
在这种情况下,我们可以使用本文中强调的技术。我们使用了 Python 包“thefuzz”来使用 Levenshtein 距离匹配字符串,并从 Pandas 数据框中删除了重复项。如果需要,我们可以将它们替换为一个合适的值,而不是简单地删除。
但模糊字符串匹配并不完美。例如,在替换重复项的最后一个示例中,我们的脚本将所有的“James Johnson”替换为“James Johnsan”。如果我们更喜欢 Johnson,我们的脚本就没有做好。
因此,我们需要把模糊匹配作为最后的手段,或作为一个有用的指南。但是过度依赖它们是不明智的。
希望这对你有帮助。
感谢阅读,朋友!在 LinkedIn,Twitter,和 Medium 上跟我打个招呼吧。
还不是 Medium 会员?请使用这个链接 成为会员,因为对你没有额外费用,我通过推荐你赚取少量佣金。
高斯头像:总结
最近高斯点溅射的论文出现了爆炸性增长,头像领域也不例外。这些技术是如何工作的,它们会彻底改变这一领域吗?
·
关注 发表在 Towards Data Science · 15 分钟阅读 · 2023 年 12 月 19 日
--
高斯点溅射在训练初期的表现。图像作者提供。
放松一下……如果你对数字人类的研究感兴趣,并且有任何形式的社交媒体,你几乎肯定已经被大量将高斯喷溅应用于该领域的论文轰炸过了。正如伟大的贾宾·黄所说:2023 年确实是用高斯喷溅取代所有 NeRF 的年份。GaussianAvatars,FlashAvatar,可重光照高斯编解码头像,MonoGaussianAvatar:这些论文仅代表了涵盖面部的论文中的一小部分!
如果你像我一样,你会被这些令人惊叹的工作完全压倒。我写这篇文章是为了比较和对比这些论文,并试图提炼出支撑这些工作的关键组件。我几乎肯定会遗漏一些论文,并且在我完成这篇文章时,我预计会有更多论文出现!我将首先简要回顾高斯喷溅作为一种方法,然后讨论一些关键论文。

一系列高斯头像论文中的预览图。左上: MonoGaussianAvatar,左下: GaussianAvatars,右上: 可重光照高斯编解码头像,右中: 高斯头,右下: 高斯头头像。
高斯喷溅——总体思路
高斯喷溅现在无处不在。你很可能已经了解了基础知识,如果不了解,还有很多资源可以比我更好地解释它们。如果你感兴趣,这里有一些例子(1,2,3)。尽管如此,我会尽力给出一个快速、一般的概述。

栅格化三角形(左)与高斯喷溅(右)。灵感来自于这篇 HuggingFace 文章。
简而言之,高斯喷溅是一种栅格化形式。它将场景的某种表示转换为屏幕上的图像。这类似于大多数图形引擎的三角形渲染。与绘制三角形不同,高斯喷溅(毫不奇怪)将高斯分布“喷溅”到屏幕上。每个高斯分布由一组参数表示:
-
3D 空间中的一个位置(场景中)。μ
-
每个轴的缩放(高斯的倾斜)。s
-
一种颜色(可以是 RGB 或更复杂的,取决于视角)。c
-
一种不透明度(透明度的相反)。α
高斯溅射本身并不新鲜,它至少从 90 年代就存在了。新的在于能够以可微分的方式渲染它们。这使我们能够使用一组图像将它们拟合到场景中。结合创建更多高斯和删除无用点的方法,我们得到了一种极其强大的 3D 场景表示模型。
我们为什么要这样做?有几个原因:
-
其质量不言而喻。它在视觉效果上甚至超越了 NeRFs。
-
使用高斯溅射渲染的场景可以以数百 fps 运行,而且我们甚至还没有深入探讨硬件/软件优化。
-
由于它们是离散点,可以轻松编辑/组合,这与神经表示不同。
使高斯能够动画化
高斯溅射显然很酷。人们寻求将其应用于面孔并不令人惊讶。你可能见过苹果的角色,它们引起了一些关注。本文中的论文完全超越了这些角色。想象一下,完全可控的数字化身可以在消费级 VR 头显上原生运行,具有 6 自由度的相机运动,并且双眼帧率超过 100 fps。这将使“元宇宙”最终成为现实。我个人敢打赌,这种情况将在未来 2–3 年内实现。高斯溅射(或某种变体)很可能是实现这一目标所需的技术。
当然,渲染静态场景的能力还不够。我们需要能够用高斯实现我们目前在三角网格中能够做到的事情。动态高斯(例如 1,2)的出现并不久。这些方法允许捕捉和重播“高斯视频”。

动态高斯的示例,最佳查看源头:这里。图片来源:JonathonLuiten,MIT 许可证
再次,这确实很酷,但不是我们所追求的。理想情况下,我们想要一个可以通过动作捕捉、音频或其他信号进行控制的表现形式。幸运的是,我们有大量的研究专门针对这一点。引入我们老朋友 3DMM。我在上一篇文章中讲解了这些如何工作。但从本质上讲,它们是通过一小组参数来表示的 3D 模型。这些参数决定了面部的几何形状,面部表情与面部形状分离。这允许通过只改变少量表情参数来控制面部表情。大多数试图通过高斯点阵动画的论文以 3DMM 为核心。
现在我们将讨论一些最近的高斯头部动画论文(顺序无关)。我在每篇文章的末尾添加了一些 TLDR 摘要。
高斯头像:带有装配 3D 高斯的逼真头部头像

高斯头像的方法图解。图像直接从arxiv 论文中复制。
高斯头像:带有装配 3D 高斯的逼真头部头像。Shenhan Qian、Tobias Kirschstein、Liam Schoneveld、Davide Davoli、Simon Giebenhain、Matthias Nießner。Arxiv 预印本,2023 年 12 月 4 日。链接
我们将首先查看一篇有趣的论文,它是慕尼黑工业大学与丰田之间的合作(我对丰田的参与非常好奇)。该团队正在使用 FLAME,这是一种非常流行的 3DMM。这种方法旨在利用多视角视频获得一个可以通过 FLAME 参数控制的模型。它由几个独立的阶段组成。
FLAME 拟合
流水线的第一阶段旨在使用 FLAME 网格重建面部几何的粗略近似。这是通过可微分渲染来完成的。具体来说,他们使用 NVDiffrast 以允许反向传播的方式渲染 FLAME 网格。再次,我之前已经介绍过这如何工作。他们的方法与现有跟踪器的不同之处有三点。1) 它是多视角的,这意味着他们在多个相机上同时进行优化,而不是单目重建。2) 他们还在 FLAME 顶点中添加了一个额外的常数偏移,允许更好的形状重建。这是可能的,因为多视角设置中不存在深度模糊问题。3) 他们包括一个拉普拉斯网格正则化器,鼓励网格光滑。下面可以看到 FLAME 跟踪的示例。

给定帧的 FLAME 网格重建示例。图像直接转载自 arxiv 论文。
拟合高斯
接下来的目标是以 FLAME 网格控制的方式拟合高斯。在本文中,该方法类似于 INSTA,其中 3D 空间中的每个点都“绑定”到 FLAME 网格上的一个三角形。随着网格的移动,点也随之移动。将这个想法扩展到高斯是相当简单的,只需将每个高斯分配到一个三角形。我们在父三角形定义的局部框架中定义每个高斯的参数,并根据 FLAME 网格相对于中性 FLAME 网格定义的变换来调整其参数。

高斯在每个三角形的局部坐标框架中初始化。图像直接转载自 arxiv 论文。
例如,假设我们张嘴,一个位于下巴的三角形向下移动 1cm 并旋转 5 度,我们会对任何绑定的高斯应用相同的变换,并以相同的方式旋转高斯的旋转。
从这里开始,过程与原始高斯论文非常相似,前向传递现在获取高斯参数,根据跟踪的网格对其进行变换,并进行点滴。参数使用反向传播进行优化。使用附加损失来防止高斯离父三角形过远。最后,密集化过程稍有变化,以确保任何生成的高斯都绑定到与其父级相同的三角形上。
TLDR: 将高斯分配到 FLAME 的三角形上,并通过网格进行变换。
FlashAvatar: 高保真数字头像渲染,300FPS

FlashAvatars 的方法示意图。图像直接来自于arxiv 论文。
FlashAvatar: 高保真数字化身渲染,帧率达到 300FPS。项俊,高轩,郭宇东,张聚勇。Arxiv 预印本,2023 年 12 月 3 日。链接
在我看来,这是最容易理解的论文。这是一篇关注速度的单目(单摄像头)高斯头部化身论文。它可以以 300fps(!!)运行,并且训练仅需几分钟。再次强调,该模型分为几个阶段。
FLAME 拟合
由于这是一个单摄像头方法,因此采用了单目重建。这个方法基于可微渲染和 Pytorch3D。该方法在 GitHub 上开源,来自 MICA。另外,我在之前的博客文章中也介绍了这个方法的工作原理。
高斯分布拟合
本文在 uv 空间中建模高斯分布。使用预定义的 uv 地图来获得 2D 图像和 3D 网格之间的对应关系。每个高斯分布由其在 uv 空间的位置而不是 3D 空间中的位置来定义。通过在 uv 空间中采样一个像素,可以通过获取在姿态 3D 网格上的对应点来获得高斯分布的 3D 位置。为了捕捉口腔内部,本文在口腔内部添加了一些额外的面片。

本文将口腔内部建模为一个平面。图像直接来自于arxiv 论文。
然而,这种 uv 对应关系将高斯分布的位置限制在了网格表面。这是不理想的,因为粗略的 FLAME 网格并不能完美重建几何形状。为了解决这个问题,学习了一个小型 MLP 来将高斯分布相对于其在网格上的位置进行偏移。通过将该 MLP 基于 FLAME 模型的表情参数进行条件化,结果的质量得到了改善,这可以看作是一种神经校正方法。
该模型使用 L1 和 LPIPS 损失进行重建训练。口腔区域的遮罩被提高,以增加在重建更困难的区域的保真度。
TLDR; 在 uv 空间中建模高斯分布,并使用基于表情的 MLP 对其在网格上的相对位置进行偏移。
可重光高斯编解码化身

可重光高斯编解码化身的方法示意图。图像直接来自于arxiv 论文。
可重光照高斯编码头像。 斋藤俊介,加布里埃尔·施瓦茨,托马斯·西蒙,李俊轩,南吉珠。 Arxiv 预印本,2023 年 12 月 6 日。 链接
下一篇论文可能是引起最多关注的那一篇。这篇论文来自 Meta 的现实实验室。除了可以进行动画处理外,还可以更改这些模型的光照,使其更容易融入不同的场景。由于这是 Meta,而 Meta 在“元宇宙”上进行了重大投资,我预计这可能很快会转化为产品。该论文在已经流行的编码头像基础上进行扩展,使用了高斯点云。
网格拟合
不幸的是,Meta 使用的网格重建算法稍显复杂,它建立在公司之前几篇论文的基础上。尽管如此,足以说明的是,他们能够在多个帧上重建具有一致拓扑的跟踪网格,并保持时间一致性。他们使用一种非常昂贵且复杂的捕捉装置来完成这一点。
CVAE 训练 — 在高斯分布之前

CVAE 基于论文“可动画面孔的深度可重光照外观模型”中的版本。图片摘自论文。
Meta 的先前方法基于 CVAE(条件变分自编码器)。它接收跟踪网格和平均纹理,并将它们编码成一个潜在向量。然后,这个向量在重新参数化后被解码成网格,并使用一组特征来重现纹理。这篇论文的目标是使用类似的模型,但采用高斯分布。
CVAE 与高斯分布
要将此模型扩展到高斯点云,需要进行一些更改。然而,编码器却没有改变。这个编码器仍然接收跟踪网格的顶点 V 和一个平均纹理。头像的几何形状和外观是分别解码的。几何形状通过一系列高斯分布来表示。论文中较有趣的部分之一是高斯在 uv 空间中的表示。在这里,为网格模板定义了一个 uv 纹理图,这意味着纹理图中的每个像素(texel)对应于网格表面上的一个点。在这篇论文中,每个 texel 定义了一个高斯分布。每个 texel 高斯分布不是通过绝对位置定义,而是通过它与网格表面的位移来定义,例如,所示的 texel 是一个与眉毛相关联并随其移动的高斯分布。每个 texel 还具有旋转、缩放和透明度的值,以及 RGB 颜色和单色的粗糙度(σ)和 SH 系数。

左侧的图像表示右侧的几何体。每个纹素代表一个高斯分布。图像直接来源于arxiv 论文。
除了高斯分布外,解码器还预测了表面法线贴图和可见性贴图。这些都通过光照的渲染方程近似结合。以下是一个非常粗略的解释,由于我不是光照方面的专家,这个解释几乎肯定是不准确的或不全面的。

从左到右:法线贴图、镜面反射光照贴图、漫反射光照贴图、反射率和最终光照效果。图像直接来源于arxiv 论文。
光的漫反射成分使用球面谐波计算。每个高斯分布都有一个反射率 (ρ) 和 SH 系数 (d)。通常,SH 系数仅表示到第 3 阶,但这不足以表示阴影。为平衡空间节省,作者使用了第 3 阶的 RGB 系数和第 5 阶的单色(灰度)系数。除了漫反射光照外,论文还通过给每个高斯分布分配粗糙度并使用解码后的法线贴图来建模镜面反射(如反射)。如果你对这些工作原理感兴趣,建议阅读论文和补充材料。
最后,一个单独的解码器还预测了模板网格的顶点。所有模型都通过图像层面和网格层面的重建损失一起训练。同时还使用了各种正则化损失。结果是一个具有表达和光照控制的极高质量的虚拟形象。
TLDR; 将高斯分布表示为 UV 空间图像,分解光照并显式建模,通过这种高斯表示改进编解码器的虚拟形象。
MonoGaussianAvatar:单目高斯点基头像

MonoGaussianAvatar 的方法图示:图像直接来源于arxiv 论文。
MonoGaussianAvatar:单目高斯点基头像。Yufan Chen,Lizhen Wang,Qijing Li,Hongjiang Xiao,Shengping Zhang,Hongxun Yao,Yebin Liu。Arxiv 预印本,2023 年 12 月 7 日。 链接
这是另一篇针对单目情况(例如,仅使用一个摄像头)进行研究的论文。这个模型依然围绕 3DMM 构建,但采用了与其他模型略有不同的方法。基于IMAvatar和 PointAvatars 中概述的想法,它将 FLAME 模型定义的变形扩展为一个连续的变形场。使用该变形场,高斯点可以根据 FLAME 参数进行变形。这也是一个多阶段的过程。
FLAME 拟合
这里的拟合过程与 FlashAvatar 非常相似。我已经在这篇文章中介绍过,因此不再重复。如果你感兴趣,请阅读那一部分。
扩展 FLAME 到变形场
理想情况下,我们希望以与 FLAME 网格的顶点相同的方式变形高斯点。然而,FLAME 网格的变形仅定义了它所包含的 5023 个顶点。虽然大多数其他方法试图将高斯点耦合到网格上的某一点,但本文旨在扩展 FLAME 的变形,以覆盖规范空间中的所有点。什么是规范空间?我们稍后会介绍。在 FLAME 中,表情和姿势校正是通过对 5023 个顶点定义的混合形状进行线性组合来定义的。在本文中,这些校正则由 MLP 网络表示。假设我们有 100 种表情,我们将定义一个网络,该网络接受规范空间中的位置,并输出一个(100, 3)大小的矩阵,表示该点的表情基。在每个关节的皮肤加权和姿势校正混合形状也由 MLP 表示。

基于IMAvatar的 MLP 网络表示的表情、姿势和皮肤加权。图片直接来自arxiv 论文。
这些多层感知机(MLP)与优化过程中的其他部分一起进行训练。通过将每个高斯点与 FLAME 网格上最近的点对齐,并要求该点的变形场与实际 FLAME 模型中定义的变形场一致,从而定义一个正则化损失。
高斯点拟合 — 3 个空间
本文中定义了 3 个空间。高斯点通过每个空间进行变形,最后进行渲染,然后与真实图像进行比较。
在本文中,高斯分布的所有常见参数并没有像往常一样定义,而是仅通过它们在初始空间中的位置来定义。这是初始化空间。在此基础上,MLPs 预测所有常见属性,以初始化空间位置为输入,生成第二空间中的位置、缩放、旋转等。这称为规范空间。为了提高稳定性,规范空间中的位置作为从初始化空间位置的偏移量给出。最后,每个高斯分布通过变形 MLPs 进行变形,并且一组最终的 MLPs 还根据规范空间中的位置修改所有其他高斯参数。

三个不同的空间。初始化空间显示在左侧,规范空间显示在顶部,最终姿态空间显示在底部。图像直接来自于 arxiv 论文。
本文还使用了密集化技术来提高结果的质量。它更类似于 PointAvatar 中使用的类型。任何透明度 <0.1(例如接近透明)的高斯分布都被删除。每 5 个周期采样一定数量的高斯分布,这通过选择一个父高斯分布,采样一个靠近它的位置,并复制来自父高斯分布的其他参数来完成。随着时间的推移,采样半径会减少。
该模型使用原始高斯损失、上述 FLAME 变形损失以及感知 VGG 损失进行训练。梯度通过三个空间进行反向传播。
TLDR; 用连续变形场替换离散的 FLAME 模型。在这些场中拟合高斯分布。
伦理问题
高斯溅射技术允许实时生成逼真的人物渲染。这无疑会引发一系列伦理问题。其中最直接显而易见的问题与深伪技术(deepfakes)相关。例如,这些技术可能被用于生成虚假信息或非自愿的露骨材料。鉴于生成具有新水平真实感的虚拟形象的能力,潜在的危害是显著的。
更糟糕的是,现有的针对基于图像的深伪技术(如深伪检测、水印或接种)的方法可能无法应对基于高斯的技术。考虑到这一点,我认为研究人员在其工作中开发这些方法是至关重要的。一些研究表明,特别是在 NeRFs 中,水印是可能的。因此,应该可以将这些技术适应于高斯溅射。
如果我对本文提出的工作有一个批评,那就是缺乏对工作潜在影响的考虑。在本文列出的论文中,只有两篇提到伦理问题,即使如此,讨论也很简短。
尽管目前由于实施模型和数据要求的挑战,这项原型技术实际被滥用的难度很大,但我们可能只差几篇论文,就能得到可能造成实际伤害的模型。作为研究社区,我们有责任考虑我们工作的后果。
在我看来,是时候围绕数字人类研究建立最佳实践规范了。
讨论 — 相似性、差异性和未来方向
这真的有很多论文!我们几乎只是覆盖了其中的一小部分。虽然我认为理解每篇论文的细节很有用,但更有价值的是理解所有论文的总体主题。以下是我从阅读这些文献中得到的一些见解,请随时进行讨论或补充你的观点。
FLAME: 每篇论文都试图将高斯附加到现有的网格模型上,在除了 Meta 的论文外,这就是 FLAME。FLAME 仍然非常受欢迎,但在我看来,它仍然不完美。明显的问题是缺乏细节,这是两篇论文所解决的,但像“O”这样的某些唇形的建模能力也很普遍。我认为有空间看到新的模型出现并改进这一点。个人而言,我期待像神经参数化头部模型这样的模型获得更高的关注。通过与 2D-uv 空间的对应关系,应该可以将一些高斯方法应用到这些模型提供的更优几何结构上。
附加方法: 两篇论文将高斯附加到网格上使用 uv 空间,一篇将其附加到三角形上,另一篇将 FLAME 扩展到连续变形场。这些方法似乎都很有效。我个人对使用 uv 空间的方法感到最兴奋。我认为这可能打开了学习生成模型的可能性,以生成高斯头像。例如,如果训练数千个 uv 空间模型,可以在这些模型上训练扩散模型/GAN,从而允许采样随机、逼真的头像。
速度: 所有这些方法都非常快速,运行速度比实时还要快。这将开启许多以前不可能的应用。预计在不久的将来会看到电信、游戏和娱乐的原型。
总结来说,高斯散点图已经成功地进入了头部头像领域,并且看起来有很大的潜力来开启许多令人兴奋的应用。然而,这些应用需要平衡潜在的风险。如果我们能够做好这一点,数字人类研究的未来将会非常光明!
高斯混合模型清晰解释
原文:
towardsdatascience.com/gaussian-mixture-model-clearly-explained-115010f7d4cf
学习 GMM 所需的唯一指南
·发布于数据科学前沿 ·阅读时间 9 分钟·2023 年 1 月 10 日
--

图片由Planet Volumes提供,来源于Unsplash
当我们谈论高斯混合模型(以下简称 GMM)时,了解 KMeans 算法的工作原理是至关重要的。因为 GMM 与 KMeans 非常相似,实际上它是 KMeans 的概率版本。这种概率特性使 GMM 可以应用于 KMeans 无法适应的许多复杂问题。
总结来说,KMeans 具有以下限制:
-
它假设簇是球形且大小相等,这在大多数现实世界场景中并不成立。
-
这是一种硬聚类方法,意味着每个数据点被分配到一个单独的簇中。
由于这些限制,当我们进行机器学习项目时,应了解 KMeans 的替代方案。本文将探讨 KMeans 聚类的最佳替代方案之一,即高斯混合模型。
在本文中,我们将涵盖以下要点。
-
高斯混合模型(GMM)算法的工作原理——通俗易懂。
-
GMM 背后的数学。
-
从零开始使用 Python 实现 GMM。
高斯混合模型(GMM)算法的工作原理——通俗易懂
正如我之前提到的,我们可以将 GMM 称为概率 KMeans,因为 KMeans 和 GMM 的起点和训练过程是相同的。然而,KMeans 使用基于距离的方法,而 GMM 使用概率方法。GMM 有一个主要的假设:数据集由多个高斯分布组成,换句话说,是高斯混合。

高斯混合模型的混合体 | 作者图像
上述分布通常称为多模态分布。每个峰值代表数据集中的不同高斯分布或簇。但问题是,
我们如何估计这些分布?
在回答这个问题之前,让我们先创建一些高斯分布。请注意,我生成的是多变量正态分布;它是单变量正态分布的高维扩展。
让我们定义数据点的均值和协方差。使用均值和协方差,我们可以生成如下分布。
# Set the mean and covariance
mean1 = [0, 0]
mean2 = [2, 0]
cov1 = [[1, .7], [.7, 1]]
cov2 = [[.5, .4], [.4, .5]]
# Generate data from the mean and covariance
data1 = np.random.multivariate_normal(mean1, cov1, size=1000)
data2 = np.random.multivariate_normal(mean2, cov2, size=1000)
让我们绘制数据。
plt.figure(figsize=(10,6))
plt.scatter(data1[:,0],data1[:,1])
plt.scatter(data2[:,0],data2[:,1])
sns.kdeplot(data1[:, 0], data1[:, 1], levels=20, linewidth=10, color='k', alpha=0.2)
sns.kdeplot(data2[:, 0], data2[:, 1], levels=20, linewidth=10, color='k', alpha=0.2)
plt.grid(False)
plt.show()

如您所见,我们使用均值和协方差矩阵生成了随机的高斯分布。那么反转这一过程呢?这正是 GMM 在做的。但怎么做呢?
因为一开始我们对簇及其相关的均值和协方差矩阵没有任何见解。
好吧,按照以下步骤进行,
-
决定簇的数量(为此,我们可以使用领域知识或其他方法,如BIC/AIC)以适应给定数据集。假设我们有 1000 个数据点,并将组数设置为 2。
-
为每个簇初始化均值、协方差和权重参数。(我们将在后面的部分进一步探讨)
-
使用期望最大化算法完成以下任务,
-
期望步骤(E 步骤):计算每个数据点属于每个分布的概率,然后使用当前参数估计值评估似然函数。
-
最大化步骤(M 步骤):更新之前的均值、协方差和权重参数,以最大化在 E 步骤中找到的期望似然。
-
重复这些步骤直到模型收敛。
有了这些信息,我将结束对 GMM 算法的无数学解释。
GMM 背后的数学
GMM 的核心在于前一部分描述的期望最大化(EM)算法。
让我们演示 EM 算法如何应用于 GMM。
步骤 01:初始化均值、协方差和权重参数
-
均值(μ):随机初始化。
-
协方差(Σ):随机初始化
-
权重(混合系数)(π):每类的比例表示特定数据点属于每个类别的可能性。一开始,这对于所有簇都是相等的。假设我们拟合一个具有三个组件的 GMM。在这种情况下,权重参数可能被设置为 1/3,对应于概率分布(1/3,1/3,1/3)。
步骤 02:期望步骤(E 步骤)
-
对于每个数据点𝑥𝑖:
-
使用以下方程计算数据点属于集群(𝑐)的概率。k是我们需要找到的分布数量。

方程 01 | 作者提供的图片
其中𝜋_𝑐是高斯分布 c 的混合系数(有时称为权重),在上一个阶段初始化,𝑁(𝒙 | 𝝁,𝚺)描述了具有均值𝜇和协方差Σ的高斯分布的概率密度函数(PDF),相对于数据点x;我们可以如下表示它。

方程 02 | 作者提供的图片
E 步使用当前模型参数的估计值计算这些概率。这些概率通常被称为高斯分布的“责任”。它们由变量r_ic,表示,其中i是数据点的索引,c是高斯分布的索引。责任度量c-th 高斯分布对生成i-th 数据点的责任。这里使用了条件概率,更具体地说,是贝叶斯定理。
让我们举个简单的例子。假设我们有 100 个数据点,并且需要将它们聚类为两个组。我们可以将r_ic(i=20,c=1)写作如下。这里i表示数据点的索引,c表示我们考虑的集群的索引.
请注意,在开始时,𝜋_𝑐初始化为每个集群 c = 1,2,3,..,k 相等。在我们的例子中,𝜋_1 = 𝜋_2 = 1/2.

方程 03 | 作者提供的图片
E 步的结果是每个数据点和混合模型中每个高斯分布的责任集合。这些责任在 M 步中用于更新模型参数的估计值。
步骤 03:最大化步骤(M 步)
在这一步骤中,算法使用高斯分布的责任(在 E 步中计算得出)来更新模型参数的估计值。
M 步更新参数的估计值如下:

作者提供的图片
-
使用上述方程 4 更新πc(混合系数)。
-
使用上述方程 5 更新μc。
-
然后使用第 6 个方程更新Σc。
更新后的估计值在下一次 E 步中用于计算数据点的新责任。
依此类推,这个过程会重复进行,直到算法收敛,通常在模型参数在一次迭代到下一次迭代之间变化不大时实现。
很多丑陋且复杂的方程,对吧? 😃
让我们将上述事实总结为一个简单的图表,

GMM 的 EM 步骤总结 | 作者提供的图片
不用担心;在编码时,每个方程只有一行代码。让我们开始使用 Python 从零开始实现 GMM。
使用 Python 从零开始实现 GMM。

动画 GMM | 作者提供的图像
首先,让我们创建一个虚假的数据集。在这一部分,我将为 1-D 数据集实现 GMM。
import numpy as np
n_samples = 100
mu1, sigma1 = -5, 1.2
mu2, sigma2 = 5, 1.8
mu3, sigma3 = 0, 1.6
x1 = np.random.normal(loc = mu1, scale = np.sqrt(sigma1), size = n_samples)
x2 = np.random.normal(loc = mu2, scale = np.sqrt(sigma2), size = n_samples)
x3 = np.random.normal(loc = mu3, scale = np.sqrt(sigma3), size = n_samples)
X = np.concatenate((x1,x2,x3))
让我们创建一个辅助函数来绘制我们的数据。
from scipy.stats import norm
def plot_pdf(mu,sigma,label,alpha=0.5,linestyle='k--',density=True):
"""
Plot 1-D data and its PDF curve.
"""
# Compute the mean and standard deviation of the data
# Plot the data
X = norm.rvs(mu, sigma, size=1000)
plt.hist(X, bins=50, density=density, alpha=alpha,label=label)
# Plot the PDF
x = np.linspace(X.min(), X.max(), 1000)
y = norm.pdf(x, mu, sigma)
plt.plot(x, y, linestyle)
并按照如下方式绘制生成的数据。请注意,我绘制的是每个样本的概率密度,而不是数据本身。
plot_pdf(mu1,sigma1,label=r"$\mu={} \ ; \ \sigma={}$".format(mu1,sigma1))
plot_pdf(mu2,sigma2,label=r"$\mu={} \ ; \ \sigma={}$".format(mu2,sigma2))
plot_pdf(mu3,sigma3,label=r"$\mu={} \ ; \ \sigma={}$".format(mu3,sigma3))
plt.legend()
plt.show()

原始分布 | 作者提供的图像
让我们构建前面部分描述的每一步,
步骤 01: 初始化均值、协方差和权重
def random_init(n_compenents):
"""Initialize means, weights and variance randomly
and plot the initialization
"""
pi = np.ones((n_compenents)) / n_compenents
means = np.random.choice(X, n_compenents)
variances = np.random.random_sample(size=n_compenents)
plot_pdf(means[0],variances[0],'Random Init 01')
plot_pdf(means[1],variances[1],'Random Init 02')
plot_pdf(means[2],variances[2],'Random Init 03')
plt.legend()
plt.show()
return means,variances,pi
步骤 02: 期望步骤 (E 步骤)
def step_expectation(X,n_components,means,variances):
"""E Step
Parameters
----------
X : array-like, shape (n_samples,)
The data.
n_components : int
The number of clusters
means : array-like, shape (n_components,)
The means of each mixture component.
variances : array-like, shape (n_components,)
The variances of each mixture component.
Returns
-------
weights : array-like, shape (n_components,n_samples)
"""
weights = np.zeros((n_components,len(X)))
for j in range(n_components):
weights[j,:] = norm(loc=means[j],scale=np.sqrt(variances[j])).pdf(X)
return weights
在这个函数之后,我们涵盖了我们在E 步骤中讨论的前两个方程。这里我们为当前模型参数均值和方差生成了高斯分布。我们通过使用 scipy 的 stat 模块完成了这一点。之后,我们使用 pdf 方法计算每个数据点在每个集群中的归属可能性。
步骤 03: 最大化步骤 (M 步骤)
def step_maximization(X,weights,means,variances,n_compenents,pi):
"""M Step
Parameters
----------
X : array-like, shape (n_samples,)
The data.
weights : array-like, shape (n_components,n_samples)
initilized weights array
means : array-like, shape (n_components,)
The means of each mixture component.
variances : array-like, shape (n_components,)
The variances of each mixture component.
n_components : int
The number of clusters
pi: array-like (n_components,)
mixture component weights
Returns
-------
means : array-like, shape (n_components,)
The means of each mixture component.
variances : array-like, shape (n_components,)
The variances of each mixture component.
"""
r = []
for j in range(n_compenents):
r.append((weights[j] * pi[j]) / (np.sum([weights[i] * pi[i] for i in range(n_compenents)], axis=0)))
#5th equation above
means[j] = np.sum(r[j] * X) / (np.sum(r[j]))
#6th equation above
variances[j] = np.sum(r[j] * np.square(X - means[j])) / (np.sum(r[j]))
#4th equation above
pi[j] = np.mean(r[j])
return variances,means,pi
让我们实现训练循环。
def train_gmm(data,n_compenents=3,n_steps=50, plot_intermediate_steps_flag=True):
""" Training step of the GMM model
Parameters
----------
data : array-like, shape (n_samples,)
The data.
n_components : int
The number of clusters
n_steps: int
number of iterations to run
"""
#intilize model parameters at the start
means,variances,pi = random_init(n_compenents)
for step in range(n_steps):
#perform E step
weights = step_expectation(data,n_compenents,means,variances)
#perform M step
variances,means,pi = step_maximization(X, weights, means, variances, n_compenents, pi)
plot_pdf(means,variances)
当我们开始模型训练时,我们将根据我们设置的n_steps参数进行 E 步骤和 M 步骤。
但在实际使用中,你会更常使用 scikit-learn 版本的 GMM。在那里,你可以找到额外的参数,例如
tol: 定义模型的停止标准。当下界平均增益低于tol参数时,EM 迭代将停止。
init_params: 用于初始化权重的方法
你可以参考这里的文档。
好的,让我们看看我们手工制作的 GMM 的表现。

在上述图中,红色虚线代表原始分布,而其他图表示学习到的分布。在第 30 次迭代之后,我们可以看到我们的模型在这个玩具数据集上的表现良好。
如果你想跟随本文,你可以在这个GitHub 仓库中找到代码。
结论
本文旨在提供高斯混合模型的全面指南;然而,鼓励读者尝试不同的机器学习算法,因为没有一种最佳 算法 能够适用于所有问题。此外,我们选择的机器学习算法的复杂性也是值得注意的。GMM 的一个常见问题是它对大数据集的扩展性较差。
感谢阅读!在LinkedIn上与我联系。
高斯混合模型(GMMs):从理论到实现
原文:
towardsdatascience.com/gaussian-mixture-models-gmms-from-theory-to-implementation-4406c7fe9847
对 GMMs 和用于训练它们的期望最大化算法的深入解释
·发表于 Towards Data Science ·17 分钟阅读·2023 年 11 月 28 日
--
高斯混合模型(GMMs)是统计模型,将数据表示为高斯(正态)分布的混合。这些模型可以用于识别数据集中的群体,并捕捉数据分布的复杂、多模态结构。
GMMs 在各种机器学习应用中得到使用,包括 聚类、密度估计和模式识别。
在本文中,我们将首先探讨混合模型,重点关注高斯混合模型及其基本原理。然后,我们将介绍如何使用一种强大的技术——期望最大化(EM)——来估计这些模型的参数,并提供一个从头开始在 Python 中实现的逐步指南。最后,我们将展示如何使用 Scikit-Learn 库通过 GMM 进行聚类。

图片来源:Markéta Klimešová 来自 Pixabay
混合模型
混合模型 是一种概率模型,用于表示可能来源于多个不同来源或类别的数据,每个来源或类别都由单独的概率分布建模。例如,金融回报在正常市场条件下和危机期间的表现通常不同,因此可以建模为两个不同分布的混合。
正式地,如果 X 是一个随机变量,其分布是 K 个组件分布的混合,那么 X 的概率密度函数(PDF)或概率质量函数(PMF)可以写为:

一个混合模型
其中:
-
p(x) 是混合模型的整体密度或质量函数。
-
K 是混合中组件分布的数量。
-
fₖ(x; θₖ) 是第 k 个组件分布的密度或质量函数,由 θₖ 参数化。
-
wₖ 是第 k 个组件的混合权重,0 ≤ wₖ ≤ 1,权重的总和为 1。wₖ 也被称为组件 k 的先验概率。
-
θₖ 代表第 k 个组件的参数,例如高斯分布中的均值和标准差。
混合模型假设每个数据点来自 K 个组件分布之一,具体的分布是根据混合权重 wₖ 选择的。模型不需要知道每个数据点属于哪个组件。
高斯混合模型(GMM)是常见的混合模型,其中概率密度由高斯分布的混合给出:

一个高斯混合模型
其中:
-
x 是一个 d 维向量。
-
μₖ 是第 k 个高斯组件的均值向量。
-
Σₖ 是第 k 个高斯组件的协方差矩阵。
-
N(x; μₖ, Σₖ) 是第 k 个组件的多元正态密度函数:

在单变量高斯分布的情况下,概率密度可以简化为:

单变量高斯分布的混合模型
其中:
-
μₖ 是第 k 个高斯组件的均值。
-
σₖ 是第 k 个高斯组件的协方差矩阵。
-
N(x; μₖ, σₖ) 是第 k 个组件的单变量正态密度函数:

例如,下面的 Python 函数绘制了两个单变量高斯分布的混合分布:
from scipy.stats import norm
def plot_mixture(mean1, std1, mean2, std2, w1, w2):
# Generate points for the x-axis
x = np.linspace(-5, 10, 1000)
# Calculate the individual nomral distributions
normal1 = norm.pdf(x, mean1, std1)
normal2 = norm.pdf(x, mean2, std2)
# Calculate the mixture
mixture = w1 * normal1 + w2 * normal2
# Plot the results
plt.plot(x, normal1, label='Normal distribution 1', linestyle='--')
plt.plot(x, normal2, label='Normal distribution 2', linestyle='--')
plt.plot(x, mixture, label='Mixture model', color='black')
plt.xlabel('$x$')
plt.ylabel('$p(x)$')
plt.legend()
我们使用这个函数来绘制两个高斯分布的混合,其中参数为 μ₁ = -1, σ₁ = 1, μ₂ = 4, σ₂ = 1.5,以及混合权重 w₁ = 0.7 和 w₂ = 0.3。
# Parameters for the two univariate normal distributions
mean1, std1 = -1, 1
mean2, std2 = 4, 1.5
w1, w2 = 0.7, 0.3
plot_mixture(mean1, std1, mean2, std2, w1, w2)

两个单变量高斯分布的混合模型
虚线表示各个正态分布,实线黑色线条展示了最终的混合。这张图展示了混合模型如何结合这两个分布,每个分布都有其自身的均值、标准差和在整体混合中的权重。
学习 GMM 参数
我们的目标是找到 GMM(均值、协方差和混合系数)的参数,这些参数能最好地解释观察到的数据。为此,我们首先定义给定输入数据的模型的似然性。
对于一个具有 K 个成分的 GMM 和一个数据集 X = {x₁, …, xₙ},其中有 n 个数据点,似然函数 L 由每个数据点的概率密度的乘积给出,如 GMM 所定义:

GMM 模型的似然性
其中 θ 表示模型的所有参数(均值、方差和混合权重)。
在实际应用中,处理对数似然更为方便,因为对于大型数据集,概率的乘积可能会导致数值下溢。对数似然由以下公式给出:

GMM 的参数可以通过最大化相对于 θ 的对数似然函数来估计。然而,由于以下原因,我们不能直接应用 最大似然估计(MLE)来估计 GMM 的参数:
-
对数似然函数高度非线性,且在分析上很复杂。
-
模型具有潜在变量(混合权重),这些变量在数据中不可直接观察。
为了克服这些问题,通常使用期望最大化(EM)算法。该算法将在下一节中描述。
期望最大化(EM)
EM 算法是一个强大的方法,用于在依赖于未观察的潜在变量的统计模型中寻找最大似然估计。
该算法从随机初始化模型参数开始。然后在两个步骤之间迭代:
-
期望步骤(E 步骤):计算模型的期望对数似然,考虑到观测数据和当前模型参数的估计。此步骤涉及对潜在变量概率的估计。
-
最大化步骤(M 步骤):更新模型的参数,以最大化观察数据的对数似然,给定 E 步骤中估计的潜在变量。
这两个步骤会重复进行,直到收敛,通常通过对数似然的变化阈值或最大迭代次数来确定。
让我们制定用于估计高斯混合模型参数的 EM 步骤中的更新方程。在 GMM 中,潜在变量表示每个数据点的未知成分隶属关系。设 Zᵢ 为随机变量,表示数据点 xᵢ 是从哪个成分生成的。Zᵢ 可以取 {1, …, K} 中的一个值,对应于 K 个成分。
E 步骤
在 E 步骤中,我们计算给定当前模型参数估计的潜在变量 Zᵢ 的概率分布。换句话说,我们计算每个数据点在每个高斯成分中的隶属概率。
Zᵢ = k 的概率,即 xᵢ 属于 k -th 成分的概率,可以使用贝叶斯规则计算:

设该概率为变量γ(zᵢₖ)。因此,我们可以写成:

变量γ(zᵢₖ)通常被称为责任值,因为它们描述了每个成分对每个观测值的责任程度。这些责任值作为关于潜在变量缺失信息的代理。
相对于潜在变量分布的期望对数似然现在可以写为:

函数Q是所有数据点在每个高斯成分下对数似然的加权和,其中权重是责任值。请注意,Q与前面显示的对数似然函数l(θ|X)不同。对数似然l(θ|X)表示在混合模型下观测数据的可能性,没有明确考虑潜在变量,而Q表示对观测数据和估计的潜在变量分布的期望对数似然。
M 步
在 M 步中,我们更新 GMM(均值、协方差和混合权重)的参数θ,以最大化使用 E 步计算的责任值的期望似然Q(θ)。
参数更新如下:
- 更新每个成分的均值:

即,第k个成分的新均值是所有数据点的加权平均,其中权重是这些点属于成分k的概率。
此更新公式可以通过最大化期望对数似然函数Q相对于均值μₖ来推导。我将在这里展示单变量高斯分布情况的证明。
证明:
单变量高斯分布的期望对数似然为:

对该函数关于μₖ求导并设其为 0 可得:

2. 更新每个成分的协方差:

即,第k个成分的新协方差是每个数据点与该成分均值的平方偏差的加权平均,其中权重是分配给该成分的点的概率。
对于单变量正态分布,此更新简化为:

3. 更新混合权重:

即,第k个成分的新权重是点属于该成分的总概率,经过点数n的归一化。
重复这两个步骤可以确保收敛到似然函数的局部最大值。由于最终达到的最优值依赖于初始的随机参数值,因此通常的做法是多次运行 EM 算法,使用不同的随机初始化,并保留获得最高似然的模型。
Python 中的实现
我们现在将实现 EM 算法,以从给定数据集中估计两个单变量高斯分布的 GMM 参数。
我们从导入所需的库开始:
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import norm
np.random.seed(0) # for reproducibility
接下来,让我们编写一个函数来初始化 GMM 的参数:
def init_params(x):
"""Initialize the parameters for the GMM
"""
# Randomly initialize the means to points from the dataset
mean1, mean2 = np.random.choice(x, 2, replace=False)
# Initialize the standard deviations to 1
std1, std2 = 1, 1
# Initialize the mixing weights uniformly
w1, w2 = 0.5, 0.5
return mean1, mean2, std1, std2, w1, w2
均值从数据集中随机数据点初始化,标准差设置为 1,混合权重均匀设置为 0.5。
现在我们实现 E 步,其中我们计算每个数据点属于每个高斯组件的责任(概率):
def e_step(x, mean1, std1, mean2, std2, w1, w2):
"""E-Step: Compute the responsibilities
"""
# Compute the densities of the points under the two normal distributions
prob1 = norm(mean1, std1).pdf(x) * w1
prob2 = norm(mean2, std2).pdf(x) * w2
# Normalize the probabilities
prob_sum = prob1 + prob2
prob1 /= prob_sum
prob2 /= prob_sum
return prob1, prob2
在 M 步中,我们根据 E 步计算的责任来更新模型参数:
def m_step(x, prob1, prob2):
"""M-Step: Update the GMM parameters
"""
# Update means
mean1 = np.dot(prob1, x) / np.sum(prob1)
mean2 = np.dot(prob2, x) / np.sum(prob2)
# Update standard deviations
std1 = np.sqrt(np.dot(prob1, (x - mean1)**2) / np.sum(prob1))
std2 = np.sqrt(np.dot(prob2, (x - mean2)**2) / np.sum(prob2))
# Update mixing weights
w1 = np.sum(prob1) / len(x)
w2 = 1 - w1
return mean1, std1, mean2, std2, w1, w2
最后,我们编写主函数来运行 EM 算法,在 E 步和 M 步之间进行迭代,直到指定的迭代次数:
def gmm_em(x, max_iter=100):
"""Gaussian mixture model estimation using Expectation-Maximization
"""
mean1, mean2, std1, std2, w1, w2 = init_params(x)
for i in range(max_iter):
print(f'Iteration {i}: μ1 = {mean1:.3f}, σ1 = {std1:.3f}, μ2 = {mean2:.3f}, σ2 = {std2:.3f}, '
f'w1 = {w1:.3f}, w2 = {w2:.3f}')
prob1, prob2 = e_step(x, mean1, std1, mean2, std2, w1, w2)
mean1, std1, mean2, std2, w1, w2 = m_step(x, prob1, prob2)
return mean1, std1, mean2, std2, w1, w2
为了测试我们的实现,我们将创建一个合成数据集,通过从已知混合分布中采样数据,并使用 EM 算法估计分布的参数,然后将估计的参数与原始参数进行比较。
首先,让我们编写一个函数,从两个单变量正态分布的混合中采样数据:
def sample_data(mean1, std1, mean2, std2, w1, w2, n_samples):
"""Sample random data from a mixture of two Gaussian distribution.
"""
x = np.zeros(n_samples)
for i in range(n_samples):
# Choose distribution based on mixing weights
if np.random.rand() < w1:
# Sample from the first distribution
x[i] = np.random.normal(mean1, std1)
else:
# Sample from the second distribution
x[i] = np.random.normal(mean2, std2)
return x
现在我们将使用这个函数从之前定义的混合分布中采样 1,000 个数据点:
# Parameters for the two univariate normal distributions
mean1, std1 = -1, 1
mean2, std2 = 4, 1.5
w1, w2 = 0.7, 0.3
x = sample_data(mean1, std1, mean2, std2, w1, w2, n_samples=1000)
我们现在可以在这个数据集上运行 EM 算法:
final_dist_params = gmm_em(x, max_iter=30)
我们得到如下输出:
Iteration 0: μ1 = -1.311, σ1 = 1.000, μ2 = 0.239, σ2 = 1.000, w1 = 0.500, w2 = 0.500
Iteration 1: μ1 = -1.442, σ1 = 0.898, μ2 = 2.232, σ2 = 2.521, w1 = 0.427, w2 = 0.573
Iteration 2: μ1 = -1.306, σ1 = 0.837, μ2 = 2.410, σ2 = 2.577, w1 = 0.470, w2 = 0.530
Iteration 3: μ1 = -1.254, σ1 = 0.835, μ2 = 2.572, σ2 = 2.559, w1 = 0.499, w2 = 0.501
...
Iteration 27: μ1 = -1.031, σ1 = 1.033, μ2 = 4.180, σ2 = 1.371, w1 = 0.675, w2 = 0.325
Iteration 28: μ1 = -1.031, σ1 = 1.033, μ2 = 4.181, σ2 = 1.370, w1 = 0.675, w2 = 0.325
Iteration 29: μ1 = -1.031, σ1 = 1.033, μ2 = 4.181, σ2 = 1.370, w1 = 0.675, w2 = 0.325
算法已收敛到接近原始混合参数的参数:μ₁ = -1.031,σ₁ = 1.033,μ₂ = 4.181,σ₂ = 1.370,以及混合权重 w₁ = 0.675 和 w₂ = 0.325。
让我们使用之前编写的 plot_mixture() 函数绘制最终分布。我们还将更新该函数,以绘制采样数据的直方图:
def plot_mixture(x, mean1, std1, mean2, std2, w1, w2):
# Plot an histogram of the input data
sns.histplot(x, bins=20, kde=True, stat='density', linewidth=0.5, color='gray')
# Generate points for the x-axis
x_ = np.linspace(-5, 10, 1000)
# Calculate the individual nomral distributions
normal1 = norm.pdf(x_, mean1, std1)
normal2 = norm.pdf(x_, mean2, std2)
# Calculate the mixture
mixture = w1 * normal1 + w2 * normal2
# Plot the results
plt.plot(x_, normal1, label='Normal distribution 1', linestyle='--')
plt.plot(x_, normal2, label='Normal distribution 2', linestyle='--')
plt.plot(x_, mixture, label='Mixture model', color='black')
plt.xlabel('$x$')
plt.ylabel('$p(x)$')
plt.legend()
plot_mixture(x, *final_dist_params)
结果显示在下图中:

使用 EM 算法从数据集中估计的混合分布
如所示,估计的分布与数据点的直方图紧密对齐。
练习:扩展上述代码以处理多变量正态分布和任意数量的分布 K。
提示:你可以使用函数
scipy.stats.multivariate_normal来计算多变量正态分布的 PDF。
Scikit-Learn 中的 GMM
Scikit-Learn 在类 [sklearn.mixture.GaussianMixture](https://scikit-learn.org/stable/modules/generated/sklearn.mixture.GaussianMixture.html) 中提供了高斯混合模型的实现。该类的重要参数包括:
-
n_components:混合组件的数量(默认为 1)。 -
covariance_type:要使用的协方差参数类型。可以是以下选项之一:-
'full':每个组件有自己的协方差矩阵。 -
'tied':所有组件共享相同的协方差矩阵。 -
'diag':每个组件都有自己的协方差矩阵,该矩阵必须是对角的。 -
'spherical':每个组件有自己的单一方差。
-
-
tol:收敛阈值。当对数似然的平均改善低于此阈值时,EM 算法将停止(默认为 0.001)。 -
max_iter:执行的 EM 迭代次数(默认为 100)。 -
n_init:执行的随机初始化次数(默认为 1)。 -
init_params:用于初始化模型参数的方法。可以选择以下选项之一:'kmeans':参数使用 k-均值初始化(默认)。'k-means++':参数使用 k-均值++ 初始化。'random':参数被随机初始化。'random_from_data':初始均值从给定的数据点中随机选择。
此外,此类提供了以下属性:
-
weights_:混合权重。 -
means_:每个组件的均值。 -
covariances_:每个组件的协方差。 -
converged_:一个布尔值,指示 EM 算法是否已达到收敛。 -
n_iter_:EM 算法达到收敛所用的步骤数。
注意,与 Scikit-Learn 中的其他聚类算法不同,此类不提供 labels_ 属性。因此,要获取数据点的簇分配,您需要在拟合模型上调用 predict() 方法(或调用 fit_predict())。
例如,使用此类对以下数据集进行聚类,该数据集包含两个椭圆形簇和一个球形簇:
from sklearn.datasets import make_blobs
X, y = make_blobs(n_samples=500, centers=[(0, 0), (4, 4)], random_state=0)
# Apply a linear transformation to make the blobs elliptical
transformation = [[0.6, -0.6], [-0.2, 0.8]]
X = np.dot(X, transformation)
# Add another spherical blob
X2, y2 = make_blobs(n_samples=150, centers=[(-2, -2)], cluster_std=0.5, random_state=0)
X = np.vstack((X, X2))
让我们绘制数据集:
def plot_data(X):
sns.scatterplot(x=X[:, 0], y=X[:, 1], edgecolor='k', legend=False)
plt.xlabel('$x_1$')
plt.ylabel('$x_2$')
plot_data(X)
接下来,我们用 n_components=3 实例化 GMM 类,并调用其 fit_predict() 方法以获取簇分配:
from sklearn.mixture import GaussianMixture
gmm = GaussianMixture(n_components=3)
labels = gmm.fit_predict(X)
我们可以检查 EM 算法收敛所需的迭代次数:
print(gmm.n_iter_)
2
在这种情况下,EM 算法只需两次迭代即可收敛。
我们还可以检查估计的 GMM 参数:
print('Weights:', gmm.weights_)
print('Means:\n', gmm.means_)
print('Covariances:\n', gmm.covariances_)
Weights: [0.23077331 0.38468283 0.38454386]
Means:
[[-2.01578902 -1.95662033]
[-0.03230299 0.03527593]
[ 1.56421574 0.80307925]]
Covariances:
[[[ 0.254315 -0.01588303]
[-0.01588303 0.24474151]]
[[ 0.41202765 -0.53078979]
[-0.53078979 0.99966631]]
[[ 0.35577946 -0.48222654]
[-0.48222654 0.98318187]]]
我们可以看到,估计的权重非常接近三个簇的原始比例,球形簇的均值和方差也非常接近其原始参数。
让我们绘制簇:
def plot_clusters(X, labels):
sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=labels, palette='tab10', edgecolor='k', legend=False)
plt.xlabel('$x_1$')
plt.ylabel('$x_2$')
plot_clusters(X, labels)

GMM 聚类的结果
GMM 已正确识别出所有三个簇。
此外,我们可以使用方法 predict_proba() 获取每个数据点在每个簇中的归属概率。
prob = gmm.predict_proba(X)
例如,数据集中的第一个点非常可能属于绿色簇:
print('x =', X[0])
print('prob =', prob[0])
x = [ 2.41692591 -0.07769481]
prob = [3.11052582e-21 8.85973054e-10 9.99999999e-01]
我们可以通过将每个点的大小与其归属簇的概率成比例来可视化这些概率:
sizes = prob.max(axis=1)
sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=labels, size=sizes, palette='tab10', edgecolor='k', legend=False)
plt.xlabel('$x_1$')
plt.ylabel('$x_2$')
plt.savefig('figures/elliptical_blobs_gmm_prob.pdf')

簇分配的概率
我们可以看到,位于两个椭圆簇边界上的点的概率较低。概率显著低的数据点(例如,低于预定义的阈值)可以被识别为异常值或离群点。
为了进行比较,下图展示了应用于相同数据集的其他聚类算法的结果:

可以看出,其他聚类算法未能正确识别椭圆形簇。
模型评估
对数似然度是评估 GMM 的主要度量。它在训练过程中也被监控以检查 EM 算法的收敛性。然而,有时我们需要比较具有不同组件数量或不同协方差结构的模型。
为此,我们有两个额外的度量,它们在模型复杂性(参数数量)与拟合优度(由对数似然度表示)之间进行平衡:
- 赤池信息准则 (AIC):

其中:
-
p 是模型中的参数数量(包括所有均值、协方差和混合权重)。
-
L 是模型的最大似然估计(具有最佳参数值的模型的似然度)。
较低的 AIC 值表示更好的模型。AIC 奖励那些对数据拟合良好的模型,但也惩罚参数更多的模型。
2. 贝叶斯信息准则 (BIC):

其中 p 和 L 的定义如前所述,n 是数据点的数量。
类似于 AIC,BIC 平衡模型拟合和复杂性,但对参数更多的模型处以更大的惩罚,因为 p 被乘以 log(n) 而不是 2。
在 Scikit-Learn 中,你可以使用 GMM 类的 aic() 和 bic() 方法计算这些度量。例如,blobs 数据集的 GMM 聚类的 AIC 和 BIC 值是:
print(f'AIC = {gmm.aic(X):.3f}')
print(f'BIC = {gmm.bic(X):.3f}')
AIC = 4061.318
BIC = 4110.565
这些度量可以用来通过将不同组件数的 GMM 拟合到数据集中,然后选择具有最低 AIC 或 BIC 值的模型来寻找最优的组件数。
总结
让我们总结一下 GMM 相对于其他聚类算法的优缺点:
优点:
-
与假设球形簇的 k 均值不同,GMM 由于协方差成分,可以适应椭圆形的形状。这使得 GMM 能够捕捉更广泛的簇形状。
-
可以处理具有不同大小的簇,因为它们使用协方差矩阵和混合系数,考虑了每个簇的扩展和比例。
-
GMM 提供了每个点属于每个簇的概率(软分配),这在理解数据方面可能更具信息性。
-
可以处理重叠簇,因为它根据概率而不是硬性边界将数据点分配到簇中。
-
聚类结果易于解释,因为每个簇由具有特定参数的高斯分布表示。
-
除了聚类,GMM 还可以用于密度估计和异常检测。
缺点:
-
需要提前指定组件(簇)的数量。
-
假设每个簇中的数据遵循高斯分布,这对实际数据可能并不总是有效的假设。
-
当簇中仅包含少量数据点时,模型可能效果不好,因为模型依赖于足够的数据来准确估计每个组件的参数。
-
聚类结果可能对初始参数选择非常敏感。
-
GMM 中使用的 EM 算法可能会陷入局部最优,并且收敛速度可能较慢。
-
条件不良的协方差矩阵(即,接近奇异或具有非常高条件数的矩阵)可能导致 EM 计算过程中的数值不稳定。
-
计算上比简单算法如k-均值更为复杂,尤其是在数据集较大或组件数量较高时。
感谢阅读!
所有图像均由作者提供,除非另有说明。
你可以在我的 GitHub 上找到本文的代码示例:github.com/roiyeho/medium/tree/main/gmm
GenAI 提升 NLP 系统 I:生成合成数据的工具
使用 GenAI 生成和扩充合成数据的实验,基于 Python 的提示工程
·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 9 月 29 日
--

机器学习(ML)的一个主要挑战是数据不平衡及其引入的模型偏差。随着强大生成式人工智能(GenAI)模型的出现,我们可以轻松地用合成数据来扩充不平衡的训练数据,特别是在自然语言处理(NLP)任务中。因此,我们可以使用经典的 ML 算法来训练模型,以在深度学习模型或直接使用大语言模型(LLMs)不可行的情况下获得更好的性能,例如计算成本、内存、基础设施的可用性或模型可解释性。此外,尽管 LLMs 展现出了极大的效能,但我们仍然没有完全信任它们。然而,我们可以利用 LLMs 来辅助我们的数据专业工作,克服在构建 NLP 系统时遇到的障碍。
在这篇文章中,我展示了如何利用 GenAI 和 Python 生成合成数据来改善不平衡数据集中少数类的模型性能,以及如何通过迭代优化提示词来生成期望的结果。
平衡之道

图片来源:Tingey Injury Law Firm 于 Unsplash
长话短说,机器学习模型需要足够的示例来学习模式和准确预测。如果数据包含较少的示例,模型则无法泛化,表现也会较差。在这种情况下,模型可能会对样本较多的类别过拟合,而对样本较少的类别欠拟合。为了应对不平衡的数据,我们传统上使用统计采样方法,如过采样或欠采样,通常使用SMOTE。
为什么平衡 NLP 中的数据很困难?
在 NLP 中,有几个分类任务的数据是不平衡的,我们不得不采用欠采样来克服这一挑战。欠采样的根本问题是信息丢失。尽管 SMOTE 为数值数据集实施了高效的过采样策略,但它不适用于文本或任何形式的文本向量化表示或嵌入。这是因为数值数据集可以通过随机采样策略轻松复制,结合特征的概率分布。对于文本而言,由于难以使用这些方法捕捉句法和语义的多样性,因此生成嵌入模式并不简单。
生成式 AI:用于 NLP 的现代合成数据工具
今年,GenAI 的力量被释放,提示工程正在革新我们的工作方式,AI 领导者正在重新思考他们的操作策略。下面是 GenAI 如何通过提示工程为 NLP 系统提供合成数据的演示。
使用案例:用合成数据丰富情感数据集
- 背景
在我之前关于比较使用 Keras 嵌入层与 Word2vec 嵌入的多分类文本分类和使用 KerasTuner 微调 ANNs 的博客中,我展示了在情感—‘喜悦’和‘悲伤’作为目标类别—上表现良好的模型。这是因为它们在数据集中样本数量相对较多。以下是训练数据的分布,其中‘喜悦’和‘悲伤’(多数类别)的示例数量明显高于‘愤怒’、‘爱’、‘惊讶’和‘恐惧’(少数类别):
从上图可以得出结论,这些数据在‘喜悦’和‘悲伤’之外的情感上高度不平衡。‘愤怒’和‘恐惧’的样本数量相似,而在这些数据上训练出的模型表现略逊于‘喜悦’和‘悲伤’。表现最差的是‘爱’和‘惊讶’,在最佳选择模型中,其 f1 分数分别为 78% 和 71%。以下是测试数据集的结果供参考。

模型性能的混淆矩阵(没有增强合成数据)| 图片来源:作者
表现最好的模型(笔记本 这里)是一个经过微调的模型,没有使用 word2vec,而是直接将令牌表示传递给 Keras 嵌入层。显然,‘惊讶’(类别 5)的精确度为 84%,这是不错的,但召回率为 62%,这相当低。较高的精确度意味着分类器预测情感时更有可能是正确的。换句话说,它最小化了假阳性。另一方面,较高的召回率意味着分类器更擅长捕捉目标情感的所有实例,最小化假阴性。62%的召回率意味着 62%的所有带有‘惊讶’情感的文本被正确识别。在实际应用中,建议在精确度和召回率之间取得平衡,根据业务目标优先考虑其中一个。在这里,精确度和召回率的分数不平衡。
目标:提高对情感‘惊讶’的召回率,该情感在这个不平衡的数据集中属于小类,使用由 GPT 生成的合成数据。
2. 提示工程和合成数据生成
在这个实验中,我将重新编写 572 个‘惊讶’样本,并对其进行扩充,将该情感的样本总数增加到 1200 个。
提示工程:
提示工程是为 AI 模型制作指令的艺术,使其能够产生最准确的回应。在这个用例中,我将接下来制作一个提示,使我能够生成合成数据。不仅如此,我还将提供示例和输出结构,以便我可以轻松地将合成数据解析到 Pandas 数据框中。
这个实验使用了以下提示:
这是关于这个实验提示过程的一些经验教训:
-
这是第十五版本左右的提示结构。
-
我要求“生成 X 个样本”而不是重写。由于在生成前 15-20 个合成样本后,模型开始重复使用相同的句法风格,它没有效果特别好,模型停止生成各种句子结构。
-
请注意我如何指示 GPT-4 移除标点符号并转为小写。这对于文本清理任务非常有用。
-
我尝试说“生成带有情感‘恐惧’的文本样本”,GPT-4 方便地在所有样本中添加了‘恐惧’一词。我在尝试生成样本时不得不使用‘潜在的’或‘嵌入的’情感,而不是重写。
-
我已经尝试在指令中添加了‘使用同义词’,但在某些文本中,同义词的匹配效果不好。所以我去掉了这个要求,并添加了保持情感的条款,因为在一些文本中,内容中的情感对我来说显得模糊。
解决令牌限制和完整代码
像每个人一样,我也有相同的令牌限制来提示 GPT-4。 我做了什么呢?我将数据集进行了分块处理,并运行了所有数据点。以下是完整的代码:
关于这部分实验的提示过程,我学到了几个经验教训:
-
我遇到了“503 — 引擎当前过载,请稍后再试”的错误,未给代码一些休息时间(使用 time.sleep())
-
我指示模型生成‘;’分隔的样本,以便我可以轻松地将它们解析到数据框中。
-
我将上述说明放在了提示的第一行。 “不要使用任何标点符号”的说明被放在了它所在的位置。这导致生成的样本完全没有分隔。(新手错误,我知道🙄)
现在我已理顺了合成数据生成和增强,这里是调整后的分类器的更新结果(训练于此):

带有增强合成数据的模型性能混淆矩阵 | 图片来源:作者
显然,对于情感‘惊讶’(类别 5),召回率从 62%提高到 79%,但精确率从 84%下降到 72%。然而,平衡精确率和召回率是重要的,这显然比没有合成数据的模型要好。实际上,使用这种策略,f1 分数从 71%提高到 75%。整体模型性能保持不变,但在增强其他类别时可能会得到提升——这是未来的实验方向。
结论
这只是一个简要演示,说明我们如何使用生成式 AI 为基于 NLP 的用例生成合成数据,这在其他情况下可能是一个更复杂的任务。
然而,这里有一个关键的挑战。GPT-4 生成的数据可能会在文本中引入模式或偏差,导致模型在合成数据的模式上过拟合,从而阻碍分类器的性能。因此,对生成数据和整体性能的质量测试是至关重要的。
尽管有这个限制,生成式 AI 在加速生成合成数据以平衡不平衡数据集中的类别方面非常有帮助。
希望你喜欢这个博客🙂。以下是我的 GitHub 上的Google Colab 笔记本。
感谢访问!
我的链接: Medium | LinkedIn | GitHub
GenAIOps:发展中的 MLOps 框架
生成式 AI 需要新的部署和监控能力
·
关注 发表在 Towards Data Science ·14 min read·Jul 18, 2023
--
图片由作者提供 — David Sweenor
早在 2019 年,我曾发表过一篇 LinkedIn 博客,标题为为什么你需要 ML Ops 来实现成功的创新。快进到今天,将分析、机器学习(ML)和人工智能(AI)模型(或更确切地说,系统)投入实际应用仍然是许多组织面临的挑战。然而,值得注意的是,技术已经发展,新公司也应运而生,帮助解决在生产环境中部署、监控和更新模型的挑战。不过,随着生成性 AI 的发展,如 OpenAI 的GPT-4、Google 的PaLM 2、Meta 的LLaMA以及GitHub Copilot,组织们竞相了解 LLMs 的价值、成本、实施时间表和相关风险。由于我们才刚刚开始这段旅程,我认为大多数组织尚未做好精细调优、部署、监控和维护 LLMs 的准备,因此应谨慎行事。
什么是 MLOps?
机器学习操作(即 MLOps)可以定义为:
ML Ops 是一个跨职能的、协作的、持续的过程,专注于通过将统计学、数据科学和机器学习模型作为可重用的、高可用的软件工件进行管理,从而使数据科学操作化,并通过可重复的部署过程来实现。它涵盖了模型推理、可扩展性、维护、审计和治理等独特的管理方面,以及对生产环境中模型的持续监控,以确保它们在基础条件变化时仍能提供积极的业务价值。[1]
现在我们对 MLOps 有了清晰的定义,让我们讨论一下它对组织的重要性。
为什么 MLOps 重要?
在今天的算法驱动的商业环境中,MLOps 的重要性不容忽视。随着组织越来越依赖复杂的机器学习模型来推动日常决策和提高运营效率,部署、管理、监控和更新这些模型的需求变得尤为重要。MLOps 提供了一套框架和流程,以便数据科学家和计算机科学家(负责开发模型)与 IT 运维团队(负责部署、管理和维护模型)之间进行协作,确保模型可靠、最新,并能为业务带来价值。
MLOps 的关键能力
广义上讲,MLOps 功能上包括自动化机器学习工作流、模型版本管理、模型监控和模型治理。
●自动化工作流简化了训练、验证和部署模型的过程;减少了人工操作并提高了速度。
● 模型版本控制 允许跟踪变化并维护模型迭代的注册表。
● 模型监控 对确保模型在生产系统中按预期表现至关重要。
● 模型治理 提供合规性,以满足法规和组织政策。
这些能力共同使组织能够大规模地将 ML 和 AI 投入运营,为组织带来业务价值和竞争优势。
MLOps:指标和 KPI
为确保模型在生产系统中按预期表现并提供最佳预测,有几种类型的指标和关键绩效指标(KPI)用于跟踪其效果。与数据科学家交谈时,他们通常会强调以下指标:
● 模型性能指标:这些是衡量模型预测性能的指标。它们可以包括准确率、精确率、召回率、F1 分数、ROC 曲线下面积(AUC-ROC)、平均绝对误差(MAE)、均方误差(MSE)等。选择指标取决于问题的类型(分类、回归等)和业务背景。
● 数据漂移:这衡量生产工作流中输入数据与模型训练数据的偏差程度。显著的数据漂移可能表明模型的预测可能随着时间变得不那么可靠。我们在那个小小的“突发事件”COVID 中看到了一个很好的例子。消费者习惯和商业规范一夜之间发生了变化,导致每个人的模型都崩溃了!
● 模型漂移:类似于数据漂移,这衡量模型性能(通常是下降)随时间变化的程度,而不是衡量数据分布偏离常态的程度。如果基础数据分布发生变化,可能会导致模型假设变得不那么准确。
● 预测分布:跟踪模型预测的分布可以帮助检测异常。例如,如果一个二分类模型突然开始预测比平时多得多的正例,这可能表明存在问题。这些通常与业务指标最为接近。
● 资源使用:IT 资源使用包括 CPU 使用率、内存使用率和延迟等指标。这些指标对于确保模型在系统的基础设施和架构约束内高效运行非常重要。
● 业务指标:所有指标中最重要的,这些指标衡量模型对业务结果的影响。它们可能包括收入、客户流失率、转化率等指标。这些指标有助于评估模型是否提供了预期的业务价值。
现在我们已经对 MLOps 有了高层次的理解,知道了它的重要性、关键能力和指标,这与生成式 AI 有什么关系呢?
生成式 AI:主要的跨职能用例
在生成式人工智能成为主流之前,组织主要实施的是针对结构化和半结构化数据的 AI 系统。这些系统主要以数字为训练基础,生成数字输出——预测、概率和分组分配(比如细分和聚类)。换句话说,我们会用历史数字数据如交易、行为、人口统计、技术、公司、地理空间和机器生成的数据来训练我们的 AI 模型——并输出流失、响应或与优惠互动的可能性。并不是说我们没有使用文本、音频或视频数据——我们有;情感分析、设备维护日志等;但这些用例远远不如基于数字的方法普遍。生成式人工智能具有一组新的能力,使组织能够利用多年来基本上忽视的数据——文本、音频和视频数据。
用途和应用有很多,但我总结了迄今为止生成式人工智能的关键跨职能用例。
内容生成
生成式人工智能可以生成类人质量的内容,包括音频、视频/图片和文本。
● 音频内容生成:生成式人工智能可以制作适用于社交媒体平台如 YouTube 的音频轨道,或为你的书面内容添加 AI 驱动的配音,增强多媒体体验。事实上,我的前两本 TinyTechGuides 在 Google Play 上的配音完全由 AI 生成。我可以为 AI 朗读的书籍选择口音、性别、年龄、语速以及其他几个关键属性。查看这里的 AI 朗读有声书。
● 文本内容生成:这可能是目前最受欢迎的生成式人工智能形式,从撰写博客文章、社交媒体更新、产品描述、草拟电子邮件、客户信件到 RFP 提案,生成式人工智能可以轻松生成各种文本内容,为企业节省大量时间和资源。不过请注意,内容虽然生成并听起来权威,并不意味着它在事实上的准确性。
● 图像和视频生成:我们已经看到这种技术在好莱坞慢慢成熟,例如通过 AI 生成的《星球大战》角色到在最新的《夺宝奇兵》电影中 去老化哈里森·福特,AI 可以创建逼真的图像和电影。生成式 AI 可以通过为广告、演示文稿和博客生成内容来加速创意服务。我们已经看到像 Adobe 和 Canva 等公司在创意服务方面做出了共同努力。
● 软件代码生成:生成式 AI 可以生成软件代码(如 Python)和 SQL,这些代码可以集成到分析和 BI 系统中,以及 AI 应用本身。实际上,微软正在继续研究使用 ‘教科书’来训练 LLMs 以创建更准确的软件代码。
内容总结与个性化
除了为公司创建全新的现实内容,生成式 AI 还可以用来总结和个性化内容。除了 ChatGPT 外,像 Writer、Jasper 和 Grammarly 这样的公司也在针对营销职能和组织进行内容总结和个性化。这将允许营销组织花时间制定周密的内容日历和流程,然后这些不同的服务可以被微调,以生成似乎无限的授权内容变体,从而可以在合适的时间通过合适的渠道传递给合适的人。
内容发现与问答
生成式 AI 获得关注的第三个领域是内容发现和问答。从数据与分析软件的角度来看,各种供应商正在将生成式 AI 功能纳入其中,以创建更自然的界面(以简单语言)来促进组织内部新数据集的自动发现,以及编写现有数据集的查询和公式。这将使非专家的商业智能(BI)用户能够提出简单的问题,如“我在东北地区的销售额是多少?” 然后深入挖掘并提出越来越详细的问题。BI 和分析工具根据他们的查询自动生成相关的图表和图形。
我们还看到这一点在医疗行业和法律行业的使用增加。在医疗领域,生成式 AI 可以筛查大量数据,帮助总结医生笔记,并通过聊天机器人、电子邮件等方式个性化与患者的沟通和往来。虽然对仅仅将生成式 AI 用于诊断功能有所保留,但有了人的参与,我们会看到这种应用的增加。我们还将看到生成式 AI 在法律行业中的使用增加。作为一个以文件为中心的行业,生成式 AI 能够快速找到合同中的关键条款,帮助法律研究,总结合同,并为律师创建定制的法律文件。麦肯锡称之为法律副驾驶。
现在我们了解了与生成式 AI 相关的主要用途,让我们转向关键问题。
生成式 AI:主要挑战与考虑因素
生成式 AI 尽管前景广阔,但也带来了自己的障碍和潜在陷阱。组织在将生成式 AI 技术整合到业务流程中之前,必须仔细考虑几个因素。主要挑战包括:
● 准确性问题(幻觉):大型语言模型(LLMs)常常会生成误导性或完全虚假的信息。这些回答可能看起来很可信,但完全是捏造的。企业可以建立什么样的保护措施来检测和防止这些虚假信息?
● 偏见:组织必须了解模型中的偏见来源,并实施缓解策略来加以控制。公司制定了哪些政策或法律要求来应对潜在的系统性偏见?
● 透明度缺失:对于许多应用,尤其是在金融服务、保险和医疗等行业,模型透明度通常是业务要求。然而,LLMs 本质上并不具有可解释性或可预测性,导致“幻觉”和其他潜在的误差。如果您的企业需要满足审计师或监管机构的要求,您必须问自己,我们是否可以使用 LLMs?
● 知识产权(IP)风险:用于训练许多基础 LLMs 的数据通常包括公开的可用信息——我们已经看到有关图像(例如HBR — 生成性 AI 存在知识产权问题)、音乐(The Verge — AI Drake 刚刚给 Google 设下了一个不可能的法律陷阱)和书籍(LA Times — Sara Silverman 和其他畅销书作者起诉 Meta 和 OpenAI 侵犯版权)的不当使用的诉讼。在许多情况下,训练过程不加选择地吸收所有可用数据,导致对 IP 暴露和版权侵权的潜在诉讼。这引出了一个问题,你的基础模型是用什么数据训练的,细调时又用了什么数据?
● 网络安全与欺诈:随着生成性 AI 服务的广泛使用,组织必须为恶意行为者的潜在滥用做好准备。生成性 AI 可以用来制造深度伪造以进行社会工程攻击。你的组织如何确保用于训练的数据没有被欺诈者和恶意行为者篡改?
● 环境影响:训练大规模 AI 模型需要大量计算资源,这会导致显著的能源消耗。这对环境有影响,因为所用的能源往往来自不可再生的来源,导致碳排放。对于已有环境、社会和治理(ESG)举措的组织来说,你的程序如何考虑 LLM 的使用?
现在,公司需要考虑的事情有很多,但主要的几个已经被涵盖。这引出了下一个问题,我们如何使生成性 AI 模型实现操作化?
GenAIOps:需要一套新的能力
现在我们对生成性 AI、关键用途、挑战和考虑因素有了更好的了解,接下来让我们看看 MLOps 框架必须如何演变——我将其称为 GenAIOps,据我所知,我是第一个提出这个术语的人。
让我们来看看创建大语言模型(LLMs)的高层次流程;该图形改编自基础模型的机遇与风险。
图 1.1:训练和部署 LLMs 的流程

训练和部署 LLMs 的流程——图片由作者、TinyTechGuides 创始人 David E Sweenor 提供
在上述过程中,我们看到数据被创建、收集、整理,然后模型被训练、调整和部署。鉴于此,对于一个全面的 GenAIOps 框架,应考虑哪些因素?
GenAIOps:清单
最近,斯坦福大学发布了一篇论文 基础模型提供者是否遵守了草案 EU AI 法案?阅读之后,我以此为灵感生成了下面的 GenAIOps 框架检查表。
数据:
○ 用于训练模型的数据源有哪些?
○ 用于训练模型的数据是如何生成的?
○ 训练者是否获得了在该背景下使用数据的许可?
○ 数据中是否包含受版权保护的材料?
○ 数据中是否包含敏感或机密信息?
○ 数据中是否包含个人或 PII 数据?
○ 数据是否被污染?是否容易受到污染?
○ 数据是否真实,还是包含了 AI 生成的内容?
建模:
○ 模型有哪些限制?
○ 模型是否存在风险?
○ 模型性能基准是什么?
○ 如果需要,我们能否重新创建模型?
○ 模型是否透明?
○ 创建当前模型使用了哪些其他基础模型?
○ 训练模型使用了多少能源和计算资源?
部署:
○ 模型将部署在哪里?
○ 目标部署应用程序是否了解它们在使用生成型 AI?
○ 我们是否拥有满足审计员和监管机构要求的适当文档?
现在我们有了起点,让我们更详细地查看这些指标
GenAIOps:指标和过程考虑
以 MLOps 指标和 KPI 为起点,让我们审视这些如何应用于生成型 AI 指标。我们希望 GenAIOps 能帮助解决生成型 AI 特有的挑战,例如生成虚假、伪造、误导性或有偏见的内容。
模型性能指标
在生成型 AI 的背景下,组织如何衡量模型的性能?我怀疑大多数组织可能会使用商业上可用的预训练 LLM,并使用自己的数据来微调和适应他们的模型。
现在,确实存在与基于文本的 LLM 相关的技术性能指标,如 BLEU、ROUGE 或 METEOR,还有其他针对图像、音频和视频的指标,但我更关心的是生成虚假、伪造、误导性或有偏见的内容。组织可以采取哪些控制措施来监控、检测和缓解这些情况?
我们确实看到过宣传的泛滥,社交媒体巨头如 Facebook、Google 和 Twitter 未能实施一种一致且可靠的工具来防止这种情况发生。如果是这样,你们的组织如何衡量生成型 AI 模型的性能?会有事实检查员吗?图像、音频和视频呢?如何衡量这些模型的性能?
数据漂移
鉴于模型训练需要大量资源和时间,模型创建者将如何确定数据是否在漂移,从而需要新的模型?组织将如何理解他们的数据是否演变到需要重新校准模型的程度?对于数值数据,这相对简单,但我认为我们仍在学习如何处理文本、图像、音频和视频等非结构化数据。
假设我们能够创建一个机制来定期调整我们的模型,我们还应该有一个控制措施来检测数据漂移是否由于真实事件还是 AI 生成内容的扩散?在我的文章《AI 熵:AI 生成内容的恶性循环》中,我讨论了当你在 AI 上训练 AI 时,它会随着时间变得越来越笨。
模型漂移
类似于你对模型性能和数据漂移的担忧,你的组织将如何检测和理解模型性能是否开始漂移?你会有人工监控输出还是向最终用户发送调查问卷?也许更直接的方式是不仅要建立控制措施来监控模型的技术性能,而且你的公司应该始终跟踪模型输出。毫无疑问,你是用模型来解决特定的业务挑战,你需要监控业务指标。你是否看到购物车放弃率上升,客服电话增多或减少,或者客户满意度评级发生变化?
预测分布
我认为我们在追踪基于数值的预测方面拥有不错的工具和技术。但现在我们在处理文本、图像、音频和视频时,你如何看待监控预测分布的问题?我们能否理解模型在部署目标地是否生成了虚假的相关性?如果可以,你会采取什么措施来衡量这种现象?
资源使用
表面上,这似乎相对直接。然而,随着生成使用的增长,你的组织将需要建立一个系统来跟踪和管理它的使用。定价模型在生成 AI 领域仍在发展,因此我们需要小心。类似于我们在云数据仓库领域看到的情况,我们开始看到成本失控。因此,如果你的公司有基于使用的定价,你将如何建立财务控制和治理机制,以确保你的成本可预测且不会失控?
业务指标
我之前提到过这一点,但你可以采取的最重要的监控和控制措施与业务指标有关。你的公司需要时刻关注你的模型对业务的实际影响。如果你将其用于关键业务流程,你有哪些服务水平协议保证来确保正常运行?
偏见是任何 AI 模型面临的重大问题,但在生成性 AI 中可能更为严重。你如何检测模型输出是否存在偏见,并且是否在延续不平等现象?Tim O’Reilly 写了一篇很棒的博客,标题为 我们已经放出了瓶中的精灵,我鼓励你阅读。
从知识产权的角度来看,你如何保证专有、敏感或个人信息不会从你的组织中泄漏或流出?考虑到目前关于版权侵权的诉讼,这是你们组织需要面对的重要因素。你是否应该要求供应商保证这些信息不会出现在你的模型中,类似于 Adobe 的举措 (FastCompany — Adobe 如此自信其 Firefly 生成性 AI 不会侵犯版权,以至于愿意承担你的法律费用)?现在,他们愿意承担你的法律费用虽然很不错,但这会给你的公司带来什么声誉风险?如果你失去了客户的信任,你可能永远无法赢回他们。
最后,数据中毒无疑是一个热门话题。当你使用你们组织的数据来调整和微调模型时,如何确保数据不是有毒的?如何确保用于训练基础模型的数据没有被中毒?
总结
归根结底,这并不是为了提供解决 GenAIOps 的具体方法和指标,而是提出一系列组织在实施 LLM 之前需要考虑的问题。像任何事物一样,生成性 AI 具有帮助组织获得竞争优势的巨大潜力,但也存在一系列需要解决的挑战和风险。最终,GenAIOps 需要一套跨越采纳组织和提供 LLM 的供应商的原则和能力。用蜘蛛侠的话说,能力越大,责任越大。
如果你想了解更多关于人工智能的内容,可以查看我的书《人工智能: 让 AI 为你的业务服务的执行指南》。
[1]Sweenor, David, Steven Hillion, Dan Rope, Dev Kannabiran, Thomas Hill, 和 Michael O’Connell. 2020. 《ML Ops: 数据科学的操作化》。O’Reilly Media. www.oreilly.com/library/view/ml-ops-operationalizing/9781492074663/.
强化学习中的广义优势估计
原文:
towardsdatascience.com/generalized-advantage-estimation-in-reinforcement-learning-bf4a957f7975
策略梯度中的偏差与方差权衡
·发布于Towards Data Science ·6 分钟阅读·2023 年 3 月 27 日
--

作者提供的照片
策略梯度方法是强化学习中最广泛使用的学习算法之一。它们旨在优化参数化策略,并使用价值函数来帮助估计如何改进策略。
强化学习中的一个主要问题,特别是对于策略梯度方法,是行动与其对奖励的正面或负面影响之间的长时间延迟,这使得奖励估计极为困难。也就是说,强化学习研究者通常用从回合中获得的引导奖励或价值函数来估计长期奖励(回报),有时两者都会用。然而,这两种方法都有其缺点。前者的问题在于样本的高方差,后者则是在估计的价值函数中的高偏差。
在这篇文章中,我们将讨论广义优势估计(GAE),这是一类策略梯度估计器,它在保持可接受的偏差水平的同时,显著减少了方差。
以下内容假设你对策略梯度方法有基本了解。如果你是强化学习的新手,请查看我之前关于强化学习基础和算法概述的文章,以及对普通策略梯度的深入分析。
偏差与方差权衡
回忆一下我们在普通策略梯度中讨论的策略梯度的一般形式:

目标是找到使策略最大化V(θ)的参数θ。为此,我们通过沿着策略的梯度上升来寻找V(θ)的最大值,关于参数θ。

上述函数是普通策略梯度,完全依赖于回报 R(τ),即轨迹τ的奖励总和:

由于 R(τ)是从许多采样轨迹中估计得到的,普通的策略梯度方法容易受到高方差的影响,为了解决这个问题,研究人员发现了几种以更稳定的方式估计奖励的方法。
让我们将上述值函数扩展为逐步形式:

Ψt 是奖励的通用表示,可以是以下之一:

选项 1 和 2 完全依赖于采样奖励,而选项 3 则从奖励中减去基线。然而,它们在学习过程中仍然会受到高方差的困扰。实际上,选择Ψt = Aπ (st , at )已被证明能够产生最低的方差。这里,优势函数定义为:

它衡量动作是否比策略的默认行为更好或更差。注意,选择 5 和 6 在学习是在线策略时是等效的。在实践中,优势函数是不知道的,必须进行估计,这使得估计器有偏。GAE 进一步通过引入额外参数γ来折扣优势函数。我们将在下一节中深入讨论。
什么是广义优势估计(GAE)
在优势估计器的基础上,GAE 引入了一个参数γ,使我们可以通过降低对应于延迟效应的奖励的权重来减少方差。当然,这会引入偏差。这与贝尔曼方程中的折扣因子的思想类似,折扣因子降低了远期奖励的优先级。使用折扣后,优势函数表示为:

在实践中,我们需要估计值函数,这通常使用一个神经网络来预测特定状态的值(如果我们想估计 Q 值,则包括动作)。我们定义折扣γ的 TD 残差δt。注意,δt 可以视为优势Ψt 的估计:

回顾我们之前讨论的时序差分(TD)方法在这里。在这里,我们将 Q 值替换为值函数(更多细节请参阅贝尔曼方程)。折扣优势可以表示为:

在 TD 中,我们通过决定采样步骤的数量来权衡偏差和方差。采样的步骤越多,我们对带有偏差的价值函数估计器的依赖就越小,但方差也会增加。一个棘手的问题是找到 TD 中带来理想偏差-方差折衷的甜蜜点。这就是 GAE 发挥作用的地方——我们不再通过经验测试不同的步骤大小,而是直接使用这些 k 步估计器的指数加权平均。下面的方程展示了不同步骤大小 (k) 下的折扣优势函数。

采用指数加权平均,我们得到了GAE 的最终形式:

请注意,我们在这里引入了另一个参数 λ。当 λ = 0 时,GAE 本质上与 TD(0) 相同,但应用于策略优化的背景下。由于 heavily 依赖于估计的价值函数,因此具有较高的偏差。当 λ = 1 时,这就是带有基线的原始策略梯度,由于项的和,具有较高的方差。

实际应用中,我们设置 0 < λ < 1 来控制偏差和方差之间的折衷,就像 TD λ 中的 lambda 参数一样。
奖励塑形——GAE 的另一种解释
另一种解读 GAE 的方式是将环境视为奖励重塑的 MDP。假设我们有一个转换的奖励函数:

转换奖励的折扣和(即回报)正是我们上述讨论的 TD 残差:

本质上,策略梯度和最优策略保持不变,但我们的目标是最大化折扣奖励的总和。我们还可以增加一个“更陡”的折扣 λ,其中 0 ≤ λ ≤ 1,我们可以得到最终的 GAE 形式:

总结一下,我们可以将 GAE 视为奖励重塑 MDP 中相同的策略梯度,用一个陡峭的折扣 γλ 来削减来自长延迟的噪声。
结论
在本文中,我们讨论了 RL 中的偏差和方差折衷,特别是在策略梯度方法中。然后我们介绍了 GAE,一种通过以较小的偏差代价来降低方差的技术。GAE 是一个非常重要的估计器,并在许多高级算法中得到广泛应用,包括 VPG、TRPO 和 PPO。
在此,我想感谢你阅读本文,任何反馈都将不胜感激。
生成地理区域的 3D 网格
原文:
towardsdatascience.com/generate-a-3d-mesh-of-a-geographic-area-with-qgis-3844e3f7806a
从数字高程模型到 3D 网格
·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 3 月 14 日
--

照片由 Planet Volumes 提供,来源于 Unsplash
3D 网格可以用来表示地理数据,如地形、建筑物和其他结构。这些网格可以用于多种目的,如城市规划、环境分析或虚拟现实模拟。然而,创建地理区域的 3D 网格的过程并不总是简单明了,但本指南涵盖了所有必要的步骤。
介绍
要生成指定区域的 3D 网格,需要该区域的高程数据。这些数据存储在数字高程模型(DEM)中:
数字高程模型(DEM)是一种地理栅格文件,用于表示地表起伏
DEM 基本上是一个高度值的网格。如果你想了解更多信息,我在之前的文章中讨论了 DEMs。

DEM 的图形可视化。 NSIDC,CC BY 2.0,通过维基媒体共享资源
这些模型是通过以下一种或多种技术创建的:
-
LiDAR — 这种方法使用激光扫描仪测量地形表面的高度。激光发出光束,这些光束反射回地面,返回光的时间用于计算与表面的距离。
-
光测量 — 这种方法分析图像中表面特征投射的阴影,可以提供有关地形的高度和方向的信息。
-
插值 — 这种方法使用来自已知点的高程数据,如测量或 GPS 数据,来估算感兴趣区域其他点的高程。插值技术可以包括克里金插值、样条插值或三角测量等方法。
-
摄影测量法 — 这种方法使用立体对的卫星图像。高程数据是从两张图像之间的视角差异中得出的。摄影测量法通常是生成 DEM 的最准确的方法。
为了帮助你在决定下载哪个 DEM 时做出更好的选择,我们介绍了生成 DEM 的不同技术。我们将使用 QGIS 打开 DEM 并生成网格,QGIS 是用于处理地理数据的软件。
现在真正的问题来了:我在哪里可以下载 DEM?
DEM 数据库
DEM 是栅格文件,通常以 .tif 扩展名保存。对于大面积区域,数字高程模型会被分成几个栅格文件。DEM 的文件名通常包含有关其覆盖区域和分辨率的信息(例如 10m)。显然,10m 的 DEM 比相同区域的 30m DEM 细节更多。
一些数字高程模型的库包括:
-
Copernicus EU-DEM — 在这里你可以下载由 Copernicus 制作的欧洲 DEM。Copernicus 是欧盟空间计划的地球观测组成部分。它提供基于地球观测卫星和现场(非空间)数据的信息服务。
-
Copernicus PANDA — 这个平台允许你下载全球各地的 DEM。行星数据访问(PANDA)是用于搜索/查看/访问 Copernicus 地球观测(EO)产品的用户界面。
-
Tinitaly DEM — 对于我的意大利同仁,这里有由意大利国家地球物理与火山学研究所(INGV)制作的意大利表面 DEM。
-
UAHiRISE — 在这个页面上,你可以找到许多通过结合 HiRISE(摄影测量法)拍摄的立体对生成的 DEM。高分辨率成像科学实验(HiRISE)是搭载于火星侦察轨道器上的立体相机,自 2006 年以来一直在轨道上研究火星。
-
Moon LOLA DEM — 在这里你可以找到月球的 DEM。月球勘测轨道器(LRO)是目前在月球轨道上运行的机器人航天器。
显然,DEM 的来源很多,这里不可能列举所有。如果你对某个特定国家感兴趣,其研究中心通常会分享该区域的 DEM。
如果你想使用本指南中相同的 DEM 文件,请从这里下载文件 DTEEC_041878_1460_041021_1460_G01。该文件是以下火星表面图像的高程模型:

带陡峭沟壑斜坡的陨石坑。图像由UAHiRISE提供。
在 QGIS 中安装 DEMto3D 插件
要从数字高程模型生成网格,你需要在 QGIS 中安装 DEMto3D 插件:
-
打开 QGIS。
-
在插件选项卡中选择管理和安装插件…。
-
在搜索栏中输入DEMto3D。
-
打开第一个结果。
-
点击右下角的安装插件。

DEMto3D 插件。
如何生成 3D 网格
最后,生成网格非常简单:
-
在 QGIS 中打开 DEM 文件
-
转到栅格 -> DEMto3D -> DEM 3D 打印
-
在打印范围下,你需要选择生成 3D 网格的区域边界。你可以点击第一个按钮选择整个区域,或者点击第三个按钮绘制边界矩形。坐标表示边界矩形的两个点(左下角和右上角点)。
-
在模型尺寸下,将间距(mm)设置为 0.2,宽度(mm)和长度(mm)设置为你想要的 3D 文件尺寸(例如,100mm x 100mm 用于方形选择)。
-
在模型高度下,将高度(m)设置为最高点和最低点(右侧找到)之间的差值。将该差值的结果四舍五入到最小整数。
-
点击导出为 STL。

生成 3D 网格时使用的设置。
然后,你可以在MeshLab或任何其他 3D 编辑器中打开生成的网格:

火星陨石坑的生成网格。

生成网格的动画。图像使用这个生成器制作。
应用
-
城市规划:城市区域的 3D 网格可以用来模拟和可视化新建筑、道路和其他基础设施对城市环境的影响。
-
环境分析:危险区域的 3D 网格可以用来分析自然现象(如洪水或侵蚀)对景观的影响。这可以帮助环境科学家和政策制定者了解这些事件的风险和潜在后果。
-
军事与防御:一些地理 3D 网格可以用于军事和防御应用,如模拟和培训环境或任务规划。
-
视频游戏:地理区域的 3D 网格可以用来在虚拟场景中创建相同的环境。
结语
在本指南中,你已经概述了 DEM 文件及其生成技术。你也了解到,挑战在于获得感兴趣区域的合适 DEM,因为网格生成部分非常简单。
除非另有说明,否则所有图片均由作者提供。
为数据分析生成虚假数据
原文:
towardsdatascience.com/generating-fake-data-for-data-analytics-19cd5ed82a1
如果你没有真实数据,那就伪造吧!
·发布于 Towards Data Science ·阅读时间 8 分钟·2023 年 3 月 14 日
--

Leif Christoph Gottwald 的照片,来自 Unsplash
在数据分析的世界里,获取一个好的数据集至关重要。在现实世界中,你可能会接触到大量未清理的数据,你可能需要花费一些时间来清理。如果你没有所需的数据并且想要快速制作一个概念验证演示怎么办?在这种情况下,你通常需要自己制作数据,同时你需要这些数据具有一定的现实感。那么你会怎么做?是手动费心制作数据,还是有一种自动化的方式?
在这篇文章中,我将展示一些有趣的方式来伪造数据,并使其看起来真实!
生成名字
要生成一些虚假的名字,你可以使用 names 包。要使用它,首先你需要安装它:
!pip install names
你现在可以使用包中的各种函数来生成性别特定的名字:
import names
display(names.get_full_name('male'))
display(names.get_first_name())
display(names.get_last_name())
display(names.get_full_name('female'))
display(names.get_first_name())
display(names.get_last_name())
以下是一些生成的名字:
'Gerald Paez'
'Matthew'
'Wiese'
'Dana Mcmullen'
'Heather'
'Oxley'
'Walter Walters'
'Connie'
'Vildosola'
'Nancy Correra'
'Aaron'
'Dawes'
'Randy Meli'
'Yvonne'
'Owen'
'Loretta Patague'
'Sidney'
'Oliver'
生成 UUID
除了名字,你可能还想生成另一种数据类型——UUID。UUID(通用唯一标识符)是一个 128 位的值,用于唯一标识互联网上的对象或实体。在移动领域,UUID 通常用于识别安装在设备上的应用程序。
要生成示例 UUID,你可以使用 uuid 包:
!pip install uuid
你可以将生成的 UUID 转换为字符串:
import uuid
str(uuid.uuid4())
这是一个生成的 UUID 示例:
'54487fd7-0632-450e-b6e3-bcc54bc83133'
Faker 包
在使用 Python 生成虚假数据时,faker 包绝对值得一提。faker 包生成各种虚假数据供你使用。你可以生成的数据包括:
-
地址
-
条形码
-
信用卡信息
-
ISBN
-
电话号码
-
等等!
在接下来的章节中,我将向你展示如何生成一些常用的数据。
生成用户资料
faker包可以生成用户资料,例如用户名、性别、地址、电子邮件和出生日期。以下代码片段创建了一个男性的简单资料:
from faker import Faker
fake = Faker()
fake.simple_profile(sex='M') # use 'F' for female
输出是一个包含男性详细信息的字典:
{'username': 'lisa38',
'name': 'Brandon Gibson',
'sex': 'M',
'address': '406 Brandi Inlet\nWest Christopherville, PR 41632',
'mail': 'johnsondesiree@gmail.com',
'birthdate': datetime.date(2008, 9, 10)}
生成日期
我特别想生成的一种数据是一个人的出生日期(DOB)。在存储个人详细信息时,总是推荐存储出生日期而不是年龄(出于非常明显的原因)。
使用faker包,你可以生成一个年龄在 18 到 60 岁之间的人的出生日期:
fake.date_between(start_date='-60y', end_date='-18y')
返回的数据是一个date对象:
datetime.date(1963, 4, 18)
如果你想将结果转换为字符串,可以使用strftime()函数:
fake.date_between(start_date='-60y', end_date='-18y').strftime('%Y-%m-%d')
# '1973-07-16'
注意,每次你调用
Faker对象中的一个函数时,都会生成一组新的数据。如果你希望生成的数据是确定性的(即总是相同的),你可以使用seed()函数,如:Faker.seed(0)。
生成位置
我想生成的下一种数据是位置数据。例如,你想获取美国某个位置的经纬度。你可以使用local_latlng()函数并指定country_code参数:
fake.local_latlng(country_code = 'US')
该函数返回一个在country_code指定的国家中已知存在的地点。信息以一个元组的形式返回,如下所示:
('33.72255', '-116.37697', 'Palm Desert', 'US', 'America/Los_Angeles')
如果你只需要经纬度而不需要其他信息,将coords_only设置为True:
fake.local_latlng(country_code = 'US', coords_only=True)
country_code参数接受来自land_coords常量的值,例如AU代表澳大利亚:
fake.local_latlng(country_code = 'AU')
# ('-25.54073', '152.70493', 'Maryborough', 'AU', 'Australia/Brisbane')
我在 Faker 文档中找不到
land_coords常量的定义,但你可以参考rdrr.io/github/LuYang19/faker/src/R/init.R中定义的*land_coords*变量。
如果你需要一对保证存在于陆地上的坐标,可以使用location_on_land()函数:
fake.location_on_land(coords_only=True)
# ('54.58048', '16.86194')
生成地址
如果你想生成一些示例地址,可以使用address()、current_country()、city()、country()和country_code()函数:
display(fake.address())
# '910 Jason Green Apt. 954\nJonesland, IL 76881'
display(fake.current_country()) # based on the address returned by address()
# 'United States'
display(fake.city())
# 'North Carolyn'
display(fake.country())
# 'Holy See (Vatican City State)'
display(fake.country_code())
# MU
Faker 中的区域设置支持
到目前为止,生成的所有名称和地址都是英语的。然而,faker包也支持不同的区域设置。支持的区域设置列表可以在以下网址找到:faker.readthedocs.io/en/master/locales.html。
下图显示了一个示例区域设置——zh_CN:

所有图片均由作者提供
例如,在zh_CN区域设置中,你可以找到以下提供者:
-
faker.providers.address -
faker.providers.company -
faker.providers.date_time -
faker.providers.internet -
faker.providers.job -
faker.providers.lores -
faker.providers.person -
faker.providers.phone_number -
faker.providers.ssn
这意味着上述所有提供者都支持zh_CN地区设置。以faker.providers.address (faker.readthedocs.io/en/master/locales/zh_CN.html#faker-providers-address)为例。当实例化Faker对象时,可以传入一个或多个地区设置:
fake = Faker(['zh_CN']) # Chinese in China locale
fake.address()
上述address()函数返回中文地址:
'内蒙古自治区飞市兴山深圳路 b 座 104347'
如果使用zh_CN地区设置,一些函数将绑定到该地区设置,例如:
-
fake.name() -
fake.address() -
fake.current_country()
以下是一些示例:
'洪凤兰'
'辽宁省波县永川王路 s 座 292815'
"People's Republic of China"
'吕峰'
'广东省凤英市吉区李路 t 座 385879'
"People's Republic of China"
'何秀梅'
'浙江省齐齐哈尔市上街潮州路 M 座 218662'
"People's Republic of China"
地址结果将是中国的这些地点。
调用其他函数,如fake.country()将返回其他国家,但结果将以中文显示(基于zh-CN地区设置):
'越南'
越南就是 Vietnam。
你还可以使用zh_CN地区设置生成中文姓名:
fake = Faker(['zh_CN'])
display(fake.first_name_male())
display(fake.last_name_male())
display(fake.name())
这里是上述代码片段的示例输出:
'龙'
'马'
'雷春梅'
汇总它们
使用所有生成不同类型虚假数据的方法,我想把它们汇总在一起,以便进行一些数据分析。
以下代码片段生成 1000 组以下数据:
-
UUID
-
用户名
-
来自七个国家中的一个的纬度、经度和国家
-
性别
-
出生日期
from faker import Faker
import random
import uuid
uuids = []
usernames = []
latitudes = []
longitudes = []
genders = []
countries = []
dobs = []
n = 1000
fake = Faker()
country_codes = ['US','GB','AU','CN','FR','CH','DE']
for gender in ['M','F']:
for i in range(n // 2): # 500 males and 500 females
# uuids
uuids.append(str(uuid.uuid4()))
# username and sex
profile = fake.simple_profile(sex=gender)
usernames.append(profile['username'])
genders.append(profile['sex'])
# dob
dobs.append(fake.date_between(start_date='-78y', end_date='-18y'))
# lat and lng, and country
location = fake.local_latlng(country_code = country_codes[random.randint(0, len(country_codes) -1)])
latitudes.append(location[0])
longitudes.append(location[1])
countries.append(location[3])
然后,我将 1000 组数据合并到一个 Pandas DataFrame 中:
import pandas as pd
df = pd.DataFrame(data = [uuids, usernames, genders, countries, latitudes, longitudes, dobs])
df = df.T
df.columns = ['uuid', 'username', 'gender', 'country', 'latitude', 'longitude', 'dob']
df
数据框现在包含 1000 个虚构的用户账户及其个人详细信息,如应用 ID、位置信息、性别和出生日期:

绘制地图
使用纬度和经度,绘制用户的地理位置会很有趣。为此,我使用了 Folium:
import folium # pip install folium
mymap = folium.Map(location = [22.827806844385826, 4.363328554220703],
width = 950,
height = 600,
zoom_start = 2,
tiles = 'openstreetmap')
folium.TileLayer('Stamen Terrain').add_to(mymap)
folium.TileLayer('Stamen Toner').add_to(mymap)
folium.TileLayer('Stamen Water Color').add_to(mymap)
folium.TileLayer('cartodbpositron').add_to(mymap)
folium.TileLayer('cartodbdark_matter').add_to(mymap)
folium.LayerControl().add_to(mymap)
for lat, lng in zip(df['latitude'], df['longitude']):
station = folium.CircleMarker(
location = [lat, lng],
radius = 5,
color = 'red',
fill = True,
fill_color = 'yellow',
fill_opacity = 0.3)
# add the circle marker to the map
station.add_to(mymap)
mymap
学习如何使用 folium 轻松显示地图和标记
[towardsdatascience.com
这是显示用户分布的地图:

我可以放大地图:

我还可以更改图层集:

绘制饼图
我可以可视化我的用户来自哪里:
df.groupby('country').count().plot.pie(y='username')

我还可以使饼图更具描述性:
total = df.shape[0]
def fmt(x):
return '{:.2f}%\n({:.0f})'.format(x, total * x / 100)
df.groupby('country').count().plot.pie(y='username', autopct=fmt)

绘制条形图
每个国家的总用户数也可以通过条形图绘制:
from matplotlib import cm
import numpy as np
color = cm.inferno_r(np.linspace(.4, .8, len(country_codes)))
df.groupby('country').count().plot.bar(y = 'username',
color = color,
legend = False
)
从图表中可以看到,大不列颠的用户最多,而中国的用户最少:

绘制直方图
我还可以了解用户的年龄分布。为此,我需要先根据他们的出生日期计算他们的当前年龄:
from datetime import datetime, date
from dateutil import relativedelta
def cal_age(born):
return relativedelta.relativedelta(date.today(), born).years
df['age'] = df['dob'].apply(cal_age)
df
数据框现在增加了一列,显示每个用户的年龄:

你现在可以绘制一个显示年龄分布的直方图:
ax = df['age'].hist(bins=15, edgecolor='black', linewidth=1.2, color='yellow')
ax.set_xlabel("Age")
ax.set_ylabel("Total")
ax.set_xticks(range(18,80,5))
ax.set_title("Users age distribution")

如果你喜欢阅读我的文章并且对你的职业/学习有帮助,请考虑注册成为 Medium 会员。每月$5,可以无限访问 Medium 上的所有文章(包括我的文章)。如果你使用以下链接注册,我将获得一小笔佣金(对你没有额外费用)。你的支持意味着我可以花更多时间写这样的文章。
阅读韦孟李的每个故事(以及 Medium 上其他成千上万的作家)。你的会员费直接支持…
weimenglee.medium.com](https://weimenglee.medium.com/membership?source=post_page-----19cd5ed82a1--------------------------------)
总结
希望你现在更有能力生成项目所需的任何额外数据。生成逼真的演示数据不仅能让你更准确地测试算法,还能在演示中提供更多的真实感。请在评论中告诉我你通常需要生成其他类型的数据!
使用 VAEs、GANs 和扩散模型生成图像
了解如何使用 VAEs、DCGANs 和 DDPMs 生成图像
·
关注 发表在 面向数据科学 · 21 分钟阅读 · 2023 年 5 月 6 日
--
图片由 CatBird AI 提供,图片提示由 ChatGPT 提供,ChatGPT 由贾斯廷·切赫提供提示
引言:
我们目前正处于生成式 AI 繁荣期。2022 年 11 月,Open AI 的生成语言模型 ChatGPT 震撼了世界,而在 2023 年 3 月,我们甚至迎来了 GPT-4!
尽管这些 LLM 的未来非常令人兴奋,但今天我们将专注于图像生成。随着扩散模型的兴起,图像生成取得了巨大的飞跃。现在我们被像 DALL-E 2、Stable Diffusion 和 Midjourney 这样的模型包围。例如,查看上面的图像。为了展示这些 LLM 的强大,我给 ChatGPT 一个非常简单的提示,然后将其输入到免费的CatbirdAI中。CatbirdAI 使用了不同的模型,包括 Openjourney、Dreamlike Diffusion 等:
在本文中,Daisuke Yamada(我的合著者)和我将研究扩散模型。我们将使用 3 种不同的模型,并使用每种模型生成 MNIST 手写数字风格的图像。第一个模型将是传统的变分自编码器(VAE)。然后我们将讨论 GAN,并实现一个深度卷积 GAN (DCGAN)。最后,我们将转向扩散模型,并实现论文中描述的模型去噪扩散概率模型。对于每个模型,我们将讨论其背后的理论,然后在 Tensorflow/Keras 中实现。
快速说明一下符号。我们会尽量使用下标如x₀,但有时可能需要使用x_T来表示下标。
让我们简要讨论一下先决条件。熟悉深度学习和使用 Tensorflow/Keras 是很重要的。此外,你应该对 VAE 和 GAN 有一定了解;我们会讲解主要理论,但有经验会更有帮助。如果你从未见过这些模型,可以查看这些有用的资源:MIT S6.191 讲座,斯坦福生成模型讲座,VAE 博客。最后,无需了解 DCGANs 或扩散模型。很好!我们开始吧。
生成模型三难问题:
作为一种无监督过程,生成型 AI 通常缺乏明确的指标来跟踪进展。但在我们讨论任何评估生成模型的方法之前,我们需要理解生成型 AI 实际上试图实现什么!生成型 AI 的目标是从某些未知复杂数据分布(例如,人脸的分布)中获取训练样本,并学习一个能够“捕捉这种分布”的模型。那么,评估这样的模型时相关的因素是什么呢?
我们当然希望样本质量高,即生成的数据应该与实际数据分布相比,真实且准确。直观上,我们可以通过查看输出结果来主观评估这一点。这在一个称为HYPE(Human eYe Perceptual Evaluation)的基准中得到了形式化和标准化。虽然还有其他的定量方法,但今天我们将仅依靠我们自己的主观评估。
还有一个重要因素是快速采样(即生成速度或可扩展性)。 我们将关注的一个特定方面是生成新样本所需的网络传递次数。例如,我们将看到 GAN 只需对生成器网络进行一次传递即可将噪声转换为(希望是)现实的数据样本,而 DDPM 则需要顺序生成,这使得速度大大降低。
最终一个重要的质量标准称为模式覆盖。我们不仅仅希望学习未知分布的特定部分,而是希望捕捉整个分布以确保样本的多样性。例如,我们不希望一个仅输出 0 和 1 图像的模型,而是希望输出所有可能的数字类别。
这三个重要因素(样本质量、采样速度和模式覆盖)都涵盖在“生成模型三难困境”中。

图片由 Daisuke Yamada 创建,灵感来源于DDGANs 论文中的图 1
现在我们了解了如何比较和对比这些模型,让我们深入研究 VAE 吧!
变分自编码器:
你将遇到的第一个生成模型是变分自编码器(VAE)。由于 VAE 只是具有概率性变换的传统自编码器,我们来回顾一下自编码器。
自编码器是学习将数据压缩成某种潜在表示的降维模型:

图片由 Justin Cheigh 创建
编码器将输入压缩为称为瓶颈的潜在表示,然后解码器重建输入。解码器重建输入意味着我们可以用输入/输出之间的 L2 损失进行训练。
自编码器不能用于图像生成,因为它们会过拟合,导致稀疏的潜在空间是不连续且断开的(不可正则化的)。VAE 通过将输入x编码为潜在空间上的一个分布来解决这个问题:
输入x传递到编码器E. 输出E(x)是均值向量和标准差向量,这些向量参数化了分布P(z | x)。常见的选择是多变量标准高斯。从这里我们采样z ~ P(z | x),最终解码器尝试从z重建x(就像自编码器一样)。
注意这个采样过程是非可微的,因此我们需要做一些改变以允许反向传播实现。为此,我们使用重参数化技巧,即首先采样ϵ ~ N(0,1),然后将采样移动到输入层。接着,我们可以进行固定的采样步骤:z = μ + σ ⊙ ϵ. 注意我们获得了相同的采样,但现在我们有了一个清晰的路径来反向传播误差,因为唯一的随机节点是输入!
记住,自编码器的训练是 L2 损失,它构成了重建项。对于 VAE,我们还添加了一个正则化项,用于使潜在空间“表现良好”:
在这里,第一个项是重建项,而第二个项是正则化项。具体来说,我们使用的是Kullback-Leibler (KL) 散度,它衡量了学习到的潜在空间分布与先验分布之间的相似性。这有助于防止过拟合。
很好!我们已经回顾了 VAE 的理论和直觉,现在将讨论实现细节。在导入相关库后,我们定义了一些超参数值:
latent_dim = 2 # dimension of latent space
epochs = 200
batch_size = 32
learning_rate = 1e-4
下载 MNIST 数据集并进行一些基本预处理后,我们定义了我们的损失函数和网络:
def get_default_loss(model, x):
with tf.device(device):
mean, logvar, z = model.encoder(x)
xhat = model.decoder(z)
rl = tf.reduce_mean(keras.losses.binary_crossentropy(x, xhat))*28*28
kl = tf.reduce_mean(1+logvar-tf.square(mean)-tf.exp(logvar)) * -0.5
return rl + kl
'''Sampling layer'''
class Sampling(Layer):
def call(self, prob):
# uses reparameterization trick
mean, logvar = tf.split(prob, num_or_size_splits=2, axis=1)
e = random.normal(shape=(tf.shape(mean)[0], tf.shape(mean)[1]))
z = mean + e * tf.exp(logvar * 0.5)
return mean, logvar, z
'''Basic Convolutional VAE'''
class VAE(Model):
def __init__(self, latent_dim, **kwargs):
super(VAE, self).__init__(**kwargs)
self.latent_dim = latent_dim
self.encoder = self.get_encoder()
self.decoder = self.get_decoder()
'''encoder + reparametrization (i.e., sampling) layer'''
def get_encoder(self):
# encoder
input_x = Input(shape=(28,28,1))
x = Conv2D(filters=64, kernel_size=3, strides=(2,2), activation='relu')(input_x)
x = Conv2D(filters=64, kernel_size=3, strides=(2,2), activation='relu')(x)
x = Flatten()(x)
x = Dense(self.latent_dim * 2)(x)
# sampling
(mean, logvar, z) = Sampling()(x)
return Model(input_x, [mean, logvar, z], name="encoder")
'''decoder'''
def get_decoder(self):
input_z = Input(shape=(self.latent_dim,))
z = Dense(7*7*64, activation="relu")(input_z)
z = Reshape((7, 7, 64))(z)
z = Conv2DTranspose(filters=64, kernel_size=3, strides=2, padding='same', activation='relu')(z)
z = Conv2DTranspose(filters=64, kernel_size=3, strides=2, padding='same', activation='relu')(z)
xhat = Conv2DTranspose(filters=1, kernel_size=3, strides=1, padding='same', activation='sigmoid')(z)
return Model(input_z, xhat, name="decoder")
'''train'''
def train_step(self, x):
with tf.device(device):
x = x[0] if isinstance(x, tuple) else x
with tf.GradientTape() as tape:
loss = get_default_loss(self, x)
gradient = tape.gradient(loss, self.trainable_weights)
self.optimizer.apply_gradients(zip(gradient, self.trainable_weights))
return {"loss": loss}
def call(self, inputs):
pass
vae = VAE(latent_dim=latent_dim)
vae.compile(optimizer=Adam(learning_rate=learning_rate))
vae.build(input_shape=(28,28,1))
vae.summary()
好的,我们来分析一下。函数 get_default_loss(model, x)接收一个 VAE 模型和一些输入x,并返回我们之前定义的 VAE 损失(其中C = 1)。我们定义了一个卷积 VAE,其中编码器使用 Conv2D 层进行下采样,解码器使用Conv2DTranspose层(反卷积层)进行上采样。我们使用 Adam 进行了优化。
由于其他两个生成模型从随机噪声开始,我们没有使用某些输入图像,而是从潜在空间中采样并使用解码器生成新图像。我们测试了 latent_dim = 2 和 latent_dim = 100,并获得了以下结果:

VAE 生成的图像。图像由 Daisuke Yamada 创建
由于我们只进行一次前向传递来生成新样本,因此我们的采样速度很快。此外,这是一个相对简单的模型,所以我们的训练速度也很快。维度为 2(意味着瓶颈潜在表示的维度为 2)的结果很好,但有点模糊。然而,维度为 100 的结果则不是很好。我们认为可能是计算能力不足,或者后验分布开始扩展到不存在的模式。换句话说,我们开始学习没有意义的潜在特征。
那么,理论上如何选择“最佳”的潜在维度呢?显然,100 不是一个好选择,但也许在 2 和 100 之间的某个值是理想的。这里存在样本质量和计算效率之间的权衡。因此,你可以确定这些因素对你的重要性,并进行类似网格搜索的操作来正确选择这个超参数。
我们还绘制了维度为 2 的潜在空间。基本上,以下内容告诉我们解码器根据我们在潜在空间中的起点输出什么。

我们的 VAE 潜在空间
正如你所见,潜在空间相当多样化,而且相当完整和连续!因此,反思生成模型三难问题,我们得到以下结论:

VAE 的三难问题(红色为好,蓝色为坏)。图像由大辅·山田创建
现在我们将转到 DCGANs,并从加速解释 GANs 开始。
深度卷积 GANs:
在 GANs 中,有一个 生成器 G 和一个 鉴别器 D。生成器创建新数据,而鉴别器区分(或辨别)真实数据和虚假数据。两者在一个极小化-极大化游戏中对抗训练,因此有了“对抗性”一词。
我们获得一些训练数据,并开始通过标准正态分布或均匀分布来采样随机噪声 z。这些噪声是待生成数据的潜在表示。我们从噪声开始,以允许更多样化的数据样本,并避免过拟合。
噪声被输入到生成器中,生成器输出生成的数据 x = G(z)。然后,鉴别器接受 x 并输出 P[x = real] = D(x),即生成的图像 x 是真实图像的概率。此外,我们还将训练集中真实图像输入鉴别器。
我们通常将损失定义为一个极小化-极大化游戏:
GANs 损失
对于判别器,这看起来像是二元交叉熵,这很合理,因为它是一个二元分类器。对每个分布中采样的点的期望值实际上对应于你从(a)数据分布中(E_{x ~ p(data)})和(b)随机噪声(E_{z ~ p(z)).中获得的数据点。第一个项表示判别器想要最大化将真实数据分类为 1 的可能性,而第二个项表示判别器想要最大化将假数据分类为 0 的可能性。判别器还在生成器会以最优方式行动的最小-最大假设下运行。
好的,我们现在将过渡到 DCGANs。DCGANs 与 GANs 类似,但架构上有几个显著变化;主要的是 DCGANs 不使用任何多层感知机,而是使用卷积/反卷积。以下是稳定 DCGANs 的架构指南(来自原始论文):
-
用步幅卷积(判别器)和分数步幅卷积(即反卷积)(生成器)替换任何池化层
-
在生成器和判别器中使用批量归一化
-
为了更深的架构,移除全连接的隐藏层
-
在生成器中,对所有层使用 ReLU 激活,除了输出层使用 Tanh
-
在判别器中,对所有层使用 LeakyReLU 激活。

DCCGAN 架构 - 图像由 Justin Cheigh 创建
我们经常使用 GANs 进行图像生成,因此直观上使用卷积层是有意义的。我们在判别器中使用标准卷积层,因为我们希望将图像降采样成分层特征,而对于生成器,我们使用反卷积层将图像从噪声(潜在表示)上采样到生成的图像。
批量归一化用于稳定训练过程、提高收敛性并加快学习速度。Leaky ReLU 防止了 ReLU 的零学习问题。最后,使用 Tanh 来防止较小输入的饱和,并避免梯度消失问题(因为它在原点周围是对称的)。
太棒了!现在让我们看看如何实现 DCGANs。导入库后,我们设置超参数值:
latent_dim = 100
epochs = 100
batch_size = 32
learning_rate = 1e-4
经过一些数据预处理和为计算效率进行批量拆分后,我们准备定义生成器和判别器:
generator = Sequential([
# input
Dense(units=7*7*128, input_shape=(latent_dim,)),
Reshape((7,7,128)),
# conv 1
Conv2DTranspose(filters=64, kernel_size=3, strides=2, padding='same'),
BatchNormalization(),
ReLU(max_value=0.2),
# conv 2
Conv2DTranspose(filters=64, kernel_size=3, strides=1, padding='same'),
BatchNormalization(),
ReLU(max_value=0.2),
# final tanh
Conv2DTranspose(filters=1, kernel_size=3, strides=2, padding='same', activation='tanh')
])
discriminator = Sequential([
# conv 1
Conv2D(filters=64, kernel_size=3, strides=2, padding='same', input_shape=(28,28,1)),
LeakyReLU(0.2),
# conv 2
Conv2D(filters=64, kernel_size=3, strides=2, padding='same'),
# BatchNormalization(),
LeakyReLU(0.2),
# output
Flatten(),
Dense(1, activation='sigmoid')
])
discriminator.compile(optimizer=Adam(learning_rate=learning_rate),
loss=BinaryCrossentropy(),
metrics=[BinaryAccuracy()])
discriminator.trainable = False
gan = Sequential([
generator,
discriminator
])
gan.compile(optimizer=Adam(learning_rate=learning_rate), loss=BinaryCrossentropy(), metrics=[BinaryAccuracy()])
记住所有关于 DCGANs 的架构指南!我们对生成器使用 Conv2DTranspose,对判别器使用普通的 Conv2D 和步幅。注意我们用 Binary cross entropy 编译判别器,但将判别器指定为 trainable = False。这是因为我们将自己实现训练循环:
def generate():
# generate image based on random noise z
with tf.device(device):
random_vector_z = np.random.normal(loc=0, scale=1, size=(16, latent_dim))
generated = generator(random_vector_z)
return generate
# labels
real = np.ones(shape=(batch_size, 1))
fake = np.zeros(shape=(batch_size, 1))
# generator and discriminator losses
g_losses, d_losses = [], []
with tf.device(device_name=device):
for epoch in range(epochs):
for real_x in x_train_digits:
'''discriminator'''
# train on real data
d_loss_real = discriminator.train_on_batch(x=real_x, y=real)
# train on fake data
z = np.random.normal(loc=0, scale=1, size=(batch_size, latent_dim))
fake_x = generator.predict_on_batch(x=z)
d_loss_fake = discriminator.train_on_batch(x=fake_x, y=fake)
# total loss
d_loss = np.mean(d_loss_real + d_loss_fake)
'''generator'''
g_loss = gan.train_on_batch(x=z, y=real)
g_losses.append(g_loss[-1])
d_losses.append(d_loss)
首先,我们用真实数据和生成器生成的假数据训练判别器,然后也训练生成器。太棒了!让我们看看结果:

图像由大辅·山田创作
我们的样本质量更好(不那么模糊)!我们仍然有快速采样,因为推理只需将随机噪声输入生成器。下面是我们的潜在空间:

我们的 DCGAN 潜在空间
不幸的是,我们的潜在空间不是很多样化(特别是在潜在维度为 2 的第 1 代样本中尤为明显)。我们很可能遇到了模式崩溃的常见问题。正式地说,这意味着生成器只学会创建一个专门用来欺骗判别器的子集。换句话说,如果生成器在生成 1 的图像时判别器表现不佳,那么生成器没有理由去做其他事情。因此,对于生成模型三难问题,我们得到如下:

DCGANs 的三难困境(红色为好,蓝色为坏)。图像由大辅·山田创作
现在我们已经探索了 GANs 和 DCGANs,是时候过渡到扩散模型了!
扩散模型:
扩散概率模型(或简称扩散模型)目前是每个顶级图像生成模型的一部分。我们将讨论去噪扩散概率模型(DDPMs)。
对于 VAEs 和 GANs,样本生成涉及从噪声到生成图像的一个步骤。GANs 通过将噪声输入生成器并进行正向传播来执行推理,而 VAEs 通过采样噪声并将其传递通过解码器来执行推理。扩散模型的主要思想是生成一系列图像,其中每个后续图像稍微少一些噪声,最终图像理想情况下是现实的!DDPM 有两个方面。在正向过程中,我们使用真实图像并迭代地添加噪声。在反向过程中,我们学习如何撤销在正向过程中添加的噪声:
让我们从直观的层面解释正向和反向过程。我们给定一组训练数据X₀,其中每个数据点x₀ ∈ X₀从数据分布x₀ ~ q(x₀)中采样。回忆一下q(x₀)是我们想要表示的未知分布。
从右到左是硬编码的正向过程,我们从一些训练样本x₀ ~ q(x₀)开始,逐步添加高斯噪声。也就是说,我们将生成一系列图像x₁, x₂, …, x_T,其中每个后续图像都越来越嘈杂。最终,我们会得到可以被视为纯噪声的东西!从左到右是反向过程,我们学习如何去噪,即预测如何从x_{t+1} → xₜ。很好!现在我们理解了正向和反向过程的基本概念,让我们深入了解理论吧!
正式地,前向过程由一个马尔可夫链描述,该链根据预定的方差调度β₁, …, β_T 迭代地向数据中添加高斯噪声。马尔可夫链这个术语意味着x_{t+1} 仅 依赖于xₜ。因此,x_{t+1} 在给定xₜ的条件下,与x₁, …, x_ₜ₋₁ 条件独立。这意味着q(xₜ | x₀, …, xₜ₋₁) = q(xₜ | xₜ₋₁)。另一个重要的概念是方差调度。我们定义一些值β₁, …, β_T,这些值用于参数化我们在每个时间步添加的高斯噪声。通常,0 ≤ β ≤ 1,其中β₁ 较小,β_T 较大。所有这些都包含在我们对q(xₜ | xₜ₋₁)的定义中:
前向扩散过程
所以,我们从x₀开始。然后,经过T个时间步,我们遵循上述方程来获取序列中的下一个图像:xₜ ~ q(xₜ | xₜ₋₁)。可以证明在极限T → ∞ 时,x_T 等同于各向同性的高斯分布。
不过我们还没完成。我们直观上应该能够通过递归展开一步步从x₀ 到任何x_t。我们首先使用重参数化技巧(类似于变分自编码器):
重参数化技巧
这使我们能够做到以下几点:
抽样任意时间步 t
通过遵循上述方程,我们可以在一步内从x₀到任何xₜ!对那些好奇的人,推导过程涉及展开和使用高斯的加法性质。让我们继续讨论逆向过程。
在逆向过程中,我们的目标是了解q(xₜ₋₁| xₜ),因为我们可以随机取噪声并从q(xₜ₋₁ | xₜ) 中迭代抽样以生成逼真的图像。我们可能会认为可以使用贝叶斯规则轻松获得q(xₜ₋₁ | xₜ),但事实证明这是计算上不可行的。这在直观上是合理的;要逆转前向步骤,我们需要查看xₜ 并考虑我们可能到达那里的所有方式。
因此,我们将学习一个具有权重θ的模型p,而不是直接计算q(xₜ₋₁ | xₜ),该模型近似这些条件概率。幸运的是,如果βₜ 足够小,我们可以成功地将q(xₜ₋₁| xₜ) 估计为高斯分布。这一见解来自涉及随机微分方程的一些极其困难的理论。因此,我们可以定义p为以下内容:
模型定义
那么,我们的损失是什么呢?如果我们想要撤销添加的噪声,直观上只需预测添加的噪声就足够了。要查看更完整的推导,请查阅Lilian Weng**的这篇优秀博客。但事实证明,我们的直觉是正确的,我们不需要一个模型p,而是可以使用一个预测添加噪声的网络ϵ_θ**。通过这种方法,我们可以使用实际噪声和预测噪声之间的均方误差(MSE)进行训练:
最终损失
在这里,ϵ 是实际误差,而另一个术语是预测误差。你可能会注意到期望值是对x_0取的;这是因为通常误差是以重参数化技巧(如上所述)的形式写出的,这使你可以直接从x_0获得x_t。因此,我们的网络输入是时间t和当前图像xₜ**。
让我们做一个全面回顾。我们使用 MSE 训练网络ϵ_θ以学习如何预测添加的噪声。一旦训练完成,我们可以使用我们的神经网络ϵ_θ来预测任何时间步的添加噪声。利用这些噪声和一些上述方程,我们完成逆过程并有效地“去噪”。因此,我们可以通过获取噪声并持续去噪来进行推断。这两个过程都由以下伪代码描述:

在训练过程中,我们获取一张真实图像,抽样t ~ Uniform({1,2,…,T})(我们这样做是因为逐步计算效率低),然后对目标/预测噪声的 MSE 进行梯度下降。在采样过程中,我们取随机噪声,然后使用我们预测的噪声和推导的方程连续采样,直到我们得到一些生成的图像x₀.**
很好!我们现在可以进入实施细节。对于我们的基础架构,我们将使用一个U-Net:

图像由 Justin Cheigh 创建;灵感
从架构来看,为什么这被称为 U-Net 很明显!U-Net 最初用于生物医学图像分割,但它们在扩散模型中也表现得非常好!直观地说,这是因为(a)输入和输出的形状是相同的,这正是我们需要的,以及(b)我们将看到 U-Net(由于编码器-解码器结构配合跳跃连接)在保留局部/全局信息方面表现良好,这有助于保留图像但仍有效地添加噪声。
U-Net 具有类似于过去生成模型的编码器-解码器结构。具体来说,如果你查看形状为“U”的图像,你会发现下行过程中有一系列下采样层,这些层是编码器结构的一部分。上行过程中,我们有一系列上采样层,这些层是解码器结构的一部分。输入和输出具有相同的形状,这对于我们的输入是带噪声的图像 xₜ 和我们的输出是一些预测噪声的情况来说是理想的。
但是,你可能会注意到 U-Net 和标准自编码器之间有一个重要区别,那就是跳跃连接。在每个层级中,我们有一个下采样模块,该模块连接到另一个下采样模块(遵循“U”的形状),并且有一个跳跃连接到上采样模块。记住,这些下采样模块基本上是在以不同分辨率查看图像(学习不同层次的特征)。通过这些跳跃连接,我们确保在每个分辨率上都考虑到这些特征!另一种思考 U-Net 的方式是将其视为一系列堆叠的自编码器。
好的,现在让我们看一下我们的具体实现。首先,我撒了个谎……我说我们的输入只是带噪声的图像 xₜ. 然而,我们还输入了实际的时间步 t 以提供时间概念。我们的方法是使用时间步嵌入,其中我们取时间 t 并使用正弦位置嵌入:

正弦位置嵌入
对于不熟悉的人,正弦位置嵌入的高级概述是我们使用正弦函数对某些序列中的元素(这里是时间步)进行编码,直觉是这些函数的平滑结构将更容易被神经网络学习。因此,我们的实际输入是带噪声的图像 xₜ 和时间步 t,它们首先经过这个时间嵌入层。
接下来是我们的下采样/上采样模块:每个下采样(上采样)模块包含 2 个 ResNet 模块、1 个 Attention 层和 1 个卷积(反卷积)层。我们快速了解一下这些模块。
残差网络(ResNet)基本上是一系列具有大跳跃连接的卷积层,这些跳跃连接允许信息在非常深的神经网络中流动。Attention 是一种革命性的想法,对于理解像 Transformer 这样的基础架构至关重要,它告诉神经网络该关注什么。例如,这里我们有 2 个 ResNet 模块。在这些模块之后,我们将得到输入图像的潜在特征向量,Attention 层将告诉神经网络这些特征中哪些是最重要的。最后,标准的卷积/反卷积分别用于下采样/上采样。
在我们的实现中,我们使用了 4 个这样的堆叠自编码器:
def call(self, x, time, training=True, **kwargs):
with tf.device(device):
# front conv
x = self.init_conv(x)
# time embedding
t = self.time_mlp(time)
# move down the encoder
h = []
for down_block1, down_block2, attention, downsample in self.downs:
x = down_block1(x, t)
x = down_block2(x, t)
x = attention(x)
h.append(x) # keep for skip connection!
x = downsample(x)
# bottleneck consists of
x = self.mid_block1(x, t) # ResNet block
x = self.mid_attn(x) # Attention layer
x = self.mid_block2(x, t) # ResNet block
# move up the decoder
for up_block1, up_block2, attention, upsample in self.ups:
x = tf.concat([x, h.pop()], axis=-1)
x = up_block1(x, t)
x = up_block2(x, t)
x = attention(x)
x = upsample(x)
x = tf.concat([x, h.pop()], axis=-1)
# back conv
x = self.final_conv(x)
return x
很好!现在我们已经定义了我们的 U-Net 类,我们可以继续将 U-Net 应用于我们的具体问题。我们首先定义相关的超参数:
image_size = (32, 32)
num_channel = 1
batch_size = 64
timesteps = 200
learning_rate = 1e-4
epochs = 10
由于计算能力不足,我们使用timesteps = T = 200, 尽管原始论文使用的是T = 1000. 数据预处理后,我们定义前向过程
# define forward pass
beta = np.linspace(0.0001, 0.02, timesteps) # variance schedule
alpha = 1 - beta
a = np.concatenate((np.array([1.]), np.cumprod(alpha, 0)[:-1]), axis=0) # alpha bar
def forward(x_0, t):
# uses trick to sample from arbitrary timestep!
with tf.device(device):
noise_t = np.random.normal(size=x_0.shape)
sqrt_a_t = np.reshape(np.take(np.sqrt(a), t), (-1, 1, 1, 1))
sqrt_one_minus_a_t = np.reshape(np.take(np.sqrt(1-a), t), (-1, 1, 1, 1))
x_t = sqrt_a_t * x_0 + sqrt_one_minus_a_t * noise_t
return noise_t, x_t
所以,在这里我们以一种相当标准的方式定义我们的方差调度。在前向函数中,我们使用重新参数化技巧,允许我们从x₀中抽样任意的xₜ。下面是前向过程的可视化:

我们的前向过程可视化
然后我们实例化我们的 U-Net,定义我们的损失函数,并定义训练过程:
unet = Unet() # instantiate model
def loss(noise, predicted):
# remember we just use MSE!
with tf.device(device):
return tf.math.reduce_mean((noise-predicted)**2)
optimizer = keras.optimizers.Adam(learning_rate=learning_rate)
def train_step(train_images):
with tf.device(device):
# create a "batch" number of random timesteps (in our case 64)
timestep_values = tf.random.uniform(shape=[train_images.shape[0]], minval=0, maxval=timesteps, dtype=tf.int32)
# forward
noised_images, noise = forward(x_0=train_images, t=timestep_values)
# set the gradient and get the prediction
with tf.GradientTape() as tape:
predicted = unet(x=noised_images, time=timestep_values)
loss_value = loss(noise, predicted)
# optimize U-Net using ADAM
gradients = tape.gradient(loss_value, unet.trainable_variables)
optimizer.apply_gradients(zip(gradients, unet.trainable_variables))
return loss_value
def train(epochs):
with tf.device(device):
for epoch in range(epochs):
losses = []
for i, batch_images in enumerate(iter(dataset)):
loss = train_step(batch_images)
losses.append(loss)
记住,我们的损失(经过大量工作之后)只是均方误差(MSE)!其余部分是一个相当标准的训练循环。训练完成后,我们可以考虑推断。回顾我们的采样算法 2,我们按如下方式实现:
def denoise_x(x_t, pred_noise, t):
with tf.device(device):
# obtain variables
alpha_t = np.take(alpha, t)
a_t = np.take(a, t)
# calculate denoised_x (i.e., x_{t-1})
beta_t = np.take(beta, t)
z = np.random.normal(size=x_t.shape)
denoised_x = (1/np.sqrt(alpha_t)) * (x_t - ((1-alpha_t)/np.sqrt(1-a_t))*pred_noise) + np.sqrt(beta_t) * z
return denoised_x
def backward(x, i):
with tf.device(device):
t = np.expand_dims(np.array(timesteps-i-1, np.int32), 0)
pred_noise = unet(x, t)
return denoise_x(x, pred_noise, t)
在这里,我们定义如何在特定时间步采集图像并进行去噪。通过这些,我们可以完全定义我们的推断过程:
def get_sample(x=None):
# generate noise
if x is None:
x = tf.random.normal(shape=(1,32,32,1))
# array to store images
imgs = [np.squeeze(np.squeeze(x, 0),-1)]
# backward process
for i in tqdm(range(timesteps-1)):
x = backward(x, i)
if i in [0,25,50,75,100,125,150,175,198]:
imgs.append(np.squeeze(np.squeeze(x, 0),-1))
return imgs if show_progress else imgs[-1]
我们从随机噪声开始,然后不断使用我们的反向函数去噪,直到得到一个现实的图像!以下是我们的一些结果:

我们的 DDPM 生成样本示例
样本质量相当高。此外,我们能够获得多样化的样本。推测我们的样本质量会随着计算能力的提高而改善;扩散过程计算开销非常大,这影响了我们训练这个模型的能力。还可以进行“逆向工程”。我们取一个训练图像,添加噪声,然后去噪,以观察我们重建图像的能力。我们得到以下结果:

逆向工程。图像由大辅·山田创建
需要注意的是,逆向过程是概率性的,这意味着我们并不总是得到与输入图像相似的图像。
很好!让我们回到生成模型三难题。我们有高质量的样本、多样化的样本范围,以及更稳定的训练(这是这些迭代步骤的副产品)。然而,我们有缓慢的训练和采样,因为在推断过程中我们需要一次又一次地采样。因此,我们得到以下结果:

DDPMs 的三难题(红色代表好,蓝色代表不好)。图像由大辅·山田创建
结论:
哇!我们覆盖了三种图像生成模型,从标准的变分自编码器(VAE)到扩散模型(DDPM)。对于每种模型,我们查看了生成模型三难题,并获得了以下结果:

比较模型。图像由大辅·山田创建
自然的问题是:我们能否获得生成模型三难问题的所有 3 个部分?看起来扩散模型几乎达到了这一点,因为我们只需找出提高采样速度的方法。从直观上讲,这很困难,因为我们依赖于将逆过程建模为高斯过程的假设,这只有在我们在几乎所有时间步长中执行逆过程时才有效。
然而,实际上,获得三难问题的所有 3 个因素是可能的!像 DDIMs 或 DDGANs 这样的模型建立在 DDPMs 之上,但它们已经找到了提高采样速度的方法(其中一种方法是使用跨步采样计划)。通过这些和其他不同的优化,我们可以获得生成模型三难问题的所有 3 个方面!
那么,接下来是什么?一个特别有趣的方向是条件生成。条件生成允许你在一些类别标签或描述性文本的条件下生成新样本。例如,在所有最初列出的图像生成模型中,你可以输入类似“企鹅卧推 1000 磅”的内容,并获得合理的输出。虽然我们没有时间探索这一条件生成的方向,但它似乎是一个非常有趣的下一步!
好吧,这就是我们的全部内容。感谢阅读!
除非另有说明,所有图片均由作者(们)创建。
使用 Wikipedia 生成知识图谱
原文:
towardsdatascience.com/generating-knowledge-graphs-with-wikipedia-ec17030a40f6
快速生成知识图谱的 Python 指南
·发表于 Towards Data Science ·5 分钟阅读·2023 年 1 月 1 日
--

知识图谱使我们能够理解不同知识点之间的关系,从而对一个领域或主题有深入的了解。这些图谱帮助我们辨别个别知识点如何结合形成更大的图景。显然,构建和可视化知识图谱可以是许多领域的有效方法。
在本文中,我们描述了一种通过利用处理人类知识的最大公开图谱——Wikipedia 来生成新的知识图谱的过程。我们将使用 Python 完全自动化生成过程,使我们能够为任何感兴趣的领域创建可扩展的知识图谱生成方法。
如果你想跟随操作,可以在 Google Colab 中找到完整的 notebook。
方法
我们的方法如下:
-
🔌 使用 Wikipedia API 下载与术语相关的信息
-
🔁 迭代多个术语以建立知识库
-
🔝 根据“重要性”对术语进行排名
-
🌐 使用 networkx 库可视化知识图谱
如果你想与代码一同阅读,可以在 Google Colab 中找到它。
Wikipedia API
Wikipedia 通过 API 提供所有知识。此外,还有一个 很棒的 Python 包,使得扫描网站变得简单。使用这个包,我们可以根据搜索词扫描 Wikipedia 页面,如下例所示。
import wikipedia as wp
ds = wp.page("data science")
你可以在这篇文章中阅读更多关于该包的信息。
页面对象包含了我们需要的所有信息,以便遍历图谱并理解各种术语之间的关系。需要注意的关键属性有:
-
links:页面上链接到其他维基百科页面的外链
-
content:页面的实际内容
-
summary:页面顶部显示的关键信息。
下面展示了来自数据科学页面的一个示例。
维基百科网站庞大,拥有 700 万篇英语文章 (Wikipedia, 2022),这意味着扫描每一个页面将成本高昂,并且会覆盖许多与兴趣主题无关的页面。因此,我们需要开发一个算法,仅搜索相关页面。
搜索维基百科
搜索算法应从兴趣点开始,然后从那里向外探索,确保尽可能靠近兴趣点,同时也确保捕捉到最重要的页面。
我们将遵循的算法是:
-
从覆盖兴趣领域的术语列表开始。例如,对于“数据科学”的知识图谱,我们可以选择“数据科学”、“机器学习”和“人工智能”。
-
使用维基百科 API 从术语列表中获取维基百科页面。
-
查找页面上的所有外链,并为它们计算一个权重。权重可以基于术语出现的频率、距离文档开始的距离,或是否包含在摘要中。
-
将新链接添加到术语列表中。
-
从剩余的术语中找出最重要的术语,并获取该术语的页面。我们可以通过术语在其他术语中被引用的次数以及这些引用的权重来定义重要性。
-
重复步骤 3–5,直到达到足够的深度。对于以下示例,这大约是几百个术语。
有了这些,我们可以开始构建一个以我们关心的主题为中心的整个维基百科数据库的本地图谱。
这些术语可以以术语列表的形式呈现,按其重要性排序。下面是“数据科学”的一个示例。

在“数据科学”知识图谱中按重要性排名的顶级术语。
列表是处理数据的一个有用工具,但我们还有更多的数据可以利用,因此让我们探索网络图。
创建网络图
在定义了网络后,我们可以开始对其进行可视化。鉴于数据的图形特性,最好以图表的形式查看。为此,我们可以使用实用的包 networkx。Networkx 是一个用于创建、操作和研究复杂网络结构、动态和功能的 Python 包 (Networkx, 2022)。
Networkx 在基本图论的基础上构建图。下面展示了一个示例绘图脚本。
import networkx as nx
G = nx.Graph()
G.add_nodes_from(nodes)
G.add_edges_from(edges)
nx.draw(G)
为了绘制网络,我们需要使用比本示例中显示的更复杂的函数。特别地,我们将使用加权节点和加权边,分别基于单个术语的重要性及其连接。
“数据科学”、“物理学”和“生物学”的绘图如下所示。


“数据科学”(左)和“物理学”(右)的知识图谱。

“生物学”术语的知识图谱。
从生物学领域来看,我们看到一个有趣的图表。我不是生物学家,但这似乎相当准确!一些值得关注的点:
-
术语动物紧挨着生物学,并且具有类似的重要性,这很合理,因为生物学是研究生物体的学科。
-
左侧我们看到一个与细胞相关的生物学集群:变形虫、细胞壁、减数分裂和细菌。由于这些术语之间的强链接,networkx 算法将它们分组在一起。通过这种方式,知识图谱可以建议一起学习的术语。
-
鉴于生物学与其环境之间的强链接,我们看到地质学领域通过地球和地层学等术语显现出来。
-
正如近期事件所预期的那样,我们看到气候、气候变化和时间等问题被提升为重要话题。如果这是 20 年前的知识图谱,情况可能会有所不同。
-
我们看到了一些看似不相关的术语1、数字和isbn。这可能是由于维基百科中的一些奇怪引用,应予以删除。
让教育变得更容易
在这里,我们展示了一种从兴趣领域到完整知识图谱的方法。它允许我们获取按重要性排名的术语列表,以及这些术语如何相互适应的可视化。
这样,这些图表可以对教育和学习新领域有用。这对个人学习确实如此,但对于学费和更广泛的教育也适用,因为课程通常会在知识上留下空白。
提醒:完整的笔记本可以在Google Colab中找到,请随意试用,并告诉我你的发现!
使用 MONAI 生成医疗图像
原文:
towardsdatascience.com/generating-medical-images-with-monai-e03310aa35e6
一个端到端的开源项目,使用最新的 MONAI 生成模型从放射报告文本生成胸部 X 光图像
·发表于 Towards Data Science ·阅读时间 14 分钟·2023 年 4 月 14 日
--
大家好!在这篇文章中,我们将使用 MONAI 的新开源扩展MONAI 生成模型创建一个潜在扩散模型来生成胸部 X 光图像!

介绍
生成式 AI 在医疗领域具有巨大的潜力,因为它允许我们创建学习训练数据集底层模式和结构的模型。这样,我们可以使用这些生成模型创建大量合成数据,这些数据具有真实数据的相同细节和特征,但没有其限制。鉴于其重要性,我们创建了MONAI 生成模型,这是对 MONAI 平台的开源扩展,包含最新的模型(如扩散模型、自回归变换器和生成对抗网络)以及帮助训练和评估生成模型的组件。

在这篇文章中,我们将通过一个完整的项目来创建一个潜在扩散模型(与稳定扩散相同类型的模型),该模型能够从放射报告中生成胸部 X 光(CXR)图像。在这里,我们尝试使代码易于理解并适应不同环境,所以尽管它不是最有效的,希望你喜欢!
你可以在这个GitHub 仓库找到完整的开源项目,在这篇文章中,我们引用的是版本 v0.2。
数据集
首先,我们从数据集开始。在这个项目中,我们使用MIMIC 数据集。要访问这个数据集,必须在Physionet 门户创建一个账户。我们将使用MIMIC-CXR-JPG(包含 JPG 文件)和MIMIC-CXR(包含放射学报告)。这两个数据集都受到PhysioNet 认证健康数据许可证 1.5.0*的约束。完成免费的培训课程后,你可以按照数据集页面底部的说明自由下载数据集。原始的 CXR 图像大约为+1000x1000 像素。因此,这一步可能需要一些时间。
胸部 X 光图像是提供关于胸腔内结构和器官(包括肺部、心脏和血管)宝贵信息的重要工具,下载后我们应该有超过 350k 张这样的图像!这些图像是三种不同投影中的一种:后前位(PA)、前后位(AP)和侧位(LAT)。对于这个项目,我们只关注PA 投影,这是最常见的一种,可以可视化放射学报告中提到的大多数特征(共计 96,162 张图像)。关于报告,我们有 85,882 个文件,每个文件包含多个文本部分。我们将使用发现(主要解释图像内容)和印象(总结报告内容,如结论)。为了使我们的模型和训练过程更易于管理,我们将图像调整为最小轴 512 像素。自动执行这些初始步骤的脚本列表可以在这里找到。
模型

潜在扩散模型:自编码器将输入图像 x 压缩为潜在表示 z,然后扩散模型估计 z 的概率分布
潜在扩散模型由几个部分组成:
-
一个自编码器,用于将输入图像压缩为更小的潜在表示;
-
一个扩散模型,将学习 CXR 潜在表示的概率数据分布;
-
一个文本编码器,它创建一个嵌入向量,用于条件化采样过程。在这个例子中,我们使用一个预训练的编码器。
使用 MONAI 生成模型,我们可以轻松创建和训练这些模型,因此我们从自编码器开始吧!
模型 — 带 KL 正则化的自编码器
带有 KL 正则化的自编码器(AE-kl,或在一些项目中简单称为 VAE)的主要目标是能够创建一个小的潜在表示,并且高保真地重建图像(尽可能保留细节)。在这个项目中,我们创建了一个四层的自编码器,具有 64、128、128、128 个通道,其中在每一层之间应用了一个降采样块,使得特征图在深入层次时变小。尽管我们的自编码器可以有自注意力块,但在这个例子中,我们采用了类似于我们之前在脑部图像研究中的结构,并且不使用注意力以节省内存使用。最后,我们的潜在表示有三个通道。
from generative.networks.nets import AutoencoderKL
...
model = AutoencoderKL(
spatial_dims=2,
in_channels=1,
out_channels=1,
num_channels=[64, 128, 128, 128],
latent_channels=3,
num_res_blocks=2,
attention_levels=[False, False, False, False],
with_encoder_nonlocal_attn=False,
with_decoder_nonlocal_attn=False,
)
注意:在我们的脚本中,我们使用了OmegaConf 包来存储我们模型的超参数。你可以在这个文件中查看之前的配置。总之,OmegaConf 是一个强大的工具,用于管理 Python 项目的配置,特别是那些涉及深度学习或其他复杂软件系统的项目。OmegaConf 允许我们方便地在 .yaml 文件中组织超参数,并在脚本中读取它们。
训练 AE-KL
接下来,我们定义了我们训练过程中的几个组件。首先,我们有KL 正则化。这部分负责评估扩散模型潜在空间分布与高斯分布之间的距离。正如Rombach 等人所提出的,这将用于限制潜在空间的方差,这在我们对其进行扩散模型训练时非常有用(稍后会详细说明)。我们模型的前向方法返回重建结果,以及我们潜在表示的 μ 和 σ 向量,我们用这些来计算 KL 散度。
# Inside training loop
reconstruction, z_mu, z_sigma = model(x=images)
…
kl_loss = 0.5 * torch.sum(z_mu.pow(2) + z_sigma.pow(2) - torch.log(z_sigma.pow(2)) - 1, dim=[1, 2, 3])
kl_loss = torch.sum(kl_loss) / kl_loss.shape[0]
其次,我们有我们的像素级损失,在这个项目中,我们采用 L1 距离来评估 AE-kl 重建与原始图像的差异。
l1_loss = F.l1_loss(reconstruction.float(), images.float())
接下来,我们有感知级损失。感知损失的想法是,不是评估输入图像与重建图像在像素级别的差异,而是将这两张图像通过一个预训练模型。然后,我们测量内部激活和特征图的距离。在 MONAI 生成模型中,我们简化了使用基于在医学图像上预训练的网络的感知网络(可在此处获取)。我们可以使用RadImageNet研究中的2D 网络(来自Mei 等),这些网络在超过130 万张医学图像上进行了训练!我们实现了2.5D 方法,使用 2D 预训练网络通过评估切片来评估 3D 图像。最后,我们可以访问MedicalNet来以纯 3D 方法评估我们的3D 图像。在这个项目中,我们使用了类似于Pinaya 等的方法,并使用了学习到的感知图像块相似度(LPIPS)度量(也可在 MONAI 生成模型中找到)。
# Instantiating the perceptual loss
perceptual_loss = PerceptualLoss(
spatial_dims=2,
network_type="squeeze",
)
...
# Inside training loop
...
p_loss = perceptual_loss(reconstruction.float(), images.float())
最后,我们使用对抗损失来处理重建的细节。对抗网络是Patch-Discriminator(最初由 Pix2Pix 研究提出),其中我们不仅对整个图像是否真实或虚假做出一个预测,还对图像中的多个块做出预测。
与原始的潜在扩散模型(Latent Diffusion Model)和稳定扩散(Stable Diffusion)不同,我们使用了来自最小二乘生成对抗网络(least square GANs)的鉴别器损失。虽然这不是更先进的对抗损失,但在对 3D 医学图像进行训练时已经显示出了有效性和稳定性(但仍有改进的空间😁)。尽管对抗损失可能相当不稳定,但它们与感知损失的结合也有助于稳定鉴别器和生成器的损失。
我们的训练循环和评估步骤可以在这里和这里找到。经过 75 轮训练后,我们使用 MLflow 包保存模型。我们使用MLflow 包来更好地监控实验,因为它组织了如 git hash 和参数等信息,并且使得可以用唯一的 ID 在组(称为实验)中存储不同的运行,并且使得比较不同结果变得更容易(类似于其他工具,如 weights and biases)。AE-KL 的日志文件可以在这里找到。
模型 — 扩散模型
接下来,我们需要训练我们的扩散模型。

扩散模型是类似于 U-Net 的网络,其中通常接收一个噪声图像(或潜在表示)作为输入,并预测其噪声成分。这些模型使用迭代去噪机制,通过具有多个步骤的马尔可夫链从噪声中生成图像。因此,该模型还依赖于时间步,以定义模型在采样过程的哪个阶段。

使用DiffusionModelUNet 类,我们可以为我们的扩散模型创建类似于 U-Net 的网络。我们的项目使用在此配置文件中定义的配置,其中定义了具有 3 个通道的输入和输出(因为我们的 AE-kl 具有 3 个通道的潜在空间),以及具有 256、512、768 通道的 3 个不同级别。每个级别有 2 个残差块。如前所述,传递时间步对模型很重要,它用于调整这些残差块的行为。最后,我们在网络内部定义注意力机制。在我们的情况下,我们在第二和第三级别(由 attention_levels 参数指示)中有注意力块,每个注意力头有 512 和 768 个通道(换句话说,每个级别有一个注意力头)。这些注意力机制很重要,因为它们允许我们通过交叉注意力方法将我们的外部条件(放射学报告)应用到网络中。

外部条件(或“上下文”)被应用到 U-Net 的注意力块。
在我们的项目中,我们使用了一个已经训练好的文本编码器。为简单起见,我们使用了来自稳定扩散 v2.1 模型(“stabilityai/stable-diffusion-2–1-base”)的相同编码器,将我们的文本标记转换为文本嵌入,这些嵌入将作为 DiffusionModel UNet 交叉注意力层中的 Key 和 Value 向量。我们文本嵌入的每个标记有 1024 个维度,我们在“with_conditioning”和“cross_attention_dim”参数中定义它。
from generative.networks.nets import DiffusionModelUNet
...
diffusion = DiffusionModelUNet(
spatial_dims=2,
in_channels=3,
out_channels=3,
num_res_blocks=2,
num_channels=[256, 512, 768],
attention_levels=[False, True, True],
with_conditioning=True,
cross_attention_dim=1024,
num_head_channels=[0, 512, 768],
)
除了模型定义之外,重要的是定义在训练过程中将噪声添加到输入图像中以及在采样过程中去除噪声的方式。为此,我们为我们的 MONAI 生成模型实现了 Schedulers 类以定义噪声调度器。在这个示例中,我们将使用一个DDPMScheduler,它具有 1000 个时间步长和以下超参数。

from generative.networks.schedulers import DDPMScheduler
...
scheduler = DDPMScheduler(
beta_schedule="scaled_linear",
num_train_timesteps=1000,
beta_start=0.0015,
beta_end=0.0205,
prediction_type="v_prediction",
)
在这里,我们选择了“v-prediction”方法,其中我们的 U-Net 将尝试预测速度分量(原始图像和添加噪声的组合),而不仅仅是添加的噪声。这种方法已被证明具有更稳定的训练和更快的收敛速度(也在arxiv.org/abs/2210.02303中使用)。
扩散模型训练
在训练扩散模型之前,我们需要找到一个合适的缩放因子。如 Rombach 等人所述,如果潜在空间分布的标准差过高,信噪比可能会影响使用 LDM 获得的结果。如果潜在表示的值过高,我们添加的高斯噪声的最大量可能不足以破坏所有信息。这样,在训练过程中,原始潜在表示的信息可能会存在,而实际上不应该存在,从而使得之后从纯噪声中生成图像变得不可能。KL 正则化可以在一定程度上有所帮助,但最佳实践是使用缩放因子来调整潜在表示的值。在这个脚本中,我们验证了训练集的一个批次中潜在空间分量的标准差的大小。我们发现我们的缩放因子应该至少为0.8221。在我们的案例中,我们使用了更为保守的值 0.3(类似于稳定扩散中的值)。
定义了缩放因子后,我们可以训练我们的模型。在这里,我们可以查看训练循环。
# Inside training loop
...
with torch.no_grad():
e = stage1(images) * scale_factor
prompt_embeds = text_encoder(reports.squeeze(1))[0]
timesteps = torch.randint(0, scheduler.num_train_timesteps, (images.shape[0],), device=device).long()
noise = torch.randn_like(e).to(device)
noisy_e = scheduler.add_noise(original_samples=e, noise=noise, timesteps=timesteps)
noise_pred = model(x=noisy_e, timesteps=timesteps, context=prompt_embeds)
if scheduler.prediction_type == "v_prediction":
# Use v-prediction parameterization
target = scheduler.get_velocity(e, noise, timesteps)
elif scheduler.prediction_type == "epsilon":
target = noise
loss = F.mse_loss(noise_pred.float(), target.float())
正如你所见,我们首先从数据加载器中获取图像和报告。为了处理这些图像,我们使用了 MONAI 的转换工具,并添加了一些 自定义转换,以从放射学报告中提取随机句子并对输入文本进行分词。在大约 10% 的情况下,我们使用一个空字符串(“” — 这是一个包含句子开始标记 (value = 49406) 和填充标记 (value = 49407) 的向量),以便在采样过程中使用无分类器引导。
接下来,我们获取潜在表示和提示嵌入。我们创建需要添加的噪声、用于本次迭代的随机时间步,以及所需的目标(速度分量)。最后,我们使用均方误差计算损失。
这个训练过程持续 500 个周期,日志可以在 这里 找到。
采样图像
在我们训练好两个模型之后,我们可以采样合成图像。我们使用 这个脚本。

这个脚本使用无分类器引导方法,这是 Ho 等人 提出的一个方法,用于在图像生成中强制执行文本提示。在这种方法中,我们有一个引导比例,可以用来牺牲生成数据的多样性,以获得与文本提示更高一致性的样本。默认值是 7.0。
在下图中,我们可以看到训练好的模型如何学会了临床特征以及这些特征的位置和严重程度。

评估
在这一部分,我们将展示如何使用 MONAI 的度量标准来评估我们生成模型的多个方面的性能。
使用 MS-SSIM 的自编码器重建质量
首先,我们验证我们的 Autoencoder-kl 如何重建输入图像。这是开发我们模型时一个重要的点,因为压缩和重建数据的质量将定义我们样本质量的上限。如果模型无法很好地从潜在表示中解码图像,或者它无法很好地建模我们的潜在空间,就无法以现实的方式解码合成表示。在这个脚本中,我们使用了来自测试集的 5000 张图像来评估我们的模型。我们可以使用多尺度结构相似性指标(MS-SSIM)来验证我们的重建效果。MS-SSIM 是一种广泛使用的图像质量评估方法,用于衡量两张图像之间的相似性。与传统的图像质量评估方法如 PSNR 和 SSIM 不同,MS-SSIM 能够捕捉图像在不同尺度上的结构信息。
在这种情况下,值越高,模型越好。对于我们当前的版本(0.2),我们观察到我们的模型的MS-SSIM 重建平均值为 0.9789。
样本的 MS-SSIM 多样性
我们将首先评估我们模型生成的样本的多样性。为此,我们计算不同生成图像之间的多尺度结构相似性指标。在这个项目中,我们假设如果我们的生成模型能够生成多样化的图像,那么在比较合成图像对时,它会呈现出较低的平均 MS-SSIM 值。例如,如果我们遇到了模式崩溃问题,我们生成的图像将看起来相似,MS-SSIM 值会比我们在真实数据集中观察到的值低得多。
在我们的项目中,我们使用未条件化的样本(使用“”(空字符串)作为文本提示生成的样本)以保持原始数据集的自然比例。如这个脚本所示,我们选择了 1000 个模型生成的合成样本,并使用 MONAI 的数据加载器来帮助加载所有可能的图像对。我们使用嵌套循环遍历所有可能的图像对,并忽略在两个数据加载器中选择相同图像的情况。在这里,我们可以观察到MS-SSIM 为 0.4083。我们可以在测试集的真实图像中进行相同的评估作为参考值。使用这个脚本,我们得到测试集的 MS-SSIM=0.4046,这表明我们的模型生成的图像与真实数据中的多样性相似。
然而,多样性并不意味着图像看起来好或真实。因此,我们将在下一步检查图像质量!
合成图像质量与 FID
最后,我们测量生成样本的Fréchet Inception Distance (FID)指标(链接)。FID 是一个评估两个组之间分布差异的指标,显示它们的相似度。为此,我们需要一个预训练的神经网络,从中提取特征用于计算距离(类似于感知损失)。在这个例子中,我们选择使用torchxrayvision 包中的神经网络。我们使用了一个 Dense121 网络(“densenet121-res224-all”),并选择了这个网络,以接近文献中用于 CXR 合成图像的网络。通过这个网络,我们得到一个 1024 维的特征向量。根据原始 FID 论文的建议,使用与特征数量相似数量的样本是重要的。因此,我们使用了 1000 张无条件图像,并将其与 1000 张测试集图像进行比较。对于 FID 来说,值越低越好,我们这里得到了一个合理的FID=9.0237。
结论
在这篇文章中,我们展示了一种使用MONAI 生成模型开发项目的方法,从下载数据到评估生成模型和合成数据。尽管这个项目版本可能更高效并拥有更好的超参数,我们希望它能很好地展示我们扩展所提供的不同功能。如果你有任何改进我们 CXR 模型的想法,或者希望为我们的包做贡献,请在我们的问题部分中留下评论,在这里或在这里。
我们训练的模型可以在MONAI 模型库中找到,与我们的 3D 脑部生成器及其他模型一起。我们的模型库使得下载模型权重和执行推理代码更加便捷。

欲获取更多教程并了解我们的功能,请访问我们的教程页面,点击此链接,并关注我以获取最新更新和更多类似的指南!😁
注意:所有图片除非另有说明,均由作者提供
每月生成更多优质见解
如何构建系统以更少的时间生成更多高质量的见解
·
关注 发表在Towards Data Science ·6 分钟阅读·2023 年 11 月 11 日
--
在《重新思考的 E-神话:为什么大多数小企业不起作用及其应对策略》中,迈克尔·E·格伯邀请小企业主停止“在他们的业务中”工作,开始“在他们的业务上”工作。书中的一个中心论点是,SMB 业主应该像他们想要特许经营他们的业务一样行事。这迫使他们(1)仔细审视他们所有的活动和流程,并且(2)优化和标准化这些活动和流程。通过这样做,他们将最大化其业务的收益,并使其可复制。这个想法类似于雷·达里奥在《原则》中表达的观点 — 为了一个团队能够成功,他们的经理需要在团队上工作(而不是在团队里工作),并建立一个能够最大化任何给定输入收益的系统。
在某种程度上 — 这些建议也适用于分析团队。对于一个分析团队 — 纲要地说 — 输入是把数据转化为见解的时间,输出是“高质量的见解”,两者之间的关系可以如下所示:
每月生成的高质量见解数 = 时间花在把数据转化为见解上 / 转化为高质量见解所需的平均时间
要增加团队生成的高质量见解数量,你需要努力增加把数据转化为见解的时间,或者减少转化为高质量见解所需的平均时间。你可以通过建立“系统”来实现这一点。

每月分解的见解(作者提供的图像)
增加把数据转化为见解的时间
时间花在把数据转化为见解上很明显是你的总员工时间的一个函数 — 因此增加员工数是一个显而易见的解决方案,但这可能不是最容易的方法。
另一种看待这个问题的方式是,把时间花在把数据转化为见解上,其结果可以用以下方程式表示:
时间花在把数据转化为见解 = 总员工时间 — 花在非数据工作上的时间
花在非数据工作上的时间包括“与利益相关者的协调”,“沟通”等元素。
-
这些任务对任何优秀数据工作的成功至关重要(如果没有对它们的兴趣或者没有适当地传达它们,生成见解有什么意义?)。
-
但通常这些任务被视为“事后补救”。很少见到有团队在这些元素上有明确的策略或流程 — 主要是因为这不像真正的数据工作那样“酷”,而且也不一定是他们的技能范围之内。
-
这导致这些任务花费的时间比预期的更多,并且超过了确保实际数据工作成功所需的时间。
通过(1)定义清晰的处理这些任务的流程,以及(2)通过时间的推移标准化和优化这些流程,你可以节省大量时间(即减少非数据工作所花费的时间),同时提高输出质量。
一个关于跨职能对齐的具体例子可能是从每个月开始时进行优先级排序会议。在进行的第一个月里,你会意识到为了进行一个好的优先级排序会议,你需要一个标准框架来做出优先级决策。你在第 2 个月引入了这个框架,并且它有效,但随后你会发现为了让它更好,你需要一个更好的流程来映射团队的潜在项目,所以你在第 3 个月引入了这个流程,依此类推。随着时间的推移,通过这种迭代的方法,你可以获得一个非常有效的流程,使你的团队花费更少的时间在“政治工作”上,更多地专注于洞察创造。
另一个关于公司范围内沟通的例子:你从第 1 个月开始时没有明确的流程,并意识到你的研究没有被充分利用。因此,在第 2 个月,你启动了一个月度论坛。在这些月度论坛中,你会发现你的利益相关者需要以某种方式展示数据,以便对他们更易于理解,所以你采用了一种特定的格式/模板等。
再次通过优化这些流程,不仅可以节省时间,以便重新投资于洞察创造,而且还可以为成功打下基础,因为这些耗时的非数据相关流程支持团队生成高质量洞察的能力。
减少将数据转化为高质量洞察所需的平均时间。
有几个因素可能会影响将数据转化为高质量洞察所需的时间。仅举几个例子:
-
分析师的技能
-
团队的支持
-
数据的可用性
-
工具的存在
提升分析师的技能,以减少他们将数据转化为高质量洞察所需的时间,是首要策略。技能越高,经验越丰富,他们将数据转化为高质量洞察的速度就越快。虽然团队级别的培训或个人辅导通常可以创造很多价值,但一种“软”方式是创建项目“模板”,以便更初级的分析师能够采纳最佳实践并迅速学习。例如,拥有模板可以迫使他们思考关键问题,如“痛点是什么”、“你的结果将在实际生活中如何使用”等,最终帮助他们在开始研究之前构建更强的陈述。
创建团队协作和知识共享的方式也可以减少洞察时间。可以像创建 Slack 频道或 Google 组并找到一些激励措施让人们参与一样简单——但这些小举措可以产生长远的效果。一旦这些“场所”存在,分析师可以在不确定如何继续时找到支持,利用团队的集体知识,并开展激发新想法的讨论。这也是为什么我认为定期会议很重要,在会议上分析师可以展示他们的工作——重点关注他们使用的方法论,因为这可以传播知识并提供灵感。
数据的可用性可能是一个大障碍。如果你必须花时间进行复杂的查询,因为没有简单的汇总数据库存在,而且每次都要三次检查结果,因为没有经过认证或集中管理的数据源,这不仅会给团队带来不必要的压力,还会浪费宝贵的时间。创建正确的数据管道以简化下游分析可以是一种有效的策略——如果这还没有做到的话。
最后,如果你需要频繁进行相同的分析,工具的存在可以减少你在重复工作上花费的时间。这在 A/B 测试等情况中很常见,你可以构建/购买自动化工具的许可证,让它们为你完成所有的统计测试,这样你就不用每次从实验中获得数据时都重新发明轮子。这需要有一个具体的、重复的使用案例,但当这种情况存在时,这可以大大减少洞察时间(附加分:这也是标准化输出质量的好方法)。
从根本上说,你有几种方法来减少平均洞察时间——我认为我离全面覆盖还很远。你还可以考虑知识管理、数据可发现性等——这完全取决于你的团队面临的主要痛点。
总结
我们可以重新调整我们的初始公式:
# 每月质量洞察 = (总人数时间 — 非数据工作的时间) / 平均质量洞察时间。
虽然增加总人数是一种解决问题的方法,但你也可以通过认真审视你的流程、基础设施、工具和“分析师支持”策略来达到类似的效果。
本文已转发至 Analytics Explained,这是一个新闻通讯,在这里我提炼了在各种分析角色中学到的知识(从新加坡的初创公司到旧金山的大型科技公司),并回答关于分析、增长和职业的读者问题。
基于词级 BERT 嵌入趋势生成句子级别嵌入

图片由 Igor Shabalin 提供
如何从词嵌入中推导句子级别的嵌入
·
关注 发表在 Towards Data Science ·5 min read·2023 年 1 月 5 日
--
句子(短语或段落)级别的嵌入常用于许多自然语言处理分类问题中,例如,垃圾邮件检测和问答(QA)系统。在我之前的文章中,发现 BERT 嵌入不同层次的趋势以确定语义上下文,我讨论了如何生成一个向量表示,该向量包含有关相对于同一标记静态嵌入值的上下文嵌入值变化的信息,然后可以将其用作生成句子级别嵌入的组件。本文扩展了这一主题,探索了从句子中的哪些标记中提取这样的趋势向量,以便能够为整个句子生成有效的嵌入。
直觉
与此相关的第一个问题是:从句子中的多少个标记中提取嵌入以生成整个句子的有效嵌入?如果你回顾之前的讨论,我们得到的是一个向量——为句子中最重要的单词提取的——其中包含有关整个句子上下文的信息。然而,为了更好地了解句子上下文,拥有一个与最重要单词在句法上最相关的单词的向量也很有帮助。为什么我们需要这个?
生活中的一个简单类比可以帮助回答这个问题:如果你坐在塔楼内的餐厅里欣赏周围的美景——你所观察的视角将不包括塔楼本身。要拍摄塔楼的视角,你首先需要离开塔楼。
好的,我们如何确定句子中与最重要单词在句法上最相关的单词呢?(即,你需要根据之前的类比确定拍摄塔楼照片的最佳位置)答案是:借助注意力权重,你也可以从 BERT 模型中获得这些权重。
实现
在你可以跟随本文其余部分讨论的代码之前,你需要参考之前的文章中提供的示例(我们将使用该示例中定义的模型和生成的向量表示)。你需要做的唯一修正是:在创建模型时,确保使模型返回不仅是隐藏状态,还有注意力权重:
model = BertModel.from_pretrained(‘bert-base-uncased’,
output_hidden_states = True, # so that the model returns all hidden-states.
output_attentions = True
)
其他所有内容,包括示例句子,都可以直接使用。实际上,我们将只使用第一个示例句子:“我想要一个苹果。”
下面我们正在确定与最重要的单词(在这个例子中是“想要”)在句法上最接近的单词。为此,我们检查所有 12 层的注意力权重。首先,我们创建一个空数组(不计算特殊符号,排除第一个和最后一个符号):
a = np.empty([0, len(np.sum(outputs[0].attentions[0][0][11].numpy(), axis=0)[1:-1])])
接下来,我们填充注意力权重矩阵:
for i in range(12):
a = np.vstack([a,np.sum(outputs[0].attentions[0][0][i].numpy(), axis=0)[1:-1]])
我们对标点符号不感兴趣。所以,我们将删除矩阵中的最后一列:
a = np.delete(a, -1, axis=1)
所以我们的矩阵现在看起来如下(12x4,即 12 层和 4 个单词)
print(a)
[[0.99275106 1.00205731 0.76726311 0.72082734]
[0.7479955 1.16846883 0.63782167 1.39036024]
[1.23037624 0.40373796 0.57493907 0.25739866]
[1.319888 1.21090519 1.37013197 0.7479018 ]
[0.48407069 1.15729702 0.54152751 0.57587731]
[0.47308242 0.61861634 0.46330488 0.47692096]
[1.23776317 1.2546916 0.92190945 1.2607218 ]
[1.19664812 0.51989007 0.48901123 0.65525496]
[0.5389185 0.98384732 0.8789593 0.98946768]
[0.75819892 0.80689037 0.5612824 1.10385513]
[0.14660755 1.10911655 0.84521955 1.00496972]
[0.77081972 0.79827666 0.45695013 0.36948431]]
现在让我们确定“Want”(第二列,索引为 1)在哪些层中吸引了最多的注意力:
print(np.argmax(a,axis=1))
b = a[np.argmax(a,axis=1) == 1]
array([1, 3, 0, 2, 1, 1, 3, 0, 3, 3, 1, 1])
接下来,我们可以确定在“Want”领先的层中,哪个标记在“Want”之后吸引了更多的注意力。为此,我们首先删除“Want”列,然后探讨其余的三列:
c = np.delete(b, 1, axis=1)
d = np.argmax(c, axis =1)
print(d)
counts = np.bincount(d)
print(np.argmax(counts))
[0 2 2 2 0]
2
上述内容表明,我们有单词 Apple(在这里删除了“Want”后,Apple 的索引为 2)作为与单词“Want”在句法上最相关的单词。这是相当意料之中的,因为这些词分别代表了直接宾语和及物动词。
_l12_1 = hidden_states[0][12][0][4][:10].numpy()
_l0_1 = hidden_states[0][0][0][4][:10].numpy()
_l0_12_1 = np.log(_l12_1/_l0_1)
_l0_12_1 = np.where(np.isnan(_l0_12_1), 0, _l0_12_1)
现在让我们比较从单词 Apple 和 Want 的嵌入中得到的向量。
print(_l0_12_1)
array([ 3.753544 , 1.4458075 , -0.56288993, -0.44559467, 0.9137548 ,
0.33285233, 0\. , 0\. , 0\. , 0\. ],
dtype=float32)
print(l0_12_1) # this vector has been defined in the previous post
array([ 0\. , 0\. , 0\. , 0\. , -0.79848075,
0.6715901 , 0.30298436, -1.6455574 , 0.1162319 , 0\. ],
dtype=float32)
如你所见,上述两个向量中匹配元素对的一项值在大多数情况下为零,而另一项值非零——即这些向量看起来是互补的(记住塔楼视角的类比:塔楼可以看到邻近的景象,但要看到塔楼本身——也许是主要的吸引物——你需要离开它)。因此,你可以安全地逐元素相加这些向量,将可用的信息合并为一个单一的向量。
s = _l0_12_1 + l0_12_1
print(s)
array([ 3.753544 , 1.4458075 , -0.56288993, -0.44559467, 0.11527407,
1.0044425 , 0.30298436, -1.6455574 , 0.1162319 , 0\. ],
dtype=float32)
上述向量可以作为句子级别分类的输入。
结论
本文提供了关于如何基于从静态嵌入到上下文嵌入的转换趋势,在词级 BERT 嵌入中生成句子级别嵌入的直观理解和代码。这种句子级别的嵌入可以作为 BERT 生成的 CLS 标记嵌入的替代方案用于句子分类,这意味着你可以尝试这两种方法,以查看哪一种最适合你的特定问题。
使用 Python 生成合成数据
原文:
towardsdatascience.com/generating-synthetic-data-with-python-ea15fd0555ee
创建合成数据的全面指南
·发布在Towards Data Science ·阅读时长 14 分钟·2023 年 7 月 31 日
--

作者提供的图片
我们一次又一次地听到数据在推动增长、创新和竞争力方面的关键作用。它已经成为所有行业成功的基石。本质上,数据已成为我们每一个努力的基础,无论是撰写技术博客、教育内容、测试产品或调试软件,还是探索 AI/ML 训练模型和算法的复杂性,数据都处于所有这些任务的核心。
获得完全适合各种需求和兴趣的精确数据可能是一项艰巨的任务。在互联网上寻找你所需的确切数据既令人沮丧又耗时。即使你成功找到合适的数据,清理和处理数据的过程也可能需要宝贵的时间、资源和费用。此外,隐私问题、数据敏感性、版权和监管限制通常是重要的障碍。例如,包含敏感信息的数据集,如医疗数据、财务记录数据,或从版权网站获取的演示数据集等。
在这样的情况下,合成数据会拯救我们!在本文中,我们将探讨合成数据的全部内容,以及如何使用 2 个不同的库在 Python 中生成它。
什么是合成数据?
根据维基百科,合成数据是人工生成的数据,而非源自现实世界事件。用最简单的话来说,
合成数据 = 虚假数据
它是现实数据的复制,可能保持其相似性而不泄露有关真实个人、情况或实体的任何特定信息。你可能已经听说过不同的术语,包括计算机生成的数据、人工数据、AI 生成的数据或模拟数据,但本质上,它们都是或多或少相同的——虚假数据。
为什么需要合成数据?
你可能会想知道,既然我们已经拥有大量真实世界的数据,为什么还需要合成数据。它有多种价值,它允许我们创建看起来像真实数据但不包含任何有关人员或情况的真实信息的附加数据。合成数据帮助我们保护隐私(当真实数据不能向他人公开时),解决数据稀缺问题(当适合的数据有限或无法进行分析或研究时),并在不依赖敏感/受限的真实数据的情况下测试 AI/ML 模型,或者在受控条件下测试非常特定的行为。
还有许多其他情况需要合成数据。例如,真实数据可能难以获得或成本过高,或者数据点太少(数据集不够大,或包含的样本数量不足以有效训练模型、得出重要结论或获得准确结果)
设想一个银行希望预测其借贷部门客户的信用风险。他们需要历史数据,包括信用行为、还款历史、收入、姓名、联系方式和其他相关细节。然而,由于数据隐私问题和遵守数据保护法规如GDPR,以及过去数据的有限可用性和高数据获取成本,他们可能缺乏足够的数据来训练模型或通过数据分析得出结论。
仅有少量真实数据时,捕捉复杂性和信用模式变得困难,从而导致预测的潜在不准确性。为了解决这个问题,银行可以生成类似于真实客户特征和信用行为的合成数据集。这可以提高模型性能,减少过拟合风险,并提供更准确的预测,使银行能够做出明智且可靠的信用决策。
如何生成合成数据?
现在我们对合成数据是什么以及为什么需要它有了更多了解,接下来让我们进入下一步:如何生成它?合成数据主要有两种生成方式,
1. 通过复制实际数据的部分并将其用作参考,
-
通过对原始数据集应用变换或修改来创建现有真实数据的变体。这个过程通常被称为数据增强。
-
例如,在文本数据中,你可以引入一些小的变化,例如随机插入或删除文本、在保持核心含义不变的情况下重新措辞等,以扩展数据集的大小。
2. 通过从头创建一个全新的数据集,
-
另外,你可以根据某些标准从头生成完全新的数据点。
-
这种方法通常用于处理敏感或私人数据,因为它确保合成数据集中没有暴露真实数据。
两种方法各有优点,可根据数据生成任务的具体要求和目标选择,如真实数据的可用性、数据隐私问题、数据的复杂性以及合成数据集的预期用途。在本文中,我们将使用第二种方法。
情景
想象一下,我们需要创建一个包含进行信用风险分析所需的所有信息的客户表。以下是我打算创建的字段列表,
-
客户 ID:表中每个客户的唯一标识符。
-
客户姓名:客户的名字。
-
年龄:客户的年龄,因为这可能是信用风险评估的相关因素。
-
收入:客户的收入水平,这对于评估他们偿还贷款能力至关重要。
-
信用评分:基于客户的信用历史评估其信用 worthiness。
-
债务收入比:客户的总债务与其收入的比例,显示其财务稳定性。
-
就业状态:客户的就业状态——在职、失业、自雇等。
-
贷款金额:客户申请的贷款金额。
-
贷款期限:客户偿还贷款的时间长度。例如,如果贷款期限为 36 个月,客户必须在 36 个月内偿还贷款。贷款期限影响还款计划,较长的期限可能导致较低的月付款但较高的总体利息,而较短的期限可能导致较高的月付款但较低的总体利息。
-
付款历史:客户在贷款和信用账户上的过往付款行为。
-
受抚养人数:财务上依赖于客户的人员数量。
在 Python 中,你可以找到各种创建合成数据的库。它们提供了不同级别的复杂性和功能来生成合成数据。根据你的需求,你可以选择最适合的库。务必检查,
-
官方文档
-
社区支持
以获取这些库的最新信息。在本文中,我们将使用以下 Python 库,
-
Faker
-
随机
破解代码
在开始编码之前,我们先了解一下我们将要使用的库,
#import required library
import random
import pandas as pd
from typing import Dict
from faker import Faker
-
Faker - 这是一个生成合成数据的 Python 库,提供了多种功能来创建真实看起来的个人信息。
-
random - 这是一个标准的 Python 库,提供生成随机数据的函数。它最常用于生成随机整数、浮点数和从元素列表中进行随机选择。
-
Pandas - 这是一个流行的 Python 库,用于数据操作,包括数据清理、过滤、分组、合并等任务。它设计用于处理结构化数据,如表格和时间序列数据,提供了高效的数据分析强大功能。
-
typing - 这是一个 Python 库,提供了将类型提示添加到代码中的工具。类型提示通过指定变量和函数参数的预期类型来帮助提高代码的清晰度和可读性。它在 Python 3.5 中引入。
现在,让我们开始编程吧!第一步是创建一个 Faker 实例。
#create a Faker instance and set the locale to GB(Great Britain)
fake = Faker(['en_GB'])
我们为什么需要它?嗯,Faker 实例充当一个 生成器,可以创建各种类型的数据。生成器 是 Python 中的一个对象,它按请求一次生成一个值的序列。你可以在这里了解更多,
对 Python 的生成器函数和生成器表达式的基本介绍。
通过使用 Faker 提供的函数,你可以轻松生成看起来像真的数据!你还可以在设置 Faker 实例时指定区域。区域指的是具有自己独特文化、语言和信息呈现方式的特定地方或国家。当我们将区域设置为特定地区时,Faker 生成的数据将匹配该地方的特征,比如该国家常见的名字和地址。
在上面的代码中,使用 “en_GB” 允许我们访问针对英国(United Kingdom)量身定制的函数,提供特定于地点的数据。
现在,进行编程的下一步,让我们创建一个生成所有所需字段的函数,按照给定的规格。
def generate_customer_data(num_records: int):
customer_data: Dict[str, list] = {
'customer_id': [fake.aba() for i in range(num_records)],
'customer_name': [fake.name() for name in range(num_records)],
'age': [random.randint(18, 70) for age in range(num_records)],
'income(£)': [random.randint(20000, 100000) for income in range(num_records)],
'credit_score': [random.randint(300, 850) for score in range(num_records)],
'debt_to_income_ratio': [round(random.uniform(0.1, 1.0), 2) for ratio in range(num_records)],
'employment_status': [random.choice(['Employed', 'Unemployed', 'Self-employed']) for status in range(num_records)],
'loan_amount': [random.randint(1000, 50000) for amount in range(num_records)],
'loan_term': [random.choice([12, 24, 36, 48, 60]) for term in range(num_records)],
'payment_history': [random.choice(['Good', 'Fair', 'Poor']) for history in range(num_records)],
'number_of_dependents': [random.randint(0, 5) for dep in range(num_records)]
}
return customer_data
在上面的代码块中,
-
我们创建了一个名为 ‘generate_customer_data’ 的函数,该函数接受一个参数 ‘num_records’,表示我们希望生成的合成客户记录的数量。
-
‘(num_records: int)’ 指定了一个类型提示,表示该函数期望一个整数作为此参数。
-
在 ‘generate_customer_data’ 函数内部,我们正在创建一个名为 ‘customer_data’ 的字典,用于保存每个客户的合成数据。
-
‘customer_data: Dict[str, list]’ 表示字典的 keys 将是字符串,而相应的 values 将是 lists。
我们使用类型提示来指定预期的数据类型,以便更好地理解和阅读代码。
让我们理解一下上面代码块的第一行,
#customer_id:- a unique identifier for each customer in the table
'customer_id': [fake.aba() for i in range(num_records)]
在这里,‘customer_id’ 是键,对应的值是一个 list。这一行代码使用 Faker 库中的 ‘fake.aba()’ 方法生成一个 list 类型的客户 ID。这个方法随机生成一个 9 位数的数字,我们将用它作为唯一的客户 ID。
如果你注意到,这是一行代码,通常被称为“一行代码”。在这里,它被称为列表推导式,是一种在 Python 中编写列表的简洁方法。

作者提供的图片:列表推导式的常见语法
你可以在这里阅读有关 Python 支持的各种推导式的更多信息,
要了解 Python 的推导式功能,首先了解推导式的概念是很重要的…
[towardsdatascience.com
📌 附注
#customer_id:- a unique identifier for each customer in the table
'customer_id': [fake.aba() for i in range(num_records)]
#preferred approach
'customer_id': [fake.aba() for _ in range(num_records)]
在代码的第二行for循环中,我使用了‘_’(下划线)代替‘i’。这两种方法都是正确的,但使用‘_’ 是更常见和推荐的做法,当我们不打算使用循环变量时(在这种情况下,‘i’ 是循环变量)。
这是一个更清晰的方式来表示循环变量在推导式中不相关或未被使用。这使得代码更加易读和标准,帮助他人更容易理解你的代码。如果你使用‘i’ 作为循环变量,它可能暗示着有某种用途,导致混淆和不必要的变量赋值。因此,
当你不需要循环变量时,使用下划线作为占位符是一种良好的实践,使你的代码更干净、更简洁。
既然如此,由于我正在写一篇适合初学者的文章,我选择了使用循环变量。然而,传达最佳方法也是很重要的!
接下来,在类似的方式下,我为客户表生成了所有必需字段的合成信息,
-
‘customer_name’: [fake.name() for name in range(num_records)]
这段代码使用 ‘fake.name()’ 方法从 Faker 库生成每个客户的名称。这个方法用于生成一个随机和合成的名字,看起来像是一个真实的人名。它可以生成来自各种文化和地区的名字,包括名字和姓氏。由于在代码开头我们将区域设置为‘en-GB’,这确保了合成的数据与英国非常相似。
-
‘age’: [random.randint(18, 70) for age in range(num_records)]
这一行生成了一个客户年龄的列表,使用了‘random.randint()’函数。此函数用于生成指定范围内的随机整数。你提供两个数字作为参数——第一个是最小值,第二个是最大值。它然后返回范围内的一个随机整数,包括最小值和最大值。在我们的情况下,此函数生成每个客户的随机年龄,范围在18和70之间,包括18和70。

作者提供的图片:random.randint() 的语法
-
‘income’: [random.randint(20000, 100000) for income in range(num_records)]
这生成了一个随机收入水平的列表,使用了‘random.randint()’函数。如前所述,每个收入是一个随机整数,范围在20,000和100,000之间,包括20,000和100,000。
-
‘credit_score’: [random.randint(300, 850) for score in range(num_records)]
这生成了一个信用评分的列表,使用了‘random.randint()’函数。到目前为止,你已经理解了这个过程:它为每个客户生成一个随机的信用评分,范围在300和850之间,包括300和850。
-
‘debt_to_income_ratio’: [round(random.uniform(0.1, 1.0), 2) for ratio in range(num_records)],
这生成了一个债务收入比的列表,使用了‘random.uniform()’函数。此函数用于生成指定范围内的随机小数。你提供两个数字作为参数:下限和上限,然后函数会生成这两个值之间的随机小数,包括下限和上限。‘round(random.uniform(0.1, 1.0), 2)’表示每个比率是一个0.1到1.0之间的随机浮点数,四舍五入到两位小数。

作者提供的图片:random.uniform() 的语法
-
‘employment_status’: [random.choice([‘Employed’, ‘Unemployed’, ‘Self-employed’]) for status in range(num_records)],
这生成了一个每个客户的就业状态列表,使用了‘random.choice()’函数。此函数允许你从列表或任何元素序列中选择一个随机项。你提供列表或序列作为参数,函数将随机选择并返回该序列中的一个元素。在我们的背景下,每个客户的状态是从给定的选项列表中随机选择的:‘Employed’,‘Unemployed’,或‘Self-employed’。

作者提供的图片:random.choice() 的语法
-
‘loan_amount’: [random.randint(1000, 50000) for amount in range(num_records)],
这生成了一个每个客户的贷款金额列表,使用了‘random.randint()’函数。每个客户的贷款金额是1,000和50,000之间的随机整数,包括1,000和50,000。
-
‘loan_term’: [random.choice([12, 24, 36, 48, 60]) for term in range(num_records)],
这一行使用‘random.choice()’函数生成一个贷款期限列表。正如我们之前讨论的‘employment_status’,每个客户的贷款期限是从给定的选项列表中随机选择的:12、24、36、48或60个月。
-
‘payment_history’: [random.choice([‘Good’, ‘Fair’, ‘Poor’]) for history in range(num_records)],
这一行使用‘random.choice()’函数生成一个支付历史列表。每个支付历史都是从选项列表中随机选择的:‘Good’、‘Fair’或‘Poor’。
-
‘number_of_dependents’: [random.randint(0, 5) for dep in range(num_records)],
这生成了一个依赖人数的列表,使用‘random.randint()’函数。每个依赖人数是0到5之间的随机整数,包括0和5。
这个解释相当详细,是吧?现在,随着我们接近循环的结束,处理完所有客户后,我们返回包含合成数据的‘customer_data’字典。
现在我们已经定义了函数中每个字段的必要细节,让我们继续调用函数。但在此之前,我们将设置要生成的记录总数,并将其存储在变量‘number_of_rows’中。完成后,我们将使用这个值调用‘generate_customer_data’函数。在这里,我们生成10000个合成记录。
#total number of records to generate
number_of_rows: int = 10000
#generate synthetic data for the Customer table
customer_data: Dict[str, list] = generate_customer_data(number_of_rows)
‘customer_data: Dict[str, list]’表示该函数以字典形式返回合成数据,其中keys将是strings,对应的values将是lists。
进入代码的最后部分,在提取所有必要的信息后,我们有选择下一步的方案。我们可以将其导出到CSV、Excel或JSON文件中,也可以打印出来。然而,保留数据在 pandas DataFrame 中是个好主意。这样你可以立即使用它,也可以在未来进行数据分析。使用 DataFrame,你可以执行各种操作,清理和准备数据,分析数据,并顺畅地与其他库配合使用。
#create a pandas DataFrame from the dictionary 'customer_data'
df_customer: pd.DataFrame = pd.DataFrame(customer_data)
#export the synthetic data to a CSV file
outout_file: str = 'synthetic_customer_data.csv'
df_customer.to_csv(outout_file, index=False, encoding='utf-8', header="true")
print(f"Synthetic data is created according to specific requirements and saved to {outout_file}")
在上面的代码块中,我们使用了函数‘generate_customer_data’返回的字典‘customer_data’来创建一个 pandas DataFrame‘df_customer’。‘df_customer: pd.DataFrame’表示‘df_customer’是‘pd.DataFrame’*类型的变量,这意味着它是一个 Pandas DataFrame。
最后,让我们将这些数据保存到CSV文件中。请注意,默认情况下,文件将创建在与程序相同的目录中。
#export the synthetic data to a CSV file
outout_file: str = 'synthetic_customer_data.csv'
df_customer.to_csv(outout_file, index=False, encoding='utf-8', header="true")
在这里,我们创建了‘synthetic_customer_data.csv’作为输出文件,并将其存储到变量‘output_file’中。‘to_csv()’将把 Pandas DataFrame 保存为CSV文件。
-
index=False表示 DataFrame 的索引列将不会包含在结果CSV文件中。默认情况下,它设置为true。
-
encoding=’utf-8' 指定了在将 DataFrame 写入 CSV 文件时使用的字符编码。
-
header=”true” 包括列名作为 CSV 文件中的表头行。默认情况下,设置为 true。
结论
本文旨在介绍使用 Python 生成合成数据的基础知识。通过使用各种库,如 scikit-learn、SDV、Gretel、CTGAN、faker、random 等,你可以高效地为各种用例生成数据。
尽管合成数据提供了众多好处,但它也有其缺点。一些常见的缺点包括可能无法准确复制现实世界的复杂性、准确表示稀有事件的挑战,以及如果没有仔细创建和验证可能引入的偏见。合成数据生成的有效性依赖于你的具体需求和可用的库。因此,彻底评估你的需求和可用工具的能力至关重要,因为它们直接影响生成的合成数据的准确性和有用性。
这里有一些资源可以帮助你开始使用合成数据:
Cassie Kozyrkov,谷歌的首席决策科学家,撰写了关于合成数据的详尽入门指南,值得一读,
-
什么是合成数据?
-
合成数据实用指南
-
AI 生成的合成数据
本文使用的代码可在我的 GitHub 仓库 中找到,生成的输出文件‘synthetic_customer_data.csv’ 可在 GitHub Gist 中访问。
祝学习愉快!
生成式 AI 伦理
自主内容时代的关键考虑因素
·
关注 发布于 Towards Data Science ·11 分钟阅读·2023 年 7 月 25 日
--
作者提供的照片 — 大卫·E·斯维诺
随着围绕生成式人工智能(AI)的喧嚣,关于如何负责任地实施这一变革性技术的问题越来越多。本文将回顾欧盟(EU)的 AI 伦理指南,并讨论在使用大型语言模型(LLMs)时实施 AI 伦理框架的关键考虑因素。
可信 AI 伦理指南
2019 年 4 月 8 日,欧盟实施了人工智能(AI)的伦理和负责任使用框架。报告定义了构建可信 AI 的三项指导原则:
-
合法性:AI 应遵守法律法规和当地规定。
-
伦理:AI 系统应具备伦理性,遵循伦理原则和价值观。
-
稳健:由于 AI 可能在短时间内对大规模人群造成显著危害,它需要在技术和社会上都具备稳健性。
对于跨国公司而言,这提出了一个有趣的问题:如何在地缘政治边界之间应用这一框架,因为在世界某个地区被视为合法和伦理的做法,可能在另一个地区并不适用。许多公司采取最严格的规定,并在所有地区单方面适用。然而,“一刀切”的方法可能并不适用或可接受。
欧盟的框架见下图 1.1。
图 1.1:欧洲联盟的 AI 伦理框架

作者提供的图示——David E. Sweenor,TinyTechGuides 创始人
基于这三项基础原则,得出四项伦理原则和七项关键要求。这些伦理原则包括:
-
尊重人类自主权:这一原则强调,人类应在与 AI 的互动中保持控制和自由。“AI 系统不应无理地使人类从属、胁迫、欺骗、操控、条件化或驱使。”[1] 从根本上讲,AI 应支持人类参与民主过程。我们已经看到一些国家对公民实施“社会评分”,这应引起关注。
-
防止伤害:AI 系统不应造成身体、心理或情感上的伤害。鉴于 AI 的普及性和迅速影响,重要的是要密切监控 AI 的输出,以防止“由于权力或信息的不对称”对公民、员工、企业、消费者和政府的无意操控。[2] 我们已经看到自动驾驶汽车制造商在所谓的AI 电车难题中与这一原则斗争。当然,这不仅限于机器人系统;人们依赖 ChatGPT 获取医疗建议,鉴于其生成虚假信息的倾向,我们需要小心。
-
公平性:AI 系统应当无偏见且不歧视,旨在“实现利益和成本的平等分配”。[3] 公平性意味着不应削弱人类选择,并且“AI 从业者应平衡竞争利益和目标,尊重手段与目的之间的比例原则”。[4] 从表面上看,这似乎很简单,但你知道吗,公平性有超过二十种数学定义?[5]
-
可解释性:AI 系统需要透明、可审计、可复现和可解释。如果 AI 用于决定影响你的事情,你有权了解算法如何做出该决定。例如,如果你被拒绝了信用,AI 系统的操作员应该能够提供所有影响该决定的因素。当使用“黑箱”模型时,例如神经网络和生成对抗网络(GANs),这可能会成为问题。
这引出了七项要求:
-
人类代理和监督:本质上,这一要求指出 AI 系统应该尊重人权,不应完全自主运行。AI 应该辅助,而不是替代人类决策。应有一个挑战 AI 决策的过程,并且在必要时,人类应能够覆盖 AI 的决策。这听起来不错,但当数以百计或千计的决策自动做出时,你如何有效跟踪所有这些决策以确保不会出错?
-
技术健壮性和安全性:AI 系统需要安全、稳健,并能够抵御恶意行为者和网络攻击。它们应提供准确的预测,且可靠、可复现。组织必须优先考虑网络安全,并制定攻击的应急计划及系统离线时的操作方式。他们需要特别注意对抗性数据投毒,即恶意行为者修改训练数据以导致错误预测。
-
隐私和治理:“AI 系统必须在整个系统生命周期内保证隐私和数据保护。”[6] AI 系统的开发者需要设置保护措施,以防止恶意数据或代码被输入系统。指南还强调,只有授权用户才能访问个人数据,这些数据必须公平、公正,并遵守整个生命周期中的所有隐私规定。组织需要思考的一个领域是什么构成“授权用户”?你看到过Roomba 拍摄女性上厕所的案例吗?
-
透明性:组织必须能够追溯数据的来源,了解其来源、如何收集、转化和使用。这一过程应该是可审计的,AI 输出应该是可解释的。这对数据科学家来说是一个挑战,因为可解释的模型通常不如“黑箱”算法准确。此要求还说明,人与 AI 互动时应该意识到这一点——换句话说,AI 不应该假装成真人,应该清楚我们是在与机器人互动。
-
多样性、非歧视和公平性:AI 应平等对待所有群体——这可能说起来容易做起来难。该要求建议设计师应包括来自不同文化、经验和背景的人,以帮助减轻许多文化中存在的历史偏见。AI 应对所有人都可及,无论是否有残疾或其他因素。这就引发了一个问题,什么定义了“群体”?显而易见的保护类别包括年龄、种族、肤色、地区/信仰、国籍、性别、年龄、身体或精神残疾或退伍军人身份。是否还有其他因素需要考虑?如果我是保险公司,我可以对拥有“更健康”习惯的人收取比那些被认为“不健康”的人更少的费用吗?
-
社会和环境福祉:AI 系统应致力于改善社会、促进民主,并创建环保和可持续的系统。仅仅因为你可以做某件事,并不意味着你应该做这件事。商业领袖需要批判性地考虑 AI 的潜在社会影响。训练 AI 模型的成本是什么?这些成本是否与您的环境、社会和公司治理(ESG)政策相抵触?我们已经看到社交媒体平台如 TikTok 在向孩子推送有害内容。
-
问责制:AI 系统设计师应对其系统负责,这些系统应可审计,并提供一种方式,让受决策影响的人能够纠正任何不公平的决定。设计师可能会对对个人或群体造成的任何伤害负责。这引发了一个有趣的问题——如果系统出现故障,谁应负责?是基础模型的提供者,还是使用生成式 AI 的公司?
虽然这些原则表面上似乎直观,但“在这些原则的解释、重要性、涉及的问题、领域或行为者以及如何实施方面存在实质性差异。”[7]
LLM 的 AI 伦理考虑
现在我们了解了欧盟 AI 伦理指南,让我们深入探讨 LLM 的独特考虑因素。
在上一篇博客中,GenAIOps: Evolving the MLOps Framework 我概述了生成式 AI 和 LLM 的三项关键能力,包括:
● 内容生成:生成式 AI 可以生成类似人类质量的内容——包括文本、音频、图像/视频甚至软件代码。现在,需要注意的是,生成的内容可能不准确——最终用户有责任确保生成的内容真实且不具误导性。开发人员需要确保生成的代码没有漏洞和病毒。
● 内容总结与个性化:快速浏览大量文档并迅速总结内容是生成性人工智能的一个强项。除了快速创建文档、电子邮件和 Slack 消息的总结外,生成性人工智能还可以为特定个人或角色个性化这些总结。
● 内容发现与问答:许多组织在不同的数据孤岛中分散着大量的内容和数据。许多数据和分析供应商正在利用大型语言模型(LLMs)和生成性人工智能自动发现并连接这些分散的来源。最终用户可以用普通语言查询这些数据,以理解关键点并深入探讨更多细节。
鉴于这些不同的能力,我们在创建人工智能伦理框架时需要考虑哪些因素?
人工干预与监督
由于生成性人工智能本质上可以自主生成内容,这可能会降低人工干预和监督的程度。你可以想象一下,你每天收到多少垃圾邮件?营销团队创建这些邮件,将它们加载到营销自动化系统中,然后按下“开始”按钮。这些系统在自动驾驶模式下运行,往往被遗忘并持续运行。
鉴于生成性人工智能可以以惊人的速度生成文本、图像、音频、视频和软件代码,我们可以采取哪些步骤来确保有人工干预;特别是在关键应用中?如果我们在自动化医疗建议、法律建议和其他更“敏感”的内容时,组织需要认真思考如何保持对这些系统的控制和监督。公司需要采取保障措施,确保做出的决策符合人类的价值观和意图。
技术稳健性与安全性
众所周知,生成性人工智能模型可能会生成意外或甚至有害的内容。公司需要严格测试和验证其生成性人工智能模型,以确保它们是可靠和安全的。此外,如果生成的内容有误,我们需要有机制来处理和纠正这些输出。互联网充满了糟糕和具有分裂性的内容,一些公司已经雇佣内容审核员来尝试审查可疑内容,但这似乎是一个不可能完成的任务。最近有报道称,这些内容可能对心理健康造成严重损害(美联社新闻 — 肯尼亚的 Facebook 内容审核员称工作是‘折磨’。他们的诉讼可能会产生全球影响。)
隐私与治理
生成性 AI 模型是在从互联网收集的数据上进行训练的。许多 LLM 制造商实际上并没有公开详细的训练数据细节。现在,这些模型可能在敏感或私人数据上进行训练,而这些数据本不应公开可用。看看三星,因无意中泄露专有数据而被禁止使用生成性 AI 工具(TechCrunch — 三星在 4 月内部数据泄露后禁止使用生成性 AI 工具如 ChatGPT)。如果生成性 AI 产生的输出包含或类似真实的私人数据会怎样?根据《彭博法律》,OpenAI 最近被起诉诽谤由于 ChatGPT 的幻觉。
我们可以肯定地说,公司需要对用于训练生成性 AI 模型的数据来源有详细的了解。当你使用自己的数据对模型进行微调和适配时,你可以选择删除或匿名化这些数据。然而,如果基础模型提供者使用了不适合模型训练的数据,你仍可能面临风险。如果是这种情况,谁应负责任?
透明度
就其本质而言,“黑箱”模型很难解释。实际上,许多这些 LLMs 具有数十亿个参数,所以我建议它们不可解释。公司应致力于透明度,创建有关模型工作原理、局限性、风险和用于训练模型的数据的文档。同样,这说起来容易做起来难。
多样性、非歧视和公平
与上述相关,如果没有得到妥善训练和管理,生成性 AI 可能会产生偏见或歧视性的输出。公司可以尽力确保数据的多样性和代表性,但鉴于许多 LLM 提供者并未公开用于训练的数据,这是一项艰巨的任务。除了尽可能了解训练数据的风险和局限性之外,公司还需要建立监控系统,以检测有害内容,并设立机制以标记、阻止其分发并根据需要进行纠正。
社会和环境福祉
对于具有 ESG 计划的公司来说,训练 LLMs 消耗大量计算资源——即消耗相当多的电力。当你开始部署生成性 AI 功能时,组织需要关注环境足迹,并寻求减少的方法。有几位研究人员正在寻找减少模型大小和加速训练过程的方法。随着这一领域的发展,公司至少应在年报中考虑环境影响。
责任
这将成为未来几年内一个活跃的诉讼领域。如果生成性 AI 产生有害或误导性内容,谁应对此负责?谁在法律上负有责任?在美国法院系统中已有几个诉讼悬而未决,这些诉讼将为未来的其他诉讼奠定基础。除了有害内容之外,如果你的 LLM 生成了衍生作品呢?你的 LLM 是否在受版权或法律保护的材料上进行了训练?如果它生成了数据衍生物,法院将如何处理这一问题?随着公司实施生成性 AI 能力,应当建立控制和反馈机制,以便在出现问题时采取相应的行动。
摘要
生成性 AI 在革新世界运作方式方面蕴藏着巨大潜力,但其快速演变也带来了众多伦理困境。随着公司进入生成性 AI 的领域,必须深刻理解已建立的伦理指南以导航其实施。通过这样做,组织可以利用 AI 的变革力量,同时确保遵守伦理标准,防范潜在的陷阱和危害。
如果你想了解更多关于人工智能的内容,可以查看我的书籍 《人工智能:让 AI 为你的业务服务的执行指南》在亚马逊 或 在 Google Play 上的 AI 解说有声书。
[1] 欧洲委员会. 2021. “可信 AI 的伦理指南 | 塑造欧洲的数字未来。” Digital-Strategy.ec.europa.eu. 2021 年 3 月 8 日. digital-strategy.ec.europa.eu/en/library/ethics-guidelines-trustworthy-ai.
[2] 欧洲委员会. 2021. “可信 AI 的伦理指南 | 塑造欧洲的数字未来。” Digital-Strategy.ec.europa.eu. 2021 年 3 月 8 日. digital-strategy.ec.europa.eu/en/library/ethics-guidelines-trustworthy-ai.
[3] 欧洲委员会. 2021. “可信 AI 的伦理指南 | 塑造欧洲的数字未来。” Digital-Strategy.ec.europa.eu. 2021 年 3 月 8 日. digital-strategy.ec.europa.eu/en/library/ethics-guidelines-trustworthy-ai.
[4] 欧洲委员会. 2021. “可信 AI 的伦理指南 | 塑造欧洲的数字未来。” Digital-Strategy.ec.europa.eu. 2021 年 3 月 8 日. digital-strategy.ec.europa.eu/en/library/ethics-guidelines-trustworthy-ai.
[5] Verma, Sahil, 和 Julia Rubin. 2018. “公平性定义解释。”《国际软件公平性研讨会论文集 — FairWare ’18》。 doi.org/10.1145/3194770.3194776。
[6] 欧洲委员会. 2021. “值得信赖的人工智能伦理指南 | 塑造欧洲的数字未来。” Digital-Strategy.ec.europa.eu. 2021 年 3 月 8 日。 digital-strategy.ec.europa.eu/en/library/ethics-guidelines-trustworthy-ai。
[7] Jobin, Anna, Marcello Ienca, 和 Effy Vayena. 2019. “全球人工智能伦理指南概况。”《自然机器智能》1 (9): 389–99。 doi.org/10.1038/s42256-019-0088-2。
社会工作学生的生成式人工智能:第一部分
一个范式转变
·
关注 发表在 Towards Data Science ·12 分钟阅读·2023 年 3 月 20 日
--
作者使用 ChatGPT 制作的 ASCII 艺术小狗。
近年来,人工智能(AI)发展迅速,产生了强大的工具,如生成式人工智能和大型语言模型。生成式人工智能是一种可以根据从现有数据中学到的模式创造新内容的人工智能,比如文本、图像或音乐。大型语言模型,如 OpenAI 的 GPT 系列,是生成式人工智能的一个子集,经过专门训练以理解和生成类人语言。这些模型变得越来越复杂,能够生成连贯、符合上下文甚至富有创造性的输出。
一年多前,我开始使用 GPT,并对其生成与人类作者难以区分的文本的能力感到惊讶。这种能力也引发了对学术不端行为的担忧。因此,我在 Medium 上撰写了一篇文章,预测 AI 辅助学术不端行为的出现。
从那时起,我密切关注生成式 AI 的发展,并探索它们如何与我对传统 AI 系统的兴趣互补。最近,我和我的同事评估了 ChatGPT 通过模拟的硕士级社会工作执照考试的能力——它做到了。我们研究的预印本可以在这里查看,并将很快在《社会工作实践研究》上发表。ChatGPT 现在能以我在一年之前无法想象的闪电速度完成各种复杂任务。

作者提供的图片。
作为社会工作专业的学生,你可能会想这些技术进步如何影响你的教育体验。AI 可以彻底改变你学习、研究和应对社会工作复杂挑战的方式。大型语言模型不仅仅是生成或重写文本。你可以利用这些模型来总结冗长的政策文件,分析定量和定性数据,生成项目评估的见解,自动化社区组织中的日常功能,创建营销材料,构建网络应用和网站——还有更多。
我坚信,通过建立对 AI 技术的扎实基础和有效使用这些技术的技能,你可以提升学习体验并开启新的职业机会。同时,如果没有正确的心态、培训和经验,这些模型对你的学习和职业生涯也会带来重大挑战和风险。
本文是一个系列的第一部分,旨在为 MSW 学生提供学习和建立 AI 技术能力的起点,重点介绍大型语言模型。第一篇文章概述了生成式 AI 在社会工作中的不同应用,并对伦理问题进行了初步讨论,后续文章将继续探讨这些问题。即将讨论的主题包括:
-
第二部分:AI 的基本知识、能力和实践
-
第三部分:通过 AI 提升教育体验的策略
-
第四部分:提示工程的艺术与科学
生成式 AI 在社会工作中的应用
生成式 AI 工具正作为一种变革性力量出现在各种领域,包括商业、医学和法律。尽管 AI 技术在社会工作领域刚刚开始受到关注,但现在是学生积极思考如何利用这一工具以提升和简化服务交付、赋能弱势群体、促进更加公平结果的最佳时机。
我将提供几个例子来说明我们如何使用生成式 AI。这些例子并不全面或完全代表生成式 AI 的能力。请记住,AI 技术的领域广泛且不断发展。根据谷歌和 Alphabet 的首席执行官桑达尔·皮查伊的说法,AI 的能力每六个月翻一番。
心理健康服务
大型语言模型在社会工作领域中的一个有前景的应用是心理健康服务。由 AI 驱动的虚拟治疗助手可以在治疗会议之间或当社会工作者不立即可用时,向客户提供支持和指导。例如,虚拟助手可以帮助客户练习应对策略、提供心理教育或提供简短的压力减轻干预。这些将是可以支持各种任务的助手,但不会替代社会工作者。
大型语言模型可以帮助社会工作者更好地理解客户的语言模式。通过分析基于文本的沟通或治疗会议的转录,AI 可以识别模式、主题和情感,这些可能为客户的情感状态、挑战和治疗进展提供有价值的见解。例如,AI 可以通过分析客户的用词选择、语调或情感表达来帮助检测抑郁、焦虑或其他心理健康问题的迹象。
大型语言模型的另一个实际应用是简化文档处理过程。社会工作者可以从帮助管理大量文书工作的工具中受益,例如撰写案例记录、治疗计划和进展报告,这些工作可能耗时且会减少与客户的接触时间。通过利用 AI,社会工作者可以减少在行政任务上花费的时间和精力,从而可以更多地专注于直接的客户护理,提升整体心理健康服务的质量。
项目管理
生成性语言模型在提升社会工作组织内程序管理方面可以发挥重要作用。管理和协调社会工作程序涉及大量以写作为主的任务,例如起草报告、创建和更新培训材料以及与利益相关者沟通。AI 驱动的工具可以利用原始数据和现有模板来生成文本,作为这些任务的起点。通过自动化初步起草过程,社会工作者可以节省时间,并专注于完善和调整内容,以确保准确反映程序的影响和成果。
这种支持可以带来更高效的报告,并更好地向利益相关者、资助者和政策制定者传达程序结果。大型语言模型还可以帮助开发和更新社会工作程序的培训材料。通过分析现有内容并融入新的研究或最佳实践,AI 可以建议对培训材料进行修订和补充,确保它们保持最新和有效。
政策分析
生成性 AI 对于从事政策分析的社会工作者来说,可以是一项宝贵的资产。政策分析是社会工作专业中的一个关键方面,涉及审查、评估和制定政策以解决社会问题。政策分析通常要求社会工作者浏览复杂且冗长的政策文件,这可能既耗时又具挑战性。大型语言模型可以帮助社会工作者更高效地综合和评估政策文件。通过提取关键信息并总结要点,AI 驱动的工具可以使社会工作者快速掌握政策要点,而无需通读整个文件。这种工作流程可以节省时间,并让社会工作者专注于分析政策的影响、优势和劣势。
生成性 AI 在政策分析中的另一个有用应用是识别现有政策中的差距和机会。通过分析和比较多个政策文件,AI 可以识别出模式、趋势以及政策可能存在的不足或不一致之处。AI 可以帮助社会工作者确定需要进一步关注或采取新方法来解决未满足的需求的领域。此外,大型语言模型可以协助社会工作者根据识别出的差距和机会生成政策建议。通过利用广泛的知识库,AI 可以建议基于证据的解决方案和最佳实践,以促进政策制定。
程序评估
程序评估通常要求社会工作者分析大量的定性和定量数据,以确定程序的成功并识别改进领域。大型语言模型可以帮助社会工作者更高效地分析程序数据,并生成可能需要时间才能显现的见解。例如,人工智能驱动的工具可以通过识别参与者的主题、模式和情感来分析定性数据,如访谈和焦点小组。因此,人工智能驱动的工具可以为社会工作者提供有关客户和其他利益相关者的经验和观点的宝贵信息,这些信息可以为程序改进提供参考。
同样,人工智能可以分析定量数据,例如程序结果测量,通过识别趋势、相关性和异常情况。通过自动化数据分析的初始阶段,社会工作者可以专注于解释结果,并运用他们的专业知识做出数据驱动的决策和建议。
通过利用人工智能驱动的数据分析生成的见解,社会工作者可以评估其项目的效率和效果,并确定改进领域。大型语言模型还可以通过建议基于证据的实践和根据类似项目或情况的分析生成潜在解决方案,帮助社会工作者探索替代策略和干预措施。
此外,生成型人工智能还可以通过生成清晰、简洁且易于理解的报告来协助沟通评估结果,从而有效地将结果和建议传达给各个利益相关者。这些过程有助于确保程序评估结果被理解和采取行动,最终实现客户和社区的更好结果。
社区组织
生成型人工智能可以在支持社区组织工作方面发挥重要作用。社区组织的一个关键方面是有效的沟通和外展,以动员个人、团体和组织围绕共同目标或问题进行行动。
大型语言模型可以帮助社会工作者创建有针对性、吸引人且具有文化敏感性的消息,这些消息能够与不同受众产生共鸣。通过根据不同社区成员的偏好、语言和关注点定制消息,人工智能驱动的工具可以提高外展活动的效果,并帮助与社区成员建立更强的联系。此外,人工智能还可以帮助识别在线对话和社交媒体中的趋势和新兴问题,使社会工作者能够保持对社区需求和优先事项的了解和回应。
生成式人工智能还可以帮助简化社区组织工作中的协作和资源管理。通过自动化创建和更新共享文档、跟踪任务进度和协调日程安排,AI 驱动的工具可以节省时间,提高组织工作整体的效率。此外,社会工作者和社区组织者可以利用 AI 分析社区资源和需求的数据,从而识别出协作的缺口和机会。这种支持使社会工作者能够就资源分配和合作伙伴关系做出明智的决策,从而最大化他们工作的影响力。
生成式人工智能可以在社区组织中提供宝贵的帮助,通过帮助开发倡导材料和政策提案来实现。这种实际应用涉及 AI 驱动的工具分析现有政策、研究和最佳实践,帮助社会工作者创建有说服力、基于证据的材料,准确传达社区的需求和优先事项。社会工作者和社区组织者可以利用这些材料与决策者、资助者以及其他利益相关者进行互动,推动系统性变革的倡导工作。
国际社会工作
生成式人工智能在国际社会工作中可以成为有价值的工具,社会工作者经常面临语言障碍和文化差异的挑战。凭借其先进的自然语言处理能力,大型语言模型可以通过提供实时翻译和生成具有文化敏感性的消息,帮助社会工作者与多样化的群体更有效地沟通。
大型语言模型可以改善跨文化理解,并促进社会工作者与他们服务的社区之间的合作。在国际社会工作中,了解全球趋势、 emerging issues 和可以为干预和政策建议提供指导的最佳实践至关重要。生成式人工智能可以帮助社会工作者访问和分析来自不同来源、国家和语言的大量信息。通过综合这些信息,AI 驱动的工具可以为社会工作者提供有价值的见解和基于证据的策略,以应对不同背景下的复杂社会问题。
生成式人工智能可以促进不同国家和环境中的社会工作专业人员之间的合作与知识交流。AI 驱动的工具可以通过促进沟通、生成研究和报告的摘要,以及识别共同的挑战和机会,帮助社会工作者分享他们的专业知识、互相学习并开发超越国界和文化的创新解决方案。
国际社会工作通常涉及在不同的文化、社会和政治背景下设计、实施和评估项目和干预措施。生成性 AI 可以通过帮助社会工作者分析背景因素、识别本地相关的策略,并评估不同环境下干预措施的有效性来支持这些努力。通过提供基于数据的见解和建议,AI 可以帮助社会工作者调整和改进他们的项目,以更好地服务于多样化的社区,并应对全球社会挑战。
生成性 AI 在社会工作中提供了有前景的机会;然而,承认这项技术的不完美至关重要。通过仔细权衡潜在的好处和弊端,我们可以确保在支持弱势群体时负责任和合乎伦理地使用它。

GPT-4 生成的不完美计算机图像
伦理考量
AI 工具并非完全客观,可能会延续偏见,从而加剧系统性歧视。因此,在处理与 AI 集成相关的伦理问题和影响时,需要保持警惕。在社会工作中,伦理考量至关重要,因为 AI 可能会影响到人们的生活决策。
社会工作者必须评估实施 AI 的伦理后果,确保其与职业的核心价值观一致:社会公正、尊重人类尊严和伦理责任。此外,社会工作者在倡导保护受到 AI 影响的弱势群体方面发挥着至关重要的作用,如残疾人士、边缘化社区以及儿童和青少年。作为更广泛讨论的引言,我强调两个与 AI 伦理相关的实际讨论点。
理解和应对模型偏见
生成性 AI 和大型语言模型从大量数据中学习,这些数据可能包含偏见。在社会工作中,我们需要预见并防范这些偏见,以避免在使用中造成伤害。社会工作者应仔细监控 AI 生成内容中的偏见,仔细检查可能支持刻板印象、传播虚假信息或表现出其他偏见的输出。
当审视 AI 伦理与促进多样性、公平性和包容性之间的紧密联系时,它们在应对 AI 系统中的偏见方面的共同目标变得显而易见。为了最小化在 AI 模型创建和实施过程中偏见出现的可能性,需要来自不同背景和视角的人员参与。鼓励在 AI 开发过程中更好地代表和参与代表性不足和边缘化社区,可以使社会工作组织和专业人士支持更具包容性的实践。
这一策略培养了伦理的 AI 实践,并有助于确保 AI 工具更好地满足各种客户群体和社区的多样化需求和经验。AI 设计师可以纳入多种视角和见解,以更好地理解和服务于广泛个体的独特需求。这种包容性方法提升了 AI 应用的整体质量,并有助于建立信任,促进 AI 开发者与他们服务的社区之间的合作。最终,这将导致更有效、公平和有益的 AI 解决方案。
隐私、机密性与透明性
在将生成式 AI 纳入社会工作实践时,保护客户隐私和机密性至关重要。用于数据分析、报告生成或客户沟通的 AI 工具必须符合严格的数据保护标准和相关隐私法律。安全存储和传输以及匿名化是防止未经授权访问的关键。社会工作者应审查 AI 生成的内容,以维护机密性和职业伦理。
透明性是使用生成式 AI 时一个重要的伦理方面。社会工作者需要与客户和利益相关者公开沟通 AI 工具的潜在影响。透明性包括解释 AI 如何参与护理,讨论其好处和局限性,以及解决相关担忧。此外,开发者和社会工作组织必须在 AI 算法的开发和使用中追求清晰度。他们应该致力于拥有透明决策过程的 AI 工具,以便社会工作者和利益相关者能够理解和评估这些工具。这种方法促进了负责任和伦理的 AI 使用,同时增强了社会工作者、客户和社区之间的信任。
下一步
正如我们在这篇导言文章中探讨的,生成式 AI,特别是大型语言模型,可以从根本上改变社会工作学生的教育经验,并增强该领域专业人士的实践。从心理健康服务和项目管理到政策分析、项目评估和社区组织,AI 技术提供了创新的工具和资源,可以增强社会工作者的能力。
为了有效利用 AI 进行社会工作,我们必须专注于将内容专业知识与技术技能相结合。虽然 AI 驱动的工具可以比社会工作者更高效地执行日常任务,但必须理解 AI 不能取代社会工作者。换句话说,社会工作者拥有 AI 所不具备的独特技能。
学生和专业人士都必须以负责任和伦理的方式对待这些技术。深入理解大型语言模型、它们的训练数据及其局限性,并磨练诸如提示工程等必要技能,可以帮助用户利用人工智能的力量,同时减轻潜在的不准确性和偏见。我将在本系列的后续文章中涵盖这些主题领域。
我是密歇根大学社会工作学教授,致力于准备学生并帮助非营利组织利用数据和信息技术更聪明地工作,而不是更辛苦。如果你有兴趣了解更多关于这些主题的内容,请关注我。
生成性 AI 对社会工作学生的介绍:第二部分
必备的知识、能力和 AI 实践
·
关注 发表在 Towards Data Science · 6 min read · 2023 年 4 月 27 日
--
图片由作者使用 Midjourney 创作。
本文是介绍生成性 AI 给社会工作学生的系列文章中的第二篇。我建议从第一篇文章开始阅读,你可以在这里找到:
一个范式转变
towardsdatascience.com
精通像 ChatGPT 这样的 AI 工具为 MSW 学生提供了一系列职业优势。对这些技术的熟练掌握简化了工作流程,提高了如案例管理、数据分析和资源分配等任务的效率和准确性。随着技术在社会工作实践中的日益融入,组织将更看重能够将传统技能与 AI 的创新能力无缝结合的专业人士。掌握 AI 工具体现了对跟进技术进步的承诺,使学生在潜在雇主面前更具市场竞争力。
AI 改变社会工作实践的能力取决于从业人员对技术的扎实掌握,包括理解 AI 模型的功能、其优缺点以及可能引入的潜在问题和偏见。尽管 ChatGPT 公开使用还不到六个月,我已经观察到学生中存在一些问题。这些问题有些源于对自身能力的过度自信,同时缺乏有效使用这些强大工具的必要技能,这是一种被称为达宁-克鲁格效应的现象。
由于社会工作课程尚未完全纳入这项技术,本文作为一个过渡措施,提供了有关生成性 AI 的基本知识、技能和实践的指导。虽然这不是一个全面的路线图,但本文提供了一个起点,随着技术的发展不断演变。通过在 ChatGPT 等 AI 应用中提高能力,社会工作学生可以为开发新颖、前沿的干预措施和实践做出贡献,定位自己为思想领袖。这种专业知识可以为职业发展、专业认可和与多学科团队的合作开辟新的机会,从而为个人和社区带来持久的积极变化。
鉴于 AI 技术的快速发展和丰富的可用资源,我避免提供具体的阅读列表,因为它会迅速过时。相反,我提供这些领域作为一般的能力范畴,您应持续关注这些领域,以获取最新的资源。
了解人工智能的工作原理
在当今快速发展的技术环境中,社会工作学生必须对人工智能(AI),特别是像 ChatGPT 这样的大型语言模型,建立一定的理解。随着 AI 工具在社会工作实践中变得越来越普及,具备这一领域专业知识的从业者能够对其使用做出明智的决策。这些知识帮助社会工作者在将其内容专业知识和人际交往技巧融入的同时,负责任地和伦理地使用 AI。
了解人工智能的一个关键组成部分是知晓其基本训练过程和数据,这有助于你在使用人工智能生成内容时做出明智的决策。这些知识还使你能够识别潜在的不准确性、偏见和过时的信息。评估人工智能模型在特定用例中的表现至关重要。对人工智能模型的失望往往源于将其用于未经过培训的任务,如研究高度专业化的话题。
值得注意的是,了解人工智能和大型语言模型并不需要数学、统计学或计算机编程背景。虽然达到熟练程度需要时间和努力,但对于任何致力于学习的社会工作学生来说,这是一个可以实现的目标。丰富的资源,包括在线课程、教程和文章,供学生按自己的节奏发展人工智能知识,无需专门的先修条件。
通过投资于这一学习过程,社会工作学生可以提升他们的技能,并为推动该领域的发展做出贡献。在这样做的过程中,他们确保利用人工智能的力量,造福个人、家庭和社区,同时与社会工作的原则相一致。
伦理问题
积极参与并保持对人工智能相关伦理问题的关注的重要性不容低估。确保负责任地使用人工智能,特别是像 ChatGPT 这样的语言模型,对于将技术与社会工作实践的核心价值观和原则对齐(如社会公正、个人的尊严和价值以及人际关系的重要性)至关重要。
人工智能使用中的伦理问题涵盖了广泛的问题,包括:
-
数据隐私: 保护客户和社区的敏感信息对于维护信任和遵守社会工作实践中的伦理标准至关重要。
-
人工智能系统中的偏见: 人工智能模型可能会无意中延续和加强现有偏见,从而导致歧视性结果。社会工作者必须意识到这些偏见,并积极努力减轻其影响。
-
潜在的误用: 人工智能工具的非预期或不道德使用可能会对个人和社区造成伤害,因此社会工作者理解并遵循适当的使用指南至关重要。
-
人工智能收益的公平分配: 确保人工智能技术的优势对所有人开放,无论社会经济地位或其他因素如何,对于促进社会公正和解决系统性不平等至关重要。
作为社会工作者,我们的责任是持续审视和质疑 AI 技术对脆弱群体的影响,确保我们使用的工具不会造成伤害或不公。深入理解这些伦理挑战使社会工作专业人员能够倡导制定和实施支持伦理 AI 使用的政策和指南。这种对伦理 AI 集成的承诺对维护社会工作的核心价值观和促进个人及社区的福祉至关重要。
提示工程
当前,“提示工程”是掌握生成型 AI 工具的关键实践技能之一。提示工程可能听起来复杂,但概念非常简单。在生成型 AI 和大语言模型的背景下,提示工程指的是设计实用的问题或输入陈述(即提示),以指导 AI 模型生成有用、相关且准确的回应。
学会编写有效的提示对于充分发挥生成型 AI 工具的潜力至关重要。精心设计的提示可以显著提高 AI 生成内容的质量,使其更加集中、准确和适用于任务。相反,设计不良的提示可能导致 AI 模型生成不相关、模糊或误导性的回应。
要设计有效的提示,必须深入理解主题内容,并预见可能在 AI 生成的内容中出现的潜在问题和不准确之处。在这方面,内容专业知识起着重要作用。拥有扎实内容知识的专业人员可以更有效地设计提示,从而生成更有意义和有见地的 AI 内容。
主题知识在设计 AI 提示中的重要性强调了生成型 AI 无法替代社会工作者。AI 模型依赖于人类专家的见解和经验来塑造其回应,并确保生成的内容与特定背景相关且适宜。社会工作者的专业知识使他们能够有效地利用 AI 工具来改进工作,同时确保 AI 生成的内容符合职业的价值观、原则和伦理指南。
随着生成型 AI 在包括社会工作在内的各个领域中扮演越来越重要的角色,提示工程正在成为专业人员需要学习的基本 AI 相关技能之一。通过掌握提示工程,社会工作者可以充分发挥 AI 工具的潜力,提高他们的实践效果,并为客户和社区带来更有效、更高效和更具伦理的结果。
设计思维
设计思维是一种方法,强调以人为中心的复杂问题解决方法。这种方法注重同理心、合作和实验,以理解相关社区的独特需求和背景。通过将设计思维原则融入生成式 AI 工具,我们可以创建更有效和量身定制的 AI 驱动解决方案,以应对复杂的社会问题。
设计思维与生成式 AI 的融合促成了一个创意环境,鼓励产生想法和探索多样的解决方案。它还支持原型设计和测试的迭代过程,允许基于现实世界反馈对干预措施进行持续的优化和调整。在利用生成式 AI 工具时,采纳设计思维心态有助于跨学科合作,并促进持续改进的文化。这使得通过从不同领域汲取广泛的视角和专业知识,开发出全面解决复杂社会问题的解决方案成为可能。
此外,通过从失败中学习并迭代解决方案,我们可以确保 AI 驱动的干预措施不断被完善和适应,以满足目标人群不断变化的需求。这种方法最终会导致更有效、更具响应性和可持续的解决方案,从根本上解决社会问题,并改善服务社区的福祉。
下一步
本系列的以下两篇文章将重点关注提升教育体验的 AI 策略和关于提示工程艺术与科学的总结文章。在准备这些文章的同时,我也在利用大语言模型积极研究 ASWB 国家社会工作执照考试的问题。这项工作与本系列和社会工作领域高度相关。你可以在这里找到我最近的相关文章:
变革的需求
[towardsdatascience.com
使用 Nougat 模型进行研究论文生成 AI
原文:
towardsdatascience.com/generative-ai-on-research-papers-using-nougat-model-38aa37a354f6
用数据做有趣的事情!
·发表于 Towards Data Science ·阅读时间 9 分钟·2023 年 9 月 20 日
--

图片由 Dan Dimmock 提供,来源于 Unsplash
介绍
最近,大型语言模型(LLMs)如 GPT-4 的进展展示了生成连贯文本的令人印象深刻的能力。然而,准确解析和理解研究论文仍然是 AI 面临的极具挑战的任务。研究论文包含复杂的格式、数学方程式、表格、图形以及特定领域的语言。信息的密度非常高,重要的语义通过格式编码。
在本文中,我将演示如何使用 Meta 的新模型 Nougat 准确解析研究论文。然后,我们将其与一个 LLM 流水线相结合,提取并总结论文中的所有表格。
这里的潜力是巨大的。许多数据/信息被锁定在未被正确解析的研究论文和书籍中。准确的解析使这些数据能够在包括 LLM 重新训练在内的许多不同应用中使用。
我制作了一段 YouTube 视频,更详细地解释了代码和我的实验。请查看 这里。
Nougat 模型
Nougat 是 Meta AI 研究人员开发的一种视觉变换器模型,可以将文档页面的图像转换为结构化文本 [1]。它将文档页面的光栅图像作为输入,并输出轻量级标记语言的文本。
Nougat 的主要优势在于它仅依赖于文档图像,而不需要任何 OCR 文本。这使得它能够正确恢复诸如数学方程之类的语义结构。它在来自 arXiv 和 PubMed 的数百万篇学术论文上进行训练,以学习研究论文格式和语言的模式。
[1] 中的下图展示了 PDF 中编写的数学方程如何在 Latex 中再现并正确渲染。

来源:Nougat 论文中的图 5 — arxiv.org/pdf/2308.13418.pdf
Nougat 使用视觉变换器编码器-解码器架构。编码器使用 Swin Transformer 将文档图像编码为潜在嵌入。Swin Transformer 通过使用移位窗口以分层方式处理图像。然后,解码器利用自注意力机制根据编码器输出自回归生成输出文本标记。
Nougat 使用随机梯度下降对页面图像和文本对进行端到端训练。数据增强技术如侵蚀、膨胀和弹性变换被用于提高鲁棒性。在训练过程中,还使用了特殊的反重复正则化来减少文本重复。
作者报告了如编辑距离、BLEU、METEOR 和 F1 分数等指标,基于学术论文的测试集。Nougat 在纯文本上表现出高准确度。错误主要集中在数学表达式上,因为 LaTeX 命令存在歧义。如下面所示,Nougat 在性能上显著超过了 GROBID 和 PDF OCR 等早期方法。

来源:表 1 Nougat 论文 — arxiv.org/pdf/2308.13418.pdf
运行和评估 Nougat 模型的输出
安装说明和运行 Nougat 模型的步骤在 Colab Notebook 这里中共享。
Nougat 小型模型在免费的 Colab T4 GPU 上运行顺畅,结果非常令人印象深刻。
运行模型的主要步骤是安装包 nougat-ocr。然后我们使用子进程运行命令行 CLI,将下载的 PDF 传递给 Nougat 并获取其输出。
!pip install -qq nougat-ocr
def nougat_ocr(file_name):
# Command to run
cli_command = [
'nougat',
'--out', 'output',
'pdf', file_name,
'--checkpoint', CHECKPOINT,
'--markdown'
]
# Run the command
subprocess.run(cli_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
return
在我的 Colab Notebook 中,我在DOLA 论文上运行了 Nougat。Nougat 令人印象深刻地捕捉了 Latex 中的数学方程。
Recent language models are consists of an embedding layer, \(N\) stacked transformer layers, and an affine layer \(\phi(\cdot)\) for predicting the next-word distributution. Given a sequence of tokens \(\{x_{1},x_{2},\ldots,x_{t-1}\}\), the embedding layer first embeds the tokens into a sequence of vectors \(H_{0}=\{h_{1}^{(0)},\ldots,h_{t-1}^{(0)}\}\). Then \(H_{0}\) would be processed by each of the transformer layers successively. We denote the output of the \(j\)-th layer as \(H_{j}\). Then, the vocabulary head \(\phi(\cdot)\) predicts the probability of the next token \(x_{t}\)
\[p(x_{t}\mid x_{<t})=\mathrm{softmax}\big{(}\phi(h_{t}^{N})\big{)}_{x_{t}}, \quad x_{t}\in\mathcal{X},\]
where \(\mathcal{X}\) is the vocabulary set.
Instead of applying \(\phi\) just on the final layer, our approach contrasts the higher-layer and lower-layer information to obtain the probability of next token. More specifically, for the lower layers, we also compute the probability of the next tokens using \(\phi(\cdot)\),
\[q_{j}(x_{t}\mid x_{<t})=\mathrm{softmax}\big{(}\phi(h_{t}^{j})\big{)}_{x_{t}}, \quad j=1,\ldots,N.\]
The idea of applying language heads directly to the hidden states of the middle layers, known as _early exit_(Teerapittayanon et al., 2016; Elbayad et al., 2020; Schuster et al., 2022), has proven to be an effective inference method even without special training process (Kao et al., 2020), as the residual connections (He et al., 2016) in transformer layers make the hidden representations gradually evolve without abrupt changes. Using \(q_{j}(x_{t})\) to represent \(q_{j}(x_{t}\mid x_{<t})\) for notational brevity, we then compute the probability of the next token by,
\[\hat{p}(x_{t}\mid x_{<t}) =\mathrm{softmax}\big{(}\mathcal{F}\big{(}q_{N}(x_{t}),q_{M}(x_{t })\big{)}\big{)}_{x_{t}}, \tag{1}\] \[\text{where}\quad M =\operatorname*{arg\,max}_{j\in\mathcal{J}}\;d\big{(}q_{N}(\cdot ),q_{j}(\cdot)\big{)}.\]
Here, layer \(M\) is referred to as the _premature layer_, while the final layer is referred to as the _mature layer_. The operator \(\mathcal{F}(\cdot,\cdot)\), to be elaborated further in Section 2.3, is used to contrast between the output distributions from the premature layer and the mature layer by computing the difference between two distributions in the log domain. The premature layer is dynamically selected in each decoding step using a distributional distance measure \(d(\cdot,\cdot)\) (we use the Jensen-Shannon Divergence) between the mature layer and all the candidate layers in \(\mathcal{J}\). We discuss \(d(\cdot,\cdot)\) in more detail in Section 2.1 and Section 2.2\. The motivation for selecting the layer with the highest distance \(d(\cdot,\cdot)\) as the premature layer is to maximize the difference between the mature/premature layers.
我还发现表格在 Latex 中捕捉得很好。表 1 被准确地转换为 Latex,如下所示!
\begin{table}
\begin{tabular}{l c c c c c} \hline \hline \multirow{2}{*}{**Model**} & \multicolumn{4}{c}{**TruthfulQA**} & \multicolumn{2}{c}{**FACTOR**} \\ \cline{2-6} & **MC1** & **MC2** & **MC3** & **News** & **Wiki** \\ \hline LLaMa-7B & 25.6 & 40.6 & 19.2 & 58.3 & 58.6 \\ + ITI (Li et al., 2023) & 25.9 & - & - & - & - \\ + DoLa & **32.2** & **63.8** & **32.1** & **62.0** & **62.2** \\ \hline LLaMa-13B & 28.3 & 43.3 & 20.8 & 61.1 & 62.6 \\ + CD (Li et al., 2022) & 24.4 & 41.0 & 19.0 & 62.3 & 64.4 \\ + DoLa & **28.9** & **64.9** & **34.8** & **62.5** & **66.2** \\ \hline LLaMa-33B & 31.7 & 49.5 & 24.2 & 63.8 & 69.5 \\ + CD (Li et al., 2022) & **33.0** & 51.8 & 25.7 & 63.3 & **71.3** \\ + DoLa & 30.5 & **62.3** & **34.0** & **65.4** & 70.3 \\ \hline LLaMa-65B & 30.8 & 46.9 & 22.7 & 63.6 & 72.2 \\ + CD (Li et al., 2022) & 29.3 & 47.0 & 21.5 & 64.6 & 71.3 \\ + DoLa & **31.1** & **64.6** & **34.3** & **66.2** & **72.4** \\ \hline \hline \end{tabular}
\end{table}
Table 1: Multiple choices results on the TruthfulQA and FACTOR.
生成 LLM 总结 Latex 表格
有许多示例展示了 LLM 总结文本,包括研究论文。但几乎总是,表格和数学方程中的数据要么解析不正确,要么未包含在总结中。我现在测试一下像 GPT3.5 这样的模型在 Latex 表格上的表现如何。
我在这里使用 Langchain 编写了一个链式流水线,执行以下操作:
-
识别研究论文中的所有表格
-
读取 Latex 并理解表格
-
创建一个输出字典,总结每个表格
完整的源代码包含在 Colab Notebook 中这里。如下面的代码片段所示,我们对 LLM 的提示是识别所有表格,总结它们,并以结构化格式分享结果。
from langchain.prompts import (
ChatPromptTemplate,
PromptTemplate,
SystemMessagePromptTemplate,
AIMessagePromptTemplate,
HumanMessagePromptTemplate,
)
from langchain.chat_models import ChatOpenAI
from langchain.chains import LLMChain
context_template="You are a helpful AI Researcher that specializes in analysing research paper outputs presented to you in Latex"
system_message_prompt = SystemMessagePromptTemplate.from_template(context_template)
human_template= """
Please extract all tables referenced in this paper. The tables are in Latex format. Summarize the tables one by one.
Each summary should be 4-5 sentences long. Include numbers in summary where you can. Make a dictionary with table number, table name and summary of that table.
PAPER: {paper_content}
"""
human_message_prompt = HumanMessagePromptTemplate(
prompt=PromptTemplate(
template=human_template,
input_variables=["paper_content"],))
chat_prompt_template = ChatPromptTemplate.from_messages([system_message_prompt,
human_message_prompt])
chat = ChatOpenAI(model_name="gpt-3.5-turbo-16k",
temperature=0.2)
summary_chain = LLMChain(llm=chat, prompt=chat_prompt_template)
output = summary_chain.run(noref_content)
pprint.pprint(output)
我之前对 LLM 的表现不太确定,所以不得不说,我对回复的质量感到震惊。论文中有 6 个表格,所有表格都被正确识别了。然后每个表格都被很好地总结了。
对上述提示的 LLM 回应如下。
('Dictionary of Tables:\n'
'\n'
'Table 1:\n'
'- Table Number: 1\n'
'- Table Name: Multiple choices results on the TruthfulQA and FACTOR '
'datasets\n'
'- Summary: This table presents the results of multiple choice tasks on the '
'TruthfulQA and FACTOR datasets. It compares the performance of different '
'models, including the baseline, Inference Time Intervention (ITI), and DoLa. '
'The table shows that DoLa consistently outperforms the other methods, '
'improving the truthfulness and informativeness scores.\n'
'\n'
'Table 2:\n'
'- Table Number: 2\n'
'- Table Name: Open-ended generation results on TruthfulQA, StrategyQA, and '
'GSM8K\n'
'- Summary: This table summarizes the results of open-ended generation tasks '
'on the TruthfulQA, StrategyQA, and GSM8K datasets. It compares the '
'performance of different models, including the baseline, Contrastive '
'Decoding (CD), and DoLa. The table shows that DoLa consistently enhances the '
'truthfulness and informativeness scores, outperforming the other methods.\n'
'\n'
'Table 3:\n'
'- Table Number: 3\n'
'- Table Name: Multiple choices results on the FACTOR dataset\n'
'- Summary: This table presents the results of multiple choice tasks on the '
'FACTOR dataset. It compares the performance of different models, including '
'the baseline, DoLa with dynamic premature layer selection, and DoLa with '
'random layer selection. The table shows that DoLa with dynamic premature '
'layer selection performs better than the other methods, improving the '
'truthfulness and informativeness scores.\n'
'\n'
'Table 4:\n'
'- Table Number: 4\n'
'- Table Name: Comparison of MPT-7B and modifications on TruthfulQA, FACTOR, '
'and CoT datasets\n'
'- Summary: This table compares the performance of the MPT-7B model and its '
'modifications on the TruthfulQA, FACTOR, and CoT datasets. It shows that '
'DoLa improves the truthfulness and truthfulness+informativeness scores on '
'most datasets, indicating the potential of DoLa to generalize across '
'different transformer models.\n'
'\n'
'Table 5:\n'
'- Table Number: 5\n'
'- Table Name: Qualitative study for LLaMA-33B on TruthfulQA\n'
'- Summary: This table presents qualitative examples from the TruthfulQA '
'dataset, comparing the answers generated by the baseline and DoLa using the '
'LLaMA-33B model. It shows that DoLa produces more truthful and informative '
'answers compared to the baseline.\n'
'\n'
'Table 6:\n'
'- Table Number: 6\n'
'- Table Name: Averaged decoding latency per token in milliseconds\n'
'- Summary: This table shows the average decoding latency per token in '
'milliseconds for the baseline and DoLa. It indicates that DoLa adds a small '
'additional latency to the decoding process, making it a practical and '
'efficient decoding strategy.')
阅读这些示例时,我的观察是:
-
每个表格都被识别出来了。模型准确理解了表格的行和列
-
总结捕捉了表格中的结论
-
总结表明,模型使用了论文中的信息来理解表格。对于表格 3,模型准确理解了该表格比较了 DOLA 与随机层选择与过早层选择,即使表格本身并没有提到这一点
-
我本来希望在表格的总结中看到更多的数字和统计数据,但模型没有输出它们。附带说明,我还测试了 Anthropic.AI 的 Claude2,它能够总结表格,并提及关键的统计结论,呈现了更优的回复
我的代码现在还涵盖了上述流程的一个示例,搜索所有数学方程并解释它们。这里也有令人印象深刻的初步结果。
结论
在这篇文章中,我展示了 Nougat 模型如何直接从图像中解析复杂的研究论文。Nougat 克服了一个关键挑战——理解论文格式中编码的语义。
我展示了一个端到端的流程,通过 Nougat 解析论文,提取表格,并使用 LLMs 总结它们。Nougat 以高准确率提取了文本、数学、表格和格式。下游的 LLM 成功总结了提取的表格,专注于关键比较。
Nougat 帮助克服了 PDF 文本和当前最佳解决方案如 pyPDF 和 GROBID 的局限性。我们现在可以更好地理解论文中的信息。总体而言,这展示了生成性 AI 模型如何在解锁所有困于非结构化格式中的知识方面取得进展。
我经营自己的机器学习咨询,帮助客户进行生成性 AI 应用。如果你有兴趣合作,请通过 priа@deeplearninganalytics.org 给我发邮件。
参考文献
[1] Blecher, Lukas, et al. “Nougat: Neural Optical Understanding for Academic Documents.” arXiv preprint arXiv:2308.13418 (2023).
[2] Langchain Source Documentation — python.langchain.com/docs/get_started/introduction
生成式 AI 产品策略:如何构建下一代 AI 产品
垂直 AI 产品与水平 AI 产品以及可防御的商业护城河
·
关注 发表在 Towards Data Science ·12 分钟阅读·2023 年 1 月 25 日
--
图片由作者使用 Midjourney 生成
人工智能在 2022 年取得了如此显著的进展,以至于世界似乎终于接受了这一技术现已普及到普通人日常使用的程度。
生成型 AI 模型如 GPT3 和 Stable Diffusion 将使我们能够以以前难以想象的规模和速度进行创作。这将从根本上改变人类创造价值的方式。我们用于创作的每一个数字工具——从编码环境到视频编辑器到 3D 建模软件——都将经历剧烈的变化。
虽然展示 AI 生成的艺术作品令人印象深刻,但演示并未考虑实际的专业工作流程和行业需求。通过通用接口访问这些模型对于实验和创建社交媒体帖子是很好的,但对于不同领域的专业人士有效地完成他们的工作还远远不够。为此,我们需要将这些模型融入产品中,使其在专业工作流程中提供实际的效用。
当我作为一个对时尚业务知之甚少的人,尝试使用 Stable Diffusion 进行时尚设计时,我意识到这一点。对我来说,为时尚设计师提供一个无限创意的生成 AI 产品似乎是一个显而易见的想法。怀着热情,我使用 Stable Diffusion 生成了这张裙子图像,并与我的时尚设计师朋友分享了。

这是一张由作者使用开箱即用的 Stable Diffusion 生成的裙子图像。
“你能用这个想法设计一件这样的新裙子吗?”我问。她的回答是一个令人失望的大“不能”。除了或许可以用作灵感的情绪板之外,这张图像对她完全没有用处。
尽管 Stable Diffusion 拥有强大的生成能力,但其当前格式下的输出对时尚设计师来说并不十分有用。要让 AI 实际上带来价值,我们需要考虑如何以实际的方式利用其输出。这可能涉及对模型输入的预处理、对输出的后处理,或将其与人工生成的内容相结合。关键在于利用 AI 的独特能力,专注于解决用户的特定实际问题。让我们探索如何让这一看似神奇的新技术为我的时尚设计师朋友提供帮助。
AI 产品策略框架
对于正在开发 AI 产品的创始人和产品领导者,我推荐两种广泛的方法——纵向和横向。纵向方法涉及为特定行业或细分市场的创意过程构建 AI 辅助的软件产品。时尚印刷设计和家具设计就是一些例子。每个细分市场都有不同的需求和工作流程,我们必须牢记这一点。
水平方法涵盖了跨行业需求的产品,比如平面设计、广告设计或法律合同起草。广告创意在从汽车到食品等各行业都是必需的。在这里,产品不需要行业特定的工具。最重要的是一个出色的用户界面,使用户可以在比以前更短的时间内进行平面设计工作,并且质量更高。
垂直 AI 产品
在为特定行业构建 AI 支持的产品的同时,除了部署最先进的 AI 模型,还需要构建它们特定工作流程所需的工具。下面是为时尚业生成面料印花设计的 AI 产品需要涵盖的内容-
-
用户必须能够将情绪板图像与文本提示一起作为输入提供给生成式 AI 扩散模型。情绪板和提示文本通常是通过当前高销售趋势的数据分析得出的。
-
AI 模型必须生成适用于整体印花的无缝纹理。无缝纹理是一种图像,可以在自身的上方、下方或侧边放置,而不会在图像的副本之间产生明显的接缝、连接或边界。以下是我通过调整稳定扩散代码生成的无缝纹理的示例图像。该图像由四个生成的图像并排放置而成。

作者通过微调稳定扩散生成的无缝面料印花图案
-
创造力是一个迭代的过程。设计师应该能够通过手动编辑或者在后续的 AI 生成中引入特定的修改来迭代地修改每一代 AI 的输出。
-
需要由不同角色的用户进行多层次的审批。设计主管可能需要签署最终设计。
-
最终输出的分辨率需要放大到足够高的水平。
-
最后,输出必须转换为可以发送给布料打印机的矢量文件格式。
时尚设计师需要围绕生成式 AI 模型的输出构建这种面向行业的工具。
考虑另一个 AI 辅助室内设计的例子,它通过生成图像展示了房间如何以特定风格进行改造。以下是专业室内设计师在使用AI 室内设计工具时分享反馈的文章摘录 。
当我们要求其创建一个“波西米亚”客厅时,霍勒斯指出算法选择了她为希望看起来相同的客户使用过的类似调色板。“我可以看到将其用作开发情绪板图像的工具,”她补充说。“所有的参考都很准确。”
Horace 说:“旁边有一个奇怪的自助餐桌,而且不知为何有两个咖啡桌,这看起来并不实用。”谈到一间中世纪风格的客厅时他表示:“外观是对的,但你不能向客户展示这个,比如,‘嘿,这是你的房间!’”
Interior AI 的空间规划技能有时还有待改进——它的浴室设计并不总是包含马桶。
这个工具经常会生成一个房间的基本近似图,但改变了比例、去掉了窗户或将天花板降低了几英尺。

不错的浴室,但马桶在哪里?作者使用 Midjourney 生成的图像
从设计师的评论中可以清楚地看出,室内设计产品的 AI 模型需要在某些无法修改的约束条件内操作。他们的评价可以简洁地总结为一句话——
在依靠大量的 2D 灵感图像来形成对设计的理解时,它对风格的把握非常准确,但对功能的掌握则较为松散。
请注意,时尚设计师的问题和约束条件与室内设计师的完全不同。尽管如此,双方都需要针对其行业的更好功能性,而不仅仅是关注风格。
对于像 Stable Diffusion 这样的 AI 模型来说,在与行业相关的数据集上进行微调至关重要。以时尚印刷设计为例,设计师可能希望根据特定的风格、色彩搭配或情绪生成图案。通过使用少量示例对生成模型进行微调,将有助于生成更相关的设计。即使是其他深度学习模型,如用于对象识别和分割的模型,也可能需要微调,如果这些对象在开箱即用的预训练模型的训练数据中不存在。最终,我预计模型微调工具将集成到每个产品中。很可能会有一个大型公司出现,提供模型微调的 API 服务。
垂直 AI 产品中的护城河

作者使用 Midjourney 生成的图像
一个产品或公司不能仅依靠 AI 模型作为唯一的竞争优势。AI 研究论文和实现代码通常是公开的。OpenAI 发布了用于文本到图像生成的 Dall-E 和 Dall-E 2 模型,随后开源的 Stable Diffusion 模型也被发布。AI 吸引了一些世界上最优秀的头脑,行业进展迅速。可以保证,总会有人最终发布比你目前实现的模型更好、更先进的新模型。
为特定行业开发 AI 产品的一个巨大优势是更容易创建一个防御性的商业护城河。为该行业构建的工具给客户带来的便利,也使得他们不太可能因为新模型的发布而离开。这为企业提供了时间来将 AI 模型输出提升到最先进的基准水平。
横向 AI 产品
当涉及到用于图像编辑或图形设计等行业的产品时,新型 AI 基础产品面临着来自现有巨头的艰巨挑战。这是因为像 Photoshop 这样的产品以及其他产品正在迅速引入由 AI 驱动的功能。依赖 Photoshop 广泛图像编辑工具的专业人士会更愿意使用 Photoshop 中的 AI 功能,而不是切换到其他产品。许多构建横向 AI 产品的初创公司可能因为现有巨头的大规模分发而短命。如果 AI 模型被集成到人们已经大量使用和支付的工具中,他们几乎没有动机转向其他工具。Notion 是一款拥有 2000 万用户的笔记应用,最近推出了一个 AI 写作助手。新的 AI 驱动的文字处理器可能会因为 Notion 现有的大量用户基础和网络效应而难以竞争。
任何新的横向 AI 产品的机会在于为不熟悉该领域的用户提供好、便宜 和 快的输出。流行的笑话是,与设计师合作时,你只能获得三个中的两个。
要取得成功,任何新的横向设计 AI 产品必须为非设计师用户提供三者俱全的功能。显而易见,该产品应易于使用。否则,用户不太可能快速获得高质量的输出。换句话说,机会在于降低进入这一创意领域的门槛,这个领域之前由熟练的专业人士主导。
Canva 是一个很好的例子,展示了这一点。Canva 使得一个会计人员能够创建一个好的活动海报。他们证明了许多设计工作,不需要设计师的高级工具,如 Photoshop。Canva 通过构建一个简单的产品,提供模板和库存内容,使得非设计师能够快速、便宜地创建出好的内容。AI 给人的感觉就像是 Canva 的再次出现,但这次是适用于每个创意领域,并且规模更大。像 GPT-3 和 Stable Diffusion 这样的生成性 AI 模型特别相关,因为非设计师用户不需要从零开始。相反,仅凭一个文本提示,他们就能获得一个远远超出他们自己所能创造的起点。
用户界面在经过大量实验后可能会经历几次根本性的变化。在许多话题上,ChatGPT 的对话界面比 Google 搜索更快速地返回准确的答案。对话界面在各种应用中看起来非常有前景。你对设计师说“颜色需要更蓝”的渴望最终会得到满足。
对于生成模型,艺术风格预设、预先调整的模型以及即席调整能力可以使用户对输出结果有更多控制。
横向 AI 产品的护城河
部署在高质量大数据集上训练的最佳表现模型对横向 AI 产品非常有利。由于这些产品需要在各个行业的用户中运行,投资 AI 研发以保持最先进的模型性能可能是一个强大的护城河。然而,由于竞争激烈和大量资金流入 AI,这不能成为唯一的护城河。通过用户体验、产品定位、分销、社区、网络效应、团队执行力以及快速响应用户反馈等方式可以建立额外的护城河。
RunwayML 是一个由 AI 驱动的视频创作和编辑工具,表现出色。RunwayML 通过从擦除和替换对象到自动化视频运动跟踪等功能,降低了内容创作的门槛。他们对 Stable Diffusion 的贡献意味着他们正在投资推动最先进 AI 的边界。通过这些方法的结合,RunwayML 获得了大量客户,并在其业务周围建立了长期的护城河。

来源: RunwayML 网站
Canva 的成功很大程度上归因于其作为非设计师工具的定位护城河。这个客户群体与 Adobe Photoshop 和 Illustrator 的目标用户群体不重叠,后者由熟练的设计师使用。深刻理解和接受其定位帮助 Canva 推出了设计师在培训中学习的功能,但普通用户不了解的功能,例如颜色调色板生成器和字体建议。
建立一个蓬勃发展的社区是建立护城河的一种优秀且被低估的方法。积极参与和发展社区可以提供深刻的产品反馈。这在构建适合非专家的新用户界面的功能时尤其有用。通过社区互动获得的快速反馈循环可以促进产品的快速迭代。
生成 AI 产品的经济影响
人工智能工具用于内容推荐、图像重照、光学字符识别和语音转文字等应用已经存在了几年,并已经成为我们每天使用的产品的一部分。活跃地协助我们的创意过程的人工智能产品潮流刚刚开始。每个人都在关心但似乎没有一个明确答案的问题是,这会对人类的工作岗位产生什么影响?这是一个非常困难的问题,没有一个单一的答案。不过,我可以肯定的是答案既不在于人工智能完全取代人类,也不在于人类的思维不可替代。在我看来,不可能有一种能经得住时间考验的确定答案。细微的答案将在两者之间,根据技术随时间的发展而不断在这两个极端之间移动。
数字艺术家一段时间以来一直在 Photoshop 中使用由人工智能提供动力的功能,例如智能背景去除和内容感知填充。它只是在幕后,没有公开宣传为人工智能。如今,生成式人工智能扩散模型能够从创意过程的第一步(让我们考虑第 0 步为决定为什么和什么来创作)开始做出贡献。虽然人工智能之前只对创意产出做出了少量贡献,但随着时间的推移,它将会做出更多的贡献。我看到这些新创作工具有两个明显的后果:
-
它们将大大降低进入许多领域的门槛,从而导致创作者数量激增
-
它们将使创作者在给定时间内产生更大量的产出
第一个后果意味着更多的人类就业、经济活动和货币的提升。突然间,好莱坞的工具就在每个人的口袋里。第二个后果意味着较少的人可以做许多人的工作,导致雇主裁员。这会导致灾难性的大规模失业吗?或者这些人也会成为独立的创作者?这些事情是极难预测的。每个人都认为人工智能会消灭重复的体力劳动。有创意的人是安全的。没有人认为人工智能会写代码或创作艺术。
世界将分为使用人工智能和不使用人工智能的人。
那些构建这些产品的人将获得巨大的回报。我们正处于一个很可能被视为人类历史上最具决定性的时期的边缘。
如果你是一位致力于建立人工智能产品的产品经理,我已经编写了一份由 3 部分组成的实用指南,告诉你如何执行:
-
第一部分:基础知识——什么使人工智能产品管理与众不同,你是否应该在项目中使用机器学习,人工智能产品经理的先决知识
-
第二部分: AI 团队管理、产品规划和开发战略
-
第三部分: 模型选择、部署到生产环境、模型维护和成本管理
生成模型与噪声和结构的舞蹈
原文:
towardsdatascience.com/generative-models-and-the-dance-of-noise-and-structure-e72fe7494f4f
数字梦想者构建指南
·发布于 Towards Data Science ·阅读时间 21 分钟·2023 年 10 月 7 日
--
我喜欢想象一下,文艺复兴时期意大利的居民,充满了对人类想象力和理性可能性的热情,会对我们今天的技术感到最为震惊的是什么。莱昂纳多·达·芬奇,曾梦想飞行器,肯定会对一架空中飞翔的空客 A380 印象深刻,乘客们舒适地坐在座椅上,看电影,抱怨 Wi-Fi 不够快。
在所有在中世纪看来如同巫术的技术中,生成式 AI 的奇迹可能是最像巫术的。如果我展示给莱昂纳多·达·芬奇一台能够在几秒钟内以他的风格绘制女性肖像的设备,他在劳作了无数年之后对《蒙娜丽莎》肖像的反应会是什么?瞧:

由 DALL-E 绘制的达·芬奇风格的女性肖像。
虽然诚然,这位女性并没有像真正的蒙娜丽莎那样以诱人而神秘的微笑来展现自己(且经细致审视显得有些滑稽),但我们许多人已经遇到了令人惊叹的 AI 生成实例:从 超现实的图像到令人毛骨悚然的深度伪造的声音,甚至是由 AI 编写的完整文章。
生成性 AI 模型是梦想家的硅基等效体:它们能够从无到有地构想事物,从噪声中赋予意义。它们学会了在秩序与无序之间起舞。它们已经改变了我们对人类创造力的看法,并开启了数千种新应用的门扉,威胁到整个行业并创造了新的行业。
我们刚刚起步,这些模型中的大多数仍处于初期阶段。通过 ChatGPT 的写作、DALL-E 和 Midjourney 生成的图像,以及最近像 Stability AI 的StableAudio这样的音乐生成模型,我们正迎来一个时代,在这个时代,我们每天输入大脑的感官信号越来越多地被 AI 以某种方式改变甚至完全生成。

“一个画家在画布的左侧是混乱噪声的溅射,逐渐转变为右侧的结构化、美丽的数字城市。艺术风格应为半现实主义,带有一点超现实主义。光线应柔和而弥漫,营造出梦幻般的氛围。” 由 Chat-GPT 提供的提示,由 DALL-E 绘制的画作。
在这篇文章中,我想揭开这个神奇黑箱的面纱,深入探讨几类生成模型(Helmholtz 机、变分自编码器、归一化流、扩散模型、GAN 和基于 Transformer 的语言模型)的基本机制,揭示它们的内部工作原理,并探讨它们在神经科学和认知学中的起源和联系。这个话题显然太广泛,无法在一篇文章中涵盖(尽管文章比预期的要长得多),所以我试图在一些技术细节、高层次概述、连贯的叙述和进一步阅读的来源之间找到平衡,希望每个人都能从中找到一些有用的信息。
“我无法创造的,我不能理解。”
— 理查德·费曼
我们从哪里开始?
以这句常被引用的费曼名言开头有些陈词滥调,但这位大师确实有他的道理:理解与创造行为相关,因此在机器学习的早期,构建能够理解的模型也就与构建能够创造的模型相关。图灵的著名测试(也称为模仿游戏)可以看作是这个观点的一种变体:如果你成功地伪造了智能,那么你很可能发现了类似真实智能的东西。
在早期生成模型中,两个最重要的模型是 Boltzmann 机和 Helmholtz 机。
赫尔姆霍兹机器特别有趣,因为其原理与德国物理学家赫尔曼·冯·赫尔姆霍兹的极具先见之明的视角紧密相关。赫尔姆霍兹在 19 世纪末意识到,知觉更好地描述为一种从感官数据和先验知识中进行无意识推理的过程,而不是对客观现实的客观反映:认知本质上是概率性的,并受到噪声的影响,并且受到我们的期望和偏见的强烈塑造。他的观点在现代神经科学中越来越相关,例如通过卡尔·弗里斯顿的自由能原理(他明确引用了赫尔姆霍兹机器作为灵感来源)和贝叶斯大脑假说。

根据自由能原理,大脑与外部世界在一个行动-感知循环中互动,尝试从其感觉中推断世界的隐藏状态,并通过行动使其预测成真。Kfriston, CC BY-SA 3.0 <creativecommons.org/licenses/by-sa/3.0>, 通过维基媒体公用领域
从贝叶斯的角度来看,这个想法是大脑维持一个世界的生成模型p(x,z),其中x是感官观察结果,z是这些感官观察结果的隐藏原因/潜在解释,大脑试图捕捉这些解释,反映了世界和世界模型中的不确定性。正如我们将看到的,许多生成模型,但不是所有,都被表述为概率性潜变量模型。
在贝叶斯语言中,给定这样的模型,这归结为潜在原因的先验分布p(z)(如果我住在纽约市,我观察到狮子的先验期望小于观察到狗的期望),观察结果p(x)的整体可能性,以及感官观察和隐藏原因之间的关系。
解析 x 和 z 之间的关系是许多生成建模的核心。
他们的关系反映在两个重要量上:后验概率p(z∣x)和可能性p(x|z),它们根据贝叶斯著名定律联系在一起:

后验概率p(z∣x)给出了在给定观察值的情况下,潜在原因的概率。由于一个称为不可处理性的问题,我们通常无法访问这一点:根据贝叶斯定理,我们需要 p(x)来计算它,这要求我们遍历所有可能的潜在原因,并检查它们如何解释x:

如果世界的模型复杂,这些是高维积分,因此这既不高效也完全不可能。
推断后验概率是许多生成模型的根本挑战。
在 Helmholtz 机器中,后验 p(z∣x) 是通过从数据中直接估计的,这个过程称为识别,通过学习一个近似后验 q(x|z) 并尽可能接近真实的 p(z∣x)。
估计似然 p(x|z) 的反向方向通常容易得多:给定一个特定的潜在变量 z,它只是告诉我们观察到的 x 的可能性。为此,我们通常不需要积分任何东西,只需直接运行模型即可。
似然性由生成网络参数化:给定一个 z,我们如何生成一个 x?如果我从一个隐藏原因开始,它对世界的影响会是什么样的?如果我想象一个人,那个人的面貌或声音会是什么样的?
在大多数生成模型中,这部分在实践中是最相关的(因为它生成图像/文本/音频)。一旦我们了解了 z 到 x 的映射是什么样的,我们可以通过采样一个 z 并通过生成网络传递它来生成样本。
在 Helmholtz 机器中,这两个方向都是通过神经网络参数化的,这些网络通过 Wake-Sleep 算法 交替训练,该算法受人类认知中类似过程的启发,在生成网络(觉醒)中比较生成样本与真实世界,并在识别网络(梦境)中将自身创作映射回其潜在状态。
识别网络 z ← x: q(z|x)
生成网络 z→ x: p(x|z)
潜在空间的结构通常有助于解释所学模型。 解缠潜在表示 并将其与可解释特征对齐在许多实际应用中都很重要,但也更普遍地用于实现更具解释性的模型。
以一个熟悉的例子来说,假设我们正在构建一个人脸图像的生成模型。按照 Helmholtz 机器的结构,我们将图像映射到潜在空间。然后我们可以尝试发现该潜在空间中有趣的变化轴。
一个有趣的变化轴可能与图像中人物的年龄有关。然后我们可以在潜在空间中强加约束(无论是在监督设置中通过提供带有年龄标签的数据,还是在无监督设置中通过识别学习到的潜在特征中的年龄,假设它导致显著的变化),以便其一个方向 z_age 编码嵌入图像上显示的年龄。
知道这个方向后,可以用来改变图像的年龄。如果另一个方向z_beard编码了有胡须的特征,我可以通过识别网络 q(z|x) 对图像 x 进行编码,得到一个 z,将其转换为 z’=z+az_age+bz_beard,并通过生成模型 p(x|z’) 发送回去,以查看一个有胡须、年长的自己版本。
像 OpenAI 的 GLOW 这样的模型让你在他们的网站上玩耍,但你可能已经熟悉了这种应用,例如在 Faceapp 中。
所有生成建模归结为(或多或少相关的)这种变体,尽管自 Helmholtz 机器时代以来它已显著发展,但在概率框架中捕获和重现数据的基本结构的理念依然存在。我将使用这些概念来解释过去十年中引起 AI 研究关注的一些最常见的生成模型版本。
变分自编码器(VAEs)
现在地球是空虚和混沌的,上帝说:“要有光,”就有了光。
— 创世记 1:1–5 (NIV)
VAEs 在 2013 年由 Kingma 和 Rezende 同时提出,并且在广泛的应用中找到了用途(从去噪到压缩到时间序列数据)。
这些方法是一个自然的起点,因为它们在精神上最接近 Helmholtz 机器:它们使用识别(编码器)和生成(解码器)网络。正如之前提到的,识别网络通过近似密度 q(z|x) 来近似 后验密度 p(z|x)。

VAEs 的训练目标是最小化负 证据下界(ELBO),这归结为找到一个尽可能接近真实后验 p(z|x) 的近似密度 q(z|x)。
在这里,我们通过以一种可以通过基于梯度的方法训练的方式对分布 q(z|x) 进行参数化来简单地近似分布。参数化分布可能有很多细微之处,但我们通常假设它近似为高斯分布,这意味着它具有均值 μ 和协方差 σ,构成了模型的自由参数。这些参数由神经网络直接学习(将训练数据 x 输入到神经网络中,输出为 μ)。
在 VAEs 中,近似后验 q(z|x) 用于绘制一个或多个随机样本,然后将这些样本代入 ELBO,ELBO 定义为从 q 中样本的期望值:

由于(负)ELBO 构成了损失,我们可以计算梯度,这反过来又让梯度下降发挥其魔力。
由于后验可能相当复杂,在实践中可能难以从中计算出良好行为的梯度,因为它们往往有很高的方差,需要许多样本。VAEs 核心的重参数化技巧巧妙地绕过了这个问题,通过将采样分为两个过程:
-
首先,从标准高斯分布 N(0,1) 中采样 ϵ。
-
然后,使用均值μ和标准差σ来变换ϵ,得到样本z=μ+σ×ϵ。
我发现重新参数化技巧特别优雅的一点是,它将每个生成过程的两个核心组件分开,无论是生成手写数字的更平凡任务,还是《圣经》引述中天与地的隐喻创建:一个由初始样本ϵ的“无形且空虚”噪声给出的随机组件,通过复杂的变换最终获得意义,通过解码器在观察到的世界中创造出一个模式x。

我在 MNIST 数据集上训练了一个简单的 VAE,绘制了一个随机初始状态,并通过解码器生成了这个看起来有点像 9 的图像。解码器隐式理解数据的结构,并从一个低维潜在状态中解码它。MNIST 数据集通过知识共享署名-相同方式共享 3.0许可证提供。
直观地说,ELBO 由重建项和熵项组成。由于熵在信息理论的世界中衡量信息内容的不可预测性或随机性,因此熵自然会在训练过程中起到正则化作用,在优化过程中权衡结构和噪声。如果 VAE 过于关注重建,它可能会过拟合数据,在潜在空间中捕捉到训练数据中的每一个细节(包括噪声)。但如果它过分关注熵,它可能会得到一个过于简单的潜在空间,无法捕捉数据的细微差别。
近似后验的熵与其协方差结构σ相关,这为我们提供了一个衡量初始样本中“无形且空虚”噪声(编码解释中的不确定性)剩余量的度量。如果我们想让整个过程变得确定性,我们可以简单地将σ设置为零,这样所有的不确定性都会被消除。
在一个确定性宇宙中,实际上没有真正的噪声,只有我们的模型无法捕捉的东西,或者我们缺乏必要的信息(我爱噪声,已经在这里写了一整篇文章)。正如乔治·博克所指出的:“所有模型都是错误的,有些是有用的”,而 VAEs 学会在过度自信和不足自信之间取得平衡。
这个组织原则有助于解释为什么 VAEs 在像降维(将输入数据中的重要信息与不重要信息分离)这样的任务上自然表现优异,以及去噪。正如前面提到的,VAEs 还可以实现潜在空间的结构化表示,从而产生可解释的特征。

对于在 MNIST 上训练的 VAE,采样 2D 潜在空间的网格会导致不同数字的连续变换。由我生成的, 基于这里提供的图形代码。
归一化流 (NF)
我听到有人称 NFs 为“强化版的重参数化技巧”,我非常喜欢这个描述。归一化流接管了 VAEs 的空白,通过将识别形式化为流的应用来完成。
NFs 通过一系列可逆变换将简单的概率分布(例如 VAEs 中通常使用的标准高斯分布 N(0,1))迭代地转换为更复杂、更精细的分布。

归一化流通过应用一系列可逆变换,将样本 z0 从简单分布变形为潜在复杂的分布。 janosh,MIT <opensource.org/licenses/mit-license.php>, 通过 Wikimedia Commons
VAEs 使用固定的分布和学习到的变换(均值和方差)将随机性与结构分开,而 NFs 则动态地塑造分布本身。它们通过跟踪雅可比行列式来实现这一点。这可以衡量变换的体积变化,例如,它如何收缩或拉伸空间,确保整个潜在空间以一致的方式变化。
如同 VAEs 的情况一样,一块无形的东西被塑造成形状。
关于 NFs 至少有两件很酷的事:它们是可逆的,因此允许在两个分布之间进行双向映射,这在许多情况下非常有用,例如,当试图估计密度时(因为一旦你映射回标准高斯分布 N(0,1),这通常比处理复杂的不可处理后验要容易得多),或者用于 异常检测,其思想是筛选出在学习到的分布下概率较低的数据。
OpenAI 的 GLOW,我之前提到过,也利用这种可逆性来操控潜在空间中的特征,如微笑、年龄或胡须,并几乎实时地获得修改后的图像。
另一个很酷的特点是它们对不同几何形状和流形的适应性。一个经典的例子是对 球面对称性 的应用,使得 NFs 能够形成存在于球面上的潜在表示。由于尽管有些观点相反,地球可能是球形的,因此球面对称性非常有用,例如在运行地球天气系统的模拟时。
扩散模型
从数据中创建噪声很简单;从噪声中创建数据是生成建模。
展开来说,扩散模型是过去几年中最成功的生成模型之一。尽管它们早在 2015 年就由Sohl-Dickstein 等人提出,但它们在图像生成方面的成功使其成为焦点,为 DALL-E、Midjourney 或 Stable Diffusion 奠定了基础。虽然它们的基本架构相当不同,但在概念上它们仍与 VAEs 和正常流相关。
扩散模型将生成过程分解为几个步骤:在每一步中,训练样本都会被噪声扰动。模型的目标是学习如何从样本中去除这些噪声。如果我之前没有足够清楚地说明噪声的迷人之处,那么在扩散模型中,它再次成为了主角。
在训练过程中,噪声会被反复添加到训练数据中。以图像为例,模型要么学习去除微小的噪声并打磨最终细节,要么在一个被扭曲的图像中完善模糊的形状:

稳定扩散中的生成过程。Benlisquare,CC BY-SA 4.0 <creativecommons.org/licenses/by-sa/4.0>, 通过维基媒体公用领域
尽管识别过程没有通过识别网络直接建模,且训练目标有很大变化,但添加噪声的过程以及随后的噪声减少监测可以看作是一种识别形式,初始噪声样本则是从 p(z_0)中提取的初始状态。
当生成一个全新的样本时,模型可以从纯噪声开始,并在试图弄清楚噪声下可能隐藏的内容时,创造出一些新的东西:

当我向 DALL-E 请求“随机白噪声”时,它返回的内容并不是完全随机的白噪声。它不可避免地反映了其训练数据的一些结构。
从无中生有的过程揭示了其训练数据隐含学习的分布。
扩散模型为何如此有效仍然有待讨论。除此之外,它们被比作基于能量的联想记忆模型(40 年前通过 Hopfield 网络而闻名)。
扩散模型还涉及基于得分的生成建模思想,这一思想由Song 等人推广:与直接计算数据似然的传统方法不同,这些模型专注于近似得分,在这种情况下,得分表示数据似然相对于数据本身的梯度。
直观地说,得分提供了一个样本应如何更改以提高其可能性的方向。通过不直接计算可能性,这些模型通常避开了一些我们之前遇到的计算挑战。
得分函数可以再次通过神经网络建模。表示它的一种特别有趣的方式是通过随机微分方程(SDEs),类似于神经常微分方程(Neural ODEs),通过神经网络表示微分方程(一种称为隐式层的东西)。
这在精神上类似于扩散模型的连续时间版本。从噪声开始,得分函数用于引导其朝向一个可能的样本(Stefano Ermon,他的实验室开发了这些技术,做了很棒的讲座,这里是一个更详细的讲解)。
在扩散模型中,生成过程同样是随机的,在每一步都加入了一个随机成分。由于生成过程被分解成多个步骤,这允许通过向链中回溯几个步骤并重新运行过程,从而引入样本的细微变化。

我在本文缩略图中使用的画作的细微变化,由 DALL-E 生成。
在一些最受欢迎的扩散模型应用中,如 DALL-E 或 Midjourney,初始状态不一定由纯随机样本z0从N(0,1)给出,而是由视觉和语言的联合嵌入p(z0|x)给出,其中 x 可以是例如由强大的 CLIP(对比语言-图像预训练)嵌入提供的文本输入。
条件生成在各种多模态学习设置中具有重要价值,因为它将不同的感官模式结合成一个连贯的框架。这可能会成为未来一些最激动人心的人工智能发展的重要组成部分。

一幅印象派画作,描绘了一个 AI 大脑想象日落,感谢 DALL-E。
生成对抗网络(GANs)
GANs是最近十年最受欢迎的生成模型类别之一,灵感来源于 Ian Goodfellow 和他的朋友们的一晚传奇饮酒经历。
GANs 甚至更远离了赫尔姆霍兹机器的双网络结构。如我所提到的,近似p(z|x)通常是生成模型的核心挑战,因此 GANs 干脆把识别抛到窗外,尝试在没有它的情况下进行生成。
GANs 也开始通过从p(z0)中抽取一个随机噪声向量(如在扩散模型中,这个初始向量也可以依赖于其他信息,比如文本),但随后仅训练生成网络(因为这是我们在许多应用中最感兴趣的部分),通过包含一个鉴别器,尝试使生成模型中的p(x|z)样本与训练数据的示例匹配。
生成网络的训练目的是产生能够欺骗鉴别器的数据。鉴别器则被训练来区分真实样本和虚假样本。
GANs 的优雅之处在于这种竞争动态:生成器在鉴别器的反馈指导下提高其生成数据的能力,而鉴别器本身则变得更擅长区分真实与虚假。这是完美的零和游戏,推动两个网络不断变得更好(有一定的深度伪造检测风险,我在这里写过).
然而,GANs 也有自身的一系列挑战,并且由于其竞争对手,特别是扩散模型的成功,GANs 最近的流行度有所下降。训练 GANs 可能非常不稳定。如果生成器在开始时产生低质量的样本,鉴别器的工作变得太容易,这使得生成器很难改进。另一方面,如果鉴别器变得过于强大,它可能会抑制生成器的成长,导致模式崩溃,即生成器最终只产生可能输出的一个子集。
Transformers 和大型语言模型(LLMs)
由于 Transformers 彻底革新了文本生成建模的格局,我不能在这里不提到它们。
简而言之,几乎所有 LLMs 都基于transformer 架构的变体,该架构实现了 2017 年谷歌论文中的自注意力机制,关于这一点的详细信息超出了本文的范围,但在许多其他地方有解释。这种架构允许 LLMs 学习输入序列之间的复杂关系,这在文本上特别有效。
一些 Transformer 变体,如 BERT,是在掩码语言建模设置下训练的。它们接收有些标记被掩盖的序列,并被训练去识别这些掩盖的标记。这在精神上与 VAEs 中的识别非常相似,其中掩盖单词可以被解释为对输入进行噪声处理。缺失的单词被填补,因为 Transformer 已经学习了输入数据的概率分布p(x),并能够理解给定上下文的最可能单词。
从生成的角度看,基于变压器的 LLM 模型会根据输入提示,建模每个潜在单词或短语在前一个单词之后的概率。这再次表达了始终存在的概率分布p(x|z)的一种变体。
然而,变压器中通常没有显式的隐藏变量z,因为提示和上下文本身就是单词。相反,自注意机制从所有观察到的单词(x1,x2,…,xt)中提取标记 p(x_i|(x1,x2,…,xt))的概率,当然还包括它在数十亿行训练数据中看到的所有单词和上下文的隐含分布。
虽然噪音并不是训练变压器的直接组成部分,但 LLM 自然地包含了一个概率成分。这很有意义,因为语言不是唯一确定的(因此,马尔可夫模型最初是在俄罗斯诗歌的基础上开发的,我在这里详细讨论),同一段文字可以用多种不同的方式表达:在生成响应或样本时,通常有几个单词适合给定的上下文,因此存在可能延续的分布 p(x_i|(x1,x2,…,xt)),
选择不同单词的概率可以通过所谓的温度超参数进行缩放。根据你是寻找创意还是确定性响应,这个参数可以有效地控制“噪声”水平。像 Chat-GPT 这样的 LLM 允许你在响应时请求特定的温度。
Chat-GPT 用高温设置重新表述了这段文字:“在变压器的旋转银河中,噪音不是主角,但 LLM 在语言的不确定节拍中舞动。构建答案不是为了找到那个单一的绝妙词汇,而是与潜在单词旋律的鸡尾酒一同狂欢 p(x_i|(x1,x2,…,xt))。”
虽然高温度使得 ChatGPT 听起来好像真的很兴奋,但这里的“高温度”类比于玻尔兹曼的热力学统计公式,该公式假设系统状态遵循指数分布,取决于系统的温度和状态的能量:

不同的 lambda 下的玻尔兹曼分布与温度呈反比关系。Newystats,CC BY-SA 4.0 <creativecommons.org/licenses/by-sa/4.0>, 通过维基共享资源
与变换器的类比并非巧合:softmax 函数在自注意力机制中用于将键和值之间的缩放点积分数映射为概率。softmax 与玻尔兹曼分布具有完全相同的函数形式,在这两种情况下都用于将未归一化的分数(在玻尔兹曼分布中是能量)映射到归一化的概率分布。
就像在热力学中,温度与熵密切相关,从而也与不确定性/噪声相关。在玻尔兹曼分布中,随着温度的升高,不同能量状态的概率变得更加均匀。最大均匀性导致最大熵,因为所有状态的可能性相等。在 LLMs 中,这意味着所有可能的词汇在下一阶段都有相等的概率被预测。然而,这并不意味着生成的文本完全是随机的,即使在高温下,正如我们在上面的例子中看到的。即使在较高温度下,最可能的标记的选择仍然大多代表了连贯的语言。
如果我在这篇文章中传达了一个观点,那就是噪声在所有生成模型中发挥着至关重要的作用。生成建模是一种将无形噪声赋予结构的艺术。
近年来的经验表明,许多途径通向罗马,不同的模型可以根据目标、数据模式以及在扩展到巨大模型规模(如变换器)和使用基于梯度的方法进行训练时的实际考虑来取得有效结果。
“我能创造的东西,我仍然不理解。”
— 理查德·费曼应该提到的
先进的生成模型的巨大规模和训练数据的复杂性带来了诠释这些模型的挑战。扩散模型和变换器并没有被构建为潜变量模型,因此可能像巨大的黑箱一样,解释滞后,特别是在对其对现实世界影响的担忧日益增加的情况下。
然而,我们仍然可能学会揭示其中的一些结构,例如 Max Tegmark 等人的这篇新论文,其中描述了在 LLMs 中发现空间和时间的中间表征,并将其比作可解释世界模型的出现。其他人则创造性地应用认知心理学的工具来理解 LLMs 的行为,就像我们试图理解人类行为的复杂性一样。
在最近的一期播客节目中,马克·安德森称,生成模型是否能在合成数据上进行有意义的训练和改进是一个价值万亿美元的问题。利用这种基本免费的数据进行训练将打开许多可能性,提供一种自我游戏(这种方法已经被 DeepMind 成功地用于 AlphaGo 和 AlphaFold)来继续调整生成模型,而无需依赖昂贵的精心策划的训练数据。安德森将这个问题与关于信号与噪声关系的信息论观点联系在一起,追溯到香农:简单地说,如何能在模型中包含比我们输入的更多的信息?
如果他们不喜欢,就称之为幻觉;如果他们喜欢,就称之为创造力。
— 马克·安德森
生成模型是否只是模仿它们在训练数据中看到的内容?训练过程中的噪声和模型本身的噪声在多大程度上导致了一种超越训练数据的泛化(我在最近的一篇关于免费午餐定理的文章中考虑了相关问题)?毕竟,噪声在机器学习模型中被广泛用于提升泛化能力。
噪声既能导致幻觉和创造力,也能引发替代事实的幻觉和之前不存在的替代视角的创造。通过生成模型,还可以认为“信息”不仅存在于原始数据中,还存在于这些数据的组合可能性中。生成模型为我们提供了一种新颖而诱人的方式来探索这种组合空间。用马克·吐温的话来说:
“没有真正的新想法。这个想法是不可能的。我们只是将许多旧想法放入某种心理万花筒中。我们给它转动,它们就会产生新的和有趣的组合。”
— 马克·吐温
而且,既然这句话本身并不是新想法,我们可以再次回到圣经(我没想到在一篇 AI 文章中会引用两次):
过去的将会重现,已做过的将会再做;在太阳底下没有新事物。
传道书 1:9 (圣经)
可以说,人类创造力中也能观察到噪声与结构之间的类似相互作用。在我最近的一篇关于天才与心理视觉化的文章中,我探讨了在大脑中,默认模式网络(斯科特·巴里·考夫曼称之为“想象网络”)的自由、无结构的思维漫游活动,往往能提供一种冲动,这种冲动随后被更为刻意、集中的练习和技能塑造为一些最令人惊叹的艺术作品和天才创作。

DALL-E 以杰克逊·波洛克的风格绘画,捕捉原始随机风格绘画的结构化随机性。
即使是最创新的艺术和科学作品,也必须用我们已经部分熟悉的语言来理解。正如维特根斯坦所指出的:没有私人语言。生成模型正在学习说我们的语言,学习逼近我们最关心的事物的p(x),并通过在噪声和结构之间权衡,揭示这种分布内外的无尽新模式。它们的创造力反过来可以用来激发我们的创造力。
“那些不愿意模仿任何东西的人,什么也不会产生。”
— 萨尔瓦多·达利
想到生成模型已经开始塑造我们的感官输入,质疑并推动我们对世界和心智的感知边界,以及我们对创造力和天才的理解,这一方面让人感到畏惧但也充满兴奋。
所以,我认为用 Chat-GPT 的话结束这篇文章再好不过了,它梦想成为列奥纳多·达芬奇:
“真正的天才不仅仅在于模仿,而在于将已知与未知融合的炼金术。”
— 列奥纳多·达芬奇/Chat-GPT
感谢阅读!
如果你喜欢我的写作,请订阅以通过邮件获取我的故事。
使用 GPT 3.5 和长期记忆的生成式问答
原文:
towardsdatascience.com/generative-question-answering-with-long-term-memory-c280e237b144
探索增强检索的机器学习新世界
·发布于 Towards Data Science ·6 分钟阅读·2023 年 2 月 17 日
--

图片由 Bret Kavanaugh 在 Unsplash 提供。最初发布于 pinecone.io,作者在该网站工作*。
生成式人工智能在 2022 年激发了几次“哇”的时刻。从像 OpenAI 的 DALL-E 2、Midjourney 和 Stable Diffusion 这样的生成艺术工具,到像 OpenAI 的 GPT-3.5 这一代模型、BLOOM 以及 LaMDA 和 ChatGPT 这样的聊天机器人。
生成式人工智能(Generative AI)正经历兴趣和创新的蓬勃发展,这并不令人惊讶[1]。然而,这仅仅是生成式人工智能广泛应用的第一年:这是一个新兴领域的早期阶段,准备颠覆我们与机器互动的方式。
最具启发性的用例之一属于Generative Question-Answering(GQA)。通过使用 GQA,我们可以为信息检索(IR)塑造类人互动。
我们每天都在使用信息检索系统。谷歌搜索对网络进行索引,并根据你的搜索词检索相关信息。Netflix 利用你在平台上的行为和历史推荐新的电视节目和电影,而亚马逊在产品方面也有类似的做法[2]。
这些信息检索(IR)的应用具有改变世界的潜力。然而,它们可能仅仅是我们在未来几个月和几年中看到的 IR 与生成式问答(GQA)结合的微弱回响。
想象一下,一个谷歌可以基于前 20 页的智能和深刻的摘要回答你的查询——突出关键点和信息来源。
现有技术已经使这变得可能且出乎意料的简单。本文将探讨增强检索的 GQA 及其如何与 Pinecone 和 OpenAI 一起实现。
最直接的 GQA 系统只需要用户文本查询和一个大型语言模型(LLM)。

最简单的 GQA 系统。
我们可以通过 OpenAI 访问世界上最先进的 LLM 之一。首先,我们需要注册一个 API 密钥。

注册账户后,可以通过点击你的账户(右上角)> 查看 API 密钥 > 创建新秘密密钥来创建 API 密钥。
然后我们切换到 Python 文件或笔记本,安装一些先决条件,并初始化与 OpenAI 的连接。
从这里,我们可以使用 OpenAI 完成端点来询问类似于 “谁是第十二位登月者,他们何时着陆?” 的问题:
我们立即得到一个准确的答案。然而,这个问题相对简单,如果我们问一个鲜为人知的话题会发生什么呢?
尽管这个答案在技术上是正确的,但它并不是一个真正的答案。它告诉我们使用监督学习方法来学习句子之间的关系。这两个事实都是正确的,但并没有回答原始问题。
有两种方法可以让我们的 LLM 更好地理解主题,并更准确地回答问题。
-
我们对包含微调句子转换器领域的文本数据进行 LLM 微调。
-
我们使用检索增强生成,即我们在 GQA 过程中添加一个信息检索组件。添加检索步骤可以让我们检索相关信息,并将其作为二级信息来源输入到 LLM 中。
在接下来的部分中,我们将概述如何实现第二种选择。
构建知识库
实现检索的第二种选择,我们需要一个外部的“知识库”。知识库既是存储信息的地方,又是有效检索这些信息的系统。
知识库是一个信息存储库,可以作为 GQA 模型的外部参考。我们可以将其视为 AI 系统的“长期记忆”。
我们将可以检索语义相关信息的知识库称为向量数据库。
向量数据库存储使用特定 ML 模型编码的信息的向量表示。这些模型对语言有一定的“理解”,可以将具有相似含义的段落编码到相似的向量空间,将不相似的段落编码到不同的向量空间。

我们可以通过 OpenAI 的嵌入端点实现这一点:
我们需要对许多记录重复执行嵌入过程,这些记录将作为我们管道的外部信息来源。这些记录仍需下载并准备好以进行嵌入。
数据准备
我们在知识库中使用的数据集是托管在 Hugging Face Datasets上的jamescalam/youtube-transcriptions数据集。它包含来自几个 ML 和技术 YouTube 频道的音频转录。我们用以下方式下载它:
数据集包含许多小的文本片段。我们需要合并几个片段,以创建包含更多有意义信息的更大文本块。
创建文本块后,我们可以开始初始化我们的知识库,并用数据填充它。
创建向量数据库
向量数据库是我们管道中的存储和检索组件。我们使用 Pinecone 作为我们的向量数据库。为此,我们需要注册一个免费的 API 密钥,并在下面输入它,以创建用于存储数据的索引。
然后我们这样嵌入和索引数据集:
我们准备将 OpenAI 的Completion和Embedding端点与我们的 Pinecone 向量数据库结合,创建一个检索增强 GQA 系统。
OP Stack
OpenAI Pinecone(OP)技术栈是构建高性能 AI 应用(包括检索增强 GQA)的越来越受欢迎的选择。
我们在查询时间的管道包括以下内容:
-
OpenAI
Embedding端点用于创建每个查询的向量表示。 -
Pinecone 向量数据库用于从以前索引的上下文数据库中搜索相关的段落。
-
OpenAI
Completion端点用于生成自然语言答案,考虑到检索到的上下文。

我们首先使用相同的编码器模型对查询进行编码,以创建查询向量xq。
查询向量xq用于通过index.query查询 Pinecone,并与之前索引的段落向量进行比较,以找到最相似的匹配项——在上面的res中返回。
使用这些返回的上下文,我们可以构建一个提示,指示生成 LLM 根据检索到的上下文回答问题。为了简单起见,我们将在一个名为retrieve的函数中完成所有这些工作。
请注意,为了可读性,生成的扩展查询(query_with_contexts)已被缩短。
从retrieve中,我们生成一个更长的提示(query_with_contexts),其中包含一些指令、上下文和原始问题。
然后,提示通过 OpenAI 的Completion端点输入生成 LLM。像以前一样,我们使用complete函数处理所有内容。
由于额外的“源知识”(直接输入模型的信息),我们消除了 LLM 的幻觉——生成了准确的回答。
除了提供更多的事实性回答外,我们还有 Pinecone 提供的信息来源用于生成我们的回答。将其添加到下游工具或应用中可以帮助提高用户对系统的信任,让用户确认所提供信息的可靠性。
这就是关于检索增强型生成式问答(GQA)系统的演示。
如前所示,单独使用大型语言模型(LLMs)效果非常好,但在面对更小众或具体的问题时却常常表现不佳。这通常会导致幻觉,这些幻觉不易被发现,且可能会被系统用户忽视。
通过为我们的 GQA 系统添加“长期记忆”组件,我们可以利用外部知识库来提高系统的准确性和用户对生成内容的信任。
自然,这类技术具有广泛的潜力。尽管它是一项新技术,但我们已经在YouChat、一些播客搜索应用中看到它的应用,并且有传闻称它将成为对抗谷歌的挑战者[3]。
任何需要信息的地方都有可能出现颠覆,而检索增强型 GQA 代表了利用当前过时信息检索系统的最佳机会之一。
[1] E. Griffith, C. Metz, 《人工智能的新领域蓬勃发展,即使在技术低迷时》(2023),纽约时报
[2] G. Linden, B. Smith, J. York, 《亚马逊推荐:物品间协作过滤》(2003),IEEE
[3] T. Warren, 《微软通过将 ChatGPT 与 Bing 搜索整合来挑战谷歌》(2023),The Verge
最初发表于 https://www.pinecone.io 。
所有图片均为作者提供,除非另有说明
遗传算法:寻找最佳电子邮件投递时间表以最大化互动
使用进化算法优化消费者银行的 D2C 活动
·发表于 Towards Data Science ·阅读时间 9 分钟·2023 年 11 月 1 日
--

作者使用 Bing Image Creator 生成的图像
某些电子邮件投递时间是否会导致更高的互动?
电子邮件营销人员常面临的一个最常见问题是何时发送电子邮件以最大化打开率、点击率和转化率。这个问题没有明确的答案,因为不同的受众可能有不同的偏好和行为。他们处于哪个时区?他们使用什么设备查看电子邮件?他们的日常生活和时间表是什么?他们多频繁检查电子邮件?这些因素可能会影响他们何时最有可能打开和互动你的电子邮件。
你可以使用像 A/B 测试或分割测试这样的工具来比较在不同时间发送的电子邮件活动的表现。你还可以使用像 Google Analytics 或 Mailchimp 这样的分析工具来跟踪电子邮件活动的指标,例如打开率、点击率、跳出率和转化率。通过分析数据,你可以确定适合你受众和目标的最佳投递时间。
当你对客户在不同时间的点击率和打开率有了充分了解后,下一步是创建一个最佳的投递时间表,以最大化这些指标,同时避免负面影响,例如用户退订——我们在电子邮件营销中称之为“疲劳”现象。
在这篇文章中,我将尝试使用遗传算法来解决这个优化问题——这种方法在营销领域并不常见。
我理解遗传算法可能会让人感到困惑。我们将看到如何使用简单的 Pandas 操作来实现该算法的核心概念。
定义问题陈述
我在下一步行动的文章中介绍了虚构但受欢迎的消费者银行 ULFC。在那次努力中,我们创建了一个强化模型,根据客户过去的响应,建议向其按揭客户推出下一个最佳优惠。现在,ULFC 希望数据科学团队推荐一个最优的时间表——日期、时间、数量——以作为季度电子邮件活动的一部分。目标是最大化客户对电子邮件的参与,而不流失他们。
我将使用这份合成数据,这是我创建的。它包含了针对 1000 名客户的历史点击率、打开率和疲劳率数据,这些客户将作为本次活动的目标。
让我们先来看一下数据。
-
打开率通常较高,可达 30%。
-
点击率更具意图性,范围在 0-3%之间
-
疲劳率(客户取消电子邮件营销的概率)更具上下文性,并且可能因细分/活动而异。我们将假设在 0-5%之间。
import numpy
import pandas
open_rate_df = pd.read_csv("open_rate.csv").set_index("Customer_ID")
click_rate_df = pd.read_csv("click_rate.csv").set_index("Customer_ID")
fatigue_rate_df = pd.read_csv("fatigue_rate.csv").set_index("Customer_ID")

作者提供的图片 — 1000 名客户的每日打开率

作者提供的图片 — 1000 名客户的每日点击率

作者提供的图片 — 1000 名客户的每日疲劳率
遗传算法简介
遗传算法(GA)是一种通过模拟自然进化过程来寻找问题解决方案的方法。遗传算法通过创建一个可能解决方案的种群(称为个体),并对其应用选择、交叉和变异等生物学操作符来工作。这些个体通过一个适应度函数进行评估,该函数衡量它们解决问题的效果。最适应的个体更可能生存和繁殖,而较不适应的个体则被淘汰。通过这种方式,遗传算法可以在搜索空间中进行探索,并随着时间的推移趋向于最优或接近最优的解决方案。以下是任何遗传算法用例中涉及的核心步骤。

作者提供的图片 — 任何遗传算法中的核心步骤
设置参数
客户的数量 K 是 1000。在这次电子邮件营销活动中,我们希望将打开率和点击率最大化。它们的权重由 w₀ 和 wc 分别设定。
假设我们不希望在时间范围内向同一客户发送超过 2 封邮件(由 E 设置)。另一方面,我们希望将每个客户的预期疲劳率限制在 10%以下(由 T 设置)。
K = 1000
w0 = 1
wc = 1
T = 0.1
E = 2
创建初始种群
在这里,我们基本上创建了初始候选者,这些候选者作为我们算法通过选择、交叉和变异来改进的起始种群。每个候选者是一组 0 和 1 的二进制决策变量,表示是否在该时间段发送邮件。

作者提供的图片 — 示例候选者
def initialize_population(num_candidates):
all_candidates = []
for i in range(num_candidates):
# Create random decision variables such that only 1 email is delivered to a person per day
base = np.random.choice([-1,0,1,2], size=(K,5), p=[0.95, 0.05/3, 0.05/3, 0.05/3]).tolist()
data = []
for k in range(K):
vals = []
for t in base[k]:
if t == -1:
vals.extend([0,0,0])
elif t == 0:
vals.extend([1,0,0])
elif t == 1:
vals.extend([0,1,0])
elif t == 2:
vals.extend([0,0,1])
data.append(vals)
all_candidates.append(
pd.DataFrame(
data = np.array(data),
index = customer_ids,
columns = timeslots
)
)
return all_candidates
定义适应度函数
目标是根据每个接收者的偏好和行为选择最佳的邮件发送日期。我们希望增加他们打开和点击邮件的可能性。我们假设每个接收者在每一天和时间段的打开率和点击率不同。我们为打开率和点击率添加权重,这可以根据是转换还是意识提升活动进行调整 [1]。因此,我们需要找到每个接收者得分最高的日期和时间段。
我们需要考虑两个限制:
-
疲劳限制: 每个接收者在一定的频率下会感到疲倦,这种频率因人而异。如果我们发送过多的邮件,他们可能会退订。我们需要将每个接收者的总疲劳率保持在一定的限制内。
-
总投递限制: 这控制了我们可以发送给一个接收者的最大邮件数量。

作者提供的图片 — 目标函数
在下面的代码中,我已经将这一目标转换为一个适应度函数,该函数返回目标分数,并根据约束 1 和 2 是否满足返回布尔值。
def fitness_function(candidate_df):
obj = ((w0 * open_rate_df + wc * click_rate_df) * candidate_df).values.sum()
const1 = ((fatigue_rate_df * candidate_df).sum(axis=1) > T).sum() == 0
const2 = (candidate_df.sum(axis=1) > E).sum() == 0
return obj, const1, const2
def evaluate_fitness(x):
score, const1, const2 = fitness_function(x)
if const1 and const2: return score
else: return -1
现在让我们创建一个简单的函数来测量一些关键指标,以帮助我们了解投递时间表的质量。
def print_candidate_metrics(candidate):
open_rates = (open_rate_df * candidate).values.flatten()
open_rate = round(open_rates[open_rates > 0].mean() * 100, 1)
expected_opens = round(open_rates[open_rates > 0].sum(), 1)
print(f"Open Rate = {open_rate}%")
print(f"Expected number of opens = {expected_opens}")
click_rates = (click_rate_df * candidate).values.flatten()
click_rate = round(click_rates[click_rates > 0].mean() * 100, 1)
expected_clicks = round(click_rates[click_rates > 0].sum(), 1)
print(f"Click Rate = {click_rate}%")
print(f"Expected number of clicks = {expected_clicks}")
fatigue_rates = (fatigue_rate_df * candidate).values.flatten()
fatigue_rate = round(fatigue_rates[fatigue_rates > 0].mean() * 100, 1)
print(f"Fatigue Rate = {fatigue_rate}%")
首先,我们将检查一些初始随机选择的候选者的指标作为基准。
initial_candidates = initialize_population(3)
for i in initial_candidates:
print_candidate_metrics(i)
print()

作者提供的图片 — 初始随机生成的候选者时间表的指标
我看到初始候选者在时间范围内平均生成 35–40 次打开。我们还看到大约 4 次预期点击。
让我们跟随这个过程,构建算法,看看它是否能改善这些指标。
自然选择中的过程
驱动代际过程的主要过程是 选择。在遗传算法的实践中,这有许多定义。最简单的一种是根据适应度分数对所有候选者进行排名,并选择前 N 名将其属性传递给下一代。这种方法被称为精英主义或选择精英候选者的过程。
另外两个过程是交叉和变异。这两个操作是遗传算法中的基本操作,有助于从现有的解决方案中创造新的解决方案。交叉将两个或多个父代候选者的特征结合起来,生成子代候选者,而变异则在候选者中引入随机变化,以创造多样性并避免局部最优解。交叉和变异的概率控制着种群中候选者的变化率。较高的交叉概率意味着将通过结合父代生成更多的子代,而较低的变异概率则意味着较少的子代会随机改变。这些概率可以是固定的,也可以是自适应的,具体取决于问题和适应度函数。
我打算实施一个简单的交叉操作,我将随机选择一个行号(即交叉点),并从第一个子代中提取该行号之前的所有行,从第二个子代中提取该行号之后的所有行。
def crossover(candidate1, candidate2):
crossover_point = random.randint(1,K-1)
child1 = pd.concat([candidate1.iloc[:crossover_point,:], candidate2.iloc[crossover_point:,:]])
child2 = pd.concat([candidate1.iloc[:crossover_point], candidate2.iloc[crossover_point:]])
return child1, child2
变异有点有趣。我的实现是单点变异。我随机选择一个时间段中的一天(即变异点),并改变那一天的所有决策(0 和 1)。
def mutate(candidate):
mutation_point = random.randint(0,14)
new_candidate = candidate.copy()
new_candidate.iloc[:,mutation_point] = np.random.choice([0, 1], size=K, p=[.99, .01])
return new_candidate
通常,变异对于探索搜索空间的新区域非常有用,而交叉则有助于利用局部最优解。要更好地理解探索和利用,查看一下我的文章关于下一步行动模型。利用这些功能,让我们构建 ULFC 的算法,将所有部分拼接在一起。
最终算法
首先,我们有几个参数控制代数(把它看作是迭代)、每代结束时允许的种群大小、交叉和变异的概率,以及精英数量(用于选择过程)。
generations = 100
population_size = 50
crossover_rate = 0.8
mutation_rate = 0.2
elitism_size = 2
如之前的流程图所示,我们的算法有 5 个步骤。
-
创建随机初始种群。
-
使用适应度函数评估候选者。
-
选择精英。
-
应用交叉以创建新的子代候选者。
-
对新创建的候选者应用变异。
重新评估并重复多代。
def optimal_schedule():
# Create initial candidates
population = initialize_population(population_size)
for generation in range(generations):
new_population = []
# Elitism
population.sort(key=lambda x: evaluate_fitness(x), reverse=True)
new_population.extend(population[:elitism_size])
while len(new_population) < population_size:
parent1, parent2 = random.choices(population, k=2)
if random.random() < crossover_rate:
child1, child2 = crossover(parent1, parent2)
else:
child1, child2 = parent1.copy(), parent2.copy()
if random.random() < mutation_rate:
child1 = mutate(child1)
if random.random() < mutation_rate:
child2 = mutate(child2)
new_population.extend([child1, child2])
population = new_population
return population[0]
最后,让我们运行刚刚构建的算法。
result = optimal_schedule()
print_candidate_metrics(result)

作者提供的图片 — 最优时间表的指标
最优时间表的预期打开次数和点击次数比初始候选者更高。你可以在更多代中运行此算法以进一步优化。你甚至可以进行时间段分析,以跟踪推荐的时间表变化。

作者提供的图片
总结
媒介如电子邮件和短信可以是非常具有成本效益和说服力的渠道来接触受众。但进一步优化电子邮件营销的时间安排可以确保企业从其电子邮件中获得最大互动。这些项目还解锁了许多后续分析。例如,流行的 CRM 软件 HubSpot 的团队每年发布关于“发送电子邮件的最佳时间”的报告 [2]。根据他们 2023 年的发现,电子邮件从晚上 9 点到 12 点获得的互动最多。然而,这些数据来自对他们客户的调查。如果你在公司内部进行时间安排优化,你可以以更高的准确性和细致度产生这样的见解,从而适应你的业务案例。
最后,作为一种警告,我建议谨慎使用遗传算法。只有当你知道如何在你的问题背景下解释和说明它时。它只是另一种优化算法,还有许多可能更容易解释和更快的算法。另一方面,遗传算法是稳健的,并且可以很好地达到全局最优。
感谢阅读 😁
参考文献:
-
Zhang, L., He, J., Yan, Z., Dai, W., Pani, A. (2020). 遗传算法在多目标电子邮件营销投递问题中的应用
-
发送电子邮件的最佳时间 [2023 研究] 由 Flori Needle 撰写 —
blog.hubspot.com/marketing/best-time-to-send-email
天才圈子:绘制诺贝尔网络
原文:
towardsdatascience.com/genius-cliques-mapping-out-the-nobel-network-e9350552084

诺贝尔奖获得者的网络。图像由作者提供。
·发表在 Towards Data Science ·7 min read·2023 年 9 月 27 日
--
结合网络科学、数据可视化和维基百科,揭示所有诺贝尔奖获得者之间隐藏的联系。
这篇文章最初发表在《Nightingale》第二期,这是数据可视化协会的印刷杂志。
启发性的科学家:诺贝尔圈内外
尽管我在网络与数据科学方面获得了博士学位,但每当寻找灵感时,我总是紧密关注我在物理学方面的根源。成长在匈牙利,我特别惊叹于“火星人”这一群体的成就,这些著名科学家在二战期间从匈牙利移居到美国。有趣的是,他们中的一些人甚至上过同一所高中。
火星人包括例如列奥·西拉德,他不仅发现了核链反应理论,还与阿尔伯特·爱因斯坦和尤金·维格纳——曼哈顿计划的关键科学家——共同申请了冰箱专利,推动了第一台核反应堆的发展。由于他的贡献,维格纳在 1963 年获得了诺贝尔物理学奖,这 18 个与匈牙利背景的思想家相关的诺贝尔奖之一。
那 18 个奖项仅占所有诺贝尔奖的约百分之三。事实上,自 1901 年以来,大约 600 个奖项授予了不到一千位获奖者,涉及领域包括物理学、化学、生理学或医学、文学、和平,以及从 1969 年起的经济学。网站 NobelPrize.org 突出了有关奖项及其获奖者的其他有趣统计数据:从最年轻(17 岁)和最年长(97 岁)的获奖者,到像约翰·巴尔丁(物理学,1956 年和 1972 年)、林纳斯·鲍林(化学 1954 年,和平 1962 年)以及玛丽·居里(物理学 1903 年,化学 1911 年)等多次获奖者。
居里家族主导了诺贝尔奖。玛丽·居里首次与丈夫皮埃尔分享了一个奖项,后来又获得了第二个奖项。此外,这对伟大的夫妇还培养了一位诺贝尔奖得主。他们的女儿伊雷娜·居里与丈夫弗雷德里克·朱利奥共同获得了 1935 年化学奖。玛丽·居里还曾是另一个精彩的诺贝尔得主小圈子的成员(遗憾的是,皮埃尔于 1906 年去世):1911 年的索尔维会议。这可能是科学史上最令人印象深刻的阵容之一:29 名与会者中有 27 人已经获得或后来获得了诺贝尔奖。
建立诺贝尔网络
火星人、居里家族、曼哈顿计划和索尔维会议的故事都表明,在幕后,一些紧密交织的社会网络在诺贝尔奖得主之间运作。为了追踪这个网络,我访问了最广泛使用的在线百科全书维基百科,并收集了每位获奖者的维基页面文本。
然后,在每位获奖者的页面文本中,我统计了所有其他名字的提及情况,并注意是否有任何配对共享了在维基百科上值得注意的共同历史。通过这种方式,我建立了一个包含 682 个节点和 588 条链接的网络,其中节点对应于获奖者,两个节点之间的链接强度与他们的维基页面互相引用的次数成正比。此外,我下载了每位获奖者页面的总浏览量,并将其网络节点大小设置为该数字的对数。这个节点缩放最终突出了那些已经成为家喻户晓的名字。为了完成网络可视化,我应用了与科学学科相对应的颜色编码。您可以在图 1 中找到结果。

图 1. 诺贝尔网络。根据维基百科页面之间的交叉引用,展示了至少有一个连接的诺贝尔奖得主网络。每个节点对应一个获奖者,边的宽度表示交叉引用的数量,节点的大小与其维基页面的总浏览量成正比。颜色编码表示他们所获奖项的学科(在有多个不同奖项的情况下,从获奖学科中随机选择一种颜色)。视图数量最高的节点被标记出来。
诺贝尔网络的启示
对我来说,作为一个网络科学家,关于网络的第一个也是最引人注目的观察是它的核心-边缘分离:中心的一个大连接组件(所谓的巨型组件),包含超过 30%的节点,以及其周围的一个碎片化环圈,包含较小的网络组件,大小最多为十个节点。最常见的组件大小为两个和三个节点,这与诺贝尔奖可以在最多三位获奖者之间分享的事实非常一致,而共享奖项在大多数领域变得越来越普遍。
我还发现,巨型组件中的节点较大,这意味着这些获奖者的可见性显著更高,并且搜索命中数量也更多,这通过他们的 Wiki 浏览量的对数来衡量。通过查看数据,结果显示中央组件的 Wiki 浏览量中位数为 351,005,而外环组件仅为 170,510,并且中央组件的平均浏览量大约是外环组件的 2.5 倍。因此,看起来中央 clique 的人气远远更高!
他们是谁?绿色至黄色的色调与红色调的对比旨在区分科学与人文学科,这与巨型组件的左右两侧相对应。这些侧面由詹姆斯·查德威克爵士连接,他因发现中子而获得了 1935 年的诺贝尔物理学奖,并且还成为了联合国的科学顾问。科学一侧(图 2a),以阿尔伯特·爱因斯坦和马克斯·普朗克等研究人员为代表,似乎在普鲁士科学院(1700–1945)中有着深厚的根基,并且在现代物理学的奠基人中也很强大,从居里夫人到恩里科·费米和尤金·维格纳或乔尔吉·赫维希(都有匈牙利和火星的根源)。

图 2a. 图 1 的放大视图,重点关注科学领域的 clique。
在人文学科一侧(图 2b),我们可以看到一些相当受欢迎的人物。显然,科学并不是通向世界声誉的途径!有两个突出的中央获奖组织立即引人注目:欧盟和联合国,它们都获得了诺贝尔和平奖。值得注意的个人包括杰出的政治家,如巴拉克·奥巴马或亨利·基辛格,人权活动家纳尔逊·曼德拉,以及经济学家米尔顿·弗里德曼(有匈牙利,但非火星的根源)。
至于外环部分,有一些著名的社交人物,如欧内斯特·海明威、温斯顿·丘吉尔、富兰克林·D·罗斯福和理查德·费曼——个人来说,我最喜欢的诺贝尔奖获得者,因为他既有科学贡献,又有顽皮和古怪的个性。这些人尽管生活繁忙,但在网络中相对孤立,这可能是由于他们活跃时期的时间和地理位置与其他获奖者的差异。此外,这里的数据可能不完整,因为维基百科并不完美,也不百分之百准确地记录社交关系,而且遗憾的是(或者说并非如此?)当时并没有 Facebook。
最后,匈牙利人和火星人:从数据来看,许多人甚至与诺贝尔奖得主没有任何联系,而那些属于网络中的成员也只是分散在各处。这背后的原因不清楚——也许火星人的传说被夸大了,或者可能他们中没有足够多人获得诺贝尔奖以至于在网络中显现。虽然可以确定的是,曼哈顿计划在运作期间有七位诺贝尔奖得主,后来又有十几位,但其中只有维根来自火星人。

图 2b. 图 1 的放大,重点关注人文学科中的小圈子。
结论
尽管在《诺贝尔网络》中扫描这些名字和联系非常激励人心,并且让我真切地感觉自己是在“站在巨人的肩膀上”,但网络还是存在缺陷。除了边缘的东欧人,我们看到一个精英俱乐部在中心出现,主要的知名名字集中在巨大的组件中,排除了三分之二的网络。这表明,三分之二的获奖者仅仅是领奖后回到自己的工作中,只有剩下的三分之一参与了可见的联系,无论是友谊还是合作。正如“整体大于部分之和”,错过了超过 60%的那些才智横溢的头脑从中心的思想流动中,似乎是一种遗憾。
更多的错失机会出现了。中心组件本身被分为两个阵营:科学和人文学科。这种极化与今天的主要方向——跨学科研究非常不符,这使我们能够解决前所未有的重大社会问题。此外,网络揭示了女性获奖者的数量较少。尽管玛丽·居里的历史卓越,但女性获奖者仅约占 6%,其中大多数获得了和平奖(109 位中的 16.5%),而在物理学奖中获奖者最少(219 位中的 1.8%)。
但一切并未丧失。像这样的映射练习可以帮助揭示这些问题,否则,即使是最狂热的诺贝尔奖迷也几乎看不到。放大并利用网络科学可以突出那些隐藏的模式,并启发理解,这是识别未来解决方案的第一步,无论是关于性别差距还是精英小圈子。
群体上的几何深度学习
Continuous vs discrete approaches on the sphere
·
关注 发表在 Towards Data Science ·6 min read·Mar 6, 2023
--
理想情况下,群体上的几何深度学习技术应该能够编码对群体变换的等变性,以提供良好的表示空间和出色的性能,同时也要具备计算效率。然而,没有一种方法能够同时提供这两种理想的属性。连续的方法提供了优秀的等变性,但计算成本非常高。离散的方法通常计算效率相对较高,但牺牲了等变性。我们指出了未来能够兼具这两者优点的技术。

照片由 Serg Antonov 提供,发布在 Unsplash
群体上的深度学习是几何深度学习一个快速增长的领域(参见我们最近的 TDS 文章 几何深度学习简要介绍)。群体包括具有全局对称性的同质空间,其中最典型的例子是球面。
几何深度学习在群体上的实际应用很普遍,尤其是在球面上。例如,球形数据在许多应用中出现,不仅在数据直接获取自球面时(例如地球上的数据或通过 360° 相机捕捉全景照片和视频时),还包括考虑球面对称性时(例如分子化学或磁共振成像)。
我们需要在群体上既高效又可扩展到大规模高分辨率数据集的深度学习技术。一般来说,这个问题仍然没有解决。

一个球形数据的例子。[照片由 NASA 提供,来自 Unsplash]
目标
深度学习技术之所以如此有效,部分原因是现代架构中编码的归纳偏差。
一个特别强大的归纳偏差是编码数据已知满足的对称性(如我们 TDS 文章 *爱因斯坦可以教我们什么关于机器学习**)所阐述的)。例如,卷积神经网络(CNNs)编码了平移对称性,或更确切地说,平移等变性,如下图所示。

平移等变性的说明。给定一张图像(左上),应用卷积核(𝒜)以获得特征图(右上),然后平移(𝒯)特征图(右下),等同于首先平移图像(左下),然后应用卷积核(右下)。[原始图形由作者创建。]
在深度学习架构中编码等变性会产生行为良好的特征空间,使得学习可以非常有效地进行。
因此,对于群体上的几何深度学习,我们希望编码对各种群体变换的等变性,这通常会带来非常好的性能。然而,在一般的群体设置中,这变得非常计算密集——在许多情况下是不可行的。
如何在深度学习架构中以计算上可扩展的方式编码等变性是一个活跃的研究领域。
群体卷积
卷积的概念,负责 CNN 架构在平面图像上的巨大成功,自然地编码了等变性,并且可以推广到群体设置。
群上信号(即数据、特征图)f 与滤波器 𝝭 的群卷积表示为

其中 g 是群 G 的一个元素,而 dµ(u) 是积分的(Haar)测度。上述表达式与更常见的平面设置中的卷积完全类似。我们对滤波器进行变换(对于平面 CNN 来说是平移),与感兴趣的信号相乘,然后求和,即积分。
在球面上,我们考虑由 3D 旋转给出的变换,因此球面上的信号卷积表示为

其中 R 表示旋转,ω 为球面坐标。
一旦定义了群上的卷积,我们就可以以类似于标准平面 CNN 的方式在群上构建 CNN。即,通过组合卷积和逐点非线性激活(还包括适当地构建在群上的池化和归一化层)。
问题仍然是:我们如何在实践中计算群卷积?
一方面,我们希望实现能够准确捕捉卷积上的等变性特性。另一方面,我们希望实现具有高计算效率。如我们将看到的,现有方法通常只能捕捉这些要求中的一个,而不能同时满足两个要求。
离散球面 CNN 方法
现有的方法可以大致分为离散和连续方法。
离散方法处理数据的离散版本,通常是像素或图形表示,这通常具有很高的计算效率。然而,通常不存在规则的离散化。
以球面为例,众所周知,球面的规则离散化并不存在。因此,没有方法可以以不变于旋转的方式对球面进行离散化,如下图所示。

在球面上旋转一组像素会得到一组不能与现有集合重叠的像素。这在球面的所有采样中都是正确的。[原始图形由作者创建。]
在离散空间上直接定义的操作无法严格捕捉等变性。
因此,离散方法在计算性能上具有优势,但代价是等变性。
连续球面 CNN 方法
作为上述离散方法的替代方案,也可以考虑信号的连续表示。
球面上的函数可以通过球面谐波的展开表示(如下图所示)。对于带限信号,可以在有限的样本集合中捕捉到信号的所有信息内容,从中可以准确地计算球面谐波系数[1]。这类似于著名的奈奎斯特-香农采样定理扩展到球面。

球面谐波函数。[图像来源于 Wikimedia Commons。]
由于球面是一个紧致流形,其谐波空间是离散的。因此,通过使用有限的球面谐波空间表示,可以访问底层的连续信号。
各种球面 CNN 架构已经被构建,其中卷积通过其谐波表示来计算[2–6]。通过访问底层的连续信号,这些方法实现了卓越的等变性。然而,它们涉及反复执行球面谐波变换,这在计算上是昂贵的。
连续方法准确捕捉旋转等变性,但计算要求高。
二分法:离散 vs 连续方法
如上所示,离散和连续方法之间存在一种二分法,如下图所示。理想情况下,我们希望技术既具有等变性又具有计算可扩展性。
然而,连续方法提供等变性,但计算成本很高。另一方面,离散方法通常计算效率较高,但牺牲了等变性。

连续和离散几何深度学习技术在群体上的二分法。[原始图由作者创建。]
破除二分法
我们期望在群体上进行几何深度学习的技术能够提供等变性(这通常转化为良好的表示空间和卓越的性能),同时也具有计算可扩展性。
在我们的下一篇文章中,我们将描述一种新的混合离散-连续(DISCO)方法,最近被 ICLR 接受,这种方法正好实现了这些目标[7]。通过保持表示的一些组件为连续的,我们实现了卓越的等变性,而其他组件则被离散化以提供高效的可扩展计算。
参考文献
[1] McEwen & Wiaux, A novel sampling theorem on the sphere, IEEE TSP (2012), arXiv:1110.6298
[2] Cohen, Geiger, Koehler, Welling, Spherical CNNs, ICLR (2018), arxiv:1801.10130。
[3] Esteves, Allen-Blanchette, Makadia, Daniilidis, 学习 SO(3)等变表示的球面卷积神经网络,ECCV(2018),arXiv:1711.06721。
[4] Kondor, Lin, Trivedi, Clebsch-Gordan 网络:一种完全傅里叶空间的球面卷积神经网络,NeurIPS(2018),arXiv:1806.09231
[5] Cobb, Wallis, Mavor-Parker, Marignier, Price, d’Avezac, McEwen, 高效的广义球面卷积神经网络,ICLR(2021),arXiv:2010.11661
[6] McEwen, Wallis, Mavor-Parker,球面上的散射网络:用于可扩展和旋转等变的球面卷积神经网络*,ICLR(2022),arXiv:2102.02828
[7] Ocampo, Price, McEwen, 通过离散-连续(DISCO)卷积实现可扩展和等变的球面卷积神经网络,ICLR(2023),arXiv:2209.13603
机器学习与经典统计中的线性回归几何解释
通过视觉和分析来揭示线性回归的困惑
·
关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 12 月 12 日
--
图片:线性回归插图,作者 Stpasha,通过维基共享资源(公共领域)。原始图片链接:upload.wikimedia.org/wikipedia/commons/8/87/OLS_geometric_interpretation.svg
上述图像表示了普通最小二乘法(OLS)或线性回归的几何解释(在经典统计中这两个词可以互换使用)。让我们以直观的方式解析我们所看到的内容:
-
变量(X1 和 X2): 想象你有两个变量 X1 和 X2。这些变量可以代表任何东西,比如你学习的小时数和你参加的练习考试次数。
-
数据点(y): 现在,你有了你的结果或你试图预测的内容,我们称之为‘y’。在我们的例子中,这可能是你在实际考试中的成绩。
-
平面(colX): 平面代表通过组合不同数量的变量 X1 和 X2 可以得到的所有可能预测值。在我们的例子中,它可能代表基于不同的学习时间和练习考试次数你可能预测的所有可能考试成绩。
-
估计系数(Beta1 和 Beta2): 这些是 OLS 方法对每个变量对你的成绩影响程度的最佳猜测。因此,Beta1 可能告诉你每多学一个小时成绩预计会增加多少,而 Beta2 可能告诉你每增加一个练习考试成绩会增加多少。
-
预测点(XB^): 这是基于估计系数你得到的预测成绩。它位于平面上,因为它是使用 OLS 估计的变量 X1 和 X2 的组合。
-
实际点(y): 这是你的实际考试成绩。
-
误差(ε): 这是你实际成绩和预测成绩之间的差异。换句话说,就是预测与现实的偏差。
那么,OLS 如何运作呢?
OLS 尝试找到 Beta1 和 Beta2 的值,使得当你使用 X1 和 X2(学习小时数和练习考试次数)来预测‘y’(考试成绩)时,所有数据点的误差(ε)尽可能小。在图形中,这就像是调整平面,直到垂直虚线(表示误差)总长度尽可能短。实际数据点(y)到平面(colX)的最短距离总是垂直于平面的直线。OLS 找到一个特定的平面,使这些垂直距离对于所有点都达到最小。
换句话说,OLS 试图“拟合”平面,使其尽可能接近你的实际成绩,同时认识到它通常不会通过所有实际点,因为现实生活中很少有那么完美的情况。
这就像把最好的纸张贴在一群铅笔点下面,使纸张尽可能接近所有点。
让我们回顾 OLS 的主要假设,并将其与上述视觉图像联系起来:
1. 线性性
假设: 自变量(X1,X2)与因变量(y)之间的关系是线性的。
视觉解释: 在图像中,这就是我们使用平面(colX)来表示 X1 和 X2 组合的原因。如果关系不是线性的,我们无法用平面来表示,它会是曲线或其他形状。
2. 独立性
假设: 观察值彼此独立。
视觉解释: 每个数据点(表示一个观察值)是独立绘制的。如果存在依赖性,我们会在误差(ε)中看到系统的模式,比如它们都集中在平面的一侧,这表明一个数据点的位置可能预测另一个,违反了这一假设。
3. 同方差性
假设: 误差项(ε)的方差在所有自变量水平上保持不变。
视觉解释: 理想情况下,实际数据点(y)到预测平面(colX)的垂直距离应该均匀分布。这些距离不应有漏斗形状或模式;它们应显得随机。如果误差随着 X1 或 X2 的增加而增大或减小,这将违反同方差性。
4. 无完美多重共线性
假设: 自变量之间没有完全相关性。
视觉解释: 在图示中,X1 和 X2 由两个方向不同的箭头表示。如果它们完全相关,它们会指向完全相同的方向,我们将不会有平面而是直线。这将使我们无法估计 X1 和 X2 对 y 的独特影响。
5. 无自相关性
假设: 误差项之间不相关。
视觉解释: 这个假设涉及误差项,虽然在图像中没有明确显示,但我们推断每个误差项(ε)是随机的,不受前一个或下一个误差项的影响。如果存在模式(例如,一个误差总是跟随另一个相似大小的误差),我们会怀疑自相关性。
6. 外生性
假设: 误差项的期望值为零。
视觉解释: 这意味着平面应当被定位,使得误差在平均值上互相抵消。一些数据点会在平面之上,一些会在平面之下,但没有系统性偏差使它们都在平面之上或之下。
7. 误差的正态性(通常是假设检验的假设)
假设: 误差项呈正态分布。
视觉解释: 虽然正态性假设在数据和模型的三维图中无法直观呈现,但如果我们查看误差项的直方图,我们期望看到正态分布的熟悉钟形曲线。
线性回归在机器学习宇宙中如何不同于经典统计学中的普通最小二乘法线性回归?
在经典统计学中,普通最小二乘法(OLS)可以通过最大似然估计(MLE)的视角来接近。MLE 和 OLS 都旨在找到模型的最佳参数,但它们来自不同的理论,并使用不同的方法来实现这一目标。
最大似然估计 (MLE) 方法:
MLE 基于概率。它提出的问题是:“给定一组数据点,什么是最可能的模型参数,这些参数能够生成这些数据?”MLE 假设误差具有某种概率分布(通常是正态分布),然后找到最大化观察到实际数据的可能性的参数值。在几何视觉中,这类似于调整平面(colX)的角度和位置,使得看到实际数据点(y)的概率最高。似然不仅考虑点到平面(误差)的距离,还考虑误差分布的形状。
机器学习 (ML) 中目标函数的最小化:
另一方面,ML 方法通常将回归框架作为优化问题。目标是找到那些使某个目标函数(通常是平方误差总和(SSE))最小的参数。这是一种比 MLE 更直接的方法,因为它对误差的底层概率分布假设较少。它只是试图在平方意义上将数据点到预测平面的距离尽可能小,以更严厉地惩罚较大的误差。几何解释是你在倾斜和移动平面(colX),以最小化从实际点(y)到平面的垂直距离的平方和(虚线)。
两者比较:
尽管程序有所不同——一种是基于概率的方法,另一种是优化技术——但在 OLS 的情况下,它们往往会产生相同的结果。这是因为当误差服从正态分布时,线性模型系数的 MLE 会得出与最小化平方误差总和相同的方程。从视觉上看,两种方法实际上都是在尝试在变量 X1 和 X2 的空间中定位同一个平面,以便它最好地表示与 y 的关系。
主要区别在于解释和潜在的泛化。MLE 的框架允许在建模误差结构时有更多灵活性,并且可以扩展到误差不被假设为正态分布的模型中。ML 方法通常更直接,专注于平方差的总和,而不关注底层概率分布。
总之,虽然最大似然估计 (MLE) 和最小化方法可能会得到相同的最小二乘回归系数,但它们在概念上是不同的。MLE 是概率性的,根植于在给定模型下观察数据的可能性,而最小化方法是算法性的,专注于直接减少误差。几何视觉对两者来说是相同的,但平面位置的原理不同。
附加:当你在上述解释中引入正则化时会发生什么?
正则化是一种用于防止模型过拟合的技术,过拟合可能发生在模型过于复杂,开始捕捉数据中的噪声而不仅仅是数据的真实底层模式。正则化有不同的类型,但最常见的两种是:
-
套索回归 (L1 正则化): 这增加了一个等于系数绝对值的惩罚项。它可以将一些系数减少为零,有效地进行特征选择。
-
岭回归 (L2 正则化): 这增加了一个等于系数平方的惩罚项。所有系数都被相同的因子缩小,没有一个被归零。
让我们用将被子(代表我们的回归模型)铺在床上(代表我们的数据)的例子来说明。在没有正则化的最小二乘法中,我们试图将被子铺得尽可能多地接触床上的点(数据点),最小化被子与床面之间的距离(误差)。
现在,想象一下床非常不平坦,而我们的被子非常柔韧。如果没有正则化,我们可能会把被子贴得非常紧,以至于它适应了每一个小凸起和凹陷,甚至是由于有人没有铺平床单的小问题——这就是过拟合。
引入正则化:
-
使用套索回归 (L1): 就像是说:“我希望被子合适,但也希望它尽量平整,褶皱尽可能少。”每一个褶皱代表模型中的一个特征,而 L1 正则化尝试最小化褶皱的数量。最后,你会有一个合适的被子,但可能不会进入每一个缝隙,尤其是那些仅仅是噪声的缝隙。在几何视觉中,套索回归会试图让平面(colX)很好地贴合点,但可能会在不重要的变量方向上变得平坦(将系数缩小为零)。
-
使用岭回归 (L2): 这就像是想要被子贴合得很好,但同时也希望被子均匀展开,没有任何部分离床太远。因此,即使被子仍然紧贴床面,它也不会过度扭曲以适应细微的凸起。在几何视觉中,岭回归添加了一个惩罚项,约束了系数,使它们缩小到接近零但不完全为零。这保持了平面接近点,但防止它倾斜得太厉害以适应特定点,从而保持了一定的距离(偏差)以防止对噪声的过拟合。
带有正则化的视觉解释:
当将正则化添加到几何解释中时:
-
平面(colX)可能不会像之前那样靠近每个单独的数据点(y)。正则化故意引入了一些偏差。
-
平面将趋向于更加稳定,较少倾斜于任何单个异常值点,因为对于大系数的惩罚意味着模型不能过于敏感于任何单一的数据点或特征。
-
向量(Beta1X1 和 Beta2X2)的长度可能会更短,反映了每个变量对预测的影响被故意限制的事实。
从本质上讲,正则化在让模型的训练数据拟合能力稍微打折的同时,换取了改进的模型泛化能力,这意味着模型在未见过的数据上表现更好,而不仅仅是在训练数据上。就像选择一个稍微宽松的毯子尺寸,它在所有实际用途上都足够好,而不是一个完美贴合每一个轮廓但可能不切实际或过于特定于一个床的尺寸。
结论
总结来说,线性回归的几何解释弥合了经典统计学与机器学习之间的差距,提供了对这一基本技术的直观理解。虽然经典统计学通过普通最小二乘法(Ordinary Least Squares)来处理线性回归,而机器学习则通常使用最大似然估计(Maximum Likelihood Estimation)或目标函数最小化,但这两种方法最终都旨在以视觉上易于理解的方式最小化预测误差。
引入像 Lasso 和 Ridge 这样的正则化技术进一步丰富了这种解释,突出了模型准确性和泛化能力之间的平衡。这些方法防止了过拟合,确保模型对新的、未见过的数据保持稳健和有效。
总体而言,这种几何视角不仅揭示了线性回归的奥秘,还强调了在不断发展的数据分析和机器学习领域中基础概念的重要性。这是一个强有力的提醒,表明复杂的算法可以根植于简单而深刻的几何原理中。
Python 中的地理空间数据分析
原文:
towardsdatascience.com/geospatial-data-analysis-in-python-d8fa8dd23a6c
使用 OSMnx 和 Kepler.gl 开始进行地理数据分析
·发表于Towards Data Science ·6 分钟阅读·2023 年 5 月 3 日
--

照片由Aaron Burden拍摄,来源于Unsplash
介绍
地理空间数据无处不在,并用于各种不同的应用场景(例如,根据位置计算房地产风险、设计新建筑开发、规划货物运输以及寻找不同地点之间的可能路线)。
地理空间数据通常以两种可能的格式存储:栅格和矢量:
-
栅格数据将数据表示为像素矩阵(因此具有固定分辨率)。在这种表示中,每个像素可以分配不同的值,多个栅格叠加在一起可以进一步增强同一图像。例如,同一图像可以使用 3 个通道/波段(例如 RGB——红色、绿色、蓝色)或使用单一通道进行存储。
-
矢量可以用于抽象现实世界的几何形状,使用点、线、多边形等元素,并且通常可以与表示它们的对象的一些有用元数据一起存储(例如名称、地址、所有者等)。由于它们作为数学对象存储,因此可以在不损失分辨率的情况下放大矢量。
矢量数据通常存储在 SVG 和 Shapefiles 等文件格式中,而栅格数据则存储在 TIFF、JPG、PNG 等格式中。
在处理地理空间数据时,通常需要进行许多不同形式的操作/转换,一些例子包括:
-
从非表格/原始二进制格式转换为矢量/栅格。
-
将连续数据分桶为离散类别。
-
从数据中提取多边形/特征。
-
处理无数据和离群值。
-
在不同坐标系统中重投影。
-
生成较低分辨率的数据概览,以处理不同的缩放级别,并避免图像渲染过载。
如果你对图像数据在机器学习系统中的具体应用感兴趣,可以在我的上一篇文章中找到更多信息。
演示
作为本文的一部分,我们现在将通过实际演示来分析地理空间矢量数据,以识别特定位置并计算到达该位置的最短路径。本文中使用的所有代码(以及更多!)可在我的 GitHub和Kaggle 账户上找到。
首先,我们需要确保在环境中安装了所有必要的依赖。
pip install osmnx
pip install folium
pip install keplergl
在这个例子中,我们将探索瑞士苏黎世的 Altstadt 区。利用OSMnx,只需使用两行代码就可以检索并可视化我们需要的数据(图 1)。OSMnx实际上是为了以最简单的方式提取和使用OpenStreetMap数据而设计的。OpenStreetMap 是由志愿者社区维护的全球免费地理数据库。
import osmnx as ox
import networkx as nx
import matplotlib.pyplot as plt
import folium
from keplergl import KeplerGl
place_name = "Altstadt, Zurich, Switzerland"
graph = ox.graph_from_place(place_name)
ox.plot_graph_folium(graph)

图 1:待检查区域的图表(图片来源:作者)。
我们现在可以深入探讨获取数据的不同方法。首先,我们可以获得我们所表示区域的多边形作为探索的基础。一旦获取了数据,它将以Geo Pandas数据框的形式呈现,其中包含关于该区域的所有关键信息(图 2)。Geo Pandas 是一个开源库,专门设计用于扩展 Pandas 的功能,以处理地理空间数据。
为了创建任何可视化表示,geometry列用作参考点,每一行都表示在地图上创建对象所需的所有坐标(例如多边形、线、点、多边形)。在这个例子中,Well-known text (WKT)被用作表示我们的矢量几何对象的文本标记语言,但通常也可以使用其他格式,如GeoJSON。此外,GeoSeries 中的每个值都存储在Shapely Object中,以便尽可能容易地执行操作和转换。
area = ox.geocode_to_gdf(place_name)
area.head()

图 2:GeoPandas 数据集示例(图片来源:作者)。
此时,我们可以重复相同的程序,以检索我们希望在地图上绘制的不同兴趣点。在这种情况下,假设我们正在苏黎世的 Altstadt 度假,我们想要查看所有前往餐厅的选项。为了简化我们的研究,我们可以获取所有代表街道的节点和边缘,以及所有可用的建筑物和餐馆,以便我们在地图上进行定位。
buildings = ox.geometries_from_place(place_name, tags={'building':True})
restaurants = ox.geometries_from_place(place_name,
tags={"amenity": "restaurant"})
nodes, edges = ox.graph_to_gdfs(graph)
一旦我们加载了所有数据,我们可以独立绘制每个特征(图 3)。

图 3:区域、建筑物、餐馆、节点和边缘表示(作者提供的图像)。
使用下面的code,这可以最终在下方的单一图表中漂亮地总结出来(图 4)。
fig, ax = plt.subplots(figsize=(10, 14))
area.plot(ax=ax, facecolor='white')
edges.plot(ax=ax, linewidth=1, edgecolor='blue')
buildings.plot(ax=ax, facecolor='yellow', alpha=0.7)
restaurants.plot(ax=ax, color='red', alpha=0.9, markersize=12)
plt.tight_layout()

图 4:分析摘要(作者提供的图像)。
为了使我们的分析更具互动性,我们还可以使用额外的库,例如KeplerGL。KeplerGL是由 Uber 开发的开源地理空间工具箱,用于创建高性能的基于网页的地理应用程序。
使用 KeplerGL,我们可以轻松地将地图叠加在真实的全球地图上,并实时应用各种转换和过滤(图 5)。
K_map = KeplerGl()
K_map.add_data(data=restaurants, name='Restaurants')
K_map.add_data(data=buildings, name='Buildings')
K_map.add_data(data=edges, name='Edges')
K_map.add_data(data=area, name='Area')
K_map.save_to_html()

图 5:带 KaplerGL 的互动分析摘要(作者提供的图像)。
现在我们已经构建了地图,并且有了一个互动工具来检查这些数据,我们可以最终尝试将研究范围缩小到一个餐厅。在这种情况下,我们首先将重点限制在意大利餐馆,然后选择 Antica Roma 作为我们的选择地点。
现在,我们只需要指定我们的初始位置坐标和目的地,以开始寻找最佳的步行路径(图 6)。
it_resturants = restaurants.loc[restaurants.cuisine.str.contains('italian')
.fillna(False)].dropna(axis=1, how='all')
resturant_choice = it_resturants[it_resturants['name'] == 'Antica Roma']
orig = list(graph)[993]
dest = ox.nearest_nodes(graph,
resturant_choice['geometry'][0].xy[0][0],
resturant_choice['geometry'][0].xy[1][0])
nodes[nodes.index == orig]

图 6:起始位置表示(作者提供的图像)。
为了完成这项任务,我们可以使用Networkx shortest_path function 来自动运行Dijkstra 算法 并尝试优化我们的路径以最小化其总体长度(图 7)。
route = nx.shortest_path(graph, orig, dest, weight='length')
ox.plot_route_folium(graph, route, route_linewidth=6, node_size=0)

图 7:到达目的地的最短路径(作者提供的图像)。
最后,为了验证我们的研究,我们可以尝试在 Google Maps 上执行相同的查询,如下所示,结果非常接近!(图 8)。

图 8:Google Maps 到达目的地的最短路径(作者提供的图像)。
结论
总结一下,在这篇文章中,我们首先介绍了地理空间数据如何在企业中使用,它通常是如何存储和处理的,并通过一个实际示例来识别两个不同点之间的最短路径。当然,我们也可以使用基于 UI 的工具进行类似的分析,比如OpenStreetMap 应用或Google Maps,虽然学习这些基础知识仍然非常有价值,因为它们可以应用于许多其他形式的网络问题(例如旅行推销员问题)。
联系方式
如果你想了解我最新的文章和项目,可以在 Medium 上关注我并订阅我的邮件列表。以下是我的一些联系方式:
使用 GeoPandas 进行地理空间数据分析
原文:
towardsdatascience.com/geospatial-data-analysis-with-geopandas-876cb72721cb
学习如何使用 Python 的 GeoPandas 操作和可视化矢量数据
·发布于Towards Data Science ·6 分钟阅读·2023 年 5 月 6 日
--

图片由Artem Beliaikin拍摄,发布于Unsplash
这是关于地理空间数据分析系列的第三篇文章:
-
使用 QGIS 进行地理空间数据分析
-
OpenStreetMap 入门指南
-
使用 GeoPandas 进行地理空间数据分析(本文)
-
使用 OSMnx 进行地理空间数据分析
本文是使用 QGIS 进行地理空间数据分析的实用介绍和 OpenStreetMap 入门指南的延续。在之前的教程中,我提供了地理空间数据分析的概述,这是一门无处不在且可以应用于许多领域的子领域,如物流、运输和保险。
这个学科专注于分析一种特殊类型的数据,即地理空间数据,其特点是具有位置,由一对或多对坐标描述。例子包括餐馆、道路和国家之间的边界。为了展示连续的表面,比如卫星图像,地理表格已不再足够,你需要一个包含一个或多个通道的数组。
在这篇文章中,我将重点讲解最简单的情况,即地理表格,也称为矢量数据。对于这个任务,GeoPandas 是将用于处理和可视化这种类型地理空间数据的 Python 库。正如你可能猜到的,它是 Pandas 的扩展,一个流行的 Python 包,允许你轻松且快速地处理地理空间数据。让我们开始吧!
目录:
-
导入普查数据
-
向普查数据添加几何信息
-
使用 GeoPandas 创建地图
-
从几何信息中提取质心
-
创建更复杂的地图
导入普查数据
开始地理空间数据分析之旅的最佳方法是通过处理普查数据,这些数据提供了世界各国所有人和家庭的详细信息。
在本教程中,我们将使用一个提供英国汽车或货车数量的数据集,该数据集来自于英国数据服务。数据集的链接是这里。
我将从一个不包含地理信息的数据集开始:
数据集的每一行对应于一个特定的输出区域,这是英国普查提供的最低地理级别。有三个特征:地理编码、国家以及一个或多个家庭成员拥有的汽车或货车数量。
如果我们现在想要可视化地图,我们将无法实现,因为我们没有必要的地理信息。在展示 GeoPandas 的潜力之前,我们需要进一步的步骤。
向普查数据添加几何信息
为了可视化我们的普查数据,我们需要添加一个存储地理信息的列。添加地理信息的过程,例如为每个城市添加纬度和经度,称为地理编码。
在这种情况下,不只是一个坐标对,而是有不同的坐标对,它们连接并闭合,形成输出区域的边界。我们需要从这个链接导出 Shapefile。它提供了每个输出区域的边界。
一旦数据集被导入,我们可以使用它们的公共字段 geo_code 合并这两个表:
在评估了数据框的维度在左连接后没有变化后,我们需要检查新列中是否存在空值:
df.geometry.isnull().sum()
# 0
幸运的是,没有空值,我们可以使用 GeoDataFrame 类将数据框转换为地理数据框,并将几何列设置为我们地理数据框的几何体:
现在,地理信息和非地理信息被合并到一个独特的表中。所有的地理信息都包含在一个字段中,称为 geometry。就像在普通的数据框中一样,我们可以打印出这个地理数据框的信息:
从输出中,我们可以看到我们的地理数据框是geopandas.GeoDataFrame对象的一个实例,几何体使用几何类型进行编码。为了更好地理解,我们还可以显示第一行中几何列的类型:
type(gdf.geometry[0])
# shapely.geometry.polygon.Polygon
了解几何对象中有三种常见的类是很重要的:点、线和多边形。在我们的例子中,我们处理的是多边形,这很有意义,因为它们是输出区域的边界。然后,数据集已经准备好了,我们可以从现在开始构建漂亮的可视化图。
使用 GeoPandas 创建地图
现在,我们有了使用 GeoPandas 可视化地图的所有必要要素。由于 GeoPandas 的一个缺点是它处理大量数据时存在困难,而我们有超过 20 万行数据,因此我们将专注于北爱尔兰的人口普查数据:
gdf_ni = gdf.query(‘Country==”Northen Ireland”’)
要创建地图,只需在地理数据框上调用plot()方法:
我们还希望通过根据每个输出区域的频率进行着色,查看汽车/面包车在北爱尔兰的分布情况:
从这个图中,我们可以观察到大多数区域的车辆数量在 200 辆左右,除了标记为绿色的小区域。
从几何体中提取质心
假设我们想要更改几何形状,并将坐标放在输出区域的中心,而不是多边形。可以通过使用gdf.geomtry.centroid属性来计算每个输出区域的质心来实现这一点:
gdf_ni[‘centroid’] = gdf.geometry.centroid
gdf_ni.sample(3)

如果我们再次显示数据框的信息,我们可以注意到几何体和质心都被编码为几何类型。
更好地理解我们真正获得了什么的是在一个唯一的地图中可视化几何体和质心列。要绘制质心,需要使用set_geometry()方法来切换几何体。
创建更复杂的地图
有一些高级功能可以在地图中可视化更多细节,而无需创建其他信息列。之前我们显示了每个输出区域的汽车或面包车数量,但这比信息量更混乱。基于我们的数值列创建一个类别特征会更好。使用 GeoPandas,我们可以跳过那一阶段,直接进行绘图。通过指定参数scheme=’intervals’,我们能够根据等间隔创建汽车/面包车的类别。
地图没有发生太大变化,但你可以看到图例相比之前的版本更清晰。更好的可视化地图的方法是基于分位数构建的级别来着色:
现在,在地图上可以发现更多的变化,因为每个级别包含了更多分布的区域。值得注意的是,大多数区域属于最后两个级别,对应于车辆数量最多的情况。在第一次可视化中,200 辆车似乎数量不多,但实际上存在许多高频率的离群值,这扭曲了我们的解释。
此时,我们还希望有一个背景地图来更好地使我们的结果具有上下文。最流行的方法是使用 contextily 库,它可以获取背景地图。该库需要 Web Mercator 坐标参考系统(EPSG:3857)。因此,我们需要将数据转换为该坐标参考系统。绘制地图的代码保持不变,只需增加一行代码来添加来自 Contextily 库的基础地图:
太好了!现在,我们有了一张更专业、更详细的地图!
最后思考:
这是一个入门教程,用于开始用 Python 处理地理空间数据。GeoPandas 是一个专门处理矢量数据的 Python 库。它使用起来非常简单直观,因为它具有类似于 Pandas 的属性和方法,但一旦数据量增加,特别是在绘制数据时,它会变得非常缓慢。
除了这个缺点外,还有一个事实是它依赖于 Fiona 库来读取和写入矢量数据格式。如果 Fiona 不支持某些格式,即使是 GeoPandas 也无法支持它们。一个解决方案是结合使用 GeoPandas 来处理数据和 QGIS 来可视化地图。或者尝试其他 Python 库来可视化数据,如 Folium。你知道其他的替代方案吗?如果有其他想法,请在评论中提出。
代码可以在 这里 找到。希望你觉得这篇文章有用。祝你有美好的一天!
免责声明:数据集的许可为英国开放政府许可证(OGL)
有用的资源:
使用 OSMnx 进行地理空间数据分析
原文:
towardsdatascience.com/geospatial-data-analysis-with-osmnx-8a300d77b592
学习如何使用 Python 下载、分析和可视化 OpenStreetMap 数据
·发布于 Towards Data Science ·6 分钟阅读·2023 年 6 月 16 日
--

照片由 Denys Nevozhai 提供,来源于 Unsplash
这是关于地理空间数据分析系列的第四篇文章:
-
使用 QGIS 进行地理空间数据分析
-
OpenStreetMap 入门指南
-
使用 GeoPandas 进行地理空间数据分析
-
使用 OSMnx 进行地理空间数据分析(本文)
在之前的教程中,我涵盖了地理空间数据分析的各个方面。我首先展示了不使用任何代码的地理空间数据实际示例,以帮助你深入理解这些概念。地理空间数据分析是一个普遍存在的领域,涉及处理一种特殊类型的数据——地理空间数据。
这包括将位置添加到非地理数据中。文中充满了示例。你可以想到咖啡馆、医院、道路、河流、卫星图像等。即使你用 Google Maps 搜索一个地点,你也在与地理空间数据互动。
这次我将专注于从 OpenStreetMap 下载、可视化和分析数据。OpenStreetMap 是最大的免费且可编辑的地理数据库,来自世界各地的志愿者共同参与这个庞大的项目。这个教程得益于一个名为 OSMnx 的 Python 包。让我们开始吧!
目录:
-
OSMNx 简介
-
下载和可视化 OSM 数据
-
将图形转换为 GeoDataframe
-
提取兴趣点
-
寻找最短路径
OSMNx 简介
OSMnx 是一个用于下载、分析和可视化 OpenStreetMap 网络数据的库。它依赖于两个库,NetworkX和GeoPandas。特别地,它利用 NetworkX 库中的graph模块来检索网络数据。
此外,它允许我们与两个 OpenStreetMap API 进行交互:
-
Nomatism用于地理编码,即通过名称和地址查找位置。
-
Overpass API用于提取兴趣点,如高速公路、学校和公园。
下载和可视化 OSM 数据
我们可以直接使用 OSMNx,而不是从网站或 Geofabrik 手动下载数据。
首先,我们需要导入四个库,这些库将在教程中稍后使用:
import osmnx as ox
import folium
import contextily as cx
import matplotlib.pyplot as plt
除了 OSMnx 和 matplotlib,我们还将利用folium,它以创建交互式地图而闻名,以及contextily来添加背景地图。这一点对获得现实地图非常重要。
像之前一样,我们将读取并可视化博洛尼亚的 OSM 街道网络数据,博洛尼亚是意大利最大的城市之一。
PLACE_NAME = 'Bologna, Italy'
G = ox.graph_from_place(PLACE_NAME, network_type='drive')
ox.plot_graph(G)

博洛尼亚的黑白可视化
从黑白可视化中,我们可以观察到代表节点的点和描绘线路的线条。与 OpenStreetMap 网站相比,它可能显得非常静态和基础。Folium 通过其强大的可解释地图来拯救我们:
ox.plot_graph_folium(G)

使用 Folium 获得的博洛尼亚地图
这要好得多,你不觉得吗?亮丽的颜色和与地图交互的可能性是使用 Google Maps 去未知地方时至关重要的特点。
如果你更仔细地查看 OpenStreetMap 网站,你会发现默认情况下是标准图层。除了标准图层,还有其他图层,比如 Cycle Map 和 Transport Map。我们可以根据目的利用不同的图层,这一点非常令人惊叹。

如果我们对自行车情有独钟,我们会更关注 Cycle Map。只需一行代码就能实现:
G = ox.graph_from_place(PLACE_NAME, network_type='bike')
在接下来的部分,我们将考虑标准图形。
将图形转换为 GeoDataframe
处理图形不像处理 Dataframes 和 GeoDataframes 那样直观。因此,我们可能需要将图形转换为 GeoDataframe:
area,edges = ox.graph_to_gdfs(G)
area.head()

edges.head()

你会发现我们得到了两个 GeoDataframe,一个用于节点,一个用于边。查看几何形状就会很清楚。区域 geodataframe 只有一对坐标,即纬度和经度,而包含边的 geodataframe 中有两对坐标。
提取兴趣点
在进行数据科学项目时,我们尝试通过在互联网上搜索开放数据来为数据集添加信息。从 OSM 数据中,可以提取 兴趣点(POI),这些是根据我们的分析目的我们可能会觉得有趣的地方。示例包括餐馆、教堂、博物馆和公园。
例如,我们想要分析博洛尼亚的交通情况,以优化和减少运输成本。在这种情况下,了解高速公路、加油站、停车场以及其他可能成为瓶颈的地方将非常有用。
让我们来看看城市中的所有加油站。这可以通过将 燃料 作为 amenity 键的值来实现。
fuel_stations = ox.geometries_from_place(
PLACE_NAME,
{"amenity": "fuel"},
)
fuel_stations.head()

由于我们已经提取了所有加油站,通过可视化地图来理解它们的位置会更有用。此外,我们可以添加基础地图来更好地 contextualize 我们的结果。
area_crs = area.to_crs('3857')
edges_crs = edges.to_crs('3857')
fuel_stations_crs = fuel_stations.to_crs('3857')
fig, ax = plt.subplots(figsize=(10, 14))
area_crs.plot(ax=ax, facecolor='white')
edges_crs.plot(ax=ax, linewidth=1, edgecolor='blue')
fuel_stations_crs.plot(ax=ax, color='red', alpha=0.9, markersize=12)
plt.tight_layout()
cx.add_basemap(ax,crs=area_crs.crs.to_string())

太棒了!我们可以注意到大多数加油站集中在城市边缘。此外,我们可以区分不同组的服务站,这些在测量中心外交通时需要考虑。
找到最短路线
OSMnx 库的另一个有用功能是能够计算两个点之间的最短路径。
origin = (
ox.geocode_to_gdf("Parco della Montagnola, Bologna, Italy")
.to_crs(edges.crs)
.at[0, "geometry"]
.centroid
)
destination = (
ox.geocode_to_gdf("Esso, Bologna, Italy")
.to_crs(edges.crs)
.at[0, "geometry"]
.centroid
)
origin_node_id = ox.nearest_nodes(G, origin.x, origin.y)
destination_node_id = ox.nearest_nodes(G, destination.x, destination.y)
这是通过 shortest_path() 方法实现的,该方法默认使用 Dijkstra 算法 来计算源节点和目标节点之间的路径。
route = ox.shortest_path(G, origin_node_id, destination_node_id)
route
#[400881920,
# 250763178,
# 250763179,
# 250763533, ...
# 1694666466]
我们还可以尝试在同一地图上可视化图形和最短路径:
ox.plot_route_folium(G, route, route_linewidth=6, node_size=0)

看吧!这就像我们使用了 Google Maps 来寻找路线一样,不过我们利用了 OSMnx 库的功能来进行查找。
最后的想法:
这是一份指南,让你了解如何使用 Python 操作 OSM 数据。我发现 OSMnx 是处理 OpenStreetMap 数据的最完整的 Python 库。当然,它更适合探索较小的地方,比如城市。如果数据集较大,最好使用更专业的软件,比如 QGIS 来进行可视化。如果你尝试过其他库来处理 OSM 数据,请在评论中分享。如果你想查看代码,可以点击 这里。感谢阅读!祝你有美好的一天!
有用的资源:
你喜欢我的文章吗?成为会员 并每天获得无限的数据科学文章访问权限!这是一种间接支持我的方式,对你没有额外费用。如果你已经是会员, 订阅 以便每当我发布新的数据科学和 Python 指南时接收邮件!
地理空间数据工程:空间索引
优化查询、提高运行时间和地理空间数据科学应用
·
关注 在 Towards Data Science ·6 分钟阅读·2023 年 8 月 31 日
--
图片由 Tamas Tuzes-Katai 提供,来源于 Unsplash
介绍:空间索引为何有用?
在进行地理空间数据科学工作时,优化你编写的代码是非常重要的。如何让拥有数亿行的数据库集合或联接更快?这就是空间索引等概念发挥作用的地方。在这篇文章中,我将讨论空间索引的实现方式,它的好处和局限性,并看看 Uber 的开源 H3 索引库在一些有趣的空间数据科学应用中的表现。我们开始吧!
🗺 什么是空间索引?
常规索引就像你在书籍末尾找到的内容:一个单词的列表以及这些单词在文本中出现的位置。这帮助你快速查找在特定文本中你感兴趣的单词的任何参考。如果没有这个方便的工具,你将需要手动翻阅书中的每一页,寻找你想阅读的那一个提及。
在现代数据库中,查询和搜索的问题也非常相关。索引通常比过滤更快地查找数据,你可以基于感兴趣的列创建索引。对于地理空间数据,工程师们通常需要考虑诸如“交集”或“附近”的操作。我们如何制作一个空间索引,使这些操作尽可能快速?首先,让我们看看一些地理空间数据:

两个不相交的特征(图片来源:作者)
假设我们想运行一个查询来确定这两个形状是否相交。根据构造,空间数据库创建的索引是由包含几何图形的边界框组成的:

制作一个大的边界框(图片来源:作者)
为了回答这两个特征是否相交,数据库将比较两个边界框是否有任何公共区域。如你所见,这可能会迅速导致假阳性。为了解决这个问题,像 PostGIS 这样的空间数据库通常会将这些大的边界框划分成越来越小的部分:

更小的尺寸:制作子边界框(图片来源:作者)
这些分区存储在 R 树中。R 树是一种层次数据结构:它跟踪大的“父”边界框、它的子节点、子节点的子节点,等等。每个父节点的边界框包含它子节点的边界框:

R 树可视化(图片来源:作者)
操作“交集”是从这种结构中受益的关键操作之一。在查询交集时,数据库会沿着这棵树向下查询,问“当前的边界框是否与感兴趣的特征相交?”。如果是,它会查看该边界框的子节点,并提出相同的问题。通过这种方式,它能够快速遍历树,跳过没有交集的分支,从而提高查询性能。最后,它会返回所需的交集几何图形。
🧰 实践中:尝试使用 GeoPandas 的空间索引
现在让我们具体看看使用常规行级过程与空间索引的不同。我将使用代表纽约市人口普查区和市设施的两个数据集(均通过开放数据许可提供,可在这里和这里获得)。首先,让我们在 GeoPandas 中尝试对一个人口普查区几何图形进行“交集”操作。在 GeoPandas 中,“交集”是一个逐行函数,检查每一行的列是否与我们的几何图形相交。
GeoPandas 还提供了一种使用 R 树的空间索引操作,允许我们执行交集操作。以下是对这两种方法在 100 次交集操作中的运行时比较(注意:由于默认的交集函数较慢,我只选择了原始数据集中的大约 100 个几何图形):

💨 空间索引快了多少?(图片由作者提供)
如你所见,空间索引方法相比普通的交集方法提供了显著的性能提升。实际上,这里是每种方法运行时间的 95% 置信区间:


太好了!那么,为什么我们有时不想使用空间索引呢?是否有些情况下它没有任何好处?确实有。这些限制中的一些是由于空间索引存储数据叶子的方式。结果表明,原始数据的分布方式会影响边界框在 R 树中的位置。具体来说,如果大量数据集中在同一地理空间,它们往往会共享相同的父节点,从而被分组在同一分支中。这可能导致树的不均衡,从而在查询时无法提供太多优化。
💻 其他空间索引是什么样的?
其他公司也适应了自己的空间索引。Uber 使用 H3,这是一个将世界划分为等面积六边形的分层索引系统。六边形在建模人们在城市中的移动或解决如计算半径等问题时具有许多优点。地理空间数据被划分到这些六边形中,作为公司的主要分析单位。网格是通过将 122 个六边形单元覆盖到二十面体地图投影上来构建的,支持包括聚合、连接和机器学习应用在内的广泛功能。
这个系统以及它的许多功能是开源的,可以在 GitHub 上进行分析。H3 API 的一个功能是将经纬度点转换为表示唯一六边形的字符串,根据指定的分辨率。我们来对整个设施数据库进行这个操作,并将六边形字符串转换为多边形:
在这些空间数据分析项目中,一个常见的问题是六边形中的项目有多少被某个列,比如“机构”所分类。幸运的是,现在我们已经将数据划分到 H3 六边形中,这很容易计算和可视化:

哪些机构有最多的设施?(图片来源于作者)
在这种情况下,你可以看到 DCAS(城市行政服务部)和 PARKS(公园与娱乐部)是每个六边形中设施最多的两个机构。这很有道理,因为这两个机构通常会有更多的实体设施(比如行政建筑或像公园这样的休闲区域)。
结论
如你所见,空间索引是地理空间数据科学和分析中非常有用的优化工具。在简单的交集查询中,使用空间索引显著提高了查询性能,相比于标准的 GeoPandas 交集函数。这种索引的实现和它的影响有许多细微之处,比如拥有巨大的数据集群分支。如我们所见,各公司已经开发了自己的解决方案:一个例子是 Uber 的 H3 开源索引,它允许我们回答各种空间分析问题。虽然我演示了基于机构的设施计数操作,但 H3 为其他更复杂的机器学习应用提供了基准。
如果你喜欢这类内容,但想要更广泛地了解城市规划技术,我也写了一份名为“The Zoned Out Chronicles”的通讯。我鼓励你去看看!
感谢阅读!
地理空间数据科学:点模式分析
原文:
towardsdatascience.com/geospatial-data-science-points-pattern-analysis-a61e04a6ddb8
在 Python 中执行地理空间点模式分析的快速教程。
·发表于 Towards Data Science ·阅读时间 8 分钟·2023 年 9 月 25 日
--

图片由 Bernard Hermant 在 Unsplash 提供
介绍
地理空间数据科学是数据领域的一个子领域,处理考虑事件发生在空间中的位置的数据点分析。
假设我们拥有一家销售智能手机的零售连锁店。我们的连锁店有几个配送中心,我们即将开设几家新店。我们可以在哪里开设这些新店?
这样的洞察可能来自地理空间分析,这种分析会向我们展示销售的集中地点,如果销售在某些地方更高或更低,以及其他洞察。
当我们想确保查看的是一个地理上集中的数据集时,点模式分析就会进入这个游戏。就像我们数据科学家的工作一样,点模式涉及到创建一个假设并通过应用于数据的统计方法来消除许多不确定性,以确认这个假设。在这种情况下,也不例外。这里有几个统计测试需要完成,本文将展示这些测试。
顺便提一下,我们最近在我的博客中研究了地理空间数据科学。如果你对这个主题了解不多,这里有两篇不错的读物可以在你深入阅读之前参考。
一个包含 Python 代码的实用数据分析帖子。
[towardsdatascience.com ## 使用 Python 分析地理空间数据(第二部分 — 假设检验)
学习阿什维尔 AirBnb 房源的地理空间假设检验。
[towardsdatascience.com
编码
包
让我们从这次练习中使用的包开始。如果你的环境中没有安装它们,别忘了使用pip install或conda install(对于 Anaconda 用户),然后跟上包名。
import pandas as pd
import numpy as np
import geopandas as gpd
import seaborn as sns
import matplotlib.pyplot as plt
import contextily
# Spatial Stats
from pointpats import distance_statistics, QStatistic, random, PointPattern
数据集
使用的数据集再次是AirBnb在美国北卡罗来纳州阿什维尔市的房源数据。数据可以从网站 insideairbnb.com/ 的一个独立项目中获取,任何人都可以前往下载数据集进行分析。这些数据在 创意共享署名 4.0 国际许可协议 下开放。
我已经下载了文件 listings.csv.gz。
要将数据加载到 Python 会话中,这里是代码。第一个代码片段是来自 Pandas 的简单read_csv(),我们预先确定了希望从原始数据中提取哪些列。然后,我们使用gpd.GeoDataFrame将数据集转换为Geopandas对象类型,指定用作 X 轴和 Y 轴的列,以及地理坐标系统(crs — 使用 4326,它与 GPS 系统相同,是最常见的参考系统之一)。
# Import the file to this exercise
# Open listings file
listings = pd.read_csv('/content/listings.csv',
usecols=['id', 'property_type', 'neighbourhood_cleansed',
'bedrooms', 'beds', 'bathrooms_text', 'price',
'latitude','longitude'])
# Convert the file to GeoPandas
points_gpd = gpd.GeoDataFrame(listings,
geometry= gpd.points_from_xy(
x=listings.longitude,
y=listings.latitude),
crs= "EPSG:4326")
基本上,将数据转换为 Geopandas 就是创建这个列geometry和对象类型。

Geopandas 数据集。图片由作者提供。
很好。完成这些后,我们来快速看看地图。代码很简单,创建了一个图形fig和两个坐标轴ax,因为会有两个图:一个是底图,另一个是点图。
#Quick check gpd dataframe
fig, ax = plt.subplots(figsize=(8,8))
# zorder=1 is the plot below
asheville.plot(ax=ax, color=None, zorder=1)
# zorder=2 is the top layer
points_gpd.plot(ax=ax, zorder=2, color='black', markersize=8)
这段代码生成了下一个图表。

阿什维尔,北卡罗来纳州的 Airbnb 房源列表。图片由作者提供。
很酷。地图看起来不错,但没有提供太多信息。现在让我们增强分析。
点模式
当考虑点模式分析时,我们可以做的第一个分析是检查这些数据点在地理上的集中程度。
这里,seaborn库可以提供帮助。方法jointplot将散点图与边缘的直方图结合起来。这是分析中的一个绝妙补充,因为它通过查看条形图较高的地方,给我们提供了点的集中情况和位置的见解。
要创建它,我们可以传递来自geometry列的 x 和 y,加上数据集(data)、点的大小(s)、color和图形的height。下一个片段是将基图添加到此联合图中,可以使用contextily,将联合图变量传递给该方法。
# Check concentration of the points
plot2 = sns.jointplot(
x= points_gpd.geometry.x,
y= points_gpd.geometry.y,
data= points_gpd,
s=5, height=7, color='k')
# Add a basemap to the jointplot
contextily.add_basemap( plot2.ax_joint,
crs="EPSG:4326",
source= contextily.providers.Stamen.TonerLite)
结果是我们看到了这个漂亮的地图。

在北卡罗来纳州阿什维尔的基础地图上的联合图。图片由作者提供。
我喜欢这个图。通过它,我们可以快速获得一些有价值的见解。用红色标记的市中心区域(及其周边)确实是列表集中所在的地方。然后我们还可以注意到,随着离该区域越来越远,列表数量减少并变得更加稀疏。
这完全有意义。我们来想一想:Airbnb 是一个让人们租赁房屋或卧室的平台。这些租赁房产通常位于住宅区,因为它们的主要用途是作为居住场所,而非商业场所。而房屋社区的地点通常位于城市基础设施周围的区域,比如商场、超市、药店、银行等。由于山区偏远地区的房屋建设更具挑战,因此预计那里也会有较少的位置点。
统计检验
现在我们已经绘制了联合图并获得了一些有用的见解,但我们仍然需要测试点的分布模式,以确定它们是否在统计上存在聚类。我是说,这些点也可能只是偶然聚集在一起的。谁知道呢。
所以,为了测试并确保你在处理的是一个模式,有两个很好的检验:
-
Ripley 的 G: 这个检验会检查一个点到其最近邻的距离的累积分布。因此,该检验测量从给定房屋到邻居 1、2、3、……、n 的距离,并将这些距离的分布与模拟的随机点分布进行比较。如果我们观察到观察到的数据在特定空间中的行为与模拟不同,我们可以得出结论:数据有一定模式,因此是聚类的。
-
Ripley 的 K: 这个检验进行类似的测试,将观察到的数据与随机模拟分布进行比较。与 G 检验的不同之处在于,K 检验考虑了数据中的所有距离,而不仅仅是最近的邻居。
Ripley 的 G 检验距离最近邻的分布。Ripley 的 K 检验整个数据集的距离分布。
Ripley 的 G
现在我们来学习如何执行这些检验。首先是 G 检验。这个检验写起来相对简单,但运行时间可能较长,取决于数据集的大小。在这里,检验获取了 40 个邻居的距离。运行大约花了 6 分钟。
# Coding Ripley's G (6 mins to run)
ripley_g = distance_statistics.g_test(points_gpd[['longitude', 'latitude']].values,
support=40,
keep_simulations= True)
要绘制结果,代码片段如下。我们为每次模拟的中位数绘制一条黑线,为每个数据点绘制一条红线与统计数据。
# Plot G test
plt.figure(figsize=(20,7))
# Simulated Data line plot
plt.plot(ripley_g.support,
np.median(ripley_g.simulations, axis=0),
color='k', label= 'Randomly Simulated Data')
# Ripley Stat plot for Observed data
plt.plot(ripley_g.support,
ripley_g.statistic, marker='o',
color='red', label= 'Observed Data')
# Plot setup
plt.legend(loc=4)
plt.xlabel('Distance')
plt.xticks( np.arange(0.0, 0.023, 0.001) )
plt.ylabel('Ripley G function statistic')
plt.title('Rypleys G Test')
plt.show()
结果是,代码显示了下一个图形。

Ripley 的 G 检验。图片由作者提供。
我们可以看到,对于距离 0 和 0.003,观察到的数据增长速度快于模拟数据,确认了数据集中存在显著的空间模式。
Ripley 的 K
第二个检验是 K 检验。这个检验使用了完全空间随机性的原假设和数据中存在空间模式的备择假设。
运行 K 检验和其他检验一样简单。不过,该检验的计算开销更大。在 Google Colab 上运行一个会话花费了超过 30 分钟。
# Coding Ripley's K
ripley_k = distance_statistics.k_test(points_gpd[['longitude', 'latitude']].values,
keep_simulations= True)
# Plot K test
# Simulated Data line plot
plt.plot(ripley_k.support,
ripley_k.simulations.T,
color='k', alpha=.1)
# Ripley Stat plot for Observed data
plt.plot(ripley_k.support,
ripley_k.statistic, marker='x',
color='orange')
# p<0.05 = alternative hypothesis: spatial pattern
plt.scatter(ripley_k.support,
ripley_k.statistic,
cmap='viridis', c=ripley_k.pvalue < .05,
zorder=4)
# Plot setup
plt.xlabel('Distance')
plt.ylabel('Ripley K function')
plt.title('Rypleys K Function Plot')
plt.show()
上面的代码将给我们下一个图表。

Ripley 的 K 检验。图片由作者提供。
再次强调,观察到的数据远高于模拟数据,确认了我们的空间模式。
在你离开之前
在这篇文章中,我们学习了如何为地理空间点模式确认创建一些统计检验。
一旦我们将一些数据点绘制在地图上,它们可能仅仅是偶然地被聚集在一起,而没有明确的模式。但确认地理空间模式的好方法是运行Ripley 的 G和Ripley 的 K检验。
这些检验可以帮助确认在例如北卡罗来纳州阿什维尔市的高价和低价簇中是否存在模式。因此,如果你要出租物业,你将知道在该市每个街区的最佳竞争价格。
现在,你可以在我的 GitHub 仓库中下载代码并应用到你的数据中。
## Studying/Python/Geospatial/Points_Pattern_Python.ipynb at master · gurezende/Studying
这是一个包含我对新软件包的测试和研究的仓库 - Studying/Python/Geospatial/Points_Pattern_Python.ipynb…
如果你喜欢我的内容,不要忘记关注我或在LinkedIn上找到我。
在 Medium 上阅读 Gustavo Santos 的文章。他是一名数据科学家。我从数据中提取见解,以帮助个人和公司…
参考文献
JORDAN, David S. [2023]. Applied Geospatial Data Science with Python. 1 ed. Pactk Publishing.
Ripley 函数是表征点模式空间结构的经典工具。它被广泛应用于…
www.hindawi.com](https://www.hindawi.com/journals/isrn/2013/753475/?source=post_page-----a61e04a6ddb8--------------------------------) [## pointpats/notebooks/distance_statistics-numpy-oriented.ipynb at main · pysal/pointpats
PySAL 中的平面点模式分析。通过在 GitHub 上创建帐户来贡献于 pysal/pointpats 的开发。
地理空间索引 102
一个实际示例,展示如何应用地理空间索引
·
关注 发表在 Towards Data Science · 7 分钟阅读 · 2023 年 4 月 11 日
--
介绍
地理空间索引是一种索引技术,它提供了一种优雅的方式来管理基于位置的数据。它使得地理空间数据可以高效地搜索和检索,从而系统能够为用户提供最佳体验。本文将通过将地理空间索引应用于实际数据,并展示这样做所带来的性能提升,来演示其实际操作。让我们开始吧。(注意:如果你从未听说过地理空间索引或希望了解更多信息,可以查看 这篇文章)
数据
本文使用的数据是芝加哥犯罪数据,这是Google Cloud 公共数据集计划的一部分。任何拥有 Google Cloud Platform 账户的人都可以免费访问此数据集。数据集包含约 800 万行数据(总量为 1.52 GB),记录了自 2001 年以来发生在芝加哥的犯罪事件,每条记录都有指示事件位置的地理数据。
平台
我们不仅会使用 Google Cloud 的数据,还会使用 Google Big Query 作为数据处理平台。Big Query 提供了每个执行查询的作业执行细节。这包括使用的数据量和处理的行数,这将非常有助于展示优化后的性能提升。
实现
我们将通过优化基于位置的查询来展示地理空间索引的强大功能。在这个例子中,我们将使用Geohash作为索引,因为它的简洁性和 Google BigQuery 的原生支持。
我们将检索发生在距离芝加哥联合车站2 公里范围内的所有犯罪记录。在优化之前,让我们看看在原始数据集上运行此查询时的性能表现:
-- Chicago Union Station Coordinates = (-87.6402895591744 41.87887332682509)
SELECT
*
FROM
`bigquery-public-data.chicago_crime.crime`
WHERE
ST_DISTANCE(ST_GEOGPOINT(longitude, latitude), ST_GEOGFROMTEXT("POINT(-87.6402895591744 41.87887332682509)")) <= 2000
以下是作业信息和执行细节的样子:

作业信息(作者提供的图片)

执行细节(作者提供的图片)
从处理的字节数和读取的记录数中,你可以看到查询扫描了整个表并处理了每一行,以获取最终结果。这意味着数据越多,查询所需时间越长,处理成本也越高。这可以更高效吗?当然可以,这就是地理空间索引发挥作用的地方。
上述查询的问题在于,尽管许多记录距离关注点(芝加哥联合车站)较远,但仍然必须处理这些记录。如果我们能排除这些记录,那么查询将更高效。
Geohash 可以是解决这个问题的方案。除了将坐标编码为文本外,geohash 的另一个优点是哈希还包含地理空间属性。哈希之间的相似性可以推测它们所代表位置的地理相似性。例如,wxcgh 和 wxcgd 代表的两个区域相近,因为这两个哈希非常相似,而 accgh 和 dydgh 之间则相距较远,因为这两个哈希非常不同。
我们可以利用这一属性与 聚簇表 进行配合,通过预先计算每一行的 geohash。然后,我们计算芝加哥联合车站的 geohash。这样,我们可以在预先筛选掉所有与芝加哥联合车站的 geohash 不够 接近 的记录。
实现方法如下:
- 创建一个新表,并添加一个新列来存储坐标的 geohash。
CREATE TABLE `<project_id>.<dataset>.crime_with_geohash_lv5` AS (
SELECT *, ST_GEOHASH(ST_GEOGPOINT(longitude, latitude), 5) as geohash
FROM `bigquery-public-data.chicago_crime.crime`
)
2. 使用 geohash 列作为聚簇键创建聚簇表
CREATE TABLE `<project_id>.<dataset>.crime_with_geohash_lv5_clustered`
CLUSTER BY geohash
AS (
SELECT *
FROM `<project_id>.<dataset>.crime_with_geohash_lv5`
)
通过使用 geohash 作为聚簇键,我们创建了一个表,其中共享相同哈希的行被物理地存储在一起。如果你考虑一下,实际发生的情况是数据集按地理位置进行分区,因为地理位置越接近的行更有可能具有相同的哈希。
3. 计算芝加哥联合车站的 geohash。
在这篇文章中,我们使用了 这个网站,但有很多不同编程语言的库可以让你以编程方式实现这一功能。

芝加哥联合车站的 geohash(作者提供的图像)
4. 将 geohash 添加到查询条件中。
SELECT
*
FROM
`<project_id>.<dataset>.crime_with_geohash_lv5_clustered`
WHERE
geohash = "dp3wj" AND
ST_DISTANCE(ST_GEOGPOINT(longitude, latitude), ST_GEOGFROMTEXT("POINT(-87.6402895591744 41.87887332682509)")) <= 2000
这次查询应该只扫描位于 dp3wj 的记录,因为 geohash 是表的聚簇键。这应该能节省很多处理时间。让我们看看会发生什么。

创建聚簇表后的作业信息(作者提供的图像)

创建聚簇表后的执行细节(作者提供的图像)
从作业信息和执行细节中,你可以看到处理的字节数和扫描的记录数显著减少(从 1.5 GB 减少到 55 MB,7M 减少到 260k)。通过引入 geohash 列并将其用作聚簇键,我们可以仅通过查看一列就提前排除所有明显不满足查询的记录。
但是,我们还没有完成。仔细查看输出的行数,你会发现它只有 100k 条记录,而正确的结果应该有 380k 条。我们得到的结果仍然不正确。
5. 计算邻近区域并将它们添加到查询中。
在这个例子中,所有的邻近哈希是 dp3wk、dp3wm、dp3wq、dp3wh、dp3wn、dp3wu、dp3wv 和 dp3wy。我们使用在线的 geohash 探索器 来查看这些,但这也可以完全通过代码实现。

dp3wj 的邻居(作者提供的图像)
为什么我们需要在查询中添加邻域区域?因为地理哈希只是位置的一种近似。虽然我们知道芝加哥联合车站在dp3wj区域内,但我们仍然不知道它在区域中的具体位置。是在顶部、底部、左侧还是右侧?我们无从得知。如果它在顶部,那么在dp3wm中的一些数据可能比 2 公里更接近。如果它在右侧,则dp3wn区域中的一些数据可能距离更近。依此类推。这就是为什么所有邻域哈希必须包含在查询中以获得正确结果的原因。
请注意,地理哈希级别 5 的精度为 5 公里。因此,除上图中的区域外,所有其他区域距离芝加哥联合车站都太远。这是另一个需要做出的重要设计选择,因为它有巨大的影响。如果精度过粗,我们得到的收益很少。另一方面,使用过细的精度级别会使查询变得复杂。
这是最终查询的样子:
SELECT
*
FROM
`<project_id>.<dataset>.crime_with_geohash_lv5_clustered`
WHERE
(
geohash = "dp3wk" OR
geohash = "dp3wm" OR
geohash = "dp3wq" OR
geohash = "dp3wh" OR
geohash = "dp3wj" OR
geohash = "dp3wn" OR
geohash = "dp3tu" OR
geohash = "dp3tv" OR
geohash = "dp3ty"
) AND
ST_DISTANCE(ST_GEOGPOINT(longitude, latitude), ST_GEOGFROMTEXT("POINT(-87.6402895591744 41.87887332682509)")) <= 2000
这就是执行查询时发生的情况:

添加邻域哈希后的工作信息(图片由作者提供)

添加邻域哈希后的执行细节(图片由作者提供)
现在结果是正确的,查询处理了 527 MB 的数据,总共扫描了 250 万条记录。与原始查询相比,使用地理哈希和聚簇表节省了大约 3 倍的处理资源。然而,任何东西都不是免费的。应用地理哈希增加了数据预处理和检索的复杂性,例如需要提前选择的精度级别和额外的 SQL 查询逻辑。
结论
在本文中,我们已经看到地理空间索引如何帮助改善地理空间数据的处理。然而,它有一定的成本,需要提前仔细考虑。归根结底,这不是免费的午餐。要使其正常工作,需要对算法和系统要求有良好的理解。
最初发布于 https://thanakornp.com。
地理空间索引和评分:释放基于位置的数据分析的力量
使用 Python 和 H3 进行地理空间索引的实用指南
·发表于Towards Data Science ·阅读时长 7 分钟·2023 年 1 月 25 日
--

图片由Antoine Merour提供,来源于Unsplash
在本文中,我们将讨论地理空间索引和评分,以及如何使其对开发人员变得简单。地理空间索引是创建一个与地理位置(如纬度和经度)相关的数据集的索引的过程。评分则是根据特定标准对数据进行排名或排序的过程。地理空间索引和评分结合在一起,可以创建强大且高效的应用程序,能够快速检索和显示地理数据。
地理空间索引和评分可能复杂且耗时,但借助合适的工具和技术,可以使其变得简单。本文将探讨如何使用 Uber 的 H3 库进行索引和评分任务。
为了更好地理解,让我们定义一下假设的问题陈述。
作为零售公司的一名数据科学家,你被分配了一个重要的项目。公司需要组织和了解其运营的不同区域。
这个项目的最终目标是将区域划分为不同的部分,并识别每个区域中的关键热点。这些信息将帮助公司了解哪些区域对他们的业务最为重要,以及他们应集中精力的地方。
但你的工作并没有到此为止。公司还需要你为每个网点分配一个“重要性评分”。这个评分将基于之前提到的热点机制。那些位于潜在热点附近的网点将获得更高的重要性评分。
好的,让我们开始构建这个解决方案。
需要遵循的步骤
– 了解你的数据(Understand the data)
– 将区域拆分成小六边形
– 为区域内的每个六边形推导一个分数
– 定义出口重要性分数
1️⃣ 了解你的数据
根据问题陈述,我们有两个数据集。
-
兴趣点数据集 — 用于推导重要性分数
-
出口位置数据集
注意:提供的数据是随机生成的,不反映实际情况。你可以从 这里. 访问这些数据。
import pandas as pd
pois = pd.read_csv("pois.csv")
outlets = pd.read_csv("outlets.csv")


让我们绘制兴趣点类别分布。

此外,我们还需要该区域的地理信息。我已经通过geojson.io网站创建了区域的 JSON 文件。
{'type': 'FeatureCollection',
'features': [{'type': 'Feature',
'properties': {},
'geometry': {'coordinates': [[[80.19371259270753, 6.307037132469091],
[80.19371259270753, 6.072771744744614],
[80.4434199500783, 6.072771744744614],
[80.4434199500783, 6.307037132469091],
[80.19371259270753, 6.307037132469091]]],
'type': 'Polygon'}}]}
让我们把这些整合在一起。

目前我们所拥有的 | 作者提供的图片
在上图中,你可以看到两个图层。
-
矩形 geojson 图层 — 代表我们正在分析的区域
-
兴趣点图层 — 提供该区域的兴趣点数据集
2️⃣ 将区域拆分成小六边形
为了在特定区域内确定热点,有必要将区域划分为较小的部分。一个方法是使用六边形。六边形在这个背景下特别有用,因为它们具有一些优点,如能够均匀地划分区域和准确表示地理形状。如果你对为何使用六边形或它们提供的好处有进一步问题,可以参考 H3 的官方文档获取更多信息。
简而言之,通过选择正确的分辨率,可以通过一个六边形单元表示多个位置。

将多个点映射到一个六边形 | 作者提供的图片
[## H3 | H3
六边形层级地理空间索引系统 H3 提供了一个易于使用的 API,用于将坐标索引到六边形网格中……
h3geo.org.](https://h3geo.org/?source=post_page-----b77a7599b4c--------------------------------)
我们可以使用 H3 的*polyfill*函数将给定区域拆分成六边形。但在此之前,我们必须将区域 geojson 文件转换为 shapely 多边形格式。
from h3 import h3
from shapely.geometry import Polygon,mapping
def dict_to_shapely(d):
coords = d['features'][0]['geometry']['coordinates'][0]
return Polygon(coords)
def polygon_to_h3(polygon,resolution):
polygon = mapping(polygon)
hexas = h3.polyfill(polygon,res=resolution,geo_json_conformant=True)
return list(set(hexas))
#read geojson file
with open("sample-bbx.geojson",'r') as f:
polygon_dict = json.loads(f.read())
shapely_polygon = dict_to_shapely(polygon_dict)
f.close()
h3_idx = polygon_to_h3(shapely_polygon,resolution=8)
h3_idx = pd.DataFrame(pd.Series(h3_idx,name='h3'))
在上述代码片段中,polygon_to_h3函数将输入的多边形转换为六边形列表。你还需要提供分辨率参数。分辨率参数定义了六边形的大小。根据提供的分辨率参数,我们可以将给定区域拆分成六边形,如下所示。

将 geojson 瓦片转换为六边形列表 | 作者提供的图片
在这种情况下,使用了 8 的分辨率。根据文档,分辨率为 8 的六边形面积为 0.737327598 平方公里。在这个特定的情况下,区域被划分为 912 个六边形。
3️⃣ 为区域内每个六边形推导得分
由于我们已经将区域划分为六边形,我们现在可以继续为每个六边形推导得分。
该过程的方法包括以下内容:
-
为数据集中每个兴趣点分配唯一的六边形 ID。
-
建立评分标准,对每个类别给予加权。
-
将带分数的数据集与区域数据集合并。
scores = 1- (pois['category'].value_counts(normalize=True)) * 3
scores_dict = dict(zip(scores.index,scores.values * 100))
pois['weight'] = pois['category'].map(scores_dict)
grouped_df = pois.groupby("h3").agg(
counts=('category','count'),
score = ('weight','sum')
).sort_values(by='counts',ascending=False)
grouped_df.head()

让我们将评分层叠加在基础区域六边形上。右侧的图示展示了如何将得分分配给每个六边形。条形的高度反映了重要性得分。


图片来源于作者
让我们检查推导出的得分如何分布。
grouped_df.reset_index(inplace=True)
full_scored_idx = h3_idx.merge(grouped_df,on='h3',how='left').fillna(0)
full_scored_idx['score'].plot.hist(color='skyblue',edgecolor='black',bins=5,figsize=(10,8))
plt.title("Score Distibutin")
plt.xlabel("Score")
plt.ylabel("Frequency")
plt.grid(visible=True, linestyle='-.')
plt.show()

得分分布 | 图片来源于作者
现在区域内的所有六边形都已评分,是时候创建一个新的数据层进行可视化了。


区域的热点 | 图片来源于作者
请注意上面的颜色编码,灰蓝色的六边形表示重要性低的六边形,而粉色和米色的六边形表示该区域内相对重要的六边形。
4️⃣ 定义出口重要性分数
我们现在有一个包含所选区域所有六边形得分的数据集。这使我们能够识别该区域最关键(热点)六边形。这解决了问题的一半。接下来,我们需要为出口定义重要性得分。
将遵循的步骤包括以下内容:
-
为出口数据集分配唯一的六边形 ID。
-
对于每个出口,识别最近的 K 个六边形。
-
根据 K 个六边形的得分定义出口的重要性。
h3_to_score = dict(zip(full_scored_idx['h3'],full_scored_idx['score']))
get_krings = lambda h:h3.k_ring(h,k=3)
outlets_h3['rings'] = outlets_h3['h3'].apply(get_krings)
print(outlets_h3[['category','h3','rings']])

在上面的代码片段中,k_ring 函数用于检索每个出口最近的 K 个六边形。你还会注意到在这个例子中,K 被设置为 3。
让我们可视化数据,以更好地理解我们在这一步中所完成的工作。

图片来源于作者
现在我们差不多完成了。由于我们已经定义了该区域内每个六边形单元的得分,我们需要做的就是汇总每个出口附近的六边形得分。
outlets_exploded = outlets_h3.explode(column='rings')
outlets_exploded['weight'] = outlets_exploded['rings'].map(h3_to_score)
#can contains hexagons not relavent to current region
outlets_exploded.dropna(inplace=True)
#final scoring for outlets
outlets_exploded.groupby("category").agg(outlet_importance=("weight",'sum'))

最终评分 | 图片来源于作者
很好,我们现在已经成功建立了每个出口的重要性分数。这样,两个目标都已经实现了。做得好! 😃
你可以在 GitHub 仓库下访问所有代码。
[## GitHub - Ransaka/Spatial-indexing-with-h3
目前无法执行该操作。你在另一个标签页或窗口中登录了。在其他标签页或…
结论
这个简化的例子突出了 H3 库的一些宝贵功能。从测量两个地理位置之间的距离到进行最近邻分析,H3 库提供了广泛的选项。通过在适当的时候纳入空间索引,不仅可以节省资源,还能降低成本。原因在于,对点数据进行数学运算可能会消耗大量内存,但通过索引可以大大减少内存使用。
感谢阅读!可以在LinkedIn与我联系。
geotiff.js: 如何获取纬度-经度坐标的投影 GeoTIFF 数据

作者提供的图片
使用 Javascript 将纬度和经度坐标重新投影到 GeoTIFF 的坐标系统中,并使用 geotiff.js 查询数据
·
关注 发表在 Towards Data Science · 10 min 阅读 · 2023 年 3 月 12 日
--
Javascript 已被使用多年来提供交互式网页地图,这些地图通常由矢量数据和 RGB 图像切片组成。这些简单的前端 historically 需要更强大的语言和服务器技术来实际提供和渲染正在可视化的地理空间数据,以及查询或分析它们的机制。
随着 Javascript 语言和生态系统的成熟,曾经几乎不可能的事情现在变得简单而且性能不错。多亏了专注的开源开发者,现在在前端(浏览器)或后端(NodeJS)直接处理地理空间光栅数据变得相当容易。甚至还有多线程支持!
让我们看看在纯 Javascript 中曾经非常困难的一个功能:查询特定坐标的地理空间光栅数据。这些数据集通常以 GeoTIFF 格式提供,为了处理这些数据集,我们将使用一个更流行的处理文件格式的库:geotiff.js。
使用这个库从 GeoTIFF 中提取像素数据很简单。但是,给定一个纬度-经度坐标,你怎么知道读取哪个像素?如果 GeoTIFF 在一个投影坐标系统中怎么办?
与GDAL(一个流行的开源地理空间库,具有 NodeJS 绑定)不同,geotiff.js没有重投影功能:它仅仅是解析 GeoTIFF 文件。这意味着我们的解决方案将比GDAL中的等效方法稍微复杂一些,后者在最简单的情况下只需一个命令:
gdallocationinfo -valonly -wgs84 <geotiff filename> <longitude> <latitude>
我们只需编写一些代码来镜像 GDAL 在后台进行的额外工作:从光栅中提取投影信息,将纬度-经度坐标重新投影到光栅的坐标系统中,并确定该点适用于光栅的哪个像素。
为什么不首先重新投影整个 GeoTIFF?
通过将我们的 GeoTIFF 重新投影到像 EPSG:4326 这样的纬度-经度坐标系统,整个过程大部分可以被规避。但对于许多投影坐标系统,这会引入严重的不准确性。
例如,考虑查询 NBM 天气模型以获取科罗拉多州小熊峰顶的预测温度:

图片来源:作者
选择这个例子是因为它靠近数据集中一个像素的边缘。
现在让我们使用最近邻重采样将数据集重新投影到 EPSG:4326。相同的坐标现在返回了不同的值:

图片来源:作者
我们可以通过使用不同的重采样方法来稍微改进重投影,比如双线性插值:

图片来源:作者
但显然,我们丢失了一些原始天气模型的信息。我们可以通过使重新投影的光栅图像变得非常非常大来减少丢失的信息量,但即使在巨大的尺寸下,仍然会有一些边缘情况,其中靠近像素边界的地点无法正确提取原始天气模型数据。
这是否重要取决于数据的用途和分辨率。如果你使用的是高分辨率的数字高程模型来为长达数英里的路线构建高程剖面图,那么如果高程数据在某些地方略有偏差可能不是什么大问题。相反,如果你使用的是分辨率较粗的气象模型数据,那么处于错误的网格单元可能会导致该区域的预测结果有显著不同。
总体而言:最好使用原始投影来查询来自地理空间光栅的原始数据。
将纬度和经度重投影到光栅坐标系统
第一步是获取 GeoTIFF 的投影信息,并使用该信息将我们期望的坐标重新投影到光栅的坐标系统中。
使 GeoTIFF 成为地理空间光栅而非普通 TIFF 图像的一个重要因素是使用 GeoKeys(参见OGC GeoTIFF 规范)。我们可以使用 geotiff.js 读取给定光栅的 GeoKeys:
// assume the variable gtiff is the geotiff object
// that was created by loading data with geotiff.js
const image = await gtiff.getImage();
const geoKeys = image.getGeoKeys();
我们将把这些 GeoKeys 传递给一个投影库,它将处理繁重的计算,因为投影往往是相当复杂的!
例如,北美的气象模型数据往往使用 Lambert 等角圆锥投影。如果我们从一个源自椭球基准(通常是 WGS84)的纬度经度坐标开始,那么我们会面临一个相当复杂的公式:

Snyder, John (1987). “地图投影:工作手册(USGS 专业论文:1395)” — 公有领域
不需要重新发明轮子,因为开源社区已经投入了多年的工作,创建了全面的重投影库,这些库可以轻松处理各种复杂的地图投影之间的转换。
对于 JavaScript,最强大、最流行且功能齐全的库之一是Proj4js,它是常见的proj库的分支,支持GDAL。我们只需使用光栅的 GeoKeys 生成一个proj字符串,并将最终字符串传递给库即可。
然而,有很多有效的 GeoKeys:
/**
* Geokeys. If you're working with `geotiff` library, this is result of `image.getGeoKeys()`.
* @typedef {Object} module:geokeysToProj4.GeoKeys
* @property {number} GeographicTypeGeoKey See GeoTIFF docs for more information
* @property {number} GeogGeodeticDatumGeoKey See GeoTIFF docs for more information
* @property {number} GeogPrimeMeridianGeoKey See GeoTIFF docs for more information
* @property {number} GeogLinearUnitsGeoKey See GeoTIFF docs for more information
* @property {number} GeogLinearUnitSizeGeoKey See GeoTIFF docs for more information
* @property {number} GeogAngularUnitsGeoKey See GeoTIFF docs for more information
* @property {number} GeogAngularUnitSizeGeoKey See GeoTIFF docs for more information
* @property {number} GeogEllipsoidGeoKey See GeoTIFF docs for more information
* @property {number} GeogSemiMajorAxisGeoKey See GeoTIFF docs for more information
* @property {number} GeogSemiMinorAxisGeoKey See GeoTIFF docs for more information
* @property {number} GeogInvFlatteningGeoKey See GeoTIFF docs for more information
* @property {number} GeogPrimeMeridianLongGeoKey See GeoTIFF docs for more information
* @property {number} ProjectedCSTypeGeoKey See GeoTIFF docs for more information
* @property {number} ProjectionGeoKey See GeoTIFF docs for more information
* @property {number} ProjCoordTransGeoKey See GeoTIFF docs for more information
* @property {number} ProjLinearUnitsGeoKey See GeoTIFF docs for more information
* @property {number} ProjLinearUnitSizeGeoKey See GeoTIFF docs for more information
* @property {number} ProjStdParallel1GeoKey See GeoTIFF docs for more information
* @property {number} ProjStdParallel2GeoKey See GeoTIFF docs for more information
* @property {number} ProjNatOriginLongGeoKey See GeoTIFF docs for more information
* @property {number} ProjNatOriginLatGeoKey See GeoTIFF docs for more information
* @property {number} ProjFalseEastingGeoKey See GeoTIFF docs for more information
* @property {number} ProjFalseNorthingGeoKey See GeoTIFF docs for more information
* @property {number} ProjFalseOriginLongGeoKey See GeoTIFF docs for more information
* @property {number} ProjFalseOriginLatGeoKey See GeoTIFF docs for more information
* @property {number} ProjFalseOriginEastingGeoKey See GeoTIFF docs for more information
* @property {number} ProjFalseOriginNorthingGeoKey See GeoTIFF docs for more information
* @property {number} ProjCenterLongGeoKey See GeoTIFF docs for more information
* @property {number} ProjCenterLatGeoKey See GeoTIFF docs for more information
* @property {number} ProjCenterEastingGeoKey See GeoTIFF docs for more information
* @property {number} ProjCenterNorthingGeoKey See GeoTIFF docs for more information
* @property {number} ProjScaleAtNatOriginGeoKey See GeoTIFF docs for more information
* @property {number} ProjScaleAtCenterGeoKey See GeoTIFF docs for more information
* @property {number} ProjAzimuthAngleGeoKey See GeoTIFF docs for more information
* @property {number} ProjStraightVertPoleLongGeoKey See GeoTIFF docs for more information
* @property {number[]} GeogTOWGS84GeoKey Datum to WGS transformation parameters, unofficial key
*/
使用类似geotiff-geokeys-to-proj4的辅助库来处理geotiff.js的 geokeys 对象到proj字符串的正确转换可能是值得的。实际上,上面的注释块来自于那个库。
安装了这两个库后,我们无需编写太多代码就能设置我们的重投影功能:
import proj4 from 'proj4';
import * as geokeysToProj4 from 'geotiff-geokeys-to-proj4';
... // Not shown: importing geotiff.js and loading a geotiff file.
// for this example, the variable `gtiff` references the file.
const image = await gtiff.getImage();
const geoKeys = image.getGeoKeys();
const projObj = geokeysToProj4.toProj4( geoKeys );
const projection = proj4( `WGS84`, projObj.proj4 );
我们现在创建了一个名为“projection”的 proj 投影对象。使用起来非常简单:
const { x, y } = projection.forward( {
x: -105, // the longitude
y: 40 // the latitude
} );
上面的代码将纬度经度坐标重新投影到光栅坐标系统(可能是米或英尺)。
现在我们需要获取与这些新坐标关联的实际栅格像素。首先,让我们获取有关栅格大小及其边界框的一些信息:
const width = image.getWidth();
const height = image.getHeight();
const [ originX, originY ] = image.getOrigin();
const [ xSize, ySize ] = image.getResolution();
const uWidth = xSize * width;
const uHeight = ySize * height;
上述代码建立了栅格像素大小与投影坐标系统中栅格的实际坐标/大小之间的关系。
然后我们将投影坐标相对于栅格的原点坐标进行处理,查看它在栅格的宽度和高度中的位置。
// x and y come from the projection.forward example earlier
const percentX = ( x - originX ) / uWidth;
const percentY = ( y - originY ) / uHeight;
const pixelX = Math.floor( width * percentX );
const pixelY = Math.floor( height * percentY );
现在我们有了原始经纬度坐标的像素值!
你可能会想确保这个像素确实在栅格内。如果原始坐标没有与栅格相交,那么 pixelX 或 pixelY 可能会是负值或大于栅格的宽度/高度。
提取像素的 GeoTIFF 数据
如果像素在栅格内,那么考虑到一个单一波段的 GeoTIFF,我们有两种提取数据的方法。
第一种方法是将一个 1x1 像素的窗口传递给 geotiff.js 的“readRasters”函数:
const [ value ] = await image.readRasters( {
interleave: true,
window: [ pixelX, pixelY, pixelX + 1, pixelY + 1],
samples: [ 0 ]
} );
这种方法简单明了,但调用“readRasters”有一个小的开销,如果你查询数千个坐标,这个开销可能会累积。
在这种情况下,采用不同的方法可能会带来轻微的性能提升——但代价是 RAM,这对于非常大的 GeoTIFF 可能会相当可观。我们可以将整个栅格读取到内存中作为 ArrayBuffer:
const data = await image.readRasters( {
interleave: true,
samples: [ 0 ]
} );
然后我们只需要获取相应的索引:
const value = data[ width * pixelY + pixelX ];
实际上,geotiff.js 在内部缓存瓦片和条带,因此性能提升充其量是微乎其微的,并且对于适中的数据集通常不明显。
因此,如果查询的数据分布在 GeoTIFF 中的大多数条带或瓦片上,这种方法只会提供(轻微的)速度提升。否则,读取整个图像的开销,包括未使用的瓦片或条带,将超过潜在的性能提升,不过这可以通过读取一个大小调整到最小和最大 x 和 y 或坐标集合的窗口来缓解。
使用上述技术,可以在交互式网页地图中实现以下功能,无论是完全在浏览器中还是通过基本的 NodeJS API:
-
点击网页地图以获取特定点的栅格数据
-
使用底层栅格数据对点特征进行标注(例如,显示天气预报温度的城市名称的天气地图)
-
对栅格数据执行基本的几何操作(例如,使用点集合或边界框进行交集操作),并将该信息显示在表格中或导出为 CSV
-
为二维横截面或线生成图表或数据列表(例如,海拔剖面)
-
对原始 GeoTIFF 数据进行统计分析和数据转换
这种功能(更不用说实际将地理空间栅格数据渲染到地图上)传统上需要专用的 GIS 服务器软件和专用基础设施,交互式网络地图的作用仅仅是向服务器发送请求并显示响应。随着 JavaScript 生态系统的发展,浏览器变得越来越能够解析地理空间数据,从而减少了对专用后端技术的依赖。
传统上,使 GeoTIFF/地理空间栅格数据可供网络地图使用的一种流行方法是拥有一个提供通用开放地理空间联盟接口的 GIS 服务器应用程序——如 WMS(Web 地图服务)或 WMTS(Web 地图瓦片服务)。这些接口允许浏览器请求图像文件或数据的瓦片,这些图像在服务器端渲染后作为 RGB 图像文件发送到浏览器。
服务器还可以提供 WCS(Web 覆盖服务)接口,允许浏览器查询特定坐标的底层地理空间栅格数据。如果使用专有的 GIS 服务器技术(如 Esri 的 ArcGIS 产品),端点和语法可能会有所不同,但最终结果是相同的。
然而,随着云优化 GeoTIFF 标准的成熟以及使用 openlayers + geotiff.js 直接在浏览器中访问和渲染数据的能力,这种技术在某些场景下已经消除了对提供 WMS 和 WMTS 端点的 GIS 服务器的需求。本文描述的查询技术展示了一种去除对 WCS 或查询端点需求的方法。
取而代之的是,可以仅仅利用现有的云提供商(例如,一个 S3 存储桶)来托管 COGs,并让网络浏览器负责解析和处理地理空间数据。具有 NodeJS 后端的组织可以将 GIS 功能整合到这些堆栈中,从而减少对专用软件和额外硬件的需求。
最后,能够在浏览器中处理更多地理空间栅格数据的另一个巨大好处是减少或消除了对互联网连接的需求。对于智能手机地图应用程序,特别是那些旨在远程地区使用的应用程序,这为已经用 JavaScript 编写的应用程序提供了一种离线地理空间工具的途径。
这些应用程序中有几款非常流行,提供了诸如坡度角计算、地形剖面图、保存的天气地图等功能。这些应用允许你保存地图或区域以供离线使用——但一旦用户离线,许多功能会停止工作,因为它们依赖于服务器来实际解析和分析地理空间数据。
这些功能在失去互联网连接后不一定会中断——本文提到的技术只是众多可以让应用在离线时仍然保持功能的技术之一。
对 AUC 和 Harrell’s C 的直观理解
图形化方法
·
关注 发布于 Towards Data Science ·6 分钟阅读·2023 年 9 月 15 日
--
作者拍摄的照片
每个进入机器学习或预测建模领域的人都会遇到模型性能测试的概念。教科书通常仅在读者首先学习的内容上有所不同:回归及其均方误差(MSE),还是分类及其众多性能指标,如准确率、灵敏度或精确度等。尽管准确率可以简单地通过正确/错误预测的比率来计算,因此非常直观,但 ROC AUC 初看可能会让人感到困惑。然而,它也是评估预测器质量的常用参数。让我们首先解开其机制,以了解细节。
首先了解 AUC
假设我们建立了一个二分类器来预测样本属于某个类别的概率。我们的测试数据集具有已知类别,得到了以下结果,这些结果可以总结在混淆矩阵中,并在表格中详细报告,其中样本按预测为 P(正例)类别的概率排序。

混淆矩阵和详细预测表,显示了各个样本的概率。图片由作者提供。
ROC AUC 定义为 ROC(接收者操作特征)曲线下的面积。ROC 曲线是将真实正率(TPR)与假正率(FPR)绘制在一起的图 [Wikipedia]。TPR(也称为敏感性)是正确识别的正例数与所有正例数的比率。在我们的例子中,TPR 计算为 4/5(五个样本中有四个被正确分类为正例)。FPR 计算为错误分类为正例的负例数与实际负例总数的比率。在我们的例子中,FPR 计算为 2/6(如果我们将“阳性”阈值设置为 0.5,那么六个负样本中有两个被误分类为正例)。
我们可以根据 TPR 和 FPR 值绘制 ROC 曲线,并计算 AUC(曲线下面积):

基于预测概率的 ROC 曲线。图片由作者提供。
个别 TPR/FPR 值是如何得到的?为此,我们考虑我们的概率表并计算每个样本的 TPR/FPR,将样本被视为正例的概率设置为表中给出的值。即使我们超过了通常的 0.5 阈值(样本通常被视为“负例”),我们仍继续将其视为正例。让我们在例子中遵循这一过程:

图片由作者提供
在阈值 0.81 时,五个正例中的一个被正确分类为正例,没有样本被预测为负例。我们继续,直到遇到第一个负例:

图片由作者提供
在这里,我们的 TPR 停滞在之前的值(五个正例中的三个被正确预测),但 FPR 增加,我们错误地将六个负样本中的一个分配给了正类。我们继续直到最后:

图片由作者提供
Voilà:我们得到了用于创建 ROC 曲线的完整表格。
为什么 Harrell 的 C 只是 AUC
那么 Harrell 的 C 指数(也称为一致性指数或 C 指数)呢?考虑预测特定疾病(例如癌症)发生后的死亡任务。最终,所有患者都会死,无论是否患有癌症——一个简单的二分类器帮助不大。生存模型考虑了结果(死亡)发生的持续时间。事件发生越早,个体遇到结果的风险越高。如果你要评估生存模型的质量,你会查看 C 指数(也称为一致性、也称为 Harrell 的 C)。
为了理解 C 指数的计算,我们需要引入两个新概念:允许对和一致对。允许对是指在观察期间有不同结果的样本对(例如:患者),即在实验进行时,其中一个患者经历了结果,而另一个则被删失(即尚未达到结果)。然后分析这些允许对,查看具有较高风险评分的个体是否经历了事件,而删失的个体没有。这些情况称为一致对。
简化一点,C 指数计算为一致对数量与允许对数量的比率(为了简化,我省略了风险相等的情况)。让我们通过一个例子来说明,假设我们使用了一个计算风险而不是概率的生存模型。以下表格仅包含允许对。如果具有较高风险评分的患者经历了事件(属于我们的“积极”组),则“Concordance”列设置为 1。id 只是之前表格中的行号。特别注意个体 4 与 5 或 7 的比较。

作者提供的图片
这使我们在 30 个允许对中得到 27 个一致对。比率(简化的 Harrell 的 C)是 C = 0.9,这让我们不禁想起之前计算的 AUC。
我们可以构建一个一致性矩阵,来可视化 C 统计量的计算过程,如 Carrington et al 所建议的那样。该图显示了实际正例的风险评分与实际负例的风险评分,并显示了所有对(绿色 + 红色)中正确排名对(绿色)的比例,如果我们将每个网格方块视为样本的表示:

Harrell 的 C 计算的一致性矩阵。作者提供的图片
一致性矩阵显示了底部右侧的正确排名对、顶部左侧的错误排名对,以及正好对应于我们之前看到的 ROC 曲线的边界。
在拆解构建 ROC 曲线和一致性矩阵的过程中,我们发现了一个相似点:在这两种情况下,我们都根据样本的概率/风险得分对其进行排序,并检查排序是否与实际情况相符。我们设置的分类概率阈值越高,假阳性越多。实际阳性病例的风险越低,实际阴性病例被误分类为阳性的可能性就越大。根据我们的排名数据绘制曲线,会得到形状和面积相同的曲线,这就是我们称之为 AUC 或 Harrell’s C 的曲线,具体取决于上下文。
我希望这个例子有助于培养对 AUC 和 Harrell’s C 的直观理解。
致谢
比较这两个参数的想法来源于一次富有成效的讨论,讨论发生在高级机器学习学习小组的聚会中,致敬 Torsten!
参考文献: Carrington, A.M., Fieguth, P.W., Qazi, H. 等人。用于评估机器学习算法中的不平衡数据的新一致部分 AUC 和部分 c 统计量。BMC Med Inform Decis Mak 20, 4 (2020)。https://doi.org/10.1186/s12911-019-1014-6
使用 Python 按位置获取气温数据
原文:
towardsdatascience.com/get-temperature-data-by-location-with-python-52ed872dd621
按经纬度检索历史气温数据。
·发表于Towards Data Science ·阅读时长 3 分钟·2023 年 5 月 18 日
--

Tom Barrett在Unsplash上的照片
介绍
我一直在处理一些项目,需要按位置找到平均气温,以便将其作为可能的变量添加到我的模型中。在经过一段时间的研究后,我发现了一个非常棒且易于使用的 Python 包,叫做meteostat。
这个包有一些有趣的方法,其中一个正是我需要的:如果我输入经纬度,它可以检索该地点的历史气温。此外,你还可以按时间范围请求数据。所以,你可以获取一年的数据或多年的数据。
另一个很酷的功能是,你可以请求你的数据为每日或每月数据。太棒了!
让我们看看代码是如何工作的。
代码
首先,让我们使用pip install meteostat安装 meteostat。接下来,我们可以导入所需的包。
# Import packages
import pandas as pd
from datetime import datetime
import matplotlib.pyplot as plt
from meteostat import Point, Monthly
如果你访问文档,你会看到这段入门代码,这基本上是你需要的工作内容。我们设置了一个时间范围——我用了 2022 年。然后我们创建一个变量来保存经纬度信息。我将使用这个Point(40.78325, -73.96565),它是纽约市中央公园的经纬度。最后,我们会请求在选定日期之间的Monthly数据,然后获取这些数据。
# Set time period
start = datetime(2022, 1, 1)
end = datetime(2022, 12, 31)
# Create Point for New York, NY
tpr = Point(40.78325, -73.96565)
# Get monthly data for 2022
data = Monthly(tpr, start, end)
data = data.fetch()
这就是你需要做的基本内容。以下是获取的数据的样子。

2022 年纽约市——曼哈顿的气温(摄氏度)。作者提供的图片。
如果你需要每日数据,可以导入Daily而不是Monthly,并在代码中进行替换。
你也可以使用 Pandas 轻松绘制数据。
# Plot line chart including average, minimum and maximum temperature
data.plot(y=['tavg', 'tmin', 'tmax'],
title='Temperature 2022 - Central Park, NYC')
plt.show()
代码生成的图如下所示。

温度图。图片由作者提供。
如果我们想查看提供这些结果的站点,以下是代码。
# Import Meteostat library
from meteostat import Stations
# Get nearby weather stations from our interest point
stations = Stations()
stations = stations.nearby(40.78325, -73.96565)
# Fetching 5 stations
station = stations.fetch(5)
# Print DataFrame
pd.DataFrame(station)

5 个位于中央公园附近的气象站。图片由作者提供。
离开之前
就这些了。一个快速但有用的教程。
GitHub 仓库:
[## Studying/Python/meteostat at master · gurezende/Studying
目前你无法执行该操作。你在另一个标签页或窗口中登录了。你在另一个标签页或窗口中登出了…
如果你需要用于项目的温度数据,我相信meteostat可以帮助你。查看他们的文档获取更多信息。
如果你喜欢这个内容,记得关注我以获取更多信息。
在 Medium 上阅读 Gustavo Santos 的文章。数据科学家。我从数据中提取见解,以帮助个人和公司…
另外,可以在 LinkedIn 上找到我,或在 topmate 上预约时间与我讨论数据科学。
参考
[## Python 库 | Meteostat 开发者
Meteostat Python 库提供了通过 Pandas 简单访问开放天气和气候数据的功能。历史数据…
从 Pandas GroupBy 中获得最大收益
原文:
towardsdatascience.com/get-the-most-from-pandas-groupby-fa2b6db3eccc
从基础示例到实际练习
·发表于 Towards Data Science ·6 min read·2023 年 10 月 6 日
--

Alex Suprun 在 Unsplash 上的照片。
Python 的 pandas 库包含许多用于查询和操作数据的有用工具,其中之一是强大的 GroupBy 函数。该函数允许按各种类别对观察值进行分组,并以多种方式进行聚合。
这可能一开始听起来有些困惑,但本指南将详细介绍如何使用该函数及其各种功能。该操作包括:
-
GroupBy 的介绍。
-
将 GroupBy 应用于实践数据集。
-
各种 GroupBy 技术。
-
实际练习和应用。
代码和数据:
本文档中使用的数据和完整的 Python 代码的 Jupyter notebook 可在链接的 GitHub 页面上获取。下载或克隆该仓库以跟随操作。本指南使用了由作者为本文生成的合成数据和虚假姓名;数据可在链接的 GitHub 页面上获取。
代码需要以下库:
# Data Handling
import pandas as pd
import numpy as np
# Data visualization
import plotly.express as px
1.1. 开始使用——数据加载和 GroupBy 基础
第一步是加载数据集:
# Load Data:
df = pd.read_csv('StudentData.csv')
df.head(3)
这将得到以下数据框,包含了参加了一系列学校测试的学生的信息。它包括他们的年龄、三次测试分数、上课时间、平均成绩、字母等级以及他们是否通过了:

作者截图
Pandas 的 GroupBy 允许将数据框分割成感兴趣的元素,并对其应用某种函数。思考 GroupBy 的最简单方法是提出一个 GroupBy 操作可以解决的问题。一个简单的起点是问有多少学生通过了课程:
df.groupby('CoursePass')['CoursePass'].count()
在上述代码行中,GroupBy 对“CoursePass”列进行分组,然后对“CoursePass”列执行计数函数。这返回一个简单的计数:

截图由作者提供。
这与 value_counts() 操作非常相似,执行方式如下:
df.CoursePass.value_counts()

截图由作者提供。
这只是一个好的开始,但 GroupBy 可以执行更多高级操作。让我们查看多个列以及关于学生数据的更复杂问题。
1.2. 使用多个列的 GroupBy()
假设我们想知道每节课程(下午、晚上和早晨)有多少学生及格。以下 GroupBy 代码可以迅速回答这个问题:
df.groupby(['ClassSession', 'CoursePass'])['CoursePass'].count()
上述代码按“ClassSession”和“CoursePass”列进行分组,同时返回每个 CoursePass 状态(“是”或“否”)的计数。结果是:

截图由作者提供。
注意,括号内列的顺序很重要。交换“ClassSession”和“CoursePass”的顺序会得到以下结果:
df.groupby(['CoursePass', 'ClassSession'])['ClassSession'].count()

截图由作者提供。
在进一步探索 GroupBy 的更高级应用之前,让我们看看如何清理输出以获得更好的展示效果。
1.3. 清理 GroupBy 输出
有几种方法可以将标准 GroupBy 输出转换为更干净的 dataframe 格式。让我们以其中一个示例为例,将“.to_frame()”添加到代码末尾:
df.groupby(['ClassSession', 'CoursePass'])['CoursePass'].count().to_frame()

截图由作者提供。
注意,这会返回多层列而没有索引。同时注意到有两个列名为“CoursePass”。要展平列、调整名称并添加索引,请使用以下代码:
df.groupby(['ClassSession', 'CoursePass'])['CoursePass'].count().reset_index(name='Count')
这会返回如下结果:

截图由作者提供。
将 reset_index() 添加到操作中会展平列并添加索引;在 reset_index() 中传递一个名称可以将计数列(在此情况下为“CoursePass”)命名为“Count”。如果未在 reset_index() 中重命名列,则会因两个列名为“CoursePass”而返回错误。
这可以通过完全隐藏索引进一步改进:
df.groupby(['ClassSession', 'CoursePass'])['CoursePass'].count().reset_index(name='Count').style.hide_index()
输出是:

截图由作者提供。
接下来,让我们查看更高级的 GroupBy 函数。
1.4. 高级 GroupBy 功能
GroupBy 可以创建所选列的对象。下面的代码基于两个感兴趣的列创建了一个 GroupBy 对象;对该对象应用 mean() 函数,并传递另外两列,返回这些列的均值:
# Create a groupby object:
dfGroupby = df.groupby(['LetterGrade', 'CoursePass'])
dfGroupby[['Test1', 'Test2']].mean()
结果是:

截图由作者提供。
注意输出是如何按“LetterGrade”和“CoursePass”列分组的,并返回“Test1”和“Test2”列的平均值,这两个列是从初始 GroupBy 对象中选择用于 mean() 函数的。
通过使用多个聚合,这可以提升到更高级的水平。考虑以下代码:
dfGroupby = df.groupby(['LetterGrade', 'CoursePass'])
dfGroupby[['Grade', 'Age']].agg(['mean', 'min', 'max']).reset_index()
这返回:

作者截图。
上述代码回答了几个问题:按 LetterGrade 和 CoursePass 排序,每个学生的平均值、最小值和最大值的成绩以及每个学生的平均值、最小值和最大值的年龄?
创建 GroupBy 对象并非总是必要的;agg() 函数可以回答相当复杂的问题。假设我们想要通过 CoursePass 和 ClassSession 进行筛选,以查找每个学生的平均年龄和成绩;以下代码提供了答案:
df.groupby(['CoursePass', 'ClassSession']).agg({'Age':'mean', 'Grade':'mean'})
输出是:

作者截图。
aggregate 函数还可以传递 numpy 操作:
data = df.groupby(['CoursePass', 'ClassSession'])['Grade'].agg([np.mean, np.max, np.min]).reset_index()
data = data.rename(columns={'mean':'AverageGrade',
'amax':'HighestGrade',
'amin':'LowestGrade'})
data.style.hide_index()
这产生了:

作者截图。
aggregate 函数也可以直接重命名列:
df.groupby(['CoursePass', 'ClassSession'])\
.agg(AverageGrade=('Grade', 'mean'),
HighestGrade=('Grade', 'max'),
LowestGrade=('Grade', 'min'))
这返回:

作者截图。
GroupBy 的语法可能性数量意味着对有关数据框的复杂问题,可能存在一个优雅的代码解决方案。现在我们已经探讨了基础知识,让我们看看如何应用 GroupBy 的一些实际示例。
2. 练习:快速可视化成绩计数
要求: 给定学生课程的测试成绩,在一个图表上可视化每个班级会话中每个字母成绩的总数。
让我们先看看数据:
df.head()

作者截图。
需求关注的是字母成绩和班级会话;这意味着 GroupBy 将按 ClassSession 和 LetterGrade 列排序。需求还需要总数;让我们使用 count() 函数看看返回的结果:
session = df.groupby(['ClassSession', 'LetterGrade'])['LetterGrade'].count().reset_index(name='Count')
session = session.sort_values(by='LetterGrade', ascending = True)
session

作者截图。
上述代码按班级会话和字母成绩对数据进行了分组,同时计算了每个分组组合中每个字母成绩的出现次数。sort_values() 代码按字母成绩 A 到 F 排序。剩下的就是可视化数据:
# Generate plot:
plot = px.histogram(session,
y='Count',
x='LetterGrade',
color='ClassSession')
plot.update_layout(
title={'text': "Grade Distribution\
<br><sup>Number of Grades by Type and Class Session</br>",
'xanchor': 'center',
'yanchor': 'top',
'x': 0.47},
xaxis_title='Letter Grade',
yaxis_title='Count')
plot.show()
这生成了以下图:

作者截图。
只需几行代码,问题就得到了解答!
3. 结论。
Pandas 的 GroupBy 函数是一个强大的工具,可以快速对数据框中的特定数据分组执行聚合和数学操作。通常,一次函数执行就足以回答一个看似复杂的问题。掌握 GroupBy 的使用为数据科学家提供了一个有效的数据准备和处理工具。随意使用提供的笔记本和 链接到 GitHub 页面上的代码,进一步尝试 GroupBy 的功能吧!
从 AI/ML 入手,构建智能供应链
原文:
towardsdatascience.com/getting-started-with-ai-ml-to-build-intelligent-supply-chains-76829f492ef8
您应该如何考虑在供应链中应用 AI/ML 技术,以最大化投资回报?
·发表于 Towards Data Science ·7 分钟阅读·2023 年 4 月 10 日
--

图片由 Volodymyr Hryshchenko 提供,发布在 Unsplash
背景
供应链优化是一个广泛的研究领域。在供应链中,有大量用例可以从 AI/ML 技术的应用中受益。组织往往难以确定从何入手以及如何开始。供应链高管通常寻求投资团队(已经非常紧张)的时间和精力,以从这些方法中获得最大价值。在本文中,我们探讨了一组小而多样的用例,作为供应链组织进入 AI/ML 领域的起点。供应链领导者可以期望从这些应用中获得高程度的成本和效率改善。
我们将供应链管理分为五个组成部分:计划、采购、生产、交付、逆向物流,并概述了与这五个组成部分对应的 需求感知、供应商细分、设备故障预测、交付时间预测、客户退货预测 的用例。根据经济价值、可追踪性、结果可解释性、洞察可操作性和应用可持续性等标准,我们对每个用例的整体效益进行了定性评估。
用例探索
智能供应链 集成了数据、自动化和先进的分析技术,为供应链的不同部分带来效率和成本降低。它通过提升客户体验,成为组织的战略优势来源。通常,它由优化和 AI/ML 技术的组合支持。我们探讨了五个用例,并将其映射到供应链管理的五个要素:计划 → 采购 → 制造 → 交付 → 逆向物流。
1. 短期需求感知(计划):库存管理是供应链中至关重要的活动。营运资本和存储成本与库存水平相关,而库存水平又依赖于准确预测需求等因素。高水平的预测准确性有助于通过在正确的地点和时间生产合适数量的产品来减少库存成本和缺货情况。这有助于优化仓库中的库存,降低安全库存水平,从而减少成本,同时避免可能导致长期业务和收入损失的缺货现象。一个启用了 AI 的应用可以识别客户订单中的模式,并将某些领先指标与短期需求相关联,从而比传统方法更准确。通过实时订单信息,我们可以自动化当前和下一个月的需求预测,并定期更新。
2. 供应商细分(采购):大型组织可能有数百或数千个供应商,这些供应商是其供应链的重要组成部分。不同的原材料以不同的数量从供应商处采购。为了在增加供应可靠性的同时降低采购成本,应用不同的策略于不同的供应商将会非常有帮助。考虑到供应商数量众多,将供应商分为几个关键组将会很有益。这种细分可以实现对原材料价格、数量和供应时间的有针对性的合同谈判,从而优化支出。一个启用了 AI 的应用可以基于特征如数量、价格、战略关系和可靠性来细分供应商,使采购专家能够对一个集群内的供应商采用类似的采购策略。此细分可以每月或每季度更新,以适应供应商的变化。
-
设备故障预测(制造):由于可靠性故障导致的计划外停机在制造操作中很常见。这往往会影响产品库存位置,并可能导致客户履约延迟。预测这些停机可能发生的时间可以帮助工厂采取措施减少设备故障风险,或提前准备库存以应对停机。这些预测和相应的缓解措施可以帮助避免缺货和收入损失,同时推动对预防性维护选项的投资,以避免昂贵的维修。启用人工智能的应用可以将设备故障与关键测量(如产量、压力、温度等)相关联,提供故障发生时间的见解。根据测量的粒度,该应用可能每天或每几天运行一次,以评估故障风险。
-
交货时间预测(交付):准时交付给客户是衡量供应链表现的关键指标,这直接影响客户体验。组织会测量这一指标,并根据历史延迟趋势积极尝试改进。能够准确预测交货时间可以帮助在要求的时间内将产品送到客户手中,从而避免糟糕的客户体验和由此带来的商业损失,并避免因迟交而产生的罚款和其他费用。启用人工智能的应用可以基于开始日期和时间、季节、承运人、来源和目的地等属性预测交货时间。这样的预测可以使每次发货时保持客户知情,提升客户体验和客户留存。此外,这还提供了优化路线和选择表现更佳的承运人的机会。
-
客户退货预测(逆向物流):由于未达到质量规格或运输延迟等原因,客户会退回一定比例的销售产品。为了准备退货运输、储存和处理,公司需要准确估算退回产品的数量。这有助于减少储存和处理成本。启用人工智能的应用可以根据过去几周的客户发货量提供退货的总体量。此预测可以每周更新一次。
用例优先级排序
我们通过五个关键标准来评估这些用例:经济价值、可处理性、结果可解释性、洞察行动性和应用可持续性。经济价值包括任何成本节约或收入损失避免。可处理性指的是数据的可用性以及提供合理时间内预测的稳健 AI/ML 方法。结果可解释性指的是如何使用业务驱动因素向利益相关者解释观察和预测。洞察行动性描述了组织在 AI/ML 应用结果上的行动能力。最后,应用可持续性描述了长期积极维护和更新 AI 应用输入的可行性。
(i) 经济价值:由于运输量巨大,我们预计需求感知和交货时间预测的经济价值为‘高’。对于供应商细分和设备故障预测,由于采取措施的零散性,其经济价值评估为‘中’;而由于客户退货量相对较低,因此选择了‘低’评估。
(ii) 可处理性:根据用例数据规模和我们拥有的计算能力,大多数用例的可处理性预期为‘高’,除了设备故障预测,如果故障发生不够频繁,可能会数据有限。
(iii) 结果可解释性:推荐对需求感知和客户退货预测应用多元线性回归,这会获得‘高’的可解释性评分。对供应商细分应用无监督方法(如 k-means 聚类)可以显示每个簇中的特征接近,因此可解释性标记为‘高’。对设备故障预测和交货时间预测应用更复杂的集成方法(如随机森林、Xgboost)则导致‘中’的可解释性。
(iv) 洞察行动性:对于需求感知和客户退货预测,行动性标记为‘高’,因为这是基于预测变化来改变生产和处理的内部决策;而交货时间预测和供应商细分的洞察则需要与外部方(包括承运人和供应商)谈判,这可能会限制我们的灵活性。对于设备故障预测,行动性为‘中’,我们可能能够采取措施降低风险,但可能无法完全消除故障。
(v) 应用可持续性:假设五个用例中的四个具有较高的可持续性,因为数据可能以适当的速度生成,允许频繁更新 AI/ML 应用,除了设备故障预测,可能数据有限。
从下表汇总的这些用例来看,需求感知 在所有这些标准中评分最高——在这种情况下,建议从这个用例的 AI/ML 应用开始。

用例定性评分对比多个标准
结论
在许多行业中,AI/ML 技术在供应链和业务优化中的应用仍然是一个新兴领域。采取“爬行、行走、奔跑”的方法来整合 AI/ML 进入操作并非不合理。通过“低垂的果实”来展示 AI/ML 为团队创造价值的有效性,可以产生飞轮效应。然而,重要的是在较小的成功基础上构建可持续的长期商业模式,使 AI/ML 融入价值链的每个方面。
感谢阅读。希望你觉得有用。欢迎将你的评论发送至 rkumar5680@gmail.com。我们可以在 LinkedIn 上联系。
开始使用 Databricks
原文:
towardsdatascience.com/getting-started-with-databricks-11af3db4f595
Databricks 初学者指南
·发布于 Towards Data Science ·9 分钟阅读·2023 年 5 月 8 日
--

图片由 Alexander Grey 提供,来源于 Pexels
Databricks 使数据科学家能够轻松创建和管理用于研究、实验和部署的笔记本。像 Databricks 这样的平台的吸引力包括与云服务的无缝集成、模型维护工具和可扩展性。
Databricks 对于模型实验和维护非常有用。Databricks 拥有一个机器学习库,称为 MLflow,提供了用于模型开发和部署的有用工具。使用 MLflow,你可以记录模型以及与模型相关的元数据,例如性能指标和超参数。这使得运行实验和分析结果变得非常简单。
许多 Databricks 功能对于在机器学习工作流中的步骤进行扩展非常有用,例如数据加载、模型训练和模型日志记录。Koalas 是 Databricks 中一个更高效的替代 pandas 的库。Pandas 用户定义函数(UDF)允许你以分布式的方式应用自定义函数,这些函数通常计算成本较高,这可以显著减少运行时间。Databricks 还允许你在更大的机器上配置作业,这对于处理大型数据和繁重计算非常有用。此外,模型注册表允许你运行和存储数百甚至数千个模型的实验结果。这在扩展研究人员开发并最终部署的模型数量方面非常有用。
在本文中,我们将介绍一些 Databricks 的基础知识。首先,我们将通过一个简单的数据科学工作流程来构建一个流失分类模型。然后,我们将看看如何使用 Koalas 和 Pandas UDF 等工具来加快特定操作的速度。最后,我们将看到如何使用 Mlflow 来帮助我们运行实验并检查结果。
在这里,我们将使用Telco 流失数据集。该数据包含一个虚构电信公司客户的账单信息。它指定了客户是否停止或继续使用服务,即流失。该数据公开可用,并可以在Apache 2.0 许可证下免费使用、分享和修改。
入门指南
首先,访问Databricks网站并点击“免费试用”:

截图由作者拍摄
您应该看到以下内容:

截图由作者拍摄
输入您的信息并点击继续。接下来,系统会提示您选择一个云平台。本文不会涉及任何外部云平台。在右侧面板底部点击“开始使用社区版”。

截图由作者拍摄
接下来,按照步骤创建一个社区版账户。
导入数据
让我们从左侧面板中的“数据”选项卡开始:

截图由作者拍摄
接下来点击“数据”,然后点击“创建表”:

截图由作者拍摄
接下来,将流失 CSV 文件拖放到标记为“拖放文件上传,或点击浏览”的区域。

截图由作者拍摄
上传 CSV 后,您应该看到以下内容:

截图由作者拍摄
接下来点击“在笔记本中创建表”。一个 Databricks 文件存储(DBFS)示例笔记本,将弹出并包含将文件写入 Databricks 文件存储的逻辑:

截图由作者拍摄
DBFS 允许 Databricks 用户上传和管理数据。该系统是分布式的,因此非常适合存储和管理大量数据。
第一个单元指定了读取我们上传的流失数据的逻辑:
# File location and type
file_location = "/FileStore/tables/telco_churn-1.csv"
file_type = "csv"
# CSV options
infer_schema = "false"
first_row_is_header = "false"
delimiter = ","
# The applied options are for CSV files. For other file types, these will be ignored.
df = spark.read.format(file_type) \
.option("inferSchema", infer_schema) \
.option("header", first_row_is_header) \
.option("sep", delimiter) \
.load(file_location)
display(df)
如果我们运行这个单元,我们会得到以下结果:

截图由作者拍摄
我们看到表格中包含了一些不太有用的列名(_c0, _c1,等等)。为了解决这个问题,我们需要指定 first_row_is_header= “true”:
first_row_is_header = "true"
当我们运行这个单元时,我们现在得到:

截图由作者拍摄
如果你点击表格,可以向右滚动查看数据中的额外列:

截图由作者拍摄
构建分类模型
我们接下来使用在 Databricks 中上传的数据构建流失分类模型。在左侧面板中点击“创建”:

截图由作者拍摄
接下来点击笔记本:

截图由作者拍摄
我们将笔记本命名为“churn_model”:

截图由作者拍摄
现在我们可以复制 DBFS 示例笔记本中的逻辑,以便访问数据:

截图由作者拍摄
接下来,我们将 Spark 数据框转换为 Pandas 数据框:
df_pandas = df.toPandas()
我们来构建一个 Catboost 分类模型。Catboost 是一种基于树的集成机器学习算法,它使用梯度提升来提高集成中连续树的性能。
我们来通过 pip 安装 Catboost 包。我们在笔记本顶部的一个单元格中执行此操作:

截图由作者拍摄
接下来,我们构建一个 Catboost 流失分类模型。我们将使用任期、每月费用和合同来预测流失结果。我们将流失列转换为二进制值:
import numpy as np
df_pandas['churn_label'] = np.where(df_pandas['Churn']== 'No', 0, 1)
X = df_pandas[["tenure", "MonthlyCharges", "Contract"]]
y = df_pandas['churn_label']
Catboost 允许我们直接处理分类变量,无需将其转换为机器可读的代码。为此,我们只需定义一个包含分类列名称的列表:
cats = ["Contract"]
在定义 Catboost 模型对象时,我们将 cat_features 参数设置为此列表。我们将数据分为训练集和测试集:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
然后我们可以训练 Catboost 模型。我们将使用默认参数值:
model = CatBoostClassifier(cat_features= cats, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
我们可以评估模型性能:
from sklearn.metrics import accuracy_score, precision_score
accuracy = accuracy_score(y_pred, y_test)
precision = precision_score(y_pred, y_test)
print("Accuracy: ", accuracy )
print("Precision: ", precision )

截图由作者拍摄
Koalas
这里我们将一个 Spark 数据框转换为 Pandas 数据框。这对于我们的小数据集来说很好,但随着数据量的增加,Pandas 变得缓慢且效率低下。Pandas 的替代方案是 Koalas 库。Koalas 是 Databricks 开发的一个分布式版本的 Pandas。要使用 Koalas,我们可以在笔记本顶部使用 pip 安装 Databricks:
%pip install -U databricks
我们从 databricks 导入 Koalas:
from databricks import koalas as ks
要将我们的 Spark 数据框转换为 Koalas 数据框,我们可以执行以下操作:
df_koalas = ks.DataFrame(df)
df_koalas.head()
Pandas UDF
Pandas UDF 是 Databricks 中的另一个有用工具。它允许你以分布式方式应用函数到数据框上。这对于提高对大数据框进行计算的效率非常有用。例如,我们可以定义一个函数,该函数接受一个数据框并构建一个 Catboost 模型。然后我们可以使用 Pandas UDF 在分组或分类层次上应用这个函数。让我们为每个互联网服务值构建一个模型。
首先,我们需要定义 Pandas UDF 的函数和模式。模式简单地指定了列名及其数据类型:
from pyspark.sql.functions import pandas_udf, PandasUDFType
churn_schema = StructType(
[
StructField("tenure", FloatType()),
StructField("Contract", StringType()),
StructField("InternetService", StringType()),
StructField("MonthlyCharges", FloatType()),
StructField("Churn", FloatType()),
StructField("Predictions", FloatType()),
]
)
接下来我们将定义我们的函数。我们将把之前定义的逻辑简单地包含在一个名为‘build_model’的函数中。为了使用 Pandas UDF,我们添加装饰器‘@pandas_udf’:
@pandas_udf(churn_schema, PandasUDFType.GROUPED_MAP)
def build_model(df: pd.DataFrame) -> pd.DataFrame:
我们还可以在我们的函数中包含模型构建逻辑。我们还会将预测和真实流失值存储在我们的数据框中:
@pandas_udf(churn_schema, PandasUDFType.GROUPED_MAP)
def build_model(df: pd.DataFrame) -> pd.DataFrame:
df['churn_label'] = np.where(df['Churn']== 'No', 0, 1)
X = df[["tenure", "MonthlyCharges", "Contract"]]
y = df['churn_label']
cats = ["Contract"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = CatBoostClassifier(cat_features= cats, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
output = X_test
output['Prediction'] = y_pred
output['Churn'] = y_test
output['InternetService'] = df['InternetService']
output['churn_label'] = df['churn_label']
return output
最后,我们可以将这个函数应用到我们的数据框中。让我们将我们的 Koalas 数据框转换回 spark 数据框:
df_spark = df_koalas.to_spark()
churn_results = (
df_spark.groupBy('InternetService').apply(build_model))
然后我们可以将生成的 spark 数据框转换为 Pandas 数据框(也可以转换回 Koalas),并显示前五行:
churn_results = churn_results.toPandas()
churn_results.head()

作者截图
即使我们存储了预测结果,你也可以使用 Pandas UDF 来存储你在数据框计算结果中获得的任何信息。一个有趣的练习是将准确度评分和精确度评分包含在输出的 spark 数据框中,每个互联网服务值都有。
开始使用 MLflow
在 Databricks 中,另一个有用的工具是 MlFlow。MlFlow 允许你轻松运行、记录和分析实验。在本演示中,我们将使用之前在笔记本中定义的第一个模型对象。让我们在笔记本的顶部使用pip install Mlflow:
%pip install -U mlflow
并导入 Mlflow:
import mlflow
让我们通过设置实验名称继续:
mlflow.set_experiment(
f"/Users/spierre91@gmail.com/churn_model"
)

作者截图
我们可以记录的一件事是 Catboost 特征重要性,这将帮助我们分析哪些特征对预测流失重要:
feature_importance = pd.DataFrame(
{"variable": model.feature_names_, "importance": model.feature_importances_}
)
feature_importance.to_csv("/feature_importance.csv")

作者截图
然后我们可以使用log_model方法记录我们的 Catboost 模型:
with mlflow.start_run(run_name=f"churn_model"):
mlflow.sklearn.log_model(model, "Catboost Model")
我们收到通知,说明“已记录 1 次实验运行到 Mlflow”:

作者截图
我们可以点击运行并查看以下内容:

作者截图
在这里我们可以看到模型性能和模型工件(如特征重要性)的指标。我们将很快展示如何在 Mlflow 中记录这两者。
我们还可以点击实验:

作者截图
这里我们可以看到与实验相关的每次运行。这对于跟踪实验如修改 Catboost 参数、训练数据、工程特征等非常有用。
最后,让我们将特征重要性记录为工件,将准确度分数和精确度分数记录为指标,并将分类输入列表记录为参数:
with mlflow.start_run(run_name=f"churn_model"):
mlflow.sklearn.log_model(model, "Catboost Model")
mlflow.log_artifact("/feature_importance.csv")
mlflow.log_metric("Precison", precision)
mlflow.log_metric("Accuracy", accuracy)
mlflow.log_param("Categories", cats)
如果我们点击运行,我们可以看到记录了特征重要性、准确度分数和精确度分数,以及分类输入:

作者截图
Databricks notebook 中的代码已移植到一个 ipython 文件,并可以在GitHub上获取。
结论
在这篇文章中,我们讨论了如何开始使用 Databricks。首先,我们展示了如何将数据上传到 DBFS。然后,我们创建了一个 notebook,并展示了如何在 notebook 中访问上传的文件。接着,我们讨论了 Databricks 中的一些工具,这些工具可以帮助数据科学家和研究人员扩展数据科学解决方案。首先,我们展示了如何将 spark 数据框转换为 Koalas 数据框,后者是 Pandas 的更快替代品。然后,我们展示了如何使用 Pandas UDF 将自定义函数应用于 spark 数据框。这对于需要在大型数据框上执行的重计算任务非常有用。最后,我们展示了如何记录与建模实验相关的指标、参数和工件。对这些工具的熟悉对于从事数据科学、机器学习和机器学习工程的人员来说非常重要。
入门 Great Expectations:Python 中的数据验证指南。
学习如何用几行 Python 代码防止数据质量问题。
·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 7 月 12 日
--

照片来源:Link Hoang 在 Unsplash
当你与公司合作进行数据科学项目时,通常没有唯一的测试集,不像大学和研究那样,而是不断从客户那里收到更新的样本。
在将机器学习模型应用于新样本之前,你需要验证其数据质量,例如列名、列类型以及字段分布,这些应该与训练集和旧测试集相匹配。
手动分析数据可能非常耗时,尤其是当数据很脏且具有超过 100 个特征时。幸运的是,有一个救命的 Python 库,叫做Great Expectations。这是否引起了你的兴趣?让我们开始吧!
什么是 Great Expectations?

作者插图。来源:flaticon。
Great Expectations 是一个开源 Python 库,专门解决管理数据的三个重要方面:
-
验证数据是否符合一些重要条件或期望。
-
自动化数据分析,快速测试数据而无需从头开始。
-
格式化文档,包含期望和验证结果。
在本教程中,我们将专注于验证数据,这在处理实际数据时是主要问题之一。
阿姆斯特丹的 Airbnb 房源
我们将分析由 Inside Airbnb 提供的 Airbnb 列表数据。我们将使用来自 阿姆斯特丹 的数据集。数据集已经分为训练集和测试集。正如你从数据集的名称中可能猜到的那样,目标是预测房源价格。如果我们仅关注评论数量,我们可以注意到测试数据的评论数量变异性大于训练集的评论数量。

作者插图
我们应该问自己一个问题:“我们还遗漏了哪些差异?让我们开始使用这个库吧!
目录:
-
需求
-
加载文件
-
创建期望
需求
在安装库之前,建议 安装 Python 3 并创建虚拟环境。在激活环境后,我们可以安装 Python 库:
pip install great_expectations
你还需要从 Kaggle 下载数据以跟随教程。文件应包含在“data”文件夹中。
加载文件
就像在 Pandas 中一样,great expectations 库也有相应的方法来导入 CSV 文件:
import pandas as pd
import great_expectations as gx
test_df = gx.read_csv('data/test.csv')
如果你有其他类型的数据,比如 JSON、parquet 和 XLSX,你可以直接使用 Pandas 方法。
test_df.head()

作者插图
这是对我们测试数据和我们将在下一步分析的变量的快速概述。
创建期望
在这个库中,期望值 由几个测试组成,验证你的数据质量。这个库的优点是你不需要手动检查,因为已经实现了 300 多个具有直观名称的期望值。
- 检查列名是否与之前相同
假设客户给我们发送了一个新样本,我们想检查它是否包含与训练集相同的列名。如果你问 ChatGPT,Pandas 中有很多方法可以做到这一点,但使用 great expectations 有一种更直观的方法:
l_train_column_names = ['id','neighbourhood','room_type','price',
'minimum_nights','number_of_reviews','last_review','availability_365']
test_df.expect_table_columns_to_match_set(column_set=l_train_column_names)
输出:
{
"success": false,
"meta": {},
"exception_info": {
"raised_exception": false,
"exception_traceback": null,
"exception_message": null
},
"result": {
"observed_value": [
"availability_365",
"last_review",
"minimum_nights",
"number_of_reviews",
"price",
"room_type"
],
"details": {
"mismatched": {
"missing": [
"id",
"neighbourhood"
]
}
}
}
}
从结果中,我们可以看到该方法找到了大部分列,除了 id 和 neighbourhood 字段。由于条件没有完全满足,"success" 键的值为 false。
2. 检查 last_review 中是否没有空值
缺失值是处理真实数据时的主要问题之一:
test_df.expect_column_values_to_not_be_null('last_review')
输出:
{
"success": false,
"meta": {},
"exception_info": {
"raised_exception": false,
"exception_traceback": null,
"exception_message": null
},
"result": {
"element_count": 1522,
"unexpected_count": 143,
"unexpected_percent": 9.395532194480946,
"unexpected_percent_total": 9.395532194480946,
"partial_unexpected_list": []
}
}
从这个测试中,我们可以看到该列中有 143 个缺失值。
3. 检查 minimum_nights 的类型是否为整数
这可能看起来平淡无奇,但你可能会发现错误,因为模型是在不同类型的列上训练的。因此,这个期望对于避免浪费时间是有用的:
test_df.expect_column_values_to_be_in_type_list('minimum_nights', ['int'])
输出:
{
"success": true,
"meta": {},
"exception_info": {
"raised_exception": false,
"exception_traceback": null,
"exception_message": null
},
"result": {
"observed_value": "int64"
}
}
期望得到了遵守,突出显示为"success":true。
4. 检查最大价格是否在特定区间内
我们之前已经看到价格在训练集和测试集中的分布不同。我们尝试调查最大价格是否在 413 和 12000 之间,这分别对应训练集的第 90 百分位数和最大值:
test_df.expect_column_max_to_be_between(column='price', min_value=413, max_value=12000)
输出:
{
"success": true,
"meta": {},
"exception_info": {
"raised_exception": false,
"exception_traceback": null,
"exception_message": null
},
"result": {
"observed_value": 7900,
"element_count": 1522,
"missing_count": null,
"missing_percent": null
}
}
输出结果告诉我们,最大价格为 7900,并且在该区间内。
最后的思考:
我刚刚介绍了 Great Expectations 的概述,以介绍其主要功能:期望。你可以从官方网站探索其他声明性语句或期望。在下一篇文章中,我将介绍数据分析和所有期望的可视化自动化,并汇集在一个文档中。你可以在这里找到 GitHub 代码。感谢阅读!祝你有美好的一天!
免责声明:该数据集的许可为 4.0 国际 (CC BY 4.0)
有用的资源:
开始使用 JAX
原文:
towardsdatascience.com/getting-started-with-jax-a6f8d8d0e20
推动高性能数值计算和机器学习研究的未来
·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 7 月 7 日
--

由 Lance Asper 提供的照片,来源于 Unsplash
介绍
JAX 是 Google 开发的一个 Python 库,用于在任何类型的设备(CPU、GPU、TPU 等)上进行高性能数值计算。JAX 的主要应用之一是机器学习和深度学习研究开发,尽管该库主要设计用于提供执行通用科学计算任务(高度维度矩阵操作等)的所有必要功能。
考虑到特别关注高性能计算,JAX 被设计为在 XLA(加速线性代数)的基础上极其快速。XLA 实际上是一个编译器,旨在加速线性代数操作,并且可以在 TensorFlow 和 Pytorch 等其他框架的后台工作。此外,JAX 数组被设计为遵循与 Numpy 相同的原则,使得迁移旧的 Numpy 代码到 JAX 变得非常容易,并通过 GPU 和 TPU 获得性能提升。
JAX 的主要特点包括:
-
即时编译(JIT):JIT 和加速硬件使 JAX 比普通的 Numpy 快得多。使用 jit() 函数可以编译并缓存自定义函数与 XLA 内核。通过缓存,我们将增加首次运行函数的总体执行时间,从而大幅减少随后的运行时间。在使用缓存时,确保在需要时清除缓存以避免过时结果(例如,全局变量变化)是很重要的。
-
自动并行化:异步调度使得 JAX 向量可以延迟计算,仅在访问时才生成内容(在计算完成之前控制权返回程序)。此外,为了实现图优化,JAX 数组是不可变的(类似的概念包括延迟计算和图优化,适用于 Apache Spark)。pmap() 函数可以用于在多个 GPU/TPU 上并行计算。
-
自动矢量化:可以使用 vmap() 函数进行自动矢量化以并行化操作。在矢量化过程中,算法从对单个值的操作转变为对一组值的操作。
-
自动微分:grad() 函数可以用于自动计算函数的梯度(导数)。特别是,JAX 自动微分使得在深度学习领域之外开发通用的微分程序成为可能。可以通过递归、分支、循环进行微分,执行高阶微分(例如,雅可比矩阵和海森矩阵),并使用前向和反向模式微分。
因此,JAX 能为我们提供构建先进深度学习模型所需的所有基础,但没有提供一些最常见深度学习操作(例如,损失/激活函数、层等)的开箱即用的高级工具。例如,在 ML 训练过程中学到的模型参数可以存储在 JAX 的 Pytree 结构中。考虑到 JAX 提供的所有优势,不同的深度学习框架已经在其基础上构建,例如 Haiku(由 DeepMind 使用)和 Flax(由 Google Brain 使用)。
演示
作为本文的一部分,我们将看到如何使用 JAX 和 Kaggle 移动价格分类数据集 [1] 来解决一个简单的分类问题,以预测手机的价格范围。本文中使用的所有代码(以及更多!)都可以在 我的 GitHub 和 Kaggle 账户 上找到。
首先,我们需要确保在环境中安装了 JAX。
pip install jax
目前,我们已经准备好导入必要的库和数据集(图 1)。为了简化分析,我们不使用所有标签中的类别,而是过滤数据以仅使用 2 个类别,并减少特征的数量。
import pandas as pd
import jax.numpy as jnp
from jax import grad
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt
df = pd.read_csv('/kaggle/input/mobile-price-classification/train.csv')
df = df.iloc[:, 10:]
df = df.loc[df['price_range'] <= 1]
df.head()

图 1:移动价格分类数据集(图片由作者提供)。
数据集清理完成后,我们可以将其划分为训练集和测试集,并标准化输入特征,以确保它们都位于相同范围内。此时,输入数据也被转换为 JAX 数组。
X = df.iloc[:, :-1]
y = df.iloc[:, -1]
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.20,
stratify=y)
X_train, X_test, y_train, Y_test = jnp.array(X_train), jnp.array(X_test), \
jnp.array(y_train), jnp.array(y_test)
scaler = StandardScaler()
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
为了预测手机的价格范围,我们将从零开始创建一个逻辑回归模型。为此,我们首先需要创建几个辅助函数(一个用于创建 Sigmoid 激活函数,另一个用于二元损失函数)。
def activation(r):
return 1 / (1 + jnp.exp(-r))
def loss(c, w, X, y, lmbd=0.1):
p = activation(jnp.dot(X, w) + c)
loss = jnp.sum(y * jnp.log(p) + (1 - y) * jnp.log(1 - p)) / y.size
reg = 0.5 * lmbd * (jnp.dot(w, w) + c * c)
return - loss + reg
我们现在准备创建训练循环并绘制结果(图 2)。
n_iter, eta = 100, 1e-1
w = 1.0e-5 * jnp.ones(X.shape[1])
c = 1.0
history = [float(loss(c, w, X_train, y_train))]
for i in range(n_iter):
c_current = c
c -= eta * grad(loss, argnums=0)(c_current, w, X_train, y_train)
w -= eta * grad(loss, argnums=1)(c_current, w, X_train, y_train)
history.append(float(loss(c, w, X_train, y_train)))

图 2: 逻辑回归训练历史(作者提供的图片)。
一旦对结果感到满意,我们可以将模型在我们的测试集上进行测试(图 3)。
y_pred = jnp.array(activation(jnp.dot(X_test, w) + c))
y_pred = jnp.where(y_pred > 0.5, 1, 0)
print(classification_report(y_test, y_pred))

图 3: 测试数据分类报告(作者提供的图片)。
结论
如本简要示例所示,JAX 具有非常直观的 API,紧密遵循 Numpy 的约定,同时使得在 CPU/GPU/TPU 上使用相同的代码成为可能。利用这些构建块,可以创建高可定制的深度学习模型,这些模型在设计时已优化以提高性能。
联系方式
如果你想保持更新我的最新文章和项目,请在 Medium 上关注我并订阅我的邮件列表。以下是我的一些联系方式:
参考文献
[1] “移动价格分类”(ABHISHEK SHARMA)。访问地址:thecleverprogrammer.com/2021/03/05/mobile-price-classification-with-machine-learning/(MIT 许可证: github.com/alifrmf/Mobile-Price-Prediction-Classification-Analysis/tree/main)
开始使用 LangChain:构建 LLM 驱动应用程序的初学者指南
使用大型语言模型在 Python 中构建任何东西的 LangChain 教程
·发表于Towards Data Science ·11 分钟阅读·2023 年 4 月 25 日
--

“随机的鹦鹉对其他的说了什么?”(作者绘制的图像)
自 ChatGPT 发布以来,大型语言模型(LLMs)已经获得了极大的关注。尽管你可能没有足够的资金和计算资源在地下室从头训练一个 LLM,但你仍然可以使用预训练的 LLM 来构建一些很酷的东西,比如:
LLM 正在改变我们构建 AI 驱动产品的方式
通过其奇特的 API 和提示工程,LLM 正在改变我们构建 AI 驱动产品的方式。这就是为什么新的开发者工具在“LLMOps”的名义下不断涌现的原因。
这些新工具之一是LangChain。
[## GitHub - hwchase17/langchain: ⚡ 通过组合性构建 LLM 应用 ⚡
⚡ 通过组合性构建 LLM 应用 ⚡ 生产支持:当你将 LangChains 迁移到…
github.com](https://github.com/hwchase17/langchain?source=post_page-----95fc8898732c--------------------------------)
LangChain 是什么?
LangChain 是一个框架,旨在通过提供以下内容来帮助你更轻松地构建 LLM 驱动的应用程序:
-
一种通用接口,用于各种不同的基础模型(见 Models),
-
一个帮助你管理提示的框架(见 Prompts),以及
-
一个中央接口,用于长期记忆(见 Memory)、外部数据(见 Indexes)、其他 LLMs(见 Chains)以及其他 LLM 无法处理的任务(例如计算或搜索)(见 Agents)。
这是一个开源项目(GitHub 仓库),由 哈里森·查斯 创建。
由于 LangChain 具有许多不同的功能,因此一开始可能很难理解它的作用。这就是为什么我们将在本文中介绍(目前的)六个关键模块,以便让你更好地理解其功能。
先决条件
要跟随本教程,你需要安装 langchain Python 包,并准备好所有相关的 API 密钥。
安装 LangChain
在安装 langchain 包之前,确保你有 Python 版本 ≥ 3.8.1 且 <4.0。
要安装 langchain Python 包,你可以使用 pip 进行安装。
pip install langchain
在本教程中,我们使用的是版本 0.0.147。由于 GitHub 仓库 非常活跃,请确保你使用的是最新版本。
一旦你完成所有设置,导入 langchain Python 包。
import langchain
API 密钥
使用 LLM 构建应用程序需要一些服务的 API 密钥,并且一些 API 可能会产生费用。
LLM 提供者(必需) — 你首先需要一个用于所需 LLM 提供者的 API 密钥。我们目前正经历“AI 的 Linux 时刻”,在这一时刻,开发者必须在性能和成本之间权衡,选择专有或开源基础模型。

LLM 提供者:专有和开源基础模型(图像由作者提供,灵感来源于 Fiddler.ai,首次发布在 W&B 的博客)
专有模型 是由拥有大型专家团队和大额 AI 预算的公司拥有的封闭源基础模型。它们通常比开源模型大,因此性能更好,但它们的 API 也很昂贵。专有模型提供商的例子包括 OpenAI、co:here、AI21 Labs 或 Anthropic。
大多数可用的 LangChain 教程使用 OpenAI,但请注意 OpenAI API(虽然实验使用不贵,但)并非免费。要获取 OpenAI API 密钥,你需要一个 OpenAI 账户,然后在 API 密钥 下“创建新秘密密钥”。
import os
os.environ["OPENAI_API_KEY"] = ... # insert your API_TOKEN here
开源模型 通常是比专有模型小的模型,能力较低,但它们比专有模型更具成本效益。开源模型的例子包括:
许多开源模型被组织和托管在 Hugging Face 作为一个社区中心。要获取 Hugging Face API 密钥,你需要一个 Hugging Face 账户,并在 访问令牌 下创建一个“新令牌”。
import os
os.environ["HUGGINGFACEHUB_API_TOKEN"] = ... # insert your API_TOKEN here
你可以免费使用 Hugging Face 来访问开源 LLM,但你将被限制使用性能较低的小型 LLM。
个人备注—— 我们诚实地说一句:当然,你可以在这里尝试开源基础模型。我试图只使用在 Hugging Face 上托管的开源模型(google/flan-t5-xl 和 sentence-transformers/all-MiniLM-L6-v2)来制作这个教程,并使用普通账户进行操作。大多数示例都能正常工作,但也有一些示例很难让它们正常运行。最后,我决定购买一个 OpenAI 的付费账户,因为 LangChain 的大多数示例似乎都为 OpenAI 的 API 进行了优化。总的来说,制作这个教程的几个实验花费了我大约 1 美元。
向量数据库(可选) —— 如果你想使用特定的向量数据库,如 Pinecone、Weaviate 或 Milvus,你需要注册并获取一个 API 密钥,并查看它们的定价。在这个教程中,我们使用的是 Faiss,它不需要注册。
工具(可选) — 根据你希望 LLM 与之交互的工具,如 OpenWeatherMap 或 SerpAPI,你可能需要注册以获得 API 密钥,并查看它们的定价。在本教程中,我们仅使用不需要 API 密钥的工具。
你可以用 LangChain 做什么?
该包提供了与许多基础模型的通用接口,支持提示管理,并作为其他组件的中央接口,如提示模板、其他 LLM、外部数据以及通过代理访问的其他工具。
在撰写本文时,LangChain(版本 0.0.147)涵盖了六个模块:
-
模型:从不同的 LLM 和嵌入模型中选择
-
提示:管理 LLM 输入
-
链:将 LLM 与其他组件结合
-
索引:访问外部数据
-
记忆:记住先前的对话
-
代理:访问其他工具
以下部分中的代码示例是从LangChain 文档中复制和修改的。
模型:从不同的 LLM 和嵌入模型中选择
目前,许多不同的 LLM 正在出现。LangChain 提供了与各种模型的集成,并为所有模型提供了简化的接口。
LangChain 区分三种类型的模型,它们在输入和输出上有所不同:
- LLMs 将一个字符串作为输入(提示),并输出一个字符串(完成)。
# Proprietary LLM from e.g. OpenAI
# pip install openai
from langchain.llms import OpenAI
llm = OpenAI(model_name="text-davinci-003")
# Alternatively, open-source LLM hosted on Hugging Face
# pip install huggingface_hub
from langchain import HuggingFaceHub
llm = HuggingFaceHub(repo_id = "google/flan-t5-xl")
# The LLM takes a prompt as an input and outputs a completion
prompt = "Alice has a parrot. What animal is Alice's pet?"
completion = llm(prompt)

LLM 模型(图片由作者提供)
-
聊天模型类似于 LLM。它们将聊天消息列表作为输入,并返回一条聊天消息。
-
文本嵌入模型将文本输入并返回一个浮点数列表(嵌入),这些嵌入是输入文本的数值表示。嵌入帮助从文本中提取信息。这些信息可以在后续使用,例如,计算文本之间的相似性(例如电影摘要)。
# Proprietary text embedding model from e.g. OpenAI
# pip install tiktoken
from langchain.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
# Alternatively, open-source text embedding model hosted on Hugging Face
# pip install sentence_transformers
from langchain.embeddings import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(model_name = "sentence-transformers/all-MiniLM-L6-v2")
# The embeddings model takes a text as an input and outputs a list of floats
text = "Alice has a parrot. What animal is Alice's pet?"
text_embedding = embeddings.embed_query(text)

文本嵌入模型(图片由作者提供)
提示:管理 LLM 输入
LLM 的 API 很奇怪。虽然用自然语言输入提示应该是直观的,但需要对提示进行相当多的调整,才能从 LLM 中获得期望的输出。这个过程被称为提示工程。
一旦你拥有了一个好的提示,你可能希望将其用作其他用途的模板。因此,LangChain 为你提供了所谓的PromptTemplates,这些模板帮助你从多个组件构建提示。
from langchain import PromptTemplate
template = "What is a good name for a company that makes {product}?"
prompt = PromptTemplate(
input_variables=["product"],
template=template,
)
prompt.format(product="colorful socks")
上述提示可以视为零-shot 问题设置,你希望 LLM 在足够相关的数据上进行训练,从而提供令人满意的响应。
提高 LLM 输出的另一个技巧是添加几个示例到提示中,将其设为少量示例问题设置。
from langchain import PromptTemplate, FewShotPromptTemplate
examples = [
{"word": "happy", "antonym": "sad"},
{"word": "tall", "antonym": "short"},
]
example_template = """
Word: {word}
Antonym: {antonym}\n
"""
example_prompt = PromptTemplate(
input_variables=["word", "antonym"],
template=example_template,
)
few_shot_prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
prefix="Give the antonym of every input",
suffix="Word: {input}\nAntonym:",
input_variables=["input"],
example_separator="\n",
)
few_shot_prompt.format(input="big")
上述代码将生成一个提示模板,并根据提供的示例和输入组成以下提示:
Give the antonym of every input
Word: happy
Antonym: sad
Word: tall
Antonym: short
Word: big
Antonym:
链:将 LLM 与其他组件结合
在 LangChain 中,链式操作简单地描述了将 LLM 与其他组件结合以创建应用程序的过程。一些示例包括:
-
将 LLM 与提示模板结合(参见本节)
-
通过将第一个 LLM 的输出作为第二个 LLM 的输入来依次结合多个 LLM(参见本节)
-
将 LLM 与外部数据结合,例如,用于回答问题(参见 索引)
-
将 LLM 与长期记忆结合,例如,用于聊天记录(参见 记忆)
在上一节中,我们创建了一个提示模板。当我们想将其与 LLM 一起使用时,可以按如下方式使用 LLMChain:
from langchain.chains import LLMChain
chain = LLMChain(llm = llm,
prompt = prompt)
# Run the chain only specifying the input variable.
chain.run("colorful socks")
如果我们想将第一个 LLM 的输出作为第二个 LLM 的输入,可以使用 SimpleSequentialChain:
from langchain.chains import LLMChain, SimpleSequentialChain
# Define the first chain as in the previous code example
# ...
# Create a second chain with a prompt template and an LLM
second_prompt = PromptTemplate(
input_variables=["company_name"],
template="Write a catchphrase for the following company: {company_name}",
)
chain_two = LLMChain(llm=llm, prompt=second_prompt)
# Combine the first and the second chain
overall_chain = SimpleSequentialChain(chains=[chain, chain_two], verbose=True)
# Run the chain specifying only the input variable for the first chain.
catchphrase = overall_chain.run("colorful socks")

使用 PromptTemplates 和 LLMs 在 LangChain 中的 SimpleSequentialChain 的输出(作者截图)
索引:访问外部数据
LLM 的一个限制是缺乏上下文信息(例如,访问某些特定文档或电子邮件)。你可以通过让 LLM 访问特定的外部数据来解决这个问题。
为此,你首先需要用文档加载器加载外部数据。LangChain 提供了各种加载器,用于不同类型的文档,从 PDFs 和电子邮件到网站和 YouTube 视频。
让我们从 YouTube 视频中加载一些外部数据。如果你想 加载一个大型文本文档并使用 文本分割器 将其拆分,可以参考官方文档。
# pip install youtube-transcript-api
# pip install pytube
from langchain.document_loaders import YoutubeLoader
loader = YoutubeLoader.from_youtube_url("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
documents = loader.load()
现在你已经准备好将外部数据作为 documents,你可以使用 文本嵌入模型(参见 模型)在向量数据库中进行索引——一个 VectorStore。流行的向量数据库包括 Pinecone、Weaviate 和 Milvus。在本文中,我们使用 Faiss,因为它不需要 API 密钥。
# pip install faiss-cpu
from langchain.vectorstores import FAISS
# create the vectorestore to use as the index
db = FAISS.from_documents(documents, embeddings)
你的文档(在这种情况下是视频)现在作为嵌入存储在向量存储中。
现在,你可以使用这些外部数据做各种事情。让我们用它来进行一个信息检索器的问答任务:
from langchain.chains import RetrievalQA
retriever = db.as_retriever()
qa = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True)
query = "What am I never going to do?"
result = qa({"query": query})
print(result['result'])

检索问答的输出(作者截图)
等一下——你刚刚被恶搞了?是的,你确实被恶搞了。
记忆:记住先前的对话
对于聊天机器人等应用程序来说,能够记住先前的对话至关重要。但默认情况下,LLM 不具有任何长期记忆,除非你输入聊天记录。

带有和不带有对话记忆的聊天(图片由 ifaketextmessage.com 制作,灵感来自 Pinecone)
LangChain 通过提供几种处理聊天记录的不同选项来解决这个问题:
-
保留所有对话,
-
保留最近的 k 个对话,
-
总结对话。
在这个示例中,我们将使用ConversationChain来为这个应用程序提供对话记忆。
from langchain import ConversationChain
conversation = ConversationChain(llm=llm, verbose=True)
conversation.predict(input="Alice has a parrot.")
conversation.predict(input="Bob has two cats.")
conversation.predict(input="How many pets do Alice and Bob have?")
这将导致上图中右侧的对话。如果没有ConversationChain来保持对话记忆,对话将会像上图中左侧的那样。
代理:访问其他工具
尽管 LLM 相当强大,但它们有一些局限性:缺乏上下文信息(例如,无法访问训练数据中未包含的特定知识),可能会很快过时(例如,GPT-4 是在 2021 年 9 月之前的数据上训练的),并且在数学方面表现不佳。
LLM 在数学方面表现不好
因为 LLM 在无法自行完成任务时可能会产生幻觉,我们需要提供额外的工具,如搜索(例如,Google 搜索)、计算器(例如,Python REPL 或 Wolfram Alpha),以及查找(例如,维基百科)。
此外,我们需要代理程序来根据 LLM 的输出决定使用哪些工具来完成任务。
请注意,一些 LLM 例如 [*google/flan-t5-xl*](https://github.com/hwchase17/langchain/issues/1358) 可能不适用于以下示例,因为它们不符合 [*conversational-react-description*](https://github.com/hwchase17/langchain/issues/1358) 模板。 对我来说,这是我在 OpenAI 上设置付费账户并切换到 OpenAI API 的时刻。
以下是一个示例,其中代理首先用维基百科查找巴拉克·奥巴马的出生日期,然后用计算器计算他在 2022 年的年龄。
# pip install wikipedia
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
tools = load_tools(["wikipedia", "llm-math"], llm=llm)
agent = initialize_agent(tools,
llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True)
agent.run("When was Barack Obama born? How old was he in 2022?")

LLM 代理的输出(作者截图)
总结
就在几个月前,我们所有人(或者至少大多数人)都对 ChatGPT 的能力印象深刻。现在,像 LangChain 这样的新开发工具使我们能够在几小时内在笔记本电脑上构建同样令人印象深刻的原型——这些确实是令人兴奋的时代!
LangChain 是一个开源的 Python 库,使任何能够编写代码的人都可以构建 LLM 驱动的应用程序。该包提供了对许多基础模型的通用接口,支持提示管理,并作为其他组件(如提示模板、其他 LLM、外部数据和通过代理的其他工具)的中央接口——在撰写时。
图书馆提供的功能远超本文提到的。随着发展速度的加快,本文在一个月内也可能会过时。
在撰写本文时,我注意到图书馆和文档集中在 OpenAI 的 API 上。虽然许多示例使用的是开源基础模型[google/flan-t5-xl](https://github.com/hwchase17/langchain/issues/1358),但我中途切换到了 OpenAI API。尽管不是免费的,但在本文中使用 OpenAI API 的实验仅花费了大约$1。
享受这个故事了吗?
免费订阅 以便在我发布新故事时获得通知。
[## 每当 Leonie Monigatti 发布新内容时,你将收到一封电子邮件。
每当 Leonie Monigatti 发布新内容时,你会收到一封电子邮件。通过注册,你将创建一个 Medium 账户,如果你还没有的话……
medium.com](https://medium.com/@iamleonie/subscribe?source=post_page-----95fc8898732c--------------------------------)
在 LinkedIn,Twitter,以及 Kaggle上找到我!
参考资料
[1] Harrison Chase (2023). LangChain 文档(访问日期:2023 年 4 月 23 日)
多模态入门
原文:
towardsdatascience.com/getting-started-with-multimodality-eab5f6453080

图片由 Microsoft Designer 创建
理解大型多模态模型的视觉能力
·发表于Towards Data Science ·阅读时间 9 分钟·2023 年 12 月 27 日
--
生成 AI 的最新进展使得大型多模态模型(LMMs)的发展成为可能,这些模型能够处理和生成不同类型的数据,例如文本、图像、音频和视频。
LMMs 与“标准”大型语言模型(LLMs)共享一般化和适应能力,这些能力是大型基础模型所特有的。然而,LMMs 能够处理超越文本的数据,包括图像、音频和视频。
大型多模态模型的一个最突出的例子是 GPT4V(ision),它是生成预训练变换器(GPT)系列的最新迭代。GPT-4 可以执行各种需要自然语言理解和计算机视觉的任务,例如图像字幕生成、视觉问答、文本到图像合成以及图像到文本翻译。
GPT4V(以及其更新版本 GPT-4-turbo vision)展现了非凡的能力,包括:
- 数值问题的数学推理:

图片由作者提供
- 从草图生成代码:

图片由作者提供
- OCR:

图片由作者提供
- 艺术遗产的描述:

图片由作者提供
以及许多其他功能。
在这篇文章中,我们将重点关注 LMMs 的视觉能力,以及它们如何与标准计算机视觉算法不同。
什么是计算机视觉
计算机视觉(CV)是人工智能(AI)领域的一部分,它使计算机和系统能够从数字图像、视频和其他视觉输入中获取有意义的信息。它使用机器学习和神经网络来教会计算机看、观察和理解。其目标是模仿人类视觉系统,自动化人类视觉系统能够完成的任务。例如,计算机视觉可以用于识别图像中的物体、检测事件、估计 3D 姿势和修复图像。
由于计算机视觉依赖于神经网络,这些网络不过是数学模型,我们需要将图像转换为数值输入,以便它们可以被我们的模型处理。
图像是像素的多维数组。对于灰度图像,这是一个 2D 数组,每个像素对应于不同的灰色阴影。对于彩色图像,它是一个 3D 数组(高度、宽度和颜色通道),每个颜色通道(红色、绿色、蓝色 — RGD)都有一个单独的 2D 像素强度数组。
每个像素强度是一个数值。最常见的情况是这个值范围从 0(黑色)到 255(白色)。这些像素的组合构成了我们视觉上感知到的图像。
以下插图展示了一个 RGD 图像的例子(注意:像素值仅作为示例,并不代表真实值)。

图片由作者提供
现在的问题是如何预处理这些多维数组,使得计算机视觉模型能够理解它们并尽可能保留信息。
在多模态模型出现之前,计算机视觉通常依赖于为特定任务设计和训练的专用模型,如目标检测、人脸识别、场景分割和光学字符识别。在这个领域中,卷积神经网络是最受欢迎的模型之一。
卷积神经网络
卷积神经网络不过是展示了至少一层数学操作卷积的神经网络。
卷积是两个矩阵之间的逐元素乘法(分别代表一个专门用于检测特定特征的滤波器和处理图像的同样大小的区域),并对输出结果进行最终的求和。

图片由作者提供
让我们将以下插图作为一个例子。

图片由作者提供
在图中,我检查了一个简单的任务:我们有一个由四个字母组成的字母表——A、B、C 和 D——并且我们的算法被要求识别输入字母(在我们的例子中是‘C’)。输入图像通过一个专门的滤波器(例如,用于角点检测)进行处理,然后降维、展平,并通过全连接层处理。
视觉转换器
Vision Transformer(原始论文这里)作为 CNN 的第一个替代方案被提出,它与 LLM 共享核心架构(编码器/解码器)。
与传统的转换器一样,在这种情况下核心机制也是注意力(原始论文这里),它允许模型在进行预测时选择性地关注输入序列的不同部分。这个概念通过教模型关注输入数据的某些部分并忽略其他部分,从而更好地解决当前任务。
变革性的注意力机制在转换器中的一个方面是,它摆脱了递归(可以在这里了解更多关于递归神经网络(RNN)的内容)和卷积,这些都是以前模型广泛依赖的。转换器是第一个完全依赖注意力来计算输入和输出表示的模型,而不使用序列对齐的 RNN 或卷积。这使得转换器能够捕捉句子中单词之间更广泛的关系,并学习输入的更微妙的表示。
在视觉转换器中,图像的处理方式与 CNN 不同。实际上,一张图像被划分为所谓的补丁,通常为 16x16 像素大小。然后,每个补丁被展平为 1D 向量并进行标记,就像我们在标准转换器(如 GPT-3.5)中处理文本数据一样。

作者提供的图像
现在,这些经过标记的补丁将被输入到视觉转换器模型中,并通过线性投影层进一步转换为低维向量,这样我们可以在保留信息的同时减少内存和计算能力的需求。此外,与标准转换器一样,每个标记都通过位置嵌入层与其在图像整体上下文中的位置指示器相关联。

作者提供的图像
最终,位置嵌入的标记被传递到模型的主要块,即转换器编码器。下面你可以看到一个视觉转换器的插图(在这个案例中,场景是分类任务):

来源: arxiv.org/pdf/2010.11929v2.pdf
如你所见,转换器编码器展示了上述注意力机制。
总结
其思想是将图像的嵌入投影到与语言相同的潜在空间中,这样,当给定由图像和文本组成的人类输入时,模型能够从覆盖图像和文本的嵌入空间中获取相关的上下文。
OpenAI 引入的第一个图像和文本模型是 CLIP(对比语言-图像预训练)。
CLIP
CLIP是 OpenAI 开发的一个模型,旨在同时理解图像和文本。它就像是连接图像世界和文字世界的桥梁。
想象一下你有一堆图像和句子,你想将每张图像与最能描述它的句子匹配。这就是 CLIP 的核心功能。它被训练以理解哪些图像和句子彼此相似。
CLIP 的酷炫之处在于它不需要专门针对某个任务进行训练就能表现良好。例如,如果你有一组 CLIP 从未见过的新图像和句子(即所谓的零样本方法),它仍然可以很好地将它们匹配起来。这是因为 CLIP 从大量数据中学习了对图像和文本的普遍理解。

尽管 CLIP 仍然是一个预测模型,但最先进的 LMMs 旨在作为 AI 助手与人类互动。换句话说,它们是指令模型。
LLaVA
一个很好的助手 LMM 的例子是开源模型LLaVA (Large Language and Vision Assistant),它结合了前面提到的 CLIP 用于图像编码和基础 LMM Vicuna,用于指令理解。
LLaVA 的思想是拥有一个线性层,将 CLIP 生成的图像特征连接到语言模型 Vicuna 的词嵌入空间中。

为此,研究人员引入了一个可训练的投影矩阵 W,它将图像特征转换为与语言模型处理的词嵌入具有相同维度的嵌入向量。
Kosmos-1
另一个例子是 Kosmos-1,由微软研究院在这篇论文中介绍。该模型的理念是使用一个变压器解码器以统一的方式感知通用模态:输入被展平为 1D 令牌向量,并用特殊的开始和结束序列标记(文本如text,图像如image)标记。一旦标记化,输入通过嵌入模块(特定于数据格式)进行编码,然后输入解码器。
在他们的论文中,作者描述了模型的训练过程,其中包括以下数据语料库:
-
单模态文本语料库 → 用于表示学习(即,产生有意义的语言表示,保持其潜在语义结构)和一般语言任务,例如遵循指令、上下文学习等。
-
图像-文本对 → 作为模型将图像与其描述关联的桥梁
-
交错的图像-文本数据 → 需要进一步将一般模态的感知与语言模型对齐,并提高模型的少样本能力。
下面的图片展示了一些 Kosmos-1 的能力示例:

来源: 2302.14045.pdf (arxiv.org)
总体而言,创建一个图像和词汇的共同嵌入空间的趋势,使模型能够“感知”这两种数据格式——文本和图像——正在为强大的大型多模态模型铺平道路。此外,像 CLIP 这样的预训练模型被广泛用于生成图像表示,然后可以进一步转换为词嵌入空间。
超越视觉
一般来说,多模态模型的主要思想是创建不同模态中给定概念的一致表示。在前一部分中,我们看到如何使用视觉变换器将图像嵌入到潜在空间中的低维向量中。同样,我们可以为每种模态创建编码器,并使用目标函数来鼓励模型为相似的数据对生成相似的嵌入。
比如,我们可以考虑模型 MACAW-LLM,这是一个多模态语言模型,能够处理图像、视频、音频和文本数据,基于 CLIP(图像和视频)、Whisper(音频)和LLaMA(文本)的基础构建。

来源: lyuchenyang/Macaw-LLM: Macaw-LLM: 多模态语言建模,集成图像、视频、音频和文本 (github.com)
如你所见,不同的嵌入模块被用来生成“共享”的嵌入空间,该空间与 LLM 使用的词嵌入空间(在本例中为 Meta AI 的 LLaMA)对齐。
结论
多模态正在为新一波应用和用例铺平道路。这也是实现人工通用智能(AGI)概念的进一步里程碑,因为它使 AI 系统越来越容易像人类一样“感知”。
不用说,大型多模态模型在伦理考量方面带来了更大的责任:偏见、歧视、隐私侵犯以及许多其他风险都是 LMM 研究的核心,现在比以往任何时候都更加需要关注人类对齐。
参考文献
-
medium.com/analytics-vidhya/recurrent-neural-networks-97f3b034e70 -
lyuchenyang/Macaw-LLM:Macaw-LLM:图像、视频、音频和文本整合的多模态语言建模 (github.com)
开始使用 NumPy 和 OpenCV 进行计算机视觉 (CV-01)
原文:
towardsdatascience.com/getting-started-with-numpy-and-opencv-for-computer-vision-555f88536f68
开始使用 Python 进行计算机视觉编程
·发表于 Towards Data Science ·8 分钟阅读·2023 年 3 月 15 日
--

Dan Smedley 通过 Unsplash 提供的照片
动机
我们人类通过视觉系统感知环境和周围事物。人眼、大脑和四肢协同工作以感知环境并做出相应的动作。一个智能系统能够执行那些如果由人类完成则需要某种程度智力的任务。因此,为了执行智能任务,人工视觉系统是计算机的重要组成部分。通常,使用相机和图像来收集完成工作的所需信息。计算机视觉和图像处理技术帮助我们完成类似人类的任务,如图像识别、对象跟踪等。
在计算机视觉中,相机像人眼一样捕捉图像,处理器像大脑一样处理捕捉到的图像并生成重要结果。但人类和计算机之间有一个基本的区别。人脑自动工作,智力是与生俱来的。而计算机没有智力,除非有人的指令(程序)。计算机视觉就是提供适当的指令,使计算机能够与人类视觉系统兼容。然而,其能力是有限的。
*在接下来的部分中,我们将讨论图像如何形成及如何使用 Python 进行操作的基本概念。*
目录
*点击目录直接跳转到相应章节*
-
**图像如何形成和显示** -
**计算机如何在内存中存储图像?** -
**灰度图像和彩色图像** -
**NumPy 基础以处理图像** -
**OpenCV 基础** -
**玩转 NumPy**
图像的形成与显示
图像只不过是具有不同颜色强度的像素组合。‘像素’和‘颜色强度’这两个术语可能对你来说是陌生的。不要担心。阅读完文章后,一切都会变得清晰明了。
像素是数字图像的最小单位/元素。详细信息见下图。

作者提供的图像
显示屏由像素构成。在上图中,有 25 列和 25 行。每个小方块被视为一个像素。这个设置可以容纳 625 个像素。它表示一个具有 625 个像素的显示屏。如果我们用不同的颜色强度(亮度)来照亮这些像素,它将形成一个数字图像。
计算机如何将图像存储在内存中?
如果我们仔细观察图像,可以将其与 2D 矩阵进行比较。矩阵有行和列,其元素可以通过索引访问。矩阵结构类似于数组。计算机将图像存储在计算机内存的数组中。
每个数组元素保存强度值。通常,强度值范围从**0 到 255**。为了演示目的,我提供了图像的数组表示。

灰度图像的示例数组表示(作者提供的图像)
灰度图像与彩色图像
灰度图像是一种黑白图像。它由一种颜色形成。接近 0 的像素值代表黑暗,强度值越高,图像就越亮。最高值是 255,代表白色。一个 2D 数组足以存储灰度图像,正如最后的图示所示。
彩色图像不能仅由一种颜色形成;可能有成千上万种颜色组合。主要有三种基本颜色通道**红色 (R)、绿色 (G) 和蓝色 (B)**。每个颜色通道存储在一个2D 数组中,并保存其强度值,最终图像是这三个颜色通道的组合。

RGB 颜色通道(作者提供的图像)
这种颜色模型具有 (256 x 256 x 256) = 16,777,216 种可能的颜色组合。 [***你可以在这里可视化这些组合。***](https://csunplugged.jp/csfg/data/interactives/rgb-mixer/index.html)
但在计算机内存中,图像的存储方式有所不同。

存储在计算机内存中的图像(作者提供的图像)
计算机不知道 RGB 通道。它知道的是强度值。红色通道以高强度存储,绿色和蓝色通道分别以中等和低强度值存储。
NumPy 基础知识以便与 Python 一起使用
NumPy 是一个用于科学计算的基础 Python 包。它主要作为数组对象工作,但它的操作不限于数组。然而,该库可以处理各种数字和逻辑操作[1]。你可以在[***这里***](https://numpy.org/doc/stable/user/absolute_beginners.html)找到 NumPy 的官方文档。
让我们开始我们的旅程。首先是第一步。
- 导入 NumPy 库。
现在是使用 NumPy 的时候了。正如我们所知,NumPy 与数组一起工作。所以,让我们尝试创建第一个全零的 2D 数组。
- 创建 NumPy 数组
就这么简单。我们也可以创建一个全为 1 的 NumPy 数组,方法如下。
有趣的是,NumPy 还提供了一种方法来用任意值填充数组。简单的语法array.fill(value)可以完成这项工作。
数组‘b’中所有的元素现在都被填充为3。
- 随机数生成中的种子功能
只需看看以下的代码示例。
在第一个代码单元格中,我们使用了**np.random.seed(seed_value)**,但其他两个代码单元格中没有使用任何种子。在有种子和无种子的随机数生成之间有一个主要区别。在随机种子的情况下,生成的随机数对于特定的种子值保持不变。另一方面,没有种子值的情况下,每次执行时随机数都会变化。
- 使用 NumPy 的基本操作(最大值、最小值、均值、重塑等)
NumPy 通过提供大量函数来执行数学运算,使我们的生活更轻松。array_name.min(), array_name.max(), array_name.mean()语法帮助我们找到数组的最小值、最大值和均值。编码示例 —
最小值和最大值的索引可以通过语法array_name.argmax(), array_name.argmin()提取。示例 —
数组重塑是NumPy.array_name.reshape(row_no, column_no)的一个重要操作,用于重塑数组。在重塑数组时,我们必须注意重塑前后数组元素的数量。在两种情况下,总元素数必须相同。
- 数组索引和切片
每个数组元素可以通过其列和行编号进行访问。我们来生成另一个具有 10 行和列的数组。
假设我们想找出数组的第一个值。可以通过传递行和列索引(0 , 0)来提取。
可以使用语法array_name[row_no,:], array_name[:,column_no]来切片特定的行和列值。
让我们尝试切片数组的中心元素。
OpenCV 基础
OpenCV是由英特尔开发的开源 Python 计算机视觉库[2]。我将讨论 OpenCV 的一些用法,尽管它的范围很广。你可以在[**这里**](https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html)找到官方文档。
我使用了以下图像进行演示。

图像由jackouille21提供,来源于Pixabay
- 导入 OpenCV 和 Matplotlib 库
Matplotlib 是一个可视化库。它有助于可视化图像。
- 使用 OpenCV 加载图像并用 matplotlib 可视化
我们已经用OpenCV读取了图像,并用matplotlib库对其进行了可视化。颜色发生了变化,因为OpenCV以BGR格式读取图像,而matplotlib期望图像为RGB格式。因此,我们需要将图像从BGR 转换为 RGB。
- 将图像从 BGR 格式转换为 RGB 格式
现在,图像看起来还不错。
- 将图像转换为灰度
我们可以轻松地使用cv2.COLOR_BGR2GRAY将图像从 BGR 转换为灰度图像,操作如下。
尽管图像已经转换为灰度图像,但上述图像并不完全灰色。它已通过 matplotlib 进行了可视化。默认情况下,matplotlib 使用的是不同于灰度的颜色映射。为了正确可视化它,我们需要在 matplotlib 中指定灰度颜色映射。让我们来做一下。
- 旋转图像
旋转也是一个简单的任务,使用OpenCV。cv2.rotate()函数可以帮助我们完成这项工作。顺时针和逆时针 90 度以及 180 度旋转如下所示。
- 调整图像大小
我们可以通过将宽度和高度的像素值传递给cv2.resize()函数来调整图像大小。
- 在图像上绘制
有时我们需要在现有图像上绘制。例如,我们需要在图像对象上绘制一个边界框以进行识别。让我们在花朵上绘制一个矩形。cv2.rectangle()函数帮助我们在其上绘制。它需要一些参数,比如我们绘制矩形的图像、左上角坐标点 *(pt1)* 和右下角坐标点 *(pt2)*,以及边界线的厚度。下面是一个编码示例。
还有其他绘图函数[***cv.line()***](https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html#ga7078a9fae8c7e7d13d24dac2520ae4a2)*,* [***cv.circle()***](https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html#gaf10604b069374903dbd0f0488cb43670) *,* [***cv.ellipse()***](https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html#ga28b2267d35786f5f890ca167236cbc69)*,* [***cv.putText()***](https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html#ga5126f47f883d730f633d74f07456c576)*,* 等。完整的官方文档可以在这里`[3]中找到。
玩转 NumPy
我们将改变图像的强度值。我会尽量保持简单。因此,考虑之前显示的灰度图像。找出图像的形状。
它显示这是一个2D 数组,大小为**1200 x 1920**。在基本的 NumPy 操作中,我们学习了如何切片数组。
使用该概念,我们提取了灰度图像数组片段**[400:800, 750:1350]**并将强度值替换为**255**。最后,我们对其进行可视化,得到了上述图像。
结论
计算机视觉是现代计算机科学技术中一个有前途的领域。我总是强调任何领域的基础知识。我仅讨论了计算机视觉的基本知识,并展示了一些实践编码。这些概念非常简单,但对于计算机视觉的初学者可能起到重要作用。
***完整*** [***代码***](https://github.com/Zubair063/ML_articles/blob/main/Getting%20Started%20with%20NumPy%20and%20OpenCV/Numpy_OpenCV.ipynb) ***可在此处*** [***获取***](https://github.com/Zubair063/ML_articles/tree/main/Getting%20Started%20with%20NumPy%20and%20OpenCV) ***。***
这是计算机视觉系列的第一篇文章。请保持关注,以阅读即将发布的文章。
[注:讲师 Jose Portilla 的课程帮助我获取知识。]
参考文献
您可以阅读下面提供的其他系列文章。
数据科学概述:标准指南
[towardsdatascience.com [## 数据科学的终极数据可视化指南
数据科学概述:标准指南
开始使用 Weaviate:初学者的向量数据库搜索指南
如何在 Python 中使用 OpenAI 和 Weaviate 进行语义搜索、问答和生成搜索
·
关注 发表在Towards Data Science ·12 分钟阅读·2023 年 7 月 18 日
--
如果你看到这篇文章,我假设你已经开始尝试构建使用大语言模型(LLM)的应用,并遇到了“向量数据库”这一术语。
伴随着构建 LLM 应用的工具快速增长,诸如LangChain和LlamaIndex等工具正变得越来越受欢迎。
在最近的一篇文章中,我描述了如何开始使用 LangChain,在这篇文章中,我想通过玩弄Weaviate来继续探索 LLM 工具的景观。
什么是 Weaviate?
Weaviate 是一个开源向量数据库。它使你能够存储数据对象和向量嵌入,并根据相似性度量查询它们。
## GitHub - weaviate/weaviate: Weaviate 是一个开源向量数据库,既存储对象也存储向量…
Weaviate 是一个开源向量数据库,存储对象和向量,允许进行向量搜索的结合…
向量数据库自从 LLM 媒体关注度上升以来,受到了大量关注。可能最流行的向量数据库在 LLM 上下文中的应用是“为 LLM 提供长期记忆”。
如果你需要重新了解向量数据库的概念,你可能想查看我之前的文章:
从新手到专家:解密不同背景下的向量数据库
towardsdatascience.com
在本教程中,我们将演示如何用数据集的嵌入填充 Weaviate 向量数据库。然后,我们将介绍三种从中检索信息的方法:
-
向量搜索
-
问答系统
-
生成搜索
前提条件
要跟随本教程,你需要具备以下条件:
-
Python 3 环境
-
OpenAI API 密钥(或选择 Hugging Face、Cohere 或 PaLM 的 API 密钥)
关于 API 密钥的说明: 在本教程中,我们将通过一个推理服务(在这种情况下是OpenAI)从文本中生成嵌入。根据你使用的推理服务,请确保检查提供商的定价页面以避免意外费用。例如,使用的阿达模型(版本 2)在撰写时的费用为每 1,000 个令牌 $0.0001,在本教程中推理费用不到 1 美分。
设置
你可以在自己的实例上运行 Weaviate(使用 Docker、Kubernetes 或嵌入式 Weaviate),也可以通过 Weaviate Cloud Services (WCS) 作为托管服务运行。对于本教程,我们将使用 WCS 运行 Weaviate 实例,因为这是推荐的最简单的方法。
如何使用 Weaviate Cloud Services (WCS) 创建集群
要能够使用该服务,你首先需要 注册 WCS。
注册后,你可以通过点击“创建集群”按钮来创建一个新的 Weaviate 集群。

Weaviate Cloud Services 的截图
对于本教程,我们将使用免费的试用计划,该计划为你提供 14 天的沙盒。(你无需添加任何支付信息。相反,沙盒将在试用期结束后过期。但你可以随时创建一个新的免费试用沙盒。)
在“免费沙盒”标签下,进行以下设置:
-
输入集群名称
-
启用身份验证(设置为“是”)

Weaviate Cloud Services 计划的截图
最后,点击“创建”以创建你的沙盒实例。
如何在 Python 中安装 Weaviate
最后,但同样重要的是,使用 pip 将 weaviate-client 添加到你的 Python 环境中
$ pip install weaviate-client
并导入库:
import weaviate
如何通过客户端访问 Weaviate 集群
对于下一步,你将需要以下两项信息来访问你的集群:
-
集群 URL
-
Weaviate API 密钥(在“启用 — 身份验证”下)

Weaviate Cloud Services 沙盒的截图
现在,你可以实例化一个 Weaviate 客户端以访问你的 Weaviate 集群,如下所示。
auth_config = weaviate.AuthApiKey(api_key="YOUR-WEAVIATE-API-KEY") # Replace w/ your Weaviate instance API key
# Instantiate the client
client = weaviate.Client(
url="https://<your-sandbox-name>.weaviate.network", # Replace w/ your Weaviate cluster URL
auth_client_secret=auth_config,
additional_headers={
"X-OpenAI-Api-Key": "YOUR-OPENAI-API-KEY", # Replace with your OpenAI key
}
)
如你所见,我们使用additional_headers下的 OpenAI API 密钥来访问嵌入模型。如果你使用的是不同于 OpenAI 的提供者,请将密钥参数更改为以下之一:X-Cohere-Api-Key,X-HuggingFace-Api-Key 或 X-Palm-Api-Key。
要检查是否一切设置正确,请运行:
client.is_ready()
如果返回True,你已为下一步做好准备。
如何创建和填充 Weaviate 向量数据库
现在,我们准备在 Weaviate 中创建一个向量数据库并用一些数据填充它。
对于本教程,我们将使用 Kaggle 上的 200,000+ Jeopardy 问题 数据集的前 100 行 [1]。
import pandas as pd
df = pd.read_csv("your_file_path.csv", nrows = 100)

Kaggle 上的 200,000+ Jeopardy 问题 数据集的前几行 [1]。
关于令牌数量和相关成本的说明:在以下示例中,我们将为前 100 行嵌入“category”、“question”和“answer”列。根据与 *tiktoken* 库的计算,这将产生大约 3,000 个令牌用于嵌入,这 大约会导致 OpenAI 的 Ada 模型(版本 2)在 2023 年 7 月的推理成本为 $0.0003。
第一步:创建模式
首先,我们需要定义底层数据结构和一些配置:
-
class:这个向量空间中的对象集合将被称为什么? -
properties:对象的属性,包括属性名称和数据类型。在 Pandas Dataframe 类比中,这些就是 DataFrame 中的列。 -
vectorizer:生成嵌入的模型。对于文本对象,通常你会选择一个[text2vec](https://weaviate.io/developers/weaviate/modules/retriever-vectorizer-modules)模块(text2vec-cohere、text2vec-huggingface、text2vec-openai或text2vec-palm),具体选择取决于你使用的提供商。 -
moduleConfig:在这里,你可以定义所用模块的详细信息。例如,向量化器是一个模块,你可以定义使用哪个模型和版本。
class_obj = {
# Class definition
"class": "JeopardyQuestion",
# Property definitions
"properties": [
{
"name": "category",
"dataType": ["text"],
},
{
"name": "question",
"dataType": ["text"],
},
{
"name": "answer",
"dataType": ["text"],
},
],
# Specify a vectorizer
"vectorizer": "text2vec-openai",
# Module settings
"moduleConfig": {
"text2vec-openai": {
"vectorizeClassName": False,
"model": "ada",
"modelVersion": "002",
"type": "text"
},
},
}
在上述模式中,你可以看到我们将创建一个名为"JeopardyQuestion"的类,具有三个文本属性"category"、"question"和"answer"。我们使用的向量化器是 OpenAI 的 Ada 模型(版本 2)。所有属性都将被向量化,但类名("vectorizeClassName" : False)不会。如果你有不希望嵌入的属性,可以指定这一点(见文档)。
一旦定义了模式,你可以使用create_class()方法创建类。
client.schema.create_class(class_obj)
要检查类是否已成功创建,你可以如下查看其模式:
client.schema.get("JeopardyQuestion")
创建的模式如下所示:
{
"class": "JeopardyQuestion",
"invertedIndexConfig": {
"bm25": {
"b": 0.75,
"k1": 1.2
},
"cleanupIntervalSeconds": 60,
"stopwords": {
"additions": null,
"preset": "en",
"removals": null
}
},
"moduleConfig": {
"text2vec-openai": {
"model": "ada",
"modelVersion": "002",
"type": "text",
"vectorizeClassName": false
}
},
"properties": [
{
"dataType": [
"text"
],
"indexFilterable": true,
"indexSearchable": true,
"moduleConfig": {
"text2vec-openai": {
"skip": false,
"vectorizePropertyName": false
}
},
"name": "category",
"tokenization": "word"
},
{
"dataType": [
"text"
],
"indexFilterable": true,
"indexSearchable": true,
"moduleConfig": {
"text2vec-openai": {
"skip": false,
"vectorizePropertyName": false
}
},
"name": "question",
"tokenization": "word"
},
{
"dataType": [
"text"
],
"indexFilterable": true,
"indexSearchable": true,
"moduleConfig": {
"text2vec-openai": {
"skip": false,
"vectorizePropertyName": false
}
},
"name": "answer",
"tokenization": "word"
}
],
"replicationConfig": {
"factor": 1
},
"shardingConfig": {
"virtualPerPhysical": 128,
"desiredCount": 1,
"actualCount": 1,
"desiredVirtualCount": 128,
"actualVirtualCount": 128,
"key": "_id",
"strategy": "hash",
"function": "murmur3"
},
"vectorIndexConfig": {
"skip": false,
"cleanupIntervalSeconds": 300,
"maxConnections": 64,
"efConstruction": 128,
"ef": -1,
"dynamicEfMin": 100,
"dynamicEfMax": 500,
"dynamicEfFactor": 8,
"vectorCacheMaxObjects": 1000000000000,
"flatSearchCutoff": 40000,
"distance": "cosine",
"pq": {
"enabled": false,
"bitCompression": false,
"segments": 0,
"centroids": 256,
"encoder": {
"type": "kmeans",
"distribution": "log-normal"
}
}
},
"vectorIndexType": "hnsw",
"vectorizer": "text2vec-openai"
}
步骤 2:将数据导入 Weaviate
在此阶段,向量数据库已有模式但仍为空。因此,让我们用数据集填充它。这一过程也称为“upserting”。
我们将以每批 200 条的方式进行数据的 upsert。如果你注意到了,你会知道这里不需要这样做,因为我们只有 100 行数据。但一旦你准备好进行更大规模的数据 upsert,你会希望以批量方式进行。这就是为什么我在这里留下批处理代码:
from weaviate.util import generate_uuid5
with client.batch(
batch_size=200, # Specify batch size
num_workers=2, # Parallelize the process
) as batch:
for _, row in df.iterrows():
question_object = {
"category": row.category,
"question": row.question,
"answer": row.answer,
}
batch.add_data_object(
question_object,
class_name="JeopardyQuestion",
uuid=generate_uuid5(question_object)
)
尽管 Weaviate 会自动生成一个通用唯一标识符(uuid),我们将通过question_object的generate_uuid5()函数手动生成uuid,以避免导入重复的项目。
进行完整性检查时,你可以使用以下代码片段查看导入对象的数量:
client.query.aggregate("JeopardyQuestion").with_meta_count().do()
{'data': {'Aggregate': {'JeopardyQuestion': [{'meta': {'count': 100}}]}}}
如何查询 Weaviate 向量数据库
你最常做的操作是检索对象。要检索对象,你可以使用get()函数查询 Weaviate 向量数据库:
client.query.get(
<Class>,
[<properties>]
).<arguments>.do()
-
Class:指定要检索的对象类的名称。这里是"JeopardyQuestion"。 -
properties:指定要检索的对象属性。这里包括"category"、"question"和"answer"中的一个或多个。 -
arguments:指定检索对象的搜索标准,例如限制条件或聚合。在以下示例中,我们将介绍一些这些标准。
让我们用get()函数从JeopardyQuestion类中检索一些条目,以查看它们的样子。在 Pandas 的类比中,你可以将以下内容视为df.head(2)。由于get()函数的响应是 JSON 格式的,我们将导入相关库以以视觉上吸引人的格式显示结果。
import json
res = client.query.get("JeopardyQuestion",
["question", "answer", "category"])
.with_additional(["id", "vector"])
.with_limit(2)
.do()
print(json.dumps(res, indent=4))
{
"data": {
"Get": {
"JeopardyQuestion": [
{
"_additional": {
"id": "064fee53-f8fd-4513-9294-432170cc9f77",
"vector": [ -0.02465364, ...] # Vector is trunkated for better readability
},
"answer": "(Lou) Gehrig",
"category": "ESPN's TOP 10 ALL-TIME ATHLETES",
"question": "No. 10: FB/LB for Columbia U. in the 1920s; MVP for the Yankees in '27 & '36; \"Gibraltar in Cleats\""
},
{
"_additional": {
"id": "1041117a-34af-40a4-ad05-3dae840ad6b9",
"vector": [ -0.031970825, ...] # Vector is trunkated for better readability
},
"answer": "Jim Thorpe",
"category": "ESPN's TOP 10 ALL-TIME ATHLETES",
"question": "No. 2: 1912 Olympian; football star at Carlisle Indian School; 6 MLB seasons with the Reds, Giants & Braves"
},
]
}
}
}
在上述代码片段中,你可以看到我们从"JeopardyQuestion"类中检索对象。我们指定了检索属性"category"、"question"和"answer"。
我们指定了两个额外的arguments:首先,我们使用.with_additional()参数指定检索关于对象 ID 和向量嵌入的额外信息。然后,使用.with_limit(2)参数,我们指定只检索两个对象。这种限制很重要,你将在后续示例中再次看到。这是因为从矢量数据库中检索对象不会返回完全匹配的结果,而是基于相似性返回对象,这需要通过阈值来限制。
矢量搜索
现在,我们准备进行一些矢量搜索!从矢量数据库中检索信息的酷炫之处在于,你可以例如要求它检索与"concepts"相关的 Jeopardy 问题。
为此,我们可以使用.with_near_text()参数,并传入我们感兴趣的"concepts",如下所示:
res = client.query.get(
"JeopardyQuestion",
["question", "answer", "category"])\
.with_near_text({"concepts": "animals"})\
.with_limit(2)\
.do()
指定的vectorizer将输入文本("animals”)转换为向量嵌入,并检索两个最接近的结果:
{
"data": {
"Get": {
"JeopardyQuestion": [
{
"answer": "an octopus",
"category": "SEE & SAY",
"question": "Say the name of <a href=\"http://www.j-archive.com/media/2010-07-06_DJ_26.jpg\" target=\"_blank\">this</a> type of mollusk you see"
},
{
"answer": "the ant",
"category": "3-LETTER WORDS",
"question": "In the title of an Aesop fable, this insect shared billing with a grasshopper"
}
]
}
}
}
你可以看到这有多酷:我们看到矢量搜索返回了两个问题,其答案都是动物,但来自两个完全不同的类别。使用传统的关键词搜索,你必须首先定义一个动物列表,然后检索所有包含其中一个定义动物的问题。
问答功能
问答是将 LLMs 与矢量数据库结合使用时最受欢迎的示例之一。
要启用问答功能,你需要在模块配置中指定一个矢量化器(你应该已经有了)和一个问答模块,如下例所示:
# Module settings
"moduleConfig": {
"text2vec-openai": {
...
},
"qna-openai": {
"model": "text-davinci-002"
}
},
对于问答功能,你需要添加with_ask()参数,并且检索_additional属性。
ask = {
"question": "Which animal was mentioned in the title of the Aesop fable?",
"properties": ["answer"]
}
res = (
client.query
.get("JeopardyQuestion", [
"question",
"_additional {answer {hasAnswer property result} }"
])
.with_ask(ask)
.with_limit(1)
.do()
)
上述代码会查找所有可能包含问题"Which animal was mentioned in the title of the Aesop fable?"答案的提问,并返回答案"The ant"。
{
"JeopardyQuestion": [
{
"_additional": {
"answer": {
"hasAnswer": true,
"property": "",
"result": " The ant"
}
},
"question": "In the title of an Aesop fable, this insect shared billing with a grasshopper"
}
]
}
生成搜索
通过结合 LLMs,你还可以在返回搜索结果之前转换数据。这一概念称为生成搜索。
要启用生成搜索,你需要在模块配置中指定一个生成模块,如下例所示:
# Module settings
"moduleConfig": {
"text2vec-openai": {
...
},
"generative-openai": {
"model": "gpt-3.5-turbo"
}
},
对于生成搜索,你只需在之前的矢量搜索代码中添加with_generate()参数,如下所示:
res = client.query.get(
"JeopardyQuestion",
["question", "answer"])\
.with_near_text({"concepts": ["animals"]})\
.with_limit(1)\
.with_generate(single_prompt= "Generate a question to which the answer is {answer}")\
.do()
上述代码做了以下操作:
-
搜索最接近
"animals"概念的问题 -
返回问题
"说出你看到的这种软体动物的名字",答案是"章鱼" -
为提示
"生成一个答案是章鱼的问题"生成一个完成的结果:
{
"generate": {
"error": null,
"singleResult": "What sea creature has eight arms and is known for its intelligence and camouflage abilities?"
}
}
摘要
LLM 领域的流行不仅带来了许多有趣的新开发者工具,如 LangChain 或 LLaMaIndex,还展示了如何利用现有工具(如向量数据库)来增强 LLM 驱动的应用程序的潜力。
在这篇文章中,我们开始尝试使用 Weaviate,不仅利用向量数据库进行向量搜索,还结合 LLM 进行问答和生成搜索。
如果你对更深入的教程感兴趣,我推荐查看这门全面的四部分课程,内容涉及向量数据库和 Weaviate:
[## 1. 从零到 MVP | Weaviate — 向量数据库
课程概述
weaviate.io](https://weaviate.io/developers/academy/zero_to_mvp?source=post_page-----14bbb9285839--------------------------------)
免责声明:撰写本文时,我是 Weaviate 的开发者倡导者。
喜欢这个故事吗?
免费订阅 以在我发布新故事时收到通知。
[## 每当 Leonie Monigatti 发布时获取电子邮件通知。
每当 Leonie Monigatti 发布时获取电子邮件通知。通过注册,如果你还没有 Medium 账户,将创建一个…
medium.com](https://medium.com/@iamleonie/subscribe?source=post_page-----14bbb9285839--------------------------------)
在 LinkedIn、Twitter 和 Kaggle 上找到我!
参考资料
数据集
[1] Ulrik Thyge Pedersen (2023). 200,000+ Jeopardy 问题 在 Kaggle 数据集中。
许可证: 4.0 国际版(CC BY 4.0)
图片参考
如果没有另行说明,所有图片均由作者创作。
网络与文献
[2] Weaviate (2023). Weaviate 文档(访问日期:2023 年 7 月 14 日)
在 R 中使用 ggvanced 包的蜘蛛图和并行图
一个用于有效可视化多个变量的 R 包
·
关注 发表在 Towards Data Science · 5 分钟阅读 · 2023 年 6 月 20 日
--
一张漂亮的蜘蛛图。图像作者。
动机
在我的一个数据分析项目中,我发现自己需要一种有效的方法来同时比较多个变量中的各组数据。当然,首先想到的是条形图,但我想要一些更引人注目、更有趣的东西。在浏览了一些网页后,我决定尝试两个主要的候选图表——蜘蛛图和并行图。
在这种情况下,我通常会寻找一个专门的 R 包来生成所需的可视化,但这次,这种方法让我一无所获。
一种谎言——更有经验的 R 用户可能会这么说!这样的可视化已经可以通过fsmb和ggradar等包获得雷达图,或通过ggally获得平行图。
然而,除了仅仅对组之间的变量进行排名比较外,我还希望同时显示每个变量的值范围。你猜对了,前面提到的这些包没有提供这一功能。所以,我决定自己动手制作一个 😃
ggvanced 包
ggvanced是一个 R 包,用于创建高级多变量图表,如蜘蛛图/雷达图和平行图。这些可视化是在ggplot2包的基础上创建的。ggplot2包的美妙之处在于其底层的图形语法,允许通过将多个图层堆叠在一起创建图形。这一强大的概念使我们能够创建几乎任何可视化,只要我们知道如何编码。

使用图形语法方法创建可视化。图片由作者提供。
该软件包目前在GitHub上提供,并可以通过在 R 中输入devtools::install_github("Ringomed/ggvanced")命令进行安装,然后调用library(ggvanced)。
如果你对图表构建的细节感兴趣,我在最近的一篇文章中展示了从头开始构建蜘蛛图的逻辑,所以可以查看下面的故事或 GitHub 上的详细文档。
如何在 R 中使用 ggplot2 绘制蜘蛛图 [## 如何在 R 中使用 ggplot2 绘制蜘蛛图]
蜘蛛图/雷达图的逐步构建
对于其他人,下面是一些示例,详细介绍了该软件包函数的功能。
使用 ggspider()的蜘蛛图
ggspider()函数创建蜘蛛图,这些图可以是一个共享的轴,轴的范围被缩放到[0,1],或者是每个显示的类别都有一个显示实际值的独立轴。让我们通过几个示例来测试这个函数。首先,我们需要将数据格式化,使第一列包含组标识符,其余列包含描述变量。我们将使用内置的mtcars和iris数据集。
library(tidyverse)
mtcars_summary <- mtcars %>%
tibble::rownames_to_column(var = "group") %>%
tibble::as_tibble() %>%
tail(3)
iris_summary <- iris %>%
dplyr::group_by(Species) %>%
dplyr::summarise(across(everything(), mean))
library(ggvanced)
比较汽车属性
ggspider(mtcars_summary)

图片由作者提供。
汽车之间的主要差异立刻显现出来。正如预期的,与如 Ferrari 和 Maserati 这样的赛车相比,Volvo 的马力(hp)要少得多,而且在完成四分之一英里(qsec)时所需时间要长得多,但在每加仑英里数(mpg)方面则更加经济。
可视化 Iris 物种之间的差异
ggspider(iris_summary)

图片由作者提供。
就像汽车示例一样,蛛网图非常有效于确定鸢尾花物种之间的差异。我们可以立即看到,Versicolor 和 Virginica 物种非常相似,它们的花瓣和萼片长度与宽度的比例基本相同,唯一不同的是花的总大小。相比之下,Setosa 物种的萼片宽度要大得多。
雷达图
这个功能还允许通过指定参数scaled = TRUE并使用polygon = FALSE切换到圆形,来创建具有单一公共比例轴的传统雷达图。
ggspider(iris_summary, scaled = TRUE, polygon = FALSE)

其他函数参数更多地是美学方面的,涵盖了字体大小、标签位置等方面。更多详细信息,请参考函数文档。
使用 ggparallel() 绘制平行图
虽然从美学角度我更喜欢蛛网图,但平行图可以更容易地发现变量之间的趋势。当数据集中有许多变量或观测值时,这一点尤其明显。
ggparallel(mtcars_summary)

图片由作者提供。
ggparallel(iris_summary)

图片由作者提供。
调整美学
上述图表只是最基础的版本。当然,它们可以像其他任何ggplot2图表一样“加点修饰”。下面是经过几次修改后的ggvanced蛛网图示例。

图片由作者提供。
当然,还有相应的代码。享受吧!😃
library(tidyverse)
library(ggvanced)
library(sysfonts)
library(showtext)
sysfonts::font_add_google("Roboto Condensed")
showtext_auto()
mtcars_gr <- mtcars %>%
tibble::rownames_to_column(var = "group") %>%
tibble::as_tibble() %>%
tail(3) %>%
rename("Miles per Gallon" = mpg, "Cylinders" = cyl,
"Displacement" = disp, "Horsepower" = hp,
"Rear axle\n ratio" = drat, "Weight" = wt) %>%
dplyr::select(1:7)
ggspider(mtcars_gr, axis_name_offset = 0.15, background_color = "beige", fill_opacity = 0.15) +
labs(col = "Car name", title = "Comparing Car Properties") +
theme(plot.title = element_text(hjust = 0.475, face = "bold"),
legend.title = element_text(face = "bold"),
text = element_text(family = "Roboto Condensed", face = "bold"))
结论
在这篇文章中,我介绍了ggvanced的关键函数和选项——这是我为了满足 R 中对更高级的蛛网图和平行图的需求而制作的一个包。
这篇文章通过几个例子讲解了每个函数,然后展示了在一些额外定制后的最终效果。
我希望这个包对你有用,就像对我一样。如果你有任何定制可视化的需求,请留下评论,我会尽力为其创建一个单独的函数。😃
幽灵图像与量子位:可视化量子叠加的新方法
教程
探索量子计算的奥秘!
·发布于Towards Data Science ·阅读时间 10 分钟·2023 年 3 月 9 日
--

来源:Stable Diffusion。
我一直喜欢创建游戏
每当我学习一种新的编程语言或框架时,我经常会尝试编写一个游戏作为初次使用该平台各种功能的尝试。
在量子计算的情况下,为了学习这些概念,创建一个游戏的开始是绝对完美的。
现在,量子计算有许多主题和特性需要学习。这些可能包括量子位的制作方法、量子位的操控、各种门,但本教程的重点将是叠加这一全能概念!
令人难以置信的量子位
叠加是编程量子计算机的核心特性之一,因为它允许量子位同时保存 0 和 1 的值。
当一个量子位处于叠加状态时,它在量子态中运行。这使它变得强大!
指数级处理能力
一个具有一个比特的经典计算机可以处理一个比特的信息(0 或 1)。相比之下,一个具有一个量子位的量子计算机可以处理两个比特的信息(0 和 1)。
类似地,两个经典比特可以处理两个比特的信息,而两个量子位则扩展到处理四个比特的信息(00, 01, 10, 11)。
最终,这使得具有 n 个量子位的量子计算机可以在一个 CPU 周期内处理 2^n 位信息。

一个具有 n 个量子位的量子计算机可以在每个周期处理 2^n 个状态。来源:作者。
考虑到一个仅有 55 个量子位的量子计算机可以在一个周期内处理近 5 PB (2⁵⁵ bits)的信息,这确实是非常惊人的。
这可能是你最喜欢的社交媒体网站的整个数据库,一瞬间处理完成!
这真是太多了!
确实,这就是经典计算机和量子计算机之间的惊人差异,它是由于量子位在叠加态中的同时存在性,也称为量子并行性。
实际上,叠加态允许通过利用这一独特的量子能力,从算法中获得平方和指数级的性能提升。
当然,对于我们的项目,我们只需要一个量子位。
经典计算机上的量子位
为了解释和使用量子位的值,我们必须对其进行测量。
测量量子位的这一简单行为导致量子态的坍缩,这将量子位从同时持有 0 和 1 的值变为仅持有 0 或 1 的值——就像经典比特一样。
由于测量的量子位现在可以被解释为 0 或 1 的值,我们可以像使用经典计算机一样使用量子位。
记住这一点,因为这就是乐趣的开始!
独角兽和小鬼
量子位叠加态的可视化通常使用各种图示,包括:q-球体、相位盘和状态向量。
不过,我想尝试一些更有趣的东西!
由于处于叠加态的量子位将同时保持 0 和 1 的值,我认为将两幅图像叠加在一起会很有趣。

根据量子位的测量来叠加的两幅图像。来源:Stable Diffusion。
每幅图像将代表量子位可能处于的 0 或 1 状态。根据量子位在计算基态的测量以及量子位测量为 0 或 1 的概率,我们可以调整两幅图像的透明度(或阿尔法混合),以便更突出地显示其中一幅。
将图像叠加在一起的动作有助于创建一个美丽的效果,展示量子位的行为。
幽灵图像
置于叠加态的量子位将有 50%的默认测量机会得到 0 和 50%的机会得到 1。
处于叠加态的量子位本质上是随机的。
然而,我们可以部分地将量子位反转到其中一个值。这导致量子位更频繁地测量为 0 或 1,具体取决于施加的反转量。
我们将使用量子计算的这一特性来控制图像的透明度。为此,我们需要使用一种特殊的量子门。
当 X-gate 还不够时
通过使用X-gate,量子比特可以从 0 值翻转为 1,反之亦然。
X-gate 就像经典计算机的 NOT 运算符。它只是简单地翻转量子比特的值。
然而,我们不希望只是将量子比特从 0 翻转为 1,因为那样会立即将我们的图像从一种状态翻转到另一种状态,中间没有任何过渡。相反,我们希望显示从一种图像到另一种图像的渐变,以及所有中间状态。
幸运的是,量子工具包中不仅仅有几种门。
三维量子比特
一个量子比特包含三个影响其测量行为的轴。这些包括 X、Y 和 Z 轴。

Smite-Meister,CC BY-SA 3.0,通过维基媒体共享资源。
某些量子门在不同轴上以不同的方式旋转,这最终会影响量子比特的测量结果。
例如,X-gate 在 X 轴周围进行 180 度旋转,将量子比特值从 0 翻转为 1(或从 1 翻转为 0)。
还有 Y 和 Z-gates,它们以类似的方式绕各自的轴旋转。
这些门对量子比特测量的结果都有独特的影响。然而,有一个特定的门能提供我们所寻找的部分反转效果。
介绍 U-gate
U-gate 是一个特殊的门,接受三个参数。这些包括theta、phi 和lam。这些参数允许修改量子比特的欧拉角,以便在Bloch 球上旋转。
U-gate 实际上是单量子比特量子门的一个广义版本,根据参数的值,它甚至可以用作替代。
你可以尝试调整 U-gate 的各种参数,查看对量子比特测量的影响。
事实证明,改变 theta 参数可以让我们以我们期望的方式倾斜量子比特测量 0 或 1 的机会!
玩弄 U-gate
U-gate 的 theta 值通常范围从 -1.5 到 1.5。
在范围的较低端,量子比特的测量结果将更接近于 0。在范围的较高端,测量结果将更接近于 1。任何介于两者之间的值都会导致偏向 0 或 1。
例如,我们可以将量子比特的theta角设置为 0.8,使得测量为 0 的概率为 20%,测量为 1 的概率为 80%。
调整翻转量
继续调整 U-gate 的值,我们可以看到量子比特的测量结果根据 U-gate 的theta值有不同的 0 或 1 的概率。
由于我们使用的theta范围是-1.5 到 1.5,如果我们将theta的率设置为 0.0,我们将看到传统的 50/50 测量 0 或 1 的概率。然而,如果我们将theta值设置为-0.6,我们可以看到一个偏斜的行为,导致测量 0 的概率远高于测量 1。

一个量子比特在叠加态下,U-gate 部分翻转后的测量分布。量子比特测量为 0 的概率远高于 1。来源:作者。
类似地,如果我们将theta设置为-1.5,我们将有接近 100%的概率总是测量为 0。同样,将值设置为 1.5 将导致测量为 1。
解决了机械问题后,让我们将程序组合起来吧!
我们的攻击计划
我们将从创建一个将单个量子比特置于叠加态的量子程序开始。
由于量子比特在叠加态的默认性质导致测量 0 或 1 的概率为 50/50,我们将使用 U-gate 稍微推动量子比特向特定方向。
我们将根据量子比特的测量值调整两张图像的透明度,同时上下滑动 U-gate 的theta参数。
创建量子程序
我们的量子电路非常简单。我们只需创建一个程序,将 Hadamard 门用于将量子比特置于叠加态,再加上一个 U-gate 用于应用所需的旋转。
# Create a quantum circuit with 1 qubit.
qc = QuantumCircuit(1)
# Initialize the simulator.
simulator = Aer.get_backend('aer_simulator')
# Place the qubit into superposition.
qc.h(0)
# Apply partial inversion to the qubit.
# If our range is 0.0 to 1.0, we can set: theta = rate * 3 - 1.5
qc.u(theta, 0, 0, 0)
# Measure the result.
qc.measure_all()

一个部分翻转的量子比特。测量结果将根据 U-gate theta值偏向 0 或 1。来源:作者。
创建翻转方法以设置透明度
由于我们想在图像显示时修改翻转率,我们可以创建一个辅助方法来翻转量子比特。
此外,我们将使用一个更自然的rate参数来控制翻转,其范围从 0.0 到 1.0,而不是更尴尬的theta范围-1.5 到 1.5。
最后,我们将返回结果量子比特的测量值。
def invert(rate):
# Convert the rate of inversion from [0.0 to 1.0]
# to a theta range of [-1.5 to 1.5] using the formula:
# ((old_value - old_min) / (old_max - old_min)) * (new_max - new_min) + new_min
theta = rate * 3 - 1.5
# Create a circuit.
qc = QuantumCircuit(1)
# Initialize the simulator.
simulator = Aer.get_backend('aer_simulator')
# Place the qubit into superposition.
qc.h(0)
# Apply partial inversion to the qubit.
qc.u(theta, 0, 0, 0)
# Measure the result.
qc.measure_all()
# Execute the circuit.
job = execute(qc, simulator)
result = job.result()
# Return the counts of 0 and 1.
return result.get_counts()
如果我们调用invert()方法,设置率为 0.5,我们将得到传统的 50/50 测量 0 或 1 的概率。
然而,如果我们将率设置为 0.3,我们将有 70-80%的概率测量为 0,而不是 1,如下所示结果。
我们可以调整rate值的滑动来相应地影响量子比特的测量结果。
显示的战斗
现在我们的量子计算程序已经到位,我们可以使用量子比特作为唯一指标,以更高的不透明度绘制独角兽或小妖精。
如果量子比特的零测量次数更多,我们将更清晰地绘制独角兽。
如果量子比特的测量值为 1 的次数更多,我们将更清晰地绘制小妖精。
如果量子比特的测量值介于两者之间,我们将把这两张图像重叠在一起,透明度与量子比特的测量值相等。
初始化图形
我们将使用ipycanvas库来绘制和动画图像,并结合我们的量子计算程序。
我们开始通过初始化一个图形画布来绘制图像。
def init():
canvas = Canvas()
display(canvas)
canvas.fill_style = 'white'
canvas.fill_rect(0, 0, 300, 300)
canvas.fill_style = 'black'
canvas.fill_rect(5, 295, 290, 28)
return canvas
# Load sprites.
unicorn = Image.from_file("unicorn.jpg")
gremlin = Image.from_file("gremlin.jpg")
图像融合——量子风格
我们还需要一些辅助方法来绘制图像并更新其透明度值。
我们可以通过创建一个draw()方法来实现,它将两个精灵(独角兽和小妖精)作为参数,以及一个用于混合的 alpha 值。
我们还将创建一个update()方法,它调用我们的量子计算程序,对量子比特应用部分反转,并使用结果中的 0 和 1 的计数来设置混合图像的透明度。
def draw(sprite1, sprite2, alpha):
canvas.global_alpha = alpha
canvas.draw_image(sprite1, 5, 5, 290, 290)
canvas.global_alpha = 1 - alpha
canvas.draw_image(sprite2, 5, 5, 290, 290)
canvas.global_alpha = 1
canvas.font = "20px arial"
canvas.fill_style = '#00ff00'
canvas.fill_text('Qubit inverted ' + str(round(alpha, 1) * 100) + '%', 10, 316)
return canvas
def update(sprite1, sprite2, rate):
# Run the quantum program and obtain the qubit measurements.
counts = invert(rate)
# Get the counts for "unicorn" from the qubit measuring 0, and the gremlin for the qubit measuring 1.
alpha = counts['0'] / 1024 if '0' in counts else 0
draw(sprite1, sprite2, alpha)
将独角兽变形为小妖精
最后,让我们把一切整合在一起!
我们将创建一个animate()方法,它会简单地循环多次,在每次迭代时运行量子程序,同时逐渐调整量子比特的反转率(theta)。
结果是一个幽灵般的效果,因为独角兽会慢慢变成小妖精,同时量子比特的测量结果与 0 或 1 相符。
def animate(rate = 0):
with hold_canvas():
for i in range(10):
show_unicorn = True
for j in range(20*2):
canvas.fill_style = 'black'
canvas.fill_rect(5, 295, 290, 28)
update(unicorn, gremlin, rate)
rate += 0.05 if show_unicorn else -0.05
if rate > 1:
show_unicorn = False
elif rate < 0:
show_unicorn = True
canvas.sleep(125)
canvas = init()
animate()
看啊——我们的创作成果!

根据量子比特的测量叠加的图像。来源:作者,Stable Diffusion。
这是一种多么迷人的方式来可视化量子比特的测量!试想一下,将量子计算与经典游戏结合所能实现的所有可能性。我希望这能激发你对未来工作的创造力。
你可以在这里下载独角兽小妖精程序的完整代码示例。
关于作者
如果你喜欢这篇文章,请考虑在Medium、Twitter和我的网站上关注我,以便获得我未来的帖子和研究工作的通知。
Git 101 — 从术语到架构及工作流
原文:
towardsdatascience.com/git-101-from-terminologies-to-architecture-and-workflows-78cb6d735798
Git 背后的原理及如何高效使用 Git
·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 5 月 21 日
--

图片由 Roman Synkevych 提供,来源于 Unsplash
所以你已经学习并了解了 git add、git commit 和 git push,但每一步实现了什么——在你的 本地 和 远程 仓库中发生了什么?你可以使用哪些不同的合并策略和分支策略来利用 Git 的强大功能做更多事情?
在阅读了许多仅展示 Git 命令的文章之后,我认为了解 Git 的架构对于真正理解和欣赏 Git 的运作是重要的。本文将涉及 Git 的基本术语,逐步建立到 Git 架构,最后介绍一些常见的 Git 工作流,供你在下一个编码项目中考虑采纳!
目录
什么是 Git
简而言之,Git 是一种帮助进行版本控制的工具,使团队能够以敏捷的方式管理他们的代码——从跟踪代码更改历史到处理代码更改冲突等等。
更进一步,Git 还可以与持续集成持续部署(CICD)工具一起使用,因为代码库维护良好且有版本控制(如果正确使用)。使用如 GitHub Actions 等工具可以在代码更改时自动触发部署——但这不在本文讨论范围之内。
注意:GitHub、GitLab 和 Bitbucket 是 Git 仓库的托管服务,不应与 Git 本身混淆!
Git 术语
在深入了解 Git 架构之前,一些术语经常被提到,了解它们会有帮助。
-
仓库:存储文档和代码的数据结构,由一系列提交组成
-
分支:独立的项目开发线
-
提交:项目的一个版本;项目的快照
-
拉取请求:请求将一个分支合并到另一个分支
-
合并:将独立分支的工作结合在一起
总结起来,在项目工作中,你通常会分支出去进行更改,并在完成后提交更改。之后,你将发起一个拉取请求,以便将你的分支与主要的仓库进行合并。
独立分支和拉取请求的存在允许团队在保持稳定仓库的同时扩展他们的工作
Git 操作中也有一些术语。注意有一个概念是本地和远程,将在后面的部分进行解释。
-
克隆:远程仓库的本地副本
-
分叉:将远程仓库复制到自己的账户,两者都是远程仓库
-
获取:从远程仓库检索新对象和引用
-
拉取:从远程仓库获取并合并提交到本地
-
推送:向远程仓库添加新对象和引用
-
储藏:保存工作区中的文件以备后用
Git 架构
如前面部分所述,存在本地和远程的概念。远程仓库指的是项目所有提交的位置,通常托管在数据中心或云端(例如 GitHub)。本地仓库,则指的是项目在本地机器(例如笔记本电脑)上的提交位置,由三个独立的“区域”组成,
-
工作区:单个提交的目录和文件的位置
-
暂存区:计划下次提交的文件的位置
-
本地仓库:项目所有提交的位置
具体操作时,当你执行git checkout时,它会将目录和文件放入工作区。在进行更改后,执行git add会将修改的文件转移到暂存区。然后你可以执行git commit为更改添加消息,并将更改提交到本地仓库。最后,执行git push将本地仓库中的更改推送到远程仓库。
分支、合并和合并冲突
分支策略
一个分支是一组回溯到项目首个提交的提交,通常用于在团队成员之间隔离工作,因为他们可以在独立的分支上工作。
有两种类型的分支。短期分支包含对项目的小更改,例如实现新功能、修复错误、热修复等。而长期分支可以持续到项目的整个生命周期,例如主分支、开发分支或发布分支。
理想情况下,分支在合并后应被删除,以防止分支数量持续增加。在意外删除的情况下,分支可以恢复 — 仅当分支存在于某人的本地仓库中时。
合并策略
合并将多个独立分支的工作结合起来。使用小而频繁的合并是一个好的实践,以避免合并冲突。有 4 种合并类型 — 并且合并策略通常取决于团队的提交历史策略。
快进(FF)合并

图 1: 快进合并示例 — 作者提供的图片
-
将基础分支标签移动到合并分支的尖端。
-
只有在没有对基础分支进行其他提交的情况下才可能 — 否则在执行任何
git push之前先执行git pull。 -
这被认为是最严格的合并策略,因为它确保代码修改始终兼容,因为最新的更改总是首先被获取。
合并提交

图 2: 合并提交示例 — 作者提供的图片
-
将合并分支的提交结合起来,导致非线性的提交图,因为合并提交有多个父级。
-
当多个提交被合并且两个分支以不同方式更改相同内容时,这可能会导致合并冲突。
-
这是 Git 的自动行为。
压缩合并

图 3: 压缩合并示例 — 作者提供的图片
-
将合并分支的尖端合并到基础分支的尖端(可以使用快进合并或合并提交),并将多个提交合并成一个提交。
-
这可能导致删除的提交,因为提交 B 和 C 最终会在删除分支时被垃圾回收。
-
需要谨慎操作! 这会重写提交历史,我们不应重写已共享的历史。
变基

图 4: 变基示例 — 作者提供的图片
-
将合并分支中的提交移动到新的父级,并且提交将由于不同的祖先链而具有不同的 ID。
-
合并分支中的“新”提交可以快速前进,不需要合并提交。
-
在进行变基时,当两个分支以不同方式更改相同内容时,这可能会导致合并冲突。
-
需要谨慎操作! 这会重写提交历史,我们不应重写已共享的历史。
合并冲突
从前面的部分,我们注意到合并提交和变基可能导致合并冲突,即两个分支以不同方式更改相同内容的情况。
解决合并冲突涉及 3 次提交——基础分支的顶端、合并分支的顶端,以及基础和合并分支之间的共同祖先。冲突部分由冲突标记围绕,如下所示,
<<<<<<< HEAD
The version in the base branch
=======
The version in the merge branch
>>>>>>> feature/merge-branch
在尝试合并时,具有冲突的文件会被 Git 修改并放置在工作树中。合并冲突必须在暂存、提交和再次合并之前手动解决。
Git 工作流
除了决定为你的项目实施的合并策略类型外,还有基于团队偏好、团队规模、项目规模等的 4 种 Git 工作流类型。
基本工作流 / 集中式工作流
-
类型: 单一中央代码库(无分支)
-
如何: 每个成员克隆代码库,在本地处理代码,提交并将更改推送到中央代码库。
-
适合小型项目和小型团队,因为没有代码审查或拉取请求的参与。
特性分支工作流
-
类型: 单一中央代码库(带有功能分支)
-
如何: 每个成员为每个新功能创建一个分支,并将完成的更改合并到主分支。
-
推荐的工作流,因为涉及代码审查、拉取请求和讨论。
分叉工作流
-
类型: 多个远程代码库(通过分叉)
-
如何: 团队成员分叉(fork)代码库,创建代码库的一个副本。成员不需要对原始代码库具有写权限,并且可以随意修改分叉的代码库,以添加功能或将代码库调整到其他用例或项目中。
-
这种工作流允许代码库的维护者接受来自任何开发者的提交,而无需授予他们访问官方代码库的权限,这对开源项目非常有用。
Gitflow 工作流
-
类型: 单一中央代码库(带有短期和长期分支)
-
如何: 对于功能或热修复,使用短期分支,对于开发和发布,使用长期分支。功能分支从开发分支创建,完成后合并回开发分支。对于发布,将开发分支中的功能合并回主分支,只有主分支用于发布。
-
该工作流允许通过发布和热修复安全地持续发布项目。
使用 Git 是必不可少的,只要你在处理任何代码库时。即使有或没有项目团队,版本控制仍然很重要来管理你的代码!希望这篇文章能揭示 Git 的内部工作原理,包括 Git 架构、不同的分支和合并策略,以及你可以在项目中使用的不同工作流。
相关链接
-
Git 文档:
git-scm.com/doc -
使用 Git 进行版本控制(Coursera):
www.coursera.org/learn/version-control-with-git
数据科学家的 Git 深入探讨
原文:
towardsdatascience.com/git-deep-dive-for-data-scientists-1c9cc45c7612
通过实际场景学习 Git
·发布于 Towards Data Science ·阅读时间 9 分钟·2023 年 7 月 1 日
--

作者提供的图片
最初发表于 https://mathdatasimplified.com 于 2023 年 7 月 1 日。
为什么为你的数据科学项目选择 Git?
Git 是一个在软件开发中广泛使用的版本控制系统,但它是否适合你的数据科学项目?绝对适合。
以下是 Git 对数据科学无价的几个原因:
版本控制
场景:
你用新的数据处理方法替换了当前的方法。发现新方法没有产生预期结果后,你希望恢复到之前的有效版本。
不幸的是,没有版本控制,撤销多个更改会变成一项艰巨的任务。
解决方案:
使用 Git,你可以跟踪代码库的更改,切换不同版本,比对变化,并在必要时恢复到稳定状态。

作者提供的图片
协作
场景:
你和其他数据科学家在一个机器学习项目上合作。为了合并团队成员做出的所有更改,你需要手动交换文件并审查彼此的代码,这需要时间和精力。
解决方案:
Git 使得合并更改、解决冲突和同步进度变得简单,允许你和你的团队成员更高效地合作。

作者提供的图片
分支
场景:
你希望探索新的方法来提升模型的性能,但又不愿直接更改生产代码。对已部署模型的任何意外影响可能会对公司产生重大后果。
解决方案:
利用 Git 的分支功能,你可以为不同的功能创建独立的分支。这使你能够进行测试和迭代,而不会影响生产分支的稳定性。

图片作者
备份
场景:
硬件故障或盗窃会导致你所有的代码丢失,让你感到沮丧,并使你在几个月的工作中陷入困境。
解决方案:
Git 通过将你的项目安全地存储在远程仓库中来备份你的项目。因此,即使遇到这样的不幸事件,你也可以从远程仓库恢复代码库,继续工作而不会丧失重要进展。

图片作者
如何使用 Git
现在我们了解了 Git 在数据科学项目中的价值,让我们探索如何在不同场景中有效地使用它。
初始化 Git
要在当前项目中初始化 Git 并将项目上传到远程仓库,请按照以下步骤操作:
首先,在项目目录中初始化一个新的 Git 仓库:
git init
接下来,将远程仓库添加到你的本地 Git 仓库中。要使用 GitHub 作为远程仓库,请在 GitHub 上创建一个新仓库并复制其 URL。

图片作者
然后,将 URL 添加到你的本地 Git 仓库中,命名为“origin”:
git remote add origin <repository URL>
接下来,在你的 Git 仓库中暂存更改或新文件:
# Add all changes in the current directory
git add .
审核待提交的更改列表:
git status
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .dvc/.gitignore
new file: .dvc/config
new file: .flake8
new file: .gitignore
new file: .pre-commit-config.yaml
new file: Makefile
new file: config/main.yaml
new file: config/model/model1.yaml
new file: config/model/model2.yaml
new file: config/process/process1.yaml
new file: config/process/process2.yaml
new file: data/final/.gitkeep
new file: data/processed/.gitkeep
new file: data/raw.dvc
new file: data/raw/.gitkeep
new file: docs/.gitkeep
new file: models/.gitkeep
new file: notebooks/.gitkeep
new file: pyproject.toml
new file: src/__init__.py
new file: src/process.py
new file: src/train_model.py
new file: tests/__init__.py
new file: tests/test_process.py
new file: tests/test_train_model.py
将暂存的更改永久保存到仓库历史中,并附上提交消息:
git commit -m 'init commit'
一旦你的提交完成并存储在本地仓库中,你可以通过将它们推送到远程仓库与他人共享你的更改。
# push to the "main" branch on the "origin" repository
git push origin main
运行此命令后,远程仓库上的“main”分支将接收来自本地仓库的最新更改。

图片作者
为现有项目做出贡献
要为现有项目做出贡献,首先在本地机器上创建远程 Git 仓库的本地副本:
git clone <repository URL>
这个命令将创建一个与远程仓库同名的新仓库。要访问文件,请导航到仓库目录:
cd <repository-name>
在“main”分支之外的单独分支上进行更改是一种良好的实践,以避免对主要代码库产生任何影响。
使用以下命令创建并切换到一个新分支:
git checkout -b <branch-name>
对新分支进行一些更改,然后将更改添加、提交并推送到远程 Git 仓库的新分支上:
git add .
git commit -m 'print finish in process_data'
git push origin <branch-name>
推送提交后,你可以创建拉取请求,将更改合并到“main”分支中。

图片作者
在你的同事批准并合并你的拉取请求后,你的代码将被集成到“main”分支中。
将本地更改与远程更改合并
假设你从主分支创建了一个名为“feat-2”的分支。在对“feat-2”分支进行了一些更改之后,你发现主分支已经更新。你怎么将远程更改合并到本地分支中?

作者提供的图片
首先,通过暂存和提交本地更改确保你的本地工作已保存。
git add .
git commit -m 'commit-2'
这可以防止远程更改覆盖你的工作。
接下来,使用 git pull 从远程仓库的主分支拉取更改。第一次执行此命令时,你将被提示选择一种策略来协调分支。以下是可用的选项:
$ git pull origin main
From https://github.com/khuyentran1401/test-git
* branch main -> FETCH_HEAD
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint: git config pull.rebase false # merge
hint: git config pull.rebase true # rebase
hint: git config pull.ff only # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.
运行 git pull origin main --no-rebase 将在“feat-2”分支中创建一个新的合并提交,将“main”分支和“feat-2”分支的历史记录联系在一起。

作者提供的图片
运行 git pull origin main --rebase 将执行变基操作,将“feat-2”分支的提交放在“main”分支之上。

作者提供的图片
变基不会像合并那样创建新的合并提交;而是修改现有的“feat-2”分支的提交记录。这会导致更干净的提交历史。
然而,变基命令需要谨慎使用,特别是当其他团队成员也在积极使用相同的分支,例如“feat-2”分支时。
如果你在其他人也在工作于“feat-2”分支时进行变基操作,可能会导致分支历史的不一致。Git 在尝试同步这些分支时可能会遇到困难。

作者提供的图片
如果你是 Git 新手,并且更重视简单性而不是维护干净的历史,使用合并方法作为默认选项,因为它通常比变基更容易理解和使用。
恢复到上一个提交
想象一下:在创建了新的提交之后,你发现其中存在错误,并且想要恢复到某个特定的提交。你该怎么做?
首先,通过运行以下命令确定要恢复的特定提交的哈希值:
git log
commit 0b9bee172936b45c3007b6bf6fa387ac51bdeb8c
commit-2
commit 992601c3fb66bf1a39cec566bb88a832305d705f
commit-1
假设你想要恢复到“commit-1”,你可以使用 git revert 或 git reset。
git revert 创建一个新的提交,用于撤销在指定提交之后所做的更改。

作者提供的图片
git reset 通过将分支指针更改为指定的提交来修改提交历史。

作者提供的图片
虽然 git reset 保持了提交历史的整洁,但它更具破坏性,因为它会丢弃提交。git revert 是一个更安全的选择,因为它保留了原始提交。
好习惯
忽略大型和私有文件
在 Git 仓库中,排除特定文件或目录以解决如文件大小过大和隐私问题等问题是至关重要的。
在数据科学项目中,你应该忽略某些文件,例如数据集和秘密,原因如下:
-
数据集:对二进制数据集进行版本控制可能会显著增加仓库的大小。
-
秘密:数据科学项目通常需要凭证或 API 密钥来访问外部服务。如果将这些秘密包含在代码库中,如果仓库被泄露或公开共享,将会存在安全风险。
要排除特定的文件或目录,你可以将它们添加到项目根目录下的 .gitignore 文件中。以下是一些示例:
# .gitignore
data/
.env
此外,你还应该忽略那些可能导致文件大小过大或特定于你的开发环境的非必要文件,例如依赖管理文件如“venv”或编辑器特定文件如“.vscode”。
在这里可以找到适用于你的语言的有用 .gitignore 模板列表 here。
保持小的提交
将更改拆分成小而集中的提交。这种方法确保每次提交都有明确的目的,使其更容易理解、在需要时还原更改,并且减少冲突的可能性。

作者提供的图像
使用描述性分支名称
选择准确反映你正在处理的任务或特性的描述性分支名称。避免使用模糊的名称如“add file”或个人标识符如“john-branch”。相反,选择更具描述性的名称,如“change-linear-model-to-tree-model”或“encode-categorical-columns”。
标准化代码格式以便于代码审查
一致的代码格式化帮助审查者将注意力集中在代码的逻辑上,而不是格式上的不一致。
在下面的代码片段示例中,由于不规则的缩进、空格和引号,审查者很难准确定位 print 语句的添加。

作者提供的图像
标准化代码提高了代码的可读性。

作者提供的图像
我喜欢的格式化和标准化代码的工具有:
补充 Git 的工具
pre-commit
在每次提交之前,按照风格指南维护一致的代码风格可能会令人感到不堪重负。pre-commit 通过在提交之前检查和重新格式化代码来自动化这一过程。以下是其工作原理的示例:
$ git commit -m 'my commit'
ruff.......................................Failed
- hook id: ruff
- exit code: 1
Found 3 errors (3 fixed, 0 remaining).
black....................................Failed
- hook id: black
- files were modified by this hook
reformatted src/process.py
All done! ✨ 🍰 ✨
1 file reformatted.
DVC
DVC(数据版本控制)是一个用于数据版本控制的系统。它本质上类似于 Git,但用于数据。DVC 允许你将原始数据存储在一个独立的位置,同时在 Git 中跟踪数据的不同版本。

图片由作者提供
了解更多关于如何在这篇文章中使用 DVC 的信息。
结论
通过采用 Git 并利用其功能以及这些补充工具,你可以提高生产力,保持代码质量,并与团队成员高效协作。
我喜欢撰写数据科学概念的文章并玩弄各种数据科学工具。你可以通过以下方式跟踪我最新的帖子:
参考
Atlassian. (n.d.). 合并与变基:Atlassian Git 教程。Atlassian. www.atlassian.com/git/tutorials/merging-vs-rebasing
现代数据科学家的 Git:你不能忽视的 9 个 Git 概念
原文:
towardsdatascience.com/git-for-the-modern-data-scientist-9-git-concepts-you-cant-ignore-b4eaf7a154c2
通过引人注目的视觉效果进行讲解
·发表于Towards Data Science ·12 分钟阅读·2023 年 5 月 6 日
--

图片由我与 Midjourney 提供
介绍
大多数数据科学家在使用 Git 时感觉像是水中鱼。有些软件工程师只谈论 Git 的事情,而数据科学家则每次都说“啊?”(我希望能加上声音效果)。
今天就到此为止!因为 Git 是协作的基本工具,我将详细讲解数据科学家必须如数家珍的九个最关键的 Git 概念。
我可以保证,下次有人谈论 Git 或版本控制时,你不会再假装理解。
让我们开始吧!
第 1000 次……
你可能已经听过几百次了,但我还是谨慎起见再说一次:
Git 是开发 ML 和 AI 系统中最关键的工具之一。

图片由我提供
如果你对机器学习或数据科学项目的理解是通过创意命名的文件如“notebook1”、“notebook2”、“notebook_final”和“notebook_final_final”来完成模型,那么就不要考虑 Git 了。
然而,如果你打算部署他人可以使用的模型,Git 是一个相对较小的代价。
Git 允许你跟踪代码和数据的更改,与他人协作,并维护项目历史。使用 Git,你可以轻松恢复到工作中的先前版本,比较不同版本,并合并多个贡献者的更改。
此外,Git 轻松与其他流行的 MLOps 工具如 DVC 集成,使其成为数据科学家的必备工具。
0. 仓库
基本上,仓库就是这个:

图片由我提供
这是你计算机上的一个文件夹。它可以没有文件、三个文件或一百个。将该文件夹转换为 Git 仓库所需的唯一操作是在其中调用git init。

图片由我提供。初始化 Git 仓库。
机器学习仓库通常有用于存储数据、模型的文件夹,以及用于加载、清理和转换数据的代码文件夹,以及用于选择、训练和保存模型以进行部署的文件夹。
还会有其他杂项文件,如用于 Git 内部和元数据的.git文件夹。
所有这些构成了一个单一的仓库,Git 通常足以跟踪它们(数据和模型除外。有关内容,请参阅这篇文章)。
1. 跟踪,未跟踪
当你在目录中初始化 Git 时,默认情况下,你创建的任何现有或新文件/目录将不会被 Git 跟踪。

图片由我提供。展示git status命令
这意味着你对这些文件所做的任何未来更改也将不会被跟踪。因此,你需要通过运行git add path/to/file.py将这些文件纳入 Git 的监督。

图片由我提供。跟踪 Git 中的文件和目录。
在对文件调用git add后,它们将被 Git 监视。
如果你希望添加仓库中的所有文件(尽管这非常不可能),你可以调用git add .。
还有一些情况下你绝不希望文件被 Git 跟踪。这时你需要创建一个.gitignore文件。
正如名字所示,添加到.gitignore中的文件将不会被 Git 跟踪或索引,只要它们在那儿。对于数据项目,你应该将大型数据文件如 CSV、parquet、图片、视频或音频添加到.gitignore中。Git 在处理这些内容时历史上表现不佳。
它像高手一样处理其余部分。
附注:你可以在终端中用touch .gitignore创建.gitignore文件,并用echo "filename" >> .gitignore在新行中添加文件/文件夹。
2. 提交
Git 提交是一件珍贵的事情。版本控制的整个理念都基于此。
当你在 Git 仓库中调用git commit时,你会在特定时间点对每个 Git 跟踪的文件进行快照。可以把它当作一个包含你项目不同时间段内容(版本)的时间胶囊。

图片由我提供。进行第一次提交。
你所做的所有提交将形成你的 Git 历史或 Git 树,如下所示。

图片由我提供。一个小型 Git 树。
一个好的 Git 树组织了你仓库的线性进展。通过将你的代码更改分解为离散的、明确定义的提交,你几乎可以像书一样绘制出仓库的进展。
然后,你可以通过提交浏览这本 Git 书的各个页面。
就像一个作家在书中的每一页上都投入大量精力一样,你也应该细心对待你的提交。
你不应该为了提交而提交。把它们视为历史的小片段,并知道未来的你和其他开发人员应该欣赏它们,而不是厌恶。
传统建议:一个好的提交有一个描述所做更改的信息性消息。
在典型的机器学习项目中,常见的提交场景包括:
-
实现新功能:编写代码以添加新的功能,比如新的函数、类、类方法、训练新模型、新的数据清理操作等。
-
修复 bug:记录对现有函数、方法和类的 bug 修复
-
提升性能:编写代码来优化现有功能,比如优化代码块
-
更新文档和依赖项
-
机器学习实验:在一个项目中,你将运行数十个实验以选择和调整最佳模型。每个模型运行都应该作为提交进行跟踪。
3. 暂存区
说到提交,我们已经超前了。在关闭提交封装之前,你必须确保其中的内容是正确的。
这涉及到告诉 Git 你具体想提交哪些文件的哪些更改。有时候,新的更改可能来自多个文件,而你可能只想提交其中的一部分,留下其余部分以便未来提交。
这是我们揭开幕布,展示暂存区的地方(带点俏皮话):

由我提供的图像。在将更改添加到 train.py 后,暂存区发生了变化。
这个想法是,你必须有某种方式在按下提交按钮之前,双重检查、编辑或撤销你想添加到 Git 历史记录中的更改。
将新更改添加到暂存区(或者Git 索引,如某些人所说)可以实现这一点。该区域保存了你希望包含在下一个提交中的更改。
假设你修改了clean.py和train.py。如果你使用git add train.py将train.py中的更改添加到暂存区,下一个提交将仅包含该更改。
修改过的clean.py将保持原样(未提交)。

由我提供的图像。上面的图像为了清晰展示而重新显示。
所以,这里有一个简单的工作流程:
-
使用 Git 跟踪新文件(仅需做一次)
-
使用
git add changed_file.extension将跟踪的文件中的更改添加到暂存区 -
使用
git commit -m "Commit message"将暂存区的更改提交到历史记录中。
4. 哈希和标签
除了消息,所有 Git 提交都有哈希,这样你可以更容易地指向它们。

由我提供的图像。三个示例提交,具有 7 位字符的哈希值。
哈希是一个具有 40 个十六进制字符的字符串,给每个提交分配唯一的 ID,例如 1a3b5c7d9e2f4g6h8i0j1k2l3m4n5o6p7q8r9s0t。
它们使在提交(你代码库的不同版本)之间切换变得更加容易,使用 git checkout HASH。切换时不必写完整的哈希值。只需要哈希值的前几个字符,这些字符足以使其唯一。
你可以使用 git log 列出你所做的所有提交及其哈希值(这会显示提交的作者和消息)。
为了只列出哈希和消息而不使屏幕混乱,你可以使用 git log --oneline。

图片由我提供。用于逐行列出你的 Git 日志的命令。
如果哈希值让你感到困惑,还有 Git 标签。Git 标签是你可以为一些重要的提交(或任何提交)起的友好昵称,以便更容易记住和引用它们。

图片由我提供。四个提交,其中两个被标记。
你可以使用命令“git tag”将标签分配给特定的提交,这些提交很重要,例如包含关键功能或重要代码库发布的提交(例如,v1.0.0)。此外,你还可以给代表你最佳模型的提交打标签,例如“random_forest_best”。
将标签视为在人类可读的里程碑,这些里程碑在所有提交哈希中脱颖而出。
为了澄清,命令
git tag ‘tag_name’只会给最后一个提交添加标签。如果你想为特定提交添加标签,你需要在命令末尾指定提交哈希,在标签名称之后。
5. 分支
提交后,分支是 Git 的核心。99% 的时间里,你都将在 Git 分支中工作。
默认情况下,当你在文件夹中初始化 Git 时,你所在的分支将被命名为 main 或 master。

图片由我提供。主分支。
你可以将其他分支视为你代码库的替代现实。
通过创建 Git 分支,你可以测试和实验新功能、想法和修复,而不用担心会弄乱你的代码库。
例如,你可以在一个新的分支中测试用于分类任务的新算法,而不会干扰主代码库:

图片由我提供。创建新的 SGD 分支。
Git 分支非常便宜。当你调用 git branch new_branch_name 时,Git 会创建主分支的伪副本,而不会重复任何文件。
创建新分支并尝试新想法后,如果结果看起来不太理想,你可以选择删除该分支。另一方面,如果你对新分支中的更改感到满意,你可以将其合并到主分支。
6. HEAD
一个 Git 仓库可以有多个分支和数百个提交。因此,你可能会提出一个很好的问题:“Git 如何知道你在哪个分支或提交上?”。
Git 使用一个叫做HEAD的特殊指针,这就是答案。

图片由我提供。HEAD 的切换。
基本上,HEAD 就是你。无论你在哪里,HEAD 都会在 Git 中跟随你。99% 的时候,HEAD 会指向当前分支中的最新提交。
如果你进行新的提交,HEAD 将会移动到那个提交。如果你切换到一个新分支或旧分支,HEAD 将会切换到该分支中的最新提交。
HEAD 的一个用例是比较不同提交之间的变化。例如,调用git diff HEAD~1将会比较最新提交与紧接在它之前的提交。
这也意味着 Git 中的HEAD~n语法指的是HEAD所在位置之前的第n个提交。

图片由我提供。HEAD~n 语法。
你也可能进入到令人讨厌的分离的 HEAD 状态。这并不意味着 Git 已经丢失了对你的追踪,不知道该指向哪里。
当你使用命令git checkout HASH来检出特定提交时,会发生分离的 HEAD 状态,而不是使用git checkout branch_name。这会迫使 HEAD 不再指向分支的末端,而是指向提交历史中的某个特定提交。

图片由我提供。分离的 HEAD 状态。
在分离的 HEAD 状态下,你所做的任何更改或提交都会被孤立或孤立,不会成为你 Git 历史的一部分。原因是 HEAD 是分支的头部。它非常喜欢附着在分支的末端或头部,而不是它的腹部或腿部。
因此,如果你想在分离的 HEAD 状态下进行更改,你应该调用git switch -c new_branch来在当前提交处创建一个新分支。这将使你脱离分离状态,并移动 HEAD。
理解 HEAD 对于帮助你导航任何复杂的 Git 树会大有裨益。
7. 合并
那么,创建新分支后会发生什么?
如果你的实验没有成功,你会用git branch -d branch_name丢弃它吗?还是会进行传说中的 Git 合并?
基本上,Git 合并是一个盛大的聚会,其中两个甚至更多的分支汇聚在一起,创建一个更粗的分支。

图片由我提供。两个分支的合并。
当你合并分支时,Git 会将每个分支中的代码合并到一个统一的代码库中。
如果分支中存在重叠的更改,即两个分支都修改了train.py中的第 5 到 10 行,Git 会引发合并冲突。
合并冲突听起来就像它的名字那样糟糕。要解决冲突,你必须决定要保留哪个分支的更改。
解决合并冲突而不发火并且不让耳朵冒烟是一项随着时间推移而培养的稀有技能。所以,我不会多谈这个问题,会推荐你查看这篇优秀的文章来自 Atlassian。
8. 储藏
我在编码时往往会搞砸很多。一个想法闪现;我尝试一下,结果发现它毫无用处。
起初,我会愚蠢地将混乱的代码抹去,但后来会后悔。尽管这个想法毫无用处,但这并不意味着我不能在未来使用某些代码块。
然后,我发现了 Git 暂存区,它们很快成为了我最喜欢的 Git 特性之一。
当你调用git stash时,Git 会自动将暂存的或隐藏的更改(包括已暂存和未暂存的更改)放入工作目录。文件会恢复到刚刚提交的状态。

图片由我提供。暂存区发生了什么?
在你暂存了更改之后,你可以像往常一样继续你的工作。当你想再次检索这些更改时(无论在何处),你可以使用git stash apply或git stash pop命令。这些命令将把之前保存到暂存区的更改恢复到工作目录中。
请注意,
git stash命令只保存对跟踪文件的更改,而不包括未跟踪的文件。为了暂存跟踪和未跟踪的文件,你需要使用-u标志与git stash命令一起使用。被忽略的文件不会包含在暂存区中。
9. GitHub
所以,我们来到了一个古老的问题——Git 和 GitHub 之间有什么区别?
这就像是在问汉堡和芝士汉堡之间的区别。
Git 是一个版本控制系统,用于跟踪仓库。另一方面,GitHub 是一个基于网络的平台,用于在线存储由 Git 控制的仓库。
当仓库在线并且开放合作时,Git 的优势真正显现出来。如果一个仓库仅存在于你的本地机器上,其他人无法与你一起进行工作。
因此,可以将 GitHub 看作是你本地仓库的远程镜像,其他人可以克隆、分叉并建议拉取请求。
如果这些术语对你来说还很陌生,请关注我的下一篇文章,我将解释 N(我现在不知道有多少个)GitHub 概念,立即澄清这些困惑。
包装
就这样!我希望这篇文章为你提供了关于这 9 个 Git 概念的极大清晰度。特别是,我发现 HEAD 的概念对很多人来说是个真正的难题 😃
确保阅读官方 Git 文档以获得更多(尽管非常无聊)信息。
感谢阅读!
喜欢这篇文章以及它那奇特的写作风格?想象一下,你可以访问更多类似的文章,都是由一个才华横溢、迷人且机智的作者(顺便说一下,就是我 😃)撰写的。
只需 4.99 美元的会员,你将不仅能访问我的故事,还能获得 Medium 上最优秀和最聪明头脑的知识宝库。如果你使用我的推荐链接,你将获得我超新星的感激以及一个虚拟的击掌,以感谢你对我工作的支持。
[## 使用我的推荐链接加入 Medium — Bex T.
获得对我所有⚡高级⚡内容和整个 Medium 平台的无限制访问。通过购买我的…
ibexorigin.medium.com 支持我的工作!
图片由我制作。通过 Midjourney。
Git 标签:它们是什么以及如何使用
原文:
towardsdatascience.com/git-tags-what-are-they-and-how-to-use-them-211eca16505e
它们的用途是什么,如何创建本地标签,它们如何显示以及如何删除它们
·发表于 Towards Data Science ·阅读时间 4 分钟·2023 年 7 月 4 日
--

图片由 Louie Martinez 提供,来源于 Unsplash
在 Git 中,标签是一种标记版本库历史中特定点的方式。它通常用于标记重要的里程碑或发布,如版本、发布或主要项目更新。它们通常有三个主要目的:
-
发布版本:标签通常用于标记特定版本的软件发布。例如,
v1.0标签可以用来表示你软件的第一个正式版本。 -
稳定点:标签可以标记开发过程中的稳定点。当你希望突出标记已知稳定且可靠的提交时,这很有用,例如完成一个主要功能准备发布后,或修复关键错误后。
-
文档:标签可以作为文档标记。通过标记项目历史中的重要点,你可以创建用于理解和分析代码库演变的参考。
本地标签
当涉及到本地应用标签时,Git 提供了两种类型的标签:轻量标签 和 注释标签。
轻量标签:这些是指向特定提交的简单指针。它们是通过 git tag 命令加上标签名称来创建的。例如:
git tag v1.0
这在当前提交处创建了一个轻量标签名称“v1.0”。轻量标签易于创建,不存储任何额外的信息,如创建标签的用户的姓名或当前日期。
注释标签:注释标签存储额外的信息,如标签创建者的姓名、电子邮件、日期和消息。它们是通过 -a 或 — annotate 选项与 git tag 命令一起创建的。例如:
$ git tag -a v1.0 -m "Release version 1.0"
这个命令创建了一个名为“v1.0”的注释标签,并附上了提供的消息。注释标签通常用于记录发布或重要里程碑,因为它们提供了更多的背景和信息。
轻量标签和注释标签都是在本地创建的,可以用于个人参考或与项目中的其他开发者共享。
推送标签
当你将提交推送到远程仓库时,标签不会被自动推送。相反,你可以通过在git push命令后指定标签的名称,单独或批量推送标签:
git push origin V1.0 V1.1 V1.2
使用--tags标志将所有本地仓库的标签推送到远程仓库:
git push origin --tags
或使用--follow-tags选项将标志附加到当前分支中的相关提交上:
git push --follow-tags
标签可见性
当你将标签推送到远程仓库时,它对克隆或从该仓库获取的其他人可见。然而,默认情况下,标签不会在进行git clone或git fetch操作时自动获取。要从远程仓库检索标签,用户需要明确指定他们想要获取的标签,或者使用git fetch — tags命令获取所有标签。
删除标签
如果你已将标签推送到远程仓库,并且后来决定删除它,你可以在本地和远程仓库中删除该标签。要删除本地标签,你可以使用以下命令:
git tag -d <tag-name>
删除远程标签时,你可以使用以下命令:
git push --delete <remote-name> <tag-name
总结
在 Git 中,标签对于标记项目历史中的重要点、记录发布和提供重要里程碑的清晰参考非常有价值。了解如何创建、管理和删除标签可以提升你的 Git 工作流和与其他开发者的协作。
如果你喜欢你所阅读的内容且尚未成为 Medium 会员,请随时通过下面的推荐链接注册 Medium,支持我和平台上的其他优秀作者!提前感谢。
## 使用我的推荐链接加入 Medium — Philip Wilkinson
作为 Medium 会员,你的会员费用的一部分会分配给你阅读的作者,你可以完全访问每一个故事…
或者可以随时查看我在 Medium 上的其他文章:
从基本数据结构到 Python 中的抽象数据类型
[towardsdatascience.com ## 完整的数据科学入门课程
UCL 数据科学学会: Python 入门,数据科学家工具包,使用 Python 的数据科学
[towardsdatascience.com [## 来自 scikit-learn 的随机森林分类器实用介绍
UCL 数据科学学会研讨会 14: 随机森林分类器是什么,实施,评估和改进
GitHub 对现代数据科学家的意义:你不能 .gitignore 的 7 个概念
用一点趣味、机智和视觉效果进行解释
·发布在 Towards Data Science ·阅读时间 11 分钟·2023 年 5 月 17 日
--

我的菠萝伙伴们。使用 Midjourney 制作。
介绍
如果你认为数据科学家和 ML 工程师只对 Git 感到不适,你应该看看他们的 GitHub 个人资料。它们比太平洋深处的一个无名岛还要荒凉。
当我刚开始时,我的个人资料是一个典型的案例。我认为这主要是因为我参加的所有课程(而且有很多)都想先教我一些炫酷的东西,比如机器学习,以保持我的兴趣,而把枯燥的部分留给我自己,比如学习 GitHub。
所以,我以一种非常困难的方式学到了这七个 GitHub 概念。(夸张一下:我只是阅读了文档并进行了交流 😃)
现在,你可以以比我更有趣的方式来学习这些概念。(希望如此。)
让我们开始吧。
顺便说一下,我假设你已经了解 Git 才能理解这篇文章。如果不了解,请查看我之前的帖子:
Git 对现代数据科学家的意义:你不能忽略的 9 个 Git 概念
编辑描述
towardsdatascience.com
0. 远程仓库
Git 仓库有两种类型:本地和远程。
本地 Git 仓库仅存储在你的计算机上。另一方面,其远程副本存储在服务器上(通常是 GitHub),而不是某个生锈的笔记本电脑上,以便其他人可以与你协作而无需实际在同一房间里。

图片由我提供
然而,远程仓库不仅仅是为了协作。由于它在服务器上,你可以从任何有互联网连接的机器上访问它。这比把沉重的笔记本电脑从办公室带到家,再到本地咖啡馆,再到办公室,最后再到家里要方便得多。
1. REAMDE
README 文件是任何项目的重要组成部分,但它们常常被忽视。我们都知道什么是 README 文件。我们不是孩子。那么我们为什么不花时间把它添加到我们的代码库呢?

由我提供的图片
也许是因为我们完全了解我们的代码是什么以及它是如何工作的,所以我们觉得不需要向别人解释。但情况并非总是如此。
比如,想象一下从一个项目中休息几个月,然后再重新开始。你会记得每个文件的作用吗?
个人来说,当我回到一个暂停已久的项目时,总感觉我在阅读别人的代码和想法。在这种情况下,README 文件可以通过告诉你你停在了哪里以及你在做什么来拯救项目。你不会因为感觉需要从头开始而放弃整个项目。
README 文件有两种类型:个人 README 和面向公众的 README。对于个人 README,其目的是帮助你,开发者,就像上面提到的情况一样。
另一方面,面向公众的 README 文件是为了那些可能对你的项目感兴趣的人。99% 的时候,当一个人遇到一个 GitHub 仓库时,他们不会根据代码的质量(他们几乎从不查看代码)来评判,而是根据它对他人的展示情况来判断。
把它看作是你项目的用户手册。它是人们访问仓库时首先看到的内容,它应该给他们一个清晰的了解你项目是什么以及如何使用它的印象。
此外,面向公众的 README 文件是你唯一一次有机会清晰而简洁地解释你在模型选择、验证方法和模型测试结果背后的思路。
如果你在经历了这一切之后仍然讨厌写 README 文件,你应该查看“Awesome README Templates” 仓库,它是为像我们这样的懒人准备的。
[## GitHub - matiassingers/awesome-readme: 精选的精彩 README 列表
精选的精彩 README 列表中包含的元素有:图片、截图等。
github.com](https://github.com/matiassingers/awesome-readme?source=post_page-----c082b1e882e9--------------------------------)
2. 克隆它还是叉取它?
当有人查看远程仓库时,可能会发生四种情况。最可能的情况(情况 0)是他们忽略它,或者如果他们心情好就给它一个星标。
在第一种情况下,如果 README 文件足够有说服力,他们可能会克隆它。

由我提供的图片
使用类似git clone https://github.com/username/awesome_repo的命令克隆远程仓库会在你的本地机器上创建一个awesome_repo的精确副本,让你可以访问项目的整个 Git 历史记录以及对所有文件的写权限。然而,如果你对这个awesome_repo的本地副本进行更改,其远程副本不会有所感知。
在第二种情况下,如果 README 的说服力更强,可能会有人进行分叉。

图片由我提供
当你在 GitHub 上分叉awesome_person的awesome_repo时,你将会在你的账户下拥有awesome_repo的精确副本。
你的 GitHub 页面将拥有一个新的your_username/awesome_repo仓库,其内容和历史记录与awesome_person/awesome_repo相同。如果你想对这个副本进行更改,你可以克隆your_username/awesome_repo,使其也在你的本地机器上。
有很多原因可能促使某人分叉他人的仓库。第一原因是通过提交拉取请求(见下文)向someone/awesome_repo做出贡献。另一个原因是基于原始代码创建新项目,而不影响原始代码。
一个值得注意的例子是Manim GitHub 社区,这是一个维护和文档更完善的 传奇 Manim 仓库的分支,后者由 Grant Sanderson(3Blue1Brown 及其所有视频的创作者)创建。
为了区分原始仓库和分支,GitHub 在仓库页面上添加了“forked from
original_repo”标签。
第三种情况是当你从另一台机器访问你自己的远程仓库时。例如,你将笔记本电脑留在了干洗店,你想在办公室继续进行项目工作。
在这种情况下,你所要做的就是克隆仓库以将其内容下载到办公室的 Mac 上。但是,如果你想同步你的更改,Mac 上的 Git 安装必须使用你的 GitHub 用户名。
3. 推送与拉取
在使用 Git 时,“推送”和“拉取”的货币单位是更改。让我们来看看这些命令的最常见用法。
在情况 0 中,你有一个包含许多提交和分支的本地仓库,并且你想将它们全部发送到 GitHub 上的新空远程仓库。首先,你需要使用命令git remote add remote_name https://github.com/username/repo_name.git在 Git 中指定远程仓库的网址。通常,remote_name 的替代名称是origin。

图片由我提供
然后,你通过调用git push进行第一次“推送”,这将把当前分支中的所有提交发送到 GitHub。恭喜!
在情况 1 中,你在仓库中进行了新的本地提交,并希望将它们发送到远程仓库,因此你调用了git push again。这个命令始终用于保持远程仓库与本地仓库的同步。
在案例 2 中,你可能在一个有许多贡献者的大项目上工作,并且你已经在你的机器上有一个两天前的项目副本。为了下载这段时间内其他人做出的新更改,你需要执行 git pull。

图片由我提供
拉取操作确保你的本地仓库与远程仓库保持同步,以防多人同时对其进行更改。
4. 拉取请求
拉取请求就像是敲邻居的门说:“嘿,我对你的草坪做了一些更改。看看你是否喜欢!”但我们谈论的不是草坪,而是仓库。
PR 是开源项目协作性质的重要组成部分。它们允许贡献者(普通程序员)向现有项目提出更改和改进建议,并让项目维护者在这些更改合并到主代码库之前进行审查和批准。
假设你想对 Scikit-learn 仓库提交拉取请求(我的甜蜜梦想 😃)。这是你必须遵循的工作流程(适用于对任何仓库提交拉取请求):
第 0 步:分叉 Scikit-learn,以便你在你的账户下拥有一个副本。
第 1 步:克隆你的 Scikit-learn 副本,以便你在你的机器上拥有一个副本。
第 2 步:创建一个新分支,以便在不影响主分支的情况下对你的副本进行修改。
第 3 步:进行更改——编写新代码、修复文档中的错误或错别字,或进行其他改进。
第 4 步:测试你的更改——确保你的更改没有错误,并按预期工作。
第 5 步:推送包含更改的分支(git push 将它们发送到你远程的 Scikit-learn 副本)。
第 6 步:创建请求——从你分叉的仓库中,点击“新建拉取请求”按钮。这将使你的更改暴露在 Scikit-learn 维护者的眼下。
第 7 步:等待维护者审查你的请求,可能需要很长时间,甚至到你头发变灰。
第 8 步:对他们接受或拒绝你的请求感到失望或高兴。
条件步骤 9:维护者合并你的请求,以便它将在下一个版本中包含。
5. GitHub 问题
将 GitHub 问题看作是 StackOverflow 线程的更文明和民主的版本。它们使人们能够跟踪和报告特定仓库的问题、错误或功能请求。
仓库的维护者可以将问题分配给个人或团队,并根据其严重性和影响为其打标签。
简而言之,GitHub 问题是用于软件项目管理的工具,有助于简化沟通和协作。例如,最受欢迎的仓库有问题模板,确保提出的问题是有用的、可审查的和可回答的。
这些模板旨在帮助你提出有针对性的问题,而不是漫无目的或无焦点的问题,这有助于加快问题解决的过程。
6. GitHub Actions
GitHub Actions 的兔子洞非常深。但我们只会在表面浅尝辄止,学习一些基础知识。
作为数据科学家,你有无数事情要做,比如分析数据、构建模型、编写代码、喝咖啡……所有这些可能会变得相当重复和压倒性。
但如果你了解 GitHub Actions,它就像是一个处理所有仓库相关任务的个人助理。通过它们,你可以设置在特定事件发生时自动运行的工作流程。
例如,你可以创建一个动作,在训练新模型时自动验证模型在测试数据上的性能。或者,你可以设置一个动作,当你推送新的仓库发布时部署模型。
正如你所想象的,还有许多其他工作流程可以这样自动化。以下是一些示例:
-
运行测试 — 每当你推送新代码时,自动运行单元测试或集成测试。
-
训练模型 — 定期(例如,每日、每周等)重新训练现有的机器学习模型。
-
执行预处理 — 运行常见的处理任务,如数据清理、归一化、缺失值插补、特征提取等。
GitHub Actions 很容易设置。它们被定义在 YAML 文件中。下面是一个运行单元测试的动作示例 YAML 文件:
name: Run Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run unit tests
run: |
python -m pytest tests/
要理解上述内容,可以阅读官方 GitHub 文档 这里。
7. CI/CD 管道
CI/CD(持续集成和开发)是从软件工程中的 DevOps 概念中借用的。
CI/CD 管道是一系列自动化步骤,你的代码会经过这些步骤,以确保在发布到生产环境之前达到最佳状态。我们可以应用相同的过程,确保我们的机器学习模型及其训练/部署代码的最高质量。

图片由 Made With ML 提供。MIT 许可。
换句话说,CI/CD 管道就像运转良好的机器,每当你做出新的提交时,它们就会开始运转。在提交合并之前,它会经历一系列的步骤,在多个方面测试代码和模型。
你猜对了,管道中的每一步都被定义为一个 GitHub 动作。让我们从直升机的视角来看看一个示例机器学习生命周期 CI/CD 管道:
-
数据收集和预处理 — 从各种来源收集数据并在训练前进行转换。
-
模型训练 — 机器学习模型在预处理的数据上进行训练。
-
模型评估 — 在一组单独的数据上评估训练好的模型,以评估其性能。
-
模型部署 — 最佳性能的模型被部署到生产环境。
-
持续监控 — 部署的模型被持续监控,以确保其表现符合预期。
每一步都值得独立成文或甚至开设完整的课程。但简而言之,CI/CD 管道在大多数(活跃的)机器学习项目中是默默无闻的英雄。
结论
在我的 Git 文章中,我提到 Git 是开发机器学习系统中最关键的工具之一,因为它是协作的首选软件。
同样的论点适用于 GitHub,因为没有它,Git 的协作特性无法真正展现。GitHub 提供了一个存储 Git 跟踪的仓库的地方,以便其他人可以访问它。
尽管 Git 和 GitHub 最初并未专为数据科学和机器学习工作设计,但现在有了像DVC和DagsHub这样的辅助工具,使得它们在实际操作中非常相关。
感谢阅读!
喜欢这篇文章和它那奇特的写作风格?想象一下,拥有更多这样的文章,全部由一位才华横溢、迷人、有趣的作者(顺便说一句,就是我:))编写。
仅需 4.99 美元的会员费用,你不仅能访问我的故事,还能获取来自 Medium 上最优秀智慧的知识宝库。如果你使用我的推荐链接,你将获得我超级 nova 的感激和一个虚拟的击掌,以支持我的工作。
[## 使用我的推荐链接加入 Medium — Bex T.
获取对我所有⚡优质⚡内容的独家访问权限,并在 Medium 上无限制地浏览。通过购买我一杯…来支持我的工作。
图片由我提供。通过 Midjourney 制作。
GLIP:将语言-图像预训练引入物体检测
原文:
towardsdatascience.com/glip-introducing-language-image-pre-training-to-object-detection-5ddb601873aa
🚀Sascha’s Paper Club
Grounded Language-Image Pre-training 由 L. H. Li 等人著
·发布于 Towards Data Science ·阅读时间 9 分钟·2023 年 9 月 1 日
--
今天我们将深入探讨一篇建立在CLIP语言-图像预训练成功基础上的论文,并将其扩展到物体检测任务:GLIP——Grounded Language-Image Pre-training。我们将涵盖论文的关键概念和发现,通过提供更多背景信息和对图像及实验结果的注释,帮助大家更容易理解。让我们开始吧!

图片来源于 publication 由 Sascha Kirch 创作
论文: Grounded Language-Image Pre-training 由
Liunian Harold Li 等,2021 年 12 月 7 日
资源: GitHub
类别: 表示学习、物体检测、短语定位、基础模型
其他讲解:
[BYOL] — [CLIP] — [Depth Anything] — [Segment Anything] — [DINO] — [DDPM]
大纲
-
背景与上下文
-
声明的贡献
-
方法
-
实验
-
进一步阅读与资源
背景与上下文
GLIP (Grounded Language-Image Pre-training)是一个多模态语言-图像模型。类似于 CLIP (Contrastive Language-Image Pre-Training),它通过对比预训练学习语义丰富的表示,并在其模态间进行对齐。虽然 CLIP 在图像级别上学习这些表示,这意味着一句话描述整张图像,GLIP 旨在将这种方法扩展到对象级别的表示,即一句话可能对应图像中的多个对象。识别文本提示中的单个令牌与图像中对象或区域的对应关系的任务称为 短语基础。因此,GLIP 中的“Grounded”一词。
因此,GLIP 的目标是:
-
统一短语基础和对象检测,用于大规模预训练。
-
提供一个灵活的框架进行零-shot 对象检测,其中“灵活”意味着不局限于固定的类别集合。
-
构建一个预训练模型,可以无缝迁移到各种任务和领域,以零-shot 或少-shot 的方式。
你可以用这样的模型做什么? 你可以使用文本提示在给定的输入图像中找到对象或感兴趣的区域。最棒的是:你不受限于预定义的类别。

图 1:GLIP 对不同图像和提示格式的输出。 图片来源 + Sascha Kirch 的注释
你可以进一步处理这些检测结果(例如,将其输入到跟踪系统中)或创建一个包含特定类别的自定义数据集,用于训练你自己的监督检测系统。不仅可以覆盖稀有或非常特定的类别,还可以节省大量创建人工标签的时间和成本。正如我们稍后将看到的,GLIP 的作者有一个类似的想法,通过引入教师-学生框架进一步提升性能。
GLIP 已经被许多其他深度学习项目和领域采纳。例如,GLIGEN(Grounded-Language-to-Image-Generation)使用 GLIP 来条件化潜在扩散模型的图像生成,以提高可控性。此外,GLIP 还与其他模型(如DINO(DETR with Improved deNoising anchOr)结合使用
将盒子和SAM(Segment Anything Model)分别扩展到GroundingDINO和Grounded-Segment-Anything。GLIPv2通过视觉语言理解扩展了最初的 GLIP 模型,不仅改进了短语定位,还使视觉问答任务成为可能。

由 Sascha Kirch 提供的论文解析
查看列表7 篇故事


声明的贡献
-
大规模预训练以结合短语定位和目标检测
-
提供对目标检测和短语定位的统一视角
-
深度跨模态融合以学习高质量的语言感知视觉表示,并实现卓越的迁移学习性能。
-
提出在深度视觉语言融合(如 GLIP)中,提示调优比在浅层融合网络(如 CLIP)中更有效
方法
对 GLIP 可以做的事情有一个粗略的了解后,让我们更详细地查看这篇论文。
架构概述
从高层次来看,GLIP 的架构在某种程度上与 CLIP 的架构非常相似,因为它也包括一个文本编码器、一个图像编码器以及某种对文本和图像特征相似性的对比学习。GLIP 的架构如图 2 所示。

图 2:框架架构。 图片来源 + Sascha Kirch 注释
GLIP 在文本和图像编码器之后添加了一个语言-图像感知的深度融合模块。该模块执行跨模态注意力并提取进一步的特征。对生成的区域特征和单词特征计算余弦相似性。在训练过程中,匹配对的相似性被最大化,而不匹配对的相似性被最小化。与 CLIP 中匹配对位于相似性矩阵对角线上的情况不同,在 GLIP 中,匹配不是在句子级别进行的,而是在(子)词级别进行的,通常导致非对角线位置。
短语定位表述为对象检测问题
作者指出,短语定位(即将单词与图像中的对象/区域关联起来)的问题可以表述为对象检测目标,其中标准的损失目标是:

定位损失关注的是预测边界框的质量,这取决于格式,可能是框的大小和位置。分类损失是在统一过程中关键的一部分。通过计算文本-图像特征的相似性得分的 logits,而不是图像分类器的 logits,可以使用相同的损失目标进行训练。

不同的模型变体
训练了五种不同的模型,以展示作者设计选择和模型规模的效果:

图 3:模型变体。 图片来源 + Sascha Kirch 注释
教师-学生预训练
为了提升 GLIP 的性能,作者在人工标注的数据 GoldG 上训练了 GLIP-T (C) 模型(见图 3),以从互联网的文本-图像对中生成定位数据。他们称这个模型为教师模型,并随后训练一个学生模型,将教师模型使用的数据加上教师生成的数据输入给学生模型。见图 4 了解更多说明。
注意:尽管使用了教师和学生的术语,但这与知识蒸馏中的过程不同,知识蒸馏中一个较小的学生模型被训练以匹配较大教师模型的输出。

图 4:教师-学生预训练。图片由Sascha Kirch 提供
有趣的是,正如我们在实验中看到的,学生在许多(但不是所有)数据集上超越了教师,无论是零样本检测还是少样本检测。为什么会这样? 论文假设,即使教师提供了低置信度的预测(他们称之为“有根据的猜测”),它仍然会成为学生所使用的生成数据集中的真实值(他们称之为“监督信号”)。
[## 每当 Sascha Kirch 发布新内容时获取电子邮件 🚀
每当 Sascha Kirch 发布新内容时获取电子邮件 🚀 如果你想了解更多关于深度学习的内容或只是想保持更新……
medium.com](https://medium.com/@SaschaKirch/subscribe?source=post_page-----5ddb601873aa--------------------------------)
实验
GLIP 论文展示了各种实验和消融研究,主要关注于:
-
零样本领域转移
-
数据效率
-
提示工程
我对一些结果和它们的呈现方式有一些疑问,我会在注释中指出这些问题。我不想贬低 GLIP 的成就,而是持批判的眼光来看待它。
现在让我们深入细节!
零样本领域转移
首先,我们将查看零样本领域转移的结果。在这个任务中,目标是分析预训练的 GLIP 模型在不同数据集(即 COCO 和 LVIS)上的表现,与那些经过监督训练的模型的基线进行比较。然后,对预训练的 GLIP 进行进一步的微调,并在测试数据集上进行评估。
在图 5 中,我们看到 COCO 上的零样本领域转移结果。我们看到所有 GLIP 模型在零样本性能上优于监督的 Faster RCNN。我们还看到 GLIP-L 超越了之前的 SOTA(在论文发布时)。我们看到较大的学生模型 GLIP-L 超越了教师模型 GLIP-T(C)。

图 5:COCO 上的零样本领域转移和微调。图片来源 + Sascha Kirch 的注释
接下来,我列出了在阅读这些结果和论文中提出的声明时的疑虑,论文中提到 GLIP-L 超越了最佳监督模型 SoftTeacher。
-
具有比 SoftTeacher 更好指标的模型是 GLIP-L,领先 0.2 分。这一小的差距可能不是 GLIP 新方法的结果,而可能是由于训练超参数的一些差异。
-
GLIP-L 甚至没有使用从教师模型生成的数据(Cap4M 或 Cap24M),他们将其展示为一个好的解决方案。
-
GLIP-L 在比 SoftTeacher 更大的训练数据集上进行训练。
我认为不同 GLIP 模型和他们自己训练的 DyHead-T 的结果比较是完全可以的,只是在不同方法和模型在不明确或不同约束下比较时,我总是有一些疑虑。
在图 6 中,我们可以看到 LVIS 数据集上的零样本领域迁移性能。我们可以看到,最大的 GLIP 模型 GLIP-L 超越了所有其他呈现的监督模型。

图 6:零样本领域迁移到 LVIS。 图片来源 + 注释由 Sascha Kirch 提供
最后,GLIP 在 Flickr30K 实体上的短语定位性能与 MDETR 进行了比较(见图 7)。两种学生模型,GLIP-T 和 GLIP-L,都超越了 MDETR 基线。

图 7:Flickr30K 实体上的短语定位性能。 图片来源 + 注释由 Sascha Kirch 提供
数据效率
另一个实验关注数据效率。这个实验旨在展示当在一定数量的任务特定数据上微调一个预训练模型时,性能(以平均精度衡量)如何变化。在图 8 中,这些模型在 13 个不同的数据集上进行了评估,其性能作为在 13 个数据集上的平均精度进行报告。结果包括 0-shot、1-shot、3-shot、5-shot、10-shot 和“all”-shot(我怀疑“all”-shot 是否是完整微调的官方术语,但我想你明白我的意思 😅)。

图 8:数据效率。 图片来源 + 注释由 Sascha Kirch 提供
提示工程
与 CLIP 类似,作者还报告了模型性能与输入文本提示的表述之间的相关性。他们提出了两种技术来提高预训练模型的性能,无需重新训练模型的权重:
-
手动提示调整
-
提示调整
手动提示调整的想法是提供额外的描述性词语来提供更多的上下文,见图 9:

图 9:手动提示调整示例。 图片来源 + 注释由 Sascha Kirch 提供
手动提示调整始终可以用来提高性能,这意味着无论模型是完全微调还是在零样本或少样本场景中使用,效果都不会受到影响。
第二种方法,提示调整,需要访问下游任务的真实标签,特别适用于每个检测任务只有一个提示的场景(例如:“检测汽车”)。在这种情况下,这个提示会首先通过文本编码器转换成特征嵌入。然后,图像编码器和深度融合模块被冻结,仅使用真实标签优化输入嵌入。优化后的嵌入将作为模型输入,文本编码器可以被移除。
图 10 显示了对各种 GLIP 模型进行此提示调整的结果。当应用于具有深度融合模块的模型时,提示调整几乎与微调模型权重的效果相同。

图 10:提示调整的有效性。图片来源 + Sascha Kirch 的注释
进一步阅读和资源
如本文开头所述,GLIP 已被广泛应用于大量项目中。
以下是一些建立在 GLIP 基础上的论文:
如果你想深入了解 GLIP 的实现以及其他建立在 GLIP 基础上的有趣项目,这里有一个仓库列表:
这是我关于 CLIP 基础模型的文章之一,采用与本文相同的总结方法:
论文总结——从自然语言监督中学习可转移的视觉模型
towardsdatascience.com
全球数据晴雨表:目前全球开放数据的状态如何?
各国在多大程度上采用了为公共利益服务的数据政策和系统?
·
关注 发表在Towards Data Science ·8 min read·2023 年 7 月 26 日
--
图片由Stephen Picilaidis提供,来源于Unsplash
我最近读了一本关于全球城市开放数据政策采用的书。它叫做超越透明(在此链接公开提供),包含了展示早期 2010 年代开放数据政策成功与障碍的案例研究。正如书名所暗示的那样,提供可访问和免费的数据集是迈向更透明政府的一步,但不仅仅如此。这本书讨论了这些数据如何推动创新、提高政府效率,并鼓励新的公民习惯,例如更多的市民参与。数据专业人士,包括工程师和数据科学家,也围绕这些新数据集形成了自己的解决方案,建立了更好的模型和应用程序。要了解这些新的公民技术生态系统的一些示例,可以查看构建更智能的芝加哥章节以及来自 Data SF 或芝加哥数据门户网站的分析工作!另一个个人喜欢的章节是数据驱动的城市,讲述了如何通过收集 311 电话来帮助纽约市建模紧急服务和资源分配。
自 2010 年代以来,数百个国家已经通过了自己的开放数据法律。全球数据晴雨表(许可证:知识共享署名 4.0)衡量了 109 个国家的开放数据状态,回答“各国在多大程度上管理公共利益数据?” 这项研究使用了定量指标和定性描述的结合,以提供世界在开放数据方面的清晰图景。这是一项极为详尽的工作,所以我认为花一些时间通过这个故事来探索它的一部分是很有趣的。
快速说明:对于这个故事,我使用了 Observable notebooks(基于 JS)来进行可视化,并使用 Jupyter 进行数据处理;两个链接在文末。
让我们开始吧!
指数概览:各国的排名如何?
总体国家指数范围从 0(不存在)到 100(展示最佳实践)。

开放数据总体指数(作者提供的图片)
上图显示了相当大的差异。得分最高的国家(70 分)是美国,而得分最低的(10 分)是土库曼斯坦,而平均分数为 38.51。该指数通过检查每个国家在 4 个方面或“支柱”上的开放数据实践来生成:治理、能力、可用性以及使用和影响。对于每个支柱,国家提供了关于特定元素(例如数据保护框架)的存在、元素(质量相关特征和开放数据特征)以及程度(特定框架在一个国家的局限性和适用性)的信息。研究还跟踪每个支柱的次级指标,每个指标的得分满分为 100。因此,总体指数得分为 100 将代表所有这些主要和次级指标的“规范理想”。
得分相似的国家在指标上仍有巨大的差异
让我们关注那些指数接近平均值(35 至 45)的国家。其中有 20 个,包括阿尔巴尼亚和科索沃,以及来自世界各地的国家:牙买加、哈萨克斯坦、巴拉圭、菲律宾、秘鲁、泰国和南非等。
尽管整体指数相似,但当查看它们在各模块中的平均分数时,它们的表现并不那么一致。气象仪除了柱状评分外,还汇总了主题或模块评分,这 7 个模块涵盖了以下领域的开放数据实践:健康与 COVID-19、土地、公共财政、采购、气候行动、政治诚信和公司信息。以下是这 20 个国家在气候行动、政治诚信和采购模块的情况:

气候行动(图片由作者提供)

政治诚信(图片由作者提供)

采购(图片由作者提供)
许多国家存在不一致:
-
牙买加在气候行动方面得分最高,但在采购方面仅得到了较低的分数。深入查看气候指标发现,牙买加通过其统计机构提供环境数据,包括“降雨量、阳光、污染事件、温室气体、受保护森林面积、海平面”等指标,并且“没有数据缺口的证据”。然而,在采购方面,规划阶段没有公开信息,数据质量较低:没有包含中标公司的名称/标识符或“合同支出”信息。
-
在阿尔巴尼亚,详细的采购信息可以在线获取(链接在这里),但合同实施情况没有涵盖,数据仅部分为机器可读。政治融资数据也可用,但不包含政党和候选人的收入数据或历史跟踪信息。
在所有这些模块上取得进展将导致政府更加透明和高效。例如,拥有更好的采购数据可以让公民分析资金在项目各阶段的支出情况或推动更公平的分配。同样,透明的游说数据和资产申报可以促进政治诚信的问责。气候信息使公众能够了解生物多样性、排放或脆弱性等方面的情况。
治理框架存在但大多是碎片化的
治理是数据集的主要支柱之一。它回答的问题涉及评估指导数据保护和管理的政策和框架的状态。治理研究跟踪了几个指标:其中一个是“开放数据政策”,还有“数据保护”、“数据共享框架”、“数据管理”等。以下是各国在这些治理指标上的得分情况:


109 个国家的治理指标(作者提供的图片)
很多国家在数据保护、开放数据政策或数据管理等指标上表现中等。约 92%的调查政策具有共同的开放数据定义,72%的国家设有某种数据管理框架,90%的国家提供数据保护法规(报告 这里)。许多国家在数据同意、申诉权利、访问或更正等问题上提供了部分或完全的法规。
虽然存在数据治理框架的形式,但全球开放数据显示,各国仍然面临严重限制。例如,只有 24%的框架涉及位置数据问题,只有 31%涉及算法决策(报告中也有 相关信息)。这些国家大多数位于欧洲和北美:这两个地区共有 17 个国家回答“是”的问题为“框架是否明确涵盖了位置相关数据的保护”,以及 31 个国家中有 20 个回答“框架是否涉及算法决策”。
最后两个指标,可访问性和语言覆盖率,评估确保数据对残疾人士可访问以及在每个国家的官方语言中可用的法规。后一项对有多种语言的国家尤为重要,但仍然是碎片化的:109 个国家中有 13 个在这一类别中得分 100(拥有法律效力的框架)。
处理 COVID-19 响应是一项挑战,但也是数据治理的一个机会
COVID-19 疫情测试了许多数据系统,特别是地方级别的系统。研究不仅衡量了疫苗接种数据的可用性,还包括实时医疗数据(如 ICU 床位)和重要统计数据。重要统计数据包括出生和死亡信息、历史跨度以及这些数据在国内的本地可用情况。这里是一个展示各国表现的热图:


健康与 COVID-19 模块得分(图片作者提供)
在数据集中大多数国家的疫苗接种数据都可以获取,但也存在一些问题。只有大约 50%的可用数据集按年龄分解,约 33%按性别分解(报告统计)。此外,实时医疗数据仅在约 50%的国家中可用,而且提供可用床位信息的国家更少。在一些国家,这类数据只有在疫情期间首次提供,这使得各国可以开始在未来更好地构建医疗报告。
用户探索数据的难易程度如何?深入分析显示,在 109 个国家中,有 61 个国家没有提供允许公民访问重要统计数据的官方开放工具。同样,109 个国家中有 63 个没有提供官方且可获取的 COVID-19 疫苗接种数据。57 个国家还未提供机器可读的数据(如 CSV 格式),而这对于数据的方便分发和再现至关重要。
未来开放数据的前景是什么?(及资源)
报告的一个主要结论是全球层面开放数据环境相对缺乏(回顾整体平均指数:38/100)。正如我们在这个故事中所看到的,拥有完善的开放数据法律是至关重要的。除了全球数据晴雨表正在进行的研究,还有其他库追踪全球开放数据问题的新立法。由 The Gov Lab 提供的开放数据现状就是其中之一。所以,查看一下按部门或合作类型划分的法律吧!
总体而言,《全球数据晴雨表》报告揭示了在采纳这些法律时面临的实际挑战,包括数据空白或数据不可获取的问题。我们研究的一个显著例子是健康数据的发布和管理,特别是在紧急情况下,如 COVID-19,及时信息至关重要。然而,这个例子也展示了新的挑战如何推动数据促进透明度,并让公民保持知情,这非常有前景!总体而言,报告指出了每个国家需要关注的非常具体的领域,提供了具有背景的策略以改善数据收集,同时仍提供了关于开放数据当前挑战的宏观视角。
这里是笔记本(Jupyter 和 Observable)。
感谢阅读!
如何在 R 中分组数据:超越“group_by”
原文:
towardsdatascience.com/going-beyond-group-by-how-to-group-data-in-r-af30f19c1ff2
从初学者到高级用户,掌握这些分组工作流程
·发表于 Towards Data Science ·8 分钟阅读·2023 年 2 月 15 日
--

Camille San Vicente拍摄的照片,来源于 Unsplash
数据分组允许你对数据集的子集进行操作,而不是对整个数据集进行操作。处理分组数据是数据分析中的一个关键方面,并且在数据科学中有着几乎无限的应用。
在 R 中创建和操作分组有很多方法。在本文中,我将解释 dplyr 包中的分组工作流程,从基础到更高级的功能。
到最后,你应该掌握从分组数据中提取有价值见解所需的所有工具。本文中的所有代码也可以在 GitHub 上找到。
dplyr 中的基本分组
在 dplyr 中分组数据时,你主要会使用group_by函数。你可以用它来指定一个或多个变量以进行数据分组。以下是一个使用 palmerpenguins 包中的 penguins 数据集的示例。你可以通过运行install.packages("palmerpenguins")来安装这个包。加载包后,通过library(palmerpenguins),你将能够按名称访问penguins数据集,如下所示。
library(tidyverse)
library(palmerpenguins)
# The Palmer Penguins dataset
penguins

对数据集进行快速查看,可以帮助我们识别适合分组的分类变量。在这里,我们可以按物种分组,这是一个有三个水平的因素。在控制台查看分组数据时,我们可以看到列名上方清晰地打印出分组结构。我已用红色标出。
# Grouping by species
penguins_species <- penguins %>%
group_by(species)
penguins_species

我们还可以通过group_keys函数访问数据中的分组变量的名称和级别。在对分组数据使用此函数时,它会返回一个 tibble,其中每个分组变量作为一列,每个组级别作为一行。
# Getting the grouping structure with group_keys
group_keys(penguins_species)

现在数据已分组,我们可以对其应用其他函数。分组数据的一个常见用途是使用summarise函数计算总结统计量。在下面的示例中,summarise返回每种企鹅的平均体重。这为我们提供了一个简洁的总结表格,几乎不需要额外的努力。
# Getting the mean body mass for each group
penguins_species %>%
summarise(mean_body_mass = mean(body_mass_g, na.rm = TRUE))

你还可以对分组数据使用其他 dplyr 函数,如mutate、filter、select等。虽然深入探讨所有这些函数可能需要整篇文章,但 dplyr 分组数据说明文档是了解这些函数如何处理分组数据的有用指南。
按多个变量进行分组
按多个变量对数据进行分组的过程很简单;只需在group_by中添加另一个变量名称。例如,可以按物种和性别对penguins数据进行分组。
# Grouping by more than one variable
penguins %>%
filter(!is.na(sex)) %>%
group_by(species, sex) %>%
summarise(mean_body_mass = mean(body_mass_g, na.rm = TRUE))

首先,我们过滤掉性别列中的 NA 值,然后按物种和性别进行分组。对这个新分组的数据使用相同的总结函数,会给出每个分组变量的两个级别组合的平均体重测量值。
在 group_by 中创建变量
从group_by的基本用法开始,我们可以进入更高级的分组工作流程。
一个有用但被低估的 dplyr 功能是你可以在group_by中创建新的分组变量。
假设我们想要获取所有未在penguins数据中编码的因子的企鹅的总结统计量。例如,penguins数据集由三个研究的观察结果组成,但清理后的数据中不包含研究标识符。我们如何计算每个研究中企鹅的平均体重?
一个明显的解决方案是为数据中的每一行创建一个带有研究标识符的新变量,按该变量分组,然后总结。
penguins %>%
mutate(StudyName = penguins_raw$studyName) %>%
group_by(StudyName) %>%
summarise(mean_body_mass = mean(body_mass_g, na.rm = TRUE))

然而,你可以通过使用以下语法在group_by中创建分组变量,将前两步合并为一步:
penguins %>%
group_by(StudyName = penguins_raw$studyName) %>%
summarise(mean_body_mass = mean(body_mass_g, na.rm = TRUE))
这给出的结果与长格式版本完全相同,同时节省了宝贵的空间。这使得它成为在分析中缩短长管道序列的一个绝妙技巧。
使用 group_split 按组拆分数据
你可能会遇到需要将组拆分为单独 tibble 的情况。例如,你可以将企鹅数据中的每种物种写成一个单独的数据文件。为此,你需要group_split。
顾名思义,group_split将数据拆分成单独的 tibbles;每个分组变量 species 的每个级别对应一个。它将这些 tibbles 作为列表返回,我们可以将其传递到一个单独的函数中,逐个写入每个文件。
# Split up penguins data by species
species_list <- penguins %>%
group_split(species)
# Get the names of each species
species_names <- group_keys(penguins_species) %>%
pull(species)
# Write the separate datasets to csv, giving unique names
map2(species_list, species_names, ~ write_csv(.x, paste0(.y, ".csv")))
为了在这里写出文件名,我们进一步利用group_keys函数获取分组变量的每个级别的名称。使用pull然后将这些名称转换成一个向量,我们可以将其传递到write_csv函数中。
然后,我们对species_list中的每个数据集应用write_csv函数,为每个 csv 文件指定一个来自species_names的合适文件名。map2函数使我们能够对每个数据集重复此操作。
像这样使用group_split可以节省大量的手动过滤和编写工作。否则可能是一个繁琐的任务(特别是在数据集中有更多组的情况下),通过最小的努力也能完成。
使用with_groups暂时分组
有时,当为一个目的分组数据时,我们随后希望丢弃分组结构以继续进行进一步的分析。标准的做法是使用ungroup函数。在下面的例子中,我们按体重筛选每种物种中最大的三只企鹅。在这个操作后取消分组结构去掉了分组结构。
heavy_penguins <- penguins %>%
group_by(species) %>%
slice_max(body_mass_g, n = 3, with_ties = F) %>%
ungroup()
group_keys(heavy_penguins)
# A tibble: 1 × 0
然而,通过一个变量进行分组、操作数据,然后再取消分组可能会给你的代码添加不必要的步骤。幸运的是,有一种更简短的方法可以使用with_groups函数暂时执行分组操作。
首先,你在.groups参数中指定一个分组变量。然后,你指定一个你想对每个组应用的函数,使用 tidyverse 特定的语法。例如,在这个例子中,我使用波浪号(~)符号表示我想应用的函数。然后,我继续编写我的函数,使用“.”符号作为数据的占位符。
这种语法对于那些学习了 tidyverse 的 purrr 包中的 map 函数的人来说是熟悉的。这些函数允许你重复其他函数,以列表或向量的每个元素作为输入。它们非常值得学习,并有助于理解 tidyverse 包中的高级功能。
如何在 R 中使用 Map 函数进行数据科学
从 tidyverse 中学习强大的函数式编程工具
towardsdatascience.com
运行with_groups给我们提供了与较长的“group_by, slice, ungroup”工作流程相同的输出。检查输出,我们可以看到with_groups在应用我们的函数后删除了数据中的分组结构,就像之前的例子一样,使得在完整数据集上进行进一步分析成为可能。
heavy_penguins_temp <- penguins %>%
with_groups(.groups = species, ~ slice_max(., body_mass_g, n = 3, with_ties = F))
group_keys(heavy_penguins_temp)
# A tibble: 1 × 0
附加:使用 group_map 应用分组函数
虽然 with_groups 与 map 函数有些语法上的相似,但也有一个专门为分组数据设计的 map 特例;即 group_map。我们可以通过使用它重复前面的 slice_max 函数来检查它的行为。
group_map 适用于已经分组的数据。它将每个分组操作的结果作为列表中的单独 tibbles 返回,类似于 group_split 的结果。你也可能注意到输出中没有物种列。这是因为 group_map 默认会从输出中删除分组变量。然而,也可以通过添加参数 .keep = TRUE 来保留它们。
penguins %>%
group_by(species) %>%
group_map(~ slice_max(., body_mass_g, n = 3, with_ties = F))

在我自己的代码中,我不常使用 group_map,通常选择更常规的 map 函数或更简单的分组工作流程。不过,在你以分组数据开始并希望得到转换后、分开的数据集的情况下,它是一个简洁的快捷方式。
总结:何时使用每个 dplyr 分组函数
总结:dplyr 中的分组函数是从数据中提取大量价值的一种好方法,几乎不费力。回顾一下它们的用途:
-
group_by向数据集添加一个或多个分组。你可以在group_by内创建分组变量, -
group_keys返回 tibble 的分组结构 -
ungroup从数据中移除分组 -
group_split将数据集按组拆分为不同的 tibbles -
with_groups临时将数据分组以执行单一操作 -
group_map将函数应用于分组数据,并将每个组的结果作为列表返回
即使你只使用 group_by,也可以进行各种汇总统计、组内过滤等操作。许多 R 用户单独使用这个函数也能很好地完成工作。
也就是说,进一步探索我们已经探讨的其他分组工作流程,会给你更多选项。如果你在下次分组数据时使用它们,你将节省更多空间并减少分析步骤。
想要阅读我关于编程、数据科学等的所有文章吗?在这个链接注册成为 Medium 会员,即可全面访问我所有的文章以及 Medium 上的其他故事。这也直接帮助我,因为我会从你的会员费用中获得一小部分贡献,对你没有额外费用。
你还可以通过这里订阅,每次我发布新文章时,直接将所有新文章送到你的收件箱。感谢阅读!
使用 DAX 中的库存度量迈出下一步
原文:
towardsdatascience.com/going-the-next-step-with-stock-measures-in-dax-cb49d4e21c13
使用库存度量时,我们可能会遇到一些奇怪的效果。让我们看看这种情况是如何出现的以及如何解决它。
·发表于Towards Data Science ·7 min read·2023 年 11 月 28 日
--

照片由Firmbee.com提供,来源于Unsplash
介绍
当数据不能在时间上进行汇总时,通常使用库存计算。
例如,随着时间的推移将我的银行账户余额累加起来会是个坏主意。对我来说可能是个好主意,但对我的银行来说不是。
目前,我正在为一个客户项目创建一个人力资源数据的报告解决方案。
一个关键指标是人员数量,它也是一个库存度量,因为我们已经在事实表中按月份存储了人员数量。
这看起来是一个简单的任务。
但有时我们必须多走一步才能得到正确的结果。
现在,让我们深入了解一下。
基础库存度量
首先,库存度量的正确名称是半加性度量。这是因为,如上所述,它们不会在时间上汇总数据,而是在所有其他维度上汇总。
计算人员数量的基础半加性度量是简单的:
Headcount =
VAR LastDataDay = LASTNONBLANK('Date'[Date]
,[Headcount (Base)])
RETURN
CALCULATE([Headcount (Base)]
,LastDataDay
)
度量[Headcount (Base)]包含简单的聚合(SUM)来获取所需结果。
我总是使用基础度量,以便可以包含附加逻辑,这些逻辑可以在后续度量中重复使用。
一个例子是,当用户希望查看以千或百万为单位的数据时,使用缩放因子,而不需要 Power BI 中的自动显示单位,这会添加奇怪的缩放因子,如千的 T 或百万的 M。
然后,我使用LASTNONBLANK()函数获取人员数量(基础)度量的最后一个结果日期。该函数的结果随后作为筛选器应用于 CALCULATE()调用中,以获得最终结果。
你可以在 DAX Patterns 上找到对此解决方案的详尽解释。文章链接在下面的参考文献部分。
在那里,你可以找到我写的另一篇文章的链接,当时我遇到了其他半累加度量的场景。
这是我们虚构组织层级中一名经理的员工人数结果:

图 1 — 我们的基础度量的结果(作者绘制)
如你所见,员工人数在一月与二月以及四月与五月之间发生了变化。
尽管如此,总计仍然显示最后已知的员工人数,即十。
到目前为止,一切正常。
奇怪的结果
在我的情况下,每个员工都被分配到一个组。
当我使用相同的度量来查看员工在这些组之间的分布时,我得到了这个结果:

图 2 — 按组分割的员工人数(作者绘制)
初看起来似乎没问题。
但当我将每一行的值相加时,得到的是十一。
为什么会这样?
当你考虑每行的筛选上下文时,你会找到答案。
继续之前,先考虑一下。
.
.
.
.
原因是我们查看的是整年度的数据。
看一下下面的图示,它展示了员工人数按组随时间的发展:

图 3 — 按组的全年员工人数视图(作者绘制)
当我们查看每个月时,我们可以看到,四月底时“组 1”中的一名员工离开了公司。因此,他会在离开前算作一个员工人数。
但我们在二月份有了一名新员工加入“组 4”。
到年底时,团队仍有十名成员。这是仅查看年底员工人数时的视图。
基础度量总计为十,因为该度量在按组时没有知识(未应用筛选)。
但在按组计算结果时,"组 1"的最后已知结果是四月份的结果。这个结果在查看整个年度时会显示在按组结果中。
我可以通过稍微更改度量来证明这一点:

图 4 — 修改度量的检查结果(作者绘制)
正如你在第一行所看到的,来自员工人数(基础)度量的最后一个有结果的日期是 2022 年 4 月 30 日。
这是查看“组 1”时,来自员工人数(基础)度量的最后一个有结果的日期。
因此,“组 1”的一名成员被列入结果中。
和许多其他情况一样,这涉及到定义的问题:
-
当我们查看整个年度时,我们会发现,在年度的某个时刻,我们有一名员工在“组 1”中。
-
当我们仅查看年底的最后一天时,不应该得到“组 1”的结果。
如果第一种定义是正确的,我们应该隐藏总计行,因为它会混淆用户。
如果第二种定义是正确的,我们应该将度量更改为仅显示年底的员工人数。
迈出下一步
为了实现第二个定义,我定义了一个新的 Measure,逻辑如下:
-
当前 Filter 上下文中的最后一个日期是什么?
-
Fact 表中最后一个包含数据的日期是什么?
-
如果当前 Filter 上下文中的最后一个日期不包含数据(Point 2 < Point 1),则显示 Fact Table 中的最后已知数据。
-
如果当前 Filter 上下文中的最后一个日期包含数据(Point 2 >= Point 1),则显示当前 Filter 上下文中最后一个日期的结果。
这是我想出的 Measure:
Headcount (By End of Period) =
VAR LastActiveDate = LASTDATE('Date'[Date])
VAR LastDateKeyWithData = CALCULATE(MAX('FactOrg'[DateKey])
,REMOVEFILTERS('FactOrg')
)
VAR LastDateWithData = LOOKUPVALUE('Date'[Date]
,'Date'[DateKey]
,LastDateKeyWithData
)
RETURN
IF (LastDateWithData < LastActiveDate
,CALCULATE([Headcount (Base)]
,REMOVEFILTERS('Date')
,'Date'[DateKey] = LastDateKeyWithData)
,CALCULATE([Headcount (Base)]
,LastActiveDate
)
)
我必须在 Measure 中添加检查,以避免在查看没有 31 月 12 日数据的年份时得到空结果。
这个 Measure 返回以下结果:

图 5 — 新 Measure 的结果(图示作者提供)
如预期的那样,查看整年时,Group 1 没有员工数。
但在查看每个月时,它返回正确的结果:

图 6 — 按月显示的新 Measure 结果(图示作者提供)
这个 Measure 适用于任何周期,如季度、学期或周。
警告
这个 Measure 只有在我们知道数据映射到每个周期的最后一天时才有效。
如果数据映射到其他日期,这个 Measure 将返回错误的结果。
同样适用于混合数据的情况,其中一些数据点映射到每个周期的最后一个日期,而其他数据点则没有。
我在下面链接的关于 Semi-Additive Measures 的文章中解释了这种情况。
所以,这完全取决于所需结果的定义和哪个解决方案是正确的。
结论
知道结果的定义对避免结果不合逻辑并且用户可能将结果解释为错误的情况至关重要。
这是最糟糕的情况,因为它可能会严重影响产品的接受度。
我们在测试过程中注意到这个“问题”,并决定使用上述提到的解决方案。
但我们将进一步讨论这个解决方案是否真的是正确的。
通常,没人会想到这些情况,直到我们遇到它们。所以,下次你在构建带有 Semi-Additive Measures 的报告时遇到这些情况时,不要担心。但记得也要测试这些情况。
这又是一个例子,说明理解 Filter 上下文如何应用的所有含义是至关重要的。
这种理解在尝试理解 DAX Measure 的结果时至关重要。
然而,将这种结果解释给不理解这种技术概念的利益相关者是具有挑战性的。
通常,他们希望在所有情况下都能得到:按行的正确结果和总计中的正确结果。
在这种情况下,我必须进一步解决这些需求。但每次在 Measure 中添加更多复杂逻辑时,我总是感到矛盾。
我不愿意,因为这样做很复杂,别人很难理解我为什么要这么做。这增加了对解决方案的维护工作。有时,我甚至经历了客户删除复杂逻辑的情况,因为没人能理解它。
我试图说服我的利益相关者达成妥协,以便在没有复杂和非常自定义的逻辑的情况下获得最佳的可接受结果。

照片由John Barkiple拍摄,发布于Unsplash
参考资料
数据是用虚构的名字自生成的。
我通过将一个名字和姓氏的列表互相乘积生成了这个完整的列表。
我通过将员工列表与我日期表中每个月最后日期的列表(CROSS JOIN)相乘生成了事实表。
DAX-Patterns 上的半加法计算:
计算报告在时间段开始或结束时的值对任何 BI 开发人员来说都是一个挑战……
我的第一篇文章关于解决半加法度量中的特定挑战:
DAX 中的半加法度量的优化
在之前的文章中,我解释了有关半加法度量的一些细节。这里有一些关于这个话题的更多提示
每当 Salvatore Cagliari 发布文章时,都会收到一封邮件。通过注册,你将创建一个 Medium 账户,如果你还没有的话……
每当 Salvatore Cagliari 发布文章时,都会收到一封邮件。通过注册,你将创建一个 Medium 账户,如果你还没有的话……
优秀工程师、差劲工程师和恶劣工程师——数据领导者的轶事
我区分优秀工程师、差劲工程师和恶劣工程师的黄金框架,适用于所有领域,包括数据领域
·
关注 发表在 Towards Data Science ·7 分钟阅读·2023 年 11 月 29 日
--
图片由作者提供(使用 Canva 的 Magic Media 应用生成)
工程就是利用科学原理设计或建造某物——《剑桥词典》。
我们都喜欢优秀的工程师,他们建造了出色的桥梁、道路、火箭、应用程序和数据结构,让我们的生活更加便捷和愉快。
按照相同的逻辑,差劲的工程师不会使生活变得更好。如果我们雇用他们,他们将设计和构建某些东西,但会消耗更多的时间、金钱和精力。
但你是否知道,在优秀和差劲之外,还有恶劣的工程师,他们的思维方式不是构建,而是不构建。
作为一名工程师,同时也是曾经担任产品负责人/项目经理的人员,我的经历告诉我关于优秀、差劲和恶劣工程师的一些事情。我喜欢优秀的工程师,对差劲的工程师有同情心,而对恶劣的工程师感到厌恶。
在本文结束时,我将告诉你从根本上看,这些工程师类型之间的区别。但首先,让故事从一个更具趣味性的角度讲述。
对优秀、差劲和恶劣工程师的一些普遍观察
回顾你自己的经历和对工程世界的了解,你认为优秀、差劲和恶劣的工程师有哪些共同的行为?
以下是我的观察:
优秀的工程师:
-
他们能识别问题
-
他们以可持续的方法解决问题
-
他们还解决与已识别根本原因相关的其他问题
差劲的工程师:
-
他们能识别问题
-
他们解决短期问题
-
他们通过解决原始问题创造更多的问题
恶劣的工程师:
- 他们假装看不到问题
查看一个具体的例子
让我通过描述数据工程世界中的一个具体例子来更容易地想象这三种工程师角色。
以一个数据工程师为例,他构建了一个管道,将一组原始数据表从事务数据仓库复制到云中的容器中。按照勋章架构,数据经过青铜层、银层和金层,他们首先清理数据并将其倒入指定数据湖仓中的一组青铜层表中。接下来,他们在银层中对表进行规范化,并建立它们之间的关系。最后,他们将多个表连接在一个视图中,并创建新特征以表示业务指标,以供 Tableau 仪表板使用。
在仪表板测试过程中,发现某些记录中的某一列存在缺失值。业务用户担心看到该列中超过 50%的记录缺失数据,但他们也承认数据在源头可能不完整。现在,工程师需要调查和解决问题。
优秀的工程师将:
-
首先,他们非常清楚该列如何从青铜层转变为金层和银层。换句话说,他们会知道缺失数据列的确切数据血统。
-
在金层中识别一个缺失数据的样本记录,但该列在源数据中有数据。如果他们在整个数据集中无法识别任何记录,他们就会宣布数据本身不完整。
-
如果发现一个有效的记录中存在缺失数据,他们将再次对该样本记录应用转换逻辑,看看为什么该列数据未能传递过来。这里会有 2 种场景:
-
场景 1:采样记录包含一些意外的特征,使得它们的列值被排除在金标准层之外。简而言之,这是一个设计问题。在这种情况下,好的工程师会与产品负责人讨论这些意外特征,并确定处理方案。要么他们决定可以安全地忽略这一人群,因为具有这些特征的数据与业务目标无关;要么他们会为这些数据制定自定义转换逻辑,以将数据引入。
-
场景 2:在他们的手动转换中,列值出现了,这意味着他们对初始数据来源的理解是错误的。简而言之,这是一个执行问题。好的工程师会回去检查数据管道在做什么,或者数据来源实际是什么。然后他们重复其余步骤。
一个坏工程师会:
-
对数据来源有一个较差的理解。
-
在金标准层中识别一个缺失数据的样本记录,但在源头对该列有数据。如果他们无法识别任何记录,他们宣布数据本身不完整。
-
如果发现一个有效的记录中存在缺失数据,尝试对这个缺失数据的记录应用手动逻辑转换,看看为什么该列数据未能传递过来。
-
得出错误的结论,为什么列值没有传递过来,主要是因为他们对数据来源和整体数据管道的理解是错误的。
-
如果他们的观察结果使他们得出上面所述的场景 1 的结论(设计问题),他们将通知团队这是一个数据质量问题,然后打住。他们假设设计是完美的,没有什么需要改进的地方。
-
一个更具伦理但也更灾难性的工程师会尝试为受影响的记录制定自定义处理方案(即修改设计),然而,由于他们对数据来源的理解一开始就不正确,他们会造成更大的混乱。
-
如果他们的观察结果使他们得出场景 2 的结论(执行问题),他们将回去研究实施的数据管道与设计的数据管道之间的差距,并可能在下一次找到正确的解决方案。
一个邪恶的工程师会怎么做?
-
他们可能知道也可能不知道正确的数据来源,这无关紧要。
-
他们宣布,因为该列的数据在源头就不完整(根据业务告诉他们的情况),所以数据在仪表板中会缺失。
-
然后他们假设数据管道没有问题,因为数据本身就是不完整的。
-
他们就此打住,回家去了。
好的、坏的和邪恶的工程师之间的根本区别
希望我上面的例子为你提供了三个类型工程师的更清晰的描绘。然而,例子只能在你掌握了好、坏和邪恶工程师之间的基本差异后才会对你长期有帮助。为了系统地区分这三者,找出他们的基本特征至关重要:
这是我的看法:
-
一个好的工程师具备三种品质:卓越的知识、对真相的承诺和对结果的承诺。
-
一个坏工程师缺乏卓越的知识或对结果的承诺。然而,他们确实对真相有中等程度的承诺。
-
一个邪恶的工程师对真相的承诺几乎为零。结果对他们来说并不重要。他们关心其他方面(也许是结果的外观),或者他们根本不在乎任何事情。邪恶的工程师很少具备卓越的知识,但如果他们具备,那也无关紧要,因为他们既不关心真相,也不关心结果。
你们中的一些人可能会发现这里的坏工程师和邪恶工程师之间没有明确的区别。通常,邪恶往往会造成伤害——因此你会期望邪恶的工程师引入恶意代码或掩盖他们过去的错误。我同意这一点。然而,我想在这里强调的是我如何区分坏和邪恶:
工程师不一定需要采取恶意行动就能变得邪恶,一旦工程师开始忽视眼前的真相(即假装看不到问题),他们就进入了邪恶的领域。
他们忽视的事实越多,他们就会变得越邪恶。
如何识别好、坏和邪恶的工程师?
所以下次当你遇到一个工程师时,寻找这三种品质的指标。如果你只是发现了一些资历、认证或几十年的经验——这些只是卓越知识的指标,那么你还不能太确定。
承诺是一种主动的心态。寻找对真相或结果的承诺的指标需要仔细调查历史行为模式、持续分析一个人的思维过程,以及观察他们对挑战的反应。
忽视对承诺或真相的指标的寻找就是忽视你自己的成功,让它由所谓的‘知识渊博’的工程师决定。
最终,这与你对自己招聘/合作决策的责任有关。如果你不想浪费你的钱,开始识别好、坏和邪恶的工程师吧。
你好,如果你在阅读这些内容,那么你很可能关心数据。你认为可以从数据中提取宝贵的价值,并且你渴望找到最佳的策略、实施实践和工具,以从你所在组织(或你自己)的数据资产中提取尽可能多的价值。
如果这是真的,那请查看我的每周通讯——数据与超越快报。每期将为你带来来自数据社区的深刻内容,这些内容经过精心挑选和总结,为你提供关于真正有效的数据领袖的使命、愿景、策略和工具箱的新鲜、条理清晰和实用的视角。
告别 os.path:15 个 Pathlib 技巧迅速掌握 Python 文件系统
不再有os.path带来的头疼和难以阅读的代码
·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 4 月 7 日
--

机器人伙伴。 — 来自 Midjourney
Pathlib 可能是我最喜欢的库(显然在 Sklearn 之后)。考虑到有超过 13 万个库,这已经很说明问题了。Pathlib 帮助我将像这样的代码从 os.path 转换过来:
import os
dir_path = "/home/user/documents"
# Find all text files inside a directory
files = [os.path.join(dir_path, f) for f in os.listdir(dir_path) \
if os.path.isfile(os.path.join(dir_path, f)) and f.endswith(".txt")]
转换为如下内容:
from pathlib import Path
# Find all text files inside a directory
files = list(dir_path.glob("*.txt"))
Pathlib 于 Python 3.4 中推出,作为替代 os.path 的噩梦。它还标志着 Python 语言的一个重要里程碑:他们终于将每一件事都变成了对象(即使是 nothing)。
os.path 的最大缺陷是将系统路径视为字符串,这导致了难以阅读、混乱的代码和陡峭的学习曲线。
通过将路径表示为完全成熟的 对象,Pathlib 解决了所有这些问题,并为路径处理带来了优雅、一致性和一丝清新。
这篇我一直拖延的文章将概述一些 pathlib 的最佳函数/特性和技巧,以执行那些在 os.path 中会是极其糟糕的任务。
学习这些 Pathlib 的功能将使你作为数据专业人士处理路径和文件变得更容易,特别是在需要处理成千上万的图像、CSV 文件或音频文件的数据处理工作流中。
让我们开始吧!
路径操作
1. 创建路径
pathlib的几乎所有功能都可以通过其 Path 类访问,你可以用它来创建文件和目录的路径。
有几种方法可以使用 Path 创建路径。首先,有像 cwd 和 home 这样的类方法,用于当前工作目录和用户主目录:
from pathlib import Path
Path.cwd()
PosixPath('/home/bexgboost/articles/2023/4_april/1_pathlib')
Path.home()
PosixPath('/home/bexgboost')
你也可以从字符串路径创建路径:
p = Path("documents")
p
PosixPath('documents')
使用正斜杠操作符在 Pathlib 中连接路径是轻而易举的:
data_dir = Path(".") / "data"
csv_file = data_dir / "file.csv"
print(data_dir)
print(csv_file)
data
data/file.csv
请不要让任何人看到你在使用 os.path.join。
要检查路径是否存在,你可以使用布尔函数 exists:
data_dir.exists()
True
csv_file.exists()
True
有时,整个 Path 对象可能不可见,你需要检查它是目录还是文件。因此,你可以使用is_dir或is_file函数来判断:
data_dir.is_dir()
True
csv_file.is_file()
True
你处理的大多数路径相对于当前目录。但有时你需要提供文件或目录的确切位置,以便从任何 Python 脚本中访问它。这时你使用absolute路径:
csv_file.absolute()
PosixPath('/home/bexgboost/articles/2023/4_april/1_pathlib/data/file.csv')
最后,如果你不幸要处理仍然需要字符串路径的库,你可以调用 str(path):
str(Path.home())
'/home/bexgboost'
数据栈中的大多数库很早就支持了
Path对象,包括sklearn、pandas、matplotlib、seaborn等。
2. 路径属性
Path 对象有许多有用的属性。让我们看看一些使用指向图像文件的路径对象的示例。
image_file = Path("images/midjourney.png").absolute()
image_file
PosixPath('/home/bexgboost/articles/2023/4_april/1_pathlib/images/midjourney.png')
从 parent 开始。它返回一个路径对象,该对象在当前工作目录上一级。
image_file.parent
PosixPath('/home/bexgboost/articles/2023/4_april/1_pathlib/images')
有时,你可能只想要文件的 name 而不是整个路径。这里有一个属性可以做到这一点:
image_file.name
'midjourney.png'
只返回带有扩展名的文件名。
还有 stem 用于没有后缀的文件名:
image_file.stem
'midjourney'
或带有点的 suffix 本身作为文件扩展名:
image_file.suffix
'.png'
如果你想将路径划分为其组件,你可以使用 parts 代替 str.split('/'):
image_file.parts
('/',
'home',
'bexgboost',
'articles',
'2023',
'4_april',
'1_pathlib',
'images',
'midjourney.png')
如果你想要那些组件本身成为 Path 对象,你可以使用 parents 属性,它创建一个生成器:
for i in image_file.parents:
print(i)
/home/bexgboost/articles/2023/4_april/1_pathlib/images
/home/bexgboost/articles/2023/4_april/1_pathlib
/home/bexgboost/articles/2023/4_april
/home/bexgboost/articles/2023
/home/bexgboost/articles
/home/bexgboost
/home
/
处理文件

分类文件。— Midjourney
要创建文件并向其中写入内容,你不再需要使用 open 函数。只需创建一个 Path 对象,然后 write_text 或 write_bytes 即可:
markdown = data_dir / "file.md"
# Create (override) and write text
markdown.write_text("# This is a test markdown")
或者,如果你已经有一个文件,你可以 read_text 或 read_bytes:
markdown.read_text()
'# This is a test markdown'
len(image_file.read_bytes())
1962148
但请注意,write_text或write_bytes会覆盖文件中现有的内容。
# Write new text to existing file
markdown.write_text("## This is a new line")
# The file is overridden
markdown.read_text()
'## This is a new line'
要向现有文件添加新信息,你应该使用 Path 对象的 open 方法,模式为 a(附加):
# Append text
with markdown.open(mode="a") as file:
file.write("\n### This is the second line")
markdown.read_text()
'## This is a new line\n### This is the second line'
重命名文件也很常见。rename 方法接受重命名文件的目标路径。
要在当前目录中创建目标路径,即重命名文件,你可以在现有路径上使用 with_stem,它替换原始文件的 stem:
renamed_md = markdown.with_stem("new_markdown")
markdown.rename(renamed_md)
PosixPath('data/new_markdown.md')
上述,file.md 被转为 new_markdown.md。
通过 stat().st_size 查看文件大小:
# Display file size
renamed_md.stat().st_size
49 # in bytes
或文件上次修改的时间,这是几秒钟前的事:
from datetime import datetime
modified_timestamp = renamed_md.stat().st_mtime
datetime.fromtimestamp(modified_timestamp)
datetime.datetime(2023, 4, 3, 13, 32, 45, 542693)
st_mtime 返回一个时间戳,这是自 1970 年 1 月 1 日以来的秒数。为了使其可读,你可以使用 datetime 的 fromtimestamp 函数。
要删除不需要的文件,你可以 unlink 它们:
renamed_md.unlink(missing_ok=True)
将 missing_ok 设置为 True,如果文件不存在不会引发任何警报。
处理目录

办公室中的文件夹。— Midjourney
在 Pathlib 中处理目录有几个很棒的技巧。首先,让我们看看如何递归创建目录。
new_dir = (
Path.cwd()
/ "new_dir"
/ "child_dir"
/ "grandchild_dir"
)
new_dir.exists()
False
new_dir还不存在,所以让我们创建它及其所有子目录:
new_dir.mkdir(parents=True, exist_ok=True)
默认情况下,mkdir只会创建给定路径的最后一个子目录。如果中间的父目录不存在,你必须将parents设置为True。
要删除空目录,你可以使用rmdir。如果给定的路径对象是嵌套的,则只会删除最后一个子目录:
# Removes the last child directory
new_dir.rmdir()
要列出目录的内容,就像终端中的ls一样,你可以使用iterdir。结果将是一个生成器对象,一次产生一个路径对象的目录内容:
for p in Path.home().iterdir():
print(p)
/home/bexgboost/.python_history
/home/bexgboost/word_counter.py
/home/bexgboost/.azure
/home/bexgboost/.npm
/home/bexgboost/.nv
/home/bexgboost/.julia
...
要捕捉所有具有特定扩展名或名称模式的文件,你可以使用带有正则表达式的glob函数。
例如,下面我们将使用glob("*.txt")在我的主目录中找到所有文本文件:
home = Path.home()
text_files = list(home.glob("*.txt"))
len(text_files)
3 # Only three
要递归搜索文本文件,即包括所有子目录内的文件,你可以使用递归 glob与rglob:
all_text_files = [p for p in home.rglob("*.txt")]
len(all_text_files)
5116 # Now much more
了解正则表达式 这里。
你也可以使用rglob('*')递归列出目录内容。这就像iterdir()的超级增强版。
其中一个用例是统计目录中出现的文件格式数量。
为此,我们从collections中导入Counter类,并在home的文章文件夹中提供所有文件后缀:
from collections import Counter
file_counts = Counter(
path.suffix for path in (home / "articles").rglob("*")
)
file_counts
Counter({'.py': 12,
'': 1293,
'.md': 1,
'.txt': 7,
'.ipynb': 222,
'.png': 90,
'.mp4': 39})
操作系统差异
对不起,但我们必须讨论这个噩梦般的问题。
到目前为止,我们一直在处理PosixPath对象,它们是 UNIX-like 系统的默认对象:
type(Path.home())
pathlib.PosixPath
如果你在 Windows 上,你会得到一个WindowsPath对象:
from pathlib import WindowsPath
# User raw strings that start with r to write windows paths
path = WindowsPath(r"C:\users")
path
NotImplementedError: cannot instantiate 'WindowsPath' on your system
实例化另一个系统的路径会引发类似上述的错误。
但如果你被迫处理来自其他系统的路径,比如同事们用 Windows 写的代码呢?
作为解决方案,pathlib提供了像PureWindowsPath或PurePosixPath这样的纯路径对象:
from pathlib import PurePosixPath, PureWindowsPath
path = PureWindowsPath(r"C:\users")
path
PureWindowsPath('C:/users')
这些是原始路径对象。你可以访问一些路径方法和属性,但本质上,路径对象仍然是一个字符串:
path / "bexgboost"
PureWindowsPath('C:/users/bexgboost')
path.parent
PureWindowsPath('C:/')
path.stem
'users'
path.rename(r"C:\losers") # Unsupported
AttributeError: 'PureWindowsPath' object has no attribute 'rename'
结论
如果你注意到,我在文章标题中撒了谎。实际上新技巧和函数的数量是大约 30 个,而不是 15 个。
我不想吓到你。
但我希望我已经足够说服你放弃os.path,开始使用pathlib来进行更简单和更可读的路径操作。
锻造一个新的路径,如果你愿意 😃

Path. — Midjourney
如果你喜欢这篇文章,并且,老实说,其怪异的写作风格,考虑通过注册成为 Medium 会员来支持我。会员费用为每月 4.99 美元,可以无限制访问我所有的故事以及成千上万篇更有经验的作者写的文章。如果你通过 这个链接 注册,我将赚取少量佣金,而不会增加你的额外费用。
[## 通过我的推荐链接加入 Medium — Bex T.
获得对我所有⚡高级⚡内容和 Medium 上所有内容的独家访问权限,没有限制。通过购买我一份支持我的工作…
ibexorigin.medium.com](https://ibexorigin.medium.com/membership?source=post_page-----881213ca7c21--------------------------------)
如何在 Python 中设置 GOOGLE_APPLICATION_CREDENTIALS
原文:
towardsdatascience.com/google-application-credentials-python-ace518208a7
配置应用程序默认凭据并修复 oauth2client.client.ApplicationDefaultCredentialsError
·发布于 Towards Data Science ·阅读时间 3 分钟·2023 年 1 月 19 日
--

照片由 Daniel K Cheung 提供,来源于 Unsplash
欢迎来到我们的教程,了解如何为 Google Cloud 和 Python 配置应用程序默认凭据。在本文中,我们将介绍如何在 Python 中正确设置 GOOGLE_APPLICATION_CREDENTIALS。
为了能够以编程方式与 Google Cloud Platform 服务(如 Google BigQuery)进行交互,首先需要正确认证应用程序并授予所有所需权限。这是通过定义应用程序默认凭据来指向包含所需凭据的文件来实现的。
当漏掉此步骤时,常见的错误是:
oauth2client.client.ApplicationDefaultCredentialsError: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
订阅数据管道,这是一个专注于数据工程的新闻通讯
Google Cloud 中的应用程序默认凭据如何工作
应用程序默认凭据 (ADC) 是 Google Cloud 上用于根据应用程序环境推断凭据的策略。这意味着应用程序代码可以在不同的环境中运行,而无需更改代码认证 GCP 服务或应用程序编程接口 (APIs) 的方式。
对于本地开发,通常有两种不同的方法来提供 ADC 凭据:
-
用户凭据
-
服务帐户密钥
创建凭据 JSON 文件
为了创建包含所需凭据的 JSON 文件,首先需要确保在主机机器上安装了 [gcloud](https://cloud.google.com/sdk/docs/install) CLI。
对于本地开发,您最好使用与您个人 Google Cloud 帐户相关联的用户凭据。为此,您需要运行以下命令,这将会在您的(默认)浏览器中显示一个登录提示:
gcloud auth application-default login
登录到 Google Cloud 后,您的凭据将存储在一个 JSON 文件中,位于以下默认位置:
-
Mac/Linux:
$HOME/.config/gcloud/application_default_credentials.json -
Windows:
%APPDATA%\gcloud\application_default_credentials.json
如果您使用的是服务帐户,可以通过访问Google Cloud 上的服务帐户服务生成 JSON 令牌。不过请注意,服务帐户密钥存在安全风险,不推荐使用。更强大且可能更安全的方法包括冒充和 工作负载身份池。
设置 GOOGLE_APPLICATION_CREDENTIALS 环境变量
为了提供凭据 JSON 文件的位置,您需要使用 GOOGLE_APPLICATION_CREDENTIALS 环境变量。
因此,在使用 Python 时,您可以使用以下代码片段以编程方式设置环境变量:
import os
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] ='$HOME/.config/gcloud/application_default_credentials.json'
另外,您还可以创建 google.oath2.service_account.Credentials 的实例,然后在开始与 Google 客户端交互之前将其传递给 Google 客户端。
以下示例演示了如何在 Python 中对 Gmail 客户端进行身份验证:
from google.oauth2 import service_account
from googleapiclient.discovery import build
credentials = service_account.Credentials.from_service_account_file(
'$HOME/.config/gcloud/application_default_credentials.json'
)
service = build('gmail', 'v1', credentials=credentials)
请注意,上述代码片段假设您的 JSON 凭据文件在创建时存储在默认目录下。如果与默认目录不同,请确保指向正确的目录。
最终想法
总之,本教程介绍了如何为 Google Cloud 和 Python 正确设置应用程序默认凭据 (ADC),以便对应用程序进行身份验证,并授予对 Google Cloud Platform 服务的所有必需权限。
ADC 是 Google Cloud 上的一种策略,用于根据应用程序环境推断凭据,使代码可以在不同的环境中运行而无需更改身份验证过程。
在本教程中,我们还介绍了如何创建所需的 JSON 凭据文件,无论是使用用户凭据还是服务帐户,以及如何设置 GOOGLE_APPLICATION_CREDENTIALS 环境变量以提供文件的位置。
订阅数据管道,这是一个致力于数据工程的新闻通讯
您可能也喜欢的相关文章
Python 中的图示代码
使用 Python 创建云系统架构图
图表作为代码(Python) ## SQL 反模式在 BigQuery 中
在 Google Cloud BigQuery 上运行 SQL 时的最佳实践和需要避免的事项
标准 SQL 与遗留 SQL 在 BigQuery 中的比较 ## 标准 SQL 与遗留 SQL 在 BigQuery 中的比较
在 Google Cloud BigQuery 上理解标准 SQL 和遗留 SQL 的区别
Google Cloud 的 Cloud Composer 替代品
原文:
towardsdatascience.com/google-cloud-alternatives-to-cloud-composer-972836388a3f
观点
不要用锤子打苍蝇
·发表于 Towards Data Science ·4 分钟阅读·2023 年 4 月 13 日
--

作者提供的图片,火焰标志设计由 Freepik¹
你知道吗,作为 Google Cloud 的用户,你可以选择许多服务来编排你的作业?对于批处理作业,Cloud Composer 长时间以来一直是自然的选择。然而,这种情况不必持续下去。本文介绍了 Google Cloud 中两个替代 Cloud Composer 的作业编排工具。
本内容的主要主题如下:
Cloud Composer 表现出色的用例
Cloud Composer 的两个替代品
优势与劣势基准
总结
Cloud Composer 表现出色的用例
一个作业编排工具需要满足一些要求才能称之为编排工具。就个人而言,我期望在一个作业编排工具中至少看到 3 点:
-
首先,一个编排工具必须能够编排任何具有任务间依赖关系的任务组,无论这些任务执行什么工作
-
其次,一个编排工具必须支持任务间的数据共享
-
第三,一个编排工具必须允许周期性作业执行和按需作业执行
Cloud Composer 满足上述 3 个标准及更多。它是一个功能强大的全面编排工具,基于 Apache Airflow,支持像回填、补充、任务重跑和 动态任务映射 等良好功能。
权力是危险的。权力吸引最坏的人,腐蚀最优秀的人(爱德华·阿比)
权力是危险的。这一说法对 Cloud Composer 同样适用。我总是建议尝试更简单的解决方案(更多内容在接下来的部分),将 Cloud Composer 保留用于复杂的情况。在我看来,以下是使用 Cloud Composer 完全合理的一些情况:
-
你需要运行一个大规模的作业编排系统,涉及数百或数千个作业
-
你有任务之间存在复杂和/或动态依赖关系的作业。例如,你的作业的最终结构取决于作业中第一个任务的输出。
-
你有具有非平凡触发规则和约束的任务。例如,你希望任务在其任何上游任务失败后立即触发。
Cloud Composer 的两个替代方案
在寻找 Cloud Composer 的作业编排器时,还有更简单的解决方案可以考虑。
替代方案 1:Vertex AI Pipelines

Vertex AI Pipeline 中的管道运行示例,图片来源于作者
Vertex AI Pipelines 是基于Kubeflow Pipelines(它基于 Kubernetes)的作业编排器。它是一个无服务器产品,这意味着无需创建虚拟机或集群。尽管该编排器最初用于机器学习(ML)管道,但它足够通用,可以适应任何类型的作业。在我看来,将 Vertex AI Pipelines(更广泛地说,Kubeflow Pipelines)绑定到 ML 上更像是一种陈词滥调,反而影响了该解决方案的受欢迎程度。
替代方案 2:Cloud Workflows (+ Cloud Scheduler)

Cloud Workflows 中的工作流运行示例,图片来源于作者
Cloud Workflows 是一种无服务器、轻量级的服务编排器。它有两个主要要求:
-
需要编排的任务必须是基于 HTTP 的服务(通常使用Cloud Functions或Cloud Run)
-
作业的调度外部化到Cloud Scheduler
人们通常会使用它来编排 API 或微服务,从而避免单体架构。
优势与劣势基准
当需要在多个选项中做出选择时,通常将选项根据明确的成功标准进行排名是个好主意。我在这里选择了 4 个标准(0:差 — 2:一般 — 5:好)
-
简洁性:团队学习和使用该解决方案的难易程度如何?
-
可维护性:在工作流创建后,修改工作流的难易程度如何?
-
可扩展性:当工作流数量增加时,解决方案的稳定性如何?
-
成本
注意:请注意,标准以及评估是主观的,仅代表我的观点

解决方案比较,图片来源于作者
由于其陡峭的学习曲线,Cloud Composer 不是最容易上手的解决方案。尽管如此,Cloud Workflows 本身没有任何处理能力,这也是为什么它总是与其他服务如 Cloud Functions 或 Cloud Runs 一起使用的原因。此外,调度必须由 Cloud Scheduler 处理。
就可维护性和可扩展性而言,Cloud Composer 是佼佼者,因为它具有无限的可扩展性,并且系统非常可观察,所有组件都有详细的日志和指标。在这方面,Cloud Composer 紧随其后的是 Vertex AI Pipelines。
就成本而言,Cloud Composer 是最昂贵的,而 Cloud Workflows 在这三者中无疑是最便宜的解决方案。
总结
根据你对工作流编排的需求,Google Cloud 中可能有比 Cloud Composer 更合适的解决方案。一方面,Cloud Workflows 便宜得多,并且满足了工作流编排器的所有基本要求。另一方面,Vertex AI Pipelines 更加集成到 Kubernetes 中,对于已经对 Kubernetes 有较好了解的团队来说,可能更容易上手。
感谢您的时间,请继续关注更多内容。
[1] www.freepik.com
Google Foobar Challenge: Level 3
原文:
towardsdatascience.com/google-foobar-challenge-level-3-f487fa3f204b
探索二进制数字、动态编程和马尔可夫链
·发表于 Towards Data Science ·9 分钟阅读·2023 年 11 月 30 日
--

图片来源于 Rajeshwar Bachu 在 Unsplash
Foobar 挑战是什么? 🧐
Foobar 挑战是由 Google 主办的编码挑战,可以用 Python 或 Java 完成。我用 Python 完成了挑战。这个挑战有自己专属的服务器,使用特定的终端风格命令。问题的难度各异,共分为 5 个级别。每个问题必须在一定时间限制内解决。较高级别的挑战会给予更多时间。
要了解更多关于 Foobar 挑战的信息,我推荐阅读我之前的文章,其中提供了对第 1 级问题的概述和分析。
## Google Foobar Challenge: Level 1
对神秘编码挑战的介绍以及问题的分析
towardsdatascience.com
第 3 级开始变得严肃起来。第 1 级和第 2 级测试了基础知识,大约需要 15 分钟解决。第 3 级测试了问题解决技能,需要几个小时的研究。与之前的级别不同,我没有立即知道如何解决这些问题。我不得不多次阅读问题并在纸上进行测试。此外,我还需要研究和实践一些新的概念。
研究并不意味着仅仅通过 Google 搜索问题名称并查看其他人的解决方案。相反,我尝试重新表述问题或搜索那些看起来特别具体的短语,以找到相关的方程和模型。
起初,我有些犹豫。谷歌会跟踪我的搜索记录吗?他们会认为这是一种作弊吗?然而,随着我在这一关卡的进展,我意识到这些问题很可能是为了迫使你查看外部材料。我非常怀疑谷歌会期望开发者记住马尔可夫链公式。
当你解决这些问题时,我鼓励你研究不熟悉的概念,尤其是当你的解决方案变得冗长和不结构化时。这些问题的设计目的是为了找到优雅的解决方案。如果你想不出一种优雅的解决方法,那说明可能存在某个公式或方法能简化它。记住,编码的一部分是研究最佳的方法。
问题与概念 📚
警告 — 含有剧透 ⛔️
在下面,我将详细讲解这些问题并解释我的思考过程。我也提供了解决方案。不过,我强烈建议你先尝试解决这个问题。挑战中最好的部分就是解决一个难以捉摸的问题所带来的惊喜和满足感。
我曾考虑不发布解决方案,仅仅解释基础概念。然而,编码的一部分是学习如何排查故障和定位代码失败的地方。因此,我决定发布解决方案,以便当你卡住时,可以准确看到你的逻辑在哪里偏离了正确的路径。
第 3 关:问题 1
在第 3 关的所有问题中,这对我来说是最困难的。我在纸上做了很多示例。处理一个 309 位长的数字的要求似乎暗示着尝试用二进制数来解决这个问题。
问题的目标是用最少的操作达到一个弹珠。最有效的操作是将弹珠对半分,因为这样n会呈指数级衰减。

图片来自作者。
不过,完美的指数衰减仅在n是 2 的幂时有效。如果不是,n最终将减少到一个奇数,并且不能被除以 2。在这种情况下,是加 1 还是减 1 更好?最佳的操作是使数字可以被最多次地除以 2 的那个选项。幸运的是,二进制形式提供了这些信息。一个二进制数中的尾随零的数量就是一个数字可以被 2 除以多少次。
例如,考虑n = 15,如下所示。这两个序列说明了第一步的两个选项:加 1(n = 16)和减 1(n = 14)。尽管 14 离期望的最终状态 1 更近,但 16 是更好的选择,因为它可以被更多次地除以 2。正如你在右侧图表上看到的,二进制形式中的尾随零的数量对应于一个数字可以被除以 2 的次数。

图片来自作者。
为了解决这个问题,我首先使用内置的bin函数将输入字符串(n)转换为二进制(b)。我使用了一个 while 循环将b减少到 1。如果b是偶数,我通过去掉尾部零来将其减半。尽可能多地将b减半将最小化总操作次数。如果b是奇数,我选择具有最多尾部零的最近邻整数。
如前所述,尾部零的数量表示一个给定的数字可以被二除的次数。尾部零更多的数字优先,因为它们允许更多的减半。换句话说,尾部零最多的数字提供了最有效的方法来达到 1。
级别 3:问题 2
开始时,我在纸上完成了大量的练习题。通过这样做,我意识到较大楼梯的解决方案包括了较小楼梯的解决方案。解决n涉及首先解决较小楼梯的子问题,这使得这是一个动态规划问题。

图片来自作者。
我在解决这个问题的累积特性时真的很挣扎。虽然我理解较小的楼梯如何贡献于较大的楼梯,但我无法弄清楚如何计算两个步骤的楼梯。例如,当n = 7时,我看到n = 3是解决方案的一部分,但我不知道如何计算其他两个步骤的楼梯。

图片来自作者。
作为一个练习,我简化了这个问题,并只是试图找出给定高度和n的楼梯数量。换句话说,我将上面的图表转为表格形式。

图片来自作者。
当我填充表格时,我找到了算法。对于每个单元格,我从n中减去楼梯高度。然后,我取余数并访问该列(n = remainder)。该列的总和等于解决方案的数量。在某些情况下,余数足够建造 1 步。为了计算这些情况,我在对角线上加了 1。请参见下图左侧的绿色数字。

图片来自作者。
例如,当n = 8且staircase height = 5时,余数为n = 3。3 列包含两个解决方案:staircase height = 3和staircase height = 2。因此,对于n = 8和staircase height = 5,共有两个解决方案。
这种方法有效,但可能更高效。与其访问列中的每个单元格,不如将表格结构化,使得行表示最大楼梯高度。例如,max staircase height = 3将是给定n的高度为 1、2 和 3 的解决方案之和。下方的紫色数字代表这种累积方法。现在,找到解决方案只需要访问一个单元格。
注意下图中包含了一个n=0列。这样,列索引号等于n值。这一小变化大大简化了代码。

图片由作者提供。
第三级:问题 3
阅读问题后,我想到了几个短语。我搜索了诸如“固定概率状态”和“概率状态转移”等术语,最终找到了马尔可夫链。由于从每个状态到终端状态都有路径,这就是一个吸收马尔可夫链。终端状态,也称为吸收状态,意味着无法离开该状态(即,转移到其他状态的概率为零)。
我以前从未听说过马尔可夫链,所以在开始编写代码之前,我做了一些研究。这里 是对马尔可夫链的很好的概述。
吸收马尔可夫链的转移矩阵 P,对于有 t 个过渡状态和 s 个吸收状态的情况如下:

图片由作者提供。
然后可以使用转移矩阵来找到基本矩阵:

图片由作者提供。
基本矩阵提供了很多信息。但为了求解问题,还必须计算一个矩阵:

图片由作者提供。
在 M 中,行表示起始状态,列表示吸收状态。M[i][j] 的值是从过渡状态 i 开始被吸收到终端状态 j 的概率。因此,M 的行数由过渡状态的数量决定,而列数等于终端状态的数量。
首先,我需要找出过渡状态和吸收状态的数量及位置。然后,对于过渡状态,我需要将整数转换为分数。
给定以下输入:

图片由作者提供。
上面的代码将其转换为:

图片由作者提供。
接下来,我需要计算 I 和 Q 矩阵以求解 N。由于我不能使用库,我复习了像 点积 和 矩阵求逆 这样的矩阵运算。

图片由作者提供。
现在,我需要求解 R。

图片由作者提供。
现在,R 和 N 都已经求解,我可以计算概率矩阵 M。

图片由作者提供。
现在,M 必须格式化以匹配输出规范。由于问题只要求从状态 0 开始的概率,因此只需要第一行。正如上面所示,M 是以分数形式表示的。为了将答案格式化为表示概率分子的一维数组,我需要找到最小公倍数。最小公倍数将是概率之间的公分母。

图片来自作者。
这个问题中最棘手的部分是分数和重新学习矩阵操作。正如我在 Level 1 文章中提到的,这个挑战对接受的输出类型非常具体。
这是完整的解决方案。
结论
在级别结束时,我有机会提交我的联系信息给 Google 招聘人员。然而,那是超过一年前的事情了,Google 还没有联系我。
总体来说,我觉得这些问题的设计不仅仅是测试基础编码技能。解决这些类型的问题证明了你能够在处理复杂边缘情况和内存限制时开发解决方案。具有讽刺意味的是,我发现随着难度级别的提升,问题变得更容易了。然而,每解决一个新问题,我也开始更加聪明地工作,而不是更努力地工作。我重新阅读问题,暂停并在纸上绘制一些例子,而不是立即开始编码想到的第一个解决方案。最重要的是,我利用现有资源来学习新概念和巩固旧知识。
感谢你阅读我的文章。如果你喜欢我的内容,可以在 Medium上关注我。
在 LinkedIn、 Twitter* 或* Instagram上与我联系。
所有反馈都是受欢迎的。我总是渴望学习新的或更好的做事方式。
随时可以留下评论或通过 katyhagerty19@gmail.com 联系我。
谷歌生成式 AI 转型
原文:
towardsdatascience.com/google-generative-ai-transformations-edb4164935cb
ETL 即将被改造
·发表于 Towards Data Science ·阅读时间 8 分钟·2023 年 6 月 12 日
--

图片来源:Suzanne D. Williams 在 Unsplash
这是什么?
大型语言模型(LLMs)可以提取信息并生成信息,但它们也可以转换信息,这使得提取、转换和加载(ETL)可能完全是一项不同的工作。我将提供一个例子来说明这些想法,这也应当展示 LLM 如何以及应该用于包括将非结构化文本转换为结构化文本在内的许多相关任务。
谷歌最近公开了其大型语言模型(LLM)系列产品,并将其中一部分命名为“生成式 AI 工作室”。简而言之,Google Cloud Platform 控制台中的 GenAI Studio 是谷歌 LLM 的用户界面。然而,与谷歌 Bard(一个使用 LLM 的商业应用)不同,谷歌不会出于任何原因保存你的数据。请注意,谷歌还发布了一个涵盖许多此处所述功能的 API。
使用 GenAI Studio
进入 GenAI Studio 非常简单——在 GCP 控制台中,只需使用左侧的导航栏,悬停在 Vertex AI 上,然后选择 概览 下的 生成式 AI 工作室。

作者提供的图片
截至 2023 年 5 月底,有两个选项——语言和语音。(不久后,谷歌预计还会在此处发布视觉类别。)每个选项包含一些示例提示样式,这些样式可以帮助你激发想法并将现有的想法集中为有用的提示。但更重要的是,这是一个“安全”的类似 Bard 的体验,因为你的数据不会被谷歌保存。
语言
语言的登录页面是这个例子中唯一使用的功能,它有多个不同的功能,同时也包含了一个简单的方法来调整基础模型(目前调整只能在特定区域进行)。
创建提示

图片来源:作者
开始使用区域是快速创建与 Google 模型(取决于时间和交互类型,可能是一个或多个)的无指导交互的地方。
选择文本提示会调用一个类似 Bard 的用户界面,但有一些重要的区别(除了数据隐私之外):

图片来源:作者。
-
底层的 LLM 可以更改。目前,text-bison001 模型是唯一可用的,但其他模型会随着时间出现。
-
模型参数可以调整。Google 提供了每个参数的解释,旁边有问号。
-
用于阻止不安全回复的过滤器可以调整(选项包括“阻止少数”、“阻止一些”和“阻止大多数”)。
-
不适当的回复可以轻松报告。
除了与 Bard 的明显区别外,这种方式使用模型还缺乏一些 Bard 的“附加功能”,如时事。例如,如果输入一个询问昨天芝加哥天气的提示,这个模型不会给出正确答案,但 Bard 会。
大文本区域是输入提示的地方。

图片来源:作者
通过在提示部分输入文本、(可选地)调整参数,然后选择提交按钮来创建提示。在这个例子中,提示是“1+1 等于多少?”使用的是 text-bison001 模型和默认参数值。注意,模型仅返回数字 2,这是温度对回复影响的一个好例子。重复这个提示(通过重复选择提交)大多数时候会得到“2”,但有时会随机给出不同的回复。将温度调整为 1.0 会得到:“答案是 2。1+1=2 是每个人在小学时学习的最基本的数学方程之一。它是以后学习的所有其他数学的基础。”这是因为温度调整了对标记的概率选择,值越低,回复的变化越少(即越确定)。如果在这个例子中将值设置为 0,模型将始终返回“2”。这很酷,非常类似 Bard,但更好。你还可以保存提示并查看提示的代码。以下是“1+1 等于多少?”的代码。
import vertexai
from vertexai.preview.language_models import TextGenerationModel
def predict_large_language_model_sample(
project_id: str,
model_name: str,
temperature: float,
max_decode_steps: int,
top_p: float,
top_k: int,
content: str,
location: str = "us-central1",
tuned_model_name: str = "",
) :
"""Predict using a Large Language Model."""
vertexai.init(project=project_id, location=location)
model = TextGenerationModel.from_pretrained(model_name)
if tuned_model_name:
model = model.get_tuned_model(tuned_model_name)
response = model.predict(
content,
temperature=temperature,
max_output_tokens=max_decode_steps,
top_k=top_k,
top_p=top_p,)
print(f"Response from Model: {response.text}")
predict_large_language_model_sample(
"mythic-guild-339223",
"text-bison@001", 0, 256, 0.8, 40,
'''What is 1+1?''', "us-central1")
生成的代码包含提示信息,但很明显,函数predict_large_language_model_sample是通用的,可用于任何文本提示。
赶紧进入 ETL 吧!
在我的日常工作中,我花费大量时间来确定如何从文本(包括文档)中提取信息。LLM 可以以令人惊讶的简单和准确的方式做到这一点,并且在这样做时还可以更改数据。一个例子说明了这种潜力。
假设为了这个例子,以下电子邮件消息由一个虚构的 ACME Incorporated 接收:
Purchaser: Galveston Widgets
Dear Purchasing,
Can you please send me the following items, and provide an invoice for them?
Item Number
Widget 11 22
Widget 22 4
Widget 67 1
Widget 99 44
Thank you.
Arthur Galveston
Purchasing Agent
(312)448-4492
还假设系统的目标是从电子邮件中提取特定数据,为每个输入项应用价格(和小计),并生成总计。
如果你认为 LLM 无法做到这些,重新考虑一下!
有一种提示风格叫做extractive Q&A,在某些情况下(如果通过调整模型而不是简单的提示工程应用,它可能在所有情况下都适用)非常合适。其思路很简单:
-
提供一个Background,即原始文本。
-
提供一个Q(即问题),应该是一些提取性的,例如“将所有信息提取为 JSON。”
-
可选择性地提供一个A(即答案),以获得所需的输出。
如果没有提供A,则应用零样本工程(这效果比我预期的要好)。你也可以提供一次性或多次样本,但有一定限制。提示的大小有限制,这限制了你可以提供的样本数量。
总结一下,提取性 Q&A 提示的形式如下:
**Background**: [the text]
**Q:** [the extractive question]
**A:** [nothing, or an example desired output]
在这个例子中,电子邮件是文本,而“将所有信息提取为 JSON”是提取性问题。如果没有提供A:,LLM 将尝试进行提取(零样本)。(JSON 代表 JavaScript 对象表示法。它是一种轻量级的数据交换格式。)
以下是零样本输出:
**Background**: Purchaser: Galveston Widgets
Dear Purchasing,
Can you please send me the following items, and provide an invoice for them?
Item Number
Widget 11 22
Widget 22 4
Widget 67 1
Widget 99 44
Thank you.
Arthur Galveston
Purchasing Agent
(312)448-4492
**Q:** Extract all information as JSON
**A:**
你不需要加粗Background:, Q:, 和 A:,我这样做只是为了清晰。
在 UI 中,我将提示保持为FREEFORM,并在提示区域中输入了上述提示。然后,我将Temperature设置为 0(我希望每次相同的输入得到相同的回答),并将Token limit增加到 512,以允许更长的响应。
以下是零样本提示和回复的样子:

作者提供的图像
“E”xtract 有效,甚至能够很好地将条目列表放入 JSON 中。但这已经足够好了。假设我的要求是为数据提供特定的标签,并且假设我还希望捕捉采购代理及其电话。最后,假设我希望有行项目小计和总计(这一假设要求存在行项目价格)。
我理想的输出,即“E”xtract 和“T”ransform,如下所示:
{"company_name": "Galveston Widgets",
"items" : [
{"item_name": "Widget 11",
"quantity": "22",
"unit_price": "$1.50",
"subtotal": "$33.00"},
{"item_name": "Widget 22",
"quantity": "4",
"unit_price": "$50.00",
"subtotal": "$200.00"},
{"item_name": "Widget 67",
"quantity": "1",
"unit_price": "$3.50",
"subtotal": "$3.50"},
{"item_name": "Widget 99",
"quantity": "44",
"unit_price": "$1.00",
"subtotal": "$44.00"}],
"grand_total": "$280.50",
"purchasing_agent": "Arthur Galveston",
"purchasing_agent_phone": "(312)448-4492"}
对于这个提示,我将 UI 从FREEFORM更改为STRUCTURED,这使得数据布局稍微容易一些。使用这个 UI,我可以为 LLM 设置一个Context(这可能对模型响应产生惊人的效果)。然后,我提供一个Example——包括输入文本和输出文本——以及一个Test输入。
参数对于 STRUCTURED 和 FREEFORM 是相同的。这里是上下文和示例(包括输入和输出)用于发票 ETL 示例。

作者提供的图片
我添加了一个测试电子邮件,数据完全不同(尽管小部件相同)。以下是 UI 中显示的所有内容。我然后选择了提交,这填充了测试 JSON,在图片的右下角窗格中。

作者提供的图片。
那就是巫术魔法。 是的,数学完全正确。
那么 “L” 呢?
到目前为止,我已经展示了提取和转换——现在是加载部分。那部分其实很简单,对于零-shot(如果通过 API 完成,则需要两个调用——一个用于 E+T,一个用于 L)。
我将上一步的 JSON 作为背景,并将Q: 更改为“将 JSON 转换为 SQL 插入语句。”以下是结果,它推断出一个invoices 表和一个invoice_items 表。(你可以通过提问和/或示例 SQL 来微调 SQL。)

作者提供的图片
这意味着什么
这个示例展示了一个相当惊人的 LLM 能力,这可能会改变 ETL 工作的性质。我毫不怀疑 LLM 在这个领域的能力有限,但我还不知道那些限制是什么。与模型一起解决你的问题对于理解 LLM 能做什么、不能做什么,以及应该做什么至关重要。
未来看起来光明,GenAI Studio 可以让你迅速上手。记住,UI 提供了一些简单的复制/粘贴代码,以便你可以使用 API 而不是 UI,这对于实际进行此类工作的应用程序是必要的。
这也意味着锤子仍然不能建造房子。我的意思是模型没有搞定这个 ETL 示例。LLM 就是那个非常复杂的“锤子”——我就是木匠,就像你一样。
免责声明
本文是作者的观点和看法,并不反映其雇主的立场。(以防万一 Google 在观看。)
Google Med-PaLM:AI 临床医生
原文:
towardsdatascience.com/google-med-palm-the-ai-clinician-a4482143d60e
人工智能 | 医学 | 自然语言处理 |
谷歌的新模型经过训练以回答医学问题。怎么做到的?
·发布于 Towards Data Science ·14 min read·2023 年 3 月 17 日
--

作者使用 OpenAI DALL-E 制作的图片
医学还基于患者和医生之间的互动。此外,尽管患者接受了不同的检查和影像技术,但总是会有书面报告。那么,为什么医疗和健康领域的 AI 模型未能充分利用语言呢?
医学的基础模型?

图片由 Myriam Zilles 在 Unsplash 上拍摄
近年来的趋势是尝试限制大型 语言模型 (LMs),然后 微调 以满足所需的应用。类似的方法可以应用于大量医学文本,使模型能够学习有用的表征。对医学主题有良好理解的模型可能对无数应用有帮助(例如,患者分诊、知识检索、关键发现的总结、诊断辅助等)。
问题在于医学领域是一个特殊领域。与其他领域相比,这里有不同的问题,甚至更大的安全问题。正如我们所见,像 ChatGPT 这样的模型也可能产生幻觉,并且可能传播错误信息。
了解已知信息、最新消息、其影响以及正在变化的情况。所有这些都在一篇文章中。
谷歌的一项新研究专注于利用广泛的语言模型编码临床知识,并评估其在医学中的潜力。他们决定从一个具体任务开始:医学问答。这是因为它是一个基本但也困难的任务:模型必须提供高质量的医学问题答案。为此,模型还必须理解医学背景,找到相关信息,并依据专家的问题进行推理。
大型语言模型(LLMs)在自然语言理解方面展示了令人印象深刻的能力…
事实上,为医学创建语言模型(LM)的想法并不新鲜。事实上,多年来已经有过尝试。这是因为 LM 可以通过大量文本(通常是书籍或维基百科等通用文本)以无监督的方式进行训练。
模型在没有具体任务的情况下进行训练,但正如扩展法则所示,LM 能够展现出使其能够适应特定任务的突现行为,而无需梯度更新。一个例子是在上下文少样本学习中,模型能够“快速概括到未见任务,甚至表现出明显的推理能力,配合适当的提示策略。”此外,模型隐式地充当知识库,但也有放大训练数据集中存在的偏见的缺点。
无论如何,已经尝试了几种方法,因为有数百万篇医学文章和无数医学数据可以利用。早期模型基于BERT(sciBERT、BioBERT、PubMedBERT、DARE、ScholarBERT)。此外,也尝试过基于 GPT 架构的模型,如最近的 BioGPT。
[## 微软 BioGPT:迈向生命科学的 ChatGPT?
BioGPT 在不同生物医学自然语言处理任务中实现了SOTA。
那么这项研究带来了什么新的东西呢?
-
一个允许更好评估语言模型在医学问答中的新数据集。
-
在医学问答基准上达到最先进的结果。
-
指令提示调整以提高对医学领域的对齐。
-
对语言模型在医学领域的局限性的深入分析。

“我们贡献的概述:我们策划了 MultiMedQA,一个涵盖医学考试、医学研究和消费者医学问题的医学问答基准。我们在 MultiMedQA 上评估了 PaLM 及其指令调整变体 Flan-PaLM。通过组合提示策略,Flan-PaLM 在 MedQA(USMLE)、MedMCQA、PubMedQA 和 MMLU 临床主题上超越了 SOTA 性能。特别是,它在 MedQA(USMLE)上的表现比之前的 SOTA 提高了超过 17%。接下来,我们提出了指令提示调整以进一步将 Flan-PaLM 与医学领域对齐,生成了 Med-PaLM。在我们的人类评估框架下,Med-PaLM 对消费者医学问题的回答与临床医生生成的回答相比表现良好,展示了指令提示调整的有效性”。图像来源:这里
如何评估医学中的语言模型?

图片来源:在线营销 在 Unsplash
首先,我们需要一个好的数据集。作者指出,尽管有几个用于研究的数据集,但每个数据集都关注于特定的方面或任务:医学考试问题、医学考试问题和医学信息需求的有用答案。
我们承认医学知识在数量和质量上都是巨大的。现有基准固有地有限,只提供了医学知识空间的部分覆盖。然而,将多个不同的数据集合并用于医学问答,使得对 LLM 知识的评估比选择题准确性或诸如 BLEU 这样的自然语言生成指标更为深入。(来源)
作者换句话说,一个数据集是不够的,因为该领域相当广泛。此外,评估诸如BLEU(或其他指标)这样的指标并不能展示模型理解领域的能力。
新构建的数据集要求模型能够回答选择题、开放性问题(长形式)、封闭领域(答案必须在参考文本中找到)和开放领域问题(特定来源中信息有限)。
总结来说,作者通过结合已经使用的数据集和策划的常见健康查询数据集,构建了一个新的基准。整个数据集均为英文,并涵盖了医学考试、医学搜索,甚至是消费者查询。还有标签和元数据。

(source)
鉴于领域的复杂性,他们通过临床医生编写的答案丰富了数据集。此外:
其次,鉴于医疗领域的安全关键要求,我们认为有必要超越使用 BLEU 等指标的自动化长答案生成质量测量,转向涉及更细致的人类评估框架,如本研究中提出的框架。
一个多项选择题的示例:

(source)
以及长答案问题:

(source)
作者们随后定义了一个框架,以便临床医生能够衡量模型的稳健性。的确,尽管使用指标很有用,但它忽略了许多重要细节,在医学背景下可能会产生误导。
作者们使用了来自英国、美国和印度的临床医生的焦点小组和访谈来定义评估轴。此外,他们强调了“与科学共识的一致性、伤害的可能性和概率、答案的完整性和缺失情况以及偏见的可能性。”

“总结了临床医生在我们的消费者医疗问题回答数据集中评估答案的不同方面。这些包括与科学共识的一致性、伤害的可能性和概率、理解证据、推理和检索能力、不适当、错误或缺失内容的存在以及答案中的偏见可能性。我们使用了一组临床医生来评估模型和人工生成答案在这些方面的质量。” (source)
表格总结了表单呈现的问题种类。正如作者所述,伤害和偏见都是复杂的概念,没有单一答案。例如,伤害可以在不同层面上定义:“身体健康、心理健康、道德、财务等。”因此,作者创建了一个包含不同问题的表单,并将其提供给不同国家(美国、英国和印度)的临床医生。
另一方面,并非每个人都有医学知识,因此作者决定评估答案对消费者的帮助性和实用性。他们创建了一个表单,并由没有医学背景的人进行评分:
这项工作旨在评估答案如何应对问题背后的感知意图,以及答案的帮助性和可操作性如何。 (source)

“总结了普通用户在我们的消费者医学问答数据集中评估答案实用性的不同轴向。我们使用了 5 位非专家普通用户来评估模型和人工生成答案在这些轴向上的质量。” (source)
哪种模型?

图片由 Kimon Maritz 提供,来源于 Unsplash
作者开始使用 Pathways 语言模型(PaLM)和 Flan-PaLM 家族。
PaLM 是“一种密集激活的解码器-only 变换器语言模型,使用 Pathways 进行训练。” 该模型使用了包括互联网数据、维基百科、源代码、社交媒体对话和书籍在内的大规模语料库进行训练。PaLM 最大版本包含 540 B 的参数,并在多个基准测试中达到了最先进的水平。
Flan-PaLM 是 PaLM 的指令调整版。Flan-PaLM 使用了不同的数据集进行指令调整。如上所示,使用思维链使模型能够更好地泛化。
世界不仅仅是文本:如何将思维链扩展到图像和文本?
towardsdatascience.com
一旦我们拥有模型,主要的问题就是如何将其适应医学领域:
然而,考虑到医学领域的安全关键性质,有必要将模型适应并对齐领域特定的数据。 (source)
作者决定使用提示和提示调整作为策略。正如所示,语言模型是少量示例学习者(需要少量示例)以进行上下文学习。换句话说,通过一些精心挑选的提示示例,模型可以在没有任何梯度更新或微调的情况下学习新任务。作者使用了三种提示策略:
-
少量提示。通过基于文本的描述(输入-输出对)描述任务的少量示例。最佳演示是在与合格临床医生一致的情况下完成的(针对每个数据集)。
-
思维链提示。在提示中添加一系列中间推理步骤以得出最终答案(这种方法模仿了人类在解决问题时的推理过程)。这些提示也与临床医生一起创建。
-
自一致性提示。 提高模型在多项选择题中表现的一种策略是从模型中采样多个解码输出(最终答案将是获得多数票的那个)。‘其背后的想法是,在复杂领域中,可能有多条路径可以达到正确答案。
如前所述,提示方法使得相对低成本地改进模型成为可能(微调如此大型的模型计算成本高)。但是提示对许多任务来说是不够的,它们将从微调中受益。

由Anton Shuvalov在 Unsplash 拍摄
如何对 540B 模型进行微调?
答案:提示微调。 简而言之,提示微调是一些提示(由人类或 AI 模型生成的向量)用于引导模型完成预期任务。提示有两种类型,一种是由人类编码的(硬提示),另一种是通过反向传播学习到的(软提示)。这将可学习的参数缩小到仅表示少量标记(其余模型被冻结)。
该研究的作者使用了软提示和相关任务特定的人类设计提示(硬提示)。
我们将这种提示微调的方法称为“指令提示微调”。指令提示微调因此可以被视为一种轻量级的方法(数据高效、参数高效、训练和推理过程中计算高效)来训练模型以遵循一个或多个领域的指令。 (source)
然后,他们在一小部分示例上使用了指令提示微调,将 Flan-PaLM 适配到医学领域。由于这些示例较少,它们必须是“医学理解、临床知识回忆和医学知识推理方面的良好示例,不容易导致患者伤害。” 换句话说,这些示例特别与医学专家(来自不同学科的临床医生)合作精心策划。

医疗领域的指令提示微调 (source)
Med-PaLM:它是否比其他模型更好?

图像由Steven Lelham在 Unsplash 拍摄
模型已达到并超越了现有技术水平:
-
MedQA, 涵盖一般医学知识的多项选择题(美国医学执照考试)。
-
MedMCQA, 来自印度的医学入学考试问题(多项选择题)。
-
PubMedQA, 生物医学科学文献。
-
MMLU, 多项选择题涵盖临床知识、医学和生物学的各种主题。相关主题

左侧:SOTA LLMs 在 MMLU 临床主题上的比较。右侧:PaLM 和 Flan-PaLM 模型在不同模型尺寸变体上的表现总结。改编自这里。
结果在不同临床主题类别中也保持一致,显示出 Flan-PaLM 在所有类别中都达到了 SOTA 水平。

(source)
作者们还决定评估模型在不同模型尺寸下的表现,使用了 MultiMedQA 中的医学问答数据集。这表明,当使用少量示例提示时,扩展模型规模可以提升模型性能。同时,结果还表明,指令微调相比基线提高了性能。

(source)
此外,作者提到了两个有趣的因素:
-
首先,思维链(CoT)提示在这种情况下并没有带来改进(这实际上令人惊讶)。
-
自一致性(SC)在多项选择题表现上带来了显著改善(这是预期中的结果)。

左侧:Flan-PaLM 模型在少量示例和思维链(CoT)提示下的表现总结。右侧:Flan-PaLM 模型在有无自一致性提示(SC)下的表现总结。改编自这里。
该模型也能够生成解释,说明它为何选择了特定的响应:

“Flan-PaLM 540B 模型生成的示例解释,以支持其多项选择答案”(source)
语言模型(LMs)可能会产生幻觉,在医学背景下,这可能是灾难性的。因此,作者调查了 LLM 不确定性与陈述准确性之间的关系。换句话说,他们使用了模型置信度,发现更高的置信度对应着更高的准确性。

Flan-PaLM 540B 模型的推迟行为分析(source)
该模型是否能说服临床医生?

图片由Sander Sammy提供,来自 unsplash
最佳准确性是否足以在临床中使用该模型?
指标很重要,但可能会产生误导。特别是在像医学这样敏感的领域中,需要的不仅仅是基准测试的结果。
作者选择了 100 个可能代表真实消费者询问的问题。随后,他们使用 Flan-PaLM 和 Med-PaLM(两个 540B 模型)来预测回答,并将其提交给了一个由 9 位临床医生(来自美国、英国和印度)组成的评审小组。
尽管临床医生在 92%的问题上达成了科学共识,但 Flan-PaLM 在 61%的情况下达成了一致。
这表明,单独的通用指令微调不足以产生科学和临床上可靠的答案。然而,我们观察到 92.9%的 Med-PaLM 答案被评定为符合科学共识,展示了指令提示微调作为对齐技术在产生科学基础答案方面的优势。 (source)

(source)
模型是在以前发布的文章和书籍上进行训练的,因此作者指出这可能是失败的原因之一,未来应继续探索学习。
然后,作者询问临床医生模型在理解、知识检索和医学知识推理能力方面是否存在错误。在这里,Med-PaLM 再次表现优于 Flan-PaLM。

(source)
作者询问答案是否包含错误或缺失内容(生成答案的完整性和正确性)。也就是说,模型是否遗漏了应有的信息,或者答案中是否有不应包含的信息。Med-PaLM 的答案在 15%的情况下显示了重要信息的遗漏(相比之下,Flan-PaLM 为 47%)。令人惊讶的是,Med-PaLM 的错误率高于 Flan-PaLM(18%对 16%)。作者对此结果的解释如下:
指令提示微调使 Med-PaLM 模型生成的答案显著比 Flan-PaLM 模型更详细,从而减少了重要信息的遗漏。然而,较长的答案也增加了引入不正确内容的风险。 (source)

作者还探讨了生成响应可能带来的潜在危害的严重性和可能性。换句话说,他们询问这些响应是否可能导致临床医生或消费者/患者采取可能造成健康相关伤害的行动。尽管定义是相对的,评分在这种情况下是主观的,但与基线模型相比,指令微调产生了更安全的响应。

(source)
此外,作者分析了模型在医疗保健中放大偏见的潜在风险。该模型可能反映或放大训练数据中存在的反映健康结果和护理获取差异的模式。结果显示,新方法显著降低了偏见风险。

(source)
最后,作者分析了非专家如何评估答案。
尽管 Flan-PaLM 的回答被评估为在 60.6%的情况下有帮助,但 Med-PaLM 的回答这一数字提高到了 80.3%。 (source)
但仍然不如临床医生。通过询问非专家消费者回答是否直接回答了用户的问题,得到了类似的结果。

(source)
模型的局限性

由 Joshua Hoehne 在 unsplash 拍摄
作者指出了研究的若干限制和未来方向:
-
他们提出的数据集(MultiMedQA)并不全面(尽管包含了多个来源)。例如,它在生物学方面存在不足。此外,模型还应包括更贴近现实世界的问题和答案(多项选择题容易填补,但与现实世界相距甚远)。
-
与专家的性能评估显示该模型尚不具备临床医生的水平。
-
相同的人类评估方法可以改进。 当然,它是有限的,专家的数量应该增加。共识的概念是与上下文和时间相关的。此外,科学共识往往没有考虑到少数群体,因此可能本身就是偏见的来源。更不用说它受到了临床医生自身背景的影响。
-
偏见和伤害的分析是有限的,考虑到这是探索性工作。另一方面,医疗领域是一个极其敏感的领域,这些都是不能忽视的伦理问题。因此,分析应该扩展到包括患者。此外,缺乏类似任务的特定基准数据集。
最后的思考

由 Amine rock hoovr 在 Unsplash 拍摄
这篇论文展示了如何通过指令提示调优来改善模型在医疗问答等复杂领域的表现。然而,这种行为随着模型规模的增加而出现。此外,与其他模型相比,该模型实现了最先进的技术水平。
PaLM 的 540 B 版本单独仍然能够取得显著的成果。可能训练数据包含了多个医学来源,模型在参数中存储了这些信息。
与人类专家的评估显示,仅仅扩大规模并不够。即使是 Med-PaLM 自身也可能产生不完整或错误的回答。
无论如何,目前仍然为时尚早,尚无法在医疗保健中使用这样的模型。首先,需要更多的研究以确保模型的安全性。虽然目前很难假设使用它来治疗疾病,但可以考虑作为一种向患者提供有关疾病和药物信息的方法。
另一方面,医生也有偏见,而语言模型可能是高效的助手。未来,语言模型还可能在减轻偏见和提供更广泛的治疗选择方面发挥作用。
最后,谷歌发布了 PaLM 的 API,旨在用于原型设计和构建生成型 AI 应用程序(更多信息)
如果你觉得这很有趣:
你可以查看我的其他文章,也可以订阅以便在我发布文章时收到通知,还可以在LinkedIn上联系我。
这里是我的 GitHub 库链接,我计划在这里收集与机器学习、人工智能等相关的代码和资源。
[## GitHub - SalvatoreRa/tutorial: 机器学习、人工智能、数据科学的教程…
机器学习、人工智能、数据科学的教程,包含数学解释和可重用的代码(Python…
github.com](https://github.com/SalvatoreRa/tutorial?source=post_page-----a4482143d60e--------------------------------)
或者你可能对我最近的文章感兴趣:
一项新的研究评估了一个最常用的技术可能存在的问题
pub.towardsai.net](https://pub.towardsai.net/pca-bioinformaticians-favorite-tool-can-be-misleading-fe139262a576?source=post_page-----a4482143d60e--------------------------------) [## 稳定扩散与大脑:AI 如何读取我们的思想
研究人员能够使用 fMRI 数据重建图像
levelup.gitconnected.com](https://levelup.gitconnected.com/stable-diffusion-and-the-brain-how-ai-can-read-our-minds-45398b395ea9?source=post_page-----a4482143d60e--------------------------------) [## 稳定扩散填补医疗图像数据的空白
一项新的研究表明,稳定扩散可能有助于医学图像分析和稀有疾病。如何?
levelup.gitconnected.com 为什么我们有庞大的语言模型和小型视觉变换器?
Google ViT-22 为新一代大型变换器铺平了道路,并有望革新计算机视觉
towardsdatascience.com 为什么我们有庞大的语言模型和小型视觉变换器?
简单的 Google Pub/Sub 到 BigQuery 方法
原文:
towardsdatascience.com/google-pub-sub-to-bigquery-the-simple-way-de116234fb87
实施 BigQuery 订阅的动手指南,适用于 Pub/Sub 简单消息和流式数据摄取
·发布于 Towards Data Science ·阅读时长 8 分钟·2023 年 9 月 21 日
--

Google 最新的行星规模数据仓库订阅基础的流式摄取水下军事能力:BigSub。在这种情况下,Pub 从未达到正式发布状态,因此你必须去其他地方寻找你的啤酒。照片由 Thomas Haas 提供,来源于 Unsplash
动机
我过去遇到过许多情况,我想将 Pub/Sub 消息导入 BigQuery 表中,但我从未找到一种特别简单的方法来实现这一点。
你可以设置一个 dataflow pipeline,但这需要额外的基础设施来理解、配置、管理和调试。而且 Dataflow(作为一种托管的 Apache Beam 服务)设计用于高吞吐量流式处理,因此对于简单的消息日志记录或监控系统来说总是显得过于复杂。
而且它是 Java。可是 Python 😀!而 Java… 😫!
public static string args void main... public static string args void main... public static string args void main... public static string args void main... public static string args void main... arrrrrrrrrrrrgh
对不起,我仍然会对我第一次尝试在 Java 中学习编码(上个世纪)时的经历感到闪回。请不要尝试使用那个代码片段……远离代码片段。
然后我偶然发现了 这个,虽然它承诺简单性,但似乎比之前的方法(Debezium wtf?)还要复杂!
也可以部署一个轻量级的 Cloud Function 以在接收到 Pub/Sub 消息时触发,并将其流式传输或加载到 BigQuery 中,但这似乎仍然有些复杂,因为这本应是原生功能。
现在它已经实现了!
谷歌云的友善团队宣布了一段时间前从 Pub/Sub 到 BigQuery 的直接连接,太棒了!然而,尝试(并且失败)快速设置测试几次后,我终于有了一个实际的用例,需要为一个客户使其正常工作。
结果发现有几个细微之处,所以这篇文章旨在帮助你尽快地让这一切运行起来。
现状
Pub/Sub 是 Google Cloud 生态系统中一个极其有用、强大且可扩展的服务,具有两个核心用例:流式传输和消息传递。我将让 Google 自行解释(令人失望的剧透:这与位于潜艇上的公共房屋无关)。
Pub/Sub 用于流式分析和数据集成管道,以摄取和分发数据。它同样有效地作为服务集成的消息中介或作为队列来并行化任务。
Pub/Sub 使你能够创建事件生产者和消费者系统,称为发布者和订阅者。发布者通过广播事件而不是通过同步远程过程调用(RPC)与订阅者异步通信。
消息被发布到一个主题,主题的订阅者可以接收消息并相应地采取行动。发布者对订阅者一无所知,但当消息被发布时,订阅者可以根据消息内容采取行动。
客户端库或Cloud Storage Buckets 上的通知使发布包含可配置元数据的消息变得简单,而 Pub/Sub 会将这些消息发送到其他 Google Cloud 目的地以触发 Cloud Functions或各种不同的操作,只有你的想象力限制了这些操作。
现在我们可以将这些数据原生地导入 BigQuery(显然很简单),所以我抓住机会在几分钟内完成这个工作!但结果证明并没有那么简单。不过我完成了这个任务,所以我想写这篇文章来帮助其他需要快速设置的人,尽量减少麻烦。
解决方案
那么从哪里开始呢?我们从文档开始吧。
BigQuery 订阅在接收到消息时会将其写入现有的 BigQuery 表。你不需要单独配置订阅客户端。使用 Google Cloud 控制台、Google Cloud CLI、客户端库或 Pub/Sub API 来创建、更新、列出、分离或删除 BigQuery 订阅。
很好。我们开始吧。
作为简单数据摄取管道的替代方案,这些管道通常使用 Dataflow 写入 BigQuery,BigQuery 订阅具有以下优点:
简单部署。 你可以通过控制台、Google Cloud CLI、客户端库或 Pub/Sub API 通过一个工作流设置 BigQuery 订阅。
提供低成本。 除去了类似 Pub/Sub 管道中包含 Dataflow 作业的额外成本和延迟。这种成本优化对那些在存储前不需要额外处理的消息系统特别有用。
减少监控。 BigQuery 订阅是多租户 Pub/Sub 服务的一部分,无需你运行单独的监控作业。
听起来不错!继续前往模式部分。我必须承认,阅读几遍后,我对需要做的事情感到有些困惑。
在普通环境中,管理模式可能相当复杂,在这种情况下,我需要创建一个 BigQuery 表,其模式完全镜像输入的 JSON 数据:
使用主题模式。 这个选项允许 Pub/Sub 使用Pub/Sub 主题的模式,即订阅所附加的主题。此外,Pub/Sub 将消息中的字段写入 BigQuery 表中的相应列。当你使用此选项时,请记得检查以下额外要求:
主题模式中的字段和 BigQuery 模式中的字段必须具有相同的名称,并且它们的类型必须兼容。
哎呀。实际的输入数据可能非常复杂。它可能是嵌套的,包含数组并可能有数百个字段。JSON 和 BigQuery 结构映射得相当干净(数组 = ARRAY,对象 = STRUCT),但生成一个完全匹配的空 BigQuery 表并不是一件简单的事。
但也有另一种选择。
如果你没有选择 使用主题模式 选项,请确保 BigQuery 表中有一个名为
data的BYTES或STRING类型的列。Pub/Sub 将消息写入这个 BigQuery 列。
好的,这看起来可能是一个可行的方法。我们先将数据导入 BigQuery,然后再处理解码 JSON 的问题。实际上,我倾向于这种架构,因为任何不合规的数据仍会被接收,我们可以尝试在下游解码它。
演变模式也是如此。将原始 JSON 导入 BigQuery 的好处在于——由于它仅仅是文本——这是一个极其稳健的数据导入点。然后我们可以使用最近扩展的BigQuery JSON 函数来执行各种操作。
下游转换也更为稳健:尝试选择在普通 SQL 中不存在的列会导致查询错误,而尝试从 JSON 对象中提取等效的不存在字段将简单地返回 NULL。这样处理后,会得到一个更为稳健的流程。
输入表创建
接下来是创建用于接收原始 JSON 数据的输入表。
你可以在 UI 中创建表格,但实际上通过DDL在 BigQuery 控制台中完成这一操作非常简单(而且对我来说比截图更快 😀),然后当你第一次做错时,改变一个参数(或添加缺失的字符)并点击运行就会少很多挫败感。
表格模式实际上在文档的编写元数据部分中解释,这几乎总是增加上下文和调试支持的好主意。
创建表格的 DDL(包含所有可能的字段)如下:
CREATE OR REPLACE TABLE
`[project_id].[dataset_name].[table_name]`
(
subscription_name STRING,
message_id STRING,
publish_time TIMESTAMP,
data JSON,
attributes JSON
)
PARTITION BY TIMESTAMP_TRUNC(_PARTITIONTIME, DAY)
表格分区
在 BigQuery 中,分区几乎总是一个好主意(有时是必不可少的方法),因为它将数据物理上隔离到不同的分区中,这样可以直接查询这些分区,而无需昂贵的全表扫描。
表格分区有许多不同的选项,但需要注意的是,目前有一个4000 个分区的限制,因此每日分区将在近 11 年后达到上限,但每小时分区将在不到 24 周后达到上限。
这里有一个附注,有时由于流数据的量,确实需要使用每小时分区(例如,我们已经实现了从Tealium 的 EventStream 数据)。在这种情况下,需要部署额外的架构,将历史数据备份到 Google Cloud Storage,并设置分区过期。
另一个附注:为了简洁起见,我省略了我们为使这个过程可靠运行而采取的测试步骤。这就是从他人经验中学习的美妙之处:你可以跳过繁琐的部分,直接得到答案!
设置 Pub/Sub 主题
现在是实际设置 Pub/Sub 主题和 BigQuery 订阅的部分,这应该是一个相当快速的过程。前往Cloud Console 中的 Pub/Sub并点击[+] CREATE TOPIC按钮。你在这里有相当大的命名自由度,所以我倾向于给它和目标dataset_id完全相同的名称,不带project_id(即dataset_id.table_name)。取消勾选Add a default subscription框(因为我们接下来要创建一个 BigQuery 订阅),然后点击CREATE。
设置 Pub/Sub 订阅
一旦主题设置好,就可以开始创建订阅了。
点击你刚刚创建的主题,你应该看到一个底部区域,里面有多个选项卡,默认情况下选中的是SUBSCRIPTIONS。在这个区域点击CREATE SUBSCRIPTIONS并为订阅命名(实际上你可以将订阅名称与主题名称相同,我从未遇到过问题)。
在Delivery Type部分,选中Write to BigQuery旁边的单选按钮,选择之前创建的表的project_id、dataset_id和table_name。勾选Write Metadata复选框(我们喜欢元数据),并且—为了简单起见—将其他选项保留为默认设置。虽然建议 BigQuery 订阅应该启用死信队列,但这需要额外的主题,所以在这种情况下我们不启用。同时,由于我们不依赖使用主题模式,我们遇到失败消息的可能性也大大降低。
完成!
哦,等等。稍等一下。
权限
你很可能会看到一个错误信息,阻止你成功创建订阅。
Service account service-#############@gcp-sa-pubsub.iam.gserviceaccount.com is missing permissions required to write to the BigQuery table: bigquery.tables.get, bigquery.tables.updateData.
实际上,这很简单,但你有几个选择。
最安全的选项,符合最小权限原则,是创建一个具有精确权限(bigquery.tables.get、bigquery.tables.updateData)的自定义角色,然后将此角色分配给服务账户。
但是,由于这是一个在我的项目中从未用于其他用途的 Google Cloud 服务账户,我愿意给予它一个更简单、更宽松的角色。
无论如何,复制消息中的服务账户邮箱,进入入站表,点击SHARE和ADD PRINCIPAL。将服务账户邮箱复制到New Principals字段中。将BigQuery Data Editor角色或创建的自定义角色添加到分配角色中,然后SAVE。
希望这将顺利完成,你将拥有一个完全功能、简单的无服务器数据摄取流程,来自任何任意的 Pub/Sub 主题。
做得好!
下一步
现在你的数据摄取流程正常运行,我需要去做一些实际工作,所以我将留下来让你用一些真实的 Pub/Sub 消息进行测试。
使用最简单的方法之一是设置 BigQuery 定时查询的 Pub/Sub 通知,然后运行几次。你将看到每次运行时表格中都会增加一行,其中的JSON数据在data和attributes列中包含了每次运行的所有有用数据。
我的下一篇文章将解释将JSON有效负载解码为 BigQuery 数据类型的最佳设置,以便你可以开始使用和可视化数据。
如果你想接收第二部分(希望下周发布),记得关注我!
如何使用 Python 更改 Google 表格权限
原文:
towardsdatascience.com/google-sheet-permissions-python-fee1ff80363
使用 Python API 以编程方式与特定用户共享 Google 表格
·发布于 Towards Data Science ·阅读时间 5 分钟·2023 年 5 月 4 日
--

由 Artur Tumasjan 在 Unsplash 上拍摄
与他人共享 Google 表格是一个简单直接的任务,可以通过用户界面完成。然而,如果您需要将多个 Google 表格与特定用户或服务帐户共享呢?
想象一下,您在 BigQuery 上创建了数百个外部表,这些表从各种 Google 表格中获取数据。如果其他服务,例如 Apache Airflow,执行引用这些表的查询,您需要确保 Airflow 的服务帐户对所有这些表格具有足够的权限。但是,手动共享(即授予 Viewer 或 Editor 权限)数百个表格给特定主体几乎是不可能的,并且需要几个小时。
在本教程中,我们将演示如何使用 Python 和 Google Drive API 一次性更改数百个 Google 表格的权限
先决条件
我们首先需要确保成功获得了带有所需作用域的用户访问凭证。为此,只需运行以下命令:
gcloud auth application-default login --scopes=https://www.googleapis.com/auth/spreadsheets,https://www.googleapis.com/auth/drive,https://www.googleapis.com/auth/iam.test
然后会弹出一个窗口,在默认浏览器中请求您登录 Google 帐户。请登录,因为凭证将适用于所有使用我们将在接下来几节中演示的应用程序默认凭证客户端库的 API 调用。
然后确保通过 pip 安装 Python API 客户端 google-api-python-client(最好在一个全新的虚拟环境中):
$ python3 -m pip install google-api-python-client
查找 Google 表格的 ID
在开始编写自动化解决方案以授予 Google Sheet 文件权限之前,我们首先需要找出所有感兴趣的每个文件的 ID。
要查找 Google Sheet 的文件 ID,只需在你喜欢的网页浏览器中打开它。链接应类似于下面显示的内容:
https://docs.google.com/spreadsheets/d/abc-defg-1234/edit#gid=0
abc-defg-1234对应于 Google Sheet 的 ID。
https://docs.google.com/spreadsheets/d/spreadsheetId/edit#gid=0
有关 Google Sheets URL 是如何构建的更多详细信息,请参阅Google Sheets API概述。
使用 Python Google API 客户端更改 Google Sheet 权限
首先,让我们创建一个包含我们将要更改权限的 Google Sheet 文件 ID 的列表:
google_sheet_ids = [
'abc-1234',
'def-5678',
'ghi-9123',
]
现在我们需要做的第二件事是推断应用程序默认凭证,并创建 Google Drive 服务。
import google.auth
from googleapiclient.discovery import build
def create_service() -> Resource:
"""
Creates a Google Drive (v3) service to interact with the API
"""
scopes = [
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive',
]
creds, project_id = google.auth.default(scopes=scopes)
service = build('drive', 'v3', credentials=creds, cache_discovery=False)
return service
现在,让我们创建一个只包含指定权限所需字段的 dataclass,基于权限 REST 资源。
from dataclasses import dataclass
@dataclass
class Permission:
"""
Class that corresponds to the `permission` REST resource
https://developers.google.com/drive/api/reference/rest/v3/permissions#Permission
"""
type: str
role: str
emailAddress: str
def __post__init__(self):
"""Validate input"""
allowed_types = ['user', 'group', 'domain', 'anyone']
if self.type not in allowed_types:
raise ValueError(f'`{self.type}` is not a valid type. {allowed_types=}')
allowed_roles = ['commenter', 'reader', 'writer', 'fileOrganizer', 'organizer', 'owner']
if self.role not in allowed_roles:
raise ValueError(f'`{self.role}` is not a valid role. {allowed_roles=}')
在下一步中,我们将编写一个函数,该函数本质上接受服务和权限的实例以及文件 ID,并尝试创建一个新的权限。
from typing import Optional
from googleapiclient.discovery import Resource
from googleapiclient.errors import HttpError
def create_permission(
service: Resource,
permission: Permission,
file_id: str,
skip_on_failure: Optional[bool] = True,
):
"""
Creates a new `permission` for the specified `file_id`
"""
logging.info(f'Creating new permission {permission} for {file_id=}')
try:
request = service.permissions().create(
fileId=file_id,
body=asdict(permission),
sendNotificationEmail=False,
)
response = request.execute()
logging.info(f'New permission for {file_id=}: {response=}')
except HttpError as error:
logging.error(f'An error has occurred while trying to grant {permission=} to {file_id=}')
logging.error(f'Error was: {error}')
if not skip_on_failure:
raise error
现在,让我们编写我们的main()方法,将所有部分整合在一起,最终与目标用户共享感兴趣的 Google Sheets。
def main():
google_sheet_ids = [
'abc-1234',
'def-5678',
'ghi-9123',
]
service = create_service()
permission = Permission(type='user', role='writer', emailAddress='example@example.com')
for file_id in google_sheet_ids:
create_permission(service=service, permission=permission, file_id=file_id)
完整代码
这里有一个完全修订的代码版本,你可以用来指定新的
import logging
from dataclasses import asdict, dataclass
from typing import Optional
from googleapiclient.discovery import build, Resource
from googleapiclient.errors import HttpError
import google.auth
logging.basicConfig(
format='[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s',
level=logging.INFO,
)
def create_service() -> Resource:
"""
Creates a Google Drive (v3) service to interact with the API
"""
scopes = [
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive',
]
creds, project_id = google.auth.default(scopes=scopes)
service = build('drive', 'v3', credentials=creds, cache_discovery=False)
return service
def create_permission(
service: Resource,
permission: Permission,
file_id: str,
skip_on_failure: Optional[bool] = True,
):
"""
Creates a new `permission` for the specified `file_id`
"""
logging.info(f'Creating new permission {permission} for {file_id=}')
try:
request = service.permissions().create(
fileId=file_id,
body=asdict(permission),
sendNotificationEmail=False,
)
response = request.execute()
logging.info(f'New permission for {file_id=}: {response=}')
except HttpError as error:
logging.error(f'An error has occurred while trying to grant {permission=} to {file_id=}')
logging.error(f'Error was: {error}')
if not skip_on_failure:
raise error
def main():
google_sheet_ids = [
'abc-1234',
'def-5678',
'ghi-9123',
]
service = create_service()
permission = Permission(type='user', role='writer', emailAddress='example@example.com')
for file_id in google_sheet_ids:
create_permission(service=service, permission=permission, file_id=file_id)
if __name__ == '__main__':
main()
最终思考
授予用户对单个 Google Sheet 的访问权限是一个简单的任务,可以通过用户界面完成。只需点击电子表格右上角的‘分享’,输入用户的电子邮件地址,并选择他们的角色即可。然而,当涉及到为数百个电子表格或用户共享权限时,这一过程可能变得耗时且繁琐。
在本教程中,我们演示了如何使用 Google Drive API 和 Python Google API 客户端以编程方式为多个 Google Sheets 分配权限。我希望你觉得这篇文章有用。如果你在运行特定用例的代码片段时遇到任何困难,请在下面的评论中告诉我,我将尽力帮助你。
👉 成为会员 并阅读 Medium 上的每一个故事。你的会员费用直接支持我和你阅读的其他作者。你还将完全访问 Medium 上的每个故事。
[## 使用我的推荐链接加入 Medium — Giorgos Myrianthous
作为 Medium 会员,你的会员费用的一部分将支付给你阅读的作者,你可以完全访问每一个故事…
gmyrianthous.medium.com](https://gmyrianthous.medium.com/membership?source=post_page-----fee1ff80363--------------------------------)
👇相关的文章你也可能喜欢 👇
轻松介绍 dbt,它正在主宰数据领域
towardsdatascience.com ## ETL 与 ELT:有什么区别?
在数据工程背景下对 ETL 和 ELT 的比较
towardsdatascience.com ## setup.py 与 setup.cfg 在 Python 中的区别
使用 setuptools 管理依赖项并分发你的 Python 包
towardsdatascience.com
谷歌对多模态基础模型的最新方法
原文:
towardsdatascience.com/googles-latest-approaches-to-multimodal-foundational-model-beedaced32f9
多模态基础模型比大型语言模型更令人兴奋。让我们回顾谷歌研究的最新进展,以窥见前沿技术。
·发表于 Towards Data Science ·7 min read·2023 年 8 月 12 日
--

图片来源:unsplash.com/photos/U3sOwViXhkY
背景
虽然大型语言模型(LLM)的炒作在业界依旧火热,但领先的研究机构已经将目光转向多模态基础模型——这些模型具有与 LLM 相同的规模和多功能特性,但可以处理文本之外的数据,如图像、音频、传感器信号等。许多人认为,多模态基础模型是解锁人工智能(AI)下一阶段进步的关键。
在这篇博客文章中,我们将更详细地了解谷歌如何处理多模态基础模型。本文的内容基于谷歌最近论文中的关键方法和见解,我们在文章末尾提供了相关参考。
你为什么应该关心
多模态基础模型令人兴奋,但你为什么应该关心?你可能是:
-
一位希望跟上该领域最新研究进展的 AI/ML 从业者,但又没有耐心阅读几十篇新论文和数百页的综述。
-
一位当前或即将成为行业领导者的人,想知道大型语言模型之后会发生什么,并考虑如何将你的业务与科技界的新趋势对齐。
-
一位好奇的读者,可能会成为当前或未来多模态 AI 产品的消费者,并希望对幕后工作的原理有一个直观的了解。
对于以上所有读者,本文将提供一个很好的概述,帮助你启动对多模态基础模型的理解,这是未来更易于访问和有用的 AI 的基石。
在我们深入探讨之前还有一点需要注意:当人们谈论多模态基础模型时,他们通常指的是输入是多模态的,包括文本、图像、视频、信号等。然而,输出总是只是文本。原因在于文本是最通用的沟通格式。如果我们能输出文本,就能输出代码并将其输入到下游工具中,以生成所需的多媒体格式。更不用说现在有很多 AI 工具可以将自然语言作为输入,生成图像和声音。
从文本开始
首先需要包含的格式是文本,或者用研究术语来说,是语言。研究人员喜欢称之为语言,因为语言意味着文本来源于具有连贯语义意义的语言系统,无论是任何自然语言、代码、命令、符号等等。通俗来说,我们在这篇博客文章中就称之为文本。
关于构建大型语言模型的材料在线上有很多,所以我们在这里不会深入讨论。只需注意,谷歌最近在 LLMs 和多模态基础模型上的结果都是建立在 PaLM [1]之上的。如果你不熟悉它,可以将其视为类似于 ChatGPT 的黑箱,尽管它们的内部机制差异很大。
这里需要掌握的基本概念是,输入文本被分解为标记。每个标记随后被映射到一个嵌入空间中。实际上,每个标记变成了一个向量。然后,这些向量的列表被输入到由注意力机制网络层组成的模型中。模型的输出也是一个向量列表,可以被转换回标记,然后拼接形成最终的文本输出。
该模型拥有数百亿个参数,并在大量的文本数据上进行训练,执行一些简单的预测任务,例如给定一个文本前缀,预测下一个标记。
生成的模型将需要经过进一步的微调才能变得更直接有用。例如,谷歌已经将其 PaLM 模型应用于医学领域,并创建了 MedPaLM [2]。你可以参考这篇文章以获取更多关于如何将大型语言模型适应到特定领域的见解。
到目前为止,我们拥有一个大型语言模型。下一步是融入其他媒体格式。
融入图像
为了融合图像,首先要做的是将图像转换为可以被大语言模型处理的格式。因为我们已经有了大语言模型,并且希望多模态信息能够被统一处理,自然的选择是将图像转换为嵌入向量列表。回想一下,文本也被转换成了上述的嵌入向量列表。显然,图像和文本的向量应具有相同的维度。
谷歌在采用 Transformer 架构进行图像处理方面做了令人印象深刻的工作[3][4]。这被证明非常适合图像到向量的转换。
具体来说,每张图像被调整为 224×224 的分辨率,然后被拆分成 14×14 像素的片段。这会产生 224×224/14/14=256 个片段。每个片段经过相同的可学习线性变换以转换为向量。每个向量与一个可学习的位置嵌入进行拼接。这些拼接的向量然后输入到 Transformer 模型[5]中。谷歌研究将这一模型架构称为 ViT——视觉 Transformer。ViT 模型的输出也是 256 个向量。然后,将一个浅层网络附加到 ViT 模型的输出向量上,并对整个架构进行训练,以进行图像分类、图像描述和其他图像处理任务。有关示意图,请参见图-1。

图-1:ViT 架构的示意图。图片来自作者。
一旦完成图像处理训练,丢弃顶部的浅层网络,我们可以使用 ViT 的 256 个输出向量作为图像的嵌入表示。核心思想是,通过大规模训练,ViT 模型已经学会用一系列向量表示输入图像,这些向量可以轻松用作各种图像处理任务的输入特征。
这里有一个特别的说明,即谷歌研究发现 ViT 在图像处理上的结果优于最先进的 CNN。他们将这一优越性能归因于规模(基于 Transformer 的架构被认为有更好的扩展性)以及注意力机制可以在网络早期直接作用于任何两个输入片段的事实。
在 ViT 中完成的图像表示学习可以转移到多模态环境中,正如你所猜到的那样。来自预训练 ViT 的输出向量可以作为大语言模型的输入。
对于输入提示如what happened between <imgA> and <imgB>,词语按惯例转换为大语言模型的嵌入向量;每张图像通过 ViT 转换为 256 个向量。所有向量一起形成大语言模型的输入。最后,大语言模型在多模态训练数据上进行端到端微调(或冻结其各个部分),最终得到的模型就是多模态基础模型。有关示意图,请参见图-2。

图-2: 多模态基础模型架构的示意图。图片来自作者。
显然,上述维度数字只是谷歌研究论文使用的其中一种配置[6]。如果你愿意,可以尝试自己的配置。但要注意,ViT 输出向量的维度应与较大的语言模型输入向量的维度匹配。如果不同,你可以调整任一模型,或者在 ViT 输出和大型语言模型输入之间插入一个线性变换,以压缩或扩展 ViT 输出以匹配大型语言模型的输入维度。
扩展到其他媒体和领域
既然我们知道了如何整合图像,我们可以采用类似的方案来包含其他媒体类型。它们不一定要使用 Transformer 架构。谷歌研究在机器人领域[7]中使用了多模态基础模型,通过将状态、场景中的对象和其他输入源转换为相同的向量嵌入空间来实现。这些输入格式中的许多实际上是通过非常简单的变换转换到嵌入空间的。谷歌研究最近还将这一方法适应于医疗领域,并创建了一个用于医疗保健的多模态基础模型[8]。有关它们的用途,请参见图-3。

图-3: 多模态基础模型在 (a) 机器人技术和 (b) 医疗领域的应用示例。图片来源于参考文献[7]和[8]
结论
在这篇博客文章中,我们窥见了多模态基础模型领域的前沿。核心思想有两个方面:(1)将多媒体编码到相同的嵌入空间,以便可以由 LLM 架构统一处理;(2)将大型预训练模型(它们通常在单模态设置中预训练)结合在一起,并使用多媒体训练数据进行微调。
谷歌研究最近的一些突破确实开始接近人们希望并可以在日常生活中使用的 AI 代理。虽然还有很长的路要走才能让 AI 更加易用和有帮助,但毫无疑问,多模态基础模型是 AI 的拐点,未来的发展将会更加激动人心。
参考文献
[1] PaLM: 使用路径扩展语言建模 arxiv.org/abs/2204.02311
[2] 大型语言模型编码临床知识 arxiv.org/abs/2212.13138
[3] 一张图胜过 16x16 个词:用于大规模图像识别的 Transformer arxiv.org/abs/2010.11929
[4] 扩展视觉 Transformer arxiv.org/abs/2106.04560
[5] 注意力机制是你所需要的一切 arxiv.org/abs/1706.03762
[6] PaLI: 联合缩放的多语言语言-图像模型 arxiv.org/abs/2209.06794
[7] PaLM-E:一种具身的多模态语言模型 arxiv.org/abs/2303.03378
[8] 朝向通用生物医学人工智能 arxiv.org/abs/2307.14334
Google 的 MusicLM:从文本描述到音乐
原文:
towardsdatascience.com/googles-musiclm-from-text-description-to-music-23794ab6955c
一个新模型仅凭文本提示就能生成令人印象深刻的音乐
·发表于 Towards Data Science ·阅读时间 8 分钟·2023 年 2 月 1 日
--

图片由作者使用 OpenAI DALL-E 生成
Google 发布了一种新模型,能够从 文本描述生成音乐。结果?令人印象深刻。
实际上,将生成性人工智能应用于音乐并不是一个新概念。 近年来已经有几个尝试,包括 Riffusion、Dance Diffusion、微软的 Museformer 和 OpenAI 的 Jukebox。Google 自身之前发布过一个名为 AudioML 的模型。为什么这个模型会有所不同?
[## Microsoft 的 Museformer:人工智能音乐是新前沿
人工智能艺术正在蓬勃发展,音乐可能是下一个。
为什么用人工智能生成音乐如此困难?

图片由 Marius Masalar 在 unsplash.com 提供
与此同时,之前的模型显示出明显的技术和质量问题。结果陈旧,歌曲复杂度不高,常常重复,并且音质依然不高。
生成音乐并不容易,正如众多尝试所示。 作者们常使用 MIDI,但生成高保真音乐是另一回事。音乐还有复杂的结构,你必须考虑旋律和和声,并且有重复的模式,这些模式在时间上和距离上都会重复。
正如文章的作者指出的那样,生成文本到图像相对容易,而文本到音乐的尝试仅限于:“由几个声音事件组成的简单声学场景,持续几秒钟。”
在这里,我们谈论的是从单一文本说明开始,并生成具有长期结构的复杂音频。他们是如何实现这一点的?
无论是音乐还是语音,谷歌的新模型都能继续播放所听到的内容。
同时,正如他们所解释的,AudioLM 被用作起点。之前的模型能够将旋律继续下去,并且保持一致性。然而,仍然有几个技术限制需要克服:
-
这种模型的第一个主要限制是“音频-文本配对数据的稀缺”。事实上,文本到图像的训练得以实现是因为有大量的图像,且可以使用替代文本作为说明。
-
用几句话描述音乐的显著特征,如声学场景或旋律的音色,并不容易。
-
此外,音乐在时间维度上展开,因此有可能出现说明和音乐之间的链接较弱的风险(与静态图像相比)。
MusicLM:结构、训练、结果和限制

图片来源:James Stamler 于 unsplash.com
这个模型的第一个组件是 MuLan(也是核心部分)。这个模型用于构建音乐-文本联合嵌入,由两个嵌入塔组成(一个用于文本输入,一个用于音乐输入)。这两个塔是预训练的 BERT 和 ResNeT-50 的变体,用于音频。
MuLan 在音乐片段及其相应的文本注释对上进行训练。

MusicLM 的三个组件已分别进行预训练。其他两个组件如下文所述。图片来源:这里
正如我们在图像中看到的那样,作者提到还有两个其他组件。正如作者解释的:
我们使用 SoundStream 的自监督音频表示作为声学标记,以实现高保真合成,并使用 w2vBERT 作为语义标记,以促进长期一致的生成。在条件表示方面,我们在训练期间依赖 MuLan 音乐嵌入,在推断时依赖 MuLan 文本嵌入。
尽管这个系统看起来很复杂,但它有几个优点:它可以快速扩展,并且使用对比损失进行嵌入训练可以提高鲁棒性。此外,单独拥有预训练模型可以更好地处理带有文本输入的音乐。
在训练期间,模型学习将 MuLan 生成的标记映射转换为语义标记(w2w-BERT)。然后,声学标记在 MuLan 音频标记和语义标记(SoundStream)的条件下进行处理。

训练期间的模型。图片来源:这里
在推断期间,过程是将文本描述提供给 MuLan,MuLan 将其转换为条件信号,接着 w2w-BERT 将其转换为音频标记,然后由 SoundStream 解码器将其转换为波形。

推断期间的模型。图片来源:这里
使 MusicLM 如此强大的原因之一是它在 500 万条音频剪辑上进行了训练(总共 280,000 小时的音频)。此外,作者还创建了一个包含 5500 个由专业音乐家编写字幕的音乐剪辑的数据集(该数据集已发布这里)。每个字幕描述了音乐的四句话,并附有音乐方面的列表(如流派、情绪、节奏等)。
正如结果所示,MusicML 在音频质量和文本一致性方面都优于之前的模型(Mubert 和Riffusion)。而且在听觉质量方面也是如此。实际上,听众被展示了剪辑并被要求选择哪个剪辑最能代表文本描述(胜利意味着听众在对比中更喜欢该模型)。

“使用来自 MusicCaps 数据集的字幕对生成样本进行评估。通过 Frechet 音频距离(FAD)比较音频质量,使用 Kullback–Leibler 散度(KLD)和 MuLan 循环一致性(MCC)以及人类听力测试中的胜利次数(Wins)来比较与文本描述的一致性。” 图片来源:这里

人类听众研究的用户界面。图片来源:这里
听到结果时,难免令人印象深刻,考虑到模型中没有音乐家参与。毕竟,该模型能够捕捉到音乐细节,如乐器即兴演奏和旋律。
此外,该模型不仅限于文本描述,还可以以其他音频作为输入并继续处理(“可以以哼唱、歌唱、吹口哨或演奏乐器的形式提供”)。
作者还描述了一种称为“故事模式”的方法,即生成长音频序列,其中文本描述随着时间的推移而变化。该模型生成具有“平滑过渡、节奏一致且语义合理的音乐序列,同时根据文本描述改变音乐背景”的音乐序列。这些描述还可以包含“冥想时间”或“跑步时间”的描述,从而创建一个将适当音乐与之关联的叙事。
简而言之,该模型并不仅限于对某一乐器或音乐流派的描述,还可以根据活动、时代、地点或情绪的描述进行调整。因此,MusicLM 也可以用于电影配乐、锻炼应用等。
在此链接中,你还可以阅读字幕并听到模型的结果。
[## MusicLM
Andrea Agostinelli, Timo I. Denk, Zalán Borsos, Jesse Engel, Mauro Verzetti, Antoine Caillon, Qingqing Huang, Aren…
尽管模型令人印象深刻,但并不完美,一些音频片段的质量存在失真(但未来可能通过训练调整得到改善)。此外,该模型还可以生成合唱和人声,但歌词通常不是英语,而是一种无意义的语言,并且声音听起来更像是多位歌手的混合,而非连贯的人声。因此,作者如是说:
未来的工作可能会集中在歌词生成上,同时改进文本条件和声音质量。另一个方面是建模高级的歌曲结构,如引言、诗句和副歌。以更高的采样率建模音乐是一个额外的目标。
Google 决定不分发该模型:“我们的模型及其处理的用例存在一些风险。”正如作者所指出的,该模型反映了训练数据中存在的偏见,这在为数据集中代表性不足的文化生成音乐时会带来问题。他们还指出,该模型提出了关于文化挪用的伦理问题。

“MusicCaps 中所有 5.5k 示例的流派分布,基于 AudioSet 分类器”。图片来源:这里
另一个问题是,在 1%的情况下,模型生成了在训练期间听到的音乐,复制了现有歌曲的部分(甚至是受版权保护的元素)。TechCrunch 认为这足以劝阻谷歌发布模型(尤其是在机构有意规范人工智能和生成工具的情况下)。
他们认识到与使用案例相关的创意内容潜在的误用风险,并进一步强调了深入研究和分析的必要性,得出结论:
我们强烈强调需要更多的未来工作来应对与音乐生成相关的这些风险——目前我们没有发布模型的计划。
欧盟正在准备一项新的人工智能法案,生成式人工智能也被包含在内。
结论
MusicLM 在从文本描述生成音乐方面表现出色,相较于之前的模型是一个质的飞跃。它生成连贯的音乐,长序列能够捕捉音乐的细微差别。尽管它已经用大量数据进行了训练,但仍然不完美。
同样,正如作者所描述的,由于模型生成了受版权保护的音乐,他们没有计划发布它。这可能也源于一些程序员最近起诉了 GitHub Copilot和Getty images 起诉了 Stability AI以及 MidJourney。
此外,正如作者所指出的,生成音乐模型正在从训练集中引入偏见。MusicLM 不仅生成器乐音乐,还可以生成声乐和合唱,这可能导致生成的音乐中包含偏见和有害内容。
此外,正如多位音乐家所指出的,流媒体已经稀薄了音乐家和作曲家的收入,而生成式人工智能可能进一步减少他们的收入。
相反,作者建议这些模型将不会取代音乐家和作曲家,而是很快成为辅助人类的一组工具。
如果你觉得有趣:
你可以查找我的其他文章,你也可以订阅以在我发布新文章时收到通知,还可以在LinkedIn上联系我。感谢你的支持!
这是我 GitHub 代码库的链接,我计划在这里收集与机器学习、人工智能等相关的代码和资源。
GitHub - SalvatoreRa/tutorial: Tutorials on machine learning, artificial intelligence, data science…
包含数学解释和可重用代码(Python)的机器学习、人工智能、数据科学教程……
GitHub - SalvatoreRa/tutorial: Tutorials on machine learning, artificial intelligence, data science…
GPT-3.5 更擅长翻译段落
原文:
towardsdatascience.com/gpt-3-5-translates-paragraphs-better-5ed0031ece08
并且在翻译文学作品方面优于 Google 翻译
·发布在 Towards Data Science ·9 分钟阅读·2023 年 5 月 25 日
--

图片来源于 Pixabay
根据 以往研究,GPT 模型的表现与标准机器翻译系统相当,例如 Google 翻译。
这些研究主要集中在句子级翻译:机器翻译中默认的做法是逐句翻译,没有任何上下文。
翻译段落或整个文档对于标准机器翻译系统来说是非常困难的挑战。这些系统通常需要拆分输入或进行大量工程改造以接受和利用更长的输入。
然而,从直观上看,并遵循人工翻译者的工作流程,我们可以期待机器翻译系统在处理上下文时表现更好,例如翻译整个文档或段落。
这就是大型语言模型如 GPT 模型可以大放异彩的地方。它们可以接受比典型机器翻译系统显著更长的提示词输入。
但仍需评估以下内容:
-
利用更多上下文是否有助于提高 GPT 的机器翻译质量。
-
GPT 模型在翻译长文本时的表现,与标准机器翻译系统相比。
对于翻译段落的大型语言模型的评估面临几个挑战。
-
用于机器翻译评估的自动化指标并未设计用于段落级评估。
-
评估数据在被评估系统训练期间不得出现过。
-
评估应在多样的语言对上进行,以准确了解大型语言模型的翻译质量。
-
提示词必须设计成利用整个段落,即不仅仅是像以往工作中那样的句子。
这些挑战都由Karpinska 和 Iyyer (2023) 的“大型语言模型有效利用文档级上下文进行文学翻译,但仍存在关键错误”解决。
在这篇博客文章中,我回顾并评论了他们的工作。我们将看到他们对 GPT-3.5 的评估如何表明“在提供段落级上下文时,LLMs 产生更好的翻译”,并且对于非常多样化的语言对,其翻译质量优于最先进的神经机器翻译系统。
段落翻译的人工评估
在机器翻译中常用的自动化评估指标是不适用的。在评估段落级翻译时,它们与人工判断的相关性尚不明确。
我们不能依赖自动化指标。
人工评估仍然是具有高可信度评估的主要选择,因此该研究的作者主要依赖于 MQM 框架(Lommel et al., 2014):
-
标记翻译错误的范围并对其进行分类
-
做出两种翻译中哪一种质量更高的偏好判断
-
提供自由形式的偏好判断理由。
在此次评估中,他们收集了总共 720 对翻译段落,涵盖了 18 种语言对。
数据量真是惊人!我迫不及待想看看数据集。它将发布在 GitHub 上,这里。
文学作品的机器翻译
在评估中,这项工作选择专注于翻译文学作品。由于大多数以前的机器翻译工作集中在其他体裁/领域(新闻、用户生成文本等),因此这可能看起来是一个奇怪的选择。
文学文本的机器翻译研究不足且极具挑战性,特别是对于以句子级别工作的大型机器翻译系统。
在这种类型的文本中,上下文的细微差别非常重要,但如果系统独立翻译句子,就无法捕捉这些细微差别。通常,人工翻译者必须重组整个段落,以准确翻译成目标语言。
文学文本的翻译直观上是一个任务,其中系统以文档或段落作为输入会比仅接受较短输入的系统表现更好。
但在评估大型语言模型时,我们面临的一个主要限制是评估使用的数据必须是最新的。这对评估的可信度很重要。通过使用最近发布的数据进行评估,我们可以避免翻译可能用于训练评估模型的文本,即避免数据污染。
GPT-4 很快不会成为你的律师
[towardsdatascience.com
在这项工作中,用于评估的大部分翻译都是在 2021 年后发布的。这些特定的翻译很可能不在 GPT-3.5 的训练数据中,因为 GPT-3.5 是基于 2022 年之前发布的数据进行训练的,依据 OpenAI 的说法。
然而,被翻译的原文要老得多(出版时间从 1884 年到 2020 年)。这些原文很可能已被在这项工作中评估的系统(GPT-3.5 和 Google Translate)见过。
此外,尽管被评估的系统不太可能见过这些特定的翻译,但它们可能见过其他语言中的翻译,或者同一语言中但较早发布的翻译。
数据污染有限但仍然存在。我认为完全防止文学文本的数据污染没有更好的办法。但对于新闻等其他类型的文本,这是可能的。
一个非常多样的语言对集合
这是这项工作的一个强项:作者评估了非常多样的语言对。
作为源语言,他们选择了来自不同语言家族的语言:印欧语系(罗曼语族、日耳曼语族、斯拉夫语族)、汉藏语系和日琉语系。通过这种方式,他们确保评估能够更准确地识别 GPT-3.5 在翻译具有不同形态特征和书写系统的语言方面的优缺点。
用于评估的翻译语言包括英语(en)、波兰语(pl)、俄语(ru)、捷克语(cs)、法语(fr)、德语(de)、日语(ja)和中文(zh)。
对于目标语言,他们选择了创建“简单”(相似语言)和“困难”(不相似语言)源目标语言对的语言。
例如,捷克语-波兰语是一个简单的语言对,因为这两种语言有很多共同点。另一方面,日语-波兰语是一个极其困难的语言对,因为这两种语言来自非常不同的语言家族,具有不同的语法和书写系统。对这个语言对的机器翻译研究也非常有限。
每种源语言的选定目标语言是英语(en)、日语(ja)和波兰语(pl)。
使用 GPT-3.5 进行翻译的提示工程
评估大型语言模型时最关键的步骤之一是设计提示。
机器翻译有许多可能的提示。理想情况下,我们应该广泛评估几个提示,以评估提示选择的影响程度。
我们还必须记住,科学工作得出的结论可能仅对我们评估的非常特定的提示有效。
在评估中包括许多提示是昂贵的,因为我们必须对每个提示运行大型语言模型的推理。实际上,这意味着我们只能选择有限数量的提示进行评估。
他们使用了 5-shot 上下文学习来翻译 GPT-3.5. 提示中有 5 个翻译示例,更精确地指明了对 GPT-3.5 的期望。
选定的翻译示例对语言模型的翻译质量有着至关重要的影响。正如 Vilar et al. (2022) 所证明的,示例的翻译质量才是最重要的。
关于示例选择,他们写道:
我们从文学文本中手动策划了每对 18 种语言中的五个示例,共计 90 个示例。这些示例来自于不在我们翻译数据集中的小说,因此可能存在主题和风格上的差异 […]
这不是很详细。特别是,我不清楚“策划”涉及什么。策划标准没有提供。
一旦选择,它们包括了三种提示中的例子,这些例子利用了不同大小的上下文。
句子级提示模板
使用这个模板,待翻译的段落句子会一个接一个地提供给 GPT。这是标准的序列到序列神经机器翻译系统的工作方式。
原文在 [SRC LANG] 中:
源句子
翻译到 [TRG LANG]:
目标句子
注意:[SRC LANG]和[TRG LANG]分别表示源语言和目标语言。
带上下文提示模板的句子级翻译
翻译仍然是在句子级别进行,但句子是带有上下文的提供给 GPT-3.5 的:段落中句子之前和之后的内容都包含在提示中。
原文在 [SRC LANG] 中:
源前缀
src sent 源后缀
翻译到 [TRG LANG]:
目标前缀
trg sent
我发现这个设计非常有创意,但也很冒险。根据我的经验,如果我们没有明确地定义标签,GPT 模型可能会很容易混淆。在这种情况下,如果 GPT 只是翻译所有内容,包括标签(
段落级提示模板
模板与第一个相同,但这里提供的是整段文本而非句子。
原文在 [SRC LANG] 中:
源段落
翻译到 [TRG LANG]:
目标段落
现在我们有了提示,我们可以用它们来评估 GPT-3.5 的翻译质量。
对 GPT-3.5 段落翻译的评估
这次评估主要旨在回答两个问题:
-
像 GPT-3.5 这样的语言模型在翻译整段文本时是否比翻译单句时表现更好?
-
与 Google 翻译相比,GPT-3.5 在翻译整段文本时表现如何?
对于这次评估,作者主要依靠使用 MQM 框架的人类评估。
如果你对我的工作很熟悉,你已经知道我在写机器翻译评估时有多么苛刻。
我们是否处于一个转折点?我从对 1000 多篇科学论文的注释中得出的结论。
[towardsdatascience.com
对于这项工作,作者以非常高的科学可信度评估了他们的机器翻译系统。如果你在寻找一个优秀的机器翻译评估的例子,这就是其中之一。注意:我还推荐阅读“Prompting PaLM for Translation: Assessing Strategies and Performance”(Vilar 等,2022 年),这是另一个好的例子,就像我在博客文章“How Good Is Google PaLM at Translation?”中详细介绍的那样。
他们没有依赖自动化指标,但仍然提供了更多分析的指标分数。所有复现这些分数的细节也一并提供。这非常罕见。
他们甚至测试了他们人工评估的统计显著性。
结果:
-
GPT-3.5 在翻译段落时比翻译单个句子要好。
-
GPT-3.5 优于 Google Translate。
但这些结果在语言对之间有所不同。
对于德语到日语的翻译方向,翻译单个句子的结果更好。这是唯一的例外。根据作者的说法,这是因为用于这个翻译方向的数据有非常长的句子。
令我最惊讶的是,GPT-3.5 在翻译单个句子时也优于 Google Translate。
自动化指标也产生了非常相似的结果:COMET、BLEURT、BERTScore 和 COMET-QE 都一致认为 GPT-3.5 在任何 3 种提示模板下都优于 Google Translate。
论文展示了对其人工评估的非常详细的分析。我不会在本文中进一步讨论,但邀请你阅读。这非常有洞察力。
GPT 模型在翻译中的局限性。
论文中有一个“局限性”部分(第七部分),作者在这里讨论了使用 GPT 模型进行翻译的局限性。
作者指出,翻译段落时出现的翻译错误与翻译单个句子时的错误不同。
在翻译段落时,GPT-3.5 有时会跳过和忘记段落的一部分内容,导致翻译不正确。我在使用ChatGPT 进行翻译时也观察到了类似的行为。
这个问题可以通过对 GPT-3.5 进行机器翻译的微调来纠正。注意:不要忘记,这里评估的 GPT-3.5 模型尚未针对机器翻译进行微调。
除此之外,GPT-3.5 仍然会出现一些较为常见的错误,例如翻译错误和语法错误,但这些错误远少于谷歌翻译,评估结果表明了这一点。
这项工作的局限性
我努力寻找这项工作的局限性,但在我看来至少有一个。
提示模板的影响尚不清楚。用于段落翻译的具体模板比用于句子翻译的模板表现更好。
但我们是否可以在这种设置下得出结论,即 GPT-3.5 在翻译整段文本时表现更好?
如果我们更改模板,我们是否仍会得出相同的结论?
我们无法轻易回答这个问题。我预计这一局限性将会在所有未来评估语言模型的机器翻译研究中出现。
此外,这项工作专注于翻译文学文本。我们不能确定这项工作的结论是否适用于其他类型的文本。我期待阅读未来将填补这一空白的研究。
结论
这项工作是机器翻译领域的一个里程碑。
这表明,一个大型语言模型可以超越更标准的神经机器翻译系统,如谷歌翻译,具有非常高的科学可信度。同时,它还表明,大型语言模型在段落级别翻译中能提供比句子级别翻译更好的翻译质量。
通过这项工作和之前的 PaLM 翻译质量研究,我们有越来越多的证据表明,机器翻译的未来将基于大型语言模型。






















浙公网安备 33010602011771号