Kleine-Marchen — Binding Detector (ConvNeXt-Tiny)
Модель на базе ConvNeXt-Tiny (ImageNet-22k) для определения, является ли изображение фотографией переплёта рукописи.
Используется как первая ступень в двухэтапном пайплайне классификации переплётов рукописей РГБ.
Назначение
Бинарная классификация:
- Класс 1 — переплёт: изображение является фотографией крышки переплёта рукописи
- Класс 0 — не переплёт: страница текста, разворот, предметная съёмка и т.д.
Модель решает задачу фильтрации при массовом скачивании изображений из цифровых архивов рукописей, где первые страницы оцифровки содержат как переплёты, так и текстовые страницы.
Метрики
Оценка проводилась на тестовом датасете из 1661 изображения (1136 переплётов + 525 не-переплётов) с применением TTA (5 трансформаций).
| Метрика | Значение |
|---|
| Accuracy | 92.53% |
| F1-macro | 0.9139 |
| Precision | 0.9127 |
| Recall | 0.9152 |
Confusion Matrix:
| Предсказано: не-переплёт | Предсказано: переплёт |
|---|
| Реально: не-переплёт | 466 | 59 |
| Реально: переплёт | 65 | 1071 |
Метрики на уверенных предсказаниях (confidence ≥ 0.6):
| Метрика | Значение |
|---|
| Доля уверенных предсказаний | 60.7% (1009 из 1661) |
| Accuracy | 97.13% |
| F1-macro | 0.9689 |
Использование порога уверенности позволяет автоматически делегировать сомнительные случаи на ручную проверку, при этом на уверенно классифицированных изображениях модель достигает точности выше 97%.
Данные
- Источник: фотографии из цифровых фондов РГБ
- Размер обучающей выборки: ~525 изображений на каждый класс (75/25 hold-out validation)
- Классы: переплёт / не переплёт (страницы текста, развороты, обложки-папки)
- Разрешение при обучении: 320×320 px
Архитектура
Базовая модель: convnext_tiny.fb_in22k (ImageNet-22k pretrain, 28M параметров)
Стратегия обучения: fine-tuning только классификационной головы при замороженном бэкбоне. Полная разморозка слоёв приводила к коллапсу обучения из-за ограниченного размера датасета.
Параметры обучения:
- Оптимизатор: AdamW (lr=3e-5, weight_decay=1e-4)
- Scheduler: CosineAnnealingLR
- Loss: CrossEntropyLoss
- Batch size: 16
- Mixed precision training (AMP)
Аугментации (Albumentations): RandomResizedCrop, HorizontalFlip, RandomRotate90, ShiftScaleRotate, ColorJitter, GridDistortion, GaussianBlur, ToGray.
Использование
Готовые скрипты доступны в репозитории:
Быстрый старт
1import torch
2import timm
3import numpy as np
4from PIL import Image
5import albumentations as A
6from albumentations.pytorch import ToTensorV2
7
8IMAGE_SIZE = 320
9
10transform = A.Compose([
11 A.LongestMaxSize(max_size=IMAGE_SIZE),
12 A.PadIfNeeded(min_height=IMAGE_SIZE, min_width=IMAGE_SIZE,
13 border_mode=0, value=[255, 255, 255]),
14 A.Normalize(mean=[0.485, 0.456, 0.406],
15 std=[0.229, 0.224, 0.225]),
16 ToTensorV2(),
17])
18
19device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
20model = timm.create_model('convnext_tiny_in22k', pretrained=False, num_classes=2)
21ckpt = torch.load('kleine-marchen_preprocess.pth', map_location=device, weights_only=False)
22model.load_state_dict(ckpt['model_state_dict'])
23model.to(device).eval()
24
25img = np.array(Image.open('page.jpg').convert('RGB'))
26tensor = transform(image=img)['image'].unsqueeze(0).to(device)
27with torch.no_grad():
28 probs = torch.softmax(model(tensor), dim=1)[0]
29
30print(f"Не переплёт: {probs[0]:.1%} | Переплёт: {probs[1]:.1%}")
Место в пайплайне
Эта модель используется совместно с binding-srednik-dinov2:
Изображение → [ConvNeXt: переплёт?] → [DINOv2: есть средник?] → Результат
Скрипт двухступенчатого скрапера scrape_bindings_v2.py из репозитория Kleine-Marchen реализует этот пайплайн полностью.
Ограничения
- Не предназначена для определения типа переплёта или его элементов — только факт наличия переплёта на снимке
- При сильно нестандартных условиях съёмки (белый фон, крупный план фрагмента) точность может снижаться
- Около 39% входных изображений модель помечает как «сомнительные» (confidence < 0.6) — эти случаи рекомендуется проверять вручную или пропускать (так как большая часть пограничных случаев не является переплётом)
Kleine-Marchen — Binding Srednik Detector (DINOv2 Ensemble)
Ансамбль из 5 моделей на базе DINOv2 ViT-Small для классификации переплётов рукописей по наличию средника — центрального декоративного элемента крышки переплёта.
Модель разработана в рамках исследования рукописного фонда Российской государственной библиотеки (РГБ).
Назначение
Модель решает задачу бинарной классификации изображений переплётов:
- Класс 1 — со средником: на крышке переплёта присутствует средник
- Класс 0 — без средника: средник отсутствует
Модель предназначена для автоматической предварительной сортировки больших коллекций фотографий переплётов рукописей. Окончательная верификация результатов производится специалистом.
Метрики
Оценка проводилась на полном датасете из 1136 изображений (568 со средником + 568 без средника) с применением ансамбля 5 моделей и TTA (5 трансформаций) — итого 25 предсказаний на изображение.
| Метрика | Значение |
|---|
| Accuracy | 93.66% |
| F1-macro | 0.9366 |
| Precision | 0.9368 |
| Recall | 0.9366 |
Confusion Matrix:
| Предсказано: без средника | Предсказано: со средником |
|---|
| Реально: без средника | 537 | 31 |
| Реально: со средником | 41 | 527 |
Анализ ошибок:
- Всего ошибочных предсказаний: 72
- False positive (без средника принят за со средником): 31
- False negative (со средником принят за без средника): 41
- Сомнительных предсказаний (confidence < 0.7): 123 (10.8% датасета)
Симметричное распределение ошибок указывает на отсутствие смещения модели в сторону одного из классов.
Данные
- Источник: фотографии переплётов рукописей из цифровых фондов РГБ
- Размер обучающей выборки: 568 изображений на каждый класс (1136 итого)
- Формат: цветные фотографии переплётов
- Разрешение при обучении: 280×280 px
Датасет собирался итеративно: после каждого цикла обучения производился анализ ошибок и доразметка сложных случаев (изношенные переплёты, пограничные экземпляры).
Архитектура
Базовая модель: vit_small_patch14_dinov2.lvd142m (DINOv2 ViT-Small, 22M параметров, предобучен self-supervised на LVD-142M — 142 миллиона курированных изображений)
Голова классификатора:
LayerNorm(384) → Linear(384→256) → GELU → Dropout(0.3) → Linear(256→2)
Стратегия обучения: двухфазный fine-tuning
- Фаза 1 (6 эпох): обучение только головы, LR = 5e-4
- Фаза 2 (20 эпох): голова + последние 4 блока ViT, дифференциальный LR (голова: 3e-5, бэкбон: 2e-6)
Регуляризация: drop_path_rate = 0.1, dropout = 0.3, label smoothing = 0.05
Ансамбль: 5 моделей (по одной на каждый фолд 5-fold стратифицированной кросс-валидации) с усреднением softmax-вероятностей.
TTA: 5 трансформаций (оригинал, horizontal flip, vertical flip, resize ×1.05 + center crop, brightness/contrast jitter).
Использование
Готовые скрипты для обучения, оценки и инференса доступны в репозитории:
Быстрый старт
1import torch
2import torch.nn as nn
3import torch.nn.functional as F
4import timm
5import numpy as np
6from PIL import Image
7import albumentations as A
8from albumentations.pytorch import ToTensorV2
9
10MODEL_NAME = 'vit_small_patch14_dinov2.lvd142m'
11IMAGE_SIZE = 280
12DINO_MEAN = [0.485, 0.456, 0.406]
13DINO_STD = [0.229, 0.224, 0.225]
14
15def build_model():
16 backbone = timm.create_model(
17 MODEL_NAME, pretrained=False, num_classes=0,
18 img_size=IMAGE_SIZE, dynamic_img_size=True,
19 )
20 head = nn.Sequential(
21 nn.LayerNorm(backbone.embed_dim),
22 nn.Linear(backbone.embed_dim, 256),
23 nn.GELU(),
24 nn.Dropout(0.3),
25 nn.Linear(256, 2),
26 )
27 class DinoClassifier(nn.Module):
28 def __init__(self, b, h):
29 super().__init__()
30 self.backbone, self.head = b, h
31 def forward(self, x):
32 return self.head(self.backbone(x))
33 return DinoClassifier(backbone, head)
34
35transform = A.Compose([
36 A.LongestMaxSize(max_size=IMAGE_SIZE),
37 A.PadIfNeeded(min_height=IMAGE_SIZE, min_width=IMAGE_SIZE,
38 border_mode=0, value=[255, 255, 255]),
39 A.Normalize(mean=DINO_MEAN, std=DINO_STD),
40 ToTensorV2(),
41])
42
43# Загрузка модели
44device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
45model = build_model().to(device)
46ckpt = torch.load('fold_1_km.pth', map_location=device, weights_only=False)
47model.load_state_dict(ckpt['model_state_dict'])
48model.eval()
49
50# Инференс
51img = np.array(Image.open('binding.jpg').convert('RGB'))
52tensor = transform(image=img)['image'].unsqueeze(0).to(device)
53with torch.no_grad():
54 probs = F.softmax(model(tensor), dim=1)[0]
55
56print(f"Без средника: {probs[0]:.1%} | Со средником: {probs[1]:.1%}")
Рекомендуемый порог
При использовании ансамбля рекомендуется порог 0.70–0.75 для отнесения изображения к одному из классов; при меньшей уверенности изображение целесообразно отправлять на ручную проверку специалистом. На уверенных предсказаниях точность ансамбля заметно превышает интегральную.
Ограничения
- Модель обучена на фотографиях переплётов РГБ и может хуже работать на изображениях из других коллекций (domain shift)
- Сильно изношенные переплёты с плохо читаемым средником являются наиболее сложными случаями
- Не предназначена для работы с изображениями страниц, не являющихся переплётами — для фильтрации используйте модель
klein-marchen_preprocess на первом этапе
- В обучающей выборке возможно присутствие небольшого количества ошибочно размеченных изображений (~2%), что отражается в нижней границе оценки точности
Цитирование
Если вы используете эту модель в исследовании, пожалуйста, укажите репозиторий:
1@misc{kleine-marchen-binding,
2 author = {Infarondus},
3 title = {Kleine-Marchen — Binding Classification Pipeline for Manuscript Bindings},
4 year = {2026},
5 url = {https://github.com/Infarondus/Kleine-marchen}
6}