自然语言处理应用篇 · 第4章

词性标注与命名实体识别(NER)

在词语层面,给每个词打上词性;在短语层面,从句子里找出人名、地名、机构名——这是信息抽取与知识图谱的基石。

🎯 学习目标

  • 理解词性标注(POS)的任务定义与实际用途
  • 认识常见中文标注集,熟练使用 jieba.posseg
  • 理解命名实体识别(NER)与实体类别
  • 理解 BIO/BIOES 序列标注体系与 NER 的技术路线
  • 会用 spaCy / LAC / HanLP 进行中文实体抽取
  • 能完成"从新闻文本中抽取人名、地名、机构名"的实战

1词性标注是什么

词性标注(Part-of-Speech Tagging,POS Tagging):给分词后每个词标注语法类别(名词、动词、形容词……)。

这/d 座/q 城市/n 建设/v 得/u 很/d 美丽/a
(d=副词 q=量词 n=名词 v=动词 u=助词 a=形容词)

难点在于兼类词:同一个词在不同语境词性不同。"花"可以是名词(一朵花)、动词(花钱)、形容词(花哨);"在"可以是动词(他在家)、介词(在家看书)、副词(在学习)。

POS 的典型用途:① 信息抽取时只保留名词/动词等实词;② 帮助分词与句法分析消歧;③ 文本-to-语音中多音字判断("行长/cháng/zhǎng");④ 知识图谱、问答系统的特征。

2中文主流标注集

不同工具用不同标注规范,读文档时要先确认是哪一套:

标注集使用方特点
北大标注集(ICTCLAS)jieba、HanLP 默认约 40~99 个细粒度标记,nr/ns/nt 专名细分
宾州中文树库 CTBLTP、学术语料NN/NR/NT/VV/VA 等英文缩写
Universal POSspaCy、UD 通用17 个跨语言统一标记(NOUN/VERB/PROPN…)

通用词性(UPOS)17 类速记:NOUN 名词、PROPN 专有名词、VERB 动词、ADJ 形容词、ADV 副词、NUM 数词、PRON 代词、ADP 介词、CONJ 连词、DET 限定词、PART 助词、PUNCT 标点、X 其他。

3jieba.posseg 实战

import jieba
import jieba.posseg as pseg

# 基本用法:得到 (词, 词性) 对
for w, f in pseg.cut("雷军在小米汽车工厂发布了新款SU7"):
    print(f"{w}/{f}", end=" ")
# 雷军/nr 在/p 小米/nz 汽车/n 工厂/n 发布/v 了/u 新款/n SU7/eng

# 直接转列表
pairs = [(w.word, w.flag) for w in pseg.cut("他/r 喜欢/v 旅游/vn")]

# 自定义词典时可以指定词性:jieba.add_word(词, freq, 词性)
jieba.add_word("小米汽车", tag="nt")       # 标注为机构团体
jieba.add_word("SU7", tag="nz")
for w, f in pseg.cut("雷军在小米汽车发布了新款SU7"):
    print(f"{w}-{f}", end=" ")

按词性过滤的实用模式

text = "百度公司在上海建设新的人工智能研发中心,张教授对此发表了评论"

# 只取实体性较强的名词类(n/nr人名/ns地名/nt机构/nz其他专名)
entities_like = [w for w, f in pseg.cut(text)
                 if f in ("n", "nr", "ns", "nt", "nz", "eng")]
print(entities_like)
# ['百度', '公司', '上海', '人工智能', '研发', '中心', '张', '教授', '评论']

# 只取动词(分析"用户在说什么动作/诉求")
verbs = [w for w, f in pseg.cut(text) if f == "v"]
print(verbs)     # ['建设', '发表']

# 批量处理文档
def pos_tag_many(docs):
    return [[(w, f) for w, f in pseg.cut(d)] for d in docs]

jieba 的词性标注基于 HMM,准确率中等(85%~90% 上下,随领域变化),够用于特征过滤、粗分析;科研/高精度场景建议用 HanLP、LAC 或 BERT 类模型。

4命名实体识别(NER)

命名实体识别(Named Entity Recognition,NER):从文本中识别出具有特定意义的实体边界与类型。三大经典类(实体名三分):

  • PER(人名):张三、马斯克
  • LOC(地名):北京、浦东新区
  • ORG(机构名):华为技术有限公司、北京大学

垂直领域还有:时间、日期、货币、产品型号、疾病/药物(医疗 NER)、法律条款、股票代码等。NER = 边界检测 + 类型分类,比分词更难,因为实体长度不定、嵌套常见("北京大学"里"北京"也是地名)、新词极多。

