各类神经网络学习:(三)RNN 循环神经网络(中集),同步多对多结构的详细解释

发布于:2025-03-24 ⋅ 阅读:(22) ⋅ 点赞:(0)
上一篇 下一篇
RNN(上集) RNN(下集)

同步多对多结构

1)结构详解

①图解:

在这里插入图片描述

②参数含义:

  • x t x_t xt :表示每一个时刻的输入;
  • o t o_t ot :表示每一个时刻的输出;
  • s t s_t st :表示每一个隐藏层的状态输出;
  • 右侧小圆圈代表隐藏层的一个单元;
  • U 、 V 、 W U、V、W UVW 参数共享,即所有的隐藏层都共用这三个参数。

③通用公式:

  • s 0 = 0 s_0=0 s0=0 (实际计算中是 0 0 0 列向量)。
  • s t = g 1 ( U ⋅ x t + W ⋅ s t − 1 + b s ) s_t=g1(U·x_t+W·s_{t-1}+b_s) st=g1(Uxt+Wst1+bs) g 1 ( ) g1() g1() 是激活函数, b s b_s bs 是偏置。
  • o t = g 2 ( V ⋅ s t + b o ) o_t=g2(V·s_t+b_o) ot=g2(Vst+bo) g 2 ( ) g2() g2() 是激活函数, b o b_o bo 是偏置。

通过将公式分解,可以发现: o t o_t ot 的值和前面每个时刻的输入都有关系(展开式形似 累乘 )。

④激活函数的选择

总结:多分类使用 t a n h tanh tanh + s o f t m a x softmax softmax ;单分类使用 t a n h tanh tanh + s i g m o i d sigmoid sigmoid

  • 激活函数 g 1 ( ) g1() g1() 一般选用 t a n h tanh tanh 。不用其他的函数的原因如下:

    • 梯度消失问题(相比于 s i g m o i d sigmoid sigmoid ):

      s i g m o i d sigmoid sigmoid 函数的导数范围是 ( 0 , 0.25 ] (0,0.25] (0,0.25] t a n h tanh tanh 函数的导数是 ( 0 , 1 ] (0,1] (0,1] 。由于 R N N RNN RNN 中会执行很多累乘,小于 1 1 1 的小数累乘会导致梯度越来越接近于 0 0 0 ,从而导致梯度消失现象。 t a n h tanh tanh s i g m o i d sigmoid sigmoid 相比,梯度收敛速度更快并且相对不易出现梯度消失问题。

    • 梯度爆炸问题(相比于 r e l u relu relu ):

      虽然 r e l u relu relu 函数能有效缓解梯度消失,但是由于 r e l u relu relu 的导数不是 0 0 0 就是 1 1 1 ,恒为 1 1 1 的导数容易导致梯度爆炸,尤其是在会执行很多累乘的 R N N RNN RNN 中。

    • 对称问题:

      t a n h tanh tanh 的输出范围为 [ − 1 , 1 ] [−1,1] [1,1] ,这使得它能够将输入值映射到一个对称的区间,有助于梯度的传播和模型的收敛。

  • 激活函数 g 2 ( ) g2() g2()

    • 对于多分类问题,使用 s o f t m a x softmax softmax
    • 对于单分类问题,使用 s i g m o i d sigmoid sigmoid

2)在同步多对多RNN网络中,句子如何作为输入

①首先将句子进行分词

分词就是将一个句子拆分成单词或字符,例如 “我喜欢打篮球” ,会被分词为 “我、喜欢、打、篮球” 。

  • 对于英文,可用 NLTKword_tokenizesent_tokenize 等工具;对于中文,可用 Jieba 等工具。

  • 分词后,通常会过滤掉语料库中出现频率较低的词,以减少词汇表的规模并提高模型训练效率,低频词通常会被替换为 unknown_token

  • 为了帮助模型识别句子的开始和结束,通常会在句首和句尾添加标识符,如开始符 sentence_start 和结束符 sentence_end

②将分词结果映射为向量

分词后的单词或字符需要被映射为向量表示。这通常通过构建一个词汇表,将每个单词或字符映射到一个唯一的索引,然后将这些索引转换为向量。例如,使用 O n e − H o t One-Hot OneHot 编码或嵌入层( E m b e d d i n g L a y e r Embedding Layer EmbeddingLayer)将单词表示为向量( o n e − h o t one-hot onehot 编码上网一搜便知,这里不做额外解释)。

