인공지능

[빅데이터 직무연구회] 6회차 모임 정리 (2)

Chipmunks 2018. 6. 6.
728x90

[빅데이터 직무연구회] 6회차 모임 정리 (2)

모임 요일 : 5월 24일 목요일 저녁 6시


Chapter 5. 모델 평가와 성능 향상

모델 평가와 매개변수 선택에 대해 더 자세히 배우는 파트다.


두 가지 관점에서 평가 방법을 확장한다. 먼저 안정적인 일반화 성능 측정 방법인 교차 검증이다. 그 다음 score 메서드가 제공하는 정확도와 R^2 값 이외에 분류와 회귀 성능을 측정하는 다른 방법이다.


또한 가장 좋은 일반화 성능을 얻기 위해서 지도 학습 모델의 매개변수를 조정하는 데 유용한 그리드 서치도 있다.


5.1 교차 검증

교차 검증(cross-validation)은 일반화 성능을 재기 위해 훈련 세트와 테스트 세트로 한 번 나누는 것 보다 더 안정적이고 뛰어난 통계적 평가 방법이다. 데이터를 여러 번 반복해서 나누고 여러 모델을 학습한다.


가장 널리 사용되는 교차 검증 방법은 k-겹 교차 검증(k-fold cross-validation)이다. k는 특정 숫자로 보통 5 또는 10을 사용한다.


5-겹 교차 검증을 하려면 데이터를 먼저 폴드(fold)라고 하는 (거의) 비슷한 크기의 '부분 집합' 다섯 개로 나눈다. 그 다음 일련의 모델들을 만든다. 첫 번째 모델은 첫 번째 폴드를 테스트 세트로 사용하고 나머지 (2에서 5까지) 폴드를 훈련 세트로 사용하여 학습한다. 두 번째 모델은 두 번째 폴드를 테스트 세트로 사용하고 나머지 폴드를 훈련 세트로 사용하여 학습한다. 이 과정을 거쳐 다섯 개의 정확도 값을 얻게 된다.


5.1.1 scikit-learn의 교차 검증

scikit-learn에서 교차 검증은 model_selection 모듈의 cross_val_score 함수로 구현되어 있다. cross_val_score 함수의 매개변수는 평가하려는 모델과 훈련 데이터, 타깃 레이블이다.


5.1.2 교차 검증의 장점

테스트 세트에 각 샘플이 정확하게 한 번씩 들어간다. 따라서 교차 검증의 점수를 (그리고 평균값을) 높이기 위해서는 데이터셋에 있는 모든 샘플에 대해 모델이 잘 일반화되어야 한다.

데이터를 여러 개로 나누면 모델이 훈련 데이터에 얼마나 민감한지 알 수 있다. 예제에서는 90~100%의 정확도를 얻었으며, 이 범위는 꽤 넓으며 새로운 데이터를 적용했을 때 최악의 경우와 최선의 경우를 짐작케한다.

교차 검증은 분할을 한 번 했을 때보다 데이터를 더 효과적으로 사용할 수 있다. 더 많은 데이터는 보통 더 정확한 모델을 만든다.

교차 검증의 단점은 연산 비용이 늘어난다는 것이다. 모델을 k개 만들어야 하므로, 대략 k배 느리게 된다.

Note. cross_val_score 함수를 호출하면 내부적으로 여러 모델이 만들어지지만, 새로운 데이터에 적용할 모델을 만드는 방법은 아니다.

5.1.3 계층별 k-겹 교차 검증과 그외 전략들

데이터셋을 나열 순서대로 k개의 폴드로 나누는 것이 항상 좋지는 않다. 클래스를 분류해야 하는 데이터셋의 경우, 클래스가 순서대로 나열되어 있을 경우 k개의 폴드로 나누면, 각 폴드마다 하나의 클래스만을 갖게 되는 경우가 있다.


이런 문제를 해결하기 위해서는 계층별 k-겺 교차 검증(stratified k-fold cross_validation)을 사용한다.

폴드 안의 클래스 비율이 전체 데이터셋의 클래스 비율과 같도록 데이터를 나눈다.


분류기의 일반화 성능을 측정할 때 k-겹 교차 검증보다 더 안정적인 계층별 k-겹 교차 검증을 사용하는 것이 좋다. scikit-learn은 회귀에서 기본 k-겺 교차 검증을 사용한다.


교차 검증 상세 옵션

scikit-learn에서는 cv 매개변수에 폴드의 개수를 직접 넣는것이 아닌, 교차 검증 분할기(cross-validation spliter)를 전달함으로써 데이터를 분할할 때 더 세밀하게 제어할 수 있다. 대부분 회귀에서는 k-겹 교차 검증, 분류에서는 계층별 k-겹 교차 검증의 기본값이 잘 작동한다.


기본값 말고 다른 검증을 사용해야 할 때, cv 매개변수로 전달해주면 된다.


데이터를 섞어서 샘플의 순서를 랜덤으로 만들 때, KFold는 shuffle 매개변수를 True로 주면 된다.


LOOCV

 또 다른 교차 검증 방법이다. LOOCV(Leave-one-out cross-validation)도 자주 사용한다. LOOCV 교차 검증은 폴드 하나에 샘플 하나만 들어 있는 k-겹 교차 검증으로 생각할 수 있다. 각 반복에서 하나의 데이터 포인트를 선택해 테스트 세트로 사용한다. 특히 데이터셋이 클 경우 시간이 매우 오래 걸린다. 그러나 작은 데이터셋에서는 종종 좋은 결과를 만들어낸다.


임의 분할 교차 검증

매우 유연한 교차 검증 전략이다. 임의 분할 교차 검증(shuffle-split cross-validation)에서는 train_size만큼의 포인트로 훈련 세트를 만들고, test_size만큼의 (훈련 세트와 중첩되지 않은) 포인트로 테스트 세트를 만들도록 분할한다. 이 분할은 n_splits 횟수만큼 반복된다.

train_size와 test_size에 정수를 입력하면 데이터 포인트의 절대 개수를 의미한다. 실수를 입력하며 전체 데이터에서의 비율을 나타낸다.

반복 횟수를 훈련 세트나 테스트 세트의 크기와 독립적으로 조절해야할 때 유용하다. train_size와 test_size의 합을 전체와 다르게 함으로써 전체 데이터의 일부만을 사용할 수 있다. 부분 샘플링(subsampling)하는 방식은 대규모 데이터셋으로 작업할 때 도움이 된다.

그룹별 교차 검증

데이터 안에 매우 연관된 그룹이 있을 때도 교차 검증을 사용한다. 데이터셋에 없는 데이터를 구분하는 분류기를 만드는 것이 목표다. 그러나 같은 그룹의 데이터가 훈련 세트와 테스트 세트 모두에 들어 있다면, 원래 목적과 부합하지 않는다. 성능은 과장돼서 나올게 뻔하다.

이런 문제를 해결하기 위해 GroupKFold 를 사용한다. groups 배열에 훈련 세트와 테스트 세트를 만들 때 분리되지 않아야 할 그룹을 지정한다. ( 클래스 레이블과 혼동해서는 안된다. )

5.2. 그리드 서치

앞서 모델의 일반화 성능을 측정하는 법을 배웠다. 이제 매개변수를 튜닝하여 일반화 성능을 개선하는 법이다. 그리드 서치(grid search)는 널리 사용되며, 관심 있는 매개변수들을 대상으로 가능한 모든 조합을 시도해보는 것이다.


5.2.1 간단한 그리드 서치

python의 for 문으로 그리드 서치를 만들 수 있다.


5.2.2 매개변수 과대적합과 검증 세트

매개 변수를 조정하기 위해 테스트 세트를 이미 사용해, 모델이 얼마나 좋은지 평가하는 데는 더 이상 사용할 수 없다. 매우 낙관적인 정확도가 나오기 때문이다. 평가를 위해서는 모델을 만들 때 사용하지 않은 독립된 데이터셋이 필요하다.