BIO / BIOES 标注体系

NER 被建模为序列标注:给每个字一个标签。最常见的 BIO:

句子:马 化 腾 在 深 圳 创 办 了 腾 讯
BIO: B-PER I-PER E-PER O B-LOC I-LOC O O O B-ORG I-ORG
(这里用 BIOES 更细:B=实体首字 I=内部 E=末字 S=单字实体 O=非实体)

B-PER:人名开始(begin)
I-PER:人名内部(inside)
E-PER:人名结束(end)   ← BIOES 比 BIO 多 E 和 S
S-ORG:机构名单字(单字实体)
O:与实体无关(outside)

技术演进:规则+词典 → HMM/CRF(特征工程)→ BILSTM-CRF → BERT-CRF / LLM(当前主流,F1 可达 95%+ 通用领域)。

5方法一:规则 + 词性 + 词典抽取

没有训练条件时,"词性模式 + 地名词典 + 正则"可以解决不少封闭场景:

import jieba.posseg as pseg

text = "5月10日,王芳在北京会见了阿里巴巴集团的马云,双方谈到杭州总部扩建。"

# ① 词性线索:nr 人名、ns 地名、nt 机构名
for w, f in pseg.cut(text):
    if f in ("nr", "ns", "nt"):
        kind = {"nr": "人名", "ns": "地名", "nt": "机构名"}[f]
        print(kind, w)

# ② 词典匹配(地名/机构名单维护在文件中)
city_file = {"北京", "上海", "杭州", "深圳", "广州"}
found = [w for w in jieba.lcut(text) if w in city_file]
print("命中城市:", found)

# ③ 正则提取数字/时间类实体
import re
print(re.findall(r"\d{1,2}月\d{1,2}日", text))      # ['5月10日']
print(re.findall(r"\d+(?:\.\d+)?亿?元", "投资额达3.5亿元"))  # ['3.5亿元']

模式抽取套路:"动词 + nr/nt"(收购了 OOO、会见了 XXX)、"位于 ns"、地名词典后位词(省/市/路/大厦)、机构后缀词典(公司/集团/大学/医院/局/院)。规则法可解释、零样本,缺点是召回有限、维护成本高。

6方法二:spaCy 工业级 NER

pip install spacy
python -m spacy download zh_core_web_sm
import spacy

nlp = spacy.load("zh_core_web_sm")   # 加载中文小模型
text = "华为技术有限公司总部位于深圳,创始人任正非出席了在北京举行的发布会。"
doc = nlp(text)

# 一行代码拿到实体:文本、类型、起止位置
for ent in doc.ents:
    print(ent.text, "|", ent.label_, "|", ent.start_char, ent.end_char)
# 华为技术有限公司 | ORG | 0 8
# 深圳 | GPE | 12 14(GPE:地理政治实体,城市/国家)
# 任正非 | PERSON | 19 22
# 北京 | GPE | 28 30

# 词性、依存关系也都有
for token in doc[:6]:
    print(token.text, token.pos_, token.dep_)

# 英文模型 en_core_web_sm 的实体类型更丰富:
# PERSON人 / GPE城市国家 / ORG机构 / DATE日期 / MONEY钱 / PRODUCT产品

小模型(sm)在中文上能力有限,复杂文本可换 zh_core_web_trf(Transformer 版,更准更大)。spaCy 的优势是工程化流水线:分句→词性→NER→依存一步到位,易于批量处理与定制规则。

7方法三:LAC / HanLP(中文模型)

# ===== 百度 LAC:词法分析(分词+词性+专名一体)=====
# pip install lac
from LAC import LAC
lac = LAC(mode="lac")          # 首次运行自动下载模型

# 单句输入:返回 (词列表, 标签列表) 两个等长列表
words, tags = lac.run("李四在北京大学学习计算机,腾讯总部在深圳")
for w, t in zip(words, tags):
    if t in ("PER", "LOC", "ORG", "TIME"):
        print(t, w)
# PER 李四
# ORG 北京大学
# ORG 腾讯
# LOC 深圳

# 批量输入:传入列表,返回 (多句的词列表, 多句的标签列表)
# batch_words, batch_tags = lac.run(["句子1", "句子2"])
# LAC 标签:PER人名 LOC地名 ORG机构名 TIME时间
# ===== HanLP 2.x(功能最全,选学)=====
# pip install hanlp
# import hanlp
# tagger = hanlp.load(hanlp.pretrained.ner.MSRA_NER_BERT_BASE_ZH)
# print(tagger([["李四", "在", "北京", "大学", "学习"]]))
# HanLP 支持分词、POS、NER、依存句法、语义角色等一整套流水线

