循环神经网络
参考学习资料:
- 邱锡鹏老师-《神经网络与深度学习》—循环神经网络
- 吴恩达老师深度学习课程—序列模型
序列数据
在前馈神经网络中,信息的传递是单向的,虽容易学习,但在一定程度上也减弱了神经网络模型的能力。前馈神经网络和一些卷积神经网络可以看做一整个复杂的函数,每次的输入都是相互独立的,每一次的结果之间互不影响,也即网络当前输出只依赖当前输入。
但是,在目前的很多任务中,网络的输出不仅和当前时刻的输入相关,同时也和其过去一段时间的输出相关。其实也可以理解为一个有限状态自动机的功能:其下一个时刻的状态(输出)不仅仅和当前输入相关,也和当前状态(上一个时刻的输出)相关。
比如,一些视频、语音、文本分析处理任务等。下面介绍一下序列数据
序列数据
序列数据是常见的数据类型,前后数据通常具有关联性。
- Speech recognition(语音识别):给定一个输入音频片段 X X X,要求输出片段对应的文字记录 Y Y Y, X X X是按时序播放的音频片段, Y Y Y是一段单词
- Music generation(音乐生成):输入 X X X可以是空集或单一整数,输出 Y Y Y是序列数据,可以要生成的一系列音符等
- Sentiment classification(情感分类):输入 X X X是一段序列,而输出 Y Y Y则对输入序列进行一个态度或情绪的一个打分,得到一个评级
- DNA sequence analysis(DNA序列分析):DNA序列组成可用 A、C、G、T 四个字母表示,给定一段DNA序列 X X X,输出 Y Y Y需要标记出哪一部分是匹配哪一种蛋白质等等
- Machine translation(机器翻译):将输入 X X X的句子翻译成另一种语言的序列 Y Y Y
- Video activity recognition(视频行为识别):在给定的一系列视频帧 X X X中识别视频中的行为在干什么
- Name entity recognition(命名实体识别):在给定的一段句子 X X X中,识别出句子中包含的人名
以上是各种各样类型的序列数据的相关任务。
那么,与传统机器学习任务中的数据集不同,序列数据需要引入索引来表示数据中的第几个元素。
语言模型
语言模型是在自然语言处理(NLP)中的重要技术,在NLP中常常把文本看作是一个离散时间序列。一段长度为T的文本的词依次为 w 1 , w 2 , . . . , w T w_1,w_2,...,w_T w1,w2,...,wT,其中, w t ( 1 ≤ t ≤ T ) w_t(1\leq t\leq T) wt(1≤t≤T)是时间步t的输出或标签。
语言模型会计算该序列概率: P ( w 1 , w 2 , . . . , w T ) = ∏ t = 1 T P ( w t ∣ w 1 , . . . , w t − 1 ) P(w_1,w_2,...,w_T)=\prod_{t=1}^TP(w_t|w_1,...,w_{t-1}) P(w1,w2,...,wT)=∏t=1TP(wt∣w1,...,wt−1)
如,给出一个句子:
“Cats average 15 hours of sleep a day”
可以得出总时间步T=8,将每一个单词看做一个标签
统计语料库(Corpus)中的词频,得到概率:
P ( C a t s , a v e r a g e , 15 , h o u r s , o f , s l e e p , a , d a y ) = P ( C a t s ) P ( a v e r a g e ∣ C a t s ) P ( 15 ∣ C a t s , a v e r a g e ) . . . . . . P(Cats,average,15,hours,of,sleep,a,day)=P(Cats)P(average|Cats)P(15|Cats,average)... ... P(Cats,average,15,hours,of,sleep,a,day)=P(Cats)P(average∣Cats)P(15∣Cats,average)......
- 缺点:时间步t的词需要考虑t-1步的词,其计算量随t呈指数增长
构建序列模型
比如,给出一个命名实体识别问题的输入 X X X:
希望得到如下输出 y y y:输入的每个单词都对应一个输出值
这个输出 y y y 能告诉我们输入中的单词是否是人名的一部分,也许有更复杂的表示还能告诉我们这一部分从哪里开始,从哪里结束。
那么回头看这输入,是由9个单词组成,也就是会需要9个特征集合来表示这9个单词,并按序列中的位置进行索引,那么就可以用 x < t > x^{<t>} x<t> 来索引每个单词的不同位置:
x = [ x < 1 > , x < 2 > , x < 3 > , . . . , x < t > , . . . , x < 9 > ] y = [ y < 1 > , y < 2 > , y < 3 > , . . . , y < t > , . . . , y < 9 > ] x=[x^{<1>},x^{<2>},x^{<3>}, ... ,x^{<t>},... ,x^{<9>}]\\y=[y^{<1>},y^{<2>},y^{<3>}, ... ,y^{<t>},... ,y^{<9>}] x=[x<1>,x<2>,x<3>,...,x<t>,...,x<9>]y=[y<1>,y<2>,y<3>,...,y<t>,...,y<9>]
用 T x T_x Tx 表示输入序列的长度: T x = 9 T_x=9 Tx=9
用 T y T_y Ty 表示输入序列的长度: T y = 9 T_y=9 Ty=9
那么就可以有如下表示:
- x ( i ) < t > x^{(i)<t>} x(i)<t>:第 i i i 个训练样本输入集合中的第 t t t 个元素
- y ( i ) < t > y^{(i)<t>} y(i)<t>:第 i i i 个训练样本输出集合中的第 t t t 个元素
- T x ( i ) T_x^{(i)} Tx(i):第 i i i 个训练样本的输入序列长度
- T y ( i ) T_y^{(i)} Ty(i):第 i i i 个训练样本的输出序列长度
那么如何进一步表示我们序列中具体的每一个单词呢?以NLP中的文本分析任务和上面的这句话 x x x 为例:
首先,做一张词表,里面包含表示方法中要用到的单词(比如构建一个含10000个单词的的一个词表)
可以用 one-hot向量 来表示词典里的每个单词,如,“Harry” 就可以表示为:
最终就会得到9个one-hot向量:
如果在序列中遇到了不在我所构建的词表中的单词,就可以为其创建一个新的标记伪造单词: < U N K > <UNK> <UNK> 来表示不在词表中的单词。
循环神经网络
有了序列数据的一个构建,就需要我们把它输入到神经网络中去进行训练,如果将其放到我们的传统前馈神经网络中,是这样的过程:
那么此时就存在几个问题:
- 不能保证每一个训练样本的输入输出都具有同样长度的序列(如都是 T x = T y = 9 T_x=T_y=9 Tx=Ty=9),就算是采用填充方法使每个句子达到同样的最大长度,这种表示方式也不是很好,况且,词表很大的话,one-hot向量的维度也会变得巨大,增加了很大的参数计算量
- 这样的方式并不共享从文本不同位置上学到的特征,我们往往希望模型能够根据之前学到的特征,进行一些相关的预测,比如在某一位置识别到“H”为人名一部分,那么在下一段某一位置再遇见“H”也能自动识别其为人名一部分
那这就提出了一个新思路:能不能让神经元不仅能接收其他神经元的信息,同时也能接收自身的信息,也就是说,让模型不仅能学习到样本与样本之间的特征,最重要的是能在同一个样本中学习到前后相关联的特征。
如图,对于上面这个句子,我们从左至右读起来,去扫描这个句子:假如从第1个单词开始,输入 x < 1 > x^{<1>} x<1>经过一段神经网络隐藏层,得到预测输出 y ^ < 1 > \hat{y}^{<1>} y^<1>,然后接着下一个 x < 2 > x^{<2>} x<2>经过一段神经网络隐藏层,得到预测输出 y ^ < 2 > \hat{y}^{<2>} y^<2>,而此时,这个 y ^ < 2 > \hat{y}^{<2>} y^<2>不仅是由 x < 2 > x^{<2>} x<2>得到的,还由前面的时间步<1>的信息获得,这样就利用到了前面的特征信息,另外,整个流程还需要一个初始的激活值 a < 0 > a^{<0>} a<0>用于时间步的更新, w a x , w a a , w y a w_{ax},w_{aa},w_{ya} wax,waa,wya是权重参数。以下就是大致的流程图:
以上就是RNN大致的一个示意图。
RNN是针对序列数据而生的神经网络结构,核心在于循环使用网络层参数,避免时间步增大带来的参数激增。并引入隐藏状态用于记录历史信息,有效的处理数据的前后关联性。
隐藏状态:用于记录历史信息,有效处理数据的前后关联性。
循环神经网络是从左到右扫描数据的,同时每个时间步的参数也是共享的。有时,对于一些识别任务,我们不仅需要前面时间步的信息,还会需要后面的信息,这就需要对RNN进行一些其他调整,可以参考后面的BRNN(双向循环网络)。
前向传播
前向传播的过程如下:
【注】其中的 g 1 g_1 g1、 g 2 g_2 g2 一般是两个不同的激活函数,在RNN中一般使用Tanh来计算得出隐藏状态的激活值 a a a(Tanh能够将输出值域限制在(-1,1)之间,防止数值呈指数级变化),而输出结果往往会根据任务的不同而设置不同的激活函数,如果是命名实体识别任务,那么预测结果只有0和1两种,那么一般就会采用Sigmoid函数进行激活得到一个二分类的结果。
下面是将公式进行一下简化:
反向传播(随时间)
这个是我们RNN的前向传播过程:
为了计算反向传播过程,我们需要引入一个损失函数,这里先定义一个元素损失函数 L L L,对应序列中的一个具体的词, y ^ < t > \hat{y}^{<t>} y^<t> 为预测的结果,是人名的一部分则为1,否则为0, y < t > y^{<t>} y<t>为计算得出的这个词是名字的概率值。 L < t > ( y ^ < t > , y < t > ) = − y < t > l o g ( y ^ < t > ) − ( 1 − y < t > ) l o g ( 1 − y ^ < t > ) L^{<t>}(\hat{y}^{<t>},y^{<t>})=-y^{<t>}log(\hat{y}^{<t>})-(1-y^{<t>})log(1-\hat{y}^{<t>}) L<t>(y^<t>,y<t>)=−y<t>log(y^<t>)−(1−y<t>)log(1−y^<t>)这里可以定义为标准的Logistic 回归损失函数,也叫交叉熵损失函数。这是关于单个单词或者说某个时间步 t t t 上的预测值的损失函数,那么整个序列的损失函数就为:
L ( y ^ , y ) = ∑ t = 1 T y L < t > ( y ^ < t > , y < t > ) L(\hat{y},y)=\sum_{t=1}^{T_y}L^{<t>}(\hat{y}^{<t>},y^{<t>}) L(y^,y)=t=1∑TyL<t>(y^<t>,y<t>)
于是,整个过程就是这样,根据每一个时间步的 y ^ \hat{y} y^ 和 y y y 计算对应的损失 L < t > L^{<t>} L<t>,最后把每个单独时间步的损失函数都加起来,得到整个序列的损失函数 L L L。
不同类型的循环神经网络
应用到机器学习的几种模式
序列—类别
输入:序列 x 1 : T = ( x 1 , . . . , x T ) x_{1:T}=(x_1,...,x_T) x1:T=(x1,...,xT),长度为T
输出:类别 y ∈ ( 1 , . . . , C ) y\in {(1,...,C)} y∈(1,...,C)
- 将样本 x x x 按不同时刻输入RNN中,得到不同时间步的隐藏状态 h 1 , . . . , h T h_1,...,h_T h1,...,hT
- 可将 h T h_T hT 作为整个序列的最终表示(用于特征分类),并送入分类器如Softmax等: y ^ = g ( h T ) \hat{y}=g(h_T) y^=g(hT);也可取整个所有隐藏状态的平均值: y ^ = g ( 1 T ∑ t = 1 T h t ) \hat{y}=g(\frac{1}{T}\sum_{t=1}^Th_t) y^=g(T1∑t=1Tht)
同步的序列—序列
同步的序列到序列模式,意味着和前面只在最后有输出不一样,模型的每一时刻都有输入输出,且输入序列和输出序列的长度相同,每个时刻得到的隐藏状态 h t h_t ht 代表当前时刻和历史的信息,每一个时间步t都要送入分类器中得到分类概率: y t ^ = g ( h t ) , ∀ t ∈ [ 1 , T ] \hat{y_t}=g(h_t),\quad \forall t\in [1,T] yt^=g(ht),∀t∈[1,T]
异步的序列—序列
异步的序列—序列模式也称为:编码器—解码器(Encoder—Decoder)模型,也就是说,输入序列和输出序列不一定要有严格的对应关系,而且序列长度可以不一样,比如一些机器翻译任务,输出序列长度和输入序列长度一般是不一样的。一般通过先编码后解码方式实现。
输入:序列 x 1 : T = ( x 1 , . . . , x T ) x_{1:T}=(x_1,...,x_T) x1:T=(x1,...,xT),长度为T
输出:序列 y 1 : M = ( y 1 , . . . , y M ) y_{1:M}=(y_1,...,y_M) y1:M=(y1,...,yM),长度为M
- 先将输入序列按不同时刻输入到编码器(一个RNN)中,得到编码结果 h T h_T hT
- 再使用解码器(另一个RNN)对其进行解码(一些非线性计算操作)得到输出序列 y ^ 1 : M \hat{y}_{1:M} y^1:M ,为建立输出序列之间的依赖关系,在解码器中通常使用非线性的自回归模型
整个编码—解码过程如下:
- f 1 f_1 f1、 f 2 f_2 f2:分别为用作编码器和解码器的循环神经网络
- g ( ⋅ ) g(\cdot) g(⋅):分类器
门控循环单元(GRU)
GRU,门控循环单元。
相比传统的RNN,改变了其隐藏层,引入门控机制,使其能够更好地捕捉深层连接,控制信息更新的方式,并改善了梯度消失问题。
如下面公式为RNN模型第t个时间步的激活值计算:
a < t > = g ( W a [ a < t − 1 > , x < t > ] + b a ) a^{<t>}=g(W_a[a^{<t-1>},x^{<t>}]+b_a) a<t>=g(Wa[a<t−1>,x<t>]+ba)
- 上一个时间步的激活值 a < t − 1 > a^{<t-1>} a<t−1> 乘以权重矩阵,加上当前时间步的输入 x < t > x^{<t>} x<t>乘以权重矩阵,加上偏置得到当前时间步t的激活值 a < t > a^{<t>} a<t>。
而在GRU单元中有一个新的参数变量:c,代表细胞,即记忆细胞,用于给网络提供记忆能力。那么,在时间步 t t t 时,网络得到激活值 a < t > a^{<t>} a<t>,此时令 c < t > = a < t > c^{<t>}=a^{<t>} c<t>=a<t>,先让记忆细胞记一下当前时间步的激活值,到了后面通过计算记忆细胞 c c c 会得到新的值,这个值会作为候选值来重写更新记忆细胞的原来值,我们用 c ~ < t > \tilde{c}^{<t>} c~<t> 表示。
但是,记忆细胞中的值我们不一定每次都需要更新,这里GRU采用一个门控单元 Γ u \Gamma_u Γu 来控制记忆值的更新,和另一个门控单元 Γ r \Gamma_r Γr 来计算要更新的值与原来值的相关性,以便在恰当的时机进行合适的更新:
Γ u = σ ( W u [ c < t − 1 > , x < t > ] + b u ) \varGamma _u=\sigma \left( W_u\left[ c^{<t-1>},x^{<t>} \right] +b_u \right) Γu=σ(Wu[c<t−1>,x<t>]+bu)
Γ r = σ ( W r [ c < t − 1 > , x < t > ] + b r ) \varGamma _r=\sigma \left( W_r\left[ c^{<t-1>},x^{<t>} \right] +b_r \right) Γr=σ(Wr[c<t−1>,x<t>]+br)
- Γ u \Gamma_u Γu:更新门(值在0~1之间),用于控制信息流通,也就是控制是否需要更新记忆值
- Γ r \Gamma_r Γr:相关门(值在0~1之间),表示候选值与当前记忆细胞值的相关性
- σ \sigma σ:Sigmoid函数,用于控制门控值在0~1之间
这样,候选值 c ~ < t > \tilde{c}^{<t>} c~<t>通过tanh作为激活函数(用tanh是由于其导数有比较大的值域,能够缓解梯度消失问题)计算得到: c ~ < t > = t a n h ( W c [ Γ r ∗ c < t − 1 > , x < t > ] + b c ) \tilde{c}^{<t>}=tanh\left( W_c\left[ \Gamma_r*c^{<t-1>},x^{<t>} \right] +b_c \right) c~<t>=tanh(Wc[Γr∗c<t−1>,x<t>]+bc)
对于相关门 Γ r \Gamma_r Γr:
- 当 Γ r = 0 \Gamma_r=0 Γr=0时,候选值 c ~ < t > = t a n h ( W c ⋅ x < t > + b c ) \tilde{c}^{<t>}=tanh(W_c\cdot x^{<t>}+b_c) c~<t>=tanh(Wc⋅x<t>+bc),只与当前时间步t的输入 x t x_t xt 相关,和历史记忆值无关。
- 当 Γ r = 1 \Gamma_r=1 Γr=1时,候选值 c ~ < t > = t a n h ( W c [ c < t − 1 > , x < t > ] + b c ) \tilde{c}^{<t>}=tanh\left( W_c\left[c^{<t-1>},x^{<t>} \right] +b_c \right) c~<t>=tanh(Wc[c<t−1>,x<t>]+bc),和当前输入和历史记忆值都相关,此时就相当于一个简单的RNN。
那么综上,记忆细胞 c < t > c^{<t>} c<t> 中的值的更新情况可以用这样的公式来表示:
c < t > = Γ u ∗ c ~ < t > + ( 1 − Γ u ) ∗ c < t − 1 > c^{<t>}=\varGamma _u* \tilde{c}^{<t>}+\left( 1-\varGamma _u \right) * c^{<t-1>} c<t>=Γu∗c~<t>+(1−Γu)∗c<t−1>
对于更新门 Γ u \Gamma_u Γu:
- 当 Γ u = 1 \Gamma_u=1 Γu=1时, c < t > = c ~ < t > c^{<t>}=\tilde{c}^{<t>} c<t>=c~<t>,就是把记忆细胞赋值为当前的候选值,记忆值就更新了。
- 当 Γ u = 0 \Gamma_u=0 Γu=0时, c < t > = c < t − 1 > c^{<t>}=c^{<t-1>} c<t>=c<t−1>,也就是等于旧的记忆值,不更新,一直记忆的是之前时间步的值。
最终,得到新的记忆细胞值,直接输入给下一个GRU单元: a < t > = c < t > a^{<t>}=c^{<t>} a<t>=c<t>
GRU的核心在于门控,通过门确定候选值 c ~ < t > \tilde{c}^{<t>} c~<t> 和 c < t > c^{<t>} c<t> 之间的相关性(依赖性)来决定是否保留原来的记忆(记忆or遗忘)。下图即为GRU循环单元的一个简单的示意图:
长短期记忆(LSTM)
长短期记忆网络(LSTM)引入了三个门控单元来控制信息传递,并且在记忆细胞值进行传递到下一单元作为激活值时,另外引入了门控来控制输出,与将记忆细胞值直接作为下一单元的激活值的GRU是不同的。
LSTM中参与当前时间步t计算的上一时间步t-1的激活值 a < t − 1 > a^{<t-1>} a<t−1>已经不再是上一单元得出的记忆细胞中的值 c < t − 1 > c^{<t-1>} c<t−1>了(两者不相等),所以此时当前时间步t的记忆细胞候选值的计算如下:
c ~ < t > = t a n h ( W c [ a < t − 1 > , x < t > ] + b c ) \tilde{c}^{<t>}=tanh\left( W_c\left[ a^{<t-1>},x^{<t>} \right] +b_c \right) c~<t>=tanh(Wc[a<t−1>,x<t>]+bc)
引入三个门控单元:更新门( Γ u \varGamma_u Γu)、遗忘门( Γ f \varGamma_f Γf)和输出门( Γ o \varGamma_o Γo)
Γ u = σ ( W u [ a < t − 1 > , x < t > ] + b u ) Γ f = σ ( W f [ a < t − 1 > , x < t > ] + b f ) Γ o = σ ( W o [ a < t − 1 > , x < t > ] + b o ) \varGamma _u=\sigma \left( W_u\left[ a^{<t-1>},x^{<t>} \right] +b_u \right) \\ \varGamma _f=\sigma \left( W_f\left[ a^{<t-1>},x^{<t>} \right] +b_f \right) \\\varGamma _o=\sigma \left( W_o\left[ a^{<t-1>},x^{<t>} \right] +b_o \right) Γu=σ(Wu[a<t−1>,x<t>]+bu)Γf=σ(Wf[a<t−1>,x<t>]+bf)Γo=σ(Wo[a<t−1>,x<t>]+bo)
- 更新门( Γ u \varGamma_u Γu):控制当前时间步t的记忆细胞值的更新,对于当前时间步,候选值 c ~ < t > \tilde{c}^{<t>} c~<t>应保留多少需要的信息
- 遗忘门( Γ f \varGamma_f Γf):控制上一个时间步t-1得到的记忆细胞值 c < t − 1 > c^{<t-1>} c<t−1>需要遗忘掉多少不需要的信息
- 输出门( Γ o \varGamma_o Γo):控制当前时间步t最终得到的输出值,有多少部分信息需要传递给下一单元
- σ \sigma σ:Sigmoid函数,输出区间(0,1)
得到的当前时间步t的记忆细胞值 c < t > c^{<t>} c<t>为:
c < t > = Γ u ∗ c ~ < t > + Γ f ∗ c < t − 1 > c^{<t>}=\varGamma _u* \tilde{c}^{<t>}+\varGamma _f* c^{<t-1>} c<t>=Γu∗c~<t>+Γf∗c<t−1>
- 当 Γ u = 1 , Γ f = 0 \varGamma_u=1,\varGamma_f=0 Γu=1,Γf=0时,记忆细胞将历史信息清空,并更新为候选值 c ~ < t > \tilde{c}^{<t>} c~<t>,此时记忆细胞中的值依然和上一时间步的历史信息(激活值 a < t − 1 > a^{<t-1>} a<t−1>)相关
- 当 Γ u = 0 , Γ f = 1 \varGamma_u=0,\varGamma_f=1 Γu=0,Γf=1时,记忆细胞为上一时间步t-1的旧值 c < t − 1 > c^{<t-1>} c<t−1>,不进行更新
最后,通过输出门( Γ o \varGamma_o Γo)的控制得到新的用于下一单元的激活值 a < t > a^{<t>} a<t>:
a < t > = Γ o ∗ t a n h ( c < t > ) a^{<t>}=\varGamma _o* tanh\left( c^{<t>} \right) a<t>=Γo∗tanh(c<t>)
整个LSTM网络的示意图:
【注】有关“记忆”
RNN中,无论是GRU或LSTM,其中的记忆细胞 c c c 存储了历史信息,就起到了记忆的功能,只不过,在简单循环网络中,记忆单元 c c c 每个时刻都会被重写更新,因此可以看作一种短期记忆( Short-Term Memory)。在神经网络中,长期记忆( Long-Term Memory) 可以看作网络参数,隐含了从训练数据中学到的经验,其更新周期要远远慢于短期记忆。而在 LSTM 网络中,记忆单元 c c c 可以在某个时刻捕捉到某个关键信息, 并有能力将此关键信息保存一定的时间间隔。此时,记忆单元中保存信息的生命周期要长于短期记忆,但又远远短于长期记忆,长短期记忆是指长的“短期记忆”。
因此称为长短期记忆(Long Short-Term Memory)。
双向神经网络(BRNN)
在之前提到的命名实体识别任务中,我们知道,对于有些句子,为了识别出句子中代表人名的主体,我们光看句子的前面的部分是往往不够的,需要纵观整个序列去进行全局把握,才能有充分的上下文信息去预测准确的结果。因此,在这样的任务中,我们可以增加一个按照时间的逆序来传递信息的网络层,来增强网络的能力。
如:
- He said,“Teddy bears are on sale!”
- He said, “Teddy Roosevelt was a great President!”
会发现,在对第三个位置“Teddy”进行预测时,根据前三个位置的信息无法判断这是否是人名的一部分,只有看到后面的泰迪熊和前美国总统,才能判断前美国总统的这个Teddy才是人名。对于简单的RNN以及GRU、LSTM都只会考虑历史信息,而无法考虑未来的信息来获得更准确的预测结果。
那么一个双向的RNN模型就能解决这个问题,如图:
如给定的T个时间步的输入序列: x < 1 > , x < 2 > , . . . , x < T > x^{<1>},x^{<2>},...,x^{<T>} x<1>,x<2>,...,x<T>
- 前向循环单元(右箭头)
- 反向循环单元(左箭头)
给定一个输入序列 x < 1 > x^{<1>} x<1>到 x < T > x^{<T>} x<T>,首先依次计算前向循环单元的激活值从第一个时间步到第T个时间步,然后,反向序列从计算第T个时间步开始,反向进行,一直到第一个时间步的激活值,会发现整个网络构成了一个无环图。在所有前向反向激活值计算出之后,就可以进行每个时间步的预测输出了:
y ^ < t > = g ( W y [ 前 向 激 活 值 , 反 向 激 活 值 ] + b y ) \hat{y}^{<t>}=g(W_y[前向激活值,反向激活值]+b_y) y^<t>=g(Wy[前向激活值,反向激活值]+by)
深层循环神经网络
有时候对于一些复杂函数的学习,通常我们会把RNN的多个层堆叠在一起构建更深的模型,这种堆叠结构称为堆叠RNN(SRNN),也叫循环多层感知机(RMLP)。第 𝑙 层网络的输入是第 𝑙 − 1层网络的输出: h t ( l ) = g ( W ( l ) [ h t − 1 ( l ) , h t ( l − 1 ) ] + b ( l ) ) h_t^{(l)}=g(W^{(l)}[h_{t-1}^{(l)},h_{t}^{(l-1)}]+b^{(l)}) ht(l)=g(W(l)[ht−1(l),ht(l−1)]+b(l))
RNN扩展到图神经网络
如果将循环神经网络按时间展开, 每个时刻的隐藏状态 𝒉𝑡 看作一个节点, 那么这些节点可以构成一个链式结构, 每个节点 𝑡 都收到其父节点的消息,并更新自己的状态,传递给其子节点。而链式结构是一种特殊的图结构, 我们可以比较容易地将这种消息传递的思想扩展到任意的图结构上。
在实际应用中,很多数据是图结构的,比如知识图谱、社交网络、分子网络等。而前馈网络和反馈网络很难处理图结构的数据。
图神经网络(GNN)就是将消息传递的思想扩展到图结构数据上的神经网络。
对于一个任意的图结构 G ( v , e ) G(v,e) G(v,e),其中 v v v表示节点集合, e e e表示边集合,而每条边表示两个节点之间的依赖关系,节点之间的连接可以是有向的,也可以是无向的。图中的每一个节点 v v v 都用一组神经元来表示其状态信息 h ( v ) h^{(v)} h(v),初始状态可以为节点 v v v的输入特征 x ( v ) x^{(v)} x(v),每个节点可以收到来自相邻节点的消息,并更新自己的状态(所有的结构同时接受信息并更新自己的状态,同步更新)。
m t ( v ) = ∑ u ∈ N ( v ) f ( h t − 1 ( v ) , h t − 1 ( u ) , e ( u , v ) ) m_t^{(v)}=\sum_{u\in N(v)}f(h_{t-1}^{(v)},h_{t-1}^{(u)},e^{(u,v)}) mt(v)=u∈N(v)∑f(ht−1(v),ht−1(u),e(u,v))
h t ( v ) = g ( h t − 1 ( v ) , m t ( v ) ) h_t^{(v)}=g(h_{t-1}^{(v)},m_t^{(v)}) ht(v)=g(ht−1(v),mt(v))
- m t ( v ) m_t^{(v)} mt(v):在时间步 t t t 时节点 v v v 得到的特征信息
- h t ( v ) h_t^{(v)} ht(v):在时间步 t t t 时节点 v v v 的状态信息值
- N ( v ) N(v) N(v):节点 v v v 的邻居节点
- e ( u , v ) e^{(u,v)} e(u,v): u — v u—v u—v这条边上的特征信息
在整个图更新了 T T T 次后,可以通过一个输出函数 g ( ⋅ ) g(\cdot) g(⋅) 来得到整个网络的表示:
o t = g ( { h T ( v ) ∣ v ∈ V } ) o_t=g(\lbrace h_T^{(v)}|v\in V\rbrace) ot=g({hT(v)∣v∈V})