Browse documentation
aner.ml · Reference

K nearest neighbor classification

Configure every KNN input, inspect votes and neighbor identities, understand exact tie rules and fit scaling on training data.

Released in Aner 0.0.5Aner 0.0.5 or newer

KNN is a fitted classification technique in aner.ml. It stores the supplied feature rows and class IDs, then predicts by a uniform vote among the nearest rows. It has no neural layers, gradient updates, epochs or optimizer. Start from the model reference to compare it with other techniques, or return to the aner.ml overview.

aner
import aner.ml

let model = ml.fit_knn(
    features: [[0.0], [2.0], [10.0], [12.0]],
    labels: [[10.0], [10.0], [30.0], [30.0]],
    k: 3)
let predicted = model.predict(input: [[1.0], [11.0]])

Import aner.tensor to inspect returned values and aner.metrics to evaluate predicted IDs. The native fitted value type is KNNClassifier. It is created by ml.fit_knn; there is no public unfitted constructor.

Every fitting parameter

text
ml.fit_knn(features: Tensor, labels: Tensor, k: Int64) -> KNNClassifier

All three arguments are required. There are no omitted defaults.

ParameterShape, type and accepted valuesMeaning and effect
featuresFinite numerical Tensor; represent samples as an N × D matrix with positive dimensions.Each row is one fitted observation and each column one feature. Euclidean distance uses these values directly, so feature order, units and scaling determine what “near” means. The model retains an independent detached copy.
labelsN × 1 Tensor; each stored value is an exact integer in [0, 9007199254740991].Class ID for the corresponding feature row. IDs can be noncontiguous, such as 10, 20, 40. Numerical order only resolves vote ties; it does not represent distance between classes.
kInt64 in [1, N].Number of neighboring fitting rows that vote. Small k responds to local observations; larger k averages more of the fitted population and can suppress small local groups. Every neighbor has the same vote weight.

KNN does not choose k or scale features automatically. It does not use random sampling, so there is no KNN seed parameter. A dataset partition may still need a seed. Exact distance ties depend on fitting row order, which should be retained with the experiment.

Prediction and every readout

Here Q is the number of query rows and C the number of distinct fitted classes. All query matrices have shape Q × D, use the fitted column order and contain finite values. The argument name is input, including receiver calls such as model.predict(input: queries).

Qualified signatureReturned value and interpretation
ml.predict(model: KNNClassifier, input: Tensor)Q × 1 predicted class IDs, preserving the original fitted label values.
ml.predict_proba(model: KNNClassifier, input: Tensor)Q × C neighbor vote fractions. Each row sums to one up to rounding; columns follow ml.classes(model).
ml.classes(model: KNNClassifier)C × 1 sorted unique fitted class IDs. Use this mapping when interpreting probability columns.
ml.neighbor_indices(model: KNNClassifier, input: Tensor)Q × k zero based indices into the matrix originally passed as features, nearest first.
ml.neighbor_distances(model: KNNClassifier, input: Tensor)Q × k ordinary Euclidean distances in the same order. These are not squared distances.

model must be a fitted KNN value; input must have the same feature count as fitting. These are required arguments. Receiver forms omit only model: model.predict_proba(input: queries), model.classes(), model.neighbor_indices(input: queries) and model.neighbor_distances(input: queries).

The fitted state is immutable. Copies share that immutable state; queries do not update it. Use the readouts above to inspect it; direct model printing and equality are not defined. Results are detached CPU Float64 Tensors. Classical KNN calculations run on the CPU. Float32 input values can be read, but a later Float64 result cannot restore precision already lost in those values. Reading accelerator resident inputs can synchronize and download them; it does not turn KNN into a GPU algorithm.

Distance, tie and probability rules

For one query, the implementation calculates Euclidean distance to every fitted row. Neighbors are ordered first by distance and then by fitted row index. If two selected classes receive the same number of votes, the smallest class ID wins. Repeated coordinates remain distinct fitted rows and each selected row contributes a vote.

