知识蒸馏中的数据筛选:RSR方法如何挑选黄金数据 做模型蒸馏这些年我最常被问的一句话是教师模型是不是越强越好直觉上当然越强越好但你真正动手把一个 70B 教师的知识压进 7B 学生的时候会发现事情远没这么简单。前阵子某课题组放出了一套叫 RSR 的数据筛选思路核心观点非常反直觉教师模型的能力上限和它提供给你的蒸馏数据质量根本不是一回事。简单说就是教你怎么从一大堆无标注数据里挑出真正适合学生学习的“黄金数据”。我照着这个思路重新跑了一遍自己的蒸馏管线有收获也踩了不少坑今天这篇就把这套方法从头到尾拆开讲。1. 强师翻车现场强教师怎么教出弱学生1.1 蒸馏的实质不是压缩能力上限而是传递决策边界很多人把知识蒸馏理解成“让大模型教小模型”好像教师模型越大、学生学到的就越多。但真正做蒸馏的人会告诉你蒸馏学的不是教师的能力上限而是教师在一个具体数据分布上的决策规律。教师输出的是什么是软标签也就是模型在各类别或词表上的概率分布。比如一个简单的分类问题教师可能给出“猫 0.6、狗 0.35、树 0.05”这样的结果。相比 one-hot 标签软标签最大的价值在于保留了“猫和狗之间存在竞争关系”这一信息这比单纯告诉学生“答案是猫”高级得多。但如果教师对某个样本输出的分布是“猫 0.999、狗 0.0008、树 0.0002”呢这时候软标签几乎没有提供任何相对关系信息学生从里面能学到的只有“背下这个答案”而不是“理解猫和狗在特征空间里的边界”。这就是强教师的第一个隐患它太自信了自信到把教学变成了抄答案。1.2 强教师的三个隐藏毛病第一个毛病是过度自信的尖峰分布。大模型在自己训练过的分布上logits 经常拉开非常大的差距softmax 之后基本是一个像素级的独热分布。你说它错了吗没错但这种输出对蒸馏几乎没有信息量。就像让短跑冠军给小学生示范跑姿他直接按最高速度冲了一遍小学生看到的只是残影完全学不到蹬地和摆臂的发力逻辑。第二个毛病是系统性错误。教师越强它的错误往往越有规律。它会在某一类样本上持续出现同一种误判比如把包含特定句式的问题全部导向一个错误答案。这类错误一旦被学生模仿就会变成很难纠正的偏差。因为学生没有足够的能力判断教师哪里错了它只会把教师的所有行为都当成标准。第三个毛病是难度分布失衡。强教师看简单样本和中等样本几乎都是高分区分度很低。大量样本落在“教师闭眼都能做对”的区域真正处于“临界区”——也就是学生跳一跳能够到的样本占的比例反而很小。这时候如果不做筛选你得到的蒸馏集会被简单样本淹没学生训练到后期基本没有梯度信号。所以强师翻车这件事根子不在教师强弱而在数据没选对。教师是好的但喂给学生的“教材”是坏的。RSR 解决的就是这个“教材挑选”问题。2. 想筛黄金数据先定义清楚什么叫“可教”2.1 置信度、一致性、分歧区间三件套要选数据首先得有一套判断标准。我拆过几版思路之后发现基本离不开三个信号教师置信度、教师预测与标签的一致性、以及教师输出分布的分歧区间。教师置信度指的是教师给真实标签分配的概率。它衡量教师对这个样本的把握程度。一致性则是指教师 argmax 出来的答案和真实标签是否一致。如果教师的高置信答案都是错的那置信度再高也不能用。分歧区间我一般用 top1 概率减 top2 概率来度量。这个值如果太小说明教师自己也在两个答案之间摇摆输出分布熵很高学生学起来容易懵如果太大说明样本对教师太简单没有教学价值。综合来看一块合适的“黄金数据”大概率同时满足三个条件教师对真实标签的置信度处于中高区间我一般看 0.65 到 0.92教师 argmax 和标签一致以及 top2 的差距在一个适中的范围内。这三个条件合起来等价于说教师大概率能做对但并没有做到“闭眼秒杀”它还保留了一点犹豫和比较空间。而学生要学的恰恰就是这个空间里的判断逻辑。2.2 容易样本不是垃圾难样本也未必是金子很多团队做蒸馏筛选时会有一个执念多选难样本学生才能学得更快。这个想法有一定道理但直接照搬会出事。难样本里混着大量噪声标签。尤其在大规模爬取的数据里很多“难”不是真的知识难而是标注本身有问题教师在上面输出混乱学生硬学只会把错误也学进去。更关键的是如果蒸馏集里全是困难样本学生学习曲线的方差会非常大。前几步还在学基础规则后几步突然被一堆极端样本冲击训练稳定性很难保证。容易样本反而承担了一个重要职责提供稳定的梯度信号。它让学生快速建立对基础模式的识别能力这部分能力是后续学习困难样本的底座。一个好的蒸馏集应该是“基础样本 临界样本 少量高难样本”的混合体而不是单吊某一类。所以“可教”这个概念本质上不是一个绝对难度而是相对学生当前能力“略高一步”的样本。但问题来了学生能力在训练过程中是不断变化的我们没法实时估计所以只能退而求其次用教师输出内部的“自我难度”来近似这个“可教区间”。2.3 校准度在这里比想象中更重要只看置信度还有一个暗坑置信度不等于正确率。一个校准不良的教师可能对某类样本给出 0.9 的置信度但实际正确率只有 0.7。这时候按置信度排序你会优先选中那些“教师自信满满但本质上在做错题”的样本。校准度这个概念从分类模型那边来指的是模型预测概率和经验频率是否一致。如果模型对所有预测为 0.8 的样本实际正确率也接近 0.8那它就是校准良好的。在无标注蒸馏场景里我们不能直接算校准误差但可以用教师自身的自洽性做近似比如看教师在不同温度下是否稳定做同一个预测或者看教师输出分布和模型整体正确率之间的偏移趋势。RSR 把校准误差作为一个惩罚项集成进去本质上就是在说别只看教师“敢不敢打包票”还要看它打包票的含金量。3. RSR 筛选框架的完整拆解3.1 相对排序到底在排什么RSR 的全称我习惯理解成 Relative Signal Ranking它的核心变化在于不做绝对打分而是在候选样本内部做相对排序。为什么相对排序这么重要因为不同领域、不同来源的样本教师置信度的天然分布完全不同。某些领域的文本教师随便给到 0.95另一些领域可能最高只有 0.8。你设一个全局阈值 0.9就会把第二类领域的数据几乎全部砍掉。相对排序没有这个毛病它只看每个样本在自己那一批候选者中的位置。整体分数由几个信号线性加权组合。我使用的版本大致是这样RSR_score w1 * consistency w2 * true_confidence w3 * (1 - sharpness) w4 * (1 - calib_gap)其中 consistency 是教师 argmax 与标签是否一致true_confidence 是教师给真实标签的概率sharpness 用 top1 减 top2 来度量calib_gap 是教师置信度和经验正确率之间的偏移估计。权重我一般取 0.25、0.4、0.2、0.15但这不是固定的不同任务需要自己调。3.2 分桶采样策略黄金数据不是“排名前 N”而是“各段都有一点”拿到 RSR 分数之后直觉做法是直接取最高的前 10%。但我强烈不建议这么做。原因有两个一是同质化排在最前面的样本大概率看起来特别像清一色的高置信、低分歧学生学完容易变成某种模式的特化模型二是多样性崩塌很多中低区间的样本虽然单看“教学价值”没那么高但它覆盖了语言表达的另一种形态全部丢掉之后模型的泛化能力会退化。RSR 的做法是把候选集按分数从低到高分成 5 个分位桶然后每个桶按预设比例采样。我常用的比例是低分段取 10%、中低段取 20%、中段取 40%、中高段取 20%、高段取 10%。这样既保证临界区样本的密度又留出了基础样本和高难样本的覆盖。如果候选数据本身有领域或者任务属性分桶之后还要做一次分层采样让每个领域在各难度桶里的比例和整体分布保持一致。这一步在实操中对模型最终效果的影响非常大。3.3 一个 30 行左右的筛选脚本我把整个筛选过程写成了一个独立脚本输入是教师模型在候选集上保存的 logits 和对应的标签输出是筛选后的索引。以下是核心部分可以直接照着改import numpy as np def softmax(logits, temperature1.0): logits logits / temperature exp np.exp(logits - logits.max(axis-1, keepdimsTrue)) return exp / exp.sum(axis-1, keepdimsTrue) def compute_rsr_scores(logits, labels, temperature1.0): probs softmax(logits, temperaturetemperature) n len(logits) true_conf np.array([probs[i, labels[i]] for i in range(n)]) top_idx probs.argsort(axis-1)[:, ::-1][:, :2] top1_conf probs[np.arange(n), top_idx[:, 0]] top2_conf probs[np.arange(n), top_idx[:, 1]] sharpness top1_conf - top2_conf consistency np.array( [1.0 if top_idx[i, 0] labels[i] else 0.0 for i in range(n)] ) # 用教师自身的分布熵作为校准偏移的近似估计 entropy -np.sum(probs * np.log(probs 1e-9), axis-1) calib_gap np.abs(entropy - entropy.mean()) / (entropy.std() 1e-8) w [0.25, 0.4, 0.2, 0.15] score (w[0] * consistency w[1] * true_conf w[2] * (1 - sharpness) w[3] * (1 - calib_gap)) return score def select_by_bucket(scores, ratios, seed42): rng np.random.default_rng(seed) order np.argsort(scores) n len(scores) bucket_size n // len(ratios) selected [] for idx, ratio in enumerate(ratios): bucket order[idx * bucket_size: (idx 1) * bucket_size] keep rng.choice(bucket, sizeint(len(bucket) * ratio), replaceFalse) selected.extend(keep.tolist()) return np.array(selected)这段代码没有做什么高深的事它就是把前面定义的三个信号量化然后按分桶策略采样。实际项目里我会把 logits 预处理成 float16 的 numpy 文件分批读入避免一次加载几十 G 数据把内存打爆。4. 把 RSR 塞进真实蒸馏管线实验配置与效果4.1 一个 70B 教师带 7B 学生的流程设计我用于验证 RSR 的蒸馏管线大概是这样的候选池是一批无标注文本数据规模在百万级别教师用一个稠密 70B 模型学生是 7B蒸馏目标采用软标签损失和硬标签损失加权组合。流程分四步走。第一步教师模型在候选池上做一次前向推理把所有样本的 logits 按 hash 分片保存到磁盘这个步骤最贵但只需要做一次。第二步用上面的脚本计算 RSR 分数做分桶采样得到蒸馏集。第三步把蒸馏集拆成训练和验证两部分学生模型训练若干个 epoch。第四步在任务评估集上做对比。关键点在于选数据的过程和训练过程完全解耦。我可以在不重跑教师推理的情况下反复调整分桶比例和权重实验效率高很多。4.2 一次内部对照实验的效果差异我在一次内部模拟实验里跑了几组对照筛选比例统一控制在 25% 左右学生训练步数和超参保持一样最后看学生在几个任务集合上的平均表现。因为只是内部验证数值不具备普遍性但趋势非常明显筛选策略数据占比学生平均表现训练稳定性随机采样25%基准较稳定只看高置信 top 25%25%略低于基准后期波动明显只选困难样本 25%25%波动大不稳定只用中间难度 25%25%略高于基准稳定RSR 分桶采样 25%25%最高稳定只看高置信 top 那组初期学生 loss 降得很快但后面在下游评测上反而没占到便宜。只选困难样本那组更明显训练途中出现过一次较大的指标抖动。RSR 分桶那组整体是全场最稳的它在保留高价值临界样本的同时没有把基础样本丢光。4.3 计算成本怎么控制RSR 本身不增加多少推理成本真正的成本大头是教师前向推理。规模一旦到百万级70B 模型全量跑一轮是很贵的。我有三个控制成本的经验。第一logits 缓存一定要做不要每次换筛选策略就重跑一遍教师。第二先在候选池里随机抽 5% 到 10% 的样本把教师跑完估算 RSR 分数的分布和分位点然后再决定全量筛选的参数。第三如果候选池特别大可以先让一个小一点的教师模型做粗筛把明显不合适的低质量样本过滤掉再用大教师在剩余样本上精算 RSR。这个两级筛选策略能省下接近一半的推理开销。5. 复现路上我踩过的坑与修正5.1 同一个温度处理所有样本是最大的误区知识蒸馏基本都要用温度系数软化教师分布但 RSR 的落地过程中我踩得最狠的一个坑就是统一用固定温度处理所有样本。不同样本的 logits 尺度差异很大有的样本教师输出本身就比较平缓用 T1 效果尚可有的样本 logits 差距巨大T1 出来的分布还是接近 one-hotsharpness 依然很高。如果你在计算 RSR 时用的是 T1蒸馏训练时又用 T3那筛选信号和实际训练信号就对不上了。我的修正做法是计算 RSR 之前先统计一批样本 logits 的标准差设定一个能让 softmax 后熵不至于过低的温度区间再在 1.0 到 3.0 之间按样本的 sharpness 做自适应调整。简单说就是 sharpness 高的样本用更高的温度摊平sharpness 低的样本少摊平一点。5.2 重复采样带来的过拟合分桶采样本身带有随机性某些 RSR 分数特别高的样本会在多个桶的边缘反复出现加上权重采样策略的话就可能被选中多次。刚开始我没当回事结果学生训练两三个 epoch 之后明显开始“背”这些重复样本体现在 loss 曲线上就是反复震荡验证集表现停滞。解决方式是在 sampler 里加一个限制每个样本在一个 epoch 中最多出现两次并且第二次出现时软标签损失权重减半。这个限制加入之后重复样本的负面影响很快消失了。5.3 标签不全的时候怎么维持一致性我上面提到 consistency 信号需要知道真实标签但工业生产中很多无标注数据根本没有标签。这时候有两个替代方案。方案一是用教师自身的自洽性替代对同一个样本用温度和输入扰动各跑一次看教师 argmax 是否稳定。如果教师在扰动下都做同一个预测那就近似认为它对这个预测是一致的。方案二是用多个教师投票取两个教师的预测一致性作为 soft consistency 信号。在我自己的场景里投票方案的效果明显优于单一教师自洽性因为两个模型在同一处犯同样错误的概率要低很多。6. 选数据这件事最后还是要看下游任务说话6.1 抽一万条先做小实验别一上来就全量RSR 里的权重、分桶比例、采样比例都不是一个固定答案。不同任务的数据分布不同教师模型的特性也不同照着别人给的参数直接套大概率翻车。我的习惯是在正式全量筛选前先从候选池里抽一万条数据跑一个几百步的小型蒸馏实验快速对比随机采样、纯高置信采样、困难样本采样、RSR 分桶采样这几种策略在小验证集上的曲线。这个实验整个耗时一个小时以内但能帮你省掉后面全量训练浪费的几天时间。指标上重点看两个东西训到后半段验证 loss 是否还在继续下降以及最终下游评测分数的方差大小。6.2 RSR 的适用边界我也摸了一下我目前主要在分类、抽取、短文本生成这类任务上验证 RSR效果不错。但在开放式长文本生成场景里情况要复杂得多因为这类任务非常依赖输出的多样性和长尾覆盖。RSR 的分桶采样虽然保留了低分段样本但这种保留比例如果调得不够宽松依然会把一些“冷门但独特”的表达方式挤掉。我的处理方式是在长文本生成任务里降低 RSR 分数中 consistency 和 true_confidence 的权重同时把低分段样本的保留比例从 10% 提到 30%。本质上就是让筛选的触手短一点不要过度干预多样性。你可以在自己的任务上试几组权重找找那个“边界值”。这个平衡点没有标准答案只能靠实验去摸。6.3 最后分享一个我自己在用的工程小技巧所有 RSR 相关的中间结果我都会按“数据版本 教师版本 权重配置 RNG seed”打个全量 hash 存档。这个习惯救过我很多次。因为筛选过程本身就是带随机性的如果没有固定版本后续要复现某个训练结果时会非常痛苦。另外保存教师 logits 的时候推荐直接存成 float16 的.npy分片不要存 pickle也不要存成文本不然吃满磁盘后你连删都不知道从哪删起。每次筛选前把 RSR 分数的直方图打出来看一眼如果某个桶里样本数量明显异常大概率是数据分布变了或者教师推理环节出了问题。选数据说到底是一个和数据打交道的工程活保持可追溯、可复现比追求一时的“最优筛选策略”更重要。