☰
SIGHAN中文纠错数据集解析与平行句对生成实战
2026/9/26 11:24:54 网站建设 项目流程

简介:SIGHAN中文纠错数据集及转换后格式.zip面向中文自然语言处理研究者与开发者,聚焦汉语语法错误检测、拼写检查与拼音标注任务,适合需要训练和评估中文纠错模型的中高级学习者。压缩包共78个文件,约19.92MB,以txt文本、sgml标注文件、zip子数据集、readme说明、jar工具、pdf论文、xlsx表格及py脚本为主,兼顾原始语料、转换脚本与格式说明。资源涵盖SIGHAN原始版本及CLP14、SIGHAN7/8等历年CSC数据,并附pair_data、simplified、traditional等目录,便于按简繁与配对格式组织实验。转换流程涉及数据预处理、错误位置标注、训练验证测试集划分、CoNLL等格式转换及纠正标签创建,读者可据此快速搭建纠错训练管线,理解不同格式对模型效果与评估准确性的影响。目前已有378人学习下载,适合作为中文纠错算法复现与语料建设的参考素材。

1. 拿到 SIGHAN 中文纠错数据集先别急着解压:这份 zip 里到底装了什么

如果你正在做中文拼写检查(CSC)或者语法纠错(GEC),大概率绕不开 SIGHAN 这个名字。但很多人第一次拿到SIGHAN中文纠错数据集及转换后格式.zip时,解压完看着一堆clp14csc_release1.1、sighan8csc_release1.0、pair_data、raw_data的目录直接懵了——哪个是原始语料,哪个是能直接喂给模型的,generate_pair_data.py又该怎么跑。这份资源解决的正是这个断层:它把 SIGHAN 历年(2013/2014/2015/2017/2018 等届)的官方发布包和一份已经转好的平行句对格式放在一起,省去你自己写解析脚本的功夫。适合两类人:一是刚入门 CSC、想快速搭起训练/验证/测试流水线的同学;二是已经跑过模型、但被原始 XML/文本格式折腾过的从业者,可以直接拿pair_data做 baseline 对比。下面按「先看清结构 → 再动手转换 → 最后避坑」的顺序拆。

2. 目录结构与数据来源:raw_data、pair_data 和 file_io.py 各管什么

2.1 原始发布包与转换后格式的对应关系

先把 zip 解开,用tree -L 2或资源管理器看一眼顶层。典型结构长这样(不同打包版本略有出入,但核心目录一致):

SIGHAN中文纠错数据集及转换后格式/ ├── sighan_raw-master/ # 原始语料与官方脚本 │ ├── raw_data/ # 各届原始发布包解压后的内容 │ │ ├── clp14csc_release1.1/ │ │ ├── sighan7csc_release1.0/ │ │ └── sighan8csc_release1.0/ │ ├── file_io.py # 读写原始格式的工具函数 │ ├── generate_pair_data.py # 生成平行句对的核心脚本 │ ├── ss.md # 说明文档 │ └── README.md ├── pair_data/ # 转换后的平行句对 │ ├── simplified/ # 简体 │ └── traditional/ # 繁体 └── clp14csc_release1.1.zip 等 # 原始压缩包备份

raw_data里放的是官方发布包解压后的原始文件,通常是「每行一个句子 + 错误位置标注」的文本,或者带 XML 标签的结构。pair_data是转换后的成果,simplified和traditional分别对应简繁两套,每行一般是「错误句 \t 正确句」的平行格式,可以直接被 seq2seq、BERT 类纠错模型读取。file_io.py负责解析原始格式,generate_pair_data.py调用它批量生成平行句对。理解这条链路,后面出问题才知道该查哪一环。

2.2 各届数据集的特点与选型建议

SIGHAN 各届的语料来源和标注粒度不一样,选错版本会让你的实验结论没法跟别人对齐。常见几届的差异大致如下:

发布包主要来源简繁典型用途
clp14csc_release1.1母语者写作 + 学习者语料简/繁2014 届 CSC 评测基准
sighan7csc_release1.0新闻 + 网络文本简/繁2017 届,规模较大
sighan8csc_release1.0多来源混合简/繁2018 届,含更多错误类型

