0
0

Sparkによるハイパーパラメータチューニング

Last updated at Posted at 2024-03-27

2024/4/12に翔泳社よりApache Spark徹底入門を出版します!

書籍のサンプルノートブックをウォークスルーしていきます。Python/Chapter10/10-7 Hyperparameter Tuningとなります。

翻訳ノートブックのリポジトリはこちら。

ノートブックはこちら

ベストなハイパーパラメータを見つけ出すために、ランダムフォレストに対してハイパーパラメータチューニングを実施しましょう!

from pyspark.ml.feature import StringIndexer, VectorAssembler

filePath = "/databricks-datasets/learning-spark-v2/sf-airbnb/sf-airbnb-clean.parquet"
airbnbDF = spark.read.parquet(filePath)
(trainDF, testDF) = airbnbDF.randomSplit([.8, .2], seed=42)

categoricalCols = [field for (field, dataType) in trainDF.dtypes if dataType == "string"]
indexOutputCols = [x + "Index" for x in categoricalCols]

stringIndexer = StringIndexer(inputCols=categoricalCols, outputCols=indexOutputCols, handleInvalid="skip")

numericCols = [field for (field, dataType) in trainDF.dtypes if ((dataType == "double") & (field != "price"))]
assemblerInputs = indexOutputCols + numericCols
vecAssembler = VectorAssembler(inputCols=assemblerInputs, outputCol="features")

ランダムフォレスト

from pyspark.ml.regression import RandomForestRegressor
from pyspark.ml import Pipeline

rf = RandomForestRegressor(labelCol="price", maxBins=40, seed=42)
pipeline = Pipeline(stages = [stringIndexer, vecAssembler, rf])

グリッドサーチ

チューニング可能なハイパーパラメータは多数存在し、手動で設定するには長い時間を要します。

よりシステマティックなアプローチで最適なハイパーパラメータを見つけ出すために、SparkのParamGridBuilderを活用しましょう Python/Scala

テストするハイパーパラメータのグリッドを定義しましょう:

  • maxDepth: 決定木の最大の深さ(2, 4, 6の値を使用)
  • numTrees: 決定木の数(10, 100の値を使用)
from pyspark.ml.tuning import ParamGridBuilder

paramGrid = (ParamGridBuilder()
            .addGrid(rf.maxDepth, [2, 4, 6])
            .addGrid(rf.numTrees, [10, 100])
            .build())

交差検証

最適なmaxDepthを特定するために、3フォールドの交差検証を活用します。

crossValidation

3フォールドの交差検証によって、データの2/3でトレーニングを行い、(ホールドアウトされた)残りの1/3で評価を行います。このプロセスを3回繰り返すので、それぞれのフォールドは検証用セットとして動作する機会があります。そして、3ラウンドの結果を平均します。

以下を伝えるために、CrossValidatorにはestimator(パイプライン), evaluator, estimatorParamMapsを入力します:

  • 使用するモデル
  • モデルの評価方法
  • モデルに設定するハイパーパラメータ

また、データを分割するフォールドの数を(3)に設定し、データが同じように分割されるようにシードも設定します Python/Scala

from pyspark.ml.evaluation import RegressionEvaluator
from pyspark.ml.tuning import CrossValidator

evaluator = RegressionEvaluator(labelCol="price", 
                                predictionCol="prediction", 
                                metricName="rmse")

cv = CrossValidator(estimator=pipeline, 
                    evaluator=evaluator, 
                    estimatorParamMaps=paramGrid, 
                    numFolds=3, 
                    seed=42)

問題: この時点でいくつのモデルをトレーニングしていますか?

cvModel = cv.fit(trainDF)

Screenshot 2024-03-27 at 18.17.03.png

Parallelismパラメーター

うーん...実行に長い時間を要しています。これは、並列ではなく直列でモデルがトレーニングされているからです!

Spark 2.3では、parallelismパラメータが導入されました。ドキュメントでは、並列アルゴリズムを実行する際のスレッド数 (>= 1)と述べられています。

この値を4に設定し、トレーニングが早くなるかどうかを見てみましょう。

cvModel = cv.setParallelism(4).fit(trainDF)

Screenshot 2024-03-27 at 18.17.28.png

