700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > NLP-Beginner 任务二:基于深度学习的文本分类+pytorch(超详细!!)

NLP-Beginner 任务二:基于深度学习的文本分类+pytorch(超详细!!)

时间:2024-02-15 00:20:29

相关推荐

NLP-Beginner 任务二:基于深度学习的文本分类+pytorch(超详细!!)

NLP-Beginner 任务二:基于深度学习的文本分类

传送门一. 介绍1.1 任务简介1.2 数据集1.3 流程介绍二. 特征提取——Word embedding(词嵌入)2.1 词嵌入的定义2.2 词嵌入的词向量说明2.3 词嵌入模型的初始化2.3.1 随机初始化2.3.2 预训练模型初始化2.4 特征表示三. 神经网络3.1 卷积神经网络(CNN)3.1.1 卷积层(Convolution)3.1.1.1 卷积定义3.1.1.2 卷积的步长与零填充3.1.1.3 卷积层设计3.1.1.4 总结3.1.2 激活层(可选)3.1.3 汇聚层/池化层(Pooling)3.1.4 全连接层(Fully connected)3.1.5 总结3.2 循环神经网络(RNN)3.2.1 隐藏层(Hidden)3.2.2 激活层(可选)3.2.3 全连接层(Fully connected)3.2.4 总结3.3 训练神经网络3.3.1 神经网络参数3.3.2 损失函数3.3.3 参数求解——梯度下降四. 代码及实现4.1 实验设置4.2 结果展示4.2.1 Part 14.2.2 Part 24.3 代码4.3.1 主文件——main.py4.3.2 特征提取——feature_batch.py4.3.3 神经网络——Neural_network_batch.py4.3.4 结果&画图——comparison_plot_batch.py五. 总结六. 自我推销

传送门

NLP-Beginner 任务传送门

我的代码传送门

数据集传送门

一. 介绍

1.1 任务简介

本次的NLP(Natural Language Processing)任务是利用深度学习中的卷积神经网络(CNN)和循环神经网络(RNN)来对文本的情感进行分类。

1.2 数据集

数据集传送门

训练集共有15万余项,语言为英文,情感分为0~4共五种情感。

例子

输入: A series of escapades demonstrating the adage that what is good for the goose is also good for the gander , some of which occasionally amuses but none of which amounts to much of a story .

输出: 1

输入: This quiet , introspective and entertaining independent is worth seeking .

输出: 4

输入:Even fans of Ismail Merchant 's work

输出: 2

输入: A positively thrilling combination of ethnography and all the intrigue , betrayal , deceit and murder of a Shakespearean tragedy or a juicy soap opera .

输出: 3

1.3 流程介绍

本篇博客将会一步一步讲解如何完成本次的NLP任务,具体流程为:

数据输入(英文句子)→特征提取(数字化数据)→神经网络设计(如何用pytorch建立神经网络模型)→结果输出(情感类别)

二. 特征提取——Word embedding(词嵌入)

2.1 词嵌入的定义

词嵌入模型即是把每一个词映射到一个高维空间里,每一个词代表着一个高维空间的向量,如下图:

图中的例子是把每个单词映射到了一个7维的空间。

词嵌入模型有三点好处:

当向量数值设计合理时,词向量与词向量之间的距离能体现出词与词之间的相似性

比如,上面的四个单词,cat和kitten是近义词,所以他们在高维空间中很相近(为了更好地展现它们的空间距离,可以把7维向量做一个线性变换变成2维,然后进行展示)。

然而,dog和cat意思并不相近,所以离得稍远一点,而houses的意思和cat,kitten,dog更加不相近了,因此会离得更远。

当向量数值设计合理时,词向量与词向量之间的距离也有一定的语义。

比如下面的四个单词,man和woman是两种性别,king和queen也是对应的两种性别,这两对的单词的差异几乎一致(性别差异),因此他们的距离应该也应该是几乎相同的。

因此,可以从图中看到,man和woman的距离恰好与king和queen的距离相等。

用相对较少的维数展现多角度特征差异

词袋模型和N元特征所提取出来的的特征向量都是超高维0-1向量,而词嵌入模型的向量每一维是实数,即不仅仅是0或1。

也就是说,词袋模型和N元特征所形成的特征矩阵是稀疏的,但是规模又很大,因而信息利用率很低,其词向量与词向量之间的距离也不能体现词间相似性。

而词嵌入模型所形成的特征矩阵不是稀疏的,且规模相对较小,因此能更好的利用每一维的信息,不再只是局限于0或1。

词袋模型和N元特征的定义可以参考《NLP-Beginner 任务一:基于机器学习的文本分类》。

2.2 词嵌入的词向量说明

在词袋模型/N元特征中,只要设置好词和词组,就能把每一个句子(一堆词)转换成对应的0-1表示。

但是,在词嵌入模型中,并没有明确的转换规则,因此我们不可能提前知道每一个词对应的向量。

上面的图的7维向量分别对应(living being, feline, human, gender, royalty, verb, plural),然而这只是为了方便理解所强行设计出的分类,很明显houses在gender维度的取值很难确定。因此,给每一个维度定义好分类标准,是不可能的。

所以,在词嵌入模型中,我们选择不给每一个维度定义一个所谓的语义,我们只是单纯的把每一个词,对应到一个词向量上,我们不关心向量的数值大小代表什么意思,我们只关心这个数值设置得合不合理。换句话说,每个词向量都是参数,是待定的,需要求解,这点和词袋模型/N元特征是完全不同的。

2.3 词嵌入模型的初始化

既然词向量是一个参数,那么我们就要为它设置一个初始值。

而上面2.1提到的词嵌入前两大好处,是基于一个前提:向量数值设计合理,因此选取参数的初始值至关重要。

如果参数的初始值选的不好,那么优化模型求解的时候就会使参数值难以收敛,或者收敛到一个较差的极值;相反,如果选得好,就能求出一个更好的参数,甚至能起到加速模型优化的效果。

一般来说,有两种初始化的方法。

2.3.1 随机初始化

随机初始化这种方式十分简单粗暴。

给定一个维度d(比如50),对于每一个词www,我们随机生成一个d维的向量x∈Rdx\in \mathbb{R}^dx∈Rd。

注:随机生成的方式有很多,比如x∼N(0,σ2Id)x\sim N(\textbf{0},\sigma^2I_d)x∼N(0,σ2Id​),即xxx服从于一个简单的多元标准正态分布,等等。

这种初始化方式非常简单,但是有可能会生成较劣的初值,也没有一个良好的解释性。

2.3.2 预训练模型初始化

预训练模型初始化,顾名思义,就是拿别人已经训练好的模型作为初值。

也就是说,把别人已经设置好的词向量直接拿过来用。

这种方式的初始化时间会比较长,因为要从别人的词库里面找,需要一定的时间,但是这种初值无疑比随机初始化要好很多,毕竟是别人已经训练好的模型。

网上也有很多训练好的词嵌入模型,比如GloVe(本篇文章会用到)。

2.4 特征表示

给定每个词的词向量,那么就可以把一个句子量化成一个ID列表,再变成特征(矩阵)。

例子:(d=5)

(ID:1)I : [+0.10,+0.20,+0.30,+0.50,+1.50]\quad\; [+0.10, +0.20, +0.30, +0.50, +1.50][+0.10,+0.20,+0.30,+0.50,+1.50]

(ID:2)love : [−1.00,−2.20,+3.40,+1.00,+0.00][-1.00, -2.20, +3.40, +1.00, +0.00][−1.00,−2.20,+3.40,+1.00,+0.00]

