法律实体识别(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」

我画了个结构图,方便你理解:

BiLSTM-CRF 法律NER模型结构 输入:原告张三诉被告李四合同纠纷一案 Word Embedding + 字符Embedding + 词典特征 BiLSTM层(前向LSTM + 后向LSTM) 捕捉上下文语义信息 CRF层(约束标签转移规则) 输出:O B-PER I-PER O B-DEF I-DEF O O O

训练这个模型需要标注数据。我一般用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主要有三种方式:

  1. Prompt工程:直接让模型输出实体列表
  2. In-Context Learning:给几个例子,让模型模仿
  3. 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做主力,用大模型做补充。三者配合,才能达到最好的效果。

嗯,实体识别这块就先聊到这儿。记住,抽出来的实体只是第一步,后面还有关系抽取、事件抽取等着你呢。


专注资料整理