十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

中文NER实战:BERT+BiLSTM+CRF源码解析与课程设计指南

中文NER实战:BERT+BiLSTM+CRF源码解析与课程设计指南 简介基于BERTBiLSTMCRF实现中文命名实体识别的Python源码面向需要完成课程设计或期末大作业的高校学生也适合正在学习自然语言处理与序列标注的开发者。项目覆盖数据预处理、模型训练到指标评估的完整流程下载解压即可运行无需修改配置。资源包共18个文件核心为9个Python脚本分别对应模型封装、数据处理、训练入口、验证评估等模块同时附带Markdown说明文档、XML配置及3张结构示意图便于对照代码理解整体架构。整个压缩包仅134KB体量紧凑但内容完整已有691人学习使用。代码封装了BERT_BiLSTM_CRF模型类、数据加载、训练及评估脚本并配有模型结构图与运行效果图目录清晰适合快速上手和二次开发也能帮助初学者深入理解预训练模型与条件随机场结合的NER实现思路可作为课程设计答辩成果。1. BERTBiLSTMCRF 的中文命名实体识别直接能跑的 Python 课程设计源码又到了课程设计周题目是「基于深度学习的命名实体识别」。如果你抽到中文场景大概率会在 BERT、BiLSTM、CRF 这三件套里打转。基本原理翻翻论文都能懂但真要写一版能训练、能出 F1、能撑住答辩现场运行的代码没两天时间下不来。这个源码包的价值就在这模型结构、数据预处理、训练验证评估脚本全给齐了下载下来把路径一配就能跑适合把它当课程设计和期末大作业的主干工程也适合拿来做 baseline 往自己的数据集上改。下面我按文件结构、数据流、训练复现、坑点排查、进阶改动的顺序把这个包的代码拆开讲清楚。2. 源码包拆解六个关键模块各管哪一段活2.1 从文件清单看项目的运行主线解压之后目录不长但每个文件都有明确的职责边界。先看主路径上的核心文件文件/目录职责在整个流程中的位置run.py推理入口加载已有模型对新句子做预测训练完成之后演示/验收阶段train_val_test.py训练、验证、测试三合一入口使用阶段的最主要入口data_helper.py数据读取、标签编码、BERT 分词、padding数据进入模型前的最后一站models/BERT_BiLSTM_CRF.py完整网络结构定义模型核心models/rnncell.py自定义 LSTM/RNN 单元被 BERT_BiLSTM_CRF 引用base_config.py全局参数配置所有脚本共用utils.py小工具函数各脚本通用辅助conlleval.pyCONLL 标准评估脚本训练后计算 P/R/F1log/ result/ ckpt/训练日志、预测结果、模型权重目录输出产物这个分工方式是典型的「课程设计友好型」训练和测试合成一个入口推理单独拆出来这样答辩演示时运行run.py输入一句话就能看标注结果不用重新走训练流程。train_val_test.py承担了三个职责内部应该有一组 mode 参数来控制当前行为。2.2 BERT_BiLSTM_CRF.py 的模型结构逻辑BERT_BiLSTM_CRF.py是整个项目的核心文件它的结构可以概括成三个层次。第一层是 BERT 编码中文文本经过 BERT 分词后得到 token ids送入预训练模型拿到每个 token 的上下文表示。第二层是 BiLSTM 序列建模把 BERT 的输出接一个双向 LSTM由 LSTM 对每个 token 的上下文特征再提炼一轮。第三层是 CRF 序列解码BiLSTM 的输出进线性层得到发射分数CRF 利用转移矩阵约束标签序列的合法性。模型对预训练部分和下游部分应该采用了不同的学习率。常见做法是 BERT 层用 2e-5 量级因为微调幅度要小BiLSTM 和 CRF 的线性层可以用 1e-3 量级。这样能避免预训练权重被大步长破坏也保证下游新增层能够快速收敛。源码里默认冻结或不冻结 BERT 层取决于 base_config.py 里的参数设定训练前务必检查这个开关。2.3 base_config.py 与 rnncell.py 的配角作用base_config.py是全局参数出口。这类代码包我一般会先打开它把所有路径、维度、学习率、迭代次数过一遍。典型配置项包括max_seq_len、batch_size、epochs、learning_rate、hidden_dim、num_labels、bert_pretrained_path等。num_labels一定要和数据标注的标签数量对齐否则 CRF 层初始化就崩。rnncell.py提供了 LSTM 单元的实现。这个文件的作用有两个一是避免某些环境下torch.nn.LSTM的初始化方式不符合项目预期时不好排查二是方便在 BiLSTM 的隐藏状态复用、dropout 位置上做微调。课程设计答辩时这个文件很适合拿来解释「双向循环网络如何拼接前向和后向隐藏状态」属于典型的答辩得分点。整个源码包最重要的一条主线是base_config定参数 →data_helper整理数据 →BERT_BiLSTM_CRF完成前向计算 →train_val_test.py控制训练循环 →conlleval.py出指标。后面所有复现步骤都在这条线上展开。3. 数据流设计中文文本如何变成模型能吃的输入3.1 标签体系与数据文件组织课程设计的 NER 任务数据格式通常遵循两个约定一是标签体系二是文件与字段排列。这个项目大概率使用的标签体系是 BIO 标注B-表示实体开始、I-表示实体中间、O表示非实体。一份标准的训练数据通常是三列字符、标签、句子分隔符。类似下面这种组织方式李 B-PER 小 I-PER 明 I-PER 的 O 爸 O 爸 O 去 O 北 B-LOC 京 I-LOC 旅 O 游 O 空 O 调 O 维 O 修 O第一列为中文字符第二列是 BIO 标签空行表示句子结束。注意这里数据预处理的工作量集中在「BERT tokenizer 切出来的 token 和标签如何一一对应」。BERT 的中文模型虽然基本按字切分但遇到 [CLS]、[SEP]、特殊符号和英文片段时token 数量会变化标签对齐就成了一道边界问题。3.2 data_helper.py 里的对齐逻辑data_helper.py的处理流程一般分成五步读原始文本、生成 BIO 标签序列、调 BERT tokenizer 转 token ids、标签序列同步到 token 级别、padding 到固定长度。def encode_example(sentence, labels, tokenizer, max_seq_len): # 第一步BERT 分词得到 token 级别的 ids 和实际 token 数 tokens list(sentence) token_ids tokenizer.convert_tokens_to_ids([[CLS]] tokens [[SEP]]) # 第二步标签也要带上 [CLS] 和 [SEP] 对应的占位 label_ids [-1] labels [-1] # -1 在 loss 计算时会被忽略 # 第三步截断到 max_seq_len - 1留一个位置给 [SEP] token_ids token_ids[:max_seq_len - 1] label_ids label_ids[:max_seq_len - 1] # 第四步padding 到固定长度attention_mask 标记真实 token attention_mask [1] * len(token_ids) padding_len max_seq_len - len(token_ids) token_ids [0] * padding_len label_ids [-1] * padding_len attention_mask [0] * padding_len return { input_ids: token_ids, attention_mask: attention_mask, label_ids: label_ids }这个函数的关键点在于label_ids里用-1填充的位置在损失计算时会被 mask 掉不参与交叉熵计算。如果你自己在写类似逻辑时把 padding 位置的标签设成了 0也就是OCRF 会学习到一堆全是O的转移特征指标看着高实际换数据就翻车。3.3 CRF 层在数据流里的位置models/BERT_BiLSTM_CRF.py拿到data_helper的输出之后前向计算分三段。BERT 部分产出sequence_outputBiLSTM 部分产出双向拼接后的lstm_output线性层把隐藏维度映射到标签数量产出emissions发射分数。CRF 层再接收emissions和真实标签label_ids计算负对数似然损失。import torch import torch.nn as nn def forward(self, input_ids, attention_mask, label_idsNone): # BERT 编码shape [batch_size, seq_len, hidden_dim] bert_output self.bert(input_ids, attention_maskattention_mask) sequence_output bert_output[0] # BiLSTM 编码前向和后向隐藏状态在最后一维拼接 lstm_output, _ self.bilstm(sequence_output) # 线性层映射到 num_labels得到发射分数 emissions self.fc(lstm_output) # [batch_size, seq_len, num_labels] if label_ids is not None: # CRF 负对数似然损失 loss self.crf(emissions, label_ids, maskattention_mask.bool()) return loss else: # 推理模式viterbi 解码 return self.crf.decode(emissions, maskattention_mask.bool())attention_mask在这里是双向作用BERT 端控制哪些 token 参与注意力计算CRF 端控制哪些位置参与路径分数累计。我在调试中经常遇到一个现象padding 位置的 mask 没传对CRF 解码结果最后多出一串无意义的标签。排查方法很简单打印一次attention_mask和label_ids检查后面 padding 段的数值是不是 0 和 -1。4. 从零到有train_val_test.py 训练与评估完整流程4.1 环境准备与依赖安装这个项目依赖 PyTorch 和 Hugging Face Transformers。安装时要注意版本匹配transformers 4.x 和 3.x 的 API 有差异如果包里的代码用的是早期写法装了最新版会报AttributeError。# 创建虚拟环境Python 建议 3.8 或 3.9 conda create -n ner python3.9 conda activate ner # 安装核心依赖 pip install torch1.13.0 pip install transformers4.10.0 pip install tqdm scikit-learn如果网速不理想可以给 pip 换国内源。我一般在命令行追加-i https://pypi.tuna.tsinghua.edu.cn/simple速度会快很多。训练完成后的评估不需要 sklearn但数据预处理阶段可能会用到装上没坏处。4.2 数据放置与路径配置项目训练前必须先把数据放到train_val_test.py或base_config.py指定的路径下。默认结构通常是data/目录下放三个文件train.txt、dev.txt、test.txt。打开base_config.py找到路径相关的配置项# base_config.py 里的典型配置项 class Config: # 数据路径 train_data_path data/train.txt dev_data_path data/dev.txt test_data_path data/test.txt # 模型参数 max_seq_len 128 hidden_dim 768 num_labels 7 # 取决于你的标签体系 # 训练参数 batch_size 16 epochs 20 learning_rate 2e-5 bert_learning_rate 2e-5 crf_learning_rate 1e-3 # 预训练模型 bert_pretrained_path bert-base-chinesehidden_dim设为 768 是因为中文 BERT base 的输出维度就是 768如果换用了 larger 版本这个数字要跟着改。num_labels的赋值是最容易出错的完整体验课设数据集的标签一般是B-PER/I-PER/B-LOC/I-LOC/B-ORG/I-ORG/O再加 padding 占位具体以data_helper.py里的标签映射字典为准。4.3 训练命令与运行参数说明环境装好、路径配好之后训练就一条命令python train_val_test.py --mode train训练过程会输出每个 epoch 的 loss以及在验证集上的 P/R/F1。如果想调整训练精度可以用--batch_size、--epochs、--learning_rate这些命令行参数覆盖base_config.py里的默认值。内存不足时优先把batch_size降到 8 或者 4同时检查max_seq_len是否被设成了过长。训练完成后ckpt/目录下会生成模型权重文件通常是pytorch_model.bin或者model.pt。log/目录下会有每个 epoch 的训练日志答辩的时候直接贴 F1 曲线就行。4.4 测试与评估conlleval.py 怎么用验证集上效果不错之后跑一次正式测试# 测试模式加载最佳模型输出预测结果到 result 目录 python train_val_test.py --mode test # 用 conlleval 评估预测结果 python conlleval.py result/pred_test.txtconlleval.py是经典的 CONLL 评估脚本它会计算三种实体级别的指标精确率、召回率、F1 值。这里有个细节conlleval 对输入格式有严格要求每行必须是「字符 正确标签 预测标签」的结构三个字段用空格切分句子之间用空行分隔。result/目录下生成的预测文件一般已经按这个格式排版了但如果你自己写输出代码一定要保证这三个字段不要换顺序。评估列表参数的含义指标含义实体系别要求precision预测为实体的 token 中真正是实体的比例只看实体类型和边界都正确的recall真实实体中被预测出来的比例漏报越少越好F1两者的调和平均课程设计一般看综合分如果test.txt是未标注数据那run.py就是演示入口。它的用法是在交互式命令行里输入一句话python run.py 输入李小明在北京上班 输出李小明/B-PER 北京/B-LOC5. 避坑清单BERT 版本、标签错位和显存溢出的排查记录5.1 transformers 版本升级后报 AttributeError现象加载 BERT 时提示AttributeError: BertModel object has no attribute from_pretrained或者module transformers has no attribute BertForTokenClassification。原因transformers 3.x 和 4.x 的 API 变化很大最明显的区别是 4.x 之后一些类被移动到transformers.models子模块下而代码里用的是旧版from transformers import BertForTokenClassification这种最原始的写法。升级到 5.x 之后from_pretrained内部的参数签名也有调整。解决固定安装兼容版本。我装的是transformers4.10.0这个版本同时兼容BertModel和BertForTokenClassification的旧式导入。建议看一遍BERT_BiLSTM_CRF.py头部 import 语句确认它用的是哪种写法再对着写 requirements。5.2 tokenizer 分词后标签数量对不上现象训练时 loss 是nan或者在data_helper.py里报label length does not match。原因中文 BERT 基本按字切分但遇到括号、数字、英文字母时可能切成子词导致 token 数量大于原始字符数。这时候如果只按字符维度扩展标签就会出现错位。解决在data_helper.py里加入对齐校验tag 序列的每个位置都用tokenizer.convert_tokens_to_ids的结果长度做一次 assert。推荐用tokenizer.tokenize返回的 token 列表长度来生成标签序列不要在外部手动拼接。做完这一层校验模型训练阶段的绝大多数玄学问题都能排除。5.3 显存溢出 OOM现象训练启动后没跑几步报CUDA out of memory特别是 batch_size 设得比较大时。原因BERT 系列模型显存占用本来就高加上 BiLSTM 的中间激活值显存峰值会出现在反向传播阶段。max_seq_len设成 256 时每句 token 占用的显存几乎是 128 的四倍。解决按显存大小做一个经验对照8G 显存用batch_size8、max_seq_len12816G 显存用batch_size16、max_seq_len128。如果显存还是不够可以把数据截断阈值调到 64。另一招是gradient_accumulation_steps把一次大 batch 拆成多个小 batch 累计梯度效果接近但显存压力低很多。5.4 conlleval 脚本报错或指标全为 0现象conlleval.py输出Token number error或者 P/R/F1 全部为 0。原因conlleval 严格要求每行三个字段且字段间用空格分隔。很多同学在写结果文件时用了制表符或者把 BIO 标签写成了B-PER/李小明这种拼接格式conlleval 解析时完全无法识别。解决先检查result/pred_test.txt的前三行确保格式和下面一致李 B-PER B-PER 小 I-PER I-PER 明 I-PER I-PER 的 O O字段之间只用空格。conlleval 第二个指标全为 0 还可能是标签名称不一致比如模型输出了B-PER而评估脚本期望PER打开脚本头部的实体类别集合看一眼就清楚了。6. 进阶改动换自己的数据、换标签体系、做对比实验的取舍6.1 换自己的数据集三个要点课程设计如果不想用原始数据集可以自己标注。数据文件保持三列结构注意三个约定标签用 BIO 体系、字符和标签之间用空格、句子之间用空行。换数据后要改的是data_helper.py底部的标签字典保证label2id和num_labels同步变化。6.2 BIO 换 BIOES只改数据不换模型如果想要提升效果常见做法是 BIO 升级成 BIOES。训练数据和label2id里把I-拆成I-和E-开头单实体用S-。CRF 对E-和S-的位置约束比I-更严格对长实体的收尾判断更准。6.3 对比实验关掉 CRF 当消融基线答辩时最稳妥的一步是做个消融对比。改法很简单在BERT_BiLSTM_CRF.py的训练分支里跳过 CRF 的负对数似然计算直接把emissions和label_ids做 CrossEntropyLoss。拿这个结果和完整模型的 F1 对比能清晰说明 CRF 带来的收益。从那以后我每次拿到新的课程设计或 GitHub 项目第一件事不是训练而是把base_config.py和data_helper.py里所有路径、标签数、padding 方式手动画一遍流程图确认数据从文本变成 tensor 的每一步都在掌控中。这不是什么高深技巧纯粹是踩多了坑之后的肌肉记忆。这个源码包整体结构完整训练、验证、推理都有独立入口适合直接跑通之后再做二次改造。希望帮到你。本文还有配套的精品资源点击获取
返回列表