递归神经网络-RNN

本文介绍 RNN,进而介绍 LSTM、GRU,RNN 是在 ANN 的基础上,加入了隐藏状态,LSTM 为解决长程依赖的问题,甚至提出遗忘门的概念

  • 什么是时序数据?

  • 时序数据:在给定时间段内以连续顺序发生的各种数据点的序列,如上图是某地 8 月前的气温、降雨量、风强度数据,假设间隔 3 个月取 1次数据,那么图 s1、s2、s3 都是时序数据

  • 时间序列数据与其他数据的区别在于“时间/顺序”,它给数据增加了一个维度

  • 现实生活中有很多事物是“按时间/顺序”发生的,原始的 MLP 虽然能处理时间段的数据,但是不能显式利用历史时序数据,因此在 MLP 的基础上提出递归神经网络 (RNN)

  • 什么是 RNN?

  • 对于时序数据,MLP、CNN 只考虑当前输入,而不考虑其他时刻的输入 (意思是更新网络时,只有当前输入参与),这对有序列的数据不友好,于是在在 MLP 的基础上设计的,一种专门处理时序/顺序的网络结构,主要改动是:在每个隐藏层引入“记忆机制”,使得隐藏层计算时,不仅要学习当前的数据,还要利用历史的数据,即每个隐藏神经元的更新机制由公式 1 变为公式 2,其中 WSSt1W_{S}S_{t-1} 是上一时间步(历史)的输出,

    S=f(WinX+b)(1)St=f(WinX+WSSt1+b)(2)\begin{array}{l}S=f\left(W_{in}X+b\right)\quad\quad\quad\quad\quad\quad(1)\\ S_{t}=f\left(W_{in}X+W_{S}S_{t-1}+b\right)\quad(2)\end{array}

  • 注意:由于要记录隐藏状态,RNN 学习数据时,是一个一个时刻 timestep 去学习的,而不是 Batch_size,即上面的 (B, S, L)是先学习(B, S1, L),再学习(B, S2, L),依此类推

  • Pytorch 的 torch.nn.RNN 可以直接定义 RNN 网络结构,包括定义隐藏层的层数,如果不需要使用隐藏层,使用torch.nn.RNNCell 即可

  • RNN 的基本单元结构 (RNNCell)?

  • 递归神经网络-RNN-20230704212904

  • 一个最基本的 RNN 单元中有 2 个权重矩阵+2 个 Bias,即可训练的参数 W,U,BW, U, B,以及两个输入变量 inputh0,最后输出只有 hth_t,因为 output 其实和 h1h_1 是一个东西。输出计算公式为:

    ht=tanh(wihxt+bih+whhht1+bhh)=WXt+Uht1+Bh_t=tanh(w_{ih}*x_t+b_{ih}+w_{hh}*h_{t-1}+b_{hh})=W* X_t+U*h_{t-1}+B

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import torch
    #construction of RNNCell
    cell=torch.nn.RNNCell(input_size=4,hidden_size=2)
    dataset=torch.randn(3,1,4) #(seq,batch,features)
    hidden=torch.zeros(1,2) #(batch,hidden)
    for idx,input in enumerate(dataset):
    print('Input size:',input.shape) // torch.Size([1,4])*3
    hidden=cell(input,hidden)
    print('Output size:',hidden.shape) // torch.Size([1,2])*3
  • RNN 的结构?

  • 递归神经网络-RNN-20230704212905

  • 基于 RNNCell 可以构建出不同层的 RNN 网络,上图左右分别是 1 层、3 层的 RNN 网络,虽然网络在时间维度将隐藏状态展开了,但是其实每个 RNNCell 都只有 1 个输出,这个输出既是自己下次输入的隐藏状态,也是传递给下一层 RNNCell 的输入

  • 虽然上图都按时间横向展开了,但是每一列是任一时刻的权重矩阵

    1
    2
    3
    4
    5
    6
    7
    8
    import torch
    #construction of RNNCell
    cell=torch.nn.RNN(input_size=4,hidden_size=2,num_layers=2)
    inputs=torch.randn(3,1,4) #(seq,batch,features)
    hidden=torch.zeros(2,1,2) #(batch,hidden)
    out,hidden=cell(inputs,hidden)
    print('Output size:',out.shape) #(3,1,2) (seq_len,batch_size,hidden_size)
    print('Hidden size:',hidden.shape) # (2,1,2) #(num_layers,batch_size,hidden_size)
  • RNNCell 和 RNN 的输入输出?

-输入 x输出 y隐藏变量 h权重 W/U/B
RNNCell(S, B, L)(S, B, L’)(B, L’)(L, L’), (L’, L’), (B)
RNN(S, B, L)(S, B, L’)(M, B, L’)(L, L’) x M , (L’, L’) x M , (B) x M
  • 以上假设输入是 (S, B, L),中间隐藏变量长度是 L’,并且为了区分 RNNCell 和 RNN,假设 RNN 有 M 层

  • 关于输入输出:两者都可以接受 (S, B, L)输入,RNNCell 需要自己遍历 S 个时间步,去更新隐藏变量 h;而 RNN 在内部遍历,多层 RNN 实际上多层的 RNNCell 的堆叠

  • 关于隐变量:隐变量其实就是输出变量,只不过它使用1个时间步长度来记忆历史信息[(S, B, L’) VS (1,B, L’)= (B, L’)],每加1层RNNCELL,隐变量数量翻一倍

  • 关于权重:每个RNNCELL包含3个权重W/U/B,因为前向计算要执行xW+hU+B,所以W权重是(L, L’),U权重是(L’,L’),最终xW+hU=(B,L’),所以B=(L’)

  • 对 RNN 输入 (S, B, L)的理解?

  • S,B,L分别是time_steps、batch、input_size,这在实际场景分别表示什么含义?训练按照那个维度展开呢?

  • 文字 (段落/句子 (字词)/字词向量):假设有一段文字,包含 1000 个句子,每个句子 25 个字,每个字向量化后长度是 300,则 (S, B, L) 是 (25, 1000, 300)

  • 图片 (图片/横向 (位置)/位置向量表示):假设有1000 张大小为 28 x 28 的彩色图片 (1000, 3,28,28),将其采样为 (1000, 256, 1,28),去掉 1 维空间并调整位置得到为 (28, 1000, 256),对应着 (S, B, L) ,表示是 1000 张图片采样后横向 28 个位置,每个位置是 256 长度的隐向量,使用 RNN 学习这个矩阵是 OCR 识别的技术

  • 音频 (语音/帧/帧向量表示):假如训练数据有225段语音,每段语音对其进行分帧处理,每段语音有480帧,每一帧数据 shape=(8910,), (S, B, L) 为 (480,225,8910)

  • 训练:RNN 学习的是不同时刻数据的关系,那么输入就是[时刻 1,时刻 2,时刻 3,…],而不是类似 CNN 的 [batch 1,batch 2,batch 3,…]。所以模型按 time_steps 上遍历更新 RNN 状态,每个时刻输入的是 (batch,input_size),表示训练 batch 个样本的时刻 1,然后再训练 batch 个样本的时刻 2

  • 堆叠式 RNN 的前向计算过程?

  • 递归神经网络-RNN-20230704212906

  • 已知单层的 RNN 是按 time_steps 去循环更新隐状态和输出,那么多层 RNN 是如何去做的呢?比如是先使用第 1个时刻更新所有的 RNN?还是先在一个 RNN 上更新等到所有的隐状态,然后收集所有时刻隐状态作为下一个 RNN 的输入?

  • 实验证明,先在一个 RNN 上更新等到所有的隐状态,然后收集所有时刻隐状态作为下一个 RNN 的输入,意思是上图先横向扩展再进行纵向计算

  • RNN 的后向传播?

  • 递归神经网络-RNN-20230704212906-1

  • RNN 的 BP 算法的主要难点在于它 State 之间的通信,亦即梯度除了按照空间结构传播(ot>st>xto_t -> s_t -> x_t)以外,还得沿着时间通道传播(st>st1>...>s1s_t->s_{t-1}->...->s_1),例如,为了计算时刻 t = 4 的梯度,我们还需要反向传播3步,然后将梯度相加

  • 什么是双向 RNN?

  • 单向的时间步学习只一次只学习到当前时刻的“上文”,而“下文”信息不知道,因此同层内新增一个 RNNCell,逆时间步输入学习“下文”信息,最后将两个 RNNCell 的学习到的信特征 concate 即

  • 对于单层的 RNN,加了双向相当于权重数量翻倍,但是对于堆叠式双向 RNN,从第2层开始,其权重数量更多,比如第一层的 W 是 (3,7), 第2层的 W 是 (4,14),因为第1层输出是 (x, 14), 相当于是第2层的输。并且因为第2层以后输出是 (x, 14),所以第2层以后层的 W 都是 (4,14)

  • RNN 的应用方向?

  • N VS 1:输入是一个序列,输出是取最后一个 RNNCell 的输出,即序列分类,使用场景有音乐分类、文字情感倾向分析 递归神经网络-RNN-20230704212907

  • 1 VS N:输入一个值,生成一个序列,即序列生成,有以下 2 种方式,使用场景有从图像生成文字、从类别生成语音或音乐递归神经网络-RNN-20230704212907-1

  • N VS m (m<N):RNN 最重要的一个变种:N vs M,这种结构又叫 Encoder-Decoder 模型,也可以称之为 Seq2Seq 模型,使用场景有机器翻译、文本摘要、阅读理解、语音识别 递归神经网络-RNN-20230704212908

  • 什么是 RNN 的 Seq2Seq 模型?

  • 一种重要的 RNN 模型,也称为 Encoder-Decoder 模型,可以理解为一种 N×M 的模型。模型包含两个部分:Encoder 用于编码序列的信息,将任意长度的序列信息编码到一个向量 c 里。而 Decoder 是解码器,解码器得到上下文信息向量 c 之后可以将信息解码,并输出为序列

  • Encoder:Encoder 的 RNN 接受输入 x,最终输出一个编码所有信息的上下文向量 c,中间的神经元没有输出。Encoder 与一般的 RNN 区别不大,只是中间神经元没有输出。其中的上下文向量 c 可以采用多种方式进行计算。c 可以直接使用最后一个神经元的隐藏状态 hN 表示;也可以在最后一个神经元的隐藏状态上进行某种变换 hN 而得到,q 函数表示某种变换;也可以使用所有神经元的隐藏状态 h1, h2, …, hN 计算得到

    c=hNc=q(hN)c=q(h1,h2,...,hN)\begin{aligned}c&=h_N\\ c&=q(h_N)\\ c&=q(h_1,h_2,\text{...},h_N)\end{aligned}

  • Decoder-1:将上下文向量 c 当成是 RNN 的初始隐藏状态,输入到 RNN 中,后续只接受上一个神经元的隐藏层状态 h’ 而不接收其他的输入 x

    h1=σ(Wc+b)ht=σ(Wht1+b)yt=σ(Vht+c)\begin{aligned}h'_{1}=\sigma(Wc+b)\\ h'_{t}=\sigma(Wh'_{t-1}+b)\\ y'_{t}=\sigma(Vh'_{t}+c)\end{aligned}

  • Decoder-2:有了自己的初始隐藏层状态 h’0 ,不再把上下文向量 c 当成是 RNN 的初始隐藏状态,而是当成 RNN 每一个神经元的输入

    ht=σ(Uc+Wht1+b)yt=σ(Vht+c)\begin{aligned}h'_t&=\sigma(Uc+Wh'_{t-1}+b)\\ y'_t&=\sigma(Vh'_t+c)\end{aligned}

  • Decoder-3:和第二种类似,但是在输入的部分多了上一个神经元的输出 y’。即每一个神经元的输入包括:上一个神经元的隐藏层向量 h’,上一个神经元的输出 y’,当前的输入 c (Encoder 编码的上下文向量)。对于第一个神经元的输入 **y’**0,通常是句子其实标志位的 embedding 向量

    ht=σ(Uc+Wht1+Vyt1+b)yt=σ(Vht+c)\begin{aligned}h'_{t}=\sigma(Uc+Wh'_{t-1}+Vy'_{t-1}+b)\\ y'_{t}=\sigma(Vh'_{t}+c)\end{aligned}

  • 如何给 Seq2Seq 模型加 Attention?

  • 原始 Seq2SeqEncoder 部分只给 Decoder 传递最后一层的输出 C,这就要求 C 必须有足够的信息,但实际上很难做到。因此引入 Attention 机制,在不同时刻输入经过 Attention 的不同 C 去解决这个问题

  • 为了在不同的时刻使用不同的 context vector,Decoder 利用“初始状态 h+输入 x”生成对 context vector 的权重 (绿色区域),经过加权后 context vector 再输入到 Decoder (红换黄区域),这样预测不同的位置就有不同的 context vector

  • 上面的 Decoder 使用 GRU 计算,所以每个时刻输入要求 2 个,即隐状态+输入。其中输入=当前输入+经过注意力加权的context vector

  • RNN 的两种训练方式?

  • free-running mode:常见的训练网络的方式:,上一个 state 的输出作为下一个 state 的输入

  • Teacher Forcing:在训练网络过程中,每次不使用上一个 state 的输出作为下一个 state 的输入,而是直接使用训练数据的标准答案 (ground truth)的对应上一项作为下一个 state 的输入

  • 例子:free-running 模式如果一开始生成"a",之后作为输入来生成下一个单词,模型就偏离正轨。因为生成的错误结果,会导致后续的学习都受到不好的影响,导致学习速度变慢,模型也变得不稳定;teacher-forcing 模型生成一个"a",可以在计算了 error 之后,丢弃这个输出,把"Marry"作为后续的输入

  • teacher-forcing 过于依赖 ground truth 数据,导致模型泛化能力差,这个可以在前期使用 teacher-forcing 后期使用 free-running 或者使用 Beam Search(不用于训练的过程,而是用在测试的。在每一个神经元中,我们都选取当前输出概率值最大的 top k 个输出传递到下一个神经元)

  • RNN 的缺点?

  • RNN机制实际中存在长程梯度消失的问题,对于较长的句子,很难将输入的序列转化为定长的向量而保存所有的有效信息,所以随着所需翻译句子的长度的增加,这种结构的效果会显著下降

  • 什么是 LSTM?

  • 递归神经网络-RNN-20230704212910

  • 长短期记忆(Long short-term memory, LSTM)是一种特殊的RNN,主要是为了解决长序列训练过程中的梯度消失和梯度爆炸问题。简单来说,就是相比普通的RNN,LSTM能够在更长的序列中有更好的表现

  • LSTM 的输入和输出都比 RNN 多了一个,多的都是内部状态 C。它的作用是将过去与现在的记忆进行合并,这就需要遗忘门(过去)与输入门(现在)进行控制,它可以认为是理解 LSTM 的核心

  • LSTM 的基本单元结构 (LSTMCell)?

  • 递归神经网络-RNN-20230704212910-1

  • 一个最基本的 LSTM 单元中有4个可训练的参数 wf,wi,wj,wow_f,w_i,w_j,w_o,接受输入的 shape 仍旧为 [batchsize,n],如果每个样本包含 timestep 个时刻,则最后的输入 shape 同样为 [batchsize,timestep,n](或者 [timestep,batchsize,n]

  • 遗忘门:对上一个节点传进来的输入进行选择性忘记。简单来说就是会 “忘记不重要的,记住重要的”

  • 输入门:这个阶段将这个阶段的输入有选择性地进行“记忆”。主要是会对输入 xtx_t 进行选择记忆。哪些重要则着重记录下来,哪些不重要,则少记一些

  • 输出门:控制当前时刻的细胞状态有多少可以输出到下一时刻

  • LSTM 每个时刻的前向计算其实就是 2 件事,一是更新自己的细胞状态,从上一时刻的细胞状态+上一时刻的输出+现在的输入,通过构建遗忘门+输入门,选择性地遗忘知识和记住知识;二是更新自己的输出,通过细胞状态更新自己的输出,用于后续时刻的学习

  • 什么是 GRU?

  • 递归神经网络-RNN-20230704212911

  • GRU 是将 LSTM 里面的遗忘门和输入门合并为更新门

  • 重置门 rtr_t:更新门帮助模型决定到底要将多少过去的信息传递到当前,或到底前一时间步和当前时间步的信息有多少是需要继续传递的,它由历史状态 ht1h_{t-1} +当前输入 xtx_t 决定

    rt=σ(Wrxt+Urht1+br)r_t =\sigma(W_r x_t+U_r h_{t-1}+b_r)

  • 更新门 ztz_t:决定了到底有多少过去的信息需要被当前遗忘,它由历史状态 ht1h_{t-1} +当前输入 xtx_t 决定

    zt=σ(Wzxt+Uxht1+bz)z_t=\sigma(W_z x_t+U_x h_{t-1}+b_z)

  • 当前需要记忆的内容 h^t\hat h_t:根据当前输入和经过重置门后的历史状态,生成当前的状态

    h^t=tanh(Whxt+Uh(rtht1)+bh)\hat h_t=tanh(W_h x_t+U_h(r_t\odot h_{t-1})+b_h)

  • 当前时刻需要给下一时刻传递的信息 hth_t:根据当前时刻记忆的内容+更新门,向下一时刻更新状态

    ht=ziht1+(1zt)h^th_t=z_i\odot h_{t-1}+(1-z_t)\odot\hat h_t

  • RNN、LSTM、GRU 的区别?

  • 递归神经网络-RNN-20230704212912

  • RNN 为2输入,1输出:两个输入为上一单元输出状态和数据特征,输出为本单元的输出状态。本单元输出有两个作用,第一是在本单元作为输出依据进行后续计算,第二是传入下一个单元

  • LSTM 3输入,2输出:三输入分别为上一单元的内部状态 C、上一单元的输出状态 h 以及本单元需要输入的数据特征,两个输出为本单元的内部状态 C 以及本单元的输出状态 h。LSTM的输入和输出都比RNN多了一个,多的都是内部状态C。它的作用是将过去与现在的记忆进行合并,这就需要遗忘门(过去)与输入门(现在)进行控制,它可以认为是理解LSTM的核心

  • GRU 为2输入,1输出:GRU 的两个输入为上一单元输出状态以及当前数据特征,输出为本单元的输出状态,GRU是将LSTM里面的遗忘门和输入门合并为更新门

参考:

  1. 一文看尽RNN(循环神经网络) - 知乎
  2. Recurrent Neural Networks (RNNs). Implementing an RNN from scratch in… | by Javaid Nabi | Towards Data Science
  3. 请问rnn和lstm中batchsize和timestep的区别是什么? - 知乎
  4. 读PyTorch源码学习RNN(1) - 知乎
  5. PyTorch中的RNN和RNNCell_torch rnn和rnncell_还记得樱花正开~的博客-CSDN博客
  6. PyTorch实现RNN(两种构造RNN的方式;序列到序列的训练)_pytorch rnn_WeilingDu的博客-CSDN博客
  7. 【PyTorch学习笔记】21:nn.RNN和nn.RNNCell的使用_LauZyHou的博客-CSDN博客
  8. 反向传播(BP)及随时间反向传播(BPTT)推导解析 - 知乎
  9. RNN、lstm、gru详解 - 知乎
  10. RNN、LSTM、GRU序列模型对比 - 知乎
  11. Teacher forcing是什么? - MissHsu - 博客园
  12. TensorFlow中实现RNN,彻底弄懂time_step - 凌逆战 - 博客园
  13. RNN vs LSTM vs GRU – 该选哪个? - 知乎
  14. 【译】理解LSTM(通俗易懂版) - 简书
  15. RNN、LSTM、GRU序列模型对比 - 知乎
  16. nn.Embedding与nn.Embedding.from_pretrained - 知乎
  17. Embedding — PyTorch 2.0 documentation