danbooru-tagger-v1
An EVA02-L Danbooru tagger with a 24,000-tag general vocabulary, fine-tuned
including the encoder from wd-eva02-large-tagger-v3 on 677,355 posts newer
than any public tagger's training cutoff.
Every prior WD-derived model froze the encoder. pixai-tagger-v0.9 says so on its
own card β "We continued training the classification head of EVA02 (from WD v3) β¦
encoder frozen" β and it scores below the plain WD model it started from.
Unfreezing is what this release tests. Internal validation macro-F1 went
0.4380 β 0.5692 when the encoder was unfrozen.
Read the results section before using this. v1 wins some metrics and loses one, and which you care about depends on what you are building.
Results
11,639 held-out posts with id > 8,600,750 β after the training cutoff of every
model compared (WD 7.22M, Camie ~8.36M, PixAI 8.60M), so none of them has seen
these images. Scored on the intersection of all seven vocabularies (7,779 tags;
3,101 with β₯10 positives), because macro-F1 over each model's own vocabulary
rewards having a bigger vocabulary rather than being more accurate.
| model | micro-F1 | macro-F1 | macro-AP | fine AP | vocab | coverage |
|---|---|---|---|---|---|---|
| v1 (this model) | 0.6569 | 0.4889 | 0.4672 | 0.5643 | 24,000 | 0.914 |
| wd-eva02-large-v3 | 0.6369 | 0.4728 | 0.4668 | 0.5979 | 8,106 | 0.506 |
| wd-vit-large-v3 | 0.6366 | 0.4574 | 0.4576 | 0.5960 | 8,106 | 0.506 |
| wd-swinv2-v3 | 0.6380 | 0.4522 | 0.4549 | 0.5821 | 8,106 | 0.506 |
| pixai-tagger-v0.9 | 0.6060 | 0.4481 | 0.4482 | 0.5696 | 9,740 | 0.588 |
| cl_tagger-1.02 | 0.5744 | 0.3973 | 0.3960 | 0.5329 | 25,098 | 0.869 |
| camie-tagger-v2 | 0.5848 | 0.3592 | 0.3509 | 0.4609 | 30,841 | 0.858 |
coverage = the share of tags actually present in the eval set that the model is able to emit at all.
The verdict is split, and it is not a clean win.
- macro-F1 +0.0161 and micro-F1 +0.0200 over the best incumbent β won.
- coverage 0.914 vs 0.506 β won, and by a lot.
- macro-AP 0.4672 vs 0.4668 β a tie. On the tags both models share, ranking quality is identical; the F1 win comes from calibration, not discrimination.
- fine-grained AP 0.5643 vs 0.5979 β lost, and it is not noise: bootstrap over images gives β0.0336, 95% CI [β0.052, β0.015].
Per-tag, the pattern is consistent: v1 improves where WD is weak and degrades
where WD is strong. It gains nontraditional_miko +0.387 (WD manages only 0.186),
cityscape +0.097, hands_up +0.081, miko +0.070; it loses lily_(flower)
β0.182, flower_field β0.168, v β0.142, bouquet β0.125, torii β0.116. That
is drift from a single-epoch fine-tune, not a worse encoder.
The configuration that does beat WD: ensemble.py
Because v1 and WD fail on different tags (probability correlation r = 0.876), averaging them beats either alone. Paired bootstrap over images, vs wd-eva02:
| system | macro-AP | Ξ (95% CI) | fine AP | Ξ (95% CI) |
|---|---|---|---|---|
| v1 alone | 0.4685 | +0.0016 [β0.003, +0.004] | 0.5666 | β0.0315 [β0.049, β0.012] |
| v1 + wd-eva02 | 0.5090 | +0.0421 [+0.039, +0.044] | 0.6113 | +0.0133 [+0.002, +0.022] |
| v1 + eva02 + vitL + swinv2 | 0.5153 | +0.0483 [+0.045, +0.050] | 0.6262 | +0.0281 [+0.013, +0.041] |
| eva02 + vitL + swinv2 (control, no v1) | 0.4860 | +0.0190 [+0.017, +0.020] | 0.6145 | +0.0164 [+0.002, +0.028] |
Ensembling anything helps β that control row matters. But v1 + wd-eva02, two models, beats an ensemble of all five incumbents (macro-AP 0.4919), and adding v1 to the three-WD ensemble lifts macro-AP by a further +0.029. On macro-AP, v1 is the most valuable single member available. On fine AP its marginal contribution over a WD-only ensemble is smaller (+0.0117) but still positive.
ensemble.py implements the two-model blend: average on the 7,800 shared
general tags, v1 alone on the other 16,200. It costs two forward passes.
Which should you use?
- Deployed tagger with a threshold, or you need the 16,199 extra tags β v1
alone (
predict.py). Better F1, far better coverage, names the specific tag more often (79.8% vs 72.3% on fine/parent pairs). - Ranking quality matters most and you can afford 2Γ inference β
ensemble.py. - You only care about WD's 8,106 tags and want one model β keep using wd-eva02-large-v3. v1 does not beat it on fine-grained AP.
Usage
pip install onnxruntime pillow numpy
python predict.py image.png --thr 0.38
# the blend that wins on ranking; downloads wd-eva02 on first run
pip install torch timm pillow numpy huggingface_hub
python ensemble.py image.png --thr 0.38
Thresholds: 0.38 maximises macro-F1, 0.73 maximises micro-F1. Both were swept on the evaluation set itself, so treat them as reasonable defaults rather than as held-out-validated values β the same sweep was applied to every model in the table, so the comparison is fair even though the absolute F1s are optimistic.
Preprocessing β this is not optional
The model was trained on a specific pipeline and degrades quietly if you change it:
- composite alpha onto white
- pad to a square with white
- bicubic resize to 448Γ448
- scale to
[0,1], then(x - 0.5) / 0.5 - swap RGB β BGR
- transpose to NCHW
timm's resolve_data_config returns CLIP normalisation for this architecture,
which is wrong here. predict.py is the reference implementation.
Files
| file | what |
|---|---|
onnx/model.onnx |
fp16, self-contained, opset 18. In [batch,3,448,448] fp32 NCHW, BGR; out logits [batch,24000] |
onnx/model_prob.onnx |
same graph with a Sigmoid appended; out probs [batch,24000] |
weights.fp16.pt |
PyTorch state dict + tag list, for further fine-tuning |
selected_tags.csv |
tag_id,name,category,count,n_train,n_fresh in output order |
predict.py, ensemble.py |
reference inference |
Not a drop-in replacement for WD's ONNX. WD publishes [batch,448,448,3]
NHWC with 10,861 outputs; this is NCHW with 24,000 outputs and a different tag
list. Existing WD14 tooling needs a transpose and the tag CSV from this repo.
Caveats
61% of the vocabulary is untested. Of 24,000 tags, 14,626 have fewer than 1,000 training examples, and on an 11,639-image eval set almost none appear often enough to score:
| n_train | tags | measurable (β₯5 eval positives) | mean AP |
|---|---|---|---|
| 0β100 | 656 | 0 | n/a |
| 100β1,000 | 13,970 | 67 | 0.4095 |
| 1,000β10,000 | 6,507 | 2,278 | 0.3826 |
| 10,000+ | 2,867 | 2,828 | 0.4714 |
The tail is not shown to be good or bad β it is unmeasured. "24,000 tags" rests
on roughly 5,173 tags this eval set can actually check. selected_tags.csv carries
n_train per tag so you can see what each prediction is backed by.
Other limitations:
- Trained 1 epoch, not 2 β the unfrozen encoder ran at 43.8 img/s against 120 img/s frozen, and 2 epochs did not fit the compute budget.
- The training corpus was stored at 640px long side to fit on disk, so the model only ever saw double-resampled images (native β 640 β 448). It is scored that way too rather than tuned around.
- Ratings and character tags are not included; general tags only.
fine APabove averages only 31 tags across 3 groups, so its confidence interval is wide (Β±0.019). Treat small differences on that column with care.
Training
- Base
SmilingWolf/wd-eva02-large-tagger-v3, encoder unfrozen after a head-only warm-up of 0.3 epoch. - 677,355 images: 548,410 posts newer than 8,600,750 plus 126,704 replay images
from older posts, built from
nyanko-devs/danbooru2026. - Head extended to 24,000 general tags, overlapping rows warm-started from the
base. New rows start at zero weight with a log-odds bias from the tag's base
rate β initialising them the usual way made 16,199 tags emit
sigmoid(0)=0.5and outrank every real prediction, dropping precision@20 from 0.762 to 0.037. - Asymmetric Loss (Ξ³β»=4, Ξ³βΊ=1, clip 0.05); instance-aware repeat-factor sampling (thresh 1e-3, cap 4, mean repeat 2.32Γ); discriminative LRs, encoder 1e-5 and head 1e-3, cosine to 5%; bf16; ~100.5k steps at batch 16Γ2.
- Split rule: everything except
post_id > 8,600,750 AND id % 1000 >= 950, which is provably disjoint from the evaluation set.
License
Apache-2.0, inherited from wd-eva02-large-tagger-v3. Tag data originates from
Danbooru.
Model tree for Redstonexs/danbooru-tagger-v1
Base model
SmilingWolf/wd-eva02-large-tagger-v3