AI 모델이 학습을 시작하려면 가중치에 초기값을 넣어야 합니다. 그 초기값을 어떤 분포에서 뽑는지에 따라 학습이 안정되기도 하고, 처음부터 흔들리기도 합니다. 이 글은 가우시안 분포와 균등 분포라는 두 가지 확률 개념을 NumPy 시뮬레이션으로 직접 구현해, 실제로 동작하는 프로토타입을 손으로 확인하는 과정을 다룹니다.
AI 모델은 왜 확률적으로 사고하는가: 불확실성의 정량화
AI 모델은 "정답을 외우는 기계"가 아닙니다. 정답에 가까울 확률을 높여 나가는 구조입니다.
분류 모델을 예로 들면, 마지막 출력층은 "이 이미지가 고양이일 확률은 0.82, 개일 확률은 0.18"처럼 확률 벡터를 내놓습니다. 이 확률은 소프트맥스(Softmax) 함수가 만들어 내고, 손실 함수는 그 확률이 실제 정답 분포와 얼마나 다른지를 수치로 계산합니다. 손실을 줄이는 과정이 곧 학습입니다.
학습이 시작되는 첫 순간에도 확률이 개입합니다. 가중치 초기값을 어떤 분포에서 샘플링하느냐에 따라 그래디언트(gradient)가 폭발하거나 소멸할 수 있습니다. 확률 분포는 그래서 AI 수학에서 장식이 아니라 뼈대에 해당합니다.
어려운 수학이 필요하다고 걱정할 필요는 없습니다. 지금 당장 필요한 건 분포 두 가지의 성질을 이해하고, NumPy 함수를 한 줄씩 실행해 보는 경험입니다.
가우시안 분포와 균등 분포: 가중치 초기화에 쓰이는 핵심 개념
가우시안 분포(정규 분포)는 평균을 중심으로 종 모양으로 퍼지는 분포입니다. 두 매개변수로 정의됩니다.
- 평균(μ): 값들이 모이는 중심
- 표준편차(σ): 평균에서 얼마나 넓게 퍼지는지의 정도
딥러닝에서 가중치를 초기화할 때 평균 0, 작은 표준편차(예: 0.01)로 설정하는 이유는 초기 가중치가 너무 크면 출력이 포화 상태에 빠져 그래디언트가 사라지기 때문입니다.
균등 분포(Uniform Distribution)는 지정한 구간 [a, b] 안에서 모든 값이 동일한 확률로 선택됩니다. Xavier 초기화(Glorot Initialization)처럼 층의 입출력 뉴런 수를 고려해 구간을 설정하는 방식이 여기서 나옵니다.
두 분포를 나란히 비교하면 이렇습니다.
| 분포 | 형태 | 초기화 활용 예 | 장점 | 주의점 |
|---|---|---|---|---|
| 가우시안(정규) | 종 모양, 평균 중심 | 작은 σ로 무작위 초기화 | 학습 초기 안정성 | σ가 크면 출력 포화 위험 |
| 균등 | 구간 내 평평 | Xavier / He 초기화 | 층별 분산 제어 | 구간 설정에 신중해야 함 |
| 표준정규 (μ=0, σ=1) | 가우시안의 특수형 | z-score 정규화 | 스케일 통일 | 원래 단위 해석 주의 |
Xavier 초기화는 활성화 함수가 선형에 가까울 때(Sigmoid, tanh) 잘 작동합니다. 반면 ReLU 계열 활성화 함수를 쓸 때는 He 초기화(가우시안 기반, σ를 입력 뉴런 수에 맞게 조정)가 더 적합합니다. 초기화 방식 선택은 단순한 취향이 아니라, 쓰는 활성화 함수에 따라 달라지는 구조적 결정입니다.
파이썬 NumPy를 활용한 확률적 시뮬레이션 프로토타입 작성
아래 예제는 비전공자도 한 줄씩 따라올 수 있도록 단계를 나눴습니다. 실행 환경은 Python 3.8 이상, NumPy와 Matplotlib이 설치된 상태면 충분합니다.
1단계: 가우시안 분포로 가중치 초기화 시뮬레이션
import numpy as np
import matplotlib.pyplot as plt
# 뉴런 수가 64인 레이어의 가중치를 가우시안 분포로 초기화
np.random.seed(42)
weights_gaussian = np.random.normal(loc=0.0, scale=0.01, size=(64, 64))
print("가우시안 초기화 가중치 통계")
print(f" 평균: {weights_gaussian.mean():.6f}")
print(f" 표준편차: {weights_gaussian.std():.6f}")
print(f" 최솟값: {weights_gaussian.min():.6f}")
print(f" 최댓값: {weights_gaussian.max():.6f}")
출력 예시:
가우시안 초기화 가중치 통계
평균: 0.000032
표준편차: 0.009987
최솟값: -0.030451
최댓값: 0.027913
평균이 거의 0, 표준편차가 0.01에 근접합니다. 이 수치가 의미하는 건 초기 가중치들이 0 근방에 조밀하게 모여 있다는 것입니다.
2단계: Xavier 균등 초기화 구현
Xavier 초기화의 구간은 [-√(6 / (fan_in + fan_out)), +√(6 / (fan_in + fan_out))]입니다. fan_in은 입력 뉴런 수, fan_out은 출력 뉴런 수입니다.
fan_in, fan_out = 64, 64
limit = np.sqrt(6.0 / (fan_in + fan_out))
weights_xavier = np.random.uniform(low=-limit, high=limit, size=(fan_in, fan_out))
print(f"\nXavier 초기화 구간: [{-limit:.4f}, {limit:.4f}]")
print(f" 평균: {weights_xavier.mean():.6f}")
print(f" 표준편차: {weights_xavier.std():.6f}")
출력 예시:
Xavier 초기화 구간: [-0.3062, 0.3062]
평균: 0.000847
표준편차: 0.176951
균등 분포라서 표준편차가 가우시안보다 훨씬 크게 나옵니다. 층이 깊어질수록 이 차이가 학습 속도와 안정성에 영향을 줍니다.
3단계: 크로스 엔트로피 손실로 확률 분포 평가
분류 모델의 출력은 확률 벡터입니다. 손실 함수인 크로스 엔트로피(Cross-Entropy)는 예측 확률과 실제 정답 사이의 거리를 측정합니다.
def cross_entropy_loss(y_true, y_pred):
"""
y_true: 정답 원-핫 벡터 (예: [0, 1, 0])
y_pred: 모델 출력 확률 벡터 (예: [0.1, 0.8, 0.1])
"""
epsilon = 1e-9 # log(0) 방지
y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
return -np.sum(y_true * np.log(y_pred))
# 예시: 정답이 클래스 1인 경우
y_true = np.array([0, 1, 0])
# 잘 예측한 경우
y_pred_good = np.array([0.05, 0.90, 0.05])
# 잘못 예측한 경우
y_pred_bad = np.array([0.60, 0.20, 0.20])
loss_good = cross_entropy_loss(y_true, y_pred_good)
loss_bad = cross_entropy_loss(y_true, y_pred_bad)
print(f"좋은 예측의 손실: {loss_good:.4f}")
print(f"나쁜 예측의 손실: {loss_bad:.4f}")
출력 예시:
좋은 예측의 손실: 0.1054
나쁜 예측의 손실: 1.6094
손실값이 작을수록 예측 확률 분포가 정답에 가깝습니다. 학습의 목표가 이 숫자를 줄이는 것이고, 그 과정에서 확률 분포 개념이 핵심 역할을 합니다.
4단계: 두 초기화 방식의 분포를 시각화
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].hist(weights_gaussian.flatten(), bins=50, color='steelblue', alpha=0.8)
axes[0].set_title('가우시안 초기화 (μ=0, σ=0.01)')
axes[0].set_xlabel('가중치 값')
axes[0].set_ylabel('빈도')
axes[1].hist(weights_xavier.flatten(), bins=50, color='tomato', alpha=0.8)
axes[1].set_title('Xavier 균등 초기화')
axes[1].set_xlabel('가중치 값')
plt.tight_layout()
plt.savefig('weight_distributions.png', dpi=150)
plt.show()
히스토그램을 나란히 놓으면 가우시안은 중심에 몰리고, 균등은 구간 전체에 고르게 퍼진 모양을 직접 눈으로 확인할 수 있습니다. 코드 한 줄이 개념을 몸으로 이해하게 만드는 순간입니다.
모두의AI 인터랙티브 Playground를 통한 실전 확률 연습
위 코드를 직접 타이핑하는 것도 좋지만, 처음엔 결과를 즉시 보며 감을 잡는 편이 훨씬 빠릅니다. 모두의AI의 인터랙티브 Playground에서는 σ 값이나 fan_in/fan_out 숫자를 슬라이더로 바꾸면 분포 그래프가 실시간으로 바뀌는 환경을 제공합니다. 코드를 처음부터 짜지 않아도 "σ를 크게 하면 어떻게 되는가"를 직접 실험할 수 있습니다.
확률 분포 대시보드와 실전 실습 환경은 mdooai.com에서 확인할 수 있습니다. 개념 설명 직후 바로 실습으로 이어지는 구조라, 글만 읽고 넘어가는 것보다 개념이 훨씬 오래 남습니다.
자주 묻는 질문
가우시안 분포와 정규 분포는 다른 건가요?
같은 분포를 부르는 두 이름입니다. 정규 분포(Normal Distribution)가 공식 명칭이고, 가우시안 분포는 수학자 가우스의 이름에서 온 별칭입니다. AI 문서에서는 두 용어를 혼용하므로 어느 쪽을 만나도 동일하게 이해하면 됩니다.
가중치 초기화를 전부 0으로 하면 왜 안 되나요?
모든 뉴런이 같은 값으로 시작하면 역전파 과정에서 그래디언트도 동일하게 계산됩니다. 결과적으로 모든 뉴런이 똑같이 업데이트되어 층이 아무리 많아도 하나의 뉴런과 다를 바 없어집니다. 이를 대칭성 깨기 실패(symmetry breaking failure)라 부릅니다.
크로스 엔트로피 손실을 직접 구현하지 않고 라이브러리를 써도 되나요?
실무에서는 PyTorch의 nn.CrossEntropyLoss나 TensorFlow의 tf.keras.losses.CategoricalCrossentropy를 씁니다. 직접 구현하는 이유는 내부에서 어떤 확률 계산이 일어나는지 이해하기 위해서입니다. 구조를 한 번 손으로 써봤다면 라이브러리를 쓸 때 매개변수 의미가 훨씬 명확해집니다.
NumPy만으로 딥러닝을 공부할 수 있나요?
초기화와 손실 계산 같은 개념을 이해하는 데는 NumPy로 충분합니다. 실제 모델을 학습시키려면 자동 미분 기능이 있는 PyTorch나 TensorFlow가 필요하지만, 확률 분포의 작동 원리는 NumPy 수준에서 완전히 실험할 수 있습니다.
확률 분포 개념 없이 AI 프레임워크를 바로 배우면 안 되나요?
배울 수는 있습니다. 다만 초기화 옵션이나 손실 함수 선택 앞에서 막히는 순간이 반드시 옵니다. 그때 "왜 이 옵션인가"를 이해하지 못하면 무작정 복붙하는 코드가 됩니다. 확률 분포를 먼저 짚어두면 그 선택 기준이 생깁니다.
AI 모델을 공부할 때 확률통계가 어렵게 느껴지는 건 개념이 어렵기 때문이 아니라, 코드와 개념이 연결되는 경험이 없어서인 경우가 대부분입니다. 가우시안 분포로 가중치를 초기화하고, 균등 분포로 Xavier 범위를 계산하고, 크로스 엔트로피로 확률 예측을 평가하는 이 세 단계를 NumPy로 직접 실행해 보면 수식이 살아있는 도구처럼 느껴집니다. 모두의AI가 추구하는 "한 걸음씩, 올바르게"는 바로 이 지점에서 시작합니다. 개념을 외우기 전에 먼저 실행해 보세요.
더 보기: mdooai.com
Top comments (0)