news 2026/10/5 7:51:28

基于PaddleNLP的中文信息抽取:从Doccano标注到UIE模型部署全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于PaddleNLP的中文信息抽取:从Doccano标注到UIE模型部署全流程

简介:本资源面向自然语言处理初学者与信息抽取方向的开发者,提供一套基于PaddleNLP框架的完整中文实体识别项目实践。内容围绕Doccano标注工具构建中文实体识别数据集,并借助UIE-base预训练模型进行微调训练,最终实现从非结构化文本中自动提取姓名、地名、机构名等关键信息,覆盖数据准备、标注、审查到模型训练与部署的全流程。压缩包共23个文件,约74KB,以Python脚本为主,辅以txt说明文档、jsonl/json数据文件、yml配置、Dockerfile及docx附赠资料,结构清晰,便于按模块查阅与复现。目前已有134人学习下载。读者可获得可运行的微调训练代码、数据集样例、部署配置与使用说明,快速理解信息抽取项目的落地路径,适合作为课程设计、毕业设计或NLP入门实战的参考素材。

1. 从一堆简历和合同里抽姓名:这套中文信息抽取流水线到底怎么跑

手里有几百份中文简历、合同或者工单,老板让你把里面的人名、公司、时间、金额全抽出来做成结构化表格。正则写了几十条,换个模板就崩;人工复制粘贴,眼睛都快瞎了。这个场景下,基于 PaddleNLP 框架的信息抽取模型训练与部署就是一条能落地的路:用 Doccano 标一批中文实体识别数据,拿 UIE-base 预训练模型微调,最后封装成能批量跑的推理服务。整套流程不依赖 GPU 集群,一台带显卡的 Windows 或 Linux 机器就能起步。适合有 Python 基础、想从规则匹配升级到模型抽取的工程师,也适合需要快速验证业务可行性的算法同学。下面按我实际跑过的顺序,把数据标注、格式转换、微调训练、部署推理和踩坑点一次讲透。

2. 用 Doccano 构建中文实体识别数据集:从安装到导出

2.1 Windows 环境下部署 Doccano 的可行路径

Doccano 官方推荐 Linux + Docker,但很多同学习惯在 Windows 上干活。我试过三种方式,最稳的是Docker Desktop + WSL2 后端。先确认 Windows 版本在 10 2004 以上,然后装 Docker Desktop,安装时勾选 “Use WSL 2 based engine”。装完后在 PowerShell 里拉镜像:

docker pull doccano/doccano docker container create --name doccano \ -e "ADMIN_USERNAME=admin" \ -e "ADMIN_EMAIL=admin@example.com" \ -e "ADMIN_PASSWORD=password" \ -p 8000:8000 doccano/doccano docker container start doccano

浏览器打开http://localhost:8000,用上面设的账号登录。如果 8000 端口被占,把-p 8000:8000改成-p 9000:8000,访问时换端口即可。不想用 Docker 的话,pip 安装也能跑,但 Windows 下doccano依赖的psycopg2和nodejs容易出玄学问题,我一般直接 Docker 省事。

提示:Docker 容器删掉后数据会丢,生产标注前务必把doccano容器里的/data目录挂载到宿主机,命令里加-v D:\doccano_data:/data。

2.2 标注项目配置与实体标签设计

登录后点 “Create Project”,选 “Sequence Labeling”。项目名随便填,但标签体系要提前想清楚。中文实体识别常见标签有PERSON、ORG、TIME、MONEY、LOC。别一上来搞几十个标签,先覆盖业务里最高频的 5 到 8 类。在 “Labels” 页签里逐个添加,每个标签给一个短名和颜色。

导入数据时支持 JSONL、CoNLL、Plain Text。我一般把原始文本一行一条存成raw.txt,用 “Plain Text” 导入,每行成为一条待标注样本。标注界面里选中文字,点标签即可。标完几百条后,点 “Export Dataset”,选 “JSONL(TextLabel)” 格式导出,得到doccano_export.jsonl。

2.3 从 Doccano 导出格式转到 UIE 训练格式

Doccano 导出的 JSONL 长这样:

{"text": "张三于2023年加入北京智源研究院", "labels": [[0, 2, "PERSON"], [3, 8, "TIME"], [10, 16, "ORG"]]}

UIE 训练需要的是prompt + 答案的格式,每条样本要转成:

{"text": "张三于2023年加入北京智源研究院", "prompt": "人名", "result_list": [{"text": "张三", "start": 0, "end": 2}]}

写个转换脚本:

import json label_map = {"PERSON": "人名", "ORG": "组织机构", "TIME": "时间", "MONEY": "金额"} def convert(input_path, output_path): with open(input_path, "r", encoding="utf-8") as fin, \ open(output_path, "w", encoding="utf-8") as fout: for line in fin: item = json.loads(line) text = item["text"] # 按标签分组,同一标签的实体合并到一个 prompt 下 grouped = {} for start, end, label in item["labels"]: prompt = label_map.get(label, label) grouped.setdefault(prompt, []).append( {"text": text[start:end], "start": start, "end": end} ) for prompt, entities in grouped.items(): sample = { "text": text, "prompt": prompt, "result_list": entities } fout.write(json.dumps(sample, ensure_ascii=False) + "\n") convert("doccano_export.jsonl", "uie_train.json")

这段逻辑的核心是按 prompt 拆分样本:同一条文本里如果有人名和组织机构,会生成两条训练样本,分别对应不同 prompt。参数上注意start和end是字符级索引,Doccano 导出的就是字符级,不用额外转换。如果导出的是 “JSONL(TextLabel)” 以外的格式,索引可能是 token 级,需要先对齐。

3. UIE-base 微调训练:数据切分、参数设置与训练脚本

3.1 训练数据切分与负样本构造

把uie_train.json按 8:1:1 切成训练集、验证集、测试集。UIE 训练需要负样本,也就是 prompt 和文本里实际不存在的实体类型。PaddleNLP 的uie示例里通常用doccano转出来的正样本,再按比例随机替换 prompt 生成负样本。我一般用官方脚本uie/data_processor.py里的convert_example函数,它会自动处理。

如果自己写切分:

import json, random with open("uie_train.json", "r", encoding="utf-8") as f: data = [json.loads(line) for line in f] random.seed(42) random.shuffle(data) n = len(data) train, dev, test = data[:int(0.8*n)], data[int(0.8*n):int(0.9*n)], data[int(0.9*n):] for name, subset in [("train", train), ("dev", dev), ("test", test)]: with open(f"{name}.json", "w", encoding="utf-8") as f: for item in subset: f.write(json.dumps(item, ensure_ascii=False) + "\n")

注意:随机种子固定住,否则每次切分结果不同,模型效果没法复现。负样本比例控制在正样本的 1 到 2 倍,太多会让模型倾向于预测 “无实体”。

3.2 UIE-base 微调的关键参数

PaddleNLP 里 UIE 微调通常用paddlenlp/transformers/uie下的model.py和run_uie.py。我用的启动命令:

python -u run_uie.py \ --model_name_or_path uie-base \ --train_path train.json \ --dev_path dev.json \ --save_dir ./uie_checkpoint \ --learning_rate 1e-5 \ --batch_size 16 \ --max_seq_len 256 \ --num_epochs 20 \ --seed 42 \ --logging_steps 10 \ --eval_steps 100 \ --save_steps 100 \ --device gpu

几个参数我踩过坑:

  • learning_rate:UIE-base 微调用 1e-5 到 3e-5,再大容易把预训练学到的语义冲掉,表现为验证集 F1 先升后降。
  • batch_size:显存 8G 时设 16 比较稳,12G 以上可以到 32。太小梯度噪声大,太大收敛慢。
  • max_seq_len:中文实体识别一般 256 够用,文本特别长再调到 512,但显存翻倍。
  • num_epochs:小数据集 500 条以下,20 到 30 轮;几千条的话 10 轮左右就够,看验证集 F1 不再涨就停。

训练日志里重点看eval_f1,如果连续几个 eval 点都不涨,可以提前停。UIE 的 loss 一开始在 0.5 左右,正常会降到 0.05 以下。

3.3 训练完怎么验证模型有没有学到东西

训练结束后,用run_uie.py的--do_predict模式在测试集上跑一遍:

python -u run_uie.py \ --model_name_or_path ./uie_checkpoint \ --test_path test.json \ --do_predict \ --device gpu

输出里会有 precision、recall、F1。中文实体识别任务,500 条标注数据、5 个标签,F1 能到 0.75 以上就算可用。如果 F1 低于 0.6,先检查标注质量:Doccano 里有没有漏标、错标,标签边界有没有切错。我遇到过把 “北京市” 标成LOC但只选了 “北京”,模型学出来边界一直偏,后来统一标全称才正常。

