paularoni commited on
Commit
dff1fcc
·
verified ·
1 Parent(s): 1efd3ab

Promote convnext_tiny_all_lands_r8_s67 (nonland macro F1 0.611)

Browse files
Files changed (3) hide show
  1. README.md +94 -3
  2. model_bundle.pt +3 -0
  3. model_info.json +65 -0
README.md CHANGED
@@ -1,3 +1,94 @@
1
- ---
2
- license: mit
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ tags:
3
+ - image-classification
4
+ - multi-label-classification
5
+ - timm
6
+ - pytorch
7
+ ---
8
+
9
+ <!-- Generated by scripts/promote_model.py on every promotion: edits made here are overwritten. -->
10
+
11
+ # MTG Color Classifier
12
+
13
+ Looks at a piece of Magic: The Gathering card art and predicts which of the five
14
+ Magic colors it belongs to: **W**hite, bl**U**e, **B**lack, **R**ed, **G**reen. It is
15
+ multi-label: each color gets its own probability and its own decision threshold,
16
+ so a picture can be several colors, or none (colorless). The model only ever sees
17
+ the image, never the card's name, text or type.
18
+
19
+ Trained by fine-tuning an ImageNet-pretrained `convnext_tiny.fb_in22k_ft_in1k` (via `timm`) on
20
+ the art crops of every unique illustration on [Scryfall](https://scryfall.com).
21
+ Lands are labeled with their color identity (Forest -> G, a dual land -> both colors); every other card with its `colors` field.
22
+
23
+ ## Current model
24
+
25
+ | | |
26
+ |---|---|
27
+ | Run | `convnext_tiny_all_lands_r8_s67` |
28
+ | Backbone | `convnext_tiny.fb_in22k_ft_in1k` |
29
+ | Test macro F1 (nonland test cards, n=4,477) | **0.611** |
30
+ | Test macro AP | 0.657 |
31
+ | Exact match (all five colors right) | 0.416 |
32
+ | Promoted | 2026-10-19T20:01:47+00:00 |
33
+ | Trained with | torch 2.11.0+cu128, timm 1.0.29 |
34
+
35
+ | Color | Test F1 | Threshold |
36
+ |---|---|---|
37
+ | W | 0.567 | 0.25 |
38
+ | U | 0.621 | 0.42 |
39
+ | B | 0.548 | 0.37 |
40
+ | R | 0.643 | 0.31 |
41
+ | G | 0.674 | 0.34 |
42
+
43
+ Thresholds were tuned per color on the validation split. Test cards never share
44
+ an `oracle_id` with training cards, so the scores are on cards the model never saw.
45
+
46
+ ## Files
47
+
48
+ - `model_bundle.pt`: one dict with the weights (`state_dict`) and everything needed
49
+ to use them: `backbone`, `classes`, `thresholds`, `preprocess`. Loads with
50
+ `torch.load(..., weights_only=True)`.
51
+ - `model_info.json`: the same fields without the weights.
52
+
53
+ ## Usage
54
+
55
+ ```python
56
+ import timm, torch
57
+ from huggingface_hub import hf_hub_download
58
+ from io import BytesIO
59
+ from PIL import Image
60
+ from torchvision import transforms as T
61
+
62
+ bundle = torch.load(hf_hub_download("paularoni/mtg-color-classifier", "model_bundle.pt"),
63
+ map_location="cpu", weights_only=True)
64
+ net = timm.create_model(bundle["backbone"], pretrained=False, num_classes=len(bundle["classes"]))
65
+ net.load_state_dict(bundle["state_dict"])
66
+ net.eval()
67
+
68
+ pp = bundle["preprocess"]
69
+ im = Image.open("art.jpg").convert("RGB") # ideally color-managed to sRGB first
70
+ im = im.resize((pp["cache_size"], pp["cache_size"]), Image.Resampling.LANCZOS)
71
+ buf = BytesIO() # JPEG round trip: the training images were JPEGs
72
+ im.save(buf, "JPEG", quality=pp["cache_jpeg_quality"], subsampling=pp["cache_jpeg_subsampling"])
73
+ im = Image.open(buf).convert("RGB")
74
+ x = T.Compose([T.Resize((pp["img_size"], pp["img_size"])), T.ToTensor(),
75
+ T.Normalize(pp["mean"], pp["std"])])(im)
76
+ with torch.no_grad():
77
+ probs = torch.sigmoid(net(x.unsqueeze(0)))[0].tolist()
78
+ colors = [c for c, p, t in zip(bundle["classes"], probs, bundle["thresholds"]) if p >= t]
79
+ print(dict(zip(bundle["classes"], probs)), "".join(colors) or "colorless")
80
+ ```
81
+
82
+ Images are squashed to a square (no crop): color cues are often at the edges of the art.
83
+ The two-step resize (Lanczos to 256, JPEG quality 95, then bilinear
84
+ to 224) matches how the training images were prepared. Skipping the JPEG step
85
+ shifts some probabilities by up to 0.1.
86
+
87
+ ## Limitations
88
+
89
+ - Art alone is often ambiguous: a macro F1 around 0.61 means many pictures get
90
+ a wrong or missing color. Treat the probabilities as a guess, not a rule lookup.
91
+ - Trained on Magic card art; other pictures get an answer, but it means little.
92
+
93
+ Unofficial fan project, not affiliated with or endorsed by Wizards of the Coast.
94
+ Card art is (c) Wizards of the Coast and its artists; card data and images from Scryfall.
model_bundle.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3cb06ee58206ac1f398f03bf2efd969ed86798578361062ee7f13ba773b45119
3
+ size 111354175
model_info.json ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "format_version": 1,
3
+ "run": "convnext_tiny_all_lands_r8_s67",
4
+ "backbone": "convnext_tiny.fb_in22k_ft_in1k",
5
+ "classes": [
6
+ "W",
7
+ "U",
8
+ "B",
9
+ "R",
10
+ "G"
11
+ ],
12
+ "thresholds": [
13
+ 0.25,
14
+ 0.42,
15
+ 0.37,
16
+ 0.31,
17
+ 0.34
18
+ ],
19
+ "preprocess": {
20
+ "resize": "squash",
21
+ "color": "srgb",
22
+ "cache_size": 256,
23
+ "cache_resample": "lanczos",
24
+ "cache_jpeg_quality": 95,
25
+ "cache_jpeg_subsampling": "4:4:4",
26
+ "img_size": 224,
27
+ "resample": "bilinear",
28
+ "mean": [
29
+ 0.485,
30
+ 0.456,
31
+ 0.406
32
+ ],
33
+ "std": [
34
+ 0.229,
35
+ 0.224,
36
+ 0.225
37
+ ]
38
+ },
39
+ "land_labels": "identity",
40
+ "metrics": {
41
+ "population": "nonland test cards",
42
+ "n": 4477,
43
+ "macro_f1": 0.6105,
44
+ "macro_ap": 0.6575,
45
+ "f1": {
46
+ "W": 0.5667,
47
+ "U": 0.6207,
48
+ "B": 0.548,
49
+ "R": 0.6434,
50
+ "G": 0.6738
51
+ },
52
+ "exact_match": 0.4157
53
+ },
54
+ "training": {
55
+ "torch": "2.11.0+cu128",
56
+ "timm": "1.0.29",
57
+ "run_tag": "r8",
58
+ "land_variant": "all_lands",
59
+ "seed": 67,
60
+ "best_val_macro_ap": 0.6556,
61
+ "kaggle_notebook": "paularoni/mtg-color-classification-training"
62
+ },
63
+ "weights_sha256": "a00d794b7397f7137cb6299d02c7707a19a5b31c90e6b74ad274f4591c4e4d68",
64
+ "promoted_at": "2026-10-19T20:01:47+00:00"
65
+ }