Skip to content
8 changes: 7 additions & 1 deletion mllib/src/main/scala/org/apache/spark/ml/Predictor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,15 @@ abstract class PredictionModel[FeaturesType, M <: PredictionModel[FeaturesType,
* @return transformed dataset with [[predictionCol]] of type [[Double]]
*/
override def transform(dataset: DataFrame): DataFrame = {
transformImpl(dataset, predict)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could call:

transformImpl(dataset)

protected def transformImpl(
dataset: DataFrame,
predictFunc: (FeaturesType) => Double): DataFrame = {
transformSchema(dataset.schema, logging = true)
if ($(predictionCol).nonEmpty) {
dataset.withColumn($(predictionCol), callUDF(predict _, DoubleType, col($(featuresCol))))
dataset.withColumn($(predictionCol), callUDF(predictFunc, DoubleType, col($(featuresCol))))
} else {
this.logWarning(s"$uid: Predictor.transform() was called as NOOP" +
" since no output columns were set.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ object RandomForestClassifier {
RandomForestParams.supportedFeatureSubsetStrategies
}


/**
* :: AlphaComponent ::
*
Expand All @@ -134,13 +135,23 @@ final class RandomForestClassificationModel private[ml] (

override def treeWeights: Array[Double] = _treeWeights

override def transform(dataset: DataFrame): DataFrame = {
val bcastModel = dataset.sqlContext.sparkContext.broadcast(this)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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?

val predictFunc = (features: Vector) => predictImpl(features, () => bcastModel.value)
transformImpl(dataset, predictFunc)
}

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 model
predictImpl(features, () => this)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still want to support prediction without broadcasting?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

}

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
}
Expand Down