4. 部署推理:把微调后的 UIE 模型封装成批量抽取服务

4.1 用 PaddleNLP Taskflow 做快速推理

PaddleNLP 提供了Taskflow接口,加载微调后的模型只要几行:

from paddlenlp import Taskflow schema = ["人名", "组织机构", "时间", "金额"] ie = Taskflow("information_extraction", model="./uie_checkpoint", schema=schema) texts = [ "李四于2022年加入上海某某科技有限公司,年薪50万元", "王五在2021年从深圳大学毕业后进入腾讯工作" ] results = ie(texts) for text, res in zip(texts, results): print(text) print(res)

schema就是训练时用的 prompt 列表,顺序无所谓,但必须和训练时的标签语义一致。model参数指向保存的 checkpoint 目录。输出是每个文本对应的实体列表,包含text、start、end、probability。

4.2 批量推理的性能调优

Taskflow 默认单条推理,几百条文本会慢。可以设batch_size:

ie = Taskflow("information_extraction", model="./uie_checkpoint", schema=schema, batch_size=32)

另外device参数可以指定gpu或cpu。CPU 上跑 UIE-base,一条 256 长度的文本大概 200ms,GPU 上 20ms 左右。如果只是离线批量处理,CPU 也能接受;要做在线接口,建议 GPU。

提示:Taskflow 第一次加载模型会下载预训练权重,如果服务器没外网,提前把uie-base模型文件放到本地,用model参数指向本地路径。

4.3 封装成 HTTP 接口的简单做法

用 FastAPI 包一层:

from fastapi import FastAPI from pydantic import BaseModel from paddlenlp import Taskflow app = FastAPI() schema = ["人名", "组织机构", "时间", "金额"] ie = Taskflow("information_extraction", model="./uie_checkpoint", schema=schema, batch_size=16) class Request(BaseModel): texts: list[str] @app.post("/extract") def extract(req: Request): results = ie(req.texts) return {"results": results}

启动:uvicorn main:app --host 0.0.0.0 --port 8080。请求时 POST 一个 JSON,{"texts": ["...", "..."]}。这个接口没有鉴权,内网用没问题,公网部署要加 token 校验。

5. 避坑与排查:标注、训练、部署里最容易翻车的 5 个点

5.1 标注边界不一致导致模型学偏

现象:训练 loss 正常下降,但验证集 F1 卡在 0.5 左右上不去。
原因:同一类实体在不同样本里标注边界不统一,比如 “北京市朝阳区” 有时标全称,有时只标 “北京”。模型看到矛盾信号,学出来的边界模糊。
解决:标注前写一份标注规范,明确每个标签的边界规则。已经标完的,用脚本统计同一标签下实体长度分布,把明显偏短的挑出来重新标。

5.2 Doccano 导出索引与文本编码不一致

现象:转换后的start、end切出来的文字和标注的不一样,甚至报越界。
原因:Doccano 导出的索引是字符级,但如果文本里有 emoji 或特殊符号,Python 的len()和 Doccano 内部计数可能差一位。
解决:转换脚本里加断言assert text[start:end] == entity_text,不通过就打印出来人工核对。导入 Doccano 前把文本里的 emoji 和不可见字符清掉。

5.3 学习率过大导致预训练知识被冲掉

现象:训练前几个 step loss 降到很低,但验证集 F1 反而比没微调时还差。
原因:学习率设成 1e-3 或 1e-4,UIE-base 的预训练权重被大幅更新,语义理解能力丢失。
解决:UIE 微调学习率控制在 1e-5 到 3e-5。如果已经跑飞了,加载uie-base原始权重重新训,别在坏 checkpoint 上继续。

5.4 显存不足导致训练中断

现象:跑几个 step 后报Out of memory。
原因:batch_size或max_seq_len设太大,或者同时开了多个训练进程。
解决:先把batch_size减半,再不行把max_seq_len从 512 降到 256。PaddleNLP 支持梯度累积,用--gradient_accumulation_steps 2可以在小 batch 下模拟大 batch 效果。

5.5 部署时 schema 与训练标签不匹配

