Знание механизма внимания и трансформеров - база любых собеседований на все грейды в NLP!
Статья не рассчитана на изучение тем с нуля, если вы еще не слышали ничего про attention, то лучше обратиться к полноценным лекциям.
Это чеклист и тренажёр, по которому стоит пройтись перед техническим интервью по NLP, чтобы закрыть пробелы и вспомнить необходимую базу.
Содержание:
Архитектура трансформера
Механизм внимания
Позиционные эмбеддинги
Токенизация
Трансформерные архитектуры (BERT, GPT и тд)
Полезные материалы
Статьи серии
classic ML: основы мл, линейные модели, метрики классификации и регресии
classic ML: Деревья и ансамбли, кластеризация, метрические модели
NLP: трансформеры и внимание [эта часть]
NLP: GPT, LLM, Alignment и оптимизации [Soon… Stay fine-tuned…]
NLP: LLM и агенты [Soon… Stay fine-tuned…]
Чтобы не пропустить выход статей и видео по ML, NLP, LLM, подпишись на мои соц. сети:
В Telegram канале — регулярный контент по ML и DL
На Ютуб канале — видеоразборы вопросов с собеседований (и по этой статье скоро выйдет)
На Boosty — разборы реальных собеседований и не только
Полная карта со всем моим контентом
Вкат с нуля или повышение грейда в ML - менторство
Архитектура Transformer и Attention
Что такое трансформер?

Трансформер — это енкодер-декодер архитектура нейросети, предложенная в работе "Attention is All You Need".
Отказываемся от рекуррентных и сверточных слоев в обработке последовательностей, полагаясь только на механизм внимания (attention).
Трансформер смотрит на всю входную последовательность сразу. Каждый токен получает информацию обо всех других через self-attention.
В чем отличие трансформера от других архитектур? Сравни с RNN и CNN
Особенность |
RNN |
CNN |
Transformer |
Как обрабатывает токены |
Обрабатывает вход поэтапно, последовательно: токен за токеном. Зависимости "в прошлом" передаются через скрытое состояние. |
Использует локальные фильтры, фокусируясь на соседних токенах с постепенным расширением рецептивного поля*. |
Обрабатывает все токены одновременно (параллельно), используя механизм self-attention, чтобы каждый токен "видел" другие. |
Как работают с длинными контекстами |
Трудно помнить контекст, далёкий от текущего токена (даже с LSTM/GRU). |
Чтобы охватить большой контекст, нужно много слоёв (увеличение глубины). |
Self-attention сразу учитывает все позиции → хорошо справляется с долгими зависимостями. |
Параллелизация |
Ее нет. По определению — последовательная модель. |
Фильтры применяются параллельно |
Да, полностью: все токены обрабатываются полностью параллельно. Это делает обучение на GPU эффективнее. |
Алгоритмическая сложность |
?(?·?²) n — длина последовательности d — размер скрытого состояния (hidden size) |
?(?·k·?²) n - количество пикселей k - количество фильтров d - размерность ядра |
?(?²·?) |
Сложность памяти по длине последовательности |
линейна |
линейна |
все еще квадратична |
Рецептивное поле
Рецептивное поле — это область входных данных, которая влияет на значение конкретного нейрона в сети. В контексте CNN — это количество входных элементов (например, токенов или пикселей), охватываемое фильтром с учётом глубины предыдущих слоёв.
Как выглядит архитектура трансформера? Из каких частей состоит?
Глобально трансформер это енкодер и декодер.
Енкодер кодирует информацию из входного предложения, а декодер генерирует новое предложение на основании информации из енкодера, причем генерация авторегрессивная (то есть токен за токеном).

Состоит трансформер из следующих слоев:
input embeddings
по токен айди берет обучаемый эмбеддинг токенаpositional encodings
по номеру позиции токена берет обучаемый или необучаемый эмбеддинг позицииmulti-head self-attention
токены обмениваются информацией и обновляют свои векторные представленияresidual connections, layer normalization и dropout
для стабилизации обучения и регуляризацииfeed forward, он же многослойный перцептрон (FW / FFN / MLP)
информация после слоя внимания переваривается уже независимо для каждого векторного представления токеновmasked multi-head self-attention
маскируем будущие токены в механизме вниманияcross-attention
связывает представления токенов между енкодером и декодеромlinear и softmax (head)
для вектора представления токена t предсказывает следующий токен t+1
Как мы обрабатываем входные токены перед енкодером?
Входные токены сначала отдаем в input embeddings - там получаем эмбеддинги токенов
Позиции токенов отдаем в слой positional encodings, чтобы закодировать позицию каждого токена
Складываем input embeddings и positional encodings и передаем полученное в енкодер