在这里插入图片描述

将词典通过上述方法转换之后,就会得到右边的一个高维、稀疏的向量组( m m m 个词组成的向量组为 m m m 行, m m m 列)(稀疏指的是绝大部分元素都是 0 0 0 )。

之前提到的开始和结束标志也会在此向量组里,比如用 [1,0,0,...] 表示开始符, [...,0,0,1] 表示结束符。

③将分词后的结果按照时刻依次输入模型

以“我喜欢打篮球”→“我、喜欢、打、篮球”为例

模型的输出后续会给出图解。

在这里插入图片描述


3)模型训练过程中的矩阵运算是怎样的

仅代表模型在 t t t 时刻的矩阵运算

回顾通用公式:

  • s 0 = 0 s_0=0 s0=0 (实际计算中是 0 0 0 列向量)。
  • s t = g 1 ( U ⋅ x t + W ⋅ s t − 1 ) s_t=g1(U·x_t+W·s_{t-1}) st=g1(Uxt+Wst1) 偏置 b s b_s bs 先省略。
  • o t = g 2 ( V ⋅ s t ) o_t=g2(V·s_t) ot=g2(Vst) 偏置 b o b_o bo 先省略。

先确定输出向量的维度: o t o_t ot [ m , 1 ] [m,1] [m,1] (维度和 x t x_t xt 一样), s t s_t st [ n , 1 ] [n,1] [n,1] n n n 可自定义)。公式展开如下:
[ s t 1 : s t n ] = g 1 ( U ⋅ [ x t 1 ┇ x t m ] + W ⋅ [ s t − 1 1 : s t − 1 n ] ) − − − − − − − − − − − − − − − − − − − − − − − [ o t 1 ┇ o t m ] = g 2 ( V ⋅ [ s t 1 : s t n ] ) \large\left[ \begin{matrix} s^1_t\\ :\\ s^n_t\\ \end{matrix} \right]=g1(U·\left[ \begin{matrix} x^1_t\\ ┇\\ x^m_t\\ \end{matrix} \right]+W·\left[ \begin{matrix} s^1_{t-1}\\ :\\ s^n_{t-1}\\ \end{matrix} \right])\\ -----------------------\\ \large\left[ \begin{matrix} o^1_t\\ ┇\\ o^m_t\\ \end{matrix} \right]=g2(V·\left[ \begin{matrix} s^1_t\\ :\\ s^n_t\\ \end{matrix} \right])\\ st1:stn =g1(U xt1xtm +W st11:st1n )−−−−−−−−−−−−−−−−−−−−−−− ot1otm =g2(V st1:stn )
注意其中的 n n n m m m 。参数矩阵 U 、 W 、 V U、W、V UWV 可由此确定维度( U U U [ n , m ] [n,m] [n,m] W W W [ n , n ] [n,n] [n,n] V V V [ m , n ] [m,n] [m,n] )。

不难推出参数 b s b_s bs [ n , 1 ] [n,1] [n,1] b o bo bo [ m , 1 ] [m,1] [m,1]


4)在同步多对多RNN网络中,模型的输出是什么

①以语言建模为例:输出激活函数使用softmax,即多分类

语言建模:输入一个句子并输出句子中每个词在下一个时刻最有可能的词,例如,给定句子 “The cat is on the”,RNN 会预测下一个词可能是 “mat”、“roof” 等,并给出每个词的概率。这个过程可以逐词进行,直到生成完整的句子或序列。

每一个时刻的输出是一条概率向量,表示下一个最可能的词。

概率向量构成:由预测概率组成的向量,长度为 N N N 。形如: [0.00001,...,0.018,...,0.00023,...] 。这些概率都由 s o f t m a x softmax softmax 函数计算得出。

例如下方图解:

在这里插入图片描述

模型的完整输入输出为(输入仍以“我、喜欢、打、篮球”为例):

(输出结果可以是其他的,但是这里图方便,就正好假设输出也是“我、喜欢、打、篮球”。)

在这里插入图片描述

举一个语言建模逐词进行的例子:

