본문 바로가기

Deep Learning/Pytorch Tutorial

Pytorch Tutorial 기본 (2) : <AutoGrad: 자동 미분>

AutoGrad: AUTOMATIC DIFFERENTIATION

튜토리얼 링크 및 자료.

 

Autograd: Automatic Differentiation — PyTorch Tutorials 1.3.1 documentation

Note Click here to download the full example code Autograd: Automatic Differentiation Central to all neural networks in PyTorch is the autograd package. Let’s first briefly visit this, and we will then go to training our first neural network. The autograd

pytorch.org

Run in Google Colab Downlad Notebook View on GitHub

Pytorch로 만들어지는 모든 Neural Network의 중심은 autograd 패키이지이다. 이 패키지에 대해서 간단하게 살펴본 다음 바로 Neural Network를 학습하는 방법에 대해서 알아보자.

autograd 패키지는 모든 Tensor연산에 대해서 자동 미분을 제공해 준다. Define-by-run의 형태를 지닌 프레임워크이기 때문에 back propagation이 코드가 어떻게 동작하는가에 따라 정의된다. 그래서 반복 하나하나가 달라질 수 있다. 아래의 예제를 참고하자

Tensor

torch.Tensorautograd 패키지의 주요 클래스이다. 만약에 이 클래스의 .requires_grad 값을 True로 설정한다면, 해당 Tensor에서 이루어지는 모든 연산들을 추적한다. 연산이 끝난 후에는 .bachward() 매소드를 사용하여 기울기(gradient)를 계산할 수 있다.

추적(tracking history)를 멈추고 싶다면, .detach() 매소드를 사용하여 연산을 더이상 기록하지 않게 만들 수 있다.

연산 기록을 멈추고( 메모리 사용을 줄이기) 위해서, 코드 블럭을 with torch.no_grad(): 로 감싸면 된다. 이는 모델을 평가할 때 도움된다. 왜냐하면 evaluation 단계에서는 gradient를 계산할 필요가 없기 때문이다. 학습된 파라미터 값을 평가하는 단계이기 때문에 파라미터 값을 정하기 위해서 사용되는 gradient는 평가단계에서 더이상 필요하지 않다.

autograd를 실행하기 위해서는 Function 클래스도 필요하다.

TensorFunction은 서로 연결되어 있으며 acyclic graph를 만들어 준다. 그리고 그러한 그래프는 완전한 computation을 기록한다. 각각의 Tensor.grad_fn 이라는 값을 갖고 있으며 Tensor를 만

들어준 Function을 참조한다. (사용자에 의해서 만들어진 Tensor는 해당사항이 아니다, 이 경우, .grad_fn은 None 이다.)

만일 미분값을 계산하고 싶다면 Tensor의 .backward()를 호출하면 된다. 만약 Tensor 가 스칼라 값(예를 들어 단일 원소를 데이터를 지닌 것)이라면, bachkward() 호출 시 매개변수를 지정하지 않아도 된다. 하지만 스칼라가 아닌 복수의 원소를 지닌 값이라면, tensor와 일치하는 shape을 갖도록 gradient 값을 지정해주어야 한다.

Tensor를 선언할 때, requires_grad=True를 매개변수로 전달하면 연산을 기록한다.

 

import torch            # pytorch 패키지 불러오는 곳

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

tensor([[1., 1.],
            [1., 1.]], requires_grad=True)

텐서 연산을 진행한 다음 출력하면 다음과 같다.

 

y = x + 2
print(y)

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

y는 사용자에 의해서 만들어진 것이 아닌 연산을 통해 만들어졌기 때문에grad_fn값이 존재한다.

 

print(y.grad_fn)

<AddBackward0 object at 0x7f6eedaa88d0>

뒤의 0x로 시작하는 숫자는 메모리 주소로 추정되며 앞에 있는 것은 Tensor이름? 인 듯 하다.

y에 대해서 추가적인 연산을 했을 때,

 

z = y * y * 3
out = z.mean()
print(z, '\n', out)

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

.requires_grad_(...)매소드는 이미 존재하는Tensor의requires_grad값을 바꿔준다. 만약 생성시에requires_grad값이 주어지지 않았다면 디폴트 값은False가 된다.

 

a = torch.randn(2, 2)    #reuires_grad 값을 설정해 주지 않았음으로 False
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 0x7f6eedaa8a90>

Gradients

backpropagation(backprop)을 해보자. 위의 예제에서 out = z.mean()코드로 인하여 out은 스칼라 값을 갖는다. 그래서 out.backward()out.backward(torch.tensor(1.))과 똑같이 동작한다.

backprop 연산을 진행한 후 .grad로 기울기를 알아보도록 하자.

 

out.backward()
print(x.grad)

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