Зачем нужны позиционные эмбеддинги?
Позиционные эмбеддинги (positional embeddings) — это способ передачи информации о порядке элементов в последовательности. Без нее модель не сможет найти отличие:
"The cat sat on the mat."
VS
"The mat sat on the cat."
В RNN у нас нет такой проблемы, потому что токены обрабатываются последовательно. В трансформере обработка токенов происходит параллельно (как мешок векторов).
Как работает позиционное кодирование?
-
Входную последовательность "The cat sat on the mat." токенизируем и для каждого токена получаем эмбеддинг:
E("The") = [0.1, 0.2, 0.3]
E("cat") = [0.4, 0.5, 0.6]
…
E(".") = [0.9, 1.0, 1.1]
-
Для каждого токена определяем его позицию и считаем эмбеддинг позиции
P(1) = [0.01, 0.02, 0.03] (for "The")
P(2) = [0.04, 0.05, 0.06] (for "cat")
…
P(7) = [0.19, 0.20, 0.21] (for ".")
-
Поэлементно складываем полученные эмбеддинги
Final("The") = E("The") + P(1) = [0.1+0.01, 0.2+0.02, 0.3+0.03] = [0.11, 0.22, 0.33]
Final("cat") = E("cat") + P(2) = [0.4+0.04, 0.5+0.05, 0.6+0.06] = [0.44, 0.55, 0.66]
…
Какие виды позиционного кодирования есть?
Sinusoidal (Fixed) классическое позиционное кодирование в Transformer. Использует синусы и косинусы разных частот. Используется в оригинальном Transformer.

Learnable обучаемые позиционные эмбеддинги, как в BERT, GPT. В отличие от фиксированных, они обучаются вместе с моделью, как обычные word embeddings.
Relative учитывают относительные позиции токенов (расстояния между ними), а не абсолютные позиции (пример — Transformer-XL, DeBERTa). Лучше обобщаются на длинные последовательности, чем простые обучаемые.
Rotary (RoPE) применяются, например, в LLaMA. Кодируются внутри attention-сопоставлений, а не добавляются к эмбеддингам. Вращает эмбеддинги слов в зависимости от их позиции с помощью матрицы вращения. Сохраняет относительные расстояния между токенами. Улавливает дальние зависимости лучше, чем абсолютное кодирование. Работает с переменной длиной последовательности. Используется в большом кол-ве современных LLM, например LLaMA.

ALiBi (Attention with Linear Biases) добавляет линейный штраф к вниманию, пропорциональный расстоянию между токенами. Также хорошо работает с длинным последователностями. Используется в MPT архитектуре.
Как выглядит енкодер?
Енкодер получает на вход векторные представления каждого токена и на выходе отдает обновленные векторные представления
Каждый блок енкодера и каждый слой внутри блока делает то же самое (держим это в голове)

Блок енкодера повторяется N раз и состоит из:
Multi-head self-attention
Residual connections, layer normalization и dropout.
Feed forward, он же многослойный перцептрон (FFN / MLP)
Снова Residual connections, layer normalization и dropout.
Attention - токены смотрят друг на друга и собирают информацию. То есть attention смешивает информацию между токенами.
FFN - переваривает полученную информацию. Причем преобразует каждый токеновый вектор независимо.

Что такое Feed Forward (FFN / MLP)?

Два линейных слоя с нелинейностью (обычно ReLU или GELU)
Причем FF делаем по элементно, то есть к каждому векторному представлению токенов независимо друг от друга. Можно представить, что это делает как будто в цикле for, torch делает все за нас в один вызов.
Также часто добавляют слой dropout в MLP, хотя в статье его не упомянули
Пример кода
class PositionwiseFeedForwardNet(nn.Module):
"""
It's position-wise because this feed forward net will be independently applied to every token's representation.
Representations batch is of the shape (batch size, max token sequence length, model dimension).
This net will basically be applied independently to every token's representation (you can think of it as if
there was a nested for-loop going over the batch size and max token sequence length dimensions
and applied this net to token representations. PyTorch does this auto-magically behind the scenes.
"""
def __init__(self, model_dimension, dropout_probability, width_mult=4):
super().__init__()
self.linear1 = nn.Linear(model_dimension, width_mult * model_dimension)
self.linear2 = nn.Linear(width_mult * model_dimension, model_dimension)
# This dropout layer is not explicitly mentioned in the paper but it's common to use to avoid over-fitting
self.dropout = nn.Dropout(p=dropout_probability)
self.relu = nn.ReLU()
def forward(self, representations_batch):
return self.linear2(self.dropout(self.relu(self.linear1(representations_batch))))Зачем нужны Residual connections?

Появились остаточные связи (skip / residual connections) еще во время доминирования CNN и ResNet-а. Но до сих пор являются неотъемлемой частью глубоких нейронок.
Работает так, что к выходу из блока суммируем вход в этот блок.
Простой пример в коде
import torch
import torch.nn as nn
class TinyBlock(nn.Module):
def __init__(self, d_model):
super().__init__()
self.linear = nn.Linear(d_model, d_model) # любой подслой
self.norm = nn.LayerNorm(d_model)
def forward(self, x):
y = self.linear(x) # преобразование
x = x + y # ← skip connection / residual
x = self.norm(x) # нормализация (post-norm)
return x
# пример работы
x = torch.randn(1, 10, 32)
block = TinyBlock(32)
out = block(x)
print(out)Что такое Layer Norm? Отличие от Batch Norm?

Слой нормализации нужен для улучшения сходимости. Его относят к одному из возможных способ регуляризации нейронных сетей.
Работает примерно также как и Batch Norm (подробнее тут), только нормализацию делает по другой оси: не по семплам во всем батче, а по каждому отдельному вектору токенов.

- статистики, которые считаем для каждого токена. Причем в отличии от BatchNorm считаются налету для вектора как на трейне, так и на инференсе.
scale и bias - обучаемые параметры слоя Layer Norm, они уже общие для всех токенов.
Пример кода
# Note: the original paper had LayerNorm AFTER the residual connection and addition operation
# multiple experiments I found showed that it's more effective to do it BEFORE, how did they figure out which one is
# better? Experiments! There is a similar thing in DCGAN and elsewhere.
class SublayerLogic(nn.Module):
def __init__(self, model_dimension, dropout_probability):
super().__init__()
self.norm = nn.LayerNorm(model_dimension)
self.dropout = nn.Dropout(p=dropout_probability)
# sublayer - здесь может быть слоем multi-head attention или feed forward (mlp)
def forward(self, representations_batch, sublayer_module):
# Residual connection between input and sublayer output, details: Page 7, Chapter 5.4 "Regularization",
return representations_batch + self.dropout(sublayer_module(self.norm(representations_batch)))Как выглядит декодер?
Декодер получает на вход векторные представления каждого токена и на выходе отдает обновленные векторные представления
Каждый блок декодера и каждый слой внутри блока делает то же самое (держим это в голове)

- Перед декодером нам снова нужны Input Embedding и Positional Encoding, чтобы получить эмбеддинги сгенерированного текста
- После этого идет декодер, который повторяется также N раз и состоит из:
- masked multi-head attention
- Residual connections, layer normalization и dropout
- потом cross-attention, связывающий активации енкодера и декодера
- Residual connections, layer normalization и dropout
- Feed Forward
- Residual connections, layer normalization и dropout
- После линейный слой, софтмакс.
Это голова трансформера (head):
Логиты для каждого токена → softmax* → предсказание следующего токена
Softmax
softmax - это функция активации, которая преобразует набор численных выходов нейронной сети в вероятностное распределение. Она преобразовывает каждый логит так, чтобы все значения лежали в диапазоне от 0 до 1 и в сумме давали 1.
формула:
Что мы подаем в голову декодера (head)?
В голову отдаем векторные представления для каждого токена. И дальше к каждому вектору независимо применяем линейный слой и softmax, чтобы получить логиты и вероятности следущего токена соответственно.
При этом токены прошлого ничего не могут знать про токены будущего, так как мы применяем маскированное внимание Masked Multi-head Attention (ниже про все виды внимания).
То есть токен t+1 предсказывается по векторному представлению токена t.
Пример кода
class DecoderGenerator(nn.Module):
def __init__(self, model_dimension, vocab_size):
super().__init__()
self.linear = nn.Linear(model_dimension, vocab_size)
# -1 stands for apply the log-softmax along the last dimension i.e. over the vocab dimension as the output from
# the linear layer has shape (B, T, V), B - batch size, T - max target token-sequence, V - target vocab size
# again using log softmax as PyTorch's nn.KLDivLoss expects log probabilities (just a technical detail)
self.log_softmax = nn.LogSoftmax(dim=-1)
def forward(self, trg_representations_batch):
# Project from D (model dimension) into V (target vocab size) and apply the log softmax along V dimension
return self.log_softmax(self.linear(trg_representations_batch))Как работает механизм внимания (attention)?

Attention позволяет модели "сфокусироваться" на релевантных частях входа при обработке каждого токена. Вместо того чтобы просто проходить последовательно, как это делают RNN, трансформер вычисляет степень взаимного влияния каждого токена на каждый другой.
Приходит на вход
, матрица размерности
где- размер батча
- максимальное количество токенов в последовательности
- размерность эмбеддинга
-
Далее для
вычисляются
:
— Query (запрос)
— Key (ключ)
— Value (значение)
Здесь
— обучаемые весовые матрицы (параметры слоя внимания) размерности

после имеют размерности
-
После этого обновляем представления эмбеддингов по формуле внимания:

формула с размерностями и визуализацией матриц Где
— оценивает степень сходства между токенами одного семпла в батче
Деление на корень из размерности
уменьшает величину скалярных произведений и стабилизирует градиенты
softmax — превращает оценки похожести в веса внимания
Умножением на V получаем взвешенную сумму значений, где веса — это то, сколько внимания один токен уделяет другому
На выходе имеем обновленные векторные представления токенов X_new, содержащие уже контекстуализированную информацию. Размерностью

Метафора для запоминания смысла q, k, v:
q — запрос к базе данных
k — ключи хранящихся в базе, по которым будет осуществляться поиск
v — сами значения

Что такое Multi-Head Attention?
В многоголовом внимании мы "нарезаем" матрицына несколько независимых голов размерностями
:

И запускаем процесс вычисления внимания как обычно для каждой отдельной головы, потом результаты конкатенируем и применяем еще один линейный слой (output:), чтобы перевести полученные векторные представления токенов к исходной размерности.

При использовании нескольких голов, сложность вычисления внимания ассимптотически не вырастает, остается также, так как мы используем такое же количество параметров, просто исходную матрицу нарезаем на части и просто используем независимо друг от друга, таким образом, разные головы, выучивают разные закономерности: кто-то семантический окрас, кто-то синтаксические связи.
Что такое Masked Multi-Head Attention?
Этот вид механизма внимания нужен, чтобы токены в декодере не подглядывали в будущие токены, которые еще не успели бы сгенерироваться.
Реализовано это через прибавление маски из единиц и :

Какие виды attention бывают?
Attention — механизм внимания в общем виде, все остальное частный случай. Подразумевается способ концентрации/фокусировки на токенах внутри одной последовательности или на токены из другой последовательности.

Self-Attention - каждый токен смотрит на все токены внутри одной последовательности, включая себя. Сложность: O(n2⋅d)
-
Cross-Attention - используется в encoder-decoder.
decoder смотрит на выходы encoder'а (Decoder (query) ↔ Encoder (key & value)). То есть у нас все тот же механизм внимания, но query берем по сгенерированным декодером токенам, а key и value берем от активаций енкодера по входному тексту.
Multi-Head Attention - нарезаем матрицы весов внимания на части, чтобы слой внимания учитывал более разнообразные закономерности.
Multi-Query Attention - Все головы деляют одни и те же матрицы W_K и W_V (но W_Q остаются отдельными). Сильно ускоряет декодирование и снижает затраты памяти, но может ухудшить качество модели.
Grouped Query Attention - компромисс между MHA и MQA: группирует головы запросов Q и назначает им общие ключи K и значения V в рамках группы. Оптимизация по скорости и памяти почти как у MQA, но качество почти как у MHA.

Disentangled Attention - механизм, придуманный в DeBERTa. Раздельное кодирование содержания и позиции токенов.

