C++前端中的Autograd¶
创建日期: 2020年4月1日 | 最后更新日期: 2022年9月12日 | 最后验证: 未验证
The autograd 包是构建高度灵活和动态神经网络的基础包。PyTorch Python 前端中的大多数 autograd API 也在 C++ 前端可用,允许轻松地将 autograd 代码从 Python 转换为 C++。
在本教程中,我们将探讨在PyTorch C++前端中进行自动微分的几个示例。 请注意,本教程假设您已经对Python前端中的自动微分有了基本了解。如果不是这样,请先阅读 Autograd:自动微分。
基本自动求导操作¶
(改编自 此教程)
创建一个张量并设置 torch::requires_grad() 来跟踪其计算
auto x = torch::ones({2, 2}, torch::requires_grad());
std::cout << x << std::endl;
Out:
1 1
1 1
[ CPUFloatType{2,2} ]
执行张量操作:
auto y = x + 2;
std::cout << y << std::endl;
Out:
3 3
3 3
[ CPUFloatType{2,2} ]
y 是某个操作的结果,因此它有一个 grad_fn。
std::cout << y.grad_fn()->name() << std::endl;
Out:
AddBackward1
在 y 上执行更多操作
auto z = y * y * 3;
auto out = z.mean();
std::cout << z << std::endl;
std::cout << z.grad_fn()->name() << std::endl;
std::cout << out << std::endl;
std::cout << out.grad_fn()->name() << std::endl;
Out:
27 27
27 27
[ CPUFloatType{2,2} ]
MulBackward1
27
[ CPUFloatType{} ]
MeanBackward0
.requires_grad_( ... ) 在原地更改现有张量的requires_grad标志。
auto a = torch::randn({2, 2});
a = ((a * 3) / (a - 1));
std::cout << a.requires_grad() << std::endl;
a.requires_grad_(true);
std::cout << a.requires_grad() << std::endl;
auto b = (a * a).sum();
std::cout << b.grad_fn()->name() << std::endl;
Out:
false
true
SumBackward0
现在让我们反向传播。因为out包含一个单一标量,out.backward()
等价于out.backward(torch::tensor(1.))。
out.backward();
打印梯度 d(out)/dx
std::cout << x.grad() << std::endl;
Out:
4.5000 4.5000
4.5000 4.5000
[ CPUFloatType{2,2} ]
您应该得到一个 4.5 的矩阵。关于如何得出这个值的解释,请参见 本教程中的相应部分。
现在让我们来看一个向量-雅可比乘积的示例:
x = torch::randn(3, torch::requires_grad());
y = x * 2;
while (y.norm().item<double>() < 1000) {
y = y * 2;
}
std::cout << y << std::endl;
std::cout << y.grad_fn()->name() << std::endl;
Out:
-1021.4020
314.6695
-613.4944
[ CPUFloatType{3} ]
MulBackward1
如果我们想要向量-雅可比乘积,将向量作为参数传递给 backward:
auto v = torch::tensor({0.1, 1.0, 0.0001}, torch::kFloat);
y.backward(v);
std::cout << x.grad() << std::endl;
Out:
102.4000
1024.0000
0.1024
[ CPUFloatType{3} ]
您也可以通过在代码块中放置torch::NoGradGuard来停止对需要梯度的张量进行历史跟踪
std::cout << x.requires_grad() << std::endl;
std::cout << x.pow(2).requires_grad() << std::endl;
{
torch::NoGradGuard no_grad;
std::cout << x.pow(2).requires_grad() << std::endl;
}
Out:
true
true
false
Or by using .detach() 获取一个新的张量,内容相同但不需要梯度:
std::cout << x.requires_grad() << std::endl;
y = x.detach();
std::cout << y.requires_grad() << std::endl;
std::cout << x.eq(y).all().item<bool>() << std::endl;
Out:
true
false
true
有关C++张量自动求导API的更多信息,如grad / requires_grad /
is_leaf / backward / detach / detach_ / register_hook / retain_grad,
请参阅对应的C++ API文档。
在C++中计算高阶梯度¶
One of the 应用之一 是高阶梯度计算梯度惩罚。
让我们看一个使用 torch::autograd::grad 的例子:
#include <torch/torch.h>
auto model = torch::nn::Linear(4, 3);
auto input = torch::randn({3, 4}).requires_grad_(true);
auto output = model(input);
// Calculate loss
auto target = torch::randn({3, 3});
auto loss = torch::nn::MSELoss()(output, target);
// Use norm of gradients as penalty
auto grad_output = torch::ones_like(output);
auto gradient = torch::autograd::grad({output}, {input}, /*grad_outputs=*/{grad_output}, /*create_graph=*/true)[0];
auto gradient_penalty = torch::pow((gradient.norm(2, /*dim=*/1) - 1), 2).mean();
// Add gradient penalty to loss
auto combined_loss = loss + gradient_penalty;
combined_loss.backward();
std::cout << input.grad() << std::endl;
Out:
-0.1042 -0.0638 0.0103 0.0723
-0.2543 -0.1222 0.0071 0.0814
-0.1683 -0.1052 0.0355 0.1024
[ CPUFloatType{3,4} ]
请参阅torch::autograd::backward
的文档(链接)
和 torch::autograd::grad
的文档(链接)
以获取更多关于如何使用它们的信息。
用C++实现自定义自动求导函数¶
(改编自 此教程)
向torch::autograd添加一个新的基本操作需要为每个操作实现一个新的torch::autograd::Function子类。torch::autograd::Function 是 torch::autograd 用来计算结果和梯度,并编码操作历史的类。每一个新函数都需要你实现两个方法:forward 和 backward,请参阅此链接以获取详细的要求。
Below you can find code for a Linear 函数从 torch::nn:
#include <torch/torch.h>
using namespace torch::autograd;
// Inherit from Function
class LinearFunction : public Function<LinearFunction> {
public:
// Note that both forward and backward are static functions
// bias is an optional argument
static torch::Tensor forward(
AutogradContext *ctx, torch::Tensor input, torch::Tensor weight, torch::Tensor bias = torch::Tensor()) {
ctx->save_for_backward({input, weight, bias});
auto output = input.mm(weight.t());
if (bias.defined()) {
output += bias.unsqueeze(0).expand_as(output);
}
return output;
}
static tensor_list backward(AutogradContext *ctx, tensor_list grad_outputs) {
auto saved = ctx->get_saved_variables();
auto input = saved[0];
auto weight = saved[1];
auto bias = saved[2];
auto grad_output = grad_outputs[0];
auto grad_input = grad_output.mm(weight);
auto grad_weight = grad_output.t().mm(input);
auto grad_bias = torch::Tensor();
if (bias.defined()) {
grad_bias = grad_output.sum(0);
}
return {grad_input, grad_weight, grad_bias};
}
};
然后,我们可以这样使用 LinearFunction:
auto x = torch::randn({2, 3}).requires_grad_();
auto weight = torch::randn({4, 3}).requires_grad_();
auto y = LinearFunction::apply(x, weight);
y.sum().backward();
std::cout << x.grad() << std::endl;
std::cout << weight.grad() << std::endl;
Out:
0.5314 1.2807 1.4864
0.5314 1.2807 1.4864
[ CPUFloatType{2,3} ]
3.7608 0.9101 0.0073
3.7608 0.9101 0.0073
3.7608 0.9101 0.0073
3.7608 0.9101 0.0073
[ CPUFloatType{4,3} ]
这里,我们给出一个由非张量参数化的函数的额外示例:
#include <torch/torch.h>
using namespace torch::autograd;
class MulConstant : public Function<MulConstant> {
public:
static torch::Tensor forward(AutogradContext *ctx, torch::Tensor tensor, double constant) {
// ctx is a context object that can be used to stash information
// for backward computation
ctx->saved_data["constant"] = constant;
return tensor * constant;
}
static tensor_list backward(AutogradContext *ctx, tensor_list grad_outputs) {
// We return as many input gradients as there were arguments.
// Gradients of non-tensor arguments to forward must be `torch::Tensor()`.
return {grad_outputs[0] * ctx->saved_data["constant"].toDouble(), torch::Tensor()};
}
};
然后,我们可以这样使用 MulConstant:
auto x = torch::randn({2}).requires_grad_();
auto y = MulConstant::apply(x, 5.5);
y.sum().backward();
std::cout << x.grad() << std::endl;
Out:
5.5000
5.5000
[ CPUFloatType{2} ]
有关torch::autograd::Function的更多信息,请参见
其文档。
将自动微分代码从Python翻译成C++¶
在高层次上,使用 C++ 中的 autograd 最简单的方法是在 Python 中先编写好工作中的 autograd 代码,然后使用以下表格将你的 autograd 代码从 Python 转译为 C++。
Python |
C++ |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
翻译后,大部分您的Python autograd代码在C++中应该可以直接运行。 如果不行,请在GitHub issues提交bug报告,我们会尽快修复。