机器学习(七)循环神经网络

参考:https://zhuanlan.zhihu.com/p/123211148https://zhuanlan.zhihu.com/p/24018768

一、循环神经网络

1.RNN

传统的神经网络并不能做到持续记忆,这应该是传统神经网络的一个缺陷。假想一下,你想让神经网络对电影中每个时间点的事件进行分类,很明显,传统神经网络不能使用前一个事件去推理下一个事件。递归神经网络可以解决这个问题。它们是带有循环的神经网络,允许信息保留一段时间。

循环神经网络开山之作_LSTM

在上图中,A 代表神经网络主体, xt 是网络输入,ht是网络输出,循环结构允许信息从当前输出传递到下一次的网络输入。这些循环让递归神经网络看起来有点神秘,然而,如果你再进一步思考一下,它与传统神经网络并没有太多不同。一个递归神经网络可以看多是一个网络的多次拷贝,每次把信息传递给他的继任者。

循环神经网络开山之作_深度学习_02

举个例子,有一句话是,I love you,那么在利用RNN做一些事情时,比如命名实体识别,上图中的 循环神经网络开山之作_循环神经网络开山之作_03代表的就是I这个单词的向量, 循环神经网络开山之作_循环神经网络开山之作_04代表的是love这个单词的向量,循环神经网络开山之作_RNN_05代表的是you这个单词的向量,以此类推,我们注意到,上图展开后,W一直没有变,W其实是每个时间点之间的权重矩阵,我们注意到,RNN之所以可以解决序列问题,是因为它可以记住每一时刻的信息,每一时刻的隐藏层不仅由该时刻的输入层决定,还由上一时刻的隐藏层决定,公式如下,其中 循环神经网络开山之作_深度学习_06代表t时刻的输出, 循环神经网络开山之作_人工智能_07代表t时刻的隐藏层的值:

循环神经网络开山之作_深度学习_08

循环神经网络开山之作_人工智能_07的值不仅仅取决于循环神经网络开山之作_循环神经网络开山之作_10,还取决于循环神经网络开山之作_RNN_11

2.RNN的长期依赖问题

人门希望RNNs能够连接之前的信息到当前的任务中,例如,使用之前的图像帧信息去辅助理解当前的帧。如果RNNs可以做到这个,它们将会特别的有用,但是它们可以做到吗?这要视情况而定。 有时,我们仅仅需要使用当前的信息去执行当前的任务。例如, 一个语言模型试图根据之前的单词去预测下一个单词。如果我们试图去预测“the clouds are in the sky”,我们不需要更多的上下文信息–很明显下一个单词会是sky。在类似这种相关信息和需要它的场合并不太多的情景下,RNNs可以学习使用之前的信息。

但是,也有很多场景需要使用更多的上下文。当我们去尝试预测“I grew up in France…I speak fluent French”的最后一个单词,最近的信息表明下一个单词应该是语言的名字,但是如果我们想缩小语言的范围,看到底是哪种语言,我们需要France这个在句子中比较靠前的上下文信息。相关信息和需要预测的点的间隔很大的情况是经常发生的。不幸的事,随着间隔的增大,RNNs连接上下文信息开始力不从心了。

这里就牵扯到梯度消失和爆炸的问题,上面那个最基础版本的RNN,我们可以看到,每一时刻的隐藏状态都不仅由该时刻的输入决定,还取决于上一时刻的隐藏层的值,如果一个句子很长,到句子末尾时,它将记不住这个句子的开头的内容详细内容。

LSTM通过它的“门控装置”有效的缓解了这个问题,这也就是为什么我们现在都在使用LSTM而非普通RNN。

3.LSTM

长短期记忆网络–通畅叫做”LSTMs”–是一种特殊的RNNs, 它能够学习长期依赖。,后来在很多人的努力下变得越来越精炼和流行。它们在大量的问题上有惊人的效果,现在被广泛的使用。LSTMs被明确的设计用来解决长期依赖问题,记住长时间段的信息是他们的必备技能,不像RNNs那么费力去做还做不好。所有的递归神经网络都有重复神经网络本身模型的链式形式。

在标准的RNNs, 这个复制模块只有一个非常简单的结构,例如一个双极性(tanh)层。

循环神经网络开山之作_RNN_12

LSTMs 也有这种链式结构,但是这个重复模块与上面提到的RNNs结构不同:LSTMs并不是只增加一个简单的神经网络层,而是四个,它们以一种特殊的形式交互。

循环神经网络开山之作_深度学习_13

循环神经网络开山之作_人工智能_14

在上图中,每条线表示一个向量,从一个输出节点到其他节点的输入节点。这个粉红色圆圈表示逐点式操作,就像向量加法。黄色的盒子是学习好的神经网络的层。线条合表示联结,相反,线条分叉表示内容被复制到不同位置。

Sigmod层输出0~1之间的数字,描述了一个神经元有多少信息应该被通过。输出“0”意味着“全都不能通过”,输出“1”意味着“让所有都通过”。一个LSTM有三个这样的门限,去保护和控制神经元状态。

LSTM是RNN的一种变体,更高级的RNN,那么它的本质还是一样的,还记得RNN的特点吗,可以有效的处理序列数据,当然LSTM也可以,还记得RNN是如何处理有效数据的吗,是不是每个时刻都会把隐藏层的值存下来,到下一时刻的时候再拿出来用,这样就保证了,每一时刻含有上一时刻的信息,如图,我们把存每一时刻信息的地方叫做Memory Cell,中文就是记忆细胞,可以这么理解。

