符号说明
-
上标 [ l ] [l] [l] 表示 l t h l^{th} lth 层.
- 例如: a [ 4 ] a^{[4]} a[4] 为 4 t h 4^{th} 4th 层激活. W [ 5 ] W^{[5]} W[5] 和 b [ 5 ] b^{[5]} b[5] 为 5 t h 5^{th} 5th 层参数.
-
上标 ( i ) (i) (i) 表示 i t h i^{th} ith 样本.
- 例如: x ( i ) x^{(i)} x(i) 为 i t h i^{th} ith 训练样本输入.
-
上标 ⟨ t ⟩ \langle t \rangle ⟨t⟩ 表示在 t t h t^{th} tth 时间步.
- 例如: x ⟨ t ⟩ x^{\langle t \rangle} x⟨t⟩ 为输入 x 在 t t h t^{th} tth 时间步. x ( i ) ⟨ t ⟩ x^{(i)\langle t \rangle} x(i)⟨t⟩ 是样本 i i i 在 t t h t^{th} tth 时间步的输入.
-
下标 i i i 表示一个向量的 i t h i^{th} ith 输入.
- 例如: a i [ l ] a^{[l]}_i ai[l] 表示在层 l l l 的 i t h i^{th} ith 激活输入.
1、基本循环神经网络的前向传播
循环神经网络的前向传播示意图如图
1
1
1
图中每个
R
N
N
−
c
e
l
l
RNN-cell
RNN−cell 模块计算方式如下:
说明:
- 使用 t a n h tanh tanh 激活函数计算激活状态: a ⟨ t ⟩ = tanh ( W a a a ⟨ t − 1 ⟩ + W a x x ⟨ t ⟩ + b a ) a^{\langle t \rangle} = \tanh(W_{aa} a^{\langle t-1 \rangle} + W_{ax} x^{\langle t \rangle} + b_a) a⟨t⟩=tanh(Waaa⟨t−1⟩+Waxx⟨t⟩+ba).
- 使用新的激活状态 a ⟨ t ⟩ a^{\langle t \rangle} a⟨t⟩ 计算预测 y ^ ⟨ t ⟩ = s o f t m a x ( W y a a ⟨ t ⟩ + b y ) \hat{y}^{\langle t \rangle} = softmax(W_{ya} a^{\langle t \rangle} + b_y) y^⟨t⟩=softmax(Wyaa⟨t⟩+by).
- 存储 ( a ⟨ t ⟩ , a ⟨ t − 1 ⟩ , x ⟨ t ⟩ , p a r a m e t e r s ) (a^{\langle t \rangle}, a^{\langle t-1 \rangle}, x^{\langle t \rangle}, parameters) (a⟨t⟩,a⟨t−1⟩,x⟨t⟩,parameters).
- 返回 a ⟨ t ⟩ a^{\langle t \rangle} a⟨t⟩ , y ⟨ t ⟩ y^{\langle t \rangle} y⟨t⟩ 及缓存.
将 m m m 个样本向量化,因此 x ⟨ t ⟩ x^{\langle t \rangle} x⟨t⟩ 维度为 ( n x , m ) (n_x,m) (nx,m), a ⟨ t ⟩ a^{\langle t \rangle} a⟨t⟩ 维度为 ( n a , m ) (n_a,m) (na,m).
# RNN 前向传播计算单元
def rnn_cell_forward(xt, a_prev, parameters):
"""
实现图 2 描述的 RNN 单元
变量:
xt -- "t" 时间步输入数据, numpy 数组,形状为 (n_x, m).
a_prev -- 时间步 "t-1" 的状态, numpy 数组,形状为 (n_a, m)
参数 -- python 字典包含:
Wax:乘以输入的权重矩阵, numpy 数组形状为 (n_a, n_x)
Waa:乘以隐藏状态的权重矩阵, numpy 数组形状为 (n_a, n_a)
Wya:表征隐藏状态与输出关系的权重矩阵, numpy 数组形状为 (n_y, n_a)
ba:偏移量, numpy 数组形状为 (n_a, 1)
by:隐藏状态和输出关系的偏移量, numpy 数组形状为 (n_y, 1)
返回:
a_next:下一个激活状态, 形状为 (n_a, m)
yt_pred:时间步 "t" 的预测, numpy 数组形状为 (n_y, m)
cache:反向传播所需值的元组, 包含 (a_next, a_prev, xt, 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
可以将 RNN 看作之前建立的单元的重复. 如果输入数据序列超过 10 时间步,需要复制 RNN 单元 10 次。每个单元将之前单元的激活状态 ( a ⟨ t − 1 ⟩ a^{\langle t-1 \rangle} a⟨t−1⟩) 和当前时间步的输入数据 ( x ⟨ t ⟩ x^{\langle t \rangle} x⟨t⟩) 作为输入。输出一个当前时间步的激活状态 ( a ⟨ t ⟩ a^{\langle t \rangle} a⟨t⟩) 和一个预测 ( y ⟨ t ⟩ y^{\langle t \rangle} y⟨t⟩)。
说明:
- 创建零向量 ( a a a) 用来存储 RNN 计算的所有激活状态。
- 初始化 “next” 激活状态为 a 0 a_0 a0 (初始激活状态).
- 循环时间步, 索引值为
t
t
t :
- 更新 “next” 激活状态以及
rnn_step_forward
运行的缓存 - 将 “next” 激活状态存储在 a a a ( t t h t^{th} tth 位置)
- 存储预测 y
- 将缓存添加到缓存列表
- 更新 “next” 激活状态以及
- 返回
a
a
a,
y
y
y 以及缓存
Figure 3: Basic RNN
def rnn_forward(x, a0, parameters):
"""
实现 Figure (1) 所述 RNN 前向传播.
变量:
x:每个时间步的输入数据,形状为 (n_x, m, T_x).
a0:初始激活状态, 形状为 (n_a, m)
参数 -- python 字典包含:
Waa:乘以隐藏状态的权重矩阵, numpy 数组形状为 (n_a, n_a)
Wax:乘以输入的权重矩阵, numpy 数组形状为 (n_a, n_x)
Wya:表征隐藏状态与输出关系的权重矩阵, numpy 数组形状为 (n_y, n_a)
ba:偏移量, numpy 数组形状为 (n_a, 1)
by:隐藏状态和输出关系的偏移量, numpy 数组形状为 (n_y, 1)
返回:
a:每个时间步的激活状态, 形状为 (n_a, m, T_x)
y_pred:每个时间步的预测, numpy 数组形状为 (n_y, m, T_x)
caches:反向传播所需值的元组, 包含 (list of caches, x)
"""
# 初始化缓存 "caches"
caches = []
# 从 x 和 Wy 形状中检索尺寸
n_x, m, T_x = x.shape
n_y, n_a = parameters["Wya"].shape
# 零初始化 "a" 和 "y"
a = np.zeros([n_a, m, T_x])
y_pred = np.zeros([n_y, m, T_x])
# 初始化 a_next
a_next = a0
# 循环
for t in range(T_x):
# 更新下一激活状态,计算预测获取缓存
a_next, yt_pred, cache = rnn_cell_forward(x[:, :, t], a_next, parameters)
# 将新的 "next" 激活状态保存在 a 里面
a[:,:,t] = a_next
# 保存预测 y
y_pred[:,:,t] = yt_pred
# 将 "cache" 添加到 "caches"
caches.append(cache)
# 缓存反向传播需要的量
caches = (caches, x)
return a, y_pred, caches
2、Long Short-Term Memory (LSTM) 网络
下图为 LSTM 单元.
和 RNN 例子相似, 首先实现 LSTM 单时间步单元. 然后,从 for 循环内部反复调用它,实现 T x T_x Tx 时间步长处理。
遗忘门
假设阅读一段文本中的单词,并希望使用 LSTM 来跟踪语法结构,例如主语是单数还是复数。如果主语从单数变成了复数,我们需要找到一种方法来摆脱我们之前存储的单个/多个状态的记忆值。在 LSTM,遗忘门实现:
(1) Γ f ⟨ t ⟩ = σ ( W f [ a ⟨ t − 1 ⟩ , x ⟨ t ⟩ ] + b f ) \Gamma_f^{\langle t \rangle} = \sigma(W_f[a^{\langle t-1 \rangle}, x^{\langle t \rangle}] + b_f)\tag{1} Γf⟨t⟩=σ(Wf[a⟨t−1⟩,x⟨t⟩]+bf)(1)
这里, W f W_f Wf 是控制遗忘门的权重。将 [ a ⟨ t − 1 ⟩ , x ⟨ t ⟩ ] [a^{\langle t-1 \rangle}, x^{\langle t \rangle}] [a⟨t−1⟩,x⟨t⟩] 连接,并且用 W f W_f Wf 乘以它。以上等式产生值在 0 和 1 之间的向量 Γ f ⟨ t ⟩ \Gamma_f^{\langle t \rangle} Γf⟨t⟩。遗忘门向量与之前单元状态 c ⟨ t − 1 ⟩ c^{\langle t-1 \rangle} c⟨t−1⟩ 进行基于元素的相乘。所以如果 Γ f ⟨ t ⟩ \Gamma_f^{\langle t \rangle} Γf⟨t⟩ 的一个值为 0 (或接近 0) 意味着 LSTM 应该移除相应组件 c ⟨ t − 1 ⟩ c^{\langle t-1 \rangle} c⟨t−1⟩ 这一块信息 (例如,单数主语)。如果值接近 1, 则保留信息。
更新门
一旦我们遗忘正在讨论的主语是单数,我们需要找到一种方法来更新它,以反映新的主语现在是复数。以下是更新门的公式:
(2) Γ u ⟨ t ⟩ = σ ( W u [ a ⟨ t − 1 ⟩ , x { t } ] + b u ) \Gamma_u^{\langle t \rangle} = \sigma(W_u[a^{\langle t-1 \rangle}, x^{\{t\}}] + b_u)\tag{2} Γu⟨t⟩=σ(Wu[a⟨t−1⟩,x{t}]+bu)(2)
和遗忘门相似,这里 Γ u ⟨ t ⟩ \Gamma_u^{\langle t \rangle} Γu⟨t⟩ 也是值在 0 到 1 之间的向量。为了计算 c ⟨ t ⟩ c^{\langle t \rangle} c⟨t⟩,将它和 c ~ ⟨ t ⟩ \tilde{c}^{\langle t \rangle} c~⟨t⟩ 对应元素相乘。
更新单元
为了更新新的主语,我们需要创建一个新的数字向量,我们可以将它添加到之前的单元状态中。使用的等式是:
(3) c ~ ⟨ t ⟩ = tanh ( W c [ a ⟨ t − 1 ⟩ , x ⟨ t ⟩ ] + b c ) \tilde{c}^{\langle t \rangle} = \tanh(W_c[a^{\langle t-1 \rangle}, x^{\langle t \rangle}] + b_c)\tag{3} c~⟨t⟩=tanh(Wc[a⟨t−1⟩,x⟨t⟩]+bc)(3)
最后新的单元状态为:
(4) c ⟨ t ⟩ = Γ f ⟨ t ⟩ ∗ c ⟨ t − 1 ⟩ + Γ u ⟨ t ⟩ ∗ c ~ ⟨ t ⟩ c^{\langle t \rangle} = \Gamma_f^{\langle t \rangle}* c^{\langle t-1 \rangle} + \Gamma_u^{\langle t \rangle} *\tilde{c}^{\langle t \rangle} \tag{4} c⟨t⟩=Γf⟨t⟩∗c⟨t−1⟩+Γu⟨t⟩∗c~⟨t⟩(4)
输出门
决定使用哪个输出,使用以下两个表达式:
(5)
Γ
o
⟨
t
⟩
=
σ
(
W
o
[
a
⟨
t
−
1
⟩
,
x
⟨
t
⟩
]
+
b
o
)
\Gamma_o^{\langle t \rangle}= \sigma(W_o[a^{\langle t-1 \rangle}, x^{\langle t \rangle}] + b_o)\tag{5}
Γo⟨t⟩=σ(Wo[a⟨t−1⟩,x⟨t⟩]+bo)(5)
(6)
a
⟨
t
⟩
=
Γ
o
⟨
t
⟩
∗
tanh
(
c
⟨
t
⟩
)
a^{\langle t \rangle} = \Gamma_o^{\langle t \rangle}* \tanh(c^{\langle t \rangle})\tag{6}
a⟨t⟩=Γo⟨t⟩∗tanh(c⟨t⟩)(6)
等式 5 中决定使用 sigmoid 函数的输出,等式 6 将其乘以先前状态的 tanh 。
# lstm_cell_forward 函数
def lstm_cell_forward(xt, a_prev, c_prev, parameters):
"""
实现 LSTM-cell 计算
变量:
xt:时间步 "t" 的输入数据, numpy 数组形状为 (n_x, m).
a_prev:时间步 "t-1" 的激活状态, numpy 数组形状为 (n_a, m)
c_prev:时间步 "t-1" 的记忆状态, numpy 数组形状 (n_a, m)
参数 -- python 字典包括:
Wf:遗忘门权重矩阵 numpy 数组形状为 (n_a, n_a + n_x)
bf :遗忘门偏移量, numpy 数组形状为 (n_a, 1)
Wi:保留门的权重矩阵, numpy 数组形状为 (n_a, n_a + n_x)
bi:保留门偏移量, numpy 数组形状为 (n_a, 1)
Wc:第一个 "tanh" 权重矩阵, numpy 数组形状为 (n_a, n_a + n_x)
bc:第一个 "tanh" 偏移量, numpy 数组形状为 (n_a, 1)
Wo:关注门的权重矩阵, numpy 数组形状为 (n_a, n_a + n_x)
bo:关注门偏移量为, numpy 数组形状为 (n_a, 1)
Wy:激活状态到输出的权重矩阵, numpy 数组形状为 (n_y, n_a)
by:激活状态到输出的偏移量, numpy 数组形状为 (n_y, 1)
返回:
a_next -- 下一激活状态,形状为 (n_a, m)
c_next -- 下一记忆状态,形状为 (n_a, m)
yt_pred -- 时间步 "t" 的预测, numpy 数组形状为 (n_y, m)
cache -- 反向传播所需值的元组, 包含 (a_next, c_next, a_prev, c_prev, xt, parameters)
注意:ft/it/ot 代表 遗忘/更新/输出门, cct 代表候选值, c 代表记忆值
"""
# 参数
Wf = parameters["Wf"]
bf = parameters["bf"]
Wi = parameters["Wi"]
bi = parameters["bi"]
Wc = parameters["Wc"]
bc = parameters["bc"]
Wo = parameters["Wo"]
bo = parameters["bo"]
Wy = parameters["Wy"]
by = parameters["by"]
# 从 xt 和 Wy 的形状获取维度
n_x, m = xt.shape
n_y, n_a = Wy.shape
# 连接 a_prev 和 xt
concat = np.zeros([n_a + n_x, m])
concat[: n_a, :] = a_prev
concat[n_a :, :] = xt
# 计算 ft, it, cct, c_next, ot, a_next
ft = sigmoid(np.dot(Wf, concat) + bf)
it = sigmoid(np.dot(Wi, concat) + bi)
cct = np.tanh(np.dot(Wc, concat) + bc)
c_next = ft * c_prev + it * cct
ot = sigmoid(np.dot(Wo, concat) + bo)
a_next = ot * np.tanh(c_next)
# 计算 LSTM 单元预测
yt_pred = softmax(np.dot(Wy, a_next) + by)
# 缓存反向传播所需值
cache = (a_next, c_next, a_prev, c_prev, ft, it, cct, ot, xt, parameters)
return a_next, c_next, yt_pred, cache
LSTM 前向传播
# lstm_forward 函数
def lstm_forward(x, a0, parameters):
# 初始化 "caches"
caches = []
n_x, m, T_x = x.shape
n_y, n_a = parameters['Wy'].shape
# 初始化 "a", "c" 和 "y"
a = np.zeros([n_a, m, T_x])
c = np.zeros([n_a, m, T_x])
y = np.zeros([n_y, m, T_x])
# 初始化 a_next 和 c_next
a_next = a0
c_next = np.zeros([n_a, m])
# 循环
for t in range(T_x):
a_next, c_next, yt, cache = lstm_cell_forward(x[:, :, t], a_next, c_next, parameters)
a[:,:,t] = a_next
y[:,:,t] = yt
c[:,:,t] = c_next
caches.append(cache)
caches = (caches, x)
return a, y, c, caches
3、反向传播
3.1、基本循环神经网络的反向传播
反向传播过程如图所示
def rnn_cell_backward(da_next, cache):
(a_next, a_prev, xt, parameters) = cache
Wax = parameters["Wax"]
Waa = parameters["Waa"]
Wya = parameters["Wya"]
ba = parameters["ba"]
by = parameters["by"]
# 计算 tanh 相对于 a_next 的梯度
dtanh = (1-a_next * a_next) * da_next
# 计算 loss 对 Wax 的梯度
dxt = np.dot(Wax.T,dtanh)
dWax = np.dot(dtanh, xt.T)
# 计算 Waa 梯度
da_prev = np.dot(Waa.T,dtanh)
dWaa = np.dot(dtanh, a_prev.T)
# 计算对 b 的梯度
dba = np.sum(dtanh, keepdims=True, axis=-1)
# 将梯度存储在字典中
gradients = {"dxt": dxt, "da_prev": da_prev, "dWax": dWax, "dWaa": dWaa, "dba": dba}
return gradients
def rnn_backward(da, caches):
"""
RNN 反向传播实现
"""
(caches, x) = caches
(a1, a0, x1, parameters) = caches[0]
n_a, m, T_x = da.shape
n_x, m = x1.shape
# 初始化梯度
dx = np.zeros([n_x, m, T_x])
dWax = np.zeros([n_a, n_x])
dWaa = np.zeros([n_a, n_a])
dba = np.zeros([n_a, 1])
da0 = np.zeros([n_a, m])
da_prevt = np.zeros([n_a, m])
# 循环实现
for t in reversed(range(T_x)):
# 计算时间步 t 的梯度
gradients = rnn_cell_backward(da[:, :, t] + da_prevt, caches[t])
dxt, da_prevt, dWaxt, dWaat, dbat = gradients["dxt"], gradients["da_prev"], gradients["dWax"], gradients["dWaa"], gradients["dba"]
# 将时间步 t 的梯度添加到全局梯度中
dx[:, :, t] = dxt
dWax += dWaxt
dWaa += dWaat
dba += dbat
da0 = da_prevt
# 存储梯度
gradients = {"dx": dx, "da0": da0, "dWax": dWax, "dWaa": dWaa,"dba": dba}
return gradients
3.2、LSTM 反向传播
门导数
(7) d Γ o ⟨ t ⟩ = d a n e x t ∗ tanh ( c n e x t ) ∗ Γ o ⟨ t ⟩ ∗ ( 1 − Γ o ⟨ t ⟩ ) d \Gamma_o^{\langle t \rangle} = da_{next}*\tanh(c_{next}) * \Gamma_o^{\langle t \rangle}*(1-\Gamma_o^{\langle t \rangle})\tag{7} dΓo⟨t⟩=danext∗tanh(cnext)∗Γo⟨t⟩∗(1−Γo⟨t⟩)(7)
(8) d c ~ ⟨ t ⟩ = d c n e x t ∗ Γ i ⟨ t ⟩ + Γ o ⟨ t ⟩ ( 1 − tanh ( c n e x t ) 2 ) ∗ i t ∗ d a n e x t ∗ c ~ ⟨ t ⟩ ∗ ( 1 − tanh ( c ~ ) 2 ) d\tilde c^{\langle t \rangle} = dc_{next}*\Gamma_i^{\langle t \rangle}+ \Gamma_o^{\langle t \rangle} (1-\tanh(c_{next})^2) * i_t * da_{next} * \tilde c^{\langle t \rangle} * (1-\tanh(\tilde c)^2) \tag{8} dc~⟨t⟩=dcnext∗Γi⟨t⟩+Γo⟨t⟩(1−tanh(cnext)2)∗it∗danext∗c~⟨t⟩∗(1−tanh(c~)2)(8)
(9) d Γ u ⟨ t ⟩ = d c n e x t ∗ c ~ ⟨ t ⟩ + Γ o ⟨ t ⟩ ( 1 − tanh ( c n e x t ) 2 ) ∗ c ~ ⟨ t ⟩ ∗ d a n e x t ∗ Γ u ⟨ t ⟩ ∗ ( 1 − Γ u ⟨ t ⟩ ) d\Gamma_u^{\langle t \rangle} = dc_{next}*\tilde c^{\langle t \rangle} + \Gamma_o^{\langle t \rangle} (1-\tanh(c_{next})^2) * \tilde c^{\langle t \rangle} * da_{next}*\Gamma_u^{\langle t \rangle}*(1-\Gamma_u^{\langle t \rangle})\tag{9} dΓu⟨t⟩=dcnext∗c~⟨t⟩+Γo⟨t⟩(1−tanh(cnext)2)∗c~⟨t⟩∗danext∗Γu⟨t⟩∗(1−Γu⟨t⟩)(9)
(10) d Γ f ⟨ t ⟩ = d c n e x t ∗ c ~ p r e v + Γ o ⟨ t ⟩ ( 1 − tanh ( c n e x t ) 2 ) ∗ c p r e v ∗ d a n e x t ∗ Γ f ⟨ t ⟩ ∗ ( 1 − Γ f ⟨ t ⟩ ) d\Gamma_f^{\langle t \rangle} = dc_{next}*\tilde c_{prev} + \Gamma_o^{\langle t \rangle} (1-\tanh(c_{next})^2) * c_{prev} * da_{next}*\Gamma_f^{\langle t \rangle}*(1-\Gamma_f^{\langle t \rangle})\tag{10} dΓf⟨t⟩=dcnext∗c~prev+Γo⟨t⟩(1−tanh(cnext)2)∗cprev∗danext∗Γf⟨t⟩∗(1−Γf⟨t⟩)(10)
参数导数
(11)
d
W
f
=
d
Γ
f
⟨
t
⟩
∗
(
a
p
r
e
v
x
t
)
T
dW_f = d\Gamma_f^{\langle t \rangle} * \begin{pmatrix} a_{prev} \\ x_t\end{pmatrix}^T \tag{11}
dWf=dΓf⟨t⟩∗(aprevxt)T(11)
(12)
d
W
u
=
d
Γ
u
⟨
t
⟩
∗
(
a
p
r
e
v
x
t
)
T
dW_u = d\Gamma_u^{\langle t \rangle} * \begin{pmatrix} a_{prev} \\ x_t\end{pmatrix}^T \tag{12}
dWu=dΓu⟨t⟩∗(aprevxt)T(12)
(13)
d
W
c
=
d
c
~
⟨
t
⟩
∗
(
a
p
r
e
v
x
t
)
T
dW_c = d\tilde c^{\langle t \rangle} * \begin{pmatrix} a_{prev} \\ x_t\end{pmatrix}^T \tag{13}
dWc=dc~⟨t⟩∗(aprevxt)T(13)
(14)
d
W
o
=
d
Γ
o
⟨
t
⟩
∗
(
a
p
r
e
v
x
t
)
T
dW_o = d\Gamma_o^{\langle t \rangle} * \begin{pmatrix} a_{prev} \\ x_t\end{pmatrix}^T \tag{14}
dWo=dΓo⟨t⟩∗(aprevxt)T(14)
(15) d a p r e v = W f T ∗ d Γ f ⟨ t ⟩ + W u T ∗ d Γ u ⟨ t ⟩ + W c T ∗ d c ~ ⟨ t ⟩ + W o T ∗ d Γ o ⟨ t ⟩ da_{prev} = W_f^T*d\Gamma_f^{\langle t \rangle} + W_u^T * d\Gamma_u^{\langle t \rangle}+ W_c^T * d\tilde c^{\langle t \rangle} + W_o^T * d\Gamma_o^{\langle t \rangle} \tag{15} daprev=WfT∗dΓf⟨t⟩+WuT∗dΓu⟨t⟩+WcT∗dc~⟨t⟩+WoT∗dΓo⟨t⟩(15)
(16) d c p r e v = d c n e x t Γ f ⟨ t ⟩ + Γ o ⟨ t ⟩ ∗ ( 1 − tanh ( c n e x t ) 2 ) ∗ Γ f ⟨ t ⟩ ∗ d a n e x t dc_{prev} = dc_{next}\Gamma_f^{\langle t \rangle} + \Gamma_o^{\langle t \rangle} * (1- \tanh(c_{next})^2)*\Gamma_f^{\langle t \rangle}*da_{next} \tag{16} dcprev=dcnextΓf⟨t⟩+Γo⟨t⟩∗(1−tanh(cnext)2)∗Γf⟨t⟩∗danext(16)
(17) d x ⟨ t ⟩ = W f T ∗ d Γ f ⟨ t ⟩ + W u T ∗ d Γ u ⟨ t ⟩ + W c T ∗ d c ~ t + W o T ∗ d Γ o ⟨ t ⟩ dx^{\langle t \rangle} = W_f^T*d\Gamma_f^{\langle t \rangle} + W_u^T * d\Gamma_u^{\langle t \rangle}+ W_c^T * d\tilde c_t + W_o^T * d\Gamma_o^{\langle t \rangle}\tag{17} dx⟨t⟩=WfT∗dΓf⟨t⟩+WuT∗dΓu⟨t⟩+WcT∗dc~t+WoT∗dΓo⟨t⟩(17)
def lstm_cell_backward(da_next, dc_next, cache):
"""
实现 LSTM 单元的方向传播
"""
(a_next, c_next, a_prev, c_prev, ft, it, cct, ot, xt, parameters) = cache
n_x, m = xt.shape
n_a, m = a_next.shape
dot = da_next * np.tanh(c_next) * ot * (1 - ot)
dcct = (dc_next * it + ot * (1 - np.square(np.tanh(c_next))) * it * da_next) * (1 - np.square(cct))
dit = (dc_next * cct + ot * (1 - np.square(np.tanh(c_next))) * cct * da_next) * it * (1 - it)
dft = (dc_next * c_prev + ot * (1 - np.square(np.tanh(c_next))) * c_prev * da_next) * ft * (1 - ft)
concat = np.concatenate((a_prev, xt), axis=0).T
dWf = np.dot(dft, concat)
dWi = np.dot(dit, concat)
dWc = np.dot(dcct, concat)
dWo = np.dot(dot, concat)
dbf = np.sum(dft,axis=1,keepdims=True)
dbi = np.sum(dit,axis=1,keepdims=True)
dbc = np.sum(dcct,axis=1,keepdims=True)
dbo = np.sum(dot,axis=1,keepdims=True)
da_prev = np.dot(parameters["Wf"][:, :n_a].T, dft) + np.dot(parameters["Wc"][:, :n_a].T, dcct) + np.dot(parameters["Wi"][:, :n_a].T, dit) + np.dot(parameters["Wo"][:, :n_a].T, dot)
dc_prev = dc_next*ft+ot*(1-np.square(np.tanh(c_next)))*ft*da_next
dxt = np.dot(parameters["Wf"][:, n_a:].T, dft) + np.dot(parameters["Wc"][:, n_a:].T, dcct) + np.dot(parameters["Wi"][:, n_a:].T, dit) + np.dot(parameters["Wo"][:, n_a:].T, dot)
gradients = {"dxt": dxt, "da_prev": da_prev, "dc_prev": dc_prev, "dWf": dWf,"dbf": dbf, "dWi": dWi,"dbi": dbi,
"dWc": dWc,"dbc": dbc, "dWo": dWo,"dbo": dbo}
return gradients
def lstm_backward(da, caches):
(caches, x) = caches
(a1, c1, a0, c0, f1, i1, cc1, o1, x1, parameters) = caches[0]
n_a, m, T_x = da.shape
n_x, m = x1.shape
dx = np.zeros([n_x, m, T_x])
da0 = np.zeros([n_a, m])
da_prevt = np.zeros([n_a, m])
dc_prevt = np.zeros([n_a, m])
dWf = np.zeros([n_a, n_a + n_x])
dWi = np.zeros([n_a, n_a + n_x])
dWc = np.zeros([n_a, n_a + n_x])
dWo = np.zeros([n_a, n_a + n_x])
dbf = np.zeros([n_a, 1])
dbi = np.zeros([n_a, 1])
dbc = np.zeros([n_a, 1])
dbo = np.zeros([n_a, 1])
for t in reversed(range(T_x)):
gradients = lstm_cell_backward(da[:,:,t],dc_prevt,caches[t])
dx[:,:,t] = gradients['dxt']
dWf = dWf+gradients['dWf']
dWi = dWi+gradients['dWi']
dWc = dWc+gradients['dWc']
dWo = dWo+gradients['dWo']
dbf = dbf+gradients['dbf']
dbi = dbi+gradients['dbi']
dbc = dbc+gradients['dbc']
dbo = dbo+gradients['dbo']
da0 = gradients['da_prev']
gradients = {"dx": dx, "da0": da0, "dWf": dWf,"dbf": dbf, "dWi": dWi,"dbi": dbi,
"dWc": dWc,"dbc": dbc, "dWo": dWo,"dbo": dbo}
return gradients