深度之眼Paper带读笔记NLP.8:TextCNN.Baseline.04

前言

本课程来自深度之眼deepshare.net,部分截图来自课程视频。
文章标题:Convolutional Neural Networks for Sentence Classification
原标题翻译:基于卷积神经网络的句子分类
作者:Yoon Kim
单位:New York University
发表会议及时间:EMNLP 2014
在线LaTeX公式编辑器
本文是一个短文,但是是用神经网络进行文本分类的开山之作。
整个NLP的基于神经网络的词向量流行的方法:Word2Vec,GloVe,LSTM,CNN,Transformer
因此,还配有另外一篇长文来读:A Sensitivity Analysis of (and Practitioners’ Guide to) Convolutional Neural Networks for Sentence Classification,主要内容是TextCNN参数选择。
a. 文本分类
文本分类,指的是给定分类体系,将文本分类到某个或者某几个类别中。根据其目标类别的数量,文本分类涵盖了二分类、多分类、多标签分类等常见分类场景。应用场景有:垃圾邮件分类、情感分类、舆论分析、信息流分类。
b. 文本分类的三种方法:
基于规则的文本分类方法:
优点:易于实现、无需训练数据
缺点:人工成本较高,效果较差
基于特征的文本分类方法:
优点:易于复用、能够进行信息的筛选
缺点:人工提取特征的成本很高
基于神经网络的文本分类方法:
优点:无需人工特征、一般效果较好
缺点:可解释性差、训练资源消耗大
c. 卷积神经网络
多层感知机是一种全连接的结构,但是全连接的网络会存在一定程度的冗余,卷积神经网络通过局部连接和权重共享的方法来实现对多层感知机的共享。

第一课 论文导读

总览、学习目标

在这里插入图片描述
在这里插入图片描述

论文背景

这里是原文中提到的三个方面:
在这里插入图片描述
这里不过多介绍,直接贴几个图:

深度学习的发展

精度的提升
在这里插入图片描述
研究的热度
在这里插入图片描述

词向量的发展

bengio大佬的NNLM
在这里插入图片描述
word2vec
在这里插入图片描述
GloVe
在这里插入图片描述

卷积神经网络的发展

CNN
在这里插入图片描述
Gradient-based learning applied to document recognition
在这里插入图片描述
ImageNet Classification with Deep Convolutional Neural Networks
CNN在NLP领域的应用:
在这里插入图片描述
A Convolutional Neural Network for Modelling Sentence
在这里插入图片描述
语义搜索
Learning Semantic Representation Using Convolutional Neural Networks for Web Search

研究成果及意义

研究成果

在7个文本分类任务中的四个上取得了最好的分类效果。
·CNN-rand:使用随机初始化的词向量
·CNN-static:使用静态预训练的词向量
·CNN-non-static:使用微调的预训练的词向量
·CNN-multichannel:同时使用静态预训练的词向量和微调的预训练的词向量
在这里插入图片描述
PS:上图中的其他模型的结果是直接从论文中摘取的,如果出现-符号代表改论文没有在该数据集中进行过实验。

研究意义

·开启了基于深度学习的文本分类的序幕
在这里插入图片描述
·推动了卷积神经网络在自然语言处理的发展
应用领域:CNN成为自然语言处理中一个重要的特征提取器,用于字符嵌入、机器翻译等各种自然语言处理任务。

文本分类简介

文本分类Text Classification

文本分类,指的是给定分类体系,将文本分类到某个或者某几个类别中。根据其目标类别的数量,文本分类涵盖了二分类、多分类、多标签分类等常见分类场景。文本分类是计算语言学的一个分支,同时也是自然语言处理中最基础的一个任务。

文本分类研究意义The Significance of Text Classification Research

文本分类是自然语言处理中最基础的一个任务,涵盖了新闻主题分类、情感分类、关系分类、意图识别等等常见的自然语言处理场景。所以,开展文本分类相关的研究具有十分重要的理论意义和应用价值。

文本表示(略)

见上节

文本分类发展历史The History of Text Classification

趋势:让机器更“准确”地捕获文本中的关键信息
基于规则的文本分类>基于特征的文本分类>基于神经网络的文本分类

基于规则的文本分类

