Фаза 04 · урок 12
Понимание видео — временное моделирование
Цель урока: 30-секундное видео при 30 fps — это 900 изображений. Наивно классификация видео — это классификация изображений, запущенная 900 раз с последующей агрегацией. Такой подход работает, когда действие видно почти в каждом кадре (спорт,…
Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.
Содержание урока
- Цели обучения
- Проблема
- Концепция
- Три семейства архитектур
- 2D + pool
- 3D-свёртки
- Пространственно-временные трансформеры
- Выборка кадров
- Оценка
- Наборы данных, которые вы встретите
- Соберите
- Шаг 1: Сэмплер кадров
- Шаг 2: Базовое решение 2D+pool
- Шаг 3: Раздутая 3D-свёртка в стиле I3D
- Шаг 4: Факторизованная свёртка (2+1)D
- Используйте
- Поставьте
- Упражнения
- Ключевые термины
- Дополнительные материалы
Видео — это последовательность изображений плюс физика, которая их связывает. Каждая модель видео либо рассматривает время как дополнительную ось (3D-свёртка), либо как последовательность для внимания (transformer), либо извлекает признаки один раз и агрегирует их (2D+pool).
Тип: Изучите + соберите Языки: Python Предварительные требования: Фаза 4, урок 03 (CNN), фаза 4, урок 04 (Классификация изображений) Время: ~45 минут
Цели обучения
- Различать три основных подхода к моделированию видео (2D+pool, 3D-свёртки, пространственно-временные трансформеры) и предсказывать их компромиссы между стоимостью и точностью
- Реализовать выборку кадров, временной pooling и базовый классификатор 2D+pool в PyTorch
- Объяснить, почему «раздутые» (inflated) 3D-ядра I3D хорошо переносят веса ImageNet и чем факторизованная свёртка (2+1)D отличается от них
- Ориентироваться в стандартных наборах данных и метриках распознавания действий: Kinetics-400/600, UCF101, Something-Something V2; точность top-1 на уровне клипа и видео
Проблема
30-секундное видео при 30 fps — это 900 изображений. Наивно классификация видео — это классификация изображений, запущенная 900 раз с последующей агрегацией. Такой подход работает, когда действие видно почти в каждом кадре (спорт, готовка, упражнения), и плохо работает, когда действие определяется самим движением: «толкнуть что-то слева направо» в каждом отдельном кадре выглядит как два неподвижных объекта.
Главный вопрос для каждой видеоархитектуры: когда и как моделируется временная структура? Ответ определяет всё остальное — стоимость вычислений, стратегию предобучения, возможность повторно использовать веса ImageNet и наборы данных, на которых обучается модель.
Этот урок намеренно короче уроков о статических изображениях. Основные механизмы обработки изображений уже есть; понимание видео в основном посвящено временной истории: выборке, моделированию и агрегации.
Концепция
Три семейства архитектур
2D + pool
Возьмите 2D CNN (ResNet, EfficientNet, ViT). Запустите её независимо на каждом выбранном кадре. Усредните (или примените max-pool либо attention-pool к) встраиваниям кадров. Передайте агрегированный вектор классификатору.
Преимущества:
- Предобучение ImageNet переносится напрямую.
- Самая простая реализация.
- Дёшево: T кадров * стоимость инференса одного изображения.
Недостатки:
- Не может моделировать движение. Действие = агрегация внешних признаков.
- Временной pooling инвариантен к порядку; «открыть дверь» и «закрыть дверь» выглядят одинаково.
Когда использовать: задачи, где важен внешний вид, transfer learning на небольших наборах видео, первоначальные базовые решения.
3D-свёртки
Замените 2D-ядра (H, W) на 3D-ядра (T, H, W). Сеть выполняет свёртку и по пространству, и по времени. Ранние представители семейства: C3D, I3D, SlowFast.
Приём I3D: возьмите предобученную 2D-модель ImageNet и «раздуйте» каждое 2D-ядро, копируя его вдоль новой временной оси. Свёртка 2D 3x3 превращается в свёртку 3D 3x3x3. Так 3D-модель получает сильные предобученные веса вместо обучения с нуля.
Преимущества:
- Непосредственно моделирует движение.
- Раздувание I3D даёт бесплатное transfer learning.
Недостатки:
- В T/8 раз больше FLOPs, чем у 2D-аналога (для временного ядра 3, применённого трижды в стеке).
- Временные ядра малы; для дальнего движения нужна пирамида или двухпоточный подход.
Когда использовать: распознавание действий, где движение — это сигнал (Something-Something V2, Kinetics с классами, сильно зависящими от движения).
Пространственно-временные трансформеры
Токенизируйте видео в сетку пространственно-временных патчей и выполняйте внимание по всем им. TimeSformer, ViViT, Video Swin, VideoMAE.
Важные паттерны внимания:
- Совместное (joint) — одно большое внимание по (t, h, w). Квадратичное по
T*H*W; дорого. - Разделённое (divided) — два внимания на блок: одно по времени, другое по пространству. Почти линейное масштабирование.
- Факторизованное (factorised) — внимание по времени чередуется с вниманием по пространству между блоками.
Преимущества:
- SOTA-точность на каждом крупном бенчмарке.
- Переносится из трансформеров изображений (ViT) через раздувание патчей.
- Поддерживает видео с длинным контекстом через разреженное внимание.
Недостатки:
- Требователен к вычислениям.
- Требует тщательного выбора паттерна внимания, иначе время выполнения резко растёт.
Когда использовать: большие наборы данных, высокоточное понимание видео, мультимодальные задачи видео+текст.
Выборка кадров
10-секундный клип при 30 fps — это 300 кадров; подавать все 300 в любую модель расточительно. Стандартные стратегии:
- Равномерная выборка (uniform sampling) — выбрать T кадров равномерно по всему клипу. Значение по умолчанию для 2D+pool.
- Плотная выборка (dense sampling) — случайное непрерывное окно из T кадров. Обычна для 3D-свёрток, поскольку движение требует соседних кадров.
- Несколько клипов (multi-clip) — выбрать несколько окон из T кадров из одного видео, классифицировать каждое и усреднить предсказания во время тестирования.
T обычно равно 8, 16, 32 или 64. Большее T означает больше временного сигнала за большую цену вычислений.
Оценка
Два уровня:
- Точность на уровне клипа — модель видит один клип из T кадров и возвращает top-k.
- Точность на уровне видео — усредняйте предсказания на уровне клипа по нескольким клипам каждого видео; она выше и стабильнее.
Всегда сообщайте обе. Модель с 78% для клипа / 82% для видео сильно полагается на усреднение во время теста; модель с 80% / 81% более устойчива на каждом клипе.
Наборы данных, которые вы встретите
- Kinetics-400 / 600 / 700 — универсальный набор данных действий. 400 тыс. клипов; URL YouTube (многие уже недоступны).
- Something-Something V2 — действия, определяемые движением («переместить X слева направо»). Нельзя решить с помощью 2D+pool.
- UCF-101, HMDB-51 — старые, небольшие, но всё ещё упоминаемые наборы.
- AVA — локализация действий в пространстве и времени; сложнее классификации.
Соберите
Шаг 1: Сэмплер кадров
Равномерный и плотный сэмплеры, работающие со списком кадров (или тензором видео).
import numpy as np
def sample_uniform(num_frames_total, T):
if num_frames_total <= T:
return list(range(num_frames_total)) + [num_frames_total - 1] * (T - num_frames_total)
step = num_frames_total / T
return [int(i * step) for i in range(T)]
def sample_dense(num_frames_total, T, rng=None):
rng = rng or np.random.default_rng()
if num_frames_total <= T:
return list(range(num_frames_total)) + [num_frames_total - 1] * (T - num_frames_total)
start = int(rng.integers(0, num_frames_total - T + 1))
return list(range(start, start + T))
Оба возвращают T индексов, которые используются для среза тензора видео.
Шаг 2: Базовое решение 2D+pool
Запустите 2D ResNet-18 на каждом кадре, усредните признаки и классифицируйте.
import torch
import torch.nn as nn
from torchvision.models import resnet18, ResNet18_Weights
class FramePool(nn.Module):
def __init__(self, num_classes=400, pretrained=True):
super().__init__()
weights = ResNet18_Weights.IMAGENET1K_V1 if pretrained else None
backbone = resnet18(weights=weights)
self.features = nn.Sequential(*(list(backbone.children())[:-1])) # global avg pool kept
self.head = nn.Linear(512, num_classes)
def forward(self, x):
# x: (N, T, 3, H, W)
N, T = x.shape[:2]
x = x.view(N * T, *x.shape[2:])
feats = self.features(x).view(N, T, -1)
pooled = feats.mean(dim=1)
return self.head(pooled)
model = FramePool(num_classes=10)
x = torch.randn(2, 8, 3, 224, 224)
print(f"output: {model(x).shape}")
print(f"params: {sum(p.numel() for p in model.parameters()):,}")
Одиннадцать миллионов параметров, предобучение ImageNet, работа по кадрам, усреднение, классификация. На задачах, где важен внешний вид, это базовое решение часто отстаёт от настоящих 3D-моделей лишь на 5–10 пунктов — а иногда лучше, потому что повторно использует более сильный backbone ImageNet.
Шаг 3: Раздутая 3D-свёртка в стиле I3D
Превратите одну 2D-свёртку в 3D-свёртку, повторив веса вдоль новой временной оси.
def inflate_2d_to_3d(conv2d, time_kernel=3):
out_c, in_c, kh, kw = conv2d.weight.shape
weight_3d = conv2d.weight.data.unsqueeze(2) # (out, in, 1, kh, kw)
weight_3d = weight_3d.repeat(1, 1, time_kernel, 1, 1) / time_kernel
conv3d = nn.Conv3d(in_c, out_c, kernel_size=(time_kernel, kh, kw),
padding=(time_kernel // 2, conv2d.padding[0], conv2d.padding[1]),
stride=(1, conv2d.stride[0], conv2d.stride[1]),
bias=False)
conv3d.weight.data = weight_3d
return conv3d
conv2d = nn.Conv2d(3, 64, kernel_size=3, padding=1, bias=False)
conv3d = inflate_2d_to_3d(conv2d, time_kernel=3)
print(f"2D weight shape: {tuple(conv2d.weight.shape)}")
print(f"3D weight shape: {tuple(conv3d.weight.shape)}")
x = torch.randn(1, 3, 8, 56, 56)
print(f"3D output shape: {tuple(conv3d(x).shape)}")
Деление на time_kernel сохраняет величины активаций примерно постоянными — это важно, чтобы при первом проходе не нарушить статистики batch norm.
Шаг 4: Факторизованная свёртка (2+1)D
Разделите 3D-свёртку на 2D-свёртку (пространственную) и 1D-свёртку (временную). То же поле восприятия, меньше параметров, на некоторых бенчмарках — лучше точность.
class Conv2Plus1D(nn.Module):
def __init__(self, in_c, out_c, kernel_size=3):
super().__init__()
mid_c = (in_c * out_c * kernel_size * kernel_size * kernel_size) \
// (in_c * kernel_size * kernel_size + out_c * kernel_size)
self.spatial = nn.Conv3d(in_c, mid_c, kernel_size=(1, kernel_size, kernel_size),
padding=(0, kernel_size // 2, kernel_size // 2), bias=False)
self.bn = nn.BatchNorm3d(mid_c)
self.act = nn.ReLU(inplace=True)
self.temporal = nn.Conv3d(mid_c, out_c, kernel_size=(kernel_size, 1, 1),
padding=(kernel_size // 2, 0, 0), bias=False)
def forward(self, x):
return self.temporal(self.act(self.bn(self.spatial(x))))
c = Conv2Plus1D(3, 64)
x = torch.randn(1, 3, 8, 56, 56)
print(f"(2+1)D output: {tuple(c(x).shape)}")
Полная сеть R(2+1)D устроена так же, как ResNet-18, только каждая свёртка 3x3 заменена на Conv2Plus1D.
Используйте
Две библиотеки покрывают рабочие задачи с видео:
torchvision.models.video— R(2+1)D, MViT, Swin3D с предобученными весами Kinetics. Тот же API, что у моделей изображений.pytorchvideo(Meta) — зоопарк моделей, загрузчики данных для Kinetics / SSv2 / AVA, стандартные преобразования.
Для video-language-моделей (создание подписей к видео, video QA) используйте transformers (VideoMAE, VideoLLaMA, InternVideo).
Поставьте
Этот урок создаёт:
outputs/prompt-video-architecture-picker.md— промпт, который выбирает 2D+pool / I3D / (2+1)D / transformer по соотношению внешнего вида и движения, размеру набора данных и бюджету вычислений.outputs/skill-frame-sampler-auditor.md— навык, который проверяет сэмплер видеоконвейера и отмечает распространённые ошибки: off-by-one в индексе, неравномерную выборку приnum_frames < T, отсутствие кропа с сохранением соотношения сторон и т. д.
Упражнения
- (Легко) Вычислите FLOPs (приблизительно) для FramePool с T=8 и для 3D ResNet в стиле I3D с T=8. Объясните, почему 2D+pool в 3–5 раз дешевле.
- (Средне) Создайте синтетический набор видео: случайные шарики движутся в случайных направлениях и размечаются направлением движения («слева направо», «справа налево», «по диагонали вверх»). Обучите на нём FramePool. Покажите, что она достигает точности, близкой к случайной, доказывая недостаточность одного лишь внешнего вида для задач движения.
- (Сложно) Постройте R(2+1)D-18, заменив каждую Conv2d в ResNet-18 на
Conv2Plus1D. Раздуйте веса первой свёртки из предобученной на ImageNet ResNet-18. Обучите на наборе движения из упражнения 2 и превзойдите FramePool.
Ключевые термины
| Термин | Как обычно говорят | Что это на самом деле означает |
|---|---|---|
| 2D + pool | «Покадровый классификатор» | Запустить 2D CNN на каждом выбранном кадре, усреднить признаки по времени и классифицировать |
| 3D-свёртка | «Пространственно-временное ядро» | Ядро выполняет свёртку по (T, H, W); может нативно моделировать движение |
| Раздувание (inflation) | «Поднять 2D-веса до 3D» | Инициализировать веса 3D-свёртки повторением весов 2D-свёртки вдоль новой временной оси, затем разделить на kernel_T для сохранения масштаба активаций |
| (2+1)D | «Факторизованная свёртка» | Разделить 3D на пространственную 2D и временную 1D; меньше параметров, дополнительная нелинейность между ними |
| Разделённое внимание | «Сначала время, затем пространство» | Блок трансформера с двумя механизмами внимания на слой: один по токенам в одном кадре, другой по токенам в одинаковой позиции |
| Клип | «Окно из T кадров» | Выбранная подпоследовательность T кадров; единица входа видеомодели |
| Точность клипа и видео | «Два режима оценки» | Клип = один образец из видео, видео = среднее по нескольким выбранным клипам |
| Kinetics | «ImageNet для видео» | 400–700 классов действий, более 300 тыс. клипов YouTube, стандартный корпус для предобучения видео |
Дополнительные материалы
- I3D: Quo Vadis, Action Recognition (Carreira & Zisserman, 2017) — вводит раздувание и набор данных Kinetics
- R(2+1)D: A Closer Look at Spatiotemporal Convolutions (Tran et al., 2018) — факторизованная свёртка, по-прежнему сильное базовое решение
- TimeSformer: Is Space-Time Attention All You Need? (Bertasius et al., 2021) — первый сильный видеотрансформер
- VideoMAE (Tong et al., 2022) — предобучение masked autoencoder для видео; текущий доминирующий рецепт предобучения
Источник: Video Understanding — Temporal Modeling 04.11 — Stable Diffusion — Architecture & Fine-Tuning · Фаза 04 — Компьютерное зрение · 04.13 — 3D Vision — Point Clouds & NeRFs · Полный каталог