深度学习|深度学习之循环神经网络(RNN) — 理论与代码相结合

RNN基本概念 如何才能让神经网络记住整个句子来正确预测下一个单词呢?这正是RNN发挥作用的时候。
RNN的输出不仅是基于当前的输入,还会基于先前的隐态。此时可能会好奇为什么不能根据当前输入和之前的输入来预测输出,而必须是当前的输入和先前的隐态。这是因为先前的输入只保存了前一个单词的信息,而先前的隐态捕获了整个句子的信息,即先前的隐态捕获了上下文。因此,基于当前的输入和先前的隐态能够预测输出,而不是根据当前输入和先前输入。
RNN广泛用于诸如机器翻译,情感分析等各种自然语言处理(NLP)任务。另外,还适用于股票市场数据等时序数据。
深度学习|深度学习之循环神经网络(RNN) — 理论与代码相结合
文章图片

RNN与普通神经网络的不同之处在于隐态中有一个循环,这表明是如何利用先前的隐态来计算输出的。
如下图所示,输出y 1 y_1 y1? 是根据当前输入x 1 x_1 x1?、当前隐态h 1 h_1 h1?,和先前隐态h 0 h_0 h0?预测的, y 2 y_2 y2?同理。这就是RNN的工作原理,是采用当前输入和先前隐态来预测输出的。由于这些稳态可以保存所有已观察过的信息,因此可称为记忆单元。
深度学习|深度学习之循环神经网络(RNN) — 理论与代码相结合
文章图片

接下来,从数学角度分析:

  • U表示输入到隐态的权重矩阵
  • W表示隐态到隐态的权重矩阵
  • V表示隐态到输出的权重矩阵
在前向传递中,可计算如下:
???????????? h t = ? ( U x t + W h t ? 1 ) h_{t}=\phi\left(U x_{t}+W h_{t-1}\right) ht?=?(Uxt?+Wht?1?)
即,时刻 t 的隐态=tanh([输入 - 隐态权重矩阵 x 输入] + [ 隐态 - 隐态权重矩阵 x 时刻 t-1 的前一隐态]):
?????????????? y ^ t = σ ( v h t ) \hat{y}_{t}=\sigma\left(v h_{t}\right) y^?t?=σ(vht?)
即,时刻 t 的输出=Sigmoid(隐态 - 输出权重矩阵 x 时刻 t 的隐态)
另外,还可以定义损失函数为交叉熵损失,如下:
??????????????损失= ? y t log ? y ^ t =-y_{t} \log \hat{y}_{t} =?yt?logy^?t?
?????????????总损失= ? ∑ t y t log ? y t ^ =-\sum_{t} y_{t} \log \hat{y_{t}} =?∑t?yt?logyt?^?
y t y_t yt? 为时刻 t 的实际单词, y t ^ \hat{y_{t}} yt?^?为 t 时刻的预测单词,由于是取整个句子为训练样本,因此总的损失是每个时间步的损失之和。
基于时间的反向传播 可以利用反向传播训练RNN。但在RNN中,由于与所有时间步都相关,因此每个输出的梯度不仅与当前的时间步有关,而且还取决于先前的时间步。将上述过程称为基于时间的反向传播(BPTT) 这基本与反向传播相同,只是应用了RNN。
深度学习|深度学习之循环神经网络(RNN) — 理论与代码相结合
文章图片

上图中, L 1 L_1 L1?、 L 2 L_2 L2?、 L 3 L_3 L3?是在每个时间步的损失。现在,需要在每个时间步计算这些损失相对于权重矩阵U、V和W的梯度。与之前通过在每个时间步的损失之和来计算总的损失一样,在此利用每个时间步的梯度之和来更新权重矩阵。
???????????? ? L ? V = ∑ t ? L t ? V \frac{\partial L}{\partial V}=\sum_{t} \frac{\partial L_{t}}{\partial V} ?V?L?=∑t??V?Lt??
但是,该方法有一个问题。梯度计算涉及计算相对于激活函数的梯度。在计算相对于Sigmoid/tanh函数的梯度时,梯度值会非常小。而当经过多个时间步反向传播网络并乘以梯度时,梯度值会变得越来越小。称为梯度消失问题。那么就不能学习长期相关信息,即RNN不能长时间保存信息。
【深度学习|深度学习之循环神经网络(RNN) — 理论与代码相结合】梯度消失不仅发生在RNN中,而且也会在采用Sigmoid/tanh函数且具有多个隐层的其他深度神经网络中出现。如果梯度值大于1,当乘以这些梯度时,会产生一个非常大的值,这称为梯度爆炸问题。
一种解决方法是采用ReLU作为激活函数。另外,使用RNN的改进网络LSTM来解决梯度消失的问题。
LSTM RNN LSTM是RNN的一种变型,主要用于解决梯度消失问题。只要需要,LSTM就会在记忆中保存信息。实质上是由LSTM替换了RNN单元。
利用LSTM RNN来生成歌词
首先,导入库
import warnings warnings.filterwarnings('ignore') import tensorflow as tf import numpy as np

