标量函数线性化的不同形式
如果没有特别说明,本文中提到的函数默认为输出是实数标量的函数。
1. 单变量函数的导数
学习微积分时最先接触的是单变量函数,比如 \( f(x)=x^2 \),其导数是 \( f'(x)=2x \)。
之后我们会发展出许多基础函数的导数,比如 \( \sin'(x) = \cos(x) \) 等,并且构建出函数组合下的求导规则,包括加法,乘法,除法,链式法则,最终形成了一个完整的符号求导抽象层,最终几乎能对任何复杂的函数求导。
1.1. 物理解释:瞬时变化率
物理上,如果 f(x) 中的 x 表示时间,即写成 f(t) ,那么导数的意义是瞬时变化率,这个解释下更容易写出数学化后的导数定义:
\[ f'(t) = \lim_{\Delta t\to 0} \frac{f(t+\Delta t)-f(t)}{\Delta t} \]
1.2. 几何解释:切线
如果把 y=f(x) 绘制在笛卡尔坐标系下,那么 f'(x) 在几何方面表示的是 y=f(x) 图像在给定点的切线的斜率,这使得我们可以用这种性质去做很多事情,比如线性近似:
在点 x0 附近,函数值 f(x) 可以用该点切线去近似 \( f(x) \approx f(x_0) + f'(x_0)(x - x_0) \)。
注意为了能保持近似 x 需要控制在和 \( x_0 \) 很近的范围,但这个范围是多少需要依据实际问题需求而定。
基于这种切线近似的思路可以发展出函数求根的牛顿法迭代算法。
值得一提的是,用导数作为切线的斜率实际是一种定义,而不是逻辑推导的结果,因为切线这个概念几乎只是为初等几何里的“圆”量身定制的,它表示的是和圆只有一个交点的直线,但这个定义无法扩展到一般曲线,比如 \( y=x^3 \) 在 x=0 点处的任何直线必然和曲线有一个交点,但我们还是可以用导数来谈论它的切线,而这种导数定义的切线本身是和圆的切线的经典定义相容,而在数学上又是唯一的,可以扩展到所有可微函数上。
现实中我们并不需要这种经典的“和曲线只有一个接触点”的语义来定义或解决现实问题,“作圆的切线”更多是数学练习或者解决和圆有关问题的特殊场景下辅助手段,实际中关注的是函数的变化率和局部线性近似(见后文)。线性化(一种简化问题的手段)才是真正的核心,而不是“切”(恰好接触)的语义概念。
1.3. 优化属性
根据几何图像,切线斜率为正说明往 x 大的方向函数值增大,往 x 小的方向则减小求极值,因此如果它提供了函数极值的相对该点的方向,于是可以发展出迭代优化算法。
注意考虑 \( f(x)=x^2 \) 初始为 x=2 ,用导数信息去迭代寻求最小值算法,我们计算导数 f'(x)=2x, 代入初始点导数为 f'(2)=4, 它表明如果增大 x 那么 f(x) 也会增大,于是我们应该减小 x ,此时可以用一个很小的步长 \( \eta \) 去乘以 \( -f'(x) \) 并更新 \( x=x-\eta f'(x) \)
可以说,我们用 f'(x) 的符号 sign(f'(x)) 来指示优化的方向,和 f'(x) 的具体大小值无关。
1.4. 线性化形式
继续在 f(x) 切线近似 \( f(x) \approx f(x_0) + f'(x_0)(x - x_0) \) 的基础上,如果 x 非常接近 \( x_0 \) 以至于接近到某种无穷小量,或者更实际地,比如问题关注的需求精度或者计算机能表示的最小浮点数还小时,定义 \( df=f(x)-f(x_0) \)(即函数在该区间上的实际增量,但在足够小下称为微分)以及 \( dx = x - x_0 \) 表示自变量的增量。
由于足够“局部”,此时可以把这个约等于变成等于: \[ df = f'(x_0) \, dx \]
导数被解释成对自变量微分 dx 的一个变换,它回答的问题是:在输入端施加一个足够小的扰动,会导致输出端多少的变化?
而回答是,以导数 \( f'(x_0) \) 的倍数去缩放。
2. 双变量函数的线性化
2.1. 瞬时变化率:方向导数
当函数的输入是两个变量比如 z=f(x,y) 时,此时没法用纯粹的“瞬时变化率”来解释了,因为 x,y 构成了一个平面,而“瞬时变化率”蕴含着一个特定的方向,于是我们只能说在 x,y 平面上某个方向函数值 z 的瞬时变化率。
比如在 x 方向(即 \( \vec{u}= 1 \hat{i}+0 \hat{j} \))的瞬时变化率是偏导 fx
\[ \frac{\partial f}{\partial x} = \lim_{\Delta x\to 0} \frac{f(x+\Delta x, y)-f(x,y)}{\Delta x} \]
类似地可以得到 y 方向的偏导 fy。
而任意方向可以用向量 \( \vec{u} = a\hat{i} + b\hat{j} \) ,这里我们约定 \( a^2+b^2=1 \),即 u 是单位向量,这样函数在 u 方向变化量为 \( \Delta t \) ,对应的输出是 f(x,y) 移动到了 \( f(x+a \Delta t, y+b \Delta t) \), 这样 f(x,y) 在 u 方向的导数就定义为:
\[ D_{u}f = \lim_{\Delta t \to 0} \frac{f(x+a\Delta t, y+b \Delta t) - f(x, y)}{\Delta t} \]
这种添加了“方向条件”下的导数定义在“瞬时变化率”解释上和单变量函数的导数解释是一致的。
而如果函数可微,可以在分母引入一个中间量:
\[ D_{u}f = \lim_{\Delta t \to 0} \frac{f(x+a\Delta t, y+b \Delta t) - f(x, y+b \Delta t) + f(x, y+b \Delta t) - f(x, y)}{\Delta t} \]
极限部分拆成两个独立的极限,分别对应 x 方向的净变化与 y 方向的净变化:
\[ D_{u}f = \lim_{\Delta t \to 0} \frac{f(x+a\Delta t, y+b\Delta t) - f(x, y+b\Delta t)}{\Delta t} + \lim_{\Delta t \to 0} \frac{f(x, y+b\Delta t) - f(x, y)}{\Delta t} \]
先看第二个极限,它完全是固定 x 不动,只让 y 从 y 变化到 \( y+b \Delta t \)。令 \( \Delta y = b \Delta t \),则分母的 \( \Delta t \) 就等于 \( \Delta y/b \),于是这一项写成:
\[ b \cdot \lim_{\Delta y \to 0} \frac{f(x, y+\Delta y) - f(x, y)}{\Delta y} \]
而这就是 \( b \cdot f_y \) 。
再看第一个极限,它的分子是在 x 方向上的增量,只是此时 y 的坐标被临时固定在了 y+bΔt 位置上。令 Δx = aΔt,则这一项写成:
\[ a \cdot \lim_{\Delta x \to 0} \frac{f(x+\Delta x, y+b\Delta t) - f(x, y+b\Delta t)}{\Delta x} \]
虽然分子里出现的中间坐标是 y+bΔt 而不是 y,但关键在于极限过程:当 Δt 趋于 0 时,bΔt 也必然趋于 0,所以这个临时坐标 y+bΔt 会自然而然地被“挤”回到 y。因此这个极限值就是偏导数 \( f_x \),第一项的结果就是 \( a \cdot f_x \)。
把两部分合并起来就得到了方向导数基于偏导数的定义 \( D_{u}f = a f_x + b f_y \)。
这个式子体现了一种抽象层的还原,即对于二维输入函数 f(x,y), 给定某个单位向量 u 表示方向,在该方向的导数可以规约成 x 和 y 方向偏导数的线性组合,而偏导数完全是单变量函数求导问题,因此当建立起了单变量求导函数库和规则后,任何方向导数的计算问题都可以规约到普通单变量求导问题中。
2.2. 几何解释:切平面
单变量函数导数的几何解释是切线斜率,而双变量函数中与之对应的是特定方向的切线的斜率,如果函数在该点光滑可微,这些切线实际都在一个平面上,这个平面就是切平面。
对此进行简单证明,根据前文方向导数的定义,给定任何一个单位向量 (a,b) 表示方向,可以到该点在该方向上变化率为 \( af_x+bf_y \) ,这意味着该方向的切线的方向可以用 \( (a, b, af_x+bf_y) \) 向量来表示。
所以我们得到了一族直线,它们都经过点 (x,y,f(x,y)) ,方向则被 \( \vec{v} = (a, b, af_x+bf_y) \) 定义。
但如果我们引入一个新的向量 \( \vec{n} = (f_x, f_y, -1) \) 会发现 \( \vec{n} \cdot \vec{v} = 0 \) ,也就是所有经过改点的直线都和向量 \( \vec{n} \) 正交。
这表明各个方向的切线都在同一个以 \( \vec{n} \) 为法向量的平面上(考虑到这些切线都经过同一个点)。
因此我们用 \( (f_x,f_y, -1) \) 法向量以及当前点 (x,y,f(x,y)) 就能刻画所有切线,这个法向量和点组成了切平面的数学表征。
用法向量的语言重新表述单变量场景下的切线:对于曲线 y = f(x) ,一般用斜率 \( f'(x_0) \) 来描述切线,即切线方程为 \( y - f(x_0) = f'(x_0)(x - x_0) \)。但切线的方向向量可以写为 \( \vec{v} = (1, f'(x_0)) \),因为当 x 增加 1 个单位时,y 沿着切线增加 \( f'(x_0) \) 个单位。
于是可以找一个向量 \( \vec{n} = (f'(x_0), -1) \),计算点积:
\[ \vec{n} \cdot \vec{v} = f'(x_0) \cdot 1 + (-1) \cdot f'(x_0) = 0 \]
这说明 \( \vec{n} \) 与切线方向垂直,即 \( \vec{n} \) 是切线的法向量。
作为对比:双变量的切平面法向量是 \( (f_x, f_y, -1) \),单变量函数的切线法向量是 \( (f'(x_0), -1) \),后者是前者的“降维版本”——去掉 y 方向的偏导分量。而由于其中 -1 是常数,当我们在单变量函数中谈论导数 \( f'(x_0) \) 时,在双变量函数中谈论的对应物应该是 \( (f_x, f_y) \)
2.3. 优化属性:梯度
由于方向导数是 \( D_{u} f = a f_x + b f_y \) , (a,b) 是单位向量,那么这个结果在几何上解释就是 \( (f_x, f_y) \) 在 (a,b) 方向上的投影的长度,而从几何上就可以看出,只有 (a,b) 方向和 \( (f_x,f_y) \) 一致的时候这个投影最大,即等于 \( (f_x,f_y) \) 模长,这是一个正数,也就是它是函数增长最大的反向。
这意味着在 \( (f_x,f_y) \) 方向上,函数的变化率是最大的,此时我们定义这个方向为梯度,用 \( \nabla f \) 表示。
注意 \( (f_x,f_y) \) 在多个地方出现,首先是方向导数中,它作为基,或者作为一种内积运算的对象,用来把方向转成变化率;在切面中作为法向量的部分分量;而在梯度中,指示的增长最快的方向。虽然三者都涉及偏导,但意义是不同的。
在寻找最值过程中,我们可以找到梯度方向,然后移动一小步向上爬升(寻找极大值),或者反方向移动向下移动寻找极小值。
和单变量函数优化场景类似,我们需要的只是梯度所指示的方向,而不需要它的模长。甚至也不需要严格按照梯度方向去更新,因为它指示的是变化率最大的方向,如果找最大值,只需要保证每次函数值都上升,而更新的方向与梯度方向的夹角为锐角即可满足,因为方向导数 \( \nabla f \cdot \hat{u} = \mid\nabla f\mid \cos(\theta) \) 表明,只要夹角 θ 小于 90 度,方向导数就为正,函数值就会上升。
所以我们甚至可以只朝着 \( (sign(f_x), sign(f_y)) \) 方向小步更新,即只关注各个偏导的符号,这和值关注单变量函数导数的符号进行更新是一致的。
换句话说,梯度方向是上升最快的方向,但绝不是唯一的上升方向——凡是和梯度指向同一半平面内的方向,都能让函数值有所增加,只是增加的快慢不同罢了。
这里只是稍微体积,比如 Kronecker 乘积,它是否有梯度性质呢?
2.4. 线性化形式
回顾单变量函数的线性化,给定一个极小的扰动 dx(不需要假设无穷小量,而是假设在人类可接受精度边缘的扰动),那么输出端对扰动的响应是 \( df=f'(x)dx \)
这里线性化和导数的区别在于,我们不是关心变化率,而是关心具体的某个微小扰动产生的绝对变化。
因此原本导数可以看作是 \( \frac{\Delta f}{\Delta x} \) 的极限,或者写成 \( \frac{df}{dx} \), 线性化则只关心 \( df \) 和 \( dx \) 的函数关系。
之所以称为线性化,是因为扰动的高阶的部分如 \( \frac{1}{2}f''(x)dx^2 \) 中 \( dx^2 \) 已经比需求精度还要低,不影响实际用途了,应用中完全可以忽略,只有关于误差的线性缩放部分精度仍然在人类要求的边缘,因此被保留。
对于二阶,目前我们通过“变化率”的解释去定义出了方向导数:
\[ D_{u}f = \lim_{\Delta t \to 0} \frac{f(x+a\Delta t, y+b \Delta t) - f(x, y)}{\Delta t} \]
而如果关注微小扰动的绝对影响,那么对应的数学表达是:
\[ df_u = \lim_{\Delta t \to 0} \Delta t D_{u}f = \lim_{\Delta t \to 0} f(x+a\Delta t, y+b \Delta t) - f(x, y) \]
用和求方向导数类似的技巧拆分:先在 x 方向上单独制造一个变化,再在 y 方向上补上剩余的变化:
\[ \Delta f = f(x+a\Delta t, y+b\Delta t) - f(x, y+b\Delta t) + f(x, y+b\Delta t) - f(x, y) \]
令 \( dx = a\Delta t \),第一项在极限下是 \( f_x \cdot dx \),令 \( dy = b\Delta t \),第二项在极限下等于 \( f_y \cdot dy \)
将两部分加总,得到:
\[ df = f_x dx + f_y dy \]
注意这里的 \( dx = a\Delta t \) 和 \( dy = b\Delta t \) 不再是抽象的单位方向分量 (a,b),而是自变量空间中实际施加在 x 和 y 两个坐标轴上的具体扰动量,也就是说 (dx,dy) 里已经蕴含了方向,它是带方向的极小变化。
如果我们把输入 (x,y) 向量化为 \( \vec{x} \), 那么 \( (dx,dy) \) 就可以记为 \( d\vec{x} \), 由于有 dx 和 dy 两个变化方向,所有的微小扰动是在 dx 和 dy 构成的平面里,就像是由标准线性空间的 x 和 y 轴张成的平面一样,这种输入端微小扰动所在的空间称为切空间。
那么双变量函数的线性化数学化表达为 \( df = \nabla f \cdot d\vec{x} \), 这和单变量的 \( df = f'(x)dx \) 形式上是统一的,梯度对应了导数,梯度的优化意义和导数的优化意义对应了起来,梯度作为切平面方向量的分量和导数作为切线法向量分量的意义也对应了起来。
但关键区分在于, \( \nabla f \) 不是变化率,但 \( f'(x) \) 是变化率。
另外,在二维情形下,输出端的变化量是通过向量内积给出的,因此无法直接写成 \( \frac{df}{d\vec{x}} = \nabla f \) ,若要恢复“变化率”的形式,必须引入一个中间变量 dt 并指定具体的单位方向 \( \hat{u} \),使得沿该方向的变化率为 \( \frac{df}{dt}\big|_u = \nabla f \cdot \hat{u} \)。
因此在从单变量升到双变量输入后,导数 f'(x) 的三种解释(物理变化率,切线,对扰动关系的线性化)中的第一种在代数形式上并没有得到自然扩充(必须依赖特定方向),但切平面和线性化是被保留了。
而这种通用性或者一般性是数学所偏爱的,从这个角度看,导数的变化率解释只是一维输入下对函数线性化得到的特例,我们正好能得到一个缩放因子。这也使得在单变量函数求导中把导数看作两个微分的除法 \( \frac{df}{dx} \) 并不会有什么问题。
但在二维中,无法把方向导数写成 \( \frac{df}{d\vec{x}} \), 这个表达式也不能说是梯度,因为标量和向量的除法本身就不是良好定义的,用编程语言的话来说,这是一种类型错误。
因此最标准的方式就是不去写成变化率的除法形式,而只是展示 df 和 dx 的函数关系: \( df = \nabla f \cdot d\vec{x} \) 。
此时 \( \nabla f \) 被解释为对切空间里扰动的映射,就像 f'(x) 是对单变量扰动 dx 的映射一样。
3. 多变量函数的线性化
如果扩展到包含 n 个输入的函数,那么我们还是可以把这些变量收集起来写成一个向量,写成 \( \vec{x} \) ,而它的扰动就是 \( d\vec{x} \) ,根据一般方向导数的极限形式,仍然能够得定义出方向导数是 \( D_uf = \nabla f \cdot \vec{u} \) 。
类似地,线性化形式能写成 \( df = \nabla f \cdot d\vec{x} \), 即输入变量的扩展不影响线性化的高层表示,输出扰动始终是梯度和切空间里扰动向量做内积的形式。
在几何上,切平面扩展到了切超平面,梯度是超平面法向量的部分分量,而且根据内积的性质,梯度方向仍然是变化量增加最多的方向。
所以,实际中的核心问题是如何高效地求出梯度 \( \nabla f \) ,我们当然可以每次将其他变量都固定住,然后针对特定变量求偏导,在输入只有少数变量情况下,这确实是标准做法。
但当输入非常多,函数很复杂,而且人们本身会主动去控制复杂度对函数输入分组并结构化,比如 \( f(x,A) = x^TAx \) , 一部分参数被打包在向量 x 中,另一部分被打包在矩阵 A 中。
问题在于如何利用这种已经设计好的结构化函数进行结构化地求高效求梯度。
3.1. 输入变量模块化后的线性化
考虑例子 \( z = x^T y \) , x 和 y 都是 n 维的向量,如果把输入看作集合对每个 \( x_i,y_i \) 求偏导,会得到对 \( x_i \) 的偏导是 \( y_i \) ,对 \( y_i \) 的偏导则是 \( x_i \) ,因此综合起来看, \( z_x = y \) 而 \( z_y = x \), 即我们可以对整个向量来谈论梯度(这里梯度始终看作列向量)。
考虑直接对 z 线性化,即同时给所有输入一个在需求可接受的最小精度边缘的扰动,这个扰动也能被函数结构拆分成 dx 和 dy, 于是 \( z'=(x+dx)^T(y+dy) \) ,展开得到:
\[ z' = x^T y + x^T dy + dx^T y + dx^T dy \]
第一项 \( x^T y \) 是原来的输出值 \( z \),左右两边同时减去 z,左边就是值域的变化量:
\[ dz = x^T dy + dx^T y + dx^T dy \]
最后一项 \( dx^T dy \) 是两个一阶小量的乘积,它是每个 \( \sum dx_i dy_i \) 而每个 \( dx_i \) 和 \( dy_i \) 都在需求可接受的精度边缘,比如假设计算机最小能支持的是 \( 10^{-10} \) 次方进精度,那么每个 \( dx_i dy_i \) 都是 \( 10^{-20} \) ,直接被 round 到 0 了,因此不对结果产生任何影响,于是被忽略,得到关于 dx 和 dy 的线性部分:
\[ dz = x^T dy + dx^T y \]
由于 \( dx^Ty \) 是标量,于是它等于 \( y^Tdx \) ,于是式子可以写成:
\[ dz = y^T dx + x^T dy \]
如果部分输入是常数,比如 y 向量里所有输入是常数,那么 \( dz=y^T dx \), 即我们得到了 z 对 x 的梯度是 y 向量,而梯度是 \( y^T \), 这里我们约定梯度是直接可以直接和微分 dx 采样矩阵乘法的向量,那么就是 \( y^T \) ,而梯度则始终是列向量。
同理,如果 x 向量里所有输入是常数,那么可以看出 z 对 y 的梯度是 x, 偏导则是 \( x^T \)
这和 x 和 y 都是标量的情况形式上是一样的。
如果把 x 和 y 看作一个完整的参数列向量 \(\theta = \begin{bmatrix} x \\ y \end{bmatrix}\),那么对应的扰动就是 \( d\theta = \begin{bmatrix} dx \\ dy \end{bmatrix} \)。此时线性化式子 \( dz = y^T dx + x^T dy \) 可以重新写成:
\[ dz = \begin{bmatrix} y \\ x \end{bmatrix}^T \begin{bmatrix} dx \\ dy \end{bmatrix} \]
也就是说,完整的梯度向量是 \( \nabla_{\theta} z = \begin{bmatrix} y \\ x \end{bmatrix} \)。这个向量把原本对 x 的梯度 y 和对 y 的梯度 x 上下拼接在一起,形成了一个长度为 2n 的列向量。
这种写法主要是为了用例子说明,无论输入参数有多少、如何分组,只要把它们堆成一个列向量,线性化形式始终保持 \( dz = \nabla z \cdot d\theta \)。所有的结构化信息都被压缩进了这个列向量的排列方式里,而线性化公式的结构本身不会被破坏。
但现实中,我们已经从 \( dz = y^T dx + x^T dy \) 里读到了各个模块化后输入参数的梯度了,因此并不在意最终是否是一个完整梯度和输入参数微分的内积。
再看一个更复杂的例子 \( f(x,A)=x^TAx \) ,代入扰动后:
\[ f(x+dx, A+dA) = (x+dx)^T (A+dA) (x+dx) \]
展开,保留一阶项。我们按来源逐项写:
\[ = x^T A x + x^T A dx + x^T dA x + dx^T A x + \dots \]
第一项 \( x^T A x \) 是原函数值。省略号表示二阶项(包括 \( dx^T A dx \)、\( dx^T dA x \)、\( x^T dA dx \) 等,这些项中一定会包括两个极小量的乘积,而由于两个极小量都已经在精度边缘,因此相乘就被丢弃了),线性部分就是后面三项:
\[ df = x^T A dx + x^T dA x + dx^T A x \]
有两项包含 \( dx \):\( x^T A dx \) 和 \( dx^T A x \) ,因为两者都是标量,互为转置,因此可以对第二个转置后合并:
\[ x^T A dx + (A x)^T dx = \big( x^T A + (A x)^T \big) dx = \big( A^T x + A x \big)^T dx \]
所以 \( dx \) 的系数是 \( (A + A^T)x \),即 \( \nabla_x f = (A + A^T)x \)。
再看 dA 相关的项 \( x^T dA x\) 。注意 dA 是一个矩阵,而 \( x^T dA x \) 是标量,为了读出梯度(与 dA 做内积得到这个标量),需要把表达式改写为内积形式。
这里要引入矩阵内积的标准定义 \( \langle U, V \rangle = \text{tr}(U^T V) \) ,它实际就是把 U 和 V 拉成向量后进行向量内积在矩阵乘法上的一种表示。
再利用 tr(ABC)=tr(CAB) 性质有:
\[ x^T dA x = \text{tr}(x^T dA x) = \text{tr}(x x^T dA) = \langle x x^T, dA \rangle \]
因此 \( \nabla_A f = x x^T \),它是一个 n×n 矩阵,与 dA 同形状。
最终 \( df = (A^Tx + Ax)^Tdx + tr(x x^TdA) \)
如果把输入变量看作一个集合,把 A 看作常数矩阵,那么 \( f(x) = x^T A x \) 本质上就是一个关于 n 个输入分量 \( x_1, x_2, \ldots, x_n \) 的普通多元函数,只不过这些分量通过矩阵 A 耦合在一起而已。如果硬要展开,它就是 \( \sum_{i,j} A_{ij} x_i x_j \),对每个 \( x_k \) 求偏导,得到 \( \partial f/\partial x_k = \sum_j (A_{kj} + A_{jk}) x_j \),然后把这些偏导堆成一个向量,就得到了梯度 \( \nabla_x f = (A + A^T)x \)。这和以上线性化结果一致,但在线性化操作中,输入的结构始终被运算结构维持着,不需要先手动去掉结构,然后再组装回去。
如果把 A 看作变量, x 为常向量,那么就是 \( f(A) = x^T A x \) 对 A 求梯度。从集合视角看,它就是一个 n^2 元函数,每个矩阵元素 \( A_{ij} \) 都是一个输入分量。求偏导时,固定其他元素不动,只让 \( A_{ij} \) 变化,得到 \( \frac{\partial f}{\partial A_{ij}} = x_i x_j \),然后把这些偏导按行和列排成一个矩阵,就得到了梯度矩阵 \( x x^T \)。这和以上线性化结果一致,但线性化操作中始终保持了模块化信息。
4. 反向传播和雅克比矩阵
回顾前文,我们通过不断增加函数的参数个数,得到了“求导”的一个最为一般的形式,即对函数在局部点的线性化。
如果输入是 \( \mathbb{R}^n \) 的向量那么给定一个单位方向向量 \( \hat{u} \), 通过极限定义可以得到方向导数是 \( \nabla f \cdot \hat{u} \) 形式,而如果不关注特定方向变化率,而是把各个方向绝对变化量编码到 \( d\vec{x} \) 里,则可以得到线性化一般公式
\( df = \nabla f \cdot d\vec{x} \)
这在理论上统一了函数导数定义,那么实践中问题就变成了如何高效计算 \( \nabla f \)
函数 f 往往是模块化的,它的输入(或者参数)被人为分组并放置在不同的计算点上,因此计算 \( \nabla f \) 的不同部分会有不同的复杂度,比如神经网络最后一层假设是对所有特征加权并求和,即 \( \sum w_i h_i \), 那么对 \( w_i \) 的求梯度就是 \( h_i \) ,而第一层的参数参与了整个网络的层层计算,梯度必然和各层的机制都有关系,计算起来更为复杂。
因此虽然理论上一个 \( \nabla f \) 就包含了整个梯度,但各个局部分量是基于不同的计算复杂性的。
前文介绍的一般的线性化方法,比如 \( f(x,A) = x^TAx \) 线性化后是 \( df = (A^Tx + Ax)^Tdx + tr(x x^TdA) \)
这并不是一个高效的方式,为了得到这个公式必须对整个表达式进行符号展开,如果函数套用了 100 层,那么分离出高阶项只留下线性项并整理分组几乎不太可能。但它能展示出函数正向求值中各个分组决策,如 x 和 A 是如何使得梯度也分组的。
而现实中计算则是充分利用链式法则,每次只计算局部的梯度,并且充分复用已经计算的结果来进行梯度计算。