Klein-marchen_Base / README.md
Infarondus's picture
Update README.md
9ad178c verified
---
---
license: apache-2.0
language:
- ru
- en
base_model:
- facebook/convnext-tiny-224
- facebook/dinov2-small
pipeline_tag: image-classification
tags:
- manuscript
- bookbinding
- cultural-heritage
- digital-humanities
- convnext
- fine-tuned
library_name: timm
---
# Kleine-Marchen — Binding Detector (ConvNeXt-Tiny)
Модель на базе **ConvNeXt-Tiny (ImageNet-22k)** для определения является ли изображение фотографией переплёта рукописи.
Используется как **первая ступень** в двухэтапном пайплайне классификации переплётов рукописей РГБ.
---
## Назначение
Бинарная классификация:
- **Класс 1 — переплёт**: изображение является фотографией крышки переплёта рукописи
- **Класс 0 — не переплёт**: страница текста, разворот, предметная съёмка и т.д.
Модель решает задачу фильтрации при массовом скачивании изображений из цифровых архивов рукописей, где первые страницы оцифровки содержат как переплёты, так и текстовые страницы.
---
## Метрики
| Метрика | Значение |
|---|---|
| Accuracy (валидация) | **96%** |
---
## Данные
- **Источник**: фотографии из фондов РГБ
- **Размер**: ~500 изображений на каждый класс
- **Классы**: переплёт / не переплёт (страницы текста, развороты)
- **Разрешение при обучении**: 320×320 px
---
## Архитектура
**Базовая модель**: `convnext_tiny.fb_in22k` (ImageNet-22k pretrain, 28M параметров)
**Стратегия обучения**: обучение только классификационной головы при замороженном бэкбоне.
---
## Использование
Готовые скрипты доступны в репозитории:
**[https://github.com/Infarondus/Kleine-marchen](https://github.com/Infarondus/Kleine-marchen)**
### Быстрый старт
```python
import torch
import timm
import numpy as np
from PIL import Image
import albumentations as A
from albumentations.pytorch import ToTensorV2
IMAGE_SIZE = 320
transform = A.Compose([
A.LongestMaxSize(max_size=IMAGE_SIZE),
A.PadIfNeeded(min_height=IMAGE_SIZE, min_width=IMAGE_SIZE,
border_mode=0, value=[255, 255, 255]),
A.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
ToTensorV2(),
])
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = timm.create_model('convnext_tiny_in22k', pretrained=False, num_classes=2)
ckpt = torch.load('kleine-marchen_preprocess.pth', map_location=device, weights_only=False)
model.load_state_dict(ckpt['model_state_dict'])
model.to(device).eval()
img = np.array(Image.open('page.jpg').convert('RGB'))
tensor = transform(image=img)['image'].unsqueeze(0).to(device)
with torch.no_grad():
probs = torch.softmax(model(tensor), dim=1)[0]
print(f"Не переплёт: {probs[0]:.1%} | Переплёт: {probs[1]:.1%}")
```
---
## Место в пайплайне
Эта модель используется совместно с `binding-srednik-dinov2`:
```
Изображение → [ConvNeXt: переплёт?] → [DINOv2: есть средник?] → Результат
```
Скрипт двухступенчатого скрапера `scrape_bindings_v2.py` из репозитория **Kleine-Marchen** реализует этот пайплайн полностью.
---
## Ограничения
- Не предназначена для определения типа переплёта или его элементов — только факт наличия переплёта на снимке
- При сильно нестандартных условиях съёмки (белый фон, крупный план фрагмента) точность может снижаться
---
# Kleine-Marchen — Binding Srednik Detector (DINOv2 Ensemble)
Ансамбль из 5 моделей на базе **DINOv2 ViT-Small** для классификации переплётов рукописей по наличию **средника** — центрального декоративного элемента крышки переплёта.
Модель разработана в рамках исследования рукописного фонда Российской государственной библиотеки (РГБ).
---
## Назначение
Модель решает задачу бинарной классификации изображений переплётов:
- **Класс 1 — со средником**: на крышке переплёта присутствует средник
- **Класс 0 — без средника**: средник отсутствует
Модель предназначена для автоматической предварительной сортировки больших коллекций фотографий переплётов рукописей. Окончательная верификация результатов производится специалистом.
---
## Метрики
Оценка проводилась методом 5-fold стратифицированной кросс-валидации.
| Метрика | Значение |
|---|---|
| Accuracy (OOF Ensemble) | **94.50%** |
| F1-macro (OOF Ensemble) | **0.9450** |
| Precision | 0.9454 |
| Recall | 0.9450 |
**Confusion Matrix (OOF, все 5 фолдов):**
| | Предсказано: без средника | Предсказано: со средником |
|---|---|---|
| **Реально: без средника** | 471 | 20 |
| **Реально: со средником** | 34 | 457 |
---
## Данные
- **Источник**: фотографии переплётов рукописей из фондов РГБ
- **Размер обучающей выборки**: 567 изображений на каждый класс (1134 итого)
- **Формат**: цветные фотографии переплётов на тёмном фоне
- **Разрешение при обучении**: 280×280 px
Датасет собирался итеративно: после каждого цикла обучения производился анализ ошибок и доразметка сложных случаев (изношенные переплёты, пограничные экземпляры).
---
## Архитектура
**Базовая модель**: `vit_small_patch14_dinov2.lvd142m` (DINOv2 ViT-Small, 22M параметров)
**Голова классификатора**:
```
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)
**Ансамбль**: 5 моделей (по одной на каждый фолд) с усреднением вероятностей + TTA (5 аугментаций)
---
## Использование
Готовые скрипты для обучения, оценки и инференса доступны в репозитории:
**[https://github.com/Infarondus/Kleine-marchen](https://github.com/Infarondus/Kleine-marchen)**
### Быстрый старт
```python
import torch
import torch.nn as nn
import torch.nn.functional as F
import timm
import numpy as np
from PIL import Image
import albumentations as A
from albumentations.pytorch import ToTensorV2
MODEL_NAME = 'vit_small_patch14_dinov2.lvd142m'
IMAGE_SIZE = 280
DINO_MEAN = [0.485, 0.456, 0.406]
DINO_STD = [0.229, 0.224, 0.225]
def build_model():
backbone = timm.create_model(
MODEL_NAME, pretrained=False, num_classes=0,
img_size=IMAGE_SIZE, dynamic_img_size=True,
)
head = nn.Sequential(
nn.LayerNorm(backbone.embed_dim),
nn.Linear(backbone.embed_dim, 256),
nn.GELU(),
nn.Dropout(0.3),
nn.Linear(256, 2),
)
class DinoClassifier(nn.Module):
def __init__(self, b, h):
super().__init__()
self.backbone, self.head = b, h
def forward(self, x):
return self.head(self.backbone(x))
return DinoClassifier(backbone, head)
transform = A.Compose([
A.LongestMaxSize(max_size=IMAGE_SIZE),
A.PadIfNeeded(min_height=IMAGE_SIZE, min_width=IMAGE_SIZE,
border_mode=0, value=[255, 255, 255]),
A.Normalize(mean=DINO_MEAN, std=DINO_STD),
ToTensorV2(),
])
# Загрузка модели
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = build_model().to(device)
ckpt = torch.load('fold_1_km.pth', map_location=device, weights_only=False)
model.load_state_dict(ckpt['model_state_dict'])
model.eval()
# Инференс
img = np.array(Image.open('binding.jpg').convert('RGB'))
tensor = transform(image=img)['image'].unsqueeze(0).to(device)
with torch.no_grad():
probs = F.softmax(model(tensor), dim=1)[0]
print(f"Без средника: {probs[0]:.1%} | Со средником: {probs[1]:.1%}")
```
### Рекомендуемый порог
При использовании ансамбля рекомендуется порог **0.55–0.75** для класса «со средником» в зависимости от допустимого уровня ложных срабатываний.
---
## Ограничения
- Модель обучена на фотографиях переплётов РГБ и может хуже работать на изображениях из других коллекций (domain shift)
- Сильно изношенные переплёты с плохо читаемым средником являются наиболее сложными случаями
- Не предназначена для работы с изображениями страниц, не являющихся переплётами — для фильтрации используйте модель `binding-detector-convnext` на первом этапе
---
## Цитирование
Если вы используете эту модель в исследовании, пожалуйста, укажите репозиторий:
```
@misc{kleine-marchen-binding,
author = {Infarondus},
title = {Kleine-Marchen — Binding Detector (ConvNeXt-Tiny)},
year = {2026},
url = {https://github.com/Infarondus/Kleine-marchen_Base}
}
```
---