理解自然语言处理中的CNN

本文翻译自Denny Britz的博客《Understanding Convolutional Neural Networks for NLP》,文章对于CNN在NLP领域中的应用讲述深入浅出,引用的很多资料也非常有用。

作者的个人博客:DENNY’S BLOG


每当我们听到卷积神经网络(CNNs),我们最先想到的应该就是计算机视觉。CNNs的应用是图片分类任务取得巨大突破的主要原因,也是目前大多数计算机视觉系统的核心,比如Facebook的照片自动标记以及自动驾驶等领域。

最近以来,CNNs也被应用到许多自然语言处理方面的问题上,并且已经取得了一些很有趣的成果。在这篇博文中,我将尝试概述CNNs到底是什么,以及它在NLP中是如何使用的。CNNs在计算机视觉领域的应用从直觉上来说更加容易理解,所以我也将从计算机视觉开始讲起,并逐渐延伸到自然语言处理中。

卷积是什么?

对我来说最简单的理解卷积的方式是把它想象成作用在一个矩阵上的滑动窗口函数。虽然这样很口语化,但是这种方式很容易清楚的理解它,请看下面的可视化例子: 大小为3x3的卷积核进行的卷积操作,图片来源

可以把左边的矩阵想象为一张黑白图片。矩阵中的每个数字对应一个像素,0代表黑色,1代表白色(在灰度图中,像素的值一般是在0~255之间)。滑动的窗口就叫做卷积核、过滤器或者特征检测器。这里我们用了一个3x3大小的过滤器,把它的值与原始矩阵中的值对应相乘,再求和。为了完成完整的卷积操作,我们通过滑动窗口来对矩阵中的每个元素都做上述操作。

你可能想知道,通过上述的卷积操作,到底做了什么?下面是一些直观的例子:

通过求每个像素与其邻近像素的平均值来对图片进行模糊化处理:

image

通过求每个像素与其邻近像素之间的差值来检测边缘:

这里GIMP manual还有一些例子。为了对CNN的工作机制有更加深入的理解,我推荐阅读这篇博客

卷积神经网络是什么?

现在你已经懂了卷积是什么。但是,卷积神经网络是什么呢?CNNs基本上就是若干层卷积再加上作用在结果上的ReLU或者tanh等非线性激活函数。在传统的前馈神经网络中,我们将输入层的神经元与下一层输出层的神经元挨个连接起来,这样的神经网络也被称为一个全连接层,或者仿射层。在CNN中,我们并不这么做。取而代之的,我们用卷积遍历输入层来获得输出值。这样的操作就是局部链接,即输入的每个区域连接到输出层中的神经元。每一层用不同的卷积核,通常是成百上千个上面提到的卷积操作,并把它们的结果结合起来。另外还有个东西叫做池化层(subsampling),这个后面会讲到。在训练阶段,CNN可以根据具体任务的不同来自动的学习卷积核的值。例如,在图片分类任务中,CNN的第一层可能会学习如何识别边缘,然后利用第一层识别到的边缘特征在第二层中识别简单的形状,然后利用这些简单的形状来探测更加高级的特征,比如说在更高的层中试着探测面部形状。最后一层是一个可以利用前面提取到的特征的分类器。 CNN in CV

这些如何应用于NLP呢?

不同于图片像素,大部分NLP任务的输入是有句子或者文档的矩阵表示。矩阵的每一行对应于一个token,通常是一个单词,但也可以是一个字母。也就是说,每一行是一个单词的向量表示。通常情况,这些向量就是词嵌入(word embeddings)(低维度表示),例如word2vec或者GloVe,但是它们也可以是one-hot向量,将所有的词索引为一个词汇表。例如一个有10个单词的句子,用100维的word embedding,我们就会得到一个10x100的矩阵作为我们的输入。这就是我们的“图像”。

在视觉领域,卷积核依次滑过图片的每一块,但是在NLP中卷积核一般是滑动经过矩阵的整个一行(一些单词),因此,卷积核的宽度通常情况下与输入矩阵的宽度相等。卷积核的高度,或者说区域大小,是可以随意取值的,但是滑动经过2-5个单词的窗口大小是最常使用的。综上所述,一个适用于NLP的卷积神经网络看起来长这个样子(花几分钟时间来理解下面这幅图片,关注一下向量的维度是如何计算的。你可以先不用管池化操作,我们后面会讲到):

