본문 바로가기

딥러닝/딥러닝 수학 기초

확률과 통계 핵심 (확률분포, 기댓값, 정규분포)

들어가며

딥러닝을 공부하면서 확률과 통계가 왜 필요한지 처음엔 와닿지 않았다. 그런데 딥러닝 모델의 출력값은 결국 확률이고, 손실 함수도 확률 개념에서 나온다. 데이터의 분포를 이해해야 모델이 왜 그렇게 동작하는지 파악할 수 있다. 딥러닝에 필요한 확률과 통계 핵심을 정리해둔다.


확률 기초

확률이란?

어떤 사건이 일어날 가능성을 0과 1 사이의 숫자로 나타낸 것이다.

0 ≤ P(A) ≤ 1
모든 사건의 확률 합 = 1

딥러닝에서 Softmax 출력값이 바로 확률이다.

import numpy as np

# Softmax 출력: 각 클래스에 속할 확률
probs = np.array([0.7, 0.2, 0.1])  # 클래스 0: 70%, 1: 20%, 2: 10%
print(f"확률 합: {probs.sum()}")    # 1.0

predicted_class = np.argmax(probs)
print(f"예측 클래스: {predicted_class}")  # 0

조건부 확률

다른 사건이 일어났을 때 특정 사건이 일어날 확률이다.

P(A|B) = P(A ∩ B) / P(B)
"B가 주어졌을 때 A의 확률"

딥러닝에서 이미지 분류 모델은 결국 P(클래스 | 입력 이미지)를 학습하는 것이다.

P(고양이 | 이미지) = 0.85
P(개     | 이미지) = 0.12
P(기타   | 이미지) = 0.03

확률분포 (Probability Distribution)

확률변수가 어떤 값을 가질 수 있는지, 각 값의 확률이 얼마인지를 나타낸다.

이산 확률분포 (Discrete)

# 동전 던지기: 앞면(0), 뒷면(1)
outcomes = [0, 1]
probs = [0.5, 0.5]

# 딥러닝 분류 모델의 출력도 이산 확률분포
class_probs = {
    '고양이': 0.85,
    '개': 0.12,
    '기타': 0.03
}

연속 확률분포 (Continuous)

가질 수 있는 값이 연속적인 경우다. 딥러닝에서 가중치 초기화, 데이터 분포 등에서 자주 등장한다.

# 연속 확률분포의 예: 정규분포
x = np.linspace(-4, 4, 100)
mean, std = 0, 1
y = (1 / (std * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mean) / std) ** 2)

print(f"전체 면적 ≈ {np.trapz(y, x):.4f}")  # ≈ 1.0

기댓값과 분산

기댓값 (Expected Value)

확률변수의 평균값이다. 많은 시행을 반복했을 때 기대되는 평균적인 결과다.

E[X] = Σ x * P(x)    (이산)
E[X] = ∫ x * f(x) dx  (연속)
# 주사위의 기댓값
outcomes = np.array([1, 2, 3, 4, 5, 6])
probs = np.array([1/6] * 6)
expected = np.sum(outcomes * probs)
print(f"주사위 기댓값: {expected}")  # 3.5

# 배치 손실의 평균 = 기댓값의 추정
losses = np.array([0.3, 0.5, 0.2, 0.8, 0.1])
batch_loss = np.mean(losses)
print(f"배치 평균 손실: {batch_loss}")  # 0.38

분산 (Variance)과 표준편차 (Standard Deviation)

데이터가 평균에서 얼마나 퍼져 있는지를 나타낸다.

Var(X) = E[(X - μ)²] = E[X²] - (E[X])²
std(X) = √Var(X)
data = np.array([2, 4, 4, 4, 5, 5, 7, 9])

mean = np.mean(data)
variance = np.var(data)
std = np.std(data)

print(f"평균: {mean}")      # 5.0
print(f"분산: {variance}")  # 4.0
print(f"표준편차: {std}")   # 2.0

가중치 초기화 시 분산이 너무 크면 값이 폭발하고, 분산이 너무 작으면 기울기 소실이 발생한다. 적절한 분산으로 초기화하는 게 중요하다.


정규분포 (Normal Distribution)

딥러닝에서 가장 자주 등장하는 확률분포다.

f(x) = (1 / σ√2π) * exp(-(x-μ)² / 2σ²)

μ (mu)    : 평균 (분포의 중심)
σ (sigma) : 표준편차 (분포의 퍼짐 정도)

표준 정규분포

평균 0, 표준편차 1인 정규분포다.

# 표준 정규분포에서 샘플링
samples = np.random.randn(10000)

print(f"평균: {samples.mean():.4f}")    # ≈ 0
print(f"표준편차: {samples.std():.4f}") # ≈ 1

# 정규분포의 68-95-99.7 규칙
within_1std = np.sum(np.abs(samples) < 1) / len(samples)
within_2std = np.sum(np.abs(samples) < 2) / len(samples)
within_3std = np.sum(np.abs(samples) < 3) / len(samples)

print(f"±1σ 내: {within_1std:.1%}")  # ≈ 68%
print(f"±2σ 내: {within_2std:.1%}")  # ≈ 95%
print(f"±3σ 내: {within_3std:.1%}")  # ≈ 99.7%

딥러닝에서 정규분포 활용

# Xavier 초기화: 입력/출력 수에 맞게 분산 조정
def xavier_init(n_in, n_out):
    std = np.sqrt(2 / (n_in + n_out))
    return np.random.randn(n_in, n_out) * std

# He 초기화 (ReLU에 적합)
def he_init(n_in, n_out):
    std = np.sqrt(2 / n_in)
    return np.random.randn(n_in, n_out) * std

W_xavier = xavier_init(128, 64)
W_he = he_init(128, 64)

