뉴비에욤

PyTorch 튜토리얼 2 - Autograd : 자동 미분 본문

Machine Learning/PyTorch

PyTorch 튜토리얼 2 - Autograd : 자동 미분

초보에욤 2018. 4. 30. 18:04

2017/07/13 - [Machine Learning/PyTorch] - 윈도우 10 PyTorch 환경 구성 - 설치

2018/04/30 - [Machine Learning/PyTorch] - PyTorch 튜토리얼 1 - PyTorch란?



원문 : http://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html



* 17년도 07월에 초번 번역 후, 파이토치 최신 버전인 0.4.0에 맞추어 갱신 된 번역본

* 최근 갱신일 : 2018-04-30



목 차 ( 초보자 튜토리얼 )

A. 파이토치와 함께하는 딥러닝 : 60분만에 끝장내기!

2. Autograd : 자동 미분

- 텐서

- 그라디언트



Autograd : 자동 미분(Automatic Differentiation)

PyTorch에서 뉴럴 네트워크의 중심은 autograd 라는 패키지라 할 수 있다. 일단 간단히 살펴 본 후에 첫 번째 뉴럴 네트워크 학습을 진행할 것이다.


autograd 패키지는 텐서의 모든 연산에 대하여 자동 미분을 제공한다. 이 패키지는 실행 시점에 정의되는(define-by-run) 프레임워크인데 다시 말하면 코드가 어떻게 실행되는지에 따라 역전파(backprop)가 정의되며, 각각의 반복[각주:1]마다 역전파가 달라질 수 있다는 것이다.


  • 텐서플로우는 정의 된 다음 실행되는(defined-and-run) 프레임워크인데 이는 그래프 구조에서 미리 조건과 반복을 정의하고 나서 실행 된다.

  • PyTorch를 비롯한 Chainer, DyNet 등은 define-by-run 프레임워크이다.


몇몇의 예제를 통해 좀 더 쉽게 설명하자면 다음과 같다.




텐서 ( Tensors )


torch.Tensor는 패키지에서 가장 중심이 되는 클래스이다. 텐서의 속성 중 하나인 .requires_gradTrue로 세팅하면, 텐서의 모든 연산에 대하여 추적을 시작한다. 계싼 작업이 모두 수행 되었다면 .backward()를 호출하여 모든 그라디언트들을 자동으로 계산할 수 있다. 이 텐서를 위한 그라디언트는 .grad 속성에 누적되어 저장 된다.


텐서에 대하여 기록(history) 추적을 중지하려면, .detach()를 호출하여 현재의 계산 기록으로 부터 분리시키고 이후에 일어나는 계산들은 추적되지 않게 할 수 있다.


기록 추적(및 메모리 사용)에 대하여 방지를 하려면, 코드 블럭을 with torch.no_grad():로 래핑 할(wrap)수 있다. 이는 특히 모델을 평가할 때 엄청난 도움이 되는데, 왜냐하면 모델은 requires_grad=True 속성이 적용 된 학습 가능한 파라미터를 가지고 있을 수 있으나 우리는 그라디언트가 필요하지 않기 때문이다.


자동 미분을 위해 매우 중요한 클래스가 하나 더 있는데 바로 Function 이다.



TensorFunction은 상호 연결되어 있으며, 비순환(비주기) 그래프를 생성하는데, 이 그래프는 계산 기록 전체에 대하여 인코딩을 수행한다. 각 변수는 Tensor를 생성한 Function을 참조하는 .grad_fn 속성을 가지고 있다. (단, 사용자에 의해 생성 된 텐서는 제외한다-해당 텐서들은 grad_fn 자체가 None 상태이다)


만약 도함수(derivatives)들을 계산하고 싶다면, Tensor의 .backward()를 호출하면 된다. 만약 Tensor가 스칼라(i.e. 한 개의 요소(element)를 가지고 있는) 형태라면, backward() 사용에 있어 그 어떠한 파라미터도 필요하지 않는다. 그러나 한 개 이상의 요소를 가지고 있다면 올바른 모양(matching shape)의 텐서인 gradient 파라미터를 명시할 필요가 있다.


파이토치 패키지 임포트

- 소스

import torch


텐서를 생성하고 requires_grad=True로 세팅하여 계산 추적한다.

- 소스

x = torch.ones(2, 2, requires_grad=True)
print(x) 

- 결과

tensor([[ 1.,  1.],
        [ 1.,  1.]])


텐서에 대하여 임의의 연산을 수행한다.

- 소스

y = x + 2
print(y) 

- 결과

tensor([[ 3.,  3.],
        [ 3.,  3.]])


y는 연산의 결과로써 생성된 것이기 때문에 grad_fn을 가지고 있다.

- 소스

print(y.grad_fn) 

- 결과

<AddBackward0 object at 0x7fb9f73ea518>


y에 대하여 임의의 연산을 수행한다.

- 소스

z = y * y * 3
out = z.mean()

print(z, out) 

- 결과

tensor([[ 27.,  27.],
        [ 27.,  27.]]) tensor(27.)


.requires_grad_( ... )은 이미 존재하는 텐서의 requires_grad 플래그를 제자리(in-place)에서 변경한다. 입력 플래그를 명시하지 않은 경우 기본적으로 True가 된다.

- 소스

a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn) 

- 결과

False
True
< SumBackward0 object at 0x7fb9f74f1a58 >




그라디언트 ( Gradients )


자, 다시 돌아와서 이제 out은 하나의 스칼라(single scalar) 값을 가지고 있기 때문에 out.backward()out.backward(torch.tensor(1))와 동등한 결과를 리턴한다.

- 소스

out.backward()


d(out)/dx[각주:2] 그라디언트 출력

- 소스

print(x.grad) 

- 결과 (4.5 행렬이 출력되어야 함)

tensor([[ 4.5000,  4.5000],
        [ 4.5000,  4.5000]])


4.5 행렬이 출력되었다면 텐서 o에 대하여 out을 호출을 해보자. 

우리의 o가 와 같다면이 된다.

그러므로,  식이 유도되고 최종적으로 가 된다.



autograd를 이용하여 수많은 미친짓들을 할 수 있다.

- 소스

x = torch.randn(3, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
    y = y * 2

print(y) 

- 결과

tensor([-590.4467,   97.6760,  921.0221])


gradients = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(gradients)

print(x.grad)

결과

tensor([  51.2000,  512.0000,    0.0512])


또한 코드 블럭을 with torch.no_grad(): 로 래핑하여 requires_grad=True 속성이 명시된 autograd가 텐서의 추적 기록에 남지 않게 할 수 있다.

- 소스

print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
    print((x ** 2).requires_grad) 

- 결과

True
True
False


추후 시간이 있다면 autograd와 Function에 대한 문서를 읽어보기 바란다.

http://pytorch.org/docs/stable/autograd.html



  1. Weight와 Bias가 갱신되는 단위인 Mini-Batch를 의미. [본문으로]
  2. d(out)을 x에 관하여 미분 [본문으로]
Comments