图标检索 · 开集判定演示

把一个野生图标丢进来,看它在公开图标库里的排名,以及系统敢不敢说"库里没有"。下面是效果演示、原理拆解、数据与结果,以及还没解决的问题。

粘贴截图 · 拖入图片 · 或点击选择

从设计稿里截一个图标即可,四周留白会被自动裁掉

库内图标:
公司视觉稿里的图标(库是 Lucide,跨来源):
外部图标(Bootstrap Icons,画法完全不同):

描述模式匹配的是图标库自带的名称与标签,不是语义向量。图片模式才是论文里的检索路径。

正在加载编码器(约 105 MB,只需一次)…

结果会出现在这里。

点左边任意一个示例图标开始。库内图标应当自匹配;跨来源的同义图标看分数;库外无关图标和照片应该被拒掉。

低于阈值即判为"库里没有"。这个滑块是演示开关,不是标定过的业务阈值——真实实验里它正是最难的部分。

01

一次查询到底发生了什么

五个步骤,全部在浏览器里完成。没有一个后端,也没有一次网络请求带着你的图片走出这台设备。

  1. 取图与合成

    粘贴、拖入或点选一个图标。透明背景会先合成到白底上——否则透明通道在张量里会变成一块黑。

  2. 归一化

    检测墨迹的包围盒。只有墨迹覆盖不到画面 60% 时(真正的设计稿截图)才裁边;已经是裁好的图标就保持原样。然后等比缩放、居中留白成方形,不拉伸。

    为什么不无条件裁边:留白本身是图标语义的一部分(搜索的柄、加号的边距)。实测无条件裁边 Top-1 从 0.494 掉到 0.447。

  3. 编码成向量

    两个塔并联。SigLIP ViT-B/16 吃 224×224、(x−0.5)/0.5,输出 768 维;微调 ResNet18 吃 96×96、原始 [0,1],输出 256 维。

    融合 v = normalize( [ √0.7 · vsiglip , √0.3 · vresnet ] )

    按 √w 缩放再归一化,点积就恰好等于 0.7·cossiglip + 0.3·cosresnet,不必在代码里算两遍。

  4. 算余弦、排序

    查询向量和库里每个图标向量做一次点积,降序取前 8 个。索引里的向量离线算好,存成 float16。

    相似度 sim(q, g) = q · g / (‖q‖ ‖g‖) ∈ [−1, 1]

    归一化之后就是纯点积。它只看方向不看长度,所以线宽、画布大小、导出尺寸都不会影响打分;在单位球面上按余弦排序与按欧氏距离排序完全等价。

  5. 开集判定

    拿 Top-1 的分数和阈值比:s₁ ≥ τ 就返回这个图标,否则返回"库里没有"。这一步才是论文的题目——不是"能不能找到最像的",而是"最像的那个够不够格被当成答案"。

这个页面跑的是单塔。 为了把模型压进浏览器,这里只用了 SigLIP(768 维,int8 量化后 96 MB)。论文主方法是上面那个 1024 维融合,在内部图标库上有完整实现;因为库是公司数据,那部分没有公开部署。因此这里的绝对分数不能和论文表格直接比。
02

数据

查询侧 · 真实野生图标

从 107 个代码仓库里扫出 669 个不重复图标,人工标注 668 条。四档判定:

  • A 精确 同一图形,仅细微差异——真实数据里一条都没有
  • B 可替换 语义相同,画法不同
  • C 缺失 库里没有对应图标
  • X 非图标 不是图标,或无法判定
B 可替换441
C 缺失175
X 非图标52
A 精确0

A 档为 0 是这条赛道最关键的事实:跨来源场景下"完全相同"几乎不存在,所以正例必须由 B 来承担。

库侧

库规模用途
corp-svg-icons796论文主库(内部,不公开)
Lucide1539本页索引,ISC 协议
Bootstrap Icons样本库外查询样例,MIT

预处理