以句子 “The cat is on the” 为例,RNN 会逐词预测下一个词,并给出每个词的概率分布。以下是假设的逐词生成过程:

  1. 输入“The”
    RNN 会基于 “The” 预测下一个词,可能的输出概率分布为:
    • cat: 0.8
    • dog: 0.1
    • bird: 0.05
  2. 输入“The cat”
    RNN 会基于 “The cat” 预测下一个词,可能的输出概率分布为:
    • is: 0.7
    • was: 0.2
    • jumped: 0.05
  3. 输入“The cat is”
    RNN 会基于 “The cat is” 预测下一个词,可能的输出概率分布为:
    • on: 0.6
    • under: 0.2
    • sleeping: 0.1
  4. 输入“The cat is on”
    RNN 会基于 “The cat is on” 预测下一个词,可能的输出概率分布为:
    • the: 0.7
    • a: 0.2
    • my: 0.05
  5. 输入“The cat is on the”
    RNN 会基于 “The cat is on the” 预测下一个词,可能的输出概率分布为:
    • mat: 0.5
    • roof: 0.3
    • table: 0.1

最终,RNN 可能会生成完整的句子,例如 “The cat is on the mat” 或 “The cat is on the roof” ,具体取决于概率分布和模型训练数据。

②输出激活函数使用sigmoid,即单分类

(这里就不展开了,随便找 D e e p S e e k DeepSeek DeepSeek 写了个例子):

假设我们有一个 R N N RNN RNN 模型用于预测某个事件是否会在每个时间步发生。模型的输入是一个时间序列数据,输出是一个与输入序列长度相同的二进制序列
0 0 0 表示不会, 1 1 1 表示会)。

具体步骤:

  1. 输入处理:输入序列被逐个时间步输入到 R N N RNN RNN 中。
  2. 隐藏状态更新: R N N RNN RNN 在每个时间步更新其隐藏状态,基于当前输入和前一隐藏状态。
  3. 输出生成:在每个时间步, R N N RNN RNN 的输出层使用 S i g m o i d Sigmoid Sigmoid 激活函数生成一个输出值,表示当前时间步的事件发生概率(概率 ≥ 0.5 ≥0.5 0.5 时判为 1 1 1,否则为 0 0 0 )。
  4. 序列输出:最终,模型输出一个与输入序列长度相同的二进制序列,每个值表示对应时间步的事件是否会发生。

总结:在同步多对多结构的 R N N RNN RNN 中,使用 S i g m o i d Sigmoid Sigmoid 作为输出层的激活函数,可以生成一个二进制序列(一开始是概率序列,经过阈值判别后变成二进制序列),适用于二分类问题或概率预测任务。


5)模型训练中的损失

一般采用交叉熵损失函数。

一整个序列(句子)作为一个输入样本时,其损失为各个时刻词的损失之和。

损失计算公式定义:

  • 时刻 t t t 的损失: E t ( y t , y t ^ ) = − y t ⋅ l o g ( y t ^ ) \large E_t(y_t,\hat{y_t}) =-y_t·log(\hat{y_t}) Et(yt,yt^)=ytlog(yt^)
  • 各时刻损失之和: E ( y , y ^ ) = ∑ t E t ( y t , y t ^ ) = − ∑ t y t ⋅ l o g ( y t ^ ) \large E(y,\hat{y})=\sum_t E_t(y_t,\hat{y_t}) =-\sum_ty_t·log(\hat{y_t}) E(y,y^)=tEt(yt,yt^)=tytlog(yt^)

这里和 C N N CNN CNN 的交叉熵损失函数有所不同,这里的 y t y_t yt 代表时刻 t t t 上正确词的向量, y t ^ \hat{y_t} yt^ 代表预测词的向量。

单个时刻的损失对模型输出 o t o_t ot 的导数为 ∂ J t ∂ o t = o t − y t \large \frac{\partial J_t}{\partial o_t} = o_t - y_t otJt=otyt

单个时刻的损失对隐层状态输出 s t s_t st 的导数为 ∂ J t ∂ s t = V T ∗ ( o t − y t ) \large \frac{\partial J_t}{\partial s_t} = V^T * (o_t - y_t) stJt=VT(otyt)


6)时序反向传播算法(BPTT)

BPTT,Back Propagation Through Time,对 RNN 来说,梯度是沿时间通道反向传播的。
看看过程,理解一下就行,实际写代码不用亲自写,直接模块化调用就行。

