この記事では、説明可能なブースティング マシン (EBM) を使用して分類モデルをトレーニングする方法について説明します。 説明可能なブースティング マシンは、勾配ブースティングの力とモデルの解釈可能性に重点を置いた機械学習手法です。 勾配ブースティングに似たデシジョン ツリーのアンサンブルを作成しますが、人間が判読できるモデルの生成に固有の焦点を当てています。 EBM は正確な予測を提供するだけでなく、それらの予測に対して明確で直感的な説明を提供します。 これらは、医療、財務、規制コンプライアンスなど、モデルの決定を推進する基になる要因を理解することが不可欠なアプリケーションに適しています。
SynapseML では、Apache Spark を利用した EBM のスケーラブルな実装を使用して、新しいモデルをトレーニングできます。 この記事では、Apache Spark を使用して Microsoft Fabric 内の EBM のスケーラビリティと解釈可能性を適用するプロセスについて説明します。
この記事では、NYC イエロー タクシー乗車を記録する Azure Open Datasets からデータを取得して前処理するプロセスについて説明します。 次に、特定の旅行が発生するかどうかを決定する最終的な目的を使用して予測モデルをトレーニングします。
説明可能なブースト マシンの利点
EBM は、解釈可能性と予測能力を独自に組み合わせたものであり、機械学習モデルの透明性と理解性が重要な場合に理想的な選択肢となります。 EBM を使用すると、ユーザーは、予測を推進する基になる要因に関する貴重な分析情報を得ることができ、モデルが特定の決定や予測を行う理由を理解できます。これは、AI システムでの信頼を構築するために不可欠です。
データ内の複雑な関係を明らかにしながら、明確で解釈可能な結果を提供する能力は、財務、医療、不正行為の検出などの分野で非常に貴重になります。 これらの分野では、モデルの説明可能性は望ましいだけでなく、多くの場合、規制要件です。 最終的に、EBM を選択するユーザーは、モデルのパフォーマンスと透明性のバランスを取ることができ、AI ソリューションが正確で、簡単に理解でき、説明責任を持つことができます。
前提 条件
Microsoft Fabric サブスクリプションを取得します。 または、無料で Microsoft Fabric 試用版にサインアップします。
Microsoft Fabric にサインインします。
ホーム ページの左下にあるエクスペリエンス スイッチャーを使用して、Fabric に切り替えます。
- [+] を選択し、ノートブックを使用して、ワークスペースに新しいノートブックを作成します。
ライブラリのインストール
まず、データセットへのアクセスを許可する Azure Machine Learning Open Datasets ライブラリをインストールします。 このインストール手順は、データセットに効果的にアクセスして利用するために不可欠です。
%pip install azureml-opendatasets
MLflow のインポート
MLflow を使用すると、モデルのパラメーターと結果を追跡できます。 次のコード スニペットは、実験と追跡のために MLflow を使用する方法を示しています。
ebm_classification_nyc_taxi
値は、情報が記録される実験の名前です。
import mlflow
# Set up the experiment name
mlflow.set_experiment("ebm_classification_nyc_taxi")
ライブラリのインポート
次に、次のコードを追加して、分析に使用される重要なパッケージをインポートします。
# Import necessary packages
import interpret
from pyspark.ml.feature import StringIndexer, VectorAssembler
from pyspark.ml import Pipeline
from pyspark.mllib.evaluation import BinaryClassificationMetrics
from pyspark.sql import SparkSession
from pyspark.sql.functions import *
from pyspark.sql.types import DoubleType
from synapse.ml.ebm import EbmClassification
これらのインポートされたパッケージは、分析およびモデリング タスクに必要な基本的なツールと機能を提供します。
データの読み込み
次に、次のコードを追加して、Azure Machine Learning Open Datasets から NYC Yellow Taxi データを取得し、必要に応じてデータセットをダウンサンプリングして開発を迅速化します。
# Import NYC Yellow Taxi data from Azure Open Datasets
from azureml.opendatasets import NycTlcYellow
from datetime import datetime
from dateutil import parser
# Define the start and end dates for the dataset
end_date = parser.parse('2018-05-08 00:00:00')
start_date = parser.parse('2018-05-01 00:00:00')
# Load the NYC Yellow Taxi dataset within the specified date range
nyc_tlc = NycTlcYellow(start_date=start_date, end_date=end_date)
nyc_pd = nyc_tlc.to_pandas_dataframe()
nyc_tlc_df = spark.createDataFrame(nyc_pd)
開発を容易にし、計算オーバーヘッドを減らすために、データセットをダウンサンプリングできます。
# For development convenience, consider down-sampling the dataset
sampled_taxi_df = nyc_tlc_df.sample(True, 0.001, seed=1234)
ダウン サンプリングを使用すると、特に大規模なデータセットを操作する場合に、より高速でコスト効率の高い開発エクスペリエンスを実現できます。
データを準備する
このセクションでは、外れ値と無関係な変数を含む行をフィルターで除外して、データセットを準備します。
特徴を生成する
モデルのパフォーマンスを向上させるために必要な新しい派生機能のコードを追加します。
taxi_df = sampled_taxi_df.select('totalAmount', 'fareAmount', 'tipAmount', 'paymentType', 'rateCodeId', 'passengerCount',
'tripDistance', 'tpepPickupDateTime', 'tpepDropoffDateTime',
date_format('tpepPickupDateTime', 'hh').alias('pickupHour'),
date_format('tpepPickupDateTime', 'EEEE').alias('weekdayString'),
(unix_timestamp(col('tpepDropoffDateTime')) - unix_timestamp(col('tpepPickupDateTime'))).alias('tripTimeSecs'),
(when(col('tipAmount') > 0, 1).otherwise(0)).alias('tipped')
)\
.filter((sampled_taxi_df.passengerCount > 0) & (sampled_taxi_df.passengerCount < 8)
& (sampled_taxi_df.tipAmount >= 0) & (sampled_taxi_df.tipAmount <= 25)
& (sampled_taxi_df.fareAmount >= 1) & (sampled_taxi_df.fareAmount <= 250)
& (sampled_taxi_df.tipAmount < sampled_taxi_df.fareAmount)
& (sampled_taxi_df.tripDistance > 0) & (sampled_taxi_df.tripDistance <= 100)
& (sampled_taxi_df.rateCodeId <= 5)
& (sampled_taxi_df.paymentType.isin({"1", "2"}))
)
新しい変数を作成したので、派生元の列を削除するコードを追加します。 データフレームは、モデル入力に対してより合理化されました。 さらに、新しく作成された列に基づいてさらに特徴を生成できます。
taxi_featurised_df = taxi_df.select('totalAmount', 'fareAmount', 'tipAmount', 'paymentType', 'passengerCount',
'tripDistance', 'weekdayString', 'pickupHour', 'tripTimeSecs', 'tipped',
when((taxi_df.pickupHour <= 6) | (taxi_df.pickupHour >= 20), "Night")
.when((taxi_df.pickupHour >= 7) & (taxi_df.pickupHour <= 10), "AMRush")
.when((taxi_df.pickupHour >= 11) & (taxi_df.pickupHour <= 15), "Afternoon")
.when((taxi_df.pickupHour >= 16) & (taxi_df.pickupHour <= 19), "PMRush")
.otherwise(0).alias('trafficTimeBins'))\
.filter((taxi_df.tripTimeSecs >= 30) & (taxi_df.tripTimeSecs <= 7200))
これらのデータ準備手順により、データセットが調整され、後続のモデリング プロセス用に最適化されます。
データをエンコードする
SynapseML EBM のコンテキストでは、Estimator
は入力データが org.apache.spark.mllib.linalg.Vector
(基本的に Doubles
のベクトル) の形式であると想定しています。 そのため、カテゴリ (文字列) 変数を数値表現に変換する必要があります。また、数値を持ち、数値以外のデータ型を持つ変数は、数値データ型にキャストする必要があります。
現時点では、SynapseML EBM はカテゴリ変数を管理するために StringIndexer
アプローチを採用しています。 現時点では、SynapseML EBM ではカテゴリ機能に特化した処理は提供されていません。
SynapseML EBM で使用するデータを準備するために、コードに追加する一連の手順を次に示します。
# Convert categorical features into numerical representations using StringIndexer
sI1 = StringIndexer(inputCol="trafficTimeBins", outputCol="trafficTimeBinsIndex")
sI2 = StringIndexer(inputCol="weekdayString", outputCol="weekdayIndex")
# Apply the encodings to create a new dataframe
encoded_df = Pipeline(stages=[sI1, sI2]).fit(taxi_featurised_df).transform(taxi_featurised_df)
final_df = encoded_df.select('fareAmount', 'paymentType', 'passengerCount', 'tripDistance',
'pickupHour', 'tripTimeSecs', 'trafficTimeBinsIndex', 'weekdayIndex',
'tipped')
# Ensure that any string representations of numbers in the data are cast to numeric data types
final_df = final_df.withColumn('paymentType', final_df['paymentType'].cast(DoubleType()))
final_df = final_df.withColumn('pickupHour', final_df['pickupHour'].cast(DoubleType()))
# Define the label column name
labelColumnName = 'tipped'
# Assemble the features into a vector
assembler = VectorAssembler(outputCol='features')
assembler.setInputCols([c for c in final_df.columns if c != labelColumnName])
vectorized_final_df = assembler.transform(final_df)
これらのデータ準備手順は、SynapseML EBM の要件に合わせてデータ形式を調整し、正確で効果的なモデル トレーニングを確保するために不可欠です。
テスト データセットとトレーニング データセットを生成する
次に、単純な分割を使用して、データセットをトレーニング セットとテスト セットに分割するコードを追加します。 トレーニングには 70% のデータが割り当てられ、モデルのテストには 30% が割り当てられます。
# Decide on the split between training and test data from the dataframe
trainingFraction = 0.7
testingFraction = (1-trainingFraction)
seed = 1234
# Split the dataframe into test and training dataframes
train_data_df, test_data_df = vectorized_final_df.randomSplit([trainingFraction, testingFraction], seed=seed)
このデータ分割により、パフォーマンスを効果的に評価するために別の部分を予約しながら、モデルを実質的な部分でトレーニングできます。
モデルをトレーニングする
次に、EBM モデルをトレーニングするコードを追加し、メトリックとして受信側動作特性 (ROC) 曲線 (AUROC) の下の領域を使用してそのパフォーマンスを評価します。
# Create an instance of the EBMClassifier
estimator = EbmClassification()
estimator.setLabelCol(labelColumnName)
# Fit the EBM model to the training data
model = estimator.fit(train_data_df)
# Make predictions for tip (1/0 - yes/no) on the test dataset and evaluate using AUROC
predictions = model.transform(test_data_df)
predictionsAndLabels = predictions.select("prediction", labelColumnName)
predictionsAndLabels = predictionsAndLabels.withColumn(labelColumnName, predictionsAndLabels[labelColumnName].cast(DoubleType()))
# Calculate AUROC using BinaryClassificationMetrics
metrics = BinaryClassificationMetrics(predictionsAndLabels.rdd)
print("Area under ROC = %s" % metrics.areaUnderROC)
このプロセスでは、トレーニング データセットで EBM モデルをトレーニングし、それを使用してテスト データセットで予測を行い、その後、AUROC を主要なメトリックとして使用してモデルのパフォーマンスを評価します。
グローバルな説明を表示する
モデルの全体的な説明を視覚化するには、視覚化ラッパーを取得し、 interpret
ライブラリの show
メソッドを利用するコードを追加します。 視覚化ラッパーは、モデルの視覚化エクスペリエンスを容易にするブリッジとして機能します。 これを行う方法は次のとおりです。
wrapper = model.getVizWrapper()
explanation = wrapper.explain_global()
次に、 interpret
ライブラリをインポートするコードを追加し、 show
メソッドを使用して説明を表示します。
import interpret
interpret.show(explanation)
"重要度" という用語は、予測に対する各用語 (特徴または相互作用) の平均絶対寄与度 (スコア) を表します。 これらの貢献度は、各ビン内のサンプル数とサンプルの重み (該当する場合) を考慮して、トレーニング データセット全体で平均化されます。 上位 15 の最も重要な用語が説明に表示されます。
ローカルの説明を表示する
提供される説明はグローバル レベルですが、機能ごとの出力も重要なシナリオがあります。 トレーナーとモデルの両方に、featurescores
を設定する機能が用意されています。この機能を設定すると、別のベクター値列が導入されます。 この列内の各ベクターは、特徴列の長さと一致し、各値は同じインデックスにある特徴に対応します。 これらの値は、モデルの最終的な出力に対する各フィーチャの値の寄与を表します。
グローバルな説明とは異なり、現在のところ、機能ごとの出力の interpret
視覚化との直接統合はありません。 これは、グローバルな視覚化は主に特徴の数 (通常は小さい) に応じてスケーリングされ、ローカルの説明は行数に応じてスケーリングされるためです (Spark データフレームの場合は、相当な数になる可能性があります)。
featurescores
を設定して使用するために追加するコードを次に示します。
prediction2 = model.setFeatureScoresCol("featurescores").transform(train_data_df)
説明のために、最初の例の詳細を出力するコードを追加しましょう。
# Convert to Pandas for easier inspection
predictions_pandas = prediction2.toPandas()
predictions_list = prediction2.collect()
# Extract the first example from the collected predictions.
first = predictions_list[0]
# Print the lengths of the features and feature scores.
print('Length of the features is', len(first['features']), 'while the feature scores have length', len(first['featurescores']))
# Print the values of the features and feature scores.
print('Features are', first['features'])
print('Feature scores are', first['featurescores'])