C++ 前端中的 Autograd

原文:https://pytorch.org/tutorials/advanced/cpp_autograd.html

autograd包对于在 PyTorch 中构建高度灵活和动态的神经网络至关重要。 PyTorch Python 前端中的大多数 autograd API 也可以在 C++ 前端中使用,从而可以轻松地将 Autograd 代码从 Python 转换为 C++。

在本教程中,我们将看几个在 PyTorch C++ 前端中进行 Autograd 的示例。 请注意,本教程假定您已经对 Python 前端中的 Autograd 有基本的了解。 如果不是这种情况,请先阅读 Autograd:自动微分

基本的 Autograd 操作

(改编自本教程

创建一个张量并设置torch::requires_grad()以跟踪它的计算

  1. auto x = torch::ones({2, 2}, torch::requires_grad());
  2. std::cout << x << std::endl;

出:

  1. 1 1
  2. 1 1
  3. [ CPUFloatType{2,2} ]

进行张量运算:

  1. auto y = x + 2;
  2. std::cout << y << std::endl;

出:

  1. 3 3
  2. 3 3
  3. [ CPUFloatType{2,2} ]

y是由于操作而创建的,因此具有grad_fn

  1. std::cout << y.grad_fn()->name() << std::endl;

出:

  1. AddBackward1

y上执行更多操作

  1. auto z = y * y * 3;
  2. auto out = z.mean();
  3. std::cout << z << std::endl;
  4. std::cout << z.grad_fn()->name() << std::endl;
  5. std::cout << out << std::endl;
  6. std::cout << out.grad_fn()->name() << std::endl;

出:

  1. 27 27
  2. 27 27
  3. [ CPUFloatType{2,2} ]
  4. MulBackward1
  5. 27
  6. [ CPUFloatType{} ]
  7. MeanBackward0

.requires_grad_( ... )原地更改现有张量的requires_grad标志。

  1. auto a = torch::randn({2, 2});
  2. a = ((a * 3) / (a - 1));
  3. std::cout << a.requires_grad() << std::endl;
  4. a.requires_grad_(true);
  5. std::cout << a.requires_grad() << std::endl;
  6. auto b = (a * a).sum();
  7. std::cout << b.grad_fn()->name() << std::endl;

出:

  1. false
  2. true
  3. SumBackward0

现在让我们反向传播。 因为out包含单个标量,所以out.backward()等效于out.backward(torch::tensor(1.))

  1. out.backward();

打印梯度d(out) / dx

  1. std::cout << x.grad() << std::endl;

出:

  1. 4.5000 4.5000
  2. 4.5000 4.5000
  3. [ CPUFloatType{2,2} ]

您应该具有4.5的矩阵。 有关如何获得此值的说明,请参见本教程中的相应部分。

现在,让我们来看一个向量雅各布产品的示例:

  1. x = torch::randn(3, torch::requires_grad());
  2. y = x * 2;
  3. while (y.norm().item<double>() < 1000) {
  4. y = y * 2;
  5. }
  6. std::cout << y << std::endl;
  7. std::cout << y.grad_fn()->name() << std::endl;

出:

  1. -1021.4020
  2. 314.6695
  3. -613.4944
  4. [ CPUFloatType{3} ]
  5. MulBackward1

如果我们想要向量-Jacobian 乘积,请将向量作为参数传递给backward

  1. auto v = torch::tensor({0.1, 1.0, 0.0001}, torch::kFloat);
  2. y.backward(v);
  3. std::cout << x.grad() << std::endl;

出:

  1. 102.4000
  2. 1024.0000
  3. 0.1024
  4. [ CPUFloatType{3} ]

您也可以通过在代码块中放置torch::NoGradGuard来停止对需要梯度的张量的跟踪历史的自动定格

  1. std::cout << x.requires_grad() << std::endl;
  2. std::cout << x.pow(2).requires_grad() << std::endl;
  3. {
  4. torch::NoGradGuard no_grad;
  5. std::cout << x.pow(2).requires_grad() << std::endl;
  6. }

出:

  1. true
  2. true
  3. false

或者使用.detach()获得具有相同内容但不需要梯度的新张量:

  1. std::cout << x.requires_grad() << std::endl;
  2. y = x.detach();
  3. std::cout << y.requires_grad() << std::endl;
  4. std::cout << x.eq(y).all().item<bool>() << std::endl;

出:

  1. true
  2. false
  3. true

有关 C++ 张量自动梯度 API 的更多信息,例如grad/requires_grad/is_leaf/backward/detach/detach_/register_hook/retain_grad,请参见相应的 C++ API 文档

用 C++ 计算高阶梯度

高阶梯度的应用之一是计算梯度罚分。 我们来看看使用torch::autograd::grad的示例:

  1. #include <torch/torch.h>
  2. auto model = torch::nn::Linear(4, 3);
  3. auto input = torch::randn({3, 4}).requires_grad_(true);
  4. auto output = model(input);
  5. // Calculate loss
  6. auto target = torch::randn({3, 3});
  7. auto loss = torch::nn::MSELoss()(output, target);
  8. // Use norm of gradients as penalty
  9. auto grad_output = torch::ones_like(output);
  10. auto gradient = torch::autograd::grad({output}, {input}, /*grad_outputs=*/{grad_output}, /*create_graph=*/true)[0];
  11. auto gradient_penalty = torch::pow((gradient.norm(2, /*dim=*/1) - 1), 2).mean();
  12. // Add gradient penalty to loss
  13. auto combined_loss = loss + gradient_penalty;
  14. combined_loss.backward();
  15. std::cout << input.grad() << std::endl;

出:

  1. -0.1042 -0.0638 0.0103 0.0723
  2. -0.2543 -0.1222 0.0071 0.0814
  3. -0.1683 -0.1052 0.0355 0.1024
  4. [ CPUFloatType{3,4} ]

有关如何使用它们的更多信息,请参见torch::autograd::backwardtorch::autograd::grad的文档。

在 C++ 中使用自定义 Autograd 函数

(改编自本教程

torch::autograd添加新的基本操作需要为每个操作实现一个新的torch::autograd::Function子类。 torch::autograd::Function用于torch::autograd计算结果和梯度,并对操作历史进行编码。 每个新函数都需要您实现两种方法:forwardbackward,有关详细要求,请参见此链接

在下面,您可以从torch::nn找到Linear函数的代码:

  1. #include <torch/torch.h>
  2. using namespace torch::autograd;
  3. // Inherit from Function
  4. class LinearFunction : public Function<LinearFunction> {
  5. public:
  6. // Note that both forward and backward are static functions
  7. // bias is an optional argument
  8. static torch::Tensor forward(
  9. AutogradContext *ctx, torch::Tensor input, torch::Tensor weight, torch::Tensor bias = torch::Tensor()) {
  10. ctx->save_for_backward({input, weight, bias});
  11. auto output = input.mm(weight.t());
  12. if (bias.defined()) {
  13. output += bias.unsqueeze(0).expand_as(output);
  14. }
  15. return output;
  16. }
  17. static tensor_list backward(AutogradContext *ctx, tensor_list grad_outputs) {
  18. auto saved = ctx->get_saved_variables();
  19. auto input = saved[0];
  20. auto weight = saved[1];
  21. auto bias = saved[2];
  22. auto grad_output = grad_outputs[0];
  23. auto grad_input = grad_output.mm(weight);
  24. auto grad_weight = grad_output.t().mm(input);
  25. auto grad_bias = torch::Tensor();
  26. if (bias.defined()) {
  27. grad_bias = grad_output.sum(0);
  28. }
  29. return {grad_input, grad_weight, grad_bias};
  30. }
  31. };

然后,我们可以通过以下方式使用LinearFunction

  1. auto x = torch::randn({2, 3}).requires_grad_();
  2. auto weight = torch::randn({4, 3}).requires_grad_();
  3. auto y = LinearFunction::apply(x, weight);
  4. y.sum().backward();
  5. std::cout << x.grad() << std::endl;
  6. std::cout << weight.grad() << std::endl;

出:

  1. 0.5314 1.2807 1.4864
  2. 0.5314 1.2807 1.4864
  3. [ CPUFloatType{2,3} ]
  4. 3.7608 0.9101 0.0073
  5. 3.7608 0.9101 0.0073
  6. 3.7608 0.9101 0.0073
  7. 3.7608 0.9101 0.0073
  8. [ CPUFloatType{4,3} ]

在这里,我们给出了一个由非张量参数设置参数的函数的附加示例:

  1. #include <torch/torch.h>
  2. using namespace torch::autograd;
  3. class MulConstant : public Function<MulConstant> {
  4. public:
  5. static torch::Tensor forward(AutogradContext *ctx, torch::Tensor tensor, double constant) {
  6. // ctx is a context object that can be used to stash information
  7. // for backward computation
  8. ctx->saved_data["constant"] = constant;
  9. return tensor * constant;
  10. }
  11. static tensor_list backward(AutogradContext *ctx, tensor_list grad_outputs) {
  12. // We return as many input gradients as there were arguments.
  13. // Gradients of non-tensor arguments to forward must be `torch::Tensor()`.
  14. return {grad_outputs[0] * ctx->saved_data["constant"].toDouble(), torch::Tensor()};
  15. }
  16. };

然后,我们可以通过以下方式使用MulConstant

  1. auto x = torch::randn({2}).requires_grad_();
  2. auto y = MulConstant::apply(x, 5.5);
  3. y.sum().backward();
  4. std::cout << x.grad() << std::endl;

出:

  1. 5.5000
  2. 5.5000
  3. [ CPUFloatType{2} ]

有关torch::autograd::Function的更多信息,请参见其文档

将 Autograd 代码从 Python 转换为 C++

在较高的层次上,在 C++ 中使用 Autograd 的最简单方法是先在 Python 中拥有可用的 Autograd 代码,然后使用下表将您的 Autograd 代码从 Python 转换为 C++:

Python C++
torch.autograd.backward torch::autograd::backward
torch.autograd.grad torch::autograd::grad
torch.Tensor.detach torch::Tensor::detach
torch.Tensor.detach_ torch::Tensor::detach_
torch.Tensor.backward torch::Tensor::backward
torch.Tensor.register_hook torch::Tensor::register_hook
torch.Tensor.requires_grad torch::Tensor::requires_grad_
torch.Tensor.retain_grad torch::Tensor::retain_grad
torch.Tensor.grad torch::Tensor::grad
torch.Tensor.grad_fn torch::Tensor::grad_fn
torch.Tensor.set_data torch::Tensor::set_data
torch.Tensor.data torch::Tensor::data
torch.Tensor.output_nr torch::Tensor::output_nr
torch.Tensor.is_leaf torch::Tensor::is_leaf

翻译后,您的大多数 Python Autograd 代码都应仅在 C++ 中工作。 如果不是这种情况,请在 GitHub ISSUE 中提交错误报告,我们将尽快对其进行修复。

总结

现在,您应该对 PyTorch 的 C++ autograd API 有了一个很好的了解。 您可以在此处找到本说明中显示的代码示例。 与往常一样,如果您遇到任何问题或疑问,可以使用我们的论坛GitHub ISSUE 进行联系。