再次回顾前向传播通用公式,并带入激活函数:

  • 公式一: s t = t a n h ( z t ) = t a n h ( U ⋅ x t + W ⋅ s t − 1 + b s ) \large s_t=tanh(z_t)=tanh(U·x_t+W·s_{t-1}+b_s) st=tanh(zt)=tanh(Uxt+Wst1+bs)
  • 公式二: o t = s o f t m a x ( V ⋅ s t + b o ) \large o_t=softmax(V·s_t+b_o) ot=softmax(Vst+bo)

令损失为 J J J

①梯度计算要求及规则:

  1. 目标 是:计算损失 J J J 关于参数 U 、 V 、 W 、 b s 、 b o U、V、W、b_s、b_o UVWbsbo 的梯度;

  2. 因为上述五个参数在每个时刻都是共享的,所以每个时刻的梯度都得计算出来,求出所有时刻梯度之和,才可用于参数更新;

    图解如下:

    在这里插入图片描述

  3. 每个时刻的梯度计算 规则:

    • 最后一个时刻:

      • 第一步:依据交叉熵计算公式和公式二计算出: ∂ J t ∂ s t 、 ∂ J t ∂ V ( √ ) 、 ∂ J t ∂ b o ( √ ) \Large \frac{\partial J_t}{\partial s_t}、\frac{\partial J_t}{\partial V}(√)、\frac{\partial J_t}{\partial b_o}(√) stJtVJt()boJt()
      • 第二步:依据公式一可以计算出: ∂ s t ∂ z t 、 ∂ s t ∂ U 、 ∂ s t ∂ W 、 ∂ s t ∂ b s 、 ∂ s t ∂ x t \Large \frac{\partial s_t}{\partial z_t}、\frac{\partial s_t}{\partial U}、\frac{\partial s_t}{\partial W}、\frac{\partial s_t}{\partial b_s}、\frac{\partial s_t}{\partial x_t} ztstUstWstbsstxtst
      • 第三步:依据 链式法则 ,继续求出: ∂ J t ∂ U ( √ ) 、 ∂ J t ∂ W ( √ ) 、 ∂ J t ∂ b s ( √ ) \Large \frac{\partial J_t}{\partial U}(√)、\frac{\partial J_t}{\partial W}(√)、\frac{\partial J_t}{\partial b_s}(√) UJt()WJt()bsJt()
    • 非最后一个时刻:

      • 第一步:依据交叉熵计算公式、公式二计算出: ∂ J t ∂ s t 、 ∂ J t ∂ V 、 ∂ J t ∂ b o \Large \frac{\partial J_t}{\partial s_t}、\frac{\partial J_t}{\partial V}、\frac{\partial J_t}{\partial b_o} stJtVJtboJt

      • 第二步( 不同点 ):在下一个时刻 t + 1 t+1 t+1 (反向传播时,其实应称为上一个时刻)时,要求出 ∂ s t + 1 ∂ s t \Large \frac{\partial s_{t+1}}{\partial s_t} stst+1 ,进一步求出 ∂ J t + 1 ∂ s t \Large \frac{\partial J_{t+1}}{\partial s_{t}} stJt+1 ,则此刻的 ∂ J t ∂ s t \Large \frac{\partial J_t}{\partial s_t} stJt 应再加上一部分: ∂ J t ∂ s t ( ☆ ) = ∂ J t ∂ s t + ∂ J t + 1 ∂ s t \Large \frac{\partial J_t}{\partial s_t}(☆)=\frac{\partial J_t}{\partial s_t}+\frac{\partial J_{t+1}}{\partial s_{t}} stJt()=stJt+stJt+1

        即:当前时刻损失对于隐层状态输出值 s t s_t st 的梯度要再加上下一时刻损失对 s t s_{t} st 的导数。

      • 第三步:照搬 “最后一个时刻” 的第二步;

      • 第四步:照搬 “最后一个时刻” 的第三步。

单个时刻的反向传播可以借鉴此视频中的公式(引自《85.09_手写RNN案例:单个cell的反向传播_哔哩哔哩_bilibili》),内部公式推导不需要记住。

↑ ↑ 但是其中的 x x x (不是指 x t x^t xt )要改写成 z t z_t zt ∂ x \partial x x (不是指 ∂ x t \partial x^t xt )要改写成 ∂ z t \partial z_t zt ,右侧第六行的 ∂ W \partial W W 改成 ∂ b s \partial bs bs

