资讯动态

零样本学习:语义对齐、广义评测与工程选型指南

发布时间:2026/10/25 11:22:02 来源:尧图企业网站定制
简介面向零样本学习入门、课程实验与论文复现需求整理的一份实践包围绕零样本人体行为识别等场景提供基础复现和基于PyTorch的原文复现思路适合具备一定机器学习基础、希望对照论文理解语义空间、属性表示与兼容性方法的学生和研究者使用。压缩包共11个文件约38.98MB包含6个Python脚本、2份PDF论文及说明、1个Excel属性表、1个文本说明和1个Jupyter Notebook分别用于数据处理、模型实现、属性矩阵查看与实验记录便于按模块阅读、运行和调试。该资源已有2851人学习下载。包内附复现论文、语义空间矩阵、数据集说明及程序代码因数据集体积较大需自行下载读者可据此梳理从数据读取、属性构造到零样本分类验证的完整流程并结合Notebook和说明文档快速定位关键实现与参数设置。1. 零样本学习到底在解决什么一个从没见过也能认出来的问题先做个小测试。你教一个小孩认猫和狗给他看了几十张照片他基本就不会认错了。现在你拿出一张他从未见过的羊驼照片问他这是什么他大概会愣一下然后说「像羊又像骆驼」。他没有被教过「羊驼」这个类别但他靠「毛茸茸」「长脖子」「四條腿」这些描述把一个新类别挂到了已有的认知上。零样本学习zero-shot learning要干的就是这件事让模型在训练阶段完全没见过某个类别的样本推理时却能把它认出来。这里的「零」指的是目标类别的标注样本数量为零而不是模型什么都不用学。它真正依赖的是另一份东西——每个类别的文字描述、属性清单或者词向量相当于给每个类别配一张「说明书」。模型学的是「图像长什么样」和「说明书上怎么写」之间的对应关系遇到新类别时靠读说明书来猜。这个概念值得搞懂是因为它戳中了所有做分类任务的人都会撞上的那堵墙你能标注的类别永远是有限的而现实世界的新类别是无限的。搞懂零样本学习你就知道模型的能力边界在哪里也就不会在业务里对它抱有不切实际的期待。2. 为什么会有零样本学习标注成本与类别膨胀的现实2.1 人工标注的天花板在哪里常规的图像分类是一条很朴素的流水线收集图片、人工打标签、训练模型、上线。这套流程在类别少、样本充足的时候非常有效但它的成本结构是线性的——每增加一个类别就要重新收集几百上千张图再雇人一张张标。一条产品线有二十个类别还好说有兩万个类别时标注预算会先于算法崩溃。更麻烦的是长尾。真实的业务数据里绝大部分类别只出现寥寥几次剩下的海量类别每个都只有个位数的样本。你不可能为每个稀有类别都配一份像样的训练集。还有一类情况是类别本身在动态增长今天上线时只有一百种商品三个月后运营加了两百种新品模型如果不认识新品就永远把它们判成某个老类别错得还很自信。这就是零样本学习存在的第一层理由它把「认识新类别」的成本从「收集并标注样本」转移到了「写一段描述」。写描述这件事人力成本低一个数量级而且可以批量做、可以外包、甚至可以由模型自动生成。虽然精度会打折扣但在很多场景里「大致认对」比「完全不认识」有价值得多。提示零样本不等于免标注。它省掉的是目标类别的样本标注换成了类别层面的语义标注。语义描述写得糙后面的精度会一路塌方。2.2 语义描述给每个类别留一份「说明书」要让模型认识一个没见过的类别你必须提供某种「关于这个类别的知识」。最常见的载体是属性attribute。比如鸟类识别里每个类别会被描述成一组可判断的二元属性或数值属性有没有红色头顶、翅膀有没有条纹、喙是长是短、体型大概多大。一只没见过的鸟只要它的属性组合能和已知类别区分开模型就有机会把它分对。另一类载体是自然语言描述。与其手动列属性不如直接写一句话「一种体型较小的鸟背部和翅膀呈橄榄绿色腹部偏黄有明显的白色眼圈。」大规模图文预训练模型走的就是这条路它把整段文本编码成一个向量作为类别的语义表示。属性描述和自然语言描述的区别打个比方就是填表格和写作文。填表格精确、规整、便于计算但需要领域专家设计属性体系换个领域就得重来写作文灵活、覆盖广、随手就能写但信息密度低、噪声大模型得自己有本事从文字里抽出关键点。早期的零样本研究几乎都在填表格近几年的趋势明显偏向写作文。这里有个容易被忽略的细节语义描述必须是「可判别」的。如果所有类别的描述都写成「有毛、有四条腿、会动」那这份说明书对分类毫无帮助。好的属性体系要能把类别之间的差异撑开让不同类别在语义空间里离得足够远。2.3 从属性到词向量桥梁是怎么搭的把语义变成数字是整件事的技术前提。属性可以直接拼成一个定长向量每一位对应一个属性取值零或一或者一个连续分数。文本则用词向量或句向量模型编码成几百维的稠密向量。不管哪种方式最后每个类别都会得到一个定长的语义向量所有类别排在一起就构成了一个语义空间。这个空间有意思的地方在于它自带结构。语义相近的类别在空间里距离近语义差别大的类别距离远。这种结构不是人为规定的而是从描述本身长出来的。零样本学习之所以能泛化靠的就是这份结构模型在见过的类别上学到的「视觉到语义」的映射关系可以顺着这份结构延伸到没见过的区域。但语义空间也有它的问题。它是人写的带有主观性和不完整性它是粗糙的一个类别压成几百个数字丢掉了大量细节它和视觉特征之间还存在天然的鸿沟——文字里的「条纹」和图像里的条纹纹理是两种完全不同的信号。整条技术路线的难点几乎都集中在怎么把这两个空间对齐上。3. 零样本学习的核心机制把图像和文字塞进同一个空间3.1 视觉空间与语义空间的对齐最经典的思路叫嵌入映射。模型里有两套东西一套是图像编码器把一张图变成视觉向量另一套是类别语义向量。训练阶段只用见过的类别目标是学一个映射函数把视觉向量投到语义空间里或者反过来把语义向量投到视觉空间里让「图像投过去的位置」和「它所属类别的语义向量」尽量重合。推理阶段是这套思路最精妙的地方。来一张没见过的类别的图编码器先把它变成视觉向量映射函数把它投到语义空间得到一个落点。然后拿这个落点和所有候选类别的语义向量算相似度最近的那个就是预测结果。注意候选集里可以包含训练时压根没出现过的类别模型不需要为它们准备任何分类头。输出空间是动态的这是零样本和普通分类最本质的差别。不同的做法在「往哪个方向投」上有分歧。投到语义空间的好处是语义空间维度低、结构稳缺点是视觉信息被压缩得太狠投到视觉空间则保留了更多图像细节但视觉空间维度高、噪声大容易过拟合到见过的类别上。后来的研究又提出了第三种方案在两个空间之间搭一个中间空间让两边都往里投。这类方法在论文里经常被表述成一个双线性的相容函数形式不复杂但效果好。3.2 生成式路线先造样本再分类上面那条路有个绕不开的毛病模型从没真正见过目标类别的样本只能靠相似度去猜一旦语义描述和真实视觉分布对不上预测就会翻车。生成式路线换了个思路——既然缺样本那就造样本。具体做法是训练一个条件生成模型输入是类别的语义向量输出是对应的视觉特征。训练时用见过的类别学「给定语义向量怎么生成特征」训好之后把没见过的类别的语义向量喂进去就能凭空生成一批该类的视觉特征。有了这批合成特征零样本问题就被转化成了普通的监督分类问题后面接一个常规分类器就行。这条路子把零样本的难点从「跨空间对齐」挪到了「生成质量」上。生成的特征够不够真、多样性够不够、有没有塌缩到少数几个模式直接决定最终精度。好处是它天然支持广义零样本场景因为合成样本可以和真实样本混在一起训练缓解后面要说的偏置问题。注意生成式方法看着很美但它对生成器的训练稳定性极其敏感。同一份代码换个随机种子结果可能差好几个点这部分调参有不少玄学成分。3.3 广义零样本与那个叫 H 的指标早期的零样本评测有个理想化假设测试时只在没见过的类别里选。这叫传统零样本设定干净但和现实脱节——真实系统面对的往往是见过的和没见过的类别混在一起模型必须先判断「这是不是新类别」再判断「是哪一类」。这个更难的设定叫广义零样本学习。广义设定一上来就暴露了一个尖锐的问题偏置。模型在训练时只见过老类别天然会偏向老类别新类别的样本很容易被判成某个语义相近的老类别。极端情况下模型把所有测试样本都判成老类别老类别准确率能到九成以上新类别准确率接近零但整体准确率看着还挺好看。这就是为什么评测不能只看总体精度。业界通用的做法是分别报告见过类别的准确率S和没见类别的准确率U再算它们的调和平均 H 2SU/(SU)。调和平均对短板极其敏感一边接近零H 就接近零。用 H 做指标等于逼着模型在两边都不能摆烂。血泪经验是看论文里的数字时先看它报的是哪种设定、哪个指标只报总体精度的结果基本没有参考价值。下表把几种设定摆在一起方便对照设定训练时见过的类别测试时候选集常见指标传统零样本只有老类别只有新类别新类准确率 U广义零样本只有老类别老类别 新类别调和平均 H少样本老类别 新类少量样本老类别 新类别总体准确率3.4 一个最小可跑的代码骨架概念讲完落到代码上其实骨架很短。下面这段用最小二乘做视觉到语义的线性映射能跑通传统零样本的基本流程重点是看清「候选集是变量」这件事。import numpy as np # semantic: 所有类别的语义向量形状 [C, D_s]行序即类别编号 # image_feats: 图像特征形状 [N, D_v] # labels: 训练样本的类别编号只包含老类别 def train_mapping(image_feats, labels, semantic): # 只取老类别的语义向量和图像特征做最小二乘拟合 seen_sem semantic[labels] # [N, D_s] W, *_ np.linalg.lstsq(image_feats, seen_sem, rcondNone) return W # [D_v, D_s] def predict(image_feats, W, semantic, candidate_ids): # 先把特征和语义向量都做 L2 归一化避免模长干扰相似度 feats image_feats / np.linalg.norm(image_feats, axis1, keepdimsTrue) cand semantic[candidate_ids] cand cand / np.linalg.norm(cand, axis1, keepdimsTrue) projected feats W # 投到语义空间 [N, D_s] projected projected / np.linalg.norm(projected, axis1, keepdimsTrue) sim projected cand.T # 余弦相似度 [N, len(candidate_ids)] return candidate_ids[sim.argmax(axis1)] # 取最相似的候选类别train_mapping里的np.linalg.lstsq求的是让映射误差平方和最小的解返回的W就是那座桥。predict的第四个参数candidate_ids是关键设计传统零样本传新类别的编号列表广义零样本传全部类别编号同一个函数就能覆盖两种设定。归一化那两行别省不归一化的话语义向量模长大的类别会系统性地占便宜结果会明显偏。这套骨架的精度当然比不过论文里的方法但它是理解所有复杂模型的底座。看任何一篇零样本论文的代码你都能在里面找到这两个函数的变体一个负责对齐一个负责在候选集里检索。4. 常见误区与易混概念零样本、少样本、开集识别差在哪4.1 易混点一零样本学习并不等于零训练最容易产生的误解是以为零样本就是「模型不用训练直接推理」。恰恰相反零样本模型的训练过程一点都不少只是训练数据里没有目标类别的样本而已。它照样要在大量老类别样本上迭代照样要调超参、照样会过拟合。混淆的原因在于「零」这个字太有迷惑性它修饰的是样本数量不是训练过程。区分方法很简单问一句「模型在训练阶段见过什么」。如果见过老类别的大量标注样本靠语义描述迁移到新类别那是零样本如果一个样本都没见过、纯靠预训练模型的通用能力回答那属于另一回事通常叫零训练或者免训练推理。4.2 易混点二零样本和少样本不是程度差别很多人把这两个当成同一个光谱上的两端——零样本是一个样本都没有少样本是有一点点。这个理解只对了一半。数量上确实如此但方法上差别很大。零样本的核心是「靠语义描述建立跨类别的桥」没有目标类别的样本你只能依赖外部知识少样本的核心是「靠少量样本微调」既然有样本了就可以直接在这几个样本上做梯度更新语义描述反而变成辅助。区分方法是看方法文档里有没有「微调」这个动作需要在目标类别样本上更新参数的基本都是少样本路线只用语义向量做检索或生成的才是零样本路线。这两类方法在工程上的部署成本、迭代速度完全不同混为一谈会做出错误的选型判断。4.3 易混点三零样本和开集识别目标相反开集识别要解决的是「这条样本是不是我认识的类别」重点是拒识遇到不认识的类别应该报「未知」而不是硬塞进某个已知类别。零样本学习要解决的恰恰相反——遇到不认识的类别它必须给出一个具体答案还得答对。混淆的原因是两者都在处理「训练时没见过的东西」。区分看输出开集识别的输出是「已知类别之一」或者「未知」零样本的输出一定是候选类别里的某一个哪怕候选类别训练时压根没出现。工程上这两个能力经常要组合使用先用开集识别判断该不该答再用零样本决定答什么。但如果只做其中一个别指望它顺带把另一个也解决。4.4 易混点四语义描述不等于提示词调优现在流行用自然语言描述类别于是一个新误解出现了写一段类别描述就叫零样本了。其实关键不在于「写了文字」而在于这段文字有没有被用来建立跨类别的映射关系。区分方法看用途如果文字只是拼进输入模板里模型本身已经在海量图文对上训练过这是提示词层面的技巧如果文字被编码成语义向量作为类别的代理参与到分类决策中新旧类别共用同一套对齐逻辑那才是零样本的机制。前者改几个词就能影响结果后者需要重新训练映射工程含义差很远。4.5 易混点五精度数字不能跨设定比较第四个高频误区是拿两篇论文的数字直接对比。传统零样本只在新类别里选广义零样本在新旧混合里选两者的难度不是一个量级。前者的准确率天然更高因为候选集小、干扰少。区分方法是先看评测设定再看指标。看到一篇报「新类准确率九成」的先确认它是不是传统设定、候选集只有几十类看到报 H 值的才知道它是在混合设定下比较均衡的结果。跨设定比数字等于拿百米成绩去比跨栏结论一定是错的。5. 这份论文加代码的资源能帮你省掉哪些摸索5.1 论文部分该盯住哪几段一份零样本的论文加代码资料价值不在于让你从零推导公式而在于帮你跳过「读了十篇才发现设定不一样」的阶段。拿到论文时建议先翻三处而不是从头读到尾。第一处是实验设置那一节看清楚它用的是传统设定还是广义设定候选集是怎么划的类别划分遵循什么协议。这一节决定了后面所有数字的含义。第二处是消融实验看作者拆掉了哪个模块、数字掉了多少这比主结果更能说明方法的关键在哪里。第三处是失败案例分析好的论文会老实交代模型在哪些类别上错得离谱这部分信息在代码复现时极其有用因为你能预判自己会撞上什么。读的时候留意一个细节很多方法的性能提升来自对语义描述的预处理而不是模型结构本身。资料里如果有这部分说明别跳过它往往是最容易复现收益的地方。5.2 代码部分的阅读顺序代码不要按文件目录顺序读按数据流读。先找数据加载部分确认语义向量是怎么构造的、类别划分文件长什么样、老类别和新类别的编号是怎么分开的。这三件事搞清楚了后面所有逻辑才有落脚点。接着找训练入口看它优化的是什么损失——是对齐损失、生成损失还是两者都有。然后找推理入口重点看候选集是怎么传进去的。很多复现代码把候选集的构造藏在评测脚本里不仔细看会以为模型直接输出全部类别实际不是。最后看评测脚本。零样本的评测比普通分类复杂要分别算新类和旧类的精度再做调和平均。如果评测脚本写错了你会得到一组看着正常、实际没意义的结果。建议第一遍跑通后手动构造几个极端输入验证一下比如全部判成老类别看看指标是不是如预期地掉到接近零。5.3 复现时最容易翻车的地方第一类是随机种子。生成式方法对种子特别敏感同一份代码两次运行差三五个点是常事。想判断一个方法稳不稳至少跑三个种子取平均单次结果没有说服力。第二类是语义向量的归一化方式。有的代码在训练前归一化有的在计算相似度时归一化有的两处都做。这些细节在论文里通常不写但会明显影响结果。复现时如果数字对不上先查这里。第三类是见过的类别和新类别的划分协议。同一份数据换一种划分方式结果就不可比。资料里如果附带了划分文件照着用不要自己随手切分否则你跑出来的数字和论文里的一比会误以为自己实现错了。第四类是评测时的候选集范围。这个坑最隐蔽因为代码可能默认把全部类别都放进候选集而你以为是传统设定。跑之前打印一下候选集长度一行代码的事能省掉半天的困惑。6. 什么时候该用零样本学习什么时候别硬上先说该用的时候。第一类是类别会持续新增、而你没法为每次新增都重训模型的场景比如商品类目、内容标签、工单分类语义描述可以直接写成一段业务说明新类目上线当天就能被识别虽然精度不如专门训练的模型但比「全部判成老类目」强太多。第二类是需要冷启动的场景新产品、新业务线一开始没有历史数据用语义描述先撑起一个能用的基线等样本攒够了再切到常规监督训练。第三类是做候选召回零样本的排序能力一般但用来把几万个类别筛到几十个候选性价比很高。再说别硬上的时候。如果你的类别是固定的、样本也充足那就老老实实做普通分类零样本的精度一定不如它多绕这一圈纯属自找麻烦。如果类别之间的差异极其细微、必须靠专业视觉特征才能分辨比如同一种零件的外观瑕疵分级语义描述根本写不清那些差别零样本的表现会惨不忍睹。还有一类是对错误代价极其敏感的场景零样本的错法往往是「自信地错到语义相近的类别上」这种错比直接报未知更难排查没有兜底机制的话不要上。下面这张清单可以直接拿来过一遍判断项倾向用零样本倾向别用类别是否持续新增高频新增来不及重训固定不变每类样本量长尾稀疏多数类别样本极少每类都有充足标注语义描述能否写清差异可用属性或文字概括差异细微难以言说错误代价可容忍有兜底一次错判代价很高当前基线完全没有新类识别能力已有可用的监督模型我自己的习惯是在动手之前先做一个小实验从已有数据里挑出三个类别把它们从训练集里彻底剔除只用文字描述让模型去认。如果这三类的召回率连随机猜都赢不了多少那说明这个领域的语义描述和视觉特征之间缺乏可对齐的结构后面再怎么调参也是白费力气。这个十分钟的小验证帮我省下过好几次白干一周的后悔药。踩过的坑里最深的一个是早期只看总体精度就上了一个广义零样本的模型线下指标很漂亮上线后新类别的召回几乎为零。后来每次做类似的事我都会强制把新类和旧类的精度分开打印出来再算一遍调和平均数字难看就难看至少知道自己站在哪里。希望这些能帮到你也祝你在自己的数据上少走几步弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