Developer's Development

3.2.19 [딥러닝] 최적 모델 학습 (오차역전파법) 본문

데이터 분석과 머신러닝, 딥러닝/딥러닝

3.2.19 [딥러닝] 최적 모델 학습 (오차역전파법)

mylee 2025. 8. 5. 11:29
오차역전파법 (Backpropagation)

 

다층 신경망(MLP, Multi-Layer Perceptron)에서 가중치를 조정하여 최적의 학습을 수행하는 알고리즘이다.

출력층에서 계산된 오차를 이전 층(은닉층)으로 전파하여, 모든 가중치의 기울기를 업데이트 하는 방식이다.

 

⭐ 핵심 개념

1. 순전파 (Fowrard Propagation) : 입력 데이터가 신경망을 거쳐 출력을 생성

2. 오차 계산 (Loss Calculation) : 예측값과 실제값의 차이를 계산

3. 역전파 (Backpropagation, BP) : 연쇄법칙을 이용하여 기울기를 전파

4. 가중치 업데이트 (Weight Update) : 경사하강법으로 가중치 조정

 

👉🏻 필요성

가중치 최적화, 연쇄법칙(Chain Rule) 적용, 비선형 문제 해결

 

  • 오차역전파법 동작 과정

1. 순전파

  a. 입력값 X가 신경망을 통해 전달된다.

  b. 각 층에서 가중치 W와 활성화 함수 적용 후 출력을 계산한다.

  c. 마지막 출력층에서 예측값을 생성한다.

 

2. 손실 계산

  a. 예측값과 실제값의 차이를 계산한다.

  b. 손실함수를 적용한다. ex) Mean Squared Error, Cross Entropy

 

3. 역전파

  a. 출력층 → 은닉층 방향으로 오차를 전파한다.

  b. 연쇄법칙을 이용하여 각 가중치의 기울기를 계산한다.

 

4.  가중치 업데이트

  a. 경사하강법을 이용해 가중치 W를 조정한다.

 

5. 오차가 최소화 될 때까지 반복한다.

 

  • 국소적 계산 (local computation)

신경망에서 각 층(layer)이 독립적으로 연산을 수행하는 원리를 의미한다.

각 층은 입력을 받아 가중치를 적용한 후 활성화 함수를 통해 출력을 생성하며, 이 과정에서 다른 층에 직접적인 영향을 주지 않는다.

따라서 오차역전파법을 적용할 때도 개별적인 노드에서 기울기를 계산하여 전달하는 방식이 사용된다.

이러한 계산이 연쇄적으로 이루어지므로 전체 모델의 기울기를 효과적으로 계산할 수 있다.

이러한 국소적 계산 덕분에 신경망의 구조를 변경하더라도 일부 층의 연산만 변경하면 되므로 확장성이 높아진다.

 

  • 연쇄법칙과 계산 그래프

👉🏻 연쇄법칙 (chain rule)

복합 함수의 미분을 수행할 때 각 구성 요소의 미분을 곱하는 방식으로 미분을 계산하는 방법이다.

신경망에서는 역전파 시 연쇄법칙을 이용하여 기울기를 구한다.

신경망에서는 오차의 기울기가 출력층에서 시작하여 이전 층으로 전파되므로, 연쇄법칙을 통해 각 층에서 기울기를 계산하고 전달하는 방식으로 학습이 진행된다.

 

👉🏻 계산 그래프 (computational graph)

연산 과정을 시각적으로 표현한 구조이다.

신경망에서의 각 연산(덧셈, 곱셈, 활성화 함수 적용 등)을 노드로 표현하고, 오차역전파법 수행 시 각 노드에서 연쇄법칙을 이용하여 미분을 계산한다.

 

  • 오차역전파법으로 구한 기울기 검증

[ 기울기 검증의 필요성 ]

오차역전파법으로 계산한 기울기가 정확한지 확인하는 과정이 필요하다.

일반적으로 수치 미분과 오차역전파법을 비교하여 검증한다.

기울기 검증을 수행하면 구현 오류를 줄이고 신경망이 올바르게 학습하는지 확인할 수 있다.

 

👉🏻 기울기 검증 방법

- 오차 역전파법을 통한 기울기 계산 : 신경망의 각 파라미터에 대해 연쇄법칙을 사용하여 기울기를 구한다.

- 수치 미분을 통한 검증 : 작은 변화량을 적용하여 수치적으로 기울기를 구한 후 비교한다.

 

실습 (역전파)

 

  • 연쇄법칙
# 기본 수식의 역전파 & 연쇄법칙 적용
import numpy as np

def forward(x):
    y = x ** 2  # 순전파 (y = x²)
    return y

def backward(x):
    dy_dx = 2 * x  # 역전파 (y = x²의 미분값인 dy/dx = 2x)
    return dy_dx

x = 3.0
print(forward(x))   # 9.0
print(backward(x))  # 6.0  /기울기(=변화율), x가 조금 변하면 y는 얼마나 변하는지?

# 다층 신경망에서 연쇄법칙 적용
import numpy as np

def forward(x):
    y = x ** 2  # 첫번째 층: 제곱
    z = 2 * y   # 두번째 층 : 2배
    return z    # z = 2 * (x ** 2) : 다층 신경망처럼 구성

def backward(x):
    dy_dx = 2 * x
    dz_dy = 2
    dz_dx = dy_dx * dz_dy
    return dz_dx

x = 3.0
print(forward(x))   # 18.0
print(backward(x))  # 12.0

 

  • 신경망에서의 활용

👉🏻 단순 신경망 학습

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def sigmoid_d(x):
    return sigmoid(x) * (1 - sigmoid(x))
    
X = np.array([0.5, 0.8])
y = np.array([1])

W = np.array([0.2, 0.4])

# 순전파
z = np.dot(X, W)
r = sigmoid(z)

# 오차 계산
loss = 0.5 * ((y - r) ** 2)

# 역전파 (기울기 계산)
delta = (r - y) * sigmoid_d(z)
grad_w = delta * X

# 가중치 갱신
W -= 0.1 * grad_w  # 0.1 == learning_rate

print(W)  # [0.23306662 0.43582766]

 

👉🏻 은닉층 추가

def relu(x):
    return np.maximum(0, x)

def relu_d(x):
    return np.where(x > 0, 1, 0)
    
X = np.array([0.5, 0.8])    # (2,)
y = np.array([1])           # (1,)

W1 = np.array([[0.2, 0.4], [0.1, 0.3]])     # (2, 2)
b1 = np.array([0.1, 0.2])                   # (2,)
W2 = np.array([[0.5], [0.6]])               # (2, 1)
b2 = np.array([0.3])                        # (1,)

# 순전파
z1 = np.dot(X, W1) + b1
r1 = relu(z1)

z2 = np.dot(r1, W2) + b2
r2 = relu(z2)

# 손실 계산 -> 역전파 (기울기 계산)
delta2 = (r2 - y) * relu_d(z2)
grad_w2 = np.outer(r1, delta2)

delta1 = np.dot(W2, delta2) * relu_d(z1)
grad_w1 = np.outer(X, delta1)

# 가중치 갱신
learning_rate = 0.01
W2 -= learning_rate * grad_w2
W1 -= learning_rate * grad_w1

print(W2)
print(W1)

 

👉🏻 수치 미분과 역전파

def f(x):
    return x ** 2

def num_d_gradient(f, x):
    h = 1e-5
    return (f(x + h) - f(x - h)) / (2 * h)

def backward_gradient(x):
    return 2 * x

print(num_d_gradient(f, 3.0))  # 6.000000000039306
print(backward_gradient(3.0))  # 6.0

 

👉🏻 숫자 맞추기 AI

target_number = 107
guess = np.random.randn()

learning_rate = 0.1
epochs = 500

for i in range(epochs):
    # 오차 계산
    loss = 0.5 * ((guess - target_number) ** 2)

    # 역전파 (기울기 계산)
    grad = (guess - target_number)

    # 업데이트 (guess 업데이트)
    guess -= learning_rate * grad

    # epoch 100마다 예측값과 손실 출력
    if (i + 1) % 100 == 0:
        print(f"Epoch {i + 1} 예측값: {guess:.4f}, 손실: {loss:.4f}")

# 최종 예측값 guess 출력
print(f"최종 예측값: {guess:.4f}, 목표값: {target_number}")

"""
Epoch 100 예측값: 106.9971, 손실: 0.0000
Epoch 200 예측값: 107.0000, 손실: 0.0000
Epoch 300 예측값: 107.0000, 손실: 0.0000
Epoch 400 예측값: 107.0000, 손실: 0.0000
Epoch 500 예측값: 107.0000, 손실: 0.0000
최종 예측값: 107.0000, 목표값: 107
"""