본문 바로가기
처음부터 하는 딥러닝

[딥러닝 기초] 학습은 어떻게 이루어질까

by 빈이름 2023. 4. 30.

이번에는 인공지능의 학습이 어떻게 이루어지는지 간단한 예시를 통해 알아보겠습니다. 이번 포스트를 통해서 "아 인공지능은 대략 이런 식으로 학습되는구나" 하고 대강 알 수 있길 바랍니다.

1.문제

문제를 하나 내보겠습니다. 아래와 같이 왼쪽의 입력값에 의해 오른쪽의 출력값이 나오는 데이터 통계가 있다고 가정해 봅시다.

0 → 3.025
3 → 10.048
4 → 14.622
7 → 23.332
9 → 24.827

이 데이터를 그래프로 그려보면 다음과 같이 그려질 것입니다.

x축은 입력값, y축은 출력값을 나타낸다.

이 때 만약 입력값이 6이 들어온다면 출력값은 어떻게 나올까요?

 

저라면 쉽게 생각해서 대강 점을 따라 선을 그어볼 것 같습니다.

그 뒤에 해당 선 그래프를 보고 y가 대충 18정도로 보이니 18이라고 예측을 하면 대강 비슷하지 않을까요?

다시, 이번엔 수식으로 직관적으로 생각해 보겠습니다. 위와 같은 직선 그래프는 다음과 같은 일차방정식 형태로 나타낼 수 있습니다.

 

$$y=wx+b$$

 

여기서 x는 입력값을, y는 출력값을 나타냅니다. 그렇다면 제가 그린 주황색 그래프와 같이 그리기 위해선 $w$와 $b$에 어떤 값이 들어가야 할까요?

 

가장 쉬운 방법은 $w$와 $b$에 아무 값이나 넣어보고, 주황색 선과 가까워질 때까지 값을 수정해 보면 될 것 같습니다. 처음엔 대충 $w=4$, $b=1$ 정도로 찍어보겠습니다.

w=4, b=1로 설정했을 때의 그래프(초록색)

주황색 그래프와 차이가 큰 것 같네요... 그래프가 조금 더 완만해져야할 것 같습니다. $w$를 2정도로 줄여보고, $b$도 3저도로 올려보겠습니다.

w=2, b=1일 때의 그래프(초록색)

이번엔 너무 완만해진 것 같습니다. 조금만 더 기울기를 증가시켜보겠습니다. $w$를 3으로 설정하면 어떨까요?

w=3, b=1일 때의 그래프(초록색)

다시 더 가팔라졌네요. 아무래도 $w$는 2랑 3사이 어딘가쯤이어야 할 것 같습니다.

 

이렇게 적절한 $w$와 $b$를 찾는 과정이 조금 야매같아 보이나요? 놀랍게도 인공지능의 학습 과정은 이와 매우 유사합니다. 딥러닝 학습 과정은 입력값과 출력값 사이의 적절한 관계를 정의하기 위해 적절한 수치를 계속해서 조정해 나가는 과정입니다. 이제부터 본격적으로 인공지능 학습 과정을 살펴보겠습니다.

2. 문제 정의

손쉬운 계산과 시각화를 위해 아래 노트북 코드와 함께 살펴보도록 하겠습니다.

https://drive.google.com/file/d/1T4vFKIcNW_9ZkWIbTuvxU43gZQBLGVnB/view?usp=sharing

 

very simple deeplearning.ipynb

Colaboratory notebook

drive.google.com

 

우선, 입력값(x)과 출력값(y)을 확실히 정의하고 넘어가겠습니다. 위 문제의 입력값과 출력값은 다음과 같습니다.

$x=[0, 3, 4, 7, 9]$
$y=[3.025, 10.048, 14.622, 23.332, 24.827]$

저희가 풀고자 하는 문제는 데이터에 기반해서 입력값 x가 들어왔을 때, 적절한 출력값 y를 출력하는 것입니다.

