序言

1. 内容介绍

  本章介绍深度学习算法-循环神经网络,主要介绍循环神经网络的常见变种,包括 递归神经网络双向循环神经网络 以及 深度循环神经网络

2. 理论目标

  • Recursive 语法解析树
  • Recursive NN 传播与参数更新
  • BiRNN 模型结构
  • BiRNN 传播的数学推导
  • DRNN 设计理念
  • DRNN 的模型结构与参数更新

3. 实践目标

  • 掌握 Recursive 语法解析树
  • 掌握 Recursive NN 传播与参数更新
  • 掌握 BiRNN 模型结构
  • 掌握 BiRNN 传播的数学推导
  • 掌握 DRNN 设计理念
  • 掌握 DRNN 的模型结构与参数更新

4. 内容目录

  • 1.递归神经网络
  • 2.双向循环神经网络
  • 3.深度循环神经网络

第1节 递归神经网络

  递归神经网络 (Recursive Neural Network) 由 Pollack 于 1990 年引入,而 Bottou 在 2011 年描述了这类网络代表循环网络的潜在用途 学习推理

神经网络的输入层单元个数是固定的,因此必须用循环或者递归的方式来处理长度可变的输入,递归神经网络是代表循环网络的另一种扩展,它被构建为深的 树状结构 而不是 RNN 的链状结构,因为是不同类型的计算图。

  与循环网络相比,递归网络的明显优势是

  • 对于具有相同长度 \tauτ 的序列,深度可以急剧从 \tauτ 减小为 \theta(\log\tau)θ(logτ), 可以有效地解决长期依赖的问题
  • 对于含有不同歧义的输入数据,递归网络不再采用序列而是按照树/图结构处理信息,输出不同的 语法解析树 则对应不同的意思

  不过同样因为递归神经网络的输入是树/图结构,而这种结构需要花费很多人工去标注,导致其在行业应用中并不流行

1.1 递归网络语法解析树

  因为神经网络的输入层单元个数是固定的,因此必须用 循环 或者 递归 的方式来处理 长度可变 的输入。循环神经网络实现了前者,通过将长度不定的输入分割为等长度的小块,然后再依次的输入到网络中,从而实现了神经网络对变长输入的处理。例如处理一句话的输入时,可以将其看作是不同词依照时间顺序组成的序列,然后每次向循环神经网络输入一个词,如此循环直至整句话输入完毕,循环神经网络将产生对应的输出,依此就能处理任意长度的输入

  

改变神经网络的结构有哪些 神经网络变种_神经网络

  然而,有时候把句子看做是词的序列是不够的,比如这句话 『两个外语学院的学生』,可以看出这句话有明显歧义

  • 『两个外语学院的/学生』,学生可能有许多,但他们来自于两所外语学校
  • 『两个/外语学院的学生』,也就是只有两个学生,他们是外语学院的

  为了使模型区分出两个不同的意思,递归网络必须能够按照 图/树结构 去处理信息,而不是序列,即通过两个不同的语法解析树则区别不同的语义

  

改变神经网络的结构有哪些 神经网络变种_rnn_02

上例显示了自然语言可组合的性质,即词可以组成句、句可以组成段落、段落可以组成篇章,而更高层的语义取决于底层的语义以及不同组合方式

  递归神经网络是一种 表示学习,它可以将词、句、段、篇按照他们的语义映射到同一个向量空间中,也就是把可组合图/数结构的信息表示为一个个有意义的向量,并以此为基础去完成更高级的任务,比如递归神经网络在做情感分析时,可以比较好的处理否定句

  

改变神经网络的结构有哪些 神经网络变种_神经网络_03

蓝色表示正面评价,白色是中性评价,红色表示负面评价

每个节点是一个向量,这个向量表达了以它为根的子树的情感评价

比如 intelligent humor 是正面评价,而 care about cleverness wit or any other kind of intelligent humor 是中性评价

模型能够正确的处理 doesn’t 的含义,将正面评价转变为负面评价

  尽管递归神经网络具有更为强大的表示能力,但是在实际应用中并不太流行。其中一个主要原因是,递归神经网络的图/树结构需要花费很多人工去标注

循环神经网络处理句子,可以直接把句子作为输入。然而,递归神经网络处理句子,就必须把每个句子标注为语法解析树的形式,这无疑要花费非常大的精力。很多时候,相对于递归神经网络能够带来的性能提升,这个投入是不太划算的

