ML/Articles

Why Bootstrap Sampling Is the Badass Tool of Probabilistic Thinking

a292run 2021. 3. 22. 08:22
반응형

원본 링크



Why Bootstrap Sampling Is the Badass Tool of Probabilistic Thinking

실제 바로 어떻게 사용할 수 있을까?


Photo by Pixabay from Pexels



Introduction

전자기기를 사용하여 여러분의 방에서 50회 소리의 속도를 특정하기 위해 최선을 다해 다음의 결과를 얻었다.


처음 10번의 측정이후 기기의 기술적 실패가 있어 전혀 측정되지 않았다. 이제 무엇을 할까?

여러분은 가능한 데이터에 대한 평균을 구할 수 있지만 여러분은 얼마나 자신할 수 있는가? 기기가 올바르게 동작했다면 여러분이 계산한 평균은 약간 다른 숫자일 수 있다.

사실, 50회 속도를 측정하는 것에 대한 전반적인 이유는 여러분이 찾은 것에 자신이 있고 여러분의 방이 아닌 어떤 환경에서 소리의 속도에 대한 유효한 추론을 만들 수 있는 것이다.

더 많은 측정을 기록하기 위해 모든 단계를 거치고 새로운 기기를 설정해야만 하는가? 적어도 여러분이 파이선과 Bootstrapping이란 것을 안다면 아니다!

부트스트래핑(bootstrapping)은 데이터의 분포를 시뮬레이션 할 수 있게 한다. 즉 추가적인 데이터 수집을 피할 수 있다. 그리고 단지 서브셋(subset)을 사용하여 훨씬 더 큰 그룹에 대한 꽤 건전한 추정을 할 수 있다.

부트스트래핑을 사용하여 작은 샘플에서 전체 그룹에 대한 예측을 만드는 개념은 많은 시간동안 사람들을 따르게 했다. 실제로 사용되면 약간 속임수같아 보일 수 있다. 하지만 사실 아주 조금의 수학과 아주 많은 컴퓨팅 파ㅝ의 조합 그이상은 아니다.

부트스트래핑이 아주 간단한 메소드임에도 불구하고 이것의 응용은 이 글의 범위를 훨씬 넘어선다. Random forestsStocastic Gradient boosting 같은 많은 앞서가는 머신러닝 알고리즘 역시 이를 사용한다.

이 글에서는 왜 이 메소드가 동작하고 모집단(populations)에 대한 예측, 신뢰구간(confidence intervals)을 계산하고 가설검정(hypothesis test)를 수행하는 데 도움이 되는 데이터 분포를 시뮬레이션 하기 위해 파이썬을 사용하여 이를 구현하는 방법을 알아본다.



Terminology Alert

이 글에서 모집단(population), 샘플(sample), 통계(statistic), 파라미터(parameter) 단어를 자주 사용한다. 따라서 이 단어를 먼저 이해하는 것이 중요하다.

  • 모집단(population)과 샘플(sample) : 통계학에서

    모집단(population)은 흥미가 있는 전체 그룹을 나타내고

    샘플(sample)은 모집단에 대한 작고 무작위적인 하위그룹(subset)을 나타낸다.

    예를 들면, 모든 Towards Data Science의 팔로워들은 전체 모집단이 될 수 있고, 이 글의 읽는 누군가가 그 모딪단에 대한 아주작은 샘플이 될 수 있다. 위 소리의 속도 예제에서는 방에서 10개의 소리 속도 측정이 샘플이고 어떠한 환경에서 소리의 속도 측정이 모집단이다.
  • 파라미터(paramter)와 통계(statistic) : 모집단에 대한 특정 지표(metric) 예를 들면 평균 또는 표준편차를 계산한다면 이를 모집단의 파라미터(parameter)라 한다. 만약 샘플에 대해 이를 시행하면 샘플 통계(statistic)이라 부른다.

예를 들면, 여러분에게 10,000명의 이메일 구독자가 있다고 하자. 여러분은 모든 구독자에게 선호하는 프로그램 언어를 뭍는 이메일을 보낸다. 단지 2,500명의 구독자만이 회신하였고 그중 70%가 파이썬을 선호한다. 그러면,

  • 모집단(population) : 10,000명의 이메일 구독자
  • 샘플(sample) : 회신을 한 2,500명의 구독자
  • 통계(statistic) : 파이썬을 선호하는 70%
  • 파리마터(Parameter) : 모름(unknown). 샘플만을 사용하여 파이썬을 선호하는 전체 구독자의 비율을 계산해야 한다.



부스스트래핑 이론(Bootstrapping Theory)

실제 부트스트래핑을 보여주기 위해 19개의 음속(speed of sound) 측정으로 시작한다. 우선 데이터포인트를 그린다.


