
情感识别中恐惧、悲伤样本太少怎么办一套少数类数据优化实践在做情感识别的时候有一个问题非常容易被忽略不是模型不够大而是数据根本不够。尤其是把情感进一步细分之后像“喜悦”“中性”这类情感通常比较容易收集而“恐惧”“悲伤”“失望”“焦虑”等类别的数据量明显偏少。例如一个实际项目的数据统计可能是这样中性 52000 喜悦 18600 愤怒 9200 惊讶 6800 悲伤 2300 恐惧 1100 失望 760如果直接拿这样的数据训练模型最终很容易出现一种情况整体 Accuracy 看起来不错但是少数情感几乎识别不出来。这类问题在语音转写后的文本情感识别、会议内容分析等场景中尤其明显。类似熙瑾会悟这样的智能会议应用如果进一步做情绪分析也会遇到同样的数据分布问题。下面记录一下比较完整的解决思路。一、先别急着换模型先确认到底是不是数据问题遇到少数类识别效果差我一般不会第一时间去换 BERT、RoBERTa 或者其他更大的模型。先统计训练集from collections import Counter labels [ neutral, neutral, sad, fear, happy, sad ] counter Counter(labels) for label, count in counter.most_common(): print(label, count)如果发现neutral 52000 happy 18600 angry 9200 sad 2300 fear 1100那么基本可以确定存在比较严重的类别不均衡。这里有一个容易犯的错误不要只看 Accuracy。假设测试集有 10000 条数据其中 7000 条都是中性那么模型全部预测成中性也能获得 70% Accuracy。所以情感识别更应该关注PrecisionRecallF1-scoreMacro-F1每个类别的 Confusion Matrix尤其是 Macro-F1。二、第一步重新清洗少数类数据数据少并不可怕真正麻烦的是少而且还不干净。例如“悲伤”类别里可能混入我真的太开心了 这个结果让我有点难过 哈哈这次终于成功了 我不知道该怎么办这些样本如果标签本身就不准确后面再怎么增强都没有意义。因此建议先做一轮人工抽检。可以建立这样的数据检查表检查项处理方式标签明显错误删除或重新标注重复文本去重极短文本根据任务决定是否保留无情绪表达重新确认是否属于中性多情绪表达允许多标签或制定主情绪规则上下文缺失补充上下文ASR错误回查原始音频这里特别需要注意语音场景。例如“我真的太难过了”如果 ASR 转成“我真的太难过啦”通常问题不大。但如果变成“我真的太难过了吧”甚至出现关键词漏识别就可能影响情绪分类。所以语音情感数据最好保留audio_id text emotion speaker confidence source后续才能追溯错误。三、第二步不要简单复制少数类最直接的办法是悲伤 1000 ↓ 复制 ↓ 悲伤 10000虽然数量上平衡了但模型实际上看到了大量完全相同的句子。这样很容易造成过拟合。所以更推荐少数类过采样 数据增强例如原始数据恐惧1000 悲伤2000 中性50000可以调整成恐惧5000 悲伤6000 中性20000注意这里的目标并不是强行做到 1:1。很多时候只需要把极端的数据分布拉回来即可。imbalanced-learn本身就提供了 RandomOverSampler 等重采样工具可以针对少数类进行有放回采样。四、第三步文本增强比盲目复制更重要对于“恐惧”和“悲伤”这类少数情感可以做一些受约束的数据增强。例如原始 这个结果让我很难过。 增强 这个结果真的让我有些难过。 这个结果让我感觉很失落。 看到这个结果心里挺不是滋味的。常见方法包括同义表达替换句式改写局部词语替换回译大模型辅助生成基于模板生成但是这里有一个原则增强之后情感标签必须保持稳定。例如我有点害怕。不能增强成我一点都不害怕。否则就是“数据增强把标签改没了”。因此建议给增强程序增加一个简单的质量过滤流程原始样本 ↓ 文本增强 ↓ 语义相似度检查 ↓ 情感一致性检查 ↓ 人工抽检 ↓ 进入训练集五、第四步训练集可以重采样但验证集不要动这是实践中特别重要的一点。假设原始数据 ↓ Train / Validation / Test ↓ 只处理 Train不要对 Validation 和 Test 做过采样。正确流程应该是原始数据 ↓ 数据清洗 ↓ Train / Validation / Test ↓ ├── Train → 增强 重采样 │ ├── Validation → 保持原始分布 │ └── Test → 保持原始分布这样最终测出来的指标才更接近真实线上效果。否则验证集也被人为“平衡”指标很容易虚高。六、第五步加入 Class Weight数据层面处理完之后还可以从 Loss 层面解决。对于类别数量差异比较大的情况可以给少数类别更高的权重。一个常见的 balanced 权重形式是weight_i N / (C × n_i)其中N训练样本总数C类别数量n_i第 i 个类别的样本数量scikit-learn 的class_weightbalanced就采用了与类别频率成反比的方式计算权重。PyTorch 中可以直接这样实现import torch import torch.nn as nn class_counts [52000, 18600, 9200, 2300, 1100] total sum(class_counts) num_classes len(class_counts) weights [ total / (num_classes * count) for count in class_counts ] weights torch.tensor(weights, dtypetorch.float) criterion nn.CrossEntropyLoss( weightweights )这样模型计算 Loss 时错误预测“恐惧”的代价就会高于错误预测“中性”。七、第六步少数类特别难时可以尝试 Focal LossClass Weight 解决的是类别之间的重要程度不同。Focal Loss 更关注那些模型很难判断的样本。它的基本形式为FL(pt) -α(1 - pt)^γ log(pt)其中pt模型对真实类别的预测概率α类别权重γ聚焦参数当模型已经非常确定一个样本时pt → 1那么(1 - pt)^γ → 0这个样本对 Loss 的贡献就会降低。而那些模型一直判断错误的困难样本会获得更高的训练关注度。一个简单实现如下class FocalLoss(nn.Module): def __init__(self, alphaNone, gamma2.0): super().__init__() self.alpha alpha self.gamma gamma def forward(self, logits, targets): ce nn.functional.cross_entropy( logits, targets, weightself.alpha, reductionnone ) pt torch.exp(-ce) loss (1 - pt) ** self.gamma * ce return loss.mean()实际项目里建议先使用gamma 2作为初始值再根据验证集表现调整。八、不要一上来同时使用所有方法这是我比较建议注意的一点。如果一开始同时使用最后效果变好了你反而不知道到底是哪一步起了作用。而且权重过大还有可能造成新的问题模型过度关注少数类开始误报。因此更合理的实验顺序是每一步都记录Macro-F1 Fear Recall Sad Recall Fear Precision Sad Precision Overall Accuracy最终形成实验表。例如方案Macro-F1Fear RecallSad RecallBaseline0.710.380.45数据增强0.750.490.53Oversampling0.770.560.59Class Weight0.790.610.64Focal Loss0.800.660.68具体数值只是示例实际项目应该使用自己的验证结果不能直接套用。九、最后还要做一次“少数类专项测试”普通测试集跑完以后我建议额外建立一个Rare Emotion Test Set专门收集恐惧 悲伤 失望 焦虑 困惑这些难分类样本。然后单独统计from sklearn.metrics import classification_report print( classification_report( y_true, y_pred, target_names[ neutral, happy, angry, sad, fear ] ) )重点观察sad recall fear recall而不是只看整体 Accuracy。十、最终形成一套完整的数据处理链整个方案可以总结成真正做情感识别时我认为最值得投入时间的其实不是“换一个更大的模型”而是把这条数据链路跑通。尤其是恐惧、悲伤这种天然比较稀缺的情绪数据质量往往比模型参数量更加重要。如果后续发现模型仍然存在悲伤 → 中性 恐惧 → 中性 恐惧 → 悲伤这样的集中误判再进一步检查上下文、ASR错误、声学特征以及标签边界往往比继续堆训练轮数更有效。总结情感数据稀缺不是单纯的“数据量少”问题而是一个完整的数据分布问题。比较稳妥的工程方案是先清洗再增强先调整数据分布再调整 Loss最后通过独立测试集验证少数类到底有没有真正提升。对于实际项目建议把每一次数据处理都保留版本例如dataset_v1 dataset_v2_clean dataset_v3_aug dataset_v4_resample同时保存每次实验的模型版本 数据版本 类别分布 训练参数 Macro-F1 各类别 Recall 混淆矩阵这样当线上再次出现情绪误判时就能快速定位到底是数据问题、模型问题还是阈值和场景分布问题而不是重新从头排查。