CNN in NLP
用于文本分类的CNN的结构图如上所示。这里我们画出了三个不同大小的卷积核:2,3,4,每个大小都有两个卷积核。每个卷积核对句子的矩阵表示进行卷积操作,生成不同大小的特征图(feature map)。然后对每个特征图进行最大池化操作(max pooling),即,只把每个特征图中最大的那个数记录下来。这样的话通过组合6个特征图的池化结果,我们可以得到一个特征向量(通过倒数第二层来结合它们)。最后一层的softmax接收这个特征向量作为输入,据此来对句子进行分类,这里我们假设是一个二分类问题,因此只画出了两个输出结果。 图片来源:Zhang, Y., & Wallace, B. (2015). A Sensitivity Analysis of (and Practitioners’ Guide to) Convolutional Neural Networks for Sentence Classification.


对于上述维度变化的解释:(翻译时增加内容)

首先,输入矩阵的大小为7x5,对于大小为4的卷积核,即覆盖区域为4个单词,卷积核矩阵的维度为4x5,每次卷积操作可以得到特征图中的一个值(对应相乘相加),昨晚第一次卷积操作后,大小为4的卷积核开始滑动,滑动步长默认为1。经过4次滑动可以覆盖整个输入矩阵,所以输出的特征图的维度为4x1。后面对应大小的滑动操作以此类推。特别的,对于输入长度为n,卷积核大小为m的卷积操作,最终输出的特征图大小(滑动次数)为:n - m + 1。

那么,在计算机视觉领域我们那种良好的直觉感受,现在还适用吗?位置不变性和局部构图性对图像具有直观意义,但对于NLP而言并非如此。你可能会非常关心句子中某一个词出现的位置。位置上相邻的像素点在语义上通常也是有联系的(共同组成图像中某个物体的一部分),但是对于单词来说这是不一定的。在很多种语言中,一个短语的各部分可能会被很多的其他单词分隔开。在组成成分方面,单词表现得也不是那么明显。可以确定的是,单词确实有固定的搭配方式,比如形容词通常用于修饰名词,但是这样的搭配多大程度上是正确的,或者说卷积操作提取物出的更高级别的表示到底是什么意思,表现得并不像在计算机视觉中那么的明显。

了解了这些以后,似乎对于NLP任务来说,CNN并不是一个很好的方案。RNN似乎更加直观一些。它处理语言的过程跟我们人类类似(或者说至少是我们认为的我们处理语言的方式):从左到右按顺序读一遍。幸运的是,这并不意味着CNN就真的没用。 All models are wrong, but some are useful.事实证明,CNN在解决NLP问题方面表现十分良好。最简单的Bag of Words 模型基于错误的假设,显得太简单了。但是尽管如此,它也曾是一种NLP的标准方法,并取得了很好的效果。

CNN的一个得天独厚的优势是速度快。非常的快。卷积是计算机图形的核心部分,在GPU上的硬件级别上实现。对比n-gram,CNN在表示方面也是很有效的。在词汇表很大的前提下,计算超过3-gram的表示会造成巨大的成本。甚至Google也没有提供超过5-gram的任何东西。卷积核能够自动的学习词的很好的表示,并且不需要表示出整个词汇表。使用大于5的卷积核是很常见的。我认为第一层的卷积核获取到的特征跟n-gram很像(但是并不止这些),但是却能够用一种更加紧凑的方式表达出来。

CNN的超参数

在介绍CNN在NLP中具体是如何工作之前,我们先看一下你在构建自己的CNN之前必须要做出的一些选择。希望这将有助于你理解该领域内的一些文献

窄VS宽的卷积

在上面解释卷积操作的过程中我忽略了一个细节,就是我们是怎样放置卷积核的(在数据上)。将一个3x3的卷积核放在图片的中心位置能够,它会很好的进行工作。但是边缘位置怎么办呢?你怎么样把卷积操作用在矩阵的第一个元素呢?因为它的左边跟上边并没有相邻的元素。

你可以使用zero-padding。所有处在矩阵外部的元素我们都把它初始化为0。这样做了以后,你就可以在输入矩阵的任意位置进行卷积操作了,并获得更大或相同大小的输出。加入zero-padding也被称作 wide convolution,没有加入zero-padding被称为 narrow convolution。下面是一个1维的例子: Narrow vs. Wide convolution Narrow vs. Wide convolution.卷积核大小为5,输入大小为7.来源:A Convolutional Neural Network for Modelling Sentences (2014)

当你的卷积核大小相较于输入大小来说比较大的时候,通过上图你就可以看出为什么说宽的卷积是有用的,甚至说是必要的。窄的卷积输出大小为(7-5)+ 1 = 3,宽的卷积的输出为(7 + 2 * 4 - 5)+ 1 = 11。更加一般化的来说,输出大小的公式为 \(n_{out} = (n_{in} + 2 * n_{padding} - n_{filter}) + 1\)

