第7章 自然语言处理基础

7.7 Pytorch实现词性判别

我们知道每一个词都有词性,如train这个单词,可表示火车或训练等意思,具体表示为哪种词性,跟这个词所处的环境或上下文密切相关。要根据上下文来确定词性,正是循环网络擅长的事,因循环网络,尤其是LSTM或GRU网络,具有记忆功能。
这节将使用LSTM网络实现词性判别。

7.7.1 词性判别主要步骤

如何用LSTM对一句话里的各词进行词性标注?需要采用哪些步骤?这些问题就是这节将涉及的问题。用LSTM实现词性标注,我们可以采用以下步骤:
(1)实现词的向量化
假设有两个句子,作为训练数据,这两个句子的每个单词都已标好词性。当然我们不能直接把这两个语句直接输入LSTM模型,输入前需要把每个语句的单词向量化。假设这个句子共有5个单词,通过单词向量化后,就可得到序列[V_1, V_2, V_3, V_4, V_5],其中V_i表示第i个单词对应的向量。如何实现词的向量化?我们可以直接利用nn.Embedding层即可。当然在使用该层之前,需要把每句话对应单词或词性用整数表示。
(2)构建网络
词向量化之后,需要构建一个网络来训练,可以构建一个只有三层的网络,第一层为词嵌入层,第二层为LSTM层,最后一层用于词性分类的全连接层。
以下用Pytorch实现这些步骤。

7.7.2 数据预处理

(1)定义语句及词性
训练数据有两个语句,定义好每个词对应的词性。测试数据为一句话,没有指定词性。

(2)构建每个单词的索引字典
把每个单词用一个整数表示,将它们放在一个字典里。词性也如此。

手工设置词性的索引字典。
tag_to_ix = {"DET": 0, "NN": 1, "V": 2}

7.7.3 构建网络

构建训练网络,共三层,分别为嵌入层、LSTM层、全连接层。

其中有一个nn.Embedding(vocab_size, embed_dim)类,它是Module类的子类,这里它接受最重要的两个初始化参数:词汇量大小,每个词汇向量表示的向量维度。Embedding类返回的是一个形状为[每句词个数,词维度]的矩阵。nn.LSTM层的输入形状为(序列长度,批量大小,输入的大小),序列长度就是时间步序列长度,这个长度是可变的。F.log_softmax()执行的是一个Softmax回归的对数。
把数据转换为模型要求的格式,即把输入数据需要转换为torch.LongTensor张量。

7.7.4 训练网络

(1)定义几个超参数、实例化模型,选择损失函数、优化器等

(2)简单运行一次

['The', 'cat', 'ate', 'the', 'fish']
tensor([0, 1, 2, 3, 4])
tensor([[-1.4376, -0.9836, -0.9453],
[-1.4421, -0.9714, -0.9545],
[-1.4725, -0.8993, -1.0112],
[-1.4655, -0.9178, -0.9953],
[-1.4631, -0.9221, -0.9921]], grad_fn=)
(tensor([-0.9453, -0.9545, -0.8993, -0.9178, -0.9221], grad_fn=),
tensor([2, 2, 1, 1, 1]))
显然,这个结果不很理想。而下面我们循环多次训练该模型,精度将大大提升。
(3)训练模型

['The', 'cat', 'ate', 'the', 'fish']
tensor([[-4.9405e-02, -6.8691e+00, -3.0541e+00],
[-9.7177e+00, -7.2770e-03, -4.9350e+00],
[-3.0174e+00, -4.4508e+00, -6.2511e-02],
[-1.6383e-02, -1.0208e+01, -4.1219e+00],
[-9.7806e+00, -8.2493e-04, -7.1716e+00]], grad_fn=)
(tensor([-0.0494, -0.0073, -0.0625, -0.0164, -0.0008], grad_fn=),
tensor([0, 1, 2, 0, 1]))
这个精度为100%

7.7.5 测试模型

这里我们用另一句话,来测试这个模型

