4、实体识别(NER):从规则到深度学习的实战之路
实体识别,也就是 NER,在投研分析里是个绕不开的活儿。说白了,就是从一堆非结构化的文本里,把公司名、产品名、人名这些关键信息给揪出来。我做了这么多年量化,发现很多策略的成败,其实就卡在这一步——数据没洗干净,后面的分析全是白搭。
今天咱们就聊聊两种主流做法:基于规则的实体抽取和基于 BERT 的序列标注模型。最后我会带大家走一遍实战,从财报里把公司名和产品名抽出来。
4.1 基于规则的实体抽取:简单粗暴但有效
先说说规则方法。你想想看,很多公司名都有固定后缀,比如“有限公司”、“集团”、“股份”。产品名也常有品牌词或型号。规则方法就是利用这些模式,写正则表达式或者词典匹配。
我在项目中遇到过一种情况:客户要求快速验证一个舆情因子,数据量不大,就几千份公告。这时候上 BERT 有点杀鸡用牛刀。我直接用 Python 写了个规则引擎,配合一个公司名词典,半小时就搞定了。
核心思路:
- 词典匹配:维护一个公司名和产品名的白名单,用 Aho-Corasick 算法做多模式匹配。
- 模式规则:比如“<公司名>发布<产品名>”这种句式,用正则提取。
- 边界修正:匹配到“华为技术有限公司”,不能只抽“华为”,要带上全称。
举个例子,一段文本是:“贵州茅台酒股份有限公司推出飞天茅台系列酒。” 用规则抽的话,我会先匹配词典里的“贵州茅台酒股份有限公司”,再匹配“飞天茅台”。
import re
# 简单的规则示例
text = "贵州茅台酒股份有限公司推出飞天茅台系列酒。"
company_pattern = r'[\u4e00-\u9fa5]+(?:有限公司|集团|股份|有限合伙)'
product_pattern = r'(?:飞天|五星|生肖)[\u4e00-\u9fa5]+'
companies = re.findall(company_pattern, text)
products = re.findall(product_pattern, text)
print(companies) # ['贵州茅台酒股份有限公司']
print(products) # ['飞天茅台']
我的经验:规则方法最大的坑是“过拟合”。我曾经为了覆盖所有产品名,写了上百条正则,结果新来的公告格式一变,全废了。所以我的建议是:规则只用来处理高频、稳定的模式,剩下的交给模型。
4.2 基于 BERT 的序列标注模型:更聪明但更重
当数据量大、实体类型多的时候,规则就力不从心了。这时候就该 BERT 上场了。BERT 做 NER,本质是个序列标注任务——给每个字打标签,比如 B-ORG(公司名开头)、I-ORG(公司名中间)、O(非实体)。
为什么会这样?因为 BERT 能理解上下文。比如“苹果”这个词,在“苹果公司发布新品”里是公司名,在“我吃了一个苹果”里就是水果。规则很难区分这种歧义,但 BERT 可以。
我记得有一次处理港股公告,里面公司名经常用英文缩写,比如“腾讯控股”写成“Tencent”。规则词典根本覆盖不全,但 BERT 模型通过上下文能准确识别出来。
模型架构:
- 输入层:BERT 预训练模型,输出每个 token 的向量表示。
- 标注层:接一个线性层 + CRF(条件随机场),做序列解码。
- 标签体系:BIO 或 BIOES 方案。我习惯用 BIO,简单够用。
下面是一个简化的训练代码框架,用的是 HuggingFace 的 Transformers 库:
from transformers import BertTokenizer, BertForTokenClassification
from transformers import Trainer, TrainingArguments
# 加载预训练模型和分词器
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertForTokenClassification.from_pretrained(
'bert-base-chinese',
num_labels=len(label_list) # 标签数量
)
# 训练参数
training_args = TrainingArguments(
output_dir='./ner_model',
num_train_epochs=3,
per_device_train_batch_size=16,
save_steps=500,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
)
trainer.train()
避坑指南:我曾经在训练 BERT NER 模型时,忽略了标签对齐问题。BERT 的分词器会把中文词切成子词,比如“茅台”可能被切成“茅”和“##台”。这时候标签必须跟着子词走,否则模型学出来的全是错的。切记,数据预处理时要做对齐校验。
4.3 实战:从财报中抽取公司名与产品名
好了,理论说完了,咱们直接上手。假设我们有一份贵州茅台的财报片段,要抽取出里面的公司名和产品名。
文本示例:
“贵州茅台酒股份有限公司(以下简称公司)2023年实现营业总收入1505.60亿元。其中,茅台酒销售收入1265.89亿元,系列酒销售收入239.71亿元。公司主要产品包括飞天茅台、五星茅台、茅台1935等。”
第一步:规则方法快速验证
我会先用规则跑一遍,看看效果。这里我维护一个简单的公司名词典和产品名关键词:
company_dict = ['贵州茅台酒股份有限公司', '贵州茅台']
product_keywords = ['飞天茅台', '五星茅台', '茅台1935', '系列酒']
def rule_based_ner(text):
entities = {'company': [], 'product': []}
for c in company_dict:
if c in text:
entities['company'].append(c)
for p in product_keywords:
if p in text:
entities['product'].append(p)
return entities
print(rule_based_ner(text))
# 输出: {'company': ['贵州茅台酒股份有限公司', '贵州茅台'], 'product': ['飞天茅台', '五星茅台', '茅台1935', '系列酒']}
你看,规则方法能抓到大部分,但“系列酒”其实是个品类,不是具体产品名。这就是规则的局限。
第二步:BERT 模型精准抽取
接下来用训练好的 BERT 模型来抽。假设我们已经有了一个微调好的 NER 模型:
from transformers import pipeline
ner_pipeline = pipeline(
'ner',
model='./ner_model',
tokenizer='bert-base-chinese'
)
results = ner_pipeline(text)
for entity in results:
print(f"实体: {entity['word']}, 标签: {entity['entity']}, 置信度: {entity['score']:.2f}")
输出大概是这样:
| 实体 | 标签 | 置信度 |
|---|---|---|
| 贵州茅台酒股份有限公司 | B-ORG | 0.99 |
| 飞天茅台 | B-PROD | 0.97 |
| 五星茅台 | B-PROD | 0.96 |
| 茅台1935 | B-PROD | 0.95 |
你看,BERT 模型没有把“系列酒”当成产品名,因为它知道“系列酒”是个泛称。这就是上下文理解的优势。
我的建议:在实际项目中,我通常采用“规则 + 模型”的混合策略。规则负责召回高频、确定的实体,模型负责处理歧义和长尾。两者结合,效果最好。
4.4 本章知识体系总览
下面这张图,是我梳理的本章核心逻辑。你可以看到,从数据输入到实体输出,规则和模型两条路径各有优劣,最终在实战中融合。
嗯,到这里,实体识别这块的核心内容就差不多了。规则和 BERT 各有千秋,关键看你的场景和数据量。我个人习惯是:先搭规则快速出活,再上 BERT 提升精度。两者结合,才是投研分析里最稳的做法。
公众号:蓝海资料掘金营,微信deep3321