
简介面向自然语言处理开发者的文本纠错实战项目包以ChatGLM3-6B大模型和开源中文纠错库Pycorrector为核心帮助解决中文文本智能纠错中的模型选型、数据准备、模型训练、系统部署与接口集成等实际问题。压缩包共包含62个文件整体大小约27.33MB其中23个Python脚本构成主要功能实现17个pyc编译文件用于快速调用另有9张模型训练与系统架构示意图、4个Shell启动脚本、配置文件与教程文档覆盖从数据处理、模型微调到Web界面展示的完整链路。目前累计已有429人次学习/浏览适合需要快速落地文本纠错项目的开发者、研究人员以及相关课程实践者作为参考。资源内含可直接运行的Gradio演示系统、OpenAI风格接口服务、Pycorrector服务端以及ChatGLM3-6B微调代码并配套详细流程教程与说明文档通过项目源码可系统理解模型加载、LoRA微调、损失曲线分析、接口封装等关键步骤便于在此基础上按需扩展和定制。1. 文本纠错先想清楚为什么偏偏是ChatGLM3-6B配Pycorrector「文本纠错」这个需求看起来简单真正落地却总在「不敢改」和「乱改」之间找平衡。我自己最早用规则加词典同音形近能修一部分遇到「他做火车去北京参加会义」这种要靠上下文才能判断的错字就哑火后来直接上ChatGLM3-6B能改了但模型顺手把句式、标点全给你改了线上口碑直接翻车。最后稳定下来的方案是让ChatGLM3-6B和Pycorrector配合Pycorrector先快速召回疑似错误ChatGLM3-6B再针对性地做语义审校决定改还是不改。这篇按这个实战项目的源码和流程教程思路讲清楚分工、数据、微调、推理和踩坑适合做文本清洗、OCR后处理、客服质检、搜索query纠错的工程师直接照着复现。2. 原理与分工Pycorrector召回候选ChatGLM3-6B做语义审校这两种工具不是替代关系而是上下游。Pycorrector擅长在短时间里用词典、混淆集和语言模型快速圈出「这里好像错了」ChatGLM3-6B则负责回答「这里到底是不是错了、改成什么最自然」。把这个分工理解透后面所有数据构造和微调才有依据。2.1 Pycorrector毫秒级召回疑似错误Pycorrector是开源中文纠错工具内置了同音字、形近字混淆集也支持规则过滤和语言模型打分。像「坐/做」「在/再」「的地得」这类高频错误它处理得又快又稳单条文本在CPU上也就几毫秒这是大模型完全比不了的。项目里把它放在第一道工序就是要这个低成本筛查能力。from pycorrector import correct, detect text 他做火车去北京参加会义。 corrected_text, errors correct(text) print(corrected_text) # 他坐火车去北京参加会议。 print(errors) # [(做, 坐), (会义, 会议)] detected detect(text) print(detected) # 只返回疑似错误位置不返回改法这里correct返回改后句子和错误详情列表detect只返回疑似错误的位置和错误词用于和后面的ChatGLM3-6B联动。不同pycorrector版本的返回结构略有差异新版多用上面这种双返回值老版本可能只返回字符串。跑通后建议把自定义混淆集也加进去因为通用包对行业词、人名地名的误报率不低后面第5章会细说。它对上下文的理解非常弱。同样的「做」字在「你做什么工作」里是正确表达却被它标成「坐」的错字——这种误报就是它最大的问题所以不能拿它当最终决策者只能当候选生成器。2.2 ChatGLM3-6B判断「是否真错」的语义层ChatGLM3-6B是个60亿参数量级的中文对话模型在消费级显卡上用Lora就能微调。我选它而不是更大的模型一方面是显存和推理成本能控住另一方面是它的中文语境理解力足够处理纠错这种细粒度任务。它解决的问题很具体判断一个词在完整句子里是不是真的错了。比如「我把书放再桌子上」这种句子Pycorrector能给出一堆候选但「桌子上面/桌子附近」哪个对必须读完整句才能定。ChatGLM3-6B能结合前后文做判断把「放过」的候选去掉保留「放在」。但直接裸用大模型也有翻车现场模型会顺手把「今天心情很好」改成「今日心情甚佳」甚至把「我们」改成「咱们」。这不是能力问题是它默认自己在做润色而不是纠错。所以训练数据里必须强化「只改错字」的指令同时让Pycorrector在入口严格控制哪些句子才值得送进大模型。2.3 推荐配合流程先过滤、后审校、只改该改的实际项目里我最常用的流程是规则粗筛 → Pycorrector检测 → ChatGLM3-6B审校 → 白名单兜底。规则粗筛处理标点、空格和数字格式Pycorrector给出疑似错误列表只有存在疑似错误的句子才拼成prompt送给ChatGLM3-6B最后再用业务白名单把不该改的词恢复回去。方案单条延迟误改率成本适用场景纯规则/词典毫秒级中低极低错别字密集、领域固定纯ChatGLM3-6B秒级高高少量重要文本Pycorrector ChatGLM3-6B百毫秒级低中线上批量文本清洗纯大模型方案最大的坑不是贵而是「过度改写」。改错字这件事用户容忍「漏改」但绝不容忍「乱改」。混合方案用Pycorrector挡掉99%的无关句子大模型只需要在每个句子两三处候选上做判断题输出自然克制得多。后面第4章微调也是围绕「克制」这两个字来设计数据。3. 从零搭环境与构造数据最小依赖、JSONL模板和增强脚本项目标题里写「附项目源码流程教程」但源码不是拿来就跑的环境先对不上就全卡住。我一般先把依赖装好、把pycorrector跑通、把训练数据结构化再谈微调。数据构造的优先级永远高于训练参数因为文本纠错这个任务数据决定模型是「敢改」还是「乱改」。3.1 环境依赖与底座权重建议用Python 3.9或3.10PyTorch 2.1以上CUDA版本和torch一一对应Linux环境比Windows省心得多。Windows上跑ChatGLM3-6B的Lora训练不是不行但path拼接和数据加载的小毛病会耗掉半天时间。pip install torch2.1.0 transformers4.30.0 accelerate0.26.0 pip install peft0.7.0 sentencepiece pip install pycorrector装完依赖后从Hugging Face或ModelScope的官方渠道下载chatglm3-6B底座权重。加载时需要用trust_remote_codeTrue因为这个模型的代码不在transformers核心库里要信任它自带的自定义代码。下载后先跑一个最小加载脚本确认权重路径和显存匹配。顺便说这批依赖里gradient_checkpointing相关的显存优化和transformers版本强相关如果后面训练报显存相关的错先检查是不是transformers版本太新或太旧。3.2 先跑通Pycorrector拿到「错句候选」这一步不是为了看效果是为了确认pycorrector在你自己的文本分布上的召回能力。from pycorrector import correct samples [ 他做火车去北京参加会义。, 小明把电视机的音像调大了。, 我终于明白拉原来是这样。, ] for s in samples: corrected, errors correct(s) print(f原文: {s}) print(f纠错: {corrected}) print(f候选: {errors}) print(- * 40)跑完看三点第一errors里有没有把正确词圈进来这就是误报来源第二召回的错误是不是集中在音近/形近说明你对混淆集的需求在哪第三人名地名被改成什么样这是之后要进白名单的。把这些观察记下来后面的prompt和训练数据都会用到。3.3 构造ChatGLM微调数据JSONL模板和增强脚本ChatGLM3-6B微调数据按JSONL存每条一条记录三个字段instruction、input、output。instruction是固定的纠错指令input是原始错句output是标准纠错结果。{instruction: 你是一个文本纠错助手只修改错别字和标点错误不改变句式、语气和表达风格。, input: 他做火车去北京参加会义。, output: 他坐火车去北京参加会议。} {instruction: 你是一个文本纠错助手只修改错别字和标点错误不改变句式、语气和表达风格。, input: 我在公司做项目忙的不可开交。, output: 我在公司做项目忙得不可开交。} {instruction: 你是一个文本纠错助手只修改错别字和标点错误不改变句式、语气和表达风格。, input: 今天天气很好我们出去走走吧。, output: 今天天气很好我们出去走走吧。}注意最后一条这是「负样本」原文本身没错output和input完全一样。这类数据要占三到四成否则模型会被带偏成「逢句必改」。真实项目里正确句子远多于错句模型必须学会「不修改」。增强脚本的常见做法是拿一批正确语料新闻、评论都行按概率随机替换汉字来造错句import random confusion_pairs [(坐, 做), (在, 再), (的, 得), (地, 的), (未, 末)] def make_noise(text, error_prob0.15): chars list(text) # 只对长度4的句子做增强太短容易改得不成样 if len(chars) 4: return text for i in range(len(chars)): for wrong, right in confusion_pairs: if chars[i] right and random.random() error_prob / len(confusion_pairs): chars[i] wrong break return .join(chars) with open(article.txt, encodingutf-8) as f: lines [line.strip() for line in f if len(line.strip()) 4] with open(train.jsonl, w, encodingutf-8) as f: for line in lines[:5000]: noisy make_noise(line) sample { instruction: 你是一个文本纠错助手只修改错别字和标点错误不改变句式、语气和表达风格。, input: noisy, output: line, } f.write(json.dumps(sample, ensure_asciiFalse) \n)error_prob0.15控制错字密度我一般控制在0.1到0.3之间。太低模型看不到错误太高模型会把句子改得面目全非。关键的取舍是增强数据和真实错句的比例。常见做法是3:1到4:1增强数据管广度真实数据管贴合业务。如果你手头有真实用户输入过的错句哪怕只有几百条效果也比几千条增强数据强。4. 用Lora微调ChatGLM3-6B最小训练脚本与关键参数Pycorrector能给出候选但「要不要改」这个判断题必须让模型在业务数据上再适应一轮。这一步我用Lora而不是全参数微调理由很现实单卡24G显存就能训6B模型训练参数量只有1%左右跑完只多几个LoRA权重文件不影响底座模型本身。4.1 为什么选Lora而不是全量微调全参数微调6B模型至少需要两块A100级别的卡花销和调参成本都高。Lora的做法是冻结原模型权重在注意力层的权重矩阵旁插入低秩分解矩阵训练时只更新这些新增参数。效果上和微调接近但训练速度快、显存占用低、换业务场景只要换个LoRA adapter就行。Lora里有三个参数需要理解r是低秩矩阵的秩lora_alpha是缩放系数lora_dropout是防止过拟合的随机失活。r设8到16在纠错任务里够用设太大不仅显存涨还容易把底座模型的通用中文能力带偏。4.2 最小训练脚本我用transformers的Trainer peft的LoraConfig代码量最少、不容易写错训练循环。核心脚本如下import json import torch from datasets import Dataset from transformers import ( AutoModel, AutoTokenizer, TrainingArguments, Trainer, DataCollatorForSeq2Seq ) from peft import LoraConfig, get_peft_model, TaskType model_path /path/to/chatglm3-6b tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModel.from_pretrained( model_path, trust_remote_codeTrue, torch_dtypetorch.bfloat16, device_mapauto ) def build_prompt(instruction, input_text): return f{instruction}\n{input_text} def tokenize(example): prompt build_prompt(example[instruction], example[input]) text prompt example[output] # input_ids和labels都来自同一段文本只保留output部分参与loss encoding tokenizer(text, max_length128, truncationTrue, paddingFalse) prompt_len len(tokenizer(prompt, max_length128, truncationTrue).input_ids) labels encoding[input_ids].copy() labels[:prompt_len] [-100] * prompt_len encoding[labels] labels return encoding with open(train.jsonl, encodingutf-8) as f: raw_data [json.loads(line) for line in f] dataset Dataset.from_list(raw_data) tokenized_dataset dataset.map(tokenize, remove_columnsdataset.column_names) lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, r8, lora_alpha16, lora_dropout0.05, target_modules[query_key_value], ) model get_peft_model(model, lora_config) training_args TrainingArguments( output_dir./chatglm3_lora_ckpt, per_device_train_batch_size2, gradient_accumulation_steps8, learning_rate5e-4, num_train_epochs3, optimadamw_torch, fp16False, bf16True, logging_steps50, save_strategyepoch, gradient_checkpointingTrue, ) trainer Trainer( modelmodel, argstraining_args, train_datasettokenized_dataset, data_collatorDataCollatorForSeq2Seq(tokenizertokenizer, modelmodel, paddingTrue), ) trainer.train() model.save_pretrained(./chatglm3_lora_adapter)几个关键点说明。target_modules填的是ChatGLM3注意力层的模块名在这个架构里是query_key_value如果你的transformers或模型版本不同可以用print(model)查实际名字改成q_proj/k_proj/v_proj这类命名。labels里把prompt部分置为-100意思是loss只算output部分否则模型花费大量精力去学重复输出指令纠错能力反而变差。训练参数上learning_rate5e-4是Lora的常见起点不要照搬全参数微调的2e-5。max_length128对纠错任务够用一般的句子不会超过这个长度设得过长既吃显存又延长训练时间。per_device_train_batch_size2配合gradient_accumulation_steps8等价于batch size等于16改这两个值时注意显存余量。4.3 训练产物与检查点管理训练完的目录里会生成LoRA适配器实际推理加载的是适配器文件而不是整个模型。Trainer按epoch保存的中间checkpoint占用不小等训练稳定后只保留最后一个epoch就行。中间checkpoint不建议直接拿去推理loss还没收敛误改率会明显偏高。保存适配器时只跑save_pretrained得到一个adapter_config.json和一个adapter_model文件加起来不到几十MB换环境部署非常方便。5. 推理评估与常见问题五个必踩的坑微调完要接回业务就不能只看loss。文本纠错的上线标准很简单该改的改了、不该改的一个没改。这一章先给推理和评估命令再列我踩过的五个坑每一条都是真金白银换来的。5.1 推理命令与输出格式加载底座和LoRA适配器对单条文本做推理import torch from transformers import AutoModel, AutoTokenizer from peft import PeftModel base_path /path/to/chatglm3-6b adapter_path ./chatglm3_lora_adapter tokenizer AutoTokenizer.from_pretrained(base_path, trust_remote_codeTrue) model AutoModel.from_pretrained( base_path, trust_remote_codeTrue, torch_dtypetorch.bfloat16, device_mapauto ) model PeftModel.from_pretrained(model, adapter_path) model.eval() def correct_text(text, max_new_tokens64): prompt 你是一个文本纠错助手只修改错别字和标点错误不改变句式、语气和表达风格。\n text inputs tokenizer(prompt, return_tensorspt).to(model.device) outputs model.generate( **inputs, do_sampleFalse, max_new_tokensmax_new_tokens, pad_token_idtokenizer.eos_token_id, ) response outputs[0][len(inputs.input_ids[0]):] return tokenizer.decode(response, skip_special_tokensTrue) print(correct_text(他做火车去北京参加会义。))生成参数上文本纠错任务要让模型「敢改但少发挥」所以do_sample直接关掉每一步都选概率最高的输出比任何temperature调节都稳定。max_new_tokens控制在64以内纠错输出和输入长度差不多给太少会截断给太多模型容易开始聊闲天。5.2 评估指标不要只看准确率纠错领域的经典指标是精确率、召回率和F1但对业务真正重要的是「误改率」——原本对的句子被改成错句的比例。这个指标在纯模型的评估报告里看不到必须拿真实语料自己算。from difflib import SequenceMatcher def edit_pairs(original, corrected): sm SequenceMatcher(None, original, corrected) edits [] for tag, i1, i2, j1, j2 in sm.get_opcodes(): if tag ! equal: edits.append((original[i1:i2], corrected[j1:j2])) return edits def evaluate(samples): exact_match 0 over_correct 0 total len(samples) for src, ref, pred in samples: ref_edits edit_pairs(src, ref) pred_edits edit_pairs(src, pred) if ref pred: exact_match 1 if len(pred_edits) len(ref_edits): over_correct 1 print(f句级完全正确率: {exact_match / total:.2%}) print(f误改率: {over_correct / total:.2%})samples每条是一个三元组原句、人工标出的标准纠错结果、模型输出。edit_pairs用difflib找出两段文本之间的字符级差异是评估里最常用的工具。误改率算出来如果超过2%就别急着上线宁可少改也不要乱改。5.3 避坑记录五个高频问题的现象、原因与解决坑一Pycorrector把正确的「你做什么工作」标成错误。原因是通用混淆集把「做」和「坐」无条件映射没有语境判断。解决给pycorrector传自定义混淆集把「做/坐」这类词留在候选里但在上游把置信度阈值调高同时让ChatGLM3-6B再审一道模型的语义判断能过滤掉九成这类误报。坑二微调后模型把「今天心情很好」改成「今日心情甚佳」。原因是训练数据里所有output都与input不完全一致模型学到了「必须改点什么」。解决在数据集里加入三到四成「原句正确、output等于input」的负样本并且把prompt里的指令写死成「只修改错别字」。坑三训练时报CUDA out of memory。原因基本是max_length设太大或batch size太高。解决把max_length降到128per_device_train_batch_size降到1同时开起gradient_checkpointingTrue。如果还OOM把bf16换成fp16有些显卡对bf16支持不好。坑四loss下降很慢甚至震荡。原因一般是数据里有空字符串或者错字密度太高。解决训练前过滤掉input或output为空的样本error_prob控制在0.15左右错字太密的文本模型学不到稳定规律。学习率也可以从5e-4降到2e-4试试。坑五线上推理太慢单条文本要两三秒。原因是没有做前置过滤全部句子都送进大模型。解决先跑pycorrector.detect没有疑似错误的句子直接短路返回只有疑似错误句子才进ChatGLM3-6B。线上批量场景里六到八成句子根本不需要过模型。6. 把纠错能力接到业务里验证方法与三个落地技巧6.1 上线前验证按业务口径算误改率不要用公开测试集衡量线上效果。我每次上线前都从真实语料里抽100到200条按「原句、标准改法、模型输出」三列人工标注再跑一遍评估脚本。表格长这样每条样本记录是否完全正确、是否有额外改写、错误是否漏改。只公布准确率的项目后来都在误改率上翻过车。6.2 技巧一白名单保护专有词业务词、品牌名、人名地名经常被当成错字改掉这是纯模型方案最容易翻车的地方。whitelist {苹果, 得物, 闲鱼, 越狱} def protect_whitelist(src, pred): for word in whitelist: if word in src and word not in pred: pred pred.replace(word, word) return pred逻辑很简单原句里出现的白名单词在预测结果里必须保留。实现上可以用字符串替换也可以在ChatGLM3-6B的prompt里直接写明「以下词汇为专有名词不得修改xxx、xxx」。两种方式可以叠加prompt负责引导、白名单负责兜底。6.3 技巧二让Pycorrector当守门员只放大模型处理疑似错的句子成本控制的核心是短路。批量场景里大量文本是正确的根本不需要大模型介入。用detect先判断没有疑似错误就不调用ChatGLM3-6B有疑似错误的句子再走完整链路。这种方法能把大模型调用量降到原来的两到三成延迟和成本都同步降下来。我最早那版把所有句子直接丢给ChatGLM延迟和账单都很难看后来改成Pycorrector过滤误改率反而因为干扰减少而降了一个量级。希望帮到你。本文还有配套的精品资源点击获取