循环神经网络(RNN)介绍2:keras代码分析

循环神经网络(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,也就是“积极情绪”

编辑于 2018-01-12 15:58