Что такое трансформерная архитектура (Transformer)?

Ответ

Трансформер (Transformer) — это архитектура нейронной сети, представленная в 2017 году в работе «Attention Is All You Need». Ее ключевая инновация — полный отказ от рекуррентных (RNN) и сверточных (CNN) слоев для обработки последовательностей в пользу механизма внимания (attention), что позволило эффективно распараллеливать вычисления и лучше улавливать долгосрочные зависимости.

Основные компоненты архитектуры (на примере энкодера):

  1. Входные эмбеддинги: Слова преобразуются в векторы.
  2. Позиционное кодирование (Positional Encoding): Добавляет информацию о порядке слов, так как сам механизм внимания не учитывает последовательность.
  3. Слой самовнимания (Self-Attention): Позволяет каждому элементу последовательности «взвешивать» влияние всех остальных элементов. Вычисляется как взвешенная сумма значений (Value), где веса определяются совместимостью запроса (Query) с ключом (Key). [ text{Attention}(Q, K, V) = text{softmax}(frac{QK^T}{sqrt{d_k}})V ]
  4. Многоголовое внимание (Multi-Head Attention): Несколько независимых механизмов самовнимания работают параллельно, что позволяет модели фокусироваться на разных типах взаимосвязей (например, синтаксических и семантических).
  5. Позиционно-зависимая полносвязная сеть (Position-wise Feed-Forward Network): Применяется независимо к каждому позиционному вектору после слоя внимания.
  6. Остаточные связи (Residual Connections) и нормализация слоя (LayerNorm): Стабилизируют обучение глубокой сети.

Упрощенная реализация самовнимания на PyTorch:

import torch
import torch.nn as nn
import torch.nn.functional as F

class SelfAttention(nn.Module):
    def __init__(self, embed_size, heads):
        super(SelfAttention, self).__init__()
        self.embed_size = embed_size
        self.heads = heads
        self.head_dim = embed_size // heads
        assert self.head_dim * heads == embed_size, "Embed size must be divisible by heads"
        self.values = nn.Linear(self.head_dim, self.head_dim, bias=False)
        self.keys = nn.Linear(self.head_dim, self.head_dim, bias=False)
        self.queries = nn.Linear(self.head_dim, self.head_dim, bias=False)
        self.fc_out = nn.Linear(heads * self.head_dim, embed_size)
    def forward(self, values, keys, query, mask=None):
        N = query.shape[0]
        value_len, key_len, query_len = values.shape[1], keys.shape[1], query.shape[1]
        # Разделение эмбеддингов на головы
        values = values.reshape(N, value_len, self.heads, self.head_dim)
        keys = keys.reshape(N, key_len, self.heads, self.head_dim)
        queries = query.reshape(N, query_len, self.heads, self.head_dim)
        energy = torch.einsum("nqhd,nkhd->nhqk", [queries, keys]) / (self.head_dim ** 0.5)
        if mask is not None:
            energy = energy.masked_fill(mask == 0, float("-1e20"))
        attention = torch.softmax(energy, dim=3)
        out = torch.einsum("nhql,nlhd->nqhd", [attention, values])
        out = out.reshape(N, query_len, self.heads * self.head_dim)
        return self.fc_out(out)

Применение:

  • NLP: Является основой для BERT (двунаправленный энкодер), GPT (авторегрессионный декодер), T5 и других SOTA-моделей.
  • Компьютерное зрение: Vision Transformer (ViT) разбивает изображение на патчи и обрабатывает их как последовательность.
  • Мультимодальные задачи: Модели типа CLIP, DALL-E.

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

Ответ 18+ 🔞

Давай разберемся, что это за зверь такой — трансформер. Ну, не тот, что в кино, а тот, что в нейросетях. Представь, что в 2017 году какие-то умные ребята взяли и сказали: «А пошли вы все нахуй со своими RNN и CNN для текстов!». И выкинули эту статью «Attention Is All You Need». И там, блядь, всё держится на механизме внимания. Это как если бы ты читал предложение и вместо того, чтобы тупо по словам ползти, мог сразу взглядом выцепить, что вот это «он» относится к тому «Васе» в начале абзаца. Удобно, да? И главное — это всё можно на видеокартах параллелить, овердохуища быстрее получается.

