Cream-Kuchen

MMLSpark LightGBM の使い方 - サンプルコード付き

はじめに

MMLSparkのLightGBMはどのように使うのでしょう?

サンプルデータで教師あり学習・予測を行い、精度等や変数重要度を見てみます。


また、sample codeをGoogle driveで共有しているので参考にしてください。
html:mmlspark_lightGBM_sample_usage.html - Google ドライブ
ipynb:mmlspark_lightGBM_sample_usage.ipynb - Google ドライブ

※ 実行環境:DatabricksのCommunity Edition
※ mmlsparkのversion:1.0.0-rc1

目次


1, MMLSparkの公式ドキュメント

こちらのリンク先になります。

github.com

公式にもサンプルコードはありますが、情報量が乏しくあまり参考になりません...


2, MMLSparkのinstall

Databricksへのinstall方法を以下の記事にまとめているので、参考にしてみてください。

cream-kuchen.hatenablog.com


3, サンプルデータ取得

Databricksにある赤・白ワインのデータセットを使うことにします。

inpath_red= "dbfs:/databricks-datasets/wine-quality/winequality-red.csv"
inpath_white= "dbfs:/databricks-datasets/wine-quality/winequality-white.csv"
df_red = spark.read.csv(inpath_red, header="true", sep=";", inferSchema="true")
df_white = spark.read.csv(inpath_white, header="true", sep=";", inferSchema="true")

display(df_red.limit(5))

f:id:Cream-Kuchen:20200723142707p:plain

f:id:Cream-Kuchen:20200723142906p:plain

カラム「quality」がint型である以外、他すべてがdouble型です。
いずれのデータセットでも重複するレコードが存在するようです。


4, データ加工

赤・白ワインのユニークなレコードを結合し、正例・負例のフラグやIDを付与します。

from pyspark.sql import functions as f
from pyspark.sql.window import Window as w

df_master = (
  df_red
  .distinct()
  .unionByName(df_white.distinct())
  # フラグ付与: quality7以上を正、7未満を負
  .withColumn("FLAG", f.when(f.col("quality")>=7, f.lit(1)).otherwise(f.lit(0)))
  .drop("quality")
  # ID付与: フラグに用いたquality以外のカラムで昇順に付与(再現性担保のため)
  .withColumn("ID", f.concat(f.lit("ID"), f.row_number().over(w.partitionBy().orderBy(list(set(df_red.columns)-set(["quality"]))))))
  .sort("ID")
)
display(df_master.limit(5))

f:id:Cream-Kuchen:20200723143438p:plain

このデータを特徴量の元として用いることにします。


5, 学習・検証データ作成

pipelineを用いてspark特有の特徴量を作成します。

from pyspark.ml.feature import StringIndexer, VectorAssembler, VectorIndexer  # 特徴量作成
from pyspark.ml import Pipeline  # パイプライン構築

# パイプラインを格納するステージ
stages = []
# 特徴量にするカラムの設定: 再現性担保のために並び替え、IDとFLAGを落とす
cols_feature = df_master.columns
cols_feature.sort()
df_master = df_master.select(cols_feature)
cols_feature.remove("ID"), cols_feature.remove("FLAG")
# パイプラインへ正例・負例の設定
label_string = StringIndexer(inputCol="FLAG", outputCol="label")
stages += [label_string]
# パイプラインへ特徴量の設定
feature_assemble = VectorAssembler(inputCols=cols_feature, outputCol="features")
stages += [feature_assemble]
# パイプライン構築
partial_pipeline = Pipeline().setStages(stages)
pipeline_model = partial_pipeline.fit(df_master)
prepared_data = pipeline_model.transform(df_master)
# 学習・検証データに分割
train, test = prepared_data.randomSplit([0.7, 0.3], seed=0)
# 検証データを可視化確認
display(test.select("ID","features","label").limit(5))

f:id:Cream-Kuchen:20200723155830p:plain

特徴量カラム「features」と正例・負例カラム「label」が作成できました。


6, 学習・予測

ようやく本題です。設定できるハイパーパラメータを確認してモデルの学習・予測をします。

from mmlspark.lightgbm import LightGBMClassifier
LightGBMClassifier.__init__

f:id:Cream-Kuchen:20200723145225p:plain

# 学習モデル構築
model = LightGBMClassifier(baggingSeed=1024,
                           learningRate=0.01,
                           numIterations=1000,
                           maxBin=8,
                           numLeaves=8,
                           metric='auc').fit(train)
# 学習・予測
predict_train = model.transform(train)
predict_test = model.transform(test)
# 検証データの予測を可視化
display(predict_test.select("ID","features","rawPrediction","probability","prediction").limit(5))

f:id:Cream-Kuchen:20200723160546p:plain

ハイパーパラメータをPythonのLightGBMと比べると、keyがキャメル記法 (Pythonはスネーク記法) で、
いくつか見慣れないものも含まれているようですね。予測を行うと、特徴量データにそのまま
予測スコアや予測フラグが紐づけられました。
※ ちなみに、パラメータ設定をせずにモデルの学習を行うと、著しい過学習をおこしました。


7, 学習・検証精度の確認

予測の正解率・精度・再現率・F値ROC_AUCを見てみます。

import pandas as pd
from sklearn.metrics import accuracy_score, precision_score, recall_score, roc_auc_score, confusion_matrix, f1_score

# 精度等算出関数
def get_score(df_predict):
  pd_predict = df_predict.select("label","prediction").toPandas()
  y_test, y_predict = pd_predict["label"], pd_predict["prediction"]
  accuracy = accuracy_score(y_test, y_predict)
  precision = precision_score(y_test, y_predict)
  recall = recall_score(y_test, y_predict)
  f1 = f1_score(y_test, y_predict)
  roc_auc = roc_auc_score(y_test, y_predict)
  return accuracy, precision, recall, f1, roc_auc

# 精度等の算出
accuracy_, precision_, recall_, f1_, roc_auc_ = get_score(predict_train)
accuracy, precision, recall, f1, roc_auc = get_score(predict_test)
comp_score = (
  pd.DataFrame([["train_score", accuracy_, precision_, recall_, f1_, roc_auc_],
                ["test_score", accuracy, precision, recall, f1, roc_auc]]
               ,columns=["index", "accuracy", "precision", "recall", "Fvalue", "roc_auc"])
)
display(comp_score)

f:id:Cream-Kuchen:20200723151209p:plain

前処理を殆ど行っていないせいか、精度等スコアがイマイチの中途半端なモデルができました。


8, 変数重要度の確認

学習モデルの変数重要度を見てみます。

feature_imp = model.getFeatureImportances(importance_type='gain')
imp = pd.DataFrame(data=feature_imp, index=cols_feature, columns=["gain"])
imp["score"] = imp["gain"] / imp["gain"].sum()
imp.sort_values(by=["score"], ascending=False, inplace=True)
imp[["score"]].head(20)

f:id:Cream-Kuchen:20200723152121p:plain

カラム「alcohol」一本足打法の極端なモデルです。精度等がイマイチなわけです。


おわりに

MMLSparkのLightGBMClassifierを使ってみました。

公式ドキュメントは具体的なコード例が少ないので、参考にしてもらえれば幸いです。