월간 보관물: 2017 12월

[Review] Hands-On Machine Learning with Scikit-Learn & TensorFlow

lrg 이 책의 번역을 시작한지 벌써 6개월이 훌쩍 넘어가 버렸습니다. 개인적인 핑계가 없는 것은 아니지만, 그럼에도 불구하고 이렇게 오랜 시간이 걸린 것은 번역 작업이 어떤 병렬화도 불가능한 노동 집약적인 일이기 때문일 것입니다 🙂 작업이 거의 마무리되어 가는 시점에 원서 전체에 대한 리뷰와 작업에 대한 기록을 위해 글을 남깁니다.

이 책은 500페이지가 넘어 두툼한 편이고, Scikit-Learn 라이브러리를 사용한 머신러닝Machine Learning텐서플로TensorFlow를 사용한 딥러닝Deep Learning 파트 두 부분으로 크게 나누어 집니다(실제 최근 프랑스어 판은 두 권의 책으로 출판되었다고 합니다). 분량만으로 비교했을 때에도 약간 딥러닝 쪽에 더 무게를 두고 있습니다. 간간히 Scikit-Learn과 텐서플로를 함께 다루고 있는 책들이 있는데, 아마도 딥러닝만을 설명하기엔 머신러닝 개념이 꼭 필요하기 때문인 것 같습니다. 물론 텐서플로만 다루는 책이더라도 도입부에 회귀와 분류 모델을 간단히 텐서플로로 만들어 봄으로써 머신러닝에 대한 개념을 다루고 진행하는 것이 일반적입니다. 이 책은 두 라이브러리를 모두 다루고 있으며 유사한 책 중에서는 가장 포괄적이고 세세한 내용을 담고 있습니다. 또 이론서와 실용서의 중간 형태라고 볼 수 있습니다. 수식없이 코드만 있지 않고, 코드는 없고 이론만 늘어 놓지도 않습니다.

작업을 시작하고 나서 7월, 10월에 원서의 내용이 크게 개정되었습니다. 주로 딥러닝 파트였으며, 원서의 여러가지 오류 때문이기도 하지만 아마도 이 분야의 변화가 빠른 것도 한 몫 했을 것입니다. 바뀐 부분을 PDF로 받았는데 책으로 옮겨 적기에는 너무 양이 많아 출력해서 책에 오려 붙이고 작업을 진행했습니다. 그 외에도 괴장히 많은 에러타가 있고, 아직 컨펌되지 않은 에러타도 남아 있습니다(에러타가 많이 발견되었다는 것은 그 만큼 높은 인기를 반증합니다). 최근에는 에러타와 개정판에도 언급되지 않은 변경사항이 사파리온라인에서 발견되어 저의 멘탈을 붕괴 직전까지 몰고 가기도 했습니다. 😦

이 책이 다른 책과 구별되는 한 장을 고르라고 한다면 저는 2장을 선택할 것 같습니다. 2장은 회귀 모델을 사용해 머신러닝 프로젝트의 준비에서부터 런칭까지 모든 단계를 짚어가고 있습니다. 이 책에는 특성 공학feature engineering에 대한 장을 따로 할당하지 않았는데 2장에서 이런 부분이 다소 해소됩니다. 다른 책에서도 프로젝트 운영에 대한 가이드라인이 다소 포함되어 있기도 하지만, 여기에서처럼 실제 프로젝트를 진행하듯이 자세히 기술되어 있지는 않습니다. 이런 부분은 저자의 프로젝트 경험을 엿볼 수 있는 좋은 사례인 것 같습니다.

이 책은 구성상 독특한 점이 두 가지가 있습니다. 첫째는 다른 책들과는 달리 샘플 코드 전체를 싣지 않고 있는 것입니다. 비교적 많은 주제에 대해 상세히 논하고 있기 때문에 만약 코드를 모두 썼다면 이보다 훨씬 더 두꺼운 책이 되었을 것입니다. 설명을 위해 꼭 필요한 코드 부분만 실었으며 전체 코드는 깃허브github.com의 주피터 노트북Jupyter Notebook을 참고하도록 안내하고 있습니다. 둘째, 각 장의 끝에 연습문제를 포함하고 있습니다. 그렇다고 이 책이 대학 교재스러운 편집 구성을 가지는 것은 아닙니다. 일반 컴퓨터 과학 도서에서 연습문제를 가진 경우는 드물기 때문에 신선하기도 하고 저자의 노력을 느낄 수 있습니다. 연습문제는 크게 본문의 내용을 질문하는 것과 실습을 하는 것, 두 종류로 나누어 집니다. 내용에 대한 질문의 답은 책의 부록에 실려 있습니다. 실습 문제는 깃허브의 장별 주피터 노트북 말미에 포함되어 있습니다. 다만 아직 모든 연습문제의 답이 올려져 있지 않고 점진적으로 추가되고 있습니다. 번역서가 출간될 쯤에는 깃허브의 주피터 노트북의 내용도 모두 한글로 번역되어 제공됩니다. 추후 업데이트되는 연습문제도 함께 번역이 될 것입니다.

Scikit-Learn을 이용해서는 회귀, 분류에 대한 기본 내용으로 시작해서 서포트 벡터 머신Support Vector Machine, 결정 트리Decision Tree, 앙상블emsemble, 차원 축소Dimensionality Reduction까지 다룹니다. 아무래도 ‘파이썬 라이브러리를 활용한 머신러닝‘처럼 머신러닝 파이프라인의 모든 부분을 다루기엔 지면이 부족합니다. 하지만 다루는 모델의 이론(수식)을 충분히 설명하고 예제 코드를 병행하고 있습니다. 텐서플로를 사용해서는 기본 인공 신경망과 텐서플로에 대한 소개를 먼저 시작합니다. 그리고 심층 신경망을 학습하기 위해 당면한 문제들을 해결하는 여러가지 방법을 소개합니다. 모델 재사용, 여러가지 최적화 알고리즘, 초기화, 규제 등입니다. 보통 GPU나 분산처리 부분은 딥러닝 책의 말미에 나오는 경우가 많은데 이 책에서는 합성곱Convolution 신경망 보다도 먼저 나오고 비중이 비교적 큽니다. 그 다음 합성곱 신경망Convolution Neural Networks에서 합성곱, 패딩, 스트라이딩에 대한 설명 뿐만 아니라 LeNet-5, AlexNet, GooLeNet, ResNet 구조를 상세히 설명하고 있습니다. 순환 신경망Recurrent Neural Networks에서는 직접 간단한 RNN 연산을 만들어 보면서 순환 신경망에 대한 이해를 돕고 있으며, LSTM, GRU와 기본적인 기계번역 RNN 구조까지 설명하고 있습니다. 그 다음 오토인코더Autoencoder, 강화학습Reinforcement Learning을 다루는 장으로 이어집니다.

아래 장별 제목을 옮겨 놓았습니다. 책에 관련하여 궁금한 점이나 리뷰, 추천사에 의향이 있으시면 언제든지 댓글이나 메일 주세요. 앗 그리고 새해 복 많이 받으세요! 🙂

  1. 한눈에 보는 머신러닝
  2. 머신러닝 프로젝트 시작부터 끝까지
  3. 분류
  4. 모델 훈련
  5. 서포트 벡터 머신
  6. 결정 트리
  7. 앙상블 학습과 랜덤 포레스트
  8. 차원 축소
  9. 텐서플로 시작하기
  10. 인공 신경망 소개
  11. 심층 신경망 훈련
  12. 다중 머신과 장치를 위한 분산 텐서플로
  13. 합성곱 신경망
  14. 순환 신경망
  15. 오토인코더
  16. 강화학습
  • 연습문제 정답
  • 머신러닝 프로젝트 체크 리스트
  • SVM 쌍대 문제
  • 자동 미분
  • 인기 있는 다른 인공 신경망 구조

반복 교차 검증

Scikit-Learn 0.19 버전에서 추가된 기능 중 이번에는 모델 선택 패키지 하위에 있는 RepeatedKFold와 RepeatedStratifiedKFold를 알아 보겠습니다. 이 두 클래스는 각각 KFold와 StratifiedKFold를 감싸고 있는 래퍼 클래스처럼 보아도 무방합니다. 데이터셋의 크기가 크지 않을 경우, LOOCV를 사용하기에는 결과가 불안정해서 교차 검증을 반복적으로 여러번 수행하곤 합니다. 0.19 버전에서는 이런 반복적인 교차 검증을 수행할 수 있는 두 개의 클래스를 추가하였습니다.

RepeatedKFold와 RepeatedStratifiedKFold를 사용하는 방법은 다른 분할 클래스들과 매우 비슷합니다. 이 클래스들은 폴드를 한번만 나누는 것이 아니고 지정한 횟수(n_repeats)만큼 반복해서 나누게 되고 교차 검증 점수도 반복한 만큼 얻을 수 있습니다. 이때 기본적으로 랜덤하게 나누므로 분할기에 자동으로 Shuffle=True 옵션이 적용됩니다. n_repeats의 기본값은 10입니다.

