Class KMeans
fit entry point
runs the whole algorithm and returns a KMeans.Result describing the chosen partitioning.
The algorithm assumes the external metric is ordinary L2, but the k-means core optimizes the standard squared-L2 objective internally:
- assignment uses squared distance,
- centroid update uses arithmetic mean,
- k-means++ initialization weights candidate centroids by squared distance to the nearest already-chosen centroid, and
- run selection compares the per-run k-means objective, preferring the runs whose cluster sizes pass the caller's size predicate.
Optional soft size balancing biases the assignment steps of the Lloyd iterations by the running projected
cluster size (see KMeans.SizePenalty), which steers where the centroids settle; set relativeLambda == 0 or
sizePenalty == null to disable.
After the main Lloyd loop of a run exits — whether via convergence (changed == 0) or by hitting
maxIterations — fit(java.util.SplittableRandom, com.apple.foundationdb.linear.DistanceEstimator, com.apple.foundationdb.util.Lens<V, com.apple.foundationdb.linear.RealVector>, com.apple.foundationdb.util.Lens<C, com.apple.foundationdb.linear.RealVector>, java.util.List<V>, int, int, int, double, com.apple.foundationdb.kmeans.KMeans.SizePenalty, java.util.function.Predicate<int[]>) runs one final assignment pass against the latest centroids by distance
alone, without any size penalty. This guarantees that the returned KMeans.Result.assignment(),
KMeans.Result.clusterSizes(), and KMeans.Result.distances() describe the nearest-centroid partitioning of
KMeans.Result.clusterCentroids(), regardless of how the loop terminated and of whether balancing was active.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final recordOutcome of afitcall: the chosen centroids, the per-vector cluster assignment, the per-cluster sizes, the per-vector objective contributions, and the total objective.static interfaceSoft size-balancing penalty hook. -
Method Summary
Modifier and TypeMethodDescriptionstatic <V,C> KMeans.Result <C> fit(SplittableRandom random, DistanceEstimator distanceEstimator, Lens<V, RealVector> vectorLens, Lens<C, RealVector> centroidLens, List<V> vectors, int k, int maxIterations, int numRuns, double relativeLambda, KMeans.SizePenalty sizePenalty, Predicate<int[]> sizePredicate) Fitskclusters using Lloyd-style squared-L2 k-means with k-means++ initialization, makingnumRunsindependent runs and returning one of them.static KMeans.SizePenaltyReturns aKMeans.SizePenaltythat penalizes only overflow above the target cluster size, quadratically:(max(0, projected - target))^2 / max(1, target).
-
Method Details
-
overflowQuadraticPenalty
Returns aKMeans.SizePenaltythat penalizes only overflow above the target cluster size, quadratically:(max(0, projected - target))^2 / max(1, target). A reasonable default for soft size balancing.- Returns:
- a non-null overflow-quadratic penalty function
-
fit
public static <V,C> KMeans.Result<C> fit(@Nonnull SplittableRandom random, @Nonnull DistanceEstimator distanceEstimator, @Nonnull Lens<V, RealVector> vectorLens, @Nonnull Lens<C, RealVector> centroidLens, @Nonnull List<V> vectors, int k, int maxIterations, int numRuns, double relativeLambda, @Nullable KMeans.SizePenalty sizePenalty, @Nullable Predicate<int[]> sizePredicate) Fitskclusters using Lloyd-style squared-L2 k-means with k-means++ initialization, makingnumRunsindependent runs and returning one of them.Each run makes at most
maxIterationsLloyd iterations of (assignment, centroid update). Empty clusters detected during the centroid update are reseeded with the data point currently farthest from any centroid. After the main loop, a final assignment pass assigns every vector to its nearest centroid by distance alone, so the run's assignment, cluster sizes, distances and objective describe the nearest-centroid partitioning of its centroids.Run selection is two-tier. If the cluster sizes of at least one run pass
sizePredicate,fitreturns the passing run with the lowest objective; if no run passes, it returns the run with the lowest objective overall, so that the caller still gets a concrete partitioning to judge and report. Equivalently, the runs are ordered by passing before failing, then by lower objective, and the first one is returned. The predicate is thus a preference, not a guarantee: a caller that needs the sizes to pass checks the returnedKMeans.Result.clusterSizes()itself, which is also how it recognizes the fallback. WithsizePredicate == nullevery run passes, and the run with the lowest objective is returned.The size-balancing strength is given relative to the natural scale of the base objective of
vectors: during the iterations, the penalty is weighted bylambda = relativeLambda * s, wheresis the per-vector objective of the closed-form single-cluster fit (their mean squared L2 distance to their mean in Euclidean mode, their mean cosine distance to their normalized mean in cosine mode). A relative strength is thus measured in units of the data's own spread. That makes it exactly invariant under scaling the vectors, which scales both terms of the assignment score alike, and comparable in practice, though not identical, across metrics and numbers of dimensions. When all vectors are identical (in cosine mode: parallel),s == 0, and so is the weight: balancing is off. Computingsconsumes no randomness, and is skipped fork == 1, forrelativeLambda == 0and forsizePenalty == null.The penalty acts only in the assignment steps of the iterations, where it steers the centroids: the final pass of each run ignores it, and so does the objective the runs are ranked by. With the penalty active (
lambda > 0) the assignment step of an iteration is order-dependent (each vector's size-balance penalty is evaluated against the running projected sizes of all clusters assigned before it). To stop a fixed input ordering from biasing the centroids,fitthen reshuffles the assignment order via Fisher–Yates at the top of every iteration. Otherwise, and in the final pass of every run, the assignment is order-independent and shuffling is skipped as wasted work. As the returned partitioning is the nearest-centroid one, a cluster can in rare cases come out empty.- Type Parameters:
V- caller's input vector representationC- caller's centroid representation- Parameters:
random- the random source; consumed by the k-means++ initialization of every run and by the per-iteration shuffling (when the penalty is active)distanceEstimator- the distance estimator. Its metric must be eitherEUCLIDEAN_METRICorCOSINE_METRIC; other metrics are rejected with anUnsupportedOperationExceptionvectorLens- lens that extracts aRealVectorfrom each input elementcentroidLens- lens that wraps aRealVectoras the caller's centroid typevectors- the input points to cluster; must contain at leastkelementsk- number of clusters; must be at least 1.k == 1is handled in closed form (the single centroid is the mean of all vectors), bypassing iteration, the runs andsizePredicatemaxIterations- maximum Lloyd iterations per run; must be at least 1numRuns- total number of runs, each from its own k-means++ initialization; must be at least 1relativeLambda- strength of the size-balancing bias added to the assignment score of the iterations, relative tos(see above); must be finite and non-negative.0disables balancingsizePenalty- penalty function applied to projected cluster sizes during the assignment steps of the iterations;nulldisables balancing. Ignored whenrelativeLambda == 0sizePredicate- preference over the final cluster sizes of a run (see above), tested on a copy of each run's sizes;nulllets every run pass- Returns:
- the passing run with the lowest objective or, if no run passes
sizePredicate, the run with the lowest objective
-