4. 命名实体识别(NER)基础:从规则到深度学习
命名实体识别,说白了就是让机器从文本里把人名、地名、机构名、时间这些关键信息给揪出来。在金融领域,这活儿尤其重要——你想想看,一份财报里藏着多少公司名、产品名、高管名字?要是靠人工去标,那得累死。
我个人习惯把NER的发展分成三个阶段:规则驱动、统计学习、深度学习。这三个阶段不是替代关系,而是互补的。我在项目中遇到过不少场景,最后是混着用的。
4.1 基于规则的方法:最朴素,但有时最管用
规则方法,就是人工写一堆模式匹配规则。比如「XX公司」、「XX银行」、「XX证券」这种固定模式,用正则表达式就能搞定。
import re
def rule_based_ner(text):
entities = []
# 匹配公司名模式
company_pattern = r'[\u4e00-\u9fa5]{2,}(?:公司|集团|银行|证券|基金)'
for match in re.finditer(company_pattern, text):
entities.append({
'text': match.group(),
'type': 'ORG',
'start': match.start(),
'end': match.end()
})
# 匹配金额模式
money_pattern = r'\d+(?:\.\d+)?[万亿]?元'
for match in re.finditer(money_pattern, text):
entities.append({
'text': match.group(),
'type': 'MONEY',
'start': match.start(),
'end': match.end()
})
return entities
规则方法的优点很明显:不需要标注数据,解释性强。缺点也致命:维护成本高,遇到新实体就得加新规则。说白了,规则是死的,语言是活的。
4.2 基于统计的方法:CRF,让模型自己学规律
CRF(条件随机场)是统计方法的代表。它不写死规则,而是从标注数据里学「转移特征」和「状态特征」。比如「B-ORG」后面大概率跟「I-ORG」,不太可能直接跳到「B-PER」。
为什么会这样?因为CRF本质上是在建模标签之间的依赖关系。你想想看,一个实体内部标签是连续的,跨实体之间标签会跳变,CRF就是学这个规律。
# 使用sklearn-crfsuite实现
import sklearn_crfsuite
def word2features(sent, i):
word = sent[i][0]
features = {
'word': word,
'word.isdigit': word.isdigit(),
'word.isupper': word.isupper(),
'word[-3:]': word[-3:],
'word[-2:]': word[-2:],
'word[:2]': word[:2],
}
# 上下文特征
if i > 0:
features['prev_word'] = sent[i-1][0]
if i < len(sent)-1:
features['next_word'] = sent[i+1][0]
return features
# 训练CRF模型
crf = sklearn_crfsuite.CRF(
algorithm='lbfgs',
c1=0.1, # L1正则化
c2=0.1, # L2正则化
max_iterations=100
)
crf.fit(X_train, y_train)
CRF的优点在于:对序列标注任务天然适配,能利用全局信息做决策。缺点也很明显:特征工程太依赖人工经验,而且训练速度慢,数据量大了就跑不动。
4.3 基于深度学习的方法:BiLSTM-CRF,端到端的革命
深度学习的出现,让NER从「手工特征」进化到了「自动特征」。BiLSTM-CRF是经典组合——BiLSTM负责提取上下文特征,CRF负责解码标签序列。
说白了,BiLSTM就是双向看上下文。比如「工商银行」这个词,从左往右看「工商」是前缀,「银行」是核心;从右往左看「银行」确认了机构属性。双向信息一结合,准确率就上去了。
import torch
import torch.nn as nn
class BiLSTM_CRF(nn.Module):
def __init__(self, vocab_size, tag_to_ix, embedding_dim, hidden_dim):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embedding_dim)
self.lstm = nn.LSTM(embedding_dim, hidden_dim // 2,
num_layers=2, bidirectional=True)
self.hidden2tag = nn.Linear(hidden_dim, len(tag_to_ix))
self.crf = CRF(len(tag_to_ix), batch_first=True)
def forward(self, sentence, tags=None):
embeds = self.embedding(sentence)
lstm_out, _ = self.lstm(embeds)
emissions = self.hidden2tag(lstm_out)
if tags is not None:
loss = -self.crf(emissions, tags)
return loss
else:
return self.crf.decode(emissions)
核心要点:BiLSTM-CRF相比纯CRF,最大的优势是不用手工设计特征。词向量(如Word2Vec、GloVe)已经包含了语义信息,模型自己就能学到「银行」和「证券」在语义空间里是相近的。
我在项目中实测过,BiLSTM-CRF在金融NER任务上,F1值比传统CRF高出5-8个百分点。但代价是:需要大量标注数据,而且训练时间翻了好几倍。
4.4 BERT时代:预训练+微调,NER的终极形态?
BERT的出现,让NER又上了一个台阶。它用海量无标注文本做预训练,学到的语言表示比词向量丰富得多。你只需要在BERT后面接一个CRF层,微调一下就能用。
嗯,这里要注意:BERT不是万能的。我遇到过的情况是,BERT对长文本处理吃力,而且模型太大,推理速度慢。在实时性要求高的场景里,反而BiLSTM-CRF更实用。
from transformers import BertTokenizer, BertForTokenClassification
import torch
# 加载预训练模型
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertForTokenClassification.from_pretrained(
'bert-base-chinese',
num_labels=len(label_list)
)
# 推理示例
text = "阿里巴巴集团在杭州召开股东大会"
inputs = tokenizer(text, return_tensors="pt")
outputs = model(**inputs)
predictions = torch.argmax(outputs.logits, dim=2)
- 标注数据少(<1000条):用规则+CRF
- 标注数据中等(1000-10000条):用BiLSTM-CRF
- 标注数据充足(>10000条):用BERT-CRF
- 对实时性要求高:用BiLSTM-CRF或蒸馏后的BERT
4.5 四种方法对比:没有银弹
| 方法 | 准确率 | 标注数据需求 | 训练速度 | 推理速度 | 可解释性 |
|---|---|---|---|---|---|
| 规则方法 | 中(特定场景高) | 无 | 无 | 极快 | 极高 |
| CRF | 中高 | 中等 | 慢 | 快 | 高 |
| BiLSTM-CRF | 高 | 多 | 较慢 | 中等 | 低 |
| BERT-CRF | 极高 | 很多 | 很慢 | 慢 | 极低 |
你看这个表格就明白了:没有哪个方法能通吃所有场景。我个人的经验是,先评估你的数据量和实时性要求,再决定用哪种。别一上来就上BERT,杀鸡用牛刀。
4.6 本章知识体系
下面这张图,是我梳理的NER方法演进路线。从规则到深度学习,每一步都有它的适用场景。
最后说一句:别迷信某个方法。我在金融项目里,经常是规则方法打底,CRF做兜底,深度学习模型做主力。三层架构,各司其职,效果反而最好。
公众号:蓝海资料掘金营,微信deep3321