Backward

참고
[1] https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html#sphx-glr-beginner-blitz-autograd-tutorial-py
[2] https://pytorch.org/tutorials/intermediate/custom_function_double_backward_tutorial.html
[3] https://pytorch.org/docs/stable/notes/extending.html#extending-autograd
[4] https://pytorch.org/tutorials/advanced/cpp_extension.html


torch.tensor.backward() or torch.autograd.backward(tensor, …)

  • 텐서를 기준으로 하여 만들어진 그래프를 따라 텐서들의 gradient를 계산한다.
  • gradient는 그래프에 있는 operation의 backward 메소드를 연쇄적으로 실행시킴으로써 수행된다.
  • 결과물은 텐서의 .grad 변수에 더해짐으로써 존재하므로, backward를 실행하기전 .grad 값을 0으로 초기화해야 올바른 결과값을 얻을 수 있다.
  • backward 메소드를 통해 .grad 변수에 축적되는 gradient는 그래프의 leaf node에서만 행해진다.
  • backward를 진행할 때, 기준이 되는 텐서는 스칼라여야만 한다. 그래야 jacobian matrix가 tensor와 shape이 동일해진다. gradient 파라미터롤 고차원의 텐서를 기준으로 할 수도 있는데 이것은 아마 하나씩 여러번 실행하는게 아닐까 추측한다.
  • gradient(Tensor or None) : 기준이 되는 텐서의 gradient 값이다. default는 1이다. 기준이 되는 텐서가 스칼라가 아닌 경우에 유용하게 사용할 수 있다.
    external_grad = torch.tensor([1., 1.])
    Q.backward(gradient=external_grad) # Q.shape = 2
    
  • create_graph(bool) : True라면, 미분을 위한 그래프가 생성되며 이것은 다중 미분을 위해 사용될 수 있다. default: False
  • retain_graph(bool) : True라면, backward를 계산한 후 그래프가 유지된다. 중복해서 backward 함수를 시행할 수 있다. 반대로 False라면 backward 메소드는 한번만 시행될 수 있다.default: False
  • inputs(sequence of Tensor) : None이라면 그래프의 leaf node에 대해서만 .grad에 미분 값이 축적되지만, inputs 인자에 들어온 텐서들로만 .grad에 미분 값이 축적되게 할 수 있다.

torch.autograd.grad(outputs, inputs, …)

  • outputs 노드를 기준으로 해서 inputs 노드의 미분 값을 계산해준다.
  • backward 메소드와 파라미터는 거의 유사하다.
  • 결과값은 input에 대한 gradient 값이다.
  • 이 메소드 또한 operation으로 취급하기 때문에 gradient 값이지만 output이고 이 값에 대해서 다시 gradient 계산이 가능하다. 그래프도 만들 수 있다.
    out = model(x)
    grad_x, _ = troch.autograd.grad(out, x)
    grad_x.requires_grad # True
    

Computational Graph

  • backward를 실행시켜주는 근본은 torch.autograd 엔진이다.
  • autograd는 forward 과정에서 데이터들을 저장하고, 모든 operation을 저장한다. Function 객체로 이루어진 DAG(Directed Acyclic Graph)를 형성하며 저장한다.

torch.autograd.Function class

  • tensor graph의 기본적인 요소이다.
  • forward, backward 두개의 static method가 존재한다.
  • forward를 수행하기 위해서 forward method를 직접 콜해서는 안되며, apply() 메소드를 통해서 콜해야한다.
  • backward는 현재 노드의 미분 값을 출력해야하며, 체인룰에 의해 grad_output은 해당 operation의 결과물의 그레디언트 값이다. \(\partial L / \partial y\)
  • ctx를 변수를 통해 그래프의 노드에 데이터를 저장한다.
    class Exp(Function):
      @staticmethod
      def forward(ctx, i):
          result = i.exp()
          ctx.save_for_backward(result)
          return result
     
      @staticmethod
      def backward(ctx, grad_output):
          result, = ctx.saved_tensors
          return grad_output * result
     
    # Use it by calling the apply method:
    output = Exp.apply(input)
    
  • DAG에서 모든 leaf들은 input tensor를 의미하고, root는 output tensor를 의미한다.
  • 이 그래프를 root부터 leaf까지 추적(tracing) 함으로써 chain rule과 함께 gradient 값을 계산한다.

forward

  • forward pass에서는 autograd는 두가지 일을 동시에 한다.
  • Function의 forward method를 실행한다.
  • grad_fn(Function의 backward method)을 DAG에 저장한다.

backward

  • backward 메소드를 call한 텐서를 DAG의 root로 간주한다.
  • DAG에서 저장된 grad_fn을 기반으로 해서 gradient를 계산한다.
  • tensor의 .grad attribute에 계산한 gradient 값을 축적한다.
  • chain rule을 이용해서 leaf tensor까지 전파한다.
  • 아래는 DAG의 한 예시를 보여준다.
  • DAG는 forward pass를 중신으로 화살표가 그려진다.
  • 파란색 노드는 leaf node를 의미하고, 회색 노드는 backward function을 의미한다.
  • tensor의 required_grad 옵션이 False로 되어 있으면 autograd가 DAG로부터 노드를 때어내고 추적하지 않는다.