DBSCAN with PyTorch data
PyTorch 2.14 does not provide a native high-level DBSCAN estimator. It can calculate tensor distances or produce learned embeddings; scikit-learn supplies the maintained estimator below. A short breadth-first loop is easy to get subtly wrong because only core points propagate density reachability while border points merely join a cluster.
1. Prepare deterministic non-convex data
import numpy as np
import torch
from sklearn.datasets import make_moons
from sklearn.preprocessing import StandardScaler
torch.manual_seed(42)
X, _ = make_moons(n_samples=300, noise=0.06, random_state=42)
X_scaled = StandardScaler().fit_transform(X).astype("float32")
points = torch.from_numpy(X_scaled)
assert points.shape == (300, 2)
2. Inspect neighborhoods with tensors
epsilon = 0.30
min_samples = 5
distances = torch.cdist(points, points)
neighbor_counts = torch.sum(distances <= epsilon, dim=1)
core_mask_torch = neighbor_counts >= min_samples
assert bool(torch.any(core_mask_torch))
This dense matrix uses quadratic memory. A maintained neighbor search can avoid materializing it for favorable metrics and data geometry; it still may degrade in high dimensions or for large eps.
3. Fit DBSCAN at the library boundary
from sklearn.cluster import DBSCAN
dbscan = DBSCAN(eps=epsilon, min_samples=min_samples, metric="euclidean")
labels = dbscan.fit_predict(points.numpy())
core_mask = np.zeros(labels.shape, dtype=bool)
core_mask[dbscan.core_sample_indices_] = True
np.testing.assert_array_equal(core_mask, core_mask_torch.numpy())
n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
assert n_clusters == 2
print(f"Clusters: {n_clusters}; noise points: {(labels == -1).sum()}")
4. Evaluate and visualize
import matplotlib.pyplot as plt
from sklearn.metrics import silhouette_score
non_noise = labels != -1
silhouette = silhouette_score(X_scaled[non_noise], labels[non_noise])
assert 0.20 < silhouette < 1.0
fig, ax = plt.subplots()
ax.scatter(X_scaled[~core_mask, 0], X_scaled[~core_mask, 1], c=labels[~core_mask], cmap="viridis", s=18)
ax.scatter(X_scaled[core_mask, 0], X_scaled[core_mask, 1], c=labels[core_mask], cmap="viridis", s=48, edgecolor="black")
ax.set(title=f"DBSCAN: {n_clusters} clusters", xlabel="scaled feature 1", ylabel="scaled feature 2")
fig.tight_layout()
plt.close(fig)
Noise is a density-relative label rather than proof of fraud or abnormality. Validate parameter stability, domain usefulness, and sensitivity to scaling; if densities vary strongly, compare OPTICS or HDBSCAN rather than forcing one global eps.