Skip to content

Latest commit

Β 

History

History
439 lines (305 loc) Β· 17.8 KB

File metadata and controls

439 lines (305 loc) Β· 17.8 KB

C++ ν”„λ‘ νŠΈμ—”λ“œμ˜ μžλ™ λ―ΈλΆ„ (autograd)

λ²ˆμ—­: μœ μš©ν™˜

autograd λŠ” PyTorch둜 μœ μ—°ν•˜κ³  역동적인 신경망을 κ΅¬μΆ•ν•˜κΈ° μœ„ν•΄ ν•„μˆ˜μ μΈ νŒ¨ν‚€μ§€μž…λ‹ˆλ‹€. PyTorch 파이썬 ν”„λ‘ νŠΈμ—”λ“œμ˜ μžλ™ λ―ΈλΆ„ API λŒ€λΆ€λΆ„μ€ C++ ν”„λ‘ νŠΈμ—”λ“œμ—μ„œλ„ μ‚¬μš©ν•  수 있으며, νŒŒμ΄μ¬μ—μ„œ C++둜 μžλ™ λ―ΈλΆ„ μ½”λ“œλ₯Ό μ‰½κ²Œ λ³€ν™˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

이 νŠœν† λ¦¬μ–Όμ—μ„œλŠ” PyTorch C++ ν”„λ‘ νŠΈμ—”λ“œμ—μ„œ μžλ™ 미뢄을 μˆ˜ν–‰ν•˜λŠ” λͺ‡ κ°€μ§€ 예λ₯Ό μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€. 이 νŠœν† λ¦¬μ–Όμ€ μ—¬λŸ¬λΆ„μ΄ 파이썬 ν”„λ‘ νŠΈμ—”λ“œμ˜ μžλ™ 미뢄에 λŒ€ν•΄ 기본적으둜 μ΄ν•΄ν•˜κ³  μžˆλ‹€κ³  κ°€μ •ν•©λ‹ˆλ‹€. κ·Έλ ‡μ§€ μ•Šμ€ 경우 λ¨Όμ € Autograd: Automatic Differentiation 을 μ½μ–΄λ³΄μ„Έμš”.

기초 μžλ™ λ―ΈλΆ„ μ—°μ‚°

(이 νŠœν† λ¦¬μ–Ό 의 λ‚΄μš©μ— κΈ°λ°˜ν•¨)

ν…μ„œλ₯Ό μƒμ„±ν•˜κ³  κ·Έκ²ƒμ˜ 계산을 μΆ”μ ν•˜κΈ° μœ„ν•΄ torch::requires_grad() λ₯Ό μ‹€ν–‰ν•΄λ΄…μ‹œλ‹€.

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

Out:

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

ν…μ„œ 연산을 μˆ˜ν–‰ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

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

Out:

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

y λŠ” μ—°μ‚°μ˜ 결과둜 μƒμ„±λ˜μ—ˆμœΌλ―€λ‘œ grad_fn λ₯Ό κ°–κ³  μžˆμŠ΅λ‹ˆλ‹€.

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

Out:

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;

Out:

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

.requires_grad_( ... ) λŠ” in-place둜 ν…μ„œμ˜ κΈ°μ‘΄ 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;

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

Out:

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

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::NoGradGuard no_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>

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;

Out:

-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++μ—μ„œ μ‚¬μš©μž μ§€μ • μžλ™ λ―ΈλΆ„ ν•¨μˆ˜ μ‚¬μš©ν•˜κΈ°

(이 νŠœν† λ¦¬μ–Ό 의 λ‚΄μš©μ— κΈ°λ°˜ν•¨)

torch::autograd 에 μƒˆλ‘œμš΄ κΈ°λ³Έ(elementary) 연산을 μΆ”κ°€ν•˜λ €λ©΄ 각 연산에 λŒ€ν•΄ μƒˆλ‘œμš΄ torch::autograd::Function ν•˜μœ„ 클래슀(subclass)λ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•©λ‹ˆλ‹€. torch::autograd λŠ” 결과와 κ·Έλž˜λ””μ–ΈνŠΈλ₯Ό κ³„μ‚°ν•˜κ³  μ—°μ‚° 기둝을 μΈμ½”λ”©ν•˜κΈ° μœ„ν•΄ μœ„ν•΄ 이 torch::autograd::Function 듀을 μ‚¬μš©ν•©λ‹ˆλ‹€. λͺ¨λ“  μƒˆλ‘œμš΄ ν•¨μˆ˜μ—λŠ” 두 κ°€μ§€ 방법, 즉 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;

Out:

 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;

Out:

 5.5000
 5.5000
[ CPUFloatType{2} ]

torch::autograd::Function 에 λŒ€ν•œ 더 λ§Žμ€ λ‚΄μš©μ€ 이 λ¬Έμ„œ μ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

파이썬 μžλ™ λ―ΈλΆ„ μ½”λ“œλ₯Ό C++둜 λ³€ν™˜ν•˜κΈ°

개랡적으둜 λ§ν•˜λ©΄, C++μ—μ„œ μžλ™ 미뢄을 μ‚¬μš©ν•˜λŠ” κ°€μž₯ μ‰¬μš΄ 방법은 λ¨Όμ € νŒŒμ΄μ¬μ—μ„œ λ™μž‘ν•˜λŠ” μžλ™ λ―ΈλΆ„ μ½”λ“œλ₯Ό μž‘μ„±ν•œ ν›„, μ•„λž˜ ν‘œλ₯Ό μ°Έκ³ ν•΄ 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 (링크)

λŒ€λΆ€λΆ„μ˜ λ³€ν™˜λœ 파이썬 μžλ™ λ―ΈλΆ„ μ½”λ“œκ°€ C++μ—μ„œλ„ 잘 λ™μž‘ν•  κ²ƒμž…λ‹ˆλ‹€. λ™μž‘ν•˜μ§€ μ•Šμ„ 경우, GitHub issues 에 버그 리포트λ₯Ό μ œμΆœν•΄ μ£Όμ‹œλ©΄ μ΅œλŒ€ν•œ 빨리 κ³ μ³λ“œλ¦¬κ² μŠ΅λ‹ˆλ‹€.

κ²°λ‘ 

이제 PyTorch의 C++ μžλ™ λ―ΈλΆ„ API에 λŒ€ν•œ κ°œκ΄„μ μΈ 이해가 생겼을 κ²ƒμž…λ‹ˆλ‹€. μ—¬κΈ°μ„œ μ‚¬μš©λœ μ½”λ“œ μ˜ˆμ œλ“€μ€ μ—¬κΈ° μ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. μ–Έμ œλ‚˜ 그렇듯이 μ–΄λ–€ λ¬Έμ œκ°€ μƒκΈ°κ±°λ‚˜ 질문이 있으면 저희 포럼 을 μ΄μš©ν•˜κ±°λ‚˜ Github 이슈 둜 μ—°λ½μ£Όμ„Έμš”.