-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-7127] [MLLIB] Adding broadcast of model before prediction for ensembles #6300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
83904bb
aaad77b
6fd153c
171a6ce
1f34be4
9afad56
40a139d
86e73de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
| package org.apache.spark.ml | ||
|
|
||
| import org.apache.spark.annotation.DeveloperApi | ||
| import org.apache.spark.broadcast.Broadcast | ||
| import org.apache.spark.ml.param._ | ||
| import org.apache.spark.ml.param.shared._ | ||
| import org.apache.spark.ml.util.SchemaUtils | ||
|
|
@@ -176,17 +177,53 @@ abstract class PredictionModel[FeaturesType, M <: PredictionModel[FeaturesType, | |
| override def transform(dataset: DataFrame): DataFrame = { | ||
| transformSchema(dataset.schema, logging = true) | ||
| if ($(predictionCol).nonEmpty) { | ||
| dataset.withColumn($(predictionCol), callUDF(predict _, DoubleType, col($(featuresCol)))) | ||
| transformImpl(dataset) | ||
| } else { | ||
| this.logWarning(s"$uid: Predictor.transform() was called as NOOP" + | ||
| " since no output columns were set.") | ||
| dataset | ||
| } | ||
| } | ||
|
|
||
| protected def transformImpl(dataset: DataFrame): DataFrame = { | ||
| dataset.withColumn($(predictionCol), callUDF(predict _, DoubleType, col($(featuresCol)))) | ||
| } | ||
|
|
||
| /** | ||
| * Predict label for the given features. | ||
| * This internal method is used to implement [[transform()]] and output [[predictionCol]]. | ||
| */ | ||
| protected def predict(features: FeaturesType): Double | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * :: DeveloperApi :: | ||
| * | ||
| * Abstraction for a model for prediction tasks that will broadcast the model used to predict. | ||
| * | ||
| * @tparam FeaturesType Type of features. | ||
| * E.g., [[org.apache.spark.mllib.linalg.VectorUDT]] for vector features. | ||
| * @tparam M Specialization of [[PredictionModel]]. If you subclass this type, use this type | ||
| * parameter to specify the concrete type for the corresponding model. | ||
| */ | ||
| @DeveloperApi | ||
| abstract class PredictionModelBroadcasting[ | ||
| FeaturesType, M <: PredictionModelBroadcasting[FeaturesType, M] | ||
| ] | ||
| extends PredictionModel[FeaturesType, M] { | ||
|
|
||
| protected def transformImpl(dataset: DataFrame, bcastModel: Broadcast[M]): DataFrame = { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if using the Broadcast variable as a parameter is a good idea |
||
|
|
||
| dataset.withColumn($(predictionCol), | ||
| callUDF((features: FeaturesType) => predictWithBroadcastModel(features, bcastModel), | ||
| DoubleType, col($(featuresCol))) | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Predict label for the given features using a broadcasted model. | ||
| * This internal method is used to implement [[transform()]] and output [[predictionCol]]. | ||
| */ | ||
| protected def predictWithBroadcastModel(features: FeaturesType, bcastModel: Broadcast[M]): Double | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,8 @@ package org.apache.spark.ml.classification | |
| import scala.collection.mutable | ||
|
|
||
| import org.apache.spark.annotation.AlphaComponent | ||
| import org.apache.spark.ml.{PredictionModel, Predictor} | ||
| import org.apache.spark.broadcast.Broadcast | ||
| import org.apache.spark.ml.{PredictionModelBroadcasting, Predictor} | ||
| import org.apache.spark.ml.param.ParamMap | ||
| import org.apache.spark.ml.tree.{RandomForestParams, TreeClassifierParams, DecisionTreeModel, TreeEnsembleModel} | ||
| import org.apache.spark.ml.util.{Identifiable, MetadataUtils} | ||
|
|
@@ -109,6 +110,7 @@ object RandomForestClassifier { | |
| RandomForestParams.supportedFeatureSubsetStrategies | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * :: AlphaComponent :: | ||
| * | ||
|
|
@@ -122,7 +124,7 @@ object RandomForestClassifier { | |
| final class RandomForestClassificationModel private[ml] ( | ||
| override val uid: String, | ||
| private val _trees: Array[DecisionTreeClassificationModel]) | ||
| extends PredictionModel[Vector, RandomForestClassificationModel] | ||
| extends PredictionModelBroadcasting[Vector, RandomForestClassificationModel] | ||
| with TreeEnsembleModel with Serializable { | ||
|
|
||
| require(numTrees > 0, "RandomForestClassificationModel requires at least 1 tree.") | ||
|
|
@@ -134,13 +136,28 @@ final class RandomForestClassificationModel private[ml] ( | |
|
|
||
| override def treeWeights: Array[Double] = _treeWeights | ||
|
|
||
| override def transform(dataset: DataFrame): DataFrame = { | ||
| val bcastModel = dataset.sqlContext.sparkContext.broadcast(this) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mentioned that we might want to selectively broadcast the model, only if it's large enough. Do you think that is something we can do here automatically, or would it need to be a configuration setting? |
||
| transformImpl(dataset, bcastModel) | ||
| } | ||
|
|
||
| override protected def predict(features: Vector): Double = { | ||
| // TODO: Override transform() to broadcast model. SPARK-7127 | ||
| // TODO: When we add a generic Bagging class, handle transform there: SPARK-7128 | ||
| // Predict without using a broadcasted mode | ||
| predictImpl(features, () => this) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we still want to support prediction without broadcasting?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we will want to. If it's a small model, then it can be faster to not broadcast it. |
||
| } | ||
|
|
||
| override protected def predictWithBroadcastModel(features: Vector, | ||
| bcastModel: Broadcast[RandomForestClassificationModel]): Double = { | ||
| // Predict using the given broadcasted model | ||
| predictImpl(features, () => bcastModel.value) | ||
| } | ||
|
|
||
| protected def predictImpl(features: Vector, modelAccesor: () => TreeEnsembleModel): Double = { | ||
| // Classifies using majority votes. | ||
| // Ignore the weights since all are 1.0 for now. | ||
| val votes = mutable.Map.empty[Int, Double] | ||
| _trees.view.foreach { tree => | ||
| modelAccesor().trees.view.foreach { tree => | ||
| val prediction = tree.rootNode.predict(features).toInt | ||
| votes(prediction) = votes.getOrElse(prediction, 0.0) + 1.0 // 1.0 = weight | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could call: