Перейти к основному содержимому

Встраивания

Узнайте, как преобразовывать текст в числа, что открывает такие возможности, как поиск.

Обзор

text-embedding-3-small и text-embedding-3-large, наши новейшие и самые производительные модели встраивания, теперь доступны с более низкой стоимостью, высокой многоязычной производительностью и новыми параметрами для управления общим размером.

Что такое встраивания?

Текстовые встраивания OpenAI измеряют связанность строк текста. Встраивания обычно используются для:

  • Поиска (где результаты ранжируются по релевантности к строке запроса)
  • Кластеризации (где строки текста группируются по сходству)
  • Рекомендаций (где рекомендуются элементы с похожими строками текста)
  • Обнаружения аномалий (где выявляются аутсайдеры с низкой связанностью)
  • Измерения разнообразия (где анализируются распределения сходства)
  • Классификации (где строки текста классифицируются по их наиболее похожему ярлыку)

Встраивание представляет собой вектор (список) чисел с плавающей запятой. Расстояние между двумя векторами измеряет их связанность. Малые расстояния указывают на высокую связанность, а большие расстояния указывают на низкую связанность.

Посетите нашу страницу ценообразования, чтобы узнать о стоимости встраиваний. Запросы оплачиваются на основе количества токенов во входных данных.

Как получить встраивания

Чтобы получить встраивание, отправьте строку текста на конечную точку API встраиваний вместе с названием модели встраивания (например, text-embedding-3-small). Ответ будет содержать встраивание (список чисел с плавающей запятой), которое вы можете извлечь, сохранить в векторной базе данных и использовать для множества различных случаев использования:

Пример: получение встраиваний

curl https://api.openai.com/v1/embeddings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"input": "Ваш текст здесь",
"model": "text-embedding-3-small"
}'

Ответ будет содержать вектор встраивания вместе с некоторыми дополнительными метаданными.

Пример ответа со встраиванием

{
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [
-0.006929283495992422,
-0.005336422007530928,
... (опущено для краткости)
-4.547132266452536e-05,
-0.024047505110502243
],
}
],
"model": "text-embedding-3-small",
"usage": {
"prompt_tokens": 5,
"total_tokens": 5
}
}

По умолчанию длина вектора встраивания составляет 1536 для text-embedding-3-small или 3072 для text-embedding-3-large. Вы можете уменьшить размерность встраивания, передав параметр dimensions без потери его концептуальных свойств. Подробности о размерностях встраиваний приведены в разделе о случаях использования встраиваний.

Модели встраивания

OpenAI предлагает две мощные модели встраивания третьего поколения (обозначенные -3 в идентификаторе модели). Вы можете ознакомиться с подробностями в блоге об анонсе встраиваний v3.

Использование оплачивается за входной токен, ниже приведен пример стоимости страниц текста в долларах США (предполагается около 800 токенов на страницу):

МОДЕЛЬ~ СТРАНИЦ НА ДОЛЛАРПРОИЗВОДИТЕЛЬНОСТЬ НА MTEB ОЦЕНКЕМАКС. ВХОД
text-embedding-3-small62,50062.3%8191
text-embedding-3-large9,61564.6%8191
text-embedding-ada-00212,50061.0%8191

Случаи использования

Здесь мы показываем некоторые представительные случаи использования. Мы будем использовать набор данных отзывов о продуктах питания Amazon для следующих примеров.

Получение встраиваний

Набор данных содержит в общей сложности 568,454 отзывов о продуктах питания, оставленных пользователями Amazon до октября 2012 года. Для иллюстрации мы будем использовать подмножество из 1,000 самых последних отзывов. Отзывы на английском языке и, как правило, положительные или отрицательные. Каждый отзыв имеет ProductId, UserId, Score, заголовок отзыва (Summary) и текст отзыва (Text). Например:

PRODUCT IDUSER IDSCORESUMMARYTEXT
B001E4KFG0A3SGXH7AUHU8GW5Good Quality Dog FoodI have bought several of the Vitality canned...
B00813GRG4A1D87F6ZCVE5NK1Not as AdvertisedProduct arrived labeled as Jumbo Salted Peanut...

