简介:本资源是一套基于机器学习的中文错别字智能检索与自动纠正系统完整实现,面向人工智能、计算机科学及相关专业(如通信工程、自动化、电子信息等)的在校学生、教师及初级开发者,解决中文文本中常见形近、音近错别字的识别与修正难题,适用于课程设计、毕业设计、项目立项演示及算法实践进阶。压缩包共12个文件,含3个核心Python脚本(主窗口、接口、检索逻辑)、6个文本资源(词典、拼音映射、停用词、分词配置等)、1个README说明文档、1个MP4项目成果展示视频及1个.gitignore,整体7.61MB,结构清晰、模块职责明确,便于理解算法流程与工程集成。已有54人学习下载,提供经导师评审认可(95分高分)、全功能测试通过的可运行代码,配套详细文档与实操演示,支持直接复用或二次开发拓展纠错场景。
1. 中文错别字自动纠正不是拼写检查:它得懂“的得地”混用、拼音近似、形近字混淆,还要在没标点的长句里准确定位错误位置
你有没有试过把“他明天会来”打成“他名天会来”,结果 Word 只标红“名天”,但不告诉你该改成“明天”?或者输入“在再见”时,系统只提示“再见”重复,却对前面那个“在”视而不见?这不是 Word 不够聪明,而是传统规则引擎根本处理不了中文错别字的三重黑匣子:音近(zhi→zi)、形近(己→已)、义近(做→作)交叉叠加,且缺乏英文那样的空格分词边界。这个高分项目.zip 就是冲着这个痛点来的——它不用词典暴力匹配,也不靠人工写一百条 if-else 规则,而是用机器学习模型学出“哪些字组合在一起才自然”。核心是三个模块联动:先用 jieba 分词+拼音映射构建候选集,再用基于字符 n-gram 的轻量级分类器打分排序,最后用编辑距离约束做兜底校验。整个流程跑在本地 Python 环境里,不依赖在线 API,训练数据就藏在cn_dict.txt和pinyin.txt里,连stopwords.txt都按中文语境专门筛过。适合计算机相关专业学生直接当毕设开题原型,也适合想搞懂“机器学习怎么落地到中文文本纠错”这个具体场景的工程师——它不讲 SVM 公式推导,但每行代码都在解决真实问题:比如FeInterface.py里那个get_similar_chars()函数,就是专门对付“未”和“末”这种笔画差一横却读音完全不同的形近字。
2. 从零跑通项目:环境准备、数据加载、模型推理三步闭环
2.1 环境依赖与版本锁定:为什么必须用 Python 3.7 而不是 3.9+
这个项目在答辩时用的是 Python 3.7.12 + PyTorch 1.8.1 + jieba 0.42.1 组合,不是随便选的。关键在于cellmainwindow_jm.py里用了QTableWidget.setItem()的旧版信号绑定方式,而 PyQt5 5.15.0 之后把这个接口改了;同时mainwindow_jm.py中的QGraphicsDropShadowEffect在 Python 3.9+ 的某些 Qt 版本下会触发RuntimeError: wrapped C/C++ object has been deleted。所以第一步不是 pip install,而是建隔离环境:
# 创建指定版本虚拟环境(conda 更稳) conda create -n typoenv python=3.7.12 conda activate typoenv pip install pyqt5==5.14.2 jieba==0.42.1 numpy==1.21.6 scikit-learn==0.24.2提示:不要用
pip install -r requirements.txt—— 原压缩包里根本没有 requirements.txt 文件,所有依赖都硬编码在.py文件头部注释里,比如FeInterface.py第 3 行写着# requires: jieba>=0.42.0,<0.43.0。这是学生项目常见做法,也是后续排查报错的第一线索。
2.2 数据文件结构解析:words.txt是词表,cn_dict.txt才是纠错核心
很多人解压后第一眼只看words.txt,以为那是主词典,结果运行时报KeyError: '的'。其实真正的纠错知识库藏在cn_dict.txt里——它不是简单词表,而是按“正确词 → 常见错词”键值对组织的映射,格式如下:
明天: 明天|名天|明田|鸣天 已经: 已经|以经|已径|已荆 的: 的|得|地|迪|笛每一行冒号前是标准词,后面竖线分隔的是人工标注的高频错写变体。pinyin.txt则存着每个汉字的标准拼音及声调(如的:de1),用于计算音近度得分;jieba.txt是为 jieba 定制的用户词典,把项目里高频专业词(如“神经网络”“梯度下降”)加进去,避免分词切错导致纠错失效。stopwords.txt里删掉了“啊”“哦”“嗯”这类语气助词——因为它们极少被写错,加入反而稀释模型注意力。
2.3 启动 GUI 并验证基础功能:mainwindow_jm.py的隐藏初始化逻辑
双击mainwindow_jm.py会启动一个带搜索框和结果表格的界面,但如果你直接运行,大概率卡在“加载中…”。原因在于程序启动时会自动执行init_model(),而这个函数内部做了三件事:
- 读取
cn_dict.txt构建self.error_map字典(内存占用约 12MB); - 加载
pinyin.txt生成self.pinyin_dict,并预计算所有汉字两两之间的拼音编辑距离(Levenshtein distance); - 最关键一步:调用
jieba.initialize()并加载jieba.txt,否则后续分词会漏掉“反向传播”这类复合词。
所以正确启动方式是:
python mainwindow_jm.py而不是用 IDE 直接 Run。如果看到窗口左下角显示 “模型加载完成 (12487 个纠错对)”,说明数据加载成功。此时在搜索框输入“我明田会来”,点击“纠错”,表格第一行应显示“明天”并标注置信度 0.92——这个数字来自FeInterface.py的calculate_score()函数,它综合了音近度(拼音 Levenshtein)、形近度(笔画结构相似性)、频次(words.txt中词频加权)三个维度。
2.4 命令行模式快速测试:绕过 GUI 直接调用核心纠错函数
不想等 GUI 启动?可以直接调用FeInterface.py里的correct_text()方法:
from FeInterface import FeInterface fe = FeInterface() result = fe.correct_text("他名天会来,我以经准备好了") print(result) # 输出: {'original': '他名天会来,我以经准备好了', # 'corrected': '他明天会来,我已经准备好了', # 'details': [{'pos': 2, 'wrong': '名天', 'right': '明天', 'score': 0.94}, # {'pos': 11, 'wrong': '以经', 'right': '已经', 'score': 0.87}]}注意pos是字符偏移量(不是字数),所以“名天”从第 2 个字符开始(索引 2),对应原文“他名天会来”中的“名”。这个设计是为了后续对接 Web API 时能准确定位错误位置,比单纯返回修正后字符串有用得多。
3. 模型原理拆解:为什么不用 BERT,而用字符 n-gram + 编辑距离混合策略
3.1 放弃预训练大模型的现实理由:算力、延迟与可解释性
看到“机器学习”就想到 BERT 或 RoBERTa?这个项目恰恰反其道而行之。答辩 PPT 第 12 页明确写了放弃 Transformer 的三条硬约束:
- 部署成本:导师要求能在 4GB 内存的树莓派上跑通,BERT-base 至少需要 2GB 显存;
- 响应延迟:课程设计演示要求单次纠错 < 300ms,BERT 推理平均 800ms;
- 可解释性缺失:评委问“为什么把‘已径’纠成‘已经’而不是‘已进’?”,BERT 只能说“attention 权重高”,而本项目能输出具体依据:“‘径’和‘经’拼音都是 jing,但‘经’在
cn_dict.txt中与‘已经’配对出现 37 次,‘进’仅出现 2 次”。
所以最终方案是三层过滤:
- 候选生成层:对输入文本逐字扫描,用
pinyin.txt查找所有拼音相同/相近(声母韵母相同,仅声调不同)的汉字,组成候选集; - 打分排序层:对每个候选替换,计算三项得分:
- 音近分:
1 - levenshtein(pinyin_wrong, pinyin_right) / max_len - 形近分:查
char_shape_sim.csv(项目未提供,但代码里预留了接口)中预存的 1000 个常用字两两笔画结构相似度; - 语境分:用
words.txt中的词频做平滑,比如“已经”词频 1248,“已进”仅 3,直接加权;
- 音近分:
- 约束校验层:强制要求编辑距离 ≤ 2,且替换后不能产生未登录词(查
cn_dict.txt键集合)。
3.2cellmainwindow_jm.py中的纠错流程图:GUI 如何驱动底层逻辑
这个文件名字里的 “cell” 不是“细胞”,而是“cellular”(蜂窝)的缩写,指代其模块化设计——每个纠错单元(cell)独立封装。核心流程在on_search_clicked()方法里:
def on_search_clicked(self): raw_text = self.input_edit.toPlainText().strip() if not raw_text: return # Step 1: 分词预处理(避免把“神经网络”切成“神经/网络”) seg_list = jieba.lcut(raw_text, HMM=False) # 关闭隐马尔可夫,用精确模式 # Step 2: 对每个词调用纠错引擎 corrected_parts = [] for word in seg_list: if len(word) == 1: # 单字直接查 cn_dict.txt candidates = self.fe.get_candidates_from_dict(word) else: # 多字词走 n-gram 模式 candidates = self.fe.get_ngram_candidates(word) # Step 3: 选最高分候选,记录原始位置 best = max(candidates, key=lambda x: x['score']) if candidates else {'word': word, 'score': 0} corrected_parts.append({ 'original': word, 'corrected': best['word'], 'score': best['score'], 'pos': raw_text.find(word) # 注意:这里用 find() 不是 index(),防错位 }) # Step 4: 拼接结果并高亮 self.show_result(corrected_parts)关键细节:jieba.lcut(..., HMM=False)强制关闭隐马尔可夫模型,因为 HMM 会引入不确定性分词(比如把“机器学习”分成“机器/学习”或“机/器/学/习”),而纠错必须基于稳定分词结果。raw_text.find(word)用find而不是index,是因为 jieba 分词可能切出不在原文中的词(比如把“CSDN”切为“CS/DN”),find返回 -1 时程序会跳过该词,避免崩溃。
3.3FeInterface.py的get_ngram_candidates()实现:字符级 n-gram 如何捕捉上下文
多字词纠错不用整词替换,而是用字符 n-gram 捕捉局部模式。比如输入“以经”,函数会:
- 拆成字符序列
['以', '经']; - 对每个字符生成音近候选:
以→已/矣/易/意,经→径/京/景/精; - 组合所有排列(笛卡尔积),得到
['已径','已京','已景','已精',...,'意径','意京',...]共 25 种; - 过滤掉不在
cn_dict.txt键中的组合(如“意京”),剩下['已径','已经','已精']; - 对每个剩余组合,计算
n-gram 语言模型得分:查words.txt中该词的出现频次,再除以总词数归一化。
这个设计的妙处在于:它不需要训练语言模型,直接用统计频次替代,既轻量又符合中文特点——“已经”在语料中出现 1248 次,“已径”仅 7 次,分数自然拉开。words.txt里共收录 23841 个常用词,频次数据来自搜狗输入法公开语料,不是随机生成。
4. 避坑指南:那些让答辩前夜崩溃的五个真实报错及根因修复
4.1 现象:GUI 启动后搜索框输入中文,点击纠错无反应,控制台静默
原因:jieba.txt编码是 GBK,但 Python 3.7 默认用 UTF-8 打开,导致jieba.load_userdict()读入乱码,后续分词全崩。jieba.lcut("已经")返回['已经']正常,但jieba.lcut("已径")却返回['已', '径'],破坏了多字词纠错前提。
解决:打开jieba.txt,用记事本另存为 UTF-8 编码(不要带 BOM),或在mainwindow_jm.py中修改加载方式:
# 原代码(报错) jieba.load_userdict('jieba.txt') # 改为(显式指定编码) jieba.load_userdict(open('jieba.txt', 'r', encoding='utf-8'))4.2 现象:输入“的得地”混用句子,如“你做的很好”,纠错结果变成“你做得很好”,但置信度只有 0.31
原因:cn_dict.txt中“的/得/地”三字互纠条目缺失。原文件只有的:得|地,没有反向得:的|地和地:的|得,导致模型单向信任“的→得”,却不认为“得→的”合理。
解决:手动补全cn_dict.txt,添加两行:
得:的|地 地:的|得然后重启程序。补全后“你做的很好”纠错置信度升至 0.89,因为模型现在能双向评估语法合理性。
4.3 现象:在FeInterface.py中调用correct_text("Python很强大"),返回结果包含“Python”被误纠为“派森”
原因:pinyin.txt里只存了中文汉字拼音,没处理英文单词。当 jieba 遇到“Python”,默认切为单字['P', 'y', 't', 'h', 'o', 'n'],然后对每个字母查拼音表——查不到就返回空字符串,导致get_similar_chars('P')返回所有拼音首字母为 P 的汉字(如“派”“盘”“胖”),最终组合出“派森”。
解决:在FeInterface.py的correct_text()开头加过滤:
import re def correct_text(self, text): # 过滤纯英文单词,跳过纠错 text = re.sub(r'[a-zA-Z]+', lambda m: f"__ENGLISH__{m.group()}__ENGLISH__", text) # ...原有逻辑... # 最后还原英文 result['corrected'] = re.sub(r'__ENGLISH__(\w+)__ENGLISH__', r'\1', result['corrected']) return result4.4 现象:project成果展示.mp4里演示的“神经网络”纠错成功,但自己运行时总返回原词
原因:jieba.txt中“神经网络”词条被写成了“神经网路”(“络”字错写),导致 jieba 分词时无法匹配到这个词,降级为单字分词,破坏了多字词纠错路径。
解决:用文本编辑器全局搜索jieba.txt,把所有“网路”改为“网络”。注意jieba.txt是用户词典,不是纠错词典,它只影响分词粒度,不影响cn_dict.txt的纠错逻辑。
4.5 现象:Data文件夹下stopwords.txt修改后,重启 GUI 仍不生效
原因:FeInterface.py中load_stopwords()函数有缓存机制,首次加载后存入self.stopwords,后续不再重读文件。
解决:两种方法任选其一:
- 重启 Python 进程(最简单);
- 或在
FeInterface.py中找到load_stopwords(),在函数末尾加一行self.stopwords = set()强制清空缓存,再重新加载。
5. 进阶改造:把单机纠错升级为可部署服务,支持批量文本与 API 对接
5.1 批量纠错脚本:处理.txt文件列表,输出带定位的 JSON 报告
课程设计常需处理百篇作文,GUI 逐个粘贴太慢。我在FeInterface.py同级目录新建batch_correct.py:
import json import os from FeInterface import FeInterface def batch_correct(input_dir, output_dir): fe = FeInterface() results = [] for filename in os.listdir(input_dir): if not filename.endswith('.txt'): continue filepath = os.path.join(input_dir, filename) with open(filepath, 'r', encoding='utf-8') as f: text = f.read().strip() # 调用纠错,保留原始位置信息 res = fe.correct_text(text) res['filename'] = filename res['input_length'] = len(text) results.append(res) # 输出为 JSONL(每行一个 JSON 对象) with open(os.path.join(output_dir, 'batch_result.jsonl'), 'w', encoding='utf-8') as f: for item in results: f.write(json.dumps(item, ensure_ascii=False) + '\n') print(f"完成处理 {len(results)} 个文件") if __name__ == '__main__': batch_correct('./input_texts/', './output_reports/')运行后生成的batch_result.jsonl每行是一个 JSON 对象,含details数组,每个元素带pos(字符偏移)、wrong、right、score。老师批改时可直接用 Excel 导入 JSONL,按score列排序,优先看低分项(可能是新错字)。
5.2 轻量级 Flask API 封装:三步暴露为 HTTP 服务
不想装 Docker?用 Flask 10 行代码搞定:
# api_server.py from flask import Flask, request, jsonify from FeInterface import FeInterface app = Flask(__name__) fe = FeInterface() # 单例,避免重复加载模型 @app.route('/correct', methods=['POST']) def correct_api(): data = request.get_json() text = data.get('text', '') if not text: return jsonify({'error': 'text is required'}), 400 result = fe.correct_text(text) return jsonify(result) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=False) # 关闭 debug 防止敏感信息泄露启动命令:python api_server.py,然后用 curl 测试:
curl -X POST http://localhost:5000/correct \ -H "Content-Type: application/json" \ -d '{"text":"他名天会来"}' # 返回: {"original":"他名天会来","corrected":"他明天会来","details":[{"pos":2,"wrong":"名天","right":"明天","score":0.94}]}注意:生产环境务必加 Nginx 反向代理和请求频率限制,但课程设计演示用
flask-limiter就够了——毕竟评委只关心“能不能跑”。
5.3 模型热更新机制:不重启服务,动态加载新错词对
答辩后老师说“你们漏了‘帐号’和‘账号’的互纠”,总不能让服务停机半小时。我在FeInterface.py里加了个reload_dict()方法:
def reload_dict(self, dict_path='cn_dict.txt'): """热重载纠错词典,无需重启进程""" new_map = {} with open(dict_path, 'r', encoding='utf-8') as f: for line in f: if ':' not in line: continue right, wrongs = line.strip().split(':', 1) new_map[right.strip()] = [w.strip() for w in wrongs.split('|')] # 原子替换,避免并发读取时出错 self.error_map = new_map print(f"[INFO] 词典重载完成,共 {len(new_map)} 个正确词")然后在api_server.py里加个管理端点:
@app.route('/reload_dict', methods=['POST']) def reload_dict(): fe.reload_dict() return jsonify({'status': 'success'})运维同学 curl 一下就生效,比改代码再 git push 快十倍。
从那以后我每次给学生讲毕设,都强制他们先跑通batch_correct.py处理 100 篇样例,再截图对比纠错前后差异——因为真正的好模型,不是在 demo 里炫技,而是让老师一眼看出“这确实改对了”。希望帮到你。
本文还有配套的精品资源,点击获取