SVG 用 cairosvg 渲染成 256px 再 LANCZOS 缩到 96×96 白底 PNG,编码时再放大到塔的输入尺寸。野生图标在采集阶段统一转黑、重建 viewBox,墨迹占比低于 0.4% 或高于 88% 的直接丢弃,最后按 SVG 文本哈希与像素哈希去重。

库里有 19 个图标渲染出来是纯白(无墨迹),19 个在 796 里占 2.4%。它们对应的标注正例为 0,但作为图库项仍会参与打分——已在待清理清单上。

03

结果

以下数字全部来自公司图标库与 668 条人工标注,5 折交叉验证、阈值只在训练折上定、折外汇报。

61.0%Top-1(融合)
88.7%recall@8
0.889拒识 AUROC
49.8%TNR@TPR95

检索已经够用,判定不够

正确图标有 88.7% 进了前 8,但首选正确的只有 61.0%。也就是说召回基本饱和,丢掉的是排序精度和"该不该接受"的判断。

把库内接受率锁在 95%——这是业务能容忍的误拒上限——库外图标只能拒掉 49.8%。一半的"库里没有"会被当成"有"硬答出去。

工作点曲线

库内接受率99%95%90%80%70%
库外拒识率24.7%49.8%67.0%83.7%92.1%

Pareto 前沿很平:想把库外拒干净,库内就开始大面积误拒。这就是"开集判定"的难度所在,也是 AUROC 0.889 看起来不错、实际却不好用的原因。

AUROC 会骗人

融合模型的 AUROC 是 0.889,看起来判得挺好;但同一组分数在真实工作点上只能拒掉一半库外图标。AUROC 衡量的是整个阈值范围的排序质量,而业务只关心 95% 接受率附近那一个点。所以本课题统一用 TNR@TPR95 和部分 AUC(TPR ≥ 0.90)汇报,不看总 AUROC。

合成基准高估了 31 个点

在合成测试集上,同一个模型的 Top-1 是 89.5%;换到真实标注上只有 58.0%。合成集的风格差异是"程序生成"的,与设计师手写图标的差异不同分布。这条差距本身是论文的发现之一:这类任务不能拿合成基准当主要证据。

04

还没解决的问题

三条判定路线已经用实验否掉了,下面这些是剩下的方向,每条都标了它要付出的代价。

主攻 · 前置检验未通过

未知类感知的表示学习

训练时留出一部分库类当"未知类",让未知样本落在已知原型之外。目标:TNR@TPR95 从 0.50 提到 0.70,Top-1 不掉。

两道检验都没过。第一,留出的类里 57% 在库里还有 0.95 以上的近邻——它们并不"未知",只是换了个名字。第二,在四个模型上,"留出类被拒掉的比例"与"真实库外被拒掉的比例"秩相关是 −0.31:按这个代理选模型,会稳定地选中真实拒识最差的那个。所以留出类只能用于训练,不能用于评估。

结构

两阶段:粗排 + 精排/判定

第一阶段把候选收敛到 Top-8,第二阶段专门做比对。

前提是第二阶段必须带来 s₁ 之外的信息。用"第一名与第二名的差值"做过,TNR@TPR95 直接归零——这个信号和 s₁ 高度相关。

外部效度

公开可复现基准

用 Lucide / Feather / Material Symbols 另建一套跨库基准,回答"换一套库还成不成立"。本页面已经是这个方向的一次预演。

跨库实测:单色线条图标上几乎不掉点,但公司数据训出来的分支一遇颜色就崩(彩色变体 Top-1 0.985 → 0.235),融合能兜回一部分(0.995 → 0.905)。

数据

把标注口径做实

补标约 100 条用于区分 A/B;清理库里 19 个全白图标;按重复组重新划分训练/验证/测试。

还有一类假错误:Top-1 命中了与标注项逐字节相同的别名图标。实测 9/441 属于这种情况,即真实 Top-1 应比报出的高约 2 个点。