循环神经网络(二)

简介: 循环神经网络(二)

4.1.2.7 时序反向传播算法(BPTT)(重要)


对于RNN来说有一个时间概念,需要把梯度沿时间通道传播的 BP 算法,所以称为Back Propagation Through Time-BPTT


image.png


我们的目标是计算误差关于参数U、V和W以及两个偏置bx,by的梯度,然后使用梯度下降法学习出好的参数。由于这三组参数是共享的,我们需要将一个训练实例在每时刻的梯度相加。


1、要求:每个时间的梯度都计算出来t=0,t=1,t=2,t=3,t=4,然后加起来的梯度, 为每次W更新的梯度值。


2、求不同参数的导数步骤:


  • 最后一个cell:
  • 计算最后一个时刻交叉熵损失对于s_t的梯度,记忆交叉熵损失对于s^t,V,by的导数
  • 按照图中顺序计算


  • 最后一个前面的cell:


  • 第一步:求出当前层损失对于当前隐层状态输出值 s^{t}st 的梯度 ++ 上一层相对于s^{t}st 的损失
  • 第二步:计算tanh激活函数的导数
  • 第三步:计算Ux_t + Ws_{t-1} + b_{a}Uxt+Wst−1+ba的对于不同参数的导数


image.png


image.png


4.1.2.8 梯度消失与梯度爆炸


由于RNN当中也存在链式求导规则,并且其中序列的长度位置。所以


  • 如果矩阵中有非常小的值,并且经过矩阵相乘N次之后,梯度值快速的以指数形式收缩,较远的时刻梯度变为0。
  • 如果矩阵的值非常大,就会出现梯度爆炸


image.png


4.1.3 RNN 总结



总结使用tanh激活函数。


完整计算流程


image.png



反向传播过程结果


image.png

 

4.1.4 案例:手写一个RNN的前向传播以及反向传播



4.1.4.1 案例演示


4.1.4.2 流程


  • 前向传播过程
  • 单个cell的前向传播
  • 所有cell的前向传播


  • 反向传播过程


4.1.4.3 代码案例


1、前向传播过程


根据前向传播的公式来进行编写:


image.png


def rnn_cell_forward(x_t, s_prev, parameters):
"""
    单个RNN-cell 的前向传播过程
    :param x_t: 单元的输入
    :param s_prev: 上一个单元的输入
    :param parameters: 单元中的参数
    :return: s_next, out_pred, cache
    """
    # 获取参数
    U = parameters["U"]
    W = parameters["W"]
    V = parameters["V"]
    ba = parameters["ba"]
    by = parameters["by"]
    # 计算激活函数
    s_next = np.tanh(np.dot(U, x_t) + np.dot(W, s_prev) + ba)
    # 计算当前cell输出预测结果
    out_pred = softmax(np.dot(V, s_next) + by)
    # 存储当前单元的结果
    cache = (s_next, s_prev, x_t, parameters)
    return s_next, out_pred, cache


测试前向过程:假设创建下面形状的数据进行测试,m=3是词的个数,n=5为自定义数字:


UX + WS + ba = S
[n, m] x [m, 1] +[n, n] x [n, 1] + [n, 1]= [n, 1]
[5, 3] x [3, 1] + [5, 5] x [5, 1] + [5, 1] = [5, 1]
U:(5, 3)
X:(3,1)
W:(5, 5)
s:(5, 1)
ba:(5, 1)
VS + by = out
[m, n] x [n, 1] + [m, 1]= [m, 1]
[3, 5] x [5, 1] + [3, 1] = [3, 1]
V:(3, 5)
by:(3, 1)
if __name__ == '__main__':
    np.random.seed(1)
    x_t = np.random.randn(3, 1)
    s_prev = np.random.randn(5, 1)
    U = np.random.randn(5, 3)
    W = np.random.randn(5, 5)
    V = np.random.randn(3, 5)
    ba = np.random.randn(5, 1)
    by = np.random.randn(3, 1)
    parameters = {"U": U, "W": W, "V": V, "ba": ba, "by": by}
    s_next, out_pred, cache = rnn_cell_forward(x_t, s_prev, parameters)
    print("s_next = ", s_next)
    print("s_next.shape = ", s_next.shape)
    print("out_pred =", out_pred)
    print("out_pred.shape = ", out_pred.shape)


