C++ 前端中的 Autograd
autograd
包在 PyTorch 中对于构建高度灵活和动态的神经网络至关重要。PyTorch Python 前端中的大多数 autograd API 在 C++ 前端中同样可用,这使得将 autograd 代码从 Python 迁移到 C++ 变得容易。
在本教程中,我们将探索几个在 PyTorch C++ 前端中执行 autograd 的示例。请注意,本教程假设您已经对 Python 前端中的 autograd 有了基本的了解。如果情况并非如此,请先阅读 Autograd: 自动求导。
基本 autograd 操作
(改编自本教程)
创建一个张量并设置 torch::requires_grad()
以跟踪其计算过程
autox=torch::ones({2,2},torch::requires_grad());
std::cout<<x<<std::endl;
输出:
11
11
[CPUFloatType{2,2}]
执行张量操作:
autoy=x+2;
std::cout<<y<<std::endl;
Out:
33
33
[CPUFloatType{2,2}]
y
是由某个操作生成的,因此它有一个 grad_fn
。
std::cout<<y.grad_fn()->name()<<std::endl;
输出:
AddBackward1
对 y
进行更多操作
autoz=y*y*3;
autoout=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;
输出:
2727
2727
[CPUFloatType{2,2}]
MulBackward1
27
[CPUFloatType{}]
MeanBackward0
.requires_grad_( ... )
会就地修改现有张量的 requires_grad
标志。
autoa=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;
autob=(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;
输出:
4.50004.5000
4.50004.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
方法:
autov=torch::tensor({0.1,1.0,0.0001},torch::kFloat);
y.backward(v);
std::cout<<x.grad()<<std::endl;
输出:
102.4000
1024.0000
0.1024
[CPUFloatType{3}]
您还可以通过将torch::NoGradGuard
放入代码块中,停止autograd跟踪需要梯度的张量的历史记录。
std::cout<<x.requires_grad()<<std::endl;
std::cout<<x.pow(2).requires_grad()<<std::endl;
{
torch::NoGradGuardno_grad;
std::cout<<x.pow(2).requires_grad()<<std::endl;
}
Out:
true
true
false
或者使用 .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;
输出:
true
false
true
有关 grad
/ requires_grad
/ is_leaf
/ backward
/ detach
/ detach_
/ register_hook
/ retain_grad
等 C++ 张量自动求导 API 的更多信息,请参阅 相应的 C++ API 文档。
在 C++ 中计算高阶梯度
高阶梯度的一个应用是计算梯度惩罚。让我们通过 torch::autograd::grad
来看一个示例:
#include<torch/torch.h>
automodel=torch::nn::Linear(4,3);
autoinput=torch::randn({3,4}).requires_grad_(true);
autooutput=model(input);
// Calculate loss
autotarget=torch::randn({3,3});
autoloss=torch::nn::MSELoss()(output,target);
// Use norm of gradients as penalty
autograd_output=torch::ones_like(output);
autogradient=torch::autograd::grad({output},{input},/*grad_outputs=*/{grad_output},/*create_graph=*/true)[0];
autogradient_penalty=torch::pow((gradient.norm(2,/*dim=*/1)-1),2).mean();
// Add gradient penalty to loss
autocombined_loss=loss+gradient_penalty;
combined_loss.backward();
std::cout<<input.grad()<<std::endl;
Out:
*0.1042-0.06380.01030.0723
*0.2543-0.12220.00710.0814
*0.1683-0.10520.03550.1024
[CPUFloatType{3,4}]
请参阅 torch::autograd::backward
(链接) 和 torch::autograd::grad
(链接) 的文档,了解如何使用它们。
在 C++ 中使用自定义自动求导函数
(改编自本教程)
向torch::autograd
添加新的基本操作需要为每个操作实现一个新的torch::autograd::Function
子类。torch::autograd::Function
是torch::autograd
用于计算结果和梯度的工具,并编码操作历史。每个新函数都需要实现两个方法:forward
和backward
,详细要求请参见此链接。
以下您可以找到来自torch::nn
的Linear
函数的代码:
#include<torch/torch.h>
usingnamespacetorch::autograd;
// Inherit from Function
classLinearFunction:publicFunction<LinearFunction>{
public:
// Note that both forward and backward are static functions
// bias is an optional argument
statictorch::Tensorforward(
AutogradContext*ctx,torch::Tensorinput,torch::Tensorweight,torch::Tensorbias=torch::Tensor()){
ctx->save_for_backward({input,weight,bias});
autooutput=input.mm(weight.t());
if(bias.defined()){
output+=bias.unsqueeze(0).expand_as(output);
}
returnoutput;
}
statictensor_listbackward(AutogradContext*ctx,tensor_listgrad_outputs){
autosaved=ctx->get_saved_variables();
autoinput=saved[0];
autoweight=saved[1];
autobias=saved[2];
autograd_output=grad_outputs[0];
autograd_input=grad_output.mm(weight);
autograd_weight=grad_output.t().mm(input);
autograd_bias=torch::Tensor();
if(bias.defined()){
grad_bias=grad_output.sum(0);
}
return{grad_input,grad_weight,grad_bias};
}
};
然后,我们可以按以下方式使用 LinearFunction
:
autox=torch::randn({2,3}).requires_grad_();
autoweight=torch::randn({4,3}).requires_grad_();
autoy=LinearFunction::apply(x,weight);
y.sum().backward();
std::cout<<x.grad()<<std::endl;
std::cout<<weight.grad()<<std::endl;
Out:
0.53141.28071.4864
0.53141.28071.4864
[CPUFloatType{2,3}]
3.76080.91010.0073
3.76080.91010.0073
3.76080.91010.0073
3.76080.91010.0073
[CPUFloatType{4,3}]
这里,我们提供了一个额外示例,展示了一个由非张量参数化定义的函数:
#include<torch/torch.h>
usingnamespacetorch::autograd;
classMulConstant:publicFunction<MulConstant>{
public:
statictorch::Tensorforward(AutogradContext*ctx,torch::Tensortensor,doubleconstant){
// ctx is a context object that can be used to stash information
// for backward computation
ctx->saved_data["constant"]=constant;
returntensor*constant;
}
statictensor_listbackward(AutogradContext*ctx,tensor_listgrad_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
:
autox=torch::randn({2}).requires_grad_();
autoy=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++ |
---|---|
torch.autograd.backward |
torch::autograd::backward (link) |
torch.autograd.grad |
torch::autograd::grad (link) |
torch.Tensor.detach |
torch::Tensor::detach ( |
```[link](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor6detachEv)) |
| `torch.Tensor.detach_` | `torch::Tensor::detach_`([link](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor7detach_Ev)) |
| `torch.Tensor.backward` | `torch::Tensor::backward`([link](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor8backwardERK6Tensorbb)) |
| `torch.Tensor.register_hook` | `torch::Tensor::register_hook`([link](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4I0ENK2at6Tensor13register_hookE18hook_return_void_tI1TERR1T)) |
| `torch.Tensor.requires_grad` | `torch::Tensor::requires_grad_``torch::Tensor::requires_grad_`
(
[link](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor14requires_grad_Eb)[link](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor14requires_grad_Eb)) |
| `torch.Tensor.retain_grad` | `torch::Tensor::retain_grad`([link](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor11retain_gradEv)) |
| `torch.Tensor.grad` | `torch::Tensor::grad`([link](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor4gradEv)) |
| `torch.Tensor.grad_fn` | `torch::Tensor::grad_fn`([link](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor7grad_fnEv)) |
| `torch.Tensor.set_data` | `torch::Tensor::set_data``torch::Tensor::set_data`[link](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor8set_dataERK6Tensor)) |
| `torch.Tensor.data` | `torch::Tensor::data`([link](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor4dataEv)) |
| `torch.Tensor.output_nr` | `torch::Tensor::output_nr`([link](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor9output_nrEv)) |
| `torch.Tensor.is_leaf` | `torch::Tensor::is_leaf`([link](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor7is_leafEv))
(注意:此处是一个闭合的括号,通常用于表示代码块或链接的结束,无需翻译) |
翻译后,您的 Python autograd 代码大部分应该可以直接在 C++ 中运行。如果遇到问题,请在 [GitHub issues](https://github.com/pytorch/pytorch/issues) 提交错误报告,我们将尽快修复。
<!--/div--><!--div class='modoc-hide-section'-->
## 结论
您现在应该对 PyTorch 的 C++ 自动求导 API 有了一个全面的了解。您可以在[这里](https://github.com/pytorch/examples/tree/master/cpp/autograd)找到本文中展示的代码示例。一如既往,如果您遇到任何问题或有疑问,可以通过我们的[论坛](https://discuss.pytorch.org/)或 [GitHub issues](https://github.com/pytorch/pytorch/issues)与我们联系。
<!--/div-->
<!--/div-->