Из чего эта штука собрана, если брать энкодер (кодировщик):

  1. Входные эмбеддинги. Словам дают цифровые паспорта — векторы. Без этого нихуя не работает.
  2. Позиционное кодирование. А вот это хитрая жопа. Сам механизм внимания не понимает, какое слово за каким идёт. Ему похуй. Поэтому к этим векторам прикручивают специальные метки о порядке, как будто номера в гардеробе вешаешь.
  3. Слой самовнимания. Вот тут самое мясо, ёпта. Каждый элемент (слово) может «посмотреть» на всех остальных и решить, на кого ему обратить больше внимания. Формула там, конечно, softmax(QK^T / sqrt(d_k)) * V, но если по-простому: запрос (Query) ищет, с какими ключами (Key) он совпадает, а потом берет оттуда значения (Value). Получается взвешенная сумма.
  4. Многоголовое внимание. Это чтобы не быть тупым однобоким мудаком. Вместо одного такого механизма их запускают несколько параллельно. Одна «голова» может смотреть на грамматические связи, другая — на смысловые. Потом результаты склеивают. Гениально и просто.
  5. Полносвязная сеть для каждой позиции. После внимания каждый вектор прогоняют через свою маленькую нейросеть. Независимо от соседей.
  6. Остаточные связи и нормализация. Без этого глубокую сеть не обучить — всё развалится. Это как подпорки, которые не дают градиентам сдохнуть на полпути.

Вот тебе кусок кода на PyTorch, как это самовнимание выглядит под капотом. Не пугайся:

import torch
import torch.nn as nn
import torch.nn.functional as F

class SelfAttention(nn.Module):
    def __init__(self, embed_size, heads):
        super(SelfAttention, self).__init__()
        self.embed_size = embed_size
        self.heads = heads
        self.head_dim = embed_size // heads
        assert self.head_dim * heads == embed_size, "Embed size must be divisible by heads"
        self.values = nn.Linear(self.head_dim, self.head_dim, bias=False)
        self.keys = nn.Linear(self.head_dim, self.head_dim, bias=False)
        self.queries = nn.Linear(self.head_dim, self.head_dim, bias=False)
        self.fc_out = nn.Linear(heads * self.head_dim, embed_size)
    def forward(self, values, keys, query, mask=None):
        N = query.shape[0]
        value_len, key_len, query_len = values.shape[1], keys.shape[1], query.shape[1]
        # Разделение эмбеддингов на головы
        values = values.reshape(N, value_len, self.heads, self.head_dim)
        keys = keys.reshape(N, key_len, self.heads, self.head_dim)
        queries = query.reshape(N, query_len, self.heads, self.head_dim)
        energy = torch.einsum("nqhd,nkhd->nhqk", [queries, keys]) / (self.head_dim ** 0.5)
        if mask is not None:
            energy = energy.masked_fill(mask == 0, float("-1e20"))
        attention = torch.softmax(energy, dim=3)
        out = torch.einsum("nhql,nlhd->nqhd", [attention, values])
        out = out.reshape(N, query_len, self.heads * self.head_dim)
        return self.fc_out(out)

И где эту хуйню применяют? Да везде, блядь:

  • Обработка текста (NLP): Это основа всех нынешних монстров. BERT — это типа энкодер, который смотрит на текст сразу целиком. GPT — это декодер, который генерирует текст слово за словом, как сумасшедший рассказчик. Все они — детища трансформера.
  • Компьютерное зрение: Да-да, и картинки тоже. Vision Transformer (ViT) режет изображение на кусочки-патчи и кормит их трансформеру как последовательность. И он, уёбок, справляется, иногда даже лучше классических CNN.
  • Мультимодальные задачи: Когда нужно и текст, и картинки понимать вместе. CLIP, DALL-E — все эти ваши нейрохудожники оттуда.

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