1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation
def generate_custom_data(): rng = np.random.default_rng(42)
theta = rng.uniform(0, 2 * np.pi, 100) r = rng.normal(1, 0.3, 100) x1 = np.stack([r * np.cos(theta), r * np.sin(theta)], axis=1) x2 = rng.normal(loc=[3, 3], scale=[1.2, 0.6], size=(60, 2)) x3 = rng.normal(loc=[-2, 4], scale=0.6, size=(70, 2)) x4 = rng.normal(loc=[3, 0], scale=0.3, size=(40, 2))
return np.vstack([x1, x2, x3, x4])
X = generate_custom_data()
class KMeansAnimator: def __init__(self, X, k, max_iters=100, tol=1e-4): self.X = X self.k = k self.max_iters = max_iters self.tol = tol self.history = []
def fit(self): n_samples, _ = self.X.shape rng = np.random.default_rng() centroids = self.X[rng.choice(n_samples, self.k, replace=False)]
for _ in range(self.max_iters): distances = np.linalg.norm(self.X[:, np.newaxis] - centroids, axis=2) labels = np.argmin(distances, axis=1) self.history.append((labels.copy(), centroids.copy()))
new_centroids = np.array( [ ( self.X[labels == j].mean(axis=0) if len(self.X[labels == j]) > 0 else centroids[j] ) for j in range(self.k) ] )
if np.linalg.norm(new_centroids - centroids) < self.tol: self.history.append((labels.copy(), new_centroids.copy())) break centroids = new_centroids
return self.history[-1]
k = 4 animator = KMeansAnimator(X, k) labels, centroids = animator.fit()
fig, ax = plt.subplots() scat = ax.scatter([], [], c=[], cmap="viridis", s=30) cent_scat = ax.scatter([], [], c="red", s=100, marker="X") ax.set_xlim(X[:, 0].min() - 1, X[:, 0].max() + 1) ax.set_ylim(X[:, 1].min() - 1, X[:, 1].max() + 1) ax.set_title("K-means Clustering - Animation")
def update(frame): labels, centroids = animator.history[frame] scat.set_offsets(X) scat.set_array(labels.astype(float)) cent_scat.set_offsets(centroids) ax.set_title(f"K-means Iteration {frame + 1}") return scat, cent_scat
ani = FuncAnimation( fig, update, frames=len(animator.history), interval=800, repeat=False ) ani.save("kmeans_animation.gif")
|