모든 몬테카를로 시뮬레이션의 결과는 데이터 $x_i$가 가우스 분포를 따를 때 다음과 같은 평균입니다.
$\mu=\frac{1}{N}\Sigma x_i$
따라서 이 평균의 오차는 다음과 같이 추정할 수 있는데요,
$\delta_{\mu}=\frac{\sigma}{\sqrt{N}}=\sqrt{\frac{1}{N}(\frac{1}{N}\Sigma x_i^2 - \mu^2)}$
하지만 데이터가 정규 분포를 따른다는 가정이 꼭 필요할까요? 그리고 모집단으로부터 추출된 한정된 샘플들만을 가지고 정확한 모수의 분포를 추정할 수 있을까요? 이때 가장 범용적으로 사용될 수 있는 방법은 부트스트랩 알고리즘입니다. (bootstrap)
부트스트랩 알고리즘의 개요
부트스트랩 알고리즘을 소개하기 전에 어원에 대한 재미있는 글이 있어 먼저 소개합니다.
Bootstrap을 이해하기 전에, 장화의 손잡이 부분을 의미하는 bootstrap이라는 단어가 왜 사용되었는지 그 기원이 재미있다. The Adventures of Baron Munchausen(바론의 대모험)이라는 책을 보면 주인공 바론이 늪에 빠지게 되는데 이때 자신의 장화 끝 단(bootstrap)을 잡아 올라 스스로 늪에서 빠져나오는 장면이 나온다. 사실 작용 반작용의 법칙에 따라 불가능한 일이지만 논리는 차치하고, 이 일화처럼 스스로를 구해낸다는 뜻으로 bootstrap이라는 단어가 사용되었다. 실제로 bootstrap sampling이 보이는 결과를 보면 의미가 꽤나 잘 맞는다는 것을 알 수 있다. (https://modern-manual.tistory.com/31)
위 그림에서처럼 부트스트랩 알고리즘은 현재 가지고 있는 샘플에서 1) 여러 번의 복원 추출을 시행하고 (bootstrap sample), 2) 복원 추출해서 얻은 샘플들의 평균을 추정하여 (bootstrap statistics), 3) 모집단의 분포를 추정하는 (bootstrap distribution) 방식으로 데이터 $x_i$가 가우스 분포를 따른다고 가정하지 않고 복원 추출로부터 리샘플링된 샘플의 평균 $\mu=(1/N)\Sigma x_i$ 으로부터 모수의 분포를 (평균, 오차) 추정하겠다는 것입니다.
부트스트랩 알고리즘 프로세스
부트스트랩 알고리즘의 첫 단계는 원래 가지고 있는 샘플로부터 리샘플링 (복원 추출)해서 얻은 데이터 샘플의 평균을 계산하는 것입니다. 유한한 데이터로부터 (보통 100개 이상의 표본이 있다고 가정합니다) 여러 번의 복원 추출을 시행하게 되니 추출된 샘플들의 평균은 조금씩 다른 결과가 나오게 됩니다.
두 번째 단계는 부트스트랩 샘플들의 평균들을 정렬하고 주어진 신뢰수준에 따른 추정 범위를 계산하는 것입니다. 즉, 부트스트랩 평균들로부터 주어진 백분율만큼 (신뢰수준) 평균으로부터 양쪽으로 떨어진 신뢰구간의 경계값, $\mu^{-}, \mu^{+}$를 계산하는 것이죠.
부트스트랩 알고리즘 구현
파이썬의 random 패키지를 이용한 구현은 다음과 같습니다. 복원 추출하는 부트스트랩 샘플링의 과정은 'resample' 함수에서 random.choice 함수로 구현하였고 random.choice 함수의 replace 인자는 기본값이 True로 설정되어 있기 때문에 이미 뽑은 값들도 중복으로 추출합니다. 'bootstrap' 함수를 보면 'nsamples' 인자를 통해 몇 번 복원 추출할지 결정하며, 복원 추출한 샘플들에 대해 평균을 구한 이후 이를 정렬하고 주어진 신뢰수준 (confidence 인자)에 따른 신뢰구간 $\mu^{-}, \mu^{+}$를 계산하게 됩니다.
import random
def resample(S, size=None):
return [random.choice(S) for i in range(size or len(S))]
def bootstrap(x, confidence=0.68, nsamples=100):
"""Computes the bootstrap errors of the input list."""
def mean(S):
return float(sum(x for x in S)) / len(S)
means = [mean(resample(x)) for k in range(nsamples)]
means.sort()
left_tail = int(((1.0 - confidence) / 2) * nsamples)
right_tail = nsamples - 1 - left_tail
return means[left_tail], mean(x), means[right_tail]
가우스 분포를 따르는 100개의 표본을 대상으로 위 코드를 실행하면 다음과 같은 결과를 얻을 수 있습니다. 'bootstrap' 함수의 출력은 $(\mu^{-}, \mu, \mu^{+})$ 가 됩니다.
S = [random.gauss(2, 1) for k in range(100)]
bootstrap(S)
>>> (1.8771349005459432, 1.9863078494609356, 2.0668997082817278)
S의 데이터는 평균이 2이고 표준편차가 1이기 때문에 $\mu$는 2에 가까울 것으로 기대할 수 있고 실제 결과로도 1.98의 결과를 얻었습니다. 따라서 68%의 확률 (신뢰수준) 으로 이 수들의 참 평균은 1.877과 2.066 사이에 있음을 알 수 있습니다. 또한, 불확실성 $(\mu^{+}- \mu^{+})/2 = 0.09485$ 는 실제 표준 오차인 $\sigma/\sqrt{100}=0.1$과 비슷한 것을 볼 수 있습니다. 따라서 가지고 있는 표본으로부터 bootstrapping을 통한 모집단 분포 추정이 실제 모집단과 굉장히 근사함을 알 수 있고 부트스트랩 반복 횟수 (boostrap 함수의 nsamples 인자)를 늘린다면 더 정확한 신뢰구간 추정이 가능하겠죠.
'Theory > Algorithms' 카테고리의 다른 글
몬테카를로 시뮬레이션 (5) - 적분 계산하기 (0) | 2022.03.20 |
---|---|
몬테카를로 시뮬레이션 (4) - 범용 몬테카를로 엔진 (0) | 2022.03.09 |
몬테카를로 시뮬레이션 (2) - 온라인 판매자 예제 (0) | 2022.02.26 |
몬테카를로 시뮬레이션 (1) - 파이 계산하기 (0) | 2022.02.25 |
Cholesky Decomposition (촐레스키 분해) - 파이썬 구현 (0) | 2022.02.20 |