序言
在探讨序列建模的奥秘时,循环神经网络( RNN \text{RNN} RNN)与递归神经网络(虽名称相近,但在此讨论中侧重于其不同的应用场景,即递归网络常用于处理树状或层次化结构数据)的计算图展开成为了理解其工作原理的关键。计算图,作为描述算法执行流程的可视化工具,在 RNN \text{RNN} RNN和递归网络中尤为重要,因为它们通过时间或结构上的展开,使得我们能够清晰地看到序列信息是如何在网络中传递和处理的。这一过程不仅揭示了 RNN \text{RNN} RNN如何捕捉序列中的时间依赖,也展示了递归网络如何深入探索数据的层次结构。
展开计算图
- 计算图是形式化一组计算结构的方式,如那些涉及将输入和参数映射到输出和损失的计算。
- 综合的介绍请参考
深度前馈网络之反向传播和其他的微分算法篇 - 计算图
。 - 我们对展开 ( unfolding \text{unfolding} unfolding) 递归或循环计算得到的重复结构进行解释,这些重复结构通常对应于一个事件链。
- 展开 ( unfolding \text{unfolding} unfolding) 这个图导致深度网络结构中的参数共享。
- 例如:考虑动态系统的经典形式:
s
(
t
)
=
f
(
s
(
t
−
1
)
;
θ
)
\boldsymbol{s}^{(t)}=f(\boldsymbol{s}^{(t-1)};\boldsymbol{\theta})
s(t)=f(s(t−1);θ)
—
公式1
\quad\textbf{---\footnotesize{公式1}}
—公式1
- 其中, s ( t ) \boldsymbol{s}^{(t)} s(t)称为系统的状态。
-
s
\boldsymbol{s}
s在时刻
t
t
t的定义需要参考时刻
t
−
1
t-1
t−1时同样的定义,因此
公式1
是循环的。
- 对有限时间步
τ
\tau
τ,
τ
−
1
\tau-1
τ−1次应用这个定义可以展开这个图。例如,如果我们对
公式1
关于 τ = 3 \tau=3 τ=3展开,可以得到:
{ s ( 3 ) = f ( s ( 2 ) ; θ ) — 公式2 = f ( f ( s ( 1 ) ; θ ) ; θ ) — 公式3 \begin{cases}\begin{aligned}\boldsymbol{s}^{(3)}&=f(\boldsymbol{s}^{(2)};\boldsymbol{\theta}) & \quad\textbf{---\footnotesize{公式2}}\\&=f(f(\boldsymbol{s}^{(1)};\boldsymbol{\theta});\boldsymbol{\theta})&\quad\textbf{---\footnotesize{公式3}}\end{aligned}\end{cases} {s(3)=f(s(2);θ)=f(f(s(1);θ);θ)—公式2—公式3 — 公式2 \quad\textbf{---\footnotesize{公式2}} —公式2 — 公式3 \quad\textbf{---\footnotesize{公式3}} —公式3 - 以这种方式重复应用定义,展开等式,就能得到不涉及循环的表达。现在可以使用传统的有向无环计算图表示这样的表达。
公式2
和公式3
的展开计算图如图例1
所示。- 作为另一个例子,让我们考虑由外部信号
x
(
t
)
\boldsymbol{x}^{(t)}
x(t) 驱动的动态系统:
s ( t ) = f ( s ( t − 1 ) , x ( t ) ; θ ) \boldsymbol{s}^{(t)}=f(\boldsymbol{s}^{(t-1)},\boldsymbol{x}^{(t)};\boldsymbol{\theta}) s(t)=f(s(t−1),x(t);θ) — 公式4 \quad\textbf{---\footnotesize{公式4}} —公式4
我们可以看到,当前状态包含了整个过去序列的信息。 - 循环神经网络可以通过许多不同的方式建立。就像几乎所有函数都可以被认为是前馈网络,基本上任何涉及循环的函数可以被认为是一个循环神经网络。
- 很多循环神经网络使用
公式5
或类似的公式定义隐藏单元的值。为了表明状态是网络的隐藏单元,我们使用变量 h \boldsymbol{h} h 代表状态重写公式4
:
h ( t ) = f ( h ( t − 1 ) , x ( t ) ; θ ) \boldsymbol{h}^{(t)}=f(\boldsymbol{h}^{(t-1)},\boldsymbol{x}^{(t)};\boldsymbol{\theta}) h(t)=f(h(t−1),x(t);θ) — 公式5 \quad\textbf{---\footnotesize{公式5}} —公式5
如图例2
所示,典型 RNN \text{RNN} RNN会增加额外的架构,如读取状态信息 h \boldsymbol{h} h 进行预测的输出层。 - 当训练循环网络根据过去预测未来时,网络通常要学会使用
h
(
t
)
\boldsymbol{h}^{(t)}
h(t) 作为过去序列(直到
t
t
t)与任务相关方面的有损摘要。
- 此摘要一般而言一定是有损的,因为其映射任意长度的序列 ( x ( t ) , x ( t − 1 ) , x ( t − 2 ) , … , x ( 2 ) , x ( 1 ) ) (\boldsymbol{x}^{(t)},\boldsymbol{x}^{(t-1)},\boldsymbol{x}^{(t-2)},\dots,\boldsymbol{x}^{(2)},\boldsymbol{x}^{(1)}) (x(t),x(t−1),x(t−2),…,x(2),x(1)) 到一固定长度的向量 h ( t ) \boldsymbol{h}^{(t)} h(t)。
- 根据不同的训练准则,摘要可能选择性地精确保留过去序列的某些方面。
- 例如,如果在统计语言建模中使用的 RNN \text{RNN} RNN,通常给定前一个词预测下一个词,可能没有必要存储 t t t 前输入序列中的所有信息;而仅仅存储足够预测句子其余部分的信息。
- 最苛刻的情况是我们要求 h ( t ) \boldsymbol{h}^{(t)} h(t) 足够丰富,并能大致恢复输入序列,如自编码器框架(敬请期待)。
公式5
可以用两种不同的方式绘制。- 一种方法是为可能在模型的物理实现中存在的部分赋予一个节点,如生物神经网络。
- 在这个观点下,网络定义了实时操作的回路,如图
图例2
的左侧,其当前状态可以影响其未来的状态。 - 在这里,我们使用回路图的黑色方块表明在时刻 t t t 的状态到时刻 t + 1 t + 1 t+1 的状态单个时刻延迟中的相互作用。
- 在这个观点下,网络定义了实时操作的回路,如图
- 另一个绘制
RNN
\text{RNN}
RNN的方法是展开的计算图,其中每一个组件是由许多不同的变量表示,每个时间步一个变量,表示在该时间点组件的状态。
- 每个时间步的每个变量绘制为计算图的一个独立节点,如
图例2
的右侧。 - 我们所说的展开是将左图中的回路映射为右图中包含重复组件的计算图的操作。
- 目前,展开图的大小取决于序列长度。
- 每个时间步的每个变量绘制为计算图的一个独立节点,如
- 一种方法是为可能在模型的物理实现中存在的部分赋予一个节点,如生物神经网络。
- 我们可以用一个函数
g
(
t
)
g^{(t)}
g(t)代表经
t
t
t步展开后的循环:
{ h ( t ) = g ( t ) ( x ( t ) , x ( t − 1 ) , x ( t − 2 ) , … , x ( 2 ) , x ( 1 ) ) — 公式6 = f ( h ( t − 1 ) , x ( t ) ; θ ) — 公式7 \begin{cases}\begin{aligned}\boldsymbol{h}^{(t)}&=g^{(t)}(\boldsymbol{x}^{(t)},\boldsymbol{x}^{(t-1)},\boldsymbol{x}^{(t-2)},\dots,\boldsymbol{x}^{(2)},\boldsymbol{x}^{(1)})&\quad\textbf{---\footnotesize{公式6}}\\&=f(\boldsymbol{h}^{(t-1)},\boldsymbol{x}^{(t)};\boldsymbol{\theta})&\quad\textbf{---\footnotesize{公式7}}\end{aligned}\end{cases} {h(t)=g(t)(x(t),x(t−1),x(t−2),…,x(2),x(1))=f(h(t−1),x(t);θ)—公式6—公式7 — 公式6 \quad\textbf{---\footnotesize{公式6}} —公式6 — 公式7 \quad\textbf{---\footnotesize{公式7}} —公式7- 函数
g
(
t
)
g^{(t)}
g(t)将全部的过去序列
(
x
(
t
)
,
x
(
t
−
1
)
,
x
(
t
−
2
)
,
…
,
x
(
2
)
,
x
(
1
)
)
(\boldsymbol{x}^{(t)},\boldsymbol{x}^{(t-1)},\boldsymbol{x}^{(t-2)},\dots,\boldsymbol{x}^{(2)},\boldsymbol{x}^{(1)})
(x(t),x(t−1),x(t−2),…,x(2),x(1))作为输入来生成当前状态,但是展开的循环架构允许我们将
g
(
t
)
g^{(t)}
g(t)分解为函数
f
f
f的重复应用。因此,展开过程一如两个主要优点:
- 无论序列的长度,学习好的模型始终具有相同的输入大小,因为它指定的是从一种状态到另一种状态的转移,而不是在可变长度的历史状态上操作。
- 我们可以在每个时间步使用相同参数的相同转移函数 f f f。
- 这两个因素使得学习在所有时间步和所有序列长度上操作的单一模型 f f f 是可能的,而不需要在所有可能时间步学习独立的模型 g ( t ) g^{(t)} g(t)。
- 学习单一的共享模型允许泛化到没有见过的序列长度(没有出现在训练集中),并且估计模型所需的训练样本远远少于不带参数共享的模型。
- 函数
g
(
t
)
g^{(t)}
g(t)将全部的过去序列
(
x
(
t
)
,
x
(
t
−
1
)
,
x
(
t
−
2
)
,
…
,
x
(
2
)
,
x
(
1
)
)
(\boldsymbol{x}^{(t)},\boldsymbol{x}^{(t-1)},\boldsymbol{x}^{(t-2)},\dots,\boldsymbol{x}^{(2)},\boldsymbol{x}^{(1)})
(x(t),x(t−1),x(t−2),…,x(2),x(1))作为输入来生成当前状态,但是展开的循环架构允许我们将
g
(
t
)
g^{(t)}
g(t)分解为函数
f
f
f的重复应用。因此,展开过程一如两个主要优点:
- 无论是循环图和展开图都有其用途。
- 循环图简洁。
- 展开图能够明确描述其中的计算流程。
- 展开图还通过显式的信息流动路径帮助说明信息在时间上向前(计算输出和损失)和向后(计算梯度)的思想。
- 图例1: 将
公式1
描述的经典动态系统表示为展开的计算图。-
将
公式1
描述的经典动态系统表示为展开的计算图
-
说明:
- 每个节点表示在某个时间 t t t 的状态,并且函数 f f f 将 t t t 处的状态映射到 t + 1 t + 1 t+1 处的状态。
- 所有时间步都使用相同的参数(用于参数化 f f f的相同 θ \boldsymbol{\theta} θ 值)。
-
- 图例2: 没有输出的循环网络。
-
没有输出的循环网络。
-
说明:
- 此循环网络只处理来自输入 x \boldsymbol{x} x 的信息,将其合并到经过时间向前传播的状态 h \boldsymbol{h} h。
- 左图:回路原理图。黑色方块表示单个时间步的延迟。
- 右图:同一网络被视为展开的计算图,其中每个节点现在与一个特定的时间实例相关联。
-
总结
序列建模中的循环和递归网络,通过展开计算图的方式,为我们提供了一种直观理解其内部机制的方法。 RNN \text{RNN} RNN的计算图在时间轴上展开,每一步都接收新的输入并更新隐藏状态,从而保留了对过去信息的记忆。而递归网络的计算图则沿着数据的层次结构展开,每个节点根据其子节点的输出计算自身的表示,逐步构建出整个序列或结构的深层特征。这种展开计算图的方法,不仅增强了网络处理复杂序列和层次结构数据的能力,也为深度学习在更多领域的应用提供了坚实的理论基础和技术支持。