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 is point '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 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.