新的嵌入模型
text-embedding-3-small 和 text-embedding-3-large,即我们最新且性能最强的嵌入模型,现已推出。它们具备更低的成本、更高的多语言性能,以及用于控制整体维度的全新参数。
OpenAI 的文本嵌入用于衡量文本字符串之间的相关性。嵌入通常用于:
搜索 (结果根据与查询字符串的相关性进行排序)
聚类 (文本字符串按相似性进行分组)
推荐 (推荐具有相关文本字符串的项目)
异常检测 (识别相关性较低的异常值)
多样性测量 (分析相似性分布)
分类 (根据最相似的标签对文本字符串进行分类)
嵌入是一个浮点数向量(列表)。两个向量之间的距离 衡量了它们的相关性。距离越小,相关性越高;距离越大,相关性越低。
访问我们的定价页面 以了解嵌入服务的价格。请求根据输入 中的令牌 (tokens) 数量计费。
要获取嵌入,请将您的文本字符串连同嵌入模型名称(例如 text-embedding-3-small)发送至嵌入 API 端点 。
1
2
3
4
5
6
7
8
9
10
import OpenAI from "openai" ;
const openai = new OpenAI();
const embedding = await openai.embeddings.create({
model : "text-embedding-3-small" ,
input : "Your text string goes here" ,
encoding_format : "float" ,
});
console .log(embedding); 1
2
3
4
5
6
7
8
9
from openai import OpenAI
client = OpenAI()
response = client.embeddings.create(
input = "Your text string goes here" ,
model= "text-embedding-3-small"
)
print (response.data[ 0 ].embedding) 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"
}'
响应中包含嵌入向量(浮点数列表)以及一些额外的元数据。您可以提取该嵌入向量,将其保存在向量数据库中,并用于多种不同的使用场景。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"object" : "list" ,
"data" : [
{
"object" : "embedding" ,
"index" : 0 ,
"embedding" : [
-0.006929283495992422 , -0.005336422007530928 , -4.547132266452536e-5 ,
-0.024047505110502243
]
}
],
"model" : "text-embedding-3-small" ,
"usage" : {
"prompt_tokens" : 5 ,
"total_tokens" : 5
}
}
默认情况下,text-embedding-3-small 的嵌入向量长度为 1536,text-embedding-3-large 为 3072。若要在不丢失其概念表示属性的情况下减少嵌入维度,请传入 dimensions 参数 。有关嵌入维度的更多详细信息,请参阅嵌入使用场景部分 。
OpenAI 提供两种强大的第三代嵌入模型(由模型 ID 中的 -3 表示)。阅读嵌入 v3 公告博客文章 以获取更多详情。
使用按输入令牌计费。以下是每美元(美元)对应的文本页面定价示例(假设每页约 800 个令牌):
模型 ~ 每美元页面数 MTEB 评估性能最大输入 text-embedding-3-small 62,500 62.3% 8192 text-embedding-3-large 9,615 64.6% 8192 text-embedding-ada-002 12,500 61.0% 8192
在此,我们使用 亚马逊优质食品评论数据集 展示一些代表性的使用场景。
该数据集包含截至 2012 年 10 月亚马逊用户留下的总计 568,454 条食品评论。为了演示,我们使用了其中 1000 条最新评论的子集。这些评论均为英文,且通常分为正面或负面。每条评论都有一个 ProductId(产品ID)、UserId(用户ID)、Score(评分)、评论标题(Summary)和评论正文(Text)。例如:
产品 ID 用户 ID 评分 摘要 文本 B001E4KFG0 A3SGXH7AUHU8GW 5 优质狗粮 我买了几罐 Vitality…… B00813GRG4 A1D87F6ZCVE5NK 1 与广告描述不符 产品送达时标记为巨型咸花生……
在下文中,我们将评论摘要和评论正文合并为一段组合文本。模型将对这段组合文本进行编码,并输出单个向量嵌入。
Get_embeddings_from_dataset.ipynb
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 )
要从已保存的文件中加载数据,您可以运行以下操作:
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)
使用较大的嵌入(例如将其存储在向量存储中以便检索)通常比使用较小的嵌入成本更高,且消耗更多的计算、内存和存储资源。
我们两款新的嵌入模型均采用一种技术 训练,允许开发者在嵌入的性能和使用成本之间进行权衡。具体而言,开发者可以通过传入 dimensions API 参数 来缩短嵌入(即删除序列末尾的部分数字),而不会导致嵌入失去其概念表示属性。例如,在 MTEB 基准测试中,text-embedding-3-large 嵌入可以缩短至 256 维,同时其性能仍优于 1536 维的未缩短 text-embedding-ada-002 嵌入。您可以阅读我们的嵌入 v3 发布博客文章 ,了解有关更改维度如何影响性能的更多信息。
通常,建议在创建嵌入时使用 dimensions 参数。在某些情况下,您可能需要在生成嵌入后更改其维度。当手动更改维度时,您需要确保按照下文所示对嵌入维度进行归一化。
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 参数设为 1024,这会将嵌入从 3072 维缩短,以牺牲少量精度换取更小的向量尺寸。
Question_answering_using_embeddings.ipynb
在许多常见情况下,模型并未在包含您希望在生成用户查询响应时访问的关键事实和信息的特定数据上进行过训练。解决此问题的一种方法(如下所示)是将额外信息放入模型的上下文窗口中。这种方法在许多用例中都很有效,但会导致更高的令牌成本。在本笔记本中,我们将探索这种方法与基于嵌入的搜索之间的权衡。
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)
Semantic_text_search_using_embeddings.ipynb
为了检索最相关的文档,我们使用查询和每个文档的嵌入向量之间的余弦相似度,并返回得分最高的文档。
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 )
Code_search.ipynb
代码搜索的工作原理与基于嵌入的文本搜索类似。我们提供了一种从给定存储库中的所有 Python 文件中提取 Python 函数的方法。然后,每个函数都由 text-embedding-3-small 模型进行索引。
要执行代码搜索,我们使用相同的模型对自然语言查询进行嵌入。然后,我们计算生成的查询嵌入与每个函数嵌入之间的余弦相似度。余弦相似度最高的结果即为最相关的。
1
2
3
4
5
6
7
8
9
10
11
12
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 )
Recommendation_using_embeddings.ipynb
由于嵌入向量之间的距离越短代表相似度越高,因此嵌入对于推荐系统非常有用。
下面我们说明一个基本的推荐器。它接收一个字符串列表和一个“源”字符串,计算它们的嵌入,然后返回一个按相似度从高到低排序的字符串列表。作为一个具体示例,下方链接的笔记本将此函数的一个版本应用于 AG 新闻数据集 (下采样至 2,000 条新闻文章描述),以返回与任何给定源文章最相似的前 5 篇文章。
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."""
embeddings = [embedding_from_string(string, model=model) for string in strings]
query_embedding = embeddings[index_of_source_string]
distances = distances_from_embeddings(query_embedding, embeddings, distance_metric= "cosine" )
indices_of_nearest_neighbors = indices_of_nearest_neighbors_from_distances(distances)
return indices_of_nearest_neighbors
Visualizing_embeddings_in_2D.ipynb
嵌入的尺寸随底层模型的复杂性而变化。为了可视化这些高维数据,我们使用 t-SNE 算法将数据转换为二维。
我们根据审阅者给出的星级评分对个别评论进行着色:
1 星:红色
2 星:深橙色
3 星:金色
4 星:绿松石色
5 星:深绿色
可视化结果似乎产生了大约 3 个聚类,其中一个聚类大多包含负面评论。
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()
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" )
Regression_using_embeddings.ipynb
嵌入可以用作机器学习模型中的通用自由文本特征编码器。如果某些相关输入是自由文本,合并嵌入将提高机器学习模型的性能。嵌入还可以用作机器学习模型中的分类特征编码器。如果分类变量的名称具有意义且数量众多(例如职位名称),这会发挥最大价值。对于此类任务,相似度嵌入通常比搜索嵌入表现更好。
我们观察到,通常嵌入表示是非常丰富且信息密集的。例如,使用 SVD 或 PCA 减少输入的维度(即使减少 10%),通常会导致特定任务的下游性能下降。
此代码将数据拆分为训练集和测试集,后续的两个用例(即回归和分类)将使用这些数据集。
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
) 嵌入为预测数值提供了一种优雅的方式。在此示例中,我们根据评论文本预测审阅者的星级评分。由于嵌入中包含的语义信息量大,即使评论很少,预测效果也相当不错。
我们假设评分为 1 到 5 之间的连续变量,并允许算法预测任何浮点值。机器学习算法会最小化预测值与真实分数之间的距离,平均绝对误差为 0.39,这意味着预测平均偏差不到半颗星。
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)
Classification_using_embeddings.ipynb
这次,我们不再让算法预测 1 到 5 之间的任何值,而是尝试将评论的星级精确分类到 1 到 5 星的 5 个桶中。
训练后,模型学习预测 1 星和 5 星评论的效果比预测较为微妙的评论(2-4 星)好得多,这可能是由于评论者表达了更极端的情感。
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)
Zero-shot_classification_with_embeddings.ipynb
我们可以使用嵌入进行零样本分类,而无需任何标记的训练数据。对于每个类别,我们对类别名称或类别的简短描述进行嵌入。为了以零样本方式分类新文本,我们将其嵌入与所有类别嵌入进行比较,并预测相似度最高的类别。
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'
User_and_product_embeddings.ipynb
我们可以通过对用户的所有评论进行平均来获得用户嵌入。同样,我们可以通过对有关该产品的所有评论进行平均来获得产品嵌入。为了展示这种方法的有效性,我们使用了 50k 条评论的子集,以覆盖每个用户和每个产品的更多评论。
我们在独立的测试集上评估这些嵌入的实用性,绘制用户和产品嵌入的相似度作为评分的函数。有趣的是,基于这种方法,即使在用户收到产品之前,我们也能够比随机猜测更好地预测他们是否会喜欢该产品。
user_embeddings = df.groupby( 'UserId' ).ada_embedding.apply(np.mean)
prod_embeddings = df.groupby( 'ProductId' ).ada_embedding.apply(np.mean)
Clustering.ipynb
聚类是理解大量文本数据的一种方式。嵌入对于此任务非常有用,因为它们为每个文本提供了语义上有意义的向量表示。因此,聚类可以以无监督的方式揭示数据集中隐藏的分组。
在此示例中,我们发现了四个不同的聚类:一个侧重于狗粮,一个侧重于负面评论,两个侧重于正面评论。
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 将字符串拆分为令牌。
代码示例
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 编码。
更多详细信息和代码示例请参阅 OpenAI Cookbook 指南:如何使用 tiktoken 计算令牌 。
为了快速搜索大量向量,我们建议使用向量数据库。您可以在 GitHub 上的 Cookbook 中找到有关使用向量数据库和 OpenAI API 的示例。
我们建议使用余弦相似度 。距离函数的选择通常影响不大。
OpenAI 嵌入被归一化为长度 1,这意味着:
余弦相似度可以使用点积更快速地计算出来
余弦相似度和欧几里得距离将产生相同的排序
是的,客户拥有我们模型输入和输出的所有权,包括嵌入的情况。您有责任确保您输入到我们 API 中的内容不违反任何适用法律或我们的使用条款 。
不了解,text-embedding-3-large 和 text-embedding-3-small 模型缺乏 2021 年 9 月之后发生的事件的知识。这通常不像对于文本生成模型那样是一个巨大的限制,但在某些边缘情况下可能会降低性能。