-
2021-12-21 18:00:02
文章目录
0 前言
这几天在帮助同学开发基于深度学习的情感分类项目,这里学长复现了两篇论文的实现方法,带大家实现一个基于深度学习的文本情感分类器。
**基于LSTM的文本情感分类 **
毕设帮助,开题指导,技术解答 🇶746876041
1 项目背景
文本情感分析作为NLP的常见任务,具有很高的实际应用价值。这里学长将采用LSTM模型,训练一个能够识别文本postive, neutral, negative三种情感的分类器。
本文的目的是快速熟悉LSTM做情感分析任务,所以我提到的只是一个baseline,并在最后分析了其优劣。对于真正的文本情感分析,在本文提到的模型之上,还可以做很多工作,以后有空的话,我会可以再做优化。
2 文本情感分类理论
3 RNN
RNN相对于传统的神经网络,它允许我们对向量序列进行操作:输入序列、输出序列、或大部分的输入输出序列。如下图所示,每一个矩形是一个向量,箭头则表示函数(比如矩阵相乘)。输入向量用红色标出,输出向量用蓝色标出,绿色的矩形是RNN的状态(下面会详细介绍)。
从左到右:
- (1)没有使用RNN的Vanilla模型,从固定大小的输入得到固定大小输出(比如图像分类)。
- (2)序列输出(比如图片字幕,输入一张图片输出一段文字序列)。
- (3)序列输入(比如情感分析,输入一段文字然后将它分类成积极或者消极情感)。
- (4)序列输入和序列输出(比如机器翻译:一个RNN读取一条英文语句然后将它以法语形式输出)。
- (5)同步序列输入输出(比如视频分类,对视频中每一帧打标签)。我们注意到在每一个案例中,都没有对序列长度进行预先特定约束,因为递归变换(绿色部分)是固定的,而且我们可以多次使用。
3.1 word2vec 算法
建模环节中最重要的一步是特征提取,在自然语言处理中也不例外。
在自然语言处理中,最核心的一个问题是,如何把一个句子用数字的形式有效地表达出来?如果能够完成这一步,句子的分类就不成问题了。
显然,一个最初等的思路是:给每个词语赋予唯一的编号1,2,3,4…,然后把句子看成是编号的集合,比如假设1,2,3,4分别代表“我”、“你”、“爱”、“恨”,那么“我爱你”就是[1, 3, 2],“我恨你”就是[1, 4, 2]。这种思路看起来有效,实际上非常有问题,比如一个稳定的模型会认为3跟4是很接近的,因此[1, 3, 2]和[1, 4, 2]应当给出接近的分类结果,但是按照我们的编号,3跟4所代表的词语意思完全相反,分类结果不可能相同。因此,这种编码方式不可能给出好的结果。
同学们也许会想到,我将意思相近的词语的编号凑在一堆(给予相近的编号)不就行了?嗯,确实如果,如果有办法把相近的词语编号放在一起,那么确实会大大提高模型的准确率。可是问题来了,如果给出每个词语唯一的编号,并且将相近的词语编号设为相近,实际上是假设了语义的单一性,也就是说,语义仅仅是一维的。然而事实并非如此,语义应该是多维的。
比如我们谈到“家园”,有的人会想到近义词“家庭”,从“家庭”又会想到“亲人”,这些都是有相近意思的词语;另外,从“家园”,有的人会想到“地球”,从“地球”又会想到“火星”。换句话说,“亲人”、“火星”都可以看作是“家园”的二级近似,但是“亲人”跟“火星”本身就没有什么明显的联系了。此外,从语义上来讲,“大学”、“舒适”也可以看做是“家园”的二级近似,显然,如果仅通过一个唯一的编号,是很难把这些词语放到适合的位置的。
3.2 高维 Word2Vec
从上面的讨论可以知道,很多词语的意思是各个方向发散开的,而不是单纯的一个方向,因此唯一的编号不是特别理想。那么,多个编号如何?换句话说,将词语对应一个多维向量?不错,这正是非常正确的思路。
为什么多维向量可行?首先,多维向量解决了词语的多方向发散问题,仅仅是二维向量就可以360度全方位旋转了,何况是更高维呢(实际应用中一般是几百维)。其次,还有一个比较实际的问题,就是多维向量允许我们用变化较小的数字来表征词语。怎么说?我们知道,就中文而言,词语的数量就多达数十万,如果给每个词语唯一的编号,那么编号就是从1到几十万变化,变化幅度如此之大,模型的稳定性是很难保证的。如果是高维向量,比如说20维,那么仅需要0和1就可以表达2^20=1048576220=1048576(100万)个词语了。变化较小则能够保证模型的稳定性。
扯了这么多,还没有真正谈到点子上。现在思路是有了,问题是,如何把这些词语放到正确的高维向量中?而且重点是,要在没有语言背景的情况下做到这件事情?(换句话说,如果我想处理英语语言任务,并不需要先学好英语,而是只需要大量收集英语文章,这该多么方便呀!)在这里我们不可能也不必要进行更多的原理上的展开,而是要介绍:而基于这个思路,有一个Google开源的著名的工具——Word2Vec。
简单来说,Word2Vec就是完成了上面所说的我们想要做的事情——用高维向量(词向量,Word Embedding)表示词语,并把相近意思的词语放在相近的位置,而且用的是实数向量(不局限于整数)。我们只需要有大量的某语言的语料,就可以用它来训练模型,获得词向量。词向量好处前面已经提到过一些,或者说,它就是问了解决前面所提到的问题而产生的。另外的一些好处是:词向量可以方便做聚类,用欧氏距离或余弦相似度都可以找出两个具有相近意思的词语。这就相当于解决了“一义多词”的问题(遗憾的是,似乎没什么好思路可以解决一词多义的问题。)
关于Word2Vec的数学原理,读者可以参考这系列文章。而Word2Vec的实现,Google官方提供了C语言的源代码,读者可以自行编译。而Python的Gensim库中也提供现成的Word2Vec作为子库(事实上,这个版本貌似比官方的版本更加强大)。
3.3 句向量
接下来要解决的问题是:
我们已经分好词,并且已经将词语转换为高维向量,那么句子就对应着词向量的集合,也就是矩阵,类似于图像处理,图像数字化后也对应一个像素矩阵;可是模型的输入一般只接受一维的特征,那怎么办呢?一个比较简单的想法是将矩阵展平,也就是将词向量一个接一个,组成一个更长的向量。这个思路是可以,但是这样就会使得我们的输入维度高达几千维甚至几万维,事实上是难以实现的。(如果说几万维对于今天的计算机来说不是问题的话,那么对于1000x1000的图像,就是高达100万维了!)
在自然语言处理中,通常用到的方法是递归神经网络或循环神经网络(都叫RNNs)。
它们的作用跟卷积神经网络是一样的,将矩阵形式的输入编码为较低维度的一维向量,而保留大多数有用信息。
4 代码实现
工程代码主要是结合参考资料2做三分类的文本情感分析;
4.1 数据预处理与词向量模型训练
处理过程,包括:
- 不同类别数据整理成输入矩阵
- jieba分词
- Word2Vec词向量模型训练
这里学长就不做重复介绍了,想要了解的,可以问我要。
三分类除了涉及到positive和negative两种情感外,还有一种neural情感,从原始数据集中可以提取到有语义转折的句子,“然而”,“但”都是关键词。从而可以得到3份不同语义的数据集。
4.2 LSTM三分类模型
代码需要注意的几点是,第一是,标签需要使用keras.utils.to_categorical来yummy,第二是LSTM二分类的参数设置跟二分有区别,选用softmax,并且loss函数也要改成categorical_crossentropy,代码如下:
def get_data(index_dict,word_vectors,combined,y): n_symbols = len(index_dict) + 1 # 所有单词的索引数,频数小于10的词语索引为0,所以加1 embedding_weights = np.zeros((n_symbols, vocab_dim)) # 初始化 索引为0的词语,词向量全为0 for word, index in index_dict.items(): # 从索引为1的词语开始,对每个词语对应其词向量 embedding_weights[index, :] = word_vectors[word] x_train, x_test, y_train, y_test = train_test_split(combined, y, test_size=0.2) y_train = keras.utils.to_categorical(y_train,num_classes=3) y_test = keras.utils.to_categorical(y_test,num_classes=3) # print x_train.shape,y_train.shape return n_symbols,embedding_weights,x_train,y_train,x_test,y_test ##定义网络结构 def train_lstm(n_symbols,embedding_weights,x_train,y_train,x_test,y_test): print 'Defining a Simple Keras Model...' model = Sequential() # or Graph or whatever model.add(Embedding(output_dim=vocab_dim, input_dim=n_symbols, mask_zero=True, weights=[embedding_weights], input_length=input_length)) # Adding Input Length model.add(LSTM(output_dim=50, activation='tanh')) model.add(Dropout(0.5)) model.add(Dense(3, activation='softmax')) # Dense=>全连接层,输出维度=3 model.add(Activation('softmax')) print 'Compiling the Model...' model.compile(loss='categorical_crossentropy', optimizer='adam',metrics=['accuracy']) print "Train..." # batch_size=32 model.fit(x_train, y_train, batch_size=batch_size, epochs=n_epoch,verbose=1) print "Evaluate..." score = model.evaluate(x_test, y_test, batch_size=batch_size) yaml_string = model.to_yaml() with open('../model/lstm.yml', 'w') as outfile: outfile.write( yaml.dump(yaml_string, default_flow_style=True) ) model.save_weights('../model/lstm.h5') print 'Test score:', score
4.3 测试
代码如下:
def lstm_predict(string): print 'loading model......' with open('../model/lstm.yml', 'r') as f: yaml_string = yaml.load(f) model = model_from_yaml(yaml_string) print 'loading weights......' model.load_weights('../model/lstm.h5') model.compile(loss='categorical_crossentropy', optimizer='adam',metrics=['accuracy']) data=input_transform(string) data.reshape(1,-1) #print data result=model.predict_classes(data) # print result # [[1]] if result[0]==1: print string,' positive' elif result[0]==0: print string,' neutral' else: print string,' negative'
经过检测,发现,原先在二分类模型中的“不是太好”,“不错不错”这样子带有前后语义转换的句子,都能正确预测,实战效果提升明显,但是也有缺点,缺点是中性评价出现的概率不高,笔者分析原因是,首先从数据集数量和质量着手,中性数据集的数量要比其他两个数据集少一半多,并且通过简单规则“然而”,“但”提取出来的中性数据集质量也不是很高,所以才会出现偏差。总而言之,训练数据的质量是非常重要的,如何获取高质量高数量的训练样本,也就成了新的难题。
5 最后-毕设帮助
毕设帮助,开题指导,技术解答 🇶746876041
关注公众号获取更多毕设资料
更多相关内容 -
论文研究-一种基于情感词典和朴素贝叶斯的中文文本情感分类方法.pdf
2019-07-22 18:31:24基于朴素贝叶斯理论提出了一种新的中文文本情感分类方法。这种方法利用情感词典对文本进行处理和表示,基于朴素贝叶斯理论构建文本情感分类器,并以互联网上宾馆中文评论作为分类研究的对象。实验表明,使用提出的... -
一种基于情感词典和朴素贝叶斯的中文文本情感分类方法
2018-10-09 20:42:17一种基于情感词典和朴素贝叶斯的中文文本情感分类方法 -
论文研究-文本情感分类中生成式情感模型的发展.pdf
2019-07-22 23:14:04描述了生成式模型的概念及它在文本情感分类领域的发展,分析了生成式情感模型的分类,着重研究了不同生成式情感模型之间的关联性,并对生成式模型中最有代表性的三类模型进行了介绍,最后对生成式情感模型发展以及... -
基于LSTM结构的文本情感分析
2019-12-26 03:11:50基于LSTM结构的文本情感分析,张玉环,钱江,随着互联网的迅猛发展,越来越多的用户在互联网上发表着自己的评论,这些评论中包含着很多有价值的信息,而这些对于厂家进一步了 -
论文研究-一种基于情感句模的文本情感分类方法 .pdf
2019-08-15 14:05:08一种基于情感句模的文本情感分类方法,陈涛,徐睿峰,考虑到同类型的情感句往往具有相同或者相似的句法和语义表达模式,本文提出了一种基于情感句模的文本情感自动分类方法。首先,将 -
基于BRC的不平衡文本情感分类的样本切割方法
2021-04-05 07:03:04基于BRC的不平衡文本情感分类的样本切割方法 -
论文研究-多级中文文本情感分类算法研究.pdf
2019-09-07 22:41:23针对文本情感分类准确率不高的问题,提出基于CCA-VSM分类器和KFD的多级文本情感分类方法。采用典型相关性分析对文档的权重特征向量和词性特征向量进行降维,在约简向量集上构建向量空间模型,根据模型之间的差异度... -
论文研究-基于依存句法关系的文本情感分类研究.pdf
2019-09-10 19:10:34为增加向量空间模型的文本语义信息,提出三元组依存关系特征构建方法,将此方法用于文本情感分类任务中。三元组依存关系特征构建方法在得到完整依存解析树的基础上,先依据中文语法特点,制定相应规则对原有完整树... -
论文研究-基于情感时序距离和转折同化的文本情感分类.pdf
2019-09-11 11:10:28考虑到中文评价文本的整体情感倾向性与其表达的情感顺序有很大关系,且在具有情感倾向的中文文本中,越是靠近文本最后所表达的情感倾向,对于整个文本的情感分类影响越大。因此对于情感倾向表达不明显或者表达不单一... -
八分类情感分类数据集
2019-02-13 10:17:15自己写论文手工标注的8分类的数据集,做文本情感分类用 -
论文研究-基于BERT的德语社交媒体文本情感分析 .pdf
2019-08-24 17:46:25基于BERT的德语社交媒体文本情感分析,李澜,叶勇超,德语语法复杂,语序多变,造成其社交媒体文本情感分析难度较大,相关研究较少。为解决以上研究难点,本文分析了德语及其社交媒体文本�� -
论文研究-一种基于粒运算的文本情感分类方法研究.pdf
2019-09-06 17:57:28针对基于轮廓线拼接重建物体表面所出现的轮廓对应和分叉问题,提出了一种通过体数据转换由轮廓线实现重建物体表面的方法。在分析体数据构造中出现逼近精度问题的前提下,通过提高轮廓线上点的密度,生成精确度较高的... -
Python实现购物评论文本情感分析操作【基于中文文本挖掘库snownlp】
2021-01-20 04:44:24本文实例讲述了Python实现购物评论文本情感分析操作。分享给大家供大家参考,具体如下: 昨晚上发现了snownlp这个库,很开心。先说说我开心的原因。我本科毕业设计做的是文本挖掘,用R语言做的,发现R语言对文本处理... -
论文研究-基于fcmpCNN模型的网络文本情感多分类标注.pdf
2019-07-22 18:38:17针对网络文本情感分析,提出了一种基于全卷积—多池化单元的卷积神经网络模型,实现情感多分类标注。无须手动指定多种上下文窗口大小和尽量保留文本的多层次语义,模型通过堆叠多级全卷积—多池化单元,提取出文本... -
基于改进特征选择方法的文本情感分类研究
2021-02-08 02:30:20基于改进特征选择方法的文本情感分类研究 -
文本情感分类中生成式情感模型的发展 (2014年)
2021-06-17 14:06:08描述了生成式模型的概念及它在文本情感分类领域的发展,分析了生成式情感模型的分类,着重研究了不同生成式情感模型之间的关联性,并对生成式模型中最有代表性的三类模型进行了介绍,最后对生成式情感模型发展以及... -
论文研究-中文文本情感分析研究综述.pdf
2019-07-22 20:52:39对中文文本情感分析的研究进行了综述。将情感分类划分为信息抽取和情感识别两类任务, 并分别介绍了各自的研究进展; 总结了情感分析的应用现状, 最后提出了存在的问题及不足。 -
【论文】基于特定实体的文本情感分类总结(PART I)
2019-05-03 20:50:290. 写在前面 最近在看一个比赛:2019 ...这里说的是实体识别,马上想到的就是利用分步走解决:先去对文章进行实体识别,然后对提取出来的实体进行情感分类。但是后来一想这样两步的话会使得最终结果的错误率叠加,也...最近在看一个比赛:2019 搜狐校园算法大赛,赛题说的是
给定若干文章,目标是判断文章的核心实体以及对核心实体的情感态度。每篇文章识别最多三个核心实体,并分别判断文章对上述核心实体的情感倾向(积极、中立、消极三种)。
这里说的是实体识别,马上想到的就是利用分步走解决:先去对文章进行实体识别,然后对提取出来的实体进行情感分类。但是后来一想这样两步的话会使得最终结果的错误率叠加,也就是说第一步做的不好的话会很大程度影响到第二步的结果。其实仔细看一下数据集会发现,它给出的很多样本实体并不是传统实体识别的实体,而是句子中的某一个token。这就跟SemEval2014的subtask 4非常像了,也就是Aspect Based Sentiment Analysis (ABSA)。不说废话了,接下来我们来看看关于ABSA问题都有哪些常用的算法。
1. A glance at ABSA(什么是ABSA)
在介绍具体算法之前,先来看一下什么是ABSA?可以粗暴翻译为基于方面的情感分析,本质就是对句子中不同对象可能会存在不同的情感倾向,例如:
“I bought a new camera. The picture quality is amazing but the battery life is too short”
,这句话中对于target=picture quality
,情感是正的;而对于target=battery
,情感就是负的了。ABSA任务的目的就是去找出给定句子中的不同target的情感倾向。
2. Effective LSTMs for Target-Dependent Sentiment Classification(Tang/COLING2016)
在这篇论文里面作者主要是介绍了三种解决ABSA任务的模型:
- LSTM
- Target-Dependent LSTM (TD-LSTM)
- Target-Connection LSTM (TC-LSTM)
LSTM
第一种方法就是直接使用NLP中的万金油模型LSTM,在该模型中,target words是被忽略的,也就是说跟普通的对文本情感分析的做法没有区别,最终得到的也是这个句子的全局情感,可想而知最后的效果一般般。具体做法就是对句子中的token进行embedding处理作为模型的输入,经过一次一次的计算隐层和输入之后得到一个句子表示 h n h_{n} hn,接着对这个向量进行softmax计算概率, 其中C是情感类别种类。
softmax i = exp ( x i ) ∑ i ′ = 1 C exp ( x i ′ ) \operatorname{softmax}_{i}=\frac{\exp \left(x_{i}\right)}{\sum_{i^{\prime}=1}^{C} \exp \left(x_{i^{\prime}}\right)} softmaxi=∑i′=1Cexp(xi′)exp(xi)TD-LSTM
为了解决上面LSTM忽略目标词的问题,提出了TD-LSTM模型,如下图所示。
其基本思想是对于一个target-word,充分考虑其上下文信息,具体来说使用了两个LSTM,从左往右的和从右往左的,分别对target word的左边和右边的信息建模。接着将两个LSTM得到的隐状态 h l h_{l} hl和 h r h_{r} hr concat一下,也就得到了关于这个词的句子情感表示,然后一样也是送入到softmax进行分类。除了concat作者也提到可以尝试进行sum或者average。TC-LSTM
TC-LSTM在TD-LSTM的基础上进一步加强了target-word与句子中每个token的关联,看模型框架就会很清晰,
可以看出,这里在模型输入的时候多加入了一部分: v t a r g e t v_{target} vtarget,这样就可以加强target与句子的交互,使得最后的表示更为确切。那么这个 v t a r g e t v_{target} vtarget是怎么得到的呢?其实就是简单地对target words的向量表示进行平均化处理得到一个向量。实验结果比对
3. Attention-based LSTM for Aspect-level Sentiment Classification(Wang/EMNLP2016)
这是在Effective LSTMs for Target-Dependent Sentiment Classification后面发出的文章,指出前者的不足之处:
However, those models can only take into consideration the target but not aspect information which is proved to be crucial for aspect-level classification.
作者这里提到了两个概念:target和aspect。我们可以认为target是包含在句子中出现的词,而aspect属于预先定义的比较high-level的类别刻画。
基于以上,提出了两种模型:- Attention-based LSTM (AT-LSTM)
- Attention-based LSTM with Aspect Embedding (ATAE-LSTM)
Aspect Embedding
对于ABSA问题,aspect信息对于最终的情感判别是非常重要的。因此作者对每个aspect都学习一个相应地aspect embedding来表示, v a i v_{a_{i}} vai
AT-LSTM
既然学习出了aspect embedding,那么怎么把它结合进模型里呢?这里使用的是超级火的注意力机制,如下图所示
为了更好地理解上述模型,首先回顾一下 vanilla attention mechanism,也就是key-value-query模型,具体可以参考理解Attention机制原理及模型。这里可以认为key=H
,value=H
,query=aspect embedding
,整个attention的过程可以用数学表示为:M = tanh ( [ W h H W v v a ⊗ e N ] ) α = softmax ( w T M ) r = H α T \begin{array}{l}{M=\tanh \left(\left[ \begin{array}{c}{W_{h} H} \\ {W_{v} v_{a} \otimes e_{N}}\end{array}\right]\right)} \\ {\alpha=\operatorname{softmax}\left(w^{T} M\right)} \\ {r=H \alpha^{T}}\end{array} M=tanh([WhHWvva⊗eN])α=softmax(wTM)r=HαT
其中r表示各hidden state带权重后的表示,然后最终句子的表示为:
h ∗ = tanh ( W p r + W x h N ) h^{*}=\tanh \left(W_{p} r+W_{x} h_{N}\right) h∗=tanh(Wpr+WxhN)
得到句子的表示后再进行情感判别:
y = softmax ( W s h ∗ + b s ) y=\operatorname{softmax}\left(W_{s} h^{*}+b_{s}\right) y=softmax(Wsh∗+bs)ATAE-LSTM
为了进一步利用aspect embedding的信息,类似于上一节中TC-LSTM中的思想,即将aspect embedding与word embedding共同组合成模型的输入。模型的其他部分与AT-LSTM相同。
注意力结果可视化
试验分析
论文使用的数据集是SemEval 2014 Task 4,正如开篇提到的一样。
4. Learning to Attend via Word-Aspect Associative Fusion for Aspect-based Sentiment Analysis(Tay/AAAI2018)
对于上一节的ATAE-LSTM,作者认为仍然存在以下不足:
- ATAE中的attention层学习上下文词之间的重要性关系,而不是对aspect和context之间建模。(???没懂,上下文词之间的重要性关系不就是相对于aspect而言的吗,就是上下文词与aspect之间的重要程度关系吧…)
- 简单的aspect embedding和word embedding拼接使得模型难以训练
针对以上提出了Aspect Fusion LSTM (AF-LSTM),模型整体框架如下:
Word-Aspect Fusion Attention Layer
在输入经过embedding层和LSTM层之后进入到Word-Aspect Fusion Attention Layer,这也是该模型的重点。
- Normalization Layer(optional): 在隐状态矩阵和aspect vector进行交互之前可以选择性地对其进行正规化操作,可以选用Batch Normalization;
- Associative Memory Operators: 用于计算context word 和 aspect word之间的关系。有两种:
- circular correlation和circular convolution
[ h ⋆ s ] k = ∑ i = 0 d − 1 h i s ( k + i ) m o d d [h \star s]_{k}=\sum_{i=0}^{d-1} h_{i} s_{(k+i)} \bmod d [h⋆s]k=i=0∑d−1his(k+i)modd
也可以用傅里叶变化得到: h ⋆ a = F − 1 ( F ( h ) ‾ ⊙ F ( a ) ) h \star a=\mathcal{F}^{-1}(\overline{\mathcal{F}(h)} \odot \mathcal{F}(a)) h⋆a=F−1(F(h)⊙F(a)) - circular convolution
[ h ∗ s ] k = ∑ i = 0 d − 1 h i s ( k − i ) m o d d [h * s]_{k}=\sum_{i=0}^{d-1} h_{i} s_{(k-i) \bmod d} [h∗s]k=i=0∑d−1his(k−i)modd
- circular correlation和circular convolution
- Learning Attentive Representations: 将aspect和context进行fusion之后得到的向量表示进行attention操作
试验分析
5. Interactive Attention Networks for Aspect-Level Sentiment Classification(Ma/IJCAI2017)
这篇文章作者的思路也是将target和context进行交互获取句子的准确表达,利用的模型是attention。与上面几个模型不同的在于,这里考虑了target可能存在好几个word组成的短语,另外添加了一层对于target的attention操作用于计算权重。提出了Interactive Attention Networks(IAN), 整体模型框架如下:
IAN
- 输入包括n个单词的context: [ w c 1 , w c 2 , … , w c n ] \left[w_{c}^{1}, w_{c}^{2}, \ldots, w_{c}^{n}\right] [wc1,wc2,…,wcn]和m个单词的target : [ w t 1 , w t 2 , … , w t m ] \left[w_{t}^{1}, w_{t}^{2}, \ldots, w_{t}^{m}\right] [wt1,wt2,…,wtm]
- 对输入进行embedding层后输入到LSTM网络中得到各个隐状态表示;
- 对所有隐状态求平均分别得到target和context的隐状态表示,以此作为后续attention两者的交互:
c a v g = ∑ i = 1 n h c i / n c_{a v g}=\sum_{i=1}^{n} h_{c}^{i} / n cavg=i=1∑nhci/n t a v g = ∑ i = 1 m h t i / m t_{a v g}=\sum_{i=1}^{m} h_{t}^{i} / m tavg=i=1∑mhti/m - 分别计算attention权重得分:
γ ( h c i , t a v g ) = tanh ( h c i ⋅ W a ⋅ t a v g T + b a ) \gamma\left(h_{c}^{i}, t_{a v g}\right)=\tanh \left(h_{c}^{i} \cdot W_{a} \cdot t_{a v g}^{T}+b_{a}\right) γ(hci,tavg)=tanh(hci⋅Wa⋅tavgT+ba) α i = exp ( γ ( h c i , t a v g ) ) ∑ j = 1 n exp ( γ ( h c j , t a v g ) ) \alpha_{i}=\frac{\exp \left(\gamma\left(h_{c}^{i}, t_{a v g}\right)\right)}{\sum_{j=1}^{n} \exp \left(\gamma\left(h_{c}^{j}, t_{a v g}\right)\right)} αi=∑j=1nexp(γ(hcj,tavg))exp(γ(hci,tavg))
γ ( h t i , c a v g ) = tanh ( h t i ⋅ W a ′ ⋅ c a v g T + b a ′ ) \gamma\left(h_{t}^{i}, c_{a v g}\right)=\tanh \left(h_{t}^{i} \cdot W_{a'} \cdot c_{a v g}^{T}+b_{a'}\right) γ(hti,cavg)=tanh(hti⋅Wa′⋅cavgT+ba′) β i = exp ( γ ( h t i , c a v g ) ) ∑ j = 1 m exp ( γ ( h t j , c a v g ) ) \beta_{i}=\frac{\exp \left(\gamma\left(h_{t}^{i}, c_{a v g}\right)\right)}{\sum_{j=1}^{m} \exp \left(\gamma\left(h_{t}^{j}, c_{a v g}\right)\right)} βi=∑j=1mexp(γ(htj,cavg))exp(γ(hti,cavg)) - 根据单词权重计算target和context的最终表示:
c r = ∑ i = 1 n α i h c i t r = ∑ i = 1 m β i h t i \begin{aligned} c_{r} &=\sum_{i=1}^{n} \alpha_{i} h_{c}^{i} \\ t_{r} &=\sum_{i=1}^{m} \beta_{i} h_{t}^{i} \end{aligned} crtr=i=1∑nαihci=i=1∑mβihti - 将 c r c_{r} cr和 t r t_{r} tr拼接起来作为整个输入句子的向量表示,并送入softmax计算类别概率
试验分析
同样数据集选用的也是SemEval 2014 Task 4,
以上~
2019.05.03 -
领域实例迁移的交互文本非平衡情感分类方法
2021-03-07 05:33:50领域实例迁移的交互文本非平衡情感分类方法 -
基于JST 模型的新闻文本的情感分类研究
2021-02-08 23:14:27基于JST 模型的新闻文本的情感分类研究 -
基于协同过滤和文本相似性的Web文本情感极性分类算法
2021-02-09 23:20:54基于协同过滤和文本相似性的Web文本情感极性分类算法 -
基于双向时间深度卷积网络的中文文本情感分类
2021-03-15 20:55:16基于双向时间深度卷积网络的中文文本情感分类 -
论文研究-基于语义特征的文本情感倾向识别研究.pdf
2019-07-22 17:58:07由于网络评论用语的多样性,常用的文本主题分类方法并不能完全适应情感倾向识别。针对这个问题,从语义理解的角度出发,提出一种基于语义特征的情感倾向识别方法,通过增加语义特征使得原始文本表现出更加明确的情感... -
使用情感语境对短文本进行情感分类
2021-04-16 08:14:38使用情感语境对短文本进行情感分类 -
高级实训任务三:文本情感分类
2022-01-26 14:35:41高级实训任务三:文本情感分类 1.任务描述 ● 将循环任务(RNN)应用在图像分割任务上,我们需要对网络结构进行设计。 ● 需要提交博客报告以及GitHub代码仓库。 ● 可选的任务:文本情感分类(正向、负向)。 ● 可...高级实训任务三:文本情感分类
1.任务描述
● 将循环任务(RNN)应用在图像分割任务上,我们需要对网络结构进行设计。
● 需要提交博客报告以及GitHub代码仓库。
● 可选的任务:文本情感分类(正向、负向)。
● 可选的网络结构:GRU、LSTM。
● 可选的数据集:
○ imdb数据集:https://www.kaggle.com/c/sentiment-analysis-on-movie-reviews
○ 烂番茄数据集:https://www.kaggle.com/c/sentiment-analysis-on-movie-reviews/overview
● 可选深度学习框架:Tensorflow、PyTorch、Keras。
● 完成期限:1月21日
● 提交结果:项目报告、答辩幻灯片、相关代码和测试用例。2.任务选择
将循环任务(RNN)应用在图像分割上,需要对网络结构进行设计。
任务选择:文本情感分类(正向,负向)
选择的网络结构:LSTM
语言:python
框架选择:pytorch(主框架,构建网络结构)
其他辅助框架:pickle(python 的文件库。由于数据集的一部分放在pkl文件里,需要pickle库进行读取)
tqdm (UI方面的库,用于添加进度条,方便观察计算的进度)
数据集:aclImdb(大型电影评论数据集)3.任务开始准备
-
循环任务(RNN)应用在图像分割任务原理
循环神经网络的应用场景比较多,比如暂时能写论文,写程序,写诗,但是,(总是会有但是的),但是他们现在还不能正常使用,学习出来的东西没有逻辑,所以要想真正让它更有用,路还很远。
一般的神经网络应该有的结构:
-
使用循环神经网络原因:
无论是卷积神经网络,还是人工神经网络,他们的前提假设都是:元素之间是相互独立的,输入与输出也是独立的,比如猫和狗。但现实世界中,很多元素都是相互连接的,比如股票随时间的变化,我们是根据上下文的内容推断出来的,但机会要做到这一步就相当得难了。因此,就有了现在的循环神经网络,他的本质是:像人一样拥有记忆的能力。因此,他的输出就依赖于当前的输入和记忆。 -
RNN的网络结构及原理
其中每个圆圈可以看作是一个单元,而且每个单元做的事情也是一样的,因此可以折叠呈左半图的样子。用一句话解释RNN,就是一个单元结构重复使用。 -
LSTM原理
1.LSTM内部结构:
LSTM看上去就是这样一种效果,一个一个首尾相接,同一层的会把前面单元的输出作为后面单元的输入;前一层的输出会作为后一层的输入。细胞状态
2.LSTM 的关键就是细胞状态,水平线在图上方从左到右贯穿运行。
细胞状态类似于传送带。直接在整个链上运行,只有一些少量的线性交互。信息在上面流传保持不变会很容易
左面的乘号是一个乘法操作,右面的加号就是普通的线性叠加。
LSTM规避了标准RNN中梯度爆炸和梯度消失的问题,所以会显得更好用,学习速度更快下图是最基本的LSTM单元连接起来的样子
3.LSTM的门结构
**遗忘门:**遗忘门层决定我们会从细胞状态中丢弃什么信息。该门会读取ht−1 和xt ,输出一个在 0到 1之间的数值给每个在细胞状态 Ct−1 中的数字。1 表示“完全保留”,0 表示“完全舍弃”。
[ht−1,xt]代表把两个向量连接起来。
更新门:C t 表示新的输入带来的信息,tanh这个激活函数讲内容归一化到-1到1;
it用于选择保留新信息的哪个部分。ft * Ct-1表示过去信息有选择的遗忘(保留),
Ct 表示新信息有选择的遗忘(保留),两者相加得到新状态Ct。
输出门:4.实验过程和结果
代码:
1.读取imdb数据集:max_f=10000 (x_train, y_train),(x_test,y_test) = imdb_data(num_words = max_f) maxlength = 400 x_train = sequence.pad_sequences(x_train,maxlength = maxlength) x_test = sequence.pad_sequences(x_test,maxlength = maxlength
2.构建LSTM训练模型
model = Sequential([layers.Embedding(max_f,32), layers.LSTM(32), layers.Dense(1,activation="sigmoid") ]) model.compile(optimizer="rmsprop",loss='binary_crossentropy',metrics=["accuracy"]) history = model.fita(x_train , y_train, epochs=10,batch_size = 128, validation_data = (x_train , y_train),callbacks=callbacks_list)
3.实验结果:
Epoch 1/10 25000/25000 [==============================] - 313s 12ms/sample - loss: 0.4751 - acc: 0.7779 - val_loss: 0.6679 - val_acc: 0.7521 Epoch 2/10 25000/25000 [==============================] - 265s 11ms/sample - loss: 0.2848 - acc: 0.8869 - val_loss: 0.2021 - val_acc: 0.9271 Epoch 3/10 25000/25000 [==============================] - 251s 11ms/sample - loss: 0.2362 - acc: 0.9128 - val_loss: 0.2687 - val_acc: 0.8936 Epoch 4/10 25000/25000 [==============================] - 172s 7ms/sample - loss: 0.2011 - acc: 0.9239 - val_loss: 0.2430 - val_acc: 0.9182 Epoch 5/10 25000/25000 [==============================] - 145s 6ms/sample - loss: 0.1855 - acc: 0.9311 - val_loss: 0.1540 - val_acc: 0.9434 Epoch 6/10 25000/25000 [==============================] - 148s 6ms/sample - loss: 0.1686 - acc: 0.9394 - val_loss: 0.1093 - val_acc: 0.9639 Epoch 7/10 25000/25000 [==============================] - 143s 6ms/sample - loss: 0.1542 - acc: 0.9439 - val_loss: 0.1670 - val_acc: 0.9368 Epoch 8/10 25000/25000 [==============================] - 145s 6ms/sample - loss: 0.1438 - acc: 0.9491 - val_loss: 0.1907 - val_acc: 0.9227 Epoch 9/10 25000/25000 [==============================] - 147s 6ms/sample - loss: 0.1351 - acc: 0.9524 - val_loss: 0.1359 - val_acc: 0.9539 Epoch 10/10 25000/25000 [==============================] - 143s 6ms/sample - loss: 0.1293 - acc: 0.9561 - val_loss: 0.1029 - val_acc: 0.9655
实验结论:可以看到经过10轮训练后,训练集准确度为0.9561,测试集准确度为0.9655,训练过程中测试集准确度最高为0.9655。可以看出随着周期数的增多,准确度也在不断增高。
总结
LSTM和其他神经网络用途类似,主要用于分类或预测。能够改善了RNN中存在的长期依赖问题;LSTM的表现通常比时间递归神经网络及隐马尔科夫模型(HMM)更好;作为非线性模型,LSTM可作为复杂的非线性单元用于构造更大型深度神经网络。
-
-
基于情感词典的藏语文本句子情感分类
2021-04-03 22:45:48基于情感词典的藏语文本句子情感分类 -
【NLP】文本分类-情感分类
2021-08-25 22:29:44本文针对深度学习早期文本分类方法汇总和实验。1 常见NLP文本分类模型
1.1 TextCNN
论文原文:《Convolutional Neural Networks for Sentence Classification》
论文地址:1408.5882.pdf (arxiv.org)
结构图如下:
值得一提的是,在2016年的《A Sensitivity Analysis of (and Practitioners’ Guide to) Convolutional Neural Networks for Sentence Classification》作者通过大量实验对TextCNN进行网络参数选取,并给出了参数建议。论文地址:https://arxiv.org/pdf/1510.03820.pdf,文章经典的结构图如下:
1.2 TextRNN
TextRNN指的是利用RNN循环神经网络解决文本分类问题。
论文原文:《Recurrent Neural Network for Text Classification with Multi-Task Learning》
论文链接:https://www.ijcai.org/Proceedings/16/Papers/408.pdf
结构图如下:
1.3 TextRCNN
论文原文:《Recurrent Convolutional Neural Networks for Text Classification》
论文链接:TextRCNN论文
结构图如下:
1.4 FastText
论文原文:《Bag of Tricks for Efficient Text Classification》
论文链接:https://arxiv.org/pdf/1607.01759v2.pdf
结构图如下:
1.5 HAN
论文原文:《Hierarchical Attention Networks for Document Classification》
论文链接:https://aclanthology.org/N16-1174.pdf
结构图如下:
1.6 CharCNN
论文原文:《Character-level Convolutional Networks for Text Classification》
论文链接:CharCNN论文
结构图如下:
1.7 Transformer
论文原文:《Attention is all you need》
论文链接:https://arxiv.org/pdf/1706.03762.pdf
结构图如下:
2 代码实现
import torch import torch.nn as nn import torch.nn.functional as F import numpy as np import math import copy #TextCNN class TextCNN(nn.Module): def __init__(self, args): super(TextCNN, self).__init__() self.args = args class_num = args.class_num chanel_num = 1 filter_num = args.filter_num filter_sizes = args.filter_sizes vocabulary_size = args.vocabulary_size embedding_dimension = args.embedding_dim self.embedding = nn.Embedding(vocabulary_size, embedding_dimension) if args.static: self.embedding = self.embedding.from_pretrained(args.vectors, freeze=not args.non_static) if args.multichannel: self.embedding2 = nn.Embedding(vocabulary_size, embedding_dimension).from_pretrained(args.vectors) chanel_num += 1 else: self.embedding2 = None self.convs = nn.ModuleList( [nn.Conv2d(chanel_num, filter_num, (size, embedding_dimension)) for size in filter_sizes]) self.dropout = nn.Dropout(args.dropout) self.fc = nn.Linear(len(filter_sizes) * filter_num, class_num) def forward(self, x): if self.embedding2: x = torch.stack([self.embedding(x), self.embedding2(x)], dim=1) else: x = self.embedding(x) x = x.unsqueeze(1) x = [F.relu(conv(x)).squeeze(3) for conv in self.convs] x = [F.max_pool1d(item, int(item.size(2))).squeeze(2) for item in x] x = torch.cat(x, 1) x = self.dropout(x) logits = self.fc(x) return logits #TextRNN class LSTM(torch.nn.Module): def __init__(self, args): super(LSTM, self).__init__() self.embed_size = args.embedding_dim self.label_num = args.class_num self.embed_dropout = 0.1 self.fc_dropout = 0.1 self.hidden_num = 1 self.hidden_size = 50 self.hidden_dropout = 0 self.bidirectional = True vocabulary_size = args.vocabulary_size embedding_dimension = args.embedding_dim self.embeddings = nn.Embedding(vocabulary_size, embedding_dimension) # self.embeddings.weight.data.copy_(torch.from_numpy(vocabulary_size)) self.embeddings.weight.requires_grad = False self.lstm = nn.LSTM( self.embed_size, self.hidden_size, dropout=self.hidden_dropout, num_layers=self.hidden_num, batch_first=True, bidirectional=True ) self.embed_dropout = nn.Dropout(self.embed_dropout) self.fc_dropout = nn.Dropout(self.fc_dropout) self.linear1 = nn.Linear(self.hidden_size * 2, self.label_num) self.softmax = nn.Softmax() def forward(self, input): x = self.embeddings(input) x = self.embed_dropout(x) batch_size = len(input) _, (lstm_out, _) = self.lstm(x) lstm_out = lstm_out.permute(1, 0, 2) lstm_out = lstm_out.contiguous().view(batch_size, -1) out = self.linear1(lstm_out) out = self.fc_dropout(out) out = self.softmax(out) return out #TextRCNN class BiLSTM(nn.Module): def __init__(self, args): super(BiLSTM, self).__init__() self.embed_size = args.embedding_dim self.label_num = args.class_num self.embed_dropout = 0.1 self.fc_dropout = 0.1 self.hidden_num = 2 self.hidden_size = 50 self.hidden_dropout = 0 self.bidirectional = True vocabulary_size = args.vocabulary_size embedding_dimension = args.embedding_dim self.embeddings = nn.Embedding(vocabulary_size, embedding_dimension) # self.embeddings.weight.data.copy_(torch.from_numpy(word_embeddings)) self.embeddings.weight.requires_grad = False self.lstm = nn.LSTM( self.embed_size, self.hidden_size, dropout=self.hidden_dropout, num_layers=self.hidden_num, batch_first=True, bidirectional=self.bidirectional ) self.embed_dropout = nn.Dropout(self.embed_dropout) self.fc_dropout = nn.Dropout(self.fc_dropout) self.linear1 = nn.Linear(self.hidden_size * 2, self.hidden_size // 2) self.linear2 = nn.Linear(self.hidden_size // 2, self.label_num) def forward(self, input): out = self.embeddings(input) out = self.embed_dropout(out) out, _ = self.lstm(out) out = torch.transpose(out, 1, 2) out = torch.tanh(out) out = F.max_pool1d(out, out.size(2)) out = out.squeeze(2) out = self.fc_dropout(out) out = self.linear1(F.relu(out)) output = self.linear2(F.relu(out)) return output #FastText class FastText(nn.Module): def __init__(self, args): super().__init__() self.output_dim = args.class_num vocabulary_size = args.vocabulary_size embedding_dimension = args.embedding_dim self.embeddings = nn.Embedding(vocabulary_size, embedding_dimension) self.fc = nn.Linear(embedding_dimension, self.output_dim) def forward(self, text): # text = [sent len, batch size] text = text.permute(1,0) embedded = self.embeddings(text) # embedded = [sent len, batch size, emb dim] embedded = embedded.permute(1, 0, 2) # embedded = [batch size, sent len, emb dim] pooled = F.avg_pool2d(embedded, (embedded.shape[1], 1)).squeeze(1) # pooled = [batch size, embedding_dim] return self.fc(pooled) #HAN class SelfAttention(nn.Module): def __init__(self, input_size, hidden_size): super(SelfAttention, self).__init__() self.W = nn.Linear(input_size, hidden_size, True) self.u = nn.Linear(hidden_size, 1) def forward(self, x): u = torch.tanh(self.W(x)) a = F.softmax(self.u(u), dim=1) x = a.mul(x).sum(1) return x class HAN(nn.Module): def __init__(self,args): super(HAN, self).__init__() hidden_size_gru = 50 # 50 hidden_size_att = 100 # 100 num_classes = args.class_num vocabulary_size = args.vocabulary_size embedding_dimension = args.embedding_dim self.num_words = 64 #词Pading大小 self.embed = nn.Embedding(vocabulary_size, embedding_dimension) self.gru1 = nn.GRU(embedding_dimension, hidden_size_gru, bidirectional=True, batch_first=True) self.att1 = SelfAttention(hidden_size_gru * 2, hidden_size_att) self.gru2 = nn.GRU(hidden_size_att, hidden_size_gru, bidirectional=True, batch_first=True) self.att2 = SelfAttention(hidden_size_gru * 2, hidden_size_att) # 这里fc的参数很少,不需要dropout self.fc = nn.Linear(hidden_size_att, num_classes, True) def forward(self, x): # 64 512 200 x = x.view(x.size(0) * self.num_words, -1).contiguous() x = self.embed(x) x, _ = self.gru1(x) x = self.att1(x) x = x.view(x.size(0) // self.num_words, self.num_words, -1).contiguous() x, _ = self.gru2(x) x = self.att2(x) x = self.fc(x) x = F.log_softmax(x, dim=1) # softmax return x #CharCNN class CharCNN(nn.Module): def __init__(self, args): super(CharCNN, self).__init__() self.num_chars = 64 self.features = [128, 128, 128, 128, 128, 128] self.kernel_sizes = [7, 7, 3, 3, 3, 3] self.dropout = args.dropout self.num_labels = args.class_num vocabulary_size = args.vocabulary_size embedding_dimension = args.embedding_dim # Embedding Layer self.embeddings = nn.Embedding(vocabulary_size, embedding_dimension) self.embeddings.weight.requires_grad = False self.in_features = [self.num_chars]+self.features[:-1] self.out_features = self.features self.conv1d_1 = nn.Sequential( nn.Conv1d(self.in_features[0], self.out_features[0], self.kernel_sizes[0], stride=1), nn.BatchNorm1d(self.out_features[0]), nn.ReLU(), nn.MaxPool1d(kernel_size=3, stride=3) ) self.conv1d_2 = nn.Sequential( nn.Conv1d(self.in_features[1], self.out_features[1], self.kernel_sizes[1], stride=1), nn.BatchNorm1d(self.out_features[1]), nn.ReLU(), nn.MaxPool1d(kernel_size=3, stride=3) ) self.conv1d_3 = nn.Sequential( nn.Conv1d(self.in_features[2], self.out_features[2], self.kernel_sizes[2], stride=1), nn.BatchNorm1d(self.out_features[2]), nn.ReLU() ) self.conv1d_4 = nn.Sequential( nn.Conv1d(self.in_features[3], self.out_features[3], self.kernel_sizes[3], stride=1), nn.BatchNorm1d(self.out_features[3]), nn.ReLU() ) self.conv1d_5 = nn.Sequential( nn.Conv1d(self.in_features[4], self.out_features[4], self.kernel_sizes[4], stride=1), nn.BatchNorm1d(self.out_features[4]), nn.ReLU() ) self.conv1d_6 = nn.Sequential( nn.Conv1d(self.in_features[5], self.out_features[5], self.kernel_sizes[5], stride=1), nn.BatchNorm1d(self.out_features[5]), nn.ReLU(), nn.MaxPool1d(kernel_size=3, stride=3) ) self.fc1 = nn.Sequential( nn.Linear(128, 128), nn.ReLU(), nn.Dropout(self.dropout) ) self.fc2 = nn.Sequential( nn.Linear(128, 128), nn.ReLU(), nn.Dropout(self.dropout) ) self.fc3 = nn.Linear(128, self.num_labels) def forward(self, x): # x = torch.Tensor(x).long() # batch_size=128, num_chars=128, seq_len=64 x = self.embeddings(x) # x = x.permute(0,2,1) x = self.conv1d_1(x) # b, out_features[0], (seq_len-f + 1)-f/s+1 = 64, 256, (1014-7+1)-3/3 + 1=1008-3/3+1=336 x = self.conv1d_2(x) # 64, 256, (336-7+1)-3/3+1=110 x = self.conv1d_3(x) # 64, 256, 110-3+1=108 x = self.conv1d_4(x) # 64, 256, 108-3+1=106 x = self.conv1d_5(x) # 64, 256, 106-3=1=104 x = self.conv1d_6(x) # 64, 256, (104-3+1)-3/3+1=34 x = x.view(x.size(0), -1) # 64, 256, 34 -> 64, 8704 out = self.fc1(x) # 64, 1024 out = self.fc2(out) # 64, 1024 out = self.fc3(out) # 64, 4 return out #Transformer class Transformer_Config(object): """配置参数""" def __init__(self, args): # self.model_name = 'Transformer' # self.train_path = dataset + '/data/train.txt' # 训练集 # self.dev_path = dataset + '/data/dev.txt' # 验证集 # self.test_path = dataset + '/data/test.txt' # 测试集 # self.class_list = [x.strip() for x in open( # dataset + '/data/class.txt', encoding='utf-8').readlines()] # 类别名单 # self.vocab_path = dataset + '/data/vocab.pkl' # 词表 # self.save_path = dataset + '/saved_dict/' + self.model_name + '.ckpt' # 模型训练结果 # self.log_path = dataset + '/log/' + self.model_name # self.embedding_pretrained = torch.tensor( # np.load(dataset + '/data/' + embedding)["embeddings"].astype('float32'))\ # if embedding != 'random' else None # 预训练词向量 self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 设备 self.dropout = 0.5 # 随机失活 self.require_improvement = 2000 # 若超过1000batch效果还没提升,则提前结束训练 self.num_classes = args.class_num # 类别数 self.n_vocab = args.vocabulary_size # 词表大小,在运行时赋值 self.num_epochs = args.epochs # epoch数 self.batch_size = args.batch_size # mini-batch大小 self.pad_size = 64 # 每句话处理成的长度(短填长切) self.learning_rate = 5e-4 # 学习率 self.embedding_pretrained = None # self.embed = self.embedding_pretrained.size(1)\ # if self.embedding_pretrained is not None else 300 # 字向量维度 self.embed = 128 self.dim_model = args.embedding_dim self.hidden = 1024 self.last_hidden = 512 self.num_head = 2 self.num_encoder = 2 '''Attention Is All You Need''' class Transformer(nn.Module): def __init__(self, config): super(Transformer, 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) self.postion_embedding = Positional_Encoding(config.embed, config.pad_size, config.dropout, config.device) self.encoder = Encoder(config.dim_model, config.num_head, config.hidden, config.dropout) self.encoders = nn.ModuleList([ copy.deepcopy(self.encoder) # Encoder(config.dim_model, config.num_head, config.hidden, config.dropout) for _ in range(config.num_encoder)]) self.fc1 = nn.Linear(config.pad_size * config.dim_model, config.num_classes) # self.fc2 = nn.Linear(config.last_hidden, config.num_classes) # self.fc1 = nn.Linear(config.dim_model, config.num_classes) def forward(self, x): out = self.embedding(x) out = self.postion_embedding(out) for encoder in self.encoders: out = encoder(out) out = out.view(out.size(0), -1) # out = torch.mean(out, 1) out = self.fc1(out) return out class Encoder(nn.Module): def __init__(self, dim_model, num_head, hidden, dropout): super(Encoder, self).__init__() self.attention = Multi_Head_Attention(dim_model, num_head, dropout) self.feed_forward = Position_wise_Feed_Forward(dim_model, hidden, dropout) def forward(self, x): out = self.attention(x) out = self.feed_forward(out) return out class Positional_Encoding(nn.Module): def __init__(self, embed, pad_size, dropout, device): super(Positional_Encoding, self).__init__() self.device = device self.pe = torch.tensor([[pos / (10000.0 ** (i // 2 * 2.0 / embed)) for i in range(embed)] for pos in range(pad_size)]) self.pe[:, 0::2] = np.sin(self.pe[:, 0::2]) self.pe[:, 1::2] = np.cos(self.pe[:, 1::2]) self.dropout = nn.Dropout(dropout) def forward(self, x): out = x + nn.Parameter(self.pe, requires_grad=False).to(self.device) out = self.dropout(out) return out class Scaled_Dot_Product_Attention(nn.Module): '''Scaled Dot-Product Attention ''' def __init__(self): super(Scaled_Dot_Product_Attention, self).__init__() def forward(self, Q, K, V, scale=None): ''' Args: Q: [batch_size, len_Q, dim_Q] K: [batch_size, len_K, dim_K] V: [batch_size, len_V, dim_V] scale: 缩放因子 论文为根号dim_K Return: self-attention后的张量,以及attention张量 ''' attention = torch.matmul(Q, K.permute(0, 2, 1)) if scale: attention = attention * scale # if mask: # TODO change this # attention = attention.masked_fill_(mask == 0, -1e9) attention = F.softmax(attention, dim=-1) context = torch.matmul(attention, V) return context class Multi_Head_Attention(nn.Module): def __init__(self, dim_model, num_head, dropout=0.0): super(Multi_Head_Attention, self).__init__() self.num_head = num_head assert dim_model % num_head == 0 self.dim_head = dim_model // self.num_head self.fc_Q = nn.Linear(dim_model, num_head * self.dim_head) self.fc_K = nn.Linear(dim_model, num_head * self.dim_head) self.fc_V = nn.Linear(dim_model, num_head * self.dim_head) self.attention = Scaled_Dot_Product_Attention() self.fc = nn.Linear(num_head * self.dim_head, dim_model) self.dropout = nn.Dropout(dropout) self.layer_norm = nn.LayerNorm(dim_model) def forward(self, x): batch_size = x.size(0) Q = self.fc_Q(x) K = self.fc_K(x) V = self.fc_V(x) Q = Q.view(batch_size * self.num_head, -1, self.dim_head) K = K.view(batch_size * self.num_head, -1, self.dim_head) V = V.view(batch_size * self.num_head, -1, self.dim_head) # if mask: # TODO # mask = mask.repeat(self.num_head, 1, 1) # TODO change this scale = K.size(-1) ** -0.5 # 缩放因子 context = self.attention(Q, K, V, scale) context = context.view(batch_size, -1, self.dim_head * self.num_head) out = self.fc(context) out = self.dropout(out) out = out + x # 残差连接 out = self.layer_norm(out) return out class Position_wise_Feed_Forward(nn.Module): def __init__(self, dim_model, hidden, dropout=0.0): super(Position_wise_Feed_Forward, self).__init__() self.fc1 = nn.Linear(dim_model, hidden) self.fc2 = nn.Linear(hidden, dim_model) self.dropout = nn.Dropout(dropout) self.layer_norm = nn.LayerNorm(dim_model) def forward(self, x): out = self.fc1(x) out = F.relu(out) out = self.fc2(out) out = self.dropout(out) out = out + x # 残差连接 out = self.layer_norm(out) return out
3 结果讨论
本文针对文本情感二分类任务展开训练,采用数据集的数据量包含Train有56700条,Evaluate有7000条。得到测试结果如下表。
可以看到由于本文的数据量比较小,所以小模型还有更好的检测效果。如Transformer有点大材小用,缺少发挥空间。欢迎大家学习讨论。