资讯详情

资讯详情

BERT文本纠错项目拆解:规则检测与掩码预测实战

简介基于BERT的文本纠错项目面向自然语言处理方向的毕业设计、课程设计与期末大作业场景提供可运行的Python源码、预训练模型使用说明及配套数据帮助学习者快速上手BERT掩码语言模型在中文拼写纠错中的应用。压缩包内共39个文件涵盖19个py脚本含纠错主程序、规则纠错器、文本处理工具等、12个txt词典与停用词数据、4个xml工程配置以及模型文件、Markdown说明和KenLM语言模型文件等整体仅22.37MB轻量易部署。已有303人浏览学习。资源附带了完整的目录结构与运行命令并给出针对垂直领域重新训练的微调方式便于扩展。无论是入门BERT实践还是直接作为毕设、大作业的初期演示均具备较高参考价值。1. BERT 文本纠错项目从下载到跑通先看清这套架构中文文本纠错是个容易被低估的问题。你搜“BERT 文本纠错”会看到大量论文和开源仓库但能直接下载、跑通、还能当课程设计交差的并不多。这个资源属于后者一套典型的“规则检测 BERT 掩码预测”混合架构。它先用同音字、形近字和词频字典把可疑位置标出来再把可疑字替换成 [mask] 交给 BERT 预测候选字最后按置信度从候选里挑出纠错结果。压缩包里把检测器、纠错器、九个数据字典、预训练模型目录、微调脚本、新闻语料全部放在一起适合期末大作业、课程设计也适合拿来做毕设的基座做二次开发。下面所有分析基于我实际拆包跑通的过程新手可以照着做熟手可以直接跳到第 3 章看 mask 纠错的参数细节。2. 检测层拆解同音字、形近字与词频字典如何找出可疑字2.1 detector.py 的检测流程先看它怎么工作拿到压缩包后我先看的是detector.py。这个文件负责“找出哪里错了”是整套系统的第一层筛子。常见做法是跑一个滑窗对输入句子逐个取词把每个词去查同音字表、形近字表生成一组候选接着用词频词典对“原文 vs 候选”所在的上下文打分判断原文是不是可疑。单纯靠查表会误伤大量人名地名所以项目里把place_name.txt、person_name.txt作为白名单命中白名单的词直接跳过这一步非常关键。按项目文件还原的逻辑我可以把检测流程简化成下面这段伪代码# 检测器核心流程示意接口和原项目一致内部实现做了简化 class Detector: def __init__(self, data_dir): self.same_pinyin load_dict(data_dir / same_pinyin.txt) # 同音字映射 self.same_stroke load_dict(data_dir / same_stroke.txt) # 形近字映射 self.place_name load_set(data_dir / place_name.txt) # 地名白名单 self.person_name load_set(data_dir / person_name.txt) # 人名白名单 self.word_freq load_freq(data_dir / word_freq.txt) # 通用词频 def detect(self, sentence): errors [] for pos, char in enumerate(sentence): if char in self.place_name or char in self.person_name: continue # 白名单优先避免人名地名误报 candidates set(self.same_pinyin.get(char, [])) candidates | set(self.same_stroke.get(char, [])) if not candidates: continue # 用词频做一次上下文粗筛频次显著更高的候选才留下 ranked [c for c in candidates if self.word_freq.get(c, 0) self.word_freq.get(char, 0)] if ranked: errors.append((pos, char, ranked)) return errors这段代码的逻辑是先将place_name.txt和person_name.txt读成集合作为白名单再对句子逐字取候选最后用word_freq.txt的词频做粗筛候选字频次高于原字的才认为“可疑”。注意这里有个参数细节word_freq.txt里如果缺失某个字get(c, 0)会返回 0恰好避免 KeyError。如果你想提高召回率可以把改成但代价是误报变多。实际调参时我一般保持默认靠第二层 BERT 去精排。2.2 data 目录下的字典文件九个 txt 各自管什么data目录是这套资源最值钱的地方。每个 txt 的责任很清晰拆包后我按用途整理如下文件名类型内容与格式约定place_name.txt地名白名单每行一个地名命中则跳过检测person_name.txt人名白名单每行一个姓名避免把人名当错字word_freq.txt通用词频表常见格式为“字/词 空格 频次”custom_word_freq.txt自定义词频格式同上覆盖默认词频优先级更高same_stroke.txt形近字表每行一组字形近关系互查same_pinyin.txt同音字表每行一组同音字全拼相同互查custom_confusion.txt用户混淆集错词 空格 正确词专门给常见易错词纠错common_char_set.txt常用字集合决定哪些字参与候选生成stopwords.txt停用词表语气词、标点等不参与检测词频类文件注意编码项目默认 UTF-8。实际使用中我最常改的是custom_confusion.txt它解决的是同音和形近都覆盖不到的语义错误。比如“账户”与“帐户”这样拼音相同、但字面不完全落在同音表里的场景你在这个文件里手工加一行“帐户 账户”就能稳定纠出来。common_char_set.txt的作用是缩小候选范围不在常用字集里的生僻字不参与生成能明显加快推理速度。这几个文件相互独立但也有依赖关系白名单要在候选生成之前查词频表在候选生成之后做粗排混淆集则可以跳过规则直接进入 BERT 精排。改动任何一个 txt 后不用重新训练直接重启detector.py即可生效这也是这个项目适合拿来二次开发的原因。2.3 规则检测的边界为什么不能只用同音字纯规则的检测层有天生的盲区。第一同音字表覆盖有限多音字、方言发音、拼音输入漏字它都管不到第二形近字表容易把“己/已/巳”这类高频混淆字全列成候选没有上下文区分能力第三词频粗筛对专业名词不友好“卷秩”这种低频但正确的词会被当成错字。所以正确的打开方式是检测层负责召回即宁多勿漏纠错层用 BERT 的上下文表征负责精排即从候选里挑最合适的字。这也是这个资源把 BERT 放在第二层的原因。实际跑的时候我还发现一个细节白名单的作用比想象中大。地名、人名如果不设白名单“张三”里的“三”会被同音字表换成“伞山叁”等一批候选误报直接爆炸。项目把place_name.txt和person_name.txt单独拆出来就是在告诉你不要在算法里写死白名单要把它们做成可维护的数据文件。课程设计答辩时这个设计点可以单独讲两分钟。另外stopwords.txt只在检测阶段影响高频虚词不参与 BERT 精排。3. 纠错层拆解把可疑位置换成 [mask]让 BERT 给出候选3.1 bert_corrector.py 主流程检测、mask、预测、过滤第二层的核心是bert_corrector.py。它从data/bert_models/chinese_finetuned_lm加载 BERT 模型拿到第 2 章检测出的可疑位置后把每个可疑字替换成[MASK]送入模型取该位置的输出分布然后按概率挑 top-k 候选。这里很多人把 BERT 当成黑匣子直接用其实有一个参数值得注意因为一个句子可能有多个错误项目一般会逐位替换、逐位预测而不是一次性把所有可疑位全 mask 掉。一次性全 mask 会让模型丧失上下文预测质量明显下降。简化的核心代码如下import torch from transformers import BertTokenizer, BertForMaskedLM model_dir data/bert_models/chinese_finetuned_lm tokenizer BertTokenizer.from_pretrained(model_dir) model BertForMaskedLM.from_pretrained(model_dir) def correct_with_bert(sentence, error_positions, top_k5): result list(sentence) for pos, char, rule_candidates in error_positions: # 逐位替换保留其余上下文完整 masked sentence[:pos] [MASK] sentence[pos1:] inputs tokenizer(masked, return_tensorspt) with torch.no_grad(): outputs model(**inputs).logits # [1, seq_len, vocab] mask_index inputs[input_ids].tolist()[0].index( tokenizer.convert_tokens_to_ids([MASK])) logits outputs[0, mask_index] # 只取 mask 位置 probs torch.softmax(logits, dim-1) # 取前 top_k 个候选 token并转成汉字 top_tokens torch.topk(probs, top_k).indices.tolist() candidates [tokenizer.decode([t]) for t in top_tokens] # 与规则候选取交集优先没有交集就取模型第一位 final intersect(candidates, rule_candidates) or candidates[0] result[pos] final return .join(result)逻辑说明代码只对 mask 位置的 logits 做 softmax避免全词表 21128 维输出干扰判断topk(probs, top_k)的top_k5是项目里比较常用的值小于 3 会漏掉正确答案大于 10 引入太多噪声。intersect这一步值得单独说规则候选和 BERT 候选如果有交集说明两个来源都认可正确率最高如果没交集信任 BERT 的第一名因为它的上下文表征比单纯词频粗筛可信。另外data/bert_models/chinese_finetuned_lm目录下必须有config.json、pytorch_model.bin、vocab.txt三者齐备才能加载成功。vocab.txt是 BERT 词表的顺序tokenizer 依赖它做 id 到字的映射缺了它即使有权重文件也跑不起来。这个目录放的是已经微调过的中文模型而不是裸的bert-base-chinese这也是一个容易误解的点。提示从input_ids里用.index()查找[MASK]的位置在中文场景基本安全因为每个汉字是一个 token如果换成英文组合词输入可能被拆成多个 token需要改用inputs[input_ids]与tokenizer.mask_token_id逐位比对。3.2 predict_mask.py直接观察 mask 位置的候选字predict_mask.py是一个调试利器。它的用法是你手动在句子里把某个字写成[mask]运行脚本直接输出该位置模型认为最可能的若干个汉字。我拆包后第一件事就是拿它跑了一句“我今天[马]上要去开会”观察模型能不能填回“马”。这类手测的意义在于它把 BERT 从黑匣子变成可交互的工具你能快速判断这份预训练模型对当前领域文本的覆盖程度。一个很典型的判断方法是拿十句你领域内的句子手动挖掉一个关键字看模型 top-1 命中率。如果命中率明显低于通用文本的预期水平那就说明你需要第 4 章的微调而不是盲目在规则层加字典。这一步成本极低几分钟就能得到结论很多人在毕设里忽略了它直接一股脑训练最后效果差又不知道原因。python predict_mask.py运行后按提示输入带[mask]的句子脚本会打印当前 mask 位置的前五个候选字。你可以直接改脚本里的top_k来观察候选变化这是评估模型领域适应度最直观的手段。3.3 text_utils.py 与 langconv.py文本预处理和简繁转换预处理层有两个文件容易被忽略一是text_utils.py二是langconv.py。langconv.py负责简繁转换配合zh_wiki.py的词条映射使用。为什么纠错要管简繁因为开源预训练语料里繁体比例不低如果输入是简体、模型训练时见过大量繁体同一个字在 tokenizer 里会被切成不同 tokenmask 预测结果会不稳定。常见做法是统一转成简体再送模型输出时再转回用户需要的字型。text_utils.py里还封装了去空白字符、统一引号、过滤停用词等操作。这里有一个容易踩的细节停用词过滤只应该作用于检测阶段不能作用到 BERT 输入。如果提前把“的、了”从句子中删掉BERT 拿到的就不是完整上下文模型在 mask 位置的预测会整体漂移。我在第一次跑的时候就犯过这个错纠错后句子变得不通顺后来查代码才发现是预处理顺序反了这个放到第 5 章避坑里细说。4. 重新训练语言模型微调脚本参数、数据准备与验证4.1 run_lm_finetuning.py 的两套命令行参数对比压缩包里给了两份训练命令分别对应两代 transformers 接口。第一份是现代 Trainer 风格export CUDA_VISIBLE_DEVICES0 TRAIN_FILEdata/train.txt TEST_FILEdata/test.txt python run_lm_finetuning.py \ --output_dirchinese_finetuned_lm \ --model_typebert \ --model_name_or_pathbert-base-chinese \ --do_train \ --train_data_file$TRAIN_FILE \ --do_eval \ --eval_data_file$TEST_FILE \ --mlm \ --num_train_epochs3第二份是旧式的脚本式写法python -m run_lm_finetuning \ --bert_model bert-base-uncased \ --do_lower_case \ --do_train \ --train_file ./samples/sample_text.txt \ --output_dir ./samples/samples_out \ --num_train_epochs 5.0 \ --learning_rate 3e-5 \ --train_batch_size 16 \ --max_seq_length 128两套命令的目标相同都是对 BERT 做掩码语言模型继续训练区别在参数面向的接口版本。第一套里--model_typebert指定模型结构--model_name_or_pathbert-base-chinese指定初始权重--mlm是掩码语言模型任务的开关第二套里--bert_model bert-base-uncased是英文 uncased 模型如果你要处理中文需要把它改成bert-base-chinese或者干脆改成你本地微调好的模型目录。注意第二套默认是英文小写处理--do_lower_case对中文没有实际意义保持原样即可。关键参数对比整理如下参数含义第一套写法第二套写法取值建议初始权重model_name_or_pathbert_model中文任务用 bert-base-chinese 或本地微调目录训练数据train_data_filetrain_file纯文本按行切分学习率默认3e-53e-5 常见再低收敛慢训练轮数num_train_epochs3num_train_epochs 5.03-5 轮轮数多过拟合批大小默认1612G 显存建议 16不足降到 8有个参数特别值得说明--train_batch_size 16是典型单卡12G 显存的配置。如果显存不足把--train_batch_size降到 8同时把--gradient_accumulation_steps换成 2效果等价但不爆显存。第一次看到一个参数我建议优先改 batch size不要动--learning_rate 3e-5这个数值对 BERT 微调是相对安全的范围。4.2 准备训练数据把新闻语料转成纯文本数据准备是微调里面最容易被糊弄的一步。项目自带sample里有一个新闻语料压缩包解压后是一大段纯文本新闻拿来训练完全没有问题但要注意三件事。第一BERT 的 MLM 训练只接收纯文本不要带标签、不要带 json 结构按段落换行即可。第二语料里如果混有繁体要在训练前统一简繁否则模型会把繁简两个 token 当成两个语义单元来学。第三按 9:1 切分训练集和测试集测试集不参与训练只用来跑--do_eval。数据准备的常见流程是这样# 解压并转码 unzip sample/新闻语料.zip -d sample/news_texts # 简单切分为训练和测试两份纯文本 head -n 9000 sample/news_texts/clean.txt data/train.txt tail -n 1000 sample/news_texts/clean.txt data/test.txt这里我一般会在切分前先做一次清洗把每行超过 128 字的句子截断按句号或换行拆成短行因为--max_seq_length 128决定了 BERT 最长只能看 128 个 token超长的部分会被截掉。另一个建议是train.txt里的行不要给句子加空格中文不需要按空格分词不加反而更符合 BERT 中文词表的分词逻辑。注意train.txt每行建议控制在 64 个中文字以内留出 [CLS]、[SEP] 和随机 mask 的 token 空位。超长行进入训练会被截断等于浪费一部分语料。4.3 微调之后怎么判断效果有没有变好很多人微调完只看 loss 降了就以为成功实际上 loss 降低不代表纠错正确率提高。原因是 MLM 任务学的是词语概率分布而纠错任务关心的是在候选集中选对字。跑完--do_eval后拿到评估 loss 只能说明模型对语料的拟合程度要判断有没有变好我自己的习惯是固定 20 条垂直领域句子每句挖掉一个关键词分别用微调前、微调后的模型跑predict_mask.py对比 top-1 是否更接近正确答案。如果微调后 top-1 命中率没有提升先检查训练数据的量和纯文本质量。说实话这块存在一定玄学同一个参数跑两次结果可能因为随机种子而有细微差别。我一般会在命令里固定--seed 42并把训练轮数控制在 3 到 5 轮之间轮数再多容易在垂直领域上过拟合反而降低了通用文本上的表现。毕设里想展示效果建议把微调前后的纠错结果做成前后对比表同时给 loss 曲线截图答辩时比一堆抽象指标更有说服力。5. 避坑跑通与部署中的 5 个高频问题5.1 现象一from_pretrained 加载失败报找不到文件拆包后第一次跑最常见的报错是Cant load config.json或pytorch_model.bin路径不存在。原因通常是压缩包解压后data/bert_models/chinese_finetuned_lm的目录层级变了两层比如多了一层外层文件夹from_pretrained(data/bert_models/chinese_finetuned_lm)指向的目录里缺少config.json、pytorch_model.bin、vocab.txt三个文件加载必然报错。解决方法是打开目录确认这三个文件存在且在同一层缺哪个补哪个如果只缺二进制权重可以重新解压原压缩包单独拷出来。这里提醒一句config.json不仅记录配置还定义了模型结构权重和配置不匹配时加载会静默出错所以尽量不要自己改动它。5.2 现象二transformers 版本不一致tokenizer 接口报错项目是基于较老版本的 transformers 写的如果你用新版本跑常见报错是tokenizer.encode参数变了batch_decode行为不同甚至BertForMaskedLM.from_pretrained警告权重不一致。原因很好理解新版把很多旧接口改成了统一风格旧代码里convert_tokens_to_ids系列还能用但encode返回对象从 list 变成BatchEncoding。我一般会先在脚本里打印type(tokenizer(测试, return_tensorspt))确认是新接口后把按 key 取input_ids的写法统一别依赖位置索引。如果实在不想改代码就按requirements.txt里的版本装一个干净环境这是最省事的后悔药。5.3 现象三繁简体混用导致检测结果全是误报输入句子是简体但字典里的词或预训练模型输出的是繁体纠错后你会发现每个位置都“被纠错”结果变成一片繁体字。原因在langconv.py的转换链路没走到常见做法是在检测前统一调to_simplified在输出阶段再按需要转回目标字型。我一开始图省事直接在bert_corrector.py里注释了转换调用结果训练语料里的繁体词全部被当成候选误报直接爆炸。排错时可以先跑一句纯简体、一句纯繁体看转换是否对称再跑一句简繁混合看结果是否稳定。记住这条链路是输入 - 繁体转简体 - 检测 - BERT 纠错 - 简体转目标字型。5.4 现象四通用模型对垂直领域错误视而不见微调完跑自己领域的句子发现 BERT 经常把正确字换成错误字甚至 mask 出来的候选里压根没有正确答案。这不是代码问题是通用bert-base-chinese在微调之前并没有见过足够多的领域表达。我踩过的典型场景是某次处理一个含大量机器术语的 demo模型把术语里的“转矩”纠成“转举”而且概率极高。原因就是领域词在预训练语料里出现次数太少mask 预测的分布被通用高频词带偏了。解决方案不是反复调top_k而是老老实实按第 4 章用领域语料做继续预训练并把custom_confusion.txt里的领域易错词加进去。这里有个量级感觉领域语料至少 10 万行以上模型才可能在 mask 位置给出稳定输出低于这个量级的大部分提升来自混淆集而非模型微调。5.5 现象五命令行参数复制到终端直接“翻车”run_lm_finetuning.py给的命令里行尾必须加反斜杠续行如果不信直接复制粘贴到 bash第二行--num_train_epochs3会被解析成一条新命令报错command not found。我第一次跑就翻了车后来总结成习惯凡是从 md 文件复制多行命令行先在文本编辑器里确认每行末尾的反斜杠没有少。同时注意export CUDA_VISIBLE_DEVICES0是 Linux 下的写法Windows 命令行要用set CUDA_VISIBLE_DEVICES0替代。这类问题不算项目 bug属于平台差异但排错时最容易让人怀疑是代码坏了。6. 进阶把这份资源改造成属于自己的毕设项目跑通bert_corrector.py只是第一步。要把这套资源变成能拿出手的毕设或课程设计我给你一个最省力的改造组合拳先扩custom_confusion.txt做领域纠错再用predict_mask.py做上线前的验证最后写一张前后对比表。先说混淆集。这个文件的格式非常简单每行一对词左边错词右边正确词转举 转矩 帐户 账户加入后规则层会直接把“转举”标成可疑并优先选混淆集给定答案。这一步对垂直领域效果立竿见影且完全不需要重训模型。其次是验证改造完混淆集用第 3.2 章的 predict_mask 方法挖掉十句领域句子的关键字跑 top-1记录命中率。命中率稳定在 70% 以上再考虑要不要动微调反而是模型微调的边际收益在数据量不足时往往不如混淆集来得直接。最后是成果呈现。毕设答辩最怕你说“我跑通了代码”答辩评委其实想听的是“你改了哪一层、为什么这么改”。我建议做一个三列表格把领域特有错误的纠正单独摘出来场景原文纠错结果同音帐户余额不足账户余额不足形近我要买只笔我要买支笔领域混淆转举机构异常转矩机构异常这个表比 loss 曲线更直观也是判断项目有没有真实工程落地能力的硬标准。回顾我拆这个项目的全程有两点教训值得分享。第一是永远先跑通最小案例再追求精度我一度在规则检测上反复加字典几天没进展回头看其实 BERT 精排早就给出了正确候选问题只在我的预处理顺序上第二是每次改动数据字典前把原文件做个备份这是个低成本后悔药但真到模型效果被改坏了想回滚时它比重新解压快得多。从那以后我每次拿到这种开源资源都强制走一遍“先跑通 demo、再逐层换数据、最后写对比表”的流程把黑匣子一点一点打开。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →