-
Notifications
You must be signed in to change notification settings - Fork 397
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
Making model selectors robust to failing models #372
Conversation
Codecov Report
@@ Coverage Diff @@
## master #372 +/- ##
=========================================
+ Coverage 86.77% 86.8% +0.02%
=========================================
Files 336 336
Lines 10922 10928 +6
Branches 335 354 +19
=========================================
+ Hits 9478 9486 +8
+ Misses 1444 1442 -2
Continue to review full report at Codecov.
|
log.info(s"Got metric $metric for model $name trained with ${params(i)}.") | ||
metrics(i) = metric | ||
val paramsMetrics = params.map { p => | ||
Try { |
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 might wanna consider to parallelize by splits AND models, I.e just use futures instead of tries nested inside futures.
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 tried that but I think the copy of the model causes some kind of lock - when I had them both in futures the first test where the model selectors are ran for 30 minutes without ever finishing....
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.
Let’s put a todo there or perhaps Chris S. Can help?
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.
@tovbinm @leahmcguire I was able to replace params
with params.par
and although it worked it was much slower than one would expect. First, does it make sense to fit and eval all models in parallel when there is only one spark context?
BTW do you have jvisualvm
avail in Zulu?
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 can always get visualvm standalone https://visualvm.github.io/download.html
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.
#373 - works fine for me. Though I am not sure about running anything in parallel with spark context as dependency.
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.
Please refer to the implementation of cross validation in Spark (it uses Futures and there is a good reason why).
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.
And that’s why I propose to stay with Futures, but just use them correctly.
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.
@tovbinm If you are referring to https://github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/tuning/CrossValidator.scala then the only difference with my PR is that they are using foldMetricFutures.map(ThreadUtils.awaitResult(_, Duration.Inf))
instead of Future.sequence
therefore can you elaborate more?
|
||
val data = sc.parallelize(rawData).toDF("label", "features") | ||
data.show() |
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.
No show() in tests.
log.warn(s"Model attempted in model selector failed with following issue: \n${e.getMessage}") | ||
None | ||
}} | ||
val summary = SparkThreadUtils.utils.awaitResult(Future.sequence(summaryOfAttempts), maxWait).flatten.toArray |
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.
Perhaps it’s worth to log the maxWait value
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 see any explicit changes to the ModelInsights, so what does it look like there when a model fails. Is it easy to parse out a list of failed model types / settings from ModelInsights if we wanted to add these to downstream metrics?
It looks like the summary
for a failed model is None
, which then disappears when it's flatMap
d over, is that right?
If a model fails it doesnt appear in model insights - if you wanted to know which models you tried and failed you would need to compare the modelselector params to the sequence of evaluated models |
Ok, so for our use case we could still get a list of failed models with that comparison. What about more complicated cases where we have a random search or a data-dependent hyperparameter search? It looks like for random search we'd still have the info stored in the ParamMap in the models argument to ModelSelector so we'd probably do the same thing for a data-dependent hyperparam search too? |
Maybe - I still need to think about how we would implement data dependent hyperparmeter search. I can make it a requirement that the models tried exist somewhere... |
} | ||
|
||
|
||
it should "fail when all models fail" in { |
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.
can you explain in comments or assertions which model fails and why in each case?
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.
maxWait could be tested explicitly with some short duration.
val dataUse = dataOpt.getOrElse(data) | ||
|
||
val theBestEstimator = validator.validate(modelInfo = modelsUse, dataset = dataUse, | ||
label = in1.name, features = in2.name, dag = dag, splitter = splitter |
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.
should we use labelColName
everywhere for consistency?
addressed all the comments - can someone approve? |
} | ||
setInputSchema(dataset.schema).transformSchema(dataset.schema) | ||
require(!dataset.isEmpty, "Dataset cannot be empty") | ||
val data = dataset.select(in1.name, in2.name) |
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.
nit: labelColName?
setInputSchema(dataset.schema).transformSchema(dataset.schema) | ||
require(!dataset.isEmpty, "Dataset cannot be empty") | ||
val data = dataset.select(in1.name, in2.name) | ||
val (BestEstimator(name, estimator, summary), splitterSummary, datasetWithID) = bestEstimator.map{ e => |
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.
nit: whitespace inconsistent, some map {
and map{
val model = estimator.fit(train, p).asInstanceOf[M] | ||
val metric = evaluator.evaluate(model.transform(test, p)) | ||
log.info(s"Got metric $metric for model $name trained with $p.") | ||
Some(p -> metric) |
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.
nit: we seem to prefer Option(blah)
many places
metrics(i) = metric | ||
|
||
val paramsMetricsF = params.seq.map { p => | ||
val f = Future { |
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.
nit: we can get away without defining f
since we don't use it much
Future {
//
}.recover {
//
}
Some(p -> metric) | ||
} | ||
f.recover({ case e: Throwable => | ||
log.warn(s"Model $name attempted in model selector with failed with following issue: \n${e.getMessage}") |
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.
let us add the raw e
as a second arg to warn
to get a stack trace if logger is configured this way
|
||
val summaryOfAttempts = summaryFuts.map { f => f.map(Option(_)).recover { | ||
case e: Throwable => | ||
log.warn(s"Model attempted in model selector failed with following issue: \n${e.getMessage}") |
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.
prefer adding raw e as the second arg to logger
parallelism = 4, | ||
seed = 10L, |
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.
the args are not in particular order, so can we put it back to the original spot to eliminate this diff
parallelism = 4, | ||
seed = 10L, |
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.
let us get rid of that non-diff
summary2.selectedModelInfo.get.validationResults | ||
).forall{ case (v1, v2) => | ||
println(v1.metricValues.asInstanceOf[SingleMetric].value, v2.metricValues.asInstanceOf[SingleMetric].value) | ||
v1.metricValues.asInstanceOf[SingleMetric].value < v2.metricValues.asInstanceOf[SingleMetric].value |
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.
nit: indentation
.setInput(label, features) | ||
|
||
|
||
intercept[Exception](testEstimator.fit(data)) |
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.
let us use more specific exceptions in intercepts. e.g. TimeoutException here
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.
LGTM
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.
LGTM
This reverts commit 496174c.
Bug fixes: - Ensure correct metrics despite model failures on some CV folds [#404](#404) - Fix flaky `ModelInsight` tests [#395](#395) - Avoid creating `SparseVector`s for LOCO [#377](#377) New features / updates: - Model combiner [#385](#399) - Added new sample for HousingPrices [#365](#365) - Test to verify that custom metrics appear in model insight metrics [#387](#387) - Add `FeatureDistribution` to `SerializationFormat`s [#383](#383) - Add metadata to `OpStandadrdScaler` to allow for descaling [#378](#378) - Improve json serde error in `evalMetFromJson` [#380](#380) - Track mean & standard deviation as metrics for numeric features and for text length of text features [#354](#354) - Making model selectors robust to failing models [#372](#372) - Use compact and compressed model json by default [#375](#375) - Descale feature contribution for Linear Regression & Logistic Regression [#345](#345) Dependency updates: - Update tika version [#382](#382)
Thanks for the contribution! Before we can merge this, we need @wsuchy to sign the Salesforce.com Contributor License Agreement. |
Thanks for the contribution! Unfortunately we can't verify the commit author(s): leahmcguire <l***@s***.com> Leah McGuire <l***@s***.com>. One possible solution is to add that email to your GitHub account. Alternatively you can change your commits to another email and force push the change. After getting your commits associated with your GitHub account, refresh the status of this Pull Request. |
Related issues
#370
Describe the proposed solution
Make model selector no fail when a portion of attempted models don't work. Also exposed parameter to limit max time will wait for modeling to finish in model selector
Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
Additional context
Add any other context about the changes here.