우리의 목표는 단순히 방안에서가 아닌 어떤 개방된 환경에서 올바르게 음속을 추정하는 것이다. 음속에서 이상치를 볼 가능성이 없기 때문에 평균은 속도를 나타내는 좋은 지표가 될 수 있다. 따라서 도표에 평균을 표시한다.


평균은 344.39 m/s(meters per second)이다. 이제 샘플을 부트스트랩한다.

  1. 우선, 중복을 허용하면서 속도에서 무작위로 10개의 측정치를 선택한다.
  2. 무작위 샘플에 대한 평균을 계산하고 이를 배열에 저장한다.
  3. 1번과 2번을 아주 많이 반복한다. 최적으로 1,000 또는 10,000회

그러면 가능한 데이터에서 무작위로 10개 포인트를 선택하자. 원하는 만큼 많이 데이터 포인트를 선택할 수 있다.


(이후에 알아보겠지만) 무작위로 포인트를 선택하기 위해 파이썬을 사용하였다. 보이는 것처럼 일부 중복되기 때문에 겹쳐서 그려지는 3개의 포인트가 있다는 의미인 7개의 포인트만이 보인다. 그리고 평균을 측정하고 도표로 그린다. 이번에는 344.396이다.

여기서는 원래 10개의 측정치에서 10개 무작위 포인트를 샘플링하였다.


>>> list(temp)

[345.59, 345.07, 340.4, 349.47, 340.92, 349.47, 340.26, 349.47, 342.91, 340.4]

위의 것은 단일 부트스트랩 샘플(single bootstrap sample)이라고 한다. 344.396인 이것의 평균은 부트스트랩 복제(replicate) 또는 지표(metric)이라고 한다.

다음으로 앞의 두 단계를 수천번 반복해야 한다. 예를 들면, 다음은 1,000번 부트스트랩 샘플을 가져와 단일 그래프에 각각의 평균을 그린것이다.


위 그래프는 원래의 10개 소리 측정치에서 샘플링된 1,000개 부트스트랩 복제(평균)의 그래프이다. 겹쳐지는 선이 실제 추정된 평균 근처에 더 밀집된 배경을 만들도록 더 낮은 투명도로 선을 그렸다. 샘플 평균은 약 344 m/s이다. 이는 어떤 환경에서 일반적인 평균음속으로 자신있게 사용할 수 있다.

우리가 방금 한 것을 교체를 사용하는 부트스트랩 샘플링(bootstrap sampling with replacement)이라고 한다. 교체를 사용하는(with replacement)은 기본 샘플로부터 무작위 데이터를 선택하고 모든 데이터포인트가 다시한번 선택되어지는 것에 동일한 확률을 갖도록 이를 다시 되돌려 놓는 것을 의미한다.

또한 각 부트스트랩 샘플이 기본샘플과 같은 길이를 갖는 것이 중요하다. 우리 예제의 경우, 각 부트스트랩 샘플은 우리가 시작하기 위해 단지 10개의 속도 측정치만을 가지고 있기 때문에 10의 길이를 갖는다.

이제, 질문은 "왜 부트스트랩 샘플링으로 계산된 평균이 실제 모집단 파라마터에 가깝다고 추정해야 하는가?"이다.



Why Bootstrapping Works(부트스트래핑이 동작하는 이유)

확률에 대수의 법칙(Law of Large Numbers)이라는 이론이 있다. 이 이론은 만약 동일한 실험이 큰 횟수로 수행되었다면 그 실험의 평균 결과는 실험의 수가 증가하는 만큼 실제 추정에 더 가까워지는 경향이 있다는 것을 말한다.

예를 들면, 균일한 주사위 던지기는 1, 2, 3, 4, 5, 6인 6가지 결과중 하나가 나올 것이다. 여러분이 기대하는 점수는 이들 6가지 숫자의 평균인 3.5일 것이다. 실제로 몇번 주사위를 던졌다면 평균 3.5인 점수를 얻을 수 없을 것이다. 그러나 대수의 법칙 때문에 주사위를 던지는 횟수가 증가하는 만큼 평균 점수도 3.5에 더욱더 가까워진다.

잠시 이에 대해 생각하면 실제로 타당하다. 좀 더 실제 예제를 들어보면 여러분이 2번 SAT시험을 봤다ㅕㄴ 아마도 매두 다른 2개의 결과를 얻었을 것이다. 그러나 계속 시험을 치루면 시험에서 여러분의 실제 기술 측정치에 더욱더 가까워질 것이다.

부트스트래핑은 내부적으로 이와 동일한 아이디어를 사용한다. 좋은 샘플이 주어지면(무엇이 좋은 샘플인지는 이후에 설명한다.) 이 샘플로부터 무작위 부트스트랩 샘플을 추하는 것은 실제 모집단 파라미터에 매우 근접하는 결과를 보일 것이다.



How Effective Is Bootstrapping(부트스트래핑의 효과)

멸확한 질문은 메서드가 얼마나 효과적인가이다. 그 답은 대부분 부트스트래핑의 결과가 올바르고 사용가능하다는 것을 기대할 수 있다. 그러나 모집단 파라미터에 대한 추론을 유도하는 것의 정확도는 여러분이 수집한 샘플에 따라 달라진다.

부트스트래핑이 올바르게 동작하기 위해 여러분이 사용하는 샘플은 흥미를 갖는 모집단을 정확하게 표현해야 한다. 예를 들면, 만약 여러분이 사람의 키에 대한 정보를 수집하여 지구 전체 사람의 평균키를 계산하려 한다면 여러분은 올바른 샘플을 선택해야만 한다.

또한 수집된 데이터는 무작위여야하고 모집단내 폭넓은 그룹의 데이터포인트들을 포함해야 한다. 운동선수 같은 특정그룹만을 선택하는 것은 운동선수가 더 큰 경향이 있기 때문에 충분히 좋은 샘플이 아닐 수 있다.

충분히 좋은 샘플로 작업하고 있다고 확신한다면 부트스트래핑으로 얻은 결과가 믿을 수 있다는 것을 거의 항상 확신할 수 있다. 그렇지 않다면 여러분은 더 많은 믿을을 갖고 주어진 데이터가 대부분 부차적(다른 사람이 수집한)이기 때문에 어찌되었건 부트스트래핑을 사용해야만 한다.

추가적인 이점으로 부트스트래핑은 모집단의 다른 파라미터의 전체 범위를 측정하기 위해 사용될 수 있다. 좋은 예로는 중간값(median), 최빈값(mode), 표준편차(standard deviation), 분산(variance), 상관(correlations) 등이 있다.



샘플링 분포(sampling distribution)란

'부트스트래핑(bootstrapping)'과 '샘플링 분포(sampling distribution)' 용어는 종종 함께 나오고 실제로 샘플링 분포는 부트스트래핑을 사용하여 얻어진다. 앞의 음속 측정치를 사용하여 이를 명확히 알아보자.

원래 10개의 측정치로 1000개의 부트스트랩 샘플을 취해 각 부트스트랩 샘플의 평균을 도표로 그린것을 기억하자. 이번에는 도표로 그리는것 대신 파이썬 배열에 모든 평균을 저장한다.


>>> means[:20]

array([344.38, 345.02, 345.05, 343.44, 342.81, 345.12, 346.42, 344.56,
       344.82, 344.46, 344.76, 344.08, 344.58, 344.65, 343.74, 344.25,
       345.05, 344.15, 343.38, 345.32])

변수 means에 저장된 1000개중 처음 20개의 평균을 표시하였다. means샘플 평균에 대한 샘플링 분포(sampling distribution of the sample mean)라고 한다. 대신에 각 부트스트랩 샘플에 대한 표준편차를 계산한다.


>>> stds[:20]

array([3.38, 3.19, 2.14, 3.2 , 2.97, 2.76, 2.9 , 2.75, 3.17, 3.01, 3.48,
       3.77, 2.4 , 3.2 , 3.6 , 2.89, 2.64, 1.95, 3.09, 2.48])

이를 '샘플 표준편차에 대한 샘플링 분포(a sampling distribution of the sample standard deviation)'라고 한다.

이 분포는 도표로 그리지 않는한 거의 쓸모가 없다. 샘플링 분포에 대한 일반적인 도표는 히스토그램이다.


샘플링 분포가 거의 정규분포를 따른다는 것을 볼 수 있다. 사실, 올바른 부트스트래핑 조건으로 얻어진 모든 단일 샘플링 분포는 부트스트랩 샘플의 수가 증가함에 따라 거의 완벽한 정규분포를 따른다.



Bootstrapping in Python

파이썬에서 부트스트래핑은 보통 데이터프레임으로 시작한다. 기술 자체는 numpy를 확장하여 사용한다. 따라서 pandas와 두가지 도표를 그리는 라이브러리 함께 import한다.


import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
import seaborn as sns

sns.set_context('talk')
plt.style.use('ggplot')

여기서는 seaborn 내장 다이어몬드 데이터셋을 로드하고 전체 다이어몬드 모집단에 대한 평균 가격을 예측하기 위해 부트스트래핑을 사용한다.


diamonds = sns.load_dataset('diamonds')

>>> diamonds.head()




>>> diamonds.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 53940 entries, 0 to 53939
Data columns (total 10 columns):
 #   Column   Non-Null Count  Dtype   