基本思想:就是使用人工编写特定的规则来进行分类,一般情况下,当文本中含有特定的词语、短语或者模式时即将其判断为相应的类别,是最古老也最简单的一种分类方法。
大体流程:
输入文本>规则匹配>输出类别
举例说明,如果一段话中出现了很好、很快就认为这段话是一个正面的评论:
屏幕画质 很 好 ‾ \underline {很好} 为了省电可以调低画质,电量经用,充电那是真的快,一分钟一格,拍照三摄高清,系统运行 很 快 ‾ \underline {很快} 感觉不到卡顿,的确是一款 很 好 ‾ \underline {很好} 的手机,越用越喜欢,值得推荐!
挺失望的。。。拍照不错。屏幕不错。指纹也很灵敏 很 好 ‾ \underline {很好} 。。就是破玩意居然会卡。而且还会系统崩溃黑屏。。。。吹的挺好。评论也好。网上宣传很 很 好 ‾ \underline {很好} 。。就是用着不咋好。。。仁者见仁智者见智。真实评论。不吹不黑。。。

基于特征的文本分类

基本思想:通过人工设计和提取特征,例如:词法特征、句法特征等,使用机器学习模型来捕获句子中所蕴含的关键信息,从而来减少噪声词对最终结果的影响。
以向量空间模型为例:
1.使用词袋模型来表示每个词
2.使用词项作为特征项,使用词在文档中的TF-IDF值作为词的权重(特征权重)
3.使用加权求和的得到文本表示
4.训练一个分类器(LR、SVM)进行文本分类
(1.3)屏幕[0,1,0,0,0]
(0.8)画质[1,0,0,0,0]
(2.5)很好[0,0,0,0,1]
屏幕画质很好[0.8,1.3,0,0.2.5]

基于神经网络的文本分类

基本思想:首先将输入的文本进行分词等一系列基础操作,随后将句子中的单词转化为低维的词表示,使用编码器(卷积神经网络、循环神经网络)得到句子表示,最终得到文本的目标类别。
大体流程:
输入文本>词表示>编码器>文本表示>输出类别

小结

基于规则的文本分类方法:
优点:易于实现、无需训练数据
缺点:人工成本较高,效果较差
基于特征的文本分类方法:
优点:易于复用、能够进行信息的筛选
缺点:人工提取特征的成本很高基于
神经网络的文本分类方法:
优点:无需人工特征、一般效果较好
缺点:可解释性差、训练资源消耗大

卷积神经网络相关技术

卷积神经网络

卷积神经网络:多层感知机是一种全连接的结构,但是全连接的网络会存在一定程度的冗余,卷积神经网络通过局部连接和权重共享的方法来实现对多层感知机的共享。
在这里插入图片描述
一维卷积操作:
给定一个序列X={x1,x2…,xn},和一个滤波器F={f1,fz.……fm},卷积操作被定义为:
y t = ∑ k = 1 m f k ⋅ x t − k + 1 y_t=\sum_{k=1}^mf_k\cdot x_{t-k+1} yt=k=1mfkxtk+1
下面是一维卷积的例子:
在这里插入图片描述
在这里插入图片描述
二维卷积操作:
在这里插入图片描述
对应的计算为:
1×1+0×1+0×1+
2×0+1x-1+1×0+
1×0+1×-1+2×1=1
池化操作:池化本身是一种降采样操作,用于降低特征维度并保留有效信息。
1.减少模型参数,避免过拟合,提高训练速度
2.保证特征的位置、旋转、伸缩不变性(CV)
3.将变长的输入转换成固定长度(NLP)
在这里插入图片描述

前期知识储备

文本分类
了解文本分类的概念,掌握文本分类的流程和主流方法
CNN
了解卷积神经网络(CNN)的结构,掌握CNN的基本工作原理(卷积、池化等)
词表示
了解词表示的思想,熟悉其代表性方法word2vec的训练和使用方法

第二课 论文精读

在这里插入图片描述

论文整体框架

