Recurrent Neural Network(RNN)——循环神经网络

本文详细介绍了循环神经网络(RNN)的基本概念、工作原理及实现方式,包括其在序列数据处理中的优势、状态更新公式、激活函数的作用、短期性问题以及解决方案如多层RNN和双向RNN。此外,还提供了从零开始实现RNN的代码示例,并展示了如何使用梯度裁剪防止梯度爆炸。文章通过训练和预测示例展示了RNN在文本生成任务上的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

循环神经网络(RNN),附代码

参考:(代码目前版本基本是参考沐神的)
[1] https://www.bilibili.com/video/BV1BQ4y1R7V7?p=3
[2] https://www.bilibili.com/video/BV1kq4y1H7sw?spm_id_from=333.999.0.0
在小规模问题上,RNN可能效果还是不错。
特点:输入和输出长度无需固定,适合语音,文本等时序序列数据。

RNN架构

摘自王树森教授的视频
上图为RNN的整体架构,RNN每次看到一个词,通过状态hi来积累看到的信息。例如,h0包含x0的信息,h1包含x0和x1的信息,最后一个状态ht包含了整句话的信息,从而可以把它作为整个句子的特征,用来做其他任务,比如分类。这是从架构图中直观看到的信息。注意,无论RNN的链条有多长,都只有一个参数矩阵A,A可以随机初始化,然后再通过训练来学习。

RNN具体状态更新过程

在这里插入图片描述
这个公式是从沐神[2]视频中截取的,用架构图中的形式描述可以写为下式:
在这里插入图片描述
这个b(bias)加不加其实都是可以的,不加比较简单,就像刚上手神经网络的时候,y=Wx+b,就干脆省掉b,方便学习。
在这里插入图片描述
(h_t-1 x_t)^T这个表示h_t-1和x_t拼接起来的转置,A就是第一个公式中Whh和Whx的拼接,所以两个是一模一样的。

计算可视化

在这里插入图片描述
RNN的激活函数用的是tanh,非sigmoid和relu
计算过程上面也有说明了,就是将上一个隐藏状态h_t-1和当前的输入x_t拼接在一起,和参数矩阵A相乘,然后在外面套一个激活函数tanh

tanh激活函数是个什么

在这里插入图片描述
这个就是可以将自变量映射到-1到1之间的一个函数,叫双曲正切函数,定义式长这样。
在这里插入图片描述

那么为什么要用tanh作为激活函数?

设想一下,如果没有这个激活函数,输入的xi为全0向量,那么状态更新就变成了如下所示:
在这里插入图片描述
由此可以推导h_100 = Ah_99 = … = A^100h_0
若A的特征值是0.9,0.9的100次方是非常接近0的,h_100就几乎是一个0向量;同样地,若A的特征值是1.1,1.1的100次方是非常大的,h_100可能就溢出了。
用了tanh激活函数就会让每个数字映射到-1到1的这个合理的区间中。

RNN的用法

RNN的用法有两种,一种是直接将最后一个状态h_t拿出来作为最终的输出,也可以将所有状态一起拿出来作为最终的输出。
第二种用法的具体操作可以为:将h1,h2……ht拼接成一个大矩阵,然后用flatten将这个矩阵展开成一个向量,然后乘上一个参数矩阵套一层激活函数作为输出。

Simple RNN的缺陷

上述介绍的是Simple RNN,缺陷也是比较容易看出来的。
短期性:距离某个输入较远的隐状态h几乎不会受到这个输入的影响。
例如,h_100很可能和x_0已经无关了。
到这里可能就疑惑了,从架构图中不是看得出来h_100包含x0到x100的所有信息吗?但是h_100毕竟只是一个隐状态,而且离x0是非常远的,离x100是很近的,那它很明显要更关心和它更亲近的人,即h_100是和x100最相关的,这就是Simple RNN的短期性或者遗忘性的问题。

提升效果的办法:多层RNN,双向RNN

多层RNN(Stacked RNN)

在这里插入图片描述

这个比较简单,就是将一层一层的RNN拼接起来,下层的输出作为上层的输入,注意这里需要下层的h1到ht的所有状态,而不是只要ht一个状态。
每一层都是自己的参数矩阵A

双向RNN(Bidirectional RNN)

在这里插入图片描述
就是两条独立的RNN,一条从右往左读数据,一条从左往右读数据。
不共享参数,不共享状态,两条RNN各自输出自己的状态向量h,然后拼接起来成y,如果有多层,就把y作为上层的输入;如果没有多层,y可以都丢掉,只保留两条RNN最后的状态向量,ht和ht’,把他们拼接起来作为最后的特征来完成任务。
双向的RNN总是比Simple RNN效果好,这也是比较好理解的,Simple RNN容易忘掉早些的输入,双向的恰恰把早些的输入记的更深了。

还有个提升效果的办法的预训练,这个就不多说了,懂得都懂,不懂的可以搜一下,火的不得了。

代码实现:
先放沐神的版本,后面我自己写的版本。

import 模块

%matplotlib inline
import math
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'    #不加这个我的程序在训练时候会报一个dll的错误

定义batch_size、num_steps,导入数据

batch_size, num_steps = 32, 35    #num_steps是时间T的大小,一次看多长的序列   输入是batch_size*vocab_size   (因为输入的编码的char级别的onehot向量)
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

vocab可以看成是一个dict,keys是包含26个字母和’’,’ '(空格)两个元素
train_iter是包含训练数据的内容,已经封装好了,由于不是RNN的重点,就不细说了。里面的内容如下
在这里插入图片描述
从X和Y中的内容和vocab可以看出,Xi表示一个字母,是char级别的,而非word级别的

所需求解的RNN层参数W_xh、W_hh、b_h、输出层参数W_hq、b_q

因为是手撸RNN,所以需要先将参数矩阵都显式写出来,后面需要用矩阵乘法来实现
至于为什么写成一个函数的形式,这是为了方便封装,实现模型时显得简洁

def get_params(vocab_size, num_hiddens, device):
    num_inputs = num_outputs = vocab_size
    def normal(shape):
        return torch.randn(size=shape, device=device) * 0.01    #使得后面的矩阵按照正态分布初始化
    # 隐藏层参数
    W_xh = normal((num_inputs, num_hiddens))
    W_hh = normal((num_hiddens, num_hiddens))
    b_h = torch.zeros(num_hiddens, device=device)
    # 输出层参数
    W_hq = normal((num_hiddens, num_outputs))
    b_q = torch.zeros(num_outputs, device=device)
    # 附加梯度
    params = [W_xh, W_hh, b_h, W_hq, b_q]
    for param in params:
        param.requires_grad_(True)
    return params

RNN的运算实现

def rnn(inputs, state, params):
    # `inputs`的形状:(`时间步数量`,`批量⼤⼩`,`词表⼤⼩`)     
    W_xh, W_hh, b_h, W_hq, b_q = params
    H, = state
    outputs = []
    # `X`的形状:(`批量⼤⼩`,`词表⼤⼩`)
    for X in inputs:
        #W_xh:num_inputs, num_hiddens;W_hh:num_hiddens, num_hiddens
        H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)  
        Y = torch.mm(H, W_hq) + b_q
        outputs.append(Y)
    return torch.cat(outputs, dim=0), (H,)
为什么inputs的形状是(num_steps,batch_size,vocab_size)而不是(batch_size,num_steps,vocab_size)

这是因为每次送入模型运算的是当前时间步Xi下的字符,而不能将所有时间步都送入,所以要先将inputs转置成(num_steps,batch_size,vocab_size),每次取一个时间步作为输入

从零开始实现的循环神经⽹络模型

class RNNModelScratch: 
    def 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值