向量嵌入
新的嵌入模型source
text-embedding-3-small
和text-embedding-3-large
,我们最新、性能最高的嵌入模型现已推出,具有更低的成本、更高的多语言性能以及控制整体大小的新参数。什么是嵌入?
OpenAI 的文本嵌入测量文本字符串的相关性。嵌入通常用于:source
- 搜索(其中结果按与查询字符串的相关性排名)
- 聚类(其中文本字符串按相似性分组)
- 推荐(推荐包含相关文本字符串的项目)
- 异常检测(识别出相关性不大的异常值)
- 多样性测量(分析相似性分布)
- 分类(其中文本字符串按其最相似的标签进行分类)
嵌入是浮点数的向量(列表)。两个向量之间的距离衡量它们的相关性。小距离表示高相关性,大距离表示低相关性。source
请访问我们的定价页面,了解 Embeddings 定价。请求根据输入中的令牌数量计费。source
如何获取嵌入
要获取嵌入,请将您的文本字符串与嵌入模型名称(例如text-embedding-3-small
).响应将包含一个嵌入(浮点数列表),您可以提取该嵌入(浮点数列表),并将其保存在矢量数据库中,并用于许多不同的用例:source
1
2
3
4
5
6
7
curl https://api.openai.com/v1/embeddings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"input": "Your text string goes here",
"model": "text-embedding-3-small"
}'
响应将包含嵌入向量以及一些其他元数据。source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [
-0.006929283495992422,
-0.005336422007530928,
... (omitted for spacing)
-4.547132266452536e-05,
-0.024047505110502243
],
}
],
"model": "text-embedding-3-small",
"usage": {
"prompt_tokens": 5,
"total_tokens": 5
}
}
默认情况下,嵌入向量的长度将为 1536text-embedding-3-small
或 3072 表示text-embedding-3-large
.您可以通过传入 dimensions 参数来减小嵌入的维度,而不会使嵌入丢失其概念表示属性。我们将在 embedding use case 部分更详细地介绍嵌入维度。source
嵌入模型
OpenAI 提供了两个强大的第三代嵌入模型(用-3
在模型 ID 中)。有关更多详细信息,您可以阅读 embedding v3 公告博客文章。source
使用量按输入令牌定价,以下是每美元文本页数的定价示例(假设每页 ~800 个令牌):source
MODEL | ~ 每美元页数 | MTEB 评估性能 | 最大输入 |
---|---|---|---|
文本嵌入 3 小 | 62,500 | 62.3% | 8191 |
text-embedding-3-large (文本嵌入 3 大) | 9,615 | 64.6% | 8191 |
文本嵌入 ADA 002 | 12,500 | 61.0% | 8191 |
使用案例
在这里,我们展示了一些具有代表性的使用案例。对于以下示例,我们将使用 Amazon fine-food reviews 数据集。source
获取嵌入
该数据集包含截至 2012 年 10 月的 Amazon 用户留下的总共 568454 条食品评论。我们将使用 1,000 条最新评论的子集进行说明。评论是英文的,往往是正面的或负面的。每条评论都有一个 ProductId、UserId、Score、评论标题 (Summary) 和评论正文 (Text)。例如:source
产品 ID | 用户 ID | 得分 | 总结 | 文本 |
---|---|---|---|---|
B001E4KFG0 | A3SGXH7AUHU8GW | 5 | 优质狗粮 | 我已经买了好几个 Vitality 罐装...... |
B00813GRG4 | A1D87F6ZCVE5NK | 1 | 与宣传的不符 | 产品到达时贴有 Jumbo Salted Peanut 的标签... |
我们会将评论摘要和评论文本合并为一个组合文本。该模型将对此组合文本进行编码并输出单个向量嵌入。source
Get_embeddings_from_dataset.ipynbsource
1
2
3
4
5
6
7
8
9
from openai import OpenAI
client = OpenAI()
def get_embedding(text, model="text-embedding-3-small"):
text = text.replace("\n", " ")
return client.embeddings.create(input = [text], model=model).data[0].embedding
df['ada_embedding'] = df.combined.apply(lambda x: get_embedding(x, model='text-embedding-3-small'))
df.to_csv('output/embedded_1k_reviews.csv', index=False)
要从保存的文件中加载数据,您可以运行以下命令:source
1
2
3
4
import pandas as pd
df = pd.read_csv('output/embedded_1k_reviews.csv')
df['ada_embedding'] = df.ada_embedding.apply(eval).apply(np.array)
与使用较小的嵌入相比,使用较大的嵌入(例如将它们存储在向量存储中以供检索)通常成本更高,并且消耗更多的计算、内存和存储。source
我们的两个新嵌入模型都使用一种技术进行训练,该技术允许开发人员在使用嵌入的性能和成本之间进行权衡。具体来说,开发人员可以通过传入dimensions
API 参数.例如,在 MTEB 基准测试中,text-embedding-3-large
嵌入可以缩短到 256 的大小,同时仍然优于未缩短的大小text-embedding-ada-002
嵌入大小为 1536。您可以在我们的 embeddings v3 发布博客文章中详细了解更改维度如何影响性能。source
通常,使用dimensions
parameter 是建议的方法。在某些情况下,您可能需要在生成嵌入维度后更改嵌入维度。手动更改维度时,需要确保对嵌入的维度进行规范化,如下所示。source
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
from openai import OpenAI
import numpy as np
client = OpenAI()
def normalize_l2(x):
x = np.array(x)
if x.ndim == 1:
norm = np.linalg.norm(x)
if norm == 0:
return x
return x / norm
else:
norm = np.linalg.norm(x, 2, axis=1, keepdims=True)
return np.where(norm == 0, x, x / norm)
response = client.embeddings.create(
model="text-embedding-3-small", input="Testing 123", encoding_format="float"
)
cut_dim = response.data[0].embedding[:256]
norm_dim = normalize_l2(cut_dim)
print(norm_dim)
动态更改尺寸可实现非常灵活的使用。例如,当使用仅支持最多嵌入 1024 个维度的向量数据存储时,开发人员现在仍然可以使用我们最好的嵌入模型text-embedding-3-large
并为dimensions
API 参数,该参数将嵌入时间从 3072 维缩短,以牺牲一些准确性来换取更小的向量大小。source
在许多常见情况下,模型未针对数据进行训练,这些数据包含您希望在生成对用户查询的响应时可访问的关键事实和信息。解决此问题的一种方法,如下所示,是将附加信息放入模型的上下文窗口中。这在许多用例中都有效,但会导致更高的代币成本。在本笔记本中,我们将探讨这种方法与 embeddings bases 搜索之间的权衡。source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
query = f"""Use the below article on the 2022 Winter Olympics to answer the subsequent question. If the answer cannot be found, write "I don't know."
Article:
\"\"\"
{wikipedia_article_on_curling}
\"\"\"
Question: Which athletes won the gold medal in curling at the 2022 Winter Olympics?"""
response = client.chat.completions.create(
messages=[
{'role': 'system', 'content': 'You answer questions about the 2022 Winter Olympics.'},
{'role': 'user', 'content': query},
],
model=GPT_MODEL,
temperature=0,
)
print(response.choices[0].message.content)
为了检索最相关的文档,我们使用查询和每个文档的嵌入向量之间的余弦相似性,并返回得分最高的文档。source
1
2
3
4
5
6
7
8
9
from openai.embeddings_utils import get_embedding, cosine_similarity
def search_reviews(df, product_description, n=3, pprint=True):
embedding = get_embedding(product_description, model='text-embedding-3-small')
df['similarities'] = df.ada_embedding.apply(lambda x: cosine_similarity(x, embedding))
res = df.sort_values('similarities', ascending=False).head(n)
return res
res = search_reviews(df, 'delicious beans', n=3)
代码搜索的工作方式与基于嵌入的文本搜索类似。我们提供了一种从给定存储库中的所有 Python 文件中提取 Python 函数的方法。然后,每个函数都由text-embedding-3-small
型。source
为了执行代码搜索,我们使用相同的模型将查询嵌入到自然语言中。然后,我们计算生成的查询嵌入和每个函数嵌入之间的余弦相似性。最高余弦相似度结果最相关。source
1
2
3
4
5
6
7
8
9
10
11
from openai.embeddings_utils import get_embedding, cosine_similarity
df['code_embedding'] = df['code'].apply(lambda x: get_embedding(x, model='text-embedding-3-small'))
def search_functions(df, code_query, n=3, pprint=True, n_lines=7):
embedding = get_embedding(code_query, model='text-embedding-3-small')
df['similarities'] = df.code_embedding.apply(lambda x: cosine_similarity(x, embedding))
res = df.sort_values('similarities', ascending=False).head(n)
return res
res = search_functions(df, 'Completions API tests', n=3)
由于嵌入向量之间的距离越短,相似性越强,因此嵌入可用于推荐。source
下面,我们说明一个基本的推荐器。它接收一个字符串列表和一个 'source' 字符串,计算它们的嵌入,然后返回字符串的排名,从最相似到最不相似排序。作为一个具体示例,下面的链接笔记本将此函数的一个版本应用于 AG 新闻数据集(采样到 2,000 篇新闻文章描述),以返回与任何给定源文章最相似的前 5 篇文章。source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def recommendations_from_strings(
strings: List[str],
index_of_source_string: int,
model="text-embedding-3-small",
) -> List[int]:
"""Return nearest neighbors of a given string."""
# get embeddings for all strings
embeddings = [embedding_from_string(string, model=model) for string in strings]
# get the embedding of the source string
query_embedding = embeddings[index_of_source_string]
# get distances between the source embedding and other embeddings (function from embeddings_utils.py)
distances = distances_from_embeddings(query_embedding, embeddings, distance_metric="cosine")
# get indices of nearest neighbors (function from embeddings_utils.py)
indices_of_nearest_neighbors = indices_of_nearest_neighbors_from_distances(distances)
return indices_of_nearest_neighbors
嵌入的大小随基础模型的复杂程度而变化。为了可视化这些高维数据,我们使用 t-SNE 算法将数据转换为二维数据。source
我们根据评论者给出的星级评分为各个评论着色:source
- 1 星:红色
- 2 星:深橙色
- 3 星:金
- 4 星:绿松石色
- 5 星:深绿色