(ID:3)you : [−3.12,−1.14,+5.14,+1.60,+7.00]\, [-3.12, -1.14, +5.14, +1.60, +7.00][−3.12,−1.14,+5.14,+1.60,+7.00]

I love you 便可表示为 [1,2,3][1,2,3][1,2,3],经过词嵌入之后则得到

X=[+0.10+0.20+0.30+0.50+1.50−1.00−2.20+3.40+1.00+0.00−3.12−1.14+5.14+1.60+7.00]X=\left[ \begin{matrix} +0.10& +0.20& +0.30& +0.50& +1.50 \\ -1.00& -2.20& +3.40& +1.00& +0.00 \\ -3.12& -1.14& +5.14& +1.60& +7.00 \end{matrix} \right]X=⎣⎡​+0.10−1.00−3.12​+0.20−2.20−1.14​+0.30+3.40+5.14​+0.50+1.00+1.60​+1.50+0.00+7.00​⎦⎤​

(ID:1)I : [+0.10,+0.20,+0.30,+0.50,+1.50]\quad\, \, [+0.10, +0.20, +0.30, +0.50, +1.50][+0.10,+0.20,+0.30,+0.50,+1.50]

(ID:4)hate : [−8.00,−6.40,+3.60,+2.00,+3.00][-8.00, -6.40, +3.60, +2.00, +3.00][−8.00,−6.40,+3.60,+2.00,+3.00]

(ID:3)you : [−3.12,−1.14,+5.14,+1.60,+7.00]\ [-3.12, -1.14, +5.14, +1.60, +7.00][−3.12,−1.14,+5.14,+1.60,+7.00]

I hate you 便可表示为 [1,4,3][1,4,3][1,4,3],经过词嵌入之后则得到

X=[+0.10+0.20+0.30+0.50+1.50−8.00−6.40+3.60+2.00+3.00−3.12−1.14+5.14+1.60+7.00]X=\left[ \begin{matrix} +0.10& +0.20& +0.30& +0.50& +1.50 \\ -8.00& -6.40&+3.60&+2.00&+3.00\\ -3.12& -1.14& +5.14& +1.60& +7.00 \end{matrix} \right]X=⎣⎡​+0.10−8.00−3.12​+0.20−6.40−1.14​+0.30+3.60+5.14​+0.50+2.00+1.60​+1.50+3.00+7.00​⎦⎤​

得到句子的特征矩阵X后,便可以把它放入到神经网络之中。

三. 神经网络

本部分详细内容可以参考神经网络与深度学习。

3.1 卷积神经网络(CNN)

CNN一般来说有3~4层

卷积层(convolution)激活层(activation)(可选)池化层(pooling)全连接层(fully connected)

3.1.1 卷积层(Convolution)

3.1.1.1 卷积定义

首先需要搞清楚卷积的定义。

先介绍一维卷积,先看一张图:

对于左边的图,待处理的向量x∈Rnx\in \mathbb{R}^nx∈Rn是:

[1,1,−1,1,1,1,−1,1,1][1, 1, -1, 1, 1, 1, -1, 1, 1][1,1,−1,1,1,1,−1,1,1]

卷积核w∈RKw\in \mathbb{R}^Kw∈RK是:

[13,13,13][\frac{1}{3}, \frac{1}{3}, \frac{1}{3}][31​,31​,31​]

结果y∈Rn−K+1y\in \mathbb{R}^{n-K+1}y∈Rn−K+1是:

[13,13,13,1,13,13,13][\frac{1}{3}, \frac{1}{3}, \frac{1}{3}, 1, \frac{1}{3}, \frac{1}{3}, \frac{1}{3}][31​,31​,31​,1,31​,31​,31​]

记为:

y=w∗xy=w * xy=w∗x

具体公式为:

yt=∑k=1Kwkxt+k−1,t=1,2,...,n−K+1y_t=\sum_{k=1}^Kw_kx_{t+k-1},\ \small{t=1,2,...,n-K+1}yt​=k=1∑K​wk​xt+k−1​,t=1,2,...,n−K+1

然后解释二维卷积,如下图:

左边第一项,就是待处理的矩阵X∈Rn×dX\in \mathbb{R}^{n\times d}X∈Rn×d。

左边第二项,就是3*3的卷积核W∈RK1×K2W\in \mathbb{R}^{K_1\times K_2}W∈RK1​×K2​。

如图所示,对待处理矩阵的右上角,进行卷积操作,就可以得到右边矩阵Y∈R(n−K1+1)×(d−K2+1)Y\in \mathbb{R}^{(n-K_1+1)\times (d-K_2+1)}Y∈R(n−K1​+1)×(d−K2​+1)右上角的元素-1。

记为:

Y=W∗XY=W * XY=W∗X

具体公式为:

Yij=∑k1=1K1∑k2=1K2Wk1,k2Xi+k1−1,j+k2−1i=1,2,...,n−K1+1,j=d−K2+1Y_{ij}=\sum_{k_1=1}^{K_1}\sum_{k_2=1}^{K_2}W_{k_1,k_2}X_{i+k_1-1,j+k_2-1}\\ ~\\ \small{i=1,2,...,n-K_1+1},\ \small{j=d-K_2+1}Yij​=k1​=1∑K1​​k2​=1∑K2​​Wk1​,k2​​Xi+k1​−1,j+k2​−1​i=1,2,...,n−K1​+1,j=d−K2​+1

3.1.1.2 卷积的步长与零填充

上面的卷积例子的步长都是1,但是步长可以不为1,见下图:

上图的左部分步长为2,右部分的步长为1,不同的步长会得到长度不一样的结果,越长的步长,得到的结果长度越短。

二维卷积的步长也可以类似地进行定义,只不过除了横向的步长,也有纵向的步长,这里不详细叙述。

除此之外,还有值得注意的是padding(零填充),可以看到上图的右部分进行了零填充操作,使得待处理向量的边界元素能进行更多次数的卷积操作。

例子:

待处理的向量x∈Rnx\in \mathbb{R}^nx∈Rn是:

[1,1,−1,1,1,1,−1,1,1][1, 1, -1, 1, 1, 1, -1, 1, 1][1,1,−1,1,1,1,−1,1,1]

进行了零填充的待处理向量x~\tilde{x}x~是:

[0,1,1,−1,1,1,1,−1,1,1,0][0, 1, 1, -1, 1, 1, 1, -1, 1, 1, 0][0,1,1,−1,1,1,1,−1,1,1,0]

卷积核w∈RKw\in \mathbb{R}^Kw∈RK是:

[13,13,13][\frac{1}{3}, \frac{1}{3}, \frac{1}{3}][31​,31​,31​]

可以看到,xxx的最左边的元素111只被卷积到了1次,而经过了padding之后,最左边的111元素可以被卷积两次(第一次是[0,1,1]∗w[0,1,1]*w[0,1,1]∗w,第二次是[1,1,−1]∗w[1,1,-1]*w[1,1,−1]∗w).

因此,如果有了零填充的操作,待处理的向量边界的特征也能得意保留。

二维卷积的padding也可以进行类似地定义,只不过除了横向补0,还可以纵向补0。

3.1.1.3 卷积层设计

卷积层的设计参考了论文Convolutional Neural Networks for Sentence Classification。