接着,读取包含歌词的文件
with open("data/ZaynLyrics.txt","r") as f: data=https://www.it610.com/article/f.read() data=data.replace('\n','') data = https://www.it610.com/article/data.lower()

可以观察数据内容:
print(data[:50])

然后,在all_chars变量中保存所有字符:
all_chars = list(set(data))

将唯一字符的个数保存在unique_chars中:
unique_chars = len(all_chars)

现在,创建每个字符与其编号之间的对应关系。char_to_ix具有字符到编号的映射,而ix_to_char是编号到字符的映射:
char_to_ix = { ch:i for i,ch in enumerate(all_chars) } ix_to_char = { i:ch for i,ch in enumerate(all_chars) }

例如:
char_to_ix ['e'] 9 ix_to_char[9] e

接下来,定义一个generate_batch函数来生成输入和目标值。目标值就是移位i i i次后的输入值
例如:如果 input = [12,13,24] 且移位值为1,则目标值为 [13,24]
def generate_batch(seq_length,i): inputs = [char_to_ix[ch] for ch in data[i:i+seq_length]] targets = [char_to_ix[ch] for ch in data[i+1:i+seq_length+1]] inputs = np.array(inputs).reshape(seq_length, 1) targets = np.array(targets).reshape(seq_length, 1) return inputs, targets

接着,定义句子长度、学习率和节点个数,即神经元个数:
seq_length = 25 learning_rate = 0.1 num_nodes = 300

现在开始构建LSTM RNN。 Tensorflow提供了一种构建LSTM单元的BasicLSTMCell() 函数,但需要指定LSTM单元中的单元个数和所采用的激活函数类型。
在此,创建一个LSTM单元,并利用tf.nn.dynamic_rnn函数来构建包含这一单元的RNN,该函数可返回输出值和状态值:
def build_rnn(x): cell= tf.contrib.rnn.BasicLSTMCell(num_units=num_nodes, activation=tf.nn.relu) outputs, states = tf.nn.dynamic_rnn(cell, x, dtype=tf.float32) return outputs,states

接着,创建输入X和目标Y的占位符
X=tf.placeholder(tf.float32,[None,1]) Y=tf.placeholder(tf.float32,[None,1])

将X和Y转换为int型
X=tf.cast(X,tf.int32) Y=tf.cast(Y,tf.int32)

另外,还需创建对于X和Y的onehot表示,具体如下:
X_onehot=tf.one_hot(X,unique_chars) Y_onehot=tf.one_hot(Y,unique_chars)

调用build_rnn函数,可由RNN得到输出和状态:
outputs,states=build_rnn(X_onehot)

输出转置:
outputs=tf.transpose(outputs,perm=[1,0,2])

初始化权重和偏置:
W=tf.Variable(tf.random_normal((num_nodes,unique_chars),stddev=0.001)) B=tf.Variable(tf.zeros((1,unique_chars)))

将输出乘以权重并加上偏置来计算最终的输出:
Ys=tf.matmul(outputs[0],W)+B

接着,应用Softmax激活函数,得到概率:
prediction = tf.nn.softmax(Ys)

计算cross_entropy
cross_entropy=tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=Y_onehot,logits=Ys))

目标是使得损失最小化,因此,需反向传播网络并执行梯度下降:
optimiser = tf.train.GradientDescentOptimizer(learning_rate=learning_rate).minimize(cross_entropy)

在此,定义一个称为predict的帮助函数,用于根据RNN模型得到下一个预测字符的编号:
def predict(seed,i): x=np.zeros((1,1)) x[0][0]= seed indices=[] for t in range(i): p=sess.run(prediction,{X:x}) index = np.random.choice(range(unique_chars), p=p.ravel()) x[0][0]=index indices.append(index) return indices

设置batch_size、批次数和epochs个数以及shift值,来生成一个批次:
batch_size=100 total_batch=int(total_chars//batch_size) epoch=1000 shift=0

最后,启动Tensorflow模型,来构建损失:
init=tf.global_variables_initializer()with tf.Session() as sess: sess.run(init) for epoch in range(epoch): print("Epoch {}:".format(epoch)) if shift + batch_size+1 >= len(data): shift =0# 通过generate_batch函数获得每个批量的输入目标 # 该函数是通过 shifts 值将输入移位并形成目标值的 for i in range(total_batch): inputs,targets=generate_batch(batch_size,shift) shift += batch_size# 计算损失 if(i%100==0): loss=sess.run(cross_entropy,feed_dict={X:inputs, Y:targets}) # 获得predict函数得到下一预测字符的符号 index =predict(inputs[0],200)# 将该序号传给ix_to_char字典,并得到char txt = ''.join(ix_to_char[ix] for ix in index) print('Iteration %i: '%(i)) print ('\n %s \n' % (txt, ))sess.run(optimiser,feed_dict={X:inputs,Y:targets})

7.10 Generating Song Lyrics Using LSTM RNN.ipynb

    推荐阅读