什么是嵌入?
OpenAI的文本嵌入能够衡量文本字符串之间的相关性。嵌入常用于:
- 搜索(根据查询字符串的相关性对结果进行排名)
- 聚类(根据相似性对文本字符串进行分组)
- 推荐(推荐具有相关文本字符串的项目)
- 异常检测(识别几乎没有相关性的离群点)
- 多样性测量(分析相似性分布)
- 分类(根据最相似的标签对文本字符串进行分类)
嵌入是一个由浮点数组成的向量(列表)。两个向量之间的距离衡量它们的相关性。距离越小意味着相关性越高,距离越大意味着相关性越低。
参阅我们的定价页面了解嵌入定价。请求根据输入中的标记数量进行计费。
要亲身体验嵌入,请查看我们的代码示例
- 分类
- 主题聚类
- 搜索
- 推荐
如何获取嵌入
要获取嵌入,请将文本字符串与嵌入模型ID(例如text-embedding-ada-002
)一起发送到嵌入API端点。响应将包含一个嵌入,您可以提取、保存并使用它。
示例请求:
curl https://api.openai.com/v1/embeddings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"input": "您的文本字符串",
"model": "text-embedding-ada-002"
}'
示例响应:
{
"data": [
{
"embedding": [
-0.006929283495992422,
-0.005336422007530928,
...
-4.547132266452536e-05,
-0.024047505110502243
],
"index": 0,
"object": "embedding"
}
],
"model": "text-embedding-ada-002",
"object": "list",
"usage": {
"prompt_tokens": 5,
"total_tokens": 5
}
}
在OpenAI Cookbook中可以看到更多Python代码示例。
使用OpenAI嵌入时,请注意它们的局限性和风险。
嵌入模型
OpenAI提供了一个第二代嵌入模型(text-embedding-ada-002
,模型ID中带-002
)和16个第一代模型(模型ID中带-001
)。
我们建议对绝大多数使用案例使用text-embedding-ada-002。它更好更便宜也更简单。请阅读博文公告。
模型代号 | 分词器 | 最大输入标记数 | 知识切割时间 |
---|---|---|---|
V2 | cl100k_base | 8191 | 2021年9月 |
V1 | GPT-2/GPT-3 | 2046 | 2020年8月 |
用量根据输入标记计费,价格为每1000个标记0.0004美元,即约每美元可以嵌入3000页(假设每页约800个标记):
模型 | 每美元可嵌入页数估计 | BEIR搜索评估示例性能 |
---|---|---|
text-embedding-ada-002 | 3000 | 53.9 |
-davinci--001 | 6 | 52.8 |
-curie--001 | 60 | 50.9 |
-babbage--001 | 240 | 50.4 |
-ada--001 | 300 | 49.0 |
第二代模型
模型名称 | 分词器 | 最大输入标记数 | 输出维度 |
---|---|---|---|
text-embedding-ada-002 | cl100k_base | 8191 | 1536 |
使用案例
这里我们展示一些代表性的使用案例。我们将在下面的示例中使用亚马逊美食评论数据集。
获取嵌入
该数据集包含总共568,454条亚马逊用户在2012年10月之前发表的食品评论。为了说明,我们将使用最近的1000条评论的子集。这些评论为英文,倾向于正面或负面。每条评论都有ProductId、UserId、评分、评论标题(Summary)和评论正文(Text)。例如:
产品ID | 用户ID | 评分 | 标题 | 正文 |
---|---|---|---|---|
B001E4KFG0 | A3SGXH7AUHU8GW | 5 | 优质狗粮 | 我买过几袋Vitality罐头狗粮... |
B00813GRG4 | A1D87F6ZCVE5NK | 1 | 与广告描述不符 | 收到的产品标记为加大装咸花生... |
我们将评论标题和正文组合成单个组合文本。模型将对这个组合文本进行编码并输出一个向量嵌入。
def get_embedding(text, model="text-embedding-ada-002"):
text = text.replace("\n", " ")
return openai.Embedding.create(input = [text], model=model)['data'][0]['embedding']
df['ada_embedding'] = df.combined.apply(lambda x: get_embedding(x, model='text-embedding-ada-002'))
df.to_csv('output/embedded_1k_reviews.csv', index=False)
要从已保存的文件加载数据,可以运行以下代码:
import pandas as pd
df = pd.read_csv('output/embedded_1k_reviews.csv')
df['ada_embedding'] = df.ada_embedding.apply(eval).apply(np.array)
2D数据可视化
嵌入的大小随着底层模型的复杂性而变化。为了可视化这种高维数据,我们使用t-SNE算法将数据转换为两维。
我们根据评论者给出的星级对单个评论进行颜色编码:
- 1星:红色
- 2星:深橙色
- 3星:金色
- 4星:绿松石
- 5星:深绿色
可视化似乎产生了大致3个聚类,其中一个主要包含负面评论。
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()
# 创建t-SNE模型并转换数据
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("使用t-SNE可视化的亚马逊评分")
作为机器学习算法的文本特征编码器
嵌入可以作为机器学习模型中的一般自由文本特征编码器。如果一些相关输入是自由文本,将嵌入纳入将提高任何机器学习模型的性能。嵌入也可以作为机器学习模型中的分类特征编码器。如果分类变量的名称有意义且数量众多(如职称),这会带来最大价值。相似性嵌入通常比搜索嵌入更适合这个任务。
我们观察到,嵌入表示通常非常丰富且信息密度高。例如,即使使用SVD或PCA将输入的维度减少10%,通常也会导致特定下游任务的性能降低。
这段代码将数据拆分为训练集和测试集,后面的两个用例即回归和分类将使用它们。
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,这意味着平均情况下预测偏差不到半星。
from sklearn.ensemble import RandomForestRegressor
rfr = RandomForestRegressor(n_estimators=100)
rfr.fit(X_train, y_train)
preds = rfr.predict(X_test)
使用嵌入特征的分类
这次,我们不让算法预测1到5之间的任意值,而是试图将评论的明确星级分类到5个桶中,范围从1到5星。
训练后,模型可以更好地预测1星和5星的评论,而不是更微妙的评论(2-4星),这可能是由于情感表达更加极端。
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)
零样本分类
我们可以在没有任何标记训练数据的情况下使用嵌入进行零样本分类。对于每个类别,我们嵌入类别名称或类别的简短描述。为了以零样本的方式对新的文本进行分类,我们将其嵌入与所有类别嵌入进行比较,并预测与其最相似的类别。
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'
获得用户和产品嵌入以处理冷启动问题
我们可以通过对用户的所有评论进行平均来获得用户嵌入。类似地,我们可以通过对有关该产品的所有评论进行平均来获得产品嵌入。为了展示这种方法的有用性,我们使用了包含更多用户和产品评论的5万条评论的子集。
我们在一个单独的测试集上评估这些嵌入的有用性,其中我们将用户和产品嵌入的相似度绘制为评级的函数。有趣的是,基于这种方法,即使在用户收到产品之前,我们也可以比随机猜测更好地预测他们是否会喜欢该产品。
user_embeddings = df.groupby('UserId').ada_embedding.apply(np.mean)
prod_embeddings = df.groupby('ProductId').ada_embedding.apply(np.mean)
聚类
聚类是理解大量文本数据的一种方法。嵌入对这个任务很有用,因为它们为每段文本提供了语义上有意义的向量表示。因此,以无监督的方式,聚类将在我们的数据集中发现隐藏的分组。
在这个例子中,我们发现了四个不同的聚类:一个关注狗粮,一个关注负面评论,还有两个关注正面评论。
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_
使用嵌入的文本搜索
为了检索最相关的文档,我们使用查询和每个文档的嵌入向量之间的余弦相似度,并返回得分最高的文档。
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-ada-002')
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-ada-002
模型建立索引。
要执行代码搜索,我们使用相同的模型以自然语言嵌入查询。然后我们计算查询嵌入与每个函数嵌入之间的余弦相似度。余弦相似度最高的结果最相关。
from openai.embeddings_utils import get_embedding, cosine_similarity
df['code_embedding'] = df['code'].apply(lambda x: get_embedding(x, model='text-embedding-ada-002'))
def search_functions(df, code_query, n=3, pprint=True, n_lines=7):
embedding = get_embedding(code_query, model='text-embedding-ada-002')
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)
使用嵌入的推荐
因为嵌入向量之间的较短距离表示较大的相似度,嵌入对推荐很有用。
下面,我们演示了一个基本的推荐器。它接受一系列字符串和一个“源”字符串,计算它们的嵌入,然后根据相似度对字符串进行排名,从最相似到最不相似。作为具体示例,下面链接的notebook将此函数应用于AG新闻数据集(下采样至2000条新闻文章描述)中,以返回与任何给定源文章最相似的前5篇文章。
def recommendations_from_strings(
strings: List[str],
index_of_source_string: int,
model="text-embedding-ada-002",
) -> 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
限制和风险
在某些情况下,我们的嵌入模型可能不可靠或具有社会风险,如果不采取缓解措施可能会造成伤害。
社会偏见
局限性:模型包含社会偏见,例如对某些群体的刻板印象或负面情绪。
我们通过运行SEAT(May等,2019)和Winogender(Rudinger等,2018)基准测试,发现了我们模型中的偏见证据。这两个基准测试共包含7个测试,用于测量模型在应用于性别化姓名、区域姓名和某些刻板印象时是否存在隐性偏见。
例如,我们发现与非裔美国人姓名相比,我们的模型更强烈地将欧裔美国人姓名与积极情感相关联;与黑人妇女相关的负面刻板印象也更强。
这些基准测试有几个局限:a)它们可能无法推广到您的特定用例,b)它们只测试了可能的社会偏见的一小部分。
我们建议针对您的特定用例运行测试。这些结果应被视为存在这种现象的证据,而不是您的用例的权威性表征。请参阅我们的使用政策以获取更多详细信息和指导。
如果您有任何疑问,请通过聊天联系我们的支持团队,我们很乐意提供建议。
对最近事件的“盲目”
局限性:模型缺乏2020年8月之后发生事件的知识。
我们的模型是在包含一些关于真实世界事件信息的数据集上训练的,时间截止到2020年8月。如果你依赖模型来表示最近的事件,那么它们可能表现不佳。
常见问题解答
在嵌入字符串之前,我如何判断它包含多少标记?
在Python中,您可以使用OpenAI的分词器 tiktoken
将字符串拆分为标记。
示例代码:
import tiktoken
def num_tokens_from_string(string: str, encoding_name: str) -> int:
"""返回文本字符串中的标记数。"""
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-ada-002
),使用cl100k_base
编码。
更多详细信息和示例代码在OpenAI Cookbook指南如何用tiktoken计算标记。
如何快速检索K个最近的嵌入向量?
对于大量向量的快速搜索,我们建议使用向量数据库。您可以在我们的GitHub Cookbook中找到与OpenAI API一起使用向量数据库的示例示例。
向量数据库选项包括:
- Chroma,开源嵌入存储
- Milvus,用于可扩展相似性搜索的向量数据库
- Pinecone,完全托管的向量数据库
- Qdrant,向量搜索引擎
- Redis 作为向量数据库
- Typesense,快速开源向量搜索
- Weaviate,开源向量搜索引擎
- Zilliz,基于Milvus的数据基础设施
我应该使用哪种距离函数?
我们推荐使用余弦相似度。距离函数的选择通常差异不大。
OpenAI嵌入经过正则化,长度为1,这意味着:
- 余弦相似度可以稍快地用点积计算
- 余弦相似度和欧几里得距离将产生相同的排序
我可以在线共享我的嵌入结果吗?
客户拥有来自我们模型的输入和输出,包括嵌入的情况。您有责任确保输入到我们API的内容不违反任何适用的法律或我们的服务条款。