역전파를 이용한 학습: 딥러닝 기초 시리즈 5

역전파(Backpropagation)는 신경망에서 사용되는 학습 알고리즘 중 하나입니다.

이 알고리즘은 신경망의 성능을 향상시키기 위해 오차를 최소화하는 방향으로 가중치를 조정합니다.

이 글에서는 역전파에 대해 알아보고, 파이썬 코드로 구현해 보겠습니다.


목차

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

참고 문헌 및 참고자료: 밑바닥부터 시작하는 딥러닝


역전파 (Back Propagation)

역전파 (Back Propagation)

역전파(Back propagation)는 인공 신경망을 훈련시키는 데 사용되는 알고리즘 중 하나입니다. 역전파의 주요 목적은 손실 함수의 값을 최소화하기 위해 각 노드에서의 가중치를 조절하는 것입니다. 이는 미분을 사용하여 각 가중치에 대한 손실 함수의 기울기를 계산함으로써 달성됩니다.

역전파의 주요 단계는 다음과 같습니다:

  1. 순전파(Forward Propagation): 입력 데이터를 네트워크의 입력층에서 출력층까지 전파하고, 각 노드의 출력 값을 계산합니다. Affine층 과 활성화 함수를 을 포함하여 입력층에서 시작하여 출력층까지 데이터가 통과하는 전체 과정
    (참고: 퍼셉트론: 딥러닝 기초 시리즈 1, 활성화 함수: 딥러닝 기초 시리즈 2)
  2. 손실 함수의 계산: 출력층에서의 예측과 실제 값을 비교하여 손실을 계산합니다.
    (참고: 손실 함수: 딥러닝 기초 시리즈 3)
  3. 역전파(Backward Propagation): 출력층에서 입력층까지 손실 함수의 기울기(그래디언트)를 계산합니다.
  4. 가중치 업데이트: 계산된 그래디언트를 사용하여 네트워크의 가중치를 업데이트합니다. 이 때, 보통 경사하강법(Gradient Descent) 또는 그 변형 알고리즘을 사용합니다.
    (참고: 경사 하강법: 딥러닝 기초 시리즈 4)

간단한 신경망의 예시

우선 간단한 신경망으로 예시를 들어 이에 대한 역전파를 구해보겠습니다.

간단한 신경망의 예시

과정은 아래와 같습니다.

  • 1. x input 값을 받는다
  • 2. Affine 층 a_1 = w_1x +b를 거친다.
  • 3. Sigmoid 층 a_2 = \frac{1}{1 + e^{-a_1}} 을 거친다.
  • 4. 다시 Affine 층 y = w_2a_2 +b를 거친다.
  • 5. y 의 값이 구해진다.
  • 6. SoftMax with Loss(여기서 소프트맥스와 Loss를 묶는이유는 따로하는것보다 간단하기 때문입니다.)
    • 6-1. SoftMax h(x_i)= \frac{e^{x_i}}{\sum_{k=1}^{n} e^{x_k}} 구해 확률로 바꾼다.
    • 6-2. CCE 와 Onehot 벡터(정답이 1이고 나머진 0인 벡터)를 이용해 Loss 값을 구한다. CCE= - \sum_k t_k \ log \ (y_k)

역전파 과정

다시한번 정리하자면 순전파 과정은 아래와 같습니다.

순전파

\text{Affine1: } a_1 = w_1x + b_1 \text{Sigmoid: }a_2 = \frac{1}{1 + e^{-a_1}} \text{Affine2: }y = w_2a_2 + b_2 \text{SoftmaxWithLoss: }L(y,t) = - \sum_k t_k \log(\frac{e^{y_k}}{\sum_{j=1}^{n} e^{y_j}})

이제 필요한 미분값 즉 최종적으로 가중치와 편향값의 기울기(한걸음 한걸음 나가가기 위함입니다.)을 구하기 위해 역전파를 시행해 보겠습니다.

최종적으로 필요한 값은 경사 하강법을 위한 미분값인 w= w - \eta \frac{\partial L}{\partial w} b= b - \eta \frac{\partial L}{\partial b}에서

\frac{\partial L}{\partial w}, \ \frac{\partial L}{\partial b} 이 값들 입니다.

체인룰(연쇄 법칙)의 법칙에 따라 구하면 아래와 같이 미분값을 구할 수 있습니다.