위의 연산들을 따라가보도록 하자.

$$x = \begin{bmatrix} 1 & 1 \\ 1&1\end{bmatrix}$$ $$y = x + 2 \\ = \begin{bmatrix} 3&3\\3&3 \end{bmatrix}$$ $$z = y^2 +3 = (x+2)^2 + 3 \\ = \begin{bmatrix} 27&27 \\ 27&27 \end{bmatrix}$$

위의 수식과 같이 연산은 진행된다. x의 경우 .ones 연산으로 1을 원소로 하는 2x2행렬을 만들었다. 그리고 나머지 y 와 z에 대해서도 앞서 진행했던 코드 예제들을 수식화 한 것이다. 이 때 out은 다음의 수식으로 표현될 수 있다. (out은 o로 표현하겠다.)

$$o = \frac{1}{4} \sum_{i}^{4}z_i, \ \ z_i=3(x_i +2)^2 , \ \ z_i | _{{x_i} = 1 } = 27$$

여기서 xi에 대해서 o를 미분하면 다음과 같은 형식을 갖는다.

$$\frac{\partial o}{\partial x_i} = \frac{3}{2}(x_i + 2)$$

이를 우리의 입력에 맞춰서 계산을 해주면 다음과 같은 결과를 갖게 된다.

$$\frac{\partial o}{ \partial x_i} | _{{x_i}=1}=\frac{9}{2}=4.5$$

가 되는 것이다. 소스코드의 결과를 봐도 결과적으로 x.grad 값이 4.5가 되는 것을 볼 수 있을 것이다.

이런 식으로 gradient를 계산하는데 있어 사용하는 방법이 back propagation(줄여서 backprop)이다. backprop에 대해서는 cs231n 강의 정리 때 다시한번 자세하게 설명하도록 하겠다.

위의 함수를 벡터를 인자로 받는 함수로 표현하면 수식은 다음과 같아진다.

$$\vec{y} = f(\vec{x})$$

그리고 이러한 벡터y에 대한 벡터 x의 변화값은 야코비안 행렬에 의해서 표현될 수 있다.

$$J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)$$

일반적으로, torch.autograd는 vector-Jacobian 연산을 위한 엔진이다. \( v=(v_1, v_2, ..., v_n)^T \) 일때, \( v^T \cdot J \)를 계산한다. 만일 \( v \)가 스칼라 함수 \( g=( \vec{x}) \)의 gradient라면 \( v=\left( \begin{array}{ccc} \frac {\partial l}{\partial y_1} & \cdots & \frac{\partial l}{\partial y_m} \end{array} \right)^T \) 이며 back propagation의 계산법인 chain rule 이용하면, vector-Jacobian product는 \( \vec{x} \)에 대한 \( l \)의 gradient는 다음과 같아진다.

$$J^{T}\cdot v=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\left(\begin{array}{c} \frac{\partial l}{\partial y_{1}}\\ \vdots\\ \frac{\partial l}{\partial y_{m}} \end{array}\right)=\left(\begin{array}{c} \frac{\partial l}{\partial x_{1}}\\ \vdots\\ \frac{\partial l}{\partial x_{n}} \end{array}\right)$$

\( v^T \cdot J \)는 열 벡터를 만들어내며 \( J^T \cdot v \)로 행 벡터처럼 다뤄질 수 있다.

vector-Jacobian product의 이러한 특징은 외부의 gradient를 스칼라 값이 아닌 output을 지닌 모델에 전달하는 것을 매우 편리하게 해준다.

 

이러한 vector-Jacobain product의 예시는 다음과 같다.

 

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

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

print(y)

tensor([ 51.2189, 202.6366, 1309.0981], grad_fn=)

여기서 \( y \)는 스칼라 값이 아니다. torch.autograd는 full Jacobian을 바로 계산할 수는 없지만, 만약 단순한 vector-Jacobian product만을 원한다면 벡터 값을 backward에 매개 변수로 전해주면 된다.

 

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

print(x.grad)

tensor([5.1200e+01, 5.1200e+02, 5.1200e-02])

'.requires_grad=True'인 상태에서 'with torch.no_grad():'로 코드를 감싸준다면 autograd가 Tensor 연산을 기록하는 것을 멈출 수 있다.

 

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

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

True
True
False

같은 효과를 내기 위해서 '.detach()' 매소드를 호출하기도 한다. 이때는 같은 내부구조를 갖고 있지만 gradient를 더이상 필요하지 않는 새로운 Tensor를 만들 때 사용한다.

 

print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all())

True
False
tensor(True)

 

 

'autograd.Function'에 대한 추가적인 정보를 원한다면 다음의 링크에서 확인해보자.
https://pytorch.org/docs/stable/autograd.html#function