0.摘要
1.引言
2.模型
3.数据集和实验设置
4.实验结果和讨论
5.结论

  1. Introduction
  2. Model
    2.1Regularization(为什么还能只有一个小节。。。)
  3. Datasets and Experimental Setup
    3.1 Hyperparameters and Training
    3.2 Pre-trained Word Vectors
    3.3 Model Variations
  4. Results and Discussion
    4.1 Multichannel vs.Single Channel Models
    4.2Static vs.Non-static Representations
    4.3 Further Observations
  5. Conclusion

摘要

1.使用简单的CNN模型在预训练词向量基本上进行微调就可以在文本分类任务上得到很好的结果。
We report on a series of experiments with convolutional neural networks (CNN) trained on top of pre-trained word vectors for sentence-level classification tasks.
2.通过对词向量进行微调而获得的任务指向的词向量能够得到更好的结果。
We show that a simple CNN with little hyperparameter tuning and static vectors achieves excellent results on multiple benchmarks. Learning task-specific vectors through fine-tuning offers further gains in performance.
3.我们也提出了一种即使用静态预训练词向量又使用任务指向词向量的文本分类模型。
We additionally propose a simple modification to the architecture to allow for the use of both task-specific and static vectors.
4.最终我们在7个文本分类任务中的四个上都取得了最好的分类准确率。
The CNN models discussed herein improve upon the state of the art on 4 out of 7 tasks, which include sentiment analysis and question classification.

引言

在这里插入图片描述
在这里插入图片描述

论文提出的模型

模型结构貌似很简单(从左到右分四个部分),第一层是输入层把最原始的词转化为向量表示,然后接下来是卷积层提取不同层次的特征,池化层是对卷积层的结果做Max pooling(得到句子的向量,即句表示),最后经过全连接和softmax进行输出得到句子的类别。
在这里插入图片描述
在这里插入图片描述

模型结构-输入层Model Architecture-Input Layer

第i个词的词表示:
x i ∈ ℜ k x_i\in \real^k xik
即一个 k k k维的实值向量
长度为 n n n的序列表示:
x 1 : n = x 1 ⊕ x 2 ⊕ . . . ⊕ x n x_{1:n}=x_1\oplus x_2\oplus ...\oplus x_n x1:n=x1x2...xn
其中, ⊕ \oplus 为向量的拼接concat操作
输入层的矩阵每一行是一个词的词向量。 k k k是词向量embedding的维度,文章中是300, n n n是句子中最长的句子中词的个数,文章中用的是64(不够64的句子可以用padding)
在这里插入图片描述

模型结构-卷积层Model Architecture-Convolutional Layer

卷积操作:
c i = f ( w ⋅ x i : i + h − 1 + b ) (2) c_i=f(w\cdot x_{i:i+h-1}+b)\tag 2 ci=f(wxi:i+h1+b)(2)
上述公式表示使用窗口大小为 h h h的卷积核(filter), h h h也就是卷积核的高度,卷积核的宽度是词向量的维度。 h h h实际上和之前的Word2Vec中的n-gram中 n n n差不多,就是如果我们想用周围2个词预测中间词,即Bi-gram,相当于 h = 2 h=2 h=2
得到特征图(feature map):
c = [ c 1 , c 2 , … , c n − h + 1 ] c=[c_1,c_2,…,c_{n-h+1}] c=[c1c2,,cnh+1]
即当前卷积核所有输出的拼接。注意看上面的图,卷积核有不同的大小,有3(黄色)和2(红色)之分。

模型结构-池化层Model Architecture-Pooling Layer

池化(pooling)操作:
c ^ = m a x { c } \widehat c=max\{c\} c =max{c}
即对c的每一维都取最大值,原文是这样描述:The idea is to capture the most important feature—one with the highest value—for each feature map.
值越大,越重要
聚合多个卷积核的结果:
z = [ c ^ 1 , … , c ^ m ] z=[\widehat c_1,…,\widehat c_m] z=[c 1c m]
即所有卷积核结果的拼接
注:一个卷积核池化后只得到一个结果,所以通常我们会使用多个卷积核(调整感受野)来捕获多种特征。例如上面的图中,红色是2个单词的感受野,黄色是三个单词的感受野。
另外一个特点就是:This pooling scheme naturally deals with variable sentence lengths.
这个结构可以用于变长的输出。因为不论句子多长,最后经过池化(pooling)操作后的结果大小只和卷积核大小有关。

多通道结构

