项目代码在我的GitHub上
1 问题引入
1.1 病句标注问题
病句纠错是自然语言分析领域中的一个常见问题。神经网络的纠错模型往往需要较大的数据量。但由于语病是一个小概率事件,真实生活中的病句数据量往往难以满足模型训练的需要,所以制造伪数据成为必要。使用模型自动产生的伪数据中不含有具体错误的位置和类型的标签,无法直接输入模型。所以我们需要一种方法,在给出病句和正确句子的前提下,在病句上标注出错误的类型和位置,或者说,使得依据标注能够正确地将病句改正。
修改操作
有以下四种:
插入
:在病句中插入一个单词,操作为insert,标签为M删除
:删除病句中的某个单词,操作为delete,标签为R替换
:将病句中某个单词替换成另一个单词,操作为replace,标签为S换序
:将病句中某两个相邻短语交换顺序,操作为disorder,标签为W
其中,由于标签是打在字上的,所以插入标签会打在插入缝隙的下一个字上(这也是病句标注有待解决的难题),在句尾加入一个特殊字作为padding来避免插入时索引溢出。
1.2 有关difflib
difflib
作为python的标准库模块,无需安装,作用是比对文本之间的差异,且支持输出可读性比较强的HTML文档,与Linux下的diff命令相似。可以使用该模块比对代码和配置文件的差异,在版本控制方面非常有用。Python2.3以后的版本默认自带difflib
模块,无需额外安装。
利用diff.SequenceMatcher(_, source_list, target_list).get_opcodes()
可以得到序列化的字符串输入source修改为target需要的操作以及操作的位置下标范围。但是,这个工具生成的操作的可选空间为:equal, insert, delete, replace。我们不需要equal,而需要额外的disorder。考虑这个问题以及以下诸多问题,我们编写了这个DiffExtend
类的脚本,作为difflib
在病句标注上的扩展。
主要方法是在difflib
的修改操作序列的基础上,依据jieba
的分词和词性特征对操作进行修正。分情况讨论时只预见了下一个操作,从这个角度讲,该工具可以看做DiffExtend(k)
,当前k=1
主要考虑的问题:
1 考虑插入操作是否存在以下情况,与前后组成单词:
eg1 生效果 >> 产生效果; 原始:插入产; 修正:生替换为产生
eg2 癌症导致死 >> 癌症导致死亡; 原始:插入亡; 修正:死替换为死亡
排除以下情况:
eg1 一微笑 >> 一个微笑; 正确:插入个; 修正:一替换为一个
eg2 个微笑 >> 一个微笑: 正确:插入一; 修正:个替换为一个
2 如果当前操作为替换,且替换的不是整个单词,且替换位置的单词不为数量词,则替换整个词
eg 生产 >> 生活; 原始:产替换为活; 修正:生产替换为生活
判断词性是为了避免以下情况:
eg 一个 >> 一种; 正确:个替换为种; 修正:一个替换为一种
3 如果替换的目标短语不是一个单词,则添加一步插入
eg 生活 >> 生产劳动; 原始:生活替换为生产劳动; 修正:生活替换为生产,插入劳动
4 diff工具原理为子序列的最大匹配,若句中有多个匹配会导致混乱
使用逗号分割得到短句后再将短句的match进行归并,默认短句不存在多个匹配
5 归并短句的操作以得到整句的操作,注意更新操作起止下标
6 去除equal操作
7 如果是一个插入接着一个删除
如果插入与删除在同一单词内,认定为替换
8 如果是一个插入接着一个删除
如果插入与删除隔了若干单词,且插入与删除的内容相同,认定为换序
9 如果是一个删除接着一个插入
如果删除和插入是相邻单词,认定为替换
10 如果是一个删除接着一个插入
如果删除和插入隔了若干单词,且插入与删除的内容相同,认定为换序
11 如果是一个插入接着一个替换
如果插入和替换在同一单词且插入的词与被替换的词相同,认定为替换后插入
eg 产生在工作 >> 生产队在工作
eg 原始:在产前插入生,将生替换为产队
eg 修正:产生换序,插入队
12 如果是一个插入接着一个替换
如果插入和替换的单词相邻且插入的词与被替换的词相同,认定为换序后插入
eg 工具好的解决问题 >> 好的工具能解决问题
eg 原始:在好前插入工具,将好的替换为工具能
eg 修正:工具好的换序,插入能
13 如果是一个替换接着一个删除
如果替换和删除在同一单词且替换的目标词与被删除的词相同,认定为删除后替换
eg 好产生队在工作 >> 生产队在工作
eg 原始:将好替换为生,删除生
eg 修正:删除好,将产生替换为生产
14 如果是一个替换接着一个删除
如果替换和删除的单词相邻且替换的目标词与被删除的词相同,认定为删除后换序
eg 还工具好的能解决问题 >> 好的工具能解决问题
eg 原始:将还替换为好的,删除好的
eg 修正:删除还,将工具与好的换序
2 运行方式
2.1 环境要求
依赖 | 版本 |
---|---|
Python | 3.4 |
difflib | auto |
re | auto |
jieba | 0.39 |
2.2 具体步骤
代码中使用2016和2017年的CGED_TrainingSet
作为数据集,分别在文件2016_CGED_TrainingSet.txt
和train.release.xml
中,格式为xml格式,使用前对数据进行过简单清洗和修复。
# 运行测试的方法
python test.py # 使用文件输入做测试,可以修改文件中的xml_file_name指定数据集
python evaluate.py # 使用数据集给出的正确标签评估DiffExtend工具的准确率
python compare_tool.py # 可选项,输出DiffExtend工具与真实标签不同的句对
# 使用的方法
from DiffExtend import DiffExtend
source = "ABC"
target = "XYZ"
operations = DiffExtend.label(source, target)
# 每一个operation是一个三元组,(操作,操作在source上的起始下标,操作在source上的结束下标)
3 成果与不足
2016年CGED_TrainingSe
t的Difflib
结果
总句子数:9593 判错完全正确的句子数:4711 0.4910872511206088
类型S判断正确5044 类型S验证10400 0.4850000000000000
类型R判断正确4444 类型R验证5271 0.8431037753746917
类型M判断正确5544 类型M验证6195 0.8949152542372881
类型W判断正确0 类型W验证1620 0.0000000000000000
2016年CGED_TrainingSet
的DiffExtend
结果
总句子数:9593 判错完全正确的句子数:6194 0.645679141040342
类型S判断正确7371 类型S验证10400 0.70875
类型R判断正确4609 类型R验证5271 0.8744071333712768
类型M判断正确5089 类型M验证6195 0.8214689265536723
类型W判断正确1031 类型W验证1620 0.6364197530864197
2017年CGED_TrainingSet
的DiffExtend
结果
总句子数:10449 判错完全正确的句子数:6791 0.6499186525026318
类型S判断正确8367 类型S验证11584 0.722289364640884
类型R判断正确5165 类型R验证5848 0.8832079343365253
类型M判断正确5843 类型M验证7008 0.8337614155251142
类型W判断正确1538 类型W验证1993 0.7717009533366783
从结果上看,相对于单纯的difflib
结果,DiffExtend
的准确率有了很大提升,并且能够标注出换序错误,有一定的实用价值。但这其中也存在着一些问题:
- 在修正删除操作成为替换操作时,提升了替换的准确率,但牺牲了一部分删除的准确率
- 在考虑多词换序(注意上文写的是短语不是单词)时,如果考虑序列过短,会漏掉一些错误,考虑序列过长也会产生负面影响
- 只预见了下一个操作,只能处理较为简单的情况
- 给出的数据集中存在一定的差错,如标记错误,标点错误等