C++ 프론트엔드의 자동 미분 (autograd)#
번역: 유용환
autograd 는 PyTorch로 유연하고 역동적인 신경망을 구축하기 위해
필수적인 패키지입니다. PyTorch 파이썬 프론트엔드의 자동 미분 API 대부분은 C++ 프론트엔드에서도
사용할 수 있으며, 파이썬에서 C++로 자동 미분 코드를 쉽게 변환할 수 있습니다.
이 튜토리얼에서는 PyTorch C++ 프론트엔드에서 자동 미분을 수행하는 몇 가지 예를 살펴보겠습니다. 이 튜토리얼은 여러분이 파이썬 프론트엔드의 자동 미분에 대해 기본적으로 이해하고 있다고 가정합니다. 그렇지 않은 경우 먼저 Autograd: Automatic Differentiation 을 읽어보세요.
기초 자동 미분 연산#
(이 튜토리얼 의 내용에 기반함)
텐서를 생성하고 그것의 계산을 추적하기 위해 torch::requires_grad() 를 실행해봅시다.
autox=torch::ones({2,2},torch::requires_grad()); std::cout<<x<<std::endl;
Out:
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;
Out:
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;
Out:
2727 2727 [CPUFloatType{2,2}] MulBackward1 27 [CPUFloatType{}] MeanBackward0
.requires_grad_( ... ) 는 in-place로 텐서의 기존 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;
Out:
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;
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::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;
Out:
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 에 새로운 기본(elementary) 연산을 추가하려면 각 연산에 대해 새로운 torch::autograd::Function
하위 클래스(subclass)를 구현해야 합니다. torch::autograd 는 결과와 그래디언트를 계산하고 연산 기록을 인코딩하기 위해 위해
이 torch::autograd::Function 들을 사용합니다. 모든 새로운 함수에는 두 가지 방법, 즉 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 에 대한 더 많은 내용은
이 문서 에서 확인할 수 있습니다.
파이썬 자동 미분 코드를 C++로 변환하기#
개략적으로 말하면, C++에서 자동 미분을 사용하는 가장 쉬운 방법은 먼저 파이썬에서 동작하는 자동 미분 코드를 작성한 후, 아래 표를 참고해 C++ 코드로 변환하는 것입니다.
Python |
C++ |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
대부분의 변환된 파이썬 자동 미분 코드가 C++에서도 잘 동작할 것입니다. 동작하지 않을 경우, GitHub issues 에 버그 리포트를 제출해 주시면 최대한 빨리 고쳐드리겠습니다.
결론#
이제 PyTorch의 C++ 자동 미분 API에 대한 개괄적인 이해가 생겼을 것입니다. 여기서 사용된 코드 예제들은 여기 에서 확인할 수 있습니다. 언제나 그렇듯이 어떤 문제가 생기거나 질문이 있으면 저희 포럼 을 이용하거나 Github 이슈 로 연락주세요.