Clustering
Learn to group similar data points with AiDotNet’s clustering algorithms — all through the same AiModelBuilder facade.
What is Clustering?
Clustering is an unsupervised learning task: group similar points together without predefined labels. Examples include customer segmentation, document organization, anomaly detection, and image segmentation.
Clustering uses the features-only loader DataLoaders.FromMatrix(...). Cluster assignments come from result.Predict(...), and quality metrics are cached on result.Evaluation.ClusteringMetrics.
Types of Clustering
- Partitioning (
KMeans,KMedoids): non-overlapping clusters around centers. - Density-based (
DBSCAN,MeanShift): dense regions separated by sparse ones; finds outliers. - Hierarchical (
AgglomerativeClustering,BIRCH): a tree of clusters.
Quick Start (K-Means)
using AiDotNet;
using AiDotNet.Clustering.Options;
using AiDotNet.Clustering.Partitioning;
using AiDotNet.Data.Loaders;
using AiDotNet.Tensors.LinearAlgebra;
// Customer purchase patterns: [age, annual spend]
double[][] customers =
{
new[] { 25.0, 45000.0 }, new[] { 35.0, 67000.0 }, new[] { 45.0, 89000.0 },
new[] { 32.0, 52000.0 }, new[] { 28.0, 43000.0 }, new[] { 48.0, 92000.0 },
};
var data = ToMatrix(customers);
var result = await new AiModelBuilder<double, Matrix<double>, Vector<double>>()
.ConfigureModel(new KMeans<double>(new KMeansOptions<double> { NumClusters = 3, Seed = 42 }))
.ConfigureDataLoader(DataLoaders.FromMatrix(data))
.BuildAsync();
var labels = result.Predict(data);
Console.WriteLine($"Customer 0 -> cluster {(int)labels[0]}");
var metrics = result.Evaluation.ClusteringMetrics;
if (metrics is not null)
Console.WriteLine($"Silhouette: {metrics.Silhouette:F4}");
static Matrix<double> ToMatrix(double[][] rows)
{
var m = new Matrix<double>(rows.Length, rows[0].Length);
for (int i = 0; i < rows.Length; i++)
for (int j = 0; j < rows[0].Length; j++)
m[i, j] = rows[i][j];
return m;
}
Available Clustering Algorithms
| Family | Algorithms |
|---|---|
| Partitioning | KMeans, KMedoids, BisectingKMeans, FuzzyCMeans |
| Density-based | DBSCAN, MeanShift, Denclue |
| Hierarchical | AgglomerativeClustering, BIRCH, CURE |
| Graph / other | SpectralClustering, AffinityPropagation |
Finding the Optimal K
There is no magic K — sweep candidate values and keep the best silhouette, which the facade computes for you.
using AiDotNet;
using AiDotNet.Clustering.Options;
using AiDotNet.Clustering.Partitioning;
using AiDotNet.Data.Loaders;
using AiDotNet.Tensors.LinearAlgebra;
double[][] points =
{
new[] { 1.0, 2.0 }, new[] { 1.5, 1.8 }, new[] { 1.0, 0.6 },
new[] { 5.0, 8.0 }, new[] { 8.0, 8.0 }, new[] { 9.0, 11.0 },
new[] { 8.0, 2.0 }, new[] { 10.0, 2.0 }, new[] { 9.0, 3.0 },
};
var data = ToMatrix(points);
int bestK = 2;
double bestSilhouette = double.NegativeInfinity;
for (int k = 2; k <= 5; k++)
{
var result = await new AiModelBuilder<double, Matrix<double>, Vector<double>>()
.ConfigureModel(new KMeans<double>(new KMeansOptions<double> { NumClusters = k, Seed = 42 }))
.ConfigureDataLoader(DataLoaders.FromMatrix(data))
.BuildAsync();
double silhouette = result.Evaluation.ClusteringMetrics?.Silhouette ?? double.NaN;
Console.WriteLine($"K={k}: Silhouette={silhouette:F4}");
if (silhouette > bestSilhouette) { bestSilhouette = silhouette; bestK = k; }
}
Console.WriteLine($"Best K: {bestK}");
static Matrix<double> ToMatrix(double[][] rows)
{
var m = new Matrix<double>(rows.Length, rows[0].Length);
for (int i = 0; i < rows.Length; i++)
for (int j = 0; j < rows[0].Length; j++)
m[i, j] = rows[i][j];
return m;
}
DBSCAN — Density-Based Clustering
Finds clusters of arbitrary shape and flags outliers as DBSCAN<double>.NoiseLabel (-1).
using AiDotNet;
using AiDotNet.Clustering.Density;
using AiDotNet.Clustering.Options;
using AiDotNet.Data.Loaders;
using AiDotNet.Tensors.LinearAlgebra;
double[][] points =
{
new[] { 1.0, 2.0 }, new[] { 1.5, 1.8 }, new[] { 1.2, 2.2 }, new[] { 1.1, 1.9 },
new[] { 8.0, 8.0 }, new[] { 8.2, 7.8 }, new[] { 7.9, 8.1 }, new[] { 8.1, 8.3 },
new[] { 25.0, 80.0 },
};
var data = ToMatrix(points);
var result = await new AiModelBuilder<double, Matrix<double>, Vector<double>>()
.ConfigureModel(new DBSCAN<double>(new DBSCANOptions<double> { Epsilon = 1.0, MinPoints = 3 }))
.ConfigureDataLoader(DataLoaders.FromMatrix(data))
.BuildAsync();
var labels = result.Predict(data);
int outliers = 0;
for (int i = 0; i < labels.Length; i++)
if ((int)labels[i] == DBSCAN<double>.NoiseLabel) outliers++;
Console.WriteLine($"Outliers: {outliers}");
static Matrix<double> ToMatrix(double[][] rows)
{
var m = new Matrix<double>(rows.Length, rows[0].Length);
for (int i = 0; i < rows.Length; i++)
for (int j = 0; j < rows[0].Length; j++)
m[i, j] = rows[i][j];
return m;
}
Hierarchical Clustering
using AiDotNet;
using AiDotNet.Clustering.Hierarchical;
using AiDotNet.Clustering.Options;
using AiDotNet.Data.Loaders;
using AiDotNet.Tensors.LinearAlgebra;
double[][] points =
{
new[] { 1.0, 2.0 }, new[] { 1.5, 1.8 }, new[] { 1.0, 0.6 },
new[] { 5.0, 8.0 }, new[] { 8.0, 8.0 }, new[] { 9.0, 11.0 },
};
var data = ToMatrix(points);
var result = await new AiModelBuilder<double, Matrix<double>, Vector<double>>()
.ConfigureModel(new AgglomerativeClustering<double>(new HierarchicalOptions<double>
{
NumClusters = 3,
Linkage = LinkageMethod.Ward
}))
.ConfigureDataLoader(DataLoaders.FromMatrix(data))
.BuildAsync();
Console.WriteLine($"Davies-Bouldin: {result.Evaluation.ClusteringMetrics?.DaviesBouldin:F4}");
static Matrix<double> ToMatrix(double[][] rows)
{
var m = new Matrix<double>(rows.Length, rows[0].Length);
for (int i = 0; i < rows.Length; i++)
for (int j = 0; j < rows[0].Length; j++)
m[i, j] = rows[i][j];
return m;
}
Linkage Methods
| Method | Best For |
|---|---|
Ward |
Compact, equal-sized clusters |
Complete |
Well-separated clusters |
Average |
Balanced approach |
Single |
Elongated clusters |
Evaluation Metrics
Every clustering build fills result.Evaluation.ClusteringMetrics with the standard internal validity measures.
using AiDotNet;
using AiDotNet.Clustering.Options;
using AiDotNet.Clustering.Partitioning;
using AiDotNet.Data.Loaders;
using AiDotNet.Tensors.LinearAlgebra;
double[][] points =
{
new[] { 1.0, 2.0 }, new[] { 1.5, 1.8 }, new[] { 1.0, 0.6 },
new[] { 5.0, 8.0 }, new[] { 8.0, 8.0 }, new[] { 9.0, 11.0 },
new[] { 8.0, 2.0 }, new[] { 10.0, 2.0 }, new[] { 9.0, 3.0 },
};
var result = await new AiModelBuilder<double, Matrix<double>, Vector<double>>()
.ConfigureModel(new KMeans<double>(new KMeansOptions<double> { NumClusters = 3, Seed = 42 }))
.ConfigureDataLoader(DataLoaders.FromMatrix(ToMatrix(points)))
.BuildAsync();
var m = result.Evaluation.ClusteringMetrics;
if (m is not null)
{
Console.WriteLine($"Silhouette: {m.Silhouette:F4} (higher is better)");
Console.WriteLine($"Calinski-Harabasz: {m.CalinskiHarabasz:F2} (higher is better)");
Console.WriteLine($"Davies-Bouldin: {m.DaviesBouldin:F4} (lower is better)");
}
static Matrix<double> ToMatrix(double[][] rows)
{
var mtx = new Matrix<double>(rows.Length, rows[0].Length);
for (int i = 0; i < rows.Length; i++)
for (int j = 0; j < rows[0].Length; j++)
mtx[i, j] = rows[i][j];
return mtx;
}
Best Practices
- Scale features: clustering is distance-based; normalize before building (
ConfigurePreprocessing(...)). - Try multiple algorithms: K-Means, DBSCAN, and hierarchical find different shapes.
- Validate K: sweep and compare silhouette across candidates.
- Handle outliers: prefer DBSCAN for noisy data.
- Interpret results: inspect cluster sizes and centers.
Next Steps
- Customer Segmentation Example
- Classification Tutorial — for labeled data