Skip to main content

DBSCAN with TensorFlow data

TensorFlow 2.21 has no native high-level DBSCAN estimator. Tensor operations can prepare features and illustrate neighborhoods, but scikit-learn supplies the maintained clustering implementation in this example. That distinction matters: a correct DBSCAN implementation must expand only from core points, may attach border points without expanding them, and must handle ambiguous border assignments consistently.

1. Create a suitable non-convex dataset​

Interlocking half-moons exercise the connected-density behavior that distinguishes DBSCAN from centroid methods. The fixed seed makes the added noise deterministic.

import numpy as np
import tensorflow as tf
from sklearn.datasets import make_moons
from sklearn.preprocessing import StandardScaler

tf.random.set_seed(42)
X, _ = make_moons(n_samples=300, noise=0.06, random_state=42)
X_scaled = StandardScaler().fit_transform(X).astype("float32")
points = tf.constant(X_scaled)
assert points.shape == (300, 2)

2. Inspect epsilon neighborhoods​

The point itself counts toward min_samples. The full pairwise matrix below costs quadratic memory and is included only as a small educational diagnostic, not as a scalable DBSCAN implementation.

epsilon = 0.30
min_samples = 5
distances = tf.norm(points[:, tf.newaxis, :] - points[tf.newaxis, :, :], axis=2)
neighbor_counts = tf.reduce_sum(tf.cast(distances <= epsilon, tf.int32), axis=1)
core_mask_tf = neighbor_counts >= min_samples
assert bool(tf.reduce_any(core_mask_tf))

3. Fit the maintained estimator​

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_tf.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()}")

Cluster numbers are arbitrary identifiers; -1 means noise under this fitted density definition, not a proven anomaly.

4. Evaluate and plot​

For an internal shape diagnostic, exclude noise and require at least two non-noise clusters. Reporting a score on all labels would treat noise as one ordinary cluster and answer a different question.

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)

Use TensorFlow upstream for learned features when needed, then freeze and convert the representation before clustering. Reassess the metric, scaling, eps, and density variation whenever that representation changes.