Skip to content
Closed
135 changes: 135 additions & 0 deletions docs/ml-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,141 @@ jsc.stop();

</div>

## Examples: Summaries for LogisticRegression.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Examples -> Example

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Summaries -> Summary (There is only one summary)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No period at end


Once LogisticRegression is run on data, it is useful to extract statistics such as the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

When referencing code in docs, use backticks or link to API docs

loss per iteration which will provide an intuition on overfitting and metrics to understand
how well the model has performed on training and test data.

LogisticRegressionTrainingSummary provides an interface to access such relevant information

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please link API docs for LogisticRegressionTrainingSummary

i.e the objectiveHistory and metrics to evaluate the performance on the training data
directly with very less code to be rewritten by the user.
In the future, a method would be made available in the fitted LogisticRegressionModel to obtain
a LogisticRegressionSummary of the test data as well.

This examples illustrates the use of LogisticRegressionTrainingSummary on some toyData.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"toyData" -> "toy data"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The Scala / Java effect :P


<div class="codetabs">
<div data-lang="scala">
{% highlight scala %}
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.ml.classification.{LogisticRegression, BinaryLogisticRegressionSummary}
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.sql.{Row, SQLContext}

val conf = new SparkConf().setAppName("LogisticRegressionSummary")
val sc = new SparkContext(conf)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No need for conf, sc, or sqlContext since they exist already in spark-shell (the code examples should be copy-pasteable into spark-shell)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I copied the example code shown previously where all these are set.

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.

We are not always consistent, but I've been trying to discourage those. Basically, I want users to be able to copy and paste the code into the spark shell and have it work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I realized that and I removed it.

val sqlContext = new SQLContext(sc)
import sqlContext.implicits._

// Use some random data for demonstration.
// Note that the RDD of LabeledPoints can be converted to a dataframe directly.
val data = sc.parallelize(Array(
LabeledPoint(0.0, Vectors.dense(0.2, 4.5, 1.6)),
LabeledPoint(1.0, Vectors.dense(3.1, 6.8, 3.6)),
LabeledPoint(0.0, Vectors.dense(2.4, 0.9, 1.9)),
LabeledPoint(1.0, Vectors.dense(9.1, 3.1, 3.6)),
LabeledPoint(0.0, Vectors.dense(2.5, 1.9, 9.1)))
)
val logRegDataFrame = data.toDF()

// Run Logistic Regression on your toy data.
// Since LogisticRegression is an estimator, it returns an instance of LogisticRegressionModel
// which is a transformer.
val logReg = new LogisticRegression()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use builder notation new LogisticRegression().setMaxIter(...).setRegParam(...)

logReg.setMaxIter(5)
logReg.setRegParam(0.01)
val logRegModel = logReg.fit(logRegDataFrame)

// Extract the summary directly from the returned LogisticRegressionModel instance.
val trainingSummary = logRegModel.summary

// Obtain the loss per iteration. This should decrease upto a certain point and
// then increase or show negligible change after this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Training loss should always decrease; overfitting only affects the loss on test-set

val objectiveHistory = trainingSummary.objectiveHistory
objectiveHistory.foreach(loss => println(loss))

// Obtain the metrics useful to judge performance on test data.
val binarySummary = trainingSummary.asInstanceOf[BinaryLogisticRegressionSummary]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Add comment that that this cast is OK because we have a binary classification problem

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ditto for java example


// Obtain the receiver-operating characteristic as a dataframe and areaUnderROC.
val roc = binarySummary.roc
val truePositiveRate = roc.select("FPR")
val area = binarySummary.areaUnderROC

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Make these printlns so users can C/P directly into spark-shell