补充(其中 y t y_t yt 为预测真实值):
∂ J t ∂ o t = o t − y t ∂ o t ∂ s t = V T ∂ J t ∂ s t = V T ∗ ( o t − y t ) ∂ J t ∂ V = ∑ t = 1 T ( o t − y t ) ∗ s t T ∂ J t ∂ b o = ∑ t = 1 T ( o t − y t ) \begin{align*} \frac{\partial J_t}{\partial o_t} &= o_t - y_t\\ \frac{\partial o_t}{\partial s_t} &= V^T\\ \frac{\partial J_t}{\partial s_t} &= V^T * (o_t - y_t)\\ \frac{\partial J_t}{\partial V} &= \sum^T_{t=1} (o_t - y_t) * s_t^T\\ \frac{\partial J_t}{\partial b_o} &= \sum^T_{t=1} (o_t - y_t) \end{align*} otJtstotstJtVJtboJt=otyt=VT=VT(otyt)=t=1T(otyt)stT=t=1T(otyt)

②参数更新

当每个时刻的参数梯度都计算出来,求和得出最终的梯度 ∂ J ∂ U 、 ∂ J ∂ W 、 ∂ J ∂ b s 、 ∂ J ∂ V 、 ∂ J ∂ b o \Large \frac{\partial J}{\partial U}、\frac{\partial J}{\partial W}、\frac{\partial J}{\partial b_s}、\frac{\partial J}{\partial V}、\frac{\partial J}{\partial b_o} UJWJbsJVJboJ 时,可按照下方公式进行更新:
U = U − α ⋅ ∂ J ∂ U W = W − α ⋅ ∂ J ∂ W ⋅ ⋅ ⋅ b o = b o − α ⋅ ∂ J ∂ b o U=U-\alpha·\frac{\partial J}{\partial U}\\ W=W-\alpha·\frac{\partial J}{\partial W}\\ ···\\ b_o=b_o-\alpha·\frac{\partial J}{\partial b_o}\\ U=UαUJW=WαWJ⋅⋅⋅bo=boαboJ
其中 α \alpha α 为学习率。


7)相关理解性代码

下面的代码仅供参考,并且实际使用pytorch的时候,都是模块化的,可直接调用的。

这里就仅仅给读者看看,方便理解前向传播和反向传播的代码逻辑。

import numpy as np

# Language modeling: 语言建模(简写为 LMing ), 本质上是多分类任务

# ***************************** 语言建模 *****************************

# softmax自定义函数
def softmax(x):
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum(axis=0)


# 单个时刻的模型前向传播过程
def rnn_LMing_moment_forwad(x_t, s_prev, parameters):
    """
    单个时刻的模型前向传播过程
    :param
        x_t: 当前时刻的分词输入
        s_prev: 上一个时刻的隐层状态输出
        parameters: 字典形式的其他参数,包括 U、W、V、bs、bo
    :return:
        当前隐层状态 s_t,
        输出 o_t,
        缓存参数 cache 元组
    """
    U = parameters['U']
    W = parameters['W']
    V = parameters['V']
    bs = parameters['bs']
    bo = parameters['bo']

    # 计算公式一, 即当前隐层状态s_t
    s_t = np.tanh(np.dot(U, x_t) + np.dot(W, s_prev) + bs)

    # 计算公式二, 即输出o_t
    o_t = softmax(np.dot(V, s_t) + bo)

    # 记录此时刻的一些参数(在反向传播中会用到)
    cache = (s_t, s_prev, x_t, parameters)

    return s_t, o_t, cache


# 整体模型前向传播
def rnn_LMing_forwad(x, s_0, parameters):
    """
    整体模型前向传播过程
    :param
        x: 输入序列(分词矩阵), 尺寸为[m,1,T], T为该序列中分词个数
        s_0: 初始化隐层状态输入, 尺寸为[n,1]
        parameters: 字典形式的其他参数,包括 U、W、V、bs、bo
    :return:
        全部时刻的隐层状态输出 s_total,尺寸为[n,1,T];
        全部时刻的输出 o_total,尺寸为[m,1,T];
        全部缓存参数元组, 并整合进一个列表 caches 中
    """
    m, _, T = x.shape  # 获取分词的向量形式长度、时刻数(分词数)
    n, _ = parameters['U'].shape  # 获取单个时刻隐层状态的向量形式长度

    # 初始化全部隐层状态输出矩阵、全部时刻输出、
    s_total = np.zeros((n, 1, T))
    o_total = np.zeros((m, 1, T))
    caches = []

    s_t = s_0
    for t in range(T):
        s_t, o_t, cache = rnn_LMing_moment_forwad(x[:, :, t], s_t, parameters)  # 生成的 s_t 在下一个循环中就是 s_prev
        s_total[:, :, t] = s_t
        o_total[:, :, t] = o_t
        caches.append(cache)  # 将中间参数元组添加进列表中

    return s_total, o_total, caches


