命名实体识别(NER):从规则到深度学习的进化之路

命名实体识别,简称 NER,是信息抽取里最基础也最关键的环节。说白了,就是从文本里把人名、地名、机构名、时间、数字这些「实体」给揪出来。我刚开始做知识图谱的时候,花在 NER 上的时间占了整个项目一半还多。

这节课我们就聊聊 NER 的三种主流实现方式。每种方式各有长短,实际项目中怎么选,看场景说话。

命名实体识别(NER)方法体系 命名实体识别 基于规则的方法 基于统计的方法 基于深度学习的方法 正则表达式 词典匹配 隐马尔可夫 HMM 条件随机场 CRF BiLSTM-CRF BERT + 微调 选型建议 规则 → 小规模、领域固定、格式规范的文本 统计 → 中等规模、带标注数据、对可解释性有要求 深度 → 大规模、多领域、标注数据充足或能用预训练

基于规则的方法:简单直接但脆皮

规则方法是最早的 NER 实现方式。核心思路就是人工编写匹配模式。你想想看,这不就是写一堆正则表达式和词典吗?对,但也没那么简单。

我个人习惯用这种方式做快速原型验证。比如从学术文献里抽作者名,规则就很好使。

# 一个简单的基于规则的NER示例
import re

def extract_entities(text):
    entities = []
    # 中文人名模式
    name_pattern = r'[\u4e00-\u9fa5]{2,4}(?:教授|博士|院士|研究员)'
    # 机构名模式
    org_pattern = r'(?:中国科学院|清华大学|北京大学|复旦大学)[\u4e00-\u9fa5]{0,10}(?:研究所|学院|实验室|中心)'
    # 时间表达式
    time_pattern = r'\d{4}年\d{1,2}月\d{1,2}日'
    
    for match in re.finditer(name_pattern, text):
        entities.append({
            'text': match.group(),
            'type': 'PERSON',
            'start': match.start(),
            'end': match.end()
        })
    
    for match in re.finditer(org_pattern, text):
        entities.append({
            'text': match.group(),
            'type': 'ORGANIZATION',
            'start': match.start(),
            'end': match.end()
        })
    
    return entities

text = "中国科学院张伟教授于2024年3月15日在清华大学发表演讲"
entities = extract_entities(text)
for e in entities:
    print(f"{e['text']} -> {e['type']}")

但是,规则方法有个致命问题——脆弱。我在项目中遇到过,换个领域的文本,规则就全废了。比如医学文献里「发热」是症状实体,放到金融新闻里就是噪音。

我曾经踩过一个大坑:给一个生物医学项目写了200多条正则规则,覆盖了80%的情况。结果来了一批新论文,语料里多了很多拉丁文术语,规则匹配率直接掉到30%以下。从那以后我学乖了,规则只做兜底或者后处理。

规则方法优缺点很鲜明:

  • 优点:实现快、可解释性强、不需要标注数据
  • 缺点:覆盖有限、维护成本高、跨领域能力差

基于统计的方法:数据驱动的进化

统计方法把 NER 看成一个序列标注问题。说白了,就是给每个字或词打一个标签,比如 B-PER(人名开始)、I-PER(人名中间)、O(非实体)。

这里有几种经典模型:

模型 核心思想 典型应用
隐马尔可夫模型 (HMM) 假设状态依赖于前一状态,观测独立 早期 NLP 工具,任务单一
最大熵马尔可夫模型 (MEMM) 用最大熵建模状态转移,克服独立性假设 信息抽取,对局部特征敏感
条件随机场 (CRF) 全局归一化,考虑整个序列的依赖关系 经典 SOTA,至今仍用于

我个人比较推荐 CRF。为什么?它不像 HMM 那样假设观测独立,也不像 MEMM 那样存在「标注偏置」问题。CRF 是在整个序列上做条件概率建模,能学到标签之间的约束关系。比如「I-PER」不能直接跟在「O」后面,这种规则 CRF 自动就会学到。

实际项目中,CRF 的特征工程很关键。我们通常会组合多种特征:当前词、前后词、词性、前缀后缀、是否命中等。特征质量直接影响 F1 值,差别能有 5-10 个点。
# CRF 特征模板示例
def word2features(sent, i):
    word = sent[i][0]
    features = {
        'bias': 1.0,
        'word.lower()': word.lower(),
        'word[-3:]': word[-3:],
        'word[-2:]': word[-2:],
        'word.isupper()': word.isupper(),
        'word.istitle()': word.istitle(),
        'word.isdigit()': word.isdigit(),
    }
    if i > 0:
        word1 = sent[i-1][0]
        features.update({
            '-1:word.lower()': word1.lower(),
            '-1:word.istitle()': word1.istitle(),
        })
    else:
        features['BOS'] = True
    if i < len(sent)-1:
        word1 = sent[i+1][0]
        features.update({
            '+1:word.lower()': word1.lower(),
            '+1:word.istitle()': word1.istitle(),
        })
    else:
        features['EOS'] = True
    return features

基于深度学习的方法:端到端的革命

深度学习的出现,把 NER 的准确率又推了一个台阶。核心变化是——不再需要手写特征了。模型自己从原始文本里学语义表示。

主流架构有这么几个:

  1. BiLSTM-CRF:双向 LSTM 编码上下文,CRF 层做解码。这是 2015-2019 年的标配。
  2. BERT + 全连接层:预训练模型提取特征,一个线性层做分类。简单粗暴。
  3. BERT + CRF:目前生产中常见的选择,用 BERT 替代 BiLSTM 做编码。

我建议刚入门的朋友先跑一遍 BiLSTM-CRF。代码量适中,能理解序列标注的整个 pipeline。之后再过渡到 BERT 微调。

# PyTorch 风格的 BiLSTM-CRF 核心结构
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=1, bidirectional=True, batch_first=True)
        self.hidden2tag = nn.Linear(hidden_dim, len(tag_to_ix))
        self.crf = CRF(len(tag_to_ix), batch_first=True)
    
    def forward(self, x, tags=None, mask=None):
        embeds = self.embedding(x)
        lstm_out, _ = self.lstm(embeds)
        emissions = self.hidden2tag(lstm_out)
        if tags is not None:
            loss = -self.crf(emissions, tags, mask=mask)
            return loss
        else:
            return self.crf.decode(emissions, mask=mask)

这里要注意,BERT 虽然强,但也不是万能的。我做过对比实验:在相同标注数据(比如 1 万条)下,BERT 微调比 BiLSTM-CRF 高 3-5 个 F1 点。但如果数据只有几百条,BiLSTM-CRF 反而不输 BERT。

我曾经遇到一个情况:客户要求对 50 万篇专利文献做实体抽取,技术关键词很专业。一开始用 BERT 微调,效果不错但推理速度太慢,一天只能跑 2 万篇。后来改用 BiLSTM-CRF,速度提升 5 倍,F1 只降了 0.8%。最后线上用的是 BiLSTM-CRF + 规则后处理。所以你看,实际工程不是选最强的,而是选最合适的。

三种方法怎么选?

给你一个决策框架,纯属个人经验:

  • 数据规模 < 500 条:规则方法打底,配合少量正则做兜底
  • 数据 500 - 5000 条:CRF 或 BiLSTM-CRF,特征工程要花心思
  • 数据 > 5000 条:大胆上 BERT 微调,注意推理效率

还有一种混合打法:用深度学习抽候选实体,用规则做合法性校验。我在金融文档抽取项目里就是这么干的,实体准确率从 92% 升到了 97%。说白了,深度模型解决召回,规则解决精度。