先定义一些符号,nnn是句子的长度,图中的例子(wait for the video and do n’t rent it)是n=9n=9n=9,词向量的长度为ddd,图中的例子d=6d=6d=6,即该句子的特征矩阵X∈Rn×d=R9×6X\in \mathbb{R}^{n\times d}= \mathbb{R}^{9\times 6}X∈Rn×d=R9×6。

在本次的任务中,我们采用四个卷积核,大小分别是2×d2\times d2×d, 3×d3\times d3×d, 4×d4\times d4×d, 5×d5\times d5×d。

2×d2\times d2×d 的卷积核在图中显示为红色的框框,3×d3\times d3×d 的卷积核在图中显示为黄色的框框。

例如:

“wait for” 这个词组,的特征矩阵的大小为2×d2\times d2×d,经过2×d2\times d2×d的卷积之后,会变成一个值。

对于某一个核WWW,对特征矩阵XXX进行卷积之后,会得到一个矩阵。

例如:

特征矩阵X∈Rn×dX\in \mathbb{R}^{n\times d}X∈Rn×d与卷积核W∈R2×dW\in \mathbb{R}^{2\times d}W∈R2×d卷积后,得到结果Y∈R(n−2+1)×(d+d−1)=R(n−1)×1Y\in \mathbb{R}^{(n-2+1)\times (d+d-1)}=\mathbb{R}^{(n-1)\times 1}Y∈R(n−2+1)×(d+d−1)=R(n−1)×1。

需要注意的是,这里采用四个核的原因是想挖掘词组的特征。

比如说,2×d2\times d2×d 的核是用来挖掘连续两个单词之间的关系的,而5×d5\times d5×d 的核用来连续挖掘五个单词之间的关系。

3.1.1.4 总结

卷积层的参数即是卷积核。

对于一个句子,特征矩阵是X∈Rn×dX\in \mathbb{R}^{n\times d}X∈Rn×d,经过了四个卷积核WWW的卷积后,得到了Y1∈R(n−1)×1,Y2∈R(n−2)×1,Y3∈R(n−3)×1,Y4∈R(n−4)×1Y_1\in \mathbb{R}^{(n-1)\times 1},\ Y_2\in \mathbb{R}^{(n-2)\times 1},\ Y_3\in \mathbb{R}^{(n-3)\times 1},\ Y_4\in \mathbb{R}^{(n-4)\times 1}Y1​∈R(n−1)×1,Y2​∈R(n−2)×1,Y3​∈R(n−3)×1,Y4​∈R(n−4)×1的结果。

上面说的是一个通道的情况,我们可以多设置几个通道,每个通道都像上述一样操作,只不过每个通道的卷积核是不一样的,都是待定的参数。因此,设置lll_lll​个通道,就会得到lll_lll​组(Y1,Y2,Y3,Y4)(Y_1, Y_2, Y_3, Y_4)(Y1​,Y2​,Y3​,Y4​)。

3.1.2 激活层(可选)

激活函数可以参考维基百科。

在本次实战中,采用了ReLu函数:

ReLu(x)=max(x,0)ReLu(x)=\text{max}(x,0)ReLu(x)=max(x,0)

lll_lll​组(Y1,Y2,Y3,Y4)(Y_1, Y_2, Y_3, Y_4)(Y1​,Y2​,Y3​,Y4​)经过了激活之后,还是得到lll_lll​组(Y1,Y2,Y3,Y4)(Y_1, Y_2, Y_3, Y_4)(Y1​,Y2​,Y3​,Y4​)。

3.1.3 汇聚层/池化层(Pooling)

Pooling层相当于是对特征矩阵/向量提取出一些有用的信息,从而减少特征的规模,不仅减少了计算量,也能去除冗余特征。

Pooling有两种方法:

最大汇聚

对一个区域,取最大的一个元素

ym,n=maxi∈Rm,nxiy_{m,n}=\text{max}_{i\in R_{m,n}} x_iym,n​=maxi∈Rm,n​​xi​

即取Rm,nR_{m,n}Rm,n​这个区域里,最大的元素

平均汇聚

对一个区域,取所有元素的平均值

ym,n=1∣Rm,n∣∑i∈Rm,nxiy_{m,n}=\frac{1}{|R_{m,n}|}\sum_{i\in R_{m,n}} x_iym,n​=∣Rm,n​∣1​i∈Rm,n​∑​xi​

即取Rm,nR_{m,n}Rm,n​这个区域里,所有元素的平均值

看一张图:

上面的是最大汇聚,下面的是平均汇聚。

在本次实战中,我用的是最大汇聚。

对于lll_lll​组(Y1,Y2,Y3,Y4)(Y_1, Y_2, Y_3, Y_4)(Y1​,Y2​,Y3​,Y4​),对任意一组lll里的任意一个向量Yi(l)∈R(n−i)×1Y^{(l)}_i\in \mathbb{R}^{(n-i)\times 1}Yi(l)​∈R(n−i)×1,我取其最大值,即

yi(i)=maxjYi(l)(j)y_i^{(i)}=max_j\; Y^{(l)}_i(j)yi(i)​=maxj​Yi(l)​(j)

Yi(l)(j)Y^{(l)}_i(j)Yi(l)​(j)表示Yi(l)Y^{(l)}_iYi(l)​的第jjj个元素。

经过最大汇聚后,我们可以得到lll_lll​组(y1,y2,y3,y4)(y_1, y_2, y_3, y_4)(y1​,y2​,y3​,y4​)。

把它们按结果类别拼接起来,可以得到一个长度为ll∗4l_l*4ll​∗4的向量,即

Y=(y1(1),...,y1(ll),y2(1),...,y2(ll),y3(1)...,y3(ll),y4(1),...,y4(ll))T∈R(ll∗4)×1Y=(y_1^{(1)}, ..., y_1^{(l_l)}, y_2^{(1)}, ..., y_2^{(l_l)}, y_3^{(1)}... , y_3^{(l_l)}, y_4^{(1)}, ..., y_4^{(l_l)})^T\in \mathbb{R}^{(l_l*4)\times 1}Y=(y1(1)​,...,y1(ll​)​,y2(1)​,...,y2(ll​)​,y3(1)​...,y3(ll​)​,y4(1)​,...,y4(ll​)​)T∈R(ll​∗4)×1

3.1.4 全连接层(Fully connected)

看一张示意图:

左边的神经元便是我们上一部分得到的向量,长度为ll∗4l_l*4ll​∗4。

而我们的目的是输出一个句子的情感类别,参考上一次任务《NLP-Beginner 任务一:基于机器学习的文本分类》,我们的输出也应该是五类情感的概率,即(0~4共五类)

p=(0,0,0.7,0.25,0.05)Tp=(0,0,0.7,0.25,0.05)^Tp=(0,0,0.7,0.25,0.05)T

则代表,其是类别2的概率为0.7,类别3的概率为0.25,类别4的概率为0.05。

因此,在全连接层,我们要把长度为ll∗4l_l*4ll​∗4的向量转换成长度为 555 的向量。

而最简单的转换方式便是线性变换 p=AY+bp=AY+bp=AY+b,其中A∈R5×(ll∗4),Y∈R(ll∗4)×1,b∈R5×1A\in \mathbb{R}^{5\times (l_l*4)}, Y\in \mathbb{R}^{(l_l*4)\times 1}, b\in \mathbb{R}^{5\times 1}A∈R5×(ll​∗4),Y∈R(ll​∗4)×1,b∈R5×1。

最终,整个神经网络会输出一个长度为 555 的向量 ppp。

如此一来 AAA 和 bbb 便是需要待定的系数。

