【NLP competition】中文信息学会 文本溯源技术评测(SMP ETST)Ranking First

此次的文本溯源项目我们以n-gram为核心思想,构建候选句子对的评测标准。
用TF-IDF和词袋模型的的思想来预筛选候选句子对,大大提升算法效率。
最后用了两种切词方式的模型融合和规则后处理(提升很小)。
Final-Leaderboard Ranking First


2018.07.24更新
经过组委会测试算法的高效性和原创性,我们最终获得了SMP-ETSE测评的第一名。
最终获奖名单
评测任务介绍
测评代码开源


以前没有参加过NLP类型的比赛和测评,每天看论文想idea的日子有些许的枯燥和单调。
就和一个同学趁着期末考试复习期间(实力作死)的空闲时间,抽出时间玩了玩这个比赛。
SMP 2018 测评地址

题目分析

本次的文本溯源题目和同期的另外两个比赛其实很相似。
蚂蚁金服计算句子相似度
拍拍贷识别相似的问题句子
但文本溯源又和另外两个题有着本质的不同,因为主办方放出的数据并没有任何标签。很明显,我们需要一个无监督的算法来找到句子之间的潜在语义关系,红的发紫的深度学习在此没有用武之地。
恐怕这也是为什么这个测评的参加人数如此之少的主要原因吧。

  • 验证集
    • 待溯源句子1000
    • 候选句子约10W
  • 测试集
    • 待溯源句子4000
    • 候选句子约500W

思路一

根据我们之前的一些经验,探讨两个句子之前的相似性的时候,n-gram就是一个简单,并且行之有效的方法。这也是我们最早的想法。

思路二

另一个想法,传统的NLP parsing技术在理论上会比简单的n-gram 方法更好的分析句子结构,从而帮助计算机理解句子语义,找到对应的句子。
但是,整个代码构建工程量大,实现难度较高。

我们的当然要从简单的第一种思路入手尝试。(也是我们最后采用的方案)

预处理

按照思路一构建一个简单的baseline,在没有调参和仔细预处理之前,在验证集上取得了0.8737 的成绩,这也给了我们继续这一方法的信心。

随后我们反过头来仔细进行预处理。

  • 处理影响分词效果的杂乱字符
    • 直接删除
    • 将他们替换为空格
  • 对于数据进行全半角格式转换(计算机并不认为相同字符的全半角格式是一样的字符)

符号预处理之后,在验证集获得了0.9008的表现 ,
然后又轻微调参(根据不等式,我们知道,P、R相等的时候,F值表现会是最好的),验证集表现上涨到了0.9087

切词

在预处理方面,切词是最令人头疼的第一个地方。
我们有了很多开源的切词工具,最终选了用了thulacjieba两种。

我们发现这两种切词方法的本身都有一定的局限性,但是又有一定的互补性。
虽然用两种切词工具在时间上会对花费很多,但是为了更好的算法效果,我们采用两种切词方式(后来想想好像有点亏,这样的处理大约只能带来一个点的提升,但花费了大量的时间)。

python 代码运行效率的低效众所周知,对于测试集500W句子的数据量来说,可谓是十分头疼。

解决方式也很简单,直接调用两种切词方法的c++接口就好了。就可以体验飞一般的速度。

值得注意的是,thulac 的切词包在大数据量的情况下,会有崩溃的情况,原因未知。我们的处理方式是将500W数据切分成了四份,调用四次thulac的c++接口(多了三次的model载入时间),切词后再将所有数据合并起来。

TF-IDF

n-gram 方法固然简单高效,但也很容易想到一个缺陷。我们不应该对于所有的gram“一视同仁”!

所以我们建立了TF-IDF,根据每个词语在文档中的几个句子中出现,设立TF。
并且设立反向的IDF,为了不同频次的词语反向加权。

这使我们的验证集F值表现达到了0.9356

优化目标

对于深度学习,大家都知道。当我们的loss和最后的评价指标越相近的时候,模型的训练效果也往往是更好的。

一个简单的n-gram 通常是仅仅考虑精确率,我们为什么不进一步的考虑召回率呢?甚至直接对于F值进行优化?
这里有一篇ACL2014的文章作为参考

这样的优化操作之后,我们的验证集表现达到了0.9430

倒排索引(词袋模型)

以上主要是在算法精度上的优化,并且没有可以的优化算法效率。导致我们在每一次对齐的时候都要对10W数据做遍历比较……

为了使算法快点出结果,我们用了多进程的方法,但这显然不是长久之计。

经过观察发现。10W句子中的绝大多数句子,算法评价两个句子的相关性,都极低,对于我们的溯源任务造不成任何干扰,那么如何去掉他们呢?

不难想到,我们的算法基于n-gram,而对齐表现差的句子显然和目标句子重合的n-gram非常少。那么,我们为什么不讲这样的句子直接过滤掉呢?可以减少句子对的评价次数几百倍,大大提升算法运行效率!

具体的,我们首先用one gram对句子进行过滤(候选句子中连一个词语都没有出现在目标句子中,全部去掉不考虑);进一步的,还可以对 two gram 设定一定的阈值。让我们在算法精度和运行速度上进行权衡。

最终,我们算法经过此处理后。在10W验证集上的表现,从单进程500min缩短为了40s 以下,还没有损失精度!

模型融合 & 后处理

以上提到了两种切词方式,以及两种预处理方式。为了取得更好的算法效果。我们从它们排列组合后的四种方法中挑取了两个互补性较强的model进行模型融合。
验证集达到了0.9549的数值水准。

然后我们肉眼观察一些阈值附近的“疑似”对齐错误的句子。建立几个规则性的后处理操作。
验证集达到了0.96036的数值水准。

最终测试集结果

  1. 硬件环境

    • Intel(R) Xeon(R) CPU E5-2697 v4 @ 2.30GHz
    • 内存 188 GB
    • Linux version 3.10.0-514.el7.x86_64 ,
    • gcc version 4.8.5 20150623 (Red Hat 4.8.5-11)
    • Python 3.4.5 ,numpy
  2. 运行时间参考

    • 预处理时间共 1267秒 (21.1分钟)
      • 符号处理 & 编码格式转换:218秒
      • THULAC分词(c++版本):272秒
      • jieba分词(c++版本):112秒
      • 计算TF-IDF 164秒
      • 建立倒排索引表 501秒
    • 核心算法:115秒 (1.9分钟)
  3. 最后数值表现

    • F1-Measure : 0.801258108905052
    • Precision: 0.7133356667833392
    • Recall : 0.9139013452914798
    • Ranking: First

尾声

虽然时间紧迫,经验不足。在比赛中和队友都有一些失误,但最终侥幸排名第一。

  • 做的不够好的地方:

    • 预处理不够精细
    • 切词处理没有去增加一个字典
    • 懒得去寻找同义词源,或者训练一个词语级别的翻译模型。理论上可以进一步提高算法表现。
    • 由于留给测试集出结果的时间只有24h,事先准备的代码不够充分。
    • 在测试集阈值设定的时候陷入了思维误区,算法最终的F值结果损失精度百分之三以上。
  • 做得比较好的地方:

    • 基本的n-gram 思路简单而高效。
    • TF-IDF使算法的评价方式更加合理。
    • 倒排索引是使算法高效,简洁。
    • 艰苦卓绝的后处理和肉眼调参……

最后:感谢队友这些天的付出,也感谢努力的自己。

-------------本文结束 感谢您的时间-------------
0%