Microsoft Fabricを使用して機械学習とレポート作成をやってみた(中編)

前編から引き続き、Microsoftが提供しているSaaS型のサービスであるMicrosoft Fabricを使用して、機械学習を用いた予測レポートを作成してみたいと思います。

本記事はMicrosoft Learnの以下の記事を参考に作業していきます。

データ サイエンス チュートリアル - 概要 - Microsoft Fabric | Microsoft Learn

こちらの記事は中編となり、前編の記事は以下となります。

blog.jbs.co.jp

作業の概要

用意されたノートブックを使用して、以下の内容のことを実施していきます。

  1. 事前準備 前編で実施
  2. データの取得 前編で実施
  3. データの加工(クレンジング)と保存 前編で実施
  4. 機械学習モデルのトレーニングと登録
  5. 予測データの作成
  6. 予測データの視覚化(レポート作成)

公開されているテストデータ(銀行の顧客データ)をもとに、機械学習モデルを使用して顧客の解約状況を予測を立てるというのが目的となっています。

この中編記事では、機械学習モデルのトレーニングと登録を行います。

機械学習モデルのトレーニングと登録

作業内容

マイワークスペースから3-train-evaluate.ipynbを選択します。 このノートブックでは以下のようなことが行われます。

  • ライブラリのインストール
  • 機械学習モデルとトレーニングのための前準備
  • 各種データセットの準備
  • マイノリティクラスの新しいサンプルの合成
  • モデルのトレーニング
  • トレーニング済みモデルのパフォーマンス評価
  • トレーニング済みモデルの精度の評価

「すべて実行」を選択します。

最後のコードまでエラーが発生せず完了することを確認します。

※ 一部のコードでWarningのメッセージが発生しますが、無視して大丈夫です。

ライブラリのインストール

imbalanced-learnは、不均衡なデータセットを処理するときに使用される SMOTE (Synthetic Minority Oversampling Technique) のライブラリです。 マイノリティクラスの新しいサンプルの合成で使用します。

pipコマンドを使用してインストールします。

# SMOTEのためにimbalanced-learnのインストール
%pip install imblearn

なお、ノートブックにてライブラリをインストールする場合、そのライブラリはノートブックのセッション中でのみ使用できます。 そのノートブックを再起動した場合などは、再度インストールする必要があります。

よく使用するライブラリがある場合は、そのライブラリをインストールしたFabric環境を作成した方がよいでしょう。

機械学習モデルとトレーニングのための前準備

前準備

前準備として3つの事を行っています。

1) データの読み込み 前編で作成したクレンジング済みのデータをレイクハウスからデルタ テーブルに読み込みます。

import pandas as pd
SEED = 12345
df_clean = spark.read.format("delta").load("Tables/df_clean").toPandas()

2) モデルの追跡とログ記録のための実験を生成する MLflow*1のライブラリをインポートします。 その後、bank-churn-experimentという名前で実験を作成して自動ログ機能を設定します。

import mlflow
# 実験名の設定
EXPERIMENT_NAME = "bank-churn-experiment"  # MLflow experiment name
mlflow.set_experiment(EXPERIMENT_NAME)
# 自動ログ機能の設定
mlflow.autolog(exclusive=False)

3) 機械学習モデルのライブラリのインポート ランダム フォレストと LightGBM のトレーニングのために、scikit-learn と LightGBM をインポートします。

# ライブラリのインポート
from sklearn.model_selection import train_test_split
from lightgbm import LGBMClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score, precision_score, confusion_matrix, recall_score, roc_auc_score, classification_report
補足:用語解説
ランダムフォレスト

バギングと決定木を使用するアンサンブル学習のアルゴリズムです。

サンプル抽出した複数のデータ群に対してそれぞれ決定木モデルを構築します。

また、それぞれのモデルを構築する際に一部の学習データを使用せず、分割する特徴量をランダムに決定することで、互いに相関の低い複数の決定木モデルが生成されます。

LightGBM

決定木の勾配ブースティングアルゴリズムの一つです。

最初の決定木モデルの出力結果と正解値との誤差を計算し,二つ目の決定木ではその誤差を埋めるような値を学習します。これを何度も繰り返して誤差を小さくしていき、最終的な予測値を求めます。

