Всем привет! Меня зовут Максимов Максим, я — NLP‑инженер в компании red_mad_robot. В этой статье я хотел бы рассказать о подходах в работе с векторными представлениями данных, а именно — эмбеддингами.
Сегодня в меню:
Что такое эмбеддинг? Освежим свои знания, и вспомним что это такое формально.
Из чего можно получить эмбеддинги? Рассмотрим популярные форматы данных, которые мы можем представить в векторном виде. Также рассмотрим способы, которыми мы можем преобразовать эти данные в эмбеддинги.
Как использовать эмбеддинги в работе? Приведем различные примеры использование векторных представлений, которые могут быть полезны в работе
Все примеры в данной статье будут приведены с использованием языка Python.
Что такое эмбеддинг?
Если начать с простого, то эмбеддинг — это массив чисел, который получается преобразованием какого‑то объекта (например текста или картинки).

Взглянем на эмбеддинги с математической точки зрения. Для начала вспомним о таком понятии как «система координат», представляющая собой перпендикулярные числовые прямые. Рассмотрим двумерную систему координат, как ее еще называют, декартову систему координат:

Введем такое понятие, как векторное пространство: это математическая структура, состоящая из векторов.
Векторы (или эмбеддинги) можно понимать как точки в некотором пространстве. В нашем случае, это будут точки в двумерном пространстве. Эти вектора будут иметь 2 координаты [x, y], то есть иметь размерность 2.
Как можно заметить, размерность вектора определяется количеством его координат. В реальности, работая с эмбеддингами, вектора могут иметь размерность и 512, 1024 и более.
Представим, что у нас имеется 2 объекта (например текста). После определенного преобразования получаем 2 двумерных вектора: [1, 2] и [5, 5] (вектора v1 и v2 соответственно). Эти вектора мы можем разместить в нашей системе координат, поместив начала этих векторов в точку [0, 0].

Если обобщить операцию, которую мы сделали с объектами, то можно сказать, что мы перевели их (наш текст) в некое векторное пространство. В этом пространстве мы можем выполнять различные операции с векторами: сложение, умножение на число, вычисление расстояния между ними, и другие.
Нам будет интересна операция вычисления расстояния. Эта операция производится при помощи различных формул. Приведем наиболее популярные из них:
Евклидово расстояние
Манхэттенское расстояние
Косинусная близость
Косинусное расстояние
Каждая из них имеет свои плюсы и минусы относительно их сравнения. Например, минусом евклидовой и манхэттенской расстояний является то, что если все координаты двух векторов практически одинаковы, кроме одной координаты, расстояние между такими векторами может быть большим. То есть учитывается координатное расстояние.
Здесь как раз таки приходит на помощь косинусное расстояние, которое рассчитывает расстояние между векторами, с точки зрения их направления. То есть, если они направлены в одну сторону, но какая‑то координата сильно отличается, расстояние все равно будет относительно близким.
Сравним евклидову и косинусную метрики для наших векторов v1 = [1, 2] и v2 = [5, 5]:
Евклидово расстояние:

Косинусное расстояние

Эти операции могут быть представлены в Python:
from sklearn.metrics.pairwise import cosine_distances, euclidean_distances
v1 = [[1, 2]]
v2 = [[5, 5]]
print("euclidean_distances", *euclidean_distances(v1, v2)[0])
print("cosine_distances", *cosine_distances(v1, v2)[0])
# euclidean_distances 5.0
# cosine_distances 0.05131670194948634
Несмотря на различный масштаб расстояний, по картинкам можно заметить, что евклидово расстояние дало нам большее расстояние, чем косинусное. Но не стоит забывать, что выбор расстояний зависит от конкретной задачи.
Отлично, мы вспомнили об определении эмбеддингов, и об операции подсчета расстояние между ними.
Из чего можно получить эмбеддинги?
Далее рассмотрим, какие объекты мы можем преобразовывать в эмбеддинги.
Текст
Для преобразование текста в эмбеддинги можно использовать разные подходы.
Например, это могут быть алгоритмы, учитывающие частотность слов в тексте. Примерами таких алгоритмов являются Bag of Word (BOW) и TF‑IDF. Рассмотрим их на примере с использованием библиотеки scikit‑learn.
Алгоритм BOW можно визуализировать следующим образом:

