深度学习:RNN基础

符号说明

  • 上标 [ 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} xt 为输入 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

在这里插入图片描述

Figure 1: Basic RNN model

图中每个 R N N − c e l l RNN-cell RNNcell 模块计算方式如下:
在这里插入图片描述

Figure 2: 基本 RNN 单元. 输入为 x ⟨ t ⟩ x^{\langle t \rangle} xt (当前输入) 和 a ⟨ t − 1 ⟩ a^{\langle t - 1\rangle} at1 (之前时间步包含的信息),输出为 a ⟨ t ⟩ a^{\langle t \rangle} at,它将被输入到下一 RNN 单元,并且用于预测 y ⟨ t ⟩ y^{\langle t \rangle} yt

说明

  1. 使用 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) at=tanh(Waaat1+Waxxt+ba).
  2. 使用新的激活状态 a ⟨ t ⟩ a^{\langle t \rangle} at 计算预测 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(Wyaat+by).
  3. 存储 ( 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) (at,at1,xt,parameters).
  4. 返回 a ⟨ t ⟩ a^{\langle t \rangle} at , y ⟨ t ⟩ y^{\langle t \rangle} yt 及缓存.

m m m 个样本向量化,因此 x ⟨ t ⟩ x^{\langle t \rangle} xt 维度为 ( n x , m ) (n_x,m) (nx,m) a ⟨ t ⟩ a^{\langle t \rangle} at 维度为 ( 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} at1) 和当前时间步的输入数据 ( x ⟨ t ⟩ x^{\langle t \rangle} xt) 作为输入。输出一个当前时间步的激活状态 ( a ⟨ t ⟩ a^{\langle t \rangle} at) 和一个预测 ( y ⟨ t ⟩ y^{\langle t \rangle} yt)。

说明:

  1. 创建零向量 ( a a a) 用来存储 RNN 计算的所有激活状态。
  2. 初始化 “next” 激活状态为 a 0 a_0 a0 (初始激活状态).
  3. 循环时间步, 索引值为 t t t :
    • 更新 “next” 激活状态以及 rnn_step_forward 运行的缓存
    • 将 “next” 激活状态存储在 a a a ( t t h t^{th} tth 位置)
    • 存储预测 y
    • 将缓存添加到缓存列表
  4. 返回 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 单元.

在这里插入图片描述

Figure 4: 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} Γft=σ(Wf[at1,xt]+bf)(1)

这里, W f W_f Wf 是控制遗忘门的权重。将 [ a ⟨ t − 1 ⟩ , x ⟨ t ⟩ ] [a^{\langle t-1 \rangle}, x^{\langle t \rangle}] [at1,xt] 连接,并且用 W f W_f Wf 乘以它。以上等式产生值在 0 和 1 之间的向量 Γ f ⟨ t ⟩ \Gamma_f^{\langle t \rangle} Γft。遗忘门向量与之前单元状态 c ⟨ t − 1 ⟩ c^{\langle t-1 \rangle} ct1 进行基于元素的相乘。所以如果 Γ f ⟨ t ⟩ \Gamma_f^{\langle t \rangle} Γft 的一个值为 0 (或接近 0) 意味着 LSTM 应该移除相应组件 c ⟨ t − 1 ⟩ c^{\langle t-1 \rangle} ct1 这一块信息 (例如,单数主语)。如果值接近 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} Γut=σ(Wu[at1,x{t}]+bu)(2)

和遗忘门相似,这里 Γ u ⟨ t ⟩ \Gamma_u^{\langle t \rangle} Γut 也是值在 0 到 1 之间的向量。为了计算 c ⟨ t ⟩ c^{\langle t \rangle} ct,将它和 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[at1,xt]+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} ct=Γftct1+Γutc~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} Γot=σ(Wo[at1,xt]+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} at=Γottanh(ct)(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 前向传播
在这里插入图片描述

Figure 5: 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、基本循环神经网络的反向传播

反向传播过程如图所示
在这里插入图片描述

Figure 5: RNN 单元的反向传播。 类似于全连接网络,通过链式法则来计算损失函数梯度 ( ∂ J ∂ W a x , ∂ J ∂ W a a , ∂ J ∂ b ) (\frac{\partial J}{\partial W_{ax}},\frac{\partial J}{\partial W_{aa}},\frac{\partial J}{\partial b}) (WaxJ,WaaJ,bJ) 进而更新参数 ( W a x , W a a , b a ) (W_{ax}, W_{aa}, b_a) (Wax,Waa,ba).

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Γot=danexttanh(cnext)Γot(1Γot)(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Γit+Γot(1tanh(cnext)2)itdanextc~t(1tanh(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Γut=dcnextc~t+Γot(1tanh(cnext)2)c~tdanextΓut(1Γut)(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Γft=dcnextc~prev+Γot(1tanh(cnext)2)cprevdanextΓft(1Γft)(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Γft(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Γut(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Γot(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=WfTdΓft+WuTdΓut+WcTdc~t+WoTdΓot(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Γft+Γot(1tanh(cnext)2)Γftdanext(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} dxt=WfTdΓft+WuTdΓut+WcTdc~t+WoTdΓot(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
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值