1.2 递归网络前向传播

  递归神经网络的输入是两个子节点,输出是将这两个子节点编码后产生的父节点,父节点的维度和每个子节点是相同的

  

改变神经网络的结构有哪些 神经网络变种_递归_04

C_1,C_2C1,C2 分别是表示两个子节点的向量,PP 是表示父节点的向量

子节点和父节点组成一个 全连接神经网络,也就是子节点的每个神经元都和父节点的每个神经元两两相连

  矩阵 WW 表示这些连接上的权重,维度为 d \times 2dd×2d,其中,dd 表示每个节点的维度, 则父节点的计算公式可以写成


改变神经网络的结构有哪些 神经网络变种_递归_05

其中 \tanhtanh 是激活函数,bb 是偏置项,也是一个维度为 dd 的向量

  将产生的父节点的向量和其他子节点的向量再次作为网络的输入,再次产生它们的父节点。如此递归下去,直至整棵树处理完毕。最终将得到 根节点 (Root) 的向量,可以认为它是对整棵树的表示,这样就实现了把树映射为一个向量

  

改变神经网络的结构有哪些 神经网络变种_深度学习_06

1.3 递归网络反向传播

  递归神经网络的反向传播算法 Back Propagtion Through Structure (BPTS) 和循环神经网络 BPTT 算法类似,两者不同之处在于,前者需要将残差 \deltaδ 从根节点反向传播到各个子节点,而后者是将残差 \deltaδ 从当前时刻 t^ktk 反向传播到初始时刻 t^1t1

  设 \mathbf{net}_pnetp 是父节点的加权输入,则有

改变神经网络的结构有哪些 神经网络变种_递归_07

  定义 \delta_pδp 为误差函数 EE 相对于父节点 PP 的加权输入 \mathbf{net}_pnetp 的导数,则有
改变神经网络的结构有哪些 神经网络变种_递归_08

  因篇幅限制,在此不列举各项偏函数矩阵转换的过程,其梯度简化式为

改变神经网络的结构有哪些 神经网络变种_rnn_09  则其权重更新公式为
改变神经网络的结构有哪些 神经网络变种_神经网络_10

1.4 PyTorch 代码复现

# %load recursivenn.py import torch import torch.nn as nn import torch.nn.functional as F class RecursiveNN(nn.Module): def __init__(self, vocabSize, embedSize=100, numClasses=5): super(RecursiveNN, self).__init__() self.embedding = nn.Embedding(int(vocabSize), embedSize) self.W = nn.Linear(2*embedSize, embedSize, bias=True) self.projection = nn.Linear(embedSize, numClasses, bias=True) self.activation = F.relu self.nodeProbList = [] self.labelList = [] def traverse(self, node): if node.isLeaf(): currentNode = self.activation(self.embedding(torch.LongTensor([node.getLeafWord()]))) else: currentNode = self.activation(self.W(torch.cat((self.traverse(node.left()),self.traverse(node.right())),1))) self.nodeProbList.append(self.projection(currentNode)) self.labelList.append(torch.LongTensor([node.label()])) return currentNode def forward(self, x): self.nodeProbList = [] self.labelList = [] self.traverse(x) self.labelList = torch.cat(self.labelList) return torch.cat(self.nodeProbList) 

第2节 双向循环神经网络

  第 06 章介绍的 RNN 网络隐含了一个假设,即时刻 tt 的状态只能由过去的输入序列 \mathcal{S} = \lbrace \vec{x}^{(1)}, \vec{x}^{(2)}, \dots, \vec{x}^{(t-1)}\rbraceS={x(1),x(2),…,x(t−1)},以及当前的输入 \vec{x}^{(t)}x(t) 来决定。但在实际应用中,网络输出 \vec{o}^{(t)}o(t) 可能依赖于整个输入序列

如语音识别任务中,当前语音对应的单词不仅取决于前面的单词,也取决于后面的单词。因为词与词之间存在 语义依赖

  双向循环神经网络 (Bidirectional RNN, BiRNN) 就是为了解决这种双向依赖问题,它在需要双向信息的应用中非常成功,如 手写识别语音识别 等。BiRNN 是一个相对简单的 RNNs,由两个 RNNs 上下叠加在一起组成,输出由这两个 RNNs 的隐藏层状态决定

2.1 BiRNN 模型结构