w_1에 대한 미분
\frac{\partial L}{\partial w_1} = \frac{\partial L}{\partial y} \frac{\partial y}{\partial a_2} \frac{\partial a_2}{\partial a_1} \frac{\partial a_1}{\partial w_1}

b_1에 대한 미분
\frac{\partial L}{\partial b_1} = \frac{\partial L}{\partial y} \frac{\partial y}{\partial a_2} \frac{\partial a_2}{\partial a_1} \frac{\partial a_1}{\partial b_1}

w_2에 대한 미분
\frac{\partial L}{\partial w_2} = \frac{\partial L}{\partial y} \frac{\partial y}{\partial w_2}

b_2에 대한 미분
\frac{\partial L}{\partial b_2} = \frac{\partial L}{\partial y} \frac{\partial y}{\partial b_2}

이제 모든 항을 볼때 체인룰 계산시 필요한 미분값은 \frac{\partial L}{\partial y}, \frac{\partial y}{\partial a_2}, \frac{\partial a_2}{\partial a_1}, \frac{\partial a_1}{\partial w_1}, \frac{\partial a_1}{\partial b_1} ,\frac{\partial y}{\partial w_2} ,\frac{\partial y}{\partial b_2} 입니다.

이제 각각 필요한 미분값을 구해보겠습니다. 이부분에서의 Softmax With Loss는 각각 미분하는 것보다 하나로 묶어 미분하여 역전파를 하면 훨씬 간단해 지기 때문에 묶었습니다.

SoftmaxWithLoss 미분:
\frac{\partial L}{\partial y} = \frac{\partial}{\partial y} \left(- \sum_k t_k \log(\frac{e^{y_k}}{\sum_{j=1}^{n} e^{y_j}}) \right) = y_i - t_i.

Affine 2 미분:
\frac{\partial y}{\partial a_2} = w_2, \frac{\partial y}{\partial w_2} =a_2, \frac{\partial y}{\partial b_2} =1.

Sigmoid 미분:
\frac{\partial a_2}{\partial a_1} = a_2(1 - a_2).

Affine 1 미분:
\frac{\partial a_1}{\partial w_1} = x, \quad \frac{\partial a_1}{\partial b_1} = 1.

이제 이 모든 값들을 체인룰에 대입을 하면 아래와 같습니다.

\frac{\partial L}{\partial w_1} = \frac{\partial L}{\partial y} \times \frac{\partial y}{\partial a_2} \times \frac{\partial a_2}{\partial a_1} \times \frac{\partial a_1}{\partial w_1} = (y_i - t_i) \times w_2 \times a_2(1 - a_2) \times x ,

\frac{\partial L}{\partial b_1} = \frac{\partial L}{\partial y} \times \frac{\partial y}{\partial a_2} \times \frac{\partial a_2}{\partial a_1} \times \frac{\partial a_1}{\partial b_1} = (y_i - t_i) \times w_2 \times a_2(1 - a_2) \times 1 ,

\frac{\partial L}{\partial w_2} = \frac{\partial L}{\partial y} \times \frac{\partial y}{\partial w_2} = (y_i - t_i) \times a_2 ,

\frac{\partial L}{\partial b_2} = \frac{\partial L}{\partial y} \times \frac{\partial y}{\partial b_2} = (y_i - t_i) \times 1 .

이렇게 체인룰을 통해 각 가중치와 편향에 대한 미분을 구했습니다. 이제 이 미분값을 사용하여 아래와 같이 경사 하강법을 적용하면, 각 가중치와 편향을 업데이트할 수 있습니다.

w_1= w_1 - \eta \frac{\partial L}{\partial w_1} b_1= b_1 - \eta \frac{\partial L}{\partial b_1} w_2= w_2 - \eta \frac{\partial L}{\partial w_2} b_2= b_2 - \eta \frac{\partial L}{\partial b_2}

역전파 과정 코드로 구현

이제 앞서 설명한 역전파 알고리즘을 활용하여 간단한 신경망의 학습 과정을 코드로 구현하고자 합니다. (단, 본 구현은 앞서 제시한 특정 역전파 과정에 한정된 것이며, 다양한 조건이나 변수에는 반영되지 않았습니다.)

1. 먼저 필요한 라이브러리를 임포트 합니다.

Python
import numpy as np
import matplotlib.pyplot as plt

# 설정 값 항상랜덤으로 하면 매번 값이 바뀌기 때문에 시드값을 고정했습니다.
np.random.seed(0)

2. 순전파 함수들 구현하기

\text{Affine1: } a_1 = w_1x + b_1 \text{Sigmoid: }a_2 = \frac{1}{1 + e^{-a_1}} \text{Affine2: }y = w_2a_2 + b_2
Python
# Affine Layer 1
def affine1_forward(x, w1, b1):
    a1 = np.dot(x, w1) + b1
    return a1

# Sigmoid Layer
def sigmoid_forward(a1):
    a2 = 1 / (1 + np.exp(-a1))
    return a2

# Affine Layer 2
def affine2_forward(a2, w2, b2):
    y = np.dot(a2, w2) + b2
    return y

3. 손실함수와 소프트맥스 with 손실함수의 역전파

\text{SoftMax: } h(x_i)= \frac{e^{x_i}}{\sum_{k=1}^{n} e^{x_k}} \frac{\partial L}{\partial y} = \frac{\partial}{\partial y} \left(- \sum_k t_k \log(\frac{e^{y_k}}{\sum_{j=1}^{n} e^{y_j}}) \right) = y_i - t_i
Python
# 소프트 맥스 함수
def softmax(y):
    exp_y = np.exp(y - np.max(y))
    return exp_y / np.sum(exp_y, axis=-1, keepdims=True)

# Softmax with Loss 역전파(미분)
def softmax_with_loss(y, t):
    y_softmax = softmax(y)
    loss = -np.sum(t * np.log(y_softmax + 1e-8))
    dy = y_softmax - t  # dL/dy
    return loss, dy

4. 각 레이어에 대한 미분값 계산(역전파)

단순 1차원 계산시.

\frac{\partial L}{\partial w_1} = (y_i - t_i) \times w_2 \times a_2(1 - a_2) \times x \frac{\partial L}{\partial b_1} = (y_i - t_i) \times w_2 \times a_2(1 - a_2) \times 1 \frac{\partial L}{\partial w_2} = (y_i - t_i) \times a_2 \frac{\partial L}{\partial b_2} = (y_i - t_i) \times 1

다차원 입력값에 대하여

하지만 만약 입력값이 다차원이 되면 계산이 조금 복잡해집니다. 행렬 내적을 하기 위해서 행렬의 Transpose(대각선 대칭)를 해 주어야 합니다. 모양이 맞아야 계산이 됩니다. 즉

\frac{\partial L}{\partial w_1} = x^T \times ( \frac{\partial L}{\partial y} \times w_2^T \times a_2(1 - a_2)) \frac{\partial L}{\partial w_2} = a_2^T \times \frac{\partial L}{\partial y}

이러한 미니배치를 통해 손실 함수 그래디언트를 한 번에 계산합니다. 이 경우, 각 데이터 포인트에 대한 그래디언트를 모두 합쳐서 평균을 내거나 합계를 구해야 합니다. 특히 편향 에 대한 그래디언트를 구할 때도 이렇게 해야 하는데, 이를 수식으로 표현하면 다음과 같이 됩니다.

\frac{\partial L}{\partial b_1} = \sum (y_i - t_i) \times w_2 \times a_2(1 - a_2) \times 1 \frac{\partial L}{\partial b_2} = \sum (y_i - t_i) \times 1

여기서 \frac{\partial L}{\partial y} = (y_i - t_i) 입니다.

즉, 미니배치를 사용할 때와 하나의 데이터 포인트만 사용할 때의 주요 차이는 여러 데이터 포인트에 대한 그래디언트를 합쳐야 하는지 여부입니다.

Python
# Affine 2 역전파(미분)
def affine2_backward(dy, a2, w2, b2):
    dw2 = np.dot(a2.T, dy)
    db2 = np.sum(dy, axis=0)
    da2 = np.dot(dy, w2.T)
    return dw2, db2, da2

# Sigmoid 역전파(미분)
def sigmoid_backward(da2, a2):
    da1 = da2 * (a2 * (1 - a2))
    return da1

# Affine 1 역전파(미분)
def affine1_backward(da1, x, w1, b1):
    dw1 = np.dot(x.T, da1)
    db1 = np.sum(da1, axis=0)
    return dw1, db1

5. 경사 하강법을 이용한 가중치와 편향의 업데이트

아래는 경사 하강법을 이용하여 가중치와 편향을 업데이트를 합니다.

w_1= w_1 - \eta \frac{\partial L}{\partial w_1} b_1= b_1 - \eta \frac{\partial L}{\partial b_1} w_2= w_2 - \eta \frac{\partial L}{\partial w_2} b_2= b_2 - \eta \frac{\partial L}{\partial b_2}
Python
# 가중치와 편향을 업데이트하는 경사하강법
def update_parameters(w1, b1, w2, b2, dw1, db1, dw2, db2, learning_rate):
    w1 -= learning_rate * dw1
    b1 -= learning_rate * db1
    w2 -= learning_rate * dw2
    b2 -= learning_rate * db2
    return w1, b1, w2, b2

6. 간단한 예제를 이용한 테스트

Python
# 초기 가중치 및 편향값의 차원 정하기
input_dim = 2  # 입력 값의 차원
hidden_dim = 3  # 숨은 레이어의 차원
output_dim = 2  # 출력 값의 차원

# Affine1에 대한 가중치와 편향값 초기화(랜덤으로 지정)
w1 = np.random.randn(input_dim, hidden_dim)
b1 = np.zeros(hidden_dim)

# Affine2에 대한 가중치와 편향값 초기화(랜덤으로 지정)
w2 = np.random.randn(hidden_dim, output_dim)
b2 = np.zeros(output_dim)

# 학습률(Learning rate)
learning_rate = 0.1

이제 입력값 x 를 넣고 반복해 줍니다.

Python
# Example data
x = np.array([[0.6, 0.9]])  # Input
t = np.array([[0, 1]])  # True label

# Initialize loss history for plotting
loss_history = []

# Perform 100 iterations of forward and backward pass
for i in range(100):
    # Forward pass
    a1 = affine1_forward(x, w1, b1)
    a2 = sigmoid_forward(a1)
    y = affine2_forward(a2, w2, b2)
    
    # Compute loss and dL/dy
    loss, dy = softmax_with_loss(y, t)
    
    # Record the loss
    loss_history.append(loss)
    
    # Backward pass
    dw2, db2, da2 = affine2_backward(dy, a2, w2, b2)
    da1 = sigmoid_backward(da2, a2)
    dw1, db1 = affine1_backward(da1, x, w1, b1)
    
    # Update parameters
    w1, b1, w2, b2 = update_parameters(w1, b1, w2, b2, dw1, db1, dw2, db2, learning_rate)
    if i % 10 == 0:
        print(loss)

결과적으로 손실값을 보여주게 되고

최종적으로 아래코드를 입력해 그래프로 그리면 아래와 같이 나오게 됩니다.

Python
# Plot the loss history
plt.figure(figsize=(10, 6))
plt.plot(loss_history)
plt.title('Loss History')
plt.xlabel('Iteration')
plt.ylabel('Loss')
plt.grid(True)
plt.show()
역전파를 이용한 loss 업데이트 최종 그래프

역전파로 미분값을 구하여 경사하강법을 이용해 가중치와, 편향치를 최적화 해 본 결과 한걸음 한걸음 나아 갈때마다 손실값이 줄어드는 양상을 볼 수 있습니다.


결론

“이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.”

이 글에서는 인공 신경망의 학습 과정 중 핵심 알고리즘인 역전파(Back propagation)에 대해 깊이 있게 탐구하였습니다. 역전파는 손실 함수의 그래디언트를 효율적으로 계산하여 네트워크의 가중치와 편향을 조정하는 최적화 알고리즘입니다. 이를 통해 신경망의 성능을 상당히 향상시킬 수 있습니다.

순전파와 역전파의 수학적 원리를 소개하고, 이를 바탕으로 파이썬 코드로 간단한 신경망의 학습 과정을 구현하였습니다. 특히, 체인룰을 활용하여 각 레이어의 가중치와 편향에 대한 손실 함수의 편미분을 계산하는 방법을 상세히 설명하였습니다.

이론적인 내용을 코드로 경사 하강법을 이용한 파라미터 업데이트 방식 등을 포괄적으로 다루었습니다. 그러나 이 코드는 특정 역전파 과정에 한정된 것으로, 다양한 활성화 함수나 최적화 알고리즘, 레이어 구조 등에 대한 확장성은 제한적입니다.

최종적으로, 구현한 코드를 통해 학습 과정에서의 손실값 감소를 그래프로 시각화하였습니다. 이를 통해 역전파 알고리즘의 효율성과 신경망 학습을 구현해 보았습니다. 이 글을 작성하며 딥러닝과 신경망 알고리즘에 대한 이해를 좀더 하게 되었습니다.

5 thoughts on “역전파를 이용한 학습: 딥러닝 기초 시리즈 5”

Leave a Comment