['They', 'ate', 'the', 'fish']
tensor([5, 2, 3, 4])
tensor([[-7.6594e+00, -5.2700e-03, -5.3424e+00],
[-2.6831e+00, -5.2537e+00, -7.6429e-02],
[-1.4973e-02, -1.0440e+01, -4.2110e+00],
[-9.7853e+00, -8.3971e-04, -7.1522e+00]], grad_fn=)
(tensor([-0.0053, -0.0764, -0.0150, -0.0008], grad_fn=),
tensor([1, 2, 0, 1]))
测试精度达到100%

7.8 用LSTM预测股票行情

这里采用沪深300指数数据,时间跨度为2010-10-10至今,选择每天最高价格。假设当天最高价依赖当天的前n(如30)天的沪深300的最高价。用LSTM模型来捕捉最高价的时序信息,通过训练模型,使之学会用前n天的最高价,判断当天的最高价(作为训练的标签值)。

7.8.1 导入数据

这里使用tushare来下载沪深300指数数据。可以用pip 安装tushare。

7.8.2 数据概览

(1)查看下载数据的字段、统计信息等。

图7-15 沪深300指数统计信息
从图7-15可知,共有2295条数据。
(2)可视化最高价数据

图7-16 可视化最高价

7.8.3 预处理数据

(1)生成训练数据

(2)规范化数据
#对数据进行预处理,规范化及转换为Tensor
df_numpy = np.array(df)

df_numpy_mean = np.mean(df_numpy)
df_numpy_std = np.std(df_numpy)

df_numpy = (df_numpy - df_numpy_mean) / df_numpy_std
df_tensor = torch.Tensor(df_numpy)

trainset = mytrainset(df_tensor)
trainloader = DataLoader(trainset, batch_size=batch_size, shuffle=False)

7.8.4 定义模型

这里使用LSTM网络,LSTM输出到一个全连接层。

7.8.5 训练模型

图7-17 batch-size=20的损失值变化情况
图7-17为batch-size=20时,损失值与迭代次数之间的关系,开始时振幅有点大,后面逐渐趋于平稳。如果batch-size变小,振幅可能更大。

7.8.6 测试模型

(1)使用测试数据,验证模型

(2)查看预测数据与源数据

图7-18 放大后预测数据与源数据比较
从图7-18 来看,预测结果还是不错的。

第6章 视觉处理基础

传统神经网络层之间都采用全连接方式,这种连接方式,如果层数较多,输入又是高维数据,其参数数量可能是一个天文数字。比如训练一张1000*1000像素的灰色图片,输入节点数就是1000*1000,如果隐含层节点是100,那么输入层到隐含层间的权重矩阵就是 1000000*100!如果还要增加隐含层,还要进行反向传播,那结果可想而知。这还不是全部,采用全连接方式还容易导致过拟合。
因此,为更有效处理像图片、视频、音频、自然语言等大数据,必须另辟蹊径。经过多年不懈努力,人们终于找到了一些有效方法或工具。其中卷积神经网络、循环神经网络就是典型代表。接下来我们将介绍卷积神经网络,下一章将介绍循环神经网络。
那卷积神经网络是如何解决天量参数、过拟合等问题的呢?卷积神经网络这么神奇,如何用代码实现?这章就是为解决这些问题而设的,本章主要内容为:
卷积神经网络简介
卷积定义
卷积运算
卷积层
池化层
现代经典网络架构
实例:用TensorFlow实现一个卷积神经网络

6.1卷积神经网络简介

卷积神经网路(Convolutional Neural Network, CNN)是一种前馈神经网络,对于CNN最早可以追溯到1986年BP算法的提出。1989年LeCun将其用到多层神经网络中,直到1998年LeCun提出LeNet-5模型,神经网络的雏形基本形成。在接下来近十年的时间里,卷积神经网络的相关研究处于低谷,原因有两个:一是研究人员意识到多层神经网络在进行BP训练时的计算量极大,当时的硬件计算能力完全不可能实现;二是包括SVM在内的浅层机器学习算法也开始崭露头角。
2006年,Hinton一鸣惊人,在《科学》上发表文章,CNN再度觉醒,并取得长足发展。2012年,ImageNet大赛上CNN夺冠。2014年,谷歌研发出20层的VGG模型。同年,DeepFace、DeepID模型横空出世,直接将LFW数据库上的人脸识别、人脸认证的正确率刷到99.75%,已超越人类平均水平。
卷积神经网路由一个或多个卷积层和顶端的全连通层(对应经典的神经网路)组成,同时也包括关联权重和池化层(pooling layer)等。图6-1就是一个卷积神经网络架构。

