Kaggle比赛:Text Normalization for English银牌全程记录

问题描述

所谓“文本正则”,即将手写形式的文本转换成语音形式的文本。

例子:

  • 手写:A baby giraffe is 6ft tall and weighs 150lb.
  • 语音:A baby giraffe is six feet tall and weighs one hundred fifty pounds.

调研

目前kernel上主要以收集大辞典的方法为主流。

基于RNN的方法在paper中提到说效果不佳,paper上的实现方法是char进去,word出来。这样word的个数在训练集里就有6w多个,太多,训练效果不好。char到char的话又会丢掉很多上下文信息,比如$ 6对应的six dollars,要互换顺序,还要加复数,很难有好的效果。

token的所有类别:['PLAIN', 'PUNCT', 'DATE', 'LETTERS', 'CARDINAL', 'VERBATIM', 'DECIMAL', 'MEASURE', 'MONEY', 'ORDINAL', 'TIME', 'ELECTRONIC', 'DIGIT', 'FRACTION', 'TELEPHONE', 'ADDRESS']。 其中,'PLAIN', 'PUNCT'是输出和输入相同的类别。但训练数据中,只有'PUNCT'的输出和输入完全相同,'PLAIN'的输入和输出相同的个数是:7317175,总共个数是:7353693。比例是:0.995034059757458。这里后续需要继续调查。

在训练集中,'PLAIN', 'PUNCT'所占的比例是0.931。

解决方法进化记录

2017-11-2

0.9937-198/489

使用kernel中提供的方法尝试了一下。大致上就是从训练语料中提取一个大辞典,这个词典包括所有词以及其映射。不同的方法引进了更多的额外训练语料。

2017-11-5

0.9164

用xgboost训练了一个二分类器,判别某个token是不是属于'PLAIN'+'PUNCT'。是则输入和输出相同,否则输出""。得到这个准确率。

2017-11-6

0.9835

用xgboost训练了一个三分类器,判别某个token是否属于'PLAIN', 'PUNCT'还有其他。

2017-11-8

最终发现还是要使用全分类器full_classifier,即16个类别全都分全。然后针对每个类别手动设计normalize的过程full_replacereplace_by_rule。并使用test/test_one_class.py一个一个类别调准确率。这一阶段准确率的提升主要来自对每个单独类别的normalize的优化。

准确率:0.9908->0.9921->0.9937->0.9942->0.9951->0.9954->0.9956

排名:75/554,已经进入铜牌区域,但也进入了瓶颈,单独类别的转换准确率很难找到大幅提升的改进点。

使用脚本full_replace_train.py发现所有的训练数据,如果知道其分类,那么normalize的准确率是:9913201 / 9918441 = 0.9994716911659807。也就是说,错误的数目只有5k+了,继续修改normalize上升空间有限。

2017-11-11

转换思路,继续提高分类器的准确率。

思路一:增加人为判定规则

根据对数据的分析,发现分类器有几类典型错误:

  • 1.被误认为是cardinal的实际上的date的年份。这类情况特征明显,长度是4的数字都判为date即可。
  • 2.被误认为是plain的实际上的electronic的网站,比如baidu.com之类的,写一个简单的规则判断。

上面总结的规则都集中在patch_classifier.py脚本中,在分类器给出判定结果之后再跑一遍,作为对分类器的修正。

思路二:直接提升模型准确率

  • 1.特征工程:token的长度;是否是一句话中第一个token。目前特征工程对模型准确率没有明显提升。v2
  • 2.调整权重:因为本次任务是非常unbalanced的分类任务,因此最好对于不同类别的数据,在计算loss的时候,采用不同的权重。v3
  • 3.调参:关键参数:max_depthround_numv2

比赛记录

2017-11-15

比赛第一天,新的测试数据发布。按照之前的模型跑一版,到0.9933,直接窜到第三名。也不知道可以坚挺多久。

使用大字典的方法跑了一个baseline:0.9893,用作后续提高的对比数据。

2017-11-17

寻找错误大概从两个方向上去找:1.找分类器分错的类别。2.找normalizer没有换对的情况。

分类器分错的类别可以通过分类器输出的分类信心概率来获取。比如使用xgboost中的'objective': 'multi:softprob'参数设定。检查prob值特别小的数据。

通过最终结果去找可以直接使用大字典跑出来的baseline去比较不同,但这个方法也不尽靠谱,不同的不一定错,相同的不一定对。但也可以从中观察,找到一些规律。

还可以直接在最终的结果中去找数字和特殊符号,一般是normalize失败的情况,这个方法可以查出来。

目前是0.9947。

2017-11-20

今天是比赛最后一天,发现之前xgboost的参数设置有问题: 'nthread': -1,直接删掉,因为默认值是最大值。果然重新运行之后cpu的8核满负荷运行。快了很多。

另外把之前的基于context的方法做了一下修正:之前每个token都会包括上一个token和下一个token。那么句子中的最后一个token的context会包含下一句的第一个token,句子的第一个token的context会包含上一句的最后一个token。这显然是不对的。因此在每句之间加入全0向量。忽略该向量本身,只是用于其他向量的context。

最后一天重新check所有类别的rule函数,发现还有不错的可改进空间。

还能继续改的:

  • ORDINAL: 前面有没有the的问题。
  • VERBATIM: # 是hash-tag还是number的问题。

2017-11-21

比赛结束,最终在private上的结果是0.9937,15/260,属于top6%,silver medal。

可以继续改进的是分类器的性能,以后有机会继续做。

其他信息

使用到的第三方包

  • roman:罗马数字和阿拉伯数字的转换。
  • num2words:文字和数字的转换,支持序数的数字。
  • inflect:单词复数形式的转换等。