• Text-CNN出自《 Convolutional Neural Networks for Sentence Classification》这篇经典论文,由New York University的Yoon Kim大佬发表,作为文本分类的必入坑之一,论文整体简洁明了,本文就来窥视一波,这个经典的网络结构。
  • 本文依据原论文,不加任何多余trick。

整体论文初识

  • 整篇论文做到了什么?
  • a simple CNN with little hyperparameter tuning and static vectors achieves excellent results
  • Learning task-specific vectors through fine-tuning offers further gains in performance
  • The CNN models discussed herein improve upon the state of the art on 4 out of 7 tasks, which include sentiment analysis and question classification。
  • 好了,我们说人话,其实就是论文提出的模型,以训练好的词向量 + CNN的方式在多个领域表现都不错,包括情感分析和问题分类的问题上。
  • 在这里我们需要注意的一点是,这个训练好的词向量可以是静态的,也可以是动态的,恩哼?什么意思,总得来说就是你想不想:你预先训练好的词向量随着(下游)分类任务的优化进行微调,不想,静态,其实就是常数,想,动态,就是变量。论文说基于下游任务微调过得向量表现更佳。
  • OK, 到这里我们来圈一下重点,我们假设有了预选练好的词向量,我们将目光放在:CNN的结构上。
  • 关于CNN的结构效果的讨论:
  • we train a simple CNN with one layer of convolution on top of word vectors obtained from an unsupervised neural language model.
  • We initially keep the word vectors static and learn only the other parameters of the model.
  • Despite little tuning of hyperparameters, this simple model achieves excellent results on multiple benchmarks, suggesting that the pre-trained vectors are ‘universal’ feature extractors that can be utilized for various classification tasks.
  • 上面解释起来就是:作者提出一个很简单的模型,虽然超参数很少,而且即使我们使用预训练的静态的词向量,也有不错的效果。
  • Learning task-specific vectors through fine-tuning results in further improvements
  • We finally describe a simple modification to the architecture to allow for the use of both pre-trained and
    task-specific vectors by having multiple channels.
  • 相当于上面的进阶,同时使用预训练的静态词向量和允许微调的动态词向量,做成多通道的输入输入模型。

模型

  • 先放上原论文的图:
  • 文本分类 情感分析 cnn rnn textcnn情感分类_文本分类 情感分析 cnn rnn

  • 输入层:双channel,一个是预训练好的词向量(设为静态),另一个和它一样(但是设为动态)。所以输入层一个句子样本的Size为[ n,k,2 ],考虑bach_size,则为[ bach_size,n,k,2 ],其中n为最大句长度(所以很明显,长了要截掉,端了要填充),k为词向量的维度(你自己训练时设置的维度,常为2^m)
  • 卷积层:也叫filter,我们先看超参数,h:filter_size(卷积大小,注意,这里和图片的区别是,它的横向维度和词向量维度一样,见图,所以卷积只在竖直方向移动)。m:卷积的个数(体现了模型的宽度)。
  • 其他超参数:stride:步长 padding:填充方式,为了和论文公式相同,我们这里步长设置为1,padding为VALID(其实由于步长是1,无所谓了)
  • 规定了超参数,我们来算一下,原一个batch的样本,现在的维度是什么样的?我jio的我放个论文上的公式比较正统:

文本分类 情感分析 cnn rnn textcnn情感分类_词向量_02

  • 其实就是卷积运算,可视化的效果可以看图中的连线,我们这里给出数值,filter_size分别为[3,4,5],每一个都有100个,所以?这里放出整个的过程:
  • 原本:[ bach_size,n,k,2 ],经过[3,k,100]的卷积操作,变为:[ bach_size,n - 3 + 1, 1, 100 ],就是中间那样的竖条条,但是有100维度。
  • 原本:[ bach_size, n, k,2 ],经过[ 4, k, 100 ]的卷积操作,变为:[ bach_size,n - 4 + 1, 1, 100 ]
  • 原本:[ bach_size, n, k,2 ],经过[ 5, k, 100 ]的卷积操作,变为:[ bach_size,n - 5 + 1, 1, 100 ]
  • 池化层:最大值池化max-overtime pooling
  • (优点:<1>capture the most important feature—one with the highest value—for each feature map
  • <2> This pooling scheme naturally deals with variable sentence lengths,我理解为是可以解决0填充句子定长的问题
  • <3>不同尺寸的卷积核得到的特征(feature map)大小也是不一样的,因此我们对每个feature map使用池化函数,使它们的维度相同)
  • 经过池化层的维度:
  • 原本:A: [ bach_size,n - 3 + 1, 1, 100 ],变为:[bach_size, 1,1,100]
  • 原本:B: [ bach_size,n - 4 + 1, 1, 100 ],变为:[bach_size, 1,1,100]
  • 原本:C: [ bach_size,n - 5 + 1, 1, 100 ],变为:[bach_size, 1,1,100]
  • 然后:These features form the penultimate layer and are passed to a fully connected softmax layer whose output is the probability distribution over labels.
  1. 用代码解释一下,我们需要把上面的三个卷积结果合起来,用: D = tf.concat( [A,B,C] , 3 ),然后维度就变为了: [ batch_size,1,1,300]
  2. 接着,tf.reshape( D , [ -1, num_filters_total ] ),其中 num_filters_total = 100 * len([ 3, 4, 5 ] ) = 100 * 3 = 300,维度变成了:[ batch_size, 300 ]
  3. 对这一层做一个dropout
  4. 接着对输出做:tf.matmul( self.final_output,fc_w) + fc_b,fc_w的维度为[ 300 , class( 分类类别 ) ]
  5. 至此,基本就没了,后就是softmax啥的。

