Link

실습: 경사하강법 구현

우리는 경사하강법gradient descent을 통해 함수의 출력을 최소화하는 함수의 입력을 찾을 수 있습니다. 파이토치에서도 경사하강법을 위한 자동편미분Auto Grad 기능을 제공하며, 이 기능을 통해 경사하강법을 구현할 수 있습니다. 그리고 이 경사하강법은 나중에 딥러닝에서 유용하게 활용될 것입니다.

이번에는 경사하강법을 통해 랜덤하게 생성한 텐서가 특정 텐서 값을 근사하도록 파이토치를 통해 구현해보겠습니다. 그럼 여기서 함수의 출력은 목표 텐서와 랜덤 텐서 사이의 차이가 될 것이고, 함수의 입력은 랜덤 생성한 텐서의 현재 값이 될 것입니다. 따라서 랜덤 생성 텐서의 값을 경사하강법을 활용하여 이리저리 바꿔가며 함수의 출력 값(목표 텐서와의 차이 값)을 최소화하도록 하겠습니다.

먼저 구현에 필요한 패키지들을 불러옵니다.

import torch
import torch.nn.functional as F

그리고 목표 텐서를 생성합니다.

target = torch.FloatTensor([[.1, .2, .3],
                            [.4, .5, .6],
                            [.7, .8, .9]])

그리고 랜덤 값을 갖는 텐서를 하나 생성합니다. 그리고 중요한 점은 이 텐서의 requires_grad 속성이 True가 되도록 설정해줍니다.

x = torch.rand_like(target)
# This means the final scalar will be differentiate by x.
x.requires_grad = True
# You can get gradient of x, after differentiation.

print(x)

그럼 다음과 같이 랜덤 생성 텐서의 값이 출력될 것입니다.

tensor([[0.8693, 0.6091, 0.5072],
        [0.7900, 0.3290, 0.6847],
        [0.3789, 0.1166, 0.3602]], requires_grad=True)

이제 while 반복문을 사용하여 두 텐서가의 차이가 변수 threshold의 값보다 작아질 때까지 반복하여 미분 및 경사하강법을 수행합니다.

threshold = 1e-5
learning_rate = 1.
iter_cnt = 0

while loss > threshold:
    iter_cnt += 1
    
    loss.backward() # Calculate gradients.

    x = x - learning_rate * x.grad
    
    # You don't need to aware following two lines, now.
    x.detach_()
    x.requires_grad_(True)
    
    loss = F.mse_loss(x, target)
    
    print('%d-th Loss: %.4e' % (iter_cnt, loss))
    print(x)

여기서 가장 주목해야 할 점은 backward 함수를 통해 편미분을 수행한다는 것입니다. 그럼 편미분을 통해 얻어진 그래디언트들이 x.grad 에 자동으로 저장되고, 이 값을 활용하여 경사하강법을 수행합니다. 참고로 backward를 호출하기위한 텐서의 크기는 스칼라scalar여야 합니다. 만약 스칼라가 아닌 경우에 backward를 호출한 경우, 파이토치가 오류를 발생시키며 친절하게 오류의 원인을 알려줍니다.

다음은 코드의 실행 결과입니다. 점차 손실 값이 줄어드는 것을 볼 수 있고, 실제로 텐서 x의 값이 목표 텐서 값에 근접해가는 것을 볼 수 있습니다.

1-th Loss: 1.2450e-01
tensor([[0.6984, 0.5182, 0.4612],
        [0.7033, 0.3670, 0.6659],
        [0.4502, 0.2685, 0.4801]], requires_grad=True)
2-th Loss: 7.5312e-02
tensor([[0.5654, 0.4475, 0.4253],
        [0.6359, 0.3966, 0.6512],
        [0.5057, 0.3866, 0.5734]], requires_grad=True)
3-th Loss: 4.5559e-02
tensor([[0.4620, 0.3925, 0.3975],
        [0.5835, 0.4196, 0.6398],
        [0.5489, 0.4785, 0.6460]], requires_grad=True)
4-th Loss: 2.7560e-02
tensor([[0.3815, 0.3497, 0.3758],
        [0.5427, 0.4374, 0.6310],
        [0.5825, 0.5499, 0.7024]], requires_grad=True)
.
.
.
16-th Loss: 6.6194e-05
tensor([[0.1138, 0.2073, 0.3037],
        [0.4070, 0.4969, 0.6015],
        [0.6942, 0.7877, 0.8903]], requires_grad=True)
17-th Loss: 4.0043e-05
tensor([[0.1107, 0.2057, 0.3029],
        [0.4054, 0.4976, 0.6012],
        [0.6955, 0.7905, 0.8925]], requires_grad=True)
18-th Loss: 2.4224e-05
tensor([[0.1083, 0.2044, 0.3022],
        [0.4042, 0.4981, 0.6009],
        [0.6965, 0.7926, 0.8941]], requires_grad=True)
19-th Loss: 1.4654e-05
tensor([[0.1065, 0.2035, 0.3017],
        [0.4033, 0.4986, 0.6007],
        [0.6973, 0.7942, 0.8954]], requires_grad=True)
20-th Loss: 8.8647e-06
tensor([[0.1050, 0.2027, 0.3014],
        [0.4026, 0.4989, 0.6006],
        [0.6979, 0.7955, 0.8965]], requires_grad=True)

만약 학습률 변수를 조절한다면 텐서 x가 목표 텐서에 근접해가는 속도가 달라질 수 있을 것입니다.