콘텐츠로 이동
Data Prep
상세

확률 분포 (Probability Distributions)

데이터 생성 과정을 모델링하는 수학적 함수. 적절한 분포 선택은 ML 모델의 성능에 직접적인 영향을 미친다.

왜 확률 분포가 중요한가

  • 손실 함수 설계: MSE는 정규분포, Cross-Entropy는 베르누이/카테고리
  • 생성 모델: VAE, GAN, Diffusion 모델의 핵심
  • 베이지안 추론: 사전 분포 선택
  • 데이터 이해: 현상의 생성 메커니즘 파악

이산 확률 분포

베르누이 분포 (Bernoulli Distribution)

성공/실패 두 가지 결과만 있는 단일 시행.

\[P(X=k) = p^k(1-p)^{1-k}, \quad k \in \{0, 1\}\]
통계량
평균 \(E[X] = p\)
분산 \(Var(X) = p(1-p)\)

직관: 동전 던지기 한 번. 앞면(1) 또는 뒷면(0).

from scipy import stats
import numpy as np

# 베르누이 분포
p = 0.7
bernoulli = stats.bernoulli(p)

# 샘플링
samples = bernoulli.rvs(1000)
print(f"표본 평균: {samples.mean():.3f}, 이론값: {p}")

# PMF (확률 질량 함수)
print(f"P(X=0) = {bernoulli.pmf(0):.3f}")
print(f"P(X=1) = {bernoulli.pmf(1):.3f}")

ML 활용: - 이진 분류의 출력 (로지스틱 회귀) - Dropout 마스크 - 클릭/미클릭, 구매/미구매

이항 분포 (Binomial Distribution)

n번의 독립적인 베르누이 시행에서 성공 횟수.

\[P(X=k) = \binom{n}{k}p^k(1-p)^{n-k}\]
통계량
평균 \(E[X] = np\)
분산 \(Var(X) = np(1-p)\)

직관: 동전을 n번 던져서 앞면이 나온 횟수.

n, p = 100, 0.3
binomial = stats.binom(n, p)

# PMF 시각화
import matplotlib.pyplot as plt

x = np.arange(0, n+1)
pmf = binomial.pmf(x)

plt.figure(figsize=(10, 4))
plt.bar(x, pmf, width=1, edgecolor='black')
plt.xlabel('k (성공 횟수)')
plt.ylabel('P(X=k)')
plt.title(f'Binomial(n={n}, p={p})')
plt.xlim(0, 60)

# 누적 확률
print(f"P(X <= 30) = {binomial.cdf(30):.4f}")
print(f"P(25 <= X <= 35) = {binomial.cdf(35) - binomial.cdf(24):.4f}")

ML 활용: - A/B 테스트 (전환 수) - 샘플링된 정확도 분포

포아송 분포 (Poisson Distribution)

단위 시간/공간당 사건 발생 횟수. 드문 사건의 모델.

\[P(X=k) = \frac{\lambda^k e^{-\lambda}}{k!}\]
통계량
평균 \(E[X] = \lambda\)
분산 \(Var(X) = \lambda\)

직관: 1시간에 평균 5건의 전화가 온다면, 정확히 3건 올 확률은?

핵심 특성: 평균 = 분산 (등분산성)

# 시간당 평균 5건의 이벤트
lambda_ = 5
poisson = stats.poisson(lambda_)

# P(10건 이상)
p_10_or_more = 1 - poisson.cdf(9)
print(f"P(X >= 10) = {p_10_or_more:.4f}")

# 포아송 과정 시뮬레이션
# 시간 간격 0.01로 24시간 시뮬레이션
hours = 24
dt = 0.01
rate_per_dt = lambda_ * dt  # 작은 구간의 발생률

events = []
for t in np.arange(0, hours, dt):
    if np.random.random() < rate_per_dt:
        events.append(t)

print(f"발생 이벤트 수: {len(events)} (기대값: {lambda_ * hours})")

ML 활용: - 웹 트래픽 모델링 - 텍스트의 단어 빈도 (희귀 단어) - 이상 탐지 (비정상적 이벤트 수)

