ARTICLE DETAIL

资讯详情

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

Pycorrector+ChatGLM3-6B:构建中文文本纠错管线的实践

Pycorrector+ChatGLM3-6B:构建中文文本纠错管线的实践 简介面向自然语言处理开发者和研究人员提供基于ChatGLM3-6B大模型与Pycorrector开源库的中文文本纠错完整实战方案。该方案可应用于文本编辑、校对、翻译等场景帮助减少人工复核成本。内容从原理讲解到系统测试覆盖数据处理、模型选择、训练与评估、接口部署等关键环节并提供可直接运行的源码与流程教程。压缩包共62个文件以Python脚本23个py和编译输出的pyc为主另有Shell启动脚本、JSON配置、训练数据集、模型结构/训练曲线截图、Gradio演示及Jupyter Notebook样例整体仅27.33MB目录划分清晰便于按模块研读和二次开发。项目内置gradio_demo、pycorrector_server等模块还展示了ChatGLM3-6B的LoRA微调过程。目前已有429人学习/下载适合希望快速搭建中文纠错系统并理解大模型落地流程的研发人员。1. 文本纠错叠上ChatGLM3-6B和Pycorrector到底在纠什么错你在清洗客服工单、OCR扫描件或语音转写文本时最头疼的往往不是“完全不认识”的错字而是那种“半对半错”的句子——字面看着通顺一读意思不对。文本纠错要解决的就是把这种错误拉回原意而标题里这套方案把Pycorrector和ChatGLM3-6B放在一起本质上是在回答一个问题传统的规则/统计纠错工具负责“快”大模型负责“懂”两者怎么配合才不互相拖后腿。适合谁做内容质检、知识库入库、NLP数据清洗的工程师以及想在自己业务里落地一个“够用且不烧钱”纠错服务的团队。先说结论这类项目难点不在跑通而在误伤率。2. 先让Pycorrector跑通这个纠错套件到底能干什么2.1 装好环境用一条带错句子验证最小流程Pycorrector是shibing624开源的纠错工具箱它最大的价值不是某个模型多强而是把“检测-纠错-过滤”这一套流程给你封装好了。先把它跑起来后面所有管线都建立在它之上。pip install pycorrector装完之后用一条带常见错字的句子验证一下from pycorrector import MacBertCorrector m MacBertCorrector() text 今天天汽很好我们出去挖土。 corrected, detail m.correct(text) print(corrected) # 期望输出中“天汽”变为“天气” print(detail) # 输出每个错误位置、原文和修正建议MacBertCorrector是Pycorrector里基于MacBERT的中文纠错模型在字级错误上表现稳定。注意correct()返回两个东西一个是修正后的完整句子一个是detail列表。detail里每个元素大概是[错误词, 开始位置, 结束位置, 修正词]这个结构在后面做置信度判断时非常有用。如果你只是做快速验证也可以直接用自带的Corrector()它走的是规则统计混合路线不需要下载模型权重速度更快但效果弱一些。正式项目里我一般直接上MacBertCorrector或者CSC系列的模型后面接大模型时这个前置模型的“误报率”直接决定整个管线的开销。2.2 Pycorrector的几种纠错策略以及它管不到哪类错Pycorrector不是单一模型它是一组策略的集合理解这些策略才能知道它哪里强、哪里弱。策略原理典型适用场景短板混淆集预置常见错别字映射表例如“天汽→天气”高频别字、拼音输入错误覆盖有限遇到生僻错法直接哑火语言模型候选用BERT类模型对当前位置做mask预测取概率最高的候选形近字、语序问题对长尾专有名词误伤严重规则过滤标点、空格、全半角、非法字清洗文本规范化不是真正“纠错”只是预处理实际跑一遍你会发现Pycorrector擅长的是“字级替换”——某个字确实写错了换上另一个字句子就通了。但遇到下面这类情况它就无能为力了“他把文件给了张总张总看完后表示明天再给李总确认” —— 两个“总”都没错但指代关系要懂业务的人才能判断“我去年去了趟云南感觉非常好今年还想再去” —— 没有错字但语义冗余需要改写。这就是为什么要在它后面接ChatGLM3-6BPycorrector负责把“字典里能查到的错”快速过滤掉大模型负责处理“上下文层面的别扭”。2.3 把Pycorrector包成HTTP服务别让它在进程里裸奔很多人在Notebook里跑通Pycorrector就直接写业务代码结果每次调用都要重新加载模型推理几秒、加载几十秒。正确做法是把它包成一个独立的HTTP服务模型常驻内存。from fastapi import FastAPI from pydantic import BaseModel from pycorrector import MacBertCorrector app FastAPI() m MacBertCorrector() class TextIn(BaseModel): text: str class TextOut(BaseModel): corrected: str detail: list app.post(/correct, response_modelTextOut) def correct(item: TextIn): corrected, detail m.correct(item.text) return TextOut(correctedcorrected, detaildetail) # 启动uvicorn pycorrector_server:app --host 0.0.0.0 --port 8001这里我把模型初始化放在了模块顶层保证进程启动后只加载一次。detail直接透传出去后面做门槛判断时不用再重新检测一遍。FastAPI的/correct接口最好设置一个超时时间因为MacBertCorrector在CPU上对长文本推理可能超过1秒接口调用方要有重试机制。这个服务化的步骤不是为了好看是为了后续在它前面加业务规则、在它后面接ChatGLM3-6B时模块边界清楚出了故障能定位到具体环。3. ChatGLM3-6B接入纠错任务先从提示词开始再谈要不要微调3.1 用transformers加载ChatGLM3-6B跑通一个纠错PromptPycorrector服务跑通后第二步是把ChatGLM3-6B拉进来。先用transformers加载跑通一条最小推理路径import torch from transformers import AutoTokenizer, AutoModel tokenizer AutoTokenizer.from_pretrained(THUDM/chatglm3-6b, trust_remote_codeTrue) model AutoModel.from_pretrained( THUDM/chatglm3-6b, trust_remote_codeTrue, torch_dtypetorch.float16, device_mapauto ).eval() prompt 你是一个中文文本纠错助手。只修正输入文本中的错别字和明显语病不要改变原意和风格不要润色。 输入今天天汽很好我们出去挖土。 输出 response, _ model.chat(tokenizer, prompt, history[]) print(response)关键点有两个。第一trust_remote_codeTrue不能省ChatGLM3-6B的模型结构不在transformers标准注册表里它靠仓库内的自定义代码加载。第二torch_dtypetorch.float16一定要加不然fp32直接吃掉24GB显存单卡3090都吃力。Prompt设计是这里最容易翻车的地方。纠错任务和大模型最擅长的“聊天”不一样它要求模型“克制”——只改该改的不发挥。我一般会在Prompt里明确写上“不要润色、不要改写整句、不要解释”否则模型会把“我们出去挖土”改成“我们出去郊游踏青”这叫过度改写。3.2 什么时候需要微调什么时候只用提示词就够了跑通提示词之后先别急着微调先判断自己的数据属于哪种情况场景提示词是否够用是否需要微调通用文本错字以常见别字为主够用Pycorrector已解决大部分不需要领域文本医疗、法律、金融专有名词多不够模型会乱改专名需要小规模微调语音转写文本错在“同音不同字”部分够用但要配合上下文视量而定文本风格需要严格保持如法律条款不够必须微调强约束判断方法很简单拿200条真实错误句子跑一遍提示词推理人工标注“修正正确率”和“误改率”。如果误改率超过5%说明提示词约束不住模型这时候微调才有意义。微调不是让模型学会“纠错”这个概念而是让它学会“你的业务里哪些词不能动”。比如医疗病历里“阿斯美”不能改成“阿司美”通用模型不知道但微调数据里会告诉它。3.3 用LoRA做微调的最小配置确定要微调后别全参数微调6B全参微调需要至少4张24GB显卡成本高且容易过拟合。用LoRA是最省事的方案。from peft import LoraConfig, get_peft_model, TaskType lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, r8, lora_alpha16, target_modules[query_key_value], lora_dropout0.05 ) model get_peft_model(model, lora_config) model.print_trainable_parameters() # 只显示可训练参数约占总参数量1%target_modules要写[query_key_value]因为ChatGLM3-6B的注意力结构里Q、K、V是合并在一个线性层里的这是它和LLaMA系列最大的不同。r8是一个保守值纠错任务不复杂秩太高反而容易过拟合到训练集。训练数据怎么造是关键。最可靠的方式是拿真实业务文本人工引入错别字利用Pycorrector的混淆集构成“错误句-正确句”对。一条数据长这样输入今天天汽很好我们出去挖土。 输出今天天气很好我们出去挖土。数量上纠错任务有3000-5000对高质量数据就够LoRA收敛了不需要硬凑几万条。重点是覆盖你的领域词汇而不是覆盖所有中文错字。4. 把Pycorrector和ChatGLM3-6B串成一条纠错管线流程设计与效果验证4.1 管线主体先召回后重写用threshold控制开销两条路都跑通之后真正的问题来了怎么让它们协作而不是互相打架我的做法是Pycorrector先生成候选和置信度低置信度或高风险的句子才送大模型。这样大模型只处理“真正需要语义理解”的句子而不是把全部文本都灌给6B模型——每调一次大模型都是算力成本。import requests def correct_text(text: str) - str: # 第一步Pycorrector先纠 resp requests.post(http://localhost:8001/correct, json{text: text}, timeout3.0) pyc_result resp.json() corrected pyc_result[corrected] detail pyc_result[detail] # 第二步如果没有检测到错误直接返回 if not detail: return corrected # 第三步有错但没改出来的或者改得很多需要语义确认的送大模型 if len(detail) 1 or _score_below_threshold(detail): return chatglm_correct(text) return corrected这段代码的逻辑顺序很重要先让Pycorrector给出“有没有错”的判断再决定“要不要让大模型出手”。detail为空说明Pycorrector都没发现问题这时候送大模型纯属浪费——虽然大模型可能发现更深层问题但那个概率远低于每次调用6B的GPU成本。4.2 触发策略不是每一句都要送大模型“送大模型”的条件不能拍脑袋我用的是一个置信度门槛def _score_below_threshold(detail) - bool: # Pycorrector的detail中score越低越可能是误报 if any(item[3] is None for item in detail): return True return False实际操作中Pycorrector返回的修改建议不是100%可靠的。比如“他把文件给了我”这句话模型可能会尝试把“了”改成“吧”这种修改没有依据但概率很高。我一般用两个信号决定要不要送大模型信号含义处理检测到错字但无修正建议Pycorrector不确定但确实有问题送大模型修正建议分数低于0.6修改置信度不足可能误改送大模型修正建议分数高于0.9且只有一处模型很确定直接用Pycorrector结果这里没有用Pycorrector内部暴露的具体分数字段因为不同版本API返回结构略有差异。我的习惯是先打印一条详细结果看结构再写阈值逻辑不要闭着眼睛就写detail[0][4]这种硬编码。4.3 用人工标注集算一遍精确率、召回率别拿感觉当效果管线搭好后必须用标注集算指标。纠错领域最常见的指标是句子级准确率Sentence Accuracy和字级精确率/召回率。from sklearn.metrics import classification_report # 假设有100条人工标注样本 # y_true: 每条样本是否“真的存在错误且被正确修正” # y_pred: 你的管线是否给出修正 y_true [1, 0, 1, 1, 0, ...] # 人工标注 y_pred [1, 1, 0, 1, 0, ...] # 管线输出 print(classification_report(y_true, y_pred, target_names[未改, 修正]))光看准确率不够。纠错系统有两大核心指标修正率该改的都改了和误改率不该改的被改了。误改率一旦高业务方就会觉得“这个系统净帮倒忙”宁愿不用。所以每次迭代都要盯着这两个数字指标计算方法阈值建议修正率修正正确的样本数 / 总错误样本数目标 ≥ 85%误改率被误改的样本数 / 总修正样本数必须 ≤ 3%如果误改率超标优先检查Pycorrector的阈值设置而不是动大模型的Prompt——因为大模型的误改通常来自Prompt约束不足但Pycorrector的误改来自规则本身。两个源头都排查一遍再决定调谁。5. 文本纠错项目最常翻车的五个地方避坑与排查5.1 现象1Pycorrector把全角标点改成半角拿到结果一脸懵现象输入“你好世界。”输出“你好,世界.”标点全被改了。原因Pycorrector内置的文本规范化步骤会把全角ASCII字符转成半角这是它为了对齐词典做的预处理但对中文业务文本来说标点改变就是“改了不该改的”。解决在调用Pycorrector之后、返回结果之前把原文本的标点位置还原回去。最简单的方式是遍历原文本标点如果修正后对应位置是半角标点就替换回全角。或者直接修改Pycorrector的配置文件关闭全半角转换不同版本配置项不同搜full_to_half。5.2 现象2ChatGLM3-6B把“李逵”改成“李鬼”还特别自信现象模型输出“李鬼”的时候既不报错也不犹豫人工一看完全错误。原因大模型没有你的领域知识它在概率分布上认为“李鬼”比“李逵”常见。对模型来说这不是错误但对业务来说这是致命误改。越通用的模型越容易出现这种“看着合理、实际离谱”的替换。解决两种手段结合。第一在Prompt里加“专有名词、人名、产品名不得修改”第二维护一个领域专名白名单在管线里做硬过滤——Pycorrector修正后凡是命中白名单的词直接还原成原文。白名单的来源可以是你的业务词典或历史标注数据。5.3 现象36B模型加载就OOM还没跑已经GG现象torch.from_pretrained直接报CUDA out of memory连推理都没开始。原因大多数人只记得加torch_dtypetorch.float16但忘记同时设置device_mapauto。如果机器是多卡但没设置device_map模型可能全部塞进第一张卡里。另外model.chat()内部会拼接history长文本场景下KV Cache占用会猛增。解决第一步加载时同时加上device_mapauto和torch_dtypetorch.float16。第二步控制单次推理的max_length纠错任务一般不需要超过128个token的输入输出。第三步如果还OOM开load_in_8bitTrue或load_in_4bitTrue需要bitsandbytes库显存占用能再降一半。5.4 现象4微调后模型只改训练集里的那几百种错法现象训练时Loss降得很低但拿到新数据上表现很差甚至不如微调前。原因LoRA本身参数量小如果训练数据里只有少量错误模式模型会过拟合到这些固定模式上。比如训练集里只有“天汽→天气”“做→作”两种错模型见了其他错法就不动。解决训练数据不要只靠人工写用程序批量生成。方法很简单拿正常文本按混淆集随机替换字频率控制在10%-15%确保每种错误类型至少有几十条样本。另外微调后的评估必须用“训练集外”的样本不能拿训练数据打回测。5.5 现象5并发场景下GPU显存堆积服务越来越慢现象刚开始推理速度正常跑了半小时后响应越来越慢最后卡死。原因ChatGLM3-6B的推理过程中所有请求都走同一个显存上下文如果每个请求的max_length设置过大或者没有做批处理控制显存碎片会越来越多。另外多线程调用model.chat()时如果不加锁会产生不可预期的上下文覆盖。解决在FastAPI服务里加一个全局锁限制同一时刻只有一个推理请求在跑。用asyncio.Lock或threading.Lock都行同时把max_length设为固定值而不是让模型自适应如果并发要求高直接上vLLM或TensorRT-LLM把ChatGLM3-6B转成连续批处理吞吐量能提高3-5倍。6. 把纠错模型收进工程输出置信度、对比原文与回归验证6.1 让ChatGLM3-6B输出JSON化的纠错结果避免自由文本解析在生成阶段给模型加一个输出格式约束比事后解析文本可靠得多prompt 你是中文纠错助手只修改错别字不做润色。以JSON格式返回。 输入今天天汽很好。 输出{corrected: 今天天气很好。, has_error: true} response, _ model.chat(tokenizer, prompt, history[]) try: result json.loads(response) except json.JSONDecodeError: result {corrected: text, has_error: False}json.loads失败时要回退到原文本这是大模型输出不稳定的最常见场景。不要把JSON解析错误直接暴露给业务方让它静默回退并打一条warning日志方便事后统计模型输出质量。6.2 上线前必跑的三条回归验证纠错系统最怕“改好了旧错引来了新错”。我每个版本上线前都会跑三件事保留一份历史误改案例集每次改模型或调阈值后跑一遍确认误改没有再出现做字符级diff逐字对比原文和修正文本用脚本统计被修改的字的位置和类型人工过一遍“修改内容是否合理”用200条新标注样本跑一次精确率/召回率对比上一个版本的数字变化。这三条不需要自动化工具写几十行脚本就能完成但能拦住大部分回归问题。6.3 一个值得做的方向OCR后处理里的规则冲突这类纠错管线最适合落地的场景之一是OCR文本的后处理。OCR输出里既有“字错了”也有“排版乱了”但你不能指望一个模型把两者都解决。我自己踩过的坑是OCR文本里经常把“0”识别成“O”但这个“O”在特定业务词里又必须保留——这种矛盾没法靠单一模型解决只能靠管线前端的规则层和白名单层处理干净了再交给大模型。所以我做到最后养成了一个习惯大模型永远只在“最后一段”出手前面能靠规则和轻量模型解决的事不轻易交给它。文本纠错的性价比不在于模型多强而在于你能让它少做多少无意义的事。希望帮到你。本文还有配套的精品资源点击获取
返回列表