Когда я впервые начал работать с текстовыми данными много лет назад, вся концепция встраиваний казалась излишне сложной.Я чувствовал себя комфортно с моими подходами с мешком слов и простыми векторами TF-IDF.
Я работал с обзорами продуктов, и мои традиционные модели продолжали ошибочно классифицировать отзывы с сарказмом или оттенком языка. Проблема стала ясной: мои модели не понимали, что "этот продукт болен" на самом деле может быть положительным или что "работал точно так, как ожидалось" может быть нейтральным или отрицательным в зависимости от контекста.
Это не просто фантастическая новая техника – они решают фундаментальные ограничения в том, как машины понимают язык.
The Old Days: Life Before Embeddings
The Old Days: Life Before Embeddings (Старые времена: жизнь до встраиваний)Давайте разберемся с историей использования текстовых представлений перед встраиванием, не вникая в детали этих методов и подходов.
One-Hot Encoding
Он представлял каждое слово как редкий вектор со всеми нулями, за исключением одного «1» на позиции, соответствующей этому слову в словаре. Он представлял слова как массивные, редкие векторы, где каждое слово получило свое собственное измерение. Если ваш словарь имел 100 000 слов (что скромно), каждый словесный вектор имел 99,999 нулей и один 1. Эти представления говорили нам абсолютно ничего о смысле. Слова «отличный» и «фантастический» были математически так же различны, как «отличный» и «ужасный» – полностью лишенные очевидных семантических отношений.
«кошка» → [1, 0, 0, 0, ..., 0] (позиция 5432 в словаре) «собака» → [0, 1, 0, 0, ..., 0] (позиция 8921 в словаре)
Ограничения
- →
- Векторы имели столько же размеров, сколько и словарь (часто более 100 000). →
- Нет семантических отношений: «кошка» и «котенок» были такими же разными, как «кошка» и «самолет» (все равные расстояния) →
- Вычислительная неэффективность: умножение этих скудных матриц было чрезвычайно ресурсоемким →
- Нет обобщения: система не могла понять слова за пределами исходного словаря →
Bag-of-Words Approach
Он подсчитывал случаи появления слов в документах, иногда взвешивая по их важности.Он рассматривал слова в документах как необрезанные коллекции слов, полностью отбрасывая порядок слов.
Документ: «Кошка сидела на ковчеге» BoW: {"the": 2, "cat": 1, "sat": 1, "on": 1, "mat": 1}
Об ограничениях :
- →
- Потеря слов: «Собака кусает человека» и «Человек кусает собаку» имели одинаковые представления →
- Высокоразмерные векторы: все еще требуются векторы размера словаря →
- Нет семантического понимания: синонимы были представлены как совершенно разные черты →
- Нет контекстного значения: каждое слово имело фиксированное представление независимо от контекста →
N-grams
Чтобы запечатлеть какой-то порядок слов, мы начали использовать n-граммы (последования n последовательных слов).
С униграммами (едиными словами) у вас может быть словарный запас в 100 000 слов. С биграмами (парными словами) внезапно вы смотрите на потенциальные миллионы функций. С триграммами? Миллиарды, теоретически. Даже с агрессивным обрезкой, размерность стала неуправляемой.
Limitations:
Об ограничениях :- →
- Комбинаторный взрыв: число возможных n-грамм растет экспоненциально →
- Резкость данных: большинство возможных n-грамм никогда не появляются в данных обучения →
- Ограниченное контекстное окно: только захваченные отношения в малых окнах (обычно 2-5 слов) →
TF-IDF (Term Frequency-Inverse Document Frequency)
TF-IDF улучшил вещи, взвешивая слова на основе того, насколько они важны для конкретного документа, относящегося к корпусу.
Limitations:
Об ограничениях :Нет семантического значения: это количество и частота слов, которые определяют важность их использования.
The Embedding Revolution: What Changed?
Встроенная революция: что изменилось?Переход к встраиванию был не только постепенным улучшением; это был сдвиг парадигмы в том, как мы представляем язык.
Meaning Through Context
Основное понимание, лежащее за встраиванием, обманчиво просто: слова, которые появляются в похожих контекстах, вероятно, имеют схожие значения.Если вы видите, что «собака» и «кошка» появляются вокруг одного и того же типа слов («животное», «пища», «кошка»), они, вероятно, связаны семантически.
Ранние модели встраивания, такие как Word2Vec, запечатлели это, обучая нейронные сети предсказывать:
- →
- Слово, основанное на его окружающем контексте (непрерывный мешок слов) →
- Окружающий контекст, основанный на слове (Skip-gram) →
Скрытые весы слоев из этих моделей стали нашими словесными векторами, кодирующими семантические отношения в геометрических свойствах векторного пространства.
Когда я впервые придумал слова векторов и увидел, что "король" - "мужчина" + "женщина" ≈ "королева", я знал, что мы находимся на чем-то революционном.
Ранние модели, такие как Word2Vec и GloVe, давали каждому слову один вектор независимо от контекста.
"I need to bank the money" vs. "I'll meet you by the river bank"
Такие модели, как BERT и GPT, решали эту проблему, создавая различные встраивания для одного и того же слова в зависимости от его окружающего контекста.
Итак, сначала давайте разберемся, что такое встраивания и как они преобразили НЛП и решили ограничения предыдущих подходов.
What Are Embeddings?
Что такое Embeddings?Встраивания - это численные представления данных (текста, изображений, звука и т. д.) в непрерывном векторном пространстве. Для текста встраивания захватывают семантические отношения между словами или документами, позволяя машинам понимать смысл таким образом, который математически обрабатывается.
Key Concepts:
- →
- Векторы: упорядоченные списки чисел, представляющих точку в многомерном пространстве →
- Размеры: Количество значений в каждом векторе (например, 768-dim, 1024-dim) →
- Векторное пространство: математическое пространство, в котором существуют вставки →
- Семантическое сходство: Измеряется расстоянием или углом между векторами (ближе = больше похоже) →
What Do Dimensions Represent?
Каждое измерение в встраивающем векторе представляет собой изученную особенность или аспект данных.В отличие от классической инженерной техники, где люди определяют, что означает каждый измерение, в современных моделях встраивания:
- →
- Размеры возникают во время обучения, чтобы представлять абстрактные «концепции» →
- Индивидуальные измерения часто не имеют конкретного человеко-интерпретируемого значения. →
- Полный вектор, однако, захватывает семантическую информацию целостным образом →
- Некоторые измерения могут соответствовать настроению, формальности, теме или синтаксису, но большинство представляют собой сложные комбинации особенностей. →
Why We Need Embeddings
Компьютеры в основном работают с числами, а не с словами.Когда мы обрабатываем язык, мы должны преобразовывать текст в численные представления, которые:
- →
- Захват семантических отношений — похожие понятия должны иметь похожие представления →
- Сохранить контекстный смысл – одно и то же слово может означать разные вещи в разных контекстах →
- Разрешение математических операций – например, поиск сходств или выполнение аналогий →
- Эффективная работа в масштабе – обработка больших объемов текста без вычислительного взрыва →
Встраивания решают эти проблемы, представляя слова, фразы или документы как плотные векторы в непрерывном пространстве, где семантические отношения сохраняются как геометрические отношения.
Основы встраивания
Векторное представление
Вместо редких векторов с тысячами или миллионами измерений, встраивания используют несколько сотен плотных измерений, где каждое измерение способствует смыслу.
"cat" → [0.2, -0.4, 0.1, -0.8, ..., 0.3] (300 dimensions)
"kitten" → [0.19, -0.38, 0.15, -0.75, ..., 0.29] (similar to "cat")
Это делает вычислительные порядки величины более эффективными, позволяя при этом более богатое семантическое представление.
Семантика распределения
Встраивания построены на принципе, что «вы будете знать слово по компании, которую она держит» (J.R. Firth).
Например, «король» и «королева» будут иметь схожие контексты, поэтому они будут иметь схожие встраивания, хотя они редко появляются в точном том же положении.
Mathematical Properties
Встроенные пространства обладают замечательными математическими свойствами:
vector("king") - vector("man") + vector("woman") ≈ vector("queen")
Это позволяет проводить аналоговые рассуждения и семантические операции непосредственно в векторном пространстве.
Трансфер обучения
Предварительно обученные встраивания захватывают общие языковые знания, которые могут быть тонко настроены для конкретных задач, резко сокращая данные, необходимые для новых приложений.
Контекстное понимание
Современные контекстные встраивания (такие как из BERT, GPT и т.д.) представляют одно и то же слово по-разному в зависимости от контекста:
"I'll deposit money in the bank" → "bank" relates to finance
"I'll sit by the river bank" → "bank" relates to geography
Имея все знания об истории и понимании встраиваний, пришло время приступить к их использованию.
Используйте модели LLM/SLM для создания встраиваний
Различные исследовательские группы разработали модели встраивания, обученные на различных наборах данных, охватывающих несколько языков и доменов. Это разнообразие приводит к модели с очень разными лексиками и семантическими возможностями. Например, модели, обученные преимущественно по английской научной литературе, будут кодировать технические концепции по-разному, чем те, которые обучены по многоязычному контенту социальных сетей. Эта специализация позволяет практикам выбирать модели встраивания, которые лучше всего соответствуют их конкретным случаям использования.
Практическая реализация встраиваний была значительно упрощена библиотеками, такими как пакет SentenceTransformer от Hugging Face, который обеспечивает всеобъемлющий SDK для работы с различными моделями встраивания. Аналогичным образом, SDK OpenAI предлагает простой доступ к своим моделям встраивания, которые продемонстрировали впечатляющую производительность по многим показателям. Эти инструменты, и есть много других, имеют демократизированный доступ к новейшим технологиям встраивания, позволяя разработчикам интегрировать семантическое понимание в приложения, не нуждаясь в обучении моделей с нуля.
Модели, в интересах этой статьи, следует рассматривать как черный ящик, который принимает предложения в качестве ввода и возвращает их соответствующее векторное представление.
Использование библиотеки SentenceTransformers для встраивания
Самый простой способ генерировать встраивания с помощью SentenceTransformer:
from sentence_transformers import SentenceTransformer
# Load a pre-trained model
model = SentenceTransformer('all-MiniLM-L6-v2') # 384 dimensions
# Generate embeddings
texts = ["This is an example sentence", "Each sentence becomes a vector"]
embeddings = model.encode(texts)
print(f"Shape: {embeddings.shape}") # (2, 384)
max_seq_length = model.tokenizer.model_max_length
print(max_seq_length) # 256
Модель «All-MiniLM-L6-v2», доступная от HuggingFace, имеет 384 измерения. Это означает, что она может захватить 384 черты или нюансы для данного слова или предложения. Длина последовательности этой модели составляет 256 токенов. Ссылки делятся на слова и слова на токены токенизатором во время процесса встраивания. Число токенов, генерируемых для предложения, обычно на 25% - 40% больше, чем количество слов в предложении.
Длина последовательности обозначает количество токенов, которые могут быть обработаны моделью как данное введение. Все, что меньше, подстраивается, чтобы сделать его 256 в длину, и все больше выбрасывается.
Метод кодирования класса SentenceTransformer является оболочкой в режиме вывода PyTorch для использования модели.
from sentence_transformers import SentenceTransformer
import torch
# Load the model directly with SentenceTransformer
model = SentenceTransformer("sentence-transformers/msmarco-distilbert-base-tas-b")
# Input text
texts = ["This is an example sentence", "Each sentence becomes a vector"]
# Get embedding directly
with torch.no_grad():
embedding = model.encode(texts, convert_to_tensor=True)
print(embedding)
Здесь функция torch.no_grad гарантирует, что во время размножения спины не вычисляются градиенты.
Другой, более общий способ создания встраиваний с помощью PyTorch:
# Load the model
model = AutoModel.from_pretrained("sentence-transformers/msmarco-distilbert-base-tas-b")
# Get the tokenizer
tokenizer = AutoTokenizer.from_pretrained("sentence-transformers/msmarco-distilbert-base-tas-b")
# Tokenize input
text = ["This is an example sentence"]
encoded_input = tokenizer(text, padding=True, truncation=True, return_tensors='pt')
# Get embedding of the [CLS] token
with torch.no_grad():
outputs = model(**encoded_input, return_dict=True)
cls_embedding = outputs.last_hidden_state[:, 0]
print(cls_embedding)
Разница между этим и предыдущими фрагментами кода заключается в том, что функция кодирования была заменена использованием токенизатора и модели явно.
Другая разница заключается в том, что мы используем outputs.last_hidden_state[:, 0] для получения вектора, связанного с токеном CLS. Этот специальный токен CLS добавляется к каждому предложению в начале каждого предложения, и он содержит накопленную информацию о всем предложении.
Следует отметить, что этот подход к добавлению токена CLS применим только к определенным архитектурам, основанным на трансформаторе, и это включает в себя BERT и его варианты и трансформаторы, основанные только на кодерах.
Best for:Классификация и задачи прогнозирования на уровне последовательности
Why they work:Токен [CLS] в моделях в стиле BERT специально подготовлен для агрегирования информации из всей последовательности во время предварительной подготовки.
When to choose:
- →
- При использовании BERT, RoBERTa или аналогичных моделей для классификации →
- Когда требуется один вектор, представляющий целую последовательность →
- Когда задача вниз включает в себя предсказание свойства всего текста →
Метод CLS, используемый, является лишь одним из методов для захвата встраиваний для предложения.
Mean Pooling
Принимая среднюю величину всех встраиваний токенов, это удивительно эффективно для многих задач.Это мой метод, когда я использую встраивания для задач сходства или поиска.
Best for:Семантическое сходство, восстановление и представления общего назначения.
Why it works:Средняя по всем представлениям токенов, средняя группировка улавливает коллективный семантический контент при одновременном уменьшении шума.
When to choose:
- →
- Для приложений сходства документов или семантического поиска →
- Когда вам нужны прочные представления, которые не доминируют ни одним токеном →
- Когда эмпирическое тестирование показывает, что оно превосходит другие методы (часто это делается для задач сходства) →
import torch
from transformers import AutoTokenizer, AutoModel
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
# Tokenize input
texts = ["This is an example sentence", "Each sentence becomes a vector"]
inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt")
# Mean pooling
with torch.no_grad():
outputs = model(**inputs)
# Get attention mask to ignore padding tokens
attention_mask = inputs['attention_mask']
# Sum token embeddings and divide by the number of tokens
sum_embeddings = torch.sum(outputs.last_hidden_state * attention_mask.unsqueeze(-1), dim=1)
count_tokens = torch.sum(attention_mask, dim=1, keepdim=True)
mean_embeddings = sum_embeddings / count_tokens
print(f"Shape: {mean_embeddings.shape}") # (2, 768)
Max Pooling
Максимальное объединение принимает максимальное значение для каждого измерения на всех токенах.Он удивительно хорошо улавливает важные функции независимо от того, где они появляются в тексте.
Best for:Задачи обнаружения и извлечения информации
Why it works:Max pooling выбирает сильнейшую активацию для каждого измерения на всех токенах, эффективно захватывая наиболее заметные функции независимо от того, где они появляются в тексте.
When to choose:
- →
- Когда конкретные черты имеют большее значение, чем их частота или положение →
- При поиске наличия конкретных понятий или субъектов →
- При работе с длинными текстами, где важные сигналы могут быть разбавлены в среднем →
import torch
from transformers import AutoTokenizer, AutoModel
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
# Tokenize input
texts = ["This is an example sentence", "Each sentence becomes a vector"]
inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt")
# Max pooling
with torch.no_grad():
outputs = model(**inputs)
# Create a mask to ignore padding tokens for max pooling
attention_mask = inputs['attention_mask'].unsqueeze(-1)
# Replace padding token representations with -inf so they're never selected as max
token_embeddings = outputs.last_hidden_state.masked_fill(attention_mask == 0, -1e9)
# Take max over token dimension
max_embeddings = torch.max(token_embeddings, dim=1)[0]
print(f"Shape: {max_embeddings.shape}") # (2, 768)
Weighted Mean Pooling
Метод взвешенного объединения пытается придать больше веса более важным токенам, основанным на позиции (например, придавая больше веса более поздним токенам).
Best for:Задачи, в которых разные части ввода имеют разное значение
Why it works:Весомое объединение позволяет подчеркнуть определенные токены на основе их позиции, баллов внимания или других показателей значимости.
When to choose:
- →
- Когда имеет значение порядок последовательности (например, придавая больше веса более поздним токенам) →
- Когда определенные токены по своей сути являются более информативными (например, номенклатуры и глаголы против статей) →
- Когда у вас есть эвристическая важность, которая имеет смысл для вашей задачи →
import torch
import torch.nn.functional as F
from transformers import AutoTokenizer, AutoModel
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
# Tokenize input
texts = ["This is an example sentence", "Each sentence becomes a vector"]
inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt")
# Weighted mean pooling - more weight to later tokens
with torch.no_grad():
outputs = model(**inputs)
# Get token embeddings and attention mask
token_embeddings = outputs.last_hidden_state
attention_mask = inputs['attention_mask']
# Create position-based weights (later positions get higher weights)
input_lengths = torch.sum(attention_mask, dim=1).unsqueeze(-1)
position_indices = torch.arange(token_embeddings.size(1)).unsqueeze(0).expand_as(attention_mask)
position_weights = position_indices.float() / input_lengths.float()
position_weights = position_weights * attention_mask
# Normalize weights to sum to 1
position_weights = position_weights / torch.sum(position_weights, dim=1, keepdim=True)
# Apply weights and sum
weighted_embeddings = torch.sum(token_embeddings * position_weights.unsqueeze(-1), dim=1)
print(f"Shape: {weighted_embeddings.shape}") # (2, 768)
Последний токен Pooling
Последнее объединение токенов - это техника создания одного встраивающего вектора из последовательности встраиваний токенов, выбирая только представление окончательного токена.
Best for:Авторегрессивные модели и последовательная обработка
Why it works:В моделях слева направо, таких как GPT, конечный токен содержит накопленный контекст из всей последовательности, что делает его богатым информацией для определенных задач.
When to choose:
- →
- При использовании GPT или других моделей, предназначенных только для декодера →
- При работе с задачами, которые сильно зависят от полного предшествующего контекста →
- Для создания текста или выполнения задач
import torch
from transformers import AutoTokenizer, AutoModel
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
# Tokenize input
texts = ["This is an example sentence", "Each sentence becomes a vector"]
inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt")
# Last token pooling
with torch.no_grad():
outputs = model(**inputs)
# Get the last non-padding token for each sequence
attention_mask = inputs['attention_mask']
last_token_indices = torch.sum(attention_mask, dim=1) - 1
batch_indices = torch.arange(attention_mask.size(0))
# Extract the last token embedding for each sequence
last_token_embeddings = outputs.last_hidden_state[batch_indices, last_token_indices]
print(f"Shape: {last_token_embeddings.shape}") # (2, 768)
Есть много других способов, и эти методы могут быть объединены вместе, а также для создания индивидуальных методов.Это было только началом для понимания встраиваний как концепции и базовой реализации для получения встраиваний с использованием различных методов.
Looking Forward: Where Embeddings Are Headed
Глядя вперед: куда направляются вставкиПространство встраивания (предназначенное для пуна) продолжает развиваться:
- →
- Мультимодальные встраивания разрушают барьеры между текстом, изображениями, аудио и видео.Модели, такие как CLIP и DALL-E, используют встраивания для создания общего семантического пространства между различными модальностями. →
- Более эффективные архитектуры, такие как MobileBERT и DistilBERT, позволяют использовать мощные встраивания на краевых устройствах с ограниченными ресурсами. →
- Доменные встраивания, предварительно обученные в специализированных корпорациях, продвигают современные технологии в таких областях, как медицина, право и финансы. →
Я особенно взволнован композиционными встраиваниями, которые лучше улавливают, как смысл построен из меньших единиц, что, наконец, может решить давние проблемы с отрицанием и композиционными фразами.
Final Thoughts
Окончательные мыслиВстраивания - это не просто еще одна методика НЛП - это фундаментальный сдвиг в том, как машины понимают и обрабатывают язык.Они перевели нас от обработки текста как произвольных символов к захвату богатой, сложной сети значений и отношений, которые люди интуитивно понимают.
Независимо от задачи НЛП, над которой вы работаете, есть вероятность, что тщательно приложенные встраивания могут сделать это лучше.
И если вы все еще используете пакет слов или одноразовое кодирование для текстового анализа...
Весь мир возможностей ждет вас.