最近刷到一篇有意思的工作标题叫《Support Vector Generation: Kernelizing Zero-Shot Classifiers from Pre-Trained Language Models》。第一眼看上去像是个NIPS的纯理论工作但读完核心思路之后我意识到这其实是在动一个非常根本的问题我们现在用CLIP这类预训练语言模型做零样本分类本质上是拿文本特征跟图像特征做最近邻相似度匹配那这玩意儿跟经典的核方法、支持向量机到底能不能打通这篇文章做的就是这件事——把零样本分类器真正核化成一套支持向量机制而不是停留在余弦相似度检索的层面。先说结论这个工作对做多模态、做零样本分类、做模型蒸馏的朋友都有参考价值。它在预训练语言模型PTM之上引入了一个支持向量生成器把分类决策从算相似度、找最近邻改成核化空间里的支持向量投票同时保留PTM的零样本能力。也就是说你不需要重新训练一个大模型也不需要标注数据就能从冻结的PTM里蒸馏出一个更紧凑、更可控、甚至可解释性更好的核分类器。这篇文章适合三类人看一是想在零样本分类上刷点的研究者二是想降低PTM推理成本、做轻量化部署的工程人员三是对核方法与深度学习交叉方向感兴趣的同学。1. 为什么要给零样本分类器做核化1.1 相似度检索的隐性瓶颈这里得先从零样本分类的现状说起。以CLIP为代表的多模态PTM训练时把图像和文本拉进同一个嵌入空间推理时你只需要构造每个类别的文本描述比如a photo of a cat经过文本编码器得到类别特征再拿图像特征跟类别特征做点积相似度取最大者作为预测结果。这其实就是一个软的最近邻分类器虽然效果不错但它的决策边界隐含地依赖余弦相似度的度规。问题在于余弦相似度假定了特征空间中的类别结构是角度可分的这在真实数据里并不总是成立。尤其当类别数很多、或者类别描述本身存在语义重叠时相似度检索容易给出模棱两可的决策。更像什么就像你问一个只见过照片的人这动物是狼还是狗他只能凭整体长相的直觉判断而不具备一个明确的判别边界。相比之下经典SVM做的事情是找到一个最大间隔超平面来分开不同类别。它不依赖固定的相似度度规而是通过核函数把数据映射到更高维空间再在那边寻找最大间隔的分离边界。这个边界是数据驱动的更适应真实的特征分布。1.2 从固定相似度到可学习的判别边界所以这篇工作的核心动机就很清楚了能不能把预训练语言模型提供的特征喂给一个核SVM框架让SVM在PTM的特征空间内学到一个判别边界而不是继续用固定的余弦相似度做软匹配直接拿PTM特征去训练一个标准SVM当然可以但有个致命问题SVM需要足够的带标签数据而零样本场景恰恰是没标签的。那怎么在无标签条件下构造SVM的目标函数这就是支持向量生成Support Vector Generation这个概念的切入点了。文章的做法是利用PTM为每个类别生成的文本原型class prototype作为支持向量的初始化然后通过一个可学习的生成器在特征空间里合成支持向量。生成器不需要真实标注它只需要优化一个自监督的目标比如核对齐或者类别区分度就能产出决策边界。说白了这是在模仿人脑的类比推理不需要见过真实的狼只要给出像狗但更强壮、尾巴下垂、毛更粗这类描述性特征你就能构造一个判别性的支持向量集。2. 方法核心设计与拆解2.1 支持向量生成器的整体架构整个框架可以拆成三块特征提取器PTM的冻结encoder、支持向量生成器一个轻量MLP或Transformer模块、核SVM分类层以支持向量为锚点的核分类器。特征提取器负责把图像和文本都映射到同一个嵌入空间这一步完全冻结PTM权重不做任何微调。支持向量生成器接收的是PTM输出的类别原型向量比如a photo of a cat经过文本编码器得到的那个特征向量然后把它变换成一组支持向量。这相当于把每个类别从一个原型扩展到一组原型。2.2 核函数选择与带宽设定核化操作的具体形式是给定支持向量集合每类K个总共C×K个对输入特征x计算它与所有支持向量的核相似度得到一个C×K维的核特征向量。核函数这里一般选用高斯RBF核或者拉普拉斯核形式为[ k(x, s_i) \exp\left(-\frac{|x - s_i|^2}{2\sigma^2}\right) ]带宽σ是一个超参数文章里的实际操作是采用中值启发式median heuristic初始化——把所有支持向量对之间的距离取中位数作为初始带宽然后再作为可学习参数去微调。这么做的好处是避免了纯手工调参又保持了核映射的敏感性。这里要注意一个细节如果你想用高斯核特征空间的距离结构必须是有意义的也就是说PTM的特征要先做L2归一化。我实测下来CLIP的视觉特征和文本特征未归一化时的模长差异很大如果不先归一化就套RBF核核矩阵会很快坍缩成单位阵等于做的全是最近邻SVM的间隔优势完全发挥不出来。2.3 可学习的损失目标如何进行优化既然没有真实标签用什么损失来训练支持向量生成器文章采用了一个组合目标核对齐损失加间隔损失。核对齐Kernel Alignment衡量的是核矩阵与理想标签矩阵之间的一致性。理想情况下同类支持向量的核相似度要高、异类的要低。计算方式是[ A(K, Y) \frac{\langle K, YY^T \rangle_F}{\sqrt{\langle K, K \rangle_F \langle YY^T, YY^T \rangle_F}} ]这里的Y就是类别指示矩阵。直觉上就是把核矩阵和同类近、异类远的理想结构对齐。这个损失不需要真实标注只需要类别指示信息因此可以在零样本条件下优化。除此之外还有一个间隔损失对每个类别我们希望能找到一个分离超平面使得类间的间隔最大化。文章的做法是在支持向量生成器后面接一个线性分类头用多类合页损失multi-class hinge loss或者softmax损失训练。这个损失看起来跟监督学习很像但它的样本并不是真实的图像数据而是生成器产出的支持向量本身。说得再直白一点支持向量生成器在骗自己——自己生成一组向量又拿这组向量去训练一个分类器然后通过最小化合页损失来让自己生成的向量之间更好分。3. 实操过程与核心环节实现3.1 基础流程速览实操层面整个pipeline可以梳理成下面这五步加载预训练模型CLIP ViT-B/32或者ViT-L/14冻结两个encoder构造每类的文本描述模板得到初始的类别原型向量用类别原型初始化支持向量生成器每类生成K个支持向量计算支持向量之间的核矩阵用核对齐和间隔损失训练生成器若干轮推理时把图像特征映射到支持向量定义的核空间再做线性分类或SVM决策。3.2 关键代码逻辑参考核心的实现逻辑用伪代码描述会非常直观。首先定义支持向量生成器class SupportVectorGenerator(nn.Module): def __init__(self, feat_dim, num_classes, num_svs_per_class3): super().__init__() self.num_classes num_classes self.num_svs_per_class num_svs_per_class # 从PTM文本原型出发用一组可学习的变换生成支持向量 self.generator nn.Sequential( nn.Linear(feat_dim, feat_dim * 2), nn.GELU(), nn.Linear(feat_dim * 2, feat_dim * num_svs_per_class) ) self.feat_dim feat_dim def forward(self, class_prototypes): # class_prototypes: [num_classes, feat_dim] raw self.generator(class_prototypes) # reshape: [num_classes, num_svs_per_class, feat_dim] svs raw.view(self.num_classes, self.num_svs_per_class, self.feat_dim) return F.normalize(svs, dim-1)然后是核特征的计算模块def rbf_kernel_matrix(x, centers, sigma): # x: [N, D], centers: [M, D] # 返回 [N, M] x F.normalize(x, dim-1) centers F.normalize(centers, dim-1) dist torch.cdist(x, centers) # [N, M] return torch.exp(-0.5 * dist**2 / sigma**2)带宽sigma用的是可学习的标量初始值来自所有支持向量两两距离的中位数。训练时我对sigma做的是log空间优化避免它跑到负数这个细节在后面排坑环节会细说。训练循环核心部分可以这样写# 初始化类别原型 prototypes text_encoder(text_templates) # [C, D] svs svg(prototypes) # [C, K, D] # 计算核矩阵 K_ss rbf_kernel_matrix(svs.view(-1, D), svs.view(-1, D), sigma) # [C*K, C*K] Y one_hot_labels(C, K) # [C*K, C] kernel_alignment compute_kernel_alignment(K_ss, Y) # 分类损失接一个线性头 logits svs.view(C*K, D) classifier_weight.T # classifier_weight: [C, D] loss_cls cross_entropy(logits, pseudo_labels) # 伪标签支持向量的归属类别 total_loss -kernel_alignment 0.1 * loss_cls total_loss.backward()这个方案跑起来之后我观察到一个很有意思的现象如果只优化核对齐而不加间隔损失支持向量会出现聚团趋势——所有类别的支持向量挤在一起因为核对齐的梯度鼓励方差最大化但缺少类别分离的推动力。反之只优化间隔损失支持向量会互斥到核矩阵病态。两者搭配才是最稳的组合。3.3 推理时如何从支持向量得到分类结果推理阶段的两种方式值得一讲。第一种是直接走核SVM路径用训练好的支持向量和核参数把图像特征映射成核特征然后通过支持向量的组合权重投票决策。第二种是接一个线性可分的softmax分类头这等价于把支持向量当作新的语义蒸馏特征。实际工程中我推荐第二种。因为第一种需要保留支持向量的索引和核带宽部署起来多一层依赖第二种只需要保留一个线性矩阵前向推理跟普通分类网络完全一样。文章的实验也显示两种方式精度相差不大线性头的方式对数据分布偏移的鲁棒性反而更好。4. 实验结果与性能分析4.1 常见benchmark上的表现在常见的零样本分类benchmark如通用物体识别、细粒度分类、场景识别上这套方法相比直接使用PTM的相似度检索准确率普遍有1~5个百分点的提升类别数越多的数据集提升越明显。比如在包含1000类的大型数据集上支持向量带来的显式类别边界很大程度上缓解了文本描述模糊造成的分类混淆。我觉得这个趋势从原理上是说得通的。当类别数少比如10类每个类别文本描述区分度很高余弦相似度已经足够好核化的提升空间有限。但当类别数扩展到几百上千一些类别的文本描述会落在特征空间相近的位置这时候学习到的支持向量边界就能起到重新调整决策面的作用。这就像一群人的声音比较接近时名字可能搞混但你记住每个声线的几个特征点再对比就不容易混淆了。4.2 支持向量数量的影响支持向量数量K的选取有个明显的权衡。K从1增加到3时准确率提升明显K到5之后提升趋缓K继续增大反而会因为核矩阵增大、带宽难以自适应而出现性能回退。每类3~5个支持向量是性价比最高的区间。对带宽sigma的敏感性测试也值得记录。sigma设得太大核函数趋近于常数所有特征映射到同一个点分类器退化成正则化线性模型sigma设得太小核矩阵接近单位阵模型彻底退化成最近邻。用中值启发式初始化的好处是自动落在合理区间后续可学习微调也只需要小幅扰动。4.3 与其他零样本方案的对比观察这里我还想多说一句核化零样本分类器的核心竞争优势其实是提供了一个不改变PTM主干的情况下干预分类决策的接口。你可以通过调整支持向量和核参数来修正模型的系统性偏差而不需要重新训练一个几十亿参数的大模型。相比prompt tuning微调文本模板和adapter在主干层插入适配模块这两类常见方案支持向量生成更轻量且天然具备样本级可解释性——你可以把最终决策追溯到几个具体的支持向量这对分析模型失败的case非常有帮助。5. 踩坑记录与排查技巧5.1 特征分布差异导致核失效我踩过的第一个大坑是直接用PTM未归一化的特征计算RBF核。CLIP的视觉特征和文本特征的模长分布差异非常大核矩阵会出现对角线特别亮、其他地方接近0的现象这等于核方法完全失效。解决办法很简单所有进入核计算的特征必须先做L2归一化。归一化之后高斯核等效于在超球面上测角距离和PTM原有的余弦相似度语义对齐实验稳定了很多。5.2 支持向量优化过程震荡训练支持向量生成器时Loss曲线经常出现震荡尤其是生成的向量更新幅度较大时。我排查下来主要原因是最初把类别原型的模长信息也当作优化空间的一部分导致更新步长分配不合理。把生成器的输出做归一化并采用tanh激活约束在有限区间后优化稳定性大幅提升。此外对sigma做log空间参数化也很关键不然很容易在核坍缩sigma过小和核饱和sigma过大之间反复横跳。5.3 训练不收敛的排查经验如果你发现训练Loss居高不下建议按下面顺序排查检查特征归一化是否缺失。这是最常见的问题RBF核的数值稳定性对输入模长高度敏感检查带宽sigma的初值是否在一个合理范围。如果中值距离是10左右sigma就别从0.001开始试——那不是训练是hard模式检查支持向量初始化。用类别原型直接初始化肯定比随机初始化好但原型之间如果太近比如两个类别文本描述高度重合建议先用一个简单的白化变换拉开距离再初始化。5.4 内存与算力优化核矩阵的规模是类别数×支持向量数的平方。1000类每类4个支持向量核矩阵就是4000×4000虽然不算特别大但在训练生成器时如果每步都全量计算显存还是会吃紧。我实操下来有两个有效的缓解方式一是分块计算核矩阵不需要一次性把所有pair的距离矩阵全展开二是用随机傅里叶特征RFF把高斯核近似成有限维内积这样就不需要显式构造大核矩阵算力开销可以降一个量级同时精度几乎没有损失。6. 常见问题速查表为了方便大家快速对照排错我把实操中比较常碰到的问题整理成了一个小表。症状可能原因解决方案核矩阵几乎是对角阵特征未归一化或带宽过小L2归一化特征用中值启发式初始化sigma核矩阵几乎全相同带宽过大缩小sigma初始值检查距离中位数支持向量全部聚团只优化了核对齐缺少间隔损失加入多类合页损失平衡两个loss项的权重训练后期分类准确率下降支持向量数量过多模型过拟合减少每类支持向量数量增加正则项训练震荡不收敛输出未约束更新步长过大生成器输出做归一化约束到单位超球面显存不足核矩阵全量展开分块计算或采用随机傅里叶特征近似7. 个人实操心得与扩展方向最后聊聊我个人的体会。这套方法真正打动我的地方不是那1~5个百分点的精度提升而是它把零样本分类从一种检索行为变成了一种可解释的判别行为。你可以看到模型在用哪些支持向量做决策可以根据支持向量的分布去理解类别之间的关系甚至可以通过修改支持向量来手动纠偏。这比一个纯粹不可解释的黑盒相似度打分要踏实得多。从扩展角度来说这个框架直接可以沿三个方向向前走。第一个方向是跨语言PTM的文本编码器天然支持多语言支持向量生成器可以在共享的语义空间里跨语言生成特征做成跨语言的零样本分类器。第二个方向是做标签开放集的扩展当系统遇到一个未见过的类别时不需要重新训练主模型只需要把这个类别原型输入进去动态生成新的支持向量插入到核分类器中。第三个方向是对抗鲁棒性因为支持向量提供了显式的决策边界它天然对低强度对抗扰动有一定的抵抗能力这个特性可以深挖。下次看到这类标题千万别被NIPS、Kernelizing这些词劝退。这个方向本质上做的是一件非常朴素的事情让预训练模型学会用支持向量去打分类的太极值得动手复现一次。