---  ------   --------------  -----   
 0   carat    53940 non-null  float64 
 1   cut      53940 non-null  category
 2   color    53940 non-null  category
 3   clarity  53940 non-null  category
 4   depth    53940 non-null  float64 
 5   table    53940 non-null  float64 
 6   price    53940 non-null  int64   
 7   x        53940 non-null  float64 
 8   y        53940 non-null  float64 
 9   z        53940 non-null  float64 
dtypes: category(3), float64(6), int64(1)
memory usage: 3.0 MB

다음은 검정색 선으로 평균이 표시된 다이어몬드 가격에 대한 Kernel Density Estimate plot이다.



# Create fig, ax objects
fig, ax = plt.subplots(figsize=(12, 8))

# Plot the kde
sns.kdeplot(diamonds['price'], bw_adjust=1)

ax.set(title='KDE plot of 54k Diamond Prices', 
       xlabel='Price ($)', 
       ylabel='Density')

# Annotate the mean
ax.axvline(x=diamonds['price'].mean(), color='black', label='Mean Price')
# Show the legend
ax.legend()

plt.show()


평균 가격은 3932.8달러이다.

이제, 다이어몬드 모집단에 대한 가능한 평균의 전체 범위를 얻기 위해 10,000번 다이어몬드 가격을 부트스트랩한다.


# Create an empty numpy array of size 10k
means = np.empty(10000)

# Initialize the bootstrap
for i in range(10000):
    # Take random draws from the underlying data
    bs_sample = np.random.choice(diamonds['price'], size=len(diamonds['price']))
    # Append its mean to means
    means[i] = bs_sample.mean()

우선, 평균을 저장하기 위한 넘파이 배열을 생성하고 for 루프로 10,000번 반복하여 초기화한다. 각 반복에서는

  • np.random.choice를 사용하여 부트스트랩 샘플을 취한다.
  • size 인자에 대한 기본 분포(다이어몬드 가격)의 길이(length)를 사용한다.
  • 각 샘플을 사용하여 평균을 계산하고 이를 means에 저장한다.

np.random.choice는 어떤 일련의 값을 받아 기본적으로 교체를 사용하여(with replacement) 무작위로 데이터포인트를 선택한다.

샘플 평균의 샘플링 분포를 도표로 그리기 위해 seaborn의 kdeplot 또는 Matplotlib의 hist를 사용할 수 있다. 여기서는 적은 노이즈와 binning bias를 피하기 때문에 KDE를 사용한다. (선택은 여러분의 몫으로 저자의 선호도일 뿐이다.)

Binning bias는 히스토그램 bin의 수를 변경하여 동일한 데이터의 다른 표현을 얻을 수 있는 히스토그램의 위험이다.


# Create fig, ax objects
fig, ax = plt.subplots(figsize=(12, 8))

# Create a kdeplot
sns.kdeplot(means, ax=ax, bw_adjust=1.5)
# Labeling
ax.set(title='KDE Plot of the Sampling Distribution of the Sample Mean',
       xlabel='Mean Price ($)')

plt.show();


예상처럼 정규분포에 꽤 근접한 것을 볼 수 있다. 이제, 질문은 "실제로 어떻게 모집단 파리미터에 대한 추정을 하는가?"이다. 이는 신뢰구간(confidence intervals)을 사용하여 대답할 수 있다.



신뢰구간(Confidence Intervals)

보통 여러분이 [m, n)의 구간을 가진 몇몇 지표의 k%/i> 신뢰구간을 나타내는 누군가를 본다면 이는 그들은 모집단에 대한 지표가 m과 n사이 어딘가에 있는 것을 k% 확신한다는 의미이다.

통계적으로 생각할 때 여러분은 단지 소규모 샘플을 사용하여 여러분의 추정을 계산하였기 때문에 모집단 파라미터가 특정 숫자와 같다고 절대적인 확신으로 결코 말할 수 없다는 것을 안다. 따라서 통계학자들은 파라미터의 구간을 계산하고 파라미터가 어떤 구간에 있다는 것을 나타낸다.

신뢰구간의 일반적인 백분율은 95% 또는 99%이다. 구간 자체는 기본적으로 백분위를 사용하여 계산된다. 예를 들면, 샘플링 분포의 95% 신뢰구간을 계산하려면 여러분은 하위 제한으로 2.5번째 백분위를 상위제한으로 97.5번째 백분위를 취한다.

또한 0번째와 95번째 또는 5번째와 100번째 백분위가 아닌 '중간(middel)' 구간을 사용한다는 것을 기억하자. 실제 파라미터는 항상 더 중심 구간에 위치한다.

파이썬으로 이를 구현하려면 파라미터로 샘플링 분포와 백분위를 취하는 넘파이의 np.percentile 함수를 사용한다.


# Calculate the 99% CI of diamond prices
>>> np.percentile(means, [0.5, 99.5]).round(2)

array([3889.45, 3977.59])

이번에는 [3889.45, 3977.59)를 얻었다.

반응형