Мы объединим заголовок отзыва и текст отзыва в один общий текст. Модель закодирует этот общий текст и выдаст один вектор встраивания.

Get_embeddings_from_dataset.ipynb

from openai import OpenAI
client = OpenAI(
api_key = '$ROCKAPI_API_KEY',
base_url = 'https://api.rockapi.ru/openai/v1'
)

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)

Чтобы загрузить данные из сохраненного файла, вы можете запустить следующий код:

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, при этом оно по-прежнему превосходит несокращенное встраивание модели text-embedding-ada-002 размером 1536. Вы можете прочитать больше о том, как изменение размерности влияет на производительность в блоге о запуске встраиваний v3.

В общем случае, использование параметра dimensions при создании встраивания является рекомендуемым подходом. В некоторых случаях вам может потребоваться изменить размерность встраивания после его создания. При изменении размерности вручную необходимо убедиться в нормализации размерностей встраивания, как показано ниже.

from openai import OpenAI
import numpy as np

client = OpenAI(
api_key = '$ROCKAPI_API_KEY',
base_url = 'https://api.rockapi.ru/openai/v1'
)

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 и указать значение 1024 для параметра API dimensions, что сократит встраивание с 3072 размерностей, компенсируя некоторую точность в обмен на меньший размер вектора.

Ответы на вопросы с использованием поиска на основе встраиваний

Существуют многие распространенные случаи, когда модель не обучена на данных, содержащих ключевые факты и информацию, которые вы хотите сделать доступными при генерации ответов на запросы пользователей. Один из способов решения этой проблемы, показанный ниже, заключается в добавлении дополнительной информации в контекстное окно модели. Это эффективно во многих случаях использования, но приводит к более высоким затратам на токены. В этом блокноте мы исследуем компромисс между этим подходом и поиском на основе встраиваний.

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)

Поиск текста с использованием встраиваний

Для извлечения наиболее релевантных документов мы используем косинусное сходство между векторами встраивания запроса и каждого документа и возвращаем документы с наивысшими оценками.

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.

Для выполнения поиска кода мы встраиваем запрос на естественном языке, используя ту же модель. Затем мы вычисляем косинусное сходство между встраиванием запроса и каждым из встраиваний функций. Наиболее релевантные результаты имеют наибольшее косинусное сходство.

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)

Рекомендации с использованием встраиваний

Поскольку более короткие расстояния между векторами встраивания представляют большее сходство, встраивания могут быть полезны для рекомендаций.

Ниже мы иллюстрируем базовый рекомендатель. Он принимает список строк и одну 'исходную' строку, вычисляет их встраивания, а затем возвращает ранжирование строк от наиболее похожих до наименее похожих. В качестве конкретного примера приведенный ниже блокнот применяет версию этой функции к набору данных AG news (выборка до 2000 описаний новостей), чтобы вернуть 5 наиболее похожих статей на любую данную исходную статью.

def recommendations_from_strings(
strings: List[str],
index_of_source_string: int,
model="text-embedding-3-small",
) -> List[int]:
"""Вернуть ближайших соседей данной строки."""

# получить встраивания для всех строк
embeddings = [embedding_from_string(string, model=model) for string in strings]

# получить встраивание исходной строки
query_embedding = embeddings[index_of_source_string]

# получить расстояния между встраиванием запроса и другими встраиваниями (функция из embeddings_utils.py)
distances = distances_from_embeddings(query_embedding, embeddings, distance_metric="cosine")

# получить индексы ближайших соседей (функция из embeddings_utils.py)
indices_of_nearest_neighbors = indices_of_nearest_neighbors_from_distances(distances)
return indices_of_nearest_neighbors

Визуализация данных в 2D

Размер встраиваний варьируется в зависимости от сложности подлежащей модели. Для визуализации этих данных высокой размерности мы используем алгоритм t-SNE для преобразования данных в два измерения.

Мы раскрасим отдельные отзывы в зависимости от звездного рейтинга, который дал рецензент:

  • 1 звезда: красный
  • 2 звезды: темно-оранжевый
  • 3 звезды: золотой
  • 4 звезды: бирюзовый
  • 5 звезд: темно-зеленый

Amazon рейтинги визуализированы в языке с использованием t-SNE Визуализация, похоже, создала примерно 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", "turquoise", "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 рейтинги визуализированы в языке с использованием t-SNE")

Встраивание как кодировщик текстовых признаков для ML алгоритмов

Встраивание может быть использовано в качестве общего кодировщика свободного текста в модели машинного обучения. Включение встраиваний улучшит производительность любой модели машинного обучения, если некоторые из релевантных входов являются свободным текстом. Встраивание также может быть использовано в качестве кодировщика категориальных признаков в 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 и 5, и разрешаем алгоритму прогнозировать любое значение с плавающей запятой. Алгоритм минимизирует расстояние между прогнозируемым значением и истинной оценкой и достигает средней абсолютной ошибки в 0.39, что означает, что в среднем прогноз ошибается менее чем на ползвезды.

from sklearn.ensemble import RandomForestRegressor

rfr = RandomForestRegressor(n_estimаторов=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_estimаторов=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'

Получение встраиваний пользователей и продуктов для рекомендаций при отсутствии данных

Мы можем получить встраивание пользователя, усредняя все его отзывы. Аналогично, мы можем получить встраивание продукта, усредняя все отзывы о нем. Чтобы продемонстрировать полезность этого подхода, мы используем подмножество из 50k отзывов, чтобы покрыть больше отзывов на пользователя и на продукт.

Мы оцениваем полезность этих встраиваний на отдельной тестовой выборке, где мы строим график сходства встраивания пользователя и продукта в зависимости от рейтинга. Интересно, что на основе этого подхода мы можем лучше предсказать, понравится ли пользователю продукт, даже до того, как он его получит.

Boxplot grouped by Score
user_embeddings = df.groupby('UserId').ada_embedding.apply(np.mean)
prod_embeddings = df.groupby('ProductId').ada_embedding.apply(np.mean)

Кластеризация

Кластеризация является одним из способов анализа большого объема текстовых данных. Встраивания полезны для этой задачи, поскольку они предоставляют семантически значимые векторные представления каждого текста. Таким образом, в несупервизорском режиме кластеризация обнаруживает скрытые группировки в нашем наборе данных.

В этом примере мы обнаруживаем четыре различных кластера: один фокусируется на кормах для собак, один на отрицательных отзывах и два на положительных отзывах.

Clusters identified visualized in language 2d using t-SNE
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.

Пример кода:

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-3-small, используйте кодировку cl100k_base.

Подробнее и пример кода можно найти в руководстве OpenAI Cookbook как подсчитывать токены с tiktoken.

Как быстро найти K ближайших векторных встраиваний?

Для быстрого поиска по многим векторам мы рекомендуем использовать векторную базу данных. Примеры работы с векторными базами данных и API OpenAI можно найти в нашем Cookbook на GitHub.

Какую функцию расстояния следует использовать?

Мы рекомендуем косинусное сходство. Выбор функции расстояния обычно не имеет большого значения.

Встраивания OpenAI нормализованы до длины 1, что означает, что:

  • Косинусное сходство можно вычислить немного быстрее, используя только скалярное произведение
  • Косинусное сходство и Евклидово расстояние дадут идентичные ранжирования

Могу ли я делиться своими встраиваниями онлайн?

Да, клиенты владеют своими входными и выходными данными из наших моделей, включая в случае встраиваний. Вы несете ответственность за то, чтобы контент, который вы вводите в наш API, не нарушал никаких применимых законов или наших Условий использования.

Знают ли модели встраивания V3 о недавних событиях?

Нет, модели text-embedding-3-large и text-embedding-3-small не знают о событиях, произошедших после сентября 2021 года. Это, как правило, не является большой проблемой для моделей генерации текста, но в некоторых крайних случаях это может снизить производительность.