[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입니다.

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

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()

먼저 가장 기본이 되는 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를 그리드서치에 적용해 보겠습니다.

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

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)에 일정에 맞춰 출시하네요. 🙂

더욱 랜덤한 포레스트-익스트림 랜덤 트리(ExtraTreesClassifier)

파이썬 라이브러리를을 활용한 머신러닝‘ 2장의 지도학습에서 대표적인 앙상블 모델로 랜덤 포레스트를 소개하고 있습니다. 랜덤 포레스트는 부스트랩 샘플과 랜덤한 후보 특성들을 사용해 여러개의 결정 트리decision tree를 앙상블 합니다. 그래서 훈련 데이터에 과대적합을 막아주고 모델의 일반화 성능이 항상 단일 트리보다 높습니다. 랜덤 포레스트 모델의 변종으로 익스트림 랜덤 트리extremely randomized trees 혹은 엑스트라 트리ExtraTrees라 부르는 모델이 있습니다. 엑스트라 트리는 포레스트 트리의 각 후보 특성을 무작위로 분할하는 식으로 무작위성을 증가 시킵니다. Scikit-Learn은 앙상블 패키지 안에 엑스트라 트리 모델을 제공합니다.

from sklearn.ensemble import ExtraTreesClassifier

​RandomForestClassifier 클래스가 사용하는 결정 트리는 DecisionTreeClassifier입니다. 이에 반해 ExtraTreesClassifier가 사용하는 결정 트리는 ExtraTreeClassifier입니다. 이름이 매우 비슷하니 유의하세요. ExtraTreesClassifier의 매개변수는 RandomForestClassifier와 동일합니다. 책에서와 같이 make_moons 데이터셋에 엑스트라 트리를 학습시켜 보겠습니다.

X, y = make_moons(n_samples=100, noise=0.25, random_state=3)
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, 
                                                    random_state=42)

xtree = ExtraTreesClassifier(n_estimators=5, random_state=2)
xtree.fit(X_train, y_train)

엑스트라 트리가 사용하는 ExtraTreeClassifier()의 기본값으로 베이스 트리가 만들어 집니다. 랜덤 포레스트와 같이 베이스 모델을 직접 만들어 주입할 수는 없습니다. 엑스트라 트리가 만든 베이스 트리와 전체의 결정 경계를 그려 보겠습니다.

extratrees

그래프에서 볼 수 있듯이 랜덤 포레스트가 만든 것보다 두 클래스의 경계를 잘 구분하고 있습니다. 사실 엑스트라 트리의 베이스 트리인 ExtraTreeClassifier는 DecisionTreeClassifier를 상속한 것이며 splitter=’best’가 아니고 splitter=’random’인 것과 max_features=’auto’인 것을 제외하고는 동일합니다.

이번에는 cancer 데이터셋에 적용해 보겠습니다.

X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, 
                                                    random_state=0)
xtree = ExtraTreesClassifier(n_estimators=100, random_state=0)
xtree.fit(X_train, y_train)

print("훈련 세트 정확도: {:.3f}".format(xtree.score(X_train, y_train)))
print("테스트 세트 정확도: {:.3f}".format(xtree.score(X_test, y_test)))

훈련 세트 정확도: 1.000
테스트 세트 정확도: 0.972

훈련 세트와 테스트 세트에서의 정확도는 랜덤 포레스트와 동일합니다. 하지만 특성 중요도를 그려보면 차이점을 발견할 수 있습니다.

extratree_feature_importance_

이 그래프에서 볼 수 있듯이 엑스트라 트리가 랜덤 포레스트 보다 전반적으로 특성의 중요도를 더 높게 평가하고 있습니다. 이는 엑스트라 트리가 더 폭넓은 시각으로 특성들을 평가한다고 생각할 수 있습니다. 단일 결정 트리에서는 ‘worst radius’가 거의 독점적으로 주요한 특성으로 평가되었지만 랜덤 포레스트는 ‘worst perimeter’가 더 높았고 ‘worst area’는 훨씬 더 낮게  나왔습니다. 이에 비해 엑스트라 트리는 ‘worst area’, ‘worst perimeter’, ‘worst radius’, ‘mean concavity’ 등의 특성 중요도가 비교적 고르게 나온 것을 볼 수 있습니다.

