命名实体识别


文档摘要

命名实体识别 本节摘要:把名字抠出来。听着容易,直到你碰上歧义边界、嵌套实体和领域行话。"Apple sued Google over its iPhone search deal in the US."——五个实体:Apple(ORG)、Google(ORG)、iPhone(PRODUCT)、search deal(可能算)、US(GPE)。好的 NER 系统全抽对类型,差的漏掉 iPhone、把水果 Apple 和公司 Apple 搞混、还把 US 标成 PERSON。NER 是每个结构化抽取流水线底下的役马:简历解析、合规日志扫描、病历匿名、搜索查询理解、聊天机器人回答落地、法律合同抽取。你从来看不见它,却总依赖它。

命名实体识别

本节摘要:把名字抠出来。听着容易,直到你碰上歧义边界、嵌套实体和领域行话。"Apple sued Google over its iPhone search deal in the US."——五个实体:Apple(ORG)、Google(ORG)、iPhone(PRODUCT)、search deal(可能算)、US(GPE)。好的 NER 系统全抽对类型,差的漏掉 iPhone、把水果 Apple 和公司 Apple 搞混、还把 US 标成 PERSON。NER 是每个结构化抽取流水线底下的役马:简历解析、合规日志扫描、病历匿名、搜索查询理解、聊天机器人回答落地、法律合同抽取。你从来看不见它,却总依赖它。本节走完从规则、HMM、CRF 到 BiLSTM-CRF 再到 Transformer 的演进,每一步都在修上一步的具体局限——这条演进路径本身就是教训。

对应原课程:Phase 5 · Lesson 06 · named-entity-recognition(原英文 phases/05-nlp-foundations-to-advanced/06-named-entity-recognition/docs/en.md)。前置依赖:第 02 节(词袋与 TF-IDF)、第 03 节(词嵌入)。

学习目标

阅读完本节,你应当能够:

  1. 说清 BIO/BILOU 标注如何把实体抽取转成序列标注,以及架构从规则到 Transformer 的演进逻辑。
  2. 从零实现 span↔BIO 互转、手工特征与词形、规则+地名词典基线。
  3. sklearn-crfsuite 训 CRF、spaCy 与 Hugging Face 跑生产级 NER,并知道 2026 年 LLM 零样本 NER 的位置。
  4. 识别 NER 的领域漂移、嵌套实体、长实体、稀疏类型四大失败,以及古典 NER 仍胜出的场景。

一、问题与直觉

"Apple sued Google over its iPhone search deal in the US." 五个实体:Apple(ORG)、Google(ORG)、iPhone(PRODUCT)、search deal(可能)、US(GPE)。好的 NER 全抽对类型,差的漏掉 iPhone、把水果 Apple 当公司 Apple、还把 US 标成 PERSON。

NER 是每个结构化抽取流水线底下的役马。简历解析、合规日志扫描、病历匿名、搜索查询理解、聊天机器人回答落地、法律合同抽取。你从来看不见它,却总依赖它。

BIO 标注(或 BILOU)把实体抽取变成序列标注问题。给每个 token 打标签:B-TYPE(实体开头)、I-TYPE(实体内部)、O(任何实体之外)。

Apple B-ORG sued O Google B-ORG over O its O iPhone B-PRODUCT search O deal O in O the O US B-GPE . O

多 token 实体链起来:New B-GPEYork I-GPECity I-GPE。懂 BIO 的模型能抽任意长度的 span。

架构演进,每一步修上一步的局限:

  • 规则式:正则 + 地名词典查找。已知实体上精确率高,新实体零覆盖。
  • HMM:隐马尔可夫模型。给定标签的发射概率、标签间转移概率,维特比解码。在标注数据上训练。
  • CRF:条件随机场。像 HMM 但判别式,能混合任意特征(词形、大小写、邻近词)。2026 年低资源部署仍是古典生产役马。
  • BiLSTM-CRF:用学到的神经特征替代手工特征。LSTM 双向读句,顶上 CRF 层强制标签序列一致。
  • Transformer 式:BERT 加 token 分类头微调。精度最高,算力最贵。

二、从零实现

第 1 步:BIO 标注辅助函数

def spans_to_bio(tokens, spans): labels = ["O"] * len(tokens) for start, end, label in spans: labels[start] = f"B-{label}" for i in range(start + 1, end): labels[i] = f"I-{label}" return labels def bio_to_spans(tokens, labels): spans = [] current = None for i, label in enumerate(labels): if label.startswith("B-"): if current: spans.append(current) current = (i, i + 1, label[2:]) elif label.startswith("I-") and current and current[2] == label[2:]: current = (current[0], i + 1, current[2]) else: if current: spans.append(current) current = None if current: spans.append(current) return spans
>>> tokens = ["Apple", "sued", "Google", "over", "iPhone", "sales", "."] >>> labels = ["B-ORG", "O", "B-ORG", "O", "B-PRODUCT", "O", "O"] >>> bio_to_spans(tokens, labels) [(0, 1, 'ORG'), (2, 3, 'ORG'), (4, 5, 'PRODUCT')]