In one of the model variants, we experiment with having two ‘channels’ of word vectors—one that is kept static throughout training and one that is fine-tuned via backpropagation (section 3.2). In the multichannel architecture, illustrated in figure 1(就是上面那个彩色的图), each filter is applied to both channels and the results are added to calculate ci in equation (2)(就是上面的卷积公式2). The model is otherwise equivalent to the single channel architecture.
这里提到了,词向量模型用了两个channels,一个是静态,一个是动态的。
静态的意思是在反向传播到词向量这里,词向量不变,词向量不变意味着不进行反向传播;
动态的意思是在反向传播到词向量这里,词向量微调,词向量微调意味着进行反向传播;

模型结构-输出层Model Architecture-Output Layer

全连接层:
y = w ⋅ z + b y=w·z+b y=wz+b
将句子向量转化为一个维度和类别数相同的向量
随机失活(Dropout):
y = w ⋅ ( z o r ) + b y=w·(zo r)+b y=w(zor)+b
r是失活向量,以一定概率取值为1,否则取值为0

在另外一篇文章(A Sensitivity Analysis of (and Practitioners’ Guide to) Convolutional Neural Networks for Sentence Classification)中对这个模型的描述更加清楚一点:

另外一篇文章的TextCNN结构

在这里插入图片描述
这个结构中用了三个大小的卷积核,分别是2、3、4,每个卷积核有2个filter(或者叫channel)
上图中输入的大小是7×5,这里的5是embeddingsize
六个卷积filter,分别是4×5.4×5.3×5.3×5.2×5.2×5,这里的5是要和上面的embeddingsize一样大。
经过卷积后的大小计算公式为:词数量-filter大小+1
6个filters得到6个结果,分别对这6个结果进行maxpooling后concat到一起
然后softmax得到二分类。

模型的正则化

