Всем привет! Меня зовут Максимов Максим, я — NLP‑инженер в компании red_mad_robot. В этой статье я хотел бы рассказать о подходах в работе с векторными представлениями данных, а именно — эмбеддингами.

Сегодня в меню:

  1. Что такое эмбеддинг? Освежим свои знания, и вспомним что это такое формально.

  2. Из чего можно получить эмбеддинги? Рассмотрим популярные форматы данных, которые мы можем представить в векторном виде. Также рассмотрим способы, которыми мы можем преобразовать эти данные в эмбеддинги.

  3. Как использовать эмбеддинги в работе? Приведем различные примеры использование векторных представлений, которые могут быть полезны в работе

Все примеры в данной статье будут приведены с использованием языка Python.

Что такое эмбеддинг?

Если начать с простого, то эмбеддинг — это массив чисел, который получается преобразованием какого‑то объекта (например текста или картинки).

Эмбеддинги  - векторное представление объектов
Эмбеддинги — векторное представление объектов

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

Координатная плоскость
Координатная плоскость

Введем такое понятие, как векторное пространство: это математическая структура, состоящая из векторов.

Векторы (или эмбеддинги) можно понимать как точки в некотором пространстве. В нашем случае, это будут точки в двумерном пространстве. Эти вектора будут иметь 2 координаты [x, y], то есть иметь размерность 2.

Как можно заметить, размерность вектора определяется количеством его координат. В реальности, работая с эмбеддингами, вектора могут иметь размерность и 512, 1024 и более.

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

Два вектора в двумерном пространстве
Два вектора в двумерном пространстве

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

Нам будет интересна операция вычисления расстояния. Эта операция производится при помощи различных формул. Приведем наиболее популярные из них:

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

d(x, y) = \sqrt{\sum_{i=1}^{n} (x_i - y_i)^2}

Манхэттенское расстояние

d(x, y) = \sqrt{\sum_{i=1}^{n} |x_i - y_i|}

Косинусная близость

\begin{equation*} \begin{cases}     \cos(x, y) = \dfrac{x \cdot y}{\|x\| \cdot \|y\|}, & \\     & \\     x \cdot y = \sum_i x_i y_i, \quad \|x\| = \sqrt{x \cdot x}. \end{cases} \end{equation*}

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

d(x, y) = 1- \cos(x, y)

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

Здесь как раз таки приходит на помощь косинусное расстояние, которое рассчитывает расстояние между векторами, с точки зрения их направления. То есть, если они направлены в одну сторону, но какая‑то координата сильно отличается, расстояние все равно будет относительно близким.

Сравним евклидову и косинусную метрики для наших векторов v1 = [1, 2] и v2 = [5, 5]:

Евклидово расстояние:

d(v_1, v_2) = \sqrt{(1 - 5) ^ 2 + (2 - 5) ^ 2} = 5
Евклидово расстояние между v1 и v2
Евклидово расстояние между v1 и v2

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

 \cos(v_1, v_2) = \dfrac{v_1 \cdot v_2}{\sqrt{v_1 \cdot v_1} \cdot \sqrt{v_2 \cdot v_2}} = \dfrac{1 \cdot 5 + 2 \cdot 5}{\sqrt{1\cdot1+2\cdot2} \cdot \sqrt{5 \cdot 5 + 5 \cdot 5}} \approx 0.95d(v_1, v_2) = 1 - \cos(v_1, v_2) \approx 0.05
Косинусное расстояние между v1 и v2
Косинусное расстояние между v1 и v2

Эти операции могут быть представлены в 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 можно визуализировать следующим образом:

Bag of Words
Bag of Words

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

Сразу можно увидеть минус данного подхода, что чем больше количество документов, тем больше будет размерность вашего векторного пространства. А также минус в том, что полученные вектора будут иметь большое количество нулей (они будут разреженными).

Код на 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-IDF
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 технологиях.

Комментарии (0)


  1. Alexey_Yastrebov
    16.09.2025 03:38

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