深度学习之RNN循环神经网络

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/BigDream123/article/details/100015718

最近在看RNN循环神经网络,但是网上的教程杂七杂八的太乱了,这里我将网上的教程大概整理一下,以供大家一起起学习。

1、为什么需要RNN

我们知道 对于传统神经网络,通过训练之后,给特定的输入就会得到期望的输出。但是对于传统的神经网络,前一个输入与后一个输入之间通常是没有联系的,并且其输出之间也是没有联系的。

试想一下,如果我们要处理进行翻译一段文字,这时候将单个文字输入到神经网络里面,输出之间是没有任何关联的,这就导致我们翻译的文本前后不连贯,尤其对于这种序列模型,输入与输出类型并不一定一致。

RNN具有强大的处理各种输入输出类型的能力:

  • 情感分析(Sentiment Classification) – 这可以是简单的把一条推文分为正负两种情绪的任务。所以输入是任意长度的推文, 而输出是固定的长度和类型.

  • 图像标注(Image Captioning) – 假设我们有一个图片,我们需要一个对该图片的文本描述。所以,我们的输入是单一的图像,输出是一系列或序列单词。这里的图像可能是固定大小的,但输出是不同长度的文字描述。

  • 语言翻译(Language Translation) – 这里假设我们想将英文翻译为法语. 每种语言都有自己的语义,对同一句话有不同的长度。因此,这里的输入和输出是不同长度的。

2、RNN基本结构

如图这是一种基本的RNN结构

从图可以看出,输入层输入X后,经过权重矩阵U之后进入隐藏层S,隐藏层S同时通过权重矩阵V之后得到输出O和通过权重矩阵W又和输入X一起成为输入。我这样描述感觉描述不清楚,我们可以把它展开来看:

这里输入展开成为一个个序列X1,X2,X3....XT,前一个输入通过RNN-cell之后成为输出y和a,并通过a来影响下一个输出,这样就建立起前一个输入和后一个输入之间的联系。

对于一个单独的RNN-Cell结构如图所示:

同时这也是RNN单步前向传播的计算方法,Python实现:

# GRADED FUNCTION: rnn_cell_forward
def rnn_cell_forward(xt, a_prev, parameters):
    """
    根据图2实现RNN单元的单步前向传播
    
    参数:
        xt -- 时间步“t”输入的数据,维度为(n_x, m)
        a_prev -- 时间步“t - 1”的隐藏隐藏状态,维度为(n_a, m)
        parameters -- 字典,包含了以下内容:
                        Wax -- 矩阵,输入乘以权重,维度为(n_a, n_x)
                        Waa -- 矩阵,隐藏状态乘以权重,维度为(n_a, n_a)
                        Wya -- 矩阵,隐藏状态与输出相关的权重矩阵,维度为(n_y, n_a)
                        ba  -- 偏置,维度为(n_a, 1)
                        by  -- 偏置,隐藏状态与输出相关的偏置,维度为(n_y, 1)
    
    返回:
        a_next -- 下一个隐藏状态,维度为(n_a, m)
        yt_pred -- 在时间步“t”的预测,维度为(n_y, m)
        cache -- 反向传播需要的元组,包含了(a_next, a_prev, xt, parameters)
    """
    # Retrieve parameters from "parameters"
    Wax = parameters["Wax"]
    Waa = parameters["Waa"]
    Wya = parameters["Wya"]
    ba = parameters["ba"]
    by = parameters["by"]
    # compute next activation state using the formula given above
    a_next = np.tanh(np.dot(Wax,xt) + np.dot(Waa,a_prev) + ba)
    # compute output of the current cell using the formula given above
    yt_pred = softmax(np.dot(Wya,a_next) + by)       
    # store values you need for backward propagation in cache
    cache = (a_next, a_prev, xt, parameters)
    
    return a_next, yt_pred, cache

对于整个RNN的前向传播:

整体的前向传播就是讲每一步输出的a作为下一步的输入一直向前传播。

Python代码如图:

# GRADED FUNCTION: rnn_forward

