法律实体识别(NER):从规则到大模型的进化之路
法律实体识别,说白了就是让机器能从法律文书中把人名、地名、机构名、法条名这些关键信息给揪出来。我做了这么多年法律NLP,这步可以说是所有上层应用的基石。你想想看,连实体都抽不准,后面的知识图谱、智能问答全是空中楼阁。
今天咱们就聊聊四种主流的做法:基于规则的、基于BiLSTM-CRF的、基于大模型的,以及实体对齐与消歧。每种方法我都踩过坑,咱们一个一个说。
核心观点:没有银弹。规则方法适合小规模、高精度的场景;深度学习方法适合大规模、泛化需求;大模型方法适合零样本或少样本场景。实际项目中,我通常混合使用。
一、基于规则的法律实体抽取
这是最传统的方法,也是我最早接触的方式。2018年我刚入行时,第一个项目就是给法院的裁判文书做实体抽取。那时候深度学习还没普及,全靠正则表达式和词典。
规则方法的核心思路很简单:
- 词典匹配:构建法律实体词典,比如《民法典》所有法条编号、常见法院名称、律所名称等
- 正则表达式:利用法律文本的固定模式,比如「(2023)京01民初123号」这种案号格式
- 上下文规则:比如「原告XXX」后面的词大概率是当事人姓名
举个例子,抽取案号的规则长这样:
import re
def extract_case_number(text):
# 匹配中国法院案号格式
pattern = r'((\d{4}))[^\d]+(\d+)民初(\d+)号'
matches = re.findall(pattern, text)
return [f"({y}){c}民初{n}号" for y, c, n in matches]
text = "本院于(2023)京01民初123号案件中..."
print(extract_case_number(text))
# 输出:['(2023)京01民初123号']
我的经验:规则方法在特定场景下精度极高,能达到95%以上。但维护成本也高——我曾经为一个项目写了200多条正则,结果新来一批文书格式变了,全得重写。
规则方法的优缺点很明显:
| 优点 | 缺点 |
|---|---|
| 精度高,可解释性强 | 覆盖不全,维护成本高 |
| 不需要标注数据 | 对新格式、新实体类型不敏感 |
| 部署简单,速度快 | 难以处理歧义和嵌套实体 |
二、基于BiLSTM-CRF的NER模型
到了2019年,我开始转向深度学习方法。BiLSTM-CRF是当时最主流的序列标注模型,我印象特别深——第一次跑通这个模型时,F1值直接从规则方法的82%跳到了91%。
这个模型的结构其实不复杂:
- BiLSTM层:双向LSTM捕捉上下文信息,左边看前文,右边看后文
- CRF层:条件随机场,保证标签序列的合法性。比如「B-PER」后面不能跟「I-ORG」
我画了个结构图,方便你理解:
训练这个模型需要标注数据。我一般用BIO标注体系:
- B-PER:人名开始
- I-PER:人名中间
- B-ORG:机构名开始
- O:非实体
代码实现的核心部分:
import torch
import torch.nn as nn
from torchcrf import CRF
class BiLSTM_CRF(nn.Module):
def __init__(self, vocab_size, tag_size, embedding_dim, hidden_dim):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embedding_dim)
self.lstm = nn.LSTM(embedding_dim, hidden_dim // 2,
bidirectional=True, batch_first=True)
self.fc = nn.Linear(hidden_dim, tag_size)
self.crf = CRF(tag_size, batch_first=True)
def forward(self, x, tags=None):
x = self.embedding(x)
x, _ = self.lstm(x)
x = self.fc(x)
if tags is not None:
# 训练时计算损失
loss = -self.crf(x, tags)
return loss
else:
# 推理时用维特比解码
return self.crf.decode(x)
避坑指南:我曾经在训练时忘记处理标签不平衡问题——法律文本中「O」标签占了80%以上。后来加了类别权重和Focal Loss才解决。另外,CRF的转移矩阵一定要初始化好,否则模型容易陷入局部最优。
三、基于大模型(LLM)的实体抽取
到了2023年,大模型火了。说实话,我第一次用GPT-4做法律NER时,效果惊艳到我了——零样本场景下F1值居然有85%。但别高兴太早,大模型也有自己的问题。
用大模型做NER主要有三种方式:
- Prompt工程:直接让模型输出实体列表
- In-Context Learning:给几个例子,让模型模仿
- Fine-tuning:在开源模型上微调
我常用的Prompt模板:
prompt = """
请从以下法律文书中抽取实体,按JSON格式输出。
实体类型包括:当事人、法院、案号、法条、日期。
文书内容:
{text}
输出格式:
{
"当事人": ["张三", "李四"],
"法院": ["北京市第一中级人民法院"],
"案号": ["(2023)京01民初123号"],
"法条": ["《民法典》第584条"],
"日期": ["2023年5月10日"]
}
"""
我的经验:大模型做NER最大的优势是泛化能力强,能处理没见过的实体类型。但缺点也很明显——输出不稳定,同一个文本抽两次可能结果不一样。我建议用大模型做初筛,再用规则或小模型做精校。
四、实体对齐与消歧
实体抽出来了,但问题还没完。同一个实体在不同文书里可能有不同写法。比如「北京市第一中级人民法院」和「北京一中院」指的是同一个法院。这就是实体对齐要解决的问题。
我常用的对齐方法:
| 方法 | 原理 | 适用场景 |
|---|---|---|
| 字符串相似度 | 编辑距离、Jaccard系数 | 同义词、缩写 |
| 向量相似度 | BERT编码后计算余弦相似度 | 语义相近但字面不同 |
| 知识库映射 | 对齐到统一的法律知识图谱 | 标准化实体库 |
实体消歧更麻烦。比如「张三」这个名字,可能出现在100个案件里,但每个案件里的「张三」是不是同一个人?这需要结合上下文信息来判断。
我一般用这个流程:
def entity_disambiguation(entity, context):
# 1. 提取上下文特征(案由、时间、地点、其他关联实体)
features = extract_context_features(context)
# 2. 计算与已有实体的相似度
candidates = query_knowledge_graph(entity.name)
# 3. 如果相似度超过阈值,合并
for candidate in candidates:
if similarity(features, candidate.features) > 0.85:
return candidate.id
# 4. 否则创建新实体
return create_new_entity(entity, features)
避坑指南:我曾经在实体对齐时用了太高的阈值,导致大量「北京一中院」和「北京市第一中级人民法院」没对齐。后来发现,法律文本中缩写和全称混用的情况非常普遍,建议阈值设在0.7-0.8之间,再辅以人工审核。
小结
法律实体识别这条路,我从规则走到大模型,每一步都有收获也有教训。规则方法像老黄牛,踏实但笨重;深度学习方法像骏马,跑得快但需要好草料(数据);大模型方法像变形金刚,功能强大但不好控制。
我个人建议:不要迷信任何一种方法。实际项目中,我通常用规则做兜底,用BiLSTM-CRF做主力,用大模型做补充。三者配合,才能达到最好的效果。
嗯,实体识别这块就先聊到这儿。记住,抽出来的实体只是第一步,后面还有关系抽取、事件抽取等着你呢。