第 2 步:手工特征

古典(非神经)NER 里,特征是胜负手。有用的那些:

def token_features(token, prev_token, next_token): return { "lower": token.lower(), "is_upper": token.isupper(), "is_title": token.istitle(), "has_digit": any(c.isdigit() for c in token), "suffix_3": token[-3:].lower(), "shape": word_shape(token), "prev_lower": prev_token.lower() if prev_token else "<BOS>", "next_lower": next_token.lower() if next_token else "<EOS>", } def word_shape(word): out = [] for c in word: if c.isupper(): out.append("X") elif c.islower(): out.append("x") elif c.isdigit(): out.append("d") else: out.append(c) return "".join(out)

word_shape("iPhone") 返回 xXxxxx,word_shape("USA-2024") 返回 XXX-dddd。大小写模式对专有名词是高信号特征。

第 3 步:简单的规则 + 词典基线

ORG_GAZETTEER = {"Apple", "Google", "Microsoft", "OpenAI", "Meta", "Amazon", "Netflix"} GPE_GAZETTEER = {"US", "USA", "UK", "India", "Germany", "France"} PRODUCT_GAZETTEER = {"iPhone", "Android", "Windows", "ChatGPT", "Claude"} def rule_based_ner(tokens): labels = [] for token in tokens: if token in ORG_GAZETTEER: labels.append("B-ORG") elif token in GPE_GAZETTEER: labels.append("B-GPE") elif token in PRODUCT_GAZETTEER: labels.append("B-PRODUCT") else: labels.append("O") return labels

生产地名词典有数百万条目,从维基百科和 DBpedia 抓取。覆盖好,消歧(Apple 公司 vs 水果)糟透。这正是统计模型取胜的原因。

第 4 步:CRF 步骤(草图,非完整实现)

没有概率论基础,50 行从零写 CRF 不启明。改用 sklearn-crfsuite:

import sklearn_crfsuite def to_features(tokens): out = [] for i, tok in enumerate(tokens): prev = tokens[i - 1] if i > 0 else "" nxt = tokens[i + 1] if i + 1 < len(tokens) else "" out.append({ "word.lower()": tok.lower(), "word.isupper()": tok.isupper(), "word.istitle()": tok.istitle(), "word.isdigit()": tok.isdigit(), "word.suffix3": tok[-3:].lower(), "word.shape": word_shape(tok), "prev.word.lower()": prev.lower(), "next.word.lower()": nxt.lower(), "BOS": i == 0, "EOS": i == len(tokens) - 1, }) return out crf = sklearn_crfsuite.CRF(algorithm="lbfgs", c1=0.1, c2=0.1, max_iterations=100, all_possible_transitions=True) X_train = [to_features(s) for s in sentences_tokenized] crf.fit(X_train, bio_labels_train)

c1c2 是 L1、L2 正则。all_possible_transitions=True 让模型学到非法序列(如 O 后接 I-ORG)不太可能——这就是 CRF 在你不写约束的情况下强制 BIO 一致性的方式。

第 5 步:BiLSTM-CRF 增加了什么

特征变成学出来的。输入是 token 嵌入(GloVe 或 fastText),LSTM 从左到右、从右到左读,拼接的隐藏态过一个 CRF 输出层。CRF 仍强制标签序列一致,LSTM 则用学到的特征替代手工特征。

import torch import torch.nn as nn class BiLSTM_CRF_Head(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, n_labels): super().__init__() self.embed = nn.Embedding(vocab_size, embed_dim) self.lstm = nn.LSTM(embed_dim, hidden_dim, bidirectional=True, batch_first=True) self.fc = nn.Linear(hidden_dim * 2, n_labels) def forward(self, token_ids): e = self.embed(token_ids) h, _ = self.lstm(e) emissions = self.fc(h) return emissions

CRF 层用 torchcrf.CRF(pip install pytorch-crf)。相比手工特征 CRF,增益可测但比你预期的小——除非你有数万标注句。

三、框架对比

spaCy 开箱即用,提供生产级 NER。

import spacy nlp = spacy.load("en_core_web_sm") doc = nlp("Apple sued Google over its iPhone search deal in the US.") for ent in doc.ents: print(f"{ent.text:20s} {ent.label_}")
Apple ORG Google ORG iPhone ORG US GPE

注意 iPhone 被标成 ORG 而非 PRODUCT——spaCy 小模型对产品实体覆盖弱。大模型(en_core_web_lg)更好,Transformer 模型(en_core_web_trf)再好一截。

Hugging Face 跑基于 BERT 的 NER:

from transformers import pipeline ner = pipeline("ner", model="dslim/bert-base-NER", aggregation_strategy="simple") print(ner("Apple sued Google over its iPhone in the US."))
[{'entity_group': 'ORG', 'word': 'Apple', ...}, {'entity_group': 'ORG', 'word': 'Google', ...}, {'entity_group': 'MISC', 'word': 'iPhone', ...}, {'entity_group': 'LOC', 'word': 'US', ...}]

