循环神经网络(RNN)介绍2:keras代码分析
根据上一篇的介绍,我们已经对循环神经网络有了基本了解。上一篇文章的“keras部署神经网络”部分,我们提供了搭建一个简单RNN网络的代码,但是实际运行代码时总会遇见各种问题,笔者就是如此,为了方便理解代码,笔者带着大家一步步分析代码,对代码中涉及到的一些自然语言处理的概念解释,实际运行中遇到的错误进行分析并给出解决方法。
我们的目标是:使用keras搭建RNN网路,使用推文数据训练模型
实现目标的思路是:准备工作 -> 数据序列化 -> 得到词嵌入矩阵 -> 训练网络
- 准备工作:读取相关库函数,读入数据集,划分训练集和测试集,
- 数据序列化:将文本转换为数字序列
- 得到词嵌入矩阵:读取glove模型,得到词嵌入矩阵
- 训练网络:划分训练集和验证集,搭建简单的RNN网络,训练网络
准备工作
1. 读取相关库函数.
import keras
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout
from keras.layers.convolutional import Conv1D
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import pandas as pd
import numpy as np
import spacy
这里需要先安装keras库和spacy。spacy是一个Python自然语言处理工具包,诞生于2014年年中,号称“Industrial-Strength Natural Language Processing in Python”,是具有工业级强度的Python NLP工具包。
2. 读入数据集
#load the dataset
train=pd.read_csv("./datasets/training.1600000.processed.noemoticon.csv" , encoding= "latin-1")
Y_train = train[train.columns[0]]
X_train = train[train.columns[5]]
首先读入数据,然后提取我们需要的数据,也就是标签和推文。我们可以使用train.head(3)
来查看文件的前三行,其中第1行数据是我们需要的标签数据,第6行数据是我们需要的推文。如何得到这些数据呢,我们知道对于一个panda.Dataframe类型的数据,可以通过列名称直接提取列数据train['column_name']
,但是这里我们想通过列的索引提取,所以这里我们使用了一个技巧,就是通过train.columns[i]
得到指定列的名称。
我们可以看一下数据和标签。首先看一下前三行数据:
X_train.head(3)
0 is upset that he can't update his Facebook by ...
1 @Kenichan I dived many times for the ball. Man...
2 my whole body feels itchy and like its on fire
Name: @switchfoot [http://twitpic.com/2y1zl](http://twitpic.com/2y1zl) - Awww, that's a bummer. You shoulda got David Carr of Third Day to do it. ;D, dtype: object
Y_train.head(3)
0 0
1 0
2 0
Name: 0, dtype: int64
很明显,前三行数据的情绪都是负情绪,标签也都被标记为0.
再看一下最后3行数据。
X_train.tail(3)
19996 Are you ready for your MoJo Makeover? Ask me f...
19997 Happy 38th Birthday to my boo of alll time!!! ...
19998 happy #charitytuesday @theNSPCC @SparksCharity...
Name: @switchfoot [http://twitpic.com/2y1zl](http://twitpic.com/2y1zl) - Awww, that's a bummer. You shoulda got David Carr of Third Day to do it. ;D, dtype: object
Y_train.tail(3)
19996 4
19997 4
19998 4
Name: 0, dtype: int64
很明显,表达开心情绪的推文被标记为4
我们的目的就是使用RNN网络对这些推文进行分类。
3. 将数据划分为训练集和测试集
# split the data into test and train
from sklearn.model_selection import train_test_split
trainset1x, trainset2x, trainset1y, trainset2y = train_test_split(X_train.values, Y_train.values, test_size=0.02,random_state=42 )
trainset1y=pd.get_dummies(trainset1y)
我们使用sklean的train_test_split函数进行训练集测试集分类,最后一行代码pd.get_dummies()
的意思是将数据转换为one-hot向量,我们的y只有2个值0和4,所以使用两维的one-hot向量进行编码。
数据序列化
tokenizer = Tokenizer()
tokenizer.fit_on_texts(trainset1x)
sequences = tokenizer.texts_to_sequences(trainset1x)
word_index = tokenizer.word_index
data = pad_sequences(sequences, maxlen=15, padding="post")
Found 29863 unique tokens.
(19599, 15)
这里比较复杂,也涉及了较多的自然语言处理知识,为了照顾一般读者,我们不长篇大论这段代码背后的nlp原理。
我们只关注每一步完成了什么变化,以及这些变化的目的。仔细观察,这部分代码可以分为3部分。第一部分得到了sequences,我们先来看看sequences是什么
sequences[1]
[27, 95, 94, 15, 4, 27, 105, 976, 487, 78, 139]
trainset1x[1]
'good night twitter have a good sleep everybody xx 2 days!'
通过对比sequences[1]
和trainset1x[1]
,我们发现,这一步的变化就是将单词映射成了数字,注意sequences[1]
的第一个值和第六个值都是27,对应的trainset1x[1]
的第一个单词和第六个单词都是good。这样做的目的就是将单词映射为数字,后面,这些数字作为索引能得到对应单词的词向量。
接下来看第二部分
print('Found %s unique tokens.' % len(word_index))
Found 29863 unique tokens.
这部分很简单,得到一共有多少个唯一的单词。
第三部分也只有一行代码,我们观察得到的数据data
data.shape
(19599, 15)
pad_sequences
的作用是将sequences的每一列扩展到15,扩展方式是向后填充0,我们再来对比一下sequences[1]
和data[1]
data[1]
array([ 27, 95, 94, 15, 4, 27, 105, 976, 487, 78, 139, 0, 0,
0, 0], dtype=int32)
我们发现原来的sequences[1]
被扩展为了15个元素,且最后的元素使用0进行填充。
得到词嵌入矩阵
1. 读取glove模型
#loading the glove model
def loadGloveModel(gloveFile):
print("Loading Glove Model")
f = open(gloveFile,'r')
model = {}
for line in f:
splitLine = line.split()
word = splitLine[0]
embedding = [float(val) for val in splitLine[1:]]
model[word] = embedding
print ("Done.",len(model)," words loaded!")
return model
# save the glove model
model=loadGloveModel("./glove/glove.twitter.27B/glove.twitter.27B.200d.txt")
Loading Glove Model
Done. 1177902 words loaded!
glove模型是什么,我们可以简单的理解为单词对应的词向量,这里我们用200维的向量来表示一个单词。继续使用单词good来看一下,这里只看前3个值,避免太长。
model.get('good')[0:3]
[0.018223, -0.012323, 0.035569]
读者一定注意到了这部分代码的输出,我们一共读取了1177902个单词的词向量,但是实际上,根据上一部分的代码,我们的数据集只有29863个单词。所以接下来我们通过得到词嵌入矩阵,只保留我们需要的单词。
2.得到词嵌入(the embedding)矩阵
#calculate the number of words
nb_words=len(word_index)+1
#obtain theembedding matrix
embedding_matrix = np.zeros((nb_words, 200))
for word, i in word_index.items():
embedding_vector = model.get(word)
if embedding_vector is not None:
embedding_matrix[i] = embedding_vector
print('Null word embeddings: %d' % np.sum(np.sum(embedding_matrix, axis=1) == 0))
Null word embeddings: 12354
直接来看看第27个向量,也就是good对应的词向量的前三个数是多少。
embedding_matrix[27][0:3]
array([ 0.018223, -0.012323, 0.035569])
ok,与我们之前看到的词向量一致,矩阵搞定,每一行代表一个单词的词向量。
训练网络
1. 将训练集再次划分为训练集和验证集
#reshape the data and preparing to train
data=data.reshape(19599,15,1)
trainx, validx, trainy, validy = train_test_split(data, trainset1y, test_size=0.3,random_state=42 )
这一步是为了做交叉验证,也是为了使用数字化之后的data,而不是原始的数据trainset1x。
trainy=np.array(trainy)
validy=np.array(validy)
保证格式合法
2. 打造简单的RNN网络
#building a simple RNN model
def modelbuild():
model = Sequential()
model.add(keras.layers.InputLayer(input_shape=(15,1)))
keras.layers.embeddings.Embedding(nb_words, 15, weights=[embedding_matrix], input_length=15,
trainable=False)
model.add(keras.layers.recurrent.SimpleRNN(units = 100, activation='relu',
use_bias=True))
model.add(keras.layers.Dense(units=1000, input_dim = 2000, activation='sigmoid'))
model.add(keras.layers.Dense(units=500, input_dim=1000, activation='relu'))
model.add(keras.layers.Dense(units=2, input_dim=500,activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
这里是打造rnn网络的主体部分,也比较简单,首先初始化一个序列模型的实例model
.
整个模型需要明确输入层,权重矩阵,全连接层和输出层。
首先第一层的维度是(15,1),因为我们把所有句子的长度都限定为15个单词了。
然后明确我们的权重矩阵维度是(nb_words, 15),使用我们之前已经得到的矩阵embedding_matrix。
主体部分是简单rnn网络,我们指定循环单元数量是100,激活函数是relu。
接下来是两层,分别为1000层和500层的全连接层。
最后的输出使用softmax函数,得到两个输出0或1。
3. 训练模型
最后我们使用训练集和验证集进行模型训练。
#compiling the model
finalmodel = modelbuild()
finalmodel.fit(trainx, trainy, epochs=10, batch_size=120,validation_data=(validx,validy))
Train on 13719 samples, validate on 5880 samples
Epoch 1/10
13719/13719 [==============================] - 6s 439us/step - loss: 0.7447 - acc: 0.5237 - val_loss: 0.6976 - val_acc: 0.5235
Epoch 2/10
13719/13719 [==============================] - 5s 343us/step - loss: 0.6947 - acc: 0.5254 - val_loss: 0.6891 - val_acc: 0.5376
Epoch 3/10
13719/13719 [==============================] - 5s 329us/step - loss: 0.6881 - acc: 0.5306 - val_loss: 0.6863 - val_acc: 0.5425
Epoch 4/10
13719/13719 [==============================] - 4s 326us/step - loss: 0.6876 - acc: 0.5350 - val_loss: 0.6858 - val_acc: 0.5417
Epoch 5/10
13719/13719 [==============================] - 5s 334us/step - loss: 0.6857 - acc: 0.5431 - val_loss: 0.6873 - val_acc: 0.5255
Epoch 6/10
13719/13719 [==============================] - 5s 358us/step - loss: 0.6857 - acc: 0.5462 - val_loss: 0.6852 - val_acc: 0.5410
Epoch 7/10
13719/13719 [==============================] - 4s 326us/step - loss: 0.6850 - acc: 0.5482 - val_loss: 0.6841 - val_acc: 0.5383
Epoch 8/10
13719/13719 [==============================] - 5s 359us/step - loss: 0.6848 - acc: 0.5491 - val_loss: 0.6924 - val_acc: 0.5306
Epoch 9/10
13719/13719 [==============================] - 5s 363us/step - loss: 0.6838 - acc: 0.5528 - val_loss: 0.6851 - val_acc: 0.5454
Epoch 10/10
13719/13719 [==============================] - 5s 356us/step - loss: 0.6837 - acc: 0.5516 - val_loss: 0.6874 - val_acc: 0.5378
<keras.callbacks.History at 0x171a31b38>
最终,我们得到了训练集精度为0.5516的RNN模型,接下来我们使用第二部分用于分析的推文’good night twitter have a good sleep everybody xx 2 days!’来测试一下模型效果。
finalmodel.predict(np.array(data[1].reshape(1,15,1)))
array([[ 0.54520983, 0.4547902 ]], dtype=float32)
输出层softmax得到了两个输出,其中比较大的是0.54520983,如果阈值是0.5,那么我们可以将该推文分类为0,也就是“积极情绪”