NLP敏感信息检测任务中类别失衡问题的解决方案咨询
敏感信息检测模型的类别失衡问题及解决方案
问题背景
我正在训练NLP模型检测医疗笔记中的敏感信息,需识别15类敏感内容。为让模型区分敏感与非敏感数据,新增了非敏感类别,但数据集出现严重失衡——模型对非敏感数据预测准确率极高,却无法有效识别其余15类敏感类别。
纠结点:
- 屏蔽非敏感数据,怕模型无法区分敏感/非敏感内容
- 考虑对海量非敏感数据做大量欠采样,但不确定是否合理
当前数据集与模型细节:
- 数据集:含伪造敏感信息的医疗笔记,Excel格式(26MB),按50%训练集、25%验证集、25%测试集划分,确保测试集有足够敏感类样本
- 数据处理:以句子为单位分类,处理为长度38的token序列,用
bert-base-uncased分词器;每个token带标签(padding为0,非敏感为1,敏感类为2-15),训练时通过mask忽略padding - 模型:因数据集规模小,采用GRU模型,已加Dropout缓解过拟合,但仅非敏感类指标优异,其余类别表现差
针对性解决方案
1. 类别权重调整(优先尝试)
在损失函数中为少数类(敏感类)赋予更高权重,强制模型关注这些类别的错误。修改模型编译部分:
import numpy as np from sklearn.utils.class_weight import compute_class_weight # 过滤padding标签(标签0),仅计算有效类别权重 train_labels = train_y_gru.flatten() train_labels = train_labels[train_labels != 0] class_weights = compute_class_weight('balanced', classes=np.unique(train_labels), y=train_labels) class_weight_dict = {i+1: class_weights[i] for i in range(len(class_weights))} class_weight_dict[0] = 0 # padding标签权重设为0,不参与损失计算 # 编译模型时传入类别权重 model_gru.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['sparse_categorical_accuracy'], class_weight=class_weight_dict)
2. 分层欠采样(谨慎操作)
不建议直接大量砍掉非敏感数据,采用分层欠采样更合理:
- 仅对训练集操作,验证集、测试集保留原始分布,避免评估失真
- 按敏感类样本量的2-5倍保留非敏感样本,既缩小失衡比例,又保留足够非敏感数据让模型学习区分边界
3. 敏感类数据增强
针对敏感类样本做数据增强,提升样本量:
- 同义词替换:用医疗领域同义词库替换敏感词(比如用"心肌梗死"替换"心梗")
- 句子重组:在不改变敏感信息类别的前提下,调整句子语序
- 回译:将敏感句子翻译成英文再译回中文,生成语义相近的变体样本
4. 评估与输出调整
- 放弃准确率作为核心评估指标,改用F1-score、精确率/召回率,更能反映少数类的真实表现
- 降低非敏感类的预测阈值:比如将原本0.5的阈值提升至0.7,让模型更倾向于预测敏感类
5. 模型结构小优化
在现有GRU基础上添加全局平均池化层,减少序列冗余信息:
from keras.layers import GlobalAveragePooling1D model_gru = Sequential() model_gru.add(Embedding(input_dim=vocab_size, output_dim=out_dim, input_length=input_len, mask_zero=True)) model_gru.add(GRU(64, return_sequences=True, activation='tanh')) model_gru.add(Dropout(.25)) model_gru.add(GRU(32, return_sequences=True, activation='tanh')) model_gru.add(Dropout(.25)) model_gru.add(GlobalAveragePooling1D()) # 添加全局平均池化 model_gru.add(Dense(numb_classes, activation='softmax'))
原始模型代码
#Dataset statistics numb_classes = 17 #Number of classes in the NLP problem vocab_size = 30522 #The vocab size of the bert-base-uncased model out_dim = 100 input_len = 38 callback = keras.callbacks.EarlyStopping(monitor='loss', patience=3) model_gru = Sequential() model_gru.add(Embedding(input_dim=vocab_size, output_dim=out_dim, input_length=input_len, mask_zero=True)) model_gru.add(GRU(64, return_sequences=True, activation='tanh')) model_gru.add(Dropout(.25)) model_gru.add(GRU(32, return_sequences=True, activation='tanh')) model_gru.add(Dropout(.25)) model_gru.add(Dense(numb_classes, activation='softmax')) model_gru.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['sparse_categorical_accuracy']) history_gru = model_gru.fit(train_X_gru, train_y_gru, epochs=10, batch_size=70, validation_data=(val_X_gru, val_y_gru))
内容的提问来源于stack exchange,提问作者dunha2j
相关产品推荐
相关产品推荐

