-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-3261] [MLLIB] KMeans clusterer can return duplicate cluster centers #15450
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
Closed
+85
−65
Closed
Changes from 3 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
012979e
Return potentially fewer than k cluster centers in cases where k dist…
srowen 85c9857
Deduplicate result of random selection without replacement to match b…
srowen ebebcb9
Add more tests
srowen 793e4d5
Use only distinct outputs of initial k-means || selection; update sca…
srowen d1004d9
More test changes; fix doc problems
srowen 79c84ad
Further refine tests; remove deprecated runs param usages
srowen f870fe9
Remove last refs to 'runs'
srowen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,18 +64,34 @@ class KMeansSuite extends SparkFunSuite with MLlibTestSparkContext { | |
| assert(model.clusterCenters.head ~== center absTol 1E-5) | ||
| } | ||
|
|
||
| test("no distinct points") { | ||
| test("fewer distinct points than clusters") { | ||
| val data = sc.parallelize( | ||
| Array( | ||
| Vectors.dense(1.0, 2.0, 3.0), | ||
| Vectors.dense(1.0, 2.0, 3.0), | ||
| Vectors.dense(1.0, 2.0, 3.0)), | ||
| 2) | ||
| val center = Vectors.dense(1.0, 2.0, 3.0) | ||
|
|
||
| // Make sure code runs. | ||
| var model = KMeans.train(data, k = 2, maxIterations = 1) | ||
| assert(model.clusterCenters.size === 2) | ||
| var model = KMeans.train(data, k = 2, maxIterations = 1, initializationMode = "random") | ||
| assert(model.clusterCenters.length === 1) | ||
|
|
||
| model = KMeans.train(data, k = 2, maxIterations = 1, initializationMode = "k-means||") | ||
| assert(model.clusterCenters.length === 1) | ||
| } | ||
|
|
||
|
|
||
| test("fewer clusters than points") { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer to remove these two tests since we've added a more thorough test below. We can check the "random" init method in that test as well, then we can eliminate these two. |
||
| val data = sc.parallelize( | ||
| Array( | ||
| Vectors.dense(1.0, 2.0, 3.0), | ||
| Vectors.dense(1.0, 3.0, 4.0)), | ||
| 2) | ||
|
|
||
| var model = KMeans.train(data, k = 1, maxIterations = 1, initializationMode = "random") | ||
| assert(model.clusterCenters.length === 1) | ||
|
|
||
| model = KMeans.train(data, k = 1, maxIterations = 1, initializationMode = "k-means||") | ||
| assert(model.clusterCenters.length === 1) | ||
| } | ||
|
|
||
| test("more clusters than points") { | ||
|
|
@@ -85,9 +101,11 @@ class KMeansSuite extends SparkFunSuite with MLlibTestSparkContext { | |
| Vectors.dense(1.0, 3.0, 4.0)), | ||
| 2) | ||
|
|
||
| // Make sure code runs. | ||
| var model = KMeans.train(data, k = 3, maxIterations = 1) | ||
| assert(model.clusterCenters.size === 3) | ||
| var model = KMeans.train(data, k = 3, maxIterations = 1, initializationMode = "random") | ||
| assert(model.clusterCenters.length === 2) | ||
|
|
||
| model = KMeans.train(data, k = 3, maxIterations = 1, initializationMode = "k-means||") | ||
| assert(model.clusterCenters.length === 2) | ||
| } | ||
|
|
||
| test("deterministic initialization") { | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
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.
There's nothing that enforces these centers to be distinct. The following test(s) fail (IMO these are much more thorough than the existing tests):
(BTW I had to modify
initKMeansParallelto be non-private for that test)I guess we can call distinct on the centers before this if statement?
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.
That's right, although it's not a goal to guarantee distinct centers, though that's a nice to have where possible. I think my goal is improving the straightforward cases, and, maintaining as much consistency as possible. I agree with adding a test like this and adding a call to
.distinct. Might as well take this to a logical conclusion.It also raises the interesting question: if you have >= k distinct points, and happen to pick < k distinct centroids, should you go back and replenish the set of centroids? I am punting on that right now but it's a legitimate point. It's quite a corner case though.