前几天在网上看到了一个blog关于大规模文本分类的内容,在这里转发保存一下。
大规模文本分类实践-知乎看山杯总结
原文地址:http://coderskychen.cn/2017/08/20/zhihucup/
本文主要介绍了我在知乎看山杯机器学习挑战赛中的一些实验和总结,代码已公开,传送门。
阅读本篇大约需要10分钟。 尊重原创,转载请注明出处。
先晒一发排名,9th,有小遗憾,但是通过这个比赛把深度学习搞文本分类这一套东西走了一遍,还是有收获的。
赛题简述
一句话概括:对知乎上的问题进行自动的话题标注,标注个数5个。
参赛者需要根据知乎给出的问题及话题标签的绑定关系的训练数据,训练出对未标注数据自动标注的模型。
标注数据中包含 300 万个问题,每个问题有 1 个或多个标签,共计1999 个标签。每个标签对应知乎上的一个「话题」,话题之间存在父子关系,并通过父子关系组织成一张有向无环图(DAG)。
由于涉及到用户隐私及数据安全等问题,本次比赛不提供问题、话题描述的原始文本,而是使用字符编号及切词后的词语编号来表示文本信息。同时,鉴于词向量技术在自然语言处理领域的广泛应用,比赛还提供字符级别的 embedding 向量和词语级别的 embedding 向量,这些 embedding 向量利用知乎上的海量文本语料,使用 google word2vec 训练得到。
除了对原始文本进行大小写转换、全半角转换及去除一些特殊字符(如 emoji 表情、不可见字符)等处理之外,训练数据和预测数据都没有经过任何清洗。
- 对于一个问题有四部分的描述:字符级的标题(titlechar),词级的标题(titleword),字符级的描述(dspchar),词级的描述(dspword)
- 训练集300w的有标注问题,测试集20w的无标注问题,需要我们的模型给这些问题打5个标签
- 验证集未给出,需要自己划分,我在实验过程中使用了简单的hold-out方法,从300w训练集中留出了10%的数据作为验证集。
- 1999个label不是互相独立的
- 数据集下载及其说明,请移步至:。。。
不同网络结构的理解与回顾
整体来说,我们主要把这个比赛看成了一个分类问题,即1999个类的分类问题。
我尝试过从问答匹配的角度来做,即:topic看做是question对应的答案,因为topic出了自身的label以外还有一个自身的描述(因此也是一段话)。我认为从问答匹配的角度来看待此问题是更合适的,因为这样可以把topic的描述信息利用上,而分类的角度来做是很难利用这个信息的。但是问答匹配的话面临两个问题:
- 第一个是正负样例的采样,对于一个问题来说,正样例即为其相应的topic label,而负样例为:剩余其他的label(1999-n),负样例过多需要采取措施。我实验过降采样,即一个question扩展为20个样本,正样本为n那么负样本从
1999-n
中抽取20-k
个 - 第二个是训练和预测的时间复杂度太高。训练的话相当于原训练集的k倍(
k=20
),而预测的过程更加耗时,如果不采用粗过滤的方式,那么必须对1999个topic都进行预测得分,才能选出top5,计算量增加了1999倍。很可怕了。
所以最终我只是简单跑了一个小demo,没有从问答匹配的角度做本次比赛。
测试过的一些常见元素的组合,比如,TextCNN,VDNN(very deep cnn 17年的论文),LSTM,C-LSTM,双向LSTM,双向LSTM+原始的词向量(三元组),RCNN,attention机制,结构化的attention,多通道trick以及多任务loss(融合LDA主题信息)。
最终结果,我的单模型最高线上0.411分(RCNN+ATTENTION),是我们队的state-of-the-art,与另外两个队友的结果融合之后A榜0.4229
,其中模型融合大概能长1个百分点,这里的融合指的是评分矩阵的加权和。
TextCNN
该方法是kim在2014年提出的,非常经典的CNN做文本分类的模型。(Convolutional Neural Networks for Sentence Classification )
- 卷积核的宽度等于词向量的维度,这样做是有意义的,因为一个完成的词向量代表一个词。而卷积核的高度是人为设定的,一般在1~7之间,这样卷积的含义类似于n-gram的效果,这点不同于图像中的卷积核的尺寸,因为图像是有局部相关性的。
- 每一个卷积核卷积完后会得到一个m*1的一维feature map,对feature map进行max pooling,这样卷积核的个数就决定了pooling之后的维度,因此顺便也解决了不同文本长度带来的维度不一致问题,当然本身这个问题也可以通过padding来解决。
- 最后通过一个dropout+fc层使用softmax输出结果完成分类任务。
- 该论文中,做了一些对比试验,比较了是否使用预先训练的w2v来初始化embedding层,以及是否使用多通道,即:一个通道使用静态w2v,另一个使用可训练的w2v。
VDNN
该方法是Yann Le Cun的学生在17年提出来的。(Very Deep Convolutional Networks for Text Classification)
主要思想:
- 虽然一个只有单隐含层的神经网络从理论上有能力学习任何函数,但是实践证明,针对特定问题设计的深层网络能够有效的学习结构化的表征
- 深层的CNN在计算机视觉取得了好成绩,是因为图像可以由一些底层特征组合出来(点动成线,线动成面),而文本也有类似的结构:字符可以组合成n-grams,stems,words,phrase,sentences
文中提到,TextCNN的一个缺点是需要人工指定卷积核的尺寸,而这个超参数对结果的影响很大,而VDCNN就可以通过深层的结构自动学习到n-gram的组合。
从结构图中可以看到,他们借鉴了残差网络的shortcut机制,但是这个机制是一个可选项,有时候结果并不好。
他们设计网络时遵循的原则:
- 输出分辨率相同时,feature map的个数也相同
- 当分辨率减半时,feature map加倍(变厚)
个人感觉没有太新奇的地方。。。难道是人家调参调的好?反正我自己实验的时候结果不如TextCNN,可能是我调参太差劲了。
C-LSTM
该方法来源于15年的一篇论文。(A C-LSTM Neural Network for Text Classification)
主要思想:既然CNN 可以获取文本的语义特征,那么何不将这些语义信息经过整理使其依然具有时序结构,然后再送入到LSTM中呢?
- 一个重点是reshape feature map,使其保留原始文本的时序,这样送入到LSTM才有意义。
- 一种filter size对应一个LSTM单元,因为不同的filter size产生不同尺寸的feature map,而feature map的维度决定了LSTM的步数(step)
RCNN
论文:Recurrent Convolutional Neural Networks for Text Classification
开始我对这个方法的理解有一定的误区,主要是对论文中的循环结构理解有误,应该仔细看一下公式(1)、(2)。它有一个操作,把左右语境的文本移动一位,然后循环的计算左右语义。
我在实现时与原始论文是有改动的,使用了双向LSTM来获取某一个位置的左右语义信息,这样再加上本身的embedding,就组成了一个三元组,即每一个词都可以用这样的一个三元组来表示。
然后再根据论文的公式4,使用一个dense层tanh激活函数,来把这样的三元组映射到一个指定维度,最后采取纵向的maxpooling来获取一句话的表示。使用RCNN的一个感觉是,纵向的maxpooling效果竟然很好。
attention机制
我理解的attention机制,就是一个加权和,然后其中的权重是通过学习得到的。keras实现很简单。
记得有一篇HAN的论文,使用结构化的attention来做document的分类,即:单词之间的attention+句子之间的attention。
多通道trick
两个通道相当于增加了一倍的计算量和参数量,两个embedding层,一个可以训练,一个固定死。从实验看来不一定会有提升,需要具体情况具体分析。
multi-loss
借鉴了17年的video的一篇论文。(CVPR2017)Improving Interpretability of Deep Neural Networks with Semantic Information
首先训练一个LDA模型,获得每个文档的topic分布向量,这样相当于增加了有监督的信息。然后在原有任务的loss基础上,再加一个输出层来学习LDA主题分布。相当于多输出多loss。
得分最高的单模型:RCNN+ATTENTION
模型结构图
- 四个输入:titlechar,titleword,dspchar,dspword,四个embedding层,都可以训练
- 使用了双向LSTM,最后用
LSTM_left:embedding:LSTM_right
这样的三元组形式来表示一个词的信息,好处是可以比较好的捕捉到当前词的上下文语义。 - 使用纵向的maxpooling把k三元组向量规约到一个一维向量上,用以表达一个句子的语义信息。
- titlechar和titleword直接连接,dspchar和dspword直接连接,这样连接之后的两组向量分别表示了title和dsp的语义信息。
- 最后将question represention输入到一个fc层(期望该层能学习到label之间的依赖关系),然后再连接dropout+fc层做一个softmax获取各个类别上的得分分布。
- title和dsp语义之间的attention
为什么要title和dsp之间进行attention,而不是四部分都attention呢?
我个人的出发点是:word和char能够从不同的级别来表示语义,所以char和word之间使用concat的方式直接拼起来,这样比attention加权和能保留更有多的信息。同时,title还有的信息比dsp更有用(单个实验过,单个dsp得分很惨,也符合常理),所以使用attention机制加权和一下,万一有用呢?233
总结与反思
- 最后一层激活函数,sigmoid和softmax相比训练速度变慢,sigmoid貌似更适合multi-label,但是我们一直使用的是softmax,在multi-label问题中,我觉得softmax会有一点问题:过于突出向量中的某一个值。
- lstm units从256到512会带来提升
- 通过一个dense层压缩embedding维度,会使结果变差
- dsp部分最长的有上万个字符,受限于GPU显存,我的截断设置为:300,大一点可能会好
- 在我的实验中,LSTM比GRU结果好,而GRU训练速度快,参数相对少。
- batchnorm层在有attention的时候放置的位置需要更多的实验,没来及测试BN层
- 学习率特别重要,我开始训练的模型lr都有一点偏大
- 一个队友说,我们的一个问题是:模型更迭的速度远大于模型调参的尝试,还是应该多花一些精力调参。。
- 最后模型融合的时候,融合的权重是我们根据单模型得分人为的指定的,更好的方法是学习出这些权重参数,还是没时间做了…
- 我一直对引入LDA的multi-loss抱有很大希望,但是在tw单个通道上测试了一下,结果不如单个loss的,然后也就没有继续深究,可能是LDA训练的不好?loss加权设定的不好?
- 借鉴残差网络的思想,我应该尝试一下跨层连接,队友貌似有测试