改变神经网络的结构有哪些 神经网络变种_递归_11

  顾名思义,BiRNN 结合时间上从序列 起点移动 的 RNN 和另一个时间上从 序列末尾 移动的 RNN

  • \overrightarrow{h}^{(t)}h(t) 代表通过时间向未来移动的子 RNN 状态,向右传播信息
  • \overleftarrow{h}^{(t)}h(t) 代表通过时间向过去移动的子 RNN 状态,向左传播信息

  这允许输出单元 o^{(t)}o(t) 能够计算同时依赖于过去和未来且对时刻 tt 的输入值最敏感的表示,而不必指定 tt 周围固定大小的窗口,因此在每个点 tt,输出单元 o^{(t)}o(t) 可以受益于 \overrightarrow h^{(t)}h(t) 中关于过去的相关概要以及输出 \overleftarrow h^{(t)}h(t) 中关于未来的相关概要

2.2 BiRNN 传播推导

  给定时间步 tt 的小批量输入 X_t \in \Re^{n\times d}Xt∈ℜn×d (样本数为 nn,输入个数为 dd) 和隐藏层激活函数为 \phiϕ, 在 BiRNN 的模型架构中,设共有 hh 个隐藏单元,该时间步 tt 正向隐藏状态为 \overrightarrow h^{(t)} \in \Re^{n\times h}h(t)∈ℜn×h, 反向隐藏状态为 \overleftarrow h^{(t)} \in \Re^{n\times h}h(t)∈ℜn×h, 则有


\qquad\qquad\overleftarrow h^{(t)} = \phi(x_tW_{x,h}^{(f)} + \overleftarrow h^{(t-1)}W_{h,h}^{(f)} + b_h^{(f)})h(t)=ϕ(xtWx,h(f)+h(t−1)Wh,h(f)+bh(f))


\qquad\qquad\overrightarrow h^{(t)} = \phi(x_tW_{x,h}^{(b)} + \overrightarrow h^{(t+1)}W_{h,h}^{(b)} + b_h^{(b)})h(t)=ϕ(xtWx,h(b)+h(t+1)Wh,h(b)+bh(b))
 

其中 \lbrace W^{(f)}_{x,h}, W^{(b)}_{x,h}\rbrace \in \Re^{d \times h}, \lbrace W^{(f)}_{h,h}, W^{(b)}_{h,h}\rbrace \in \Re^{h \times h}{Wx,h(f),Wx,h(b)}∈ℜd×h,{Wh,h(f),Wh,h(b)}∈ℜh×h 和偏差 b^{(f)}_h \in \Re^{1 \times h}, b^{(b)}_h \in \Re^{1 \times h}bh(f)∈ℜ1×h,bh(b)∈ℜ1×h 均为模型参数

  连结两个方向的隐藏状态 \lbrace \overrightarrow h^{(t)}, \overleftarrow h^{(t)}\rbrace{h(t),h(t)} 来得到隐藏状态 h^{(t)} \in \Re^{n \times 2h}h(t)∈ℜn×2h,并将其输入到输出层, 假设共有 qq 个输出单元个数,计算输出 o^{(t)} \in \Re^{n \times q}o(t)∈ℜn×q


\qquad\qquad o^{(t)} = h^{(t)} W_{h,q} + b_qo(t)=h(t)Wh,q+bq
 

其中权重 W_{h,q} \in \Re^{2h\times q}Wh,q∈ℜ2h×q 和偏差 b_q \in \Re^{1 \times q}bq∈ℜ1×q 为输出层的模型参数

2.3 PyTorch 代码复现

  BiRNN 可以使用 RNN 类似的算法来做训练,因为两个方向的神经元没有任何相互作用。然而反向传播时,由于不能同时更新输入和输出层,因此需要额外的过程

  • 对于前向传播,传递正向状态和后向状态,然后输出神经元通过
  • 对于反向传播,首先输出神经元,然后传递正向状态和反向状态
  • 在进行前向和反向传播之后,更新权重

  BiRNN 使用的模型是 nn.LSTM, 在第 07 章中提到,LSTM 是一类可以处理长期依赖问题的特殊的 RNN,由 Hochreiter 和 Schmidhuber 于 1977 年提出,目前已有多种改进,且广泛用于各种各样的问题中

  • 参数 bidirectional=True 是表示该网路是一个双向的网络
  • 参数 batch_first=True 因为 nn.lstm() 接受的数据输入是 [序列长度,batch,输入维数],使用 batch_first 可以将输入变成 [batch,序列长度,输入维数]