이항 → 포아송 근사: n이 크고 p가 작을 때, \(\lambda = np\)로 포아송 근사 가능.

기하 분포 (Geometric Distribution)

첫 번째 성공까지의 시행 횟수.

\[P(X=k) = (1-p)^{k-1}p\]
통계량
평균 \(E[X] = 1/p\)
분산 \(Var(X) = (1-p)/p^2\)

직관: 처음으로 앞면이 나올 때까지 몇 번 던져야 하나?

무기억성: 이미 k번 실패했어도, 앞으로의 기대 시행 횟수는 여전히 1/p.

p = 0.2
geometric = stats.geom(p)

print(f"E[X] = {1/p}")  # 평균 5번 시행 필요
print(f"P(X <= 3) = {geometric.cdf(3):.4f}")  # 3번 이내 성공 확률

ML 활용: - 사용자가 처음 구매까지 걸리는 방문 횟수 - 토큰 생성에서 특정 토큰까지의 거리

음이항 분포 (Negative Binomial Distribution)

r번째 성공까지의 시행 횟수. 기하 분포의 일반화.

\[P(X=k) = \binom{k-1}{r-1}p^r(1-p)^{k-r}\]

ML 활용: - 과산포(overdispersion) 카운트 데이터 (분산 > 평균) - 포아송보다 유연한 모델

# 음이항: r번 성공까지
r, p = 5, 0.3
nbinom = stats.nbinom(r, p)

# 포아송 vs 음이항 비교
lambda_ = r * (1-p) / p  # 같은 평균
poisson = stats.poisson(lambda_)

print(f"포아송 분산: {lambda_:.2f}")
print(f"음이항 분산: {nbinom.var():.2f}")  # 더 큼

카테고리/다항 분포 (Categorical/Multinomial)

K개 범주 중 하나를 선택.

\[P(X=k) = p_k, \quad \sum_{k=1}^{K}p_k = 1\]

직관: 주사위 던지기 (6개 범주). LLM의 다음 토큰 선택.

# 다항 분포 (여러 번 시행)
probs = [0.2, 0.3, 0.5]  # 3개 범주
n_trials = 100
multinomial = stats.multinomial(n_trials, probs)

# 샘플링: 각 범주의 발생 횟수
sample = multinomial.rvs(1)
print(f"각 범주 발생 횟수: {sample}")

# LLM의 출력 분포가 카테고리 분포
# Softmax 출력 = 어휘의 각 토큰에 대한 확률

연속 확률 분포

균등 분포 (Uniform Distribution)

구간 내 모든 값이 동일한 확률.

\[f(x) = \frac{1}{b-a}, \quad a \leq x \leq b\]
통계량
평균 \(E[X] = (a+b)/2\)
분산 \(Var(X) = (b-a)^2/12\)

직관: 버스가 0~10분 사이에 균등하게 온다면?

a, b = 0, 1
uniform = stats.uniform(loc=a, scale=b-a)

# 난수 생성의 기본
samples = uniform.rvs(1000)

# 파라미터 초기화에 활용
# Xavier 초기화: U(-sqrt(6/(n_in+n_out)), sqrt(6/(n_in+n_out)))

ML 활용: - 신경망 가중치 초기화 - 하이퍼파라미터 랜덤 서치 - 무정보적 사전 분포

정규 분포 (Normal/Gaussian Distribution)

가장 중요한 분포. 중심극한정리에 의해 자연적으로 발생.

\[f(x) = \frac{1}{\sigma\sqrt{2\pi}}e^{-\frac{(x-\mu)^2}{2\sigma^2}}\]
통계량
평균 \(E[X] = \mu\)
분산 \(Var(X) = \sigma^2\)

왜 정규분포가 특별한가? 1. 중심극한정리: 독립적인 것들의 합은 정규분포로 수렴 2. 최대 엔트로피: 평균과 분산만 주어졌을 때, 정규분포가 가장 불확실(가정 최소) 3. 재생성: 정규분포의 합도 정규분포

mu, sigma = 0, 1
normal = stats.norm(mu, sigma)

