LibTorch 支持自动微分, 一般情况下可以调用 backward()
函数直接计算,和 python 操作基本一致。
$$ \begin{aligned} & y = wx+b \ & \frac{\partial y}{\partial w} = x \end{aligned} $$
torch::Tensor x = torch::tensor({2.0});
torch::Tensor w = torch::tensor({3.0}, torch::requires_grad());
torch::Tensor b = torch::tensor({4.0}, torch::requires_grad());
torch::Tensor y = w * x + b;
y.backward();
std::cout << w.grad() << std::endl;
std::cout << b.grad() << std::endl;
注意:只有浮点和复数可以获取梯度。
已经计算的张量也是可以通过修改 required_grad
属性,但获取梯度需要重新执行计算,接着上面的代码:
std::cout << x.requires_grad() << std::endl;
x.requires_grad_(true);
std::cout << x.grad() << std::endl;
y.backward();
std::cout << x.requires_grad() << std::endl;
注意:libtorch 默认不保留计算图,如果想要得到正确的结果需要第一次计算梯度时候设置相关参数,并且清除已有梯度。
雅可比矩阵 $J$ 计算的是向量 $y$ 对于向量 $w$ 的导数,这里假设向量 $w=[w_1, w_2, w_3]$ 是当前某个中间层的权重,$y=[y_1, y_2, y_3]$ 由 $w$ 经过某个可导函数产生。反向传播的时候,实际的梯度向量就是本层的导数 $J$ 与上层的梯度向量 $v$ 的乘积。
对于 $y = x^2+2x$,雅可比矩阵为: $$ J=\left( \begin{array}{ccc} \frac{\partial y{1}}{\partial x{1}} & \frac{\partial y{1}}{\partial x{2}} & \frac{\partial y{1}}{\partial x{3}} \ \frac{\partial y{2}}{\partial x{1}} & \frac{\partial y{2}}{\partial x{2}} & \frac{\partial y{2}}{\partial x{3}} \ \frac{\partial y{3}}{\partial x{1}} & \frac{\partial y{3}}{\partial x{2}} & \frac{\partial y{3}}{\partial x{3}} \end{array} \right) = \left( \begin{array}{ccc} 2 x{1}+2 & 0 & 0 \ 0 & 2 x{2}+2 & 0 \ 0 & 0 & 2 x_{3}+2 \end{array}\right) $$
如果向量是$v=[1, 2, 3]$,那么实际的梯度值为:
$$ vJ=[2 x{1}+2, 4 x{2}+4, 6 x_{3}+6] $$
这个向量其实就可以理解为通过链式法则传递的上一层的梯度值,也可以理解为在投影方向,实现的时候传入 backward()
函数中即可。在 python 也有同样的特性,但一般各种运算算子一般都是封装好的,所以很少用到。查看下面的代码:
torch::Tensor xx = torch::randn({3}, torch::requires_grad());
torch::Tensor yy = xx * xx + 2 * xx;
yy.backward(torch::tensor({1, 2, 3}));