월간 보관물: 2017 12월

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)에서 확인할 수 있습니다.

참고 자료

Pipeline에서 캐싱을 사용하기

며칠전 ‘파이썬 라이브러리를 활용한 머신러닝‘의 원저자 안드리아스 뮐러가 한 유투브 채널에 나와 Scikit-Learn의 0.19버전에서 추가된 기능과 0.20에서 추가될 내용을 소개했습니다.

0.19에 새롭게 추가된 기능으로 대표적으로 언급한 것이 Pipeline 캐싱, SAGA solver, RepeatedKFold, QuantileTransformer, ClassifierChain, 다중 scoring 설정입니다. 또 0.20에 추가될 기능으로는 GradientBoosting의 early stopping, CategoricalEncoder, PowerTransfomer, ColumnTransformer, OpenML 데이터셋 로더, matplotlib 기반의 트리 그래프 등입니다. 시간날 때마다 차례대로 살펴 보도록 하겠습니다. 먼저 오늘은 파이프라인 캐싱입니다!

파이프라인 캐싱은 파이프라인의 단계마다 transformer의 fit 결과를 저장하여 다시 사용한다는 것입니다. 전처리 단계가 복잡한 그리드서치를 사용할 때 특히 유용할 수 있습니다. 어 그럼 지금까지는 캐싱없이 무식하게 반복적으로 전처리를 매번 다시 했다는 건가요? 네 맞습니다. ㅠ.ㅠ

캐싱을 사용하려면 Pipeline 클래스의 memory 매개변수에 캐싱에 사용할 디렉토리를 지정하기만 하면 됩니다. 간단한 예를 살펴 보겠습니다. 책의 예제와 비슷하게 보스턴 주택가격 데이터셋을 로드하여 훈련 데이터를 준비하고 매개변수 탐색을 위한 그리드를 정의합니다.

boston = load_boston()
X_train, X_test, y_train, y_test = train_test_split(boston.data, boston.target,
                                                    random_state=0)
param_grid = {'polynomialfeatures__degree': [1, 2, 3, 4, 5],
              'ridge__alpha': [0.001, 0.01, 0.1, 1, 10, 100]}

그 다음은 파이프라인을 만들고 GridSearchCV로 매개변수 탐색을 하면 됩니다. 파이프라인 클래스를 사용해서 Pipeline(memory=’…’)와 같이 사용해도 되고 파이프라인을 간단하게 만들어 주는 make_pipeline(memory=’…’) 함수를 사용할 수도 있습니다. 캐싱에 사용할 임시 디렉토리를 지정해 주기위해 mkdtemp와 rmtree 함수를 임포트하고 임시 디렉토리를 만듭니다.

from tempfile import mkdtemp
from shutil import rmtree
cache_dir = mkdtemp()

다음 make_pipeline으로 파이프라인 단계를 만들고 GridSearchCV로 매개변수 탐색을 합니다.

pipe = make_pipeline(StandardScaler(), PolynomialFeatures(), Ridge(),
                     memory=cache_dir)
grid = GridSearchCV(pipe, param_grid=param_grid, cv=5, n_jobs=-1)
grid.fit(X_train, y_train)

간단하죠? 작업이 끝나고 난 뒤에는 임시 디렉토리를 지워 줍니다.

rmtree(cache_dir)

전처리 단계가 복잡하고 많을 수록 캐싱의 효과는 큽니다. 이 샘플 코드를 캐싱을 사용하지 않고 실행했을 때와 비교하면 10.7초에서 6.57초로 30%이상 속도가 빨라 졌습니다!

이 포스트에 사용한 전체 코드는 깃허브(https://github.com/rickiepark/introduction_to_ml_with_python/blob/master/Pipeline-cache.ipynb)에서 확인할 수 있습니다.

PyTorch 0.3.0 Release

파이토치PyTorch 0.3.0 버전이 릴리즈되었습니다. 주요한 변경 사항으로는 loss를 reduce할지 여부를 지정할 수 있는 매개변수, autograd를 위한 프로파일러, 새로운 레이어와 다양한 함수 등 많은 기능이 추가, 변경되었고 성능이 많이 향상되었다고 합니다. 자세한 변경사항은 릴리즈 노트를 참고하세요.

파이토치 0.3.0 부터는 CUDA 9과 cuDNN 7을 지원하고 conda 채널이 pytorch로 바뀌었습니다. CUDA 지원은 텐서플로보다 조금 더 빠른 것 같습니다. 또 CUDA 8, 7.5 버전에 대한 바이너리도 유지하고 있어 편리한 것 같습니다. 지원하는 파이썬 버전은 2.7, 3.5, 3.6 입니다.

# macOS (no GPU)
$ conda install pytorch torchvision -c pytorch

# Linux CUDA 7.5
$ conda install pytorch torchvision cuda75 -c pytorch
# Linux CUDA 8.0
$ conda install pytorch torchvision -c pytorch
# Linux CUDA 9.0
$ conda install pytorch torchvision cuda90 -c pytorch

0.2.0 버전도 그러더니 파이토치는 이번에도 컨퍼런스(NIPS 2017)에 일정에 맞춰 출시하네요. 🙂