タイタニック号で機械学習のパイプライン(Pipeline)を学ぶには【sklearn Pipeline】
タイタニックのサバイバルデータで機械学習(Machine Learning)シリーズは、次の8つの記事から構成されています。
機械学習に興味のある方は、以下に掲載されている記事を順番に読むことをおすすめします。
この記事では機械学習(ML: Machine Learning)でクロスバリデーション(Cross-Validation)を行うときにパイプライン(Pipeline)を使う方法を解説します。
「記事(Article048)」ではデータをラングリングするとき、Pandasのget_dummies()メソッドを使用しました。
そして、「記事(Article059)」ではデータをラングリングするとき、
Pandasのapply(), map(), transform()メソッド等を使用しました。
しかし、データのラングリングをPandasで行うと新規の検証データを取得したとき、その都度検証データのラングリングが必要になります。
なので、Pandasでのラングリングは可能な限り回避してSklearnの「make_column_transformer, OneHotEncoder」を使用します。
make_column_transformer()を使用するとPandasのget_dummies()と同等の処理が可能になります。
この場合、新規の検証データを取得したときデータのラングリングを行わないでそのデータを評価することができるといったメリットがあります。
ここではCross-Validationを行うときに、
Pipelineを使用してPandasの列「Sex, Embarked」をmake_column_transformer()でstr型からbit型に変換します。
行5-7ではPandasのDataFrameの列「Sex(性別)」「Embarked(乗船港)」の値をstr型からbit型に変換します。
行20ではmake_pipeline()でmake_column_transformerとClassifierを連結しています。
つまり、モデルの評価を行う前にPandasのDataFrameの特定の列をstr型からbit型に変換してラングリングさせています。
行21ではcross_val_score()にPipelineを指定しています。
from sklearn.compose import make_column_transformer
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import OneHotEncoder
col_trans = make_column_transformer(
(OneHotEncoder(), ['Sex', 'Embarked']),
remainder='passthrough')
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(),
ExtraTreesClassifier()]
for i in range(len(clf)):
pipe = make_pipeline(col_trans, clf[i])
score = cross_val_score(pipe, X, y, cv=5, n_jobs=1, scoring='accuracy')
最近よく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]をクリックします。
画像の任意の場所をクリックして閉じることもできます。
Cross-ValidationにPipelineを使用してモデルを評価する前にmake_column_transformer()を行う
-
まずはPythonのライブラリを取り込む
Visual Studio Code(VSC)を起動したら新規ファイルを作成して行1-34をコピペします。
行2-32ではPythonのライブラリを取り込んでいます。
行34では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
# Cross Validation(k-fold)
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
# Pipeline and GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
import sklearn.metrics as skm
# Make
from sklearn.compose import make_column_transformer
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import OneHotEncoder
import warnings
warnings.simplefilter('ignore')
図1はVisual Studio Code(VSC)の画面です。
-
PandasのDataFrameにタイタニックのサバイバルデータを取り込む
行2ではCSVファイルのパスを定義しています。
当サイトからダウンロードするときは行3のコメント(#)を外してください。
行4ではPandasのread_csv()メソッドでCSVファイルをDataFrameに取り込んでいます。
### Load the data
csv_file = 'data/csv/titanic/train.csv'
#csv_file = 'https://money-or-ikigai.com/menu/python/article/data/titanic/train.csv'
raw = pd.read_csv(csv_file)
図2は実行画面です。
VSCのインタラクティブ・ウィンドウにDataFrame(raw)の構造と内容が表示されています。
CSVファイルには891人の乗客データが格納されています。
-
データをクリーンナップする
行19-20ではDataFrameの列「Embarked」の値がNullの行を削除しています。
行24-31ではDataFrameの列「Age」の値がNullのとき年齢の中央値(median)で置換しています。
ここでは段順に年齢の中央値を計算するのはなく敬称(Mr, Miss, Mrs,...)毎の中央値を計算しています。
行57-58ではDataFrameから不要の列「PassengerId, Name, Ticket, Cabin, Title」を削除しています。
### Data Cleanup
raw.isnull().sum()
# raw
# PassengerId 0
# Survived 0
# Pclass 0
# Name 0
# Sex 0
# Age 177 => Fill with median
# SibSp 0
# Parch 0
# Ticket 0
# Fare 0
# Cabin 687
# Embarked 2 => Remove rows
# dtype: int64
# Remove rows if 'Embarked' is null
filter_mask = raw['Embarked'].notna()
raw = raw[filter_mask] # drop rows
#raw.isnull().sum()
# Fill 'Age with median if 'Age' is null
raw['Title'] = raw['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)
title_mapping = {'Mr': 0, 'Miss': 1, 'Mrs': 2,
'Master': 3, 'Dr': 3, 'Rev': 3, 'Col': 3, 'Major': 3, 'Mlle': 3,'Countess': 3,
'Ms': 3, 'Lady': 3, 'Jonkheer': 3, 'Don': 3, 'Dona' : 3, 'Mme': 3,'Capt': 3,'Sir': 3 }
raw['Title_map'] = raw['Title'].map(title_mapping)
raw['Title_map'] = raw['Title_map'].fillna(0)
raw['Age'].fillna(raw.groupby('Title_map')['Age'].transform('median'), inplace=True)
raw.isnull().sum()
# raw.shape
train = raw.copy()
#df.info()
# Int64Index: 889 entries, 0 to 890
# Data columns (total 14 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 PassengerId 889 non-null int64 => Drop
# 1 Survived 889 non-null int64
# 2 Pclass 889 non-null int64
# 3 Name 889 non-null object => Drop
# 4 Sex 889 non-null object => Transform
# 5 Age 889 non-null float64
# 6 SibSp 889 non-null int64
# 7 Parch 889 non-null int64
# 8 Ticket 889 non-null object => Drop
# 9 Fare 889 non-null float64
# 10 Cabin 889 non-null object => Drop
# 11 Embarked 889 non-null object => Transform
# 12 Title 889 non-null object => Drop
# 13 Title_map 889 non-null int64
# dtypes: float64(2), int64(6), object(6)
features_drop = ['PassengerId','Name','Ticket','Cabin','Title']
temp = train.drop(features_drop, axis=1)
X = temp.drop('Survived', axis=1) # Exclude Survived column
y = temp['Survived'] # Survived column only
図3-1にはDataFrame(raw)のNullの件数を列毎に表示しています。
列「Age, Cabin, Embarked」にNullの値が存在します。
図3-2にはDataFrame(raw)の列「Embarked」の値がNullの行を削除した後の状態を表示しています。
列「Embarked」のNullの件数が0件になっています。
図3-3には列「Age」のNullを中央値(median)で置換後の状態を表示しています。
列「Age」のNullの件数が0件になっています。
図3-4にはDataFrame(X)とSeries(y)の構造と内容を表示しています。
DataFrame(X)には889件の乗客データが格納されています。
Series(y)には889件の生死(0:Dead, 1:Survived)のデータが格納されています。
-
OneHotEncoder(), make_column_transformer()の処理を事前に確認する
行2-3ではOneHotEncoder()でDataFrame(X)の列「Sex(female, male)」がどのように変換されるか確認しています。
行6-7ではOneHotEncoder()でDataFrame(X)の列「Embarked(C, Q, S)」がどのように変換されるか確認しています。
### Test transform columns
ohe = OneHotEncoder(sparse=False)
ohe.fit_transform(X[['Sex']])
#ohe.categories_
ohe = OneHotEncoder(sparse=False)
ohe.fit_transform(X[['Embarked']])
#ohe.categories_
図4-1にはDataFrame(X)の列「Sex」をstr型からbit型に変換した結果が表示されています。
それぞれの配列の要素が「1」のとき、性別「female, male」に対応します。
図4-2にはDataFrame(X)の列「Embarked」をstr型からbit型に変換した結果が表示されています。
それぞれの配列の要素が「1」のとき、乗船港「C, Q, S」に対応します。
C = Cherbourg, Q = Queenstown, S = Southampton
-
Cross-ValidationにPipelineを使用して複数のモデルを評価する
行4ではCross-ValidationのK-Fold(分割回数)を設定しています。
ここでは学習データ(X)を10回分割させます。
行8-10ではDataFrame(X)の列「Sex, Embarked」をstr型からbit型に変換させています。
列「Sex(female, male)」は2要素の配列に分割されます。配列の要素0が「1」のときは「female」、
配列の要素1が「1」のときは「male」を意味します。
列「Embarked(C, Q, S)」は3要素の配列に分割されます。配列の要素0が「1」のときは「C」、
配列の要素1が「1」のときは「Q」、配列の要素2が「1」のときは「S」を意味します。
行18-26ではモデルを定義しています。
行29-39のforループではCross-Validation()を実行してそれぞれのモデルを評価します。
Cross-Validion()の引数にはPipelineを指定してDataFrame(X)の列「Sex, Embarked」をstr型からbit型に変換させています。
### 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)
## Transform columns
col_trans = make_column_transformer(
(OneHotEncoder(), ['Sex', 'Embarked']),
remainder='passthrough')
# 4 Sex 889 non-null object => Transform
# 11 Embarked 889 non-null object => Transform
## 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(),
ExtraTreesClassifier()]
for i in range(len(clf)):
pipe = make_pipeline(col_trans, clf[i])
#print('Pipeline:', pipe)
score = cross_val_score(pipe, X, y, 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)
図5には実行結果が表示されています。
ここでは、RandomForestClassifier()がもっとも高いスコアになっています。
-
ここで解説したコードをまとめて掲載
最後にここで解説したすべてのコードをまとめて掲載しましたので参考にしてください。
### 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
# Cross Validation(k-fold)
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
# Pipeline and GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
import sklearn.metrics as skm
# Make
from sklearn.compose import make_column_transformer
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import OneHotEncoder
import warnings
warnings.simplefilter('ignore')
# %%
### Load the data
csv_file = 'data/csv/titanic/train.csv'
#csv_file = 'https://money-or-ikigai.com/menu/python/article/data/titanic/train.csv'
raw = pd.read_csv(csv_file)
# %%
### Data Cleanup
raw.isnull().sum()
# raw
# PassengerId 0
# Survived 0
# Pclass 0
# Name 0
# Sex 0
# Age 177 => Fill with median
# SibSp 0
# Parch 0
# Ticket 0
# Fare 0
# Cabin 687
# Embarked 2 => Remove rows
# dtype: int64
# %%
# Remove rows if 'Embarked' is null
filter_mask = raw['Embarked'].notna()
raw = raw[filter_mask] # drop rows
raw.isnull().sum()
# %%
# Fill 'Age with median if 'Age' is null
raw['Title'] = raw['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)
title_mapping = {'Mr': 0, 'Miss': 1, 'Mrs': 2,
'Master': 3, 'Dr': 3, 'Rev': 3, 'Col': 3, 'Major': 3, 'Mlle': 3,'Countess': 3,
'Ms': 3, 'Lady': 3, 'Jonkheer': 3, 'Don': 3, 'Dona' : 3, 'Mme': 3,'Capt': 3,'Sir': 3 }
raw['Title_map'] = raw['Title'].map(title_mapping)
raw['Title_map'] = raw['Title_map'].fillna(0)
raw['Age'].fillna(raw.groupby('Title_map')['Age'].transform('median'), inplace=True)
raw.isnull().sum()
# raw.shape
# (889, 14)
# %%
train = raw.copy()
#df.info()
# Int64Index: 889 entries, 0 to 890
# Data columns (total 14 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 PassengerId 889 non-null int64 => Drop
# 1 Survived 889 non-null int64
# 2 Pclass 889 non-null int64
# 3 Name 889 non-null object => Drop
# 4 Sex 889 non-null object => Transform
# 5 Age 889 non-null float64
# 6 SibSp 889 non-null int64
# 7 Parch 889 non-null int64
# 8 Ticket 889 non-null object => Drop
# 9 Fare 889 non-null float64
# 10 Cabin 889 non-null object => Drop
# 11 Embarked 889 non-null object => Transform
# 12 Title 889 non-null object => Drop
# 13 Title_map 889 non-null int64
# dtypes: float64(2), int64(6), object(6)
features_drop = ['PassengerId','Name','Ticket','Cabin','Title']
temp = train.drop(features_drop, axis=1)
X = temp.drop('Survived', axis=1) # Exclude Survived column
y = temp['Survived'] # Survived column only
# %%
## Test transform columns
ohe = OneHotEncoder(sparse=False)
ohe.fit_transform(X[['Sex']])
#ohe.categories_
# %%
## Test transform columns
ohe = OneHotEncoder(sparse=False)
ohe.fit_transform(X[['Embarked']])
#ohe.categories_
# array([[0., 0., 1.], => S
# [1., 0., 0.], => C
# [0., 0., 1.], => S
# ...,
# [0., 0., 1.],
# [1., 0., 0.],
# [0., 1., 0.]])
# %%
### 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)
## Transform columns
col_trans = make_column_transformer(
(OneHotEncoder(), ['Sex', 'Embarked']),
remainder='passthrough')
# 4 Sex 889 non-null object => Transform
# 11 Embarked 889 non-null object => Transform
## 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(),
ExtraTreesClassifier()]
def model_fit():
for i in range(len(clf)):
pipe = make_pipeline(col_trans, clf[i])
#print('Pipeline:', pipe)
score = cross_val_score(pipe, X, y, 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()
# %%