def rnn_forward(x, a0, parameters):
    """
    根据图3来实现循环神经网络的前向传播
    
    参数:
        x -- 输入的全部数据,维度为(n_x, m, T_x)
        a0 -- 初始化隐藏状态,维度为 (n_a, m)
        parameters -- 字典,包含了以下内容:
                        Wax -- 矩阵,输入乘以权重,维度为(n_a, n_x)
                        Waa -- 矩阵,隐藏状态乘以权重,维度为(n_a, n_a)
                        Wya -- 矩阵,隐藏状态与输出相关的权重矩阵,维度为(n_y, n_a)
                        ba  -- 偏置,维度为(n_a, 1)
                        by  -- 偏置,隐藏状态与输出相关的偏置,维度为(n_y, 1)
    
    返回:
        a -- 所有时间步的隐藏状态,维度为(n_a, m, T_x)
        y_pred -- 所有时间步的预测,维度为(n_y, m, T_x)
        caches -- 为反向传播的保存的元组,维度为(【列表类型】cache, x))
    """
    
    # Initialize "caches" which will contain the list of all caches
    caches = [] 
    # Retrieve dimensions from shapes of x and Wy
    n_x, m, T_x = x.shape
    n_y, n_a = parameters["Wya"].shape
    # initialize "a" and "y" with zeros (≈2 lines)
    a  = np.zeros((n_a,m,T_x))
    y_pred = np.zeros((n_y,m,T_x))
    
    # Initialize a_next (≈1 line)
    a_next = a0
    
    # loop over all time-steps
    for t in range(T_x): # T_x为时间,表示按照时间前进
        # Update next hidden state, compute the prediction, get the cache (≈1 line)
        a_next, yt_pred, cache = rnn_cell_forward(x[:,:,t],a_next,parameters)
        # Save the value of the new "next" hidden state in a (≈1 line)
        a[:,:,t] = a_next
        # Save the value of the prediction in y (≈1 line)
        y_pred[:,:,t] = yt_pred
        # Append "cache" to "caches" (≈1 line)
        caches.append(cache)    
    # store values needed for backward propagation in cache
    caches = (caches, x)
    
    return a, y_pred, caches

对于神经网络都有其反向传播,RNN当然也有其对应的反向传播,如图所示:

python实现:

def rnn_cell_backward(da_next, cache):
    """
    实现基本的RNN单元的单步反向传播
    
    参数:
        da_next -- 关于下一个隐藏状态的损失的梯度。
        cache -- 字典类型,rnn_step_forward()的输出
        
    返回:
        gradients -- 字典,包含了以下参数:
                        dx -- 输入数据的梯度,维度为(n_x, m)
                        da_prev -- 上一隐藏层的隐藏状态,维度为(n_a, m)
                        dWax -- 输入到隐藏状态的权重的梯度,维度为(n_a, n_x)
                        dWaa -- 隐藏状态到隐藏状态的权重的梯度,维度为(n_a, n_a)
                        dba -- 偏置向量的梯度,维度为(n_a, 1)
    """
    # Retrieve values from cache
    (a_next, a_prev, xt, parameters) = cache
    
    # Retrieve values from parameters
    Wax = parameters["Wax"]
    Waa = parameters["Waa"]
    Wya = parameters["Wya"]
    ba = parameters["ba"]
    by = parameters["by"]
    # compute the gradient of tanh with respect to a_next (≈1 line)
    # 计算tanh相对于a_next的梯度.
    dtanh = (1 - np.square(a_next)) * da_next

    # compute the gradient of the loss with respect to Wax (≈2 lines)
    dxt = np.dot(Wax.T,dtanh)
    dWax = np.dot(dtanh,xt.T)

    # compute the gradient with respect to Waa (≈2 lines)
    da_prev = np.dot(Waa.T,dtanh)
    dWaa = np.dot(dtanh,a_prev.T)

    # compute the gradient with respect to b (≈1 line)
    dba = np.sum(dtanh,axis = 1,keepdims=True)
    # 必须加keepdims = True,这样可以保证输出为矩阵形式
    # Store the gradients in a python dictionary
    gradients = {"dxt": dxt, "da_prev": da_prev, "dWax": dWax, "dWaa": dWaa, "dba": dba}
    
    return gradients

3、双向RNN

我们知道,对于一句话“他来自__,他的国家首都是北京”,这句话空格处不止受到前半句的影响,而且受到后半段的影响,并且后半段的影响更为严重。之前的基本RNN结构只能处理受到前面的影响,而不能处理来自后面的影响,故这就需要一种能够处理前后影响的RNN结构----双向RNN。

结构如图所示:

计算方法与单向类似,只是多了反向计算。

4、深层双向RNN

之前介绍的RNN都只有一个隐藏层,将两个或两个以上的隐藏层堆起来就实现了深层RNN。

5、传统RNN的缺点

  • 梯度爆炸:RNN梯度消失是因为激活函数tanh函数的倒数在0到1之间,反向传播时更新前面时刻的参数时,当参数W初始化为小于1的数,则多个(tanh函数’ * W)相乘,将导致求得的偏导极小(小于1的数连乘),从而导致梯度消失。
  • 梯度消失:当参数初始化为足够大,使得tanh函数的倒数乘以W大于1,则将导致偏导极大(大于1的数连乘),从而导致梯度爆炸。

猜你喜欢

转载自blog.csdn.net/BigDream123/article/details/100015718
今日推荐