Eisen's Blog

© 2024. All rights reserved.

CIKM query detection

August 13, 2014

mlcikmpythonsklearn

自上次做了阿里的比赛之后似乎有点不过瘾,趁巧发现群里在宣传百度与 cikm 合作的这个比赛,于是 又来打酱油。之前的比赛搞的有点混乱,这次想把思路捋顺清楚。就用这个新的 blog 把一些关键的东西 记下来吧。

Task

首先说一下大体的比赛任务:对给出的搜索词 (query) 做分类。百度对一些搜索词做了人工标注,然后给 出了这些数据在 session 中的查询以及点击的页面标题。然后要对另一批标记为 TEST 的数据做标注。 而在 session 中出现的并没有标记的 query 被标记为 UNKNONW。

Baselines

naive baseline 1

大体思路是首先处理已经有标记的数据,取标记数据的 query, title 的前 N 个字符然后标记这个 prefix 的标签为当前的 label。如果出现了同一个 prefix 有多个标签的情况,就把这个 prefix 的标签设置成有最多标记次数的标签。

然后读取 test 数据,同样取 test query 的前 N 个字符字符,然后在之前的 query-label 对中查看这个 prefix 是否存在,如果 存在就设置 prefix 为 label,否则就设置成 OTHER。

这个 baseline 可以得到大概 34% 的结果。

naive baseline 2

如果说上面的那个似乎有点低了,那么这个我刚刚想到的 baseline 就靠谱多了。

仔细观察数据会发现这样子的 session:

CLASS=TEST	0417191 0750813 0396059	-
CLASS=LOTTERY	0417191 0750813 0396059 0040630 0030792 0079323 0387331	-
CLASS=LOTTERY	0417191 0750813 0396059 0040630 0030792 0079323 0387331	0557667 0208773 0631387 0949083 0040630 0030792 0750813 0396059 0490159 0293898 0514247 0754212 0208773 0631387 0949083 0040630 0030792 0750813 0396059 0297573 0040630 0030792 0750813 0396059 0293898 0998010 0359806 0040630 0030792 0580929
CLASS=LOTTERY	0417191 0750813 0396059 0040630 0030792 0079323 0387331	0557667 0208773 0631387 0949083 0040630 0030792 0750813 0396059 0490159 0293898 0514247 0754212 0208773 0631387 0949083 0040630 0030792 0750813 0396059 0297573 0040630 0030792 0750813 0396059 0293898 0998010 0359806 0040630 0030792 0580929
CLASS=LOTTERY	0417191 0750813 0396059 0040630 0030792 0079323 0387331	0949083 0750813 0396059 0040630 0030792 0079323 0387331 1116997 0040630 0030792 0079323 0387331 0750813 0396059 0729226 1081042 0754212 0631387 0976685 0694263
CLASS=LOTTERY	0417191 0750813 0396059 0040630 0030792 0079323 0387331	0949083 0750813 0396059 0438139 0396059 1081042 0729226 0784491 0208773 0631387 0949083 0750813 0396059 0438139 0396059 1081042 0729226 0846596 0750813 0396059 1081042 0729226 0846596 0438139 0396059 1081042 0729226 0297573 0781505 0878719

标记为 TEST 的与有标记的数据出现在了同一个 session 之中,然后我觉得在同一个 session 中的 query 的动机应该是极其类似的。 那么我就做一个非常粗略的判定:同一个 session 中的 label 应该是一样的。那么,我们就可以把既有 TEST 标签又有标定的数据的 session 中的 query 全部设置为这个已标定的标签。然后我发现在 session 中出现 label 的情况还是挺多的,经过以上规则的标定,我可以找出 21000+ 个这样的 test query 的标签, 然后把剩下的 query 标记为出现最多的 VIDEO。这样的线上提交结果达到了 67%。


以上是一些比较基本的提交,为的是大概了解数据的情况以及提交数据的样子。后面就是一些模型的尝试了。

Ngram

做文本的分类,肯定会想到用 bag of word 抽取特征,然后用一些模型去做分类了。我一开始也是这个思路,迅速的用 sklearn 的 TfidfVectorizer 对标记了的 query + title 做了 BOW 然后用 NaiveBayes 去预测结果。 用一元文法、二元文法、三元文法做了尝试之后发现三元的效果比较好,就用三元的提交了一个结果,线上的结果是 80% 了呢,感觉还算靠谱。然后又尝试了 LogisticRegression 以及 LinearSVC 的模型,结果也有所提升。

然后...似乎就不知道要做什么了...跑去群里和别人讨论了一下,发现如果仅仅使用 query 而不用 title 的数据反而会有所提升。于是我也用了这个方法,最后达到目前的最高分 84.61% ...

