Views
No views yet
| Property | Value |
|---|---|
| Architecture | DINOv2 Base + multi-task classification heads |
| Backbone | facebook/dinov2-base |
| Framework | PyTorch |
| Integration | Hugging Face Transformers |
| Number of sides | 4 |
| Side classes | 3 |
| Pattern classes | 60 output classes |
| Input size | 224 × 224 |
| Color format | RGBA/RGB converted by the image processor |
| Training strategy | Two-stage training |
| Augmentation | Rotation, mirroring, brightness, contrast, color, noise and blur |
1TOP
2RIGHT
3BOTTOM
4LEFTSMOOTH — a flat puzzle border.OUTER — an outward protruding tab.INNER — an inward indentation.1TOP = SMOOTH
2RIGHT = OUTER
3BOTTOM = INNER
4LEFT = SMOOTHSOIS1S = SMOOTH
2O = OUTER
3I = INNER[SMOOTH, OUTER, INNER, SMOOTH] -> SOISfacebook/dinov2-base as its visual backbone.1Input Image
2 │
3 ▼
4DINOv2 Base
5 │
6 ▼
7CLS Embedding
8 │
9 ├─────────────────────────────┐
10 │ │
11 ▼ ▼
12Sides Head Pattern Head
13 │ │
14 ▼ ▼
154 × 3 logits 60 logits
16 │ │
17 ▼ ▼
18TOP/RIGHT/BOTTOM/LEFT Pattern4 × 3 = 12 logits(batch_size, 4, 3)10 = TOP
21 = RIGHT
32 = BOTTOM
43 = LEFT10 = SMOOTH
21 = OUTER
32 = INNER1LayerNorm
2Linear(hidden_size → 512)
3GELU
4Dropout(0.2)
5Linear(512 → 12)1pattern_input = torch.cat(
2 [
3 features,
4 sides_logits.detach().flatten(start_dim=1)
5 ],
6 dim=1
7)1LayerNorm
2Linear(hidden_size + 12 → 512)
3GELU
4Dropout(0.2)
5Linear(512 → 60)1SMOOTH
2OUTER
3INNERTOP RIGHT BOTTOM LEFT1SSOI
2SSIO
3OSSI
4...SOIS1TOP = SMOOTH
2RIGHT = OUTER
3BOTTOM = INNER
4LEFT = SMOOTH1def build_pattern_label(sides_labels):
2 return "".join([side[0] for side in sides_labels])1Original puzzle image
2 │
3 ▼
4Piece bounding box
5 │
6 ▼
720% expanded bounding box
8 │
9 ▼
10Crop
11 │
12 ▼
13Resize to 224 × 224
14 │
15 ▼
16DINOv2 image processor1Optimizer: AdamW
2Learning rate: 1e-3
3Batch size: 64
4Maximum epochs: 30
5Backbone: frozen
6Pattern loss weight: 0.50
7Early stopping patience: 4loss = sides_loss + pattern_weight × pattern_losspattern_weight = 0.501Optimizer: AdamW
2Learning rate: 5e-6
3Batch size: 16
4Maximum epochs: 40
5Backbone: trainable
6Pattern loss weight: 0.50
7Early stopping patience: 4pip install torch torchvision transformers pillowAutoModel.trust_remote_code=True is required.1from transformers import AutoModel, AutoImageProcessor
2from PIL import Image
3import torch
4
5MODEL_ID = "pablo-moreira/puzzle-piece-sides-classifier"
6
7processor = AutoImageProcessor.from_pretrained(
8 MODEL_ID,
9 trust_remote_code=True
10)
11
12model = AutoModel.from_pretrained(
13 MODEL_ID,
14 trust_remote_code=True
15)
16
17model.eval()image = Image.open("puzzle_piece.png").convert("RGBA")1inputs = processor(
2 images=image,
3 return_tensors="pt"
4)1with torch.no_grad():
2 outputs = model(**inputs)1outputs.sides_logits
2outputs.pattern_logits1sides_logits:
2(batch_size, 4, 3)
3
4pattern_logits:
5(batch_size, 60)1print(outputs.sides_logits.shape)
2# torch.Size([1, 4, 3])
3
4print(outputs.pattern_logits.shape)
5# torch.Size([1, 60])sides_ids = outputs.predicted_sides_labels()[0]1sides = processor.decode_sides_labels(
2 [sides_ids.tolist()]
3)[0]
4
5print(sides)['SMOOTH', 'OUTER', 'INNER', 'SMOOTH']1[
2 TOP,
3 RIGHT,
4 BOTTOM,
5 LEFT
6]1sides_probabilities = outputs.sides_probabilities()[0]
2
3print(sides_probabilities.shape)
4# torch.Size([4, 3])1for side_index, probabilities in enumerate(sides_probabilities):
2 print(
3 side_index,
4 probabilities.tolist()
5 )10 → SMOOTH
21 → OUTER
32 → INNERpattern_id = outputs.predicted_pattern_labels()[0].item()1pattern = processor.decode_pattern_labels(
2 [pattern_id]
3)[0]
4
5print(pattern)SOIS1pattern_probabilities = outputs.pattern_probabilities()[0]
2
3print(pattern_probabilities.shape)
4# torch.Size([60])1from transformers import AutoModel, AutoImageProcessor
2from PIL import Image
3import torch
4from io import BytesIO
5import requests
6
7MODEL_ID = "pablo-moreira/puzzle-piece-sides-classifier"
8
9IMAGES = [
10 "https://huggingface.co/datasets/pablo-moreira/puzzle-map/resolve/main/samples/puzzle-piece-sides-classifier/120_avengers_20260709_105353_d1ee901639e04ba7972a6505cb07d541_OOII.png",
11 "https://huggingface.co/datasets/pablo-moreira/puzzle-map/resolve/main/samples/puzzle-piece-sides-classifier/120_avengers_20260709_110848_3b131fdfeb134505b994c6c7cab791ad_IIOO.png",
12 "https://huggingface.co/datasets/pablo-moreira/puzzle-map/resolve/main/samples/puzzle-piece-sides-classifier/120_avengers_20260709_112115_5a0ca30002bf48dfb97b9184f038cf30_IISI.png",
13 "https://huggingface.co/datasets/pablo-moreira/puzzle-map/resolve/main/samples/puzzle-piece-sides-classifier/camera_2c545dac37064e56aea6139df0951608_SOIS.png",
14 "https://huggingface.co/datasets/pablo-moreira/puzzle-map/resolve/main/samples/puzzle-piece-sides-classifier/puzzle-focus_d378d8e8-20240108_211624.redimensionado_ISSO.png"
15]
16
17
18# Load processor
19processor = AutoImageProcessor.from_pretrained(
20 MODEL_ID,
21 trust_remote_code=True
22)
23
24# Load model
25model = AutoModel.from_pretrained(
26 MODEL_ID,
27 trust_remote_code=True
28)
29
30model.eval()
31
32# Load image
33response = requests.get(IMAGES[0])
34response.raise_for_status()
35
36image = Image.open(BytesIO(response.content)).convert("RGB")
37
38# Prepare input
39inputs = processor(
40 images=image,
41 return_tensors="pt"
42)
43
44# Inference
45with torch.no_grad():
46 outputs = model(**inputs)
47
48# --------------------------------------------------
49# Sides
50# --------------------------------------------------
51
52sides_ids = outputs.predicted_sides_labels()[0].tolist()
53
54sides = processor.decode_sides_labels(
55 [sides_ids]
56)[0]
57
58print("Sides:")
59print(f"TOP: {sides[0]}")
60print(f"RIGHT: {sides[1]}")
61print(f"BOTTOM: {sides[2]}")
62print(f"LEFT: {sides[3]}")
63
64# --------------------------------------------------
65# Pattern
66# --------------------------------------------------
67
68pattern_id = outputs.predicted_pattern_labels()[0].item()
69
70pattern = processor.decode_pattern_labels(
71 [pattern_id]
72)[0]
73
74print()
75print("Pattern:")
76print(pattern)1Sides:
2TOP: INNER
3RIGHT: SMOOTH
4BOTTOM: SMOOTH
5LEFT: OUTER
6
7Pattern:
8ISSO1sides = processor.convert_pattern_to_sides("SOIS")
2
3print(sides)1[
2 "SMOOTH",
3 "OUTER",
4 "INNER",
5 "SMOOTH"
6]TOP → RIGHT → BOTTOM → LEFTPuzzlePieceSidesClassifierOutput.1outputs.sides_logits
2outputs.pattern_logits1outputs.sides_probabilities()
2outputs.pattern_probabilities()
3
4outputs.predicted_sides_labels()
5outputs.predicted_pattern_labels()1sides_probabilities = outputs.sides_probabilities()
2pattern_probabilities = outputs.pattern_probabilities()
3
4sides_predictions = outputs.predicted_sides_labels()
5pattern_predictions = outputs.predicted_pattern_labels()10°
290°
3180°
4270°puzzle_piece_sides_classifier.pypuzzle_piece_sides_classifier_processor.pysave_pretrained() mechanism.1AutoModel.from_pretrained(
2 "pablo-moreira/puzzle-piece-sides-classifier",
3 trust_remote_code=True
4)1AutoImageProcessor.from_pretrained(
2 "pablo-moreira/puzzle-piece-sides-classifier",
3 trust_remote_code=True
4)| Metric | v11 | v12 | v13 | v14 | v15 | v16 | v17 | v18 | v19 | v20 | v21 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Dataset | 518 | 518 | 2072 | 2080 | 3648 | 4664 | 6824 | 7160 | 7784 | 7784 | 9472 |
| Training examples | 414 | 414 | 1657 | 1664 | 2918 | 3731 | 5459 | 5728 | 6227 | 6227 | 7577 |
| Validation examples | 104 | 104 | 415 | 416 | 730 | 933 | 1365 | 1432 | 1557 | 1557 | 1895 |
| Pattern weight | 0.35 | 0.50 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 |
| Best epoch | 14 | 11 | 9 | 9 | 8 | 6 | 6 | 16 | 10 | 14 | 8 |
| Train loss | 0.0419 | 0.1039 | 0.0134 | 0.0060 | 0.0041 | 0.0080 | 0.0136 | 0.0049 | 0.0011 | 0.0106 | 0.0235 |
| Train sides loss | 0.0178 | 0.0455 | 0.0031 | 0.0007 | 0.0006 | 0.0009 | 0.0034 | 0.0011 | 0.0001 | 0.0044 | 0.0089 |
| Train pattern loss | 0.0688 | 0.1168 | 0.0206 | 0.0106 | 0.0069 | 0.0142 | 0.0204 | 0.0076 | 0.0019 | 0.0124 | 0.0291 |
| Train sides accuracy | 0.9982 | 0.9886 | 0.9995 | 1.0000 | 1.0000 | 1.0000 | 0.9992 | 0.9996 | 1.0000 | 0.9991 | 0.9984 |
| Train pattern accuracy | 0.9952 | 0.9808 | 0.9982 | 1.0000 | 0.9993 | 0.9997 | 0.9965 | 0.9988 | 1.0000 | 0.9979 | 0.9933 |
| Validation loss | 0.3734 | 0.3613 | 0.0634 | 0.0625 | 0.0285 | 0.0246 | 0.0212 | 0.0148 | 0.0052 | 0.0038 | 0.0011 |
| Validation sides loss | 0.1471 | 0.1156 | 0.0304 | 0.0299 | 0.0092 | 0.0084 | 0.0085 | 0.0078 | 0.0022 | 0.0019 | 0.0005 |
| Validation pattern loss | 0.6465 | 0.4914 | 0.0661 | 0.0652 | 0.0387 | 0.0325 | 0.0253 | 0.0139 | 0.0059 | 0.0038 | 0.0012 |
| Validation sides accuracy | 0.9815 | 0.9630 | 0.9939 | 0.9976 | 0.9986 | 0.9989 | 0.9987 | 0.9983 | 0.9990 | 0.9992 | 0.9999 |
| Validation pattern accuracy | 0.8426 | 0.9167 | 0.9904 | 0.9928 | 0.9918 | 0.9979 | 0.9978 | 0.9951 | 0.9987 | 0.9987 | 1.0000 |
| Validation sides F1 | 0.9998 | ||||||||||
| Validation pattern F1 | 1.0000 |
#9472#77840000#7784#7160#6824#4664#3648#20800.500.351Pablo Moreira.
2Puzzle Piece Sides Classifier.
3PuzzleMap projectfacebook/dinov2-base model is subject to its own license and terms of use.