본문 바로가기

DEVELOP_NOTE/ML

Pytorch의 'AutoGrad' 과정을 하나하나 뜯어보자!

우리는 보통 딥러닝 모델을 학습할때, Pytorch, TensorFlow, Keras와 같은 딥러닝 라이브러리를 사용한다.

딥러닝 라이브러리들을 사용할때의 장점은 아래의 몇가지로 요약해볼 수 있다.

 

1) 복잡한 수학적 연산을 간편한 API 구조 뒤에 숨김으로써, 딥러닝 모델의 학습 및 개발 과정을 단순화 해준다.

2) 모델 파라미터를 업데이트하기 위한 미분과정에 대한 자동화 기능을 제공한다.

3) GPU를 사용한 데이터의 batch 처리, 멀티스레딩, 데이터 로딩등의 기능을 지원한다.

 

 

그 중에서도 딥러닝 라이브러리를 사용하는 가장 주된 이유는 2번에 해당하는 'AutoGrad' 기능일 것이다.

 

먼저, 딥러닝 모델이 데이터를 통해 학습을 진행하는 과정을 간단하게 요약하면, 

1) 먼저 예측값을 도출하고, 예측값과 정답값의 차이를 통해 loss를 구한다.
2) 그리고, loss에 영향을 미친 parameter에 대하여, parameter가  loss에 영향을 미친 정도에 대해 편미분값을 통해 gradient를 계산한다.
3) 계산된 gradient를 통해 모델(네트워크) parameter의 weight와 bias를 업데이트하며 학습을 진행한다.

 

와 같은 과정, 즉 backpropagation 과정을 거친다.

그리고 딥러닝 라이브러리는 'AutoGrad' 기능을 통해 위 계산 과정을 자동으로 수행하는 기능을 제공한다.

 

오늘은 딥러닝 라이브러리 중 pytorch 라이브러리에서 제공하는  AutoGrad 기능이, 구체적으로 어떤 과정을 통해

backpropagation을 수행하는지 자세히 살펴보도록 하자.


 

먼저, AutoGrad의 개념을 한번 더 정리해보자.

 

'AutoGrad' :

     1) *variable연산을 통해 생성 된 계산 그래프를 사용하여, 변수들의 그래디언트를 자동으로 계산하는

          Pytorch의 기능을 의미한다.

     2) *variable 연산 과정을 바탕으로, 역전파를 자동으로 수행하여, 각 변수의 그래디언트를 계산한다.

 

위 설명을 보면, 'variable 연산'이라는 과정이 결국 autograd를 수행하는데 핵심적인 역할을 하게되는데,

variable은 다음과 같은 기능을 담당한다.

 

 'Variable 연산' : 

     1) Pytorch에서 변수들간의 연산을 의미하며, 이는 자동으로 계산 그래프를 구성하여 나중에 그래디언트를 계산할 수 있게 한다.

     2) 사용자가 직접 그래디언트를 계산하기 위한 수식을 정의할 필요없이 연산과정을 자동으로 추척하고 저장한다는 특징을 가진다.

 

* 참고
과거 pytorch에서는 연산과정을 기록하기 위해, 아래와 같이 variable클래스로 tensor를 감싸(wrapping)미분이 가능한 형태로 만들었어야 했으나, 최근 Pytorch에서는 tensor와 variable개념이 통합되어, 모든 tensor는 variable클래스로 감싸지 않고도, 자동 미분을 지원하기때문에, requires_grad() 속성을 사용해 그라디언트 계산 여부를 설정할 수 있다. 다만, 해당 포스팅에서는 자동 미분을 할 수 있는 연산과정을 자동으로 추적하고 저장하는 과정을 variable이라는 개념으로 설명하도록 하겠다.

 


 

AutoGrad에 대해 어느정도 개념적인 이해가 되었다면, 이제 실제로 pytorch 코드내에서 자동 그래디언트 계산과정이

어떻게 진행되는지 예시를 통해 살펴보자.

 

 

1) 최초 변수 선언

먼저, 2x2의 1만 있는 행렬을 Tensor를 선언해주도록 하고, requires_grad옵션을 True로 설정하여,

