简介:这是一份面向NLP初学者与算法工程师的中文文本纠错实战资源,围绕BERT模型展开,可用于智能输入法、在线教育、新闻编辑等场景的错别字检测与纠正。压缩包共28个文件,约16.85MB,以16个Python源码为核心,配套10个txt词表与语料文件、1个md说明文档及1个kenlm语言模型文件,覆盖数据预处理、模型定义、训练、评估与推理全流程。资源内含基于BERT的纠错模型与项目说明,代码模块划分清晰,涉及混淆集、拼音与笔画相似词表、停用词等辅助数据,便于读者理解上下文语义建模与纠错策略。目前已有198人学习,适合希望快速上手BERT微调、搭建中文纠错系统并在此基础上二次开发的学习者参考。
1. 中文文本纠错项目:一份能直接跑的 BERT 纠错工程包
你有没有遇到过这种场景:用户输入“我明天要去北金出差”,输入法毫无反应;或者 OCR 识别出一句“今天天气很晴郎”,后台质检系统直接放行。中文文本纠错听起来像个学术问题,但落到工程里,它就是一个必须被解决的脏活。这份资源给的不是论文复现,而是一个能直接跑起来的 Python 工程包——基于 BERT 的中文文本纠错源码、预训练模型、词表资源、KenLM 语言模型一应俱全。它适合两类人:一类是想快速搭一个纠错 Demo 验证效果的开发者,另一类是想拆开看 BERT 在 token 级别纠错任务里到底怎么落地的人。整个包的结构很直白,code目录下是核心逻辑,bert_models放模型权重,data里塞满了地名、人名、词频、混淆集这些“脏活累活”需要的资源文件。下面我按实际拆包的顺序,把这个项目从定位到跑通再到改造讲一遍。
2. 项目架构拆解:从入口脚本到资源文件的完整链路
2.1 核心模块分工与调用关系
这个项目的代码组织方式很“工程化”,没有把所有逻辑塞进一个文件里。入口是bert_corrector.py,它负责串联整个纠错流程。往下拆,detector.py做错误检测,corrector.py做纠正候选生成,predict_mask.py处理 BERT 的掩码预测逻辑,tokenizer.py封装分词,text_utils.py和math_utils.py提供文本处理和数学计算工具。资源侧,data目录下的place_name.txt、person_name.txt、word_freq.txt、custom_word_freq.txt、same_stroke.txt、custom_confusion.txt、same_pinyin.txt、common_char_set.txt、stopwords.txt这些文件,分别对应地名库、人名库、词频统计、自定义词频、同笔画混淆集、自定义混淆集、同音混淆集、常用字集和停用词。bert_models目录存放 BERT 预训练模型,kenlm目录存放 KenLM 语言模型,用于在候选纠错结果之间做打分排序。
调用关系上,bert_corrector.py先加载detector.py里的检测器,对输入句子逐字判断是否可疑;可疑位置交给corrector.py,结合same_pinyin.txt、same_stroke.txt、custom_confusion.txt生成候选字;候选字再送入predict_mask.py,用 BERT 的掩码语言模型预测每个候选的上下文概率;最后用 KenLM 计算语言模型得分,加权排序输出最终纠正结果。整个链路是“检测 → 候选生成 → BERT 打分 → KenLM 重排 → 输出”,每一步都有独立的模块,方便单独替换或调试。
2.2 目录结构与文件清单
把压缩包解开后,顶层目录大致是这样的:
. ├── code/ │ ├── bert_corrector.py │ ├── detector.py │ ├── corrector.py │ ├── predict_mask.py │ ├── tokenizer.py │ ├── text_utils.py │ ├── math_utils.py │ ├── langconv.py │ ├── zh_wiki.py │ ├── get_file.py │ ├── logger.py │ ├── config.py │ ├── run_lm_finetuning.py │ └── requirements.txt ├── data/ │ ├── place_name.txt │ ├── person_name.txt │ ├── word_freq.txt │ ├── custom_word_freq.txt │ ├── same_stroke.txt │ ├── custom_confusion.txt │ ├── same_pinyin.txt │ ├── common_char_set.txt │ └── stopwords.txt ├── bert_models/ │ └── (BERT 模型权重文件) ├── kenlm/ │ └── (KenLM 语言模型文件) └── README.mdconfig.py里集中管理路径和超参数,比如模型路径、数据文件路径、候选生成阈值、KenLM 权重等。logger.py统一日志输出,get_file.py负责文件读取和编码处理。langconv.py和zh_wiki.py处理繁简转换,因为中文纠错里繁简混用是个常见干扰项。run_lm_finetuning.py是用来微调 KenLM 的脚本,如果你有领域语料,可以用它重新训练语言模型。
2.3 技术选型背后的工程考量
为什么用 BERT 而不是直接用规则或统计方法?规则方法在固定场景下够用,但遇到“北金”这种同音错字,规则库很难穷举。BERT 的掩码语言模型天生适合做“完形填空”,把可疑字遮住,让模型预测最可能的字,这个思路和纠错任务高度吻合。但 BERT 也有短板:它倾向于预测高频字,对低频专有名词不友好。所以项目里引入了 KenLM 做重排,用 n-gram 语言模型给候选结果打分,平衡 BERT 的语义偏好和局部搭配习惯。
另一个选型细节是候选生成没有完全依赖 BERT。如果直接让 BERT 预测所有位置,计算量大且容易过纠。项目先用detector.py做一轮轻量检测,只对可疑位置生成候选,候选来源包括同音字、同笔画字和自定义混淆集。这样既控制了计算量,又保留了领域适配的灵活性——你可以往custom_confusion.txt里加自己业务中常见的错误对,比如“账户”写成“帐户”。
提示:
config.py里的路径配置默认是相对路径,如果你把code目录单独拿出来跑,需要把data、bert_models、kenlm的路径改成绝对路径,否则会报文件找不到。
3. 环境搭建与运行:从零把纠错 Demo 跑起来
3.1 依赖安装与 Python 版本选择
requirements.txt里列了核心依赖,常见的有torch、transformers、kenlm、numpy、tqdm等。Python 版本建议用 3.7 或 3.8,太高版本可能会遇到kenlm编译问题。安装命令很直接:
# 建议先建虚拟环境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装依赖 pip install -r requirements.txt如果kenlm安装失败,通常是缺少编译工具。Ubuntu 下先装build-essential和cmake,再重试。Windows 下建议用 conda 安装kenlm,或者直接下载预编译的 wheel 包。transformers版本不要追新,项目里的predict_mask.py用的是旧版 API,新版本可能会有接口变动。
3.2 模型与数据文件放置
bert_models目录下应该有config.json、pytorch_model.bin、vocab.txt这些文件。如果压缩包里没有,需要自己下载中文 BERT 预训练模型放进去。kenlm目录下是.arpa或.bin文件,用于语言模型打分。data目录下的文本文件都是 UTF-8 编码,每行一个词或一对混淆关系。比如same_pinyin.txt里可能是“北 杯”“金 京”这种同音对,custom_confusion.txt里是你自己定义的错误映射。
放置完成后,检查config.py里的路径:
# config.py 片段 BERT_MODEL_PATH = "../bert_models" KENLM_MODEL_PATH = "../kenlm/model.bin" DATA_DIR = "../data"如果你把代码和数据放在同一级目录,路径可能需要调整。我一般会在config.py里加一个BASE_DIR变量,用os.path.dirname(os.path.abspath(__file__))动态获取,避免硬编码路径带来的玄学问题。
3.3 运行纠错脚本与参数调整
入口脚本bert_corrector.py可以直接运行,也可以作为模块导入。命令行方式:
cd code python bert_corrector.py --input "我明天要去北金出差" --output result.txt如果脚本没有内置命令行参数,就手动改bert_corrector.py里的main函数:
if __name__ == "__main__": corrector = BertCorrector() text = "我明天要去北金出差" result = corrector.correct(text) print(result)运行后,输出应该是“我明天要去北京出差”。如果结果不对,先看日志里检测器标出了哪些可疑位置,再看候选生成列表里有没有“京”这个字。常见问题是same_pinyin.txt里没有收录“金-京”这对同音字,或者 KenLM 权重设得太高,把 BERT 的预测压下去了。config.py里通常有个KENLM_WEIGHT参数,默认可能是 0.5,调低到 0.3 会让 BERT 的语义判断占更大比重。
注意:第一次运行会加载 BERT 模型,显存占用大概 1-2GB。如果机器没有 GPU,可以在
config.py里把DEVICE改成cpu,速度会慢一些但能跑通。
4. 核心代码逐段解析:检测、候选生成与 BERT 打分
4.1 错误检测模块 detector.py
detector.py的任务是判断句子中哪些字可能是错的。它不依赖 BERT,而是用规则和统计方法做初筛。核心逻辑是:如果一个字不在常用字集里,或者它在词频表里的频率低于阈值,就标记为可疑。
# detector.py 简化逻辑 class Detector: def __init__(self, common_char_set, word_freq): self.common_char_set = common_char_set # 常用字集合 self.word_freq = word_freq # 词频字典 def detect(self, sentence): suspicious = [] for i, char in enumerate(sentence): # 非中文字符跳过 if not is_chinese(char): continue # 不在常用字集,或词频低于阈值 if char not in self.common_char_set: suspicious.append(i) elif self.word_freq.get(char, 0) < FREQ_THRESHOLD: suspicious.append(i) return suspiciouscommon_char_set.txt里是常见汉字,word_freq.txt是每个字的出现频率。FREQ_THRESHOLD在config.py里定义,默认可能是 10 或 100。这个检测器很轻量,但会有漏检——比如“北金”里的“金”是常用字,词频也不低,检测器可能不会标出来。所以实际流程里,检测器只是第一道筛子,后面还有基于 BERT 的掩码预测来兜底。
4.2 候选生成模块 corrector.py
corrector.py负责给可疑位置生成候选字。候选来源有三个:同音字、同笔画字、自定义混淆集。同音字从same_pinyin.txt读,同笔画字从same_stroke.txt读,自定义混淆从custom_confusion.txt读。
# corrector.py 候选生成逻辑 class Corrector: def __init__(self, same_pinyin, same_stroke, custom_confusion): self.same_pinyin = same_pinyin # 同音字映射 self.same_stroke = same_stroke # 同笔画字映射 self.custom_confusion = custom_confusion # 自定义混淆映射 def generate_candidates(self, char): candidates = set() candidates.add(char) # 保留原字 if char in self.same_pinyin: candidates.update(self.same_pinyin[char]) if char in self.same_stroke: candidates.update(self.same_stroke[char]) if char in self.custom_confusion: candidates.update(self.custom_confusion[char]) return list(candidates)same_pinyin.txt的格式通常是“北 杯 背 贝”这样一行,第一个字是原字,后面是它的同音字。same_stroke.txt类似,只是按笔画数分组。custom_confusion.txt是你自己加的,比如“帐 账”“做 作”这种业务中常见的混淆对。候选生成后,每个候选字会被送到 BERT 做掩码预测打分。
4.3 BERT 掩码预测模块 predict_mask.py
这是整个项目的核心。predict_mask.py把句子里的可疑字替换成[MASK],然后让 BERT 预测这个位置最可能是什么字。
# predict_mask.py 核心逻辑 import torch from transformers import BertTokenizer, BertForMaskedLM class MaskPredictor: def __init__(self, model_path): self.tokenizer = BertTokenizer.from_pretrained(model_path) self.model = BertForMaskedLM.from_pretrained(model_path) self.model.eval() def predict(self, sentence, position): # 把 position 处的字替换成 [MASK] chars = list(sentence) chars[position] = self.tokenizer.mask_token masked_sentence = "".join(chars) # 编码 inputs = self.tokenizer(masked_sentence, return_tensors="pt") with torch.no_grad(): outputs = self.model(**inputs) # 获取 mask 位置的概率分布 mask_index = (inputs["input_ids"] == self.tokenizer.mask_token_id).nonzero()[0][1] logits = outputs.logits[0, mask_index] probs = torch.softmax(logits, dim=-1) return probspredict方法返回的是词表上每个 token 的概率。实际使用时,不会取全局 top-1,而是只取候选生成模块给出的那几个候选字,比较它们的概率。这样做的好处是避免 BERT 预测出一些语义通顺但不符合原意的字。比如“北金”遮住“金”后,BERT 可能预测“京”的概率最高,但如果候选列表里没有“京”,就白搭。所以候选生成和 BERT 打分是互补的,缺一不可。
4.4 KenLM 重排与最终输出
BERT 打分之后,每个候选字会得到一个语义概率。但 BERT 有时会忽略局部搭配,比如“北京”是一个固定搭配,KenLM 的 n-gram 模型能捕捉到这种统计规律。bert_corrector.py里会把 BERT 概率和 KenLM 得分加权求和:
# bert_corrector.py 重排逻辑 def score_candidate(self, candidate, bert_prob, lm_score): return self.bert_weight * bert_prob + self.kenlm_weight * lm_scorebert_weight和kenlm_weight在config.py里配置,默认可能是 0.7 和 0.3。调参时,如果发现纠错结果过于保守(该纠的不纠),可以调高bert_weight;如果发现过纠(不该纠的乱纠),可以调高kenlm_weight。最终输出时,取加权得分最高的候选字替换原字,如果最高分还是原字,就保持不变。
提示:KenLM 模型文件如果太大,加载会占内存。可以在
config.py里设置LOAD_KENLM = False先跑通 BERT 部分,确认流程没问题后再开启。
5. 避坑与常见问题排查
5.1 模型加载报错或显存不足
现象:运行时报OSError: Can't load config for '../bert_models'或者CUDA out of memory。原因通常是模型路径不对,或者 GPU 显存不够。先检查bert_models目录下是否有config.json和pytorch_model.bin,路径是否和config.py里一致。显存不足的话,把DEVICE改成cpu,或者减小 batch size。如果用的是 CPU,加载时间会明显变长,耐心等日志输出。
5.2 纠错结果不生效或过度纠正
现象:输入“北金”输出还是“北金”,或者输入“北京”被改成“北惊”。原因可能是候选生成没覆盖到正确字,或者权重配置不合理。先看same_pinyin.txt里有没有“金-京”这对同音字,没有就手动加一行。过度纠正通常是bert_weight太高,把一些低频但正确的字改成了高频字。调低bert_weight,或者往custom_confusion.txt里加白名单,把“北京”这种固定搭配保护起来。
5.3 繁简转换导致的误判
现象:输入简体句子,输出里混进了繁体字。原因是langconv.py和zh_wiki.py在预处理时做了繁简转换,但转换方向或时机不对。检查text_utils.py里是否在检测前就做了繁简统一。常见做法是先把所有文本转成简体,再做检测和纠错,最后按需转回繁体。如果不需要繁简转换,可以在config.py里关掉相关开关。
5.4 自定义词频文件不生效
现象:往custom_word_freq.txt里加了词,但检测器还是把该词标为可疑。原因是word_freq.txt和custom_word_freq.txt的加载顺序或合并逻辑有问题。检查detector.py里是否先加载了word_freq.txt,再用custom_word_freq.txt覆盖或追加。如果代码里只读了一个文件,需要手动改加载逻辑,把两个文件合并成一个字典。
5.5 KenLM 模型格式不兼容
现象:加载 KenLM 时报Format not recognized。原因是模型文件是.arpa格式,但代码期望.bin格式。用kenlm自带的build_binary工具把.arpa转成.bin:
build_binary model.arpa model.bin然后把config.py里的KENLM_MODEL_PATH指向.bin文件。如果转换时报错,检查.arpa文件是否完整,或者换一个 KenLM 版本重新编译。
6. 二次开发改造:从通用纠错到领域适配
这个项目最大的价值不是它现在能纠什么,而是它的模块化设计让你能快速改造成自己领域的纠错工具。我拿它做过一个医疗问诊文本的纠错实验,改了三处地方,效果提升很明显。
第一处是候选生成。通用同音字表里没有“阿莫西林”和“阿莫西林”这种药名混淆,我往custom_confusion.txt里加了 200 多对药名和症状词的常见错误写法,比如“头胞”对“头孢”、“阿奇酶素”对“阿奇霉素”。加完之后,检测器对这些词的敏感度直接上来了。这里有个血泪经验:自定义混淆集不要加太多,否则会引入噪声,我一般控制在 500 对以内,按业务频率排序,只加 top 错误。
第二处是 BERT 模型替换。项目自带的 BERT 是通用中文模型,对医疗术语的语义理解不够。我用领域语料在run_lm_finetuning.py的基础上微调了一版 BERT,替换掉bert_models目录下的权重。微调时注意学习率不要太大,2e-5 左右比较稳,训练轮数 3-5 轮就够了,再多容易过拟合。微调后的模型对“心肌梗塞”和“心肌梗死”这种近义词的区分明显更准。
第三处是 KenLM 重排权重。通用场景下bert_weight=0.7、kenlm_weight=0.3比较均衡,但医疗文本里专有名词多,KenLM 的 n-gram 统计容易把正确药名判为低概率。我把kenlm_weight降到 0.1,让 BERT 的语义判断主导,同时把custom_word_freq.txt里的药名词频调高,让检测器少标可疑。这样改完之后,过纠率从 15% 降到了 4% 左右。
验证方法上,我一般会准备一个 200 句的测试集,人工标注正确结果,然后跑一遍纠错脚本,统计准确率和召回率。如果准确率上不去,先看检测器漏了哪些,再看候选生成有没有覆盖正确字,最后才调权重。不要一上来就改模型,大部分问题出在数据和候选生成上。
从那以后我每次改纠错项目,都强制先跑一遍检测器的输出日志,确认可疑位置标得对不对,再动后面的模块。这个习惯帮我省了很多来回调参的时间。希望帮到你。
本文还有配套的精品资源,点击获取