原文:http://blog.csdn.net/zzulp/article/details/76589044
1 RNN简介
RNN即循环神经网络,其主要用途是处理和预测序列数据。在CNN中,神经网络层间采用全连接的方式连接,但层内节点之间却无连接。RNN为了处理序列数据,层内节点的输出还会重新输入本层,以实现学习历史,预测未来。 RNN的两个主要改进是LSTM(长短时记忆网络)和GRU(门控循环单元),二者为基本神经单元增加了额外的功能门,从而更好的实现长时记忆的处理。 在此基础上,通过两层或者多个RNN层的堆叠,可以实现双向循环神经网络(bidirectionalRNN)及深层循环神经网络(deepRNN)。
2 Keras对RNN的支持
Keras在layers包的recurrent模块中实现了RNN相关层模型的支持,并在wrapper模块中实现双向RNN的包装器。
2.1 recurrent模块导出类
名称作用原型参数
SimpleRNN全连接RNN网络SimpleRNN(units, activation=’tanh’, use_bias=True, kernel_initializer=’glorot_uniform’, recurrent_initializer=’orthogonal’, bias_initializer=’zeros’,dropout=0.0, recurrent_dropout=0.0))GRU门限循环单元层GRU(units, activation=’tanh’, recurrent_activation=’hard_sigmoid’, use_bias=True, kernel_initializer=’glorot_uniform’, recurrent_initializer=’orthogonal’, bias_initializer=’zeros’, dropout=0.0, recurrent_dropout=0.0)LSTM长短期记忆模型层LSTM(units, activation=’tanh’, recurrent_activation=’hard_sigmoid’, use_bias=True, kernel_initializer=’glorot_uniform’, recurrent_initializer=’orthogonal’, bias_initializer=’zeros’, unit_forget_bias=True, dropout=0.0, recurrent_dropout=0.0)
2.2 wrapper模块导出类
名称作用原型参数
TimeDistributed TimeDistributed(layer)Bidirectional双向RNN包装器Bidirectional(layer, merge_mode=’concat’, weights=None)
model = Sequential()
model.add(Bidirectional(LSTM(
10, return_sequences=
True), input_shape=(
5,
10)))
model.add(Bidirectional(LSTM(
10)))
model.add(Dense(
5))
model.add(Activation(
'softmax'))
model.compile(loss=
'categorical_crossentropy', optimizer=
'rmsprop')
2.3 参数说明
名称作用
unitsRNN的单元数,也是输出维度activation激活函数,为预定义的激活函数名dropout0~1之间的浮点数,控制输入线性变换的神经元断开比例recurrent_dropout0~1之间的浮点数,控制循环状态的线性变换的神经元断开比例return_sequencesTrue返回整个序列,用于stack两个层,False返回输出序列的最后一个输出go_backwardsTrue,逆向处理输入序列并返回逆序后的序列statefulTrue,则一个batch中下标为i的样本的最终状态将会用作下一个batch同样下标的样本的初始状态implementation0为大的矩阵乘法,便于CPU加速。1为小矩阵乘法 便于GPU加速。2 LSTM和GRU有效,优化GPU运行input_dim当使用该层为模型首层时,应指定该值input_length当输入序列的长度固定时,该参数为输入序列的长度。当需要在该层后连接Flatten层,然后又要连接Dense层时,需要指定该参数merge_mode前向和后向RNN输出的结合方式,为sum,mul,concat,ave和None之一,若为None,则不结合,以列表形式返回
3 情感分析示例
下面的示例使用了LSTM模型,通过对豆瓣电视剧评论进行训练,最终使得模型可以对评论的好恶进行预测,或者说简单的情感分析。
3.1 语料处理
原始语料来自豆瓣,采集了约100w条豆瓣国产剧评论及对应的评分。在语料处理中,借助jeiba分词工具进行分词,并去除停词。这里下载停词表。最终处理得到的语料类似于下面的格式,即每行一条评论。每行第一个字段为评分,其余字段为分词去停词后的评论。 将语料文件review.csv放在corpus目录下。将stop_word.txt放在dict目录下
5 经典作品 道听途说 以为 懂 实际 读 觉得 独特 意想不到 新颖
5 东京 看过 热海 看过 回家
5 这部 电影 里 看出 东西 太多 比方说 注意 尾道 家中 鸡冠花 会 明白 黑白 影像 彩色 影像 不能 取代 魅力 母亲 热海 防波堤 上 说 东京 游览 热海 回家 真是 人生 非常 隽永 总结
5 刚 几幕 觉得 极 做作 哪有 众人 说 这般 好 再 耐心 看 下去 方 发觉 表面 客套 微笑 下 内心深处 悲凉 其实 很 幸福 其实 幸福 等到 老时 会 老伴 相视而笑
5 生活 总是 人 失望
3.2 评分与情感的关系
为了简化示例,简单的认为1-2分为负面情感,4-5分为正面情感。未评分及3分为中性,不计入训练。这样将问题转化为一个二分类问题。
3.3 文本向量表示
借助Keras提供的文本预处理类Tokenizer,可以很容易的实现文本向量化。处理代码如下:
texts = []
labels = []
for line
in lines:
fields = line.split()
rate = int(fields[
0])
if rate==
0 or rate==
3:
continue
elif rate <
3:
rate =
0
else:
rate =
1
cont = fields[
1:]
cont =
" ".join(cont)
texts.append(cont)
labels.append(rate)
tokenizer.fit_on_texts(texts)
tokenizer.texts_to_sequence(texts)
由于每句长度不同,为便于计算,最终统一用0填充成长度为100的向量.
3.4 模型构建
采用双向LSTM的结构,构建代码如下:
model = Sequential()
model.add(Embedding(vocab_size,
256, input_length=sentence_max_len))
model.add(Bidirectional(LSTM(
128,implementation=
2)))
model.add(Dropout(
0.5))
model.add(Dense(
2, activation=
'relu'))
model.compile(
'RMSprop',
'categorical_crossentropy', metrics=[
'accuracy'])
3.5 完整代码
from keras.models
import Sequential
from keras.preprocessing.text
import Tokenizer
import keras.preprocessing.sequence
as S
from keras.utils
import to_categorical
from keras.layers
import Embedding, Bidirectional, LSTM, Dropout, Dense
import jieba
import json
import numpy
as np
vocab_size =
350000
sentence_max_len =
100
model_path =
'keras.h5'
class SentimentLSTM:
def __init__
(self):
self.tokenizer = Tokenizer(num_words=vocab_size)
self.stop_words = []
self.model =
None
def load_stop_word
(self,path='dict/stop_word.txt'):
with open(path,
'r')
as f:
for line
in f:
content = line.strip()
self.stop_words.append(content.decode(
'utf-8'))
def jieba_cut
(self,line):
lcut = jieba.lcut(line)
cut = [x
for x
in lcut
if x
not in self.stop_words]
cut =
" ".join(cut)
return cut
def load_cuted_corpus
(self, dir, input):
f = open(dir +
'/' + input ,
'r')
lines = f.readlines()
texts = []
labels = []
for line
in lines:
fields = line.split()
rate = int(fields[
0])
if rate==
0 or rate==
3:
continue
elif rate <
3:
rate =
0
else:
rate =
1
cont = fields[
1:]
cont =
" ".join(cont)
texts.append(cont)
labels.append(rate)
self.tokenizer.fit_on_texts(texts)
f.close()
return texts,labels
def load_data
(self):
x,y = self.load_cuted_corpus(
'corpus',
'review.csv')
x = self.tokenizer.texts_to_sequences(x)
x = S.pad_sequences(x,maxlen=sentence_max_len)
y = to_categorical(y,num_classes=
2)
return ((x[
0:
500000],y[
0:
500000]), (x[
500000:], y[
500000:]))
def train
(self,epochs=50):
print 'building model ...'
self.model = SentimentLSTM.build_model()
print 'loading data ...'
(text_train, rate_train), (text_test, rate_text) = self.load_data()
print 'training model ...'
self.model.fit(text_train, rate_train,batch_size=
1000,epochs=epochs)
self.model.save(
'model/keras.model')
score = self.model.evaluate(text_test,rate_text)
print score
def load_trained_model
(self,path):
model = SentimentLSTM.build_model()
model.load_weights(path)
return model
def predict_text
(self,text):
if self.model ==
None:
self.model = self.load_trained_model(model_path)
self.load_stop_word()
self.load_cuted_corpus(
'corpus',
'review.csv')
vect = self.jieba_cut(text)
vect = vect.encode(
'utf-8')
vect = self.tokenizer.texts_to_sequences([vect,])
print vect
return self.model.predict_classes(S.pad_sequences(np.array(vect),
100))
@staticmethod
def build_model
():
model = Sequential()
model.add(Embedding(vocab_size,
256, input_length=sentence_max_len))
model.add(Bidirectional(LSTM(
128,implementation=
2)))
model.add(Dropout(
0.5))
model.add(Dense(
2, activation=
'relu'))
model.compile(
'RMSprop',
'categorical_crossentropy', metrics=[
'accuracy'])
return model
def main
():
lstm = SentimentLSTM()
lstm.train(
10)
while True:
input = raw_input(
'Please input text:')
if input ==
'quit':
break
print lstm.predict_text(input)
if __name__==
"__main__":
main()
运行代码,在训练完模型之后,在交互器中输入新的评论,即可以查看训练的模型对评论的预测了.负向输出为0,正向输出为1.
PS:在约60w的数据集上,CPU上跑10轮至少要10个小时.在GeForce GTX 1080上跑需要30分钟. 模型在测试集上的准确度能达到86%,召回率98%,精确度61%,F1评分75%.增大训练的轮数,100轮左右,仍可提升相关得分.
4 学习资料
1 深入浅出Tensorflow(五):循环神经网络简介
2 LSTM与GRU
语料下载,和采集到的评论数据,密码:dva4
搭建LSTM做文本情感分类的代码2:
import pandas as pd #导入Pandas
import numpy as np #导入Numpy
import jieba #导入结巴分词
from keras.preprocessing import sequence
from keras.optimizers import SGD, RMSprop, Adagrad
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM, GRU
from __future__ import absolute_import #导入3.x的特征函数
from __future__ import print_function
neg=pd.read_excel('neg.xls',header=None,index=None)
pos=pd.read_excel('pos.xls',header=None,index=None) #读取训练语料完毕
pos['mark']=1
neg['mark']=0 #给训练语料贴上标签
pn=pd.concat([pos,neg],ignore_index=True) #合并语料
neglen=len(neg)
poslen=len(pos) #计算语料数目
cw = lambda x: list(jieba.cut(x)) #定义分词函数
pn['words'] = pn[0].apply(cw)
comment = pd.read_excel('sum.xls') #读入评论内容
#comment = pd.read_csv('a.csv', encoding='utf-8')
comment = comment[comment['rateContent'].notnull()] #仅读取非空评论
comment['words'] = comment['rateContent'].apply(cw) #评论分词
d2v_train = pd.concat([pn['words'], comment['words']], ignore_index = True)
w = [] #将所有词语整合在一起
for i in d2v_train:
w.extend(i)
dict = pd.DataFrame(pd.Series(w).value_counts()) #统计词的出现次数
del w,d2v_train
dict['id']=list(range(1,len(dict)+1))
get_sent = lambda x: list(dict['id'][x])
pn['sent'] = pn['words'].apply(get_sent) #速度太慢
maxlen = 50
print("Pad sequences (samples x time)")
pn['sent'] = list(sequence.pad_sequences(pn['sent'], maxlen=maxlen))
x = np.array(list(pn['sent']))[::2] #训练集
y = np.array(list(pn['mark']))[::2]
xt = np.array(list(pn['sent']))[1::2] #测试集
yt = np.array(list(pn['mark']))[1::2]
xa = np.array(list(pn['sent'])) #全集
ya = np.array(list(pn['mark']))
print('Build model...')
model = Sequential()
model.add(Embedding(len(dict)+1, 256))
model.add(LSTM(256, 128)) # try using a GRU instead, for fun
model.add(Dropout(0.5))
model.add(Dense(128, 1))
model.add(Activation('sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', class_mode="binary")
model.fit(xa, ya, batch_size=16, nb_epoch=10) #训练时间为若干个小时
classes = model.predict_classes(xa)
acc = np_utils.accuracy(classes, ya)
print('Test accuracy:', acc)