해당 변수는 gradient 계산이 필요한 변수로 설정한다.

만약, requires_grad를 False로 설정한 경우, 이후에 설명하겠지만 grad_fn이라는 메서드에 gradient를 계산하기 위한

함수(연산기록)가 기록(저장)되지않았으므로, gradient계산이 불가하다는 *오류가 발생한다.

RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

 

즉, requires_grad=True로 설정한다는 것은 "해당 변수는 gradient계산이 필요하며, gradient를 계산하기 위해 이후 연산과정에 대해 그래디언트를 계산할 수 있는 함수들을 저장하겠다." 라는 의미를 가지는 것이다.

해당 변수에 대한 gradient 함수의 추적/기록 설정

 

 

2) 내부 메서드 : data, grad, grad_fn

자, 그렇다면 위에서 언급한 gradient계산을 위한 정보들은 어디에서 기록되고 추적되는걸까?

 

pytorch에서는 'requires_grad=True'로 설정된 tensor의 경우 아래와 같은 메서드를 통해 그래디언트 연산 결과값과 

그래디언트를 계산할 수 있는 함수정보를 기록한다

1) data (현재는 굳이 사용할 필요 없는 메서드로, 변수명 호출을 통해 데이터 확인가능 함. 참고만 할 것)
Tensor 형태의 실제 변수 데이터가 담기고, 이를 확인할 수 있는 메서드이다. 
다만, 최신 Tensor에서는 variable이 tensor로 대체된 관계로, data 속성으로 굳이 원본 데이터를 확인할 필요없이
변수명 그대로 호출 하여 확인할 수 있다.

2) grad
data가 거쳐온 layer의 미분값이 축적되고, 확인할 수 있는 매서드이다.
backward()함수가 호출 될 때, Pytorch는 tensor의 그래디언트를 자동으로 계산하고, 이를 grad속성에 할당한다. 
weight 및 bias의 update를 위한 미분 계산값, 즉, gradient desent value가 여기에 들어가게 된다.

3) grad_fn
해당 Tensor를 생성한 연산을 나타내며,미분값을 계산한 함수에 대한 정보가 축적된다.
즉, '어떤 연산(함수)에 대한 backward를 진행했다'라는 정보가 들어가게 되는데,
Pytorch의 모든 연산(Operation)이, 결과 텐서에 대해 grad_fn 을 가지고 있으며, 이를 통해 그래디언트 계산 시
역전파를 위한 연산의 체인을 구성하게 된다.
사용자가 직접 생성한 텐서는 아래 예시와 같이 grad_fn이 None으로 출력된다.

data, grad, grad_fn 출력 예시

 

 

3) Tensor 연산

 

위 Tensor연산을 살펴보면, 

먼저, 첫번째 연산에서는 텐서에 특정 상수를 더하는 연산을, 두번째는 제곱, 세번째는 합계 연산을 수행했다.

그리고, 각 메서드를 확인해보면 grad 메서드에는 아직 gradient계산이 진행되지않았기때문에 None으로

출력되는것을 확인할 수 있다.

그리고, grad_fn에는 아래와 같은 내용이 출력되는데, 

<AddBackward0 object at 0x7fe6809d4dc0>

 

앞의 'AddBackward0'은 해당 tensor가 수행한 '연산의 종류'를 나타내며, 뒤의 '0x7fe6809d4dc0'

고유식별자 정보를 나타낸다.

고유식별자 정보는 해당 텐서를 생성한 연산을 가리키고, 이 연산 정보를 통해 pytorch는 *계산그래프를 역순으로

거슬러올라가면서, 그래디언트를 계산할 수 있다.(backward과정)

* 계산그래프 (중요)

: 계산그래프는 엣지와 노드로 구성되어 있으며, 노드는 텐서(데이터)를, 엣지는 텐서간의 연산정보
(예를들어 덧셈, 곱셈, 지수연산 등)를 나타낸다.

: 'backward()'가 호출되면, 계산 그래프는 마지막 연산(일반적으로 손실함수에서 시작함)부터 시작해서, 각 연산의 그래디언트를 계산하고, 이를 통해 입력텐서의 그래디언트를 업데이트하게 된다. 그리고 이 과정을 chain rule로 계산하게된다.

 

 

