☰
文本纠错模型选型与工程实践:KenLM、MacBERT、T5、ChatGLM3、LLaMA一次配齐
2026/10/7 17:01:59 网站建设 项目流程

简介:中文文本纠错是NLP落地中的高频需求,这一资源包收集了开源项目pycorrector的完整代码与配置,面向算法工程师、NLP学习者和需要快速集成纠错能力的开发人员。项目基于Python 3.8实现,涵盖KenLM、ConvSeq2Seq、BERT、MacBERT、ELECTRA、ERNIE、Transformer以及T5、ChatGLM3、LLaMA等多种模型的调用与评估,支持音似、形似和语法错误纠正,可开箱即用。资源共192个文件,以Python源码为主,辅以txt说明、Markdown文档、YAML配置、图片示例和测试数据,压缩包约10.95MB,其中py文件对应模型训练与推理,md与txt负责使用说明,jpg/png展示结果截图,yml便于调整环境参数,结构清晰。已有445人学习下载,适合希望对比不同模型纠错效果、在SigHAN数据集上复现评估结果的读者。解压后可直接查看各模型的实现脚本、运行配置、结果截图和说明文档,Dockerfile与元信息文件也一应俱全,能够帮助快速搭建中文纠错实验环境或集成到自身业务中。

1. 文本纠错不是单一模型:这个资源把KenLM、MacBERT、T5、ChatGLM3、LLaMA一次配齐

文本纠错在真实项目里不是什么高深算法题,而是每天都要面对的脏数据清洗:OCR出来的文档有错字、客服会话里有同音字、输入法上屏有谐音。做这类需求时最头疼的不是找不到模型,而是各说各话——统计语言模型、预训练模型、大语言模型各有各的适用场景,却很少有人把它们放到同一个工程框架里做对比和组合。这个资源把KenLM、MacBERT、T5、ChatGLM3、LLaMA五条技术路线整理成可直接跑的工程包,覆盖从字符级替换到句子级重写再到对话式改写的完整梯度,模型权重、推理脚本、加噪工具和评测代码都配齐了。适合想把纠错能力快速接进自己业务的NLP工程师,也适合拿来做模型选型对比的算法同学。

2. KenLM与MacBERT:统计噪声过滤与掩码预测的互补组合

2.1 先用KenLM给句子排困惑度:训练、二值化与调用

KenLM是一个n-gram语言模型工具,它本身不负责找出错字,而是负责回答“哪句话更像人话”。纠错场景里它最常见的用途是给候选句做排序:比如MacBERT或别的手段生成了三个候选纠错句,KenLM分别计算困惑度,取困惑度最低的作为最终结果。优点是纯C++实现,训练速度快,CPU就能跑;缺点是严重依赖语料质量和分词粒度。

训练一般分两步。先准备一份清洗过的txt语料,每行一句,中文用空格分词或直接按字切分。按字切分可以回避分词器的词表边界问题,代价是n-gram上下文变短,一般我会在按字切分时把阶数调到5。命令是这样:

# 训练5元字级别语言模型,输出arpa格式 lmplz -o 5 --text cleaned_corpus.txt --arpa model.arpa # 转成二进制的klm格式,推理速度能快一个数量级 build_binary model.arpa model.klm

逻辑说明:-o 5表示训练5-gram模型,阶数越高对语序的约束越强,但数据稀疏问题也越明显;--arpa输出标准的arpa格式,方便后面做模型插值或者剪枝;build_binary把arpa文本格式转成内存映射的二进制,加载后不必把整个模型读进内存,对超大规模n-gram尤其有用。

调用端我用一个最简单的打分函数来演示:

import kenlm model = kenlm.Model("model.klm") def sentence_score(model, text): # text按空格分词,因为训练时是按空格拼接后的格式 return model.score(text, bos=True, eos=True) / max(len(text.split()), 1) candidates = ["今天天气不错", "今天天气不好", "今天替气不错"] scored = sorted(candidates, key=lambda x: sentence_score(model, x), reverse=True) print(scored)

参数说明:bos和eos表示给句子补上开头和结尾标记,相当于给短句子的概率做一个归一;score返回的是log10概率,所以之后要除以token数量,否则长句子天然得分低。reverse=True让得分最高的候选排最前。实际项目里我不会直接拿这个结果当最终纠错输出,而是把它作为后置过滤器:如果候选句和原句的困惑度差值小于某个阈值(经过标定,常见是0.5~1.0),就认为这个改动不必要,保持原样。这能有效减少过度纠错。

