Автоматическое обнаружение заставок в эпизодах сериалов с использованием трансформерной модели и CRF.
Проект SearchIntroSerials решает задачу автоматического обнаружения временных интервалов заставок в эпизодах сериалов. Это полезно для стриминговых платформ, автоматизации обработки видео или улучшения пользовательского опыта (например, пропуск заставок). Модель использует эмбеддинги кадров, извлечённые с помощью CLIP, и трансформер с CRF-слоем для предсказания последовательности меток (заставка/не заставка). Проект включает обучение, инференс и оценку с метриками на уровне кадров (Precision, Recall, F1) и интервалов (IoU).
- Установка
- Использование
- Подход
- Архитектура модели
- Обучение
- Инференс
- Результаты
- Примеры предсказаний
- Зависимости
- Лицензия
- Будущие улучшения
Для работы с проектом требуется Python 3.8+. Клонируйте репозиторий и установите зависимости:
git clone https://github.com/serttyzar/SearchIntroSerials.git
cd SearchIntroSerials
pip install -r requirements.txtУбедитесь, что у вас есть доступ к видеофайлам в формате data_train_short/video_id/video_id.mp4 и меткам в data_train_short/labels.json.
-
Подготовка данных:
- Извлеките кадры из видео с частотой 1 FPS (до 180 секунд).
- Сгенерируйте эмбеддинги кадров с помощью CLIP (
openai/clip-vit-large-patch14). - Используйте
src/generate_dataset.pyдля созданияdataset.pt:
python src/generate_dataset.py
-
Обучение модели:
- Запустите Jupyter Notebook
training_notebook.ipynbдля обучения модели:
jupyter notebook training_notebook.ipynb
- Запустите Jupyter Notebook
-
Инференс:
- Используйте
src/inference.pyдля предсказания заставок в новом видео:
python src/inference.py --video_path videos/test_video.mp4 --model_path processed_dataset/model.pt
Пример вывода:
Заставка обнаружена: 20.00с - 35.00с - Используйте
Проект использует следующий подход:
- Извлечение кадров: Видео разбиваются на кадры с частотой 1 FPS (до 180 секунд).
- Эмбеддинги: Кадры преобразуются в эмбеддинги с помощью CLIP для кодирования визуальной информации.
- Моделирование последовательности: Трансформер обрабатывает последовательности эмбеддингов, учитывая временные зависимости.
- Последовательная разметка: CRF-слой предсказывает метки (0 — не заставка, 1 — заставка) с учётом контекста.
- Постобработка: Применяется
binary_closingдля сглаживания предсказаний и удаления коротких шумов.
-
CLIP (CLIP Paper):
- Выбран за способность создавать семантически богатые эмбеддинги, которые эффективно кодируют визуальные особенности заставок (анимация, логотипы, текст).
- Альтернативы (ResNet, VGG) менее подходят, так как требуют дообучения для специфических задач.
- Преимущество: Предобученная модель, не требующая дообучения, что экономит ресурсы.
-
Трансформер (Attention Is All You Need):
- Эффективен для обработки последовательностей благодаря механизму внимания, который улавливает зависимости между кадрами на большом временном диапазоне.
- Альтернативы (LSTM, GRU) менее эффективны для длинных последовательностей из-за проблем с градиентами.
- Выбраны параметры:
n_heads=12,n_layers=6для баланса между производительностью и вычислительными затратами.
-
CRF (CRF Paper):
- Обеспечивает согласованность предсказанных меток, что критично для выделения непрерывных интервалов заставок.
- Кастомная реализация CRF выбрана для избежания зависимости от внешних библиотек (например,
torchcrf) и гибкости настройки переходов. - Альтернативы (Softmax) не учитывают зависимости между метками, что снижает качество интервалов.
-
Веса классов:
- Веса классов (
class_weights=[1.0, 2.0]) использованы для борьбы с дисбалансом (4.5175% меток 1). - Веса увеличивают значимость положительного класса (заставка), улучшая
recall.
- Веса классов (
-
Постобработка (
binary_closing):- Сглаживает предсказания, устраняя короткие шумы, что повышает
precisionи IoU.
- Сглаживает предсказания, устраняя короткие шумы, что повышает
- Эффективность: CLIP и трансформеры обеспечивают высокое качество без необходимости дообучения на больших датасетах.
- Гибкость: Кастомный CRF позволяет настраивать переходы (например, увеличивать вероятность "заставка → заставка").
- Робастность: Постобработка повышает качество интервалов, что критично для реальных приложений.
Модель IntroDetectionTransformer состоит из:
- Позиционное кодирование: Добавляет временную информацию к эмбеддингам
[batch_size, 60, 768]. - Трансформерный энкодер: 6 слоёв, 12 голов внимания, обрабатывает последовательность эмбеддингов.
- Классификатор: Линейный слой, преобразующий выход трансформера в логиты для 2 классов (0, 1).
- CRF-слой: Моделирует зависимости между метками, вычисляя оптимальную последовательность.
class IntroDetectionTransformer(nn.Module):
def __init__(self, d_model=768, n_heads=8, n_layers=4, num_labels=2, class_weights=None):
super().__init__()
self.pos_encoding = nn.Parameter(torch.zeros(1, 60, d_model))
encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=n_heads, batch_first=True, dropout=0.1)
self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=n_layers)
self.classifier = nn.Linear(d_model, num_labels)
self.crf = CRF(num_labels, batch_first=True)
if class_weights is not None:
self.register_buffer('class_weights', class_weights)
else:
self.class_weights = None
def forward(self, embeddings, labels=None, mask=None):
x = embeddings + self.pos_encoding
x = self.transformer(x)
logits = self.classifier(x)
if labels is not None:
if self.class_weights is not None:
logits[:, :, 1] = logits[:, :, 1] * self.class_weights[1]
return self.crf(logits, labels.long(), mask=mask)
else:
return self.crf.decode(logits, mask=mask)Кастомная реализация CRF ввиду проблем с импортом библиотеки на Kaggle, где обучалась модель:
- Переходы: Матрица
[num_tags, num_tags](2x2) для вероятностей переходов между метками. - Алгоритм Витерби: Используется для декодирования оптимальной последовательности меток.
- Лосс: Отрицательное логарифмическое правдоподобие, учитывающее истинные метки и маску.
Пример изменения весов CRF для повышения recall:
model.crf.transitions.data[1, 1] += 1.5 # Увеличиваем вероятность "заставка → заставка"
model.crf.transitions.data[1, 0] -= 1.5 # Уменьшаем вероятность "заставка → не заставка"Модель обучалась в Jupyter Notebook training_notebook.ipynb:
- Датасет:
train_dataset.ptс эмбеддингами CLIP[60, 768]и метками[60]. Сформирован из эмбеддингов сериалов, обрезанных первыми 3 минутами(зачастую заставка в этом интервале) - Предобработка:
- Многократная аугментация (2 копии) для кадров с меткой 1.
- Oversampling с весами
[20.0, 1.0]для окон с метками 1.
- Лосс: CRF-лосс с весами классов
[1.0, 2.0]. - Оптимизатор: Adam (
lr=3e-5,weight_decay=1e-5). - Эпохи: 30.
- Устройство: CUDA (GPU).
Код обучения:
for epoch in range(30):
total_loss = 0
for embeddings, labels in tqdm(dataloader, desc=f"Эпоха {epoch+1}/30"):
embeddings, labels = embeddings.to(device), labels.to(device)
mask = torch.ones(embeddings.shape[0], 60, device=device).bool()
optimizer.zero_grad()
loss = model(embeddings, labels, mask)
if loss.dim() > 0:
loss = loss.mean()
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Эпоха {epoch+1}, Средняя потеря: {total_loss / len(dataloader):.4f}")Инференс выполняется с помощью inference.py или в training_notebook.ipynb:
- Загружается модель и данные (
test_dataset2.pt). - Модель предсказывает последовательности меток с помощью CRF-декодирования.
- Применяется постобработка (
binary_closingс окном 3) для сглаживания. - Вычисляются интервалы заставок с помощью
scipy.ndimage.label.
Пример кода инференса:
model.eval()
with torch.no_grad():
for embeddings, labels, video_ids, start_indices in test_dataloader:
embeddings = embeddings.to(device)
mask = torch.ones(embeddings.shape[0], 60, device=device).bool()
predicted_sequences = model(embeddings, mask=mask)
predicted_labels = np.array(predicted_sequences)
closed_labels = [binary_closing(seq, structure=np.ones(3)).astype(int) for seq in predicted_labels]После обучения и постобработки достигнуты следующие метрики:
| Метрика | Значение |
|---|---|
| Precision | 0.6586 |
| Recall | 0.7726 |
| F1-мера | 0.7111 |
| Средний IoU | 0.6295 |
- Прогресс: F1-мера (0.7111) и Recall (0.7726) превышают целевые 0.7, но IoU (0.6295) ниже цели 0.7.
- Анализ: Постобработка улучшает
precisionиF1, и делаетrecallстабильным. Низкий IoU указывает на неточности в границах интервалов.
Пример 1: Видео -220020068_456255414.mp4.
- Предсказание:
. - Значение в labels: {"url": "https://vkvideo.ru/video-220020068_456249358", "name": "24 \u0447\u0430\u0441\u0430. 8 \u0441\u0435\u0437\u043e\u043d, 3 \u0441\u0435\u0440\u0438\u044f", "start": "0:00:05", "end": "0:00:15"}
Пример 2: Видео 220020068_456255389.mp4.
- Предсказание:
. - Значение в labels: {"url": "https://vkvideo.ru/video-220020068_456255389", "name": "\u0411\u0430\u0441\u043a\u0435\u0442\u0441. 1 \u0441\u0435\u0437\u043e\u043d, 9 \u0441\u0435\u0440\u0438\u044f", "start": "0:00:00", "end": "0:00:08"}.
- Python 3.8+
- torch
- torchvision
- transformers
- numpy
- scipy
- scikit-learn
- tqdm
- opencv-python
Установите зависимости:
pip install -r requirements.txtПроект распространяется под лицензией MIT. См. LICENSE.
- Дообучение CLIP для улучшения эмбеддингов.
- Добавление звуковых эмбеддингов может очень сильно повысить качество, к примеру MFCC или спектрограммы, так как заставка часто имеет яркое звуковое сопровождение
- Эксперименты с другими архитектурами (например, LSTM+CRF).
- Увеличение датасета с добавлением новых видео. Очень важный шаг + более сбалансированный датасет в плане классов.
- Настройка гиперпараметров CRF для повышения IoU.