# PDF 시각화
x = np.linspace(-4, 4, 100)
plt.plot(x, normal.pdf(x))
plt.title('Standard Normal Distribution')

# z-점수 변환: 어떤 정규분포든 표준 정규분포로
data = np.random.normal(10, 2, 1000)
z_scores = (data - data.mean()) / data.std()

# 확률 계산
print(f"P(X < 1.96) = {normal.cdf(1.96):.4f}")  # 0.975
print(f"P(-1.96 < X < 1.96) = {normal.cdf(1.96) - normal.cdf(-1.96):.4f}")  # 0.95

68-95-99.7 법칙: - 68%: \(\mu \pm 1\sigma\) 내 - 95%: \(\mu \pm 2\sigma\) 내 - 99.7%: \(\mu \pm 3\sigma\)

ML 활용: - 가중치 초기화 (Kaiming, Xavier) - 배치 정규화의 목표 분포 - VAE의 잠재 공간 - Gaussian Noise 추가

지수 분포 (Exponential Distribution)

사건 사이의 대기 시간.

\[f(x) = \lambda e^{-\lambda x}, \quad x \geq 0\]
통계량
평균 \(E[X] = 1/\lambda\)
분산 \(Var(X) = 1/\lambda^2\)

핵심 특성: 무기억성 (Memoryless) $\(P(X > s+t | X > s) = P(X > t)\)$

직관: 이미 10분 기다렸어도, 앞으로 기다릴 시간 분포는 처음과 동일.

lambda_ = 0.5  # rate (1/평균)
exponential = stats.expon(scale=1/lambda_)  # scipy는 scale = 1/rate

# 서버 응답 시간 모델링
response_times = exponential.rvs(1000)
print(f"평균 응답 시간: {response_times.mean():.2f}s (이론값: {1/lambda_})")

# P(응답 > 2초)
print(f"P(X > 2) = {1 - exponential.cdf(2):.4f}")

포아송-지수 관계: - 포아송: 단위 시간당 사건 수 - 지수: 사건 간 시간 간격 - rate \(\lambda\)가 동일

감마 분포 (Gamma Distribution)

지수 분포의 일반화. k번째 사건까지의 대기 시간.

\[f(x) = \frac{\beta^\alpha}{\Gamma(\alpha)}x^{\alpha-1}e^{-\beta x}\]
파라미터 의미
\(\alpha\) (shape) 사건 수, 분포 형태 결정
\(\beta\) (rate) 발생률
alpha, beta = 2, 0.5
gamma_dist = stats.gamma(a=alpha, scale=1/beta)

# 다양한 shape에 따른 분포 형태
fig, ax = plt.subplots(figsize=(10, 4))
x = np.linspace(0, 20, 200)
for alpha in [1, 2, 5, 10]:
    gamma = stats.gamma(a=alpha, scale=2)
    ax.plot(x, gamma.pdf(x), label=f'α={alpha}')
ax.legend()
ax.set_title('Gamma Distribution')

ML 활용: - 대기 시간, 수명 모델링 - 베이지안에서 정밀도(precision)의 사전 분포 - 양수 데이터 모델링

베타 분포 (Beta Distribution)

[0, 1] 구간의 확률/비율 모델링.

\[f(x) = \frac{x^{\alpha-1}(1-x)^{\beta-1}}{B(\alpha, \beta)}\]
통계량
평균 \(\frac{\alpha}{\alpha + \beta}\)
분산 \(\frac{\alpha\beta}{(\alpha+\beta)^2(\alpha+\beta+1)}\)

직관: \(\alpha\)번 성공, \(\beta\)번 실패 관측 후 성공률에 대한 믿음.

# 다양한 베타 분포 형태
params = [(0.5, 0.5), (1, 1), (2, 2), (2, 5), (5, 2), (5, 1)]

fig, ax = plt.subplots(figsize=(10, 4))
x = np.linspace(0.01, 0.99, 100)
for alpha, beta in params:
    beta_dist = stats.beta(alpha, beta)
    ax.plot(x, beta_dist.pdf(x), label=f'α={alpha}, β={beta}')
