Обо мне
Привет, меня зовут Василий Техин, и последние 6 лет я живу в мире машинного обучения — от первых шагов с линейной регрессией до экспериментов с современными VLm.
Когда я только начинал, мне не хватало материалов, где сложные концепции объяснялись бы без формул на трех страницах и обязательного PhD по математике. Я верил (и верю до сих пор), что любую идею можно разложить на понятные кирпичики — так, чтобы после прочтения у вас в голове складывалась цельная картина, а не россыпь терминов. Поэтому я начал эту серию статей, которая имеет целью объяснить "на пальцах", как устроены архитектуры сыгравшие большую роль в развитие машинного обучения.
Часть 1: ResNet-18 — Архитектура, покорившая глубину
Пролог: Революция в компьютерном зрении
Представьте, что лингвист внезапно стал экспертом по живописи. Именно это произошло в 2020 году, когда архитектура для обработки текста — трансформеры — научилась "видеть" изображения (оригинальная статья An Image is Worth 16x16 Words). Vision Transformer (ViT) доказал: для понимания картинок не обязательны свёртки!
Ключевая идея: Разрежьте изображение на кусочки-патчи, обработайте их как слова в предложении — и запустите через классический трансформер. Гениально? Да! Универсально? Не всегда.

Разберём на простом примере
Как ViT из картинки делает предсказание?
Возьмём задачу: определить "человек" (класс 0) или "машина" (класс 1) на изображении 4×4 пикселя.
Возьмем тоже простое крошечное изображение 4×4, что и в предыдущей статье про Resnet:
1. Разбиение на патчи: "Пазл из пикселей"
Канал R: Канал G: Канал B:
[[2, 4, 1, 3] [[2, 4, 1, 3] [[2, 4, 1, 3]
[0, 5, 8, 2] [0, 5, 8, 2] [0, 5, 8, 2]
[4, 2, 7, 1] [4, 2, 7, 1] [4, 2, 7, 1]
[3, 6, 0, 4]] [3, 6, 0, 4]] [3, 6, 0, 4]]
Разрежем на 4 патча 2×2:
Патч 1 (верх-лево): R[[2,4],[0,5]] G[[2,4],[0,5]] B[[2,4],[0,5]] → Вектор [2,4,0,5,2,4,0,5,2,4,0,5]
Патч 2 (верх-право): R[[1,3],[8,2]] G[[1,3],[8,2]] B[[1,3],[8,2]] → [1,3,8,2,1,3,8,2,1,3,8,2]
Патч 3 (низ-лево): R[[4,2],[3,6]] G[[4,2],[3,6]] B[[4,2],[3,6]] → [4,2,3,6,4,2,3,6,4,2,3,6]
Патч 4 (низ-право): R[[7,1],[0,4]] G[[7,1],[0,4]] B[[7,1],[0,4]] → [7,1,0,4,7,1,0,4,7,1,0,4]
Аналогия: Как слова в предложении несут смысл, так патчи несут визуальную информацию. Патч 1 = небо, Патч 4 = колесо.
2. Линейное проецирование: "Перевод на язык модели"
Каждый 12-мерный вектор патча сжимаем до 4-мерного эмбеддинга:
# Веса W (12×4):
W = [[0.1, 0.2, -0.3, 0.4],
[0.5, -0.6, 0.7, 0.8],
...
[0.9, -1.0, 0.2, 0.3]]
# Для Патча 1:
z1 = [2*0.1 + 4*0.5 + 0*0.2 + ... + 5*0.9] = [3.8, -2.1, 1.4, 0.7]
Пояснение: Как переводчик переводит слова между языками, так W
переводит пиксели в "язык трансформера".
3. Позиционные эмбеддинги: "Где находится патч"
Без информации о позиции модель не отличит небо (верх) от колеса (низ). 2 подхода:
? Фиксированные синусоидальные эмбеддинги (как в оригинальных трансформерах):
# Формула:
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
# Для Патча 1 (pos=0) при d_model=4:
z1 = [3.8, -2.1, 1.4, 0.7] + [sin(0), cos(0), sin(0), cos(0)]
= [3.8 + 0, -2.1 + 1, 1.4 + 0, 0.7 + 1]
= [3.8, -1.1, 1.4, 1.7]
Почему синусы/косинусы? Они кодируют позиции волнами разной частоты — модель легко понимает "расстояние" между позициями.
? Обучаемые эмбеддинги (как в оригинальном ViT):
pos_embeddings = nn.Parameter(torch.randn(4, 4)) # 4 позиции × 4 размерности
z1 = [3.8, -2.1, 1.4, 0.7] + [0.1, 0.3, -0.2, 0.5] = [3.9, -1.8, 1.2, 1.2]
Плюсы/минусы:
Тип |
Преимущества |
Недостатки |
---|---|---|
Синусоидальные |
Работает для любых длин последовательностей |
Менее гибкие |
Обучаемые |
Лучше адаптируются к данным |
Требуют фиксированной длины |
Примеры из практики:
ALBERT: Использует синусоидальные эмбеддинги для экономии параметров
DeBERTa: Комбинирует абсолютные и относительные позиционные эмбеддинги
Swin Transformer: Вводит "сдвигаемые окна" для эффективного учёта позиций
4. [КЛАСС]-токен: "Учимся обобщать"
Добавляем специальный вектор, который собирает информацию обо всех патчах:
z0 = [0.9, -0.3, 1.1, 0.4] # Обучаемый параметр!
Вход трансформера: [z0, z1, z2, z3, z4] # z0 всегда на первом месте
Аналогия: Этот токен — как директор на совещании: слушает доклады отделов (патчей) и формирует общую картину.
Трансформерный блок: "Мозг ViT"

Шаг 1: Self-Attention — "Поиск связей"
Ключевые компоненты:
Query (Q): "Вопрос" от текущего токена ("Что вокруг меня?")
Key (K): "Описание" других токенов ("Я — небо")
Value (V): Фактическое содержание токена
Как работает для z1 (небо):
# 1. Создаём Q, K, V для всех токенов:
Q_z1 = z1 * W_Q = [3.9*0.4, -1.8*(-0.1), ...] = [1.56, 0.18, ...]
K_z3 = z3 * W_K = [0.2, -1.1, 0.5, 0.7] # Для z3 (дерево)
# 2. Считаем "сходство" между z1 и z3:
score = Q_z1 • K_z3 / sqrt(4) = (1.56*0.2 + 0.18*(-1.1) + ...) / 2 = 0.85
# 3. Взвешенная сумма Values:
attention_z1 = 0.85*V_z3 + 0.1*V_z0 + ... # V_z3 = [0.4, 0.1, -0.2, 0.3]
Итог: Патч "небо" (z1) сильнее всего взаимодействует с "деревом" (z3), слабее — с "колесом" (z4).
Шаг 2: Multi-Head Attention — "Команда экспертов"
Вместо одного "взгляда" используем несколько параллельных attention-блоков:
# Пример для 2 heads:
head1 = attention(Q1, K1, V1) # Специализируется на цветах
head2 = attention(Q2, K2, V2) # Специализируется на формах
combined = concat(head1, head2) * W_out # Объединяем результаты
Зачем? Так модель одновременно анализирует разные аспекты изображения.
Шаг 3: MLP — "Углубляем понимание"
После внимания каждый вектор проходит через "мини-мозг":
h1 = [1.2, -0.3, 0.8, 1.1] →
GeLU(h1 * W1 + b1) → # W1: расширяем 4→8 размерностей
h1 * W2 + b2 → # W2: сжимаем 8→4 → [0.9, -0.2, 1.4, 0.3]
Пояснение: Как ассистент, который углубляет заметки после совещания: выделяет главное, отбрасывает шум.
Предсказание: Итоговая картина
После 12 трансформерных блоков [КЛАСС]-токен содержит сжатое представление всего изображения:
z0_final = [0.2, 1.8, -0.4, 0.9] # После всех слоёв
# Линейный классификатор:
Веса_класса0 = [1.1, 0.3, -0.7, 0.5]
Веса_класса1 = [0.4, -0.9, 0.2, 1.3]
Логиты = [
0.2*1.1 + 1.8*0.3 + (-0.4)*(-0.7) + 0.9*0.5 = 1.43, # "машина"
0.2*0.4 + 1.8*(-0.9) + (-0.4)*0.2 + 0.9*1.3 = -0.25 # "человек"
]
Softmax: [e¹.⁴³, e⁻⁰.²⁵] / сумма = [0.84, 0.16] → 84% "машина"
Полный путь данных:
Пиксели → Патчи → Эмбеддинги → 12×[Attention+MLP] → [CLS]-токен → Классификатор
ViT в коде: Главные компоненты
1. Разбиение на патчи
class PatchEmbed(nn.Module):
def __init__(self, img_size=224, patch_size=16, embed_dim=768):
super().__init__()
self.proj = nn.Conv2d(3, embed_dim, kernel_size=patch_size, stride=patch_size)
def forward(self, x):
# (B, 3, 224, 224) → (B, 768, 14, 14) → (B, 196, 768)
x = self.proj(x).flatten(2).transpose(1, 2)
return x
2. Синусоидальные позиционные эмбеддинги + [CLS]
class ViT(nn.Module):
def __init__(self, num_patches, embed_dim):
super().__init__()
# [CLS]-токен
self.cls_token = nn.Parameter(torch.randn(1, 1, embed_dim))
# Синусоидальные позиционные эмбеддинги
position = torch.arange(0, num_patches+1).unsqueeze(1)
div_term = torch.exp(torch.arange(0, embed_dim, 2) * (-math.log(10000.0) / embed_dim))
pe = torch.zeros(num_patches+1, embed_dim)
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
self.register_buffer('pe', pe.unsqueeze(0)) # (1, num_patches+1, embed_dim)
def forward(self, x):
# Добавляем [CLS]-токен
cls_tokens = self.cls_token.expand(x.shape[0], -1, -1)
x = torch.cat([cls_tokens, x], dim=1) # (B, num_patches+1, embed_dim)
# Добавляем позиционные эмбеддинги
x = x + self.pe
return x
3. Трансформерный блок
class TransformerBlock(nn.Module):
def __init__(self, dim, num_heads=4):
super().__init__()
# Multi-head Attention
self.norm1 = nn.LayerNorm(dim)
self.attn = nn.MultiheadAttention(dim, num_heads)
# MLP
self.norm2 = nn.LayerNorm(dim)
self.mlp = nn.Sequential(
nn.Linear(dim, 4*dim),
nn.GELU(),
nn.Linear(4*dim, dim)
)
def forward(self, x):
# 1. Self-Attention + skip-connection
residual = x
x = self.norm1(x)
attn_out, _ = self.attn(x, x, x)
x = residual + attn_out
# 2. MLP + skip-connection
residual = x
x = self.norm2(x)
x = residual + self.mlp(x)
return x
Как ViT учится?
4 ключевых этапа:
-
Прямой проход:
5 токенов (4 патча + [CLS]) проходят через 12 блоков
На каждом шаге: Attention → MLP → LayerNorm
-
Классификация:
Только [CLS]-токен после последнего слоя → линейный классификатор
-
Расчёт ошибки:
Кросс-энтропия между предсказанием и меткой:
loss = -log(0.84) # Если метка "машина"
-
Обратное распространение:
-
Градиенты обновляют:
Веса проецирования патчей (
W
из шага 2)Параметры Q/K/V в каждом блоке
Веса MLP
Позиционные эмбеддинги (если обучаемые)
-
Почему ViT — это не всегда лучше ResNet?
✅ Сильные стороны ViT:
Глобальный контекст: Видит всю картинку сразу (свёртки видят только локальные области).
Масштабируемость: Чем больше данных — тем лучше качество (JFT-300M: 89.3% accuracy).
Унификация: Одна архитектура для текста, аудио и изображений.
❌ Слабые стороны ViT:
Данные: Требует в 10-100 раз больше обучающих изображений, чем ResNet.
Индуктивные ограничения: Плохо переносит изменения размера изображения (в отличие от свёрток).
Вычислительная сложность: Квадратичный рост времени работы относительно числа патчей.
Когда выбирать:
? ViT: Если у вас >1 млн изображений и нужна state-of-the-art точность.
? ResNet: Если данных мало (<100K) или нужна реальная скорость (мобильные приложения).
Философский итог
ViT не "убил" свёрточные сети — он показал, что внимание может быть универсальным механизмом обучения. Но как и в жизни, универсальных решений нет: ResNet остаётся рабочим инструментом, а ViT — мощным, но требовательным прорывом.
В следующей части: "Сначала мы учим модели видеть, потом — воображать. DiT — следующий шаг к искусственному воображению."
P.S. Ошибки в статье? Хотите глубже разобрать attention? Пишите в комментариях!
Проверь себя
Почему патч 4×4 пикселей нельзя подавать напрямую в трансформер?
Зачем ViT [CLS]-токен, если можно усреднить все патчи?
Комментарии (2)
Flokis_guy
28.06.2025 13:48Vision Transformer (ViT) доказал: для понимания картинок не обязательны свёртки!
Я больше скажу, не нужны ни свертки, ни трансформеры. Можно вернуться к истокам используя MLP и получить отличные результаты.
S_A
спасибо! доступно.
но задача классификации сейчас не самая полезная в CV. есть ли трансформеры для сегментации? ещё бы желательно претренированные