Python {Article060}

ようこそ「Python」へ...

タイタニック号で機械学習の交差検証(クロスバリデーション)を学ぶには【sklearn Cross-validation】

タイタニックのサバイバルデータで機械学習(Machine Learning)シリーズは次の8つの記事から構成されています。 機械学習に興味のある方は以下に掲載されている記事を順番に読むことをおすすめします。

この記事では機械学習(ML: Machine Learning)で複数のモデルを効率的に評価できるクロスバリデーション(Cross-Validation)の使い方について解説します。 機械学習でモデルを評価するには学習用のデータを何等分かに分割して複数回学習させてそれぞれのスコアの平均値・中央値をそのモデルの評価とします。

これら一連の処理をPythonで記述すると以下のようになります。 行2-3ではデータを取り込んでいます。 行6-7ではデータを学習用とテスト用に分割しています。 ここでは学習用とテスト用を「7対3」の比率で分割させています。 行11-13では学習させてモデルのスコアを取得しています。 複数回学習させるには(2)と(3)の処理を繰り返す必要があります。

# 1) Load the data
from sklearn.datasets import load_digits
digits = load_digits()    

# 2) Split the data
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, test_size=0.3)

# 3) Fit & Score    
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(solver='liblinear', multi_class='ovr')
model.fit(X_train, y_train)
model.score(X_test, y_test)
Skleanはデータの分割とモデルの評価を効率的に行うために「KFold」と「cross_val_score」を用意しています。 KFoldを使用するとデータをK等分に分割してモデルを評価することができます。 行2ではKFoldでデータを3回分割するように指定しています。 行4-5では配列(1-9)のデータにKFoldを適用して3回分割させています。 配列のデータは行7-9のように分割されます。
from sklearn.model_selection import KFold
kf = KFold(n_splits=3)

for train_index, test_index in kf.split([1,2,3,4,5,6,7,8,9]):
    print(train_index, test_index)

# [3 4 5 6 7 8] [0 1 2]
# [0 1 2 6 7 8] [3 4 5]
# [0 1 2 3 4 5] [6 7 8]
データを3回分割させて学習&評価を行うには、次のような処理が必要になります。 行1-2ではKFoldを設定しています。ここではデータを3回分割するように設定しています。 行4-10のforループではデータの分割、学習、評価の一連の処理を行っています。 評価の結果は行12-14のように表示されます。
from sklearn.model_selection import KFold
kf = KFold(n_splits=3)

for train_index, test_index in kf.split(digits.data, digits.target):
    X_train, X_test, y_train, y_test = digits.data[train_index], digits.data[test_index], 
                                       digits.target[train_index], digits.target[test_index]   
    model = LogisticRegression(solver='liblinear', multi_class='ovr')
    model.fit(X_train, y_train)
    score = model.score(X_test, y_test)    
    print('score=', score)

# score= 0.8964941569282137
# score= 0.9515859766277128
# score= 0.9115191986644408
ここではデータの分割、学習、評価の一連の処理をCross-Validationを利用して行っています。 さらに、3つのモデル(LogisticRegression, SVC, RandomForestClassifier)を同時に評価しています。 行1ではKFoldに5回データを分割するように設定しています。 行3-5では評価するモデルを定義しています。 ここでは「LogisticRegression, SVC, RandomForestClassifier」の3種類のモデルを評価します。

行7-15では関数「model_fit()」を定義しています。 この関数ではデータの分割、学習、評価を行ってスコアを表示しています。 行17では関数を呼び出しています。 スコアは行19-27のように表示されます。 それぞれのモデルが5回評価されていることが分かります。 Cross-Validationの概念については図Fを参照してください。

評価にバラツキがあるときは、「KFold」の代わりに「StratifiedKFold」を使用します。
kf = KFold(n_splits=5, shuffle=True, random_state=0)

clf = [LogisticRegression(solver='liblinear', multi_class='ovr'),  # 0
       SVC(gamma='auto'),                                          # 1
       RandomForestClassifier(n_estimators=40)]                    # 2

def model_fit():
    for i in range(len(clf)):
        score = cross_val_score(clf[i], X_train, y_train, cv=kf, n_jobs=1, scoring='accuracy')      

        print(f'Score of Model({i}):', 
            '{0:.2f}'.format(round(np.mean(score)*100, 2)), clf[i].__class__.__name__)
        
        print('Cross-validation scores:', score)                    
        print('-'*80)

model_fit()

# Score of Model(0): 96.58 LogisticRegression
# Cross-validation scores: [0.975      0.95833333 0.9625     0.9748954  0.958159  ]
# --------------------------------------------------------------------------------
# Score of Model(1): 33.65 SVC
# Cross-validation scores: [0.27916667 0.26666667 0.35416667 0.42259414 0.35983264]
# --------------------------------------------------------------------------------
# Score of Model(2): 97.16 RandomForestClassifier
# Cross-validation scores: [0.97916667 0.975      0.9625     0.9748954  0.9665272 ]
# --------------------------------------------------------------------------------


最近よくAI(Artificial Intelligence)、ML(Machine Learning)、DL(Deep Learning)という言葉を聞きますが、 AIはMLとDLを含んだ総称です。 そしてMLはDLを含んでいます。 そしてDLはNeural Networks(ニューラルネットワーク)を使用しています。 MLとDLはAIのサブセットということになります。 DL(Deep Learning)については「記事(Article028)」で詳しく解説しています。

ML(Machine Learning)は、Supervised Learning, UnSupervised Learning, Reinforcement Learningの3つに分類されています。 それぞれのタイプの概念は図(B, C, D)を参照してください。 そしてSupervised LearningはアルゴリズムによりReguression, Classfication, Clusteringの3つに分類されています。 それぞれのアルゴリズムの種類は図(E)を参照してください。

今回予測するのは、 タイタニック号の乗船客が「生存するか」「死亡するか」の2択ですから、 MLのClassficationのアルゴリズムを利用することになります。 ここではClassficationの8種類のアルゴリズム (KNeighborsClassifier, DecisionTreeClassifier, RandomForestClassifier, GaussianNB, SVC, ExtraTreeClassifier, GradientBoostingClassifier, AdaBoostClassifier) を使用して乗船客の生死を予測します。

ML(Macine Learning)を使用して予測するとき、予測するデータの属性によりアルゴリズム(モデル)の評価方法が異なります。 「記事(Article056)」で解説した売上データからお客さんが商品を「買う、買わない」といった予測では、 accuracy_score(正解率)で予測値を評価してもとくに問題はありません。 ところが、今回のタイタニックのような人間の「生死」を予測するケースでは、accuracy_score(正解率)だけで予測を評価することはできません。 たとえば、モデルが「死亡する」と予測して、その予測が外れてもとくに問題にはなりません。 ところが、モデルが「生存する」と予測して、その予測が外れると「死亡」するということになるので問題になります。 このような場合は、モデルの予測値を4パターンに分けて評価する必要があります。 MLはこれら4パターンの評価情報を取得する方法としてclassification_report()とconfusion_matrix()メソッドを用意しています。

「初級」編では、データの取り込み、データの分析(可視化)、学習、予測、予測評価の順番に説明します。 「予測評価」では、予測を評価するための情報を取得する3種類の方法(メソッド)を解説します。 さらに、なぜclassification_report(), confusion_matrix(), accuracy_score()の3種類のメソッドが用意されているのか、 そして、これらのメソッドで取得した評価情報をどのように活用するのかについても説明しています。

「中級」編では、Pandas、Matplotlibを使用したデータ分析を詳しく解説しています。 データを分析するには可視化することが重要ですが、Pandasのplot()メソッドを使用する簡単にデータを可視化することができます。 さらにMatplotlibを使用するとグラフを見栄えよくする、見やすくする、グラフにさまざまな補足情報を表示するといったことが可能になります。

