Skip to content
Snippets Groups Projects
Commit 8ecf7f77 authored by Yanbo Liang's avatar Yanbo Liang Committed by Joseph K. Bradley
Browse files

[SPARK-15292][ML] ML 2.0 QA: Scala APIs audit for classification

## What changes were proposed in this pull request?
Audit Scala API for classification, almost all issues were related ```MultilayerPerceptronClassifier``` in this section.
* Fix one wrong param getter function: ```getOptimizer``` -> ```getSolver```
* Add missing setter function for ```solver``` and ```stepSize```.
* Make ```GD``` solver take effect.
* Update docs, annotations and fix other minor issues.

## How was this patch tested?
Existing unit tests.

Author: Yanbo Liang <ybliang8@gmail.com>

Closes #13076 from yanboliang/spark-15292.
parent 1052d364
No related branches found
No related tags found
No related merge requests found
...@@ -32,22 +32,22 @@ import org.apache.spark.ml.util._ ...@@ -32,22 +32,22 @@ import org.apache.spark.ml.util._
import org.apache.spark.sql.Dataset import org.apache.spark.sql.Dataset
/** Params for Multilayer Perceptron. */ /** Params for Multilayer Perceptron. */
private[ml] trait MultilayerPerceptronParams extends PredictorParams private[classification] trait MultilayerPerceptronParams extends PredictorParams
with HasSeed with HasMaxIter with HasTol with HasStepSize { with HasSeed with HasMaxIter with HasTol with HasStepSize {
/** /**
* Layer sizes including input size and output size. * Layer sizes including input size and output size.
* Default: Array(1, 1)
* *
* @group param * @group param
*/ */
@Since("1.5.0")
final val layers: IntArrayParam = new IntArrayParam(this, "layers", final val layers: IntArrayParam = new IntArrayParam(this, "layers",
"Sizes of layers from input layer to output layer" + "Sizes of layers from input layer to output layer. " +
" E.g., Array(780, 100, 10) means 780 inputs, " + "E.g., Array(780, 100, 10) means 780 inputs, " +
"one hidden layer with 100 neurons and output layer of 10 neurons.", "one hidden layer with 100 neurons and output layer of 10 neurons.",
(t: Array[Int]) => t.forall(ParamValidators.gt(0)) && t.length > 1 (t: Array[Int]) => t.forall(ParamValidators.gt(0)) && t.length > 1)
)
/** @group getParam */ /** @group getParam */
@Since("1.5.0")
final def getLayers: Array[Int] = $(layers) final def getLayers: Array[Int] = $(layers)
/** /**
...@@ -59,42 +59,49 @@ private[ml] trait MultilayerPerceptronParams extends PredictorParams ...@@ -59,42 +59,49 @@ private[ml] trait MultilayerPerceptronParams extends PredictorParams
* *
* @group expertParam * @group expertParam
*/ */
@Since("1.5.0")
final val blockSize: IntParam = new IntParam(this, "blockSize", final val blockSize: IntParam = new IntParam(this, "blockSize",
"Block size for stacking input data in matrices. Data is stacked within partitions." + "Block size for stacking input data in matrices. Data is stacked within partitions." +
" If block size is more than remaining data in a partition then " + " If block size is more than remaining data in a partition then " +
"it is adjusted to the size of this data. Recommended size is between 10 and 1000", "it is adjusted to the size of this data. Recommended size is between 10 and 1000",
ParamValidators.gt(0)) ParamValidators.gt(0))
/** @group getParam */ /** @group expertGetParam */
@Since("1.5.0")
final def getBlockSize: Int = $(blockSize) final def getBlockSize: Int = $(blockSize)
/** /**
* Allows setting the solver: minibatch gradient descent (gd) or l-bfgs. * The solver algorithm for optimization.
* l-bfgs is the default one. * Supported options: "gd" (minibatch gradient descent) or "l-bfgs".
* Default: "l-bfgs"
* *
* @group expertParam * @group expertParam
*/ */
@Since("2.0.0")
final val solver: Param[String] = new Param[String](this, "solver", final val solver: Param[String] = new Param[String](this, "solver",
" Allows setting the solver: minibatch gradient descent (gd) or l-bfgs. " + "The solver algorithm for optimization. Supported options: " +
" l-bfgs is the default one.", s"${MultilayerPerceptronClassifier.supportedSolvers.mkString(", ")}. (Default l-bfgs)",
ParamValidators.inArray[String](Array("gd", "l-bfgs"))) ParamValidators.inArray[String](MultilayerPerceptronClassifier.supportedSolvers))
/** @group getParam */ /** @group expertGetParam */
final def getOptimizer: String = $(solver) @Since("2.0.0")
final def getSolver: String = $(solver)
/** /**
* Model weights. Can be returned either after training or after explicit setting * The initial weights of the model.
* *
* @group expertParam * @group expertParam
*/ */
final val weights: Param[Vector] = new Param[Vector](this, "weights", @Since("2.0.0")
" Sets the weights of the model ") final val initialWeights: Param[Vector] = new Param[Vector](this, "initialWeights",
"The initial weights of the model")
/** @group getParam */
final def getWeights: Vector = $(weights)
/** @group expertGetParam */
@Since("2.0.0")
final def getInitialWeights: Vector = $(initialWeights)
setDefault(maxIter -> 100, tol -> 1e-4, blockSize -> 128, solver -> "l-bfgs", stepSize -> 0.03) setDefault(maxIter -> 100, tol -> 1e-4, blockSize -> 128,
solver -> MultilayerPerceptronClassifier.LBFGS, stepSize -> 0.03)
} }
/** Label to vector converter. */ /** Label to vector converter. */
...@@ -145,14 +152,32 @@ class MultilayerPerceptronClassifier @Since("1.5.0") ( ...@@ -145,14 +152,32 @@ class MultilayerPerceptronClassifier @Since("1.5.0") (
@Since("1.5.0") @Since("1.5.0")
def this() = this(Identifiable.randomUID("mlpc")) def this() = this(Identifiable.randomUID("mlpc"))
/** @group setParam */ /**
* Sets the value of param [[layers]].
*
* @group setParam
*/
@Since("1.5.0") @Since("1.5.0")
def setLayers(value: Array[Int]): this.type = set(layers, value) def setLayers(value: Array[Int]): this.type = set(layers, value)
/** @group setParam */ /**
* Sets the value of param [[blockSize]].
* Default is 128.
*
* @group expertSetParam
*/
@Since("1.5.0") @Since("1.5.0")
def setBlockSize(value: Int): this.type = set(blockSize, value) def setBlockSize(value: Int): this.type = set(blockSize, value)
/**
* Sets the value of param [[solver]].
* Default is "l-bfgs".
*
* @group expertSetParam
*/
@Since("2.0.0")
def setSolver(value: String): this.type = set(solver, value)
/** /**
* Set the maximum number of iterations. * Set the maximum number of iterations.
* Default is 100. * Default is 100.
...@@ -181,12 +206,21 @@ class MultilayerPerceptronClassifier @Since("1.5.0") ( ...@@ -181,12 +206,21 @@ class MultilayerPerceptronClassifier @Since("1.5.0") (
def setSeed(value: Long): this.type = set(seed, value) def setSeed(value: Long): this.type = set(seed, value)
/** /**
* Sets the model weights. * Sets the value of param [[initialWeights]].
* *
* @group expertParam * @group expertSetParam
*/
@Since("2.0.0")
def setInitialWeights(value: Vector): this.type = set(initialWeights, value)
/**
* Sets the value of param [[stepSize]] (applicable only for solver "gd").
* Default is 0.03.
*
* @group setParam
*/ */
@Since("2.0.0") @Since("2.0.0")
def setWeights(value: Vector): this.type = set(weights, value) def setStepSize(value: Double): this.type = set(stepSize, value)
@Since("1.5.0") @Since("1.5.0")
override def copy(extra: ParamMap): MultilayerPerceptronClassifier = defaultCopy(extra) override def copy(extra: ParamMap): MultilayerPerceptronClassifier = defaultCopy(extra)
...@@ -204,16 +238,26 @@ class MultilayerPerceptronClassifier @Since("1.5.0") ( ...@@ -204,16 +238,26 @@ class MultilayerPerceptronClassifier @Since("1.5.0") (
val labels = myLayers.last val labels = myLayers.last
val lpData = extractLabeledPoints(dataset) val lpData = extractLabeledPoints(dataset)
val data = lpData.map(lp => LabelConverter.encodeLabeledPoint(lp, labels)) val data = lpData.map(lp => LabelConverter.encodeLabeledPoint(lp, labels))
val topology = FeedForwardTopology.multiLayerPerceptron(myLayers, true) val topology = FeedForwardTopology.multiLayerPerceptron(myLayers, softmaxOnTop = true)
val trainer = new FeedForwardTrainer(topology, myLayers(0), myLayers.last) val trainer = new FeedForwardTrainer(topology, myLayers(0), myLayers.last)
if (isDefined(weights)) { if (isDefined(initialWeights)) {
trainer.setWeights($(weights)) trainer.setWeights($(initialWeights))
} else { } else {
trainer.setSeed($(seed)) trainer.setSeed($(seed))
} }
trainer.LBFGSOptimizer if ($(solver) == MultilayerPerceptronClassifier.LBFGS) {
.setConvergenceTol($(tol)) trainer.LBFGSOptimizer
.setNumIterations($(maxIter)) .setConvergenceTol($(tol))
.setNumIterations($(maxIter))
} else if ($(solver) == MultilayerPerceptronClassifier.GD) {
trainer.SGDOptimizer
.setNumIterations($(maxIter))
.setConvergenceTol($(tol))
.setStepSize($(stepSize))
} else {
throw new IllegalArgumentException(
s"The solver $solver is not supported by MultilayerPerceptronClassifier.")
}
trainer.setStackSize($(blockSize)) trainer.setStackSize($(blockSize))
val mlpModel = trainer.train(data) val mlpModel = trainer.train(data)
new MultilayerPerceptronClassificationModel(uid, myLayers, mlpModel.weights) new MultilayerPerceptronClassificationModel(uid, myLayers, mlpModel.weights)
...@@ -224,6 +268,15 @@ class MultilayerPerceptronClassifier @Since("1.5.0") ( ...@@ -224,6 +268,15 @@ class MultilayerPerceptronClassifier @Since("1.5.0") (
object MultilayerPerceptronClassifier object MultilayerPerceptronClassifier
extends DefaultParamsReadable[MultilayerPerceptronClassifier] { extends DefaultParamsReadable[MultilayerPerceptronClassifier] {
/** String name for "l-bfgs" solver. */
private[classification] val LBFGS = "l-bfgs"
/** String name for "gd" (minibatch gradient descent) solver. */
private[classification] val GD = "gd"
/** Set of solvers that MultilayerPerceptronClassifier supports. */
private[classification] val supportedSolvers = Array(LBFGS, GD)
@Since("2.0.0") @Since("2.0.0")
override def load(path: String): MultilayerPerceptronClassifier = super.load(path) override def load(path: String): MultilayerPerceptronClassifier = super.load(path)
} }
...@@ -250,7 +303,9 @@ class MultilayerPerceptronClassificationModel private[ml] ( ...@@ -250,7 +303,9 @@ class MultilayerPerceptronClassificationModel private[ml] (
@Since("1.6.0") @Since("1.6.0")
override val numFeatures: Int = layers.head override val numFeatures: Int = layers.head
private val mlpModel = FeedForwardTopology.multiLayerPerceptron(layers, true).model(weights) private val mlpModel = FeedForwardTopology
.multiLayerPerceptron(layers, softmaxOnTop = true)
.model(weights)
/** /**
* Returns layers in a Java List. * Returns layers in a Java List.
......
...@@ -70,6 +70,7 @@ class MultilayerPerceptronClassifierSuite ...@@ -70,6 +70,7 @@ class MultilayerPerceptronClassifierSuite
.setBlockSize(1) .setBlockSize(1)
.setSeed(123L) .setSeed(123L)
.setMaxIter(100) .setMaxIter(100)
.setSolver("l-bfgs")
val model = trainer.fit(dataset) val model = trainer.fit(dataset)
val result = model.transform(dataset) val result = model.transform(dataset)
val predictionAndLabels = result.select("prediction", "label").collect() val predictionAndLabels = result.select("prediction", "label").collect()
...@@ -93,9 +94,9 @@ class MultilayerPerceptronClassifierSuite ...@@ -93,9 +94,9 @@ class MultilayerPerceptronClassifierSuite
.setMaxIter(1) .setMaxIter(1)
.setTol(1e-6) .setTol(1e-6)
val initialWeights = trainer.fit(dataFrame).weights val initialWeights = trainer.fit(dataFrame).weights
trainer.setWeights(initialWeights.copy) trainer.setInitialWeights(initialWeights.copy)
val weights1 = trainer.fit(dataFrame).weights val weights1 = trainer.fit(dataFrame).weights
trainer.setWeights(initialWeights.copy) trainer.setInitialWeights(initialWeights.copy)
val weights2 = trainer.fit(dataFrame).weights val weights2 = trainer.fit(dataFrame).weights
assert(weights1 ~== weights2 absTol 10e-5, assert(weights1 ~== weights2 absTol 10e-5,
"Training should produce the same weights given equal initial weights and number of steps") "Training should produce the same weights given equal initial weights and number of steps")
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment