
简介本资源面向自然语言处理初学者与信息抽取方向开发者提供一套基于PaddleNLP框架的完整中文实体识别项目实践。内容围绕Doccano标注工具构建中文实体识别数据集并借助UIE-base预训练模型进行微调训练最终实现从非结构化文本中自动提取姓名、地名、机构名等关键信息可应用于知识图谱构建、问答系统等场景。压缩包共23个文件约74KB以py训练与推理脚本、txt数据与说明文档、jsonl标注数据、yml配置、Dockerfile部署文件及docx附赠资料为主覆盖数据准备、标注审查、模型微调、部署应用全流程。目前已有134人学习下载。读者可获得可运行的微调代码、标注数据样例、部署配置与使用说明便于快速复现并迁移到自身业务中。1. 从一堆简历里自动抽姓名这套 PaddleNLP UIE 方案到底能不能落地上周帮朋友处理一批中文简历两千多份纯文本要从每份里把姓名、电话、学历、工作单位抠出来。人工干了两天眼睛都花了还漏了一堆。这种场景其实特别典型——非结构化文本里藏着结构化信息靠正则写规则遇到格式一变就翻车。这套基于 PaddleNLP 的信息抽取项目核心就是用 Doccano 标一份中文实体识别数据集再拿 UIE-base 预训练模型微调最后封装成能直接调用的推理脚本。它适合两类人一类是手头有几百上千条中文文本、想快速跑通实体抽取全流程的工程师另一类是想搞明白 UIE 微调到底怎么配参数、Doccano 导出的数据怎么转成训练格式的从业者。项目包里带了 finetune.py、doccano.py、usemodel.py 这些脚本还有 train.txt、dev.txt、test.txt 样例数据基本是照着改改就能用的状态。下面我按自己拆包复现的顺序把标注、转换、微调、部署这条链路捋一遍顺带把踩过的坑标出来。2. 用 Doccano 标一份能喂给 UIE 的数据集从安装到导出的完整链路2.1 为什么选 Doccano 而不是自己写标注脚本中文实体识别数据集构建最原始的做法是拿 Excel 一行行标标完再写脚本转格式。我试过标到第三百条的时候Excel 已经卡得打不开而且多人协作根本没法合并。Doccano 解决的就是这个问题——它是个开源的文本标注工具支持序列标注、文本分类、序列到序列这几类任务实体识别正好对应序列标注。它的数据存在后端数据库里多人可以同时标标完直接导出 JSONL省掉了手工合并的环节。选它的另一个理由是导出格式和 PaddleNLP 的 UIE 微调脚本能对上。Doccano 导出的 JSONL 里每条样本带text和label两个字段label里是实体列表每个实体有start_offset、end_offset、label三个值。UIE 的doccano.py脚本就是读这个结构转成模型训练需要的prompt和result_list格式。如果你用别的标注工具导出格式对不上还得自己写转换层多一道工序就多一个出错点。安装这块官方推荐 Docker 起服务但很多人卡在doccano安装这一步。我一般直接用 pip 装省事# 建个干净的虚拟环境避免和现有依赖打架 python -m venv doccano_env source doccano_env/bin/activate # Windows 下用 doccano_env\Scripts\activate # 安装 doccano指定版本避免自动升级到不兼容的新版 pip install doccano1.8.4 # 初始化数据库这一步会建表 doccano init # 创建管理员账号按提示输用户名密码 doccano createuser --username admin --password pass1234 # 启动 Web 服务默认监听 8000 端口 doccano webserver --port 8000这里有个细节doccano init必须在createuser之前跑顺序反了会报数据库表不存在的错。启动之后浏览器开http://localhost:8000用刚建的账号登录。如果 8000 端口被占换--port 8001就行但后面导出数据时记得端口号别写错。2.2 标注配置与导出格式的对应关系登录进去第一件事是建项目。选「序列标注」类型然后在标签配置里把你要抽的实体类型加进去。比如抽简历就加姓名、电话、学历、工作单位这几个标签。标签名建议用中文因为 UIE 的 prompt 构造会直接拿标签名拼句子中文标签在中文语料上效果更稳。标的时候有个习惯我强烈建议养成每标完一批比如 200 条就导出一次 JSONL 备份。Doccano 的数据库偶尔会因为并发写入出问题我遇到过标了四百多条结果页面刷新后数据回滚的情况血泪经验。导出路径在项目页面的「导出数据集」里选 JSONL 格式。导出的文件长这样{text: 张三男1988年生毕业于清华大学计算机系。, label: [{start_offset: 0, end_offset: 2, label: 姓名}, {start_offset: 12, end_offset: 16, label: 学历}]}注意start_offset和end_offset是字符级偏移不是字节偏移。中文一个字算一个字符这个和 Python 字符串索引一致转换时不用额外处理编码。但如果你标的是英文混合文本得确认 Doccano 的偏移计算方式和你后续脚本一致否则会出现实体错位。2.3 用 doccano.py 把标注数据转成训练格式项目包里的doccano.py就是干转换这件事的。它读 Doccano 导出的 JSONL输出train.txt、dev.txt、test.txt三个文件格式是 UIE 微调脚本能直接吃的。调用方式python doccano.py \ --doccano_file ./doccano_export.jsonl \ --save_dir ./data \ --task_type ext \ --splits 0.8 0.1 0.1 \ --negative_ratio 5参数逐个说--doccano_file指向导出的 JSONL--save_dir是输出目录脚本会自动建--task_type ext表示信息抽取任务别写成cls或nerUIE 的脚本只认ext--splits是训练/验证/测试的切分比例三个数加起来得等于 1--negative_ratio控制负样本比例默认 5 表示每一条正样本配五条负样本这个值调大有助降低误召回但太大会拖慢训练。转换完打开train.txt看一眼每行是一个 JSON结构大概是{prompt: 张三男1988年生毕业于清华大学计算机系。, result_list: [{text: 张三, start: 0, end: 2}], prompt_start: 0}这里prompt就是原始文本result_list是实体列表。UIE 的训练脚本会拿prompt和标签名拼成「文本中姓名的部分是」这种问句形式所以标签名在转换阶段其实已经隐式编码进去了。如果你发现转换后的实体类型丢了检查 Doccano 导出时标签名有没有写对。3. UIE-base 微调参数怎么设、显存怎么省、效果怎么盯3.1 UIE 模型的结构特点与微调策略选择UIE-base 是百度提出的统一信息抽取模型底层是 ERNIE 3.0 的编码器参数量大概 1.18 亿。它的核心思路是把信息抽取统一成「文本到结构」的生成任务——给定一段文本和一个 prompt比如「抽取出文本中的姓名」模型输出对应的实体片段。这种设计的好处是同一个模型能同时处理实体识别、关系抽取、事件抽取不用为每个任务单独设计输出层。微调的时候UIE 的finetune.py脚本会把 prompt 和文本拼在一起送进模型计算 token 级别的损失。和常规分类任务不同它的输出是 span 的起止位置所以评估指标用的是精确率、召回率和 F1而不是准确率。这意味着如果你的数据里负样本太少模型会倾向于多召回精确率掉得厉害负样本太多又可能漏掉一些边界模糊的实体。我一般会先跑一轮默认参数看 baseline再根据 F1 调negative_ratio和学习率。默认学习率是 1e-5对 UIE-base 来说偏保守如果数据量在两千条以上可以试 2e-5 或 3e-5但再大就容易震荡。batch size 默认 16显存不够就降到 8同时把max_seq_len从 512 降到 256能省不少显存。3.2 finetune.py 的关键参数与训练命令训练命令长这样python finetune.py \ --train_path ./data/train.txt \ --dev_path ./data/dev.txt \ --save_dir ./checkpoint \ --learning_rate 2e-5 \ --batch_size 8 \ --max_seq_len 256 \ --num_epochs 10 \ --model uie-base \ --seed 1000 \ --logging_steps 10 \ --save_steps 100 \ --device gpu--train_path和--dev_path指向doccano.py生成的文件--save_dir是模型保存路径每--save_steps步存一个 checkpoint--model指定基座模型写uie-base会自动从 PaddleNLP 的模型库拉取第一次跑会下载几百兆的权重网络不稳的话建议提前手动下好放到缓存目录--seed固定随机种子方便复现--device写gpu或cpu有卡就写gpuCPU 训练会慢到怀疑人生。训练过程中重点盯eval_f1这个指标。如果它在前几轮就冲到 0.9 以上然后不动了大概率是数据里有重复样本或者标签泄露如果一直低于 0.5先检查doccano.py转换后的train.txt里实体偏移对不对再检查标签名有没有拼错。我遇到过标签写成「姓名 」带了个空格结果模型死活学不会的情况排查了半天。3.3 用 usemodel.py 做推理与批量抽取训练完的 checkpoint 用usemodel.py加载推理。这个脚本封装了模型加载、prompt 构造和结果解析调用方式python usemodel.py \ --model_path ./checkpoint/model_best \ --input_file ./test_samples.txt \ --output_file ./extract_results.json \ --batch_size 16 \ --max_seq_len 256--model_path指向保存的最优模型目录一般是model_best--input_file是待抽取的纯文本文件一行一条--output_file是输出 JSON每条结果里带原文和抽出的实体列表。如果只想测单条文本可以进 Python 交互环境from paddlenlp import Taskflow # 加载微调后的模型schema 里写你要抽的实体类型 schema [姓名, 电话, 学历, 工作单位] ie Taskflow(information_extraction, model./checkpoint/model_best, schemaschema) # 单条推理 result ie(李四联系方式13800138000硕士学历就职于某互联网公司。) print(result)输出会是{姓名: [{text: 李四, start: 0, end: 2, ...}], 电话: [...], ...}这种结构。注意schema里的标签名必须和训练时 Doccano 里配的完全一致差一个字都会导致对应实体抽不出来。批量跑的时候batch_size别设太大UIE 的推理显存占用比训练还高16 在 8G 卡上已经接近上限。4. 避坑与排查标注、转换、训练三个环节的翻车记录4.1 现象Doccano 导出 JSONL 后doccano.py 报 KeyError原因Doccano 不同版本导出的 JSONL 字段名有差异。1.8 之前的版本用labels而不是label而且实体偏移字段可能叫start_offset也可能叫offset。项目里的doccano.py是按某个特定版本写的版本对不上就报键不存在。解决先打开导出的 JSONL 看第一行的字段名如果和脚本预期的不一致要么改脚本里的字段映射要么在 Doccano 里换导出格式。我一般直接在doccano.py里加一层兼容# 兼容不同版本的字段名 label_key label if label in item else labels for entity in item[label_key]: start entity.get(start_offset, entity.get(offset)) end entity.get(end_offset, entity.get(offset) len(entity[text]))4.2 现象训练 loss 正常下降但 eval_f1 始终为 0原因dev.txt里的样本格式和train.txt不一致最常见的是result_list为空或者prompt字段缺失。UIE 的评估脚本会跳过没有实体的验证样本如果整个dev.txt都是空实体F1 自然算不出来。解决打开dev.txt检查前几行确认每条都有非空的result_list。如果doccano.py切分时把正样本全分到训练集了调--splits比例或者手动从训练集里挪几条到验证集。另外检查negative_ratio是不是设得太大导致验证集里混入了大量负样本。4.3 现象推理时实体边界多一个字或少一个字原因UIE 的 span 解码依赖 tokenizer 的偏移映射如果max_seq_len截断了文本或者文本里有特殊字符比如全角空格、emoji偏移会错位。解决推理前先做文本清洗把连续空格、换行符、特殊符号统一处理掉。max_seq_len设成 256 时超过这个长度的文本会被截断截断点之后的实体抽不出来。如果文本普遍较长要么调大max_seq_len显存允许的话要么先分句再逐句抽取。4.4 现象模型在测试集上 F1 很高但实际用的时候误召回一堆原因训练数据里的负样本比例太低模型没见过足够多的「非实体」样本导致它在真实文本上把很多无关片段也标成实体。解决调大doccano.py的--negative_ratio从默认 5 提到 10 甚至 15重新生成训练数据再微调。另一个办法是在推理时加置信度阈值Taskflow返回的结果里每个实体带probability字段低于 0.5 的直接过滤掉。4.5 现象换了一台机器跑 finetune.py报显存不足原因不同机器的 GPU 显存不一样默认batch_size16和max_seq_len512在 8G 卡上跑不动。解决按显存阶梯降配。8G 卡用batch_size8、max_seq_len2564G 卡用batch_size4、max_seq_len128。如果还是不够开梯度累积--batch_size 4 --gradient_accumulation_steps 4这样等效 batch size 还是 16但显存占用按 4 算。5. 把模型塞进 Docker 并压到 300MB部署阶段的几个硬技巧训练完的模型要给别人用最省事的办法是打成 Docker 镜像。项目包里有个Dockerfile但默认写法会把整个 PaddlePaddle 和模型权重都塞进去镜像轻松上 2G。我一般会做三层裁剪。第一层换基础镜像。别用paddlepaddle/paddle:latest那个带了一堆用不上的算子库。用python:3.8-slim自己装paddlepaddle-gpu或paddlepaddleCPU 版体积能少一半。第二层模型权重转成 inference model。训练保存的 checkpoint 带优化器状态和梯度推理用不上。用 PaddleNLP 的导出工具转一下from paddlenlp.transformers import UIE model UIE.from_pretrained(./checkpoint/model_best) model.save_pretrained(./inference_model)转完的inference_model目录里只有model_state.pdparams和配置文件体积从几百兆压到一百多兆。第三层Dockerfile 里只拷推理脚本和模型不拷训练数据。写个最小化的FROM python:3.8-slim WORKDIR /app # 只装推理需要的依赖paddlenlp 指定版本 RUN pip install --no-cache-dir paddlenlp2.5.2 paddlepaddle2.4.2 # 拷推理模型和脚本 COPY ./inference_model /app/inference_model COPY ./usemodel.py /app/usemodel.py # 暴露服务端口如果用 Flask 封装的话 EXPOSE 5000 CMD [python, usemodel.py, --model_path, /app/inference_model, --serve]这样打出来的镜像大概 300-400MB推送到镜像仓库也快。如果要用 HTTP 服务对外提供接口在usemodel.py里加个 Flask 路由把Taskflow的推理结果包成 JSON 返回就行。注意 Flask 默认是单线程的并发上来会排队生产环境建议用 gunicorn 起多个 worker每个 worker 加载一份模型显存够的话开 2-4 个。从那以后我每次部署前都强制走一遍「导出 inference model → 本地起容器测通 → 再推仓库」这个流程直接推训练 checkpoint 进镜像的亏我吃过一次就够了。希望帮到你。本文还有配套的精品资源点击获取