От нейрона до механизма внимания: подробная лекция с математикой, кодом и интуицией.
Для тех, кто хочет по-настоящему понять, как работает GPT, BERT и всё, что после.
Каждая ячейка показывает, насколько сильно токен строки «смотрит» на токен столбца при обработке фразы. Чем ярче — тем больше внимания.
01
Основы искусственных нейронных сетей
Искусственная нейронная сеть (ИНС) — это вычислительный граф, вдохновлённый биологической нейронной сетью. Каждый узел — нейрон — выполняет простую операцию: взвешенную сумму входов плюс нелинейность. Сила метода — в композиции таких узлов в глубокие слои.
💡
Ключевая идея
ИНС — это универсальный аппроксиматор. Согласно теореме Хорника (1989), сеть с одним скрытым слоем и нелинейной функцией активации способна приблизить любую непрерывную функцию с произвольной точностью при достаточном количестве нейронов.
Конкретный числовой пример. Пусть нейрон получает x₁=1, x₂=0, x₃=−1, веса w₁=0.5, w₂=0.3, w₃=−0.8, смещение b=0.1. Тогда: z = 0.5·1 + 0.3·0 + (−0.8)·(−1) + 0.1 = 1.4. После ReLU: y = max(0, 1.4) = 1.4. Нейрон «активировался». Если бы z = −0.3, то y = 0 — нейрон «молчит».
Рассмотрим один полносвязный слой. Входной вектор x ∈ ℝⁿ преобразуется в выходной вектор y ∈ ℝᵐ:
z = W · x + b y = f(z) # W ∈ ℝᵐˣⁿ — матрица весов; b ∈ ℝᵐ — вектор смещений; f — функция активации
Для батча из B примеров: X ∈ ℝB×n, тогда Z = X @ W.T + b. Это единственная операция во всём слое — и именно поэтому современные ускорители (GPU/TPU) так эффективны: они заточены под умножение матриц (GEMM).
# PyTorch: вручную vs nn.Linearimport torch
import torch.nn as nn
B, n, m = 32, 128, 64
X = torch.randn(B, n)
# Вручную
W = torch.randn(m, n, requires_grad=True)
b = torch.zeros(m, requires_grad=True)
Z = X @ W.T + b # (B, m)# Через nn.Linear (то же самое внутри)
layer = nn.Linear(n, m)
Z = layer(X) # (B, m)
Что происходит при увеличении глубины: слой 1 учит простые комбинации признаков (например, «цена > 1M И площадь > 80м²»); слой 2 — комбинации этих комбинаций («новостройка В дорогом районе»); слой 3 — высокоуровневое решение («элитная недвижимость»). Именно иерархия представлений делает глубокие сети мощнее мелких.
Без нелинейности стек линейных слоёв остаётся линейным преобразованием. Функция активации ломает линейность, позволяя сети учить сложные зависимости.
Функция
Формула
Диапазон
Плюсы / Минусы
Sigmoid
σ(x) = 1/(1+e⁻ˣ)
(0, 1)
Интерпретируема как вероятность; страдает от vanishing gradient на насыщении
Tanh
tanh(x) = (eˣ−e⁻ˣ)/(eˣ+e⁻ˣ)
(−1, 1)
Центрирована в 0 — лучше sigmoid; но vanishing gradient остаётся
ReLU
max(0, x)
[0, +∞)
Быстрая, нет насыщения при x>0; «умирающие нейроны» при x≤0
GELU
x·Φ(x), Φ — CDF Гаусса
ℝ
Стандарт в трансформерах (BERT, GPT); дифференцируема везде
SiLU/Swish
x·σ(x)
ℝ
Используется в LLaMA, PaLM; гладкая, немного лучше GELU на практике
⚠️
Проблема vanishing gradient
Sigmoid и Tanh насыщаются: при |x| ≫ 0 производная стремится к 0. При глубоком стеке слоёв градиент, умноженный на множество таких малых чисел, экспоненциально уменьшается — сеть перестаёт учиться в нижних слоях. ReLU решает это частично; трансформеры решают это остаточными соединениями (см. ниже).
Обучение ИНС — это минимизация функции потерь L. Алгоритм обратного распространения вычисляет частные производные L по каждому параметру, применяя цепное правило дифференцирования от выхода к входу.
# PyTorch делает это автоматически (autograd)
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)
for x, y in dataloader:
optimizer.zero_grad() # Очищаем градиенты
logits = model(x) # Forward pass
loss = criterion(logits, y) # Считаем L
loss.backward() # Backward pass (autograd)
optimizer.step() # Обновляем параметры
⚡
Оптимизаторы: от SGD до AdamWSGD: W ← W − η·∇W. Простой, медленно сходится. Momentum: добавляет «инерцию» — накапливает градиент, меньше осциллирует. Adam (2015): адаптивный LR для каждого параметра — усредняет первый момент (m̂) и второй (v̂) градиента, делит на √v̂. AdamW: Adam + weight decay без смешивания с градиентом — стандарт для трансформеров. При обучении LLM используют также gradient clipping (ограничение нормы градиента ≤1.0) для стабильности.
02
Основные архитектуры ИНС
Полносвязные сети (MLP / Dense)
Каждый нейрон слоя L соединён с каждым нейроном слоя L+1. Хорошо работают на табличных данных. Не используют пространственную или временную структуру входа — в этом их слабость.
Пример применения: классификация транзакций по набору числовых признаков, предсказание цены квартиры.
Свёрточные сети (CNN)
Применяют разделяемые по пространству фильтры (kernels). Фильтр 3×3 скользит по изображению и вычисляет свёртку — локальную взвешенную сумму. Ключевые свойства: локальная связность, разделение весов, инвариантность к трансляции.
# Свёрточный слой: обнаруживает локальные паттерны
conv = nn.Conv2d(
in_channels=3,
out_channels=64,
kernel_size=3,
padding=1
)
# Каждый из 64 фильтров 3×3 обнаруживает свой паттерн# (края, текстуры, формы...)
🔍
Почему CNN работают для изображений?
Кошка — это кошка, где бы она ни стояла на фото. CNN использует один и тот же фильтр для всего изображения (weight sharing), что резко снижает число параметров и даёт инвариантность к позиции.
Рекуррентные сети (RNN)
Для последовательных данных (текст, временные ряды) нужна память о прошлом. RNN хранит скрытое состояние hₜ, которое обновляется на каждом шаге:
hₜ = tanh(Wₕ · hₜ₋₁ + Wₓ · xₜ + b)
yₜ = Wᵧ · hₜ # hₜ — «память» о всех предыдущих токенах
Проблема: при длинных последовательностях градиент при обратном проходе перемножается много раз на одну и ту же матрицу Wₕ. Если её спектральный радиус <1 — градиент исчезает (vanishing); >1 — взрывается (exploding). Это делает стандартный RNN практически непригодным для последовательностей длиннее ~20 шагов.
Разработан Хохрайтером и Шмидхубером (1997) специально для решения vanishing gradient. LSTM добавляет ячейку памяти cₜ и три гейта (вентиля), управляющих потоком информации:
cₜ = fₜ ⊙ cₜ₋₁ + iₜ ⊙ g̃ₜ ← обновление памяти
hₜ = oₜ ⊙ tanh(cₜ) ← скрытое состояние
Символ ⊙ — поэлементное умножение. Ключевое: ячейка памяти cₜ распространяется через сложение, а не умножение — это создаёт «шоссе» для градиентов, решая vanishing gradient.
GRU (Gated Recurrent Unit)
Упрощённая версия LSTM (Чо и др., 2014): только два гейта вместо трёх, нет отдельной ячейки памяти. Быстрее в обучении, на практике часто не уступает LSTM.
Простая рекуррентность. Плохо масштабируется на длинные последовательности из-за vanishing gradient.
🧠
LSTM
Три гейта + ячейка памяти. Долгий SOTA в NLP до 2018 года. Сложнее GRU, чуть мощнее.
⚡
GRU
Два гейта, нет cell state. Быстрее LSTM, часто сопоставим по качеству. Популярен в production.
🔄
Трансформер
Без рекуррентности вообще. Параллелен, масштабируется. Вытеснил RNN/LSTM почти везде.
🎯
Ахиллесова пята RNN-семейства
Все рекуррентные сети обрабатывают последовательность последовательно — шаг за шагом. Это не параллелизуется. При обучении на длинном тексте GPU простаивает, ожидая результата предыдущего шага. Трансформер обрабатывает всю последовательность одновременно — именно это открыло эру масштабирования LLM.
03
Архитектура трансформера
Трансформер предложен в статье «Attention Is All You Need» (Vaswani et al., Google, 2017). Основная идея: полностью отказаться от рекуррентности и свёрток, заменив их механизмом self-attention, который позволяет каждому токену напрямую «смотреть» на любой другой токен последовательности.
Self-Attention: механизм внимания
Self-attention — сердце трансформера. Для каждого токена в последовательности он вычисляет взвешенную сумму значений всех остальных токенов, где веса отражают «релевантность» каждого токена для текущего.
Из входного вектора каждого токена x создаются три вектора через три разные линейные проекции:
Q = X · Wq ← Query: «что я ищу?»
K = X · Wk ← Key: «что я предлагаю?»
V = X · Wv ← Value: «что я передам, если буду выбран?»
# Скалярное произведение Q·Kᵀ измеряет похожесть запроса и ключа
scores = Q · Kᵀ / √dₖ
weights = softmax(scores) ← нормировка в вероятности (строки суммируются в 1)
output = weights · V ← взвешенная сумма значений
🔢
Зачем делить на √dₖ?
При большой размерности dₖ скалярные произведения Q·Kᵀ растут по величине, что приводит к насыщению softmax (веса стремятся к 0 или 1, а градиенты исчезают). Деление на √dₖ нормирует дисперсию и стабилизирует обучение. Это эквивалентно тому, что если компоненты Q и K — независимые случайные переменные с μ=0, σ=1, то Q·K имеет дисперсию dₖ, а после деления — дисперсию 1.
import torch.nn.functional as F
defscaled_dot_product_attention(Q, K, V, mask=None):
"""
Q: (batch, heads, seq_len, d_k)
K: (batch, heads, seq_len, d_k)
V: (batch, heads, seq_len, d_v)
"""
d_k = Q.shape[-1]
# Матрица оценок: насколько каждый токен внимателен к другим
scores = Q @ K.transpose(-2, -1) / d_k ** 0.5# (B, H, T, T)if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9) # Маскируем будущее
weights = F.softmax(scores, dim=-1)
return weights @ V, weights # Взвешенная сумма + сами веса
Multi-Head Attention
Одна пара Q, K, V захватывает только один тип зависимостей. Multi-Head Attention запускает h независимых «голов» внимания параллельно, каждая с проекциями меньшей размерности dₖ = d_model / h. Результаты конкатенируются и проецируются обратно.
Зачем несколько голов? Разные головы специализируются на разных типах отношений:
📍
Позиционная голова
Обращает внимание на соседние токены — локальный контекст.
🔗
Синтаксическая голова
Учит зависимости между подлежащим и сказуемым, независимо от расстояния.
🌐
Семантическая голова
Связывает кореференции: «она» → «Мария» через весь абзац.
❓
Задача-специфичная
Fine-tuning развивает головы под конкретный тип задачи.
classMultiHeadAttention(nn.Module):
def__init__(self, d_model, n_heads):
super().__init__()
self.d_k = d_model // n_heads
self.n_heads = n_heads
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)
defforward(self, Q, K, V, mask=None):
B = Q.shape[0]
# Проецируем и разбиваем по головам
Q = self.W_q(Q).view(B, -1, self.n_heads, self.d_k).transpose(1,2)
K = self.W_k(K).view(B, -1, self.n_heads, self.d_k).transpose(1,2)
V = self.W_v(V).view(B, -1, self.n_heads, self.d_k).transpose(1,2)
attn_out, _ = scaled_dot_product_attention(Q, K, V, mask)
# Конкатенируем головы и проецируем обратно
attn_out = attn_out.transpose(1,2).contiguous().view(B, -1, self.n_heads * self.d_k)
return self.W_o(attn_out)
Positional Encoding
Self-attention не знает, в каком порядке стоят токены — операция симметрична. Чтобы передать позиционную информацию, к эмбеддингам прибавляют позиционное кодирование.
В оригинальной статье — детерминированные синусоиды разных частот:
# pos — позиция токена; i — индекс измерения # Низкие частоты кодируют грубую позицию, высокие — тонкую
Почему синусоиды? Модель может обобщаться на длины, которых не видела при обучении (например, обучалась на 512 токенах, а инференс на 1024). Современные модели (RoPE в LLaMA, ALiBi в MPT) используют обучаемые позиционные кодирования или относительные смещения — они лучше экстраполируют.
Residual-соединения и Layer Normalization
Каждый подблок трансформера (Attention и FFN) обёрнут в residual connection (остаточное соединение):
output = LayerNorm(x + SubLayer(x))
# SubLayer — это Attention или FFN # x прибавляется «напрямую» — создаёт градиентное шоссе
Зачем residual? Идея из ResNet (He et al., 2015): при обратном распространении градиент идёт по двум путям — через SubLayer (сложный) и напрямую через сложение (+). Прямой путь гарантирует, что градиент дойдёт до нижних слоёв без затухания, даже если SubLayer выдаёт малый градиент.
Layer Normalization нормирует активации по признакам (не по батчу как Batch Norm), что стабилизирует обучение независимо от размера батча:
Pre-LN vs Post-LN
В оригинальном трансформере LayerNorm стоит после сложения (Post-LN). В GPT-2 и далее — перед SubLayer (Pre-LN): x + SubLayer(LN(x)). Pre-LN обучается стабильнее без warmup и стал стандартом в LLM.
Feed-Forward Sublayer и полная архитектура
После attention каждый токен проходит через позиционно-независимую FFN — одинаковую для всех позиций, но с разными активациями:
FFN работает как ассоциативная память: каждый нейрон первого слоя — это «паттерн», активирующийся при определённом контексте. Исследования показывают, что фактические знания (факты) во многом хранятся именно в весах FFN, а не в attention.
04
Transfer Learning
Transfer learning — обучение модели на одной задаче (обычно большой и дешёвой в получении данных), а затем перенос выученных представлений для решения другой задачи (часто маленькой и дорогостоящей в разметке).
🚀
Почему это работает?
Нижние слои сети учат универсальные признаки: края и текстуры в CNN; синтаксис и морфологию в LLM. Верхние слои — задача-специфичные признаки. Перенос позволяет «бесплатно» получить нижние слои, обученные на гигантских данных, и дообучить только верхние.
Парадигма в NLP: Pretrain → Fine-tune
1
Предобучение (Pretraining): модель обучается на огромном корпусе (Common Crawl, Wikipedia, GitHub) на self-supervised задаче — предсказать следующий токен (GPT) или восстановить замаскированный токен (BERT). Данных — триллионы токенов, вычислений — тысячи GPU-дней.
2
Fine-tuning (тонкая настройка): берём предобученные веса и дообучаем на размеченных данных целевой задачи. Данных может быть всего несколько тысяч примеров. Обучение — часы на одном GPU.
3
RLHF / Instruction Tuning (опционально): после fine-tuning — дополнительное выравнивание на предпочтения человека (как в ChatGPT, Claude).
BERT vs GPT: два стиля предобучения
Параметр
BERT (2018, Google)
GPT (2018–2023, OpenAI)
Архитектура
Encoder-only
Decoder-only
Задача pretraining
Masked LM (MLM): восстановить 15% замаскированных токенов
Causal LM: предсказать следующий токен
Направление внимания
Двустороннее (bidirectional)
Одностороннее (left-to-right)
Сильные стороны
Классификация, NER, QA с ответом
Генерация текста, reasoning, few-shot
Типичный fine-tune
+ classification head, 3 эпохи
Prompt-based, RLHF
Современные потомки
RoBERTa, DeBERTa, ModernBERT
GPT-4, LLaMA 3, Claude, Gemini
Практические стратегии Fine-tuning
❄️
Feature Extraction
Замораживаем всю предобученную модель. Используем выходные эмбеддинги как признаки для отдельного классификатора. Быстро, мало памяти.
🔥
Full Fine-tuning
Обновляем все веса. Максимальное качество, но дорого и опасно — можно перетренировать или «забыть» предобученные знания.
🎯
LoRA / QLoRA
Добавляем low-rank матрицы ΔW = A·B к весам. Обучаем только их (~0.1% параметров). SOTA эффективность. Стандарт для LLM.
💬
Prompt Tuning
Обучаем только «мягкие токены» (soft prompts), добавленные к входу. Экстремально эффективно по памяти.
# LoRA: добавляем обучаемые матрицы к заморозенным весамfrom peft import get_peft_model, LoraConfig, TaskType
config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=16, # ранг матриц A, B
lora_alpha=32, # масштабирующий коэффициент
lora_dropout=0.05,
target_modules=["q_proj", "v_proj"] # только Q и V attention
)
model = get_peft_model(base_model, config)
model.print_trainable_parameters()
# trainable params: 4,194,304 || all params: 6,742,609,920 || 0.06%
⚠️
Catastrophic Forgetting
При агрессивном fine-tuning модель может «забыть» знания из pretraining. Лечится: низкий learning rate (1e-5 – 5e-5), warmup scheduler, LoRA, или добавление данных pretraining в смесь для fine-tuning.
Zero-shot и Few-shot Learning
Мощные языковые модели (GPT-4, Claude, LLaMA 3) демонстрируют emergent-способности:
0
Zero-shot: описываем задачу в prompt, модель решает без примеров. «Переведи на французский: Hello world»
k
Few-shot: даём 2–10 примеров в контексте (in-context learning). Модель угадывает паттерн без обновления весов — только чтение контекста.
05
Сводная таблица и дорожная карта
Концепция
Зачем
Без чего не обойтись
Матрицы весов W
Линейное преобразование признаков
Основа любого слоя
Функция активации
Нелинейность → сложные функции
ReLU/GELU в каждом слое
Backprop
Вычислить градиенты для обновления
Autograd в PyTorch/JAX
CNN
Локальные паттерны в изображениях
Weight sharing + pooling
LSTM/GRU
Долгосрочная память в последовательностях
Гейты управляют потоком
Self-Attention
Глобальные зависимости, параллелизм
Q·Kᵀ/√dₖ → softmax → V
Multi-Head
Несколько типов зависимостей одновременно
h голов → concat → проекция
Positional Encoding
Порядок токенов в attention
Sinusoidal или RoPE
Residual + LN
Стабильность обучения глубоких сетей
x + SubLayer(LN(x))
Transfer Learning
Не учить с нуля, переиспользовать знания
Pretraining → fine-tuning
LoRA
Дешёвый fine-tuning больших моделей
ΔW = A·B, обучаем A и B
🗺️
Что читать дальше
Оригинальные статьи: «Attention Is All You Need» (2017), «BERT» (2018), «Language Models are Few-Shot Learners» GPT-3 (2020), «LoRA» (2021), «LLaMA 2» (2023). Практика: реализуй мини-трансформер с нуля — Andrej Karpathy's «nanoGPT» (GitHub) — 300 строк, полная архитектура.