Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

stough 202-

Preview: K-means single iteration

Demo on k means clustering. We generate a little data and then do a single step of the EM scheme for clustering.

Finding objects in images:

  • Color coherence

  • Spatial coherence

Pixels belonging to the same object tend to have similar colors (color coherence) and to be near one another (spatial coherence). Clustering groups data points that are similar, so it’s a natural first step toward finding objects. K-means is the simplest and most widely used clustering method. Given a number of clusters KK, it looks for KK cluster centers c1,…,cK\mathbf{c}_1, \ldots, \mathbf{c}_K and an assignment of every point to a cluster that minimize the total squared distance from each point to its center:

J=∑i=1N∥xi−ck(i)∥2J = \sum_{i=1}^{N} \left\| \mathbf{x}_i - \mathbf{c}_{k(i)} \right\|^2

where k(i)k(i) is the cluster point ii is assigned to. We’ll first see the algorithm on easy 2D data, then on the colors of an image, and finally in SLIC superpixels, which clusters on color and position.

Two blobs of 500 points each: one centered at (0,0)(0,0) with variance 1, and one centered at (5,3)(5,3) with variance 4, so it’s more spread out. We know the right answer here, which makes it easy to see how k-means does.

Look at the generated data

<Figure size 640x480 with 1 Axes>

Step 0: initialize

K-means needs a starting guess for the centers. A simple choice is KK distinct data points picked at random.

array([[4.67585676, 4.24890125], [1.946762 , 4.81379048]])
<Figure size 400x400 with 1 Axes>

Step 1: assign each point to its nearest center

cdist computes the distance from every point (row of X) to every center (row of CC), an N×KN \times K matrix. argmin along each row then picks the closest center for each point. With the centers fixed, this is the assignment that makes JJ as small as possible.

array([[5.52699637, 4.5086391 ], [6.41080128, 5.09573214], [4.54117375, 4.18543854], [5.15470562, 4.14259554], [7.35665814, 6.3058581 ], [6.42398001, 5.24878489], [5.83518661, 4.44120574], [5.80056776, 4.33449988], [6.40652037, 5.32732835], [6.83636001, 5.85796149]])
array([0.60634515, 0.50901299])
<Figure size 400x400 with 1 Axes>

With only two centers, the boundary between the clusters is a straight line, the perpendicular bisector between the two centers. Points on either side go to the nearer center, whether or not that matches the true blobs.

Step 2: move each center to the mean of its points

Now hold the assignments fixed, and ask where each center should be to make JJ as small as possible. The point that minimizes the total squared distance to a set of points is their mean, so each center moves to the average of the points assigned to it.

array([5.63446637, 3.09083537])
np.int64(579)
<Figure size 400x400 with 1 Axes>

Repeat

That’s one iteration: assign, then update. Now we assign again using the moved centers, then update again, and so on. Each step can only lower JJ (or leave it unchanged), and there are only finitely many ways to assign the points, so the process must eventually stop changing: k-means always converges. Here’s the second assignment:

<Figure size 400x400 with 1 Axes>

A summary of the first iteration: the data, the random initial centers, and the clusters and centers after one update.

<Figure size 900x300 with 3 Axes>

Running to convergence

In practice we just loop until the assignments stop changing. This is the EM-style alternation in a few lines, starting again from the same initial centers:

iteration 0: J =    5436.5, centers = [[5.63, 3.09], [0.28, 0.52]]
iteration 1: J =    4540.1, centers = [[5.43, 3.37], [0.14, 0.14]]
iteration 2: J =    4509.8, centers = [[5.36, 3.39], [0.13, 0.07]]
iteration 3: J =    4508.4, centers = [[5.34, 3.38], [0.11, 0.06]]
iteration 4: J =    4508.3, centers = [[5.33, 3.39], [0.11, 0.05]]
<Figure size 400x400 with 1 Axes>

JJ decreases at every iteration, and within a handful of iterations the centers settle near the true blob centers, (0,0)(0,0) and (5,3)(5,3). They won’t be exactly there, though. K-means draws the boundary halfway between centers, so the wide blob’s outlying points that fall closer to the tight blob’s center get assigned to it, pulling that center slightly toward the wide blob. Losing those points pushes the wide blob’s center slightly away. K-means implicitly assumes round clusters of similar size.

Two other caveats to keep in mind:

  • Initialization matters. K-means finds a local minimum of JJ, not necessarily the best one. Re-run the notebook a few times: on easy data like this it nearly always finds the same answer, but on harder data different starting centers can give different clusterings. Practical implementations run several random starts and keep the best, or use smarter seeding such as k-means++.

  • You have to choose KK. Try K = 3 at the top. K-means will happily split the data into three clusters whether or not three is meaningful.