Uh oh!
There was an error while loading. Please reload this page.
[SPARK-10780][ML] Add an initial model to kmeans - #11119
Conversation
SparkQA
commented
Feb 8, 2016
Test build #50935 has finished for PR 11119 at commit
|
holdenk
commented
Feb 8, 2016
re the first question - I don't think this necessarily needs to be a code generated param (although if we do end up having more shared params with templated types we should definitely do codegen). For now maybe just a hand written HasInitialModel seems fine (although I'd put it in a separate file rather than tacking it on the end of the generated code) - but thats just my personal thoughts. Maybe @dbtsai can chime in too? |
| /** @group setParam */ | ||
| @Since("2.0.0") | ||
| def setInitialModel(value: Model[_]): this.type = { |
There was a problem hiding this comment.
If this is something we intended to have be a general function, should probably go in the HasInitialModel trait.
There was a problem hiding this comment.
Sure, I'll try to make the setter to HasInitialModel
There was a problem hiding this comment.
This can go into the trait, but the pattern matching will be different tho. Are we just overwriting it here?
There was a problem hiding this comment.
We can leave it here for now.
dbtsai
commented
Feb 9, 2016
Agree, for code-gen, if we want to do it in this way, we would rather put them in a separate place. But will be nice to extend the code-gen framework so it can use one codebase to handle generic type. +@jkbradley@mengxr BTW, we still need to run the separate |
| value match { | ||
| case m: KMeansModel => set(initialModel, m) | ||
| case other => | ||
| logInfo(s"KMeansModel required but ${other.getClass.getSimpleName} found.") |
SparkQA
commented
Feb 11, 2016
Test build #51090 has finished for PR 11119 at commit
|
dbtsai
commented
Feb 12, 2016
@yinxusen I'll be away for Spark summit east. Gonna work on this again when I'm back. Thanks. |
yinxusen
commented
Feb 12, 2016
Never mind, take your time. 2016年2月12日星期五,DB Tsai notifications@github.com 写道:
CheersXusen Yin (尹绪森) |
yinxusen
commented
Feb 22, 2016
Ping @dbtsai Coming back? :) |
dbtsai
commented
Feb 23, 2016
Yes, but busy on work. :( Will soon start it in couple days. |
yinxusen
commented
Mar 7, 2016
Ping @dbtsai |
SparkQA
commented
Mar 7, 2016
Test build #52578 has finished for PR 11119 at commit
|
yinxusen
commented
Oct 20, 2016
sethah
commented
Oct 21, 2016
Related thought: if the model holds a pointer to its initialModel, then it will be serialized and shipped along with the model at prediction time. This will be inefficient for large models and even if we cut the lineage, we needlessly double the size of the closure. It seems best not to have the initialModel in the model at all, but then it is an edge case for params, and it's still nice to know how the model was initialized at fit time. Thoughts? |
yinxusen
commented
Oct 23, 2016
How about the following:
|
jkbradley
left a comment
There was a problem hiding this comment.
There are quite a few algorithms where the Model does not contain all of the Params of its Estimator. This has been inconsistent, but I do think it's fine for the KMeansModel not to store the initialModel (except through its parent). Users can identify the initialization method of the model by looking at Model.parent.initialModel.
As far as serializing and shipping the initialModel accidentally, I don't think that has to be an issue. Currently, predictUDF in transform() is likely capturing the whole Model class, but it doesn't have to. We could change it to:
val tmpParent: MLlibKMeansModel = parentModel
val predictUDF = udf((vector: Vector) => tmpParent.predict(features)(vector))
This is an issue throughout spark.ml because of the Predictor abstraction...which should probably be corrected as we add more support for initial models.
As far as saving and loading Models, I agree with your previous statements about not needing to save the initialModel in general. I do want us to save/load Model.parent eventually, at which time we could revisit this issue. But not storing initialModel as a Model Param would avoid this issue.
Also, your discussion have been much longer than this, so it would be great to document decision in a public design doc which others can refer to when adding initialModel to other algorithms.
| // Check that the number of clusters are equal | ||
| val kOfInitialModel = $(initialModel).parentModel.clusterCenters.length | ||
| require(kOfInitialModel == $(k), |
There was a problem hiding this comment.
I'd recommend that this log a warning instead of causing a failure. If we use CrossValidator to select amongst initial models, then
sethah
commented
Oct 24, 2016
@jkbradley Thanks for your thoughts. I agree it's a good idea to change the KMeans prediction function to not use the entire model in its closure, but that we need a more thorough solution when we generalize this to predictors. Would you mind pointing me to an example of an algorithm which only copies some, but not all, of the estimator params?
Sure, but will they? How will they know that valkm=newKMeans().setInitialModel(...)
km.getInitMode
>"k-means||"It's especially misleading to have a model that says valkm=newKMeans().setInitialModel(...)
km.getInitMode
>"initialModel"valmodel= km.fit(df)
model.getInitMode
>"initialModel"That solves the problem of users having to know to access the initialization modes via its parent, and having conflicting |
jkbradley
commented
Oct 24, 2016
ALS is a good example: [https://github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/recommendation/ALS.scala#L98]
I agree it's misleading to have mismatched Params initialModel and initMode, especially if Model.initialModel does not exist. I'd say this is an ideal solution:
|
sethah
commented
Oct 24, 2016
Ok, unless anyone has strong objections, it seems our plan moving forward with this PR should be:
Let me know if I have missed something. @yinxusen Does this seam reasonable? |
jkbradley
commented
Oct 25, 2016
On second thought, for this one, it could be good to have it work as long as initialModel is already set. Otherwise, that plan matches what I have in mind. Thanks! |
sethah
commented
Nov 3, 2016
@yinxusen Status update? |
yinxusen
commented
Nov 7, 2016
@sethah Sorry, I got stuck in other things. I'll update this PR tonight. |
SparkQA
commented
Nov 8, 2016
Test build #68325 has finished for PR 11119 at commit
|
sethah
commented
Nov 8, 2016
This is probably going to miss 2.1 since we are officially in QA now, just as an fyi. |
SparkQA
commented
Nov 9, 2016
Test build #68368 has finished for PR 11119 at commit
|
| @Since("0.8.0") | ||
| val K_MEANS_PARALLEL = "k-means||" | ||
| @Since("2.1.0") | ||
| val K_MEANS_INITIAL_MODEL = "initialModel" |
There was a problem hiding this comment.
Does it need to be public? This only serves a purpose when used with ML I think.
| logWarning(s"initialModel is set, so initMode will be ignored. Clear initialModel first.") | ||
| } | ||
| if (value == MLlibKMeans.K_MEANS_INITIAL_MODEL) { | ||
| logWarning(s"initMode of $value is not supported here, please use setInitialModel.") |
There was a problem hiding this comment.
From the discussion, I think we decided to throw an error for setInitMode("initialModel") if initialModel wasn't already set. If initialModel has been set, then we'd just update the initMode as normal.
| def setInitMode(value: String): this.type = set(initMode, value) | ||
| def setInitMode(value: String): this.type = { | ||
| if (isSet(initialModel)) { | ||
| logWarning(s"initialModel is set, so initMode will be ignored. Clear initialModel first.") |
There was a problem hiding this comment.
We say it will be ignored, but then still set it below.
| override def transform(dataset: Dataset[_]): DataFrame = { | ||
| transformSchema(dataset.schema, logging = true) | ||
| val predictUDF = udf((vector: Vector) => predict(vector)) | ||
| val tmpParent: MLlibKMeansModel = parentModel |
There was a problem hiding this comment.
maybe a comment would be useful? // avoid encapsulating the entire model in the closure
| if ($(k) != kOfInitialModel) { | ||
| val previousK = $(k) | ||
| set(k, kOfInitialModel) | ||
| logWarning(s"Param K is set to $kOfInitialModel by the initialModel." + |
There was a problem hiding this comment.
nit: Maybe s"Param k was changed from $previousK to $kOfInitialModel to match the initialModel"
| assert(wrongDimModelThrown.getMessage.contains("mismatched dimension")) | ||
| } | ||
| test("Infer K from an initial model") { |
There was a problem hiding this comment.
So, the behavior is getting a bit confusing because we now have three params which are intertwined. For that reason, we should be very thorough on the tests. With my understanding of the behavior we decided on, the following tests should all pass. Can you tell me if it looks right to you?:
test("initialModel params") {
valinitialK=3valinitialEstimator=newKMeans()
.setK(initialK)
valinitialModel= initialEstimator.fit(dataset)
valkm=newKMeans()
.setK(initialK +1)
.setInitialModel(initialModel)
// intialModel sets k and init mode
assert(km.getInitMode ===MLlibKMeans.K_MEANS_INITIAL_MODEL)
assert(km.getK === initialK)
assert(km.getInitialModel.getK === initialK)
// setting k is ignored
km.setK(initialK +1)
assert(km.getK === initialK)
// this should work since we already set initialModel
km.setInitMode(MLlibKMeans.K_MEANS_INITIAL_MODEL)
// this is ignored because initialModel is set
km.setInitMode(MLlibKMeans.RANDOM)
assert(km.getInitMode ===MLlibKMeans.K_MEANS_INITIAL_MODEL)
km.clear(km.initialModel)
// kmeans now accepts init mode
km.setInitMode(MLlibKMeans.RANDOM)
assert(km.getInitMode ===MLlibKMeans.RANDOM)
// kmeans should throw an error since we shouldn't be allowed to set init mode to "initialModel"
intercept[IllegalArgumentException] {
km.setInitMode(MLlibKMeans.K_MEANS_INITIAL_MODEL)
}
}| * Param for KMeansModel to use for warm start. | ||
| * Whenever initialModel is set: | ||
| * 1. the initialModel k will override the param k; | ||
| * 2. the param initMode is set to initialModel and manually set is ignored; |
There was a problem hiding this comment.
- the param initMode is set to "initialModel" and manually setting initMode will be ignored
nit: Let's just remove the punctuation from the numbered list
| * Params for KMeans | ||
| */ | ||
| private[clustering] trait KMeansInitialModelParams extends HasInitialModel[KMeansModel] { |
There was a problem hiding this comment.
If we follow the convention in ALS, then we should have KMeansModelParams and KMeansParams extends KMeansModelParams with .... I think it would be good to do the same here.
| * 3. other params are untouched. | ||
| * @group param | ||
| */ | ||
| final val initialModel: Param[KMeansModel] = |
sethah
commented
Nov 18, 2016
@yinxusen I took a look at the updates. Will you be able to create the design doc that Joseph mentioned? |
sethah
commented
Dec 7, 2016
ping? |
sethah
commented
Jan 10, 2017
@yinxusen Do you think you'll have time to work on this? |
sethah
commented
Feb 1, 2017
ping! I could take this over if needed :) |
SparkQA
commented
Mar 22, 2017
Test build #75004 has finished for PR 11119 at commit
|
HyukjinKwon
commented
May 4, 2017
Do you guys mind if I propose to close this PR? |
## What changes were proposed in this pull request? This PR proposes to close a stale PR, several PRs suggested to be closed by a committer and obviously inappropriate PRs. Closesapache#11119Closesapache#17853Closesapache#17732Closesapache#17456Closesapache#17410Closesapache#17314Closesapache#17362Closesapache#17542 ## How was this patch tested? N/A Author: hyukjinkwon <gurwls223@gmail.com> Closesapache#17855 from HyukjinKwon/close-pr.
https://issues.apache.org/jira/browse/SPARK-10780
This PR aims to add warm-start to KMeans algorithm.