跳过正文
  1. Posts/

从局部线性化到 RNN:理解 Jacobian 连乘

·3082 字·7 分钟
目录

最近在重拾一些深度学习基础,发现了一个很有意思的视角。

以前在看 RNN(Recurrent Neural Network)的长距离梯度传播公式时,总能看到一堆 Jacobian 矩阵的连乘,比如 \(\frac{\partial h_T}{\partial h_0} = J_T J_{T-1} \dots J_1\)。

如果只是接受这个结论,其实很简单:多维的求导链式法则嘛,把偏导数组装成矩阵乘一乘就是了。但我发现,这种纯代数的理解方式往往在遇到更复杂的梯度消失(vanishing gradient)或爆炸(exploding gradient)现象时,很难建立起直观的物理图像。

这个 Jacobian 到底在描述什么?为什么它能描述梯度传播?为什么它的连乘会导致梯度消失或爆炸?我们需要从更深层的直觉重新建立这条认知链。

从导数重新理解“局部线性化”
#

要理解 Jacobian,首先得跳出对普通导数 \(f'(x)\) 的刻板印象:不要仅仅把它理解为一条曲线在某一点的“斜率”。

导数更深层的物理意义,是局部线性化(Local Linearization)。

我们知道,大多数有趣的非线性函数整体上都是弯曲的。但在足够小的局部,它看起来总是像一条直线(或者说切线)。导数,就是用来描述在这个足够小的局部里,输入的变化是如何线性地引起输出的变化的。

从公式上看,对于 \(y = f(x)\),在 \(x_0\) 附近:

$$ f(x_0 + \Delta x) \approx f(x_0) + f'(x_0)\Delta x $$

也就是说,当输入有一个微小扰动 \(\Delta x\) 时,输出的扰动可以近似为:

$$ \Delta y \approx f'(x_0) \Delta x $$

举个极简的例子,假设 \(f(x) = x^2\)。如果你给 \(x\) 加上一个小的扰动 \(\Delta x\),输出的变化是:

$$ (x + \Delta x)^2 = x^2 + 2x\Delta x + (\Delta x)^2 $$

当 \(\Delta x\) 极小时,\((\Delta x)^2\) 这个二阶项比 \(\Delta x\) 小得多,可以忽略不计。于是我们得到:

$$ \Delta y \approx 2x \Delta x $$

这就把一个二次函数的非线性扰动,转化为了一个只乘上常数 \(2x\) 的线性映射。这就是局部线性化:用简单的比例关系,去近似复杂的非线性变化。

Jacobian:多维函数的局部线性变换
#

一维世界里的局部线性化,对应的就是切线思想。而当我们走向多维空间,这个思想自然就推广成了 Jacobian。

在深度学习里,我们通常面对的是向量到向量的映射,比如 \(\mathbf{y} = f(\mathbf{x})\)。

那么在多维空间中,当我们给输入向量加上一个微小的扰动向量 \(\Delta\mathbf{x}\) 时,输出的扰动 \(\Delta\mathbf{y}\) 会怎样变化?

直接将上面的切线公式推而广之:

$$ f(\mathbf{x} + \Delta\mathbf{x}) \approx f(\mathbf{x}) + J\Delta\mathbf{x} $$

也就是说:

$$ \Delta\mathbf{y} \approx J\Delta\mathbf{x} $$

这是全文理解 Jacobian 最重要的公式。

很多教材会告诉你,Jacobian 矩阵 \(J\) 是把所有输出对所有输入的偏导数组装成的一个表。这当然没错,但这种定义缺乏 Insight。

更深刻的理解是:Jacobian 描述了在当前点附近,一个输入的小扰动经过函数以后,会怎样变成输出的小扰动。

你可以把它看作是局部扰动传播规则,或者当前点附近的局部线性模型

为什么这里自然会出现一个矩阵?因为当我们有多个输入 \((x_1, x_2)\) 变成多个输出 \((y_1, y_2)\) 时,\(x_1\) 的微小变化可能同时影响 \(y_1\) 和 \(y_2\)。我们必须同时描述 \(\frac{\partial y_1}{\partial x_1}, \frac{\partial y_1}{\partial x_2}\) 等等交织在一起的影响。矩阵,就是用来干净利落地把这些局部影响关系组织起来的数学工具。

矩阵本质上是线性变换
#

在 \(\Delta\mathbf{y} \approx J\Delta\mathbf{x}\) 这个公式里,\(J\) 作为一个矩阵,其实扮演了一个线性变换(Linear Transformation)的角色。

\(A\mathbf{x}\) 的操作,直观上就是把一个向量经过特定的拉伸、压缩或旋转,变成另一个向量。

举个非常简单的对角矩阵缩放的例子:

$$ A = \begin{bmatrix} 2 & 0 \\ 0 & 3 \end{bmatrix} $$

如果它作用在一个向量上,效果就是把第一个方向放大 2 倍,把第二个方向放大 3 倍。

所以,Jacobian 之所以能够精确描述“局部扰动怎么变化”,正是因为它本身就是一个线性变换算子。在当前这个局部,无论函数本身多么复杂,微小扰动 \(\Delta\mathbf{x}\) 的传播就等价于被这个矩阵 \(J\) 做了拉伸、压缩或旋转等线性操作。

从极简标量 RNN 建立直觉
#

在进阶到真正的 RNN 之前,我们先看一个极简的标量版本 RNN。

假设隐藏状态(hidden state)是一个标量 \(h_t\),并且我们忽略非线性激活函数,只保留核心的时间传播路径:

$$ h_t = wh_{t-1} + x_t $$

我们把前几步的时间展开看看:

$$ h_1 = wh_0 + x_1 $$

$$ h_2 = wh_1 + x_2 $$

如果我们想知道最初的隐藏状态 \(h_0\) 发生微小变化时,会对 \(h_2\) 产生什么影响,可以通过链式法则求导:

$$ \frac{\partial h_2}{\partial h_0} = \frac{\partial h_2}{\partial h_1} \frac{\partial h_1}{\partial h_0} = w \cdot w = w^2 $$

推广到 \(T\) 个时间步:

$$ \frac{\partial h_T}{\partial h_0} = w^T $$

这个极简的式子直接揭示了循环神经网络在时间维度上的核心问题:

  • 如果 \(|w| \lt 1\),不断相乘以后导数会越来越小,最终趋近于 0,这就导致了最早的扰动无法传导到最后。
  • 如果 \(|w| \gt 1\),不断相乘以后导数会爆炸,模型更新时直接崩溃。

这个因为连乘而导致的指数级衰减/放大的现象,就是 vanishing / exploding gradient 的原型。它是我们理解真正矩阵版 RNN 的重要桥梁。

真正的矩阵 RNN 发生了什么?
#

现在我们来看真实的 RNN 转移方程:

$$ h_t = \tanh(W_h h_{t-1} + W_x x_t + b) $$

最核心的区别在于,\(h_t\) 不再是一个标量,而是一个高维向量。这意味着,一个向量状态 \(h_{t-1}\) 如何影响下一个向量状态 \(h_t\),不可能再用一个简单的数字 \(w\) 来描述了。

于是,我们前面提到的“一维导数”自然升级成了 Jacobian 矩阵:

$$ J_t = \frac{\partial h_t}{\partial h_{t-1}} $$

回顾前面的局部线性化公式,这就意味着:

$$ \Delta h_t \approx J_t \Delta h_{t-1} $$

它回答了这个问题:上一个 hidden state 中的一个小扰动 \(\Delta h_{t-1}\),经过当前时间步的神经网络以后,会怎样被变换传导成新的 hidden state 中的扰动 \(\Delta h_t\)。

在这个过程中,\(W_h\) 负责做隐藏状态间的全局线性映射,而 \(\tanh\) 这种非线性激活函数的导数,则负责在不同分量上对局部扰动进行“自适应的压缩缩放”(比如 \(1-h_t^2\) 项)。它们共同组成了当前这一步独特的局部线性模型 \(J_t\)。

链式法则与 Jacobian 连乘的诞生
#

终于,我们要来看整条时间线上的梯度传播了。考虑信息从 \(t=0\) 一直传播到 \(T\):

RNN 状态转移中的 Jacobian

根据多元复合函数的链式法则,我们要看 \(h_0\) 对 \(h_T\) 的总影响,就把每一步的偏导相乘:

$$ \frac{\partial h_T}{\partial h_0} = \frac{\partial h_T}{\partial h_{T-1}} \frac{\partial h_{T-1}}{\partial h_{T-2}} \cdots \frac{\partial h_1}{\partial h_0} $$

带入前面的定义,我们得到了全文最关键的式子:

$$ \frac{\partial h_T}{\partial h_0} = J_T J_{T-1} \cdots J_1 $$

不要只把它当成一个代数公式。通过上面的铺垫,我们可以非常直觉地解释它的物理意义:

每一个时间步,都在问“一个局部扰动经过这一步以后会被怎样进行线性变换?” 一个时间步由一个 \(J_t\) 描述。 而跨越几十甚至上百个时间步的长距离梯度传播,就是这些局部线性变化规则被依次复合。

RNN 的长期梯度传播,本质上就是大量局部线性变换的连续作用。 这就是 Jacobian 连乘必然出现的根本原因。

重新审视梯度消失与爆炸
#

有了“矩阵连乘就是局部线性变换连续作用”的视角,我们再回头看梯度消失和爆炸。

在标量时代,是因为 \(w\) 这个数字不断乘自己。而在高维向量时代,一个 Jacobian 矩阵 \(J_t\) 可能在某些方向上放大扰动,在另一些方向上缩小扰动。

当连续成百上千个 Jacobian 矩阵 \(J_T J_{T-1} \dots J_1\) 连乘在一起时:

  • 如果模型在大部分传播路径上持续地把扰动“压扁”,使得最终的复合线性变换把任意微小扰动都缩到了接近零向量的位置,那么 \(\text{gradient} \rightarrow 0\),这就是高维世界里的 vanishing gradient
  • 如果这条长链条上存在某些方向被持续不断地拉伸放大,使得最终扰动的模长 \(\|\text{gradient}\| \rightarrow \infty\),就出现了 exploding gradient

结语:从这个视角看 LSTM 和 GRU
#

梳理到这里我们会发现,普通 RNN 面临的真正困难并不单纯是因为 \(\tanh\) 的导数小于 1,而是:长距离的有效信息和梯度,被迫在每一次状态转移时都经历一整套完整的局部线性变换(Jacobian 连乘)。

只要步数足够多,绝大部分方向上的扰动都很难在这么多轮不可控的连乘中幸存下来。

这也自然引出了 LSTM 和 GRU 出现的原因。它们的设计,本质上是在状态传播的路径上引入了更可控的 Gating 机制以及加性路径(additive path)。这使得在很多时间步里,重要信息的 Jacobian 接近于单位矩阵 \(I\)(即扰动原样通过,不放大也不缩小),从而避免了每一步都被强制重新变换和压缩的命运。

相关文章