Probability column zero means the first entry of model.classes(), which may be class 10 rather than class 0. tensor.argmax(probabilities) returns a column index; it must be mapped through the fitted class list before treating it as an external class ID. Calling model.predict performs that mapping. Vote fractions describe the selected neighbors; they are not guaranteed calibrated confidence estimates.

Querying a fitted row does not exclude that row from its own neighbors. Training accuracy is therefore not leave one out evaluation, particularly at k = 1.

Complete inspectable example

Download the deterministic reference example. It declares every fitting variable and inspects predictions, class mapping, vote fractions, equal distance ordering and a tied vote. It has no external files or output writes.

sh
aner check examples/knn_reference.aner
aner run examples/knn_reference.aner

Expected output:

text
4
10.0
10.0
30.0
0.6666666666666666
0.3333333333333333
0.0
2.0
1.0
1.0
1.0
10.0

The first query has neighbors at fitting indices 0, 2, 1: the first two are equally distant and retain row index order. Labels 10, 20, 10 give class 10 two of the three votes. The final two neighbor fixture resolves a tied vote to class 10, although its first fitting row has class 20.

Training only scaling and validation

For a Dataset experiment, split before fitting preprocessing. dataset.fit_standardizer(training) fits population means and scales only on authorized training rows. dataset.transform reuses those values; the KNN model does not own or automatically apply the standardizer.

aner
import aner.dataset
import aner.ml
import aner.metrics

let parts = dataset.iris().partition(validation: 0.2, test: 0.2, seed: 2026)
let training = parts.train()
let validation = parts.validation()
let scaler = dataset.fit_standardizer(training)
let x_train = dataset.transform(scaler, dataset.features(training))
let x_validation = dataset.transform(scaler, dataset.features(validation))
let model = ml.fit_knn(features: x_train, labels: dataset.targets(training), k: 5)
print(metrics.accuracy(model.predict(input: x_validation), dataset.targets(validation)))

Use a declared validation comparison to choose k and feature preparation, then evaluate a test view that did not influence those decisions. The snippet deliberately leaves test rows unused. For a final test evaluation, reuse the selected scaler and model; do not fit a second scaler on the test population. Dataset partitions documents membership and grouping.

Extracting Tensors discards Dataset role and source membership enforcement. ml.fit_knn validates numerical inputs but cannot reconstruct whether those rows were training, validation or test observations. Retain the original Dataset views, call the relevant partition checks before extraction and keep the preprocessing artifact with the model.

Neighbors and original source identity

Neighbor indices refer to positions in the fitted matrix, not automatically to source row IDs. If fitting used a Dataset view, retain dataset.row_ids(training) in the same order and use each neighbor position to look up the corresponding source ID. A shuffled, selected or concatenated fitting matrix needs its own matching row map. Feature names, label meanings, source IDs and scaling statistics are not stored as a schema by this Tensor based model.

Limits, errors and computational cost

Every input and output Tensor is limited to 1,000,000 elements. Each prediction or neighbor query admits at most Q × N × D = 50,000,000 distance coordinate evaluations. Probability output also requires Q × C ≤ 1,000,000; neighbor outputs require Q × k ≤ 1,000,000. These are checked operation limits, not a promise that a large request fits a process memory budget.

OperationTimeAdditional storage, excluding input Tensors
FitO(ND + N log N)Retained O(ND + N + C) fitted values, class mapping and row classes.
Predict or inspect neighborsDistance work O(QND) plus average linear neighbor selection and O(Qk log k) sorting.O(N) working neighbor buffer; output O(Q) predictions, O(QC) probabilities or O(Qk) neighbors, plus class vote working storage.
Read classesO(C)O(C) detached output.

Invalid k, wrong label shape, fractional/negative class IDs, mismatched query columns and nonfinite or unrepresentable distance arithmetic fail with an ML diagnostic (R2301). Signature and import errors fail during checking. Use Float64 storage for large exact class IDs; Float32 can round them before the label check.

Distance weighting, configurable distance metrics, approximate search, radius queries, KNN regression, automatic standardization, partial fitting and KNN serialization are not implemented. Preserve the source data, row order, preprocessing and chosen k to refit this model; Dense classifier checkpoint APIs do not serialize a KNNClassifier.

Aner documentation · Reference and guides