태그 보관물: 0.19

QuantileTransformer

Scikit-Learn 0.19 버전에 추가된 기능 중 오늘은 QuantileTransformer() 변환기를 살펴 보겠습니다. 먼저 ‘파이썬 라이브러리를 활용한 머신러닝‘ 3장에 있는 스케일 조정의 예제와 비슷한 데이터 셋을 make_blobs 함수로 만들겠습니다. 여기서는 샘플 개수가 어느 정도 되어야 눈으로 확인하기 좋으므로 500개를 만들겠습니다.

X, y = make_blobs(n_samples=500, centers=2, random_state=4)
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolors='black')

quantile-1

QuantileTransformer 는 지정된 분위수에 맞게 데이터를 변환합니다. 기본 분위수는 1,000개이며 n_quantiles 매개변수에서 변경할 수 있습니다. 여기서는 100개 정도로 지정해 보겠습니다.

quan = QuantileTransformer(n_quantiles=100)

fit() 메서드에서 입력 데이터의 범위를 분위수에 맞게 나누어 quantiles_ 속성에 저장합니다. 이를 위해 넘파이 percentile() 함수를 사용하여 분위에 해당하는 값을 손쉽게 구할 수 있습니다. QuantileTransformer 에는 기본값이 100,000인 subsample 매개변수가 있습니다.  만약 입력 데이터 개수가 subsample 보다 크면 계산량을 줄이기 위해 subsample 개수만큼 데이터를 샘플링하여 percentile() 함수를 적용합니다. percentile() 함수는 특성마다 각각 적용되므로 quantiles_ 속성의 크기는 [n_quantiles, X.shape[1]] 이 됩니다.

quan.fit(X)
print(quan.quantiles_.shape)
(100, 2)

transform() 메서드에서는 데이터를 quantiles_를 기준으로 하여 0~1 사이로 매핑합니다. 이를 위해 넘파이 interp() 함수를 사용합니다. 두 개의 특성이 모두 0~1 사이로 균등하게 나뉘어진 것을 그래프로 확인할 수 있습니다.

X_quan = quan.transform(X)
plt.scatter(X_quan[:, 0], X_quan[:, 1], c=y, edgecolors='black')

quantile-2

이런 변환은 RobustScaler와 비슷하게 이상치에 민감하지 않게 됩니다. 하지만 균등 분포라서 무조건 [0, 1] 사이로 클리핑합니다.

사실 transform() 메서드에서는 scipy.stats.uniform.ppf() 함수를 사용하여 균등 분포로 변환합니다. 하지만 interp() 함수에서 동일한 변환을 이미 하고 있기 때문에 효과가 없습니다. QuantileTransformer 에서 output_distribution=’normal’ 로 지정하면 scipy.stats.norm.ppf() 함수를 사용하여 정규 분포로 변환합니다.

quan = QuantileTransformer(output_distribution='normal', n_quantiles=100)
X_quan = quan.fit_transform(X)
plt.scatter(X_quan[:, 0], X_quan[:, 1], c=y, edgecolors='black')

quantile-3

변환된 데이터는 평균이 0, 표준편차가 1인 정규 분포임을 확인할 수 있습니다.

X_quan.mean(axis=0), X_quan.std(axis=0)
(array([-0.00172502, -0.00134149]), array([ 1.0412595 ,  1.03818794]))

StandardScaler 와 비교해 보면 평균 과 표준편차는 같지만 정규 분포를 따르지 않는 분포에서 차이를 확인할 수 있습니다. 🙂

from sklearn.preprocessing import StandardScaler
X_std = StandardScaler().fit_transform(X)
plt.scatter(X_std[:, 0], X_std[:, 1], c=y, edgecolors='black')

quantile-4

이 글의 샘플 코드는 ‘파이썬 라이브러리를 활용한 머신러닝‘ 깃허브(https://github.com/rickiepark/introduction_to_ml_with_python/blob/master/QuantileTransformer.ipynb)에서 확인할 수 있습니다.

New SAGA solver

scikit-learn 0.19 버전에서 추가된 기능 중 SAGA 알고리즘에 대해 살펴 보겠습니다. SAGA는 SAGStochastic Average Gradient 알고리즘의 변종, 혹은 개선 버전입니다(SAGA의 A가 특별한 의미가 있는 것은 아닙니다). SAG 알고리즘은 이전 타임스텝에서 계산된 그래디언트를 모두 평균내어 적용하는 알고리즘입니다. 기본 공식은 일반적인 SGDStochastic Gradient Descent과 비슷합니다. 다만 이전에 계산했던 그래디언트를 저장하고 다시 활용한다는 측면에서 SGD와 배치 그래디언트 디센트의 장점을 취하고 있습니다.

w^{k+1} = w^k - \dfrac{\alpha}{n} \left( f'_j(w^k)-f'_j(\theta_j^k)+\sum_{i=1}^{n}f'_i(\theta_i^k) \right)

첨자가 조금 장황해 보일 수 있지만 사실 특별한 내용은 아닙니다. 이전까지의 그래디언트를 모두 누적하고 있는 항이 \sum_{i=1}^{n}f'_i(\theta_i^k)입니다. 그리고 현재 스텝에서 구한 그래디언트를 f'_j(w^k) 더하되 혹시 이전에 누적된 것에 포함이 되어 있다면, 즉 랜덤하게 추출한 적이 있는 샘플이라면 이전의 그래디언트 값을 f'_j(\theta_j^k) 빼 줍니다. 혼돈을 줄이기 위해 현재의 스텝의 파라미터와 이전의 스텝의 파라미터를 w\theta로 구별하였습니다.

SAGA 알고리즘은 여기에서 과거 그래디언트에만 평균을 적용하는 방식입니다. 위 공식과 비교해 보시면 금방 눈치챌 수 있습니다.

w^{k+1} = w^k - \alpha \left( f'_j(w^k)-f'_j(\theta_j^k)+\dfrac{1}{n}\sum_{i=1}^{n}f'_i(\theta_i^k) \right)

SAGA 알고리즘은 Ridge, RidgeClassifier, LogisticRegression 등에 solver 매개변수를 ‘saga’로 설정하여 사용할 수 있습니다. 이 모델들은 대량의 데이터셋에서 SAG 알고리즘을 사용할 수 있었는데 SAGA가 SAG 보다 성능이 좋으므로 데이터셋이 클 경우 SAGA를 항상 사용하는 것이 좋을 것 같습니다. 그럼 예를 살펴 보겠습니다.

먼저 익숙한 cancer 데이터셋에서 로지스특회귀로 SAG와 SAGA를 비교해 보겠습니다.

logreg_sag = LogisticRegression(solver='sag', max_iter=10000)
logreg_sag.fit(X_train, y_train)
print("훈련 세트 점수: {:.3f}".format(logreg_sag.score(X_train, y_train)))
print("테스트 세트 점수: {:.3f}".format(logreg_sag.score(X_test, y_test)))
훈련 세트 점수: 0.927
테스트 세트 점수: 0.930

다음은 SAGA solver일 경우입니다.

logreg_saga = LogisticRegression(solver='saga', max_iter=10000)
logreg_saga.fit(X_train, y_train)
print("훈련 세트 점수: {:.3f}".format(logreg_saga.score(X_train, y_train)))
print("테스트 세트 점수: {:.3f}".format(logreg_saga.score(X_test, y_test)))
훈련 세트 점수: 0.920
테스트 세트 점수: 0.937

둘이 거의 비슷하지만 SAGA의 테스트 점수가 조금 더 좋습니다. 좀 더 큰 데이터셋에 적용해 보기 위해서 캘리포니아 주택 가격 데이터셋을 사용해 보겠습니다. 이 데이터셋은 8개의 특성을 가지고 있고 2만개가 넘는 샘플을 가지고 있습니다. 타깃값은 평균 주택 가격입니다. scikit-learn에 이 데이터를 다운 받아 로드할 수 있는 함수가 있습니다.

from sklearn.datasets import fetch_california_housing
housing = fetch_california_housing()

housing도 scikit-learn의 다른 샘플 데이터와 동일하게 Bunch 클래스의 객체입니다. 데이터를 분할하고 릿지 회귀를 사용하여 이전과 마찬가지로 ‘sag’와 ‘saga’를 비교해 보겠습니다.

X_train, X_test, y_train, y_test = train_test_split(
    housing.data, housing.target, random_state=42)
ridge = Ridge(solver='sag').fit(X_train, y_train)
print("훈련 세트 점수: {:.3f}".format(ridge.score(X_train, y_train)))
print("테스트 세트 점수: {:.3f}".format(ridge.score(X_test, y_test)))
훈련 세트 점수: 0.061
테스트 세트 점수: 0.062

이번에는 SAGA solver 입니다.

ridge_saga = Ridge(solver='saga').fit(X_train, y_train)
print("훈련 세트 점수: {:.3f}".format(ridge_saga.score(X_train, y_train)))
print("테스트 세트 점수: {:.3f}".format(ridge_saga.score(X_test, y_test)))
훈련 세트 점수: 0.035
테스트 세트 점수: 0.036

확실히 SAGA solver의 R2 스코어가 더 낮게 나온 것을 알 수 있습니다. 🙂

이 샘플 코드는 ‘파이썬 라이브러리를 활용한 머신러닝‘ 깃허브(https://github.com/rickiepark/introduction_to_ml_with_python/blob/master/SAGA%20solver.ipynb)에서 확인할 수 있습니다.

참고 자료

scikit-learn 0.19 Release

파이썬의 대표적인 머신 러닝 라이브러리인 scikit-learn 0.19 버전이 릴리즈 되었습니다. 0.19 버전에는 여러가지 새로운 기능과 버그 수정들이 포함되었습니다. 대표적으로는 이상치 탐지를 위한 sklearn.neighbors.LocalOutlierFactor, 분위 값을 사용하는 sklearn.preprocessing.QuantileTransformer, 이진 분류기를 엮어 앙상블 시킬 수 있는 sklearn.multioutput.ClassifierChain,  교차 검증에서 훈련 세트와 테스트 세트의 점수를 모두 리턴해 주는 sklearn.model_selection.cross_validate가 추가되었습니다.

sklearn.decomposition.NMF의 solver 매개변수에 ‘mu'(Multiplicative Update)가 추가 되었고 sklearn.linear_model.LogisticRegression에 L1 규제를 사용한 SAGA 알고리즘의 구현인 ‘saga’ 옵션이 solver 매개변수에 추가되었습니다. 또 cross_val_score와 GridSearchCV, RandomizedSearchCV의 scoring 매개변수에 복수개의 스코어 함수를 지정할 수 있게 되었고 Pipeline 클래스에 memory 매개변수가 추가되어 그리드 서치 안에서 반복적으로 수행될 때 전처리 작업을 캐싱할 수 있게 되었습니다. 이 외에도 많은 버그가 수정되고 기능이 향상되었습니다. 자세한 내용은 릴리즈 노트를 참고하세요.

scikit-learn 0.19 버전은 pip 나 conda 를 이용하여 손쉽게 설치가 가능합니다.

$ conda install scikit-learn

$ pip install --upgrade scikit-learn