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 操作¶
(改編自本教程)
建立一個張量並設定 torch::requires_grad() 以跟蹤其計算
auto x = torch::ones({2, 2}, torch::requires_grad());
std::cout << x << std::endl;
輸出
1 1
1 1
[ CPUFloatType{2,2} ]
執行張量操作
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_( ... ) 會原地改變現有張量的 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 的矩陣。有關如何得出此值的解釋,請參閱本教程中的相應部分。
現在讓我們看一個向量-Jacobian 積的示例
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
如果我們想要向量-Jacobian 積,將向量作為引數傳遞給 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 跟蹤需要梯度的張量歷史
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() 獲取一個內容相同但不要求梯度的張量
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++ 張量 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::Function 是 torch::autograd 用於計算結果和梯度的類,並編碼操作歷史。每個新函式都需要您實現兩個方法:forward 和 backward,詳細要求請參閱此連結。
下方是 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} ]
這裡,我們給出一個由非張量引數化的函式的額外示例
#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++ |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
翻譯後,您的 Python autograd 程式碼在 C++ 中應該大部分都能直接使用。如果情況並非如此,請在GitHub issues 上提交錯誤報告,我們會盡快修復。
總結¶
現在您應該對 PyTorch 的 C++ autograd API 有了很好的概覽。您可以在此處找到本文中展示的程式碼示例。一如既往,如果您遇到任何問題或有疑問,可以使用我們的論壇或GitHub issues 聯絡我們。