print(f"Xavier 초기화 std: {W_xavier.std():.4f}")
print(f"He 초기화 std: {W_he.std():.4f}")

# 데이터 정규화 (평균 0, 표준편차 1로 변환)
data = np.random.randn(100, 10) * 5 + 3   # 평균 3, 표준편차 5
mean = data.mean(axis=0)
std = data.std(axis=0)
normalized = (data - mean) / std

print(f"정규화 전 평균: {data.mean():.2f}, std: {data.std():.2f}")
print(f"정규화 후 평균: {normalized.mean():.4f}, std: {normalized.std():.4f}")

엔트로피 (Entropy)

정보이론에서 불확실성의 정도를 나타낸다. 딥러닝의 손실 함수와 직접 연결된다.

H(X) = -Σ P(x) * log P(x)
def entropy(probs):
    probs = np.array(probs)
    return -np.sum(probs * np.log(probs + 1e-10))

# 완전히 확실한 경우 (엔트로피 = 0)
certain = [1.0, 0.0, 0.0]
print(f"확실한 예측 엔트로피: {entropy(certain):.4f}")   # ≈ 0

# 완전히 불확실한 경우 (엔트로피 최대)
uncertain = [1/3, 1/3, 1/3]
print(f"불확실한 예측 엔트로피: {entropy(uncertain):.4f}")  # ≈ 1.099

# 중간 경우
medium = [0.7, 0.2, 0.1]
print(f"중간 예측 엔트로피: {entropy(medium):.4f}")  # ≈ 0.802

모델이 특정 클래스라고 확신할수록 엔트로피가 낮다. 어느 클래스인지 모를수록 엔트로피가 높다.


크로스 엔트로피 (Cross-Entropy)

딥러닝 분류 문제에서 가장 많이 쓰이는 손실 함수다. 실제 분포와 예측 분포 사이의 차이를 측정한다.

CE(y, ŷ) = -Σ y * log(ŷ)

y  : 실제 정답 (one-hot 벡터)
ŷ  : 모델 예측 확률
def cross_entropy(y_true, y_pred):
    y_pred = np.clip(y_pred, 1e-10, 1.0)  # log(0) 방지
    return -np.sum(y_true * np.log(y_pred))

y_true = np.array([1, 0, 0])  # 클래스 0이 정답

y_pred_good = np.array([0.9, 0.05, 0.05])
print(f"좋은 예측 CE: {cross_entropy(y_true, y_pred_good):.4f}")   # ≈ 0.105

y_pred_bad = np.array([0.1, 0.8, 0.1])
print(f"나쁜 예측 CE: {cross_entropy(y_true, y_pred_bad):.4f}")    # ≈ 2.303

y_pred_perfect = np.array([1.0, 0.0, 0.0])
print(f"완벽한 예측 CE: {cross_entropy(y_true, y_pred_perfect):.4f}")  # ≈ 0

왜 크로스 엔트로피를 쓰는가:

정확도(Accuracy)를 직접 최적화하면 안 되는 이유:
- 정확도는 불연속 함수 → 미분 불가
- 크로스 엔트로피는 연속 함수 → 미분 가능 → 경사하강법 적용 가능

또한 크로스 엔트로피는 모델이 확신을 가지고 틀렸을 때 더 큰 패널티를 준다.
예: 정답이 0인데 모델이 99% 확률로 1이라고 예측 → 큰 손실
    정답이 0인데 모델이 51% 확률로 1이라고 예측 → 작은 손실

MSE (Mean Squared Error)

회귀 문제에서 주로 사용하는 손실 함수다.

MSE = (1/n) * Σ (y_true - y_pred)²
def mse(y_true, y_pred):
    return np.mean((y_true - y_pred) ** 2)

y_true = np.array([1.0, 2.0, 3.0, 4.0, 5.0])

y_pred_good = np.array([1.1, 1.9, 3.2, 3.8, 5.1])
print(f"좋은 예측 MSE: {mse(y_true, y_pred_good):.4f}")  # ≈ 0.024

y_pred_bad = np.array([2.0, 3.0, 4.0, 5.0, 6.0])
print(f"나쁜 예측 MSE: {mse(y_true, y_pred_bad):.4f}")   # 1.0

분류 문제 → Cross-Entropy / 회귀 문제 → MSE


최대우도추정 (MLE)

딥러닝 학습의 이론적 근거다. 주어진 데이터를 가장 잘 설명하는 파라미터를 찾는 방법이다.

θ* = argmax P(데이터 | θ)
# 이진 분류에서 MLE = Cross-Entropy 최소화
# 크로스 엔트로피 최소화 = 로그 우도 최대화

# 로그를 취하면 곱셈이 덧셈으로 변환되어 계산이 쉬워진다
# log P(D|θ) = Σ log P(xᵢ|θ)  → 최대화
# -log P(D|θ) = -Σ log P(xᵢ|θ) → 최소화 (= Cross-Entropy)

# 따라서 Cross-Entropy를 최소화하는 것이
# 데이터를 가장 잘 설명하는 모델을 찾는 것과 동일하다

정리

개념 설명 딥러닝 활용
확률 사건이 일어날 가능성 Softmax 출력값
조건부 확률 조건 하에서의 확률 P(클래스|이미지) 학습
기댓값 확률 가중 평균 배치 손실 평균
분산 데이터의 퍼짐 정도 가중치 초기화
정규분포 가장 흔한 연속 분포 가중치 초기화, 데이터 정규화
엔트로피 불확실성의 정도 손실 함수 기반
크로스 엔트로피 두 분포 사이의 차이 분류 손실 함수
MSE 예측값과 실제값의 차이 회귀 손실 함수
MLE 데이터 우도 최대화 딥러닝 학습의 이론적 근거