ax.legend()
ax.set_title('Beta Distribution')

# Beta(1, 1) = Uniform(0, 1): 무정보적 사전
# Beta(0.5, 0.5): Jeffreys prior

ML 활용: - 이항 분포의 켤레 사전 (베이지안) - 클릭률, 전환율 추정 - 확률 값 모델링

로그 정규 분포 (Log-Normal Distribution)

로그를 취하면 정규분포.

\[X \sim LogNormal(\mu, \sigma^2) \iff \log(X) \sim N(\mu, \sigma^2)\]

직관: 곱셈적 효과가 누적될 때 발생. (덧셈적 → 정규, 곱셈적 → 로그정규)

mu, sigma = 0, 0.5
lognorm = stats.lognorm(s=sigma, scale=np.exp(mu))

# 양수, 오른쪽 꼬리
samples = lognorm.rvs(1000)
print(f"평균: {samples.mean():.3f}")
print(f"중앙값: {np.median(samples):.3f}")  # 평균 < 중앙값

ML 활용: - 금융 수익률 - 파일/문서 크기 - 소득 분포 - 주택 가격

다변량 정규 분포 (Multivariate Normal)

여러 변수의 결합 분포.

\[f(\mathbf{x}) = \frac{1}{(2\pi)^{d/2}|\Sigma|^{1/2}}\exp\left(-\frac{1}{2}(\mathbf{x}-\boldsymbol{\mu})^T\Sigma^{-1}(\mathbf{x}-\boldsymbol{\mu})\right)\]
# 2변량 정규분포
mean = [0, 0]
cov = [[1, 0.8], [0.8, 1]]  # 강한 양의 상관

mvn = stats.multivariate_normal(mean, cov)
samples = mvn.rvs(1000)

plt.figure(figsize=(6, 6))
plt.scatter(samples[:, 0], samples[:, 1], alpha=0.5)
plt.title('Bivariate Normal (corr=0.8)')
plt.axis('equal')

ML 활용: - 임베딩 공간 모델링 - GMM (Gaussian Mixture Model) - Kalman Filter

Student's t 분포

정규분포보다 두꺼운 꼬리. 소표본이나 이상치에 강건.

\[f(x) = \frac{\Gamma(\frac{\nu+1}{2})}{\sqrt{\nu\pi}\Gamma(\frac{\nu}{2})}\left(1+\frac{x^2}{\nu}\right)^{-\frac{\nu+1}{2}}\]
  • \(\nu\) (자유도)가 클수록 정규분포에 가까워짐
  • \(\nu = 1\): Cauchy 분포 (평균 없음)
  • \(\nu \to \infty\): 정규분포
# 자유도에 따른 변화
x = np.linspace(-5, 5, 200)
plt.figure(figsize=(10, 4))
for df in [1, 3, 10, 30]:
    t_dist = stats.t(df)
    plt.plot(x, t_dist.pdf(x), label=f'df={df}')
plt.plot(x, stats.norm.pdf(x), 'k--', label='Normal')
plt.legend()
plt.title('Student t Distribution')

ML 활용: - 이상치에 강건한 회귀 (t-분포 likelihood) - 소표본 추론 - 불확실성 모델링


분포 선택 가이드

데이터 특성에 따른 선택

데이터 특성 권장 분포 예시
이진 결과 베르누이, 이항 클릭, 구매
카운트 (분산≈평균) 포아송 방문 수, 오류 수
카운트 (분산>평균) 음이항 댓글 수 (과산포)
대기 시간 지수, 감마 응답 시간
비율 (0~1) 베타 클릭률, 전환율
무제한 연속값 정규, t 키, 온도
양수, 오른쪽 꼬리 로그정규, 감마 소득, 가격
K개 범주 카테고리, 다항 감정 분류
이상치 있는 연속값 t 분포 금융 수익률

분포 적합도 검정

from scipy.stats import kstest, shapiro, normaltest, anderson

data = np.random.normal(0, 1, 500)

