-
Notifications
You must be signed in to change notification settings - Fork 28.5k
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
[SPARK-19155][ML] MLlib GeneralizedLinearRegression family and link should case insensitive #16516
Conversation
cc @felixcheung |
Test build #71082 has finished for PR 16516 at commit
|
This is a nice fix. It looks like some other learners have this issue as well, eg LogisticRegression.scala under $(root)/mllib/src/main/scala/org/apache/spark/ml/classification/LogisticRegression.scala |
Maybe a more generic fix would be to fix the method ParamValidators.inArray to be case insensitive. I see this method used in a lot of places. Doing a simple search brings up not just LogisticRegression.scala but also: |
I'd agree with that. Given that wider scope of changes I'd suggest creating another JIRA to make it clear the scope & impact - it wouldn't be just affecting SparkR. |
@imatiach-msft @felixcheung Sounds good, I opened SPARK-19155 to track and will update this PR soon. Thanks. |
@@ -365,7 +365,7 @@ class LogisticRegression @Since("1.2.0") ( | |||
case None => histogram.length | |||
} | |||
|
|||
val isMultinomial = $(family) match { | |||
val isMultinomial = $(family).toLowerCase match { |
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.
is there a way to store the param as the lowered case version, instead of turning it into lower case when accessed? it might be less error prone that way?
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.
It can, but I think it would need to be done in the concrete setXXX
method each time.
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.
I don't think we can do that in setXXX
methods, since they are not the only entrance to set params, we can also use the following API to set value for params:
def fit(dataset: Dataset[_], firstParamPair: ParamPair[_], otherParamPairs: ParamPair[_]*): M = {
val map = new ParamMap()
.put(firstParamPair)
.put(otherParamPairs: _*)
fit(dataset, map)
}
Please feel free to correct me if I have misunderstand. Thanks.
cc @jkbradley @sethah
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.
maybe we need to have a different accessor that is consistently used on the transform/estimator side internally to:
1.) change the value to lowercase 2.) trim any whitespace
Changing the setter might cause issues because then when users try to validate that their parameters are set correctly they will see that they are modified, which is unexpected. The case-insensitive compare should be done as in this PR, but instead of calling toLowerCase everywhere explicitly we should be accessing using some other method that normalizes the parameter internally
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.
@yanboliang is correct that there are other entrance points for setting and getting Params. I agree it'd be nice to consolidate them, but that would be quite a bit of work and lower priority than other tech debt we currently have, IMO.
Test build #71178 has finished for PR 16516 at commit
|
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.
Is it possible to change the ParamValidators.inArray[String] method to verify the given string in a case-insensitive way? Then you wouldn't need to make as many changes.
@@ -91,8 +91,8 @@ private[classification] trait LogisticRegressionParams extends ProbabilisticClas | |||
@Since("2.1.0") | |||
final val family: Param[String] = new Param(this, "family", | |||
"The name of family which is a description of the label distribution to be used in the " + | |||
s"model. Supported options: ${supportedFamilyNames.mkString(", ")}.", | |||
ParamValidators.inArray[String](supportedFamilyNames)) | |||
s"model (case-insensitive). Supported options: ${supportedFamilyNames.mkString(", ")}.", |
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.
Is it possible to change the ParamValidators.inArray[String] method to verify the given string in a case-insensitive way? Then you wouldn't need to make as many changes. (eg this change could be reverted)
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.
maybe you could add a ParamValidators.inStringArray(supportedFamilyNames)) method which would both normalize to lowercase and trim whitespace (?)
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.
@imatiach-msft I think we should not to change the behavior of ParamValidators.inArray[String]
, since some other string params may case-sensitive
which use the original check.
Adding a new method sounds reasonable, but I'm a bit worried that whether we should add a so concrete method in the common validation object ParamValidators
which use generic type. I'm still open on this topic and would like to hear more thoughts. Thanks.
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.
you're right, I searched through the code base and case-sensitivity matters when:
1.) we are specifying some column name as a parameter
2.) RModel formula (from RFormula.scala)
3.) Tokenizer.scala regex pattern
In all other cases it doesn't seem like it should matter.
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.
Searching through the code base these are the places where we use Param[String]:
spark-mllib_2.11
org.apache.spark.ml.classification
LogisticRegression.scala
final val family: Param[String] = new Param(this, "family",
MultilayerPerceptronClassifier.scala
final val solver: Param[String] = new Param[String](this, "solver",
final val solver: Param[String] = new Param[String](this, "solver",
NaiveBayes.scala
final val modelType: Param[String] = new Param[String](this, "modelType", "The model type " +
final val modelType: Param[String] = new Param[String](this, "modelType", "The model type " +
org.apache.spark.ml.clustering
KMeans.scala
final val initMode = new Param[String](this, "initMode", "The initialization algorithm. " +
LDA.scala
final val optimizer = new Param[String](this, "optimizer", "Optimizer or inference" +
final val topicDistributionCol = new Param[String](this, "topicDistributionCol", "Output column" +
org.apache.spark.ml.evaluation
BinaryClassificationEvaluator.scala
val metricName: Param[String] = {
MulticlassClassificationEvaluator.scala
val metricName: Param[String] = {
RegressionEvaluator.scala
val metricName: Param[String] = {
org.apache.spark.ml.feature
Bucketizer.scala
val handleInvalid: Param[String] = new Param[String](this, "handleInvalid", "how to handle " +
val handleInvalid: Param[String] = new Param[String](this, "handleInvalid", "how to handle " +
ChiSqSelector.scala
final val selectorType = new Param[String](this, "selectorType",
QuantileDiscretizer.scala
val handleInvalid: Param[String] = new Param[String](this, "handleInvalid", "how to handle " +
val handleInvalid: Param[String] = new Param[String](this, "handleInvalid", "how to handle " +
RFormula.scala
val formula: Param[String] = new Param(this, "formula", "R model formula")
SQLTransformer.scala
final val statement: Param[String] = new Param[String](this, "statement", "SQL statement")
final val statement: Param[String] = new Param[String](this, "statement", "SQL statement")
Tokenizer.scala
val pattern: Param[String] = new Param(this, "pattern", "regex pattern used for tokenizing")
org.apache.spark.ml.param
ParamsSuite.scala
val param = new Param[String](dummy, "name", "doc")
org.apache.spark.ml.param.shared
sharedParams.scala
final val featuresCol: Param[String] = new Param[String](this, "featuresCol", "features column name")
final val featuresCol: Param[String] = new Param[String](this, "featuresCol", "features column name")
final val labelCol: Param[String] = new Param[String](this, "labelCol", "label column name")
final val labelCol: Param[String] = new Param[String](this, "labelCol", "label column name")
final val predictionCol: Param[String] = new Param[String](this, "predictionCol", "prediction column name")
final val predictionCol: Param[String] = new Param[String](this, "predictionCol", "prediction column name")
final val rawPredictionCol: Param[String] = new Param[String](this, "rawPredictionCol", "raw prediction (a.k.a. confidence) column name")
final val rawPredictionCol: Param[String] = new Param[String](this, "rawPredictionCol", "raw prediction (a.k.a. confidence) column name")
... P...
... P...
final val varianceCol: Param[String] = new Param[String](this, "varianceCol", "Column name for the biased sample variance of prediction")
final val varianceCol: Param[String] = new Param[String](this, "varianceCol", "Column name for the biased sample variance of prediction")
final val inputCol: Param[String] = new Param[String](this, "inputCol", "input column name")
final val inputCol: Param[String] = new Param[String](this, "inputCol", "input column name")
final val outputCol: Param[String] = new Param[String](this, "outputCol", "output column name")
final val outputCol: Param[String] = new Param[String](this, "outputCol", "output column name")
... P...
... P...
final val weightCol: Param[String] = new Param[String](this, "weightCol", "weight column name. If this is not set or empty, we treat all instance weights as 1.0")
final val weightCol: Param[String] = new Param[String](this, "weightCol", "weight column name. If this is not set or empty, we treat all instance weights as 1.0")
final val solver: Param[String] = new Param[String](this, "solver", "the solver algorithm for optimization. If this is not set or empty, default value is 'auto'")
final val solver: Param[String] = new Param[String](this, "solver", "the solver algorithm for optimization. If this is not set or empty, default value is 'auto'")
org.apache.spark.ml.recommendation
ALS.scala
val userCol = new Param[String](this, "userCol", "column name for user ids. Ids must be within " +
val itemCol = new Param[String](this, "itemCol", "column name for item ids. Ids must be within " +
val ratingCol = new Param[String](this, "ratingCol", "column name for ratings")
val intermediateStorageLevel = new Param[String](this, "intermediateStorageLevel",
val finalStorageLevel = new Param[String](this, "finalStorageLevel",
org.apache.spark.ml.regression
AFTSurvivalRegression.scala
final val censorCol: Param[String] = new Param(this, "censorCol", "censor column name")
final val quantilesCol: Param[String] = new Param(this, "quantilesCol", "quantiles column name")
GeneralizedLinearRegression.scala
final val family: Param[String] = new Param(this, "family",
final val link: Param[String] = new Param(this, "link", "The name of link function " +
final val linkPredictionCol: Param[String] = new Param[String](this, "linkPredictionCol",
final val linkPredictionCol: Param[String] = new Param[String](this, "linkPredictionCol",
org.apache.spark.ml.tree
treeParams.scala
final val impurity: Param[String] = new Param[String](this, "impurity", "Criterion used for" +
final val impurity: Param[String] = new Param[String](this, "impurity", "Criterion used for" +
final val impurity: Param[String] = new Param[String](this, "impurity", "Criterion used for" +
final val impurity: Param[String] = new Param[String](this, "impurity", "Criterion used for" +
final val featureSubsetStrategy: Param[String] = new Param[String](this, "featureSubsetStrategy",
final val featureSubsetStrategy: Param[String] = new Param[String](this, "featureSubsetStrategy",
val lossType: Param[String] = new Param[String](this, "lossType", "Loss function which GBT" +
val lossType: Param[String] = new Param[String](this, "lossType", "Loss function which GBT" +
val lossType: Param[String] = new Param[String](this, "lossType", "Loss function which GBT" +
val lossType: Param[String] = new Param[String](this, "lossType", "Loss function which GBT" +
org.apache.spark.ml.util
DefaultReadWriteTest.scala
final val stringParam: Param[String] = new Param[String](this, "stringParam", "doc")
final val stringParam: Param[String] = new Param[String](this, "stringParam", "doc")
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.
maybe we can add an additional string param validators class then to the same params.scala file in ml folder? There should be a generic function and the params.scala file seems to be the right place.
It looks like you can also update the metric name in the evaluators (binary, regression, multiclass) as well. Those should be case-insensitive too, I think. |
@imatiach-msft I think not all string params should be case-insensitive, such as:
Please see the PR description. And for lots of other string params that you searched out, like |
yep, I wrote that in a comment above, I totally agree: |
de6994c
to
f1f4c89
Compare
I found this involves lots of problems which need further defined and refactor some code, so I will narrow the scope of this PR to only make |
Hmm ok, I guess that's fine. I'm just worried this line is duplicated, maybe you could add a method for it and put it in a common place: Otherwise the code looks great to me! |
@imatiach-msft Yeah, that line was also duplicated in some other estimators. I don't think we have a good way to add it to the base class |
Test build #71722 has finished for PR 16516 at commit
|
looks good to me |
…hould case insensitive ## What changes were proposed in this pull request? MLlib ```GeneralizedLinearRegression``` ```family``` and ```link``` should be case insensitive. This is consistent with some other MLlib params such as [```featureSubsetStrategy```](https://github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/tree/treeParams.scala#L415). ## How was this patch tested? Update corresponding tests. Author: Yanbo Liang <[email protected]> Closes #16516 from yanboliang/spark-19133. (cherry picked from commit 3dcad9f) Signed-off-by: Yanbo Liang <[email protected]>
…hould case insensitive ## What changes were proposed in this pull request? MLlib ```GeneralizedLinearRegression``` ```family``` and ```link``` should be case insensitive. This is consistent with some other MLlib params such as [```featureSubsetStrategy```](https://github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/tree/treeParams.scala#L415). ## How was this patch tested? Update corresponding tests. Author: Yanbo Liang <[email protected]> Closes #16516 from yanboliang/spark-19133. (cherry picked from commit 3dcad9f) Signed-off-by: Yanbo Liang <[email protected]>
Merged into master and branch-2.1. Thanks for all your reviewing. |
## What changes were proposed in this pull request? This is a supplement to PR #16516 which did not make the value from `getFamily` case insensitive. Current tests of poisson/binomial glm with weight fail when specifying 'Poisson' or 'Binomial', because the calculation of `dispersion` and `pValue` checks the value of family retrieved from `getFamily` ``` model.getFamily == Binomial.name || model.getFamily == Poisson.name ``` ## How was this patch tested? Update existing tests for 'Poisson' and 'Binomial'. yanboliang felixcheung imatiach-msft Author: actuaryzhang <[email protected]> Closes #16675 from actuaryzhang/family. (cherry picked from commit f067ace) Signed-off-by: Yanbo Liang <[email protected]>
## What changes were proposed in this pull request? This is a supplement to PR apache#16516 which did not make the value from `getFamily` case insensitive. Current tests of poisson/binomial glm with weight fail when specifying 'Poisson' or 'Binomial', because the calculation of `dispersion` and `pValue` checks the value of family retrieved from `getFamily` ``` model.getFamily == Binomial.name || model.getFamily == Poisson.name ``` ## How was this patch tested? Update existing tests for 'Poisson' and 'Binomial'. yanboliang felixcheung imatiach-msft Author: actuaryzhang <[email protected]> Closes apache#16675 from actuaryzhang/family.
…hould case insensitive ## What changes were proposed in this pull request? MLlib ```GeneralizedLinearRegression``` ```family``` and ```link``` should be case insensitive. This is consistent with some other MLlib params such as [```featureSubsetStrategy```](https://github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/tree/treeParams.scala#L415). ## How was this patch tested? Update corresponding tests. Author: Yanbo Liang <[email protected]> Closes apache#16516 from yanboliang/spark-19133.
## What changes were proposed in this pull request? This is a supplement to PR apache#16516 which did not make the value from `getFamily` case insensitive. Current tests of poisson/binomial glm with weight fail when specifying 'Poisson' or 'Binomial', because the calculation of `dispersion` and `pValue` checks the value of family retrieved from `getFamily` ``` model.getFamily == Binomial.name || model.getFamily == Poisson.name ``` ## How was this patch tested? Update existing tests for 'Poisson' and 'Binomial'. yanboliang felixcheung imatiach-msft Author: actuaryzhang <[email protected]> Closes apache#16675 from actuaryzhang/family.
…hould case insensitive ## What changes were proposed in this pull request? MLlib ```GeneralizedLinearRegression``` ```family``` and ```link``` should be case insensitive. This is consistent with some other MLlib params such as [```featureSubsetStrategy```](https://github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/tree/treeParams.scala#L415). ## How was this patch tested? Update corresponding tests. Author: Yanbo Liang <[email protected]> Closes apache#16516 from yanboliang/spark-19133.
## What changes were proposed in this pull request? This is a supplement to PR apache#16516 which did not make the value from `getFamily` case insensitive. Current tests of poisson/binomial glm with weight fail when specifying 'Poisson' or 'Binomial', because the calculation of `dispersion` and `pValue` checks the value of family retrieved from `getFamily` ``` model.getFamily == Binomial.name || model.getFamily == Poisson.name ``` ## How was this patch tested? Update existing tests for 'Poisson' and 'Binomial'. yanboliang felixcheung imatiach-msft Author: actuaryzhang <[email protected]> Closes apache#16675 from actuaryzhang/family.
What changes were proposed in this pull request?
MLlib
GeneralizedLinearRegression
family
andlink
should be case insensitive. This is consistent with some other MLlib params such asfeatureSubsetStrategy
.How was this patch tested?
Update corresponding tests.