Advanced Usage

This page covers advanced usage patterns and customization.

Custom Distance Metrics

While kmeans-seeding uses Euclidean distance internally, you can preprocess your data:

from sklearn.metrics import pairwise_distances
from sklearn.manifold import MDS
from kmeans_seeding import rskmeans

# Compute custom distance matrix
distances = pairwise_distances(X, metric='cosine')

# Embed in Euclidean space
mds = MDS(n_components=50, dissimilarity='precomputed')
X_embedded = mds.fit_transform(distances)

# Now use Euclidean k-means++
centers_embedded = rskmeans(X_embedded, n_clusters=10)

# Map back to original indices
center_indices = [np.argmin(np.linalg.norm(X_embedded - c, axis=1))
                  for c in centers_embedded]
centers = X[center_indices]

Incremental Clustering

Add more clusters to existing centers:

from kmeans_seeding import rskmeans
from sklearn.cluster import KMeans
import numpy as np

# Initial clustering
centers_10 = rskmeans(X, n_clusters=10, random_state=42)

# Add 5 more clusters
# Compute distances to existing centers
kmeans_temp = KMeans(n_clusters=10, init=centers_10, n_init=1, max_iter=0)
kmeans_temp.fit(X)
distances = kmeans_temp.transform(X)
min_distances_sq = np.min(distances, axis=1) ** 2

# Sample 5 more centers using D² distribution
probs = min_distances_sq / min_distances_sq.sum()
new_indices = np.random.choice(len(X), size=5, replace=False, p=probs)

# Combine
centers_15 = np.vstack([centers_10, X[new_indices]])

Parallel Initialization

Initialize multiple clusterings in parallel:

from joblib import Parallel, delayed
from kmeans_seeding import rskmeans
from sklearn.cluster import KMeans

def run_clustering(seed):
    centers = rskmeans(X, n_clusters=100,
                      index_type='FastLSH',
                      random_state=seed)
    kmeans = KMeans(n_clusters=100, init=centers, n_init=1)
    kmeans.fit(X)
    return kmeans.inertia_, kmeans.labels_

# Run 10 clusterings in parallel
results = Parallel(n_jobs=-1)(
    delayed(run_clustering)(seed) for seed in range(10)
)

# Get best result
best_idx = np.argmin([r[0] for r in results])
best_inertia, best_labels = results[best_idx]

Hierarchical Initialization

Use k-means++ for hierarchical clustering:

from kmeans_seeding import kmeanspp
from sklearn.cluster import AgglomerativeClustering

# Initialize with well-spread points
initial_centers = kmeanspp(X, n_clusters=100, random_state=42)

# Use as initial linkage for hierarchical clustering
hierarchical = AgglomerativeClustering(n_clusters=10)
labels = hierarchical.fit_predict(initial_centers)

Weighted Sampling

Initialize with sample weights:

import numpy as np
from kmeans_seeding import rskmeans

# Define weights (e.g., based on density)
weights = compute_density(X)  # Your density estimation

# Sample weighted subset
n_sample = min(50000, len(X))
probs = weights / weights.sum()
indices = np.random.choice(len(X), size=n_sample, p=probs)
X_weighted = X[indices]

# Initialize on weighted sample
centers = rskmeans(X_weighted, n_clusters=100,
                  index_type='FastLSH')

Warm Starting

Use previous centers to initialize new clustering:

from sklearn.cluster import KMeans
from kmeans_seeding import rskmeans
import numpy as np

# Initial clustering
centers_old = rskmeans(X, n_clusters=10, random_state=42)
kmeans = KMeans(n_clusters=10, init=centers_old, n_init=1)
kmeans.fit(X)

# New data arrives
X_new = np.vstack([X, new_data])

# Warm start with old centers
centers_new = rskmeans(X_new, n_clusters=10,
                      index_type='FastLSH',
                      random_state=42)

# Could also just use old centers directly
kmeans = KMeans(n_clusters=10, init=centers_old, n_init=1)
kmeans.fit(X_new)

Memory-Efficient Processing

For very large datasets that don’t fit in memory:

from kmeans_seeding import rskmeans
from sklearn.cluster import MiniBatchKMeans
import numpy as np

# Sample for initialization
n_sample = 100000
indices = np.random.choice(len(X), size=n_sample, replace=False)
X_sample = X[indices]

# Initialize on sample
centers = rskmeans(X_sample, n_clusters=500,
                  index_type='FastLSH')

# Use MiniBatchKMeans on full data
mbkmeans = MiniBatchKMeans(n_clusters=500,
                           init=centers,
                           n_init=1,
                           batch_size=10000)

# Process in batches
for batch in generate_batches(X, batch_size=10000):
    mbkmeans.partial_fit(batch)

labels = mbkmeans.predict(X)

Custom Index Parameters (RS-k-means++)

Fine-tune FAISS index behavior:

from kmeans_seeding import rskmeans

# IVFFlat with custom nprobe
# Note: This requires modifying the C++ code
# The Python API doesn't expose all FAISS parameters yet

# For now, use the provided index types
centers = rskmeans(X, n_clusters=100,
                  index_type='IVFFlat',
                  max_iter=50)

# FastLSH parameters can be tuned
# (No FAISS needed, highly optimized)
centers = rskmeans(X, n_clusters=100,
                  index_type='FastLSH',
                  max_iter=50)

Debugging and Profiling

Profile initialization time:

import cProfile
import pstats
from kmeans_seeding import rskmeans

profiler = cProfile.Profile()
profiler.enable()

centers = rskmeans(X, n_clusters=100,
                  index_type='FastLSH')

profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats(20)

See Also