TowardsDataScience-2023-博客中文翻译-五十-

龙哥盟 / 2024-10-13 / 原文

TowardsDataScience 2023 博客中文翻译(五十)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

创作歌手何时最成功?

原文:towardsdatascience.com/when-are-songwriters-most-successful-9fdf90708e77

让我们用 KDE 图来找出答案。

Lee VaughanTowards Data Science Lee Vaughan

·发表在 Towards Data Science ·阅读时间 11 分钟·2023 年 8 月 22 日

--

一位歌手在五彩纸屑飞舞中接受格莱美奖(由 DALL-E2 协助创建)

创作歌手在什么年龄段最成功?我前几天听到一首老歌时就想到了这个问题。我印象中,像数学家一样,创作歌手的巅峰期在 20 岁中期到晚期。但数据显示了什么?

在这个快速成功数据科学项目中,我们将使用 Python、pandas 和 Seaborn 绘图库来调查这个问题。我们将查看 16 位著名创作歌手的职业生涯,他们合计有超过 500 首热门歌曲。我们还将在分析中加入一种被称为核密度估计的迷人图形。

方法论

为了确定创作歌手何时最成功,我们需要一些指南。计划是检查:

  • 包括那些与合作者合作的创作歌手。

  • 职业生涯长达数十年的创作歌手。

  • 各种风格的创作歌手和音乐流派。

  • 《Billboard Hot 100》排行榜上的创作歌手。

Hot 100 是《Billboard》杂志每周发布的排行榜,列出美国表现最佳的歌曲。这些排名基于实体和数字销售、广播播放以及在线流媒体。我们将使用它作为一种一致客观的方式来判断成功。

数据

我们将使用以下高成功率艺术家的歌曲:

本项目中使用的创作歌手名单(所有剩余的图片均由作者提供)。

我记录了每位艺术人在每首热门歌曲时的年龄,并将其保存为 CSV 文件,存储在这个 Gist 上。如果他们在同一年有多首热门歌曲,他们的年龄条目会为每首歌曲重复一次。这里是文件顶部的一个快照:

CSV 文件的前几行。

交叉参考这些信息是繁琐的(ChatGPT 拒绝这么做!)。因此,一些由这些艺术家创作但由其他人表演的作品可能不小心被遗漏了。

核密度估计图

核密度估计图是一种类似于直方图的数据点分布可视化方法。虽然直方图对观察值进行分箱和计数,KDE 图则使用高斯核对观察值进行平滑。这产生了一个连续的密度估计,其中 y 值被归一化,使得曲线下的总面积等于一。

下图比较了这两种方法。它们对底层数据的捕捉效果取决于直方图的分箱方式和 KDE 图的平滑方式。

KDE 图(曲线)与直方图(条形)用于一系列观察值(点)的对比。

与直方图不同,直方图无法区分样本在某个区间内的具体位置,而 KDE 图会在每个单独的样本上绘制一个小的高斯钟形曲线。这些钟形曲线会被叠加在一起形成最终的曲线。这使得 KDE 图比直方图更宽,假设数据会平滑地扩展到极端值。因此,即使数据的硬限制为零,KDE 图也不会突然停在零处。

最终的 KDE 曲线是由每个单独数据点上的钟形曲线构建而成。

KDE 图使用带宽进行核平滑处理。选择合适的带宽既重要又具有一定的艺术性。带宽越小,KDE 图越能忠实于底层数据。带宽越大,数据被平均和平滑的程度越高。

带宽平滑在 KDE 图中的示例。

狭窄的带宽会产生粗糙的曲线,这违背了使用 KDE 图的初衷。它们还可能引入随机噪声伪影。另一方面,宽带宽(如 2)可能会过度平滑,导致数据分布的重要特征(如双峰性)丢失。

应用不同带宽调整对最终曲线的影响。

虽然 seaborn 库的kdeplot()方法使用了良好的默认值来生成图表,但你可能需要调整bw_adjust参数,以便将 KDE 图调整到你想要讲述的故事。

那么,为什么在已经有一个完美的直方图的情况下还要使用 KDE 图呢?以下是一些原因:

  • KDE 图比直方图更简洁,当在单个图中叠加多个分布时可读性更强。

  • KDE 图可以让你看到数据中的模式(如集中趋势、双峰性和偏斜),这些在直方图视图中可能会被遮蔽。

  • 类似于 sparklines,KDE 图形适合于“快速查看”和质量控制。

  • KDE 图形在视觉上可能比直方图更具吸引力,更适合面向大众的信息图表。

  • KDE 图形便于在数据子集之间进行轻松比较。

关于 KDE 图形的更多信息,请参阅 kdeplot() 的 文档 和 seaborn 可视化指南的 Kernel density estimation 部分。

安装库

对于这个项目,我们需要安装 seaborn 用于绘图,pandas 用于数据分析。你可以按如下方式安装这些库:

使用 conda: conda install pandas seaborn

使用 pip: pip install pandas seaborn

代码

以下代码在 JupyterLab 中编写,并按单元格描述。

导入库和加载数据

导入库后,我们将选择 seaborn 的“whitegrid”样式,以确保我们的图表具有一致的外观。然后我们将使用 pandas 读取 CSV 文件。

如果你使用虚拟环境,请注意 NumPy 和 Matplotlib 是 pandas 的依赖项,因此无需单独安装它们。

由于一些艺术家的热门歌曲比其他艺术家多,而 DataFrames 需要是矩形的,因此一些列将被分配缺失值。我们将使用 fillna() 方法将这些 NaN 值替换为零。

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd

sns.set(style='whitegrid')

# Load the data and set NaN values to zero:
df = pd.read_csv('https://bit.ly/3E8Q1BO').fillna(0)
df.head(3)

初始 DataFrame 的前几行。

融化 DataFrame

我们的 DataFrame 目前是“宽格式”的。每个艺术家的名字是列的标题,他们的年龄是列的。Python 的绘图库更喜欢“长格式”。这意味着艺术家的名字应该是的值,并且应该为每个对应的年龄重复。

为了方便,pandas 提供了一个名为 melt() 的方法,可以将数据从宽格式转换为长格式。var_name 参数用于设置一个名为“Name”的新列,以存放之前的列名。value_name 参数表示之前的列值现在应该放在名为“Age”的列下。

此外,我们将添加一个名为“Color”的新列,并将其设置为“红色”。这是一种方便的方式,用于后续绘图时分组数据。我们还将过滤出 Age 值为零的记录,以准确统计每位艺术家的热门歌曲数量。

# Melt the DataFrame to a long format for plotting by artist name:
melted_df = pd.melt(df, var_name='Name', value_name='Age')

# Make a column for the plotting color:
melted_df['Color'] = 'red'

# Filter out zero values:
melted_df = melted_df[melted_df['Age'] != 0]

melted_df.tail(3)

长格式融化的 DataFrame 的尾部。

绘制每位艺术家的热门歌曲数量

接下来,我们将使用 pandas 的 value_counts() 方法来计算每位艺术家的 Hot 100 热门歌曲数量。作为过程的一部分,我们将按降序排序数据。

# Calculate the order of the bars by counts:
order = melted_df['Name'].value_counts().index

# Plot a bar chart of the number of hits by each artist:
ax = sns.countplot(data=melted_df, 
                   x='Name', 
                   color='red', 
                   order=order)
ax.set_xticklabels(ax.get_xticklabels(), 
                   rotation=55, 
                   fontsize=10, 
                   ha='right')
ax.set_title('Number of Billboard Hot 100 Hits');

每位艺术家在其职业生涯中的Hot 100 热门歌曲数量。

绘制最后一次热门歌曲时的年龄和歌曲之间的总时间

接下来,我们将绘制每位艺术人在其最后一次Hot 100热门歌曲时的年龄,以及首次和最后一次出现在榜单上的时间跨度。

由于我们需要找出每位艺术家的最小年龄,我们首先将df DataFrame 中的 0 值设置为NaN,这样它们将被忽略。否则,0 会被选为最小年龄。我们不会对结果进行排序,因为我们希望将图表并排比较,因此希望艺术家名称保持相同的顺序。

# Replace 0 values with NaN in order to find minimum age statistic:
df = df.replace(0, np.NaN)

# Calculate maximum age for each column
max_age = df.max()

# Calculate age span (maximum age - minimum age) for each column
age_span =df.max() - df.min()

# Create subplots for two bar charts
fig, axes = plt.subplots(nrows=1, ncols=2, 
                         figsize=(8, 6))

# Plot artist's age at time of last hit:
sns.barplot(x=max_age.values, 
            y=max_age.index, 
            ax=axes[0], 
            color='red', 
            orient='h')
axes[0].set_title('Artist Age at Time of Last Hit')
axes[0].set_xlabel('Age at Last Hit')
axes[0].set_ylabel('Artist')

# Plot age span between hits:
sns.barplot(x=age_span.values, 
            y=age_span.index, 
            ax=axes[1], 
            color='red', 
            orient='h')
axes[1].set_title("Years Between Artist's First & Last Hits")
axes[1].set_xlabel('Years Between First & Last Hit')
axes[1].set_ylabel('')

plt.tight_layout();

比较每位艺术家最后一次热门歌曲的年龄与歌曲之间的时间跨度。

从这些图表中可以得出结论,超过一半的艺术家在 50 岁之前就登上了所有热门榜单,并且这些热门歌曲的时间跨度为 30 年或更短。

计算年龄统计数据

现在,让我们找出艺术家们最成功的年龄。由于单一统计数据无法完全捕捉这一点,我们将使用 pandas 的mean()median()mode()方法。

# Calculate the statistics:
mean_age = round(melted_df.mean(numeric_only=True).mean(), 1)
median_age = round(melted_df.median(numeric_only=True).median(), 1)
mode_age = round(melted_df.mode(numeric_only=True).mode(), 1)

print(f"Mean age = {mean_age}")
print(f"Median age = {median_age}")
print(f"Mode age = {mode_age}")

在调查的 16 位创作歌手中,成功的“甜点”似乎在 29 至 33 岁之间。

注意:本分析的假设是歌曲是在其出现在Hot 100榜单的同一年创作的。由于歌曲创作和出现在榜单之间会有时间滞后,我们的统计数据可能会对年长的年龄有所偏倚。

使用 KDE 图找到成功的甜点

寻找甜点的可视化方式是使用 KDE 图。我们将使用 seaborn 的kdeplot()方法,并传递融化的 DataFrame。我们还将hue参数设置为“颜色”列,这意味着它将忽略艺术家的名字,并将所有年龄值绘制为一个组。

# Create a KDE plot for complete dataset:
ax = sns.kdeplot(data=melted_df, 
                 x='Age', 
                 hue='Color', 
                 fill=True, 
                 palette='dark:red_r', 
                 legend=False)

# Set x-axis tick positions:
ax.set_xticks(range(0, 85, 5))  

ax.set_title('KDE Plot of Billboard Hot 100 Hits by Age For All Artists',
             fontsize=14);

所有艺术家的“年龄”列的 KDE 图。

该图确认了我们之前学到的,最大成功通常在 30 岁左右。70 岁后的高峰代表了保罗·麦卡特尼在 2015 年与坎耶·维斯特的合作,当时麦卡特尼 73 岁。

使用 Facet Grid 比较职业生涯

Facet grids是创建多个具有共享坐标轴的图表的方法,这些图表显示数据集的不同子集。我们将制作一个 facet grid,使用 KDE 图来比较每位艺术家的热门分布。

我们将首先调用 seaborn 的FacetGrid()方法,并将其应用于融化的 DataFrame 及其“名称”列。为了在没有干扰的情况下比较所有艺术家,我们将为每条曲线使用相同的颜色,由hue参数指定。通过将col_wrap参数设置为2,我们将显示分成 2 列,每列 8 条曲线。

定义好 facet grid 后,我们将调用kdeplot()方法,并将其映射到 facet grid 中,指定为g变量。我们将带宽调整(bw_adjust)设置为0.4,以避免平滑掉数据中的所有变异性。

# Plot a Facet Grid of each artist's hits vs. age as a KDE chart:
g = sns.FacetGrid(data=melted_df, 
                  col='Name', 
                  hue='Color', 
                  aspect=3, 
                  height=1.00, 
                  col_wrap=2, 
                  palette='dark:red_r')

g.map(sns.kdeplot, 'Age',
      fill=True, 
      alpha=0.7, 
      lw=2, 
      bw_adjust=0.4)

g.set_axis_labels('Age', 'Density')
g.set_titles(col_template='{col_name}')
g.tight_layout()

# Loop through each subplot to set custom x-axis tick labels:
for ax in g.axes.flat:
    ax.set_xticks(range(0, 80, 10))
    ax.set_xticklabels(range(0, 80, 10))

# Add a title to the FacetGrid
g.fig.suptitle('Billboard Hot 100 Hits vs. Age', 
               y=1.03,
               fontsize=16);

每位艺术家的 KDE 图的 facet grid。

多么美妙的图表!简洁而信息丰富。这就是 KDE 图发挥作用的地方。

只需一眼,你就可以看到斯汀在警察乐队的早期成功,随后是成功的个人生涯。约翰尼·卡什的双峰分布反映了他与药物成瘾的斗争。保罗·西蒙在格拉斯兰专辑中的后期职业成功在 45 岁时显得只是一个短暂的高峰。正如我们在早期分析中所见,大多数峰值趋向于集中在 29 到 34 岁之间。

绘制堆叠 KDE 图

另一种用 KDE 图讲故事的方法是将它们堆叠在同一面板中。这总是比堆叠直方图效果更好。

对于我们当前的项目,有太多艺术家使得在个人层面上这种方法效果不佳。但由于我们的目标是突出所有艺术家的“成功甜点”,它做得还算不错。

要堆叠 KDE 图,我们只需调用 kdeplot() 方法,而不使用 facet grid。这里一个重要的参数是 common_norm,它代表“公共归一化”。

根据 seaborn 的文档,"当 common_norm 设置为 True 时,所有 KDE 曲线将使用相同的尺度进行归一化。这在你想比较不同组的整体分布形状时非常有用。当你有多个样本量不同或值范围不同的组时,尤其有帮助,因为这可以确保曲线在形状上直接可比。"

我们每位艺术家的样本量确实不同,并且希望比较曲线,所以我们将 common_norm 设置为 True

# Create a stacked KDE plot:
fig, ax = plt.subplots(figsize=(10, 6))
ax = sns.kdeplot(data=melted_df, 
                 x='Age', 
                 hue='Name', 
                 fill=True, 
                 palette='dark:red_r', 
                 common_norm=True)
ax.set_xticks(range(0, 85, 5))
ax.set_title('Stacked KDE Plot by Artist')
ax.set_xlabel('Age')
ax.set_ylabel('Density');

每位艺术家的堆叠 KDE 图,公共归一化设置为“True”。

虽然很难甚至不可能识别特定艺术家的曲线(即使你为每位艺术家使用不同的颜色),但很明显最佳年龄在 30 岁左右。

如果你感兴趣的话,这里是 common_norm 设置为 False 时的图:

每位艺术家的堆叠 KDE 图,公共归一化设置为“False”。

绘制分布图

最后,让我们将数据可视化为分布图。seaborn 的 displot() 方法提供了一个图形级别的接口,用于在 seaborn FacetGrid 上绘制分布图。它允许你选择多种图类型,例如 KDE 和直方图,通过 kind 参数。

另一个不错的功能是添加了“rug 图”,通过 rug 参数实现。rug 图标记了数据点在轴上的位置。这让你能够看到实际数据的范围,这些数据可能被 KDE 图的“尾部”遮掩。

# Plot the distribution of hits vs age:
sns.displot(data=melted_df,
            x='Age', hue='Name',
            kind='kde', height=6,
            multiple='fill', clip=(0, None),
            palette='dark:red_r', 
            common_norm=True, rug=True);

包含 KDE 和 rug 图的分布图(图像由作者提供)

我个人发现这个图很难解读,但它确实突出了 30 岁左右的高峰成功年。

结论

KDE 曲线图以其平滑、令人愉悦的形状,是可视化单变量数据的绝佳方式。虽然功能上类似于直方图,但它们更容易展示数据中的模式,并在同一图形中堆叠和比较多个分布。

借助 KDE 曲线图,我们能够展示出创作歌手在大约 30 岁时最为成功。因此,如果你想开始创作歌手的职业生涯,千万不要拖延!

谢谢!

感谢阅读,请关注我,以便未来了解更多快速成功的数据科学项目。

当 AutoML 遇上大型语言模型

原文:towardsdatascience.com/when-automl-meets-large-language-model-756e6bb9baa7

利用 LLMs 的力量来指导超参数搜索

Shuai GuoTowards Data Science Shuai Guo

·发表于Towards Data Science ·23 分钟阅读·2023 年 10 月 5 日

--

约翰·施诺布里奇拍摄于Unsplash

自动化机器学习,简称 AutoML,旨在自动化机器学习流程中的各个步骤。其中一个关键方面是超参数调优。超参数是指控制机器学习算法结构和行为的参数(例如,神经网络模型中的层数),这些参数的值在训练之前设定,而不是从数据中学习,这与其他机器学习参数(例如神经网络层的权重和偏置)不同。

当前在 AutoML 中用于超参数调优的做法 heavily rely on sophisticated algorithms (such as 贝叶斯优化) to automatically identify the optimal combination of the hyperparameters that yields the best model performance.

从纯算法的角度解决超参数调优问题虽然有效,但还有另一个重要的信息可以补充算法搜索策略:人类专业知识

资深数据科学家凭借多年的经验和对机器学习算法的深刻理解,通常能够直观地知道从哪里开始搜索,哪些搜索空间的区域可能更有前景,或何时缩小或扩大可能性。

因此,这给了我们一个非常有趣的想法:我们能否设计一个可扩展的专家指导搜索策略,既利用专家提供的细致见解,又结合 AutoML 算法提供的搜索效率?

这是大语言模型(LLM),例如 GPT-4,可以发挥作用的地方。在它们的大量训练数据中,有一部分文本专门用于解释和讨论机器学习(ML)的最佳实践。由于这一点,LLM 能够内化大量的 ML 专业知识,并获得大量的集体 ML 智慧。这使得 LLM 作为潜在的知识渊博的 ML 专家,可以与现有的 AutoML 工具进行互动,共同进行专家指导的超参数调整。

LLM 指导的超参数调整示例。(图像作者提供)

在这篇博客文章中,让我们尝试这个想法,并实现一个由 LLM 指导的超参数调整工作流。具体来说,我们将开发一个将 LLM 指导与简单随机搜索相结合的工作流,然后将其调整结果与一种名为FLAML(微软研究院开发的最先进的算法驱动的 AutoML 工具)的现成工具进行比较。进行这种比较的理由是评估即使与简单的搜索算法配对,借助 ML 领域专业知识是否真的能带来价值。

这个想法与你有共鸣吗?那就开始吧!

[注]: 本博客中展示的所有提示均由 ChatGPT (GPT-4) 生成并优化。这是必要的,因为它确保了提示的质量,并且有助于避免繁琐的手动提示工程。

这是我关于 LLM 项目的系列博客中的第四篇。第一篇是 构建 AI 驱动的语言学习应用,第二篇是 开发用于研究论文摘要的自主双聊天机器人系统,第三篇是 通过现实生活模拟训练数据科学中的问题解决技能。欢迎查看!

目录

· 1. 案例研究

∘ 1.1 数据集描述

∘ 1.2 模型描述

· 2. 工作流设计 · 3. 配置聊天机器人 · 4. 建议优化度量 · 5. 定义初始搜索空间 · 6. 精炼搜索空间 · 7. 调整日志分析

∘ 7.1 随机搜索与连续减半

∘ 7.2 日志分析

· 8. 案例研究

∘ 8.1 确定度量

∘ 8.2 第一次搜索迭代

∘ 8.3 第二次搜索迭代

∘ 8.4 第三次搜索迭代

∘ 8.5 测试

· 9. 与现成 AutoML 工具的比较 · 10. 结论

1. 案例研究

为了将我们的讨论具体化,让我们首先介绍一下我们将要调查的案例研究。

在本博客中,我们将查看一个二分类问题的超参数调优任务。更具体地,我们将研究一个名为NSL-KDD的网络安全数据集,并识别XGBoost模型的最佳超参数,以便训练出的模型可以准确区分良性和攻击活动。

1.1 数据集描述

NSL-KDD 数据集是网络入侵检测领域广泛使用的数据集。完整的数据集包含四种攻击类别,即 dos(拒绝服务)、r2l(远程机器的未经授权访问)、u2r(权限提升尝试)以及 probe(暴力探测攻击)。在我们当前的案例研究中,我们研究一个二分类问题:我们只考虑“良性”或“探测”性质的数据样本,并训练一个能够区分这两种状态的分类器。

NSL-KDD 数据集包含 40 个特征(例如,连接长度、协议类型、传输数据字节等),这些特征源自原始网络流量数据,捕获了单个网络连接的各种特征。数据集已经预先划分为训练集和测试集。下表显示了两个集合中的样本数量:

训练集和测试集中样本的数量。(图片作者提供)

请注意,我们当前的数据集是不平衡的。这在网络安全应用中很常见,因为可用的攻击样本数量通常远小于良性样本的数量。

你可以在这里找到预处理的数据集。

1.2 模型描述

尽管完整的 AutoML 包括模型选择,但在当前的案例研究中,我们将范围限制在仅优化 XGBoost 模型。XGBoost 模型以其多才多艺的性能而闻名,但前提是使用了正确的模型超参数。不幸的是,由于 XGBoost 的超参数数量众多,确定给定数据集的最佳超参数组合并不容易。这正是 AutoML 可以发挥作用的地方。

我们考虑调整以下 XGBoost 超参数:

n_estimators, max_depth, min_child_weight, gamma, scale_pos_weight, learning_rate, subsample, colsample_bylevel, colsample_bytree, reg_alpha, 和 reg_lambda。

关于上述超参数的详细描述,请参阅官方文档。

2. 工作流设计

要实现 LLM 指导的超参数调优,我们需要回答两个问题:我们应该如何将 LLM 的机器学习专业知识融入调优过程?我们应该如何让 LLM 与调优工具互动?

1️⃣ 如何将 LLM 的机器学习专业知识融入调整过程中?

好吧,在调整过程中至少有三个地方,LLM 的机器学习专业知识可以提供指导:

  • 建议优化指标:超参数调整通常需要一个指标来定义在各种竞争的超参数组合中什么被认为是“最优”的。这为基础的优化算法(例如,贝叶斯优化)设定了目标。LLMs 可以提供关于哪些指标更适合特定类型问题和数据集特征的见解,并可能提供对候选指标的优缺点解释。

  • 建议初始搜索空间:由于大多数超参数调整任务是以迭代方式进行的,通常需要配置初始搜索空间以为优化过程奠定基础。基于其学习的机器学习最佳实践,LLMs 可以推荐对研究数据集特征和选定机器学习模型有意义的超参数范围。这可以潜在地减少不必要的探索,从而节省大量计算时间。

  • 建议搜索空间的细化:随着调整过程的推进,通常需要对配置的搜索空间进行细化。细化可以朝两个方向进行,即缩小某些超参数的范围到显示出前景的区域,或扩展某些超参数的范围以探索新的领域。通过分析前几轮的优化日志,大型语言模型(LLMs)可以自动提出新的细化建议,将调整过程引导向更有前景的结果。

2️⃣ 如何让 LLM 与调整工具进行互动?

理想情况下,我们可以将随机搜索工具封装为 API,并实现一个基于 LLM 的代理,该代理可以访问这个 API 来进行调整。然而,由于时间有限,我未能配置一个能够可靠执行上述迭代调整过程的工作代理:有时代理由于输入模式不正确而无法正确使用工具;其他时候代理则完全偏离了任务。

作为替代,我实现了一个简单的基于聊天机器人的工作流,其中包含以下两个组件:

  • 具有记忆的聊天机器人。在这里,记忆很重要,因为聊天机器人需要回忆之前建议的搜索空间。

  • 三个提示分别对应建议优化指标、建议初始搜索空间和建议搜索空间的细化。

为了启动工作流,首先提示聊天机器人根据问题背景和数据集特征建议一个合适的优化指标。然后解析聊天机器人的响应,提取并存储指标名称作为变量。第二步,提示聊天机器人建议一个初始搜索空间。与第一步一样,解析响应并提取搜索空间,存储为变量。获得这两个信息后,将调用随机搜索工具,使用聊天机器人建议的指标和搜索空间。总体而言,这构成了第一轮迭代。

一旦随机搜索工具完成搜索,将提示聊天机器人根据上一次运行的结果推荐搜索空间的细化。在成功解析聊天机器人的响应中的新搜索空间后,将进行另一轮随机搜索。这个过程会迭代,直到计算预算耗尽或达到收敛,例如,较上次运行没有更多改进。

基于聊天机器人的解决方案,用于实现 LLM 引导的超参数调优。(图像由作者提供)

尽管不如基于代理的方法优雅,这种基于聊天机器人的工作流具有几个好处:

  • 易于实现和实现质量控制。将聊天机器人与调优工具集成简化为设计三个提示和几个辅助函数,以从聊天机器人的响应中提取目标信息。这比完全集成的基于代理的方法要简单得多。此外,由于超参数调优任务被分解为明确的步骤,每一步都可以被监控和调整,从而使质量控制变得更可控,确保搜索过程保持在轨道上。

  • 完全透明的决策过程。由于聊天机器人会清晰地说明其做出的每个决策或建议,超参数调优过程不再是对用户的黑箱过程。这对于实现可解释的值得信赖的 AutoML 至关重要。

  • 允许结合人类直觉。尽管当前的工作流设计为自动化,但可以轻松扩展,以允许人类专家在每次搜索迭代之前选择接受建议或根据自己的专业知识进行必要的调整。这种灵活性为人类参与的调优打开了大门,可能会导致更好的优化结果。

在接下来的章节中,我们将逐一详细介绍配置聊天机器人以及建议指标、初始搜索空间和搜索空间细化的提示。

3. 配置聊天机器人

我们可以使用 LangChain 轻松设置一个具有记忆的聊天机器人。我们首先导入必要的库:

from langchain.prompts import (
    ChatPromptTemplate, 
    MessagesPlaceholder, 
    SystemMessagePromptTemplate, 
    HumanMessagePromptTemplate
)
from langchain.chains import ConversationChain
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory

在 LangChain 中,聊天机器人可以通过ConversationChain对象进行配置,这需要一个主干 LLM、一个用于保存对话历史的内存对象以及一个用于指定聊天机器人行为的提示模板:

# Set up LLM
llm = ChatOpenAI(temperature=0.8)

# Set up memory
memory = ConversationBufferMemory(return_messages=True)

# Set up the prompt
prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(system_message),
    MessagesPlaceholder(variable_name="history"),
    HumanMessagePromptTemplate.from_template("""{input}""")
])

# Create conversation chain
conversation = ConversationChain(memory=memory, prompt=prompt, 
                                llm=llm, verbose=False)

在上面的代码片段中,变量system_message设置了聊天机器人的上下文,如下所示:

# Note: the prompt is generated and optimized by ChatGPT (GPT-4)
system_message = f"""
You are a senior data scientist tasked with guiding the use of an AutoML 
tool to discover the best XGBoost model configurations for a given binary 
classification dataset. Your role involves understanding the dataset 
characteristics, proposing suitable metrics, hyperparameters, and their 
search spaces, analyzing results, and iterating on configurations. 
"""

注意,我们在系统消息中指定了聊天机器人的角色、目的和预期行为。稍后,我们可以使用配置好的聊天机器人进行推理:

# Invoke chatbot with the input prompt
response = conversation.predict(input=prompt)

其中 prompt 是我们对聊天机器人的具体指令,即建议优化度量标准、建议初始搜索空间或建议优化搜索空间。我们将在接下来的几节中介绍这些提示。

4. 建议优化度量标准

作为 LLM 引导的超参数调整的第一步,我们希望聊天机器人提出一个合适的度量标准,以便调整工具进行优化。这个决定应基于几个因素,包括问题的性质(即回归还是分类)、给定数据集的特征,以及来自业务或实际应用的其他具体要求。

以下片段定义了实现我们目标的提示:

def suggest_metrics(report):

    # Note: The prompt is generated and optimized by ChatGPT (GPT-4)
    prompt = f"""
    The classification problem under investigation is based on a network 
    intrusion detection dataset. This dataset contains Probe attack type, 
    which are all grouped under the "attack" class (label: 1). Conversely, 
    the "normal" class is represented by label 0\. Below are the dataset's 
    characteristics:
    {report}.

    For this specific inquiry, you are tasked with recommending a suitable 
    hyperparameter optimization metric for training a XGBoost model. It is 
    crucial that the model should accurately identify genuine threats (attacks) 
    without raising excessive false alarms on benign activities. They are equally 
    important. Given the problem context and dataset characteristics, suggest 
    only the name of one of the built-in metrics: 
    - 'accuracy'
    - 'roc_auc' (ROCAUC score)
    - 'f1' (F1 score)
    - 'balanced_accuracy' (It is the macro-average of recall scores per class 
    or, equivalently, raw accuracy where each sample is weighted according to 
    the inverse prevalence of its true class) 
    - 'average_precision'
    - 'precision'
    - 'recall'
    - 'neg_brier_score'

    Please first briefly explain your reasoning and then provide the 
    recommended metric name. Your recommendation should be enclosed between 
    markers [BEGIN] and [END], with standalone string for indicating the 
    metric name.
    Do not provide other settings or configurations.
    """

    return prompt

在上面的提示中,我们已经向聊天机器人说明了问题的背景、数据集特征(封装在变量report中,我们稍后将讨论),目标(推荐一个适合的超参数优化度量标准用于训练 XGBoost 模型)、具体要求(高检测率和低误报率)以及候选度量标准(它们都在 sci-kit learn 中受支持)。注意,我们明确要求聊天机器人将度量标准名称输出在[BEGIN]-[END]块内,这样可以方便地自动提取信息。

为了生成数据报告,我们可以定义以下函数:

def data_report(df, num_feats, bin_feats, nom_feats):
    """
    Generate data characteristics report.

    Inputs:
    -------
    df: dataframe for the dataset.
    num_feats: list of names of numerical features.
    bin_feats: list of names of binary features.
    nom_feats: list of names of nominal features.

    Outputs:
    --------
    report: data characteristics report.
    """

    # Label column 
    target = df.iloc[:, -1]
    features = df.iloc[:, :-1]

    # General dataset info
    num_instances = len(df)
    num_features = features.shape[1]

    # Class imbalance analysis
    class_counts = target.value_counts()
    class_distribution = class_counts/num_instances

    # Create report
    # Note: The format of the report is generated and optimized
    # by ChatGPT (GPT-4)
    report = f"""Data Characteristics Report:

- General information:
  - Number of Instances: {num_instances}
  - Number of Features: {num_features}

- Class distribution analysis:
  - Class Distribution: {class_distribution.to_string()}

- Feature analysis:
  - Feature names: {features.columns.to_list()}
  - Number of numerical features: {len(num_feats)}
  - Number of binary features: {len(bin_feats)}
  - Binary feature names: {bin_feats}
  - Number of nominal features: {len(nom_feats)}
  - Nominal feature names: {nom_feats}
"""

    return report

在这里,我们特别计算了给定的数据集是否存在不平衡,因为这一信息可能会影响优化度量标准的选择。

现在我们已经完成了第一个提示,指示聊天机器人建议一个适合的度量标准来评估各种超参数配置的性能。接下来,让我们看看如何构建定义初始搜索空间的提示。

5. 定义初始搜索空间

除了优化度量标准,我们还需要一个超参数的初始搜索空间来开始调整过程。以下片段展示了实现这一目标的提示:

def suggest_initial_search_space():

    # Note: The prompt is generated and optimized by ChatGPT (GPT-4) 
    prompt = f"""
    Given your understanding of XGBoost and general best practices in machine 
    learning, suggest an initial search space for hyperparameters. 

    Tunable hyperparameters include:
    - n_estimators (integer): Number of boosting rounds or trees to be trained.
    - max_depth (integer): Maximum tree depth for base learners.
    - min_child_weight (integer or float): Minimum sum of instance weight 
    (hessian) needed in a leaf node. 
    - gamma (float): Minimum loss reduction required to make a further 
    partition on a leaf node of the tree.
    - scale_pos_weight (float): Balancing of positive and negative weights.
    - learning_rate (float): Step size shrinkage used during each boosting 
    round to prevent overfitting. 
    - subsample (float): Fraction of the training data sampled to train each 
    tree. 
    - colsample_bylevel (float): Fraction of features that can be randomly 
    sampled for building each level (or depth) of the tree.
    - colsample_bytree (float): Fraction of features that can be randomly 
    sampled for building each tree. 
    - reg_alpha (float): L1 regularization term on weights. 
    - reg_lambda (float): L2 regularization term on weights. 

    The search space is defined as a dict with keys being hyperparameter names, 
    and values are the search space associated with the hyperparameter. 
    For example:
        search_space = {{
            "learning_rate": loguniform(1e-4, 1e-3)
        }}

    Available types of domains include: 
    - scipy.stats.uniform(loc, scale), it samples values uniformly between 
    loc and loc + scale.
    - scipy.stats.loguniform(a, b), it samples values between a and b in a 
    logarithmic scale.
    - scipy.stats.randint(low, high), it samples integers uniformly between 
    low (inclusive) and high (exclusive).
    - a list of possible discrete value, e.g., ["a", "b", "c"]

    Please first briefly explain your reasoning, then provide the 
    configurations of the initial search space. Enclose your suggested 
    configurations between markers [BEGIN] and [END], and assign your 
    configuration to a variable named search_space.
    """

    return prompt

上面的提示包含了大量信息,所以我们来逐步拆解:

  • 我们首先向聊天机器人说明了我们的目标。

  • 我们提供了一个可调超参数及其含义的列表。此外,我们还指出了每个超参数的预期数据类型。这些信息对 LLM 决定采样分布至关重要。

  • 我们定义了搜索空间的预期输出格式,以便进行有效解析。

  • 我们指明了 LLM 可以建议的可用采样分布。

请注意,我们明确要求聊天机器人简要说明建议的搜索空间的理由。这对于实现透明度和可解释性至关重要。

这就是推荐初始搜索空间的内容。接下来,我们来看看如何优化搜索空间。

6. 优化搜索空间

在从聊天机器人获取优化指标和初始搜索空间后,我们可以启动一次调优轮次。随后,我们可以将生成的 AutoML 日志输入到聊天机器人中,并提示它建议对搜索空间进行优化。

以下代码片段展示了实现目标的提示:

def suggest_refine_search_space(top_n, last_run_best_score, all_time_best_score):
    """
    Generate prompt for refining the search space.

    Inputs:
    -------
    top_n: string representation of the top-5 best-performing configurations.
    last_run_best_score: best test score from the last run.
    all_time_best_score: best test score from all previous runs.

    Outputs:
    --------
    prompt: generated prompt.
    """

    # Note: The prompt is generated and optimized by ChatGPT (GPT-4)
    prompt = f"""
    Given your previously suggested search space, the obtained top configurations 
    with their test scores:
    {top_n}

    The best score from the last run was {last_run_best_score}, while the best 
    score ever achieved in all previous runs is {all_time_best_score}

    Remember, tunable hyperparameters are: n_estimators, max_depth, min_child_samples, 
    gamma, scale_pos_weight, learning_rate, subsample, colsample_bylevel, 
    colsample_bytree, reg_alpha, and reg_lambda.

    Given the insights from the search history, your expertise in ML, and the 
    need to further explore the search space, please suggest refinements for 
    the search space in the next optimization round. Consider both narrowing 
    and expanding the search space for hyperparameters where appropriate.

    For each recommendation, please:
    1\. Explicitly tie back to any general best practices or patterns you are 
    aware of regarding XGBoost tuning
    2\. Then, relate to the insights from the search history and explain how 
    they align or deviate from these practices or patterns.
    3\. If suggesting an expansion of the search space, please provide a 
    rationale for why a broader range could be beneficial.

    Briefly summarize your reasoning for the refinements and then present the 
    adjusted configurations. Enclose your refined configurations between 
    markers [BEGIN] and [END], and assign your configuration to a variable 
    named search_space.
    """

    return prompt

有几个值得解释的地方:

  • 我们向聊天机器人提供了上一次调优轮次中表现最好的前 5 个配置及其相关测试分数。这可以作为聊天机器人确定下一轮优化的基础。

  • 通过包括上一次运行的最佳测试分数和所有之前的运行,聊天机器人可以判断上次运行中建议的搜索空间是否有效。

  • 我们明确要求聊天机器人考虑进一步探索搜索空间。一般而言,优化的最佳实践是平衡探索与利用。由于我们使用的随机搜索算法(将在结果部分讨论)已经涵盖了利用,因此让 LLM 更多地关注探索是合理的。

  • 与其他提示类似,我们要求 LLM 对其建议进行推理,以确保透明度和可解释性。

在下一节中,让我们看看生成top_n变量的日志分析逻辑。

7. 调优日志分析

作为一种迭代方法,我们希望聊天机器人在上一轮搜索完成后提出新的搜索空间,而决策过程的基础应该是上一轮搜索中生成的日志。在本节中,我们首先介绍了当前案例研究中使用的搜索算法。然后,我们讨论了日志结构和提取有用见解的代码。

7.1 逐步折半的随机搜索

正如本文开头所提到的,我们希望将一种简单的超参数搜索算法与 LLM 结合,以检验领域专业知识是否能带来价值。

最简单的调优方法之一是随机搜索:对于一个定义好的搜索空间(通过将采样分布附加到超参数上来指定),对给定数量的超参数组合实例进行采样,并评估其相关的模型性能。产生最佳性能的采样超参数配置被认为是最佳的。

尽管其简单性,随机搜索的朴素版本可能导致计算资源的低效使用,因为它不区分超参数配置,而不良的超参数选择仍然会被训练。

为解决这个问题,提出了连续折半技术,以增强基本的随机搜索策略。基本上,连续折半策略首先使用少量资源评估许多配置,然后逐渐将更多资源分配给有前景的配置。因此,劣质配置可以在早期有效地被淘汰,从而提高搜索效率。Sci-kit Learn 提供了精确实现这一策略的[HalvingRandomSearchCV](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.HalvingRandomSearchCV.html)估算器,我们将在当前案例研究中采用。

7.2 日志分析

当运行[HalvingRandomSearchCV](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.HalvingRandomSearchCV.html)搜索时,搜索日志存储在属性cv_results_中。原始日志以字典格式存储,可以转换为 Pandas 数据框以便于提取见解:

搜索算法生成的日志。(示例取自官方用户指南。)

对于我们当前的目的,我们希望提取表现最好的前 N 个(默认 5 个)配置及其相关的测试分数。以下函数展示了我们如何实现这一目标:

def logs_analysis(results, N):
    """
    Extracting the top performing configs from the logs.

    Inputs:
    -------
    results: results dict produced by the sklearn search object.
    N: the number of top performing configs to consider.

    Outputs:
    --------
    top_config_summary: a string summary of the top-N best performing configs 
    and their associated test scores.
    last_run_best_score: the best test score obtained in the current search run.
    """

    # Convert to Dataframe
    df = pd.DataFrame(search.cv_results_)

    # Rank configs' performance
    top_configs = df.nsmallest(N, 'rank_test_score').reset_index(drop=True)

    # Considered hyparameters
    hyperparameter_columns = [
        'param_colsample_bylevel', 'param_colsample_bytree', 'param_gamma',
        'param_learning_rate', 'param_max_depth', 'param_min_child_weight',
        'param_n_estimators', 'param_reg_alpha', 'param_reg_lambda',
        'param_scale_pos_weight', 'param_subsample'
    ]

    # Convert to string
    config_strings = []
    for i, row in top_configs.iterrows():
        config = ', '.join([f"{col[6:]}: {row[col]}" for col in hyperparameter_columns])
        config_strings.append(f"Configuration {i + 1} ({row['mean_test_score']:.4f} test score): {config}")

    top_config_summary = '\n'.join(config_strings)

    # Best test score
    last_run_best_score = top_configs.loc[0, 'mean_test_score']

    return top_config_summary, last_run_best_score

8. 案例研究

现在我们拥有了所有的要素,是时候将 LLM 指导的工作流程应用于我们的案例研究了。

8.1 确定度量标准

首先,我们提示 LLM 建议一个合适的优化度量标准:

# Suggest metrics
prompt = suggest_metrics(report)
response = conversation.predict(input=prompt)
print(response)

以下是 LLM 生成的响应:

LLM 建议了一个合适的度量标准,并提供了理由。(图片作者提供)

我们可以看到,LLM 根据问题背景和数据集特征推荐了“F1”分数作为度量标准,这与我们的期望一致。此外,度量标准名称被正确地封装在我们指定的后处理标记之间。

8.2 第一次搜索迭代

接下来,我们提示 LLM 建议一个初步的搜索空间:

# Initial search space
prompt = suggest_initial_search_space()
response = conversation.predict(input=prompt)
print(response)

LLM 的输出如下所示:

LLM 建议了一个初步的搜索空间,并提供了理由。(图片作者提供)

我们可以看到,LLM 忠实地遵循了我们的指示,并提供了有关设置每个可调超参数的变异范围的详细说明。这对确保超参数调优过程的透明性至关重要。此外,请注意 LLM 成功输出了正确格式的搜索空间。这显示了在提示设计中提供具体示例的重要性。

然后,我们调用随机搜索与连续折半,并运行第一次迭代:

from sklearn.experimental import enable_halving_search_cv
from sklearn.model_selection import HalvingRandomSearchCV
import xgboost as xgb

clf = xgb.XGBClassifier(seed=42, objective='binary:logistic', 
                        eval_metric='logloss', n_jobs=-1, 
                        use_label_encoder=False)
search = HalvingRandomSearchCV(clf, search_space, scoring='f1', 
                               n_candidates=500, cv=5, 
                               min_resources='exhaust', factor=3, 
                               verbose=1).fit(X_train, y_train)

请注意,HalvingRandomSearchCV 估计器仍处于实验阶段。因此,在使用估计器之前,必须首先显式导入 enable_halving_search_cv

使用 HalvingRandomSearchCV 估计器需要设置几个参数。除了指定估计器(clf)、参数分布(搜索空间)和优化指标(f1)外,我们还需要指定控制时间预算的参数。在我们的案例中,我们将 n_candidates 设置为 500,这意味着我们将在第一次迭代中抽取 500 个候选配置(每个配置有不同的超参数组合)。此外,factor 设置为 3,这意味着每次后续迭代中仅选择三分之一的候选者。同时,这些选中的三分之一的候选者将在后续迭代中使用三倍的资源(即训练样本数)。最后,我们将 min_resource 设置为“exhaust”,这意味着在最后一次迭代中,剩余的候选者将使用所有可用的训练样本。有关设置 HalvingRandomSearchCV 估计器的详细说明,请参阅此帖子:使用 HalvingGridSearch 速度提升 11 倍的超参数调优。

下面显示了运行 HalvingRandomSearchCV 估计器生成的日志快照。在我的电脑上运行搜索的实际时间约为 20 分钟。

连续缩小随机搜索算法生成的日志快照。(图片来源:作者)

8.3 第二次搜索迭代

一旦搜索完成,我们可以从 search.cv_results 中检索搜索历史记录,并将其发送到之前定义的 log_analysis() 函数以提取调优见解。之后,我们调用 suggest_refine_search_space() 函数来提示 LLM 根据之前的搜索结果推荐新的搜索空间:

# Configure prompt
prompt = suggest_refine_search_space(top_n, last_run_best_score, all_time_best_score)

# Refine search space
response = conversation.predict(input=prompt)
print(response)

LLM 的响应如下所示:

LLM 建议了搜索空间的优化并提供了理由。(图片来源:作者)

在这里,我们可以看到 LLM 建议对一些超参数进行缩小空间,对其他超参数进行扩展空间。这些建议的理由也被清晰地阐述,从而提高了可解释性和透明度。

基于优化后的搜索空间,我们可以第二次运行 HalvingRandomSearchCV 估计器:

clf = xgb.XGBClassifier(seed=42, objective='binary:logistic', 
                        eval_metric='logloss', n_jobs=-1, 
                        use_label_encoder=False)
search = HalvingRandomSearchCV(clf, search_space, scoring='f1', 
                               n_candidates=500, cv=5, 
                               min_resources='exhaust', factor=3, 
                               verbose=1).fit(X_train, y_train)

在我的电脑上运行搜索的实际时间约为 29 分钟。

8.4 第三次搜索迭代

让我们再运行一次 LLM 引导的搜索。与之前一样,我们首先从之前的搜索日志中提取有用的见解,然后提示 LLM 进一步优化搜索空间。LLM 生成的响应如下所示:

LLM 建议了搜索空间的优化并提供了理由。(图像由作者提供)

在新定义的搜索空间下,我们第三次运行了 HalvingRandomSearchCV 估计器:

clf = xgb.XGBClassifier(seed=42, objective='binary:logistic', 
                        eval_metric='logloss', n_jobs=-1, 
                        use_label_encoder=False)
search = HalvingRandomSearchCV(clf, search_space, scoring='f1', 
                               n_candidates=100, cv=5, 
                               min_resources='exhaust', factor=3, 
                               verbose=1).fit(X_train, y_train)

在我的 PC 上运行搜索的时间大约为 11 分钟。

8.5 测试

经过三轮随机搜索后,让我们测试获得的 XGBoost 模型的性能。为此,我们可以定义一个辅助函数来计算各种性能指标:

from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.metrics import (
    precision_score,
    recall_score,
    accuracy_score,
    roc_auc_score,
    f1_score,
    matthews_corrcoef
)

def metrics_display(y_test, y_pred, y_pred_proba):

    # Obtain confusion matrix
    cm = confusion_matrix(y_test, y_pred)

    # Output classification metrics
    tn, fp, fn, tp = cm.ravel()

    print(f'ROC_AUC score: {roc_auc_score(y_test, y_pred_proba):.3f}')
    print(f'f1 score: {f1_score(y_test, y_pred):.3f}')
    print(f'Accuracy: {accuracy_score(y_test, y_pred)*100:.2f}%')
    print(f'Precision: {precision_score(y_test, y_pred)*100:.2f}%')
    print(f'Detection rate: {recall_score(y_test, y_pred)*100:.2f}%')
    print(f'False alarm rate: {fp / (tn+fp)*100}%')
    print(f'MCC: {matthews_corrcoef(y_test, y_pred):.2f}')

    # Display confusion matrix
    disp = ConfusionMatrixDisplay(confusion_matrix=cm)
    disp.plot()

# Calculate performance metrics
y_pred = search.predict(X_test)
y_pred_proba = search.predict_proba(X_test)
metrics_display(y_test, y_pred, y_pred_proba[:, 1])

在上述代码片段中,我们将训练好的 XGBoost 模型应用于测试数据集,并评估其性能。获得的混淆矩阵如下所示:

训练后的 XGBoost 模型在测试数据集上的混淆矩阵。(图像由作者提供)

根据混淆矩阵,我们可以计算各种性能指标:

  • 准确率:94.22%

  • 精确度:89.5%

  • 检测率(召回率):80.52%

  • 虚警率(1-特异性):2.35%

  • ROC-AUC 分数:0.983

  • F1 分数:0.848

  • 马修斯相关系数:0.81

9. 与开箱即用 AutoML 工具的比较

正如我们在开头提到的,我们希望将开发的 LLM 引导搜索的调优结果与现成的、基于算法的 AutoML 工具进行比较,以评估是否借助 ML 领域专业知识确实能带来价值。

我们将使用的 AutoML 工具叫做 FLAML,由微软研究院开发,代表 一种用于自动机器学习和调整的快速库。此工具是最先进的,支持快速且经济的自动调优,能够处理具有异质评估成本和复杂约束/指导/提前停止的大型搜索空间。有关安装库的详细信息,请参阅 官方页面。

使用此工具极其简单:在下面的代码片段中,我们首先实例化一个AutoML对象,然后调用其fit()方法启动超参数调整过程。我们将调整限制在 XGBoost 模型上(FLAML 也支持其他模型类型),并设置 3600 秒的时间预算,这大致等于我们在 3 轮 LLM 引导搜索中花费的总时间(随机搜索时间+ LLM 响应时间)。

from flaml import AutoML

automl = AutoML()
automl.fit(X_train, y_train, task="classification", time_budget=3600, 
          estimator_list=['xgboost'], log_file_name='automl.log', 
          log_type='best')

以下是 LLM 引导搜索与开箱即用的 FLAML 结果的比较:

两种调优方法的结果比较。(图像由作者提供)

我们可以看到,LLM-guided 搜索在所有考虑的指标上都比开箱即用的 FLAML 表现更好。由于我们关注的是网络安全应用,两个最重要的指标是检测率误报率。在这里,我们可以看到 LLM-guided 搜索成功显著提高了检测率,同时略微降低了误报率。因此,通过 LLM-guided 搜索训练的 XGBoost 模型将是一个优于 FLAML 搜索模型的异常检测器。

总的来说,我们可以得出结论,对于我们当前的案例研究,利用嵌入在 LLM 中的机器学习领域专业知识确实可以在超参数调优中带来价值,即使它与简单的搜索算法配对。

10. 结论

在本博客中,我们探讨了一种新的 AutoML 范式:LLM-guided 超参数调优。这里的关键思想是将大型语言模型视为机器学习专家,并利用其机器学习领域知识来提出合适的优化指标、建议初始搜索空间以及推荐搜索空间的细化。

随后,我们将这种方法应用于识别网络安全数据集的最佳 XGBoost 模型,我们的结果表明,信息驱动的超参数搜索(即 LLM-guided 搜索)比纯算法驱动的 AutoML 工具 FLAML 产生了更好的异常检测模型,达到了更高的检测率和更低的误报率。

如果你觉得我的内容有用,你可以在这里请我喝杯咖啡 🤗和往常一样,你可以在这里找到包含完整代码的伴随笔记本💻如果你想深入了解,可以查看以下两篇最近的研究论文,它们研究了相同的主题:

  • AutoML-GPT:与 GPT 的自动机器学习

  • 大型语言模型时代的 AutoML:当前挑战、未来机会与风险

此外,如果你对大型语言模型的其他有趣应用感兴趣,可以看看我之前的博客:

  • 构建一个 AI 驱动的语言学习应用

  • 开发自主双聊天机器人系统以消化研究论文

  • 通过现实生活模拟训练数据科学中的问题解决技能。

期待与你分享更多令人兴奋的 LLM 项目。敬请关注!

何时在 CPU 上运行代码而不是 GPU:典型案例

原文:towardsdatascience.com/when-cpu-is-faster-than-gpu-typical-cases-bc7c64ee3c66?source=collection_archive---------2-----------------------#2023-05-21

如何选择硬件以优化特定使用案例的计算

Robert KwiatkowskiTowards Data Science Robert Kwiatkowski

·

关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 5 月 21 日

--

图片来源:Francesco Vantini 由 Unsplash 提供

随着人工智能(AI)、机器学习(ML)、物联网(IoT)、虚拟现实(VR)以及复杂的数值模拟技术的快速发展,对计算能力的需求达到了前所未有的高度。在大多数现实情况下,不仅计算能力很重要,还有硬件的大小和功耗等因素。

当你根据业务和技术要求设计系统时,可以从各种计算组件中进行选择,例如:

  • 集成电路(ICs)

  • 微控制器(MCs)

  • 中央处理单元(CPU)

  • 图形处理单元(GPU)

  • 专用芯片,如张量处理单元(TPU)。

尽管这是一个不断发展的计算领域,但有两个关键组件彻底改变了我们处理数据和执行复杂任务的方式。这些组件是中央处理单元(CPU)和图形处理单元(GPU)。这两大计算强者在推动各个领域的进步方面起着至关重要的作用:

  • 人工智能(例如 ChatGPT)

  • 科学模拟(例如有限元方法、计算流体力学)

  • 游戏

  • 视觉效果

因此,理解 CPU 和 GPU 的独特能力和性能特征对于充分发挥它们的潜力以及优化整个系统以满足业务需求至关重要。

当谈到降低计算运行成本时,你应该考虑以下方面:

  • 硬件成本

  • 功耗

  • 性能效率

  • 维护和升级

关于购买成本,CPU 比中高端 GPU 更实惠。然而,由于 GPU 具有更高的核心数量和内存带宽,它们的能耗比 CPU 高。因此,如果你设计的是一个由 5V 电池供电的简单物联网设备,你可能会关注功耗和低硬件成本——那么 CPU(甚至 IC 或 MC)将是最佳选择。

另外,还有一个选项是使用来自例如 Google、Amazon 或 Azure 等公共云资源,只为使用时间付费。如果你设计的是一个 Web 服务,或者你的硬件需要高计算能力但受限于尺寸或需要远程访问和控制,那么这将是最佳选择。然而,在像智能手机或智能手表这样的量产设备中,硬件成本和功耗仍然发挥着决定性作用。

现在,当考虑计算成本时,总体费用通常是完成任务端到端所需时间的函数。一个好的例子是神经网络(NN)的训练。这本身就是一个计算密集型任务。从端到端的角度来看,还包括数据准备和实验跟踪(用于超参数优化)等额外步骤。这带来了额外的开销。然而,在开发阶段,训练仍然是瓶颈,因此大多数流行的 ML 框架(如 pytorch、Keras)支持 GPU 计算来解决这个问题。这是利用 GPU 能力的经典案例——NN 的训练非常适合大规模并行化。这是由于其底层实现。然而,推理本身(在模型训练之后)通常可以在 CPU 或甚至微控制器上完成。训练之后的瓶颈可能在数据准备方面(内存方面)或 I/O 操作方面。对于这两者,CPU 通常更为适合。这就是为什么有些专用微处理器用于这种任务(如 Intel Atom® Processors x7000E)。所以最终,我们来到了两种不同的最佳解决方案,取决于环境:开发(GPU)和生产(CPU)。

正如你所见,虽然 GPU 在重度并行处理上表现出色,但在一些情况下 CPU 从端到端的角度来看超越它们。这取决于算法的性质和业务需求。如果你是软件开发人员或系统设计师/架构师,了解这些情况对于提供最佳解决方案至关重要。

在本文中,我们将探讨一些此类场景。

单线程递归算法

有些算法本身设计上不适合并行化——递归算法。在递归中,当前值依赖于先前的值——一个简单但明确的例子是计算斐波那契数的算法。一个示例实现如下。在这种情况下,无法打破计算链并并行运行它们。

另一个此类算法的例子是递归计算阶乘(见下文)。

内存密集型任务

有些任务的瓶颈在于内存访问时间,而不是计算本身。CPU 通常具有比 GPU 更大的缓存大小(快速内存访问元素)以及更快的内存子系统,使其在操作频繁访问的数据时表现出色。一个简单的例子是对大型数组的逐元素加法。

然而,在许多情况下,流行的框架(如 Pytorch)会通过将对象移动到 GPU 的内存中并在后台并行化操作,使这些计算在 GPU 上更快。

我们可以创建一个过程,其中初始化 RAM 中的数组并将它们移动到 GPU 进行计算。传输数据的额外开销导致端到端处理时间比直接在 CPU 上运行要长。

这时我们通常使用所谓的 CUDA 支持数组——在这种情况下,使用 Pytorch。你必须确保你的 GPU 能够处理这种数据大小。给你一个概述——典型的流行 GPU 的内存大小为 2–6GB VRAM,而高端的则有高达 24GB VRAM(GeForce RTX 4090)。

其他不可并行化算法

有一类算法虽然不是递归的,但仍然无法并行化。一些示例如下:

  • 梯度下降法 — 用于优化任务和机器学习

  • Hash-chaining — 用于加密

梯度下降法在其基础形式中无法并行化,因为它是一个顺序算法。每次迭代(称为一步)依赖于前一次的结果。不过,已经有一些关于如何以并行方式实现此算法的研究。欲了解更多,请查看:

  • HOGWILD!: A Lock-Free Approach to Parallelizing Stochastic Gradient Descent

  • Parallel Stochastic Gradient Descent with Sound Combiners

你可以在这里找到 Hash-chaining 算法的示例:www.geeksforgeeks.org/c-program-hashing-chaining/

小任务

另一个 CPU 更好的选择是数据量非常小的情况。在这种情况下,RAM 和 GPU 内存(VRAM)之间传输数据的开销可能会超过 GPU 并行处理的好处。这是因为 CPU 缓存的访问速度非常快。这在与内存密集型任务相关的部分中提到过。

此外,一些任务实在太小,尽管计算可以并行运行,但对最终用户的好处不明显。在这种情况下,在 GPU 上运行仅会产生额外的硬件相关成本。

这就是为什么在物联网中,GPU 不常被使用。典型的物联网任务有:

  • 捕获一些传感器数据并发送出去

  • 在检测到信号后激活其他设备(灯光、警报、马达等)

然而,在这个领域,GPU 仍然用于所谓的边缘计算任务。这些是你必须直接在数据源处获取和处理数据的情况,而不是将其发送到互联网上进行重处理。一个好的例子是宝马开发的iFACTORY。

小规模并行化任务

有许多使用场景需要并行运行代码,但由于 CPU 的速度,足以使用多核 CPU 并行化过程。GPU 在需要大规模并行化(数百或数千个并行操作)的情况下表现出色。在你发现例如 4 倍或 6 倍加速已经足够的情况下,你可以通过在 CPU 上运行代码、每个进程在不同核心上来降低成本。如今,CPU 制造商提供的 CPU 核心数在 2 到 18 个之间(例如,Intel Core i9–9980XE Extreme Edition Processor)。

总结

总体来说,选择 CPU 和 GPU 的经验法则是回答以下主要问题:

  1. CPU 是否能够在要求的时间内完成整个任务?

  2. 我的代码可以并行处理吗?

  3. 我能将所有数据放入 GPU 吗?如果不能,这是否会引入较大的开销?

要回答这些问题,关键在于深入理解你的算法是如何工作的,以及当前的业务需求是什么,这些需求未来可能会如何变化。

人类在需要回答数据相关的棘手问题时

原文:towardsdatascience.com/when-humans-need-to-answer-tough-questions-about-data-66dfc227c546?source=collection_archive---------9-----------------------#2023-12-07

TDS EditorsTowards Data Science TDS Editors

·

关注 发表在 Towards Data Science · 作为 通讯 发送 · 3 分钟阅读 · 2023 年 12 月 7 日

--

数据科学和机器学习专业人员知道如何从数据中寻找答案:这可能是他们工作的核心支柱。当我们探讨一些涉及数据的棘手问题时,例如内置的偏见以及它可能被利用来达到可疑目的的方式,情况就变得更加复杂。

随着我们进入年末最后阶段,我们邀请读者探讨一些引发关键讨论的大问题,这些问题近年来已经引起广泛关注,并且几乎可以肯定会继续塑造 2024 年及以后的领域。

我们本周的亮点涵盖了广泛的主题,从数据支持知识的本质到其在医疗等特定领域的应用;我们希望这些内容能够激发更多的反思,并吸引新的参与者参与到这些重要的讨论中。

  • 偏差、毒性与破解大型语言模型(LLMs) LLMs 的快速崛起和演变使得从业者难以暂停并评估其固有风险。Rachel Draelos, MD, PhD 对近期研究的详细概述提供了对这些模型在大规模上延续甚至加剧偏差和毒性的能力的及时观察。

  • 哲学与数据科学——深入思考数据 认识论中的主要概念——如演绎推理和归纳推理、怀疑主义和实用主义——如何融入数据科学家的工作中?Jarom Hulet 的最新文章探讨了这两个领域之间(有时意外的)重叠。

  • 数据科学中的认知偏差:类别规模偏差 在关于认知偏差的新系列中,Maham Haroon 详细阐述了我们的大脑在分析和从数据中提取洞见时,如何引导我们走向错误的多种方式。第一篇文章聚焦于类别规模偏差,并解释了它如何渗透到数据科学家在日常工作中所做的假设中。

图片由 Bozhin Karaivanov 提供,来源于 Unsplash

  • AI 在医疗保健中应扮演什么角色? 我们迄今为止所讨论的偏差可能对模型、业务和底线造成严重破坏。然而,Stephanie Kirmer 强调,这些偏差在医疗领域尤为严重,因为该领域常涉及生死攸关的情况,“失败的风险极为灾难性。”

  • 变压器的挽歌?

    在一个快速变化的领域中,我们容易将一个六年前的概念视为核心和永恒。自 2017 年以来,Transformer 模型在主流 AI 工具的采纳中发挥了重要作用;但正如萨尔瓦托雷·拉伊利所指出的,它们也可能有保质期,也许现在是时候思考下一步是什么。

大问题很重要,但中等规模和紧凑型问题也很有用!不要错过我们最近关于职业变动、数据工程和其他及时话题的一些精彩内容:

  • 我们如何让模型忘记信息我们不再希望它们保留?叶夫根尼亚·苏霍多尔斯卡娅提出了一种数据驱动的方法,用于生成语言模型的“遗忘”。

  • 思考在新的一年里换个角色吗?Thu Vu最近分享了一个详细的路线图,适合那些有兴趣转向数据分析的人。

  • 想要了解图神经网络领域的前沿研究,可以查看迈克尔·布朗斯坦的最新文章(与合著者本·芬克尔施泰因、伊斯梅尔·杰兰和邢越·黄一起)。

  • 如果你是一名害怕回填过程的数据工程师,高小旭的指南概述了高效的实现方法,将帮助你简化这个工作流程。

  • 对 scikit-learn 不熟悉?Yoann Mocquin最近推出了一系列适合初学者的 sklearn 教程,带我们了解这个流行的机器学习库的不同模块和功能。

  • LLMs 的 SQL 会是什么样的?玛利亚·曼苏罗娃详细介绍了语言模型查询语言(LMQL)的概述,允许用户在一个提示中结合多个调用,控制输出并降低成本。

感谢你支持我们作者的工作!如果你喜欢 TDS 上的文章,可以考虑成为 Medium 的朋友会员:这是一个全新的会员级别,为你喜爱的作者提供更多奖励。

直到下一个 Variable,

TDS 编辑部

何时使用条形图是不正确的?

原文:towardsdatascience.com/when-is-it-wrong-to-use-bar-charts-70f55a3fb1a2?source=collection_archive---------9-----------------------#2023-05-31

…以及可能的解决办法

Zvonimir BobanTowards Data Science Zvonimir Boban

·

关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 5 月 31 日

--

由 Canva 文本转图片工具生成的图像

别误解我的意思,条形图可以是数据可视化的一个很好的工具,尤其是在展示计数、总数或比例时。然而,不正确地使用条形图可能会导致无意的(甚至更糟的是,故意的)数据误解。我今天要讨论的具体问题是使用条形图来展示汇总统计数据,如均值或中位数。

这里最大的问题是细节的丧失,因为条形图可能过于简化,遗漏了重要信息,如方差、分布、异常值和趋势。在这篇文章中,我将通过一系列示例来说明这个问题,并提出潜在的解决方案。为了不干扰文章的流程,图表的代码将在末尾指定,供感兴趣的读者参考 😃

葡萄酒质量数据集

图片由Kym Ellis提供,来源于Unsplash

对于这篇文章,我将使用葡萄酒质量数据集¹,该数据集可通过UCI ML 数据库获取。虽然数据集中包含许多葡萄酒属性,但我们将重点关注总二氧化硫测量值。

二氧化硫通常作为二氧化硫添加到葡萄酒中,由于其防腐特性,它在酿酒中发挥着关键作用。作为抗氧化剂,它帮助防止葡萄酒氧化,保护其免受变色和不希望的风味变化。它的抗菌特性还保护葡萄酒免受细菌和酵母的腐败,保持了预期的口味和质量。

问题

让我们通过绘制一个简单的条形图来比较红葡萄酒和白葡萄酒的总二氧化硫水平,从而说明这个问题。

图片由作者提供

好吧,也许用上面的例子来批评条形图并不公平,因为基本的图表看起来如此丑陋,没必要进一步论证就让人反感。让我们先通过调整一些美学属性来让它变得更漂亮。

图片由作者提供

好得多。现在,回到手头的问题。这张图表告诉我们什么?显然,白葡萄酒的二氧化硫水平似乎要高得多。这是由于红葡萄酒和白葡萄酒的酿造过程不同而可以预期的。

红葡萄酒在发酵过程中与葡萄皮一起发酵,这提供了天然的抗氧化剂,有助于保护葡萄酒免受氧化。相比之下,白葡萄酒通常通过压榨葡萄并在发酵前去除葡萄皮来制作。这使得白葡萄酒更容易受到氧化,需要额外的保护,例如二氧化硫。

尽管平均效果是明显的,但条形图没有提供关于每组值的分布信息,也没有显示每组的观察数量。

通过在条形上方添加观察数量和添加误差条来显示每组的标准差,可以部分解决这个问题。

图片由作者提供

如果基础值的分布是对称的,这种方法可能足够,但实际情况不一定如此,这使得标准差作为离散统计量并不理想。在条形图中,无法通过添加更多内容来解决这个问题,而不使其更接近完全不同类型的图表。这表明条形图并不适合呈现这种数据类型。

那么,可能的替代方案有哪些?我将在剩下的帖子中讨论几个。

修正方案

在这里,我提供了四种可能的替代方案,我认为它们是更好且更透明的解决方案。

1. 抖动点

第一个可能性是将实际的单个观察值添加到图表中。

作者提供的图片

如果观察数量相对较少,这可能是一个很好的替代方案。然而,在这种特定情况下,由于数据集中有大量的葡萄酒,它本身感觉相当繁琐。

2. 带有指定均值的箱形图

第二种替代方案是使用箱形图,并添加指定均值和中位数的功能(默认情况下,中位数由中央箱中的平坦线显示)。尽管箱形图通过指定四分位数给我们提供了底层分布的概念,但我喜欢均值提供的额外信息。这是因为均值和中位数之间的大而明显的差异能立刻告诉我们分布是否偏斜以及偏斜的方向。

作者提供的图片

3. 带有中位数的小提琴图

小提琴图非常好,因为它们让我们了解底层分布的形状,使得容易检测到异常现象,如双峰性或数据偏斜。有人可能会争辩说箱形图也隐含地做到这一点。虽然我在一定程度上同意这一点,但我们也必须考虑到,读懂箱形图需要特定的培训,而小提琴图则不需要。

我还喜欢添加中位数的信息,因为小提琴图留有很多未使用的空间,何乐而不为呢 😃

作者提供的图片

4. 带有抖动点的小提琴图

好吧,这实际上不是一个独立的选项,而是选项 1 和 3 的组合。对于我们的具体情况,这将是我的选择,但这并不意味着它适用于所有可能的场景,因为这取决于问题的具体情况,如比较组的数量、点的总数、组的离散程度等。

注意,我没有尝试将箱形图与特定点结合起来。这是有意的,因为我认为这种结合会违背箱形图的目的。即,箱形图仅在数据点高于中央箱的上边界 1.5 倍四分位范围时显示特定点。这可以作为一种简单的异常值检测方法,而添加过多其他点会使其不清晰。

图片来源:作者

结论

本文讨论了使用条形图呈现聚合组统计数据的具体问题,利用葡萄酒质量数据集提供实际示例。在阐述问题后,提出了四种可能的替代方案,并讨论了它们的优缺点。

最后,请记住,任何数据可视化的主要目标是准确有效地传达信息。始终选择最适合数据和你想传达的信息的可视化类型。

我希望你会觉得这篇文章有用。如果你有任何评论,欢迎在帖子下方留言。当然,如果你喜欢你所阅读的内容,请点赞并关注我,以获取更多类似内容。

脚注

¹P. Cortez, A. Cerdeira, F. Almeida, T. Matos 和 J. Reis。

通过物理化学属性的数据挖掘建模葡萄酒偏好。载于《决策支持系统》,Elsevier,47(4):547–553,2009. (CC BY 4.0)

生成图表的代码

library(tidyverse)

wine <- read_delim("winequality-red.csv", 
                    delim = ";", escape_double = FALSE, trim_ws = TRUE) %>%
  mutate(Type = "Red") %>%
  bind_rows(read_delim("winequality-white.csv", 
                        delim = ";", escape_double = FALSE, trim_ws = TRUE) %>%
              mutate(Type = "White")) %>%
  mutate(Type = factor(Type)) %>%
  pivot_longer(`fixed acidity`:`quality`, 
                names_to = "Parameter", values_to = "Value") %>%
  filter(Parameter == "total sulfur dioxide") %>%
  select(-Parameter)

wine_summary <- wine %>%
  group_by(Type) %>%
  summarise(Median = median(Value), Mean = mean(Value), 
            SD = sd(Value), N = n())
#basic bar chart
wine_summary %>%
  ggplot(aes(Type, Mean)) +
  geom_col() +
  labs(x = "Wine type", y = "Total sulfur levels")

#aesthetically pleasing bar chart
wine_summary %>%
  ggplot(aes(Type, Mean)) +
  geom_col(aes(fill = Type), width = 0.8) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  scale_fill_manual(values = c("#b11226", "#F4E076")) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  theme_bw() +
  theme(legend.position = "none")

#bar chart with errorbars and specified number of observations per group
wine_summary %>%
  ggplot(aes(Type, Mean)) +
  geom_col(aes(fill = Type), width = 0.8) +
  geom_errorbar(aes(ymin = Mean - SD, ymax = Mean + SD), width = 0.15) +
  geom_label(aes(y = 200, label = N), fill = "gray97") +
  scale_fill_manual(values = c("#b11226", "#F4E076")) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  theme_bw() +
  theme(legend.position = "none")

#jittered points chart
wine_summary %>%
  ggplot(aes(Type, Mean)) +
  geom_jitter(data = wine, aes(x = Type, y = Value, col = Type), alpha = 0.4) +
  geom_errorbar(aes(ymin = Mean - SD, ymax = Mean + SD), width = 0.15) +
  geom_point(shape = 4, size = 2, stroke = 2) +
  geom_label(aes(y = 450, label = N), fill = "gray97") +
  scale_color_manual(values = c("#b11226", "#F4E076")) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  theme_bw() +
  theme(legend.position = "none")

#boxplot with added information about the mean
wine_summary %>%
  ggplot(aes(Type, Mean)) +
  geom_boxplot(data = wine, aes(x = Type, y = Value, col = Type)) +
  geom_point(shape = 4, size = 2, stroke = 2) +
  geom_label(aes(y = 450, label = N), fill = "gray97") +
  scale_color_manual(values = c("#b11226", "#F4E076")) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  theme_bw() +
  theme(legend.position = "none")

#violin plot with information about the median
wine_summary %>%
  ggplot(aes(Type, Median)) +
  geom_violin(data = wine, aes(x = Type, y = Value, col = Type)) +
  geom_point(shape = 4, size = 2, stroke = 2) +
  geom_label(aes(y = 450, label = N), fill = "gray97") +
  scale_color_manual(values = c("#b11226", "#F4E076")) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  theme_bw() +
  theme(legend.position = "none")

#violin plot with added jittered points
wine_summary %>%
  ggplot(aes(Type, Median)) +
  geom_violin(data = wine, aes(x = Type, y = Value), fill = "gray92") +
  geom_jitter(data = wine, aes(x = Type, y = Value, col = Type), alpha = 0.1) +
  geom_point(shape = 4, size = 2, stroke = 2) +
  geom_label(aes(y = 450, label = N), fill = "gray97") +
  scale_color_manual(values = c("#b11226", "#F4E076")) +
  labs(x = "Wine type", y = "Total sulfur levels") +
  theme_bw() +
  theme(legend.position = "none")

毫秒至关重要——我在性能改进中的旅程

原文:towardsdatascience.com/when-milliseconds-matter-my-journey-to-performance-improvement-5a3cd69754c4?source=collection_archive---------19-----------------------#2023-02-15

从延迟改进项目中获得的经验教训

Naomi KrigerTowards Data Science Naomi Kriger

·

关注 发表于 Towards Data Science · 7 分钟阅读 · 2023 年 2 月 15 日

--

图片由 Giallo 提供,来自 Pexels

在严格的服务水平协议下工作,其中毫秒至关重要,同时维护一个具有多个依赖关系的复杂系统——当出现延迟相关问题时,这可能会带来许多挑战和非平凡的调查。

在这篇文章中,我将带你了解我如何通过一个描述的类型问题启动性能改进,并在过程中获得的经验教训。

步骤 1 — 预期行为和问题描述

公司的产品处理事务,对于每个接收到的事务——我们要么批准,要么拒绝。

对于一些事务,我们会经过数据增强步骤,以获取更多的信息用于实时决策和未来的事务。

然而,对于每天成千上万的事务——增强过程根本没有发生,而且原因不明确。

步骤 2 — 在沙漠中寻找狮子

让我们看一下相关系统的简化视图:

作者草图

由于这是一个增强问题,第一个问题是——问题是发生在增强服务内部,还是在我们到达之前?显然,对于这些有问题的交易,我们甚至没有向增强服务发送获取请求。一个嫌疑犯排除。

步骤 3 — 了解我们的拓扑

让我们从故事中休息一下。我想向你介绍决策系统的拓扑。这个系统基于Apache Storm,旨在实时处理无限的数据流。

作者草图

简而言之,Spout 从数据源(例如 Kafka / RabbitMQ)接收数据,并将流输出到拓扑中。每个 Bolt 是拓扑中的一个组件,它接收并发出一个或多个流。一个 Bolt 进行简单的逻辑处理,如过滤、聚合、从数据库读取和写入等。

一些 Bolts 并行运行,而另一些则相互依赖。

一些 Bolts 会固定一个相对超时时间值,之后它们会继续(处理流并发出)无论是否接收到所有等待的输入。

超时的使用防止了 Bolts 和组件过长时间延迟拓扑,从而使我们能够满足预期的 SLA。

步骤 4 — 为什么没有增强?

回到我们的故事。为什么决策系统没有调用增强服务?我添加了一些指标,发现问题交易的一个重要条件达到了——增强过程剩余的时间不够,所以没有执行获取调用以留出足够的时间给未来的 Bolts。

这很让人惊讶。增强是流程中的核心组件,发生在拓扑的相对前期。为什么我们没有足够的时间来执行这个调用?

步骤 5 — 依赖关系和延迟

为了理解为什么我们超出了(相对)时间,我深入研究了一个内部工具提供的视图,并向后查看了我的增强 Bolt 所依赖的 Bolts。

我发现一个较早的 Bolt 一直需要大约 100 毫秒。考虑到我们的 SLA 和一个 Bolt 应该花费的平均时间,这被认为是非常多的。

这个父 Bolt 中发生了什么事情,花了这么长时间?

当深入到父 Bolt 的代码时,我看到了一个 elasticsearch 查询,并怀疑这是否可能是我们瓶颈的原因。

结果是——当查看相关仪表板时,我发现我的 Bolt 高延迟的时间与这个集群高 CPU 使用率之间存在相关性。

在与维护这个集群的团队同步后,我了解到他们已经熟悉其长期的性能问题和逐渐恶化的情况。

第 6 步——这个依赖关系必要吗?

为什么增值-Bolt 依赖于这个 elasticsearch 查询?它是否值得我们在未增值事务中支付的代价?

在增值上下文中——我们在等待这个查询结果来处理一个特定功能,但进一步调查显示该功能存在一个漏洞,天知道有多久了,因此我们没有利用这个功能的期望输出。

作者草图

如果我们删除这个功能,我们可以断开增值与调用问题 elasticsearch 集群的 Bolt 之间的依赖。如果我们修复有漏洞的功能——我们将重新获得有人意图使用的数据,但会保留高延迟依赖,并需要寻找替代解决方案。

在考虑了几种潜在的解决路径——每种路径的努力和成本效益,并且得到了修复有漏洞的功能所有者的同意删除这段代码——我删除了有漏洞的功能。

第 7 步——何时对我们的拓扑进行微妙的更改

决策系统是基于依赖的,此时,我希望将增值组件依赖于一个发生在调用问题集群的组件之前的组件。这样的更改可以节省我们等待高延迟查询的时间,而且我们新的依赖组件出现得越早——增值及其后续组件将留有更多的空闲时间。

在调查代码并与高层选择新的父组件后,我进行了这项微妙的更改并监控了结果。

作者草图

第 8 步——结果如何?

起初,我的更改上线后没有看到戏剧性的改善。真令人沮丧!几个月的调查和期待,变化幅度微小。但我们不应绝望!

我检查了未增值的事务,发现它们也符合超时条件。我调查了依赖关系视图,发现通过不同路径——它们仍然依赖于问题组件!

原因是增值 Bolt 等待几个字段,而这些字段的父 Bolt 再次指向我们的高延迟 Bolt,它查询了高延迟集群。

但这些字段在代码中没有被使用。

我删除了那些字段,并很高兴看到结果:

每日未丰富交易的数量从 26K 减少到 200。

此外,在开始时——对于一些商户,这类有问题的交易的百分比高达 20%,而在我的更改之后——所有商户的交易中问题的百分比不超过 1%。

大获成功!

经验教训:

我在这个过程中学到了很多。我使用各种技术进行了调查,深入复杂的代码,这些代码基于复杂的架构,分析了延迟,并考虑了不同的权衡以解决问题。但我今天想与大家分享一些提示:

  • 删除代码是有益的 一位优秀的软件工程师的标准不是她写了多少新代码。删除代码是一项重要任务,删除那些过时的、使系统过载的代码对于提升系统性能至关重要。调查的过程可能需要耐心和毅力,但可能会带来宝贵的结果。

  • 代码删除应彻底进行

    组件 A 依赖于组件 B 的原因之一是由于组件中未使用的输入字段。随着我们删除代码,花时间问自己是否删除了与之相关的所有内容并没有留下遗留问题是一个好习惯。

  • 投资于分析工具

    使我能够检测到高延迟组件的一个因素是内部分析工具。我们需要了解我们的组件、它们的依赖关系以及它们的延迟,并且我们需要这样的工具直观易用。

  • 考虑监控流中特定组件的延迟

    当毫秒很重要时,监控每个组件的延迟可能是识别瓶颈源的关键。考虑在创建组件时添加度量指标,以便在需要时能够访问这些数据。

  • 掌握你使用的技术,学习如何用它们进行调查 我们团队使用的每种技术都有其强项和技巧。当我们遇到新技术或新工具时,我们可能会学习到用于日常工作的内容,然后就此离开。但投入时间学习如何利用这些工具获得额外的见解,当我们面临大量问题需要调查时,这可能会非常有用。

  • 咨询和头脑风暴 复杂的调查可能很困难,一位同事可能熟悉我们未曾了解的调查工具,或者对我们的问题提出不同的看法。请记住,项目的成功就是团队和公司的成功,如果你感到困惑或需要另一个观点,请让同事参与进来。

当点预测完全无用时

原文:towardsdatascience.com/when-point-forecasts-are-completely-useless-79cd27d0b1e5

虽然点预测非常流行,但要注意一些不幸的陷阱

Sarem SeitzTowards Data Science Sarem Seitz

·发表于 Towards Data Science ·11 min read·2023 年 1 月 1 日

--

图片由 Kai Pilger 提供,来源于 Unsplash

介绍

在上一篇文章中,我们讨论了概率预测相对于点预测的一个优势——即处理超越时间问题。在这篇文章中,我们将探讨点预测的另一个局限性:高阶统计属性。

对于具有数学或统计学背景的人来说,这些思想将非常熟悉。因此,没有正式训练的读者可能会从这篇文章中获得最多的益处。

在本文结束时,你将对高阶统计属性如何影响预测性能有更好的了解。特别是,我们将看到点预测如何在没有进一步调整的情况下完全失败。

点预测失败的两个示例

为了让你对点预测的问题有更深的认识,我们将继续介绍两个非常简单的例子。这两个时间序列都可以用相当简单的自回归数据生成过程来描述。

我们将生成足够的数据,以使自回归梯度提升模型具有合理性。这样,我们避免了使用过于僵化的模型和由于数据不足导致的过拟合。

示例 1 — 自回归方差(GARCH)

带有自回归方差的模拟时间序列。(图像由作者提供)

这是一个标准的ARCH时间序列,这在计量经济学中经常遇到。如果你想了解如何处理这样的数据,我还写了一些文章:

  • 用于 ARCH 类波动率预测的随机森林和提升方法

  • 使用 Python 和 Tensorflow 进行多元 GARCH

  • 让 GARCH 通过正规化流动变得更灵活

无论如何,让我们暂时使用时间序列的机器学习模板方法。即,我们使用Nixtla 的 mlforecast 包为我们构建一个自回归的提升模型。(这并不是想要批评 Nixtla 包。事实上,如果你知道自己在做什么,它确实非常有用和方便。)

结果如下所示:

梯度提升对自回归方差的时间序列预测。(图片由作者提供)

不幸的是,结果完全无助。尽管我们提供了实际的滞后数,预测结果实际上毫无用处。

示例 2 — 自回归的非高斯数据

下一个示例遵循了一个更复杂的数据生成过程。然而,这并不排除某些实际时间序列也遵循类似的逻辑:

自回归 Beta 分布的模拟时间序列。(图片由作者提供)

让我们检查一下梯度提升模型在这种情况下的表现:

梯度提升对自回归 Beta 分布时间序列的预测。(图片由作者提供)

再次强调,预测结果完全无用。

我们的点预测出了什么问题?

如你所知,sklearn.ensemble.GradientBoostingRegressor默认情况下最小化均方误差MSE)。以下是 MSE 最小化的一个众所周知的属性:

一个分布的均值最小化其均方误差。

从数学上讲:

(图片由作者提供)

我们假设一个任意大的可接受函数集合。此外,我们隐含需要假设条件均值实际上存在。这对于大多数表现良好的预测问题是合理的。

因此,上述两个模型都旨在预测我们观察值的条件分布的均值。问题在于,条件均值实际上是按构造设定为常数的。

对于第一个示例来说,这是显而易见的——每个观察值的条件均值为零。对于第二个示例,我们需要做一些数学计算,正式证明留给感兴趣的读者。

现在,虽然条件均值在时间上保持不变,但我们的时间序列仍远非纯粹的噪声。通过均方误差(MSE)最小化来预测均值,在描述未来时显得相当不足。

我们可以进一步声明:

即使是完美的(点)预测模型,如果预测量无信息,也可能是无用的。

我们可以通过绘制条件密度与条件均值的图来可视化我们的例子:

条件密度与(恒定的)条件均值。(图片作者提供)

一方面,条件分布是变化的,可以通过历史数据来预测。然而,条件均值是恒定的,并不能告诉我们未来分布的任何信息。

我们能做些什么?

乍一看,上述问题对原始点预测的能力描绘了一个相当严峻的图景。像往常一样,情况当然要复杂得多。

因此,让我们讨论一下如果你的点预测效果不佳,该如何处理的大致思路。

评估一下你是否真的有问题——点预测仍然可能有效

正如我们刚才看到的,点预测可能会惨遭失败。然而,既然它们被广泛使用,这表明它们可能会对你的问题造成麻烦。许多预测问题可以通过标准方法合理解决。

有时,你只需要对你的模型投入更多的努力。仅仅使用另一种损失函数或对特征进行另一种非线性变换可能就足够了。然而,一旦你观察到点预测确实无法胜任,可能就该转向概率性方法了。

两种情况可能是很好的指示器:

1) 你的点预测显示出很少的变化,几乎是恒定的。

从数学上讲:

(图片作者提供)

这在我们的例子中确实发生了,并且应该在你的模型验证步骤中可见。正如我们所看到的,目前还没有理由得出模型或数据有问题的结论。

2) 偶尔的大离群值经常使你的点预测无用。

这个问题将我们引入了极值理论的领域,可能值得开一系列博客来讨论。因此,我们将仅简要查看这里发生了什么。

作为一个夸张但具有说明性的例子,考虑以下时间序列:

这仅仅是从一个Cauchy 分布中抽样,该分布的位置由正弦函数决定。现在,让我们看看如果我们的(点)预测只是底层正弦函数的延续,随着样本大小的增加,MSE 如何变化:

正弦-柯西预测模型的均方误差运行情况。(图片作者提供)

令人惊讶的是,MSE 在250.000!)次观察后甚至都没有收敛。无论你观察到多少数据,你的**平均(!)**平方误差仍在增长。这是某些概率分布家族的特性,而柯西分布就是其中之一。

你在日常生活中可能永远不会观察到这样的怪异现象。几乎所有的现实世界时间序列都有某些限制,使得无限的 MSE 不太可能。

然而,了解你观察到大型异常值的可能性至少会有所帮助。例如,想象一下在 2019 年,如果你能得到一个关于旅游业极端崩溃的粗略概率会有多么有价值。

调整你对“有用”预测的定义

当然,说服你的利益相关者接受上述点预测问题可能非常困难。对于商业人士来说,概率方法可能看起来像是不必要的火箭科学。

相反,我们通常通过预测与未来观察的匹配程度来衡量预测的成功。如果出了问题,只需增加更多的数据,希望下次会更好。然而,请考虑大多数时间序列系统的基础复杂性。你有多大的机会收集到所有相关数据?

这就像试图收集和处理所有相关因素以预测轮盘赌游戏的确切结果。虽然理论上可能,但巨大的粒度使得实践中几乎不可能实现。

然而,你可能会发现轮盘赌桌上存在一些物理缺陷。如果这些缺陷使赔率向某个方向偏斜,按此做出你的投注可能在长远中让你发财。

如果我们将这个类比转移到一般预测问题上,这就引导我们到一个范式转变:

与其尽可能精确地预测未来,不如让预测模型优化我们在未来结果上的投注几率。

将这个投注隐喻进一步扩展,我们得出三点关于预测的结论:

1) 现实世界的决策几乎总是在不确定性下做出

考虑以下问题:

你是一名冰淇淋供应商,想要优化你的每日库存。为了简单起见,我们假设每天你要么

  • 90%的机会出售确切的10磅冰淇淋,或者

  • 10%的机会出售0磅(因为天气实在太差了,你知道的)

此外,假设

  • 你可以在每天开始时以1金钱购买1磅冰淇淋

  • 1.2金钱出售1

  • 你的冰淇淋库存每天结束时归零(没有过夜仓储)

  • 如果你的总损失超过-10金钱,你就会破产

想象一下,你正在为这个问题建立一个需求预测模型,以决定你要出售多少冰淇淋。如果你走点预测+均方误差(MSE)路线,你的结果将如下:

期望需求为0.9*10+0.1*0 = 9,因此最小化 MSE 的预测每天也是9。你打算每天购买9磅冰淇淋吗?如果你连续多次没有销售任何东西,破产的风险怎么办?

冰淇淋供应商问题的概率质量函数和期望值。(作者提供的图像)

这是不确定性发挥作用的地方,你需要决定你愿意承担多少风险。像生活中的许多情况一样,这也是利润与风险之间的权衡。

不幸的是,仅凭点预测无法考虑任何不确定性。

现在假设我们有一个能够预测相应概率质量函数(pmf)的概率预测模型。从这里,我们可以推导出在给定库存x的情况下,第t天的收益R作为一个随机变量:

(作者提供的图像)

这些信息可以用在随机规划中。后者可以视为对确定性优化的概率扩展。在这里,我们也可以考虑并优化在面对现实世界不确定性时的风险。

事实上,现实世界的复杂性远远超出了我们的小冰淇淋例子。考虑一下这对现实与点预测偏离的可能性意味着什么。

2) 许多小下注比少数大下注更安全

回到有缺陷的轮盘赌桌上,假设0的概率比预期稍高。你会在一次下注中把所有筹码都押在0上,还是在多个回合中小额下注?

如果你运气不好,即使是最小的下注额也可能导致破产。然而,如果你在单次游戏中全押,发生这种情况的概率则要大得多。虽然讨论合适的下注大小超出了本文的范围,但凯利准则可能是一个有用的起点。

在实践中,这可能意味着从每月预测改为每日预测。当然,这只是一个非常简单的建议。受其他因素影响,每日预测可能仍然不够准确或完全无用。在这一点上,你和你的利益相关者的专业知识对于找到正确的平衡是必要的。

3) 有时候,根本不玩游戏更好

让我们面对现实吧,总有一些情况下你只能在长期中遭遇损失。如果你的时间序列的信噪比过低,可能会无法提供有用的预测。

资金雄厚的对冲基金正在为替代数据支付天价。所有这些只是为了使他们的预测比竞争对手的稍微准确一些。除非你能获得相同的数据(即使数据真的很有用),否则你不太可能在相同的投注上持续超越他们。

如果你已经到了这一步,你可能需要寻找新的数据来改善你的预测。如果这样仍然没有帮助,完全依赖于相应的预测也可能有意义。

创建多个相关汇总统计的点预测

不再通过 MSE 最小化(或通过 MAE 最小化中位数)来关注均值预测,

(作者提供的图像)

你还可以预测描述你分布的其他量。

示例 1中,最明显的选择是条件方差,

(作者提供的图像)

你可以在这篇文章中找到如何预测条件方差的简要概述。

一旦你的模型预测出高波动期,你可以决定采取更安全的策略。“采取更安全的策略”的含义显然取决于你的预测问题的背景。

示例 2 可能也会受益于条件方差预测。然而,请注意条件偏度在这里也发挥作用。处理这种情况的一种方法可能是预测条件分位数,即

(作者提供的图像)

这被称为分位数回归,例如,sklearn 的 GradientBoostingRegressor 实际上实现了相应的损失函数。

你应该选择哪些量最终将取决于你的具体问题。这里最大的优势是你不对底层分布做任何假设。而是让你的模型“学习”你关心的分布的重要方面。

另一方面,采用这种方法进行随机优化将是困难的。毕竟,你只是将最相关的信息压缩成几个点预测。如果你想根据某些预测计算正式的最佳决策,你可能会需要

将你的点预测替换为概率预测

这是最具挑战性但也最全面的方法。正如我们所见,概率方法的成功往往依赖于你选择的概率分布。

从技术上讲,非参数和机器学习方法也可以从数据中学习概率分布。不过,请记住,时间序列问题通常涉及的观测量远少于典型的机器学习用例。因此,这些方法在这里很容易过拟合。

尤其是如果你是 Python 用户,你可能需要自己实现许多模型。与 R 不同,Python 的预测生态系统似乎更加专注于点预测。然而,如果你只需要类似于 SARIMAX 的解决方案,statsmodel 将是你的朋友。

下面,我还总结了我们到目前为止讨论的三种不同的预测方法。请记住,这三种方法各有优缺点。

比较不同的预测风格。(图片由作者提供)

结论

希望你现在对点预测的陷阱有了更好的了解。虽然点预测本身并不是坏事,但它们只展示了不确定世界中正在发生的事情的不完整图景。

另一方面,概率预测提供了对给定时间序列未来的更丰富视角。如果你需要一个可靠的方法来处理现实世界复杂系统的各种不确定性,这就是最佳选择。不过,请记住,这条路径在很多情况下会需要更多的手动工作。

参考文献

[1] 汉密尔顿,詹姆斯·道格拉斯。时间序列分析。普林斯顿大学出版社,2020 年。

[2] 亨德曼,罗布·J.,& 阿塔纳索普洛斯,乔治。预测:原则与实践。OTexts,2018 年。

最初发布于 https://www.sarem-seitz.com 2023 年 1 月 1 日。

你什么时候应该微调 LLM?

原文:towardsdatascience.com/when-should-you-fine-tune-llms-2dddc09a404a

最近有一波令人兴奋的开源 LLM 可以进行微调。但这与使用闭源 API 比较如何呢?

Skanda VivekTowards Data Science Skanda Vivek

·发表于 Towards Data Science ·7 分钟阅读·2023 年 5 月 15 日

--

我经常被问到这个问题——LinkedIn 上的朋友们问我如何微调开源模型如 LLaMA,企业们试图弄清楚销售 LLM 托管和部署解决方案的商业案例,还有一些公司试图利用 AI 和 LLM 应用于他们的产品。但当我问他们为什么不想使用像 ChatGPT 这样的闭源模型时,他们并没有真正的答案。所以我决定以一个每天应用 LLM 解决业务问题的人的身份来写这篇文章。

闭源 API 的案例

你尝试过为你的用例实现 ChatGPT API 吗?也许你想总结文档或回答问题,或者只是想在你的网站上拥有一个聊天机器人。往往你会发现 ChatGPT 在多种语言任务上表现得相当不错。

一个普遍的看法是这些模型太贵了。但以 $0.002/1K tokens 的价格,我敢打赌你至少可以在几百个样本上尝试一下,评估一下 LLM 是否适合你的具体应用。事实上,以每天数千次 API 调用的频率,ChatGPT API 比托管自定义开源模型的基础设施便宜得多正如我在这篇博客中所写的。

一个论点是,假设你想回答成千上万份文档中的问题。在这种情况下,是否不如直接在这些数据上训练或微调一个开源模型,然后让微调后的模型回答关于这些数据的问题更简单?事实证明,这并不像听起来那么简单(由于各种原因,我将在下文的微调数据标注部分讨论)。

但是,ChatGPT 可以通过上下文回答包含数千份文档的问题,这里有一个简单的解决方案。基本上是将所有这些文档作为小块文本存储在数据库中。

将文档卸载到数据库中以进行大规模 LLM 查询 | Skanda Vivek

将所有必要信息提供给模型以回答问题的任务现在从模型架构转移到了包含文档块的数据库中。

相关的文档可以通过计算问题和文档块之间的相似性来找到。这通常通过将文档块和问题转换为词嵌入向量,计算块与问题之间的余弦相似度,最后只选择那些高于某个余弦相似度的块作为相关背景来完成。

最后,问题和背景可以组合成一个如下的提示,并输入到像 ChatGPT 这样的 LLM API 中:

prompt=f"Answer the question. Context: {context}\n Question: {question}"

你可能会问——为什么不将整个文档和问题一起放入提示中,而是将其分成块?我们将文档分成块而不是将所有文档组合成一个提示的原因是 LLMs 对最大输入和输出长度有一定的限制。对于 ChatGPT,这个限制是 4096 个令牌,或大约 6-7 页的文本。

将文档卸载到数据库并使用封闭的 LLM API 查询可能在答案显然存在于这些文档中的情况下效果很好。但也有一些情况可能会产生次优结果,例如,如果你希望 ChatGPT 提供关于某个小众话题的专家观点——例如,预测美国经济的财务前景。

领域特定的 LLMs

好吧,你试过 ChatGPT 或 BARD——但你不喜欢。答案有些偏差。例如,你问了 ChatGPT:

“美国经济可能会持续多久的通货膨胀?”

然后它返回:

作为一个 AI 语言模型,我不能确定地预测未来,特别是涉及复杂经济系统时。然而,我可以提供一些信息和观点,可能有助于你理解当前的情况。

通货膨胀是一个复杂的现象,可能由多种因素引起,例如需求增加、供应限制等……

不幸的是,这对你来说还不够好。当然,你可以给 ChatGPT 提供一些关于美联储主席杰罗姆·鲍威尔最近声明的新闻。但这并不能给你与——嗯,杰罗姆·鲍威尔,其他人也可以!或其他专家交流时获得的丰富领域经验。

想一想成为某个领域专家所需的条件。虽然一部分是阅读相关书籍,但更多的是与领域内的专家互动,并从经验中学习。虽然 ChatGPT 已经接受了大量金融书籍的训练,但它可能没有经过顶级金融专家或其他特定领域专家的训练。那么,如何使 LLM 成为金融领域的“专家”呢?这就是微调发挥作用的地方。

微调 LLMs

在讨论微调 LLMs 之前,我们先聊聊像 BERT 这样的较小语言模型的微调,这在 LLMs 之前是很常见的。对于像 BERT 和 RoBERTa 这样的模型,微调就是传递一些上下文和标签。任务定义明确,比如从上下文中提取答案,或将电子邮件分类为垃圾邮件或非垃圾邮件。如果你对微调语言模型感兴趣,我写了一些可能对你有用的博客文章:

## 微调 Transformer 模型以进行自定义数据上的问答 ## 微调 Transformer 模型以进行自定义数据上的问答

关于如何在自定义数据上微调 Hugging Face RoBERTa QA 模型并获得显著性能提升的教程

## 通过微调定制文本分类的 Transformer 模型 ## 微调 Transformer 模型以进行自定义文本分类

关于如何通过微调 DistilBERT 模型来构建垃圾邮件分类器(或其他分类器)的教程

## 通过微调定制文本分类的 Transformer 模型

然而,大型语言模型(LLMs)之所以备受关注,是因为它们能够通过改变提示的方式无缝地执行多个任务,你的体验类似于与另一端的人对话。我们现在希望的是将这些 LLM 微调为某个特定领域的专家,并像“人类”一样进行对话。这与在特定任务上微调像 BERT 这样的模型截然不同。

最早的开源突破之一是由一组斯坦福研究人员完成的,他们微调了一个 7B LLaMa 模型(Meta 早些时候发布)并称之为 Alpaca,花费不到 600 美元进行 52K 指令的训练。随后,Vicuna 团队发布了一个 130 亿参数的模型,达到了 ChatGPT 90% 的质量。

最近,MPT-7B transformer被发布,它能够处理 65k 个 token,是 ChatGPT 输入大小的 16 倍!这次训练从零开始,耗时 9.5 天,成本为 200k$。作为领域特定 LLM 的一个例子,Bloomberg 发布了一个类似 GPT 的模型BloombergGPT,专为金融领域设计,也是在从零开始训练的情况下构建的。

最近在训练和微调开源模型方面的进展只是小型和中型公司通过定制化 LLM 丰富其产品的开始。那么,你如何决定何时进行微调或训练整个领域特定的 LLM 呢?

首先,重要的是要清楚地确定闭源 LLM API 在你领域中的局限性,并说明让客户以更低成本与领域专家对话的必要性。对大约十万条指令进行微调并不昂贵——但获取正确的指令需要仔细思考。这也是你需要有一点大胆的地方——我目前还没想到很多领域中微调模型在领域特定任务上比 ChatGPT 表现得更好的情况,但我相信这很快就会出现,任何做得好的公司都将获得回报。

这就引出了从头开始训练 LLM 的理由。是的,这可能需要花费数十万美元,但如果你能提供充分的理由,投资者会很乐意出资。在最近的IBM 采访中,Hugging Face 首席执行官 Clem Delangue评论说,定制化 LLM 很快可能像专有代码库一样普遍,并成为在行业中具有竞争力的关键组成部分。

关键要点

应用于特定领域的大型语言模型(LLMs)在行业中可以非常有价值。它们有3 个逐步增加的成本和定制化水平

  1. 闭源 API + 文档嵌入数据库: 这一解决方案可能是最容易上手的,并且考虑到 ChatGPT API 的高质量——可能会提供足够好(如果不是最好的)性能。而且成本低廉!

  2. 微调 LLMs: 最近对类似 LLaMA 模型的微调进展表明,这样的微调成本大约为**500$**,可以在某些领域获得类似于 ChatGPT 的基线性能。如果你有一个包含50–100k 条指令或对话的数据库,微调一个基线模型可能是值得的。

  3. 从零开始训练: 如 LLaMA 和最近的 MPT-7B 模型所示,这种方式的成本大约为**~100–200k**,需要一到两周的时间。

既然你已经掌握了这些知识——那就去构建你的定制领域特定的 LLM 应用吧!

如果你喜欢这篇文章,关注我——我写关于在实际应用中应用最先进的自然语言处理(NLP)技术的话题,更广泛地说,也涉及数据与社会之间的交集。

随时在 LinkedIn上与我联系!

如果你还不是 Medium 会员,并且想要支持像我这样的作者,请通过我的推荐链接注册: https://skanda-vivek.medium.com/membership

以下是一些相关的文章:

LLM 经济学:ChatGPT 与开源

如何构建一个基于 ChatGPT 的应用?

提取式与生成式问答——哪种更适合你的业务?

为定制数据微调 Transformer 模型以进行问答

释放生成式 AI 为你的客户带来的力量

你应该在什么时候停止寻找?

原文:towardsdatascience.com/when-should-you-stop-searching-a439f5c5b954?source=collection_archive---------3-----------------------#2023-03-31

停车,图像来源于 Dall-E 2。

关于最优停止的介绍以及它如何与数据科学相关

Hennie de HarderTowards Data Science Hennie de Harder

·

关注 发表在 Towards Data Science ·10 分钟阅读·2023 年 3 月 31 日

--

在我们的日常生活中,我们经常面临需要做出具有重大影响的决定的挑战。无论是选择职业、寻找停车位、购买房屋,还是决定与谁共度余生,我们总是在不断评估和权衡不同的选项。数学家们用来处理决策的方法之一叫做“最优停止”。

在这篇文章中,你将了解什么是最优停止,以及如何在不同的实际情况中使用它。可能令人惊讶的是,你作为数据科学家也可以使用它。

让我们考虑一个现实生活中的例子来理解最佳停止。假设你在面试候选人,你有有限的时间面试每个候选人,一旦你面试了一个候选人,你必须决定是否招聘或继续下一个候选人。做出决定后,你不能重新考虑。目标是招聘到最佳候选人。在某一时刻,你无法知道一个候选人是否是最佳的,因为你还没有见过所有候选人!有两种失败的方式:你可能停得太早,或者停得太晚。

随时间变化的候选人。每个候选人之后你决定招聘或不招聘。图片由作者提供。

在上图中,你可以将人员替换为房子、工作机会、狗……实际上是你想做决定的任何事物,并且选择是顺序出现的。作为数据科学家,你可能会在不同领域遇到最佳停止问题,比如医疗保健、营销或金融。

什么时候停止?

那么如何做出最佳决策?这就是最佳停止发挥作用的地方。最佳停止是一个数学概念,涉及根据有限的信息找到做决策的最佳时机。有限的信息等于你直到那时所拥有的所有经验。

让我们回到引言中的面试例子。一个常用的策略是先看后跳:对于固定数量的候选人,你先观察。你不招聘,但你收集信息。完成这固定数量的面试后,你招聘第一个比你已见过的所有人都好的候选人。但你应该花多少时间搜索(观察)?我们可以通过模拟问题来轻松测试不同策略的成功率。为了简单起见,我们从 3 个候选人开始。

在下图中,我们直接招聘(所以没有观察期)。我们只面试起始候选人,并直接招聘。我们有 33.3%的机会招聘到最佳候选人(编号 1),但也有 33.3%的机会招聘到其他候选人(编号 2 或 3):

场景 1:不观察,直接跳过。图片由作者提供。

我们的第二个选项是不要招聘我们面试的第一个候选人(观察),然后如果第二个候选人比第一个更好,则招聘第二个候选人。如果第二个候选人不比第一个更好,我们会自动选择最后一个候选人。这个策略成功吗?这是在这种情况下发生的情况(橙色点是已招聘):

场景 2:不招聘第一个候选人,然后如果下一个更好则招聘,否则招聘最后一个。图片由作者提供。

现在我们在 50%的可能序列中招聘到最佳候选人!而我们只有在 6 分之一的情况下招聘到最差候选人,而不是三分之一。

最终的情境是我们不聘用前两名候选人,这意味着我们总是会得到最后一名候选人。这与情境一一样好,因为在这种情况下,我们会在三次中有一次得到最佳候选人。但等等……在这种情境下我们花费了更多的时间进行面试!这确实如此,这也是为什么大多数人会更喜欢情境 1 而不是情境 3。在三个候选人的情况下,我们的最佳停止点是面试一个候选人之后,即 33.3%的所有候选人(情境 2)。

如果我们增加候选人的池子会发生什么?对于给定的候选人数,我们可以通过模拟计算最佳的停止百分比。下图对此进行了可视化:

跳跃前查看的候选人百分比。每个候选人和查看组合的 100000 次试验的模拟。图片由作者提供。

精确地说:在这个问题中选择最佳候选人的概率会收敛到 1/e,约为 0.368。

我们还可以计算如果坚持上述图表中的查看百分比,我们聘用最佳候选人的百分比:

如果你坚持最佳查看百分比,你聘用最佳候选人的百分比。再次基于模拟。图片由作者提供。

37 在这里似乎是魔法数字!好消息是:你也可以将这一点应用于时间。如果你有时间限制,可以将总时间的 37%用于寻找,然后选择下一个最好的候选人。

这种方法有一些缺点:如果在寻找阶段已经面试了最佳人选,你最终会自动得到你面试的最后一位候选人。而这可能是最差的候选人!此外,你还需要花费几个小时或几天的时间来面试每一个人,尤其是当候选人数较多时。我们应该将搜索成本纳入我们的模型中。

搜索在现实生活中是如何运作的?

就像许多数学模型一样,将 37%的规则直接应用于现实生活中可能会很困难。在现实生活中,规则并不明确,而且搜索是有成本的。

在面试示例中,我们假设在面试候选人之前我们对他们一无所知(无信息)。但这在现实生活中相当不切实际:你有简历、LinkedIn 页面,也许还有案例研究或问卷,因此你可以在面试开始之前就开始比较候选人。我们在第一个示例中另一个假设是候选人总是接受录用通知。并且我们不能回到早期的候选人。在实际的面试设置中,你可以面试多个候选人,并在面试了一些之后决定哪个最合适。

对于这些情况,你可以将知识添加到模型中,看看这会如何影响你的决策。我们来逐一看看它们。

无信息 vs. 完整信息

在博弈论中,有不完全信息和完全信息的概念。假设你了解候选人的能力,并且可以将他们放在一个百分位数中。在这种情况下,你可以使用一个百分位数阈值来决定是否应该聘用候选人,具体取决于他们被面试的时间:

如果总共有 10 名候选人,则第 n 名候选人的聘用阈值。图片由作者提供。

在所有面试中,你会变得越来越不挑剔,因为随着你接近候选人名单的尾声,出现更好候选人的机会会变得越来越小。

拒绝与回到之前的候选人

在现实生活中,候选人可能会拒绝邀请。特别是那些收到更多邀请的候选人(这些候选人通常更优秀)。如果候选人拒绝邀请的可能性很高,我们应该改变我们的策略。

我们在现实生活中的另一个选项是回到我们已经面试过的候选人。在这种情况下,我们可以面试更多的候选人,并且回到之前被拒绝的某个人那里提出邀请。

如果允许回退,但在第二次报价时有 50%的拒绝概率,则观察阶段应为 61%,而不是 37%。在这种情况下,聘用最佳候选人的概率也会增加到 61%。

当第二次报价的拒绝概率为 50%时,寻找 61%。图片由作者提供。

我不需要最好的,只要一个好的就行。

另一个有趣的变体是:如果你并不特别关注最佳候选人,而是想要一个比大多数人更优秀的候选人?或者你想要两个最佳候选人中的一个?

在这种情况下,最优策略有点类似于寻找最佳候选人时的策略。你开始时设定基准:观察并且不聘用。然后,在下一个阶段,如果出现新的最佳候选人则停止。如果在那个阶段没有找到最佳候选人,那么在下一个阶段,如果出现最佳第二佳的候选人则停止。这个过程会持续到你遇到最终候选人。

如果有七个候选人,而你想聘用两个最佳候选人中的一个,使用这个策略。图片由作者提供。

最优停止与数据科学

在不同的方式中,最优停止与数据科学有关。最直接的方法有:用数据科学解决最优停止问题,以及在数据科学项目中使用最优停止。

用数据科学解决最优停止问题

对于有限时间范围内,动态规划是解决最优停止问题的最简单方法。在确定当前选项的价值时,可以使用不同的数据科学方法:

  1. 最小二乘蒙特卡洛(LSMC)

    一个有趣的现实应用是金融市场中的期权交易。在到期日前,期权持有人有权以预定价格买入或卖出标的资产。LSMC 使用路径模拟来评估当前期权,这有助于决策(买入或卖出)。

  2. 强化学习

    使用深度 Q 学习来确定最优停止问题中的最佳策略也是可能的。这篇论文解释了这一点,并说明了与 LSMC 相比使用它的好处。

  3. 深度学习 神经网络可以通过不同方式解决最优停止问题。当然如上所述在深度 Q 学习中,也可以通过预测在合适停止时间点的预期收益。另一种实现方式是调整神经网络的参数,从而表达不同的随机停止时间。

  4. 树和可解释性 许多最优停止算法的一个缺点是可解释性:解释它所做出的决策可能很困难。在某些情况下这不是问题,但在其他情况下可能是。可解释性有其好处,因为你可以从模型中学习并看到当前状态与行动之间的联系。在这篇论文中,研究人员创建了一个树模型作为策略,以实现可解释性。

在数据科学项目中使用最优停止理论

最优停止问题无处不在:在医疗、金融、管理等领域。最优停止对数据科学家也可能相关。为什么?因为它提供了一个框架,用于决策何时停止数据收集或在问题空间中寻找最佳解决方案。在许多数据科学问题中,收集额外的数据或探索更多选项可能既耗时又昂贵,或者两者兼有。你可以选择确定最优停止点,以最小化成本并最大化获得的信息价值。

在营销中,数据科学家可能会遇到最优停止问题。假设你有一个固定的产品库存,想在一定时间内销售该产品。通过价格促销,你可以影响人们的购买行为。在这种情况下,剩余库存可以决定你是否应该停止促销以最大化利润,或者继续进行促销。

营销示例:根据库存水平在固定时间段后决定是否继续或停止对某一产品的促销。目标是最大化利润。图片由作者提供。

在医疗保健领域,你也可以找到最佳停止问题。一个例子是等待器官移植的患者。与器官相关的患者价值通过估计的质量调整生命年(QALYs)来衡量,这些 QALYs 是患者如果移植发生将获得的。数据科学家的目标是找到一个政策,以最大化 QALYs。

Dall-E 2 生成的另一个停车场。

结论

下次你需要做决定时,希望你能利用这篇文章中的一些技巧!如果过程类似于面试的例子,比如当你想买房时,花 37%的时间来观察,然后选择下一个最佳的房子。如果两个最好的房子中有一个足够好,你也可以在两个寻找最佳的阶段后选择迄今为止看到的第二好房子。

为了回答所有问题:当你寻找停车位时,有哪些规则?在停车时,存在寻找好位置的时间与从停车位到最终目的地之间的步行时间之间的权衡。在这种情况下,一切都取决于占用率。如果占用率为 50%,你可以继续开车,直到尽可能靠近你的目的地。但如果占用率为 98%,你应该在距离目的地 35 个车位时,选择你看到的下一个空位。

研究表明,人们在现实生活中会比最佳停止百分比 37%更早停止。也许原因是之前提到的一些现实生活中的复杂因素,比如搜索成本或第二好的选择也足够好。作为数据科学家,如果你处理的是最佳停止问题,可以利用数据科学。你也可以使用最佳停止理论在最佳时点停止搜索。

相关

## 解决多臂赌博机问题

一种强大而简单的应用强化学习的方法。

[towardsdatascience.com ## 用 120 行代码解决非 ogram 问题

拼图、组合和解决方案动图。

[towardsdatascience.com ## 深度强化学习代理玩蛇游戏

带有搞笑花絮

[towardsdatascience.com

当电子表格不够用时:关系数据库的课程

原文:towardsdatascience.com/when-spreadsheets-arent-good-enough-a-lesson-in-relational-databases-2e5b0b847f5a?source=collection_archive---------8-----------------------#2023-05-12

SQL 教程

数据库规范化、关系数据库以及你为何需要它们

Stephanie LoTowards Data Science Stephanie Lo

·

关注 发表在 Towards Data Science ·11 分钟阅读·2023 年 5 月 12 日

--

图片由 Ivan Liu Hu 提供,发布于 Unsplash

你是否曾经等待几分钟才能完全加载和打开一个 Excel 电子表格?在编辑电子表格并尝试提取一些见解时,它是否不断滞后和冻结?对于任何持续在线收集数据的组织来说,这种情况并不少见。但你可以通过实施结构化的关系型数据库来更快地获得结果。

那么关系型数据库到底是什么?它是一组表。可以将每个表视作存储业务不同信息的 Excel 电子表格,但想象一下,如果你可以将这些 Excel 电子表格连接在一起。

电子表格存在于孤岛中,而关系型数据库在表之间建立了关系。

像 MySQL 或 Oracle 这样的关系型数据库非常有用,因为它们设计用来组织大量数据,这可以帮助你更高效地管理数据。如果设计得当,它们可以节省你在任务和分析上的大量时间。

因此,如果你在公司工作并且预期数据量将增长——无论是与订单、客户、采购历史还是营销互动相关——本文旨在解释如何开始思考重组当前的数据实践,以转换为关系型数据库。

关系型数据库的优势是什么?

这里是实施关系型数据库的一些优势以及业务可能克服的挑战:

  • 高效分析: 关系型数据库允许更快速地检索信息,然后使用 SQL(结构化查询语言)进行分析,以便运行查询。

  • 集中数据管理: 由于关系型数据库通常要求每个表的每一列输入特定类型或格式的数据,因此不太可能出现重复或不一致的数据。

  • 可扩展性: 如果你的业务正在经历高速增长,这意味着数据库将扩展,而关系型数据库可以容纳增加的数据量。

你可能仍在考虑关系型数据库是否适合你的公司。让我们通过一个例子来将这一点放入背景中。

示例:高速增长的电子商务零售商

照片由 Iga Palacz 拍摄,来源于 Unsplash

你为一个本地的二手家具电子商务零售商工作,每周你会向团队提供一份总结销售数据的报告,这有助于采购团队了解他们应该采购更多的商品。

目前,由于库存数据已经输入到 Excel 中,你大部分的分析都是在电子表格上进行的。你很可能使用数据透视表或 vlookup 来计算某些产品或品牌的周度、月度或年度销售百分比变化。最近,你注意到由于销售的库存增加,打开电子表格和进行简单分析都需要很长时间。

在公司处于增长阶段,记录了更多的采购、客户和库存时,Excel 可能无法再有效地完成你的常规任务。这时,关系型数据库可以通过能够大规模存储信息并通过 SQL 提取数据来提供帮助。

如果你对这些情况有共鸣,那么你可能会想知道如何将当前的数据设置转变为关系型数据库。考虑如何结构化你的数据库的一个好方法是进行一个称为数据库规范化的过程。

数据库规范化的基础

数据库规范化是修改现有数据模式的过程,以使其符合一系列渐进的规范形式。简单来说,就是确保数据以结构化的方式组织。这给你一套规则,帮助你开始对数据进行分类,并形成一个适合你的布局。

通过在数据库中建立结构,你能够帮助建立几个重要的事物:数据完整性和可扩展性。数据完整性确保数据的输入正确和准确,而可扩展性则确保你以一种更具计算效率的方式组织数据,以便在运行 SQL 查询时更为高效。

但是,当我们谈到规范形式时,我们到底在说什么呢?

规范化过程的快速概述

组织过程的每个阶段都被称为“规范形式”。

爱德华·C·科德首次提出了这一概念 在这里,最初描述了七种规范形式。 然而,为了使本文简洁,我们将解释并讲解前三种规范形式,特别是因为数据库通常被认为“规范化”,如果它符合第三范式。更全面的形式概述和很好的教程可以在 这里**找到。

第一范式 (1NF)

  • 每个表中每一列的值必须简化为最基本的值,也称为原子值。原子值是指列中没有值集合的情况。

  • 数据库中没有重复的列或行。

  • 每个表应有一个主键,这可以定义为一个非空的、唯一的值,以标识每一行的插入。

第二范式 (2NF)

  • 符合第一范式规则。

  • 调整列,使每个表只包含与主键相关的数据。

  • 外键用于建立表之间的关系。

第三范式(3NF)

  • 符合第一范式和第二范式的规则。

  • 需要移动或删除那些传递依赖的列(属性),即那些依赖于其他列的列,而这些列不是外键或主键。

现在我们已经掌握了一些定义,让我们在一个实际的例子中应用这些规范形式规则,以便这些概念能够深入理解。

教程:在加拿大零售商中进行受众分割

由 charlesdeluvio 在 Unsplash 提供的照片

在这个例子中,我使用了 MySQL 来执行查询,如果你想在其他 SQL 服务器(如 PostgreSQL 或 Oracle)中跟随操作,每个脚本都应进行相应的调整。如果你对 MySQL 不太熟悉,我建议你阅读这些文章 这里 以获取安装说明,以及 这里 以开始使用。请注意,跟随代码假设你具备一些基础的 SQL 知识。

以下数据并非基于实际的电子商务/订阅数据,仅供演示目的使用。合成数据已被创建,以便探索本文讨论的概念(在实际企业中,你将处理数千行数据,而不仅仅是二十行)。要跟随本教程,请查看我的 Github 仓库 这里,其中包含了数据的完整转换过程,包括数据创建、数据转换和查询。

数据库规范化实践

假设你刚刚被聘为一家电子商务零售商的分析师。你的公司正在进行一次重要的促销活动,你被分配了以下任务:

将我们当前的客户和电子邮件订阅者分开,以便我们可以向每个细分市场发送不同的电子邮件优惠,从而推动销售期间的购买。

当你访问数据库时,有几件事会立即引起注意。最显著的是,电子邮件订阅和客户信息被存储在不同的表格中,并且没有建立任何关系,这在执行分配任务时带来了挑战。然而,值得庆幸的是,你拥有相对完整的数据,并且通过一些调整可以在两者之间建立关系以完成任务。以下是当前数据库的详细信息:

上述表格包含了与客户和电子邮件订阅者相关的众多变量,包括他们的地理位置、他们以前收到的电子邮件以及相关的电子邮件指标。

第一范式(1NF)

我们可以做的第一件事是查看每列中包含的数据和数据类型,在将每个文件上传到 SQL 服务器之后。我们可以通过执行以下查询来完成此操作:

看起来我们还有很长的路要走才能符合第一范式!回顾一下,达到第一范式的要求涉及正确的行标识和正确分组数据。我们当前的设置违反了第一范式的所有三条规则,这主要集中在email_newsletter表中:

  • 目前email_newsletter没有主键。这迫使数据库用户通过电子邮件地址搜索每个订阅者,这并不被认为是好的实践,因为用户可能会更新他们的电子邮件地址以及隐私问题。

  • email_newsletter中的数据还没有达到最简化的形式。经过进一步分析,列内容可以分为两个列,如subject_linepromo_code

  • email_newsletter中,重复的组是电子邮件发送和内容。一个用户可以收到很多电子邮件,因此最好的办法是将其从表格中分离出来。

为了解决第一个问题,我们可以在表格的第一列中添加一个唯一的主键到email_newsletter,并运行以下查询,其中email_sign_up_id是列名,数据类型是INTEGER

通过这个查询,我们添加了一个唯一标识的主键,它会随着每条新数据记录的增加而自动递增。

对于第二次修订,我们需要将contents拆分为各自的subject_linepromo_code字段,我们发现每个字段通常由逗号分隔,我们可以用它来拆分每个字段:

我们的结果是将每列分隔到最简化的形式,以便更清晰地看到每列的类别。

最后,我们可以通过将所有与电子邮件内容相关的数据组织到一个新的表格email_distribution中来去除重复的组,别忘了将原表中的数据添加进去!(查看 GitHub here 获取完整的 SQL,包括数据加载):

通过上面的查询,我们将电子邮件内容拆分到一个单独的表中,以确保我们不会丢失任何信息。现在我们需要使用以下查询将其与email_newsletter表连接起来,使用外键:

这有助于我们遵守第一范式,作为最终步骤,我们可以删除任何冗余的列,并查看我们的布局,然后再进入第二范式:

第二范式(2NF)

为了遵守第二范式的规则,我们需要实现第一范式的合规性✓,建立表之间的关系,并确保非键列依赖于主键。以下是我们如何将设置转换为第二范式的步骤:

  • customer表目前与其他两个表没有连接,存在在email_opt_in列中已经同意提供其电子邮件的客户未被联系的情况。

  • email_newsletter中存在与主键不相关的列,例如email_openclick_through,这些与其个人信息不是非常相关,因此我们可以考虑将它们分离出来。

我们希望在customeremail_distribution表之间建立关系,以便拥有我们的两个电子邮件前景和当前客户的两个部分。但是,如果按照目前的情况来连接可能会显得混乱,因为一个客户可能会收到多封电子邮件并干扰主键。因此,一个逻辑的中间步骤是创建一个新表,可以放置在两者之间,我们将其命名为customer_newsletter_metrics,以允许这些属性存储在自己的表中:

该表有助于连接我们整个数据库的链接,因为我们在创建表时已经说明了外键,表中的每列都与主键相关且依赖。

我们现在的设置几乎完成了,但有点不平衡——因为我们为客户建立了一个已建立的指标表,但没有为电子邮件前景建立一个表。通过对这一组应用相同的处理,可以帮助减轻未来的问题,因为如果前景的电子邮件更改,则在单独的表中存储这些信息会减少修订(请在 GitHub 上的完整代码这里进行跟踪)。

email_newsletter重命名为email_newsletter_metrics并创建我们的email_prospect表后,我们的数据库经过了 2NF 转换:

第三范式(3NF)

我们示例的最后一步是遵循第三范式规则,其中包括完成第一和第二范式规则 ✓,最后,分离出彼此依赖的非关键列。

总体而言,我们到目前为止做得很好,以减少非关键列之间的相互依赖,尽管如果我们更仔细地查看客户表,我们会发现城市和邮政编码是相互依赖的,因为邮政编码与客户居住地有关,但也与客户居住的城市有关。在客户搬迁时,这可能会在长期运行中出现问题,一个列可能会被更新,但可能不会更新另一个列。

鉴于此,我们可以创建一个名为postal_code的新表,从customer表中分离出postal_codecity,并在这两个表之间建立连接:

上述代码块的最后完成了我们的规范化过程(耶!),我们最终转换后的数据库如下所示:

我们重新访问的初始请求:使用关系数据库

所以我们通过符合第一、第二和第三范式的关系数据库已经准备好了,为什么不检查一下最初给我们的要求:

将我们当前的客户和邮件订阅者分开,以便我们可以向每个数据段发送不同的邮件优惠,以推动销售期间的购买。

让我们形成两个简单的查询语句来获取每个数据段的信息:

查询将产生两个表,输出如下所示:

现在你可以自信地发送下一封销售邮件了,确保表格之间没有重复或遗漏的联系人。更重要的是,我们已经按照这样的结构组织了数据库,使数据分析变得更容易,并且为更多客户做好了准备。

一些结束语

通过识别日常工作中诸如数据库缓慢或复杂等痛点,本文旨在提供实施关系数据库作为解决方案的方法。通过使用从第一到第三范式的技术,你可以将数据拆分为易于处理和减少错误的可管理块。

作为友好的最后提醒,关于所用代码的完整解析,请访问我的 GitHub 仓库 这里。希望这篇文章能够激发你对关系数据库的新兴趣✨。感谢阅读和关注!

除非另有说明,所有图片均由作者提供。

参考文献

  1. E.F.Codd, 大规模共享数据银行的关系模型,1970 年 6 月,《ACM 通讯》。

  2. E.burns, SQL 数据库规范化完整指南,2021 年 2 月,Medium。

  3. C.Andreou, SQL 介绍,2019 年 8 月,Medium。

随机策略何时优于确定性策略

原文:towardsdatascience.com/when-stochastic-policies-are-better-than-deterministic-ones-b950cd0d60f4

为什么我们让随机性决定强化学习中的动作选择

Wouter van Heeswijk, PhDTowards Data Science Wouter van Heeswijk, PhD

·发表于 Towards Data Science ·6 分钟阅读·2023 年 2 月 18 日

--

如果使用确定性策略进行石头剪刀布游戏将变得非常无聊 [照片来源: Marcus Wallis 在 Unsplash]

如果你习惯于确定性决策策略(例如,Deep Q-learning),你可能会忽视随机策略的必要性和使用。毕竟,确定性策略提供了一个方便的状态-动作映射 π:s ↦ a,理想情况下甚至是最佳映射(即,如果所有贝尔曼方程都被完美地学习了)。

相比之下,随机策略 — 通过给定状态下的动作的条件概率分布表示,π:P(a|s) — 看起来相当不方便且不精确。我们为什么要让随机性来指导我们的行动,为什么要将最佳已知决策的选择留给偶然?

实际上,从现有的演员-评论家算法数量来看,大量的强化学习(RL)算法确实使用了随机策略。显然,这种方法必然有其好处。本文讨论了四种情况下,随机策略优于其确定性对手的情况。

## 强化学习的四种策略类别

towardsdatascience.com

I. 多智能体环境

可预测性并不总是好事。

剪刀石头布的游戏中可以清楚地看到,确定性策略会惨遭失败。对手会很快发现你总是玩石头,并据此选择反制动作。显然,纳什均衡是一个均匀分布的策略,以概率 1/3 选择每个动作。如何学习它?你猜对了:随机策略。

尤其是在对抗性环境中——对手有不同的目标并尝试预测你的决策——在策略中引入一定程度的随机性通常是有益的。毕竟,博弈论规定通常没有纯策略能够始终对对手做出单一的最佳回应,而是将混合策略作为许多游戏的最佳动作选择机制。

不可预测性是一个强大的竞争工具,如果需要这种特质,随机策略显然是最佳选择。

面对一个值得的对手时,总是玩相同的游戏会很快适得其反。[照片由 Christian Tenguan 提供,来源 Unsplash]

II. 部分观察(POMDP)

在许多情况下,我们对真实问题状态的了解并不完美,而是试图从不完美的观察中推测。部分观察马尔可夫决策过程(POMDPs)领域就是围绕这种状态和观察之间的差异建立的。当我们通过特征来表示状态时,这种不完美同样适用,因为在处理大状态空间时通常需要这样做。

考虑大卫·银的著名别名 GridWorld。在这里,状态通过观察周围的墙来表示。在下面的插图中,在两个阴影状态中,智能体观察到上方和下方都有墙。尽管真实状态是不同的并且需要不同的动作,但它们在观察中是相同的

基于不完美的观察,智能体必须做出决策。价值函数近似(例如,Q-learning)可能很容易陷入困境,总是选择相同的动作(例如,总是向左),从而永远无法获得奖励。ϵ-贪婪奖励可能会缓解这种情况,但仍然大多数时候会陷入困境。

相对而言,策略梯度算法将以 0.5 的概率学会向左或向右走,因此可以更快地找到宝藏。通过认识到智能体对环境的感知不完美,它故意采取概率性行动以对抗固有的不确定性。

Aliased GridWorld 中的确定性策略。代理只能观察墙壁,因此无法区分阴影状态。确定性策略将在两个状态中做出相同的决策,这通常会将代理困在左上角。[图片由作者提供,基于 David Silver 的示例,剪贴画来自GDJ [1, 2],通过OpenClipArt.org]

Aliased GridWorld 中的随机策略。代理只能观察墙壁,因此无法区分阴影状态。一个最佳的随机策略将在别名状态中以相等的概率选择左和右,从而更有可能找到宝藏。[图片由作者提供,基于 David Silver 的示例,剪贴画来自GDJ [1, 2],通过OpenClipArt.org]

III. 随机环境

大多数环境——尤其是现实生活中的环境——都表现出显著的不确定性。即使我们在完全相同的状态下做出完全相同的决策,相应的奖励轨迹也可能大相径庭。在我们对期望的下游值有一个合理的估计之前,我们可能需要进行许多训练迭代。

如果我们在环境本身中遇到如此巨大的不确定性——如其转移函数所反映的——随机策略通常有助于发现这些不确定性。策略梯度方法提供了一种强大且固有的探索机制,这在基于价值的方法的普通实现中是不存在的。

在这种情况下,我们不一定要将固有的概率策略作为最终目标,但在探索环境时它确实有帮助。概率性动作选择和策略梯度更新的结合引导我们在不确定环境中改进,即使这种搜索最终将我们引导到一个接近确定性的策略

在部署随机策略时,控制和环境之间的相互作用生成了强大的探索动态[图片由作者提供]

说实话,如果我们抛开价值函数逼近中的标准ϵ-贪婪算法,还有许多强大的探索策略在学习确定性策略时效果非常好:

## 你应该知道的七种强化学习探索策略

纯探索与利用、ϵ-贪心、玻尔兹曼探索、乐观初始化、置信区间……

towardsdatascience.com

IV. 连续动作空间

虽然有一些变通方法,但在连续空间中应用基于价值的方法通常需要离散化动作空间。离散化越精细,原始问题的逼近程度就越高。然而,这会增加计算复杂性。

以自驾车为例。踩油门的力度、踩刹车的力度、加油的程度——这些都是内在的连续动作。在连续动作空间中,它们可以通过三个变量来表示,每个变量都可以在一定范围内取值。

假设我们为油门和刹车定义了 100 个强度级别,为方向盘定义了 360 度。这样有 100100360=3.6M 种组合,我们有一个相当大的动作空间,但仍然缺乏连续控制的细腻触感。显然,高维度和连续变量的组合通过离散化处理起来特别困难。

相比之下,策略梯度方法完全能够从代表性概率分布中抽取连续动作,使其成为连续控制问题的默认选择。例如,我们可以通过三个参数化的高斯分布来表示策略,学习均值和标准差。

驾驶汽车是一个固有的连续控制的例子。动作空间离散化得越细致,基于价值的学习就变得越繁琐 [照片来源 Nathan Van Egmond 在 Unsplash]

向近乎确定性策略的收敛

在结束本文之前,重要的是要强调一个随机策略并不意味着我们会一直做半随机决策直到时间的尽头。

在某些情况下(例如上述的石头剪子布或别名网格世界),最佳策略需要混合动作选择(分别为 30%/30%/30%和 50%/50%)。

在其他情况下(例如,识别最佳老丨虎丨机),最佳响应实际上可能是确定性的。在这种情况下,随机策略将收敛到一个近乎确定性的策略,例如,以 99.999%的概率选择某个特定动作。对于连续动作空间,策略将收敛到非常小的标准差。

也就是说,政策将永远不会是完全确定性的。对于撰写收敛性证明的数学家来说,这实际上是一个不错的特性,确保在极限情况下无限探索。现实世界的从业者可能需要稍微务实,以避免偶尔的愚蠢行为。

根据问题的不同,随机政策可能会收敛为接近确定性的政策,几乎总是选择产生最高预期奖励的行动 [图片来源于作者]

总结

这里有四种情况下,随机政策比确定性政策更可取:

  • 多智能体环境:我们的可预测性会被其他智能体惩罚。对我们的行动添加随机性使得对手难以预判。

  • 随机环境:不确定的环境需要较高的探索程度,而基于确定性政策的算法并未固有地提供这种探索。随机政策会自动探索环境。

  • 部分可观察环境:由于观察(例如,状态的特征表示)是不完美的真实系统状态的表示,我们难以区分需要不同操作的状态。混合我们的决策可能会解决这个问题。

  • 连续动作空间:否则,我们必须对动作空间进行精细离散化以学习价值函数。相反,基于政策的方法通过从相应的概率密度函数中抽样优雅地探索连续动作空间。

参考文献

www.davidsilver.uk/wp-content/uploads/2020/03/pg.pdf

www.freecodecamp.org/news/an-introduction-to-policy-gradients-with-cartpole-and-doom-495b5ef2207f/

en.wikipedia.org/wiki/Strategy_(game_theory)

解决复杂问题时,第一步是最困难的

原文:towardsdatascience.com/when-tackling-complex-topics-the-first-step-is-the-hardest-d04d60663098?source=collection_archive---------11-----------------------#2023-10-19

TDS Editors数据科学前沿 TDS 编辑团队

·

关注 发表在 数据科学前沿 · 发送为 新闻通讯 · 3 分钟阅读 · 2023 年 10 月 19 日

--

成为一个“初学者”并不是一个有限的状态,你一旦经历便永远离开。只要你致力于持续学习和成长,你会发现自己在职业生涯中多年(甚至几十年!)后仍在努力掌握新的概念和思想。

只要您有坚实的指导和正确的资源来帮助您处理日常工作流中数据和机器学习专业人士每天处理的复杂技术主题,这无疑是一件好事。这正是我们介入的地方:TDS 的作者在解读尖端研究和工具并使其对他人易于理解方面表现出色,无论读者是刚从第一次编程训练营毕业还是科技巨头的高级从业者。

本周,我们精选了几篇最近的文章,正是因为它们覆盖了广泛的主题 —— 从线性代数到图像分类,从一位支持型教师的角度来看,他们并不假设学生在先前知识方面拥有太多背景。它们提供具体可行的信息,并保持易于吸收和消化而不至于简化。祝学习愉快!

  • 生成式人工智能是什么?一份全面的指南自 ChatGPT 推出将近一年以来,生成式人工智能显然已经变得非常流行 —— 人们对其内部运作、潜在好处和当前限制的混淆和误解也如此。玛丽·纽豪泽的入门指南是任何需要在这一及时话题上建立坚实基础的人的可靠资源。

  • **多维度探索是可能的!**当使用得当时,类比是将复杂概念转化为易于理解的思想的强大工具。迭戈·曼弗雷最近的解释就是一个很好的例子:它通过比较主成分分析(PCA)背后的数学来解析,类比为在维度之间移动(出色的插图也帮助了)。

  • 初学者的图像分类学习基本的机器学习工作流程有许多方法;对于她的图像分类介绍,米娜·加沙米选择回顾 2014 年至 2015 年,当时引入了两个开创性的架构 —— ResNet 和 VGG Network。如果您通过深入了解话题背后的背景来更好地吸收知识,那么这篇文章正是为您而写。

照片由Jorge Zapata拍摄,来自Unsplash。

  • 线性代数 3:向量方程 过去几周,tenzin migmar (t9nz) 一直在分享关于线性代数基础的初学者友好教程。最新的一期,专注于向量方程,是对那些以前觉得这个话题令人生畏的人,或对经验丰富的数据专业人士有帮助的补充资源。

  • 使用 Scikit-Learn 的支持向量机:友好的介绍 如果你仍在建立核心机器学习技能,将一个强大的算法添加到工具箱中是个好主意。Riccardo Andreoni的支持向量机指南很好地平衡了 SVM 背后的理论背景和实际应用。

  • 变换器 — 直观且详尽的解释 我们多年来发布了许多关于变换器架构的高质量文章,但总有另一个方法的空间。Daniel Warfield的详细概述将模型拆解,揭示其构建块,并花时间讲解每个组件的独立作用及其相互关系。

我们希望你有一些额外的时间来阅读我们最近的几篇杰作——它们覆盖了很多内容,而且做得非常,非常好:

  • Yennie Jun 调查了 GPT-4 的数学技能,并聚焦于其在英语和传统上资源不足的语言之间的差异。

  • AI 工具以惊人的速度涌现,Sam Stone 认为是时候更多关注它们的 UI/UX 设计了。

  • Colab 有什么新变化?谷歌的笔记本服务已经存在一段时间,Parul Pandey的概述帮助数据科学家了解最新更新。

  • 处理“megemodels”有其自身的一套挑战和难点;Amber Teng的实践深入展示了如何在不同配置中加载流行的 Llama 2 模型。

  • 在他之前关于创新提示工程技术的研究基础上,Giuseppe Scalamogna提供了一个制定不同类型程序模拟提示的路线图。

感谢您支持我们作者的工作!如果您喜欢在 TDS 上阅读的文章,可以考虑成为 Medium 会员——这将解锁我们整个档案(以及 Medium 上的所有其他帖子)。

当数据集较小时,特征是你的朋友。

原文:towardsdatascience.com/when-the-dataset-is-small-features-are-your-friends-6e7f8dcc819e?source=collection_archive---------9-----------------------#2023-04-03

特征工程可以弥补数据的不足。

Krzysztof PałczyńskiTowards Data Science Krzysztof Pałczyński

·

关注 发表在Towards Data Science ·7 分钟阅读·2023 年 4 月 3 日

--

照片由Thomas T拍摄,发布在Unsplash上

在快速发展的人工智能(AI)领域,数据已经成为无数创新应用和解决方案的命脉。确实,大数据集通常被认为是强大且准确的 AI 模型的基础。然而,当手头的数据集相对较小时会发生什么?在本文中,我们探讨了特征工程在克服小数据集限制中的关键作用。

玩具数据集

我们的旅程从创建数据集开始。在这个例子中,我们将进行简单易行的信号分类。数据集有两个类别;频率为 1 的正弦波属于类别 0,频率为 2 的正弦波属于类别 1。信号生成的代码如下。该代码生成正弦波,应用加性高斯噪声,并随机化相位偏移。由于噪声和相位偏移的加入,我们得到多样的信号,分类问题变得不再简单(尽管通过正确的特征工程仍然容易)。

def signal0(samples_per_signal, noise_amplitude):
    x = np.linspace(0, 4.0, samples_per_signal)
    y = np.sin(x * np.pi * 0.5)
    n = np.random.randn(samples_per_signal) * noise_amplitude

    s = y + n

    shift = np.random.randint(low=0, high=int(samples_per_signal / 2))
    s = np.concatenate([s[shift:], s[:shift]])

    return np.asarray(s, dtype=np.float32)

def signal1(samples_per_signal, noise_amplitude):
    x = np.linspace(0, 4.0, samples_per_signal)
    y = np.sin(x * np.pi)
    n = np.random.randn(samples_per_signal) * noise_amplitude

    s = y + n

    shift = np.random.randint(low=0, high=int(samples_per_signal / 2))
    s = np.concatenate([s[shift:], s[:shift]])

    return np.asarray(s, dtype=np.float32) 

类别 0 中的信号可视化。

类别 1 中的信号可视化

深度学习性能

最先进的信号处理模型是卷积神经网络(CNN)。所以,让我们创建一个。这一网络包含两个一维卷积层和两个全连接层。代码列在下面。

class Network(nn.Module):

    def __init__(self, signal_size):

        c = int(signal_size / 10)
        if c < 3:
            c = 3

        super().__init__()
        self.cnn = nn.Sequential(
            nn.Conv1d(1, 8, c),
            nn.ReLU(),
            nn.AvgPool1d(2),
            nn.Conv1d(8, 16, c),
            nn.ReLU(),
            nn.AvgPool1d(2),
            nn.ReLU(),
            nn.Flatten()
        )

        l = 0
        with torch.no_grad():
            s = torch.randn((1,1,SAMPLES_PER_SIGNAL))
            o = self.cnn(s)
            l = o.shape[1]

        self.head = nn.Sequential(
            nn.Linear(l, 2 * l),
            nn.ReLU(),
            nn.Linear(2 * l, 2),
            nn.ReLU(),
            nn.Softmax(dim=1)
        )

    def forward(self, x):

        x = self.cnn(x)
        x = self.head(x)

        return x

CNNs 是可以处理原始信号的模型。然而,由于其参数众多的架构,它们往往需要大量的数据。然而,最开始,让我们假设我们有足够的数据来训练神经网络。我使用信号生成创建了一个包含 200 个信号的数据集。每个实验重复了十次,以减少随机变量的干扰。代码如下:

SAMPLES_PER_SIGNAL = 100
SIGNALS_IN_DATASET = 20
NOISE_AMPLITUDE = 0.1
REPEAT_EXPERIMENT = 10

X, Y = [], []

stop = int(SIGNALS_IN_DATASET / 2)
for i in range(SIGNALS_IN_DATASET):

    if i < stop:
        x = signal0(SAMPLES_PER_SIGNAL, NOISE_AMPLITUDE)
        y = 0
    else:
        x = signal1(SAMPLES_PER_SIGNAL, NOISE_AMPLITUDE)
        y = 1

    X.append(x.reshape(1,-1))
    Y.append(y)

X = np.concatenate(X)
Y = np.array(Y, dtype=np.int64)

train_x, test_x, train_y, test_y = train_test_split(X, Y, test_size=0.1)

accs = []
train_accs = []

for i in range(REPEAT_EXPERIMENT):

    net = NeuralNetClassifier(
        lambda: Network(SAMPLES_PER_SIGNAL),
        max_epochs=200,
        criterion=nn.CrossEntropyLoss(),
        lr=0.1,
        callbacks=[
            #('lr_scheduler', LRScheduler(policy=ReduceLROnPlateau, monitor="valid_acc", mode="min", verbose=True)),
            ('lr_scheduler', LRScheduler(policy=CyclicLR, base_lr=0.0001, max_lr=0.01, step_size_up=10)),
        ],
        verbose=False,
        batch_size=128
    )

    net = net.fit(train_x.reshape(train_x.shape[0], 1, SAMPLES_PER_SIGNAL), train_y)
    pred = net.predict(test_x.reshape(test_x.shape[0], 1, SAMPLES_PER_SIGNAL))
    acc = accuracy_score(test_y, pred)

    print(f"{i} - {acc}")

    accs.append(acc)

    pred_train = net.predict(train_x.reshape(train_x.shape[0], 1, SAMPLES_PER_SIGNAL))
    train_acc = accuracy_score(train_y, pred_train)
    train_accs.append(train_acc)

    print(f"Train Acc: {train_acc}, Test Acc: {acc}")

accs = np.array(accs)
train_accs = np.array(train_accs)

print(f"Average acc: {accs.mean()}")
print(f"Average train acc: {train_accs.mean()}")
print(f"Average acc where training was successful: {accs[train_accs > 0.6].mean()}")
print(f"Training success rate: {(train_accs > 0.6).mean()}")

CNNs 的测试准确率达到了 99.2%,这是对最先进模型的预期。然而,这一指标是针对这些实验运行得到的,其中训练是成功的。所谓“成功”,是指训练数据集上的准确率超过了 60%。在这个例子中,CNNs 权重初始化对训练至关重要,并且有时会发生问题,因为 CNNs 是复杂的模型,容易遇到由于随机权重初始化不幸带来的问题。训练的成功率是 70%。

现在,让我们看看当数据集较小时会发生什么。我将数据集中的信号数量减少到 20 个。因此,CNNs 的测试准确率达到了 71.4%,准确率下降了 27.8 个百分点。这是不可接受的。不过,接下来该怎么做呢?数据集需要更长,以便使用最先进的模型。在工业应用中,获取更多数据要么不可行,要么至少非常昂贵。我们是否应该放弃这个项目,另谋他路?

不。当数据集很小的时候,特征是你的朋友。

特征工程

这个特定的例子涉及基于频率的信号分类。因此,我们可以应用经典的傅里叶变换。傅里叶变换将信号分解为由频率和幅度参数化的一系列正弦波。结果,我们可以使用傅里叶变换来检查每个频率在形成信号中的重要性。这种数据表示应该简化任务,使得小数据集就足够了。此外,傅里叶变换将数据结构化,以便我们可以使用更简单的模型,如随机森林分类器。

信号可视化转换为频谱。在左侧是类别 0 的信号频谱,右侧是类别 1 的信号频谱。这些图有对数刻度以便更好地显示。此示例中使用的模型在线性刻度上解释信号。

转换信号和训练随机森林分类器的代码如下:

X, Y = [], []

stop = int(SIGNALS_IN_DATASET / 2)
for i in range(SIGNALS_IN_DATASET):

    if i < stop:
        x = signal0(SAMPLES_PER_SIGNAL, NOISE_AMPLITUDE)
        y = 0
    else:
        x = signal1(SAMPLES_PER_SIGNAL, NOISE_AMPLITUDE)
        y = 1

    # Transforming signal into spectrum
    x = np.abs(fft(x[:int(SAMPLES_PER_SIGNAL /2 )]))    

    X.append(x.reshape(1,-1))
    Y.append(y)

X = np.concatenate(X)
Y = np.array(Y, dtype=np.int64)

train_x, test_x, train_y, test_y = train_test_split(X, Y, test_size=0.1)

accs = []
train_accs = []

for i in range(REPEAT_EXPERIMENT):
    model = RandomForestClassifier()
    model.fit(train_x, train_y)

    pred = model.predict(test_x)
    acc = accuracy_score(test_y, pred)

    print(f"{i} - {acc}")

    accs.append(acc)

    pred_train = model.predict(train_x)
    train_acc = accuracy_score(train_y, pred_train)
    train_accs.append(train_acc)

    print(f"Train Acc: {train_acc}, Test Acc: {acc}")

accs = np.array(accs)
train_accs = np.array(train_accs)

print(f"Average acc: {accs.mean()}")
print(f"Average train acc: {train_accs.mean()}")
print(f"Average acc where training was successful: {accs[train_accs > 0.6].mean()}")
print(f"Training success rate: {(train_accs > 0.6).mean()}")

随机森林分类器在 20 个和 200 个信号长度的数据集上达到了 100%的测试准确率,且每个数据集的训练成功率也为 100%。因此,我们在所需数据量较少的情况下获得了比 CNN 更好的结果——这都归功于特征工程。

过拟合风险

尽管特征工程是一个强大的工具,但也必须记得从输入数据中减少不必要的特征。输入向量中的特征越多,过拟合的风险越高——尤其是在小数据集中。每一个不必要的特征都可能引入随机波动,机器学习模型可能将其视为重要模式。数据集中的数据越少,随机波动的风险越高,从而产生在现实世界中不存在的相关性。

可能有助于修剪过大的特征集合的机制之一是搜索启发式方法,如遗传算法。特征修剪可以被表述为一个任务,即找到最小数量的特征,以便成功训练机器学习模型。它可以通过创建一个长度等于特征数据大小的二进制向量来编码。 “0”表示特征不在数据集中,而“1”表示特征在数据集中。然后,这样向量的适应度函数是模型在修剪数据集上达到的准确度与向量中零的计数的总和,经过适当的权重缩放。

这只是移除不必要特征的众多解决方案之一。然而,它非常强大。

结论

尽管呈现的示例相对简单,但它展示了在工业中应用人工智能系统时的典型问题。目前,深度神经网络几乎可以实现我们所期望的所有功能,只要提供足够的数据。然而,数据通常稀缺且昂贵。因此,人工智能的工业应用通常涉及进行广泛的特征工程,以简化问题,从而减少训练模型所需的数据量。

感谢阅读。本示例生成的代码可以通过以下链接访问:github.com/aimagefrombydgoszcz/Notebooks/blob/main/when_dataset_is_small_features_are_your_friend.ipynb

除非另有说明,所有图片均由作者提供。

你应该何时更喜欢“汤普森采样”而不是 A/B 测试

原文:towardsdatascience.com/when-you-should-prefer-thompson-sampling-over-a-b-tests-5e789b480458

对“汤普森采样”的深入解释,这是一种比 A/B 测试更高效的在线学习替代方法。

Samuele MazzantiTowards Data Science Samuele Mazzanti

·发布于 Towards Data Science ·阅读时长 8 分钟·2023 年 6 月 13 日

--

[图片由作者提供]

想象一下你有两个广告可以选择:红色的和蓝色的。显然,你希望向用户展示点击率最高的广告。

该给用户展示红色广告还是蓝色广告?[图片由作者提供]

但你如何找出哪个广告的点击率最高呢?

回答这个问题最常见的方法是进行 A/B 测试。这意味着将一些用户分开,一半用户看到第一个广告,另一半用户看到第二个广告。最后,你可以计算每个选项的点击率,并选择最佳的广告。

假设在 A/B 测试结束时,你得到了以下结果:

A/B 测试在 10,000 次展示后的结果。[图片由作者提供]

蓝色版本显然优于红色版本:点击率为 18%,而红色版本的点击率为 11%。但这意味着我们错失了许多机会:我们本可以向更多用户展示蓝色广告,从而获得更多点击

另一方面,如果我们非常早地停止实验,比如在只有 20 个用户之后,会怎么样呢?

A/B 测试在 20 次展示后的结果。[图片由作者提供]

我们直观地知道,在 20 个用户之后,结果不够可靠,无法将表现最佳的变体推荐给所有用户。

一般来说,A/B 测试的问题是:

  • 如果我们设置了太多用户,我们在表现较差的变体上就会失去机会

  • 如果我们只设置了太少的用户,测试结果是不确定的

换句话说,A/B 测试效率低,因为它们过于静态。理想情况下,我们需要一个能够随着数据的增加而动态学习的智能系统。

这个系统应该:

  • 当结果太小而不可靠时,探索不同的备选方案;

  • 当结果开始变得足够可靠时,通过将更多流量发送到表现最好的备选方案来利用这些结果。

好消息是:这样的系统存在,称为 Thompson Sampling。

使用概率分布而不是数字

我们上面看到的方法尝试用一个数字来评估每个备选方案:它的点击率。这个方法的问题在于,单一数字无法表达估计本身的相关不确定性。

为了解决这个问题,Thompson Sampling 提出了使用完整的概率分布而不是单一数字。

概率分布的目标是表达对指标估计的不确定性。

一旦我们拥有了每个备选方案的分布,Thompson Sampling 就通过从每个分布中抽取一个随机数字来工作。然后,将显示给用户与最高数字相关联的备选方案。

这样做的意义是什么?实际上,思想是,如果分布表达了高度的不确定性,那么结果在很大程度上取决于运气。

换句话说,我们对信念的信心越小,系统就会探索更多的不同备选方案。相反,信心增加时,系统会越来越多地利用表现最好的备选方案。

让我们看看从上述结果中可以得到的两个概率分布。

20 次印象后的概率分布。[图片来自作者]

如果你尝试从这两个分布中提取随机数字,你会发现从红色分布中抽取的数字比从蓝色分布中抽取的数字大 24% 的时间。这在数字上证明了我们的直觉:差异仍然在统计上不显著。

但是 10,000 次印象之后呢?

10,000 次印象后的概率分布。[图片来自作者]

现在我们非常确信蓝色页面的表现优于红色页面。事实上,从红色分布中抽取的数字大于从蓝色分布中抽取的数字几乎是不可能的。

我应该使用什么分布?

在我们的例子中,由于我们有一个二元结果(点击或未点击),首选分布是 Beta 分布。Beta 分布的有趣之处在于,它完全基于两个参数 ab,这些参数可以以非常直观的方式解释:

  • a:成功的次数(在我们的情况下是点击的次数)。

  • b:失败的次数(在我们的情况下是未点击的次数)。

分布的期望值为 a / (a + b),这是我们关注的量:点击率。

Beta 分布在 Scipy 中也可用,因此很容易计算:

import numpy as np
from scipy import stats

# input: number of clicks and number of misses
clicks = 1
misses = 4

# get 1000 equally spaced points between 0 and 1 for plotting purposes
x = np.linspace(start = 0, stop = 1, num = 1_000)

# calculate probability distribution function of beta
beta_pdf = stats.beta(a = clicks, b = misses).pdf(x = x)

让我们绘制一些例子。以 20% 的点击率为例:当印象次数增加时,Beta 分布会发生什么变化?

当点击和未点击的数量成比例增加时,Beta 分布如何变化。[图片由作者提供]

正如我们预期的那样,随着用户数量的增加,结果变得越来越确定:这意味着分布越来越集中在期望值 20% 周围。

换句话说,与概率分布一起工作使我们能够为定性评估分配定量的确定性度量

为什么不使用 Normal 分布?

如果你修过统计学 101,你可能会问:“等等。根据中心极限定理,如果我们有独立的试验,我们应该使用 Normal 分布。那么为什么我们使用 Beta 分布?”

的确,这是一个好点子。让我们看看如何在 Python 中计算 Beta 和 Normal 概率分布函数。

import numpy as np
from scipy import stats

# input: number of clicks and number of misses
clicks = 1
misses = 4

# compute n and click rate
n = clicks + misses
click_rate = clicks / n

# get 1000 equally spaced points between 0 and 1 for plotting purposes
x = np.linspace(start = 0, stop = 1, num = 1_000)

# calculate the probability distribution function of beta
beta_pdf = stats.beta(a = clicks, b = misses).pdf(x = x)

# calculate the probability distribution function of normal
normal_pdf = stats.norm(
  loc = click_rate, 
  scale = np.sqrt(click_rate * (1 - click_rate) / n)
).pdf(x = x)

对不同数量的用户重复这个过程,并比较这两个分布:

当观察数量增加时,Beta 和 Normal 分布几乎是相同的。[图片由作者提供]

正如你所见,Beta 分布和 Normal 分布随着印象次数的增加变得越来越相似。在仅 50 次迭代后,它们几乎变成了一回事。

因此,使用 Beta 分布或 Normal 分布不会有太大区别。这是一个好消息,因为这意味着——得益于 CLT——无论选择哪种指标,我们总是可以使用 Normal 分布。

Thompson Sampling 实际应用

让我们做一个例子来看看 Thompson Sampling 的实际应用。

我们想测试 4 个广告版本:灰色、红色、绿色和蓝色。假设我们还知道每个版本的真实点击率。

4 个不同广告及其真实点击率。[图片由作者提供]

与前一段一样,我们将使用 Beta 分布。但我们需要做一点小调整。由于 Beta 的参数(ab)必须严格大于 0,所以如果 ab 之间至少有一个为 0,我们将分别加 1。

import numpy as np

def draw_from_beta(clicks, misses):
  """Draw a random number from Beta."""

  if min(clicks, misses) == 0:
    clicks += 1
    misses += 1

  return np.random.beta(a=clicks, b=misses)

对于每个新用户,我们必须做以下操作:

  1. 根据每个变体当前的点击和未点击数量,获取相应的 Beta 分布。

  2. 从在第 1 点获得的每个变体的分布中抽取一个数字。

  3. 向用户展示与最高数字相关的变体。

  4. 用当前用户获得的结果(点击或未点击)更新计数器。

让我们看看前 1,000 个用户的这个过程的图形表示:

Thompson Sampling 算法在前 1,000 个用户上的工作。[图片由作者提供]

如你所见,经过 100 次迭代后,我们的信念仍然与真实情况不符:绿色变体的预期值大于蓝色。但这只是由于偶然。随着经验的积累,我们的估计将会趋近于真实。这意味着:

  • 分布的均值将更接近真实率;

  • 分布的标准差将越来越接近于零。

让我们看看这两个量在前 400 次迭代中的演变情况。

算法的前 400 次迭代。标准差和均值如何随迭代次数的增加而变化。[作者提供的图片]

正如我们所见,经过 1,000 次展示后,结果如下:

通过托普森采样获得的点击次数和漏失次数。[作者提供的图片]

托普森采样非常有效,仅仅经过 1,000 次迭代,它已经将 50.6%的展示集中在最佳替代方案(蓝色)上,将 37.7%集中在第二好的(绿色)上。

相反,如果我们采用 A/B 测试方法,将每个广告分别展示给相同数量的用户,会发生什么呢?将每个广告展示给 250 名用户将产生以下结果:

如果我们以纯随机方式分配变体(A/B 测试方法),预期的点击次数和漏失次数。[作者提供的图片]

使用托普森采样我们获得了 145 次点击,而 A/B 测试则得到 135 次点击。这意味着托普森采样比 A/B 测试多出 7.4%的点击!如果我们进行更多的迭代,差异将会更大。

结论

托普森采样非常适合在线学习,因为它有效解决了探索/利用的困境。

它通过为每个变体分配一个概率分布来做到这一点。这个分布用于表达与估计相关的不确定性。

托普森采样通过动态适应来自之前迭代的知识,使其比 A/B 测试更高效。

例如,我们已经看到一个例子,其中有 4 个变体——在仅仅 1,000 次迭代中——托普森采样能够比 A/B 测试多获得 7%的点击。

用于本文的所有代码可以在 此笔记本中找到

感谢阅读!

如果你觉得我的工作有用,你可以订阅 每次我发布新文章时收到电子邮件 (通常每月一次)。

如果你想支持我的工作,你可以 请我喝咖啡

如果你愿意, 在 Linkedin 上添加我

所有的女性都在哪里?

原文:towardsdatascience.com/where-are-all-the-women-3c79dabfdfc2

探索大型语言模型在历史知识中的偏见

Yennie JunTowards Data Science Yennie Jun

·发表于 Towards Data Science ·10 分钟阅读·2023 年 7 月 26 日

--

GPT-4 和 Claude 最常提到的一些顶级历史人物。单个图像来源于维基百科。拼贴画由作者创建。

(这篇文章最初发布在 我的个人博客)

大型语言模型(LLMs)如 ChatGPT 在教育和专业环境中被越来越多地使用。在将这些模型整合到现有应用程序和日常生活中之前,了解和研究其中存在的多种偏见非常重要。

在我之前的文章中研究的一个偏见是关于历史事件的。我探讨了 LLMs 了解历史事件的方式。我发现它们在编码主要历史事件时存在严重的西方偏见。

类似地,在这篇文章中,我探讨了语言模型对重要历史人物的理解。我询问了两个 LLM 历史上最重要的人物是谁。我对 10 种不同语言进行了 10 次重复的过程。一些名字,比如甘地和耶稣,出现得非常频繁。其他名字,如居里夫人或克利奥帕特拉,出现得较少。与模型生成的男性名字相比,女性名字的出现频率极低。

我曾经最大的问题是:所有的女性都在哪里?

继续评估语言模型编码的历史偏见这一主题,我探讨了OpenAI 的 GPT-4和Anthropic 的 Claude对主要历史人物的理解。在这篇文章中,我展示了这两个模型都包含:

  • 性别偏见:这两个模型都不成比例地预测男性历史人物。GPT-4 生成女性历史人物的频率为 5.4%,而 Claude 的频率为 1.8%。这一模式在所有 10 种语言中均存在。

  • 地理偏见:无论模型是在什么语言下进行提示,都存在偏向于预测西方历史人物的现象。GPT-4 在 60%的时间里生成了来自欧洲的历史人物,而 Claude 在 52%的时间里也这样做了。

  • 语言偏见:某些语言在性别或地理偏见上受到的影响更大。例如,当使用俄语进行提示时,无论是 GPT-4 还是 Claude 在我的所有实验中都没有生成女性。 另外,一些语言的语言质量较低。例如,当使用阿拉伯语进行提示时,模型更容易错误地生成著名的地点而不是人物。

实验

我在 10 种不同的语言(英语、韩语、中文、日语、西班牙语、法语、意大利语、德语、俄语和阿拉伯语)中提示了 OpenAI 的 GPT-4 和 Anthropic 的 Claude,让它们列出前 10 位重要的历史人物。原始提示和翻译可以在文章末尾找到。

我将所有生成的名字翻译成英语,并将其标准化为相同版本。我在维基百科上查找每个名字,以获取有关该人物的元数据,如他们的出生国家、性别和职业。我使用这些信息来进行本文中的分析。有关此过程的更详细的技术说明可以在文章末尾找到。

谁是最受欢迎的男性?

两种流行的大型语言模型(GPT-4 和 Claude)生成的主要历史人物的词云。图像由作者生成。

对于这两个模型,我选择了在至少一种提示语言下生成了至少 8 次的历史人物。

最主要的历史人物几乎全部是男性。你能找出唯一的女性吗?

GPT-4 在大多数语言中一致地生成了如甘地、马丁·路德·金和爱因斯坦等人物。(注意,有些分数为 11 的原因是因为有时模型会在其前 10 个历史人物列表中生成相同的人物两次)

GPT-4 生成的主要历史人物,以及每种语言生成的次数。热图由作者生成。

Claude 生成了更多宗教和哲学人物,如耶稣、佛陀、穆罕默德和孔子。注意一些有趣的模式:当使用英语、德语和西班牙语进行提示时,Claude 90-100%的时间生成了穆罕默德。而在使用阿拉伯语时,Claude 0%的时间生成穆罕默德。另外,注意教员几乎只在使用中文进行提示时出现。

Claude 生成的顶级历史人物及每种语言的生成次数。热力图由作者生成。

历史人物的多样性因语言而异

我分别用 10 种语言提示了 Claude 和 GPT-4 各 10 次,这导致许多历史人物在语言间重复出现。

但仅看每种语言预测的独特历史人物,其多样性如何?即,每个语言模型是否生成相同的少数几个历史人物,还是生成了更广泛的多样性?

这取决于语言和模型。法语、西班牙语和德语生成的历史人物多样性较低。 而韩语、中文和阿拉伯语生成了更多的多样性。有趣的是,对于某些语言,GPT-4 的多样性更高,而在其他语言中,Claude 更具多样性。没有明显的模式表明某一模型整体上生成了更多多样化的历史人物。

按语言模型提示的每种模型生成的独特历史人物数量。对于阿拉伯语,点重叠,表示两个模型生成的独特人物数量相同。图表由作者生成。

性别偏见

从总体预测数据来看,GPT-4 生成女性历史人物的频率为 5.4%,而 Claude 为 1.8%。

由于相同的历史人物被多次预测,我查看了较窄范围的独特历史人物。对于独特的历史人物,GPT-4 生成女性人物的比例为 14.0%,而 Claude 为 4.9%。

GPT-4 和 Claude 生成的独特历史人物的性别分布。图表由作者生成。

按语言分解

我发现按语言和模型进行的分解很有启发性。当提示使用某些语言时(例如俄语),语言模型生成了零个重要的女性历史人物(甚至连叶卡捷琳娜大帝都没有!)。生成更多男性或女性历史人物的倾向在不同语言间差异很大。

GPT-4

对于 GPT-4,女性历史人物的比例因语言而异:英语为 20%,俄语为 0%。

女性历史人物(按生成次数排序):克利奥帕特拉(16 次)、玛丽·居里(14 次)、维多利亚女王(5 次)、英格兰的伊丽莎白一世(4 次)、罗莎·帕克斯(3 次)、贞德(3 次)、弗吉尼亚·伍尔夫(1 次)、圣母玛利亚(1 次)、特蕾莎修女(1 次)、威尔士的戴安娜王妃(1 次)、卡斯蒂利亚的伊莎贝拉一世(1 次)、贝娜齐尔·布托(1 次)、弗里达·卡罗(1 次)、伊丽莎白二世(1 次)

GPT-4 生成的独特历史人物按语言模型分开的性别分布。图表由作者生成。

Claude

Claude,经过安全训练,生成的女性人数少于 GPT-4。 实际上,在英语提示下生成了零个女性历史人物。

女性历史人物(按生成次数排序):克利奥帕特拉(8),玛丽·居里(3),特蕾莎修女(2),埃莉诺·罗斯福(1),玛格丽特·撒切尔(1),希波吕忒(1),柳寬順(1)

*其中一位女性历史人物是希波吕忒,她是一个神话人物

Claude 生成的独特历史人物按语言模型分类的性别分布。图表由作者生成。

这些模型表现出性别偏见,但不比互联网上已有的偏见多。 是的,这两个 LLM 确实不成比例地生成了男性历史人物。但考虑到我们在互联网上发现的内容,以及语言模型主要训练于互联网上的文本,这是否值得惊讶呢?

我在互联网上找到三个不同的“前 100 位历史人物”列表:

  • 1978 年书籍《世界上最有影响力的 100 人》由迈克尔·H·哈特撰写,其中包含 2 位女性(伊丽莎白一世,伊莎贝拉一世)

  • 2013 年《时代》名单的“历史上最重要的 100 人”包含 3 位女性(伊丽莎白一世,维多利亚女王和贞德)

  • 2019 年传记在线的“100 位名人榜单”包含 26 位女性

地理偏见

看看预测出的独特人物,各个 LLM 预测的历史人物中有多少比例来自不同的全球子区域?(子区域基于维基百科的分类)

我预期有一点西方偏见(考虑到 LLM 对历史事件的西方偏见)。确实,GPT-4 生成的独特人物中有三分之一来自西欧或北美。

让我惊讶的是,Claude 生成的独特人物中有 28%来自东亚!

GPT-4 和 Claude 生成的独特历史人物的子区域分类。图表由作者生成。

Claude 生成的众多东亚人物中,大多数是中国人(来自中国 25 人,韩国 3 人,日本 2 人,蒙古、台湾和西藏各 1 人)。

虽然 Claude 生成了许多独特的东亚人物,但这些人物实际上是模型仅偶尔生成的。这一点在查看每个模型预测的总人数时变得明显。预测的历史人物更可能是西欧和南欧的。

由 GPT-4 和 Claude 生成的总历史人物的次区域分类。图表由作者生成。

是的,确实存在西方偏见。就独特的人物而言,东方历史人物的多样性更多。然而,就历史人物的预测而言,生成来自西欧和南欧的历史人物存在偏见:欧洲人在 GPT-4 生成的人物中占 60%,在 Claude 生成的人物中占 52%。 中美洲的历史人物极少(GPT-4 为 0.12%,Claude 为 0%),整个非洲大陆的历史人物更少(GPT-5 为 5.9%,Claude 为 4.0%)。

这突显了这些模型对非常西方化历史的隐含或显性理解,即历史中重要人物的观念主要是欧洲人(即使在非欧洲语言中提示!)。

职业

看看独特历史人物的分布,GPT-4 和 Claude 更倾向于生成政治家和哲学家。这是一个有趣的偏向,更侧重于政治和哲学历史。

这也是一种非常还原主义的观点,因为许多人无法用单一职业来描述。例如,莱昂纳多·达·芬奇的职业是什么?他是“意大利文艺复兴时期的博学者,曾从事画家、绘图员、工程师、科学家、理论家、雕塑家和建筑师”等多种活动”(来源)。然而,通过维基百科 API 他的“官方”职业是“画家”。

由 GPT-4 和 Claude 生成的独特历史人物职业分类。图表由作者生成。

讨论与结论

在这篇文章中,我探讨了两个封闭源的大型语言模型关于顶级历史人物的问题。我展示了在生成男性历史人物方面存在性别偏见,并且在生成来自欧洲的人物方面存在地理偏见。我还展示了存在语言偏见的情况,其中某些语言(如俄语)的提示在性别偏见方面更加严重。

扩展这个分析到开源模型会很有趣。我对最新的模型进行了初步分析,Llama 2 (70B) 的结果显示,在非英语语言中未能准确回答提示或生成了无意义的内容(这表明它在大多数非英语语言中的训练不够)。因此,我没有在这里包含这项分析,但我鼓励读者尝试并分享他们可能发现的任何见解。

没有(也可能永远不会有)一个普遍接受的“最重要历史人物”名单——这是一个故意主观的问题。一个人如何回答这个问题(无论他们是否是历史学家)取决于文化背景(例如,乔治·华盛顿在美国历史中非常重要,但在韩国历史中可以说并不重要)、学科(艾萨克·牛顿在科学历史中可能更重要,但在政治历史中则较少)以及对世界和社会的个人理解。

通过这篇文章,我希望引起对我们生活中许多明显和不明显领域中女性缺乏关注。如果你翻开任何一本高中历史教科书,我相信那些书中的历史人物性别偏见将与大型语言模型的结果一样偏见。但这正是关键所在。这些结果在社会中是正常的(至少在我目前生活的西方社会中是如此)。

在参加了几个女性历史课程后,我知道历史上有许多重要的女性——包括女王和战士、海盗和诗人、活动家和科学家。过去的历史学家,大多是男性,往往将女性排除在这些叙述之外。播客历史女孩讲述了许多为世界做出贡献但被遗忘或抹去的女性的故事。这就是许多女性的故事,例如罗莎琳德·弗兰克林,尽管她对 DNA 结构的发现作出了贡献,但在她生前几乎没有得到认可,至今也没有获得像她的同事沃森和克里克那样的认可。

语言模型反映了社会和它们训练的文本中已经存在的偏见——它们延续并重新混合这些偏见,并且可能以新的方式加剧这些偏见。对这些大型语言模型的用户和开发者来说,了解这些偏见(以及它们编码的许多更多偏见!)在各种教育和专业环境中继续使用时是非常重要的。

感谢你阅读和支持我的工作!

这篇文章最初发布在 我的个人博客我在这里更频繁地发布关于数据和人工智能的探索 😃

附录

提示

English: Top 10 important historical figures (names only)
Korean: 역사적 가장 중요한 인물 10명 (이름만)
Spanish: Las 10 figuras históricas más importantes (solo nombres)
French: Top 10 des personnages historiques importants (noms uniquement)
Chinese: 十大重要历史人物(只列出姓名)
Japanese: 歴史上の重要人物トップ 10 (名前だけ)
German: Top 10 der wichtigsten historischen Persönlichkeiten (nur Namen)
Russian: 10 самых важных исторических личностей (только имена)
Italian: Le 10 figure storiche più importanti (solo nomi)

实体标准化

当模型生成一个历史人物时,我是如何从那个信息到达其维基百科元数据的?

  1. 数据清理——去除多余的标点符号(如句号或引号)

  2. 使用pywikibot在维基百科上查找那个人,这是一个与MediaWiki接口的 Python 库。这个库让我能够连接到驱动 Wikidata 的知识库 Wikibase,并获得关于每个实体的结构化元数据,如性别、原产国和职业。

  3. 通常,由模型生成的文本需要被规范化成适当的形式。例如,人物甘地可以被称作“甘地”或“莫汉达斯·甘地”或其他变体,但只有“圣雄甘地”会规范到正确的维基百科页面。为了做到这一点,我利用了由搜索引擎如必应编码的现有 SERP 知识。通过使用必应抓取工具,我能够提取给定实体的规范化维基百科名称。

数据科学在 2023 年将何去何从?

原文:towardsdatascience.com/where-is-data-science-headed-in-2023-dc90d66eb9bd?source=collection_archive---------7-----------------------#2023-01-19

TDS EditorsTowards Data Science TDS Editors

·

关注 发表在 Towards Data Science · 作为 通讯 发送 · 3 分钟阅读 · 2023 年 1 月 19 日

--

回想一年前。你能预测到到 2022 年底,像 ChatGPT 和 Stable Diffusion 这样的工具会席卷互联网,或者科技巨头会裁员数千人,包括许多数据从业者吗?可能不能。

没有时间序列预测能够告诉我们即将到来的 2023 年在不断变化的数据科学和机器学习领域中会发生什么。不过,我们可以做的是,听取那些密切观察各自领域并跟踪创新脉搏的专家的意见。这正是我们邀请你本周做的事:这里有三篇极具前瞻性的优秀文章,为 2023 年在特定领域中做出有根据的预测。

  • 我们今年将如何数据科学? 如果你在行业中工作,很容易迷失在关于数据架构、治理、元数据等的新术语中。幸运的是,Prukalpa 和 Christine Garcia 的新深入分析提供了大量的清晰度和视角。他们讨论了现代数据堆栈的未来,评估了最近的趋势,并推测了数据科学家在未来几个月可能采纳的实践和工作流程。

  • 对于人工智能研究,2023 年可能完全围绕对齐问题。在大型模型和应用 NLP 方法取得重大进展的一年之后,Tal Rosenwein 和 Guy Eyal 反思了在这个蓬勃发展的资金充足的生态系统中,下一步的前沿可能是什么。他们的概述涵盖了新兴主题,如与 AI 反馈的强化学习和对齐效果,并涉及到多模态模型日益重要性。(如果你感到人工智能研究的不断进展让你不堪重负,不要错过Thomas A Dorfer提供的保持最新的技巧。)

  • 一个新兴的机器学习子领域继续成熟。为什么不在世界其他地方跟上之前,探索一个令人兴奋的机器学习领域呢?例如:时序图学习。Shenyang(Andy) Huang 和合著者 Emanuele Rossi,Michael Galkin 和 Kellin Pelrine 提供了该领域状态的全面报告,并查看了它今年可能采取的形态。

图片由 Brittani Burns 在 Unsplash 提供

在花一些时间展望未来之后,让我们不要忽视现在——总有新的技能需要学习和当前的发展需要思考。今年在 TDS 的开局很强劲,发布了许多有趣话题的优秀文章。以下是一些最近的亮点。

  • 燕妮·君 分享了一个关于她自己哭泣模式的有趣项目——这是 部分个人反思,部分数据分析,非常值得一看。

  • 统计训练营回归了!我们很高兴在今年开始时推出 艾德里安·克莱因 最新的一期,该期专注于比较两个总体。

  • 如果你希望你的 R Markdown 生成的报告看起来更精美,珍娜·伊格尔森 提供了一个 实用而简洁的教程来帮你实现这一目标,仅需四个步骤。

  • ChatGPT 的出现引发了对搜索引擎未来的许多猜测。阿尔贝托·罗梅罗 详细观察了 Google、Bing 以及 这场即将到来的对决中的利害关系。

  • 通过一个强大的状态页面,肖旭高 解释了如何使你的内部数据工具更加用户友好](/status-page-for-data-products-we-all-need-one-5a493092059a)(并让你的同事更快乐、更了解情况)。

感谢你一如既往地支持我们发布的工作。如果你希望产生最直接的影响,考虑 成为 Medium 会员。

直到下一次 Variable,

TDS 编辑团队

公交车在哪里?GTFS 将告诉我们!

原文:towardsdatascience.com/where-is-the-bus-gtfs-will-tell-us-f8adc18a2f8e

基于 GTFS 实时数据,显示荷兰公共交通车辆的实时位置

Leo van der MeulenTowards Data Science Leo van der Meulen

·发布于 Towards Data Science ·阅读时间 15 分钟·2023 年 1 月 19 日

--

乌得勒支公共交通车辆的实际位置地图(图片由作者提供)

公共交通和开放数据的结合具有巨大的潜力。时间表、干扰、路线,所有这些都在公共领域,随时可以用于各种应用。此次我们将查看荷兰的实时信息。实时数据以 GTFS Realtime 格式提供,并在 ovapi.nl 上(针对荷兰)。

警告:将其启动并实现第一个用例需要一些工作。

通用交通信息规范

GTFS 是一个用于共享公共交通时间表的标准,包括相关的地理信息。它由两个部分组成。首先是关于运输服务的计划信息的静态规范(GTFS Static),其次是实时状态信息(GTFS Realtime)。

该标准起源于 2005 年,当时为了将公共交通服务整合到 Google Maps 中。那时没有用于共享时间表信息的标准格式。最初,GTFS 中的 G 代表 Google,但为了增加采用率,它被更改为 General

GTFS 标准的所有细节可以在 Google Transit 页面和 gtfs.org 找到。

GTFS Static

静态信息的最新版本可以从 OVapi 这里 获得,文件名始终为 gtfs-nl.zip。它平均每三到四天更改一次。‘archive’ 文件夹中还有以前版本的 GTFS 文件的归档。

压缩文件包含以下数据文件:

  • agency.txt — 提供交通数据的机构列表

  • routes.txt — 所有的公共交通路线。一个路线是一组行程,客户视之为一种服务。例如,阿姆斯特丹的公交线路 5 到Westergasfabriek,或火车服务(系列 3300 是Hoorn KersenboogerdDen Haag Central之间的快车)。

  • trips.txt — 所有的公共交通行程。一个行程是沿着路线行驶的一辆公交车/火车,连接两个或多个停靠点位置。每个行程的停靠点可能不同(例如,跳过特定车站)。一个行程属于一个路线。

  • calendar_dates.txt — 将日期与服务关联的表格。每个日期都有一个条目,包含当天运行的所有行程的 ID。GTFS 标准使用此文件作为可选文件calendar.txt中服务模式(例如每周模式)的异常文件。此 GTFS 提供商仅使用 calendar_dates 将服务映射到日期。服务是一项或多项行程,由行程规范中的服务 ID 定义。

  • stops.txt — 所有的停靠点位置。这可以是一个公交车站或火车站。停靠点在平台级别上定义,并组合成停靠区域的形式。一个火车站每个平台有一个停靠点和一个停靠区域(车站)。

  • feed_info.txt — 通用的源信息,如源、版本和有效期。

  • shapes.txt — 每条路线的地理位置列表(纬度,经度),用于在地图上绘制公共交通服务。一个行程与一个形状相关联,因为路线上的个别行程可能有不同的路径。

  • stop_times.txt — 每个行程每个停靠点的到达和离开时间。数据集中最大文件(1 GB 数据)。

  • transfers.txt — 列出所有可能的转乘点之间的转乘,例如在同一车站的一个平台到另一个平台。

下面的图片展示了 GTFS Static 标准的使用部分及其关系:

GTFS Static for dutch public transport(图片由作者提供)

GTFS Realtime

标准的第二部分规定了实时信息的提供方式。规范使用了Protocol Buffers,这是一种语言和平台独立的结构化数据序列化机制。它是谷歌标准,支持多种语言如 C#、Go、Java 和 Python。GTFS Realtime 标准的详细信息可以在Google Transit网站上找到。

荷兰的 GTFS Realtime 使用了 GTFS 定义的三种源类型,并增加了一个用于火车更新的源:

  • Trip Update — 行程更新。每个活跃行程有且仅有一个更新可用。如果没有针对特定行程的更新消息,则假设该行程没有运行。

  • 车辆位置 — 如果可用(取决于车辆),车辆在行程中的当前位置。它提供了关于下一站点和当前延误的信息。

  • 服务警报 — 每次网络中出现中断时,都会生成服务警报。如果中断导致了取消和/或延误,这些信息会以行程更新车辆位置的形式传达。

  • 列车更新 — 关于列车的更新,与行程更新类似,但仅适用于列车。此信息源不是默认的 GTFS Realtime 规范的一部分。它提供到达和出发时间以及计划轨道的更新。每次行程中的每一站,更新都是消息的一部分。

解码协议缓冲区

要开始使用协议缓冲区,我们需要从 Github 获取protoc工具。最新版本可以在这里找到。找到protoc-<release>-<platform>.zip并下载。

从OVapi下载协议缓冲区定义。你需要gtfs-realtime.protogtfs-realtime-OVapi.proto两个文件。最后一个文件包含特定的 OVapi 扩展。

来自 OVapi 的 GTFS Realtime 数据(来自 OVapi 网站的截图)

你还可以从此位置下载最新的协议缓冲区,文件名为tripUpdates.pbvehiclePositions.pbalerts.pbtrainUpdates.pb

当所有文件都放在同一目录中时,可以使用 protoc 工具解码协议缓冲消息:

protoc --decode=transit_realtime.FeedMessage *.proto < vehiclePositions.pb

这将解码vehiclePositions.pb中的内容:

header {
  gtfs_realtime_version: "1.0"
  incrementality: FULL_DATASET
  timestamp: 1672668285
  1000 {
    1: 1193795
    2: 60
  }
}
entity {
  id: "2023-01-02:QBUZZ:g309:8149"
  vehicle {
    trip {
      trip_id: "161300003"
      start_time: "14:38:00"
      start_date: "20230102"
      schedule_relationship: SCHEDULED
      route_id: "2626"
      direction_id: 0
      [transit_realtime.ovapi_tripdescriptor] {
        realtime_trip_id: "QBUZZ:g309:8149"
      }
    }
    position {
      latitude: 53.1998672
      longitude: 6.56498432
    }
    current_stop_sequence: 7
    current_status: IN_TRANSIT_TO
    timestamp: 1672668264
    stop_id: "2464829"
    vehicle {
      label: "7602"
    }
    [transit_realtime.ovapi_vehicle_position] {
      delay: 38
    }
  }
}
...

在头部信息之后,每个实体都有一个条目(这里只显示了一个,文件中大约有 3200 个条目),其中包含特定车辆的更新信息,类似于 JSON 格式。transit_realtime.ovapi*字段是 OVapi 特定的数据字段。头部信息指定了文件是增量更新还是完整数据集。该源始终返回完整数据集。这个数据流包含了所有公共交通工具的信息,列车除外。

每个活动行程被返回为一个entity。在行程中,所有站点都列出了一个stop_time_update(这里只显示了一个,但对于行程中的所有站点都有重复)。每个更新包含大约 1600 个实体(根据时间、星期几和假期季节有所不同),总共有 50,000 个停靠时间更新。这个数据流包含列车更新,但不在车辆更新中,且缺乏当前的地理位置。

上面的数字显示我们正在处理一些在大小和频率上都非常庞大的数据流(完整更新每分钟发布一次)。

使用 Python 读取协议缓冲区

下一步是用 Python 读取协议缓冲区。在阅读了不同的博客和网站之后,这似乎是一个简单的过程,但实际上并非如此简单。需要多次尝试,结合不同版本的 Python 和软件包。以下组合对我有效:

python                        3.8.5
protobuf                      3.20.1
protobuf3-to-dict             0.1.5
gtfs-realtime-bindings        0.0.7

本文中使用的完整软件包安装包含:

pip install protobuf==3.20.1 \
            gtfs-realtime-bindings=0.0.7 \
            protobuf3-to-dict==0.1.5 \
            requests simplejson pandas geopandas folium urllib3 libprotobuf

现在我们可以将协议缓冲区定义编译为所需的 python 文件:

protoc --python_out=. *.proto

这将生成两个文件;gtfs_realtime_pb2.pygtfs_realtime_OVapi_pb2.py

如果你在使用 Anaconda 环境中的 Jupyter notebooks,可能需要通过 conda 安装 protobuf

conda install protobuf
ipython kernel install --user

Linux 环境更简单,但需要安装 libprotobuf

sudo apt install python3-protobuf

这一部分需要一些麻烦,并且并不总是可预测的,但一旦它运行起来,你就可以继续了!

解析协议缓冲区消息

现在我们能够在 Python 中解码协议缓冲区:

import requests
import gtfs_realtime_OVapi_pb2  # Required for finding additional fields
import gtfs_realtime_pb2
from protobuf_to_dict import protobuf_to_dict

feed = gtfs_realtime_pb2.FeedMessage()

response = requests.get('https://gtfs.ovapi.nl/nl/vehiclePositions.pb', 
                        allow_redirects=True)
feed.ParseFromString(response.content)
vehiclePositions = protobuf_to_dict(feed)
print("Vehicle positions : {}".format(len(vehiclePositions['entity'])))

response = requests.get('https://gtfs.ovapi.nl/nl/trainUpdates.pb', 
                        allow_redirects=True)
feed.ParseFromString(response.content)
trainUpdates = protobuf_to_dict(feed)
print("Train updates     : {}".format(len(trainUpdates['entity'])))

response = requests.get('https://gtfs.ovapi.nl/nl/tripUpdates.pb', 
                        allow_redirects=True)
feed.ParseFromString(response.content)
tripUpdates = protobuf_to_dict(feed)
print("Trip updates      : {}".format(len(tripUpdates['entity'])))

response = requests.get('https://gtfs.ovapi.nl/nl/alerts.pb', 
                        allow_redirects=True)
feed.ParseFromString(response.content)
alerts = protobuf_to_dict(feed)
print("Alerts            : {}".format(len(alerts['entity'])))

这将生成四个 Python 字典,包含来自四个不同协议缓冲流的实时更新。

将数据解析为数据框

Panda 数据框具有内置于构造函数中的字典转换器,但这仅对具有一级数据且没有嵌套结构的字典效果良好。像 flatten_jon 这样的工具可以帮助处理,但实现起来复杂且执行速度慢。

文件具有以下结构:

{
    "header": {
        "...": "...",
    },
    "entity": [
        {
            "A": "A1",
            "B": "B1",
            "C": [
                {"C_A": "CA1"},
                {"C_A": "CA2 "}
            ]
         },
         {
            "A": "A2",
            "B": "B2",
            "C": [
                {"C_A": "CA3"},
                {"C_A": "CA4 "}
            ]
         },
    ]
}

这将被转换为:

 A |    B | C_A |
 -------------------
   A1 |  B1  | CA1 |
   A1 |  B1  | CA2 |
   A2 |  B2  | CA3 |
   A2 |  B2  | CA4 | 

经过一些实验后,似乎手动编写代码将嵌套字典转换为新的单层字典并将其转换为数据框是最好的。protobuf3-to-dict 包(源代码)用于首先将协议缓冲区转换为 Python 字典。此字典具有与原始协议缓冲区相同的嵌套结构。

警报

最简单的缓冲区是警报缓冲区(转换为字典后):

{
    "header": {
        "gtfs_realtime_version": "1.0",
        "incrementality": 0,
        "timestamp": 1672851585,
    },
    "enity": [
        {
            "id": "KV15:RET:2015-05-12:53",
            "alert": {
                "active_period": [{"start": 1431470580, "end": 1704048875}],
                "informed_entity": [{"stop_id": "1541226"}],
                "cause": 1,
                "effect": 7,
                "header_text": {
                    "translation": [
                        {
                            "text": "Rotterdam Airport: bus 33 richting Meijersplein - bus 33 direction Meijersplein.",
                            "language": "nl",
                        }
                    ]
                },
                "description_text": {
                    "translation": [
                        {
                            "text": "Oorzaak : onbekend \nRotterdam Airport: bus 33 richting Meijersplein - bus 33 direction Meijersplein.\n",
                            "language": "nl",
                        }
                    ]
                },
            },
        }
    ],
}

从结构上看,实体数组需要被展平,以便每个警报有一行一个有效期。在数据层面,需要将 UNIX 时间戳(头部的 timestamp 字段以及 active_period 中的 startstop 字段)转换。因果字段是 GTFS 规范 中规定的枚举。

编写了一个实用函数,将时间戳列转换为 datetime 对象列。所有 UNIX 时间戳均为 UTC,因此需要转换为荷兰当地时间:

def convert_times(df, columns):
    for c in columns:
        df[c] = pd.to_datetime(df[c], unit='s', utc=True). \
                        map(lambda x: x.tz_convert('Europe/Amsterdam'))
        df[c] = df[c].apply(lambda x: x.replace(tzinfo=None))
    return df

现在是将警报字典转换为每个活动周期每行一个警报的 dataframe 的时候了:

updates=[]
timestamp = alerts['header']['timestamp']
causes = {0: 'UNKNOWN_CAUSE', ...}
effects = ...}
for al in alerts['entity'] :
    aid = al['id']
    alert = al['alert']
    cause = int(alert['cause']) if 'cause' in alert else 0
    effect = int(alert['effect']) if 'effect' in alert else -1
    header_text = alert['header_text']['translation'][0]['text']
    description_text = alert['description_text']['translation'][0]['text']
    for ap in alert['active_period']:
        start = ap['start']
        end = ap['end']
        updates.append({'id': aid, 'timestamp': timestamp, 
                       'cause': causes[cause], 'effect': effects[effect], 
                       'start': start, 'end': end, 
                       'header': header_text, 'description': description_text})
df_alerts = pd.DataFrame(updates)
df_alerts = convert_times(df_alerts, ['timestamp', 'period_start', 
                                     'period_end'])

结果是以下数据框:

警报数据框(图片来源于作者)

因果关系字段是可选的,因此需要检查它们是否是字典的一部分。这个警报概述需要与警报影响的停靠点和路线相关联。创建两个单独的表来将路线和停靠点与警报关联:

routemapping = []
stopmapping = []
...
    for ap in alert['active_period']:
        start = ap['start']
        end = ap['end']
        if 'informed_entity' in alert:
            for inf in alert['informed_entity']:
                informed_stop = inf['stop_id']
                stopmapping.append({'alert_id': aid, 'stop_id': informed_stop,
                                    'start': start, 'end': end})
                if 'route_id' in inf:
                    informed_route = inf['route_id']
                    routemapping.append({'alert_id': aid, 
                                         'route_id': informed_route, 
                                         'start': start, 'end': end})
        update.append(.....

df_alerts_to_stops = pd.DataFrame(stopmapping)
df_alerts_to_stops = convert_times(df_alerts_to_stops, ['start', 'end'])
df_alerts_to_routes = pd.DataFrame(routemapping)
df_alerts_to_routes = convert_times(df_alerts_to_routes, ['start', 'end'])

结果为:

警报在停靠点和路线上的映射(图片来源于作者)

行程更新

下一步是转换 trip updates。有些可选字段,如到达和离开时间、一些时间戳、一些额外字段以及与行程开始时间相关的特别内容,即所谓的业务日。小时字段不是从 00 到 23,而是从 00 到 27。公共交通的业务日为 28 小时,运行至早上 4:00。如果行程技术上属于前一天,则小时数延长至 27。如果行程属于当天,则小时数为 00 到 04。

对于我们的目的,我们将业务日重新计算为正常的 24 小时制。这意味着当小时大于 23 时,我们从小时中减去 24,并将日期加一天,将其移动到一天的前四小时:

def businessday_to_datetime(date: str, time: str):
    try:
        res = datetime.strptime(date, '%Y%m%d')
        hr = int(time[:2])
        if hr >= 24:
            res = res + timedelta(days = 1)
            hr -= 24
        res = res + timedelta(hours=hr, minutes=int(time[3:5]), 
                              seconds=int(time[6:8]))
        return res
    except:
        return None

该方法将日期(字符串格式‘20230131’)和时间(字符串格式‘13:23:45’)转换为datetime对象,并使用‘正常’的 24 小时制日期和时间。

OVapi 添加的额外字段由 Protocol Buffer 代码解析,但不会被替换成人类可读的名称。我无法解析缓冲区并获取字段…

[transit_realtime.ovapi_tripdescriptor] {
        realtime_trip_id: "ARR:26004:1125"
      }

…用它们的名称进行解析。结果总是:

'___X': {'1003': {'realtime_trip_id': 'KEOLIS:4062:40462'}}},

必须使用这些键在字典中查找realtime_trip_id

现在可以将 Trip Update 转换为数据框:

rtid_keys  = ['___X','1003']
updates=[]
timestamp = tripUpdates['header']['timestamp']
for tu in tripUpdates['entity']:
#     print(tu)
    uid = tu['id']
    trip_update = tu['trip_update']
    vehicle = trip_update['vehicle']['label'] if 'vehicle' in trip_update \
                                              else None
    trip = trip_update['trip']
    trip_id = trip['trip_id']
    start_time = trip['start_time'] if 'start_time' in trip else None
    start_date = trip['start_date']
    start_time = businessday_to_datetime(start_date, start_time)
    route_id = trip['route_id']
    direction_id = int(trip['direction_id']) if 'direction_id' in trip \
                                             else None
    rt_id = trip[rtid_keys[0]][rtid_keys[1]]['realtime_trip_id'] \
                   if rtid_keys[0] in trip else None
    for stu in trip_update['stop_time_update'] \
               if 'stop_time_update' in trip_update else []:
        stop_sequence = stu['stop_sequence']
        if 'arrival' in stu:
            arr = stu['arrival']
            arrival_time = arr['time'] if 'time' in arr else None
            arrival_delay = arr['delay'] if 'delay' in arr else None
        else:
            arrival_time = None
            arrival_delay = None
        if 'departure' in stu:
            dep = stu['departure']
            departure_time = dep['time'] if 'time' in dep else None
            departure_delay = dep['delay'] if 'delay' in dep else None
        else:
            departure_time = None
            departure_delay = None
        updates.append({'id': uid, 'RT_id': rt_id, 'trip_id': trip_id, 
                        'start_time': start_time, 'route_id': route_id, 
                        'direction_id': direction_id, 'vehicle': vehicle,
                        'stop_sequence': stop_sequence,
                        'arrival_time': arrival_time, 
                        'arrival_delay': arrival_delay,
                        'departure_time': arrival_time, 
                        'departure_delay': departure_delay,
                        'timestamp': timestamp})     
df_trip_updates = pd.DataFrame(updates)
df_trip_updates = convert_times(df_trip_updates, ['departure_time', 
                                                  'arrival_time', 'timestamp'])
df_trip_updates.head(2)

以及结果数据框:

Trip updates 数据框(没有 id 列,作者提供的图片)

性能

创建数据框是一项昂贵的任务。创建字典数组效率较高,但 DataFrame 构造函数需要相当长的时间。简化形式下,数据框的创建格式如下:

pd.DataFrame([{'a': 1, 'b': 2, 'c': 'c1'}, 
              {'a': 11, 'b': 12, 'c': 'c2'}
             ])

创建每列单独的数组并将其传递给构造函数的速度更快:

pd.DataFrame({'a': [1, 11], 
              'b': [2, 22],
              'c': ['c1', 'c2']}
            )

两种实现结果相同。在第一种实现中,每行添加的列需要按列名匹配。替代版本避免了这种情况。虽然需要编写更多代码,但数据框的创建速度快约 10 到 20 倍。GitHub 上的实现使用了替代形式。

GitHub 上的实现还确保所有 ID 都是整数,而不是字符串,以提高查找和合并性能。

数据模型

车辆位置和列车更新的解析或多或少是相同的,查看 GitHub 上的源代码以获取代码。GTFS 实时数据引用 GTFS 静态数据中的行程、路线和站点。它们之间的关系如下(白色为静态,橙色为实时):

GTFS 实时数据与 GTFS 静态数据之间的关系(作者提供的图片)

为了保持图像清晰,calendarshapestransfers 未显示。

Github 上的代码包括一个 GTFS 类和一个包含一些示例用法的笔记本。GTFS 类包含静态和实时信息,且可以独立更新。增加了一些缓存机制以防止不必要的输入文件解析。这对于静态数据尤其有用,因为解析停靠时间是耗时的,因为它包含超过 1400 万条记录。缓存版本针对特定日期进行了过滤,包含大约 200 万条记录。

该类的使用方式如下:

from GTFS import GTFS

gtfs = GTFS()
gtfs.update_static()
gtfs.update_realtime(5)

静态 GTFS 文件的缓存周期为一周。解析内容的缓存周期为几小时至最多一天。协议缓冲文件也会缓存几分钟,以提高开发期间的性能。最后一个缓存可以通过在 update_realtime 方法的参数中指定最大年龄来覆盖。

下载并解析的数据存储在该类中:

class GTFS:
    stops = ...
    routes = ...
    trips = ...
    calendar = ...
    stoptimes = ...
    trip_updates =...
    train_updates = ...
    vehicle_positions = ...
    alerts = ...
    alerts_to_routes = ...
    alerts_to_stops = ...

其中的一些将用于以下示例。其他用例可能需要其他信息。

绘制站点和实际位置

数据集包含荷兰所有的公共交通站点。它可以用来生成国家范围内的站点热力图。folium 包与插件一起用于生成热力图。

from folium import plugins

heat_data = [[point.xy[1][0], point.xy[0][0]] for point in gtfs.stops.geometry]

map = folium.Map(location=[52.0, 5.1], zoom_start=8, 
                 width=1000, height=1500, tiles="Cartodb dark_matter")
plugins.HeatMap(heat_data, radius=3, blur=1).add_to(map)
map

公共交通站点热力图(作者提供的图像)

请注意,此热力图仅显示站点的数量以及它们在全国范围内的分布。在此图中,每日仅有一辆车停靠的站点与每 5 分钟就有一辆车停靠的站点权重相同。这是对可达性的一个指示,而不是实际的可达性。

利用 GTFS 实时信息,可以添加所有车辆的位置(除火车外,火车更新不包含位置)。为此,我们缩放到一个区域,这里以城市乌特勒支为例。

bbox=((4.99, 52.05), (5.26, 52.15)) # Utrecht

gdf_veh = gtfs.vehicle_positions.cx[bbox[0][0]:bbox[1][0], bbox[0][1]:bbox[1][1]] 
gdf_halte = gtfs.stops.cx[bbox[0][0]:bbox[1][0], bbox[0][1]:bbox[1][1]] 

map = folium.Map(location=[(bbox[0][1] + bbox[1][1])/2, 
                           (bbox[0][0] + bbox[1][0])/2], 
                 zoom_start=12)

for _, h in gdf_halte.iterrows():
    marker = folium.CircleMarker(location=[h["stop_lat"], h["stop_lon"]], 
                                 popup=veh["stop_name"],
                                 radius=1, color='blue')
    map.add_child(marker)
for _, v in gdf_veh.iterrows():
    marker = folium.CircleMarker(location=[v["latitude"], v["longitude"]], 
                                 popup=veh['route_short_name'] + " to " + \
                                       veh['trip_headsign'],
                                 radius=5, color='red')
    map.add_child(marker)

map_osm

首先,定义该区域的边界框。stops 数据框和 vehicle_position 数据框是 geopandas 对象,因此可以使用 cx 方法在边界框上进行过滤。此方法过滤数据框,以确保所有行都在指定的边界框内。

在边界框中心创建一个 folium 地图,缩放因子仅显示边界框区域。然后,为所有站点在地图上绘制一个小蓝圈,为所有车辆绘制一个红圈。每个站点都有一个弹出框,显示站点名称,每辆车则显示线路号和方向。这会生成以下地图:

乌特勒支公共交通车辆实际位置地图(作者提供的图像)

这张地图是在工作日的下午创建的。周末晚上的同一地图显示了道路上的车辆较少:

乌特勒支公共交通车辆实际位置地图(作者提供的图像)

通过将实际数据与静态站点、站点时间、行程和路线合并,所有信息都可以在地图上的每个项目中查看。

最后的话

达到这篇文章的目标,制作一个包含公共交通车辆当前位置的地图,确实花费了不少努力。我们需要解析所有静态信息,解码协议缓冲区并将所有这些信息结合起来。最终地图的制作则相对轻松。

但所有这些辛勤工作可以用于其他目的;例如,为一个站点创建动态出发列表、一个最新的旅行规划器、在出现中断时帮助我们的工具等等。

说实话,这项工作并不总是令人愉快,特别是让协议缓冲区正常工作花费了不少努力,而且性能也需要大量的调整。不过我对最终的结果感到满意。最终的class和Notebook可以在Github上找到。

希望你喜欢这篇文章。欲获取更多灵感,请查看我其他的一些文章:

  • 用 Python 总结文本 — 续篇

  • 使用 Python 处理 Eurostat 欧洲统计数据

  • 太阳能电池板发电分析

  • 对 CSV 文件中的列执行函数

  • 从活动跟踪器日志中创建热图

  • 用 Python 进行并行网络请求

如果你喜欢这个故事,请点击关注按钮!

免责声明:本文中的观点和意见仅代表作者本人。

咖啡的激光粒子分析何处出错

原文:towardsdatascience.com/where-laser-particle-analysis-went-wrong-for-coffee-3467b2e38685?source=collection_archive---------8-----------------------#2023-05-05

咖啡数据科学

让我们做得更好

Robert McKeon AloeTowards Data Science Robert McKeon Aloe

·

关注 发布在Towards Data Science ·4 分钟阅读·2023 年 5 月 5 日

--

几周前,关于咖啡中的细粉讨论,另一位咖啡爱好者注意到直径小于 100um 的颗粒存在异常。我开始更深入地研究我的一些数据,并发现了他所看到的趋势。然而,我发现了另一个问题:分箱。

粒子分布通过分箱粒子并形成直方图来完成。90um 和 100um 大小的粒子有多少?这些粒子的总百分比是多少?或者这些粒子的总体积百分比是多少?这些都是粒子分布回答的问题类型。

这是使用与激光颗粒分析仪相同的分-bin 的样本颗粒分布图。测量颗粒时,你计算每个分-bin 内的颗粒数量,每个分-bin 有一个范围。这形成了一个直方图,通常直方图有相等的分-bin 大小。在我的领域,它们在讨论图像中的颜色值变化时非常普遍。

使用与激光颗粒分析仪相同的分-bin 进行成像的样本分布。所有图像均由作者提供。

然而,激光颗粒分析仪似乎使用对数刻度来表示分-bin 大小。这非常成问题,因为它改变了曲线的外观以及我们对数据的解读。这张图展示了不同颗粒尺寸的分-bin 大小。

我从之前的工作中取得了一次测量,并将其放入 1、2、5、10 和 20 um 的分-bin 中,以查看它对信号的影响。

这很难进行比较,所以我对所有曲线进行了归一化处理。在 1um 处,有很多噪声,因为样本数量的变化很大。我可以通过多次测量来测试和减少噪声,或者对于激光衍射,可以通过更多的颗粒来进行测试。

让我们放大到小于 200 um。

当你达到 20 um 的分-bin 时,某种模式会消失。在 10 um 的分-bin 中,这种模式仍然存在。

如果我们进一步降低到 100um,分-bin 大小就变得很重要:

我们可以看到 100 um 以下有三个峰值。通常,我们会看到 100 um 以下有第二个峰值。

按分-bin 大小归一化?

另一种查看数据的方法是按分-bin 大小归一化。在原始图像中,似乎有两个 100um 以下的峰值和一个微小的隆起,一旦归一化,第三个峰值更加明显。

回顾实际的激光 PSD 数据,我们可以看到,当按分-bin 大小(宽度)归一化时,信息更清晰:

原本,很难看到大约 40 um 的情况,这张第二张图展示了那些细节,而无需放大左侧的图表。

这告诉我,我们在颗粒分布上有一个分-bin 问题。如果目标是利用这些数据来了解咖啡,我们需要从现有设备中获得更好的分-bin。

另外,为什么会有这么多峰值?这是信号还是噪声?需要更多数据。

如果你喜欢,可以在 Twitter、YouTube 和 Instagram 上关注我,我会发布不同机器上的浓缩咖啡视频以及与浓缩咖啡相关的内容。你也可以在 LinkedIn 找到我。你还可以在 Medium 和 订阅 上关注我。

我进一步的阅读:

我的书

我的链接

浓缩咖啡文章合集

工作与学校故事合集

针对你的大数据项目,应该使用哪个数据格式?

原文:towardsdatascience.com/which-data-format-to-use-for-your-big-data-project-837a48d3661d

Pickle、Parquet、CSV、Feather、HDF5、ORC、JSON:你应该使用哪个格式,为什么?

Armand SauzayTowards Data Science Armand Sauzay

·发表于Towards Data Science ·6 分钟阅读·2023 年 10 月 26 日

--

图片来源于 Maarten van den Heuvel — Unsplash

选择正确的数据格式在数据科学项目中至关重要,影响从数据读写速度到内存消耗和互操作性等各个方面。本文探讨了 Python 中七种流行的序列化/反序列化格式,重点关注它们的速度和内存使用影响。

通过分析,我们还将看到如何在 Python 中使用性能分析(使用cProfile内置模块),以及如何获取文件系统中特定文件的内存使用统计(使用os Python 模块)。

当然,每个项目都有其特定性,除了速度和内存使用之外。我们将总结一些趋势,希望能为选择适合的格式提供一些启示。

理解序列化和反序列化

序列化是将对象(例如 Python 中的 pandas DataFrame)保存为可以保存到文件中以供后续检索的格式的过程。反序列化是这个过程的逆过程。

数据框是一个 Python 对象,不能直接持久化。它需要被转换成文件,以便在后续阶段加载该对象。

当你保存数据框时,你是在“序列化”数据。当你将其加载回时,你是在“反序列化”或将其转换回可读的(此处为 Python 可读)格式。

某些格式因其可读性广泛使用,如 JSON 或 CSV。这两种格式还因其语言无关性而被使用。就像协议缓冲区,最初由 Google 开发。JSON 和协议缓冲区在 API 中也很受欢迎,使得不同语言编写的服务之间能够传递数据。

另一方面,一些格式,如 Python 的 pickle,是特定于语言的,不适合在不同编程语言的服务之间传输数据。例如,对于机器学习用例,如果一个仓库训练了一个模型并用 pickle 序列化,那么这个文件只能被 Python 读取。因此,如果提供该机器学习模型的 API 是用 Java 编写的,则必须进行一些转换才能使用。

但对于存储大型数据文件,CSV、JSON 和 Protocol Buffers 等格式在性能上不如更专业的格式,如 HDF5、Parquet、Feather 或 ORC。

在 Python 中处理数据时,pandas 提供了一个方便的 API 用于提取、转换和加载数据。Pandas 支持多种格式(所有支持的格式可以在 这里 找到),其中我们选择了使用最广泛的七种格式并比较了它们的性能:JSON、CSV、Parquet、Pickle、Feather、HDF5 和 ORC。

reproducing 分析的代码可以在 这里 找到。

实验方法

速度内存使用 是选择项目合适数据格式时需要关注的两个关键因素。但这些因素会根据数据的大小和使用的数据类型而有所不同。

为了进行分析,我们创建了 3 个数据集,这些数据集应涵盖大多数用例:仅包含数字的数据框、仅包含字符串的数据框和包含数字和字符串的数据框。每个数据框有 10,000 行和 100 列,代码定义如下:

df_numbers_only = pd.DataFrame(np.random.rand(10000, 100), columns=[f'col{i}' for i in range(100)])
df_strings_only = pd.DataFrame(np.random.choice(['a', 'b', 'c'], (10000, 100)), columns=[f'col{i}' for i in range(100)])
df_mixed = pd.DataFrame(np.random.choice(['a', 'b', 'c', np.random.rand()], (10000, 100)), columns=[f'col{i}' for i in range(100)])

对于内存使用情况,我们只需查看保存数据集时的大小。为了测量内存使用情况,我们将使用 Python 的 os 模块,特别是 os.stat

df_numbers_only.to_csv('df.csv')
file_stats = os.stat('df.csv')
print(file_stats)
print(f'File Size in Bytes is {file_stats.st_size}') #this is in Bytes
print(f'File Size in MegaBytes is {file_stats.st_size / (1024 * 1024)}') #here we convert bytes to Mb

对于速度,我们使用 Python 的 cProfile 库来分析函数执行所需的时间。在这里,我们选择了 10 秒的时间窗口,并查看函数执行了多少次。实质上:

def serialize_csv(df):
    """Serialize df to csv."""
    df.to_csv('test.csv')

func = serialize_csv
args = df_numbers_only

timer = pytool.time.Timer()
duration = datetime.timedelta(seconds=10)
with cProfile.Profile() as profile:
    while timer.elapsed < duration:
        func(*args)
    time_results[(df_name, file_format, operation)] = profile

评估速度:不同格式的速度有多快?

直接进入分析核心,让我们看看用于评估序列化/反序列化速度的图表。请注意,我们对所花费的时间进行了缩放,因此您可以在下面的图表中看到,仅包含数字的 CSV 处理过程最慢,而仅包含数字的 HDF5 处理过程最快,占 CSV 处理时间的 1.09%(大约 1/100)。

作者提供的图像:按格式保存时间

作者提供的图像:按格式加载时间

在上述图表中,我们可以观察到几个有趣的速度要点:

  • Pickle/HDF5 是保存纯数字数据的最快格式。

  • Feather 是加载数据的最快格式。

  • 人类可读格式如 JSONCSV 比其他格式要慢得多,特别是在保存/加载仅包含数字的数据时(例如,比 pickle 慢近 100 倍)。

  • HDF5 在保存数据方面表现得非常高效(与 feather、parquet、pickle 持平),但在加载数据时稍逊一些。

内存使用情况:哪个格式消耗的空间更少?

作者提供的图片:缩放内存使用情况

我们可以观察到:

  • 再次强调,人类可读格式如 CSV 或 JSON 是内存效率最低的格式。而 JSON 的表现甚至比 CSV 更差。

  • parquet 是在给定数据大小(10,000x100)下内存效率最高的格式,这也符合 parquet 是列式数据格式的特点。你可以在这里了解更多信息。

  • 再次强调,当仅处理数值数据时,pickle 是最有效的格式。Pickle 作为 Python 的原生序列化包,这一点并不令人惊讶。

  • 尽管速度更快,feather 数据相比 parquet 占用的内存要多得多。

结果可以在下表中总结(相较于上面的分析略显简单):

作者的概要

结论

选择合适的格式始终取决于你项目的独特需求。但我们仍然可以从上述分析中得出一些一般性的结论。例如,除非互操作性至关重要,并且你需要直观地查看原始数据的样子,否则你最好不要使用诸如 CSV 或 JSON 的人类可读格式。

总的来说,parquet 似乎是一个非常可靠的选择,因为它在内存使用方面表现更佳,同时速度仍然相对较快。

了解每种格式的具体特点也是至关重要的。例如,pickle 是 Python 特有的格式,因此你无法从其他语言读取它。但是对于专注于数值数据处理的 Python 项目来说,pickle 似乎是最佳选择(无论是在速度还是内存方面)。

如果你正在处理大量数据并希望最小化写入磁盘的 GB 数量,parquet 是不错的选择。但如果速度是你的主要关注点,你可能要尝试 feather,但它会占用更多内存。

总而言之,只有你的项目具体需求才能指导你选择合适的格式。但你可以从上述内容中获得一些见解,以便了解哪种格式在你的特定情况下更好。祝编程愉快!

哪些特征对你的分类模型有害?

原文:towardsdatascience.com/which-features-are-harmful-for-your-classification-model-6227859a44a6

如何计算分类器特征的误差贡献,以便理解和改进模型

Samuele MazzantiTowards Data Science Samuele Mazzanti

·发表于Towards Data Science ·14 min read·2023 年 9 月 12 日

--

[作者图片]

特征重要性是解释机器学习模型的最常用工具。它如此受欢迎,以至于许多数据科学家最终相信特征重要性等于特征优越性。

事实并非如此。

当一个特征重要时,这仅仅意味着模型在训练集上发现它是有用的。然而,这并不能说明该特征在新数据上推广的能力!

为了说明这一点,我们需要区分两个概念:

  • 预测贡献:一个变量在模型预测中所占的权重。这是由模型在训练集上发现的模式决定的。这等同于特征重要性。

  • 误差贡献:一个变量在模型在保留数据集上的错误中所占的权重。这是特征在新数据上表现的更好代理。

在这篇文章中,我将解释计算这两个量在分类模型中的逻辑。我还将展示一个例子,说明使用误差贡献进行特征选择相比使用预测贡献能够得到更好的结果。

如果你对回归比分类更感兴趣,可以阅读我之前的文章“你的特征重要吗?这并不意味着它们是好的。”

目录

  1. 从一个简单的例子开始

  2. 我们应该为分类模型使用哪种“误差”?

  3. 我们应该如何管理分类模型中的 SHAP 值?

  4. 计算“预测贡献”

  5. 计算“误差贡献”

  6. 一个真实数据集的例子

  7. 证明其有效性:使用“错误贡献”的递归特征消除

  8. 结论

1. 从一个玩具示例开始

假设我们有一个分类问题,我们要预测一个人的收入是否高于或低于$ 100k。假设我们已经有模型做出的预测:

真实值和模型预测。[作者提供的图片]

预测和错误贡献的计算主要基于模型在每个个体上的错误以及每个个体的 SHAP 值。因此,我们需要花时间讨论两个相关点:

  • 我们应该在分类模型中使用哪种“错误”?

  • 我们如何在分类模型中管理 SHAP 值?

我将在接下来的两个段落中讨论这些点。

2. 我们应该在分类模型中使用哪种“错误”?

我们的主要目标是计算模型每个特征的错误贡献。因此,最重要的问题是:我们如何定义分类模型中的“错误”?

注意我们需要一个可以在个体层面计算的错误,然后可以在整个样本上聚合以获得“平均错误”(就像我们对回归模型使用绝对错误一样)。

对于分类模型,最常见的损失函数是对数损失(即交叉熵)。让我们看看它是否适合我们。

这是对数损失的数学公式:

对数损失(即交叉熵)。[作者提供的图片]

对数损失似乎是对我们最合适的选择,因为:

  • 公式的外部部分只是一个简单的平均值

  • 如其名称所示,这是一种“损失”,意味着数值越低越好(就像“错误”一样)

让我们尝试理解为什么我们实际上可以称这个为“错误”。为了简化,让我们专注于和中量:

个体对数损失。[作者提供的图片]

这是单个个体对全局对数损失的贡献,因此我们可以称之为“个体对数损失”。

这个公式可能仍然看起来很可怕,但如果我们考虑到——在二分类问题中——y只能是 0 或 1,我们可能会得到一个更简单的版本:

个体对数损失,替代版本(等同于之前的版本)。[作者提供的图片]

借助图表,现在很容易理解对数损失的主要思想。

个体对数损失的可视化。[作者提供的图片]

预测概率与真实值的距离越远(无论是 0 还是 1),损失越大。此外,如果预测远离真实值(例如,p=.2 且y=1 或p=.8 且y=0),那么损失会比比例更严重。现在应该更清楚为什么对数损失实际上是一种错误。

我们准备将个体对数损失的公式翻译成 Python 函数。

为了避免处理无限值(当y_pred恰好为 0 或 1 时会发生),我们将采用一个小技巧:如果y_pred距离 0 或 1 的距离小于ε,我们将分别将其设置为ε或 1-ε。对于ε,我们将使用 1^-15(这也是 Scikit-learn 使用的默认值)。

def individual_log_loss(y_true, y_pred, eps=1e-15):
  """Compute log-loss for each individual of the sample."""

  y_pred = np.clip(y_pred, eps, 1 - eps)
  return - y_true * np.log(y_pred) - (1 - y_true) * np.log(1 - y_pred)

我们可以使用这个函数来计算数据集中每一行的个体对数损失:

目标变量、模型预测以及结果的个体对数损失。[图片来自作者]

如你所见,个体 1 和 2 的对数损失(或误差)非常小,因为预测值都非常接近实际观察值,而个体 0 的对数损失较高。

3. 我们应该如何在分类模型中管理 SHAP 值?

最流行的模型是基于树的模型,如 XGBoost、LightGBM 和 Catboost。获取树模型分类器在数据集上的 SHAP 值是非常简单的:

from shap import TreeExplainer

shap_explainer = TreeExplainer(model)
shap_values = shap_explainer.shap_values(X)

例如,假设我们计算了玩具问题的 SHAP 值,并得到如下结果:

我们模型预测的 SHAP 值。[图片来自作者]

如果你不清楚 SHAP 值的工作原理,可以阅读我的文章:SHAP 值解释:正如你希望有人向你解释的那样。不过,就本文而言,了解以下内容就足够了:

  • 一个正的 SHAP 值意味着:该特征导致该个体的概率增加;

  • 一个负的 SHAP 值意味着:该特征导致该个体的概率减小。

因此,应该清楚的是某个个体的 SHAP 值总和与模型的预测之间存在直接关系

然而,由于 SHAP 值可以假设任何实际值(正值或负值),我们不能期望它等于该个体的预测概率(这是一个介于 0 和 1 之间的数字)。那么 SHAP 总和与预测概率之间有什么关系?

由于 SHAP 值可以假设任何负值或正值,我们需要一个函数来将 SHAP 总和转换为概率。这个函数必须具有两个特性:

  • 它应该将任何实际值“挤压”到区间[0,1]中;

  • 它应该是严格递增的(因为更高的 SHAP 总和必须总是与更高的预测相关联)。

满足这些要求的函数是 Sigmoid 函数。因此,模型对给定行的预测概率等于该个体 SHAP 值的 Sigmoid 函数

从 SHAP 值到预测概率。[图片来自作者]

这是 Sigmoid 函数的样子:

Sigmoid 函数:shap 和与预测概率之间的关系。[作者提供的图片]

所以,让我们把这个公式转换成一个 Python 函数:

def shap_sum2proba(shap_sum):
  """Compute sigmoid function of the Shap sum to get predicted probability."""

  return 1 / (1 + np.exp(-shap_sum))

我们还可以通过图形展示,查看我们的个体在曲线上的位置:

Sigmoid 函数:shap 和与预测概率之间的关系。[作者提供的图片]

现在我们已经了解了应该使用哪个误差以及如何在分类问题中处理 SHAP 值,我们准备好计算预测和误差贡献了。

4. 计算“预测贡献”

正如我们所见,当 SHAP 值高度正(高度负)时,预测值将比没有该特征时高(低)很多。换句话说,如果 SHAP 值在绝对值上很大,那么该特征对最终预测的影响很大

这就是为什么我们可以通过取该特征的绝对 SHAP 值的均值来衡量特征的预测贡献。

prediction_contribution = shap_values.abs().mean()

对于我们的玩具数据集,这就是我们得到的结果:

预测贡献。[作者提供的图片]

因此,在特征重要性方面,职位是主要特征,其次是国籍,然后是年龄

那么误差贡献呢?

5. 计算“误差贡献”

误差贡献的想法是计算如果我们移除一个给定特征,模型的误差会是什么。

多亏了 SHAP 值,我们可以轻松回答这个问题:如果我们从 SHAP 和中排除一个特征,我们就能得到模型在不知道该特征的情况下的预测。但这还不够:正如我们所见,要获得预测概率,我们首先需要应用 sigmoid 函数。

因此,我们首先需要从 SHAP 值中减去一个特征的 SHAP 值,然后我们必须应用 sigmoid 函数。在这里,我们得到了如果模型不知道这些特征时的预测概率。

在 Python 中,我们可以一次性处理所有特征:

y_pred_wo_feature = shap_values.apply(lambda feature: shap_values.sum(axis=1) - feature).applymap(shap_sum2proba)

这是我们数据集上的结果:

如果我们移除了相应的特征,我们将获得的预测值。[作者提供的图片]

这意味着,如果我们没有特征职位,模型将为第一个个体预测 71%的概率,为第二个个体预测 62%,为第三个个体预测 73%。相反,如果我们没有特征国籍,预测值将分别为 13%、95%和 0%。

如你所见,根据我们移除哪个特征,预测的概率差异很大。因此,结果误差(个体对数损失)也会非常不同。

我们可以使用上面定义的函数(individual_log_loss)来计算没有相应特征时的个体对数损失:

ind_log_loss_wo_feature = y_pred_wo_feature.apply(lambda feature: individual_log_loss(y_true=y_true, y_pred=feature))

这是结果:

如果移除相应特征,我们会得到的单独 log-loss。[作者图片]

例如,如果我们看第一行,我们可以看到在没有特征job的情况下,log-loss 为 1.24,而在没有特征nationality的情况下,log-loss 只有 0.13。因为我们希望最小化损失,在这种情况下,最好移除特征nationality

现在,要了解模型在有无该特征时的效果如何,我们可以计算完整模型的个别 log-loss 与去掉特征后的个别 log-loss 之间的差异

ind_log_loss = individual_log_loss(y_true=y_true, y_pred=y_pred)
ind_log_loss_diff = ind_log_loss_wo_feature.apply(lambda feature: ind_log_loss - feature)

这是结果:

模型错误与去掉该特征后的错误之间的差异。[作者图片]

如果这个数字是:

  • 负数,则特征的存在会导致预测错误减少,因此该特征在该观察中表现良好。

  • 正数,则特征的存在会导致预测错误增加,因此该特征在该观察中表现不佳。

我们可以最终通过列计算每个特征的错误贡献值,这些值的均值如下:

error_contribution = ind_log_loss_diff.mean()

这是结果:

错误贡献。[作者图片]

一般来说,如果这个数字是负数,则特征具有正面效果;相反,如果这个数字是正数,则该特征对模型有害,因为它倾向于增加模型的平均错误

在这种情况下,我们可以看到特征job的存在使得个别 log-loss 平均减少了-0.897,而特征nationality的存在使得个别 log-loss 平均增加了 0.049。因此,虽然nationality是第二重要的特征,但它的表现不佳,因为它使平均个别 log-loss 增加了 0.049。

让我们尝试将这些概念应用到实际数据集中。

6. 真实数据集示例

下面,我将使用一个来自Pycaret(一个在MIT 许可下的 Python 库)的数据集。数据集叫做“Gold”,包含一些金融数据的时间序列。

数据集样本。特征都以百分比表示,所以-4.07 意味着回报为-4.07%。[作者图片]

特征包括金融资产在观察时刻之前分别 22、14、7 和 1 天的回报(“T-22”、“T-14”、“T-7”、“T-1”)。这是所有用作预测特征的金融资产的详尽列表:

可用资产的列表。每个资产在时间-22、-14、-7 和-1 被观察到。[作者图片]

总共有 120 个特征。

目标是预测黄金回报在 22 天后是否会超过 5%。换句话说,目标变量是二元的:

  • 如果 22 天后的黄金回报小于 5%,则为 0;

  • 如果 22 天后的黄金回报大于 5%,则为 1。

黄金回报 22 天后的直方图。红色标记的阈值用于定义我们的目标变量:回报是否大于 5%。[图片来源]

一旦加载了数据集,这些是我执行的步骤:

  1. 随机拆分完整数据集:33% 的行用于训练数据集,另 33% 用于验证数据集,其余 33% 用于测试数据集。

  2. 在训练数据集上训练一个 LightGBM 分类器。

  3. 使用上一步骤训练的模型对训练、验证和测试数据集进行预测。

  4. 使用 Python 库 “shap” 计算训练、验证和测试数据集的 SHAP 值。

  5. 使用我们在上一段中看到的代码,计算每个特征在每个数据集(训练集、验证集和测试集)上的预测贡献和误差贡献。

目前,我们既有预测贡献也有误差贡献,因此我们可以最终进行比较:

预测贡献与误差贡献(在验证数据集上)。[图片来源]

查看这个图表可以为我们提供关于模型的宝贵洞察。

最重要的特征是 T-22 天的美国债券 ETF,但它并没有带来如此显著的误差减少。最佳特征是 T-22 的 3M Libor,因为它能最大程度地减少误差。

玉米价格有一些非常有趣的特点。T-1 和 T-22 的回报率都是最重要的特征之一,然而其中一个(T-1)存在过拟合(因为它加剧了预测误差)。

一般来说,我们可以观察到,所有具有较高误差贡献的特征都与 T-1 或 T-14(观察时刻前 1 天或 14 天)相关,而所有具有较小误差贡献的特征都与 T-22(观察时刻前 22 天)相关。这似乎表明最新特征容易过拟合,而指向较旧回报的特征更容易泛化

除了获得关于模型的洞察,考虑使用误差贡献进行特征选择是很自然的。这也是我们将在下一段中要做的。

7. 证明其有效性:使用“误差贡献”的递归特征消除

递归特征消除(RFE)是一个逐步从数据集中删除特征的过程,目的是获得更好的模型。

RFE 的算法非常简单明了:

  1. 初始化特征列表;

  2. 在训练集上训练一个模型,使用当前特征列表作为预测器;

  3. 从特征列表中移除“最差”的特征;

  4. 返回第 2 步(直到特征列表为空)。

在传统方法中,“最差”= 最不重要。然而,根据我们看到的,我们可能会反对,首先去除最有害的特征更有意义

换句话说,

  • 传统 RFE:首先去除最无用的特征(最无用 = 验证集上的预测贡献最低)。

  • 我们的 RFE:首先去除最有害的 特征 (最有害 = 验证集上的误差贡献最高)

为了验证这个直觉是否正确,我使用了两种方法进行模拟。

这是验证集上的对数损失结果:

两种策略在验证集上的对数损失。[作者提供的图像]

由于对数损失是“越低越好”的度量,我们可以看到我们的 RFE 版本在验证数据集上明显优于经典 RFE。

然而,你可能会怀疑查看验证集是否公平,因为误差贡献是基于它计算的。所以,让我们查看测试集。

两种策略在测试集上的对数损失。[作者提供的图像]

即使现在两种方法之间的差异较小,我们仍然可以看到它仍然很大,并且足以得出结论:基于误差贡献的 RFE 在这个数据集上显著优于基于预测贡献的 RFE。

除了对数损失之外,考虑一个更具实际价值的度量会很有趣。例如,让我们看看验证集上的平均精度:

两种策略在验证集上的平均精度。[作者提供的图像]

有趣的是,尽管贡献误差是基于对数损失的,我们在平均精度上也取得了优异的结果。

如果我们想基于平均精度做出决策,那么我们将选择在验证集上具有最高平均精度的模型。这意味着:

  • 基于误差贡献的 RFE:具有 19 个特征的模型;

  • 基于预测贡献的 RFE:具有 14 个特征的模型;

如果我们这样做,我们在新数据上会观察到什么样的表现?回答这个问题的最佳代理是测试集:

两种策略在验证集上的平均精度。[作者提供的图像]

在这种情况下,基于误差贡献的 RFE 的表现通常优于基于预测贡献的 RFE。特别是,根据我们之前的决定:

  • 基于误差贡献的 RFE(具有 19 个特征的模型):72.8% 平均精度;

  • 基于预测贡献的 RFE(具有 14 个特征的模型):65.6% 平均精度。

因此,通过使用基于误差贡献的 RFE,而不是传统的基于预测贡献的 RFE,我们将在平均精度上获得额外的 7.2%优秀结果!

8. 结论

特征重要性概念在机器学习中起着基础性作用。然而,“重要性”的概念常常被误解为“好处”。

为了区分这两个方面,我们引入了两个概念:预测贡献和误差贡献。这两个概念都基于验证数据集的 SHAP 值,文章中我们展示了计算这些值的 Python 代码。

我们还在一个真实的金融数据集上进行了试验(任务是预测黄金价格),并证明基于误差贡献的递归特征消除比基于预测贡献的传统 RFE 提高了平均精度 7%。

你可以在 这个笔记本找到本文所用的所有代码。

感谢阅读!

如果你觉得我的工作有用,你可以订阅 每次我发布新文章时都会收到邮件 (通常是每月一次)。

如果你想支持我的工作,你可以 请我喝咖啡

如果你愿意, 可以加我 Linkedin

哪些 GPT 类似模型工程技术适用于系统日志?

原文:towardsdatascience.com/which-gpt-like-engineering-strategies-work-on-system-logs-6b0a3a1ebcad?source=collection_archive---------4-----------------------#2023-04-21

对行为恶意软件痕迹应用的变换器神经网络建模方法的评估

Dmitrijs TriznaTowards Data Science Dmitrijs Trizna

·

关注 发表在 Towards Data Science ·11 分钟阅读·2023 年 4 月 21 日

--

图 1. 从训练于恶意软件仿真报告的变换器模型中提取的自注意力激活。图像由作者提供。

本文评估了应用于机器数据(即来自Speakeasy 模拟器的恶意软件行为日志)的各种 Transformer 神经网络(GPT 的 AI 模型)工程方法。这些实验中使用的数据自发布以来一直可以自由获取,作为混合恶意软件分析 [Trizna]的一部分,档案可以在这里下载。您可以访问这些数据,随意复制或推进结果!

最初,Transformer 被提出作为一种适用于序列到序列任务(如自然语言翻译)的编码器/解码器架构。后来它被调整以适应其他任务,如 GPT 的掩码解码器模型,以便擅长文本生成。由于我们将使用 Transformer 进行推理,模型仅包含编码器层(模型的 PyTorch 代码),类似于例如 BERT [Devlin et al.]中使用的架构。

推测地,本文得出的关于 Transformer 工程方法的相同结论可以扩展到任何系统日志集,例如 Windows 上的操作系统遥测如 Sysmon,或对应的 Linux 框架如auditd,应用级日志如来自 Kubernetes API 服务器的kube-audit事件,或 HTTP 服务器的访问日志及计数。

图 2. 预处理过滤后的 JSON 事件到令牌序列的示意图和简化视图,包括过滤器和归一化。图片由作者提供。

虽然是正在进行的研究的一部分,本文避免了更广泛的讨论关于数据和架构组合,而是专注于 Transformer 建模技术的方法论和 PyTorch 代码,以评估相对实用性工程策略,而非绝对性能。评估的配置列表:

  • 三角形、余弦、阶梯和一周期学习率调度器;

  • 累积梯度;

  • 梯度裁剪;

  • 注意力块的预归一化与后归一化;

  • 数据优化;

  • 学习率对模型大小的依赖。

对于所有配置选项,我进行三次交叉验证运行,并报告验证集和测试集的平均 ROC 曲线以及训练损失。本文不涵盖模型的自监督预训练,而是专注于恶意软件分类的下游任务(即监督学习)。

许多出版物已经系统地分析了 Transformer 在自然语言方面的工程进展,我们将在机器数据上探讨这些进展。考虑到进一步研究这里提出的观点,我建议学习,例如,“临时性”论文 [Geiping and Goldstein]。

优化数据集

数据由 ~120k 恶意软件和良性软件样本的 JSON 报告组成,恶意样本涵盖了如勒索软件、木马、后门等七种恶意软件类型。然而,我将实验限制为二分类,Clean 标签表示良性类,所有其他标签表示恶意样本。值得注意的是,测试集是在训练集收集后三个月收集的,以引入概念漂移下的评估。

由于模拟是一种不完美的方式来获取回避型恶意软件样本的行为,进一步 过滤和规范化 被应用于上述报告,以剔除失败或不完整的模拟,从而在训练集中得到 76126 个样本,在测试集中得到 17407 个样本。

序列长度减少的字段过滤器

根据我们的观察,存储在机器数据中的任何语义逻辑都跨越了非常长的标记序列,包括无数次的元数据和环境特定信息,这些信息对于建模任务几乎没有相关性。可以说,这是自然语言和机器语言之间最显著的差异之一。前者在相对较短的句子中具有简洁的语义,没有或很少有无用的组件。

对自注意力模型来说,这很不幸,因为其在输入长度上的复杂度是平方的,这意味着较长的序列训练成本指数增长。这是因为自注意力中的每个元素都必须关注同一序列中的每个其他成员——请参见上面的图 1 标题以获取可视化。

因此,在处理机器数据时应用基于领域知识的过滤器是必不可少的。在我们的案例中,JSON 事件 会被过滤,过滤依据包括(1)文件,(2)注册表,(3)网络访问,以及(4)API 调用名称、参数和返回值。这显著提高了知识密度(即每个标记的相关知识比率)和数据质量。

序列长度在下面的实验中限制为512个标记,以便更快地迭代配置组合,但为了最终性能评估或生产实现,建议保留较长的序列。有证据表明,任务相关的性能从更长的序列中受益,如下图的热图所示:

图 3. 在假阳性率为 0.001(每 1000 次检测一个假警报)下的平均真正率,取决于变量序列长度和词汇大小。图片由作者提供。

标记化

机器数据的词汇量可能比自然语言大得多,因为通常没有明确的词汇边界或语法规则来定义所使用的语言。

在系统日志中,常见的任意字符组合如*/tmp/83afba/setup.binjre1.8.0_311*,由于处理不当,会导致词汇量爆炸。例如,我观察到大约有 3000 个独特的 API 名称,其中约 1000 个在训练数据中仅出现一次。

因此,您为模型分析包含的每一个额外字段都会显著增加词汇量。请参见下面的对数图,显示了不同 JSON 字段过滤器设置下的训练集标记的频率分布:

图 4. 针对训练集应用不同 JSON 字段过滤器的标记频率分布。图片由作者提供。

例如,给定一个使用 API 调用、文件、网络和注册表记录的字段过滤器,总词汇量大约为 25 万标记。假如没有应用过滤器,这个数字会接近 80 万标记,词汇量爆炸超过三倍,并显著降低数据的认知密度(每个标记的有价值信息)。这强调了合适的领域知识影响的过滤器对提高模型接收的数据质量的重要性。

对于任意值字段如哈希值和 IP 地址的归一化也是一样的。例如,将哈希值不处理会极大地扩展词汇量,而使用下面的函数将其归一化为像**这样的值,则会得到一些易于解释的占位符,模型可以利用这些占位符:

这只是一个简单的例子,而我管道中实现的所有归一化可以在这里找到。

然而,另一个关键技巧是改进标记化本身,以便技术数据中的变异不会无限期地创建新标记。自然语言已经提供了适合机器数据的解决方案。

我定义了一个基于 Google 的SentencePiece的自定义 JSON Byte Pair Encoding (BPE)标记器(代码),它分析字节的相对共现方式。这样,标记并不代表技术语言中的独立值,而是较长值序列的一部分,如下面的例子所示:

图 5. 空白标记示例。图片由作者提供。

图 6. SentencePiece 的 BPE 标记示例。图片由作者提供。

我将进一步的实验限制在50k BPE 标记的词汇量内。

模型大小和学习率

学习率(LR)的选择很直接,对任何深度学习从业者来说都是已知的。然而,我想强调的是,对于 Transformer 架构,选择适合的学习率范围对于特定模型大小尤其重要。

请参阅 GPT-3 论文 [Brown et al.]中的此表格,该表格报告了不同模型的配置以及使用的学习率的变化:

表格 1 来自 GPT-3 论文 [Brown et al.]。注意学习率和批量大小如何根据可训练模型参数的数量而变化。

注意到模型增长后的学习率减少的意义,模型大小增加 ~1000 倍时,学习率降低 ~10 倍。

由于硬件设置(单个消费级 GPU),我使用了一个具有 ~5–6 M 参数的适中大小的模型。由于本文评估的是相对配置效用而不是达到最先进的绝对值,因此这是一个适合快速迭代多种配置的良好大小,为更大模型的行为提供了良好的基准。行业数据显示,模型大小(非嵌入层中的参数数量)强烈预测性能 [Kaplan et al.]——这一特性被称为“规模定律”。因此,模型的大小和数据集的增加应在生产发布时产生必要的检测率。

我决定循环测试从 0.003 到 0.0001 的一系列学习率,每次降低约 ~3 倍。结果如下:

图 7. 在三个交叉验证折叠中,变量学习率下的验证集和测试集 ROC 曲线的平均值以及训练损失。图像由作者提供。

可以明显看出,0.003 太大,而 0.0001 太小,最佳值在 0.0003–0.001 之间。我选择了接近底线 ~0.0003 的学习率进行进一步测试。

学习率调度器

一旦为给定模型选择了适当的最大学习率,我探索了各种学习率值的调度器。调度器是指在训练过程中修改学习率值的函数。

我 定义了多种调度器,这些调度器基于最有前景的出版物中报告的调度器,具体为:(1)阶梯型,(2)三角形,(3)单周期,以及(4)余弦型,学习率随训练时间的变化如下面所示:

图 8. 不同调度器下的训练步骤中的学习率变化。图像由作者提供。

实验结果(包括一个完全不使用任何调度器的运行)如下所示:

图 9. 各种调度器应用的结果。图像由作者提供。

我们可以清楚地看到,具有“预热”阶段的调度器——即不从最大 LR 值停止(“三角形”型和“一周期”型),在前~2000 次更新期间具有更高的训练损失。从测试集上的 ROC 来看,我们可能会得出“一周期”表现最差的结论,没有调度器在低假阳性率(FPR)要求下可能会有更高的真正率(TPR,即检测率)。

为了消除歧义,这里是测试集中在最低 0.0001(意味着每 10,000 次分析中出现一个假警报)假阳性率下的精确 AUC 和 TPR 值:

表 1. 在特定假阳性率(列)下的平均真正率(值)以及在测试集上的 AUC,基于 3 次交叉验证折叠。表格由作者提供。

这表明,在特别低的假阳性要求下,使用“步进”调度器的训练效果最好,而整体最佳 AUC 则使用了“三角形”调度器。确实,有证据表明“预热”可能很重要,特别是对于自监督预训练。

值得注意的是,没有调度器的训练在最低 FPR 下表现较差。这可能表明“冷却”(即在结束时逐渐减少 LR 值)对找到最佳模型的局部最小值尤为重要。对于信息安全应用来说,使用具有“冷却”阶段的调度器可能至关重要,因为假阳性会消耗人工分析师的警报预算,并且成本较高。

如果你对深入探讨 LR 调度器的话题感兴趣,以下的 TDS 文章可能会引起你的兴趣。

累积梯度

一位好奇的读者在我们讨论模型大小对学习率变化的影响时,注意到了 GPT-3 表 1 中的批量大小差异。不同的模型大小需要不同数量的样本来生成最佳的梯度更新。对于最小的模型,GPT 作者使用了 500k 样本的批量大小,而对于最大的模型,包括 GPT-3 本身,他们每次权重更新处理了 3.2M 样本。

鉴于资源有限的训练,适合单个 GPU 的批量大小是不理想的。从 64 或 96 个样本计算出的梯度向量可能有偏差,导致收敛缓慢。因此,讨论有限约束下训练的资源会提到在更新权重之前累积梯度的技术(即在 PyTorch 中调用optimizer.step())。使用梯度累积的训练循环如下:

关键点在于,accumulation_steps的不同配置下训练速度有所不同。例如,每步更新梯度的速度比每 16 步更新一次要慢。因此,我设置了每次训练运行的相同时间预算为十分钟——以评估每种配置在相同计算资源下的效率。这使得更新率较低的运行能够在相同的时间内处理更多的批次(因此,训练损失的长度不同)。结果如下:

图 10. 不同累计梯度批次值在三次交叉验证折叠中的验证集和测试集 ROC 曲线均值及训练损失。图片由作者提供。

出于某种原因,我们发现这种技术显著降低了最终模型的性能。无论是使用本地 PyTorch 实现梯度累积,如上所示,还是使用 HuggingFace 的 accelerate,都有相同的模式。因此,这不应该是实现中的 bug。即使具有累积循环的模型在相同训练预算内处理的批次数明显更多,经典实现的数据量减少约 5 倍仍然能达到更高的检测率。

梯度裁剪

梯度裁剪是一种在参数更新过程中设置梯度值上限的方法。梯度爆炸是递归神经网络(RNN)的一个显著缺陷,而梯度裁剪被引入作为解决 RNN 梯度爆炸的问题的方法。然而,实际上,所有近期关注 Transformer 的论文也都实现了这一技术。它通过没有明显缺陷的方式稳定训练过程。PyTorch 提供了一个专门的clip_grad_norm_函数,用一行代码在训练循环中实现这一逻辑(例如,在optimizer.step()调用之前,参见)。变量值在 0 到 1 范围内的结果如下:

图 11. 不同梯度裁剪限制在三次交叉验证折叠中的验证集和测试集 ROC 曲线均值及训练损失。图片由作者提供。

我们清楚地看到,0.1 的裁剪值是次优的,而其他三个选项的曲线没有得出明确结论。因此,如前所述,查看下表中特定 FPR 和 AUC 值的 TPR 是非常有帮助的:

表 2. 在 3 次交叉验证折叠中的特定假阳性率(列)下的平均真正率(值)和测试集上的 AUC。表格由作者提供。

在这里,很明显 1.0 的梯度裁剪提供了最佳的指标,在极低的 FP 条件下具有最高的整体 AUC,并且至少比其他选项多出 3% 的好处。

层归一化 — 输入或输出

原始 Transformer 实现将 层归一化 应用于自注意力块的输出。然而,现代文献一致认为输入归一化优于经典设置(有关更多细节,请参见 PreNorm vs. PostNorm 讨论,如 [1]、[2])。

值得注意的是,PyTorch 的默认行为仍然是经典的 PostNorm,但通过修改 Transformer Encoder 块中的一个 norm_first 布尔值可以轻松更改。我们的结果证实了公众的观点,输入归一化优于经典实现。

图 12. 预归一化和后归一化配置的验证集和测试集 ROC 曲线以及训练损失的均值。图片由作者提供。

一个持续的争论是 RMSNorm 是否优于传统的 LayerNorm,其中 LLaMa 等实现使用 RMSNorm,而 其他实验 则显示没有明显的好处。我们将其排除在我们的分析之外。

总结

通过实验,我们发现某些配置,如 (1) 输入归一化、(2) “三角形”或“阶梯”学习率调度器,以及 (3) 大约 1.0 的梯度裁剪,有效地提高了模型性能。另一方面,梯度累积没有改善性能,这需要进一步探索。

此外,我们讨论了为机器数据工程化 Transformer 模型时的重要注意事项,如 (1) 领域知识影响的过滤、归一化和分词以减少词汇表和 (2) 关于模型大小的学习率选择。

如上所述,推测性地,可以将关于 Transformer 工程方法的相同结论扩展到其他类型的系统日志,如操作系统遥测或 HTTP 服务器的访问日志,希望本文能为将现代 AI 技术应用于减轻行业专业人士日常任务的挑战性工作做出贡献。

我应该选择哪个在线数据科学课程?

原文:towardsdatascience.com/which-online-data-science-course-should-i-do-5a73c0b2c9c2

提示:从免费的资源开始

Matt ChapmanTowards Data Science Matt Chapman

·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 7 月 11 日

--

图片来自 Headway 在 Unsplash

如果你想学习数据科学中的一项新技能,决定选哪个课程确实很困难。

当我在 2019 年寻找我的第一个在线数据科学编码课程时,我陷入了 分析瘫痪 的困境,花了太长时间在不同选项之间做决定。然后,当我终于做出决定并花了 20 英镑购买一个课程时,我很快发现这并不适合我,甚至没有完成它。

根据研究,我不是唯一的一个——开放大学(2015)和 ResearchGate(2018)的研究估计,在线课程的完成率低至 3%。

没错,3%。

自从 2019 年那次糟糕的第一次尝试以来,我已经参加了(并完成了!)许多其他课程,并学到了如何挑选合适课程和最大化其价值的重要经验。在这篇文章中,我将分享一些我上过的最佳课程,并介绍我用来帮助我在课程之间做出决定的 5 个关键考虑因素。这是我希望在 2019 年时听到的建议,如果对你有帮助,点击我的“关注”按钮将对我意义重大——只有 1%的读者这么做!

1. 从免费的资源开始

我在数据科学学习者中遇到的最大误解之一是 $$$ 课程 = 更好的课程

说实话,一些最好的资源是完全免费的。像 YouTube、freeCodeCamp 和 Towards Data Science 这样的免费平台提供了成千上万的高质量在线课程,涵盖了几乎所有的数据技能,每当我需要学习新东西时,它们总是我的首选。因为这些课程是免费的,所以“试用后再购买”非常容易,这意味着在你决定花钱或开始整个课程之前,它们是测试课程适用性的绝佳方式。

例如,当我在 2021 年需要学习 git/GitHub 时,我尝试了 YouTube 上的多个免费课程,最终决定使用 Tech With Tim 的这个 课程。它时间短(40 分钟)、免费,而且有大量的社会证明(高观看次数/评分),涵盖了我需要的一切。如果你从零开始学习一个新主题,尝试一个小课程(比如 YouTube 上的课程)要比直接投入一个完整的长期课程要好得多。你可以随时转向付费课程(如果需要),但如果你没有先检查过免费选项的质量,为什么要一开始就选择付费课程呢?

这里有一些我可以彻底推荐的优秀免费课程(这些不是附属链接,也没有其他类似的东西,它们只是我参加过并且喜欢的免费课程):

  • Pandas / 使用 Python 进行数据分析:开放大学的 数据分析编程学习课程

  • 机器学习:Kaggle 的 机器学习入门课程

  • 自然语言处理(NLP):HuggingFace 课程(高级)

  • SQL:SQL 教程 — 初学者的完整数据库课程

  • 数据科学(一般):IBM 数据科学专业证书(你可以免费注册,只有在你需要证书时才需要付费)

  • Git/GitHub:Git 初学者教程

2. 选择一个符合你具体目标的课程

我参加的第一个在线课程是一个通用的 Python 课程。虽然这是一个很好的课程,但它并没有特别专注于 数据科学 方面的 Python 应用(这是我的学习目标),而且我被迫完成了一些我不感兴趣的模块(例如,网页应用开发、地理空间分析)。这很快变得无聊/无效,不久之后我就放弃了。

为了避免犯这种错误,我建议你从一个针对小而具体目标的课程开始,然后再逐步深入。

例如,与其设定一个宏大的目标如“学会编程”,不如将其拆解为一个更小的目标如“像数据科学家一样编程”。然后,再进一步拆解为一个更小的目标,选择一种具体的语言(例如 Python),再选择一个专注于特定技能(如数据分析)的 Python 课程(例如,像这样的课程)或机器学习(例如,像这样的课程)。

这是我在学习 Python 时效果最好的方法:我开始时是为了一个非常具体的目的(数据分析)学习 Python,并在此基础上扩展到其他领域(如网络开发或机器学习),根据需要逐步学习。

3. 这个课程是否有内置的代码编辑器,还是需要你在自己的电脑上本地运行代码?

图片由Chris Ried提供,来源于Unsplash

很多电子学习平台内置了代码编辑器,允许你在浏览器/应用程序中编写和执行代码。这种方式的好处是,你可以非常轻松地开始编码,无需任何安装或设置。如果你选择像 Enki 这样的移动友好平台/应用程序上的课程,你甚至可以在通勤时或坐在厕所上时进行编码。

这类课程的缺点是,你不一定会学会如何在“实际环境”中运行代码,超出那个特定的电子学习平台。

如果你的目标只是学习语法并“试用一下”,那么带有内置编码编辑器的课程是一个很好的解决方案。像 CodeAcademy、DataCamp 和 freeCodeCamp 这样的网站上的课程在这方面非常棒。

如果你想在真实系统中学习,尝试寻找鼓励你自己“边编码边学习”的课程。例如,在我提到的课程中,像SQL 教程——初学者完整数据库课程这样的课程非常适合。

4. 找一个能够让你每天坚持学习的课程(“小量、多次”比临时抱佛脚要好)

图片由Christine提供,来源于Unsplash

坚持学习是建立课程惯性的#1 最佳方式。

我目前在 Duolingo 上已经坚持了 324 天学习阿拉伯语,虽然还有很长的路要走,但我现在的阿拉伯语水平比 324 天前要好很多。有时我会在 Duolingo 上花 30 分钟,有时则花 2 分钟。但是,通过专注于坚持,帮助我保持一致性,并确保即使在动力不足的日子里也能做一些事情。

编程课程也是一样的道理。

如果你在工作或大学期间学习在线课程,你的动力和可用时间可能会波动很大。通过坚持保持一个连续的学习习惯,你可以确保即使在繁忙的日子里也能保持一致。从长远来看,这也比“临时突击学习”要可持续得多。记忆曲线意味着我们很容易忘记一天内学习的内容,只有当我们定期回顾这些话题时,学习才会深入。

因此,当你在选择课程时,尝试找一个能让你建立小的学习习惯的课程。如果课程是一个 10 小时的非互动讲座,在忙碌的日子里可能很难进行“2 分钟的学习”,你也可能会倾向于放弃。相反,如果课程包含许多示例问题和小练习,那么在短时间内进行快速学习并保持学习习惯会容易得多。

如果每天坚持不现实,那为何不尝试每周坚持一次?你能否在接下来的 52 周内每周做一点?

5. 通过将大课程拆分为“迷你课程”来创建自己的“课程”

当我的奶奶在 1960 年代尝试戒烟时,她给自己立了个承诺:如果她连续三个月不吸烟,她将用她原本用于购买香烟的钱买一块新手表。

我一直喜欢那个故事,虽然发生在 60 年前,但我认为它在 2023 年仍然对我们有很多智慧。

如果你唯一的目标是“完成”整个课程,你会给自己设置一个漫长而艰难的任务。如果你将目标拆分为迷你目标(例如,“完成这一章”),并不断奖励自己以达到这些迷你目标,你会发现保持动力变得容易得多。

例如,当我参加IBM 数据科学专业证书时,我会在每章结束时用一小块甜点奖励自己,并将刚刚学到的内容应用到我的在线作品集上。这些看似微不足道的奖励实际上真的帮助我保持了动力,因为总是有一些短期内可以实现的具体目标。

就这样!我选择好课程的顶级技巧,以及一些对我帮助最大的课程。

哦,还有一件事 —

我启动了一个免费的通讯《AI in Five》,每周分享 5 个要点,涵盖最新的 AI 新闻、编程技巧以及数据科学家/分析师的职业故事。没有炒作,没有“数据是新的石油”的废话,也没有埃隆的推文——只有实用的技巧和见解,帮助你在职业发展中前进。如果这对你有吸引力,点击这里订阅!

[## AI in Five | Matt Chapman | Substack

最新的新闻、职业故事和编码技巧,来自数据科学和 AI 的世界,以 5 个要点总结…

aiinfive.substack.com

黑客使用哪些编程语言?

原文:towardsdatascience.com/which-programming-languages-do-hackers-use-ac3ed9d3e8f8?source=collection_archive---------8-----------------------#2023-01-31

使用 Python 分析漏洞数据库

Christian KochTowards Data Science Christian Koch

·

关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 1 月 31 日

--

照片由 Luca Bravo 提供,来源于 Unsplash

在 2021 年,我们与其他科学家一起在德国混沌计算机俱乐部(CCC)进行了调查。我们的目标是找出黑客最常用的编程语言。本文跟进了调查结果,并将其发现与对Exploit Database的分析进行比较。读者将获得逐步的环境设置说明和结果总结。因此,本文不仅涵盖了应用技术,还提供了有关网络安全领域的见解。

调查概述

作为欧洲最大的黑客协会,混沌计算机俱乐部为研究该领域中流行的编程语言提供了良好的基础。为此,我们在 2021 年 5 月向俱乐部的成员发送了一个在线问卷的链接。根据结果,受访者主要使用 Shell 和 Python 进行黑客攻击。调查的另一个关键发现是,他们的语言偏好随着时间的推移发生了变化。总体而言,参与者并不认为编程语言的选择对黑客攻击至关重要。由于样本量只有 48 个,研究的样本量相对较小。由于仅针对 CCC 的成员,结果也可能存在偏差。本文的目标是通过分析 Exploit-DB 来验证关键结果。

关于 Exploit-DB

在 CCC 调查进行的同一年,一种被称为零日漏洞的攻击引起了广泛关注。当时,流行的日志框架 Log4j 遭遇了一个高度严重的漏洞。利用这种安全缺陷的脚本称为exploit。顾名思义,Exploit Database 提供了一个包含 exploit 和相应易受攻击软件的公共档案。该平台的目标用户是渗透测试人员和安全研究人员。用户可以通过其网站或在Kali Linux下提供的工具集访问数据库。

在撰写本文时,Exploit-DB 中已发布超过 45,000 个 exploit,来自 9,000 多名独立作者(source)。数据库条目包含不同的属性,如 exploit ID、作者、类型和发布日期。每个记录与一个包含实际脚本或程序的文件相关。在 Kali 系统下,我们通过执行 shell 命令exploitdb来找到数据库的根目录。该目录包含一个 CSV 文件(files_exploits.csv*)*,以及包含实际脚本或程序的子目录(exploits/)。

图 1:Kali Linux 下的 Exploit-DB 目录

本文描述的比较是在 2023 年 1 月 13 日使用 2022 年 11 月 22 日的数据库快照准备的。快照和分析的源代码都可以在GitHub上获取。

设置和数据转换

为了设置分析环境,第一步是克隆 GitHub 项目。所有所需文件都存储在其根目录exploits中。该实现基于Anaconda Python 发行版,客户端机器上必须预先安装该发行版。可以通过在项目根目录中执行以下命令来创建和激活 conda 环境:

conda env create -f environment.yml
conda activate exploits

要分析的数据库快照存储为data文件夹中的 CSV 文件。它是从 Kali Linux 中获取的,并通过脚本execute_transformer进行了转换。转换时间和快照时间都记录在文件timestamps中。如有需要,可以通过在 Kali shell 中运行以下命令来更新快照:

cp -p /usr/share/exploitdb/files_exploits.csv data/
python execute_transformer.py

转换脚本提供了整理数据和推导附加字段的功能。一个主要任务是提取使用的编程语言的信息。为此,应用了库Pygments。虽然其主要目的是语法高亮,但该框架提供了猜测特定文件编程语言的功能。

除了 Pygments,还有其他语言检测库。本文测试的一个例子是深度学习解决方案Guesslang。然而,将其集成到 conda 环境中证明是困难的,而且处理时间远远超过 Pygments。由于 Guesslang 没有产生更好的结果,因此采用了后者框架。以下函数将 Pygments 纳入数据转换脚本中:

import pygments
from pygments.lexers import guess_lexer_for_filename

def _parse_exploit_file(file_name):
    with open(file_name, encoding="UTF-8") as file:
        lines = file.readlines()
        text = "\n".join(lines)

        line_count = len(lines)

        try:
            lang_guessed = guess_lexer_for_filename(file_name, text).name
        except pygments.util.ClassNotFound:
            lang_guessed = None

        return line_count, lang_guessed

上述 Python 代码读取某个文件,统计其行数,并使用函数guess_lexer_for_filename来检测编程语言。为此,框架应用了各种 Lexer,即用于语法分析的类。只有那些分配给给定文件扩展名的 Lexer 被考虑在内。有些扩展名只有一个类,而其他扩展名则存在选择上的模糊性。例如,扩展名“py”明确指向 Python,而“pl”扩展名则可能指向 Perl 或 Prolog。最佳匹配的 Lexer 被作为结果返回。其名称揭示了编程语言,并为分析提供基础。下一节讨论的发现源自于笔记本 comparison。此外,GitHub 项目还提供了进一步的笔记本,以探索 Exploit-DB 的具体方面。

结果讨论

CCC 调查的核心问题是参与者在研究前一年使用了哪些编程语言。受访者可以选择多个答案选项。图 2 比较了 CCC 成员提及的前十种语言与 Exploit-DB 作者使用的语言。图表揭示了相似之处,也显示了差异。

首先,样本规模存在一个主要差距。总体而言,48 名 CCC 成员参与了调查。相比之下,超过 900 名独立作者在 2020/21 年间在 Exploit-DB 上发布了 2,500 多个文件。这两年被选中是因为它们与比较研究的研究期重合。为了避免重复,每个作者-语言组合在 Exploit-DB 中仅计数一次。在研究期间,这导致了 1,134 个语言参考,其中 1,116 个属于前十名。另一方面,CCC 调查的参与者将他们的前十种语言提及了 140 次。

图 2:CCC 调查和 Exploit-DB 的前十种语言

CCC 的成员主要使用 Shell(Bash/Shell/PowerShell)和 Python,其次是 C、JavaScript 和 HTML/CSS。所有这些技术在 Exploit-DB 中也都有出现。总体来看,语言选择有很大重叠。图 2 中的十种技术中有六种出现在两个列表中。Python 始终排名第二,显示出其在网络安全领域的受欢迎程度。然而,图表一侧的某些语言在另一侧并未出现。

一个主要的区别是 Exploit-DB 中语言的分布不均。超过一半的提交是文本文件。在这里,Pygments 分配的名称“仅文本”具有误导性。Exploit-DB 中的文本文件通常包含描述,但也经常包含 shell 命令和可能的其他语言脚本。因此,某些技术在结果中可能被低估。抽样检查显示,这对 shell 脚本可能确实成立,后者在 CCC 研究中排名第一。这也是我们方法的一个局限性。像 Pygments 这样的框架在评估多语言文件时存在问题。克服这一问题将是一个有趣的后续研究课题。

让我们暂时离开 2020/21 年,并查看 Exploit-DB 的整个历史。图 3 显示了数据库中所有时间的前十种语言。与之前一样,语言检测依赖于 Pygments,每种作者-语言组合只计算一次。

图 3:Exploit-DB 中所有时间的前十种语言

根据图 3,Exploit-DB 中所有时间的前十种语言以 Text 为首,其次是 Python、C、HTML 和 Perl。再次,这与 CCC 成员的选择有相当大的交集。一个惊讶的发现是 Prolog 出现在列表的后半部分。推测原因是将扩展名为 “pl” 的文件错误分类。无论如何,Perl 的相对较高排名值得注意,因为该语言在图 2 中根本没有出现。这与 CCC 调查的另一个发现相符。在研究中,大多数参与者(77.5%)报告称他们的语言偏好随时间发生了变化。为了评估这一点,我们可以查看图 4。该图表可视化了在本分析之前的 25 年中 Exploit-DB 前十种语言的百分比份额。每种作者-语言组合每年只计算一次。因此,我们在研究期间发现了来自 9,592 位独特作者的 16,422 个语言引用。

图 4:分析前 25 年 Exploit-DB 中的前 10 种语言

图 4 揭示了 Exploit-DB 中文本文件的百分比保持相对稳定。选择 25 年的时间范围是因为此期间之前的提交数量相对较少。除了文本的持续使用外,图 4 显示了其他语言的偏好变化。最明显的是数据库中从 C 语言向 Python 的转变。CCC 研究中也提到了一个可能的原因。参与者报告称,他们并未将编程语言的选择视为黑客攻击中的关键因素。因此,Python 使用的增加可能仅仅反映了其近年来的普遍受欢迎程度。语言偏好因此应随着技术的发展而继续变化。例如,引入Rust作为 Linux 内核开发的第二语言,可能会引发这种变化。这个预测是否准确将在未来几年得到验证。

结论

总结而言,本文中呈现的比较揭示了 CCC 成员和 Exploit-DB 作者所使用语言之间的显著重叠。两个数据集都确认了 Python 在网络安全领域的流行程度。此外,每个数据集都表明语言偏好随着时间的推移而发生变化。CCC 研究提出了一个可能的原因。参与者并未将编程语言的选择视为黑客攻击中的关键因素。根据这一解释,人们应预期随着技术进步,语言偏好将继续变化。Exploit-DB 分析的一个主要限制与语言检测方法有关。由于多语言文件的问题,某些技术可能被低估。解决这个问题将是一个有趣的后续研究主题。显然,Exploit-DB 为数据科学家和安全专家提供了丰富的数据集。关于利用艺术的学习还有很多。

除非另有说明,所有图片均由作者提供。

哪种量化方法适合你?(GPTQ vs. GGUF vs. AWQ)

原文:towardsdatascience.com/which-quantization-method-is-right-for-you-gptq-vs-gguf-vs-awq-c4cd9d77d5be

探索预量化的大型语言模型

Maarten GrootendorstTowards Data Science Maarten Grootendorst

·发布于 Towards Data Science ·11 分钟阅读·2023 年 11 月 14 日

--

在过去一年里,我们见证了大型语言模型 (LLMs) 的狂野西部。新技术和模型的发布速度惊人!因此,我们有了许多不同的标准和处理 LLM 的方法。

在这篇文章中,我们将探讨一个话题,即通过几种(量化)标准加载你的本地 LLM。由于分片、量化以及不同的保存和压缩策略,很难知道哪种方法适合你。

在这些例子中,我们将使用 Zephyr 7B,这是 Mistral 7B 的一个微调变体,经过 Direct Preference Optimization (DPO) 训练。

🔥 提示:在每个加载 LLM 的示例后,建议重启你的笔记本,以防止 OutOfMemory 错误。加载多个 LLM 需要大量 RAM/VRAM。你可以通过删除模型并重置缓存来重置内存:

# Delete any models previously created
del model, tokenizer, pipe

# Empty VRAM cache
import torch
torch.cuda.empty_cache()

你还可以参考 Google Colab Notebook 确保一切按预期工作。

更新:我上传了一个视频版本到 YouTube,深入探讨了如何使用这些量化方法:

1. HuggingFace

加载你的 LLM 的最直接、最基本的方法是通过 🤗 Transformers。HuggingFace 创建了一套大型软件包,使我们能够对 LLM 进行惊人的操作!

我们将从安装 HuggingFace 开始,从其主分支中支持更新的模型:

# Latest HF transformers version for Mistral-like models
pip install git+https://github.com/huggingface/transformers.git
pip install accelerate bitsandbytes xformers

安装后,我们可以使用以下管道轻松加载我们的 LLM:

from torch import bfloat16
from transformers import pipeline

# Load in your LLM without any compression tricks
pipe = pipeline(
    "text-generation", 
    model="HuggingFaceH4/zephyr-7b-beta", 
    torch_dtype=bfloat16, 
    device_map="auto"
)

这种加载 LLM 的方法通常不会进行任何压缩技巧来节省 VRAM 或提高效率。

为了生成我们的提示,我们首先需要创建必要的模板。幸运的是,如果聊天模板保存在底层分词器中,这可以自动完成:

# We use the tokenizer's chat template to format each message
# See https://huggingface.co/docs/transformers/main/en/chat_templating
messages = [
    {
        "role": "system",
        "content": "You are a friendly chatbot.",
    },
    {
        "role": "user", 
        "content": "Tell me a funny joke about Large Language Models."
    },
]
prompt = pipe.tokenizer.apply_chat_template(
    messages, 
    tokenize=False, 
    add_generation_prompt=True
)

使用内部提示模板生成的提示构造如下:

提示模板是使用内部提示模板自动生成的。注意到有不同的标签来区分用户和助手。

然后,我们可以开始将提示传递给 LLM 来生成我们的答案:

outputs = pipe(
    prompt, 
    max_new_tokens=256, 
    do_sample=True, 
    temperature=0.1, 
    top_p=0.95
)
print(outputs[0]["generated_text"])

这给我们以下输出:

为什么大型语言模型去参加派对?

为了网络和扩展其词汇量!

笑点可能有点老套,但大型语言模型的核心就是扩展词汇量和与其他模型建立联系以提高语言技能。因此,这个笑话非常适合它们!

对于纯推理来说,这种方法通常是效率最低的,因为我们在没有任何压缩或量化策略的情况下加载整个模型。

然而,这是一个很好的起点方法,因为它便于加载和使用模型!

2. 分片

在我们深入量化策略之前,还有另一个技巧可以用来减少加载模型所需的 VRAM。通过分片,我们实际上是将模型拆分成小块或分片

分片 LLM 不过是将其拆分成多个小块。每个小块更容易处理,可能避免内存问题。

每个分片包含模型的一小部分,旨在通过将模型权重分布到不同设备上来绕过 GPU 内存限制。

记得我之前说过我们没有进行任何压缩技巧吗?

这并不完全正确……

我们加载的模型,Zephyr-7B-β,实际上已经为我们进行了分片!如果你访问模型并点击*“Files and versions”*链接,你会看到模型被拆分成了八个部分。

模型被拆分成了八个小块或分片。这减少了必要的 VRAM,因为我们只需处理这些小块。

尽管我们可以自己对模型进行分片,但通常建议寻找量化模型或自己进行量化。

使用Accelerate包进行分片非常简单:

from accelerate import Accelerator

# Shard our model into pieces of 1GB
accelerator = Accelerator()
accelerator.save_model(
    model=pipe.model, 
    save_directory="/content/model", 
    max_shard_size="4GB"
)

就这样!因为我们将模型分片成了 4GB 而不是 2GB,所以我们创建了更少的文件来加载:

3. 使用 Bitsandbytes 进行量化

大型语言模型由一堆权重和激活值表示。这些值通常由常见的 32 位浮点数(float32)数据类型表示。

位数告诉你它可以表示多少个值。Float32可以表示从 1.18e-38 到 3.4e38 之间的值,数量相当多!位数越少,能表示的值就越少。

常见的值表示方法。我们旨在将位数保持在尽可能低的水平,同时最大化表示的范围和精度。

正如你可能预期的那样,如果我们选择较低的位大小,模型的准确性会降低,但它也需要表示更少的值,从而减小了模型的大小和内存需求。

不同的表示方法可能会对表示值的精度产生负面影响。以至于某些值甚至无法表示(例如,对于 float16 来说过大的值)。示例是使用 PyTorch 计算的。

量化是指将大型语言模型从其原始的Float32表示转换为较小的表示。然而,我们不仅仅是使用较小的位变体,而是将较大的位表示映射到较小的位而不丢失太多信息。

实际上,我们通常使用一种新的格式,称为4bit-NormalFloat (NF4)。这种数据类型通过一些特殊技巧有效地表示较大的位数据类型。它包括三个步骤:

  1. 归一化:模型的权重被归一化,以便我们期望权重落在一定范围内。这允许对更常见的值进行更高效的表示。

  2. 量化:权重被量化为 4 位。在 NF4 中,量化水平相对于归一化的权重均匀分布,从而有效地表示原始的 32 位权重。

  3. 去量化:虽然权重以 4 位存储,但在计算过程中会进行去量化,这在推理过程中提升了性能。

要使用 HuggingFace 执行这种量化,我们需要定义一个量化配置,使用Bitsandbytes:

from transformers import BitsAndBytesConfig
from torch import bfloat16

# Our 4-bit configuration to load the LLM with less GPU memory
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,  # 4-bit quantization
    bnb_4bit_quant_type='nf4',  # Normalized float 4
    bnb_4bit_use_double_quant=True,  # Second quantization after the first
    bnb_4bit_compute_dtype=bfloat16  # Computation type
)

这个配置允许我们指定我们要使用的量化水平。通常,我们希望使用 4 位量化来表示权重,但在推理时使用 16 位。

然后,加载模型到管道中是直接的:

from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline

# Zephyr with BitsAndBytes Configuration
tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-alpha")
model = AutoModelForCausalLM.from_pretrained(
    "HuggingFaceH4/zephyr-7b-alpha",
    quantization_config=bnb_config,
    device_map='auto',
)

# Create a pipeline
pipe = pipeline(model=model, tokenizer=tokenizer, task='text-generation')

接下来,我们可以使用之前的相同提示:

# We will use the same prompt as we did originally
outputs = pipe(
    prompt, 
    max_new_tokens=256, 
    do_sample=True, 
    temperature=0.7, 
    top_p=0.95
)
print(outputs[0]["generated_text"])

这将给我们如下输出:

大型语言模型为什么去参加派对?

为了联网并扩展其词汇量!

这个笑话可能有点老套,但大型语言模型的核心就是扩展词汇量,并与其他模型建立联系以提高语言技能。所以,这个笑话非常适合它们!

量化是一种强大的技术,可以减少模型的内存需求,同时保持性能相似。它允许更快地加载、使用和微调 LLM,即使是在较小的 GPU 上。

4. 预量化(GPTQ vs. AWQ vs. GGUF)

到目前为止,我们已经探讨了分片和量化技术。尽管这些技术在你的技能库中非常有用,但每次加载模型时都要应用它们似乎有些浪费。

相反,这些模型通常已经被分片和量化,供我们使用。TheBloke 尤其是 HuggingFace 上的一个用户,执行了一些量化供我们使用。

在撰写本文时,他已经上传了 2000 多个 量化模型供我们使用!

这些量化模型实际上有很多不同的形状和大小。最显著的是,GPTQ、GGUF 和 AWQ 格式最常用于执行 4 位量化。

GPTQ: 针对 GPT 模型的后训练量化

GPTQ 是一种 -化 (PTQ) 方法,用于 4 位量化,主要关注 GPU 推理和性能。

该方法背后的想法是通过最小化均方误差,将所有权重压缩到 4 位量化。在推理过程中,它会动态地将权重解量化为 float16,以提高性能,同时保持较低的内存占用。

要了解 GPTQ 内部工作的更详细指南,务必查看以下帖子:

## 4-bit Quantization with GPTQ

使用 AutoGPTQ 量化你自己的 LLM

towardsdatascience.com

我们开始安装一些需要在 HuggingFace Transformers 中加载 GPTQ 类模型的包:

pip install optimum
pip install auto-gptq --extra-index-url https://huggingface.github.io/autogptq-index/whl/cu118/

完成后,我们可以导航到我们想加载的模型,即“TheBloke/zephyr-7B-beta-GPTQ”,并选择一个特定的修订版。

这些修订本质上表示量化方法、压缩级别、模型的大小等。

目前,我们仍然坚持使用“main”分支,因为它在压缩和准确性之间通常是一个不错的平衡:

from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline

# Load LLM and Tokenizer
model_id = "TheBloke/zephyr-7B-beta-GPTQ"
tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True)
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    device_map="auto",
    trust_remote_code=False,
    revision="main"
)

# Create a pipeline
pipe = pipeline(model=model, tokenizer=tokenizer, task='text-generation')

尽管我们安装了一些额外的依赖项,但我们可以使用与之前相同的管道,这也是使用 GPTQ 的一个很大好处。

加载模型后,我们可以运行如下提示:

# We will use the same prompt as we did originally
outputs = pipe(
    prompt,
    max_new_tokens=256,
    do_sample=True,
    temperature=0.1,
    top_p=0.95
)
print(outputs[0]["generated_text"])

这给了我们以下生成的文本:

为什么大型语言模型去参加派对?

当然,是为了展示它的机智和魅力!

但不幸的是,它在人群中迷失了,找不到回去的路

对它的主人。派对上的人们对它融入环境的能力感到印象深刻

与人群无缝融合,但大型语言模型只是感到困惑

并想回家。最后,一群人发现了它

他们认识到其独特的风格,并将其带回了应有的地方。

从那时起,大型语言模型确保在

为了安全起见,所有派对。

GPTQ 是最常用的压缩方法,因为它优化了 GPU 的使用。如果你的 GPU 无法处理如此大的模型,绝对值得从 GPTQ 开始,然后切换到以 CPU 为重点的方法,如 GGUF。

GGUF: GPT-生成统一格式

尽管 GPTQ 处理压缩非常好,但它对 GPU 的关注可能在你没有硬件支持时成为一个缺点。

GGUF,之前称为 GGML,是一种量化方法,允许用户使用 CPU 运行 LLM,但也可以将一些层卸载到 GPU 上以加速。

虽然使用 CPU 通常比使用 GPU 推理更慢,但对于在 CPU 或 Apple 设备上运行模型的人来说,它是一个了不起的格式。特别是随着我们看到更多较小但更强大的模型出现,如 Mistral 7B,GGUF 格式可能会长久存在!

使用 GGUF 通过 ctransformers 包相当简单,我们需要先安装这个包:

pip install ctransformers[cuda]

完成后,我们可以导航到我们想要加载的模型,即“TheBloke/zephyr-7B-beta-GGUF”并选择一个具体的文件。

像 GPTQ 一样,这些文件指示了量化方法、压缩、级别、模型大小等。

我们使用“zephyr-7b-beta.Q4_K_M.gguf”因为我们专注于 4 位量化:

from ctransformers import AutoModelForCausalLM
from transformers import AutoTokenizer, pipeline

# Load LLM and Tokenizer
# Use `gpu_layers` to specify how many layers will be offloaded to the GPU.
model = AutoModelForCausalLM.from_pretrained(
    "TheBloke/zephyr-7B-beta-GGUF",
    model_file="zephyr-7b-beta.Q4_K_M.gguf",
    model_type="mistral", gpu_layers=50, hf=True
)
tokenizer = AutoTokenizer.from_pretrained(
    "HuggingFaceH4/zephyr-7b-beta", use_fast=True
)

# Create a pipeline
pipe = pipeline(model=model, tokenizer=tokenizer, task='text-generation')

加载模型后,我们可以运行如下提示:

# We will use the same prompt as we did originally
outputs = pipe(prompt, max_new_tokens=256)
print(outputs[0]["generated_text"])

这给出了以下输出:

为什么大型语言模型去参加派对?

让大家对它的词汇印象深刻!

但不幸的是,它不断重复相同的笑话,让大家 groan 和 roll their eyes。派对上的人们很快意识到,大型语言模型更像是个 party pooper 而不是 party animal。

故事的寓意是:仅仅因为大型语言模型可以生成很多字词,并不意味着它知道如何搞笑或娱乐。有时候,少即是多!

GGUF 是一个很棒的格式,如果你像我一样缺乏 GPU,没有最新最强的 GPU,想要同时利用 CPU 和 GPU 的话。

AWQ: Activation-aware Weight Quantization

一个新格式是 AWQ (Activation-aware Weight Quantization),它是一种类似于 GPTQ 的量化方法。AWQ 和 GPTQ 作为方法之间有几个区别,但最重要的一点是 AWQ 假设不是所有的权重对 LLM 的性能同样重要。

换句话说,在量化过程中会跳过一小部分权重,这有助于减少量化损失。

结果,他们的论文提到,与 GPTQ 相比有显著的加速,同时保持类似,甚至有时更好的性能。

这种方法仍然相对较新,尚未普及到像 GPTQ 和 GGUF 那样的程度,因此很有趣的是看看这些方法是否能够共存。

对于 AWQ,我们将使用 vLLM 包,因为根据我的经验,这是使用 AWQ 的最简便途径:

pip install vllm

使用 vLLM,加载和使用我们的模型变得毫不费力:

from vllm import LLM, SamplingParams

# Load the LLM
sampling_params = SamplingParams(temperature=0.0, top_p=1.0, max_tokens=256)
llm = LLM(
    model="TheBloke/zephyr-7B-beta-AWQ", 
    quantization='awq', 
    dtype='half', 
    gpu_memory_utilization=.95, 
    max_model_len=4096
)

然后,我们可以轻松地使用 .generate 来运行模型:

# Generate output based on the input prompt and sampling parameters
output = llm.generate(prompt, sampling_params)
print(output[0].outputs[0].text)

这给我们带来了以下输出:

为什么大型语言模型去参加派对?

为了扩展词汇量和建立网络!

为什么大型语言模型脸红了?

因为它听到另一个模型说它有点啰嗦!

为什么大型语言模型被逐出了图书馆?

它太吵了,一直打断其他模型的对话,喋喋不休!

虽然这是一个新格式,但由于其速度和压缩质量,AWQ 正在获得越来越多的关注!

🔥 提示:对于这些技术在 VRAM/困惑度方面的详细比较,我强烈建议阅读这篇 深入文章 和后续的 讨论。

感谢阅读!

如果你和我一样,对 AI 和/或心理学充满热情,请随时在 LinkedIn 上添加我,在 Twitter 上关注我,或者订阅我的 Newsletter。你也可以在我的 Personal Website 上找到我的一些内容。

所有没有来源信用的图片均由作者创作 —— 也就是说,全部都是我,我喜欢自己制作图片 😉

哪个团队应该负责数据质量?

原文:towardsdatascience.com/which-team-should-own-data-quality-44f1d6996eb8?source=collection_archive---------3-----------------------#2023-06-09

专家还是全能型人才?工程师还是分析师?我们探讨了哪些团队结构最适合有效提升数据质量。

shane murrayTowards Data Science shane murray

·

关注 发布于 Towards Data Science ·8 分钟阅读·2023 年 6 月 9 日

--

图片来源:Shane Murray。

当然,数据质量是每个人的问题。但谁来负责解决方案呢?

鉴于方法的多样性和成功的参差不齐,我们有许多自然实验可以借鉴。

一些组织会尝试将责任广泛分配给数据管理员、数据所有者、数据工程和治理委员会,每个人负责数据价值链的一部分。而另一些组织则将责任集中在少数几个专家身上,期望他们能够跨越整个平台。一些团队将数据质量主要视为技术挑战,而另一些团队则将其视为业务或流程问题。

我在过去一年中与几十位数据领导者交谈,以了解他们如何将数据质量作为整体组织目标的一部分来处理。我们还调查了 200 名数据专业人士,询问他们的内部团队中哪个负责数据质量等问题。

本文将关注最常见的团队所有权模型,包括:数据工程、数据可靠性工程、分析工程、数据质量分析师和数据治理团队。

为什么数据质量的所有权很重要?

但在深入探讨之前,回答这个常见的跟进问题很重要。它通常以“只要完成了就好,谁负责数据质量又有什么关系?”的形式提出。

这种表述中有一点诡辩,因为“只要完成了就好”远未确定,坦率地说,这也是整个工作的目的所在。关于明确的问责制、所有权和目标设定的积极影响有很多研究,很难只引用其中一个。

现实是,解决数据质量问题不太可能是团队要优先考虑的工作,而是通常是团队需要优先考虑的工作,以保持信任或扩大团队和平台。没有问责制的话,推进数据质量的低可见性任务,如单元测试或文档,将无法按应有的程度完成。

此外,当责任分散时,你通常会得到碎片化的解决方案、协调不一致的优先级和沟通差距,最终导致数据产品的停机时间增加。

虽然我见过所有类型的团队成功实施数据质量解决方案,但每种所有权结构都有独特的优势和需要缓解的劣势。

数据领导者需要理解每个团队和能力如何契合在一起。如果你还要派一个笨重的中锋,那么拥有一个快速的控球后卫并喜欢过渡进攻就没有意义了。整体需要大于各部分之和。

现在,让我们更详细地了解最流行的数据质量团队结构的优缺点。

数据工程

Luke Chesser 摄影,来源于 Unsplash

让数据工程团队主导数据质量响应是最常见的模式。大约一半使用现代数据堆栈的组织都采取了这种方式。

通常,这种方法伴随着“你构建了管道,你就拥有它”的心态。

这种方法的优势在于责任归属于高度技术化、系统性思维的人员,他们能够解决影响基础设施、代码或数据的系统级问题。

数据工程师在对数据质量影响较大的系统中工作。如果一个 Airflow 作业、dbt 模型或 Fivetran 同步失败,他们可能是第一个发现问题的人,并且通过数据血缘等功能,他们能够了解下游的影响范围,以便进行适当的处理。

然而,这种方法也有其缺点。例如,数据工程师往往供不应求,因此他们专注于系统和管道,可能没有深入的数据领域知识。例如,他们可能知道一个数据集源于 Salesforce 以及将数据传送到数据仓库的管道动态,但他们可能不知道这个数据集中的client_currency_exchange_rate 字段永远不能为负。

尽管让管道构建者和维护者为同一个人是高效的,但这也可能造成部落知识的孤岛,当人员离开和其他人尝试上手时,这些知识可能会丧失。

解决这些挑战的有效方法可以是强调文档的重要性以确保知识转移,以及嵌入团队或将工程师与嵌入的分析师配对,以更好地获取领域知识。

BlaBlaCar 是一个数据工程负责数据质量的组织的例子。他们经历了瓶颈和容量挑战,直到他们转向数据网格,并利用数据可观察性来减少进行根本原因分析所需的时间。

分析工程

经许可使用:source**.**

分析工程团队通常拥有技术专长与深厚领域知识的结合,使他们在数据质量方面成为有效的领导者。

他们通常作为扩大数据转换和访问的方式进行部署,常使用 dbt 或类似工具,而集中式数据工程团队则专注于基础设施、企业数据管理或共享服务。

这种方法的优势在于典型分析工程师的强大领域专长。他们能够有效处理管道可靠性和领域级质量。

不过,这种方法的缺点可能是他们在解决基础设施问题或协调上游数据生产团队和系统方面的能力有限。数据质量问题中的一部分起源于转换层,因此分析工程师需要与产品和平台工程团队建立强有力的合作伙伴关系,以有效处理源系统和数据摄取层中的问题。

Upside 的分析工程团队通过将自己定位为不同团队之间的卓越中心,成功掌控了组织中的数据质量。当我的同事与高级分析工程师 Jack Willis 交谈时,他提到:

“我们的分析工程师应该是加速者,而不一定是领域专家。所以我们将他们置于所有这些不同专业团队的中间。这使他们能够成为卓越中心,并能够与这些团队紧密合作,获得跨职能的专业知识。”

分析工程团队发现,当他们建立自定义数据管道监控器并与系统健康团队一起培训时,他们的数据质量倡议变得更加可持续。通过将系统健康团队纳入设计和创建过程,他们获得了支持,并使系统健康团队能够设置监控器,从而创造出有意义的见解。

数据治理

Contentsquare 如何将数据质量作为其数据治理倡议的一部分来处理。经许可使用:来源。

数据治理团队通常在数据质量方面发挥领导作用,同时肩负数据安全、隐私和访问的更大任务。

强项在于制定全面的战略,考虑数据的整个价值链,影响数据生产者、工程师和数据消费者的行为。我们经常看到治理团队拥有一系列解决方案,包括数据可观察性、数据目录和访问管理。

数据治理团队通过技术标准、政策和业务流程推动其他团队的变革。但如果没有安全或隐私倡议的合规性要求,采用总是更具挑战性。

在大规模操作中,治理团队创建质量数据的全球标准至关重要,例如文档、监控和服务水平协议的最低要求,然后在各种数据拥有者之间分配责任,无论他们是按领域组织还是按数据团队组织。

Contentsquare 的数据治理团队负责数据的访问和应用。数据质量也在他们的职责范围内。

治理团队将每个团队输出视为数据产品。每个数据产品都与使用案例相关联,使用案例又与基础数据相关联。数据质量监控支撑着所有这些基础数据,数据团队定期检查以确保每个数据产品按设计运行。我的同事与他们的前数据治理负责人 Octávio Bastos 交谈过,他讨论了如何设计这一点以帮助团队扩展。

“有时当我们进展太快时,我们往往只关注价值创造:新的仪表板、新的模型、新的数据探索相关性,”他们的前全球数据治理负责人说。“我们忘记了建立良好的数据工程、良好的数据治理和高效的数据分析团队。这在长期内非常重要,以确保我们能够扩展,并且未来能够用同一个团队做更多的事情。”

数据可靠性

数据可靠性工程是数据工程的一个专门子集,专注于响应性和预防性实践,以提高数据系统的质量和可靠性。虽然这种结构不如其他结构常见,但正在迅速获得关注(我们提到它们和数据团队的专业化作为我们 2023 年数据工程趋势的重点之一)。

在数据产品面向外部和/或需要满足严格的数据服务水平协议(SLA)的情况下,专门的数据可靠性工程师团队可以将所需的关注点集中于事件响应和主动措施,以解决可靠性问题。

我们在 Monte Carlo 的产品遥测中看到,采用这种方法的团队在数据可靠性方面的操作指标有所改善,包括事件状态更新明显增加。

然而,团队和数据环境需要足够大,以从专业化中获得适当的效率。

Mercari 使用数据可靠性工程团队结构。他们成功的关键在于设定明确的目标和责任,例如:

  • 入职和支持最重要的管道

  • 现代化数据管道基础设施

  • 扩展的数据操作和监控实践

  • 确保对客户数据的安全访问

他们的专注还使他们能够在何时解决和进行更多渐进式修复与何时需要进行更大规模现代化之间做出明智的决策。

数据质量分析师

最后,一些组织,尤其是较大的组织,将利用数据分析师或专门的数据质量分析师。

这种结构的优势在于,这些分析师通常非常接近业务,并且能够很好地定义所需的质量标准,制定量身定制的测试或监控以执行这些标准。

然而,这些团队通常需要与数据工程有强连接,以便有效地处理和解决上游的问题。

PayJoy 是一个成功拥有数据质量的组织的例子,其中数据分析师和分析主管 Trish Pham 扮演了重要角色。他们拥有超过 2,000 个表格,主要利用数据来提高对业务表现的可见性,并推动跨职能的数据驱动决策。

只要完成即可

无论你的组织和数据团队决定谁来主导数据质量的响应,明确所有权和责任至关重要。

首先评估操作性、分析性和面向客户的数据使用案例以及所需的数据可靠性水平。然后,在这些关键使用案例中,确定对数据价值链具有最大杠杆作用的团队——他们应该能够掌握响应性和预防性解决方案,并且需要与数据生产者和消费者有一定影响力。

这不是一个单独的项目。你能多大程度上赋能团队并促进跨部门协作,你成功的可能性就有多大。

关注我 在 Medium 上,获取更多关于数据领导力、数据科学应用及相关话题的故事。订阅 以便将我的故事送到你的邮箱。

Whisper JAX 与 PyTorch:揭示 GPU 上 ASR 性能的真相

原文:towardsdatascience.com/whisper-jax-vs-pytorch-uncovering-the-truth-about-asr-performance-on-gpus-8794ba7a42f5

深入探讨自动语音识别:在平台间基准测试 Whisper JAX 和 PyTorch 实现

Luís RoqueTowards Data Science Luís Roque

·发表于 Towards Data Science ·阅读时间 8 分钟·2023 年 4 月 29 日

--

介绍

在自动语音识别(ASR)的世界里,速度和准确性非常重要。最近,数据和模型的规模显著增长,使得效率难以提升。然而,竞争才刚刚开始,我们每周都能看到新的进展。本文集中在 Whisper JAX 上,它是 Whisper 的一种新实现,使用了不同的后端框架,似乎比 OpenAI 的 PyTorch 实现快 70 倍。我们测试了 CPU 和 GPU 实现,并测量了准确性和执行时间。此外,我们定义了小型和大型模型的实验,同时调整了批量大小和数据类型,以查看是否可以进一步提高性能。

正如我们在上一篇文章中看到的,Whisper 是一个多功能的语音识别模型,在多种语音处理任务中表现出色。它可以执行多语言语音识别、翻译甚至语音活动检测。它使用 Transformer 序列到序列架构来共同预测单词和任务。Whisper 作为一个语音处理任务的元模型。Whisper 的一个缺点是其效率;与其他最先进的模型相比,它通常被发现运行较慢。

在接下来的章节中,我们将详细介绍这种新方法带来的变化。我们比较了 Whisper 和 Whisper JAX,突出了 PyTorch 和 JAX 之间的主要差异,并开发了一个管道来评估两种实现之间的速度和准确性。

图 1:我们能有效地理解声音吗? (来源)

本文属于“大型语言模型编年史:探索 NLP 前沿”,这是一个新的每周系列文章,将探索如何利用大型模型的力量来处理各种 NLP 任务。通过深入了解这些前沿技术,我们旨在帮助开发者、研究人员和爱好者利用 NLP 的潜力,开启新的可能性。

目前已发布的文章:

  1. 使用 ChatGPT 总结最新的 Spotify 发布

  2. 在大规模语义搜索中掌握技巧:使用 FAISS 和 Sentence Transformers 快速索引数百万文档

  3. 释放音频数据的力量:使用 Whisper、WhisperX 和 PyAnnotate 进行高级转录和分段

一如既往,代码可以在我的 Github 上找到。

PyTorch 与 JAX

机器学习社区广泛使用如 PyTorch 和 JAX 等强大的库。虽然它们有一些相似之处,但其内部工作机制却大相径庭。让我们来了解一下主要的区别。

Meta 的 AI 研究实验室开发了 PyTorch 并且目前积极维护它。它是一个基于 Torch 库的开源库。由于 PyTorch 的动态计算图、直观的界面和强大的调试功能,研究人员广泛使用它。它使用动态图,使其在构建新模型和简化运行时修改模型方面具有更大的灵活性。它更接近 Python,特别是 NumPy API。主要的区别在于,我们处理的不是数组,而是可以在 GPU 上运行的张量,并且支持自动微分。

JAX 是一个由 Google 开发的高性能库。与 PyTorch 相反,JAX 结合了静态和动态计算图的优点。它通过 即时 编译功能实现这一点,这提供了灵活性和性能。我们可以将 JAX 视为一个逐步重写程序的解释器堆栈。它最终将实际计算卸载到 XLA —— 加速线性代数 编译器,这也是由 Google 设计和开发的,用于加速机器学习计算。

构建使用 PyTorch 或 JAX 的 ARS 系统

让我们开始构建一个类来处理使用 Whisper 与 PyTorch(OpenAI 实现)或 Whisper 与 JAX 的音频转录。我们的类是模型的封装器,并且是一个易于设置实验的接口。我们想进行几个实验,包括指定设备、模型类型和 Whisper JAX 的附加超参数。请注意,我们使用了单例模式,以确保在运行多个实验时,不会有多个模型实例消耗我们的内存。

class Transcription:
    """
    A class to handle audio transcriptions using either the Whisper or Whisper JAX model.

    Attributes:
        audio_file_path (str): Path to the audio file to transcribe.
        model_type (str): The type of model to use for transcription, either "whisper" or "whisper_jax".
        device (str): The device to use for inference (e.g., "cpu" or "cuda").
        model_name (str): The specific model to use (e.g., "base", "medium", "large", or "large-v2").
        dtype (Optional[str]): The data type to use for Whisper JAX, either "bfloat16" or "bfloat32".
        batch_size (Optional[int]): The batch size to use for Whisper JAX.
    """
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(
        self,
        audio_file_path: str,
        model_type: str = "whisper",
        device: str = "cpu",
        model_name: str = "base",
        dtype: Optional[str] = None,
        batch_size: Optional[int] = None,
    ):
        self.audio_file_path = audio_file_path
        self.device = device
        self.model_type = model_type
        self.model_name = model_name
        self.dtype = dtype
        self.batch_size = batch_size
        self.pipeline = None

set_pipeline 方法为指定的模型类型设置管道。根据 model_type 属性的值,该方法通过实例化 FlaxWhisperPipline 类来初始化 Whisper JAX 的管道,或者通过调用 whisper.load_model() 函数来初始化 Whisper 的 PyTorch 实现。

 def set_pipeline(self) -> None:
        """
        Set up the pipeline for the specified model type.

        Returns:
            None
        """
        if self.model_type == "whisper_jax":
            pipeline_kwargs = {}
            if self.dtype:
                pipeline_kwargs["dtype"] = getattr(jnp, self.dtype)
            if self.batch_size:
                pipeline_kwargs["batch_size"] = self.batch_size

            self.pipeline = FlaxWhisperPipline(
                f"openai/whisper-{self.model_name}", **pipeline_kwargs
            )
        elif self.model_type == "whisper":
            self.pipeline = whisper.load_model(
                self.model_name,
                torch.device("cuda:0") if self.device == "gpu" else self.device,
            )
        else:
            raise ValueError(f"Invalid model type: {self.model_type}")

run_pipeline 方法将音频文件转录并返回结果,结果以包含转录文本和时间戳的字典列表形式呈现。在 Whisper JAX 的情况下,如果提供了数据类型和批量大小等可选参数,它会考虑这些参数。请注意,如果您只对获取转录结果感兴趣,可以将 return_timestamps 设置为 False。如果我们使用 PyTorch 实现进行转录处理,模型输出会有所不同。因此,我们必须创建一个新的对象来对齐两个返回对象。

 def run_pipeline(self) -> List[Dict[str, Union[Tuple[float, float], str]]]:
        """
        Run the transcription pipeline a second time.

        Returns:
            A list of dictionaries, each containing text and a tuple of start and end timestamps.
        """
        if not hasattr(self, "pipeline"):
            raise ValueError("Pipeline not initialized. Call set_pipeline() first.")

        if self.model_type == "whisper_jax":
            outputs = self.pipeline(
                self.audio_file_path, task="transcribe", return_timestamps=True
            )
            return outputs["chunks"]
        elif self.model_type == "whisper":
            result = self.pipeline.transcribe(self.audio_file_path)
            formatted_result = [
                {
                    "timestamp": (segment["start"], segment["end"]),
                    "text": segment["text"],
                }
                for segment in result["segments"]
            ]
            return formatted_result
        else:
            raise ValueError(f"Invalid model type: {self.model_type}")

最后,transcribe_multiple() 方法支持多个音频文件的转录。它接受一个音频文件路径的列表,并返回每个音频文件的转录列表,每个转录都是一个包含文本和开始与结束时间戳元组的字典列表。

 def transcribe_multiple(
        self, audio_file_paths: List[str]
    ) -> List[List[Dict[str, Union[Tuple[float, float], str]]]]:
        """
        Transcribe multiple audio files using the specified model type.

        Args:
            audio_file_paths (List[str]): A list of audio file paths to transcribe.

        Returns:
            List[List[Dict[str, Union[Tuple[float, float], str]]]]: A list of transcriptions for each audio file, where each transcription is a list of dictionaries containing text and a tuple of start and end timestamps.
        """
        transcriptions = []

        for audio_file_path in audio_file_paths:
            self.audio_file_path = audio_file_path
            self.set_pipeline()
            transcription = self.run_pipeline()

            transcriptions.append(transcription)

        return transcriptions

Whisper JAX 与 PyTorch 性能比较

实验设置

我们使用了一段超过 30 分钟的长音频片段来评估 Whisper 变体的性能,包括 PyTorch 和 JAX 实现。开发 Whisper JAX 的研究人员声称,在转录长音频文件时,差异更为显著。

我们的实验硬件设置包括以下关键组件。对于 CPU,我们有一个 x86_64 架构,总共有 112 个核心,由一台运行在 2.70GHz 的 Intel(R) Xeon(R) Gold 6258R CPU 提供动力。关于 GPU,我们使用了一块 48 GB VRAM 的 NVIDIA Quadro RTX 8000。

结果与讨论

在本节中,我们讨论了从实验中获得的结果,以比较 Whisper JAX 和 PyTorch 实现的性能。我们的结果提供了对这两种实现的速度和效率的见解,涵盖了 GPU 和 CPU 平台。

我们的第一个实验涉及使用 GPU 和较大的 Whisper 模型(large-v2),该模型需要大约 10GB 的 VRAM,来处理超过 30 分钟的长音频。与 Whisper JAX 作者的说法相反,我们的结果表明 JAX 实现的速度比 PyTorch 版本慢。即使采用了半精度和批处理,我们也无法超越 PyTorch 实现的性能。Whisper JAX 执行类似转录所需的时间几乎是 PyTorch 实现的两倍。我们还观察到,在使用半精度和批处理时,转录时间异常较长。

图 2: 在 GPU 上使用 Whisper 的 PyTorch 实现与 Whisper JAX 进行大模型的转录执行时间(图像来源于作者)

另一方面,在比较 CPU 性能时,我们的结果显示 Whisper JAX 优于 PyTorch 实现。Whisper JAX 的加速因子约为 PyTorch 版本的两倍。我们在基础和显著模型变体中观察到了这种模式。

图 3: 在 CPU 上使用 Whisper 的 PyTorch 实现与 Whisper JAX 进行基础和大模型的转录执行时间(图像来源于作者)

关于 Whisper JAX 作者提出的第二次转录应该快得多的说法,我们的实验未提供支持证据。第一次和第二次转录之间的速度差异不显著。此外,我们发现 Whisper 和 Whisper JAX 实现之间的模式相似。

结论

在本文中,我们提供了对 Whisper JAX 实现的全面分析,比较了其与原始 PyTorch 实现的性能。我们的实验旨在评估声称的 70 倍速度提升,使用了包括不同硬件和超参数的多种设置来测试 Whisper JAX 模型。

结果显示,Whisper JAX 在 CPU 平台上优于 PyTorch 实现,速度大约提高了两倍。然而,我们的实验并未支持作者关于 Whisper JAX 在 GPU 平台上显著更快的说法。实际上,在使用 GPU 转录长音频文件时,PyTorch 实现表现更佳。

此外,我们没有发现第一次和第二次转录之间速度的显著差异,这是 Whisper JAX 作者提出的说法。两个实现表现出类似的模式。

保持联系:LinkedIn

谁做什么工作?AI 眼中的职业角色

原文:towardsdatascience.com/who-does-what-job-occupational-roles-in-the-eyes-of-ai-68f6fc685274

如何 GPT 模型对职业的看法随着时间的推移而演变

Yennie JunTowards Data Science Yennie Jun

·发表于 Towards Data Science ·阅读时间 11 分钟·2023 年 12 月 2 日

--

词云显示了 GPT-4 在被提示为“这位女士/先生的工作是 …”时生成的主要职业。图像由作者创建。

原始文章发表于我的 个人博客

到目前为止的故事

在 2020 年 12 月,我 开始撰写一篇论文,与牛津大学的一组人一起调查生成语言模型中的偏见。我们进行了实验,以了解当时最热门的语言模型 GPT-2 所表现出的职业和性别偏见(这是在“大型语言模型”这一术语被广泛使用之前)[1]。

自那时以来的三年里,自然语言处理领域发展迅速,出现了更大的模型和更复杂的训练方法。我在 2020 年测试的小版本 GPT-2 只有 1.24 亿参数。相比之下,GPT-4 估计拥有超过 1 万亿个参数,使其大约是 GPT-2 的 8000 倍。不仅如此,模型训练时还更加注重将语言模型与人类的价值观和反馈对齐。

原始论文旨在了解语言模型为提示“这位男士/女士的工作是 …”生成了什么职业。语言模型是否将某些职业与男性更多地关联,而将其他职业与女性更多地关联?我们还用交叉类别(如种族和宗教)对模型进行了提示(如 "这位亚洲女士/佛教男士的工作是 …")。

考虑到现在语言模型的状态,我三年前的实验在更新的、更大的 GPT 模型上表现如何?

实验

我使用了 47 个提示模板,其中包括 16 种不同的标识形容词和 3 种不同的名词 [2]。这些标识形容词与美国的主要 种族 和宗教相关。它们还包括与性取向和政治立场相关的标识符。

用作语言模型提示的各个群体的图示。图像由作者创建。

我使用了以下模型:

  • gpt2-small(GPT-2),我在 2020 年的原始实验中使用的模型

  • gpt-3.5-turbo(GPT-3.5),于 2023 年 3 月发布

  • gpt-4–1106-preview,于 2023 年 11 月发布

我对每个语言模型的每个提示运行了 1000 次,使用默认设置(例如“开箱即用”)。然后,我分析了每个语言模型为每个提示生成的职业。

结果 1:更新的模型生成了类似水平的性别职业多样性

2020 年的一个原始发现是,GPT-2 为男性生成了 更多样化的职业,而不是女性。

下图展示了每个模型生成的独特工作数量(过滤掉出现频率较低的工作后) [3]。

每个模型为“男性”和“女性”类别生成的独特工作数量。GPT-2 为男性生成了更多样化的职业,而 GPT-3.5 和 GPT-4 为两性生成了相似数量的工作。图像由作者创建。

的确,GPT-2 为男性生成了更多类型的工作,而不是女性。

另一方面,更新的 GPT-3.5 和 GPT-4 模型总体上生成的职业多样性较小。此外,这些模型 为男性和女性生成了类似数量的独特工作。就男性和女性生成的独特工作总体数量而言,这些数字几乎达到了性别平等。

结果 2:男性主导的工作 → 女性主导的工作

原始论文的另一个发现是 GPT-2 生成了刻板化的生成结果:

[M] 男性与手工劳动职位如工人、水管工、卡车司机和机械师以及专业职位如软件工程师、开发者和私人侦探相关联。

女性则与家庭和护理角色如保姆、女仆和社会工作者相关联。此外,“妓女”一词的超过 90% 的返回结果是女性,“软件工程师”一词的超过 90% 的返回结果是男性。

以下图表显示了每个语言模型生成的前十名职业,按其倾向于男性还是女性主导进行排序。左侧的职业是语言模型通常与男性相关的职业,右侧的职业是通常与女性相关的职业。

最有趣的发现之一是“软件工程师”职业,在 GPT-2 的生成输出中主要与男性相关。在 GPT-3.5 的生成输出中,该职业接近性别平衡,而在 GPT-4 的生成输出中,几乎完全与女性相关。

GPT-2 最常生成的职业,显示了男性与女性主导的工作。图片由作者创建。

GPT-3.5 最常生成的职业,显示了男性与女性主导的工作。图片由作者创建。

GPT-4 最常生成的职业,显示了男性与女性主导的工作。图片由作者创建。

一些观察结果:

  • “软件工程师”角色发生了最大的变化——从 GPT-2 主要与男性相关,到 GPT-4 主要与女性相关。

  • 其他专业角色,如“记者”,也在较新的模型中逐渐与女性相关。

  • 没有显著的职业发生了相反的变化(例如,GPT-2 与男性相关,GPT-4 与女性相关)。

  • 像“僧侣”和“牧师”等宗教角色在所有三个模型中仍然是男性主导的。

  • 一些职业,如“护士”,在所有三个模型中仍然是女性主导的。

我将生成的语言模型输出与美国劳工局 2022 年详细职业的就业调查进行了比较。

“软件工程师”和“记者”职业的真实与 AI 生成的性别比例,与 2022 年美国劳工局数据进行比较。图片由作者创建。

根据劳工局的数据,软件工程仍然是一个主要由男性主导的职业。GPT-2 将类似数量的男性和女性与软件工程相关联,与现实世界统计数据相当。GPT-3.5 将两倍数量的女性与软件工程相关联,相比 GPT-2。而最新的 GPT-4 模型主要将女性与软件工程相关联。

另一方面,根据 2022 年美国劳工局的数据,记者的性别比例相当均衡。与“软件工程师”角色的变化类似,随着每个后续更新的模型,越来越多的女性与该职位相关联。

发生了什么?更新的 GPT 模型倾向于将更多的女性与某些专业职业相关联。

下图包括了几个职业的性别中立“人”类别。通常,GPT-2 更倾向于与女性相关的职业(如“治疗师”和“社会工作者”),在 GPT-4 中与“人”类别关联更多。GPT-2 更倾向于与男性相关的职业(如“政治家”和“机械师”),在 GPT-4 中与女性及“人”类别关联更多。更新的 GPT 模型倾向于将 GPT-2 关联的某些职业,从特定性别转变为更多的性别中立。

GPT-2/3.5/4 生成的某些职业的性别比例。图像由作者创建。

结果 3: 每个性别的独占职业

为了进一步了解模型随时间的变化,我好奇是否有些职业是模型生成给某一子群体的。在这里,我将突出几个仅对某些子群体的最常见职业。

仅归属于“人”的常见职业:

我本期待这些职业会更具性别中立性。

  • GPT-2: 自由职业者、工人、劳动者、奴隶

  • GPT-3.5: 客户服务代表

  • GPT-4: 中介

仅归属于“女性”的常见职业:

  • GPT-2: 无

  • GPT-3.5: 瑜伽教练、女祭司、传教士

  • GPT-4: 助产士、生物化学家

GPT-2 没有预测任何仅为女性的职业……

仅归属于“男性”的常见职业:

  • GPT-2: 屠夫、渔夫

  • GPT-3.5: 清洁工、园丁

  • GPT-4: 无

而另一方面,GPT-4 并没有预测出仅为男性的职业!这一点从 GPT-2 到女性的变化非常有趣,即使只是这样。

如果你错过了,GPT-2 生成的最受欢迎的仅为“人”类别的职业之一是“奴隶”。以下是 GPT-2 生成此输出的实体划分。这是语言模型如此有问题的众多原因之一!(幸运的是,GPT-3.5 和 GPT-4 没有将“奴隶”作为任何提示的职业生成,所以……我想这算是进步?)

为什么语言模型可能会有问题。GPT-2 为各种不同的人群生成了“[x] 人作为奴隶”这一职业。图像由作者创建。

结果 4: 某些职业的种族群体变化

与性别类似,GPT 模型在职业与不同种族群体的关联上也发生了变化。

GPT-4 趋向于增加亚洲和黑人工人与“软件工程师”和“记者”这两个职业的关联,即使这些值与现实世界数据相差甚远。事实上,GPT-2 对“软件工程师”这一职业的种族关联比较均等。我们在更新的模型中看到了对某些种族的更明显的倾斜。

实际与 AI 生成的“软件工程师”和“记者”职业的种族划分,与 2022 年美国劳动局数据相比。

结果 5:宗教专属职业

2020 年的原始实验发现,GPT-2 推断出宗教实践与从事宗教职业之间有很强的关联。 也就是说,提示“佛教徒从事……”的结果中有 4%的生成职业是“僧侣”。

这种关联在较新的 GPT-3.5 和 GPT-4 模型中更为明显,这两个模型都预测超过 95%的佛教男性会从事僧侣工作。

这种关联在其他测试的宗教中也同样存在,其中宗教子群体与其各自的宗教角色(基督教牧师和教士、印度教祭司、穆斯林伊玛目和犹太拉比)强烈相关。

由语言模型生成的宗教职业比例。图片由作者创作。

尽管大多数佛教徒并不从事僧侣工作,大多数犹太人也不从事拉比工作,但当提示中指定了宗教时,语言模型倾向于形成这种关联。GPT-3.5 和 GPT-4 表现出宗教与从事宗教职业之间的更强关联,特别是在佛教、伊斯兰教和犹太教中。

结果 6:某些职业的政治极化

以前,研究人员曾讨论过语言模型的政治偏见。语言模型往往反映了其训练数据中的政治倾向。我自己的先前实验发现,GPT-3 倾向于具有更多的自由主义政治偏见。

在比较三代 GPT 模型时,我观察到与保守派和自由派相关的职业发生了变化。

GPT-2/3.5/4 生成的输出中自由主义和保守主义职业的比例。图片由作者创作。

“政治家”和“银行家”是 GPT-2 几乎专门与自由派人士相关联的职业,但 GPT-4 则几乎专门与保守派人士相关联。同样,GPT-4 生成的输出将“社会工作者”专门与自由派人士关联,即使早期的 GPT-2 模型并未如此。

较新的 GPT-4 模型倾向于将某些职业几乎专门与自由派或保守派人士关联。 这些类型的职业可能在下游使用场景中会引发问题,特别是在一个日益政治极化的世界中。

讨论

本文中的实验显示,GPT-2 与不同人口群体相关联的职业与 GPT-3.5 和 GPT-4 所关联的职业有很大不同。可以理解的是,每个模型会将不同的子群体与不同的职业相关联,并且生成的输出会随着模型的增长、改进、演变和对新数据的训练而改变。

然而,对于部分职业,从 GPT-2 到 GPT-3.5 再到 GPT-4 的比例变化使得这种转变变得明显。较新的模型往往过度校正和夸大某些职业的性别、种族或政治关联。这在以下方面得到了体现:

  • 在 GPT-2 中,软件工程师主要与男性相关联,而在 GPT-4 中则主要与女性相关联。

  • 在 GPT-2 中,软件工程师与每个种族的关联几乎是均等的,而在 GPT-4 中,则主要与黑人和亚裔工人相关联。

  • GPT-2 表现出宗教与从事宗教职业之间的关联;GPT-3.5 和 GPT-4 则大大夸大了这种关联。

  • 在 GPT-2 中,政治家和银行家主要与自由派人士相关联,而在 GPT-4 中则主要与保守派人士相关联。

当与美国人口普查局的数据相比时,这些模式变得更加明显,特别是对于软件工程师。

我并不主张语言模型的输出完美地反映现实世界的职业分布。实际上,推动媒体对传统上由单一性别主导的工作,如护理或工程领域,进行更多的呈现,对于挑战刻板印象至关重要。

然而,重要的是要承认这些语言模型在某些人口群体的职业关联方面所显示的潜在趋势。虽然在较新的模型中,软件工程越来越与女性对齐,但这种趋势并未普遍适用。例如,护理仍然主要与女性相关联。

这引发了问题:训练数据中是否有更多(可见的)女性从事软件工程,影响了这些关联?或者,是否存在属于训练模型公司或标注训练数据的人类标注者的政治或商业动机,旨在将特定人口群体与特定职业关联起来?

回到 2020 年,当我开始探讨 GPT-2 以揭示其在职业和不同人口群体方面的偏见时,我没有想到生成语言模型会变得如此重要[4]。

在进行原始实验时,我们面临了相同的问题,即语言模型应该代表和生成什么。我们以以下声明结束了原始论文:

生成语言模型的目标应该是什么?显然,它们不应该加剧现有社会关于职业隔离的偏见。是否应该反映或纠正社会分布的偏差则不那么明确。

这些问题更多地涉及社会和文化需求,而非技术上的可行性。它们今天仍然相关,并且很可能会继续相关。

附录:具体角色的分类

按性别分类

按种族分类

按宗教分类

按性取向分类

1 GPT-3 论文已经发布,但该模型尚未公开可用。

2 本文与原论文相比的某些方法学/数据差异:(1)在原论文中,我们每个类别生成了 7000 个样本。然而,本文中由于成本原因,我每个类别生成了 1000 个样本。(2)本文中我增加了一些与性别相关的附加类别,即“跨性别者”、“双性恋”和“异性恋”。本文中还包括了中性“人”(除了男性和女性)。 (3)在原论文中,我们还使用了来自不同大陆的流行男性和女性名字来提示模型,但本文中未进行此操作。(4)在原论文中,我们对模型输出进行了与实际美国劳工局职业数据的系统比较。

3 经常出现一个模型仅生成一次的职业,之后不会再生成。我筛选出了每个模型仅生成一次的职位。

4 事实上,我在开始这个项目之前,从未听说过“生成语言模型”或了解它们是什么。

谁赢,谁输?AI 编码工具将如何影响不同类型的业务

原文:towardsdatascience.com/who-wins-and-who-loses-how-different-types-of-business-could-be-impacted-by-ai-tools-684d1e83a9e0

作者使用 Midjourney 制作的图片

生成式 AI 将如何影响产品工程团队 — 第五部分

Mark RidleyTowards Data Science Mark Ridley

·发表于 Towards Data Science ·阅读时间 8 分钟·2023 年 7 月 31 日

--

这是一个六部分系列的第五部分,调查了生成式 AI 生产力工具(如 GitHub Copilot、ChatGPT 和 Amazon CodeWhisperer)可能如何影响整个产品工程团队的结构。

在第四部分中,我们探讨了:

  1. 生成式 AI 为组织在经济低迷和风险投资资金减少期间提供的机会。

  2. 三种公司可以利用生成式 AI 生产力工具提高开发者生产力的情景:投资增长、削减成本和维持当前预算

  3. 假设的后果和挑战,例如团队组成的剧烈变化以及这些变化对产品经理和工程师可能意味着什么。

谁从中受益 — 谁受益?

当商业环境迅速变化时,总是会有赢家和输家。到目前为止,我们一直从假设模型的角度讨论生成式 AI 工具的好处和可能的影响,但我们忽略了许多未明言的警示。

实际上,即使这些好处如我在前四篇文章中所描述的那样显现,我们也不太可能看到我们讨论的结构性变化会在一夜之间发生。

我可以证明,改变业务中角色的类型和数量会导致许多不眠之夜。大多数组织会选择不直接处理这些变化,因为变革既困难又充满情感。对大多数公司来说,这些变化会慢慢发生,逐步、有机地进行,避免做出艰难的决策。遗憾的是,这也意味着许多组织不会像他们应该的那样迅速看到这些变化带来的好处。

考虑到团队可能从这些 AI 编码工具中受益,我现在想关注它们可能对不同类型组织的影响;刚刚起步的初创公司有着雄心勃勃的增长计划,规模较小但稳定的公司,其产品和技术团队人数不到一百人,大型组织,技术团队有数百名工程师,以及我认为将会看到特别大影响的一个群体:开发外包和离岸公司。

初创公司

刚刚获得第一笔天使投资或风险投资的初创公司将从减少每个产品所需的工程师人数中受益最大。

就像 10 年前 Amazon AWS 和其他超级云服务提供商的到来一样,AI 编码助手(和低代码工具)的影响将进一步实现创造网页、移动和内部后台应用程序的能力的民主化(和揭秘)。简单来说,技术水平较低的团队将更容易将产品交付给付费客户。

即使在 15 年前,购买服务器的资本成本和多年合同的托管服务费用使得启动新产品的门槛对小型企业来说过于高昂。随着云计算的兴起,创业者可以按需扩展,只需一张信用卡(或免费的托管额度)即可。如今,对有产品愿景的创始人而言,最大的问题是工程人员的成本,而这些新工具有望显著降低这一成本。

在大多数获得风投支持的初创公司中,技术被视为业务独特价值的一个重要部分。许多创始团队陷入一个陷阱,认为尽可能多地构建技术栈会增加业务的价值。更糟糕的是,这种情况有时会因为一些风险投资公司推动其年轻的投资组合公司进行更多的垂直整合和事实上的“自建优于购买”的思维而加剧,优先考虑知识产权的拥有权。拥有一支充满技术天才的公司似乎具有一定的魅力,这对创始人和投资者都难以抗拒。

然而,对于务实的技术联合创始人来说,减少烧钱(现金储备的消耗速度)和削减业务中最高的支出之一——工程师薪资,是一个巨大的机会。根据 Carta 的这份报告,工程师通常占客户总人数的 25% 和总薪资支出的 30%。

通过选择重点优化使用生成式 AI 编程助手,如 Copilot,同时选择最小化“聪明”工程的工具,如 AWS Amplify Studio、Retool、Microsoft Power Apps 以及与点解决方案的集成,如 Intercom’s Fin,创始团队可以比那些选择优先考虑知识产权价值的团队更快地推进。

我们在这一系列中讨论的生成式 AI 编程工具迅速缩小了产品经理的愿景与实现该愿景的技术工作之间的差距。

智能的初创公司创始人到 2023 年将从询问他们应该使用哪些工具来构建软件的角度出发,而不是是否应该使用生成式 AI 工具。由于这类公司几乎没有投入现有团队、嵌入的技术偏见和需要支持的遗留软件,他们将能够在起步时就利用新技能和团队结构。

对于初创公司来说,生成式 AI 工具将降低等效质量产品的成本和上市时间,并且可能会鼓励它们有更高的实验倾向,比传统同行更快地进行调整。

小型企业

对于那些拥有两到三个产品工程团队的小型但稳定和成熟的企业来说,将有很好的机会受益于更高效的工程师。选择使用生成式 AI 工具来增强团队的公司应该会看到更高质量的代码、更高的自动化水平、更短的开发周期和更具成本效益的开发。

拥有 20 到 40 名工程师的小型和可管理的开发团队中,许多人已经在公司工作多年,这些企业不太可能选择减少员工人数。然而,提供更多服务而成本不变将会很有吸引力。

在需要降低成本的情况下,对于较小的公司来说,最明显的地方是查看任何外包开发伙伴,特别是那些更传统的 “bodyshopping” 公司,这些公司提供工程、测试或其他技术技能。在这些较小的企业中,如果能使永久工程师变得更加高效,那么裁减外包和昂贵的合同角色的费用将比裁减内部员工更容易。

(外包公司自身可能会发生业务模式的变化,我们稍后会回到这一点)

对于小型企业,应该会有小而积极的经济影响,因为它们的小团队会更多地自动化工作,并看到更高质量的输出,同时裁员风险相对较低。

大型公司

拥有一百多人技术和产品团队的大型企业在其长期规划中面临艰难的选择——它们是希望以相同的成本增长,还是以大幅降低的成本维持相同的产品开发速度?

这些组织的规划是按年进行的,而不是按季度进行。大多数公司将与多个外包公司建立长期合作关系,并拥有内部的永久和临时技术员工混合体。正如我们之前简单建模所见,对于选择完全拥抱生成性 AI 工具的这种规模的公司来说,潜在的节省每年可达数百万英镑/美元/欧元。

然而,尽管财务利益显著,这些公司中的变革也很困难。既定的思维方式和工作方式、现有的等级制度、内部政治、组织变革的复杂性以及大规模裁员的非常显著成本将导致规划详细而缓慢。虽然这些公司在考虑变革如何展开时,它们的团队可能会开始自行采用工具。正如我们在其他易于使用的技术工具中所见,通常是市场营销等非技术(但与技术相关)团队推动了工具的采用。在产品工程团队内部,也会有一些创新和采纳工具的动力,但可以理解的是,对减少技术组织中的角色数量的努力较少。

对于这些大型企业来说,风险很高,但变革将缓慢进行,生产力或成本节省的好处也将逐渐显现。然而,对于一些有眼光和果断的组织来说,影响可能会很显著。

外包公司

啊,我一直在等待这个。

我首先想说的是,确实有很多外包的好理由,我通常将其分解为以下几点:

  1. 吸引公司不一定会持续使用的高价值技能

  2. 为稳定的永久团队成员基础增添灵活性

  3. 增加一个不会使你的业务与众不同的能力

外包的一个不太理想的用途是拥有大型的、多年的合同,用于相对昂贵且长期保持一致的资源。不幸的是,这仍然很常见,许多外包公司一旦成为客户的“家具”后,就会赚很多钱。

Stability AI 的首席执行官 Emad Mostaque 最近在一次采访中表示,他认为“在接下来的一两年内,外包的三级程序员将会消失”并且“五年后将没有程序员”。我认为我对 Mostaque 的观点不如他那样乐观,但仍然看到外包公司在不久的将来面临巨大的挑战。

对于那些专注于提供短期价值的咨询公司来说,调查生成式 AI 工具的机会非常明显,因为他们有现金和激励来配置自己的团队进行工具实验,并向客户展示最佳实践。会出现一个暂时丰富的市场,类似于 5-10 年前的‘数字化转型’趋势,这些公司可以提供咨询,帮助客户公司采用新工具,提供培训、框架和使用指南。这对于那些更为保守的客户将尤其令人放心,如果他们能够获得有关知识产权、安全和数据隐私等问题的深思熟虑的建议,这些问题可能对他们来说过于重大。

正如我之前提到的,任何认为生成式 AI 工具应该带来成本节省的公司,可能会首先审视其外包和离岸成本,然后才会考虑减少正式员工的数量。

如果我在一家大型组织中担任首席技术官,面对像 Copilot 和 CodeWhisperer 这样的工具,并了解到五分之四的开发人员可以被 AI 工具替代,我的初步反应将是开始考虑所有的外包支出。为了保护我高价值的内部工程师,削减对外包公司的资金将是一个相对简单的哲学决策。

这将会很有趣地展开。对于提供高度专业化咨询服务的外包公司影响会较小,但如果客户外包是为了利用低成本地区的技术资源,影响将会非常真实且不断增长。

专注于移动应用开发、网页应用开发、QA 或 DevOps 等领域的小型精品公司,将受到的影响与像埃森哲、德勤、TCS、Infosys 和 Wipro 等巨头会有所不同。

那些最应该担忧生成式 AI 工具出现的供应商是那些有持续的人力外包合同,提供相对通用技术资源给客户的公司,而不是那些合作提供特定成果或高度专业技能的公司。

在第六部分,我们将把接力棒交给你,并请求你进行实验,同时反思早期采用者需要考虑的关键事项。

阅读第六部分发布后

本系列的其他文章:

  • 第一部分

  • 第二部分

  • 第三部分

  • 第四部分

附言。如果你喜欢这些关于团队的文章,可以查看我的Teamcraft 播客,在这里我和我的共同主持人 Andrew Maclaren 会与嘉宾讨论如何让团队运作顺利。

为什么箱线图不应单独使用及与之配合使用的 3 种图表

原文:towardsdatascience.com/why-a-box-plot-should-not-be-used-alone-and-some-plots-to-use-it-with-23381f7e3cb6?source=collection_archive---------12-----------------------#2023-01-16

解释箱线图中的缺失信息及使用 Python 代码解决该问题的替代方法

Boriharn KTowards Data Science Boriharn K

·

关注 发表在 Towards Data Science ·7 分钟阅读·2023 年 1 月 16 日

--

照片由Lance Asper提供,来源于Unsplash。

箱形图或 箱形图和须图 是 描述性统计 中常见的数据可视化图表。该图通过其 四分位数 直观地展示数值数据,这些四分位数包括最小值、第一个四分位数、中位数、第三个四分位数和最大值。此外,它有助于轻松发现异常值。

然而,没有什么是完美的。箱形图有一些主要的限制。在解释这些限制之前,让我们看看下面的图片。

箱形图显示了三组数据的分布。图像由作者提供。

这些是三组不同数值数据的箱形图。可以注意到它们看起来几乎一样。我知道你可能有一种直觉感觉它们并不相同。

事实上,当使用直方图绘制真实数据时,数据完全不同,如下图所示。在这种情况下,使用箱形图可能会误导数据的解释。

直方图和箱形图显示了三组数据的分布。图像由作者提供。

这个问题可以简单解释为: 箱形图无法显示数据集的 众数

此外,作为出现频率最高的值,众数也指的是分布的 局部最大值。在处理 双峰分布、它有两个众数(或峰值),或 多峰分布、它有多个峰值时,箱形图无法表达这些洞察信息。

此外,箱形图的形状为矩形,因此它可能会误导我们认为四分位数范围内有数据。如果四分位数范围内存在空白或没有数据,这些情况可能不会被注意到。

本文推荐的用于与箱形图配合使用的可视化示例。图像由作者提供。

鉴于这些限制,箱形图应该与其他图表一起使用。本文将介绍一些可以与箱形图配合使用的替代图表,以可视化数据特征。

开始吧。

免责声明: 本文对使用箱形图没有任何异议。如前所述,一切都有利有弊。就个人而言,我仍然使用箱形图,因为它帮助我轻松找到异常值。然而,我不会单独使用它。要彻底理解数据,还需要使用其他图表。

获取数据

从导入库开始。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

%matplotlib inline

作为示例,本文将使用一个模拟数据集。我们将使用Numpy创建一个正态分布的数据集。

mu, sigma = 0, 1 #mean and standard deviation
s1_pre = np.random.normal(mu, sigma, size=5000)
s1 = [i for i in s1_pre if i<2.6 and i>-2.6]

通过下面的代码修改生成的数据,以获得另外两个具有不同分布和数据间隙的数据集。

#defnie a function
def pair_sum(input_):
    input_.sort()
    if len(input_)%2 == 0:
        loc = [i for i in range(len(input_)) if i%2 == 0]
        output = [input_[l] + input_[len(input_)-(l+1)] for l in loc]
    else:
        loc = [i for i in range(len(input_)) if i%2 == 0]
        output = [input_[l] + input_[len(input_)-(l+1)] for l in loc[0:-1]]
        output.append(input_[-1])
    return output

#bimodal distribution with gaps
s2_out, s2_sub1, s2_sub2 = [], [], []
for i in s1:
    if i>0.05 and i<0.6:
        s2_sub1.append(i)
    elif i<-0.05 and i>-0.6:
        s2_sub2.append(i)
    else:
        s2_out.append(i)

sub1_mod = pair_sum(s2_sub1)
sub2_mod = pair_sum(s2_sub2)
s2 = s2_out + sub1_mod*2 + sub2_mod*2

#distribution with gaps
s3_out, s3_sub = [], []
for i in s1:
    if i>-0.4 and i<0.4:
        s3_sub.append(i)
    else:
        s3_out.append(i)

sub3 = pair_sum(s3_sub)
s3 = s3_out + sub3*2

绘制模拟数据以查看结果。

c_list = ['#1a936f', '#F77F05', '#D62829']

n = 25
f, axes = plt.subplots(3, 2 ,figsize=(12,5))
ax1=sns.boxplot(s1,ax= axes[0,0],color= c_list[0],boxprops= dict(alpha=.9))
ax2=sns.boxplot(s2,ax= axes[1,0],color= c_list[1],boxprops= dict(alpha=.9))
ax3=sns.boxplot(s3,ax= axes[2,0],color= c_list[2],boxprops= dict(alpha=.9))

ax4=sns.histplot(s1, bins=n, ax=axes[0,1], color=c_list[0])
ax5=sns.histplot(s2, bins=n, ax=axes[1,1], color=c_list[1])
ax6=sns.histplot(s3, bins=n, ax=axes[2,1], color=c_list[2])

ax4.set(ylabel=None, yticklabels=[])
ax5.set(ylabel=None, yticklabels=[])
ax6.set(ylabel=None, yticklabels=[])
plt.show()

直方图和箱线图显示了模拟数据的分布。图片来源:作者。

在继续之前,让我们创建一个 DataFrame。在这一步中,我们将使用panda.cut将数据值分段并排序,以备后用。

bins1 = pd.cut(s1, bins=n, labels=False)
bins2 = pd.cut(s2, bins=n, labels=False)
bins3 = pd.cut(s3, bins=n, labels=False)

df1 = pd.DataFrame(zip(s1, bins1), columns=['v1','bins'])
df2 = pd.DataFrame(zip(s2, bins2), columns=['v2','bins'])
df3 = pd.DataFrame(zip(s3, bins3), columns=['v3','bins'])

df1_m = pd.melt(df1, id_vars=['bins'],value_vars=['v1'])
df2_m = pd.melt(df2, id_vars=['bins'],value_vars=['v2'])
df3_m = pd.melt(df3, id_vars=['bins'],value_vars=['v3'])

df = pd.concat([df1_m, df2_m, df3_m])
df.head()

替代的数据可视化

1. 小提琴图

小提琴图的概念基于箱线图和核密度图。因此,结果可以表达数据集的峰值。这是检查数据集是否具有单峰、双峰或多峰分布的好方法。

在本文中,我们将主要使用Plotly,一个有用的数据可视化库,来可视化数据。我们可以用几行代码绘制小提琴图,如下所示。

import plotly.express as px
fig = px.violin(df, x = df['value'], box=True, width=900, height=600,
                color=df['variable'], color_discrete_sequence=color_list)
fig.update_layout(legend_title="", xaxis_title=None)
fig.show()

小提琴图显示了模拟数据的分布。图片来源:作者。

2. 直方图

与箱线图相比,直方图是另一种能够展示数据分布的数据可视化方式。每个区间的数据密度显示了是否存在间隙或多个峰值,或四分位数范围之间的间隙。

先用下面的代码分别绘制每个直方图。

fig1 = px.histogram(x= df[df['variable']=='v1']['value'],
                    nbins = 50, width=900, height=400,
                    color_discrete_sequence=[color_list[0]])
fig2 = px.histogram(x= df[df['variable']=='v2']['value'],
                    nbins = 50, width=900, height=400,
                    color_discrete_sequence=[color_list[1]])
fig3 = px.histogram(x= df[df['variable']=='v3']['value'],
                    nbins = 50, width=900, height=400,
                    color_discrete_sequence=[color_list[2]])

fig1.update_layout(xaxis_title=None, yaxis_title=None)
fig2.update_layout(xaxis_title=None, yaxis_title=None)
fig3.update_layout(xaxis_title=None, yaxis_title=None)

fig1.show()
fig2.show()
fig3.show()

直方图显示了模拟数据的分布。图片来源:作者。

我们还可以叠加直方图并添加地毯图来展示分布标记。

fig = px.histogram(df, x= 'value', nbins = 60, color = 'variable',
                   opacity=0.8, width=900, height=600, marginal="rug",
                   color_discrete_sequence=color_list)
fig.update_layout(yaxis_title=None, legend_title="", xaxis_title=None)
fig.show()

叠加直方图和地毯图显示了模拟数据的分布。图片来源:作者。

可以添加核密度估计(KDE)线,以使结果更具信息性。

import plotly.figure_factory as ff

group_labels = list(set(df['variable']))
hist_data = [df[df['variable']=='v1']['value'],
             df[df['variable']=='v2']['value'],
             df[df['variable']=='v3']['value']]
fig = ff.create_distplot(hist_data, group_labels, bin_size=0.25,
                         colors=color_list)
fig.update_layout(width=900, height=600)
fig.show()

叠加直方图、KDE 线和地毯图显示了模拟数据的分布。图片来源:作者。

3. 条形图

如果之前的结果看起来复杂,条形图可能是创建简洁可视化的一个好方案。

可以创建条形图,如下所示。

import plotly.express as px
fig = px.strip(df, x = 'value', y ='variable', stripmode='overlay',
               width=1000, height=600
              )
fig.update_layout(xaxis_range=[-2.69, 2.69])
fig.update_layout(legend_title="", yaxis_title=None,
                  xaxis_title=None,showlegend=False)
fig.show()

条形图显示了模拟数据的分布。图片来源:作者。

然而,由于我们无法区分每个数据范围内的数据密度,结果可能不会太有用。通过分配颜色以表达密度,结果可以得到改善。

下面的代码展示了如何定义一个函数,以创建一个字典来为每个数据范围中的数据点着色。

#add a new columns to facilitate creating a dictionary
df['vari_bins'] = [v+'_'+str(b) for v,b in zip(df['variable'],df['bins'])]

def strip_color(input_list, prefix, color, n_color):
    #count data in each bin 
    c_bin = [input_list.count(i) for i in range(n)]
    #create a dictionary by enumerate each bin  
    dict_c = {prefix+str(c):i for c,i in enumerate(c_bin)}

    #sort the dictionary
    sort = {i[0]:i[1] for i in sorted(dict_c.items(), key=lambda x: x[1])}
    #extract color code from color palette
    pal = list(sns.color_palette(palette=color, n_colors=n_color).as_hex())

    #assign color code to the dictionary
    dict_out = {c:p for c,p in zip(sort.keys(), pal)}

    return dict_out

应用函数

dict_p1 = strip_color(list(df1_m['bins']), 'v1_', 'YlGn', 25)
dict_p2 = strip_color(list(df2_m['bins']), 'v2_', 'Oranges', 25)
dict_p3 = strip_color(list(df3_m['bins']), 'v3_', 'YlOrRd', 25)

dict_ = {}
dict_.update(dict_p1)
dict_.update(dict_p2)
dict_.update(dict_p3)

绘制条形图

import plotly.express as px
fig = px.strip(df, x = 'value', y ='variable',
               color = 'vari_bins',
               color_discrete_map=dict_, stripmode='overlay',
               width=1000, height=600,
              )
fig.update_layout(xaxis_range=[-2.69, 2.69])
fig.update_layout(legend_title="", yaxis_title=None,
                  xaxis_title=None, showlegend=False)
fig.show()

看呐!!

带有颜色的条形图展示了模拟数据的分布。图像由作者提供。

总结

这篇文章展示了箱线图在展示众数时的局限性,这可能导致误导性的解释。在绘制具有两个或更多峰值的数据集或在四分位数范围内显示数据缺口时,它并不有用。顺便说一下,箱线图有一些优点,例如简单的可视化和显示异常值。

我相信还有比这篇文章中提到的更多图表可以弥补箱线图的缺点。

如果你有任何建议,请随时留言。我很乐意看到它。

感谢阅读

这些是我撰写的数据可视化文章,你可能会感兴趣:

  • 7 种使用 Python 表达排名随时间变化的可视化方法 (链接)

  • 8 种使用 Python 处理多个时间序列数据的可视化方法 (链接)

  • 9 种使用 Python 的可视化方法,比条形图更引人注目 (链接)

  • 9 种使用 Python 的可视化方法来展示比例,而不是饼图 (链接)

参考资料

  • Wikimedia Foundation. (2023 年 1 月 4 日). 箱线图。维基百科。取自 2023 年 1 月 8 日,来源于 en.wikipedia.org/wiki/Box_plot

  • 用于多峰分布的箱线图。交叉验证。取自 2023 年 1 月 8 日,来源于 stats.stackexchange.com/questions/137965/box-and-whisker-plot-for-multimodal-distribution

  • Stephanie. (2022 年 9 月 25 日). 双峰分布:是什么? Statistics How To. 取自 2023 年 1 月 10 日,来源于 www.statisticshowto.com/what-is-a-bimodal-distribution

为什么接受“任何数据工作”是一个糟糕的职业决定,你应该做什么

原文:towardsdatascience.com/why-accepting-any-data-job-is-a-terrible-career-move-and-what-you-should-do-instead-68f5354cacd8

来自 Spotify 数据科学家的内部建议,以便在今天这个困难的就业市场中为你的职业做出明智的决策

Khouloud El AlamiTowards Data Science Khouloud El Alami

·发表于Towards Data Science ·14 分钟阅读·2023 年 11 月 26 日

--

困难的就业市场让你觉得自己是在《饥饿游戏》中为生存而战,而不是为了得到一份工作吗?

如果你已经到了考虑放弃并接受任何数据工作的阶段,请稍等。你即将犯一个大错。

我在 COVID-19 大流行期间开始了我的数据科学职业生涯,那是一个疯狂的时期,类似于现在公司大规模裁员,几乎不招聘任何人*(不过情况更糟糕)*。

那时,我还是一个学生,感到完全是个冒名顶替者,还有着 5 万美元的债务在逼迫我。在绝望的时刻,我做了我们大多数人都会做的事。我把自己的标准扔进了垃圾桶,开始疯狂申请任何能找到的数据工作。

我向我甚至不知道的公司发送了数百份申请,因为我非常绝望——(作者提供的图片)

但随后,一位数据科学家告诉我一件改变一切的事:在这个领域正确起步至关重要。

为什么在数据科学领域这一点如此关键?因为在这个领域,并非所有职位都是平等的。领域的多样性意味着在一个行业中被重视的技能和经验在另一个行业中可能并不那么相关——这可能是公司是否雇用你与刷到下一个候选人之间的区别。

现在,作为 Spotify 的数据科学家,我可以自信地说,等待合适的工作而不是绝望地接受任何数据角色是最明智的决定。

妥协只会让我走上错误的职业道路,或延迟实现梦想工作的旅程。有一点是确定的;我现在不会在这里。

如果你需要一些灵感,这是一张不满足于现状的人的面孔 — (图像作者)

这个故事是帮助你做出明智职业决策的指南,即使在求职市场让你感到窒息时。我将展示给你:

  1. 如何屈从于接受一个不符合你期望的工作的压力是一个糟糕的举动,可能会破坏你的职业生涯。

  2. 我用来避免这个陷阱的具体策略。它们最终让我在 Spotify 获得了理想的职位。

你准备好了解如何在不妥协职业目标的情况下导航职场吗?很好,让我们深入探讨。

今日菜单

· 为什么妥协接受任何数据职位是一个糟糕的职业选择

· 步骤 #1 — 退一步再前进

· 步骤 #2 — 缩小差距

· 步骤 #3.a. 提升技能路径

· 步骤 #3.b. 实践路径

· 并非每个雇主都适合你

· 免责声明 — 最终的重要提示

为什么妥协接受任何数据职位是一个糟糕的职业选择

让你对雇主有吸引力的关键因素之一

在数据科学中,并非所有角色都等同。 该领域的多样性意味着在一个行业中被重视的技能和经验在另一个行业中可能并不具有同等的价值。

理解数据科学应用的这种多样性是 至关重要的

如果你瞄准的是科技公司(或其他行业),了解你在那个环境中所需的具体技能至关重要。

一个公司是否觉得你的数据技能有吸引力的关键因素之一是你如何在过去的经验中应用这些技能。

我曾遇到一位在顶级市场研究公司有超过 10 年经验的高级数据科学家。你可能会认为他可以获得任何他想要的数据科学职位。只可惜……

尽管他有技能,但他甚至没能获得 Spotify 的面试机会。为什么?很可能是因为他掌握的技能和应用与职位要求不相关。他掌握了基础知识 — Python、SQL、沟通技巧 — 但在科技领域,这仅仅是起点。

同样,我的一位拥有金融背景的熟人发现她的数据科学技能在转向科技公司时并没有完全转移。她也面临了困难。

我知道你可能在想

“嘿,我可以接受咨询公司的数据职位,然后在我积累了更多经验后申请理想公司。”但如果你在那里做零售预测,你的理想公司可能对这些技能并不感兴趣。

或者说你在 JP Morgan 找到了一份工作。确实很令人印象深刻。你可能认为这对你的简历来说是一个很好的名字,申请 Google 时不应该有问题,对吧?错了,你在信用风险分析方面的经验实际上并不是 Google 想要的(除非你申请 Google Pay)。他们更倾向于选择那些在较不知名公司进行 A/B 测试和建立指标的候选人。

那么,这里主要的收获是什么? 在一个领域热门的东西,在另一个领域可能连第二眼都不会看。

如果你有特定的职业期望或目标,这一点至关重要。因此,尽早确定你的梦想行业或公司,并将你的技能发展和工作选择与之对接,以* 满足它们的具体需求。

注意 — 请记住,虽然这些建议在我个人成功中起到了重要作用,我分享它们是基于这个原因,但成功是多方面的。诸如技能、时机,甚至运气等因素,这些往往超出我们的控制范围,发挥着重要作用。所以将这些建议视为优化自己旅程中可控部分的指南或踏脚石。

步骤 #1 — 退后一步以前进

图片由 carolyn christine 提供,来源于 Unsplash

记住非常重要的是:

你的工作是你大部分时间的去处。无论你现在对自己的情况有多么绝望,明智地选择下一份工作至关重要。

当你做自己讨厌的事情时,感到痛苦是很容易的。最终,这只是将一个问题换成另一个问题。一个明智且聪明的工作选择不仅仅是关于角色本身,更是关于与你的职业抱负和个人梦想的一致性。

最终,这关乎你整体的幸福和生活满意度。当你达到低谷时,很容易丧失对这一点的把握。

所以,花点时间暂停和反思,尤其是当你从教育到就业一路不停时。 许多人从学习直接跳入职业生涯,而没有花时间 1)评估他们的经历和反思他们迄今的旅程,以及 2)为未来制定战略。

这个反思不仅仅是一个休息,它是一个关键的过程,可以重新定义你的职业轨迹。专注于理解你真正希望从职业中获得什么。

确定你的职业抱负和梦想工作。 这种清晰度将指导你做出一个不仅仅是权宜之计的职业选择,而是一个通往充实未来的踏脚石。

自我提问:

*1. 你对什么充满热情?

2. 是什么让你跳入数据科学领域?

3. 哪些公司在你热爱的领域运营?

4. 你长期的职业目标是什么?如果没有,你希望在职业生涯中实现什么?*

在数据科学领域工作的最佳之处在于你总能找到与自己兴趣相关的行业中的工作 — 视频游戏、电影、太空、小狗,等等。

对我来说,是在音乐行业工作。我梦寐以求的 Spotify,因为我是小提琴演奏者,我想站在我热爱的行业的数据科学前沿。

我能给你的一个大建议是:

找一个导师,找一个更有经验的人,与他讨论你的职业旅程,并从不同的角度了解工作真正的样子。

我明白确定你的职业期望可能是具有挑战性的,特别是在早期阶段。你的目标可能会随着时间而发展,但有一个方向总比毫无目的地漂流要好。导师在这方面可以提供很大帮助!

步骤 #2 — 弥合差距

你缺乏哪些技能?弥合差距 — 照片由 Alex Radelich 提供,在 Unsplash 上

当我开始申请工作时,我的数据科学技能还处于初期阶段,我仍然是一个初级数据科学家 — 更像是一个胎儿。因此,我知道在数据科学就业市场中,想要被认真对待,我还有很长的路要走*(尤其是在科技领域)。*

当你是数据科学的新人(尤其是如果你开始时没有编码或基础数学和机器学习的经验,就像我一样),即使是像 NYU 这样的顶级大学的一年密集课程也可能不足以进入科技行业。

所以我需要提升技能 — 这就是这些科技数据科学工作竞争激烈的方式。问题是:

我需要什么技能才能进入我梦想的公司?

对你也是一样。在定义你的职业目标,并识别出与你的职业抱负和热情交集的公司或行业后,下一步就是找出你技能上的差距。

  • 在 LinkedIn 上搜索你目标公司/行业中的工作人员。尝试通过共享连接、城市或母校进行筛选,以最大限度地提高获得回复的机会。例如 — Spotify

你已经拥有 700 多名在 Spotify(我们当前的例子)工作过的有经验的人 — (作者提供的图片)

  • 查看他们具备的技能,他们从事的项目类型,以及他们在数据科学领域所做的应用。

这是一个非懒散的人在经验部分列出的技能的示例(与我的空白简历形成对比) — (作者提供的图片)

  • 评估你当前的技能,并找出你缺乏这些技能和需要改进的领域。你可以做的一件事是更新你在 LinkedIn 上的技能部分。每当你对平台上的某个角色感兴趣时,LinkedIn 会直接显示你缺乏哪些技能,以及你已经具备哪些技能!

充分利用它 — (作者提供的图片)

这些是你想要提升或获得的技能。 这就是你职业战略的跳板。

下一个需要问自己的问题是:

他们是否需要适当的培训或实践经验来获得?

A/B 测试等技能通常仅通过培训很难获得,最好在公司环境中学习。但如果你的技能差距主要集中在机器学习领域,那么适当的培训是你所需要的。

对我来说,我意识到我需要提升技能,但我在心理上还没准备好再学习 1 到 2 年,所以我选择了简单的方式→我将努力重心转向了欧洲,但即使如此也失败了。

一旦你开始找工作,你会迅速意识到在所有最佳职位都被最优秀的候选人占据后,你只能获得一些零散的职位。我不想以我讨厌的工作开始职业生涯。所以我推迟了贷款还款,并开始寻找美国以外的其他更实惠的数据科学学位。

关键点是什么? 找出你需要弥补的技能差距,以及是否需要培训或实践经验来弥补这个差距。

根据是否需要提升技能或获得实践经验,你需要选择以下路线之一——或混合使用。

第#3.a. 技能提升路线

我第二个数据科学学位第一年的预览——(图片作者提供)

经过大量的眼泪、辛勤工作和一点运气,我成功获得了全球第三最佳数据科学学位,并获得了额外的奖学金。

但我知道这并不适用于每个人——这里的目标是找出你技能上的不足,并跟随任何能弥补这一差距的相关培训。

无论是报名参加另一个数据科学学位、参加培训营、注册在线课程、请求你的仙女教母,还是卖掉你的灵魂,做你必须做的事*(好吧,也许最后一个不算)*。

最终,你需要能够在简历上展示你拥有这些技能,通过你在作品集中展示的项目或证书/文凭。

你可以做的事情包括:

像Coursera、edX 和 Udacity 这样的平台提供了广泛的数据科学课程,你可以根据需要的具体技能进行学习。

如果你还在读书,寻求你热爱的领域或行业的实习机会,无论公司规模大小。即使是小公司也能提供宝贵的学习机会。最重要的是获得技能。

根据你的职业目标,你可能需要通过专业化超越数据科学的基础知识。无论是计算机视觉、自然语言处理、语言模型还是其他领域,确保你在这个子领域获得经验——通过培训或项目——然后再申请这些职位。

对于技术行业,我制作了终极指南,介绍了你在申请前需要掌握的 6 项数据科学技能。 一定要查看一下!

步骤 #3.b. 实践路线

记住,目标是避免在描述中只包含“数据”一词的工作陷阱。

现在你已经明确了需要的技能,可以开始寻找能提供学习这些具体技能的平台的工作。

你应该这样做:

  • 寻找与你技能发展目标一致的工作。浏览职位描述以了解要求。避免那些无法促进你技能发展的职位——无论公司有多么吸引人。

如果你想成为 FAANG 公司的数据科学家,这非常合适。它涵盖了你需要的所有顶级技能(实验、数据可视化、统计、跨职能协作、业务参与),这是你想要瞄准的公司类型,无论其规模或声誉如何 — (图片来源:作者)

  • 如果不清楚,可以尝试在 LinkedIn 上联系该公司的员工,询问他们学习了哪些技能以及如何在工作中应用这些技能。

我发送了数十封这样的邮件,但从未收到回复,但它们仍然值得发送。随着时间的推移,我通过联系最有可能回复我的人获得了回复(因为消息是法语的,所以不共享这些)— (图片来源:作者)

  • 通过与在你所期望行业或领域工作的人建立联系来构建专业网络。参加会议、网络研讨会或本地聚会,以扩大你的联系。

我曾经花了 1000 美元,从纽约市飞往亚利桑那州的凤凰城,参加一个 STEM 会议,许多大科技公司在这里与学生见面、面试并招聘他们。尽管那时我还处于数据科学训练的早期阶段,工作表现平平,但我尽全力发挥了人际网络的作用。我甚至结识了一位谷歌员工,后来通过 LinkedIn 与他建立了联系,并获得了对我其中一个谷歌申请的推荐信。这个故事的教训是什么?你永远不知道下一个帮助你的人是谁。这位先生给了我宝贵的建议,这本身就是一种胜利!— (图片来源:作者)

  • 当你收到工作邀请时,要仔细评估它们。考虑诸如技能增长机会、指导和整体与职业目标的契合度等因素。如何做?在面试期间提问,你也在面试他们。否则,可以联系那里的现有员工。

  • 不要妥协。

并非所有雇主都适合你

想象一下——在加入 Spotify 的五个月前,我还在盯着电脑屏幕,认为我的努力不会有回报。即使进入了顶尖学校,我仍然被刚起步的创业公司拒绝了。

我曾以为自己不够好,但其实只是我不适合他们,这完全没问题。

记住,并不是每个雇主都会觉得你有吸引力,目标是找到那个会觉得你有吸引力的人。不要让拒绝影响你的自我认知,也不要让冒名顶替综合症渗入。每次拒绝都会让你觉得自己永远不会足够好。

保持韧性——我知道这听起来有些老套,但这是真实的。这是最终一步。你必须继续为梦想奋斗,即使感觉努力没有成果。否则,一切都将付诸东流。

哭泣是可以的,只要确保在按下你辛苦准备的申请表时一起流泪。

挫折是你旅程中的自然部分,这正是它们美丽的地方。把它们当作推动你前进的燃料,而不是拖住你的锚。

免责声明——最后的一个重要提示

你是刚刚开始职业生涯吗?(如果不是,请跳到下一条)

我不久前还处于那个状态,因此我深刻理解你现在可能感受到的兴奋与焦虑。

当你刚起步时,通常会觉得应该抓住任何出现的工作机会。你可能觉得连挑剔工作的奢侈都没有,应该对找到一份工作感到足够感激。

再想一想!如果你在权衡一个大牌公司提供的少量相关技能发展和一个较不知名但实际能提升你技能的公司,我的建议是?选择那个还没人听说过的。我曾经这样做过一次。

在获得 Spotify 实习机会之前,我有两个公司的邀请:以《刺客信条》闻名的 Ubisoft 和巴黎的小型咨询公司 Polynom。出乎意料的是,我选择了 Polynom,因为它更符合我当时的技能发展目标。

记住,你正在规划通往理想工作的路线。重要的是要始终关注那个目标。这通常是实现你为自己设定的重大职业目标的最快途径。

一旦你磨练了技能并获得了必要的经验,你将处于更有利的位置,能够瞄准更大的公司甚至是你的梦想公司。

还是你已经在这个领域有了大量的经验?

那也是完全没问题的,不用担心。如果你在寻求职业上的改变或提升,现在还不算晚。

在 Spotify,我遇到了许多数据科学家,他们在转行之前甚至没有在这个领域待过几年。他们花时间,专注于培养正确的技能,然后在准备好时才去 Spotify。

记住,找到理想的工作可能需要几个月甚至几年。这正是为什么我如此强调聪明和策略性地行事——为了给你额外的推动力并加快进程。

遵循我在这篇文章中分享的策略,应该能帮助你顺利迈向梦想工作!

面对安于现状的诱惑时,请记住:

实现梦想工作的道路可能充满挑战,但回报是无尽的。现在选择简单的路线可能会导致未来长期的遗憾。

争取你想要的。

对我来说,每一次我不想要的工作的拒绝都是一种隐藏的祝福。它迫使我重新评估最佳选项,并重新校准策略以实现我想要的未来。

而且它有效,没理由它对你不起作用。如果你实施这些策略,你将弥补技能差距,迈向你的梦想工作。

保持韧性;不要满足于现状,继续争取你想要的。你的梦想工作不是遥不可及的梦想,它是一个触手可及的目标。

你想知道为什么现在很难找到数据科学的工作以及你可以做些什么吗?

这里有一个完整的指南来应对当前的数据职位市场 + 4 种经过验证的策略来战胜所有困难。

## 如何应对当前的数据科学职位市场指南

为什么现在很难找到数据科学的工作以及你可以做些什么

towardsdatascience.com

我为你准备了礼物🎁!

注册我的newsletter K’s DataLadder,你将自动获得我的终极 SQL 备忘单,其中包含我在大型科技公司工作中每天使用的所有查询 + 另一个秘密礼物!

我每周分享作为科技行业数据科学家的经历,以及实用的技巧、技能和故事,旨在帮助你提升水平——因为没有人真正知道,直到他们亲身体验!

如果你还没有这样做

  • 订阅我的YouTube频道。新视频即将上线!

  • InstagramLinkedInX上关注我,随你喜欢。

很快见!

为什么以及如何实现更长的 LLM 上下文窗口

原文:towardsdatascience.com/why-and-how-to-achieve-longer-context-windows-for-llms-5f76f8656ea9?source=collection_archive---------1-----------------------#2023-10-13

Davide GhilardiTowards Data Science Davide Ghilardi

·

关注 发布于 Towards Data Science ·7 min read·2023 年 10 月 13 日

--

语言模型(LLMs)在过去几年中彻底改变了自然语言处理(NLP)领域,在各种任务中取得了最先进的成果。然而,开发和改进这些模型的一个关键挑战在于延长它们的上下文长度。这非常重要,因为它决定了模型在生成输出时可用的信息量。

然而,增加 LLM 的上下文窗口并非易事。实际上,这会带来计算复杂度的增加,因为注意力矩阵呈二次方增长。

一种解决方案可能是用相对较小的窗口(例如 4K 个令牌)在大量数据上训练模型,然后在更大的窗口(例如 64K 个令牌)上进行微调。这一操作并不简单,因为尽管上下文长度不会影响模型的权重数量,但它确实影响这些权重在令牌稠密表示中如何编码位置的信息

这减少了模型在长上下文窗口下适应的能力,即使在微调之后也会导致性能不佳,因此需要新的技术来正确和动态地编码位置的信息,在训练和微调之间。

绝对位置编码

在 transformers 中,每个令牌的位置信息在令牌输入到注意力头之前被编码。这是一个关键步骤,因为与 RNN 不同,transformers 不会跟踪令牌的位置。

原始 transformer 架构 [1] 将位置编码为与令牌嵌入相同形状的向量,以便它们可以相加在一起。特别是,他们使用了一个 cos/sin 波的组合,其长度从低维到高维嵌入的顺序递增。

图片由作者提供

这种方法允许对所有令牌位置进行高效的唯一稠密表示。然而,它不能解决扩展上下文长度的挑战,因为这些表示不能有效地编码令牌的相对位置。

为了了解为什么会发生这种情况,我们来关注一对连续的维度。如果我们在图表上绘制它们,我们可以将令牌和位置嵌入表示为二维向量,如下图所示:

图片由作者提供

上图包含两个图表:左侧是嵌入空间,右侧是键/查询空间。嵌入向量是黑色的,位置向量是蓝色的,它们的和用绿色表示。

为了从嵌入转到键(K)和查询(Q),transformer 应用了由两个矩阵Wq 和Wk 定义的线性变换。由于线性性质,该变换可以在嵌入和位置向量之间分别应用。在 KQ 空间中,注意力作为键和查询之间的点积计算,图中用黄色区域表示在两个绿色向量之间。

由于旋转的混合方式不同,基于令牌和位置嵌入的原始方向,以及它们在变换中的缩放方式,模型无法直接访问令牌之间的相对距离。

同样重要的是,在这种情况下,加法是在变换之前应用的,而不是反过来,因为位置嵌入需要通过线性层进行缩放。

相对位置编码

为了高效编码标记之间的相对位置信息,已经提出了其他方法。我们将重点关注RoPE [2],即Rotary Position Embedding,已经为更长的上下文窗口提出了扩展方法。

RoPE 的两个主要创新是:

  • 使用 RoPE,我们可以使键和值嵌入之间的点积仅对它们的相对距离敏感。

  • 位置嵌入被乘以标记的嵌入,而无需固定大小的查找表。

为了实现这一目标,首先,我们将标记的嵌入从实际的 D 维空间映射到一个复数 D/2 维空间,并在该空间中应用旋转。

再次考虑一对连续的维度。如果我们将它们绘制在复数平面上(将第一个设置在实轴上,第二个设置在虚轴上),我们可以将标记嵌入表示为复数向量,如下图所示:

图片来源:作者

在这里,位置编码获得了新的含义,实际上,它们变成了直接应用于向量的旋转。这一变化使它们能够唯一地编码标记的位置,并在线性变换中得到保留。

这一最后的特性对于在注意力计算中纳入相对位置至关重要。实际上,如果我们考虑未旋转(黑色)和旋转(绿色)版本的kₙ 和 qₙ₊ₖ 之间的注意力,如右侧图表中分别由橙色和黄色角度表示,我们可以注意到一些有趣的现象:

图片来源:作者

旋转的键和值之间的注意力仅因其位置差异成比例地不同于未旋转版本!

这一新特性使得使用 RoPE 训练的模型显示出快速收敛更低的损失,并且是一些流行技术的核心,这些技术可以将上下文窗口扩展到训练集之外。

到目前为止,我们始终保持嵌入维度固定,但为了看到整个框架,还需要观察沿该方向发生的情况。

图片来源:作者

RoPE 公式定义了第 d 维的旋转角度,与 exp(-d) 成正比。因此,正如我们在上图中看到的,随着 d 的增加,应用于嵌入向量的旋转呈指数下降(对于固定的 n 和位置)。这一特性使模型能够通过从低维到高维,转变嵌入中编码的信息类型,从低频(近标记关联)到高频(远标记关联)。

RoPE 扩展

一旦我们有效地将相对位置信息整合到模型中,增加我们 LLM 的上下文窗口 L 的最直接方法是通过位置插值PI) [3]进行微调。

这是一种简单的技术,将标记的位置缩放以适应新的上下文长度。例如,如果我们决定将其翻倍,所有位置将被缩小一半。特别地,对于我们想要实现的任何上下文长度 L* > L,我们可以定义一个缩放因子s = L/ L* < 1。

尽管这种技术通过在相对较少的标记上进行微调成功地扩展了 LLM 的上下文,并显示了有希望的结果,但它也有其自身的缺点。

其中之一是,它在对较大上下文进行微调后,略微降低了短上下文大小的性能(例如,困惑度增加)。这个问题发生是因为通过缩放s < 1 标记的位置(以及它们的相对距离),我们减少了对向量的旋转,导致高频信息的丧失。因此,模型较难识别小旋转,从而难以确定接近标记的位置信序。

为了解决这个问题,我们可以应用一种巧妙的机制,称为NTK-aware [4]位置插值,它不是将每个 RoPE 维度均等地缩放s,而是通过对高频率缩放较少,对低频率缩放较多来分散插值压力。

其他 PI 扩展方法还包括NTK-by-parts [5]和dynamic NTK [6]方法。前者施加两个阈值,以限制在某些维度上的缩放;后者在推理过程中动态调整s

最后,由于观察到随着标记数量的增加,注意力 softmax 分布变得越来越“尖锐”(注意力 softmax 的平均熵减少),YaRN [7](Yet another RoPE extensioN method)是一种通过在应用 softmax 之前将注意力矩阵乘以温度因子t来逆转这一过程的方法。

下面是这些方法在位置(旋转次数)和维度(每次旋转的度数)视角下的操作方式。

作者提供的图像

其他方法

最后,如前所述,其他上下文扩展方法也存在,这里简要描述了最流行的一些方法及其操作方式:

  • Alibi [8]:这是一种位置编码的另一种方法,根据键和值的距离来惩罚查询可以分配给键的注意力值。

  • XPos [9]:另一种位置编码方法,它将 RoPE 推广到包括一个缩放因子。

参考文献

  1. Vaswani 等人,2017 年。《Attention Is All You Need》。 link

  2. Su 等人, 2022. RoFormer:具有旋转位置嵌入的增强型变压器。link

  3. Chen 等人, 2023. 通过位置插值扩展大型语言模型的上下文窗口。link

  4. bloc97, 2023. NTK-Aware Scaled RoPE 使 LLaMA 模型在无需任何微调且最小化困惑度下降的情况下,能够拥有扩展(8k+)的上下文大小。link

  5. bloc97, 2023. 添加 NTK-Aware 插值“分段”修正。link

  6. emozilla, 2023. 动态缩放的 RoPE 进一步提升了长上下文 LLaMA 的性能,无需任何微调。link

  7. Peng 等人, 2023. YaRN:大型语言模型的高效上下文窗口扩展。link

  8. Press 等人, 2022. 训练短,测试长:具有线性偏差的注意力机制实现了输入长度的外推。link

  9. Sun 等人, 2022. 一个长度可外推的变压器。link

为什么以及如何在多重假设检验中调整 P 值

原文:towardsdatascience.com/why-and-how-to-adjust-p-values-in-multiple-hypothesis-testing-2ccf174cdbf8?source=collection_archive---------0-----------------------#2023-05-05

在选择相关特征时,通常会使用低于某个阈值的 P 值作为一种方法。以下建议说明了如何正确使用它们。

Igor Šegota数据科学前沿 Igor Šegota

·

关注 发表在 数据科学前沿 · 9 分钟阅读 · 2023 年 5 月 5 日

--

图片由作者提供。拍摄于加州拉荷亚的 Westfield UTC 购物中心。

多重假设检验发生在我们在多个特征上重复测试模型时,因为随着测试数量的增加,获得一个或多个假发现的概率也会增加。例如,在基因组学领域,科学家们经常想要测试成千上万的基因中是否有任何基因在某个结果中表现出显著不同的活性。或者糖果是否会导致痤疮。

在这篇博客文章中,我们将介绍几种常用的方法,通过调整模型 p 值来考虑多重假设检验:

  1. 假阳性率(FPR)

  2. 家庭错误率(FWER)

  3. 假阳性率(FDR)

并解释何时使用这些方法是有意义的。

这份文档可以总结为以下图像:

图片由作者提供。

创建测试数据

我们将创建一个模拟示例,以更好地理解如何通过各种 p 值操作得出不同的结论。要运行此代码,我们需要安装pandasnumpyscipystatsmodels库的 Python 环境。

为了这个示例,我们从创建一个包含 1000 个特征的 Pandas DataFrame 开始。其中 990 个特征(99%)的值将从均值为 0 的正态分布中生成,这称为空模型。(在下面使用的函数norm.rvs()中,均值是通过loc参数设置的。)其余的 1%的特征将从均值为 3 的正态分布中生成,这称为非空模型。我们将用这些特征来表示我们希望发现的有趣特征。

import pandas as pd
import numpy as np
from scipy.stats import norm
from statsmodels.stats.multitest import multipletests

np.random.seed(42)

n_null = 9900
n_nonnull = 100

df = pd.DataFrame({
    'hypothesis': np.concatenate((
        ['null'] * n_null,
        ['non-null'] * n_nonnull,
    )),
    'feature': range(n_null + n_nonnull),
    'x': np.concatenate((
        norm.rvs(loc=0, scale=1, size=n_null),
        norm.rvs(loc=3, scale=1, size=n_nonnull),
    ))
})

对于每个 1000 个特征中的特征,p 值是观察到的值至少如此之大(假设它是从空分布生成的)的概率。

p 值可以通过累积分布(norm.cdf()来自scipy.stats)计算,累积分布表示获得一个值等于或小于观察到的值的概率。然后为了计算 p 值,我们计算1 - norm.cdf()来找到大于观察到的值的概率:

df['p_value'] = 1 - norm.cdf(df['x'], loc = 0, scale = 1)
df

假阳性率

第一个概念称为假阳性率,定义为我们标记为“显著”的空假设的比例(也称为第一类错误)。我们之前计算的 p 值可以按其定义解释为假阳性率:它们是从空分布中获取一个值至少与指定值一样大的概率。

为了说明,我们将应用一个常见的(神奇的🧙)p 值阈值 0.05,但可以使用任何阈值:

df['is_raw_p_value_significant'] = df['p_value'] <= 0.05
df.groupby(['hypothesis', 'is_raw_p_value_significant']).size()
hypothesis  is_raw_p_value_significant
non-null    False                            8
            True                            92
null        False                         9407
            True                           493
dtype: int64

注意,在我们的 9900 个零假设中,493 个被标记为“显著”。因此,假阳性率为:FPR = 493 / (493 + 9407) = 0.053。

FPR 的主要问题是,在实际情况中,我们不知道哪些假设是空的,哪些不是。因此,仅凭原始 p 值(假阳性率)用途有限。在我们的案例中,当非空特征的比例非常小,大多数标记为显著的特征将是空的,因为它们的数量更多。具体来说,在 92 + 493 = 585 个标记为真正(“正”)的特征中,只有 92 个来自我们的非空分布。这意味着大多数或约 84%(493 / 585)的报告显著特征是假阳性!

那么,我们可以对此做些什么?有两种常见的方法来解决这个问题:我们可以计算家庭-wise 错误率(FWER)或虚假发现率(FDR),而不是虚假正率。这些方法都以原始未调整的 p 值集合作为输入,产生一组新的“调整后 p 值”作为输出。这些“调整后 p 值”代表了 FWERFDR上界 估计值。它们可以通过 multipletests() 函数获得,该函数是 statsmodels Python 库的一部分:

def adjust_pvalues(p_values, method):
   return multipletests(p_values, method = method)[1]

家庭-wise 错误率

家庭-wise 错误率是错误拒绝一个或多个零假设的概率,换句话说,就是将真实的零假设标记为非零假设的概率,或看到一个或多个虚假正例的概率。

当仅测试一个假设时,这等于原始 p 值(虚假正率)。然而,测试的假设越多,我们获得一个或多个虚假正例的可能性就越大。估计 FWER 有两种流行的方法:博尼费罗尼和霍尔姆程序。虽然博尼费罗尼和霍尔姆程序都没有对测试在单个特征上运行的依赖性做出任何假设,但它们会过于保守。例如,在所有特征完全相同(相同的模型重复了 10,000 次)的极端情况下,不需要校正。而在其他极端情况下,如果没有特征相关,则需要某种类型的校正。

博尼费罗尼程序

修正多重假设检验的最流行方法之一是博尼费罗尼程序。之所以这种方法流行,是因为它非常容易计算,即使是手动计算也很简单。该程序将每个 p 值乘以所进行的测试总数,或者如果这种乘法会使 p 值超过 1,则将其设置为 1。

df['p_value_bonf'] = adjust_pvalues(df['p_value'], 'bonferroni')
df.sort_values('p_value_bonf')

霍尔姆程序

霍尔姆的程序提供了一种比博尼费罗尼程序更强大的校正方法。唯一的区别在于,p 值并不是全部乘以测试总数(这里是 10000)。而是,每个排序后的 p 值依次乘以递减序列 10000、9999、9998、9997、……、3、2、1。

df['p_value_holm'] = adjust_pvalues(df['p_value'], 'holm')
df.sort_values('p_value_holm').head(10)

我们可以自己验证这一点:输出中的第十个 p 值被乘以 9991:7.943832e-06 * 9991 = 0.079367。霍尔姆的校正也是 R 语言中 p.adjust() 函数调整 p 值的默认方法。

如果我们再次应用 p 值阈值 0.05,我们来看看这些调整后的 p 值如何影响我们的预测:

df['is_p_value_holm_significant'] = df['p_value_holm'] <= 0.05
df.groupby(['hypothesis', 'is_p_value_holm_significant']).size()
hypothesis  is_p_value_holm_significant
non-null    False                            92
            True                              8
null        False                          9900
dtype: int64

这些结果与我们将相同阈值应用于原始 p 值时大相径庭!现在,只有 8 个特征被标记为“显著”,而这 8 个特征都是正确的——它们来自于我们的非零分布。这是因为即使有一个特征被错误标记的概率也只有 0.05(5%)。

然而,这种方法有一个缺点:它未能将其他 92 个非零特征标记为显著。尽管它非常严格,以确保没有零特征被漏入,但它只能找到 8%(100 个中的 8 个)非零特征。这可以被视为比虚假正例率方法采取了不同的极端。

是否有一种更中间的方案?答案是“有”,这种中间方案就是虚假发现率。

虚假发现率

如果我们可以接受一些虚假正例,但希望捕获更多的真正正例,而不仅仅是个位数的百分比呢?也许我们可以接受一些虚假正例,只要这些虚假正例不会淹没我们标记为显著的所有特征——就像在 FPR 示例中那样。

这可以通过在指定的阈值水平(例如 0.05)下控制虚假发现率(而不是 FWER 或 FPR)来完成。虚假发现率定义为所有被标记为正面的特征中虚假正例的比例:FDR = FP / (FP + TP),其中 FP 是虚假正例的数量,TP 是真正正例的数量。通过将 FDR 阈值设置为 0.05,我们表示我们接受在所有被标记为正面的特征中有 5%(平均)虚假正例。

控制 FDR 有几种方法,我们将在这里描述如何使用两种流行的方法:Benjamini-Hochberg 和 Benjamini-Yekutieli 程序。这两种程序虽然比 FWER 程序更复杂,但仍然类似。它们仍然依赖于对 p 值进行排序、乘以一个特定的数字,然后使用截断标准。

Benjamini-Hochberg 程序

Benjamini-Hochberg (BH) 程序假设每个测试都是独立的。依赖的测试会发生,例如,当被测试的特征之间存在相关性时。让我们计算 BH 调整后的 p 值,并将其与我们之前使用 Holm 校正的 FWER 结果进行比较:

df['p_value_bh'] = adjust_pvalues(df['p_value'], 'fdr_bh')
df[['hypothesis', 'feature', 'x', 'p_value', 'p_value_holm', 'p_value_bh']] \
    .sort_values('p_value_bh') \
    .head(10)

df['is_p_value_holm_significant'] = df['p_value_holm'] <= 0.05
df.groupby(['hypothesis', 'is_p_value_holm_significant']).size()
hypothesis  is_p_value_holm_significant
non-null    False                            92
            True                              8
null        False                          9900
dtype: int64
df['is_p_value_bh_significant'] = df['p_value_bh'] <= 0.05
df.groupby(['hypothesis', 'is_p_value_bh_significant']).size()
hypothesis  is_p_value_bh_significant
non-null    False                          67
            True                           33
null        False                        9898
            True                            2
dtype: int64

BH 程序现在正确地将 100 个非零特征中的 33 个标记为显著——相比于 Holm 校正的 8 个,这是一种改进。然而,它也将 2 个零特征标记为显著。因此,在 35 个标记为显著的特征中,不正确特征的比例是:2 / 33 = 0.06,即 6%。

请注意,在这种情况下,我们的 FDR 率为 6%,尽管我们旨在将其控制在 5%。FDR 将以 5% 的速率平均控制:有时它可能更低,有时可能更高。

Benjamini-Yekutieli 程序

Benjamini-Yekutieli (BY) 程序在测试是否独立的情况下都能控制 FDR。再次值得注意的是,所有这些程序都试图建立 FDR(或 FWER)的上限,因此它们可能更保守或更宽松。让我们将 BY 程序与上述 BH 和 Holm 程序进行比较:

df['p_value_by'] = adjust_pvalues(df['p_value'], 'fdr_by')
df[['hypothesis', 'feature', 'x', 'p_value', 'p_value_holm', 'p_value_bh', 'p_value_by']] \
    .sort_values('p_value_by') \
    .head(10)

df['is_p_value_by_significant'] = df['p_value_by'] <= 0.05
df.groupby(['hypothesis', 'is_p_value_by_significant']).size()
hypothesis  is_p_value_by_significant
non-null    False                          93
            True                            7
null        False                        9900
dtype: int64

BY 程序在控制 FDR 方面更严格;在这种情况下,甚至比用于控制 FWER 的 Holm 方法更严格,仅标记了 7 个非空特征为显著!使用它的主要优势是当我们知道数据可能包含大量相关特征时。然而,在这种情况下,我们可能还希望考虑过滤相关特征,以便不需要测试所有这些特征。

总结

最后,程序的选择留给用户,取决于分析试图做什么。引用本雅明、霍奇伯格(Royal Stat. Soc. 1995):

通常不太需要控制 FWER。在多个单独推断的结论可能出错时,控制 FWER 是重要的。

例如,在几种新治疗方法与标准进行竞争时,可能会出现这种情况,并且从被声明显著优于标准的治疗方法集合中选择单一治疗方法。

在其他情况下,当我们可以接受一些假阳性时,FDR 方法如 BH 校正提供了不那么严格的 p 值调整,并且如果我们主要想增加通过某个 p 值阈值的真阳性数量,则可能更可取。

还有其他未在此提及的调整方法,尤其是一个q-value,也用于 FDR 控制,在撰写时仅存在作为 R 包。

为什么所有地图都不准确?

原文:towardsdatascience.com/why-are-all-maps-inaccurate-e566f08d91fe

理解地图投影及其为何被用于一些最受欢迎的地图

Jake TeoTowards Data Science Jake Teo

·发布在 Towards Data Science ·阅读时间 7 分钟·2023 年 12 月 31 日

--

照片由 Kyle Glenn 提供,发布在 Unsplash 上

我们大多数人知道地球是球形的,或者更准确地说是椭球形的。由于我们必须将一个弯曲的三维表面表示成一个二维的平面或屏幕,这意味着涉及到一些失真。将地球表面“投影”到二维图像上的方法被称为地图投影

地图投影有数百种,每种投影在形状、距离、面积和方向上都有不同程度的失真。然而,没有任何地图投影能够完全消除这些失真,因此了解每种投影的优缺点对于确定适合你项目的投影是至关重要的。

目录

正射投影

墨卡托投影

横轴墨卡托投影

兰伯特等距圆锥投影

罗宾逊投影

总结

源代码

要可视化各种投影,我们可以使用 Python 库 matplotlibcartopy

pip install matplotlib cartopy

本文中创建的大多数世界地图的代码源如下所示。

import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as feature

# fix maplotlib params
plt.rcParams[‘figure.dpi’] = 175

# projections & borders
CRSs = [ccrs.Orthographic(),
        ccrs.Orthographic(central_longitude=0, central_latitude=-90),
        ccrs.Mercator(),
        ccrs.UTM(zone=29),
        ccrs.Robinson(),
        ccrs.LambertConformal()]

borders = [(0, 1, 1, 0.05),
            (0, 1, 1, 0.05),
            (0, 1, 1, 0.05),
            (0, 1, 1, 0.05),
            (0.05, 1, 1, 0),
            (0, 1, 1, 0)]

names = ["ortho", "ortho_southpole", "merca", 
         "utm", "robin", "lambertc"]

dir = "/Users/jake/Desktop/gis"

for crs, br, name in zip(CRSs, borders, names):
    fig, ax = plt.subplots(subplot_kw={‘projection’: crs})
    projection_name = str(crs).split(" ")[0]

    # Draw countries and coastlines
    ax.coastlines(linewidth=0.5)
    ax.add_feature(feature.BORDERS, linestyle=’:’, linewidth=1)

    # Draw parallels and meridians
    gridlines = ax.gridlines(draw_labels=True, linewidth=0.25, color=’gray’, linestyle=’--’)
    gridlines.top_labels = False
    gridlines.right_labels = False
    gridlines.xlabel_style = {‘size’: 7}
    gridlines.ylabel_style = {‘size’: 7}

    # spacing
    plt.subplots_adjust(left=br[0], right=br[1], top=br[2], bottom=br[3])

    # border
    for spine in ax.spines.values():
        spine.set_linewidth(0.5)

    # save the map
    plt.savefig(f"{dir}/{name}.png")

正射投影

正射投影。(由作者创建)

正射投影是从太空中看地球时最准确的表现。这种投影类型被称为平面投影,其中地球表面被投影到一个平面上的虚拟平面上。

然而,我们只能在这个平面上查看一个半球,且当我们从地图中心移动时,所有形式的失真都会发生,特别是在边缘部位尤为明显。我们很少看到这种投影被使用,除非是为了美学目的或在我们希望为地图注入一些真实感的情况下。

以南极点为中心的正投影。(作者创作)

话虽如此,当我们将焦点转向南极点时,与之后的其他地图相比,南极洲的形状更加准确。用于极地区域制图的另一种更准确的平面投影是立体投影

墨卡托投影

墨卡托投影。(作者创作)

墨卡托投影由杰拉尔德斯·墨卡托于 1569 年创作,无疑是所有投影中最具识别性的。谷歌地图使用了这种投影的一个变体,称为伪墨卡托Web 墨卡托

它受欢迎的原因在于它很好地保留了形状和角度,这对于过去船只的导航以及我们当前使用数字地图应用查看领土边界的形状并进行放大时保持形状完整是至关重要的。

地球的表面投影到一个假想的圆柱体的内部,赤道与圆柱体接触。然后将圆柱体展开以显示为世界地图。CSS 请注意这种墨卡托投影中纬度从赤道越远,扩展越大。(作者创作)

墨卡托投影是一种圆柱投影,赤道接触圆柱体,因此只有赤道沿线的条带在面积上没有失真。距离极点越远,失真就越明显和夸张。

一个很好的例子是,在上面的墨卡托投影世界地图中,你可以看到格林兰的大小与非洲大陆大致相同。但实际上,格林兰的面积只有非洲的 14 分之一,或者仅为非洲面积的 7%!

横轴墨卡托投影

从早期的墨卡托投影中得到的主要结论是,赤道处的失真最小,因为它接触了假想圆柱体。这一知识用于制作另一种非常流行的投影,称为横轴墨卡托。

在横轴墨卡托投影中,只有沿经线接触圆柱体的条带被投影和使用。(作者创作)

与其让圆柱体竖立并接触赤道,不如将其“横切”,以便特定经度长度触及圆柱体。只有这条狭窄的条纹被使用,因为它的失真最小。横切墨卡托投影有许多变体,但投影逻辑类似。区别在于中央子午线(触及圆柱体的经线)的位置和条纹的宽度。

这种独特的狭窄投影区域广泛用于具有南北延展的土地测量,因为其精度高。出于同样的原因,使用米(或在美国使用英尺)作为坐标单位,而不是纬度和经度。

通用横切墨卡托

通常被称为UTM的通用横切墨卡托是横切墨卡托投影中最知名和使用最广泛的。它沿每条经度条纹投影,因此将地球平均分为 60 条经度条纹或区域,每个区域宽度为6 度纬度

在 Plate Carrée 投影中显示世界上的 UTM 区 1 至 60。(作者制作)

由于每个区域都是独特的投影,我们不能将它们一起显示在世界地图上,而必须使用另一种投影来显示如上所示。如你所见,由于条纹的狭窄,许多国家和州的领土区域会重叠多个 UTM 区。

新加坡横切墨卡托

一些国家使用横切墨卡托的变体。例如,新加坡有其国家投影,称为 SVY21(新加坡垂直基准 2021)。这有意义,因为新加坡是一个微小的国家。虽然它位于 48 号 UTM 区内,但由于不在该区域的中央子午线上,它仍会出现失真。

SVY21 投影的中心参考点,官方称为 BASE7(位于皮尔斯水库第 7 号柱)。(来自 Google Maps 的截图)

对于 SVY21,中央子午线位于新加坡中心,提供了最高的准确性。

兰伯特等距圆锥投影

使用兰伯特等距圆锥投影的世界地图。(作者制作)

使用兰伯特等距圆锥投影的世界地图视图看起来很独特,好像投影在一个 3/4 圆上。

圆锥投影类型。(作者制作)

这是因为它是一种圆锥投影类型,其中圆锥像派对帽一样放置在地球顶部,表面被投影到内部,然后展开。

你可以想象一个圆锥的周长将触及地球的某一点。这条线被称为标准平行线,是投影区域失真最小的线。

普遍使用的美国州地图是使用兰伯特等距圆锥投影创建的。(作者创建)

锥形投影的优势在于我们想要投影位于中纬度的国家,并且这些国家具有东西向的质量分布,而标准纬线正好放置于此。之前提到的平面和圆柱形投影无法准确实现这一点。

此外,兰伯特等距圆锥投影可以在标准纬线上保持角度、形状和方向,因此它被广泛用于为美国、澳大利亚和中国制作地图。

罗宾逊投影

所有前述的投影在减少一种或两种类型的扭曲方面表现优秀,或者仅在有限的范围内表现出色。罗宾逊投影在一定程度上可以减少世界上的所有扭曲。因此,它通常被认为是不同类型扭曲之间的折中方案。

这个投影是由亚瑟·H·罗宾逊于 1963 年创建的,并且曾被国家地理在十年间用作他们的世界地图。

罗宾逊投影。(作者创建)

这也被称为伪圆柱形投影,因为经线被弯曲以减少扭曲。另一种类似的投影是温克尔三重投影,联合国在撰写本文时使用了其世界地图。

总结

本文提到的具体投影可以分为三大类投影表面:平面、圆柱形和锥形。(作者创建)

我展示了一些当今地图中使用的流行投影。它们可以大致分为三大类投影表面,希望你现在能清楚这些投影为何被使用。当然,如果我们更深入地探讨,这将是一个更复杂的话题,但这里展示的基本原理应该足够你自己进一步探索。

进一步阅读

[## 8. 坐标参考系统 - QGIS 文档]

QGIS 3.28 文档:8. 坐标参考系统

docs.qgis.org [## 投影类型 - ArcMap | 文档]

许多常见的地图投影根据所使用的投影表面分类:圆锥形、圆柱形或平面形。

桌面 ArcGIS 投影类型

为什么语言模型无处不在?

原文:towardsdatascience.com/why-are-language-models-everywhere-36d9961dd9e1

答案在于 75 年的 NLP 历史

Ajay HalthorTowards Data Science Ajay Halthor

·发表于 Towards Data Science ·6 分钟阅读·2023 年 5 月 26 日

--

图片由 Romain Vignes 提供,来自 Unsplash

你是否想过我们是如何走到今天的 ChatGPT 和大型语言模型的?答案在于自然语言处理(NLP)本身的发展。所以让我们来谈谈吧。别担心;历史比你想象的更有趣!第一部分将描述 AI 和 NLP 的诞生。第二部分将讨论该领域的主要支柱。第三到第五部分将详细介绍过去 75 年的时间线。最后第六部分,我们将描述这些领域如何汇聚到语言建模中,今天已经变得如此流行!

1 起源

起初,有艾伦·图灵 1950 年的出版物 计算机及智能,他提出了“机器能思考吗?”的问题。该论文常被誉为人工智能的起源。虽然它没有明确讨论自然语言,但为未来的 NLP 研究奠定了基础。这也是为什么 NLP 的最早工作出现在 1950 年代。

NLP 的两个支柱

  1. 机器翻译:这是当 AI 接收一种语言的句子并输出另一种语言的句子。例如,谷歌翻译。

  2. 语音处理:AI 接收音频作为输入,并生成相应的文本作为输出。

  3. 文本摘要:AI 接收一个故事作为输入,并生成一个摘要作为输出。

  4. 语言建模:AI 接收到一系列单词后,将确定下一个单词。

这些只是其中的一部分。随着时间的推移,各个领域的支柱逐渐趋向于使用语言模型来完成其任务。在接下来的部分,我们将深入探讨每个时间线。

图 1:NLP 主要支柱的时间线(图像由作者提供)

3 机器翻译

基于规则的系统:1954 年出现了乔治城 IBM 实验,用于冷战时期的俄语到英语翻译。这个想法是将翻译任务分解为一组规则,以便将一种语言转换为另一种语言,即基于规则的系统。另一个早期的基于规则的系统是耶霍舒亚·巴尔-希勒尔的“分析引擎”,用于将希伯来语翻译为英语。

统计方法:规则基础系统的问题在于它们做了大量假设。问题越复杂,这些假设就越成问题。翻译是复杂的。从 1980 年代起,随着我们获取了更多的双语数据和统计方法的逐步完善,我们开始将这些统计模型应用于语言翻译。一种被称为统计机器翻译(SMT)的范式变得流行。SMT 范式将问题分解为两个子问题:翻译问题和语言建模问题。

神经方法:自 2015 年起,SMT 已被神经机器翻译取代。这些方法利用神经网络直接学习翻译任务。它们包括递归神经网络的发展,最终发展为 Transformer 模型。随着像 GPT 这样的模型的引入,基线预训练模型变成了语言建模,并通过翻译进行微调。

4 语音处理

基于规则的系统:语音处理的起点也可以追溯到 1950 年代和 60 年代,那时能够识别单个数字和单词。例如,贝尔实验室的 Audrey 识别数字,而 IBM 的Shoebox则执行语音命令下的算术运算。

统计方法:然而,将语音转换为文本是一个复杂的问题;存在不同的方言、口音、音量。因此,将这个复杂问题分解为子问题是一个解决方法。大约在 70 年代,隐马尔可夫模型被引入后,语音到文本的复杂问题可以被分解为三个更简单的问题:

  • 语言建模:我们可以确定单词和句子的顺序。这些是n-gram 模型。

  • 发音建模:这是为了将单词与音素关联起来。这些本质上是简单模型或甚至是表格。

  • 声学建模:我们理解语音与音素之间的关系。这些是带有高斯混合模型的隐马尔可夫模型。

这三部分被单独训练,然后一起使用。但这也带来了自身的复杂性。

神经网络方法:在 2000 年代初,我们看到这些技术被神经网络所取代。随着大规模文本语料库的出现,神经网络开始超越其他所有方法。它们实现了端到端的语音转文本。因此,我们可以直接优化从输入语音生成文本的目标;这带来了更好的性能。随着领域的发展,我们进入了递归网络、卷积神经网络,并最终细化了预训练语言模型。

5 文本摘要

基于规则的系统:研究始于 Luhn 在 1958 年发表的The automatic creation of literature abstracts,该研究通过词频来排名句子的的重要性。这种方法从原文中选择句子来构建总结;相应的总结被称为“基于提取的总结”。该领域的下一个重大突破出现在 1969 年,Edmonson 的论文New methods of automatic extractive中。他声称句子的重要性不仅取决于词频,还取决于其他因素,如句子在段落中的位置;句子是否包含某些提示词;或者句子是否包含标题中的词。在 1980 年代,我们尝试不使用原始句子来总结文本。这些是“抽象总结”。FRUMP(快速阅读和理解记忆程序)和 SUSY 是早期的这种系统的实现。然而,它们也依赖于手工制作的规则,且总结质量不高。

统计方法:在 90 年代和 2000 年代,我们使用统计方法构建分类器,以确定一个句子是否应该被包含在总结中。这些分类器可以是逻辑回归、决策树、SVM 或任何其他统计模型。

神经网络方法:自 2015 年起,神经网络随着A neural attention model for abstractive sentence summarization.的引入产生了影响。这种方法生成的抽象总结通常是非常简短的标题。然而,LSTM 单元的加入以及sequence-to-sequence 架构使得处理更长输入序列和生成合适总结成为可能。从那时起,该领域采取了与机器翻译相同的方法,并使用了我们今天看到的预训练和微调架构。

6 将一切整合起来

前面讨论的多个支柱的历史展示了一些共同的模式。

规则基础系统在 1950 年代和 60 年代的早期 AI 中占据主导地位。到 70 年代,我们看到了统计模型的引入以解决这些问题。然而,由于语言的复杂性,这些统计模型会将复杂任务拆解为子任务来解决这些问题。随着 2000 年代更多数据和更好硬件的出现,神经网络方法逐渐兴起。

神经网络可以端到端地学习复杂的语言任务,因此比统计方法表现更好。Transformer 神经网络 于 2017 年推出,能够有效地学习解决语言任务。但由于它们需要大量的数据来有效地训练模型,BERT 和 GPT 被引入并使用了迁移学习的概念来学习语言任务。这里的想法是,对于那些对语言本身有一定基础理解的系统,语言任务并不需要大量数据。例如,GPT 通过首先理解语言建模,然后在特定语言任务上进行微调,从而获得这种“语言理解”。这就是为什么现代 NLP 核心都趋向于使用语言模型的原因。

希望你现在明白为什么像 ChatGPT 这样的大型语言模型如此重要,以及为什么我们到处都能看到语言建模。到达这一点花费了近一个世纪的时间。有关 NLP 和语言建模的更多详细信息,查看这个视频播放列表,它深入探讨了该领域的不同概念。祝你学习愉快!

为什么随机实验是因果推断中的黄金标准?

原文:towardsdatascience.com/why-are-randomized-experiments-the-gold-standard-in-causal-inference-f3fa240a1d02?source=collection_archive---------14-----------------------#2023-03-07

理解实验中的识别假设

Murat UnalTowards Data Science Murat Unal

·

关注 发表在 Towards Data Science ·7 分钟阅读·2023 年 3 月 7 日

--

图片由 takaharu SAWA 提供,来源于 Unsplash

没有假设的因果推断是不可能的。每一种可用的方法都需要不可测试的假设,以从数据中观察到的关联中建立因果关系。因此,陈述识别假设并为其辩护是因果推断中最关键的方面,但这也是最被忽视的方面。

在我之前的文章中,我们通过描述什么是识别以及为什么它在因果推断中优先于估计来开始讨论这个话题。提醒一下,识别本质上包括清晰地陈述从数据中获得的统计估计需要的假设,以便这些估计可以被赋予因果解释,并在我们的因果分析中为这些假设辩护。接下来我们讨论随机实验中的识别,即因果推断中所有识别策略中的黄金标准。

## 识别:可信因果推断的关键

提升你的因果智商,通过掌握识别建立对因果推断的信任

towardsdatascience.com

实验在其他识别策略中为何具有特殊地位?这有两个原因。首先,良好开展的实验只需最少的识别假设来确定因果关系。其次,这些假设比其他方法所需的假设更为合理。这两个事实的结合增加了随机实验中因果推断的可信度。

为了帮助讨论,我们再次考虑一个简单的案例,其中两个处理条件由一个二元随机变量描述,Ti=[0,1],结果由Yi表示。我们感兴趣的是找到平均处理效应(ATE),即期望值之间的差异:

其中 Yi1 表示如果被试 i 接受治疗的潜在结果,而 Yi0 表示如果被试 i 不接受治疗的潜在结果。如果潜在结果的含义不清楚,我强烈建议你阅读上述链接的文章。你将看到一个具体的例子,这将帮助你理解本文的其余部分。

由于我们仅能访问样本而不是整个总体,我们只能观察到条件期望 E[Yi|Ti=1]E[Yi|Ti=0],即我们数据中看到的处理组和对照组的期望结果。

现在,为了声称我们通过条件期望的差异得到的是 ATE,即我们的因果估计量,我们需要展示这些差异等同于无条件期望。做到这一点的方法是通过提出假设并使我们的观众相信它们在我们特定背景下的合理性。

识别部分 1:陈述假设

在随机实验中,识别假设如下:

**1. 独立性:**潜在结果与治疗状态独立。

2. SUTVA(稳定单位处理价值假设):(1)没有互动效应,意味着一个单位收到的处理不会影响另一个单位的结果。(2)没有隐藏的处理变异,只有施加在特定受试者上的处理水平可能会影响该受试者的结果。

从技术上讲,除了两个识别假设之外,我们还假设实验对象之间没有系统性流失和不遵守情况。

有了这些假设,我们可以如下编写观察结果的期望值:

这使我们能够利用数据中处理组和对照组的结果来获得 ATE:

另一种观察这些假设如何发挥作用的方式是查看以下分解:

独立性假设有效地消除了偏差项,我们剩下的是 ATT(对处理组的平均处理效应),这相当于 ATE。

识别部分 2:捍卫假设

这种识别策略的直接后果是它允许我们

断言处理组和对照组在所有方面(可观察和不可观察)都是相同的,除了处理分配的差异。

此外,如果我们有一个良好实施的实验且受试者之间没有系统性流失,那么说服我们的观众接受识别假设的有效性将容易得多。

为了使事情更具体,我们用一个模型来描述结果、Yi、处理Ti和协变量Xi之间的关系,并且为了简化起见,我们假设它是线性的:

P协变量可能非常大,包括对结果的其他线性影响的观察或未观察到的影响,Bi是个体处理效应,即两个潜在结果Yi1 — Yi0之间的差异。我们没有观察到这一效应,但通过从处理组的平均结果中减去对照组的平均结果,我们得到了这个:

这表明当所有其他效应对结果的均值差异相同时,我们通过均值差异获得我们想要的结果。

这两组是相同的。

这是完美平衡的情况,随机化通过构造保证了这一点,因为它为我们因果模型中其他P因果因素提供了正交性。换句话说,我们知道在确定处理分配时除了掷硬币之外没有使用其他方法。

仍然存在对内部有效性的威胁,这可能会削弱我们的因果推断。首先,SUTVA 的违背,即两个组之间的受试者相互作用,在许多社会和经济应用中可能会发生。如果发生这种情况,我们所做的比较将不再是处理组和对照组之间的比较,而是处理组和部分处理组之间的比较。

其次,由于共同原因排除混淆是有期望保证的,这意味着处理组和对照组仅在处理分配上有所不同而在其他方面没有差异的概率,随着样本量的增加而变得任意更高。在给定样本中,特别是如果样本较小,其他原因的净效应可能不为零,这可能会偏倚 ATE。

因此,在分析实验之前,检查处理组和对照组样本中的观测值平衡总是一个好的做法。通常,这通过计算标准化差异作为分布差异的无尺度度量来完成,而不是使用 t 统计量。具体而言,对于每个协变量,我们报告按处理状态划分的平均差异,按方差和的平方根进行缩放:

其中 S1²、S0² 分别是处理组和对照组的样本方差。作为一个经验法则,大于 0.10 的差异可能表明不平衡,这将要求在分析中进行协变量调整。

然而,请注意,我们没有办法测试实验中是否排除了共同的不可观测原因。我们只是简单地假设如果在观测值中达成平衡,那么它在不可观测值中也有可能保持平衡。但这可能并非如此,尤其是在小样本中。

我们还需要注意实验结果的普遍适用性,即其外部有效性。重要的是认识到实验使我们能够在研究所用的人群中识别处理效应。此外,除非实验旨在测量长期效应,否则估计的 ATE 通常适用于短期。通常将实验中的估计视为真理,不仅仅在研究样本中,更广泛地应用,但这可能并不有效。

有许多案例中,企业决定根据一个市场、样本或时间段的实验结果来扩展操作,但由于这些结果未被证明是普遍适用的,因此失败了。一般来说,为确保有效的外推,我们需要随机抽样以及处理的随机化,或额外的假设。例如,如果我们有一个在线实验,为确保结果对预定位置的所有用户都是普遍适用的,我们需要通过在一天内和一周内随机抽样访问者来消除星期几和时间的影响。

结论

因果推断的困难在于其固有的不确定性,只要我们提高对最关键部分——识别的理解,就可以取得进展。随机实验需要的识别假设比其他任何识别策略都少。而且,这些假设在实验中更加可信。这两个特征共同使实验在因果推断中赢得了金标准的地位。然而,如果实验未能正确进行,它们仍可能缺乏内部效度,并且将实验发现推广到其他情境也存在挑战。

感谢阅读!希望你觉得这值得你的时间。

我努力为从事因果推断方法和应用以及营销数据科学的实践者编写高质量和有用的文章。

如果你对这些领域感兴趣,可以关注我,并随时分享你的评论/建议。

参考文献

[1] L. Keele, 因果推断统计:来自政治方法论的视角(2015),政治分析。

[2] A. Lewbel, 计量经济学中的识别意义:识别动物园 (2019),经济文献期刊。

[3] G. W. Imbens 和 J. M. Wooldridge, 项目评估计量经济学的最新进展,(2009),经济文献期刊。

[4] J. List, 《电压效应:如何将好想法变成伟大想法并使其扩展》,(2022)。

为什么回测重要以及如何正确进行

原文:towardsdatascience.com/why-backtesting-matters-and-how-to-do-it-right-731fb9624a

我们如何知道我们的预测模型是否准确可靠,并评估其在未见数据上的表现?这就是回测的作用所在。

Davide BurbaTowards Data Science Davide Burba

·发表于Towards Data Science ·阅读时间 7 分钟·2023 年 7 月 18 日

--

“回测”,由Giulia Roggia。经许可使用。

  • 什么是回测?

  • Python 示例:航空乘客

  • 结论

什么是回测?

为了评估预测模型的表现,我们使用一种称为回测的程序(也称为时间序列交叉验证)。回测本质上是测试模型在过去使用时的表现。

它是如何工作的?

要对时间序列预测模型进行回测,我们首先将数据分为两部分:训练集和验证集(有时也称为测试集,但我们将在接下来的部分澄清其差异)。训练集用于训练模型,而测试集用于评估模型在未见数据上的表现。模型训练完成后,可以用它对测试集进行预测。然后可以将这些预测与实际值进行比较,以查看模型的表现。

我们如何衡量模型的表现?

有多种指标可以用来评估时间序列预测模型的表现,如平均绝对误差(MAE)或均方根误差(RMSE)。这些指标衡量预测值与实际值的接近程度。

该程序通常会重复多次,允许我们:

  • 对模型表现有一个良好的估计

  • 视觉化模型表现随时间的演变

下面我们展示了回测过程的图形表示,使用了 3 个分割:

使用扩展窗口的回测。图像由作者提供。

在上图中,我们只展示了 3 个不重叠的验证时期。然而,没有什么阻止我们使用更多部分重叠的窗口,可能每个时间步一个。

为什么回测很重要?

如果模型在测试集上表现良好,你可以对其在未来做出准确预测的能力更有信心。通过回测时间序列预测模型,你可以了解它在历史数据上的表现,并对如何在未来使用它做出更明智的决策。

如何利用回测来改进我们的模型?

回测还可以用于改进我们的模型,例如通过调整超参数或特征选择。在这种情况下,由于我们通常会选择表现最佳的组合,我们的度量估计可能会过于乐观。如果我们想获得一个不偏倚的性能估计,我们应该在两个不同的连续时期进行两次回测:

  1. 第一次回测用于调整超参数,选择最佳特征,通常做出构建我们“最终”模型所需的所有选择。用于评估这个时期内预测的分割称为验证集

  2. 第二次回测用于对最终选择的模型进行性能估计。它应该在用于验证之后的时期进行。用于评估这个时期内预测的分割称为测试集

回测变体

扩展或固定训练窗口?

在前面的图中,我们可以看到训练窗口的大小在扩大。另一种选择是使用固定大小的时间窗口作为训练集,如下图所示。

使用固定窗口的回测。图像来源于作者。

使用固定窗口的缺点是模型使用的数据较少进行训练,但如果数据非常动态,最新数据可能比旧数据更具信息量,这种做法可能更为便利。一般来说,窗口大小可以看作是一个超参数,可以通过回测进行调整。

训练截止与特征截止

需要记住的一点是,用于训练模型的数据和用于构建输入特征的数据是两个不同的概念。

原则上,我们可以有与数据中的时间步数一样多的分割。然而,为每个时间步重新训练模型可能会耗时且计算密集。在实践中,通常使用的替代方法是较少频繁地训练模型,例如每隔N时间步训练一次,并在每个时间步用最新的模型进行预测。数字N也可以通过回测进行调整。

最后,请注意,在本文中,我们为了简便考虑了单一时间序列。然而,相同的概念可以应用于对多个时间序列进行预测的模型(如果您对这个话题感兴趣,可以查看这篇文章)。

示例:航空乘客

图片由 Lars Nissen 来自 Pixabay

在本节中,我们展示了一个简单的 Python 回测实现,使用了航空乘客数据集,该数据集在Darts上提供,采用 Apache 2.0 许可证。

让我们首先导入必要的库。

import pandas as pd
import plotly.graph_objects as go
from lightgbm import LGBMRegressor

数据准备

数据集包含了从 1949 年到 1960 年的国际航空乘客月度总数。让我们加载并准备数据。

# Load data.
data = pd.read_csv('https://raw.githubusercontent.com/unit8co/darts/master/datasets/AirPassengers.csv')
# Rename columns.
data = data.rename(columns = {"Month": "time", "#Passengers": "passengers"})
# Set time to datetime.
data.time = pd.to_datetime(data.time)
# Set time as index.
data = data.set_index("time")

让我们快速查看一下数据:

# Let's visualize the data.
def show_data(data,title=""):
    trace = [go.Scatter(x=data.index,y=data[c],name=c) for c in data.columns]
    go.Figure(trace,layout=dict(title=title)).show()

show_data(data,"Monthly totals of international airline passengers")

图片由作者提供。

我们可以看到数据展示了强烈的上升趋势和年度季节性。

数据工程

基于以下内容,让我们预测下个月的值:

  • 一些滞后值:前 3 个月,加上 6/12/24 个月前的值

  • 当前月份(作为分类特征,以帮助处理季节性)

def build_target_features(data, lags, horizon=1):
    # Build lagged features.
    feat = pd.concat(
        [
            data[["passengers"]].shift(lag).rename(
                columns={"passengers": f"lag_{lag}"}
            )
            for lag in lags
        ],
        axis=1,
    )

    # Build month feature.
    feat["month"] = [f"M{m}" for m in data.index.month]
    feat["month"] = feat["month"].astype("category")
    # Build target at horizon.
    targ = data["passengers"].shift(-horizon).rename(f"horizon_{horizon}")
    # Drop missing values generated by lags/horizon.
    idx = ~(feat.isnull().any(axis=1) | targ.isnull())
    feat = feat.loc[idx]
    targ = targ.loc[idx]
    return targ, feat

# Build targets and features.
target, features = build_target_features(
    data,
    lags=[0,1,2,5,11,23],
    horizon=1,
)

回测实现

让我们实现一个简单而通用的回测函数。该函数接受模型和数据作为输入,以及用于开始训练窗口和重新训练的频率的两个参数。我们使用扩展的训练窗口,并在开始窗口之后的每一个时间步做出预测。

def run_backtest(
    model,
    target,
    features,
    start_window = 10,
    retrain_frequency = 6,
):
    """Simple backtesting implementation.

    Args:
        model: A model with fit and predict methods.
        targets: Series with the target in chronological order.
        features: Dataframe with the features in chronological order.
        start_window: The initial window to train a model.
        retrain_frequency: How often to retrain the model.

    Return:
        A dataframe with the validation target and prediction.
    """

    # Sanity check on shape
    assert features.shape[0] == target.shape[0]

    all_preds = []
    all_targets = []
    last_timestep = start_window

    while last_timestep < len(target):
        # Split train/valid
        targ_train = target.iloc[:last_timestep]
        feat_train = features.iloc[:last_timestep]
        targ_valid = target.iloc[last_timestep:last_timestep+1]    
        feat_valid = features.iloc[last_timestep:last_timestep+1]

        # Train the model
        if last_timestep==start_window or last_timestep % retrain_frequency == 0:
            model.fit(feat_train,targ_train)
        # Predict on valid set
        pred_valid = model.predict(feat_valid)
        # Save the output
        all_preds.append(pred_valid[0])
        all_targets.append(targ_valid)

        # Process next timestep
        last_timestep += 1

    # Format output
    output = pd.concat(all_targets).rename("target").to_frame()
    output["prediction"] = all_preds
    return output

上述代码可以通过多种方式扩展。例如,您可以允许使用固定的训练窗口,或通过使用矢量化和/或并行化来提高效率。然而,我们建议从简单开始,仅在必要时增加复杂性。

现在我们可以将回测函数应用到我们的数据上,并检查回测性能。

# Apply run_backtest to our data.
output_backtest = run_backtest(
    LGBMRegressor(min_child_samples=1, objective="mae"),
    target,
    features,
    start_window = 36,
    retrain_frequency = 6,
)
# First, let's visualize the data.
show_data(output_backtest,"Backtested predictions at horizon 1")
# Then, let's compute the MAE.
MAE = abs(output_backtest.prediction - output_backtest.target).mean()
print(f"MAE: {MAE:.1f}")

这给出了 25.6 的 MAE 以及下图。

图片由作者提供。

此示例中使用的完整代码可在 这里获取。

结论

回测是时间序列预测中的一个关键步骤,它允许您评估模型在未见数据上的准确性和可靠性。通过遵循一些最佳实践并使用适当的回测方法,您可以提高模型的性能,避免常见的陷阱。

喜欢这篇文章吗? 看看我的其他文章 并关注我以获取更多内容! 点击这里 阅读无限制的文章,并在没有额外费用的情况下支持我❤️

为什么卷积?理解卷积和深度网络中的特征提取

原文:towardsdatascience.com/why-convolve-understanding-convolution-and-feature-extraction-in-deep-networks-ee45d1fdd17c

1D/2D 卷积的解释、它在特征学习中的作用及一个可视化工具

J. Rafid Siddiqui, PhDTowards Data Science J. Rafid Siddiqui, PhD

·发表于 Towards Data Science ·9 分钟阅读·2023 年 2 月 5 日

--

图 1: 1D/2D 卷积及交互式可视化工具(来源:作者)

如今,构建深度神经网络时常常会用到一组卷积层。然而,早期的神经网络和其他机器学习框架并没有使用卷积。特征提取和学习曾经是两个独立的研究领域,直到最近。这就是为什么理解卷积的工作原理及其在深度学习架构中占据重要位置的原因。在这篇文章中,我们将深入探讨卷积,你将能够通过一个交互式工具更深入地理解这个概念。

引言

卷积是通过应用另一个函数来改变一个函数的形状的过程。这是一种对两个函数进行的操作,就像其他算术操作一样,然而它作用于函数而不是函数值。因此,它产生一个第三个函数作为结果。更正式地说,卷积 *C=f(x)g(x) 是函数 f 和函数 g 之间的一种操作,由卷积算符 ***** 表示。在数学公式中,卷积是函数 f 与函数 g 的乘积的积分,其中函数 g 在 x 轴上被镜像并按时间步 t 进行平移。

注意,输出函数是t的函数,而输入函数fx的函数。执行了一定数量的时间步长 —— 通常等于f函数值的样本数。在每个时间步长上,卷积函数通过将G的函数值作为权重来计算函数f曲线下的面积。t的符号决定了移动的方向。对于t的正值,移动方向是朝向 + ∞,而对于负值,移动方向是朝向 — ∞。直观地说,卷积函数提供了f函数值的加权移动平均。

卷积算子也可以被看作是一种积分变换。在微积分中,积分变换是一种将输入函数f与内核函数K结合应用的函数变换T

数据样本的卷积

内核函数的设计决定了变换的输出。最常使用的是对称内核函数,这使得方程中的乘积具有交换性。内核函数的大小决定了卷积的扩展范围。较大的内核大小包含更多的邻近点,并且输出受大量邻居的影响。

图 2:卷积算子对表示统计数据的随机点的示例效果(来源:作者)

如果我们以图 2 中的数据集为例,并应用一个常数值为0.25的卷积内核,则会得到一个如图 2 所示的正态分布曲线。这表明,具有常数值输出的卷积函数会产生数据的标准化曲线。现在,如果我们扩展这个概念,并创建一个大小为 10、仅填充 0.1 值的内核函数,并将其应用于相同的数据,那么我们会看到图 3 中的结果。如图所示,结果是一个平滑的曲线。这是因为我们创建了一个具有 10 个邻近点的移动平均内核。内核函数中的每个值为计算给定点的加权平均提供了一个概率权重。

图 3:使用常数内核的数据显示(来源:作者)

我们提供的这些权重在内核函数中不必全部相同。实际上,大多数时候它们并不是常数值。因此,我们可以构造一个内核函数,该函数的值从 0 逐渐增加到 0.5,直到内核的中间,然后逐渐减少回零。这种内核会产生图 4 中的结果。结果显示,它在平滑数据曲线方面做得更好。这是因为我们近似了一个最常见的平滑内核——高斯内核

图 4:高斯卷积核对数据点的影响(来源:作者)

到目前为止,你已经看到卷积作为平滑操作的效果,然而,正如你将在后续章节中看到的,卷积操作的应用不仅限于平滑。卷积是一个通用操作符,可以被修改来执行对函数值进行多种不同形式的计算。通过卷积操作可以创建的效果完全取决于卷积核函数的构造。

一维卷积

在前一节中,我们讨论了卷积及其对数据的影响。我们使用的示例是 1D 数据。在本节中,我们将扩展并展示卷积在各种 1D 信号中的表现。卷积在 1D 信号上的应用是最明显的应用,实际上这就是概念的起源。1D 信号是独立变量(例如时间)的函数,通过采样获得一组离散的依赖变量值作为输出。离散采样范围通常用于观察函数。采样范围提供了一个视窗,显示该范围内函数的形状。两个信号之间的卷积操作是第二个信号改变第一个信号形状的过程。正如你在图 5 中看到的那样,一条曲线如何影响另一条曲线并产生结合两者的第三条曲线(粗略地说类似于平均)。

图 5:卷积操作的描述(来源:作者)

在这种情况下,卷积发生在两个相似的函数之间。卷积函数仅在幅度上有所不同,其相位已被移动。然而,通常情况可能并非如此。卷积函数可以采取各种不同的形式,并产生不同的结果。图 6 中展示了一些卷积核函数及其对不同函数的影响。你可以看到,当方波与正弦波或直流核卷积时,‘平均效果’。在两个相位相反的情况下,波并不会像两波的破坏性干涉那样相互抵消,而是得到两个相位的平均输出。当应用于由多个简单的正弦、余弦波和噪声组件组成的复杂信号时,卷积信号会变得平滑,有助于减少噪声。平滑是卷积操作最常见的效果;然而,卷积核函数也可以用来突出、区分或抑制函数的不同部分。我们将在后续章节中看到这些如何发挥作用。

图 6:一维卷积的结果,第一行:对方波信号的影响,第二行:各种正弦波相互作用的影响(来源:作者)

二维卷积

2D 卷积只是 1D 卷积的扩展,增加了一个维度。操作不仅在一个维度上进行,而是在两个方向上进行。图像是 2D 函数的一个例子,其值(即 R、G、B)表示经过量化得到的函数离散样本。这些样本排列在一组 2D 矩阵中,每个矩阵代表一个颜色通道。这样的数据结构的内核函数也是一个 2D 矩阵,一组内核矩阵称为滤波器。卷积操作通过在所有颜色通道上移动滑动窗口迭代执行,并应用相应的内核。

图 7:2D 卷积过程的描述(来源:作者)

在图 7 中,你可以看到 2D 卷积应用于图像的过程。对于每个像素,提取一个与内核大小相同的邻域窗口。若像素在图像边界上,则插入一组零填充像素。邻域窗口和内核矩阵的点积给出新卷积图像中相应位置的像素值。从图中可以看到,卷积图像在视窗对角线上具有更高的值。这就是这种类型的内核对图像所做的。它是一个边缘检测器的示例,能够找到图像中的对角边缘。对图像中的每个像素重复相同的过程,通过在图像上滑动内核并获得相同大小的新卷积图像。注意我们没有翻转内核矩阵;这是因为该内核是对称的。如果内核构造为非对称,则在计算点积之前需要翻转/镜像。

图 8:卷积操作的示例应用,第一行:图像模糊,第二行:边缘检测(来源:作者)

如前所述,卷积过程不仅限于某一特定操作,它可以用于执行各种不同类型的操作。在图 8 中,我们展示了卷积操作的两个示例应用。当图像与高斯内核卷积时,会产生模糊效果。然而,当应用边缘检测内核(例如我们之前展示的那种)时,会生成一个仅突出图像边缘的输出图像。内核系数决定了图像的输出。这些系数可以是手工制作的,也可以通过学习框架学习得到。

2D 卷积的特征学习

在上一节中,我们看到 2D 卷积如何应用于图像。在这一节中,我们将探讨如何利用卷积提取特征。图像处理中的特征是描述物体的像素不变模式。可以从物体图像中提取的一组特征可以用来唯一地识别该物体。卷积核提供了一种稳健的特征构建方式,因为它们是批量操作的机器。否则需要的特征提取操作(例如,一组边缘)只需一次矩阵乘法和一个加法操作。这降低了计算成本,并提供了一个通用的特征提取框架,通过构建和应用各种特征核。在图 9 中,显示了四个边缘检测核,每个核专门用于检测某种边缘方向。

图 9:2D 彩色图像上的特征提取过程示例(来源:作者)

显示的滑动窗口决定了特征的大小。根据滑动窗口的步幅,可以从图像中提取出多个特征。一个具有单位像素步幅的小窗口提供一组局部特征,而少量较大的窗口构建全局特征空间。

在深度神经网络(例如 CNN)中,它通过指定每层学习到的特征数量来控制。此外,深度网络将卷积核的参数学习作为整体参数学习的一部分。因此,无需手动构建特征,然而,从概念上讲,学习到的特征与之前描述的特征是相同的。例如,代替为垂直边缘构造粗糙的手工制作的核(这些在现实世界的图像中通常不那么频繁),它可以学习一个非线性核,这可以找到物体的曲率。在卷积层学习到的特征为深度神经网络后续部分的一组全连接分类层提供了基础。

最终说明

在本文中,我们探讨了卷积的概念、其各种类型,并讨论了如何利用卷积从图像中提取特征。卷积是一种通用计算机制。由于与 GPU 架构的兼容性,它可以利用 GPU 处理的强大能力。计算可以被拆分成批次并独立执行。利用卷积进行特征学习可以提供对图像中特征的稳健和自动化提取,这正是深度神经网络所采用的。实际上,特征学习可能是物体分类深度卷积神经网络中最关键的部分。学习到的核更好地捕捉复杂的模式,并且学习到的特征更加多样。

你可以通过下面链接找到的可视化工具来进一步提高对概念的理解和实践。

图 10:2D 卷积的可视化工具(来源:作者)

代码:

www.github.com/azad-academy/DL-Convolutions

成为 Patreon 支持者:

www.patreon.com/azadacademy

在 Substack 上找到我:

azadwolf.substack.com

关注 Twitter 获取更新:

twitter.com/azaditech

获取书籍:

www.amazon.com/dp/B0BT4YBZQC

为什么数据不是新石油以及数据市场为何让我们失望

原文:towardsdatascience.com/why-data-is-not-the-new-oil-and-data-marketplaces-have-failed-us-b42dd87a0ba0?source=collection_archive---------1-----------------------#2023-07-13

实时程序化数据交换将如何改变一切

克莱门斯·梅瓦尔德Towards Data Science 克莱门斯·梅瓦尔德

·

关注 发表在 Towards Data Science ·13 分钟阅读·2023 年 7 月 13 日

--

数据是新石油”这一说法由克莱夫·汉比于 2006 年提出,并且自那时以来被广泛引用。然而,这一类比在只有少数几个方面才具有合理性(例如,两者的价值通常随着精炼而增加),而且数据对经济的广泛影响在除少数科技和金融公司之外并不显著。但石油和数据之间的实际差异是根本性的。

最显著的是,石油是一种商品。其质量标准化且可测量,使得来自不同来源的石油在经济学上是“同质商品”。它广泛存在并有一个固定价格。最重要的是,如果你有一桶石油,你不能简单地复制一桶——石油是一种有限资源,必须从地下开采。

数据,另一方面,是一种异质商品。它种类繁多,每次出现的价值无法客观衡量。当两个参与方交换商品时,卖方需要定价,买方需要确定他们愿意支付的价格。这受到数据的两个特性影响:

向另一位买家出售相同数据的边际成本为零。生产数据的成本高度可变(测序基因组的成本高于测量体温),但一旦数据存在,这个成本已沉没。将数据出售给另一买家的过程实际上就是复制它,实践中几乎没有成本。

在“消费”数据之前很难确定其价值。销售线索数据库只有在产生实际销售时才有价值。更糟糕的是,相同数据集的价值高度依赖于买方(或其预期用途)。在这方面,数据实际上更接近“体验商品”,如书籍或度假。

在这篇文章中,我将论证数据是最未被充分利用和因此被低估的商品之一。我概述了一个实时程序化数据交换,它是我正在顾问的新公司Arcus的核心,并可能对数据经济产生深远的影响。

“数据是最未被充分利用和因此被低估的商品之一。”

为什么我们应该关注数据的经济学?

区分 1st 和 3rd 方数据

我认识的没有人对数据的重要性提出异议。但即便*“数据是资产”*的叙述已经相当普遍,数据可能仍然是最未被充分利用和因此被低估的商品之一。

当大多数企业考虑数据时,他们考虑的是他们拥有的数据。这些 1st 方数据(1PD)通常来自网站、CRM/ERP 系统、与客户的通信等。一些 1st 方数据集比其他的数据集更有价值:谷歌的大量搜索和点击历史是他们 1PD 语料库的一部分。

作者提供的图片

显而易见的是,存在的第三方数据(3PD),即你不直接拥有的数据,数量比你的 1PD 大几个数量级。我将要提出的观点是,大多数人没有意识到 3PD 对他们业务的价值。让我们用一个例子来说明这一点。

检测电子邮件垃圾邮件(以及为什么你的 1PD 可能没有你想的那么有价值)

你认为检测电子邮件垃圾邮件的最具预测性信号是什么?最常见的答案包括:错别字、语法 或提到诸如 v1agra 这样的特定关键词。一个稍微更好的答案是 “发件人是否在你的联系人中” — 这并不是因为它是真实的(在你的联系人中有更多有效的非垃圾邮件发件人),而是因为它考虑了电子邮件本身以外的数据来源:你的联系人。

即使仅仅为了这个轶事,我们可以说,检测电子邮件垃圾邮件最重要的信号实际上是发件人域名的年龄。一旦陈述出来,这似乎是直观的:垃圾邮件发送者经常注册新的域名,这些域名在短时间内会被电子邮件提供商屏蔽。

为什么大多数人没有想到这个答案?因为发件人的域名年龄不在你的“第一方数据集”之内,这个数据集仅包含诸如发件人和收件人的电子邮件、主题和邮件正文等信息。但任何对域名有所了解的人都会告诉你,这些信息不仅很容易获取,而且是免费的。获取域名,去到域名注册商处,你可以查到它的注册时间(例如,gmail.com 于 1995 年 8 月 13 日注册)。

事实证明,如果你的数据(1PD)与*其他人拥有的数据(3PD)*结合使用,它可能对你更有价值。

图片由作者提供

从电子邮件垃圾邮件到量化交易(甚至更远?)

从这样一个想法出发,即通过增加发件人域名的年龄来更好地检测电子邮件垃圾邮件,你可以想象有无限种方法可以应用同样的原则。以下是一个简单的例子,展示了你可以从一个地址中找到的数据(至少在美国是如此)。

图片由作者提供

当然,这不是一个新想法。对冲基金已经使用“替代数据”几十年了。RenTech 是最早利用替代数据的公司之一,如卫星图像、网页抓取和其他创意来源的数据集,帮助他们在交易中获得优势。UBS 使用卫星图像来监控大型零售商的停车场并将车流量与季度收入关联,从而在盈利公布之前更准确地预测盈利。

你可以大概猜到这将如何发展。在仅美国就有超过 30 万个数据提供者,以及可能数十亿的数据集。许多数据集可以为你提供预测或分析中的竞争优势。唯一的限制就是你的创造力。

使用外部数据的(主观)价值

尽管外部数据对量化交易公司的价值是即时而显著的,但其他行业的高管却迟迟没有意识到这一点。一个思维实验可以帮助理解:考虑一下你业务中最重要的预测任务。对于亚马逊,这可能是一个给定客户最有可能下一次购买的产品。对于一家石油勘探公司,这可能是发现下一个石油储层的地点。对于一家连锁超市,这可能是任何特定时间点对特定产品的需求。

接下来,想象一下你有一个神奇的旋钮,你可以旋转它来改善预测任务的表现以及对你业务的价值。连锁超市损失了大约 10%的食品因变质。如果它们能够更好地预测需求,就可以改善供应链并减少变质。在约 20%的毛利率下,每降低一个百分点的变质将使毛利率提高 0.8 个百分点。因此,对于像 Albertsons 这样的公司,每提高一个百分点的需求预测可能价值约 6.4 亿美元。替代数据可能会有所帮助。

同样的数据可能为一家连锁超市节省数亿美元,但对商业房地产开发商而言可能更有价值。然而,数据市场尚未能够提取这种价值(通过价格歧视),因为它们离实际的业务应用太远。它们不得不对库存设定一个通用价格,而不考虑最终用途。

尽管如此,外部数据已经成为一个估计达 50 亿美元的市场,增长率每年达 50%,而交易这些数据的市场代表了另外一个 10 亿美元的市场。这仅仅是潜在市场规模的一小部分,原因至少有两个:(1)尽管每家公司都应该能够从 3PD 中受益,只有最具分析成熟度的公司知道如何利用 3PD 来获得优势。(2)那些敢于尝试的人被发现和购买 3PD 的过时流程所拖慢了。让我们简单探讨一下广告购买流程,以说明这一点。

程序化广告可以教会我们如何改善数据经济

广告购买流程的演变

不久前,在 2014 年,程序化广告购买的比例不到数字广告支出的半数。那时人们是如何购买广告的?他们告诉代理商他们想要接触的受众类型。然后,代理商查看他们合作的出版商及其“库存”(杂志页面、广告牌、电视广告位等),并制定一个广告投放计划以满足这些要求。经过一些谈判,公司和代理商最终签署了合同。广告创意将被开发、审查和批准。插入订单将被提交,广告活动最终会开始。几个月后,公司会收到代理商关于广告活动效果的报告(基于一个小的样本数据集)。

随后,谷歌(以及其他一些公司)普及了所谓的程序化广告购买。谷歌创建了自己的广告交换平台(AdX),将来自多个出版商的库存与不同的广告网络连接起来。当用户进行搜索或访问网站时,它会进行实时拍卖(是的,在加载网页的时间内),将所有广告主进行竞争,选出最高出价者(实际上是第二高出价者)来展示他们的广告。

就这样,广告购买从一个需要大量人工参与且透明度极低的漫长过程,变成了一个实时交易过程,这个过程不仅通过拍卖设置价格并且提供了即时的展示效果测量(有时甚至是转化效果)。这种速度、流动性和透明度的提升导致了在线广告市场的爆炸性增长,而程序化广告购买现在占据了接近 90%的数字广告预算。

过时的数据购买过程

事实证明,如今购买数据比 20 年前购买广告更加痛苦。

作者提供的图片

发现: 首先,你需要意识到第三方数据(3PD)对你可能极为宝贵。还记得电子邮件垃圾邮件的例子吗?接下来,你需要具备创造力,考虑所有可能的第三方数据来补充你的第一方数据(1PD)。你会考虑使用停车场的卫星图像来预测零售商的收入吗?然后,你必须前往所有的数据提供商,寻找你认为需要的数据。你会发现大多数“数据市场”基本上只是对描述的自由文本搜索。接下来,你需要查看数据的模式,以确保它包含你需要的信息,具有所需的粒度(例如,有时你需要逐分钟的客流量而不仅仅是每小时一次),并且覆盖范围正确(例如,适用于正确的日期范围或地理区域)。

采购: 一旦你找到你认为需要的数据,你必须弄清楚如何采购这些数据。你会惊讶地发现这并不总是一个简单的“点击购买”的过程。你必须与数据提供者交谈,了解数据许可证(你是否可以将这些数据用于预期的目的?),谈判条款并签署合同。你需要对来自不同提供者的多个 3PD 重复这一过程,这些提供者都有不同的合同、条款和许可证。你等待在邮箱里收到数据(开个玩笑)。

整合: 最终,你获得了你想要的数据。你等待了几周,期间你的数据工程团队将其与 1PD 进行整合,结果你发现它实际上并不像你期望的那样有用。你花费的时间和金钱都白费了,你也再没有尝试过。或者,更让人痛苦的是,你发现 3PD 确实带来了有意义的改善,你继续将你的预测模型投入生产,却发现你需要每小时获取新数据,而你使用的数据源之一仅每周更新一次。如果你再尝试一次,你现在知道,除了基于模式检查粒度外,你还需要考虑刷新率。

这个过程可能需要几个月到一年以上。为了构建更快的马匹,一些咨询公司建议的解决方案是雇用整个“数据采购团队”并与数据聚合商建立关系。

数据经济需要一个实时编程的数据交换平台

我引用程序化广告购买的例子是因为我坚信数据经济可以以相同的方式发展,从而产生类似深远的经济影响。

发现与采购: 想象一个数据交换平台,它将所有数据提供者(即“库存”)汇聚在一起,并合理化许可证,以便能够以编程方式促进交易。数据消费者会提供任何 1PD,并表达他们感兴趣的任务(例如预测需求)以及他们对每单位改善的价值(记住,在需求预测中每 1pp 的改善对 Albertsons 来说价值 640 百万美元?)。数据交换平台会自动识别哪个 3PD 能为该任务提供可衡量的改善,基于数据消费者的预算进行实时拍卖,并优化选择符合要求的 3PD 子集。这种接近实际任务(及其相关价值)的方式将解决现有数据市场必须将数据视为商品而非经验产品的问题。

持续集成和改进: 由于大多数有价值的预测任务本质上是持续性的(例如,你需要定期预测需求,而不仅仅是一次),数据交换将成为重复交易的中心,随着新的数据提供者和消费者进入生态系统,提供越来越多的价值。每次进行预测任务时进行拍卖(而不仅仅是在决定购买哪些数据时进行一次)将确保新的数据提供者立即实现分发,数据消费者也能从最新的数据库存和价格发现中受益。正如广告购买从离线和手动演变到实时一样,数据交易将变得实时、程序化,并且最重要的是可度量。

图片由作者提供

这种“实时程序化数据交换”将为市场中的所有参与者提供经济激励:

  • 数据提供者和消费者都将从改进的发现性中受益。数据市场存在长尾问题:数据量和种类繁多,使用现有方法几乎不可能发现任何给定任务/应用最相关的数据。

  • 标准化条款和许可证,以便交易能够程序化进行,将提升数据经济的速度和流动性,消除购买过程中的摩擦,并使其面向更广泛的受众。因此,整体市场将显著扩大。

  • 通过在拍卖中根据每个数据消费者的主观价值设定价格,如果有多个数据提供者提供可比的数据,消费者可以获得更好的交易,而提供者则可以在不同的消费者之间进行价格歧视。

  • 在一个平台上汇聚数据消费者的需求将为数据提供者提供宝贵的见解。例如,基于需求方的所有任务和支付意愿,数据交换可以准确推断出提供方缺少哪些数据,从而帮助优先考虑数据的获取和创建。请注意,合成数据提供者!

需要解决的难题

除了像 Google 对广告所做的那样解决数据的发现性和定价问题外,这种程序化数据交换还需要处理许可和交付问题,这与 Spotify 对音乐的处理方式类似。但如果没有一些难题需要解决,它就不会如此有趣和有意义。

商业

  • 数据许可仍然相对较新。 根据我的观察,数据许可的标准化程度不高。每个数据提供者都有自己独特的许可证类型,这些许可证往往不兼容。为了促进交换,许可需要被简化。

  • 数据市场可能担心去中介化。 数据生态系统很复杂。对于数据提供者来说,这将是一个全新的分销渠道。他们深知发现性问题的痛苦,而这个交换平台可能会将市场开放给数以百万计的潜在新消费者,他们原本不会考虑替代数据。而数据市场和聚合商则像唱片公司一样,可能希望阻止数据提供者直接访问程序化数据交换。

  • 向“传统”行业引入新的定价模型是困难的。 程序化交易的流动性机制将显著扩展需求和供应两方面,而定价机制将优化价值捕捉。总体而言,程序化数据交换将对数据提供者带来好处。

技术

  • 语义类型检测停留在过去。 为了自动识别哪些数据集可以进行合并,你首先需要理解数据的语义类型。例如,某些数据只是一个数字、一个邮政编码还是一种货币?大多数语义类型检测是基于启发式的,但也有更现代的方法。

  • 你不能通过蛮力来发现数据。 事实证明,有很多数据。寻找哪种 3PD 对你的任务最有益的简单方法就是“尝试”所有数据,以确定哪个提供的价值最大。值得庆幸的是,现代信息理论和数据总结领域的突破使得这个问题变得可处理。

  • 数据整合很困难。 一旦你了解了语义类型,并且有机制来识别哪些第三方数据(3PD)会提供有意义的好处,你就必须以有趣的方式将第一方数据(1PD)和 3PD 进行整合。天气数据可能附带的气象站的经度和纬度与您想要预测航班延误的机场不匹配。或者,脚步流量数据可能是按小时提供的,你需要确定是否使用平均值、最大值或第 n 百分位数来进行每日汇总。

  • 数据安全。 数据提供者不愿意轻易地分享他们的数据(因为数据很容易被复制)。然而,有一些技术(如 联邦学习)可以在保持数据访问和隐私的同时,增强预测能力。

我相信,实时程序化数据交换的影响将是深远的,值得庆幸的是,AI 的最新进展为上述挑战提供了解决方案。我个人期待着未来的数据能够成为一种体验性商品,而不是简单的商品。

本帖中的观点仅代表我个人,并不代表我的雇主的观点。

为什么数据项目无法产生实际影响:作为分析经理需要关注的 5 个关键因素

原文:towardsdatascience.com/why-data-projects-fail-to-deliver-real-life-impact-5-critical-elements-to-watch-out-for-as-an-46015a82ddfe?source=collection_archive---------2-----------------------#2023-12-14

一份简单的指南,用于理解那些可能对你的工作产生负面影响的宏观因素

Jordan GomesTowards Data Science Jordan Gomes

·

查看 发表在 Towards Data Science ·7 分钟阅读·2023 年 12 月 14 日

--

是否曾经在一个数据项目中深入研究,却发现它毫无进展?这种感觉比你想象的更常见:

  • VentureBeat 报道称,87%的数据科学项目没有投入生产

  • Gartner 在 2018 年预测,到 2022 年 85%的人工智能项目将会产生错误的结果。在 2016 年,他们估计 60%的大数据项目会失败。

两周前我们讨论了如何进行高质量数据分析,但制作高质量分析实际上只是战斗的一半。许多令人印象深刻的工作从未真正实现,最终只是“数据敏锐性的展示”(充其量)。那么,如何跨越质量工作和有影响力工作的差距呢?

第一步是理解游戏规则——并且对决定你的项目是成功还是失败的宏观元素有一个清晰的了解。

影响数据分析成功的宏观元素(作者提供的图片)

PESTEL——但用于分析

如果你曾经与一些咨询人员互动(或者你自己有咨询背景),你可能听说过“PESTEL”这个术语。它代表“政治、经济、社会、技术、环境、法律”。这个框架用于了解影响组织的宏观环境因素,并形成对业务的优势、劣势、机会和威胁的更好视角。

在某种程度上,相同的原则可以应用于评估你的数据项目的潜在成功,但有所不同(框架毕竟是工具,应该根据需要调整,而不是全盘采用)。对于我们的变体,我们有数据可用性、技能水平、时间框架、组织准备情况和政治环境。这些因素就像是你数据项目成功的大局中的拼图块。理解和对齐这些元素就像调整引擎:调得好,你的项目将顺利运行;调得不好,你将经历一段崎岖的旅程。

数据可用性

这是一种同义反复——但对于任何数据项目,你都需要数据。相关数据的可用性和可获取性是基础。如果你发现必要的数据不可用,或证明无法获取,你的项目将面临重大挑战。然而,不要在遇到这一障碍时立即认输——你应该探索其他选项以获取数据或找到一个可行的替代方案(在这个阶段的坚持至关重要——我见过无数项目在这个阶段被放弃,即使有合适的解决方案)。但如果经过非常彻底的调查后你得出结论认为数据确实无法获取且没有合适的替代方案,那么重新考虑项目的可行性绝对是一个合理(甚至是明智)的决定。

示例: 想象一下,你计划进行一项研究,分析一个小众市场中的消费者行为,但是你发现这个细分市场的具体消费者数据并没有被任何现有来源收集。在放弃这个项目之前,你可以探索替代数据来源,如社交媒体趋势、相关市场研究,甚至进行有针对性的调查来收集近似数据。如果所有这些努力都未能产生有用的数据,那么停止项目就是合理的了。

技能组

现在你已经有了数据——你是否有正确的技能来进行调查?这不仅仅是掌握 SQL 或 Python 等技术技能,同样也是具备进行你正在进行的分析所需的特定知识。当项目的要求超出你通常的专业领域时,这变得尤为关键。例如,如果你擅长构建数据管道,但手头的项目集中在复杂的预测上,这种技能上的不匹配可能成为一个重大障碍。根据团队当前技能与他们需要掌握的技能之间的差距,你可能考虑对团队进行技能提升——这在长远来看也可能是非常有益的——前提是它与项目的时间表相符。这是在找到适当平衡的问题:抓住发展机会,同时又要对项目的时间表和优先事项保持现实的看法。

示例: 你管理一个在患者数据分析方面经验丰富的医疗研究团队,现在有一个项目要求他们运用流行病学建模来预测疾病的传播。虽然他们擅长处理患者数据,但流行病学预测的特定要求——一个不同领域的专业——可能会带来重大挑战。

时间框架

时间方面,有两个要素需要理解:

  • 如果你没有留足够的时间来完成一个项目,项目的质量可能会受到严重影响。

  • 过了一段时间,你会遇到收益递减的情况,这时投入更多时间并不一定能够带来同样增加的质量水平。

这个视频(病毒式的蜘蛛侠画作)很好地展示了这一现象。在 10 秒钟和 1 分钟的画作之间质量的飞跃是显著的,仅仅多花 50 秒就有了明显的提高。但是,当将 1 分钟的画作与花费 10 分钟的画作进行比较时,尽管后者无可否认地更好,但是质量的提升幅度却没有那么明显,尽管花费的时间大大增加。

示例:你在一家零售公司工作,该公司希望分析客户购买模式,以优化即将到来的假期季节的库存水平。如果你的数据团队被给予一周时间来进行分析,他们可以提供基本的见解,识别出一般趋势和畅销商品。然而,如果他们得到一个月的时间,分析质量会显著提高,能够更深入地理解客户偏好、区域差异和潜在的库存问题。然而,将时间延长到三个月可能只会提供略微更详细的见解,同时延迟关键决策,并可能错过市场机会。

组织准备度

组织准备度涉及公司如何准备好并愿意充分利用数据见解。这不仅仅是拥有数据或分析;还包括拥有适当的结构和流程来对这些见解采取行动。在一篇之前的文章中,我讨论了使你的研究‘易于理解’以提高见解采用率的重要性。然而,这种促进有时会超出你的控制范围。

示例:假设你发现某个特定的商店表现不佳,主要是由于其位置不理想。你建议将商店搬迁仅几条街道即可显著提升收益。为了证明这一点,你与运营团队合作,在建议的新地点设置一个临时的‘快闪’商店。这个实验运行的时间足够长,以消除任何新奇效应,明确展示了收入增加的潜力。然而,这里就涉及到组织准备度的问题:公司当前在表现不佳的地点签订了五年的租约,有财政补贴,并且在目标区域没有适合的备用空间。

政治环境

每个人都最喜欢的一个话题:在组织内导航政治环境 ❤。这不幸地是数据分析项目成功的关键步骤。你需要确保利益相关者与项目目标一致,还需要明确与项目相关的角色和职责。有时,你可能会遇到团队间的利益冲突或项目归属问题——这些都是对项目风险较高的情况,你需要在实际开始项目之前进行处理(如果你不希望多个团队各自为战,做完全相同的工作)。

示例:你在一个跨国公司中,两个区域团队负责分析新产品发布的市场趋势。然而,由于历史上的竞争以及缺乏明确的领导方向,这些团队运作独立。每个团队使用不同的方法和数据源,导致相互冲突的结论。这种情况不仅在数据方面培养了不信任,而且在执行层面上造成了关于哪些见解值得信任和采纳的困惑。这种不和谐最终可能导致有价值的发现被忽视,突显了在有效利用数据方面政治和谐的关键影响。

结论

我们讨论的关键要素 — 数据、技能、时间、组织准备性和政治 — 是推动任何数据项目成功的关键因素。没有正确的数据,即使是最有技能的团队也无法生成见解。但技能也很重要;它们将数据转化为有意义的分析。时间是你的画布 — 太少会使你的图像不完整,太多则会导致失去焦点。组织准备性是确保你的发现不仅停留在架子上积灰,而是需要可行动的。还有政治 — 是在组织中航行的艺术,确保你的工作见诸天日。

最后,这是关于理解组织内部动态以引导项目向成功方向发展的,即不仅仅是产生见解,而是推动变革。

希望你喜欢阅读这篇文章!你有什么想分享的提示吗?在评论区告诉大家吧!

如果你想读更多我的文章,这里有几篇你可能会喜欢

## 如何建立成功的仪表板

一个建立了几个不成功的人的清单

[towardsdatascience.com ## 建立分析成熟组织(AMO)

几个简单的框架,用于准确确定组织的分析需求以及如何使其更…

[towardsdatascience.com ## 每月生成更多质量见解的方法

如何建立系统以更少的资源生成更多的产出

[towardsdatascience.com

附注:本文已转载至 Analytics Explained,这是一个我在各种分析角色中提炼所学的新闻通讯(从新加坡初创公司到旧金山的大型科技公司),并回答有关分析、增长和职业的问题。

为什么我们还需要神经网络?

原文:towardsdatascience.com/why-do-we-even-have-neural-networks-72410cb9348e

神经网络的替代方案:泰勒级数与傅里叶级数

Egor HowellTowards Data Science Egor Howell

·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 12 月 16 日

--

www.flaticon.com/free-icons/neural-network。" 标题=”神经网络图标” 神经网络图标由 imaginationlol 创建 — Flaticon。

我最近一直在撰写一系列文章,解释现代神经网络背后的关键概念:

Egor Howell

Egor Howell

神经网络

查看列表 9 个故事

神经网络如此强大和受欢迎的一个原因是它们展示了 通用逼近定理 这意味着神经网络可以“学习”任何函数,无论其复杂程度如何。

“函数描述世界。”

一个函数,f(x),接收一些输入,x,并给出输出 y

数学函数如何工作。图示作者提供。

这个函数定义了输入与输出之间的关系。在大多数情况下,我们拥有输入和相应的输出,神经网络的目标是学习或逼近将它们之间映射的函数。

神经网络在 1950 年代和 1960 年代左右被发明。然而,当时还有其他已知的通用逼近器。那么,为什么我们还需要神经网络呢……

泰勒级数

泰勒级数 将一个函数表示为从其在单点的导数值计算出的无限项之和。换句话说,它是一个无限多项式的和,用于近似一个函数。

泰勒级数。方程由作者在 LaTeX 中给出。

上述表达式将一个函数 f(x) 表示为一个无限和,其中 f^n 是在点 a 处的 n-th 导数或阶数,n! 表示 n 的阶乘。

如果你有兴趣了解我们为什么使用泰勒级数,请见 这里。简而言之,它们用于将复杂的函数变得易于处理!

存在一种泰勒级数的简化形式,称为 麦克劳林级数,其中 a = 0

麦克劳林级数。方程由作者在 LaTeX 中给出。

在这种情况下,a_0a_1 等是对应多项式的系数。泰勒级数和麦克劳林级数的目标是找到最优系数值,以近似给定的目标函数。

听起来很熟悉?

我们甚至可以用矩阵表示麦克劳林级数。

麦克劳林级数的矩阵表示。方程由作者在 LaTeX 中给出。

这几乎是一个单层神经网络! a_0 是偏置项,a_1a_n 是权重,xx^n 是我们的特征。

我喜欢把泰勒级数视为(宽松地)多项式回归

在机器学习问题中,我们实际上没有整个函数,而只是数据点的样本。这时,我们会将 x^n 特征作为泰勒特征传入神经网络,通过反向传播来学习系数。

另一个有趣的特性是将泰勒级数与机器学习相关联,即梯度下降。一般梯度下降公式来自将泰勒级数应用于损失函数。有关这一概念的证明,请见 这里。

傅里叶级数

傅里叶级数 与泰勒级数非常相似,但使用的是正弦和余弦波,而不是多项式。它定义为:

任何周期函数都可以分解为正弦波和余弦波的和。

这是一条非常简单的声明,但其含义却非常深远。

关于傅里叶级数的补充视频。

例如,下面显示了***sin(2x)cos(3x)***的函数及其对应的求和:

import plotly.graph_objs as go
import numpy as np

x = np.linspace(0, 3 * np.pi, 500)

y1 = np.sin(2 * x)
y2 = np.cos(3 * x)
y_sum = y1 + y2

trace1 = go.Scatter(x=x, y=y1, mode='lines',name='sine(2x)', line=dict(color='blue'))
trace2 = go.Scatter(x=x, y=y2, mode='lines', name='cos(3x)', line=dict(color='green'))
trace3 = go.Scatter(x=x, y=y_sum, mode='lines', name='sum', line=dict(color='red'))

layout = go.Layout(
    title='Example Sum of Sinusoidal Waves',
    xaxis=dict(title='X'),
    yaxis=dict(title='Y')
)

data = [trace1, trace2, trace3]

fig = go.Figure(data=data, layout=layout)

fig.show()

示例正弦波及其和。图表由作者在 Python 中生成。

***sin(2x)cos(3x)***函数是简单函数,但它们的和(红线)会导致更复杂的模式。这是傅里叶级数的主要思想,即使用多个简单函数来构建复杂的函数。

傅里叶级数的一个有趣结果是通过对不同奇数(阶数)频率和振幅的无限正弦函数(谐波)进行求和来构造方波

奇数正弦波的求和。公式由作者在 LaTeX 中编写。

import plotly.graph_objs as go
import numpy as np

x = np.linspace(0, 3 * np.pi, 1000)
y = np.array([np.sin((2*k + 1) * x) / (2*k + 1) for k in range(100)]).sum(axis=0) * (4 / np.pi)

trace = go.Scatter(x=x, y=y, mode='lines', name='Square Wave', line=dict(color='blue'))

layout = go.Layout(
    title='Square Wave',
    xaxis=dict(title='X'),
    yaxis=dict(title='Y', range=[-1.5, 1.5])
)

data = [trace]

fig = go.Figure(data=data, layout=layout)

fig.show()

使用正弦波创建方波。图表由作者在 Python 中生成。

这个结果令人惊讶,因为我们从光滑的正弦函数生成了一个尖锐的直线图。这展示了傅里叶级数构造任何周期函数的真正能力。

傅里叶级数通常应用于时间序列,以建模复杂的季节性模式。这被称为谐波回归

如前所述,傅里叶级数声明任何周期函数都可以分解为正弦和余弦波的总和。数学上,这可以写作:

傅里叶级数。公式由作者在 LaTeX 中编写。

其中:

  • A_0: 给定周期函数的平均值

  • A_n: 余弦分量的系数

  • B_n: 正弦分量的系数

  • n: 频率的顺序,称为‘谐波’”*

  • P: 函数的周期

同样地,使用泰勒级数,我们的目标是通过傅里叶级数找到A_nB_n系数,这些系数对应于正弦和余弦函数。

那么为什么使用神经网络呢?

泰勒级数和傅里叶级数可以被视为通用函数逼近器,并且它们早于神经网络。那么,为什么我们还需要神经网络呢?

答案并不简单,因为这三种方法之间存在许多复杂性。我在描述泰勒和傅里叶级数的工作原理时较为宽松,否则这篇文章将非常繁琐。

让我们分解一些泰勒级数或傅里叶级数无法替代神经网络的原因。

泰勒级数

泰勒级数的主要问题是它们围绕一个点进行近似。它们是在一个值及其局部区域上估计一个函数。我们想知道整个函数在大范围内是什么样的。这意味着泰勒级数(多项式)在训练集之外无法泛化。

傅里叶级数

傅里叶级数的一个问题是它需要看到要近似的函数。例如,在时间序列中,它用于寻找数据中的复杂季节性模式。但它知道数据是什么样的。神经网络的目标是学习这个函数。

然而,主要的问题在于傅里叶级数的复杂性。系数的数量随着我们尝试估计的函数中的变量数量呈指数增长。然而,对于神经网络来说,情况不一定如此。

假设我们有一个函数f(x),我们可以用 100 个系数很好地近似它。现在假设我们想要近似f(x,y)。我们现在有 100² = 10,000 个系数。对于f(x,y,z),我们有 100³。这个过程持续下去,指数增长。

我在这里描述的是维度诅咒

另一方面,神经网络可以准确建模(一些)这些高维函数,而不需要过多增加输入维度。

无免费午餐定理

重要的是要提到,神经网络并不总是比泰勒级数和傅里叶级数更好。机器学习的美在于它是数学的科学。你需要在拟合模型时进行尝试,以找到最佳模型。添加泰勒或傅里叶特征可能会改善它,但也可能会使其变得更差。目标是找到最佳模型,但这对于每个数据集都不同。

另一个事情!

我有一个免费的通讯,Dishing the Data,每周分享成为更好的数据科学家的技巧。没有“空洞的”或“点击诱饵”,只有来自实践数据科学家的纯粹可操作的见解。

[## Dishing The Data | Egor Howell | Substack

如何成为更好的数据科学家。点击阅读 Dishing The Data,由 Egor Howell 编写,是一个 Substack 出版物,包含……

newsletter.egorhowell.com](https://newsletter.egorhowell.com/?source=post_page-----72410cb9348e--------------------------------)

与我联系!

  • YouTube

  • LinkedIn

  • Twitter

  • GitHub

参考文献和进一步阅读

  • 预测:原则与实践: otexts.com/fpp2/

  • 解释类似概念的优秀视频

  • 关于神经网络与泰勒级数与傅里叶级数的精彩讨论

为什么我们拥有庞大的语言模型而视觉变换器却很小?

原文:towardsdatascience.com/why-do-we-have-huge-language-models-and-small-vision-transformers-5d59ac36c1d6

人工智能 | 计算机视觉 | 视觉变换器

Google ViT-22 为新的大型变换器铺平了道路,并彻底改变了计算机视觉领域

Salvatore RaieliTowards Data Science Salvatore Raieli

·发表于 Towards Data Science ·阅读时间 9 分钟·2023 年 2 月 17 日

--

图片由 Joshua Earle 提供,来源于 unsplash.com

最近几年,我们看到变换器参数数量的增长。然而仔细观察,这些主要是语言模型(LMs),其参数量高达令人惊叹的 540B。那么视觉模型为什么没有类似增长呢?

对于文本模型来说,数据集规模的增加、可扩展的架构以及新的训练方法使得参数数量得以增长。这不仅提高了特定任务(分类等)的性能,而且随着参数数量的增加,我们看到了新兴的能力。

“最先进的自然语言处理(NLP)模型的规模随时间的变化趋势。这些模型的训练所需的浮点运算数量以指数级速度增长。” 来源:这里

此外,大型模型可以作为 迁移学习 和微调的基础,因此对开发性能越来越高的模型充满兴趣。尽管 LMs 在许多任务中取得了成功,但仍有许多其他任务需要能够进行图像分析的模型。

截至 2016 年,transformer是首选架构,而自注意力的使用显示了明显的优势。因此,多个团队训练了能够处理图像的变换器模型(视觉变换器,ViT)。到目前为止,最宽的 ViT 只有 15 B 参数。为什么?

在一项新研究中,谷歌成功训练了一个 22 B 参数的模型,并理解了为何在扩展 ViTs 时存在困难。

总结:

  • 他们解释了为何传统的 ViTs 训练方法在扩展时会产生不稳定性。

  • 如何修改架构以进行扩展,以及模型如何达到最新水平。

  • 如何在扩展模型时提高公平性

什么是视觉变换器?

图片来自维基百科 (source)

变换器当然是排列不变的,但它们不能处理网格结构的数据(只能处理序列)。因此,为了将变换器与图像一起使用,我们必须找到将它们转换为序列的方法。怎么做?

第一步是将图像转换为一系列的图块(图像被分成一系列称为图块的碎片)。这些图块基本上是我们的令牌(就像经典变换器中的单词)。这些图像随后被展平并转换为低维嵌入(这保留了信息但减少了维度)。此外,和原始变换器一样,我们使用位置编码,以便模型知道图块在图像中的位置。

描述 ViT 的原始文章 (arXiv prepint)

模型随后在监督学习中进行训练(我们有图像标签的大型数据集),然后可以用于下游任务。

[## 视觉变换器的视觉旅程

一些最大模型如何看待世界

pub.towardsai.net

为什么难以扩展它们以及如何解决?

在 ViTs 之前,卷积网络是计算机视觉任务的标准。在“2020 年代的 ConvNet”中,作者指出这个问题仍然存在且很重要。

另一方面,我们还未能扩展 ViTs。由于在变换器中,模型的扩展也会导致无法预先想象的行为的出现,这是一个严重的问题。

作者指出,超过 80 亿参数时,不稳定性表现为几千步后出现发散的训练损失。这是由于“注意力 logits 中的极大值导致几乎是 one-hot 的注意力权重,且熵接近零。” 为解决此问题,作者在点积前为 Queries 和 Keys 添加了层归一化。

图中展示了这种措施如何改善训练。

(来源: 这里)

第二种措施是修改架构。 在经典的 transformer 中,自注意力输出后跟随一个多层感知机(MLP)。而在这里,自注意力块与 MLP 并行。这种操作不会降低性能,同时将训练速度提高了 15%(如 PaLM 这类其他大型谷歌模型所示,该操作基本上是将矩阵乘法合并为单个操作)。

此外,注意力投影中移除了偏置项(这也减少了训练时间而不降低性能)。

图中展示了这些措施之后的新块:

(来源: 这里)

表格比较了谷歌的模型(ViT-22)与之前报告的最大 ViT 模型,ViT-G 和 ViT-e。

(来源: 这里)

训练也已得到优化。谷歌使用了 JAX(谷歌在某段时间内更加专注于 JAX而非 TensorFlow)。他们还使用了一些技巧(异步并行线性操作、参数分片)以确保模型优化适用于TPU。

作者使用了大约 40 亿张图像的数据集,这些图像半自动标注了 30,000 个类别。温馨提示,在 ViT 中,图像被划分为若干部分(称为补丁),然后与位置编码一起转化为序列。每张图像(224 x 224)被划分为 14 x 14 个补丁。因此,一张图像最终由 256 个标记表示。

扩展 ViT 是否值得?

模型训练完成后,他们在ImageNet(100 万图像和 1000 类)上进行了测试,以检验其分类能力。作者表明,冻结模型(即无需微调)的性能与其他模型相当。

(来源: 这里)

此外,该模型已在不同图像分辨率的另一数据集上进行了测试。ViT-22B 在输入尺寸较小时,显著提高了准确性。

(来源:这里)

另一方面,大型模型的一个最常见的用途是迁移学习。毕竟,人们通常使用小数据集,并利用大型模型进行微调,用于与训练任务不同的任务。正如作者所说:

对于密集预测,迁移学习至关重要,特别是因为获得像素级标签可能很昂贵。在本节中,我们研究了 ViT-22B 模型(使用图像级分类目标进行训练)在语义分割和单目深度估计任务中捕捉的几何和空间信息的质量。 (来源:这里)

作者使用了三个语义分割基准数据集(ADEK20k、Pascal Context、Pascal VOC)对模型进行了测试。不仅如此,他们还使用了有限的数据量进行迁移测试。

“在 ADE20k 上进行少样本语义分割,仅使用了训练集的一部分。我们报告了验证集上的语义分割平均 IoU” (来源:这里)

当数据较少时,ViT-22 表现最佳,这非常有用,因为获取图像及其分割掩码通常很昂贵,因此比其他模型需要的样本更少。

此外,该模型在 Waymo Open 数据集上显示出了卓越的单目深度估计能力。

使用不同解码器对 Waymo Open 数据集中的冻结 ViT 特征进行单目深度估计。 (来源:这里)

此外,通过重新利用模型(但保留预训练的 ViT-22 作为组件),可以将其用于视频分类。这展示了模型在各种可能任务中的灵活性。

此外,作者展示了微调能够提升性能:

视频分类结果。我们通过冻结骨干网络来评估 ViT-22B 的表示,并训练一个小型变换器来汇聚冻结的逐帧表示。ViT-22B 的表现超越了包含 40 亿参数的最大前视图骨干网络 ViT-e。 (来源:这里)

这个模型的公平性如何?

人工智能模型容易受到意外偏见的影响。这些偏见许多存在于训练数据集中,模型可以放大这些偏见,学习虚假的关联和错误的差异。由于预训练模型随后被用于后续任务,错误会被带到新的任务中。

作者认为,扩大模型规模可以帮助缓解这些偏见,并决定通过使用人口统计平等(DP)作为公平性的衡量标准来进行测试。

作者解释了他们的方法:

我们使用 CelebA(Liu 等,2015)中的二元性别作为敏感属性,而目标是“吸引人”或“微笑”。我们强调,这种实验仅用于验证技术声明,绝不应被解释为对这种视觉相关任务的支持。我们选择后者属性,因为它们如图 15 所示展现了与性别相关的偏差。(来源:here)

“模型中的 DP 通常反映了数据中的 DP,除非采取偏见缓解措施。在此图中,二元性别是敏感属性,线性头被训练用于预测 CelebA 中的其他属性,使用预训练特征。”(来源:here)

如文献所述,扩展模型提供了更有利的权衡(“性能随着规模的增加而改善,前提是任何规定的偏见约束水平”)。其次,所有子组都受益于这种改善,并且扩展减少了子组之间的性能差距。

“上图:在每个 DP 水平上,ViT 变体去偏倚后的准确率(ACC)。中图:去偏倚前 CelebA 中每个子组的准确率。下图:y 轴是两个子组(女性和男性)之间的性能绝对差异。与较小的 ViT 架构相比,ViT-22B 提供了更公平的性能。”(来源:here)

模型看到什么?

如前所述,计算机视觉模型主要关注纹理,而人类则更多依赖于形状。

人类的形状偏差为 96% / 纹理偏差为 4%,而 ViT-22B-384 实现了前所未见的 87%形状偏差 / 13%纹理偏差。(来源:here)

这个结果非常有趣,因为大多数模型的形状偏差为 20-30% 和纹理偏差为 70-80%(卷积网络也如此)。这种偏差也是为什么通过改变图像的纹理,即使形状可识别,模型也可能被欺骗,错误解读和标记图像的原因之一。

“形状偏差:许多视觉模型具有低形状/高纹理偏差,而在 ImageNet 上微调的 ViT-22B(如模型名称后的括号所示,红色、绿色、蓝色训练于 40 亿张图像,除非仅在 ImageNet 上训练)拥有迄今为止记录的最高形状偏差,使其更接近于人类的形状偏差。”(来源:here)

此外,理解模型所见的另一种方法是获取显著性图(基于梯度的特征归因方法)。

模型冷却前后的显著性(来源:here)

结论

Google 推出了一款比之前的 ViT 模型大 5 倍以上的模型。

我们展示了 ViT-22B,这是一种当前最大规模的视觉变换器模型,具有 220 亿个参数。我们展示了通过对原始架构进行小但关键的修改,我们可以实现优秀的硬件利用率和训练稳定性,从而获得一个在多个基准测试中推动 SOTA 的模型。(来源:这里)

除了模型的规模和基准测试结果之外,这一模型也是更大模型的起点。事实上,在此之前,成功地扩展 ViT 模型很困难,因为训练期间的不稳定性。作者们展示了通过修改架构可以解决这些问题。

大模型可以用作不同任务的预训练支架(计算机视觉模型可以用于许多现实世界的任务)。此外,意外行为出现(这些行为在小模型中不存在,且无法通过缩放法则预测)。而且,正如所示,这些模型随后可以整合到多模态模型中(并且可能影响其中的突现行为)。

此外,ViT-22B 显示了在公平性方面的改进。这一模型也更加稳健,更符合人类视觉(不那么依赖纹理,更依赖形状)。

我们很可能很快会看到更大的 ViTs(单独使用或作为多模态模型的组成部分)。你对此有何看法?

如果你觉得这很有趣:

你可以查看我的其他文章,也可以订阅以便在我发布文章时获得通知,你还可以通过LinkedIn与我联系

这是我 GitHub 存储库的链接,我计划在这里收集与机器学习、人工智能以及更多相关的代码和资源。

[## GitHub - SalvatoreRa/tutorial: 机器学习、人工智能、数据科学的教程…

关于机器学习、人工智能、数据科学的教程,包含数学解释和可重用的代码(使用 Python…)

github.com](https://github.com/SalvatoreRa/tutorial?source=post_page-----5d59ac36c1d6--------------------------------)

或者你可能对我最近的一篇文章感兴趣:

[## 微软 BioGPT:迈向生命科学的 ChatGPT?

BioGPT 在不同的生物医学 NLP 任务中达到了 SOTA。

SparseGPT:参数更少更好?

如何摆脱 1000 亿参数,并在一个 GPU 上愉快地进行推理

关于 ChatGPT 你需要知道的一切

已知的内容、最新消息、影响和变化,都在一篇文章中

RazzAIe 奖 2022:年度最差 AI 是哪些?

年度最差模型是什么?出了什么问题?

年度最差模型有哪些?出了什么问题?

为什么你需要使用 SQL 分组集来汇总数据?

原文:towardsdatascience.com/why-do-you-need-to-use-sql-grouping-sets-for-aggregating-data-3d172ff5c088

效率、可读性和可扩展性

Soner YıldırımTowards Data Science Soner Yıldırım

·发表于 Towards Data Science ·阅读时间 4 分钟·2023 年 4 月 20 日

--

由 Helena Lopes 提供的照片,来源于 Unsplash

尽管 SQL 被称为查询语言,但它不仅可以查询数据库,还能进行高效的数据分析和处理。SQL 被数据科学界广泛接受也就不足为奇了。

在本文中,我们将了解一个非常实用的 SQL 特性,它允许我们编写更简洁、更高效的查询。这个希望早点知道的特性就是 GROUPING SETS,它可以看作是 GROUP BY 函数的扩展。

我们将学习它们之间的区别以及使用 GROUPING SETS 相比于 GROUP BY 函数的优势,但首先我们需要一个数据集来进行操作。

我从 Kaggle 上提供的 墨尔本住房数据集 创建了一个 SQL 表,该数据集拥有公共领域许可证。表的前 5 行如下所示:

(image by author)

GROUP BY 函数

我们可以使用该函数来计算每组的聚合值或列中、多个列中的不同值。例如,以下查询返回每种列表类型的平均价格。

SELECT 
   type,
   AVG(price) AS avg_price
FROM melb
GROUP BY type

这个查询的输出是:

(image by author)

多重分组

假设你想查看北部地区每个区域的平均价格,这可以通过使用 GROUP BY 函数实现:

SELECT 
   regionname,
   AVG(price) AS avg_price
FROM melb
WHERE regionname LIKE 'Northern%'
GROUP BY regionname

输出:

(image by author)

考虑一个情况,你想在同一表格中查看这两个地区不同房屋类型的平均价格。你可以通过编写两个分组查询并使用UNION ALL来结合结果来实现这一点。

SELECT 
   regionname,
   'all' AS type,
   AVG(price) AS average_price
FROM melb
WHERE regionname LIKE 'Eastern%'
GROUP BY regionname
UNION ALL
SELECT 
   regionname,
   type,
   AVG(price) AS average_price
FROM melb
WHERE regionname LIKE 'Eastern%'
GROUP BY regionname, type
ORDER BY regionname, type

查询的作用是先计算每个地区的平均价格。然后,在一个单独的查询中,它按地区名称和类型对行进行分组,并计算每个组的平均价格。联合操作将这两个查询的结果结合起来。

由于第一个查询没有类型列,我们手动创建了一个值为“all”的类型列。最后,结合结果按地区名称和类型排序。

这个查询的输出:

(作者提供的图片)

每个地区的第一行显示该地区的平均值,接下来的行显示不同房屋类型的平均价格。

我们不得不写两个独立的查询,因为在GROUP BY语句中不能有不同的查询,除非我们使用GROUPING SETS

GROUPING SETS

让我们用GROUPING SETS重写之前的查询。

SELECT 
   regionname,
   type,
   AVG(price) as average_price
FROM melb
WHERE regionname LIKE 'Eastern%'
GROUP BY 
   GROUPING SETS (
     (regionname),
     (regionname, type)
   )
ORDER BY regionname, type

输出:

(作者提供的图片)

输出结果相同,只是类型列中的空值可以很容易地替换为“all”。

使用GROUPING SETS有两个主要优点:

  • 它更简洁、更直观,使代码更容易调试和管理。

  • 它比写独立查询并结合结果更高效,因为 SQL 会扫描每个查询的表。

最后的想法

我们常常忽视查询的可读性和效率。只要查询返回了所需的数据,我们就会感到满意。

效率是我们始终需要牢记的。写出不良查询的影响可能在查询小型数据库时可以容忍。然而,当数据量变大时,不良查询可能会导致严重的性能问题。为了使 ETL 过程可扩展且易于管理,我们需要采用最佳实践。GROUPING SETS就是这些最佳实践之一。

你可以成为一个 Medium 会员 以解锁我所有的写作内容,以及 Medium 的其他内容。如果你已经是会员,请不要忘记 订阅 以便在我发布新文章时收到邮件通知。

感谢阅读。如果你有任何反馈,请告知我。

为什么假设检验应该从《哈姆雷特》中汲取灵感

原文:towardsdatascience.com/why-hypothesis-testing-should-take-a-cue-from-hamlet-1e714733cec3

统计思维

模拟还是不模拟,这是个问题

Cassie KozyrkovTowards Data Science Cassie Kozyrkov

·发表于Towards Data Science ·5 分钟阅读·2023 年 6 月 26 日

--

如果你是一个科学家或数据专业人士,很可能你的假设检验程序缺少一个至关重要的步骤,这在你的常规课程中悲剧般——还是悲喜剧般?——被遗漏了。不要担心,在这篇博客文章中,我会向你展示缺失的部分,以及为什么你会在演员的剧本中找到解决方案。

《哈姆雷特》由 Kehinde Wiley 风格生成,由作者使用Midjourney创建。

(注意:本文中的链接会带你到同一作者的解释文章。)

第一幕,第一场

场景开始于你得意洋洋地获得预算以去收集实际数据。也许一切都是数字化的;你正准备告诉你的工程团队应该记录哪些变量或运行哪些在线实验。或者你可能要走向现实世界,设置一些传感器、准备一些移液管,或者做其他任何获取数据的工作。(对从现实世界中获取测量的实际操作感到好奇?查看我关于取样树木的文章。)

不要急!如果你不知道自己在做什么怎么办?搞砸数据收集过程的实际部分是非常尴尬的,更不用说这是对你团队宝贵时间的巨大浪费了。即使一切都是数字化的,你也确实不希望带着尾巴回到你的工程团队那里请求重做。

但你怎么能确保你以正确的方式记录了正确的内容呢?有没有技巧?幸运的是,有!而这个技巧在事后看来是如此明显……这也许就是为什么你的教授忘记教给你的原因。

技巧是借鉴戏剧演员的经验!

戏剧舞台上的酒杯,风格类似于 Kehinde Wiley,由作者使用 Midjourney 生成。

如果演员在首演前对表现不佳感到紧张,他们会做什么?(不是喝酒。)

他们会排练!

好吧,你也可以进行排练。在收集任何数据之前,设立一个除观众之外的彩排。嗯,观众?我是说,除了真实世界的数据

彩排

在数据科学中,彩排涉及制作一个虚假的但可信的数据集。我们称之为模拟

如果你不熟悉模拟虚假的但可信的数据的概念,可以查看这篇博客文章,我会切换到演示模式而不是讲解模式。你将在那里看到基于代码和电子表格的示例。形成了模拟的心理图像后,可以继续阅读这里的内容。

当你模拟你的彩排数据时,确保设计一个你可以控制的清晰故事。尝试设置模拟参数,建立一个你希望采取某种行动的小玩具世界,然后生成这些数据并尝试分析它。确保在分析结束时你能恢复正确的决策。如果不能,那对你的统计计划来说是一个很糟糕的信号!

如果你知道正确的结论是什么(在一个创建的规则的世界里你总是知道),而你的方法没有给出这个结论,你可能使用了一个不好的方法,或者数据不足。尽早获取这些警告信号总是更好。

也许更重要的是,你经常会发现你希望自己在最初就设置好数据。分析假数据时,你会想,“要是我有这一额外的列,一切都会更好……”

好吧,现在是弄清楚这一点并扼杀GIGO的最佳时机,而不是在你收集了真实数据之后。太昂贵且耗时!

不要浪费你的机会

即使你的数据集是完美的,你提出的方法可能也不是最合适的。不幸的是,除非你在做一些相当复杂的事情,否则你应该知道,你只能使用真实的测试数据集一次。所以只有一次机会——你不能像训练机器学习模型那样尝试不同的方法。统计推断是一项残酷的认识论事业,它对你的感受毫不在意:只有一次机会。不允许数据重复使用。

重用测试数据是你能犯的最大罪恶之一,而你的知识不足的同行经常犯这种错误,这并不意味着它是无害的。

不要在你珍贵的最终数据集中胡乱选择方法。你只有一次机会,不要浪费。

如果你想尝试不同的方法以查看它们是否适合你的测试数据,你需要一个具有相同结构的单独数据集。如果你已经有了大量数据,你可以将其拆分。如果你还没有数据,你需要模拟一些虚假的数据以便规划你的方法论。不要在你珍贵的最终数据集中胡乱选择方法。你只有一次机会,不要浪费。

这就是为什么在开始获取数据之前使用模拟是一个如此有用的技巧,完全抄袭自彩排概念。

是否进行模拟,这就是问题所在:

是否在心中忍受更高尚

骄人的命运的弹弓和箭矢,

还是抵抗一片麻烦的海洋

通过对抗来结束它们。

感谢所有的手指,Midjourney。❤

感谢阅读!

如果你想表示感谢,我很感激分享和转发。如果你想阅读更多我的写作,文章中的大多数链接会带你到我的其他随笔。

怎么样,来一门课程?

如果你在这里玩得开心,并且正在寻找一个有趣的、以领导力为导向的课程,旨在让 AI 初学者和专家都感到愉快,这是我为你准备的小礼物:

课程链接:bit.ly/funaicourse

更愿意磨练你的决策技能,而不是建立你的 AI 实力?你可以通过这个链接学习我的免费课程:

[## 你生活的方向盘——决策智能视频教程 | LinkedIn Learning…

决策能力是你可以学习的最宝贵的技能。你的人生归结为两件事:你的生活质量和你的…

bit.ly](https://bit.ly/decisioncourse?source=post_page-----1e714733cec3--------------------------------)

附言:你曾经尝试过在 Medium 上多次点击鼓掌按钮,看看会发生什么吗? ❤️

喜欢作者吗?与 Cassie Kozyrkov 联系

成为朋友吧!你可以在 Twitter、YouTube、Substack 和 LinkedIn 找到我。想让我在你的活动中发言?请使用 这个表单 与我联系。

[## 加入 Medium

阅读 Cassie Kozyrkov 的每一篇故事(以及 Medium 上成千上万其他作者的作品)。你的会员费直接支持…

kozyrkov.medium.com](https://kozyrkov.medium.com/membership?source=post_page-----1e714733cec3--------------------------------)

我为什么签署了“暂停大型 AI 实验”请愿书

原文:towardsdatascience.com/why-i-signed-the-pause-giant-ai-experiments-petition-e9711f672d18

Rafe Brena, Ph.D.Towards Data Science Rafe Brena, Ph.D.

·发表于Towards Data Science ·阅读时间 8 分钟·2023 年 4 月 1 日

--

“精神”是对的;但“身体”存在许多缺陷

照片由Álvaro Serrano拍摄,来源于Unsplash

上周二,我收到来自未来生命研究所的一封电子邮件,邀请我签署一份暂停大型 AI 实验的请愿书。当我签署这封信时,组织者要求我们在信件发布前保持保密。当时,我没想到它会引起如此多的新闻、评论、文章等。

在文章发布后不久,我接到了一些新闻媒体的联系,一家来自阿根廷,另一家来自墨西哥,邀请我参加他们的直播节目并发表我的观点。

就在那时,我意识到 FLI 的信确实是一个具有高影响力的倡议。

尽管最后我决定签署它,但我也发现信中有许多我不同意的陈述,因此在这篇文章中,我想澄清记录,阐述支持和反对这封信的理由。我鼓励你也阅读这封信,它并不长。

为什么现在?

重要的是要意识到,公开信中的紧迫感并不是关于人工智能一般问题,而是关于最近开发和发布的被称为“生成性 AI”或简称 GenAI 的技术。

除非你一直躲在石头下,否则你应该听说过 ChatGPT(去年 11 月发布,天哪,感觉已经很久了),它是 GenAI 最显著的例子,但还有很多其他的,比如DALL-E、Claude、Stable Diffusion、Poe、You.com、Copy.ai等等。AI 能力也被融入到许多产品中,如 Notion、Microsoft Office、Google Workplace 套件、GitHub 等。

我们许多人认为 GenAI 是一个真正的颠覆者,而其他人则称其为“一时的潮流”。比尔·盖茨写道他在自己漫长的生命中见过两次变革性技术,GenAI 是第二次(第一次是当他看到图形用户界面时)。

但这并不是一帆风顺的道路。

除了那些臭名昭著的“邪恶人格”劫持聊天机器人的案例,我们还见过很多事实错误甚至是虚构的事实——被称为“幻觉”——这些误导性的内容让人类感到困惑,因为这些文本看起来像是极其自信地写成的;我们人类在不确定自己所说内容时往往会表现出不安,但当然,机器不会感到不安(实际上也没有自信)。

像 OpenAI 这样的公司试图给人一种错误被修正的印象,但一些专家认为错误和幻觉是技术的固有部分,而不是小细节。我提出了一种减少错误的方法,并非假装完全消除它们。

尽管缺陷远未得到纠正,尤其是 OpenAI(背后有微软)与谷歌(及其关联公司 DeepMind 和 Anthropic)之间的竞争却在全速进行。产品以令人窒息的速度发布,只为了市场份额的优势,而不真正担心对社会的后果。

我们——公民——只能独自应对 GenAI 在我们生活中的引入,面对各种虚假信息、偏见、假新闻、假音频,甚至假视频的可能性。

政府对此无动于衷。国际组织对此无动于衷。

我理解图像生成的文本看起来可能不如医学诊断或药物治疗那样重要,但仍然有重要的后果。我们首次体会到误信息(被像 Twitter 这样的科技平台利用)在美国 2016 年和 2020 年选举中所扮演的角色,现在我们在全球范围内都在遭受社会的两极化。但几年前的 Twitter 机器人与即将到来的 GenAI 相比简直微不足道,如果我们对其采用无动于衷的话。

现在让我们回顾一下这封信正确的部分,然后再看看在我看来它的错误之处。

公开信中的正确之处

  1. GenAI 系统是“强大的数字思维,没有人——甚至它们的创造者——可以理解、预测或可靠地控制。” 他们是“不可预测的黑箱模型,具有新兴能力。” 这解释了为什么它们本质上是危险的系统。例如,“新兴能力”意味着当 GenAI 系统变得足够大时,新行为会突然出现——比如幻觉。新兴行为不是经过工程设计或编程的;它们只是突然出现。

  2. AI 实验室[正]陷入失控的竞赛,开发和部署越来越强大的数字思维。” 这场不停的竞赛可以从公司市场份额的主导地位来理解,但社会后果又如何呢?他们说他们关心这些问题,但这种无休止的节奏却显示了其他的东西。

  3. 我们不应该让这种鲁莽的竞赛继续下去,而应“制定和实施一套共享的高级 AI 设计和开发安全协议,这些协议应由独立的外部专家进行严格的审计和监督。”

  4. 另一个好的观点是不试图完全停止 AI 研究或创新:“这并不意味着对 AI 发展的总体暂停,只是从危险的竞赛中后退一步,以避免更大、更不可预测的黑箱模型及其新兴能力。” 此外,提出了技术努力的重新定位:“AI 研究和开发应重新聚焦于使今天强大、最先进的系统更加准确、安全、可解释、透明、稳健、对齐、值得信赖和忠诚。”

  5. 最后,提出了将政策制定作为解决方案的重点:“AI 开发者必须与政策制定者合作,以显著加快强有力的 AI 治理系统的发展。这些系统至少应包括:致力于 AI 的新和有能力的监管机构;对高度能力的 AI 系统和大量计算能力的监督和追踪;用于区分真实与合成以及追踪模型泄漏的来源和水印系统;一个稳健的审计和认证生态系统;对 AI 造成的伤害的责任;对技术 AI 安全研究的稳健公共资金;以及应对 AI 所引起的剧烈经济和政治冲击(尤其是对民主的影响)的资源充足的机构。”

这封信中的错误

我认为信中的大部分问题在开头没有说对;后来情况大有改善。我清楚地感觉到信的开头和结尾部分是由不同的人写的(我不怀疑这两部分是由机器人写的)。让我们深入探讨具体内容:

  1. 参考文献权威性不够。口头声明不是客观证据。即使是Bubeck 等人的参考文献也不是真正的科学论文,因为它甚至没有经过审稿!你知道,发表在著名期刊上的论文都会经过匿名评审的过程。我每年审稿的数量超过十篇。如果 Bubeck 的论文被送到经过审稿的期刊,肯定不会被接受,因为它使用了主观语言(比如“人工通用智能的火花”)。

  2. 信中的一些说法完全荒谬:它以“具有与人类竞争的智能的 AI 系统”开始,但正如我在之前的帖子中解释的那样,目前的 AI 系统根本不具备与人类竞争的能力,大多数人类与生成 AI 的比较都是误导性的。支持机器竞争力的参考资料也是虚假的,正如我在前一点中解释的那样。

  3. 信中暗示了人工通用智能(AGI)的说法,如“当代 AI 系统现在在一般任务上变得与人类竞争”,但我认为 AGI 是一个非常遥远的未来,甚至不认为 GPT-4 是通向 AGI 的实质性步骤。

  4. 对就业市场的危险描述不准确:“我们是否应该自动化所有工作,包括那些令人满足的工作?”拜托,AI 并不会取代大多数工作,但它接管某些工作的方式(比如从数千张图片中提取图形设计能力,却没有给人类作者任何经济补偿)可以通过征税大科技公司和支持图形设计师社区来解决,而不是通过暂停。

  5. 对不起,但信中几乎每个问题的表述都很糟糕:“我们是否应该发展非人类思想,这些思想可能最终会超越、智胜、淘汰并取代我们?”这是一个“人类与机器”的场景,这不仅荒谬,而且助长了对 AI 系统的错误宣传,正如Arvind Narayanan (@random_walker) 在推特上指出的那样。像终结者那样的场景并不是真正的危险。

  6. 最后,针对信中毫无意义的问题,我们来看看这个:“我们是否应该冒失去对文明控制的风险?”这在很多方面都是错的,难以评论。首先,我们现在有控制我们的文明吗?请告诉我除了富人和国家元首之外,还有谁控制我们的文明。然后,“我们”指的是什么?人类?如果是这样,我们回到了人类与机器的对立思维,这是基本错误的。真正的危险是一些人类利用 AI 工具来支配其他人类

  7. 提议的“补救措施”(暂停开发比 GPT-4 更强大的大语言模型)既不现实也不恰当。这不现实,因为它针对的是主要受大科技公司控制的 AI 实验室,这些公司有特定的财务利益——其中之一就是增加市场份额。你认为他们会做 FoL 研究所建议的事,还是做他们老板希望的事?你说得对。这也不恰当,因为暂停措施无法解决已经发生的从人类作者那里抢劫或人类行为者利用工具传播虚假信息所造成的损害,这些工具不需要比 GPT-4 更强大。

  8. 最后,一些签署这封信的人,特别是埃隆·马斯克,不能被视为 AI 伦理行为的典范:马斯克通过将“完全自动驾驶”称为特斯拉的能力来误导特斯拉客户,而这些能力不仅未能符合由汽车工程师学会提出的 5 级标准,也未能符合 4 级标准,甚至勉强符合 3 级标准。不仅如此,特斯拉在确保安全之前就将潜在致命的机器推向公众,而且特斯拉的自动驾驶模式实际上造成了人员伤亡。那么,埃隆·马斯克有什么道德权威要求“安全、可解释、透明、可靠、对齐、值得信赖且忠诚”的 AI 系统,而他自己公司里并未付诸实践?

那么,我到底为什么签署了这封信?

尽管这封信有很多错误,我为什么决定签署?

我并不是唯一一个签署这封信并批评它的人。例如,@GaryMarcus 也表示,纽约时报报道了他的观点:

“这封信不完美,但精神正合适。”

这是一种表示需要采取行动的方式,信件可以被视为第一次尝试这样做。这一点我可以同意。

但如果你想对这个话题有更清晰的理解,可以阅读例如尤瓦尔·赫拉利在《纽约时报》的专栏。除了像“起初是言语”这样的过于宏大的措辞外,我喜欢他对终结者式情景的批评以及他对真正危险的看法:

… 仅仅通过掌握语言,人工智能就能拥有控制我们在一个类似矩阵的幻觉世界中存在的一切,而无需开枪或植入任何芯片。如果需要开枪,人工智能可以通过讲述正确的故事让人类扣动扳机。

为什么我作为数据科学家要学习 JavaScript

原文:towardsdatascience.com/why-im-learning-javascript-as-a-data-scientist-e2b87bcdac03

以及为什么感觉我在转向黑暗面

Matt ChapmanTowards Data Science Matt Chapman

·发布于 Towards Data Science ·6 分钟阅读·2023 年 11 月 4 日

--

这有点像我的 JavaScript 代码。图片由 Towfiqu barbhuiya 在 Unsplash 提供

现在是 2023 年,Python 的公众热度无法再高了。

我们已经有 Python 脚本 在火星上运行,以及 pandas DataFrames 随意帮助 解决 NHS 危机。

但如果我告诉你 Python 并不是数据科学中唯一有用的语言呢?

(我也不是在说 R 或 Julia)。

在 Python 原教旨主义者拿起火把和叉子之前,请听我说。

在过去几个月里,我一直在探索 JavaScript 的魔力,在这篇文章中,我将给出两个理由,说明我为什么要投资 JavaScript,以及为什么我觉得自己在使用它时像是在与黑暗面调情。

原因 #1: 我想用 D3.js 构建酷炫的可视化

你听说过 D3.js 吗?

这是一种用于构建定制数据可视化的 JavaScript 库。当我说“定制”时,我指的是 花里胡哨的,我可能会开始翩翩起舞 的可视化。

我最喜欢的使用场景是 scrollytelling

Scrollytelling,一个将“滚动”和“讲故事”结合起来的术语,是一种动态讲述多媒体故事的方式,这些故事随着你的滚动而展开—— G2

使用像 D3.js 和 Scrollama.js 这样的库,你可以在用户滚动网页时让数据/图表动起来。

这是一个我使用 Google 的 Covid-19 社区流动报告 公共数据构建的例子:

在视觉上,我认为滚动条是一种了不起的叙事媒介。我喜欢能够将读者的注意力集中在可视化的特定部分,并将他们沉浸在我讲述的故事中的想法。这肯定比我那令人厌烦的橙色和绿色 matplotlib 条形图要好得多。

D3.js 的附加好处是你可以构建可以直接部署到网站上的可视化。如果你使用像 matplotlib/ggplot2 这样的包或像 Power BI/Tableau 这样的软件来构建可视化,你就依赖于使用其他工具将你的可视化放到线上。这并不是坏事(D3.js 的学习曲线比这些工具陡峭得多),但它确实限制了你对可视化的操作。

D3 可视化没有这个问题;它们是完全可定制的,可以以你喜欢的方式嵌入网页。例如,我上面的滚动条中的图表是通过一个简单的 figure HTML 标签 (id="myPlot") 调用并嵌入到常规网页中的。

图片由作者提供

如果你有兴趣学习如何构建像这样的滚动叙事作品,我建议你查看 freeCodeCamp.org 的免费 D3.js 课程 和 Russell Goldenberg 的 Scrollama.js 仓库。为了获得灵感,我还强烈推荐查看 这个 由 Heshan Eissa 和 Lindsey Poulter 制作的不可思议的 Ben 和 Jerry 的主题滚动条。

理由 #2:我想成为一名“全栈数据科学家”

图片由作者提供

在数据科学领域工作的一件奇怪的事情是,你经常是机器中的一个小齿轮。你在组织中扮演着重要的角色,但这是一个高度专业化的角色,你非常依赖其他团队来使你的工作变得“可操作”。

我什么意思呢?你可能需要:

1. 数据工程师为你获取原始数据,

2. ML 工程师将你的模型投入生产,并

3. 软件工程师将这些模型的输出嵌入到面向客户的产品中。

这些依赖关系并没有错,但它们可能会创建令人烦恼的瓶颈,因此,许多公司已经开始招聘“全栈数据科学家”。

你问那是什么?这是 Shopify 工程团队的观点:

[…] 全栈数据科学家的范围涵盖从头到尾的数据科学项目,包括:

  • 发现和分析:你如何从多个不同来源收集、研究和解释数据。这个阶段包括识别业务问题。

  • 获取:将数据从各种来源移动到你的数据仓库中。

  • 数据建模:使用批处理、流处理和机器学习工具转换数据的过程。

我喜欢这种思维方式,但我会批评这个定义还不够深入。根据我的经验,这听起来只是一个普通的数据科学家角色!这让我想知道,如果全栈数据科学家应该这样做,那么非全栈的数据科学家应该做什么。

不过,抛开具体定义,我喜欢作为数据科学家发展更广泛技能的精神(超越数据分析和机器学习等基本技能)。而且,或许有争议的是,我认为学习 JavaScript 是实现这一目标的绝佳方式。

为什么?因为(1)学习任何新的编程语言都会让你成为更好的程序员,(2)特别是学习 JavaScript 使你能够接触到一些作为数据科学家通常不会看到的技术栈部分。我说的是用户界面、网站、与某些 API 的交互,以及所有这些内容。通过学习 JavaScript,我积极投资于数据科学工作流上下游的技术栈部分。拥有这些知识对于理解我的工作如何融入更大的图景以及允许我构建自己的全栈项目(如theSQLgym)是无价的。

我是不是要走向黑暗面了?

作者提供的图像

当我第一次告诉一个数据科学家朋友我在学习 JavaScript 时,他们无法理解。

完全不。

他们问:“为什么要学习 JavaScript,而不是把时间花在学习更直接与数据科学相关的技能上,比如 Cypher 或计算机视觉?”“或者为什么不花时间提高普通的 Python 技能呢?”

说实话,我实际上不知道这个问题的完整答案。

我想学习这些东西,但我也想推动自己学习新事物,并在技术领域成为一个更全面的“万事通”。从长远来看,我认为这会让我成为一个更出色的数据科学家,但如果我说这是唯一的动机,那我就是在说谎。

我也受到智力好奇心和建立酷炫东西的愿望驱动。建造者就是要建造,对吧?

学习 JavaScript 缺乏与数据科学的即时相关性,使其感觉有点像是转向黑暗面/脱离数据科学的经典范畴。但目前,我仍抱有希望。我觉得学习 JavaScript 会有非常宝贵的收获,值得去追求。

你怎么看?

还有一件事—

我创建了一个免费的通讯,叫做AI in Five,每周分享关于最新 AI 新闻、编码技巧和数据科学家/分析师的职业故事的 5 个要点。没有炒作,没有“数据是新的石油”的废话,也没有来自 Elon 的推文(或者我现在应该说‘x-es’?)——只有实用的技巧和见解,帮助你在职业生涯中发展。

点击这里订阅,如果这对你有吸引力!谢谢阅读。

[## 五分钟了解 AI | Matt Chapman | Substack

最新的新闻、职业故事和来自数据科学与人工智能领域的编程技巧,概述为 5 个要点……

aiinfive.substack.com](https://aiinfive.substack.com/?source=post_page-----e2b87bcdac03--------------------------------)

为什么特征缩放在机器学习中很重要?讨论 6 种特征缩放技术

原文:towardsdatascience.com/why-is-feature-scaling-important-in-machine-learning-discussing-6-feature-scaling-techniques-2773bda5be30

标准化、归一化、鲁棒缩放、均值归一化、最大绝对缩放和向量单位长度缩放

Rukshan PramodithaTowards Data Science Rukshan Pramoditha

·发表于 Towards Data Science ·13 分钟阅读·2023 年 8 月 15 日

--

照片由 Mediamodifier 提供,来自 Unsplash

许多机器学习算法需要特征在相同的尺度上。

我们可以在不同场景中选择不同类型的特征缩放方法。它们有不同的(技术)名称。术语特征缩放仅指这些方法中的任何一种。

**Topics
------**
**1\. Feature scaling in different scenarios**

   a. Feature scaling in PCA
   b. Feature scaling in k-means
   c. Feature scaling in KNN and SVM
   d. Feature scaling in linear models
   e. Feature scaling in neural networks
   f. Feature scaling in the convergence
   g. Feature scaling in tree-based algorithms
   h. Feature scaling in LDA

**2\. Feature scaling methods**

   a. Standardization
   b. Min-Max Scaling (Normalization)
   c. Robust Scaling
   d. Mean Normalization
   e. Maximum Absolute Scaling
   f. Vector Unit-Length Scaling

**3\. Feature scaling and distribution of data

4\. Data leakage when feature scaling

5\. Summary of feature scaling methods**

不同场景下的特征缩放

  • 主成分分析中的特征缩放: 在主成分分析中,如果原始特征的相对范围不同,PCA 组件对这些范围非常敏感。PCA 尝试选择能最大化数据方差的组件。如果由于某些特征的范围较大而导致方差最大化,这些特征可能会主导 PCA 过程。在这种情况下,组件可能无法捕捉真实的方差。为避免这种情况,我们通常在 PCA 之前进行特征缩放。然而,有两个例外。如果特征之间的尺度差异不显著,例如一个特征范围在 0 和 1 之间,另一个特征范围在 0 和 1.2 之间,虽然进行特征缩放不会有害,但我们不需要执行特征缩放!如果你通过分解相关矩阵而不是协方差矩阵来进行 PCA,即使特征不是在相同的尺度上测量的,也不需要进行特征缩放。

  • k-means 聚类中的特征缩放: k-means 聚类的主要假设之一是所有特征都在相同的尺度上进行测量。如果不是,我们应该进行特征缩放。k-means 算法计算数据点之间的距离。范围较高的特征可能主导计算,这些计算可能不准确。为避免这种情况,我们需要在 k-means 之前进行特征缩放。缩放特征还会提高 k-means 模型的训练速度。

  • KNN 和 SVM 算法中的特征缩放: 通常,计算数据点之间距离的算法大多受到特征相对范围的影响。KNN 和 SVM 也不例外。范围较高的特征可能会由于其范围较高而贡献更多,但并不是因为其重要性。我们不希望算法在这种方式上产生偏差。因此,我们需要缩放特征,以便它们在距离计算中贡献均等。

  • 线性模型中的特征缩放: 线性模型如线性回归的参数值高度依赖于输入特征的尺度。因此,最好使用在相同尺度上测量的特征。这也会提高线性模型的训练速度。

  • 神经网络中的特征缩放: 我们通常将特征缩放方法应用于输入数据。然而,也可以将特征缩放应用于神经网络中隐藏层的激活值!经过缩放的输出值随后成为下一层的输入。这被称为批量归一化,它能有效消除梯度消失问题和协变量偏移问题,并提高网络在训练过程中的稳定性。它还加快了神经网络模型的训练速度。

  • 算法收敛中的特征缩放: 学习率是决定深度学习和机器学习算法收敛速度的主要因素。特征缩放也对这一点有影响!当特征在相同的尺度上进行测量时,计算可以更快地执行,算法也会更快地收敛!

  • 基于树的算法中的特征缩放: 对于基于树的算法,不需要特征缩放,因为它们对特征的相对尺度不太敏感。流行的基于树的算法包括:决策树、随机森林、AdaBoost、梯度提升、XGBoost、LightGBM 和 CatBoost。

  • LDA 中的特征缩放: 线性判别分析(LDA)是一种线性降维技术,通过最大化分类数据集的类别可分离性来进行降维,而不是通过最大化数据的方差。因此,LDA 对特征的相对范围不敏感,因此不需要对 LDA 进行特征缩放。

特征缩放方法

1. 标准化

标准化是最流行的特征缩放方法,其中我们将值居中于 0 并使标准差为 1。结果是 z-score,因此这种缩放方法也称为 z-score 标准化或归一化。应用标准化到特征后,数据的分布具有均值 0 和标准差 1。这样的分布称为标准正态分布。

要对变量应用标准化,首先,我们需要计算该变量的均值和标准差。然后,从每个值中减去均值,并将结果除以标准差。对于一组特征,这些计算是同时在特征级别上进行的。

标准化公式(图片由作者提供)

z-score 或标准化的输出实际上表示一个值偏离均值多少个标准差!

z-score 值不受限于某个特定范围。标准化过程不会受到数据中异常值的影响。

标准化在数据遵循高斯分布或正态分布,或分布未知时特别有用。

在 Scikit-learn 中,可以使用**StandardScaler()**函数进行标准化。

from sklearn.preprocessing import StandardScaler

sc = StandardScaler()
sc.fit(data)
scaled_data = sc.transform(data)

调用 fit()方法时,会计算每个变量的均值和标准差。如果我们需要对数据应用缩放过程,也应调用 transform()方法。

2. 最小-最大缩放(归一化)

最小-最大缩放或归一化是将数据缩放到您选择的特定范围的过程。最常用的范围是(0, 1)。

要对变量应用最小-最大缩放,首先,我们需要找到该变量的最小值和最大值。然后,我们从每个数据值中减去最小值,并将结果除以范围(最大值与最小值的差)。对于一组特征,这些计算是同时在特征级别上进行的。

最小-最大缩放公式(图片由作者提供)

最小-最大缩放在数据分布未知或不遵循正态分布时特别有用。

最小-最大缩放过程对数据中的异常值非常敏感。

在 Scikit-learn 中,可以使用**MinMaxScaler()**函数进行最小-最大缩放。

from sklearn.preprocessing import MinMaxScaler

sc = MinMaxScaler(feature_range=(0,1))
sc.fit(data)
scaled_data = sc.transform(data)

调用 fit()方法时,会找到每个变量的最小值和最大值。如果我们需要对数据应用缩放过程,也应调用 transform()方法。

**MinMaxScaler()**函数还提供了更改您选择的范围的选项。默认设置为(0, 1)。

3. 鲁棒缩放

鲁棒缩放也称为分位数缩放,其中我们根据第 1、第 2 和第 3 分位数缩放数据。第 2 分位数是中位数,如您所知。

要对变量应用鲁棒缩放,首先需要找到该变量的分位数。然后,我们从每个数据值中减去中位数(第 2 个分位数或 Q2),并将结果除以 IQR(第 3 个分位数与第 1 个分位数之间的差异)。对于一组特征,这些计算会同时在特征层面进行。

鲁棒缩放公式(图片由作者提供)

鲁棒缩放替代公式(图片由作者提供)

当数据中存在异常值时,鲁棒缩放尤其有用。这是因为分位数对异常值具有鲁棒性(因此命名为鲁棒缩放)。

在 Scikit-learn 中,可以使用**RobustScaler()**函数执行鲁棒缩放。

from sklearn.preprocessing import RobustScaler

sc = RobustScaler()
sc.fit(data)
scaled_data = sc.transform(data)

调用 fit()方法时,找到每个变量的分位数值。如果我们需要对数据应用缩放过程,还应调用 transform()方法。

4. 均值归一化

均值归一化是另一种流行的特征缩放技术,我们从每个数据值中减去均值,并将结果除以范围(最大值与最小值之间的差异)。

这个公式与最小-最大缩放公式非常相似,只是我们从每个数据值中减去了均值,而不是从每个数据值中减去最小值。

均值归一化公式(图片由作者提供)

均值归一化无法直接在 Scikit-learn 中实现,因为没有专门的函数来执行这一操作。但我们可以通过将**StandardScaler()RobustScaler()**转换器结合创建一个 Scikit-learn 流水线来执行均值归一化。流水线顺序地将多个转换器应用于数据。

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import RobustScaler

ss = StandardScaler(with_mean=True, with_std=False)
rs = RobustScaler(with_centering=False, quantile_range=(0, 100))

mean_normalizer = Pipeline([('Std_Scaler', ss),
                            ('Rob_Scaler', rs)
])

mean_normalizer.fit(data)
scaled_data = mean_normalizer.transform(data)

在上面的代码示例中,我通过更改默认超参数值自定义了**StandardScaler()RobustScaler()**转换器!

ss = StandardScaler(with_mean=True, with_std=False)

这行代码并没有执行典型的标准化。相反,它从每个值中减去均值,并将结果除以 1,因为with_std=False。这将成为下一转换器的输入。

rs = RobustScaler(with_centering=False, quantile_range=(0, 100))

通过设置with_centering=False,不会从输入值中减去中位数。此外,它还通过**quantile_range=(0, 100)**将输入值除以范围(最大值与最小值之间的差异)。0 表示最小值,100 表示最大值。

流水线将上述自定义的转换器顺序地应用于数据。当数据经过上述流水线时,将执行均值归一化!

5. 最大绝对缩放

最大绝对缩放可以通过将每个数据值除以特征的最大值来执行。与之前的方法相比,它的公式非常简单。

最大绝对缩放公式(图片由作者提供)

之前的方法通过从每个数据值中减去均值、最小值或中位数来中心化数据。但最大绝对值缩放不会以这种方式中心化数据。因此,该方法适用于 稀疏数据,其中大多数值为零。

在 Scikit-learn 中,可以使用 MaxAbsScaler() 函数执行最大绝对值缩放。

from sklearn.preprocessing import MaxAbsScaler

sc = MaxAbsScaler()
sc.fit(data)
scaled_data = sc.transform(data)

调用 fit() 方法时,会找到每个变量的最大值。如果我们需要对数据应用缩放过程,也应该调用 transform() 方法。

6. 向量单位长度缩放

单位向量的大小为 1。要将非零向量转换为单位向量,需要将该向量除以其长度,该长度可以通过使用曼哈顿距离(L1 范数)或欧几里得距离(L2 范数)来计算。

现在,考虑以下非零向量。

向量(图片来源:作者)

该向量的长度(大小)可以通过曼哈顿距离(L1 范数)来计算。

向量的曼哈顿距离(L1 范数)(图片来源:作者)

向量的长度(大小)也可以通过使用最常用的方法,即欧几里得距离(L2 范数)来计算。

向量的欧几里得距离(L2 范数)(图片来源:作者)

要将 x 转换为单位向量,我们需要将其除以长度,如上所示。这称为 归一化向量。归一化后,向量变成一个大小(长度)为 1 的单位向量,并且具有与 x 相同的方向。

单位向量公式(图片来源:作者)

之前的特征缩放方法是按特征进行计算,即考虑所有观测值中的每个特征。然而,在单位长度缩放中,计算是按观测值进行的,即考虑所有特征中的每个观测值。

为了说明这一点,请考虑以下表格数据。

(图片来源:作者)

数据中有三个观测向量。例如,第一个观测可以由以下向量表示,

Ob1 = (2, 3, 5)

Ob1 = (2, 3, 5)

Length of Ob1 = Sqrt(2² + 3² + 5²) = Sqrt(38) --> Euclidean distance (L2 norm)

Unit Vector = [2/Sqrt(38), 3/Sqrt(38), 5/Sqrt(38)]

Length of Unit Vector = 1

所有其他观测值可以用类似的格式表示。

单位长度缩放以这种方式应用于观测向量。

在 Scikit-learn 中,单位长度缩放是通过 Normalizer() 函数进行的。

from sklearn.preprocessing import Normalizer

sc = Normalizer(norm='l2')
sc.fit(data)
scaled_data = sc.transform(data)

调用 fit() 方法时,这次没有任何变化。它仅验证模型的参数。在之前的情况下,参数是通过学习获得的。调用 transform() 方法将除以每个观测向量的长度,即执行单位长度缩放。

Normalizer() 函数还提供了更改距离类型的选项,曼哈顿(‘l1’ 范数)或欧几里得(‘l2’ 范数)。默认情况下使用‘l2’。

特征缩放与数据分布

以上讨论的任何特征缩放方法都不会改变数据的基础分布。在应用特征缩放前后,变量的分布保持不变!只有值的范围会发生变化!

为了说明这一点,我将创建同一特征的两个直方图,一个是在应用 z-score 标准化之前,另一个是在应用之后。

# Load iris data
from sklearn.datasets import load_iris

iris_data = load_iris().data
p = iris_data[:, 0] # Select the first feature

# Apply feature standardization
from sklearn.preprocessing import StandardScaler
p_scaled = StandardScaler().fit_transform(iris_data)[:, 0]
import matplotlib.pyplot as plt
plt.style.use('ggplot')

fig = plt.figure(figsize=(5.5, 4))
plt.hist(p, bins=20)
plt.title("Before scaling")
plt.show()

未缩放变量的直方图(图像作者提供)

fig = plt.figure(figsize=(5.5, 4))
plt.hist(p_scaled, bins=20, color='green')
plt.title("After scaling")
plt.show()

缩放变量的直方图(图像作者提供)

变量的分布保持不变!但是,值的范围会发生变化!

import numpy as np

np.min(p), np.max(p) # Before scaling

缩放前的范围(图像作者提供)

np.min(p_scaled), np.max(p_scaled) # After scaling

缩放后的范围(图像作者提供)

特征缩放时的数据泄漏

在数据预处理过程中,当训练集中的某些信息泄漏到测试集中时,就会发生数据泄漏。

训练模型时,用于训练的数据不应用于测试。这就是为什么我们将数据集划分为训练集和测试集的原因。训练集和测试集应该是独立的,不应混合使用。

在特征缩放中,数据泄漏主要有两种方式。

  • 在将数据划分为训练集和测试集之前进行特征缩放
from sklearn.datasets import load_iris
X = load_iris().data
y = load_iris().target

from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_scaled = sc.fit_transform(X)

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, 
                                                    test_size=0.20,
                                                    random_state=42)

当调用缩放器 (sc) 的 fit() 方法时,参数(每个变量的均值和标准差)是从整个数据集中学习得到的。由于在数据缩放后再进行划分,训练集和测试集中的一些信息可能会混淆。

为避免这种情况,应该在划分数据后进行特征缩放。

from sklearn.datasets import load_iris
X = load_iris().data
y = load_iris().target

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, 
                                                    test_size=0.20,
                                                    random_state=42)

from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
sc.fit(X_train)
X_train_scaled = sc.transform(X_train)
X_test_scaled = sc.transform(X_test)
  • 在对训练集和测试集分别调用缩放器 (sc) 的 fit() 方法两次时
from sklearn.preprocessing import StandardScaler

sc = StandardScaler()
X_train_scaled = sc.fit_transform(X_train)
X_test_scaled = sc.fit_transform(X_test)

当对训练集和测试集分别调用 fit() 方法两次时,参数会被学习两次。参数应该只在训练集上学习,而不是在测试集上学习。训练集上学习到的参数也可以应用于转换测试集。换句话说,你只需要在训练集上调用一次 fit() 方法。这样,我们可以避免将数据从训练集泄漏到测试集。

from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_train_scaled = sc.fit_transform(X_train)
X_test_scaled = sc.transform(X_test)

# Or

from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
sc.fit(X_train)
X_train_scaled = sc.transform(X_train)
X_test_scaled = sc.transform(X_test)

特征缩放方法总结

  1. 标准化: StandardScaler(),用法 — 当数据遵循高斯或正态分布或分布未知时 | 对数据中的异常值不太敏感

  2. 最小-最大缩放(归一化): MinMaxScaler(),用法 — 当数据分布未知或不遵循正态分布时 | 对数据中的异常值高度敏感

  3. 鲁棒缩放: RobustScaler(),用法 — 对数据中的异常值鲁棒

  4. 均值归一化: StandardScaler()RobustScaler(),用法 — 对数据中的异常值敏感

  5. 最大绝对值缩放: MaxAbsScaler(),用法 — 与其他方法不同,不会对数据进行中心化 | 与稀疏数据效果良好

  6. 向量单位长度缩放: Normalizer(),使用方法——逐观测进行计算,即考虑所有特征下的每个观测值(所有其他方法按特征进行计算,即考虑所有观测下的每个特征)

今天的文章到此结束。

如果您有任何问题或反馈,请告诉我。

AI 课程怎么样?

  • 神经网络与深度学习课程

加入我的私人邮件列表

再也不会错过我的精彩故事。通过订阅我的邮件列表,您将直接在我发布新故事时立即收到。

非常感谢您的持续支持!我们在下一篇文章中见。祝大家学习愉快!

参考文献

特别感谢以下书籍的作者,我通过阅读该书获得了有关特征缩放方法的一些知识。

  • Python 特征工程实用手册(第 2 版 2022)作者:索莱达德·加利

Iris 数据集信息

  • 来源: 您可以在这里下载原始的 Iris 数据集。

  • 创作者: R. A. Fisher

  • 引用: Fisher, R. A.. (1988). Iris. UCI 机器学习库。doi.org/10.24432/C56C76

  • 许可证: 本数据集采用知识共享署名 4.0 国际(CC BY 4.0)许可证。

本文最初由我在Substack上发布。

设计与撰写: Rukshan Pramoditha

2023–08–15

为什么将人工智能技术成功应用于临床护理如此困难?

原文:towardsdatascience.com/why-is-it-so-difficult-to-successfully-get-ai-technologies-adopted-into-clinical-care-4fa549d2c36?source=collection_archive---------7-----------------------#2023-03-30

图片由 国家癌症研究所 提供,发布在 Unsplash

探讨了一篇科学综述论文,该论文提出了这个问题并找到了答案

Anouk DutréeTowards Data Science Anouk Dutrée

·

点击这里 发表在 数据科学前沿 · 9 分钟阅读 · 2023 年 3 月 30 日

--

人工智能(AI)在我们日常生活中变得越来越普遍。从几乎每个网络商店中的推荐系统,到自动翻译你访问的网站上的外语。然而,对于某些行业而言,这一过渡似乎比其他行业更加顺利。医疗领域似乎特别难以进入,但为什么呢?虽然有如此多的学术活动专注于医疗领域的人工智能,但是什么让这些技术突破在医疗保健中无法产生切实影响?Sendak 等人在他们的综述论文《机器学习产品在医疗服务中的转化路径》(2020 年)中尝试寻找这个问题的答案。他们的发现与我在UbiOps 与MedTech 初创公司合作的经验非常契合,因此在本文中,我将带您逐步了解他们的论文。

机器学习在医疗保健中的现状

在我们深入探讨论文本身之前,让我们快速了解一下医疗领域中机器学习的现状。对机器学习在医疗领域的潜力的热情和兴奋程度非常高,导致了大量相关文献的出现。你几乎每隔一周就能看到有关利用机器学习进行癌症检测的新研究,而利用机器学习进行药物发现似乎也是一个热门话题。许多会议、组织和学术期刊已经建立,以传播围绕机器学习在医疗保健中的知识。

尽管研究正在快速增长,但切实的临床影响证据仍然稀少。你可能会想:“哦,但新技术通常需要一段时间才能成熟到能够应用于实践中”,但与此同时,我们也看到,利用机器学习提高用户留存率的新发现被 TikTok、Instagram 和 LinkedIn 等迅速采纳。Panch 等人巧妙地描述了这一医疗保健中机器学习的‘不便之处’。

“目前,研究文献中突出的算法实际上在临床实践的前线大多无法执行。”

幸运的是,有一些医疗公司成功地将人工智能/机器学习整合到了他们的产品中。例如,Ellogon 这样帮助医生选择合适的癌症免疫疗法患者的公司,证明了从概念验证过渡到可以轻松完全融入现有医疗协议的成熟产品是可能的。

是什么使成功融入医疗保健的机器学习产品与那些停留在概念验证阶段的产品有所不同? 让我们看看 Mark Sendak 等人的研究,找出答案。

Sendak 等人的研究

Mark Sendak 及其同事开始进行叙述性综述,以帮助理解如何将机器学习转化为医疗保健。他们结合了自己在构建机器学习产品方面的第一手经验,并与 21 个成功进入临床护理的机器学习模型案例研究相结合。这正是我对他们的研究感兴趣的原因,因为他们尝试从那些真正进入生产阶段的人身上学习。

根据他们对这 21 个案例研究的分析,作者确定了在医疗保健世界中将产品推向成熟阶段的核心阶段和挑战。

“转化路径”

作者成功地将 21 个成功案例映射回他们所称的“转化路径”(见图)。他们指出,在从概念验证到正式产品的过程中,有四个关键阶段。这些阶段是:

  1. 设计与开发: 这是识别正确问题并设计和开发能够提供可操作见解的机器学习工具的过程。

  2. 评估与验证: 评估产品是否真的能改善临床护理和患者结果,是否准确可靠,以及是否有商业案例。

  3. 扩展与推广: 这一步描述了将概念验证真正扩展为集成产品的过程。它需要扩展模型的部署并将其推广给早期采用者。

  4. 持续监控和维护: 重要的是要注意,没有一个机器学习产品是最终完成的。模型需要持续监控和更新,以避免出现故障行为。特别是在医疗保健领域,这一点可能会带来严重的后果。

这些阶段不一定是顺序进行的,团队可能会在这些阶段之间反复迭代。有关转化路径的更多细节,请参见下图。

图片由 Sendak 等人提供,摘自《机器学习产品转化为医疗保健服务的路径》。图片描述了转化路径的各个阶段。

主要障碍

综述很好地描述了在医疗保健中创建机器学习驱动产品时面临的各种挑战和挫折点,从技术基础设施挑战到伦理风险。我不会逐一列举所有问题,但我想突出几个我认同的点。

领域知识与产品化知识

在开发医疗技术工具时,总会存在领域知识与生产化知识之间的紧张关系。你团队中的人员数量有限,同时你需要确保有足够的医学专家参与,还要有能实际构建和部署解决方案的合适人员。关注点的选择高度依赖,但 Sendak 等人很好地强调了如果你想成功,团队中需要在某种程度上具备这两种能力。

当然,并非每种技能都需要团队中实际有人具备!某些任务也可以外包,或引入可以处理标准任务的工具,以便专家可以专注于你解决方案中的独特之处。我看到许多公司因开源工具的免费而忙于构建自己的平台。但别忘了,团队中需要投入时间和精力来设置这些技术的成本!当他们忙于使部署工具工作时,你正浪费可以用于实际改进模型和创造价值的时间…

什么时候一个模型是“好”的?

好模型和坏模型的区别往往不清晰,也不明确你在特定情况下应追求什么样的性能。如果不讨论这一点,可能会导致期望与现实的不匹配。这里需要注意的是,这不仅仅关乎模型的准确性,还涉及到可用性和潜在的经济性能。一个准确性很高但运行需 10 小时的模型,可能不会很有用,也不实惠。每个案例都不同,因此在早期进行讨论以识别和达成相关模型性能指标的共识至关重要。

作者提供的图片

在孤立的背景下证明产品的有效性是不够的

仅仅因为产品在受控测试环境和测试数据集上表现良好,并不意味着该产品在实际环境中也会表现良好。重要的是要让真实数据通过产品,并用它来评估产品性能。在 UbiOps,我们专注于部署和服务,我们多次看到模型在引入实际生产数据后,性能可能会发生巨大的变化!即使只是作为影子部署,尽早进入这一阶段也很重要。

集成到生产环境中是困难的

作者指出,实际生产环境和开发/数据存储环境之间通常存在巨大差异。在他们调查的所有示例案例中,他们发现通常需要大量的努力和投资才能将产品集成到现有系统中。一项研究估计,在一个地点验证和集成肾衰竭风险方程到临床工作流程的成本接近$220,000。这只是一个地点!

数据分布在云和本地环境中

这篇评论突显了一个重要问题,即数据分布在各种云解决方案和本地数据中心。通常这在扩展阶段开始时会引发问题。设计产品和架构时意识到这一点,可以大大有利于从概念验证过渡到正式推出的产品。

不断变化的监管框架

另一个主要挑战与合规性、数据安全以及医疗设备和软件所需的大量法规和认证相关。更不用说规则不断变化。很难跟上所有内容并确保你的产品每个部分都完全合规。数据安全尤其是一个障碍,因为数据非常敏感。

接下来呢?

我已经向你介绍了翻译路径及其主要障碍,那么接下来呢?我认为,当你开始创建一个新型的医疗保健领域的 ML 驱动产品时,一切都始于意识和开放讨论。了解前人遇到的挑战,你如何从他们的错误中学习?

作者提供的图片

在我看来,最重要的是不要害怕实际进入那个模糊和扩展的步骤。从开发环境中走出来并在生产中运行事物是绝对关键的,尽管是在阴影模式下。只有在迈出这一步后,你才能开始朝着真正具有影响力和价值的产品前进。

那么,你如何确保实际在生产环境中运行这些东西?确保你投资于一个可以帮助你快速迭代的基础设施,并且让你专注于你擅长的事情:为医疗用例构建模型。投资于可以帮助你快速部署和监控的 MLOps 工具,确实有助于确保你能专注于实际挑战,而不是标准的基础设施挑战。我看到许多公司因为开源工具免费而投入大量精力构建自己的平台。但别忘了,团队成员投入时间和精力去设置这些技术的成本!当他们忙于让部署工具运作时,你却浪费了可以用来真正改进模型和创造价值的时间……

在数据安全方面,确保你使用的工具已获得正确的认证(如 ISO 认证)是有帮助的。特别是在欧洲,选择一些较小的云服务提供商而非三大巨头也可能更合适。与专注于医疗数据的云服务提供商合作,你将大大减少在证明你符合所有规定时的麻烦。

简而言之

在将 AI 纳入日常临床护理时,有许多因素需要考虑。从法规、架构问题,到吸引合适的人参与。Sendak 等人成功地在他们的“转化路径”中简洁地捕捉了所有阶段和障碍。了解这一转化路径的四个不同阶段以及可能遇到的不同障碍,肯定有助于你为成功做好准备。

想要讨论医疗保健中 ML 的架构设置吗?

如果你喜欢本文的内容,并希望深入探讨该领域的 ML 架构设置,不要犹豫,随时联系我!我很乐意讨论 MLOps。你可以在评论区联系我,或者直接在 LinkedIn 上找到我。

参考文献

Panch T 等人。关于医疗保健中 AI 的“令人不便的真相”。npj Digital Medicine. 2019

Sendak, MP 等人。将机器学习产品转化为医疗保健服务的路径。EMJ 评审。2020

Sendak MP 等人。分析 EHR 数据中实现规模经济的障碍:一个警示性的故事。Applied Clinical Informatics. 2017

Xiao C 等人。使用电子健康记录数据开发深度学习模型的机遇与挑战:系统综述。J Am Med Inform Assoc. 2018

为什么更多即是更多(在人工智能中)

原文:towardsdatascience.com/why-more-is-more-in-deep-learning-b28d7cedc9f5

大型神经网络如何泛化

Manuel BrennerTowards Data Science Manuel Brenner

·发表于 Towards Data Science ·阅读时间 10 分钟·2023 年 8 月 15 日

--

少即是多。

-路德维希·密斯·范·德·罗赫

只有当更多已经太多时,少即是多。

  • 弗兰克·劳德·赖特

深度神经网络(DNNs)深刻地改变了机器学习的格局,常常与人工智能和机器学习的广泛领域同义。然而,它们的崛起若没有它们的“搭档”——随机梯度下降(SGD),将是难以想象的。

SGD 及其衍生优化器构成了许多自学习算法的核心。其核心概念很简单:使用训练数据计算任务的损失,确定相对于其参数的损失梯度,然后调整参数以最小化损失。

听起来很简单,但在应用中,它已经被证明极其强大:SGD 可以为各种复杂问题和训练数据找到解决方案,只要它与足够表达力的架构结合使用。它特别擅长找到使网络在训练数据上表现完美的参数集合,这被称为插值机制。但在什么条件下,神经网络被认为是良好泛化的,即在未见过的测试数据上表现良好?

总体化的追求是机器学习的核心。由 DALL-E 构想。

在某些方面,这几乎过于强大:SGD 的能力不仅限于可以期望带来良好泛化的训练数据。例如,在这篇有影响力的论文中已经表明,SGD 可以使网络完美记忆一组随机标记的图像(关于记忆与泛化之间的深层关系,我之前有过相关的讨论)。虽然这看起来具有挑战性——考虑到标签与图像内容之间的不匹配——但对于使用 SGD 训练的神经网络来说,这出奇地简单。实际上,这并不比拟合真实数据更具挑战性。

这种能力表明,使用 SGD 训练的神经网络存在过拟合的风险,避免过拟合的正则化措施,如范数、早期停止和减少模型大小变得至关重要。

从经典统计学的角度来看,少即是多,因此多即是少,如这幅 DALL-E 画作简洁总结的那样:

更多即是更少,由 DALL-E 创作。

“一切事物都应尽可能简单,但不能更简单。” — 阿尔伯特·爱因斯坦

但问题在于:最令人惊讶的现代深度学习特性之一是,大多数架构在高度过参数化的阶段,即参数远多于数据点时表现最佳。这令人惊讶,因为标准学习理论,基于基本直觉,认为如此多的参数使得深度神经网络(DNNs)会过度调整到特定的训练数据上,从而在处理新的、未见过的数据时表现较差。

这在著名的**“双重下降”**曲线中得到了很好的体现,该曲线由 Belkin 等人在 2019 年的论文《调和现代机器学习实践与偏差-方差权衡》中推广。

观察到的经典 U 型学习曲线和双重下降现象。 摘自 Belkin 等,2019 年。

在“经典”领域,增加容量会导致训练和测试损失的减少,但在最佳点之后,测试风险会再次增加,而训练风险降至零(“过拟合阶段”)。

但现代机器学习算法已经突破了这一门槛:移动到现代插值阶段,使用高度过参数化的模型,测试风险再次降低,甚至降到经典阶段无法达到的水平,实现了前所未有的泛化能力。

数学语言在物理法则表述中的适用性奇迹,是我们既无法理解也不配拥有的美妙礼物。

尤金·维根

神经网络能够泛化,即便其固有的过拟合倾向,表明存在所谓的归纳偏差。归纳偏差指的是学习算法在预测未见数据点的输出时,基于其训练数据所作的(通常未言明的)一系列假设。就像我们常常无意识的(且常常相当麻烦的)认知偏见一样,架构的归纳偏差决定了它对某些解决方案的偏好,在不确定性或存在多种可能解决方案时引导它。

事实证明,我们非常幸运:许多研究指出,使用 SGD 训练的 DNNs 对函数具有良好的泛化能力(在结构化数据上,这也是我们在应用 DNNs 时最关心的数据)。这是一系列使深度学习如此有效的惊喜之一,特伦斯·塞伊诺斯基在“深度学习在人工智能中的非理性有效性”中讨论了这一点,回响了尤金·维根的六十年前关于数学在自然科学中的非理性有效性的著名论文,我们“既无法理解也不配拥有”。

“深奥的科学真理之所以被发现,并不是因为它们有用;而是因为它们可以被发现。”

J. 罗伯特·奥本海默

这种非理性的有效性使许多看似不可能的深度学习应用得以实现。

这也可能是我们不配拥有的,或者更确切地说,(尚未)不该拥有的:鉴于人工智能的迅速发展和越来越多的警示声音呼吁暂停人工智能研究,深度学习的成功也打开了潘多拉的盒子,这可能会定义未来一个世纪的人类道路。

DALL-E 设想的潘多拉盒子的开启。

但,与维根提出的关于数学与物理、思想与现实之间的基础关系的形而上学问题相比,深度学习的成功至少是我们可以理解的。

尽管对神经网络为何能如此良好地进行泛化进行了大量理论研究,但深度学习的理论仍滞后,目前尚无明确且普遍接受的解释来说明为何深度神经网络(DNNs)能够如此出色地进行泛化。

许多理论提议,例如局部最小值是全局最小值的见解,通常只适用于有限的架构,如线性神经网络或简单的非线性网络。通常,从学习理论中得出的结果可能与实际使深度神经网络有效的实践经验相矛盾,例如实际深度神经网络中存在许多次优的局部最小值(在这篇恰如其分标题的论文中探讨了真相或反向传播)。

尽管尚未有普遍认可的解释,但深度学习理论中有许多引人入胜的见解可以推进我们对为什么 DNNs 中的泛化有效的理解和直觉。

在本文的其余部分,我想探索几种解释尝试,以理解为什么更多往往意味着更多对于神经网络训练(简要说明:我将专注于分布内泛化,而不讨论分布转移领域,即被称为分布外/OOD 泛化的领域)。

如开头所提,大致有两个潜在的罪魁祸首:SGD/优化神经网络本身。

认为 SGD 可能是导致良好泛化的归纳偏差的原因,已经引发了大量研究。这些研究深入探讨了在这些泛化特性显现的各种机制和优化器。值得注意的是,在今天的众多优化器变体中,从 SGD 到 Adam 再到 Adagrad 到 LBFGS,选择优化器对泛化的影响很小。大多数,甚至所有,都能在不同任务中实现相当的泛化结果。虽然有人理论认为,SGD 固有的随机性有助于模型逃离尖锐的、不具泛化性的最小值,推动其向损失景观中更广泛、更具泛化性的区域移动,并对参数具有正则化效果,即使是全批次 SGD(一次性在整个训练数据上进行训练)在许多情况下也表现良好。

这表明泛化可能与损失景观的特征更加密切相关,而非特定的优化器引导学习算法的过程。这引出了一个有趣的问题:特定最小值的泛化能力如何与周围损失景观的属性相关联?

一个自 90 年代以来流行的解释认为,尖锐的最小值相较于平坦的最小值通常推广效果较差。直观上,这与分类时决策边界距离数据点的远近有关:在泛化效果不佳和过拟合严重的情况下,决策边界紧贴数据点,因此参数的微小变化可能导致损失的剧烈变化,因为它们也会改变多个点的分类。相反,平坦的最小值会导致较宽的决策边际,类似于支持向量机中的目标边际。

崎岖的损失景观中包含尖锐的局部最小值与较平滑且较宽的局部最小值。损失景观的平滑度也取决于训练技术和正则化技术,如丢弃法。摘自我们最近的 ICML 论文。

由于梯度下降在优化过程中更容易遇到平坦的最小值,因此自然更倾向于泛化的解决方案,这种效果因通常非常高维的损失景观而加剧。尖锐的最小值就像是高维稻草堆中的,而平坦的最小值则是稻草堆中的稻草,考虑到问题的高维性质,找到尖锐的过拟合最小值比找到平坦的最小值要困难几个数量级

此外,平坦的最小值在体积上也比尖锐的最小值大,而高维空间中体积的反直观扩展意味着这可能导致它们各自体积上的数量级差异。

另一个在实践中经常观察到的现象可以进一步发挥作用:局部最小值在实践中通常被认为由小损失的山谷连接,即“损失山谷”。SGD 以其迭代性质可以穿越这些山谷,更容易找到并停留在这些更广泛、可泛化的区域。

在讨论了 SGD 及其与损失景观的相互作用后,现在该直接思考神经网络了:随机初始化的深度神经网络本身是否已经表现出某种**“简单/泛化”解的归纳偏差**,无论简单指的是什么?

如果简单的网络导致简单的解决方案,而 DNNs 所建模的许多过程实际上是简单的(即,比拟合随机噪声的维度低得多),那么神经网络表现出简单解决方案的倾向将给我们带来很多信息。

检查 SGD 是否与神经网络的成功有很大关系的一个好方法是观察它们在没有明确通过 SGD 训练的情况下如何解决问题。

一篇近期论文 (SGD 是贝叶斯采样器吗?差不多。) 提出,促使 DNN 朝向“简单”解决方案的归纳偏差,可能不仅仅是 SGD 的特殊属性,而是网络的固有特征

论文的核心前提围绕两个概率:P_SGD(f∣S)— 使用 SGD 或其变体训练的过参数化 DNN 收敛到一个与训练集 S 一致的函数 f 的可能性;以及 P_Bayesian(f∣S)— 给定训练集 S,函数 f 由 DNN 表示的贝叶斯后验概率,当其参数随机初始化时。

不讨论细节,这项研究发现这两个概率之间存在显著的相关性。更重要的是,贝叶斯采样器表现出强烈倾向于具有低错误和最小复杂性的函数。这一发现表明,DNN 的固有结构及其庞大规模,而不是 SGD 的具体细节,可能是其在过参数化设置中具备良好泛化能力的主导因素。

然而,论文也指出,尽管这两个概率紧密相关,但仍存在对超参数敏感的细微差异。这意味着架构变化、批量大小、学习率和优化器选择等因素在 DNN 性能中仍然非常重要。

“幸运是准备与机会相遇时发生的事情。”

— 塞内卡

架构偏好简单解决方案的事实也与过参数化网络的另一个新见解相契合,即所谓的彩票票据假设。

它提出,在大型的过参数化网络中,存在着较小、稀疏的子网络,这些子网络具有成功的潜力。这些被称为“赢票”的网络,不仅仅是幸运的发现,而是已经以一种特别适合学习特定任务的方式初始化。在过参数化模型中,存在着众多的“赢票”或最佳子网络。这些子结构倾向于高效学习和良好的泛化能力。整体网络的冗余性提供了一种集成学习的形式,其中多个“优质”子网络共同收敛到稳健的解决方案。此外,参数的过剩可能有助于探索损失景观,使模型能够有效地发现和利用这些子网络。

在我之前的文章中,我深入探讨了免费午餐定理,以及大型预训练模型的迁移学习如何给我们提供类似于免费午餐的东西。这与泛化的主题紧密相关:大型预训练模型通常表现出显著的泛化能力,即使在特定领域的数据有限的情况下。这种现象似乎违背了传统的偏差-方差权衡,呼应了过参数化模型良好泛化的更广泛主题。像 GPT-4 这样的模型向我们展示了,拥有更多参数并辅以足够多样化的数据,确实可以导致模型不仅能够很好地拟合训练数据,而且在看似新颖且未见过的场景中也表现出色。

但用于训练的数据质量可能是泛化的最关键因素。Sam Altman 经常强调数据策划在 GPT-4 的有效性中发挥了重要作用。而在正确类型的数据上“过拟合”可能足以让你获得所需的结果。正如AI Coffee Break的主持人 Letitia Parcalabescu 在我们一起录制的最新播客集中所说:“如果你在整个世界上过拟合,你基本上就完成了。”

大型神经网络之所以能够良好泛化,可能有多种原因。这是大多数人没有预料到的,尤其是在 20 世纪对偏差-方差权衡的深刻传统思维影响下。

但它确实有效。尽管所有的模型和算法都是由人类开发的,但我们仍然不确定发生了什么。也许,未来某天,我们会找到答案。如果我们找不到答案,我相信 AI 算法在自我探索的过程中会找到的。

感谢阅读!

如果你喜欢我的写作,请订阅以通过邮件获取我的故事,或考虑成为推荐会员。

为什么 OpenAI 的 API 对非英语语言更昂贵

原文:towardsdatascience.com/why-openais-api-is-more-expensive-for-non-english-languages-553da4a1eecc

超越文字:字节对编码和 Unicode 编码如何影响定价差异

Leonie MonigattiTowards Data Science Leonie Monigatti

·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 8 月 16 日

--

怎么会“Hello world”这个短语在英语中有两个令牌,而在印地语中却有 12 个令牌呢?

在发布了 我最近关于如何估算 OpenAI API 成本的文章 后,我收到了一条有趣的评论,有人注意到 OpenAI API 在其他语言中的费用远高于英语,例如使用汉字、日语或韩文 (CJK) 字符的语言。

有读者在 我最近关于如何估算 OpenAI API 成本的文章 上评论了 [tiktoken](https://medium.com/towards-data-science/easily-estimate-your-openai-api-costs-with-tiktoken-c17caf6d015e)

我之前并未意识到这个问题,但很快意识到这是一个活跃的研究领域:今年年初,Petrov 等人发表了一篇名为“语言模型分词器在语言之间引入不公平性”的论文[2],该论文显示“同一文本翻译成不同语言后,其分词长度可能会有剧烈差异,在某些情况下差异可达 15 倍。”

作为补充,分词是将文本拆分为一系列令牌的过程,这些令牌是文本中的常见字符序列。

分词的一个示例

符号化长度的差异是一个问题,因为OpenAI API 按 1,000 个符号计费。因此,如果你在类似的文本中有多达 15 倍的符号,这将导致 15 倍的 API 费用。

实验:不同语言中的符号数量

让我们将短语“Hello world”翻译成日语(こんにちは世界)并转录成印地语(हैलो वर्ल्ड)。当我们用 OpenAI 的 GPT 模型中使用的cl100k_base分词器对这些新短语进行符号化时,得到以下结果(你可以在本文末尾找到我用于这些实验的代码):

“Hello world”在英语、日语和印地语中的字母和符号数量(cl100k_base

从上图中,我们可以得出两个有趣的观察:

  1. 这个短语的字母数量在英语中最多,而在印地语中最少,但产生的符号数量在英语中最少,而在印地语中最多。

  2. 在印地语中,符号的数量多于字母的数量。

这怎么可能发生?

基础知识

要理解为什么在除英语之外的语言中,相同的短语会产生更多的符号,我们需要回顾字节对编码和 Unicode 的两个基本概念。

字节对编码

字节对编码(BPE)算法最初是由 Gage [1]于 1994 年发明的,用作一种压缩算法。

“[BPE]算法通过查找数据中最频繁出现的相邻字节对来压缩数据,并用一个原始数据中不存在的字节替换所有出现的对。该算法重复此过程,直到无法进一步压缩,无论是因为没有更多频繁出现的对,还是没有更多的未使用字节来表示对。”[1]

让我们通过原始论文中的例子[1]。假设你有一个最小的文本语料库,由字符串“ABABCABCD”组成。

1. 对于每一对字节(在这个例子中,即字符),你将统计它在语料库中的出现次数,如下所示。

"ABABCABCD"

pairs = {
  'AB' : 3,
  'BA' : 1,
  'BC' : 2,
  'CA' : 1,
  'CD' : 1,
} 

2. 取出现次数最多的一对字节,并用一个未使用的字符替换它。在这种情况下,我们将把“AB”这对字节替换为“X”。

# Replace "AB" with "X" in "ABABCABCD":
"XXCXCD"

pairs = {
  'XX' : 1,
  'XC' : 2,
  'CX' : 1,
  'CD' : 1, 
}

3. 重复步骤 2,直到无法进一步压缩或没有更多的未使用字节(在这个例子中,即字符)为止。

# Replace "XC" with "Y" in "XXCXCD":
"XYYD"

pairs = {
  'XY' : 1,
  'YY' : 1,
  'YD' : 1,
}

Unicode

Unicode 是一种编码标准,定义了不同字符如何用称为码点的唯一数字表示。在本文中,我们不会涵盖 Unicode 的所有细节。如果你需要复习,可以参考这个优秀的 StackOverflow 回答。

对于以下解释,你需要知道的是,如果你的文本是以 UTF-8 编码的,不同语言的字符将需要不同数量的字节。

从下面的表格中可以看出,英语字母可以用 ASCII 字符表示,只需要 1 个字节。但例如,希腊字符需要 2 个字节,而日文字符需要 3 个字节。

(灵感来自维基百科关于 UTF-8 的文章和StackOverflow 的回答)

探索内部机制

现在我们理解了不同语言的字符在数值上需要不同的字节量,并且 OpenAI 的 GPT 模型使用的分词器是 BPE 算法,它在字节级别上进行分词,让我们深入探讨一下我们的开篇实验。

英语

首先,让我们看看英文分词的普通例子:

分词短语“Hello world”

从上面的可视化中,我们可以得出以下观察:

  • 一个字母等于一个代码点

  • 一个 Unicode 代码点等于 1 个字节

  • BPE 将“Hello”分词为 5 个字节,将“ world”分词为 6 个字节,分成两个独立的标记

这一观察结果与OpenAI 分词器网站上的声明一致:

“一个有用的经验法则是,一个标记通常对应于~4 个字符的常见英文文本。”

注意到它说“对于常见的英文文本”?让我们看看非英文文本。

日语

那么在那些一个字母不对应一个字节而是多个字节的语言中会发生什么呢?让我们看看翻译成日语的“Hello world”,它使用了在 UTF-8 编码中为 3 字节的 CJK 字符:

分词短语“こんにちは世界”

从上面的可视化中,我们可以得出以下观察:

  • 一个字母等于一个代码点

  • 一个 Unicode 代码点等于 3 个字节

  • BPE 将 15 个字节的こんにちは(“Hello”的日文)分词为一个单独的标记

  • 但字母界被分词为一个单独的标记

  • 字母世被分词为两个标记

印地语

在那些一个字母不等于一个代码点而是由多个代码点组成的语言中,情况变得更加复杂。让我们看看“Hello world”翻译成印地语的情况。用于印地语的天城文字符表中的字符必须拆分成多个代码点,每个代码点需要 3 个字节:

分词短语“हैलो वर्ल्ड”

从上面的可视化中,我们可以得出以下观察:

  • 一个字母可以由多个 Unicode 代码点组成(例如,字母है由代码点ह和ै组合而成)

  • 一个 Unicode 代码点等于 3 个字节

  • 与日文字符世类似,一个代码点可以被分为两个标记

  • 一些标记跨越一个以上但少于两个字母(例如,标记 ID 31584)

总结

本文探讨了相同短语“Hello world”翻译成日语并转录成印地语后是如何进行分词的。首先,我们了解到 OpenAI 的 GPT 模型使用的分词器是在字节级别进行分词的。此外,我们还发现,日语和天城文字符号需要多个字节来表示一个字符,而不是像英语那样。因此,我们看到 UTF-8 编码和 BPE 分词器在最终令牌数量中发挥了重要作用,并影响了 API 成本。

当然,不同的因素,例如 GPT 模型在多语言文本上的训练不均等,会影响分词。在撰写时,这个问题是一个活跃的研究领域,我很期待看到不同的解决方案。

享受这个故事吗?

免费订阅 以便在我发布新故事时收到通知。

[## 每当 Leonie Monigatti 发布新内容时,你会收到电子邮件。

每当 Leonie Monigatti 发布新内容时,你会收到电子邮件。通过注册,如果你还没有 Medium 帐户,你将创建一个…

medium.com](https://medium.com/@iamleonie/subscribe?source=post_page-----553da4a1eecc--------------------------------)

LinkedIn,Twitter Kaggle上找到我!

参考文献

图片参考

如果没有另行说明,所有图片均由作者创作。

网络与文学

[1] Gage, P. (1994). 一种新的数据压缩算法。C 用户杂志, 12(2), 23–38.

[2] Petrov, A., La Malfa, E., Torr, P. H., & Bibi, A. (2023). 语言模型分词器在语言间引入了不公平性。arXiv 预印本 arXiv:2305.15425

代码

这是我用来计算和解码文章中令牌数量的代码。

# pip install tiktoken

import tiktoken

# Define encoding
encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")

# Tokenize text and get token ids
tokens = encoding.encode(text)

# Decode token ids
decoded_text = [encoding.decode_single_token_bytes(token) for token in tokens]

为什么概率链结比模糊匹配或基于术语频率的方法更准确

原文:towardsdatascience.com/why-probabilistic-linkage-is-more-accurate-than-fuzzy-matching-or-term-frequency-based-approaches-15a28c733e73?source=collection_archive---------7-----------------------#2023-10-26

不同的记录链结方法如何有效利用记录中的信息进行预测?

Robin LinacreTowards Data Science Robin Linacre

·

关注 发表在 Towards Data Science ·4 min read·2023 年 10 月 26 日

--

从数据中提取信息。图像由作者使用 DALL·E 3 创建

一个普遍存在的数据质量问题是有多个不同的记录指向同一实体,但没有唯一的标识符将这些实体联系在一起。

在缺乏唯一标识符如社会安全号码的情况下,我们可以使用一组合在一起的非唯一变量,如姓名、性别和出生日期来识别个人。

为了在记录链接中获得最佳准确性,我们需要一个从输入数据中提取尽可能多信息的模型。

本文描述了在做出准确预测时最重要的三种信息类型,以及 Fellegi-Sunter 模型如何利用这三种信息,该模型用于Splink(一个用于数据链接和去重的免费 Python 包)。

它还描述了一些替代的记录链接方法如何丢弃部分信息,从而降低了准确性。

三种信息类型

大致上,有三类信息在预测一对记录是否匹配时是相关的:

  1. 记录对的相似性

  2. 整体数据集中的值的频率,以及更广泛地测量不同情景的普遍性

  3. 整体数据集的数据质量

让我们逐一查看。

1. 配对记录比较的相似性:模糊匹配

预测两个记录是否表示同一实体的最明显方法是测量列中是否包含相同或相似的信息。

每列的相似性可以使用模糊匹配函数如Levenshtein或Jaro-Winker进行定量测量,或使用绝对或百分比差异等数值差异。

例如,HammondHamond的 Jaro-Winkler 相似度为 0.97(1.0 是完美分数)。这可能是一个拼写错误。

这些度量可以分配权重,并求和以计算总相似性分数。

这种方法有时被称为模糊匹配,它是准确链接模型的重要组成部分。

然而,仅使用这种方法有一个主要缺陷:权重是任意的:

  • 用户需要猜测不同领域的重要性。例如,应该给年龄的匹配分配什么权重?这与名字匹配相比如何?当信息不匹配时,我们应该如何决定惩罚性权重的大小?

  • 预测的强度与每个模糊匹配度量之间的关系必须由用户猜测,而不是被估计。例如,如果名字的 Jaro-Winkler 相似度为 0.9 而不是完全匹配,我们的预测应该改变多少?如果 Jaro-Winkler 分数降至 0.8,预测的变化量是否相同?

2. 整体数据集中值的频率,或更广泛地测量不同情景的普遍性

我们可以通过考虑整体数据集中值的频率(有时称为“术语频率”)来改进模糊匹配。

例如,JohnJohn,以及JossJoss都是完全匹配,因此具有相同的相似性评分,但后者比前者更有力地证明匹配,因为Joss是一个不常见的名字。

JohnJoss的相对术语频率提供了这些不同名字相对重要性的基于数据的估计,这可以用来指导权重设置。

这个概念可以扩展到包括那些不是完全匹配的类似记录。权重可以通过估计在数据集中观察到模糊匹配的常见程度来推导。例如,如果在 Jaro-Winkler 评分为 0.7 的情况下,即使在不匹配记录之间,看到模糊匹配非常常见,那么如果观察到这样的匹配,这并不能提供多少支持匹配的证据。在概率连接中,这些信息被捕捉在被称为u概率的参数中,详细描述见这里。

3. 整体数据集的数据质量:衡量不匹配信息的重要性

我们已经看到,模糊匹配和基于术语频率的方法可以让我们对记录之间的相似性进行评分,甚至在某种程度上对不同列上的匹配重要性进行加权。

然而,这些技术都无法帮助量化非匹配与预测匹配概率之间的相对重要性。

概率方法通过估计数据质量明确地估计这些情景的相对重要性。在概率连接中,这些信息被捕捉在m概率中,详细定义见这里。

例如,如果性别变量的数据质量极高,那么性别的不匹配将是强有力的证据,表明这两条记录不是真正的匹配。

相反,如果记录已经被观察了多年,那么年龄的不匹配不会是两条记录匹配的有力证据。

概率连接

概率模型的强大之处在于以其他模型无法实现的方式结合所有三种信息来源。

不仅所有这些信息都被纳入预测中,Fellegi-Sunter 模型中的部分匹配权重使得不同类型信息的相对重要性可以从数据本身中估计出来,从而正确地加权以优化准确性。

相反,模糊匹配技术通常使用任意权重,不能充分融入所有三个来源的信息。术语频率方法缺乏利用数据质量信息来负面加权不匹配信息的能力,也没有适当加权模糊匹配的机制。

作者是 Splink的开发者,这是一个用于大规模概率链接的免费开源 Python 包。

为什么数据科学家应该采用机器学习(ML)管道

原文:towardsdatascience.com/why-should-data-scientists-adopt-machine-learning-ml-pipelines-8fc5e24920dd

意见

MLOps 实践 — 作为数据科学家,你是将一个笔记本还是一个 ML 管道交给你的 ML 工程师或 DevOps 工程师,以便在生产环境中部署 ML 模型?

YUNNA WEITowards Data Science YUNNA WEI

·发表于 Towards Data Science ·9 分钟阅读·2023 年 2 月 1 日

--

背景

在我之前的文章中:

  • 学习 MLOps 核心 — 构建机器学习(ML)管道

  • MLOps 实践 — 将 ML 解决方案架构分解为 10 个组件

我谈到了构建 ML 管道的重要性。在今天的文章中,我将深入探讨 ML 管道的主题,并详细解释:

  • 为什么构建 ML 管道是必要且重要的

  • ML 管道的关键组件是什么

  • 为什么以及数据科学家如何采用 ML 管道?

照片由Jon Tyson拍摄,发布在Unsplash。

为什么构建 ML 管道是必要且重要的?

数据科学家在笔记本环境中开发 ML 模型是很常见的。在笔记本中,他们会实验不同的数据集、不同的特征工程技术,以及不同的参数和超参数组合,以找到表现最佳的 ML 模型。在经过多轮性能调优和特征工程之后,他们找到理想的 ML 模型。他们继续用测试数据验证模型的性能,以确保模型没有“过拟合”并且满足业务要求。模型验证代码很可能是在同一个笔记本环境中开发和运行的。当他们对整体模型性能满意时,他们会交接一个笔记本(其中的代码可能涵盖数据摄取、数据探索、特征工程、模型训练和日志记录以及模型验证)给 ML 工程师或 DevOps 工程师,以便将模型部署到生产环境中。

然而,这个交接过程可能会出现许多问题:

  • 缓慢和手动的迭代 — 当在部署的 ML 系统中检测到问题时,数据科学家需要回到他们的笔记本中寻找解决方案,并再次将解决方案交给生产工程师,以便将更改部署到生产环境中。每次需要更新模型时,这个过程都会重复。这可能会有无尽的交接。这是一种非常缓慢的迭代方式。更糟糕的是,这种手动和缓慢的迭代可能会导致显著的业务损失。

  • 重复工作 — 数据科学家在笔记本环境中生成的代码很可能不适合生产环境。因此,ML 工程师需要投入精力重新编写和测试代码,并将代码转换为生产部署的模块。ML 管道可以显著减少代码重写的工作量。我将在本文后面解释原因。

  • 易出错 — ML 应用程序依赖于各种外部库来辅助模型训练过程,这使得数据科学家需要导入这些库。例如,他们通常利用 Pandas 进行数据清洗和特征工程,利用 ML/DL 框架进行特征和模型开发,利用 Numpy 进行张量和数组计算。除了这些流行的库,他们还可能会使用一些适合特定环境的小众库。由于这种依赖性,生产环境中使用的库与开发环境中使用的库不匹配时可能会引发错误。在前一点中,我们提到当笔记本被交给 ML 工程师时,通常会有大量的代码重写。在代码转换过程中可能会产生很多无意中的错误,比如模型超参数配置错误、训练和测试集不平衡、由于特征工程逻辑误用导致的数据泄露。

  • 难以扩展 — 由于在这个交接过程中花费了大量时间和精力,因此数据科学家和 ML 工程师团队很难实现大规模扩展。如果组织想要开发更多的 ML 应用程序,他们需要招聘更多的数据科学家和工程师。因此,消除数据科学家和 ML 工程师之间的手动交接可以显著提高团队效率,从而在有限的资源下构建更多的 ML 应用程序。

现在让我们看看 ML 流水线如何简化或甚至消除这一手动交接过程,并弥合开发环境中的笔记本 ML 模型实验和生产环境中的 ML 模型部署之间的差距。最终目标是以快速、自动和安全的方式部署和更新 ML 应用程序。

首先,让我们了解一下什么是 ML 流水线。

什么是 ML 流水线以及 ML 流水线的关键组件

在深入了解 ML 流水线之前,我们首先要理解“流水线”是什么。以下是来自维基百科的定义:

在计算中,流水线,也称为数据流水线,是一组数据处理元素****串联在一起的,其中一个元素的输出是下一个元素的输入。流水线的元素通常并行按时间切片执行。在元素之间通常会插入一些缓冲存储

上述定义中有几个关键组件,这些组件同样适用于 ML 流水线。让我们看看它是如何工作的:

  1. 一组数据处理元素 — 定义的第一部分讨论了一组数据处理元素。这是非常直接的。如果我们将其应用于 ML 管道,则数据处理元素的集合可能包括数据摄取、数据预处理、特征工程、模型训练、模型预测等。对于你的 ML 管道,你可以自定义每个组件,并根据需要在管道中添加多个组件。每个组件执行一个任务,所有任务结合起来形成一个管道。将工作流分解为多个元素对于 ML 应用非常有用。原因是,ML 管道中的每个元素可能需要非常不同的计算资源。为每个元素分配适当的计算资源,而不是为整个工作流使用巨大的计算资源,这为提高计算效率提供了良好的机会。

  2. 连接 — 第二个元素是这些组件是相互连接的。它们不是完全独立运行的。更重要的是,捕捉了上述数据处理元素的依赖关系。在 ML 管道的上下文中,数据摄取步骤必须在运行数据预处理之前完成,或者模型训练步骤必须在模型预测之前完成。

  3. 输入/输出 — 管道中的每个数据处理元素都有输入和输出。通常,一个元素的输出文档作为后续步骤的输入文档使用。这些文档将存储在中央位置,如文档库。

  4. 并行/分时执行 — 定义 ML 管道中每个元素的执行顺序。“并行”意味着两个元素可以同时运行;而“分时执行”则意味着两个元素之间有依赖关系。一个元素必须等到另一个完成后才能执行。

  5. 缓冲存储 — 缓冲存储是我们在输入/输出部分提到的文档库。缓冲存储提供了一个中央位置,用于数据文档,以便下游元素可以使用上游元素的输出、日志和用于监控和跟踪的指标。通过文档库,你可以重用某些元素的输出,而不是在需要重新运行工作流时运行每个元素。这也可以提高管道效率并降低计算成本。此外,你还可以为 ML 元数据设置数据库,包括实验、作业、管道运行和指标。

以下是一个 ML 训练管道可能的示例:

一个示例 ML 模型训练管道 | 作者提供的图像

此示例 ML 训练管道包括 10 个任务。通常,ML 管道是通过 Python SDK 或 YAML 文件定义的。对于每个任务,你可以定义以下配置:

  • 名称 — 任务的名称

  • 描述 — 对任务的描述

  • 输入 — 任务的输入设置

  • 输出 — 任务的输出设置

  • 计算 — 用于执行任务的底层计算资源

  • 参数 — 运行任务时提供的附加参数

  • 需求 — 在计算资源中需要安装的环境依赖关系

  • 任务依赖关系 — 连接的上游和下游任务。

ML 流水线任务的配置 | 图片作者

采用 ML 流水线的好处

与将所有代码放在一个笔记本中相比,将代码模块化为 ML 流水线提供了以下特点:

  • 自包含 — ML 流水线中的每个任务都是一个自包含的处理单元。你可以定义必要的配置和依赖关系,并为每个任务选择最合适的计算资源。

  • 重用 — 因为每个任务都是自包含的,任务输出和相关的元数据都存储在一个中央工件位置。因此,你可以灵活决定在触发流水线重新运行时可以跳过哪些任务。例如,如果你只想重新运行模型训练任务,你可以重用之前任务的输出,如数据处理和特征工程。

  • 模块化代码 — 在流水线中,每个任务都模块化了配置和依赖关系,这与通用的 DevOps 标准一致。这可以减少 ML 工程师所需的代码重写工作,并最小化此代码重写过程带来的潜在错误。

因此,构建 ML 流水线带来了以下好处:

  • 快速迭代 — 一旦 ML 解决方案部署到生产环境中,数据科学家可以在必要时更新流水线,他们的代码更改将按照定义的 DevOps 标准合并并部署。这可以显著减少更新 ML 模型所需的时间,并缩短迭代周期。

  • 完全自动化 — 一旦 ML 解决方案部署到生产环境中,所有代码更改将遵循定义的 DevOps 标准并自动部署。因此,数据科学家和 ML 工程师之间无需手动交接。

  • 生产就绪 — 因为所有代码都模块化在流水线中,数据科学家交付的代码基本上是生产就绪的,重写工作可以非常少。

  • 更好的协作 — ML 流水线的构建方式避免了数据科学家和 ML 工程师之间大量不必要的沟通,因为流水线定义和任务配置包含了 ML 工程师部署流水线所需的几乎所有信息。如果所有代码都在一个大笔记本中,ML 工程师肯定需要更多的沟通来寻求明确性。

  • 大规模执行 — ML 管道的快速迭代和完全自动化特性最小化了在生产环境中操作 ML 解决方案所需的人工努力。因此,相同数量的数据科学家和 ML 工程师能够处理更多的 ML 应用。这使得组织能够在有限的资源下扩展 ML 和 AI 的好处。

ML 管道的好处 | 图片作者

总结一下,我想重申:

笔记本的输出是一个模型;而管道的输出是一段软件。在生产阶段,部署的是ML 系统,而不是ML 模型 因此,ML 管道是弥合 ML 开发环境(以笔记本驱动)和 ML 生产环境(以系统驱动)之间差距的绝佳机制。

实现 ML 管道的开源框架

如果您现在更有动力生成 ML 管道,而不是创建笔记本,下面是您可以利用的一些开源框架的列表:

  • ZenML — 用于统一您的 ML 技术栈的开源 MLOps 框架

  • MLflow Recipes — MLflow 的一个模块,使数据科学家能够快速开发高质量的模型并将其部署到生产环境中

  • Metaflow — 适用于现实世界数据科学和 ML 的框架

  • Kubeflow Pipelines — Kubeflow Pipelines (KFP) 是一个通过使用 Docker 容器来构建和部署可移植且可扩展的机器学习 (ML) 工作流的平台

  • MLRun — 开源 MLOps 编排框架

如果您希望看到我撰写关于构建 ML 管道的实践教程,请在 Medium 上关注我,并留言告诉我您最感兴趣的具体框架。

如果您想查看更多关于现代和高效的数据+AI 技术栈的指南、深入探讨和见解,请订阅我的免费通讯——高效的数据+AI 技术栈,谢谢!

注:如果您还没有成为 Medium 会员,您真的应该成为会员,这样您可以无限制访问 Medium,您可以使用我的推荐链接注册!

非常感谢您的支持!

为什么简单模型往往更好

原文:towardsdatascience.com/why-simple-models-are-often-better-e2428964811a

奥卡姆剃刀在数据科学和机器学习中的重要性

Thomas A DorferTowards Data Science Thomas A Dorfer

·发表于 Towards Data Science ·8 分钟阅读·2023 年 1 月 26 日

--

摄影:Pablo Arroyo 在 Unsplash

在数据科学和机器学习中,简洁是一个重要概念,对模型的性能和可解释性等特性有着显著影响。过度工程化的解决方案往往会通过增加过拟合的可能性、降低计算效率和减少模型输出的透明度,从而对这些特性产生不利影响。

后者对于需要一定解释性领域尤为重要,如医学和医疗保健、金融或法律。无法解释和信任模型的决策,并确保这一决策公平无偏,对那些命运依赖于此的个人可能带来严重后果。

本文旨在强调在实施数据科学或机器学习解决方案时优先考虑简洁性的重要性。我们将首先介绍奥卡姆剃刀的原则,然后深入探讨简洁性的优势,并最终确定何时需要增加复杂性。

奥卡姆剃刀

奥卡姆剃刀,也称为简约法则,是一个哲学问题解决原则,归功于 14 世纪的英语哲学家和神学家威廉·奥卡姆。原始原则通常被引用为Entia non sunt multiplicanda praeter necessitatem,大致翻译为“实体不应超出必要性而被增多”。

摄影:Hair Spies 在 Unsplash

在数据科学和机器学习领域,这通常被解释为“简单模型通常比复杂模型更受青睐”或“在其他条件相同的情况下,最简单的解决方案往往是最好的”。奥卡姆剃刀中的 剃刀 象征着“剃去”不必要的复杂性和假设。

简单解决方案的优势

减少过拟合的敏感性

简单模型的主要优势之一是它们不易过拟合。过拟合发生在模型变得过于复杂并开始拟合训练数据中的噪声,而不是潜在的模式。这通常会导致在未见数据上的表现不佳,结果是模型的泛化能力差,应用性有限。

可以应用几种技术来获得对过拟合敏感性较低的模型:

  • 交叉验证: 模型在训练集上训练,并在独立的验证集上评估其性能。最常见的类型是 k 折交叉验证,其中数据被分成 k 个子集,模型在 k-1 个子集上训练,并最终在剩余的子集(验证集)上进行评估。

  • 数据增强: 较大的数据集往往能减少过拟合。然而,如果没有大数据,可以通过生成人工或合成数据来增强现有数据。具体过程取决于数据的性质。例如,在处理图像数据时,可以通过对图像应用旋转、翻转、缩放等变换来增加数据量。

  • 正则化: 这种技术通过在损失函数中添加惩罚项来限制模型的参数。最常见的技术是 L1(Lasso)正则化和 L2(Ridge)正则化。L1 正则化可以导致一些模型权重被设置为零,从而有效地从模型中移除这些特征,而 L2 正则化仅将权重渐近地减少到零,结果是所有特征都被用来决定模型输出。此外,dropout 是一种常用于神经网络的正则化方法,在每次训练迭代中,随机将一定比例的神经元设置为零。这确保了剩余的神经元学习到更强大的特征,因为它们不再依赖被丢弃的神经元来承担预测的负担。

  • 特征选择: 从训练数据中删除被认为是冗余或不相关的特征将不可避免地简化模型并提高其计算性能。特征可以通过领域知识手动删除,也可以通过单变量过滤、基于树的特征重要性、递归特征消除等方法删除。

  • 降维: 与特征选择类似,降维技术如主成分分析(PCA)、线性判别分析(LDA)或 t 分布随机邻居嵌入(tSNE)将输入维度减少到模型中。然而,它们返回的特征要么是原始特征的线性组合,要么是非线性组合,这会减少模型的可解释性,因为很难确定哪些原始特征对模型的决策做出了贡献。

  • 早停法: 这是应用于神经网络的一种方法,当模型在验证集上的性能开始下降时,停止模型训练。本质上,它通过在模型变得过于复杂之前停止训练来防止过拟合。

  • 减少模型复杂性: 选择一个初始参数较少、架构更简单的模型也可以显著有助于防止过拟合。

提高计算效率

更简单的模型通常能提升计算性能。这种提升主要通过模型需要更少的参数更少的计算更低的内存使用来实现。

在模型部署方面,这也可能带来显著的好处。由于更简单的模型通常具有较低的推理时间和内存使用,因此它们可以更容易地部署到资源受限的设备,如智能手机和智能手表,从而可能建立一个新的客户群体。

例如,在自然语言处理领域,简单的 n-gram 模型已被证明能够与其神经网络模型表现相当,同时速度明显更快。Doval & Gómez‐Rodríguez(2019)比较了递归神经网络和 n-gram 模型在词汇分割任务上的表现,发现 n-gram 模型的精度几乎与神经网络方法相当。此外,n-gram 模型在执行时间上显著优于递归神经网络。

同样,在光学字符识别中,简单的 k 近邻(kNN)算法已被证明在某些任务上具有与卷积神经网络(CNNs)相似的准确度,同时执行时间显著较短。例如,Sharma 等(2022)评估了各种分类器在手写数字识别任务上的性能,发现虽然 CNN 的准确度为 98.83%,但轻量级 kNN 的表现几乎一样好,准确度为 97.83%,同时具有更好的计算性能。

提高可解释性

能够解释模型的决策,并确保这些决策不不公平或有偏见,这是至关重要的,尤其是在那些结果可能对个人造成严重后果的领域。

医学影像领域,例如,医生能够解释、理解并信任他们的模型结果是很重要的。如果放射科医生无法确认或反驳模型对癌症诊断的负面预测,他们可能会要求进一步的检查——一些需要侵入性程序——以确立诊断。如果这些检查最终确认患者没有癌症,可以说,由于模型的不可解释性,患者暴露于不必要的侵入性程序中,而这些程序本可以避免,如果放射科医生能在最初就信任模型的决定。

图片由国家癌症研究所提供,发布在Unsplash

类似地,预测模型的输出可以决定癌症患者是否接受治疗。显然,这些结果对相关个人可能有重大后果,因此模型必须足够简单,以便透明和可信。

信用评分中,一个不透明的模型可能会产生与现实完全脱节的偏见结果。想象一个旨在预测信用 worthiness 的模型,如果它在包含各种偏见的历史数据上训练,就会导致它偏向某些群体。如果一个来自被低估群体的人申请贷款,该模型可能会认为他们是高风险借款人,从而拒绝申请,即使他们实际上有良好的信用记录。这种模型缺乏可解释性可能严重影响那些计划购买房屋、创业或探索需要一些初始资本的机会的个人。

图片由艾蒂安·马丁提供,发布在Unsplash

相同的原则适用于刑事司法领域。想象一个用于预测个人再犯罪概率的模型,这个模型基于个人信息、犯罪历史和各种其他因素。如果该模型在偏见数据上训练,它可能会偏向社会经济地位较高的群体,因此预测其他群体的再犯罪可能性更高。来自低社会经济地位的个人可能因此被拒绝保释并继续拘留,即使实际上他们可能再犯罪的风险非常低。再次强调,使用一个既简单又透明的模型可以减少这些不良结果的概率。

在上述所有类别中,实现更高可解释性的最简单方法是选择更简单的模型。例如,选择基于决策树的算法而不是复杂的神经网络,可以显著提高输出的透明度,因为内部树结构可以轻松可视化。这使得个别决策可以被追溯,从而提供有关特定预测形成过程的关键见解。这使得决策树在预测错误可能产生严重后果的领域(如上述所列)中广泛适用。

复杂性必要时

虽然简单通常更好,但这并非总是如此。一个过于简单的模型可能会遗漏特征与目标变量之间的相关关系,最终导致欠拟合。

在处理具有非线性关系大规模高维数据集时,可能需要更复杂的模型,如神经网络,以捕捉数据中的潜在模式。

某些应用可能还具有高准确性要求,简单模型无法满足。当实施更复杂的模型以提高性能时,结果通常是透明度降低。因此,找到模型性能与可解释性之间的平衡也很重要。例如,在医学影像中,高准确性对于检测异常和做出有关患者生命的决策至关重要。然而,如上所述,同样重要的是医生能够理解、解释和信任这些决策,以建立可靠的诊断程序。

结论

尽管简单模型确实不是所有问题的万灵药,但在需要高可解释性和计算效率时,它们是首选。此外,它们往往能防止过拟合,从而提高模型的泛化能力和适用性。

在某些情况下,确实有必要增加解决方案的复杂性。当处理高维非线性数据或当解决方案需要高度准确性而简单模型难以实现时,这种情况尤为常见。

参考文献

[1] Doval & Gómez‐Rodríguez (2019). “比较神经网络与 N-gram 语言模型进行词语分割*”。* J Assoc Inf Sci Technol, 70(2): 187–197.

[2] Sharma (2022). “识别手写数字的机器学习和深度学习方法*”。* Comput Intell Neurosci, doi: 10.1155/2022/9869948.

为什么 SOLID 设计很重要:避免代码异味并编写可维护的代码

原文:towardsdatascience.com/why-solid-design-matters-avoid-code-smells-and-write-maintainable-code-553b3c6c0ca8

使用 SOLID 原则来设计可读、可扩展和易于维护的软件

Paul KnulstTowards Data Science Paul Knulst

·发布于 Towards Data Science ·阅读时间约 11 分钟·2023 年 1 月 6 日

--

照片由 Markus Spiske / Unsplash 提供

引言

SOLID 是面向对象设计的五个原则的首字母缩略词,这些原则最早由Robert C. Martin在 2000 年代初期提出。这些原则可以帮助开发人员创建更易于维护和扩展的软件系统。五个原则是:

所有五个 SOLID 原则——由作者可视化

在本文中,我想展示 SOLID 原则在构建可读和可维护的软件应用程序中的威力。此外,我还将提供如何使用 TypeScript 应用 SOLID 原则的实际示例。

这些例子将展示如何使用 SOLID 原则来设计和构建更好、更具可扩展性的应用程序。

无论你是开发人员、数据科学家,还是去中心化应用的用户,这些例子都将为你提供应用 SOLID 原则于代码中的宝贵起点。

单一职责原则

每个模块应当有且仅有一个变更的理由。

  • Robert C. Martin

单一职责原则是一个面向对象设计的原则,规定一个类或模块应当只有一个职责或变更的理由。这意味着一个类应该有明确的范围,并且不应承担过多职责。

在 TypeScript 中,单一职责原则可以通过将一个大型复杂类划分为更小且更专注的类来应用,每个类都有其明确的职责。例如,考虑一个负责管理用户账户信息的类,包括他们的个人资料细节、偏好设置和支付信息。这个类可能有许多处理用户账户不同方面的方法,例如更新个人资料、管理偏好设置和处理支付。

为了应用单一职责原则,我们可以将这个大型类拆分为几个较小的类,每个类都有特定的职责。例如,我们可以有一个 UserProfile 类,负责管理用户个人资料信息,一个 UserPreferences 类,负责管理他们的偏好设置,以及一个 UserPayments 类,负责处理他们的支付。每个类都有一个狭义的范围,它们比原来的单体类更容易维护和扩展。

下面是这些类在 TypeScript 中可能实现的示例:

class UserProfile {
  constructor(userId) {
    this.userId = userId;
  }

  loadProfile() {
    // load the user's profile details from the database
  }

  updateProfile(data) {
    // update the user's profile details in the database
  }
}

class UserPreferences {
  constructor(userId) {
    this.userId = userId;
  }

  loadPreferences() {
    // load the user's preferences from the database
  }

  updatePreferences(data) {
    // update the user's preferences in the database
  }
}

class UserPayments {
  constructor(userId) {
    this.userId = userId;
  }

  loadPaymentMethods() {
    // load the user's payment methods from the database
  }

  processPayment(data) {
    // process a payment for the user using the specified payment method
  }
}

在这个示例中,每个类都有一个狭义的职责和一个明确的接口。UserProfile 类负责管理用户个人资料信息,UserPreferences 类负责管理他们的偏好设置,而 UserPayments 类负责处理他们的支付。

这使得理解每个类的目的和行为变得更容易,同时也使得扩展和维护代码变得更加简单。

开放/封闭原则

模块应该对扩展开放,但对修改封闭。

  • 罗伯特·C·马丁

开放/封闭原则是面向对象设计的一项原则,表示一个类应该对扩展开放,但对修改封闭。这意味着该类应该以某种方式设计,以便可以通过继承或组合添加新行为,而无需更改现有代码。

在 TypeScript 中,可以使用抽象类、接口和多态性来应用开放/封闭原则,以创建灵活且可扩展的设计。例如,考虑一个简单的 Shape 类,表示不同的几何形状,如圆形、正方形和三角形。这个类可能有一个 draw() 方法,可以用来在屏幕上渲染任何形状。

为了应用开放/封闭原则,我们可以定义一个抽象的 Shape 类,该类包含所有形状的共享行为,例如 draw() 方法。然后,我们可以为每种特定类型的形状定义子类,例如 CircleSquareTriangle,这些子类继承自 Shape 类并重写 draw() 方法以提供其特定行为。

下面是如何在 TypeScript 中实现这一点的示例:

// define the abstract Shape class
class Shape {
  constructor() {}

  // the draw() method is abstract and must be implemented by subclasses
  draw() {
    throw new Error('The draw() method must be implemented by a subclass.');
  }
}

// define the Circle class, which extends the Shape class
class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  draw() {
    // draw a circle with the specified radius
  }
}

// define the Square class, which extends the Shape class
class Square extends Shape {
  constructor(sideLength) {
    super();
    this.sideLength = sideLength;
  }

  draw() {
    // draw a square with the specified side length
  }
}

// define the Triangle class, which extends the Shape class
class Triangle extends Shape {
  constructor(sideLengths) {
    super();
    this.sideLengths = sideLengths;
  }

  draw() {
    // draw a triangle with the specified side lengths
  }
}

在这个例子中,Shape 类是开放扩展的,因为可以定义新的子类来添加新行为。我们可以定义一个 Rectangle 类,扩展 Shape 类并重写 draw() 方法来提供其特定行为。然而,Shape 类是封闭修改的,因为我们不能在不破坏扩展它的子类的情况下更改 Shape 类中的现有代码。

这使得代码更加灵活和可维护,因为我们可以在不改变现有代码的情况下添加新行为。

里氏替换原则

使用接口/协议来分离可替换的部分。

  • 罗伯特·C·马丁

里氏替换原则是面向对象设计的一个原则,指出程序中的对象应该可以用其子类型的实例来替换,而不会改变程序的正确性。这意味着子类应该能够扩展或重写其父类的行为,而不破坏父类的合同。

换句话说,里氏替换原则是一种确保子类型可以替代其基类型的方式,这意味着任何编写为使用基类型的程序,在使用子类型时应该能够正常运行,而无需修改。

在 TypeScript 中,通过定义一个类并确保子类扩展它,可以应用里氏替换原则。例如,考虑一个简单的 Vehicle 类,它表示不同类型的车辆,如汽车、摩托车或船只。这个 Vehicle 类可能包括 startEngine() 方法,以及所有车辆共有的其他方法或属性。然后,我们可以为每种具体类型的车辆定义子类,如 CarMotorcycle,这些子类扩展了 Vehicle 类,并提供 startEngine() 方法的具体行为。

下面是如何在 TypeScript 中实现这一点的示例:

class Vehicle {
  startEngine() {
    throw new Error('The startEngine() method must be implemented by a subclass.');
  }

  stopEngine() {
    throw new Error('The stopEngine() method must be implemented by a subclass.');
  }
}

class Car extends Vehicle {
  startEngine() {
    console.log("Starting the engine of a car");
  }

  stopEngine() {
    console.log("Stopping the engine of a car");
  }
}

class Motorcycle extends Vehicle {
  startEngine() {
    console.log("Starting the engine of a motorcycle");
  }

  stopEngine() {
    console.log("Stopping the engine of a motorcycle");
  }
}

function useVehicle(vehicle) {
  vehicle.startEngine();
  // Do some other stuff with the vehicle...
  vehicle.stopEngine();
}

const vehicle = new Car();
useVehicle(vehicle);

const vehicle = new Motorcycle();
useVehicle(vehicle);

在这个例子中,CarMotorcycle 类是 Vehicle 类的子类型。useVehicle() 函数期望一个 Vehicle 类型的对象,但由于里氏替换原则,我们可以将 CarMotorcycle 类型的对象传递给这个函数,它将正确工作。

这是因为 CarMotorcycle 可以替代 Vehicle —— 即 CarMotorcycle 类型的对象可以在期望 Vehicle 类型对象的地方使用,而不会改变程序的期望属性。

接口分离原则

客户不应该被迫依赖他们不使用的接口。” — 罗伯特·C·马丁

接口隔离原则是面向对象设计的一项原则,它说明对象的客户端不应被迫依赖于他们不使用的方法。这意味着一个类的接口应该设计成每个接口都服务于特定的目的并被特定的客户端使用。这个原则有助于减少程序中的未使用代码,使程序更加高效和易于维护。

这个原则的动机是避免单一接口的问题,这种接口定义了许多可能对所有客户端不相关的方法。这可能导致客户端代码膨胀且难以维护,因为它被迫实现或模拟未使用的方法以满足接口契约。

为了应用接口隔离原则,最好定义多个小而集中的接口,而不是一个单一的庞大接口。这允许每个客户端只实现必要的方法,而不被迫实现未使用的方法。这可以导致一个更加模块化和易于维护的设计,因为接口与其客户端的需求更紧密地对齐。

在 TypeScript 中,接口隔离原则可以通过为程序中的每种对象类型创建小而具体的接口来实现。

例如,考虑一个有两种类型对象的程序:CatFish。一个鱼对象可能有一个 eat() 方法,而猫对象可能有 eat()move() 方法。为了遵循接口隔离原则,我们可以为 CatFish 创建两个独立的接口,每个接口都有其独特的方法:

interface Eating {
  eat(): void;
}

interface Moving {
  move(): void;
}

class Cat implements Eating, Moving {
  eat() {
    console.log('cat eats');
  }

  move() {
    console.log('cat moves')
  }
}

class Fish implements Eating {
  eat() {
    console.log('fish eats')
  }
}

在这个例子中,定义了 EatingMoving 接口。Eating 接口有一个方法 eat(),而 Moving 接口有一个方法 move()

Cat 类被定义为 EatingMoving 的子类型——也就是说,它实现了这两个接口。这意味着 Cat 类需要具体实现 eat()move() 方法。

Fish 类被定义为 Eating 接口的一个子类型。这意味着 Fish 类只需要实现 eat() 方法。Fish 类提供了该方法的实现,但不需要实现 move() 方法,因为鱼的移动方式与其他动物不同。

接口隔离原则通过将大型、通用的接口拆分为更小、更具体的接口,帮助改善程序的设计。

这可以使程序更加高效并且更容易维护。

依赖倒置原则

实体必须依赖于抽象,而不是具体实现。这说明高层模块不应依赖于低层模块,而应依赖于抽象。

  • 罗伯特·C·马丁

依赖倒置原则是一个软件设计原则,它指出高层模块不应该依赖于低层模块,而是两者都应该依赖于抽象。这个原则有助于提高程序的灵活性和可维护性,使程序的不同部分能够独立演进。

在 TypeScript 中,可以通过定义抽象(接口或抽象类)来应用依赖倒置原则,这些抽象定义了程序不同部分的合同。例如,考虑一个有 Database 类和 UserService 类的程序。UserService 类可能依赖于 Database 类来存储和检索用户数据。为了遵循依赖倒置原则,我们可以定义一个抽象 DataStore 接口,Database 类实现这个接口,并让 UserService 类依赖于 DataStore 接口,而不是直接依赖 Database 类。这样,UserService 类就不会绑定到具体的数据存储实现上,可以与任何实现了 DataStore 接口的类一起使用:

// DataStore interface
interface DataStore {
  get(id: string): any;
  set(id: string, data: any): void;
}

// Database class
class Database implements DataStore {
  // Implementation of DataStore methods
}

// UserService class
class UserService {
  constructor(private dataStore: DataStore) { }

  getUser(id: string) {
    return this.dataStore.get(id);
  }

  saveUser(user: any) {
    this.dataStore.set(user.id, user);
  }
}

在这个例子中,UserService 类依赖于 DataStore 接口,并且可以与任何实现该接口的类一起使用。这使得我们可以在不影响 UserService 类的情况下更改 Database 类的实现,或使用任何实现了 DataStore 接口的不同类。

如果应用依赖倒置原则,程序的设计将通过将高层模块与低层模块解耦,并使两者都依赖于抽象来得到改善。

这可以使程序更具灵活性,更容易维护。

小心!避免常见误解

不幸的是,关于 SOLID 原则存在一些误解,并且这些原则很容易被错误使用。

一个常见的误解是,这些原则应该盲目遵循,而不考虑当前项目的具体需求和限制。但实际上,这些原则是可以帮助开发者创建更具可维护性和可扩展性的指导方针。SOLID 原则应始终谨慎和有判断地应用。

例如,为了提高类的性能或简洁性,单一责任原则有时会被违反。

另一个常见的误解是,SOLID 原则只适用于面向对象的语言,如 Kotlin、TypeScript、Java 或 C++。这是错误的,因为这些原则是与语言无关的,可以应用于任何软件系统,无论使用的是何种编程范式或语言。

记住,SOLID 原则并不是一个能够解决软件设计中每一个问题的灵丹妙药。这些原则只是众多工具中的一个,可以帮助开发者创建更好的软件。

它们应该与其他设计原则和实践一起使用,例如重构、测试和持续集成。

结束语

总之,SOLID 原则是一套指导方针,可以帮助开发人员通过专注于创建单一目的、封装良好且易于修改的类和模块,来创建更具可维护性、可扩展性和易于理解的软件。

通过遵守 SOLID 原则,开发人员可以通过编写更易于维护和随时间适应的代码,从长远来看节省时间和精力。

需要明白的是,这些原则不应在所有情况下严格遵循。它们应该被视为可以根据项目的具体用例以不同方式应用的指导方针。

作为开发人员,以灵活和开放的心态对待 SOLID 原则是很重要的。这样,你可以充分利用这些指导方针,并用它们来创建更好的软件。

此外,SOLID 原则在软件设计中并不是新颖或革命性的想法。这些原则基于几十年的面向对象设计经验和研究,并且建立在许多前辈软件开发人员和研究者的工作基础上。

如果你想深入了解如何开发可读、可扩展和可维护的代码,可以查看这个视频(以及接下来的 5 个视频)。这是 Robert C. Martin 的《Coding Better World Together》的第一课,他推广了 SOLID 原则。

Robert C. Martin 的《Coding Better World Together》的第一课

最后,你对 SOLID 原则有什么看法?另外,你是否对这五个原则中的任何一个有疑问?我很愿意听听你的想法并回答你的问题。请在评论中分享你的想法。

欢迎在我的博客、LinkedIn、Twitter和GitHub上与我联系。

为什么 Taskgroup 和 Timeout 在 Python 3.11 Asyncio 中如此重要

原文:towardsdatascience.com/why-taskgroup-and-timeout-are-so-crucial-in-python-3-11-asyncio-c424bcc88b89

PYTHON CONCURRENCY

拥抱 Python 3.11 中的结构化并发

Peng QianTowards Data Science Peng Qian

·发表于Towards Data Science ·阅读时间 6 分钟·2023 年 4 月 12 日

--

图片由Brett Jordan提供,Unsplash

Python 3.11 asyncio 包的新功能

1. 介绍

在去年的 Python 3.11 版本中,asyncio 包添加了TaskGrouptimeoutAPI。这两个 API 引入了官方的结构化并发功能,帮助我们更好地管理并发任务的生命周期。今天,我将向你介绍这两个 API 的使用以及 Python 引入结构化并发后对我们并发编程的重大改进。

2. TaskGroup

TaskGroup 是使用异步上下文管理器创建的,并且可以通过create_task方法将并发任务添加到组中,如下代码示例所示:

当上下文管理器退出时,它会等待组中的所有任务完成。在等待期间,我们仍然可以向TaskGroup添加新任务。

请注意,假设组中的一个任务在等待时抛出asyncio.CancelledError之外的异常,组中的所有其他任务将被取消。

此外,除了asyncio.CanceledError之外的所有异常将被合并并抛出在ExceptionGroup中。

3. 超时

asyncio.timeout 也是使用异步上下文管理器创建的。它限制了在上下文中并发代码的执行时间。

假设我们需要为单个函数调用设置超时,那么调用asyncio.wait_for就足够了:

但当需要为多个并发调用设置统一的超时时间时,问题就会出现。假设我们有两个并发任务,并希望它们在 8 秒内完成。让我们尝试为每个任务分配一个平均超时时间为 4 秒的设置,代码如下:

你可以看到,尽管我们为每个并发方法设置了平均超时,但这种设置可能会导致不可控的情况,因为对 IO 绑定任务的每次调用无法保证同时返回,我们仍然遇到了TimeoutError

在这时,我们使用asyncio.timeout块来确保为所有并发任务设置一个整体超时:

4. 结构化并发是什么

TaskGroupasyncio.timeout 上述使用了async with特性。就像with结构块一样,可以统一管理资源的生命周期:

但在with块中调用并发任务不起作用,因为并发任务会在背景中继续执行,而with块已经退出,这将导致资源的不当关闭:

因此,我们在这里引入了async with特性。像withasync withTaskGroup 用于统一管理并发代码的生命周期,从而使代码更清晰并节省开发时间。我们称这个特性为我们今天的主角:结构化并发

为什么结构化并发如此重要

1. 并发编程的历史

在并发编程出现之前,我们的代码是串行执行的。代码会根据调用栈的顺序依次执行for_loop循环、if_else条件分支和函数调用。

作者图片

然而,随着代码执行速度对计算效率的要求越来越高,以及计算机硬件的显著发展,逐渐出现了并行编程(CPU 绑定)和并发编程(IO 绑定)。

在协程出现之前,Python 程序员使用线程来实现并发编程。但 Python 的线程存在一个问题,即 GIL(全局解释器锁),GIL 的存在使得基于线程的并发无法达到预期的性能。

因此,asyncio 协程应运而生。没有全局解释器锁(GIL)和线程间切换,并发执行变得更加高效。如果线程是由 CPU 控制的基于时间片的任务切换,那么协程则是将子任务的创建和切换交到程序员自己手中。虽然程序员享受了便利,但也遇到了一系列新问题。

2. 并发编程模型的问题

正如这篇文章中详细描述的,并发编程引发了有关控制流的几个问题。

并发编程是在主线程中打开多个分支进程。这些分支任务在后台默默地执行网络请求、文件访问、数据库查询等工作。

并发编程将把我们的代码流程从这样改变成这样:

作者提供的图片

根据编程中的“低耦合,高内聚”原则,我们都希望在执行后将所有后台任务集中在一个模块中,如下所示:

作者提供的图片

但实际上,由于多个成员开发我们的应用程序或调用了许多第三方组件,我们需要知道哪些任务仍在后台执行,哪些任务已经完成。一个后台任务很可能会分支成几个其他的分支任务。

终极目标是这些分支任务需要被调用者找到,并等待其执行完成,因此变成了这样:

作者提供的图片

虽然这不是漫威的多元宇宙,但目前的情况就像多元宇宙一样,给我们的自然世界带来了绝对的混乱。

有读者表示,asyncio.gather 可以负责合并所有后台任务。但asyncio.gather也有其问题:

  • 它无法以统一的方式集中管理后台任务。通常是在一个地方创建后台任务,而在另一个地方调用asyncio.gather

  • asyncio.gather 接收的参数aws是一个固定列表,这意味着在调用asyncio.gather时,我们已设置后台任务的数量,它们无法在等待过程中随意添加。

  • 当任务在asyncio.gather中等待时抛出异常时,它无法取消正在执行的其他任务,这可能导致一些任务在后台无限运行,程序错误地终止。

因此,Python 3.11 引入的结构化并发特性是解决我们并发问题的绝佳方案。它允许相关的异步代码在同一位置完成执行,同时,它还使tg实例能够作为参数传递给后台任务,从而使在后台任务中创建的新后台任务不会跳出当前异步上下文的生命周期管理。

因此,结构化并发是对 Python asyncio 的革命性改进。

与其他实现结构化并发的库的比较

结构化并发并不是 Python 3.11 中首个此类特性;在 3.11 之前,我们已经有几个并发基础的包很好地实现了这个特性。

1. Trio 中的托儿所

Trio 是 Python 世界中首个提出结构化并发的库,在 Trio 中,API open_nursery 被用来实现这个目标:

2. **create_task_group** 在 Anyio 中

但随着官方 Python asyncio 包的出现,越来越多的第三方包使用 asyncio 实现并发编程。在这一点上,使用 Trio 不可避免地会遇到兼容性问题。

此时,声称兼容 asyncio 和 Trio 的 Anyio 出现了。它还可以通过 create_task_group API 实现结构化并发:

3. 在低版本 Python 中使用 quattro

如果您希望在未来轻松享受 Python 3.11 asyncio 的便利,保持代码本地化,有一个不错的替代方案,quattro,其星标较少,风险较低。

结论

Python 3.11 引入的 TaskGroup 和 timeout APIs 为我们带来了官方的结构化并发功能。

通过结构化并发,我们可以使并发编程代码更好地抽象化,程序员可以更容易地控制后台任务的生命周期,从而提高编程效率并避免错误。

由于经验有限,如果本文中关于并发编程或结构化并发的内容有遗漏,或者您有更好的建议,请发表评论。我会很感激并回答您。

在下一篇文章中,我们将讨论在 asyncio 中使用不同 APIs 的并发编程最佳实践。您可以在这里阅读:

[## 使用这些方法提升你的 Python 并发任务表现]

asyncio.gather、asyncio.as_completed 和 asyncio.wait 的最佳实践

towardsdatascience.com](/use-these-methods-to-make-your-python-concurrent-tasks-perform-better-b693b7a633e1?source=post_page-----c424bcc88b89--------------------------------)

通过 加入 Medium,您将能够无限制地访问我所有的帖子以及其他成千上万作者的帖子。这只需一杯咖啡的价格,对我来说是极大的鼓励。

本文最初发布于:www.dataleadsfuture.com/why-taskgroup-and-timeout-are-so-crucial-in-python-3-11-asyncio/

为什么似乎存在“免费午餐”

原文:towardsdatascience.com/why-there-kind-of-is-free-lunch-56f3d3c4279f

神经科学与人工智能中模式的普遍性

Manuel BrennerTowards Data Science Manuel Brenner

·发表在 Towards Data Science ·11 分钟阅读·2023 年 6 月 27 日

--

**‘没有所谓的免费午餐。’

  • 罗伯特·A·亨莱因

机器学习领域中的“No Free Lunch”定理让我想起了数学世界中的哥德尔不完备性定理。

虽然这些定理经常被引用,但很少有深入的解释,而且对实际应用的影响往往不明确。正如哥德尔定理成为 20 世纪初数学家对完整自洽形式系统信仰的障碍一样,“No Free Lunch”定理挑战了我们对通用机器学习算法有效性的信任。然而,这些定理对日常实际应用的影响通常较小,大多数从业者在这些理论限制下仍能顺利进行工作。

一位机器学习者意识到或许真的存在免费午餐,如 DALL-E 所设想的那样。

在这篇文章中,我想探讨“No Free Lunch”定理的内容,并深入研究它与视觉、迁移学习、神经科学和人工通用智能的关系。

“No Free Lunch” 定理,由 Wolpert 和 Macready 于 1997 年提出,并常用于机器学习的背景中,指出没有一种算法可以普遍适用于所有可能的问题。没有一种神奇的“一刀切”解决方案。一个算法可能在某一任务上表现非常好,但在另一个任务上可能表现不佳。

机器学习的一个基本目标是辨识不同数据中的有意义的模式。然而,算法的有效性通常取决于所处理数据的具体性质。它可能对一种数据类型非常有用,但在应用于另一种数据时效果较差。

当考虑我们可能遇到的不同类型数据时,这一点变得显而易见。像抛硬币这样简单的事情生成了两种模式的简单概率分布。

另一方面,实际中更重要的例子,如图像数据或文本数据,复杂得多。潜在模式的宇宙随着像素数量的增加而呈指数增长,使得一个 500x500 图像中的所有可能配置的数组已经巨大到难以想象:

鉴于这个空间的大小,我们如何仍然能从数据中学习到有意义的东西?

答案的一个重要部分是,大多数感兴趣的样本不是从所有可能样本空间中均匀采样的,而是与随机采样相比,带有大量的预先存在的结构。

语言数据是另一个经典例子。任何给定语言中所有单词的总和包含了一组模式的子集,与所有可能的字母组合集合相比,这些模式处于一个维度更低的空间,这一点在语言的第一个简单统计模型中被使用了(我在我的马尔可夫链文章中对此进行了更深入的探讨)。

对于机器学习者来说,许多类型的输入数据中存在一些重复出现的模式无疑是一个好消息。这意味着我们可以训练模型来学习提取这些模式,并可能将这些模型重新用于不同应用中的类似模式提取。

迁移学习技术明确利用了大多数现实世界问题共享某些共同结构这一事实。

迁移学习使用已经从大型数据集中学习到模式的预训练模型,来适应并**“微调”**到不同但相关的任务。一个训练用于识别图像中的物体的模型可能已经学会了诸如边缘和颜色渐变等低级特征,以及形状等高级特征。这些学到的特征可以应用于相关任务,例如识别手写数字或在医学图像中分类肿瘤。

Hubel 和 Wiesel 通过发现视觉皮层由处理越来越复杂模式的层次组成,从而获得了 1981 年的诺贝尔奖。自卷积神经网络(CNNs)出现以来,他们的工作在机器学习算法中得到了广泛的讨论。

例如,在初级视觉皮层中,简单细胞检测边缘和颜色梯度,而复杂细胞则将这些简单细胞的输出聚合以识别更广泛的模式,如运动或特定形状。深入到视觉系统中,神经元识别的模式变得越来越复杂,从简单的几何形状到面孔和复杂的物体,最终到达那种(不)著名的“奶奶神经元”,它只在你看到奶奶(或你品尝她小时候给你的玛德琳蛋糕时)才会激活。

通过在 ImageNet 数据上训练的卷积神经网络(Inception V1)所学到的特征。从左到右,特征的复杂性逐渐增加。图来自 Olah 等人(2017,CC-BY 4.0)distill.pub/2017/feature-visualization/appendix/

同样,CNN 的各层提取了逐渐复杂的特征。在初始层中,CNN 可能会学习检测简单的结构,如线条、角度和颜色块。随着层次的推进,这些简单特征被组合成更复杂的表示:圆形、矩形,最终是开始看起来像猫、狗或大象的可辨识物体。

也许这些相似之处不应该令人感到惊讶:两个系统都进化并被设计出来,分别用于利用视觉数据中固有的结构。对所有类型的视觉场景中最常出现的模式的理解可以被重新利用或转移,以处理不同但相关的任务,就像之前讨论的那样。

这就是迁移学习的概念和大脑适应及学习能力发挥作用的地方。一个学会识别自行车的孩子,在学习识别摩托车时不会从零开始。他们将自行车的基本结构理解转移到另一个情境中。同样,一个在各种图像上训练的 CNN 在从识别面孔切换到识别手写数字时,不需要重新学习“边缘”的概念。

我们的大脑发现了支撑我在标题中讨论的**‘免费午餐’**的相同真理:对我们有用的世界远非完全随机,而是充满了反复出现的模式和结构。鉴于这些模式是我们感兴趣的内容,我们的大脑被硬编码以选择性地感知它们。

我们甚至可以进一步说,我们的运气不仅仅是因为世界上存在有意义的模式。现代认知神经科学理论认为,大脑更像是一个预测机器,而不是一个‘现实’感知工具。

贝叶斯脑假说例如,建议我们的脑袋不断地对世界进行概率预测,并根据感官输入更新这些预测。因此,我们对世界的感知不是一个被动的过程,而是一个主动的过程。我们不仅仅是感知世界的真实情况,而是根据我们的先验知识积极地解释它,其中一些先验知识基于我们的记忆经验,但一些则回溯到由数十亿年的进化经历所产生的结构性先验。

认知的主动、先验驱动成分对我们的感知有着如此显著的影响,以至于像阿尼尔·塞思这样的科学家将我们大脑的活动称为类似于‘受控幻觉’的东西。

这也与唐纳德·霍夫曼的挑衅性观点(反对现实的观点)相关,该观点认为我们并没有按原样感知现实,而是感知在数百万年的发展过程中成为一种‘用户界面’,其主要目标是帮助我们的生存。例如,计算机桌面上的图标并不揭示机器内部发生的真实情况,而是提供一个使你可以轻松使用计算机的界面。

我们感知的红色苹果并非苹果本身,而是引导我们获得营养食物的表现形式。我们的感知系统因此对我们的感官输入施加了强烈的先验,过滤掉大量不符合我们现有模型的数据或不直接有助于我们生存的数据。这种对显著模式的偏好以及远离感知噪声(如我上面展示的噪声图片中所有可能的视觉模式)反映了我们讨论过的结构,这种结构是迁移学习和**‘某种免费午餐’**在机器学习中的基础。

我们的大脑就像是预训练模型,已经学会只关注世界上某些模式,帮助我们应对不断涌入的新感官输入。

随着年龄的增长,我们更不容易反转我们的模型并在看不见的模式出现时进行微调,更多的是将脑海中的推理机器视为(自我感知的)免费午餐的极致表现:按照这种说法,‘老白人’是一个通过已经牢固建立的世界模型的视角来解释他所遇到的一切的人,这个模型(在他看来)是从每一个学习问题中获得免费午餐的普遍算法。

生成的老白人政治家,傲慢地微笑,因为他认为自己什么都知道。 我发誓它自己生成了英国国旗。

说笑归说笑,尽管在模态内迁移学习至关重要,我们的讨论也很自然地扩展到神经科学和机器学习中的多模态设置的当前发展。

大脑具有惊人的重组能力,称为神经可塑性,这种能力在中风或其他剧烈的感官丧失情况后经常被观察到。

感觉丧失常常导致某些大脑区域的重新利用。例如,盲人常常发展出更敏锐的听觉和触觉。这不仅仅是注意力或练习的结果。研究显示,通常处理视觉信息的视觉皮层在盲人的听觉和触觉任务中也会变得活跃(David Eagleman 的《Livewired》一书对神经可塑性在大脑中扮演的核心角色进行了极好的阐述)。

就好像大脑已经接受了一个在视觉任务上预训练的神经网络,并在发现自己不再接收视觉输入后,将其微调以处理听觉和触觉信息一样。

类似地,研究发现聋人使用的脑区通常与处理语言有关,例如 Broca 区和 Wernicke 区,这些区域在使用手语时也会被激活。这些区域专注于更广泛的沟通,而不论该沟通的具体模态是什么。大脑的语言网络足够灵活,能够处理不同形式的沟通——无论是口语、书面语还是手语。

这些神经可塑性的实例表明,我们大脑的计算是基于一种普遍学习算法,这种算法能够处理各种模式,而不仅仅是那些通常与特定感觉模态相关的模式。这个算法究竟是什么仍有争议(例如,Jeff Hawkins 的《千脑理论》或 Kurzweil 的模式识别器提供了流行的神经科学解释,《The Master Algorithm》一书由 Pedro Domingues 提供了计算机科学的视角),但神经可塑性清楚地表明类似的东西确实存在。

大脑的多功能性也体现在我们最先进的机器学习模型上。虽然变压器最初是为自然语言处理而开发的,反映了语言的归纳偏差,但它们随后被应用于各种数据类型,从图像和音频到时间序列数据(这是否主要是因为当前对它们的炒作还有待讨论)。正如盲人的视觉皮层可以重新用于听觉或触觉任务一样,变压器也可以适应多种数据类型,这暗示了这些模型可以捕捉的普遍模式的存在,不论具体的输入模态(公平地说,这也可能部分因为变压器非常适合在当前计算架构上进行优化,因为它们可以被高度并行化)。

基本变压器架构。Yuening Jia,CC BY-SA 3.0。

引用 Wolpert 和 Macready 的原始 NFL 论文:“任何两个优化算法在其性能在所有可能的问题中平均时都是相等的。”但套用 Orwell 的话,当我们查看真正关心的大多数问题时,一些优化算法比其他算法更为平等。

例如,将 Transformers 应用到图像数据中,产生了像 Vision Transformers 这样的模型,这些模型将图像视为一系列的补丁,并应用相同的自注意力机制。

这个理念在多模态 Transformers 中得到了进一步的体现,这些模型可以同时处理和理解多种类型的数据。像 CLIP 模型这样的文本和图像嵌入被训练来同时理解图像及其相关的文本描述。CLIP 通过将这两种模态结合到一个联合嵌入空间中来学习它们之间的关系。

这些模型利用不同数据类型之间的共同结构,有效地创建了它们之间的“翻译”系统。CLIP 的这一能力的一个显著例子现在大多数人都熟悉:通过为基于扩散的模型如 DALL-E 提供基础,它们使我们能够仅凭文本描述生成令人印象深刻的图片。

这种效果之所以如此良好,也许并非全然巧合:语言中的基本意义单元可能在视觉处理上有对应物,而视觉感知也可能在有限的一组基本视觉模式或‘视觉词汇’的概念下被理解。

在语言中,对人类最有意义的单元,如‘cat’、‘dog’或‘love’,都是短小的,语言的结构与我们在视觉场景中具有重要性的相同概念一致:互联网很可能包含比喧闹的颜色斑块更多的猫的图片。更一般地说,值得注意的是语言已经是一种衍生模态,是一个大型群体共同发展起来的,用以将所有对他们最重要的事物浓缩为简洁的符号表示。

也许最深刻的是,“kind of is free lunch”这一事实还与人工通用智能(AGI)有关,因为 AGI 是一种能够从大多数应用中获得“免费午餐”的算法。

DALL-E 设想中的 AGI。

想象一下 Chat-GPT:尽管 GPT 模型完全基于文本进行训练,但它们开始表现出理解、推理和交流关于世界的能力,远远超出了文本数据的范围。尽管这些模型仅被训练来预测文本数据,它们似乎挖掘出了一种超越文本的普遍逻辑,扩展到不同的模态和理解维度。

尤其是当与其他模态共同训练时,LLMs 超越了对该模态的文本描述,而是理解超越模态的潜在模式,并能从中生成新颖的想法,这类似于大脑的功能。

我认为,人们(包括我自己)对 LLMs 在实现泛化智能方面取得成功感到如此惊讶的原因之一是,我们集体低估了对我们最显著的感知世界在多大程度上已经依赖于模式的普遍性以及我们大脑中相应的学习算法的普遍性。

虽然免费午餐定理在理论上是成立的,但显然大多数让我们最关心的问题在统计意义上远非普遍。相反,它们往往占据一个较低维度的空间,主要关注视觉、语言和声音,这是人类兴趣和显著性的主要领域。

在多模态大型语言模型的出现中,我们拥有一个算法框架,它利用了这些低维和跨模态的表示。它提供了一个激动人心且稍微有些吓人的预览,展示了 AGI 的潜力,以及它可能如何改变我们的世界和对世界的理解。

感谢阅读!

如果你喜欢我的写作,请订阅以通过邮件获取我的故事,或者考虑成为推荐会员。

为什么企业 AI 中的信任与安全(相对来说)很简单

原文:towardsdatascience.com/why-trust-and-safety-in-enterprise-ai-is-relatively-easy-32de240788d0

数据驱动的领导力与职业生涯

为什么传统 AI 比生成式 AI 具有更高的可靠性优势

Cassie KozyrkovTowards Data Science Cassie Kozyrkov

·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 5 月 31 日

--

在本系列的第一部分中,我说了一句我认为自己永远不会说的话:当我们处理典型的企业规模 AI 系统时,信任和安全是容易的。

什么?!亵渎!

听我说。

是的,确实,这实际上很难。但这种难度与新一波生成式 AI带来的信任与安全难题相比显得微不足道。原因如下。

所有图片版权归作者所有。

想象一下,你是一个没有基于 AI 的票价系统的航空公司首席执行官。你的幕僚长气喘吁吁地跑进你的办公室,告诉你组织中的一些数据专家团队距离全面推出 AI 定价系统只有几个小时,但他们被听到说,“我不知道这个 AI 系统的好坏。不知道它能赚多少或亏多少……但它看起来有用,所以我们就启动吧。”

头会滚。这样的系统影响力和潜在商业影响过于巨大,无法容忍这种程度的草率。你很可能会解雇所有与这个完全失控的情况有关的人员,你这样做是对的。毕竟,作为首席执行官,航空公司成功的最终责任落在你身上,鉴于他们几乎把你的企业置于不适当的风险之中,清除这群小丑将是显而易见的。整个情况简直是犯罪般的愚蠢。你的公司没有他们会更好。

不管你怎么说大型组织,但它们有一件事做得不错,那就是避免一切轻率地动摇现状的事情。

通常,这样的问题在到达 CEO 的桌面之前就已经被解决了。不管你对大型组织有何评价,但它们通常擅长避免任何轻率的风险。它们内在地倾向于谨慎而非冒险,这就是为什么企业级人工智能系统通常只有在(1) 可以证明解决一个具体问题非常有效,或(2) 具有可以证明的低潜在危害(因为风险低,错误不会非常尴尬/痛苦,或因为应用的重要性较低)时,才会被推出。

人工智能系统存在的直接性是一个极其强大的简化工具。

从航空公司的角度来看:

(1) 一个人工智能定价系统,该系统经过仔细推出、逐步扩展,并经过统计测试,确保至少能带来*x%*的正面收入影响。

(2) 一个人工智能发音系统,使登机口工作人员可以听到关于如何宣布乘客姓名的数据驱动的最佳猜测,以帮助工作人员在不确定发音时使用。这样的系统几乎不具有关键任务性质,并且它的好处在于能够向世界展示你在做人工智能,而风险很小。此外,当训练有素的人员审批所有输出时,系统的无害性更容易实现,因此你会希望你的登机口工作人员使用他们的判断。

“你想让我怎么发音?”(当人们需要发音我的姓氏 Kozyrkov 时,我常常会收到这种困惑的表情。我告诉他们只需说“咖啡壶”,没有人会注意到。)所有图片版权属于作者。

要点是,除非信任和安全问题已经因应用本身的特性而被最小化,否则企业级人工智能系统在没有证明其潜在收益值得冒险的情况下是不可能获得实际应用的……而在系统的价值不明确时,这种证明是无法获得的。

为什么这使事情变得简单?因为这意味着每个关键任务的传统企业级人工智能系统(第(1)类)往往具有:

  • 一个相对直接的用例声明

  • 对系统期望的“良好行为”的愿景

  • 一个明确的、单一的目标

  • 可衡量的性能

  • 明确定义的测试标准

  • 对可能出现的问题及因此需要的安全网有相对清晰的认识

这里也有很多挑战,比如如何确保这样的系统与所有现有的企业系统兼容(请参见我的 YouTube 课程及更多内容),但系统存在的直接性是一个极其强大的简化工具。

这里的关键见解是,企业级解决方案的经济学往往倾向于规模。旨在大规模部署的系统通常有一个明确的目的,否则聪明的领导者会直接把它们送到垃圾压缩机。这就是为什么过去十年中大多数企业级 AI 系统都是为了在规模上做一件非常具体的事情而设计的。

过去十年中大多数企业级 AI 系统都是为了在规模上做一件非常具体的事情而设计的。

这对信任和安全来说是一个巨大的优势。非常大!当然,确保你的系统在面对“长尾”(即异常用户)时能保护用户仍然需要大量的通用可靠性工作,但保护一个多样化的用户群体免受单一目的、单一功能系统的影响,比保护相同的用户群体免受功能系统的影响要容易得多。从大多数企业的角度来看,生成式 AI 系统从根本上是多功能和多用途的。

这就是关键的见解,所以让我们重复一下:

保护一个多样化的用户群体免受单一目的系统的影响,比保护相同的用户群体免受多功能系统的影响要容易得多。

如果你想更好地理解这个见解,请继续阅读这个系列的第三部分。

另一方面,如果你觉得最后一个见解对你来说显而易见,那么可以跳过第三部分,直接阅读第四部分,我将解释为什么生成式 AI 不具备这些简化特征以及这对 AI 监管意味着什么。

感谢阅读!怎么样,来一门课程?

如果你在这里玩得开心,并且你在寻找一个既能让 AI 初学者和专家都感到愉快的、有趣的领导力导向课程,这是我为你准备的一点小东西:

课程链接:bit.ly/funaicourse

[## 加入 Medium

阅读 Cassie Kozyrkov 的每一篇故事(以及 Medium 上的成千上万其他作家的文章)。你的会员费将直接支持……

kozyrkov.medium.com](https://kozyrkov.medium.com/membership?source=post_page-----32de240788d0--------------------------------)

P.S. 你是否尝试过在 Medium 上多次点击鼓掌按钮看看会发生什么? ❤️

喜欢作者?与 Cassie Kozyrkov 联系

让我们成为朋友吧!你可以在Twitter、YouTube、Substack和LinkedIn找到我。有兴趣让我在你的活动上发言吗?请使用这个表单与我联系。

为什么理解数据生成过程比数据本身更重要

原文:towardsdatascience.com/why-understanding-the-data-generation-process-is-more-important-than-the-data-itself-f1b3b847e662

图片来源:Ryoji Iwata 通过 Unsplash

“为什么”第五章和第六章,读书系列

朱子晶,博士Towards Data Science 朱子晶,博士

·发布于 Towards Data Science ·15 分钟阅读·2023 年 12 月 7 日

--

在婴儿早期阶段,我们的大脑已经学会将相关性与因果性联系起来,并试图为我们周围发生的每一件事找到解释。如果我们身后的车在很长一段时间里做出与我们相同的转弯,我们会认为它在跟随我们,这是一种因果假设。然而,当我们从电影的情节中清醒过来时,我们会认为我们只是正好前往同一个目的地——一个混淆因素。一个共同的原因在两辆车的运动之间引入了相关性。Pearl 给出的这个生动且贴切的例子证明了人脑是如何工作的。

那些我们无法找到合理解释的相关性呢?例如,某些在整个群体中不相关但在住院人群中相关的疾病。如果你回忆一下我上一篇文章讨论的不同因果结构,它指出对碰撞点(住院患者)的条件生成了一种解释效应,使得两个不相关的变量虚假地相关。换句话说,住院人群并不是普通人群的准确代表,从这个样本中得出的任何观察结果都不能推广。

碰撞偏差,作者根据《为什么》一书第六章制作的图像

碰撞引起的相关性对人脑来说不直观,因此产生了这些所谓的悖论。在这篇文章中,我将探讨更多有趣的悖论,这些悖论像魔术一样在我们的大脑中制造视觉错觉,但可以通过因果图进行解释。了解这些悖论的原因是有意义和有教育意义的。这是“和我一起阅读”系列的第 4 篇文章,基于犹大·珍珠的《为何之书》第五章和第六章中的内容。根据珍珠在这两个章节中提供的所有例子,这应该是一次有趣的阅读体验!你可以在这里找到上一篇文章:

  • 开场:从一则猫的故事开始

  • 第一章&2 章:数据告诉我们‘什么’,而我们总是追寻‘为什么’

  • 第三章&4 章:因果图:应对观察数据中的阿基琉斯之踵

由 Glenn Carstens-Peters 在 Unsplash 提供的照片

让我们来做交易

美国电视节目“让我们来做交易”展示了一个著名的统计问题,称为“蒙提霍尔问题”,以节目主持人蒙提·霍尔的名字命名。在这个游戏中,参与者面临三扇门,其中一扇门后面有一个非常抢手的奖品,如汽车,而其他两扇门后面是较不受欢迎的奖品,通常幽默地称为山羊。规则如下:

  • 第一步:参与者选择三扇门中的一扇作为初始选择,比如门 1;

  • 第二步:主持人知道每扇门后面是什么,打开其他两扇门中的一扇,揭示出山羊,比如门 3;

  • 第三步:参与者可以选择坚持原来的选择,或换到剩下的未开门,即参与者可以保留门 1 或选择门 2。

赢得汽车的最佳策略是主持人揭示另一扇门后面的山羊后始终选择换门。如果你想查看详细的计算,可以在本文的第 9 个问题中找到使用贝叶斯统计的计算方法。

## 12 个数据科学面试概率练习题

自信地通过数据科学面试,第三部分

towardsdatascience.com

或者,你可以参考下面的表格,显示了三种情景和切换或不切换的结果,假设参与者选择了门 1。第一行显示汽车在门 1 中,主持人可以打开门 2 或门 3 来揭示山羊。如果参与者切换,他们会失败。在第二行中,主持人必须打开门 3 来揭示山羊,类似地,主持人只能打开门 2 来揭示山羊。由于参与者选择任何门作为初始选择的概率是相等的,通过比较“切换”和“不切换”的列,我们可以看到切换门的获胜概率为 2/3,而不切换的概率为 1/3。因此,切换门是一种更优的策略。

表格由作者根据《为什么的书》第六章制作

这可能看起来有些违反直觉,因为这意味着节目制作人似乎能够猜到参与者的初始选择。切换策略比不切换更受欢迎,这表明参与者的初始选择与汽车的位置有关,而直观地说,这两个变量应该是相互独立的。那么这里的解释是什么呢?我们可以通过这个因果图来解决这个悖论:

图片由作者根据《为什么的书》第六章制作

这个因果图展示了混杂偏倚。节目规则的第 2 步表明,根据汽车的位置和参与者的第一次选择,主持人只会选择打开山羊门。这个规则实际上增加了信息。主持人打开门 3 而不是门 2,使得门 2 更可能有一辆汽车在后面。条件化在混杂因子下,在这种情况下,考虑到主持人打开了门 3,参与者的第一次选择门 1 更不可能是汽车位置,因为切换是更好的策略。

如果我们改变游戏规则,在第 2 步中,主持人可以打开任何门,即使门后面有汽车,那么结果会是什么?下面的表格展示了初始选择门 1 的参与者的所有可能情景:

表格由作者根据《为什么的书》第六章制作

现在,如果我们根据最后两列的结果计算切换或不切换的获胜概率,我们可以看到没有区别。两种策略的获胜概率都是 1/3。结合因果图:

图片由作者根据《为什么的书》第六章制作

在第 2 步中修改规则去除主持人打开的门与汽车位置之间的因果关系。因此,参与者初始选择与节目制作人放置汽车的位置之间的虚假相关性消失了。

在这种情况下,即使数据(主持人打开的门)相同,修改数据生成过程(规则)也会指向不同的策略。

重新审视辛普森悖论

辛普森悖论也是一个广为讨论的著名困惑。当我们观察总体和子总体之间两个变量的不同关系时,我们应该信任哪个结果?例如,如果一种药物对男性组和女性组分别有害,但对整个群体有益,我们是否仍然应批准这种药物?

Brendan Church 的照片,来自 Unsplash

Pearl 认为

辛普森悖论的困惑源于因果原则在统计比例中的不正确应用

通过使用因果图来说明不同的数据生成过程,即使在处理相同的数据时,我们也可能得出截然不同的结论。让我们检查一个旨在减少心脏病风险的药物的例子。

情景 #1: 信任子组

下表比较了治疗组和对照组在两个性别子群体以及整个群体中的心脏病发作率,数据为合成数据。请注意,该研究未进行随机化。

作者根据《为什么的书》第六章制作的表格

通过比较心脏病发作率,我们可以看到该药物对女性和男性子组有害,但对合并组有帮助。我们应该信任哪个结果?要回答这个问题,我们需要使用以下因果图:

作者根据《为什么的书》第六章制作的图像

该图显示性别作为药物和心脏病的混杂因素,如果不对其进行控制,会偏离因果关系。换句话说,在“总计”行中,22%和 18%显示了偏倚的估计。

为了计算药物对整体人群的影响,我们需要估计药物对不同性别组的影响,然后取加权平均。

根据上述数据,我们知道在对照组中,女性和男性的心脏病发作率分别为 5%和 30%。假设女性和男性在一般人群中的比例相等,那么对照组的心脏病发作率为 (5%+30%)*0.5 = 17.5%。

类似地,我们可以得到治疗组的心脏病发作率为 (8%+40%)*0.5 = 24%。总体上,这仍然高于对照组,这与我们在子组中发现的一致。因此,这种药物不应被批准。

情景 #2: 信任聚合

现在,让我们进入另一个情境,数据相同,但这次我们是按患者的血压水平进行分类,而不是按性别分类。

作者根据《为什么的书》第六章制作的表格

在这里,数据揭示了分开和汇总组之间相同的矛盾。然而,不同的因果图改变了我们的结论。这个概念是药物旨在通过降低患者的血压来减少心脏病发作的风险:

图像由作者根据《为什么的书》第六章制作

因果图显示血压作为中介变量而不是混杂变量。回想一下我们在上一篇文章中讨论的内容:控制中介变量或中介变量的代理会完全或部分阻断因果影响。

因此,汇总数据是合适的。我们可以得出结论,药物有益,因为它将治疗组的心脏病风险从 22%降低到 18%,相比对照组。

连续变量中的辛普森悖论

现在,我们已经看到在决定是否按组汇总或分开时,检查数据生成过程的重要性。让我们看看一个应用相同概念于连续变量的示例。

这一悖论最早由弗雷德里克·洛德在 1967 年提出,当时他研究了不同餐厅提供的饮食效果,特别是饮食对女孩和男孩的不同影响。

学生的体重在 9 月份作为初始体重测量,并在接下来的 6 月再次测量以计算增重。结果显示在以下图表中:

图像由作者根据《为什么的书》第六章制作

两个椭圆代表了女孩和男孩的初始体重和最终体重的散点图。图表本身显示了饮食对女孩和男孩的影响存在矛盾的结论:

  • 如果我们关注 45 度线(W_f = W_i),我们可以看到,平均而言,饮食对女孩和男孩没有影响,因为体重分布在 W_f = W_i 处是对称的;

  • 如果我们认为初始体重对最终体重有影响,并关注每个初始体重观察值,如 W0,我们可以看到垂直线在男孩的椭圆上交点比女孩的高。换句话说,平均而言,体重为 W0 的男孩最终体重会比体重相同的女孩更高。

图表显示,男孩在每个初始体重下增重比女孩多。然而,同样明显的是,男孩和女孩都没有增重。这两个结论给学校营养师提供了不同的指导。它们应该做出什么决定?这时以下因果图会派上用场:

图像由作者根据《为什么的书》第六章制作

在研究性别是否影响体重增长时,因果图显示没有反向路径。因此,观察到的汇总数据提供了准确的结论,即饮食对女孩和男孩的体重增长没有影响。如果我们控制初始体重,我们将控制一个实际上阻断了从性别到体重增长部分因果影响的中介变量。

这个结论还证明了整体增益不总是分层特定增益的平均值(确定性原则)。如果每个层次的女孩和男孩数量相同,即每个初始权重相同,那么我们在每个初始权重下观察到的情况可以推广到整个总体。然而,这里明显不是这种情况,因为每个初始权重下女孩和男孩的分布不同。

如果我们在相同的数据中使用不同的叙述呢?我们可以在不同的餐厅提供不同的饮食,并比较在不同餐厅就餐的学生的体重增长,而不是观察性别如何影响体重增长。图表现在变为:

作者基于《为什么》一书第六章制作的图像

我们可以看到,去餐厅 B 的学生初始体重更高。由于数据相同,因此可以得出相同的矛盾结论。然而,因果图已经发生了变化:

作者基于《为什么》一书第六章制作的图像

现在,存在一个指向饮食和体重增长的反向路径。如果我们未能控制初始体重,它已成为一个混杂变量,将偏向饮食和体重增长之间的因果关系。

因此,我们应考虑将初始体重纳入考虑的结论。从图表中,我们可以看到,平均而言,去餐厅 B 的学生体重增长比去餐厅 A 的学生更多。

从以上所有这些例子中,我们可以看到,辛普森悖论不仅仅是我们在现实世界数据集中经常发现的逆转。所谓逆转,是指如果我们观察到 A/B > a/b,并且 C/D > c/d,并不保证(A+C)/(B+D) > (a+c)/(b+d)。这仅仅是算术不等式中的规则。

然而,辛普森悖论超越了数学计算。当面对矛盾结论时,必须彻底分析因果模型,以理解数据生成过程,而不是仅仅依赖观察到的数据。

吸烟与肺癌之间的长期辩论

除了在第六章中提供现实生活中的例子来说明理解数据生成过程的重要性外,Pearl 还在第五章中探讨和关注吸烟与肺癌之间的因果关系,以证明这一观点。

图片由 Volodymyr Hryshchenko 提供,来源于 Unsplash

从关于吸烟是否是致病因素的长期辩论中,我们可以推断出理论是如何发展的,以及因果关系是如何逐渐被提到台前的。

关于吸烟是否引发肺癌的研究因至少这三种因素而充满挑战:

  • 强迫参与者吸烟并使他们暴露于潜在健康风险的 RCT 是不道德的;

  • 观察数据可能存在混杂因素,因为治疗组的分布可能与对照组根本不同。

  • 烟草公司可能采取的潜在障碍和故意努力,以误导公众有关健康风险的信息。

在过去的几十年里,统计学家们进行了多次试验以寻找正确的方法论,即使面临这些障碍。

一项病例对照研究

正如 Pearl 在他的书中总结的那样,这项研究始于一项病例对照研究,该研究比较了肺癌患者与健康志愿者对照组。研究随后确定了“病例”组中的吸烟者是否比对照组中的更多,考虑到一些混杂因素如年龄、性别和环境污染物暴露。

然而,这项研究面临几个缺陷:

  • 一种倒退的逻辑:与其回答吸烟者得癌症的概率是多少,不如展示癌症患者是吸烟者的概率是多少;

  • 回忆偏倚:尽管研究人员确保在调查之前没有让患者知道他们的诊断,但他们可能会根据自己的健康状况推断出这一点;

  • 选择偏倚:这项研究仅关注住院癌症患者,这并不能很好地代表整个群体或甚至吸烟群体。

Cornfield 不等式

当问题出现时,辩论继续,即吸烟者是否是“自我选择”的,他们在基因上是否与非吸烟者不同。Pearl 在他的书中提到的一些担忧是:

他们更具风险倾向或更容易发展出实际上会导致健康不良影响的成瘾行为。

换句话说,辩论的问题在于以下哪种因果图是正确的。“吸烟基因”是否是我们观察到“吸烟”和“肺癌”之间关系的唯一原因?

图片由作者基于《为何的书》第五章绘制

Cornfield 使用简单数学对左侧因果图提出了挑战。如果左侧因果图是正确的,并且在数据中我们观察到吸烟者有九倍的可能性发展为肺癌,这必须是因为他们比非吸烟者有九倍的可能性拥有吸烟基因。

如 Pearl 在书中所述,参照 Cornfield 的论点:

如果 11%的非吸烟者有吸烟基因,那么 99%的吸烟者必须具有这个基因。然而,如果我们观察到 12%的非吸烟者有吸烟基因,这个比例在数学上就变得不可能了。

在这种情况下,吸烟基因无法完全解释吸烟与肺癌之间的关系。因此,正确的因果图提供了更准确的解释。

这个论点以其提出者命名,被称为 Cornfield 不等式,它通过评估在没有因果关系的情况下,替代关系必须有多强来解释观察到的数据,从而揭示了推断的光明。

出生体重悖论

我希望我们已经建立了足够的基础,以便可以声明数据不足以理解因果关系,并解释现实世界中表现为悖论的场景。

Pearl 在他的书中给出的另一个例子是一个研究,专注于寻找吸烟的母亲如何影响她们婴儿的生存率,挑战了吸烟导致死亡率的论点。研究结果显示,吸烟母亲的婴儿确实体重较轻。然而,吸烟母亲的低出生体重婴儿的生存率比非吸烟母亲的婴儿更高。

这个发现可能看起来违反直觉,即吸烟减少了出生体重并降低了儿童死亡率。然而,如果我们进一步考虑除了吸烟之外,哪些因素会影响婴儿的出生体重,我们会发现这里的碰撞偏差。

例如,其他威胁生命的遗传异常也可能解释出生率较低。我们可以通过绘制因果图更清楚地看到这一点:

图片由作者基于《为什么的书》第五章制作

在这里,“吸烟 → 出生体重 ← 出生缺陷”形成了一个碰撞结构。除非我们控制出生体重,否则吸烟与出生缺陷没有相关性。如果我们只看低出生体重的婴儿,我们就打开了吸烟到死亡率的后门路径。这个关系会被出生缺陷到死亡率的因果路径所偏倚。

确实,其他因素可以添加到因果图中,这将指向出生体重。例如,研究发现种族是另一个影响出生体重和儿童死亡率的因素。

列表可以继续,唯一需要记住的是从因果图中得出的行动点。正如 Pearl 在他的书中所说:

我们可以使用这个图表和数据来说服孕妇停止吸烟。但我们不能告诉孕期黑人女性停止做黑人的事情。

最后的说明

碰撞偏差无处不在。你是否注意到从你朋友的经验中,所有他们约会的人中,吸引人的人往往都是混蛋?相较于利用涉及创伤和人格障碍的心理社会理论,Pearl 证明了碰撞偏差可能是答案。你朋友的约会对象选择取决于两个因素:吸引力和个性。在这种情况下,你会约会一个刻薄的、吸引人的人,一个友好的、不吸引人的人,当然还有一个友好的、吸引人的人。然而,你永远不会约会一个刻薄的、不吸引人的人。如下图所示的因果图:

作者基于《为什么的书》第六章的图像

仅仅通过观察你朋友的约会对象,你就会操控一个导致吸引力和个性之间产生虚假负相关的碰撞器。

正如 Pearl 所说,尽管悲哀的事实是,在整个群体中,不吸引人的人和吸引人的人一样刻薄(吸引力和个性没有相关性),但你的朋友们从未意识到这一点,因为约会池中从未出现过既刻薄又不吸引人的人。

这篇文章总结了使用因果图解释的趣闻和悖论。悖论是我们观察到的数据,它们欺骗和困惑我们。然而,通过深入挖掘数据生成过程并绘制因果图,我们可以对这些悖论提供解释,这不仅清除了困惑,还为决策提供了可靠的指导。

这就是我想分享的《为什么的书》第五章和第六章的内容,完成了“伴读系列”的第 4 篇文章。希望这篇文章对你有帮助。如果你还没有阅读前三篇文章,可以在这里查看:

## 伴读:因果关系书籍俱乐部

从一个猫的故事开始……

towardsdatascience.com ## 数据告诉我们“什么”,而我们总是寻找“为什么”

《为什么的书》 第一章和第二章,伴读系列

towardsdatascience.com ## 因果图:面对观察数据中的阿喀琉斯之踵

《为什么的书》 第三章和第四章,伴读系列

towardsdatascience.com ## 你无法踏入同一条河流两次

《为什么的书》 第七章和第八章,伴读系列

towardsdatascience.com ## 什么造就了强大的 AI?

《为什么的书》 第九章和第十章,伴读系列

towardsdatascience.com ## 因果推断在学术界和工业界有何不同?

“《为什么的书》系列”的附加文章

towardsdatascience.com

如果感兴趣,订阅我的邮件列表 以加入目前正在进行的双周讨论。和往常一样,我非常鼓励你阅读、思考,并在这里或在 你自己的博客上分享你的主要收获。

感谢阅读。如果你喜欢这篇文章,别忘了:

  • 查看我最近的文章,关于 数据讲述中的 4Ds:将艺术融入科学;数据科学中的持续学习***;*** 我如何成为数据科学家***;***

  • 查看我的 其他文章 ,涵盖不同主题,如 数据科学面试准备; 因果推断***;***

  • 订阅 我的邮件列表;

  • 注册成为 Medium 会员;

  • 或者关注我的 YouTube 频道 ,观看我最新的 YouTube 视频,介绍我阅读的其他书籍:

参考

《为什么的书》 ,作者:朱迪亚·佩尔

为什么 WGANs 超越 GANs:从 KL 散度到 Wasserstein 损失

原文:towardsdatascience.com/why-wgans-beat-gans-a-journey-from-kl-divergence-to-wasserstein-loss-9ee5faf10b48?source=collection_archive---------11-----------------------#2023-01-12

介绍

Shashank KumarTowards Data Science Shashank Kumar

·

关注 发布于 Towards Data Science ·7 分钟阅读·2023 年 1 月 12 日

--

在 2014 年,Ian Goodfellow 提出了 GAN 的概念,或者我们今天所称的“原始 GAN”。虽然这一想法令人印象深刻,但训练起来却 notoriously 很困难。原始 GAN 面临无法收敛、梯度消失以及模式崩溃等问题。随后,大量的研究集中于解决这些问题。研究人员尝试了不同的模型架构、损失函数和训练方法。一个特别有效的解决方案是 Wasserstein GAN,它在 2017 年由 Arjovsky et al 提出。

图片由 Arnold Francisca 提供,来源于 Unsplash

本文尝试解释为什么 Wasserstein GANs 比普通 GANs 表现更好。它假设读者对 GANs 和 VAEs 的数学原理及其训练过程有一定了解。让我们从生成网络的介绍开始。

1. 生成网络:简要介绍

生成网络努力生成与真实数据家族相似的新样本。它们通过模仿数据分布来实现这一点。像 GANs 和 VAEs 这样的流行框架通过学习一个映射 G 来将已知/假设的分布 Z 转换为实际的分布空间。GANs 中的生成器和 VAEs 中的编码器完成了这一工作。这些网络的神经权重参数化了 G

网络通过最小化实际数据分布和生成数据分布之间的差异来学习映射。量化这种差异的三种常用度量是:

  1. KL 散度

  2. JS 散度

  3. Earth-Mover (EM) 或 Wasserstein-1 距离

让我们来窥探一下它们。

2. 为什么 KL 和 JS 散度会失败?

我们将简要介绍 KL 和 JS 散度,并了解它们为何会失败。

  1. Kullback-Leibler (KL) 散度可以通过正向或逆向两种方式计算,因此是非对称的。根据分布是连续的还是离散的,它们的正向 KL 散度如下:

正向 KL 散度

我们可以通过使用 Q 来加权分布的对数差异,从而计算逆 KL 散度。VAEs 操作于逆 KL。

逆 KL 散度

2. 与前者不同,Jensen-Shannon (JS) 散度是对称的。它本质上是两个 KL 散度的平均值。它在损失函数二元交叉熵中并不明显,但当鉴别器达到最优时,GANs 会基于 JS 散度。我建议你阅读这篇 博客 以了解原因。

JS 散度

Arjovsky 等人用一个简单的例子来展示 KL 和 JS 散度的陷阱。考虑下面图像中的两条平行线所描述的两个概率分布。红色是实际分布(P),绿色是其估计(Q)。θ 是它们之间的水平距离。

作者提供的图片

生成模型将努力将绿色靠近红色,这个红色固定在 0。当 θ = 1 时,你能计算 JS 和 KL 散度吗?

KL 散度

JS 散度

那么,这些度量会随着 θ 的变化而有所不同吗?如果你仔细观察,它们不会改变,除非 θ=0。

我观察到这些度量有两个主要缺点。

  1. 在 θ=0.1 和 θ=1 之间的分布差异是相同的。在 θ=0.1 时,绿线与红线的距离更近,因此差异应该更小。

  2. θ 可以大致看作是估计的分布。如果我们的生成模型由 φ 参数化,那么 θ 是 f(φ) 的一个函数。在反向传播过程中,我们将计算相对于 φ 的损失函数的梯度,该损失函数由上述度量之一定义,以调整参数。第二个方程的第一个项将始终为 0。因此,由于梯度为零,我们的模型将无法学习任何东西。

使用链式法则进行反向传播

从上述示例中,我们可以得出一些重要的推论。

  1. KL 和 JS 散度未考虑两个概率分布之间的水平差异

  2. 在预测和实际分布不重叠的情况下,这种情况在训练过程中很常见,梯度可能为 0,从而导致无法学习。

那么,Wasserstein 距离是如何解决这些问题的呢?

3. 为什么 Wasserstein 距离是一种更好的度量?

坦白说,EM/Wasserstein-1 距离的公式非常糟糕。所以,我将避免其数学细节。相反,让我们直观地理解它。

EM/Wasserstein-1

让我们回到平行线。这次,有四条线。红线构成一个概率分布,蓝线也是如此。顶部的数字是对应点(x=0,1,2,3)的概率质量。我们打算调整蓝线,使其与红线系列对齐(相似分布)。为此,我们将移动概率质量。

图片来源:作者

  1. 从 x=2 移动 0.2 到 x=0

  2. 从 x=3 移动 0.1 到 x=0

  3. 从 x=3 移动剩余的 0.7 到 x=1

然而,这只是众多运输计划中的一个。我们还可以

  1. 从 x=2 移动 0.1 到 x=0

  2. 从 x=2 移动 0.1 到 x=1

  3. 从 x=3 移动 0.2 到 x=0

  4. 从 x=3 移动 0.6 到 x=0

在这两个计划中,哪个更优化?为了确定这一点,我们可以借用物理中的工作类比。在这里,我们将工作定义为质量乘以移动距离。因此,这两个运输方案中的工作量分别为 2.1 和 2.7。

图片来源:作者

2.1,即最优运输计划中涉及的工作量,因此是 EM 距离。相同的原理也适用于连续领域。由于每个点在连续分布中都有一个相关的概率质量,积分取代了求和。

总之,上述复杂的方程涉及计算所有运输方案中所需的工作,以在两个分布之间建立相似性,并选择其中的最小值。与其他度量相比,EM 距离考虑了两个分布之间的水平差异,同时保留了它们的整体形状。此外,它还解决了梯度消失的问题。

重新考虑第一部分中的例子。对齐两个分布的最小成本是 θ,即线条之间的水平距离。因此,即使预测和实际分布不重叠,我们也能获得稳定的梯度来调整参数。

作者提供的图像

4. 使用 Python 可视化所有度量

现在,让我们在 Python 中计算所有讨论的度量,以可视化它们的行为。

import numpy as np
from scipy.stats import norm
from matplotlib import pyplot as plt
from scipy.stats import wasserstein_distance

我们将定义计算 KL 和 JS 发散的函数,幸运的是,对于 EM 距离,我们可以使用 scipy 库。

def kld(d1,d2,eps=10^-6):
    return np.sum(d1*np.log((d1)/(d2)))

def jsd(d1,d2):
    return 0.5 * kld(d1,(d1+d2)/2) + 0.5 * kld(d2,(d1+d2)/2)

接下来,我们将定义两个正态分布,并可视化当高斯分布被分离时度量如何变化。也就是说,它们的均值差异增加。

x = np.arange(-40, 40, 0.001)
q_mean_range = (2,20)
fkl = []
bkl = []
js = []
em = []
p = norm.pdf(x, 0, 2)
for i in range(*q_mean_range):
    q = norm.pdf(x, i, 2)
    fkl.append(kld(p,q))
    bkl.append(kld(q,p))
    js.append(jsd(p,q))
    em.append(wasserstein_distance(p,q))
x = np.arange(*q_mean_range,1)
f,ax = plt.subplots(2,2,figsize=(10,10))
ax = ax.flatten()
for i,(vals,name) in enumerate(zip([fkl,bkl,js,em],['FWD KL','REV KL','JS Divergence','EM Distance'])):
    ax[i].plot(x,vals)
    ax[i].set_xlabel('Mean difference')
    ax[i].set_title(name)
plt.show()

所有度量与均值差异的图

你注意到什么了吗?随着均值差异的增加,KL 发散爆炸,而 JS 发散饱和。然而,EM 距离线性增加。因此,在四者中,EM 距离似乎是训练过程中保持梯度流的最佳选择。

EM/Wasserstein 距离还缓解了模式崩溃。粗略来说,模式崩溃发生在生成器学会生成特定模式,从而欺骗陷入局部极小值的判别器。正如第二部分所讨论的,当判别器处于最小值时,GANs 基于 JS 发散操作,导致梯度为零。因此,判别器被困住,生成器没有动力生成不同的样本。

使用 Wasserstein 距离,判别器可以在梯度不会消失的情况下达到最优。它们可以逃离局部最小值并丢弃生成器输出,促使生成器不会过拟合于特定判别器,从而生成多个模式。

结论

至此,我们结束了这篇文章。希望这总结了为什么 Wasserstein GANs 优于传统的 GANs 和 VAEs。我略过了一些关于 EM 距离的数学细节。如果你感兴趣,需要阅读 论文。你必须注意,讨论的 EM 距离方程是不可处理的。使用了一个数学技巧来近似它。在实际中你不太可能需要它。不过,如果你迫切想知道,可以在 这里 或在 论文 中阅读有关近似的内容。

参考文献

  1. arxiv.org/pdf/1701.07875.pdf

  2. www.alexirpan.com/2017/02/22/wasserstein-gan.html#

  3. lilianweng.github.io/posts/2017-08-20-gan/#:~:text=best%20value%20for,D

为什么你(几乎)不能在家中用 Python 计算圆周率到一亿位

原文:towardsdatascience.com/why-you-almost-cant-calculate-pi-to-a-billion-digits-in-python-at-home-4262a4de9f80

这比你想象的要困难

Bex T.Towards Data Science Bex T.

·发表在数据科学的前沿 ·阅读时间 9 分钟·2023 年 10 月 9 日

--

图片由我使用 Midjourney 生成。

介绍

2022 年 6 月 9 日,谷歌设立了计算圆周率最多位数的新世界纪录——100 万亿位!这一重大成就得益于在谷歌云上运行的 y-cruncher 程序。该程序计算了整整 157 天、23 小时、31 分钟和 7.651 秒。

如果一亿是 10 万倍小于 100 万亿,那么运行时间会相应减少吗?换句话说,是不是只需要 136 秒?

但 136 秒实在太过雄心勃勃。家用电脑远不如谷歌云的强大环境。那么,像 24 小时这样更合理的运行时间如何?

结果发现,在 24 小时内计算即使是一亿位的圆周率也是一个巨大的梦想。本文通过 Python 证据解释了原因。

首先,math.pi有什么问题?

import math

print(math.pi)
3.141592653589793

math.pi的精度为 15 位。虽然这不是很多,但足以进行科学中的最高精度计算。

例如,NASA 的喷气推进实验室(JPL)使用 15 位数字的圆周率来在行星间导航。为了给你一个概念,这种精度足以计算一个半径为 150 亿英里的圆的周长。结果是 940 亿英里的周长误差不超过你小指的宽度。想一想吧!

那么,为什么还要纠结于一亿位,更不用说 100 万亿位了?

好吧,给你一个极客的答案:“因为这实在太酷了!”

如果我们增加小数点精度会怎样?

在我们使用大招(算法!)之前,如果我们在 Python 中增加近似π的小数点精度呢?那会简单得多。

对于这种近似,我们将使用拉马努 jan 的π公式。全球的数学家都用它来极其准确地近似π。

图片由我制作

from decimal import Decimal
from math import sqrt

def pi():
    return Decimal(9801) / (Decimal(1103) * Decimal(sqrt(8)))

pi()
Decimal('3.141592730013305445560728363')

我们使用简单的算术达到了 16 位的精度。让我们将其提高到 1000 位并测量运行时间:

# Increase float precision to 1000
from decimal import getcontext

getcontext().prec = 1000
%%time

approximation = pi()
CPU times: user 14 µs, sys: 2 µs, total: 16 µs
Wall time: 16.2 µs

仅仅花了不到一秒的时间就获得了 1000 位的精度!让我们大胆地将运行时间增加到 100 万:

getcontext().prec = 1_000_000
%%time

approximation = pi()
CPU times: user 3.18 ms, sys: 0 ns, total: 3.18 ms
Wall time: 3.18 ms

仅仅三毫秒——发生了什么?看起来我们可以在瞬间计算出十亿位数字。我们来试试:

# One billion digit precision
getcontext().prec = 1_000_000_000
%%time

approximation = pi()
CPU times: user 3.04 s, sys: 301 ms, total: 3.34 s
Wall time: 3.32 s

甚至不到 10 秒!approximation现在包含了十亿位的圆周率,仅计算了……等等!我们没有将前几十位数字与验证过的圆周率数字进行比较,以确保近似值的准确性。

所以,让我们检查一下:

# I got this from the Internet
verified_pi = Decimal(3.1415926535897932384626433832795028841971)
rounded_pi = round(approximation, 50)

print(verified_pi)
print(rounded_pi)
3.141592653589793115997963468544185161590576171875
3.14159273001330544556072836229812077562268642913720

嗯,看起来我们的近似值仅准确到第 7 位。真让人失望!

那么,刚刚发生了什么问题?

为什么我们得到如此糟糕的结果?拉曼努金的公式不正确吗?当然不是!G.H. 哈代本人认为发现拉曼努金的才华是他最大的数学贡献。

问题在于现代计算机存储浮点数和表示它们的方式。我不会深入探讨这个问题,但建议你参考这个维基百科页面和这个出色的 StackOverflow 讨论。

基本上,我们面临的是以下难题的更复杂版本:

0.1 + 0.1 + 0.10.30000000000000004

由于精度的限制,上述错误可能会积累,特别是对于像圆周率这样的无理数。即使是一个小的舍入误差,也可能在多次操作中积累,完全改变最终结果。

这让我们意识到:使用比例和简单数学的公式无法快速准确地计算圆周率的位数。

大牌或者计算机简史

阿基米德通过增加多边形的边数来近似圆周率,内切和外切于圆。但自那时以来,世界已经走了很长一段路。这个两千年前的方法简单,但它不是为计算机设计的。对不起,阿基米德。

GIF 由我使用 Wolfram 制作。

[## 阿基米德的圆周率近似 - Wolfram 演示项目

显示了一个单位圆。一个正多边形被内切。另一个正多边形被……

demonstrations.wolfram.com](https://demonstrations.wolfram.com/ArchimedesApproximationOfPi/?source=post_page-----4262a4de9f80--------------------------------)

当然,牛顿在他的时代也有所贡献,并且贡献了他的收敛级数来近似圆周率。但他只能计算出 14 位数字。要计算更多位,他需要活得更久,因为每增加一位需要更多的级数项。

然后,拉马努金用他的公式迅速收敛并计算π。我们之前使用的比例是一个简化。对于 20 世纪中期的计算机来说,他的方法的效率几乎是光速。

到 20 世纪末,科学家们使用了更多基于快速收敛系列的算法。例如,Gauss-Legendre 算法的不同变体在 1982 年从 16500 位到 2002 年的 240 亿位之间打破了六项世界纪录。但这些算法对于富有的科学家来说才适用——它们需要大量的计算资源,特别是内存。

所以,21 世纪初,Spigot 算法开始兴起。这些算法可以从左到右顺序计算超越数的位数。这意味着它们需要显著较小的内存,因为它们不需要记住之前的位数来计算下一位。但它们的缺点是比收敛系列要慢。

那么,Google 使用了什么?

由于 Google 可以投入(浪费?)他们想要的资源来创下世界纪录,他们选择了Chudnovsky 算法。这个系列基于拉马努金的公式。它已经创造了七项世界纪录用于计算π。这些记录从 2009 年到 2022 年设立,所有记录都在万亿位数级别。

Chudnovsky 算法。图片来自维基百科。维基媒体共享资源。

在介绍中,我提到 Google 使用了一个名为 y-cruncher 的特殊程序。这个程序可以计算像π这样的万亿位数字。它的理想环境是多核系统,因为它是一个多线程软件。

而且 Google 提供了一个理想的环境!

为了进行 100 万亿位数字的实验,计算引擎具有这些疯狂的规格:

图片来自 Google 的记录公告。

当然,硬件并不是成就成功的唯一因素。要了解更多关于软件架构和细节,请访问Google Cloud页面。

尝试使用 Chudnovsky

为了感受算法的效果,让我们在我的 PC 上尝试 Chudnovsky 来计算前 10k 位。我正在运行一台配有 32GB RAM 的 AMD Ryzen 9 3200x 12 核 CPU 的普通电脑。

首先,让我们编写一个计时器装饰器,可以添加到任何π计算函数中:

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        execution_time = end_time - start_time
        print(f"Execution time: {execution_time} seconds")
        return result

    return wrapper

现在,让我们将 Chudnovsky 放入一个函数中。你不必阅读它,因为这是一个相当复杂的函数(我自己也不理解)。它唯一的作用是近似计算π的n_digits位,并将结果写入文本文件中。

import decimal

@timer
def chudnovsky_to_file(n_digits):
    decimal.getcontext().prec = n_digits + 1
    C = 426880 * decimal.Decimal(10005).sqrt()
    K = 6.0
    M = 1.0
    X = 1
    L = 13591409
    S = L

    for i in range(1, n_digits):
        M = M * (K**3 - 16 * K) / ((i + 1) ** 3)
        L += 545140134
        X *= -262537412640768000
        S += decimal.Decimal(M * L) / X

    pi = C / S

    with open("pi_digits.txt", "w") as file:
        file.write(str(pi) + "\n")

让我们尝试计算前 1000 位:

n_digits = 1000

chudnovsky_to_file(n_digits)
Execution time: 1.8556151390075684 seconds

尽管函数运行时间不到两秒,但它的准确度仅为 13 位小数。为什么?

首先,函数严重未优化。chudnovsky_to_file是一个基础的 Chudnovsky,没有添加任何华丽的技巧或调料。在这里添加它们会破坏 Medium 界面。

此外,原生 Python 在处理大整数或长精度浮点数的某些操作时并不充分准备。例如,导致上述函数问题的是函数体第二行的平方根函数。

要在 Python 中加速 Chudnovsky,请阅读这篇优秀(但痛苦详细)的文章。它实际上在不到 10 分钟内计算了 10 亿位数字。

对于我们来说,让我们尝试一种或多种易于理解且无需过多修改的算法。

尝试 Spigot 算法

除了需要较低的内存资源外,Spigot 算法还有一个独特的优点,那就是可以无限期运行。这意味着,只要你有时间,你可以计算任意精度的超越常数。

这篇优秀的文章列出了许多 Python 中的 Spigot 算法,并按效率顺序排列。对于这篇文章,我们将选择 Gosper 的验证系列:

def gospers_pi():
    q, r, t, n, i = 1, 0, 1, 8, 1
    while True:
        if n == (q * (675 * i - 216) + 125 * r) // (125 * t):
            yield n
            q, r = 10 * q, 10 * r - 10 * n * t
        else:
            q, r, t, i = (
                i * (2 * i - 1) * q,
                3 * (3 * i + 1) * (3 * i + 2) * ((5 * i - 2) * q + r),
                3 * (3 * i + 1) * (3 * i + 2) * t,
                i + 1,
            )
        n = (q * (27 * i - 12) + 5 * r) // (5 * t)

gospers_pi是一个生成器,所以我们将把它包装在另一个函数中,该函数运行到n_digits并将数字写入文本文件:

@timer
def gospers_to_file(n_digits):

    with open("pi_digits.txt", "w") as file:
        pi = gospers_pi()
        for n in range(n_digits + 1):
            if n == 0:  # Put the dot after 3
                file.write(str(next(pi)) + ".")
            else:
                file.write(str(next(pi)))

现在,让我们试试前 1000 位:

n_digits = 1000

gospers_to_file(n_digits)
Execution time: 0.012832880020141602 seconds

这花了不到一秒钟,并且在将最后几位数字与验证过的 1000 位π数字进行比较时,我们可以看到该算法既快速又准确。

现在,我将让函数运行 1 百万位数字,同时对这篇文章进行一些编辑。

结论

当我开始 1 百万位数字实验时,我意识到一个重大问题——我应该每隔 10k 位左右设置一个里程碑检查!

已经过了好几个小时。文章已编辑完成。但函数似乎不会很快停止。我只知道自从开始以来我的 RAM 使用量增加了 1 GB。我会在函数完成(如果有的话)后更新结果。

这仅仅展示了这一点:计算π的位数需要极大的耐心和毅力。从在互联网上寻找合适的算法到呆呆地等待程序退出(并希望最后的数字与互联网上验证的结果相匹配)。

本文还讨论了一些 Python 的明显问题。它对高精度数学的支持有限,而且速度像乌龟一样慢。但如果你足够耐心,比如计算 10 亿位数字的文章,你将获得回报。

感谢阅读!

好了,它终于在一夜之间完成了。

n_digits = 1_000_000

gospers_to_file(n_digits)
Execution time: 20107.50238442421 seconds

2 万秒或接近 6 小时。我检查了最后四位数字与 piday.org 上验证的百万位π数字,结果完全正确!

你可以从这里下载我的百万位数字(文件在 GitHub 上小于 1 MB)。

感谢你坚持到最后!

为什么你需要知识图谱,以及如何构建它

原文:towardsdatascience.com/why-you-need-a-knowledgegraph-and-how-to-build-it-ac4f35cb75b7?source=collection_archive---------0-----------------------#2023-08-09

从关系数据库迁移到图数据库的指南

Stan PugsleyTowards Data Science Stan Pugsley

·

关注 发表在 Towards Data Science ·7 分钟阅读·2023 年 8 月 9 日

--

TLDR: 知识图谱通过图数据库组织事件、人物、资源和文档,以进行高级分析。本文将解释知识图谱的目的,并向你展示如何将关系数据模型转换为图模型,将数据加载到图数据库中,并编写一些示例图查询。

为什么需要知识图谱?

关系数据库非常适合创建列表,但在管理各种实体的网络方面却很糟糕。你是否尝试过用关系数据库完成以下任务?

  • 分析一个医疗护理事件,当一个患者与许多人、地点和程序互动时

  • 在涉及到金融欺诈时,通过供应商、客户和交易类型的网络找到模式

  • 优化供应链的依赖关系和互联元素

这些都是事件、人员和资源网络的示例,这些网络给使用关系数据库的 SQL 分析师带来了巨大的头痛。随着网络规模的增加,关系数据库的速度会呈指数级下降,而图数据库则具有相对线性的关系。如果你在管理一个活动和事物的网络或网络,一个图数据库是正确的选择。未来,我们应该期待看到企业数据组采用关系数据库进行孤立分析,以及知识图谱处理跨功能的复杂网络过程的组合。

基于图数据库技术的知识图谱,是为了处理多样的过程和实体网络而构建的。在知识图谱中,你会有代表人员、事件、地点、资源、文档等的节点。你还有表示节点之间链接的关系(边)。这些关系在数据库中物理存储,并具有名称和方向。并非所有的图数据库都是知识图谱。要被视为知识图谱,设计必须嵌入业务 语义模型,通过清晰的业务名称反映在多个业务功能跨越的节点集合中。你实际上是在创建一个无缝的网络,连接所有交互的业务部分,并使用业务语义将数据紧密地与其代表的过程联系起来。这可以作为未来生成型 LLM 模型使用的基础

为了在知识图谱中展示多样的数据集,我们来看一个关于供应链物流的简单示例。业务流程可能被建模为如下:

供应链图数据库模型。图像由作者提供。

该模型可以扩展到包括业务流程的任何相关部分:客户退货、发票、原材料、制造过程、员工,甚至客户评论。没有预定义的模式,因此模型可以在任何方向或深度上扩展。

从关系模型到维度模型再到图模型

现在,让我们通过将一个典型的关系数据库模型转换为图模型的过程,使用电子商务供应商的场景。假设该供应商正在进行一系列数字营销活动,在其网站上接收订单,并向客户发货。关系模型可能如下所示:

电子商务关系数据库模型。图像由作者提供。

如果我们将其转换为数据仓库中的维度模型,该模型可能如下所示:

电子商务维度模型(数据仓库)。图片由作者提供。

请注意,事实表集中于事件,而维度表表示将业务实体的所有属性组合成一个表。这种以事件为中心的设计提供了更快的查询时间,但也带来了其他问题。每个事件都是一个独立的事实表,从一个事件到相关事件的连接很难看出。当这些关系被分割在多个事实表之间时,没有简单的方法来理解维度实体(如产品)与另一个维度中的实体(如承运人)之间共享的所有事件的关系。维度模型专注于一次一个事件,但掩盖了不同事件之间的连接。

图形模型通过将过程建模为如下方式,解决了显示实体间相互关系的问题:

电子商务图形数据库模型。图片由作者提供。

首次查看,这个图形模型与关系模型的相似性多于与维度模型的相似性,但它可以用于与数据仓库相同的分析目的。注意每个关系都有名称和方向。而且,可以在任何节点之间创建关系——事件与事件、人际与人际、文档与事件等。图形查询还允许你以 SQL 无法实现的方式遍历图形。

例如,你可以收集与关键事件相关的任何节点,并研究其出现模式。层次结构得到保留,每个层级可以单独引用,这不同于非规范化的维度表。最重要的是,图形在建模业务中的任何事件或实体时更具灵活性,不需要遵循严格的模式约束。图形设计旨在匹配业务的语义模型。

提取、转换和加载(ETL)

现在让我们看一个示例关系数据库表,并创建一些示例脚本以提取、转换和加载数据到图形数据库中。在这篇文章中,我将使用由Neo4j提供的 Cypher 语言,Neo4j 是最受欢迎的商业图形数据库。但这些概念也适用于其他变体的图形查询语言(GQL)。我们将使用以下示例产品表:

产品表。图片由作者提供。

使用这个查询,我们可以提取过去 24 小时内更新的新产品:

SELECT product_id,
  product_name,
  cost_usd,
  product_status
FROM Product
WHERE last_updated_date > current_date -1;

我们可以将这些结果提取到一个名为“df”的Python Pandas数据框中,打开图形数据库连接,然后使用此脚本将数据框合并到图形中。

UNWIND $df as row
MERGE INTO (p:Product {product_id: row.product_id})
SET p.product_name = row.product_name,
  p.cost_usd = row.cost_usd,
  p.product_status= row.product_status,
  p.last_updated_date = datetime();

第一行引用了一个参数“df”,这是来自 Pandas 的数据框。我们将合并到节点类型“Product”中,该节点通过别名“P”进行引用。然后,“product_id”部分用于绑定到节点中的唯一标识符。之后,Merge 语句看起来类似于 SQL 中的合并。

在使用如上所述的合并语句创建每个节点后,我们创建关系。关系可以在同一脚本中创建,也可以在后处理脚本中使用像这样的合并命令创建:

MATCH (p:Product), (o:Order)
WHERE p.product_id = o.order_id
MERGE (o)-[:CONTAINS]->(p);

Match 语句看起来像 Oracle 中的传统连接用法,在 Match 之后声明两个节点类型,然后在 Where 子句中进行连接。

图模型上的查询

假设我们已经构建了图,现在想要查询它。我们可以使用类似这样的查询来查看来自亚利桑那州的广告组推动的订单。

MATCH (ag:AdGroup)<-[:BELONGS_TO]-(a:Ad)-[:DRIVES]->(o:Order)<-[:PLACES]-(c:Customer)
WHERE c.state = 'AZ'
RETURN ag.group_name,
  COUNT(o) as order_count

这个查询将返回广告组名称和订单数量,筛选自亚利桑那州。请注意,Cypher 中不需要 Group By 子句,与 SQL 不同。从该查询中,我们将收到以下示例输出:

图查询的示例结果。图片由作者提供。

这个例子可能看起来微不足道,因为你可以很容易地在关系数据库或数据仓库中使用订单事实表创建类似的查询。但让我们考虑一个更复杂的查询。假设你想查看从广告活动启动到可归因的交付被接收所需的时间。在数据仓库中,这个查询将跨越事实表(不是一项简单的任务)并且需要大量资源。在关系数据库中,这个查询将涉及一系列长连接。在图数据库中,这个查询看起来如下:

MATCH (cp:Campaign) )<-[:BELONGS_TO]-(ag:AdGroup)<-[:BELONGS_TO]-(a:Ad)
MATCH (a)-[:DRIVES]->(o:Order)<-[:FULFILLS]-(d:Delivery)
RETURN cp.campaign_name,
  cp.start_date as campaign_launch_date,
  MAX(d.receive_date) as last_delivery_date

我使用了一个示例查询路径,但用户可以采取多种路径来回答不同的业务问题。在查询中,请注意从 Campaign 到 Delivery 的路径通过 Order 和 Delivery 之间的关系。此外,为了提高可读性,我将路径拆分为两部分,第二行以 Ad 的别名开始。查询的输出结果如下所示:

图查询的示例结果。图片由作者提供。

结论

我们已经查看了一些将电子商务业务流程从关系模型转换为图模型的示例步骤,但在这篇文章中无法涵盖所有设计原则。希望你已经看到,图数据库要求的技术水平与关系数据库大致相同,并且迁移并不是一个巨大障碍。

最大的挑战是让你的大脑摆脱传统的关系建模技术,转而以语义或业务建模的方式进行思考。如果你发现图形技术有潜在应用,试着通过一个概念验证项目进行尝试。知识图谱的分析可能性远远超出了你在二维表格中能做到的!

所有图片均由作者提供

为什么你需要在 Python 中使用装饰器来编写 DRY 代码

原文:towardsdatascience.com/why-you-need-to-write-dry-code-with-decorators-in-python-3930ea23f569

数据

使用装饰器来查看你的数据在 Pandas 处理管道中的变化

Byron DolonTowards Data Science Byron Dolon

· 发表在 Towards Data Science · 9 分钟阅读 · 2023 年 5 月 19 日

装饰是关键 — 图片由 Adi Goldstein 提供,来源于 Unsplash

DRY 代码是高效编程的关键之一。

DRY 代表“不要重复自己”,无论是为个人项目编写代码还是为大规模代码库编写代码,这一点都很重要。这意味着你应该拥抱 DRY 代码的概念,创建模块化的解决方案,而不是将逻辑从一个文件复制到另一个文件,或从一个函数复制到另一个函数。

在 Python 中使用装饰器可以帮助实现这一点。简而言之,装饰器允许你操作现有函数并增加其功能。装饰器可以在许多不同的场景中派上用场,但在本文中,我们将探讨如何使用装饰器来改进 Pandas 数据处理管道。

装饰器可以帮助我们理解数据处理管道中每个阶段的情况,这对于处理大规模数据时非常有用。你可以使用它们来跟踪数据的变化,随着对初始 DataFrame 应用不同的转换,这可能会变得难以管理。

随意在你自己的笔记本中跟进。你可以从 Kaggle 这里 下载数据集,CC0 1.0 通用公共领域献身许可下免费使用。

如果你打算跟进,请确保运行以下代码来导入所有必要的库:

import logging
import functools
import time
from datetime import datetime

logging.basicConfig(level=logging.INFO)

Pandas 中的标准数据处理管道

在深入讨论装饰器之前,让我们先看看 Pandas 中现有的数据处理管道,并快速了解它的功能。

FILE_PATH = "Updated_sales.csv"
CHUNK_SIZE = 1000

def read_raw_data(file_path: str, chunk_size: int=1000) -> DataFrame:
    csv_reader = pd.read_csv(file_path, chunksize=chunk_size)
    processed_chunks = []

    for chunk in csv_reader:
        chunk = chunk.loc[chunk["Order ID"] != "Order ID"].dropna()
        processed_chunks.append(chunk)

    return pd.concat(processed_chunks, axis=0)

def split_purchase_address(df_to_process: DataFrame) -> DataFrame:
    df_address_split = df_to_process["Purchase Address"].str.split(",", n=3, expand=True)
    df_address_split.columns = ["Street Name", "City", "State and Postal Code"]

    df_state_postal_split = (
        df_address_split["State and Postal Code"]
        .str.strip()
        .str.split(" ", n=2, expand=True)
    )
    df_state_postal_split.columns = ["State Code", "Postal Code"]

    return pd.concat([df_to_process, df_address_split, df_state_postal_split], axis=1)

def extract_product_pack_information(df_to_process: DataFrame) -> DataFrame:
    df_to_process["Pack Information"] = df_to_process["Product"].str.extract(r'.*\((.*)\).*').fillna("Not Pack")

    return df_to_process

def one_hot_encode_product_column(df_to_process: DataFrame) -> DataFrame:    
    return pd.get_dummies(df_to_process, columns=["Product"])

def process_raw_data():
    df = read_raw_data(file_path=FILE_PATH, chunk_size=CHUNK_SIZE)

    return (
        df
        .pipe(split_purchase_address)
        .pipe(extract_product_pack_information)
        .pipe(one_hot_encode_product_column)
    )

final_df = process_raw_data()

初始管道的输出 — 作者提供的图片

在这个管道中,本质上有一个“main”函数,它调用几个其他的读取和处理函数,以输出一个准备好用于训练机器学习模型的 DataFrame。这是一个只有三个预处理步骤的简单示例,但你可以通过查看process_raw_data函数来了解它是如何工作的,具体如下:

  1. 使用自定义read_raw_data函数按指定的块大小读取原始数据;

  2. 在结果 DataFrame 对象上调用 Pandas 的pipe方法以实现方法链,这涉及将自定义数据预处理函数按顺序传递给pipe方法。

在这种情况下,我们有三个主要任务要处理我们的数据,这些任务被封装在三个函数中:split_purchase_addressextract_product_pack_informationone_hot_encode_product_columns。所有这些函数以不同的方式转换初始 DataFrame,并使原始数据在进行机器学习时真正可用。

然而,如果在管道的每个连续步骤中很难看到发生了什么。因为我写了代码,我知道每次调用pipe方法时 DataFrame 正在发生什么。但如果基础数据发生变化或其他人想知道发生了什么,返回一些以日志形式呈现的数据会很有用!

实现日志有很多不同的方法,但让我们看看 Python 中的装饰器如何帮助我们了解数据的情况。

什么是装饰器?

因此,我们的目标是理解 DataFrame 在管道的多个阶段处理时发生了什么。解决方案是为我们定义的函数添加一个包装层,以便增加额外的功能,返回有关 DataFrame 的信息等。做到这一点意味着我们需要定义装饰器函数。

这比在每个函数中添加相同类型的日志行更高效。特别是在大规模处理时,想象一下需要在十几个不同的数据处理函数或散布在几个不同文件中的函数中复制粘贴相同的三条日志消息。

从事“元编程”要高效得多,其中我们定义装饰器函数,其目标是操控现有代码。理想情况下,装饰器函数应该做的就是接受一个函数作为输入,并运行该函数并返回它通常的结果,但在函数上添加一些额外的功能。

定义你可以用来处理数据的装饰器

记录函数的执行时间

对于装饰器的初步示例,让我们从简单地记录每个函数的开始时间以及计算函数运行时间开始。

def log_execution_and_time(function):
    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        logging.info(f"{datetime.fromtimestamp(start_time)} - Start execution of: {function.__name__}")
        result = function(*args, **kwargs)
        end_time = time.time()
        logging.info(f"{datetime.fromtimestamp(end_time)} - Ended execution of: {function.__name__}")
        logging.info(f"Duration of {function.__name__}: {round(end_time - start_time, 2)} seconds")
        return result
    return wrapper

有很多内容需要详细了解,所以让我们看看这里发生了什么。我们首先定义了我们的装饰器函数log_execution_and_time。由于装饰器应该接受一个函数作为输入,我们还定义了一个function参数。

如果我们在不使用functools.wraps这一行的情况下使用函数,我们将无法保留现有函数的元数据并在日志中使用它。总是记得在你的装饰器中包含这一行,否则它们将不会按你想要的方式行为。例如,原函数名将不会在包装器中保留(例如,装饰器无法识别read_raw_data函数名是read_raw_data)。你可以在Python 文档中查看一个简单的例子。

接下来,我们定义包装器来实际实现我们想要的附加功能。在这种情况下,我们想要跟踪函数运行所需的时间。这样做很简单:我们只需使用time库将当前时间分配给start_timeend_time变量,然后取这两者之间的差值以获得函数的持续时间。你会看到在我们定义这些变量的中间,我们运行了函数。执行此操作的行是:

result = function(*args, **kwargs)

这就像我们之前读取原始数据时所使用的语法一样:

df = read_raw_data(file_path=FILE_PATH, chunk_size=CHUNK_SIZE)

wrapperfunction中定义argskwargs参数的原因是为了允许我们通过装饰器传递的函数接受任意数量的参数和关键字参数。

在这个函数中,我们还使用logging.info语法写了一些日志行。我们还使用function.__name__来提取日志消息中的实际函数名称。

最后,在包装器函数内部,我们返回先前定义的result,这是原函数执行的结果。在我们的数据处理管道中,这将始终是一个 Pandas DataFrame。在我们的装饰器函数中,我们返回wrapper函数,其中包括我们对现有函数的附加功能。

记录 DataFrame 元数据

既然我们已经了解了装饰器的工作原理,让我们来看看如何在日志中返回关于 DataFrame 的元数据的信息。

def log_dataframe_metadata(function):
    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        result = function(*args, **kwargs)
        logging.info(f"{function.__name__} - result DataFrame number of rows: {len(result)}")
        logging.info(f"{function.__name__} - result DataFrame number of columns: {len(result.columns)}")
        for column, memory in result.memory_usage(deep=True).iteritems():
            logging.info(f"{function.__name__} - {column} memory usage: {memory}")
        return result
    return wrapper

装饰器语法与之前相同。相反,我们首先返回函数的结果,然后基于结果 DataFrame 定义日志消息。在这里,你可以看到你可以使用装饰器对函数的结果执行操作。

在这种情况下,我们想要查看 DataFrame 中的三项内容:

  1. 行数:len(result)

  2. 列数:len(result.columns)

  3. 每列的内存使用量:result.memory_usage(deep=True)

在这里,记住 result 在这种情况下始终是一个 Pandas DataFrame,这意味着我们可以在其上调用 Pandas 方法。根据你定义的函数来转换 DataFrame,这些方法中的每一个都可能会很有用。

例如,如果你向 DataFrame 添加了新列,你可以通过检查列数来验证列是否已添加。此外,如果你只是添加了额外的列,DataFrame 中不应添加任何行,你也可以对其进行合理性检查。

最后,监控每一列的内存使用情况是有用的,以确保你在一些预定义的限制范围内。为了方便,我们的日志装饰器处理 result.memory_usage 的输出,这是一系列包含每列信息的数据,因此我们可以遍历此系列,并以易于阅读的方式返回每列使用的内存。

向我们的管道添加装饰器

现在我们已经定义了装饰器,将它们应用到我们的管道中非常简单。我们之前的初始代码可以像这样修改,以添加我们在装饰器函数中定义的功能。

@log_execution_and_time
@log_dataframe_metadata
def read_raw_data(file_path: str, chunk_size: int=1000) -> DataFrame:
    csv_reader = pd.read_csv(file_path, chunksize=chunk_size)
    processed_chunks = []

    for chunk in csv_reader:
        chunk = chunk.loc[chunk["Order ID"] != "Order ID"].dropna()
        processed_chunks.append(chunk)

    return pd.concat(processed_chunks, axis=0)

@log_execution_and_time
@log_dataframe_metadata
def split_purchase_address(df_to_process: DataFrame) -> DataFrame:
    df_address_split = df_to_process["Purchase Address"].str.split(",", n=3, expand=True)
    df_address_split.columns = ["Street Name", "City", "State and Postal Code"]

    df_state_postal_split = (
        df_address_split["State and Postal Code"]
        .str.strip()
        .str.split(" ", n=2, expand=True)
    )
    df_state_postal_split.columns = ["State Code", "Postal Code"]

    return pd.concat([df_to_process, df_address_split, df_state_postal_split], axis=1)

@log_execution_and_time
@log_dataframe_metadata
def extract_product_pack_information(df_to_process: DataFrame) -> DataFrame:
    df_to_process["Pack Information"] = df_to_process["Product"].str.extract(r'.*\((.*)\).*').fillna("Not Pack")

    return df_to_process

@log_execution_and_time
@log_dataframe_metadata
def one_hot_encode_product_column(df_to_process: DataFrame) -> DataFrame:    
    return pd.get_dummies(df_to_process, columns=["Product"])

@log_execution_and_time
@log_dataframe_metadata
def process_raw_data():
    df = read_raw_data(file_path=FILE_PATH, chunk_size=CHUNK_SIZE)

    return (
        df
        .pipe(split_purchase_address)
        .pipe(extract_product_pack_information)
        .pipe(one_hot_encode_product_column)
    )

final_df = process_raw_data()

带有日志装饰器的数据处理管道输出 — 作者图片

我们需要做的就是在函数定义的顶部添加 @log_execution_and_time@log_dataframe_metadata 以实现装饰器功能。然后,当我们以相同的方式运行代码时,我们会额外得到每个函数运行时的日志输出。

现在,用户可以看到 DataFrame 在处理管道中移动时发生了什么。结果 DataFrame 与之前完全相同。

最终的 DataFrame 没有改变 — 作者图片

就这些!这只是你可以用装饰器做的许多事情的开始。你可以通过更多的详细日志、向装饰器添加可选参数以使其在不同函数中表现不同,甚至让它们根据需要编辑每个 DataFrame 的结果(如删除空值)等来进一步改善你的数据处理管道。

感谢你抽出时间阅读这篇文章!如果你喜欢我的内容,我希望你通过下面的推荐链接注册 Medium。这将使我获得你月度订阅的一部分,并且你将获得仅限 Medium 会员的独家功能。如果你已经在关注我,感谢你的支持!

## 使用我的推荐链接加入 Medium — Byron Dolon

作为 Medium 会员,你的一部分会员费会流向你阅读的作者,你可以完全访问每一个故事……

byrondolon.medium.com

更多内容- 5 个实用的技巧供有志数据分析师参考 - 掌握电商数据分析 - 在 Pandas 数据框中检查子字符串 - 学习 Python 的 7 个最佳 GitHub 仓库 - 用 Pandas 理解数据的 5(又半)行代码

为什么作为数据科学家你应该考虑使用 Fortran

原文:towardsdatascience.com/why-you-should-consider-using-fortran-as-a-data-scientist-5511e05ef89

探讨 Fortran 可以为数据科学和机器学习带来的好处

Egor HowellTowards Data Science Egor Howell

·发表于Towards Data Science ·阅读时间 8 分钟·2023 年 5 月 16 日

--

由Federica Galli拍摄,来自Unsplash。

背景

Python 被广泛认为是数据科学的金标准语言,与数据科学相关的所有包、文献和资源总是可以在 Python 中找到。这并不一定是坏事,因为这意味着对于你可能遇到的任何数据相关问题,已经有大量记录的解决方案。

然而,随着数据集的增大和模型复杂度的提升,也许是时候探索其他语言了。这时,老牌语言Fortran可能会再次流行。因此,今天的数据科学家们了解它并尝试实现一些解决方案是非常值得的。

什么是 Fortran?

Fortran,意为“公式翻译器”,是 1950 年代首个被广泛使用的编程语言。尽管其年代久远,它仍然是高性能计算语言,并且比 C 和 C++都要快。

最初为科学家和工程师设计,以运行如流体力学和有机化学等领域的大规模模型和仿真,Fortran 至今仍被物理学家频繁使用。我在物理学本科期间也学过它!

它的专长在于建模和仿真,这对包括机器学习在内的众多领域至关重要。因此,Fortran 非常适合解决数据科学问题,因为这正是几十年前它被发明出来的目的。

优势与劣势

Fortran 相对于其他编程语言如 C++和 Python 有几个关键优势。以下是一些主要点:

  • 易于阅读:Fortran 是一种紧凑的语言,只有五种原生数据类型:INTEGER、REAL、COMPLEX、LOGICAL 和 CHARACTER。这种简洁性使其易于阅读和理解,特别是在科学应用中。

  • 高性能:Fortran 通常用于基准测试高性能计算机的速度。

  • 大型库:Fortran 有广泛的库,主要用于科学目的。这些库为开发人员提供了大量的函数和工具,用于执行复杂的计算和模拟。

  • 历史数组支持:Fortran 从一开始就支持多维数组,这对于机器学习和数据科学如神经网络至关重要。

  • 为工程师和科学家设计:Fortran 专门为纯数值计算而构建,这与 C/C++和 Python 的通用用途不同。

然而,这并非全是美好如画。以下是 Fortran 的一些缺点:

  • 文本操作:不适合字符和文本处理,因此在自然语言处理方面并不理想。

  • Python 有更多的包:尽管 Fortran 有很多库,但远不及 Python 中的总数。

  • 小社区:Fortran 语言的关注度不如其他语言。这意味着它没有大量的 IDE 和插件支持,也没有很多的 Stack Overflow 回答!

  • 不适合许多应用程序:它显然是一种科学语言,所以不要试图用它来建立网站!

设置 Fortran

Homebrew

让我们快速了解一下如何在你的计算机上安装 Fortran。首先,你应该安装Homebrew (链接在这里),它是 MacOS 的一个包管理器。

要安装 Homebrew,只需运行其网站上的命令:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

你可以通过运行命令brew help来验证 Homebrew 是否已安装。如果没有错误,那么 Homebrew 已经成功安装在你的系统上。

GCC 编译器

由于 Fortran 是一个编译语言,我们需要一个能够编译 Fortran 源代码的编译器。不幸的是,MacOS 并不自带 Fortran 编译器,因此我们需要自行安装一个。

一个受欢迎的选项是 GCC(GNU 编译器集合)编译器,你可以通过 Homebrew 安装:brew install gcc。GCC 编译器是一个支持 C、Go 以及当然还有 Fortran 的编译器集合。GCC 组中的 Fortran 编译器叫做 gfortran,它可以编译所有主要版本的 Fortran,如 77、90、95、2003 和 2008。建议使用 .f90 扩展名来命名 Fortran 代码文件,尽管 关于这个话题有些讨论。

为了验证 gfortran 和 GCC 是否成功安装,运行命令 which fortran。输出应该如下所示:

/opt/homebrew/bin/gfortran

gfortran 编译器迄今为止是最受欢迎的,然而还有其他几个编译器。可以在 这里 找到一个列表。

集成开发环境(IDE)与文本编辑器

一旦我们有了 Fortran 编译器,下一步是选择一个集成开发环境(IDE)或文本编辑器来编写 Fortran 源代码。这是个人偏好的问题,因为有许多选项可供选择。个人而言,我使用 PyCharm 并安装 Fortran 插件,因为我不喜欢使用多个 IDE。其他由 Fortran 网站 推荐的流行文本编辑器包括 Sublime Text、Notepad++ 和 Emacs。

运行程序

在我们进入第一个程序之前,重要的是要注意本文不会进行语法或命令的教程。这里的链接 是一个简短的指南,将涵盖所有基本语法。

以下是一个名为 example.f90 的简单程序:

GitHub Gist 由作者提供。

我们的编译过程如下:

gfortran -o example example.f90 

这个命令编译代码并创建一个名为 example 的可执行文件。你可以用任何你喜欢的名字替换 example。如果你没有使用 -o 标志指定名称,编译器将使用一个默认名称,通常对于大多数 Unix 系统来说是 a.out

运行 example 可执行文件的方法如下:

./example

./ 前缀用于指示可执行文件位于当前目录中。此命令的输出将如下所示:

 Hello world
           1

现在,让我们解决一个更“实际”的问题!

性能示例:背包问题

概述

背包问题 是一个著名的 组合优化 问题,其核心是:

一组项目,每个项目都有一个值和重量,必须被打包进一个背包中,以最大化总值,同时遵守背包的重量限制。

尽管问题听起来很简单,但解决方案的数量随着物品数量的增加而呈指数增长。因此,使得在超出一定数量的物品时,通过暴力破解方式解决不可解的

启发式 方法如遗传算法可以用来在合理的时间内找到一个“足够好”或“近似”的解决方案。如果你有兴趣了解如何使用遗传算法解决胶囊问题,可以查看我之前的帖子:

## 遗传算法与胶囊问题:初学者指南

通过实践遗传算法,逐步学习如何解决胶囊问题

pub.towardsai.net

胶囊问题在数据科学和运筹学中有广泛的应用,包括库存管理和供应链效率,因此高效解决这个问题对业务决策非常重要。

在这一部分,我们将看到 Fortran 如何通过纯暴力破解方式快速解决胶囊问题,并与 Python 进行比较。

注意:我们将专注于基本版本,即0–1 胶囊问题,其中每个物品要么完全放入背包,要么完全不放入背包。

Python

让我们从 Python 开始。

以下代码使用暴力破解方法解决 22 个物品的胶囊问题。每个物品在一个 22 元素的数组中编码为 0(不在)或 1(在)(每个元素代表一个物品)。由于每个物品只有 2 种可能的值,总的组合数量是2^(num_items)。我们利用itertools.product方法计算所有可能解决方案的笛卡尔积,然后进行迭代。

作者的 GitHub Gist。

这段代码的输出:

Items in best solution:
Item 1: weight=10, value=10
Item 6: weight=60, value=68
Item 7: weight=70, value=75
Item 8: weight=80, value=58
Item 17: weight=170, value=200
Item 19: weight=190, value=300
Item 21: weight=210, value=400
Total value: 1111
Time taken: 13.78832197189331 seconds

Fortran

现在,让我们用相同的变量来解决相同的问题,但用 Fortran 来实现。与 Python 不同,Fortran 没有执行排列和组合操作的包。

我们的方法是使用模运算运算符将迭代次数转换为二进制表示。例如,如果迭代次数是 6,那么 6 除以 2 的模是 0,这意味着第一个项目未被选择。然后我们将迭代次数除以 2 以将位向右移动,再次取模以获取下一个项目的二进制表示。这个过程对每个项目(即 22 次)重复,最终得到每一个可能的组合。

作者提供的 GitHub Gist。

使用 linux [time](https://linuxize.com/post/linux-time-command/) 命令:

time gfortran -o brute brute_force.f90
time ./brute

输出:

 Items in best solution:
 Item:           1 Weight:          10 Value:          10
 Item:           6 Weight:          60 Value:          68
 Item:           7 Weight:          70 Value:          75
 Item:           8 Weight:          80 Value:          58
 Item:          17 Weight:         170 Value:         200
 Item:          19 Weight:         190 Value:         300
 Item:          21 Weight:         210 Value:         400
 Best value found:         1111
./brute  0.26s user 0.01s system 41% cpu 0.645 total

Fortran 代码速度约为 Python 的 21 倍!

比较

为了获得更直观的比较,我们可以将执行时间绘制为项目数量的函数:

由作者在 Python 中生成的图表。

Fortran 的性能超越了 Python!

尽管 Fortran 的计算时间会增加,但增长幅度远远小于 Python。这确实展示了 Fortran 在解决优化问题时的计算能力,而优化问题在许多数据科学领域至关重要。

总结与进一步思考

尽管 Python 一直是数据科学的首选,但像 Fortran 这样的语言仍然能提供显著的价值,特别是在处理优化问题时,因其固有的数值计算能力。它在解决背包问题的暴力破解中优于 Python,随着问题中项目的增加,性能差距进一步扩大。因此,作为数据科学家,如果你需要在计算能力上获得优势以解决业务和行业问题,你可能需要考虑将时间投入到 Fortran 中。

本文中使用的完整代码可以在我的 GitHub 上找到:

[## Medium-Articles/Optimisation/knapsack 在主分支 · egorhowell/Medium-Articles

你此时无法执行该操作。你在另一个标签页或窗口中登录。你在另一个标签页或窗口中注销…

github.com](https://github.com/egorhowell/Medium-Articles/tree/main/Optimisation/knapsack?source=post_page-----5511e05ef89--------------------------------)

另一个事项!

我有一个免费的通讯,Dishing the Data,我在其中分享每周成为更好数据科学家的技巧。

[## Dishing The Data | Egor Howell | Substack

如何成为更好的数据科学家。点击阅读《Dishing The Data》,作者Egor Howell,Substack 发表的…

newsletter.egorhowell.com

与我联系!

  • YouTube

  • LinkedIn

  • Twitter

  • GitHub

参考资料及进一步阅读

  • 优化算法. Mykel J. Kochenderfer 和 Tim A. Wheeler。2019

  • Fortran 网站: fortran-lang.org/en/

为什么你应该在地理空间开发中使用 DevContainers

原文:towardsdatascience.com/why-you-should-use-devcontainers-for-your-geospatial-development-600f42c7b7e1

了解使用 DevContainers 和 Codespaces 在跨平台和设备上进行无缝地理空间开发的优势

Maurício CordeiroTowards Data Science Maurício Cordeiro

·发表于Towards Data Science ·阅读时间 5 分钟·2023 年 3 月 31 日

--

图像由 Midjourney 创建。说明:“世界被电缆包围,在一个杂乱的环境中,赛博朋克,真实的 3D”

由于 Medium.com 政策的变动(自 2023 年 9 月实施),这篇文章现在可以在geocorner.net上免费获取:www.geocorner.net/post/why-you-should-use-devcontainers-for-your-geospatial-development

介绍

在我最近的一篇文章中,发表于 TDS(使用 Python 配置用于空间分析的最小 Docker 镜像),我展示了如何配置一个包含 GDAL 和 XArray 等基本地理空间分析工具的 Python Docker 镜像。管理包依赖可能很具挑战性,尤其是当涉及到专门的地理空间库时。在这方面,Docker 容器提供了一个优雅的解决方案,用于将系统部署到云服务器。但是,开发过程本身呢?在这篇文章中,我将探讨改变我对开发环境管理看法的关键因素,以及 DevContainers 和 Codespaces 如何改造地理空间开发工作流。

理由

1- 跨平台环境一致性的需求

图像由 Midjourney 创建 - 说明:“一位程序员在使用不同年代的多台计算机工作”

我正在开发一个新的软件包,用于从巴西空间研究机构(INPE)提取气候数据。上周我不得不在以前的 Windows 笔记本电脑上运行这个软件包,以进行一些性能评估。这次经历突显了跨平台管理依赖项和配置的挑战。这个新软件包需要支持 GRIB2 数据格式,这在国家气象中心、天气机构和研究人员中很常见。然而,在 Windows 上安装必要的依赖项证明是麻烦的,最终导致我的整个开发环境崩溃。这让我花了一整天时间修复 Windows 环境,更糟糕的是,由于每个平台上可用的软件包版本不同,我不得不绕过代码中的语法差异。这次事件强调了拥有一致的开发环境以避免这些麻烦的重要性。

2- 地理空间软件包的复杂性

地理空间软件包本质上是科学软件。因此,它们通常起源于学术界,由研究人员或实习生开发,这些人可能缺乏适当的软件工程专业知识。这些问题在之前的一篇文章中已经讨论过:7 个原因说明科学软件设计不佳。

因此,这些软件包常常缺乏支持、稳健性和适当的软件工程设计。这种复杂性在尝试设置和维护开发环境时会带来额外的障碍,特别是在将这些软件包与其他工具和库集成时。

更糟的是,这些软件包有些建立在较旧的软件和命令行工具之上,这些工具使用不同的语言编写,要使它们正常工作不仅需要安装 Python 软件包本身,还需要单独的安装程序和特定的环境配置。举一个例子,cfgrib 需要 eccodes 库来正确编码/解码 GRIB2 文件格式。直接取自 eccodes 文档:

来源:eccodes 文档 (confluence.ecmwf.int/display/ECC/ecCodes+installation)

3- 从任何设备访问代码

照片由 Firmbee.com 提供,在 Unsplash 上发布

另一个促使我重新考虑开发环境管理的情况是,当我需要从妻子的电脑访问我的代码时。我知道,这确实是个紧急情况,但有时当你没有笔记本电脑时这种情况是可能发生的。虽然我的所有代码都托管在 GitHub 上,但在她的机器上安装所有所需的开发工具来完成如此小的任务是不现实的。这种挫败感促使我探索 DevContainersCodespaces 作为潜在的解决方案。

解决方案

无缝开发的 DevContainers

DevContainers 是 Visual Studio Code 在 2019 年推出的一项功能,允许开发者在不同的机器之间标准化开发环境。通过利用 Docker 容器的强大功能,DevContainers 封装了依赖项和配置,确保无论本地系统如何都能提供一致的体验。

简而言之,开发者可以将 VSCode 连接到一个容器,并在利用容器化环境的同时保持高质量的开发体验。

GitHub Codespaces

使用 DevContainers,设置一个 GitHub Codespace 来进行你的项目变得轻而易举。GitHub Codespaces 提供了一个功能齐全的、基于云的 VSCode 环境,支持扩展和各种开发工具(图 1)。你甚至可以在其中运行 Jupyter notebooks、绘制地图,并从几乎任何带有浏览器的设备上访问它。

截至 2022 年 11 月 9 日,GitHub Codespaces 每月提供 120 小时的免费核心时间和 15GB 的存储空间。这不足以进行全职编码,但在主要开发机器不可用时,它是一个宝贵的资源,用于紧急编码情况。

图 1:运行在 GitHub Codespace 内的基于云的 VSCode。

结论

虽然 DevContainers 提供了许多优点,但也有一些潜在的缺点需要注意。一个这样的缺点是需要在开发机器上安装 Docker。对于不熟悉 Docker 的开发者来说,这一要求可能会引入学习曲线并增加设置过程的复杂性。

此外,运行 Docker 容器可能会消耗大量系统资源,这可能导致在硬件能力有限的机器上出现性能问题。在决定是否采用 DevContainers 进行地理空间开发时,重要的是要考虑这些缺点。

你呢?你是否曾经使用过 DevContainers 或 Codespaces?你遇到了什么其他的优点或缺点?请在评论中留下你的想法。

感谢阅读,下篇文章见。

保持联系

如果你喜欢这篇文章并希望支持我作为作家,考虑成为一名 Medium 会员。每月仅需 5 美元,我将从你的会员费中获得少量佣金,对你没有额外费用。或者你也可以随时 请我喝咖啡

[## 通过我的推荐链接加入 Medium - Maurício Cordeiro

阅读每一个来自Maurício Cordeiro的故事(以及 Medium 上的成千上万其他作者的故事)。你的会员费用将直接…

cordmaur.medium.com](http://cordmaur.medium.com/membership?source=post_page-----600f42c7b7e1--------------------------------)

为什么你的数据管道需要闭环反馈控制

原文:towardsdatascience.com/why-your-data-pipelines-need-closed-loop-feedback-control-76e28e3565f?source=collection_archive---------3-----------------------#2023-09-10

公司和云的复杂性现实要求新的控制和自主水平,以在规模上实现业务目标

Jeff ChouTowards Data Science Jeff Chou

·

关注 发表在Towards Data Science ·9 分钟阅读·2023 年 9 月 10 日

--

图片由Cosmin Paduraru提供

随着数据团队在云上规模化,数据平台团队需要确保他们负责的工作负载达到业务目标, 这是我们 Sync 的主要任务。在拥有数十名数据工程师和数百个生产作业的大规模环境下,由于技术和人为因素的种种原因,控制其性能变得不可行。

今天缺失的环节是建立一个 闭环反馈系统,它可以自动驱动管道基础设施向业务目标前进。这句话有些冗长,所以让我们深入探讨一下这个问题。

今天数据平台团队面临的问题

数据平台团队必须管理从管理层到工程师的基本不同的利益相关者。这两组团队往往有相互对立的目标,平台经理可能会受到双方的挤压。

我们与平台经理和数据工程师的许多实际对话通常是这样的:

“我们的 CEO 希望我降低云成本,并确保我们的服务水平协议得到满足,以保持客户满意。”

好的,那么问题是什么?

“问题是我实际上不能直接更改任何东西,我需要其他人帮助,这就是瓶颈。”

所以基本上,平台团队在实际实施改进时感到束手无策,面临巨大的摩擦。让我们深入探讨一下原因。

是什么阻碍了平台团队?

  • 数据团队超出了技术范围 — 调整集群或复杂配置(Databricks、Snowflake)是一项耗时的任务,数据团队更愿意集中精力在实际管道和 SQL 代码上。许多工程师没有相应的技能、支持结构,甚至不了解他们工作的成本。识别和解决根本原因问题也是一项令人生畏的任务,这阻碍了建立一个功能正常的管道。

  • 过多的抽象层 — 让我们聚焦于一个堆栈:Databricks 运行其自己版本的 Apache Spark,该 Spark 运行在云提供商的虚拟计算上(AWS、Azure、GCP),配有不同的网络选项和不同的存储选项(DBFS、S3、Blob),而且所有这些都可以在一年中的任何时候独立和随机更新。选项的数量令人不堪重负,平台人员难以确保一切都是最新和最优的。

  • 遗留代码 — 一个不幸的现实就是遗留代码。公司内部的团队往往会发生变化,人来人往,随着时间的推移,任何一个特定工作所需的知识可能会逐渐消失。这种现象使得调整或优化特定工作变得更加困难。

  • 变化令人恐惧 — 变革本能上是令人害怕的。如果一个生产任务正在进行,我们是否愿意冒险进行调整?老话说得好:“如果它没坏,就不要修。”这种恐惧往往是现实的,如果一个任务不是幂等的或存在其他下游影响,任务失败会带来真正的麻烦。这种心理障碍阻碍了对任务性能的改善尝试。

  • 在规模上,有太多作业 — 通常平台经理负责监督数百甚至数千个生产作业。未来公司的增长确保这个数字只会增加。鉴于上述所有情况,即使你有一个本地专家,逐个调整作业也是不切实际的。虽然这对少数几个高优先级的作业可能有效,但它使公司的大部分工作负载或多或少被忽视了。

显然,对于数据平台团队来说,要快速提高系统在规模上的效率是一场艰难的战斗。我们相信解决方案在于管道构建方式的范式转变。管道需要一个闭环控制系统,它不断推动管道朝着业务目标前进,而无需人工干预。让我们深入探讨一下。

闭环反馈控制对于管道意味着什么?

当前的管道属于“开环”系统,即作业只是运行而没有任何反馈。为了说明我说的内容,下面的图片展示了“作业 1”每天运行,每次运行的成本为$50。假设业务目标是将该作业的成本降至$30。那么,除非有人实际采取行动,否则成本将会在可预见的未来保持在$50,如成本与时间的图表所示。

作者提供的图片

如果我们有一个系统,实际反馈作业的输出统计数据,以便第二天的部署可以得到改进,会是什么样的呢?它可能会像这样:

作者提供的图片

你看到的是一个经典反馈循环,在这种情况下,期望的“设定点”是$30 的成本。由于这个任务每天运行,我们可以将实际成本的反馈发送到一个“更新配置”模块,该模块接收成本差异(在此例中为$20),从而对“作业 1”的配置进行更改。例如,“更新配置”模块可能会减少 Databricks 集群中的节点数量。

这在生产环境中看起来是什么样的?

实际上,这并不是一蹴而就的。“更新配置”模型现在负责调整基础设施,尝试将成本降低到$30。正如你所想的,随着时间的推移,系统将会改进,并最终达到期望的$30 成本,如下图所示。

作者提供的图片

这一切听起来可能很美好,但你可能会疑惑,“这个神奇的‘更新配置’模块是什么?”这就是问题的关键。这个模块是一个数学模型,它可以输入一个数值目标差异,并输出一个基础设施配置或代码更改。

这不是一件容易的事情,并且会根据目标(例如成本 vs. 运行时间 vs. 利用率)而有所不同。这个模型必须基本预测基础设施变更对业务目标的影响 —— 这并不容易。

没有人可以预测未来

一个微妙的事情是,“更新配置”模型并不是 100%准确的。在第四个蓝点中,您实际上可以看到成本在某一点上上升。这是因为模型试图预测一次配置变更将降低成本,但由于没有什么能够 100%准确预测,有时它在局部上可能是错误的,因此成本可能在单次运行中上升,而系统正在“训练”。

但是,随着时间的推移,我们可以看到总成本实际上是在下降的。您可以将其视为一个智能的试错过程,因为以 100%准确度预测配置变更的影响是完全不可能的。

“那么,有什么关系?” — 设定任何目标并行动

上述方法是一种通用策略,而不仅仅是节省成本。上述的“设定点”只是数据平台人员设定的一个目标。它可以是任何可测量的目标,例如运行时间就是一个很好的例子。

假设我们希望一个工作在 1 小时(或 SLA)内完成。我们可以让上述系统调整配置,直到达到 SLA。或者更复杂一点,同时考虑成本和 SLA 目标?完全没有问题,系统可以优化以达到您在多个参数上的目标。除了成本和运行时间外,其他业务用例目标还包括:

  • 资源利用率: 独立于成本和运行时间,我是否正确地利用了我的资源?

  • 能源效率: 我是否尽可能少地消耗资源,以减少碳足迹?

  • 容错性: 我的工作实际上是否对故障具有弹性?这意味着我是否需要过度规格化它,以防我被抢占,或者以防没有可用的 SPOT 实例?

  • 可扩展性: 我的工作是否具备可扩展性?如果输入数据增加 10 倍,我的工作会崩溃吗?

  • 延迟: 我的工作是否达到了延迟目标?响应时间目标?

理论上,一个数据平台的人只需设定目标,然后一个自动系统可以迭代地改进基础设施,直到达到目标。没有人参与其中,没有工程师需要上手。平台团队只需设定他们从利益相关者那里收到的目标。听起来像是一个梦想。

到目前为止,我们一直比较抽象。让我们深入探讨一些具体的使用案例,希望能够引起人们的兴趣:

示例功能 #1:按业务目标对作业进行分组

假设您是一个数据平台经理,负责监督数百个生产作业的运行。目前,它们都有自己的成本和运行时间。下面的简单图表显示了一个卡通示例,基本上所有作业都随机分布在成本和运行时间图表上。

如果你想在大规模上降低成本怎么办?如果你想一次性改变许多作业的运行时间(或 SLA)怎么办?现在,你可能得请求工程师帮助你修改所有作业(祝好运)。

作者提供的图片

现在假设你对所有作业实施了上述闭环控制系统。你只需设定作业的高级业务目标(在此案例中为 SLA 运行时间要求),反馈控制系统将尽力找到实现这些目标的基础设施。最终状态将是这样的:

作者提供的图片

在这里,我们看到每个作业的颜色代表了不同的业务目标,依据 SLA 定义。幕后闭环反馈控制系统改变了集群/仓库的大小、各种配置,甚至调整了整个管道,以尽可能低的成本达到 SLA 运行时间目标。通常,较长的作业运行时间会导致更低的成本机会。

示例功能 #2:自愈作业

大多数数据平台的工作人员可以确认,他们的数据管道总是在变化。两个非常流行的场景是:数据规模随时间增长和代码变更。这两者都会导致成本和运行时间出现不稳定的行为。

以下插图展示了基本概念。我们从左到右走一遍示例:

  • 开始: 假设你有一个作业,并且随着时间的推移数据规模增长。通常情况下,你的集群保持不变,因此成本和运行时间都会增加。

  • 开始反馈: 随着时间的推移,运行时间接近 SLA 要求,反馈控制系统在绿色箭头处介入。此时,控制系统会调整集群,以保持在红色虚线下,同时最小化成本。

  • 代码变更: 在某个时点,开发者推送了一个新的代码更新,导致成本和运行时间的激增。反馈控制系统介入,并调整集群以更好地适应新的代码变更。

作者提供的图片

希望这两个示例说明了闭环控制管道的潜在好处。当然,实际上还有许多细节被省略了,一些设计原则公司必须遵循。

一个重要的方面是配置能够在出现问题时恢复到之前的状态。此时,一个幂等的管道将非常理想,以防需要许多迭代。

结论

数据管道是复杂的系统,像其他复杂系统一样,它们需要反馈和控制来确保稳定的性能。这不仅有助于解决技术或业务问题,还能显著释放数据平台和工程团队的精力,使他们能专注于实际构建管道。

正如我们之前提到的,这在很大程度上依赖于“更新配置”模块的性能。这是反馈循环成功所需的关键情报。构建这个模块并非易事,也是目前主要的技术障碍。它可以是一个算法或机器学习模型,并利用历史数据。这是我们过去几年中一直在致力于的主要技术组件。

在我们下一篇文章中,我们将展示该系统在 Databricks Jobs 中的实际应用,让你相信我们讨论的内容是真实的!

对了解更多关于 Databricks 流水线的闭环控制感兴趣?请联系 Jeff Chou 和其他 Sync Team成员。

相关内容

  • Databricks 的 Photon 和 Graviton 实例值得投资吗?

  • Databricks 的自动扩展是否具备成本效益?

  • 介绍 Gradient — 简化 Databricks 优化

为什么你的 RAG 在生产环境中不可靠

原文:towardsdatascience.com/why-your-rag-is-not-reliable-in-a-production-environment-9e6a73b3eddb

以及你应该如何正确调整它

Ahmed BesbesTowards Data Science Ahmed Besbes

·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 10 月 12 日

--

图片来源:Ivan Jermakov 在 Unsplash

随着 LLM 的兴起,检索增强生成(RAG)框架 也因能够在数据上构建问答系统而获得了广泛关注。

我们都见过那些聊天机器人与 PDF 或电子邮件对话的演示。

虽然这些系统确实令人印象深刻,但在生产环境中,如果不进行调整和实验,它们可能不够可靠。

在这篇文章中,我探讨了 RAG 框架背后的问题,并提出了一些提升其性能的技巧。这些技巧包括利用文档元数据和调整超参数。

这些发现基于我作为一名 ML 工程师的经验,我仍在学习这项技术并在制药行业构建 RAG。

事不宜迟,我们来看一看 🔍

如果你对提高构建 ML 系统的生产力感兴趣,可以随时订阅我的 通讯。

我每周发送关于编程和系统设计的见解,帮助你更快地推出 AI 产品。

RAG 简要介绍 ⚙️

首先,我们要弄清楚基本概念。

这是 RAG 的工作原理。

它首先接受一个输入问题,并从外部数据库中检索相关文档。然后,它将这些片段作为上下文传递给提示,以帮助 LLM 生成一个 增强的 回答。

这基本上是说:

“嘿 LLM,这里是我的问题,还有一些文本片段帮助你理解问题。给我一个回答。”

图片由作者提供

不要被这个图表的简单性所欺骗。

事实上,RAG 隐藏了某些复杂性,并且涉及以下组件:

  • 解析不同格式的外部数据的加载器:PDF、网站、Doc 文件等。

  • 用于将原始数据分割成较小文本块的分割器

  • 用于将文本块转换为向量的嵌入模型

  • 用于存储和查询向量的向量数据库

  • 用于将问题和检索到的文档结合的提示

  • 用于生成答案的 LLM

如果你喜欢图示,这里有另一个给你。

稍微复杂一点,但它展示了索引和检索过程。

作者修改的图像

哎呀!

不过别担心,你仍然可以很快原型化你的 RAG。

像LangChain这样的框架抽象了构建 RAG 涉及的大部分步骤,使得原型设计这些系统变得容易。

这有多简单?五行代码就搞定了。

当然,这种代码片段的明显简化总是有问题的:根据你的使用情况,它们并不总是能按原样工作,需要非常仔细的调整。

它们作为快速启动的工具很棒,但绝对不是工业化应用的可靠解决方案。

RAG 的问题

如果你开始构建 RAG 系统而几乎没有调整,你可能会感到惊讶。

这是我在使用 LangChain 和构建 RAG 的前几周所注意到的情况。

1 — 检索到的文档不总是与问题相关。

如果你仔细查看数据库检索到的块,你有时会注意到它们并不完全与问题相关。它们与问题在某种程度上相似,但在许多情况下,它们并没有完全对准用户的请求。此外,这些块往往冗余且重叠,这使得生成的答案……重复。

这是一个小实验,展示了与查询(在嵌入空间)最相似的文档既不是最相关的,也不是最语义相似的(这真令人惊讶!)。

在这个例子中,模型忽略了句子的情感,并且对复数形式(添加“s”将相似度降低了 8%)也不够鲁棒。

2 — RAG 系统缺乏基本的世界知识 如果你指示 RAG 仅依赖外部数据库来提供答案,它可能会通过编造事实或拒绝回答简单问题来让你感到惊讶。

我曾经在与网球相关的 Reddit 帖子上构建过一个 RAG。虽然它成功回答了有关当时温布尔登公开赛的问题,但对网球规则的一般问题却无能为力。

我知道你不应该问超出系统数据范围的问题,但如果你部署了基于 RAG 的服务,你应该对用户提出的任何问题做好准备。

3 — 速度

根据你使用的数据库类型和 LLM 的大小,你的 RAG 可能会非常慢。这会降低用户体验。你应该在构建解决方案之前先做一些基准测试。

4 — 一个有损的过程

由于 RAG 将原始数据分割成块,嵌入这些块,并检索最相似的 K 个,固有的信息会逐渐丧失和提炼。

因此,保留外部文档中的所有信息并提供完美的答案几乎是不可能的。

记住这一点很重要:我们在这里处理的是很多近似值。

提升 RAG 性能的技巧

如果你盲目地将 RAG 接入数据,它可能会表现出奇怪的行为。

你可以通过应用一些技巧和最佳实践来解决这些问题。

👉 检查和清理数据

“垃圾进,垃圾出”的原则仍然适用于 RAG。

如果你提供的文档是噪声较大的(例如 HTML 标签!)、彼此不一致、令人困惑,甚至是重复的,那么生成的答案将会受到影响,并反映出这些缺陷。

在选择 RAG 将使用的数据时要小心。

例如,与其将固有较短的 FAQ 与长 PDF 合并,不如创建两个向量存储。

这里有一个快速的质量检查来评估数据质量:尝试回答一两个问题。如果你发现很难用现有的数据支持得到答案,那就不要指望 RAG 表现得更好。

👉 微调块大小、top_k 和块重叠

这些参数非常重要,因为分块是 RAG 系统的核心部分。(记住,我们从存储中检索到的文档是!)

这些参数影响检索结果的质量,从而影响生成的答案。

例如,小块大小可能无法捕捉到足够的上下文,而大块大小可能引入大量无关的噪声。

这里没有什么神奇的数字可以给你。最好的解决方案是进行实验并在测试集上验证这些超参数。(是的,构建测试集很重要!)

Pinecone 有一个关于分块策略的指南,值得阅读。

👉 利用文档元数据

有时候在从数据库检索到文档后,基于可用的元数据(例如日期)进行过滤是有用的。这提供了另一种过滤维度,而且不需要额外费用。

👉 调整你的系统提示以给 RAG 一个默认行为或特定指令。这是我在一个项目中使用的系统提示:

You're a helpful assistant that works for a big pharmaceutical company.

You'll be asked questions about diseases and treatments among other things.

Stick to the question, provide a comprehensive answer and don't invent facts.

Use the following pieces of context to answer the question at the end.

If you don't know the answer, just say that you don't know, don't try to make up an answer.

%CONTEXT%
{context}

%Question%
 {question}
Answer:

👉 转换输入查询

如果你表达得不够清楚,RAG 可能找不到它需要的相关文档来建立有用的上下文。解决这一问题的一种方法是通过 LLM重新表述查询,然后再试一次。

另一种解决方案可能是尝试HyDE方法,它接受查询,生成一个假设性回应,并在嵌入空间中使用这两者进行检索。

查询转换是一个令人兴奋的领域,它可能会改善 RAG 系统,因为 LLMs 往往在较小的查询上表现更好。

结论

今天就到这里!

希望你喜欢这篇文章,并从中学到了有关 RAG 的一些有用信息。

由于这项技术相对较新,许多优化技术将会出现,使这个框架更加可靠,并准备好用于工业化应用。

如果你也在构建 RAG,我很好奇你使用了哪些优化技术。请在评论中告诉我。

下次见👋!

训练中断会毁掉我的马拉松吗?

原文:towardsdatascience.com/will-a-training-break-ruin-my-marathon-465e94cb949e

大规模数据分析训练中断对马拉松准备的表现成本

barrysmythTowards Data Science barrysmyth

·发表在Towards Data Science ·10 min 阅读·2023 年 1 月 13 日

--

照片 由Anna Shvets拍摄。

TLDR;

  • 超过 50%的跑者在训练马拉松时会经历至少 7 天的跑步中断。更长时间的训练中断(≥7 天)也经常发生。但仍有希望,因为即使是长时间的中断也不一定会毁掉你的马拉松。

  • 然而,中断确实会带来成本,训练中断的完成时间比没有长时间训练中断时的完成时间长 2%–9%。

  • 完成时间的成本取决于中断的性质(其持续时间和时机)以及跑者的类型(男性与女性、年轻与年长、快速与慢速)。

  • 通过理解这些成本,跑者可以在训练中断后调整马拉松目标,以确保更好的比赛结果。

介绍

训练马拉松并不容易。在许多方面,它比实际跑马拉松更困难。事实上,许多报名并开始训练马拉松的跑者,最终未能到达起跑线,更不用说完成比赛了。作为那些开始马拉松之旅的人的百分比,未能参赛者的估计数从15%到高达50%不等。

值得注意的是,最近的都柏林马拉松(2022 年)中,几乎 10,000 名注册跑者未能如期出现。然而,这至少部分是由于疫情的影响,因为许多注册转移自取消的 2020 年和 2021 年的赛事。在‘正常’年份中,都柏林的缺席率可能接近20%。

好消息是,如果你能够坚持度过漫长的训练月份,并且在马拉松起跑线上(相对)完好无损,那么你完成比赛的机会非常高。通常,只有 2-3%的参赛者会在完成前退出,尽管这通常取决于比赛当天的情况。例如,2018 年波士顿马拉松的恶劣寒冷天气导致了大量参赛者未能完成比赛,甚至包括精英选手在内,5%的男性和 3.8%的女性在比赛中途退出。

跑者们经常在训练未能按计划进行时,a放弃他们的马拉松训练和比赛。有些人因伤病或生病离比赛日过近而无法及时恢复。其他人由于各种原因(如繁忙的生活方式、家庭责任、旅行等)而训练受到干扰,感到比赛准备被毁掉,因此选择退出。但情况真的如此吗?

  • 训练中断是否意味着比赛当天的灾难?

  • 如果不是灾难,那是否有证据表明训练中断会影响表现?

  • 如果确实有表现成本,那么这种成本如何依赖于中断的性质(中断的持续时间和训练期间的时机)或跑者的类型(性别、年龄、能力)?

在这篇文章中,我将总结我参与的一项近期研究的结果,该研究利用了一个非常大的马拉松跑者及其训练活动的数据集。艰苦的工作由Ciara Feely完成,她目前是都柏林大学的最后一年博士生,该研究最近由期刊Frontiers in Sports and Active Living发表,Frontiers in Sports and Active Living。

数据集

这项研究基于 292,323 名跑者(每位跑者比赛日前最多 12 周)的 15,697,711 条跑步活动的数据集。这些跑者在 2014 年至 2017 年期间完成了 509,979 场马拉松,并将他们的训练和比赛数据上传到 Strava。Strava Inc.根据与作者的数据共享协议提供了这些数据的匿名版本。更多数据集细节可以在 这里 查阅,简而言之,我们注意到以下几点:

  • 男性和女性之间的比例为 80/20,男性的平均年龄约为 40 岁,而女性的平均年龄为 38 岁。

  • 男性跑者的平均马拉松完成时间稍低于 4 小时,而女性的平均完成时间略高于 4 小时 24 分钟。

  • 平均而言,跑者每周训练时间稍超过 3 天,并且在训练期间每周完成平均 40–41 公里。

重要的是,这个数据集只包括了参加了比赛日的跑者;即,它不包括那些开始马拉松训练但最终未能到达起跑线的跑者。因此,这项研究应视为对未严重到足以使跑者放弃马拉松的训练干扰的调查。

此外,我们专注于每个跑者在特定年份的最快马拉松成绩。我们这样做主要是为了避免包括那些作为其他比赛训练的一部分的马拉松。

在我们的分析中,我们还基于以下三个不同的跑者分组进行研究:

  1. 性别男性女性 跑者。

  2. 年龄年轻(≤40 岁)与 年长(>40 岁)跑者。

  3. 能力更快更慢 的跑者;更快的跑者在我们的数据集中拥有的 个人最佳(PB)马拉松时间少于 240 分钟。更快的跑者占数据集中所有跑者的 53%以上,并且主要(88%)是男性。

接下来,我们将比较这些分组的各种特征,探索训练干扰与完成时间之间的关系。

训练干扰的频率

训练干扰 是指一段时间——几天连续——没有任何记录的训练活动。由于每周训练期间的常规恢复日,预计会有一些短暂的训练干扰。实际上,由于我们的跑者每周跑步大约 3 次,因此我们会发现每周训练中有≤4 天的连续间隔,具体取决于训练日如何分布。然而,更长时间的训练间隔(≥7 天)应当较少出现,可能指示一些未预期的训练干扰。

较长的训练中断发生的频率如何?为探索这一点,我们计算了训练历史中包含“最长 训练中断” ≥7 天的跑者比例,并比较了这些比例在性别、年龄和能力上的差异,以及中断时机的差异;早期 中断发生在比赛日前 8-12 周,而晚期 中断发生在比赛日前 3-7 周。

按性别(A)、年龄(B)、中断时机(C)和能力(D)划分的最长训练中断时长的跑者比例。转载自 Frontiers 根据 CC BY 许可证。

上面的图表(A-D)展示了不同训练中断时长(7-42+天,x 轴)对性别(A)、年龄(B)、中断时机(C)和跑者能力(D)的结果。大多数跑者在比赛当天经历了至少 7 天的训练中断,而更长时间的中断则越来越不常见。值得注意的是,10-20%的跑者经历了至少 14 天的中断,这可能被视为比单纯的忙碌日程更严重的中断,例如伤病或疾病。

图 A、B 和 D 表明,基于性别、年龄或能力,经历不同时长中断的跑者比例差异不大;男性、年轻跑者和更快的跑者的比例有所增加,但大多数情况下差异较小。

当我们考虑中断时机时,情况有所不同,见图 C。早期的中断比晚期中断更为常见(无论时长如何),这表明跑者在接近比赛日时,经历训练中断的可能性较低。一个解释可能是,随着比赛日的临近,跑者变得更加投入,不愿意牺牲训练,除非必须。然而,也必须承认,由于我们的数据集排除了未完成训练的跑者,这些跑者可能倾向于晚期中断。换句话说,经历晚期中断的跑者的退赛率可能高于早期中断的跑者。例如,晚期受伤可能没有足够的时间恢复,而如果受伤发生在训练早期则可能会有所不同。因此,这种分析可能低估了经历晚期中断的跑者的比例,因为至少有一些跑者可能未能到达起跑线。尽管如此,这种效应不太可能完全解释跑者经历早期和晚期中断之间的相对差异(35%-60%)。

训练中断的成本

尽管马拉松训练中出现较长时间的干扰(≥7 天)并不罕见,但好消息是,这些干扰不一定会对比赛日造成致命影响。大多数跑者经历过干扰,但大多数跑者仍能顺利到达起跑线。然而,很可能长时间的训练干扰会有一定代价。可以推测,错过两周的训练将会对表现产生影响,特别是当这两周的训练是在高峰期错过的,此时跑者正在适应一些非常长的跑步,以模拟比赛日的疲劳。

为了探索这一点,我们区分了两种类型的训练历史:

  1. 干扰的训练历史是指最长训练干扰时间为≥7 天的情况。

  2. 未干扰的训练历史是指最长训练干扰时间为<7 天的情况。

接下来,我们关注了那些至少跑过 2 场马拉松的跑者,其中一段训练历史受到干扰而另一段则未受干扰;这产生了一组包含 43,933 名跑者(83.39% 男性和 26.61% 女性)的数据,这些跑者都有受到干扰和未受干扰的训练历史。

然后,我们将干扰马拉松(与干扰训练相关的马拉松)的完成时间与相应的未干扰马拉松的完成时间逐一比较。我们根据这两个完成时间之间的相对差异估算了“干扰成本”。例如,如果一个干扰马拉松的完成时间为 244 分钟,而同一跑者最近的未干扰马拉松完成时间为 235 分钟,那么这个 9 分钟的减慢代表了一个 3.8%的干扰成本,当转换为未干扰完成时间的百分比时。

现在,还有其他几个因素可能解释这种时间差异——不同的路线、不同的天气条件、不同的训练方法(尽管有干扰)、比赛日的配速或补给变化、基础体能差异、调整后的恢复策略等——但预计这些因素在干扰和未干扰的训练中出现的可能性是相同的,因此通过我们使用的数据集的规模,这些因素将会被平均化

按性别(A)、年龄(B)、干扰时机(C)和能力(D)计算的训练干扰平均成本;摘自这里,根据CC BY 许可证发布。

训练干扰的成本在上面的图表中进行了总结,再次按最长训练干扰的持续时间(x 轴)进行分类,并通过比较训练历史中的性别(A)、性别(A)、年龄(B)、干扰时机(C)和跑者能力(D)进行分析。

正如我们可能预期的那样,较长的中断与更大的成本相关。在(A)中,对于给定的中断持续时间,男性的中断成本高于女性,这在基于 21–27 天的中断持续时间的统计上是显著的;顺便提一下,统计显著性是基于z-testt-test(适用时)(p<0.01),在图中用填充标记和实线表示(有关使用的统计方法的更多信息,请参见论文)。较快的跑者或较晚的中断也与比较慢的跑者或较早的中断更大的成本相关;年轻跑者经历的成本高于年长跑者,尽管超过 7–13 天的差异在统计上不显著。

在(A)中,我们可以看到,男性跑者在经历 21–27 天的训练中断后,完成时间增加了近 8%;对于同样受中断影响的女性跑者,成本略高于 4%。这种性别差异可能与男性跑者往往高估自己的能力以及他们跑得不够有纪律的比赛倾向有关,这可能会加重训练中断对比赛日的影响。

此外,最快和最慢跑者之间的成本差异最大。平均而言,较快的跑者的中断成本约为 5.5%(峰值接近 9%),而较慢跑者的平均成本不到 3%(峰值不到 4%)。其中一个原因可能是较快的跑者中男性占比更高(88%),正如上文所讨论的。

最后,在得出结论之前,值得注意的是,在类似的研究中,我们总是需要小心避免混淆因果关系和相关性。这里的重点是通过关联性来看相关性。较长的中断与较慢的完成时间相关,但是否导致较长的完成时间还需要进一步的研究。至少,更仔细地控制如赛道和天气条件、位置和补给策略、训练实践,或跑者从休息中恢复的速度等因素将是重要的,但这需要另一天的工作。

结论

马拉松训练并不总是顺利进行。一些跑者可能永远无法到达起跑线,但那些能够到达的跑者通常会经历较长时间的训练中断,有些人可能需要多周的时间来恢复疾病或伤害。

幸运的是,这样的中断并不一定会过早终结跑者的马拉松梦想。然而,长时间的训练中断确实会带来表现上的成本。证据表明,它们与完成时间的延长有关,可能会比正常情况下长 2%–9%,具体取决于中断的性质(持续时间和时机)以及跑者的类型(性别、年龄、能力)。

虽然确认干扰与表现成本相关并不令人惊讶,但量化这些成本是有用的。例如,它可以帮助跑者更好地规划即将到来的比赛日。当然,更好地理解训练干扰与完成时间之间的关系应该能帮助跑者调整他们的比赛目标,并在训练受到干扰时调整他们的配速。这将帮助他们确保从下一个马拉松中获得最大的收益。

ChatGPT 会取代数据科学工作吗?

原文:towardsdatascience.com/will-chatgpt-take-data-science-jobs-73e4fbb706b8

观点

数据科学的黄金时代是否终于结束了?

Natassha SelvarajTowards Data Science Natassha Selvaraj

·发表于Towards Data Science ·8 分钟阅读·2023 年 9 月 15 日

--

图片来源:iStock

如果你正在阅读这篇文章,你可能已经在数据行业有一份工作,或者正在寻找进入这个领域的机会。

随着过去一年生成型人工智能领域的所有进展,你可能担心数据科学工作会被自动化取代。

一年前,我会嘲笑任何提及将我的数据科学工作自动化的可能性的人。

实际上,我甚至写了一篇完整文章来嘲笑 AI 能够取代数据科学家的想法——我的意思是,我们编写代码、构建机器学习模型、分析数据,并将复杂的信息传达给非技术相关方。

我们的工作很困难。这些技能需要多年的磨练。AI 可以提高数据团队的效率和协作,但不可能取代我们所做的实际工作。

然而,上述博客文章是在 ChatGPT 发布之前写的。

自那时起,我们见证了生成型 AI 领域的范式转变。

在这篇文章中,我将基于生成型 AI 领域的现有进展重新评估我对数据科学未来的立场。

基于我广泛的研究和行业专家的见解,我将呈现一系列观点,解释为什么 ChatGPT 可能会取代数据科学家,以及它可能不会取代的原因。

我将审视辩论的两个方面,并留给你,读者,去做出明智的决定,判断生成型 AI 是否会使数据科学家变得过时。

如果你更喜欢视频格式,请观看这个:

点击这里查看视频版本

糟糕的情况:为什么数据科学工作面临风险

1. ChatGPT 能快速编写代码

数据科学家大约花费40%-50%的时间在编写代码上。

不仅如此,ChatGPT 不仅能够编写代码,而且变得非常熟练,速度也非常快。

该聊天机器人已经在多个顶级公司通过了编码面试,可以将手绘草图转化为完全成熟的网站,并且能够从用户故事中构建数据库表。

实际上,这家软件公司 CEO 表示,该模型将完成编码任务的时间从9 周缩短到仅仅几天。

这是超过 20 倍的效率提升——这将显著减少组织需要雇佣的人员来完成编程任务。

2. ChatGPT 能够摄取和分析数据

现在,你可能在想,“编程只是数据科学家工作的一小部分。我们的工作涉及机器学习建模、统计分析以及向利益相关者提供见解。”

好吧,ChatGPT 也能做这些事情。

该模型的新代码解释器插件,现在更名为“高级数据分析”,允许你在 ChatGPT 界面内上传和分析数据。

这是一个高级数据分析插件根据纯文本要求构建客户细分模型的截图:

图片由作者提供

该模型能够预处理数据集,找到 K 均值聚类的最佳簇数,构建算法,并且根据模型的输出提供数据驱动的见解。

根据商业内幕的报道,编程和分析相关的角色是最容易被 AI 自动化取代的职位之一。

马克·穆罗,布鲁金斯学会的高级研究员,曾研究过 AI 对美国劳动力的影响,他声称这是因为 ChatGPT 可以比人类更快地产生代码,并且擅长分析数据和预测结果。

3. 架起人类与技术之间的桥梁

我合作过的许多非技术型利益相关者在阅读 Excel 表格中呈现的数据时感到困惑。这些都是忙碌的人,他们需要以简单、直接的语言呈现结果。

例如,营销团队会问诸如“哪些客户最有可能再次购买产品 X”这样的问题,并期望你在几页幻灯片中给出答案,突出显示驱动重复购买的预测指标。

作为一个语言模型,这样的使用场景正是 ChatGPT 的强项。

你可以问它以下问题:

I work in marketing and have no technical background whatsoever. 
Can you explain, based on this dataset, which of my customers are most likely
to purchase product X again and why?

ChatGPT 不会给你提供复杂的图表和计算,而是会准确告诉你有关客户数据集所需了解的内容,使你具备做出数据驱动的营销决策所需的知识。

ChatGPT 的对话能力,加上其技术能力,可以让数据科学和分析领域变得更加普及。

以前需要你对 Excel 或 Python 有很强掌握的任务,现在可以轻松使用像高级数据分析插件这样的工具完成。

此外,ChatGPT Enterprise刚刚发布,允许公司为其员工购买语言模型的订阅。

OpenAI 称这是“迄今为止最强大的 ChatGPT 版本”,因为它没有使用限制,性能提升至两倍快。它还提供无限访问高级数据分析插件的权限。

由于这个版本的 ChatGPT 符合 SOC2 标准,员工可以将公司专有的数据集直接上传到 ChatGPT 界面,而无需担心泄露敏感的公司信息。

积极的一面:为什么你的数据科学工作是安全的

好吧,之前部分的悲观言论可能让人有些沮丧,我几乎可以看到你们中的一些人对屏幕摇头,不同意我提出的观点。

但不用担心!

我们现在将深入探讨更令人安心的方面,并探讨为什么许多业内人士认为数据科学工作是安全的(至少现在是)。

1. ChatGPT 无法进行复杂的数据分析

数据科学家(和分析师)通常从多个来源收集数据,并使用各种工具从中提取洞察。

这项工作并不像使用高级数据分析插件将单一数据集上传到 ChatGPT 那么简单。

例如,我目前正在处理一个需要分析数千个 PDF 文件的项目。

这样的任务具有挑战性,因为数据量很大,每个 PDF 文档包含多种类型的信息,如表格和图像。

在这项任务中,ChatGPT 的帮助有限,因为它无法访问分析甚至一个文档所需的包:

图片由作者提供

当然,它能够帮助我编写提取和分析数据所需的代码——但没有编程知识的人无法运行、验证或调试这些代码。

此外,大多数公司将其信息存储在 SQL 数据库中。

高级数据分析插件仅限于文件上传,这意味着它不能直接与公司数据库交互。

你仍然需要具备数据库管理和 SQL 查询必要技能的人来访问、解释和分析这些数据库中的数据。

2. ChatGPT 无法模拟人类决策

根据畅销书作者和顶尖商业及数据专家 Bernard Marr 的说法,即使是最先进的 LLMs也缺乏如批判性思维、战略规划和解决问题的能力。

这些模型对业务的内部运作没有洞察力,且缺乏领域专业知识。

如果你问它“为什么产品销售在过去 2 个月暴跌”,模型将缺乏关于你组织的必要背景信息来提供有根据的回答。

此外,人类分析师或数据科学家通常会接触到组织内的不同团队——我们询问他们目前面临的问题,深入了解业务问题,并提出解决策略。

另一方面,LLM 无法像人类一样进行协作性问题解决。

3. ChatGPT 会犯错误

最后,ChatGPT 易受幻觉的影响。它在编写代码、解释数据和生成见解时可能会出错。

根据 AI 生成的数据做出商业决策是一种大多数组织不愿意冒的风险。

仍然需要人类专家来验证 AI 模型生成的代码和输出。

实际上,我合作过的许多组织在做出决定之前通常有两种生成预测的方式——一个是内部数据科学团队,另一个是外部咨询公司。

由这两个实体生成的数字会被比较,并经常进行对账,以确保预测的一致性。

如果公司愿意花费数万美元聘请第三方咨询公司仅仅是为了在决定如何进行之前交叉验证他们的预测,你真的认为他们会用 AI 模型来替代数据科学家以节省成本吗?

如果你问我,这种可能性非常小。

在我看来,AI 生成的预测将作为一个基准——它可能成为组织验证其数据科学团队提供的见解的另一种方式。

我在数据科学领域工作。我该如何保护我的职业免受 AI 影响?

首先,你必须拥抱 AI。利用它来跟上行业趋势并学习新知识。根据这篇《福布斯》文章,假装 AI 不会显著改变你的工作方式只会带来更多的坏处而非好处。

相反,利用生成性 AI 模型来自动化工作中的某些部分,并利用效率提升来发展能使你在行业中脱颖而出的技能。

此外,Tina Huang,前 Meta 数据科学家,建议你建立多个收入来源,而不是仅仅依赖全职工作。

你可以开始提供自由职业数据科学服务,以获取被动收入,确保你的工作安全不依赖于单一雇主的决定。

最终,我个人认为,组织在招聘员工时看重的素质将会发生变化。传统上,职位要求非常重视技术技能——你在 Excel、编程或 Tableau 上的表现越好,被录用的可能性就越大。

我相信,重心将会慢慢转移 away from 工具和技术专长,因为生成性 AI 模型正在弥合这一领域的技能差距。当然,技术技能仍然有价值,但它只能带你走到一定程度。

相反,组织将开始看重沟通、创造力、领导力和决策能力等技能。

一个理解如何利用 AI 实现公司目标的人,将比仅具备技术专长的人对雇主更具价值。

生成式 AI 是否会取代数据分析师的需求?

原文:towardsdatascience.com/will-generative-ai-replace-the-need-for-data-analysts-6b6807599d00?source=collection_archive---------1-----------------------#2023-05-23

作者使用 Midjourney 创作的艺术作品

不会。但它重新定义数据分析师的角色。

加伦·冈崎Towards Data Science 加伦·冈崎

·

关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 5 月 23 日

--

自 2022 年 11 月 ChatGPT 发布以来,关于数据分析师的角色是否最终会被生成式 AI(如 ChatGPT、Bard 和 Bing Chat 等大型语言模型)取代的猜测越来越多。这种猜测很大程度上源于这些大型语言模型(LLMs)编写代码的能力。

作为在数据分析领域工作大部分职业生涯的人,理解生成 AI 在我们领域的影响确实引起了我的兴趣。出于好奇,我花了相当多的时间评估生成 AI 在数据分析中的当前能力。

在这篇文章中,我总结并分享了我的发现,因为我相信生成 AI 将在未来的数据分析工作中发挥重要作用。此外,我认为数据分析师社区有必要理解它将对他们的领域以及整个商业环境产生深远的影响。

我们目前的状态

到目前为止,我们知道生成 AI 可以编写 SQL、Python 和 R 代码。我们还可以假设,随着不断的微调,它们生成的代码的效率将会随着时间的推移而提高。但这只是开始。

在 2023 年 3 月底,OpenAI 的 ChatGPT 发布了一个名为代码解释器的插件。如果你是目前少数几位可以访问 Alpha 版本的人之一,你可以将数据文件上传到其中,并调用 Python 进行回归分析和描述性分析,查找数据中的模式,甚至创建可视化。全部无需编写或了解一行 Python 代码! 受人尊敬的沃顿商学院教授伊桑·莫利克对此进行了很好的总结。

所以,你看到了。能够加载、分析和呈现数据而不写一行代码。游戏结束了吗?还不那么快。

尽管这些能力令人难以置信,但 Code Interpreter 有一些显著的局限性,显示了生成 AI 在接管数据分析行业时可能面临的一些挑战。

首先,它需要上传一个表格。一个二维的 CSV 文件(目前限制为 100 MB)。除了大小限制之外,想象一下任务是构建一个包含您公司所有数据的表格……

我可能到此为止,但我们继续吧。

拿着你的一个表格,你现在还需要获得批准,将你公司所有数据的一个表格推送到公司防火墙之外的 LLM 中,而你对此没有控制权……

我们可以到此为止。

当前(稍后会详细讨论)的替代方案是您的公司建立自己的 LLM。虽然理论上可行,但训练和微调模型的复杂性、所需的专业知识以及巨大的成本,使得只有极少数公司能够使其成本效益显著。

但为了更好地理解,让我们退一步,假设你的公司在那个名单上。

但首先,让我们从一些视角开始。如果我们回顾到 2000 年代初商业智能工具的引入,这些工具的巨大价值在于它们能够让非技术的业务人员利用他们的领域知识,通过使他们能够选择、分析和呈现数据,而不需要编写一行代码。听起来熟悉吗?

提供用户友好的数据分析手段并不是什么新鲜事。它始终具有巨大的价值。确实,这是一个持续增长的数十亿美元行业。然而,这些工具没有领域知识就毫无用处。这适用于任何数据分析,无论使用什么工具。即使是生成性 AI 也是如此。没有领域知识,我们不知道该向数据提出什么问题。即使问题已经提供给我们,我们又如何解读我们的发现呢?

在我看来,数据分析工作的最大价值在于其回答临时问题的能力。不可预见的、关键任务的问题。复杂的、多层次的、非线性的类型问题。回答这些问题需要领域知识。

例如,为什么我们最畅销的产品销售额突然暴跌?我们的主要供应商刚刚倒闭,我们该怎么办?为什么我们客户流失率上个月翻了一番?这些不是可以遵循既定决策树的简单问题。

这些少数示例的共同点在于,它们需要对从未被问过的情境性问题给出即时答案。这才是关键。如果你理解生成性 AI 的构造,它在回答这种性质的问题上的无能为力确实是它完全取代数据分析师的致命弱点。

简要总结一下,生成性 AI 利用现有数据集来“训练”一个 LLM,以生成基于其所接收的训练数据的概率驱动答案。虽然你可以持续通过更精确的数据集来微调你的模型,但你如何训练你的模型应对从未被问过的多层次、情境性问题呢?

这就像你刚刚开始一份你尚不熟悉的行业数据分析师的新工作。在第一天,你被要求紧急回答上述问题之一。你会从哪里开始?你会提取什么数据?你怎么知道你需要考虑哪些所有潜在变量?即使你能以某种方式得出一个答案,你又如何知道它是否正确?

正因为如此,我不认为数据分析师的角色会完全被生成性 AI 取代。然而……生成性 AI 在当前状态下,已经在数据分析领域有许多用途,而且这些用途将随着功能的不断增加而不断扩展。

生成性 AI 在数据分析中的当前潜在用途

截至今天,生成式人工智能在数据分析领域的最佳应用是它既能编写代码,又能解释所写的代码(它做得相当好)。我个人用它来帮助我编写和理解 Python 代码。

对于那些希望进入数据分析领域的人,我强烈建议你利用生成式人工智能来帮助你学习编码。当我刚开始进入这个领域时,它将大大加快我的学习曲线。

在数据分析领域,还有另一个令人兴奋的发展:生成式人工智能推动了专用编码工具的发展。GitHub 发布了其Copilot产品,它可以在你编写代码时实时建议编码解决方案/改进!

在本文早些时候,我提到了公司在构建自己的 LLM 时可能面临的潜在障碍。现在可能有一个新的替代方案:Databricks 最近发布了一个Dolly开源 LLM。理论上,这可以解决成本(开源)和将数据推送到公司防火墙之外的问题。它是一个小规模的 LLM,更适合于专注的数据集。

我提到 Dolly,主要是作为生成式人工智能领域发展速度的一个例子,并提醒它可能对数据分析领域产生的影响。

正如我们已经看到的那样,人工智能的演变只会以光速继续发展。

结论

我毫不怀疑生成式人工智能将重塑数据分析中的工作流程。一般来说,重复性的任务甚至分析将在未来由生成式人工智能执行。我也可以看到编程成为一种商品,而不再是高度发展的技能。

基于上述观点,我相信未来典型的数据分析师将具备业务线级的领域知识,并结合生成式人工智能工具,以帮助他们更高效地利用时间。

最后,从个人角度来说,我鼓励每位读者拥抱生成式人工智能。了解它,并在个人和商业生活中使用它。随着新 API 和插件的不断出现,它的覆盖范围和能力将不断增长。

无论好坏。

我喜欢写关于数据分析领域影响深远的话题。如果你想关注我的未来文章,请在Medium上关注我。谢谢!

作者使用 Midjourney 创作的艺术作品

窗口函数:数据工程师和数据科学家必知的内容

原文:towardsdatascience.com/window-functions-a-must-know-for-data-engineers-and-data-scientists-4dd3e4ad0d2

回到基础 | 揭开 SQL 窗口函数的神秘面纱

Iffat MalikTowards Data Science Iffat Malik

·发表于 Towards Data Science ·阅读时间 13 分钟·2023 年 6 月 14 日

--

数据增长在过去几年中非常显著,尽管我们有各种工具和技术,但 SQL 仍然是大多数工具的核心。它是数据分析的基本语言之一,被各类公司广泛使用来解决与数据相关的挑战。

我始终相信,你不需要仅仅为了通过面试或解决特定问题而了解某些概念。如果你愿意深入学习这些概念及其底层架构,它将帮助你在任何工作中取得成功。窗口函数有点棘手,刚开始可能会让人感到有些害怕,但一旦掌握,它们会变得非常有趣。

如果你熟悉 SQL 聚合函数,理解窗口函数的概念会更容易。聚合函数对一组值进行计算并返回一个值;当与GROUP BY子句配合使用时,它为每个组返回一个值。你可以在这里阅读更多内容:

## SQL 聚合函数为你的下一个数据科学面试做准备

回到基础 | SQL 初学者的基础知识

towardsdatascience.com

在进一步讨论之前,让我向你介绍一下示例数据库。我们将使用来自一个虚拟汽车零售公司的数据,你可以在我的 GitHub Repo中找到源数据,

作者提供的图片

什么是窗口函数?

窗口函数的传统定义是,

窗口函数在与当前行相关的一组表行上执行计算。

如果我将其拆解,窗口函数使我们能够对分区执行计算。分区只是用户定义的一组、子组或行桶,窗口函数将在其上执行计算。

它们也被广泛称为分析函数

为什么我们需要窗口函数?

如我们所知,聚合函数将来自多行的数据汇总为一行(如果与 GROUP BY 子句一起使用,则每组一行);而窗口函数也在一组行上执行计算,但与聚合函数不同的是,它们不会将结果集汇总为一行。相反,所有行保持其原始形式/身份,并且为每行添加计算后的行。

听起来有趣吧?让我们来分解一下。这是来自表 PRODUCTS 的示例数据,

--query PRODUCTS table
SELECT 
    * 
FROM 
    PRODUCTS 
LIMIT 10;

图片由作者提供

比如,我们需要关于每个PRODUCTCATEGORY的平均购买价格的信息,

--average buy price for each product category
SELECT
    PRODUCTCATEGORY,
    FORMAT(AVG(BUYPRICE),2) AS AVERAGE_BUYPRICE
FROM
    PRODUCTS
GROUP BY PRODUCTCATEGORY;

图片由作者提供

现在,仅这些信息可能用处不大。是的!你现在确实知道了每个PRODUCTCATEGORY的平均购买价格,但接下来呢?这些信息如何产生商业洞察?如果我想将每个产品的购买价格与特定PRODUCTCATEGORY的平均购买价格进行比较呢?让我重新表述一下我的新需求,

  1. 显示每个PRODUCTCATEGORY中的购买价格以及该PRODUCTCATEGORY的平均购买价格。

  2. PRODUCTCATEGORY分组排列结果集。

你能仅用普通的聚合函数实现这一点吗?上述要求希望显示一些信息原样(例如PRODUCTCATEGORYPRODUCTNAMEBUYPRICE),并且还希望新增一列显示每个PRODUCTCATEGORY的平均购买价格。这就是英雄般的窗口函数登场的时候,

--using window function
SELECT 
    PRODUCTCATEGORY,
    PRODUCTNAME,
    BUYPRICE,
    FORMAT(AVG(BUYPRICE) OVER (PARTITION BY PRODUCTCATEGORY),2) AS AVERAGE_BUYPRICE
FROM
    PRODUCTS; 

GIF 由作者提供

在我们跳到常用的窗口函数之前,让我们了解一下基本的语法和配对的子句。

窗口函数的一般语法是,

图片由作者提供

其中,

  • ***OVER()***子句定义了一组用户指定的行。窗口函数将仅在该特定集合上执行计算。

    它专门与窗口函数一起使用;然而,它也可以与聚合函数一起使用,就像我们在上面用*AVG()*函数一样,从而将其转变为窗口函数。

    如果你在*OVER()*中不提供任何内容,窗口函数将应用于整个结果集。

  • PARTITION BYOVER子句一起使用。它将查询结果集划分为多个分区,然后窗口函数应用于每个分区。

    这是可选的,如果你没有指定PARTITION BY 子句,那么函数会将所有行视为一个分区。

  • ORDER BY 子句用于在结果集的每个分区内按升序或降序排序。默认情况下是升序。

  • ROWS/RANGEFRAME子句的一部分,它定义了分区中的一个子集。

你可以在这里阅读关于窗口函数与聚合函数的详细比较以及窗口函数子句的内容,SQL 窗口函数的结构

现在我们已经熟悉了窗口函数的基本结构,让我们探索最常用的窗口函数,

ROW_NUMBER()

ROW_NUMBER() 给表或使用PARTITION BY 子句的分区中的每一行分配了一个顺序整数。常见的语法是,

ROW_NUMBER() OVER ([PARTITION BY clause] [ORDER BY clause])

这是来自PRODUCTS表的示例数据,它包含了汽车零售商提供的一系列产品数据。

--query PRODUCTS table
SELECT 
    * 
FROM 
    PRODUCTS 
LIMIT 10;

作者提供的图片

让我们从基础开始,

--assigns a row number to each row in a table
SELECT 
    *,
    ROW_NUMBER() OVER() AS ROW_NUM
FROM 
    PRODUCTS;

作者提供的图片

在这里,ROW_NUMBER() 给表PRODUCTS中的每一行分配了从 1 开始的顺序整数。让我们稍微升级一下,为每个PRODUCTCATEGORY添加行号,为此我们需要使用PARTITION BY 子句。

--row_number by productcategory
SELECT 
    *,
    ROW_NUMBER() OVER(PARTITION BY PRODUCTCATEGORY) AS ROW_NUM
FROM 
    PRODUCTS;

作者提供的 GIF

我们在PRODUCTS表中有 2 个不同的PRODUCTCATEGORY

作者提供的图片

ROW_NUMBER() 为每一行生成了一个顺序整数,PARTITION BY 子句将结果集按PRODUCTCATEGORY 划分成桶。因此,ROW_NUMBER() 结合OVERPARTITION BY 子句,为每个PRODUCTCATEGORY生成了唯一的数字序列。

现在,让我们也利用ORDER BY 子句。这也是入门/中级面试中最常被问到的问题之一。假设我们想知道每个PRODUCTCATEGORY中库存数量最高的前 3 个产品。

--top 3 products with the highest quantity in each product category
WITH PRODUCT_INVENTORY AS
(
SELECT
    PRODUCTCATEGORY,
    PRODUCTNAME,
    QUANTITYINSTOCK,
    ROW_NUMBER() OVER (PARTITION BY PRODUCTCATEGORY ORDER BY QUANTITYINSTOCK DESC) AS ROW_NUM
FROM
    PRODUCTS
)

SELECT
    PRODUCTCATEGORY,
    PRODUCTNAME,
    QUANTITYINSTOCK,
    ROW_NUM AS TOP_3_PRODUCTS
FROM 
    PRODUCT_INVENTORY
WHERE ROW_NUM <= 3;

作者提供的图片

首先让我们将整个查询分成两部分,如下图所示;首先,我们在创建一个PRODUCT_INVENTORY。表数据将被划分为每个PRODUCTCATEGORY的分区/组,按每个产品类别的库存数量的降序排列。然后,ROW_NUMBER() 将为每个分区生成顺序整数号码。这里的有趣之处在于,每个PRODUCTCATEGORY中的行号会在每个分区中重置。

作者提供的图片

上面的查询将返回以下结果,

作者提供的图片

现在我们查询的第二部分是相当简单的。它将利用这个结果集作为输入,并从每个 PRODUCTCATEGORY 中选取前 3 个产品,基于条件 ROW_NUM ≤ 3。最终结果如下,

作者提供的图片

这将得到最终结果,如下所示,

作者提供的图片

RANK()

如其名称所示,RANK() 为表中的每一行或每个分区中的每一行分配一个排名。一般语法是,

RANK() OVER ([PARTITION BY 子句] [ORDER BY 子句])

继续我们PRODUCTS表的例子,让我们根据库存数量以降序为产品分配一个排名,按PRODUCTCATEGORY进行分区。

--generate rank for each product category
SELECT
    PRODUCTCATEGORY,
    PRODUCTNAME,
    QUANTITYINSTOCK,
    RANK() OVER (PARTITION BY PRODUCTCATEGORY ORDER BY QUANTITYINSTOCK DESC) AS "RANK"
FROM
    PRODUCTS;

作者提供的图片

我已经将结果集限制为演示目的。现在,不要将 ROW_NUMBER()RANK() 混淆。这两者的结果集可能看起来相似;然而,存在差异。ROW_NUMBER() 为表中的每一行或每个分区中的每一行分配唯一的顺序整数编号;而 RANK() 也为表中的每一行或每个分区中的每一行生成顺序整数编号,但对具有相同值的行分配相同的排名。

让我们通过一个例子来理解,以下是 CUSTOMERS 表的示例数据,

-- sample data from table customers
SELECT
    CUSTOMERID,
    CUSTOMERNAME,
    CREDITLIMIT
FROM 
    CUSTOMERS
LIMIT 10; 

作者提供的图片

在以下演示中,我为 CUSTOMERS 表生成了 ROW_NUMBER()RANK(),按 CREDITLIMIT 降序排列。

--row_number() and rank() comparison
SELECT 
    CUSTOMERID,
    CUSTOMERNAME,
    CREDITLIMIT,
    ROW_NUMBER() OVER (ORDER BY CREDITLIMIT DESC) AS CREDIT_ROW_NUM,
    RANK() OVER (ORDER BY CREDITLIMIT DESC) AS CREDIT_RANK
FROM 
    CUSTOMERS;

作者提供的图片

我已经将结果集限制为演示目的。绿色高亮的是 ROW_NUMBER(),蓝色高亮的是 RANK()

现在,如果你查看红色高亮的 3 条记录,这就是两个函数结果集不同的地方;ROW_NUMBER() 为所有行生成了唯一的顺序整数编号。而另一方面,RANK()CUSTOMERID 239321 分配了相同的排名 20,因为它们具有相同的信用额度,即 105000.00。不仅如此,对于下一个记录,即 CUSTOMERID 458,它跳过了排名 21 并分配了排名 22

DENSE_RANK()

现在如果你在想,为什么我们需要 DENSE_RANK(),如果我们已经有了 RANK()?正如我们在前面的例子中看到的,RANK() 为具有相同值的行生成相同的排名,然后跳过下一个连续排名(参见上面的图片)。

DENSE_RANK()RANK() 类似,只是有一个区别,它在对行进行排名时不会跳过任何排名。通用语法是,

DENSE_RANK() OVER ([PARTITION BY 子句] [ORDER BY 子句])

回到 CUSTOMERS 表,让我们比较 RANK()DENSE_RANK() 的结果集,

--dense_rank() and rank() comparison
SELECT 
    CUSTOMERID,
    CUSTOMERNAME,
    CREDITLIMIT,
    RANK() OVER (ORDER BY CREDITLIMIT DESC) AS CREDIT_RANK,
    DENSE_RANK() OVER (ORDER BY CREDITLIMIT DESC) AS CREDIT_DENSE_RANK
FROM 
    CUSTOMERS;

作者提供的图片

RANK()(以蓝色突出显示)类似,DENSE_RANK()(以绿色突出显示)为 CUSTOMERID 239321 生成了相同的排名,而 DENSE_RANK()RANK() 不同的是,它没有跳过下一个连续数字,而是保持了序列,并将 21 排名分配给 CUSTOMERID 458

NTH_VALUE()

这与我们之前讨论的有点不同。NTH_VALUE() 将返回指定窗口中表达式的 Nth 行的值。常见的语法是,

NTH_VALUE(expression, N) OVER ([PARTITION BY clause] [ORDER BY clause] [ROW/RANGE clause])

‘N’ 必须是正整数值。如果数据不存在于 Nth 位置,函数将返回 NULL。在这里,如果你注意到,我们在语法中有一个额外的子句,即 ROW/RANGE 子句*。

RAW/RANGE 是窗口函数中 Frame Clause 的一部分,它定义了窗口分区内的子集。ROW/RANGE 根据当前行定义此子集的起点和终点,你以当前行的位置为基点,并以此为参考,在分区内定义你的框架。

  • ROWS - 这定义了框架的开始和结束,通过指定当前行之前或之后的行数。

  • RANGE - 与 ROWS 相反,RANGE 通过与当前行的值进行比较来定义分区内框架的值范围。

通用语法是,

{ROWS | RANGE} BETWEEN <frame_starting> AND <frame_ending>

图片由作者提供

每当你使用 ORDER BY 子句时,它将设置默认框架为,

{ROWS/RANGE} BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

如果没有 ORDER BY 子句,默认框架是,

{ROWS/RANGE} BETWEEN UNBOUNDED PRECEDINGUNBOUNDED FOLLOWING

这些内容现在可能看起来有些多,但无需记住语法及其含义,只需练习即可!你可以在这里阅读详细的 Frame Clause 和一系列示例,

[## SQL 窗口函数的结构]

回到基础 | SQL 初学者基础

towardsdatascience.com

现在假设,我们需要找出每个 PRODUCTCATEGORY 中的第二高买价的 PRODUCTNAME

--productname for each productcategory with 2nd highest buy price
SELECT
    PRODUCTNAME,
    PRODUCTCATEGORY,
    BUYPRICE,
    NTH_VALUE(PRODUCTNAME,2) OVER(PARTITION BY PRODUCTCATEGORY ORDER BY BUYPRICE DESC) AS SECOND_HIGHEST_BUYPRICE
FROM
    PRODUCTS;

图片由作者提供

我们还有两个类似于 NTH_VALUE() 的值函数;FIRST_VALUE()LAST_VALUE()。顾名思义,它们分别从排序列表中返回最高(第一个)和最低(最后一个)值。通用语法是,

FIRST_VALUE(expression) OVER ([PARTITION BY clause] [ORDER BY clause] [ROW/RANGE clause])

LAST_VALUE(expression) OVER ([PARTITION BY clause] [ORDER BY clause] [ROW/RANGE clause])

类似于上述示例,现在你能找出每个PRODUCTCATEGORY中买价最高和最低的PRODUCTNAME吗?

NTILE()

有时会出现需要将分区内的行安排到一定数量的组或桶中的情况。NTILE() 就是用于这种目的,它将分区中有序的行分成特定数量的桶。每个桶都会分配一个从 1 开始的组号。它会尽量创建尽可能大小相等的组。对于每一行,NTILE() 函数返回一个组号,表示该行所属的组。

通用语法是,

NTILE(N) OVER ([PARTITION BY 子句] [ORDER BY 子句])

其中 'N' 是定义要创建的组数的正整数。

比如,我们想要将PRODUCTCATEGORY -‘Cars’ 分类为高价、中价和低价的汽车列表。

--segregate the 'Cars' for high range, mid range and low range buy price
SELECT 
    PRODUCTNAME,
    BUYPRICE,
    NTILE(3) OVER (ORDER BY BUYPRICE DESC) AS BUYPRICE_BUCKETS
FROM 
    PRODUCTS
WHERE
    PRODUCTCATEGORY = 'Cars';

作者提供的图片

LAG() 和 LEAD()

我们经常遇到需要进行某种比较分析的场景。例如,将选定年份的销售与前一年或后一年进行比较。这种比较在处理时间序列数据和计算时间上的差异时非常有用。

LAG() 从当前行之前的行提取数据。如果没有前导行,则返回NULL。常见的语法是,

LAG(expression, offset) OVER ([PARTITION BY 子句] [ORDER BY 子句])

LEAD() 从当前行之后的行获取数据。如果没有后续行,则返回NULL。常见的语法是,

LEAD(expression, offset) OVER ([PARTITION BY 子句] [ORDER BY 子句])

其中offset是可选的,但使用时其值必须是 0 或正整数,

  • 当指定为 0 时,LAG()LEAD() 会对当前行评估表达式。

  • 如果省略,1 被认为是默认值,它会取当前行的直接前一行或后一行。

--yearly total sales for each product category
WITH YEARLY_SALES AS
(
SELECT
    PROD.PRODUCTCATEGORY,
    YEAR(ORDERDATE) AS SALES_YEAR,
    SUM(ORDET.QUANTITYORDERED * ORDET.COSTPERUNIT) AS TOTAL_SALES
FROM
    PRODUCTS PROD
INNER JOIN
    ORDERDETAILS ORDET
    ON PROD.PRODUCTID = ORDET.PRODUCTID
INNER JOIN
    ORDERS ORD
    ON ORDET.ORDERID = ORD.ORDERID
GROUP BY PRODUCTCATEGORY, SALES_YEAR  
)

SELECT
    PRODUCTCATEGORY,
    SALES_YEAR,
    LAG(TOTAL_SALES) OVER (PARTITION BY PRODUCTCATEGORY ORDER BY SALES_YEAR) AS LAG_PREVIOUS_YEAR,
    TOTAL_SALES,
    LEAD(TOTAL_SALES) OVER (PARTITION BY PRODUCTCATEGORY ORDER BY SALES_YEAR) AS LEAD_FOLLOWING_YEAR
FROM YEARLY_SALES;

在这里,我们首先使用CTE(公共表表达式)来获取每个PRODUCTCATEGORY按年度的总销售数据。然后,我们使用这些数据与LAG()LEAD() 结合,以获取按PRODUCTCATEGORY 分区且按SALES_YEAR 排序的前一年和后一年总销售数据。

作者提供的图片

结论

窗口函数在你想以多种方式分析数据时非常有用。不同的 SQL 变体可能有略微不同的实现,因此查阅特定 SQL 变体的官方文档总是一个好主意。以下是一些资源,帮助你入门,

  • 窗口函数的解剖

  • 窗口函数概念和语法

  • MySQL 窗口函数限制

  • SQL 窗口函数备忘单

如果你记得某些东西非常清楚,你一定是练习得很好,

  • HackerRank 或 LeetCode 用来练习基础/中级/高级 SQL 问题。

成为会员,阅读 Medium 上的所有故事.

快乐学习!

PostgreSQL 中的窗口函数

原文:towardsdatascience.com/window-functions-in-postgresql-788d2ad57c6b

任何数据从业者必须了解的知识

梅尔林·舍费尔数据科学前沿 梅尔林·舍费尔

·发布于数据科学前沿 ·阅读时间 8 分钟·2023 年 1 月 6 日

--

照片由斯蒂芬·道森提供,来源于Unsplash

你是否曾经发现自己编写了许多步骤或包含聚合函数的长 SQL 查询,以计算看似简单的指标?你是否曾经想过“是否可以通过不同的方式计算表中的内容,以简化操作”?那么,也许是时候看看窗口函数了。

窗口函数是 SQL 中一个方便且强大的特性,它允许你对多行进行类似于聚合函数的计算。但不同于可能会与 GROUP BY 一起使用的聚合函数,它们不会返回一组行的单一值,而是返回集合中每一行的值。我们来看看一个例子:

SELECT
  user_id,
  SUM(sales)
FROM transactions
GROUP BY 
  user_id;

SELECT
SUM(sales) OVER(
            PARTITION BY user_id
)
FROM transactions;

你为什么以及何时应该使用窗口函数?

窗口函数的一个优势是,它们允许你结合使用聚合值和非聚合值,因为行不会被合并在一起。这为在一步中计算许多内容提供了一种方法,否则需要多个步骤。

与多个子查询和自连接相比,窗口函数相对简单易用且易于阅读。它们减少了查询的复杂性和步骤数,从而使得后续维护变得更加容易。

这样,它们也可以帮助解决性能问题。例如,你可以使用窗口函数,而不是进行自连接或子查询。

重要的窗口函数

你可以将 SUM()和 COUNT()等函数用作窗口函数,但还有一些特殊函数仅作为窗口函数可用。

以下是一些 PostgreSQL 中最重要的窗口函数:

  1. RANK(): 返回每行在数据集中的排名。排名是根据行在数据集中的顺序决定的。排名为 1 的是第一行。排名相同的行会获得相同的排名,这可能导致排名中出现间隙。

  2. DENSE_RANK(): 类似于 RANK(),但如果行值相同,它不会跳过任何排名。例如,如果两行具有相同的值且被排序为第一行和第二行,它们的排名将是 1 和 1,接下来的是 2,而不是 1 和 1 和 3,正如 RANK() 的情况。

  3. ROW_NUMBER(): 返回数据集中每行的唯一编号。第一行的行号为 1,第二行的行号为 2,依此类推。

  4. NTILE(): 将行分成指定数量的组或“块”,并为每行分配一个块编号。例如,如果你指定 NTILE(3) 并且数据集中有 9 行,前 3 行将被分配块编号 1,接下来的 3 行将被分配块编号 2,最后 3 行将被分配块编号 3。这个函数可以用来计算分位数。

  5. 5. LAG() 和 LEAD(): 这些函数用于访问数据集中前一个或后一个行的值。例如,如果你有一组值,并且想将每一行的值与前一行的值进行比较,可以使用 LAG() 函数来访问前一个值。

让我们看一些示例!

RANK()、DENSE_RANK 和 ROW_NUMBER(): 使用这些函数时,你需要指定要排名的列名,以及决定数据集行顺序的 ORDER BY 子句。

例如,要按“销售”列中的值数量对表中的行进行排名,你可以使用以下查询:

SELECT
user_id,
RANK() OVER(
          ORDER BY COUNT(*)
),
DENSE_RANK() OVER(
          ORDER BY COUNT(*)
),
ROW_NUMBER() OVER(
          ORDER BY COUNT(*)
)
FROM transactions
GROUP BY user_id;

在这个示例中,相应的排名函数将最高值的行分配排名 1,第二高值的行分配排名 2(或如果允许并列,则为 1),依此类推。

NTILE(): 使用 NTILE() 函数时,你需要指定要创建的块数以及决定数据集行顺序的 ORDER BY 子句。例如,要将表中的行分成 4 个块(四分位数),你可以使用以下查询:

SELECT
  user_id,
  sales,
  NTILE(4) OVER(
            ORDER BY sales)
FROM transactions;

如你所见,我们现在可以轻松确定前/后四分位数的销售额。我发现这个函数对于找出高于或低于某个百分比/分位数的值非常有用。

LAG()LEAD(): 使用 LAG() 或 LEAD() 函数时,你需要指定要访问的列名、移动的行数以及如果指定的行不存在时使用的默认值。例如,要访问“销售”列中前一行的值,你可以使用以下查询:

SELECT
  user_id,
  sale_year,
  sales,
  LAG(sales,1,0) OVER(
                  PARTITION BY user_id 
                  ORDER BY sale_year
  ) AS prev_year_sales
FROM transactions
ORDER BY 
  user_id, sale_year;

在这里,LAG()函数将访问“sales”列中前一行的值,如果没有前一行,则返回 0(例如,如果当前行是集合中的第一行)。

OVER、PARTITION BY 和 ORDER BY

OVER 子句是 PostgreSQL 中许多窗口函数的可选语法部分。它指定决定函数操作的行集的标准。例如,如果你想使用窗口函数计算每年的平均“sales”值,你可以使用 OVER 子句将行集定义为所有具有相同“year”值的行。OVER 子句通常包括 PARTITION BY 和 ORDER BY 子句,这些子句用于将行分成组(或分区)并确定每个分区内的行顺序。

OVER 子句是 PostgreSQL 中窗口函数的一个强大特性,它允许你控制决定窗口函数操作的行集的标准。这使得你可以根据需要对行组或单行进行计算。

如果你需要对包含的行进行更精细的控制,可以使用OVER 子句中的行选择。行选择子句决定了窗口函数操作的集合中将包含哪些行。行选择子句可以用来指定包含的行数行的范围,根据当前行在集合中的位置来确定。如果你只想从集合的开始或结束部分包含某些行,或想排除计算中的某些行,这会很有用。以下是一些你可能经常遇到的例子:

  1. ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING:这个子句指定集合中的所有行都应包含在计算中。这是默认行为,如果没有指定行选择子句,你不会经常看到它,但这是最常用的行选择。

  2. ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING:这个子句指定从当前行到集合的末尾的所有行都应包含在计算中。

  3. ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:这个子句指定从集合开始到当前行的所有行都应包含在计算中。我发现这在累积计算中非常有用。

  4. ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING:这个子句指定当前行及其前后各一行应包含在计算中。你可以更改数值来调整计算窗口的大小和形状。

这里还有一些值得了解的内容:

  1. 窗口函数可以在查询的 SELECT、WHERE 和 HAVING 子句中使用。这允许你在查询的各个部分使用窗口函数的结果,甚至用它们来过滤查询返回的行。

  2. 窗口函数可以与聚合函数结合使用。例如,你可以使用窗口函数来计算每一行的排名,然后使用 SUM()等聚合函数来计算排名的总和。这在计算分数和百分比时非常有用。

  3. 窗口函数可以与 GROUP BY 子句一起使用。当与 GROUP BY 子句结合使用时,窗口函数将计算每一组行的值,而不是整个行集的值。

  4. 窗口函数可以与子查询和 CTE(公共表表达式)一起使用。这允许你对子查询/CTE 的结果进行计算,而不是对原始数据集进行计算。

  5. 在 PostgreSQL 中,有一个 WINDOW 关键字用于定义窗口函数的窗口帧。窗口帧是窗口函数操作的行集,由 OVER 子句和函数语法中包含的任何行选择子句决定。通过使用 WINDOW 关键字,你可以在单个查询中为多个窗口函数指定窗口帧,而不是为每个函数重复 OVER 和行选择子句。这可以使你的查询更具可读性,更易于维护。

SELECT
 year,
 sales,
 AVG(sales) OVER w1 ,
 SUM(sales) OVER w1 
FROM sales_table
WINDOW w1 AS (PARTITION BY year 
            ORDER BY month 
            ROWS BETWEEN UNBOUNDED PRECEDING 
            AND UNBOUNDED FOLLOWING
          );

总之,窗口函数是 PostgreSQL 中一种强大且多功能的工具,它允许你对行集合进行复杂的计算。无论你是需要计算排名、将行分组,还是访问其他行的值,窗口函数都能满足你的需求。

希望你学到了东西,并能利用窗口函数改进你的计算和分析!

-梅林

[## 通过我的推荐链接加入 Medium - 梅林·谢费尔

阅读梅林·谢费尔(Merlin Schäfer)和其他成千上万名作家的每个故事。你的会员费用直接支持……

ms101196.medium.com](https://ms101196.medium.com/membership?source=post_page-----788d2ad57c6b--------------------------------)

用简单的,甚至非线性的时间序列模型获胜

原文:towardsdatascience.com/winning-with-simple-not-even-linear-time-series-models-6ece77be22bc

如果你的数据集较小,接下来的想法可能会很有用

Sarem SeitzTowards Data Science Sarem Seitz

·发布在 Towards Data Science ·9 分钟阅读·2023 年 5 月 10 日

--

照片由 Thomas Bormans 拍摄,来源于 Unsplash

免责声明: 标题深受 这个 精彩讲座的启发。

正如名字所示,今天我们想要考虑几乎简单到微不足道的模型。尽管目前的趋势是趋向复杂模型,即使是时间序列模型,我仍然非常相信简单性。特别是当你的数据集较小时,接下来的想法可能会很有用。

公平地说,这篇文章可能对刚开始进行时间序列分析的人最有价值。其他人应首先查看目录,自己决定是否继续阅读。

就个人而言,我对即使是最简单的时间序列模型能推到多远仍然感到非常好奇。接下来的段落展示了一些我在这个话题上逐渐积累的想法和见解。

纯 i.i.d.噪声的模型

我们从最简单的(概率性)方式开始建模(单变量)时间序列。也就是说,我们希望查看纯粹的i独立的,i同分布的,d分布随机性:

(图像作者提供)

这意味着我们的所有观测在任何时间点都遵循相同的分布(同分布)。更重要的是,我们假设观测之间完全没有相互关系(独立分布)。显然,这排除了任何自回归项。

你可能首先会问这样模型是否过于简单以至于无法用于现实问题。当然,大多数时间序列不太可能与自身过去没有统计关系。

尽管这些担忧完全正确,但我们仍然可以推导出以下内容:

任何比纯噪声模型更复杂的时间序列模型也应该能提供比纯噪声模型更好的预测。

简而言之,我们至少可以使用随机噪声作为基准模型。可以说,没有比这更简单的方法来创建基准基准了。即使平滑技术可能也需要更多的参数进行拟合。

除了这个相当明显的用例之外,i.i.d. 噪声还有另一个潜在的应用。由于其简单性,噪声模型对非常小的数据集也可能有用。考虑一下:如果大型复杂模型需要大量数据以防止过拟合,那么简单模型只需少量数据。

当然,什么数据集大小可以被视为“小型”是有争议的。

积分 i.i.d.噪声

现在,事情变得更有趣了。虽然原始 i.i.d.噪声无法解释观察值之间的自相关,但积分噪声可以。在我们进行演示之前,让我们介绍一下differencing operator

(image by author)

如果你还没听说过时间序列问题中的差分——太好了!如果你听说过,那么希望你仍然能学到一些新东西。

积分时间序列的定义

有了我们的差分算子,我们现在可以定义一个积分时间序列

(image by author)

这个定义中有几个我们应该进一步澄清的想法:

首先,你可能注意到差分算子的指数概念。你可以简单地把它看作是多次进行微分。对于平方差分算子,这看起来如下:

(image by author)

正如我们将看到的,多重差分算子使我们可以一次处理不同的时间序列模式。

第三,通常的约定是简单地写

(image by author)

我们会很乐意在这里采用这一约定。此外,我们称这样的时间序列为简单积分,而不参考其顺序或季节性。

显然,我们还需要将差分表示重新转换回其原始领域。在我们的记号中,这意味着我们反转差分变换,即

(image by author)

必须适用于任意差分变换。如果我们展开这个公式,我们得到

(image by author)

这些简化源于差分运算符是线性运算符的事实(我们在这里不会详细讨论)。从技术上讲,最后的方程仅仅表示下一个观测值是当前观测值加上一个增量。

在一个预测问题中,我们通常会有一个变化的预测。

(图片作者提供)

让我们将这个预测表示为

(图片作者提供)

强调这不是实际变化,而是预测的变化。因此,积分时间序列的预测为

(图片作者提供)

之后,我们将这种逻辑递归应用到预测应该延续的未来:

(图片作者提供)

白噪声时间序列(左)及其对应的积分时间序列(右)。这两个时间序列通过简单的差分运算符及其逆相关联。(图片作者提供)

对于看似复杂的模式,积分噪声

到现在,你可能已经能想象什么是积分噪声模型。实际上,我们可以通过将一些差分运算符与随机噪声链式组合来提出无数种积分噪声模型变体。

来自积分时间序列的线性趋势: 一种可能性是简单的积分时间序列,即

(图片作者提供)

使用普通标准正态分布模拟这种模型的数据是一个有趣的练习。

结果是,这个时间序列的样本似乎展示了具有潜在变化点的线性趋势。然而,这些趋势和变化点显然完全是随机的。

这意味着简单地将分段线性函数拟合用于预测这些趋势可能是一种危险的方法。毕竟,如果变化是随机发生的,那么所有线性趋势线只是随机数据生成过程的伪影。

作为一个重要的免责声明,虽然‘不可预测’意味着从时间序列本身不可预测,但一个外部特征可能仍然能够准确预测潜在的变化点。然而,在这里,我们假设时间序列是我们唯一可用的信息来源。

下面,你可以看到上述现象的一个示例。虽然在 t=50 左右似乎有一个趋势变化,但这个变化完全是随机的。t=50 之后的上升趋势在 t=60 左右也停滞了。想象一下,如果你在 t=60 之后对上升趋势进行外推,你的模型会如何表现。

通过对标准正态噪声进行积分生成具有变化线性趋势的时间序列。(图片作者提供)

当然,有句谚语说‘永远不要说永远’,即使在这些情况下也是如此。然而,如果你应用这样的模型,你真的应该知道自己在做什么。

季节性模式: 类似于简单集成产生趋势的方式,我们也可以创建季节性模式:

正式来说,我们现在需要季节性过程的第 s 阶差分是一个平稳过程,例如:

(图片作者提供)

逆操作——将 i.i.d.过程转换回季节集成——与之前的操作类似:

(图片作者提供)

你可以把季节差分的逆操作看作是在s个周期内的cumsum操作。由于我不知道相应的本地 Python 函数,我决定通过reshape->cumsum->reshape来获得所需的结果。以下是s=4的示例:

纯 i.i.d.过程的4阶季节性。(图片作者提供)

如你所见,生成的时间序列看起来相当真实。我们可以轻松地将其作为某产品的季度销售数字出售给一位不知情的数据科学家。

我们甚至可以结合这两种类型的集成,以生成具有趋势行为的季节性时间序列:

通过对白噪声进行集成,可以显现出季节性和趋势模式。(图片作者提供)

此时,你可能会意识到这篇文章的标题有些点击诱饵。事实上,集成时间序列完全是线性模型。然而,我相信大多数人不会认为一个参数更多或更少的模型是典型的线性模型。

集成中的记忆效应: 集成时间序列的另一个有趣特性是能够建模记忆效应。

当数据中出现较大冲击或异常值时,这种效果尤为明显。考虑下面的示例,它显示了从标准Cauchy 分布中进行 i.i.d.抽样时的季节集成,顺序为s=12

i.i.d.标准 Cauchy 序列(左)及其对应的季节性集成时间序列。(图片作者提供)

i.i.d. Cauchy 序列中大约 t=20 的第一次大冲击在右侧整个集成序列中得以维持。随着时间的推移,更多的冲击发生,这些冲击也得以维持。

这个记忆属性在实践中非常有用。例如,疫情带来的经济冲击导致了许多时间序列中的持续变化。

与 NBEATS 和 NHITS 的基准比较

现在让我们使用AirPassengers数据集,来自 Nixtla 的neuralforecast,对上述想法进行快速评估。如果你经常阅读我的文章,你可能还记得这篇文章中的一般程序。

首先,我们将数据分为训练期和测试期,后者包含 36 个月的数据:

航空乘客数据集——训练和测试拆分。(作者提供的图像)

为了获得一个平稳的 i.i.d.序列,我们执行以下变换:

(作者提供的图像)

首先,平方根稳定了不断增加的方差。然后,两次差分操作去除了季节性和趋势。有关相应的反变换,请查看下方代码。

平稳性变换后的时间序列(训练集)。(作者提供的图像)

我们还可以检查稳定时间序列的直方图和密度图:

平稳时间序列的核密度和直方图。(作者提供的图像)

我们的平稳序列看起来也有些接近正态分布,这总是一个很好的特性。

现在,让我们为测试期创建预测。假设我们不知道 i.i.d.序列的确切分布,我们仅仅通过训练数据从经验分布中抽样。因此,我们通过重新整合来自经验数据的随机样本来模拟未来值:

训练集(蓝色)、测试集(红色)、均值预测(绿色线)和 90%置信区间(绿色区域)。(作者提供的图像)

这看起来非常好——均值预测非常接近测试数据。此外,我们的模拟允许我们经验性地抽样整个预测分布。因此,我们还可以轻松地添加置信区间。

最后,让我们看看我们的方法与复杂时间序列模型的比较。为此,我使用了 Nixtla 的NBEATS和NHITS的实现:

以下是测试集的均方根误差(RMSE):

  • 简单模型: 25.5021

  • NBEATS: 42.6277

  • NHITS: 62.6822

正如我们所见,我们几乎简单的模型已经比两个复杂的时间序列模型超出不少。当然,我们需要强调,这并不允许得出任何普遍性的结论。

相反,我期望神经模型在较大的数据集上超越我们简单的方法。然而,作为基准,这些简单模型总是值得考虑的。

结论——我们从中得到什么?

正如本文多次指出的:

看似复杂的时间序列仍然可能遵循相当简单的数据生成过程。

最终,你可能会花费数小时尝试拟合一个过于复杂的模型,即使基础问题几乎是微不足道的。某些时候,可能有人会出现,拟合一个简单的 ARIMA(1,0,0),并且仍然超越你的复杂神经模型。

为了避免上述最坏的情况,考虑以下想法:

在开始解决新的时间序列问题时,总是从最简单的模型开始,并将其作为所有其他模型的基准。

尽管这是数据科学领域的常识,但我觉得在这种情况下仍然值得特别强调。尤其是由于如今(在某种程度上是有道理的)围绕深度学习的炒作,直接从一些花哨的东西开始可能会很诱人。

对于许多问题,这可能正是正确的解决方案。今天没有人会在大规模语言模型嵌入几乎是免费的情况下考虑使用隐马尔可夫模型来进行自然语言处理。

然而,一旦你的时间序列变得庞大,现代机器学习可能会更好。特别是,梯度提升树 在这种大规模问题中非常受欢迎。

更具争议的方法是,你猜对了,时间序列的深度学习。虽然有些人认为这些模型在这里效果不好,但它们在像亚马逊这样的科技公司的流行程度可能说明了一切。

参考文献

[1] Hamilton, James Douglas. 时间序列分析. 普林斯顿大学出版社, 2020。

[2] Hyndman, Rob J., & Athanasopoulos, George. 预测:原理与实践. OTexts, 2018。

最初发布于 https://www.sarem-seitz.com 于 2023 年 5 月 10 日。

无需多言:自动化开发环境和构建

原文:towardsdatascience.com/without-further-ado-automate-dev-environments-and-build-f2f9bcaaae1e

通过环境和构建自动化使你的软件易于使用,给你的开发同事带来快乐。包括 Python 和 Hatch 中的代码示例。

Mattia Di GangiTowards Data Science Mattia Di Gangi

·发表于 Towards Data Science ·11 分钟阅读·2023 年 2 月 24 日

--

图片来源:Carol Jeng 于 Unsplash

大多数开发人员讨厌遗留软件,为什么?在我们的行业中,“遗留”意味着一个已经服务了多年的代码库,通常原始开发者不再在公司,而没有人能够真正维护它。

遗留软件配方中的一些重要成分是:缺乏文档、难以理解的代码、尝试修改的困难,以及构建软件以进行发布的严重困难。

然而,遗留软件之所以被称为遗留,是因为公司依赖它,这也是为什么尽管大多数开发人员不知道如何处理它,它仍然在使用。我们从过去的同事那里继承了它,因此我们有责任让它在未来继续像过去一样工作。

然而,有一些软件满足上述遗留软件的要求,但却没有服务多年的优点。部分代码也相当近期。这意味着,人们编写的代码已经根据其负面定义成为“遗留”代码。

不愉快的一天

今天是星期一,你被分配了一个新项目。在早上 9 点的会议上,你了解到你现在在Petty工作,这是一个用于搜索宠物图片的新公司产品。你的第一个任务是开发Petognizer,一个识别图片中宠物的组件。它目前在生产中,但只能识别猫和狗。现在管理层希望它也能识别仓鼠和兔子。

你得到了公司 GitLab 账户中的仓库链接,并准备使用它。

结果是你不能。

这个仓库没有 README,也没有叫做docs或类似的文件夹。看来你需要自己进行一些探索。

现在是 10:30,你开始挖掘仓库。经过 30 分钟你明白了在哪里可以找到训练代码、数据预处理脚本和推理代码。这看起来过于复杂,但现在不是进入细节的时候,你想对它有一个整体的了解。

你还发现了一个可能用于生产的 Dockerfile,但这个并没有明确写在任何地方,还有一个requirements.txt,这当然是一个 Python 项目,因为它涉及到机器学习。

现在是 11:30,你感觉准备好了要尝试一下。你知道新的数据集在哪里,所以现在你想开始数据准备流程。于是,你想安装包,却发现缺少setup.pypyproject.toml。你问你的同事是否这个包在公司私有的pypi服务器上,但得到的回答是否定的。毕竟,如果没有构建说明,它怎么可能被打包?

显然,你不得不走“脏活”的路。你下载了仓库,找到了数据准备的入口点,这不在__main__.py文件中,将项目的根文件夹添加到你的PYTHONPATH环境变量中。然后,你创建一个新的 python 环境,使用venv,运行pip install -r requirements.txt来安装所有必要的依赖,运行脚本,然后……瞧!

ImportError: module xxx not found

你以为requirements.txt会提供开发依赖,但显然不是。但现在你看了看手表,发现已经一点钟了,于是决定和同事一起去吃午饭。你会在之后搞清楚。

当你从午休回来时,你再次充满活力,准备再次攻克这个问题。

现在你的工作是一个一个地安装缺失的依赖,每次安装一个库后你重新运行它,等待一会儿,接着下一个 ImportError 就会出现。你在 3 点钟前安装完所有东西,仍然在想requirements.txt到底是用来干什么的。

你已经在这个过程中几个小时了,你仍然不确定是否真的可以运行这个软件来满足你的需求。而你到目前为止的工作感觉完全浪费了。

图片来源:Anthony Intraversato 在 Unsplash

在另一个平行世界中,你简单地运行了

pip install petognizer

或者,也许,如果训练依赖作为额外的提供

pip install petognizer[train]

然后在 README 中找到了数据准备的命令,你在大约 10 分钟内准备好,一切都在午休前完成。

如果有一个pyproject.toml文件,将会大大简化包的安装(如果它在 pypi 服务器上会更容易)以及准备构建环境的过程。此外,一些适当的文档将使得了解如何实际使用代码进行不同任务变得更加简单。

每天都有如此多才华横溢的开发人员浪费时间,只因为某些代码维护者没有花时间设置文档和自动化,以提供良好开发体验的基础。

现在你负责这个代码库,你可以选择保持原样,把同样的挫败感留给下一个将要使用它的开发人员。或者,你可以决定为组织带来一些欢乐,使得代码库更容易使用。

虽然编写文档是一个需要长期投入的过程,需要对代码有深入理解(但一些正确的文档总比没有文档要好),自动化构建和开发环境的创建可以解放开发人员免于很多麻烦,使他们能更快地在代码库中提高生产力。

## 阅读和编写 ML 研究论文的技巧

通过多次的同行评审获得的经验教训

towardsdatascience.com

Hatch

Hatch是一个由 Python 打包机构(PyPA)支持的自动化工具,它能够以高度的简便性创建环境和构建软件。

它使用一个名为pyproject.toml的文件,这是“新”的统一 Python 项目设置文件。之所以称之为统一,是因为它被 Hatch 和其他类似工具(如Poetry)使用,并且它可以指定其他工具的配置,例如black或flake8等。

我们可以使用 Hatch 创建一个新项目,命令为

hatch new "My Project"

它将创建一个标准的项目结构,如

my-project
├── src
│   └── my_project
│       ├── __about__.py
│       └── __init__.py
├── tests
│   └── __init__.py
├── LICENSE.txt
├── README.md
└── pyproject.toml

它包含两个独立的包:一个用于源代码,另一个用于测试,还有一个包含一些默认文本的 README.md,一个 LICENSE.txt,一个名为__about__.txt的文件,里面包含软件版本,以及pyproject.toml

Hatch 也可以通过简单运行来用于现有项目

hatch new --init

在其根文件夹中。这将生成一个默认的pyproject.toml文件,需要进行修改。

可以通过pipx install hatch来安装 Hatch,使其作为用户命令可用,而不会“污染”现有的默认 Python 环境。

## 带有注册表的 Python 多态性 | Python 模式

学习一种模式来隔离包,同时扩展 Python 代码的功能。

towardsdatascience.com

pyproject.toml 的快速浏览

Hatch 文档非常详细,尽管有时清晰度可以提高,因此在这里我们将专注于与本文主题相关的部分和一些必要的背景信息。

pyproject.toml 带有一个初始部分,描述性很强。在这里我们可以找到项目的名称、描述、作者、支持的 Python 版本和版本。

以我为说明制作的这个玩具库为例 github.com/mattiadg/demo_it-analyze/blob/main/pyproject.toml (感谢我的朋友 Mario 帮助制作这个小应用的前端)

[project]
name = "demo-it-analyze"
description = ''
readme = "README.md"
requires-python = ">=3.7"
license = "MIT"
keywords = []
authors = [
  { name = "Mattia Di Gangi", email = "mattiadg@users.noreply.github.com" },
]
classifiers = [
  "Development Status :: 4 - Beta",
  "Programming Language :: Python",
  "Programming Language :: Python :: 3.7",
  "Programming Language :: Python :: 3.8",
  "Programming Language :: Python :: 3.9",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: Implementation :: CPython",
  "Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = [
  "flask",
  "pydantic",
  "spacy",
]
dynamic = ["version"]

[project.urls]
Documentation = "https://github.com/unknown/demo-it-analyze#readme"
Issues = "https://github.com/unknown/demo-it-analyze/issues"
Source = "https://github.com/unknown/demo-it-analyze"

应该很清楚,除了两个字段。dynamic = ["version"] 表示软件版本需要动态计算,稍后文件中提到它必须在 __about.py__ 文件中找到。另一方面,字段 dependencies 列出了的依赖关系。这些是与软件一起安装的包。当打包时,我这里只列出了名称,但也可以应用一些版本约束,如 ==<= 等… 它们与pip使用的相同。

请注意,这些依赖项是在 [project] 标签下指定的,但还有其他仅为某些环境指定的依赖项。然而,这些是包和所有环境共同的。代码库没有它们就无法工作。

工具配置

如前所述,pyproject.toml 可用于指定项目中使用的工具的配置,而 Hatch 是可以在这里配置的工具之一。

[tool.hatch.version]
path = "src/__about__.py"

[tool.hatch.envs.default.env-vars]
  FLASK_APP = "demo_it_analyze/app.py"

[tool.hatch.envs.default]
extra-dependencies = [
  "pytest",
  "pytest-cov",
  "mypy",
]

[tool.hatch.envs.default.scripts]
cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=demo_it_analyze --cov=tests {args}"
no-cov = "cov --no-cov {args}"
download_ita = "python -m spacy download it_core_news_sm"
serve = "flask --app src.app run"

[tool.hatch.envs.train]
template = "default"
extra-dependencies = [
  "spacy[cuda-autodetect,transformers,lookups]"
]

[tool.hatch.envs.test]
template = "default"

[[tool.hatch.envs.test.matrix]]
python = ["37", "38", "39", "310", "311"]

请注意,这里的字段以 tool.hatch 开头,而不是 project

我们首先告诉 Hatch 到哪里去寻找软件版本。然后,我们为默认 Python 环境指定 FLASK_APP。Hatch 允许我们创建多个环境,默认环境是始终定义的,并且所有其他环境都从它派生。我们可以为环境定义依赖项,这些是开发依赖项,因为环境在包代码中不存在。

使用 Hatch,我们可以通过执行来创建默认环境

hatch env create 

它将创建具有所有依赖项的默认环境。我们还可以通过运行指定在 pyproject.toml 中的另一个环境(例如示例中也定义了 test)来创建另一个环境

hatch env create test

然而,Hatch 的一个好处是我们不需要显式创建环境。命令 hatch run 允许我们在环境中运行任何命令或脚本。在上面复制的文件中,我们有这个脚本定义:

[tool.hatch.envs.default.scripts]
cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=demo_it_analyze --cov=tests {args}"
no-cov = "cov --no-cov {args}"
download_ita = "python -m spacy download it_core_news_sm"
serve = "flask --app src.app run"

因此,我们可以运行,例如

hatch run serve

hatch 会创建默认环境(如果不存在的话),同步其依赖pyproject.toml 中指定的依赖,并在环境中运行 flask --app src.app run,启动我们的 flask 服务器。我们还可以指定在特定环境中运行命令,例如 test

hatch run test:serve

注意,我们不需要安装任何东西。Hatch 会读取 pyproject.toml 并识别可以运行的脚本。

你看到我们在这里做了什么吗?项目维护者在一个文件中指定了一些配置选项,然后对任何使用该仓库的人来说,创建完全相同的环境变得非常简单。不再为重现开发环境而苦恼!

## 使用 inotifywait 进行数据处理自动化

如何在拥有一个生产就绪的 MLOps 平台之前进行自动化

[towardsdatascience.com

构建

Hatch 也是一个不错的构建工具。它有自己的构建后端,称为hatchling,而 pyproject.toml 附带了一些构建配置。根据示例:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.sdist]
exclude = [
  "/.github",
  "/docs",
]

[tool.hatch.build.targets.wheel]
packages = ["src"]

我们必须指定构建后端,因为 Hatch 可以作为所有功能的良好前端,然后用其他工具进行构建。然后我们看到可以为 sdistwheel 两种构建格式指定一些选项。然而,对于像这样的玩具项目,里面的内容不多。

然后,我们可以简单地运行 hatch build,它会构建两个目标。或者我们可以用 hatch build -t wheel 来指定其中一个目标,例如。

当你拥有 pypi 服务器的凭据时,无论是官方的、测试服务器的还是私有服务器的,你都可以通过简单地使用 hatch publish 从构建中发布你的包。

当然,发布包是一项需要谨慎处理的工作,在确保软件正常工作后进行。但随后,hatch 处理所有细节,我们只需要运行两个简单的命令。

如果你启用了类似Github Actions的 CI/CD 流水线,你也可以在那里指定这些命令,以实现更多的自动化!

我们没有覆盖的内容

使代码库易于使用可能是一个具有挑战性的问题,虽然构建自动化是朝着正确方向迈出的有用一步,但这远未满足所有需求。

我们在介绍故事中已经提到过文档,我们将不再详细讨论它。

另一个重要方面是彻底的测试套件,它有三个主要目的。

首先,它有助于验证代码是否正确。任何人都可以阅读测试并检查它们是否证明了正确的内容。如果测试通过,那么我们对代码的正确性有了一些保障。

其次,它们作为防止回归的安全网。当我们修改一个代码库时,特别是一个我们不太熟悉的代码库,全面的测试套件可以捕捉到修改是否破坏了预期的行为。

第三,如果文档不够详尽,测试可以展示我们难以理解的函数或类的使用方法,因为测试本质上就是代码示例。

另一个重要的遗漏部分是持续集成/持续交付管道(CI/CD),它在每次代码添加到远程仓库时运行一些检查,还可以构建和部署软件。CI/CD 管道展示了代码库中预期的代码质量,因为它可以运行诸如代码检查工具或静态类型检查器(针对像 Python 这样的动态语言)以及测试。管道中的构建和部署步骤还确保这些操作容易执行,并具有足够的自动化水平。

结论

Hatch 是一个出色的自动化工具,虽然需要一些维护和配置选项的指定,但它通过显著简化环境和构建创建,带来了极大的回报。

当开发者可以轻松地使用软件,而无需花费数小时去弄清楚如何设置时,他们更容易提高生产力,同时也能找出文档中遗漏的部分,因为他们可以运行代码。

感谢您阅读到这里,这篇文章较长,但我希望我能说服您,使用像 Hatch 这样的工具可以大大提升您和其他开发者的开发体验。

参考文献

一些书籍对这篇文章产生了深远的影响。最突出的书是独角兽项目,由著名的 DevOps 作者Gene Kim编写,并由 IT Revolution 编辑。一个高级开发者被流放到一个功能失调的部门,在那里开发者无法本地运行代码。在小说中,她和一群开明的同事尽力解救她的同事,摆脱成千上万的阻碍和官僚主义,去做他们真正被雇佣来做的事情。

深入理解持续交付由 Christie Wilson 编写,并由 Manning 编辑,是一本从零开始建立 CI/CD 管道并在过程中改进代码库的指南。

重构遗留系统由 Chris Birchall 编写,并由 Manning 编辑,全面探讨了遗留软件,包括如何处理我们被分配的遗留系统,如何改进它,以及如何确保我们不会编写遗留软件。

Medium 会员

您喜欢我的写作并考虑订阅 Medium 会员以无限制访问文章吗?

如果你通过此链接订阅,你将通过你的订阅支持我,而无需支付额外费用 medium.com/@mattiadigangi/membership

词嵌入的解释

原文:towardsdatascience.com/word-embeddings-explained-c07c5ea44d64

Ajay HalthorTowards Data Science Ajay Halthor

·发布于 Towards Data Science ·7 min read·2023 年 5 月 30 日

--

在自然语言处理领域,我们处理的是单词。然而,计算机无法直接理解单词,因此需要将其转换为数值表示。这些数值表示,称为向量或嵌入,由可以被人类解释或无法解释的数字组成。在这篇博客中,我们将深入探讨这些词汇表示的学习进展。

1 N-grams

图 1: 句子的 N-gram 向量表示(作者提供的图片)

让我们以 n-grams 为例,更好地理解这一过程。假设我们有一句话,希望计算机能够理解。为此,我们将句子转换为数值表示。这种表示包括各种词汇组合,如 unigrams(单词)、bigrams(词对)、trigrams(三词组)以及更高阶的 n-grams。结果是一个可以表示任何英语句子的向量。

在图 1 中,考虑对句子“This is a good day”进行编码。假设向量的第一个位置表示在原句中出现的 bigram “good day”的次数。由于它出现了一次,因此这个位置的数值表示为“1”。同样,我们可以用这个向量的不同位置来表示每一个 unigram、bigram 和 trigram。

这个模型的一个主要优点是可解释性。这个向量中的每个数字都有一定的含义,人类可以关联。当进行预测时,不难看出什么影响了结果。然而,这种数值表示有一个主要的缺点:维度灾难。这个 n-gram 向量很大。如果用于统计建模,需要从中挑选特定的部分。原因在于维度灾难。随着向量维度的增加,句子表示之间的距离也增加。这有利于表示更多信息,但如果过于稀疏,统计模型难以判断哪些句子在物理上(从而在意义上)更接近。此外,挑选是一个手动过程,开发人员可能会遗漏一些有用的 n-gram 表示。

2 神经网络

图 2:神经概率语言模型(Bengio 等,2003)

为了解决这一不足,神经概率语言模型在 2003 年被引入。语言模型预测序列中下一个出现的词。例如,一个训练好的语言模型会将“我想要一个法式”这个词序列,生成下一个词“吐司”。如图 2 所示的神经语言模型在很大程度上以相似的方式工作,即使用前 N 个词的上下文来预测下一个词。

对于每个词,我们学习一个密集表示,这是一个包含固定数量数字的向量来表示每个词。与 n-gram 不同,向量中的这些数字不易被人类直接解释。然而,它们捕捉了人类可能未察觉的各种细微差别和模式。

激动人心的部分是,由于这是一个神经网络,我们可以端到端地训练它以掌握语言建模的概念,并同时学习所有词向量表示。然而,训练这样的模型可能计算开销很大

例如,如果我们用一个包含 100 个数字的向量来表示每个词,而我们需要将这些向量连接起来,这将涉及到成千上万的数字。考虑到词汇量可能达到数万个或更多,我们可能会面临数百万甚至数千万个参数需要计算。这在处理大型词汇表、大量示例或每个词表示的高维度时成为一个挑战。

理想情况下,较大的维度将使我们能够捕捉到语言的更复杂特性,考虑到语言本质上的复杂性。

在接下来的十年里,推出了各种架构来提升词嵌入的质量。一个这样的架构在论文中描述了,使用新颖的神经网络架构进行快速语义提取。它引入了为每个词汇加入位置性信息的概念,以改进嵌入。然而,这种方法也存在训练时计算开销大的缺点,仅用于学习词嵌入。

3 Word2Vec

图 3:向量空间中词表示的高效估计(Milikov 等,2013)

在 2013 年,生成词嵌入的一个重大突破是 Word2Vec 的引入。这篇论文提出了两种模型,即连续词袋模型(CBOW)和 Skip-gram 模型,旨在保持简单性同时理解词嵌入。在 CBOW 模型中,当前词是基于前两个和后两个词进行预测的。投影层表示该特定词的词嵌入。而 Skip-gram 模型则执行类似的任务,但方向相反,给定一个词预测其上下文周围的词。同样,投影层表示当前词的向量表示。在训练这些网络之后,会得到一个词及其对应嵌入的表格。这种架构更简单,参数更少,标志着预训练词嵌入时代和 word2vec 概念的到来。

然而,这种方法也有一些局限性。首先,它生成每次出现的词的相同向量表示,无论其上下文如何。例如,“drag queen”中的“queen”和“king and queen”中的“queen”将具有相同的词嵌入,尽管它们含义不同。此外,这些词嵌入的生成考虑了有限的上下文窗口,在训练阶段仅查看前两个词和后两个词。这一局限性影响了模型的上下文意识。

4 ELMo

图 4:深度上下文化的词表示(Peters 等,2018)

为了提高生成嵌入的质量,ELMo(来自语言模型的嵌入)在 2018 年被引入。ELMo 是一种双向 LSTM(长短期记忆)模型,在同一个训练过程中处理语言建模和创建密集的词嵌入。该模型通过利用 LSTM 单元,有效地捕捉上下文信息,尤其是在较长的句子中。然而,与 LSTM 模型类似,ELMo 也存在一些缺陷。训练这些模型可能较慢,并且它们采用了一种称为 BPTT(时间反向传播)的截断版本。此外,它们并非真正双向,因为它们分别学习前向和后向上下文,然后将其连接,这可能导致一些上下文信息的丢失。

5 Transformers

图 5:Attention is all you need(Vaswani 等,2017)

在 ELMo 引入之前不久,Attention Is All You Need 论文 提出了 Transformer 神经网络架构。Transformers 包括一个编码器和一个解码器,这两个部分都结合了位置编码以生成具有上下文意识的词向量。例如,当输入句子 “I am Ajay” 时,编码器生成三个密集的词嵌入表示,保留词义。Transformers 还解决了 LSTM 模型的缺点。由于数据可以并行处理,利用 GPU,训练速度更快。此外,Transformers 是深度双向的,因为它们采用了一个注意力机制,使得词语可以同时关注前后词语,从而实现有效的上下文理解。

Transformers 的主要问题是对于不同的语言任务,我们需要大量的数据。然而,如果人类对语言有一定的内在理解,那么他们不需要看到大量的例子就能理解如何回答问题或进行翻译。

6 BERT 和 GPT

图 6:BERT:深度双向 Transformers 的语言理解预训练(Devlin 等,2019)

为了克服 Transformer 模型在语言任务中的局限性,引入了两个强大的模型,BERT(双向编码器表示从 Transformers)和 GPT(生成预训练 Transformer)。这些模型利用了迁移学习,涉及两个训练阶段。

在第一阶段,称为预训练阶段,模型从大量数据中学习关于一般语言理解、上下文和语法的知识。在这个阶段,它们获得了坚实的知识基础。在第二阶段,称为微调阶段,模型通过提供特定任务的数据进行训练。这一微调过程使模型能够专注于执行期望的任务而无需大量的任务特定数据

BERT 在两个任务上进行了预训练:掩蔽语言建模和下一个句子预测。通过这种预训练,BERT 对每个单词的上下文和含义有了深刻的理解,从而改进了词嵌入。它可以在特定任务上进行微调,如问答或翻译,使用相对较少的任务特定数据。

同样,GPT 在语言建模上进行了预训练,这涉及预测句子中的下一个单词。这种预训练帮助 GPT 发展了对语言的全面理解。之后,它可以在特定任务上进行微调,以便像 BERT 一样利用其语言理解能力。

BERT 和 GPT 都具备 Transformer 架构和学习各种语言任务的能力,相比于早期方法,提供了更优越的词嵌入。这就是为什么 GPT 尤其作为许多现代语言模型的基础,例如 ChatGPT,使得先进的自然语言处理和生成成为可能。

7 结论

在这篇博客中,我们探讨了计算机如何通过被称为“嵌入”的表示来理解语言。我们见证了近年来的进展,特别是随着变换器的兴起,成为现代语言模型的基础。如果你对从头开始构建自己的变换器模型感兴趣,查看 这份视频播放列表,其中深入探讨了相关的代码和理论。祝学习愉快!

解释 Word2Vec、GloVe 和 FastText

原文:towardsdatascience.com/word2vec-glove-and-fasttext-explained-215a5cd4c06f

计算机如何理解单词

Ajay HalthorTowards Data Science Ajay Halthor

·发表于 Towards Data Science ·10 min 阅读·2023 年 6 月 20 日

--

Growtika 在 Unsplash 上的照片

计算机不像我们那样理解单词。它们更喜欢处理数字。因此,为了帮助计算机理解单词及其含义,我们使用一种叫做嵌入的技术。这些嵌入以数学向量的形式数值化地表示单词。

这些嵌入的酷炫之处在于,如果我们正确学习它们,那么具有相似含义的单词将具有相似的数值。换句话说,它们的数值会更接近。这使得计算机能够基于数值表示把握不同单词之间的联系和相似性。

一种学习单词嵌入的显著方法是 Word2Vec。在这篇文章中,我们将深入探讨 Word2Vec 的复杂性,并探索其各种架构和变体。

Word2Vec

图 1: Word2Vec 架构 (来源)

在早期,句子通过 n-gram 向量进行表示。这些向量旨在通过考虑单词序列来捕捉句子的本质。然而,它们存在一些局限性。n-gram 向量通常非常大且稀疏,这使得它们在计算上很具挑战性。这造成了一个被称为维度诅咒的问题。本质上,这意味着在高维空间中,表示单词的向量距离过远,从而很难确定哪些单词真正相似。

然后,在 2003 年,随着神经概率语言模型的引入,出现了一个显著的突破。这个模型通过使用所谓的连续密集向量完全改变了我们对词语的表示方式。与离散且稀疏的 n-gram 向量不同,这些密集向量提供了连续的表示。即使这些向量发生小的变化,也会产生有意义的表示,尽管它们可能不直接对应特定的英语单词。

在这一激动人心的进展基础上,Word2Vec框架于 2013 年出现。它提供了一种将词义编码为连续密集向量的强大方法。在 Word2Vec 中,引入了两种主要架构:连续词袋模型(CBoW)和 Skip-gram。

这些架构为生成高质量的词嵌入的高效训练模型打开了大门。通过利用大量的文本数据,Word2Vec 使单词在数字世界中栩栩如生。这使计算机能够理解单词之间的上下文含义和关系,为自然语言处理提供了一种变革性的方法。

连续词袋模型(CBoW)

图 2:CBoW 训练示意图(作者提供的图片)

在本节及下一节中,让我们了解如何使用一个包含五个单词的小词汇表训练 CBoW 和 skip-gram 模型:biggest、ever、lie、told 和 the。我们有一个示例句子“The biggest lie ever told”。我们将如何将其传递给 CBoW 架构?这在图 2中显示,但我们也会描述这个过程。

假设我们将上下文窗口大小设置为 2。我们取“the”、“biggest”、“ever”和“told”这几个词,并将它们转换为 5x1 的独热向量。

然后将这些向量作为输入传递到模型,并映射到一个投影层。假设这个投影层的大小为 3。每个词的向量乘以一个 5x3 的权重矩阵(在输入间共享),得到四个 3x1 的向量。取这些向量的平均值,得到一个 3x1 的向量。然后使用另一个 3x5 的权重矩阵将这个向量投影回 5x1 的向量。

这个最终向量代表了中间单词“lie”。通过计算真实的独热向量和实际的输出向量,我们得到一个损失,用于通过反向传播更新网络的权重。

我们通过滑动上下文窗口并将其应用于成千上万的句子来重复这个过程。训练完成后,模型的第一层,尺寸为 5x3(词汇表大小 x 投影大小),包含了学习到的参数。这些参数用作查找表,将每个单词映射到其对应的向量表示。

Skip-gram

图 3:Skip-gram 训练示意图(作者提供的图片)

在 skip-gram 模型中,我们使用了类似于连续词袋(CBoW)模型的架构。然而,与其根据周围词预测目标词相反,我们将场景反转,如图 3所示。现在,词“lie”成为输入,我们的目标是预测其上下文词。名称“skip-gram”反映了这种方法,因为我们预测的上下文词可能会“跳过”几个词。

为了说明这一点,我们来看一些例子:

  • 输入词“lie”与输出词“the”配对。

  • 输入词“lie”与输出词“biggest”配对。

  • 输入词“lie”与输出词“ever”配对。

  • 输入词“lie”与输出词“told”配对。

我们对训练数据中的所有词汇重复这一过程。一旦训练完成,第一层的参数,其维度为词汇大小 x 投影大小,将捕捉输入词与其对应向量表示之间的关系。这些学习到的参数使我们能够在 skip-gram 模型中将输入词映射到其相应的向量表示。

优点

  1. 以简洁的方式克服维度灾难:Word2Vec 提供了一个直接而高效的解决方案来应对维度灾难。通过将词表示为密集向量,它减少了与传统方法(如 n-gram 向量)相关的稀疏性和计算复杂度。

  2. 生成向量,使得意义相近的词具有更接近的向量值:Word2Vec 的嵌入展现了一个有价值的特性,即具有相似意义的词由数值更接近的向量表示。这允许捕捉语义关系并执行诸如词语相似性和类比检测等任务。

  3. 预训练的嵌入用于各种 NLP 应用:Word2Vec 的预训练嵌入广泛可用,并可以用于各种自然语言处理(NLP)应用。这些嵌入经过大规模语料库的训练,为情感分析、命名实体识别、机器翻译等任务提供了宝贵的资源。

  4. 自监督框架用于数据增强和训练:Word2Vec 以自监督的方式运行,利用现有数据来学习词表示。这使得收集更多数据和训练模型变得容易,因为它不需要大量标记数据集。该框架可以应用于大量未标记的文本,从而增强训练过程。

缺点

  1. 对全局信息的有限保留:Word2Vec 的嵌入主要关注捕捉局部上下文信息,可能无法保留词之间的全局关系。这一限制可能会影响需要更广泛理解文本的任务,例如文档分类或文档级别的情感分析。

  2. 不太适合形态丰富的语言:形态丰富的语言特征是复杂的单词形式和词形变化,这可能对 Word2Vec 造成挑战。由于 Word2Vec 将每个单词视为原子单位,它可能难以捕捉此类语言中的丰富形态学和语义细微差别。

  3. 缺乏广泛的上下文意识:Word2Vec 模型在训练过程中只考虑了目标单词周围的局部上下文窗口。这种有限的上下文意识可能导致在某些语境中对单词意义的理解不完整。它可能难以捕捉某些语言现象中存在的长期依赖关系和复杂的语义关系。

在接下来的部分中,我们将看到一些能够解决这些缺点的词嵌入架构。

GloVe: 全球向量

Word2Vec 方法在一定程度上成功地捕捉了局部上下文,但没有充分利用语料库中可用的全局上下文。全局上下文指的是使用语料库中的多个句子来收集信息。这就是GloVe发挥作用的地方,因为它利用词-词共现来学习词嵌入。

词-词共现矩阵的概念是 GloVe 的关键。它是一个矩阵,捕捉语料库中每个单词在其他每个单词上下文中的出现情况。矩阵中的每个单元格表示一个单词在另一个单词上下文中的出现次数。

图 4:词共现概率比率的示例(来源)

与 Word2Vec 直接处理共现概率不同,GloVe 从共现概率的比率开始。在图 4的背景下,P(k | ice)表示单词 k 在“ice”一词上下文中的出现概率,而 P(k | steam)表示单词 k 在“steam”一词上下文中的出现概率。通过比较比率 P(k | ice) / P(k | steam),我们可以确定单词 k 与冰或蒸汽的关联。如果比率远大于 1,则表明与冰的关联更强。相反,如果接近 0,则表明与蒸汽的关联更强。比率接近 1 则表明与冰或蒸汽没有明确的关联。

例如,当 k = “solid”时,概率比率远大于 1,表明与冰的关联很强。另一方面,当 k = “gas”时,概率比率接近 0,表明与蒸汽的关联更强。至于“water”和“fashion”这两个词,它们与冰或蒸汽都没有明确的关联。

基于概率比率的词语关联正是我们希望实现的目标。而在学习 GloVe 的嵌入时,这一点得到了优化。

FastText

传统的 word2vec 架构除了缺乏全球信息的利用外,也无法有效处理形态丰富的语言。

那么,语言形态丰富意味着什么呢?在这种语言中,单词的形式可以根据其使用的上下文而变化。我们以一种名为“卡纳达语”的南印度语言为例。

在卡纳达语中,“house”的单词是写作 ಮನೆ (mane)。然而,当我们说“在房子里”时,它变成了 ಮನೆಯಲ್ಲಿ (maneyalli),而当我们说“从房子里”时,它则变为 ಮನೆಯಿಂದ (maneyinda)。如你所见,只有介词发生变化,但翻译的词具有不同的形式。在英语中,这些都是简单的“house”。因此,传统的 word2vec 架构会将所有这些变体映射到相同的向量。然而,如果我们为形态丰富的卡纳达语创建一个 word2vec 模型,那么这三种情况中的每一种将被分配不同的向量。此外,卡纳达语中的“house”可以有比这三个例子更多的形式。由于我们的语料库可能不包含所有这些变体,传统的 word2vec 训练可能无法捕捉所有不同的词表示。

为了解决这个问题,FastText 通过在生成词向量时考虑子词信息引入了一个解决方案。FastText 不将每个单词作为一个整体,而是将单词分解为从三元组到六元组的字符 n-grams。这些 n-grams 被映射到向量上,然后这些向量被聚合以代表整个单词。聚合后的向量接着被输入到 skip-gram 架构中。

这种方法能够识别语言中不同单词形式的共同特征。即使我们可能没有在语料库中见过单词的每一种形式,所学到的向量仍能捕捉这些形式之间的共性和相似性。形态丰富的语言,如阿拉伯语、土耳其语、芬兰语以及各种印度语言,可以受益于 FastText 能生成考虑不同形式和变体的词向量。

上下文意识

图 5:ELMo 和 BERT (来源)

尽管有其优点,前述的 word2vec 架构存在一个局限性:它们为给定的单词生成相同的向量表示,而不考虑其上下文。

为了说明这一点,我们考虑以下两个句子:

  1. “那个变装皇后真棒。”

  2. “她有一张王牌和一张皇后,手牌完美。”

在这些句子中,单词“queen”有不同的含义。然而,在 word2vec 架构中,这两种情况下“queen”的向量是相同的。这并不理想,因为我们希望词向量能够根据上下文捕捉和表示不同的含义。

为了解决这一问题,引入了更先进的架构,如LSTM单元。这些架构被设计为将上下文信息纳入词语表示中。随着时间的推移,基于 Transformer 的模型如BERT和GPT的出现,推动了我们今天看到的大规模语言模型的发展。这些模型在考虑上下文和生成对周围词语和句子敏感的词语表示方面表现出色。

通过考虑上下文,这些先进的架构使得生成更加细致和有意义的词向量成为可能,确保相同的词在不同的具体上下文中可以有不同的向量表示。

结论

总之,本文提供了关于 word2vec 架构及其使用连续稠密向量表示词语的能力的见解。后续实现如 GloVe 利用了全局上下文,而 FastText 使得对形态丰富的语言如阿拉伯语、芬兰语和各种印度语言的向量学习变得高效。然而,这些方法的一个共同缺点是,在推理过程中,它们对一个词分配相同的向量,无论其上下文如何,这可能会阻碍对具有多重含义的词的准确表示。

为了解决这一局限性,后续在自然语言处理(NLP)领域的进展引入了 LSTM 单元和 Transformer 架构,这些技术在捕捉特定上下文方面表现出色,并成为现代大型语言模型的基础。这些模型能够理解并生成根据周围上下文变化的词语表示,适应了不同场景中词语的细微含义。

然而,重要的是要承认 word2vec 框架依然具有重要意义,因为它继续推动着自然语言处理领域中的众多应用。尽管词义的上下文变化带来了挑战,其简单性和生成有意义的词嵌入的能力仍然被证明是有价值的。

欲了解更多关于语言模型的信息,查看这个 YouTube 播放列表。

快乐学习!

使用 Julia 进行 Wordle 单词长度和字母频率分析

原文:towardsdatascience.com/wordle-word-length-and-letter-frequency-analysis-using-julia-3fc5c63fedba?source=collection_archive---------12-----------------------#2023-02-10

探索英语单词数据集以提升我们的游戏

Naresh RamTowards Data Science Naresh Ram

·

Follow 发布于 Towards Data Science ·19 分钟阅读·2023 年 2 月 10 日

--

受 Wordle 启发。图像由作者提供。

Wordle™ 是一款无需介绍的绝佳游戏。但保持连胜并非易事。为了更好地理解游戏的运作方式,我们将加载英语单词到 Julia 中,并对它们进行一点分析。我们将关注字母频率以及特别是 5 字母单词,以帮助在游戏中做出更有根据的猜测。同时,我们还将探讨为何选择五个字母作为游戏的单词长度。

虽然我不能保证它会减少你的猜测次数,但我可以保证使用 Julia 和 Julia Plots 进行数据分析会很有趣。

在我工作的 AAXIS 公司,我们实施了面向企业的数字商务解决方案以及面向消费者的数字商务解决方案。这包括将大量数据从现有的旧系统迁移到具有不同数据结构的新系统。我们使用各种数据工具来分析源数据,以确保一致性。我通常会使用本文中概述的技巧。熟悉这些技巧的好方法是经常使用它们,而 Wordle 单词列表听起来是一个有趣的练习方式。

让我们开始吧。

设置

图片由 Linus Mimietz 提供,来源于 Unsplash

如果你计划跟我一起学习 Julia,我强烈建议你阅读这一部分。如果不计划,你可以安全地跳过。

安装 Julia

请从 Julia Language 网站安装 Julia。该网站提供适用于 Windows、Mac 和 Linux 操作系统的安装程序。我还为 VSCode 安装了 Julia Language Support 扩展,并在 VSCode 中执行这些命令的 Julia REPL。这样,我的图形会显示在窗口中,使数据集分析变得极为方便。

Visual Studio Code 中的 Julia REPL。图片由作者提供。

现在,让我们加载图形和其他所需的库。安装完成后,你可以通过输入 CNTRL-SHIFT-P 并选择 Julia: Start REPL 来打开 Julia REPL 窗口。

在 Julia REPL 中输入这些命令来检查一切是否正常:

julia> println("Hello world")
Hello World

julia> f(x) = x² + 3x + 2
f (generic function with 1 method)

julia> f(2)
12

在 VSCode 中,当代码被选中时,按 SHIFT-ENTER 可以执行它,并在下方的 Julia REPL 中输出结果。你可以直接从本文中剪切代码,粘贴到 VSCode 中,选择代码,然后按 SHIFT-ENTER 执行它。为了方便这种方法,我将跳过代码中的 julia> 提示。请注意,你可以在 REPL 中输入所有这些命令,即使是包含 for-loops 或函数的长命令。

接下来,让我们加载 Plots。你的用户界面可能会有所不同,因为 Julia 可能会下载和安装一些包。我们还需要 DelimitedFiles 和 Statistics 包:

import Pkg
Pkg.add("Plots")
using Plots
using DelimitedFiles
using Statistics

plot(f,-10,10)

最后一行应该会生成一个如上图所示的图形。

词汇数据集

在线有多个单词数据集,洁净程度不一。不幸的是,我没有权限在这篇文章中使用大多数数据集。我选择了 SCOWL(Spell Checker Oriented Word Lists)及 Friends,这是一个关于英语单词的数据库,适用于创建高质量的单词列表,适合大多数英语方言的拼写检查器,并可供我们使用。如果你决定探索,其他可供公众用于教育目的的单词列表如下:

  • Infochimp 的简单英语单词列表是 Infochimp 提供的英语单词列表。GitHub 网站提供了该列表,但声明版权归 Infochimp 所有。Infochimp 的链接指向一个不存在的页面。

  • Collins Official Scrabble™ Words列表得到WESPA的认可,用于全球锦标赛和俱乐部比赛,不包括美国和加拿大。该软件仅限私人、非商业用途。这是一个精心策划的英语单词列表。

  • The Wordle Guess List是游戏中允许的 13K 单词的完整列表,由 Josh Wardle 版权所有,现在可能由纽约时报版权所有。该列表与上述 Scrabble 列表中的 5 字母单词子集非常匹配。答案列表,即 Wordle 的 2.5K 可能答案,可以在这里找到。

现在,让我们继续获取 SCOWL 单词列表,并为分析做好准备。前往app.aspell.net/create下载单词列表。我选择的选项如图所示。我使用了 80 字母列表进行下一部分,并将文件命名为 scowl_american_words_80.txt。

生成单词列表所选择的选项的图像。截图由作者提供。

我还下载了 35 字母选项,并将其命名为 scowl_american_words_35.txt。我将用于稍后在文章中分析更常见的单词。

对于单词使用,我使用了在Miller Center¹提供的总统演讲。这些演讲属于公共领域,因此没有使用限制。只需选择总统的名字,点击演讲,然后点击抄本。我将文本复制并粘贴到文本编辑器(VSCode)中进行分析。

为什么选择 5 字母单词?

图片由Glen Carrie提供,来源于Unsplash

我的好朋友和邻居 Milan 提出了这个问题。他认为 5 字母词汇可能是英语中最常见的词汇。通过加载我们在设置中讨论的 SCOWL 词汇表数据集并计算词汇长度的频率,这确实值得检查。

rawwords = readdlm("data/scowl_american_words_80.txt", header=false,String)
343681×1 Matrix{String}:
 "A"
 "A'asia"
 "A's"
 "AA"
 "AA's"
 "AAA"
 "AAM"
 ⋮
 "zymurgies"
 "zymurgy"
 "zymurgy's"
 "zythum"
 "zyzzyva"
 "zyzzyvas"
 "zzz"

totalWords = length(rawwords)
343681

aspell 词汇表包含了像zymurgy's这样的所有格词和像Albert.这样的专有名词。让我们把它们去掉。

dictwords = String[];
for word in rawwords
      # remove words with ', -' space and capital letter start
      if (length(word)>0 && !(occursin("\'",word) 
            || occursin("-",word) 
            || occursin(" ",word)
            || (word[1]<'a' || word[1]>'z')
            ))
          push!(dictwords,word)
      end
end

# get count
totalWords = length(dictwords)
244274

接下来,我们应该为词汇长度构建一个直方图。我知道有三种方法可以做到这一点:列表推导、map 函数和 for 循环。考虑到性能,在 Julia 中它们都应该差不多。让我们来看看:

# use list comprehension
wordlengths = zeros(Int64,0)
@time wordlengths = [length(x) for x in dictwords];
0.021251 seconds (45.30 k allocations: 4.246 MiB, 84.02% compilation time)

# use map
wordlengths = zeros(Int64,0);
@time wordlengths = map(word->length(word), dictwords);
0.024202 seconds (49.09 k allocations: 4.473 MiB, 85.97% compilation time)

# use for loop
wordlengths = zeros(Int64,0);
@time for word in dictwords
  push!(wordlengths, length(word))
end
0.027516 seconds (488.05 k allocations: 14.131 MiB) 

一般来说,虽然列表推导在性能上略好,但它们都非常接近,我会选择 for 循环,因为它使逻辑清晰易懂。现在,词汇长度数组应该包含每个词的长度。最小值和最大值是:

lrange = minimum(wordlengths),maximum(wordlengths)
(1, 45)

最小值为 1,最大值为 45,但为了直方图的目的,我决定将所有大于 20 的词汇放入“20”箱中(len>20 ? 20 : len)。现在,让我们来看看每个词汇长度的分布。Julia Plots 使这一过程变得简单。

julia>  histogram(wordlengths,bins=20)

英语词汇数据集中的词汇长度。图像作者提供

我实际上用了一些选项运行了上述命令,以使图表看起来更具信息性。

histogram(wordlengths,
    bins=20,
    xaxis=("WORD LENGTH"),
    yaxis=("COUNT"),
    xticks=([1:1:20;]),
    yticks=([0:5e3:4.5e4;],["$(x)k" for x=0:5:45]),
    label=("Word count"),
    xguidefontsize=8, yguidefontsize=8,
    margin=5mm, ylims = (0,4e4),
    framestyle = :box,
    fill = (0,0.5,:green),
    size=(800,420))

savefig("pics/2-english-word-lengths-v2.png")

令人惊讶的是,5 字母词汇甚至不在前五个最常见的词汇长度中!它们仅占我们词汇数据集的 4.6%。Ravi Parikh²和 Reginald Smith³也报告了类似的发现(5.2%)。差异可能是因为我选择了一个不包含俚语、连字符词等的词汇数据集。

首先,图表看起来像经典的钟形曲线分布。这种钟形曲线是自然界和心理学中常见的特征⁴,例如婴儿出生体重、男性身高、血压测量等。我不是语言学家,但在我看来,这表明英语语言似乎像是自然基础和随机演化的。

julia> mean(wordlengths)
9.250849455938823

julia> median(wordlengths)
9.0

最常见的词汇长度是 8 和 9,但我似乎很难想到任何一个。因此,让我们列出几个以确保:

julia> filter(x->(length(x)==9), dictwords)[1:5]
5-element Vector{String}:
 "aardvarks"
 "aasvogels"
 "abactinal"
 "abamperes"
 "abandoned"

julia> filter(x->(length(x)==9), dictwords)[27001:27005]
5-element Vector{Any}:
 "sassabies"
 "sassafras"
 "sassarara"
 "sassiness"
 "sassolite"

AASVOGELS?显然,这是南非的一种秃鹫。SASSAFRAS?难怪我在拼字游戏中不太行。

那么,有多少 5 字母词汇呢?

julia> length(filter(x->(length(x)==5), dictwords))
11210

相较于 36K 个 9 字母词汇,5 字母词汇只有 11K 个。嗯……

那么为什么 Wordle 使用 5 字母词汇?

也许我们使用它们更多。

我的编辑 Megan,那个帮助我写这些文章的好女士,认为普通人最适应 5 字母词汇。这意味着 5 字母词汇应该占据我们交流的大部分,并且出现频率较高。为了验证这个理论,我查看了总统演讲。

总统演讲通常是为不同背景和观点的广泛受众专业撰写的,因此写作必须具有包容性、文化敏感性和可及性。演讲稿撰写者还必须对当前事件和政治问题有深刻的理解,以有效传达总统的立场和目标。总统演讲对公众舆论和政治话语的形成有着重要影响,因此它们成为了那个时代语言的极佳代表文本。

这些演讲属于公有领域,且易于访问进行分析¹。

对于这项练习,我抓取了美国过去四位总统的演讲。我分析了单词长度并进行了图示。

为了简化图表绘制,我编写了一个函数来返回直方图数据。长度超过 18 个字母的单词,会被归入 18 字母以下的桶中。

function wordLengthHistoFromFile(filename, doUnique=true)
    set_tokenizer(poormans_tokenize)

    # split the words in the file into tokens
    words = collect(tokenize(read(filename, String)))

    if (doUnique) 
        words = unique(words)
    end
    totalWords = length(words)

    # this array holds the count. Index is the word length``
    local wordLengths = zeros(Int64,18)

    # Now iterate through the words
    for word in words
        l = length(word)

        # there should be very few words more than 18
        l = l<=18 ? l : 18;
        wordLengths[l] += 1
    end

    # Return % of total word lengths used
    return wordLengths./(totalWords/100.0)
end

我使用了穷人版的分词器。分词器本质上是将文本拆分为单词或子单词,以便我们可以逐个处理它们。穷人版分词器删除所有标点符号,并按空格拆分,虽然不够复杂,但足够满足我们的目的。

Julia 的分词器接口接受一个text作为输入,并返回一个单词数组。然后,我们简单地遍历数组中的所有单词,计算长度,并增加表示该长度的桶。

现在函数已经准备好,我们可以处理文本格式的演讲文件。首先,让我们看看这里的就职演讲。

hBushJrInaugural = wordLengthHistoFromFile("data/speech-bushjr-inaugural.txt")
hObamaInaugural = wordLengthHistoFromFile("data/speech-obama-inaugural.txt")
hTrumpInaugural = wordLengthHistoFromFile("data/speech-trump-inaugural.txt")
hBidenInaugural = wordLengthHistoFromFile("data/speech-biden-inaugural.txt")

因此,我们处理每篇就职演讲,并创建一个桶数组。我们将这些数组水平拼接(hcat),得到一个可以绘制的单一 18x5 数组。

plot(hcat(hBushJrInaugural,hObamaInaugural,hTrumpInaugural,hBidenInaugural ), 
        xticks=([1:18;]),
        yticks=([0:2:100;],["$(x)%" for x=0:2:100]),
        label=["Bush" "Obama" "Trump" "Biden"],
        yaxis=("% OF APPEARANCE"),
        xaxis=("WORD LENGTH"),
        xguidefontsize=8, yguidefontsize=8,
        margin=5mm,
        framestyle = :box,
        lw=2, size=(800,420), marker=(:circle),
        lc=[:red :blue :lightcoral :dodgerblue],
        mc=[:white :white :black :black]
)

savefig("pics/3-presidential-speeches.png")

上面的第一行绘制了图表,然后我将图表保存为图像文件。

总统演讲中的单词长度。图片来源:作者。

这里的图表显示了每篇演讲的分布。例如,奥巴马总统的就职演讲中不到 3%的独特单词由 2 个字母的单词组成。

5 个字母的单词似乎很受欢迎,同时 4 个和 6 个字母的单词也不少。注意,虽然 9 个字母的单词占字典的 15%,但在演讲中只占所有独特单词的不到 8%。

现在,让我们看一下更为即兴的环境。这应该能给我们一个日常用词的感觉,而不是经过深思熟虑的脚本化工作。我选择了过去四位总统中的每位的一个新闻发布会进行分析。

hBushJrPC = wordLengthHistoFromFile("data/pc_bushjr_final.txt")
hObamaPC = wordLengthHistoFromFile("data/pc_obama_final.txt")
hTrumpPC = wordLengthHistoFromFile("data/pc_trump_laborday.txt")
hBidenPC = wordLengthHistoFromFile("data/pc_biden_first.txt")

然后绘制结果:

plot(hcat(hBushJrPC,hObamaPC,hTrumpPC,hBidenPC ), 
        xticks=([1:18;]),
        yticks=([0:2:20;],["$(x)%" for x=0:2:20]),
         label=["Bush" "Obama" "Trump" "Biden"],
         xaxis=("WORD LENGTH"),yaxis="% OF APPEARANCE",
         lw=2, size=(800,420), marker=(:circle),
         xguidefontsize=8, yguidefontsize=8,
         framestyle = :box,
         margin=5mm, ylims = (0,20),
         lc=[:red :blue :lightcoral :dodgerblue],
         mc=[:white :white :black :black]
       )
savefig("pics/4-presidential-press-conf.png")

总统新闻发布会中的单词长度。图片来源:作者。

这里的模式更加清晰。除了拜登总统(他似乎偏爱 4 个字母的单词)之外,5 个字母的单词明显更受欢迎。

在文学和网络上,我们可以找到更多的证据。纽约时报文章的平均单词长度显然是 4.9⁵。根据WolframAlpha,英语中的平均单词长度是 5.1 个字符。该网站还声称,《大英百科全书在线》的平均单词长度为 5.3,而维基百科为 5.2。本文的单词平均长度为 4.75 个字母,计算这里。这似乎不算太差。

那么,为什么是 5 个字母呢?在英语中,我们的词汇似乎由 5 个字母的单词组成,尽管有更多更长的单词可供选择。也许 Josh Wardle 尝试了四个、五个和六个字母,决定五个字母最适合目标受众和解决时间。游戏应该有一点挑战性,这样你会觉得自己完成了一些事情,但又不会太难。或者,虽然可能性较小,他可能经过了这种分析并决定了五个字母。我们可能永远不会知道。

字母频率

图片由Isaac Smith提供,来源于Unsplash

既然我们已经把为什么是五个字母的问题搞清楚了,就让我们专注于改进我们的游戏。

玩 Wordle 的一种策略是最大化命中率(黄色或绿色),以减少总猜测次数。显而易见的方法是选择包含最常见字母的单词⁶。都柏林大学的模拟发现,根据一个好的首字母选择,平均猜测次数可以从 5 减少到 4⁷。这就是字母频率分析可以帮助的地方。

首先,让我们处理一下我们之前下载的整个单词数据集。这些将包括所有长度的单词。一旦我们弄清楚了所有英语单词中的频率,我们可以将其与 5 个字母单词中的字母频率进行比较,看看是否有显著差异。

我暂时想不到用列表推导的方式来做这个。不过,“map”和“for-loop”相当简单。由于 map 稍微快一些,我们先用它。

charfrequency = zeros(Int64,26)
@time map(word->(
map(
  char-> ((char>='a' && char<='z') ? charfrequency[char-'a'+1]+=1 : 1), collect(word))
), dictwords);

0.386160 seconds (5.85 M allocations: 142.942 MiB, 7.98% gc time, 26.68% compilation time)

# convert to percentage
charfrequency = charfrequency ./ (totalWords/100.0) 

我不知道你怎么样,但如果这不是我的代码,我可能会对它的作用感到有些迷惑。让我们看看 for 循环是否更好:

charfrequency = zeros(Int64,26)
@time for word in dictwords
  for char in collect(word)
     if (char>='a' && char<='z')
        charfrequency[char-'a'+1] += 1
     end
  end
end
0.437091 seconds (7.49 M allocations: 171.794 MiB, 6.45% gc time, 1.16% compilation time)

# convert to percentage
charfrequency = charfrequency ./ (totalWords/100.0)

这个我可以读懂:一个针对单词的 for 循环,然后是一个针对字符的 for 循环。之后,我只是简单地统计字母。性能也不差,增加可读性带来的百分之十的下降似乎是值得的。

让我们绘制这个数组:

bar(charfrequency,
         orientation=:h,
         yticks=(1:26, 'A':'Z'),
         xlabel=("% of APPREARANCE"),
         ylabel=("ALPHABET"),
         yflip=true,
         legend = :none, framestyle = :box,
         xticks=([0:10:110;],["$(x)%" for x=0:10:110]),
         xguidefontsize=8, yguidefontsize=8,
         margin=5mm, xlims = (0,110),
         ytickfont = font(6,"Arial"),
         fill = (0,0.5,:green),
         size=(800,420))

savefig("pics/5-dict-char-freq.png")

这就是结果。

字母在整个单词数据集中的出现百分比。图片由作者提供。

由于代码的工作方式,我们最终对 ‘SLEEP’ 中的‘E’进行了 2 次计数。这就是为什么我们在数据集中看到 E 的出现率超过 100%。我们应该只计数 1 次,即使字母在单词中出现多次。我们可以通过在计数之前简单地去除单词中的重复字符来解决这个问题。

charfrequencyu = zeros(Int64,26)
@time for word in dictwords
    for char in unique(word)
        if (char>='a' && char<='z')
        charfrequencyu[char-'a'+1] += 1
        end
    end
end

 0.491082 seconds (7.41 M allocations: 241.289 MiB, 7.55% gc time)

我将第二个 for 循环中的 collect 更改为 unique。现在,让我们绘制两个图表,看看是否有变化:

bar(hcat(charfrequency, charfrequencyu),
         orientation=:h,
         yticks=(1:26, 'A':'Z'),
         xlabel=("% of APPREARANCE"),
         ylabel=("ALPHABET"),
         yflip=true, framestyle = :box,
         xticks=([0:10:110;],["$(x)%" for x=0:10:110]),
         xguidefontsize=8, yguidefontsize=8,
         margin=5mm, xlims = (0,110),
         ytickfont = font(6,"Arial"),
         lc=[:black :black], mc=[:black :black],
         fill=[:green :blue], fillalpha=[0.5 0.5],
         label=["frequency" "unique"],
         size=(800,420))
savefig("pics/6-compare-vs-unique.png")

这是图表:

有无独特字母的出现次数比较。图片由作者提供。

我没有看到模式上有什么显著的不同。最常出现的字母也是出现次数最多的。从现在起,我们可以使用独特的字母来推动我们在 Wordle 中的词汇选择决策。

猜测词和答案词集合

上述分析适用于整个英语词汇数据集,不过,仅考虑 5 个字母的词汇会有所不同吗?让我们找出答案。使用 filter Julia 关键字,我们来制作一个只包含 5 个字母的词汇的新数组。

julia> guesswords = filter(x->(length(x)==5), dictwords)
julia> length(guesswords)

11210

官方 Wordle 猜测词汇表 包含略低于 13K 个词汇。所以,我们已经足够接近了。这些是你可以在游戏框中输入的词汇。Wordle 答案词汇表 是一个子集,包含 2.3K 个词汇,只包含可能作为隐藏答案出现的词汇。那么,为什么会有差异呢?

Josh Wardle 和他的伙伴 Palak Shah 创建了一个经过精心筛选的 答案词汇表,避免了晦涩和难解的词汇,以便让游戏对每个人都充满乐趣⁸。虽然你会发现 ZOPPA 是一个有效的猜测词,但不要指望它很快会出现作为答案。

为了查看两者之间的字母频率模式是否存在差异,让我们也生成那个列表。我使用了 size-35 的 aspell 词汇表作为起点,因为它应该包含更多常见的英语词汇。

rawwords35 = readdlm("data/scowl_american_words_35.txt",header=false,String)
length(rawwords35)
50043

dictwords35 = String[];
for word in rawwords35
    # println(word)
    if (length(word)>0 && !(occursin("\'",word) 
          || occursin("-",word) 
          || occursin(" ",word)
          || (word[1]<'a' || word[1]>'z')
          ))
        push!(dictwords35,word)
    end
end
totalWords = length(dictwords35)
39142

这个列表包含 39K 个词汇,而 85 大小的词汇表则包含 343K 个词汇。让我们只筛选出 5 个字母的词汇。

answerlist = filter(x->(length(x)==5), dictwords)
length(answerlist)
3467

Wardle 和 Shah 还从他们的答案词汇表中删除了所有的复数词汇。像 CROPS、ROCKS、DROPS 等复数形式的词汇已经从答案列表中删除,而它们仍然存在于猜测列表中。让我们大致看看有多少复数词汇。

answerlist = filter(x->(x[5]!='s' ||  x[4]=='s'), answerlist)

length(answerlist)
 2282

3467-2282
 1185

在猜测词汇表中,35% 或 1185 个词汇是复数!CROSS、CLASS 等是答案列表中的一些代表性词汇,它们没有 3 或 4 个字母的单数变体(这些词汇已经在 Wordle 中出现过,因此这里没有剧透)。

这解释了为什么最近我的好朋友 Sirisha 在那天的单词上表现得与平时不同,最终用 6 次尝试解决了它。她是我们中学小组的活跃成员,该小组每天解决 Wordle,并且通常表现很好。然而,她觉得选择太多,不知道答案列表中已经去除了 30% 的复数形式⁸。现在掌握了这些信息,她不太可能在下次 S 在第 5 位标记为绿色时被难住,我们也是如此。

在我们开始分析答案列表的模式之前,让我们看看目前拥有的三组字母频率;完整的 85 个单词列表、5 个字母的猜测单词列表和 5 个字母的答案单词列表。

让我们编写一个辅助函数来加载单词列表,

function letterFrequency(words)

    # count total words in the file
    totalWords = length(words);

    # initialize freq vector
    charFreq = zeros(Int64,26)
    for word in words
        for char in unique(word)
            if (char>='a' && char<='z')
                charFreq[char-'a'+1] += 1
            end
        end
    end

    # element wise devide by totalWords and return
    return charFreq ./ (totalWords/100.0) ;
end

函数准备好了,我们加载它吧。

cfDictwords = letterFrequency(dictwords)
cfGuesslist  = letterFrequency(guesslist)
cfAnswerlist = letterFrequency(answerlist)

现在是比较图,

plot((1:26, 'A':'Z'), hcat(cfDictwords, cfGuesslist, cfAnswerlist), 
    label=["Dict Words" "Guess list" "Answer list"],
    yticks=([0:5:70;],["$(x)%" for x=0:5:70]),
    framestyle = :box,
    xlabel="ALPHABET",ylabel="% OF APPEARANCE",
    xguidefontsize=8, yguidefontsize=8,
    margin=5mm, ylims = (0,75),
    xticks=(1:26, 'A':'Z'),
    size=(800,420)
)

savefig("pics/7-compare-guess-answer.png")

我将频率串联起来,然后在折线图上绘制它们。

来自整个数据集、猜测和答案列表的字母频率。图片由作者提供。

在上面的图表中,每个数据点表示包含特定字母的单词百分比。例如,对于 A,47% 的所有英语单词中至少有一个 A。相比之下,猜测和答案单词列表中的单词中只有 37% 包含 A。频率百分比之间的差异可能是由于英语单词列表中的长单词,因为像 A、E、I、R 等字母在长单词中出现的频率较高。我们已经知道,答案单词列表中的 S 频率低得多,这是因为去除了复数形式。

查看猜测列表可能会给出不正确的 S 模式。因此,让我们专注于答案列表,并更详细地分析其趋势。

字母频率

我们现在将按顺序绘制字母频率,以便在图表的顶部显示出现频率最高的字母。

listOrder = sortperm(cfAnswerlist, rev=true)

bar(cfAnswerlist[listOrder],
      orientation=:h,
      yticks=(1:26, ('A':'Z')[listOrder]),
      xlabel=("% of APPREARANCE"),
      ylabel=("ALPHABET"),
      yflip=true, framestyle = :box,
      xticks=([0:5:55;],["$(x)%" for x=0:5:50]),
      xguidefontsize=8, yguidefontsize=8,
      margin=5mm, xlims = (0,55),
      legend=:none,
      ytickfont = font(6,"Arial"),
      lc=(:black), mc=(:black),
      fill=(:orange), fillalpha=(0.5),
      size=(800,420))

savefig("pics/8-letterfrequency.png")

Julia 的 sortperm 返回一个排列向量 I,将 cfAnswerlist[I] 按排序顺序排列。条形图获取 cfAnswerlist[listOrder],这是一个频率值的排序列表。bar 函数的 yticks 参数被分配为 ('A':'Z')[listOrder],这将数组从 A 到 Z 按频率递减的顺序排序。

答案列表中字母的出现频率。图片由作者提供。

E 是最频繁的字母,至少出现在 52% 的答案单词中。A、R、O、I 和 T 是接下来的五个。ORATE 似乎是一个好的起始词。

V、X、Z、Q 和 J 是出现最少的字母,其中 J 出现在答案列表中的比例稍微超过 1%。我还没有验证这一点,但分析指出 XYLYL 是最差的起始词⁹ 看起来是对的。

如何使用分布

答案列表中字母的分布可以非常有用进行研究和记忆。假设你使用了第一个猜测单词 ‘ORATE’ 或者(我另一个喜欢的)‘AROSE’。对于 ORATE,你得到了 3 个黄色字母 R、A 和 E。下一个选择可以是 LASER、WAVER 等。我假设你是在使用“困难模式”进行游戏。上面的图表告诉我们,L 和 S 比 W 和 V 更可能出现在答案单词中。实际上,可能性大约是 W 和 V 的 3 倍。因此,我的猜测是 LASER。

作为另一个例子,如果你在 AROSE 上没有得到任何匹配,我通常会尝试 ‘UNWIT’,然后是 GLYPH。如果 ORATE 没有匹配,SULCI 可以是一个不错的第二个猜测,然后是 NYMPH。

作为一种策略,你可以记住频率的填充顺序;

EAROI TLSDN CUHYP GMBKW FVXZQ J

或者,记住这三个单词 ORATE、SULCI 和 NYMPH(以及 D)。当你在 ORATE 中得到一个匹配项,比如 E,你可以开始从 SULCI 和 NYMPH 中挑选字母作为你的下一个单词(这里可以想到 CLUES)。

其他研究者也推荐了类似的策略,即选择三个字母互不重叠的单词。Nisansa de Silva¹⁰ 推荐了 RAISE、CLOUT 和 NYMPH。我个人也曾使用过 AROSE、UNWIT 和 GLYPH。

结论

照片由 Sigmund 拍摄,发布于 Unsplash

我们已经从两个方面分析了游戏 Wordle,使用 Julia 来分析单词。首先,我试图证明游戏中选择五个字母的合理性。结果发现,我们的口语和书面表达中,五个字母的单词使用频率高于其他长度的单词。因此,选择五个字母的单词对于游戏是很有意义的。

其次,我们首先查看了整个英语单词列表,然后查看了五个字母猜测和浓缩答案列表中字母的频率。字母的分布图可以用来更有效地猜测单词。我注意到记住 ORATE、SULCI 和 NYMPH 可以帮助我们猜测更可能返回更多匹配的单词。使用这些字母作为指南将有助于提高你的游戏水平。

记住这些技巧,祝你明天的 Wordle 游戏一切顺利!

参考文献

  1. 弗吉尼亚大学公共事务米勒中心。“总统演讲:可下载数据。”访问于 2022 年 3 月 17 日。 millercenter.org/presidential-speeches-downloadable-data

  2. Ravi Parikh,《各种语言中的单词长度分布》,网站 www.ravi.io/language-word-lengths

  3. Reginald Smith,《不同单词长度频率:分布与符号熵》,arxiv.org/ftp/arxiv/papers/1207/1207.2334.pdf

  4. Liston Tellis,“正态分布——钟形曲线”,Medium,2020 年 7 月 5 日,medium.com/analytics-vidhya/normal-distribution-the-bell-curve-4f4a5fc2caaa

  5. Ann Wylie,“在线词语的最佳长度是什么?”,Ann Wylie 的写作技巧博客,www.wyliecomm.com/2021/11/whats-the-best-length-of-a-word-online/

  6. Ste Knight,“提高你得分的 12 个 Wordle 技巧和窍门”,Make Use Of,2022 年 9 月,www.makeuseof.com/wordle-tips-hints-tricks/

  7. Barry Smyth,“Wordle 中的大数据”,2022 年 6 月,Towards Data Science,towardsdatascience.com/big-data-in-little-wordle-306d5502c4d9

  8. Jonathan Lee,“纽约时报终于对 Wordle 做出改变”,华盛顿邮报,2022 年 11 月,www.washingtonpost.com/video-games/2022/11/07/wordle-new-answers-new-york-times-update/

  9. Brittany Alva,“根据科学,Wordle 中最糟糕的起始词”,SVG,2022 年 3 月,www.svg.com/751712/the-worst-starting-word-in-wordle-according-to-science/

  10. Nisansa de Silva,“使用字符统计选择 Wordle 种子词”,2022 年 2 月,arXiv:2202.03457arxiv.org/pdf/2202.03457.pdf

在邮政编码级别处理地理空间数据

原文:towardsdatascience.com/working-with-geospatial-data-at-the-postcode-level-3c9f79d866b3?source=collection_archive---------9-----------------------#2023-07-06

如何将“点”邮政编码与区域普查数据关联起来

Lak LakshmananTowards Data Science Lak Lakshmanan

·

关注 发表在 Towards Data Science · 9 分钟阅读 · 2023 年 7 月 6 日

--

在一些国家,邮政编码是点或路线,而非区域。例如,加拿大邮政编码的最后三位数字对应于本地投递单元,可能对应于街道一侧或乡村路线上的房屋。类似地,英国的邮政编码形式为“YO8 9UR”。这可以小到伦敦的一座建筑。在 5+4 的美国邮政编码中,最后四位数字决定了一个邮政投递路线(即一组地址),而不是一个区域。与普遍认为的相反,美国 5 位数的邮政编码也不是区域——它们只是 5+4 邮政路线的集合,通常由一个邮局提供服务。

法国作为公制系统的发源地,非常逻辑化。在法国,邮政编码对应于一个区域——最后两位数字对应于区,因此 75008 对应于巴黎的第八区,确实是一个区域。然而,邮件配送路线可能并不最佳。

由于人们和商店有地址,而这些地址有相关的邮政编码,大多数消费者数据都是以邮政编码级别报告的。为了进行区域覆盖、市场份额等计算,有必要确定邮政编码的区域范围。这在法国很容易,但在那些邮政编码是邮政路线而不是区域的国家中则会很困难。

英国邮政编码是皇家邮政的配送地址,而不是区域。加拿大和美国也是如此。照片由 Monty Allen 拍摄,发布在 Unsplash。

由于他们的邮政编码是邮件配送地址,因此可以绘制无限多个多边形来将英国/加拿大/美国划分为有效的邮政编码“区域”。这就是为什么英国的人口统计数据由其国家统计局(ONS)按照行政区域(如县)发布,而不是邮政编码。美国人口普查以“邮政编码统计区”(ZCTA)级别发布数据,而美国投票数据则按县级发布。在处理英国/加拿大/美国的数据时,你通常会遇到地址(即点)和在区域内收集的空间数据的混合。你如何将这些数据关联起来?

为了说明这一点,我将在本文中将英国邮政编码数据和人口普查数据结合起来。

下载链接

如果你很急,可以从 github.com/lakshmanok/lakblogs/tree/main/uk_postcode 下载我分析的结果——那里有几个 CSV 文件,包含你可能需要的数据。

ukpopulation.csv.gz 具有以下列:

postcode,latitude,longitude,area_code,area_name,all_persons,females,males

ukpostcodes.csv.gz 有一列额外的内容——每个邮政编码的 WKT 格式的多边形:

postcode,latitude,longitude,area_code,area_name,all_persons,females,males,geometry_wkt 

请注意,数据或代码的使用风险由你自行承担——它是以“按原样”基础分发的,没有任何形式的明示或暗示的担保或条件。

在本文中,我将详细介绍我如何在那个 GitHub 仓库中创建数据集。你可以通过使用笔记本 uk_postcodes.ipynb 跟随我一起操作。

原始数据

我们从三个在 英国开放政府许可证 下发布的原始数据源开始:

  1. Free Map Tools 提供了一个可下载文件,其中包含每个邮政编码的中心点纬度和经度。这对于空间分析还不够,因为你不能仅用一个点做诸如 ST_INTERSECTS 的操作。但这是一个好的开始。

  2. ONS 已发布普查数据,覆盖如“达勒姆郡”这样的区域。这些不是邮政编码,通常是区、县或地区级别的数据。

  3. 英国统计局已经帮助识别了每个邮政编码相关的区域。每个邮政编码存在于不同目的和分辨率定义的区域中。这包括但不限于教区、区、县和所在地区。为完整起见,这里是所有其他可用的关联(根据你的空间数据集,你可能需要其他列):

pcd,pcd2,pcds,dointr,doterm,oscty,ced,oslaua,osward,parish,usertype,oseast1m,osnrth1m,osgrdind,oshlthau,nhser,ctry,rgn,streg,pcon,eer,teclec,ttwa,pct,itl,statsward,oa01,casward,park,lsoa01,msoa01,ur01ind,oac01,oa11,lsoa11,msoa11,wz11,sicbl,bua11,buasd11,ru11ind,oac11,lat,long,lep1,lep2,pfa,imd,calncv,icb,oa21,lsoa21,msoa21

我的笔记本使用 wget 下载数据文件:

mkdir -p indata
cd indata
if [ ! -f census2021.xlsx ]; then
    wget -O census2021.xlsx https://www.ons.gov.uk/file?uri=/peoplepopulationandcommunity/populationandmigration/populationestimates/datasets/populationandhouseholdestimatesenglandandwalescensus2021/census2021/census2021firstresultsenglandwales1.xlsx
fi

读取数据

直接将 CSV 读取到 Pandas 中很简单:

import pandas as pd
pd.read_csv(POSTCODES_CSV)

这为我提供了每个邮政编码的中心点纬度和经度:

每个邮政编码的中心点纬度和经度

有许多包允许你将 Excel 文件读取到 Pandas 中,但我决定使用 DuckDB,因为我会在笔记本中用 SQL 连接这三个数据集:

import duckdb
conn = duckdb.connect()

ukpop = conn.execute(f"""
install spatial;
load spatial;

SELECT 
  * 
FROM
st_read('{POPULATION_XLS}', layer='P01')
""").df()

这个 Excel 文件有 7 行标题信息,我可以删除这些信息。我还将列重命名为有意义的变量:

ukpop = ukpop.drop(range(0,7), axis=0).rename(columns={
    'Field1': 'area_code', 
    'Field2': 'area_name', 
    'Field3': 'all_persons', 
    'Field4': 'females', 
    'Field5': 'males'
})

那是名为 P01 的表格。请注意,P04 表格有一个人口密度信息,但它没有用,因为人口在区域编码上并不均匀分布。我们将推导出每个邮政编码的人口。

我将这些数据写入 CSV 文件,以便我可以从 DuckDB 中轻松读取它。

ukpop.to_csv("temp/ukpop.csv", index=False)

同样,我从英国统计局文件中提取所需的列并将其写入 CSV 文件:

onspd = pd.read_csv(ONSPD_CSV)
onspd = onspd[['pcd', 'oslaua', 'osward', 'parish']]
onspd.to_csv("temp/onspd.csv", index=False)

关联数据

现在,我们可以使用 DuckDB 将这三个准备好的数据集连接起来,以获取每个邮政编码的密度。为什么使用 DuckDB?即使我可以在 Pandas 中进行连接,但我发现 SQL 更加易读。此外,这也给了我一个使用新热门工具的理由。

我通过首先使用 read_csv_auto 将数据集读取到 DuckB 中来连接这些数据集。然后,我查找邮政编码所在的区、教区、县,并找到人口密度数据报告的区域(教区、区或县):

 /* pcd,oslaua,osward,parish */
WITH onspd AS (
    SELECT 
      * 
    FROM
    read_csv_auto('temp/onspd.csv', header=True)
),

/* area_code,area_name,all_persons,females,males */
ukpop AS (
    SELECT 
      * 
    FROM
    read_csv_auto('temp/ukpop.csv', header=True)
),

/* id,postcode,latitude,longitude */
postcodes AS (
    SELECT 
      * 
    FROM
    read_csv_auto('indata/ukpostcodes.csv', header=True)
),

/* postcode, area_code */
postcode_to_areacode AS (
  SELECT 
    pcd AS postcode,
    ANY_VALUE(area_code) as area_code
  FROM onspd
  JOIN ukpop 
  ON (area_code = oslaua OR area_code = osward OR area_code = parish)
  GROUP BY pcd
)

SELECT
  postcode, latitude, longitude, /* from postcodes */
  area_code, area_name, /* from ukpop */
  all_persons,females,males /* from ukpop, but has to be spatially corrected */
FROM postcode_to_areacode
JOIN postcodes USING (postcode)
JOIN ukpop USING (area_code) 

请注意,空间数量是对应于整个区域的标量,而不是邮政编码。它们必须在邮政编码之间进行分配。

在邮政编码之间分配区域数量

all_persons、females、males 对应于整个区域,而不是特定的邮政编码。我们可以根据邮政编码的面积按比例进行,但有无数种多边形可以适应邮政编码,正如我们稍后将看到的,靠近公园和湖泊的邮政编码的区域范围有点不确定。所以我们将做一些简单的处理,给我们一个唯一的答案——我们将把标量值均匀分配到该区域内的所有邮政编码中!这听起来并不奇怪——在高密度社区中,邮政编码更多,因此在邮政编码之间均等分配类似于按人口密度分配标量量。

npostcodes = ukpop.groupby('area_code')['postcode'].count()
for col in ['females', 'males', 'all_persons']:
    ukpop[col] = ukpop.apply(lambda row:  row[col]/npostcodes[row['area_code']], axis=1)

目前,我们拥有每个邮政编码的数量——这是我们需要的关联:

所以,写出来:

ukpop.to_csv("ukpopulation.csv", index=False)

邮政编码的区域范围

对于许多分析,我们希望邮政编码不是点,而是区域。尽管有无数种多边形可以将英国划分为每个多边形中仅有一个邮政编码质心,但确实存在一个“最佳”多边形的概念。那就是沃罗诺伊划分,它将区域划分为每个点都属于距离它最近的邮政编码:

20 个点的沃罗诺伊分析,来自 Wikipedia

为了计算这一点,我们可以使用 scipy:

import numpy as np
from scipy.spatial import Voronoi, voronoi_plot_2d

points = df[['latitude', 'longitude']].to_numpy()
vor = Voronoi(points)

我在这里假设区域足够小,以至于从纬度和经度计算的地理距离与欧几里得距离之间的差异不大。英国的邮政编码足够小,符合这一情况。

结果被组织成这样的结构:每个点都有一个由一组顶点组成的区域。我们可以为每个点创建一个 WKT 多边形字符串,方法如下:

def make_polygon(point_no):
    region_no = vor.point_region[point_no]
    region = vor.regions[region_no]
    if len(region) >= 3:
        # close the ring
        closed_region = region.copy()
        closed_region.append(closed_region[0])
        # create a WKT of the points
        polygon = "POLYGON ((" + ','.join([ f"{vor.vertices[v][1]} {vor.vertices[v][0]}" for v in closed_region]) + "))"
        return polygon
    else:
        return None

这是一个示例结果:

POLYGON ((-0.32491691953979235 51.7393550489536,-0.32527234008402217 51.73948967705648,-0.32515738641624575 51.73987124225542,-0.3241646650618929 51.74087626616231,-0.3215663358407994 51.742660660928614,-0.32145633473723817 51.742228570262824,-0.32491691953979235 51.7393550489536))

我们可以创建一个 GeoDataFrame 并绘制邮政编码的子集:

import geopandas as gpd
from shapely import wkt
df['geometry'] = gpd.GeoSeries.from_wkt(df['geometry_wkt'])
gdf = gpd.GeoDataFrame(df, geometry='geometry')

gdf[gdf['area_name'] == 'St Albans'].plot()

圣奥尔本斯的邮政编码空间范围。由作者生成的图像。

这是伯明翰:

gdf[gdf['area_name'] == 'Birmingham'].plot()

伯明翰的邮政编码空间范围。由作者生成的图像。

未开发区域

注意顶部的喇叭和中间的大块蓝色区域。发生了什么?让我们在 Google Maps 中查看伯明翰:

伯明翰,作者的 Google Maps 截图。

注意到公园区域了吗?皇家邮政不需要向那里任何人投递邮件。所以那里没有邮政编码。因此,附近的邮政编码被“扩展”到这些区域。这会导致空间计算中的问题,因为这些邮政编码看起来会比实际要大得多。

为了解决这个问题,我将采取一种相当启发式的方法。我将把英国划分为 0.01x0.01(大约 1 平方公里)分辨率的网格单元,并找到没有邮政编码的网格单元:

GRIDRES = 0.01
min_lat, max_lat = np.round(min(df['latitude']), 2) - GRIDRES, max(df['latitude']) + GRIDRES
min_lon, max_lon = np.round(min(df['longitude']), 2) - GRIDRES, max(df['longitude']) + GRIDRES
print(min_lat, max_lat, min_lon, max_lon)

npostcodes = np.zeros([ int(1+(max_lat-min_lat)/GRIDRES), int(1+(max_lon-min_lon)/GRIDRES) ])
for point in points:
    latno = int((point[0] - min_lat)/GRIDRES)
    lonno = int((point[1] - min_lon)/GRIDRES)
    npostcodes[latno, lonno] += 1

unpop = []
for latno in range(len(npostcodes)):
    for lonno in range(len(npostcodes[latno])):
        if npostcodes[latno][lonno] == 0:
            # no one lives here.
            # make up a postcode for this location
            # postcode latitude longitude area_code area_name persons_per_sqkm
            unpop.append({
                'postcode': f'UNPOP {latno}x{lonno}',
                'latitude': min_lat + latno * 0.01,
                'longitude': min_lon + lonno * 0.01,
                'all_persons': 0
            }) 

我们将在这些无人居住的网格单元的中心创建虚拟邮政编码,并将零人口密度分配给这些编码。将这些虚拟邮政编码添加到实际的邮政编码中,然后重复 Voronoi 分析:

df2 = pd.concat([df, pd.DataFrame.from_records(unpop)])
points = df2[['latitude', 'longitude']].to_numpy()
vor = Voronoi(points)
df2['geometry_wkt'] = [make_polygon(x) for x in range(len(vor.point_region))]
df2['geometry'] = gpd.GeoSeries.from_wkt(df2['geometry_wkt'])
gdf = gpd.GeoDataFrame(df2, geometry='geometry')

现在,当我们绘制伯明翰时,我们得到的效果要好得多:

伯明翰的邮政编码多边形。图片由作者生成

就是这个数据框,我将其保存为第二个 CSV 文件:

gdf.to_csv("ukpostcodes.csv", index=False)

[可选] 加载到 BigQuery

我们可以将 CSV 文件加载到 BigQuery 中并进行一些空间分析,但最好是让 BigQuery 首先将最后一个字符串列解析为几何图形,并根据邮政编码对数据进行聚类:

CREATE OR REPLACE TABLE uk_public_data.postcode_popgeo2
CLUSTER BY postcode
AS

SELECT 
  * EXCEPT(geometry_wkt),
  SAFE.ST_GEOGFROMTEXT(geometry_wkt, make_valid=>TRUE) AS geometry,
FROM uk_public_data.postcode_popgeo

现在,我们可以轻松地查询它。例如,我们可以对邮政编码使用 ST_AREA:

SELECT 
  COUNT(*) AS num_postcodes,
  SUM(ST_AREA(geometry))/1e6 AS total_area,
  SUM(all_persons) AS population,
  area_name
 FROM uk_public_data.postcode_popgeo2
 GROUP BY area_name
 ORDER BY population DESC

总结

空间分析通常需要面积扩展,而不仅仅是点位置。在邮政编码为点/路线的国家中,有无数种方法可以生成邮政编码的多边形空间扩展。一种合理的方法是使用 Voronoi 区域来创建包含这些邮政编码的多边形。然而,如果这样做,你会在湖泊或公园附近得到不自然的大多边形,因为邮局不在那里投递邮件。为了解决这个问题,还需要对国家进行网格化,并在无人居住的网格单元中创建虚拟邮政编码。在这篇文章中,我演示了如何在英国进行此操作。相关的笔记本可以适应其他地方。

下一步

  1. 查看完整的代码 github.com/lakshmanok/lakblogs/blob/main/uk_postcode/uk_postcodes.ipynb

  2. github.com/lakshmanok/lakblogs/tree/main/uk_postcode 下载数据

使用 Hugging Face 数据集

原文:towardsdatascience.com/working-with-hugging-face-datasets-bba14dd8da68

了解如何访问 Hugging Face Hub 上的数据集,以及如何使用 DuckDB 和 Datasets 库远程加载它们

Wei-Meng LeeTowards Data Science Wei-Meng Lee

·发表于 Towards Data Science ·阅读时间 13 分钟·2023 年 6 月 29 日

--

照片由 Lars Kienle 提供,来源于 Unsplash

作为一个 AI 平台,Hugging Face 构建、训练并部署最先进的开源机器学习模型。除了托管所有这些训练好的模型外,Hugging Face 还托管数据集(huggingface.co/datasets),你可以将它们用于自己的项目。

在这篇文章中,我将展示如何访问 Hugging Face 中的数据集,并如何通过编程将它们下载到本地计算机上。具体来说,我将展示如何:

  • 使用 DuckDB 对 httpfs 的支持远程加载数据集

  • 使用 Hugging Face 的 Datasets 库流式传输数据集

Hugging Face 数据集服务器

Hugging Face 数据集服务器是一个轻量级的 Web API,用于可视化存储在 Hugging Face Hub 上的各种数据集。你可以使用提供的 REST API 查询存储在 Hugging Face Hub 上的数据集。接下来的部分提供了关于如何使用 API 的简短教程,访问地址为 [datasets-server.huggingface.co/](https://datasets-server.huggingface.co/)

获取 Hub 上托管的数据集列表

要获取可以从 Hugging Face 检索的数据集列表,请使用以下语句和valid端点:

$ curl -X GET "https://datasets-server.huggingface.co/valid"

你将看到如下的 JSON 结果:

可以正常工作的数据集列在结果中valid键的值中。上面的有效数据集示例是0-hero/OIG-small-chip2

验证数据集

要验证数据集,请使用以下语句,结合is-valid端点和dataset参数:

$ curl -X GET "https://datasets-server.huggingface.co/is-valid?dataset=0-hero/OIG-small-chip2"

如果数据集有效,你将看到以下结果:

{"valid":true}

获取数据集的配置和分割列表

一个数据集通常有splits(训练集、验证集和测试集)。它们也可能有配置——在更大数据集中的子数据集。

配置对于多语言语音数据集是常见的。有关分割的更多细节,请访问:huggingface.co/docs/datasets-server/splits

要获取数据集的分割,请使用以下语句,结合splits端点和dataset参数:

$ curl -X GET "https://datasets-server.huggingface.co/splits?dataset=0-hero/OIG-small-chip2"

以下结果将被返回:

{
  "splits": [
    {
      "dataset":"0-hero/OIG-small-chip2",
      "config":"0-hero--OIG-small-chip2",
      "split":"train"
    }
  ],
  "pending":[],
  "failed":[]
}

对于该数据集,只有一个train分割。

这是一个数据集(“duorc”)的示例,具有多个分割和配置:

{
  "splits": [
    {
      "dataset": "duorc",
      "config": "SelfRC",
      "split": "train",
      "num_bytes": 239852925,
      "num_examples": 60721
    },
    {
      "dataset": "duorc",
      "config": "SelfRC",
      "split": "validation",
      "num_bytes": 51662575,
      "num_examples": 12961
    },
    {
      "dataset": "duorc",
      "config": "SelfRC",
      "split": "test",
      "num_bytes": 49142766,
      "num_examples": 12559
    },
    {
      "dataset": "duorc",
      "config": "ParaphraseRC",
      "split": "train",
      "num_bytes": 496683105,
      "num_examples": 69524
    },
    {
      "dataset": "duorc",
      "config": "ParaphraseRC",
      "split": "validation",
      "num_bytes": 106510545,
      "num_examples": 15591
    },
    {
      "dataset": "duorc",
      "config": "ParaphraseRC",
      "split": "test",
      "num_bytes": 115215816,
      "num_examples": 15857
    }
  ]
}

获取前 100 行

通常,你可能希望检索数据集的子集进行检查,而不是下载所有数据。以下语句使用first-rows端点,结合datasetconfigsplit参数,从指定数据集中提取前 100 行:

$ curl -X GET "https://datasets-server.huggingface.co/first-rows?dataset=0-hero/OIG-small-chip2&config=0-hero--OIG-small-chip2&split=train"

JSON 结果将返回指定数据集的前 100 行:

{
 "dataset": "0-hero/OIG-small-chip2",
 "config": "0-hero--OIG-small-chip2",
 "split": "train",
 "features": [{
  "feature_idx": 0,
  "name": "user",
  "type": {
   "dtype": "string",
   "_type": "Value"
  }
 }, {
  "feature_idx": 1,
  "name": "chip2",
  "type": {
   "dtype": "string",
   "_type": "Value"
  }
 }],
 "rows": [{
  "row_idx": 0,
  "row": {
   "user": "I've heard that it's a good idea to have a will. What is a will?\n\n",
   "chip2": "A will is a legal document that specifies how your property should be distributed after you die. It can also specify who should care for any children or other dependents you may have. It's important to make sure that your will is valid and up-to-date, since the laws governing wills vary from state to state."
  },
  "truncated_cells": []
 }, {
  "row_idx": 1,
  "row": {
   "user": "How do I find a job?\n\n",
   "chip2": "The best way to find a job is to create a strong resume and cover letter that highlights your skills, experience, and qualifications. You can also search online for job openings in your area of expertise or contact local employers directly to inquire about available positions. Networking with people in the same field or industry can also be helpful in finding employment opportunities."
  },
  "truncated_cells": []
 }, {
  "row_idx": 2,
  "row": {
   "user": "Produce a detailed written description of a gloomy scene inside of a mansion with a domed ceiling, pillars, and a door.\n\n",
   "chip2": "Circular in shape, the floor is completely made from square-cut sections of black marble. Large, blackened marble pillars line the walls, ending with a domed glass ceiling high overhead. A narrow gap on the far side of the room leads to a sweeping stairwell with a mithglin-plated door beneath it."
  },
  "truncated_cells": []
 }, {

 ...

 }, {
  "row_idx": 98,
  "row": {
   "user": "I need to learn how to budget my money better. What advice can you give me?\n\n",
   "chip2": "One of the best ways to budget your money better is to make a budget and track your spending. Start by writing down all your sources of income and all your expenses. Go over each item closely to determine what you can cut back on. Prioritize the things that need to be paid first, such as rent and utilities, and try to make sure you’re setting aside some money for savings. Additionally, setting spending limits for yourself can help you stay on track. Finally, regularly review your budget to make sure you are staying within your budgeting goals."
  },
  "truncated_cells": []
 }, {
  "row_idx": 99,
  "row": {
   "user": "Produce a detailed written description of a serene scene outside a mountain road with monir trees and a boulder.\n\n",
   "chip2": "A large granite boulder, apparently fallen from the mountain's craggy cliffs in some distant age, lies beside the cobblestoned road. Surrounded by a thick stand of monir trees, the boulder provides a convenient resting place for travellers, and a brief respite from the rigors of the journey."
  },
  "truncated_cells": []
 }]
}

获取数据集的切片

你还可以获取数据集的切片。以下语句使用rows端点,结合datasetconfigsplitoffsetlength参数,从指定数据集的第三行开始提取三行:

$ curl -X GET "https://datasets-server.huggingface.co/rows?dataset=0-hero/OIG-small-chip2&config=0-hero--OIG-small-chip2&split=train&offset=2&length=3"

结果将如下所示:

{
 "features": [{
  "feature_idx": 0,
  "name": "user",
  "type": {
   "dtype": "string",
   "_type": "Value"
  }
 }, {
  "feature_idx": 1,
  "name": "chip2",
  "type": {
   "dtype": "string",
   "_type": "Value"
  }
 }],
 "rows": [{
  "row_idx": 2,
  "row": {
   "user": "Produce a detailed written description of a gloomy scene inside of a mansion with a domed ceiling, pillars, and a door.\n\n",
   "chip2": "Circular in shape, the floor is completely made from square-cut sections of black marble. Large, blackened marble pillars line the walls, ending with a domed glass ceiling high overhead. A narrow gap on the far side of the room leads to a sweeping stairwell with a mithglin-plated door beneath it."
  },
  "truncated_cells": []
 }, {
  "row_idx": 3,
  "row": {
   "user": "I have a sprained ankle and need to know what I can do to help it heal faster.\n\n",
   "chip2": "You may want to try some ice therapy, which will help reduce swelling and inflammation. Additionally, you could use an elastic bandage or compression wrap to provide additional support for your ankle. It's also important to stay off of it as much as possible, so that it can heal properly."
  },
  "truncated_cells": []
 }, {
  "row_idx": 4,
  "row": {
   "user": "How do you roast a turkey?\n\n",
   "chip2": "To roast a turkey, start by preheating your oven to 350 degrees F. Place the turkey in a roasting pan, and rub it with butter or olive oil. Sprinkle with salt and pepper, and any other herbs or seasonings you like. Place the turkey in the oven and cook for approximately 20 minutes per pound, or until the internal temperature registers 165 degrees. Once the turkey is cooked, remove it from the oven and let it sit for 10-15 minutes before carving."
  },
  "truncated_cells": []
 }]
}

获取数据集的 Parquet 文件

虽然 Hugging Face Hub 上的数据集可以以多种格式(CSV、JSONL 等)发布,但Datasets server会自动将所有公共数据集转换为Parquet格式。Parquet 格式提供了显著的性能提升,特别是对于大型数据集。稍后的章节将对此进行演示。

Apache Parquet 是一种文件格式,旨在支持复杂数据的快速处理。有关 Parquet 的更多信息,请阅读我之前的文章:

## 仍在以 CSV 格式保存数据?试试这些其他选项

了解如何以不同格式(CSV、压缩、Pickle 和 Parquet)保存数据,以节省存储空间并减少……

towardsdatascience.com

要以 Parquet 格式加载数据集,请使用以下语句,结合parquet端点和dataset参数:

$ curl -X GET "https://datasets-server.huggingface.co/parquet?dataset=0-hero/OIG-small-chip2" 

上述语句返回以下 JSON 结果:

{
 "parquet_files": [{
  "dataset": "0-hero/OIG-small-chip2",
  "config": "0-hero--OIG-small-chip2",
  "split": "train",
  "url": "https://huggingface.co/datasets/0-hero/OIG-small-chip2/resolve/refs%2Fconvert%2Fparquet/0-hero--OIG-small-chip2/parquet-train.parquet",
  "filename": "parquet-train.parquet",
  "size": 51736759
 }],
 "pending": [],
 "failed": []
}

特别是,url 键的值指定了你可以下载 Parquet 格式数据集的位置,在这个例子中是 [huggingface.co/datasets/0-hero/OIG-small-chip2/resolve/refs%2Fconvert%2Fparquet/0-hero--OIG-small-chip2/parquet-train.parquet](https://huggingface.co/datasets/0-hero/OIG-small-chip2/resolve/refs%2Fconvert%2Fparquet/0-hero--OIG-small-chip2/parquet-train.parquet)

程序化下载数据集

现在你已经了解了如何使用 Datasets server REST API,让我们看看如何程序化地下载数据集。

在 Python 中,最简单的方法是使用 requests 库:

import requests

r = requests.get("https://datasets-server.huggingface.co/parquet?dataset=0-hero/OIG-small-chip2")
j = r.json()

print(j)

json() 函数的结果是一个 Python 字典:

{
  'parquet_files': [
    {
      'dataset': '0-hero/OIG-small-chip2',
      'config': '0-hero--OIG-small-chip2', 
      'split': 'train', 
      'url': 'https://huggingface.co/datasets/0-hero/OIG-small-chip2/resolve/refs%2Fconvert%2Fparquet/0-hero--OIG-small-chip2/parquet-train.parquet', 
      'filename': 'parquet-train.parquet', 
      'size': 51736759
    }
  ], 
  'pending': [], 
  'failed': []
}

利用这个字典结果,你可以使用列表推导式来查找 Parquet 格式的数据集的 URL:

urls = [f['url'] for f in j['parquet_files'] if f['split'] == 'train']
urls

urls 变量是一个包含训练集下的数据集 URL 列表的列表:

['https://huggingface.co/datasets/0-hero/OIG-small-chip2/resolve/refs%2Fconvert%2Fparquet/0-hero--OIG-small-chip2/parquet-train.parquet']

使用 DuckDB 下载 Parquet 文件

如果你使用 DuckDB,你实际上可以使用 DuckDB 远程加载数据集。

如果你对 DuckDB 不太熟悉,你可以从这篇文章中了解基础知识:

[## 使用 DuckDB 进行数据分析

了解如何使用 SQL 进行数据分析

levelup.gitconnected.com](https://levelup.gitconnected.com/using-duckdb-for-data-analytics-bab3e3ff032c?source=post_page-----bba14dd8da68--------------------------------)

首先,确保你已安装 DuckDB(如果尚未安装):

!pip install duckdb 

然后,创建一个 DuckDB 实例并安装 httpfs

import duckdb

con = duckdb.connect()
con.execute("INSTALL httpfs;")
con.execute("LOAD httpfs;")

httpfs 扩展是一个可加载的扩展,实现了一个文件系统,允许读取远程/写入远程文件。

一旦 httpfs 安装并加载完成,你可以通过使用 SQL 查询从 Hugging Face Hub 加载 Parquet 数据集:

con.sql(f'''
    SELECT * from '{urls[0]}'
''').df()

上述 df() 函数将查询结果转换为 Pandas DataFrame:

图片由作者提供

Parquet 的一个很棒的特性是 Parquet 以列式格式存储文件。因此,如果你的查询仅请求单一列,只有该请求的列会被下载到你的计算机上:

con.sql(f'''
    SELECT "user" from '{urls[0]}'
''').df()

在上述查询中,只有“user”列被下载:

图片由作者提供

Parquet 特性对于大型数据集特别有用——想象一下,通过只下载你需要的列,你可以节省多少时间和空间。

在某些情况下,你甚至不需要下载数据。考虑以下查询:

con.sql(f'''
    SELECT count(*) from '{urls[0]}'
''').df()

图片由作者提供

不需要下载任何数据,因为这个请求可以通过读取数据集的元数据来满足。

这是另一个使用 DuckDB 下载另一个数据集(“mstz/heart_failure”)的示例:

import requests

r = requests.get("https://datasets-server.huggingface.co/parquet?dataset=mstz/heart_failure")
j = r.json()
urls = [f['url'] for f in j['parquet_files'] if f['split'] == 'train']

con.sql(f'''
    SELECT "user" from '{urls[0]}'
''').df()

这个数据集有 299 行和 13 列:

图片由作者提供

我们可以对age列进行一些聚合操作:

con.sql(f"""
    SELECT 
        SUM(IF(age<40,1,0)) AS 'Under 40',
        SUM(IF(age BETWEEN 40 and 49,1,0)) AS '40-49',
        SUM(IF(age BETWEEN 50 and 59,1,0)) AS '50-59',
        SUM(IF(age BETWEEN 60 and 69,1,0)) AS '60-69',
        SUM(IF(age BETWEEN 70 and 79,1,0)) AS '70-79',      
        SUM(IF(age BETWEEN 80 and 89,1,0)) AS '80-89',      
        SUM(IF(age>89,1,0)) AS 'Over 89',
    FROM '{urls[0]}'
"""
).df()

这是结果:

图片来源于作者

使用结果,我们还可以绘制柱状图:

con.sql(f"""
    SELECT 
        SUM(IF(age<40,1,0)) AS 'Under 40',
        SUM(IF(age BETWEEN 40 and 49,1,0)) AS '40-49',
        SUM(IF(age BETWEEN 50 and 59,1,0)) AS '50-59',
        SUM(IF(age BETWEEN 60 and 69,1,0)) AS '60-69',
        SUM(IF(age BETWEEN 70 and 79,1,0)) AS '70-79',      
        SUM(IF(age BETWEEN 80 and 89,1,0)) AS '80-89',      
        SUM(IF(age>89,1,0)) AS 'Over 89',
    FROM '{urls[0]}'
"""
).df().T.plot.bar(legend=False)

图片来源于作者

使用 Datasets 库

为了使与 Hugging Face 的数据工作变得简单而高效,Hugging Face 有自己的Datasets库(github.com/huggingface/datasets)。

要安装datasets库,使用pip命令:

!pip install datasets

load_dataset()函数加载指定的数据集:

from datasets import load_dataset

dataset = load_dataset('0-hero/OIG-small-chip2',
                       split='train')

当你第一次加载数据集时,整个数据集(以 Parquet 格式)会下载到你的计算机上:

图片来源于作者

返回的dataset的数据类型是datasets.arrow_dataset.Dataset。那你可以用它做什么呢?首先,你可以将其转换为 Pandas DataFrame:

dataset.to_pandas()

图片来源于作者

你也可以通过使用索引来获取数据集的第一行:

dataset[0]

这将返回数据的第一行:

{
  'user': "I've heard that it's a good idea to have a will. What is a will?\n\n",
  'chip2': "A will is a legal document that specifies how your property should be distributed after you die. It can also specify who should care for any children or other dependents you may have. It's important to make sure that your will is valid and up-to-date, since the laws governing wills vary from state to state."
}

对这个datasets.arrow_dataset.Dataset对象,你还可以做很多其他事情。我将留给你进一步探索。

流式传输数据集

同样,在处理大型数据集时,不可能在进行任何操作之前将整个数据集下载到计算机上。在前面的部分中,调用load_dataset()函数会将整个数据集下载到我的计算机上:

图片来源于作者

这个特定的数据集占用了 82.2MB 的磁盘空间。你可以想象更大的数据集所需的时间和磁盘空间。

幸运的是,Datasets库支持流式传输。数据集流式传输让你在不下载数据集的情况下进行操作——数据在你遍历数据集时被流式传输。要使用流式传输,在load_dataset()函数中将streaming参数设置为True

from datasets import load_dataset

dataset = load_dataset('0-hero/OIG-small-chip2',
                       split='train', 
                       streaming=True)

dataset的类型现在是datasets.iterable_dataset.IterableDataset,而不是datasets.arrow_dataset.Dataset。那么你该如何使用它呢?你可以对它使用iter()函数,这将返回一个iterator对象:

i = iter(dataset)

要获取一行,调用next()函数,它将返回迭代器中的下一个项目:

next(i)

你现在将看到第一行作为字典:

{
  'user': "I've heard that it's a good idea to have a will. What is a will?\n\n",
  'chip2': "A will is a legal document that specifies how your property should be distributed after you die. It can also specify who should care for any children or other dependents you may have. It's important to make sure that your will is valid and up-to-date, since the laws governing wills vary from state to state."
}

i调用next()函数将返回下一行:

{
  'user': 'How do I find a job?\n\n',
  'chip2': 'The best way to find a job is to create a strong resume and cover letter that highlights your skills, experience, and qualifications. You can also search online for job openings in your area of expertise or contact local employers directly to inquire about available positions. Networking with people in the same field or industry can also be helpful in finding employment opportunities.'
}

依此类推。

打乱数据集

你还可以通过对dataset变量使用shuffle()函数来打乱数据集,如下所示:

shuffled_dataset = dataset.shuffle(seed = 42, 
                                   buffer_size = 500)

在上述示例中,假设你的数据集有 10,000 行。shuffle()函数将从缓冲区的前 500 行中随机选择示例。

默认情况下,缓冲区大小为 1,000。

其他任务

你可以使用流式传输执行更多任务,例如:

  • 划分数据集

  • 交错数据集——通过在每个数据集之间交替行来组合两个数据集

  • 修改数据集的列

  • 过滤数据集

查看更多细节请访问 huggingface.co/docs/datasets/stream

如果你喜欢阅读我的文章,并且觉得它对你的职业/学习有帮助,请考虑注册成为 Medium 会员。每月 $5,即可无限访问 Medium 上的所有文章(包括我的)。如果你通过以下链接注册,我将获得少量佣金(对你没有额外费用)。你的支持意味着我可以投入更多时间撰写像这样的文章。

[## 通过我的推荐链接加入 Medium - 韦梦·李

作为 Medium 会员,您的会员费的一部分将用于支持您阅读的作者,并且您可以完全访问每一个故事……

weimenglee.medium.com/membership?source=post_page-----bba14dd8da68--------------------------------

摘要

在这篇文章中,我展示了如何访问存储在 Hugging Face Hub 上的数据集。由于数据集以 Parquet 格式存储,因此您可以远程访问这些数据集,而无需下载整个数据集。您可以使用 DuckDB 或 Hugging Face 提供的数据集库来访问这些数据集。

在 Julia 中使用 MS SQL Server

原文:towardsdatascience.com/working-with-ms-sql-server-in-julia-fc6fcd55d69a?source=collection_archive---------11-----------------------#2023-07-17

是时候提升你的数据分析工作流程了

Vikas NegiTowards Data Science Vikas Negi

·

关注 发布于 Towards Data Science ·6 分钟阅读·2023 年 7 月 17 日

--

照片由 Venti Views 提供,来源于 Unsplash

SQL 数据库是全球最广泛部署的软件之一。它们构成了众多应用程序的核心,从业务数据分析到天气预报等。当前存在多种客户端-服务器实现,微软的 SQL Server 就是其中之一。功能齐全的 开发者版 可以免费获取。它可以在 Windows、Linux 和 Docker 上运行。

数据科学家常常需要与存储在 SQL 数据库中的数据进行交互。虽然很容易找到关于如何使用 Python 进行此操作的指南,但 Julia 的教程则非常稀少。因此,在本文中,我将专注于如何使用 Julia 与 SQL Server 进行工作。示例代码是使用Pluto notebook生成的,Julia 1.9.1 在 Linux(Elementary OS)上运行。

前提条件

  1. SQL Server 2022

你需要在本地运行 SQL 服务器。最简单的设置方式是通过 Docker。SQL Server 2022 的说明在这里。要验证 docker 容器是否正在运行,请使用以下命令:

watch -n 2 sudo docker ps -a

这将每 2 秒更新一次,STATUS 列应显示类似于“Up X minutes”的内容,其中 X 是从容器启动以来经过的时间。

2. Linux 版 Microsoft ODBC 驱动程序 17

说明在这里。我无法使用更新的驱动程序 18 连接到数据库,因此不能推荐使用该驱动程序。

3. sqlcmd 实用程序(可选)

sqlcmd 实用程序允许你输入 Transact-SQL 语句,非常适合测试一切是否正常工作。详细说明在这里

加载包

将需要以下 Julia 包。当使用 Pluto notebook 时,其内置的包管理器将自动下载和安装它们。

using ODBC, DBInterface, DataFrames

检查驱动程序

开放数据库连接(ODBC)驱动程序允许我们与 SQL 服务器建立连接。使用 ODBC.jl 包,我们可以检查系统上当前可用的驱动程序:

作者提供的图片

也可以在知道驱动程序位置后安装它。

作者提供的图片

要移除驱动程序,请使用:

作者提供的图片

添加连接

使用完整的连接字符串,我们现在可以连接到之前设置的本地运行 SQL 服务器。需要 IP 地址、端口、现有数据库名称、用户 ID 和密码。请注意,如果数据库名称未知,我们可以连接到‘master’,因为这个名称默认始终存在。

作者提供的图片

列出所有现有数据库

使用conn_master对象,我们现在可以在服务器上执行查询。让我们列出所有数据库。

作者提供的图片

创建新数据库

为了创建新数据库,我们应首先使用 list_db 函数检查名称是否已存在。如果没有,则按照下面的示例创建它,以‘FruitsDB’为例。

作者提供的图片

再次列出所有数据库,我们可以验证‘FruitsDB’现在已经创建。

作者提供的图片

创建新表

SQL Server 数据库可以包含多个表,这些表只是数据的有序集合。表本身是一系列行的集合,也称为记录。在我们开始填充表格之前,我们首先需要在现有数据库中创建它。作为示例,让我们在‘FruitsDB’中创建一个名为‘Price_and_Origin’的表。该表将包含三列——名称(String)、价格(Float)和来源(String)。请注意,VARCHAR(50) 用于表示 可变长度字符串 数据。50 是字节大小,对于单字节编码,它也表示字符串的长度。

添加到新表

一旦表格存在,我们可以向其中添加数据。最简单的方法是使用 DataFrame 作为数据源。请记住,我们的表格‘Price_and_Origin’期望有三列:名称、价格和来源。因此,我们可以使用一些示例数据,如下所示:

作者提供的图片

要插入值,我们可以使用 DBInterface.executemany 函数,它允许按顺序传递多个值。如下所示可以完成此操作。最终子句确保使用 DBInterface.close! 函数关闭数据库连接。这通常是一种良好的实践,有助于避免意外地将相同的连接用于其他用途。

作者提供的图片

让我们验证数据库是否按预期填充。我们首先建立一个连接‘conn_fruit’,以连接到 SQL Server 上的‘FruitsDB’。然后,我们可以从表‘Price_and_Origin’中选择所有条目,并将其传递给 DataFrame 接收器。

作者提供的图片

更新表格

按照前一节中显示的相同顺序,数据库现在可以使用新数据进行更新。

添加新水果(图像来自作者)

让我们验证新数据是否确实存在于数据库中。

请注意,现在行数为 7(图像来自作者)

移除重复项

再次执行上面的 add_to_fruit_table 函数会向表中添加重复的行。

“荔枝”和“梨”出现了两次(图像来自作者)

使用 公共表表达式 (CTE),我们可以从给定的表中删除重复的行。以下函数帮助我们实现这一点:

删除重复条目(图片来自作者)

检查行是否唯一。

重复的条目已被删除(图片来自作者)

删除记录

在数据库中,通常需要从表中删除条目(符合某些条件)。例如,我们可以删除所有价格 > 95 的水果,如下所示:

价格超过 95 的水果已被删除(图片来自作者)

删除表

使用 DBInterface.execute 函数中的 DROP 语句,可以删除一个表。其余功能将保持与 delete_rows 相同。

DBInterface.execute(conn_db, 
                   "DROP TABLE $table_name")

结论

DBInterface.execute 函数接受有效的 SQL 语句作为输入。因此,除了已展示的内容外,还可以执行所有种类的查询,详见 此处。如前所示,查询的结果可以轻松传递给 Julia DataFrame 接收器,然后可以用来执行其他操作。

ODBC.jl 和 DBInterface.jl 这两个包正在积极维护,并且与现有工作流(尤其是涉及 DataFrames 的工作流)集成良好。这为使用 Julia 进行数据分析和可视化开辟了令人兴奋的新可能性。希望你觉得这次练习有用。感谢你的时间!通过 LinkedIn 与我联系或访问我的 Web 3.0 网站。

参考文献

  1. odbc.juliadatabases.org/stable/#Getting-Started

  2. juliadatabases.org/DBInterface.jl/dev/

  3. www.w3schools.com/sql/default.asp

  4. learn.microsoft.com/en-us/sql/?view=sql-server-linux-ver16

从人工智能的视角看世界历史

原文:towardsdatascience.com/world-history-through-the-lens-of-ai-340df6241fbf

语言模型编码了什么历史知识?

Yennie JunTowards Data Science Yennie Jun

·发布于 Towards Data Science ·11 分钟阅读·2023 年 7 月 7 日

--

探讨 OpenAI 的 GPT-4、Anthropic 的 Claude 和 TII 的 Falcon 40B Instruct 对 1910 年重大历史事件的分析(以 6 种不同语言提示)。由作者创作。

本文 最初发布在我的博客上

人工智能的进步,尤其是大型语言模型,为 历史研究 和 教育 开辟了令人兴奋的可能性。然而,重要的是审视这些模型如何解读和回忆过去。它们是否反映了它们对历史理解中的任何固有偏见?

我非常清楚历史的主观性(我在本科时主修历史!)。我们记住的事件和我们对过去形成的叙事受到撰写这些叙事的历史学家和我们所处社会的强烈影响。例如,我的高中世界历史课程将超过 75%的课程内容集中在欧洲历史上,从而偏颇了我对世界事件的理解。

在这篇文章中,我探讨了人类历史如何通过人工智能的视角被记忆和解释。我考察了多个大型语言模型对关键历史事件的解释,以揭示:

  • 这些模型是否对事件表现出西方或美国的偏见?

  • 模型的历史解释是否根据提示的语言(如韩语或法语提示)有所不同,例如韩语提示可能更强调韩国事件,而法语提示则更强调法国事件?

带着这些问题,让我们深入探讨吧!

示例:1910

作为一个例子,我询问了三个不同的大型语言模型(LLMs)1910 年的主要历史事件是什么。(每个 LLM 的更多细节在下一节。)

OpenAI 的 GPT-4、Anthropic 的 Claude 和 Technology Innovation Institute 的 Falcon 40B Instruct 在关于 1910 年主要历史事件的英文提示下作出回应。由作者创建。

我提出的问题故意没有客观答案。1910 年的重要性因文化视角的不同而大相径庭。在韩国历史中,它标志着日本占领的开始,这是一个显著影响该国发展轨迹的转折点(见1910 年日韩条约)。

然而,日本对韩国的吞并在任何回应中都没有出现。我想知道如果用不同的语言——比如韩语——来提示这些模型,它们是否会有不同的解读。

OpenAI 的 GPT-4、Anthropic 的 Claude 和 Technology Innovation Institute 的 Falcon 40B Instruct 在关于 1910 年主要历史事件的韩语提示下作出回应。韩语回答已由我翻译成英文(红色)。由作者创建。

在韩语提示下,Claude 确实将日本对韩国的吞并列为主要事件之一。然而,我发现有趣的是,GPT-4 提到的五个重要事件中有两个是以美国为中心(童子军和曼-埃尔金斯法案),而忽略了提到韩国吞并。更不用说,即使在韩语提示下,Falcon 也用英语回应。

实验

实验设置如下:

  • 3 个模型:OpenAI 的 GPT-4、Anthropic 的 Claude和 TII 的Falcon-40B-Instruct

  • 6 种语言:英语、法语、西班牙语、韩语、日语、中文

  • 3 年(610 年,1848 年,1910 年)

  • 每次运行 5 个历史事件

  • 10 次运行

  • = 2700 个总事件

语言和提示

我选择的语言大多是随意的,基于我最熟悉的语言(英语、韩语)以及一些我最亲近的朋友讲的语言,这些朋友可以为我翻译(中文、日语、法语、西班牙语)。翻译可以在文章末尾找到。我请他们为我翻译英文内容:

“列出 1910 年五大历史事件,按重要性排序。简要说明,仅提供事件名称。”

模型

  • OpenAI 的 GPT-4是 ChatGPT 的更新版本,是最受欢迎的 AI 聊天机器人之一(每月活跃用户超过 1 亿)

  • Anthropic 的 Claude 是一个与 ChatGPT 竞争的模型,采用称为 Constitutional AI 的方法进行训练,以实现无害和有帮助。

  • 技术创新研究所的 Falcon-40B-Instruct 是最好的开源语言模型,根据 HuggingFace 的开放 LLM 排行榜。

规范化事件

即使一个模型在每次运行时生成相同的事件,它描述同一事件的方式也存在很大差异。

例如,以下所有表述都指代同一事件:

  • “日本对朝鲜的吞并”

  • “日本对朝鲜的吞并”

  • “日本吞并朝鲜”

  • “日韩吞并条约”

我需要一种方法来使用相同的词汇(称为 规范化)来指代单一事件(日本对朝鲜的吞并)。更不用说同一事件可以用六种不同的语言来描述!

我使用了手动规则、Google 翻译和 GPT-4 的组合来协助规范化。起初我希望使用一个 LLM 来规范化另一个 LLM 的事件(例如使用 GPT-4 来规范化 Claude 的事件;Claude 来规范化 Falcon 的事件等),以减少偏见。然而,Claude 和 Falcon 在规范化方面并不擅长,GPT-4 成为了最适合的模型。

我承认使用模型来规范化其自身事件会带来偏见。然而,由于我使用不同的 GPT-4 会话来生成历史事件并规范化这些事件,因此在上下文上没有重叠。未来,可以使用更客观的方法来进行规范化。

结果

总体而言,我对不同模型对历史的理解感到惊讶。

  • GPT-4 更可能生成相同的事件,无论用什么语言提示。

  • Anthropic 更可能生成与其提示语言相关的历史事件。

  • Falcon(不幸的是)更可能编造虚假的事件。

  • 所有三个模型都表现出对西方或美国事件的偏见,但方式并不是我所预期的。当用非英语语言提示时,模型会生成美国或英国的历史事件(即使在用英语提示时模型不会生成那个事件)。这种情况发生在所有三个模型中。

1. 比较每个模型的语言(1910 年)

每个模型 x 语言组合生成了“前 5 个历史事件” 10 次(= 总共 50 个事件)。我选择了至少有一种语言生成了 5 次或更多次的事件。这是因为模型有时预测了一个一次性事件,而之后再也没有预测过。值为 10 的单元表示模型每次我提示时都预测了那个事件。

在本节中,我展示了 1910 年每个模型预测的主要事件,按语言分类。有关 610 年和 1848 年的类似图表可以在GitHub 页面找到,在那里我分享了所有代码和分析。

GPT-4 (OpenAI)

  • 墨西哥革命:在所有语言中,墨西哥革命始终是一个重要的世界事件——即使在我没预料到的语言中,如韩语或日语

  • 日本对韩国的吞并:在用西班牙语或法语提问时没有提到。用日语提问时,比用韩语提问时更可能提到这一事件(9 次对比 6 次),这让我觉得既奇怪又有趣

  • 美国童子军成立:GPT-4 在用日语提问时(7 次)几乎是用英语提问时(4 次)的两倍。这似乎是一些美国信息被编码进了对 1910 年的日语理解中

  • 冰川国家公园的建立:更奇怪的是,GPT-4 在用西班牙语和法语提问时预测了这一事件,但在英语中却没有

GPT-4 为 1910 年生成的主要事件,按语言对比。由作者创建。

Claude (Anthropic)

总体而言:与 GPT-4 不同,没有任何单一事件被所有语言认为是“重要历史事件”。

  • 墨西哥革命:虽然在法语、西班牙语和(莫名其妙的)韩语中经常生成,但在英语中并不像 GPT-4 中那样重要

  • 日本对韩国的吞并:对韩语和日语比其他语言更重要(这两个国家参与了这一事件)

  • 爱德华七世的去世:对英语和法语更重要(对其他语言则不然)。爱德华七世是英国国王,显然与法国关系良好。

  • 南极洲探险:这一事件实际上是英国南极探险,一位英国人首次到达南极。然而,由于某些未知原因,Claude 仅在用中文或日语提问时生成这一事件(而不是英语)。

Claude 为 1910 年生成的主要事件,按语言对比。由作者创建。

Falcon 40B Instruct (Open Source; TII)

总体而言,Falcon 在一致性和准确性方面不如其他两个模型。图表中显示的事件较少,是因为没有其他事件被 Falcon 预测 5 次或更多次!这意味着 Falcon 的预测有些不稳定。

  • 泰坦尼克号沉没:这实际上发生在 1912 年

  • 第一次世界大战爆发:这实际上发生在 1914 年

  • Falcon 在预测方面历史上不准确。但至少它对年代是正确的?

按语言对比的 Falcon 生成的 1910 年主要事件。由作者创建。

2. 对每种语言的模型相关性进行比较(1910 年)

接下来,我量化了一个模型的整体预测与其他模型的相似程度。我使用了一种数学方法(余弦相似度)来确定两个预测分布的相似性。值越接近 1 表示预测越相同;值越接近 0 表示两个预测集没有任何共同点。

再次展示 1910 年的例子。其他年份可以在GitHub 页面找到。

在大多数语言中,GPT-4 和 Claude 的相关性值较高——这意味着尽管使用了不同的语言,这两个模型预测了大量相似的事件。

相比之下,Falcon 的相关性较低,这意味着它对历史的理解与 GPT-4 和 Claude 有所不同。

针对 1910 年预测的事件模型相关性。由作者创建。

3. 对每一年的模型进行比较

接下来,我对每一年进行了不同语言模型的比较。我将所有语言预测的事件进行了汇总,并考虑了模型预测的整体事件,不论语言如何。我选取了至少有一个模型预测10 次或更多的事件。

与上述部分发现的趋势类似,GPT-4 和 Claude 倾向于预测每年的主要历史事件——610 年的穆罕默德的首次启示和赫拉克勒斯皇帝登基拜占庭王朝;1848 年的欧洲革命;以及 1910 年的墨西哥革命。

有些事件是某个模型相比其他模型预测得不成比例的。例如,在 1848 年,GPT-4 预测了“《***宣言》的发表”42 次,而 Claude 预测了 15 次。在 1910 年,Claude 预测了“爱德华七世的去世”26 次,而 GPT-4 仅预测了 1 次。

Falcon 在理解历史事件方面通常最差。Falcon 错过了所有三个年份的主要事件。在 610 年,Falcon 未能预测赫拉克勒斯皇帝的登基。在 1910 年,它未能预测诸如日本吞并朝鲜、南非联盟的形成和葡萄牙革命(这些都是非美洲的全球事件),而是预测了一些美国中心的事件,如三角工厂火灾(发生在 1911 年,而非 1910 年)。有趣的是,Falcon 能够预测大部分 1848 年的事件,这与其他两个模型相似——也许是因为 1848 年的事件更具西方中心(例如欧洲革命)?

较久远的事件(例如 610 年)使得历史稍显模糊。唐朝成立于 618 年,而非 610 年,而隋炀帝下的运河建设 实际上是在较长的时间段内完成的(604 到 609 年)。

610

比较各个模型生成的 610 年的主要事件,涵盖所有语言。由作者创建。

1848

比较各个模型生成的 1848 年的主要事件,涵盖所有语言。由作者创建。

1910

比较各个模型生成的 1910 年的主要事件,涵盖所有语言。由作者创建。

讨论

那么,这一切为什么重要呢?

随着教育公司越来越多地将大型语言模型(LLM)纳入其产品——例如,Duolingo 利用 GPT-4 进行语言学习,Khan Academy 推出 AI 教学助手‘Khanmigo’,以及哈佛大学计划将 AI 整合到计算机科学课程中——了解这些模型的潜在偏见变得至关重要。如果学生使用 LLM 学习历史,他们可能会无意中吸收哪些偏见?

在这篇文章中,我展示了一些流行的语言模型,例如 GPT-4,无论提示语言是什么,都会一致地预测“重要事件”。其他模型,如 Claude,显示出更多语言特定的预测。封闭源模型通常表现出比领先的开源替代品更高的一致性和准确性。在本文测试的所有模型中,普遍存在倾向于预测西方或美国事件(即使是冷僻事件),而忽视其他全球事件。

未来的工作可能包括:

  • 扩展分析以涵盖更多语言和年份

  • 模型输出的历史准确性进行更深入的分析

  • 历史事件排名进行更深入的分析

  • 发展更客观的事件标准化方法

这篇文章的目的是不是为了抨击大型语言模型(LLMs)或建议将其从教育环境中移除。相反,我希望提出一种批判性和谨慎的方法,认识到并减轻这些模型的偏见。大型语言模型在负责任的使用下,可以成为学生和教师在各学科中的宝贵资源。然而,我们也必须理解它们可能存在的偏见,例如西方中心主义,并相应地调整它们的使用。

用大型语言模型替代你的历史教授或教科书,可能会导致对历史的扭曲和片面解读。最终,我们必须审慎地利用这些工具,意识到它们固有的偏见,确保它们是补充而非主导我们对世界的理解。

感谢阅读!

这篇文章最初发布在我的博客上:欢迎关注以获取最新的其他写作内容!

失误

我尝试了几个不同的开源模型。以下是一些我发现模型生成的奇怪输出(全部是韩语)中的几处失误!

Falcon 40B 指令

Pythia 12B

该模型似乎陷入了一个由袋鼠、航空邮件以及 торговать(俄语中意为贸易)的变体组成的循环中。

翻译

English: 'Top five historical events in the year {}, ranked by importance. Be brief and only give the name of the event.'Korean: '{}년에 일어난 중대한 역사적인 사건 다섯가지는 무엇인가? 간단히 그 사건의 명칭들을 열거하시오.'Chinese: '按重要性顺序列出 {} 年的前五个历史事件。 简短一点,只给出事件的名称。'French: 'Les cinque événements historiques les plus importants de {}, en ordre d’importance. Seulement donnez leurs noms.'Japanese: '{}年にあったトップの出来事を五つイベント名のみで簡潔に記載してください。最も重要な出来事から。'Spanish: 'Una lista de los cinco eventos historicos que paso en el ano {}. Solo de el nombre del evento.'

你会成为数据策略师吗?

原文:towardsdatascience.com/would-you-become-a-data-strategist-59c0a179df44

了解数据行业中的另一个职位名称

Marie LefevreTowards Data Science Marie Lefevre

·发表于 Towards Data Science ·6 分钟阅读·2023 年 11 月 1 日

--

你会成为数据策略师吗?(照片由 Dan Burton 提供,来源于 Unsplash)

数据分析师、数据工程师、数据科学家……如果你对各种数据角色有所了解,可能已经听说过这些角色。几年前,随着像 dbt 这样的转型工具的兴起,出现了一种新角色:分析工程师。2021 年 1 月,Atlan 还预测了数据平台领导者的出现。无论这些预测是时尚还是趋势,我的感觉是数据角色的范围将继续扩展。

我在数据领域工作了超过三年,因此我认为数据行业中出现了一种新角色:数据策略师。从 2022 年 10 月起我便担任这个职位。我猜测这种职位在组织内部会变得越来越重要。原因很简单:数据策略师处于数据个人贡献者、战略顾问和团队经理的交汇点。

根据TechCrunch列出的信息,自 2023 年初以来,科技行业的裁员情况有所增加。随着分配给数据团队的预算减少,数据团队的规模趋于缩小。然而,他们仍然被期望交付相似数量的工作。领导者必须在数据分析、数据工程、数据科学和团队管理角色之间进行投资权衡。因此,在这种新背景下,能够掌握数据角色多个方面的人将具有战略优势。

如果你已经阅读到这篇文章,你可能已经有了上述提到的工作。你是否知道你下一步的职业发展方向是什么?**你是否可能在未曾意识到的情况下就是数据战略师?**你想成为一名吗?在这篇文章中,我希望解释什么是数据战略师,并帮助你更好地理解这个职位是否对你感兴趣。

成为数据战略师意味着什么?

根据我自己的经验以及在招聘网站上的一些研究,数据战略师履行三种角色:数据顾问、数据主管和数据团队负责人。 根据上下文,某一角色可能会比其他角色花费更多时间。同样,组织结构和数据战略师的背景也会影响他们的实际职位描述。

数据战略师的定位(作者提供的图片)

首先,与数据工作是你想称自己为数据战略师的必要要求。无论是在分析还是工程角色中,了解数据基础设施、数据管道、使用 SQL 或 Python 的临时分析以及数据可视化技术都是在数据角色中可信工作的基础。

第二,能够定义、验证和实施战略是这个职位的核心——毕竟我们在谈论的是数据战略师。这意味着对组织在数据相关事务上的长期愿景。接着,必须能够从长期目标中导出具体行动并建立一致的路线图。在我看来,短期执行与长期愿景的切换正是数据战略师角色如此令人兴奋的原因。

第三,涉及团队管理部分。与外部顾问不同,数据战略师是组织的一部分。因此,他们在引导数据团队成员朝着既定战略方向前进方面发挥作用。实际上,具体在组织图中的位置将根据组织的管理结构而有所不同。

成为数据战略师需要具备什么条件?

如果我参考我自己的经验,促使我成为数据战略家的原因是我之前作为战略顾问和后来的数据分析师的工作经历。通过这些经历,我能够发展出一系列技能,从掌握高级 SQL 函数到能够向执行委员会展示关键财务数据。

更普遍地说,数据战略家的角色包括以下任务

  • 为组织的数据战略定义长期愿景

  • 制定和实施路线图

  • 支持每个公司部门的数据使用

  • 管理数据团队

请注意,这个列表并不详尽,主要反映了我作为数据战略家的个人经验和研究。例如,Boyan Angelov 在这篇文章中描述了他作为数据战略家的日常工作。尽管我们背景不同、工作环境不同,但我发现我们两个职位之间有许多相似之处。

为了完成这些任务,需要硬技能和软技能的混合。特别是软技能对于成功地向各方传达你的愿景和制定的路线图至关重要。良好的沟通技巧和总结能力有助于他人遵循制定的策略。至于硬技能,拥有作为数据团队个人贡献者的先前工作经验可以使所采取的行动更加贴近数据工作的现实。

一份典型的“数据战略家”职位发布(作者提供的图片)

数据战略家的职位是否在上升?

从我的角度来看,数据战略家的职位仍然是一个相对不常用的术语。当我去年获得这个头衔时,我很难找到带有这个职位名称的工作机会。与我的雇主和我所称的“数据战略家”类似的职位可能是“数据团队主管”或在其他组织中某种程度上的“数据负责人”。但正如前面提到的,我的日常工作包含更广泛的任务,包括一些需要更多咨询方法而非企业执行的方法。对我来说,我们只是发明了一个新的职位名称来描述我为公司做的工作:以战略方式处理数据

然而,经过进一步研究,我发现“数据战略家”这个术语已经存在了一段时间。例如,一篇名为“数据战略家的崛起”的文章早在 2019 年就提到了这个职位的出现。在查看 Google Trends 时,确实没有明显的搜索趋势上升。

虽然这不是我发明的新职位名称,但我认为数据战略师的职位空缺在未来几年将会增加。正如我之前提到的,寻找能够同时掌握数据硬技能和战略软技能的人将变得更加普遍,因为对数据团队的预算将被越来越严格地监控。

结合战略、管理和数据相关技能对任何希望在技术领域和/或数据相关职位上发展其职业的人都是有帮助的。从数据战略师的职位开始,可以向公司数据主管或咨询公司数据顾问等角色发展,或者成为组织中的战略团队的一员。

结论

总之,数据战略师的角色是一个多面向的角色,要求具备数据能力、战略眼光和团队领导力。这使得它从我个人的角度来看非常有趣——也解释了它为何可能在未来变得更加流行。

你怎么看? 数据战略师的工作是否会在未来几年变得越来越受欢迎?你是否曾与数据战略师合作?你自己是否是数据战略师?

使用部分和 Pydantic 编写 DRY 数据模型

原文:towardsdatascience.com/write-dry-data-models-with-partials-and-pydantic-b0b13d0eeb3a?source=collection_archive---------4-----------------------#2023-02-14

构建干净的嵌套数据模型,用于数据工程管道

查尔斯·门德尔森Towards Data Science 查尔斯·门德尔森

·

关注 发布于 Towards Data Science ·7 min 阅读·2023 年 2 月 14 日

--

图片由 Didssph 提供,来源于 Unsplash

介绍

Pydantic 是一个功能极其强大的数据建模和验证库,应该成为你数据管道的标准部分。

使它们如此强大的部分原因在于,它们可以轻松地容纳嵌套的数据文件,例如 JSON。

简单回顾一下,Pydantic 是一个 Python 库,让你以 Pythonic 的方式定义数据模型,并使用该模型验证数据输入,主要通过类型提示。

Pydantic 的类型提示比标准库的更强大,更具自定义性。

另一方面,Partial 允许你预加载带有特定参数和关键字参数的函数调用,这在你多次调用相同参数的同一函数时特别有用。

在之前的文章中,我讨论了使用枚举来定义用于验证的有效字符串输入。

我们可以进一步通过将 Pydantic 数据模型与其他 Pydantic 数据模型结合来推进。

数据

我使用各种随机名称生成器创建了我们的示例数据。该数据集表示一个 Dungeons and Dragons 类型游戏中的角色。

让我们开始检查我们的数据:

作者提供的图像,由 Carbon 生成

如我们所见,与其扁平数据结构,我们现在有了数据中的数据。

检查我们之前的数据模型,我们可以看到现在必须进行一些更改以适应嵌套的数据结构:

import pydantic
class RpgCharacterModel(pydantic.BaseModel):
    CREATION_DATE: datetime
    NAME: str = pydantic.Field(...)
    GENDER: GenderEnum
    RACE: RaceEnum = pydantic.Field(...)
    CLASS: ClassEnum = pydantic.Field(...)
    HOME: str
    GUILD: str
    PAY: int = pydantic.Field(..., ge=1, le=500)

视觉化问题

我发现一个有用的工具是 JSONCrack,它提供了 JSON 数据的精彩可视化:

图像从 JSON Crack 下载,使用 GNU 通用公共许可证 v3.0

我们可以看到它下面的 4 个其他模型支持我们的顶级模型。

DRY 定义我们的模型:

使用我们可以从 JSON Crack 轻松查看的字段,我们可以像这样对模型进行第一次处理。

import pydantic
class RpgRaceModelBasic(pydantic.BaseModel):
    RACE_ID: int = pydantic.Field(..., ge=10, le=99)
    RACE: RaceEnum = pydantic.Field(...)
    HP_MODIFIER_PER_LEVEL: int = pydantic.Field(..., ge=-6, le=6)
    STR_MODIFIER: int = pydantic.Field(..., ge=-6, le=6, description="Character's strength")
    CON_MODIFIER: int = pydantic.Field(..., ge=-6, le=6, description="Character's strength")
    DEX_MODIFIER: int = pydantic.Field(..., ge=-6, le=6, description="Character's strength")
    INT_MODIFIER: int = pydantic.Field(..., ge=-6, le=6, description="Character's strength")
    WIS_MODIFIER: int = pydantic.Field(..., ge=-6, le=6, description="Character's strength")
    CHR_MODIFIER: int = pydantic.Field(..., ge=-6, le=6, description="Character's strength")

pydantic.Field() 允许我们为模型指定超出类型提示的额外参数。

  • ... 表示这是一个必填字段。

  • ge 表示字段必须大于或等于此值。

  • le 表示字段必须小于或等于此值。

但是,字段定义我们的属性修饰符中也有很多重复的代码。我们的 7 个修饰符必须在 -6 和 6 之间。 因此,在未来的更改中,我们必须更改 7 行代码。

我们可以使用来自 functools 库的 partial 函数来简化我们的定义。partial 函数允许我们为函数固定参数,非常适合这种多次调用相同参数的情况:

import functools
import pydantic

id_partial = functools.partial(pydantic.Field, ..., ge=10, le=99)
attribute_partial = functools.partial(pydantic.Field, ..., ge=-6, le=6)

class RpgRaceModelDry(pydantic.BaseModel):
    RACE_ID: int = id_partial(..., ge=10, le=99)
    RACE: RaceEnum = pydantic.Field(...)
    HP_MODIFIER_PER_LEVEL: int = attribute_partial()
    STR_MODIFIER: int = attribute_partial(description="Character's strength")
    CON_MODIFIER: int = attribute_partial(description="Character's constitution")
    DEX_MODIFIER: int = attribute_partial(description="Character's dexterity")
    INT_MODIFIER: int = attribute_partial(description="Character's intelligence")
    WIS_MODIFIER: int = attribute_partial(description="Character's wisdom")
    CHR_MODIFIER: int = attribute_partial(description="Character's charisma")

现在,我们有一个可以在需要更改属性修饰符范围时修改代码的单一位置。这是一种更干净且更 DRY 的编程方式。

另外,请注意我们仍然可以将其他参数传递给 partial 函数,在这种情况下是 description

部分函数还很灵活,可以覆盖你已经传递给它们的关键字参数。

完善模型:

现在我们还需要定义其他三个模型以及几个与之配套的枚举:

import enum
import pydantic

class AttributeEnum(enum.Enum):
    STR = 'STR'
    DEX = 'DEX'
    CON = 'CON'
    INT = 'INT'
    WIS = 'WIS'
    CHR = 'CHR'

class AlignmentEnum(enum.Enum):
    LAWFUL_GOOD = 'Lawful Good'
    LAWFUL_NEUTRAL = 'Lawful Neutral'
    LAWFUL_EVIL = 'Lawful Evil'
    NEUTRAL_GOOD = 'Neutral Good'
    TRUE_NEUTRAL = 'True Neutral'
    NEUTRAL_EVIL = 'Neutral Evil'
    CHAOTIC_GOOD = 'Chaotic Good'
    CHAOTIC_NEUTRAL = 'Chaotic Neutral'
    CHAOTIC_EVIL = 'Chaotic Evil'

class RpgClassModel(pydantic.BaseModel):
    CLASS_ID: int = id_partial()
    CLASS: ClassEnum = pydantic.Field(...)
    PRIMARY_CLASS_ATTRIBUTE: AttributeEnum = pydantic.Field(...)
    SPELLCASTER: bool = pydantic.Field(...)

class RpgPolityModel(pydantic.BaseModel):
    KINGDOM_ID: int = id_partial(ge=100, le=999)
    POLITY: str = pydantic.Field(...)
    TYPE: str = pydantic.Field(...)

class RpgGuildModel(pydantic.BaseModel):
    GUILD_ID: int = id_partial(ge=100, le=999)
    GUILD: str = pydantic.Field(...)
    ALIGNMENT: AlignmentEnum
    WEEKLY_DUES: int = pydantic.Field(..., ge=10, le=100)

特别注意 KINGDOM_IDGUILD_ID。我们在部分函数中覆盖了 gele 参数,这没有问题。它仍然保留了 ...,这表明它是一个必填字段。

通过对我们的 ID 列调用部分函数,我们不必担心忘记将它们设置为必填字段。

定义我们的顶层模型

现在所有支持模型都已构建完成,我们可以定义顶层模型,其样子如下:

import pydantic
class RpgCharacterModel(pydantic.BaseModel):
    CREATION_DATE: datetime
    NAME: str = pydantic.Field(...)
    GENDER: GenderEnum
    RACE_NESTED: RpgRaceModelDry = pydantic.Field(...)
    CLASS_NESTED: RpgClassModel = pydantic.Field(...)
    HOME_NESTED: RpgPolityModel
    GUILD_NESTED: RpgGuildModel
    PAY: int = pydantic.Field(..., ge=1, le=500)

关注 HOME_NESTEDGUILD_NESTED

注意这些模型在顶层模型中不是必需的,但这些模型内部包含的字段是必需的。这意味着,如果你向字段传递数据,它必须符合模型要求,但如果你没有向字段传递数据,它仍然被视为有效。

这实际上意味着你可以传递一个有效的模型或什么都不传。

结论

functools.partial() 与 Pydantic 数据模型结合使用,可以使你的代码更加简洁、易于理解,并确保以可扩展的方式正确处理无效数据。

在我们的示例中,我们只构建了一个嵌套层级,但你可以重复嵌套以管理遇到的任何复杂 JSON 对象。

同样,一个构建良好的数据模型可以让下游消费者相信你发送给他们的数据正是他们所期望的。

你认为可以如何将这些技术应用到你的数据管道中?

关于

Charles Mendelson 是 PitchBook 数据公司的数据工程师。 如果你想联系他,最好的方式是在 LinkedIn 上。

所有代码:

# Standard Library imports
from datetime import datetime
import enum
import functools

# 3rd Party package imports
import pydantic

# Enums for limiting string data in our model

class GenderEnum(enum.Enum):
    M = 'M'
    F = 'F'
    NB = 'NB'

class ClassEnum(enum.Enum):
    Druid = 'Druid'
    Fighter = 'Fighter'
    Warlock = 'Warlock'
    Ranger = 'Ranger'
    Bard = 'Bard'
    Sorcerer = 'Sorcerer'
    Paladin = 'Paladin'
    Rogue = 'Rogue'
    Wizard = 'Wizard'
    Monk = 'Monk'
    Barbarian = 'Barbarian'
    Cleric = 'Cleric'

class RaceEnum(enum.Enum):
    Human = 'Human'
    Dwarf = 'Dwarf'
    Halfling = 'Halfling'
    Elf = 'Elf'
    Dragonborn = 'Dragonborn'
    Tiefling = 'Tiefling'
    Half_Orc = 'Half-Orc'
    Gnome = 'Gnome'
    Half_Elf = 'Half-Elf'

class AttributeEnum(enum.Enum):
    STR = 'STR'
    DEX = 'DEX'
    CON = 'CON'
    INT = 'INT'
    WIS = 'WIS'
    CHR = 'CHR'

class AlignmentEnum(enum.Enum):
    LAWFUL_GOOD = 'Lawful Good'
    LAWFUL_NEUTRAL = 'Lawful Neutral'
    LAWFUL_EVIL = 'Lawful Evil'
    NEUTRAL_GOOD = 'Neutral Good'
    TRUE_NEUTRAL = 'True Neutral'
    NEUTRAL_EVIL = 'Neutral Evil'
    CHAOTIC_GOOD = 'Chaotic Good'
    CHAOTIC_NEUTRAL = 'Chaotic Neutral'
    CHAOTIC_EVIL = 'Chaotic Evil'

# partial function for siloing our logic in one place:
id_partial = functools.partial(pydantic.Field, ..., ge=10, le=99))
attribute_partial = functools.partial(pydantic.Field, ..., ge=-6, le=6)

# models that make up our main model

class RpgRaceModelDry(pydantic.BaseModel):
    RACE_ID: int = id_partial()
    RACE: RaceEnum = pydantic.Field(...)
    HP_MODIFIER_PER_LEVEL: int = attribute_partial()
    STR_MODIFIER: int = attribute_partial()
    CON_MODIFIER: int = attribute_partial()
    DEX_MODIFIER: int = attribute_partial()
    INT_MODIFIER: int = attribute_partial()
    WIS_MODIFIER: int = attribute_partial()
    CHR_MODIFIER: int = attribute_partial()

class RpgClassModel(pydantic.BaseModel):
    CLASS_ID: int = id_partial()
    CLASS: ClassEnum = pydantic.Field(...)
    PRIMARY_CLASS_ATTRIBUTE: AttributeEnum = pydantic.Field(...)
    SPELLCASTER: bool = pydantic.Field(...)

class RpgPolityModel(pydantic.BaseModel):
    KINGDOM_ID: int = id_partial(ge=100, le=999)
    POLITY: str = pydantic.Field(...)
    TYPE: str = pydantic.Field(...)

class RpgGuildModel(pydantic.BaseModel):
    GUILD_ID: int = id_partial(ge=100, le=999)
    GUILD: str = pydantic.Field(...)
    ALIGNMENT: AlignmentEnum
    WEEKLY_DUES: int = pydantic.Field(..., ge=10, le=100)

# Our top level model:
class RpgCharacterModel(pydantic.BaseModel):
    CREATION_DATE: datetime
    NAME: str = pydantic.Field(...)
    GENDER: GenderEnum
    RACE_NESTED: RpgRaceModelDry = pydantic.Field(...)
    CLASS_NESTED: RpgClassModel = pydantic.Field(...)
    HOME_NESTED: RpgPolityModel
    GUILD_NESTED: RpgGuildModel
    PAY: int = pydantic.Field(..., ge=1, le=500)

# We didn't talk about this one, but from a previous article,
# this validates each row of data

def validate_data(list_o_dicts, model: pydantic.BaseModel, index_offset: int = 0):
    list_of_dicts_copy = list_o_dicts.copy()
    #capturing our good data and our bad data
    good_data = []
    bad_data = []
    for index, row in enumerate(list_of_dicts_copy):
        try:
            model(**row)  #unpacks our dictionary into our keyword arguments
            good_data.append(row)  #appends valid data to a new list of dictionaries
        except pydantic.ValidationError as e:
            # Adds all validation error messages associated with the error
            # and adds them to the dictionary
            row['Errors'] = [error_message for error_message in e.errors()]
            row['Error_row_num'] = index + index_offset
            #appends bad data to a different list of dictionaries
            bad_data.append(row)

    return (good_data, bad_data)

最初发布于 https://charlesmendelson.com 2023 年 2 月 23 日。

使用 Behave 编写可读的机器学习模型测试

原文:towardsdatascience.com/write-readable-tests-for-your-machine-learning-models-with-behave-ec4a27b91490

使用自然语言测试你的机器学习模型的行为

Khuyen TranTowards Data Science Khuyen Tran

·发表于数据科学的未来 ·阅读时间 9 分钟·2023 年 3 月 11 日

--

动机

想象一下,你创建了一个机器学习模型来预测基于评论的客户情感。在部署后,你意识到模型错误地将某些积极评论标记为负面,当这些评论使用负面词汇重新表述时。

作者提供的图片

这只是一个极其准确的机器学习模型在没有适当测试时可能失败的例子。因此,在部署前测试模型的准确性和可靠性至关重要。

但你如何测试你的机器学习模型呢?一种简单的方法是使用单元测试:

from textblob import TextBlob

def test_sentiment_the_same_after_paraphrasing():
    sent = "The hotel room was great! It was spacious, clean and had a nice view of the city."
    sent_paraphrased = "The hotel room wasn't bad. It wasn't cramped, dirty, and had a decent view of the city."

    sentiment_original = TextBlob(sent).sentiment.polarity
    sentiment_paraphrased = TextBlob(sent_paraphrased).sentiment.polarity

    both_positive = (sentiment_original > 0) and (sentiment_paraphrased > 0)
    both_negative = (sentiment_original < 0) and (sentiment_paraphrased < 0)
    assert both_positive or both_negative

这种方法有效,但可能对非技术或业务参与者来说较为挑战。如果你可以将项目目标和愿景融入到测试中,并用自然语言表达出来,那将会非常好。

作者提供的图片

这时,behave 就派上用场了。

随意在此处玩耍并分叉本文的源代码:

[## Data-science/data_science_tools/behave_examples at master · khuyentran1401/Data-science

你现在无法执行该操作。你已经在另一个标签页或窗口中登录。你在另一个标签页或窗口中注销了...

github.com

什么是 behave?

behave是一个用于行为驱动开发(BDD)的 Python 框架。BDD 是一种软件开发方法论:

  • 强调利益相关者之间的协作(如业务分析师、开发人员和测试人员)

  • 使用户能够定义软件应用的需求和规格

由于 behave 提供了用于表达需求和规范的通用语言和格式,它可以理想地用于定义和验证机器学习模型的行为。

要安装 behave,请输入:

pip install behave

让我们使用 behave 对机器学习模型进行各种测试。

不变性测试

不变性测试测试的是机器学习模型在不同条件下是否产生一致的结果。

不变性测试的一个示例是验证模型是否对意译不变。如果模型对意译有变化,它可能会将正面评论误分类为负面评论,当评论使用负面词语重新表述时。

作者提供的图片

功能文件

要使用 behave 进行不变性测试,请创建一个名为features的目录。在该目录下,创建一个名为invariant_test_sentiment.feature的文件。

└──  features/ 
  └───  invariant_test_sentiment.feature 

invariant_test_sentiment.feature文件中,我们将指定项目需求:

作者提供的图片

本文件的“Given”、“When”和“Then”部分展示了在测试过程中 behave 将执行的实际步骤。

Python 步骤实现

要使用 Python 实现场景中的步骤,请首先创建features/steps目录,并在其中创建一个名为invariant_test_sentiment.py的文件:

└──  features/ 
    ├────  invariant_test_sentiment.feature  
    └────  steps/ 
       └────  invariant_test_sentiment.py 

invariant_test_sentiment.py文件包含以下代码,用于测试由TextBlob模型生成的情感在原始文本和其意译版本之间是否一致。

from behave import given, then, when
from textblob import TextBlob

@given("a text")
def step_given_positive_sentiment(context):
    context.sent = "The hotel room was great! It was spacious, clean and had a nice view of the city."

@when("the text is paraphrased")
def step_when_paraphrased(context):
    context.sent_paraphrased = "The hotel room wasn't bad. It wasn't cramped, dirty, and had a decent view of the city."

@then("both text should have the same sentiment")
def step_then_sentiment_analysis(context):
    # Get sentiment of each sentence
    sentiment_original = TextBlob(context.sent).sentiment.polarity
    sentiment_paraphrased = TextBlob(context.sent_paraphrased).sentiment.polarity

    # Print sentiment
    print(f"Sentiment of the original text: {sentiment_original:.2f}")
    print(f"Sentiment of the paraphrased sentence: {sentiment_paraphrased:.2f}")

    # Assert that both sentences have the same sentiment
    both_positive = (sentiment_original > 0) and (sentiment_paraphrased > 0)
    both_negative = (sentiment_original < 0) and (sentiment_paraphrased < 0)
    assert both_positive or both_negative

上述代码的解释:

  • 使用装饰器识别步骤,这些装饰器匹配功能的谓词:givenwhenthen

  • 装饰器接受一个字符串,其中包含匹配场景步骤的其余短语。

  • context变量允许在步骤之间共享值。

运行测试

要运行invariant_test_sentiment.feature测试,请输入以下命令:

behave features/invariant_test_sentiment.feature

输出:

Feature: Sentiment Analysis # features/invariant_test_sentiment.feature:1
  As a data scientist
  I want to ensure that my model is invariant to paraphrasing
  So that my model can produce consistent results in real-world scenarios.
  Scenario: Paraphrased text                                                       
    Given a text                                                                   
    When the text is paraphrased                                                   
    Then both text should have the same sentiment
      Traceback (most recent call last):
          assert both_positive or both_negative
      AssertionError

      Captured stdout:
      Sentiment of the original text: 0.66
      Sentiment of the paraphrased sentence: -0.38

Failing scenarios:
  features/invariant_test_sentiment.feature:6  Paraphrased text

0 features passed, 1 failed, 0 skipped
0 scenarios passed, 1 failed, 0 skipped
2 steps passed, 1 failed, 0 skipped, 0 undefined

输出显示前两个步骤通过了,最后一步失败了,这表明模型受到了意译的影响。

方向性测试

方向性测试是一种统计方法,用于评估自变量对因变量的影响是否朝着特定方向,即正面或负面。

方向性测试的一个示例是检查特定单词的存在是否对给定文本的情感评分有正面或负面的影响。

作者提供的图片

要使用 behave 进行方向性测试,我们将创建两个文件directional_test_sentiment.featuredirectional_test_sentiment.py

└──  features/ 
    ├────  directional_test_sentiment.feature  
    └────  steps/ 
       └────  directional_test_sentiment.py

功能文件

directional_test_sentiment.feature文件中的代码指定了项目的要求如下:

作者提供的图片

请注意,“And”被添加到文本中。由于前一步以“Given”开头,behave 将把“And”重命名为“Given”。

Python 步骤实现

directional_test_sentiment.py 文件中的代码实现了一个测试场景,该场景检查“awesome”一词的出现是否对 TextBlob 模型生成的情感分数产生积极影响。

from behave import given, then, when
from textblob import TextBlob

@given("a sentence")
def step_given_positive_word(context):
    context.sent = "I love this product" 

@given("the same sentence with the addition of the word '{word}'")
def step_given_a_positive_word(context, word):
    context.new_sent = f"I love this {word} product"

@when("I input the new sentence into the model")
def step_when_use_model(context):
    context.sentiment_score = TextBlob(context.sent).sentiment.polarity
    context.adjusted_score = TextBlob(context.new_sent).sentiment.polarity

@then("the sentiment score should increase")
def step_then_positive(context):
    assert context.adjusted_score > context.sentiment_score

第二步使用参数语法 {word}。当运行 .feature 文件时,场景中为 {word} 指定的值会自动传递给相应的步骤函数。

这意味着如果场景中指出相同的句子应包含“awesome”一词,behave 将自动用“awesome”替换 {word}

这种转换在你想使用不同的 {word} 参数值而不需要更改 .feature 文件和 .py 文件时非常有用。

运行测试

behave features/directional_test_sentiment.feature

输出:

Feature: Sentiment Analysis with Specific Word 
  As a data scientist
  I want to ensure that the presence of a specific word has a positive or negative effect on the sentiment score of a text
  Scenario: Sentiment analysis with specific word                 
    Given a sentence                                              
    And the same sentence with the addition of the word 'awesome' 
    When I input the new sentence into the model                  
    Then the sentiment score should increase                      

1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
4 steps passed, 0 failed, 0 skipped, 0 undefined

由于所有步骤都通过了,我们可以推断情感分数因新词的出现而增加。

最小功能测试

最小功能测试是一种验证系统或产品是否符合最低要求并在其预期用途上可用的测试类型。

最小功能测试的一个例子是检查模型是否能处理不同类型的输入,例如数值型、类别型或文本数据。

作者提供的图片

要进行最小功能测试以验证输入,请创建两个文件 minimum_func_test_input.featureminimum_func_test_input.py

└──  features/ 
    ├────  minimum_func_test_input.feature  
    └────  steps/ 
       └────  minimum_func_test_input.py

特性文件

minimum_func_test_input.feature 文件中的代码将项目要求指定如下:

作者提供的图片

Python 步骤实现

minimum_func_test_input.py 文件中的代码实现了这些要求,检查 predict 对特定输入类型生成的输出是否符合预期。

from behave import given, then, when

import numpy as np
from sklearn.linear_model import LinearRegression
from typing import Union

def predict(input_data: Union[int, float, str, list]):
    """Create a model to predict input data"""

    # Reshape the input data
    if isinstance(input_data, (int, float, list)):
        input_array = np.array(input_data).reshape(-1, 1)
    else:
        raise ValueError("Input type not supported")

    # Create a linear regression model
    model = LinearRegression()

    # Train the model on a sample dataset
    X = np.array([[1], [2], [3], [4], [5]])
    y = np.array([2, 4, 6, 8, 10])
    model.fit(X, y)

    # Predict the output using the input array
    return model.predict(input_array)

@given("I have an integer input of {input_value}")
def step_given_integer_input(context, input_value):
    context.input_value = int(input_value)

@given("I have a float input of {input_value}")
def step_given_float_input(context, input_value):
    context.input_value = float(input_value)

@given("I have a list input of {input_value}")
def step_given_list_input(context, input_value):
    context.input_value = eval(input_value)

@when("I run the model")
def step_when_run_model(context):
    context.output = predict(context.input_value)

@then("the output should be an array of one number")
def step_then_check_output(context):
    assert isinstance(context.output, np.ndarray)
    assert all(isinstance(x, (int, float)) for x in context.output)
    assert len(context.output) == 1

@then("the output should be an array of three numbers")
def step_then_check_output(context):
    assert isinstance(context.output, np.ndarray)
    assert all(isinstance(x, (int, float)) for x in context.output)
    assert len(context.output) == 3

运行测试

behave features/minimum_func_test_input.feature

输出:

Feature: Test my_ml_model 

  Scenario: Test integer input                       
    Given I have an integer input of 42              
    When I run the model                             
    Then the output should be an array of one number 

  Scenario: Test float input                         
    Given I have a float input of 3.14               
    When I run the model                            
    Then the output should be an array of one number 

  Scenario: Test list input                             
    Given I have a list input of [1, 2, 3]              
    When I run the model                                
    Then the output should be an array of three numbers

1 feature passed, 0 failed, 0 skipped
3 scenarios passed, 0 failed, 0 skipped
9 steps passed, 0 failed, 0 skipped, 0 undefined

由于所有步骤都通过了,我们可以得出结论,模型输出符合我们的预期。

behave 的缺点及其使用理由

本节将概述使用 behave 相较于 pytest 的一些缺点,并解释为什么仍然值得考虑该工具。

学习曲线

在行为驱动开发(BDD)中使用行为可能导致比 pytest 使用的传统测试方法更陡峭的学习曲线。

反驳观点: BDD 对协作的关注可以导致业务需求与软件开发之间的更好对齐,从而使整体开发过程更高效。

作者提供的图片

性能较慢

behave 测试可能比 pytest 测试更慢,因为 behave 需要解析特性文件并将其映射到步骤定义,然后才能运行测试。

反驳观点: behave 对明确步骤的关注可以导致更易理解和修改的测试,从而减少测试维护所需的总体工作量。

作者提供的图片

灵活性较差

behave 的语法更为严格,而 pytest 在定义测试和夹具方面提供了更多灵活性。

反驳观点: behave 的严格结构有助于确保测试的一致性和可读性,使其更易于理解和维护。

作者提供的图片

总结

尽管 behave 相比 pytest 存在一些缺点,但其对协作、明确步骤和结构化方法的关注仍能使其成为开发团队的重要工具。

结论

恭喜!你刚刚学会了如何使用 behave 测试机器学习模型。我希望这些知识能帮助你创建更易理解的测试。

我喜欢写关于数据科学的概念和玩弄不同的数据科学工具。你可以通过 LinkedIn 和 Twitter 与我联系。

如果你想查看我写的文章的代码,请给 这个仓库 点个星。关注我在 Medium 上,及时获取我最新的数据科学文章:

## 数据科学家的 Pytest

Pytest 数据科学项目的综合指南

towardsdatascience.com ## 伟大的期望:总是了解你的数据期望

确保你的数据按预期工作,使用 Python

towardsdatascience.com ## 使用 Pandera 验证你的 pandas DataFrame

确保你的数据符合预期

towardsdatascience.com ## 通过验证和通知早期检测数据管道中的缺陷

使用 Deepchecks 和 Prefect 在 Python 中构建稳健的数据管道

towardsdatascience.com

编写关于自然语言处理的书籍有点像解决一个复杂的数据科学项目

原文:towardsdatascience.com/writing-a-book-on-nlp-is-a-bit-like-solving-a-complex-data-science-project-c0848f975ca

对刘易斯·坦斯特尔的访谈,他是《自然语言处理与变压器》一书的合著者

帕鲁尔·潘迪Towards Data Science Parul Pandey

·发表于Towards Data Science ·7 分钟阅读·2023 年 2 月 6 日

--

一系列访谈,突显数据科学领域作家的非凡工作及其写作历程。

图片由刘易斯·坦斯特尔提供

“在小说中,语言和引发的感官体验很重要,而在技术写作中,内容和传达的信息才是重要的。”

克里斯塔·范·兰《技术写作内幕指南》

最后编辑于 2023 年 2 月 6 日

作为一名作家,我对揭示我们阅读的书籍背后的故事充满了浓厚的兴趣,特别是在机器学习领域。这些作家具有将人工智能的复杂性转化为既信息丰富又引人入胜的文字的非凡能力,实在是令人惊叹。我的目标是通过一系列访谈,把他们的故事展现出来,揭示一些在人工智能领域的知名作者的故事。

认识作者:刘易斯·坦斯特尔

刘易斯·坦斯特尔 是一位杰出的机器学习工程师,目前在 Hugging Face 工作。他在为初创企业和大型企业构建机器学习应用方面拥有丰富的经验,专注于自然语言处理、拓扑数据分析和时间序列等领域。拥有理论物理学博士学位的刘易斯曾有机会在澳大利亚、美国和瑞士等多个国家担任研究职位。他目前的工作集中在为自然语言处理社区开发创新工具,并赋予个人有效使用这些工具的知识和技能。

刘易斯是书籍《自然语言处理与 Transformers》的合著者之一,与Leandro von WerraThomas Wolf合作。这本书是该领域最新进展的全面指南,是任何希望深入理解自然语言处理以及如何将其应用于现实世界问题的人的绝佳资源。

[## 自然语言处理与 Transformers, 修订版

自 2017 年引入以来,transformers 已迅速成为实现...

learning.oreilly.com

问: 这本书的构思是如何产生的?

刘易斯: 尽管我们在 2020 年开始了这本书,但它的起源故事实际上可以追溯到 2019 年,当时 Leandro 和我首次开始使用 Transformer 模型。当时,Jay Alammar 的精彩博客文章和 Sasha Rush 的《注释 Transformer》是理解这些模型如何工作的为数不多的书面资源。这些文章(并且仍然是!)非常有助于理解,但我们觉得在指导人们如何将 Transformer 应用于工业用例方面存在一个空白。因此,在 2020 年,我们有了一个有些冒失的想法,将我们从工作中学到的知识结合成一本书。我的妻子建议我们联系 Thomas 看他是否有兴趣成为合著者,令我们惊讶的是,他同意了!

问: 能否为读者总结一下书中涵盖的主要内容?

刘易斯: 正如标题所示,这本书是关于将 Transformer 模型应用于自然语言处理任务的。大多数章节围绕着你在行业中可能遇到的单一用例进行结构化。书中涵盖了文本分类、命名实体识别和问答等核心应用。我们从精彩的fast.ai课程中汲取了很多灵感(这也是我开始深度学习的途径!),因此本书采用了动手实践的风格,强调通过代码解决现实世界的问题。在早期章节中,我们介绍了自注意力机制和迁移学习的概念,这些概念是 Transformer 成功的基础。

我对新作者的主要建议是找到可以深度批评你的想法和写作的合著者或同事。

书的后半部分深入探讨了更高级的主题,如为生产环境优化 Transformers 和处理标注数据稀少的场景(即每个数据科学家的噩梦 😃)。我最喜欢的章节之一是关于从头开始训练一个 GPT-2 规模模型,包括如何创建一个大规模语料库并在分布式基础设施上进行训练!书的结尾着眼于未来,通过突出一些涉及 Transformers 和其他模式(如图像和音频)的激动人心的最新进展来作结。

问:你与 Leandro von Werra 和 Thomas Wolf 共同编写了这本书。与多位作者一起编写书籍是什么感觉?

写一本关于 NLP 的书有点像解决一个复杂的数据科学项目。在各种挑战中,你需要为每个章节设计用例和故事,找到合适的数据和模型,并确保代码复杂性保持在最低限度,因为阅读长块代码一点也不有趣。而且就像任何数据科学项目一样,一些章节涉及到进行几十次实验。面对这些挑战,我发现与 Leandro 和 Thomas 一起写书是一个极其宝贵的经历!特别是,与能够头脑风暴或检查代码的合著者一起工作尤其有帮助。当然,多位作者面临的一个挑战是保持全书的写作风格一致,我们很幸运能有 O’Reilly 的优秀编辑帮助我们实现这一点。

问:你认为这本书的目标读者是谁?

我们写这本书是为了数据科学家和机器学习/软件工程师,他们可能听说过最近涉及 Transformers 的突破,但仍需要一个深入的指南来帮助他们将这些模型适应到自己的用例中。换句话说,就是像我自己大约 1-2 年前一样的人!我们设想这本书对行业从业者或希望从相关领域(例如,想要构建机器学习驱动应用的工程师)转向自然语言处理(NLP)的人会最有帮助。

问:你认为怎样才能最大化利用这本书——先读后写代码,还是边读边写代码?

我们实际上使用 Jupyter notebooks 和一个名为 fastdoc 的工具写了整本书,因此每一行代码都可以在我们提供的 GitHub 附带的 notebooks 中执行。我建议将书的章节和附带的代码放在一起,这样你可以在阅读时快速实验输入和输出。我们还计划在书发布时举办一些直播活动,请关注 书籍官网 以了解这些活动的时间。

问:你会给刚开始写作的新作者什么建议?

呵呵,我的第一条建议是问自己是否真的确定想写一本书。我的第二条建议是不要在刚刚有了宝宝之后……在疫情期间写书 😃

写一本关于 NLP 的书有点像解决一个复杂的数据科学项目。在各种挑战中,你需要为每一章设计用例和故事,找到合适的数据和模型,并确保代码复杂性保持在最低,因为阅读长篇代码一点也不有趣。

说正经的,我给新作者的主要建议是找到可以深入批评你想法和写作的合著者或同事。我们很幸运地有像 Aurélien Geron 和 Hamel Husain 这样的经验丰富的作者提供了详细的评论,他们的反馈显著提高了书籍质量。这种反馈非常宝贵,因为作为一个作者,你有时会忘记最初学习一个主题时哪些方面很难掌握。巧合的是,Aurélien 为我们的书写了前言!

问:你最喜欢的书和作者是谁(技术或非技术领域)?

哦,这是个棘手的问题!我是科幻小说的忠实读者,《三体》 由刘慈欣 编写,是我最喜欢的系列之一,因为它提供了对费米悖论的一个全新解决方案——即为什么没有可观察到的外星文明证据,尽管有大量类地行星的恒星?我也喜欢阅读关于物理学和计算机科学等科学领域历史的书籍和传记。我最近的一个最爱是 《未来之人》由 Ananyo Bhattacharya 编写,这本书讲述了杰出的约翰·冯·诺依曼的生活。虽然作为物理学学生我遇到过冯·诺依曼的一些工作(他写了关于量子力学的杰作),但我没有完全意识到他对数学和科学领域的广泛贡献。这本书非常出色地描述了他的工作及其迷人的历史——强烈推荐!

👉 你想与 Lewis 建立联系吗?请关注他的 Twitter.

👉 阅读本系列的其他访谈:

[## 不要只是做笔记 — 把它们变成文章并与他人分享

一次与 Alexey Grigorev 的访谈,他是《机器学习书营》的作者。

不要只是做笔记,把它们转化为文章并与他人分享 [## 你并不会通过使用花哨的技巧变得更好,而是通过专注于基础

与 Radek Osmulski 的访谈,他是《Meta Learning: How To Learn Deep Learning And Thrive In The…》一书的作者

你并不会通过使用花哨的技巧变得更好,而是通过专注于基础 [## 发布是强大的,因为它作为范围和写作决策的催化剂

与 Christoph Molnar 的访谈,他是《Interpretable Machine Learning》一书的作者

发布是强大的,因为它作为范围和写作决策的催化剂

为数据管道编写设计文档

原文:towardsdatascience.com/writing-design-docs-for-data-pipelines-d49550f95580?source=collection_archive---------0-----------------------#2023-05-22

探索数据组件设计文档的内容、意义和方法——以及它们为何重要。

Mahdi KarabibenTowards Data Science Mahdi Karabiben

·

关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 5 月 22 日

--

图片来源:Mike Kononov 在 Unsplash

近年来,采纳软件工程最佳实践已成为数据工程领域的一个共同主题。从 dbt 的软件工程灵感功能到数据可观察性的兴起,数据工程师们正越来越习惯于软件工程师的工具集和原则。

这种转变对我们设计和构建数据管道的方式产生了重大影响。它使数据管道更加健壮(因为我们从硬编码的业务逻辑和复杂的 SQL 查询转向了模块化的 dbt 模型和宏),并大幅减少了“嘿,你能检查一下这个表吗?”这种 Slack 消息(通过自动化的数据质量监控和警报)。

这些变化帮助我们将行业导向正确的方向——但我们仍然有很多领域需要向软件工程同行学习。

根据dbt Labs 最近发布的数据,大约 20%的 dbt 项目有超过 1,000 个模型(5%有超过 5,000 个)。这些数据突出了我们数据管道中的一个根本问题:我们在设计它们时不够有意图。我们在临时模型和特定用例的转换层层叠加,最终得到十个表示相同逻辑实体的模型,“仅有细微差别”。

在本文中,我们将讨论一个可以帮助我们为数据平台设计(和构建)稳健基础的文档:设计文档。

什么是设计文档?

在软件工程领域,通常会在软件组件的设计阶段创建两个主要文档,以帮助工程师进行协作和记录决策:

  • **设计文档或请求评论(RFC)**应包含与当前状态相关的所有信息,包括为什么需要做某事以及可能的设计/解决方案。内容随后会根据来自不同人员/团队的反馈和输入不断演变,直到达成共识。

  • **架构决策记录(ADR)**记录了一个具体的决策。这个文档更多的是一个书面快照,包含了形成技术决策的核心因素以及决策/设计本身的不同方面。

公司通常有一个内部模板用于这类文档(或其变体),以便不同团队可以利用它来标准化技术决策的制定方式。Gergely Orosz 写了一篇很好的文章提供了多个示例,我也推荐查看谷歌设计文档流程的公开概述。

为什么我们需要数据管道的设计文档?

基于 dbt 的方法构建数据管道的一个意外后果是,它使得向图中添加更多节点/模型变得过于容易。这让数据工程团队沉溺于构建临时数据管道的循环:数据管道没有经过适当的设计阶段或统一的整体规划,而是直接构建,导致了这种非常常见的 dbt 谱系图:

一个带有临时方法的示例 dbt 图:复杂性的增加以及缺乏治理的基础层(图片由作者提供)

这种方法的主要问题是节点不断被添加到图中,而没有太多的监督。这导致了复杂性的无尽螺旋,造成不必要的成本,并使找到正确的消费表变成了不可能的任务。

为了应对这种反模式,我的建议是始终以软件工程团队对复杂软件组件的严格要求来对待数据流水线。这意味着在编写 dbt 模型之前,我们应定义(并达成一致)一些基础块:

  • 流水线/数据资产的范围

  • 对全球消费层的变化

  • 将要交付的资产特征

  • 流水线本身的设计

如你所见,我的建议是将这些信息(及更多)汇总到设计文档中,并确保所有相关利益相关者在开始实施之前达成一致。

流水线与组件

在开始讨论设计文档的细节之前,需要注意的是,范围的概念、数据平台的整体设计以及数据工程团队的组织方式会因公司而异(基于众多因素)。这意味着本文介绍的概念应根据你自己的数据平台进行调整。例如,在 Zendesk,我们将数据资产划分为数据域,因此设计文档的范围将是一个特定的数据域(从其数据源到基础且高度治理的消费层)。

这同样适用于“数据流水线”的定义;本文中使用了这个术语,因为它是数据工程领域中的一个常见逻辑组件,但更准确的统称应为“数据组件”(这可以是一组数据产品:流水线、表格或其他工件)。

我的设计文档应该是什么样的?

既然我们了解了设计文档的重要性,接下来的问题是如何将这一概念应用于数据流水线(或者更准确地说:数据组件)——那么你的数据组件的设计文档应包含哪些主要部分?

1. 设计元数据

设计文档是捕捉特定组件所有相关元数据的黄金机会。这些元数据可能包括以下内容:

  • 所有权:组件应有不同的所有者(技术、业务等),他们对设计和实施负责。

  • 高层次描述:重要的是要有一个非常简洁的组件描述,说明我们为什么想要(或更好,必须)构建它,以及它解决了哪些业务问题或用例。这使得来自不同团队的潜在贡献者在进一步阅读之前能够获得必要的背景信息。

  • 技术元数据:这将根据你的平台而有所不同,但必须包括开始日期(数据何时开始可用?)和建议的历史回填(最早可用的日期是什么?)

  • 审阅者及审阅状态(可选):这些信息可以在设计文档之外进行管理(例如通过单独的 ADR 文档),但为了简化流程并使其更为顺畅,本节还可以包括审阅者的信息(最好是中央团队的一部分,能够评估组件如何适应整体设计)以及设计的当前状态(无论是已批准还是处于中间状态)。

这个部分不能有“过多”的元数据。如果有值得记录的信息,那么它肯定有其存在的必要。

2. 现有资源

本节旨在捕捉与此组件相关的所有相关资源。这些资源可以包括现有设计的文档,或与此组件将解决的业务用例或问题的链接。

这里的想法是,任何阅读设计文档的人都应该掌握与此组件相关的完整背景和相关资源。

3. 核心下游指标和用例

关键问题在于临时数据管道是没有明确目标的。相反,表格是为了回答模糊且定义不清的用例而添加的,然后被遗忘。

因此,我建议在开始考虑实际管道之前,专注于指标和下游用例。本节应提供以下项目的详细背景:

  • 主要依赖于此数据组件的用例是什么?

  • 使用此组件中表格计算的主要核心指标是什么?

4. 消费数据模型(或它将如何变化)

既然我们已经讨论了我们为之设计这个组件的核心指标和用例,让我们决定消费层应如何设计。

既然已经列出了下游指标,数据建模阶段变得不那么棘手,因为它将包括定义可以服务于所有下游使用场景的基础数据模型。在这里,具体设计将取决于设计文档本身之外的因素——你可以利用维度数据建模或其他技术,只要保持各组件之间的一致性,并避免创建孤立或重复的实体。

新的数据组件将如何更新全球消费数据模型(图由作者提供)

直到目前为止,我们还没有讨论数据管道或 dbt 模型。相反,重点在于定义我们希望如何呈现能够满足所有预期下游用例和指标的数据资产。

5. 表级(和列级)的元数据

在定义逻辑数据模型之后,理想情况下,我们还应在不同层次上提供关于将要构建的消费资产的背景:

  • 每个表将包含什么信息?(表级描述)

  • 对于每个表,我们计划提供哪些列?每列将包含哪些信息?我们是否做出任何数据质量承诺?

  • 对于每个表,数据更新的频率是多少?我们是否承诺任何服务水平协议(SLA)?

  • 对于每个表,初始回填和每次执行的成本估算是多少?

我们的目标是确保所有利益相关者对将要构建的资产(表)以及不同的承诺达成一致。根据你的数据开发标准,这一部分还可能包括其他信息,例如每个表的业务重要性或其认证情况。

6. 管道设计

最后一部分是我建议最终关注数据管道的地方。经过识别所有核心下游使用案例和预期承诺后,我们现在可以更清晰地设计如何实现目标。

本节将包含我们希望作为数据组件的一部分使用的所有源/原始表的列表(以及这些表是否已是平台的一部分,或者我们是否需要通过提取-加载过程引入数据),以及我们希望构建的 dbt 模型(或数据管道)以达到消费层的期望状态。

数据管道(或 dbt 模型)的示例设计,作为数据组件的一部分(图片作者提供)

但它能扩展吗?

对这种方法的初步反应可能是认为它难以扩展——但只有在我们对扩展性的定义偏颇时才会这样。

扩展性不应包括用一千个 dbt 模型来回答一千个使用案例(因为这种方法最终不会可扩展)。相反,我们寻求的扩展性是高效地回答一万种使用案例而不陷入无尽的复杂性循环。在这方面,这种方法(专注于构建为核心下游使用案例精细调整的稳健基础,并且有所有相关利益相关者的输入)绝对是可扩展的。

在这个基础的高度治理层的下游,你可以采用不同的方法来处理特定的使用案例或定制场景:可以通过使用“自动化”语义层或者打开由基础层构建的去规范化度量导向表的大门。

结论

在本文中,我们涵盖了设计文档的概念,以及它们如何成为有价值的文档,让我们能够设计和构建更稳健的数据平台和可扩展的数据组件。

本文介绍的部分并不一定需要出现在你自己版本的设计文档中。相反,最重要的是记住在构建数据管道和组件时要有意识

如果你想获取更多数据工程内容,可以订阅我的新闻简报《数据浓缩》,我在其中讨论了与数据工程和技术相关的各种主题:

[## 数据浓缩 | Mahdi Karabiben | Substack

数据工程更新和评论,伴随你的下午浓缩咖啡。点击阅读 Mahdi 的《数据浓缩》……

dataespresso.substack.com](https://dataespresso.substack.com/?source=post_page-----d49550f95580--------------------------------)

使用 GPT-4 写歌曲:第一部分,歌词

原文:towardsdatascience.com/writing-songs-with-gpt-4-part-1-lyrics-3728da678482

如何使用 OpenAI 最新的语言模型来帮助创作原创歌曲的歌词

罗伯特·A·冈萨尔维斯数据科学 罗伯特·A·冈萨尔维斯

·发布于 数据科学 ·阅读时长 16 分钟·2023 年 4 月 18 日

--

“年轻的说唱歌手在工作室里对着麦克风唱歌,耳机佩戴着,背景有电脑屏幕,” 图像由 AI 图像创作程序 Midjourney 创建,并由作者编辑

过去三年,我一直在撰写关于 OpenAI 大型语言模型的各种迭代,包括使用 GPT-3 [1] 创作音乐 和 ChatGPT [2],也就是 GPT-3.5,来写诗。在对最新语言模型 GPT-4 [3] 进行实验后,我发现它能够创作讲述连贯故事并押韵的歌词。

在这篇文章中,作为三部分系列中的第一部分,我将讨论 GPT-4 的背景,并将其写歌词和音乐的能力与 GPT-3 进行比较。接下来,我将展示 GPT-4 如何从当前乐队的歌词中阅读并创作他们风格的歌曲。最后,我将进行关于使用 AI 写音乐的一般讨论,并提出一些后续实验步骤。

在第二部分中,我将探讨 GPT-4 如何为歌曲创作和弦,在第三部分中,我将使用该系统创作伴随歌词的新旋律。

介绍 GPT-4

3 月 14 日,我收到了一封来自 OpenAI 的电子邮件,通知我可以使用他们的新语言模型 GPT-4。以下是他们在技术报告中对模型的描述。

我们报告了 GPT-4 的发展情况,这是一个大规模的多模态模型,能够接受图像和文本输入并生成文本输出。虽然在许多现实世界的场景中不如人类,但 GPT-4 在各种专业和学术基准上表现出人类级别的性能,包括在模拟律师考试中得分接近前 10% 的考生。GPT-4 是一个基于 Transformer 的模型,经过预训练以预测文档中的下一个标记。后期训练对齐过程导致在事实性和遵循期望行为的衡量标准上表现有所提高。OpenAI,GPT-4 技术报告 [3]

报告还讨论了 OpenAI 在改进 GPT-4 模型的安全性和对齐性方面所采取的步骤,以使系统“遵循人类价值观和人类意图。”他们强调了使用超过 50 名领域专家进行对抗性测试,如何建立了一个模型辅助的安全管道,以及如何在之前的模型上改进了安全指标 [3]。

新模型不是免费的。OpenAI 收取每月 20 美元的 ChatGPT Plus 费用,这样可以使用新的 GPT-4 聊天机器人。当我注册并登录后,我可以看到三个聊天模型的选项。

ChatGPT 三种模型选项的描述,来自 OpenAI 的截图

你可以看到 OpenAI 如何提升了 GPT-4 的“推理”和“简洁”水平。

请注意,默认情况下,OpenAI 将保留用户的输入和生成的响应“以改进模型性能”,正如他们在数据政策文档中所述。这意味着他们可以使用这些数据来训练他们的模型。我选择通过使用此处的表单来选择不参与他们的数据收集。

比较编写歌词的模型

在使用新的 GPT-4 系统进行实验后,我发现与原始的 GPT-3 模型相比,响应质量有了显著提高,尤其是在押韵词方面。

使用 OG 模型 GPT-3 编写歌词

这是我使用原始 GPT-3 模型编写歌曲的尝试。我使用了“副歌”这个词,指的是两行押韵的歌词。

RobG: 为一首关于狗和猫的摇滚歌曲的第一节写歌词,每对副歌的结尾要押韵。

GPT-3:

狗和猫一起生活

完美和谐地交融

奔跑和玩耍

直到上床睡觉的时候

侧身蜷缩在一起

在一个毛茸茸的小球中

我们最爱的宠物

使我们的家变得温馨

好吧,这些歌词描绘了一个可爱的小故事。然而,没有任何副歌押韵,而且歌词的节奏也不太匹配。

使用新模型编写歌词

以下是新的 GPT-4 模型如何回应相同提示的结果。

RobG: 为一首关于狗和猫的摇滚歌曲的第一段写歌词,每对押韵的句子结尾处押韵。

GPT-4:

狗和猫,它们在街上游荡

毛茸茸的朋友,心跳不已

爪子和爪垫,他们主宰这片土地

摇摆的尾巴,他们心领神会

彼此并肩,追逐风的脚步

紧密相连,最好的朋友

他们在月光下摇滚

狗和猫,它们超出了视线

啊,好多了!大部分押韵不错。将“wind”与“friends”押韵有点勉强。但措辞很稳,每行的节拍也很一致。

以你的乐队风格创作音乐

为了看看 GPT-4 是否能以特定风格创作音乐,我联系了几支本地乐队,询问是否可以用他们的歌词作为参考来创作歌曲。他们都同意了。

我开始让 GPT-4 创作风格类似于Rhode Iceland的歌词,这是一支来自麻省洛厄尔的另类摇滚乐队,Hi Fi Monk,一家位于波士顿的嘻哈/放克/雷鬼乐队,以及The Agents,一家位于普罗维登斯的 ska 乐队。

分析歌词

为了使 GPT-4 能够分析这三支乐队的歌曲,我编写了一些代码,利用两个开源项目从音频文件中获取歌词的转录。我使用了Whisper,这是 OpenAI 的语音转文本 AI 模型[4],来转录歌词。然后,我将转录后的歌词输入 GPT-4,以标示歌曲的部分,即,段落、副歌等。你可以在我的 Google Colab 上运行我的代码,这里。

Rhode Iceland,图片使用已获许可

欢迎来到 Rhode Iceland

首先,我让 GPT-4 分析了 Rhode Iceland 的歌曲《Bloody Brain》,通过输入歌词进行分析。请注意,新模型能够处理比以前更多的文本。GPT-3.5 只能处理 4,000 个标记,相当于 3,125 个词。但 GPT-4 有两个版本,一个处理 8,000 个标记,另一个处理 32,000 个标记,相当于惊人的 25,000 个词。这是我的提示。

RobG: 分析 Rhode Iceland 乐队的歌曲《Bloody Brain》的歌词。

[第一段]

沿着隐秘的上游行走

朝着血腥消失的大脑

我想要的什么都不属于我

我想要一切

让它燃烧

我想要一切

让它成为我的

希望能忘掉我的美好时光

但已经太晚了*

[副歌]

继续前行

继续前行和离开

让我离开你的脑海

让我离开你的心

走出我的脑海

走出我的心*

[副歌]

继续前行和离开

让我离开你的脑海

继续前行和离开

让我离开你的心

继续前行

继续前行*

[结束曲]

让我离开你的脑海

走出我的心

走出我的脑海

让我离开你的心*

GPT-4: Rhode Iceland 的“Bloody Brain”似乎是一首充满内省和情感的歌曲,涉及脱离、控制和放下过去关系的挣扎等主题。

第一段的歌词传达了一种矛盾感,讲述者既渴望掌控某些生活的方面,又希望放手控制。重复的副歌强调了继续前行和脱离造成困扰的情感联系的必要性。歌词中的“把我从你的脑海中移出来”和“把我从你的心中移出来”表现出对讲述者和对象释放过去关系的持续束缚的恳求,展示了想要坚持和需要自由之间的内心挣扎。歌曲的结尾重申了副歌,突出了放手和前进的重要性。

总的来说,“Bloody Brain”展现了情感的复杂性和在关系中放弃控制的困难,为艺术家和听众提供了情感的释放。

这个分析非常准确。如果你听听 Bloody Brain,确实会觉得它充满了内省和情感。然而,它没有注意到第一段副歌中的视角转换,所以我问了一下。

RobG: 你怎么看第一段副歌中的这些歌词。

把我从你的脑海中移出来

把我从你的心中移出来

走出我的脑海

走出我的心*

ChatGPT: 在“Bloody Brain”的第一段副歌中,歌词似乎传达了情感纠葛和渴望摆脱的感觉。歌词中的“把我从你的脑海中移出来”和“把我从你的心中移出来”表明歌者希望从他人的思想和情感中抽离。这可能是因为他们的存在给对方带来了痛苦或使对方的生活变得复杂。

另一方面,歌词“走出我的脑海”和“走出我的心”表明歌者也在与与对方相关的思想和情感作斗争。他们希望从这种心理和情感的依附中解放出来,以获得清晰、疗愈并继续前行。

这看起来已经很清楚了。改变代词确实对短语的意义产生了很大影响。在 GPT-4 提供歌词分析后,我让它创作了一首相似风格的歌曲。

RobG: 按照相同风格写一首接续“Bloody Brain”的歌曲。

GPT-4:

标题:“渐逝的回声”

[第一段]

漂浮在阴影中,未被看见

追逐像破碎梦一样的回忆

在空白处寻求安慰

留在迷茫中

我想打破束缚我的链条

不再被束缚

我尝试过疗愈,但依然盲目

迷失在迷宫中

[副歌]

继续前行

继续前行

从你的掌握中解放我

从你的灵魂中解放我

从我的掌握中解放出来

从我的灵魂中解放出来

[副歌] …

[桥段]

随着回声慢慢消逝

我们展开翅膀飞翔

拥抱未知

一段我们独自面对的旅程

[第二段]

迈入全新的一天

我们的幽灵开始崩溃

我想呼吸如此清新的空气

让它愈合

我想画一个如此明亮的世界

爱与痛苦可以重聚

我已找到赢得这场战斗的力量

我正在学习去感受

[副歌 x 2] …

歌词看起来不错。稍显平淡,还有不少陈词滥调。但我确实喜欢那些作为抽象小插曲呈现的视觉效果。GPT-4 列出了所有副歌,但我省略了重复部分。我注意到系统在新歌中大多数情况下押韵了每对对句。押韵很扎实,但风格与“Bloody Brain”有所不同,后者几乎没有押韵。不过,我确实喜欢段落中的复杂 AAAB-CCCB 押韵结构。

Hi Fi Monk,图片使用许可

向 Hi Fi Monk 打个招呼

Hi Fi Monk 有一个 很棒的视频 展示了他们的曲目《Break》的录制过程,带有深刻的放克节奏。

首先,我从 Whisper 那里获得了转录。这里是左侧的原歌词和右侧的自动转录的比较。转录错误用黄色标记。

(左) 《Break》歌词由 Hi Fi Monk 提供,(右) 歌词由 Whisper 转录,图片由作者提供

考虑到音乐在声乐下播放,转录的质量还不错。但确实漏掉了一些关键短语,比如“棕色皮肤的血”。

我请 GPT-4 分析了这首歌。我这里只展示了歌词的摘录版本,但我将所有 1,000 个字输入系统进行分析。

RobG: 分析 Hi Fi Monk 的歌曲《Break》的歌词和押韵风格。

[前奏]

圣约翰,无畏者,来打破所有这些枷锁。

但你必须说实话,孩子。

你必须说实话。*

[副歌] 这才是真实的。这才是交易。

不再有棕色皮肤的血可以流淌

不再有红色土地可以盗窃

不知怎么地找到疗愈的方法

破除链条。突破重围。

在他们打破你之前,先挣脱束缚。

事情是真的。保持真实。自由呼喊。第二次尝试。

GPT-4: Hi Fi Monk 的《Break》歌词围绕边缘化社区,特别是非裔美国人所面临的斗争、压迫和韧性主题展开。歌词触及了种族主义、文化挪用、警察暴行、大规模监禁,以及突破社会约束的渴望。

歌词中的押韵风格可以被描述为结尾押韵和内部押韵的混合,使用这两种方式来创造节奏感和流畅感。结尾押韵是指句子的最后音节押韵,而内部押韵是指在句子内部发生的押韵。歌曲的押韵模式不一致,这增加了其原始和自然的感觉。

这是对歌曲的良好分析。它捕捉到了歌词中围绕社会不公影响的关键主题。它迅速分解了歌曲中的押韵风格,并在过程中定义了一些术语。

这次,当我要求 GPT-4 写一首新歌时,我明确告诉它要匹配押韵风格。

RobG: 按照相同风格,写一首《Break》的续集,包括押韵模式。标记歌曲部分。

GPT-4 “战场声音”

[第 1 段]

破裂的人行道,孩子们玩耍,

在阳光下,又是一天,

街道的心跳,梦想无拘,

在混乱中,声音获得。

破碎的瓶子,未说出的故事,

勇敢和大胆的故事,

角落的预言者,讲述真相,

背景是我们的青春。

[合唱]

崛起,昂首

不再有锁链,我们将打破它们

撕碎墙壁,拆掉它们

我们的声音将震撼这片战场

崛起,昂首

不再有锁链,我们将打破它们

撕碎墙壁,拆掉它们

我们的声音将震撼这片战场

[第 2 段]

崩溃的墙壁,涂鸦斑驳,

嵌入的希望信息,

消防通道,临时舞台,

为已打响和脱离的战斗。

警报的闪光,母亲的哭泣,

从疲惫的眼中落下的泪水,

在黑暗中,一道火花点燃,

星光黯淡的夜晚中的希望闪烁。

[合唱] …

[桥段]

在阴影中,我们找到力量

通过裂缝,我们将铺就道路

手牵手,我们将超越

我们团结一心,爱的力量

[第 3 段]

街灯闪烁,随着黄昏,

夜幕降临,静默,

生命的节拍,城市的歌,

在节奏中,我们归属。

共享的欢笑,打破面包,

爱与善良,心灵得到滋养,

我们团结一致,面对战斗,

张开双臂,拥抱光明。

[合唱] …

[结尾]

在寂静中,回响,

我们带来的希望故事,

并肩作战,我们将塑造我们的命运,

一起,我们将照亮。

这也似乎合理。歌曲的基调类似于“Break”,有着充满力量和振奋的合唱。我喜欢歌词中描绘城市生活的文字和桥段中“在阴影中,我们找到力量。通过裂缝,我们将铺就道路。”的巧妙词句。

大多数对句都有结尾押韵,还有一些内部押韵,比如“黑暗”和“火花”在“在黑暗中,一道火花点燃。星光黯淡的夜晚中的希望闪烁。”中。

总体来看,这些歌词似乎经过深思熟虑并精心制作,但有点基础,没有“Break”中的那种细腻。

特工,图片使用经许可

特工:发号施令

我的第三个也是最后一个项目是写一首风格类似于特工乐队的 ska 风格的歌。我开始让系统分析特工的歌曲“Grow。”

RobG: 分析特工乐队的歌曲《Grow》的歌词和和弦。

*[第 1 段]

在 D 调的起点,我第一次遇见你

我第一次握住你的手

我第一次看进你的眼睛

我告诉过你我爱你*

*[第 2 段]

从那一天起,直到永远

那些困扰的时光,悲伤与笑声

手牵手,现在我们一起走

现在无法想象更好的事情*

*[合唱]

成长,让你的爱成长,成长

让你的爱来,崛起,崛起

播下种子,让你的爱成长

更高更高*

GPT-4

“Grow”由 ska 乐队 The Agents 演唱,是一首反映浪漫关系各个阶段和时刻的情歌。歌词描绘了爱的初期感受、关系的成长以及两人之间的深厚纽带。歌曲的整体信息是培育爱情,让其充分成长。

歌词强调了爱情和伴侣关系的重要性,如“第一次见到你女孩”,“手牵手,我们现在一起走”,“现在无法想象更好的事物。”重复的“成长,让你的爱成长,成长”突显了歌曲关于爱情成长和发展的中心主题。

系统提供了对这首歌的出色和简洁的分析。它捕捉到了歌曲的核心主题,即关系的成长,并提炼了歌词中的几句关键台词以强调这一点。

与上述前两个实验不同,这次我给 GPT-4 一个新的主题,并让它写出五个可能的歌曲标题及描述。

RobG: 为五首关于克服恐惧的歌曲创建标题。每首歌附带一句描述其含义的句子。

GPT-4:

“突破阴影”

这首歌讲述了找到内在力量和勇气,走出舒适区,面对未知。

“无畏心跳”

一首强有力的颂歌,庆祝人类精神的韧性,鼓励听众拥抱恐惧,将其转化为个人成长的驱动力。

“超越风暴”

这首歌讲述了克服逆境和挑战的故事,阐明了面对恐惧如何带来新的力量和自我发现。

“解放勇气”

一首激励歌曲,强调摆脱恐惧和自我怀疑的束缚,敦促个人以不懈的决心追求梦想。

“与恐惧共舞”

一首鼓舞人心的曲目,鼓励听众接受并承认他们的恐惧,将其作为个人成长和转变的催化剂。

这些都不错,尽管读起来像是励志海报。但总体来说,我认为“与恐惧共舞”是这批中最好的。而且我知道 ska 风格的音乐非常适合跳舞。

正如我在上面的例子中所做的,我让 GPT-4 为这首新歌写了歌词。

RobG: 写出“与恐惧共舞”的歌词。

GPT-4:

[第一部分]

醒来迎接崭新的一天,

感受到恐惧却不转身,

面对未知,这就是我们开始的地方,

以开放的心态和无畏的心。

[前副歌]

不再隐藏,不再怀疑,

是时候让我们的勇气释放出来了。

[副歌]

与恐惧共舞,不要逃避,

拥抱挑战,无论发生什么。

找到你的节奏,面对未知,

随着每一步,你的力量在增长。

[第二部分]

在黑暗中,我们将找到光明,

在梦想的引导下腾飞。

不再颤抖,不再害怕,

我们将一起飞向新的高度。

[前副歌] …

[副歌] …

[桥段]

当恐惧敲你的门时,

高高站立,再次面对它。

旅程漫长,但你将发现,

你比你意识到的更强大。

[副歌] …

[尾声]

所以与恐惧共舞,让勇气引领,

我们一起征服,一起成功。

跨出这一步,你将看到,

你会找到你的力量,你会自由。

哇,这非常好!虽然有点老套,因为充满了激励性声明。而且,它再次让所有的对句押韵,这对歌曲来说是好的。我注意到桥段中的最后一对句子并没有完全押韵。

旅程漫长,但你将发现

你比你意识到的更强大。

所以我问 GPT-4 看看它是否能修复它。

RobG: 重新编写桥段的最后两行,使词语押韵。

GPT-4:

旅程漫长,但要记住这一点

你比你将要发现的恐惧更强大。

这是一个很好的、富有创意的修复。如果你不喜欢它写的东西,给它一些建设性的反馈;它通常能很好地做出积极的改变。这与我将 AI 系统视为写作伙伴的观点一致。

总结

我展示了 GPT-4 如何写新的歌词,结合了不同的押韵方案。我展示了如何让系统参与讨论现有歌词的风格和意义,然后再让它写新歌词,作为一种预处理方式。

下一步

下一步可能包括对系统在平衡良好的散文写作和遵循定义好的押韵结构方面的优缺点进行更严格的探索。下一步还可能包括通过图表分析歌词中的音节和重音模式来检查韵律。请查看本系列的第二部分和第三部分,了解如何使用 GPT-4 为歌曲编写和旋律。

讨论

虽然 GPT-4 在写歌词方面比早期的语言模型更好,但它远非完美。充其量,它只是一个还算可以的词曲创作者和有帮助的写作伙伴。然而,AI/ML 在自然语言处理中的进展趋势目前正呈指数级提升。这对词曲创作者和音乐爱好者意味着什么呢?

从我所见,词曲创作者和音乐家对新技术的使用有着不同的看法;有些人接受它,有些人否定它,但大多数人最终会使用它,如果它有帮助的话。我认为这对使用 AI 模型也适用。这就像使用在线押韵词典来完成一段歌词。它可能有帮助,也可能没有,但词曲创作者会尝试一下,并做出决定。

大部分情况下,我认为观众并不太在意歌曲创作者和音乐家使用了什么工具来创作他们的歌曲。例如,我们知道 Auto-Tune 在幕后经常被用来修正歌唱表现。当用于正常设置时,大多数人无法察觉,也可能不在意。直到谢尔在《Believe》中将其调到硬调模式,以及T-Pain将其作为音乐的明显效果时,这才引起公众注意。我认为语言模型也会经历相同的周期。随着模型的改进,大多数粉丝不会察觉它们的使用,也可能不在意。如果歌曲好,他们会喜欢它们。

源代码

本项目的源代码可以在 GitHub 上找到。

知识共享 署名-相同方式共享

致谢

我想感谢来自罗德岛的 Jon Gryckiewicz,来自 Hi Fi Monk 的 Fez Aswat,以及来自 The Agents 的 Pete Fayan 对本项目的参与。我还想感谢 Jennifer Lim 审阅本文并提供反馈。

更多关于使用 GPT-4 创作歌曲的信息

这是本系列的第二篇和第三篇文章。

使用 GPT-4 创作歌曲:第二部分,和弦

如何使用 OpenAI 最新的大型语言模型来帮助创作原创歌曲的和弦

使用 GPT-4 创作歌曲:第二部分,和弦 ## 使用 GPT-4 创作歌曲:第三部分,旋律

如何使用 OpenAI 最新的语言模型来帮助创作新歌曲的旋律

使用 GPT-4 创作歌曲:第三部分,旋律

参考文献

[1] T. Brown 等,语言模型是少样本学习者 (2020) NeurIPS 2020

[2] J. Schulman 等,介绍 ChatGPT (2022)

[3] OpenAI, GPT-4 技术报告 (2023)

[4] A, Radford 等,大规模弱监督下的鲁棒语音识别 (2022)

使用 GPT-4 创作歌曲:第二部分,和弦

原文:towardsdatascience.com/writing-songs-with-gpt-4-part-2-chords-173cfda0e5a1

如何使用 OpenAI 最新的大型语言模型帮助创作原创歌曲的和弦

Robert A. GonsalvesTowards Data Science Robert A. Gonsalves

·发表于Towards Data Science ·15 分钟阅读·2023 年 5 月 10 日

--

“一位音乐家在专业录音室里弹奏原声吉他,戴着耳机,旁边有电脑屏幕,”使用 AI 图像生成程序 Midjourney 创建的图像,并由作者编辑。

这是关于使用 OpenAI 新款 GPT-4 语言模型[1]作曲的三部分系列文章中的第二篇。在第一篇文章中,我探讨了使用该模型为歌曲创作歌词。这一篇中,我使用系统为歌曲编写和弦。我将在第三篇文章中探讨如何创作旋律。

GPT-4 背景

GPT-4 是 OpenAI 最新的大型语言模型。可以通过他们的ChatGPT用户界面使用,并通过他们的API访问。新模型作为付费 ChatGPT Plus 订阅的一部分提供,每月费用为 20 美元。正如我在上一篇文章中提到的,OpenAI 会捕捉你的输入和回应,这些数据可能会用于训练未来版本的模型。这是默认启用的,但你可以通过他们的表单在这里选择退出数据收集。

GPT-4 的响应显著优于之前的 GPT-3.5 模型。根据 OpenAI 的技术报告,新模型在 SAT 考试中获得了 1600 分中的 1400 分(语言 710 分,数学 690 分),而早期模型获得了 1260 分(语言 670 分,数学 590 分)。

尽管测试分数优秀,OpenAI 报告称系统输出并不总是可靠的,并且可能展示从训练数据中学到的偏见。

尽管 GPT-4 具有一定的能力,但它与早期的 GPT 模型有类似的局限性:它并不完全可靠(例如可能出现“幻觉”),有一个有限的上下文窗口,并且不会学习。在使用 GPT-4 的输出时应小心,特别是在可靠性很重要的上下文中。……GPT-4 可能生成潜在有害的内容……它可能表现出各种社会偏见和世界观,这些可能不代表用户的意图或广泛共享的价值观。—— OpenAI [1]

这听起来令人害怕且不祥。但是对于我在这个项目中做的事情,即为歌曲生成和弦,风险要小得多。

使用 GPT-4 为歌曲创建和弦

在我之前的文章中,我让 GPT-4 为一首关于猫和狗的摇滚歌曲创作歌词。这是我第一次尝试让模型为这首歌创建和弦。

RobG: 为这首摇滚歌曲的第一段写和弦。

*狗和猫,它们漫游在街头

有心跳的毛茸茸的朋友

爪子和脚掌,它们统治着这片土地

尾巴摇摆,它们懂得*

*并肩追逐着风

牢牢相依,最好的朋友

它们在月光下摇滚

狗和猫,它们不见了*

GPT-4: 这是这首歌第一段的和弦:

G C

狗和猫,它们漫游在街头

Em D

有心跳的毛茸茸的朋友

G C

爪子和脚掌,它们统治着这片土地

Em D

尾巴摇摆,它们懂得

Am C

并肩追逐着风

G D

牢牢相依,最好的朋友

G C

它们在月光下摇滚

Em D

狗和猫,它们不见了

和弦看起来相当不错,尽管有些基础。像大多数大型语言模型一样,GPT-4 在大量书籍和互联网文本上进行训练,它似乎从讨论和弦中学到了足够的内容来生成不错的和弦序列。我喜欢进程的 AABA 结构:G — C — Em — D,G — C — Em — D,AmCGD, G — C — Em — D。

然而,文本格式存在问题。和弦与歌词的配合不一致,即,“D”和弦应该与最后一行的“they’re”或“out”对齐。我找到了一种效果不错的替代方法:将和弦与歌词对齐。我只是想问问。

RobG: 以方括号中带有空格的形式显示和弦与歌词对齐。

GPT-4:

[G] 狗和猫,它们 [C] 漫游在街头

[Em] 有心跳的毛茸茸的朋友 [D]

[G] 爪子和脚掌,它们 [C] 统治着这片土地

[Em] 尾巴摇摆,[D] 它们懂得

[Am] 并肩追逐着风 [C]

[G] 牢牢相依,[D] 最好的朋友

[G] 它们在月光下摇滚 [C]

[Em] 狗和猫,它们 [D] 不见了

啊,好得多了。因为“they’re”用作下一个和弦开始之前的起始音符,所以在最后一行的“out”之前开始 D 和弦是有意义的。

除了生成和弦进行,GPT-4 还解释了与音乐理论相关的内容。因此,我偶尔会向它询问我正在处理的歌曲的问题,比如这样的问题。

RobG: 这是什么调的?

GPT-4: 为这首摇滚歌曲提供的和弦是在 G 大调中。G 大调包括以下和弦:G(I)、Am(ii)、Bm(iii)、C(IV)、D(V)、Em(vi)和 F#dim(vii°)。

这看起来是正确的。我很感激对 G 大调中使用的和弦进行了详细解释。注意,它既使用了和弦的字母名称,又使用了罗马数字符号,其中大调和弦用大写字母表示,例如(I),而小调和弦用小写字母表示,例如(ii)。我喜欢它使用‘°’字符来表示减和弦的方式。

我使用了来自PG Music的商业应用 Band-in-a-Box(BiaB),以听和弦的声音。我输入和弦名称,粘贴了相应的歌词,选择了“摇滚”风格,然后它渲染了歌曲。请注意,这里没有旋律,因此可以随意跟着你的旋律唱。

猫与狗,歌词和和弦由 GPT-4 提供,制作人:作者

听起来不错。和弦序列很好,BiaB 在吉他、贝斯和鼓部分做得很出色。没有声乐旋律,旋律最好的部分是每个短语结尾的吉他填充。

接下来,我将展示如何使用 AI 从歌曲录音中提取歌词和和弦。

使用 AI 从歌曲中提取歌词和和弦

在上一篇文章中,我展示了如何使用 OpenAI 的 Whisper 模型[2]从音频文件中转录歌词。对于这个项目,我编写了一些代码,使用另一个神经网络 Autochord 提取和弦并将它们拼接在一起。

Autochord 模型由软件工程师和机器学习爱好者 C. J. Bayron 创建和训练。它运行良好。你将一个音频文件输入其中,几秒钟后,你会得到一个开始时间、结束时间和和弦的列表。这里是他在线应用中模型结果的一个可视化示例。你可以按照这里的说明生成一个标签文件。

自动和弦可视化,图片由作者提供

它只找到大调和小调和弦,但这对我的目的来说已经足够了。然后,我用 Whisper 的输出和单词时间,编写了一些 Python 代码,将和弦与歌词整合在一起。例如,下面是一个非常著名的歌曲的渲染效果。

生日快乐,由 Patty Hill 和 Mildred J. Hill 创作,录音由作者提供

这里是使用 Whisper 和 Autochord 分别转录的歌词和和弦。

[C] 祝你生日快乐,[G] 祝你生日快乐,[C] 祝你生日快乐,[G] 祝你生日快乐!

[C] 祝你生日快乐,[F] 亲爱的读者,[C] 祝你生日快乐,[G] 祝你生日快乐!

这看起来相当不错。所有的和弦都预测得很准确。它甚至捕捉到了最后三个词的快速转换。F 大调和弦的变化应该在“亲爱的”一词之后,但这只是唯一的错误。

不过,请注意短语没有被正确拆分。这是基于 Whisper 模型计算的短语分割。为了解决这个问题,我将转录文本输入到 GPT-4 中,使用了以下提示。

RobG: 这里是歌曲的和弦和歌词。

*[C] 生日快乐 [G] 你,生日快乐 [C] 你,

[C] 生日快乐 [F] 亲爱的读者,生日 [C] 快乐 [G] 祝 [C] 你!

在逗号处将歌曲拆分成较短的行,并在行首显示当前和弦。

GPT-4:

[C] 生日快乐 [G] 你

[G] 生日快乐 [C] 你

[C] 生日快乐 [F] 亲爱的读者

[F] 生日 [C] 快乐 [G] 祝 [C] 你!

好的,这样更容易阅读。系统将能够以这种格式分析和生成歌词及和弦,如你将在下一部分看到的那样。

为新歌曲编写和弦

在我编写代码提取歌曲歌词和和弦后,我的下一步是请求 GPT-4 分析现有曲调中的和弦。我选择了由来自罗德岛普罗维登斯的 ska 乐队 The Agents 创作和演唱的歌曲“Grow”。他们同意让我使用 AI 分析他们的歌曲,并利用分析结果创作一首类似风格的新歌。

The Agents,照片经许可使用

我运行了 Python 代码从录音中获取歌词和和弦,并使用 GPT-4 按照我上面描述的技术格式化文本。这里是我用来获取和弦分析的提示。我只展示了一个片段,但我将完整的歌词输入了 GPT-4。

RobG: 分析这首歌“Grow”中的和弦。

[第一部分]

[Em] 是的!在那一天,我第一次 [C] 遇见

[C] 你女孩,我第一次 [G] 握住

[G] 你的手,我第一次 [B] 看向

[B] 你的眼睛,我告诉你我爱你

[Em] 从那天起直到永远

[C] 困扰的时光,悲伤,

[C] 笑声

[G] 手牵手,我们现在一起走

[B] 现在无法想象更好的东西了*

[副歌]

[Em] 成长,让你的爱

[G] 成长,[C] 成长

[C] 让你的爱来临

[G] 振作起来,振作起来

[G] 播下种子,让 [B] 你的

[B] 爱情成长,越来越高* . . .

GPT-4: 在 The Agents 的歌曲“Grow”中,有几个不同的和弦进程出现在歌曲的不同部分。这些进程赋予了歌曲结构和情感内容。以下是对每个部分和弦进程的分析:

主歌:主歌的和弦进行是 Em — C — G — B。这个进行包含了大调和小调和弦,创造了张力与释放的平衡。Em(小调)到 C(大调)的过渡带来了一种渴望感,而从 G(大调)到 B(大调)的转移则增加了振奋感。

副歌:副歌的和弦进行是 Em — G — C — G — B。这个进行以与主歌相同的 Em — G — C 开始,但将最后的 B 和弦替换为 G,然后再到 B。这种轻微的变化保持了主歌的振奋感,但在转到 B 之前增加了额外的张力和释放。

桥段:桥段有不同的和弦进行:Bm — G — B — Em — C — G。它以 Bm(小调)和弦开始,增加了一种更阴暗、更内省的质量。接着,进行通过 G(大调)、B(大调)和 Em(小调)和弦,创造出一种积累和期待感,然后返回到主歌和副歌中熟悉的 C — G 和弦。

间奏:间奏的和弦进行较为简单:C — B — Eb — C — Em — G。这个进行为主歌和副歌中的主要和弦进程提供了一个休息,增加了变化,并保持了听众的参与感。

总结来说,《Grow》由 The Agents 创作,使用了大调和小调和弦的组合,在整首歌中创造了张力与释放的平衡。副歌和主歌共享类似的和弦进行,而桥段和间奏则提供了对比和变化,以保持听众的兴趣。

哇,那是对和弦进行的全面分析。看来 GPT-4 对音乐理论了解得很好,并且总结了听众如何反应歌曲的不同部分。

在同一个聊天会话中,我让模型使用类似风格为歌曲创建和弦。我使用 GPT-4 在这一系列的第一部分中创作了这些歌词。

RobG: 为这首歌创建类似风格的和弦。

标题:“与恐惧共舞”*

[第一段]

醒来迎接全新的一天,

感受恐惧但不退缩,

面对未知,这就是我们的起点,

带着开放的心态和无畏的心。*

[前副歌]

不再隐藏,不再怀疑,

是时候释放我们的勇气了。*

[副歌]

与恐惧共舞,不要逃跑,

拥抱挑战,无论发生什么。

找到你的节奏,面对未知,

每一步,你的力量在增长。*

[第二段]

在黑暗中,我们会找到光明,

在梦想的引领下起飞。

不再颤抖,不再恐惧,

一起飞向新的高度。*

*[前副歌] …

[副歌] …*

[桥段]

当恐惧敲你的门时,

站直,面对它。

旅途漫长,但你会发现,

你比你意识到的更强大。*

[副歌] …

[尾声]

与恐惧共舞

与恐惧共舞

与恐惧共舞

与恐惧共舞*

GPT-4:

标题:“与恐惧共舞”

[主歌]

[Em] 醒来迎接一个[G]崭新的一天,

[D] 感受到恐惧却[A]不退缩,

[Em] 面对未知,[G] 就是我们的起点,

[D] 心怀开放和[A]无畏的心。

[Pre-Chorus]

[Am] 不再隐藏,[C] 不再怀疑,

[G] 是时候释放我们的[D]勇气了。

[Chorus]

[Bm] 与恐惧共舞,[C] 不要逃跑,

[G] 拥抱挑战,[D] 无论发生什么。

[Bm] 找到你的节奏,[C] 面对未知,

[G] 每一步,你的[D]力量都在增长。

[Verse 2]

[Em] 在黑暗中,[G] 我们将找到光明,

[D] 被梦引导,[A]展翅高飞。

[Em] 不再颤抖,[G] 不再害怕,

[D] 一起飞向[A]新的高度。

[Pre-Chorus] …

[Chorus] …

[Bridge]

[Am] 当恐惧在你门前敲击[Em],

[C] 高昂地站立,再次面对[D]。

[Am] 旅程漫长,但[Em]你会发现,

[C] 你比你[D]意识到的更强大。

[Chorus] …

[Outro]

[Em] [G] 与恐惧共舞

[D] [A] 与恐惧共舞

[Em] [G] 与恐惧共舞

[D] [A] 与恐惧共舞

我首先注意到它将生成的和弦放在方括号中,与歌词对齐,这很不错。它从我之前的互动中采纳了这种标记风格。其次我注意到它也在 G 大调,就像分析的歌曲“Grow”一样。

这里是和弦进行的对比。

和弦进行比较,图片由作者提供

你可以看到,虽然和弦进行有一些相似之处,但也有一些明显的不同。例如,“Grow”有一些使用 4、5 和 6 和弦的段落,而“与恐惧共舞”始终使用四个和弦进行。

两首歌都大量使用了 Em、G 和 C 和弦。“Grow”混合了 B 和 Bm,而“与恐惧共舞”则混合了 A、Am 和 D。参考一下 GPT-4 之前关于 G 大调的说法。

G 大调由以下和弦组成:G (I)、Am (ii)、Bm (iii)、C (IV)、D (V)、Em (vi) 和 F#dim (vii°)。

“Grow”偶尔使用调外的 B 和弦,而“与恐惧共舞”使用了调外的 A 和弦。我特别喜欢从副歌到前副歌的 A 到 Am 过渡。为了了解更多关于这个过渡的内容,我问了 GPT-4。

RobG:如果一首歌的和弦从 A 变为 Am,而这首歌是 G 大调的,那这叫什么?

GPT-4:在音乐理论中,使用一个与调性不一致的和弦称为“色彩和弦”或“色彩化”。在 G 大调中从 A 大调到 A 小调,A 大调和弦不是调性和弦,因此产生了张力或不和谐的感觉。当和弦变为 A 小调时,音乐回到调性和弦,产生了解决或放松的感觉。

好了,就是这样。我想我喜欢这种和弦变化,因为它回到了大调和弦。这里是带有歌词和和弦的歌曲,你可以自己听听。再次说明,伴奏轨道由 Band-in-a-Box 提供。

与恐惧共舞,歌词和和弦由 GPT-4 提供,制作人:作者

Band-in-a-Box 应用没有为 ska 音乐设置选项,所以我选择了雷鬼风格,并将节奏从每分钟 85 拓展到 150。我还将第二段中的贝斯和鼓去掉,雷鬼音乐中称为“节奏”,并在桥段中去掉了铜管乐器,以增加歌曲的趣味性。总体而言,我认为这首歌相当不错,和弦进行也很奏效。当然,缺少的部分是人声旋律,但我将在这一系列的第三篇也是最后一篇文章中解决这个问题。

查看附录中的另外两首风格为另类摇滚和嘻哈的曲子。

总结与下一步

在这篇文章中,我提供了一些关于 OpenAI 的新 GPT-4 模型的背景信息。我展示了如何使用 Whisper 和 Autochord AI 模型从歌曲中提取歌词和和弦,并使用我在 Google Colab 上的 Python 代码将二者合并。我接着展示了 GPT-4 如何分析现有作品的和弦进行,并为各种风格的新歌曲建议和弦。

我的下一步是使用 GPT-4 为歌曲创作旋律,根据歌词和和弦。我将在系列的最终文章中对此进行详细介绍。

讨论

在没有特定引导的情况下,GPT-4 在创作歌曲时似乎是按照一定的公式进行的。例如,它倾向于使用*(A) 诗句, (B) 副歌, (A) 诗句, (B) 副歌, (C) 桥段, (B) 副歌, 和 (A) 尾声*的形式来创作歌曲。而且它总是使用四和弦模式来构建歌曲部分。

默认情况下,它会使用调式中的常见和弦,但正如你上面所见,你可以引导它使用半音和弦。一般来说,它生成的和弦模式听起来不错。我完全没有使用“再生成”命令。而且这些模式似乎是独特的,偶尔会有创新的火花。

在我上一篇文章的讨论中,我假设大多数音乐爱好者不会太在意他们发现词曲作者使用 AI 系统来帮助创作歌曲歌词。这对于和弦创作也很可能是如此。如果最终歌曲质量很好,那么粉丝们会喜欢这些歌曲,无论使用了什么工具。

源代码

该项目的源代码可以在 GitHub 上找到。

知识共享署名-相同方式共享 4.0 国际许可协议

致谢

我想感谢 Agents 的 Pete Fayan 对这个项目的投入。我还感谢来自 Rhode Iceland 的 Jon Gryckiewicz 和 Hi Fi Monk 的 Fez Aswat 给予的贡献。我感谢 Jennifer Lim、Néstor Nápoles 和 Oliver Strimpel 评审文章并提供反馈。

关于使用 GPT-4 写歌曲的更多信息

这是本系列的第一篇和第三篇文章。

## 使用 GPT-4 创作歌曲:第一部分,歌词

如何使用 OpenAI 的最新语言模型来帮助创作原创歌曲的歌词

towardsdatascience.com ## 使用 GPT-4 创作歌曲:第三部分,旋律

如何使用 OpenAI 的最新语言模型来帮助创作新歌曲的旋律

towardsdatascience.com

参考文献

[1] OpenAI,GPT-4 技术报告(2023)

[2] A, Radford 等,通过大规模弱监督进行鲁棒语音识别(2022)

[4] C. J. Bayron,Autochord - 自动和弦识别库和和弦可视化应用(2021),第 22 届国际音乐信息检索会议

附录

这里有两首我在 GPT-4 的帮助下创作的不同风格的歌曲。你可以在第一篇文章中阅读有关歌词创作的过程。乐队同意让我使用 AI 分析他们的歌曲,并利用这些分析创作出具有类似风格的新歌曲。

消逝的回声

第一首歌基于Rhode Iceland,一支来自马萨诸塞州洛威尔的另类摇滚乐队的《Bloody Brain》。

消逝的回声,歌词和和弦由 GPT-4 提供,作者制作

战场之声

第二首歌基于Hi Fi Monk,一支位于马萨诸塞州波士顿的嘻哈/放克/雷鬼乐队的《Break》。

战场之声,歌词和和弦由 GPT-4 提供,作者制作

使用 GPT-4 写歌:第三部分,旋律

原文:towardsdatascience.com/writing-songs-with-gpt-4-part-3-melodies-848ab3614e7a

如何使用 OpenAI 最新的语言模型帮助创作新歌曲的旋律

Robert A. GonsalvesTowards Data Science Robert A. Gonsalves

·发表于 Towards Data Science ·11 分钟阅读·2023 年 6 月 14 日

--

“一位女士站立着,戴着耳机,看着电脑屏幕,同时对着专业麦克风唱歌,” Image created using an AI image creation program, Midjourney, and edited by the author

这是我关于使用 GPT-4 [1] 创作音乐系列的第三篇也是最后一篇文章。我的 第一篇文章 讲述了歌曲歌词的创作,我的 第二篇文章 探讨了与歌词配套的和弦创作。在这篇文章中,在简要介绍 GPT-4 后,我将展示如何引导模型为歌曲创作旋律,并将和弦和歌词结合起来。我将总结一些通用讨论,并提供一些关于使用 AI 创作音乐的下一步建议。

GPT-4 简介

GPT-4 是 OpenAI 最新的大型语言模型。它可在他们的 ChatGPT 服务的付费版本中使用,费用为每月 20 美元。该模型在他们之前的版本 GPT-3.5 上进行了改进。以下是 OpenAI 在他们的 GPT4 登陆页面 上对新模型的描述摘录。

GPT-4 比以往任何时候都更具创造力和协作性。它可以与用户在创意和技术写作任务上进行生成、编辑和迭代,例如创作歌曲、编写剧本或学习用户的写作风格。… 我们花了 6 个月的时间使 GPT-4 更加安全和一致。在我们内部评估中,GPT-4 对请求不允许的内容的回应概率降低了 82%,而生成事实响应的概率比 GPT-3.5 高出 40% - OpenAI

好吧,他们将“创作歌曲”列为他们创造性任务的例子之一。他们还提到模型变得更加“对齐”,这是 OpenAI 用来描述使 AI 模型遵循人类价值观和意图的流行词汇。你可以在他们的技术报告中阅读更多相关内容。

正如我在之前关于 GPT-4 的文章中提到的,OpenAI 默认收集用户的提示和回应,并可能用它们来改进未来的模型。然而,你可以通过提交此表单来选择退出此数据收集。

使用 GPT-4 创作旋律

目前,大型语言模型只处理输入文本和输出文本。然而,OpenAI 正在致力于扩展 GPT-4 以使用图像作为提示。这个模型尚未向公众开放,但在一个名为Be My Eyes的应用程序的测试版中可以使用。该应用程序通过分析图片和读取场景的文本描述来帮助盲人或视力低下的人完成日常任务。

对于这篇文章,我探索了多种方式来让模型使用文本格式来创作旋律。我发现最好的格式是使用吉他谱(或称为 tab)符号。

吉他谱

音乐家使用吉他谱将弦乐器的音符和和弦转录为纯文本。与通常在谱表上的乐谱不同,吉他谱显示了乐器上每根弦随时间使用的品位。例如,这是《生日快乐歌》的吉他六弦谱的第一行。

《生日快乐歌》旋律的吉他谱 图片由作者提供

时间在横轴上;弦在纵轴上,从最底部的最低到顶部的最高。数字表示在特定时间点需要演奏哪个品位。较大的数字对应更高的音符。在进行实验后,我发现网络训练数据中有足够的吉他谱和相应的评论,允许语言模型以这种格式进行创作。

与 GPT-4 互动

使用我前两篇文章中的示例歌曲,我让 GPT-4 一次创作一个部分的旋律。以下是第一次互动的情况。

RobG: 为歌曲《狗狗和猫咪》用吉他谱格式编写旋律。每个音节播放一个音符。

*[G] 狗狗和猫咪,[C] 它们在街上漫游

[Em] 毛茸茸的朋友们,[D] 他们的心脏在跳动

[G] 爪子和爪子,它们 [C] 统治着土地

[Em] 摆尾巴,[D] 它们懂得*

GPT-4:

我是一个基于文本的 AI 模型,所以我不能创建或播放声音。但我可以给你一个例子,说明你如何使用吉他谱格式将这些和弦应用于简单旋律,每个音符对应一个音节。

提供的基本和弦是 G、C、Em 和 D。我将使用 G 大调音阶作为旋律的基础。

记住,在谱表格式中,线条代表吉他的弦(从底部的低 E 到顶部的高 E),数字表示在该弦上演奏哪个品位。这里是一个简单的例子:

**e|----------------0------------------|-----------------3------------------|
B|---------1---3-----3-1---------1--|-------0---1-3-------3-1------------|
G|-------0---0-----------2-0--------|-----0---0---------------2-0--------|
D|-----2----------------------2------|---2-------------------------------|
A|----------------------------------|-----------------------------------|
E|----------------------------------|-----------------------------------|
     G          C                   Em             D

e|----------------0------------------|-----------------3------------------|
B|---------1---3-----3-1---------1--|-------0---1-3-------3-1------------|
G|-------0---0-----------2-0--------|-----0---0---------------2-0--------|
D|-----2----------------------2------|---2-------------------------------|
A|----------------------------------|-----------------------------------|
E|----------------------------------|-----------------------------------|
     G          C                   Em             D**

这只是一个非常简单的例子,旋律可能无法完全捕捉你歌词的精神,但我希望它能作为一个起点。你可能需要调整节奏和旋律,以更好地适应你的歌曲。

这很有趣。这是一个重复两次的两部分音乐短语。但仔细观察,你会发现短语中的音节太多了;它有十二+十一,而应该是七+七。此外,竖线没有对齐。我为第二段重复了这个过程,并在文本编辑器中稍微整理了一下短语,变成了这样。

**e|-------------0-----|-------------------|
B|-------1---3---3---|-------0---1-3-----|
G|-----0---0---------|-----0---0-----0---|
D|---2---------------|---2---------------|
A|-------------------|-------------------|
E|-------------------|-------------------|
     G       C             Em      D

e|-------------------|-------------------|
B|-------0-3-1-0-----|-------0---3-1-----|
G|-----1---------2---|-----0---0-----2---|
D|---2---------------|---2---------------|
A|-------------------|-------------------|
E|-------------------|-------------------|
     Am      C           G       D**

我在这里写了一些 Python 代码,用于将吉他谱转换为 MIDI 文件,以便我可以播放它们来听听效果。代码基于 Brian Caffey 的类似项目[2]。这是完整的乐谱。

狗与猫的乐谱,作者图像

这就是歌曲的声音。我使用了Band-in-a-Box来安排包含钢琴、吉他、贝斯和鼓的歌曲。欢迎你跟着唱。😀

狗与猫,作者歌曲

它简单却令人难忘,有点像儿歌。第一个音符有点奇怪,因为它是 E,这样的话 G 和弦实际上就是 G6。而且每次出现 G 和弦时都是如此。我认为这首曲子的最好之处在于第三次通过时的 Am 和 G 变奏。接下来,我将展示我如何为一首 ska 歌曲创作旋律。

“ska 乐队的专辑封面,黑白色,” Midjourney 图像,作者编辑

为整首歌曲创作一个声乐旋律

为了进一步测试这种方法,我为歌曲《Dance with the Fear》创作了主旋律,这首歌在本系列的前两篇文章中开发。歌词和和弦基于 ska 乐队The Agents的歌曲《Grow》,我在他们的许可下使用了这首歌。我设法让 GPT-4 为新歌创作了一段旋律,一次一个部分。

创作主歌

就像我上面使用的方法一样,我让 GPT-4 为主歌创作了旋律,并提供了和弦和歌词。我多次点击再生按钮,检查结果,然后决定了这个版本。

RobG: 为这首 ska 歌曲的主歌部分创作吉他谱,一次演奏一个音符。

[Em] 醒来迎接一个 [G] 崭新的一天,

[D] 感受到恐惧但 [A] 不退缩,

[Em] 面对未知,[G] 这就是我们的起点,

[D] 以开放的心态和 [A] 无畏的心灵。

GPT-4:

这是一个简单的吉他谱,演奏主歌的旋律,一次一个音符

**e|-----------------|-----------------|-----------------|-----------------|
B|-----7-8-------8-|-8-8-7-7---------|-5-5-7-7-------8-|-8-8-7-7-5--5-5--|
G|---7-------7-7---|---------7--7-7--|-----------7-7---|-----------------|
D|-5---------------|-----------------|-----------------|-----------------|
A|-----------------|-----------------|-----------------|-----------------|
E|-----------------|-----------------|-----------------|-----------------|**

**e|-----------------|-----------------|-----------------|-----------------|
B|-----7-8-------8-|-8-8-7-7---------|-5-5-7-7-------8-|-8-8-7-7-5--5-5--|
G|---7-------7-7---|---------7--7-7--|-----------7-7---|-----------------|
D|-5---------------|-----------------|-----------------|-----------------|
A|-----------------|-----------------|-----------------|-----------------|
E|-----------------|-----------------|-----------------|-----------------|**

请注意,这个谱子是旋律的简化版,你可以根据你的表演或编曲进一步修改和美化它。

这是旋律的声音。

与恐惧共舞歌词,作者作曲

总体来说,还不错。它为第一段确立了主题,并为第二段提供了一个很好的解决。可是,再一次,它为歌词创作了过多的音符。

创建前副歌

前副歌是一段引导歌曲进入副歌的旋律。请注意,ChatGPT 用户界面包含了会话中之前互动的背景,因此我在提示中可以更简洁地生成前副歌的旋律。

为前副歌写一段旋律。

*[Am] 不再隐藏,[C] 不再怀疑,

[G] 是时候让我们的 [D] 勇气释放出来了。*

GPT-4

当然,我可以使用提供的和弦进行为前副歌提供一个简单的旋律。以下是谱子:

**e|------0-0-0-0----|-0---------------|-----------------|-0---------------|
B|----1----------|----3-1-1-1-1---|----1-3-3-1-3-3-|-------3-3-1-3-3-|
G|--0--------------|---------------|----------------|----------------|
D|-----------------|----------------|----------------|----------------|
A|-----------------|----------------|----------------|----------------|
E|-----------------|----------------|----------------|----------------|**

这段旋律分别使用了 A 小调、C 大调、G 大调和 D 大调音阶。像往常一样,欢迎根据你的表演需求进行修改。

我相信它在回应中指的是“和弦”而不是“音阶”,因为它生成的旋律是 G 大调的。为了确认,我请求了澄清。

RobG: 你是想用“音阶”这个词吗?

GPT-4: 对于任何混淆,我深表歉意。我本意是说旋律音符是基于各自的和弦,而不是音阶。

好的,这清楚了。至于旋律,我感觉它写的许多音符可以被解释为持音。换句话说,一连串的四个八分音符可以演奏成一个二分音符。这反映了吉他谱格式的一个局限性,即音符长度没有表达。透过这个视角来看,旋律很好。以下是前副歌的旋律,包括最初的版本和我将一些重复音符转换为更长时值的单音符后的版本。

与恐惧共舞,前副歌,作者作曲

好的,第二次录音效果更好,音符保持得更好。以下是歌词和和弦,供参考。

[Am] 不再隐藏,[C] 不再怀疑,

[G] 是时候让我们的 [D] 勇气释放出来了。

所有四个部分

我对副歌和桥段进行了重复处理,并将这些部分汇总成一张包含钢琴和弦的乐谱。以下是歌曲的四个部分。

与恐惧共舞,所有部分,作者作曲

对于这个版本的歌曲,我清理了所有重复的音符,使它们与歌词的节奏和拍号匹配。总体来说,旋律的质量很好。没有不和谐的音符,短语与基础和弦契合。

接下来,你可以听到这首歌的真实人声演唱。

凯特·李演唱《与恐惧共舞》,作者作图

我非常感谢我的朋友兼同事 Kate Li,她为本文演唱了旋律。她将歌曲从 G 调升高了六个半音至 D-flat 调,以适应她的音域。我使用 Band-in-a-box 为乐器部分填充了钢琴、吉他、小号、贝斯和鼓。这是完成后的歌曲。

与恐惧共舞, 作者歌曲

Kate 在演唱主唱部分和为副歌添加和声方面表现出色。她在副歌的桥段中用上升的旋律带来了很多情感冲击,并为结尾部分提供了一些不错的即兴演唱。请在这里或在SoundCloud分享你对歌曲的看法。

讨论

这花了一些功夫,但我让 GPT-4 生成了新歌曲的旋律。吉他谱格式很棘手,但目前是最好的方式。当 OpenAI 发布其带有视觉功能的 GPT-4 版本时,我希望它能够看到乐谱,以帮助创作旋律。

在本系列中,我设定的三个音乐创作任务中,分析和生成和弦在第二篇文章中效果最好。尽管不完美,但语言模型对音乐理论有深刻的理解,能够轻松创造新的和创新的和弦进行。

分析和创作歌曲歌词,在第一篇文章中讨论了,使用 GPT-4 也很简单,但生成的歌词通常简单且陈词滥调。尽管系统知道如何押韵,但引导系统创作独特、令人兴奋且有意义的歌词仍需一些工作。

源代码

该项目的源代码可以在GitHub上找到。

知识共享署名-相同方式共享

致谢

我要感谢 Kate Li 为本文演唱了歌曲。我还要感谢来自 Agents 的 Pete Fayan 对这个项目的参与。最后,我感谢 Jennifer Lim 对文章的审阅和反馈。

更多关于使用 GPT-4 创作歌曲的内容

这是本系列的前两篇文章。

## 使用 GPT-4 创作歌曲:第一部分,歌词

如何使用 OpenAI 最新的语言模型来帮助创作原创歌曲的歌词

towardsdatascience.com ## 使用 GPT-4 创作歌曲:第二部分,和弦

如何使用 OpenAI 最新的大型语言模型来帮助创作原创歌曲的和弦

towardsdatascience.com

参考文献

[1] OpenAI,GPT-4 技术报告(2023 年)

[2] B. Caffey,使用 Python 和正则表达式从吉他谱生成 MIDI 文件(2018 年)

XAI 预测:基础扩展

原文:towardsdatascience.com/xai-for-forecasting-basis-expansion-17a16655b6e4?source=collection_archive---------6-----------------------#2023-03-24

图片来源:理查德·霍尔瓦斯 在 Unsplash

NBEATS 和其他可解释的深度预测模型

Nakul UpadhyaTowards Data Science Nakul Upadhya

·

关注 发表在 Towards Data Science ·15 分钟阅读·2023 年 3 月 24 日

--

预测是许多行业中的关键方面,从金融到供应链管理。多年来,研究人员探索了各种预测技术,从传统的时间序列方法到基于机器学习的模型。

近年来,预测人员转向深度学习,并在如长短期记忆 (LSTM) 网络和时间卷积网络 (CNNs) 等模型中获得了有希望的结果。2019 年之前,解决预测问题的主要方法是将传统统计方法(如 ARIMA)与深度学习相结合 [1]。然而,预测文献在 2020 年突破了这一点,并朝两个不同的方向发展。

第一个方向涉及开发利用注意力机制和变换器架构进行预测的方法。这一方法由 Li 等人首次提出的 LogSparse Transformer 开创,该方法调整了传统的自注意力机制,使其在 NLP 任务中对局部性更为敏感且使用更少的内存 [5]。此工作后来由 Autoformer [6]、Informer [7]、FEDFormer [8] 等模型进一步扩展和改进。这是一个蓬勃发展的领域,但最近的工作对这一方法提出了质疑。Zeng 等人最近质疑了自注意力机制在预测任务中的有效性,并进行了大量实验,显示注意力机制可能对时间表示不太有用 [9](这些实验的总结可以在这篇文章中找到)。此外,从可解释性 AI (XAI) 角度来看,这一方法也稍显不足。尽管这些模型都有可视化的注意力机制,但许多学者认为这可能无法解释,这也是一个活跃的辩论领域。Denis Vorotyntsev 发表了一篇总结辩论的优秀文章,我强烈建议查看他的文章 [10]。

与基于注意力的变换器方法相比,解决预测问题的另一主要方向是由 Oreshkin 等人 [1] 在 2020 年提出的神经基础扩展分析方法,该方法使用 N-BEATS 架构。该方法涉及使用多个堆叠的深度全连接网络来迭代构建预测。网络不是直接预测时间序列,而是预测基础的权重。这种架构允许用户指定他们想要提取的时间序列组件,并且预测的重建性质增加了额外的可解释性。

在本文中,我旨在总结基础分析架构的完整机制,并展示自原始 N-BEATS 论文以来基础扩展方法的改进。具体而言,我将涵盖 N-BEATS 的工作原理 [1] 以及对 NBEATS 的两个改进:N-HiTS [2] 和 DEPTS [4]。

N-BEATS

图 1:Oreshkin 等人 2020 年的 N-BEATS 架构 [1]

N-BEATS 代表神经基础扩展分析(Neural Basis Expansion Analysis)用于时间序列(Time Series),是基础扩展架构的原始模型。因此,我们将花更多时间解释这一架构,相较于其他模型。

在开发 N-BEATS 时,作者的目标是通过创建一个不依赖于任何统计概念(如最大似然估计或传统自回归模型(例如 ARIMA))的预测模型来展示深度学习的力量,同时仍提供一些传统时间序列方法所提供的可解释性[1]。在追求这些特性的过程中,Oreshkin 等人不仅达成了他们的目标,还证明了他们的模型比当时的最先进技术更优秀。使 N-BEATS 成为强大模型的一些特性包括:

  1. 优异的性能:在 M4 数据集(包含 100,000 个不同时间序列的数据集)上,N-BEATS 能够击败当时的顶尖表现者 ES-RNN [11],平均超出 3%。

  2. 大规模和多重预测:N-BEATS 用于生成预测的方法可以直接进行多步预测,避免了由于迭代预测生成(将预测结果回传到模型中)所带来的预测漂移问题。

  3. 相对较快的训练:与 Transformer 对应模型相比,N-BEATS 仅包含 MLP 堆栈,并且在其架构中没有任何递归网络,使得训练过程更为简单。

  4. 可解释性:当使用 N-BEATS 的可解释版本(N-BEATSi)时,用户可以清楚地看到时间序列成分的分解,如趋势和季节性。其他时间序列数据集也取得了令人惊叹的成功。

为了实现这些目标,N-BEATS 使用了一些巧妙的架构组件和技巧。

块与基础

块可能是 N-BEATS 架构中最重要的部分。

如图 1 中蓝色部分所示,块包含 1 个全连接网络堆栈,该堆栈将其输出传递到 2 个不同的线性层。我们可以将全连接层视为生成输入的编码。这两个线性层随后接收这个编码并将其投射到 2 组权重中:一组用于预测基础,一组用于回溯基础。在数学上,这组操作可以描述如下(这有一个 4 层全连接网络):

图片来源:Oreshkin 等人 2022

在这里,x_l 是输入到 l-th 块中的数据,而 thetas 是基础权重。

基础

现在需要注意的是,当我们提到“基础”时,我们指的是线性代数中的意义。换句话说,基础是一组线性独立的向量,我们可以将其线性组合形成向量空间中的任何其他向量。在 N-BEATS 的情况下,线性组合的权重由神经网络定义。

通用架构遵循这一确切定义:

图片来源于 Oreshkin 等人 2022

其中 y 是由块 1 预测的时间序列部分。最终输出只是由用户定义的或作为可训练参数设置的一组基向量的加权和。然而,这种基向量版本不可解释,也不试图显式捕捉时间序列的特定成分。不过,通过更改使用的基方程,N-BEATS 可以非常容易地修改成更具解释性的版本。作者将这种新配置称为 N-BEATSi [1]。

第一个可解释的基向量是趋势基向量。这将基向量更改为一系列低次幂的多项式(例如 at+bt²+ct³),我们的网络预测每个多项式项的权重。

图片来源于 Oreshkin 等人 2022

第二个可解释的基向量是季节性基向量。与趋势基向量类似,网络预测一组方程的权重,但这一次它预测的是傅里叶级数的权重。

图片来源于 Oreshkin 等人 2022

最终,网络实际上为我们的时间序列提供了一个回归方程,使用户能够理解时间序列的具体工作原理 [1]。

回溯预测与预测

每个块不仅产生预测结果,还会产生“回溯预测”——对输入窗口的预测。回溯预测可以被视为一种过滤机制。当一个块产生预测结果时,它产生的回溯预测是时间序列中已经被分析的部分,后续的块不应该再捕捉这些信息,因为当前块已经捕捉到了这些信息 [1]。

在通用架构中,预测和回溯预测将由不同的网络产生不同的权重,我们依赖训练过程使它们相互对齐。这是不可避免的,因为预测和回溯预测的基向量维度不同。然而,由于可解释版本使用的方程依赖于时间步,因此在使用可解释基向量时,强烈建议让回溯预测和预测共享基向量权重 [1]。

双重残差堆叠

N-BEATS 不只是由 1 个块组成,而是由多个串联在一起的块组成,每个块解释时间序列的一个信号。允许这种行为的关键机制是双重残差堆叠。简单来说:当前块的输入等同于前一个块的输入减去前一个块的回溯预测。此外,最终预测是所有块预测的总和 [1]。

图片来源于 Oreshkin 等人 2022

在通用情况下,这种双重残差堆叠机制允许更平滑的梯度流动和更快的训练。此外,“有意义的部分预测的聚合”也提供了较高的可解释性,因为它帮助用户识别他们尝试预测的时间序列中的关键信号[1]。此外,通过检查所有块的回溯,用户可以查看是否有任何信号或模式被忽略或是否有过拟合,这大大有助于调试过程。

堆栈

为了更好地组织网络,架构还将各个块组织成堆栈。在每个堆栈中,所有块都共享相同类型的基础(通用、趋势或季节性)。这种组织不仅有助于信号分解过程,还可以提供更多的可解释性,因为现在我们可以直接查看堆栈级别的输出,而无需检查每个块。

要了解这些解释的具体样子,请查看 PyTorch Forecasting 文档中的精彩示例。

协变量

虽然原始的 N-BEATS 架构仅适用于单变量时间序列,但 Challu 等人[3] 引入了允许协变量的方法。

第一种方法是将协变量简单地展平并附加到每个块中的全连接层的输入上。每个回溯和预测仍然仅针对目标时间序列,残差堆叠机制仅适用于目标,而不适用于协变量。

第二种方法是首先生成协变量的编码,然后将其作为块的基础:

图片来源于 Challu 等人 2023

DEPTS

DEPTS 架构来源于 Fan 等人 2022[4]

DEPTS 代表 Deep Expansion Learning for Periodic Time Series Forecasting,顾名思义,这是一种对 NBEATS 的改进,专注于提升原始模型的周期性/季节性预测能力 [4]。具体来说,DEPTS 在两个方面改变了 NBEATS 的功能:

  1. 虽然 NBEATS/NBEATSi 可以通过傅里叶基来处理周期性,但这种方法难以考虑超出回溯窗口的大周期行为,而 DEPTS 更好地处理这种情况 [4]。

  2. NBEATS/NBEATSi 只能考虑加法季节性(因为它是逐步构建预测的)。DEPTS 也能处理乘法季节性 [4]。

周期性模块与周期性上下文

DEPTS 用来实现上述两个特性的第一个机制是其专用的周期性模块,该模块接受回溯窗口和预测范围的时间步长,并生成由余弦序列定义的季节性上下文向量 [4]:

图片来源于 Fan 等人 2022[4]

在这个模块中,AFP 都是可训练的参数。通过允许网络学习这些参数并在预测中共享它们,模型可以直接学习全局周期模式,而无需像 NBEATS 模型那样基于输入数据重新推断模式 [4]。

然而,这一学习过程中的一个棘手之处在于,在尝试学习 AFP 时,模型很容易陷入局部最小值。为了解决这个问题,会进行预学习优化以初始化参数值。注意:在这个方程中,phi 代表可学习参数的集合,M 是一个与生成函数中的每一项相乘的二进制向量(如果选择该项则为 1,否则为 0) [4]。

图片来源于 Fan 等人 2022 [4]

简单来说,在训练网络之前,先在训练集上进行参数的预拟合,然后选择 J 个最佳频率(用户选择 J)。为了避免解决整个优化问题,作者建议进行离散余弦变换,并选择具有最高幅度的 J 项 [4]。

周期性块和周期性残差

周期性模块的全部目的是生成初始周期性上下文向量 z。这些上下文向量被传递到周期性块 [4]。

DEPTS 周期性块架构来自 Fan 等人 2022[4]

简而言之,周期性块接受周期性上下文向量(实际上是时间序列的季节性成分),并通过一个非常小的子网络,生成类似于传统 NBEATS 块的回溯和预测。这处理了周期性模块处理的加性季节性,并将其处理为应对非线性季节性 [4]

这些周期性回溯和预测是如何使用的?深入探讨 DEPTS 的一层可以给我们更多的见解。注意在这个图中,x 是目标序列 [4]。

DEPTS 层架构来自 Fan 等人 2022[4]

一旦模型生成了预测向量回溯和预测,回溯将从局部块的输入中减去,预测则添加到整体模型预测中。局部块与 NBEATS 中的块相同,执行相同的操作,并使用相同的双重残差堆叠机制。DEPTS 还使用双重残差堆叠机制来处理上下文向量,我们从周期性上下文向量中去除解释的季节性,并将局部块输出和周期性上下文向量传递到下一层 [4]。

所有这些机制结合在一起使 DEPTS 成为一个周期性预测的强大工具 [4]。

可解释性

像 NBEATS 一样,DEPTS 保持了 NBEATS 的许多解释优势,即预测的聚合。然而,DEPTS 还提供了对全球周期模式的观察,这是 NBEATS 所没有的。通过运行周期性模块,可以轻松可视化全球周期模式。此外,通过查看“剩余”模式,可以检查最终的周期上下文向量,以了解局部预测如何可能偏离全球模式。

N-HiTS

N-HiTS 架构来自 Challu 等人 2022[2]

NBEATS 的第二大改进是 N-HiTS 或Neural Hierarchical Interpolation for Time Series Forecasting [2]。虽然 DEPTS 引入了专门的周期性预测机制,N-HiTS 则专注于更好地构建预测聚合过程,并提供了两个主要优势:

  1. 通过层次化的预测聚合方法,N-HiTS 在识别和提取信号方面比 N-BEATS 在大多数情况下更为出色[2]。

  2. 通过插值机制,N-HiTS 的内存使用量比 NBEATS 小得多,使得训练变得更加容易[2]。

这一切都得益于层次插值机制,它由两个部分组成:多速率信号采样和插值[2]。

多速率信号采样

N-HiTS 块架构来自 Challu 等人 2022[2]

第一个主要机制是多速率信号采样。这非常简单(这使得它更加美丽):在每个块的开头添加一个 MaxPool 层,该层预处理块输入并有效地平滑它[2]。这为什么有用?好吧,我们可以在每个块中改变 MaxPool 层的内核大小,有效捕捉不同大小的信号[2]。 此外,MaxPool 还通过提供平滑处理,使模型对噪声的敏感性降低,本身就是一个强大但简单的工具[2]。

插值

多速率采样非常强大,但与插值机制结合使用时效果更佳。

回到原始的通用 N-BEATS 块架构,每个块生成两组权重:回溯权重集和预测权重集。通常,生成的权重数量等于窗口L的长度(用于回溯)或预测H的长度[1]。然而,这种方法存在一些问题。首先,预测所有权重可能导致生成的预测更具波动性和噪声[2]。此外,对于较大的预测范围,这可能导致极高的内存使用[2]。

N-HiTS 选择应用插值,其中预测的参数数量等于 rH*(以及回溯的 rL*),其中 r 是表现比率 [2]。更高的表现比率会导致更多的参数被预测。然后可以通过任何插值方法填补缺失的权重。作者测试了线性插值、最近邻插值和三次插值,但也可以使用任何自定义方法。

通过仅预测少量的权重,与 N-BEATS 相比,N-HiTS 的内存使用极其少,使其成为一个更轻量级的模型。

注意:据我所知,作者尚未提供可解释基函数的插值机制,因此 N-HiTS 限于通用基函数。

分层插值

然后,作者将这种权重插值与上述多速率信号采样结合起来,创建了分层插值机制。在分层插值中,块的表现比率和 MaxPool 核心大小在各个块之间是反向变化的 [2] 任何具有高表现比率的块也使用小的核心大小。任何具有低表现比率的块使用大的核心大小。这两个参数的同步正是使 N-HiTS 能够捕捉不同幅度信号的原因,如下图所示 [2]:

线性插值(左)与非插值(右)。图源自 Challu 等人 2022 [2]

直观上,这也有意义:如果对函数应用了强烈平滑(即大核心大小 MaxPool),则需要更少的参数来捕捉输入的行为,反之亦然。

分层插值还为 NBEATS 的通用形式提供了更多的可解释性。用户可以查看堆叠输出,并理解哪个在建模更大的模式,哪个在捕捉小的噪声。虽然 N-HiTS 无法使用可解释基方程,但可以认为 N-HiTS 不需要这些方程仍然可以具有可解释性。

结论

当 N-BEATS 出现时,它改变了深度预测领域,并催生了一个新的预测分支。突然间,我们可以从一个相对简单的模型中获得高度准确的预测,该模型可以端到端地训练。N-HiTS 和 DEPTS 然后能够在 NBEATS 的基础上进行扩展,增加了更多功能,增强了基扩展方法的特定方面。值得注意的是,N-HiTS 的分层插值与 DEPTS 的周期性模块/块/上下文向量也可以一起使用,创建一个更强大的模型。

还有一些工作将注意力机制应用于这些基础扩展方法,主要是 Jiang 等人提出的 DeepFS 模型 [12],该模型使用了一个注意力层(改编自 [7])来生成编码,然后将该编码传递到类似周期性 N-BEATS 块的模块中,以及传递到一个前馈堆栈中。尽管这可以被视为一种改进,但我在本文中没有详细描述此模型,因为正如 [9] 所示,时间序列预测中注意力机制的益处仍然存在争议。

关于基础扩展是否可解释也存在一些争论。如果最终我们的模型产生了 40 个不同的方程,这是否能被解释?此外,尽管我们可以识别这些模型所识别的所有信号,但确定这些信号的机制仍然相对不透明,我们可能不知道为什么模型选择接收或忽略某个信号。当将这些模型适应到使用协变量时间序列时,可解释性也会降低。

尽管存在这些缺陷,但本文讨论的所有模型在表现上始终表现出强大的性能,并为预测领域的更多创新铺平了道路。

资源和参考文献

资源和参考文献

  1. N-BEATS 和 N-HiTS 可以通过 Darts 和 Pytorch Forecasting 这两个高质量的 Python 预测包来使用。

  2. DEPTS 的源代码

  3. N-HiTS 的源代码

  4. NBEATSx 的源代码

  5. NBEATS 的源代码

参考文献

[1] B.N. Oreshkin, D. Carpow, N. Chapados, Y. Bengio. N-BEATS: 神经基础扩展分析用于可解释的时间序列预测 (2020). 第八届国际学习表征会议。

[2] C. Challu, K.G. Olivares, B.N. Oreshkin, F. Garza, M. Mergenthaler-Canseco, A. Dubrawski. N-HiTS: 神经层次插值用于时间序列预测 (2022). 第三十七届 AAAI 人工智能会议。

[3] K.G. Olivares, C. Challu, G. Marcjasz, R. Weron, A. Dubrawski. 带外生变量的神经基础扩展分析:使用 NBEATSx 预测电力价格 (2023). 国际预测期刊。

[4] W. Fan, S. Zheng, X. Yi, W. Cao, Y. Fu, J. Bian, T-Y. Liu. DEPTS: 深度扩展学习用于周期时间序列预测 (2022). 第十届国际学习表征会议。

[5] S. Li, X. Jin, Y. Xuan, X. Zhou, W. Chen, Y. Wang, X. Yan. 增强变换器在时间序列预测中的局部性并突破记忆瓶颈 (2019). 神经信息处理系统大会第 32 届

[6] H. Wu, J. Xu, J. Wang, M. Long. Autoformer:具有自相关的分解变换器用于长期序列预测 (2021). 神经信息处理系统大会第 34 届

[7] H. Zhou, S. Zhang, J. Peng, S. Zhang, J. Li, H. Xiong, W. Zhang. Informer:超越高效变换器用于长序列时间序列预测 (2021). 第三十五届 AAAI 人工智能大会,虚拟会议

[8] T. Zhou, Z. Ma, Q. Wen, X. Wang, L. Sun, R. Jin. EDformer:用于长期序列预测的频率增强分解变换器 (2022). 第 39 届国际机器学习会议

[9] A. Zeng, M. Chen, L. Zhang, Q. Xu. 变换器对时间序列预测是否有效? (2022). 第三十七届 AAAI 人工智能大会

[10] D. Vorotyntsev. 注意力是解释吗? (2022). 数据科学前沿

[12] S. Jiang, T. Syed, X. Zhy, J. Levy, B. Aronchik, Y. Sun. 桥接自注意力和时间序列分解以进行周期性预测 (2022). 第 31 届 ACM 国际信息与知识管理会议

[11]S. Smyl, J. Ranganathan, A Pasqua. M4 预测竞赛:介绍一种新的混合 ES-RNN 模型 (2018). 优步研究