4) AutoGrad 연산 및 Backpropagation

 

우리가 보통 model의 output과 target사이에서 구한 loss값에 대해 'loss.backward()'를 수행하듯이,

마지막 연산의 결과값에 대해 backward()를 진행하게 되면, 계산그래프를 통해 각 연산과정에 대해 역순으로

거슬러올라가며 미분값을 계산하게 된다.

그리고, 최초 변수의 grad 메서드에서 계산된 gradient값을 확인할 수 있다.

 

여기서 잠깐 주목할 부분이 있다.

최종 backward()직전에, 'out.sum()' 작업이 있는데, 

최종 tensor에 대해 sum()을 하는 이유는 backward()가 기본적으로 스칼라값에 대한 그래디언트를 계산하기 때문이다.

즉, 'backward()'가 스칼라 값에 대해서만 호출될 수 있는 이유는 그래디언트 계산이 스칼라 값에 대한 미분으로 정의되기 때문이다.

이는 딥러닝에서 손실함수(loss func)의 값을 최소화하는것과 직접적으로 관련이 있고, 손실함수는 일반적으로 스칼라 값이다!

 

다시 코드로 돌아가서, 'out=e.sum()'은 'e'텐서의 모든 요소를 더하여 스칼라값을 생성한다.

그리고, 이 스칼라에 대해 'backward()'함수를 호출함으로써 해당 스칼라 값이 'a', 'b', 'c'등의 입력 텐서에 대해 어떻게 변화하는지에

대한 그래디언트(변화율)를 계산할 수 있게된다.

 

따라서, backward()함수내부에서 연쇄법칙(chain Rule)을 사용하여, 주어진 스칼라 값(out)에 대한 입력 텐서들(a, b, c)의 미분을 계산한다. 만약, 'backward()'에 인자가 주어지지 않는다면, 이는 backward()가 호출된 스칼라값에 대한 그래디언트가 1로 가정된다는것을 의미한다. 즉, out.backward()는 'out'이라는 스칼라값에 대해 'out.backward(torch.tensor(1.))'을 호출한것과 같으며, 이는 'out'을

생성하는데 기여한 모~~든 텐서에 대한 그래디언트를 계산하라는 명령과 같다.

 


 

 

자, 이제 오늘 알아본 위 AutoGrad 과정을 간단하게 정리해보면, 

1) 텐서를 선언할때, requires_grad=True로 옵션을 선택하여 연산과정을 자동으로 추적하도록 설정한다.
2) 해당 tensor에 대해서 연산을 진행할 경우, grad_fn 메서드에 연산의 종류정보와 결과값을 얻은 연산의
     그래프 고유식별자 정보를 기록한다.
3) backward() 호출 시, 계산그래프를 통해 변수간의 연산을 단계별로 추적하며, 미분을 계산한다.
4) 최초 변수의 grad메서드에 최종 계산된 그래디언트 값을 확인할 수 있다.

 

 


 

 

마치며

이번 AutoGrad 정리를 통해,

그 동안 딥러닝 모델 학습 코드를 작성하면서, 기계적으로 작성했던 Auto backpropagation 과정에 대해 조금 더 깊게 이해할 수 있었다.

Pytorch에서는 AutoGrad외에도 딥러닝 모델 개발에 있어, 다양한 편의 기능들을 제공하고있는데,

앞으로도 실제 모델 개발시에 유용하게 활용할 수 있는 다양한 기능들에 대해 하나하나 공부하고 소개할 수 있도록 해야겠다.

 

 

 

 

 

Reference

 

PyTorch: Variable과 autograd — PyTorch Tutorials 0.3.1 documentation

PyTorch: Variable과 autograd 하나의 은닉 계층(Hidden Layer)과 편향(Bias)이 없는 완전히 연결된 ReLU 신경망에 유클리드 거리(Euclidean Distance)의 제곱을 최소화하여 x로부터 y를 예측하도록 학습하겠습니다.

9bow.github.io