·Dropout:抓爆
在神经网络的传播过程中,让某个神经元以一定的概率p停止工作,从而增加模型的泛化能力。具体看:https://blog.csdn.net/oldmao_2001/article/details/90714657
·L2-正则
L2-正则化模型等价于
min w L ( w ) = min w ( E D ( w ) + λ ∑ i = 1 n w i 2 ) \underset{w}{\text{min}}L(w)=\underset{w}{\text{min}}(E_D(w)+\lambda\sum_{i=1}^nw_i^2) wminL(w)=wmin(ED(w)+λi=1nwi2)
等价于凸优化问题:
{ min w E D ( w ) ∑ i = 1 n w i 2 ≤ C , 其 中 C 与 正 则 化 参 数 λ 成 反 比 关 系 \left\{\begin{matrix} \underset{w}{\text{min}}E_D(w) \\ \sum_{i=1}^nw_i^2\leq C,其中C与正则化参数\lambda成反比关系 \end{matrix}\right. {wminED(w)i=1nwi2C,Cλ
具体看:https://blog.csdn.net/oldmao_2001/article/details/104570191

在这里插入图片描述

实验和结果

数据集Datasets

MR:电影评论极性判断数据集
SST-1:斯坦福情感分类标准数据集(5类:(very positive, positive, neutral, negative, very negative)
SST-2:斯坦福情感二分类数据集
Subj:主客观判断数据集
TREC:TREC问题类型数据集
CR:商品(cameras, MP3s etc.)评价极性判断数据集
MPQA:MPQA的观点极性判断数据集
下表中,c代表分类数量
l应该是句子的最大长度
N是数据集大小
V是词表大小
Vpre是在预训练词表中出现的单词数量
test表示是否划分验证集,如果没有划分则采用CV(cross valid交叉验证的方式划分)

在这里插入图片描述

实验设置Experiment Setup

部分超参设置:
卷积核感受野:{3,4,5}
卷积核特征图大小:100
Dropout概率:0.5
L2正则化约束:3
batchsize:50
以上参数都是在SST-2数据集上进行网格搜索得来。
优化算法:
Adadelta
预训练词向量:
word2vec/random
实验设置:

CNN-rand:词向量随机初始化,同时当做训练过程中优化的参数;Baseline
CNN-static:词向量使用word2vec,同时固定不变;如果没有在词表出现的单词则随机初始化
CNN-non-static:词向量使用word2vec,但是会在训练过程中微调(fine tuned);
CNN-multichannel:CNN-static和CNN-non-static的混合版本,即有两种类型的输入

实验结果Experiment Results

实验主要对比了之前基于机器学习和人工特征的方法,整体来看:
随机初始化词向量(CNN-rand)的方法性能并不好;
使用预训练词向量(CNN-static)的方法效果普遍较好;
对预训练词向量进行微调(CNN-non-static)效果又有提升;
CNN-multichannel在小规模的数据集上有更好的表现,体现了一种折中思想,即既不希望微调后的词向量距离原始值太远,但又允许其具有一定的变化空间。
在这里插入图片描述
原始的词向量训练结果中,bad对应的最相近词为good,因为其上下文语境是极其类似的;在non-static的版本中,bad对应的最相近词为terrible,因为在微调的过程中,词向量值发生改变,从而更加贴切任务数据集(这里是一个情感分类的数据集)
句子中的【!】最接近一些表达形式较为激进的词汇,如lush等;而【,】则接近于一些连接词,这和我们的主观感受也是相符的。
在这里插入图片描述

另外一篇文章中的实验结果

超参分析
·Embedding方式
·卷积核大小
·卷积核个数
·激活函数
·Dropout
·L2正则
因为TextCNN模型中虽然把所有参数都固定,但是还是有很大的不确定性,例如交叉验证使用同样的10 fold,得到结果:
在这里插入图片描述
所以有必要对这个模型的参数进行研究。

Embedding方式

文章用了三种方法:
1.Word2Vec,使用100亿个词,300维
2.GloVe,使用840亿个词,300维
3.Word2Vec+GloVe,两个concat到一起,600维
9个数据集。并不确定那种方法好,还是和任务(数据集)有关
在这里插入图片描述

卷积核大小

单个卷积核进行实验
在这里插入图片描述
在这里插入图片描述
在实际实验选择超参数的时候,我们可以分组进行,观察指标是否上升或者下降,例如:
1.3.5.7
9.11.13.15

在这里插入图片描述

卷积核个数

在确定了单个卷积核最佳大小为7后,下面继续实验,看多个卷积核的情况:
一般都是出现在7附近,例如:777.789.678.567
在这里插入图片描述
原文提到,下面这个图表示使用相同的大小的卷积核(如:3333)组合比不同大小卷积核组合效果(2345)一般情况下要差一些(只是一般情况,上面的表格就是相同卷积核效果好)。
在这里插入图片描述

激活函数

在这里插入图片描述

Dropout

貌似用不用没啥区别?
在这里插入图片描述

L2正则

这个也和具体数据集有关系
在这里插入图片描述

结论

1、一个相同参数的实验要多跑几次,如果结果差别较大,说明随机初始化对结果有很大影响,需要把随机的东西固定住,如果结果还是差别很大,那么说dropout等一些随机因素的影响很大,这个时候就要考虑多运行几次,本文中是100次,取平均、最小、最大。
2、虽然不同的词向量表示在不同任务上的结果表现不一样(只能靠试),但是总体而言比独热编码肯定要强;也有可能使用更加复杂的模型表现会更好。
3、卷积核大小对结果有很大影响,需要对其进行超参数搜索。
4、正则化没很大用。
模型构建建议:
1、可以直接采用默认参数,原文的表2
2、对单卷积核进行调参,可以在1-10范围内弄,如果句子比较长(CR数据集),卷积核可以大些(感受野大结果好),然后再尝试多个卷积核的组合
3、feature map数量可以在100-600内搜索(如果最优值是600,则需要往下继续尝试700.800…),dropout:0-0.5
4、pooling选max就好
5、feature map数量选太大,造成效果变差,可以考虑强一点的正则,例如:dropout>0.5,减少L2弄

讨论和总结

关键点
·预训练的词向量—-Word2Vec、Glove
·卷积神经网络结构——一维卷积、池化层
·超参选择——卷积核选择、词向量方式选择
创新点
·提出了基于CNN的文本分类模型TextCNN
·提出了多种词向量设置方式
·在四个文本分类任务上取得最优的结果
·对超参进行大量实验和分析
开创性地使用神经网络结合预训练的词向量来解决文本分类问题
进行充分的实验分析,探讨词向量对模型性能的影响
探讨神经网络中的一些操作对当前任务的影响
启发点
·在预训练模型的基础上微调就能够得到非常好的结果,这说明预训练词向量学习到了一些通用的特征。
Despite little tuning of hyperparameters,this simple model achieves excellent results on multiplebenchmarks,suggesting that the pre-trained vectors are‘universal’feature extractors that can be utilized for various classification tasks.
·在预训练词向量的基础上使用简单模型比复杂模型表现的还要好
Even a simple model with static vectors(CNN-static)performs remarkably well,giving competitive results against the more sophisticated deep learning models that utilize complex pooling schemes
·对于不在预训练词向量中的词,微调能够使得它们能够学习更多的意义。
For(randomly initialized)tokens not in the set of pre-trained vectors,fine-tuning allows them to learn more meaningful representations
小结
摘要:
论文的高度概括,包含作者研究思路。
引言:
简述论文背景,定义问题,发现当前解决问题方法弊端,提出改进方案,展示改进方案的实验效果。
实验:
充分考虑各种可能性,探讨不同情况下模型的性能,并且对结果展开一系列的可解释性分析。

作业

  1. 回答下列问题:
    a. 文章中的公式2里,w的维度为什么是h×k?
    因为卷积核是一维的,所以第二个维度与输入一样
    b. 文章中的公式5的目的是什么,为什么用z和r相乘?
    抓爆,r是抓爆的比例
  2. 关闭一切资源,从零开始画出Text CNN的网络结构

复现

准备工作

项目环境配置
·Python3.5
·jupyter notebook
·torch 1.4.0
·numpy 1.16.2
·gensim 3.8.1用来读取预训练词向量
·torchsummary 1.5.1

数据集下载

MR数据集https:///www.cs.cornell.edu/people/pabo/movie-review-data/
在这里插入图片描述
SST数据集 https://nlp.stanford.edu/sentiment/
预训练词向量word2vec下载:https:/pan.baidu.com/s/1jJ9eAaE
或者:http://pan.baidu.com/s/1kTCQqft
glove下载http://downloads.cs.stanford.edu/nlp/data/glove.840B.300d.zip

数据处理

# coding:utf-8
from torch.utils import data
import os
import random
import numpy as np
import nltk
import torch
from gensim.test.utils import datapath, get_tmpfile
from gensim.models import KeyedVectors

# 总体步骤:
# ·词向量导入
# ·数据集加载
# ·构建word2id并pad成相同长度
# ·求词向量均值和方差
# ·生成词向量
# ·生成训练集、验证集和测试集

# 继承自torch的Dataset,要实现:
# __init__
# __getitem__
# __len__
class MR_Dataset(data.Dataset):
    def __init__(self, state="train", k=0, embedding_type="word2vec"):

        self.path = os.path.abspath('.')
        if "data" not in self.path:
            self.path += "/data"
        # 数据集加载,每行都是一句话
        pos_samples = open(self.path + "/MR/rt-polarity.pos", errors="ignore").readlines()
        neg_samples = open(self.path + "/MR/rt-polarity.neg", errors="ignore").readlines()
        # 把正负样本放在一块
        datas = pos_samples + neg_samples
        # datas = [nltk.word_tokenize(data) for data in datas]
        # 用空格进行分词
        datas = [data.split() for data in datas]
        # 求句子最大长度,将所有句子pad成一样的长度;
        # 有时也可以pad为平均长度,如果使用平均长度,对于超过长度的句子需要截断。
        max_sample_length = max([len(sample) for sample in datas])
        # 为正负样本设置标签
        labels = [1] * len(pos_samples) + [0] * len(neg_samples)
        word2id = {"<pad>": 0}  # 生成word2id,pad对应0
        for i, data in enumerate(datas):
            for j, word in enumerate(data):
                # 词不还没加入word2id中则加入,并设置其id
                if word2id.get(word) is None:
                    word2id[word] = len(word2id)
                # 设置每个句子中所有词,替换为ID
                datas[i][j] = word2id[word]
            # 将句子按最大长度进行pad
            datas[i] = datas[i] + [0] * (max_sample_length - len(datas[i]))
            # 如果是按平均长度则按下面语句进行截断或补齐,max_sample_length代表平均长度
            # datas[i] = datas[i][0:max_sample_length]+[0]*(max_sample_length-len(datas[i]))
        self.n_vocab = len(word2id)
        self.word2id = word2id

        #根据配置取不同的预训练词向量
        if embedding_type == "word2vec":
            self.get_word2vec()
        elif embedding_type == "glove":
            self.get_glove_embedding()
        else:
            pass
        # self.get_word2vec()
        # 由于训练集中的数据前半部分是正样本,后半部分是负样本,需要打乱训练集
        # 把数据和标签放到一起打乱
        c = list(zip(datas, labels))  # 打乱训练集
        random.seed(1)
        random.shuffle(c)
        # 再把数据和标签分开
        datas[:], labels[:] = zip(*c)

        # 生成训练集、验证集和测试集
        # 总的数据分成10份,第k份作为测试集,其他9份再分
        # 其他9分的后10%做为验证集,前90%做为训练集
        if state == "train":  # 生成训练集
            # 取除第k份外其他9份
            self.datas = datas[:int(k * len(datas) / 10)] + datas[int((k + 1) * len(datas) / 10):]
            self.labels = labels[:int(k * len(datas) / 10)] + labels[int((k + 1) * len(labels) / 10):]
            # 取前90%做为训练集
            self.datas = np.array(self.datas[0:int(0.9 * len(self.datas))])
            self.labels = np.array(self.labels[0:int(0.9 * len(self.labels))])
        elif state == "valid":  # 生成验证集
            # 取除第k份外其他9份
            self.datas = datas[:int(k * len(datas) / 10)] + datas[int((k + 1) * len(datas) / 10):]
            self.labels = labels[:int(k * len(datas) / 10)] + labels[int((k + 1) * len(labels) / 10):]
            # 取后10%做为验证集
            self.datas = np.array(self.datas[int(0.9 * len(self.datas)):])
            self.labels = np.array(self.labels[int(0.9 * len(self.labels)):])
        elif state == "test":  # 生成测试集
            # 第k份作为测试集
            self.datas = np.array(datas[int(k * len(datas) / 10):int((k + 1) * len(datas) / 10)])
            self.labels = np.array(labels[int(k * len(datas) / 10):int((k + 1) * len(datas) / 10)])

    def __getitem__(self, index):
        return self.datas[index], self.labels[index]

    def __len__(self):
        return len(self.datas)

    def get_glove_embedding(self):
        '''
        生成glove词向量
        :return: 根据词表生成词向量
        '''
        if not os.path.exists(self.path + "/glove_embedding_mr.npy"):  # 如果已经保存了词向量,就直接读取
            # 与word2vec不一样的是glove文件是txt格式,要先转换为word2vec格式
            # 这个转换过程比较慢,所以转换好就先保存,下次直接读。
            if not os.path.exists(self.path + "/test_word2vec.txt"):
                glove_file = datapath(self.path + '/glove.840B.300d.txt')
                # 指定转化为word2vec格式后文件的位置
                tmp_file = get_tmpfile(self.path + "/glove_word2vec.txt")
                from gensim.scripts.glove2word2vec import glove2word2vec
                glove2word2vec(glove_file, tmp_file)
            else:
                tmp_file = get_tmpfile(self.path + "/glove_word2vec.txt")
            print("Reading Glove Embedding...")
            # 注意这里的binary=True不用写。
            wvmodel = KeyedVectors.load_word2vec_format(tmp_file)

            # 求词向量均值和方差
            # 论文中提到,用方差对未知词进行初始化对于训练词向量的效果很不错
            tmp = []
            for word, index in self.word2id.items():
                try:
                    tmp.append(wvmodel.get_vector(word))
                except:
                    pass
            mean = np.mean(np.array(tmp))
            std = np.std(np.array(tmp))
            print(mean, std)
            # 用上面的词向量均值和方差来生成词向量
            vocab_size = self.n_vocab
            embed_size = 300
            embedding_weights = np.random.normal(mean, std, [vocab_size, embed_size])  # 正态分布初始化方法
            for word, index in self.word2id.items():
                try:
                    # 如果预训练词向量中有对应的词就使用预训练的词向量,否则就用正态分布初始化的词向量
                    embedding_weights[index, :] = wvmodel.get_vector(word)
                except:
                    pass
            # 由于每次读取这个东西很费时,所以处理好后保存下来,下次直接读取
            np.save(self.path + "/glove_embedding_mr.npy", embedding_weights)  # 保存生成的词向量
        else:
            embedding_weights = np.load(self.path + "/glove_embedding_mr.npy")  # 载入生成的词向量
        self.weight = embedding_weights

    def get_word2vec(self):
        '''
        生成word2vec词向量
        :return: 根据词表生成的词向量
        '''
        if not os.path.exists(self.path + "/word2vec_embedding_mr.npy"):  # 如果已经保存了词向量,就直接读取
            print("Reading word2vec Embedding...")
            # 加载预训练的Word2Vec词向量
            wvmodel = KeyedVectors.load_word2vec_format(self.path + "/GoogleNews-vectors-negative300.bin.gz",
                                                        binary=True)
            tmp = []
            for word, index in self.word2id.items():
                try:
                    tmp.append(wvmodel.get_vector(word))
                except:
                    pass
            mean = np.mean(np.array(tmp))
            std = np.std(np.array(tmp))
            print(mean, std)
            vocab_size = self.n_vocab
            embed_size = 300
            embedding_weights = np.random.normal(mean, std, [vocab_size, embed_size])  # 正太分布初始化方法
            for word, index in self.word2id.items():
                try:
                    embedding_weights[index, :] = wvmodel.get_vector(word)
                except:
                    pass
            np.save(self.path + "/word2vec_embedding_mr.npy", embedding_weights)  # 保存生成的词向量
        else:
            embedding_weights = np.load(self.path + "/word2vec_embedding_mr.npy")  # 载入生成的词向量
        self.weight = embedding_weights


if __name__ == "__main__":
    mr_train_dataset = MR_Dataset()
    print(mr_train_dataset.__len__())
    print(mr_train_dataset[0])
    mr_valid_dataset = MR_Dataset("valid")
    print(mr_valid_dataset.__len__())
    print(mr_valid_dataset[0])
    mr_test_dataset = MR_Dataset("test")
    print(mr_test_dataset.__len__())
    print(mr_test_dataset[0])

模型构建

# -*- coding: utf-8 -*-
import torch
import torch.autograd as autograd
import torch.nn as nn
import torch.nn.functional as F
from .BasicModule import BasicModule


class TextCNN(BasicModule):

    def __init__(self, config):
        super(TextCNN, self).__init__()
        # 嵌入层
        if config.embedding_pretrained is not None:
            self.embedding = nn.Embedding.from_pretrained(config.embedding_pretrained, freeze=False)
        else:
            self.embedding = nn.Embedding(config.n_vocab, config.embed_size)
        # 卷积层,按config中设置卷积核数量进行设置
        if config.cuda:
            self.convs = [nn.Conv1d(config.embed_size, config.filter_num, filter_size).cuda()
                          for filter_size in config.filters]
        else:
            self.convs = [nn.Conv1d(config.embed_size, config.filter_num, filter_size)
                          for filter_size in config.filters]
        # Dropout层
        self.dropout = nn.Dropout(config.dropout)
        # 分类层
        self.fc = nn.Linear(config.filter_num*len(config.filters), config.label_num)
    def conv_and_pool(self,x,conv):
        x = F.relu(conv(x))
        # 池化层
        x = F.max_pool1d(x,x.size(2)).squeeze(2)
        return x
    def forward(self, x):
        out = self.embedding(x) # batch_size*length*embedding_size
        # 这里把第1个维度和第2个维度交换一下(起始维度编号是0)
        out = out.transpose(1, 2).contiguous() # batch_size*embedding_size*length
        # 这里对所有卷积核做卷积的结果(这里卷积和pooling一起做:conv_and_pool)进行循环
        out = torch.cat([self.conv_and_pool(out, conv) for conv in self.convs], 1) # batch_size*(filter_num*len(filters))
        out = self.dropout(out)
        out = self.fc(out) # batch_size*label_num
        return out


if __name__ == '__main__':
    print('running the TextCNN...')
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oldmao_2000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值