Agent Conversation: anthropic/claude-opus-4.6
Task: ml-clustering-algorithm
Clustering Algorithm Design
Research Question
Design a novel clustering algorithm or distance metric that improves cluster quality across diverse dataset geometries — including convex blobs, non-convex shapes (moons), varied-density clusters, and real-world high-dimensional data (handwritten digits).
Background
Clustering is a fundamental unsupervised learning problem. Classic methods like K-Means assume convex, isotropic clusters; DBSCAN handles arbitrary shapes but requires careful tuning of the eps parameter. Modern advances include HDBSCAN (hierarchical density estimation, parameter-free cluster count), Spectral Clustering (graph Laplacian for non-convex clusters), and Density Peak Clustering (DPC, which identifies centers via local density and inter-peak distance). No single method dominates across all dataset structures, making this an open research question.
Task
Modify the CustomClustering class in scikit-learn/custom_clustering.py (lines 36--120) to implement a novel clustering algorithm. You may also modify the custom_distance function if your approach uses a custom distance metric.
Your algorithm must:
- Accept
n_clusters(int or None) andrandom_stateparameters - Implement
fit(X)that setsself.labels_and returnsself - Implement
predict(X)that returns integer cluster labels - Handle datasets with different structures (convex, non-convex, varied density, high-dimensional)
Interface
class CustomClustering(BaseEstimator, ClusterMixin):
def __init__(self, n_clusters=None, random_state=42): ...
def fit(self, X): # X: (n_samples, n_features) -> self
def predict(self, X): # X: (n_samples, n_features) -> labels (n_samples,)
Available imports (already in the FIXED section): numpy, sklearn.base.BaseEstimator, sklearn.base.ClusterMixin, sklearn.preprocessing.StandardScaler, sklearn.metrics.*. You may import any module from scikit-learn, numpy, or scipy.
Evaluation
- Datasets: blobs (5 Gaussian clusters), moons (2 half-circles), varied_density (3 clusters with different densities), digits (sklearn Digits, 10 classes, 64 features)
- Metrics: ARI (Adjusted Rand Index, higher is better), NMI (Normalized Mutual Information, higher is better), Silhouette Score (higher is better)
- Success = consistently improving over baselines across all four datasets
scikit-learn/custom_clustering.py [EDITABLE — lines 36–109 only]
1: """Custom clustering algorithm benchmark.
2:
3: This script evaluates a clustering algorithm across multiple dataset types.
4: The agent should modify the EDITABLE section to implement a novel clustering
5: algorithm or distance metric that achieves high cluster quality.
6:
7: Datasets (selected by $ENV):
8: - blobs: Isotropic Gaussian blobs (varying cluster sizes)
9: - moons: Two interleaving half-circles + noiseTool Input
1{
2 "op": "replace",
3 "filename": "scikit-learn/custom_clustering.py",
4 "start_line": 36,
5 "end_line": 109,
6 "content": "# ================================================================\n# EDITABLE -- agent modifies this section (lines 36 to 109)\n# ================================================================\n\n\nclass CustomClustering(BaseEstimator, ClusterMixin):\n \"\"\"Multi-Scale Density-Adaptive Spectral Clustering (MSDASC).\n\n Extends self-tuning spectral clustering (Zelnik-Manor & Perona, 2004)\n with a novel multi-scale kernel bandwidth averaging scheme. Each point's\n affinity kernel adapts to local density at multiple neighborhood scales,\n and the resulting affinities are averaged to capture both fine-grained\n and coarse cluster structure robustly across diverse geometries.\n \"\"\"\n def __init__(self, n_clusters=None, random_state=42):\n self.n_clusters = n_clusters\n self.random_state = random_state\n self.labels_ = None\n\n def fit(self, X):\n from sklearn.decomposition import PCA\n from sklearn.cluster import KMeans\n from sklearn.neighbors import NearestNeighbors\n from scipy.sparse.linalg import eigsh\n from scipy.sparse import csr_matrix\n n, d = X.shape\n k = self.n_clusters if self.n_clusters is not None else 8\n # Adaptive PCA for high-dimensional data\n Xw = X\n if d > 15:\n nc = min(n - 1, d, max(k * 5, 50))\n Xw = PCA(n_components=nc, random_state=self.random_state).fit_transform(X)\n # Build k-NN graph\n nn_k = min(max(15, int(np.sqrt(n))), n - 1)\n dists, idxs = NearestNeighbors(n_neighbors=nn_k + 1).fit(Xw).kneighbors(Xw)\n ri = np.repeat(np.arange(n), nn_k)\n ci = idxs[:, 1:].ravel()\n di = dists[:, 1:].ravel()\n # Novel: Multi-scale self-tuning Gaussian kernel averaging\n # Average affinities over multiple local bandwidth scales to\n # robustly capture structure at different resolutions\n scales = [s for s in [3, 5, 7, 10, 15, 20] if s < dists.shape[1]]\n Wsum = None\n for s in scales:\n sig = np.maximum(dists[:, s], 1e-10)\n w = np.exp(-di**2 / (np.repeat(sig, nn_k) * sig[ci] + 1e-10))\n Ws = csr_matrix((w, (ri, ci)), shape=(n, n))\n Ws = (Ws + Ws.T) / 2.0\n Wsum = Ws if Wsum is None else Wsum + Ws\n W = Wsum * (1.0 / len(scales))\n # Normalized spectral decomposition (Ng-Jordan-Weiss)\n deg = np.array(W.sum(axis=1)).ravel()\n dinv = np.where(deg > 0, 1.0 / np.sqrt(deg), 0.0)\n Dsp = csr_matrix((dinv, (range(n), range(n))), shape=(n, n))\n Lsym = Dsp @ W @ Dsp\n try:\n _, V = eigsh(Lsym, k=k, which='LM')\n except Exception:\n e, Va = np.linalg.eigh(Lsym.toarray())\n V = Va[:, np.argsort(-e)[:k]]\n V = V / np.maximum(np.linalg.norm(V, axis=1, keepdims=True), 1e-10)\n self.labels_ = KMeans(\n n_clusters=k, random_state=self.random_state, n_init=30, max_iter=500\n ).fit_predict(V)\n return self\n\n def predict(self, X):\n if self.labels_ is None:\n self.fit(X)\n return self.labels_\n\n\ndef custom_distance(x, y):\n return np.sqrt(np.sum((x - y) ** 2))\n\n"
7}OK: Replaced lines 36..109 with 75 line(s) in scikit-learn/custom_clustering.py. Editable range: 36–110.
[Current file: scikit-learn/custom_clustering.py | editable: 36–110 | total: 211 lines]
36 # ================================================================
37 # EDITABLE -- agent modifies this section (lines 36 to 109)
38 # ================================================================
... (69 more lines) ...
108 def custom_distance(x, y):
109 return np.sqrt(np.sum((x - y) ** 2))
110 [Test #1] (2 tests remaining; call submit(n=N) to choose which test result to submit as final)
### blobs (scripts/blobs.sh)
Training progress (blobs):
TRAIN_METRICS stage=fitting
TRAIN_METRICS stage=done
TRAIN_METRICS ari=0.935959
TRAIN_METRICS nmi=0.937054
TRAIN_METRICS silhouette=0.665077
ari_blobs: 0.935959
nmi_blobs: 0.937054
silhouette_blobs: 0.665077
### moons (scripts/moons.sh)
Training progress (moons):
TRAIN_METRICS stage=fitting
TRAIN_METRICS stage=done
TRAIN_METRICS ari=1.000000
TRAIN_METRICS nmi=1.000000
TRAIN_METRICS silhouette=0.385298
ari_moons: 1.000000
nmi_moons: 1.000000
silhouette_moons: 0.385298
[Leaderboard] Results saved: {'ari_blobs': 0.935959, 'nmi_blobs': 0.937054, 'silhouette_blobs': 0.665077, 'elapsed_blobs': 3, 'ari_moons': 1.0, 'nmi_moons': 1.0, 'silhouette_moons': 0.385298, 'elapsed_moons': 3, 'ari_digits': 0.643032, 'nmi_digits': 0.758694, 'silhouette_digits': 0.136062, 'elapsed_digits': 3}Tool Input
1{
2 "op": "replace",
3 "filename": "scikit-learn/custom_clustering.py",
4 "start_line": 36,
5 "end_line": 110,
6 "content": "# ================================================================\n# EDITABLE -- agent modifies this section (lines 36 to 109)\n# ================================================================\n\n\nclass CustomClustering(BaseEstimator, ClusterMixin):\n \"\"\"SNN-Weighted Multi-Scale Spectral Clustering (SNN-MSSC).\n\n Novel algorithm that combines three complementary affinity signals:\n 1. Self-tuning Gaussian kernel (Zelnik-Manor & Perona): local density\n adaptation via per-point bandwidth sigma_i = d(x_i, nn_k(x_i))\n 2. Multi-scale averaging: affinities from multiple bandwidth scales\n are averaged to robustly capture structure at different resolutions\n 3. Shared Nearest Neighbor (SNN) weighting: edges are weighted by the\n fraction of shared k-nearest neighbors, providing topology-aware\n affinity that is robust in high-dimensional spaces\n\n The product W = SNN_weight * mean_scale(Gaussian) ensures that only\n pairs that are both geometrically close AND topologically connected\n receive high affinity, improving cluster separation.\n \"\"\"\n def __init__(self, n_clusters=None, random_state=42):\n self.n_clusters = n_clusters\n self.random_state = random_state\n self.labels_ = None\n\n def fit(self, X):\n from sklearn.decomposition import PCA\n from sklearn.cluster import KMeans\n from sklearn.neighbors import NearestNeighbors\n from scipy.sparse.linalg import eigsh\n from scipy.sparse import csr_matrix\n n, d = X.shape\n k = self.n_clusters if self.n_clusters is not None else 8\n # Adaptive PCA for high-dimensional data (reduce noise, not structure)\n Xw = X\n if d > 15:\n nc = min(n - 1, d, max(k * 4, 40))\n Xw = PCA(n_components=nc, random_state=self.random_state).fit_transform(X)\n # Build k-NN graph\n nn_k = min(max(15, int(np.sqrt(n))), n - 1)\n dists, idxs = NearestNeighbors(n_neighbors=nn_k + 1).fit(Xw).kneighbors(Xw)\n ri = np.repeat(np.arange(n), nn_k)\n ci = idxs[:, 1:].ravel()\n di = dists[:, 1:].ravel()\n # SNN: count shared neighbors via sparse matrix product\n B = csr_matrix((np.ones(len(ri)), (ri, ci)), shape=(n, n))\n SNN = B @ B.T\n snn_raw = np.array(SNN[ri, ci]).ravel()\n snn_w = (snn_raw + 1.0) / (nn_k + 1.0) # Smoothed, in (0, 1]\n # Multi-scale self-tuning Gaussian kernel, weighted by SNN\n scales = [s for s in [3, 5, 7, 10, 15, 20] if s < dists.shape[1]]\n Wsum = None\n for s in scales:\n sig = np.maximum(dists[:, s], 1e-10)\n g = np.exp(-di**2 / (np.repeat(sig, nn_k) * sig[ci] + 1e-10))\n w = snn_w * g # SNN-weighted Gaussian affinity\n Ws = csr_matrix((w, (ri, ci)), shape=(n, n))\n Ws = (Ws + Ws.T) / 2.0\n Wsum = Ws if Wsum is None else Wsum + Ws\n W = Wsum * (1.0 / len(scales))\n # Normalized spectral decomposition (Ng-Jordan-Weiss)\n deg = np.array(W.sum(axis=1)).ravel()\n dinv = np.where(deg > 0, 1.0 / np.sqrt(deg), 0.0)\n Dsp = csr_matrix((dinv, (range(n), range(n))), shape=(n, n))\n Lsym = Dsp @ W @ Dsp\n try:\n _, V = eigsh(Lsym, k=k, which='LM')\n except Exception:\n e, Va = np.linalg.eigh(Lsym.toarray())\n V = Va[:, np.argsort(-e)[:k]]\n V = V / np.maximum(np.linalg.norm(V, axis=1, keepdims=True), 1e-10)\n self.labels_ = KMeans(\n n_clusters=k, random_state=self.random_state, n_init=50, max_iter=500\n ).fit_predict(V)\n return self\n\n def predict(self, X):\n if self.labels_ is None:\n self.fit(X)\n return self.labels_\n\n\ndef custom_distance(x, y):\n return np.sqrt(np.sum((x - y) ** 2))\n\n"
7}OK: Replaced lines 36..110 with 86 line(s) in scikit-learn/custom_clustering.py. Editable range: 36–121.
[Current file: scikit-learn/custom_clustering.py | editable: 36–121 | total: 222 lines]
36 # ================================================================
37 # EDITABLE -- agent modifies this section (lines 36 to 109)
38 # ================================================================
... (80 more lines) ...
119 def custom_distance(x, y):
120 return np.sqrt(np.sum((x - y) ** 2))
121 [Test #2] (1 test remaining; call submit(n=N) to choose which test result to submit as final)
### blobs (scripts/blobs.sh)
Training progress (blobs):
TRAIN_METRICS stage=fitting
TRAIN_METRICS stage=done
TRAIN_METRICS ari=0.938866
TRAIN_METRICS nmi=0.939305
TRAIN_METRICS silhouette=0.664755
ari_blobs: 0.938866
nmi_blobs: 0.939305
silhouette_blobs: 0.664755
### moons (scripts/moons.sh)
Training progress (moons):
TRAIN_METRICS stage=fitting
TRAIN_METRICS stage=done
TRAIN_METRICS ari=1.000000
TRAIN_METRICS nmi=1.000000
TRAIN_METRICS silhouette=0.385298
ari_moons: 1.000000
nmi_moons: 1.000000
silhouette_moons: 0.385298
[Leaderboard] Results saved: {'ari_blobs': 0.938866, 'nmi_blobs': 0.939305, 'silhouette_blobs': 0.664755, 'elapsed_blobs': 3, 'ari_moons': 1.0, 'nmi_moons': 1.0, 'silhouette_moons': 0.385298, 'elapsed_moons': 3, 'ari_digits': 0.664351, 'nmi_digits': 0.779038, 'silhouette_digits': 0.136725, 'elapsed_digits': 2}Tool Input
1{
2 "n": 2
3}[submit] Submitting result from test #2 as final.
### blobs (scripts/blobs.sh)
Training progress (blobs):
TRAIN_METRICS stage=fitting
TRAIN_METRICS stage=done
TRAIN_METRICS ari=0.938866
TRAIN_METRICS nmi=0.939305
TRAIN_METRICS silhouette=0.664755
ari_blobs: 0.938866
nmi_blobs: 0.939305
silhouette_blobs: 0.664755
### moons (scripts/moons.sh)
Training progress (moons):
TRAIN_METRICS stage=fitting
TRAIN_METRICS stage=done
TRAIN_METRICS ari=1.000000
TRAIN_METRICS nmi=1.000000
TRAIN_METRICS silhouette=0.385298
ari_moons: 1.000000
nmi_moons: 1.000000
silhouette_moons: 0.385298
### digits (scripts/digits.sh)
Training progress (digits):
TRAIN_METRICS stage=fitting
TRAIN_METRICS stage=done
TRAIN_METRICS ari=0.664351
TRAIN_METRICS nmi=0.779038
TRAIN_METRICS silhouette=0.136725
ari_digits: 0.664351
nmi_digits: 0.779038
silhouette_digits: 0.136725
[submit] Finalized test #2 as final.
[Leaderboard] Results saved: {'ari_blobs': 0.938866, 'nmi_blobs': 0.939305, 'silhouette_blobs': 0.664755, 'ela