嵌入open in new window

概述

什么是嵌入?

OpenAI的文本嵌入测量文本字符串的关联性。嵌入通常用于:

  • 搜索(根据与查询字符串的相关性对结果进行排名)
  • 聚类(按相似性对文本字符串进行分组)
  • 推荐(具有相关文本字符串的项目被推荐)。
  • 异常检测(识别关联度小的异常值)。
  • 多样性测量(对相似性分布进行分析)
  • 分类(文本串按其最相似的标签进行分类)。

嵌入是一个浮点数字的向量(列表)。两个向量之间的距离衡量它们的关联性。小距离表示高关联度,大距离表示低关联度。

访问我们的定价页面open in new window,了解嵌入的定价。请求是根据发送的输入open in new window中的tokenopen in new window数量计费的。

如何获得嵌入

要获得一个嵌入,将你的文本字符串和选择的嵌入模型ID(例如,text-embedding-ada-002)一起发送到嵌入API的端点open in new window。响应将包含一个嵌入,你可以提取、保存和使用。

请求示例:

cUrl:

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-ada-002"
  }'
1
2
3
4
5
6
7

Python:

response = openai.Embedding.create(
    input="Your text string goes here",
    model="text-embedding-ada-002"
)
embeddings = response['data'][0]['embedding']
1
2
3
4
5

示例的响应:

{
  "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
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

OpenAI Cookbookopen in new window中看到更多的Python代码例子。

在使用OpenAI嵌入时,请牢记其局限性和风险

嵌入模型

OpenAI提供一个第二代嵌入模型(模型ID中以-002表示)和16个第一代模型(模型ID中以-001表示)。

我们建议在几乎所有的用例中使用text-embedding-ada-002。它更好,更便宜,而且使用起来更简单。阅读博文公告open in new window

使用费按输入token定价,每1000个token0.0004美元,或每美元约3000页(假设每页约800个token):

第二代模型

第一代模型(不推荐)

所有第一代模型(以-001结尾的模型)使用GPT-3标记器open in new window,最大输入量为2046个标记。

第一代嵌入是由五个不同的模型系列产生的,它们针对三个不同的任务进行了调整:文本搜索、文本相似性和代码搜索。搜索模型成对出现:一个用于短的查询,一个用于长的文档。每个系列包括多达四个质量和速度方面的模型:

Davinci是能力最强的,但比其他型号慢且贵。Ada的能力最差,但速度明显更快,价格也更便宜。

相似性嵌入

相似性模型最善于捕捉文本片段之间的语义相似性。

USE CASES:聚类、回归、异常检测、可视化

文本搜索嵌入

文本搜索模型有助于衡量哪些长文档与短搜索查询最相关。使用两个模型:一个用于嵌入搜索查询,一个用于嵌入要排名的文档。与查询嵌入最接近的文档嵌入应该是最相关的。

USE CASES:搜索,上下文相关性,信息检索

代码搜索嵌入

与搜索嵌入类似,有两种类型:一种用于嵌入自然语言搜索查询,一种用于嵌入要检索的代码片段。

USE CASES:代码搜索和相关性

TIP

对于-001文本嵌入(不是-002,也不是代码嵌入),我们建议将输入中的换行符(\n)替换为单个空格,因为我们看到当换行符出现时,结果更糟糕。

使用案例

这里我们展示一些有代表性的用例。我们将在下面的例子中使用 亚马逊精细食品评论数据集open in new window

获得嵌入

该数据集包含了截至2012年10月亚马逊用户留下的总共568,454条食品评论。我们将使用1,000条最新评论的子集来说明问题。这些评论是英文的,倾向于正面或负面。每条评论都有一个ProductId, UserId, Score, 评论标题(摘要)和评论正文(文本)。比如说:

我们将把评论摘要和评论文本合并为一个单一的合并文本。该模型将对这个合并文本进行编码,并输出一个单一的矢量嵌入。

Obtain_dataset.ipynbopen in new window

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)
1
2
3
4
5
6

要从保存的文件中加载数据,你可以运行以下程序:

import pandas as pd

df = pd.read_csv('output/embedded_1k_reviews.csv')
df['ada_embedding'] = df.ada_embedding.apply(eval).apply(np.array)
1
2
3
4
二维的数据可视化

Visualizing_embeddings_in_2D.ipynbopen in new window

嵌入的大小随基础模型的复杂性而变化。为了将这种高维数据可视化,我们使用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()

# 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")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
嵌入作为ML算法的文本特征编码器

Regression_using_embeddings.ipynbopen in new window

嵌入可以作为机器学习模型中的一个普通的自由文本特征编码器。如果一些相关的输入是自由文本,那么纳入嵌入将提高任何机器学习模型的性能。嵌入也可以作为ML模型中的一个分类特征编码器。如果分类变量的名称是有意义的,且数量众多,如工作职位,这将增加最大价值。对于这项任务,相似性嵌入通常比搜索嵌入表现得更好。

我们观察到,一般来说,嵌入表示是非常丰富和信息密集的。例如,使用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
2
3
4
5
6
7
8

使用嵌入特征进行回归

嵌入提出了一种预测数值的优雅方式。在这个例子中,我们根据评论者的评论文本来预测他们的星级评价。由于嵌入所包含的语义信息很高,即使评论很少,预测结果也很好。

我们假设分数是1到5之间的连续变量,并允许算法预测任何浮点值。ML算法最小化了预测值与真实分数的距离,并实现了0.39的平均绝对误差,这意味着平均而言,预测的偏差不到半颗星。

from sklearn.ensemble import RandomForestRegressor

