自上次做了阿里的比赛之后似乎有点不过瘾,趁巧发现群里在宣传百度与 cikm 合作的这个比赛,于是 又来打酱油。之前的比赛搞的有点混乱,这次想把思路捋顺清楚。就用这个新的 blog 把一些关键的东西 记下来吧。
首先说一下大体的比赛任务:对给出的搜索词 (query) 做分类。百度对一些搜索词做了人工标注,然后给 出了这些数据在 session 中的查询以及点击的页面标题。然后要对另一批标记为 TEST 的数据做标注。 而在 session 中出现的并没有标记的 query 被标记为 UNKNONW。
大体思路是首先处理已经有标记的数据,取标记数据的 query, title 的前 N 个字符然后标记这个 prefix 的标签为当前的 label。如果出现了同一个 prefix 有多个标签的情况,就把这个 prefix 的标签设置成有最多标记次数的标签。
然后读取 test 数据,同样取 test query 的前 N 个字符字符,然后在之前的 query-label 对中查看这个 prefix 是否存在,如果 存在就设置 prefix 为 label,否则就设置成 OTHER。
这个 baseline 可以得到大概 34% 的结果。
如果说上面的那个似乎有点低了,那么这个我刚刚想到的 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%。
以上是一些比较基本的提交,为的是大概了解数据的情况以及提交数据的样子。后面就是一些模型的尝试了。
做文本的分类,肯定会想到用 bag of word 抽取特征,然后用一些模型去做分类了。我一开始也是这个思路,迅速的用 sklearn 的 TfidfVectorizer 对标记了的 query + title 做了 BOW 然后用 NaiveBayes 去预测结果。 用一元文法、二元文法、三元文法做了尝试之后发现三元的效果比较好,就用三元的提交了一个结果,线上的结果是 80% 了呢,感觉还算靠谱。然后又尝试了 LogisticRegression 以及 LinearSVC 的模型,结果也有所提升。
然后...似乎就不知道要做什么了...跑去群里和别人讨论了一下,发现如果仅仅使用 query 而不用 title 的数据反而会有所提升。于是我也用了这个方法,最后达到目前的最高分 84.61% ...
目前的数据是加了密的,每个 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 数据可不可以做一些额外的处理来降噪后加以利用呢?
之前就考虑过说把 title 考虑进去,却导致了结果的下降,这次在考虑 word 的同时,把 title 添加了进去。而且,取得是 title 的前 N 个字符,这样就可以过滤掉后面那些噪音了。
当然,在生成测试集的标签的时候同样是把 test data 的 title 考虑在内去处理。然后目前的得分是 87.39%
。
这...都说懒是没得救的,我差不多了。这 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 了。
LinearSVC 或者说是 liblinear
然后,没了...就是这样,LinearSVC 没怎么调参,随便改个参数都没有默认的好...最后 leaderboard 上 89.91