Skip to main content

난수와 샘플링

데이터 분석과 ML에서 난수(Random Number)는 시뮬레이션, 데이터 셔플링, 학습/검증 데이터 분할, 가중치 초기화 등 다양한 곳에 사용됩니다. NumPy는 numpy.random 모듈을 통해 다양한 확률분포에서 난수를 생성하는 기능을 제공합니다.

학습 목표

  • NumPy의 새로운 Random Generator API를 사용할 수 있다
  • 주요 확률분포(균일, 정규, 이항 등)에서 샘플링할 수 있다
  • seed를 설정하여 실험의 재현성을 확보할 수 있다
  • 셔플링과 무작위 선택을 수행할 수 있다

왜 중요한가

ML 실험에서 재현성(Reproducibility)은 핵심 요구사항입니다. 동일한 seed를 사용하면 같은 난수 시퀀스가 생성되어 실험을 정확히 재현할 수 있습니다. 또한 부트스트랩 샘플링, 교차 검증, 데이터 증강 등 ML의 핵심 기법이 모두 난수 생성에 의존합니다.

Generator API (권장)

NumPy 1.17+부터 권장되는 새로운 난수 생성 API입니다.
import numpy as np

# Generator 생성 (권장 방식)
rng = np.random.default_rng(seed=42)

# 균일분포 난수 [0, 1)
print(rng.random(5))
# [0.773... 0.438... 0.858... 0.697... 0.094...]

# 정수 난수
print(rng.integers(1, 10, size=5))    # 1~9 사이 정수 5개

# 정규분포 난수
print(rng.normal(loc=0, scale=1, size=5))  # 평균 0, 표준편차 1
레거시 API(np.random.seed(), np.random.randn() 등)도 여전히 동작하지만, 새로운 Generator API가 성능과 유연성 면에서 더 우수합니다. 새 코드에서는 default_rng()를 사용하세요.

주요 확률분포 샘플링

균일분포

rng = np.random.default_rng(42)

# 연속 균일분포 [low, high)
uniform = rng.uniform(low=0, high=10, size=(3, 3))
print(uniform)

# 정수 균일분포
integers = rng.integers(low=1, high=7, size=10)  # 주사위 시뮬레이션
print(integers)

정규분포

rng = np.random.default_rng(42)

# 표준 정규분포 (평균=0, 표준편차=1)
standard_normal = rng.standard_normal(size=1000)

# 일반 정규분포
heights = rng.normal(loc=170, scale=10, size=100)  # 평균 170cm, 표준편차 10cm
print(f"평균: {heights.mean():.1f}, 표준편차: {heights.std():.1f}")

기타 분포

rng = np.random.default_rng(42)

# 이항분포 — 동전 던지기, A/B 테스트
coin_flips = rng.binomial(n=10, p=0.5, size=100)  # 10번 중 앞면 수

# 포아송분포 — 단위 시간당 이벤트 수
events = rng.poisson(lam=5, size=100)  # 평균 5건의 이벤트

# 지수분포 — 이벤트 간 시간 간격
wait_times = rng.exponential(scale=2.0, size=100)  # 평균 대기시간 2분

# 베타분포 — 확률의 확률 (베이즈 추론)
probabilities = rng.beta(a=2, b=5, size=100)
분포함수주요 파라미터ML 활용
균일분포rng.uniform()low, high가중치 초기화
정규분포rng.normal()loc, scale노이즈 추가, 데이터 생성
이항분포rng.binomial()n, pA/B 테스트, 분류 시뮬레이션
포아송분포rng.poisson()lam이벤트 카운트 모델링
지수분포rng.exponential()scale생존 분석, 대기 시간

셔플링과 선택

rng = np.random.default_rng(42)

arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

# 셔플 (in-place 수정)
rng.shuffle(arr)
print(arr)  # [3 7 1 ...]

# 비복원 추출 (복원 없이 선택)
sample = rng.choice(arr, size=5, replace=False)
print(sample)

# 복원 추출
sample_with_replacement = rng.choice(arr, size=5, replace=True)

# 가중치 기반 선택
items = ['A', 'B', 'C']
weights = [0.7, 0.2, 0.1]  # A가 70% 확률
selected = rng.choice(items, size=10, p=weights)
print(selected)

학습/검증 데이터 분할

rng = np.random.default_rng(42)

# 데이터 100개
X = np.random.randn(100, 5)   # 100개 샘플, 5개 피처
y = np.random.randint(0, 2, 100)  # 이진 레이블

# 인덱스 셔플
indices = np.arange(len(X))
rng.shuffle(indices)

# 80:20 분할
split = int(0.8 * len(X))
train_idx, test_idx = indices[:split], indices[split:]

X_train, X_test = X[train_idx], X[test_idx]
y_train, y_test = y[train_idx], y[test_idx]

print(f"학습: {len(X_train)}, 검증: {len(X_test)}")
# 학습: 80, 검증: 20

재현성 확보

# 동일한 seed → 동일한 결과
rng1 = np.random.default_rng(42)
rng2 = np.random.default_rng(42)

print(rng1.random(3))  # [0.773... 0.438... 0.858...]
print(rng2.random(3))  # [0.773... 0.438... 0.858...] — 동일!

# 독립적인 시퀀스가 필요한 경우 (멀티스레드 등)
seed_seq = np.random.SeedSequence(42)
child_seeds = seed_seq.spawn(3)
rngs = [np.random.default_rng(s) for s in child_seeds]
# 각 rng는 독립적이면서 재현 가능

AI/ML에서의 활용

  • 데이터 분할: 학습/검증/테스트셋을 무작위로 나눌 때 seed를 고정합니다
  • 가중치 초기화: 딥러닝 모델의 초기 가중치를 정규분포에서 샘플링합니다
  • 부트스트랩: 복원 추출로 여러 샘플을 만들어 통계적 추정치의 신뢰구간을 계산합니다
  • 데이터 증강: 정규분포 노이즈를 추가하여 학습 데이터를 증강합니다
  • 몬테카를로 시뮬레이션: 난수 샘플링으로 복잡한 시스템의 결과를 추정합니다
np.random.seed()는 전역 상태를 변경하므로 다른 라이브러리에 영향을 줄 수 있습니다. default_rng()는 독립적인 Generator 객체를 생성하므로 상태 충돌 없이 안전합니다. 또한 Generator API는 PCG64 알고리즘을 사용하여 더 높은 품질의 난수를 생성합니다.
실험 설정 파일에 seed 값을 기록하고, NumPy, PyTorch, random 모듈의 seed를 모두 고정하세요. 실험 로그에 seed를 남기면 나중에 결과를 정확히 재현할 수 있습니다.

체크리스트

  • default_rng()로 Generator를 생성하고 다양한 난수를 만들 수 있다
  • 정규분포, 균일분포, 이항분포에서 샘플링할 수 있다
  • seed를 고정하여 실험의 재현성을 확보할 수 있다
  • rng.choice()로 비복원/복원 추출을 수행할 수 있다
  • 학습/검증 데이터를 무작위로 분할할 수 있다

다음 문서