본문 바로가기

Programming Project/Pytorch Tutorials

Pytorch 머신러닝 튜토리얼 강의 4 (Back-propagation and Autograd)


투명한 기부를 하고싶다면 이 링크로 와보세요! 🥰 (클릭!)

바이낸스(₿) 수수료 평생 20% 할인받는 링크로 가입하기! 🔥 (클릭!)

2018/07/02 - [Programming Project/Pytorch Tutorials] - Pytorch 머신러닝 튜토리얼 강의 1 (Overview)

2018/07/02 - [Programming Project/Pytorch Tutorials] - Pytorch 머신러닝 튜토리얼 강의 2 (Linear Model)

2018/07/03 - [Programming Project/Pytorch Tutorials] - Pytorch 머신러닝 튜토리얼 강의 3 (Gradient Descent)




지난 글에서 우리는 간단하게


x를 입력으로 받아서 예측한 y를 output으로 내보내는 간단한 모델을 만들고,


gradient를 통해서 network를 업데이트 해 보았습니다.



하지만 파라미터들이 많아지고 관계식이 복잡해질 수록, 최종 loss에 대한 각 파라미터들의 미분값을 구하는것이 힘들어집니다.


관계식이 복잡해진다는것은 예를 들어, 



과 같이 어떤 변수와 변수간의 상관관계들로 서로가 연관되어있고, 우리가 아는것은 y로부터의 loss function밖에 없을 때와 같은 상황을 말합니다.


지금은 단순히 한다리만 건너면 input x 에 도달할 수 있지만, 이 관계식이 몇십개가 된다면 모든 파라미터들에 대한 pure loss from y를 구하기는 힘들것입니다.


이번 글에서는 어떻게 이 것을 쉽게 계산하게 되는지, 어떤 이론들을 통해서 이것을 잘 계산하게 되는지 알아보겠습니다.


(참고로, 이론적인 부분이 이해가 안간다면 대략적인 흐름만 알아두어도 충분하고, 더욱 디테일한 강의가 듣고 싶다면 CS231n에서 backpropagation 설명을 잘 했던것으로 기억하니 찾아보시기를 추천합니다.)









Chain Rule


우리는 이걸 조금 더 쉽게 계산하기 위해서 Chain Rule라는 것을 이용합니다.



오른쪽의 식은 df/dx 를 구하기 위해서는 df/dg * dg/dx를 하면 된다는 의미입니다.


결국 f가 loss function이라고 하고 , x가 loss에 대한 gradient를 계산하고 싶은 파라미터라면, 다시말해 df/dx를 구하기 위해서는 직접 계산된 df / dg와 dg / dx를 각각 알고있기만 하면 이를 쭉 이어붙여서 곱하면 됩니다.









Back Propagation


추가적으로, chain rule이라는 것을 사용할 수 있기 때문에, 우리는 맨 마지막 Loss에 대한 미분값만 구하게되면, 그 이전의 것들을 줄줄이 사탕처럼 쭉쭉 이어붙여서 결국 모든 파라미터들에 대해서 Loss에 대한 미분값을 구할 수 있게 됩니다.


이렇게 쭉쭉 미분값을 뒤로 곱해서 나아가는것을 역전파(Back propagation)이라고 합니다.












Computational Graph


Computational graph를 그려봅시다. ( 무엇인지 모르신다면 그냥 아래와 같이 그림으로 표현하는 것이라고 생각하면 쉽습니다 )


원래는 y = wx + b 였는데, +b를 빼서 조금만 간단히 해 봅시다.



기존 우리의 모델에 대해서만 computational graph를 그리면 위와 같을 것이고,



Loss 까지 포함한 graph를 그리면 다음과 같을 것입니다.


그럼 이제 어떤 x값과 y값이 있을 때, loss를 직접 구해보도록 합시다.


(이 과정을 보통 Forward pass라고 합니다)





좋습니다.


그렇다면 이제 이 loss로부터 w의 미분값을 구해봅시다.





x와 y는 데이터(정해진 값) 이기 때문에 따로 gradient를 계산하지 않고, 우리가 파라미터를 바꿀 때 실제로 바뀌는 값에 대해서만 chain rule을 적용하기 위한 loss에 대한 gradient를 찾아주게되면 다음과 같습니다.


잘 이해가 가지 않는다고 해도, 전반적인 큰 흐름을 이해했다면 코딩에 무리가 없을 것입니다. 왜냐하면 파이토치로 코딩하면 직접 미분할 일이 없거든요!








Code ( Using Pytorch! )


파이토치는 행렬 계산을 포함한 많은 딥러닝 구현을 편하게 해주는 라이브러리입니다.


파이토치 안에서 자료형은 'Tensor' 라는 것을 씁니다.


텐서는 계산의 기본 단위이며,


혹시 numpy를 써봤다면 numpyArray같은 것과 비슷하다고 보시면 됩니다.


import torch
x_data = [1.0,2.0,3.0]
y_data = [2.0,4.0,6.0]

w=torch.tensor([1.0],requires_grad = True)

파이토치에서는 직접 미분을 해줘서 gradient descent를 일일히 수행해 줄 필요가 없습니다.


