機械学習のプロセスを効率化・合理化して精度を上げるためのコードを書くのを迅速に行いたい、ということで従来の分析プロセスとコードの書き方を見直してみることにしました。今回は「Kaggleで磨く機械学習の実践力」という書籍を参考にしています。
新しい機械学習の分析プロセス
標準的な機械学習のプロセスは以下の通りです。
- 分析設計 - 解くべき問題を理解する、そのために分析するべきことを明確にする
- データ前処理 - ファイルの読み込みと確認・欠損値と外れ値を処理する
- 特徴量生成 - 説明変数から機械学習・精度向上に適した値を抽出する
- データセット作成 - データを説明変数・目的変数に統合する
- バリデーション設計 - データを分割し評価指標を算出する方法を決定する
- モデル学習 - 機械学習モデルの学習を行い、その精度を算出する
- モデル推論 - 推論したい未知のデータの読み込み・前処理、推論データの生成を行う
これらに加え、これまではなんとなーく「このやり方のほうがいい」と試行錯誤して書いていた精度向上のための分析プロセスを以下のように3段階で構成してみることにしました。
- ベースライン作成 - 最低限度のランダムな分類・回帰以上の精度が出せるモデルを作成 (分析設計、データ前処理、データセット作成、バリエーション設計、モデル学習、モデル推論)
- 特徴量のエンジニアリング - 特徴量を加工・生成してモデル精度を向上する (データ前処理、特徴量生成)
- モデルチューニング - モデル自体やパラメータを変更する等でモデル精度を向上する(モデル学習)
タイタニック予測 - LightGBM、LazyPredictによるベースライン作成
今回はKaggleの"Titanic - Machine Learning from Disaster"のデータセットを使って学習を行います。順に分析設計、データ前処理、データセット作成、モデル学習、モデル推論の手順をコード付きで紹介していきます。
分析設計
まず必要なライブラリをすべてインストール・インポートします。
今回は分布確認に使うpandas_profiling、可視化にmatplotlib、前処理やバリエーション設計にsklearn等々が必要です。
またベースモデルとしてランダムフォレスト法と勾配ブースティングを用いるLightGBM、機械学習モデルのベースラインの検討に役立つLazyPredictを使います。LazyPredictはこちらの記事で紹介しています。
import numpy as np
import pandas as pd
import os
import pickle
import gc
# 分布確認に使う
import pandas_profiling as pdp
# 可視化
import matplotlib.pyplot as plt
# 前処理、特徴量作成 - sklearnを使う
from sklearn.preprocessing import StandardScaler, MinMaxScaler, LabelEncoder
# モデリング・精度と評価指標
from sklearn.model_selection import train_test_split, KFold, StratifiedKFold
from sklearn.metrics import accuracy_score, roc_auc_score, confusion_matrix
#LGBM
import lightgbm as lgb
# NOTE matplotでの日本語文字化けを解消
#pip install japanize-matplotlib
import japanize_matplotlib
%matplotlib inline
# NOTE matplotでの日本語文字化けを解消
#pip install japanize-matplotlib
import japanize_matplotlib
%matplotlib inline
"Titanic - Machine Learning from Disaster"のデータを読み込み表示します。
df_train = pd.read_csv("titanic_datasets/train.csv")
df_train.head()
print("データ形状:")
print(df_train.shape)
print("データ数:")
print(len(df_train))
print("データのコラム数")
print(len(df_train.columns))
print("データ型一覧")
df_train.info()
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
データ形状:
(891, 12)
データ数:
891
データのコラム数
12
データ型一覧
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 891 non-null int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null object
5 Age 714 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
データ前処理・データセット作成
"Titanic - Machine Learning from Disaster"には乗客の属性を表す量的変数・質的変数の双方が含まれており、インデックスのPassengerId
を除いた10の説明変数から目的変数のSurvived
を予測する2値分類を目指します。ベースラインの段階では仮に量的変数のみのPclass, Age, SibSp, Parch, Fare
を使って最低限の精度が出せるモデルの作成を目指します。
X_train, y_train, id_train = df_train[["Pclass","Age","SibSp","Parch","Fare"]], df_train[["Survived"]], df_train[["PassengerId"]]
X_train.head()
y_train.head()
# データ形状が問題ないか判定
print("データ形状")
print(X_train.shape)
print(y_train.shape)
print(id_train.shape)
Pclass Age SibSp Parch Fare
0 3 22.0 1 0 7.2500
1 1 38.0 1 0 71.2833
2 3 26.0 0 0 7.9250
3 1 35.0 1 0 53.1000
4 3 35.0 0 0 8.0500
Survived
0 0
1 1
2 1
3 1
4 0
データ形状
(891, 5)
(891, 1)
(891, 1)
バリデーション設計
今回は学習用・テスト用の分割を1通りのみ決める、ホールドアウト検証で汎化精度を算出できるようにします。ホールドアウト検証では目的変数に偏りが生じることもあるので、train_test_splitのstratifyを目的変数に指定して割合に応じた層化抽出が行われるようにします。
# ホールドアウト検証 - 学習用・テスト用の分割を1通り決める
X_tr, X_va, y_tr, y_va = train_test_split(X_train, y_train, test_size=0.2, shuffle=True, stratify=y_train, random_state=123)
print("学習用・訓練用データの形状:")
print(X_tr.shape)
print(y_tr.shape)
print(X_va.shape)
print(y_va.shape)
# データのラベルに偏りがないか表示
y_train_mean = y_train["Survived"].mean()
y_tr_mean = y_tr["Survived"].mean()
y_va_mean = y_va["Survived"].mean()
print("全体の生存者割合:")
print(y_train_mean)
print("学習用データの生存者割合:")
print(y_tr_mean)
print("検証用データの生存者割合:")
print(y_va_mean)
学習用・訓練用データの形状:
(712, 5)
(712, 1)
(179, 5)
(179, 1)
全体の生存者割合:
0.3838383838383838
学習用データの生存者割合:
0.38342696629213485
検証用データの生存者割合:
0.3854748603351955
モデル学習 - LightGBM
LightGBMでベースラインモデルを作成します。テーブルデータを使った教師あり学習では大抵コレが使われています。SVMやニューラルネットと比べると、LightGBMには下記の利点があります:
- モデルの精度がそれなりに高い
- 処理が高速
- カテゴリ変数を自動で前処理してくれる
- 欠損値を自動で前処理してくれる
- 異常値の影響を受けにくい
# LGBMのパラメータ
params = {"boosting_type":"gbdt",
"objective":"binary",
"metric":"auc",
"learning_rate":0.1,
"num_leaves":16,
"n_estimators":1000,
"random_state":123,
"importance_type":"gain",
"early_stopping_round":100,
"verbose":10
}
# LGBMのモデル
model = lgb.LGBMClassifier(**params)
model.fit(X_tr, y_tr, eval_set=[(X_tr, y_tr),(X_va, y_va)])
Early stopping, best iteration is:
[207] training's auc: 0.970625 valid_1's auc: 0.739394
AUC率が74%程のモデルが得られました。タイタニック予測の中では少しマシな部類です。
次にモデルを実際に動かして学習精度・検証精度を出します。検証精度は一応ランダムな予測以上の値が出ていますが、AUC率よりも低くまだまだ改善の余地があります。
# AUC値に加え精度を算出する
y_tr_pred = model.predict(X_tr)
y_va_pred = model.predict(X_va)
metric_tr = accuracy_score(y_tr, y_tr_pred)
metric_va = accuracy_score(y_va, y_va_pred)
print("モデル精度:")
print("学習精度")
print(metric_tr)
print("検証精度")
print(metric_va)
モデル精度:
学習精度
0.9058988764044944
検証精度
0.664804469273743
混合行列と評価係数
次に作成したモデルの性質を知るのに役立つ、混合行列と精度以外の評価係数について解説します。まずはラベルの正誤表となる混合行列の可視化を行います。
# 混合行列 - ラベルの正誤の分類数をまとめる
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
cm = confusion_matrix(y_pred, y_va)
print("混合行列:")
print(cm)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=["0","1"])
disp.plot()
plt.show()
混合行列:
[[78 28]
[32 41]]
このLightGBMのモデルの場合、# 生存できなかった人(True Labelが0)を正しく予測する率の方が、生存できた人(True Labelが1)を正しく予測する確率よりも高くなっています。これはモデルの適合率が再現率よりも高いこと、また正確に分類できるケースに偏りがありモデルが過学習していることを表しています。
次に正解率・適合率・再現率とF1値を算出します。大雑把にいうと、適合率は予測されたラベルのうちどれだけが正解だったかを、再現率は正解のラベルのうちどれだけそのラベルを予測できたかを表します。一方F1値は適合率・再現率の平均となる値です。般的に誤検出を少なくしたい場合には適合率。取りこぼしを少なくしたい場合には再現率を重視します。
医療等に機械学習を活用する等「取りこぼしが多いと命に関わるような重大な問題に繋がる」といったような場合には、再現率を評価指標に使用するのが適切です。
#適合率 - TP+TN / (TP+FP+TN+FN) - 単純な正答率
print("正答率")
print(accuracy_score(y_pred, y_va))
#適合率 - TP / (TP+FP) - 予測されたラベルに対する実際に正解となるサンプル
print("適合率")
print(precision_score(y_pred, y_va))
#再現率 - TP / (TP+FN) - 本来正しく予測した場合に対し実際に正解となるサンプル
print("再現率")
print(recall_score(y_pred, y_va))
print("F値")
print(f1_score(y_pred, y_va))
正答率
0.664804469273743
適合率
0.5942028985507246
再現率
0.5616438356164384
F値
0.5774647887323944
どちらか一方のラベルの正答率が低いと、このように適合率・再現率が正答率よりもかなり低くなってしまうこともあります。一般に適合率・再現率はトレードオフとなるので、「まだ精度を高められるか?」という指標としても使うことができます。
モデル学習 - LazyPredict
次に、LazyPredictでベースラインモデルを作成します。
import lazypredict
from lazypredict.Supervised import LazyClassifier
reg = LazyClassifier(ignore_warnings=True, random_state=1121, verbose=False,predictions=True)
models, predictions = reg.fit(X_tr, X_va, y_tr, y_va)
print("モデルの精度・評価指標:")
display(models)
LightGBM以外にもSVC、ナイーブベイズ、線形モデルがベースラインモデルとして有力であることが分かります。LightGBMをファインチューニングする他にも、これらの比較的精度の高いモデルをアンサンブル学習に使うなどの方策が有効かもしれません。
ベースラインとなるモデル作成はこれで完了しました。次回は説明変数の寄与率などを分析して効果的な特徴量エンジニアリングを行う方法を記事にしたいと計画中です。