可视化似乎产生了大约 3 个集群,其中一个集群大部分是负面评论。source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
import matplotlib
df = pd.read_csv('output/embedded_1k_reviews.csv')
matrix = df.ada_embedding.apply(eval).to_list()
# Create a t-SNE model and transform the data
tsne = TSNE(n_components=2, perplexity=15, random_state=42, init='random', learning_rate=200)
vis_dims = tsne.fit_transform(matrix)
colors = ["red", "darkorange", "gold", "turquiose", "darkgreen"]
x = [x for x,y in vis_dims]
y = [y for x,y in vis_dims]
color_indices = df.Score.values - 1
colormap = matplotlib.colors.ListedColormap(colors)
plt.scatter(x, y, c=color_indices, cmap=colormap, alpha=0.3)
plt.title("Amazon ratings visualized in language using t-SNE")
嵌入可用作机器学习模型中的通用自由文本特征编码器。如果某些相关输入是自由文本,则合并嵌入将提高任何机器学习模型的性能。嵌入还可以用作 ML 模型中的分类特征编码器。如果分类变量的名称有意义且数量众多,则会增加最大价值。对于此任务,相似性嵌入通常比搜索嵌入执行得更好。source
我们观察到,通常嵌入表示非常丰富且信息密集。例如,使用 SVD 或 PCA 减少输入的维度,即使降低 10%,通常也会导致特定任务的下游性能变差。source
此代码将数据拆分为训练集和测试集,将由以下两个用例使用,即回归和分类。source
1
2
3
4
5
6
7
8
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
list(df.ada_embedding.values),
df.Score,
test_size = 0.2,
random_state=42
)
使用嵌入功能的回归
嵌入提供了一种预测数值的优雅方法。在此示例中,我们根据评论文本预测评论者的星级评分。因为嵌入中包含的语义信息很高,所以即使评论很少,预测也是不错的。source
我们假设分数是 1 到 5 之间的连续变量,并允许算法预测任何浮点值。ML 算法将预测值与真实分数的距离降至最低,并实现了 0.39 的平均绝对误差,这意味着平均而言,预测的偏差不到半颗星。source
1
2
3
4
5
from sklearn.ensemble import RandomForestRegressor
rfr = RandomForestRegressor(n_estimators=100)
rfr.fit(X_train, y_train)
preds = rfr.predict(X_test)
这一次,我们将尝试将评论的确切星级数分为 5 个类别,范围从 1 到 5 个星级,而不是让算法预测 1 到 5 之间的任何值。source
训练后,模型学会了预测 1 星和 5 星评论,而不是更细致的评论(2-4 星),这可能是由于更极端的情绪表达。source
1
2
3
4
5
6
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, accuracy_score
clf = RandomForestClassifier(n_estimators=100)
clf.fit(X_train, y_train)
preds = clf.predict(X_test)
我们可以使用 embeddings 进行零镜头分类,而无需任何标记的训练数据。对于每个类,我们嵌入了类名或类的简短描述。为了以零样本的方式对一些新文本进行分类,我们将其嵌入与所有类嵌入进行比较,并预测相似度最高的类。source
1
2
3
4
5
6
7
8
9
10
11
12
from openai.embeddings_utils import cosine_similarity, get_embedding
df= df[df.Score!=3]
df['sentiment'] = df.Score.replace({1:'negative', 2:'negative', 4:'positive', 5:'positive'})
labels = ['negative', 'positive']
label_embeddings = [get_embedding(label, model=model) for label in labels]
def label_score(review_embedding, label_embeddings):
return cosine_similarity(review_embedding, label_embeddings[1]) - cosine_similarity(review_embedding, label_embeddings[0])
prediction = 'positive' if label_score('Sample Review', label_embeddings) > 0 else 'negative'
我们可以通过平均他们的所有评论来获得用户嵌入。同样,我们可以通过平均有关该产品的所有评论来获取产品嵌入。为了展示这种方法的实用性,我们使用 50k 评论的子集来涵盖每个用户和每个产品的更多评论。source
我们在单独的测试集上评估这些嵌入的有用性,在该测试集中,我们将用户和产品嵌入的相似性绘制为评级的函数。有趣的是,基于这种方法,甚至在用户收到产品之前,我们就可以比随机更好地预测他们是否喜欢该产品。source

user_embeddings = df.groupby('UserId').ada_embedding.apply(np.mean)
prod_embeddings = df.groupby('ProductId').ada_embedding.apply(np.mean)
聚类是理解大量文本数据的一种方法。嵌入对于此任务非常有用,因为它们提供了每个文本的语义上有意义的向量表示。因此,以无监督的方式,聚类将发现数据集中隐藏的分组。source
在此示例中,我们发现了四个不同的集群:一个专注于狗粮,一个专注于负面评论,两个专注于正面评论。source

1
2
3
4
5
6
7
8
9
import numpy as np
from sklearn.cluster import KMeans
matrix = np.vstack(df.ada_embedding.values)
n_clusters = 4
kmeans = KMeans(n_clusters = n_clusters, init='k-means++', random_state=42)
kmeans.fit(matrix)
df['Cluster'] = kmeans.labels_
常见问题
在嵌入字符串之前,如何判断字符串有多少个标记?
在 Python 中,您可以使用 OpenAI 的分词器将字符串拆分为令牌tiktoken
.source
示例代码:source
1
2
3
4
5
6
7
8
9
import tiktoken
def num_tokens_from_string(string: str, encoding_name: str) -> int:
"""Returns the number of tokens in a text string."""
encoding = tiktoken.get_encoding(encoding_name)
num_tokens = len(encoding.encode(string))
return num_tokens
num_tokens_from_string("tiktoken is great!", "cl100k_base")
对于像text-embedding-3-small
,请使用cl100k_base
编码。source
更多详细信息和示例代码请参阅 OpenAI 说明书指南如何使用 tiktoken 对令牌进行计数。source
如何快速检索 K 个最近的嵌入向量?
为了快速搜索多个向量,我们建议使用向量数据库。您可以在 GitHub 上的说明书中找到使用矢量数据库和 OpenAI API 的示例。source
我应该使用哪个距离函数?
我们建议使用余弦相似性。距离函数的选择通常并不重要。source
OpenAI 嵌入被归一化为长度 1,这意味着:source
- 仅使用点积即可计算余弦相似度的速度稍快一些
- 余弦相似度和欧几里得距离将导致相同的排名
我可以在线共享我的嵌入吗?
是的,客户拥有我们模型的输入和输出,包括嵌入。您有责任确保您输入到我们的 API 的内容不违反任何适用法律或我们的使用条款。source
V3 嵌入模型是否了解最近的事件?
不,该text-embedding-3-large
和text-embedding-3-small
模型缺乏对 2021 年 9 月之后发生的事件的了解。这通常不像文本生成模型那样大,但在某些边缘情况下,它会降低性能。source