-
PagedAttention (аналог виртуальной памяти для KV-кэша в vLLM)
Обычный подход: KV-кэш хранится как непрерывный блок памяти в GPU. Если контекст длинный (например, 32K токенов), память расходуется неэффективно. Невозможно частично освобождать память (например, если некоторые запросы завершились).
Решение: KV-кэш разбивается на "страницы" (например, по 16 токенов). Каждый запрос получает свою "виртуальную память" — логическое пространство для KV-кэша. Физические страницы выделяются динамически (можно переиспользовать, освобождать).
Память выделяется только под реально используемые токены.
Перед тем, как идти дальше
Внимательно изучите архитектуру и подумайте: что зачем куда как. Есть смысл выучить ее и порассказывать самому себе.
Также полистайте и постарайтесь разобраться с реализацией трансформера и механизмов внимания в коде. Просто код, или статья с кодом, или мой разбор кода и схем в видео - трансформер и механизм внимания.

Токенизация
Какие виды токенизаторов есть?
Конечно, можно токенизировать по символам или по словам, но такие подходы в какой-то степени устарели и сейчас SOTA методы токенизации по подсловам:
-
Byte-level BPE - работает на уровне байтов (вход сначала переводится в байты), затем применяют алгоритм BPE — итеративно объединяют самые частые пары (сначала байты, потом более длинные последовательности).
Благодаря работе с байтами токенизатор гарантировано покрывает любое входное словосочетание (не возникает OOV-символов)
BPE - итеративно объединяют самые частовстречаемые пары символов/подслов в единые токены
WordPiece - делаем такие токены, чтобы максимизировалась вероятность корпуса текста
BPE использует частоты пар символов, а WordPiece — вероятностную модель корпуса. Поэтому BPE проще и быстрее в обучении, а WordPiece выбирает токены, которые лучше описывают статистику языка.
SentencePiece - работает без предварительной токенизации по пробелам. Рассматривает текст как последовательность Unicode-символов, включая пробелы. Благодаря этому подходит для мультиязычных моделей и языков без пробелов.
Unigram - одна из моделей токенизации в SentencePiece. В отличие от BPE, который добавляет новые токены, Unigram начинает с большого словаря и удаляет наименее полезные токены, оптимизируя правдоподобие корпуса.
Почему именно Byte-Level, почему нельзя по символам разбивать?
Byte-Level BPE (как в GPT, RoBERTa) лучше работает с редкими символами и языками, использующими нестандартные алфавиты (китайские иероглифы, эмодзи и т. д.). Byte-Level подход изначально поддерживает все языки, так как любой Unicode-символ раскладывается на байты. Так как нет unicode нормализации, токенизация воспроизводится на всех платформах.
Char-Level может требовать ручной настройки для поддержки новых алфавитов. хуже обобщают редкие комбинации символов
Когда используем именно Char-Level:
Строго ограниченные алфавиты (например, только английский текст без специальных символов).
Задачи, где важна точная обработка символов (генерация кода, анализ DNA-последовательностей).
Какие основные гиперпараметры токенизаторов есть?
vocab_size - самый важный гиперпараметр у всех токенайзеров, от него зависит как плотность токенизации, так и кол-во параметров в слоях модели (в эмбеддингах и "голове"). Чем больше словарь, тем более плотная токенизация, но тем больше весов в слоях эмбеддингов и в финальном линейном слое. Но с другой стороны, чем меньше токенов, тем ниже стоимость внимания (помним про квадратичную сложность по длине последовательности).
Плотность токенизации
Плотность токенизации - сколько токенов нужно, чтобы токенизировать текст. Например:
vocab_size: 8k
"Автоботы, выдвигаемся!" - ["авто", "боты", ", ", "вы", "двиг", "ае", "мся"]
vocab_size: 32k
"Автоботы, выдвигаемся!" - ["автоботы", ", ", "выдвигаемся"]
-
параметры претокенизации (их, конечно, больше, но основные эти):
специальные токены - [CLS], [BOS], [SEP] и тд
регистр - нижний или оставляем как есть
как обрабатываем цифры
делаем ли претокенизацию по словам
Как обучаются эмбеддинги токенов?
В торче есть слой nn.Embeddings, он создает матрицу векторов токенов размерности (vocab_size, hidden_dim), где hidden_dim - размерность вектора.
Он обучается вместе с другими слоями через backprop.
Пример в коде
class Embedding(nn.Module):
def __init__(self, vocab_size, model_dimension):
super().__init__()
self.embeddings_table = nn.Embedding(vocab_size, model_dimension)
self.model_dimension = model_dimension
def forward(self, token_ids_batch):
assert token_ids_batch.ndim == 2, f'Expected: (batch size, max token sequence length), got {token_ids_batch.shape}'
# token_ids_batch has shape (B, S/T), where B - batch size, S/T max src/trg token-sequence length
# Final shape will be (B, S/T, D) where D is the model dimension, every token id has associated vector
embeddings = self.embeddings_table(token_ids_batch)
# (stated in the paper) multiply the embedding weights by the square root of model dimension
# Page 5, Chapter 3.4 "Embeddings and Softmax"
return embeddings * math.sqrt(self.model_dimension)Как можно расширить словарь?
Добавляем новый токен в токенизатор и расширяем матрицу эмбеддов. Тогда нужно дообучать модель, чтобы эмбеддинги новых токенов выучились, их, кстати, можно проинициализировать не случайно, а усреднив по похожим токенам.
Расширение словаря может быть нужно для адаптации токенизатора и соответственно модели к:
особенностям предметной области (медицины, юрисприденции)
новым терминам (командам, специальным токенам, брендам, жаргонам)
также можно:
Переобучаем токенайзер на новых текстах и с нуля выучиваем эмбеддинги всех токенов
Трансформерные архитектуры (BERT, GPT и др)
Какие трансформерные архитектуры есть?
Глобально можно разделить на 3 типа:
encoder-decoder - например, классический трансформер из "Attention is all you need" или T5 архитектура
encoder-only - оставляем только енкодер из трансформера, обычно такие архитектуры используются для векторных представлений текста и его понимания (классификации / регрессии, NER и др). Пример - любая вариация BERT.
decoder-only - оставляем только декодер из трансформера, обычно решаем задачу языкового моделирования через авторегрессивную генерации текста (токен за токеном генерируем), хотя векторное представление текста получить также можно из внутренних активаций. Примеры - GPT модели и все большие языковые модели (llama, mistral, qwen)
Но также есть и специализированные архитектуры:
заточенные под CV или Speech: ViT, DETR и Whisper
для работы с мультимодальными данными: CLIP, BEit
Что такое BERT?
BERT (Bidirectional Encoder Representations from Transformers) использует многослойный энкодер Transformer (без декодера). Self-Attention механизм позволяет модели находить связи между словами, даже если они далеко друг от друга.
На вход в енкодер мы подаем токены, на выходе получаем векторные представления для каждого токена.
Сам Берт состоит из енкодера и головы. Голова для разных задач - разная (для бинарной классификации - одна, для многоклассовой - другая).
Предобучение на двух задачах:
-
Masked Language Model (MLM)
15% слов в тексте маскируются, и модель учится их предсказывать (например, "кошка [MASK] на коврике" → "лежит").
На выходе энкодера для каждого [MASK] получается вектор скрытого состояния (hidden state). Этот вектор проходит через классификационный слой + softmax, который предсказывает исходное слово.

