Level 3: 딥러닝 핵심
🔮

Level 3

역전파 알고리즘

딥러닝 학습의 핵심 - Backpropagation 완전 정복

25분
역전파 알고리즘 강의 영상
강의 영상 보기 (새 탭에서 재생)YouTube

📓Google Colab에서 실습하기

이 레슨은 PyTorch/GPU가 필요합니다. 노트북을 다운로드 후 Google Colab에서 열어주세요.

학습 내용

역전파 알고리즘 (Backpropagation)

학습 목표

이 레슨을 완료하면:

  • 역전파가 왜 필요한지 직관적으로 이해합니다
  • 연쇄 법칙(Chain Rule)을 신경망에 적용할 수 있습니다
  • 2층 신경망의 역전파를 손으로 계산할 수 있습니다
  • numpy로 역전파를 직접 구현하고 XOR 문제를 풀 수 있습니다
  • 계산 그래프 개념과 역전파의 효율성을 설명할 수 있습니다

핵심 메시지

"역전파는 공장 조립 라인의 품질 검사와 같습니다" 불량품이 나왔을 때, 어느 공정에서 문제가 생겼는지 끝에서부터 역추적하는 것이죠.


1. 비유: 공장 조립 라인의 품질 검사

자동차 공장을 상상해 보세요. 완성된 차에 결함이 발견되었습니다.

공정공장 비유신경망 대응
1단계철판 절단입력층 (데이터 받기)
2단계프레임 조립은닉층 1 (특징 추출)
3단계도색은닉층 2 (특징 조합)
4단계최종 검사출력층 (예측 생성)
불량 발견!"문이 안 닫혀!"손실(Loss) 계산

품질 검사관은 끝에서부터 역추적합니다:

  • 최종 검사 -> 도색 문제? -> 프레임 문제? -> 철판 문제?
  • 각 공정이 불량에 얼마나 기여했는지 파악합니다

역전파도 똑같습니다. 출력의 오차를 뒤에서 앞으로 전파하면서, 각 가중치가 오차에 얼마나 기여했는지 계산합니다.


2. 순전파 복습: 앞으로 가는 계산

역전파를 이해하려면 먼저 순전파를 확실히 알아야 합니다.

단계수식의미
선형 변환z = W * x + b가중치를 곱하고 편향 더하기
활성화a = f(z)비선형 변환 적용
손실 계산L = loss(y_pred, y_true)예측과 정답의 차이

순전파는 입력 -> 은닉층 -> 출력 -> 손실 순서로 진행됩니다.

실행해보기: 순전파 직접 계산

python
import numpy as np # === 간단한 2층 신경망 순전파 === # 입력 x = np.array([[0.5], [0.3]]) # 2x1 print("입력 x:") print(x.T) # 은닉층 (2 뉴런) W1 = np.array([[0.4, 0.5], [0.6, 0.7]]) # 2x2 b1 = np.array([[0.1], [0.2]]) # 2x1 z1 = W1 @ x + b1 # 선형 변환 a1 = np.maximum(0, z1) # ReLU 활성화 print("\n은닉층 z1 (선형):", z1.T) print("은닉층 a1 (ReLU):", a1.T) # 출력층 (1 뉴런) W2 = np.array([[0.8, 0.9]]) # 1x2 b2 = np.array([[0.1]]) # 1x1 z2 = W2 @ a1 + b2 y_pred = 1 / (1 + np.exp(-z2)) # Sigmoid 활성화 print("\n출력 z2 (선형):", z2[0][0]) print("예측 y_pred (Sigmoid):", round(y_pred[0][0], 4)) # 손실 계산 (MSE) y_true = 1.0 loss = 0.5 * (y_pred[0][0] - y_true) ** 2 print("\n정답:", y_true) print("손실(MSE):", round(loss, 6))

3. 역전파의 핵심: 연쇄 법칙 (Chain Rule)

역전파의 수학적 기반은 미적분의 연쇄 법칙입니다.

비유: 도미노를 생각해 보세요. 첫 번째 도미노를 1cm 밀면, 두 번째는 2cm, 세 번째는 6cm 움직인다면, 첫 번째가 세 번째에 미치는 영향 = 2 x 3 = 6배 입니다.

수학으로 표현하면:

y=f(g(x))y = f(g(x)) 일 때, dydx=dydgdgdx\frac{dy}{dx} = \frac{dy}{dg} \cdot \frac{dg}{dx}

즉, "최종 변화 = 각 단계의 변화를 곱한 것"

신경망에서는 이렇게 됩니다:

계산 방향수식의미
출력 -> 손실dL/dy_pred예측이 손실에 미치는 영향
활성화 -> 출력dy_pred/dz2선형값이 예측에 미치는 영향
가중치 -> 활성화dz2/dW2가중치가 선형값에 미치는 영향
연쇄dL/dW2 = dL/dy_pred * dy_pred/dz2 * dz2/dW2모두 곱하면 = 가중치가 손실에 미치는 영향!

4. 손으로 계산하는 역전파

아까 순전파에서 사용한 값들로 역전파를 직접 해봅시다.

단계수식설명
Step 1L=12(y^y)2L = \frac{1}{2}(\hat{y} - y)^2Ly^=y^y\frac{\partial L}{\partial \hat{y}} = \hat{y} - y손실의 미분 (출발점)
Step 2y^=σ(z2)\hat{y} = \sigma(z_2)y^z2=y^(1y^)\frac{\partial \hat{y}}{\partial z_2} = \hat{y}(1-\hat{y})Sigmoid 미분
Step 3z2=W2a1+b2z_2 = W_2 a_1 + b_2z2W2=a1\frac{\partial z_2}{\partial W_2} = a_1출력층 가중치 미분
Step 4LW2=(y^y)y^(1y^)a1\frac{\partial L}{\partial W_2} = (\hat{y}-y) \cdot \hat{y}(1-\hat{y}) \cdot a_1연쇄 법칙으로 조합 → W2의 기울기!

실행해보기: 손계산을 코드로 검증

python
import numpy as np # 순전파 값들 (위에서 계산한 것) x = np.array([[0.5], [0.3]]) W1 = np.array([[0.4, 0.5], [0.6, 0.7]]) b1 = np.array([[0.1], [0.2]]) W2 = np.array([[0.8, 0.9]]) b2 = np.array([[0.1]]) y_true = 1.0 # --- 순전파 --- z1 = W1 @ x + b1 a1 = np.maximum(0, z1) z2 = W2 @ a1 + b2 y_pred = 1 / (1 + np.exp(-z2)) print("=== 순전파 결과 ===") print("y_pred:", round(y_pred[0][0], 4)) # --- 역전파 (손계산을 코드로!) --- print("\n=== 역전파 단계별 ===") # Step 1: 손실 미분 dL_dy = y_pred - y_true print("Step 1) dL/dy_pred:", round(dL_dy[0][0], 4)) # Step 2: Sigmoid 미분 dy_dz2 = y_pred * (1 - y_pred) print("Step 2) dy/dz2 (sigmoid 미분):", round(dy_dz2[0][0], 4)) # Step 3: 출력층 기울기 delta2 = dL_dy * dy_dz2 dL_dW2 = delta2 @ a1.T dL_db2 = delta2 print("Step 3) delta2 (출력 오차):", round(delta2[0][0], 4)) print(" dL/dW2:", np.round(dL_dW2, 4)) # Step 4: 은닉층으로 전파 dL_da1 = W2.T @ delta2 print("\nStep 4) 은닉층으로 전파된 오차:", np.round(dL_da1.T, 4)) # Step 5: ReLU 미분 relu_mask = (z1 > 0).astype(float) delta1 = dL_da1 * relu_mask print("Step 5) ReLU 마스크:", relu_mask.T) print(" delta1:", np.round(delta1.T, 4)) # Step 6: 은닉층 가중치 기울기 dL_dW1 = delta1 @ x.T dL_db1 = delta1 print("\nStep 6) dL/dW1:") print(np.round(dL_dW1, 4)) print(" dL/db1:", np.round(dL_db1.T, 4)) print("\n모든 가중치의 기울기를 구했습니다!") print("이 기울기로 가중치를 업데이트하면 학습이 됩니다.")

5. 계산 그래프로 이해하기

계산 그래프는 순전파의 모든 연산을 노드로 표현한 것입니다. 역전파는 이 그래프를 역방향으로 따라가며 기울기를 계산합니다.

순전파 연산역전파 규칙설명
c = a + bdc/da = 1, dc/db = 1덧셈: 기울기를 그대로 전달
c = a * bdc/da = b, dc/db = a곱셈: 서로의 값을 전달
c = f(a)dc/da = f'(a)함수: 도함수를 곱함

핵심 통찰: 역전파에서 각 노드는 "내가 받은 기울기"에 "내 지역 기울기"를 곱해서 이전 노드에 전달합니다. 이것이 연쇄 법칙의 실제 구현입니다!


6. 역전파의 효율성: O(N) vs O(N^2)

왜 역전파가 획기적인 알고리즘일까요?

방법시간 복잡도설명
수치 미분 (naive)O(N^2)파라미터 하나씩 살짝 바꿔서 기울기 측정. N개 파라미터마다 순전파 1회
역전파O(N)순전파 1회 + 역전파 1회로 모든 기울기를 한번에 계산!

GPT-3는 파라미터가 1,750억 개입니다. 수치 미분으로는 1,750억 번 순전파를 해야 하지만, 역전파로는 단 1번의 순전파 + 1번의 역전파로 끝납니다!

🔬 실습: 수치 미분 vs 역전파 비교

두 방법으로 같은 기울기를 구하고, 역전파가 정확한지 검증해봅시다.

python
import numpy as np # ═══════════════════════════════════════════════════════════════ # 📊 수치 미분 vs 역전파 비교 # 두 방법의 결과가 같으면 역전파 구현이 정확! # ═══════════════════════════════════════════════════════════════ def sigmoid(z): return 1 / (1 + np.exp(-z)) # 📌 신경망 파라미터 설정 x_val = 0.5 # 입력 w1, b1 = 0.8, 0.1 # 1층 가중치, 편향 w2, b2 = 0.6, 0.2 # 2층 가중치, 편향 y_true = 1.0 # 정답 eps = 1e-5 # 수치 미분용 작은 값 params = {'w1': w1, 'b1': b1, 'w2': w2, 'b2': b2} # 📐 순전파 함수 def forward(w1, b1, w2, b2): z1 = w1 * x_val + b1 # 1층 선형변환 a1 = max(0, z1) # ReLU 활성화 z2 = w2 * a1 + b2 # 2층 선형변환 y_pred = sigmoid(z2) # Sigmoid 출력 return 0.5 * (y_pred - y_true) ** 2 # MSE 손실 # ───────────────────────────────────────────────────────────────── # 🐌 방법 1: 수치 미분 (느리지만 직관적) # 각 파라미터를 살짝 바꿔서 손실 변화 측정 # ───────────────────────────────────────────────────────────────── print("🐌 수치 미분 (파라미터마다 순전파 2회 필요)") print("=" * 50) num_grads = {} for name, val in params.items(): p_plus = dict(params); p_plus[name] = val + eps # 살짝 증가 p_minus = dict(params); p_minus[name] = val - eps # 살짝 감소 num_grads[name] = (forward(**p_plus) - forward(**p_minus)) / (2 * eps) print(f" ∂L/∂{name} = {num_grads[name]:.6f}") # ───────────────────────────────────────────────────────────────── # 🚀 방법 2: 역전파 (빠르고 효율적) # 연쇄법칙으로 한 번에 모든 기울기 계산 # ───────────────────────────────────────────────────────────────── print("\n🚀 역전파 (순전파 1회 + 역전파 1회)") print("=" * 50) # 순전파 z1 = w1 * x_val + b1 a1 = max(0, z1) z2 = w2 * a1 + b2 y_pred = sigmoid(z2) # 역전파 (출력 → 입력 방향으로) dL_dy = y_pred - y_true # 손실의 미분 dy_dz2 = y_pred * (1 - y_pred) # Sigmoid의 미분 delta2 = dL_dy * dy_dz2 # 연쇄법칙 적용 dL_dw2 = delta2 * a1 # w2의 기울기 dL_db2 = delta2 # b2의 기울기 dL_da1 = delta2 * w2 # a1으로 전파 dL_dz1 = dL_da1 * (1 if z1 > 0 else 0) # ReLU 미분 dL_dw1 = dL_dz1 * x_val # w1의 기울기 dL_db1 = dL_dz1 # b1의 기울기 bp_grads = {'w1': dL_dw1, 'b1': dL_db1, 'w2': dL_dw2, 'b2': dL_db2} for name, val in bp_grads.items(): print(f" ∂L/∂{name} = {val:.6f}") # ───────────────────────────────────────────────────────────────── # ✅ 검증: 두 방법의 결과 비교 # ───────────────────────────────────────────────────────────────── print("\n✅ 검증: 두 방법의 차이 (0에 가까우면 정확!)") print("=" * 50) for name in params: diff = abs(num_grads[name] - bp_grads[name]) status = "✅" if diff < 1e-6 else "❌" print(f" {name} 차이: {diff:.10f} {status}")

7. 실전: numpy로 XOR 문제 풀기

XOR은 선형으로 풀 수 없는 대표적인 문제입니다. 2층 신경망 + 역전파로 풀어봅시다!

입력 A입력 BXOR 출력
000
011
101
110

🔬 실습: XOR 신경망 학습

단순 선형 모델로는 풀 수 없는 XOR 문제를 2층 신경망으로 해결해봅시다!

python
import numpy as np # ═══════════════════════════════════════════════════════════════ # 📊 XOR 문제 - 신경망으로 풀기 # XOR: 입력이 다르면 1, 같으면 0 # ═══════════════════════════════════════════════════════════════ # 📌 XOR 데이터 # (0,0)→0, (0,1)→1, (1,0)→1, (1,1)→0 X = np.array([[0,0],[0,1],[1,0],[1,1]]).T # 2×4 (입력 2개, 샘플 4개) Y = np.array([[0,1,1,0]]) # 1×4 (정답) print("📌 XOR 문제") print("입력 (X):", X.T.tolist()) print("정답 (Y):", Y.tolist()) # ───────────────────────────────────────────────────────────────── # 🧠 신경망 구조: 입력(2) → 은닉층(4) → 출력(1) # ───────────────────────────────────────────────────────────────── np.random.seed(42) W1 = np.random.randn(4, 2) * 0.5 # 1층 가중치 (4×2) b1 = np.zeros((4, 1)) # 1층 편향 (4×1) W2 = np.random.randn(1, 4) * 0.5 # 2층 가중치 (1×4) b2 = np.zeros((1, 1)) # 2층 편향 (1×1) lr = 1.0 # 학습률 def sigmoid(z): return 1 / (1 + np.exp(-np.clip(z, -500, 500))) # ───────────────────────────────────────────────────────────────── # 🚀 학습 시작! # ───────────────────────────────────────────────────────────────── losses = [] print("\n🚀 학습 시작...") for epoch in range(3000): # ═══ 순전파 ═══ Z1 = W1 @ X + b1 # 1층 선형변환 A1 = np.maximum(0, Z1) # ReLU 활성화 Z2 = W2 @ A1 + b2 # 2층 선형변환 A2 = sigmoid(Z2) # Sigmoid 출력 (확률) # ═══ 손실 계산 (Cross-Entropy) ═══ m = X.shape[1] # 샘플 수 loss = -np.mean(Y * np.log(A2 + 1e-8) + (1 - Y) * np.log(1 - A2 + 1e-8)) losses.append(loss) # ═══ 역전파 ═══ dZ2 = A2 - Y # 출력층 오차 dW2 = (1/m) * dZ2 @ A1.T # W2 기울기 db2 = (1/m) * np.sum(dZ2, axis=1, keepdims=True) # b2 기울기 dA1 = W2.T @ dZ2 # 은닉층으로 전파 dZ1 = dA1 * (Z1 > 0).astype(float) # ReLU 미분 dW1 = (1/m) * dZ1 @ X.T # W1 기울기 db1 = (1/m) * np.sum(dZ1, axis=1, keepdims=True) # b1 기울기 # ═══ 가중치 업데이트 ═══ W2 -= lr * dW2 b2 -= lr * db2 W1 -= lr * dW1 b1 -= lr * db1 if epoch % 500 == 0: print("Epoch " + str(epoch) + " | Loss: " + str(round(loss, 4))) print("\n=== 학습 완료! 예측 결과 ===") Z1 = W1 @ X + b1 A1 = np.maximum(0, Z1) Z2 = W2 @ A1 + b2 predictions = sigmoid(Z2) for i in range(4): inp = str(int(X[0,i])) + ", " + str(int(X[1,i])) pred = round(predictions[0,i], 3) label = int(Y[0,i]) result = "O" if round(pred) == label else "X" print("입력: [" + inp + "] -> 예측: " + str(pred) + " (정답: " + str(label) + ") " + result)

실행해보기: 학습 곡선 시각화

python
import numpy as np import matplotlib.pyplot as plt X = np.array([[0,0],[0,1],[1,0],[1,1]]).T Y = np.array([[0,1,1,0]]) np.random.seed(42) W1 = np.random.randn(4, 2) * 0.5 b1 = np.zeros((4, 1)) W2 = np.random.randn(1, 4) * 0.5 b2 = np.zeros((1, 1)) lr = 1.0 def sigmoid(z): return 1 / (1 + np.exp(-np.clip(z, -500, 500))) losses = [] for epoch in range(3000): Z1 = W1 @ X + b1 A1 = np.maximum(0, Z1) Z2 = W2 @ A1 + b2 A2 = sigmoid(Z2) m = X.shape[1] loss = -np.mean(Y * np.log(A2 + 1e-8) + (1 - Y) * np.log(1 - A2 + 1e-8)) losses.append(loss) dZ2 = A2 - Y dW2 = (1/m) * dZ2 @ A1.T db2 = (1/m) * np.sum(dZ2, axis=1, keepdims=True) dA1 = W2.T @ dZ2 dZ1 = dA1 * (Z1 > 0).astype(float) dW1 = (1/m) * dZ1 @ X.T db1 = (1/m) * np.sum(dZ1, axis=1, keepdims=True) W2 -= lr * dW2; b2 -= lr * db2; W1 -= lr * dW1; b1 -= lr * db1 plt.figure(figsize=(10, 5)) plt.plot(losses, linewidth=2, color='#2196F3') plt.xlabel('Epoch', fontsize=12) plt.ylabel('Loss (Cross-Entropy)', fontsize=12) plt.title('XOR Neural Network Training - Loss Curve', fontsize=14) plt.grid(True, alpha=0.3) plt.xlim(0, 3000) plt.tight_layout() plt.show() print("\nLoss:", round(losses[0], 4), "->", round(losses[-1], 4))

8. PyTorch autograd와의 연결

우리가 손으로 구현한 역전파를 PyTorch는 자동으로 해줍니다!

우리가 한 것PyTorch가 해주는 것
순전파에서 중간값 저장 (z1, a1 등)requires_grad=True로 자동 추적
연쇄 법칙으로 기울기 계산loss.backward() 한 줄로 끝
기울기를 변수에 직접 저장param.grad에 자동 저장
가중치 업데이트 수식 직접 작성optimizer.step()으로 끝
python
# ═══════════════════════════════════════════════════════════════ # ✨ PyTorch에서는 단 3줄! # ═══════════════════════════════════════════════════════════════ loss = criterion(model(x), y) # 순전파 loss.backward() # 역전파 (이 한 줄이 위의 모든 계산!) optimizer.step() # 가중치 업데이트

하지만 내부에서 일어나는 일은 우리가 구현한 것과 정확히 동일합니다. 원리를 알고 쓰면, 디버깅할 때 큰 차이를 만듭니다!


핵심 요약

개념설명비유
순전파입력 -> 출력 계산공장 조립 라인
손실 계산예측과 정답의 차이품질 검사
역전파손실을 뒤에서 앞으로 전파불량 원인 역추적
연쇄 법칙각 단계의 미분을 곱함도미노 효과
기울기가중치가 손실에 미치는 영향각 공정의 불량 기여도
가중치 업데이트w = w - lr * gradient공정 개선

학습 체크리스트

  • 역전파의 직관 이해 (공장 품질 검사 비유)
  • 연쇄 법칙이 왜 필요한지 설명 가능
  • 2층 신경망의 역전파를 단계별로 따라갈 수 있음
  • numpy로 순전파 + 역전파 구현 가능
  • 역전파가 O(N)인 이유 설명 가능
  • XOR 문제를 신경망으로 풀 수 있음

다음 강의 예고

"활성화 함수 심화" - Sigmoid, ReLU, GELU 등 활성화 함수의 세계를 깊이 탐구합니다!

레슨 정보

레벨
Level 3: 딥러닝 핵심
예상 소요 시간
25분
참고 영상
YouTube 링크

💡실습 환경 안내

이 레벨은 PyTorch/GPU가 필요하여 Google Colab 사용을 권장합니다.

Colab은 무료 GPU를 제공하여 PyTorch, CNN, Transformer 등을 실행할 수 있습니다.