본문 바로가기
AI/파이토치(Pytorch)

[파이토치] Autograd

by Hyen4110 2021. 9. 8.
반응형

파이토치 튜토리얼을 참고하여 정리한 글입니다.

https://tutorials.pytorch.kr/beginner/blitz/autograd_tutorial.html

 

torch.autograd 에 대한 간단한 소개 — PyTorch Tutorials 1.9.0+cu102 documentation

Note Click here to download the full example code torch.autograd 에 대한 간단한 소개 torch.autograd 는 신경망 학습을 지원하는 PyTorch의 자동 미분 엔진입니다. 이 단원에서는 autograd가 신경망 학습을 어떻게 돕는

tutorials.pytorch.kr

 

1. Introduction

 - 넘파이의 array와 파이토치의 tensor는 기초적인 연산 측면에서는 크게 다르지 않았습니다. 하지만 tensor는 딥러닝을 위해서 특별히 만들어진만큼 딥러닝 역전파에 필요한 연산인 미분에 대한 여러가지 기능을 제공합니다. 그중 하나인 torch.autograd는 임의의 스칼라 값 함수의 자동 미분을 구현하는 클래스와 함수를 제공합니다. torch.autograd는 사용하는것이 크게 복잡하지 않는데, 기존 코드에 require_grad=True 키워드로 그라디언트를 계산해야 하는 Tensor만 선언하면 됩니다.

 

2. 배경 개념(Background Concepts)

신경망(NN; Neural Network)은 어떤 입력 데이터에 대해 실행되는 중첩(nested)된 함수들의 모음(collection)입니다. 이 함수들은 PyTorch에서 Tensor로 저장되는, (가중치(weight)와 편향(bias)로 구성된) 매개변수들로 정의됩니다. 신경망을 학습하는 것은 2단계(순전파, 역전파)로 이루어집니다

 

① 순전파(Forward Propagation)

: 순전파 단계에서, 신경망은 정답을 맞추기 위해 최선의 추측(best guess)을 합니다. 이렇게 추측을 하기 위해서 입력 데이터를 각 함수들에서 실행합니다.

 

② 역전파(Backward Propagation)
: 역전파 단계에서, 신경망은 추측한 값에서 발생한 오류(error)에 비례하여(proportionate) 매개변수들을 적절히 조절(adjust)합니다. 출력(output)로부터 역방향으로 이동하면서 오류에 대한 함수들의 매개변수들의 미분값(변화도(gradient) )을 수집하고, 경사하강법(gradient descent)을 사용하여 매개변수들을 최적화 합니다. 

 

 

3. 간단한 Autograd 예제

- 간단 이미지 분류 모델 학습 -

 

1) 데이터 준비하기

import torch, torchvision
model = torchvision.models.resnet18(pretrained=True)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)

: 모델은  torchvision 에서 미리 학습된 resnet18 모델을 불러옵니다. 3채널짜리 높이와 넓이가 64인 이미지 하나를 표현하는 무작위의 데이터 텐서를 생성하고, 이에 상응하는 label(정답) 을 무작위 값으로 초기화합니다.

 

2) 순전파 (Forward Propagation)

prediction = model(data) # 순전파 단계(forward pass)

순전파 단계에서는 입력(input) 데이터를 모델의 각 층(layer)에 통과시켜 예측값(prediction)을 생성합니다. 

 

3) 손실함수(loss) 계산하기

loss = (prediction - labels).sum()
loss.backward() # 역전파 단계(backward pass)

- 모델의 예측값(prediction)과 그에 해당하는 정답(label)의 차이를 합하여 오차(error, 손실(loss) )를 계산하였습니다. 손실함수를 구하는 방법은 다양하지만 이번글에서는 간단하게 차이값으로만 구하도록 하겠습니다.

-  오차 텐서(error tensor)에 .backward() 를 호출하면 역전파가 시작되며, 그 다음 Autograd가 매개변수(parameter)의 .grad 속성(attribute)에, 모델의 각 매개변수에 대한 변화도(gradient)를 계산하고 저장합니다.

 

 

4) 옵티마이저 설정

optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)

- 학습율(learning rate) 0.1과 모멘텀(momentum) 0.9를 갖는 SGD로 옵티마이즈를 설정하였으며, 옵티마이저(optimizer)에 모델의 모든 매개변수(models.parameters())를 등록합니다.

 

5) 경사하강법

optim.step() # 경사하강법(gradient descent)

- step 을 호출하여 경사하강법(gradient descent)을 시작합니다. 옵티마이저는 .grad 에 저장된 기울기(gradient)에 따라 각 매개변수를 조정(adjust)합니다.

 

 

4. grad() 함수 이해하기

  1) 원소가 하나인 경우

  # [step1] tensor 생성 (requires_grad=True)

