karma689's picture
Upload folder using huggingface_hub
09ba195 verified
|
Raw
History Blame
8.6 kB
---
language:
- bo
license: apache-2.0
library_name: transformers
tags:
- image-classification
- dinov3
- tibetan
- script-classification
- paleography
- fine-tuned
- document-analysis
base_model: facebook/dinov3-vits16-pretrain-lvd1689m
datasets:
- custom
metrics:
- f1
- accuracy
pipeline_tag: image-classification
model-index:
- name: Tibetan Script Classifier (DINOv3 ViT-S)
results:
- task:
type: image-classification
name: Tibetan Script Classification
metrics:
- name: Macro F1 (whole page)
type: f1
value: 0.512
- name: Accuracy (whole page)
type: accuracy
value: 0.571
- name: Macro F1 (CLAHE patches, page-level)
type: f1
value: 0.529
---
# Tibetan Script Classifier (DINOv3)
This repository contains fine-tuned Tibetan script classification checkpoints for 18 classes, trained from the DINOv3 ViT-S backbone:
- Backbone: `facebook/dinov3-vits16-pretrain-lvd1689m`
- Task: 18-way script classification
- Training script included: `finetune_dinov3.py`
**Hugging Face access:** DINOv3 requires access approval at [huggingface.co/facebook/dinov3-vits16-pretrain-lvd1689m](https://huggingface.co/facebook/dinov3-vits16-pretrain-lvd1689m) before `from_pretrained` / downloads will work. Anyone cloning this repo will see the same gated-model error until their HF account is granted access and they are logged in (`huggingface-cli login` or `HF_TOKEN`).
## Label Set
`dhumri`, `difficult`, `drathung`, `drudring`, `druring`, `druthung`, `khyuyig`, `multi_scripts`, `non_tibetan`, `peri`, `petsuk`, `trinyig`, `tsegdrig`, `tsugchung`, `tsumachug`, `uchen_sugdring`, `uchen_sugthung`, `yigchung`.
## Preprocessing (per experiment)
Images for training were produced as follows (see `preprocess.py` in the parent project):
- **`whole_page`:** resize so the **short edge is 224 px**, then **center crop** to 224×224 (one crop per source page).
- **`patches_color`:** same short-edge resize to 224, then **sliding-window** 224×224 patches with **25% overlap** between windows (multiple crops per page).
- **`patches_clahe`:** identical patch layout as `patches_color`; each patch is converted to grayscale and **CLAHE** contrast normalization is applied (`clipLimit=2.0`, `tileGridSize=(8,8)`), then saved as BGR/RGB for training.
## Training recipe
- **Progressive unfreezing (defaults in `finetune_dinov3.py`):**
- **Stage A — head only:** 20 epochs, backbone frozen, classifier head LR **1e-3** (backbone LR 0).
- **Stage B — last 2 blocks:** 10 epochs, backbone LR **1e-5**, head LR **1e-3**.
- **Stage C — last 4 blocks:** 10 epochs, backbone LR **5e-6**, head LR **5e-4**.
- **Loss:** class-weighted **cross-entropy** with inverse-frequency weights over the training split (`nn.CrossEntropyLoss(weight=...)`).
- **Sampling:** the published runs use a standard `DataLoader` with **`shuffle=True`**. The script also defines **`get_weighted_sampler` → `WeightedRandomSampler`** if you want to switch the train loader to explicit class-balanced sampling.
- **Document-aware augmentations (train only):** `RandomRotation` **±5°** (fill white), `ColorJitter` brightness/contrast **±20%** (`0.2`), plus `RandomResizedCrop` and light `RandomErasing` as in `ScriptDataset`; **no horizontal flip**.
## Class distribution (`whole_page` split totals)
The whole-page split has **5,684** samples in total (**train 3,996 / val 844 / test 844**). The **844** figure in test metrics is **only the test split** (roughly 15% of pages per class held out for testing), not the full dataset.
The per-class table below sums **train + val + test** counts. A benchmark exclusion manifest (`benchmark_page_ids.json`, **88** page IDs across 18 classes) is consulted when building splits: any file whose page ID matches is skipped and counted in `splits.json` under `skipped_excluded_files_by_class`. For the published `whole_page/splits.json`, that skip map is **empty**—so either those pages were **not present** under the training `data_dir`, or they had **already been removed** before this split was built. The **5,684** totals are whatever files remained for stratified splitting.
| Class | Samples |
|---|---:|
| dhumri | 98 |
| difficult | 170 |
| drathung | 129 |
| drudring | 132 |
| druring | 119 |
| druthung | 207 |
| khyuyig | 113 |
| multi_scripts | 235 |
| non_tibetan | 192 |
| peri | 614 |
| petsuk | 1388 |
| trinyig | 42 |
| tsegdrig | 749 |
| tsugchung | 77 |
| tsumachug | 178 |
| uchen_sugdring | 835 |
| uchen_sugthung | 240 |
| yigchung | 166 |
## Experiments Included
### 1) `whole_page`
- Files: `whole_page/final_model.pt`, `results.json`, `confusion_matrix.png`, `confusion_matrix.csv`, `splits.json`
- Test (image-level) macro-F1: **0.5124**
- Test accuracy: **0.5711**
### 2) `patches_color`
- Files: `patches_color/final_model.pt`, `results.json`, `confusion_matrix.png`, `checkpoint_page_eval.json`, `splits.json`
- Test (patch-level) macro-F1: **0.4899**
- Re-eval **page-level** macro-F1 for shipped `final_model.pt` (`checkpoint_page_eval.json`): **0.5017**
- Best **page-level** macro-F1 among stage checkpoints on the same grid: **0.5043** (**Stage A**)
### 3) `patches_clahe`
- Files: `patches_clahe/final_model.pt`, `results.json`, `confusion_matrix.png`, `checkpoint_page_eval.json`, `splits.json`
- Test (patch-level) macro-F1: **0.4911**
- Re-eval page-level macro-F1 for shipped `final_model.pt`: **0.5261**
- Best **page-level** macro-F1 among stage checkpoints: **0.529** (**Stage B**)
## Which stage produced which checkpoint?
- **`final_model.pt` in each folder** is the stage with the highest **validation macro-F1** among `best_stage_*.pt` checkpoints (see `best_val_checkpoint` in each `results.json`): **Stage B** for `whole_page`, **Stage C** for both `patches_color` and `patches_clahe`.
- For **page-level** quality on the patch runs, the best single stage on the re-eval grid differs: **Stage A** (`patches_color`) and **Stage B** (`patches_clahe`) beat their respective `final_model.pt` page scores—use `checkpoint_page_eval.json` if you want to deploy a stage checkpoint instead of the val-selected default.
## Which experiment won?
CLAHE patches achieved the highest **page-level** macro-F1 (**0.529** on the best stage checkpoint), while **whole page** achieved the best **image-level** macro-F1 (**0.512**). **Whole page** is recommended for production due to simpler inference.
## How To Load a Checkpoint
```python
import torch
from pathlib import Path
from finetune_dinov3 import DINOv3Classifier, DINOV3_MODEL_ID
ckpt_path = Path("whole_page/final_model.pt")
payload = torch.load(ckpt_path, map_location="cpu")
label_to_idx = payload["label_to_idx"]
idx_to_label = {v: k for k, v in label_to_idx.items()}
num_classes = len(label_to_idx)
model = DINOv3Classifier(DINOV3_MODEL_ID, num_classes)
model.load_state_dict(payload["model_state_dict"])
model.eval()
```
## Inference (Single Image)
```python
import torch
from PIL import Image
from transformers import AutoImageProcessor
processor = AutoImageProcessor.from_pretrained(DINOV3_MODEL_ID)
img = Image.open("example.png").convert("RGB")
inputs = processor(images=img, return_tensors="pt")
with torch.no_grad():
logits = model(inputs["pixel_values"])
probs = torch.softmax(logits, dim=1)[0].cpu().numpy()
pred_idx = int(probs.argmax())
pred_label = idx_to_label[pred_idx]
print(pred_label, float(probs[pred_idx]))
```
## Page-Level Inference (Patch Aggregation)
For patch experiments (`patches_color`, `patches_clahe`), aggregate by page stem:
1. group patch probabilities by page ID (strip `_pN` suffix),
2. average probabilities per page,
3. take `argmax` of averaged probabilities.
This is the same logic used in the re-evaluation script output (`checkpoint_page_eval.json`).
## Known Limitations
- Class imbalance is high (for example `petsuk` and `uchen_sugdring` dominate, while `trinyig` is small).
- Results can vary by preprocessing variant and by patch vs page-level evaluation protocol.
- Patch-level metrics and page-level metrics are not directly interchangeable.
- The model expects Tibetan manuscript-style inputs; performance can drop on out-of-domain scans or mixed/noisy pages.
- Checkpoints are tied to the exact label mapping saved in each payload (`label_to_idx`).
## Reproducibility Notes
- Exclusion manifest support is enabled in training (`benchmark_page_ids.json`).
- Full training code used for these artifacts is included at `finetune_dinov3.py`.