他の決定木の勾配ブースティングアルゴリズムと比べて高速化が図られており、学習スピードが早いのが特徴です。

各種データセットの準備

クレンジング済みのデータをトレーニング、検証、およびテスト セットに分割します。

y = df_clean["Exited"]
X = df_clean.drop("Exited",axis=1)
# Split the dataset to 60%, 20%, 20% for training, validation, and test datasets
# トレーニングデータとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=SEED)
# トレーニングデータと検証データに分割
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.25, random_state=SEED)

# テストデータの保存
table_name = "df_test"
# Create PySpark DataFrame from Pandas
df_test=spark.createDataFrame(X_test)
df_test.write.mode("overwrite").format("delta").save(f"Tables/{table_name}")
print(f"Spark test DataFrame saved to delta table: {table_name}")

マイノリティクラスの新しいサンプルの合成

今回のサンプルデータでは、10,000 人の顧客のうち解約した顧客は 2,037 人 (約 20%) 程となります。

これは、データセットが非常に不均衡で、モデルが決定境界を効果的に学習するにはマイノリティ クラスの例が少なすぎることになります。そのため、 SMOTE を使用して、マイノリティ クラスの新しいサンプルを合成します。

なお、SMOTE はトレーニング データセットにのみ適用します。 元のデータに対する機械学習モデルのパフォーマンスの有効な近似を得るには、テスト データセットは元の不均衡な分布のままにしておく必要があります。

from collections import Counter
from imblearn.over_sampling import SMOTE

sm = SMOTE(random_state=SEED)
X_res, y_res = sm.fit_resample(X_train, y_train)
new_train = pd.concat([X_res, y_res], axis=1)

モデルのトレーニング

トレーニング

3つのモデルに対してトレーニングを行います。

1) 最大深度が 4 で 4 つの特徴を持つランダム フォレスト

mlflow.sklearn.autolog(registered_model_name='rfc1_sm') # Register the trained model with autologging
rfc1_sm = RandomForestClassifier(max_depth=4, max_features=4, min_samples_split=3, random_state=1) # Pass hyperparameters
with mlflow.start_run(run_name="rfc1_sm") as run:
    rfc1_sm_run_id = run.info.run_id # Capture run_id for model prediction later
    print("run_id: {}; status: {}".format(rfc1_sm_run_id, run.info.status))
    # rfc1.fit(X_train,y_train) # Imbalanaced training data
    rfc1_sm.fit(X_res, y_res.ravel()) # Balanced training data
    rfc1_sm.score(X_val, y_val)
    y_pred = rfc1_sm.predict(X_val)
    cr_rfc1_sm = classification_report(y_val, y_pred)
    cm_rfc1_sm = confusion_matrix(y_val, y_pred)
    roc_auc_rfc1_sm = roc_auc_score(y_res, rfc1_sm.predict_proba(X_res)[:, 1])

2) 最大深度が 8 で 6 つの特徴を持つランダム フォレスト

mlflow.sklearn.autolog(registered_model_name='rfc2_sm') # Register the trained model with autologging
rfc2_sm = RandomForestClassifier(max_depth=8, max_features=6, min_samples_split=3, random_state=1) # Pass hyperparameters
with mlflow.start_run(run_name="rfc2_sm") as run:
    rfc2_sm_run_id = run.info.run_id # Capture run_id for model prediction later
    print("run_id: {}; status: {}".format(rfc2_sm_run_id, run.info.status))
    # rfc2.fit(X_train,y_train) # Imbalanced training data
    rfc2_sm.fit(X_res, y_res.ravel()) # Balanced training data
    rfc2_sm.score(X_val, y_val)
    y_pred = rfc2_sm.predict(X_val)
    cr_rfc2_sm = classification_report(y_val, y_pred)
    cm_rfc2_sm = confusion_matrix(y_val, y_pred)
    roc_auc_rfc2_sm = roc_auc_score(y_res, rfc2_sm.predict_proba(X_res)[:, 1])

3) LightGBM