循环神经网络开山之作_循环神经网络开山之作_15

普通RNN就像一个乞丐,路边捡的,别人丢的,什么东西他都想要,什么东西他都不嫌弃,LSTM就像一个贵族,没有身份的东西他不要,他会精心挑选符合自己身份的物品。这是为什么呢?有没有思考过,原因很简单,乞丐没有选择权,他的能力注定他只能当一个乞丐,因此他没有挑选的权利,而贵族不一样,贵族能力比较强,经过自己的打拼,终于有了地位和身份,所以可以选择舍弃一些低档的东西,这也是能力的凸显。

LSTM和普通RNN正是贵族和乞丐,RNN什么信息它都存下来,因为它没有挑选的能力,而LSTM不一样,它会选择性的存储信息,因为它能力强,它有门控装置,它可以尽情的选择。如下图,普通RNN只有中间的Memory Cell用来存所有的信息,而从下图我们可以看到,LSTM多了三个Gate,也就是三个门,什么意思呢?在现实生活中,门就是用来控制进出的,门关上了,你就进不去房子了,门打开你就能进去,同理,这里的门是用来控制每一时刻信息记忆与遗忘的。

  • Input Gate:中文是输入门,在每一时刻从输入层输入的信息会首先经过输入门,输入门的开关会决定这一时刻是否会有信息输入到Memory Cell。
  • Output Gate:中文是输出门,每一时刻是否有信息从Memory Cell输出取决于这一道门。
  • Forget Gate:中文是遗忘门,每一时刻Memory Cell里的值都会经历一个是否被遗忘的过程,就是由该门控制的,如果打卡,那么将会把Memory Cell里的值清除,也就是遗忘掉

二、循环神经网络的应用

1.导入数据
import pandas as pd # 导入Pandas
import numpy as np # 导入NumPy
dir = '../input/product-comments/'
dir_train = dir+'Clothing Reviews.csv'
df_train = pd.read_csv(dir_train) # 读入训练集
df_train.head() # 输出部分数据

循环神经网络开山之作_循环神经网络开山之作_16

2.分词工作
from keras.preprocessing.text import Tokenizer # 导入分词工具
X_train_lst = df_train["Review Text"].values # 将评论读入张量(训练集)
y_train = df_train["Rating"].values # 构建标签集
dictionary_size = 20000 # 设定词典的大小
tokenizer = Tokenizer(num_words=dictionary_size) # 初始化词典
tokenizer.fit_on_texts( X_train_lst ) # 使用训练集创建词典索引
# 为所有的单词分配索引值,完成分词工作
X_train_tokenized_lst = tokenizer.texts_to_sequences(X_train_lst)
import matplotlib.pyplot as plt # 导入matplotlib
word_per_comment = [len(comment) for comment in X_train_tokenized_lst]
plt.hist(word_per_comment, bins = np.arange(0,500,10)) # 显示评论长度分布
plt.show()

评论长度直方图:

循环神经网络开山之作_循环神经网络开山之作_17

对长度大于120的评论截断处理,小于120则填充无意义的0值

from keras.preprocessing.sequence import pad_sequences 
max_comment_length = 100 # 设定评论输入长度为100,并填充默认值(如字数少于100)
X_train = pad_sequences(X_train_tokenized_lst, maxlen=max_comment_length)
3.SimpleRNN
from keras.models import Sequential # 导入贯序模型
from keras.layers.embeddings import Embedding #导入词嵌入层
from keras.layers import Dense #导入全连接层
from keras.layers import SimpleRNN #导入SimpleRNN层
embedding_vecor_length = 60 # 设定词嵌入向量长度为60
rnn = Sequential() # 贯序模型
rnn.add(Embedding(dictionary_size, embedding_vecor_length, 
          input_length=max_comment_length)) # 加入词嵌入层
rnn.add(SimpleRNN(100)) # 加入SimpleRNN层
rnn.add(Dense(10, activation='relu')) # 加入全连接层
rnn.add(Dense(6, activation='softmax')) # 加入分类输出层
rnn.compile(loss='sparse_categorical_crossentropy', #损失函数
            optimizer='adam', # 优化器
            metrics=['acc']) # 评估指标
print(rnn.summary()) #打印网络模型
history = rnn.fit(X_train, y_train, 
                    validation_split = 0.3, 
                    epochs=10, 
                    batch_size=64)
4.LSTM
from keras.models import Sequential # 导入贯序模型
from keras.layers.embeddings import Embedding #导入词嵌入层
from keras.layers import Dense #导入全连接层
from keras.layers import LSTM #导入LSTM层
from keras.layers import Dropout

embedding_vecor_length = 60 # 设定词嵌入向量长度为60
lstm = Sequential() # 贯序模型
lstm.add(Embedding(dictionary_size, embedding_vecor_length, 
          input_length=max_comment_length)) # 加入词嵌入层

lstm.add(LSTM(100,return_sequences=True)) # 加入LSTM层
lstm.add(Dropout(0.5))
lstm.add(LSTM(100,return_sequences=False)) # 加入LSTM层
lstm.add(Dropout(0.5))


lstm.add(Dense(10, activation='relu')) # 加入全连接层
lstm.add(Dense(6, activation='softmax')) # 加入分类输出层

lstm.compile(loss='sparse_categorical_crossentropy', #损失函数
             optimizer = 'adam', # 优化器
             metrics = ['acc']) # 评估指标
history = lstm.fit(X_train, y_train, 
                    validation_split = 0.3,
                    epochs=10, 
                    batch_size=64)