简介:这份资源是面向自然语言处理初学者与算法工程师的中文文本纠错实战项目,基于BERT模型实现从数据预处理到推理部署的完整流程,可应用于智能输入法、在线教育、新闻编辑等需要自动纠正错别字的场景。压缩包共28个文件,约16.85MB,以16个Python源码文件为核心,涵盖数据预处理、模型定义、训练、评估与推理等模块,另含10个txt词表与语料文件、1个md项目说明及1个kenlm语言模型文件,目录结构清晰,便于按模块阅读与二次开发。目前已有198人学习。读者可借助源码理解如何加载Hugging Face预训练模型、构建纠错分类层、使用AdamW与交叉熵损失训练,并通过F1等指标评估效果,同时结合拼音、字形、混淆词表等资源深入掌握中文纠错的关键技巧,适合作为课程设计或工程落地的参考。
1. 中文纠错为什么不能只靠“像不像”:从 BERT 的掩码预测说起
很多人第一次接触中文文本纠错,直觉上会觉得这事跟拼写检查差不多:把每个字拿出来,看看它在词典里存不存在,不存在就标红。但中文的麻烦在于,错字往往本身也是合法汉字。“我明天要去银行办理业务”和“我明天要去很行办理业务”,后者每个字都认识,可整句就是错的。这时候靠字形、靠词典都失效了,唯一能判断对错的依据是上下文语义。BERT 之所以能在这个任务里站住脚,核心就在于它预训练阶段干的那件事——掩码语言建模(Masked Language Modeling)。它把句子里某个字盖住,让模型根据左右两边的字去猜这个位置原本是什么。这个训练目标天然就贴近纠错:给你一个可能出错的字,你能不能根据上下文把它还原成正确的字。这份资源里的predict_mask.py和corrector.py就是围绕这个思路展开的,配合bert_models目录下的预训练权重,把“猜字”能力直接迁移到纠错场景。搞懂这条链路,比单纯跑通一个脚本重要得多,因为它决定了你后面调参、换模型、加规则时,知道自己在动哪一块。
2. 纠错系统的分层结构:从字符检测到候选替换的完整链路
2.1 检测与纠正为什么必须拆成两步
一个常见的误解是:纠错模型直接输出正确句子就行了。但实际工程里,如果让模型对每个位置都输出一个“正确字”,计算量会爆炸,而且大部分位置本来就没问题,模型很容易学成“复制输入”。所以这份源码把任务拆成了检测和纠正两个阶段。detector.py负责判断句子中哪些位置可能出错,corrector.py只对这些可疑位置生成候选替换。检测阶段通常用 BERT 输出的每个 token 的上下文表示接一个二分类头,判断该位置是否异常;纠正阶段则利用掩码预测,取 top-k 候选字,再结合拼音、字形、词频等特征做重排序。这种拆分的好处是:检测可以做得轻量,纠正可以做得精细,两者解耦后也方便单独优化。你在config.py里能看到检测阈值、候选数量这些参数,它们直接控制着召回和精排的平衡。
2.2 掩码预测如何变成纠错候选生成器
predict_mask.py是整个纠错流程里最核心的一环。它的逻辑并不复杂:把输入句子里的某个字替换成[MASK],然后让 BERT 预测这个位置最可能是什么字。但这里有个关键细节——不是只取 top-1。因为中文里同音字、形近字太多,top-1 未必对,所以源码里通常会取 top-k(比如 10 到 20 个)作为候选池。下面这段代码展示了如何用 transformers 加载模型并做掩码预测:
from transformers import BertTokenizer, BertForMaskedLM import torch # 加载预训练模型和分词器,bert_models 目录下是已经放好的权重 tokenizer = BertTokenizer.from_pretrained('./bert_models') model = BertForMaskedLM.from_pretrained('./bert_models') model.eval() def predict_mask(sentence, pos): # 把指定位置的字替换成 [MASK] chars = list(sentence) chars[pos] = '[MASK]' masked_text = ''.join(chars) # 编码并推理 inputs = tokenizer(masked_text, return_tensors='pt') with torch.no_grad(): outputs = model(**inputs) # 取该位置 top-k 候选 mask_token_index = torch.where(inputs['input_ids'] == tokenizer.mask_token_id)[1] logits = outputs.logits[0, mask_token_index, :] topk = torch.topk(logits, k=10, dim=-1) candidates = [tokenizer.decode([idx]) for idx in topk.indices[0]] return candidates这段代码里,pos是检测阶段给出的可疑位置,topk的 k 值决定了候选池大小。k 太小容易漏掉正确字,k 太大则增加后续重排序负担。实际使用时,corrector.py会把这个候选池和same_pinyin.txt、same_stroke.txt里的混淆集做交集或加权,进一步缩小范围。注意model.eval()和torch.no_grad()是必须的,否则 dropout 和梯度计算会让推理结果不稳定,这在批量处理时尤其明显。
2.3 语言模型 kenlm 在重排序中的角色
候选字出来之后,怎么选?光靠 BERT 的置信度还不够,因为 BERT 对高频字的偏好可能压过正确但少见的字。这时候kenlm目录下的 n-gram 语言模型就派上用场了。它会对替换后的整个句子打分,看哪个候选让句子更“通顺”。比如“我明天要去很行”和“我明天要去银行”,kenlm 在大量新闻语料上训练过,显然会给“银行”更高的概率。源码里run_lm_finetuning.py就是用来微调或训练这个语言模型的。BERT 负责局部上下文,kenlm 负责全局流畅度,两者结合比单用任何一个都稳。这也是为什么资源里同时放了word_freq.txt和custom_word_freq.txt——词频信息可以在重排序时作为额外特征,尤其是对专有名词和领域词汇,自定义词频能显著提升纠正准确率。
3. 数据预处理与混淆集:那些让模型真正好用的“脏活”
3.1 混淆集是怎么构建和使用的
中文纠错里,混淆集是一份“哪些字容易混”的清单。资源里的same_pinyin.txt存的是同音字,same_stroke.txt存的是形近字,custom_confusion.txt则允许你手动补充领域相关的混淆对。这些文件在纠正阶段的作用是:当 BERT 给出候选字后,如果某个候选和原字在混淆集里,它的权重会被提高。举个例子,“账号”写成“帐号”是常见错误,“账”和“帐”在same_pinyin.txt里大概率是一对,模型就能更有把握地纠正。构建混淆集通常有两种方式:一是从大规模语料里统计混淆矩阵,二是用拼音和字形编辑距离自动生成。这份资源采用的是后者为主、手工补充为辅的策略,common_char_set.txt限定了常用字范围,避免候选里出现生僻字干扰。
3.2 文本清洗与分词对齐的坑
text_utils.py和langconv.py负责的是最不起眼但最容易翻车的部分:文本清洗和繁简转换。中文文本里混着全角半角、中英文标点、多余空格,如果不统一,BERT 分词器会把它们当成不同 token,导致检测位置偏移。langconv.py做的是繁简转换,因为预训练模型通常以简体为主,繁体输入会拉低效果。text_utils.py里一般会有去除控制字符、统一标点、处理数字和英文混排的逻辑。这里有个血泪经验:分词后的 token 和原始字符不是一一对应的,BERT 的 wordpiece 会把一个词拆成多个 subword。所以detector.py在标注错误位置时,必须维护一个从 token 索引到原始字符索引的映射表,否则纠正阶段会改错位置。这个映射逻辑在tokenizer.py里能看出端倪,它封装了编码和解码时的对齐处理。
3.3 人名地名与停用词的特殊处理
person_name.txt和place_name.txt的存在说明了一个现实问题:专有名词是纠错系统最容易误伤的地方。“张三是某公司员工”里的“张三”如果被模型当成普通词,可能会被改成“张散”之类的奇怪组合。所以源码在纠正阶段会先查这两个文件,如果可疑位置属于已知人名或地名,就降低纠正力度或直接跳过。stopwords.txt则用于过滤掉“的、了、吗”这类高频虚词,它们出错概率低,但一旦被误判,对句子流畅度影响很大。这些看似简单的词表,实际上是模型上线前用大量 bad case 换来的,少了它们,准确率会掉好几个点。
4. 避坑与排查:纠错系统上线前必须跨过的几道坎
4.1 现象:模型把正确句子改错了
原因:检测阈值设得太低,导致大量正常位置被标为可疑,纠正阶段又强行替换。解决:在config.py里调高检测阈值,或者对高置信度正常位置加白名单。实际调参时,建议先用验证集画一条检测阈值 vs 误报率的曲线,找到拐点再定值。
4.2 现象:同音字错误纠正效果差
原因:候选池里正确字排名靠后,或者混淆集没覆盖到该混淆对。解决:增大predict_mask.py里的 top-k,同时检查same_pinyin.txt是否包含该对。如果是个别领域词汇,直接往custom_confusion.txt里加。
4.3 现象:长句纠错时后半句效果明显下降
原因:BERT 有最大序列长度限制(通常是 512),长句被截断后,后半句上下文丢失。解决:在text_utils.py里做句子切分,按标点或滑动窗口切成短句分别纠错,再拼接。切分时注意保留重叠区域,避免边界处的错误漏检。
4.4 现象:kenlm 打分让结果变差
原因:语言模型训练语料和实际场景领域不匹配,比如用新闻语料训的 kenlm 去纠错医学文本。解决:用run_lm_finetuning.py在领域语料上重新微调,或者降低 kenlm 分数在重排序中的权重。权重参数一般在corrector.py里能改。
4.5 现象:推理速度慢,批量处理卡死
原因:每次调用都重新加载模型,或者没有用 batch 推理。解决:把模型加载提到全局,只加载一次;在predict_mask.py里支持批量输入,一次前向传播处理多条。另外,torch.no_grad()一定要加,否则显存和耗时都会翻倍。
5. 设计取舍:为什么这套方案不追求“一步到位”
这套基于 BERT 的中文纠错方案,本质上是在准确率、召回率和工程复杂度之间做平衡。它没有用端到端的 seq2seq 直接生成正确句子,因为那样训练成本高、可控性差,而且容易产生“过度纠正”。它也没有纯用规则,因为规则覆盖不了语义层面的错误。检测 + 候选生成 + 重排序的三段式结构,虽然链路长,但每一段都可以单独替换和调优:检测可以用更轻的模型,候选生成可以换 RoBERTa 或 MacBERT,重排序可以加更多特征。这种模块化设计的好处是,当你发现某一类错误特别多时,能快速定位到对应模块去改,而不是面对一个黑盒束手无策。资源里的README.md和config.py把各个模块的入口和参数都列清楚了,顺着corrector.py的主流程读一遍,基本就能把整条链路串起来。如果你正在做中文纠错相关的项目,这份源码加模型的组合能帮你省掉大量搭骨架的时间,直接进入调优和业务适配阶段。需要的话,可以顺着文末的方式获取完整压缩包,把bert_models和kenlm跑起来,亲手改几个混淆集文件,感受一下准确率的变化——从那以后我每次调纠错系统,都会先把混淆集和词表过一遍,再动模型参数。希望帮到你。
本文还有配套的精品资源,点击获取