# lgbm_model
mlflow.lightgbm.autolog(registered_model_name='lgbm_sm') # Register the trained model with autologging
lgbm_sm_model = LGBMClassifier(learning_rate = 0.07, 
                        max_delta_step = 2, 
                        n_estimators = 100,
                        max_depth = 10, 
                        eval_metric = "logloss", 
                        objective='binary', 
                        random_state=42)

with mlflow.start_run(run_name="lgbm_sm") as run:
    lgbm1_sm_run_id = run.info.run_id # Capture run_id for model prediction later
    # lgbm_sm_model.fit(X_train,y_train) # Imbalanced training data
    lgbm_sm_model.fit(X_res, y_res.ravel()) # Balanced training data
    y_pred = lgbm_sm_model.predict(X_val)
    accuracy = accuracy_score(y_val, y_pred)
    cr_lgbm_sm = classification_report(y_val, y_pred)
    cm_lgbm_sm = confusion_matrix(y_val, y_pred)
    roc_auc_lgbm_sm = roc_auc_score(y_res, lgbm_sm_model.predict_proba(X_res)[:, 1])
実験結果の確認

各実験結果はMLflowの自動ログ機能を介してワークスペースに保存されています。

※ 毎回の実験結果が記録されています。

ワークスペースに移動すると bank-churn-experiment というアイテムが存在しているので選択します。

ページ左の枠から、各実験を選択します。

それぞれの結果を確認することができます。

トレーニング済みモデルのパフォーマンス評価

2つの方法でトレーニング済みモデルの評価が行われています。

どちらの方法でも特に問題なく、似たようなパフォーマンス結果が得られます。

1) ワークスペースに保存した実験から機械学習モデルを読み込み、検証データセットでパフォーマンスを評価

# Define run_uri to fetch the model
# mlflow client: mlflow.model.url, list model
load_model_rfc1_sm = mlflow.sklearn.load_model(f"runs:/{rfc1_sm_run_id}/model")
load_model_rfc2_sm = mlflow.sklearn.load_model(f"runs:/{rfc2_sm_run_id}/model")
load_model_lgbm1_sm = mlflow.lightgbm.load_model(f"runs:/{lgbm1_sm_run_id}/model")
# Assess the performance of the loaded model on validation dataset
ypred_rfc1_sm_v1 = load_model_rfc1_sm.predict(X_val) # Random Forest with max depth of 4 and 4 features
ypred_rfc2_sm_v1 = load_model_rfc2_sm.predict(X_val) # Random Forest with max depth of 8 and 6 features
ypred_lgbm1_sm_v1 = load_model_lgbm1_sm.predict(X_val) # LightGBM

2) トレーニング済みの機械学習モデルを直接使用して検証データセットでパフォーマンスを評価

ypred_rfc1_sm_v2 = rfc1_sm.predict(X_val) # Random Forest with max depth of 4 and 4 features
ypred_rfc2_sm_v2 = rfc2_sm.predict(X_val) # Random Forest with max depth of 8 and 6 features
ypred_lgbm1_sm_v2 = lgbm_sm_model.predict(X_val) # LightGBM

混同行列について

次の精度の評価で使用する混同行列と呼ばれる表について説明します。

2値分類問題で出力されたクラス分類の結果をまとめた表で、2 値分類機械学習モデルの性能を測る指標として使用されています。

実際の結果と機械学習の推測結果から以下の4つのパターンが表示されます。

機械学習モデルの予測
Positive Negative
実際の結果 Positive TP(True Positive) FN(False Negative)
Negative FP(False Positive) TN(True Positive)
TP(True Positive) 実際の結果が正しいものを正しいと推測している
TN(True Positive) 実際の結果が正しくないものを正しくないと推測している
FP(False Positive) 実際の結果が正しくないものを正しいと推測している
FN(False Negative) 実際の結果が正しいものを正しくないと推測している

また、この表から、機械学習モデルの性能を図るための以下のような指標を算出できます。

正確さ (Accuracy) 全体の中で正しく分類できたデータ(TP+TN)の割合。
高いほど性能がいいと言える。
Accuracy = (TP + TN) / (TP + TN + FN + FP)
適合率 (Precision) Positiveと分類されたデータ(TP+FP)の中で実際にPositiveだったデータ(TP)の割合。
高いほど間違った判断が少ないと言える。
Presision = TP / (TP + FP)
真陽性率 (True Positive Rate) Positiveなデータを取りこぼしなく正しく推測できているかどうか。
この値が高ければ間違ったPositiveの判断が少ないということ。
True Positive Rate = TP / (TP + FN)
真陰性率 (True Negative Rate) Negativeなデータを取りこぼしなく正しく推測できているかどうか。
この値が高ければ間違ったNegativeの判断が少ないということ。
True Negative Rate = TN / (TN + FP)

トレーニング済みモデルの精度の評価

混同行列を表示するスクリプトを使用して、先ほどのパフォーマンス評価を行った結果から各モデルのTP/TN/FP/FNを表示し分類の精度を評価します。

本記事では1つ目の方法の結果を使用します。

混同行列を表示するスクリプト
import seaborn as sns
sns.set_theme(style="whitegrid", palette="tab10", rc = {'figure.figsize':(9,6)})
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from matplotlib import rc, rcParams
import numpy as np
import itertools

def plot_confusion_matrix(cm, classes,
                          normalize=False,
                          title='Confusion matrix',
                          cmap=plt.cm.Blues):
    print(cm)
    plt.figure(figsize=(4,4))
    plt.rcParams.update({'font.size': 10})
    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45, color="blue")
    plt.yticks(tick_marks, classes, color="blue")

    fmt = '.2f' if normalize else 'd'
    thresh = cm.max() / 2.
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
        plt.text(j, i, format(cm[i, j], fmt),
                 horizontalalignment="center",
                 color="red" if cm[i, j] > thresh else "black")

    plt.tight_layout()
    plt.ylabel('True label')
    plt.xlabel('Predicted label')
最大深度が 4 で 4 つの特徴を持つランダム フォレストの精度の評価
cfm = confusion_matrix(y_val, y_pred=ypred_rfc1_sm_v1)
plot_confusion_matrix(cfm, classes=['Non Churn','Churn'],
                      title='Random Forest with max depth of 4')
tn, fp, fn, tp = cfm.ravel()

最大深度が 8 で 6 つの特徴を持つランダム フォレストの精度の評価
cfm = confusion_matrix(y_val, y_pred=ypred_rfc2_sm_v1)
plot_confusion_matrix(cfm, classes=['Non Churn','Churn'],
                      title='Random Forest with max depth of 8')
tn, fp, fn, tp = cfm.ravel()

LightGBMの精度の評価
cfm = confusion_matrix(y_val, y_pred=ypred_lgbm1_sm_v1)
plot_confusion_matrix(cfm, classes=['Non Churn','Churn'],
                      title='LightGBM')
tn, fp, fn, tp = cfm.ravel()

3つのモデルの結果と指標の比較
TP TN FN FP 正確さ (Accuracy) 適合率 (Precision) 真陽性率 (True Positive Rate) 真陰性率 (True Negative Rate)
最大深度が 4 で 4 つの特徴を持つランダム フォレスト 1365 247 231 157 0.806 0.896846255 0.855263158 0.611386139
最大深度が 8 で 6 つの特徴を持つランダム フォレスト 1427 229 169 175 0.828 0.890761548 0.894110276 0.566831683
LightGBM 1485 215 111 189 0.85 0.887096774 0.930451128 0.532178218

LightGBM が他の2つと比べ正確さ (Accuracy)や真陽性率 (True Positive Rate)の指標が高く性能がいいと判断しました。

まとめ

今回の記事では、機械学習モデルのトレーニングと登録を実施しました。

次回の後編では、予測データの作成とレポートの作成を行いたいと思います。

*1:機械学習ライフサイクルを管理するために設計されたオープンソースフレームワーク

執筆担当者プロフィール
白谷 恭一

白谷 恭一(日本ビジネスシステムズ株式会社)

2010年中途入社。得意な技術領域はLinux/Hadoopで、現在はデータ分析基盤の運用を担当。趣味はゲーム、温泉巡り。

担当記事一覧