目前RNN网络可以在深度学习框架下通过调用函数实现(比如:tf.nn.rnn_cell),但为了掌握更多RNN的细节,我们还是需要使用numpy来逐步实现。
由于RNN网络具有“记忆力”,因此非常适合NLP和序列任务。RNN网络每次读取一个输入X(t),输入信息从当前时间步传到下一步的过程中,网络隐含层的激活函数会“记住”一些信息或上下文内容。这种机制允许单向RNN从前面的输入中获取信息以处理后续输入,同时使得双向RNN网络能够从过去和未来的获取上下文信息。
导入所需的第三方库,其中所用辅助程序可点击此处下载。
import numpy as np
from rnn_utils import *
1.基本RNN的前向传播
基本RNN结构如下,其中Tx = Ty:
1.1RNN单元
RNN网络可以看做是一个单元的重复执行,首先要执行的单一时间步的计算,计算操作如图所示:
如图所示,需要计算的步骤如下:
(1)计算隐层状态a<t>:
(2)计算预测值y<t>_hat:
(3)在缓存中存储cache:(a<t>,a<t-1>,X<t>,parameters)
(4)返回a<t>,y<t>_hat 和 cache
我们将m个样本向量化,因此X<t>的维度是(n_x, m),a<t>的维度是(n_a, m)
def rnn_cell_forward(xt, a_prev, parameters):
Wax = parameters["Wax"]
Waa = parameters["Waa"]
Wya = parameters["Wya"]
ba = parameters["ba"]
by = parameters["by"]
a_next = np.tanh(np.dot(Waa, a_prev) + np.dot(Wax, xt) + ba)
yt_pred = softmax(np.dot(Wya, a_next) + by)
cache = (a_next, a_prev, xt, parameters)
return a_next, yt_pred, cache
np.random.seed(1)
xt = np.random.randn(3,10)
a_prev = np.random.randn(5,10)
Waa = np.random.randn(5,5)
Wax = np.random.randn(5,3)
Wya = np.random.randn(2,5)
ba = np.random.randn(5,1)
by = np.random.randn(2,1)
parameters = {"Waa": Waa, "Wax": Wax, "Wya": Wya, "ba": ba, "by": by}
a_next, yt_pred, cache = rnn_cell_forward(xt, a_prev, parameters)
print("a_next[4] = ", a_next[4])
print("a_next.shape = ", a_next.shape)
print("yt_pred[1] =", yt_pred[1])
print("yt_pred.shape = ", yt_pred.shape)
a_next[4] = [ 0.59584544 0.18141802 0.61311866 0.99808218 0.85016201 0.99980978
-0.18887155 0.99815551 0.6531151 0.82872037]
a_next.shape = (5, 10)
yt_pred[1] = [0.9888161 0.01682021 0.21140899 0.36817467 0.98988387 0.88945212
0.36920224 0.9966312 0.9982559 0.17746526]
yt_pred.shape = (2, 10)
1.2RNN前向传播
RNN的结构实质上就是1.1中介绍基本单元的多次组合,对于每一个单元将a<t-1>和X<t>,输出为a<t>和y<t>预测值,下图是一个RNN模型的前向传播的过程。
def rnn_forward(x, a0, parameters):
caches = []
n_x, m, T_x = x.shape
n_y, n_a = parameters["Wya"].shape
a = np.zeros((n_a, m, T_x))
y_pred = np.zeros((n_y, m, T_x))
a_next = a0
for t in range(T_x):
a_next, yt_pred, cache = rnn_cell_forward(x[:,:,t], a_next, parameters)
a[:,:,t] = a_next
y_pred[:,:,t] = yt_pred
caches.append(cache)
caches = (caches, x)
return a, y_pred, caches
np.random.seed(1)
x = np.random.randn(3,10,4)
a0 = np.random.randn(5,10)
Waa = np.random.randn(5,5)
Wax = np.random.randn(5,3)
Wya = np.random.randn(2,5)
ba = np.random.randn(5,1)
by = np.random.randn(2,1)
parameters = {"Waa": Waa, "Wax": Wax, "Wya": Wya, "ba": ba, "by": by}
a, y_pred, caches = rnn_forward(x, a0, parameters)
print("a[4][1] = ", a[4][1])
print("a.shape = ", a.shape)
print("y_pred[1][3] =", y_pred[1][3])
print("y_pred.shape = ", y_pred.shape)
print("caches[1][1][3] =", caches[1][1][3])
print("len(caches) = ", len(caches))
a[4][1] = [-0.99999375 0.77911235 -0.99861469 -0.99833267]
a.shape = (5, 10, 4)
y_pred[1][3] = [0.79560373 0.86224861 0.11118257 0.81515947]
y_pred.shape = (2, 10, 4)
caches[1][1][3] = [-1.1425182 -0.34934272 -0.20889423 0.58662319]
len(caches) = 2
目前我们已经完成了RNN的前向传播过程,这个模型已经可以解决不少训练问题了,但是存在着梯度消失的问题。下面我们将构建LSTM来解决这个问题,以便模型可以很好的利用上下文信息。
2.长短时记忆网络(LSTM)
LSTM的主要特点是增加了遗忘门、更新门和输出门,如下图所示;
LSTM网络也是有LSTM Cell堆叠构成的。
(1)遗忘门
假设我们在阅读一段文字,主语的单复数形式决定着后文谓语的形式,因此我们需要利用LSTM的记忆功能。但是当阅读到下一段主语形式变化了,LSTM需要丢弃之前的记忆,这个功能便需要遗忘门实现,公式如下:
上式的结果是介于0-1的矢量,如果接近0,意味着LSTM应移除记忆信息;如果为1,意味着需要保持c<t-1>的信息。
(2)更新门
一旦决定遗忘主语的单数形式