당연히 회귀에 대응하는 엑스트라 트리인 ExtraTreesRegressor도 있습니다. ExtraTreesClassifier와 ExtraTreesRegressor의 매개변수 기본값은 부스트랩 샘플을 사용하지 않도록 bootstrap=False인 것을 제외하고는 랜덤 포레스트와 동일합니다. 이 포스트의 코드는 깃허브(https://github.com/rickiepark/introduction_to_ml_with_python/blob/master/ExtraTreesClassifier.ipynb)에서 확인하실 수 있습니다.

**Complete Draft** Reinforcement Learning: An Introduction

리차드 서튼Richard Sutton 교수의 강화학습 책 “Reinforcement Learning: An Introduction”의 2판의 드래프트가 끝났다는 소식입니다. 이 책은 온라인에서 무료로 읽을 수 있습니다. 이 책은 총 449페이지이고 파일 사이즈는 16M 정도로 아주 크지 않지만 혹시 네트워크가 느릴 경우를 대비해 블로그에 다운로드 링크(bookdraft2017nov5)를 추가했습니다.

다음은 전체 책의 목차입니다.

  1. Introduction
  2. Multi-armed Bandits
  3. Finite Markov Decision Processes
  4. Dynamic Programming
  5. Monte Carlo Methods
  6. Temporal-Difference Learning
  7. n-step Bootstrapping
  8. Planning and Learning with Tabular Methods
  9. On-policy Prediction with Approximation
  10. On-policy Control with Approximation
  11. *Off-policy Methods with Approximation
  12. Eligibility Traces
  13. Policy Gradient Methods
  14. Psychology
  15. Neuroscience
  16. Applications and Case Studies
  17. Frontiers

(업데이트) 2018년 1월 1일에 마이너한 업데이트가 있었던 것 같습니다. 새로운 PDF를 참고하세요.

TensorFlow 1.4.0 Release

텐서플로 1.4.0 버전이 정식 릴리즈되었습니다. 이번 버전에서 tf.keras와 tf.data가 코어 API로 들어왔습니다. 이제 대기하고 있던 케라스 책들이 슬슬 나오기 시작할 것 같네요. 🙂

1.4.0 버전에 관한 좀 더 자세한 내용은 릴리즈 노트를 참고해 주세요. 텐서플로는 다음과 같이 pip 명령으로 간단히 설치할 수 있습니다.

$ pip install --upgrade tensorflow
$ pip install --upgrade tensorflow-gpu

TensorFlow Eager Execution

오늘 다이나믹 그래프를 지원하는 텐서플로의 Eager Execution 기능이 소개되었습니다. 올해 초에 명령형imperative 스타일의 기능이 텐서플로에 추가되었습니다(TensorFlow 1.1.0 RC2 and Imperative Style). 다음 버전인 1.5.0에서 이 기능이 빠지고 새롭게 Eager Execution으로 추가되는 것 같습니다.

텐서플로의 텐서는 연산 노드를 가리키는 핸들과 같았습니다(TF의 텐서와 상수, 변수, 플레이스홀더). Eager Execution에서 텐서는 데이터를 직접 포인팅합니다. 그래서 tf.Session을 시작하지 않고 직접 값을 얻을 수 있습니다.

>>> import tensorflow.contrib.eager as tfe
>>> tfe.enable_eager_execution()
>>> c = tf.constant(1)
>>> c
<tf.Tensor: id=0, shape=(), dtype=int32, numpy=1>

텐서 c를 확인해 보면 numpy 속성이 생긴 것을 알 수 있습니다.

>>> c.numpy()
1
>>> type(c)
EagerTensor
>>> type(c.numpy())
numpy.int32

Eager Execution은 NumPy와 호환성을 크게 높인 것 같습니다. 계산 그래프의 생성과 실행 단계가 구분되지 않으므로 파이썬의 조건문으로 계산 그래프를 동적으로 조직할 수 있습니다. 이외에도 그래디언트 계산과 동적 모델에 관한 기능들이 많이 추가되었습니다.

Eager Execution은 한번 설정하면 취소할 수 없어서 파이썬 세션을 새로 시작해야 합니다. 또 tf.data와 tf.layers 밑의 클래스를 사용해야 합니다. 아직 파이토치PyTorch 만큼은 자유도가 높아 보이지는 않지만 점점 다른 프레임워크의 장점을 흡수하는 모습은 기대가 됩니다. 텐서플로에 비하면 파이토치 사용자가 극히 적을 텐데도 상당히 의식하는 것 같습니다. 🙂

… we’re looking for feedback from the community to guide our direction.

텐서플로의 종전 API가 그랬듯이 Eager Execution도 향후에 상당히 변화가 많이 있을 것으로 예상됩니다. 좀 더 자세한 내용은 깃허브의 가이드 문서예제를 참고하세요.

Eager Execution을 사용해 보려면 다음 명령으로 텐서플로의 Nightly 빌드를 설치하면 됩니다.

$ pip install tf-nightly
$ pip install tf-nightly-gpu