3.1.5 总结

卷积层:特征矩阵→\rightarrow→ lll_lll​组(Y1,Y2,Y3,Y4)(Y_1, Y_2, Y_3, Y_4)(Y1​,Y2​,Y3​,Y4​),神经网络参数:4ll4l_l4ll​个卷积核WWW。激活层:lll_lll​组(Y1,Y2,Y3,Y4)(Y_1, Y_2, Y_3, Y_4)(Y1​,Y2​,Y3​,Y4​) →\rightarrow→ lll_lll​组(Y1,Y2,Y3,Y4)(Y_1, Y_2, Y_3, Y_4)(Y1​,Y2​,Y3​,Y4​),没有参数。汇聚层:lll_lll​组(Y1,Y2,Y3,Y4)(Y_1, Y_2, Y_3, Y_4)(Y1​,Y2​,Y3​,Y4​) →\rightarrow→ lll_lll​组(y1,y2,y3,y4)→Y(y_1, y_2, y_3, y_4)\rightarrow Y(y1​,y2​,y3​,y4​)→Y,没有参数。全连接层: Y→pY\rightarrow pY→p,神经网络参数:A,bA, bA,b

3.2 循环神经网络(RNN)

CNN一般来说有2~3层

隐藏层(hidden)激活层(activation)(可选)全连接层(fully connected)

3.2.1 隐藏层(Hidden)

看一张图:

先回顾输入是什么。

输入是一个特征矩阵X∈Rn×dX\in \mathbb{R}^{n\times d}X∈Rn×d,例如:(d=5)

I : [+0.10,+0.20,+0.30,+0.50,+1.50]\quad\; [+0.10, +0.20, +0.30, +0.50, +1.50][+0.10,+0.20,+0.30,+0.50,+1.50]

love : [−1.00,−2.20,+3.40,+1.00,+0.00][-1.00, -2.20, +3.40, +1.00, +0.00][−1.00,−2.20,+3.40,+1.00,+0.00]

you : [−3.12,−1.14,+5.14,+1.60,+7.00]\, [-3.12, -1.14, +5.14, +1.60, +7.00][−3.12,−1.14,+5.14,+1.60,+7.00]

I love you 可表示为

X=[+0.10+0.20+0.30+0.50+1.50−1.00−2.20+3.40+1.00+0.00−3.12−1.14+5.14+1.60+7.00]=[x1,x2,x3]TX=\left[ \begin{matrix} +0.10& +0.20& +0.30& +0.50& +1.50 \\ -1.00& -2.20& +3.40& +1.00& +0.00 \\ -3.12& -1.14& +5.14& +1.60& +7.00 \end{matrix} \right]=[x_1,x_2,x_3]^TX=⎣⎡​+0.10−1.00−3.12​+0.20−2.20−1.14​+0.30+3.40+5.14​+0.50+1.00+1.60​+1.50+0.00+7.00​⎦⎤​=[x1​,x2​,x3​]T

xi∈Rdx_i\in \mathbb{R}^{d}xi​∈Rd

在CNN中,我们是直接对特征矩阵X进行操作,而在RNN中,我们是逐个对xix_ixi​进行操作,步骤如下:

初始化 h0∈Rlhh_0\in \mathbb{R}^{l_h}h0​∈Rlh​从 t=1,2,...nt=1,2,...nt=1,2,...n 计算以下两个公式

(1) zt=Uht−1+Wxt+b\quad z_t=Uh_{t-1}+Wx_t+bzt​=Uht−1​+Wxt​+b, 其中U∈Rlh×lh,W∈Rlh×d,z,bh∈RlhU\in \mathbb{R}^{l_h\times l_h},\ W\in \mathbb{R}^{l_h\times d},\ z,b_h\in \mathbb{R}^{l_h}U∈Rlh​×lh​,W∈Rlh​×d,z,bh​∈Rlh​

(2) ht=f(zt)\quad h_t=f(z_t)ht​=f(zt​),其中f(⋅)f(\cdot)f(⋅)激活函数,本任务用了tanhtanhtanh函数, tanh(x)=exp(x)−exp(−x)exp(x)+exp(−x)tanh(x)=\frac{\text{exp}(x)-\text{exp}(-x)}{\text{exp}(x)+\text{exp}(-x)}tanh(x)=exp(x)+exp(−x)exp(x)−exp(−x)​

最终得到hn∈Rlhh_n\in \mathbb{R}^{l_h}hn​∈Rlh​

这一层的目的,便是把序列{xi}i=1n\{x_i\}_{i=1}^n{xi​}i=1n​逐个输入到隐藏层去,与参数发生作用,输出的结果hth_tht​也会参与到下一次循环计算之中,实现了一种记忆功能,使神经网络具有了(短期)的记忆能力。

这种记忆能力有助于神经网络中从输入中挖掘更多的特征,及其相互关系,而不再只是像CNN一样局限于2、3、4、5个词之间的关系。

3.2.2 激活层(可选)

RNN的激活层与CNN激活层是类似的,激活函数可以参考维基百科。

在本次实战中,我的RNN没有额外加入激活层。

3.2.3 全连接层(Fully connected)

RNN的全连接层与CNN全连接层也是类似的。

在全连接层,我们要把长度为lhl_hlh​的向量转换成长度为 555 的向量。

类似地,采取线性变换 p=Ahn+blp=Ah_n+b_lp=Ahn​+bl​,其中 A∈R5×lh,ht∈Rlh×1,bl∈R5×1A\in \mathbb{R}^{5\times l_h}, h_t\in \mathbb{R}^{l_h\times 1}, b_l\in \mathbb{R}^{5\times 1}A∈R5×lh​,ht​∈Rlh​×1,bl​∈R5×1, AAA 和 bbb 也是需要待定的系数。

最终,整个神经网络会输出一个长度为 555 的向量 ppp。

3.2.4 总结

隐藏层:特征矩阵→\rightarrow→ hnh_nhn​,神经网络参数:W,UW,\ UW,U 和 bhb_hbh​ 。全连接层: hn→ph_n\rightarrow phn​→p,神经网络参数:A,blA, b_lA,bl​

3.3 训练神经网络

3.3.1 神经网络参数

有了上面的CNN和RNN的模型,接下来就是求解神经网络中的参数了。先回顾一下两个模型的参数:

CNN:4ll4l_l4ll​个卷积核W;W;\quadW; A,bA, bA,bRNN:W,U,bh;A,blW,\ U,\ b_h;\quad A,b_lW,U,bh​;A,bl​

对于任意一个网络,把它们的参数记作 θ\thetaθ。

整个流程:

句子x→Wordembedding特征矩阵X→NeuralNetwork(θ)类别概率向量p句子x \xrightarrow{\text{Word\ embedding}} 特征矩阵X\xrightarrow{\text{Neural Network}(\theta)}类别概率向量p句子xWordembedding​特征矩阵XNeuralNetwork(θ)​类别概率向量p

3.3.2 损失函数

有了模型,我们就要对模型的好坏做出一个评价。也就是说,给定一组参数 θ\thetaθ,我们要去量化一个模型的好坏,那么我们就要定义一个损失函数。

一般来说,有以下几种损失函数:

因此,总上述表来看,我们应该使用交叉熵损失函数。

给定一个神经网络 NNNNNN, 对于每一个样本n,其损失值为

L(NNθ(x(n)),y(n))=−∑c=1Cyc(n)log⁡pc(n)=−(y(n))Tlog⁡p(n)L(NN_\theta(x^{(n)}),y^{(n)})=-\sum_{c=1}^C y_c^{(n)}\log{p_c^{(n)}}=-(y^{(n)})^T\log{p^{(n)}}L(NNθ​(x(n)),y(n))=−c=1∑C​yc(n)​logpc(n)​=−(y(n))Tlogp(n)

其中y(n)=(I(c=0),I(c=2),...,I(c=C))Ty^{(n)}=\big(I(c=0),I(c=2),...,I(c=C)\big)^Ty(n)=(I(c=0),I(c=2),...,I(c=C))T,是一个one-hot向量,即只有一个元素是1,其他全是0的向量。

注:下标 ccc 代表向量中的第 ccc 个元素,这里 C=4C=4C=4 。

例子:

句子 x(n)x^{(n)}x(n) 的类别是第0类,则 y(n)=[1,0,0,0,0]Ty^{(n)}=[1,0,0,0,0]^Ty(n)=[1,0,0,0,0]T

而对于N个样本,总的损失值则是每个样本损失值的平均,即

L(θ)=L(NNθ(x),y)=1N∑n=1NL(NNθ(x(n)),y(n))L(\theta)=L(NN_\theta(x),y)=\frac{1}{N}\sum_{n=1}^NL(NN_\theta(x^{(n)}),y^{(n)})L(θ)=L(NNθ​(x),y)=N1​n=1∑N​L(NNθ​(x(n)),y(n))

有了损失函数,我们就可以通过找到损失函数的最小值,来求解最优的参数矩阵 θ\thetaθ。

3.3.3 参数求解——梯度下降

梯度下降的基本思想是,对于每个固定的参数,求其梯度(导数),然后利用梯度(导数),进行对参数的更新。

在这里,公式是

θt+1←θt−α∂L(θt)∂θt\theta_{t+1}\leftarrow \theta_t-\alpha\frac{\partial L(\theta_t)}{\partial \theta_t}θt+1​←θt​−α∂θt​∂L(θt​)​

由于Pytorch求解参数并不需要我们求梯度且梯度计算非常复杂,因此在这里就暂时不介绍具体如何求梯度过程。

感兴趣的同学可以参考神经网络与深度学习。

四. 代码及实现

4.1 实验设置

样本个数:约150000训练集:测试集 : 7:3模型:CNN, RNN初始化:随机初始化,GloVe预训练模型初始化学习率:10-3lh,dl_h,\ dlh​,d:50lll_lll​:最长句子的单词数Batch 大小:500

4.2 结果展示

4.2.1 Part 1

先展示总体结果。

我们先比较CNNRNN

可以看到RNN在测试集的准确率(最大值)比CNN都要高,且测试集的损失值(最小值)也要比CNN的要低。

再比较随机初始化GloVe初始化

在同种模型下,GloVe初始化也要比随机初始化的效果好,即在测试集准确率大、测试集损失值小。

最终,测试集准确率大约在 66%66\%66% 左右。

4.2.2 Part 2

以上结果并不能说明RNN在长句子情感分类方面的优势。因为RNN具有短期记忆,能处理好词与词之间的关系,所以我想看看RNN在长句子分类上是否有一个比较好的结果。

因此,在训练的过程中,我特别关注了测试集单词数大于20的句子的损失值和正确率,结果如图:

非常遗憾的是,RNN的效果并不比CNN好,而且无论是CNN还是RNN,长句子的情感分类准确率也只有大概 55%55\%55% 左右,比总体的平均正确率低了约 10%10\%10%。

因此,在这一点上有待进一步挖掘。

4.3 代码

本次使用了Python中的torch库,并使用了cuda加速。

若不想要GPU加速,只需要把comparison_plot_batch.pyNeural_Network_batch.py中所有的.cuda().cpu()删去即可。

注1:可能在comparison_plot_batch.py中的所有.item()也要删去。

重要

注2:在理论部分,我们阐述的是一个样本从输入到输出的过程,但是实际神经网络里通常都是输入一批样本(batch)然后得到输出。

但是,一个batch内特征长短不一会使数据分batch失败,因此会进行一个零填充(padding)操作,把同一个batch内的所有输入(句子),补到一样长。

但是,由于句子长度可能会参差不齐(如一个句子只有3个单词,另一个有50个单词,那么就需要在前者的后面填充47个无意义的0。),插入过长的无意义零填充可能会对性能造成影响,因此在本次实战中,我先把数据按照句子长度进行了排序,尽量使同一个batch内句子长度一致,这样就可以避免零填充。

同时,设置padding的这个ID为0。

注3:本次实战中,还在词嵌入之后加入了一层Dropout层(丢弃法)。

解释:Dropout (丢弃法) 是指在深度网络的训练中,以一定的概率随机地“临时丢弃”一部分神经元节点。 具体来讲,Dropout 作用于每份小批量训练数据,由于其随机丢弃部分神经元的机制,相当于每次迭代都在训练不同结构的神经网络。

简单来讲,就是为了防止模型过拟合,且Dropout层在模型测试时不会有任何影响,训练时的效果如图:

4.3.1 主文件——main.py

import csvimport randomfrom feature_batch import Random_embedding,Glove_embeddingimport torchfrom comparison_plot_batch import NN_embedding_plot# 数据读入with open('train.tsv') as f:tsvreader = csv.reader (f, delimiter ='\t')temp = list ( tsvreader )with open('glove.6B.50d.txt','rb') as f: # for glove embeddinglines=f.readlines()# 用GloVe创建词典trained_dict=dict()n=len(lines)for i in range(n):line=lines[i].split()trained_dict[line[0].decode("utf-8").upper()]=[float(line[j]) for j in range(1,51)]# 初始化iter_times=50 # 做50个epochalpha=0.001# 程序开始data = temp[1:]batch_size=500# 随机初始化random.seed()random_embedding=Random_embedding(data=data)random_embedding.get_words() # 找到所有单词,并标记IDrandom_embedding.get_id() # 找到每个句子拥有的单词ID# 预训练模型初始化random.seed()glove_embedding=Glove_embedding(data=data,trained_dict=trained_dict)glove_embedding.get_words() # 找到所有单词,并标记IDglove_embedding.get_id() # 找到每个句子拥有的单词IDNN_embedding_plot(random_embedding,glove_embedding,alpha,batch_size,iter_times))

4.3.2 特征提取——feature_batch.py

import randomfrom torch.utils.data import Dataset, DataLoaderfrom torch.nn.utils.rnn import pad_sequenceimport torchdef data_split(data, test_rate=0.3):"""把数据按一定比例划分成训练集和测试集"""train = list()test = list()for datum in data:if random.random() > test_rate:train.append(datum)else:test.append(datum)return train, testclass Random_embedding():"""随机初始化"""def __init__(self, data, test_rate=0.3):self.dict_words = dict() # 单词->ID的映射data.sort(key=lambda x:len(x[2].split())) # 按照句子长度排序,短着在前,这样做可以避免后面一个batch内句子长短不一,导致padding过度self.data = dataself.len_words = 0 # 单词数目(包括padding的ID:0)self.train, self.test = data_split(data, test_rate=test_rate) # 训练集测试集划分self.train_y = [int(term[3]) for term in self.train] # 训练集类别self.test_y = [int(term[3]) for term in self.test] # 测试集类别self.train_matrix = list() # 训练集的单词ID列表,叠成一个矩阵self.test_matrix = list() # 测试集的单词ID列表,叠成一个矩阵self.longest=0 # 记录最长的单词def get_words(self):for term in self.data:s = term[2] # 取出句子s = s.upper() # 记得要全部转化为大写!!(或者全部小写,否则一个单词例如i,I会识别成不同的两个单词)words = s.split()for word in words: # 一个一个单词寻找if word not in self.dict_words:self.dict_words[word] = len(self.dict_words)+1 # padding是第0个,所以要+1self.len_words=len(self.dict_words) # 单词数目(暂未包括padding的ID:0)def get_id(self):for term in self.train: # 训练集s = term[2]s = s.upper()words = s.split()item=[self.dict_words[word] for word in words] # 找到id列表(未进行padding)self.longest=max(self.longest,len(item)) # 记录最长的单词self.train_matrix.append(item)for term in self.test:s = term[2]s = s.upper()words = s.split()item = [self.dict_words[word] for word in words] # 找到id列表(未进行padding)self.longest = max(self.longest, len(item)) # 记录最长的单词self.test_matrix.append(item)self.len_words += 1 # 单词数目(包括padding的ID:0)class Glove_embedding():def __init__(self, data,trained_dict,test_rate=0.3):self.dict_words = dict() # 单词->ID的映射self.trained_dict=trained_dict # 记录预训练词向量模型data.sort(key=lambda x:len(x[2].split())) # 按照句子长度排序,短着在前,这样做可以避免后面一个batch内句子长短不一,导致padding过度self.data = dataself.len_words = 0 # 单词数目(包括padding的ID:0)self.train, self.test = data_split(data, test_rate=test_rate) # 训练集测试集划分self.train_y = [int(term[3]) for term in self.train] # 训练集类别self.test_y = [int(term[3]) for term in self.test] # 测试集类别self.train_matrix = list() # 训练集的单词ID列表,叠成一个矩阵self.test_matrix = list() # 测试集的单词ID列表,叠成一个矩阵self.longest=0 # 记录最长的单词self.embedding=list() # 抽取出用到的(预训练模型的)单词def get_words(self):self.embedding.append([0] * 50) # 先加padding的词向量for term in self.data:s = term[2] # 取出句子s = s.upper() # 记得要全部转化为大写!!(或者全部小写,否则一个单词例如i,I会识别成不同的两个单词)words = s.split()for word in words: # 一个一个单词寻找if word not in self.dict_words:self.dict_words[word] = len(self.dict_words)+1 # padding是第0个,所以要+1if word in self.trained_dict: # 如果预训练模型有这个单词,直接记录词向量self.embedding.append(self.trained_dict[word])else: # 预训练模型没有这个单词,初始化该词对应的词向量为0向量# print(word)# raise Exception("words not found!")self.embedding.append([0]*50)self.len_words=len(self.dict_words) # 单词数目(暂未包括padding的ID:0)def get_id(self):for term in self.train: # 训练集s = term[2]s = s.upper()words = s.split()item=[self.dict_words[word] for word in words] # 找到id列表(未进行padding)self.longest=max(self.longest,len(item)) # 记录最长的单词self.train_matrix.append(item)for term in self.test:s = term[2]s = s.upper()words = s.split()item = [self.dict_words[word] for word in words] # 找到id列表(未进行padding)self.longest = max(self.longest, len(item)) # 记录最长的单词self.test_matrix.append(item)self.len_words += 1 # 单词数目(暂未包括padding的ID:0)class ClsDataset(Dataset):"""自定义数据集的结构,pytroch基本功!!!"""def __init__(self, sentence, emotion):self.sentence = sentence # 句子self.emotion= emotion # 情感类别def __getitem__(self, item):return self.sentence[item], self.emotion[item]def __len__(self):return len(self.emotion)def collate_fn(batch_data):"""自定义数据集的内数据返回方式,pytroch基本功!!!并进行padding!!!"""sentence, emotion = zip(*batch_data)sentences = [torch.LongTensor(sent) for sent in sentence] # 把句子变成Longtensor类型padded_sents = pad_sequence(sentences, batch_first=True, padding_value=0) # 自动padding操作!!!return torch.LongTensor(padded_sents), torch.LongTensor(emotion)def get_batch(x,y,batch_size):"""利用dataloader划分batch,pytroch基本功!!!"""dataset = ClsDataset(x, y)dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False,drop_last=True,collate_fn=collate_fn)# shuffle是指每个epoch都随机打乱数据排列再分batch,# 这里一定要设置成false,否则之前的排序会直接被打乱,# drop_last是指不利用最后一个不完整的batch(数据大小不能被batch_size整除)return dataloader

4.3.3 神经网络——Neural_network_batch.py

import torchimport torch.nn as nnimport torch.nn.functional as Fclass MY_RNN(nn.Module):"""自己设计的RNN网络"""def __init__(self, len_feature, len_hidden, len_words, typenum=5, weight=None, layer=1, nonlinearity='tanh',batch_first=True, drop_out=0.5):super(MY_RNN, self).__init__()self.len_feature = len_feature # d的大小self.len_hidden = len_hidden # l_h的大小self.len_words = len_words # 单词的个数(包括padding)self.layer = layer # 隐藏层层数self.dropout=nn.Dropout(drop_out) # dropout层if weight is None: # 随机初始化x = nn.init.xavier_normal_(torch.Tensor(len_words, len_feature))self.embedding = nn.Embedding(num_embeddings=len_words, embedding_dim=len_feature, _weight=x).cuda()else: # GloVe初始化self.embedding = nn.Embedding(num_embeddings=len_words, embedding_dim=len_feature, _weight=weight).cuda()# 用nn.Module的内置函数定义隐藏层self.rnn = nn.RNN(input_size=len_feature, hidden_size=len_hidden, num_layers=layer, nonlinearity=nonlinearity,batch_first=batch_first, dropout=drop_out).cuda()# 全连接层self.fc = nn.Linear(len_hidden, typenum).cuda()# 冗余的softmax层,可以不加# self.act = nn.Softmax(dim=1)def forward(self, x):"""x:数据,维度为[batch_size, 句子长度]"""x = torch.LongTensor(x).cuda()batch_size = x.size(0)"""经过词嵌入后,维度为[batch_size,句子长度,d]"""out_put = self.embedding(x) # 词嵌入out_put=self.dropout(out_put) # dropout层# 另一种初始化h_0的方式# h0 = torch.randn(self.layer, batch_size, self.len_hidden).cuda()# 初始化h_0为0向量h0 = torch.autograd.Variable(torch.zeros(self.layer, batch_size, self.len_hidden)).cuda()"""dropout后不变,经过隐藏层后,维度为[1,batch_size, l_h]"""_, hn = self.rnn(out_put, h0) # 隐藏层计算"""经过全连接层后,维度为[1,batch_size, 5]"""out_put = self.fc(hn).squeeze(0) # 全连接层"""挤掉第0维度,返回[batch_size, 5]的数据"""# out_put = self.act(out_put) # 冗余的softmax层,可以不加return out_putclass MY_CNN(nn.Module):def __init__(self, len_feature, len_words, longest, typenum=5, weight=None,drop_out=0.5):super(MY_CNN, self).__init__()self.len_feature = len_feature # d的大小self.len_words = len_words # 单词数目self.longest = longest # 最长句子单词书目self.dropout = nn.Dropout(drop_out) # Dropout层if weight is None: # 随机初始化x = nn.init.xavier_normal_(torch.Tensor(len_words, len_feature))self.embedding = nn.Embedding(num_embeddings=len_words, embedding_dim=len_feature, _weight=x).cuda()else: # GloVe初始化self.embedding = nn.Embedding(num_embeddings=len_words, embedding_dim=len_feature, _weight=weight).cuda()# Conv2d参数详解:(输入通道数:1,输出通道数:l_l,卷积核大小:(行数,列数))# padding是指往句子两侧加 0,因为有的句子只有一个单词# 那么 X 就是 1*50 对 W=2*50 的卷积核根本无法进行卷积操作# 因此要在X两侧行加0(两侧列不加),(padding=(1,0))变成 3*50# 又比如 padding=(2,0)变成 5*50self.conv1 = nn.Sequential(nn.Conv2d(1, longest, (2, len_feature), padding=(1, 0)), nn.ReLU()).cuda() # 第1个卷积核+激活层self.conv2 = nn.Sequential(nn.Conv2d(1, longest, (3, len_feature), padding=(1, 0)), nn.ReLU()).cuda() # 第2个卷积核+激活层self.conv3 = nn.Sequential(nn.Conv2d(1, longest, (4, len_feature), padding=(2, 0)), nn.ReLU()).cuda() # 第3个卷积核+激活层self.conv4 = nn.Sequential(nn.Conv2d(1, longest, (5, len_feature), padding=(2, 0)), nn.ReLU()).cuda() # 第4个卷积核+激活层# 全连接层self.fc = nn.Linear(4 * longest, typenum).cuda()# 冗余的softmax层,可以不加# self.act = nn.Softmax(dim=1)def forward(self, x):"""x:数据,维度为[batch_size, 句子长度]"""x = torch.LongTensor(x).cuda()"""经过词嵌入后,维度为[batch_size,1,句子长度,d]"""out_put = self.embedding(x).view(x.shape[0], 1, x.shape[1], self.len_feature) # 词嵌入"""dropout后不变,记为X"""out_put=self.dropout(out_put) # dropout层"""X经过2*d卷积后,维度为[batch_size,l_l,句子长度+2-1,1]""""""挤掉第三维度(维度从0开始),[batch_size,l_l,句子长度+2-1]记为Y_1""""""注意:句子长度+2-1的2是padding造成的行数扩张"""conv1 = self.conv1(out_put).squeeze(3) # 第1个卷积"""X经过3*d卷积后,维度为[batch_size,l_l,句子长度+2-2,1]""""""挤掉第三维度(维度从0开始),[batch_size,l_l,句子长度+2-2]记为Y_2"""conv2 = self.conv2(out_put).squeeze(3) # 第2个卷积"""X经过4*d卷积后,维度为[batch_size,l_l,句子长度+4-3,1]""""""挤掉第三维度(维度从0开始),[batch_size,l_l,句子长度+4-3]记为Y_3"""conv3 = self.conv3(out_put).squeeze(3) # 第3个卷积"""X经过5*d卷积后,维度为[batch_size,l_l,句子长度+4-4,1]""""""挤掉第三维度(维度从0开始),[batch_size,l_l,句子长度+4-4]记为Y_4"""conv4 = self.conv4(out_put).squeeze(3) # 第4个卷积"""分别对(Y_1,Y_2,Y_3,Y_4)的第二维(维度从0开始)进行pooling""""""得到4个[batch_size,,l_l,1]的向量"""pool1 = F.max_pool1d(conv1, conv1.shape[2])pool2 = F.max_pool1d(conv2, conv2.shape[2])pool3 = F.max_pool1d(conv3, conv3.shape[2])pool4 = F.max_pool1d(conv4, conv4.shape[2])"""拼接得到[batch_size,,l_l*4,1]的向量""""""挤掉第二维(维度从0开始)为[batch_size,,l_l*4]"""pool = torch.cat([pool1, pool2, pool3, pool4], 1).squeeze(2) # 拼接起来"""经过全连接层后,维度为[batch_size, 5]"""out_put = self.fc(pool) # 全连接层# out_put = self.act(out_put) # 冗余的softmax层,可以不加return out_put

4.3.4 结果&画图——comparison_plot_batch.py

