资讯动态

基于BERT+BiLSTM+CRF的法律文书命名实体识别源码解析

发布时间:2026/10/31 6:06:55 来源:尧图企业网站定制
简介本资源面向自然语言处理方向的学生与开发者提供一套基于BERTBiLSTMCRF的中文法律文书命名实体识别完整源码聚焦交通肇事案件的事件要素抽取任务可直接用于课程设计、期末大作业或相关课题的实践参考。压缩包共48个文件约694KB以21个Python源码文件为核心涵盖模型定义、数据加载、训练与预测脚本并配有配置文件、日志、词表与评估脚本等辅助内容另含项目说明文档便于快速理解整体流程。目前已有190人学习下载。资源包含预训练模型加载、数据预处理、模型训练与推理的完整链路目录结构清晰读者可据此掌握BERT词向量、BiLSTM序列建模与CRF解码的组合用法理解法律领域实体标注与事件要素抽取的实现思路并在此基础上迁移到其他垂直领域的命名实体识别任务。1. 从一份交通肇事判决书里把时间地点人物抽干净这套 BERTBiLSTMCRF 源码到底能干什么一份交通肇事案的刑事判决书动辄两三千字里面真正要进数据库的结构化字段其实就那么几个肇事时间、肇事地点、肇事车辆、伤亡人数、责任认定、赔偿金额。人工从几百份文书里抠这些字段一天下来眼睛发直还容易漏。这套基于 bertBiLSTMCRF 的法律文书命名实体识别源码干的就是把这件事自动化——输入一段判决书原文输出带实体标签的序列把「2021年3月15日20时许」「XX市XX区XX路」「小型轿车」「一人死亡」这类片段按预定义类别标出来。技术栈是 Python PyTorch transformers模型结构是 BERT 做字符级编码、BiLSTM 抓上下文依赖、CRF 层约束标签转移合法性属于 NER 任务里最经典也最稳的三段式组合。适合两类人一是做课程设计或期末大作业、需要一份能跑通、有完整训练和预测脚本的项目二是做法律文本结构化、想拿一份可改可调的 baseline 快速验证自己数据的人。源码包里训练、预测、评估、预训练权重加载脚本齐全不是那种只丢一个模型文件让你自己猜怎么用的半成品。2. BERTBiLSTMCRF 三段式结构为什么法律文书 NER 还在用它2.1 三个模块各管什么少一个会怎样先把这三个东西的分工说清楚不然后面调参就是瞎调。BERT 负责把输入的中文字符序列转成带上下文语义的向量它的强项是预训练阶段见过大量通用语料对「肇事」「逃逸」「赔偿」这类词的语义边界有基本感知。但 BERT 输出的是每个位置独立的向量它不直接建模标签之间的依赖关系。BiLSTM 接在 BERT 后面双向扫一遍序列把前后文信息融合进每个位置的表示让「北京市」里的「市」能吃到「北京」的信息。CRF 是最后一层它不改变每个位置的发射分数而是学一个标签转移矩阵约束输出标签序列的合法性——比如 I-地点 不能直接跟在 B-人名 后面O 标签后面接 I-时间 的概率应该被压低。少一个会怎样只用 BERT 做 token classification标签之间没有约束模型可能输出 B-时间 I-地点 这种非法序列。只用 BiLSTMCRF 不加 BERT在标注数据只有几百条的情况下词向量从零学起泛化能力差一截。三个叠起来BERT 提供语义底座BiLSTM 补序列上下文CRF 兜底标签合法性这是法律文书这种句式规整但实体边界微妙场景下比较稳的组合。2.2 标签体系怎么定BIO 标注与实体类别映射源码里用的是 BIO 标注法B- 表示实体开始I- 表示实体内部O 表示非实体。交通肇事案常见的实体类别包括时间TIME、地点LOC、车辆VEH、人员PER、伤亡结果CAS、责任RES、赔偿金额MON。标签集大小 实体类别数 × 2 1O 标签。比如 7 类实体标签数就是 15。标签映射一般存在 maps.pkl 里训练和预测共用同一份映射不然预测出来的标签 ID 对不上类别名。我一般会在 data_utils.py 里把 label2id 和 id2label 两个字典显式打印一遍确认顺序没乱。常见坑是训练时标签顺序和预测时不一致模型输出 ID 5 你以为是 LOC实际是 VEH结果全错但 loss 看着正常。2.3 数据从判决书到模型输入loader.py 里的处理链路原始数据是判决书文本加标注文件标注格式通常是每行「字符\t标签」句子之间空行分隔。loader.py 负责读入、按最大长度截断、转成 BERT 的 input_ids、attention_mask、token_type_ids同时把标签序列对齐。这里有个细节BERT 用的是字级别 tokenizer中文一个字一个 token所以字符和标签是一一对应的不需要处理 subword 对齐问题。如果用英文 BERT 或混合 tokenizer就得处理 ## 后缀和标签对齐那是另一个坑。处理链路大致是读原始文件 → 按句切分 → 过滤空句和超长句 → 构建字符列表和标签列表 → 转 ID → padding 到 batch 内最大长度 → 生成 mask。padding 用 0标签 padding 用 -1 或专门的 ignore_indexloss 计算时忽略掉不然模型会学「padding 位置的标签是 O」这种无用信息。2.4 训练脚本关键参数train.py 里改哪几个数train.py 是主训练入口几个必须关注的参数# train.py 关键参数片段 config { bert_model: bert-base-chinese, # 预训练模型路径或名称 max_seq_len: 256, # 判决书单句最大长度超长截断 batch_size: 16, # 显存 8G 以下建议 8 或 16 learning_rate: 2e-5, # BERT 微调经典学习率 crf_lr: 1e-3, # CRF 层单独用大一点的学习率 num_epochs: 30, # 小数据集 20-30 轮足够 dropout: 0.5, # BiLSTM 输出后的 dropout lstm_hidden: 256, # BiLSTM 隐藏层维度 tag_num: 15, # 标签总数按你的实体类别算 }逻辑说明BERT 层用 2e-5 是因为预训练权重已经很好学习率大了会灾难性遗忘CRF 层是随机初始化的需要更大学习率才能快速收敛所以分开设。batch_size 受显存限制256 长度下 16 batch 大约占 6-8G 显存。num_epochs 不用太大法律文书标注数据通常几千句30 轮以内验证集 F1 就趋于平稳再训就是过拟合。参数怎么改如果你的实体类别不是 7 类tag_num 要重算如果判决书句子普遍超过 256 字max_seq_len 调到 384 或 512但显存占用会翻倍batch_size 要相应降到 8 或 4。dropout 在数据量小于 2000 句时可以提到 0.6数据多就降到 0.3。3. 把源码跑起来环境、预训练权重与训练预测全流程3.1 环境依赖与 requirement.txt 逐项确认拿到源码包第一件事不是急着 python train.py而是把环境对齐。requirement.txt 里通常列了 torch、transformers、numpy、tqdm、seqeval 这几个。版本兼容性是血泪经验transformers 4.x 和 torch 1.8 搭配比较稳transformers 2.x 的 API 和现在差别大直接装最新版可能报BertModel.from_pretrained() got an unexpected keyword argument。# 建议的安装顺序避免依赖冲突 pip install torch1.10.0cu113 -f https://download.pytorch.org/whl/torch_stable.html pip install transformers4.12.0 pip install numpy tqdm seqeval scikit-learn逻辑说明先装 torch 再装 transformers因为 transformers 会检查 torch 版本。seqeval 是用来算实体级别 F1 的比 token 级别准确法律 NER 评估必须用它。如果机器没有 GPUtorch 装 CPU 版也行但训练时间从几分钟变成几十分钟调试阶段可以先用小样本跑 CPU。3.2 预训练权重放哪bert 目录与 download_electra.py 的作用源码包里有个 bert 目录通常放的是 bert-base-chinese 的配置文件config.json、vocab.txt和 pytorch_model.bin。如果这个目录是空的需要用 download_electra.py 或手动下载。常见做法是从 HuggingFace 或国内镜像拉 bert-base-chinese解压后把三个文件放进 bert/ 目录。# 检查 bert 目录是否完整 import os bert_dir ./bert required [config.json, vocab.txt, pytorch_model.bin] for f in required: path os.path.join(bert_dir, f) print(f, 存在 if os.path.exists(path) else 缺失)逻辑说明config.json 定义模型结构vocab.txt 是字符表pytorch_model.bin 是权重。三个缺一不可。如果只缺权重可以用BertModel.from_pretrained(bert-base-chinese)自动下载但需要网络能通。download_electra.py 是备选方案ELECTRA 在某些 NER 任务上比 BERT 略好但源码默认走 BERT换 ELECTRA 要改 model.py 里的加载逻辑。3.3 训练命令与日志观察train.log 里看什么启动训练python train.py --config config_file --data_dir ./data --bert_dir ./bert --save_dir ./result参数说明--config 指定配置文件路径里面是超参--data_dir 是训练和验证数据目录--bert_dir 是预训练权重目录--save_dir 是模型保存路径。训练开始后 train.log 会逐轮打印 loss、验证集 precision、recall、F1。看日志重点第一轮 loss 应该在 2.0 以上然后快速下降如果第一轮就 0.1 说明标签泄露或数据有问题验证集 F1 在前 5 轮上升最快10 轮后趋于平缓如果训练 loss 降但验证 F1 不升是过拟合加 dropout 或减 epoch。nohup.out 是后台运行的输出和 train.log 内容类似。3.4 预测与评估predict.py 和 conlleval.py 怎么配合训练完模型后用 predict.py 对新文本做预测# predict.py 核心调用逻辑 from model import BertBiLSTMCRF from transformers import BertTokenizer tokenizer BertTokenizer.from_pretrained(./bert) model BertBiLSTMCRF.from_pretrained(./result/best_model) text 2021年3月15日20时许被告人张某驾驶小型轿车行驶至XX市XX区XX路时与行人李某发生碰撞致李某当场死亡。 inputs tokenizer(text, return_tensorspt, max_length256, truncationTrue) with torch.no_grad(): outputs model(**inputs) pred_tags model.crf.decode(outputs, inputs[attention_mask]) print([id2label[i] for i in pred_tags[0]])逻辑说明tokenizer 要和训练时用的一致否则字符 ID 对不上。crf.decode 会自动走维特比算法找最优标签路径比 argmax 更合理。conlleval.py 是评估脚本输入是预测标签和真实标签的对照文件输出实体级 P/R/F1。常见做法是训练完在测试集上跑一遍 conlleval确认 F1 达到预期再部署。4. 避坑与排查法律文书 NER 训练里最容易翻车的五件事4.1 现象loss 正常下降但预测全是 O 标签原因标签不平衡严重O 标签占比 90% 以上模型学到「全预测 O 也能拿高准确率」的偷懒策略。法律文书里实体本来就稀疏这是高频翻车点。解决在 loss 里给非 O 标签加权或者用 focal loss。简单做法是在 CRF 层之前对发射分数做类别加权权重设为 O 标签的 3-5 倍。另外检查验证集 F1 而不是准确率准确率在这种场景下没有参考价值。4.2 现象训练时报 CUDA out of memory原因max_seq_len 设太大或 batch_size 太高。判决书单句超过 256 字很常见有人直接设 512显存直接爆。解决先降到 256 跑通再逐步加。或者用梯度累积batch_size 设 4累积 4 步等效 16。还可以开混合精度训练torch.cuda.amp 能省 30% 左右显存。4.3 现象预测结果里实体边界切错比如「XX市XX区」只标出「XX市」原因BiLSTM 的序列建模对长实体边界不敏感或者训练数据里这类实体标注不一致。解决检查训练数据里同类实体的标注是否统一有的标「XX市XX区」有的只标「XX市」模型就懵了。统一标注规范后重新训练。另外可以加一层注意力机制但那是改模型结构的事先确认数据没问题。4.4 现象换自己的数据后 F1 掉到 0.3 以下原因标签体系不一致。源码默认的标签集和你的数据标签集对不上maps.pkl 没重新生成。解决删掉旧的 maps.pkl用你的数据重新跑一遍 data_utils.py 里的标签统计和映射生成。确认 tag_num 和实际标签数一致。这个坑我踩过模型加载时没报错但预测结果全是乱的。4.5 现象predict.py 单条预测正常批量预测结果错位原因batch 内 padding 后crf.decode 返回的序列包含 padding 位置的标签没做长度截断。解决decode 后用 attention_mask 把 padding 位置过滤掉只保留真实长度内的标签。或者逐条预测不组 batch。批量预测时还要注意 tokenizer 的 padding 方向BERT 默认右 padding如果改成左 padding标签对齐逻辑要跟着改。5. 进阶用法用 LTP_NER.py 做交叉验证与模型融合源码包里有个 LTP_NER.py初看像是另一个模型的入口实际上它可以用来做交叉验证或者和 BERTBiLSTMCRF 做结果融合。LTP 是哈工大的语言技术平台它的 NER 模块在某些通用实体上表现不错但在法律领域细粒度实体上不如微调后的 BERT 模型。我的做法是用 LTP_NER.py 对同一批测试集做预测然后把两个模型的预测结果按实体级别做投票融合——两个模型都标为同一类且边界一致的实体保留只有一个模型标出的实体按置信度阈值决定是否保留。具体操作分三步。第一步用 LTP_NER.py 跑一遍测试集输出标签序列文件格式和 conlleval.py 的输入对齐。第二步写一个简单的融合脚本# fusion.py 模型融合逻辑 def fuse_tags(tags_a, tags_b, threshold0.8): 两个模型的标签序列融合边界一致才保留 fused [] for a, b in zip(tags_a, tags_b): if a b: fused.append(a) # 两个模型一致直接保留 elif a O and b ! O: fused.append(b) # 只有 B 模型标出按阈值决定 elif a ! O and b O: fused.append(a) # 只有 A 模型标出 else: fused.append(a) # 冲突时以 A 模型为准 return fused逻辑说明这个融合策略假设 BERT 模型是主模型LTP 是辅助。冲突时以主模型为准是因为微调后的 BERT 在法律领域更可信。threshold 参数控制辅助模型单独标出的实体是否保留设 0.8 表示只有高置信度的才留。实际跑下来融合后 F1 通常比单模型高 1-2 个点但提升有限适合对精度要求高的场景。第三步用 conlleval.py 评估融合后的结果和单模型对比。如果融合后 F1 没提升甚至下降说明两个模型的错误不互补融合没意义直接用 BERT 模型就行。另一个进阶方向是换预训练模型。bert-base-chinese 是通用语料训练的法律领域有专门的预训练模型比如 law-bert换上去通常能涨几个点。换的方法是改 config 里的 bert_model 路径同时确认 vocab.txt 和模型匹配。换完要重新训练不能直接加载旧权重。从那以后我每次拿到一个新的 NER 项目都强制先跑一遍标签统计和标签映射检查确认 tag_num、label2id、id2label 三者一致再开始训练。这个习惯帮我省了至少三次「训练正常但预测全错」的排查时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