所有cell的前向传播实现


def rnn_forward(x, s0, parameters):
    """
    对多个Cell的RNN进行前向传播
    :param x: T个时刻的X总输入形状
    :param a0: 隐层第一次输入
    :param parameters: 参数
    :return: s, y, caches
    """
    # 初始化缓存
    caches = []
    # 根据X输入的形状确定cell的个数(3, 1, T)
    # m是词的个数,n为自定义数字:(3, 5)
    m, _, T = x.shape
    # 根据输出
    m, n = parameters["V"].shape
    # 初始化所有cell的S,用于保存所有cell的隐层结果
    # 初始化所有cell的输出y,保存所有输出结果
    s = np.zeros((n, 1, T))
    y = np.zeros((m, 1, T))
    # 初始化第一个输入s_0
    s_next = s0
    # 根据cell的个数循环,并保存每组的
    for t in range(T):
        # 更新每个隐层的输出计算结果,s,o,cache
        s_next, out_pred, cache = rnn_cell_forward(x[:, :, t], s_next, parameters)
        # 保存隐层的输出值s_next
        s[:, :, t] = s_next
        # 保存cell的预测值out_pred
        y[:, :, t] = out_pred
        # 保存每个cell缓存结果
        caches.append(cache)
    return s, y, caches


进行测试


if __name__ == '__main__':
    np.random.seed(1)
    # 定义了4个cell,每个词形状(3, 1)
    x = np.random.randn(3, 1, 4)
    s0 = np.random.randn(5, 1)
    W = np.random.randn(5, 5)
    U = np.random.randn(5, 3)
    V = np.random.randn(3, 5)
    ba = np.random.randn(5, 1)
    by = np.random.randn(3, 1)
    parameters = {"U": U, "W": W, "V": V, "ba": ba, "by": by}
    s, y, caches = rnn_forward(x, s0, parameters)
    print("s = ", s)
    print("s.shape = ", s.shape)
    print("y =", y)
    print("y.shape = ", y.shape)


2、反向传播过


  • 单个cell的BP
  • 所有cell的BP
  • 单个cell的反向传播


首先根据图中确定需要计算的梯度变量有哪些?


ds_next:表示当前cell的损失对输出s^{t}st的导数
dtanh:表示当前cell的损失对激活函数的导数
dx_t:表示当前cell的损失对输入x_t的导数
dU:表示当前cell的损失对U的导数
ds_prev:表示当前cell的损失对上一个cell的输入的导数
dW:表示当前cell的损失对W的导数
dba:表示当前cell的损失对dba的导数
def rnn_cell_backward(ds_next, cache):
    """
    对单个cell进行反向传播
    :param ds_next: 当前隐层输出结果相对于损失的导数
    :param cache: 每个cell的缓存
    :return:
    """
    # 获取缓存值
    (s_next, s_prev, x_t, parameters) = cache
    print(type(parameters))
    # 获取参数
    U = parameters["U"]
    W = parameters["W"]
    V = parameters["V"]
    ba = parameters["ba"]
    by = parameters["by"]
    # 计算tanh的梯度通过对s_next
    dtanh = (1 - s_next ** 2) * ds_next
    # 计算U的梯度值
    dx_t = np.dot(U.T, dtanh)
    dU = np.dot(dtanh, x_t.T)
    # 计算W的梯度值
    ds_prev = np.dot(W.T, dtanh)
    dW = np.dot(dtanh, s_prev.T)
    # 计算b的梯度
    dba = np.sum(dtanh, axis=1, keepdims=1)
    # 梯度字典
    gradients = {"dx_t": dx_t, "ds_prev": ds_prev, "dU": dU, "dW": dW, "dba": dba}
    return gradients


  • 多个cell的反向传播