# %load birnn.py import torch import torch.nn as nn class BiRNN(nn.Module): def __init__(self, input_size, hidden_size, num_layers, num_classes): super(BiRNN, self).__init__() self.hidden_size = hidden_size self.num_layers = num_layers self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, bidirectional=True) self.fc = nn.Linear(hidden_size*2, num_classes) # 2 for bidirection def forward(self, x): # Set initial states h0 = torch.zeros(self.num_layers*2, x.size(0), self.hidden_size) # 2 for bidirection c0 = torch.zeros(self.num_layers*2, x.size(0), self.hidden_size) # Forward propagate LSTM out, _ = self.lstm(x, (h0, c0)) # out: tensor of shape (batch_size, seq_length, hidden_size*2) # Decode the hidden state of the last time step out = self.fc(out[:, -1, :]) return out 

第3节 深度循环神经网络

3.1 DRNN 模型特点

  RNN 的计算大致可以分解为三种变换

  • 从输入 \vec{x}^{(t)}x(t) 到隐状态 \vec{h}^{(t)}h(t) 的变换
  • 从前一个隐状态 \vec{h}^{(t)}h(t) 到下一个隐状态 \vec{h}^{(t+1)}h(t+1) 的变换
  • 从隐状态 \vec{h}^{(t)}h(t) 到输出 \vec{o}^{(t)}o(t) 的变换

  这三个变换都是浅层的,即由一个仿射变换加一个激活函数组成, 事实上可以对这三种变换中引入 深度

  • 通过将 RNN 隐状态分为多层来引入深度
  • 在这三种变换中,各自使用一个独立的 MLP, 可以是较浅的,也可以是较深的
  • 在第二种方式的基础上,类似 ResNet 的思想,在 “隐状态-隐状态” 的路径中引入 跳跃连接

3.2 DRNN 模型结构

3.2.1 添加 隐状态深度

  通过将 RNN 的隐状态分为多层来引入深度:隐状态有两层 \vec{h}^{(t)}h(t) 和 \vec{z}^{(t)}z(t), 隐状态层中层次越高,对输入提取的概念越抽象

  • 模型的数学表示


    \qquad\qquad o^{(t)}_k = p(y^{(t)} = k | \vec{x}^{(1)}, \dots, \vec{x}^{(t)}), k = 1,2,\dots, Kok(t)=p(y(t)=k∣x(1),…,x(t)),k=1,2,…,K
     
  • 单个样本的损失


    \qquad\qquad L = - \sum\limits^{\tau}_{t=1}\sum\limits^K_{k=1}\mathbb{I}_{k=y^{(t)}}\enspace \log o^{(t)}_kL=−t=1∑τk=1∑KIk=y(t)logok(t)
     
  • 更新方程


    \qquad\qquad \vec{a}^{(t)}_1 = \vec{b}_1 + W_1\vec{h}^{(t-1)} + Ux^{(t)}a1(t)=b1+W1h(t−1)+Ux(t)

    \qquad\qquad \vec{h}^{(t)} = tanh(\vec{a}^{(t)}_1)h(t)=tanh(a1(t))

    \qquad\qquad \vec{a}^{(t)}_2 = \vec{b}_2 + W_2\vec{z}^{(t-1)} + Rh^{(t)}a2(t)=b2+W2z(t−1)+Rh(t)

    \qquad\qquad \vec{z}^{(t)} = tanh(\vec{a}^{(t)}_2)z(t)=tanh(a2(t))

    \qquad\qquad \vec{o}^{(t)} = softmax(\vec{c} + V\vec{z}^{(t)})o(t)=softmax(c+Vz(t))

可知每一层便是一个循环神经网络,而下一层的循环神经网络的输出作为上一层的输入,依次进行迭代而成

深度循环神经网络的应用范围较少,对于单层的模型已经可以实现对序列模型的编码,而深层次模型会造成一定的 过拟合和梯度 问题,因此很少被应用

3.2.2 添加 MLP 层

  使用一个独立的 MLP 所生成的额外深度将导致从时间步 tt 到时间步 t+1t+1 的最短路径变得更长,这可能导致优化困难而破坏学习效果

改变神经网络的结构有哪些 神经网络变种_神经网络_12

3.2.3 添加 跳跃连接

  类似 ResNet 的思想,在 隐状态-隐状态 的路径中引入跳跃连接,从而缓解最短路径变得更长的问题

改变神经网络的结构有哪些 神经网络变种_递归_13

开始实验