2.2 MacBERT软掩码纠错:混淆集构造与预测阈值

MacBERT是BERT系模型,它的纠错逻辑和KenLM完全不同——不是给句子打分,而是把输入句子的每个位置看成潜在的错误位置,用掩码预测的方式输出该位置最可能的字。它跟BERT的区别在于预训练阶段用了“软掩码”策略:不是全部用[MASK]标记,而是用相似词以一定概率去替换被mask的词。这个特性让它在纠错任务上比原生BERT更稳,因为微调后它更习惯从相似的上下文里挑字,而不是纯靠[MASK]的强特征。

工程上落地MacBERT纠错需要三样东西:预训练权重、混淆集、预测阈值。混淆集是核心,它决定了模型能识别哪些错字。常见做法是从同音字表、形近字表、常用错词库整理出映射,比如“的/地/得”、“在/再”、“做/作”这类高频易错对。混淆集既用于构造训练数据,也用于推理时限定候选范围。一个可用的推理脚本长这样:

from transformers import AutoTokenizer, AutoModelForMaskedLM import torch model_name = "hfl/chinese-macbert-base" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForMaskedLM.from_pretrained(model_name) confusion = {"在": ["再"], "做": ["作"], "的": ["地", "得"]} def correct_macbert(sentence, prob_threshold=0.6): tokens = list(sentence) result = list(sentence) for i, char in enumerate(tokens): masked = "".join(tokens[:i]) + "[MASK]" + "".join(tokens[i+1:]) inputs = tokenizer(masked, return_tensors="pt") with torch.no_grad(): logits = model(**inputs).logits mask_idx = inputs["input_ids"][0].tolist().index(tokenizer.mask_token_id) probs = torch.softmax(logits[0, mask_idx], dim=-1) candidates = confusion.get(char, []) if not candidates: continue best_prob = 0.0 best_char = char for cand in candidates: cand_id = tokenizer.convert_tokens_to_ids(cand) prob = probs[cand_id].item() if prob > best_prob: best_prob, best_char = prob, cand if best_prob >= prob_threshold: result[i] = best_char return "".join(result) text = "今天在去公司的路上,我在次遇到了他" print(correct_macbert(text))

逻辑说明:这段代码对句子逐字符做掩码,拿每个掩码位置的预测概率去查混淆集。注意这里只用混淆集里的候选字参与比较,而不是直接取top1——因为top1很可能是个形近但语义完全无关的字,比如“工”预测成“公”。prob_threshold是改动阈值,模型对某候选字的置信度达不到这个值就保持原文,我一般从0.6起步,数据越脏阈值越低。

参数说明:hfl/chinese-macbert-base是常用的中文MacBERT权重,显存占用不到2G,CPU也能推理,但逐字符跑会比较慢。实际项目里我会先通过规则(比如错词词典、拼音编辑距离)筛出少数可疑位置,只对可疑位置做掩码预测,而不对整句逐字跑,能把推理时间缩短一个数量级。

2.3 传统路线的参数边界:什么场景该用这一组

KenLM和MacBERT组合起来,覆盖的是“错字级别”的纠错需求,比如OCR识别结果、用户输入法提交的短文本。它对以下几种错误特别有效:同音字替换(“在”写成“再”)、形近字替换(“未”写成“末”)、助词误用(“的地得”不分)。但它的能力边界也很清晰——处理不了漏字、多字、语序颠倒这类的结构性问题,因为掩码预测本质上是“单点替换”,它没有能力重新组织整个句子。

参数边界我按经验给一个参考表:

环节推荐参数说明
KenLM阶数按字切分用5阶数越高越准,但语料小于100万句时容易稀疏
混淆集大小3000~10000组太小漏错,太大模型学成“乱改”
改动阈值0.6~0.7线上要求严谨就调高,数据脏就调低
候选句困惑度差0.5~1.0小于该差值的改动会被回滚

在这个资源包里,KenLM和MacBERT的权重、训练语料、混淆集是分开的,可以单独替换,不用整包重训练。这也是我喜欢这套结构的原因——生产环境里往往是“错误类型变了,换混淆集就行”,而不是把整个模型重新训一遍。

3. T5生成式纠错:从加噪到改写,训练数据与解码参数全流程

