监督微调的数据准备并不止于清洗格式和删除重复项。进入进阶阶段后,更重要的问题是:现有数据是否足够、下一批数据应该补什么、合成样本是否真的有增益,以及如何加入新能力而不让模型遗忘原有能力。
这些问题不能只靠数据量回答。更可靠的方法是把学习曲线、子集选择、合成与蒸馏数据、数据源混合放进同一个迭代闭环,并始终用独立评测集验证结果。
用学习曲线判断数据是否“准备好”
学习曲线的做法很直接:从训练集中抽取多个递增规模的子集,分别训练模型,再记录训练集和验证集指标。它能帮助团队区分几种常见状态:
- 训练分数和验证分数都偏低,增加同类数据可能作用有限,应检查任务定义、标签质量或模型能力。
- 训练分数很高而验证分数明显落后,通常需要更多覆盖真实分布的数据,或减少重复、模板化样本。
- 验证指标随数据量稳定上升且尚未趋平,继续补充同分布数据仍可能有价值。
- 总体指标趋平,但某些任务切片仍然较差,应从“扩大全集”转向“补齐薄弱切片”。
曲线必须基于固定、独立的验证集。若同一模板、同一文档片段或同一合成种子同时出现在训练集和验证集,曲线会显得异常漂亮,却无法反映真实泛化能力。
不要随机买数据:优先挑高价值样本
当标注预算有限时,随机扩充训练集通常不是最有效的选择。高价值数据往往来自以下位置:
- 当前模型置信度低或多个候选答案差距很小的样本。
- 模型稳定答错、但人工可以明确判断正确答案的样本。
- 线上流量中频率不高却风险很大的边界场景。
- 现有训练集覆盖不足的语言、领域、输入长度和指令类型。
- 与已有样本语义不同,而不是只改写几个词的新样本。
可以将候选样本按“不确定性、业务风险、分布稀缺度、多样性”综合排序。需要注意,不确定性筛选容易集中到噪声、歧义问题和超出任务边界的输入,因此仍需人工审核或规则过滤。
下面是一个可以直接运行的代理实验。它使用 scikit-learn 模拟学习曲线,并从候选池中选出预测概率最接近 0.5 的样本。真实 LLM 项目可以把分类器替换成微调模型,把准确率替换成任务评测器。
运行前安装依赖:
python -m pip install scikit-learn numpy
保存为 data_strategy_demo.py 后运行 python data_strategy_demo.py:
import numpy as np
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
SEED = 42
rng = np.random.default_rng(SEED)
X, y = make_classification(
n_samples=2400,
n_features=20,
n_informative=12,
n_redundant=4,
class_sep=1.0,
flip_y=0.04,
random_state=SEED,
)
X_train, X_temp, y_train, y_temp = train_test_split(
X, y, test_size=0.5, stratify=y, random_state=SEED
)
X_pool, X_eval, y_pool, y_eval = train_test_split(
X_temp, y_temp, test_size=0.5, stratify=y_temp, random_state=SEED
)
print("Learning curve")
for size in [100, 250, 500, 800, len(X_train)]:
indices = rng.choice(len(X_train), size=size, replace=False)
model = LogisticRegression(max_iter=1000, random_state=SEED)
model.fit(X_train[indices], y_train[indices])
train_acc = accuracy_score(y_train[indices], model.predict(X_train[indices]))
eval_acc = accuracy_score(y_eval, model.predict(X_eval))
print(f"size={size:4d} train={train_acc:.3f} eval={eval_acc:.3f}")
baseline = LogisticRegression(max_iter=1000, random_state=SEED)
baseline.fit(X_train, y_train)
probabilities = baseline.predict_proba(X_pool)[:, 1]
uncertainty = np.abs(probabilities - 0.5)
selected = np.argsort(uncertainty)[:100]
X_augmented = np.vstack([X_train, X_pool[selected]])
y_augmented = np.concatenate([y_train, y_pool[selected]])
updated = LogisticRegression(max_iter=1000, random_state=SEED)
updated.fit(X_augmented, y_augmented)
print("\nSelected examples:", len(selected))
print("Baseline eval:", accuracy_score(y_eval, baseline.predict(X_eval)))
print("Updated eval: ", accuracy_score(y_eval, updated.predict(X_eval)))
单次运行的提升并不构成结论。实际项目应重复不同随机种子,并分别报告总体指标和关键切片指标。
合成数据与蒸馏数据要经过“生成、过滤、验证”
合成数据适合填补明确的覆盖缺口,例如生成新的输入形式、边界条件或较少出现的语言表达。蒸馏数据则可以借助能力更强的教师模型,为未标注输入生成回答、解释或结构化标签。
可以这样实践一个最小工作流:
- 从真实数据分析中定义缺口,不让生成模型漫无目的地产生样本。
- 为每条样本保留
source、生成模型版本、提示词版本和目标切片等元数据。 - 使用格式校验、规则检查、去重和教师模型评分做初筛。
- 对高风险任务加入人工抽检,不把教师输出自动视为事实。
- 只在未参与生成和筛选的真实评测集上判断是否有增益。
一个适合落盘的 JSONL 记录可以包含:
{"messages":[{"role":"user","content":"用户输入"},{"role":"assistant","content":"审核后的目标回答"}],"source":"distilled","slice":"long_context","generator_version":"teacher-v3","prompt_version":"p7","review_status":"human_approved"}
合成数据的主要风险包括事实错误、教师模型偏差、语言风格单一和模板污染。如果合成样本占比过高,学生模型可能学会教师的措辞,却没有提升真实请求上的可靠性。
混合数据源,防止新能力覆盖旧能力
只用新领域数据继续微调,可能导致灾难性遗忘:模型在新任务上变好,却在通用指令遵循、安全约束或旧领域任务上退化。缓解方法是在训练批次中保留一部分高质量旧数据,并对不同来源设置可追踪的采样权重。
例如,新一轮训练可以从 新领域真实数据 50% + 旧能力回放数据 30% + 审核后的合成或蒸馏数据 20% 起步。这个比例只是可实验的初始值,不是通用配方;最终权重应由新旧评测集共同决定。
混合时还应控制样本长度和任务类型。按“样本条数”分配 30% 的旧数据,不代表它贡献了 30% 的训练 token。长样本可能悄悄主导梯度,因此日志中至少要同时记录样本占比、token 占比和各数据源损失。
上线前的决策清单
- 学习曲线是否使用固定且无泄漏的真实验证集?
- 是否按领域、语言、长度、风险和任务类型查看切片指标?
- 新标注是否优先覆盖错误密集区和数据空白区?
- 合成与蒸馏样本是否可追溯、去重并经过质量审核?
- 训练混合是否保留旧能力回放数据,并按 token 检查实际权重?
- 新能力评测提升时,旧能力和安全评测是否仍在允许范围内?
进阶数据策略的核心不是把训练集做得更大,而是让每一批新增数据都回答一个明确问题。学习曲线决定是否继续扩量,子集选择决定标注预算花在哪里,合成与蒸馏补齐覆盖缺口,数据混合则守住已经获得的能力。四者只有与独立评测和可追溯元数据结合,才能形成可靠的监督微调迭代闭环。