// Obtain the threshold with the highest fMeasure.
val fMeasure = binarySummary.fMeasureByThreshold
val fScoreRDD = fMeasure.map { case Row(thresh: Double, fscore: Double) => (thresh, fscore) }
val (highThresh, highFScore) = fScoreRDD.fold((0.0, 0.0))((threshFScore1, threshFScore2) => {
if (threshFScore1._2 > threshFScore2._2) threshFScore1 else threshFScore2
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@feynmanliang Is there are a shorthand to do this directly in Spark SQL? Once I understand that I can update the Java Example as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You might be able to get it done by doing an aggregation (e.g. max) but I haven't tried myself. In any case, I think just keeping everything up to L868 is sufficient since L870-872 aren't really showing how to use the summary feature anyways

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I had thought the user can choose the best threshold and re-run LogisticRegression on this best threshold.

In the first run, it will be a random sampling of the dataset and in the second run it will be the entire dataset. Do you still think it will not be useful?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The closest I could come up with is.

val maxFMeasure = fMeasure.select(max(df("F-Measure"))).collect()(0).getFloat(0)
val threshold = fMeasure.filter(df("F-Measure") >= maxFMeasure).collect()(0).getFloat(0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice!

I think what you described is useful, but is outside the scope of LogisticRegressionSummary. L869-L872 don't demonstrate any of the functionality these docs are intended to describe, which is why I propose we remove it. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK. I wouldn't complain because it makes my job easier.

{% endhighlight %}
</div>

<div data-lang="java">
{% highlight java %}
import com.google.common.collect.Lists;

import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.ml.classification.BinaryLogisticRegressionSummary;
import org.apache.spark.ml.classification.LogisticRegression;
import org.apache.spark.ml.classification.LogisticRegressionModel;
import org.apache.spark.ml.classification.LogisticRegressionTrainingSummary;
import org.apache.spark.mllib.regression.LabeledPoint;
import org.apache.spark.mllib.linalg.Vectors;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.Row;

// Use some random data for demonstration.
// Note that the RDD of LabeledPoints can be converted to a dataframe directly.
JavaRDD<LabeledPoint> data = sc.parallelize(Lists.newArrayList(
new LabeledPoint(0.0, Vectors.dense(0.2, 4.5, 1.6)),
new LabeledPoint(1.0, Vectors.dense(3.1, 6.8, 3.6)),
new LabeledPoint(0.0, Vectors.dense(2.4, 0.9, 1.9)),
new LabeledPoint(1.0, Vectors.dense(9.1, 3.1, 3.6)),
new LabeledPoint(0.0, Vectors.dense(2.5, 1.9, 9.1)))
);
DataFrame logRegDataFrame = sql.createDataFrame(data, LabeledPoint.class);

// Run Logistic Regression on your toy data.
// Since LogisticRegression is an estimator, it returns an instance of LogisticRegressionModel
// which is a transformer.
LogisticRegression logReg = new LogisticRegression();
logReg.setMaxIter(5);
logReg.setRegParam(0.01);
LogisticRegressionModel logRegModel = logReg.fit(logRegDataFrame);

// Extract the summary directly from the returned LogisticRegressionModel instance.
LogisticRegressionTrainingSummary trainingSummary = logRegModel.summary();

// Obtain the loss per iteration. This should decrease upto a certain point and
// then increase or show negligible change after this.
double[] objectiveHistory = trainingSummary.objectiveHistory();
for (double lossPerIteration: objectiveHistory) {
System.out.println(lossPerIteration);
}

// Obtain the metrics useful to judge performance on test data.
BinaryLogisticRegressionSummary binarySummary = (BinaryLogisticRegressionSummary) trainingSummary;

// Obtain the receiver-operating characteristic as a dataframe and areaUnderROC.
DataFrame roc = binarySummary.roc();
DataFrame truePositiveRate = roc.select("FPR");
double area = binarySummary.areaUnderROC();

// Obtain the threshold with the highest fMeasure.
DataFrame fMeasure = binarySummary.fMeasureByThreshold();


{% highlight %}
</div>

</div>




# Dependencies

Spark ML currently depends on MLlib and has the same dependencies.
Expand Down