资讯动态

基于BERT的中文文本纠错工程:从检测到规则后处理的完整链路

发布时间:2026/10/18 21:46:21 来源:尧图企业网站定制
简介本资源为基于BERT的中文文本纠错Python实现包面向自然语言处理学习者与开发者可用于智能输入法、在线教育、新闻编辑等场景的纠错系统搭建。包内共28个文件以16个py源码、10个txt词表与语料、1个md说明及1个klm语言模型为主压缩包约16.85MB涵盖数据预处理、模型定义、训练、评估与推理等完整模块并附带预训练模型与项目说明。已有198人学习。读者可据此理解BERT双向上下文建模在纠错任务中的应用掌握从数据标注、加载预训练模型、添加分类层到AdamW优化与F1评估的全流程同时借助地名、人名、拼音混淆、笔画相似等词表资源快速复现并迁移到自身业务中。1. 一份能跑起来的中文纠错工程从 BERT 到规则后处理的完整链路中文文本纠错这件事真正落到工程里最难的往往不是模型本身而是「模型说该改但到底改不改」的决策。我见过太多团队拿一个 BERT 掩码预测脚本就上线结果把「的/地/得」改得满屏错把专有名词拆得七零八落。这份基于 BERT 的中文文本纠错 Python 源码包价值就在于它没有只丢一个模型出来而是把检测、候选生成、混淆集、语言模型打分、规则过滤串成了一条完整链路。它适合两类人一类是想搞懂工业级纠错系统到底由哪些模块拼起来的 NLP 工程师另一类是手上有错别字清洗需求、想直接拿一套可改可调的代码去适配自己业务场景的开发者。压缩包里 code 目录下 bert_corrector.py、detector.py、predict_mask.py、corrector.py 各司其职data 目录塞满了同音字、同形字、地名、人名、词频等资源还有 bert_models 和 kenlm 两个模型目录这套结构本身就说明了它的设计思路——模型负责找可疑点规则和统计负责拍板。2. 纠错链路拆解detector 怎么找错corrector 怎么改对2.1 为什么不能只靠 BERT 掩码预测很多人对 BERT 纠错的第一反应是把句子里的字逐个 mask 掉让模型预测原位置应该是什么字如果预测结果和原字不一致就说明有错。这个思路本身没错predict_mask.py 干的就是这件事。但直接拿它当纠错器用翻车是迟早的。原因有三第一BERT 的掩码预测本质是语言模型打分它倾向于把罕见但正确的表达改成常见但错误的表达比如把「饕餮」改成「涛涛」第二中文里大量专有名词、人名、地名在通用语料里出现频率极低模型会误判为错误第三模型对「的/地/得」「在/再」这类语法功能词的区分能力有限容易过度纠正。所以这份源码的设计是分层的detector.py 负责检测可疑位置predict_mask.py 负责生成候选字corrector.py 负责最终决策。检测阶段不追求高召回而是追求「宁可漏报不可误报」把明显没问题的位置直接跳过只把可疑片段送进后续流程。这个思路在工程上非常关键因为纠错系统的用户体验取决于误报率一个把正确文本改错的系统比一个漏掉几个错别字的系统更让人崩溃。2.2 detector 的检测逻辑与参数调节detector.py 的核心逻辑是基于字级别的语言模型困惑度做初筛。它加载 kenlm 目录下的 n-gram 语言模型对输入句子逐字计算困惑度当某个位置的困惑度超过阈值时标记为可疑。这里的关键参数是困惑度阈值和窗口大小。阈值设低了召回高但误报多设高了漏报多但误报少。我一般会先在业务语料上跑一批标注数据画一条 precision-recall 曲线找到 F1 最优点对应的阈值。# detector.py 核心检测逻辑示意 import kenlm class Detector: def __init__(self, lm_path, ppl_threshold80, window_size5): # lm_path 指向 kenlm 目录下的语言模型文件 self.lm kenlm.Model(lm_path) # ppl_threshold 是困惑度阈值超过则标记为可疑 self.ppl_threshold ppl_threshold # window_size 控制计算困惑度时的上下文窗口 self.window_size window_size def detect(self, sentence): suspicious [] for i, char in enumerate(sentence): # 取当前位置前后 window_size 个字符作为上下文 start max(0, i - self.window_size) end min(len(sentence), i self.window_size 1) context sentence[start:end] # 计算该片段的对数概率转换为困惑度 log_prob self.lm.score(context) ppl 10 ** (-log_prob / len(context)) if ppl self.ppl_threshold: suspicious.append(i) return suspicious这段代码里ppl_threshold 是最需要根据业务调整的参数。新闻编辑场景可以设到 100 以上因为新闻文本规范模型对正常表达的困惑度普遍偏低而社交媒体文本本身就不规范阈值可能要降到 50 左右否则满屏都是可疑点。window_size 一般设 3 到 5太小了上下文不足太大了会把正常片段的困惑度拉高。注意 kenlm 模型文件需要和代码里的路径对应压缩包里 kenlm 目录下应该有对应的 arpa 或 bin 文件如果加载失败先检查文件路径和格式。2.3 候选生成同音字、同形字与混淆集检测出可疑位置后下一步是生成候选纠正字。predict_mask.py 用 BERT 做掩码预测输出 top-k 个候选。但光靠 BERT 不够data 目录下的 same_pinyin.txt、same_stroke.txt、custom_confusion.txt 就是用来补充候选的。same_pinyin.txt 存的是同音字表比如「工」和「公」same_stroke.txt 存的是同形字表比如「日」和「曰」custom_confusion.txt 是自定义混淆集可以往里加业务特有的易混词。# predict_mask.py 候选生成逻辑示意 from transformers import BertTokenizer, BertForMaskedLM import torch class MaskPredictor: def __init__(self, model_path, tokenizer_path): self.tokenizer BertTokenizer.from_pretrained(tokenizer_path) self.model BertForMaskedLM.from_pretrained(model_path) self.model.eval() def predict(self, sentence, position, top_k5): # 将可疑位置替换为 [MASK] chars list(sentence) chars[position] [MASK] masked_text .join(chars) inputs self.tokenizer(masked_text, return_tensorspt) with torch.no_grad(): outputs self.model(**inputs) # 取该位置对应的 logits mask_token_index torch.where( inputs[input_ids] self.tokenizer.mask_token_id )[1] logits outputs.logits[0, mask_token_index, :] # 返回 top_k 候选字及其概率 top_k_tokens torch.topk(logits, top_k, dim-1) candidates [] for token_id, score in zip(top_k_tokens.indices[0], top_k_tokens.values[0]): char self.tokenizer.decode([token_id]) candidates.append((char, score.item())) return candidates这里 top_k 一般设 5 到 10太小了可能漏掉正确候选太大了会增加后续决策模块的负担。model_path 和 tokenizer_path 指向 bert_models 目录注意模型必须是中文预训练模型用英文模型跑中文纠错没有意义。另外BERT 掩码预测对多字词的处理比较粗糙如果错误涉及连续多个字需要逐个 mask 或者用 n-gram 方式处理这份源码里 corrector.py 有对应的多字处理逻辑。2.4 corrector 的决策融合规则、统计与语言模型打分corrector.py 是整个系统的决策中枢。它拿到 detector 标记的可疑位置和 predict_mask 生成的候选列表后会做几件事先用 same_pinyin、same_stroke、custom_confusion 过滤候选只保留在混淆集里的字然后用 word_freq.txt 和 custom_word_freq.txt 查词频优先选高频且符合语境的候选最后用 kenlm 语言模型对替换后的整句打分如果替换后句子困惑度下降才执行替换。# corrector.py 决策融合逻辑示意 class Corrector: def __init__(self, detector, predictor, lm, word_freq, confusion_set): self.detector detector self.predictor predictor self.lm lm self.word_freq word_freq self.confusion_set confusion_set def correct(self, sentence): suspicious self.detector.detect(sentence) corrected list(sentence) for pos in suspicious: candidates self.predictor.predict(sentence, pos, top_k10) best_char None best_score float(-inf) for char, bert_score in candidates: # 候选必须在混淆集里 if char not in self.confusion_set.get(sentence[pos], set()): continue # 替换后整句用语言模型打分 temp corrected.copy() temp[pos] char temp_sentence .join(temp) lm_score self.lm.score(temp_sentence) # 结合词频和 BERT 分数做加权 freq_score self.word_freq.get(char, 0) total_score lm_score * 0.6 bert_score * 0.3 freq_score * 0.1 if total_score best_score: best_score total_score best_char char if best_char: corrected[pos] best_char return .join(corrected)这段代码里的权重分配是经验值lm_score 占 0.6 是因为语言模型对整句流畅度的判断最可靠bert_score 占 0.3 是因为掩码预测提供了局部语义信息freq_score 占 0.1 是兜底。实际调参时如果发现系统过于保守可以降低 lm_score 权重如果发现误报多可以提高 lm_score 权重。confusion_set 的构建很关键custom_confusion.txt 里可以加业务特有的混淆对比如某公司产品名容易被写错就把正确写法和常见错误写法都加进去。3. 环境搭建与数据准备从 requirements 到自定义混淆集3.1 依赖安装与模型文件放置拿到压缩包后第一步是确认 Python 版本和依赖。requirements.txt 里应该列了 transformers、torch、kenlm、numpy 等库。kenlm 的安装比较特殊它需要先编译 C 扩展在 Windows 上容易出问题建议在 Linux 或 macOS 下操作。如果只是跑推理可以先用 pip install kenlm 试试不行再源码编译。# 创建虚拟环境并安装依赖 python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install -r requirements.txt # kenlm 如果 pip 安装失败用源码编译 git clone https://github.com/kpu/kenlm.git cd kenlm mkdir -p build cd build cmake .. make -j 4 pip install .模型文件方面bert_models 目录下应该有 pytorch_model.bin、config.json、vocab.txt 等文件这是 Hugging Face 格式的 BERT 模型。kenlm 目录下应该有语言模型文件通常是 .arpa 或 .bin 格式。如果这些文件缺失需要从对应来源获取注意模型文件要和代码里的路径配置一致。config.py 里一般会定义这些路径先打开看一眼把路径改成自己机器上的实际路径。3.2 数据文件的格式与自定义方法data 目录下的文本文件是纠错系统的知识库格式都很简单每行一条记录。same_pinyin.txt 每行是「正确字 错误字」或者「字1 字2」表示互为同音same_stroke.txt 类似表示同形word_freq.txt 每行是「词 频次」custom_word_freq.txt 是自定义词频优先级高于默认词频custom_confusion.txt 是自定义混淆集格式通常是「正确词 错误词」。文件名格式用途是否可自定义same_pinyin.txt每行一组同音字同音字候选过滤可追加same_stroke.txt每行一组同形字同形字候选过滤可追加word_freq.txt词 频次候选词频打分可替换custom_word_freq.txt词 频次业务词频覆盖强烈建议自定义custom_confusion.txt正确词 错误词业务混淆对强烈建议自定义place_name.txt每行一个地名地名保护可追加person_name.txt每行一个人名人名保护可追加stopwords.txt每行一个停用词检测跳过可追加自定义的方法很简单比如你的业务里「账户」经常被写成「帐户」就在 custom_confusion.txt 里加一行「账户 帐户」。如果某个产品名「星尘」经常被写成「星辰」也加进去。地名和人名文件的作用是保护这些词不被误改比如「李想」可能被模型改成「理想」把人名加进 person_name.txt 后detector 会跳过这些位置。注意这些文件都是 UTF-8 编码用记事本编辑时注意别存成 GBK。3.3 快速跑通一个纠错示例环境配好后可以用 corrector.py 跑一个简单示例验证整条链路是否通畅。先准备一个包含错别字的句子比如「我今天去公圆玩了」然后调用 corrector 的 correct 方法。# 快速验证示例 from corrector import Corrector from detector import Detector from predict_mask import MaskPredictor import kenlm # 初始化各模块 detector Detector(lm_pathkenlm/lm.bin, ppl_threshold80) predictor MaskPredictor( model_pathbert_models, tokenizer_pathbert_models ) lm kenlm.Model(kenlm/lm.bin) corrector Corrector(detector, predictor, lm, word_freq{}, confusion_set{}) # 测试句子 sentence 我今天去公圆玩了 result corrector.correct(sentence) print(f原始: {sentence}) print(f纠正: {result}) # 预期输出: 我今天去公园玩了如果输出不对先检查 detector 是否标记了「圆」的位置再检查 predict_mask 是否把「园」排在候选前列最后检查 corrector 的混淆集里是否有「圆」到「园」的映射。常见问题是 kenlm 模型加载失败导致 detector 不工作或者 BERT 模型路径不对导致候选为空。调试时可以在每个模块加 print 输出中间结果定位问题所在。4. 避坑与排查那些让纠错系统翻车的细节4.1 现象正确文本被改得面目全非原因detector 的困惑度阈值设得太低导致大量正常位置被标记为可疑或者 corrector 的决策权重里 bert_score 占比过高模型把罕见正确词改成了常见错误词。解决先把 ppl_threshold 调高比如从 50 调到 100观察误报是否减少。如果还不行检查 corrector 里的权重分配把 lm_score 权重提高到 0.7 以上让整句语言模型打分起主导作用。另外把常见正确词加入 custom_word_freq.txt 并给高频次也能抑制误改。4.2 现象专有名词、人名、地名被拆改原因这些词在通用语料里频率低BERT 和 kenlm 都会给低分detector 容易标记为可疑corrector 容易把它们改成常见词。解决把业务相关的专有名词加入 person_name.txt、place_name.txt 和 custom_word_freq.txt。detector 在检测时会跳过这些文件里的词corrector 在决策时会给这些词高频次防止被替换。如果专有名词很多可以写个脚本从业务数据库导出批量追加到这些文件里。4.3 现象连续多字错误只改对了一个字原因predict_mask 是逐字 mask 预测的对连续错误处理能力有限。比如「公圆玩」三个字里「圆」错了但「公圆」作为一个整体可能被模型认为是一个词导致只改「圆」不改「公」。解决在 corrector 里增加多字处理逻辑对可疑位置前后各扩展一个字组成片段后用 BERT 做片段级别的掩码预测或者用 n-gram 方式枚举候选组合。这份源码的 corrector.py 里应该有对应的处理如果没有可以自己加一个后处理步骤对连续可疑位置做组合搜索。4.4 现象kenlm 模型加载报错或打分异常原因kenlm 模型文件格式不对或者 Python 的 kenlm 库版本和模型文件不兼容。常见的是用 arpa 格式但代码里按 bin 格式加载或者模型文件路径写错。解决先确认 kenlm 目录下的文件格式arpa 文件是文本格式bin 是二进制格式。kenlm.Model 加载时不需要指定格式它会自动识别但如果文件损坏或路径不对就会报错。用 kenlm 自带的命令测试一下模型文件是否正常比如lmplz或build_binary。另外kenlm 的 Python 包对 Python 版本有要求3.7 到 3.10 比较稳太新的版本可能编译不过。4.5 现象推理速度慢一条句子要好几秒原因BERT 掩码预测是逐字进行的如果句子长、可疑位置多就要跑很多次前向传播。另外kenlm 打分如果每次都对整句重新计算也会拖慢速度。解决把 BERT 模型放到 GPU 上用 batch 方式处理多个可疑位置。kenlm 打分可以缓存中间结果或者只对替换位置附近的窗口打分不用整句重算。如果业务对延迟要求高可以把 detector 的阈值调高减少可疑位置数量用少量召回换速度。实际部署时一般会把 BERT 模型蒸馏成小模型或者用 ONNX 加速推理。5. 进阶技巧用自定义混淆集和词频把纠错系统调成业务专属这套源码最值得花时间的地方不是模型本身而是 data 目录下那几个文本文件。它们决定了系统在具体业务里的表现。我一般会按这个顺序调先跑一批业务语料收集所有被误改的正确词加入 custom_word_freq.txt 并给高频次再收集所有漏改的错误词把正确写法和错误写法加入 custom_confusion.txt最后把业务里的专有名词批量加入 person_name.txt 和 place_name.txt。这个过程通常要迭代两三轮每轮用标注数据算一下 F1直到误报和漏报都降到可接受范围。# 批量构建自定义混淆集的脚本示例 import re from collections import defaultdict def build_confusion_set(error_pairs_file, output_file): 从错误对文件构建混淆集 error_pairs_file 每行格式: 正确词\t错误词 confusion defaultdict(set) with open(error_pairs_file, r, encodingutf-8) as f: for line in f: parts line.strip().split(\t) if len(parts) ! 2: continue correct, wrong parts # 逐字建立映射支持多字词 for c, w in zip(correct, wrong): if c ! w: confusion[c].add(w) confusion[w].add(c) # 输出为 custom_confusion.txt 格式 with open(output_file, w, encodingutf-8) as f: for char, candidates in confusion.items(): for cand in candidates: f.write(f{char}\t{cand}\n) print(f共生成 {sum(len(v) for v in confusion.values())} 条混淆对) # 使用示例 build_confusion_set(error_pairs.txt, data/custom_confusion.txt)这个脚本把「正确词-错误词」对拆成字级别的混淆映射因为 corrector 的候选过滤是按字进行的。比如「账户-帐户」会生成「账-帐」和「户-户」两条后者因为相同会被跳过。注意多字词的处理如果错误涉及多个字比如「公圆-公园」会生成「公-公」和「圆-园」前者跳过后者保留。这样构建的混淆集比手工维护高效得多尤其适合有大量业务错误样本的场景。另一个技巧是词频文件的动态更新。word_freq.txt 是静态的但业务词频会变化可以定期从业务日志里统计词频覆盖 custom_word_freq.txt。统计时注意只统计正确文本别把错误文本也统计进去否则会把错误写法推成高频。我一般会先用纠错系统清洗一遍日志人工抽检确认无误后再统计词频。这个流程跑顺了纠错系统会越用越准形成一个正向循环。从那以后我每次拿到新的纠错项目都会先花半天时间把 custom_confusion.txt 和 custom_word_freq.txt 填一遍再跑模型。模型决定下限数据决定上限这句话在中文纠错里尤其成立。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