图6-1 卷积神经网络示意图
与其他深度学习结构相比,卷积神经网路在图像和语音识别方面能够给出更好的结果。这一模型也可以使用反向传播算法进行训练。相比其他深度、前馈神经网路,卷积神经网路用更少参数,却能获得更高性能。
图6-1为卷积神经网络的一般结构,其中包括卷积神经网络的常用层,如卷积层、池化层、全连接层和输出层;有些还包括其他层,如正则化层、高级层等。接下来我们就各层的结构、原理等进行详细说明。
图6-1是用一个比较简单的卷积神经网络对手写输入数据进行分类,由卷积层(Conv2d)、池化层(MaxPool2d)和全连接层(Linear)叠加而成。下面我们先用代码定义这个卷积神经网络,然后,介绍各部分的定义及原理。

6.2卷积层

卷积层是卷积神经网络的核心层,而卷积(Convolution)又是卷积层的核心。卷积我们直观的理解,就是两个函数的一种运算,这种运算称为卷积运算。这样说或许比较抽象,我们还是先抛开复杂概念,先从具体实例开始吧。图6-2 就是一个简单的二维空间卷积运算示例,虽然简单,但却包含了卷积的核心内容。

图6-2 在二维空间上的一个卷积运算
在图6-2中,输入和卷积核都是张量,卷积运算就是用卷积分别乘以输入张量中的每个元素,然后输出一个代表每个输入信息的张量。其中卷积核(kernel)又称为权重过滤器或简称过滤器(filter)。接下来我们把输入、卷积核推广到更高维空间上,输入由2x2矩阵,拓展为5x5矩阵,卷积核由一个标量拓展为一个3x3矩阵,如图6-3。这时该如何进行卷积呢?

图6-3 卷积神经网络卷积运算,生成右边矩阵中第1行第1列的数据
用卷积核中每个元素,乘以对应输入矩阵中的对应元素,这点还是一样,但输入张量为5x5矩阵,而卷积核为3x3矩阵,所以这里首先就要解决一个如何对应的问题,这个问题解决了,这个推广也就完成了。把卷积核作为在输入矩阵上一个移动窗口,对应关系就迎刃而解。
卷积核如何确定?卷积核如何在输入矩阵中移动?移动过程中出现超越边界如何处理?这种因移动可能带来的问题,接下来将进行说明。

6.2.1 卷积核

卷积核,从这个名字可以看出它的重要性,它是整个卷积过程的核心。比较简单的卷积核或过滤器有Horizontalfilter、Verticalfilter、Sobel filter等。这些过滤器能够检测图像的水平边缘、垂直边缘、增强图片中心区域权重等。过滤器的具体作用,我们通过以下一些图来说明。
(1)垂直边缘检测

图6-4 过滤器对垂直边缘的检测
这个过滤器是3x3矩阵(注,过滤器一般是奇数阶矩阵),其特点是有值的是第1列和第3列,第2列为0。经过这个过滤器作用后,就把原数据垂直边缘检测出来了。
(2)水平边缘检测

图6-5 水平过滤器检测水平边缘示意图
这个过滤器也是3x3矩阵,其特点是有值的是第1行和第3行,第2行为0。经过这个过滤器作用后,就把原数据水平边缘检测出来了。
(3)过滤器对图像水平边缘检测、垂直边缘检测的效果图