# Shapiro-Wilk (n <= 5000, 정규성)
stat, p = shapiro(data)
print(f"Shapiro-Wilk: p={p:.4f}")

# D'Agostino-Pearson (대표본, 정규성)
stat, p = normaltest(data)
print(f"D'Agostino: p={p:.4f}")

# Kolmogorov-Smirnov (임의의 분포)
stat, p = kstest(data, 'norm')
print(f"KS test (norm): p={p:.4f}")

# Anderson-Darling (꼬리 민감)
result = anderson(data, dist='norm')
print(f"Anderson-Darling: stat={result.statistic:.4f}")

Q-Q Plot

from scipy import stats

# 데이터
data = np.random.exponential(1, 500)

fig, axes = plt.subplots(1, 2, figsize=(12, 4))

# 정규분포와 비교
stats.probplot(data, dist="norm", plot=axes[0])
axes[0].set_title('Q-Q Plot vs Normal')

# 지수분포와 비교
stats.probplot(data, dist="expon", plot=axes[1])
axes[1].set_title('Q-Q Plot vs Exponential')

# 직선에 가까우면 해당 분포에 적합

ML/DL에서의 활용

손실 함수와 분포

핵심 통찰: 손실 함수는 암묵적으로 분포를 가정함.

손실 함수 가정하는 분포 수식 연결
MSE 정규분포 $-\log N(y
MAE 라플라스 분포 $-\log Laplace(y
Cross-Entropy 베르누이/카테고리 $-\log p(y
Poisson Loss 포아송 분포 $-\log Poisson(y
Huber 정규 + 라플라스 혼합 중간에서는 정규, 꼬리에서는 라플라스
# MSE = Gaussian NLL (상수 제외)
def gaussian_nll(y_true, y_pred, sigma=1):
    """Gaussian Negative Log-Likelihood"""
    return 0.5 * np.mean((y_true - y_pred)**2 / sigma**2)

def mse(y_true, y_pred):
    return np.mean((y_true - y_pred)**2)

# 비례 관계 확인
y_true = np.random.randn(100)
y_pred = y_true + np.random.randn(100) * 0.5

print(f"MSE: {mse(y_true, y_pred):.4f}")
print(f"Gaussian NLL: {gaussian_nll(y_true, y_pred):.4f}")

변분 오토인코더 (VAE)

import torch
import torch.nn.functional as F

# VAE의 잠재 공간은 정규분포 가정
# encoder: q(z|x) = N(μ(x), σ(x))
# prior: p(z) = N(0, I)

def reparameterize(mu, log_var):
    """Reparameterization trick: 샘플링을 미분 가능하게"""
    std = torch.exp(0.5 * log_var)
    eps = torch.randn_like(std)  # N(0, 1)에서 샘플
    return mu + eps * std  # N(mu, std)에서 샘플과 동치

def kl_divergence_gaussian(mu, log_var):
    """KL(q(z|x) || p(z)) where p(z) = N(0, I)

    해석적 계산 가능:
    KL = -0.5 * sum(1 + log(σ²) - μ² - σ²)
    """
    return -0.5 * torch.sum(1 + log_var - mu.pow(2) - log_var.exp())

Dropout의 확률적 해석

# Dropout = 베르누이 마스킹
def dropout(x, p=0.5, training=True):
    """
    p: drop 확률 (비활성화 확률)
    training: 학습 시에만 적용
    """
    if not training or p == 0:
        return x

    # 각 뉴런을 1-p 확률로 유지
    mask = np.random.binomial(1, 1-p, x.shape)

    # Inverted dropout: 학습 시 스케일 조정
    # 추론 시 변경 불필요
    return x * mask / (1 - p)

# MC Dropout: 추론 시에도 dropout → 불확실성 추정

LLM의 출력 분포

def softmax_with_temperature(logits, temperature=1.0):
    """
    Temperature로 카테고리 분포의 날카로움 조절

    T = 0: deterministic (argmax)
    T = 1: 원래 분포
    T > 1: 더 균등 (다양성 증가)
    T < 1: 더 날카로움 (자신감 증가)
    """
    logits = logits / temperature
    exp_logits = np.exp(logits - np.max(logits))  # 수치 안정
    return exp_logits / np.sum(exp_logits)

# 예시: 3개 토큰에 대한 logits
logits = np.array([2.0, 1.0, 0.5])

for T in [0.5, 1.0, 2.0]:
    probs = softmax_with_temperature(logits, T)
    print(f"T={T}: {probs.round(3)}")

분포 변환

Box-Cox 변환

양수 데이터를 정규분포에 가깝게.

\[y^{(\lambda)} = \begin{cases} \frac{y^\lambda - 1}{\lambda} & \lambda \neq 0 \\ \log(y) & \lambda = 0 \end{cases}\]
from scipy.stats import boxcox, skew

data = np.random.exponential(1, 1000) + 0.001  # 양수여야 함

# Box-Cox 변환
data_transformed, lambda_opt = boxcox(data)

print(f"최적 lambda: {lambda_opt:.3f}")
print(f"변환 전 왜도: {skew(data):.3f}")
print(f"변환 후 왜도: {skew(data_transformed):.3f}")

Yeo-Johnson 변환

Box-Cox의 확장. 음수도 처리 가능.

from scipy.stats import yeojohnson

data = np.random.randn(1000)  # 음수 포함 가능
data_transformed, lambda_opt = yeojohnson(data)

분위수 변환

어떤 분포든 균등/정규로 변환.

from sklearn.preprocessing import QuantileTransformer

# 정규분포로 변환
transformer = QuantileTransformer(output_distribution='normal', 
                                   n_quantiles=1000, random_state=42)
data_transformed = transformer.fit_transform(data.reshape(-1, 1))

# 비선형이지만 강력한 정규화

흔한 실수와 오해

1. 정규분포 만능주의

# "데이터가 정규분포일 것이다"라는 가정은 위험

# 실제 데이터:
# - 소득, 가격: 로그정규 (양수, 오른쪽 꼬리)
# - 대기 시간: 지수 분포
# - 카운트: 포아송, 음이항
# - 금융 수익률: fat tail (t-분포 또는 더 두꺼운 꼬리)

# 항상 시각화 + 검정으로 확인

2. 포아송에서 과산포 무시

# 포아송의 핵심: 평균 = 분산
# 실제 카운트 데이터에서는 분산 > 평균인 경우가 많음 (과산포)

data = np.array([0, 0, 0, 1, 2, 0, 15, 0, 0, 3])  # 과산포
print(f"평균: {data.mean():.2f}")
print(f"분산: {data.var():.2f}")

# 과산포 시 → 음이항 분포 사용

3. 연속 분포의 "확률"

# 연속 분포에서 P(X = 특정값) = 0 항상!
# PDF 값은 확률이 아니라 확률 밀도

normal = stats.norm(0, 1)
print(f"pdf(0) = {normal.pdf(0):.4f}")  # 이건 확률이 아님!

# 확률은 구간으로 계산
print(f"P(-0.1 < X < 0.1) = {normal.cdf(0.1) - normal.cdf(-0.1):.4f}")

4. 베르누이와 이항 혼동

# 베르누이: 단일 시행 (0 또는 1)
# 이항: n번 시행의 성공 횟수 (0, 1, ..., n)

# 베르누이(p)의 합 = 이항(n, p)
bernoulli_sum = sum(np.random.binomial(1, 0.3) for _ in range(10))
binomial_sample = np.random.binomial(10, 0.3)
# 두 값은 같은 분포에서 나옴

5. 지수 분포의 rate vs scale

# 혼란의 원인: 교재마다 파라미터화가 다름

# rate 파라미터화: f(x) = λ exp(-λx), E[X] = 1/λ
# scale 파라미터화: f(x) = (1/β) exp(-x/β), E[X] = β

# scipy는 scale 사용
lambda_rate = 2
scale = 1 / lambda_rate

exp_dist = stats.expon(scale=scale)  # scale = 1/rate
print(f"E[X] = {exp_dist.mean():.4f} (이론값: {1/lambda_rate})")

참고 자료