-
Next Sentence Prediction (NSP)
Модель определяет, следует ли одно предложение за другим.
Датасет собирается в формате: [CLS] Предложение A [SEP] Предложение B [SEP]
Решаем задачу бинарной классификации, в классификационный слой отдаем вектор из токена [CLS]

BERT генерирует контекстуальные векторные представления для слов, учитывая их окружение. То есть одно и то же слово в разных контекстах будет иметь разный вектор.
BERT сначала предобучается на большом корпусе данных, понимает домен, модель языка, общие паттерны. Далее его можно использовать для transfer learning с помощью fine-tuning на специфичную задачу: голову с нуля, остальное дообучаем или замораживаем.
Какие задачи можно решать с помощью encoder моделей?
классификация текста
спам / не спам письморегрессия
по описанию товара предсказать его стоимостьтокен классификация
например, NER - распознавание именованных сущностей: имена, локации и тдпоиск
найти релевантные документы по запросусемантическая близость текстов
например, чтобы найти дубликаты или тот же поисккластеризация текстов
когда неизвестны меткиранжирование и рекомендации
например, если нам нужно отранжировать товары по их описанию по релевантности какому-то юзеру
а для непосредственно рекомендаций сейчас очень популярный подход на трансформерах: bert4rec (если просто, то обучаем mlm: из истории покупок маскируем токен=товар и bert пытается его предсказать. А на инференсе ставим маску в конец истории покупок и берем топ-к товаров, которые bert предсказывает вместо маски.)
Что такое bi-encoder и cross-encoder? Когда какой вид использовать?
Bi-Encoder и cross-encoder используются для оценки сходства между двумя текстами.
Bi-Encoder независимо скорит 2 текста, получает вектора для них, а затем с помощью косинусного расстояния оцениваем близость.
Cross-Encoder обрабатывает 2 текста совместно, мы отдаем в bert сконкатенированные тексты, через специальный токен, а модель выдает скор от 0 до 1 - насколько тексты близки друг к другу.

