RNN(1)电影评论情感分析

简介

这次的任务是输入一串评价的字符串,得出其情感是正向的还是否定的。数据来源于Kaggle,搜索SI650就可以找到。
先看一下数据长什么样:

1 I loved the Da Vinci Code, but now I want something better and different!..
1 i thought da vinci code was great, same with kite runner.
1 The Da Vinci Code is actually a good movie...
1 I thought the Da Vinci Code was a pretty good book.
0 Brokeback Mountain is fucking horrible..
0 Then snuck into Brokeback Mountain, which is the most depressing movie I have ever seen..
0 , she helped me bobbypin my insanely cool hat to my head, and she laughed at my stupid brokeback mountain cowboy jokes..

标记为0的即为负面评价,否则为正面评价。思路比较简单,将单词的ID转换为更具有信息量的Vector,然后再用RNN来学习每个单词之间的关系,这个关系代表了句子的信息,最后用全连接层来输出对情感的预测。

预处理

这次相比于上次的字符级别文本生成来说,需要对单词进行处理,并需要统计一句话的最大长度,来决定RNN网络的Input形状;统计出现的单词数量来决定输入到embedding层中向量的序号范围。
代码中用到了nltk.word_tokenize来把句子拆成单词,用collections.Counter来统计每个单词出现的次数,len(word_freqs)即为出现过的单词数量。

import nltk
import collections

maxlen=0
word_freqs=collections.Counter()
num_recs=0

with open("train.txt",'r') as f:
    for line in f:
        label,sentence=line.strip().split('\t')
        words=nltk.word_tokenize(sentence.lower())
        if(len(words)>maxlen):
            maxlen=len(words)
        for word in words:
            word_freqs[word]+=1
        # 用于统计train有多少条数据
        num_recs+=1
> print(maxlen)
42
> print(len(word_freqs))
2328

Embedding

由预处理结果,我们将单词个数限制在2000个,RNN网络输入的序列长度定为40,对于测试集中未达到该长度的句子进行填充,所以还需要一个伪单词PAD;超出2000个单词范围之外的单词设为伪单词UNK(Unknown)

# input word转为int
MAX_FEATURES=2000
MAX_SEN_LEN=40
vocab_size=MAX_FEATURES+2
# most_common返回形式为[{'a':3},{'b':4}]
# x[0]为取出对应的字符
word2index={x[0]:i+2 for i,x in
           enumerate(word_freqs.most_common(MAX_FEATURES))}
word2index['PAD']=0
word2index['UNK']=1
index2word={v:k for k,v in word2index.items()}

下面的代码用到了keras.preprocessing.sequence中的pad_sequences,可以很方便的将序列补0,成为想要的长度。

import keras
from keras.layers.core import Activation,Dense,Dropout,SpatialDropout1D
from keras.layers.embeddings import Embedding
from keras.layers import LSTM
from keras.models import Sequential
from sklearn.model_selection import train_test_split
from keras.preprocessing import sequence
from keras.activations import sigmoid
from keras.losses import binary_crossentropy
from keras.optimizers import Adam
import numpy as np
X=np.empty((num_recs,),dtype=list)
y=np.zeros((num_recs,))
with open("train.txt",'r') as f:
    i=0
    for line in f:
        label,sentence=line.strip().split('\t')
        words=nltk.word_tokenize(sentence.lower())
        seqs=[]
        for w in words:
            if (w in word2index.keys()):
                seqs.append(word2index[w])
            else:
                seqs.append(word2index['UNK'])
        X[i]=seqs
        y[i]=int(label)
        i+=1
        # 用于统计train有多少条数据
    X=sequence.pad_sequences(X,maxlen=MAX_SEN_LEN)

训练

注意新加了一层Embedding层,用于将单词的ID转为某一向量,可以参考搜索Word2Vec。单词ID的范围最大值即vocab_size,该向量的长度即为EMBEDDING_SIZE。因为句子中单词具有相关性,进行普通的针对单个神经元连接的Dropout不合适,采用SpatialDropout1D用于整体进行Dropout。

Xtrain,Xtest,ytrain,ytest=train_test_split(X,y,test_size=0.2)
EMBEDDING_SIZE=128
HIDDEN_LAYER_SIZE=64
BATCH_SIZE=32
NUM_EPOCHS=10
model=Sequential()
model.add(Embedding(vocab_size,EMBEDDING_SIZE,
                   input_length=MAX_SEN_LEN))
model.add(SpatialDropout1D(0.2))
model.add(LSTM(HIDDEN_LAYER_SIZE,dropout=0.2,recurrent_dropout=0.2))
model.add(Dense(1))
# Softmax用于多分类,Sigmoid用于二分类
model.add(Activation(sigmoid))
model.compile(loss=binary_crossentropy,optimizer=Adam(),metrics=['accuracy'])

另外需要注意因为是多对一的RNN模型,最后一层全连接仅加在最后一个时间步上。如下图,连接在最后一个时间步上的64个LSTM Cell上。如果是多对多的模型,则需要另外类型的全连接层,参考下一篇文章。

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, 40, 128)           256256    
_________________________________________________________________
spatial_dropout1d_1 (Spatial (None, 40, 128)           0         
_________________________________________________________________
lstm_1 (LSTM)                (None, 64)                49408     
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 65        
_________________________________________________________________
activation_1 (Activation)    (None, 1)                 0         
=================================================================

训练

history=model.fit(Xtrain,ytrain,batch_size=BATCH_SIZE,epochs=NUM_EPOCHS,
                 validation_data=(Xtest,ytest))

结果如下

Epoch 9/10
5668/5668 [==============================] - 11s 2ms/step - loss: 0.0026 - acc: 0.9991 - val_loss: 0.0333 - val_acc: 0.9901
Epoch 10/10
5668/5668 [==============================] - 11s 2ms/step - loss: 2.0604e-04 - acc: 1.0000 - val_loss: 0.0341 - val_acc: 0.9901

验证集达到了99%的准确率。

应用测试

我们来自己测试一下效果,

Xtest=np.empty((4,),dtype=list)
sentences=['this is a good movie','what a bad shit','abcdefg','give me five']
for i,s in enumerate(sentences):
    words=nltk.word_tokenize(s.lower())
    seqs=[]
    for w in words:
        if (w in word2index.keys()):
            seqs.append(word2index[w])
        else:
            seqs.append(word2index['UNK'])
    Xtest[i]=seqs
Xtest=sequence.pad_sequences(Xtest,maxlen=MAX_SEN_LEN)   
> model.predict(Xtest)
array([[0.8109143 ],
       [0.0062162 ],
       [0.00602398],
       [0.5252423 ]], dtype=float32)

可以看出对this is a good movie评价很好,对于what a bad shit评价就属于负面了,而模棱两可的give me five评分则处于中间的0.5,未出现的单词UNK则判定在0左右,效果还算不错。

改进方案

1、训练好的Embedding层

第一个问题就是这个情感语料库里面的文本可能不太够,导致Embedding层对单词的理解不够透彻。所以我们可以选择Google基于维基百科训练好的Word2Vec模型再来调整。这里需要做的事情就是把训练好的模型的形状适应到我们的任务上来。通过gensim加载模型之后设置Embedding层的权重为该模型即可。

2、双向LSTM

当我们说一句话的时候后面的词也会影响前面的词,比如那边的那只鸟里面的,,所以可以采用双向的模型将评论后续的文本信息加入考虑。

思考

思考网络结构的时候可以在脑海中产生一幅RNN网络按时间步展开的图像,比如在这篇文章里面的多对一模型,就仅仅是在最后一个时间步加上全连接层用做预测。
这里用到了LSTM作为基本神经元,其结构如下:



(图片来自http://speech.ee.ntu.edu.tw/~tlkagk/courses_MLDS17.html
为了解决梯度消失的问题设计了类似残差网络的“高速通道”,使神经元对长程输入的记忆能力增强,且由于增加了遗忘门、输入门、输出门等机制,使模型适应训练集的能力得到了增强。

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容