如果你要复现某篇论文的指标,先确认它用的是哪一届、哪个 release 号,再决定从raw_data里取哪份。做通用纠错、想要更大规模,优先sighan8csc;做简繁对比实验,pair_data下的simplified和traditional直接可用。注意:不同届的标注规范有细微差别,混用训练集和测试集会导致指标虚高,这是新手最容易翻车的地方。

2.3 用 file_io.py 读懂原始格式

在动手转换前,先花十分钟读file_io.py,它决定了原始文件怎么被解析。常见做法是里面会有类似read_lines、parse_sighan这样的函数,把「错误位置 + 正确字」的标注还原成完整句子。你可以先跑一个小脚本,打印前几条原始记录看看结构:

# 快速窥探原始格式,路径按实际解压位置调整 import os raw_dir = "sighan_raw-master/raw_data/sighan8csc_release1.0" for root, dirs, files in os.walk(raw_dir): for f in files: if f.endswith((".txt", ".sgml", ".xml")): path = os.path.join(root, f) print("==", path) with open(path, encoding="utf-8", errors="ignore") as fp: for i, line in enumerate(fp): if i >= 3: break print(repr(line[:120])) break break

这段代码只做一件事:定位原始文件并打印前三行,帮你判断它是纯文本还是带标签。参数上,errors="ignore"是为了防止个别编码异常字符中断读取;repr能把换行、制表符显式暴露出来,方便你判断分隔符。看清格式后,再决定是直接用generate_pair_data.py,还是自己写解析逻辑。

3. 生成平行句对:generate_pair_data.py 的参数与执行流程

3.1 转换脚本的核心逻辑

generate_pair_data.py干的事可以概括为:遍历raw_data下的原始文件 → 用file_io.py解析出「错误句」和「正确句」→ 按简繁分类 → 写出到pair_data/simplified和pair_data/traditional。它通常支持指定输入目录、输出目录、是否保留简繁、是否切分训练/验证/测试等参数。执行前先确认 Python 环境,脚本一般只依赖标准库,少数版本会用到tqdm之类做进度条。

# 建议在虚拟环境里跑,避免污染全局 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install tqdm # 若脚本 import 了它 python generate_pair_data.py --help

先看--help输出,确认参数名。不同打包版本的参数命名可能不同,别照搬网上的命令,以你本地脚本为准。

3.2 一次完整的转换执行

假设脚本支持--input、--output、--lang三个参数,典型调用如下:

# 生成简体平行句对 python generate_pair_data.py \ --input sighan_raw-master/raw_data \ --output pair_data/simplified \ --lang simplified # 生成繁体平行句对 python generate_pair_data.py \ --input sighan_raw-master/raw_data \ --output pair_data/traditional \ --lang traditional

参数说明:--input指向原始语料根目录,脚本会递归扫描;--output是写出目录,不存在时一般会自动创建;--lang控制简繁过滤,因为部分原始文件同时含简繁,需要按目标拆分。跑完后检查输出行数,正常情况每行是「错误句 \t 正确句」,用wc -l和head各看一眼:

wc -l pair_data/simplified/*.txt head -n 3 pair_data/simplified/*.txt

如果行数为 0 或明显偏少,多半是--input路径不对,或者原始文件扩展名不在脚本的扫描白名单里,回到file_io.py确认它认哪些后缀。

3.3 划分训练/验证/测试集

pair_data给的是全量平行句对,真正训练前要自己切分。常见比例是 8:1:1,注意同一来源的句子不要跨集合泄漏。下面这段脚本按行随机切分并落盘:

import random random.seed(42) # 固定种子,保证可复现 src = "pair_data/simplified/all.txt" lines = open(src, encoding="utf-8").read().splitlines() random.shuffle(lines) n = len(lines) train, dev, test = lines[:int(n*0.8)], lines[int(n*0.8):int(n*0.9)], lines[int(n*0.9):] for name, data in [("train", train), ("dev", dev), ("test", test)]: with open(f"pair_data/simplified/{name}.txt", "w", encoding="utf-8") as fp: fp.write("\n".join(data)) print(name, len(data))

random.seed(42)是后悔药,保证每次切分结果一致,方便复现实验。切分后建议统计一下错误类型分布,如果某一类错误只出现在测试集,指标会失真。这一步没有标准答案,但「先看分布再定切分」是稳妥习惯。

4. 避坑与常见问题排查:编码、简繁混用和标注错位

4.1 现象:读文件报 UnicodeDecodeError

原因:原始语料里混有 GBK/GB18030 编码的旧文件,直接用 UTF-8 打开会崩。解决:读取时显式指定编码,或加errors="ignore"先跑通,再回头定位问题文件。稳妥做法是用chardet探测:

import chardet raw = open("some_file.txt", "rb").read(10000) print(chardet.detect(raw))

拿到编码后再用对应编码打开,别一上来就errors="ignore",那会静默丢字,导致平行句对错位。

4.2 现象:pair_data 里简繁混杂

原因:部分原始文件本身同时含简繁,--lang过滤不彻底,或脚本按字符集判断时把边界字判错。解决:转换后做一次简繁检测,把明显不属于目标语言的句子剔掉。常见做法是用opencc做转换对比,或维护一个高频简繁差异字表做粗筛。别指望一次转换就干净,人工抽检 200 行是必要的。

4.3 现象:错误句和正确句长度对不上

原因:原始标注里有多字替换、增删,解析时如果只按「单字替换」处理,会把长错误截断。解决:回到file_io.py看它怎么处理多字 span,必要时自己扩展解析逻辑。平行句对长度差超过阈值(比如 5 个字符)的样本,建议单独存疑,别直接进训练集。

4.4 现象:训练指标高得离谱

原因:训练集和测试集来自同一批原始文件,句子级泄漏。解决:切分前先按来源文件分组,同一文件的句子只进一个集合。这个坑很隐蔽,指标虚高十几个点都可能,血泪经验是切分脚本里加一句来源标记。

4.5 现象:脚本跑完没报错但输出为空

原因:--input指向了压缩包而不是解压后的目录,或脚本扫描的后缀与实际文件不符。解决:先ls确认目录里有文件,再对照file_io.py里的后缀白名单。别忽略README.md和ss.md,里面往往写了作者预期的调用方式。

5. 进阶用法:把 pair_data 接进纠错模型并做基线验证

拿到干净的平行句对后,下一步是验证它能不能真的训出东西。最省事的做法是先跑一个字符级 seq2seq 或直接用pycorrector这类现成工具做零样本测试,确认数据格式没问题,再上大模型。下面给一个最小验证脚本,用编辑距离粗算「错误句→正确句」的改动量,帮你判断数据难度:

import Levenshtein # pip install python-Levenshtein def stat(path): total, changed = 0, 0 for line in open(path, encoding="utf-8"): parts = line.rstrip("\n").split("\t") if len(parts) != 2: continue src, tgt = parts total += 1 if src != tgt: changed += 1 print(f"{path}: {total} 行, 需纠错 {changed} 行, 占比 {changed/total:.2%}") stat("pair_data/simplified/test.txt")

如果「需纠错占比」接近 100%,说明这份平行数据是「每句都含错」的评测风格;如果只有一部分,说明混入了正确句,训练时要留意损失计算。这个统计能帮你快速判断数据是否符合预期,比盲目开训省时间。

再进一步,可以把pair_data转成模型需要的格式。以 BERT 类纠错模型为例,常见输入是「错误句」、标签是「每个位置是否错误 + 正确字」,这时需要把平行句对做字符级对齐。对齐可以用difflib.SequenceMatcher:

import difflib def align(src, tgt): sm = difflib.SequenceMatcher(None, src, tgt) labels = ["K"] * len(src) # K=keep for tag, i1, i2, j1, j2 in sm.get_opcodes(): if tag == "replace": for i in range(i1, i2): labels[i] = tgt[j1] if i2 - i1 == j2 - j1 else "R" elif tag == "delete": for i in range(i1, i2): labels[i] = "D" return labels print(align("我今天去学校", "我今天去学院"))

get_opcodes返回的replace/delete/insert对应替换、删除、插入,labels里K表示保留、具体字符表示替换目标、D表示删除。注意等长替换和不等长替换要分开处理,否则标签会错位。这套对齐逻辑是很多 CSC 模型数据预处理的核心,跑通它,pair_data才算真正能用。

从那以后我每次拿到新的纠错数据集,都强制先跑一遍「行数统计 + 简繁抽检 + 长度分布 + 对齐测试」四件套,确认无误再开训,省下的返工时间远超这十分钟。希望帮到你。

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

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

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

立即咨询