图6-6过滤器对图像水平边缘检测、垂直边缘检测后的效果图
以上这些过滤器是比较简单的,在深度学习中,过滤器的作用不仅在于检测垂直边缘、水平边缘等,还需要检测其他边缘特征。
过滤器如何确定呢?过滤器类似于标准神经网络中的权重矩阵W,W需要通过梯度下降算法反复迭代求得。同样,在深度学习学习中,过滤器也是需要通过模型训练来得到。卷积神经网络主要目的就是计算出这些filter的数值。确定得到了这些filter后,卷积神经网络的浅层网络也就实现了对图片所有边缘特征的检测。
这节简单说明了卷积核的生成方式及作用。假设卷积核已确定,卷积核如何对输入数据进行卷积运算呢?这将在下节进行介绍。

6.2.2步幅

如何实现对输入数据进行卷积运算?回答这个问题之前,我们先回顾一下图6-3。在图6-3的左边的窗口中,左上方有个小窗口,这个小窗口实际上就是卷积核,其中x后面的值就是卷积核的值。如第1行为:x1、x0、x1对应卷积核的第1行[1 0 1]。右边窗口中这个4是如何得到的呢?就是5x5矩阵中由前3行、前3列构成的矩阵各元素乘以卷积核中对应位置的值,然后累加得到的。即:1x1+1x0+1x1+0x0+1x1+1x0+0x1+0x0+1x1=4,右边矩阵中第1行第2列的值如何得到呢?我们只要把左图中小窗口往右移动一格,然后,进行卷积运算;第1行第3列,如此类推;第2行、第3行的值,只要把左边的小窗口往下移动一格,然后再往右即可。看到这里,如果还不很清楚,没关系,看图6-7就一目了然。

图6-7卷积神经网络卷积运算,生成右边矩阵中第2行第2列的数据
小窗口(实际上就是卷积核或过滤器)在左边窗口中每次移动的格数(无论是自左向右移动,或自上向下移动)称为步幅(strides),在图像中就是跳过的像素个数。上面小窗口每次只移动一格,故参数strides=1。这个参数也可以是2或3等数。如果是2,每次移动时就跳2格或2个像素,如下图6-8所示。

图6-8 strides=2 示意图
在小窗口移动过程中,其值始终是不变的,都是卷积核的值。换一句话来说,卷积核的值,在整个过程中都是共享的,所以又把卷积核的值称为共享变量。卷积神经网络采用参数共享的方法大大降低了参数的数量。
参数strides是卷积神经网络中的一个重要参数,在用PyTorch具体实现时,strides参数格式为单个整数或两个整数的元组(分别表示在height和width维度上的值)。
在图6-8中,小窗口如果继续往右移动2格,卷积核窗口部分在输入矩阵之外,如下图6-9。此时,该如何处理呢?具体处理方法就涉及到下节要讲的内容--填充(padding)。

图6-9小窗口移动输入矩阵外

6.2.3 填充

当输入图片与卷积核不匹配时或卷积核超过图片边界时,可以采用边界填充(padding)的方法。即把图片尺寸进行扩展,扩展区域补零。如图6-10。当然也可不扩展。

图6-10采用padding方法,对图片进行扩展,然后补零。
根据是否扩展padding又分为Same、Valid。采用Same方式时,对图片扩展并补0;采用Valid方式时,对图片不扩展。如何选择呢?在实际训练过程中,一般选择Same,使用Same不会丢失信息。设补0的圈数为p,输入数据大小为n,过滤器大小为f,步幅大小为s,则有:

6.2.4 多通道上的卷积

前面我们对卷积在输入数据、卷积核的维度上进行了扩展,但输入数据、卷积核都是单个,如果从图形的角度来说都是灰色的,没有考虑彩色图片情况。在实际应用中,输入数据往往是多通道的,如彩色图片就3通道,即R、G、B通道。对于3通道的情况如何卷积呢?3通道图片的卷积运算与单通道图片的卷积运算基本一致,对于3通道的RGB图片,其对应的滤波器算子同样也是3通道的。例如一个图片是6 x 6 x 3,分别表示图片的高度(height)、宽度(weight)和通道(channel)。过程是将每个单通道(R,G,B)与对应的filter进行卷积运算求和,然后再将3通道的和相加,得到输出图片的一个像素值。具体过程如图6-11所示。

