ARTICLE DETAIL

资讯详情

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

基于PaddleNLP的ERNIE标点恢复:中文文本自动加标点实战

基于PaddleNLP的ERNIE标点恢复:中文文本自动加标点实战 简介基于PaddleNLP的标点恢复punctuation restoration源码包面向自然语言处理初学者与工程开发者用于为无标点的预测文本自动添加中文标点。包内选用ernie_linear系列中文标点预训练模型覆盖不同推理档位并通过测试脚本快速完成效果验证开发者只需准备输入文本即可看到标注后的结果适合作为轻量插件嵌入文本预处理流程。资源共6个文件其中5个Python脚本分别承担模型初始化、推理预测、日志输出与工具函数等职责另含1个txt依赖清单整个压缩包仅7KB轻量且易于部署。目前已有478人学习下载适合希望快速掌握PaddleNLP标点预测流程或在其基础上进行二次开发的读者。通过阅读源码可以了解中文标点恢复任务的模型选型、推理调用方式以及工程化目录组织思路为后续接入实际文本预处理流程节省从零搭建的时间。1. 基于PaddleNLP的预测文本加标点这套源码把“标点恢复”跑通了语音识别、会议纪要、字幕工具出来的文本往往是一整行不带标点的裸文本直接发布没法看。做标点恢复Punctuation Restoration就是给这段文字补上逗号、句号、问号甚至顿号。手上这套基于 PaddleNLP 的预测文本添加标点符号源码核心是三个 ERNIE Linear 标点模型和两个 Python 脚本test.py 负责跑通流程infer.py 负责推理应用。模型已经训练好不需要再准备标注数据解压后装好依赖就能对中文文本补标点。适合做语音识别后处理、字幕清洗、文本挖掘预处理的 NLP 工程师也适合刚入门想理解序列标注怎么落地的读者。源码调用链很短真正花时间的地方在模型选型与文本清洗。2. 先跑通 test.py模型加载路径与 ERNIE 标点分类的原理2.1 解压后的目录与文件职责先看 punc.zip 解压后的全景。我拆这类源码包的第一反应是看文件命名因为ernie_linear_p7_wudao-punc-zh、ernie_linear_p3_wudao_fast-punc-zh、ernie_linear_p3_wudao-punc-zh这三个目录名字信息量很大。它们分别是 PaddleNLP 的 ERNIE Linear 标点模型权重目录p7和p3对应不同规模的模型配置wudao表示训练语料来自悟道开源中文文本fast说明这一版在推理速度上做了裁剪或加速处理。ernie_linear是一个 Python 包里面有__init__.py和ernie_linear.py负责定义模型结构、加载预训练参数和执行预测test.py是入口测试脚本infer.py是面向业务的推理脚本log.py提供统一日志输出requirements.txt记录依赖清单。这里要特别留意三个模型目录不是 Python 包它们存放的是model_state.pdparams格式的权重文件和词表加载时要交给 PaddleNLP 的from_pretrained机制去读。动手跑之前先讲原理。标点恢复本质上是一个序列标注任务输入一串不带标点的字模型判断每个字后面要不要加标点、加什么标点。ERNIE Linear 就是在 ERNIE 编码器后接一个线性分类层把每个位置的语义向量映射到标点类别上。类别一般包括 O不加标点、逗号、句号、问号、顿号。它跟正则加标点的做法不同标点位置是由上下文语义决定的模型是真的在“理解”这句话在说什么。2.2 安装依赖与跑通 test.py依赖安装是最容易翻车的一步。requirements.txt里锁定的核心包有两个paddlepaddle和paddlenlp。我建议不要直接在系统 Python 里装因为 PaddleNLP 生态跟其他深度学习框架经常在protobuf、numpy版本上互相打架。常用做法是先建一个虚拟环境再按 requirements 安装cd punc python -m venv venv source venv/bin/activate pip install -r requirements.txt命令逻辑不复杂进入解压目录创建并激活虚拟环境然后安装依赖。为什么坚持用虚拟环境因为标点恢复只用 PaddleNLP 这一套生态混装很容易出现“模块初始化失败”或“paddle.fluid 不存在”这类难排查的错误。装完依赖后直接执行python test.py如果控制台打印出了带标点的句子说明链路已经通了。test.py 内部流程一般是加载某个默认模型目录构造几个中文无标点样本调用模型预测再把结果打印出来。这个过程在 CPU 上也就一两秒不需要 GPU。Windows 下如果你的终端是 GBK 编码第一眼看到的可能是乱码这个问题在第 5 章的避坑记录里会专门讲。2.3 从代码理解 ERNIE Linear 的标点推理逻辑test.py 跑通后建议打开它把核心调用抽出来单独看。模型包的设计方式通常是这样import sys sys.path.insert(0, .) from ernie_linear import ErnieLinear texts [ 今天天气不错我们出去走走, 你吃饭了吗我记得你下午有个会议, ] model ErnieLinear.from_pretrained(ernie_linear_p3_wudao_fast-punc-zh) results model.predict(texts, batch_size32) for text, result in zip(texts, results): print(text) print(result)第一行把当前目录加入模块搜索路径让from ernie_linear import ErnieLinear能找到包from_pretrained接收的是模型目录名会读取目录下的权重文件和词表配置predict方法接收文本列表batch_size控制同时进入模型的样本数。默认 32 对短文本足够显存紧张时改成 8 或 16。这个环节容易出错的参数就是from_pretrained里的模型目录名写错一个字符都会加载失败路径里有中文或空格同样不行。如果你只是临时给文本加标点到这里已经够用。但想二次开发要明白predict返回的是完整的、已经加好标点的字符串而不是每个位置的分类标签。内部流程是先把句子按字切分转成 token id过 ERNIE 得到每个 token 的语义向量再用线性层算出每个位置属于“逗号/句号/顿号/O”的概率最后取概率最大的标点拼回文本。一个常见误用是拿它处理已经带标点的文本想优化旧标点。模型会把原有标点当成普通字符参与建模输出很可能把原来的标点全部打乱重排结果反而更奇怪。我一般只用它处理纯无标点文本。提示标点恢复只处理纯文本输入里如果有时间戳、HTML 标签、说话人标记先统一剥掉再送进模型。从这一步已经能看出这个源码包的边界是“预测并添加标点”不负责文本纠错也不负责分句排版。下一章看 infer.py它才是真正面向批量场景的入口。3. infer.py 推理脚本命令行批处理与日志输出3.1 infer.py 的输入输出约定test.py 是给你验证用的infer.py 才是干活的。两者差别在于 test.py 把样本写死在代码里修改文本要编辑文件infer.py 通过命令行参数接收输入可以直接对接文本文件、管道或其他程序。我拆过的标点恢复工程里infer.py 最常见约定是--model_dir指定模型目录--input指定输入文本文件--output指定输出文件也可能用--text直接传一句话。运行方式大致是python infer.py \ --model_dir ernie_linear_p3_wudao_fast-punc-zh \ --input raw_texts.txt \ --output punctuated.txt如果你拿到的源码包参数名不完全一样多半是--model_path或--model_name打开 infer.py 看一眼argparse那段就能确认。输入文件的读取方式很关键是“按行读取一行一条文本”还是“整个文件读成一个字符串”。两种实现都有直接决定输出粒度。按行读取模型会把每行当作独立样本来预测整个文件读成一个字符串模型会考虑跨行的长距离上下文但内存占用会高不少。我一般会优先选择按行读取因为更可控。还有一点容易被忽略infer.py 有没有if __name__ __main__入口保护。有保护的话可以from infer import main这样手动调用没有保护的话import 的瞬间整个推理流程就执行了。这是很多源码包没做好的细节日常工程里我会把它改成一个可导入的run_infer(model_dir, input_path, output_path)函数方便在调度平台里传参调用。3.2 批处理文本文件的方法实际使用中我一般不会把几十万字一次性塞给 infer.py而是先切块。ERNIE 对单句长度有上限超过 max_seq_len常见是 512 token会被截断截断后的后半句没有足够上下文标点预测质量明显下降。常见做法是先按长度切块再喂给模型。下面这段是处理字幕文本时常用的预处理import re def split_long_text(text, max_len180): text re.sub(r\s, , text) parts [] current for char in text: current char if len(current) max_len: parts.append(current) current if current: parts.append(current) return parts raw open(raw_texts.txt, encodingutf-8).read() chunks split_long_text(raw, max_len180) with open(chunks.txt, w, encodingutf-8) as f: for i, chunk in enumerate(chunks): f.write(chunk \n)split_long_text先把所有空白符去掉因为空格和换行会干扰 ERNIE 的字粒度切分然后按 180 字切块。180 这个值不是随手拍的它保证模型看到的上下文足够长同时给 token 化后的长度膨胀留出余量。如果一句话只有 30 个字没必要强行切块如果字幕文本里混了时间轴比如[00:12:34]必须在切块前用正则把时间戳剥掉否则模型会把时间戳字符当成正文输出标点时原样保留整段结果完全没法看。切块之后再次运行 infer.py就能保证每一行都是完整的可推理文本。批量任务跑完后我建议打开输出文件抽查前 100 行和最后 100 行确认没有因为切块导致句首句尾丢标点。3.3 log.py 与日志级别调整log.py 在这个包里存在的意义是统一日志输出格式。PaddleNLP 模型加载时会打印很多底层信息跟业务日志混在一起会很难排错。log.py 一般封装了标准库logging把logger.info、logger.warning、logger.error统一成带时间戳的格式。使用上我习惯在模型加载阶段用 INFO 级别看加载日志推理阶段调成 WARNING 减少刷屏。可以通过环境变量或脚本参数控制python infer.py --model_dir ernie_linear_p7_wudao-punc-zh \ --input chunks.txt --output result.txt --log_level WARNING日志级别设成 WARNING 后控制台只打印警告和错误适合后台批量跑如果某条结果明显不对再切回 DEBUG。DEBUG 级别下你会看到每个 token 每个类别的预测概率这是排查标点错位最直接的依据。比如某个逗号是模型在概率很低的条件下硬选出来的看日志就能发现。log.py 本身不参与模型计算但它是工程提效的重要零件。正式部署时我还会给 logger 加一个FileHandler把日志同时写进文件任务结束后可以回看完整记录不用依赖终端缓冲。4. 三个 ERNIE 标点模型怎么选p7、p3、fast 的取舍4.1 模型目录命名与本质差异源码包里给了三个权重目录这是我最感兴趣的部分。ernie_linear_p7_wudao-punc-zh、ernie_linear_p3_wudao-punc-zh、ernie_linear_p3_wudao_fast-punc-zh。从命名看p7 和 p3 对应不同规模的 ERNIE 编码器配置wudao 代表基于悟道中文语料做预训练fast 代表推理加速版本。要注意不要把 p7 理解成“7 层就一定比 p3 好”。在标点分类这种细粒度任务上更大模型不总是更准训练数据分布、学习率、下游微调策略同样关键。从工程角度选模型主要看三个维度准确率、推理速度、内存占用。p7 更重语义理解能力更强但时延和显存都更敏感p3 是中间档p3_fast 明显偏部署加载快、单条推理耗时低适合 CPU 环境。我实际测试时通常会拿同一句话分别跑三个模型看逗号、句号、问号的分布差异。预期里 p7 在最难的“问号/句号”区分上更稳p3_fast 在速度上领先但遇到长难句时标点位置可能不如 p7 自然。如果你的文本是口语化会议转写只要逗号和句号能分清楚其实没必要上 p7。4.2 精度、速度与显存的取舍实测下面这个对照表是我基于这类 PaddleNLP 标点模型的使用规律整理的具体数值会随硬件变化但相对关系基本稳定。CPU 环境下用十万条短文本批量测试p3_fast 通常比 p7 快三到五倍内存占用也低一截GPU 环境下差距会缩小但 p7 的显存占用依然明显更高。模型目录推理速度内存占用标点准确率特点适用场景ernie_linear_p7_wudao-punc-zh慢高句号/问号等复杂边界更稳离线批量精标、高质量语料生产ernie_linear_p3_wudao-punc-zh中等中基础逗号句号够用常规场景、在线单条预测ernie_linear_p3_wudao_fast-punc-zh快低轻度牺牲准确性CPU 部署、实时语音转写选型时还要看单条文本长度。短句场景下 p3 和 p7 的差距很小长文本时 p7 的优势才会体现出来因为长句的上下文依赖更强复杂模型能把前 300 字的信息有效保留到后 300 字。如果你的业务是短视频字幕每句不超过 30 字p3_fast 就是最优解没必要把算力浪费在更重的模型上。4.3 用代码做模型切换与基准对比选型不能靠猜建议写一个很小的对比脚本把三个模型都加载一遍用同一批测试样本跑然后对比输出。代码可以这样写from ernie_linear import ErnieLinear texts [ 这个项目下周上线你觉得风险大吗, 今天我们讨论了预算问题大家一致认为需要控制成本, ] for model_name in [ ernie_linear_p7_wudao-punc-zh, ernie_linear_p3_wudao-punc-zh, ernie_linear_p3_wudao_fast-punc-zh, ]: print(f模型: {model_name}) model ErnieLinear.from_pretrained(model_name) for text, result in zip(texts, model.predict(texts, batch_size16)): print( 原始:, text) print( 预测:, result)这段代码把三个模型串行跑一遍。注意每次循环都会重新加载模型内存不够时可以跑完一个模型记下结果再注释掉继续跑下一个。通过这种直观对比你会发现“觉得风险大吗”结尾到底是问号还是句号三个模型可能给出不同答案。这就是选型的依据问号召回率对你重要的选 p7只是为了字幕可读性p3_fast 完全够。这个对比脚本我后来一直留在项目里每次换权重都会重跑一遍防止新模型在某些句法结构上有明显回退。5. 标点恢复实战中的常见问题与避坑记录5.1 中文编码与乱码问题现象Windows 控制台运行 test.py打印出的中文或标点变成乱码部分环境直接报 UnicodeEncodeError。原因Windows 默认终端编码是 GBKPython 打印中文时用的是标准输出编码。如果PYTHONIOENCODING没有设成 utf-8遇到全角标点或生僻字就会出错。解决运行前先设置环境变量set PYTHONIOENCODINGutf-8 python test.pyLinux 和 macOS 下一般没事但如果你用 Docker 且基础镜像没配置 utf-8 locale也要在 Dockerfile 里加上ENV PYTHONIOENCODINGutf-8。遇到乱码先看编码别急着怀疑模型。5.2 模型加载失败与目录名问题现象from_pretrained(ernie_linear_p3_wudao_fast-punc-zh)报错提示目录不存在或找不到model_state.pdparams。原因运行脚本时的工作目录跟模型目录不在同一层或者模型目录名写错。PaddleNLP 的from_pretrained会先按给定路径找找不到再去缓存目录找同名预训练模型这两个行为容易混淆。另外目录名带中文时部分 Windows 环境也会加载异常。解决用绝对路径拼模型目录不要写相对路径import os model_dir os.path.join(os.path.dirname(__file__), ernie_linear_p3_wudao_fast-punc-zh) model ErnieLinear.from_pretrained(model_dir)os.path.dirname(__file__)取得脚本所在目录再拼接模型目录名这样不管从哪里启动都不会跑偏。相对路径在命令行能跑换到 subprocess 调用时工作目录一变就废了。5.3 标点错位、漏标与输入清洗现象输入文本里带空格或英文单词时输出标点出现明显错位本该在句尾的句号跑到句子中间或者漏掉关键逗号。原因ERNIE 是字粒度模型中文按字切分但英文单词和数字会被 tokenizer 切成子词空格会额外产生 token。模型训练时看到的大多是干净中文文本带空格的脏输入会破坏上下文分布导致标点分类概率异常。解决进模型前做清洗把全角空格转半角、连续空白合并、最终去掉所有空格def clean_text(text): text text.replace(\u3000, ) text .join(text.split()) text text.replace( , ) return text .join(text.split())把连续空白缩成一个空格随后直接去掉所有空格因为标点分类任务里空格没有信息量。如果你必须在英文单词间保留空格那需要把空格作为特殊 token 参与建模但这个源码包没有这个设计最稳妥就是全去掉。处理之后再看输出标点错位问题基本消失。5.4 性能问题与 batch_size 参数现象加载模型后第一次推理特别慢后面几条才恢复正常或者长文本一多内存持续上涨直到卡死。原因第一次推理包含模型加载和 warmup这部分耗时不可避免内存上涨通常是因为predict内部把整个文本列表一次性 token 化短文本还好长文本列表会保留大量中间张量。解决长文本先切块再调小batch_size。一条 1000 字的文本直接传给模型可能超过 max_seq_len 被截断CPU 上耗时可能按分钟计算。我一般先切到 200 字以内再用batch_size8或16分批推理内存稳定单个批次两三秒内完成。batch_size不是越大越好CPU 场景 816 合理GPU 场景 3264 要看显存容量。5.5 依赖版本冲突现象装完 requirements 后 import paddlenlp 报错提示paddle.fluid相关模块不存在或protobuf版本冲突。原因PaddlePaddle 2.5 以上跟部分低版本protobuf不兼容paddlenlp对paddlepaddle也有最低版本要求。机器上如果同时装了其他深度学习框架很可能把protobuf锁到老版本。解决严格用虚拟环境安装先pip show paddlepaddle paddlenlp确认版本。已经装乱的话最省事的是重建虚拟环境按 requirements 重装不要尝试逐个降级包。requirements.txt 里锁了版本就照抄没锁的话以paddlepaddle2.4和paddlenlp2.5为底线实测多数标点模型在这两个版本组合上能正常运行。6. 进阶把标点恢复封装成服务并验证结果前面几章把脚本跑通和常见坑都讲了最后这一步说说怎么把标点恢复做成真正可用的服务而不是只跑通测试样本。6.1 封装成 HTTP 标点恢复接口常见做法是写一个轻量 HTTP 接口把模型实例放在内存里避免每个请求都重新加载一次模型。用标准库http.server可以少引依赖核心逻辑就三件事启动时加载模型请求时调用predict结果以 JSON 返回。示例代码如下import json from http.server import BaseHTTPRequestHandler, HTTPServer from ernie_linear import ErnieLinear model ErnieLinear.from_pretrained(ernie_linear_p3_wudao_fast-punc-zh) class PunctuationHandler(BaseHTTPRequestHandler): def do_POST(self): length int(self.headers.get(Content-Length, 0)) payload json.loads(self.rfile.read(length)) texts payload.get(texts, []) results model.predict(texts, batch_size8) response json.dumps({results: results}, ensure_asciiFalse).encode(utf-8) self.send_response(200) self.send_header(Content-Type, application/json; charsetutf-8) self.send_header(Content-Length, str(len(response))) self.end_headers() self.wfile.write(response) if __name__ __main__: server HTTPServer((0.0.0.0, 8765), PunctuationHandler) server.serve_forever()这里ErnieLinear实例是全局的进程启动后只加载一次模型。do_POST接收 JSON 数组把文本列表交给predict再按 UTF-8 返回结果。一个容易漏的点是ensure_asciiFalse不加的话中文会被转成\uXXXX前端拿到的字符串不可读。batch_size8是防止并发请求把内存打满如果一次只传几句话这个值不敏感一次传几千句建议在外面按 200 句拆批再请求。6.2 验证标点恢复效果的经验服务跑起来之后不能只看“加了标点”就收工。我在这个地方吃过亏只测了新闻体文本后来接会议转写文本发现模型把很多不确定语气都标成了句号旁观者很难判断说话人真实意图。从那以后我每次做标点恢复验证都会准备三类样本一是新闻体检验常规效果二是带语气词的对话体检验问号召回三是带数字、英文的文本检验输入清洗是否彻底。然后人工对比三个模型的输出统计“句号错标成问号”“逗号漏标”这类错误数量。三类样本都过关我才会把模型推到生产环境。标点恢复这个任务看起来简单真正上线后会发现瓶颈常常不是模型本身而是输入清洗和分批策略。这套基于 PaddleNLP 的源码把预训练权重、ERNIE 编码、标点分类都封装好了你只需要在调用层做好工程化。每次新增语料我都会把 test.py 的输出当成回归测试基准防止后加的清洗逻辑把原本正常的样本弄坏。希望这些拆解对你跑通并部署这套标点源码有帮助。本文还有配套的精品资源点击获取
返回列表