난수와 샘플링
데이터 분석과 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, p A/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()의 차이는?
np.random.seed()는 전역 상태를 변경하므로 다른 라이브러리에 영향을 줄 수 있습니다. default_rng()는 독립적인 Generator 객체를 생성하므로 상태 충돌 없이 안전합니다. 또한 Generator API는 PCG64 알고리즘을 사용하여 더 높은 품질의 난수를 생성합니다.
실험 설정 파일에 seed 값을 기록하고, NumPy, PyTorch, random 모듈의 seed를 모두 고정하세요. 실험 로그에 seed를 남기면 나중에 결과를 정확히 재현할 수 있습니다.
체크리스트
다음 문서