問題: うーん...依然として時間がかかっています。交差検証器の中にパイプラインを埋め込むべきか、パイプラインに交差検証器を埋め込むべきでしょうか?

パイプラインにエスティメーターやトランスフォーマーが含まれるかに依存します。StringIndexer(エスティメーター)のようなものがパイプラインにある場合、交差検証器にパイプライン全体を埋め込むと、毎回再フィットさせなくてはなりません。

cv = CrossValidator(estimator=rf, 
                    evaluator=evaluator, 
                    estimatorParamMaps=paramGrid, 
                    numFolds=3, 
                    parallelism=4, 
                    seed=42)

pipeline = Pipeline(stages=[stringIndexer, vecAssembler, cv])

pipelineModel = pipeline.fit(trainDF)

Screenshot 2024-03-27 at 18.17.53.png

ベストなハイパーパラメータの設定を持つモデルを見てみましょう。

list(zip(cvModel.getEstimatorParamMaps(), cvModel.avgMetrics))
[({Param(parent='RandomForestRegressor_cee37cf57669', name='maxDepth', doc='Maximum depth of the tree. (>= 0) E.g., depth 0 means 1 leaf node; depth 1 means 1 internal node + 2 leaf nodes. Must be in range [0, 30].'): 2,
   Param(parent='RandomForestRegressor_cee37cf57669', name='numTrees', doc='Number of trees to train (>= 1).'): 10},
  291.1822640924783),
 ({Param(parent='RandomForestRegressor_cee37cf57669', name='maxDepth', doc='Maximum depth of the tree. (>= 0) E.g., depth 0 means 1 leaf node; depth 1 means 1 internal node + 2 leaf nodes. Must be in range [0, 30].'): 2,
   Param(parent='RandomForestRegressor_cee37cf57669', name='numTrees', doc='Number of trees to train (>= 1).'): 100},
  286.7714750274078),
 ({Param(parent='RandomForestRegressor_cee37cf57669', name='maxDepth', doc='Maximum depth of the tree. (>= 0) E.g., depth 0 means 1 leaf node; depth 1 means 1 internal node + 2 leaf nodes. Must be in range [0, 30].'): 4,
   Param(parent='RandomForestRegressor_cee37cf57669', name='numTrees', doc='Number of trees to train (>= 1).'): 10},
  287.6963245160818),
 ({Param(parent='RandomForestRegressor_cee37cf57669', name='maxDepth', doc='Maximum depth of the tree. (>= 0) E.g., depth 0 means 1 leaf node; depth 1 means 1 internal node + 2 leaf nodes. Must be in range [0, 30].'): 4,
   Param(parent='RandomForestRegressor_cee37cf57669', name='numTrees', doc='Number of trees to train (>= 1).'): 100},
  279.9927057236079),
 ({Param(parent='RandomForestRegressor_cee37cf57669', name='maxDepth', doc='Maximum depth of the tree. (>= 0) E.g., depth 0 means 1 leaf node; depth 1 means 1 internal node + 2 leaf nodes. Must be in range [0, 30].'): 6,
   Param(parent='RandomForestRegressor_cee37cf57669', name='numTrees', doc='Number of trees to train (>= 1).'): 10},
  294.34810870889305),
 ({Param(parent='RandomForestRegressor_cee37cf57669', name='maxDepth', doc='Maximum depth of the tree. (>= 0) E.g., depth 0 means 1 leaf node; depth 1 means 1 internal node + 2 leaf nodes. Must be in range [0, 30].'): 6,
   Param(parent='RandomForestRegressor_cee37cf57669', name='numTrees', doc='Number of trees to train (>= 1).'): 100},
  275.39862704729984)]

テストデータセットでどうなるのかを見てみましょう。

predDF = pipelineModel.transform(testDF)

regressionEvaluator = RegressionEvaluator(predictionCol="prediction", labelCol="price", metricName="rmse")

rmse = regressionEvaluator.evaluate(predDF)
r2 = regressionEvaluator.setMetricName("r2").evaluate(predDF)
print(f"RMSE is {rmse}")
print(f"R2 is {r2}")
RMSE is 211.70370310223277
R2 is 0.2265254865671944

前回のピュアな決定木の結果よりも精度が改善されました。

はじめてのDatabricks

はじめてのDatabricks

Databricks無料トライアル

Databricks無料トライアル

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0