☰
SIGHAN中文纠错数据集转换实战:从SGML到平行句对与字符级标注
2026/9/26 11:24:47 网站建设 项目流程

简介:SIGHAN中文纠错数据集及转换后格式.zip 面向中文自然语言处理研究者、拼写检查与语法纠错方向的开发者及学生,提供汉语语法错误检测与拼音标注的权威语料,可用于训练和评估中文纠错算法。压缩包共78个文件,约19.92MB,以txt文本、sgml标注文件为主,另含zip原始发布包、readme说明、jar工具、pdf论文、xlsx表格、py脚本与md文档,覆盖原始语料、配对数据及简繁转换等模块。已有378人学习下载。资源包含SIGHAN原始版本及转换后格式,涉及错别字、词序、搭配不当等多类错误标注,并附数据预处理、错误标注、训练验证测试集划分、CoNLL等格式转换与纠正标签生成的完整流程,便于读者直接用于模型训练、性能对比与二次定制,是推进中文纠错系统研发的实用素材。

1. 拿到 SIGHAN 中文纠错数据集压缩包之后:先别急着解压

你从各种渠道拿到一个叫SIGHAN中文纠错数据集及转换后格式.zip的压缩包,双击解压,里面大概率是一堆按年份分的文件夹,夹杂着.sgml、.txt、.char、.bmes之类的文件,还有几个看起来像转换脚本的东西。很多人第一反应是「先跑起来再说」,结果卡在编码、对齐、格式转换上,白白耗掉一整天。这个标题背后真正要解决的问题是:SIGHAN 系列中文纠错数据集(主要来自 CGED、SIGHAN 拼写检查评测)原始格式不统一,需要转成模型能直接吃的平行句对格式,才能喂给序列标注或 seq2seq 纠错模型。适合谁?做中文文本纠错、拼写检查、ASR 后处理、OCR 后纠错的工程师和研究生。这篇笔记就按「压缩包里有什么 → 怎么转 → 怎么验证 → 坑在哪」的顺序,把这条链路讲透,让你拿到包当天就能跑通第一版训练数据。

2. SIGHAN 中文纠错数据集的目录结构与格式差异:先认清手里是什么

2.1 原始 SIGHAN 格式长什么样

SIGHAN 中文纠错数据最早来自 2013-2015 年的中文拼写检查评测,后来 CGED(Chinese Grammatical Error Diagnosis)又扩展了语法纠错任务。原始文件通常是 SGML 风格的标记文本,每篇文档用<DOC>包裹,句子用<SENT>标记,错误位置用<ERROR>标注。一个典型的片段长这样:

<DOC> <SENT id="1"> 我们今天<ERROR id="1" type="missing">去</ERROR>公园玩。 </SENT> </DOC>

这里<ERROR>标签的type属性告诉你错误类型(missing、replace、多余等),标签内的文字是「正确内容」还是「错误内容」取决于具体年份和任务定义,这一点是后面转换时最容易翻车的地方。2013 年的数据里,<ERROR>标签内放的是正确词,而 2014 年部分文件放的是错误词,必须逐份核对 README 或论文说明,不能一刀切。

2.2 转换后格式通常指什么

所谓「转换后格式」,在中文纠错圈子里一般指两种:

格式类型文件形态典型用途
平行句对每行错误句\t正确句seq2seq 纠错模型训练
字符级标注每行字\t标签,标签为 B/M/E/S 或 0/1BiLSTM-CRF、BERT 序列标注

平行句对格式最通用,也最容易从 SGML 转出来。字符级标注需要额外做对齐,难度高一个量级。压缩包里如果同时给了这两种,优先用平行句对跑通 baseline,再考虑标注格式。

2.3 解压前先确认的三件事

第一,文件编码。SIGHAN 原始数据多为 GB2312 或 GBK,直接当 UTF-8 读会乱码。第二,压缩包是否嵌套。有些包解压后还有一个同名 zip,需要二次解压。第三,是否有 README 或 LICENSE。没有 README 的数据集,转换规则只能靠试,试错成本很高。

# 先看压缩包内文件列表,不解压 unzip -l SIGHAN中文纠错数据集及转换后格式.zip # 解压到指定目录,避免中文路径问题 unzip -O GBK SIGHAN中文纠错数据集及转换后格式.zip -d sighan_raw # 查看文件编码,file 命令只能猜个大概 file -i sighan_raw/*/*.sgml

unzip -O GBK指定用 GBK 解码文件名,避免解压后文件名乱码。file -i输出charset=iso-8859-1或unknown-8bit时,基本可以确定不是 UTF-8,需要用iconv转码后再处理。

3. 把 SGML 转成平行句对:解析脚本与四个关键参数

3.1 解析 SGML 的核心逻辑

不要用正则硬怼整个文件,SGML 嵌套标签用正则容易漏。稳妥做法是按行读,遇到<SENT>开始收集,遇到</SENT>结束,中间用状态机处理<ERROR>标签。下面是一个最小可用的 Python 解析器:

import re def parse_sgml_line(line): """解析单行 SENT 内容,返回 (错误句, 正确句)""" # 提取 ERROR 标签及其属性 error_pattern = re.compile(r'<ERROR[^>]*>(.*?)</ERROR>') # 错误句:去掉所有标签,保留标签内文字 wrong = re.sub(r'<[^>]+>', '', line) # 正确句:把 ERROR 标签替换为其内部文字,再去掉其他标签 correct = error_pattern.sub(r'\1', line) correct = re.sub(r'<[^>]+>', '', correct) return wrong.strip(), correct.strip() def convert_sgml_to_parallel(input_path, output_path, encoding='gbk'): with open(input_path, 'r', encoding=encoding) as fin, \ open(output_path, 'w', encoding='utf-8') as fout: buffer = [] in_sent = False for line in fin: line = line.strip() if '<SENT' in line: in_sent = True buffer = [line] elif '</SENT>' in line: buffer.append(line) wrong, correct = parse_sgml_line(' '.join(buffer)) if wrong and correct: fout.write(f'{wrong}\t{correct}\n') in_sent = False elif in_sent: buffer.append(line)

这段代码的关键点有三个:encoding='gbk'应对原始编码;error_pattern.sub(r'\1', line)把错误标签替换成正确内容;输出统一用 UTF-8 和\t分隔,方便后续pandas.read_csv(sep='\t')直接读。

3.2 四个必须确认的参数

参数常见取值影响
输入编码gbk / gb2312 / utf-8读错编码直接乱码,后续全废
ERROR 语义标签内是正确词 / 错误词决定 wrong 和 correct 谁替换谁
分隔符\t/|||/ 空格影响下游读取,建议统一\t
空行处理跳过 / 保留保留空行会让 DataLoader 报错

ERROR 语义这一项,最可靠的办法是拿一条已知句子人工核对。比如原句「我今天去公园玩」,如果 SGML 里写成我<ERROR>今天</ERROR>去公园玩,而正确句应该是「我今天去公园玩」,那标签内就是正确内容,替换逻辑用sub(r'\1')没问题。反过来如果标签内是错误内容,就得用「去掉标签内文字」的逻辑。

3.3 批量转换与目录遍历

实际数据往往按年份和训练/测试集分目录,写一个批量脚本比手动一个个转靠谱:

import os from pathlib import Path def batch_convert(root_dir, out_dir): os.makedirs(out_dir, exist_ok=True) for sgml_file in Path(root_dir).rglob('*.sgml'): # 用相对路径生成输出文件名,避免重名 rel = sgml_file.relative_to(root_dir) out_name = str(rel).replace('/', '_').replace('.sgml', '.tsv') out_path = os.path.join(out_dir, out_name) try: convert_sgml_to_parallel(str(sgml_file), out_path) print(f'OK: {sgml_file} -> {out_path}') except Exception as e: print(f'FAIL: {sgml_file}, reason: {e}') batch_convert('sighan_raw', 'sighan_parallel')

rglob('*.sgml')递归找所有 SGML 文件,relative_to保留目录层级信息并压平到文件名里,避免不同年份的同名文件互相覆盖。转换失败的不要静默跳过,打印出来单独处理,通常是编码或标签不完整导致的。

4. 从平行句对到字符级标注:对齐逻辑与标签体系选择

4.1 为什么需要字符级标注

平行句对适合 seq2seq 模型,但如果你用的是 BERT + CRF 做序列标注,输入是单句,输出是每个字的标签。这时候需要把「错误句 → 正确句」的映射拆成每个字的 0/1 标签。难点在于错误句和正确句长度可能不一致,比如漏字、多字,直接按位置对齐会错位。

4.2 基于编辑距离的对齐方案

最稳的做法是用difflib.SequenceMatcher或编辑距离算法,找出错误句到正确句的最小编辑操作序列,再据此打标签:

import difflib def align_to_char_labels(wrong, correct): """返回 (chars, labels),labels 为 0/1 列表""" sm = difflib.SequenceMatcher(None, wrong, correct) labels = [0] * len(wrong) for tag, i1, i2, j1, j2 in sm.get_opcodes(): if tag == 'equal': continue elif tag == 'replace': # 替换:错误句对应位置标 1 for i in range(i1, i2): labels[i] = 1 elif tag == 'delete': # 错误句多字:标 1 for i in range(i1, i2): labels[i] = 1 elif tag == 'insert': # 错误句漏字:在 i1 位置标 1(或前一个字标 1) if i1 < len(labels): labels[i1] = 1 return list(wrong), labels

get_opcodes()返回五种操作:equal、replace、delete、insert。replace 和 delete 直接标 1;insert 表示错误句漏了字,通常把插入点前一个字标 1,或者单独定义一个「缺失」标签。如果下游模型只做二分类,统一标 1 即可。

