情感识别中恐惧、悲伤样本太少怎么办?一套少数类数据优化实践
2026/9/5 3:29:40 网站建设 项目流程

情感识别中恐惧、悲伤样本太少怎么办?一套少数类数据优化实践

在做情感识别的时候,有一个问题非常容易被忽略:

不是模型不够大,而是数据根本不够。

尤其是把情感进一步细分之后,像“喜悦”“中性”这类情感通常比较容易收集,而“恐惧”“悲伤”“失望”“焦虑”等类别的数据量明显偏少。

例如,一个实际项目的数据统计可能是这样:

中性 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。

所以情感识别更应该关注:

  • Precision

  • Recall

  • F1-score

  • Macro-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 等重采样工具,可以针对少数类进行有放回采样。


四、第三步:文本增强比盲目复制更重要

对于“恐惧”和“悲伤”这类少数情感,可以做一些受约束的数据增强。

例如:

原始: 这个结果让我很难过。 增强: 这个结果真的让我有些难过。 这个结果让我感觉很失落。 看到这个结果,心里挺不是滋味的。

常见方法包括:

  1. 同义表达替换

  2. 句式改写

  3. 局部词语替换

  4. 回译

  5. 大模型辅助生成

  6. 基于模板生成

但是这里有一个原则:

增强之后,情感标签必须保持稳定。

例如:

我有点害怕。

不能增强成:

我一点都不害怕。

否则就是“数据增强把标签改没了”。

因此建议给增强程序增加一个简单的质量过滤流程:

原始样本 ↓ 文本增强 ↓ 语义相似度检查 ↓ 情感一致性检查 ↓ 人工抽检 ↓ 进入训练集

五、第四步:训练集可以重采样,但验证集不要动

这是实践中特别重要的一点。

假设:

原始数据 ↓ 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_weight="balanced"就采用了与类别频率成反比的方式计算权重。

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, dtype=torch.float) criterion = nn.CrossEntropyLoss( weight=weights )

这样模型计算 Loss 时,错误预测“恐惧”的代价就会高于错误预测“中性”。


七、第六步:少数类特别难时,可以尝试 Focal Loss

Class Weight 解决的是:

类别之间的重要程度不同。

Focal Loss 更关注:

那些模型很难判断的样本。

它的基本形式为:

FL(pt) = -α(1 - pt)^γ log(pt)

其中:

  • pt:模型对真实类别的预测概率

  • α:类别权重

  • γ:聚焦参数

当模型已经非常确定一个样本时:

pt → 1

那么:

(1 - pt)^γ → 0

这个样本对 Loss 的贡献就会降低。

而那些模型一直判断错误的困难样本,会获得更高的训练关注度。

一个简单实现如下:

class FocalLoss(nn.Module): def __init__(self, alpha=None, gamma=2.0): super().__init__() self.alpha = alpha self.gamma = gamma def forward(self, logits, targets): ce = nn.functional.cross_entropy( logits, targets, weight=self.alpha, reduction="none" ) 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 Recall
Baseline0.710.380.45
数据增强0.750.490.53
+Oversampling0.770.560.59
+Class Weight0.790.610.64
+Focal 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 混淆矩阵

这样当线上再次出现情绪误判时,就能快速定位到底是数据问题、模型问题,还是阈值和场景分布问题,而不是重新从头排查。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询