简介:这是一套面向计算机相关专业学生与项目实战学习者的BiLSTM-CRF命名实体识别完整Python项目,源自大三期末大作业,经导师指导并获评审99分,可作为课程设计、毕设或NLP入门练手的高分参考。资源包共89个文件,约9.8MB,以35个py源码文件为核心,辅以txt说明、json配置、pkl与bmes数据文件、sh启动脚本及log日志,覆盖数据预处理、CRF层实现、模型训练、预测与知识蒸馏等模块,并附带多个中文NER数据集目录,结构清晰便于按模块学习。目前已有97人学习下载。读者可据此掌握BiLSTM-CRF的完整建模流程,理解CRF解码、序列标注与评估指标实现,并借助现成脚本快速复现训练与预测,适合需要项目实战练习或课程设计参考的学习者。
1. 从一份 99 分的课设说起:BiLSTM-CRF 到底能跑出什么
如果你正在做计算机相关专业的期末大作业或课程设计,大概率绕不开命名实体识别这个题目。我手上这份基于 BiLSTM-CRF 的命名实体识别完整项目,就是一份评审分 99 分的课设级代码包,Python 实现,目录结构完整,从数据预处理、模型定义、训练脚本到预测和服务端接口一应俱全。它解决的核心问题是:给你一段中文文本,自动标出里面的人名、地名、机构名、时间等实体片段。适合谁?正在找可运行、可复现、能写进报告里的毕设或课设的同学,以及想拿一个真实 NLP 项目练手的学习者。下面我按自己拆包、跑通、踩坑的顺序,把这份资源讲透。
2. 拆开代码包:目录结构与 BiLSTM-CRF 的选型理由
2.1 从文件树看这份资源能直接给你什么
拿到压缩包后,我习惯先看目录再动手。这份项目的文件组织很清晰,核心模块都放在根目录和几个子目录里。下面是我整理的关键文件清单,方便你对照自己的需求决定先看哪块。
| 路径 | 作用 | 是否必须 |
|---|---|---|
main.py | 训练入口,串联数据加载、模型构建、训练循环 | 是 |
bert_bilstm_crf.py | BiLSTM-CRF 模型定义,含 CRF 层 | 是 |
layers/CRF.py | 条件随机场层实现,负责转移矩阵和维特比解码 | 是 |
preprocess.py | 原始语料转成模型可读的序列标注格式 | 是 |
dataset.py | 封装 Dataset 和 DataLoader | 是 |
predict.py | 单条文本预测,输出实体片段 | 是 |
config.py | 超参数、路径、模型保存位置 | 是 |
utils/ | 指标计算、解码、通用工具 | 是 |
data/ | 多个中文 NER 数据集,如 msra、weibo、cner | 按需 |
checkpoints/ | 训练后模型权重保存目录 | 自动生成 |
server.py+templates/ | 简易 HTTP 服务,提供预测接口 | 可选 |
knowledge_distillation/kd.py | 知识蒸馏脚本,进阶用 | 可选 |
data_augment/aug.py | 数据增强,扩充训练样本 | 可选 |
从清单能看出,这不是一个只贴模型定义的“半成品”,而是把训练、评估、预测、服务化都串起来了。你如果只想跑通课设,重点看main.py、config.py、predict.py三个文件就够;如果想在报告里体现工作量,knowledge_distillation和data_augment是很好的加分项。
2.2 为什么是 BiLSTM-CRF,而不是单独 BiLSTM 或 BERT
命名实体识别本质是序列标注任务,每个字或词要打一个标签,比如 B-PER、I-PER、O。单独用 BiLSTM 做分类,每个位置独立 softmax,会出现“B-PER 后面跟 I-LOC”这种非法标签序列。CRF 层的作用就是学习标签之间的转移约束,保证输出序列全局最优。常见做法是:BiLSTM 负责提取上下文特征,CRF 负责解码时加转移分数,两者结合在中文 NER 上比纯 BiLSTM 的 F1 通常高几个点。
那为什么不直接用 BERT?BERT 效果好,但显存和训练时间对课设环境不友好。这份项目里其实也带了bert_base_model.py和bert_ner_model.py,说明作者考虑过 BERT 路线,但主推的 BiLSTM-CRF 版本更轻量,普通笔记本 CPU 也能跑小数据集。选型逻辑很务实:先保证能跑通、能复现,再谈效果上限。
2.3 数据格式与预处理链路
项目data/下放了多个数据集,包括 msra、weibo、cner、sighan2005 等。不同数据集的原始格式不一样,preprocess.py和cutSentences.py负责统一成“字 + 标签”的序列。我一般会先跑一遍预处理,确认生成的中间文件长什么样。
# preprocess.py 核心逻辑示意 def load_data(path): # 读取原始语料,按行解析 with open(path, 'r', encoding='utf-8') as f: lines = f.readlines() sentences, labels = [], [] for line in lines: # 空行表示一个句子结束 if line.strip() == '': if sentences: yield sentences, labels sentences, labels = [], [] continue # 常见格式:字 标签 char, tag = line.strip().split() sentences.append(char) labels.append(tag)这段代码的关键参数是encoding='utf-8',中文语料必须指定,否则在 Windows 上容易乱码。line.strip().split()默认按空白切分,如果你的数据用制表符分隔,也能兼容。逻辑说明:按空行切句,每句生成一个(字序列, 标签序列)对,后续dataset.py会把这些序列转成 id。注意,不同数据集的标签体系可能不同,比如 msra 用B-Nh、I-Nh表示人名,而 cner 用B-PER,跑之前先看config.py里的label2id映射是否匹配。
3. 跑通训练与预测:从 config 到维特比解码
3.1 环境准备与依赖安装
这份代码是纯 Python 项目,依赖 PyTorch。我建议用 Python 3.8 或 3.9,太新的版本有时和旧版 torch 不兼容。先建虚拟环境,再装依赖。
# 创建虚拟环境 python -m venv ner_env source ner_env/bin/activate # Linux/Mac # ner_env\Scripts\activate # Windows # 安装核心依赖 pip install torch==1.10.0 pip install numpy pandas tqdm pip install flask # 如果要跑 server.py参数说明:torch==1.10.0是我实测比较稳的版本,CUDA 版本按你显卡驱动选,没显卡就装 CPU 版。flask只在启动服务端接口时需要。装完后进项目根目录,先跑python -c "import torch; print(torch.__version__)"确认环境没问题。
3.2 修改 config.py 里的关键超参数
config.py是整份项目的控制中心,训练前必须按你的机器和数据改几个值。我列一下最常动的参数。
| 参数名 | 含义 | 建议值 |
|---|---|---|
batch_size | 每批样本数 | CPU 跑设 16,GPU 可设 64 |
max_seq_len | 句子最大长度 | 128,超过截断 |
embedding_dim | 字向量维度 | 128 或 256 |
hidden_dim | LSTM 隐藏层维度 | 256 |
num_layers | LSTM 层数 | 1 或 2 |
dropout | 丢弃率 | 0.5 |
lr | 学习率 | 1e-3 |
epochs | 训练轮数 | 30 到 50 |
data_name | 选用的数据集 | msra 或 cner |
改完data_name后,确认data/下对应目录存在,且config.py里的train_path、dev_path、test_path指向正确文件。我见过有人直接跑main.py报FileNotFoundError,九成是路径没对上。
3.3 启动训练并观察日志
训练入口是main.py,直接运行即可。项目logs/下已经带了作者跑过的日志,你可以对照自己的输出。
python main.py --data_name msra --epochs 30如果main.py没写 argparse,就改config.py后直接python main.py。训练过程中重点看三个指标:loss 是否稳定下降、验证集 F1 是否在涨、有没有出现梯度爆炸。日志里如果 loss 变成 nan,先把学习率降到 1e-4 再试。checkpoints/下会保存效果最好的模型,文件名通常带 epoch 和 F1 值。
3.4 用 predict.py 做单条预测与维特比解码
训练完,最直观的验证是拿一句话让模型标实体。predict.py封装了加载模型和维特比解码的流程。
# predict.py 核心调用示意 from bert_bilstm_crf import BiLSTM_CRF from config import Config config = Config() model = BiLSTM_CRF(config) model.load_state_dict(torch.load('checkpoints/best_model.pt')) model.eval() text = "张三在北京大学读书" entities = model.predict(text) for ent in entities: print(ent) # 输出如 ('张三', 'PER'), ('北京大学', 'ORG')逻辑说明:model.eval()关闭 dropout,保证预测稳定。predict内部先查char2id把字转 id,过 BiLSTM 得到发射分数,再送 CRF 层做维特比解码,得到全局最优标签序列,最后按 B/I/O 规则合并成实体片段。参数上注意max_seq_len要和训练时一致,否则位置编码或截断逻辑会对不上。如果预测结果全是 O,先检查char2id里有没有你输入的字,未登录字会被映射到 UNK,太多 UNK 会导致模型失效。
4. 避坑与排查:我跑这份代码时翻过的车
4.1 现象:训练 loss 不降,F1 一直为 0
原因:最常见的是标签映射错位。config.py里的label2id和数据集实际标签不一致,比如数据里是B-Nh,配置里写的是B-PER,模型学到的全是错标签。解决:打开data/下对应数据集的labels.txt或统计脚本,打印所有出现过的标签,和config.py逐一对齐。改完清空checkpoints/重新训练。
4.2 现象:预测时中文乱码或报 UnicodeDecodeError
原因:Windows 默认编码是 gbk,而项目文件都是 utf-8。读取语料或输出结果时没指定编码就会翻车。解决:所有open()加encoding='utf-8',包括preprocess.py、dataset.py、predict.py。如果控制台输出乱码,在命令前加chcp 65001切到 utf-8 代码页。
4.3 现象:CRF 层报维度不匹配
原因:layers/CRF.py里转移矩阵的维度应该是num_tags x num_tags,如果config.py里num_tags写错,或者数据集标签数变了没同步,就会在初始化时报错。解决:先运行python -c "from config import Config; print(Config().num_tags)",再统计数据集实际标签种类数,两者必须相等。改完记得删掉旧的checkpoints。
4.4 现象:GPU 显存不足,batch_size 降到 1 还报 OOM
原因:max_seq_len设太大,或者 LSTM 的hidden_dim过高。中文 NER 句子一般不会超过 128 个字,设 256 甚至 512 会浪费大量显存。解决:把max_seq_len降到 128,hidden_dim从 512 降到 256,batch_size设 16。如果还不行,检查是不是在main.py里同时加载了 BERT 模型,BiLSTM-CRF 版本不需要 BERT,把相关 import 注释掉。
4.5 现象:server.py 启动后请求返回 500
原因:templates/目录下模板文件缺失,或者test_requests.py里的请求格式和server.py预期不一致。解决:先看server.py里@app.route定义的接口路径和参数名,再用curl或test_requests.py发一条测试请求。常见做法是 POST 一个 JSON,字段名要和server.py里request.json['text']对应。如果模板渲染报错,确认templates/index.html存在。
5. 进阶技巧:知识蒸馏与 ONNX 导出怎么用
5.1 用知识蒸馏把 BERT 能力迁移到 BiLSTM-CRF
项目里knowledge_distillation/kd.py是一个容易被忽略的加分模块。思路是:先用 BERT 模型在训练集上跑出软标签,再让 BiLSTM-CRF 去拟合这些软标签,相当于用大模型教小模型。这样既保留了 BERT 的泛化能力,又让推理阶段保持轻量。我一般会先跑一遍bert_ner_model.py生成软标签文件,再改kd.py里的teacher_path和student_config,最后启动蒸馏训练。注意温度参数temperature设 2 到 5 之间,太低退化成硬标签,太高软标签过于平滑。
5.2 导出 ONNX 并验证推理一致性
convert_onnx.py和convert_onnx/bert_ner_model_onnx.py提供了 ONNX 导出能力。ONNX 的好处是脱离 PyTorch 环境也能推理,适合部署到 C++ 或移动端。导出后必须做一致性验证,否则可能因为算子不支持导致结果偏差。
import onnxruntime as ort import numpy as np # 加载 ONNX 模型 sess = ort.InferenceSession("model.onnx") # 构造输入,注意 dtype 和 shape 要和导出时一致 input_ids = np.array([[1, 2, 3, 4]], dtype=np.int64) outputs = sess.run(None, {"input_ids": input_ids}) print(outputs[0].shape)参数说明:input_ids的 shape 是[batch_size, seq_len],dtype 必须是 int64。如果导出时用了 attention_mask,这里也要一并传入。验证方法:拿同一条文本分别跑 PyTorch 和 ONNX,对比输出标签序列是否完全一致。不一致就检查导出时的opset_version,常见做法是设 11 或 12,太低不支持某些算子。
5.3 数据增强扩充小样本数据集
data_augment/aug.py适合你手头数据量少的情况。常见做法包括同义词替换、随机插入、随机交换。我一般会控制增强比例在原始数据的 20% 到 30%,太多会引入噪声。增强后重新跑preprocess.py生成新训练集,再训练时观察验证集 F1 是否稳定。如果增强后 F1 反而降了,说明增强策略和当前数据分布不匹配,换一种方式或降低比例。
5.4 一个我坚持的习惯
从那以后我每次拿到这类课设代码包,都强制先跑一遍preprocess.py并打印前三条样本,确认字和标签对齐、没有乱码、标签体系正确,再动main.py。这个习惯帮我省掉了至少一半的“训练不收敛”排查时间。希望这份拆解能帮你顺利跑通自己的命名实体识别项目,把课设稳稳拿下。
本文还有配套的精品资源,点击获取