이렇게 특정 값을 예측해내야 하는 문제를 '회귀'라고 합니다.

import random

random.seed(42)

def solution(xs):
    return [x * (2+random.random()) + (3 +random.random()) for x in xs]

x = [0, 3, 4, 7, 9]
y = solution(x)

'''
<출력값>
x : [0, 3, 4, 7, 9]
y : [3.025010755222667, 10.048298693256182, 14.622584344078962, 23.332195806563334, 24.827093596605504]
'''

3. 모델 정의

다음으로 문제를 해결하기 위한 '모델'을 정의해 보겠습니다. 모델은 입력값 x에 대해 올바른 y값을 출력하는 일종의 방정식과 같습니다.

저희는 데이터의 입력값과 출력값 사이의 관계가 $y=wx+b$와 같은 선형방정식이라고 '가정'을 할겁니다. 즉, 모델을 $y=wx+b$라고 정의한 것입니다. 여기서 $y$는 출력값을, $x$는 입력값입니다.

저희는 학습을 통해서 모델의 알맞은 $w$와 $b$를 찾아야 합니다. 학습을 통해 찾아야 하는 변수 $w$와 $b$를 모델의 '파라미터'라고 합니다.

 

앞서 했던 것과 같이 $w$와 $b$를 각각 4와 1로 설정해 보겠습니다. 모델은 $y=4x+1$이 되겠죠?

여기에 입력값 x들을 입력하면 예측값은 다음과 같이 나옵니다.

y'=[1, 13, 17, 29, 37]
# 초기값 설정
w = 4
b = 1

# 모델 정의
model = lambda x: w*x+b
preds = [model(a) for a in x]

import matplotlib.pyplot as plt

# 모델 그래프로 그려보기
plt.xlabel("X")
plt.ylabel("Y")
plt.plot(x, y, 'o')
plt.plot([model(a) for a in range(10)])

초기 모델 그래프

사람이라면, 직관적으로 적절한 기울기를 찾기 위해 $w$를 줄여야 하고, $b$를 증가시켜 봐야겠다고 생각할 수 있습니다. 그러나 눈도 직관도 없는 컴퓨터에게 이런 생각은 너무 어려운 일일 것입니다. 그렇다면 컴퓨터는 이를 어떻게 학습해야 할까요?

4. 손실 함수와 최적화 알고리즘

컴퓨터가 이를 처리하는 방식은 손실 함수와 최적화 함수를 사용합니다.

손실 함수(loss function)는 모델이 예측한 값과 실제 값이 얼마나 차이나는지를 측정합니다.

최적화 함수(optimizer)는 손실 함수를 토대로 모델의 파라미터 $w$와 $b$의 값을 갱신하는 역할을 합니다.

 

손실 함수는 실제 값과 예측 값 사이의 차이(손실값, loss)를 측정하기 위한 함수입니다. 이 손실값은 절대적인 양을 나타내며 항상 양수로 나타납니다. 회귀문제에서 자주 사용되는 손실 함수는 MSE(Mean Squared Error)MAE(Mean Absolute Error)입니다.

 

