Skip to main content

Common Mistakes and Best Practices in Affinity Propagation

Affinity Propagation exchanges dense responsibility and availability messages to select exemplars. Its output is not parameter-free simply because n_clusters is absent.

1. Reversing the preference effect​

The diagonal similarity s(k,k)s(k,k) is point kk's preference to become an exemplar. Higher preferences usually produce more exemplars; lower preferences usually produce fewer. The relationship need not be smooth or monotonic at every finite-sample transition, and neither direction reveals an automatically optimal count.

import numpy as np
from sklearn.cluster import AffinityPropagation
from sklearn.datasets import make_blobs
from sklearn.metrics import pairwise_distances

X, reference = make_blobs(n_samples=180, centers=3, cluster_std=0.60, random_state=42)
similarity = -pairwise_distances(X, metric="sqeuclidean")
off_diagonal = similarity[~np.eye(len(X), dtype=bool)]
low, middle, high = np.quantile(off_diagonal, [0.25, 0.50, 0.75])

cluster_counts = []
for preference in (low, middle, high):
model = AffinityPropagation(
affinity="precomputed", preference=preference, damping=0.8, random_state=42
).fit(similarity)
cluster_counts.append(len(model.cluster_centers_indices_))
assert cluster_counts[0] <= cluster_counts[-1]

Report the preferences tried and the resulting stability; do not pick a count solely because it matches expectations.

2. Passing distances as similarities​

With affinity='precomputed', larger values must mean more similar. A positive distance matrix reverses that meaning. Negate squared distances, use a defensible kernel or domain similarity, and verify symmetry and finite values. scikit-learn's affinity='euclidean' is shorthand for negative squared Euclidean similarity.

3. Ignoring convergence​

Oscillation is possible. Damping mixes each old message with its undamped update; values closer to one may stabilize oscillation but can take longer to respond. max_iter only stops computation, while convergence_iter specifies how long exemplar decisions must remain unchanged.

model = AffinityPropagation(
damping=0.8,
max_iter=400,
convergence_iter=30,
random_state=42,
).fit(X)
assert model.n_iter_ < model.max_iter
labels = model.labels_

Never suppress a ConvergenceWarning and report the labels as settled. Investigate scaling, similarity, preference, duplicate points, and damping.

4. Treating validation metrics as truth​

Silhouette, Davies-Bouldin, and similar indices reward particular geometry. ARI needs external labels and is not available in a truly unlabeled task. Combine quantitative diagnostics with stability, exemplar inspection, and application-specific validation.

from sklearn.metrics import adjusted_rand_score, silhouette_score

assert adjusted_rand_score(reference, labels) > 0.95
assert silhouette_score(X, labels) > 0.70

These thresholds describe the fixed blob example, not universal quality cutoffs.

5. Underestimating scale and prediction semantics​

Similarities, responsibilities, and availabilities are dense n×nn\times n arrays: memory is quadratic, and each iteration is roughly quadratic. Sampling or a different estimator is often more appropriate than forcing the method onto a large dataset.

The learned exemplars are actual training observations. scikit-learn can assign a new observation to an exemplar with predict, but refitting with that observation can change every message and exemplar. Document whether labels are fixed assignments to an old model or the result of a full refit.

Conclusion​

Use a meaningful similarity, examine preference sensitivity, confirm convergence, inspect exemplars, and account for quadratic memory. The missing n_clusters argument is a modeling trade-off, not automatic model selection.