NNP准确率总上不去?从数据清洗到特征工程,这5个优化方向帮你找到问题所在
做命名实体识别(NNP/NLP)的朋友大概都经历过这种抓狂——明明模型结构没问题,数据也够用,但准确率就是卡在那个尴尬的区间,怎么调都上不去。今天就来聊聊,从数据清洗到特征工程,到底有哪些关键点容易被忽略,导致你的模型怎么训练都不达标。
一、数据质量:垃圾进,垃圾出
很多新手一开始就急着搭模型、调参数,但回头一看,数据本身就有问题。
标注不一致是最致命的
你花一周时间标了5000条数据,然后交给模型训练,结果F1值死活上不去。先去检查一遍标注规则——同一个实体在不同标注员手里是不是被标成了不同的类型?
# 检查标注一致性的代码示例
import pandas as pd
from collections import Counter
# 假设你有多个标注员的数据
data = pd.read_csv('annotations.csv')
# 对同一批文本,检查不同标注员的实体标注是否一致
conflicts = []
for text in data['text'].unique():
annotations = data[data['text'] == text]
entities = [tuple(row['entity'], row['label']) for _, row in annotations.iterrows()]
if len(set(entities)) > 1: # 不同标注结果
conflicts.append(text)
print(f"存在标注冲突的文本数:{len(conflicts)}")
实体边界模糊
比如”北京大学”,有人标成ORG,有人标成LOC。”特朗普访问中国”里的”中国”,该标成LOC还是GPE?这些边界问题不解决,模型永远学不会。
解决方案:制定明确的标注指南,给每个实体类型配上正例和反例。最好能让标注员之间互相review,减少主观偏差。
二、特征工程:别让模型自己猜
好的特征能让模型事半功倍。
词性标注(POS)是关键信号
命名实体往往有特定的词性模式。人名后面大概率跟动词,地名后面跟”省”“市”等后缀。把POS特征加进去,模型识别准确率能提升不少。
import jieba
import jieba.posseg as pseg
def extract_pos_features(text):
"""提取词性特征"""
words = pseg.cut(text)
pos_tags = [word.flag for word in words]
# 计算词性分布特征
features = {
'noun_ratio': pos_tags.count('n') / len(pos_tags),
'verb_ratio': pos_tags.count('v') / len(pos_tags),
'has_person_title': any(tag in ['nr', 'ns', 'nt'] for word, tag in words),
'has_location': any(tag in ['ns', 'f'] for word, tag in words),
}
return features
上下文窗口不能太小
很多人用BiLSTM+CRF做NER,但上下文窗口只取前后3个字。对于长距离依赖的实体(比如”中华人民共和国”),这点上下文根本不够。
建议把上下文窗口扩大到10-15个字,或者用Transformer架构,让模型自己学习长距离依赖。
三、类别不平衡:别被准确率骗了
数据集里”LOC”(地名)可能有10000个样本,”PER”(人名)只有500个。模型训练完一看,整体准确率95%,但人名识别率才40%。
损失函数加权
import torch
import torch.nn as nn
class FocalLoss(nn.Module):
"""处理类别不平衡的Focal Loss"""
def __init__(self, gamma=2.0, alpha=None):
super().__init__()
self.gamma = gamma
self.alpha = alpha
def forward(self, inputs, targets):
ce_loss = nn.CrossEntropyLoss(reduction='none')(inputs, targets)
pt = torch.exp(-ce_loss)
focal_loss = self.alpha * (1 - pt) ** self.gamma * ce_loss
return focal_loss.mean()
数据增强
对于少样本类别,可以做同义词替换、回译等数据增强。比如”张三”和”李四”都是人名,训练时互换一下,能让模型学到更鲁棒的特征。
from transformers import pipeline
def augment_person_names(text, model_name="bert-base-chinese"):
"""对人名进行增强"""
ner_pipeline = pipeline("ner", model=model_name)
entities = ner_pipeline(text)
# 找到所有人名实体
person_indices = [i for i, e in enumerate(entities) if e['entity'].startswith('B-PER')]
if person_indices:
# 简单增强:替换人名(实际项目中应该用更聪明的方法)
augmented_texts = [text]
for idx in person_indices[:2]: # 最多替换前两个
new_text = text[:entities[idx]['start']] + "某某" + text[entities[idx]['end']:]
augmented_texts.append(new_text)
return augmented_texts
return [text]
四、模型架构:别只看效果,要看问题
CRF层的作用被低估
BiLSTM+CRF这个组合用了十几年,为什么还能用?因为CRF层学习了标签之间的转移规则。比如”PER”后面不太可能跟”LOC”,”B-ORG”后面应该是”I-ORG”。
如果你的任务里实体类型有特殊约束,一定要加CRF层。
预训练模型的陷阱
很多人直接用BERT做NER,结果发现效果不如预期。问题出在哪?
- 微调时学习率太大:预训练模型的知识被冲掉了。建议用1e-5到5e-5的小学习率微调。
- 没做domain adaptation:通用BERT在医疗、法律等专业领域效果不好。先用领域语料做预训练,再微调。
from transformers import BertForTokenClassification, Trainer, TrainingArguments
model = BertForTokenClassification.from_pretrained(
'bert-base-chinese',
num_labels=len(label2id) + 1 # 加上PAD标签
)
training_args = TrainingArguments(
output_dir='./results',
learning_rate=2e-5, # 小学习率
per_device_train_batch_size=16,
num_train_epochs=3,
weight_decay=0.01,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
)
trainer.train()
五、评估指标:别只看准确率
用F1而不是Accuracy
NER是序列标注任务,准确率会骗人。比如一个序列90%的token都是O(非实体),模型全预测O,准确率也能有90%。
一定要用Token-level F1和Entity-level F1。前者看单个token的预测质量,后者看整个实体是否完整识别出来。
from seqeval.metrics import classification_report, f1_score
# 真实标签
y_true = [['O', 'O', 'B-PER', 'I-PER', 'O']]
# 预测标签
y_pred = [['O', 'O', 'B-PER', 'O', 'O']] # 预测断了,缺少I-PER
print(classification_report(y_true, y_pred))
输出结果会告诉你:精确率、召回率、F1值分别多少,哪个类别出了问题。
错误分析比调参更重要
别急着换模型,先看看错在哪。是实体边界标错了?还是类别分错了?是数据问题还是模型问题?
可以把错误样本拿出来手动分析,常见的问题类型:
- 实体嵌套:”北京大学”里既有人名”张三”,又有地名”北京大学”,模型分不清
- 跨句实体:”张三去北京了”,”北京”被拆成两个词,实体识别时漏掉了
- 歧义实体:”招商银行”是机构名还是银行名?
总结
NNP准确率上不去,别只盯着模型结构看。先从数据入手,检查标注质量;然后看特征有没有充分表达;再看类别是否平衡;最后用正确的指标评估。
记住,好模型是跑出来的,不是调出来的。与其花一周调超参,不如花两天整理数据。数据好了,模型自然就上去了。
如果你的数据已经洗得很干净了,F1还是卡在某个瓶颈,那可能是特征不够,或者模型 capacity 不足。这时候再考虑换架构、加预训练模型、做数据增强。
有问题随时交流,一起把NER做得更准。