图6-11 3通道卷积示意图
为了实现更多边缘检测,可以增加更多的滤波器组。图6-12就是两组过滤器Filter W0和Filter W1。7*7*3输入,经过两个3*3*3的卷积(步幅为2),得到了3*3*2的输出。另外我们也会看到图6-10中的Zero padding是1,也就是在输入元素的周围补了一圈0。Zero padding对于图像边缘部分的特征提取是很有帮助的,可以防止信息丢失。最后,不同滤波器组卷积得到不同的输出,个数由滤波器组决定。

图6-12多组卷积核的卷积运算示意图

6.2.5激活函数

卷积神经网络与标准的神经网络类似,为保证其非线性,也需要使用激活函数,即在卷积运算后,把输出值另加偏移量,输入到激活函数,然后作为下一层的输入,如图6-13所示。

图6-13卷积运算后的结果+偏移量输入到激活函数ReLU
常用的激活函数有:tf.sigmoid、tf.nn.relu 、tf.tanh、 tf.nn.dropout等,这些激活函数的详细介绍可参考本书第5章。

6.2.6卷积函数

卷积函数是构建神经网络的重要支架,通常Pytorch的卷积运算是通过nn.Conv2d来完成。下面先介绍nn.Conv2d的参数,及如何计算输出的形状(shape)。
(1) nn.Conv2d函数

主要参数说明:
in_channels(int)
输入信号的通道
out_channels(int)
卷积产生的通道
kerner_size(int or tuple)
卷积核的尺寸
stride(int or tuple, optional)
卷积步长
padding(int or tuple, optional)
输入的每一条边补充0的层数
dilation(int or tuple, optional)
卷积核元素之间的间距
groups(int, optional)
控制输入和输出之间的连接: group=1,输出是所有的输入的卷积;group=2,此时相当于有并排的两个卷积层,每个卷积层计算输入通道的一半,并且产生的输出是输出通道的一半,随后将这两个输出连接起来。
bias(bool, optional)
如果bias=True,添加偏置。其中参数kernel_size,stride,padding,dilation也可以是一个int的数据,此时卷积height和width值相同;也可以是一个tuple数组,tuple的第一维度表示height的数值,tuple的第二维度表示width的数值
(2)输出形状

当groups=1时

当groups=2时

当groups=3时

in_channels/groups必须是整数,否则报错。

6.2.7转置卷积

转置卷积(Transposed Convolution)在一些文献中也称之为反卷积(Deconvolution)或部分跨越卷积(Fractionally-strided Convolution)。何为转置卷积,它与卷积又有哪些不同?
通过卷积的正向传播的图像一般越来越小,是下采样(downsampled)。卷积的方向传播实际上就是一种转置卷积,它是上采样(up-sampling)。
我们先简单回顾卷积的正向传播是如何运算的,假设卷积操作的相关参数为:输入大小为4,卷积核大小为3,步幅为2,填充为0,即 (n=4,f=3,s=1,p=0),根据公式(6.2)可知,输出 o=2。
整个卷积过程,可用图6-14 表示:

图6-14 卷积运算示意图
对于上述卷积运算,我们把图6-14所示的3×3卷积核展成一个如下所示的[4,16]的稀疏矩阵 C, 其中非0元素 ωi,j 表示卷积核的第 i 行和第 j 列 。

我们再把4×4的输入特征展成[16,1]的矩阵 X,那么 Y=CX 则是一个[4,1]的输出特征矩阵,把它重新排列2×2的输出特征就得到最终的结果,从上述分析可以看出,卷积层的计算其实是可以转化成矩阵相乘。
反向传播时又会如何呢?首先从卷积的反向传播算法开始。假设损失函数为L,则反向传播时,对L关系的求导,利用链式法则得到:

由此,可得X=C^T Y ,即反卷积的操作就是要对这个矩阵运算过程进行逆运算。
转置卷积在生成式对抗网络(GAN)中使用很普遍,后续我们将介绍,图6-15为使用转置卷积的一个示例,它一个上采样过程。


图6-15 转置卷积示例
Pytorch二维转置卷积的格式为:

待续.............