3.1 为什么生成式比判别式更适合中文口语错误

MacBERT一次只能改一个位置的字,但真实文本里的错误往往是连片的:语音输入里“我们一起去吃饭”可能被识别成“我们一起气吃法”,这里既有同音字“气/去”,又有声调错误导致的“法/饭”。遇到这种情况,逐个掩码就束手无策了,因为模型需要在宏观上知道整句话在说什么,才能决定每个字怎么改。

T5走的是生成路线。输入一个带错误的句子,输出一个改好的句子,模型自己决定哪里动、哪里不动、怎么调整语序。它把纠错从“分类”变成“翻译”,所以训练语料是成对的——错误句和正确句。在这个资源包里,T5相关的内容主要包括三块:数据加噪脚本、训练配置、推理脚本。我下面按这个顺序讲。

3.2 构造(错误句,正确句)数据集:加噪器设计

没有现成的成对错误数据,最常见的做法是用正确语料反向加噪。把干净句子扰动出错误,得到(错误句,正确句)对。加噪器设计直接决定模型习得的行为边界——加噪太狠,模型学会把对的也改掉;加噪太轻,模型学了个寂寞。常用策略是:每个句子按15%~20%比例随机选择位置,注入三种噪声:混淆集替换、随机删除单个字、相邻字交换。一个能用的加噪器如下:

import random def inject_noise(sentence, confusion, max_errors=3): chars = list(sentence) noisy = chars[:] error_count = 0 positions = random.sample(range(len(chars)), min(len(chars), max_errors)) for pos in positions: r = random.random() if r < 0.7 and chars[pos] in confusion: # 70%概率走混淆集替换,学同音/形近错字 noisy[pos] = random.choice(confusion[chars[pos]]) elif r < 0.85 and len(noisy) > 1: # 15%概率删一个字,模拟漏字 del noisy[pos] elif r < 1.0 and pos + 1 < len(noisy): # 10%概率交换相邻两个字,模拟手滑 noisy[pos], noisy[pos + 1] = noisy[pos + 1], noisy[pos] error_count += 1 if error_count >= max_errors: break return "".join(noisy), sentence # 用法:读入干净语料,逐行生成训练对,以\t分隔 # with open("clean.txt") as f: # for line in f: # noisy, correct = inject_noise(line.strip(), confusion_dict) # writer.write(noisy + "\t" + correct + "\n")

逻辑说明:真正起作用的是max_errors和随机数分支的占比。70%的噪声走混淆集替换是为了保证模型学到的是“改错字”,而不是“随意改写”;删除和交换各占15%和10%,是为了让模型具备处理结构性错误的能力。随机采样位置时用random.sample,避免全部错误挤在一段里。

