nykergoto’s blog

機械学習とpythonをメインに

pyspark (mmlspark) で LightGBM 使うときのメモ

f:id:dette:20201120134640p:plain

Spark 上で Machine Learning を行うためのツールを Microsoft が MMLSpark (Microsoft Machine Learning for Apache Spark) というパッケージで公開しています。

https://github.com/Azure/mmlspark

この中に lightGBM on spark も含まれており python を使って spark 上で lightGBM の学習を回すことができます。 これを使っていて、わからなかったこと・困ったことがいくつかありましたので、調べたメモ書きを共有します。

mmlspark での LightGBM のパラメータ指定

mmlspark の lightGBM と本家本元の lightGBM のパラメータは引数の名前が違います。本家の lightGBM では無限にエイリアスが貼られているので雰囲気で名前を与えても動きますが mmlspark はそうではありません。

パラメータに関するドキュメントが公式にあればよいのですが見当たらないようです (おそらく sコードを読んで追いかけろということ?)。以下 Classifier の方に関してざっと作ってみました。本家の方では fit の方で指定するパラメータも __init__ で指定するようになっていることに注意してください。

class LightGBMClassifier:
    def __init__(self,
                 baggingFraction=1.0, baggingFreq=0, baggingSeed=3, boostFromAverage=True, boostingType='gbdt',
                 categoricalSlotIndexes=None, categoricalSlotNames=None, defaultListenPort=12400, earlyStoppingRound=0,
                 featureFraction=1.0, featuresCol='features', groupCol=None, initScoreCol=None,
                 isProvideTrainingMetric=False, labelCol='label', labelGain=[], lambdaL1=0.0, lambdaL2=0.0,
                 learningRate=0.1, maxBin=255, maxDepth=-1, maxPosition=20, minSumHessianInLeaf=0.001, modelString='',
                 numBatches=0, numIterations=100, numLeaves=31, objective='lambdarank', parallelism='data_parallel',
                 predictionCol='prediction', timeout=1200.0, useBarrierExecutionMode=False, validationIndicatorCol=None,
                 verbosity=1, weightCol=None):
        """

        Args:
            # tree 成長に関わるパラメータ
            numIterations:
                大事. learning rate との兼ね合いで決まります.
                rate を小さくすると iterations は多くしましょう.
            learningRate:
                大事. 0.3 ~ 0.1 ぐらいが普通. 
                小さくすると精度は上がる傾向があるけれど時間もかかります
            maxDepth:
                3 ~ 8 ぐらい. 問題によって最適な値が違うことが多いですが 3=8位を設定しておけば大概はOK。

            # 正則化
            lambdaL1:
            lambdaL2:
                L1/L2 正則化. 大きくすると木の成長がゆっくりになります (極端な予測を持つ葉ができにくくなる)
            numLeaves:
                葉の数. 2 ** max_depth より小さくすることで木の成長をゆっくりにできる

            # ロバスト性 / 正則化
            baggingFraction:
                bagging (subsamples) の割合 0 ~ 1
            featureFraction:
                columns のサンプリング割合. 0.3 / 0.5 / 0.7 とかを使うことが多いです.
            baggingFreq:
                bagging を実行する頻度. たとえば 10 とすると tree を 10 個作るごとに bagging を実行します
            baggingSeed:
                bagging でランダムに選ぶときの seed 値
            boostingType:
                `"gbdt"` で問題ないと思います
            minSumHessianInLeaf:
                カジュアルに言うと一つの予測集合の最小の大きさみたいなものです。(Objective="mse" のとき一致します)
                小さすぎると細かい粒度での分割を許可してしまうのである程度大きい値を指定してあげても良いと思いいます。
                (集合サイズなので学習させるデータの大きさに依存して良い値が変わってくるのに注意)

            # eval set 作るとき
            validationIndicatorCol:
                ちょっと指定のしかたが lightGBM (python) と違っていて, evaluation set を作りたい場合,
                fit にわたすデータに valid data を concat して train or valid を表す 0-1 の flag を
                `validationIndicatorCol` に指定します.
            earlyStoppingRound:
                指定された回数以上 objective が改善しないときに学習を中断します. 50 とか 100 を指定することが多いです。
        """

References

2: evaluation set をつかって early stopping をする

学習させるとなると early stopping を使いたいでしょう。本家 lightGBM では fit を呼び出す際に eval_set に validation 用のデータを渡せば early stopping を行ってくれますが mmlspark の場合そうではなく若干クセがあります。これも公式ドキュメントに記載が何もなかったので作ってみました。大きな流れは以下のとおりです

  1. validation かどうかのフラグをデータにもたせる
  2. evaluation data を train data と merge (pyspark method でいうと union) する
  3. validationIndicatorCol に作ったフラグのカラムを指定する

python api の流儀と異なっているので注意してください。

Reference

Sample Code

train_df = # (some pyspark dataframe for train)
eval_df = # (me too. for test)

# 1. `"validation"` column にこのデータが train / eval であることを表すフラグを作る
# python api の用に fit で eval_set= とは渡さないことに注意
_train = train_df.withColumn('validation', F.col('target') * 0)[['target', 'feature', 'validation']]
_test = eval_df.withColumn('validation', F.col('target') * 0 + 1)[['target', 'feature', 'validation']]
input_df = _train.union(_test)

from mmlspark.lightgbm import LightGBMClassifier

params = {
  'numIterations': 200,
  'learningRate': .2,
  'maxDepth': 7,
  'lambdaL2': 1.,
  'numLeaves': 31,
  'baggingFraction': .7,
  'featureFraction': .5,
  'baggingFreq': 3,
  'minSumHessianInLeaf': 100.
}

# 2. `validationIndicatorCol` を指定する
clf = LightGBMClassifier(
  validationIndicatorCol='validation',
  earlyStoppingRound=50,
  labelCol='target',
  featuresCol='feature',
  **params
)

# 3. call `fit`. (fit のときにはたんに事前に作った data-frame を入れる)
clf = clf.fit(input_df)

note

validation data での objective 及び metric 評価は spark 上で分散計算されないようです。そのため training data と同じぐらい大きいデータセットを validation に指定すると速攻で Out-Of-Memory になりますので注意してください。

ref: [LightGBM] Faild evaluation phase using validationIndicatorCol #924 https://github.com/Azure/mmlspark/issues/924