滑动步长(stride size)

另一个卷积神经网络中的参数就是滑动步长,定义了每一次窗口滑动的长度。在上面所有的例子中,滑动步长均为1,并且卷积核的连续应用是相互重叠的。较大的步长导致的结果就是卷积操作的次数变少,相应的输出的大小也会变小。下面的图展示了滑动步长分别为1和2的情况下,在1维输入上进行卷积操作的情况,图片来自Stanford cs231 website

Convolution Stride Size. 卷积操作的滑动步长大小,左边为1,右边为2.来源: http://cs231n.github.io/convolutional-networks/

在文献中,我们经常见到的滑动步长为1,但是更大的步长下的CNN在一些方面的表现很像RNN。即,看起来像一棵树。  

池化层

卷积神经网络中很重要的一点就是池化层,最具代表性的就是在进行卷积操作以后紧接着进行池化操作。池化层对于输入到它们的数据进行子采样。最常见的池化操作是对每个卷积核的输出进行$a_{max}$操作(取最大值)。不需要对整个矩阵进行取最大值操作,也可以使用滑动窗口的方式进行。例如,下面展示了窗口大小为2x2的最大池化操作(在NLP中,最具代表性的池化操作是对整个输出进行池化,不用滑动窗口,对于每个卷积核的输出最终只保留一个数字):

Max pooling in CNN CNN中的最大池化操作。来源:http://cs231n.github.io/convolutional-networks/#pool

为什么需要池化呢?有这样几个原因。池化操作的一个性质是它能够提供一个固定大小的输出矩阵,这对于分类来说是必须的。例如,你有1000个卷积核,然后对每个卷积核的结果进行池化操作,最终都会得到一个1000维的输出,无论你的卷积核大小是多少,输入的大小又是多少。这样的话你就能够用不同长度的句子,使用不同大小的卷积核,但最后总是能够得到一个想同纬度的输出作为分类器的输入。

池化还有一个作用就是在减少输出的维度的情况下,尽可能地保留了最显著的信息。你可以将不同的卷积核想象成它在检测不同的特征,比如说检测句子中是否包含否定词,例如“not amazing”这样的。如果这样的短语在句子中的某个位置出现了,那个区域进行卷积计算之后的输出结果会很大,但是其他区域的输出值就很小了。通过最大池化的操作,就可以保留这个词是否在句子中出现过这样的信息,但是却丢失了它具体出现在什么位置的信息。但是关于位置的信息真的是没用的吗?是的,是没用的而且它跟n-gram词袋模型有些相似。丢失了关于位置的全局信息(在句子中一些事情发生在什么地方),但是却保留下来了卷积核获取到的本地信息,像“not amazing”与“amazing not”是十分不同的。

在图片识别领域,池化也保证了平移以及旋转变换过程中的不变性。当你对一个区域进行池化操作的时候,对图片进行几个像素的平移或者旋转,结果会几乎保持不变,因为取最大值的操作还是会获得同样的值。

通道

最后一个我们需要理解的概念就是通道。通道其实就是从不同的视角来看输入的数据。比如,在图片识别领域常用的有R,G,B通道(红、绿、蓝)。你可以针对不同的通道使用相同或者不同的权重进行卷积运算。在NLP中,你也可以想象它有不同的通道:可以针对不同的word embedding设置不同的通道(比如word2vecGloVe),或者你也可以针对同一句话在不同种类语言下的表示方法设立不同的通道,或者针对不同的表达方式设立不同的通道。

将卷积神经网络用在自然语言处理中

接下来我们来看一些卷积神经网络在自然语言处理中的具体应用。我会试着来总结一些研究的成果。我可能会一些很有趣的应用,但是我会尽力讲到一些比较流行的成果。

对于CNN来说,最自然而然的应用场景似乎就是分类问题。比如情感分析,垃圾邮件检测或者是主题分类。卷积和池化的操作是的句子中单词的顺序信息丢失了,所以对于序列标注问题,比如词性标注或者实体抽取,用一个单纯的CNN网络架构是有点难处理的(虽然并非不可能,但是你也可以将位置信息直接添加到输入当中)。