Собираем словарь из всех имеющихся слов в тексте. Для каждого документа подсчитываем частоту содержащихся слов из словаря. Для тех слов, которые не были в документе, ставим нули. Таким образом получаем вектор, размерностью равный размеру нашего словаря.
Сразу можно увидеть минус данного подхода, что чем больше количество документов, тем больше будет размерность вашего векторного пространства. А также минус в том, что полученные вектора будут иметь большое количество нулей (они будут разреженными).
Код на Python для BOW выглядит следующим образом:
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
# Документы для анализа
documents = [
"Кот сидел на дереве",
"Петр сидел на стуле",
"Воробушек улетел"
]
# Создаем матрицу Bag of Words
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(documents)
# Формируем таблицу
bow_df = pd.DataFrame(X.toarray(),
columns=vectorizer.get_feature_names_out(),
index=[f"{i+1}" for i in range(len(documents))])
print("BOW матрица:")
print(bow_df.round(3))
# BOW матрица:
# воробушек дереве кот на петр сидел стуле улетел
# 1 0 1 1 1 0 1 0 0
# 2 0 0 0 1 1 1 1 0
# 3 1 0 0 0 0 0 0 1
Алгоритм TF‑IDF — это статистическая мера, используемая для оценки важности слова в документе относительно коллекции документов (корпуса).

Как это работает:
TF (Term Frequency) — считает, как часто слово встречается в одном конкретном документе. Чем чаще, тем выше вес.
IDF (Inverse Document Frequency) — измеряет, насколько уникально слово во всём корпусе. Чем в большем количестве документов встречается слово (например, «и», «в»), тем меньше его вес.
Итоговый вес равен TF‑IDF = TF * IDF
Проще говоря: TF‑IDF увеличивает вес слов, которые часто встречаются в конкретном документе, но редко — в остальных документах корпуса. Это помогает отфильтровать общеупотребительные слова и выделить действительно значимые термины.
Код на Python выглядит следующим образом:
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
# Документы для анализа
documents = [
"Кот сидел на дереве",
"Петр сидел на стуле",
"Воробушек улетел"
]
# Создаем матрицу TF-IDF
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(documents)
# Формируем таблицу
tfidf_df = pd.DataFrame(X.toarray(),
columns=vectorizer.get_feature_names_out(),
index=[f"{i+1}" for i in range(len(documents))])
print("TF-IDF матрица:")
print(tfidf_df.round(3))
# TF-IDF матрица:
# воробушек дереве кот на петр сидел стуле улетел
# 1 0.000 0.563 0.563 0.428 0.000 0.428 0.000 0.000
# 2 0.000 0.000 0.000 0.428 0.563 0.428 0.563 0.000
# 3 0.707 0.000 0.000 0.000 0.000 0.000 0.000 0.707
Также, для получения векторного представления текста могут использовать нейронные сети, которые обучались для задач работы с текстовыми данными (чаще всего это нейронные сети с архитектурой трансформер). Как они работают, это отдельная тема для разговора. Вкратце можно сказать, что они переводят естественный язык в векторное пространство

Пример получения эмбеддингов из текста, используя нейронную сеть на Python:
Изображения
Для перевода изображений в эмбеддинги можно использовать сверточные нейронные сети.
Как это работает?

Специфика сверточных нейронных сетей такова, что они имеют сверточные слои, которые, если сказать по простому, сжимают наше изображение. Таким образом, пропустив через несколько сверточных слоев наше изображение, мы можем сжать его до одномерного вектора.
Давайте рассмотрим на примере, воспользовавшись библиотекой mediapipe:
import mediapipe as mp
BaseOptions = mp.tasks.BaseOptions
ImageEmbedder = mp.tasks.vision.ImageEmbedder
ImageEmbedderOptions = mp.tasks.vision.ImageEmbedderOptions
VisionRunningMode = mp.tasks.vision.RunningMode
options = ImageEmbedderOptions(base_options=BaseOptions(model_asset_path=r'..\mobilenet_v3_small_075_224_embedder.tflite', running_mode=VisionRunningMode.IMAGE)
with ImageEmbedder.create_from_options(options) as embedder:
# Загружаем изображение
mp_image = mp.Image.create_from_file('./bag_of_words.png')
# Пропускаем изображение через сверточную сеть и получаем эмбеддинг
embedding_result = embedder.embed(mp_image)
print(embedding_result.embeddings[0].embedding)
# [ 0.25329596 0.04558615 0.25365654 ... 1.03928709 1.01940656 -0.35320714]
В качестве примера мы использовали сверточную нейронную сеть MobileNet.
Аудио
Для перевода аудио в эмбеддинги требуются дополнительные промежуточные преобразования. Например, мы можем получить спектрограмму нашего аудио, визуализировать ее, и уже после использовать подход получения эмббединга из изображения. Либо же, можно использовать специальные нейронные сети для работы с аудио.

Рассмотрим пример получения эмбеддинга из аудио, используя специальную для этого нейронную сеть из библиотеки pyannote:
Мультимодальные данные
В реальном мире данные могут представлять комбинацию различных типов информации. Например, это может пост в социальной сети, который содержит картинку и описание к ней. Либо же это может быть картинка с аудио. Назовем такие данные мультимодальными.
Как можно догадаться, для получения эмбеддинга элемента мультимодальных данных может использоваться комбинация преобразований для каждого формата данных элемента. А именно, мы можем получать отдельно эмбеддинги для каждой модальности, и после объединять эти вектора.

Рассмотрим пример на Python:
from sentence_transformers import SentenceTransformer
import mediapipe as mp
import numpy as np
# Функция для получения эмбеддинга текста
def get_text_embedding(text):
model = SentenceTransformer("all-MiniLM-L6-v2")
sentences = [
text,
]
text_embeddings = model.encode(sentences)
return text_embeddings[0]
# Функция для получения эмбеддинга изображения
def get_image_embedding(image_path):
BaseOptions = mp.tasks.BaseOptions
ImageEmbedder = mp.tasks.vision.ImageEmbedder
ImageEmbedderOptions = mp.tasks.vision.ImageEmbedderOptions
VisionRunningMode = mp.tasks.vision.RunningMode
options = ImageEmbedderOptions(
base_options=BaseOptions(model_asset_path=r'mobilenet_v3_small_075_224_embedder.tflite'),
running_mode=VisionRunningMode.IMAGE)
with ImageEmbedder.create_from_options(options) as embedder:
# Load the input image from an image file.
mp_image = mp.Image.create_from_file(image_path)
# Perform image embedding on the provided single image.
image_embedding = embedder.embed(mp_image).embeddings[0].embedding
return image_embedding
# Получаем эмбеддинги для каждой модальности
image_embedding = get_image_embedding('./bag_of_words.png')
text_embeddings = get_text_embedding("Мама мыла раму")
# Объединяем эмбеддинги
res_embedding = np.concatenate((image_embedding, text_embeddings), axis=0)
print(image_embedding.shape, text_embeddings.shape)
print(res_embedding.shape)
# (1024,) (384,)
# (1408,)
Как использовать эмбеддинги в работе?
Мы с вами разобрались, что такое эмбеддинги и из каких объектов их можно получать, теперь давайте рассмотрим на примерах, какие задачи мы можем решать, используя векторное представление объектов.
Примечание:
Во всех примерах были опущены подходы по очистке и подготовке данных. Здесь я хотел показать, какие задачи можно решать при помощи эмбеддингов.
Классификация текстов
Возьмем для примера открытый датасет для классификации писем. Метка «spam» означает что письмо со спамом, «not spam» обычное письмо.
import pandas as pd
from xgboost import XGBClassifier
from sentence_transformers import SentenceTransformer
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
# Загрузка данных
df = pd.read_csv("./email_spam.csv")
df['type'] = df['type'].replace({"not spam": 0, "spam": 1}) # приводим метки класса к числовым
df.head()

Векторизуем письма, используя метод векторизации текста, который был описан выше.
def get_text_embedding(texts):
# 1. Загрузка предобученной модели из SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
# 2. Вычисление эмбеддинга для текста
text_embeddings = model.encode(texts)
return text_embeddings
embeddings = get_text_embedding(df['text'].to_list())
df['embedding_text'] = list(embeddings)
df.head()

Получив векторное представление каждого письма и имея метки классов (спам\не спам) для каждого письма, можем обучать модель для классификации текста.
X_train, X_test, y_train, y_test = train_test_split(X, df['type'],
test_size=0.1,
stratify=df['type'])
model = XGBClassifier()
model.fit(X_train, y_train)
После обучения получаем модель, которая классифицирует наши текста:
df_predict = pd.DataFrame({"real": y_test, "predict": model.predict(X_test)}).head()
df_predict.head()

Таким образом было продемонстрировано, как можно решать задачу классификации текста, используя эмбеддинги.
Эмбеддинги для поиска по базе знаний
С помощью эмбеддингов и подхода RAG (Retrieval Augmented Generation) вы можете осуществлять поиск необходимой информации и генерировать на основе них ответы на вопросы.
Приведем пример на Python. Возьмем научную статью, и попробуем осуществить по ней поиск информации, используя эмбеддинги.
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from sentence_transformers import SentenceTransformer
import faiss
# Функция для считывания текста с файла и разделения его на фрагменты
def preprocess_data(file_path):
loader = PyPDFLoader(file_path)
pages = []
for page in loader.load():
pages.append(page)
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
all_splits = text_splitter.split_documents(pages)
chunks = [chunk.page_content for chunk in all_splits]
return chunks
# Функция для получения эмбеддингов фрагментов
def get_embeddings(model, chunks):
embeddings = model.encode(texts)
return embeddings
# Функция для поиска релевантного фрагмента к запросу пользователя
def get_relevant_chunk(query, index, model, chunks, top_k=1):
embs = model.encode([query])
D, I = index.search(x=embs, k=top_k)
relevant_chunk = texts[I[0][0]]
return relevant_chunk
file_path = "./data/3696630.3728724.pdf"
chunks = preprocess_data(file_path)
embeddings = get_embeddings(model, chunks)
# Сбор полученных эмбеддингов в векторную базу FAISS
index = faiss.IndexFlatL2(embeddings.shape[1])
index.add(embeddings)
То что мы сделали можно визуализировать так:

Далее, по запросу пользователя мы можем найти наиболее релевантный контекст, который уже может использовать для генерации полного ответа при помощи LLM:
query = """Software engineering involves the creation and management of nu
merous artifacts across the entire SDLC. From requirements speci
fications to design documents and test plans to safety compliance
evidence, these artifacts serve as critical references throughout the
SDLC. Many types of data in the artifacts produced throughout this
process can be considered as documents due to the textual nature of
the information. """
result = get_relevant_chunk(query, index, model, chunks)
print("Relevalnt chunk:\n", result)

Если описать простыми словами то, как происходит поиск релевантного контекста, можно сказать, что мы вычисляем векторное расстояние (о котором говорилось в начале статьи) между эмбеддингом запроса пользователя, и каждым эмбеддингом контекста в нашей векторной базе данных. После вычисления этих расстояниях, мы выбираем тот контекст, который имеет наименьшее векторное расстояние с запросом пользователя.
Кластеризация объектов
Рассмотрим задачу кластеризации объектов в рамках использования эмбеддингов текста. Если простыми словами, то кластеризация это процесс разбиения набора объектов на группы (кластеры).
В качестве примера возьмем открытый датасет для классификации текстов.
Мы получим эмбеддинг каждого смс, создадим кластеры этих эмббедингов, а после визуализируем их на графике.
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sentence_transformers import SentenceTransformer
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
# Загрузка данных
df = pd.read_csv("train.csv")
df.head()

Датасет представляет собой набор смс, которые разделяются на спам и не спам.
Векторизуем все текста.
documents = df['sms'].to_list()
# Загрузка модели
model = SentenceTransformer("all-MiniLM-L6-v2")
# Векторизация корпуса текста
X = model.encode(documents)
Далее при помощи алгоритма кластеризации KMeans попытаемся выделить 2 кластера (спам и не спам).
num_clusters = 2
kmeans = KMeans(n_clusters=num_clusters, random_state=42)
kmeans.fit(X)
Теперь визуализируем кластеры, которые мы смогли выделить.
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
# Reduce dimensionality to 2D using PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
# Create the plot
plt.figure(figsize=(10, 6))
scatter = plt.scatter(X_pca[:, 0], X_pca[:, 1], c=df['label'],
cmap='viridis', alpha=0.7, s=50)
plt.grid(True, alpha=0.3)
plt.show()

Можно заметить, что наше векторной пространство достаточно хорошо разделяется на 2 кластера. Теперь давайте визуализируем реальные метки класса для каждого элемента.
plt.figure(figsize=(10, 6))
scatter = plt.scatter(X_pca[:, 0], X_pca[:, 1], c=kmeans.labels_,
cmap='viridis', alpha=0.7, s=50)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Можем увидеть, что реальные метки классов очень четко разделяются на два кластера. Таким образом, используя векторное представления текстов, мы можем строить модели, которые будут классифицировать текст на спам и не спам.
Заключение
В этой статье было рассказано о том, что такое эмбеддинги с математической точки зрения, рассмотрели примеры объектов, из которых мы можем получить эмбеддинги, а также несколько примеров практического их использования.
Подписывайтесь на мой телеграмм канал, в котором я также буду рассказывать об IT и AI технологиях.
Alexey_Yastrebov
В этой статье слово "эмбеддинги" встречается 44 раза, а как они получаются, так и не объяснено. Зато в старом цикле статей "Автоэнкодеры в Keras" можно получить исчерпывающее понимание математики латентных пространств, эмбеддингов и Metric Learning, хотя там и слова-то такого нет. Так что, кто действительно хочет разобраться - ищите те статьи. Они всё еще лучшие.