尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

基于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_USERNAMEadmin \ -e ADMIN_EMAILadminexample.com \ -e ADMIN_PASSWORDpassword \ -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, encodingutf-8) as fin, \ open(output_path, w, encodingutf-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_asciiFalse) \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, encodingutf-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, encodingutf-8) as f: for item in subset: f.write(json.dumps(item, ensure_asciiFalse) \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_rateUIE-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, schemaschema) 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_sizeie Taskflow(information_extraction, model./uie_checkpoint, schemaschema, batch_size32)另外device参数可以指定gpu或cpu。CPU 上跑 UIE-base一条 256 长度的文本大概 200msGPU 上 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, schemaschema, batch_size16) 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-4UIE-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[startlen(ent[text]):] new_entities.append({text: new_ent, start: start, end: startlen(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 个点。这套流程我跑了不下十次每次都是标注、训练、部署、回流、再训练没有捷径但每一步都算数。希望帮到你。本文还有配套的精品资源点击获取
返回列表