整理学习笔记也是把知识系统化的过程。 最近在研究Yoon Kim的一篇经典之作Convolutional Neural Networks for Sentence Classification,这篇文章可以说是cnn模型用于文本分类的开山之作(其实第一个用的不是他,但是Kim提出了几个variants,并有详细的调参)
整个模型的构造如下图所示:
这里 xi∈Rk 是在句子中第i个词的k尺寸词向量。一个句子的长度是n(如果有必要需要对句子做填充处理)表示如下:
X1:n=X1⨁X2⨁……⨁Xn(1)
卷积操作中的滤波器大小 w∈Rhk ,这个滤波器被用做h个单词上进而产生一个新的特征。例如,一个特征 ci 的产生是从一个窗口大小的单词 Xi:i+h−1 中通过式2产生:
ci=f(W⋅Xi:i+h−1+b)(2) 滤波器会被应用在一个句子中每一个可能窗口里的单词 {X1:h,X2:h+1,……,Xn−h+1:n} ,产生一个特征map: C=[C1,C2,……,Cn−h+1] (3)这里特征map: C∈Rn−h+1 。在特征map出来之后我们这里有一个maxpool操作,只提取特征图里最大的特征。
为了解决过拟合的问题,我们采用正则化的方法,在倒数第二层我们使用dropout机制,并且使用L2范数来约束权重向量。Dropout机制可以防止隐藏层的过拟合,通过随机的droppiing out:例如在反向传播的过程中将p部分的隐藏单元设置为0。即,已知,倒数第二层的特征向量z=[C1,……,Cm] (表示这里我们有m个滤波器)。经典的情况神经元的输出应该是:
y=w⋅z+b(4)
是在前向传播过程中,使用了dropout的时候输出单元变成:
y=w⋅(z∘r)+b(5)
这里 ∘ 表示按元素逐个相乘操作。这里 r∈Rm 是掩模向量,这个掩模是通过概率为p的伯努利随机变量为1。
若伯努利试验成功,则伯努利随机变量取1;若伯努利试验失败,则伯努利随机变量取0。记其成功概率为p (0≤p≤1) ,失败概率为q=1-p。 概率质量函数为:
fx(x)=px(1−p)1−x=⎧⎩⎨p,1−p,0,x=1x=0otherwise(6)梯度仅仅在unmasked上的单元进行反向传播,所谓的被masked的就是在倒数第二层隐藏单元中在dropout过程中被置为0的单元。在测试的时候,在前面训练的时候学习到的权重向量已经按p的比例改变了W的大小,新的 w^=pw , w^ (这里不在使用dropout)用来对未曾使用过的句子进行评分。我们额外通过L2范数来限制权重向量,通过重新衡量w的大小,通过 ∥w∥2=s ,无论什么时候 ∥w∥2>s 在一个梯度下降步骤之后。 Dropout注意事项:正则是解决过拟合的问题,在最后一层softmax的时候是full-connected layer,因此容易产生过拟合。 策略就是: * 在训练阶段,对max-pooling layer的输出实行一些dropout,以概率p激活,激活的部分传递给softmax层。 * 在测试阶段,w已经学好了,但是不能直接用于unseen sentences,要乘以p之后再用,这个阶段没有dropout了全部输出给softmax层。
由神经元 i 连接到神经元j的突触权值的校正值 Δwji(n) 定义如下:
(权值矫正Δwji(n))=(学习率参数η)×(局部梯度δj(n))×(神经元j的输入信号yi(n)) * 如果神经元 j 是一个输出层节点,δj(n)=ej(n)φ′j(vj(n)),输出神经元j还是用了非线性激活函数。要不然没有倒数求? * 如果神经元 j 是一个隐藏层节点,δj(n)=φ′j(vj(n))∑kδk(n)wkj(n),我们使用可公开获得的word2vec词向量,这个词向量是通过训练1000亿个来自谷歌新闻的单词而获得的。
我们在几个变种模型上进行实验。
CNN-RAND: 我们的模型是所有的单词都随机的初始化然后在训练的时候进行调整。CNN-static: 一个预训练向量来自于word2vec的模型。所有的单词(包括未知的单词也都随机的初始化)都保持固定,模型仅仅学习其它参数。CNN-non-static: 除了预训练词向量在每个任务中经过调整其它的都和CNN-static模型一样。CNN-multichannel: 一个具有两个词向量集的模型。每一个向量集被认为是一个通道,类似图像的channel,每一个滤波器同时作用于两个通道,但是梯度在反向传播的过程中仅仅作用于一个通道。因此这个模型能够更好的调整一个通道的词向量而保持另一个通道的词向量。两个通道都被初始化为word2vec。各种模型在各种数据集里的表现如下图:
我们最初希望多通道的设计能够组织过拟合的发生(通过确保学习到的向量不会偏离真实向量太远),而且通过实验结果我们可以看到多通道的结果比单通道的结果要好,特别是在小的数据集上。
positive_examples = 5331行,一行代表一条文本,一个句子,即一条数据。 negative_examples = 5331行
x_text = positive_examples + negative_examples = 10662行 max_document_length = 56 最长的句子有56个单词 x = 10662行,将每个单词转化为单词序号,最先出现的单词序号为1,然后,以此类推第二个出现的单词序号为2。没有单词填充的地方序号为0。
这个class的主要作用是什么?TextCNN类搭建了一个最basic的CNN模型,有input layer,convolutional layer,max-pooling layer和最后输出的softmax layer。 但是又因为整个模型是用于文本的(而非CNN的传统处理对象:图像),因此在cnn的操作上相对应地做了一些小调整: * 对于文本任务,输入层自然使用了word embedding来做input data representation。 * 接下来是卷积层,大家在图像处理中经常看到的卷积核都是正方形的,比如4*4,然后在整张image上沿宽和高逐步移动进行卷积操作。但是nlp中输入的“image”是一个词矩阵,比如n个words,每个word用200维的vector表示的话,这个”image”就是n*200的矩阵,卷积核只在高度上滑动,在宽度上和word vector的维度一致(=200),也就是说每次窗口滑动过的位置都是完整的单词,不会将几个单词的一部分“vector”进行卷积,这也保证了word作为语言中最小粒度的合理性。(当然,如果研究的粒度是character-level而不是word-level,需要另外的方式处理) * 由于卷积核和word embedding的宽度一致,一个卷积核对于一个sentence,卷积后得到的结果是一个vector, shape=(sentence_len - filter_window + 1, 1),那么,在max-pooling后得到的就是一个scalar。所以,这点也是和图像卷积的不同之处,需要注意一下。 * 正是由于max-pooling后只是得到一个scalar,在nlp中,会实施多个filter_window_size(比如3,4,5个words的宽度分别作为卷积的窗口大小),每个window_size又有num_filters个(比如64个)卷积核。一个卷积核得到的只是一个scalar太孤单了,智慧的人们就将相同window_size卷积出来的num_filter个scalar组合在一起,组成这个window_size下的feature_vector。 * 最后再将所有window_size下的feature_vector也组合成一个single vector,作为最后一层softmax的输入。 重要的事情说三遍:一个卷积核对于一个句子,convolution后得到的是一个vector;max-pooling后,得到的是一个scalar。
各参数的大小如下: sequecce_length:56 num_classes:2 vocab_size:18758 embedding_size:128 filter_sizes:[3,4,5] num_filters:128 l2_reg_labbda:0W存储全部word vector的矩阵,W初始化时是随机random出来的,也就是paper中的第一种模型CNN-rand。训练过程中并不是每次都会使用全部的vocabulary,而只是产生一个batch(batch中都是sentence,每个sentence标记了出现哪些word(最大长度为sequence_length),因此batch相当于一个二维列表),这个batch就是input_x。
self.input_x = tf.placeholder(tf.int32,[None,sequence_length],name="input_x")tf.nn.embedding_lookup:查找input_x中所有word的ids,获取它们的word vector。batch中的每个sentence的每个word都要查找。所以得到的embedded_chars的shape=[None, sequence_length, embedding_size] (1) 但是,输入的word vectors得到之后,下一步就是输入到卷积层,用到tf.nn.conv2d函数,再看看conv2d的参数列表: input: [batch, in_height, in_width, in_channels](2) filter: [filter_height, filter_width, in_channels, out_channels](3) 对比(1)(2)可以发现,就差一个in_channels了,而最simple的版本也就只有1通道(Yoon的第四个模型用到了multichannel) 因此需要expand dim来适应conv2d的input要求,万能的tensorflow已经提供了这样的功能: 因此只需要:
self.embedded_chars_expanded = tf.expand_dims(self.embedded_chars, -1)就能在embedded_chars后面加一个in_channels=1
conv-maxpool-3
embedded_chars_expanded = [?,56,128,1] w = [3,128,1,128] b= [128] conv = tf.nn.conv2d(embedded_chars_expanded,w,padding='VALID')=[?,54,1,128] h = tf.nn.relu(tf.nn.bias_add(conv, b))=[?,54,1,128] pooled = tf.nn.max_pool( h, ksize=[1, 56 - 3 + 1, 1, 1], strides=[1, 1, 1, 1], padding='VALID')=[?,1,1,128]对于其余卷积滤波器大小情形类似。
前文提过文章中的输出: y=w⋅(z∘r)+b 因此,我们也按照这种方式写代码,得到所有类别的score,并且选出最大值的那个类别(argmax),y的shape为[batch, num_classes],因此argmax的时候是选取每行的max,dimention=1,因此,最后scores的shape为[batch, 1]。
这部分的作用不太懂
图中蓝色代表训练集,红色代表验证集! train/dev:9662/1000