# 单个时刻的反向传播, 其中 ds_t、do_t 需要提前计算传入
def rnn_LMing_moment_backward(ds_t, do_t, cache):
    """
    对单个时刻进行反向传播
    :param
        ds_t: 当前时刻损失对隐层输出结果的导数
        do_t: 当前时刻损失对模型输出结果的导数(do_t = o_t - y_t), y_t 为真实值
        cache: 当前时刻的缓存参数
    :return:
        gradients: 梯度字典
    """
    # 获取缓存
    (s_t, s_prev, x_t, parameters) = cache

    # 获取参数
    U = parameters['U']
    W = parameters['W']
    V = parameters['V']
    bs = parameters['bs']
    bo = parameters['bo']

    dz_t = (1 - s_t ** 2) * ds_t  # 计算 z_t 的梯度
    dx_t = np.dot(U.T, dz_t)  # 计算 x_t 的梯度值
    dUt = np.dot(dz_t, x_t.T)  # 计算 U 的梯度值
    ds_prev = np.dot(W.T, dz_t)  # 计算 s_prev 的梯度值, s_prev 就是 s_t_1, ds_prev的含义是当前损失对前一时刻隐层状态输出的导数
    dWt = np.dot(dz_t, s_prev.T)  # 计算 W 的梯度值
    dbst = np.sum(dz_t, axis=1, keepdims=True)  # 计算 bs 的梯度

    dVt = np.dot(do_t, s_t.T)  # 计算 V 的梯度值
    dbot = np.sum(do_t, axis=1, keepdims=True)  # 计算 bo 的梯度值

    # 将所求梯度存进字典
    gradient = {"dz_t": dz_t, "dx_t": dx_t, "ds_prev": ds_prev, "dUt": dUt, "dWt": dWt, "dbst": dbst, "dVt": dVt,
                "dbot": dbot}

    return gradient


# 整体反向传播, 其中 ds 为所有 ds_t 的集合, do 为所有 do_t 的集合, 两者需要提前计算传入
def rnn_LMing_backward(ds, do, caches):
    """
    对单个时刻进行反向传播
    :param
        ds: 所有时刻损失对隐层输出结果的导数
        do: 所有时刻损失对模型输出结果的导数
        cache: 当前时刻的缓存参数
    :return:
        gradients: 梯度字典
    """

    # 获取第一个时刻的数据, 参数, 输入输出值
    (s1, s0, x_1, parameters) = caches[0]

    # 获取时刻数以及 m 和 n 的值
    n, _, T = ds.shape
    m, _ = x_1.shape

    # 初始化梯度值
    dx = np.zeros((m, 1, T))
    dU = np.zeros((n, m))
    dW = np.zeros((n, n))
    dbs = np.zeros((n, 1))
    dV = np.zeros((m, n))
    dbo = np.zeros((m, 1))
    ds_prev = np.zeros((n, 1))

    # 循环从后往前进行反向传播
    for t in reversed(range(T)):
        # 根据时间 T 的 s 梯度,以及缓存计算当前时刻的反向传播梯度
        gradient = rnn_LMing_moment_backward((ds[:, :, t] + ds_prev), do[:, :, t], caches[t])
        # 获取梯度准备进行更新
        dx_t, ds_prev, dUt, dWt, dbst, dVt, dbot = gradient["dx_t"], gradient["ds_prev"], gradient["dUt"], gradient[
            "dWt"], gradient["dbst"], gradient["dVt"], gradient["dbot"]
        # 进行每次 t 时间上的梯度接过相加, 作为最终更新的梯度
        dx[:, :, t] = dx_t
        dU += dUt
        dW += dWt
        dbs += dbst
        dV += dVt
        dbo += dbot

    ds0 = ds_prev
    gradients = {"dU": dU, "dW": dW, "dbs": dbs, "dV": dV, "dbo": dbo, "dx": dx, "ds0": ds0}

    return gradients


网站公告

今日签到

点亮在社区的每一天
去签到