8综合案例:新闻实体三元组抽取

import spacy
from collections import defaultdict

nlp = spacy.load("zh_core_web_sm")

news_list = [
    "字节跳动公司在北京和上海设立了新的研发中心。",
    "张三从上海乘高铁抵达北京,参观了清华大学。",
    "阿里巴巴集团宣布在杭州成立达摩院,李四担任负责人。",
]

bag = defaultdict(set)        # 类型 → 实体集合(自动去重)
for news in news_list:
    doc = nlp(news)
    for ent in doc.ents:
        label = {"PERSON": "人名", "ORG": "机构", "GPE": "地名",
                 "LOC": "地名"}.get(ent.label_, ent.label_)
        bag[label].add(ent.text)

for k, v in bag.items():
    print(f"{k}:{sorted(v)}")
# 地名:['上海', '北京', '杭州']
# 机构:['字节跳动公司', '研发中心', '清华大学', '阿里巴巴集团', '达摩院']
# 人名:['张三', '李四', '负责人'](小模型噪声,换 trf 模型可改善)

从实体到知识:关系抽取入门

# 最简单的关系模式:"ORG 位于 LOC"
doc = nlp("小米集团总部位于北京市海淀区。")
orgs = [e.text for e in doc.ents if e.label_ == "ORG"]
places = [e.text for e in doc.ents if e.label_ in ("GPE", "LOC")]
for o in orgs:
    for p in places:
        print((o, "总部位于", p))
# ('小米集团', '总部位于', '北京市海淀区') → 这就是知识图谱的一条三元组
# 成熟的关系抽取用模板/监督模型/大模型,第8章项目篇会再实践

9NER 效果评估要点

  • 仍然用 P / R / F1,但必须边界 + 类型都正确才算命中。
  • "北京大学"只识别出"北京"算错;类型把 ORG 标成 LOC 也算错。
  • 工程上关注:实体级 F1(常用 seqeval 库按块计算)、各类型分别的 F1(人名通常最高,机构名最难)、跨领域迁移效果、速度。
# pip install seqeval(标签序列与金标准按实体块计算,不要按字算准确率!)
from seqeval.metrics import classification_report
y_true = [["B-ORG", "I-ORG", "O", "B-LOC"]]
y_pred = [["B-ORG", "I-ORG", "O", "B-LOC"]]
print(classification_report(y_true, y_pred))

10本章小结与练习

📌 关键结论

  • POS 标注兼类词是难点;jieba 用北大标注集(nr/ns/nt),spaCy 用 UPOS。
  • NER 三大类:人名 PER、地名 LOC/GPE、机构 ORG;建模为 BIO(ES) 字序列标注。
  • 三条技术路线:规则词典(可解释基线)→ spaCy/LAC(统计/深度流水线)→ BERT/LLM(高精度)。
  • NER 评估按"边界+类型"的实体块算 F1,不能只看单字准确率。
练习1:"他在公司当经理"和"他当公司是好单位"中的"当"词性一样吗?词性标注靠什么解决?
不一样,前者"当经理"的"当"是动词(担任),后者"当(认为)公司是好单位"的"当"也是动词语义不同但句法角色相近;更典型的是"在"这类兼类词(动词/介词/副词)。机器靠上下文词语序列的统计模型(HMM/CRF/神经网络)判断,而不是查词典——同一个词在不同语境出现不同词性标签。
练习2:用 BIO 体系为"李 雷 去 上 海"两个实体标注标签(李雷=人名,上海=地名)。
B-PER、I-PER、O、B-LOC、I-LOC。李/雷分别是人名实体的首字和内部字;"去"非实体为 O;上/海为地名实体的首字与内部字。若用 BIOES,两字实体分别标 B-PER E-PER、B-LOC E-LOC。
练习3:没有 GPU、也不能调大模型时,如何从大量招聘文本中粗略抽取公司名和城市名?
可用三层基线:① jieba 自定义词典(公司后缀:有限公司/集团/股份,城市词典)+ 词性 nt/ns 过滤;② 规则模式("入职/就职于 + X公司"、"工作地点:X市")正则抽取;③ spaCy zh_core_web_sm 的 ORG/GPE 实体兜底。抽完用规则清洗并人工抽样校验,作为冷启动方案,积累标注数据后再上 BERT-CRF。