[1]中在不同的分类数据集上对一个CNN架构的模型给出了评价,数据集中更多的是情感分析与主题分类的问题。CNN结构的模型在这些数据集上取得了很好的效果,并且在一些数据集上取得了新的 state-of-the-art 的好成绩。出人意料的是,这篇文献中之用的模型是非常简单的,这也是它显得很强的一方面原因。输入层是由连接的word2vec字嵌入组成的句子,接下来是一层拥有很多个卷积核的卷积层,然后是一个最大池化层,最后面接一个softmax分类器。文献中还通过静态词嵌入与动态词嵌入两个通道做了实验,也就是说一个通道在训练过程中是动态调整的,另外一个则是固定下来的。之前在[2]中提出了一种类似但更复杂的架构。[6]中添加了一个对该网络体系结构执行“语义聚类”的附加层。

Kim, Y. (2014). Convolutional Neural Networks for Sentence Classification Kim, Y. (2014). Convolutional Neural Networks for Sentence Classification

[4]从零开始训练CNN,完全没有用到任何像word2vec或者GloVe这样的与训练词向量。它直接对 one-hot 向量进行卷积操作,作者也提出了节省空间的词袋式输入表示方法,很大程度的减少了模型需要学习的参数。在[5]中,作者扩展了模型,增加了一个无监督的“区域嵌入”,是通过使用CNN预测文本区域的上下文来学习的。这些文章中的方法似乎针对长文本的效果要好一些(比如影评),但是它们在短文本上的效果(比如微博)就不是很清楚了。但是从直觉上来说,对短文本的处理过程运用预训练的向量会比长文本中效果更好。

如果你要设计一个CNN模型,那就意味着有很多的超参数需要去设置,其中有一些我已经在前面提到过了:输入表示方法(word2vec,GloVe,one-hot)、卷积核的大小与数量、池化的方法(取最大值、取平均值)、激活函数(ReLU、tanh)。[7]对CNN架构中不同超参数的影响进行了实证评估,研究了它们在多次运行中对性能和方差的影响。如果你要自己构造一个用于文本分类的CNN模型的话,使用这篇文献中的结果作为一个出发点会是一个很棒的主意。还有一些实验结果正面最大池化总是要比平均池化更好,理想大小的卷积核很重要,但是也要取决于具体的任务,在NLP任务中,正则化似乎并不会产生很大的影响。但是这个结果的一个警告是,这样的规则仅仅适用于数据集中文章的长度几乎一致的情形之下,所以在数据完全不一致的情形下,这样的结果可能并不成立。

[8]探究了CNN在关系抽取以及关系分类任务中的应用。除了词向量以外,作者还使用了单词与感兴趣的实体的相对位置作为卷积层的输入。这个模型假设了实体的位置是已知的,并且每个示例输入包含一个关系。[9][10]中提出的模型与这个类似。

另一个CNN在NLP领域有意思的应用是在[11]和[12]中,出自微软的研究成果。这篇论文描述了如何学习具有语义意义的句子的表示,用来进行信息检索任务。论文中给出的示例包括基于他们当前正在阅读的内容向用户推荐可能感兴趣的文章。句子的表示是基于搜索引擎的日志进行训练的。

大多数的CNN模型都把词与句子嵌入的学习过程作为训练的一部分,并非所有论文都关注训练的这一方面或调查学习嵌入的意义。[13]提出了一种CNN架构来预测Facebook帖子的主题标签。同时为单词与句子生成有意义的嵌入。这样得到的embeddings成功的运用到了另一项任务中—向用户推荐可能感兴趣的文章,并根据用户的点击流进行训练。

字母级别的CNN

目前为止,前面提到的CNN都是基于单词的。但是同样有直接基于字母来做的CNN:[14]学习了字母级别的embeddings,并将它与预训练的word embedding结合起来,然后用一个CNN来做部分语音标记。[15][16]探究了不用任何预训练的向量,直接使用CNN基于字母来进行学习。特别的是,作者用了一个相对较深的神经网络结构,总共有9层,把它用在了情感分析和文本分类任务中。结果显示基于字母级别的CNN在比较大的数据集上(百万级)表现异常出色。但是在比较小的数据集(100或者1000数量级)上,相较于简单的模型表现就不是很好。[17]探究了基于字母级别的CNN模型在语言建模中的应用。使用字母级别CNN的输出作为LSTM每个时间步的输入。这个模型也被用于其他种类的语言中。

最令人激动的是,上面提到的所有文章实际上都是最近1-2年发布的。显然,CNN在NLP领域的应用之前就非常出色,例如 Natural Language Processing (almost) from Scratch,但是新成果的提出速度以及新的最先进的系统的提出速度都在很明显的加快。

参考文献


文章版权归原作者所有