「上級」編では、複数のアルゴリズム(モデル)を使用して実際に予測して、予測を評価する方法について解説しています。 予測値を調整するには、モデルにさまざまなパラメータを追加して、さらにパラメータの値(範囲)も同時に調整する必要があります。 これらを効率的に行う方法としてPipelineを使用したGridSearchCV()、RandomizedSearchCV()メソッドについて解説しています。 RandomizedSearchCV()を使用すると、モデルにどのようなパラメータを追加すると予測が改善するかを効率的に行うことができます。 さらに、GridSerachCV()を使用すると、モデルのパラメータの値(範囲)の調整を効率的に行うことができます。

ここではVisula Studio Code(VSC)の「Python Interactive window」 を使用してJupter Notebookのような環境で説明します。 VSCを通常の環境からインタラクティブな環境に切り換えるにはコードを記述するときコメント「# %%」を入力します。 詳しい、操作手順については「ここ」 を参照してください。 インタラクティブな環境では、Pythonの「print(), plt.show()」などを使う必要がないので掲載しているコードでは省略しています。 VSCで通常の環境で使用するときは、必要に応じて「print(), plt.show()」等を追加してください。

この記事では、Pandas、Matplotlibのライブラリを使用しますので 「記事(Article001) | 記事(Article002) | 記事(Article003) | 記事(Article004)」 を参照して事前にインストールしておいてください。 Pythonのコードを入力するときにMicrosoftのVisula Studio Codeを使用します。 まだ、インストールしていないときは「記事(Article001)」を参照してインストールしておいてください。

説明文の左側に図の画像が表示されていますが縮小されています。 画像を拡大するにはマウスを画像上に移動してクリックします。 画像が拡大表示されます。拡大された画像を閉じるには右上の[X]をクリックします。 画像の任意の場所をクリックして閉じることもできます。

click image to zoom!
図A: AI/ML/DL
click image to zoom!
図B: Supervised
click image to zoom!
図C: UnSupervised
click image to zoom!
図D: Reinforcement
click image to zoom!
図E: Algorithms
click image to zoom!
図F: Cross-Validation
click image to zoom!
Cross-Validation[1]
click image to zoom!
Cross-Validation[2]


Cross-Validationを使用して複数のモデルを評価する

  1. まずはPythonのライブラリを取り込む

    Visual Studio Code(VSC)を起動したら新規ファイルを作成して行1-24をコピペします。 行3-22ではPythonのライブラリを取り込んでいます。 行24ではPythonの警告を抑止しています。 ライブラリをまだインストールしていないときは「pip install」で事前にインストールしておいてください。
    ### Import the libraries 
    
    from functools import reduce
    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    from pandas.core.reshape.reshape import stack 
    import seaborn as sns
    
    # Importing Classifier Modules
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.tree import DecisionTreeClassifier, ExtraTreeClassifier
    from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier, BaggingClassifier, AdaBoostClassifier, GradientBoostingClassifier
    from sklearn.naive_bayes import GaussianNB
    from sklearn.svm import SVC     # SVC: Support Vecitor Classification, SVM: Support Vector Machines
    import numpy as np
    
    # Cross Validation(k-fold)
    from sklearn.model_selection import KFold
    from sklearn.model_selection import cross_val_score
    
    import warnings
    
    warnings.simplefilter('ignore')
    click image to zoom!
    図1
    図1はVisual Studio Code(VSC)の画面です。
  2. PandasのDataFrameにタイタニックのサバイバルデータを取り込む

    行2, 4ではCSVファイルのパスを定義しています。 当サイトからダウンロードするときは行3, 5のコメント(#)を外してください。 行6, 7ではPandasのread_csv()メソッドでCSVファイルをDataFrameに取り込んでいます。
    ### Load the data (train, test)
    train_file = 'data/csv/titanic/train_cleaned.csv'
    #train_file = 'https://money-or-ikigai.com/menu/python/article/data/titanic/train_cleaned.csv'
    test_file = 'data/csv/titanic/test_cleaned.csv'
    #test_file = 'https://money-or-ikigai.com/menu/python/article/data/titanic/test_cleaned.csv'
    train = pd.read_csv(train_file)
    test = pd.read_csv(test_file)       # ★ NO Survived column
    click image to zoom!
    図2
    図2は実行画面です。 VSCのインタラクティブ・ウィンドウにDataFrame(train, test)の構造が表示されています。 DataFrame(train)には891人の乗客データが格納されています。 DataFrame(test)には418人の乗客データが格納されています。
  3. 学習用、テスト用のデータを用意する

    ### Prepare the data (X_train, y_train, X_test)
    X = train.copy()
    # drop Survived column and copy to X_train
    X_train = X.drop('Survived', axis=1)
    
    # copy train to y_train
    y_train = X['Survived']
    
    X_test = test.copy()
    click image to zoom!
    図3-1
    図3-1は実行画面です。 PandasのDataFrame(X_train)とSeries(y_train)の構造と内容が表示されています。
    click image to zoom!
    図3-2
    図3-2は実行画面です。 PandasのDataFrame(X_test)の構造と内容が表示されています。
  4. Cross-Validationで8種類のモデルを評価する

    行4ではKFoldのインスタンスを生成しています。 ここではデータを10回分割するように設定しています。 行10-17では8種類のモデルを定義しています。 行20-28では関数「model_fit()」を定義しています。 この関数ではCross-validationで8種類のモデルを評価してスコアを表示しています。 行30では関数「model_fit()」を呼び出しています。
    ### Modeling
    
    ### Cross Validation(k-fold)
    k_fold = KFold(n_splits=10, shuffle=True, random_state=0)
    # KFold(n_splits=5, *, shuffle=False, random_state=None)
    
    ### Cross Validation Score()
    # cross_val_score(clf[i], X_train, y_train, cv=k_fold, n_jobs=1, scoring=scoring)
    
    clf = [ KNeighborsClassifier(n_neighbors=13),     
            DecisionTreeClassifier(),                   
            RandomForestClassifier(n_estimators=13),    
            GaussianNB(),                               
            SVC(),                                          
            ExtraTreeClassifier(),                       
            GradientBoostingClassifier(n_estimators=10, learning_rate=1, max_features=3, max_depth=3, random_state=10),  
            AdaBoostClassifier()]                       
                           
    
    def model_fit():
        for i in range(len(clf)):
            score = cross_val_score(clf[i], X_train, y_train, cv=k_fold, n_jobs=1, scoring='accuracy')
    
            print(f'Score of Model({i}):', 
                '{0:.2f}'.format(round(np.mean(score)*100, 2)), clf[i].__class__.__name__)
            
            #print('Cross-validation scores:', score)                    
            #print('-'*80)
    
    model_fit()
    click image to zoom!
    図4
    図4は実行結果です。 8種類のモデルのスコアが表示されています。 SVC(SVM)が「83.50」でもっとも高いスコアになっています。 行4のKFold()で「n_splits=10」を指定しているので各モデルとも10回学習させて評価させています。
  5. ここで解説したコードをまとめて掲載

    最後にここで解説したすべてのコードをまとめて掲載しましたので参考にしてください。
    
    ### Import the libraries
    #import urllib.request
    #from PIL import Image
    #import os, os.path
    #import os
    
    from functools import reduce
    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    from pandas.core.reshape.reshape import stack # Plot the graphes
    import seaborn as sns
    
    # Importing Classifier Modules
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.tree import DecisionTreeClassifier, ExtraTreeClassifier
    from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier, BaggingClassifier, AdaBoostClassifier, GradientBoostingClassifier
    from sklearn.naive_bayes import GaussianNB
    from sklearn.svm import SVC     # SVC: Support Vecitor Classification, SVM: Support Vector Machines
    import numpy as np
    
    # Cross Validation(k-fold)
    from sklearn.model_selection import KFold
    from sklearn.model_selection import cross_val_score
    
    import warnings
    
    warnings.simplefilter('ignore')
    
    
    # %%
    
    ### Load the data (train, test)
    train_file = 'data/csv/titanic/train_cleaned.csv'
    #train_file = 'https://money-or-ikigai.com/menu/python/article/data/titanic/train_cleaned.csv'
    test_file = 'data/csv/titanic/test_cleaned.csv'
    #test_file = 'https://money-or-ikigai.com/menu/python/article/data/titanic/test_cleaned.csv'
    train = pd.read_csv(train_file)
    test = pd.read_csv(test_file)       # ★ NO Survived column 
    
    
    # --------------------------------- Fix train and test data
    
    #train.shape
    # (891, 23)    1309 rows, 23 columns
    #test.shape
    # (418, 22)     418 rows, 22 columns    ★ NO Survived column
    
    #train.info()
    # RangeIndex: 891 entries, 0 to 890
    # Data columns (total 23 columns):
    #  #   Column       Non-Null Count  Dtype  
    # ---  ------       --------------  -----  
    #  0   PassengerId  891 non-null    int64  
    #  1   Survived     891 non-null    int64       0 = No, 1 = Yes 
    #  2   Pclass       891 non-null    int64       Ticket class 1 = 1st, 2 = 2nd, 3 = 3rd
    #  3   Name         891 non-null    object 
    #  4   Sex          891 non-null    object 
    #  5   Age          714 non-null    float64
    #  6   SibSp        891 non-null    int64       # of siblings / spouses aboard the Titanic
    #  7   Parch        891 non-null    int64       # of parents / children aboard the Titanic          
    #  8   Ticket       891 non-null    object 
    #  9   Fare         891 non-null    float64     Ticket number
    #  10  Cabin        204 non-null    object      Cabin number
    #  11  Embarked     889 non-null    object      Port of Embarkation C = Cherbourg, Q = Queenstown, S = Southampton
    
    #  12  Embarked_copy   891 non-null    object 
    #  13  Title           891 non-null    object 
    #  14  Title_map       891 non-null    int64  
    #  15  Sex_map         891 non-null    int64  
    #  16  Age_map         891 non-null    int64  
    #  17  Embarked_map    891 non-null    int64  
    #  18  Fare_map        891 non-null    int64  
    #  19  Cabin_x         204 non-null    object 
    #  20  Cabin_map       891 non-null    float64
    #  21  FamilySize      891 non-null    int64  
    #  22  FamilySize_map  891 non-null    float64
    # dtypes: float64(4), int64(11), object(8)
    # memory usage: 160.2+ KB
    
    
    #train.describe()
    
    #train.isnull().sum()
    # PassengerId         0 => Remove
    # Survived            0 => Remove
    # Pclass              0
    # Name                0 => Remove added
    # Sex                 0 => Remove
    # Age                 0 => Remove
    # SibSp               0 => Remove
    # Parch               0 => Remove
    # Ticket              0 => Remove
    # Fare                0 => Remove
    # Cabin             687 ★  => Remove
    # Embarked            0 => Remove
    # Embarked_copy       0 => Remove
    # Title               0 => Remove
    # Title_map           0
    # Sex_map             0
    # Age_map             0
    # Embarked_map        0
    # Fare_map            0
    # Cabin_x           687 ★ => Remove
    # Cabin_map           0
    # FamilySize          0 => Remove
    # FamilySize_map      0
    
    
    #test.isnull().sum()    ★ NO Survived column
    
    # PassengerId         0 R
    # Pclass              0
    # Name                0 R
    # Sex                 0 R
    # Age                 0 R
    # SibSp               0
    # Parch               0
    # Ticket              0
    # Fare                0 R
    # Cabin             327 R
    # Embarked            0 R
    # Embarked_copy       0 R
    # Title               0 R
    # Title_map           0
    # Sex_map             0
    # Age_map             0
    # Embarked_map        0
    # Fare_map            0
    # Cabin_x           327 R
    # Cabin_map           0
    # FamilySize          0 R
    # FamilySize_map      0 
    
    
    # %%
    
    #train.isnull().sum()
    # PassengerId         0 => Remove
    # Survived            0 => Remove
    # Name                0 => Remove added
    # Sex                 0 => Remove
    # Age                 0 => Remove
    # SibSp               0 => Remove
    # Parch               0 => Remove
    # Ticket              0 => Remove
    # Fare                0 => Remove
    # Cabin             687 ★  => Remove
    # Embarked            0 => Remove
    # Embarked_copy       0 => Remove
    # Title               0 => Remove
    # Cabin_x           687 ★ => Remove
    # FamilySize          0 => Remove
    
    # features_drop = ['Name','Sex','Age','SibSp','Parch',
    #     'Ticket','Fare','Cabin','Embarked','Embarked_copy',
    #     'Title','Cabin_x','FamilySize','PassengerId'
    # ]
    
    # %%
    
    ### Prepare the data (X_train, y_train, X_test)
    #X = train.drop(features_drop, axis=1)
    X = train.copy()
    # drop Survived column and copy to X_train
    X_train = X.drop('Survived', axis=1)
    
    # copy train to y_train
    y_train = X['Survived']
    
    #X_test = test.drop(features_drop, axis=1)
    X_test = test.copy()
    
    
    # train_data.shape  => (891, 9)
    #  #   Column          Non-Null Count  Dtype  
    # ---  ------          --------------  -----  
    #  0   Pclass          891 non-null    int64  
    #  1   Name            891 non-null    object 
    #  2   Title_map       891 non-null    int64  
    #  3   Sex_map         891 non-null    int64  
    #  4   Age_map         891 non-null    int64  
    #  5   Embarked_map    891 non-null    int64  
    #  6   Fare_map        891 non-null    int64  
    #  7   Cabin_map       891 non-null    float64
    #  8   FamilySize_map  891 non-null    float64
    
    
    # target.shape => (891,)
    # type(target) => pandas.core.series.Series copied from train[(Survived)]  
    
    
    # %%
    
    ### Modeling
    
    ### Cross Validation(k-fold)
    k_fold = KFold(n_splits=10, shuffle=True, random_state=0)
    # KFold(n_splits=5, *, shuffle=False, random_state=None)
    
    ### Cross Validation Score()
    # cross_val_score(clf[i], X_train, y_train, cv=k_fold, n_jobs=1, scoring=scoring)
    
    # model_type = ['KNeighborsClassifier','DecisionTreeClassifier','RandomForestClassifier',
    #     'GaussianNB','SVC(SVM)','ExtraTreeClassifier','GradientBoostingClassifier',
    #     'AdaBoostClassifier','ExtraTreesClassifier']
    
    # learning_rates = [0.05, 0.1, 0.25, 0.5, 0.75, 1]
    clf = [ KNeighborsClassifier(n_neighbors=13),       # 0
            DecisionTreeClassifier(),                   # 1
            RandomForestClassifier(n_estimators=13),    # 2
            GaussianNB(),                               # 3
            SVC(),                                      # 4    
            ExtraTreeClassifier(),                      # 5 
            GradientBoostingClassifier(n_estimators=10, learning_rate=1, max_features=3, max_depth =3, random_state = 10),  # 6
            AdaBoostClassifier(),                       # 7
            ExtraTreesClassifier()]                     # 8        
    
    
    def model_fit():
        for i in range(len(clf)):
            score = cross_val_score(clf[i], X_train, y_train, cv=k_fold, n_jobs=1, scoring='accuracy')
    
            print(f'Score of Model({i}):', 
                '{0:.2f}'.format(round(np.mean(score)*100, 2)), clf[i].__class__.__name__)
            
            #print('Cross-validation scores:', score)                    
            #print('-'*80)        
    
    model_fit()
    
    # round(np.mean(score)*100,2)
    # print('Score of :\n',score)
    
    # Score of Model(0): 82.38 KNeighborsClassifier
    # Score of Model(1): 79.46 DecisionTreeClassifier
    # Score of Model(2): 82.04 RandomForestClassifier
    # Score of Model(3): 78.78 GaussianNB
    # Score of Model(4): 83.50 SVC(SVM) ★
    # Score of Model(5): 79.80 ExtraTreeClassifier
    # Score of Model(6): 82.15 GradientBoostingClassifier
    # Score of Model(7): 81.03 AdaBoostClassifier
    # Score of Model(8): 80.70 ExtraTreesClassifier