Word

目前的数据是加了密的,每个 string 对应一个中文的汉字或者是一个英文的词,那么前面的 Ngram 还是比较粗糙的,看了之前百度的比赛,获胜选手都是要首先做分词来着。 虽然这里加了密,但是依然可以尝试一些无词典的分词方式进行分词的。我这里参见了 matrix67 的一篇旧文,首先从目前的数据生成出一个词典来,然后再用 mmseg 做分词。 我本来以为这是一个很不错的主意,但是在实现的过程过程中发现这个生成词典的计算量非常的大...程序中有一部分是计算一个词的子词构成这个词的概率,内存和 cpu 都有点吃不消,在尝试计算前三千万数据失败之后我只好使用了计算前一千万数据的结果做为词典。然后把分词之后的 bag of word 与之前的 3ngram bow 组合计算了一个新的 svm 的模型,结果...结果降了 T_T。这尼玛是为什么。难道分词太差劲了么?而且,事实上,我自己本地测试的数据是提升了几个百分点的,虽然提升的不多,但是总不至于降低吧... 我甚至有点怀疑,目前的线上测试集因为是全体测试数据的一个子集,所以还是不能太信它的,过拟合了的话就没有意义了。

不过真的是郁闷了...

然后就开始考虑各种情况了,比如那么多的 UNKNOWN 数据都没有使用啊,要怎么使用才对吧?但是要怎么使用呢,因为同一个 session 下的数据标定为同一个 label 的结果只有 67% 啊, 也就是说我不能随便判断这个 UNKNOWN 真实的标签是什么才对呢...所以说,可以考虑 如何对 test 与 labeled 在同一个 session 的情况下做标定 着手,为对 UNKNOWN 数据的标定提供参考。

然后就是 title 的数据如何利用?看起来 title 会引入很多的噪声,那么 title 数据可不可以做一些额外的处理来降噪后加以利用呢?

Add title and query together

之前就考虑过说把 title 考虑进去,却导致了结果的下降,这次在考虑 word 的同时,把 title 添加了进去。而且,取得是 title 的前 N 个字符,这样就可以过滤掉后面那些噪音了。 当然,在生成测试集的标签的时候同样是把 test data 的 title 考虑在内去处理。然后目前的得分是 87.39%


2014-10-01 结束后的总结

这...都说懒是没得救的,我差不多了。这 blog 上次写还是 8 月份。

今天按理说是比赛结束了吧?可是我发现怎么还有人在提交到 leaderboard 呢...百度的同学们放假了是吧?既然知道要放假你们为毛要把结束时间设置到 10 月 1 号呢。

好了,不废话了。说一说我自己的最好成绩是怎么做的吧...其实我也是小白,半路出家,找个类库瞎试,有什么不对的地方请指出。

我这个比赛主要就是在使用 scikit-learn 做训练的基础库。抽特征,训模型都是靠的它。后面说到的一些内容我尽量不涉及具体的库,不过还是要宣传一下,我感觉它还是很不错的呢。

数据预处理

原始数据是按照 session 组织的,既然我们要做的是 query detection,我首先是把相同 query 的数据集合在一起。大概的形式如下

label   query   text
1       121     121 123 124 ...

其中 text 也要包含 query 的内容,然后 text 中没有重复的 query 以及 title。并且剔除了 CLASS=UNKNOWN 的数据。

按照这样处理后,训练数据就只有 79M 了。

特征

  1. 3gram
  2. 做了分词之后的 2gram 分词见上文
  3. prefix 比如 text 为 "1 2 3 4 5" 那么,生成的 prefix 就是 "1", "1 2", "1 2 3"...
  4. postfix 比如 text 为 "1 2 3 4 5" 那么,生成的 postfix 就是 "5", "4 5", "3 4 5"...

模型

LinearSVC 或者说是 liblinear

然后,没了...就是这样,LinearSVC 没怎么调参,随便改个参数都没有默认的好...最后 leaderboard 上 89.91

总结

  1. sklearn 是个好东西,里面的 model 算是挺全的,然后 gridsearch 做调参很爽
  2. 我没有用其他的工具,这个其实并不好,yr_SYSU 推荐了 vowpal wabbit 以后有机会去试试
  3. unknown 根本没有使用,本来我是有尝试的,比如对有 label 的临近的 unknown 标定为相同的 label 但是这样反而导致结果的下降
  4. 尝试了词向量,也是下降了,悲剧的
  5. 分词其实提升也很有限...就一点点,看群里说分词是必须的,可我其实觉得这加过密的数据做分词都不知道效果,有的时候加了都不如不加吧
  6. 没做模型融合