저 소스코드와 같이 forward , backward propagation 을 할 때, requires_grad option에 따라서 gradient descent를 적용할 때 변수값을 바꿀것인지, 아니면 그냥 그대로 둘 것인지 (ex, input값으로 받는 x 등의 경우에는 gradient descent때 변경하면 안되곘죠 ) 정할 수 있습니다.


코드에 requires_grad를 true로 한 w는 추후에 backward propagation해서 gradient descent를 수행할 때 값이 loss를 줄이는 쪽으로 바뀝니다.






자 이제 forward 코드 부분을 짜봅시다.


그러니까 다시 말해서, 우리의 모델이 input 데이터 x에 대해서 예측을 수행하는 코드를 짜 보자는 말입니다.


지금부터 우리가 프로그래밍해서 수행되는 모든 연산은 pytorch 내부적으로 기억을 해 두었다가 back propagation을 할 때 영향을 주게 됩니다.


def forward(x):
    return x*w

아직은 모델이 복잡하지 않아 forward 함수가 간단하네요


이제 loss function을 정의 해 봅시다.

def loss(x,y):
    y_pred=forward(x)
    return (y_pred-y)*(y_pred-y)

x 와 y를 입력받은 후 forward함수를 통해 예측한 y를 저장하고,


기존의 y와 비교한 차이의 제곱을 하여 리턴하는 함수입니다.


그렇다면 이제 실제로 학습을 시켜야겠죠?


Training loop을 통해 x_data와 y_data를 묶어서 하나 하나 돌려줘봅시다.

# Training loop
print("predict (before training)" , 4, forward(4))
for epoch in range(10):
    for x_val, y_val in zip(x_data, y_data): #1
        l = loss(x_val, y_val) #2
        l.backward() #3
        print("grad: ", x_val, y_val,w.grad.data[0])
        w.data = w.data - 0.01 * w.grad.data #4
        # Manually zero the gradients after updating weights
        w.grad.data.zero_() #5

    print("progress:", epoch, l.data[0])
print("predict (after training)" , 4, forward(4))

#1 : x[i]와 y[i]가 서로 짝을 이뤄 x_val , y_val로 들어옵니다.


#2 : l에 loss를 계산한 값을 넣어줍니다.


#3 : l을 loss라고 생각하고 그 곳으로부터 back propagation을 실행합니다.

#4 : back propagation을 해서 얻은 값은 w.grad.data에 들어있습니다. 그러므로 w.data를 gradient에 작은 수 (learning rate라고 부릅니다)를 곱해서 빼줍니다.


헷갈린다면 잘 생각해봅시다. 낮은 값으로 가려면 기울기를 ''주어야 합니다.


#5 : 파이토치는 매번 backward()가 실행될 떄 gradient가 더해지는 구조이므로 zero_()를 해서 0으로 초기화 해 주어야 합니다.



자 이렇게 결과를 뽑고 나면 4를 넣은 input이 8이 나와야 하는데 gradient가 줄어들면서 가면갈수록 근접하는것을 볼 수 있습니다.


predict (before training) 4 tensor(4.)
grad:  1.0 2.0 tensor(-2.)
grad:  2.0 4.0 tensor(-7.8400)
grad:  3.0 6.0 tensor(-16.2288)
progress: 0 tensor(7.3159)
grad:  1.0 2.0 tensor(-1.4786)
grad:  2.0 4.0 tensor(-5.7962)
grad:  3.0 6.0 tensor(-11.9981)
progress: 1 tensor(3.9988)
grad:  1.0 2.0 tensor(-1.0932)
grad:  2.0 4.0 tensor(-4.2852)
grad:  3.0 6.0 tensor(-8.8704)
progress: 2 tensor(2.1857)
grad:  1.0 2.0 tensor(-0.8082)
grad:  2.0 4.0 tensor(-3.1681)
grad:  3.0 6.0 tensor(-6.5580)
progress: 3 tensor(1.1946)
grad:  1.0 2.0 tensor(-0.5975)
grad:  2.0 4.0 tensor(-2.3422)
grad:  3.0 6.0 tensor(-4.8484)
progress: 4 tensor(0.6530)
grad:  1.0 2.0 tensor(-0.4417)
grad:  2.0 4.0 tensor(-1.7316)
grad:  3.0 6.0 tensor(-3.5845)
progress: 5 tensor(0.3569)
grad:  1.0 2.0 tensor(-0.3266)
grad:  2.0 4.0 tensor(-1.2802)
grad:  3.0 6.0 tensor(-2.6500)
progress: 6 tensor(0.1951)
grad:  1.0 2.0 tensor(-0.2414)
grad:  2.0 4.0 tensor(-0.9465)
grad:  3.0 6.0 tensor(-1.9592)
progress: 7 tensor(0.1066)
grad:  1.0 2.0 tensor(-0.1785)
grad:  2.0 4.0 tensor(-0.6997)
grad:  3.0 6.0 tensor(-1.4485)
progress: 8 tensor(1.00000e-02 * 5.8279)
grad:  1.0 2.0 tensor(-0.1320)
grad:  2.0 4.0 tensor(-0.5173)
grad:  3.0 6.0 tensor(-1.0709)
progress: 9 tensor(1.00000e-02 * 3.1854)

predict (after training) 4 tensor(7.8049)



그럼 여기까지로 이번 글을 마치겠습니다.


다음 글에서는 더욱 편한 방법을 알아보죠