데이터를 다시 나눠 세 개의 세트로 만들어 문제를 해결할 수 있다. 훈련 세트로 모델을 만든다. 검증 세트로 모델의 매개변수를 선택한다. 테스트 세트로 선택된 매개변수의 성능을 평가한다.


5.2.3 교차 검증을 사용한 그리드 서치

일반화 성능을 더 잘 평가하려면 훈련 세트와 검증 세트를 한 번만 나누지 않고, 교차 검증을 사용해 각 매개변수 조합의 성능을 평가할 수 있다. 교차 검증을 사용한 그리드 서치를 매개변수 조정 방법으로 널리 사용한다. scikit-learn은 추정기 형태로 구현된 GridSearchCV 를 제공한다. 먼저 딕셔너리 형태로 검색 대상 매개변수를 지정해야 한다.


GridSearchCV 객체의 fit 메서드는 최적의 매개변수를 찾는 일뿐만 아니라, 교차 검증 서능이 가장 좋은 매개변수로 전체 훈련 데이터 세트에 대해 새로운 모델을 자동으로 만들어준다. GridSearchCV는 전체 데이터로 학습한 모델에 접근할 수 있도록 predict와 score 메서드를 제공한다.


찾은 최적 매개변수로 일반화 성능을 평가하려면 테스트 세트를 매개변수로 넘겨 score 메서드를 호출하면 된다. 선택한 매개변수는 best_params_ 속성에 담겨있다. 최상의 교차 검증 정확도 (각 분할에서 얻은 정확도의 평균) best_score_ 에 저장되어 있다.


교차 검증 결과 분석

교차 검증의 결과를 시각화하면 검색 대상 매개변수가 모델의 일반화에 영향을 얼마나 주는지 이해하는 데 도움이 된다. 그리드 서치는 연산 비용이 매우 크므로 비교적 간격을 넓게 하여 적은 수의 그리드로 시작하는 것이 좋다.


그리드 서치의 결과는 검색과 관련한 여러 정보가 함께 저장되어 있는 딕셔녀리인 cv_results_ 속성에 담겨있다. pandas 의 DataFrame 으로 변환해서 보는 것이 좋다. 히트맵으로 표현할 수 있다.


매개 변수의 검색 범위가 적절하게 선택해야 한다. 적절하게 선택되지 않으면 히트맵에서 얻을 수 있는 정보가 한정되어 있다.


교차 검증 점수를 토대로 매개변수 그리드를 튜닝하느 것은 아주 안전한 방법이다. 매개변수들의 중요도를 확인하는 데도 좋다. 그러나 최종 테스트 세트를 대상으로 여러 매개변수 범위를 테스트 해서는 안된다.


비대칭 매개변수 그리드 탐색

SVC의 경우 kernel 매개변수를 가지고 있다. kernel='linear'이면 선형 모델이고 C매개변수만 사용한다. kernel='rbf'이면 C와 gamma를 모두 사용한다. 이런 경우에 C, gamma, kernel 매개변수의 모든 조합을 조사하지 못한다. 조건부 매개변수 조합을 적용하려면, GridSearchCV에 전달할 param_grid를 딕셔너리의 리스트로 만들어주면 된다. 리스트에 있느 각 딕셔너리는 독립적인 그리드로 적용된다.


그리드 서치에 다양한 교차 검증 적용

GridSearchCV는 분류에는 기본적으로 계층형 k-겹 교차 검증을 사용하고 회귀에는 k-겹 교차 검증을 사용한다. GridSearchCV의 cv 매개변수를 통해, 다른 교차 검증 분할기를 사용할 수 있다. 특별히 훈련 세트와 검증 세트로 한 번만 분할하려면, n_splits=1로 하고 ShuffleSplit나 StratifiedShuffleSplit을 사용한다.


위 방법은 데이터셋이 매우 크거나 모델 구축에 시간이 오래 걸릴 때 유용하다.


중첩 교차 검증

GridSearchCV를 사용할 때 여전히 데이터를 훈련 세트와 테스트 세트로 한 번만 나누기 때문에, 결과가 불안정하고 테스트 데이터의 분할에 크게 의존한다. 원본 데이터를 훈련 세트와 테스트 세트로 한 번만 나누는 방식 데신 교차 검증 분할 방식을 사용할 수 있다. 이를 중첩 교차 검증(neste cross-validation)이라고 한다.


바깥쪽 루프에서 데이터를 훈련 세트와 테스트 세트로 나눈다. 그리고 각 훈련 세트에 대해 그리드 서치를 실행한다. 그런 다음 바깥쪽에서 분할된 테스트 세트의 점수를 최적의 매개변수 설정을 사용해 각각 측정한다.


이 방법은 모델이나 매개변수 설정이 아닌 테스트 점수의 목록을 만들어준다. 이 점수들은 그리드 서치를 통해 찾은 최적 매개변수가 모델을 얼마나 잘 일반화시키는지 알려준다. 새로운 데이터에 적용할 모델을 만드는 것이 아니라, 주어진 모델이 얼마나 잘 일반화되었는지 평가하는 데 유용한 방법이다.


GridSearchCV 객체를 모델로 삼아 cross_val_score 함수를 호출하면 된다.


교차 검증과 그리드 서치 병렬화

GridSearchCV와 cross_val_score에서 n_job 매개변수에 사용할 CPU 코어 수를 지정할 수 있다. -1을 집어넣으면 가능한 모든 코어를 사용한다.


5.3 평가 지표와 측정

분류 성능 평가에 정확도(정확하게 분류된 샘플의 비율)를 사용했고, 회귀 성능 평가에는 R^2을 사용했다. 그러나 주어진 데이터셋에 대한 지도 학습 모델의 성능을 재는 방법은 그 외에도 많다. 때에 따라서는 정확도와 R^2이 적합하지 않을 수 있다.

5.3.1 최종 목표를 기억해라

비즈니스 임팩트(buisiness impact) : 어떤 머신러닝 애플리케이션에서 특정 알고리즘을 선택하여 나타난 결과


5.3.2 이진 분류의 평가 지표

양성 클래스와 음성 클래스가 있으며, 우리의 관심 클래스는 양성 클래스다.


에러의 종류

거짓 양성(False Positive) : 잘못된 양성 예측

거짓 음성(False Negative) : 잘못된 음성 예측


일반적으로 거짓 양성의 중요도와 거짓 음성의 중요도가 비슷한 경우는 매우드물다. 상업적인 애플리케이션에서는 두 오류를 비용으로 화산하여, 예측 오류로 인한 금전적 손해를 측정한 값을 정확도 대신 사용하기도 한다. 이런 방식이 어떤 모델을 사용할지 비즈니스 관점에서 판단하는 데 더 도움이 될 수 있다.


불균형 데이터셋

위 두 종류의 에러(거짓 양성과 거짓 음성)는 두 클래스 중 하나가 다른 것보다 훨씬 많을 때 더 중요하다. 실제로 이는 매우 흔한 상황이다. 이를 불균형 데이터셋(imbalanced dtasets) 이라고 한다.


99% 정확도로 예측하는 분류기를 만들었다고 가정하면, 정확도는 꽤 높아보이지만 불균형 클래스를 고려하지 못했다. 따라서 정확도로는 잘못된 모델인지, 정말 좋은 모델인지 구분하기 어렵다.


오차 행렬

오차 행렬(confusion matrix)은 이진 분류 평가 결과를 나타낼 때 가자 널리 사용하는 방법 중 하나다.


오차 행렬의 대각 행렬은 정확히 분류된 경우다. 다른 항목은 하 클래스의 샘플들이 다르 클래스로 잘못 분류된 경우가 얼마나 많은지를 알려준다. 이들을 축약해 FP, FN, TP(True Positive), TN(True Negative) 이라고 쓴다.


정확도와의 관계


정확히 예측한 수 TP + TN 을 전체 샘플 수로 나눈 것이다.


정밀도, 재현율, f-점수

오차 행렬의 결과를 요약하는 여러 방법 중 가장 일반적인 것은 정밀도(precision)과 재현율(recall) 이다.



