前言
卷积神经网络(CNN)在图像处理领域取得了很大的成绩,它的卷积和池化结构能很好提取图像的信息,而在 NLP 领域循环神经网络(RNN)则使用的更多,RNN 及其各种变种因拥有记忆功能使得它们更擅长处理上下文。但 NLP 领域很多方面使用 CNN 取得了出色的效果,比如语义分析、查询检索、文本分类等任务。这篇文章看看如何用 CNN 进行文本分类。
模型结构
模型结构可以通过下图一层层来看,总共由4部分组成,包括了输入层、卷积层、池化层和全连接层。
输入层
图中最左边的部分即为输入层,总的来说输入层就是句子对应的矩阵。一般不会使用 ont-hot 向量来表示单词,而是使用 k 维的分布式词向量。那么对于一个长度为 n 的句子,则构成一个 n × k 的矩阵。
所以,可以设 为句子的第 i 个单词,它为 k 维向量。那么一个句子为 ,其中表示串联的意思。
另外,根据对词向量的作用可以分为两种模式:静态和非静态。静态模式的意思就是我直接使用第三方发布的词向量或者自己训练的词向量来初始化矩阵,并且在每次训练的过程中不对反向误差传播产生作用,不改变词向量,整个训练过程词向量都是固定不变的。而非静态模式则不同,同样是使用词向量来初始化矩阵后,在此后的每次训练过程中,根据反向误差传播会对词向量进行微调,整个训练过程词向量都会更新。
卷积层
图中第二部分为卷积层,卷积层的作用就是用于提取句子的特征。主要是通过一个 h × k 的卷积核 w 在输入层从上到下进行滑动进行卷积操作,通过该卷积操作得到一个 feature map。feature map 的列为1,行为 (n-h+1),即 ,其中 。
上图中输入层上红色框框就是卷积操作的卷积核,可以看到它是 2 × k 维的,运算后变为 feature map 的一个元素。除此之外,还可以将 h 定为3,此时卷积核变为 3 × k 维,如图中黄色框框部分。相同维度的可以有若干个参数不同的卷积核,所以最终在每种维度下都可以得到若干个 feature map。
卷积操作的意义是什么?可以看到它其实是根据 h 大小不同提取不同长度相邻单词的特征,这个其实可以跟 n-gram 语言模型对应起来。
池化层
图中第三部分为池化层,池化层的作用是对特征做进一步提取,将最重要的特征提取出来。这里使用的是 max-over-time pooling 操作,即取出 feature map 中的最大值作为最重要的特征,即。所以最终对于每个 feature map 池化后都得到一个一维向量,取最大值作为特征也解决了不同句子长短的问题,尽管短的句子会用 0 进行填充,但通过取最大值消除了该问题。
前面的通过卷积层的多个不同卷积核操作得到若干 feature map,而再经过池化层处理后得到若干个一维向量。
全连接层
图中最后部分为全连接层,全连接层通过使用 softmax 分类器得到各个分类的概率。前面的池化层的输出以全连接的形式连到 softmax 层,softmax 层定义好分类。
防止过拟合
为了防止过拟合,训练过程中在倒数第二层使用 dropout 技术,它将随机丢弃隐含层的某些节点使其不 work。具体做法可以在网络向前传输时将一些节点设置为0,比如倒数第二层上,,这里假设我们有 m 个卷积核。通过 dropout 后为,,其中和 r 实现了掩码的功能,即 r 是大小与 z 相同的一个向量,它的值为随机的0或1,0对应的节点为丢弃的节点。
同时,还可以在全连接层使用L2正则化来约束权值向量w。
主要实现代码
构建图
首先,构建需要的占位符和常量,其中占位符包括输入占位符、标签占位符和 dropout 占位符,L2 正则损失常量。
train_inputs = tf.placeholder(tf.int32, [None, sequence_length])train_labels = tf.placeholder(tf.float32, [None, classes_num])keep_prob = tf.placeholder(tf.float32)l2_loss = tf.constant(0.0)复制代码
接着我们会需要一个嵌入层将词汇嵌入到指定的维度空间上,维度由 embedding_size 指定。同时 vocabulary_size 为词汇大小,这样就可以将所有单词都映射到指定的维数空间上。嵌入层通过tf.nn.embedding_lookup
就能找到输入对应的词空间向量了,这里稍微解释下embedding_lookup
操作,它会从词汇中取到 inputs 每个元素对应的词向量,inputs 为2维的话,通过该操作后变为3维,因为已经将词用 embedding_size 维向量表示了。此外,由于要调用卷积操作,这里将结果扩展了一维。
with tf.device('/cpu:0'): embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0)) embed = tf.nn.embedding_lookup(embeddings, train_inputs) conv_inputs = tf.expand_dims(embed, -1)复制代码
接着开始做卷积操作及池化操作。由于我们会定义若干个卷积核,而且每个高度的都有若干个卷积核,所以我们会得到很多不同的 feature map,然后对这些 feature map 做 max-over-time pooling 操作,最终得到池化后的 feature。
features_pooled = []for filter_height, filter_num in zip(filters_height, filter_num_per_height): conv_filter = tf.Variable(tf.truncated_normal([filter_height, embedding_size, 1, filter_num], stddev=0.1)) conv = tf.nn.conv2d(conv_inputs, conv_filter, strides=[1, 1, 1, 1], padding="VALID") bias = tf.Variable(tf.constant(0.1, shape=[filter_num])) feature_map = tf.nn.relu(tf.nn.bias_add(conv, bias)) feature_pooled = tf.nn.max_pool(feature_map, ksize=[1, sequence_length - filter_height + 1, 1, 1], strides=[1, 1, 1, 1], padding='VALID') features_pooled.append(feature_pooled)复制代码
现在网络就剩下全连接层了,其中要先进行 dropout 操作来暂时使得一些节点失效,接着做线性计算得到分数,从而得到预测。
filter_num_total = sum(filter_num_per_height)features_pooled_flat = tf.reshape(tf.concat(features_pooled, 3), [-1, filter_num_total])features_pooled_flat_drop = tf.nn.dropout(features_pooled_flat, keep_prob)W = tf.get_variable("W", shape=[filter_num_total, classes_num], initializer=tf.contrib.layers.xavier_initializer())b = tf.Variable(tf.constant(0.1, shape=[classes_num]))scores = tf.nn.xw_plus_b(features_pooled_flat_drop, W, b)复制代码
最后计算损失,一个是L2正则损失一个是交叉熵损失,通过两者求得总的损失。并且计算得到准确率。
l2_loss += tf.nn.l2_loss(W)l2_loss += tf.nn.l2_loss(b)losses = tf.nn.softmax_cross_entropy_with_logits(logits=scores, labels=train_labels)loss = tf.reduce_mean(losses) + l2_lambda * l2_losspredictions = tf.argmax(scores, 1)correct_predictions = tf.equal(predictions, tf.argmax(train_labels, 1))accuracy = tf.reduce_mean(tf.cast(correct_predictions, "float"))复制代码
github
github附上完整代码。
https://github.com/sea-boat/nlp_lab/tree/master/cnn_text_classify
reference
Convolutional Neural Networks for Sentence Classification
-------------推荐阅读------------
------------------广告时间----------------
知识星球:远洋号
公众号的菜单已分为“分布式”、“机器学习”、“深度学习”、“NLP”、“Java深度”、“Java并发核心”、“JDK源码”、“Tomcat内核”等,可能有一款适合你的胃口。
欢迎关注: