
📓Google Colab에서 실습하기
이 레슨은 PyTorch/GPU가 필요합니다. 노트북을 다운로드 후 Google Colab에서 열어주세요.
학습 내용
손실 함수 (Loss Function) - 모델의 성적표
학습 목표
이 레슨을 완료하면:
- •손실 함수가 왜 필요한지 직관적으로 이해합니다
- •MSE(평균 제곱 오차)를 직접 구현하고 시각화할 수 있습니다
- •Cross-Entropy를 직접 구현하고 시각화할 수 있습니다
- •문제 유형에 따라 올바른 손실 함수를 선택할 수 있습니다
- •손실 지형(Loss Landscape)의 의미를 파악합니다
- •
loss.backward()가 내부적으로 무엇을 하는지 연결합니다
핵심 메시지
"손실 함수는 모델의 시험 채점표입니다. 점수(손실)가 낮을수록 모델이 잘하고 있다는 뜻!"
1. 비유: 시험 채점 시스템
학교 시험을 떠올려 봅시다.
학생이 답안을 제출하면, 선생님이 채점을 합니다. 채점 결과가 나쁘면 학생은 "어디가 틀렸는지" 파악하고, 다음번에 더 잘하려고 노력하죠.
딥러닝도 똑같습니다:
| 시험 채점 | 딥러닝 |
|---|---|
| 학생의 답안 | 모델의 예측값 |
| 정답지 | 실제 정답(label) |
| 채점 기준 | 손실 함수 |
| 점수(감점) | 손실값(loss) |
| 오답 노트 | 역전파(backpropagation) |
핵심은 이것입니다: 채점 기준(손실 함수)이 없으면, 모델은 자기가 얼마나 틀렸는지 알 수 없습니다.
2. 왜 손실 함수가 필요한가?
모델이 학습하려면 반드시 "지금 얼마나 틀렸는지" 를 숫자로 표현해야 합니다.
예를 들어 집값을 예측하는 모델이 있다고 합시다:
| 구분 | 값 |
|---|---|
| 실제 집값 | 5억 원 |
| 모델 예측 | 4억 원 |
| 오차 (손실) | 1억 원 ← 이것이 "손실(loss)" |
손실값이 크면 "많이 틀렸다", 작으면 "거의 맞았다"는 뜻입니다. 학습의 목표는 단 하나: 이 손실값을 최대한 줄이는 것!
그런데 "틀린 정도"를 측정하는 방법은 여러 가지가 있습니다. 문제 유형에 따라 다른 채점 기준(손실 함수)을 써야 합니다.
3. MSE (Mean Squared Error) - 회귀 문제의 채점표
MSE란?
MSE는 "오차를 제곱해서 평균 낸 것" 입니다. 숫자를 예측하는 회귀(regression) 문제에서 사용합니다.
수식:
여기서 는 예측값, 는 실제값, 은 데이터 개수입니다.
왜 제곱할까?
두 가지 이유가 있습니다:
| 이유 | 설명 |
|---|---|
| 방향 제거 | +1 오차와 -1 오차가 상쇄되면 안 됩니다. 제곱하면 둘 다 양수! |
| 큰 오차에 패널티 | 오차 1 -> 손실 1, 오차 2 -> 손실 4, 오차 3 -> 손실 9. 큰 실수에 더 큰 벌점! |
🔬 실습: MSE 단계별 구현
아래 코드를 실행하면 MSE가 어떻게 계산되는지 단계별로 확인할 수 있습니다.
pythonimport numpy as np # ═══════════════════════════════════════════════════════════════ # 📊 MSE(Mean Squared Error) 단계별 계산 # ═══════════════════════════════════════════════════════════════ # 📌 데이터 준비: 실제값 vs 모델의 예측값 y_true = np.array([3.0, 5.0, 2.5, 7.0, 4.5]) # 정답 (실제 데이터) y_pred = np.array([2.8, 5.2, 2.0, 6.5, 4.8]) # AI 모델의 예측 print("🎯 정답값:", y_true) print("🤖 예측값:", y_pred) print() # ───────────────────────────────────────────────────────────────── # Step 1️⃣: 오차 계산 (예측 - 정답) # ───────────────────────────────────────────────────────────────── errors = y_pred - y_true print("Step 1️⃣ 오차 (예측-정답):", errors) # 결과: [-0.2, 0.2, -0.5, -0.5, 0.3] # → 양수면 과대예측, 음수면 과소예측 # ───────────────────────────────────────────────────────────────── # Step 2️⃣: 오차 제곱 (음수를 양수로 + 큰 오차에 패널티) # ───────────────────────────────────────────────────────────────── squared_errors = errors ** 2 print("Step 2️⃣ 제곱 오차:", squared_errors) # 결과: [0.04, 0.04, 0.25, 0.25, 0.09] # → 모든 값이 양수가 됨! # ───────────────────────────────────────────────────────────────── # Step 3️⃣: 평균 계산 → 이것이 MSE! # ───────────────────────────────────────────────────────────────── mse = np.mean(squared_errors) print("Step 3️⃣ 평균 (MSE):", round(mse, 4)) print() # ═══════════════════════════════════════════════════════════════ # 💡 한 줄로 쓰면 이렇게 됩니다 # ═══════════════════════════════════════════════════════════════ mse_oneline = np.mean((y_pred - y_true) ** 2) print("📝 한 줄 공식:", round(mse_oneline, 4)) # ═══════════════════════════════════════════════════════════════ # ✅ 완벽한 예측은? MSE = 0 # ═══════════════════════════════════════════════════════════════ y_perfect = y_true.copy() # 정답과 똑같이 예측 mse_perfect = np.mean((y_perfect - y_true) ** 2) print() print("🏆 완벽한 예측의 MSE:", mse_perfect, "(0이면 완벽!)")
📈 시각화: MSE 손실 곡선
MSE가 예측 오차에 어떻게 반응하는지 그래프로 확인해봅시다.
pythonimport numpy as np import matplotlib.pyplot as plt # ═══════════════════════════════════════════════════════════════ # 📊 MSE 손실 곡선 시각화 # 정답이 5.0일 때, 예측값에 따라 손실이 어떻게 변하는지 확인 # ═══════════════════════════════════════════════════════════════ y_true = 5.0 # 🎯 정답값 (고정) y_preds = np.linspace(0, 10, 200) # 예측값 범위: 0 ~ 10 # 각 예측값에 대한 MSE 계산 mse_values = (y_preds - y_true) ** 2 # ───────────────────────────────────────────────────────────────── # 그래프 그리기 # ───────────────────────────────────────────────────────────────── plt.figure(figsize=(10, 6)) plt.plot(y_preds, mse_values, linewidth=3, color='royalblue', label='MSE Loss') plt.axvline(x=y_true, color='red', linestyle='--', linewidth=2, label='Answer = 5.0') plt.scatter([y_true], [0], color='red', s=100, zorder=5) # 정답 지점 표시 # 그래프 꾸미기 plt.xlabel('Prediction', fontsize=14) plt.ylabel('MSE Loss', fontsize=14) plt.title('MSE Loss Curve: Loss Increases Quadratically with Distance', fontsize=15) plt.legend(fontsize=12, loc='upper right') plt.grid(True, alpha=0.3) # 핵심 포인트 표시 plt.annotate('Correct! Loss=0', xy=(5, 0), xytext=(6.5, 5), fontsize=11, arrowprops=dict(arrowstyle='->', color='red'), color='red', fontweight='bold') plt.annotate('Error 2 -> Loss 4', xy=(7, 4), xytext=(8, 8), fontsize=10, arrowprops=dict(arrowstyle='->', color='gray')) plt.annotate('Error 3 -> Loss 9', xy=(8, 9), xytext=(9, 15), fontsize=10, arrowprops=dict(arrowstyle='->', color='gray')) plt.tight_layout() plt.show()
💡 핵심 포인트: 정답(5.0)에서 멀어질수록 손실이 제곱으로 커집니다!
- •오차 1 → 손실 1
- •오차 2 → 손실 4 (2배가 아닌 4배!)
- •오차 3 → 손실 9 (3배가 아닌 9배!)
이것이 MSE가 "큰 실수에 큰 벌점"을 주는 원리입니다.
4. Cross-Entropy - 분류 문제의 채점표
Cross-Entropy란?
분류(classification) 문제에서는 모델이 확률을 출력합니다. 예: "이 사진이 고양이일 확률 90%, 강아지일 확률 10%"
Cross-Entropy는 "정답 클래스의 확률이 얼마나 높은지" 를 측정합니다.
수식 (이진 분류):
여기서 는 정답(0 또는 1), 는 모델이 예측한 확률입니다.
직관적 이해
🖼️ 이미지: 고양이 사진
| 모델 | 예측 | 판정 | 손실 |
|---|---|---|---|
| 모델 A | "고양이 확률 95%" | ✅ 자신 있고 정답! | 낮음 |
| 모델 B | "고양이 확률 50%" | ⚠️ 반반이라고? | 중간 |
| 모델 C | "고양이 확률 5%" | ❌ 완전히 틀림! | 매우 높음 |
log가 하는 일
| 정답 확률 | = 손실 | 해석 |
|---|---|---|
| 0.95 (95%) | 0.05 | ✅ 거의 0, 훌륭! |
| 0.70 (70%) | 0.36 | ⚠️ 조금 불안 |
| 0.50 (50%) | 0.69 | ❌ 반반? 안 됨! |
| 0.10 (10%) | 2.30 | ❌ 큰 손실! |
| 0.01 (1%) | 4.61 | ❌ 엄청난 손실! |
확률이 0에 가까워질수록 손실이 급격히 증가합니다. "자신 있게 틀리면" 엄청난 패널티를 받는 것이죠!
🔬 실습: Cross-Entropy 직접 구현
좋은 모델과 나쁜 모델의 손실 차이를 직접 비교해봅시다.
pythonimport numpy as np # ═══════════════════════════════════════════════════════════════ # 📊 Binary Cross-Entropy (BCE) 직접 구현 # ═══════════════════════════════════════════════════════════════ def binary_cross_entropy(y_true, y_pred): """ BCE 손실 함수 - y_true: 정답 (0 또는 1) - y_pred: 모델이 예측한 확률 (0~1 사이 값) """ # ⚠️ 안전 장치: log(0)은 무한대가 되므로 극단값 방지 y_pred = np.clip(y_pred, 1e-7, 1 - 1e-7) # BCE 공식 적용 bce = -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred)) return bce # ───────────────────────────────────────────────────────────────── # 📌 데이터: 5장의 사진을 분류하는 문제 # ───────────────────────────────────────────────────────────────── # 🏷️ 정답: [고양이, 강아지, 고양이, 고양이, 강아지] # 고양이 = 1, 강아지 = 0 y_true = np.array([1, 0, 1, 1, 0]) print("🏷️ 정답:", ["고양이" if x==1 else "강아지" for x in y_true]) # ───────────────────────────────────────────────────────────────── # 🏆 좋은 모델: 정답에 가까운 확률 예측 # ───────────────────────────────────────────────────────────────── y_pred_good = np.array([0.9, 0.1, 0.85, 0.95, 0.05]) print("\n🤖 좋은 모델의 예측 (고양이 확률):", y_pred_good) print(" → 손실 (BCE):", round(binary_cross_entropy(y_true, y_pred_good), 4), "✅ 낮음!") # ───────────────────────────────────────────────────────────────── # ❌ 나쁜 모델: 엉뚱한 확률 예측 # ───────────────────────────────────────────────────────────────── y_pred_bad = np.array([0.3, 0.8, 0.4, 0.2, 0.7]) print("\n🤖 나쁜 모델의 예측 (고양이 확률):", y_pred_bad) print(" → 손실 (BCE):", round(binary_cross_entropy(y_true, y_pred_bad), 4), "❌ 높음!") # ───────────────────────────────────────────────────────────────── # 🎯 완벽한 모델: 100% 정확한 예측 # ───────────────────────────────────────────────────────────────── y_pred_perfect = np.array([1.0, 0.0, 1.0, 1.0, 0.0]) print("\n🤖 완벽한 모델의 예측:", y_pred_perfect) print(" → 손실 (BCE):", round(binary_cross_entropy(y_true, y_pred_perfect), 4), "🏆 거의 0!")
📈 시각화: Cross-Entropy 손실 곡선
pythonimport numpy as np import matplotlib.pyplot as plt # ═══════════════════════════════════════════════════════════════ # 📊 Cross-Entropy 손실 곡선 비교 # 정답이 다를 때 손실이 어떻게 달라지는지 확인 # ═══════════════════════════════════════════════════════════════ p = np.linspace(0.01, 0.99, 200) # 예측 확률 (0.01 ~ 0.99) # 📐 손실 계산 ce_loss_1 = -np.log(p) # 정답=1 (고양이) 일 때 손실 ce_loss_0 = -np.log(1 - p) # 정답=0 (강아지) 일 때 손실 # ───────────────────────────────────────────────────────────────── # 그래프 그리기 (2개 나란히) # ───────────────────────────────────────────────────────────────── fig, axes = plt.subplots(1, 2, figsize=(14, 5)) # 왼쪽 그래프: 정답이 고양이(1)인 경우 axes[0].plot(p, ce_loss_1, linewidth=3, color='crimson') axes[0].set_xlabel('Prediction: P(cat)', fontsize=12) axes[0].set_ylabel('Loss', fontsize=12) axes[0].set_title('When answer is Cat', fontsize=14) axes[0].grid(True, alpha=0.3) axes[0].annotate('High prob\nLow loss', xy=(0.9, 0.1), fontsize=10, ha='center', color='green', fontweight='bold') axes[0].annotate('Low prob\nLoss explodes!', xy=(0.1, 2.3), fontsize=10, ha='center', color='red', fontweight='bold') # 오른쪽 그래프: 정답이 강아지(0)인 경우 axes[1].plot(p, ce_loss_0, linewidth=3, color='teal') axes[1].set_xlabel('Prediction: P(cat)', fontsize=12) axes[1].set_ylabel('Loss', fontsize=12) axes[1].set_title('When answer is Dog', fontsize=14) axes[1].grid(True, alpha=0.3) axes[1].annotate('Low prob\nLow loss', xy=(0.1, 0.1), fontsize=10, ha='center', color='green', fontweight='bold') axes[1].annotate('High prob\nLoss explodes!', xy=(0.9, 2.3), fontsize=10, ha='center', color='red', fontweight='bold') plt.tight_layout() plt.show()
💡 그래프 해석:
- •왼쪽 (정답=고양이🐱): "고양이 확률"을 낮게 예측하면 손실 급증!
- •오른쪽 (정답=강아지🐕): "고양이 확률"을 높게 예측하면 손실 급증!
Cross-Entropy는 **"자신 있게 틀리면 큰 벌점"**을 주는 것이 핵심입니다.
5. MSE vs Cross-Entropy 비교
선택 가이드
| 문제 유형 | 손실 함수 | 출력층 활성화 | 예시 |
|---|---|---|---|
| 회귀 (숫자 예측) | MSE | 없음 (선형) | 집값, 온도, 주가 |
| 이진 분류 (예/아니오) | Binary Cross-Entropy | Sigmoid | 스팸/정상, 합격/불합격 |
| 다중 분류 (여러 클래스) | Categorical Cross-Entropy | Softmax | 고양이/개/새, 숫자 0-9 |
왜 분류에 MSE를 쓰면 안 될까?
| 손실 함수 | 확률 0.9 vs 0.99 비교 | 학습 속도 | 적합성 |
|---|---|---|---|
| MSE | 0.01 vs 0.0001 (거의 같음!) | ❌ 느림 | 분류에 부적합 |
| Cross-Entropy | 0.10 vs 0.01 (10배 차이!) | ✅ 빠름 | 분류에 최적 |
💡 핵심: Cross-Entropy는 "자신 있게 틀릴 때" 큰 패널티를 줘서 빠르게 학습합니다!
📈 실습: MSE vs Cross-Entropy 비교
왜 분류 문제에서 Cross-Entropy를 사용하는지 직접 확인해봅시다.
pythonimport numpy as np import matplotlib.pyplot as plt # ═══════════════════════════════════════════════════════════════ # 📊 MSE vs Cross-Entropy 손실 비교 # 분류 문제에서 왜 CE가 더 좋은지 시각화 # ═══════════════════════════════════════════════════════════════ p = np.linspace(0.01, 0.99, 200) # 예측 확률 범위 # 📐 정답이 1일 때 각 손실 함수 계산 mse_loss = (1 - p) ** 2 # MSE: (정답 - 예측)² ce_loss = -np.log(p) # CE: -log(예측) plt.figure(figsize=(8, 5)) plt.plot(p, mse_loss, linewidth=2, label='MSE', color='royalblue') plt.plot(p, ce_loss, linewidth=2, label='Cross-Entropy', color='crimson') plt.xlabel('predicted probability (label=1)', fontsize=12) plt.ylabel('Loss', fontsize=12) plt.title('MSE vs Cross-Entropy (when true label = 1)', fontsize=13) plt.legend(fontsize=12) plt.grid(True, alpha=0.3) plt.tight_layout() plt.show() print('p=0.01 -> MSE:', round((1-0.01)**2, 3), ' CE:', round(-np.log(0.01), 3)) print('p=0.50 -> MSE:', round((1-0.50)**2, 3), ' CE:', round(-np.log(0.50), 3)) print('p=0.99 -> MSE:', round((1-0.99)**2, 3), ' CE:', round(-np.log(0.99), 3))
Cross-Entropy는 예측 확률이 0에 가까울 때 MSE보다 훨씬 더 큰 패널티를 줍니다. 그래서 분류 문제에서 학습이 더 빠르고 효과적입니다!
6. 손실 지형 (Loss Landscape) 시각화
가중치를 바꿀 때 손실값이 어떻게 변하는지를 지형(landscape) 으로 표현할 수 있습니다. 마치 산과 계곡처럼 생긴 3D 표면이죠.
핵심 용어
| 용어 | 의미 |
|---|---|
| 전역 최솟값 (Global Minimum) | 전체에서 가장 낮은 곳. 이것이 목표! |
| 지역 최솟값 (Local Minimum) | 주변보다 낮지만, 전체 최저는 아닌 곳. 함정! |
| 안장점 (Saddle Point) | 한 방향은 최소, 다른 방향은 최대인 곳 |
실행해보기: 손실 지형 등고선 그래프
pythonimport numpy as np import matplotlib.pyplot as plt # 2개의 가중치(w1, w2)에 대한 손실 함수 지형 만들기 # 여러 극솟값이 있는 복잡한 표면 w1 = np.linspace(-3, 3, 200) w2 = np.linspace(-3, 3, 200) W1, W2 = np.meshgrid(w1, w2) # 복잡한 손실 함수 (여러 골짜기가 있음) Loss = (W1**2 + W2**2) * 0.5 + 2 * np.sin(W1 * 2) * np.cos(W2 * 2) + 3 plt.figure(figsize=(8, 6)) contour = plt.contourf(W1, W2, Loss, levels=30, cmap='RdYlBu_r') plt.colorbar(contour, label='Loss value') plt.xlabel('Weight 1 (w1)', fontsize=12) plt.ylabel('Weight 2 (w2)', fontsize=12) plt.title('Loss Landscape (contour plot)', fontsize=13) # 전역 최솟값 근처에 표시 min_idx = np.unravel_index(np.argmin(Loss), Loss.shape) plt.plot(W1[min_idx], W2[min_idx], 'w*', markersize=20, label='Global Minimum') plt.legend(fontsize=11) plt.tight_layout() plt.show() print('Global minimum at w1={:.2f}, w2={:.2f}'.format(W1[min_idx], W2[min_idx])) print('Minimum loss: {:.2f}'.format(Loss[min_idx]))
파란색이 손실이 낮은 곳(계곡), 빨간색이 높은 곳(산)입니다. 학습의 목표는 빨간색에서 시작해서 가장 깊은 파란색 계곡을 찾아가는 것입니다!
7. PyTorch에서의 손실 함수
실제 PyTorch 코드에서는 이렇게 사용합니다 (참고용):
loss.backward()는 "이 손실 함수를 각 가중치로 미분해라" 라는 명령입니다.
그 미분값(gradient)이 다음 레슨에서 배울 경사하강법의 핵심 재료가 됩니다!
핵심 요약
| 개념 | 설명 | 비유 |
|---|---|---|
| 손실 함수 | 모델이 얼마나 틀렸는지 측정하는 함수 | 시험 채점 기준 |
| MSE | 오차를 제곱해서 평균. 회귀 문제용 | 감점 = (오답 크기)^2 |
| Cross-Entropy | 정답 확률의 -log. 분류 문제용 | 자신있게 틀리면 큰 감점 |
| 손실 지형 | 가중치에 따른 손실값의 3D 지형 | 산과 계곡 |
| loss.backward() | 손실 함수의 기울기를 계산 | 어느 방향이 내리막인지 확인 |
학습 체크리스트
- • 손실 함수가 왜 필요한지 설명할 수 있다
- • MSE를 numpy로 직접 구현할 수 있다
- • Cross-Entropy를 numpy로 직접 구현할 수 있다
- • 회귀에는 MSE, 분류에는 CE를 쓰는 이유를 안다
- • 손실 지형에서 전역 최솟값과 지역 최솟값의 차이를 안다
- • loss.backward()가 손실 함수의 기울기를 계산한다는 것을 안다
다음 강의 예고
"경사하강법 실전" - 손실 지형에서 가장 낮은 곳을 찾아가는 방법! SGD, Momentum, Adam 등 다양한 최적화 알고리즘을 직접 구현합니다.
레슨 정보
- 레벨
- Level 3: 딥러닝 핵심
- 예상 소요 시간
- 6분 0초
- 참고 영상
- YouTube 링크
💡실습 환경 안내
이 레벨은 PyTorch/GPU가 필요하여 Google Colab 사용을 권장합니다.
Colab은 무료 GPU를 제공하여 PyTorch, CNN, Transformer 등을 실행할 수 있습니다.