ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于BERT的中文文本纠错工程实战:从Mask预测到混淆集解码

基于BERT的中文文本纠错工程实战:从Mask预测到混淆集解码 简介这份资源是面向计算机相关专业在校学生、教师及企业员工的基于BERT的文本纠错项目完整源码包可作为毕业设计、课程设计、期末大作业或初期项目立项演示使用。项目以Python实现包含纠错主程序、掩码预测、规则纠错、语言模型与数据预处理等模块并附有使用说明与配套数据帮助读者理解如何利用预训练语言模型完成中文文本纠错任务。压缩包共39个文件以19个py源码、12个txt词表与配置、4个xml工程文件为主另有md说明文档等整体约22.37MB目录结构清晰便于按模块查阅与二次开发。目前已有303人学习下载。读者可据此掌握BERT微调、混淆集与拼音字形规则结合、垂直领域重训练等实践思路并在此基础上拓展自己的纠错方案。1. 一份能跑通的 BERT 文本纠错工程到底长什么样如果你正在做毕业设计、课程设计或者期末大作业选题方向是 NLP、深度学习或者文本处理大概率会碰到「文本纠错」这个题目。它看起来不难——不就是把错别字改对吗但真正动手你会发现从模型选型、数据准备、混淆集构建到解码策略每一步都有坑。这份基于 BERT 的文本纠错项目包含完整的 Python 源码、使用说明和配套数据核心文件包括bert_corrector.py、predict_mask.py、rule_corrector.py以及run_lm_finetuning.py数据目录下有拼音混淆集、字形混淆集、词频表和人民日报语料。它解决的不是「demo 级别跑个 hello world」的问题而是一个可以实际部署、支持规则与模型融合的纠错流程。适合谁正在找 NLP 方向毕设题目的同学、想理解 BERT 在中文纠错中怎么落地的开发者以及需要一个可扩展基线系统的工程师。2. BERT 纠错的核心机制从 Mask 预测到混淆集解码2.1 为什么用 BERT 做纠错而不是 Seq2Seq文本纠错本质上是一个「序列到序列」的问题但直接用 Seq2Seq 做纠错有一个致命缺陷模型容易把原本正确的句子改错也就是过度纠正。BERT 的 MLMMasked Language Model预训练任务天然适合纠错场景——它本来就是在做「根据上下文预测被 mask 掉的 token」这和「根据上下文判断某个字是否写错、应该改成什么」高度一致。具体来说这个项目的纠错流程是这样的先通过规则检测器detector.py定位疑似错误的位置然后用 BERT 对疑似位置进行 mask 预测得到候选字列表最后结合拼音混淆集、字形混淆集和词频信息做解码排序选出最优修正结果。这个「检测 → 预测 → 解码」的三段式架构比端到端的 Seq2Seq 更可控也更容易排查问题。项目里bert_corrector.py是主入口predict_mask.py可以单独观察 mask 位置的预测结果rule_corrector.py则负责基于规则的纠错。这种模块化设计的好处是你可以单独调试每一个环节而不是面对一个黑匣子。2.2 环境搭建与依赖安装拿到源码包后第一步是把环境跑起来。项目根目录下有requirements.txt但根据我的经验直接pip install -r requirements.txt大概率会遇到版本冲突尤其是 transformers 和 torch 的版本匹配问题。我一般会先建一个干净的虚拟环境然后手动装核心依赖。# 创建虚拟环境建议 Python 3.7 或 3.8 python -m venv bert_corrector_env source bert_corrector_env/bin/activate # Linux/Mac # bert_corrector_env\Scripts\activate # Windows # 安装核心依赖注意版本匹配 pip install torch1.8.0cu111 -f https://download.pytorch.org/whl/torch_stable.html pip install transformers4.6.0 pip install numpy pandas tqdm这里有几个参数需要说明。torch1.8.0cu111是带 CUDA 11.1 支持的版本如果你没有 GPU直接pip install torch1.8.0即可。transformers4.6.0是我实测和这个项目兼容性最好的版本太新的版本 API 有变动run_lm_finetuning.py里的--mlm参数在新版中已经被废弃。装完依赖后检查一下torch.cuda.is_available()是否返回 True这决定了你后续训练时能不能用 GPU 加速。2.3 预训练模型的放置与加载项目使用说明里明确写了需要把预训练模型放在data文件夹下目录结构如下├── data │ ├── bert_config.json │ ├── config.json │ ├── pytorch_model.bin │ └── vocab.txt ├── bert_corrector.py ├── config.py ├── logger.py ├── predict_mask.py ├── README.md └── text_utils.py这里用的是bert-base-chinese的权重。如果你下载的是 HuggingFace 格式的模型文件名可能是pytorch_model.bin和config.json如果是 Google 原始格式则是bert_config.json和vocab.txt。项目里config.py会读取这些文件路径你需要确认路径配置正确。# config.py 中的关键配置 import os class Config: def __init__(self): self.bert_model_dir os.path.join(os.path.dirname(__file__), data) self.bert_config_path os.path.join(self.bert_model_dir, bert_config.json) self.bert_model_path os.path.join(self.bert_model_dir, pytorch_model.bin) self.vocab_path os.path.join(self.bert_model_dir, vocab.txt) self.max_seq_length 128 self.batch_size 16 self.learning_rate 3e-5max_seq_length128意味着单次处理的文本长度上限是 128 个 token超过会被截断。对于长文本纠错你需要自己写一个滑窗逻辑来分段处理。batch_size16和learning_rate3e-5是微调时的默认参数后面在run_lm_finetuning.py里还会用到。2.4 运行纠错与 Mask 预测环境配好、模型放对位置后直接运行bert_corrector.py就能看到纠错效果python bert_corrector.py这个脚本会加载模型读取renmin.txt或你指定的输入文件逐句进行纠错输出修正后的文本。如果你想观察 BERT 在某个位置到底预测了哪些候选字可以运行predict_mask.pypython predict_mask.py这个脚本的作用是手动构造一个带[MASK]的句子比如「今天天气真[MASK]好」然后看模型预测的前 10 个候选字是什么。这对于调试混淆集和解码策略非常有用——你可以直观地看到模型认为哪些字更合理然后再结合拼音、字形规则做筛选。# predict_mask.py 核心逻辑示意 from transformers import BertTokenizer, BertForMaskedLM import torch tokenizer BertTokenizer.from_pretrained(./data) model BertForMaskedLM.from_pretrained(./data) model.eval() text 今天天气真[MASK]好 input_ids tokenizer.encode(text, return_tensorspt) mask_pos torch.where(input_ids tokenizer.mask_token_id)[1] with torch.no_grad(): outputs model(input_ids) predictions outputs[0] # 获取 mask 位置的前 10 个候选 probs torch.softmax(predictions[0, mask_pos, :], dim-1) top_k torch.topk(probs, 10) for score, idx in zip(top_k.values[0], top_k.indices[0]): print(tokenizer.decode([idx]), score.item())这段代码的关键在于mask_pos的定位和torch.topk的候选提取。tokenizer.mask_token_id是[MASK]对应的 IDoutputs[0]是模型输出的 logits经过 softmax 后得到概率分布。你可以调整top_k的数值来观察更多候选也可以把text换成你自己的测试句子。3. 规则纠错与混淆集的工程化落地3.1 规则纠错模块的设计思路rule_corrector.py是这个项目里容易被忽视但实际很重要的模块。BERT 虽然强大但它有两个问题一是对垂直领域的专有名词容易误判二是推理速度慢不适合大规模实时纠错。规则纠错模块的作用是在 BERT 之前做一层「粗筛」把明显不是错误的字排除掉减少 BERT 的调用次数。规则纠错的核心逻辑是基于混淆集。项目data目录下有这些文件文件名用途same_pinyin.txt同音字混淆集same_stroke.txt形近字混淆集custom_confusion.txt自定义混淆集word_freq.txt词频表custom_word_freq.txt自定义词频表common_char_set.txt常用字集合stopwords.txt停用词表place_name.txt地名列表person_name.txt人名列表same_pinyin.txt里存的是同音字对比如「的/得/地」「在/再」same_stroke.txt存的是形近字对比如「己/已/巳」。rule_corrector.py会遍历句子中的每个字如果发现某个字在混淆集中就结合词频和上下文判断是否需要替换。# rule_corrector.py 核心逻辑示意 class RuleCorrector: def __init__(self, confusion_path, freq_path): self.confusion self.load_confusion(confusion_path) self.word_freq self.load_word_freq(freq_path) def load_confusion(self, path): confusion {} with open(path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) 2: confusion[parts[0]] parts[1:] return confusion def correct(self, sentence): chars list(sentence) for i, char in enumerate(chars): if char in self.confusion: candidates self.confusion[char] # 结合词频选择最优候选 best self.select_by_freq(chars, i, candidates) if best ! char: chars[i] best return .join(chars)load_confusion读取混淆集文件每行第一个字是原字后面是候选字。select_by_freq会结合前后文组成词语查词频表选择词频最高的组合。这个逻辑看起来简单但实际效果取决于混淆集的质量和词频表的覆盖度。3.2 混淆集的构建与扩展项目自带的混淆集是通用领域的如果你要做垂直领域纠错比如医疗、法律、金融必须自己扩展混淆集。常见做法是从领域语料中统计高频错别字对然后手动筛选。比如医疗领域经常把「症状」写成「证状」把「剂量」写成「计量」这些都需要加到custom_confusion.txt里。# custom_confusion.txt 格式示例 症 证 剂 计 瓣 辨每行两个字符用空格或制表符分隔。第一个是正确的字第二个是常见的错误写法。rule_corrector.py在加载时会自动读取这个文件并在纠错时优先使用自定义规则。另外word_freq.txt和custom_word_freq.txt的格式是「词语 频次」频次越高表示这个词越常见。你可以用领域语料跑一遍分词和词频统计生成自己的词频表。我一般会用jieba分词后统计import jieba from collections import Counter def build_word_freq(corpus_path, output_path): counter Counter() with open(corpus_path, r, encodingutf-8) as f: for line in f: words jieba.lcut(line.strip()) counter.update(words) with open(output_path, w, encodingutf-8) as f: for word, freq in counter.most_common(): if freq 1 and len(word) 1: f.write(f{word} {freq}\n)这段代码会统计语料中所有词语的出现频次过滤掉单字和只出现一次的词输出到custom_word_freq.txt。注意词频表的规模不宜过大否则加载和查询都会变慢一般控制在 10 万条以内。3.3 BERT 与规则纠错的融合策略单独用 BERT 或单独用规则纠错效果都不够好。这个项目的价值在于它提供了一套融合策略先用规则纠错做粗筛把高置信度的错误改掉然后把剩余疑似错误交给 BERT 做 mask 预测最后用混淆集和词频对 BERT 的候选做重排序。# bert_corrector.py 中的融合逻辑示意 def correct_pipeline(sentence, rule_corrector, bert_corrector): # 第一步规则纠错 rule_result rule_corrector.correct(sentence) # 第二步检测疑似错误位置 suspicious_positions detect_suspicious(rule_result) # 第三步BERT mask 预测 if suspicious_positions: bert_candidates bert_corrector.predict(rule_result, suspicious_positions) # 第四步结合混淆集和词频重排序 final_result rerank(rule_result, bert_candidates, suspicious_positions) else: final_result rule_result return final_resultdetect_suspicious的检测逻辑可以基于多种信号字是否在常用字集合中、前后字组成的词是否在词频表中、是否在混淆集中等。rerank则是把 BERT 的预测概率、混淆集匹配度和词频得分加权求和选出综合得分最高的候选。这套融合策略的好处是规则纠错保证了高精度BERT 保证了召回率重排序则平衡了两者。实际使用中你可以通过调整权重来控制纠错的激进程度——权重偏向规则纠错更保守权重偏向 BERT纠错更激进。4. 模型微调让 BERT 适配你的垂直领域4.1 为什么通用模型不够用项目使用说明里有一句话很关键「通用数据下训练的结果并不适用于垂直领域的纠错需要重新训练。」这是因为bert-base-chinese是在通用语料上预训练的它的 MLM 预测偏向新闻、百科等通用文本。如果你用它来纠错医疗文本它可能会把「房颤」改成「房战」把「阿司匹林」改成「阿司匹木」。解决方法是做领域自适应微调Domain-Adaptive Pretraining。具体来说就是用你的领域语料继续训练 BERT 的 MLM 任务让模型学习领域内的词汇分布和语言模式。4.2 微调脚本的参数配置项目提供了run_lm_finetuning.py来做微调。使用说明里给了两种调用方式我推荐用第二种参数更清晰python -m run_lm_finetuning \ --bert_model bert-base-chinese \ --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指定预训练模型名称或路径。如果你已经把模型下载到本地可以改成./data。--do_lower_case是否忽略大小写。中文场景下这个参数影响不大但建议保留。--train_file训练语料路径。每行一句话UTF-8 编码。--output_dir微调后模型的保存路径。--num_train_epochs训练轮数。5 轮是一个比较安全的起点语料少可以减到 3语料多可以加到 10。--learning_rate学习率。3e-5 是 BERT 微调的经典值太大容易震荡太小收敛慢。--train_batch_size批次大小。16 是 8GB 显存下的安全值显存不够就降到 8。--max_seq_length最大序列长度。128 覆盖大多数短句长文本需要截断或分段。如果你有 GPU可以在命令前加export CUDA_VISIBLE_DEVICES0指定显卡。训练过程中会输出 loss 曲线如果 loss 不下降检查学习率是否太大如果 loss 震荡严重检查批次大小是否太小。4.3 训练数据的准备与格式要求run_lm_finetuning.py对训练数据的格式要求很简单纯文本文件每行一句话不需要标注。这是因为 MLM 任务是自监督的——模型自己 mask 掉一些字然后尝试还原。# sample_text.txt 格式示例 今天天气真好 我们一起去公园散步 这家餐厅的菜很好吃但「不需要标注」不代表「随便什么数据都行」。训练数据的质量直接决定微调效果。我一般会做这几件事第一清洗数据。去掉空行、超短句少于 5 个字、包含特殊符号的句子。第二控制领域相关性。如果你做医疗纠错训练数据应该以医疗文本为主可以混入 10% 的通用文本防止过拟合。第三数据量建议在 10 万句以上太少的话微调效果不明显。# 数据清洗脚本示例 import re def clean_corpus(input_path, output_path): with open(input_path, r, encodingutf-8) as f_in, \ open(output_path, w, encodingutf-8) as f_out: for line in f_in: line line.strip() # 过滤空行和超短句 if len(line) 5: continue # 过滤包含特殊符号的句子 if re.search(r[^\u4e00-\u9fa5a-zA-Z0-9。、], line): continue f_out.write(line \n)这段代码会过滤掉长度小于 5 的句子和包含非中文、非英文、非标点符号的句子。\u4e00-\u9fa5是中文汉字的 Unicode 范围你可以根据需要调整。4.4 微调后的模型替换与验证微调完成后output_dir下会生成新的pytorch_model.bin和config.json。把这两个文件替换到data目录下重新运行bert_corrector.py就能看到领域适配后的纠错效果。验证方法很简单准备一批领域内的测试句子故意引入一些典型错误然后对比微调前后的纠错准确率。我一般会统计三个指标准确率改对的占所有错误的比例、召回率改出来的错误占所有错误的比例、误改率把正确的改错的比例。微调的目标是提高准确率和召回率同时控制误改率不上升。5. 避坑指南那些我踩过的坑5.1 模型加载报错「Unable to load weights」现象运行bert_corrector.py时抛出OSError: Unable to load weights from pytorch_model.bin。原因最常见的原因是模型文件不完整或格式不匹配。如果你从 HuggingFace 下载的是 TensorFlow 格式的权重tf_model.h5直接改名为pytorch_model.bin是没用的。另外下载过程中断导致文件损坏也很常见。解决确认下载的是 PyTorch 格式的权重。可以用torch.load(pytorch_model.bin)测试文件是否能正常加载。如果报错重新下载。如果用的是bert-base-chinese建议直接用transformers的from_pretrained自动下载避免手动放置出错。5.2 微调时 loss 不下降现象run_lm_finetuning.py跑了几百步loss 一直在 10 左右徘徊不下降。原因大概率是学习率设置有问题。BERT 微调的学习率通常在 2e-5 到 5e-5 之间如果你用了 1e-3 这种大学习率模型会震荡不收敛。另一个可能是训练数据太少模型没学到东西。解决把--learning_rate改成 3e-5 或 2e-5--num_train_epochs加到 10。如果还是不行检查训练数据是否正常加载——可以在run_lm_finetuning.py里加一行打印看看train_dataset的长度是不是 0。5.3 纠错结果过度修改现象原本正确的句子被改得面目全非比如「我今天很开心」被改成「我今天很开新」。原因BERT 的 mask 预测是概率性的如果解码策略太激进就会把一些低频但正确的字改成高频但错误的字。另外混淆集如果包含太多常见字对也会导致误改。解决调整重排序的权重提高规则纠错的比重。具体来说在rerank函数里把词频得分的权重调高把 BERT 概率的权重调低。另外可以在common_char_set.txt里加入领域内的高频正确字让规则纠错跳过这些字。5.4 中文编码问题导致乱码现象读取语料或混淆集时出现UnicodeDecodeError或者输出结果里出现乱码。原因Windows 系统默认编码是 GBK而项目文件大多是 UTF-8 编码。如果打开文件时没指定encodingutf-8就会报错。解决所有文件读写操作都显式指定encodingutf-8。如果语料本身是 GBK 编码先用iconv或 Python 脚本转成 UTF-8iconv -f GBK -t UTF-8 input.txt output.txt5.5 GPU 显存不足现象训练或推理时抛出CUDA out of memory。原因batch_size或max_seq_length设置太大超出了显存容量。解决把--train_batch_size从 16 降到 8 或 4把--max_seq_length从 128 降到 64。如果还是不够可以用梯度累积来模拟大批次--gradient_accumulation_steps 4这样实际批次是train_batch_size * gradient_accumulation_steps但显存占用只按train_batch_size算。6. 进阶技巧用 KenLM 做困惑度过滤与候选重排项目data目录下有一个kenlm文件夹里面是people_chars_lm.klm。这是一个用 KenLM 训练的中文语言模型可以用来计算句子的困惑度Perplexity。困惑度越低句子越「通顺」。这个信号在纠错里非常有用——当 BERT 给出多个候选时你可以用 KenLM 计算每个候选句子的困惑度选困惑度最低的那个。import kenlm model kenlm.Model(./data/kenlm/people_chars_lm.klm) def perplexity(sentence): return model.perplexity(sentence) # 候选重排示例 candidates [我今天很开心, 我今天很开新, 我今天很开兴] for cand in candidates: print(cand, perplexity(cand))kenlm.Model加载.klm文件后perplexity方法返回句子的困惑度。数值越低越好。在实际使用中你可以把 KenLM 得分作为重排序的一个特征和 BERT 概率、词频得分加权求和。我一般会这样设置权重BERT 概率占 0.5KenLM 困惑度占 0.3词频得分占 0.2。这个比例不是固定的你需要根据领域数据做调整。如果发现纠错结果太保守提高 BERT 权重如果发现误改太多提高 KenLM 和词频权重。还有一个技巧是用 KenLM 做前置过滤在 BERT 预测之前先用 KenLM 算一下原句的困惑度。如果困惑度很低说明句子本身就很通顺大概率没有错误直接跳过 BERT 推理。这样可以大幅减少不必要的计算提升整体速度。def should_check(sentence, threshold50): ppl perplexity(sentence) return ppl thresholdthreshold的取值需要根据你的语料统计来定。我一般会在一批正确句子上跑一遍取困惑度的 95 分位数作为阈值。这样只有 5% 的正确句子会被误判为「需要检查」但大部分错误句子都能被覆盖到。从那以后我每次拿到新的纠错项目都会先跑一遍困惑度分布确认阈值再往下做。希望帮到你。本文还有配套的精品资源点击获取
返回列表