#!/usr/bin/env python # coding: utf-8 # In[1]: from preamble import * get_ipython().run_line_magic('matplotlib', 'inline') # ## 5. Model Evaluation and Improvement # In[2]: from sklearn.datasets import make_blobs from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split # create a synthetic dataset X, y = make_blobs(random_state=0) print("X.shape:", X.shape) print("y.shape:", y.shape) # split data and labels into a training and a test set X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0) # instantiate a model and fit it to the training set logreg = LogisticRegression().fit(X_train, y_train) # evaluate the model on the test set print("Test set score: {:.2f}".format(logreg.score(X_test, y_test))) # ### 5.1 Cross-Validation # - 교차 검증 # - 데이터를 여러 번 반복해서 나누어 모델 학습 # - K-Fold cross-vailidation # - Fold: 원본 데이터에 대한 부분 집합 # - K로는 5나 10을 주로 사용 # - 첫번째 모델은 첫번째 fold를 테스트 데이터로 사용하고 나머지를 훈련 데이터로 사용 # - 두번째 모델은 두번째 fold를 테스트 데이터로 사용하고 나머지를 훈련 데이터로 사용 # - 세번째 모델은... # In[3]: import matplotlib print(matplotlib.__version__) mglearn.plots.plot_cross_validation() # #### 5.1.1 Cross-Validation in scikit-learn # - scikit-learn의 교차 검증 # - model_selection.cross_val_score(estimator, X, y=None, cv=None) 함수 사용 # - estimator # - estimator object implementing ‘fit’ # - The object to use to fit the data. # - X # - The data to fit. # - y # - The target variable to try to predict in the case of supervised learning. # - cv # - K-Fold의 K값 (기본 값: 3) # In[4]: from sklearn.model_selection import cross_val_score from sklearn.datasets import load_iris from sklearn.linear_model import LogisticRegression iris = load_iris() print("iris.data.shape:", iris.data.shape) print("iris.target.shape:", iris.target.shape) logreg = LogisticRegression() scores = cross_val_score(logreg, iris.data, iris.target) print("Cross-validation scores: {}".format(scores)) # In[5]: scores = cross_val_score(logreg, iris.data, iris.target, cv=5) print("Cross-validation scores: {}".format(scores)) # - 교차 검증의 정확도: 각 교차 검증 정확도의 평균값 사용 # In[6]: print("Average cross-validation score: {:.2f}".format(scores.mean())) # ### 5.1.2 Benefits of Cross-Validation # - 기존 train_test_split 방법만 사용하는 경우 # - 확보한 원본 데이터 중 일부의 데이터는 훈련 데이터로 활용하지 않으면서 모델을 구성함. # - cross_val_score 함수를 사용하는 경우 # - 데이터를 고르게 사용하여 fit을 하고 score를 구하기 때문에 모델의 성능을 좀 더 정확히 측정할 수 있음 # - 새로은 테스트 데이터의 예측 정확도에 대하여 최악과 최선의 경우를 짐작할 수 있음 # - [주의] **cross_val_score가 직접 모델을 구성하는 방법은 아님!** # - 즉, 이 함수를 호출하면 내부적으로 K번 모델을 구성하지만, 그러한 모델들은 평가의 목적으로만 활용됨. # ### 5.1.3 Stratified K-Fold cross-validation and other strategies # - 계층별 K-Fold 교차 검증 # - 각 Fold안의 클래스 비율이 전체 원본 데이터셋에 있는 클래스 비율과 동일하도록 맞춤 # - 즉, 원본 데이터셋에서 클래스 A가 90%, 클래스 B가 10% 비율이라면, 계층별 K-Fold 교차 검증에서 각 K개의 Fold안에는 클래스 A가 90%, 클래스 B가 10% 비율이 됨. # - scikit-learn의 cross_val_score 기본 설정 # - 분류모델: StratifiedKFold를 사용하여 기본적으로 계층별 K-Fold 교차 검증 수행 # - 회귀모델: 단순한 KFold를 사용하여 계층별이 아닌 기본 K-Fold 교차 검증 수행 # - 대신 회귀모델에서는 KFold를 사용할 때 shuffle 매개변수를 True로 지정하여 폴드를 나누기 전에 무작위로 데이터를 섞는 작업 추천 # In[7]: from sklearn.datasets import load_iris iris = load_iris() print("Iris labels:\n{}".format(iris.target)) # In[8]: mglearn.plots.plot_stratified_cross_validation() # ### More control over cross-validation # - 기본적으로... # - 분류: StratifiedKFold가 사용됨 # - 회귀: KFold가 사용됨 # - 하지만, 때때로 분류에 KFold가 사용되어야 할 필요도 있음 # - 다른 사람이 이미 수행한 사항을 재현해야 할 때 # - StratifiedKFold가 아닌 KFold를 생성하여 cross_val_score()의 cv 인자에 할당 # In[9]: from sklearn.model_selection import KFold kfold = KFold(n_splits=5) #교차 검증 분할기의 역할 수행 # In[10]: print("Cross-validation scores:\n{}".format(cross_val_score(logreg, iris.data, iris.target, cv=kfold))) # - 이런 경우 3-Fold를 사용하면 데이터 타겟 레이블 분포 특성상 성능이 매우 나쁠 수 있음 # In[11]: kfold = KFold(n_splits=3) print("Cross-validation scores:\n{}".format(cross_val_score(logreg, iris.data, iris.target, cv=kfold))) # - 해결책 # - KFold를 만들 때 shuffle=True를 통해 데이터를 임의로 섞음. # - random_state=0을 주면 추후 그대로 재현이 가능 # In[12]: kfold = KFold(n_splits=3, shuffle=True, random_state=0) print("Cross-validation scores:\n{}".format(cross_val_score(logreg, iris.data, iris.target, cv=kfold))) # #### Leave-one-out cross-validation (LOOCV) # - Fold 하나에 하나의 샘플이 들어 있는 Stratified k-Fold 교차 검증 # - 즉, 각각의 반복에서 테스트 데이터에 하나의 샘플만 존재 # - 데이터셋이 클 때 시간이 매우 오래 걸림 # - 작은 데이터셋에 대해서는 일반적인 상황에 대한 거의 확실한 score 값을 얻을 수 있음. # In[13]: from sklearn.model_selection import LeaveOneOut loo = LeaveOneOut() scores = cross_val_score(logreg, iris.data, iris.target, cv=loo) print("Number of cv iterations: ", len(scores)) print("Mean accuracy: {:.2f}".format(scores.mean())) # #### Shuffle-split cross-validation # - 임의 분할 교차 검증 # - model_selection.SuffleSplit(n_splits=10, test_size='default') or model_selection.StratifiedSuffleSplit(n_splits=10, test_size='default') # - n_splits: 10 # - 분할의 개수 # - test_size 만큼의 테스트 셋트를 만들도록 분할 # - test_size의 기본값: 0.1 # - 보통 test_size 값만 설정하며, 추가적으로 train_size 도 설정 가능 # - 이런 경우 전체 데이터 집합 중 일부만 훈련과 테스트에 사용할 수 있음 # - 대규모 데이터에 유용 # - test_size, train_size # - 정수: 데이터 포인트의 개수 # - 실수: 데이터 포인트 비율

# # - 아래 그림 예제 # - 전체 데이터 셈플 개수: 10 # - train_size = 5 # - test_size = 2 # - n_splits = 4 # In[14]: mglearn.plots.plot_shuffle_split() # In[15]: from sklearn.model_selection import ShuffleSplit shuffle_split = ShuffleSplit(n_splits=10, test_size=.5, train_size=.5) scores = cross_val_score(logreg, iris.data, iris.target, cv=shuffle_split) print("Cross-validation scores:\n{}".format(scores)) print("Mean accuracy: {:.2f}".format(scores.mean())) # In[16]: from sklearn.model_selection import StratifiedShuffleSplit shuffle_split = StratifiedShuffleSplit(n_splits=10, test_size=.5, train_size=.5) scores = cross_val_score(logreg, iris.data, iris.target, cv=shuffle_split) print("Cross-validation scores:\n{}".format(scores)) print("Mean accuracy: {:.2f}".format(scores.mean())) # ##### Cross-validation with groups # - 임의의 그룹에 속한 데이터 전체를 훈련 집합 또는 테스트 집합에 넣을 때 사용 # - 테스트 데이터가 때때로 완전히 새로운 데이터가 되어야 할 필요 있음 # - model_selection.GroupKFold # - 그룹핑을 통하여 훈련 데이터 셋트와 테스트 데이터 셋트를 완벽히 분리하기 위해 사용 # - group 배열 # - 각 데이터 포인트 별로 그룹 index 지정 필요 # - 배열 내에 index 지정을 통해 훈련 데이터와 테스트 데이터를 랜덤하게 구성할 때 분리되지 말아야 할 그룹을 지정 # - 타깃 레이블과 혼동하면 안됨 # - 더 나은 방법 # - 이 방법대신 model_selection.train_test_split을 통해 처음 부터 테스트 데이터를 미리 분리하는 것이 더 좋음. # In[17]: mglearn.plots.plot_group_kfold() # In[18]: from sklearn.model_selection import GroupKFold # create synthetic dataset X, y = make_blobs(n_samples=12, random_state=0) # assume the first three samples belong to the same group, # then the next four, etc 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("Cross-validation scores:\n{}".format(scores)) print("Mean accuracy: {:.2f}".format(scores.mean())) # ### 5.2 Grid Search # - 모델 매개변수 튜닝을 통한 일반화 성능 개선 # - 가장 널리 사용되는 방법은 Grid Search (그리드 탐색) # - 관심있는 매개변수들을 대상으로 모든 조합을 시도함. # #### 5.2.1 Simple Grid-Search # - SVC 모델에서 가장 중요한 매개변수는 gamma, C # - 그리드 탐색 범위 설정 예 # - gamma: [0.001, 0.01, 0.1, 1, 10, 100] # - C: [0.001, 0.01, 0.1, 1, 10, 100] # - 총 6x6=36개의 조합에 대하여 반복적으로 새로운 모델 생성 및 평가 # - 가장 좋은 성능을 보여주는 gamma와 C의 조합을 찾음 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 C=0.001 C=0.01 C=0.1 C=1 C=10 C=100 gamma=0.001 SVC(C=0.001, gamma=0.001) SVC(C=0.01, gamma=0.001) SVC(C=0.1, gamma=0.001) SVC(C=1, gamma=0.001) SVC(C=10, gamma=0.001) SVC(C=100, gamma=0.001) gamma=0.01 SVC(C=0.001, gamma=0.01) SVC(C=0.01, gamma=0.01) SVC(C=0.1, gamma=0.01) SVC(C=1, gamma=0.01) SVC(C=10, gamma=0.01) SVC(C=100, gamma=0.01) gamma=0.1 SVC(C=0.001, gamma=0.1) SVC(C=0.01, gamma=0.1) SVC(C=0.1, gamma=0.1) SVC(C=1, gamma=0.1) SVC(C=10, gamma=0.1) SVC(C=100, gamma=0.1) gamma=1 SVC(C=0.001, gamma=1) SVC(C=0.01, gamma=1) SVC(C=0.1, gamma=1) SVC(C=1, gamma=1) SVC(C=10, gamma=1) SVC(C=100, gamma=1) gamma=10 SVC(C=0.001, gamma=10) SVC(C=0.01, gamma=10) SVC(C=0.1, gamma=10) SVC(C=1, gamma=10) SVC(C=10, gamma=10) SVC(C=100, gamma=10) gamma=100 SVC(C=0.001, gamma=100) SVC(C=0.01, gamma=100) SVC(C=0.1, gamma=100) SVC(C=1, gamma=100) SVC(C=10, gamma=100) SVC(C=100, gamma=100)
# In[19]: # naive grid search implementation from sklearn.svm import SVC X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=0) print("Size of training set: {} size of test set: {}".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]: # for each combination of parameters, train an SVC svm = SVC(gamma=gamma, C=C) svm.fit(X_train, y_train) # evaluate the SVC on the test set score = svm.score(X_test, y_test) # if we got a better score, store the score and parameters if score > best_score: best_score = score best_parameters = {'C': C, 'gamma': gamma} print("Best score: {:.2f}".format(best_score)) print("Best parameters: {}".format(best_parameters)) # - 위 0.97의 정확도는 전혀 새로운 데이터에 대한 성능으로 이어지지 않을 수 있다. # - 즉, 위 예제에서 사용한 테스트 데이터는 모델 구성시에 사용을 해버렸기 때문에 이 모델이 얼마나 좋은지 평가하는 데 더 이상 사용할 수 없다. # #### 5.2.2 The danger of overfitting the parameters and the validation set # - 검증 데이터 세트 (Valudation Set) 필요 # - 모델 파라미터 튜닝 용도 # - 모델을 구성할 때 훈련 데이터 세트와 검증 데이터 세트를 활용 # In[20]: mglearn.plots.plot_threefold_split() # In[21]: from sklearn.svm import SVC # split data into train+validation set and test set X_trainval, X_test, y_trainval, y_test = train_test_split(iris.data, iris.target, random_state=0) # split train+validation set into training and validation sets X_train, X_valid, y_train, y_valid = train_test_split(X_trainval, y_trainval, random_state=1) print("Size of training set: {}, size of validation set: {},size of test set: {}\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]: # for each combination of parameters train an SVC svm = SVC(gamma=gamma, C=C) svm.fit(X_train, y_train) # evaluate the SVC on the validation set score = svm.score(X_valid, y_valid) # if we got a better score, store the score and parameters if score > best_score: best_score = score best_parameters = {'C': C, 'gamma': gamma} # rebuild a model on the combined training and validation set, # and evaluate it on the test set svm = SVC(**best_parameters) #[NOTE] 훈련 데이터와 검증 데이터를 합쳐서 다시 모델을 구성함 svm.fit(X_trainval, y_trainval) test_score = svm.score(X_test, y_test) print("Best score on validation set: {:.2f}".format(best_score)) print("Best parameters: ", best_parameters) print("Test set score with best parameters: {:.2f}".format(test_score)) # - 위 예제를 통하여 전혀 새로운 테스트 데이터에 대하여, 생성 모델은 92%의 정확도로 분류한다고 볼 수 있음 # #### 5.2.3 Grid-search with cross-validation # - 그리드 탐색에서도 교차 검증 필요 # - 위 두 예제에서 최고의 성능을 보여주는 파라미터가 변경된 점을 주의 # - cross_val_score 사용 # In[22]: 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("Size of training set: {}, size of validation set: {},size of test set: {}\n".format( X_train.shape[0], X_valid.shape[0], X_test.shape[0])) # reference: manual_grid_search_cv for gamma in [0.001, 0.01, 0.1, 1, 10, 100]: for C in [0.001, 0.01, 0.1, 1, 10, 100]: # for each combination of parameters, # train an SVC svm = SVC(gamma=gamma, C=C) # perform cross-validation scores = cross_val_score(svm, X_trainval, y_trainval, cv=5) # compute mean cross-validation accuracy score = np.mean(scores) # if we got a better score, store the score and parameters if score > best_score: best_score = score best_parameters = {'C': C, 'gamma': gamma} # rebuild a model on the combined training and validation set svm = SVC(**best_parameters) #[NOTE] 훈련 데이터와 검증 데이터를 합쳐서 다시 모델을 구성함 svm.fit(X_trainval, y_trainval) test_score = svm.score(X_test, y_test) print("Best score on validation set: {:.2f}".format(best_score)) print("Best parameters: ", best_parameters) print("Test set score with best parameters: {:.2f}".format(test_score)) # - 위 예에서는 반복적인 모델 생성 작업이 6 \* 6 \* 5 = 180번 이루어짐 # - 즉, 시간이 많이 소요됨에 주의 # - 아래 그림은 교차 검증에 5-fold 사용 # - 매개변수 그리드는 일부만 표시 # - 교차 검증 5번의 평균이 가장 높은 매개변수를 빨간 동그라미로 표시 # In[23]: mglearn.plots.plot_cross_val_selection() # - 그리드 서치와 교차 검증을 사용한 매개 변수 선택과 모델 평가의 전체 작업 흐름 # ![process](./images/process.jpg) # - **model_selection.GridSearchCV** # - **교차 검증을 사용하는 그리드 탐색을 통한 모델 파라미터 검색 기능 제공 객체** # - 기본적으로 사용하는 교차 검증 분류기 # - 분류에는 StratifiedKFold 사용함 # - 회귀에는 KFold 사용함 # - fit을 수행한 이후에는 가장 최적의 파라미터로 만들어진 모델을 구성하고 있음. # - 다른 estimator (or 모델)를 사용하여 만들어지는 estimator를 메타 추정기(meta-estimator)라고 함. # - GridSearchCV는 가장 널리 사용되는 메타 추정기 # - scikit-learn에서는 MetaEstimatorMixin 클래스를 상속한 모델을 메타 추정기라고 부름 # - 메타 추정기 예 # - GridSearchCV # - RandomForest # - GradientBoosting # - RFE # - ... # - 우선 모델에 들어갈 각 매개변수 값을 사전(Dict)타입으로 구성 # - 문자열 매개변수 이름을 모델(예:SVC)에 설정된 매개변수와 동일하게 맞춤 # In[24]: param_grid = { 'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100] } print("Parameter grid:\n{}".format(param_grid)) # - GridSearchCV 생성 # - model_selection.GridSearchCV(estimator, param_grid, n_jobs=1, cv=None, verbose=0, return_train_score='True') # - estimator # - param_grid # - n_jobs # - Number of jobs to run in parallel # - default: 1 # - -1 --> Using All threads # - cv # - None, to use the default 3-fold cross validation # - integer, to specify the number of folds in a (Stratified)KFold. # - fold의 개수를 cv=5와 같이 설정 # - An object to be used as a cross-validation generator. # - An iterable yielding train, test splits. # In[25]: from sklearn.model_selection import GridSearchCV from sklearn.svm import SVC estimator = SVC() grid_search = GridSearchCV( estimator = estimator, param_grid = param_grid, n_jobs = -1, cv = 5, return_train_score = True ) # - 훈련 데이터와 테스트 데이터 분리 # In[26]: X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=0) # - 훈련 데이터만 GridSearchCV 객체에 넣어 fit을 함 # - 이 때 훈련 데이터중 일부는 내부적으로 검증 데이터 (Validation Data)로 사용됨 # - GridSearchCV는 생성시 모델을 내장하므로 fit, predict, score 등의 함수를 제공 # - 모델에 따라서 predict_proba, decision_function을 제공하기도 함 # In[27]: grid_search.fit(X_train, y_train) # - 모델 구성시 사용하지 않은 완전히 새로운 데이터인 X_test와 y_test를 사용하여 모델 평가 # In[28]: print("Test set score: {:.2f}".format(grid_search.score(X_test, y_test))) # In[29]: print("Best parameters: {}".format(grid_search.best_params_)) print("Best cross-validation score: {:.2f}".format(grid_search.best_score_)) # - 위 두 예에서 grid_search.score() 메소드와 grid_search.best\_score\_ 속성은 매우 큰 차이 # - grid_search.score() 메소드 # - 새로운 데이터인 테스트 데이터 셋을 통한 모델 평가 점수 # - grid_search.best\_score\_ 속성 # - 훈련 데이터에 대하여 수행한 교차 검증에서의 최고 점수 # In[30]: print("Best estimator:\n{}".format(grid_search.best_estimator_)) # #### [NOTE] 전형적인 교차 검증 그리드 서치를 통한 모델 구성 및 테스트 집합 성능 평가 코드 # In[31]: from sklearn.model_selection import GridSearchCV from sklearn.svm import SVC X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=0) estimator = SVC() param_grid = { 'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100] } grid_search = GridSearchCV( estimator = estimator, param_grid = param_grid, n_jobs = -1, cv = 5, return_train_score = True ) grid_search.fit(X_train, y_train) print("Test set score: {:.2f}".format(grid_search.score(X_test, y_test))) # #### Analyzing the result of cross-validation # - grid_search.cv\_results\_ # - 그리드 탐색에 대한 교차 검증 결과 정보가 상세히 들어 있는 속성 # In[32]: import pandas as pd # convert to Dataframe results = pd.DataFrame(grid_search.cv_results_) pd.options.display.float_format = '{:,.7f}'.format print(results.columns) # show the first 5 rows # display(results.head(5)) display(results) # In[33]: results2 = results[['rank_test_score', 'params', 'mean_test_score', 'std_test_score', 'mean_train_score', 'std_train_score']] results2 = results2.sort_values('rank_test_score') display(results2) # - heatmap을 사용한 mean_test_score를 각 매개변수별로 시각화 # In[34]: print([x for x in results.mean_test_score]) print() print(results.mean_test_score.shape) scores = np.array(results.mean_test_score).reshape(6, 6) # plot the mean cross-validation scores mglearn.tools.heatmap( scores, xlabel='gamma', xticklabels=param_grid['gamma'], ylabel='C', yticklabels=param_grid['C'], cmap="viridis" ) # In[35]: fig, axes = plt.subplots(1, 3, figsize=(20, 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, n_jobs=-1, cv=5) grid_search.fit(X_train, y_train) scores = grid_search.cv_results_['mean_test_score'].reshape(6, 6) # plot the mean cross-validation scores 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()) # - 첫번째 그래프 # - 매개변수 C와 gamma의 스케일과 범위를 잘못 택하였음을 나타냄 # - 처음에는 더 넓은 범위의 C와 gamma 스케일 및 범위를 택하고, 이후 정확도에 따라 매개변수를 바꾸어 선택할 필요있음

# # - 두번째 그래프 # - 세로 띠 형태를 보이므로 gamma 매개변수만 정확도에 영향을 주고 있음을 나타냄 # - 두 가지 케이스 # - C 매개변수는 전혀 중요한 역할을 못할 수 있음 # - C 매개변수의 스케일과 범위를 잘못 선택하였을 수 있음

# # - 세번째 그래프 # - 그래프 왼쪽 아래에서는 변화가 없음 # - 다시 매개변수 스케일과 범위를 선택하는 과정에서 현재 선택한 것 보다 더 높은 gamma 및 C 값을 선택할 필요성 있음 # #### Grid search with asymmetric parameters # - SVC # - kernel='rbf' 일 때 # - C 매개변수, gamma 매개변수 동시 사용 # - kernel='linear' 일 때 # - C 매개변수만 사용 # - gamma 매개변수는 사용하지 않음 # In[36]: 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("List of grids:\n{}".format(param_grid)) # In[37]: grid_search = GridSearchCV(SVC(), param_grid, n_jobs=-1, cv=5, return_train_score=True) grid_search.fit(X_train, y_train) print("Best parameters: {}".format(grid_search.best_params_)) print("Best cross-validation score: {:.2f}".format(grid_search.best_score_)) # In[38]: results = pd.DataFrame(grid_search.cv_results_) results2 = results[['rank_test_score', 'params', 'mean_test_score', 'std_test_score', 'mean_train_score', 'std_train_score']] results2 = results2.sort_values('rank_test_score') display(results2) # #### Using different cross-validation strategies with grid-search # - GridSearchCV의 인자인 cv에 스스로 정의한 다음과 같은 교차 검증 분할기 제공 # - KFold(n_splits=5) # - StratifiedKFold(n_splits=5) # - ShuffleSplit(n_splits=5) # - StratifiedShuffleSplit(n_splits=5) # - n_splits=1을 사용하는 경우 # - 훈련 데이터 세트와 검증 데이터 세트로의 분리를 한번 만 수행 # - 데이터셋이 매우 크거나 모델 구축 시간이 오래 걸릴 때 사용하는 전략 # In[39]: from sklearn.model_selection import StratifiedShuffleSplit shuffle_split = StratifiedShuffleSplit(test_size=.8, n_splits=1) grid_search = GridSearchCV(SVC(), param_grid, cv=shuffle_split, return_train_score=True) grid_search.fit(X_train, y_train) print("Best parameters: {}".format(grid_search.best_params_)) print("Best cross-validation score: {:.2f}".format(grid_search.best_score_)) # In[40]: results = pd.DataFrame(grid_search.cv_results_) results2 = results[['rank_test_score', 'params', 'mean_test_score', 'std_test_score', 'mean_train_score', 'std_train_score']] results2 = results2.sort_values('rank_test_score') display(results2) # #### Nested cross-validation # - 지금까지 살펴본 코드들의 단점 # - 처음에 원본 데이터들을 훈련 데이터와 테스트 데이터로 **한번만** 나누고 있음. # - 원본 데이터를 훈련 데이터와 테스트 데이터로 나누는 시점도 교차 검증화 시킬 수 있음 --> **중첩 교차 검증** # - **중첩 교차 검증** # - outer_scores = [] # - 1st Loop: 원본 데이터를 훈련(Training) 데이터와 테스트(Test) 데이터로 분리 및 순회 # - best_params = {} # - best_score = -np.inf # - 2nd Loop: 매개변수 그리드를 순회 # - 3rd Loop: 훈련 데이터를 다시 훈련(Training) 데이터와 검증(Validation) 데이터로 분리 # - 3rd Loop의 결과 모델을 평가하여 best_params 및 best_score 조정 # - best_params와 함께 모델 구성하여 평가결과를 outer_scores에 저장 # ![process](./images/nestedkfold.jpg) # - 위 중첩 교차 검증과정을 corss_val_score 및 GridSearchCV 조합으로 간단하게 완성 # In[41]: param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100]} grid_search = GridSearchCV(SVC(), param_grid, cv=5) scores = cross_val_score(grid_search, iris.data, iris.target, n_jobs=-1, cv=5) print("Cross-validation scores: ", scores) print("Mean cross-validation score: ", scores.mean()) # - 위 코드 설명 # - 매개 변수 조합: 6 \* 6 = 36 # - 바깥 루프: 5개 분할 # - 안쪽 루프: 5개 분할 # - 모델 생성 횟수: 36 \* 5 \* 5 = 900 # In[42]: def nested_cv(X, y, inner_cv, outer_cv, Classifier, parameter_grid): outer_scores = [] outer_best_params = [] # for each split of the data in the outer cross-validation # (split method returns indices of training and test part) for training_samples, test_samples in outer_cv.split(X, y): # find best parameter using inner cross-validation best_parms = {} best_score = -np.inf # iterate over parameters for parameters in parameter_grid: # accumulate score over inner splits cv_scores = [] # iterate over inner cross-validation for inner_train, inner_test in inner_cv.split(X[training_samples], y[training_samples]): # build classifier given parameters and training data clf = Classifier(**parameters) clf.fit(X[inner_train], y[inner_train]) # evaluate on inner test set score = clf.score(X[inner_test], y[inner_test]) cv_scores.append(score) # compute mean score over inner folds mean_score = np.mean(cv_scores) if mean_score > best_score: # if better than so far, remember parameters best_score = mean_score best_params = parameters # build classifier on best parameters using outer training set clf = Classifier(**best_params) clf.fit(X[training_samples], y[training_samples]) # evaluate outer_scores.append(clf.score(X[test_samples], y[test_samples])) outer_best_params.append(best_params) return np.array(outer_scores), outer_best_params # In[43]: from sklearn.model_selection import ParameterGrid, StratifiedKFold scores, params = nested_cv( iris.data, iris.target, StratifiedKFold(5), StratifiedKFold(5), SVC, ParameterGrid(param_grid) ) print("Cross-validation scores: {}".format(scores)) print("Mean cross-validation score: ", scores.mean()) print("best params: {}".format(params)) # #### Parallelizing cross-validation and grid search # - 다중 CPU 코어 or 다중 GPU 코어 사용 # - 사용가능한 프레임워크 # - ipyparallel # - https://ipyparallel.readthedocs.io # - spark-sklearn # - https://github.com/databricks/spark-sklearn # ### 5.3. Evaluation Metrics and Scoring # - 기존의 Simple한 모델 평가 지표 (score) # - 분류 문제: 정확도 (Accuracy) # - 회귀 문제: $R^2$ # - 하지만, 어플리케이션에 따라 위의 평가 지표가 적합하지 않을 수 있음. # #### 5.3.1 Keep the End Goal in Mind (최종 목표를 기억하라) # - 어플리케이션의 고차원 목표인 비지니스 지표를 우선적으로 고려해야 함 # - 비지니스 지표 예 # - 교통사고율 낮춤 # - 입원환자 수 낮춤 # - 웹사이트 사용자 유입률 증대 # - 소비자 소비률 증대 # - 분석 모델 개발 초기 단계에 매개변수를 조정하기 위해 시험 삼아 모델을 실제 운영 시스템에 곧바로 적용하기란 위험부담이 크다. # - 비지니스 임팩트 (Business Impact) # - 어떤 머신러닝 어플리케이션에서 특정 알고리즘을 선택하여 나타난 결과 # - 훈련 모델에 대한 비지니스 임팩트를 정확하게 예상할 수 있는 다양한 평가지표 도입 필요 # - 이진 분류의 평가 지표 # - 다중 분류의 평가 지표 # - 회귀의 평가 지표 # #### 5.3.2 Metrics for Binary Classification # - 두 가지 분류 클래스 # - 양성 클래스 (주 관심 클래스) --> Positive Class # - 음성 클래스 --> Negative Class

# # - 모델 적용 결과에 대한 분류 # - True Positive (참 양성, TP) # - 모델에서 실제 양성 클래스를 정확하게 양성으로 평가한 것들 # - False Negative (거짓 음성, FN) # - 모델에서 실제 양성 클래스를 잘못하여 음성으로 평가한 것들 # - True Negative (참 음성, TN) # - 모델에서 실제 음성 클래스를 정확하게 음성으로 평가한 것들 # - False Positive (거짓 양성, FP) # - 모델에서 실제 음성 클래스를 잘못하여 양성으로 평가한 것들 # # - 참고: https://developers.google.com/machine-learning/crash-course/classification/true-false-positive-negative # # ##### Kinds of errors # - 암의 조기 발견 어플리케이션 # - 테스트가 음성(-)이면 건강함을 뜻함 # - 음성 클래스(Negative Class) # - 테스트가 양성(+)이면 암 진단이 되었음을 뜻함 # - 양성 클래스(Positive Class) # - 잘못된 분류 케이스 # - Case 1. 건강한 사람을 양성으로 잘못 분류한 경우 # - 이 환자에게 비용 손실과 불편함을 초래함 # - 즉, 잘못된 양성 예측 # - 분류: ***거짓 양성 (False Positive)*** # - Case 2. 암에 걸린 사람을 음성으로 잘못 분류한 경우 # - 제대로 된 검사나 치료를 제때에 못하게 하는 치명적인 오류 # - 즉, 잘못된 음성 예측 # - 분류: ***거짓 음성 (False Negative)*** # # - 대부분의 경우 ***거짓 음성***이 ***거짓 양성***보다 더 치명적 # - 거짓 음성 분류와 거짓 양성 분류 중 하나가 다른 것 보다 훨씬 많을 때 이 상황은 매우 중요한 상황으로 인식해야 함. # ##### Imbalanced datasets # - 불균형 데이터셋(Imbalanced datasets) # - 예) 인터넷 광고 클릭 데이터에서 원본 데이터 샘플의 99%가 '클릭 아님'이고 1%만이 '클릭'인 데이터셋 # - 현실에서 불균형 데이터는 매우 많음 # - 위 예에서 머신러닝 모델을 만들지 않고서도 무조건 '클릭 아님'으로 예측하면 그 정확도가 99%가 됨. # # - 따라서, '정확도'만으로 모델의 성능을 판별하는 것은 지양해야 함. # - digits 데이터셋에서 Target 데이터를 숫자 9이면 True, 그렇지 않으면 False로 변환하여 1:9의 불균형 데이터셋 생성 # In[44]: 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) print(X_train.shape) print(y_train.shape) print(X_test.shape) print(y_test.shape) print(y_test[:10]) print() print(len(np.where(y_test == True)[0])) print(len(np.where(y_test == False)[0])) # - 정답 '9임'의 총 개수: 47 --> ***양성 클래스*** # - 정답 '9가 아님'의 총 개수: 403 --> ***음성 클래스*** # - DummyClassifier # - strategy='stratified' # - 기본값 # - 레이블 비율에 맞추어서 예측 # - strategy='most_frequent' # - 가장 많은 레이블로 항상 예측 # # - DummyRegressor # - strategy='mean' # - strategy='median' # - 아무런 학습을 하지 않고도 90% 정확도가 나올 수 있음 # In[45]: 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("Unique predicted labels: {}".format(np.unique(pred_most_frequent))) print("Test score: {:.2f}".format(dummy_majority.score(X_test, y_test))) # - 정상적인 학습을 하더라도 92% 정확도가 나옴 --> 위의 결과와 그리 차이가 없음 # In[46]: from sklearn.tree import DecisionTreeClassifier tree = DecisionTreeClassifier(max_depth=2).fit(X_train, y_train) pred_tree = tree.predict(X_test) print("Test score: {:.2f}".format(tree.score(X_test, y_test))) # - 레이블 비율에 맞추어서 예측을 하는 Dummy 모델도 꽤 성능이 좋음. # In[47]: from sklearn.linear_model import LogisticRegression dummy = DummyClassifier().fit(X_train, y_train) pred_dummy = dummy.predict(X_test) print("Unique predicted labels: {}".format(np.unique(pred_dummy))) print("dummy score: {:.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 score: {:.2f}".format(logreg.score(X_test, y_test))) # - Dummy 분류기 조차 매우 좋은 예측 정확도를 산출하는 점에 유의 # - 현실세계에서 많이 발생할 수 있는 불균형 데이터셋(Imbalanced datasets)과 함께 **오로지 정확도만으로 모델의 성능을 지표화하는 것은 올바른 방법이 아님** # ##### Confusion matrices # - **오차 행렬(Confusion Matrix)** # - 이진 분류 평가 결과를 나타낼 때 가장 널리 사용되는 방식 # - 행(Row) # - 정답 클래스 # - 열(Colume) # - 예측 클래스 # In[48]: from sklearn.metrics import confusion_matrix print(len(np.where(y_test == True)[0])) print(len(np.where(y_test == False)[0])) confusion = confusion_matrix(y_test, pred_logreg) print("Confusion matrix:\n{}".format(confusion)) # - [***음성*** 정답] - 정답 '9가 아님'의 총 개수: 403 # - [***음성*** 예측] - 예측 '9가 아님'의 총 개수: 401 --> ***True Negative (TN)*** # - [***양성*** 예측] - 예측 '9임'의 총 개수: 2 --> ***False Positive (FP, 거짓 양성)*** --> 잘못된 양성 분류 # # - [***양성*** 정답] - 정답 '9임'의 총 개수: 47 # - [***음성*** 예측] - 예측 '9가 아님'의 총 개수: 8 --> ***False Negative (FN, 거짓 음성)*** --> 잘못된 음성 분류 # - [***양성*** 예측] - 예측 '9임'의 총 개수: 39 --> ***True Positive (TP)*** # # # In[49]: mglearn.plots.plot_confusion_matrix_illustration() # In[50]: mglearn.plots.plot_binary_confusion_matrix() # In[51]: print("Most frequent class:") print(confusion_matrix(y_test, pred_most_frequent)) print("\nDummy model:") print(confusion_matrix(y_test, pred_dummy)) print("\nDecision tree:") print(confusion_matrix(y_test, pred_tree)) print("\nLogistic Regression") print(confusion_matrix(y_test, pred_logreg)) # ###### Accuracy (정확도) # $$# \text{Accuracy} = \frac{\text{TP} + \text{TN}}{\text{TP} + \text{TN} + \text{FP} + \text{FN}} #$$ # # - 전체 샘플 수 중에서 정확히 예측한 것(TP 와 TN)의 비율 # - scikit-learn 에서 score 함수가 반환하는 값 # ##### Precision (정밀도) # $$# \text{Precision} = \frac{\text{TP}}{\text{TP} + \text{FP}} #$$ # # - 양성(Positive)로 예측한 것(TP와 FP)들 중 진짜 양성인 것(TP)의 비율 # - **거짓 양성(FP)의 수를 줄이는 것을 목표**로 할 때 사용하는 지표 # - 신약의 효과 검증 등 임상 시험에 많이 사용 # - **거짓 음성(FN)의 수가 늘어나는 것에 대해 정밀도 수치는 영향받지 않음** # - 양성 예측도 (PPV)라고도 불리움 # ##### Recall (재현율) # $$# \text{Recall} = \frac{\text{TP}}{\text{TP} + \text{FN}} #$$ # # - 진짜 양성인 것(FN과 TP)들 중 올바르게 양성으로 예측된 것(TP)의 비율 # - **거짓 음성(FN)의 수를 줄이는 것**을 목표로 할 때 사용하는 지표 # - 암 진단 # - **거짓 양성(FP)의 수가 늘어나는 것에 대해 재현율 수치는 영향받지 않음*** # - 즉, 건강한 사람이 일부 암 진단을 받더라도 암에 걸린 사람을 빠짐없이 찾는 것이 더 중요 # - 민감도(Sensitivity), 적중률(Hit Rate), 진짜 양성 비율 (TPR)라고도 불리움 # ##### f-score (f-점수) # - $P$: Precision # - $R$: Recall # $$# \text{F} = \frac{1}{\displaystyle \alpha \frac{1}{P} + (1-\alpha) \frac{1}{R}} #$$ # - 정밀도와 재현율은 상충 관계 # - 모든 샘플을 양성 클래스로만 예측한 경우 # - FP와 TP만 존재 # - 재현율: 1, 정밀도는 상대적으로 낮아짐 # - 하나의 샘플만 (올바르게) 양성 클래스로 예측하고 나머지 샘플을 음성 클래스로만 예측한 경우 # - TN과 FN만 존재 # - 정밀도: 1, 재현율은 상대적으로 낮아짐 # - f-score # - 정밀도와 재현율의 조화 평균 # - 정밀도와 재현울을 동시에 고려한 수치이므로 불균한 이진 분류문제의 정확도(Accuracy)보다 더 나은 지표 # - f1-score # - f-score 공식에서 $\alpha=0.5$ # $$# \text{f1-score} = \frac{1}{\displaystyle 0.5 \frac{1}{P} + 0.5 \frac{1}{R}} = 2 \cdot \frac{P \cdot R}{P + R} #$$ # - f-measure (f-측정)이라고도 함 # #### [Note] 주가 변동 이진 분류 예측 # - 특성 데이터 # - 일봉의 종가 기반 # - N개 종목의 과거 M일치의 종가 데이터 # - 1개 샘플의 특성 데이터 크기 N * M # - 하루씩 Shift하면서 새로운 샘플 생성 # - 타겟 데이터 # - 특정 종목의 M+1일의 종가 데이터 # - 직전 M일자 종가보다 M+1일자 종가가 올랐다면 1, 그렇지 않으면 0 # - 두 가지 분류 클래스 # - 1: 양성 (Positive) 클래스 # - 0: 음성 (Negative) 클래스 # - 성능 평가 측정 # - Accuracy는 당연히 높아야 함. # - Precision과 Recall은 상충관계이므로 둘 중 하나를 택하여 더 집중적으로 높여야 한다면 어떤것을 높여야 하나? # - Precision 관점 # - 거짓 양성(FP)을 줄이는 것을 목적 # - 즉, 주가가 올라간다고 예측을 했는 데, 실제로는 하락을 한 경우를 줄이고자 함. # - **재화의 상실** # - Recall 관점 # - 거짓 음성(FN)을 줄이는 것을 목적 # - 즉, 주가가 하락한다고 예측을 했는 데, 실제로는 상승을 한 경우를 줄이고자 함. # - **기회의 상실** # In[52]: from sklearn.metrics import f1_score print("f1 score most frequent: {:.2f}".format(f1_score(y_test, pred_most_frequent))) print("f1 score dummy: {:.2f}".format(f1_score(y_test, pred_dummy))) print("f1 score tree: {:.2f}".format(f1_score(y_test, pred_tree))) print("f1 score logistic regression: {:.2f}".format(f1_score(y_test, pred_logreg))) # - f1 score most frequent 모델의 f1 점수는 TP가 0이므로, 재현율과 정밀도가 모두 0 # - 그러므로 f1 점수 공식에서 분모가 0 # - 위 Warning 메시지의 원인 # - f1-점수로 비교해본 가장 좋은 모델 # - Logistic Regression # - sklearn.metrics.classification_report # - 각 클래스마다 교대로 양성임을 가정 # - 상위 두 개의 출력 라인 # - 해당 클래스가 양성일 때 다음 4개의 값을 출력 # - 정밀도(precision) # - 재현율(recall) # - f1-점수(f1-score) # - 해당 클래스에 실제로 속한 샘플 개수(support) # - 정답 데이터인 y_test에 대한 각 클래스별 샘플 개수 # In[53]: from sklearn.metrics import classification_report print(classification_report(y_test, pred_most_frequent, target_names=["not nine", "nine"])) # In[54]: print(classification_report(y_test, pred_dummy, target_names=["not nine", "nine"])) # In[55]: print(classification_report(y_test, pred_logreg, target_names=["not nine", "nine"])) # ##### Taking uncertainty into account # - 모델 예측의 확신도를 가늠하기 위한 함수 # - decicion_function # - 임계값: 0 # - decision_fuction의 임계값이 0일 때 클래스 분류 # - decision_function() <= 0 --> 클래스 0 (음성 클래스)로 분류 # - decision_function() > 0 --> 클래스 1 (양성 클래스)로 분류 # - predict_proba # - 임계값: 0.5 # In[56]: from mglearn.datasets import make_blobs X, y = make_blobs( n_samples=(400, 50), # 음성 클래스: 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) print(X_train.shape) print(y_train.shape) print() print(X_test.shape) print(y_test.shape) svc = SVC(gamma=.05).fit(X_train, y_train) # In[57]: mglearn.plots.plot_decision_threshold() # - 위 상위 두 개의 그림에서 검은색 동그라미 # - decision_fuction의 임계점이 0일 때와 -0.8일 때의 경계 위치 # - 이 동그라미 내부는 양성 클래스(decision_function() > 0)로 분류, 바깥쪽은 음성 클래스로 분류 # In[58]: print(classification_report(y_test, svc.predict(X_test))) # - 양성 클래스 1에 대해 정밀도(0.35)가 매우 낮음, 재현율(0.67)도 낮음. # - 음성 클래스 0에 대한 샘플 수가 많아서 생긴 결과임 --> 데이터 불균형 # - 이제 클래스 1의 재현율(recall)을 높이는 것이 중요하다고 가정. # - 즉, 거짓 양성(FP)의 수가 늘어나도 중요하지 않음. # - 진짜 양성(TP)을 늘리고 거짓 음성(FN)을 줄이려고 함. # - decision_function의 임계값을 낮추면 클래스 1로 분류되는 경우가 더 많아짐 # In[59]: y_pred_lower_threshold = svc.decision_function(X_test) > -.8 print(y_pred_lower_threshold.shape) # In[60]: print(classification_report(y_test, y_pred_lower_threshold)) # - 클래스 1의 재현율이 1.00 --> 즉, 거짓 음성은 전혀 없음 # - 반면에 정밀도는 다소 낮아짐 # - decision_function 값의 임계점을 고르는 일반적인 방법을 제시하기는 어려움 # ##### Precision-Recall curves (정밀도-재현율 곡선) # - 분류 임계값 조정 작업 # - 정밀도와 재현율의 상충 관계 조정하는 일과 동일 # - 임계값 조정은 비지니스 목표에 의존적 # - 비지니스 목표: 어떤 클래스에 대해 목표로 하는 재현율 또는 정밀도 값을 얻어냄 # - 예를 들어 양성 클래스에 대하여 **90% 재현율 산출**이 비지니스 목표가 될 수 있음 # - ***운영 포인트 (Operating Point)*** 지정 # - 예: **90% 재현율 산출** # - 분류 모델이 목표로 하는 성능지표를 지정하는 작업 # - 비지니스 목표와 연관이 깊음 # - 많은 경우 운영 포인트를 정확하게 지정하는 것은 어려움 # - 이런 경우 임계값을 폭넓게 변경해 가며 정밀도와 재현율을 산출하며 그 장단점을 살펴보는 작업 필요 # - 이를 위해 ***정밀도-재현율 곡선***을 사용 # - sklearn.metrics.precision_recall_curve # - 가능한 모든 임계값에 대한 정밀도와 재현율 값을 리스트로 반환 # In[61]: from sklearn.metrics import precision_recall_curve precision, recall, thresholds = precision_recall_curve(y_test, svc.decision_function(X_test)) print("precision: {}\n".format(precision)) print("recall: {}\n".format(recall)) print("thresholds: {}\n".format(thresholds)) close_zero = np.argmin(np.abs(thresholds)) print(close_zero) print(thresholds[close_zero]) # In[62]: # create a similar dataset as before, but with more samples # to get a smoother curve 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)) # find threshold closest to zero close_zero = np.argmin(np.abs(thresholds)) plt.plot( precision[close_zero], recall[close_zero], 'o', markersize=10, label="threshold zero", fillstyle="none", c='k', mew=2) plt.plot(precision, recall, label="precision recall curve") plt.xlabel("Precision") plt.ylabel("Recall") plt.legend(loc="best") # - 위그림의 파란색 곡선은 decision_function의 가능한 모든 임계값에 대응되는 Precision과 Recall 값을 나타냄 # - 검은색 원은 decision_function의 기본 임계값인 0의 지점을 나타냄 # - 이 지점은 predict 메소드를 호출할 때 사용되는 임계 지점 값 # - 위 정밀도-재현율 곡선은 오른쪽 위로 갈 수록 좋은 분류기 # - 오른쪽 위 --> 정밀도와 재현율이 모두 높은 곳 # - 위 그래프에서 알 수 있는 것 # - 0.9 정도의 높은 Recall을 유지하면서도 0.5 정도의 Precision을 얻을 수 있음 # - 0.5보다 더 높은 Precision을 얻어내기 위해서는 Recall을 많이 손해 봐야 함 # - RandomForestClassifier는 decision_function은 제공하지 않고 predict_proba만 제공 # - rf.predict_proba(X_test)[:, 1] # - 양성 클래스(클래스 1)의 확신 정도값을 가지고 오는 코드 # - 기본 임계값: 0.5 # In[63]: from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier(n_estimators=100, random_state=0, max_features=2) rf.fit(X_train, y_train) # RandomForestClassifier has predict_proba, but not decision_function 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="threshold zero svc", 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], '^', markersize=10, label="threshold 0.5 rf", fillstyle="none", c='k', mew=2) plt.xlabel("Precision") plt.ylabel("Recall") plt.legend(loc="best") # - 높은 Precision 또는 높은 Recall을 얻기 위해서는 RandomForestClassifier가 더 좋은 모델 # - Precision 과 Recall 두 개의 값을 적절히 동시에 높은 값을 얻기 위해서는 SVC가 더 좋은 모델 # - f1-score만으로는 이런 세세한 부분을 비교할 수 없음 # - f1-score는 정밀도-재현율 곡선의 한 지점인 기본 임계값에 대한 점수임 # In[64]: from sklearn.metrics import f1_score print("f1_score of random forest: {:.3f}".format(f1_score(y_test, rf.predict(X_test)))) print("f1_score of svc: {:.3f}".format(f1_score(y_test, svc.predict(X_test)))) # - 어느 모델이 좋은지 보다 정확하게 비교하려면... # - 특정 임계값이나 운영 포인트에 국한하지 않고 전체 곡선에 대한 정보를 요약해야 함 # - ***Average Precision (평균 정밀도)*** # - 정밀도-재현율 곡선의 아랫부분 면적을 계산한 값 # - 항상 0(가장 나쁨)에서 1(가장 좋음)사이의 값을 지님 # - sklearn.metrics.average_precision_score # In[65]: 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("Average precision of random forest: {:.3f}".format(ap_rf)) print("Average precision of svc: {:.3f}".format(ap_svc)) # - 평균 정밀도 측면에서 RandomForestClassifier와 SVC가 큰 차이 없음 # ##### Receiver Operating Characteristics (ROC) and AUC # - 진짜 양성 비율 (TPR): 전체 양성 샘플(TP와 FN)중에서 진짜 양성(TP)로 올바로 분류된 비율 = 재현율 # $$# \text{TPR} = Recall = \frac{\text{TP}}{\text{TP} + \text{FN}} #$$ #
#
# - 거짓 양성 비율 (FPR): 전체 음성 샘플(FP와 TN) 중에서 거짓 양성(FP)로 잘못 분류된 비율 # $$# \text{FPR} = \frac{\text{FP}}{\text{FP} + \text{TN}} #$$ #
#
# - TPR과 FPR의 해석 # - TPR과 FPR은 서로 반비례적인 관계에 있다. 암환자를 진단할 때, 성급한 의사는 아주 조금의 징후만 보여도 암인 것 같다고 할 것이다. 이 경우 TPR은 1에 가까워질 것이다. 그러나 FPR은 반대로 매우 낮아져버린다. (정상인 사람도 다 암이라고 하니까) # # - 반대로 돌팔이 의사라서 암환자를 알아내지 못한다면, 모든 환자에 대해 암이 아니라고 할 것이다. 이 경우 TPR은 매우 낮아져 0에 가까워 질 것이다. 그러나 반대로 FPR은 급격히 높아져 1에 가까워질 것이다.(암환자라는 진단 자체를 안하므로, 암환자라고 잘못 진단 하는 경우가 없음) # - 출처: http://newsight.tistory.com/53 [New Sight] #
#
# # - ROC 곡선 # - ROC curve is created by plotting the true positive rate (TPR) against the false positive rate (FPR) at various threshold settings. # - '수신기 운영 특성 (Receiver Operating Characteristics)'이라는 이름은 신호 탐지 이론에서 비롯 # - 이름 그 자체의 의미는 무시하고 'TPR-FPR 곡선'으로 이해하는 것이 좋음 # In[66]: 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 (recall)") # find threshold closest to zero close_zero = np.argmin(np.abs(thresholds)) plt.plot(fpr[close_zero], tpr[close_zero], 'o', markersize=10, label="threshold zero", fillstyle="none", c='k', mew=2) plt.legend(loc=4) # - ROC 곡선 해석 # - ROC 곡선은 왼쪽 상단에 가까울 수록 이상적임. # - 즉, FPR은 낮게 유지하면서 TPR(재현율)은 높은 뷴류기가 좋음. # - 위 그림에서 기본 임계값 0에 대한 FPR과 TPR값보다는 FPR을 조금 더 늘려주면(0.1 정도) TPR을 상당히 높일 수 있음(0.9 정도) # - 이러한 FPR=0.1 & TPR=0.9을 산출할 수 있는 임계값이 적절한 **운영 포인트**가 될 수 있음 # In[67]: fpr_rf, tpr_rf, thresholds_rf = roc_curve(y_test, rf.predict_proba(X_test)[:, 1]) plt.plot(fpr, tpr, label="ROC Curve SVC") plt.plot(fpr_rf, tpr_rf, label="ROC Curve RF") plt.xlabel("FPR") plt.ylabel("TPR (recall)") plt.plot(fpr[close_zero], tpr[close_zero], 'o', markersize=10, label="threshold zero SVC", 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="threshold 0.5 RF", fillstyle="none", c='k', mew=2) plt.legend(loc=4) # - 두 개의 ROC 곡선 해석 # - RandomForest 모델이 SVC 보다 좀 더 왼쪽 상단으로 ROC 곡선이 위치하는 듯 함 # - 어떤 ROC 곡선이 더 좋은지 알아보기 ROC 곡선아래의 면적을 하나의 값으로 요약할 수 있음 # - AUC (Area Under the (ROC) Curve) # - 0(최악) ~ 1(최선) # - 수집한 데이터가 불균현한 데이터 집합이라면 정확도보다 AUC가 더 의미있는 지표 # - sklearn.metrics.roc_auc_score # In[68]: 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 for Random Forest: {:.3f}".format(rf_auc)) print("AUC for SVC: {:.3f}".format(svc_auc)) # - AUC 측면에서 RandomForest 모델이 SVC 보다 좀 더 좋다고 볼 수 있음 # In[69]: 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.05, 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} accuracy = {:.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") # #### Multi-class classification # - 다중클래스 분류문제에서 불균형 데이터에 대해서 Accuracy(정확도) 지표는 좋은 지표가 되지 못함. # - 훈련 샘플 비율 # - A 클래스: 85% # - B 클래스: 10% # - C 클래스: 5% # - 실제 새로운 데이터도 위와 같은 비율로 출현한다고 하면 아무런 학습이 안된 모델 (Dummy Model)도 85% 정확도를 산출할 수 있음. # In[78]: 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("Shape of Test data: {}".format(y_test.shape)) print("Accuracy: {:.3f}".format(accuracy_score(y_test, pred))) print() print("Confusion matrix:\n{}".format(confusion_matrix(y_test, pred))) # - 다중 클래스 예측 결과에 대한 confusion matrix # - 행: 정답 레이블 # - 열: 예측 레이블 # - 위 confusion matrix에서 레이블 0에 대한 해석 # $# \begin{bmatrix} # TN & FP \\ # FN & TP # \end{bmatrix} # = # \begin{bmatrix} # 413 & 0 \\ # 0 & 37 # \end{bmatrix} #$ # - 클래스 0에 대해서는 거짓 음성(FN)이 없음 # - 첫번째 행(예측 레이블 행)에서 다른 항목들이 모두 0 # - 클래스 0에 대해서는 거짓 양성(FP)이 없음 # - 첫번째 열(정답 레이블 열)에서 다른 항목들이 모두 0 # - Accuracy = 1.0 # - Precision = 1.0 # - Recall = 1.0 # - F1-score = 1.0 #
# - 위 confusion matrix에서 레이블 1에 대한 해석 $# \begin{bmatrix} # TN & FP \\ # FN & TP # \end{bmatrix} # = # \begin{bmatrix} # 402 & 5 \\ # 4 & 39 # \end{bmatrix} #$ # - 클래스 1에 대해서는 거짓 음성(FN)이 4건 # - 클래스 1에 대해서는 거짓 양성(FP)이 5건 # - Accuracy = (402+39)/450 = 0.98 # - Precision = 39/(39+5) = 0.89 # - Recall = 39/(4+39) = 0.91 # - F1-score = 2 x 0.89 x 0.91 / (0.89 + 0.91) = 0.90 # # # - 위 confusion matrix에서 레이블 7에 대한 해석 $# \begin{bmatrix} # TN & FP \\ # FN & TP # \end{bmatrix} # = # \begin{bmatrix} # 402 & 0 \\ # 3 & 45 # \end{bmatrix} #$ # - 클래스 7에 대해서는 거짓 음성(FN)이 3건 # - 클래스 7에 대해서는 거짓 양성(FP)이 0건 # - Accuracy = (402+45)/450 = 0.99 # - Precision = 45/45 = 1.0 # - Recall = 45/(3+45) = 0.94 # - F1-score = 2 x 1.0 x 0.94 / (1.0 + 0.94) = 0.97 # In[71]: scores_image = mglearn.tools.heatmap( confusion_matrix(y_test, pred), xlabel='Predicted label', ylabel='True label', xticklabels=digits.target_names, yticklabels=digits.target_names, cmap=plt.cm.gray_r, fmt="%d") plt.title("Confusion matrix") plt.gca().invert_yaxis() # In[72]: print(classification_report(y_test, pred)) # - 관심있는 클래스를 양성, 그 외의 모든 클래스는 음성으로 두고 precision, recall, f1-score 계산 # - [NOTE] 다중 분류에서 불균형 데이터셋을 위해 가장 많이 사용되는 지표는 f1-score # - 클래스별로 f1-score를 산출한 이후, 전체 클래스에 대한 평균 f1-score 산출 전략 (다중 클래스일 때 반드시 아래 세 개의 항목 중 하나를 average 파라미터 값으로 제시해야 함) # - macro 평균 # - 클래스별 f1-score에 가중치를 고려하지 않음 # - weighted 평균 (보통은 이것을 선택) # - 클래스별 테스트 데이터 샘플 수로 가중치를 두어 f1-score 계산 (classification_report에 노출되는 값) # - micro 평균 # - 모든 클래스별로 FP, FN, TP의 총 수를 헤아린 다음 산출 # In[73]: print("Macro average f1 score: {:.3f}".format(f1_score(y_test, pred, average="macro"))) print("Weighted average f1 score: {:.3f}".format(f1_score(y_test, pred, average="weighted"))) print("Micro average f1 score: {:.3f}".format(f1_score(y_test, pred, average="micro"))) # #### Regression metrics # - [note]: http://scikit-learn.org/stable/modules/model_evaluation.html#regression-metrics #

# - **Explained variance score** # ![...](http://scikit-learn.org/stable/_images/math/494cda4d8d05a44aa9aa20de549468e4d121e04c.png) # # - $\hat{y}$: the estimated target output # - $y$: the corresponding (correct) target output # - $Var$: Variance # - **Mean absolute error** # ![...](http://scikit-learn.org/stable/_images/math/c38d771fb5eb121916c06cf8c651363583d17794.png) # # - $\hat{y}_i$: the predicted value of the $i$-th sample # - $y_i$: the corresponding (correct) target output # - $n_{samples}$: the number of target samples # - **Mean squared error** # ![...](http://scikit-learn.org/stable/_images/math/44f36557fef9b30b077b21550490a1b9a0ade154.png) # # - $\hat{y}_i$: the predicted value of the $i$-th sample # - $y_i$: the corresponding (correct) target output # - $n_{samples}$: the number of target samples # - **Mean squared logarithmic error** # ![...](http://scikit-learn.org/stable/_images/math/7ab9dd9a29d207d773d08e4d1a0fc370f9b1fa35.png) # - This metric is best to use when targets having exponential growth, such as population counts, average sales of a commodity over a span of years etc. # - $\hat{y}_i$: the predicted value of the $i$-th sample # - $y_i$: the corresponding (correct) target output # - $n_{samples}$: the number of target samples # - **Median absolute error** # ![...](http://scikit-learn.org/stable/_images/math/9252f9de0d8c2043cf34a26e6f2643a6e66540b9.png) # - It is particularly interesting because it is robust to outliers. # - The loss is calculated by taking the median of all absolute differences between the target and the prediction. # - $\hat{y}_i$: the predicted value of the $i$-th sample # - $y_i$: the corresponding (correct) target output # - **$R^2$ score (the coefficient of determination)** # ![...](http://scikit-learn.org/stable/_images/math/bdab7d608c772b3e382e2822a73ef557c80fbca2.png) # - where # ![...](http://scikit-learn.org/stable/_images/math/4b4e8ee0c1363ed7f781ed3a12073cfd169e3f79.png) # - It provides a measure of how well future samples are likely to be predicted by the model. # - $\hat{y}_i$: the predicted value of the $i$-th sample # - $y_i$: the corresponding (correct) target output # In[103]: from sklearn.metrics import explained_variance_score from sklearn.metrics import mean_absolute_error from sklearn.metrics import mean_squared_error from sklearn.metrics import mean_squared_log_error from sklearn.metrics import median_absolute_error from sklearn.metrics import r2_score y_test = [3, -0.5, 2, 7] y_pred = [2.5, 0.0, 2, 8] print("explained_variance_score:", explained_variance_score(y_test, y_pred)) print("mean_absolute_error:", mean_absolute_error(y_test, y_pred)) print("mean_squared_error:", mean_squared_error(y_test, y_pred)) print("mean_squared_log_error:", mean_squared_log_error(y_test, y_pred)) print("median_absolute_error:", median_absolute_error(y_test, y_pred)) print("r2_score:", r2_score(y_test, y_pred)) # - [NOTE]: 전형적인 교차검증을 활용한 Regression 모델 구성 및 성능 측정 # In[124]: import warnings warnings.filterwarnings('ignore') from sklearn.ensemble import GradientBoostingRegressor X, y = mglearn.datasets.make_wave(n_samples=200) print("X shape: {}".format(X.shape)) print("y shape: {}".format(y.shape)) X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42) print("X_train shape: {}".format(X_train.shape)) print("X_test shape: {}".format(X_test.shape)) print() param_grid = { 'learning_rate': [0.001, 0.01, 0.1, 1, 10, 100], 'alpha': [0.1, 0.3, 0.5, 0.7, 0.9] } estimator = GradientBoostingRegressor() grid_search = GridSearchCV( estimator = estimator, param_grid = param_grid, n_jobs = -1, cv = 5, return_train_score = True ) grid_search.fit(X_train, y_train) print("Best cross-validation accuracy: {:.2f}".format(grid_search.best_score_)) print("Best parameters:\n{}".format(grid_search.best_params_)) print("Best estimator:\n{}".format(grid_search.best_estimator_)) print("Test set score: {:.2f}".format(grid_search.score(X_test, y_test))) y_pred = gbr.predict(X_test) print() # Possible scoring print("explained_variance_score:", explained_variance_score(y_test, y_pred)) print("mean_absolute_error:", mean_absolute_error(y_test, y_pred)) print("mean_squared_error:", mean_squared_error(y_test, y_pred)) #print("mean_squared_log_error:", mean_squared_log_error(y_test, y_pred)) print("median_absolute_error:", median_absolute_error(y_test, y_pred)) print("r2_score:", r2_score(y_test, y_pred)) # ### Using evaluation metrics in model selection # - [note]: http://scikit-learn.org/stable/modules/model_evaluation.html#scoring-parameter # - 이진 분류 # In[118]: # default scoring for classification is accuracy scores = cross_val_score(SVC(), digits.data, digits.target == 9) print("Default scoring: {}".format(scores)) # providing scoring="accuracy" doesn't change the results scores2 = cross_val_score(SVC(), digits.data, digits.target == 9, scoring="accuracy") print("Explicit accuracy scoring: {}".format(scores2)) print() # 곡선의 면적을 활용한 성능 측정 (Recommended) roc_auc = cross_val_score(SVC(), digits.data, digits.target == 9, scoring="roc_auc") print("ROC_AUC scoring: {}".format(roc_auc)) average_precision = cross_val_score(SVC(), digits.data, digits.target == 9, scoring="average_precision") print("Average Precision scoring: {}".format(average_precision)) print() # 다양한 성능 측정 (Not Recommended) precision = cross_val_score(SVC(), digits.data, digits.target == 9, scoring="precision_weighted") print("Precision scoring: {}".format(precision)) recall = cross_val_score(SVC(), digits.data, digits.target == 9, scoring="recall_weighted") print("Precision scoring: {}".format(recall)) f1_score = cross_val_score(SVC(), digits.data, digits.target == 9, scoring="f1_weighted") print("F1_score scoring: {}".format(f1_score)) # - 다중 분류 # In[119]: scores = cross_val_score(SVC(), digits.data, digits.target, scoring="accuracy") print("Explicit accuracy scoring: {}".format(scores)) f1_weighted = cross_val_score(SVC(), digits.data, digits.target, scoring="f1_weighted") print("F1_weighted scoring: {}".format(f1_weighted)) # - GridSearchCV에 다양한 scoring 적용 # In[120]: X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target == 9, random_state=0) # we provide a somewhat bad grid to illustrate the point: param_grid = {'gamma': [0.0001, 0.01, 0.1, 1, 10]} # using the default scoring of accuracy: grid = GridSearchCV(SVC(), param_grid=param_grid) grid.fit(X_train, y_train) print("Grid-Search with accuracy") print("Best parameters:", grid.best_params_) print("Best cross-validation score (accuracy)): {:.3f}".format(grid.best_score_)) print("Test set AUC: {:.3f}".format(roc_auc_score(y_test, grid.decision_function(X_test)))) print("Test set accuracy: {:.3f}".format(grid.score(X_test, y_test))) print() # using AUC scoring instead: grid = GridSearchCV(SVC(), param_grid=param_grid, scoring="roc_auc") grid.fit(X_train, y_train) print("Grid-Search with AUC") print("Best parameters:", grid.best_params_) print("Best cross-validation score (AUC): {:.3f}".format(grid.best_score_)) print("Test set AUC: {:.3f}".format(roc_auc_score(y_test, grid.decision_function(X_test)))) print("Test set accuracy: {:.3f}".format(grid.score(X_test, y_test))) # In[121]: from sklearn.metrics.scorer import SCORERS print("Available scorers:\n{}".format(sorted(SCORERS.keys()))) # ### Summary and Outlook