정밀도는 거짓 양성 (FP) 의 수를 줄이는 것이 목표일 때 성능 지표로 사용한다. 정밀도를 양성 예측도(PPV)라고 한다.



재현율은 모든 양성 샘플을 식별해야 할 때 성능 지표로 사용한다. 즉 거짓 음성(FN)을 피하는 것이 중요할 때다. 재현율을 민감도(sensitivity), 적중률(hit rate), 진짜 양성 비율(TPR) 이라고도 한다.




정밀도와 재현율의 조화 평균인 f-점수(f-score) 또는 f-측정(f-measure)은 이 둘을 하나로 요약해준다. 특별히 이 공식을 f1-점수라고도 한다. 어떤 모델이 좋은지 직관적으로 판단하는 데는 정확도보다 f1-점수가 낫다. 그러나 f1-점수는 정확도보다 이해하거나 설명하기 어렵다는 게 단점이다.


classification_report 함수는 정밀도, 재현율, f1-점수 모두를 한 번에 계산해서 깔끔하게 출력한다.


Note, f-점수의 일반화된 가주치 조화 평균 공식은 정밀도를 P, 재현율을 R이라고 할 때, 다음과 같다.


f1은 β = 5일 때, 즉 정밀도와 재현율의 가중치가 동일한 α = 0.5 일 때 점수를 말한다. β가 1보다 크면 재현율이 강조되고 1보다 작으면 정밀도가 강조된다.


불확실성 고려

대부분의 분류기는 예측의 확신을 가늠하기 위한 decision_funcion이나 predict_proba 메서드를 제공한다. 예측을 만들어내는 것은 decision_function이나 predict_proba 출력의 임계값을 검증하는 것이다. 이진 탐색에서 decision_function은 0을, predict_proba는 0.5를 임계값으로 사용한다.


재현율보다 정밀도가 중요하거나 그 반대, 또는 데이터가 심하게 불균형일 때 결정 함수의 임계값을 바꾸면 더 나은 결과를 쉽게 얻을 수 있다. decision_function은 임의의 범위를 가지고 있으므로 임계점을 고르는 일반적인 방법을 제시하기는 어렵다.


predict_proba 메서드는 출력이 0에서 1 사이로 고정되니 임계값을 선택하기가 더 쉽다. 기본값인 0.5를 임계값으로 설정한 모델은 양성 클래스라는 확신이 50% 이상일 때 양성으로 분류한다. 임계값을 높이면 양성 클래스를 분류할 때 더 큰 확신이 필요하다. 반대로 음성 클래스는 확신이 덜 필요하다.


임의의 임계값보다는 확률을 사용하는 쪽이 더 직관적이지만, 모든 모델이 쓸모 있는 불확실성을 제공하는 것은 아니다. 최대 깊이까지 자란 DecisionTree는 비록 잘못된 것이라도 항상 100% 확신하다.


이는 보정(calibration) 개념과 관련이 있다. 보정된 모델은 불확실성을 정확하게 측정하는 모델이다.


정밀도-재현율 곡선과 ROC 곡선

모델의 분류 작업을 결정하는 임계값을 바꾸는 것은 해당 분류기의 정밀도와 재현율의 상충 관계를 조정하는 일이다. 분류기의 필요 조건을 지정하는 것을 종종 운영 포인트(Operating Point)를 지정한다고 말한다. 운영 포인트를 고정하면 비즈니스 목표를 설정할 때 고객이나 조직 내 다른 그룹에 성능을 보장하는 데 도움이 된다.


새로운 모델을 만들 때는 운영 포인트가 명확하지 않은 경우가 많다. 모든 임계값을 조사해보거나, 한 번에 정밀도나 재현율의 모든 장단점을 살펴보는 것이 좋다. 이를 위해 정밀도-재현율 곡선(precision-recall curve)을 사용한다.


sklearn.merics 모듈에서 정밀도-재현율 곡선을 만드는 함수를 제공한다. 이 함수는 타깃 레이블과 decision_function이나 predict_proba 메서드로 계산한 예측 불확실성을 이용한다.


precision_recall_curve 함수는 가능한 모든 임계값(결정 함수에 나타난 모든 값)에 대해 정밀도와 재현율의 값을 정렬된 리스트로 반환한다.


곡선이 오른쪽 위로 갈수록 더 좋은 분류기다. 오른쪽 위 지점은 한 임계값에서 정밀도와 재현율이 모두 높은 곳이다. 곡선은 임계값이 매우 낮아 전부 양성 클래스가 되는 왼쪽 위에서 시작한다. 임계값이 커지면서 곡선은 정밀도가 높아지는 쪽으로 이동하지만 재현율은 낮아진다. 임계값을 높일수록 양성으로 분류된 포인트 대부분이 진짜 양성(TP)이 되며, 정밀도가 매우 높아지지만 재현율은 낮아진다. 정밀도가 높아져도 재현율이 높게 유지될수록 더 좋은 ㅗ델이다.


RandomFroestClassifier 은 decision_function을 제공하지 않고 predict_proba만 가지고 있다. 정밀도-재현율 곡선 함수는 양성 클래스(클래스 1)의 확신에 대한 측정값을 두 번째 매개변수로 받는다. 그래서 샘플이 1일 확률, 즉 rf.predict_proba(X_test)[:, 1]을 넘겨줘야 한다.


정밀도-재현율 곡선을 비교하면 많은 통찰을 얻을 순 있다. 그러나 수작업이다. 모델을 자동으로 비교하려면 특정 임계값이나 운영 포인트에 국한하지 않고 전체 곡선에 담긴 정보를 요약해야 한다. 이러한 요약 방법의 하나로 정밀도-재현율 곡선의 아랫부분 면적을 계산할 수 있으며, 이를 평균 정밀도(average precision)이라고 한다.


average_precision_score 함수가 평균 정밀도를 계산해준다, decision_function이나 predict_proba 함수의 결괏값을 average_precision_score 함수로 전달해야 한다.


ROC와 AUC

ROC 곡선은 여러 임계값에서 분류기의 특성을 분석하는데 널리 사용하는 도구다. 정밀도와 재현율 대신 진짜 양성 비율(TPR)에 대한 거짓 양성 비율(FPR)을 나타낸다. 진짜 양성 비율은 재현율의 다른 이름이며, 거짓 양성 비율은 전체 음성 샘플 중에서 거짓 양성으로 잘못 분류한 비율이다.


ROC 곡선은 roc_curve 함수를 사용하여 만들 수 있다. ROC 곡선은 왼쪽 위에 가까울수록 이상적이다. 거짓 양성 비율(FPR)이 낮게 유지되면서 재현율이 높은 분류기가 좋은 것이다. 왼쪽 위에 가장 가까운 지점이 기본값으로 찾은 것보다 더 좋은 운영 포인트이다.


정밀도-재현율 곡선에서처럼 곡선 아래의 면적값 하나로 ROC 곡선을 요약할 때가 많다. 이를 보통 AUC(area under the curve)라고 하며, 여기서 곡선(curve)는 ROC 곡선을 말한다. ROC 곡선 아래 면적은 roc_auc_score 함수로 계산한다.


불균형한 데이터셋에서는 정확도보다 AUC가 훨씬 좋은 지표다. AUC는 양성 샘플의 순위를 평가하는 것으로 볼 수 있다. 분류기에서 무작위로 선택한 양성 클래스 포인트의 점수가 무작위로 선택한 음성 클래스 포인트의 점수보다 높을 확률과 같다.


5.3.3 다중 분류의 평가 지표

다중 분류의 정확도도 정확히 분류된 샘플의 비율로 정의한다. 이진 분류와 다른 점은 모든 클래스에 대해 평균을 낸 것이다. 그래서 클래스가 불균형할 때는 정확도는 좋은 평가 방법이 되지 못한다.


다중 분류에서 불균형 데이터셋을 위해 가장 널리 사용하는 평가 지표는 f1-점수의 다중 분류 버전이다. 다중 클래스용 f1-점수는 한 클래스를 양성 클래스로 두고 나머지 클래스들을 음성 클래스로 간주하여 클래스마다 f1-점수를 계산한다. 클래스별 f1-점수를 다음 전략 중 하나를 사용하여 평균을 낸다.


  • "macro" 평균 : 클래스별 f1-점수에 가중치를 주지 않는다. 클래스 크기에 상관없이 모든 클래스를 같은 비중으로 다룬다.
  • "weighted" 평균 : 클래스별 샘플 수로 가중치를 두어 f1-점수의 평균을 계산한다. 이 값이 분류 리포트에 나타나는 값이다.
  • "micro" 평균 : 모든 클래스의 거짓 양성(FP), 거짓 음성(FN), 진짜 양성(TP)의 총 수를 헤아린 다음 정밀도, 재현율, f1-점수를 이 수치로 계산한다.


각 샘플을 똑같이 간주한다면 "micro" 평균 f1-점수를 추천한다. 각 클래스를 동일한 비중으로 고려한다면 "macro" 평균 f1-점수를 추천한다.


5.3.4 회귀의 평가 지표

대부분 회귀 추정기의 score 메서드에서 이용하는 R^2 만으로 충분하다. 가끔 평균 제곱 에러나 평균 절댓값 ㅔ러를 사용하여 모델을 튜닝할 때 이런 지표를 기반으로 비즈니스 결정을 할 수 있다. 그러나 일반적으로 R^2이 회귀 모델을 평가하는 데 더 나은 지표이다.


5.3.5 모델 선택에서 평가 지표 사용하기

GridSearchCV나 cross_val_score를 사용하여 모델을 선택할 때, AUC 같은 평가 지표를 사용하고 싶은 경우가 많다. scikit-learn 에서 GridSearchCV와 cross_val_score의 scoring 매개변수를 통해 손쉽게 구현할 수 있다. 사용하려는 평가 지표를 문자열로 넘겨주기만 하면 된다. scoring 매개변수의 기본값은 정확도다.


from IPython.display import display
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import mglearn

# 폰트 관련 자료
# https://programmers.co.kr/learn/courses/21/lessons/950
import matplotlib
matplotlib.rc("font", family="NanumGothicCoding")
In [2]:
from sklearn.datasets import make_blobs
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

X, y = make_blobs(random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
logreg = LogisticRegression().fit(X_train, y_train)
print("테스트 세트 점수: {:.2f}".format(logreg.score(X_test, y_test)))
테스트 세트 점수: 0.88
In [3]:
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression

iris = load_iris()
logreg = LogisticRegression()

scores = cross_val_score(logreg, iris.data, iris.target)
print("교차 검증 점수: {}".format(scores))
교차 검증 점수: [0.96078431 0.92156863 0.95833333]
In [4]:
scores = cross_val_score(logreg, iris.data, iris.target, cv=5)
print("교차 검증 점수: {}".format(scores))
교차 검증 점수: [1.         0.96666667 0.93333333 0.9        1.        ]
In [5]:
print("교차 검증 평균 점수: {:.2f}".format(scores.mean()))
교차 검증 평균 점수: 0.96
In [6]:
from sklearn.datasets import load_iris
iris = load_iris()
print("Iris 레이블:\n{}".format(iris.target))
Iris 레이블:
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]
In [7]:
mglearn.plots.plot_stratified_cross_validation()
/usr/local/lib/python3.6/site-packages/mglearn/plot_cross_validation.py:121: MatplotlibDeprecationWarning: The *bottom* kwarg to `barh` is deprecated use *y* instead. Support for *bottom* will be removed in Matplotlib 3.0
  color=colors, hatch="//", edgecolor='k', align='edge')
/usr/local/lib/python3.6/site-packages/mglearn/plot_cross_validation.py:125: MatplotlibDeprecationWarning: The *bottom* kwarg to `barh` is deprecated use *y* instead. Support for *bottom* will be removed in Matplotlib 3.0
  color="w", edgecolor='k', align='edge')
/usr/local/lib/python3.6/site-packages/mglearn/plot_cross_validation.py:158: MatplotlibDeprecationWarning: The *bottom* kwarg to `barh` is deprecated use *y* instead. Support for *bottom* will be removed in Matplotlib 3.0
  height=.6, color="grey", hatch="//", edgecolor='k', align='edge')
/usr/local/lib/python3.6/site-packages/mglearn/plot_cross_validation.py:163: MatplotlibDeprecationWarning: The *bottom* kwarg to `barh` is deprecated use *y* instead. Support for *bottom* will be removed in Matplotlib 3.0
  hatch="//", edgecolor='k', align='edge')
/usr/local/lib/python3.6/site-packages/mglearn/plot_cross_validation.py:167: MatplotlibDeprecationWarning: The *bottom* kwarg to `barh` is deprecated use *y* instead. Support for *bottom* will be removed in Matplotlib 3.0
  edgecolor='k', align='edge')
/usr/local/lib/python3.6/site-packages/mglearn/plot_cross_validation.py:171: MatplotlibDeprecationWarning: The *bottom* kwarg to `barh` is deprecated use *y* instead. Support for *bottom* will be removed in Matplotlib 3.0
  align='edge')
/usr/local/lib/python3.6/site-packages/mglearn/plot_cross_validation.py:175: MatplotlibDeprecationWarning: The *bottom* kwarg to `barh` is deprecated use *y* instead. Support for *bottom* will be removed in Matplotlib 3.0
  color="w", edgecolor='k', align='edge')
In [8]:
from sklearn.model_selection import KFold
kfold = KFold(n_splits=5)
In [9]:
print("교차 검증 점수:\n{}".format(cross_val_score(logreg, iris.data, iris.target, cv=kfold)))
교차 검증 점수:
[1.         0.93333333 0.43333333 0.96666667 0.43333333]
In [10]:
kfold = KFold(n_splits=3)
print("교차 검증 점수:\n{}".format(cross_val_score(logreg, iris.data, iris.target, cv=kfold)))
교차 검증 점수:
[0. 0. 0.]
In [11]:
kfold = KFold(n_splits=3, shuffle=True, random_state=0)
print("교차 검증 점수:\n{}".format(cross_val_score(logreg, iris.data, iris.target, cv=kfold)))
교차 검증 점수:
[0.9  0.96 0.96]
In [12]:
from sklearn.model_selection import LeaveOneOut
loo = LeaveOneOut()
scores = cross_val_score(logreg, iris.data, iris.target, cv=loo)
print("교차 검증 분할 횟수: ", len(scores))
print("평균 정확도: {:.2f}".format(scores.mean()))
교차 검증 분할 횟수:  150
평균 정확도: 0.95
In [13]:
mglearn.plots.plot_shuffle_split()
/usr/local/lib/python3.6/site-packages/mglearn/plot_cross_validation.py:85: MatplotlibDeprecationWarning: The *bottom* kwarg to `barh` is deprecated use *y* instead. Support for *bottom* will be removed in Matplotlib 3.0
  hatch="//", edgecolor='k', align='edge')
In [14]:
from sklearn.model_selection import ShuffleSplit
shuffle_split = ShuffleSplit(test_size=.5, train_size=.5, n_splits=10)
scores = cross_val_score(logreg, iris.data, iris.target, cv=shuffle_split)
print("교차 검증 점수:\n{}".format(scores))
교차 검증 점수:
[0.90666667 0.90666667 0.90666667 0.98666667 0.89333333 0.93333333
 0.92       0.96       0.96       0.93333333]
In [15]:
from sklearn.model_selection import GroupKFold
X, y = make_blobs(n_samples=12, random_state=0)
groups = [0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3]
scores = cross_val_score(logreg, X, y, groups, cv=GroupKFold(n_splits=3))
print("교차 검증 점수:\n{}".format(scores))
교차 검증 점수:
[0.75       0.8        0.66666667]
In [16]:
# 그리드 서치
from sklearn.svm import SVC
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=0)
print("훈련 세트의 크기: {} 테스트 세트의 크기: {}".format(X_train.shape[0], X_test.shape[0]))

best_score = 0

for gamma in [0.001, 0.01, 0.1, 1, 10, 100]:
    for C in [0.001, 0.01, 0.1, 1, 10, 100]:
        svm = SVC(gamma=gamma, C=C)
        svm.fit(X_train, y_train)
        score = svm.score(X_test, y_test)
        
        if score > best_score:
            best_score = score
            best_parameters = {'C': C, 'gamma': gamma}

print("최고 점수: {:.2f}".format(best_score))
print("최적 매개변수: {}".format(best_parameters))
훈련 세트의 크기: 112 테스트 세트의 크기: 38
최고 점수: 0.97
최적 매개변수: {'C': 100, 'gamma': 0.001}
In [17]:
mglearn.plots.plot_threefold_split()
In [18]:
from sklearn.svm import SVC
X_trainval, X_test, y_trainval, y_test = train_test_split(iris.data, iris.target, random_state=0)
X_train, X_valid, y_train, y_valid = train_test_split(X_trainval, y_trainval, random_state=1)
print("훈련 세트의 크기: {} 검증 세트의 크기: {} 테스트 세트의 크기: {}\n".format(X_train.shape[0], X_valid.shape[0], X_test.shape[0]))

best_score = 0

for gamma in [0.001, 0.01, 0.1, 1, 10, 100]:
    for C in [0.001, 0.01, 0.1, 1, 10, 100]:
        svm = SVC(gamma=gamma, C=C)
        svm.fit(X_train, y_train)
        score = svm.score(X_valid, y_valid)
        
        if score > best_score:
            best_score = score
            best_parameters = {'C': C, 'gamma': gamma}
    
svm = SVC(**best_parameters)
svm.fit(X_trainval, y_trainval)
test_score = svm.score(X_test, y_test)
print("검증 세트에서 최고 점수: {:.2f}".format(best_score))
print("최적 매개변수: ", best_parameters)
print("최적 매개변수에서 테스트 세트 점수: {:.2f}".format(test_score))
훈련 세트의 크기: 84 검증 세트의 크기: 28 테스트 세트의 크기: 38

검증 세트에서 최고 점수: 0.96
최적 매개변수:  {'C': 10, 'gamma': 0.001}
최적 매개변수에서 테스트 세트 점수: 0.92
In [19]:
for gamma in [0.001, 0.01, 0.1, 1, 10, 100]:
    for C in [0.001, 0.01, 0.1, 1, 10, 100]:
        svm = SVC(gamma=gamma, C=C)
        scores = cross_val_score(svm, X_trainval, y_trainval, cv=5)
        score = np.mean(scores)
        
        if score > best_score:
            best_score = score
            best_parameters = {'C': C, 'gamma': gamma}

svm = SVC(**best_parameters)
svm.fit(X_trainval, y_trainval)
Out[19]:
SVC(C=100, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma=0.01, kernel='rbf',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)
In [20]:
mglearn.plots.plot_cross_val_selection()
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('mean_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split0_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split1_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split2_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split3_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split4_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('std_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
In [21]:
mglearn.plots.plot_grid_search_overview()
In [22]:
param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}
print("매개변수 그리드:\n{}".format(param_grid))
매개변수 그리드:
{'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}
In [23]:
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
grid_search = GridSearchCV(SVC(), param_grid, cv=5)
In [24]:
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=0)
In [25]:
grid_search.fit(X_train, y_train)
Out[25]:
GridSearchCV(cv=5, error_score='raise',
       estimator=SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma='auto', kernel='rbf',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False),
       fit_params=None, iid=True, n_jobs=1,
       param_grid={'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100]},
       pre_dispatch='2*n_jobs', refit=True, return_train_score='warn',
       scoring=None, verbose=0)
In [26]:
print("테스트 세트 점수: {:.2f}".format(grid_search.score(X_test, y_test)))
테스트 세트 점수: 0.97
In [27]:
print("최적 매개변수: {}".format(grid_search.best_params_))
print("최상 교차 검증 점수: {:.2f}".format(grid_search.best_score_))
최적 매개변수: {'C': 100, 'gamma': 0.01}
최상 교차 검증 점수: 0.97
In [28]:
print("최고 성능 모델:\n{}".format(grid_search.best_estimator_))
최고 성능 모델:
SVC(C=100, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape='ovr', degree=3, gamma=0.01, kernel='rbf',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)
In [29]:
import pandas as pd
results = pd.DataFrame(grid_search.cv_results_)
display(results.head())
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('mean_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split0_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split1_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split2_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split3_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split4_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('std_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
mean_fit_timemean_score_timemean_test_scoremean_train_scoreparam_Cparam_gammaparamsrank_test_scoresplit0_test_scoresplit0_train_score...split2_test_scoresplit2_train_scoresplit3_test_scoresplit3_train_scoresplit4_test_scoresplit4_train_scorestd_fit_timestd_score_timestd_test_scorestd_train_score
00.0007920.0003650.3660710.3660790.0010.001{'C': 0.001, 'gamma': 0.001}220.3750.363636...0.3636360.3666670.3636360.3666670.3809520.3626370.0001240.0000550.0113710.002852
10.0007540.0003540.3660710.3660790.0010.01{'C': 0.001, 'gamma': 0.01}220.3750.363636...0.3636360.3666670.3636360.3666670.3809520.3626370.0000690.0000750.0113710.002852
20.0006560.0002800.3660710.3660790.0010.1{'C': 0.001, 'gamma': 0.1}220.3750.363636...0.3636360.3666670.3636360.3666670.3809520.3626370.0000200.0000040.0113710.002852
30.0007140.0003020.3660710.3660790.0011{'C': 0.001, 'gamma': 1}220.3750.363636...0.3636360.3666670.3636360.3666670.3809520.3626370.0000460.0000170.0113710.002852
40.0006570.0002660.3660710.3660790.00110{'C': 0.001, 'gamma': 10}220.3750.363636...0.3636360.3666670.3636360.3666670.3809520.3626370.0000140.0000080.0113710.002852

5 rows × 22 columns

In [30]:
scores = np.array(results.mean_test_score).reshape(6, 6)

mglearn.tools.heatmap(scores, xlabel='gamma', xticklabels=param_grid['gamma'], ylabel='C', yticklabels=param_grid['C'], cmap="viridis")
Out[30]:
<matplotlib.collections.PolyCollection at 0x10b9feef0>
In [31]:
fig, axes = plt.subplots(1, 3, figsize=(13, 5))
param_grid_linear = {'C': np.linspace(1, 2, 6), 'gamma': np.linspace(1, 2, 6)}
param_grid_one_log = {'C': np.linspace(1, 2, 6), 'gamma': np.logspace(-3, 2, 6)}
param_grid_range = {'C': np.logspace(-3, 2, 6), 'gamma': np.logspace(-7, -2, 6)}

for param_grid, ax in zip([param_grid_linear, param_grid_one_log, param_grid_range], axes):
    grid_search = GridSearchCV(SVC(), param_grid, cv=5)
    grid_search.fit(X_train, y_train)
    scores = grid_search.cv_results_['mean_test_score'].reshape(6, 6)
    
    scores_image = mglearn.tools.heatmap(scores, xlabel='gamma', ylabel='C', xticklabels=param_grid['gamma'], yticklabels=param_grid['C'], cmap="viridis", ax=ax)
    
plt.colorbar(scores_image, ax=axes.tolist())
Out[31]:
<matplotlib.colorbar.Colorbar at 0x10b3e1390>
In [32]:
param_grid = [{'kernel': ['rbf'], 'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100]},
             {'kernel': ['linear'], 'C': [0.001, 0.01, 0.1, 1, 10, 100]}]

print("그리도 목록:\n{}".format(param_grid))
그리도 목록:
[{'kernel': ['rbf'], 'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}, {'kernel': ['linear'], 'C': [0.001, 0.01, 0.1, 1, 10, 100]}]
In [33]:
grid_search = GridSearchCV(SVC(), param_grid, cv=5)
grid_search.fit(X_train, y_train)
print("최적 매개변수: {}".format(grid_search.best_params_))
print("최고 교차 검증 점수: {:.2f}".format(grid_search.best_score_))
최적 매개변수: {'C': 100, 'gamma': 0.01, 'kernel': 'rbf'}
최고 교차 검증 점수: 0.97
In [34]:
results = pd.DataFrame(grid_search.cv_results_)
display(results.T)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('mean_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split0_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split1_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split2_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split3_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('split4_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/usr/local/lib/python3.6/site-packages/sklearn/utils/deprecation.py:122: FutureWarning: You are accessing a training score ('std_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
0123456789...32333435363738394041
mean_fit_time0.0007113930.0008055690.0006347660.0007400040.0007111550.0006247520.0006726260.0008929250.0006350520.000608206...0.0003821370.0004719730.0008035180.0008537290.0004589560.0004187580.000363350.0003669260.0003603940.000358534
mean_score_time0.0002735140.0003239630.0002689840.0003237720.0002863880.0002718930.0002798560.0003860470.0002836230.00026722...0.0002290250.0002397060.0002508160.0002585890.0002332210.0002339840.0002254490.0002254010.000219250.000222015
mean_test_score0.3660710.3660710.3660710.3660710.3660710.3660710.3660710.3660710.3660710.366071...0.9553570.9464290.9196430.56250.3660710.8482140.9464290.9732140.9642860.964286
mean_train_score0.3660790.3660790.3660790.3660790.3660790.3660790.3660790.3660790.3660790.366079...0.9887881110.3660790.8550690.9665380.9843680.9888130.993258
param_C0.0010.0010.0010.0010.0010.0010.010.010.010.01...1001001001000.0010.010.1110100
param_gamma0.0010.010.11101000.0010.010.11...0.1110100NaNNaNNaNNaNNaNNaN
param_kernelrbfrbfrbfrbfrbfrbfrbfrbfrbfrbf...rbfrbfrbfrbflinearlinearlinearlinearlinearlinear
params{'C': 0.001, 'gamma': 0.001, 'kernel': 'rbf'}{'C': 0.001, 'gamma': 0.01, 'kernel': 'rbf'}{'C': 0.001, 'gamma': 0.1, 'kernel': 'rbf'}{'C': 0.001, 'gamma': 1, 'kernel': 'rbf'}{'C': 0.001, 'gamma': 10, 'kernel': 'rbf'}{'C': 0.001, 'gamma': 100, 'kernel': 'rbf'}{'C': 0.01, 'gamma': 0.001, 'kernel': 'rbf'}{'C': 0.01, 'gamma': 0.01, 'kernel': 'rbf'}{'C': 0.01, 'gamma': 0.1, 'kernel': 'rbf'}{'C': 0.01, 'gamma': 1, 'kernel': 'rbf'}...{'C': 100, 'gamma': 0.1, 'kernel': 'rbf'}{'C': 100, 'gamma': 1, 'kernel': 'rbf'}{'C': 100, 'gamma': 10, 'kernel': 'rbf'}{'C': 100, 'gamma': 100, 'kernel': 'rbf'}{'C': 0.001, 'kernel': 'linear'}{'C': 0.01, 'kernel': 'linear'}{'C': 0.1, 'kernel': 'linear'}{'C': 1, 'kernel': 'linear'}{'C': 10, 'kernel': 'linear'}{'C': 100, 'kernel': 'linear'}
rank_test_score27272727272727272727...9111724272111133
split0_test_score0.3750.3750.3750.3750.3750.3750.3750.3750.3750.375...0.9583330.9166670.8750.5416670.3750.9166670.95833310.9583330.958333
split0_train_score0.3636360.3636360.3636360.3636360.3636360.3636360.3636360.3636360.3636360.363636...0.9886361110.3636360.8863640.9659090.9886360.9886360.988636
split1_test_score0.3478260.3478260.3478260.3478260.3478260.3478260.3478260.3478260.3478260.347826...110.9565220.4782610.3478260.8260870.9130430.95652211
split1_train_score0.3707870.3707870.3707870.3707870.3707870.3707870.3707870.3707870.3707870.370787...0.9775281110.3707870.887640.9775280.9775280.9887640.988764
split2_test_score0.3636360.3636360.3636360.3636360.3636360.3636360.3636360.3636360.3636360.363636...1110.5909090.3636360.8181821111
split2_train_score0.3666670.3666670.3666670.3666670.3666670.3666670.3666670.3666670.3666670.366667...0.9777781110.3666670.8666670.9444440.9777780.9777780.988889
split3_test_score0.3636360.3636360.3636360.3636360.3636360.3636360.3636360.3636360.3636360.363636...0.8636360.8636360.8181820.5909090.3636360.7727270.9090910.9545450.9090910.909091
split3_train_score0.3666670.3666670.3666670.3666670.3666670.3666670.3666670.3666670.3666670.366667...11110.3666670.7555560.9777780.9888890.9888891
split4_test_score0.3809520.3809520.3809520.3809520.3809520.3809520.3809520.3809520.3809520.380952...0.9523810.9523810.9523810.6190480.3809520.9047620.9523810.9523810.9523810.952381
split4_train_score0.3626370.3626370.3626370.3626370.3626370.3626370.3626370.3626370.3626370.362637...11110.3626370.8791210.9670330.98901111
std_fit_time0.0001933580.000181644.44405e-058.99021e-055.48786e-051.5099e-050.0001113387.6424e-053.7349e-051.79046e-05...2.40098e-051.04824e-052.96393e-051.60344e-052.11643e-061.08317e-063.75462e-062.42433e-051.98181e-052.85905e-05
std_score_time2.40121e-053.72136e-051.07721e-053.17073e-052.4869e-051.26111e-052.2487e-053.28504e-051.49621e-051.43277e-05...1.24532e-051.68085e-053.84734e-066.14658e-061.48816e-062.67029e-061.17383e-066.26565e-062.50328e-067.17224e-06
std_test_score0.01137080.01137080.01137080.01137080.01137080.01137080.01137080.01137080.01137080.0113708...0.04956620.05192270.06479060.04966780.01137080.05477830.03321850.02239950.03383870.0338387
std_train_score0.002851760.002851760.002851760.002851760.002851760.002851760.002851760.002851760.002851760.00285176...0.009994510000.002851760.05031140.01213160.005485070.007028010.00550551

23 rows × 42 columns

In [35]:
param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}
scores = cross_val_score(GridSearchCV(SVC(), param_grid, cv=5), iris.data, iris.target, cv=5)

print("교차 검증 점수: ", scores)
print("교차 검증 평균 점수: ", scores.mean())
교차 검증 점수:  [0.96666667 1.         0.96666667 0.96666667 1.        ]
교차 검증 평균 점수:  0.9800000000000001
In [36]:
def nested_cv(X, y, inner_cv, outer_cv, Classifier, parameter_grid):
    outer_scores = []
    
    # outer_cv의 분할을 순회하는 for 루프
    # (split 메서드는 훈련과 테스트 세트에 해당하는 인덱스를 반환합니다.)
    for training_samples, test_samples in outer_cv.split(X, y):
        best_params = {}
        best_score = -np.inf
        
        for parameters in parameter_grid:
            cv_scores = []
            for inner_train, inner_test in inner_cv.split(X[training_samples], y[training_samples]):
                clf = Classifier(**parameters)
                clf.fit(X[inner_train], y[inner_train])
                score = clf.score(X[inner_test], y[inner_test])
                cv_scores.append(score)
                
            mean_score = np.mean(cv_scores)
            if mean_score > best_score:
                best_score = mean_score
                best_params = parameters
                
        clf = Classifier(**best_params)
        clf.fit(X[training_samples], y[training_samples])
        
        outer_scores.append(clf.score(X[test_samples], y[test_samples]))
    return np.array(outer_scores)
In [37]:
from sklearn.model_selection import ParameterGrid, StratifiedKFold
scores = nested_cv(iris.data, iris.target, StratifiedKFold(5), StratifiedKFold(5), SVC, ParameterGrid(param_grid))
print("교차 검증 점수: {}".format(scores))
교차 검증 점수: [0.96666667 1.         0.96666667 0.96666667 1.        ]
In [38]:
from sklearn.datasets import load_digits

digits = load_digits()
y = digits.target == 9
X_train, X_test, y_train, y_test = train_test_split(digits.data, y, random_state=0)
In [39]:
from sklearn.dummy import DummyClassifier
dummy_majority = DummyClassifier(strategy='most_frequent').fit(X_train, y_train)
pred_most_frequent = dummy_majority.predict(X_test)
print("예측된 유니크 레이블: {}".format(np.unique(pred_most_frequent)))
print("테스트 점수: {:.2f}".format(dummy_majority.score(X_test, y_test)))
예측된 유니크 레이블: [False]
테스트 점수: 0.90
In [40]:
from sklearn.tree import DecisionTreeClassifier
tree = DecisionTreeClassifier(max_depth=2).fit(X_train, y_train)
pred_tree = tree.predict(X_test)
print("테스트 점수: {:.2f}".format(tree.score(X_test, y_test)))
테스트 점수: 0.92
In [41]:
from sklearn.linear_model import LogisticRegression

dummy = DummyClassifier().fit(X_train, y_train)
pred_dummy = dummy.predict(X_test)
print("dummy 점수: {:.2f}".format(dummy.score(X_test, y_test)))

logreg = LogisticRegression(C=0.1).fit(X_train, y_train)
pred_logreg = logreg.predict(X_test)
print("logreg 점수: {:.2f}".format(logreg.score(X_test, y_test)))
dummy 점수: 0.79
logreg 점수: 0.98
In [42]:
from sklearn.metrics import confusion_matrix

confusion = confusion_matrix(y_test, pred_logreg)
print("오차 행렬:\n{}".format(confusion))
오차 행렬:
[[401   2]
 [  8  39]]
In [43]:
mglearn.plots.plot_confusion_matrix_illustration()
In [44]:
print("빈도 기반 더미 모델")
print(confusion_matrix(y_test, pred_most_frequent))
print("\n무작위 더미 모델:")
print(confusion_matrix(y_test, pred_dummy))
print("\n결정 트리:")
print(confusion_matrix(y_test, pred_tree))
print("\n로지스틱 회귀")
print(confusion_matrix(y_test, pred_logreg))
빈도 기반 더미 모델
[[403   0]
 [ 47   0]]

무작위 더미 모델:
[[363  40]
 [ 39   8]]

결정 트리:
[[390  13]
 [ 24  23]]

로지스틱 회귀
[[401   2]
 [  8  39]]
In [45]:
from sklearn.metrics import f1_score
print("빈도 기반 더미 모델의 f1 score: {:.2f}".format(f1_score(y_test, pred_most_frequent)))
print("무작위 더미 모델의 f1 score: {:.2f}".format(f1_score(y_test, pred_dummy)))
print("트리 모델의 f1 score: {:.2f}".format(f1_score(y_test, pred_tree)))
print("로지스틱 회귀 모델의 f1 score: {:.2f}".format(f1_score(y_test, pred_logreg)))
빈도 기반 더미 모델의 f1 score: 0.00
무작위 더미 모델의 f1 score: 0.17
트리 모델의 f1 score: 0.55
로지스틱 회귀 모델의 f1 score: 0.89
/usr/local/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 due to no predicted samples.
  'precision', 'predicted', average, warn_for)
In [46]:
from sklearn.metrics import classification_report
print(classification_report(y_test, pred_most_frequent, target_names=["9 아님", "9"]))
             precision    recall  f1-score   support

       9 아님       0.90      1.00      0.94       403
          9       0.00      0.00      0.00        47

avg / total       0.80      0.90      0.85       450

/usr/local/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)
In [47]:
print(classification_report(y_test, pred_logreg, target_names=["9 아님", "9"]))
             precision    recall  f1-score   support

       9 아님       0.98      1.00      0.99       403
          9       0.95      0.83      0.89        47

avg / total       0.98      0.98      0.98       450

In [48]:
from mglearn.datasets import make_blobs
X, y = make_blobs(n_samples=(400, 50), centers=2, cluster_std=[7.0, 2], random_state=22)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
svc = SVC(gamma=.05).fit(X_train, y_train)

mglearn.plots.plot_decision_threshold()
In [49]:
print(classification_report(y_test, svc.predict(X_test)))
             precision    recall  f1-score   support

          0       0.97      0.89      0.93       104
          1       0.35      0.67      0.46         9

avg / total       0.92      0.88      0.89       113

In [50]:
X_pred_lower_threshold = svc.decision_function(X_test) > -.8
In [51]:
print(classification_report(y_test, X_pred_lower_threshold))
             precision    recall  f1-score   support

          0       1.00      0.82      0.90       104
          1       0.32      1.00      0.49         9

avg / total       0.95      0.83      0.87       113

In [52]:
from sklearn.metrics import precision_recall_curve
precision, recall, thresholds = precision_recall_curve(y_test, svc.decision_function(X_test))
In [53]:
X, y = make_blobs(n_samples=(4000, 500), centers=2, cluster_std=[7.0, 2], random_state=22)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
svc = SVC(gamma=.05).fit(X_train, y_train)
precision, recall, thresholds = precision_recall_curve(y_test, svc.decision_function(X_test))

close_zero = np.argmin(np.abs(thresholds))
plt.plot(precision[close_zero], recall[close_zero], 'o', markersize=10, label="임계값 0", fillstyle="none", c='k', mew=2)

plt.plot(precision, recall, label="정밀도-재현율 곡선")
plt.xlabel("정밀도")
plt.ylabel("재현율")
Out[53]:
Text(0,0.5,'재현율')
In [54]:
from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(n_estimators=100, random_state=0, max_features=2)
rf.fit(X_train, y_train)

precision_rf, recall_rf, thresholds_rf = precision_recall_curve(y_test, rf.predict_proba(X_test)[:, 1])

plt.plot(precision, recall, label="svc")

plt.plot(precision[close_zero], recall[close_zero], 'o', markersize=10, label="svc: 임계값 0", fillstyle="none", c='k', mew=2)

plt.plot(precision_rf, recall_rf, label="rf")

close_default_rf = np.argmin(np.abs(thresholds_rf - 0.5))
plt.plot(precision_rf[close_default_rf], recall_rf[close_default_rf], '^', c='k', markersize=10, label="rf: 임계값 0.5", fillstyle="none", mew=2)
plt.xlabel("정밀도")
plt.ylabel("재현율")
plt.legend(loc="best")
Out[54]:
<matplotlib.legend.Legend at 0x10bf6df98>
In [55]:
print("랜덤 포레스트의 f1_score: {:.3f}".format(f1_score(y_test, rf.predict(X_test))))
print("svc의 f1_score: {:.3f}".format(f1_score(y_test, svc.predict(X_test))))
랜덤 포레스트의 f1_score: 0.610
svc의 f1_score: 0.656
In [56]:
from sklearn.metrics import average_precision_score
ap_rf = average_precision_score(y_test, rf.predict_proba(X_test)[:, 1])
ap_svc = average_precision_score(y_test, svc.decision_function(X_test))
print("랜덤 포레스트의 평균 정밀도: {:.3f}".format(ap_rf))
print("svc의 평균 정밀도: {:.3f}".format(ap_svc))
랜덤 포레스트의 평균 정밀도: 0.660
svc의 평균 정밀도: 0.666
In [57]:
from sklearn.metrics import roc_curve
fpr, tpr, thresholds = roc_curve(y_test, svc.decision_function(X_test))

plt.plot(fpr, tpr, label="ROC Curve")
plt.xlabel("FPR")
plt.ylabel("TPR (재현율)")

close_zero = np.argmin(np.abs(thresholds))
plt.plot(fpr[close_zero], tpr[close_zero], 'o', markersize=10, label="임계값 0", fillstyle="none", c='k', mew=2)
plt.legend(loc=4)
Out[57]:
<matplotlib.legend.Legend at 0x10be1bcf8>
In [58]:
from sklearn.metrics import roc_curve
fpr_rf, tpr_rf, thresholds_rf = roc_curve(y_test, rf.predict_proba(X_test)[:, 1])

plt.plot(fpr, tpr, label="SVC의 ROC 곡선")
plt.plot(fpr_rf, tpr_rf, label="RF의 ROC 곡선")

plt.xlabel("FPR")
plt.ylabel("TPR (재현율)")
plt.plot(fpr[close_zero], tpr[close_zero], 'o', markersize=10, label="SVC 임계값 0", fillstyle="none", c='k', mew=2)
close_default_rf = np.argmin(np.abs(thresholds_rf - 0.5))
plt.plot(fpr_rf[close_default_rf], tpr[close_default_rf], '^', markersize=10, label="RF 임계값 0.5", fillstyle="none", c='k', mew=2)

plt.legend(loc=4)
Out[58]:
<matplotlib.legend.Legend at 0x10dfbc898>
In [59]:
from sklearn.metrics import roc_auc_score

rf_auc = roc_auc_score(y_test, rf.predict_proba(X_test)[:, 1])
svc_auc = roc_auc_score(y_test, svc.decision_function(X_test))
print("랜덤 포레스트의 AUC: {:.3f}".format(rf_auc))
print("SVC의 AUC: {:.3f}".format(svc_auc))
랜덤 포레스트의 AUC: 0.937
SVC의 AUC: 0.916
In [60]:
y = digits.target == 9

X_train, X_test, y_train, y_test = train_test_split(digits.data, y, random_state=0)

plt.figure()

for gamma in [1, 0.1, 0.01]:
    svc = SVC(gamma=gamma).fit(X_train, y_train)
    accuracy = svc.score(X_test, y_test)
    auc = roc_auc_score(y_test, svc.decision_function(X_test))
    fpr, tpr, _ = roc_curve(y_test, svc.decision_function(X_test))
    print("gamma = {:.2f} 정확도 = {:.2f} AUC = {:.2f}".format(gamma, accuracy, auc))
    plt.plot(fpr, tpr, label="gamma={:.3f}".format(gamma))
plt.xlabel("FPR")
plt.ylabel("TPR")
plt.xlim(-0.01, 1)
plt.ylim(0, 1.02)
plt.legend(loc="best")
gamma = 1.00 정확도 = 0.90 AUC = 0.50
gamma = 0.10 정확도 = 0.90 AUC = 0.96
gamma = 0.01 정확도 = 0.90 AUC = 1.00
Out[60]:
<matplotlib.legend.Legend at 0x10b269240>
In [61]:
from sklearn.metrics import accuracy_score
X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, random_state=0)
lr = LogisticRegression().fit(X_train, y_train)
pred = lr.predict(X_test)
print("정확도: {:.3f}".format(accuracy_score(y_test, pred)))
print("오차 행렬:\n{}".format(confusion_matrix(y_test, pred)))
정확도: 0.953
오차 행렬:
[[37  0  0  0  0  0  0  0  0  0]
 [ 0 39  0  0  0  0  2  0  2  0]
 [ 0  0 41  3  0  0  0  0  0  0]
 [ 0  0  1 43  0  0  0  0  0  1]
 [ 0  0  0  0 38  0  0  0  0  0]
 [ 0  1  0  0  0 47  0  0  0  0]
 [ 0  0  0  0  0  0 52  0  0  0]
 [ 0  1  0  1  1  0  0 45  0  0]
 [ 0  3  1  0  0  0  0  0 43  1]
 [ 0  0  0  1  0  1  0  0  1 44]]
In [62]:
scores_image = mglearn.tools.heatmap(confusion_matrix(y_test, pred), xlabel='예측 레이블', ylabel='진짜 레이블', xticklabels=digits.target_names, yticklabels=digits.target_names, cmap=plt.cm.gray_r, fmt="%d")
plt.title("오차 행렬")
plt.gca().invert_yaxis()
In [63]:
print(classification_report(y_test, pred))
             precision    recall  f1-score   support

          0       1.00      1.00      1.00        37
          1       0.89      0.91      0.90        43
          2       0.95      0.93      0.94        44
          3       0.90      0.96      0.92        45
          4       0.97      1.00      0.99        38
          5       0.98      0.98      0.98        48
          6       0.96      1.00      0.98        52
          7       1.00      0.94      0.97        48
          8       0.93      0.90      0.91        48
          9       0.96      0.94      0.95        47

avg / total       0.95      0.95      0.95       450

In [64]:
print("micro 평균 f1 점수: {:.3f}".format(f1_score(y_test, pred, average="micro")))
print("macro 평균 f1 점수: {:.3f}".format(f1_score(y_test, pred, average="macro")))
micro 평균 f1 점수: 0.953
macro 평균 f1 점수: 0.954
In [65]:
print("기본 평가 지표: {}".format(cross_val_score(SVC(), digits.data, digits.target == 9)))

explicit_accuracy = cross_val_score(SVC(), digits.data, digits.target == 9, scoring="accuracy")
print("정확도 지표: {}".format(explicit_accuracy))
roc_auc = cross_val_score(SVC(), digits.data, digits.target == 9, scoring="roc_auc")
print("AUC 지표: {}".format(roc_auc))
기본 평가 지표: [0.89983306 0.89983306 0.89983306]
정확도 지표: [0.89983306 0.89983306 0.89983306]
AUC 지표: [0.99372294 0.98957947 0.99594929]
In [66]:
X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target == 9, random_state = 0)

param_grid = {'gamma': [0.0001, 0.01, 0.1, 1, 10]}
grid = GridSearchCV(SVC(), param_grid=param_grid)
grid.fit(X_train, y_train)
print("정확도 지표를 사용한 그리드 서치")
print("최적의 매개변수:", grid.best_params_)
print("최상의 교차 검증 점수 (정확도)) : {:.3f}".format(grid.best_score_))
print("테스트 세트 AUC: {:.3f}".format(roc_auc_score(y_test, grid.decision_function(X_test))))
print("테스트 세트 정확도: {:.3f}".format(grid.score(X_test, y_test)))
정확도 지표를 사용한 그리드 서치
최적의 매개변수: {'gamma': 0.0001}
최상의 교차 검증 점수 (정확도)) : 0.970
테스트 세트 AUC: 0.992
테스트 세트 정확도: 0.973
In [67]:
# AUC 지표 사용
grid = GridSearchCV(SVC(), param_grid=param_grid, scoring="roc_auc")
grid.fit(X_train, y_train)
print("AUC 지표를 사용한 그리드 서치")
print("최적의 매개변수:", grid.best_params_)
print("최상의 교차 검증 점수 (AUC)) : {:.3f}".format(grid.best_score_))
print("테스트 세트 AUC: {:.3f}".format(roc_auc_score(y_test, grid.decision_function(X_test))))
print("테스트 세트 정확도: {:.3f}".format(grid.score(X_test, y_test)))
AUC 지표를 사용한 그리드 서치
최적의 매개변수: {'gamma': 0.01}
최상의 교차 검증 점수 (AUC)) : 0.997
테스트 세트 AUC: 1.000
테스트 세트 정확도: 1.000
In [68]:
from sklearn.metrics.scorer import SCORERS
print("가능한 평가 방식:\n{}".format(sorted(SCORERS.keys())))
가능한 평가 방식:
['accuracy', 'adjusted_mutual_info_score', 'adjusted_rand_score', 'average_precision', 'completeness_score', 'explained_variance', 'f1', 'f1_macro', 'f1_micro', 'f1_samples', 'f1_weighted', 'fowlkes_mallows_score', 'homogeneity_score', 'log_loss', 'mean_absolute_error', 'mean_squared_error', 'median_absolute_error', 'mutual_info_score', 'neg_log_loss', 'neg_mean_absolute_error', 'neg_mean_squared_error', 'neg_mean_squared_log_error', 'neg_median_absolute_error', 'normalized_mutual_info_score', 'precision', 'precision_macro', 'precision_micro', 'precision_samples', 'precision_weighted', 'r2', 'recall', 'recall_macro', 'recall_micro', 'recall_samples', 'recall_weighted', 'roc_auc', 'v_measure_score']


댓글