import matplotlib.pyplotimport torchimport torch.nn.functional as Ffrom torch import optimfrom Neural_Network_batch import MY_RNN,MY_CNNfrom feature_batch import get_batchdef NN_embdding(model, train,test, learning_rate, iter_times):# 定义优化器(求参数)optimizer = optim.Adam(model.parameters(), lr=learning_rate)# 损失函数 loss_fun = F.cross_entropy# 损失值记录train_loss_record=list()test_loss_record=list()long_loss_record=list()# 准确率记录train_record=list()test_record=list()long_record=list()# torch.autograd.set_detect_anomaly(True)# 训练阶段for iteration in range(iter_times):model.train() # 重要!!!进入非训练模式for i, batch in enumerate(train):x, y = batch # 取一个batchy=y.cuda()pred = model(x).cuda() # 计算输出optimizer.zero_grad() # 梯度初始化loss = loss_fun(pred, y).cuda() # 损失值计算loss.backward() # 反向传播梯度optimizer.step() # 更新参数model.eval() # 重要!!!进入非训练模式(测试模式)# 本轮正确率记录train_acc = list()test_acc = list()long_acc = list()length = 20# 本轮损失值记录train_loss = 0test_loss = 0long_loss=0for i, batch in enumerate(train):x, y = batch # 取一个batchy=y.cuda()pred = model(x).cuda() # 计算输出loss = loss_fun(pred, y).cuda() # 损失值计算train_loss += loss.item() # 损失值累加_, y_pre = torch.max(pred, -1)# 计算本batch准确率acc = torch.mean((torch.tensor(y_pre == y, dtype=torch.float)))train_acc.append(acc)for i, batch in enumerate(test):x, y = batch # 取一个batchy=y.cuda()pred = model(x).cuda() # 计算输出loss = loss_fun(pred, y).cuda() # 损失值计算test_loss += loss.item() # 损失值累加_, y_pre = torch.max(pred, -1)# 计算本batch准确率acc = torch.mean((torch.tensor(y_pre == y, dtype=torch.float)))test_acc.append(acc)if(len(x[0]))>length: # 长句子侦测long_acc.append(acc)long_loss+=loss.item()trains_acc = sum(train_acc) / len(train_acc)tests_acc = sum(test_acc) / len(test_acc)longs_acc = sum(long_acc) / len(long_acc)train_loss_record.append(train_loss / len(train_acc))test_loss_record.append(test_loss / len(test_acc))long_loss_record.append(long_loss/len(long_acc))train_record.append(trains_acc.cpu())test_record.append(tests_acc.cpu())long_record.append(longs_acc.cpu())print("---------- Iteration", iteration + 1, "----------")print("Train loss:", train_loss/ len(train_acc))print("Test loss:", test_loss/ len(test_acc))print("Train accuracy:", trains_acc)print("Test accuracy:", tests_acc)print("Long sentence accuracy:", longs_acc)return train_loss_record,test_loss_record,long_loss_record,train_record,test_record,long_recorddef NN_embedding_plot(random_embedding,glove_embedding,learning_rate, batch_size, iter_times):# 获得训练集和测试集的batchtrain_random = get_batch(random_embedding.train_matrix,random_embedding.train_y, batch_size)test_random = get_batch(random_embedding.test_matrix,random_embedding.test_y, batch_size)train_glove = get_batch(glove_embedding.train_matrix,glove_embedding.train_y, batch_size)test_glove = get_batch(random_embedding.test_matrix,glove_embedding.test_y, batch_size)# 模型建立 torch.manual_seed()torch.cuda.manual_seed()random_rnn = MY_RNN(50, 50, random_embedding.len_words)torch.manual_seed()torch.cuda.manual_seed()random_cnn = MY_CNN(50, random_embedding.len_words, random_embedding.longest)torch.manual_seed()torch.cuda.manual_seed()glove_rnn = MY_RNN(50, 50, glove_embedding.len_words, weight=torch.tensor(glove_embedding.embedding, dtype=torch.float))torch.manual_seed()torch.cuda.manual_seed()glove_cnn = MY_CNN(50, glove_embedding.len_words, glove_embedding.longest,weight=torch.tensor(glove_embedding.embedding, dtype=torch.float))# rnn+randomtorch.manual_seed()torch.cuda.manual_seed()trl_ran_rnn,tel_ran_rnn,lol_ran_rnn,tra_ran_rnn,tes_ran_rnn,lon_ran_rnn=\NN_embdding(random_rnn,train_random,test_random,learning_rate, iter_times)# cnn+randomtorch.manual_seed()torch.cuda.manual_seed()trl_ran_cnn,tel_ran_cnn,lol_ran_cnn, tra_ran_cnn, tes_ran_cnn, lon_ran_cnn = \NN_embdding(random_cnn, train_random,test_random, learning_rate, iter_times)# rnn+glovetorch.manual_seed()torch.cuda.manual_seed()trl_glo_rnn,tel_glo_rnn,lol_glo_rnn, tra_glo_rnn, tes_glo_rnn, lon_glo_rnn = \NN_embdding(glove_rnn, train_glove,test_glove, learning_rate, iter_times)# cnn+glovetorch.manual_seed()torch.cuda.manual_seed()trl_glo_cnn,tel_glo_cnn,lol_glo_cnn, tra_glo_cnn, tes_glo_cnn, lon_glo_cnn= \NN_embdding(glove_cnn,train_glove,test_glove, learning_rate, iter_times)# 画图部分 x=list(range(1,iter_times+1))matplotlib.pyplot.subplot(2, 2, 1)matplotlib.pyplot.plot(x, trl_ran_rnn, 'r--', label='RNN+random')matplotlib.pyplot.plot(x, trl_ran_cnn, 'g--', label='CNN+random')matplotlib.pyplot.plot(x, trl_glo_rnn, 'b--', label='RNN+glove')matplotlib.pyplot.plot(x, trl_glo_cnn, 'y--', label='CNN+glove')matplotlib.pyplot.legend()matplotlib.pyplot.legend(fontsize=10)matplotlib.pyplot.title("Train Loss")matplotlib.pyplot.xlabel("Iterations")matplotlib.pyplot.ylabel("Loss")matplotlib.pyplot.subplot(2, 2, 2)matplotlib.pyplot.plot(x, tel_ran_rnn, 'r--', label='RNN+random')matplotlib.pyplot.plot(x, tel_ran_cnn, 'g--', label='CNN+random')matplotlib.pyplot.plot(x, tel_glo_rnn, 'b--', label='RNN+glove')matplotlib.pyplot.plot(x, tel_glo_cnn, 'y--', label='CNN+glove')matplotlib.pyplot.legend()matplotlib.pyplot.legend(fontsize=10)matplotlib.pyplot.title("Test Loss")matplotlib.pyplot.xlabel("Iterations")matplotlib.pyplot.ylabel("Loss")matplotlib.pyplot.subplot(2, 2, 3)matplotlib.pyplot.plot(x, tra_ran_rnn, 'r--', label='RNN+random')matplotlib.pyplot.plot(x, tra_ran_cnn, 'g--', label='CNN+random')matplotlib.pyplot.plot(x, tra_glo_rnn, 'b--', label='RNN+glove')matplotlib.pyplot.plot(x, tra_glo_cnn, 'y--', label='CNN+glove')matplotlib.pyplot.legend()matplotlib.pyplot.legend(fontsize=10)matplotlib.pyplot.title("Train Accuracy")matplotlib.pyplot.xlabel("Iterations")matplotlib.pyplot.ylabel("Accuracy")matplotlib.pyplot.ylim(0, 1)matplotlib.pyplot.subplot(2, 2, 4)matplotlib.pyplot.plot(x, tes_ran_rnn, 'r--', label='RNN+random')matplotlib.pyplot.plot(x, tes_ran_cnn, 'g--', label='CNN+random')matplotlib.pyplot.plot(x, tes_glo_rnn, 'b--', label='RNN+glove')matplotlib.pyplot.plot(x, tes_glo_cnn, 'y--', label='CNN+glove')matplotlib.pyplot.legend()matplotlib.pyplot.legend(fontsize=10)matplotlib.pyplot.title("Test Accuracy")matplotlib.pyplot.xlabel("Iterations")matplotlib.pyplot.ylabel("Accuracy")matplotlib.pyplot.ylim(0, 1)matplotlib.pyplot.tight_layout()fig = matplotlib.pyplot.gcf()fig.set_size_inches(8, 8, forward=True)matplotlib.pyplot.savefig('main_plot.jpg')matplotlib.pyplot.show()matplotlib.pyplot.subplot(2, 1, 1)matplotlib.pyplot.plot(x, lon_ran_rnn, 'r--', label='RNN+random')matplotlib.pyplot.plot(x, lon_ran_cnn, 'g--', label='CNN+random')matplotlib.pyplot.plot(x, lon_glo_rnn, 'b--', label='RNN+glove')matplotlib.pyplot.plot(x, lon_glo_cnn, 'y--', label='CNN+glove')matplotlib.pyplot.legend()matplotlib.pyplot.legend(fontsize=10)matplotlib.pyplot.title("Long Sentence Accuracy")matplotlib.pyplot.xlabel("Iterations")matplotlib.pyplot.ylabel("Accuracy")matplotlib.pyplot.ylim(0, 1)matplotlib.pyplot.subplot(2, 1, 2)matplotlib.pyplot.plot(x, lol_ran_rnn, 'r--', label='RNN+random')matplotlib.pyplot.plot(x, lol_ran_cnn, 'g--', label='CNN+random')matplotlib.pyplot.plot(x, lol_glo_rnn, 'b--', label='RNN+glove')matplotlib.pyplot.plot(x, lol_glo_cnn, 'y--', label='CNN+glove')matplotlib.pyplot.legend()matplotlib.pyplot.legend(fontsize=10)matplotlib.pyplot.title("Long Sentence Loss")matplotlib.pyplot.xlabel("Iterations")matplotlib.pyplot.ylabel("Loss")matplotlib.pyplot.tight_layout()fig = matplotlib.pyplot.gcf()fig.set_size_inches(8, 8, forward=True)matplotlib.pyplot.savefig('sub_plot.jpg')matplotlib.pyplot.show()

五. 总结

本次实验跑完了15万数据,比上次任务一好多了,推荐用Google的Colab(需要科学上网),或者Kaggle(不需要科学上网)的GPU来跑代码,速度会快很多,比纯CPU快多了。

还有一点需要注意的是,尽管很多地方设置了随机种子,但好像还是每次跑出来的结果不一样?不知道是什么原因,不过结果大体上是相同的·,正确率最高到接近 67%67\%67%.

以上就是本次NLP-Beginner的任务二,谢谢各位的阅读,欢迎各位对本文章指正或者进行讨论,希望可以帮助到大家!

六. 自我推销

我的代码&其它NLP作业传送门

LeetCode练习

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。