这里我们假设知道了所有时刻相对于损失的的ds梯度值。


测试代码:


# backward
    np.random.seed(1)
    # 定义了4个cell,每个词形状(3, 1)
    x = np.random.randn(3, 1, 4)
    s0 = np.random.randn(5, 1)
    W = np.random.randn(5, 5)
    U = np.random.randn(5, 3)
    V = np.random.randn(3, 5)
    ba = np.random.randn(5, 1)
    by = np.random.randn(3, 1)
    parameters = {"U": U, "W": W, "V": V, "ba": ba, "by": by}
    s, y, caches = rnn_forward(x, s0, parameters)
    # 随机给一每个4个cell的隐层输出的导数结果(真实需要计算损失的导数)
    ds = np.random.randn(5, 1, 4)
    gradients = rnn_backward(ds, caches)
    print(gradients)


整个网络的反向传播过程


def rnn_backward(ds, caches):
    """
    对给定的一个序列进行RNN的发现反向传播
    :param da:
    :param caches:
    :return:
    """
    # 获取第一个cell的数据,参数,输入输出值
    (s1, s0, x_1, parameters) = caches[0]
    # 获取总共cell的数量以及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))
    dba = np.zeros((n, 1))
    ds0 = np.zeros((n, 1))
    ds_prevt = np.zeros((n, 1))
    # 循环从后往前进行反向传播
    for t in reversed(range(T)):
        # 根据时间T的s梯度,以及缓存计算当前的cell的反向传播梯度.
        gradients = rnn_cell_backward(ds[:, :, t] + ds_prevt, caches[t])
        # 获取梯度准备进行更新
        dx_t, ds_prevt, dUt, dWt, dbat = gradients["dx_t"], gradients["ds_prev"], gradients["dU"], gradients[
            "dW"], gradients["dba"]
        # 进行每次t时间上的梯度接过相加,作为最终更新的梯度
        dx[:, :, t] = dx_t
        dU += dUt
        dW += dWt
        dba += dbat
    # 最后ds0的输出梯度值
    ds0 = ds_prevt
    # 存储需要更新的梯度到字典当中
    gradients = {"dx": dx, "ds0": ds0, "dU": dU, "dW": dW, "dba": dba}
    return gradients


输出结果


{'dx': array([[[ 6.07961714e-02,  6.51523342e-02,  1.35284158e-02,
         -4.17389120e-01]],
       [[-1.87655227e-01,  1.88161638e-01,  2.76636979e-02,
          5.88910236e-01]],
       [[-2.10309856e-01,  1.17512701e-01, -1.81722854e-05,
          1.33762936e+00]]]), 'ds0': array([[-0.04446273],
       [-0.48089235],
       [-0.20806299],
       [ 0.05651028],
       [ 0.24527145]]), 'dU': array([[-7.75341202e-02,  1.14056089e-03, -1.39468435e-01],
       [-3.76126305e-01, -2.71092586e-01, -7.68534819e-01],
       [-2.27890773e-01, -4.52402940e-01, -5.62591790e-02],
       [ 3.67591208e-02,  1.45958528e-01,  1.47219164e-02],
       [-1.16043009e+00, -8.51763028e-01, -1.44090680e+00]]), 'dW': array([[ 0.04560171,  0.04695379,  0.0257273 , -0.02726464,  0.05504417],
       [ 0.37031535,  0.3703334 ,  0.38913814, -0.39608747,  0.36938758],
       [ 0.21223499,  0.3431846 ,  0.22255773, -0.35298064,  0.21136843],
       [-0.06210387, -0.06084794, -0.06470341,  0.06497274, -0.03480747],
       [ 0.78119033,  0.74650186,  0.34013264, -0.31155225,  0.6784628 ]]), 'dba': array([[ 0.02851001],
       [ 0.39449393],
       [ 0.35633039],
       [-0.06492795],
       [ 0.33991813]])}


那么接下来,我们看看RNN的一些改进结构,这里大家只要了解相关结构以及作用即可,不需要会公式的推导等。


4.1.4 GRU(门控循环单元)