4.3 标签体系的选择

标签体系标签数适用模型备注
0/1 二分类2BERT + 线性层最简单,漏字检测弱
B/M/E/S4BiLSTM-CRF能表示错误片段边界
错误类型多分类5+BERT + CRF需要 SGML 里的 type 属性

新手建议从 0/1 二分类起步,跑通全流程后再升级到 B/M/E/S。多分类标签需要原始 SGML 保留type属性,很多转换后的包已经丢掉了这个信息,想用也用不了。

5. 避坑与排查:SIGHAN 转换中最容易翻车的五个点

5.1 解压后文件名乱码,脚本读不到文件

现象:ls看到一堆????.sgml,Pythonglob匹配不到。原因:zip 内文件名用 GBK 编码,系统按 UTF-8 解码失败。解决:用unzip -O GBK重新解压,或在 Python 里用os.fsdecode配合surrogateescape处理。更省事的办法是在 Windows 上用 7-Zip 指定代码页解压,再拷到 Linux。

5.2 转换后平行句对里错误句和正确句一模一样

现象:打开 TSV 发现两列完全相同,模型学不到任何东西。原因:ERROR 标签语义判断反了,或者正则sub替换时把正确内容又替换回去了。解决:拿一条含错误的句子人工核对,确认wrong列确实包含错误、correct列已修正。如果原始数据里<ERROR>标签内是错误词,替换逻辑要改成「删除标签内文字」而不是「保留」。

5.3 编码转换后出现「锟斤拷」或问号

现象:GBK 转 UTF-8 后部分生僻字变成?。原因:原始数据里混了 GB2312 不支持的字符,或者文件本身是 UTF-8 但被当 GBK 读。解决:先用chardet检测编码,再决定用哪种解码。iconv -f GBK -t UTF-8时加//IGNORE会丢字符,不如在 Python 里用errors='replace'并记录替换位置,后续人工抽查。

5.4 字符级标注长度和原句对不上

现象:labels长度比wrong短或长。原因:difflib处理 insert 操作时索引越界,或者原句包含空格、标点被 strip 掉了。解决:对齐前不要 strip 中间空格,只 strip 首尾;insert 分支加if i1 < len(labels)保护。转换完统一断言len(chars) == len(labels),不满足的样本直接丢弃并计数。

5.5 训练时 loss 不降,模型输出和输入一样

现象:训练几个 epoch,模型学会「复制输入」,纠错率为零。原因:平行句对里正确句和错误句差异太小,或者数据里负样本(无错误句)占比过高。解决:统计错误句和正确句的编辑距离分布,过滤掉完全相同的句对;对无错误样本降采样,让正负样本比例控制在 1:1 到 1:3 之间。这个坑很玄学,但十有八九是数据分布问题,不是模型问题。

6. 验证转换质量与进阶用法:三个可落地的检查习惯

转换完不要直接开训,先做三件事。第一,随机抽 20 条平行句对,人工读一遍,确认错误句确实有错、正确句确实改对了。第二,统计句对数量、平均长度、编辑距离分布,和论文里报告的数据规模对比,差太多说明漏文件了。第三,用一个小模型(比如bert-base-chinese加线性层)跑 1 个 epoch,看 loss 是否从 0.6 左右开始下降,如果一直卡在 0.69 附近,基本是标签或数据格式有问题。

进阶用法上,如果你要做的不只是拼写纠错,而是语法纠错,SGML 里的type属性值得保留。可以在转换时额外输出一列错误类型,后续做多任务学习。另外,SIGHAN 数据年份跨度大,2013 和 2015 的标注规范有差异,混在一起训练时建议加年份特征或分年份评估,否则模型在某一年的测试集上会莫名翻车。

# 快速检查平行句对质量 import pandas as pd df = pd.read_csv('sighan_parallel/all.tsv', sep='\t', header=None, names=['wrong', 'correct']) df['same'] = df['wrong'] == df['correct'] print(f'总句对: {len(df)}') print(f'完全相同: {df["same"].sum()} ({df["same"].mean():.2%})') print(f'平均错误句长度: {df["wrong"].str.len().mean():.1f}') # 过滤掉完全相同的句对 df_clean = df[~df['same']].drop(columns=['same']) df_clean.to_csv('sighan_parallel/clean.tsv', sep='\t', index=False, header=False)

这段检查脚本我每次转换完都会跑一遍,完全相同比例超过 30% 就说明转换逻辑有问题,别急着往下走。df_clean存成无表头的 TSV,方便直接喂给 HuggingFacedatasets或自定义 DataLoader。

最后说个血泪经验:SIGHAN 数据集的坑不在模型,在数据本身。我见过太多人花三天调模型,最后发现是解压时编码错了。所以拿到压缩包,先花半小时把编码、标签语义、句对数量这三件事确认清楚,比后面调参省事得多。希望帮到你。

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

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

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

立即咨询