$MSE={1\over n}\Sigma^n_{i=1}(Y_i-Y_i')^2$

$MAE={1\over n}\Sigma^n_{i=1}|Y_i-Y_i'|$

 

$Y$는 실제 출력값, $Y'$은 모델의 예측값을 나타냅니다. 위 함수와 같이 손실값은 전체 데이터의 실제값과 예측값의 차이의 평균값이 됩니다.

 

이번엔 MSE를 사용해보겠습니다. 실제값 y=[3.025, 10.048, 14.622, 23.332, 24.827]과 예측값 y'=[1, 13, 17, 29, 37]의 MSE loss를 계산해보면 39.7538이 나옵니다.

def MAE(y, yhat):
    loss = 0
    for label, pred in zip(y, yhat):
        loss += (label - pred)**2
    return loss/len(y)

loss = MAE(y, preds)
print(round(loss, 4))

'''
<출력값>
39.7538
'''

 

 

최적화는 파라미터가 올바른 방향으로 갱신될 수 있도록 해주는 과정입니다. 이 올바른 방향을 결정하는 것은 손실 함수입니다. 구체적으로는, 손실 함수의 파라미터에 대한 미분을 통해 파라미터를 갱신합니다. 파라미터 $w$와 $b$는 다음과 같이 값이 갱신됩니다.

 

$w=w-\alpha {dL\over dw}$

$b=b-\alpha {dL\over db}$

 

여기서 $L$은 손실 함수를 의미하며, $\alpha$는 임의의 상수로, 학습 가중치(learning rate)라고 합니다. 학습 가중치는 모델의 학습 속도를 결정합니다. 여기선 $\alpha=0.01$로 설정하겠습니다.

 

최적화를 직접 수행해 보도록 하겠습니다! 우선 손실 함수를 각 파라미터 $w$와 $b$로 미분한 식을 계산해야 합니다.

 

${dL\over dw}={d\{y-(wx+b)\}^2\over dw}=-2x(y-wx-b)$

${dL\over db}={d\{y-(wx+b)\}^2\over db}=-2(y-wx-b)$

 

미분이 기억나신다면 직접 계산해봐도 좋고, 아니라면 그냥 '아 미분하면 식이 저렇게 되는구나' 하고 일단 넘어가면 됩니다. 미분은 파라미터를 올바른 방향으로 갱신시키는 핵심이기 때문에 이 부분에 대해서는 추후에 다시 따로 자세히 살펴 보도록 하겠습니다.

 

첫번째 입력값과 출력값 쌍인 x=0, y=3.025 를 이용해 학습을 해보겠습니다. 우선 위의 ${dL \over dw}$와 ${dL \over db}$에 x(=0), y(=3.025), w(=4), b(=1)을 대입해 각 미분값을 계산합니다.

 

${dL \over dw}=0$

${dL \over db}=-4.05$

 

이 미분값을 이용해 파라미터 $w$와 $b$는 아래와 같이 갱신됩니다.

 

$w=4-0.01*0=4$

$b=1-0.01*(-4.05)=1.0405$

 

def optimize(x, y, w, b, alpha=0.01):
    dw = -2*x*(y-(w*x)-b)
    db = -2*(y-(w*x)-b)
    w -= alpha*dw
    b -= alpha*db
    return w, b
    
print(optimize(0, 3.025, 4, 1))
'''
<출력값>
(4, 1.0405)
'''

 

파라미터 값이 갱신이 되었습니다! 나머지 데이터 쌍들에 대해서도 학습을 마저 수행해 보겠습니다.

for xx, yy in zip(x, y):
    w, b = optimize(xx, yy, w, b)
    print("w :", round(w, 4), "b :", round(b, 4))

'''
<출력값>
w : 4.0 b : 1.0405
w : 3.8205 b : 0.9807
w : 3.6893 b : 0.9479
w : 3.2076 b : 0.879
w : 2.3219 b : 0.7806
'''

파라미터값이 점차 변화하는 것이 보이시나요? 이렇게 주어진 데이터셋을 모두 한번씩 학습한 것을 "1에포크 학습했다" 라고 합니다.

갱신된 파라미터 값이 더 좋은지 확인하기 위해 1에포크 학습한 모델의 손실값을 다시 계산해 보겠습니다.

print("before training :", round(loss, 4))
model = lambda x: w*x+b
preds = [model(a) for a in x]

loss = MAE(y, preds)
print("after training :", round(loss, 4))
'''
<출력값>
before training : 39.7538
after training : 16.1313
'''

loss가 16.1313으로 학습 전의 거의 절반으로 줄어든 것을 확인할 수 있습니다. 학습된 모델을 이용해 그래프를 다시 그려보겠습니다.

plt.xlabel("X")
plt.ylabel("Y")
plt.plot(x, y, 'o')

init_model = lambda x: 4*x+1
plt.plot([init_model(a) for a in range(10)], label="before training")

plt.plot([model(a) for a in range(10)], label="after training")

plt.legend()

1에포크 학습 모델 그래프(초록색)

만약 학습을 100에포크 진행한다면 더 좋아질까요? 해봅시다.

w = 4
b = 1

model = lambda x: w*x+b
preds = [model(a) for a in x]
loss = MAE(y, preds)

losses = [loss]

for epoch in range(100):
    for xx, yy in zip(x, y):
        w, b = optimize(xx, yy, w, b)

    model = lambda x: w*x+b
    preds = [model(a) for a in x]
    loss = MAE(y, preds)
    losses.append(loss)
print("loss :", loss)

'''
<출력값>
loss : 7.4684501547629285
'''

100에포크 학습 결과 loss가 7점대까지 떨어진 것을 확인할 수 있습니다!

plt.xlabel("X")
plt.ylabel("Y")
plt.plot(x, y, 'o')

init_model = lambda x: 4*x+1
plt.plot([init_model(a) for a in range(10)], label="before trainig")

plt.plot([model(a) for a in range(10)], label="100epoch model")

plt.legend()

100에포크 학습 모델 그래프(초록색)

그래프도 파란 점들과 좀 더 가까워진 것 같습니다. 100에포크 동안 loss가 얼마나 줄었는지도 그래프를 통해 확인해 보겠습니다. 이를 위해 losses라는 리스트에 100에포크의 loss값을 저장해 뒀습니다.

plt.plot(losses)

100에포크 loss 그래프

loss가 100에포크동안 계속 줄어든 것을 확인할 수 있습니다. 100에포크 학습 결과 $w$와 $b$의 값은 1.97, 4.59가 나왔습니다. 이 모델이 예측한 x=6일 때의 y값은 16.45라고 합니다.

정리

지금까지 가장 간단한 형태의 인공지능 모델의 학습과정을 살펴봤습니다.길었으니 마지막으로 포인트들만 정리해 보겠습니다.

 

  • 모델 학습을 위해선 데이터가 필요합니다. 데이터는 입력값 x출력값 y로 이뤄집니다.
  • 입력값에 대해 특정 값을 예측하는 문제를 '회귀'라고 합니다.
  • 모델은 입력값과 출력값 사이의 관계를 정의합니다. 가장 간단한 형태는 $y=wx+b$와 같은 선형함수 형태입니다. 여기서 학습을 통해 배우고자 하는 변수 $w$와 $b$를 '파라미터'라고 합니다.
  • 손실 함수(loss function)는 실제 출력값과 모델의 예측값의 차이를 측정합니다. 회귀 문제에서는 MAE(Mean Absolute Error)MSE(Mean Squared Error)가 많이 쓰입니다.
  • 최적화 알고리즘(optimizer)는 손실 함수를 토대로 파라미터 값을 재조정합니다. 재조정은 손실 함수에 대한 파라미터의 미분값을 이용합니다. 학습은 학습 가중치(learning rate)에 의해서 속도가 조절됩니다.

가장 간단한 인공지능 학습방식이라고 했지만 어려웠을 수도 있을 것 같습니다. 그래도 괜찮습니다. 이 글을 본 뒤에 '아 인공지능은 대략 이런 식으로 학습되는구나' 하는 느낌만 알아도 저는 뿌듯할 것 같습니다. 이해가 가지 않은 부분들은 더 자세한 다음 포스트들을 통해 알아가면 됩니다.

 

다음 포스트에서는 이번 포스트의 내용을 토대로 이전 포스트의 '손글씨 인식' 인공지능 모델을 다시 한번 봐보도록 하겠습니다.