现象:推理结果为空,或者实体类型全错。
原因:Taskflow 的schema写的是 “人名”,但训练时 prompt 用的是 “姓名”。UIE 靠 prompt 语义匹配,差一个字效果就差很多。
解决:把训练数据里的 prompt 去重列出来,部署时schema严格照抄。建议训练脚本里把 prompt 列表存成label_config.json,部署时直接读。

6. 进阶技巧:用数据增强和 prompt 调优把 F1 再拉高 5 个点

标注数据不够是常态。我一般用两种方式扩数据:同义词替换和实体替换。同义词替换是把文本里的非实体词用近义词换掉,比如 “加入” 换 “入职”、“毕业于” 换 “出自”。实体替换是把标注好的实体换成同类别的其他实体,比如 “张三” 换 “李四”,“北京” 换 “上海”。这样不改变标签边界,但增加了文本多样性。

import random, json person_pool = ["张三", "李四", "王五", "赵六"] org_pool = ["某某科技", "某某集团", "某某研究院"] def augment(item): text = item["text"] new_entities = [] for ent in item["result_list"]: if item["prompt"] == "人名": new_ent = random.choice(person_pool) elif item["prompt"] == "组织机构": new_ent = random.choice(org_pool) else: new_ent = ent["text"] # 替换后重新计算 start/end start = text.find(ent["text"]) if start == -1: continue text = text[:start] + new_ent + text[start+len(ent["text"]):] new_entities.append({"text": new_ent, "start": start, "end": start+len(new_ent)}) item["text"] = text item["result_list"] = new_entities return item

这个脚本对每条样本生成一条增强样本,训练集直接翻倍。注意替换后要重新算索引,而且find只找第一个匹配,如果同一实体出现多次,得用循环处理。

另一个技巧是prompt 调优。UIE 对 prompt 措辞敏感,比如 “人名” 和 “人物姓名” 效果可能差 2 到 3 个点。我一般准备 3 到 5 个候选 prompt,在验证集上各跑一遍,选 F1 最高的。候选包括:“人名”、“人物”、“姓名”、“人名和角色”。组织机构同理:“组织机构”、“公司”、“单位”、“组织”。这个步骤不额外训练,只是推理时换 schema,成本很低。

最后说个血泪经验:别在标注数据少于 200 条的时候硬训。UIE-base 虽然强,但 200 条以下微调基本学不到稳定模式,F1 波动很大。我一般先标 300 条跑一版,看哪些标签错得多,再针对性补标 100 到 200 条,第二轮效果通常明显提升。模型部署上线后,把线上预测置信度低于 0.6 的样本捞出来人工复核,复核完的加入训练集再训一轮,两三轮下来 F1 能涨 5 到 8 个点。这套流程我跑了不下十次,每次都是标注、训练、部署、回流、再训练,没有捷径,但每一步都算数。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 7:50:49

Kubernetes DiskPressure 排查与根治:从驱逐机制到生产实践

凌晨两点四十,值班群炸了。订单服务连续被驱逐,Prometheus 弹出一片 NodeCondition 告警,逐条点开都是同一句话:The node had condition: [DiskPressure]。登录节点一看,根分区使用率 97%,kubectl get even…

作者头像 李华
网站建设 2026/10/5 7:50:46

ponytail插件模式:轻量级VS Code技能化开发实践

1. 这不是发型,是开发者圈里悄悄传开的“ ponytail ”——一个被误读却极其实用的轻量级插件生态最近在几个前端技术群和 GitHub issue 页里频繁刷到ponytail这个词,有人问“ponytail skill 是什么技能”,有人搜“ponytail 插件怎么装”&…

作者头像 李华
网站建设 2026/10/5 7:50:38

OpenShell 使用指南:Windows 开始菜单替代与效率定制

1. 从零认识 OpenShell:它到底解决什么问题第一次听到 OpenShell 这个名字,很多人会下意识以为它跟某个操作系统内核或者终端工具有关。实际上,OpenShell 是一个面向 Windows 平台的开始菜单替代与增强工具,最早脱胎于经典工具 Cl…

作者头像 李华
网站建设 2026/10/5 7:49:03

Java就业信息管理系统源码解析:从环境搭建到二次开发避坑指南

简介:这是一套基于Java技术栈的就业信息管理系统完整源码,采用前后端分离架构,前端使用Vue.js,后端基于Spring Boot,数据库为MySQL,适合计算机专业学生、开发者用于课程设计、毕业设计或企业级数据管理场景…

作者头像 李华