参数说明:max_errors=3对短句是合理值,长句(超过50字)建议按长度缩放,比如max(1, len(sentence) // 10)。当你想控制整体噪声密度时,可以改成按泊松分布采样错误数,效果差别不大,但max_errors必须显式设置,否则极端情况下模型会见到整句被删光的训练样本,输出会变得不可控。

3.3 训练与推理的关键参数

训练部分资源包应该已经提供了完整的train脚本,我这里重点说几个不调会翻车的参数。纠错是短文本到短文本的任务,max_seq_length一般设64或128足够,再长只会浪费显存。训练时用标签的交叉熵,label_pad_token_id设为-100来跳过padding。推理时T5的解码参数比训练参数更值得关注:

from transformers import AutoTokenizer, T5ForConditionalGeneration model = T5ForConditionalGeneration.from_pretrained("./t5_finetuned") tokenizer = AutoTokenizer.from_pretrained("./t5_finetuned") def correct_t5(sentence): inputs = tokenizer("纠错: " + sentence, return_tensors="pt", max_length=128, truncation=True) outputs = model.generate( **inputs, num_beams=5, max_length=len(sentence) + 8, min_length=max(1, len(sentence) // 2), no_repeat_ngram_size=3, repetition_penalty=1.2, early_stopping=True ) return tokenizer.decode(outputs[0], skip_special_tokens=True)

逻辑说明:num_beams=5是中文纠错里个人经验比较稳的值,beam太大会让模型倾向于生成和训练集里最常见句子接近的输出,beam太小又容易丢正确的候选。max_length=len(sentence)+8限制了改写长度,纠错的输出不应该比输入长出太多,多出来的部分往往是模型在自言自语补话。min_length给了一个下限,避免模型输出“好的”两个字来敷衍。

参数说明:no_repeat_ngram_size=3是防重复的关键。中文纠错里模型特别喜欢重复“的的的”或“好好好”,这个参数禁止3-gram重复出现;repetition_penalty=1.2进一步惩罚重复token的概率,两个配合才能压住复读机行为。early_stopping=True让beam search在所有beam都到EOS时提前终止,减少在长句上的推理时间。

3.4 评测指标:别让Precision和Recall打架

T5这种生成式模型最容易翻车的评测方式,是只看“句子对就是错”。实际线上场景里,一个句子可能原本就对,模型画蛇添足硬改成另一个字,句子级的“改对”指标是看不出来的。业界比较通用的做法是错误级评测:统计模型真正改对了多少个错字,又冤枉了多少个正确字。

实现上就是把纠错前后的字符串按字对齐,统计四类结果:TP(确实错且改对)、FP(本来对但被改错)、FN(确实错但没改)。精准率Precision = TP/(TP+FP),召回率Recall = TP/(TP+FN),F1取调和平均。这个脚本在这个资源的评测目录里应该已经有了,没有的话也可以用jiwer或者简单的Levenshtein对齐来实现。我个人的经验是:T5在SIGHAN这种公开评测集上句子级准确率可以做到80%以上,但在真实脏数据上F1经常掉到60%以下,核心原因是训练用的加噪分布和真实错误分布差太远,所以微调完一定要用真实数据做一轮盲测,盲测结果才是能向上汇报的数字。

4. 把ChatGLM3和LLaMA当纠错引擎用:Prompt模板与量化加载

4.1 对话式纠错的适用边界

把ChatGLM3和LLaMA塞进纠错场景,不是为了炫技,而是为了解决一个实际痛点:传统纠错模型没有“常识”。比如“他昨天去故宫玩了一下午,今天累得不行”里的“故宫”被OCR识别成“故官”,MacBERT大概率不敢改,因为“故官”也是一个合法词。但ChatGLM3知道故宫是一个景点,结合上下文常识就能判断这是错别字。

这决定了对话式纠错的适用边界:适合处理那些需要世界知识、语境理解才能判断的错误,比如实体名词、专有名词、跨句指代错误。反过来,它不适合处理高频简单的同音字替换——大模型在这些任务上推理速度慢,而且可能因为指令理解偏差而发挥不稳定。所以这个资源把LLM相关的内容定位成“会话式纠错引擎”,而不是“批处理脚本”,这个定位是对的。

4.2 Prompt模板与解码约束

大模型做纠错的输出质量,一半取决于prompt,一半取决于解码参数。一个常见的翻车现场是:模型输出“这句话的错别字是……”,而不是直接给纠错后的文本。问题就出在prompt里没有限定输出格式。我一般用下面这个模板:

prompt = """你是文本校对助手。用户会给你一句中文文本,可能包含错别字。 请你只输出纠错后的完整文本,不要输出解释,不要输出多余内容。 用户输入:{sentence} 纠错结果:""" def correct_llm(sentence, model, tokenizer): inputs = tokenizer(prompt.format(sentence=sentence), return_tensors="pt") outputs = model.generate( **inputs, do_sample=False, temperature=0.1, max_new_tokens=len(sentence) + 20, num_beams=1, ) return tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True).strip()

逻辑说明:模板里“只输出纠错后的完整文本”是强制约束,把模型的解释欲压住。do_sample=False配合temperature=0.1意味着推理走贪心解码,每次输出都稳定,不会因为采样波动出现“这次改了、下次没改”的玄学现象。num_beams=1在生成式纠错里够用,LLM的上下文理解能力已经足够把候选字挑出来,不需要靠beam search去枚举。

参数说明:max_new_tokens设置成原句长度加20,即最多允许模型在句尾多写20个字。如果模型实在没法确定怎么改,它倾向于在末尾补一句废话,这个长度限制能兜底。如果你在用ChatGLM3的官方实现,注意它内部有自带的chat模板,不要手动再拼一版;LLaMA家族则必须走tokenizer.apply_chat_template,否则对话模板里的特殊token缺失,生成结果会乱。

4.3 量化推理与硬件消耗

LLaMA这类开源底座在纠错场景里最常见的部署问题是显存。7B参数fp16推理大约需要14G显存,很多线上机器跑不动。这个资源里推荐的方案是8bit量化加载,显存占用直接砍半,推理质量在纠错任务上几乎无损。一个可用的加载方式是:

from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig import torch quant_config = BitsAndBytesConfig( load_in_8bit=True, llm_int8_threshold=6.0 ) tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-chat-hf") model = AutoModelForCausalLM.from_pretrained( "meta-llama/Llama-2-7b-chat-hf", quantization_config=quant_config, device_map="auto", torch_dtype=torch.float16, )

逻辑说明:load_in_8bit=True把线性层权重量化成int8,理论上保留了大部分表达能力;llm_int8_threshold=6.0控制哪些层走8bit、哪些层保留fp16,阈值越高,保留fp16的层越少。device_map="auto"让transformers自动分配层到GPU或CPU,显存不足时会把部分层放内存,速度变慢但至少能跑起来。

参数说明:量化加载后推理速度比fp16慢20%~30%,在纠错这种短输入任务上基本无感。如果你用的是ChatGLM3-6B,它同样支持load_in_8bit,显存需求在8G以内就能跑完整推理。这类大模型在纠错场景的瓶颈是吞吐,短句批量推理的吞吐大概只有几到十几token/s,所以只适合做小流量实时接口或离线批量任务,不适合接日均百万级的大流量。

4.4 五类模型的选型对比

把这五个模型放一块,很多人的第一反应是选最强的,但在工程里应该选“错误类型能覆盖、延迟能接受、成本能扛住”的。我在实际项目里的对比结论如下:

模型推理延迟硬件需求能处理的错误典型短板
KenLM微秒~毫秒CPU即可候选排序、通顺度过滤本身无纠错能力
MacBERT毫秒~十毫秒CPU可跑,GPU更稳单字错、同音字、形近字不能处理结构性错误
T5十毫秒~百毫秒4G显存以上多字错、漏字、语序错误需要大量成对训练数据
ChatGLM3百毫秒~秒级8G显存以上实体知识、语境错误延迟高、输出不稳定
LLaMA秒级8G~14G显存私有化部署、可控性需要调prompt和量化

这个表格不是让你选一个,而是按错误类型做路由的依据。真实项目里我不会只部署其中一个,而是两个模型叠加一道过滤:MacBERT快速扫一遍字符级错误,T5处理它搞不定的复杂错误,LLM只在高置信度的实体纠错场景里上。路由框架我在最后一章给完整代码。

5. 避坑指南:数据泄露、混淆集噪音与解码陷阱

5.1 混淆集噪音注入失控

现象:MacBERT或T5微调后,在测试集上纠错率挺高,但随便拿一句没毛病的正常句子进去,输出被改得乱七八糟,比如“我今天很开心”被改成“我今天很开星”。

原因:加噪时错误注入比例太高。一个20字的句子如果被强制注入5个错误,模型在训练时看到的就是“一句错得离谱的话”,它学到的映射是“不管上下文,只要看到相似字就换”,而不是“在确信错误时才改”。

解决:把每句错误数控制在1~3个,且严格限制混淆集替换占比。我给MacBERT加噪时会把替换概率压回70%以内,剩余30%留给删除和交换。训练完第一步不是看准确率,而是拿完全正确的语料去跑一遍,统计误改率,误改率超过5%说明加噪器太凶,直接调小max_errors重训。

5.2 训练集和验证集数据同源

现象:模型在验证集上的F1刷到85%,拿去处理线上真实客服数据,掉到55%,而且怎么调参都回不去。

原因:数据划分出问题了。很多人按文档切分训练集和验证集,同一个文档里的句子大量重复,或者同一批新闻稿里“今天天气不错”这种句式反复出现,验证集里全是训练集的变形,模型对着类似的句子自然表现出色,一遇到真实分布就现原形。

解决:按句子粒度做hash去重后再划分,同时保证验证集来自完全不同的时间窗口或来源。如果资源包自带的数据集没有这种隔离,建议直接丢掉自带的dev集,单独从线上日志里抽500句人工标注做盲测,那才是真实水平。

5.3 T5生成重复字符

现象:T5推理结果里频繁出现“他他他”“好的好的好的”这类复读,而且句子的总长度明显超过输入。

原因:生成时没做重复惩罚。纠错任务的输出长度通常和输入差不多,训练数据里很少出现连续重复3次以上的n-gram,但beam search在概率空间里探索时容易陷入重复陷阱,尤其是当训练语料里高频词聚集时。

解决:推理时显式开启no_repeat_ngram_size=3,并加repetition_penalty=1.2。如果还压不住,就把num_beams降下来,beam越大越容易堆叠重复短语。另外检查训练标签里是否有重复字符,有些加噪器会把“你好”重复生成“你好你好”,这种坏样本一旦混进训练集,模型会主动学坏。

5.4 KenLM的未登录词惩罚

现象:候选句明明改对了,但KenLM打分反而比原句低,导致排序结果把正确纠错排到最后。

原因:KenLM是n-gram统计模型,对训练语料里没出现过的词有天然敌意。比如语料里没有“元宇宙”这个词,模型看到一个候选句里含“元宇宙”,会给出极大的惩罚,而原句里的错误字“原宇宙”因为每个字都在字典里,反而得分更高。

解决:两招。第一招,给KenLM喂一份专业词表,把这些词在训练时显式打平成空格拼接,不做切分;第二招,计算候选句和原句的困惑度差值时设置下限,差值低于0.5就认为两者通顺度相当,忽略KenLM的排序建议,交给上游模型的决定。

5.5 大模型输出解释性废话

现象:LLaMA纠错输出“这句话的错别字是‘在’,应改为‘再’,纠错后文本为:……”而不是只给文本。

原因:prompt里没有约束输出格式,模型默认走“解释+解决方案”的助手风格。另一个常见原因是对话模板没走apply_chat_template,导致模型没进入system指令所规定的角色。

解决:prompt里强制加“只输出纠错后的完整文本,不要输出解释”;推理时用do_sample=False消除随机性。如果模型还废话,就在解析层做后处理:用正则把“纠错结果:”之后的部分截出来,或者直接按“输出第一个完整句子”截断。这不是好办法,但作为兜底值得留着。

6. 进阶落地:按错误类型路由模型,用改动距离兜底

6.1 置信度路由框架

五个模型都部署好后,不是让它们各跑各的,而是串成一条管线。我现在的做法是按置信度分级路由:MacBERT输出的改动置信度最高,由它拍板字符级修改;置信度不足时,交给T5做句子级改写;遇到实体名词或长句,再上ChatGLM3补一层。路由逻辑可以落成下面这个骨架:

def route_correct(sentence): mac_result = correct_macbert(sentence) if diff_chars(sentence, mac_result) == 0: return sentence # 无改动,直接返回 confidence = mac_confidences(sentence, mac_result) if confidence >= 0.8: return mac_result elif len(sentence) > 30 or has_entity_error(sentence): return correct_llm(sentence) else: return correct_t5(sentence)

逻辑说明:diff_chars统计逐字比较的改动位置数量;如果MacBERT一个位置都没改,说明它认为句子没有字符级错误,不往下走。confidence取MacBERT对每个改动位置候选字的平均预测概率。实体错误检测是简化的:句子中出现混淆集里没有覆盖的双字词,且该词在词典中不存在,就判定为疑似实体错误,交给LLM做常识判断。

参数说明:0.8这个阈值不是拍脑袋定死的。线上反馈误改率高就往上拉到0.9,漏改多就降到0.7,每次调整都要对着标注集做一次A/B,不要凭体感改。这个路由框架的价值在于:字符级错误用最便宜的模型处理,结构性错误才用贵模型,整体推理成本可以压低70%。

6.2 改动距离兜底

路由之后还有最后一道保险,就是KenLM通顺度校验。我会在最终输出前,同时计算原句和结果句的困惑度,如果结果句的困惑度反而更高,且改动字符数小于句子长度的10%,就直接回滚到原句:

if kenlm_score(result) < kenlm_score(sentence) - 0.5: return result else: return sentence

逻辑说明:这个规则的本质是假设检验——如果一次改动没有让句子变得更通顺,那这次改动大概率是画蛇添足。0.5的阈值是我在客服语料上标定出来的,它过滤掉了大部分“改错不改对”的无效修改。

这个框架搭完之后,我这个项目的实施教训也就一以贯之了:多模型纠错最忌一步到位,每个模型负责自己擅长的错误类型,再加一层成本兜底。从那以后我每次上线纠错服务都会强制走一遍“原句与结果句双写评测”,确认没有把正确句子改坏,才敢放量。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询