Фаза 04 · урок 14
Визуальные трансформеры (Vision Transformers, ViT)
Цель урока: Десятилетие свёртка была синонимом компьютерного зрения. У CNN были сильные индуктивные смещения — локальность, эквивариантность к сдвигу, — которые, как никто не думал, можно заменить. Затем Dosovitskiy et al. (2020) показали, что…
Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.
Содержание урока
- Цели обучения
- Проблема
- Концепция
- Конвейер
- Встраивание патчей
- Токен класса
- Позиционное встраивание
- Блок encoder трансформера
- Почему pre-LN
- Компромисс размера патча
- Рецепт DeiT для обучения ViT на ImageNet-1k
- Swin и ConvNeXt
- Предобучение MAE
- Соберите
- Шаг 1: Встраивание патчей
- Шаг 2: Блок трансформера
- Шаг 3: ViT
- Шаг 4: Проверка работоспособности — инференс одного изображения
- Используйте
- Поставьте
- Упражнения
- Ключевые термины
- Дополнительные материалы
Разрежьте изображение на патчи, считайте каждый патч словом, запустите стандартный transformer. Не оглядывайтесь назад.
Тип: Соберите Языки: Python Предварительные требования: Фаза 7, урок 02 (Self-Attention), фаза 4, урок 04 (Классификация изображений) Время: ~45 минут
Цели обучения
- Реализовать с нуля встраивание патчей, обучаемое позиционное встраивание, токен класса и блоки encoder трансформера, чтобы собрать минимальный ViT
- Объяснить, почему считалось, что ViT нужны огромные объёмы данных для предобучения, пока DeiT и MAE не доказали обратное
- Сравнить ViT, Swin и ConvNeXt по их архитектурным априорным предпосылкам: отсутствуют, локальное оконное внимание, свёрточный backbone
- Дообучить предобученный ViT на небольшом наборе данных с помощью
timmи стандартного рецепта linear probe / fine-tune
Проблема
Десятилетие свёртка была синонимом компьютерного зрения. У CNN были сильные индуктивные смещения — локальность, эквивариантность к сдвигу, — которые, как никто не думал, можно заменить. Затем Dosovitskiy et al. (2020) показали, что простой transformer, применённый к развёрнутым патчам изображения, вообще без свёрточной механики, может на масштабе сравняться с лучшими CNN или превзойти их.
Подвох заключался в словах «на масштабе». ViT на ImageNet-1k проигрывал ResNet. ViT, предобученный на ImageNet-21k или JFT-300M, а затем дообученный на ImageNet-1k, превосходил его. Вывод был таким: у трансформеров нет полезных априорных предпосылок, но они способны выучить их на достаточном объёме данных. Последующая работа (DeiT, MAE, DINO) показала, что при правильных рецептах обучения — сильной аугментации, self-supervised предобучении, дистилляции — ViT хорошо обучаются и на небольших данных.
К 2026 году чистые CNN всё ещё конкурентоспособны на edge-устройствах (ConvNeXt — самый сильный), но трансформеры доминируют во всём остальном: сегментации (Mask2Former, SegFormer), детекции (DETR, RT-DETR), мультимодальности (CLIP, SigLIP), видео (VideoMAE, VJEPA). Структура блока ViT — та, которую нужно знать.
Концепция
Конвейер
Семь шагов. Патчи -> токены -> внимание -> классификатор. Каждый вариант (DeiT, Swin, ConvNeXt, предобучение MAE) изменяет один или два из этих семи, а остальное оставляет без изменений.
Встраивание патчей
Первая свёртка — секрет. Размер ядра 16, stride 16, поэтому изображение 224x224 становится сеткой 14x14 патчей 16x16, каждый из которых проецируется во встраивание размерности 768. Одна эта свёртка одновременно разбивает на патчи и выполняет линейную проекцию.
Input: (3, 224, 224)
Conv (3 -> 768, k=16, s=16, no padding):
Output: (768, 14, 14)
Flatten spatial: (196, 768)
196 патчей = 196 токенов. Размерность признаков каждого токена равна 768 (ViT-B), 1024 (ViT-L) или 1280 (ViT-H).
Токен класса
Один обучаемый вектор, добавляемый в начало последовательности:
tokens = [CLS; patch_1; patch_2; ...; patch_196] shape (197, 768)
После N блоков трансформера выход [CLS] — это глобальное представление изображения. Голова классификации считывает только этот один вектор.
Позиционное встраивание
У трансформеров нет встроенного понятия пространственного положения. Добавьте обучаемый вектор к каждому токену:
tokens = tokens + learned_pos_embedding (also shape (197, 768))
Встраивание является параметром модели; обучение на основе градиента адаптирует его к двумерной структуре изображения. Синусоидальные двумерные альтернативы существуют, но на практике используются редко.
Блок encoder трансформера
Стандартный. Multi-head self-attention, MLP, residual connections, pre-LayerNorm.
x = x + MSA(LN(x))
x = x + MLP(LN(x))
MLP is two-layer with GELU: Linear(d -> 4d) -> GELU -> Linear(4d -> d)
ViT-B/16 содержит 12 таких блоков, каждый с 12 головами внимания; всего 86M параметров.
Почему pre-LN
Ранние трансформеры использовали post-LN (x = LN(x + sublayer(x))) и с трудом обучались глубже 6–8 слоёв без warmup. Pre-LN (x = x + sublayer(LN(x))) стабильно обучает более глубокие сети без warmup. Каждый ViT и каждая современная LLM использует pre-LN.
Компромисс размера патча
- Патчи 16x16 -> 196 токенов, стандартный вариант.
- Патчи 32x32 -> 49 токенов, быстрее, но с меньшим разрешением.
- Патчи 8x8 -> 784 токена, более детально, но стоимость внимания O(n^2) плохо масштабируется.
Более крупные патчи = меньше токенов = быстрее, но меньше пространственных деталей. SwinV2 использует патчи 4x4 в иерархических окнах.
Рецепт DeiT для обучения ViT на ImageNet-1k
Исходному ViT требовался JFT-300M, чтобы превзойти CNN. DeiT (Touvron et al., 2020) обучил ViT-B до 81.8% top-1 только на ImageNet-1k с четырьмя изменениями:
- Тяжёлая аугментация: RandAugment, Mixup, CutMix, Random Erasing.
- Stochastic depth (случайно отбрасывать целые блоки при обучении).
- Repeated augmentation (одно и то же изображение выбирается 3 раза на batch).
- Дистилляция от CNN-учителя (необязательна, дополнительно повышает точность).
Каждый современный рецепт обучения ViT восходит к DeiT.
Swin и ConvNeXt
- Swin (Liu et al., 2021) — внимание на основе окон. Каждый блок уделяет внимание внутри локального окна; чередующиеся блоки сдвигают окно, чтобы смешивать информацию между окнами. Возвращает CNN-подобную априорную предпосылку локальности, сохраняя оператор внимания.
- ConvNeXt (Liu et al., 2022) — переработанная CNN, соответствующая архитектурным решениям Swin (depthwise convs, LayerNorm, GELU, inverted bottleneck). Показала, что разрыв заключается не в «внимании против свёртки», а в «современном рецепте обучения + архитектуре».
В 2026 году ConvNeXt-V2 и Swin-V2 оба готовы к продакшену; правильный выбор зависит от вашего стека инференса (ConvNeXt лучше компилируется для edge) и корпуса предобучения.
Предобучение MAE
Masked Autoencoder (He et al., 2022): случайно замаскируйте 75% патчей, обучите encoder обрабатывать только видимые 25%, обучите небольшой decoder восстанавливать замаскированные патчи по выходу encoder. После предобучения отбросьте decoder и дообучите encoder.
MAE делает ViT обучаемым только на ImageNet-1k, достигает SOTA и является текущим рецептом self-supervised по умолчанию.
Соберите
Шаг 1: Встраивание патчей
import torch
import torch.nn as nn
class PatchEmbedding(nn.Module):
def __init__(self, in_channels=3, patch_size=16, dim=192, image_size=64):
super().__init__()
assert image_size % patch_size == 0
self.proj = nn.Conv2d(in_channels, dim, kernel_size=patch_size, stride=patch_size)
num_patches = (image_size // patch_size) ** 2
self.num_patches = num_patches
def forward(self, x):
x = self.proj(x)
return x.flatten(2).transpose(1, 2)
Одна свёртка, один flatten, один transpose. Это весь этап преобразования изображения в токены.
Шаг 2: Блок трансформера
Pre-LN, multi-head self-attention, MLP с GELU, residual connections.
class Block(nn.Module):
def __init__(self, dim, num_heads, mlp_ratio=4, dropout=0.0):
super().__init__()
self.ln1 = nn.LayerNorm(dim)
self.attn = nn.MultiheadAttention(dim, num_heads, dropout=dropout, batch_first=True)
self.ln2 = nn.LayerNorm(dim)
self.mlp = nn.Sequential(
nn.Linear(dim, dim * mlp_ratio),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(dim * mlp_ratio, dim),
nn.Dropout(dropout),
)
def forward(self, x):
a, _ = self.attn(self.ln1(x), self.ln1(x), self.ln1(x), need_weights=False)
x = x + a
x = x + self.mlp(self.ln2(x))
return x
nn.MultiheadAttention обрабатывает разбиение на головы, scaled dot-product и выходную проекцию. batch_first=True, поэтому формы имеют вид (N, seq, dim).
Шаг 3: ViT
class ViT(nn.Module):
def __init__(self, image_size=64, patch_size=16, in_channels=3,
num_classes=10, dim=192, depth=6, num_heads=3, mlp_ratio=4):
super().__init__()
self.patch = PatchEmbedding(in_channels, patch_size, dim, image_size)
num_patches = self.patch.num_patches
self.cls_token = nn.Parameter(torch.zeros(1, 1, dim))
self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, dim))
self.blocks = nn.ModuleList([
Block(dim, num_heads, mlp_ratio) for _ in range(depth)
])
self.ln = nn.LayerNorm(dim)
self.head = nn.Linear(dim, num_classes)
nn.init.trunc_normal_(self.pos_embed, std=0.02)
nn.init.trunc_normal_(self.cls_token, std=0.02)
def forward(self, x):
x = self.patch(x)
cls = self.cls_token.expand(x.size(0), -1, -1)
x = torch.cat([cls, x], dim=1)
x = x + self.pos_embed
for blk in self.blocks:
x = blk(x)
x = self.ln(x[:, 0])
return self.head(x)
vit = ViT(image_size=64, patch_size=16, num_classes=10, dim=192, depth=6, num_heads=3)
x = torch.randn(2, 3, 64, 64)
print(f"output: {vit(x).shape}")
print(f"params: {sum(p.numel() for p in vit.parameters()):,}")
Около 2.8M параметров — крошечный ViT, посильный для CPU. Реальный ViT-B содержит 86M; это то же определение класса с dim=768, depth=12, num_heads=12.
Шаг 4: Проверка работоспособности — инференс одного изображения
logits = vit(torch.randn(1, 3, 64, 64))
print(f"logits: {logits}")
print(f"probs: {logits.softmax(-1)}")
Должно выполниться без ошибки. Вероятности в сумме дают 1.
Используйте
timm поставляет каждый вариант ViT с предобученными весами ImageNet. Одна строка:
import timm
model = timm.create_model("vit_base_patch16_224", pretrained=True, num_classes=10)
timm — стандарт для production-задач с визуальными трансформерами в 2026 году. Поддерживает ViT, DeiT, Swin, Swin-V2, ConvNeXt, ConvNeXt-V2, MaxViT, MViT, EfficientFormer и десятки других через тот же API.
Для мультимодальных задач (изображение + текст) transformers поставляет CLIP, SigLIP, BLIP-2, LLaVA. Image encoder во всех них — вариант ViT.
Поставьте
Этот урок создаёт:
outputs/prompt-vit-vs-cnn-picker.md— промпт, выбирающий между ViT, ConvNeXt или Swin по размеру набора данных, вычислительным ресурсам и стеку инференса.outputs/skill-vit-patch-and-pos-embed-inspector.md— навык, проверяющий, что формы встраивания патчей и позиционного встраивания ViT соответствуют ожидаемой моделью длине последовательности, и выявляющий самые распространённые ошибки портирования.
Упражнения
- (Легко) Выведите формы каждого промежуточного тензора для прямого прохода через приведённый выше маленький ViT. Подтвердите: вход
(N, 3, 64, 64)-> патчи(N, 16, 192)-> с CLS(N, 17, 192)-> вход классификатора(N, 192)-> выход(N, num_classes). - (Средне) Дообучите предобученный
timmViT-S/16 на наборе synthetic-CIFAR из урока 4. Сравните с дообучением ResNet-18 на тех же данных. Сообщите время обучения и итоговую точность. - (Сложно) Реализуйте предобучение MAE для маленького ViT: замаскируйте 75% патчей, обучите encoder + небольшой decoder восстанавливать замаскированные патчи. Оцените точность linear probe на синтетических данных до и после предобучения.
Ключевые термины
| Термин | Как обычно говорят | Что это на самом деле означает |
|---|---|---|
| Встраивание патчей | «Первая свёртка» | Свёртка с размером ядра = stride = размеру патча; превращает изображение в сетку встраиваний токенов |
| Токен класса | “[CLS]” | Обучаемый вектор, добавленный в начало последовательности токенов; его итоговый выход — глобальное представление изображения |
| Позиционное встраивание | «Обучаемая позиция» | Обучаемый вектор, добавляемый к каждому токену, чтобы transformer знал, откуда пришёл каждый патч |
| Pre-LN | «LayerNorm перед подслоем» | Стабильный вариант трансформера: x + sublayer(LN(x)) вместо LN(x + sublayer(x)) |
| Multi-head attention | «Параллельное внимание» | Стандартное внимание трансформера, разделённое на num_heads независимых подпространств и затем конкатенированное |
| ViT-B/16 | «Base, patch 16» | Канонический размер: dim=768, depth=12, heads=12, patch_size=16, image=224; ~86M параметров |
| DeiT | «Эффективный по данным ViT» | ViT, обученный только на ImageNet-1k с сильной аугментацией; доказал, что большие наборы данных для предобучения не являются строго необходимыми |
| MAE | «Masked autoencoder» | Self-supervised предобучение: замаскировать 75% патчей, восстановить их; доминирующий рецепт предобучения ViT |
Дополнительные материалы
- An Image is Worth 16x16 Words (Dosovitskiy et al., 2020) — статья о ViT
- DeiT: Data-efficient Image Transformers (Touvron et al., 2020) — как обучить ViT только на ImageNet-1k
- Masked Autoencoders are Scalable Vision Learners (He et al., 2022) — предобучение MAE
- Документация timm — справочник по каждому визуальному трансформеру, который вы будете использовать в production
Источник: Vision Transformers (ViT) 04.13 — 3D-зрение: облака точек и NeRF · Фаза 04 — Компьютерное зрение · 04.15 — Зрение в реальном времени и развёртывание на периферии · Полный каталог