少样本分类这几年在文本领域越来越常见,尤其是标注成本居高不下的场景,比如客服工单归类、法律文书辅助判类、舆情评论情感分级。很多团队手里只有几十条甚至十几条种子标注,却想让模型覆盖上百个细分类别,传统微调一上来就过拟合,换大模型又面临成本和响应延迟的双重压力。我在这类项目里反复试过几种路线,最后沉淀下来三个可靠方向:原型网络、度量学习,以及基于预训练语言模型的提示调优。这篇文章就把这三条路线的原理、选型和实操经验尽量讲透。
1. 少样本文本分类的问题本质与路线全景
先明确一下我们面对的任务形态。少样本文本分类通常用 N-way K-shot 来描述:N 代表类别数目,K 代表每个类别下只有 K 条标注样本。例如 5-way 5-shot,就是 5 个类别、每个类别只有 5 条样本,总共 25 条数据训练一个分类器。这个设定下,传统微调几乎必崩,模型会把词汇共现、标点习惯甚至样本顺序当作分类信号,在训练集上表现出非常高的准确率,换一批数据就原形毕露。
少样本之所以难,本质是经验风险最小化失效了。监督学习的核心是要求模型在有限样本上拟合出能够泛化的决策边界,当样本量远小于参数数量时,拟合出来的边界严重依赖训练集的偶然噪声,而不是类别之间的真正语义差异。这就像让一个人只看三张照片就辨认一个物种,他可能抓住的是背景色、拍摄角度这种与物种本身无关的特征。
针对这个问题,主流方案分三条路线。
第一条是原型网络,思路是把分类问题转换成“到类中心的距离”问题。每个类用支持集样本向量的均值表示,查询样本根据嵌入空间中的距离归属到最近的原型。它简单直接,效果稳定,尤其适合类别语义相对紧凑的场景。
第二条是度量学习,比原型网络更进一步。原型网络假设同一类的样本在嵌入空间中呈聚类分布,而度量学习通过训练一个专门的距离函数,让同类样本拉近、异类样本推远,不再依赖人工选择的固定距离。匹配网络、关系网络都是这条线的代表作。
第三条是提示调优,思路完全不一样。它不直接训练分类器,而是把分类任务包装成预训练语言模型熟悉的“完形填空”形式。比如情感分类可以改写成“这部电影太棒了,整体感受是 [MASK]”,让语言模型用原有知识去填充答案词,再映射到分类标签。
三条路线并不是互斥的。我在实际项目中经常把提示调优和原型网络结合起来用,也有团队把度量学习中的难样本挖掘机制搬到提示调优的训练阶段。理解各自的适用边界比盲目追求其中某一种更重要。
2. 原型网络:用类中心距离做分类的经典方案
2.1 原型网络的核心机制与损失函数
原型网络的思想可以用一句话概括:在嵌入空间里,每个类别由一个“原型向量”代表,这个原型就是该类所有支持集样本的均值向量,查询样本跟哪个原型最近,就把它分到哪一类。
假设第 c 类的支持集有 K 个样本,记作 {(x₁, y₁), ..., (x_K, y_K)},通过编码器 f_φ 得到 K 个向量,原型向量 p_c 就是对这 K 个向量求平均:
p_c = (1/K) · Σ f_φ(x_i)
查询样本 x 的嵌入向量是 f_φ(x),它到第 c 类原型的距离采用欧氏距离:
d(f_φ(x), p_c) = ||f_φ(x) - p_c||²
分类时,查询样本属于哪一类取决于哪个原型距离最近。在训练阶段,不直接用“距离最近”这种硬判断,而是用 Softmax 把距离转换成类别概率,再用交叉熵损失反向更新编码器:
P(y = c | x) = exp(-d(f_φ(x), p_c)) / Σ_c' exp(-d(f_φ(x), p_c'))
这个设计有一个隐蔽但关键的好处:欧氏距离的平方加负号,恰好构成一个高斯分布的密度函数形式。换句话说,原型网络假设每个类别的样本在嵌入空间中围绕原型呈球形高斯分布。这个假设在文本场景里大多数情况下是合理的,因为语义相似的文本经过预训练编码器后,确实倾向于形成紧凑的簇。
训练方式也很特殊,采用 episode 方式。每个批次随机抽取若干个类别,每类抽取若干支持样本和若干查询样本,模拟测试时的 N-way K-shot 情形。这种“训练时怎么采样,测试时就怎么采样”的做法,让模型从一开始就适应样本极少的分类格局。我在实践中发现,episode 采样策略对效果的影响比很多人以为的要大,后面会专门展开。
2.2 编码器选型与原型质量的关系
原型网络的底座是编码器 f_φ。同样是原型网络,换一个编码器效果天差地别。我在实验中试过三类:
第一类是轻量级句向量模型,比如 Sentence-BERT 系列。优点是推理速度快,嵌入空间对齐了语义相似度,原型本身就有意义。缺点是对领域词汇不敏感,直接在法律、医疗这种术语密集的文本上跑,原型质量下降明显。
第二类是全量微调的 BERT 类模型。这是最推荐的做法。BERT 作为编码器,在 episode 训练中跟着优化,嵌入空间会专门针对当前任务做形变,把与分类相关的维度拉开。代价是训练显存占用高,batch size 不能开太大,需要配合梯度累积。
第三类是冻结参数的 LLM 嵌入接口。适合零样本基线,不适合少样本训练。因为 LLM 的嵌入空间非常稳定,仅仅通过少量样本求均值来调整原型,能够改变的非常有限。
判断原型质量是否合格,我在实践中有个粗糙但有效的验证方法:算一下每个类的类内平均距离与类间平均距离的比值。比值如果高于 1.5,说明原型的区分度偏低,不管怎么调分类器都很难有质的提升,这时候优先换编码器而不是堆训练技巧。
2.3 适用边界:原型网络擅长什么、不擅长什么
原型网络最适合类别边界清晰、类内语义集中的任务。一个典型例子是电商售后工单的一级分类,比如“退款”、“换货”、“物流”、“发票”四个方向,每个类的表述虽然多样,但核心意图非常明确,支持集 5 到 10 条就能形成还不错的原型。
不适合的场景有两类。一类是类别之间存在大量语义重叠,比如舆情标签“愤怒”和“失望”边界模糊,原型之间距离近,查询样本稍偏一点就容易误判。另一类是细粒度分类,比如区分几十种不同型号产品的故障描述,类内差异大于类间差异,均值原型会把关键判别性信息平均掉。
针对这些缺陷,有一个改进方向非常实用,就是用语义聚类替代简单均值。把支持集样本先做聚类,取最大簇的均值作为原型,或者按置信度加权而不是算术平均,能过滤掉离群样本的干扰。我在脏数据较多的工单场景里试过,效果提升约 3 到 5 个点。
3. 度量学习:把“相似”这件事本身也学出来
3.1 为什么固定距离不够用:从孪生网络到匹配网络
原型网络用欧氏距离时隐含一个假设:嵌入空间中各向同性,即每个方向对分类的贡献一样。但真实文本语义空间不是这样的,往往只有少数几个维度携带判别信息,大部分维度是共有的语法特征、句式特征。欧氏距离会把不相关信息也计入距离计算,从而干扰分类。
度量学习的思路,是把“怎么衡量相似”也变成一个可学习的部分。最早的孪生网络很简单,两个样本共享编码器,距离函数提前定好,比如余弦相似度或者欧氏距离,训练目标是让同类的距离小、异类的距离大。这个方案在图像领域效果还行,但在文本上表现一般,因为句子结构复杂,两句话语义相近不一定体现在表面的词面重合上。
匹配网络是孪生网络的重要升级,它引入了注意力机制。查询样本的最终表示不再是编码器的直接输出,而是它与每个支持集样本的相关性加权求和:
f̂(x_query) = Σ_i a(x_query, x_i) · f(x_i)
其中权重 a 是查询样本与每个支持样本的相似度,经过 Softmax 归一化。注意力机制让查询样本可以根据当前任务动态调整自己的表示,更关注和哪些支持样本有关联。这在样本非常少的时候很有用,相当于放宽了“每个类只需要一个原型”的限制,保留了每个支持样本的独立信息。
3.2 关系网络:把距离变成一个神经网络
关系网络比匹配网络走得更远,它连距离函数都用神经网络来学。结构分两部分:嵌入模块 g_φ 负责将样本映射为向量,关系模块 r_ψ 负责计算查询样本与每个支持样本的“关系分数”。
对于一对组合(查询样本 x_j,支持样本 x_i),先把两个向量拼接,送入关系模块:
r_ij = r_ψ([g_φ(x_j); g_φ(x_i)])
关系模块的输出是一个标量分数,代表二者的匹配程度。训练时让同类组合的分数趋近 1,异类组合的分数趋近 0,用均方误差回归来优化。
我最初觉得关系网络没必要,距离就是距离,为什么还专门学?后来在真实场景里试过一次就理解了。固定距离函数比较的是向量之间的几何关系,比如余弦是看夹角,欧氏是看直线长度。但语义匹配经常不是简单的几何关系,两句话有关联可能是因为它们共同指向某一个特征,而这个特征在原始向量空间里是隐性的。关系网络相当于额外学了一个“匹配解释器”,从这个角度确实能捕捉到非线性的语义关联。
代价是训练难度上升。关系网络对嵌入模块和关系模块的联合调整敏感,关系模块太强会变成记忆支持集样本,出现严重的过拟合。我的经验是关系模块不要设计太深,两层全连接加 ReLU 就够了。
3.3 难样本挖掘:少样本训练里最容易忽略的细节
度量学习训练中有一个关键问题:随机抽样的样本对大部分是容易区分的,模型学不到什么。比如从 10 个类别里随机选一对(样本A,样本B),大概率完全不像,损失很小,梯度没有信号。为了更高效的训练,需要主动挑选难样本。
难样本指的是那些当前模型最容易混淆的负样本对,比如语义接近但类别不同的文本。把它们放进训练,才能逼着度量空间朝着更有判别力的方向形变。
图片领域有成熟的难样本采样方法,比如“最难正样本 + 最难负样本”策略。文本场景我用过一个简单版本:每个 episode 训练迭代中,先正常计算所有样本对的相似度矩阵,把每个锚点样本的负样本按相似度降序排列,选相似度最高的若干负样本(最难负样本)参与损失计算。实现上其实比想象中简单,多一个排序索引操作,成本完全可以接受。
难样本挖掘还有一个副作用,它能够削弱类别不均衡问题。少样本任务里各类别的样本数往往并不均衡,有的类别 5 条,有的只有 2 条。随机采样会让大类别主导梯度方向,难样本挖潜会让分类器把注意力集中在那些难以区分的类别上,而不是被多数类带偏。
3.4 三种度量方案的横向对比与选型建议
| 方案 | 核心机制 | 训练复杂度 | 文本场景表现 | 适用情况 |
|---|---|---|---|---|
| 孪生网络 | 固定距离 + 共享编码器 | 低 | 较差 | 简单二分类、向量相似检索 |
| 匹配网络 | 注意力加权表示 | 中 | 良好 | 支持集样本差异较大的任务 |
| 关系网络 | 可学习距离函数 | 中高 | 良好 | 语义匹配非线性较强的任务 |
我之前做过一个实验,用同一组数据跑三种方案:关系网络在准确率上略低于匹配网络,但是在类别间相似度高的小类上,比匹配网络多抓回来约 6% 的召回。这说明关系网络虽然整体优势不明显,但对“难分”样本的容忍度更好。如果你的业务恰好需要兜住那些偏门、模棱两可的类别,关系网络值得投入。反之,如果整体准确率优先,匹配网络性价比更高。
4. 提示调优:借预训练语言模型的先验知识做分类
4.1 从 [MASK] 到分类标签:模板构造与答案映射
提示调优的核心思路是绕开“训练一个全新分类器”这个思路,把分类任务改造成预训练语言模型的完形填空任务。语言模型在海量语料上见过大量“前提 + 结论”的文本模式,通过设计模板,可以把任务装进它熟悉的形式里,让它已有的语言知识参与判断。
具体分三步。第一步设计模板,比如情感分类的模板可以设计成“这部电影让我感到[MASK]”;第二步定义答案词,比如“好”映射“正面”、“差”映射“负面”;第三步把文本套入模板,让模型预测 [MASK] 位置对应的词,再根据答案词映射回原始标签。
PET 是这条路线的奠基工作,它提出了基于人工模板的少样本微调框架。但 PET 对模板质量极度敏感,换一个模板可能差十几个点。例如情感分类,模板“整体感受是[MASK]”与“这部电影的观感[MASK]”答案词分布相差很大,选择什么样的答案词对结果影响也非常明显。
我用过一个很朴素的技巧:不用单个模板,而是设计 8 个模板,每个模板独立预测,然后对预测结果做加权投票。每个模板的表现权重用它在验证集上的置信度计算。这个方法比调单个模板更省力,且稳定。模板数量不在于多,关键要让模板之间差异大,比如一个侧重整体评价,一个侧重细节感受,投票才有效果。
4.2 连续提示:把模板也变成参数
人工模板要人去猜语言模型的偏好,这件事非常蛋疼。连续提示(Continuous Prompt)干脆把模板的位置替换成一组可学习的向量,让模型自己决定“前缀”应该是什么。
具体做法是,在输入序列前面拼接若干组可学习的高维向量,例如 20 个 768 维的向量,这些向量初始化为随机值,或者用预训练模型的嵌入表初始化。微调时,只更新这些前缀参数,冻结大模型本身的权重。
这个方案的实际效果是:自动搜索模板确实有效,训练后学到的前缀向量比人工模板通常高 2 到 5 个点。这里想指出一个重要区别,PET 仍属于全量微调或大部分参数微调,而连续提示属于参数高效微调,更新参数量远远小于前者。
我实测下来,连续提示在显存受限的场景下是最好用的方案。因为不需要更新模型主体,也就不需要保存大量优化器状态,一个普通的消费级显卡就能跑。当然代价也是明显的,就是学出来的前缀参数很难迁移到新任务,每个任务都要重新训练一组。
4.3 结合原型思想:提示调优中的均值原型
提示调优和原型思想可以混合,这种混合方案在实践中表现令人满意。思路是这样的:先给每个类别生成一个“提示原型”,这个原型不是简单的嵌入均值,而是在提示模板条件下的类别语义表示。
比如对类别“物流”,使用模板“与物流相关的问题是[MASK]”来生成各支持集样本的嵌入,再取平均作为该类的原型。查询时也用相同的提示模板生成嵌入,和各类原型算距离。
这个做法的核心差异在于:普通原型网络直接对原始文本编码求均值,而提示原型把类别信息通过模板注入到嵌入过程里,让原型向量更聚焦于分类目标。我在某客服项目上对比过,提示原型比普通原型提升了接近 7 个百分点,原因是原始文本中有大量与业务无关的寒暄话术,模板把这些噪音对原型的干扰过滤掉了。
4.4 人工模板设计的几条实操经验
关于模板设计,我踩过不少坑,分享几条判断标准。
模板要足够自然。语言模型是按自然语言训练出来的,太机械的模板不能激活它的语言知识。判断方式很简单,把模板拿给同事读一下,如果对方需要思考才能理解,那模型大概率也理解不好。
[MASK] 的位置很讲究。放在句尾通常比放在句中对答案词的约束力更强,但也有例外。如果放句尾,模型会倾向根据上下文风格选择词;放句中,模型会更多地依赖局部语义,两者对应不同的答案词分布,可以针对性选择。
答案词的选择需要权衡。太具体的答案词(比如“喜爱”和“厌恶”)虽然判别力强,但语言模型的预测概率偏低,容易受到噪声影响。太通用的答案词(比如“好”和“差”)预测概率高,但类别间区分度反而下降。我的建议是每个类别准备 3 到 5 个候选答案词,预测时取这组词的分数总和作为该类的得分,而不是只靠单个词。
5. 三条路线的选型对比与组合实践
5.1 核心场景对照:什么时候用哪种方案
| 条件 | 推荐方案 | 理由 |
|---|---|---|
| 每条样本都很短,类别边界清晰 | 原型网络 | 简单直接,运算开销低,效果稳 |
| 类别多且相似度高 | 匹配网络/关系网络 | 注意力机制和可学习距离能捕捉细粒度差异 |
| 有大规模预训练模型可用 | 提示调优 | 语言知识参与决策,少样本下泛化更强 |
| 显存受限、只有消费级显卡 | 连续提示 | 只训少量参数,日程可跑 |
| 领域术语密集、通用语义空间不够 | 原型网络 + 领域微调编码器 | 原型质量依赖编码器,领域适配优先 |
5.2 一个实用的混合方案:提示原型 + 度量精调
我把实践中效果最稳定的混合方案拆解一下。第一步,用连续提示初始化一组模板前缀,在支持集上微调一小段时间,让模板适应当前任务。第二步,用这组带提示的编码器生成所有支持集样本的嵌入,按类别求均值得到原型。第三步,设计一个额外的可学习线性层或小型关系网络,将“查询嵌入与原型拼接”作为输入,输出类别得分,用查询集样本训练这个小网络。
这样做的好处是:微调时只更新前缀参数,不容易过拟合;关系网络只学如何根据提示嵌入做最终判断,复杂度低,收敛快。我在 5-way 5-shot 的多组数据上跑过,整体比单一方案高 3 到 8 个点,而且训练时间增加不多。
5.3 Effect 的关键:训练与推理时的数据分布一致性
无论选哪条路线,有一个原则必须守住,训练时构造的 N-way K-shot 分布要和推理时保持一致。举例来说,训练时每个 episode 随机抽 5 类,每类 5 条支持样本,若干查询样本,那么推理时也要配置同样的大小。如果业务上某类只有 4 条标注,就不能硬套 5-shot 的训练配置,而应该把这类单独处理,否则模型完全没有适应这个 shot 数的能力。
另外一个容易被忽视的问题是类别集合的一致性。测试阶段如果出现训练时未曾见过的类别,所有基于支持集构建分类器的方法都会失效,因为原型里根本没有对应向量。这种情况下只能重新构建原型,或在构造训练 episode 时故意引入“类别采样随机化”,让模型见过足够多样的类别组合方式。
6. 少样本分类的坑与排查实录
6.1 查询集泄漏:最隐蔽的错误来源
少样本训练实验里最常见的错误就是查询集泄漏。一种情况是,同一篇文章被拆成多个句子后,有些进了支持集,有些进了查询集。模型在实际预测时,会对和训练样本高度相似的句子给出偏高的置信度,导致指标虚高。另一种情况是预处理阶段做了数据增强,增强出来的样本和原始样本混在一起,没有做去重,也可能造成评估失真。
我在项目里订了一条死规矩:episode 划分前先对文本做去重,重复样本只保留一条,然后确保同一文本的任何变体不会横跨支持集和查询集。排查泄漏的方法是训练结束后做一个邻近检索,把查询集样本和支持集样本对比,找到距离最近的对,人工检查是否有语义重复。这一步虽然糙,但很有效。
6.2 原型过于平滑:均值操作抹掉了关键特征
原型网络看似简单,但在某些任务上会出现“原型变成了四不像”的情况。原因在于,均值操作会把类别内所有样本的模式平均在一起,而那些真正区分不同类别的特征可能在每个样本上占比都很小,被共有的语言风格稀释掉。
针对这种情况,我用过两个有效手段。一是原型加权,对支持集样本用模型自身的置信度作为权重,置信度高的样本权重大,低置信度的离群样本权重小。二是把支持集样本先做一次聚类,取最大簇内样本的均值作为原型,效果比较明显。第二种做法在支持集样本数量稍多(比如 10 条以上)时效果更明显。
6.3 提示调优模板不生效:先检查数据预处理
使用提示调优时,如果发现模板预测结果和随机猜测差不多,通常不是模型的问题,而是文本预处理和模型分词不一致造成的。中文场景尤其明显,BERT 类模型使用 WordPiece 分词,某些罕见词会被拆成若干子词,导致模板中的词和文本中的词无法对齐。
排查方法很直接,把带模板的完整输入序列打印出来,人工查看 [MASK] 附近的分词结果,看看关键词语是否被切分了。另一个常见坑是,模板和文本拼接时用了“,”之类的标点,模型分布上标点也会占据 token 位置,影响对 [MASK] 附近语义的感知。我的经验是模板里尽量少用标点符号,用空格或换行符分隔即可。
6.4 常见问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 训练损失下降但验证准确率低 | episode 划分泄漏 | 检查查询集与支持集文本重复 |
| 原型网络各类别准确率差异大 | 部分类原型被离群样本污染 | 尝试聚类取最大簇均值 |
| 提示调优所有类别输出同一个答案词 | 模板与输入拼接方式有问题 | 检查分词、标点、模板自然度 |
| 度量学习效果重复性差 | 难样本采样随机性过大 | 固定随机种子,增加负样本池 |
| 连续提示训练不收敛 | 前缀初始化范围不合理 | 改用预训练嵌入表初始化,调小学习率 |
6.5 关于训练细节的三个小补充
第一,少样本场景下学习率要非常小。BERT 类模型的微调建议在 2e-5 左右,连续提示建议更低,比如 5e-6 到 1e-5,学习率太大会导致灾难性遗忘,模型原有的语言知识被冲掉。第二,批量大小不要贪大,少样本下大 batch 反而容易让模型陷入局部最优,一个类别的样本可能在一个 batch 内被过度加权。第三,早停要基于验证集而不是训练集,少样本训练集上 loss 无论如何都会下降,只有验证集上的变化才反映泛化能力。
7. 少样本分类的后续扩展方向
文本增强在前面的内容里多次被提到,这里展开说两句。对于少样本文本分类,简单的同义词替换、回译、随机删除都能提供一定帮助,但要注意增强样本不能和原始样本形成完全相等的文本,否则会加剧过拟合。我更推荐“伪标签”风格的方法,用训练好的原型网络对无标注池做预测,把置信度高的样本选出来加入支持集。这个策略在数据分布比较集中时收益显著,但需要控制每轮新增样本数量,防止噪声堆积。
少样本分类结合主动学习也值得尝试。原型网络天然能给出置信度信息,可以优先挑选模型最不确定的样本让人工标注。我有一个项目,先用原型网络在无标注池里筛出 500 条模型最不确定的文本,人工标注后加入支持集,再重新训练,相比随机标注等量样本,准确率提升明显。这种方法可以显著降低标注成本。
不过也要提醒一点,少样本分类不是万能的。当类别数量超过 100、支持集数量接近 0 时,再强的算法和策略都不如找一个像样的外部数据源做预训练,或者在真实场景允许的情况下直接走人工规则。算法解决的是“标注不够但任务边界清楚”的问题,而不是“完全没有数据”的问题。
回到我自己的实践体会,少样本文本分类的核心把握好两件事,效果就不会太差。第一是用对评测方式,不要拿着训练指标沾沾自喜,要在独立的验证集上反复验,注意 episode 划分的公平性。第二是愿意做“混合”,越是少样本,越要把不同思路的组件组合在一起。原型网络提供稳定的框架,度量学习提升判别能力,提示调优注入语言先验,三者结合往往比单独押注某一条路线更稳健。先跑通一个最简单的原型网络基线,再逐步往上叠加手段,是我反复使用的稳妥路径。