数据

  1. 词向量:自己写的word2vec跑的,但是也用了fasttext的100万预训练词向量的数据。
  2. train:value = 8:2,数据是网上找的,总共才1万多条,后面实验时一直有解决不了的过拟和现象,即train数据准确率0.9了,value一直在0.6左右。
  3. 词库:用的自己找的数据训练wor2vec时提取的词库(去过低频词,当然你也可以替换成一个特殊的符号,或者应用其他处理低频词/OOV的方法)。

预处理

  1. 第一个就是python编码问题,尤其是ubuntu读取windows的notepad文件时…一言难尽,它前面会有一个看不见的BOM,所以你唯一需要做的,就是 errors=‘ignore’。
  2. 数字和标点符号怎么处理?本人用特殊字符替换。
  3. 低频词怎么处理? 去掉?特殊字符填充?其他处理低频词的方法:bpe?subwords?需要你自己斟酌。
  4. 英文单词缩进问题。
  5. 大小写,同义词缩写替换,html标签等等,需要根据数据实际作出处理。
  6. 每一个句子的结尾,开头加一个特殊字符,(选做)
  7. 想清楚上面的顺序应该如何排列,特殊词替换后,又用符号处理把特殊标记给去了,那就太“棒”了。

正则化

  • 这里就不细细解释了,反正我的数据过拟和了,所有东西都上了,还是没用,所以这里留个坑,个人用到的方法有:
  1. 早停
  2. l2正则
  3. dropout
  4. 玄学调参…(今天刚知道有一个叫贝叶斯优化的东西,可以抽空学一下)

注意

  1. 你要考虑这样一个问题,你的句子需要填充(或者截断),如果以0填充,那么你必须保证,你的look_up embedding表的第0个的vetor是无意义的,就是全0。我最开始的时候没注意,我的0对应的是一个单词,那经过look_up,原本无意义的填充全部变成了有意义的填充,一个小细节。
  2. 我的词向量是在train语聊上训练的,用的自己参考word2vec源代码训练,但是当我用别人预训练好的词向量时,发现了一个有趣的现象,该现象不严谨,参考即可:同样参数下(这里是双通道):
  • 使用自己在train上训练的embedding:
    total_train_acc is 0.890625
    total_value_acc is 0.5418685793755335
  • 使用外部数据的情况:
    total_train_acc is 0.9440104166666666
    total_value_acc is 0.5796875928236701

注意:结果是所有结果求平均后的参考结果。

结论: 要么是我自己训练的embedding质量不高,毕竟数据样本就1万条,要么就是在train的样本上做embedding,再用这个embedding训练模型会造成一定的过拟和,使用预训练的样本更有价值(当然,第二个结论是我瞎扯,我个人偏向于embedding质量不高,所以我推荐你们使用网上预训练好的词向量)

  1. 本人最开始也尝试了单通道(动态)的情况,为了有对比价值,这里就直接用外部数据的embedding:
    total_train_acc is 0.9429347826086957
    total_value_acc is 0.578694744679852

注意:这也是平均值

最后:我觉得现在你应该知道怎么提高质量了吧,当然如果你有处理过拟和问题的思路,请赐教,我是无解了。

更新

  • 为了上面的过拟和问题的尝试,在github上看到一个点,这里尝试一下。
  • 对embedding层后面加一个droput(好残暴的感觉…)(这里是在上面双通道的基础上测试)
  • 测试结果,新加的dropout丢弃率为0.5,这里没有调参,大致知道这一层的效果是什么样了,但是感觉这一层好玄学。
    total_train_acc is 0.7629573170731707
    total_value_acc is 0.5367982606693403
    所以,玄学也救不了我…可能我丢弃率太高了,但是dropout常理来说是卷积完以后加在全连接层的,这个加法也是很奇怪。

代码实现

  • 个人由于自己实验着玩比较乱,github上一堆,我就不羡丑了。