简单的梯度运算


理论

有矩阵 A=[a1,a2,,am]TRm×n\mathbf{A} = [\mathbf{a}_1, \mathbf{a}_2, \cdots, \mathbf{a}_m]^T \in \mathbb{R}^{m \times n},和向量 x=[x1,x2,,xn]TRn\mathbf{x} = [x_1, x_2, \cdots, x_n]^T \in \mathbb{R}^{n},则有:

Ax=[a1Txa2TxamTx] \mathbf{Ax} = \begin{bmatrix} \mathbf{a}_1^T \mathbf{x} \\ \mathbf{a}_2^T \mathbf{x} \\ \vdots \\ \mathbf{a}_m^T \mathbf{x} \end{bmatrix}

又有:

xaiTx=ai \nabla_{\mathbf{x}} \mathbf{a}_i^T \mathbf{x} = \mathbf{a}_i 1TAx=1,Ax=AT1,x \mathbf{1}^T \mathbf{A} \mathbf{x} = \langle \mathbf{1}, \mathbf{Ax} \rangle = \langle \mathbf{A}^T \mathbf{1}, \mathbf{x} \rangle

所以,

x1TAx=xi=1maiTx=i=1mai=AT1m×1=AT1 \nabla_{\mathbf{x}} \mathbf{1}^T \mathbf{A} \mathbf{x} = \nabla_{\mathbf{x}} \sum_{i=1}^m \mathbf{a}_i^T \mathbf{x} = \sum_{i=1}^m \mathbf{a}_i = \mathbf{A}^T \mathbf{1}_{m \times 1} = \mathbf{A}^T \mathbf{1}

这样,有:

xAx=xAT,x1T=AT \nabla_{\mathbf{x}} \mathbf{Ax} = \nabla_{\mathbf{x}} \langle \mathbf{A}^T, \mathbf{x} \mathbf{1}^T \rangle = \mathbf{A}^T xxA=A \nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{A} = \mathbf{A} xxAx=(A+A)x \nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{A} \mathbf{x} = (\mathbf{A} + \mathbf{A}^\top)\mathbf{x} xx2=xxx=2x \nabla_{\mathbf{x}} \|\mathbf{x} \|^2 = \nabla_{\mathbf{x}} \mathbf{x}^\top \mathbf{x} = 2\mathbf{x} XXF2=2X \nabla_{\mathbf{X}} \|\mathbf{X} \|_F^2 = 2\mathbf{X}

即:

xf(x),g(x)=xf(x),g(x)+f(x),xg(x) \nabla_{\mathbf{x}} \langle f(\mathbf{x}), g(\mathbf{x}) \rangle = \langle \nabla{_\mathbf{x}} f(\mathbf{x}), g(\mathbf{x}) \rangle + \langle f(\mathbf{x}), \nabla_{\mathbf{x}} g(\mathbf{x}) \rangle

下面看一个例子:

一个例子

python
python
from xint import utils
from xint import mxnet as xint

np = xint.np
python
python
from xint import utils
from xint import tensorflow as xint

np = xint.np
python
python
from xint import utils
from xint import torch as xint

np = xint.np

创建张量 x\mathbf{x}

python
python
x = np.arange(4.0).reshape(4, 1)

计算函数 y=2xxy = 2\mathbf{x}^{\top}\mathbf{x} 的梯度:

python
python
# 我们通过调用`attach_grad`来为一个张量的梯度分配内存
x.attach_grad()
# 在我们计算关于`x`的梯度后,我们将能够通过'grad'属性访问它,它的值被初始化为0
x.grad
python
python
x = tf.Variable(x)
python
python
x.requires_grad_(True)  # 等价于 `x = torch.arange(4.0, requires_grad=True)`
x.grad  # 默认值是None

现在让计算 yy

python
python
from mxnet import autograd
# 把代码放到`autograd.record`内,以建立计算图
with autograd.record():
    y = 2 * x.T @ x
float(y)
python
python
# 把所有计算记录在磁带上
with tf.GradientTape() as t:
    y = 2 * tf.transpose(x) @ x
float(y)
python
python
y = 2 * x.T @ x
float(y)

28.0

接下来,我们可以通过调用反向传播函数来自动计算 yy 关于 x\mathbf{x} 每个分量的梯度,并打印这些梯度:

python
python
y.backward()
x.grad
array([[ 0.], [ 4.], [ 8.], [12.]])
python
python
# 把所有计算记录在磁带上
with tf.GradientTape() as t:
    y = 2 * tf.transpose(x) @ x
float(y)
<tf.Tensor: shape=(4, 1), dtype=float64, numpy= array([[ 0.], [ 4.], [ 8.], [12.]])>
python
python
y.backward()
x.grad
tensor([[ 0.], [ 8.], [16.], [24.]])

可以计算 x\mathbf{x} 的另一个函数:

python
python
with autograd.record():
    y = x.sum()
y.backward()
x.grad  # 被新计算的梯度覆盖
array([[1.], [1.], [1.], [1.]])
python
python
with tf.GradientTape() as t:
    y = tf.reduce_sum(x)