붓꽃 데이터셋으로 이 클래스들을 사용해서 교차 검증을 해 보겠습니다.

사이킷런 0.19 버전 이하에서는 LinearSVCliblinear를 사용하는 LogisticRegressionverbose 매개변수가 0이 아니고 max_iter 반복 안에 수렴하지 않을 경우 반복 횟수를 증가하라는 경고 메세지가 나옵니다. 사이킷런 0.20 버전부터는 verbose 매개변수에 상관없이 max_iter 반복 안에 수렴하지 않을 경우 반복 횟수 증가 경고가 나옵니다. 경고 메세지를 피하기 위해 max_iter 매개변수의 기본값을 증가시킵니다.

향후 사이킷런 0.22 버전에서 LogisticRegression 클래스의 solver 매개변수 기본값이 liblinear에서 lbfgs로 변경될 예정입니다. 사이킷런 0.20 버전에서 solver 매개변수를 지정하지 않는 경우 이에 대한 경고 메세지를 출력합니다. 경고 메세지를 피하고 향후 변경될 내용을 적용하기 위하여 solver 매개변수를 lbfgs로 설정합니다.

사이킷런 0.20 버전에서 LogisticRegressionmulti_class 매개변수 옵션에 auto가 추가되었습니다. auto로 설정하면 이진 분류이거나 solverliblinear일 경우에는 ovr을 선택하고 그 외에는 multinomial을 선택합니다. 사이킷런 0.22 버전부터는 multi_class 매개변수의 기본값이 ovr에서 auto​로 변경됩니다. 경고 메세지를 피하기 위해 명시적으로 ovr 옵션을 지정합니다.

from sklearn.datasets import load_iris
from sklearn.model_selection import cross_val_score, KFold, StratifiedKFold
from sklearn.linear_model import LogisticRegression

iris = load_iris()
logreg = LogisticRegression(solver='liblinear', multi_class='auto', max_iter=1000)

먼저 가장 기본이 되는 KFold를 사용한 교차 검증입니다.

kfold = KFold(n_splits=5)
scores = cross_val_score(logreg, iris.data, iris.target, cv=kfold)
scores, scores.mean()
(array([ 1.        ,  0.93333333,  0.43333333,  0.96666667,  0.43333333]),
 0.7533333333333333)

iris 데이터셋은 타깃값의 순서대로 훈련 데이터가 정렬되어 있기 때문에 기본 KFold에서는 교차 검증 점수가 매우 들쭉 날쭉 합니다.

이번엔 RepeatedKFold를 사용해 보겠습니다. 사용법은 KFold와 같지만 반복 횟수(n_repeats)를 설정해 주고 결과를 일정하게 고정하기 위해 random_state를 지정했습니다.

from sklearn.model_selection import RepeatedKFold
rkfold = RepeatedKFold(n_splits=5, n_repeats=5, random_state=42)
scores = cross_val_score(logreg, iris.data, iris.target, cv=rkfold)
scores, scores.mean()
(array([ 1.        ,  0.93333333,  0.9       ,  0.96666667,  0.96666667,
         0.96666667,  0.93333333,  1.        ,  1.        ,  0.83333333,
         0.93333333,  0.9       ,  0.96666667,  0.9       ,  0.93333333,
         0.96666667,  1.        ,  0.96666667,  0.93333333,  0.93333333,
         0.96666667,  0.9       ,  1.        ,  0.93333333,  0.93333333]),
 0.94666666666666677)

교차 검증 점수가 반복 횟수만큼 5개 행으로 출력되었습니다. 이전 보다 훨씬 고른 점수 분포를 보여 주며 전체 점수의 평균은 0.95 정도입니다. 교차 검증 점수의 범위를 박스플롯으로 가늠해 볼 수 있습니다.

boxplot1

다음엔 계층별 교차 검증인 StratifiedKFold를 적용해 보겠습니다.

skfold = StratifiedKFold(n_splits=5)
scores = cross_val_score(logreg, iris.data, iris.target, cv=skfold)
scores, scores.mean()
(array([ 1.        ,  0.96666667,  0.93333333,  0.9       ,  1.        ]),
 0.96000000000000019)

StratifiedKFold는 타깃값 클래스 비율을 고려하므로 전체적으로 고르게 폴드를 나누어서 교차 검증 점수가 안정되었습니다. 그럼 RepeatedStratifiedKFold를 사용해서 비교해 보겠습니다.

from sklearn.model_selection import RepeatedStratifiedKFold
rskfold = RepeatedStratifiedKFold(n_splits=5, n_repeats=5, random_state=42)
scores = cross_val_score(logreg, iris.data, iris.target, cv=rskfold)
scores, scores.mean()
(array([ 0.96666667,  0.96666667,  0.96666667,  0.93333333,  0.96666667,
         0.86666667,  0.96666667,  0.96666667,  0.93333333,  0.96666667,
         1.        ,  1.        ,  0.93333333,  0.93333333,  0.93333333,
         1.        ,  0.96666667,  0.96666667,  0.9       ,  0.96666667,
         0.96666667,  0.96666667,  0.96666667,  0.9       ,  0.96666667]),
 0.95466666666666655)

평균 교차 검증 점수는 0.955 정도로 RepeatedKFold와 StratifiedKFold의 중간에 해당하는 값입니다. 박스 플롯 끼리 비교해 보면 거의 비슷하지만 RepeatedStratifiedKFold가 약간 더 집중되어 있음을 알 수 있습니다.

boxplot2.png

이 두 클래스가 더욱 유용해질 때는 그리드서치에 사용할 때 입니다. cross_val_score와 마찬가지로 GridSearchCV의 분할은 회귀일 때는 KFold, 분류일 때는 StratifiedKFold가 기본입니다. 자원이 허락된다면 RepeatedKFold와 RepeatedStratifiedKFold로 바꾸지 않을 이유가 없습니다.

앞에서는 책의 예제와 비슷한 모양을 갖추기 위해 데이터셋을 나누지 않았습니다. 여기서는 훈련 데이터와 테스트 데이터로 나누고 RepeatedStratifiedKFold를 그리드서치에 적용해 보겠습니다.

사이킷런 0.22 버전부터는 GridSearchCV와 RandomizedSearchCV의 iid 매개변수 기본값이 True에서 False로 바뀝니다. 0.24 버전에서는 이 매개변수가 아예 삭제될 예정입니다. iid 매개변수가 True이면 독립 동일 분포라고 가정하고 테스트 세트의 샘플 수로 폴드의 점수를 가중 평균합니다. False로 지정하면 단순한 폴드 점수의 평균입니다. False일 때 기본 교차 검증과 동작 방식이 같습니다. 사이킷런 0.20 버전에서 iid 매개변수가 기본값일 때 가중 평균과 단순 평균의 차이가 10^-4 이상이면 경고 메세지가 발생합니다. 경고 메세지를 피하고 향후 변경될 방식을 사용하기 위해 GridSearchCV의 iid 매개변수를 False로 설정합니다

from sklearn.model_selection import GridSearchCV, train_test_split
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=42)
param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100, 1000]}
grid = GridSearchCV(logreg, param_grid, cv=rskfold, return_train_score=True, iid=False)
grid.fit(X_train, y_train)
grid.score(X_test, y_test), grid.best_params_, grid.best_score_

테스트 점수는 완벽하고 규제 매개 변수 C의 최적값은 100이 선택되었습니다. 교차 검증 최고 점수는 0.96 입니다.

(1.0, {'C': 100}, 0.96250000000000002)

cv_results_에 있는 교차 검증 결과를 출력해 보면 훈련과 테스트에 대해 모두 5번 반복해서 각각 25개의 점수가 있는 것을 확인할 수 있습니다.

for k in grid.cv_results_:
    if 'split' in k:
    print(k, grid.cv_results_[k])

내용이 장황하므로 일부만 추렸습니다. 전체 출력 내용은 깃허브를 참고해 주세요.

split21_test_score [ 0.34782609  0.65217391  0.82608696  0.91304348  0.86956522  0.91304348
  0.86956522]
split6_test_score [ 0.34782609  0.65217391  0.7826087   0.91304348  0.95652174  0.95652174
  0.95652174]
split24_train_score [ 0.34065934  0.64835165  0.83516484  0.94505495  0.96703297  0.96703297
  0.96703297]
split3_train_score [ 0.34444444  0.65555556  0.82222222  0.95555556  0.96666667  0.96666667
  0.96666667]
...
split16_test_score [ 0.34782609  0.65217391  0.91304348  0.95652174  0.95652174  1.
  0.95652174]
split23_test_score [ 0.31818182  0.63636364  0.77272727  0.95454545  0.95454545  0.95454545
  0.95454545]
split1_test_score [ 0.34782609  0.65217391  0.86956522  0.82608696  0.91304348  0.95652174
  1.        ]
split18_train_score [ 0.34444444  0.65555556  0.82222222  0.95555556  0.96666667  0.96666667
  0.96666667]

폴드 분할을 여러번 반복하여 그리드서치의 결과를 조금 더 안정적으로 만들 수 있을 것 같습니다. 반복 횟수가 클수록 훈련 시간은 느려지게 되므로 파이프라인 캐싱을 활용하고 가용한 자원내에서 반복을 수행하는 것이 좋을 것 같습니다.

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

Convolution vs Cross-correlation

합성곱convolution 혹은 콘볼루션 신경망은 주로 시각 분야 애플리케이션에 널리 사용됩니다. 위키피디아의 합성곱 정의를 보면 “하나의 함수와 또 다른 함수를 반전 이동한 값을 곱한 다음, 구간에 대해 적분하여 새로운 함수를 구하는 수학 연산자이다“라고 잘 설명되어 있습니다. 그리고 다음과 같은 애니메이션으로도 많이 익숙합니다.

no_padding_no_strides1

조금 더 적분스러운 표현의 그림은 위키피디아의 그림이 직관적입니다.

convolution_of_spiky_function_with_box2

그림으로 표현이 어려운 점은 두 함수 중 하나를 반전시킨다는 점입니다.

합성곱과 거의 비슷한 연산으로 교차상관cross-correlation이 있습니다. 교차상관의 정의를 보면 한 함수를 반전한다는 것만 빼고는 합성곱과 동일합니다.

합성곱: (f*g)(t) = \int_{-\infty}^{\infty} f(\tau)g(t-\tau)\, d\tau

교차상관: (f*g)(t) = \int_{-\infty}^{\infty} f(\tau)g(t+\tau)\, d\tau

합성곱 신경망의 입력값에 필터(가중치 혹은 커널)를 콘볼루션하려면 필터를 뒤집어서 적용해야 합니다. 그런데 어차피 필터의 값을 학습하려는 것이 목적이기 때문에 뒤집어서 콘볼루션을 하나 그냥 하나 동일합니다. 학습과 추론inference시에 필터만 일정하면 됩니다. 그래서 딥러닝 프레임워크들은 합성곱이 아니고 그냥 교차상관으로 구현되어 있습니다. 하지만 관습상 합성곱이라고 부릅니다. 사실 이게 중요한 문제는 아닌 것 같습니다.

인기를 끌었던 “파이썬 머신 러닝” 2판이 나오면서 합성곱에 대한 내용이 많이 보강되었습니다. 보통 다른 책들은 피드포워드 신경망(혹은 퍼셉트론) 정도를 넘파이NumPy로 직접 구현해 보고 합성곱 신경망부터는 텐서플로 같은 라이브러리를 사용하는 것이 일반적입니다. 합성곱이나 패딩, 스트라이드 등을 코드로 구현하기가 번거롭기 때문이겠죠.

여기서 저자는 직접 합성곱을 구현한 코드를 추가했습니다. 그런데 교차상관이 아니라 (필터를 뒤집어서) 진짜 합성곱을 했습니다. 물론 합성곱과 교차상관에 대한 비슷한 점을 언급하기는 했지만 굳이 합성곱을 구현할 필요가 있었을까, 보통의 경우 교차상관을 사용한다고 언급해주면 좋지 않았을까 하는 생각이 들었습니다. 그래서 메일을 보냈더니 다음 중쇄할 때 관련 코멘트를 추가한다고 회신이 왔네요.

혹시 원서를 읽으시는 분이 있다면 다른 라이브러리들이 진짜 합성곱 연산을 하는 것으로 오해하지 마세요. 🙂

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로 매개변수 탐색을 합니다.

사이킷런 0.22 버전부터는 GridSearchCVRandomizedSearchCViid 매개변수 기본값이 True에서 False로 바뀝니다. 0.24 버전에서는 이 매개변수가 아예 삭제될 예정입니다. iid 매개변수가 True이면 독립 동일 분포라고 가정하고 테스트 세트의 샘플 수로 폴드의 점수를 가중 평균합니다. False로 지정하면 단순한 폴드 점수의 평균입니다. False일 때 기본 교차 검증과 동작 방식이 같습니다. 사이킷런 0.20 버전에서 iid 매개변수가 기본값일 때 가중 평균과 단순 평균의 차이가 10^-4 이상이면 경고 메세지가 발생합니다. 경고 메세지를 피하고 향후 변경될 방식을 사용하기 위해 GridSearchCViid 매개변수를 False로 설정합니다

pipe = make_pipeline(StandardScaler(), PolynomialFeatures(), Ridge(),
                     memory=cache_dir)
grid = GridSearchCV(pipe, param_grid=param_grid, cv=5, n_jobs=-1, iid=False)
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)에 일정에 맞춰 출시하네요. 🙂