线性回归 — 预测机器学习建模的奥卡姆剃刀
使用 Python 进行线性回归的机器学习建模
·发表于 Towards Data Science ·阅读时间 16 分钟·2023 年 1 月 3 日
–
水晶球,由 DALL.E 2
你熟悉奥卡姆剃刀吗?我记得在《生活大爆炸》电视剧中提到过它!奥卡姆剃刀的观点是,在其他条件相同的情况下,现象的最简单解释更可能是真实的,而不是更复杂的解释(即最简单的解决方案几乎总是最佳解决方案)。我认为机器学习预测建模中的奥卡姆剃刀就是线性回归,这几乎是使用的最简单建模方法,并且对于某些任务可能是最佳解决方案。本文将介绍线性回归的概念和实现。
与我的其他帖子类似,通过实践问题和答案来实现学习。我会在问题中提供提示和解释,以便让过程更简单。最后,我使用的创建此练习的笔记本也链接在文章底部,你可以下载、运行并跟随学习。
开始吧!
(除非另有说明,否则所有图片均由作者提供。)
[## 通过我的推荐链接加入 Medium - Farzad Mahmoodinobar
阅读 Farzad 的每一个故事(以及 Medium 上其他作者的故事)。你的会员费直接支持 Farzad 和其他人…
medium.com](https://medium.com/@fmnobar/membership?source=post_page-----f2ba5b144a2b--------------------------------)
数据集
为了练习线性回归,我们将使用来自 UCI 机器学习库(CC BY 4.0)的汽车价格数据集。我已经清理了部分数据供我们使用,可以从 这个链接 下载。
我将解释我们在练习中使用的线性回归模型背后的数学。虽然理解这些数学内容不是成功理解本文内容的必要条件,但我建议你了解它,以便更好地理解创建线性回归模型时的幕后过程。
线性回归基础
线性回归是使用线性预测变量(或自变量)来预测因变量。一种简单的例子是直线公式:
在这种情况下,y
是因变量,而 x
是自变量(c
是一个常数)。线性回归模型的目标是确定最佳系数(上例中的 a
),以使 x
能最准确地预测 y
。
现在,让我们将这个例子推广到多元线性回归。在多元线性回归模型中,目标是找到描述因变量与多个自变量之间关系的最佳拟合线。
在这种情况下,我们有多个自变量(或预测变量),从 x_1
到 x_n
,每个自变量都乘以它自己的系数,以预测因变量 y
。在线性回归模型中,我们将尝试确定系数 a_1
到 a_n
的值,以获得对因变量 y
的最佳预测。
现在我们了解了线性回归的概念,让我们转向普通最小二乘(OLS)回归,这是一种线性回归形式。
普通最小二乘回归
普通最小二乘回归模型通过最小化残差平方和来估计回归模型的系数。残差是回归线(即预测值)与实际值之间的垂直距离,如下图所示。这些残差被平方,以便错误不会相互抵消(当一个预测值高于实际值,而另一个预测值低于实际值时,这两个仍然是错误,不应相互抵消)。
普通最小二乘回归——回归线和残差
现在我们理解了基本概念,我们将开始探索数据和我们可能用来预测汽车价格的变量(或特征)。然后,我们将数据分为训练集和测试集,以建立回归模型。接下来,我们将查看回归模型的性能,最后绘制结果。
让我们开始吧!
1. 探索性分析
让我们首先查看数据,可以从这里下载。首先,我们将导入 Pandas 和 NumPy。然后,我们将读取包含数据集的 CSV 文件,并查看数据集的前五行。
# Import libraries
import pandas as pd
import numpy as np
# Show all columns/rows of the dataframe
pd.set_option("display.max_columns", None)
pd.set_option("display.max_rows", None)
# To show all columns in one view
from IPython.display import display, HTML
display(HTML("<style>.container { width:100% !important; }</style>"))
# Read the csv into a dataframe
df = pd.read_csv('auto-cleaned.csv')
# Display top five rows
df.head()
结果:
列名大多是显而易见的,所以我只会添加那些对我不太明显的列名。你现在可以忽略这些,并在需要列名定义时参考它们。
-
symboling: 保险公司根据车辆的风险感知分配的值。+3 表示车辆有风险,-3 表示车辆安全
-
aspiration: 标准或涡轮
-
drive-wheels: rwd 表示后驱;fwd 表示前驱;4wd 表示四轮驱动
-
wheel-base: 前后轮中心之间的距离,以厘米为单位
-
engine-type: dohc 表示双顶置凸轮;dohcv 表示双顶置凸轮和气门;l 表示 L 型发动机;ohc 表示顶置凸轮;ohcf 表示顶置凸轮和气门 F 型发动机;ohcv 表示顶置凸轮和气门;rotor 表示旋转发动机
-
bore: 汽缸的内径,以厘米为单位
-
stroke: 汽缸的运动
问题 1:
数据框中是否有缺失值?
回答:
df.info()
结果:
如我们所见,共有 25 列(注意列编号从 0 到 24)和 193 行。列中没有空值。
2. 特征选择
特征选择是识别和选择一组相关特征(也称为“预测变量”、“输入”或“属性”)以构建机器学习模型的过程。特征选择的目标是通过减少模型的复杂性和消除不相关、冗余或噪声特征,从而提高模型的准确性和可解释性。
问题 2:
创建一个表格,显示数据框中列之间的相关性。
回答:
我们将使用pandas.DataFrame.corr
,它计算列之间的逐对相关性。需要考虑两个要点:
-
pandas.DataFrame.corr
将排除空值。我们确认我们的数据集不包含任何空值,但在处理含有空值的练习中,这可能很重要。 -
我们将仅限制数值的相关性,并将在后续练习中讨论分类值。
作为回顾,让我们在继续之前复习一下分类变量和数值变量是什么。
在机器学习中,分类变量是可以取有限数量值的变量。这些值表示不同的类别,值本身没有固有的顺序或数值意义。分类变量的例子包括性别(男性或女性)、婚姻状况(已婚、单身、离婚等)。
数值变量是可以在特定范围内取任意数值的变量。这些变量可以是连续的(意味着它们可以在特定范围内取任意值)或离散的(意味着它们只能取特定的、预定的值)。数值变量的例子包括年龄、身高、体重等。
处理完这些后,让我们计算相关性。
corr = np.round(df.corr(numeric_only = True), 2)
corr
结果:
问题 3:
在上一问题中生成了许多相关值。我们更关心与汽车价格的相关性。展示与汽车价格的相关性,并按从大到小的顺序排列。
答案:
price_corr = corr['price'].sort_values(ascending = False)
price_corr
结果:
这非常有趣。例如,“engine-size”似乎与价格的相关性最高,这在预期之中,而“compression-ratio”似乎与价格的相关性不那么高。另一方面,“symboling”,我们记得这是衡量汽车风险的指标,与汽车价格呈负相关,这也符合直觉。
问题 4:
为了更专注于相关特征以构建汽车价格模型,筛选出与价格相关性较弱的列,我们将定义任何相关性绝对值小于 0.2 的特征(这是为了本练习而任意选择的值)。
答案:
# Set the threshold
threshold = 0.2
# Drop columns with a correlation less than the threshold
df.drop(price_corr.where(lambda x: abs(x) < threshold).dropna().index, axis = 1, inplace = True)
df.info()
结果:
我们看到由于这个原因,我们现在剩下 19 个特征(总共有 20 列,但其中一个是价格本身,所以剩下 19 个特征或预测变量)。
问题 5:
现在我们有了更可管理的特征数量,重新查看一下它们,看看是否需要删除其中任何一个。
提示: 一些特征可能非常相似,可能是冗余的。有些可能实际上并不重要。
答案:
让我们查看数据框,然后查看剩余特征之间的相关性。
df.head()
结果:
# Calculate correlations
round(df.corr(numeric_only = True), 2)
结果:
“wheel-base”(前后轮之间的距离)和“length”(汽车的总长度)高度相关,似乎传达了相同的信息。此外,“city-mpg”和“highway-mpg”也高度相关,因此我们可以考虑删除其中一个。让我们继续删除“wheel-base”和“city-mpg”,然后再查看数据框的前五行。
# Drop the columns
df.drop(['wheel-base', 'city-mpg'], axis = 1, inplace = True)
# Return top five rows of the remaining dataframe
df.head()
结果:
正如我们所见,新的数据框更小,不包括我们刚刚删除的两个列。接下来,我们将讨论分类变量。
2.1. 虚拟编码
让我们更仔细地查看“make”和“fuel-type”列的值。
df['make'].value_counts()
结果:
df['fuel-type'].value_counts()
结果:
这两列是分类值(例如丰田或柴油),而不是数值。
为了将这些分类变量纳入我们的回归模型中,我们将为这些分类变量创建“虚拟编码”。
虚拟编码是将一个列中的分类变量(或预测变量)替换为多个二进制列。例如,假设我们有一个如表中所示的分类变量:
分类变量 — 虚拟编码前
如上表所示,“random_categorical_variable”可以有 A、B 和 C 三个分类值。我们希望通过虚拟编码将分类变量转换为一种更易于在回归模型中使用的格式,这将把它转换为 A、B 和 C 三个单独的列,并带有二进制值,如下所示:
分类变量 — 虚拟编码后
让我们看看如何在 Python 中实现虚拟编码。
问题 6:
对我们数据框的分类列进行虚拟编码。
答案:
让我们首先看看数据框在虚拟编码前的样子。
df.head()
结果:
从之前的问题我们知道,“fuel-type”列可以取 2 个不同的值(即汽油和柴油)。因此,在虚拟编码后,我们期望将“fuel-type”列替换为 2 个单独的列。其他分类列也是如此,具体取决于每个列有多少个唯一值。
首先,我们只对“fuel-type”列进行虚拟编码作为示例,并观察数据框的变化,然后我们可以继续对其他分类列进行虚拟编码。
# Dummy code df['fuel-type']
df = pd.get_dummies(df, columns = ['fuel-type'], prefix = 'fuel-type')
# Return top five rows of the updated dataframe
df.head()
结果:
正如预期的那样,我们现在有两个原始的“fuel-type”列,分别命名为“fuel-type_gas”和“fuel-type_diesel”。
接下来,让我们识别所有分类列并对其进行虚拟编码。
# Select "object" data types
columns = df.select_dtypes(include='object').columns
# Dummy code categorical columns
for column in columns:
df = pd.get_dummies(df, columns = [column], prefix = column)
# Return top five rows of the resulting dataframe
df.head()
结果:
请注意,上述快照未覆盖虚拟编码后的所有列,因为现在我们有 63 列,这在快照中展示会显得太小。
最后,现在我们创建了所有这些新列,让我们重新创建价格与所有其他列之间的相关性,并从高到低进行排序。
# Re-create the correlation matrix
corr = np.round(df.corr(numeric_only = True), 2)
# Return correlation with price from highest to the lowest
price_corr = corr['price'].sort_values(ascending = False)
price_corr
结果:
如上所示,一些分类变量与价格有较高的相关性,例如“drive-wheels”和“num-of-cylinders”。
到此为止,我们已经对数据有所了解,并在一定程度上清理了数据,现在让我们继续进行创建模型以根据这些属性预测汽车价格的主要目标。
3. 将数据分割为训练集和测试集
此时,我们将首先将数据拆分为因变量和自变量。因变量或“y”是我们要预测的内容,在这个练习中是“价格”。它被称为因变量,因为它的值依赖于自变量的值。自变量或“X”是目前数据框中所有其他变量或特征,包括“发动机尺寸”、“马力”等。
接下来,我们将数据拆分为训练集和测试集。顾名思义,训练数据集将用于训练回归模型,然后我们将使用测试集来测试模型的性能。我们拆分数据是为了确保模型在训练过程中看不到测试集,从而使测试集能够很好地代表模型的性能。将数据拆分为训练集和测试集很重要,因为使用相同的数据来拟合模型和评估其性能可能导致过拟合。过拟合发生在模型过于复杂时,它学习了数据中的噪声和随机波动,而不是潜在的模式。因此,模型可能在训练数据上表现良好,但在新见数据上表现不佳。
问题 7:
将因变量(目标)分配给 y,将自变量(或特征)分配给 X。
答案:
X = df.drop(['price'], axis = 1)
y = df['price']
问题 8:
将数据拆分为训练集和测试集。使用 30% 的数据作为测试集,并使用 random_state
为 1234。
答案:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 1234)
问题 9:
使用训练集训练线性回归模型。
答案:
from sklearn.linear_model import LinearRegression
# First create an object of the class
lr = LinearRegression()
# Now use the object to train the model
lr.fit(X_train, y_train)
# Let's look at the coefficients of the trained model
lr.coef_
结果:
我们将讨论这里发生了什么,但首先让我们看看如何评估机器学习模型。
4. 模型评估
4.1. R²
问题 10:
训练模型的得分是多少?
答案:
为此,我们可以使用 LinearRegression 的 “score()” 方法,它返回预测的决定系数或 R²$,计算如下:
最好的得分是 1.0. 一个始终预测“y”期望值的常数模型,无论输入特征如何,都将得到 R² 分数 0.0。
了解这些知识后,我们来看看实现过程。
score = lr.score(X_train, y_train)
print(f"Training score of the model is {score}.")
结果:
问题 11:
预测测试集的值,然后评估训练模型在测试集上的表现。
答案:
# Predict y for X_test
y_pred = lr.predict(X_test)
score_test = lr.score(X_test, y_test)
print(f"Test score of the trained model is {score_test}.")
结果:
4.2. 均方误差
均方误差(Mean Squared Error, MSE)是平方误差的平均值,计算如下:
问题 12:
计算测试集预测结果的均方误差(Mean Squared Error)和决定系数(R²)。
答案:
from sklearn.metrics import mean_squared_error, r2_score
print(f"R²: {r2_score(y_pred, y_test)}")
print(f"MSE: {mean_squared_error(y_pred, y_test)}")
结果:
问题 13:
你如何解读前一个问题的结果?对下一步有什么建议?
答案:
R² 相对较高,但均方误差(MSE)也很高,这可能表明误差可能过大——注意这实际上取决于业务需求以及模型的用途。在某些情况下,90.6% 的 R² 可能足够满足业务需求,而在其他情况下,这个数字可能还不够好。这种性能水平可能受到一些不是强预测价格的特征的影响。让我们看看能否识别出哪些特征不是强预测因子并将其删除。然后我们可以重新训练并再次查看评分,以查看是否能够改善我们的模型。
为了尝试一些新的方法,我们将使用 statsmodels 库中的普通最小二乘法(OLS)。训练和预测测试集值的步骤与之前相同。
# Import libraries
import statsmodels.api as sm
# Initialize the model
sm_model = sm.OLS(y_train, X_train).fit()
# Create the predictions
sm_predictions = sm_model.predict(X_test)
# Return the summary results
sm_model.summary()
结果:
这个方法提供了特征的良好展示和该特征显著性的 p 值测量。例如,如果我们使用 0.05 或 5% 的显著性水平(或 95% 的置信水平),我们可以排除那些“P > |t|”大于 0.05 的特征。
len(sm_model.pvalues.where(lambda x: x > 0.05).dropna().index)
结果:
43
共有 43 列这样的数据。让我们删除这些列,看看结果是否有所改善。
# Create a list of columns that meee the criteria
columns = list(sm_model.pvalues.where(lambda x: x > 0.05).dropna().index)
# Drop those columns
df.drop(columns, axis = 1, inplace = True)
# Revisit the process to create a new model summary
X = df.drop(['price'], axis = 1)
y = df['price']
# Split data into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 1234)
# Train the model
sm_model = sm.OLS(y_train, X_train).fit()
# Create predictions using the trained model
sm_predictions = sm_model.predict(X_test)
# Return model summary
sm_model.summary()
结果:
根据 R 平方值判断,整体性能从 0.967 提升至 0.972,并且我们减少了列数,这使得我们的模型和分析更加高效。
5. 预测值与实际值的图
问题 14:
创建预测值与实际值的散点图。如果所有预测值都与实际值相匹配,我们会期望所有点都沿着一条直线如 f(x) = x
分布。为比较添加一条红色的直线。
答案:
# Import libraries
import matplotlib.pyplot as plt
%matplotlib inline
# Define figure size
plt.figure(figsize = (7, 7))
# Create the scatterplot
plt.scatter(y_pred, y_test)
plt.plot([y_pred.min(), y_pred.max()], [y_pred.min(), y_pred.max()], color = 'r')
# Add x and y labels
plt.xlabel("Predictions")
plt.ylabel("Actuals")
# Add title
plt.title("Predictions vs. Actuals")
plt.show()
结果:
训练模型的预测值与实际值的散点图
正如我们预期的那样,值围绕直线散布,显示出模型产生了较好的预测水平。点在红线的右侧,意味着模型预测的价格高于实际价格,而在红线左侧的点则表示相反的情况。
带有练习问题的笔记本
以下是包含问题和答案的笔记本,您可以下载并进行练习。
结论
在这篇文章中,我们讨论了在某些情况下,最简单的解决方案可能是最合适的解决方案,并介绍了如何将线性回归作为预测机器学习任务中的一种解决方案进行实现。我们首先学习了线性回归背后的数学原理,然后实现了一个模型来根据现有的汽车属性预测汽车价格。接着,我们测量了模型的性能,并采取了某些措施以提高模型的性能,最后通过散点图可视化了训练模型预测值与实际值的比较。
感谢阅读!
如果你觉得这篇文章对你有帮助,请 关注我的 Medium 并订阅以接收我的最新文章!
将线性回归应用于 GPT 的七个步骤
这项朴素的预测方法如何指引我们进入生成式 AI 的世界
·
关注 发表在 Towards Data Science · 9 分钟阅读 · 2023 年 4 月 25 日
–
关于生成式 AI 的写作非常多。有专门讨论其应用、伦理道德问题以及对人类社会风险的文章。如果你想了解技术本身,有从原始研究论文到入门文章和视频的各种材料可供选择。根据你当前的水平和兴趣,你可以找到合适的学习资源。
这篇文章是为特定读者群体写的。这些读者已经学习了机器学习,但不是作为主修科目。他们知道预测和分类是涵盖大多数应用的 ML 的两个主要用例。他们还学习了常见的预测和分类机器学习算法,如线性回归、逻辑回归、支持向量机、决策树以及一些神经网络。他们可能用 Python 和 scikit-learn 等库编写过一些小项目,甚至使用过一些预训练的 TensorFlow 模型,如 ResNet。我认为许多学生和专业人士能够与这种描述产生共鸣。
对这些读者来说,生成式 AI 是一种新的 ML 用例吗?它似乎确实不同于预测和分类。流行的术语如变换器、多头注意力、大型语言模型、基础模型、序列到序列和提示工程,容易让人觉得这与我们熟悉的预测-分类世界截然不同。
本文的核心观点是生成式 AI 只是预测的一个特例。如果你符合我之前描述的 ML 爱好者的特点,那么你可以在七个简单的步骤中理解生成式 AI 的基本工作原理。我从线性回归(LinReg)开始,这是每个人都知道的 ML 技术。在本文中,我探讨了生成式 AI 的一个特定分支——大型语言模型(LLM),主要是因为广受欢迎的 ChatGPT 就属于这个分支。
图片来自 Rajashree Rajadhyax
第 1 步:通过线性回归进行预测
LinReg 确定代表给定数据点的最佳直线。一旦找到这条直线,就用它来预测新输入的输出。
图片来自作者
我们可以将 LinReg 模型写成一个数学函数。以易于理解的方式写出来,它看起来像:
new output = Line Function (new input)
我们也可以为其绘制一个示意图:
这是最基本的预测。LinReg 模型‘学习’最佳直线并用它进行预测。
第 2 步:通过神经网络进行预测
只有在知道数据适合直线时,你才可以使用 LinReg。这通常对单输入单输出问题很容易做到。我们可以简单地绘制图表并直观检查。但在大多数现实问题中,有多个输入。我们无法可视化这样的图表。
此外,现实世界的数据并不总是遵循线性路径。许多时候,最佳拟合形状是非线性的。见下图:
图片来自作者
在多维数据中学习这样一个函数不可能通过简单的方法如 LinReg 来实现。这就是神经网络(NN)发挥作用的地方。NN 不需要我们决定它们应该学习哪个函数。它们自己找到并学习函数,无论它多么复杂。一旦 NN 学习了复杂的、多输入的函数,它们就会使用这个函数进行预测。
我们可以再次写出类似的方程,但有所变化。我们的输入现在是多个,因此我们必须用一个向量来表示它们。实际上,输出也可以是多个,我们也会为它们使用一个向量。
output vector = NN Function (input vector)
我们将绘制这种新的、更强大预测的示意图:
作者的图片
步骤 3:预测一个单词
现在考虑我们有一个问题,输入到神经网络中的是某种语言中的一个单词。神经网络只能接受数字和向量。为了适应这一点,单词被转换为向量。你可以把它们想象成是多维空间中的居民,相关的单词彼此靠近。例如,‘Java’的向量将接近其他编程技术的向量;但它也会接近远东地区的地方,如苏门答腊的向量。
作者的(非常虚构的)单词嵌入
这样一组与语言中单词对应的向量称为‘嵌入’。有许多方法可以创建这些嵌入;Word2Vec 和 GloVe 是两个流行的例子。这些嵌入的典型大小为 256、512 或 1024。
一旦我们有了单词的向量,我们可以在它们上使用 NN 进行预测。但是通过对单词进行预测,我们能实现什么呢?我们可以做很多事情。我们可以将一个单词翻译成另一种语言,找到该单词的同义词或找到它的过去式。这个预测的方程式和示意图看起来与步骤 3 非常相似。
output word embedding = NN Function (input word embedding)
作者的图片
步骤 4:简单翻译的预测
在翻译问题中,输入是一种语言的句子,输出是另一种语言的句子。我们如何利用对单词预测的已有知识来实现翻译呢?这里我们采用一种简单的翻译方法。我们将输入句子的每个单词转换为另一种语言中的等效单词。当然,真正的翻译不会像这样工作;但在这一步骤中,假装它会。
这次我们先画出示意图:
作者的图片
第一个单词的方程式将是:
NN Translation Function (Embeddings for word. no 1 in input sentence)
= Embedding for word no.1 in output sentence
我们可以类似地为其他单词写出方程式。
此处使用的神经网络通过查看许多单词对的示例来学习翻译功能。我们为每个单词使用一个这样的神经网络。
因此,我们有了一个使用预测的翻译系统。我已经承认这是一种幼稚的翻译方法。有哪些改进可以使其在现实世界中有效?我们将在接下来的两步中看到。
步骤 5: 使用上下文的预测
幼稚方法的第一个问题是,一个词的翻译依赖于句子中的其他词。例如,考虑下面的英语到印地语的翻译:
输入(英语): ‘Ashok sent a letter to Sagar’
输出(印地语): ’Ashok ne Sagar ko khat bheja’。
词‘sent’在输出中被翻译为‘bheja’。然而,如果输入句子是:
输入(英语): ‘Ashok sent sweets to Sagar’
然后,相同的词被翻译为‘bheji’。
输出(印地语): ’Ashok ne Sagar ko mithai bheji’。
因此,在预测输出时,必须添加句子中其他词的上下文。我们只为一个词绘制示意图:
图片由作者提供
生成上下文的方法有很多种。最强大、最先进的方法称为‘注意力’。使用注意力进行上下文生成的神经网络称为‘变换器’。Bert 和 GPT 就是变换器的例子。
我们现在有了一种使用上下文的预测方法。我们可以将方程式写成:
NN Translation Function (Embeddings for word. no 1 in input sentence
+ context from other words in input sentence)
= Embedding for word no.1 in output sentence
步骤 6: 下一个词的预测
我们现在将处理幼稚翻译方法中的第二个问题。翻译不是单词的一对一映射。请参见前一步的例子:
输入(英语): ‘Ashok sent a letter to Sagar’
输出(印地语): ’Ashok ne Sagar ko khat bheja’。
你会注意到词的顺序不同,输入中没有‘a’的对应词,也没有输出中的‘ne’。我们每个词一个神经网络的方法在这种情况下不起作用。事实上,在大多数情况下它都不起作用。
幸运的是,已经有了更好的方法。在将输入句子提供给神经网络(NN)之后,我们要求它仅预测一个词,即将作为输出句子第一个词的词。我们可以将其表示为:
图片由作者提供
在我们发送信件的例子中,我们可以将其写为:
NN Translation Function (Embeddings for 'Ashok sent a letter to Sagar'
+ context from input sentence)
= Embedding for 'Ashok'
为了获取输出中的第二个词,我们将输入更改为:
Input = Embeddings for input sentence + Embedding for first word in output
我们还必须在上下文中包含这一新的输入:
Context = Context from input sentence + context from first word in output
神经网络将预测句子的下一个(第二个)词:
这可以写成:
NN Translation Function
(Embeddings for 'Ashok send a letter to Sagar + Embedding for 'Ashok',
Context for input sentence + context for 'Ashok')
= Embedding for 'ne'
我们可以继续这个过程,直到神经网络预测出‘.’的嵌入,换句话说,它会发出输出已经结束的信号。
我们因此将翻译问题简化为‘预测下一个词’的问题。在下一步中,我们将看到这种翻译方法如何引导我们走向更通用的生成 AI 能力。
步骤 7: 生成 AI 的预测
‘下一个词的预测’方法不限于翻译。神经网络可以被训练来预测下一个词,使输出成为对问题的回答,或是你指定主题的文章。想象一下,输入到这样的生成型神经网络中的句子是:
‘写一篇关于全球变暖对喜马拉雅冰川影响的文章’。
生成模型的输入被称为‘提示’。生成型神经网络预测文章的第一个词,然后继续预测后续的词,直到生成整篇漂亮的文章。这就是大型语言模型如 ChatGPT 所做的。正如你可以想象的那样,这些模型的内部机制远比我在这里描述的要复杂。但它们包含了我们看到的相同基本组件:嵌入、注意力和下一个词预测神经网络。
作者提供的图片
除了翻译和内容生成,LLMs 还可以回答问题、规划旅行和做许多其他奇妙的事情。所有这些任务中使用的基本方法仍然是预测下一个词。
摘要
我们从使用 LinReg 的基本预测技术开始。我们通过添加向量和词嵌入使预测问题变得更加复杂。我们通过解决简单的翻译问题学会了如何将预测应用于语言。在增强简单方法以处理真实翻译的过程中,我们熟悉了 LLMs 的基本元素:上下文和下一个词预测。我们意识到大型语言模型的核心是对文本序列的预测。我们熟悉了如提示、嵌入、注意力和变换器等重要术语。
序列不一定非得是文本。我们可以使用任何序列,例如图片或声音。本文的信息涵盖了生成型人工智能的全部内容,即:
生成型人工智能是利用神经网络对序列进行预测。
线员静止性
一种数据驱动的进攻线员指标
·发表于 Towards Data Science ·阅读时长 6 分钟·2023 年 2 月 6 日
–
由进攻线创造的口袋是传球游戏中的关键元素,因为四分卫需要一个干净的口袋来准确及时地传球。一个好的口袋为四分卫提供了足够的空间和时间来移动和传球,而一个差的口袋则会迅速塌陷,迫使四分卫逃跑或被擒杀(除非四分卫是帕特里克·马霍姆斯)。虽然容易想象出好的或差的口袋,但量化其质量,更普遍地说,量化线员的表现并不简单。
本文介绍了一种新的指标——“线员静止性”,该指标测量线员从球被传递到下一个事件发生之间的移动距离。线员静止性可以在每次进攻中计算,对评估进攻线员的表现和疲劳程度非常有用。通过跟踪每次进攻中线员的移动和负担,教练和分析师可以利用这一指标获得关于比赛中进攻线整体效果的洞察。所有指标和可视化图表均使用了2023 NFL 大数据碗的数据生成。
什么是线员静止性?
线员静止性是指在球被传递后,进攻线员从起始位置移动的距离。它的计算方式是球被传递的瞬间与下一个事件发生时的位置之差。在这个上下文中,事件指的是以下之一:
- 球被传球
2. 四分卫持球跑动
3. 四分卫被擒杀
4. 球被交接
5. 球被掉落
更正式地说,设 Xs 为球被传递时线员所在的码线位置,Xe 为下一个事件发生时线员所在的码线位置。线员静止性定义为 Xe−Xs。
不太复杂,是吧?尽管这是一个简单的指标,但我们会看到线卫稳定性在评估线卫表现方面非常有效。特别是,我们会看到更高的线卫稳定性与更好的团队和球员表现相关。
以下是线卫稳定性计算的动画。5 个蓝色点代表进攻线上的每个球员,棕色/红色点是足球。足球被传出的时长黄线的长度表示每个进攻线员的线卫稳定性。
线卫稳定性的可视化表现。图像由作者提供。
在这次传球中,右侧护锋的稳定性最低,约为-8 码,离开起始位置。中锋的稳定性最高,大约为-4.5 码。重要的是要注意,线卫稳定性只反映了纵向的场地移动,而没有考虑横向运动。这可以从中锋向左边护锋和护锋移动的过程中看出,但他的稳定性仍然很低。
为什么线卫稳定性是一个有用的指标?
线卫稳定性衡量的是线卫保持原地并为四分卫提供足够空间和时间执行战术的能力。我们首先注意到的是,线卫稳定性在允许擒杀的球员和没有允许擒杀的球员之间差异很大:
线卫稳定性分布——擒杀与未擒杀。图像由作者提供。
我们清楚地看到,线卫稳定性在允许擒杀时通常较低。2021 赛季首 8 周这两种分布的 Kolmogorov-Smirnov (KS) 统计量为 0.575,p 值为 1.05e-95(基本为 0)。这在统计上是显著的,并且对 KS 统计量的影响很大。
此外,平均线卫稳定性在不同的传球结果之间变化显著(使用 2021 年首 8 周的数据):
图像由作者提供。
这 5 组均值的单因素 ANOVA F 统计量为 275.19,p 值为 5.27e-234(基本为 0)。由此可见,平均线卫稳定性在传球结果较差时倾向于下降。即,完成传球的线卫稳定性最高(-3.78 码),未完成传球的线卫稳定性第二高(-4.13 码),而擒杀和拦截则具有最低的线卫稳定性。
关于线卫稳定性的另一个引人注目的观察是,它似乎可以洞察线卫疲劳情况。考虑以下图表,描绘了每场比赛中球队 5 名线卫在每次传球时的平均稳定性。垂直的红线表示发生了擒杀的进攻。
在进攻线员稳定性下降期间出现擒抱的示例。图片由作者提供。
在进攻线员稳定性下降期间出现擒抱的示例。图片由作者提供。
在进攻线员稳定性下降期间出现擒抱的示例。图片由作者提供。
在所有三个图表中,擒抱发生在进攻线员稳定性下降的趋势中。例如,在第一个折线图中,进攻方在比赛的第 25 次传球进攻中允许了一个擒抱。在擒抱之前,我们观察到平均进攻线员稳定性出现了明显的下降趋势(如果进攻线员稳定性的指数加权滚动平均差异为负,则我们说当前进攻线员稳定性处于下降趋势)。令人惊讶的是,在 2021 赛季的第 1 至第 8 周,约 85.29%的擒抱发生在进攻线员稳定性下降的趋势中。这可能表明进攻线员感到疲惫,从而在每次进攻中让出更多空间,最终导致擒抱。因此,分析整个进攻线的进攻线员稳定性以及单个进攻线员的稳定性,可以为我们提供直接的耐力、力量和疲劳指标。
当我们按周分析单个球员的进攻线员稳定性时,尤其是当球员受伤时,另一个趋势会出现。根据www.nfl.com/injuries/league/2021/REG9
,布法罗比尔队的护卫乔恩·费利西亚诺在第 9 周因小腿受伤缺席。他在第 4 周也因脑震荡缺席。以下是他参与的每周进攻线员稳定性:
进攻线员稳定性可能具有预测伤病的能力。图片由作者提供。
在费利西亚诺的脑震荡之后以及他小腿受伤之前,我们可以清楚地看到他的进攻线员稳定性分布出现了明显的负面变化。更具体地说,我们可以看到他在脑震荡后的所有周的平均进攻线员稳定性都低于脑震荡前的周。
进攻线员稳定性排名
现在我们已经看到了进攻线员稳定性为何是一个有用指标的一些原因,让我们看看每个位置的球员排名最高。以下图表显示了 2021 赛季前 8 周中,至少参与了 100 次传球进攻的进攻线员的前 10 名平均稳定性。首先是中锋:
中锋进攻线员稳定性排名。图片由作者提供。
然后是护卫:
护卫进攻线员稳定性排名。图片由作者提供。
还有截锋:
截锋进攻线员稳定性排名。图片由作者提供。
这些进攻线员中的许多人非常知名,并被广泛认为是顶尖中的顶尖。其他排名接近前列的进攻线员可能会让人感到意外。这可能表明,进攻线员的稳定性揭示了每个进攻线员在进攻线上的未被认识的优点和缺点。
最后想法
在这篇文章中,我们引入了一种新的指标——“进攻线员稳定性”,用于评估进攻线员的表现。这个指标基于这样一个理念:进攻线员维持静止位置的能力,即在没有移动的情况下保持原地,是决定传球成功的重要因素。我们发现,进攻线员的稳定性与其整体表现以及疲劳程度密切相关。尽管这不是一个完美的指标,但它确实提供了一种独特、易于解释且具有预测性的方式来评估进攻线员的表现。
成为会员: https://harrisonfhoffman.medium.com/membership
喜欢我的文章?请请我喝杯咖啡: https://www.buymeacoffee.com/HarrisonfhU
使用 Python 进行语言指纹分析
原文:
towardsdatascience.com/linguistic-fingerprinting-with-python-5b128ae7a9fc
使用标点热图进行作者归属分析
·发表于 Towards Data Science ·9 分钟阅读·2023 年 8 月 1 日
–
单一法医学指纹,黄色调与蓝色分号(图片来源:DALL-E2 和作者)
文体计量学是通过计算文本分析对文学风格进行定量研究的学科。它基于这样的观点:我们每个人在写作中都有一种独特、一致且易于识别的风格。这包括我们的词汇、标点符号的使用、单词和句子的平均长度等等。
文体计量学的一个典型应用是作者归属分析。这是一种识别文件作者的过程,比如在调查抄袭或解决历史文件来源争议时。
在这个快速成功数据科学项目中,我们将使用 Python、seaborn 和自然语言工具包(NLTK)来检验亚瑟·柯南·道尔是否在他的小说《失落的世界》中留下了语言指纹。更具体地说,我们将利用分号来确定亚瑟·柯南·道尔还是他的同时代人 H.G. 威尔斯更可能是该书的作者。
《猎犬》、《战争》与《失落的世界》
亚瑟·柯南·道尔(1859–1930)最著名的是福尔摩斯系列故事。H.G. 威尔斯(1866–1946)因几部开创性的科幻小说而闻名,如隐形人。
1912 年,《Strand Magazine》出版了失落的世界,这是一个科幻小说的连载版本。虽然它的作者是已知的,但让我们假设它存在争议,我们的任务是解开这个谜团。专家们将范围缩小到两位作者:道尔和威尔斯。威尔斯略微被倾向,因为失落的世界是一部科幻小说,并且包含了类似于他 1895 年书中莫洛克人的穴居人。
为了解决这个问题,我们需要每位作者的代表作品。对于道尔,我们将使用巴斯克维尔的猎犬,它出版于 1901 年。对于威尔斯,我们将使用世界大战,它出版于 1898 年。
对我们来说幸运的是,所有三部小说都在公共领域,并可以通过古腾堡计划获得。为了方便,我已将它们下载到这个Gist并删除了版权信息。
过程
作者归属分析需要应用自然语言处理(NLP)。NLP 是语言学和人工智能的一个分支,旨在赋予计算机从书面和口头语言中提取意义的能力。
最常见的 NLP 作者分析测试会分析文本的以下特征:
-
词语/句子长度: 文档中词语或句子长度的频率分布图。
-
停用词: 停用词的频率分布图(短的、无语境的功能词,如the、but 和 if)。
-
词性: 根据词汇的句法功能(如名词和动词)绘制的频率分布图。
-
最常见的词汇: 比较文本中最常用的词汇。
-
Jaccard 相似性: 用于衡量样本集的相似性和多样性的统计量。
-
标点符号: 比较逗号、冒号、分号等的使用情况。
对于这个玩具项目,我们将专注于标点测试。具体来说,我们将关注分号的使用。作为“可选”的语法元素,它们的使用具有潜在的独特性。它们也不受书籍类型的影响,不像问号在侦探小说中可能比在经典科幻小说中更为丰富。
高级过程将是:
-
将书籍加载为文本字符串,
-
使用 NLTK 对每个单词和标点进行标记(拆分),
-
提取标点符号,
-
将分号的值设为 1,所有其他标点的值设为 0,
-
创建数值的 2D NumPy 数组,
-
使用 seaborn 将结果绘制为热图,
我们将使用每个热图作为数字“指纹”。比较已知书籍的指纹与未知书籍(失落的世界)的指纹,希望能够暗示出作者之间的不同。
问号使用的热图(蓝色),在侦探小说(左)与科幻小说(右)中(作者提供的图像)
自然语言工具包
多个第三方库可以帮助你用 Python 进行 NLP。这些库包括 NLTK、spaCy、Gensim、Pattern 和 TextBlob。
我们将使用NLTK,它是最古老、最强大且最受欢迎的工具之一。它是开源的,适用于 Windows、macOS 和 Linux。它还配备了详细的文档。
安装库
使用 Anaconda 安装 NLTK:
conda install -c anaconda nltk
使用 pip 安装:
pip install nltk
(或参见 www.nltk.org/install.html
)
我们还需要 seaborn 来进行绘图。基于 matplotlib,这个开源可视化库提供了一个更易于使用的界面来绘制吸引人且信息丰富的统计图表,如条形图、散点图、热图等。以下是安装命令:
conda install seaborn
或
pip install seaborn
代码
以下代码在 JupyterLab 中编写,并且由单元格描述。
导入库
在导入模块中,我们需要string
模块中的标点符号列表。我们将把这个列表转化为set
数据类型,以便更快地进行搜索。
import math
from string import punctuation
import urllib.request
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import seaborn as sns
import nltk
PUNCT_SET = set(punctuation)
定义一个加载文本文件的函数
由于我们将处理多个文件,我们将首先定义一些可重用的函数。第一个函数使用 Python 的urllib
库打开存储在 URL 处的文件。第一行打开文件,第二行将其读取为字节数据,第三行将字节转换为字符串(str
)格式。
def text_to_string(url):
"""Read a text file from a URL and return a string."""
with urllib.request.urlopen(url) as response:
data = response.read() # in bytes
txt_str = data.decode('utf-8') # converts bytes to string
return txt_str
定义一个分词函数
下一个函数接受一个字典,其中键是作者的名字,值是他们以字符串格式呈现的小说。然后,它使用 NLTK 的word_tokenize()
方法将字符串重新组织为令牌。
Python 将字符串视为字符的集合,如字母、空格和标点符号。分词过程将这些字符分组为其他元素,如单词(或句子)和标点符号。每个元素称为令牌,这些令牌允许使用复杂的 NLP 分析。
该函数通过返回一个新的字典来结束,其中作者的名字作为键,标点符号令牌作为值。
def make_punct_dict(author_book_dict):
"""Accept author/text dict and return dict of punctuation by author."""
punct_by_author = {}
for author, text in author_book_dict.items():
tokens = nltk.word_tokenize(text)
punct_by_author[author] = [token for token in tokens
if token in PUNCT_SET]
print(f"Number punctuation marks in {author} = {len(punct_by_author[author])}")
return punct_by_author
我应该在这里指出,可以使用基本的 Python 来搜索文本文件中的分号并完成这个项目。然而,分词带来了两个好处。
首先,NLTK 的默认分词器(word_tokenize()
)不计算用在缩写或所有格中的撇号,而是将其视为两个单词,撇号附在第二个单词上(如I + ‘ve)。这符合语法用法。
分词器还将特殊标记(如双破折号(— —)或省略号(…))视为单一标记,如作者所意图,而不是作为单独的标记。使用基本 Python 时,每个重复的标记都会被计数。
这可能会产生显著影响。例如,使用 NLTK 计算失落的世界的总标点符号数量为 10,035,而使用基本 Python 则为 14,352!
其次,所有其他 NLP 归因测试都需要使用分词,因此在这里使用 NLTK 让你有能力在以后扩展分析。
定义一个将标点符号转换为数字的函数
Seaborn 的heatmap()
方法需要数值数据作为输入。以下函数接受按作者分类的标点符号令牌字典,将分号赋值为1
,其他所有赋值为0
,并返回list
数据类型。
def convert_punct_to_number(punct_by_author, author):
"""Return list of punctuation marks converted to numerical values."""
heat_vals = [1 if char == ';' else 0 for char in punct_by_author[author]]
return heat_vals
定义一个函数来查找下一个最低的平方值
热值列表是一维的,但我们希望将其绘制为二维。为此,我们将列表转换为正方形 NumPy 数组。
这必须是一个真正的正方形,并且每个列表中的样本数量不太可能有一个整数作为其平方根。因此,我们需要计算每个列表长度的平方根,将其转换为整数,并进行平方。得到的值将是可以作为真正的正方形显示的最大长度。
def find_next_lowest_square(number):
"""Return the largest perfect square less than or equal to the given number."""
return int(math.sqrt(number)) ** 2
加载和准备数据以进行绘图
定义好函数后,我们准备应用它们。首先,我们将每本书的文本数据加载到一个名为strings_by_author
的字典中。对于失落的世界,我们将作者的名字设置为“未知”,因为这代表一个来源不明的文档。
然后,我们将其转换为一个名为punct_by_author
的标点字典。从这个字典中,我们找到每个列表的下一个最低平方值,并选择最小值继续前进。这确保了所有数据集都可以转换为正方形数组,并且还规范化了每个数据集的样本数量(例如,失落的世界有 10,035 个标点符号标记,而巴斯克维尔的猎犬只有 6,704 个)。
war_url = 'https://bit.ly/3QnuTPX'
hound_url = 'https://bit.ly/44Gdc2a'
lost_url = 'https://bit.ly/3QhTfKJ'
# Load text files into dictionary by author:
strings_by_author = {'wells': text_to_string(war_url),
'doyle': text_to_string(hound_url),
'unknown': text_to_string(lost_url)}
# Tokenize text strings preserving only punctuation marks:
punct_by_author = make_punct_dict(strings_by_author)
# Find the largest square that fits all datasets:
squarable_punct_sizes = [find_next_lowest_square(len(punct_by_author[author]))
for author in punct_by_author]
perfect_square = min(squarable_punct_sizes)
print(f"Array size for perfect square: {perfect_square}\n")
未来使用一个包含 6,561 个标记的数组意味着每个标点符号列表将在绘图前被截断至长度 6,561。
绘制热图
我们将使用一个for
循环为每位作者的标点符号绘制热图。第一步是将标点符号标记转换为数字。记住,分号将用1
表示,其他所有标点用0
表示。
接下来,我们使用perfect_square
值和 NumPy 的array()
及reshape()
方法来截断heat
列表并将其转换为正方形 NumPy 数组。此时,我们只需设置一个 matplotlib 图形并调用 seaborn 的heatmap()
方法。循环将为每个作者绘制一个单独的图形。
分号将被标记为蓝色。你可以通过改变cmap
参数中的颜色顺序来反转这一点。如果你不喜欢黄色和蓝色,也可以输入新的颜色。
# Convert punctuation marks to numerical values and plot heatmaps:
for author in punct_by_author:
heat = convert_punct_to_number(punct_by_author, author)
arr = np.array(heat[:perfect_square]).reshape(int(math.sqrt(perfect_square)),
int(math.sqrt(perfect_square)))
fig, ax = plt.subplots(figsize=(5, 5))
sns.heatmap(arr,
cmap=ListedColormap(['yellow', 'blue']),
cbar=False,
xticklabels=False,
yticklabels=False)
ax.set_title(f'Heatmap Semicolons: {author.title()}')
plt.show();
结果
从之前的图表中应该可以清楚地看出——严格依据分号的使用——Doyle 是失落的世界最可能的作者。
这些结果的可视化展示非常重要。例如,每本书的总分号数如下:
Wells: 243
Doyle: 45
Unknown: 103
作为总标点符号的一部分,它们的比例如下:
Wells: 0.032
Doyle: 0.007
Unknown: 0.010
从这些文本数据来看,你是否立即能判断出“未知”作者很可能是道尔?试想一下如果把这些呈现给陪审团,陪审员会更被情节还是文本打动?没有什么能比可视化更有效地传达信息了!
当然,要对作者身份进行稳健的判断,你需要运行之前列出的所有NLP 归属测试,并使用所有的道尔和威尔斯的小说。
谢谢
感谢阅读,未来请关注我的更多快速成功数据科学项目。如果你想看到对该数据集进行更全面的 NLP 测试,请参阅我书中的第二章,真实世界的 Python:黑客解决代码问题的指南。
如何使用 Python 列出所有 BigQuery 数据集和表
原文:
towardsdatascience.com/list-bigquery-ds-tables-python-b92063ad0be3
使用 BigQuery API 和 Python 程序化列出所有数据集和表
·发布在 Towards Data Science ·5 分钟阅读·2023 年 5 月 15 日
–
图片由 Joanna Kosinska 提供,来源于 Unsplash
BigQuery 是 Google Cloud Platform 上的托管数据仓库服务,允许用户存储、管理和查询数据。数据和/或分析工程的一个重要部分是某些任务的自动化,包括与 BigQuery 的一些交互。这些自动化通常需要我们利用 BigQuery API,使开发人员能够以编程方式与 GCP 上的服务进行交互。
在今天的文章中,我们将演示如何使用 BigQuery API 和 Google Python 客户端以编程方式获取单个 GCP 项目下的所有表和数据集。
前提条件
为了跟随本教程中的步骤,确保安装 Google 提供的 Python 客户端库。为此,我们需要通过 pip
安装 google-cloud-bigquery
:
$ python3 -m pip install --upgrade google-cloud-bigquery
此外,你需要通过应用默认凭据(ADC)进行客户端身份验证。为此,你首先需要确保已经安装 [gcloud](https://cloud.google.com/sdk/gcloud)
命令行界面(CLI)。由于我们在本地机器上开发代码,我们只需通过 ADC 进行身份验证,如下所述:
$ gcloud auth application-default login
运行命令后,你的浏览器将打开一个新标签页,要求你使用 Google 帐号登录。请确保你的个人 Google Cloud 帐号具有足够的权限以执行接下来几个部分中的操作。
列出所有 BigQuery 数据集
首先,让我们开始创建一个 BigQuery 客户端实例:
from google.cloud import bigquery
# The client we'll use to interact with BigQuery
client = bigquery.Client(project='my-gcp-project')
我们现在可以使用客户端通过调用 list_datasets()
方法来获取所有 BigQuery 数据集,该方法返回一个指向类型为 [DaasetListItem](https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.dataset.DatasetListItem)
对象的迭代器。
# Get an iterator pointing to `DaasetListItem` objects
bq_datasets = list(client.list_datasets())
现在我们可以遍历 bq_datasets
对象并打印我们在实例化客户端时使用的项目的 BigQuery 数据集。
# Print BigQuery datasets in project `my-gcp-project`
for dataset in bq_datasets:
print(f'{client.project=}, {dataset.dataset_id=}')
这是代码的完整版本,结合了我们之前讨论的所有代码片段。
"""
Script used to iterate over BigQuery datasets in a single
BigQuery project.
"""
from google.cloud import bigquery
# The client we'll use to interact with BigQuery
client = bigquery.Client(project='my-gcp-project')
# Get an iterator pointing to `DaasetListItem` objects
bq_datasets = client.list_datasets()
# Print BigQuery datasets in project `my-gcp-project`
for dataset in bq_datasets:
print(f'{client.project=}, {dataset.dataset_id=}')
最后,这是我们脚本生成的示例输出:
client.project='my-gcp-project', dataset.dataset_id='my_dataset'
client.project='my-gcp-project', dataset.dataset_id='another_dataset'
client.project='my-gcp-project', dataset.dataset_id='oh_heres_another_one'
列出所有 BigQuery 表
同样,为了列出感兴趣的 BigQuery 项目中的所有表,我们将再次迭代数据集,然后列出每个数据集中的所有表。
为此,我们需要对每个数据集调用 client.list_tables()
,以获取指向类型为 [TableListItem](https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.table.TableListItem)
对象的迭代器。
for table in client.list_tables(dataset.dataset_id):
print(f'{table.table_id=}, {dataset.dataset_id=}, {client.project=}')
这是包含所有步骤的完整代码,用于将 BigQuery 项目中所有表的名称打印到标准输出。
"""
Script used to iterate over BigQuery table names for every single
dataset in a particular BigQuery project.
"""
from google.cloud import bigquery
# The client we'll use to interact with BigQuery
client = bigquery.Client(project='my-gcp-project')
# Get an iterator pointing to `DaasetListItem` objects
bq_datasets = client.list_datasets()
# Print BigQuery table and dataset names in project `my-gcp-project`
for dataset in bq_datasets:
for table in client.list_tables(dataset.dataset_id):
print(f'{table.table_id=}, {dataset.dataset_id=}, {client.project=}')
输出:
table.table_id='my_table', dataset.dataset_id='my_dataset', client.project='my-gcp-project'
table.table_id='another_table', dataset.dataset_id='my_dataset', client.project='my-gcp-project'
table.table_id='temp_table', dataset.dataset_id='temp_ds', client.project='my-gcp-project'
table.table_id='temp_table_2', dataset.dataset_id='temp_ds', client.project='my-gcp-project'
计算 BigQuery 中的数据集和表的数量
现在您知道如何使用 Python 客户端与 BigQuery 交互,您还可以推断出单个项目中的数据集数量。
"""
Script used to count number of datasets for a BigQuery project
"""
from google.cloud import bigquery
# The client we'll use to interact with BigQuery
client = bigquery.Client(project='my-gcp-project')
# Turn iterator into list, and count its length
dataset_count = len(list(client.list_datasets()))
print(f'Number of datasets in project {client.project}: {dataset_count}')
同样,我们甚至可以计算每个数据集或每个项目中的表数量:
"""
Script used to count number of tables per dataset for a BigQuery project
"""
from google.cloud import bigquery
# The client we'll use to interact with BigQuery
client = bigquery.Client(project='my-gcp-project')
# Turn iterator into list, and count its length
table_total_count = 0
for dataset in client.list_datasets():
table_count = len(list(client.list_tables(dataset.dataset_id)))
table_total_count += table_count
print(f'No. of tables for dataset {dataset.dataset_id}: {table_count}')
print(f'Number of tables in project {client.project}: {table_total_count}')
最终想法
在这个简短的教程中,我们概述了使用 Google 提供的 Python 客户端来利用 BigQuery API 的步骤,包括安装和身份验证步骤。
此外,我们演示了如何使用 BigQuery Python 客户端来编程列出 BigQuery GCP 项目中的数据集和表。最后,我们还创建了一些脚本,您可以使用这些脚本来计算数据集或表的数量。
希望您觉得这篇文章有用。如果您在运行教程中分享的代码时遇到任何问题,请在评论中告知我,我会尽力帮助您调试并成功运行代码。
👉 成为会员 并阅读 Medium 上的每个故事。您的会员费直接支持我和您阅读的其他作者。您还将完全访问 Medium 上的每个故事。
[## 使用我的推荐链接加入 Medium — Giorgos Myrianthous
作为 Medium 会员,您的会员费用的一部分会用于支持您阅读的作者,并且您可以完全访问每一个故事……
gmyrianthous.medium.com](https://gmyrianthous.medium.com/membership?source=post_page-----b92063ad0be3--------------------------------)
👇相关的文章您可能也会喜欢 👇
对 dbt 的温和介绍,它正在主导数据世界
towardsdatascience.com ## BigQuery 的 SQL 反模式
在 Google Cloud BigQuery 上运行 SQL 时的最佳实践和应避免的事项
towardsdatascience.com ## 什么是 Python 中的 pyproject.toml
在 pyproject.toml 文件中管理 Python 项目的依赖关系
towardsdatascience.com
列表、元组、字典和数据框在 Python 中的完全指南
掌握 Python 中最常用的数据结构所需的所有知识
·发布在 Towards Data Science ·阅读时间 16 分钟·2023 年 5 月 24 日
–
如果你已经开始学习 Python,无论你想成为一名软件工程师还是数据科学家,你都必须掌握数据结构。
Python 具有许多数据结构,可以用来存储数据。在本文中,我们将深入了解最常用的数据结构。所以,如果你刚开始你的职业生涯,并需要学习数据结构,那么这篇文章绝对适合你。
这里是你会找到的内容:
**Table of Contents:**
Lists
Definitions and creation examples
Lists manipulation
List comprehension
List of lists
Tuples
Dictionaries
Dictionaries manipulation
Nested dictionaries
Dictionary comprehension
Data frames
Basic data frames manipulations with Pandas
列表
定义和创建示例
在 Python 中,列表是一个有序元素的集合,这些元素可以是任何类型:字符串、整数、浮点数等……
要创建一个列表,项必须放在方括号中,并用逗号分隔。例如,这里是如何创建一个整数列表的:
# Create list of integers
my_integers = [1, 2, 3, 4, 5, 6]
但列表也可以包含“混合”类型的数据。例如,创建一个同时包含整数和字符串的列表:
# Create a mixed list
mixed_list = [1, 3, "dad", 101, "apple"]
要创建一个列表,我们还可以使用 Python 内置函数 list()
。这就是我们如何使用它的:
# Create list and print it
my_list = list((1, 2, 3, 4, 5))
print(my_list)
>>>
[1, 2, 3, 4, 5]
这个内置函数在某些特殊情况下非常有用。例如,假设我们想创建一个范围在 (1–10) 之间的数字列表。以下是我们可以这样做的方式:
# Create a list in a range
my_list = list(range(1, 10))
print(my_list)
>>>
[1, 2, 3, 4, 5, 6, 7, 8, 9]
**NOTE**:
Remember that the built-in function "range" includes the first value,
and excludes the last one.
现在,让我们看看如何操作列表。
列表操作
由于列表是可变的,我们有很多可能性来操作它们。例如,假设我们有一个名字列表,但我们犯了一个错误,需要更改其中一个。以下是我们可以这样做的方式:
# List of names
names = ["James", "Richard", "Simon", "Elizabeth", "Tricia"]
# Change the wrong name
names[0] = "Alexander"
# Print list
print(names)
>>>
['Alexander', 'Richard', 'Simon', 'Elizabeth', 'Tricia']
所以,在上述示例中,我们将列表中的第一个名字从 James 更改为 Alexander。
**NOTE:**
In case you didn't know, note that in Python the first element
is always accessed by "0", regarding of the type we're manipulating.
So, in the above example, "names[0]" represents the first element
of the list "names".
现在,假设我们忘记了一个名字。我们可以这样将其添加到我们的列表中:
# List of names
names = ["James", "Richard", "Simon", "Elizabeth", "Tricia"]
# Append another name
names.append("Alexander")
# Print list
print(names)
>>>
['James', 'Richard', 'Simon', 'Elizabeth', 'Tricia', 'Alexander']
如果我们需要连接两个列表,我们有两种选择:concatenate
方法或extend()
方法。让我们来看一下:
# Create list1
list1 = [1, 2, 3]
# Create list2
list2 = [4, 5, 6]
# Concatenate lists
concatenated_list = list1 + list2
# Print concatenated list
print(concatenated_list)
>>>
[1, 2, 3, 4, 5, 6]
所以,这种方法创建了一个包含其他列表之和的列表。我们来看一下extend()
方法:
# Create list1
list1 = [1, 2, 3]
# Create list2
list2 = [4, 5, 6]
# Extend list1 with list2
list1.extend(list2)
# Print new list1
print(list1)
>>>
[1, 2, 3, 4, 5, 6]
正如我们所看到的,结果是相同的,但语法不同。此方法用list2
扩展了list1
。
如果我们想要删除元素,我们有两种选择:可以使用remove()
方法或del
。让我们来看看:
# Create list
my_list = [1, 2, 3, 'four', 5.0]
# Remove one element and print
my_list.remove('four')
print(my_list)
>>>
[1, 2, 3, 5.0]
我们来看看另一种方法:
# Create list
my_list = [1, 2, 3, 'four', 5.0]
# Delete one element and print
del my_list[3]
print(my_list)
>>>
[1, 2, 3, 5.0]
因此,我们可以使用这两种方法获得相同的结果,但remove()
方法允许我们明确指定要删除的元素,而del
则需要访问列表中元素的位置。
**NOTE:**
If you've gained familiarity with accessing positions, in the above
example my_list[3] = 'four'. Because, remember: in Python we start counting
positions from 0.
列表推导式
有很多情况下我们需要从现有列表中创建新列表,一般是对现有数据应用一些过滤条件。为此,我们有两种选择:
-
我们使用循环和语句。
-
我们使用列表推导式。
实际上,它们都是写相同内容的方式,但列表推导式更简洁、更优雅。
但在讨论这些方法之前,你可能需要深入了解循环和语句。这里有几个我过去写的文章,可能会对你有帮助:
当它们看起来被理解时,还有更多的内容
towardsdatascience.com ## Python 循环:如何在 Python 中遍历的完整指南
利用 Python 中循环的力量
towardsdatascience.com
现在,让我们直接使用循环和语句来看几个例子。
假设我们有一个购物清单。我们希望我们的程序打印出我们喜欢一种水果,而不喜欢清单上的其他水果。以下是实现的方法:
# Create shopping list
shopping_list = ["banana", "apple", "orange", "lemon"]
# Print the one I like
for fruit in shopping_list:
if fruit == "lemon":
print(f"I love {fruit}")
else:
print(f"I don't like {fruit}")
>>>
I don't like banana
I don't like apple
I don't like orange
I love lemon
另一个例子可能是这样的。假设我们有一个数字列表,并且我们只想打印出偶数。以下是实现的方法:
# Create list
numbers = [1,2,3,4,5,6,7,8]
# Create empty list
even_list = []
# Print even numbers
for even in numbers:
if even %2 == 0:
even_list.append(even)
else:
pass
print(even_list)
>>>
[2, 4, 6, 8]
**NOTE:**
If you are not familiar with the sintax %2 == 0 it means that we are
dividing a number by 2 and expect a reminder of 0\. In other words,
we are asking our program to intercept the even numbers.
因此,在上述示例中,我们创建了一个数字列表。然后,我们创建了一个空列表,在循环后用于附加所有偶数。这样,我们就从包含“常规”数字的列表中创建了一个偶数列表。
现在……这种使用循环和语句创建新列表的方式有点“繁琐”。我的意思是:这需要很多代码。我们可以使用列表推导式以更简洁的方式获得相同的结果。
例如,要创建一个包含偶数的列表,我们可以使用如下的列表推导式:
# Create list
numbers = [1,2,3,4,5,6,7,8]
# Create list of even numbers
even_numbers = [even for even in numbers if even %2 == 0]
# Print even list
print(even_numbers)
>>>
[2, 4, 6, 8]
所以,列表推导式直接创建一个新列表,并在其中定义条件。如我们所见,我们获得了与之前相同的结果,但只用了一行代码:不错吧!
现在,让我们使用列表推导式创建一个包含我喜欢的水果(和我不喜欢的水果)的列表:
# Create shipping list
shopping_list = ["banana", "apple", "orange", "lemon"]
# Create commented list and print it
commented_list = [f"I love {fruit}" if fruit == "banana"
else f"I don't like {fruit}"
for fruit in shopping_list]
print(commented_list)
>>>
['I love banana', "I don't like apple", "I don't like orange",
"I don't like lemon"]
所以,我们获得了与之前相同的结果,但只用了一行代码。唯一的区别是这里我们打印了一个列表(因为列表推导式创建了一个!),而之前我们只是打印了结果。
列表的列表
还有可能创建列表的列表,即嵌套在一个列表中的列表。当我们想将列出的数据表示为一个唯一的列表时,这种可能性很有用。
例如,假设我们想创建一个学生及其成绩的列表。我们可以创建如下的内容:
# Create lis with students and their grades
students = [
["John", [85, 92, 78, 90]],
["Emily", [77, 80, 85, 88]],
["Michael", [90, 92, 88, 94]],
["Sophia", [85, 90, 92, 87]]
]
如果我们想计算每个学生的平均成绩,这是一种有用的表示方式。我们可以这样做:
# Iterate over the list
for student in students:
name = student[0] # Access names
grades = student[1] # Access grades
average_grade = sum(grades) / len(grades) # Calculate mean grades
print(f"{name}'s average grade is {average_grade:.2f}")
>>>
John's average grade is 86.25
Emily's average grade is 82.50
Michael's average grade is 91.00
Sophia's average grade is 88.50
元组
元组是 Python 中的另一种数据结构类型。它们用圆括号定义,并且像列表一样,可以包含任何数据类型,数据类型之间用逗号分隔。例如,我们可以这样定义一个元组:
# Define a tuple and print it
my_tuple = (1, 3.0, "John")
print(my_tuple)
>>>
(1, 3.0, 'John')
元组和列表的区别在于元组是不可变的。这意味着元组的元素不能被更改。例如,如果我们尝试向元组中添加一个值,我们会得到一个错误:
# Create a tuple with names
names = ("James", "Jhon", "Elizabeth")
# Try to append a name
names.append("Liza")
>>>
AttributeError: 'tuple' object has no attribute 'append'
所以,由于我们不能修改元组,它们在我们希望数据不可变时很有用;例如,在我们不想犯错误的情况下。
一个实际的例子可能是电子商务的购物车。我们可能希望这种数据不可变,以便在操作时不犯错误。假设有人从我们的电子商务网站购买了一件衬衫、一双鞋子和一只手表。我们可以将这些数据以数量和价格的形式记录在一个元组中:
# Create a chart as a tuple
cart = (
("Shirt", 2, 19.99),
("Shoes", 1, 59.99),
("Watch", 1, 99.99)
)
当然,准确地说,这是一个元组的元组。
由于元组是不可变的,它们在性能方面更高效,这意味着它们节省了计算机的资源。但在操作时,我们可以使用与列表相同的代码,所以我们不会再写一次。
最后,类似于列表,我们可以用内置函数tuple()
创建一个元组,如下所示:
# Create a tuple in a range
my_tuple = tuple(range(1, 10))
print(my_tuple)
>>>
(1, 2, 3, 4, 5, 6, 7, 8, 9)
字典
字典是一种以键和值对存储数据的方式。我们可以这样创建一个字典:
# Create a dictionary
my_dictionary = {'key_1':'value_1', 'key_2':'value_2'}
所以,我们用大括号创建一个字典,并在其中存储一对对的键和值,用冒号分隔。键值对用逗号分隔。
现在,让我们看看如何操作字典。
字典操作
字典的键和值可以是任何类型:字符串、整数或浮点数。例如,我们可以这样创建一个字典:
# Create a dictionary of numbers and print it
numbers = {1:'one', 2:'two', 3:'three'}
print(numbers)
>>>
{1: 'one', 2: 'two', 3: 'three'}
但我们也可以这样创建:
# Create a dictionary of numbers and print it
numbers = {'one':1, 'two':2.0, 3:'three'}
print(numbers)
>>>
{'one': 1, 'two': 2.0, 3: 'three'}
选择值和键的类型取决于我们需要解决的问题。无论如何,考虑到我们之前看到的字典,我们可以这样访问值和键:
# Access values and keys
keys = list(numbers.keys())
values = tuple(numbers.values())
# Print values and keys
print(f"The keys are: {keys}")
print(f"The values are: {values}")
>>>
The keys are: ['one', 'two', 3]
The values are: (1, 2.0, 'three')
因此,如果我们的字典叫做 numbers
,我们用 numbers.keys()
访问其键。使用 numbers.values()
我们可以访问其值。此外,请注意,我们使用之前看到的表示法创建了一个包含键的列表和一个包含值的元组。
当然,我们也可以遍历字典。例如,假设我们想打印出大于某个阈值的值:
# Create a shopping list with fruits and prices
shopping_list = {'banana':2, 'apple':1, 'orange':1.5}
# Iterate over the values
for values in shopping_list.values():
# Values greater than threshold
if values > 1:
print(values)
>>>
2
1.5
像列表一样,字典是可变的。因此,如果我们想向字典中添加一个值,我们必须定义要添加的键和值。我们可以这样做:
# Create the dictionary
person = {'name': 'John', 'age': 30}
# Add value and key and print
person['city'] = 'New York'
print(person)
>>>
{'name': 'John', 'age': 30, 'city': 'New York'}
要修改字典的值,我们需要访问其键:
# Create a dictionary
person = {'name': 'John', 'age': 30}
# Change age value and print
person['age'] = 35
print(person)
>>>
{'name': 'John', 'age': 35}
要从字典中删除一对键值,我们需要访问其键:
# Create dictionary
person = {'name': 'John', 'age': 30}
# Delete age and print
del person['age']
print(person)
>>>
{'name': 'John'}
嵌套字典
我们之前已经看到,我们可以创建列表的列表和元组的元组。类似地,我们可以创建嵌套字典。假设,例如,我们想创建一个字典来存储与一个班级学生相关的数据。我们可以这样做:
# Create a classroom dictionary
classroom = {
'student_1': {
'name': 'Alice',
'age': 15,
'grades': [90, 85, 92]
},
'student_2': {
'name': 'Bob',
'age': 16,
'grades': [80, 75, 88]
},
'student_3': {
'name': 'Charlie',
'age': 14,
'grades': [95, 92, 98]
}
因此,每个学生的数据表示为一个字典,所有字典都存储在一个唯一的字典中,表示整个课堂。正如我们所见,字典的值甚至可以是列表(或者元组,如果我们愿意的话)。在这个例子中,我们使用列表来存储每个学生的成绩。
要打印一个学生的值,我们只需记住,从课堂字典的角度来看,我们需要访问键,在这种情况下,键就是学生本身。这意味着我们可以这样做:
# Access student_3 and print
student_3 = classroom['student_3']
print(student_3)
>>>
{'name': 'Charlie', 'age': 14, 'grades': [95, 92, 98]}
字典推导
字典推导允许我们简洁高效地创建字典。它类似于列表推导,但不创建列表,而是创建一个字典。
假设我们有一个字典,其中存储了一些物品及其价格。我们想知道价格低于某个阈值的物品。我们可以这样做:
# Define initial dictionary
products = {'shoes': 100, 'watch': 50, 'smartphone': 250, 'tablet': 120}
# Define threshold
max_price = 150
# Filter for threshold
products_to_buy = {fruit: price for fruit, price in products.items() if price <= max_price}
# Print filtered dictionary
print(products_to_buy)
>>>
{'shoes': 100, 'watch': 50, 'tablet': 120}
所以,使用字典推导的语法是:
new_dict = {key:value for key, value in iterable}
其中 iterable 是任何可迭代的 Python 对象。它可以是一个列表、一个元组、另一个字典等…
使用“标准”方法创建字典会需要大量的代码,包括条件、循环和语句。相反,如我们所见,字典推导允许我们仅用一行代码,基于条件创建字典。
字典推导在我们需要从其他来源或数据结构中检索数据来创建字典时特别有用。例如,假设我们需要从两个列表中检索值来创建一个字典。我们可以这样做:
# Define names and ages in lists
names = ['John', 'Jane', 'Bob', 'Alice']
cities = ['New York', 'Boston', 'London', 'Rome']
# Create dictionary from lists and print results
name_age_dict = {name: city for name, city in zip(names, cities)}
print(name_age_dict)
>>>
{'John': 'New York', 'Jane': 'Boston', 'Bob': 'London', 'Alice': 'Rome'}
数据框
数据框是表格数据的表示。图片来源于熊猫网站:pandas.pydata.org/docs/getting_started/index.html
数据框是由列和行组成的二维数据结构。因此,它在某种程度上类似于电子表格或 SQL 数据库中的表格。它们具有以下特点:
-
每一行代表一个独立的观察或记录。
-
每一列代表一个变量或数据的特定属性。
-
它们有标记的行(称为索引)和列,使得操作数据变得容易。
-
列可以包含不同类型的数据,如整数、字符串或浮点数。即使是单列也可以包含不同的数据类型。
虽然数据框是数据分析和数据科学中使用的典型数据结构,但 Python 软件工程师也可能需要操作数据框,这就是我们进行数据框概述的原因。
这是数据框的显示方式:
数据框。图片由 Federico Trotta 提供。
所以,在左侧(蓝色矩形中)我们可以看到索引,意味着行数。我们可以看到数据框可以包含不同类型的数据。特别是,列“Age”包含不同的数据类型(一个字符串和两个整数)。
使用 Pandas 进行基本的数据框操作
尽管最近开始流行一种叫做“Polars”的新库来操作数据框,但在这里我们将看到一些 Pandas 的数据操作,Pandas 仍然是目前使用最广泛的库。
首先,通常我们可以通过从 .xlsx
或 .cvs
文件导入数据来创建数据框。在 Pandas 中我们可以这样做:
import pandas as pd
# Import cvs file
my_dataframe = pd.read_csv('a_file.csv')
# Import xlsx
my_dataframe_2 = pd.read_excel('a_file_2.xlsx')
如果我们想创建一个数据框:
import pandas as pd
# Create a dictionary with different types of data
data = {
'Name': ['John', 'Alice', 'Bob'],
'Age': ['twenty-five', 30, 27],
'City': ['New York', 'London', 'Sydney'],
'Salary': [50000, 60000.50, 45000.75],
'Is_Employed': [True, True, False]
}
# Create the dataframe
df = pd.DataFrame(data)
这是我们上面展示的数据框。所以,正如我们所见,我们首先创建一个字典,然后用 pd.DataFrame()
方法将其转换为数据框。
我们有三种方式来可视化数据框。假设我们有一个名为 df
的数据框:
-
第一个是
print(df)
。 -
第二个是
df.head()
,它将显示我们数据框的前 5 行。如果我们有一个包含很多行的数据框,我们可以显示超过前五行。例如,df.head(20)
显示前 20 行。 -
第三个是
df.tail()
,它的功能与head()
完全相同,但它显示的是最后几行。
在可视化方面,使用上述 df
,这是 df.head()
显示的内容:
df.head()
显示的内容。图片由 Federico Trotta 提供。
这是 print(df)
显示的内容:
print(df)
显示的内容。图片由 Federico Trotta 提供。
在像这样的小数据集的情况下,差异只是个人喜好(我更喜欢head()
因为它“显示数据的表格性”)。但在大数据集的情况下,head()
要好得多。试试看,告诉我!
考虑到 Pandas 是一个非常广泛的库,这意味着它允许我们以多种方式操作表格数据,因此需要单独处理。这里我们只是展示最基本的内容,所以我们将学习如何添加和删除列(数据框的列也称为“Pandas 系列”)。
假设我们想向上面提到的数据框df
添加一列,表示人们是否已婚。我们可以这样做:
# Add marital status
df["married"] = ["yes", "yes", "no"]
**NOTE:**
this is the same notation we used to add values to a dictionary.
Return back on the article and compare the two methods.
显示头部,我们得到:
包含婚姻状况的数据框 df。图片由 Federico Trotta 提供。
删除一列:
# Delete the "Is_Employed" column
df = df.drop('Is_Employed', axis=1)
我们得到:
删除与就业数据相关的列后的数据框。图片由 Federico Trotta 提供。
请注意,我们需要使用axis=1
,因为这里我们告诉 Pandas 删除列,并且由于数据框是二维数据结构,axis=1
表示垂直方向。
相反,如果我们想要删除一行,我们需要使用axis=0
。例如,假设我们想删除索引为 1 的行(即第二行,因为我们从 0 开始计数):
# Delete the second row
df = df.drop(1, axis=0)
我们得到:
删除第二行后的数据框。图片由 Federico Trotta 提供。
结论
到目前为止,我们已经看到了 Python 中最常用的数据结构。这些不是唯一的数据结构,但肯定是最常用的。
此外,使用其中一种数据结构并没有对错之分:我们只需要理解我们需要存储什么数据,并选择最适合这种任务的数据结构。
希望这篇文章帮助您理解这些数据结构的使用以及何时使用它们。
免费 PYTHON 电子书:
开始学习 Python 数据科学但感到困难?订阅我的新闻通讯并获取我的免费电子书:这将为您提供正确的学习路径,帮助您通过实践学习 Python 数据科学。
喜欢这个故事吗?成为 Medium 会员,享受 5$/月 通过我的推荐链接:我将获得一小笔佣金,但不会增加您的额外费用:
[## 使用我的推荐链接加入 Medium - Federico Trotta
阅读 Federico Trotta 的每一个故事(以及 Medium 上的其他成千上万的作家)。您的会员费用直接支持…
medium.com](https://medium.com/@federicotrotta/membership?source=post_page-----7ab54d4819ee--------------------------------)
LLaMA:面向所有人的大型语言模型!
原文:
towardsdatascience.com/llama-llms-for-everyone-724e737835be
高性能的开源语言模型……
·发布于 Towards Data Science ·15 分钟阅读·2023 年 7 月 11 日
–
多年来,深度学习社区一直倡导开放性和透明度,导致了像HuggingFace这样的大规模开源项目。深度学习中的许多深刻思想(例如,transformers [2],自监督学习等)在网上公开可用,无论是通过公共代码库还是Arxiv。尽管开源已经成为一种规范,但大型语言模型(LLMs)的流行(以及商业适用性)最近挑战了这一趋势。
目前许多最强大的大型语言模型(LLMs)只能通过 API 访问(例如,来自OpenAI或Anthropic),这使得源代码和模型参数对研究人员和开发者而言不可及。虽然我的目标不是引发对当前 LLM 趋势的伦理讨论,但这些信息与本文的主题:公开可用的 LLM 相关。有趣的是,并非所有强大的语言基础模型都被隐藏在付费墙后面。一些模型,如 LLaMA,既公开可用又表现出色,因此在深度学习研究社区中保持了开放性。
什么是 LLaMA? LLaMA 不是单一模型,而是一套范围从 70 亿到 650 亿参数的 LLM。受 Chinchilla [3] 启发,这些 LLM 比 它们的对应物 稍小,但经过广泛的预训练(即,较小的模型,更多的 token),并旨在提供一组具有不同性能与推理效率权衡的多样化模型。LLaMA 模型表现惊人;例如,130 亿参数模型大致可与 GPT-3 [4] 相媲美,而 650 亿参数模型常常超越 PaLM [5] 的表现。
“GPT-4 从各种许可、创建和公开可用的数据源中学习,这些数据源可能包括公开的个人信息。” — 引自 [6]
除了令人印象深刻的性能外,LLaMA 仅使用公开数据进行预训练。LLaMA 模型可以完全通过在线资源复现,标志着在 LLM 领域向开源迈出的一步。最近的模型如 GPT-4 已知是通过公共和专有/私人数据的组合进行训练的。尽管这可能有利于模型性能,LLaMA 表明我们可以充分利用在线可用的数据,从而为与 LLM 相关的开源研究计划提供了希望。
(引自 [1])
背景信息
LLaMA LLM 采用了先前工作中提出的一些想法和技术。在这一部分,我们将回顾一些有用的背景信息,这些信息对于深入理解 LLaMA 及其组件将有所帮助。
关于 LLM 的简要说明。 首先,了解 LLM 的基础知识,包括其架构、训练过程和一般方法是有帮助的。我们在之前的概述中已经广泛探讨了这一主题。因此,我们在这里不会详细介绍这一主题,但下面提供了进一步阅读和学习的链接。
均方根层归一化 (RMSNorm)
通常,transformer 架构(包括 LLM 使用的仅解码器 transformer 架构)使用LayerNorm来规范化每层内的激活值。然而,使用不同的规范化技术已被证明可以稳定训练并提高泛化性能。例如,RMSNorm [16]的定义如下面的方程所示。
(由作者创建)
RMSNorm 与 LayerNorm 有些相似,但在规范化神经网络激活值时,它去除了均值中心化操作(并使用了略微修改的分母)。与 LayerNorm 相比,RMSNorm 在计算效率和简单性上更具优势,使其能够以 10–50%的效率提升达到相当的性能水平。
(来自[16])
SwiGLU 激活函数
LLM 的仅解码器架构的每个块包含一个两层前馈神经网络(即不使用偏置并单独应用于每个标记向量),在两层之间具有非线性。最初,这个非线性是修正线性单元(ReLU)激活函数。然而,最近的工作[15]揭示了这并不是最佳选择。
(由作者创建)
特别是,LLaMA(以及其他像PaLM的 LLM)选择使用 SwiGLU 激活函数,这在上面的方程中进行了公式化。这里,我们将 Swish 激活定义如下。
(由作者创建)
SwiGLU 是输入x
的两个线性变换的逐元素乘积,其中一个已应用 Swish 激活函数。该激活函数需要三个矩阵乘法,但发现相较于其他激活函数,它在性能上有所提升,即使在保持计算量不变的情况下。
再材料化(或重新计算)
重计算,也称为再计算,是一种在训练 LLM(以及其他大型神经网络)时使用的技术,旨在减少内存消耗,但需要额外的计算。通常,当我们计算神经网络的前向传递时,我们会在每一层存储/保留网络的激活值,以便在反向传递时使用(这对于计算权重更新是必要的!)。但这需要大量的内存!
神经网络前向和反向传递的示意图(作者创作)
重计算的基本思想是在反向传递期间重新计算某些中间激活值,而不是在前向传递期间将它们存储在内存中。这有助于减少训练过程中的峰值内存使用,从而允许在可用内存限制内训练更大的模型或使用更大的批次大小。考虑到 LLM 体积庞大且消耗大量内存,这一点尤为重要。
LLaMA 套件
现在我们已经掌握了一些有用的概念,让我们了解一下 LLaMA 中的 LLM 集合及其工作原理。由于这些模型受到Chinchilla(TL;DR:仅通过对更大量数据进行小型 LLM 的预训练)[3]提出的预训练策略的强烈启发,我们将在深入了解 LLaMA 之前简要概述这些思想。总体而言,LLaMA 质疑了大规模 LLM 的趋势,声称(如果进行足够的预训练!)更小的 LLM 可以在显著较低的推理预算下实现令人印象深刻的性能。
我们如何最大化 LLM 的效率?
最近 LLM 的发展历程中,一个特别值得注意的时刻是Chinchilla [3]的提出。在 GPT-3 之后,深度学习研究界对大规模语言模型中显著的少样本学习能力感到震惊。因此,我们开始测试比 GPT-3 还要大的模型。但结果并不理想!
“Hoffmann 等人(2022 年)的最新研究表明,对于给定的计算预算,最佳性能并非由最大的模型实现,而是由在更多数据上训练的小型模型实现的。” — 引自 [1]
为了创建比 GPT-3 更优秀的 LLM,我们不能仅仅使用更大的模型。相反,我们需要更多的预训练数据!具体来说,Chinchilla 的分析表明,如果我们对稍小的 LLM 进行更广泛的预训练,可以实现更高的性能。
这就是全部情况吗? 尽管我们知道较小的 LLM 如果进行广泛预训练可以表现良好,即使 [3] 中的分析也表明,训练相对较大的 LLM 是达到高性能水平的最有效方式。这个说法完全正确,但它仅考虑了训练效率。因此,我们不得不问自己一个问题:训练效率是我们唯一关心的吗? 对于大多数从业者来说,这个问题的答案无疑是否定的!
“这项工作的重点是训练一系列语言模型,通过训练更多令牌来实现不同推理预算下最佳的性能。” — 来自 [1]
训练成本只是与 LLM 相关的全部成本中的一小部分。我们还必须考虑托管,这使得推理预算成为一个巨大的考虑因素。LLaMA 通过强调,给定目标性能水平,预训练一个较小的 LLM 更长时间最终在推理过程中会更便宜,并且随着时间的推移节省大量成本。虽然我们可能会使用更大的模型以获得性能提升,但我们应该通过广泛的预训练尽可能减少模型大小(从而降低托管成本)。
LLaMA 的组成部分
(来自 [1])
数据集。 我们知道 LLaMA 的预训练数据集基于公共数据,但这些数据究竟来自哪里? 上述内容概述了用于 LLaMA 的预训练数据集。正如所见,尽管预训练数据(完全公开)具有相当大的多样性,数据来源从 StackExchange 到 Gutenberg Project 不等。完整的数据集在标记化后大约包含 1.4T 令牌。这与 Chinchilla [3] 预训练所使用的令牌数量相同;见下文。
(来自 [3])
鉴于 LLaMA 强调透明性和可重复性,[1] 提供了大量关于构建预训练数据集的见解。讨论中最有趣的一个方面是,我们可以利用这些见解进一步了解数据在预训练 LLM 之前是如何被过滤的。例如,来自 CommonCrawl 的文本数据被过滤以排除:
-
重复文档(使用 CCNet pipeline [7])
-
非英语数据(通过训练 fastText 线性分类器)
-
低质量内容(使用 n-gram 语言模型)
此外,文献[1]中的作者训练了一个线性分类器,用于区分维基百科中作为参考的页面和随机采样的页面,然后丢弃那些未被分类为参考的页面。这些步骤都仅仅是为了过滤 CommonCrawl!从之前的工作中,我们知道,正确过滤预训练数据集对 LLM 性能至关重要。在[1]中,我们深入了解了实现有效过滤管道的具体细节。
在变压器块内的预归一化结构(由作者创建)
架构。 LLaMA 套件采用了来自流行 LLM(如GPT-3 [4]和PaLM [5])的许多常见架构技巧。例如,LLaMA 在其每一层内执行预归一化,这意味着归一化应用于每一层的输入,而不是输出;见上文。此外,RMSNorm、SwiGLU 激活函数和旋转位置嵌入(RoPE) [10](即,绝对位置嵌入和相对位置嵌入的某种混合形式)在每个变压器层中使用。
(来自[1])
在[1]中,使用了四种不同规模的模型,参数量从 67 亿到 652 亿;见上文。这些模型形成了被称为 LLaMA 的 LLM 集合,并提供了在性能与模型大小或推理预算之间的各种权衡。最显著的是,我们将看到 LLaMA-13B 在性能上与 GPT-3 竞争力强,并且可以在单个 V100 GPU 上运行。与之前的模型相比,这是一个巨大的成就,使得这些模型对大多数从业者更加可及(例如,PaLM 使用超过 6000 个加速器进行训练)。
更高效的性能。 [1] 的作者采用了一些有趣的技巧来提高 LLM 训练效率。首先,我们应该记住,基于 decoder-only transformer models 的现代 LLM 在每一层中使用因果 multi-headed attention。为了提高这种因果多头注意力操作的效率,LLaMA 使用了一种高效的实现方式,该方式既不 i) 存储注意力权重,也不 ii) 计算被掩盖的令牌的键/查询分数。通过这样做,我们可以节省大量通常浪费在因果自注意力中未考虑的被掩盖令牌上的计算。这种方法受到 [9] 中思想的启发,但我们可以在 xformers library 中找到一个开源实现。
除了高效的因果自注意力实现之外,LLaMA 在重新物化方面的处理方式与大多数 LLM 训练策略有所不同。计算成本最高的激活(例如线性层的输出)在前向传播过程中被保存,从而减少了在反向传播过程中重新计算的激活数量。这一变化需要手动重新实现 LLM 的反向传播(而不是使用 autograd 进行自动微分),是一种混合的重新物化方法,显著提高了整体训练吞吐量。
“在训练一个 65B 参数的模型时,我们的代码在 2048 A100 GPU 上处理约 380 令牌/秒/ GPU。这意味着对我们包含 1.4T 令牌的数据集的训练大约需要 21 天。” — 摘自 [1]
鉴于 LLaMA 采用的改进以提高训练效率,我们可能会想知道:这实际上使训练快了多少? 好吧,这在很大程度上取决于训练基础设施。然而,当使用 2048 A100 GPUs 时,LLaMA-65B 完成对 1.4T 令牌的预训练大约需要 21 天。
LLaMA 与 SOTA LLMs
虽然开源和可重复性很重要,但如果模型表现不佳,没有人会关心 LLaMA!此前也有尝试开源 LLM(例如 OPT 和 BLOOM [11, 12])。但这些模型在性能上无法与现代 LLM 竞争。在这一部分,我们将分析 LLaMA 模型相对于流行 LLM,如 GPT-3 和 PaLM [4, 5] 的表现。
我们如何进行评估? 如 之前的帖子 中详细描述的那样,LLaMA 的评估与大多数语言基础模型类似:通过零样本和少样本学习。值得注意的是,LLaMA 模型仅作为预训练的基础模型进行评估,这意味着没有进行微调(无论是通过 SFT 还是 RLHF)。LLaMA 与流行的闭源 LLMs(例如,GPT-3,Gopher,Chinchilla 和 PaLM [3, 4, 5, 13])以及以前的开源 LLMs(例如,OPT,GPT-J,和 GPT-Neo [11, 14])在自由生成和多项选择任务上进行比较。测试了各种领域(例如,常识和数学推理、代码生成、问答等)。
(来自 [1])
语言理解。 在闭卷问答和阅读理解任务中,我们看到 LLaMA-65B 达到了最先进的零样本和少样本表现,一贯超过了像 PaLM 和 Chinchilla 这样更大模型的表现。进一步来看,LLaMA-13B 表现令人惊讶,甚至在大多数情况下超越了 GPT-3(它大 10 倍!)的表现。这里的基本结论是 i) 更大的 LLaMA 模型与最先进的技术具有竞争力,ii) 更小的 LLaMA 模型在其尺寸范围内表现出令人惊讶的效果。
(来自 [1])
推理任务。 LLaMA 套件还在常识和数学推理任务上进行了评估。在常识推理任务中,LLaMA 超越了多个强大基准的零样本推理表现;请参见上文。然而,这里需要注意的是,没有采用特殊的提示方法(例如,思维链提示)来促进推理的改进。以往的工作 [5] 已经表明,如果没有正确的提示方法,LLMs 的“推理”能力可能会随着规模的增加而退化。
(来自 [1])
尽管这种分析存在局限性,LLaMA 的推理能力相比于基线模型仍然显得相对令人印象深刻。即,LLaMA 模型在数学推理数据集上的表现与(甚至在某些情况下优于)多个基线模型竞争。实际上,LLaMA-65B 甚至超越了一个类似规模的 Minerva 模型,该模型已明确在数学数据上进行微调,以提高在这类任务上的表现。
“Minerva 是一系列基于 38.5B 令牌的 PaLM 模型,这些令牌来自 ArXiv 和数学网页… 在 GSM8k 上,我们观察到 LLaMA65B 超越了 Minerva-62B,尽管它没有在数学数据上进行微调。” — 来源于 [1]
代码生成。 除了基本的推理能力,代码生成是 LLaMA 模型的另一个技能。尽管从未在代码上进行过微调(即代码数据占 LLaMA 预训练数据的 <5%),但 LLaMA-65B 在代码生成任务上超越了 PaLM,而 LLaMA-13B 的代码生成表现也超过了 GPT-3(不过… GPT-3 确实在生成代码方面表现较差)。
(来源于 [1])
其他细节。 在 MMLU 基准测试中,LLaMA 模型在大多数情况下表现落后于像 Chinchilla 和 PaLM 这样的 LLM。这是 LLaMA 性能被当前替代方案明显超越的唯一基准测试之一。在 [1] 中的作者声称,这种性能下降是由于 LLaMA 预训练数据集中图书和学术论文的数量有限(即,与最先进的 LLM 相比,这类预训练数据减少了 >10 倍)。
(来源于 [1])
当跟踪 LLaMA 模型在预训练过程中的性能时,我们观察到性能有明显而稳定的提升;见上文。虽然并非所有任务的表现都相同,但我们可以看到 LLaMA 采用的预训练策略总体上是相对稳定的。
关键点
长话短说,LLaMA 是一个开源的 LLM,表现令人震惊。自从 LLaMA 提出的提案以来,研究社区已经很好地利用了这一开放的出色模型。举个例子,以下研究工作已经在 LLaMA 的基础上进行了扩展:
-
Vicuna: LLaMA 的微调版本,性能(几乎)可比于 GPT-4 [link]
-
Koala: 在互联网对话数据上微调的 LLaMA [link]
-
ChatLLaMA: 使用最少的计算资源在你自己的数据上创建个性化的 ChatGPT [link]
-
ColossalChat:一个与 ChatGPT 类似的模型,基于 LLaMA 的 RLHF 管道[链接]
LLaMA 的影响力可能会显著增加。个人而言,我非常兴奋看到开源 LLMS 的研究不断进展。我希望让这些模型变得更加可访问会带来更广泛的研究社区的深入调查和发展。以下是一些基本要点。
开源 LLM。 目前,LLM 生态系统正经历一种有趣的冲突,两种不同的方法被用来将这些强大的基础模型展示给公众。一方面,像ChatGPT和GPT-4这样的模型仅通过付费 API发布,这限制了研究社区对这些模型的详细访问。LLaMA 等贡献则与这一趋势相反,通过向研究社区提供完整的模型访问权限。
什么大小最合适? LLaMA 提供了一系列不同大小的 LLM,而不是发布单一模型。之前的 LLM 研究往往倾向于使用更大的模型,因为较大的 LLM 通常在训练时能以较少的总体计算成本达到令人印象深刻的性能水平。然而,如果我们对较小的模型进行更广泛的预训练,LLaMA 表明我们可以在显著降低推理成本的同时达到可比的性能水平。因此,至少考虑使用较小的 LLM 是合理的,特别是在我们需要部署它们的时候。值得注意的是,一些 LLaMA 模型可以在单个 GPU 上运行,这大大提高了这些 LLM 的可访问性。
令人印象深刻的表现。 在 LLaMA 提出之前,许多研究小组尝试发布流行 LLM 的开源版本(例如,OPT 基本上是一个开源的 GPT-3)。但这些模型的表现远不如通过 API 访问的付费模型。尽管 LLaMA 在某些情况下的性能不尽如人意,但它是一个巨大的进步,因为它通常超越了流行的、最先进的 LLM(取决于使用的模型大小)。
结束语
非常感谢阅读这篇文章。我是 卡梅伦·R·沃尔夫,Rebuy 的 AI 总监。我研究深度学习的经验性和理论基础。你也可以查看我在 medium 上的 其他写作!如果你喜欢,请在 twitter 上关注我或订阅我的 深度(学习)焦点通讯,我通过易懂的热门论文概述帮助读者更深入地理解 AI 研究领域的主题。
参考文献
[1] 图弗龙, 休戈 等. “Llama: 开放且高效的基础语言模型.” arXiv 预印本 arXiv:2302.13971 (2023).
[2] 瓦斯瓦尼, 阿希什 等. “注意力是你所需要的一切.” 神经信息处理系统进展 30 (2017).
[3] 霍夫曼, 乔丹 等. “训练计算最优的大型语言模型.” arXiv 预印本 arXiv:2203.15556 (2022).
[4] 布朗, 汤姆 等. “语言模型是少量学习者.” 神经信息处理系统进展 33 (2020): 1877–1901.
[5] 周德瑞, 阿坎卡莎 等. “Palm: 通过路径扩展语言建模.” arXiv 预印本 arXiv:2204.02311 (2022).
[6] OpenAI (2023). “GPT-4 技术报告.” ArXiv, abs/2303.08774.
[7] 温泽克, 纪尧姆 等. “CCNet: 从网页抓取数据中提取高质量单语数据集.” arXiv 预印本 arXiv:1911.00359 (2019).
[8] 张彪 和 里科·森里奇. “均方根层归一化.” 神经信息处理系统进展 32 (2019).
[9] 拉贝, 马库斯·N. 和 查尔斯·斯塔茨. “自注意力不需要 $ O (n^ 2) $ 记忆.” arXiv 预印本 arXiv:2112.05682 (2021).
[10] 苏, 剑林 等. “Roformer: 带有旋转位置嵌入的增强型变换器.” arXiv 预印本 arXiv:2104.09864 (2021).
[11] 张, 苏珊 等. “Opt: 开放预训练变换器语言模型.” arXiv 预印本 arXiv:2205.01068 (2022).
[12] Scao, Teven Le 等. “Bloom: 一个 176b 参数的开放访问多语言模型.” arXiv 预印本 arXiv:2211.05100 (2022).
[13] 雷, 杰克·W. 等. “扩展语言模型: 方法, 分析与训练 Gopher 的见解.” arXiv 预印本 arXiv:2112.11446 (2021).
[14] 布莱克, 西德 等. “Gpt-neox-20b: 一个开源自回归语言模型.” arXiv 预印本 arXiv:2204.06745 (2022).
[15] 沙泽尔, 诺亚姆. “Glu 变体改进变换器.” arXiv 预印本 arXiv:2002.05202 (2020).
[16] 张彪 和 里科·森里奇. “均方根层归一化.” 神经信息处理系统进展 32 (2019).
LlamaIndex:终极 LLM 框架,用于索引和检索
LlamaIndex 简介
·
关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 6 月 20 日
–
LlamaIndex,之前称为 GPT Index,是一个出色的数据框架,旨在帮助您通过提供必要的工具来构建与 LLM 的应用程序,这些工具可以促进数据摄取、结构化、检索以及与各种应用程序框架的集成。LlamaIndex 提供的功能众多且极具价值:
✅ 从不同的数据源和数据格式中获取数据,使用数据连接器(Llama Hub)。
✅ 启用文档操作,如插入、删除、更新和刷新文档索引。
✅ 支持对异构数据和多个文档进行综合处理。
✅ 使用“Router”在不同的查询引擎之间进行选择。
✅ 允许假设文档嵌入以提高输出质量
✅ 提供与各种向量存储、ChatGPT 插件、跟踪工具以及 LangChain 等的广泛集成。
✅ 支持全新的 OpenAI 函数调用 API。
这些只是 LlamaIndex 提供的广泛功能中的一些示例。在这篇博客文章中,我们将探讨一些我发现与 LlamaIndex 非常有用的功能。
数据连接器(LlamaHub)
在开发 LLM 应用程序时,使 LLM 能够有效地与外部数据源交互是至关重要的。如何获取数据是关键。Llama Hub 提供了超过 100 种数据源和格式,使 LlamaIndex 或 LangChain 能够以一致的方式获取数据。
LlamaHub。来源:llama-hub-ui.vercel.app/
。
默认情况下,你可以pip install llama-hub
并将其作为独立包使用。你还可以选择使用我们的download_loader
方法单独下载一个数据加载器,以便与 LlamaIndex 一起使用。
这里是一个示例,我们从llama-hub
包中加载维基百科数据加载器。统一的语法非常好。
from llama_hub.wikipedia.base import WikipediaReader
loader = WikipediaReader()
documents = loader.load_data(pages=['Berlin', 'Rome', 'Tokyo', 'Canberra', 'Santiago'])
检查输出:
Llama Hub 还支持多模态文档。例如,ImageReader加载器使用 pytesseract 或 Donut 转换器模型从图像中提取文本。
基本查询功能
索引、检索器和查询引擎
索引、检索器和查询引擎是对数据或文档提出问题的三个基本组件:
-
索引是一个数据结构,允许我们快速从外部文档中检索相关信息。索引通过将文档解析为称为“节点”的文本块来工作,然后从这些块中构建索引。
-
检索器用于根据用户查询获取和检索相关信息。
-
查询引擎建立在索引和检索器之上,提供一个通用接口来询问有关数据的问题。
这里是询问文档问题的最简单方法。你首先从文档中创建一个索引,然后使用查询引擎作为问题的接口:
from llama_index import VectorStoreIndex
index = VectorStoreIndex.from_documents(docs)
query_engine = index.as_query_engine()
response = query_engine.query("Who is Paul Graham.")
有各种类型的索引、检索方法和查询引擎,你可以在 LlamaIndex 文档中进一步阅读。接下来,我想介绍一些我认为有用的酷功能。
处理文档更新
许多时候,一旦我们为文档创建了索引,可能需要定期更新文档。如果我们需要重新创建整个文档的嵌入,这个过程可能会很昂贵。LlamaIndex 的索引结构通过支持高效的插入、删除、更新和刷新操作提供了解决方案。例如,可以将新文档作为额外的节点(文本片段)插入,而不需要重新创建以前文档的节点:
# Source: https://gpt-index.readthedocs.io/en/latest/how_to/index/document_management.html
from llama_index import ListIndex, Document
index = ListIndex([])
text_chunks = ['text_chunk_1', 'text_chunk_2', 'text_chunk_3']
doc_chunks = []
for i, text in enumerate(text_chunks):
doc = Document(text, doc_id=f"doc_id_{i}")
doc_chunks.append(doc)
# insert
for doc_chunk in doc_chunks:
index.insert(doc_chunk)
查询多个文档
使用 LlamaIndex 可以轻松查询多个文档。此功能通过 SubQuestionQueryEngine
类实现。查询引擎在接收到查询时,会生成一个由子查询组成的“查询计划”,这些子查询针对子文档进行,然后将结果综合以提供最终答案。
# Source: https://gpt-index.readthedocs.io/en/latest/examples/usecases/10q_sub_question.html
# Load data
march_2022 = SimpleDirectoryReader(input_files=["../data/10q/uber_10q_march_2022.pdf"]).load_data()
june_2022 = SimpleDirectoryReader(input_files=["../data/10q/uber_10q_june_2022.pdf"]).load_data()
sept_2022 = SimpleDirectoryReader(input_files=["../data/10q/uber_10q_sept_2022.pdf"]).load_data()
# Build indices
march_index = VectorStoreIndex.from_documents(march_2022)
june_index = VectorStoreIndex.from_documents(june_2022)
sept_index = VectorStoreIndex.from_documents(sept_2022)
# Build query engines
march_engine = march_index.as_query_engine(similarity_top_k=3)
june_engine = june_index.as_query_engine(similarity_top_k=3)
sept_engine = sept_index.as_query_engine(similarity_top_k=3)
query_engine_tools = [
QueryEngineTool(
query_engine=sept_engine,
metadata=ToolMetadata(name='sept_22', description='Provides information about Uber quarterly financials ending September 2022')
),
QueryEngineTool(
query_engine=june_engine,
metadata=ToolMetadata(name='june_22', description='Provides information about Uber quarterly financials ending June 2022')
),
QueryEngineTool(
query_engine=march_engine,
metadata=ToolMetadata(name='march_22', description='Provides information about Uber quarterly financials ending March 2022')
),
]
# Run queries
s_engine = SubQuestionQueryEngine.from_defaults(query_engine_tools=query_engine_tools)
Run queries
response = s_engine.query('Analyze Uber revenue growth over the latest two quarter filings')
如下所示,LlamaIndex 将复杂查询分解为 2 个子查询,并能够比较来自多个文档的信息以获得最终答案。
使用“Router”在不同查询引擎之间进行选择
想象一下,你正在构建一个从 Notion 和 Slack 中检索信息的机器人,语言模型如何知道使用哪个工具来搜索信息?LlamaIndex 就像一个聪明的助手,可以为你找到东西,即使它们在不同的地方。具体而言,LlamaIndex 的“Router”是一个非常简单的抽象,它允许在不同的查询引擎之间进行“选择”。
在这个示例中,我们有两个来自 Notion 和 Slack 的文档索引,我们为每个索引创建两个查询引擎。之后,我们将所有工具汇总,并创建一个叫做 RouterQueryEngine 的超级工具,它根据我们给每个工具的描述来选择使用哪个工具。这样,当我们问有关 Notion 的问题时,路由器将自动查找 Notion 文档中的信息。
# Source: https://gpt-index.readthedocs.io/en/latest/use_cases/queries.html#routing-over-heterogeneous-data
from llama_index import TreeIndex, VectorStoreIndex
from llama_index.tools import QueryEngineTool
# define sub-indices
index1 = VectorStoreIndex.from_documents(notion_docs)
index2 = VectorStoreIndex.from_documents(slack_docs)
# define query engines and tools
tool1 = QueryEngineTool.from_defaults(
query_engine=index1.as_query_engine(),
description="Use this query engine to do…",
)
tool2 = QueryEngineTool.from_defaults(
query_engine=index2.as_query_engine(),
description="Use this query engine for something else…",
)
from llama_index.query_engine import RouterQueryEngine
query_engine = RouterQueryEngine.from_defaults(
query_engine_tools=[tool1, tool2]
)
response = query_engine.query(
"In Notion, give me a summary of the product roadmap."
)
这有很多令人兴奋的应用案例。以下是一个完整的示例,使用路由器在 SQL 和向量数据库之间进行选择:gpt-index.readthedocs.io/en/latest/examples/query_engine/SQLRouterQueryEngine.html
。
假设文档嵌入(HyDE)
通常,当我们询问有关外部文档的问题时,我们通常会使用文本嵌入来为问题和文档创建向量表示。然后我们使用语义搜索来找到与问题最相关的文本片段。然而,问题的答案可能与问题本身有很大不同。如果我们可以先生成问题的假设答案,然后找到与假设答案最相关的文本片段会怎么样?这就是假设文档嵌入(HyDE)发挥作用的地方,并可能改善输出质量。
# Source: https://gpt-index.readthedocs.io/en/latest/examples/query_transformations/HyDEQueryTransformDemo.html
# load documents
documents = SimpleDirectoryReader('llama_index/examples/paul_graham_essay/data').load_data()
index = VectorStoreIndex.from_documents(documents)
query_str = "what did paul graham do after going to RISD"
#Now, we use HyDEQueryTransform to generate a hypothetical document and use it for embedding lookup.
hyde = HyDEQueryTransform(include_original=True)
hyde_query_engine = TransformQueryEngine(query_engine, hyde)
response = hyde_query_engine.query(query_str)
display(Markdown(f"<b>{response}</b>"))
#In this example, HyDE improves output quality significantly, by hallucinating accurately what Paul Graham did after RISD (see below), and thus improving the embedding quality, and final output.
query_bundle = hyde(query_str)
hyde_doc = query_bundle.embedding_strs[0]
支持 OpenAI 功能调用
OpenAI 最近发布了 函数调用功能,以更可靠地将 GPT 的能力与外部工具和 API 连接。查看我之前的视频,了解其具体运作方式。
LlamaIndex 快速集成了这一功能,并新增了一个全新的 OpenAIAgent
。请查看这个 笔记本 以了解更多信息。
如果函数数量太多怎么办?使用 RetrieverOpenAIAgent
!查看这个 笔记本。
将 LlamaIndex 与 LangChain 结合使用
LlmaIndex 提供了与各种向量存储、ChatGPT 插件、追踪工具和 LangChain 的广泛集成。
LlamaIndex 如何与 LangChain 区别开来?
如果你使用过 LangChain,你可能会想知道 LlamaIndex 如何与 LangChain 区别开来。如果你对 LangChain 不太熟悉,请查看我之前的 博客文章 和 视频。你会发现 LlamaIndex 和 LangChain 在功能上有显著的相似之处,包括索引、语义搜索、检索和向量数据库。它们都在任务如问答、文档总结和构建聊天机器人方面表现出色。
然而,它们各自有独特的关注领域。LangChain 具有广泛的功能列表,范围较广,专注于使用链和代理来连接外部 API。另一方面,LlamaIndex 则有更狭窄的重点,在数据索引和文档检索方面表现突出。
如何将 LlamaIndex 与 LangChain 结合使用?
有趣的是,LlamaIndex 和 LangChain 并不是相互排斥的。实际上,你可以在 LLM 应用中同时使用这两者。你可以同时使用 LlamaIndex 的数据加载器和查询引擎以及 LangChain 的代理。我知道很多人实际上在他们的项目中同时使用这两种工具。
这里有一个例子,我们在使用 LangChain 代理时使用 LlamaIndex 保持聊天历史。当我们在第二轮对话中问“我叫什么名字?”时,语言模型知道在第一轮对话中“我是 Bob”:
# source: https://github.com/jerryjliu/llama_index/blob/main/examples/langchain_demo/LangchainDemo.ipynb
# Using LlamaIndex as a memory module
from langchain import OpenAI
from langchain.llms import OpenAIChat
from langchain.agents import initialize_agent
from llama_index import ListIndex
from llama_index.langchain_helpers.memory_wrapper import GPTIndexChatMemory
index = ListIndex([])memory = GPTIndexChatMemory(
index=index,
memory_key="chat_history",
query_kwargs={"response_mode": "compact"},
# return_source returns source nodes instead of querying index
return_source=True,
# return_messages returns context in message format
return_messages=True
)
llm = OpenAIChat(temperature=0)
# llm=OpenAI(temperature=0)
agent_executor = initialize_agent([], llm, agent="conversational-react-description", memory=memory)
结论
总之,LlamaIndex 是一个极其强大的工具,可以通过你自己的数据增强大型语言模型的能力。其多样的数据连接器、先进的查询接口和灵活的集成使其成为开发 LLM 应用程序的关键组件。
致谢
感谢 Jerry Liu 的建议和反馈!
图片由 Danielle Barnes 提供,来源于 Unsplash
. . .
作者 Sophia Yang ,日期为 2023 年 6 月 19 日
Sophia Yang 是一名高级数据科学家。通过 LinkedIn、Twitter 和 YouTube 与我联系,并加入 DS/ML 读书俱乐部 ❤️