# Kagami-24k(中文说明) > [English](README.md) · **中文** 一个基于 EVA02-L 的 Danbooru 标注模型,**通用标签词表 24,000 条**。在两项排序 指标上都超过 `wd-eva02-large-tagger-v3`,且是**单模型**——在 11,639 张所有对比 模型训练截止之后的图片上测得,并给出配对自助法(paired bootstrap)置信区间。 鏡——镜子只如实映出眼前之物。 ## 结果 测试集为 11,639 篇 `id > 8,600,750` 的投稿,晚于所有参与对比模型的训练截止时间 (WD 7.22M、Camie 约 8.36M、PixAI 8.60M)。评分基于**七个词表的交集**(7,779 条 标签,其中 3,101 条正样本数 ≥10)。之所以用交集而非各自词表:在自己的词表上算 宏平均 F1,等于奖励"词表更大"而不是"预测更准"。 | 模型 | micro-F1 | macro-F1 | 宏平均 AP | 细粒度 AP | 词表 | 覆盖率 | |---|---|---|---|---|---|---| | **Kagami-24k** | **0.6710** | **0.5138** | **0.5086** | **0.6170** | 24,000 | 0.914 | | danbooru-tagger-v1(我们的上一版) | 0.6569 | 0.4889 | 0.4672 | 0.5643 | 24,000 | 0.914 | | wd-eva02-large-tagger-v3 | 0.6369 | 0.4728 | 0.4668 | 0.5979 | 8,106 | 0.506 | | wd-vit-large-tagger-v3 | 0.6366 | 0.4574 | 0.4576 | 0.5960 | 8,106 | 0.506 | | wd-swinv2-tagger-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 | ### 这个提升是真的吗?配对自助法,200 次重采样 只看点估计不够,所以发布标准是**事先**定好的:**两项排序指标的差值都要 > 0, 且 95% 置信区间下界都要 > 0。** | 对比基线 | Δ 宏平均 AP | 95% CI | Δ 细粒度 AP | 95% CI | | |---|---|---|---|---|---| | wd-eva02-large-v3 | **+0.0418** | [+0.0393, +0.0448] | **+0.0190** | [+0.0060, +0.0288] | **通过** | | danbooru-tagger-v1 | +0.0414 | [+0.0392, +0.0437] | +0.0527 | [+0.0321, +0.0652] | 通过 | | wd-swinv2-v3 | +0.0537 | [+0.0507, +0.0568] | +0.0348 | [+0.0125, +0.0510] | 通过 | | wd-vit-large-v3 | +0.0510 | [+0.0477, +0.0541] | +0.0209 | **[−0.0001, +0.0349]** | **见下** | 重采样是**按图片配对**的:每一次重采样两个系统看到完全相同的图片,因此占方差 主要部分的"图片本身难易度"被抵消掉了。 **一个必须如实说明的例外**:与 `wd-vit-large-v3` 相比,细粒度 AP 的置信区间下界 擦到了零,因此**这一项优势在统计上并未成立**——尽管对同一模型的宏平均 AP 优势 非常明确。细粒度 AP 只由三个标签组(共 38 条标签)平均而来,区间自然偏宽。其余 所有对比都干净通过。 **表中的 F1 列对所有模型都偏乐观。** 评分脚本在这个测试集上扫阈值。这对所有候选 模型一视同仁,所以**比较**是公平的,但 F1 的绝对值不是干净的数字。AP 与阈值无关 ——这正是发布标准建立在 AP 上的原因。 ### 分组 AP——上一版输掉的那几类 | 模型 | flowers | hands | worn_small | scene | miko | framing | |---|---|---|---|---|---|---| | **Kagami-24k** | **0.6796** | **0.5369** | **0.6344** | 0.5505 | **0.6670** | **0.4645** | | wd-eva02-large-v3 | 0.6750 | 0.5131 | 0.6057 | **0.5546** | 0.6162 | 0.4301 | | danbooru-tagger-v1 | 0.6229 | 0.4785 | 0.5915 | 0.5207 | 0.6616 | 0.4298 | 对 wd-eva02 六组赢五组,只在 `scene` 上落后 0.0041。上一版是六组输四组,其中包括 `flowers` 和 `worn_small` 这两个小物体组。 ## 训练方法 上一版只用真实标签微调 WD 的编码器。它赢下了 F1 系列指标,却**在细粒度 AP 上输了 0.0336**——编码器发生漂移,恰好损害了它本该保住的小细节标签。 我们做了一个 2×2 消融实验,把两个编码器和两个分类头交叉组合。结果表明这次漂移 **无法归因于其中任何单一部件**:交叉组合的两格直接崩塌(宏平均 AP 0.1743 和 0.0613,而两个原配组合是 0.4230 和 0.4080)。用其中三格拟合一个可加模型去预测 第四格,得到 **−0.1873**——对一个取值在 [0,1] 的量而言这是不可能的数值。编码器和 分类头已经共同适应成了一种 WD 的任何部件都读不懂的表示。因此"冻结浅层"这个方案 从来就没有证据支持,被放弃了。 取而代之的思路是**锚定**而不是**固定**。先构造一个冻结的教师模型: teacher = 0.35 · 上一版 + 0.65 · mean(wd_eva02, wd_vit_large, wd_swinv2) 在与 WD 词表共享的 7,800 条通用标签上,对全部 677,355 张语料图片预先算好。0.35 这个权重是在留出的 dev 集上用折外交叉拟合(out-of-fold cross-fitting)选出来的, **从未使用测试集**。逐标签加权和按词频分组加权两种更复杂的方案都试过并被否决—— 单一全局权重与最优逐标签方案只差 0.0009 宏平均 AP,低于我们为"采纳额外复杂度" 设定的门槛。 训练时**所有层都可训练**,采用逐层学习率衰减 0.92(编码器顶层 2.5e-6,向下衰减 到底层 3.4e-7,分类头 7e-5)。损失是真实标签上的 ASL 加上教师项,教师项权重在 整个训练过程中从 1.0 衰减到 0.3——漂移发生在早期,所以锚定在最需要的时候最强; 后期由真实标签主导,而 8.6M 之后的新知识正来自那里。共 196,000 步,batch 12, EMA 0.9998。 教师模型在 dev 上是 0.4691 宏平均 AP / 0.6250 细粒度 AP。Kagami 达到 0.4612 / 0.6211——**追回了上一版与教师之间 87% 的宏平均 AP 差距和 94% 的细粒度 AP 差距**, 明显高于集成蒸馏通常能回收的 50–80%。 发布的权重是**原始参数,不是 EMA**。两者都在 dev 上评过分,差异是 +0.0001 宏平均 AP / −0.0002 细粒度 AP——属于噪声,所以选择更简单的那个。 ## 用法 ```python import numpy as np, onnxruntime as ort from PIL import Image import csv sess = ort.InferenceSession("model_prob.onnx", providers=["CPUExecutionProvider"]) tags = [r["name"] for r in csv.DictReader(open("selected_tags.csv"))] def prep(path, side=448): im = Image.open(path).convert("RGB") w, h = im.size s = side / max(w, h) im = im.resize((max(1, round(w*s)), max(1, round(h*s))), Image.BICUBIC) sq = Image.new("RGB", (side, side), (255, 255, 255)) sq.paste(im, ((side-im.size[0])//2, (side-im.size[1])//2)) x = np.asarray(sq, dtype=np.float32) / 255. x = (x - 0.5) / 0.5 return x.transpose(2, 0, 1)[::-1][None].copy() # NCHW, BGR p = sess.run(None, {"input": prep("image.jpg")})[0][0] for i in np.argsort(-p)[:30]: print(f"{tags[i]:30s} {p[i]:.3f}") ``` **输入是 NCHW BGR**,归一化 mean=std=0.5,白色方形填充——与 WD 自己的预处理一致, **不是 NHWC**。SmilingWolf 发布的 ONNX 是 NHWC 格式;如果你在改造现有的 WD 流水线, 请相应地转置。 推荐阈值:宏平均 F1 用 **0.37**,micro-F1 用 **0.54**。这两个值是在测试集上扫出来 的,属于参考建议,不是严格标定。 ## 局限 **词表的大部分并未得到验证。** 24,000 条标签中,**14,626 条(61%)的训练样本少于 1,000 条**,而 11,639 张图的评测集根本查不动它们: | 训练样本数 | 标签数 | 可测(评测集正样本 ≥5) | 平均 AP | |---|---|---|---| | 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 | 100–1,000 这一档的 13,970 条标签里只有 67 条在这里是可测的。长尾部分既没有被证明 好,也没有被证明差——它是**未经检验的**。"24,000 条标签"描述的是输出层的宽度, 不是 24,000 条已验证的标签。 **训练分辨率 448,而语料按长边 640 存储**,所以模型只见过二次重采样的图像。喂原始 分辨率会比它训练时见过的任何图像都更锐利。我们做过对照实验,这个存储环节的代价在 测量精度内为零(宏平均 AP +0.0001,细粒度 AP +0.0010,CI [−0.0021, +0.0045])。 **不包含角色标签和版权标签**——仅通用标签。 ## 许可与来源 Apache-2.0,衍生自 `wd-eva02-large-tagger-v3`(Apache-2.0)。用作蒸馏教师的三个 WD 模型均为 Apache-2.0。Camie 和 cl_tagger 仅作为基准对比出现,本模型未使用二者的任何 权重。 训练产物、日志与验证关卡: [`Redstonexs/danbooru-tagger-round3-artifacts`](https://huggingface.co/Redstonexs/danbooru-tagger-round3-artifacts)。