2014年,


4.1.4.1 什么是GRU

image.png


  • GRU增加了两个门,一个重置门(reset gate)和一个更新门(update gate)


  • 重置门决定了如何将新的输入信息与前面的记忆相结合


  • 更新门定义了前面记忆保存到当前时间步的量


  • 如果将重置门设置为 1,更新门设置为 0,那么将再次获得标准 RNN 模型


4.1.4.2 直观理解



The cat,which already ate,…….,was full.


对于上面的句子,was是句子前面的cat来进行指定的,如果是复数将是were。所以之前的RNN当中的细胞单元没有这个功能,GRU当中加入更新门,在cat的位置置位1,一直保留到was时候。


4.1.4.3 本质解决问题


原论文中这样介绍:


image.png


  • 为了解决短期记忆问题,每个递归单元能够自适应捕捉不同尺度的依赖关系
  • 解决梯度消失的问题,在隐层输出的地方h_t,h_{t-1}ht,ht−1的关系用加法而不是RNN当中乘法+激活函数


4.1.5 LSTM(长短记忆网络)



image.png


h_tht:为该cell单元的输出

c_tct:为隐层的状态

三个门:遗忘门f、更新门u、输出门o


4.1.5.1 作用


便于记忆更长距离的时间状态。


4.1.6 总结


  • 掌握循环神经网络模型的种类及场景
  • 掌握循环神经网络原理
  • 输入词的表示
  • 交叉熵损失
  • 前向传播与反向传播过程



目录
相关文章
|
5月前
|
机器学习/深度学习 自然语言处理 算法
RNN-循环神经网络
自然语言处理(Nature language Processing, NLP)研究的主要是通过计算机算法来理解自然语言。对于自然语言来说,处理的数据主要就是人类的语言,我们在进行文本数据处理时,需要将文本进行数据值化,然后进行后续的训练工作。
|
4月前
|
机器学习/深度学习 数据采集 人工智能
循环神经网络RNN
7月更文挑战第2天
91 11
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
7.1 NLP经典神经网络 RNN LSTM
该文章介绍了自然语言处理中的情感分析任务,探讨了如何使用深度神经网络,特别是循环神经网络(RNN)和长短时记忆网络(LSTM),来处理和分析文本数据中的复杂情感倾向。
|
5月前
|
机器学习/深度学习 存储 自然语言处理
RNN与LSTM:循环神经网络的深入理解
【6月更文挑战第14天】本文深入探讨RNN和LSTM,两种关键的深度学习模型在处理序列数据时的作用。RNN利用记忆单元捕捉时间依赖性,但面临梯度消失和爆炸问题。为解决此问题,LSTM引入门控机制,有效捕获长期依赖,适用于长序列处理。RNN与LSTM相互关联,LSTM可视为RNN的优化版本。两者在NLP、语音识别等领域有广泛影响,未来潜力无限。
|
6月前
|
机器学习/深度学习 自然语言处理 语音技术
循环神经网络
循环神经网络
36 1
|
6月前
|
机器学习/深度学习
深度学习第2天:RNN循环神经网络
深度学习第2天:RNN循环神经网络
|
6月前
|
机器学习/深度学习 自然语言处理 并行计算
神经网络结构——CNN、RNN、LSTM、Transformer !!
神经网络结构——CNN、RNN、LSTM、Transformer !!
307 0
|
机器学习/深度学习
从RNN、LSTM到GRU的介绍
从RNN、LSTM到GRU的介绍
|
6月前
|
机器学习/深度学习 自然语言处理 数据处理
一文带你了解RNN循环神经网络
一文带你了解RNN循环神经网络
378 1
|
机器学习/深度学习 数据采集
将循环神经网络(RNN)
将循环神经网络(RNN)应用于现实世界数据进行时间序列预测,是一种常见的机器学习应用。时间序列预测是一种预测未来值的方法,它基于过去和现在的数据点。这种预测方法被广泛应用于金融、气象、工业生产等领域。以下是使用 RNN 进行时间序列预测的基本步骤:
64 3