在“卷积神经网络”中我们探究了如何使用二维卷积神经网络来处理二维图像数据。在之前的语言模型和文本分类任务中,我们将文本数据看作是只有一个维度的时间序列,并很自然地使用循环神经网络来表征这样的数据。其实,我们也可以将文本当作一维图像,从而可以用一维卷积神经网络来捕捉临近词之间的关联。
TextCNN 是利用卷积神经网络对文本进行分类的算法,由 Yoon Kim 在 《 Convolutional Neural Networks for Sentence Classification 》中提出。
TextCNN结构图:
第一层将单词嵌入到低维矢量中。下一层使用多个过滤器大小对嵌入的单词向量执行卷积。例如,一次滑动3,4或5个单词。接下来,将卷积层的结果最大池化为一个长特征向量,添加dropout正则,并使用softmax对结果进行分类。与传统图像的CNN网络相比, textCNN 在网络结构上没有任何变化(甚至更加简单了), 从图中可以看出textCNN 其实只有一层卷积,一层max-pooling, 最后将输出外接softmax 来n分类。
与图像当中CNN的网络相比,textCNN 最大的不同便是在输入数据的不同:图像是二维数据, 图像的卷积核是从左到右, 从上到下进行滑动来进行特征抽取。自然语言是一维数据, 虽然经过word-embedding 生成了二维向量,但是对词向量做从左到右滑动来进行卷积没有意义. 比如 “今天” 对应的向量[0, 0, 0, 0, 1], 按窗口大小为 1* 2 从左到右滑动得到[0,0], [0,0], [0,0], [0, 1]这四个向量, 对应的都是”今天”这个词汇, 这种滑动没有帮助.
TextCNN的成功, 不是网络结构的成功, 而是通过引入已经训练好的词向量来在多个数据集上达到了超越benchmark 的表现,进一步证明了构造更好的embedding, 是提升nlp 各项任务的关键能力。
TextCNN最大优势网络结构简单 ,在模型网络结构如此简单的情况下,通过引入已经训练好的词向量依旧有很不错的效果,在多项数据数据集上超越benchmark。 网络结构简单导致参数数目少, 计算量少, 训练速度快,在单机单卡的v100机器上,训练165万数据, 迭代26万步,半个小时左右可以收敛。
textcnn使用预先训练好的词向量作embedding layer。对于数据集里的所有词,因为每个词都可以表征成一个向量,因此我们可以得到一个嵌入矩阵M, M里的每一行都是词向量。这个M可以是静态(static)的,也就是固定不变。可以是非静态(non-static)的,也就是可以根据反向传播更新。
如图所示, textCNN 首先将 “今天天气很好,出来玩” 分词成”今天/天气/很好/,/出来/玩, 通过word2vec或者GLOV 等embedding 方式将每个词成映射成一个5维(维数可以自己指定)词向量, 如 “今天” -> [0,0,0,0,1], “天气” ->[0,0,0,1,0], “很好” ->[0,0,1,0,0]等等。
这样做的好处主要是将自然语言数值化,方便后续的处理。从这里也可以看出不同的映射方式对最后的结果是会产生巨大的影响, nlp 当中目前最火热的研究方向便是如何将自然语言映射成更好的词向量。我们构建完词向量后,将所有的词向量拼接起来构成一个6*5的二维矩阵,作为最初的输入。
输入一个句子,首先对这个句子进行切词,假设有s个单词。对每个词,跟句嵌入矩阵M, 可以得到词向量。假设词向量一共有d维。那么对于这个句子,便可以得到s行d列的矩阵
。我们可以把矩阵A看成是一幅图像,使用卷积神经网络去提取特征。由于句子中相邻的单词关联性总是很高的,因此可以使用一维卷积。卷积核的宽度就是词向量的维度d,高度是超参数,可以设置。
假设有一个卷积核,是一个宽度为d,高度为h的矩阵w,那么w有h∗d个参数需要被更新。对于一个句子,经过嵌入层之后可以得到矩阵
。
表示A的第i行到第j行,那么卷积操作可以用如下公式表示:
叠加上偏置b,在使用激活函数f激活, 得到所需的特征。公式如下:
对一个卷积核,可以得到特征
, 总共个特征。我们可以使用更多高度不同的卷积核,得到更丰富的特征表达。
卷积是一种数学算子。我们用一个简单的例子来说明一下
feature_map 便是卷积之后的输出, 通过卷积操作 将输入的6*5 矩阵映射成一个 3*1 的矩阵,这个映射过程和特征抽取的结果很像,于是便将最后的输出称作feature map。一般来说在卷积之后会跟一个激活函数,在这里为了简化说明需要,我们将激活函数设置为f(x) = x
在CNN 中常常会提到一个词channel, 图中深红矩阵与浅红矩阵 便构成了两个channel 统称一个卷积核, 从这个图中也可以看出每个channel 不必严格一样, 每个4*5 矩阵与输入矩阵做一次卷积操作得到一个feature map. 在计算机视觉中,由于彩色图像存在 R, G, B 三种颜色, 每个颜色便代表一种channel。根据原论文作者的描述, 一开始引入channel 是希望防止过拟合(通过保证学习到的vectors 不要偏离输入太多)来在小数据集合获得比单channel更好的表现,后来发现其实直接使用正则化效果更好。不过使用多channel 相比与单channel, 每个channel 可以使用不同的word embedding, 比如可以在no-static(梯度可以反向传播) 的channel 来fine tune 词向量,让词向量更加适用于当前的训练。 对于channel在textCNN 是否有用, 从论文的实验结果来看多channels并没有明显提升模型的分类能力, 七个数据集上的五个数据集 单channel 的textCNN 表现都要优于 多channels的textCNN。
我们在这里也介绍一下论文中四个model 的不同:
不同尺寸的卷积核得到的特征(feature map)大小也是不一样的,因此我们对每个feature map使用池化函数,使它们的维度相同。最常用的就是1-max pooling,提取出feature map照片那个的最大值。这样每一个卷积核得到特征就是一个值,对所有卷积核使用1-max pooling,再级联起来,可以得到最终的特征向量,这个特征向量再输入softmax layer做分类。这个地方可以使用drop out防止过拟合。
得到feamap = [1,1,2] 后, 从中选取一个最大值[2] 作为输出, 便是max-pooling。max-pooling 在保持主要特征的情况下, 大大降低了参数的数目, 从图中可以看出 feature map 从 三维变成了一维, 好处有如下两点:
pooling 本身无法带来平移不变性(图片有个字母A, 这个字母A 无论出现在图片的哪个位置, 在CNN的网络中都可以识别出来),卷积核的权值共享才能。max-pooling的原理主要是从多个值中取一个最大值,做不到这一点。cnn 能够做到平移不变性,是因为在滑动卷积核的时候,使用的卷积核权值是保持固定的(权值共享), 假设这个卷积核被训练的就能识别字母A, 当这个卷积核在整张图片上滑动的时候,当然可以把整张图片的A都识别出来。
如图所示, 我们将 max-pooling的结果拼接起来, 送入到softmax当中, 得到各个类别比如 label 为1 的概率以及label 为-1的概率。如果是预测的话,到这里整个textCNN的流程遍结束了。如果是训练的话,此时便会根据预测label以及实际label来计算损失函数, 计算出softmax 函数,max-pooling 函数, 激活函数以及卷积核函数 四个函数当中参数需要更新的梯度, 来依次更新这四个函数中的参数,完成一轮训练。
以上过程可以用下图直观表示:
在自然语言领域,卷积的作用在于利用文字的局部特征。一个词的前后几个词必然和这个词本身相关,这组成该词所代表的词群。词群进而会对段落文字的意思进行影响,决定这个段落到底是正向的还是负向的。对比传统方法,利用词包,和TF-IDF 等,其思想有相通之处。但最大的不同点在于,传统方法是人为构造用于分类的特征,而深度学习中的卷积让神经网络去构造特征。以上便是卷积在自然语言处理中有着广泛应用的原因。接下来介绍如何利用Keras 搭建卷积神经网络来处理情感分析的分类问题。
下面的代码构造了卷积神经网络的结构:
from keras.layers import Dense, Dropout, Activation, Flattenfrom keras.layers import Conv1D, MaxPooling1Dfrom keras.models import Sequentialfrom keras.layers.embeddings import Embeddingfrom keras.datasets import imdbimport numpy as npfrom keras.preprocessing import sequence (X_train, y_train), (X_test, y_test) = imdb.load_data() max_word = 400X_train = sequence.pad_sequences(X_train, maxlen=max_word)X_test = sequence.pad_sequences(X_test, maxlen=max_word)vocab_size = np.max([np.max(X_train[i]) for i in range(X_train.shape[0])]) + 1 # 这里1 代表空格,其索引被认为是0。 model = Sequential()model.add(Embedding(vocab_size, 64, input_length=max_word))model.add(Conv1D(filters=64, kernel_size=3, padding='same', activation='relu'))model.add(MaxPooling1D(pool_size=2))model.add(Dropout(0.25))model.add(Conv1D(filters=128, kernel_size=3, padding='same', activation='relu'))model.add(MaxPooling1D(pool_size=2))model.add(Dropout(0.25))model.add(Flatten())model.add(Dense(64, activation='relu'))model.add(Dense(32, activation='relu'))model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='rmsprop', metrics=['accuracy'])print(model.summary()) model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=20, batch_size=100)scores = model.evaluate(X_test, y_test, verbose=1)print(scores)
整个模型的结构如下:
Model: "sequential_1"_________________________________________________________________Layer (type) Output Shape Param # =================================================================embedding_1 (Embedding) (None, 400, 64) 5669568 _________________________________________________________________conv1d_1 (Conv1D) (None, 400, 64) 12352 _________________________________________________________________max_pooling1d_1 (MaxPooling1 (None, 200, 64) 0 _________________________________________________________________dropout_1 (Dropout) (None, 200, 64) 0 _________________________________________________________________conv1d_2 (Conv1D) (None, 200, 128) 24704 _________________________________________________________________max_pooling1d_2 (MaxPooling1 (None, 100, 128) 0 _________________________________________________________________dropout_2 (Dropout) (None, 100, 128) 0 _________________________________________________________________flatten_1 (Flatten) (None, 12800) 0 _________________________________________________________________dense_1 (Dense) (None, 64) 819264 _________________________________________________________________dense_2 (Dense) (None, 32) 2080 _________________________________________________________________dense_3 (Dense) (None, 1) 33 =================================================================Total params: 6,528,001Trainable params: 6,528,001Non-trainable params: 0_________________________________________________________________
在最简单的仅一层卷积的TextCNN结构中,下面的超参数都对模型表现有影响: