Instructions to use Infarondus/Klein-marchen_Base with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- timm
How to use Infarondus/Klein-marchen_Base with timm:
import timm model = timm.create_model("hf-hub:Infarondus/Klein-marchen_Base", pretrained=True) - Notebooks
- Google Colab
- Kaggle
Kleine-Marchen — двухэтапный пайплайн классификации переплётов рукописей
Модели для автоматического отбора фотографий переплётов рукописных книг со средником (центральным декоративным элементом крышки) из оцифрованных фондов РГБ.
изображение → [этап 1: ConvNeXt-Tiny, 5 моделей] переплёт? (τ₁ = 0.60)
→ [этап 2: DINOv2 ViT-S, 5 моделей × 5 TTA] средник? (τ₂ = 0.55)
→ итоговая коллекция
Пайплайн целиком реализован в скрипте scrape_bindings_v3.py: https://github.com/Infarondus/Kleine-marchen
Обновление (ноябрь 2026). Протоколы оценки пересмотрены: прежние метрики были получены на данных, пересекавшихся с обучающими, и заменены out-of-fold-оценкой (каждое изображение оценивается моделью, не видевшей его при обучении). Модели этапа 1 заменены новыми (
convnext/), модели этапа 2 не менялись, но их метрики пересчитаны. Подробности — в разделе «Что изменилось».
Файлы
| Путь | Что это |
|---|---|
convnext/fold_1_oof.pth … fold_5_oof.pth |
этап 1, ConvNeXt-Tiny, 5 моделей (по одной на фолд) |
fold_1_km.pth … fold_5_km.pth |
этап 2, DINOv2 ViT-Small, 5 моделей (по одной на фолд) |
legacy/klein-marchen_preprocess.pth |
прежняя модель этапа 1 (апрель 2026), не рекомендуется |
В пайплайне каждый этап работает ансамблем своих пяти моделей (усреднение softmax-вероятностей).
Сквозная проверка на независимом фонде
Пайплайн применён к фонду 228 РГБ, который не использовался ни при обучении, ни при подборе параметров: 219 рукописей, по одному лучшему снимку переплёта на рукопись. Все снимки (отобранные и отклонённые) проверены автором вслепую, без знания решения модели; 7 изображений сильно фрагментированных переплётов исключены как не допускающие однозначной разметки.
| Значение | 95% ДИ | |
|---|---|---|
| Точность отбора (precision) | 0.989 (89 из 90) | 0.94–0.998 |
| Полнота (recall) | 0.840 (89 из 106) | 0.76–0.90 |
| AUC этапа 2 | 0.980 | — |
| Этап 1: precision / recall | 0.971 / 0.983 | — |
Пропущенные средники в основном получают оценки, заметно превышающие оценки переплётов без средника (медиана последних — 0.02), но не достигают порога τ₂. Пайплайн настроен консервативно: высокая точность отбора ценой части пропусков.
Этап 1 — ConvNeXt-Tiny: переплёт / не-переплёт
Метрики (out-of-fold, 5 фолдов, группировка по рукописи; одна трансформация, 352 px — как в пайплайне):
| Метрика | Значение |
|---|---|
| Accuracy | 0.988 |
| F1-macro | 0.986 |
| Precision (macro) | 0.986 |
| Recall (macro) | 0.986 |
| AUC | 0.999 |
| Предсказано: не-переплёт | Предсказано: переплёт | |
|---|---|---|
| Реально: не-переплёт (483) | 473 | 10 |
| Реально: переплёт (1131) | 9 | 1122 |
F1 по фолдам: 0.981–0.993. Доля предсказаний с уверенностью ниже 0.6 — 0.9%.
Данные: 1614 уникальных изображений (1131 переплёт, 483 не-переплёта: развороты, обложки-папки, прочие снимки описи) из 7 фондов РГБ. Точные дубли удалены; разбиение на фолды — StratifiedGroupKFold по рукописи (снимки одной рукописи всегда в одном фолде). При сверке предсказаний с разметкой 11 переплётов, ошибочно отнесённых к не-переплётам, перенесены в нужный класс, 1 неоднозначный снимок исключён.
Обучение (deep_test_v2.py): convnext_tiny.fb_in22k, три фазы с постепенной разморозкой; лучшая модель выбирается по валидационному F1 среди всех фаз (во всех 5 фолдах лучшей оказалась третья фаза).
- Фаза 1 (288 px, 10 эпох): только голова; AdamW, lr 1e-4, wd 1e-2, warmup 2 эпохи + косинус.
- Фаза 2 (288 px, до 8 эпох): stages 2–3, голова заморожена; lr 1e-5, wd 5e-3.
- Фаза 3 (352 px, до 15 эпох): stages 1–3 + голова; lr 5e-6 (backbone) / 1e-5 (голова), wd 5e-3, label smoothing 0.05, mixup/cutmix с 5-й эпохи; выбор по F1 с TTA на EMA-весах.
- Batch 16, AMP, gradient clipping 1.0, dropout 0.3, drop_path 0.1, взвешенный сэмплер для баланса классов, early stopping (patience 5).
- Аугментации (Albumentations): RandomResizedCrop, HorizontalFlip, VerticalFlip, ShiftScaleRotate, RandomBrightnessContrast / HueSaturationValue, GaussNoise / GaussianBlur / ImageCompression, CLAHE, CoarseDropout.
Этап 2 — DINOv2 ViT-Small: со средником / без средника
Определение класса. «Со средником» — центральный элемент является центром декоративной композиции крышки (тиснёный средник либо металлическая накладка в окружении орнамента, рамок или тиснения). Одиночная накладка на гладкой крышке средником не считается.
Метрики (out-of-fold: каждое изображение оценено той моделью из пяти, которая его не видела; 5 TTA):
| Метрика | Значение |
|---|---|
| Accuracy | 0.929 |
| F1-macro | 0.929 |
| Precision (macro) | 0.929 |
| Recall (macro) | 0.929 |
| Предсказано: без средника | Предсказано: со средником | |
|---|---|---|
| Реально: без средника (568) | 532 | 36 |
| Реально: со средником (568) | 45 | 523 |
Accuracy по фолдам: 0.908–0.952. При рабочем пороге τ₂ = 0.55: precision отбора 0.941, recall 0.903. Доля предсказаний с уверенностью ниже 0.7 — 12.0%.
Перенос на незнакомый фонд (модель обучена без одного фонда и проверена на нём):
| Исключённый фонд | n | AUC | F1 при τ₂ = 0.55 |
|---|---|---|---|
| 98 | 399 | 0.942 | 0.825 |
| 304-I | 498 | 0.979 | 0.839 |
Модель хорошо упорядочивает изображения нового фонда (высокий AUC), но шкала её уверенности смещается. Для новой коллекции порог τ₂ рекомендуется калибровать на небольшой размеченной выборке, а изображения с оценкой примерно 0.4–0.7 направлять на проверку специалисту.
Данные: 1136 изображений (568 / 568) из фондов РГБ; 1119 уникальных рукописей (17 повторов; их исключение на метрики не влияет). Часть положительных примеров (67) отобрана ранней версией модели и проверена автором. Разбиение — StratifiedKFold(5, shuffle=True, random_state=42) по изображениям.
Обучение (deep_test_v4_dino.py): vit_small_patch14_dinov2.lvd142m, 280 px, голова LayerNorm(384) → Linear(384→256) → GELU → Dropout(0.3) → Linear(256→2).
- Фаза 1 (6 эпох): только голова; AdamW, lr 5e-4, wd 1e-4, warmup 2 эпохи + косинус.
- Фаза 2 (до 20 эпох): голова + последние 4 блока ViT + финальная LayerNorm; lr 3e-5 (голова) / 2e-6 (backbone), wd 1e-2, warmup 1 эпоха, early stopping (patience 10).
- Batch 32, без AMP, gradient clipping 1.0, label smoothing 0.05, drop_path 0.1, взвешенный сэмплер.
- Лучшая эпоха выбирается по F1 сглаженных весов (EMA, обновление раз в эпоху); сохраняются обычные веса этой эпохи.
- TTA: оригинал, горизонтальное и вертикальное отражение, увеличение ×1.05 + центральный кроп, яркость/контраст ±10%.
Быстрый старт
import numpy as np, torch, torch.nn as nn, torch.nn.functional as F, timm
import albumentations as A
from albumentations.pytorch import ToTensorV2
from PIL import Image
MEAN, STD = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]
dev = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
def tfm(size):
return A.Compose([A.LongestMaxSize(max_size=size),
A.PadIfNeeded(size, size, border_mode=0, value=[255, 255, 255]),
A.Normalize(mean=MEAN, std=STD), ToTensorV2()])
# этап 1: ансамбль ConvNeXt
convnext = []
for k in range(1, 6):
ck = torch.load(f'convnext/fold_{k}_oof.pth', map_location=dev, weights_only=False)
m = timm.create_model('convnext_tiny.fb_in22k', pretrained=False, num_classes=2)
m.load_state_dict(ck['model_state_dict']); convnext.append(m.to(dev).eval())
# этап 2: ансамбль DINOv2
class DinoClassifier(nn.Module):
def __init__(self):
super().__init__()
self.backbone = timm.create_model('vit_small_patch14_dinov2.lvd142m', pretrained=False,
num_classes=0, img_size=280, dynamic_img_size=True)
d = self.backbone.embed_dim
self.head = nn.Sequential(nn.LayerNorm(d), nn.Linear(d, 256), nn.GELU(),
nn.Dropout(0.3), nn.Linear(256, 2))
def forward(self, x):
return self.head(self.backbone(x))
dino = []
for k in range(1, 6):
ck = torch.load(f'fold_{k}_km.pth', map_location=dev, weights_only=False)
m = DinoClassifier(); m.load_state_dict(ck['model_state_dict']); dino.append(m.to(dev).eval())
@torch.no_grad()
def score(models, img, size):
x = tfm(size)(image=img)['image'].unsqueeze(0).to(dev)
return float(np.mean([F.softmax(m(x), 1)[0, 1].item() for m in models]))
img = np.array(Image.open('binding.jpg').convert('RGB'))
p_bind = score(convnext, img, 352)
p_sred = score(dino, img, 280) # в пайплайне дополнительно усредняется по 5 TTA
print(f"переплёт: {p_bind:.1%} средник: {p_sred:.1%} →",
"отобрано" if p_bind >= 0.60 and p_sred >= 0.55 else "отклонено")
Ограничения
- Модели обучены на снимках фондов РГБ; на других коллекциях смещается калибровка уверенности (см. выше), порог τ₂ следует подбирать заново.
- Разметка выполнена одним автором; критерий для металлических накладок сформулирован после разметки, в части таких случаев она может быть непоследовательной. Около 2% изображений — пограничные случаи.
- Наиболее трудны сильно изношенные переплёты, где средник сохранился фрагментарно или различим только рельефно, а также нетипичные гладкие переплёты без тиснения (на этапе 1).
- Пайплайн рассматривает первые 5 снимков рукописи и передаёт на этап 2 только один, лучший по оценке этапа 1.
Что изменилось (ноябрь 2026)
- Этап 1. Прежняя модель (
legacy/klein-marchen_preprocess.pth, один фолд апрельского обучения, обучена только голова) оценивалась на изображениях, пересекавшихся с обучающими, и с другими параметрами (320 px); опубликованные для неё метрики (F1 0.914) независимой оценкой не являются. Она заменена ансамблем из 5 моделей, обученных тем же рецептом на исправленном и расширенном датасете и оценённых out-of-fold. - Этап 2. Модели не менялись. Прежние метрики (F1 0.937) были получены ансамблем всех пяти моделей на полном датасете, т.е. на изображениях, частично входивших в обучение; out-of-fold-оценка — F1 0.929.
- Добавлены проверка на незнакомых фондах и сквозная проверка на независимом фонде 228.
Использование генеративного ИИ
Архитектурные решения и код были реализованы при помощи инструментов генеративного искусственного интеллекта Claude, Anthropic.
Цитирование
@misc{kleine-marchen-binding,
author = {Infarondus},
title = {Kleine-Marchen — Binding Classification Pipeline for Manuscript Bindings},
year = {2026},
url = {https://github.com/Infarondus/Kleine-marchen}
}
- Downloads last month
- -