• 教學 >
  • C++ 前端中的 Autograd
捷徑

C++ 前端的 Autograd

建立於:2020 年 4 月 1 日 | 最後更新:2025 年 1 月 21 日 | 最後驗證:未驗證

autograd 套件對於在 PyTorch 中建立高度彈性和動態的神經網路至關重要。PyTorch Python 前端中的大多數 autograd API 也可在 C++ 前端中使用,從而可以輕鬆地將 autograd 程式碼從 Python 轉換為 C++。

在本教學中,探索在 PyTorch C++ 前端中進行 autograd 的幾個範例。請注意,本教學假設您已經對 Python 前端中的 autograd 有基本的了解。如果不是這種情況,請先閱讀Autograd: 自動微分

基本 autograd 運算

(改編自本教學)

建立一個 tensor 並設定 torch::requires_grad() 來追蹤它的計算

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

輸出

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

執行一個 tensor 運算

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

輸出

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

y 是作為運算的結果而建立的,因此它有一個 grad_fn

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

輸出

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;

輸出

 27  27
 27  27
[ CPUFloatType{2,2} ]
MulBackward1
27
[ CPUFloatType{} ]
MeanBackward0

.requires_grad_( ... ) 會就地變更現有 tensor 的 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;

輸出

false
true
SumBackward0

現在讓我們進行反向傳播。由於 out 包含單個純量,因此 out.backward() 等同於 out.backward(torch::tensor(1.))

out.backward();

列印梯度 d(out)/dx

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

輸出

 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;

輸出

-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;

輸出

  102.4000
 1024.0000
    0.1024
[ CPUFloatType{3} ]

您也可以透過在程式碼區塊中放置 torch::NoGradGuard 來停止 autograd 追蹤需要梯度的 tensor 的歷史記錄

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;
}

輸出

true
true
false

或使用 .detach() 取得一個具有相同內容但不要求梯度的新的 tensor

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

有關 C++ tensor autograd API(例如 grad / requires_grad / is_leaf / backward / detach / detach_ / register_hook / retain_grad)的更多資訊,請參閱相應的 C++ API 文件

在 C++ 中計算更高階的梯度

更高階梯度的應用之一是計算梯度懲罰。讓我們看看使用 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;

輸出

-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++ 中使用自訂 autograd 函式

(改編自本教學)

要將新的基本運算新增至 torch::autograd,需要為每個運算實作一個新的 torch::autograd::Function 子類別。torch::autograd::Functiontorch::autograd 用於計算結果和梯度並編碼運算歷史記錄的工具。每個新函數都需要您實作 2 個方法:forwardbackward,請參閱此連結以取得詳細需求。

您可以在下方找到 torch::nn 中的 Linear 函式的程式碼

#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;

輸出

 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} ]

在這裡,我們給出一個由非 tensor 引數參數化的函數的額外範例

#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;

輸出

 5.5000
 5.5000
[ 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 issues 上提交錯誤報告,我們會盡快修復。

結論

您現在應該對 PyTorch 的 C++ autograd API 有了很好的了解。您可以在這裡找到本筆記中顯示的程式碼範例。與往常一樣,如果您遇到任何問題或有疑問,可以使用我們的 論壇GitHub issues 與我們聯繫。

文件

獲取 PyTorch 的全面開發人員文件

查看文件

教學

獲取適用於初學者和高級開發人員的深入教學

查看教學

資源

查找開發資源並獲得您問題的解答

查看資源