Cross-Encoder обладают лучшим качеством, по сравнению с Bi-Encoder, но они не производят эмбеддингов, которые можно было бы сложить в векторную базу данных (индекс)
Более того, нам нужно составить всевозможные пары между запросом и документами, чтобы оценить близость текстов, а такие вычисления будут очень долгими.
Когда использовать би енкодер и кросс-енкодер:
Би енкодер используем для отбора кандидатов, то есть векторизуем большое число документов, по косинусной близости оставляем несколько документов
А потом с помощью кросс-енкодера скорим все пары среди кандидатов и уже финально переранжируем документы
Как векторизовать тексты с помощью берта?
Допустим, у нас есть уже обученный берт, и мы хотим с помощью него получить векторное представление текста.
Мы прогоняем предложение через берт, енкодер для каждого токена выдает векторное представление.
Но здесь еще рано останавливаться, потому что разные тексты имеют разное кол-во токенов, а мы хотим иметь вектор фиксированной длины для произвольных текстов.
Поэтому решение:
можем применить pooling: max или average pooling к векторам токенов после енкодера
можно взять векторное представление токена [CLS], потому что там как раз собрана информация о всем предложении из-за того, как обучается берт
pooler_output
из токена cls берем эмбеддинг, но еще дополнительно пропускаем его через линейный слой + гиперболический тангенс
Также есть так называемые sentence transformers, это подход, основанный на том, что мы предобучаем трансформеры, например, берт какой-нибудь на задачи, где нам требуется именно вектор всего входного текста, а не только на задачу млм, что может быть не оптимальным для задач поиска, ранжирования и кластеризации, например.
Часто во время обучения используются контрастив лоссы, чтобы похожие по смыслу тексты имели близкие эмбеддинги в векторном пространстве, а непохожие - далекие.

Какие еще разновидности берта знаешь?
Разновидности бертов:
RoBERTa (2019) - обучаемся только на MLM, маски генерируются динамически, больше данных, больше батч сайз
ALBERT (2019) - Factorized Embedding Parameterization + Cross-layer Parameter Sharing + Замена NSP на SOP (Sentence Order Prediction)
ELECTRA (2020) - генератор (маленький BERT) маскирует токены, а дискриминатор (основная модель) предсказывает, был ли токен заменён
DeBERTa (2021) - использует Disentangled Attention
DistilBERT (2019) - Knowledge Distillation, чтобы берт был в разы меньше, сохраняя 95% качества. Конечно, подходов ускорить/уменьшить берт было и есть очень много.
e5 (2022) - особенность в контрастив предобучении на парах текстов. Часто используется для поиска, ранжирования и кластеризации.
На этом все! Скоро на ютубе выйдет интервью по этим темам. А на бусти уже можно смотреть видео-разбор трансформеров и внимания в схемах и коде.
Полезные материалы
Учебник Лены Войты | Illustrated transformer | The Annotated Transformer | Attention is all you need paper | Разбор кода и схем в видео - трансформер и механизм внимания
В Telegram канале — регулярный контент по ML и DL
На Ютуб канале — видеоразборы вопросов с собеседований (и по этой статье скоро выйдет)
На Boosty — разборы реальных собеседований и не только
Полная карта со всем моим контентом
Вкат с нуля или повышение грейда в ML - менторство
egaoharu_kensei
Спасибо за статью, ловите плюсик: полезно иметь всё в одном месте. Однако хочу уточнить, что хотя аналогия q, k, v матриц с базами данных (мой самый любимый вопрос) и используется во многих объяснениях и ответах на собеседованиях, это ошибочное утверждение. На самом деле эти матрицы решают 2 проблемы:
- 1) Помогают отделять смысл слова в зависимости от контекста в пространстве признаков т.к. эмбеддинги из-за своей статичной природы не умеют делать это напрямую.
2) Это помогает определять направление зависимости в словах, то есть выделять объекты и субъекты. Проще говоря, делать матрицу оценок внимания ассиметричной.
Если вдруг вам или читателям будет эта тема интересна, то я как раз вчера делал статью про FlashAttention на Triton, где про это рассказывается более подробно и не только.