# tensor 생성 (requres_grad= True)
x = torch.tensor(2.0, requires_grad=True)

# tensor x에 대한 함수 y 생성
y = 2*x**2 + 1

print(x) # tensor(2., requires_grad=True)
print(y) # tensor(9., grad_fn=<AddBackward0>)

-> requires_grad = False로 하면 어떻게 되는지 한번 보겠습니다.

# tensor 생성 (requres_grad= False)
x = torch.tensor(2.0, requires_grad=False)

# tensor x에 대한 함수 y 생성
y = 2*x**2 + 1

print(x) # tensor(2.)
print(y) # tensor(9.)

y에서 grad_fn이 만들어지지 않은것을 볼 수 있습니다!

 

# [step2] backward() 실행

backward() 주어진 tensor에 대한 기울기(gradient)의 합을 계산합니다.
grad() 주어진 tensor에 대한 기울기(gradient)의 합을 계산하고 반환합니다.
# print(x.grad)
# >> None

#역전파 실행 
y.backward()
# print(y.backward()) >> None

print(x.grad)
# >> tensor(8.)

 

2) 원소가 여러개인 경우

  # [step1] tensor 생성 (requires_grad=True)

# <1> First Layer
x = torch.tensor([[1.0, 2.0 ,3.0],[4.0,5.0,6.0]], requires_grad=True)

print(x)
# tensor([[1., 2., 3.],
#         [4., 5., 6.]], requires_grad=True)

y = 2*x + 1
print(y)
# tensor([[ 3.,  5.,  7.],
#         [ 9., 11., 13.]], grad_fn=<AddBackward0>)

# <2> Second Layer
z = y**2
print(z)
# tensor([[  9.,  25.,  49.],
#         [ 81., 121., 169.]], grad_fn=<PowBackward0>)

out = z.mean()
print(out)
# tensor(75.6667, grad_fn=<MeanBackward0>)

> 하나의 연산을 하나의 layer로 보면, 위의  연산

 y = f(x),  z = g(y)은 두개의 layer로 이루어졌다고 할 수 있습니다.

여기서 신기한 점은, 매 연산마다, 심지어 mean() 함수로한 연산까지 모두 트래킹이 가능하도록 grad_fn으로 기록하고 있다는 것입니다.

 

# [step2] backward(), grad() 실행

out.backward()
print(x.grad)

# tensor([[2.0000, 3.3333, 4.6667],
#         [6.0000, 7.3333, 8.6667]])

우리는 x를 선형변환(2x+1)연산으로 y로, y를 제곱(y^2)해서  z로, z를 평균내서 out을 구했는데,

grad() 함수를 호출하니 갑자기 return값으로 x의 미분값을 뱉어주었습니다!

requires_grad = True로 설정하기 때문에 모든 연산을 tracking 한다고는 하지만,

미분을 구하는 것은 도대체 어떤 프로세스로 구한것일까요?

수식이 필요하기 때문에 손글씨로 정리해보았습니다. 

 

> 손으로 x의 여섯개의 원소중에서 1,2,3번만 구해보았습니다.

앞에서 x.grad() 함수로 구했던 결과를 확인해보면, 동일한것을 볼 수 있습니다!

 >> tensor([[2.0000, 3.3333, 4.6667], [6.0000, 7.3333, 8.6667]])

 

<Queston> requires_grad = True를 하면 무슨 일이 일어나길래, grad()를 호출하면 바로 미분값을 합산해줄까?
- 연산 그래프(Computational Graph)
: autograd는 텐서에서 및 실행된 모든 연산들의 기록을 Function 객체로 구성된 방향성 비순환 그래프(DAG; Directed Acyclic Graph)에 저장(keep)합니다. 이 방향성 비순환 그래프(DAG)의 잎(leave)은 입력 텐서이고, 뿌리(root)는 결과 텐서입니다. 이 그래프를 뿌리에서부터 잎까지 추적하면 연쇄 법칙(chain rule)에 따라 변화도를 자동으로 계산할 수 있습니다.

순전파 단계에서, autograd는 다음 두 가지 작업을 동시에 수행합니다:

1) 요청된 연산을 수행하여 결과 텐서를 계산하고,
2) DAG에 연산의 변화도 기능(gradient function) 를 유지(maintain)합니다.


역전파 단계는 DAG 뿌리(root)에서 .backward() 가 호출될 때 시작됩니다. 
autograd는 이때

1) 각 .grad_fn 으로부터 변화도를 계산하고,
2) 각 텐서의 .grad 속성에 계산 결과를 쌓고(accumulate),
3) 연쇄 법칙을 사용하여, 모든 잎(leaf) 텐서들까지 전파(propagate)합니다.

[Computation Graph] https://discuss.pytorch.org/t/storage-and-re-computation-of-intermediate-weight-back-propagated-gradients/29782/1 

 

 

 

 

 

반응형

댓글