aggregation_strategy="simple" 把连续的 B-X、I-X token 合并成一个 span。不开它,你拿到 token 级标签,得自己合并。

基于 LLM 的 NER(2026 年的选项)

零样本与少样本 LLM NER 如今在许多领域已可与微调模型竞争,标注数据稀缺时更是碾压。

  • 零样本提示:给 LLM 一份实体类型列表和示例 schema,要求 JSON 输出。开箱即用,新领域上精度中等。
  • ZeroTuneBio 式提示:把任务拆成「候选抽取 → 含义解释 → 判断 → 复核」。多阶段提示(而非一次)在生物医学 NER 上大幅提精度。同样的模式适用于法律、金融、科学领域。
  • 带 RAG 的动态提示:为每次推理从一小批标注种子集里检索最相似的标注样本,动态拼少样本提示。2026 年基准上,这把 GPT-4 生物医学 NER 的 F1 比静态提示抬高 11~12%。
  • 按实体类型分解:长文档里,一次调用抽所有类型会随长度掉召回。每个类型跑一遍抽取,推理成本更高,精度也高得多。这是临床笔记和法律合同的标准模式。

💡 2026 年生产建议:在收集训练数据之前,先用 LLM 零样本打个基线。F1 往往好到你不需微调。

古典 NER 仍胜出的地方

即便有 LLM,古典 NER 在以下情况仍赢:

  • 延迟预算低于 50ms。
  • 有数千标注样本,需要 98%+ F1。
  • 领域本体稳定,预训练 CRF 或 BiLSTM 迁移得好。
  • 合规要求本地、非生成式模型。

它在哪里垮掉

  • 领域漂移:在 CoNLL 上训的 NER 放到法律合同上比地名词典还差。在你的领域上微调。
  • 嵌套实体:"Bank of America Tower" 同时是 ORG 和 FACILITY。标准 BIO 表达不了重叠 span,需要嵌套 NER(多遍或基于 span 的模型)。
  • 长实体:"United States Federal Deposit Insurance Corporation"。token 级模型有时会把它拆开,用 aggregation_strategy 或后处理。
  • 稀疏类型:医学 NER 标签如 DRUG_BRAND、ADVERSE_EVENT、DOSE,通用模型一无所知。ScispaCy 和 BioBERT 是那里的起点。

四、可复用产物

保存为 outputs/skill-ner-picker.md:

--- name: ner-picker description: Pick the right NER approach for a given extraction task. version: 1.0.0 phase: 5 lesson: 06 tags: [nlp, ner, extraction] --- Given a task description (domain, label set, language, latency, data volume), output: 1. Approach. Rule-based + gazetteer, CRF, BiLSTM-CRF, or transformer fine-tune. 2. Starting model. Name it (spaCy model ID, Hugging Face checkpoint ID, or "custom, trained from scratch"). 3. Labeling strategy. BIO, BILOU, or span-based. Justify in one sentence. 4. Evaluation. Use `seqeval`. Always report entity-level F1 (not token-level). Refuse to recommend fine-tuning a transformer for under 500 labeled examples unless the user already has a pretrained domain model. Flag nested entities as needing span-based or multi-pass models. Require a gazetteer audit if the user mentions "production scale" and labels are unchanged from CoNLL-2003.

五、练习

  1. 基础:实现 bio_to_spans(spans_to_bio 的逆),在 10 个句子上验证往返一致。
  2. 进阶:在 CoNLL-2003 英文 NER 数据集上训上面的 sklearn-crfsuite CRF,用 seqeval 报每实体 F1。典型结果约 84 F1。
  3. 挑战:在领域特定 NER 数据集(医学、法律或金融)上微调 distilbert-base-cased,对比 spaCy 小模型。记录数据泄漏检查,写下让你意外的发现。

本节要点回顾

  1. NER 是结构化抽取的役马:简历、合规、病历、搜索、合同,看不见却总依赖。
  2. BIO 标注转序列标注:B-X 开头、I-X 内部、O 外,链起多 token 实体。
  3. 架构演进即修局限:规则(零新覆盖)→ HMM → CRF(判别式+任意特征)→ BiLSTM-CRF(学特征)→ Transformer(精度最高最贵)。
  4. 手工特征是古典 NER 胜负手:词形(xXxxxx)、大小写、邻近词、后缀。
  5. 规则+词典覆盖好、消歧糟,这正是统计模型取胜的原因。
  6. CRF 用 all_possible_transitions 学非法序列不太可能,无需手写 BIO 约束。
  7. spaCy 开箱即用,小模型产品实体弱;Hugging Face aggregation_strategy 合并 span。
  8. 2026 年先打 LLM 零样本基线:生物医学上 RAG 动态提示抬 11~12% F1,长文档按类型分解。
  9. 古典仍赢:延迟<50ms、数千样本要 98%+、本体稳定、合规要求本地非生成。
  10. 四大失败:领域漂移、嵌套实体(BIO 表达不了)、长实体被拆、稀疏类型(用 ScispaCy/BioBERT)。

下一节,我们从「抠实体」退一步看「标词性、画句法树」——进入「词性标注与句法分析」。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U