K means clustering
Understand cluster count, seed, iteration cap and absolute convergence tolerance, inspect centers and compare partitions.
Released in Aner 0.0.5Aner 0.0.5 or neweraner.ml provides an immutable KMeansModel fitted by seeded Lloyd iteration. It groups numerical observations around centers; class labels are not fitting inputs. The resulting cluster IDs are center indices, not class names. Return to the model reference or aner.ml overview to choose a workflow.
import aner.ml
let model = ml.fit_kmeans(features: [[0.0], [2.0], [10.0], [12.0]],
clusters: 2, max_iterations: 100, tolerance: 0.0, seed: 42)
let predicted = model.predict(input: [[1.0], [11.0]])Import aner.tensor to inspect returned Tensors and aner.metrics for an adjusted Rand comparison with a reference partition. There is no public unfitted KMeansModel constructor.
Every fitting parameter
ml.fit_kmeans(features: Tensor, clusters: Int64, max_iterations: Int64,
tolerance: Float64, seed: Int64) -> KMeansModelAll five parameters are required. No value is filled in implicitly.
| Parameter | Type, accepted range and units | Meaning and effect |
|---|---|---|
features | Finite numerical Tensor; represent observations as an N × D matrix with positive dimensions. | Euclidean coordinates to cluster. Feature ordering, units and scale affect assignments and centers. The target/class column must not be included merely to improve agreement with known labels. |
clusters | Int64 in [1, N]. | Requested center count k. Returned IDs range from 0 to k-1. Increasing it changes the optimization problem; it does not guarantee k occupied groups. |
max_iterations | Int64 in [1, 1000]. | Maximum number of Lloyd assignment/mean updates. It is a cap, not a requested convergence precision. The fitting work budget is checked using this cap before iteration begins. |
tolerance | Finite Float64 ≥ 0, in the supplied feature coordinate units. | Absolute upper bound on the largest Euclidean movement of any center that permits stopping. Larger tolerances can stop earlier. 0.0 requires zero measured movement or unchanged assignments; it does not remove the iteration cap. |
seed | Signed Int64; exact integer retained in the experiment source. | Controls the selected initial fitting row indices. The same seed, feature values and row order replay that initialization. It does not make arbitrary numerical backends or source orders equivalent. |
The public name is clusters, even though algorithm descriptions often call this number k. It is distinct from k in KNN. max_iterations counts completed updates; tolerance is absolute center movement, rather than relative loss or inertia improvement. A standardized feature matrix makes those coordinate units standardized units.
Initialization, updates and stopping
Initialization selects distinct fitting row indices using SplitMix64 and an unbiased partial Fisher and Yates selection. Signed seeds use their UInt64 bit patterns. Distinct row indices may contain the same coordinates. This implementation performs one seeded run; it does not add K means++ or automatic restarts.
Each update assigns every row to its nearest center, then replaces each occupied center by its assigned row mean. A distance tie chooses the lowest center index. An empty cluster retains its previous center. Fitting stops when assignments are unchanged or the largest center movement is at most tolerance. It also stops at max_iterations; reaching that cap without satisfying a stopping test gives converged() == false.
After stopping, fitted assignments and inertia are recomputed against the returned centers. The reported state therefore remains internally consistent when the iteration cap is reached. There is no later center update during prediction.
Prediction and every readout
All arguments shown are required. model is a fitted KMeansModel; input is a finite Q × D query matrix with the same feature count and representation as fitting.
| Qualified signature | Returned value and interpretation |
|---|---|
ml.predict(model: KMeansModel, input: Tensor) | Q × 1 nearest center indices, each in [0, clusters-1]. |
ml.centers(model: KMeansModel) | clusters × D center coordinates in fitted feature column order. |
ml.labels(model: KMeansModel) | N × 1 final assignments for the fitting rows in their supplied order. |
ml.inertia(model: KMeansModel) | Float64 sum of squared Euclidean distances from fitting rows to their returned assigned centers. |
ml.iterations(model: KMeansModel) | Int64 number of completed Lloyd updates. |
ml.converged(model: KMeansModel) | Bool; a documented stopping test was satisfied on or before the last allowed update. |
Receiver forms omit model: model.predict(input: queries), model.centers(), model.labels(), model.inertia(), model.iterations() and model.converged().
Fitted state is immutable. Model copies share it; returned Tensors are independent detached CPU Float64 values. Inspect through the listed readouts; direct model printing and equality are not defined. The algorithm calculates on the CPU. Float32 input is read in its existing stored precision, and accelerator resident inputs can require host readback; neither supplies a native GPU K means implementation. Centers do not retain feature names, physical units, Dataset roles or original row IDs as a fitted schema.
Complete inspectable example
Download the reference example. It supplies all fitting controls, inspects every readout, repeats the same seed and demonstrates an empty cluster case. It uses only small synthetic values and writes no files.
aner check examples/kmeans_reference.aner
aner run examples/kmeans_reference.anerExpected output:
2
1
true
true
4.0
1.0
true
true
true
0.0
0.0
0.0The four values 0, 2, 10, 12 form centers at 1 and 11, up to center index order, with total inertia 4.0. An adjusted Rand score of 1.0 shows that the fitted partition matches reference groups despite their labels 10 and 20. For two identical rows at 5 and two requested centers, all rows select center index zero; the second center can remain empty and inertia is zero.
Scaling, validation and interpretation
For a held out experiment, reserve data before fitting either scaling or centers. Fit a standardizer only on fitting rows and reuse its transform for validation/test queries. The model does not own or automatically run that standardizer. The existing held out Iris example demonstrates this complete path.
In an exploratory population clustering, it can be deliberate to fit on the whole declared population. Label that scope explicitly. It is a different design from measuring a model on reserved observations. Tensor extraction discards Dataset role/membership enforcement; the classical numerical API cannot detect accidental test row fitting afterward.
Cluster IDs are arbitrary center names. metrics.accuracy(cluster_ids, class_ids) does not measure class independent clustering quality. Use metrics.adjusted_rand_index when comparing two partitions of the same observations; it is invariant to numeric group renaming. It is not a percentage accuracy and can be negative. Inertia has squared coordinate units, changes with scaling and center count, and alone does not establish that a clustering is useful.
If studying several seeds, cluster counts or preprocessing settings, declare the comparisons before using final test observations. Keep every run's feature preparation, source order, seed, centers, assignments, inertia, convergence flag and iteration count. Distinct seeds do not guarantee distinct final centers or statistically independent solutions.
Limits, errors and computational cost
All Tensors have at most 1,000,000 elements. Fitting checks (max_iterations + 1) × N × clusters × D ≤ 50,000,000 distance coordinate evaluations before allocation/iteration. This conservative cap includes final assignment. Query prediction checks Q × clusters × D ≤ 50,000,000. Numeric overflow, an invalid cluster/iteration count, negative/nonfinite tolerance and mismatched query width fail with an ML diagnostic (R2301). Signature/import problems fail during checking.
For I completed updates, distance/update work is O((I+1)N k D), including final labels/inertia. Center mean updates use magnitude scaling and compensated sums. Retained state is O(kD + N) for centers and fitted assignments; working allocation adds O(N + kD) beyond the input Tensor. Prediction takes O(QkD) time with O(Q) output. Reading centers or labels copies O(kD) or O(N) values. These are allocation/operation bounds, not a total process memory quota.
There is no K means++ choice, configurable metric, automatic feature scaling, automatic restart count, silhouette score API, mini batch K means, partial fit or K means serialization. Neural TrainingConfig, dropout, SGD/Adam and Dense checkpoint APIs do not configure or serialize this model.