t.gradient(y, x)  # 被新计算的梯度覆盖
<tf.Tensor: shape=(4, 1), dtype=float64, numpy= array([[1.], [1.], [1.], [1.]])>
python
python
# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
y = x.sum()
y.backward()
x.grad
tensor([[1.], [1.], [1.], [1.]])

注意:对于非标量变量的反向传播,MXNet/TensorFlow 直接调用相应的函数即可获得梯度,但是 Pytorch 不支持直接对非标量进行反向传播,故而需要先对其求和,再求梯度。比如:

python
python
# 当我们对向量值变量`y`(关于`x`的函数)调用`backward`时,
# 将通过对`y`中的元素求和来创建一个新的标量变量。然后计算这个标量变量相对于`x`的梯度
with autograd.record():
    y = x * x  # `y`是一个向量
y.backward()
x.grad  # 等价于y = sum(x * x)
python
python
with tf.GradientTape() as t:
    y = x * x
t.gradient(y, x)  # 等价于 `y = tf.reduce_sum(x * x)`
python
python
# 对非标量调用`backward`需要传入一个`gradient`参数,该参数指定微分函数关于`self`的梯度。
## 在我们的例子中,我们只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad

分离计算

有时,我们希望将某些计算移动到记录的计算图之外。例如,假设 y\mathbf{y} 是作为 x\mathbf{x} 的函数计算的,而 z\mathbf{z} 则是作为 y\mathbf{y}x\mathbf{x} 的函数计算的。现在,想象一下,我们想计算 z\mathbf{z} 关于 x\mathbf{x} 的梯度,但由于某种原因,我们希望将 y\mathbf{y} 视为一个常数,并且只考虑到 x\mathbf{x}y\mathbf{y} 被计算后发挥的作用。

在这里,我们可以分离 y\mathbf{y} 来返回一个新变量 uu,该变量与 y\mathbf{y} 具有相同的值,但截断计算图中关于如何计算 y\mathbf{y} 的任何信息。换句话说,梯度不会向后流经 uux\mathbf{x}。因此,下面的反向传播函数计算 z=ux\mathbf{z} = u * \mathbf{x} 关于 x\mathbf{x} 的偏导数,同时将 uu 作为常数处理,而不是 z=xxx\mathbf{z} = \mathbf{x} * \mathbf{x} * \mathbf{x} 关于 x\mathbf{x} 的偏导数。

python
python
with autograd.record():
    y = x * x
    u = y.detach()
    z = u * x
z.backward()
x.grad == u
python
python
# 设置 `persistent=True` 来运行 `t.gradient`多次
with tf.GradientTape(persistent=True) as t:
    y = x * x
    u = tf.stop_gradient(y)
    z = u * x

x_grad = t.gradient(z, x)
x_grad == u
python
python
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x

z.sum().backward()
x.grad == u

通用微分函数

f:RnRf: \mathbb{R}^n \rightarrow \mathbb{R}x=[x1,x2,,xn]\mathbf{x} = [x_1, x_2, \ldots, x_n]^\top,有

xf(x)=[f(x)x1,f(x)x2,,f(x)xn] \nabla_{\mathbf{x}} f(\mathbf{x}) = \bigg[\frac{\partial f(\mathbf{x})}{\partial x_1}, \frac{\partial f(\mathbf{x})}{\partial x_2}, \ldots, \frac{\partial f(\mathbf{x})}{\partial x_n}\bigg]^\top

若有 y=[y1,y2,,ym]\mathbf{y} = [y_1, y_2, \ldots, y_m]^\topxRx \in \mathbb{R},则:

yx=[y1x,y2x,,ymx] \frac{\partial \mathbf{y}}{\partial x} = \bigg[\frac{\partial y_1}{\partial x}, \frac{\partial y_2}{\partial x}, \ldots, \frac{\partial y_m}{\partial x}\bigg]^\top

还有,

yx=[y1x,y2x,,ymx]=[y1x1y1x2y1xny2x1y2x2y2xnymx1ymx2ymxn] \frac{\partial \mathbf{y}}{\partial \mathbf{x}} = \bigg[\frac{\partial y_1}{\partial \mathbf{x}}, \frac{\partial y_2}{\partial \mathbf{x}}, \ldots, \frac{\partial y_m}{\partial \mathbf{x}}\bigg]^\top = \begin{bmatrix} \frac{\partial y_1}{\partial x_1} & \frac{\partial y_1}{\partial x_2} &\cdots &\frac{\partial y_1}{\partial x_n} \\ \frac{\partial y_2}{\partial x_1} & \frac{\partial y_2}{\partial x_2} & \cdots & \frac{\partial y_2}{\partial x_n}\\ \vdots & \vdots & \ddots & \vdots \\ \frac{\partial y_m}{\partial x_1} & \frac{\partial y_m}{\partial x_2} & \cdots & \frac{\partial y_m}{\partial x_n} \end{bmatrix}

文章作者: xinetzone
版权声明: 本博客所有文章除特别声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 xinetzone !
评论
0 comments
Anonymous
Markdown is supported

Be the first person to leave a comment!