OpenAl之Embedding
1、表示学习与嵌入
表示学习(Representation Learning)和嵌入(Embedding)是密切相关的概念,它们可以被视为在不同领域 中对同一概念的不同命名或描述。
- 表示学习:通过学习算法自动地从原始数据中学习到一种表示形式或特征表示,该表示形式能够更好地表达数据的重要特征和结构。表示学习的目标是将输入数据转换为具有良好表示能力的特征空间,使得在该空间中的数据具有更好的可分性、可解释性或推理能力。
- 可分性:表示学习的目标之一是将输入数据转换为具有更好可分性的特征空间。这意味着在学习到的表示中,不同类别或不同概念之间的样本应该有明显的边界或区别。例如,在图像分类任务中,表示学习的目标是使来自不同类别的图像在特征空间中更容易区分。这样的特征表示使得机器学习算法可以更轻松地进行分类、聚类或其他数据分析任务。
- 可解释性:另一个表示学习的目标是生成可解释性的特征表示。这意味着学习到的特征应该具有对应于原始 数据中的可理解概念或语义的含义。例如,在自然语言处理中,词嵌入模型学习到的词向量应该能够捕捉到词语之间的语义关系,使得相似的词在向量空间中更接近。这样的表示不仅有助于模型的解释性,也可以在 语义分析和文本生成等任务中提供更好的性能。
- 推理能力:另一个重要的目标是使得学习到的特征表示在推理任务中更具能力。这意味着在特征空间中,我 们可以执行类似于推理、类比或关联的操作。例如,通过在词嵌入空间中执行向量运算,如 “国王” - “男人” + “女人”,我们可以得到与 “皇后” 非常接近的结果。这种推理能力使得我们能够进行关联和类比推理,从而在自然语言处理、推荐系统和知识图谱等领域中实现更高级的语义推理和理解。
- 嵌入:表示学习的一种形式,通常用于将高维数据映射到低维空间中的表示形式。嵌入可以是词嵌入、图像嵌入、图嵌入等。例如,在自然语言处理中,词嵌入将词语映射到低维向量空间,以捕捉词语之间的语义和句法关系。在图像处理中,图像嵌入将图像映射到低维向量空间,以表示图像的视觉特征。
- 表示学习:通过学习算法自动地从原始数据中学习到一种表示形式或特征表示,该表示形式能够更好地表达数据的重要特征和结构。表示学习的目标是将输入数据转换为具有良好表示能力的特征空间,使得在该空间中的数据具有更好的可分性、可解释性或推理能力。
因此,嵌入可以被视为一种表示学习的特定形式,旨在将高维数据转换为低维向量表示。表示学习可以涉及更广泛的概念和方法,包括嵌入在内,以实现对数据的更好理解和表达。包括嵌入在内,表示学习涉及更广泛的概念和方法,以实现对数据的更好理解和表达。
词嵌入(Word Embedding):在自然语言处理中,词嵌入将词语映射到低维向量空间,以捕捉词语之间的语义和句法关系。 通过学习词嵌入,可以将词语表示为连续的向量,其中相似的词语在向量空间中彼此靠近。它在自然语言处 理任务中广泛应用,包括词语相似度计算、文本分类、命名实体识别等。词嵌入可以通过Word2Vec、GloVe 等方法进行学习。
图像嵌入(Image Embedding):在图像处理中,图像嵌入将图像映射到低维向量空间,以表示图像的视觉特征。这种嵌入方法通常通过使用卷积神经网络(Convolutional Neural Networks, CNN)等深度学习技术来提取图像的特征表示。
图嵌入(Graph Embedding):是用于学习图结构的表示学习方法,将图中的节点和边映射到低维向量空间中。通过 学习图嵌入,可以将复杂的图结构转化为向量表示,以捕捉节点之间的结构和关联关系。这些方法可以通过DeepWalk、Node2Vec、GraphSAGE等算法来实现。图嵌入在图分析、社交网络分析、推荐系统等领域中广泛应用, 用于发现社区结构、节点相似性、信息传播等图属性。
嵌入(Embedding)的价值
- 降维:在许多实际问题中,原始数据的维度往往非常高。例如,在自然语言处理中,如果使用One-hot编码来表示词汇,其维度等于词汇表的大小,可能达到数十万甚至更高。通过Embedding,我们可以将这些高维数据映射到一个低维空间,大大减少了模型的复杂度。
- 捕捉语义信息:Embedding不仅仅是降维,更重要的是,它能够捕捉到数据的语义信息。例如,在词嵌入中,语 义上相近的词在向量空间中也会相近。这意味着Embedding可以保留并利用原始数据的一些重要信息。
- 适应性:与一些传统的特征提取方法相比,Embedding是通过数据驱动的方式学习的。这意味着它能够自动适应数据的特性,而无需人工设计特征。
- 泛化能力:在实际问题中,我们经常需要处理一些在训练数据中没有出现过的数据。由于Embedding能够捕捉到数据的一些内在规律,因此对于这些未见过的数据,Embedding仍然能够给出合理的表示。
- 可解释性:尽管Embedding是高维的,但我们可以通过一些可视化工具(如t-SNE)来观察和理解Embedding的结构。这对于理解模型的行为,以及发现数据的一些潜在规律是非常有用的。
2、聚焦Word Embedding
2.1 概念
Word Embedding为自然语言处理任务提供了更好的单词表示方法,它的应用主要有:
- 语义表示和语义相似度:Word Embedding可以捕捉到单词之间的语义关系,使得相似含义的单词在向量空间中彼此靠近。这使得我们可以通过计算向量之间的距离或相似度来度量单词的语义相似度。这对于词义消歧、语义搜索、语义推理等任务非常有用。
- 词语关系和类比推理:Word Embedding可以捕捉到单词之间的关系,如同义词、反义词、上下位关系等。通过在向量空间中进行向量运算,例如求解向量之间的差异或类比推理,我们可以发现词语之间的有趣关系。例如,对于词语之间的类比关系”king - man + woman = queen”,通过Word Embedding可以得到类似的结果。
- 上下文理解:Word Embedding可以帮助理解单词的上下文信息。通过学习单词在其周围上下文中的嵌入表示,我们可以捕捉到单词的语境信息,从而帮助解决语义歧义、词语消歧和指代消解等问题。
- 文本分类和情感分析:Word Embedding可以提供丰富的词语特征表示,从而改善文本分类和情感分析任务的性能。 通过将文本中的词语映射为嵌入向量,并将这些向量作为输入特征,我们可以在分类器或情感分析模型中进行训练和预测。
- 机器翻译和生成模型:Word Embedding对于机器翻译和生成模型也是至关重要的。通过将源语言和目标语言的单词都映射为嵌入向量,可以提高翻译的准确性和生成模型的质量。嵌入向量可以捕捉到单词之间的语义和句法关系,帮助模型更好地理解和生成自然语言。
Embedding这么厉害,还要语言模型干嘛?
- Word Embedding:词嵌入通常被用来生成词的向量表示,这个过程通常是静态的,即一旦训练完成,每个词的向量表示就确定了。词嵌入的主要目标是捕获单词或短语的语义和语法信息,并将这些信息以向量形式表示出来。词嵌入的一个重要特性是,语义上相近的词在嵌入空间中的距离也比较近。然而,词嵌入并不能理解上下文信息,即相同的词在不同的上下文中可能有不同的含义,但词嵌入无法区分这些含义。
- Language Model:语言模型则是预测词序列的概率模型,这个过程通常是动态的,会根据输入的上下文进行变化。语言模型的主要目标是理解和生成文本。这包括对上下文的理解,词的预测,句子的生成等等。语言模型会用到词嵌入,但同时也会对上下文进行建模,这样可以处理词在不同上下文中的不同含义。
- 在某种程度上,你可以将词嵌入看作是语言模型的一部分或者输入,语言模型使用词嵌入捕捉的信息,来进行更深层次的语义理解和文本生成。当然,现在有一些更先进的模型,比如BERT,GPT等,它们生成的是上下文相关的词嵌入,即词的嵌入会根据上下文变化,这样一定程度上弥补了传统词嵌入模型的不足。
2.2 实战
2.2.1 安装依赖包
1 | !pip install tiktoken openai pandas matplotlib plotly scikit-learn numpy |
2.2.2 生成 Embedding(基于text-embedding-ada-002模型)
嵌入对于处理自然语言和代码非常有用,因为其他机器学习模型和算法(如聚类或搜索)可以轻松地使用和比较它们。
以亚马逊美食评论数据集(amazon-fine-food-reviews)为例:https://www.kaggle.com/datasets/snap/amazon-fine-food-reviews,该数据集包含截至2012年10月用户在亚马逊上留下的共计568,454条美食评论。为了说明目的,我们将使用该数据集的一个子集,其中包括最近1,000条评论。这些评论都是用英语撰写的,并且倾向于积极或消极。每个评论都有一个产品ID、用户ID、评分、标题(摘要)和正文。我们将把评论摘要和正文合并成一个单一的组合文本。模型将对这个组合文本进行编码,并输出一个单一的向量嵌入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# 导入 pandas 包。Pandas 是一个用于数据处理和分析的 Python 库
# 提供了 DataFrame 数据结构,方便进行数据的读取、处理、分析等操作。
import pandas as pd
# 导入 tiktoken 库。Tiktoken 是 OpenAI 开发的一个库,用于从模型生成的文本中计算 token 数量。
import tiktoken
# 加载数据集
input_datapath = "data/fine_food_reviews_1k.csv"
df = pd.read_csv(input_datapath, index_col=0)
# 在对象 df 中选取指定名称的列,然后将选取的结果重新赋值给 df
df = df[["Time", "ProductId", "UserId", "Score", "Summary", "Text"]]
# 移除数据框 df 中包含缺失值的行或列
df = df.dropna()
# 将 "Summary" 和 "Text" 字段组合成新的字段 "combined"
df["combined"] = (
"Title: " + df.Summary.str.strip() + "; Content: " + df.Text.str.strip()
)
df.head(2)1
df["combined"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31# 模型类型
# 建议使用官方推荐的第二代嵌入模型:text-embedding-ada-002
embedding_model = "text-embedding-ada-002"
# text-embedding-ada-002 模型对应的分词器(TOKENIZER)
embedding_encoding = "cl100k_base"
# text-embedding-ada-002 模型支持的输入最大 Token 数是8191,向量维度 1536
# 在我们的 DEMO 中过滤 Token 超过 8000 的文本
max_tokens = 8000
# 将样本减少到最近的1,000个评论,并删除过长的样本
# 设置要筛选的评论数量为1000
top_n = 1000
# 对DataFrame进行排序,基于"Time"列,然后选取最后的2000条评论。
# 这个假设是,我们认为最近的评论可能更相关,因此我们将对它们进行初始筛选。
df = df.sort_values("Time").tail(top_n * 2)
# 丢弃"Time"列,因为我们在这个分析中不再需要它。
# axis:指定删除的方向,可选值为 0 或 'index'(默认),表示删除行;1 或 'columns' 表示删除列
# inplace:一个布尔值,默认为 False。如果为 True,则直接在原数据框上进行修改,不返回新的数据框;如果为 False,则返回一个删除了指定行或列的新数据框,原数据框保持不变。
df.drop("Time", axis=1, inplace=True)
# 从'embedding_encoding'获取编码
encoding = tiktoken.get_encoding(embedding_encoding)
# 计算每条评论(combined列)的token数量。我们通过使用encoding.encode方法获取每条评论的token数,然后把结果存储在新的'n_tokens'列中。
df["n_tokens"] = df.combined.apply(lambda x: len(encoding.encode(x)))
# 如果评论的token数量超过最大允许的token数量,我们将忽略(删除)该评论。
# 我们使用.tail方法获取token数量在允许范围内的最后top_n(1000)条评论。
df = df[df.n_tokens <= max_tokens].tail(top_n)
# 打印出剩余评论的数量,值为1000。
len(df)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28# 生成 Embeddings 并保存
from openai import OpenAI
# OpenAI Python SDK v1.0 更新后的使用方式
client = OpenAI()
# 使用OpenAI代理方式,需要修改base_url
# client = OpenAI(
# api_key="XXX", # 你的KEY
# base_url="https://vip.apiyi.com/v1"
# )
# 替换为国内大模型 API 地址
# # 智谱API调用(glm-4v-flash)
# openai = OpenAI(
# api_key="XXX",
# base_url="https://open.bigmodel.cn/api/paas/v4/"
# )
# DeepSeek API调用(deepseek-chat / deepseek-reasoner)
# DeepSeek Completions接口 用户需要设置 base_url="https://api.deepseek.com/beta" 来使用此功能
# openai = OpenAI(
# api_key="XXX",
# base_url="https://api.deepseek.com"
# )
# 新版本创建 Embedding 向量的方法
# Ref:https://community.openai.com/t/embeddings-api-documentation-needs-to-updated/475663
res = client.embeddings.create(input="abc", model=embedding_model)
print(res.data[0].embedding)1
2
3
4
5
6
7
8
9
10# 使用新方法调用 OpenAI Embedding API
def embedding_text(text, model="text-embedding-ada-002"):
res = client.embeddings.create(input=text, model=model)
return res.data[0].embedding
# 如果你已经支付过 OpenAI API 账单,可调用以下代码测试批量Embedding
# 实际生成会耗时几分钟,逐行调用 OpenAI Embedding API
df["embedding"] = df.combined.apply(embedding_text)
output_datapath = "data/fine_food_reviews_with_embeddings_1k_1126.csv"
df.to_csv(output_datapath)1
2
3
4
5# 读取 fine_food_reviews_with_embeddings_1k 嵌入文件
embedding_datapath = "data/fine_food_reviews_with_embeddings_1k.csv"
df_embedded = pd.read_csv(embedding_datapath, index_col=0)
# 查看 Embedding 结果
df_embedded["embedding"]1
2len(df_embedded["embedding"][0]) # 值为"34410"
type(df_embedded["embedding"][0]) # 值为"str"1
df_embedded["embedding"][0]
1
2
3
4
5
6
7import ast
# 将字符串转换为向量
df_embedded["embedding_vec"] = df_embedded["embedding"].apply(ast.literal_eval)
len(df_embedded["embedding_vec"][0]) # 值为"1536"
df_embedded.head(2)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34# 使用 t-SNE 可视化 1536 维 Embedding 美食评论
# 导入 NumPy 包,NumPy 是 Python 的一个开源数值计算扩展。这种工具可用来存储和处理大型矩阵,
# 比 Python 自身的嵌套列表(nested list structure)结构要高效的多。
import numpy as np
# 从 matplotlib 包中导入 pyplot 子库,并将其别名设置为 plt。
# matplotlib 是一个 Python 的 2D 绘图库,pyplot 是其子库,提供了一种类似 MATLAB 的绘图框架。
import matplotlib.pyplot as plt
import matplotlib
# 从 sklearn.manifold 模块中导入 TSNE 类。
# TSNE (t-Distributed Stochastic Neighbor Embedding) 是一种用于数据可视化的降维方法,尤其擅长处理高维数据的可视化。
# 它可以将高维度的数据映射到 2D 或 3D 的空间中,以便我们可以直观地观察和理解数据的结构。
from sklearn.manifold import TSNE
type(df_embedded["embedding_vec"]) # 值为"pandas.core.series.Series"
# 首先,确保你的嵌入向量都是等长的
assert df_embedded['embedding_vec'].apply(len).nunique() == 1
# 将嵌入向量列表转换为二维 numpy 数组
# 当在 Series 对象后调用 .values 属性时,它会将 Series 中的数据转换为 NumPy 数组(numpy.ndarray)。NumPy 数组是 NumPy 库中用于高效存储和处理多维数值数据的对象,相比于 pandas.Series,它在进行数值计算时通常具有更高的性能。
# vstack将多个数组沿着垂直方向进行堆叠合并成一个二维数组
matrix = np.vstack(df_embedded['embedding_vec'].values)
# 创建一个 t-SNE 模型,t-SNE 是一种非线性降维方法,常用于高维数据的可视化。
# n_components 表示降维后的维度(在这里是2D)
# perplexity 可以被理解为近邻的数量
# random_state 是随机数生成器的种子
# init 设置初始化方式
# learning_rate 是学习率。
tsne = TSNE(n_components=2, perplexity=15, random_state=42, init='random', learning_rate=200)
# 使用 t-SNE 对数据进行降维,得到每个数据点在新的2D空间中的坐标
vis_dims = tsne.fit_transform(matrix)
type(vis_dims) # 值为"numpy.ndarray"
vis_dims1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18# 定义了五种不同的颜色,用于在可视化中表示不同的等级
colors = ["red", "darkorange", "gold", "turquoise", "darkgreen"]
# 从降维后的坐标中分别获取所有数据点的横坐标和纵坐标
x = [x for x,y in vis_dims]
y = [y for x,y in vis_dims]
# 根据数据点的评分(减1是因为评分是从1开始的,而颜色索引是从0开始的)获取对应的颜色索引
color_indices = df_embedded.Score.values - 1
# 确保你的数据点和颜色索引的数量匹配
assert len(vis_dims) == len(df_embedded.Score.values)
# 创建一个基于预定义颜色的颜色映射对象
colormap = matplotlib.colors.ListedColormap(colors)
# 使用 matplotlib 创建散点图,其中颜色由颜色映射对象和颜色索引共同决定,alpha 是点的透明度
plt.scatter(x, y, c=color_indices, cmap=colormap, alpha=0.3)
# 为图形添加标题
plt.title("Amazon ratings visualized in language using t-SNE")1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26# 使用 K-Means 聚类,然后使用 t-SNE 可视化
import numpy as np
# 从 scikit-learn中导入 KMeans 类。KMeans 是一个实现 K-Means 聚类算法的类。
from sklearn.cluster import KMeans
# np.vstack 是一个将输入数据堆叠到一个数组的函数(在垂直方向)。
# 这里它用于将所有的 ada_embedding 值堆叠成一个矩阵。
# matrix = np.vstack(df.ada_embedding.values)
# 定义要生成的聚类数。
n_clusters = 4
# 创建一个 KMeans 对象,用于进行 K-Means 聚类。
# n_clusters 参数指定了要创建的聚类的数量;
# init 参数指定了初始化方法(在这种情况下是 'k-means++');
# random_state 参数为随机数生成器设定了种子值,用于生成初始聚类中心。
# n_init=10 消除警告 'FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4'
kmeans = KMeans(n_clusters = n_clusters, init='k-means++', random_state=42, n_init=10)
# 使用 matrix(我们之前创建的矩阵)来训练 KMeans 模型。这将执行 K-Means 聚类算法。
kmeans.fit(matrix)
# kmeans.labels_ 属性包含每个输入数据点所属的聚类的索引。
# 这里,我们创建一个新的 'Cluster' 列,在这个列中,每个数据点都被赋予其所属的聚类的标签。
df_embedded['Cluster'] = kmeans.labels_
df_embedded['Cluster']1
df_embedded.head(2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28# 首先为每个聚类定义一个颜色。
colors = ["red", "green", "blue", "purple"]
# 然后,你可以使用 t-SNE 来降维数据。这里,我们只考虑 'embedding_vec' 列。
tsne_model = TSNE(n_components=2, random_state=42)
vis_data = tsne_model.fit_transform(matrix)
# 现在,你可以从降维后的数据中获取 x 和 y 坐标。
# : 是切片操作符,表示选取该轴上的所有元素。在这里,: 放在第一个位置,表示选取所有行。
# 选取 vis_data 中所有行的第 0 列数据
x = vis_data[:, 0]
# 选取 vis_data 中所有行的第 1 列数据
y = vis_data[:, 1]
# 'Cluster' 列中的值将被用作颜色索引。
color_indices = df_embedded['Cluster'].values
# 创建一个基于预定义颜色的颜色映射对象
colormap = matplotlib.colors.ListedColormap(colors)
# 使用 matplotlib 创建散点图,其中颜色由颜色映射对象和颜色索引共同决定
plt.scatter(x, y, c=color_indices, cmap=colormap)
# 为图形添加标题
plt.title("Clustering visualized in 2D using t-SNE")
# 显示图形
plt.show()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25# 使用 Embedding 进行文本搜索
# cosine_similarity 函数计算两个嵌入向量之间的余弦相似度。
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
type(df_embedded["embedding_vec"][0]) # 值为"list"
# 定义一个名为 search_reviews 的函数,
# Pandas DataFrame 产品描述,数量,以及一个 pprint 标志(默认值为 True)。
def search_reviews(df, product_description, n=3, pprint=True):
product_embedding = embedding_text(product_description)
df["similarity"] = df.embedding_vec.apply(lambda x: cosine_similarity(x, product_embedding))
results = (
df.sort_values("similarity", ascending=False)
.head(n)
.combined.str.replace("Title: ", "")
.str.replace("; Content:", ": ")
)
if pprint:
for r in results:
print(r[:200])
print()
return results1
2
3
4# 使用 'delicious beans' 作为产品描述和 3 作为数量,
# 调用 search_reviews 函数来查找与给定产品描述最相似的前3条评论。
# 其结果被存储在 res 变量中。
res = search_reviews(df_embedded, 'delicious beans', n=3)1
res = search_reviews(df_embedded, 'dog food', n=3)
1
res = search_reviews(df_embedded, 'awful', n=5)