rfr = RandomForestRegressor(n_estimators=100)
rfr.fit(X_train, y_train)
preds = rfr.predict(X_test)
1
2
3
4
5
使用嵌入特征进行分类

Classification_using_embeddings.ipynbopen in new window

这一次,我们不是让算法预测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)
1
2
3
4
5
6
Zero-shot分类

Zero-shot_classification_with_embeddings.ipynbopen in new window

我们可以在没有任何标记的训练数据的情况下使用嵌入进行零点分类。对于每一个类,我们都嵌入了类的名称或对该类的简短描述。为了对一些新的文本进行零点分类,我们将其嵌入与所有类别的嵌入进行比较,并预测具有最高相似性的类别。

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'
1
2
3
4
5
6
7
8
9
10
11
12
获得用户和产品嵌入以进行冷启动推荐

User_and_product_embeddings.ipynbopen in new window

我们可以通过对用户的所有评论进行平均化来获得用户的嵌入。同样地,我们可以通过对该产品的所有评论进行平均化来获得产品嵌入。为了展示这种方法的有用性,我们使用了一个5万条评论的子集,以涵盖每个用户和每个产品的更多评论。

我们在一个单独的测试集上评估这些嵌入的有用性,在那里我们绘制了用户和产品嵌入的相似性作为评级的函数。有趣的是,基于这种方法,甚至在用户收到产品之前,我们就可以比随机预测他们是否会喜欢这个产品。

聚类

Clustering.ipynbopen in new window

聚类是理解大量文本数据的一种方式。嵌入对这项任务很有用,因为它们为每个文本提供了有语义的向量表示。因此,在无监督的情况下,聚类将发现我们数据集中的隐藏分组。

在这个例子中,我们发现了四个不同的聚类:一个侧重于狗粮,一个侧重于负面评论,两个侧重于正面评论。

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_
1
2
3
4
5
6
7
8
9
使用嵌入的文本搜索

Semantic_text_search_using_embeddings.ipynbopen in new window

为了检索最相关的文件,我们使用查询和每个文件的嵌入向量之间的余弦相似度,并返回得分最高的文件。

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)
1
2
3
4
5
6
7
8
9
使用嵌入的代码搜索

Code_search.ipynbopen in new window

代码搜索的工作原理类似于基于嵌入的文本搜索。我们提供了一种方法,从给定资源库的所有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)
1
2
3
4
5
6
7
8
9
10
11
使用嵌入推荐器

Recommendation_using_embeddings.ipynbopen in new window

因为嵌入向量之间较短的距离代表了更大的相似性,所以嵌入可以对推荐有帮助。

下面,我们说明了一个基本的推荐器。它接收一个字符串列表和一个 "源 "字符串,计算它们的嵌入,然后返回一个字符串的排名,从最相似到最不相似进行排序。作为一个具体的例子,下面的链接笔记本将这个函数的一个版本应用于AG新闻数据集open in new window(取样到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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

限制和风险

我们的嵌入模型在某些情况下可能不可靠或构成社会风险,在没有缓解措施的情况下可能造成伤害。

社会偏见

TIP

限制性:这些模型对社会偏见进行了编码,例如通过陈规定型观念或对某些群体的负面情绪。

我们通过运行SEAT(May等人,2019年open in new window)和Winogender(Rudinger等人,2018年open in new window)基准,发现了我们模型中存在偏见的证据。这些基准由7项测试组成,衡量模型在应用于性别名称、地区名称和一些刻板印象时是否包含隐性偏见。

例如,我们发现我们的模型更强烈地将(a)欧洲裔美国人的名字与积极的情绪联系起来,与非洲裔美国人的名字相比,以及(b)负面的刻板印象与黑人妇女联系起来。

这些基准有几个方面的局限性:(a) 它们可能无法概括你的特定用例,(b) 它们只测试了可能的社会偏见的一小部分。

这些测试是初步的,我们建议为你的特定用例进行测试。 这些结果应被视为存在该现象的证据,而不是对你的用例的明确描述。请参阅我们的使用政策open in new window以了解更多细节和指导。

如果你有任何问题,请通过聊天联系我们的支持团队open in new window;我们很乐意提供这方面的建议。

对最近的事件视而不见

TIP

限制性:模型缺乏对2020年8月以后发生的事件的了解。

我们的模型是在包含一些直到2020年8月的真实世界事件信息的数据集上训练的。如果你依赖代表最近事件的模型,那么它们可能不会有好的表现。

常见问题

在嵌入一个字符串之前,我怎样才能知道它有多少个token?

在Python中,你可以用OpenAI的tokenizer tiktokenopen in new window将一个字符串分割成token。

代码示例:

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")
1
2
3
4
5
6
7
8
9

对于第二代嵌入模型如text-embedding-ada-002,使用cl100k_base编码。

更多细节和示例代码见OpenAI Cookbook指南如何用tiktoken计算tokensopen in new window

怎样才能快速检索出K个最近的嵌入向量?

为了快速搜索许多矢量,我们建议使用一个矢量数据库。你可以在GitHub上的Cookbookopen in new window中找到使用向量数据库和OpenAI API的例子。

矢量数据库选项包括:

我应该使用哪个距离函数?

我们推荐余弦相似性open in new window。距离函数的选择通常并不太重要。

OpenAI的嵌入被规范化为长度1,这意味着:

  • 余弦相似度可以用点积来计算,速度稍快。
  • 余弦相似性和欧几里得距离会产生相同的排名。

我可以在网上分享我的嵌入物吗?

客户拥有他们从我们的模型中的输入和输出,包括在嵌入的情况下。你有责任确保你输入到我们的API的内容不违反任何适用法律或我们的使用条款open in new window

Last Updated:
Contributors: lanheixingkong