loss.backward()
隐式地计算出所有参数的梯度。
在用神经网络求解PDE(如PINN)时,需要显式地计算梯度(导数)。这个时候 backward
就派不上用场了,那怎么显式地去计算呢?这就要用到 autograd
的 grad
方法。
神经网络的求导和PDE里的求导一个重要的区别是:神经网络里 backward
是标量对张量求导,并且想得到同形张量;PDE是矢量对矢量求导,导数是矢量,三个矢量形状相同,何解?
说来也简单,我们之前说了,不同维度之间互不干预,所以 J J J 一定是一个对角阵,非对角元素均为零。现在问题就变成了怎么把对角阵的对角元素提取出来,一个简单的想法是右乘一个全1向量:
( ∂ u ∂ x ) wanted = [ ∂ u 1 / ∂ x 1 0 ⋯ 0 0 ∂ u 2 / ∂ x 2 ⋯ 0 ⋮ ⋮ ⋱ ⋮ 0 0 ⋯ ∂ u n / ∂ x n ] [ 1 1 ⋮ 1 ] \left(\frac{\partial\boldsymbol{u}}{\partial\boldsymbol{x}}\right)_\text{wanted}=\begin{bmatrix} \partial u_1/\partial x_1 & 0 & \cdots & 0\\ 0 & \partial u_2/\partial x_2 & \cdots & 0\\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & \partial u_n/\partial x_n \end{bmatrix}\begin{bmatrix} 1 \\ 1\\ \vdots \\ 1 \end{bmatrix} (∂x∂u)wanted= ∂u1/∂x10⋮00∂u2/∂x2⋮0⋯⋯⋱⋯00⋮∂un/∂xn 11⋮1
想到了这一点,就可以看代码了。比如我们要求 y = x 1 2 sin x 2 y=x_1^2\sin x_2 y=x12sinx2 对两个自变量的偏导数。随机采集100个点。
from torch import autograd
import torch
n = 100
x = torch.rand(n, 2, requires_grad=True)
y = x[:, 0] ** 2 * torch.sin(x[:, 1])
grad = autograd.grad(
outputs=y, inputs=x,
grad_outputs=torch.ones_like(y),
)[0]
这里面用到的一个重要方法是 torch.autograd.grad
。我们用 outputs
指定因变量,用 inputs
指定自变量,用 grad_outputs
指定右乘的向量。该函数返回一个元组,通常会是单元素元组,我们取出第0个元素就可以。
这个元素是什么呢?就是 [ ∂ y 1 ∂ x 1 ∂ y 2 ∂ x 2 ] T = [ 2 x 1 sin x 2 x 1 2 cos x 2 ] T \begin{bmatrix} \dfrac{\partial y_1}{\partial x_1} & \dfrac{\partial y_2}{\partial x_2} \end{bmatrix}^T=\begin{bmatrix} 2x_1\sin x_2 & x_1^2\cos x_2 \end{bmatrix}^T [∂x1∂y1∂x2∂y2]T=[2x1sinx2x12cosx2]T。同时我们也要注意,PyTorch里二阶张量不同的维度是用不同的列表示的,所以这边的结果是一个行向量 [ 2 x 1 sin x 2 x 1 2 cos x 2 ] \begin{bmatrix} 2x_1\sin x_2 & x_1^2\cos x_2 \end{bmatrix} [2x1sinx2x12cosx2]。验证一下:
my = torch.hstack((
2 * x[:, [0]] * torch.sin(x[:, [1]]),
x[:, [0]] ** 2 * torch.cos(x[:, [1]])))
print(my.size())
print(my.equal(grad))
torch.Size([100, 2])
True
求二阶导要利用一阶导的结果,并且要求我们在求一阶导时必须指定 create_graph
为 True
。我们让 ∂ y / ∂ x 1 = 2 x 1 sin x 2 \partial y/\partial x_1=2x_1\sin x_2 ∂y/∂x1=2x1sinx2 对 x 1 x_1 x1 和 x 2 x_2 x2 求导。
grad = autograd.grad(
outputs=y, inputs=x,
grad_outputs=torch.ones_like(y),
create_graph=True
)[0] # 一阶导
grad2 = autograd.grad(
outputs=grad[:, 0], inputs=x,
grad_outputs=torch.ones_like(grad[:, 0])
)[0] # 对x_1求完一阶导后,再对x_1和x_2求二阶导
这次求出来的应该是 [ 2 sin x 2 2 x 1 cos x 2 ] \begin{bmatrix} 2\sin x_2 & 2x_1\cos x_2 \end{bmatrix} [2sinx22x1cosx2],验证一下:
my2 = torch.hstack((
2 * torch.sin(x[:, [1]]),
2 * x[:, [0]] * torch.cos(x[:, [1]])
))
print(my2.equal(grad2))
True