import numpy as np
import matplotlib.pyplot as plt
from prml.clustering import KMeans
from prml.datasets import load_old_faithful
from prml.distribution import MultivariateGaussianMixture
# Set random seed to make deterministic
np.random.seed(0)
# Ignore zero divisions and computation involving NaN values.
np.seterr(divide="ignore", invalid="ignore")
# Enable higher resolution plots
%config InlineBackend.figure_format = 'retina'
# Enable autoreload all modules before executing code
%reload_ext autoreload
%autoreload 2
Consider the problem of identifying groups, or clusters, of data points in a multidimensional space. Suppose that we have a data set {x1,…,xN}. The goal is to partition the data set into K clusters, for a given value of K. Intuitively, a cluster can be a group of data points whose inter-point distances are small compared to the distances to points outside the cluster.
We can formalize this notion by introducing a set of D-dimensional vectors μk. Each such vector is a prototype associated with the kth cluster, essentially representing the centres of the clusters. Then, the goal is then to find an assignment of data points to clusters, and a set of vectors μk, such that the sum of the squares of the distances of each data point to its closest vector μk is a minimum.
We can then define an objective function, sometimes called a distortion measure, given by
J=N∑n=1K∑k=1rnk||xn−μk||22where rnk∈{0,1} are binary indicator variables, describing which of the K clusters the data point xn is assigned. The objective function represents the sum of the squares of the distances of each data point to its assigned cluster center μk. Thus, the goal is to find values for the {rnk} and the μk that minimize J.
K-means algorithm:
This can be achieved using an iterative procedure involving two successive steps. In the first phase we minimize J with respect to the rnk, keeping the μk fixed. In the second phase we minimize J with respect to the μk, keeping rnk fixed. These two stages are then repeated until convergence. We shall see that these two stages of updating rnk and updating μk correspond respectively to the E (expectation) and M (maximization) steps of the EM algorithm.
Consider the determination of the rnk. Because J is a linear function of rnk, the optimization gives a closed form solution. The n terms are independent and so we optimize for each n separately by choosing rnk to be 1 for whichever value of k gives the minimum value of ||xn−μk||22. In other words, we simply assign each data point to its closest cluster centre or more formally,
rnk={1ifk=argminj||xn−μj||220otherwiseThen, consider the optimization of μk, while keeping rnk fixed. The objective function is a quadratic function of μk, and it can be minimized by setting its derivative with respect to μk to zero giving,
∂J∂μk=0⇔∂∂μkN∑n=1K∑k=1rnk||xn−μk||22=0⇔2N∑n=1rnk(xn−μk)=0⇔N∑n=1rnkxn−N∑n=1rnkμk=0⇔N∑n=1rnkxn=N∑n=1rnkμk⇔μk=∑Nn=1rnkxn∑Nn=1rnkThe denominator is equal to the number of points assigned to cluster k, and thus, μk equal to the mean of all of the data points xn assigned to cluster k. For this reason, the procedure is known as the K-means algorithm.
Because each phase reduces the value of the objective function, convergence of the algorithm is assured. However, keep in mind, that it may converge to a local rather than global minimum.
Below the K-means algorithm is applied on the Old Faithful data set:
old_faithful = load_old_faithful()
plt.scatter(old_faithful[:, :1], old_faithful[:, 1:2], color="springgreen", facecolors="none", s=50)
plt.xlim(1, 6)
plt.ylim(40, 100)
plt.show()
model = KMeans(2)
model.fit(old_faithful, n_iter=10)
classes = model.predict(old_faithful)
plt.scatter(
old_faithful[:, :1], old_faithful[:, 1:2], edgecolors=np.where(classes == 0, "blue", "red"), facecolors="none", s=50
)
plt.scatter(model.centers[:, 0], model.centers[:, 1], color=["blue", "red"], marker="X", s=250, edgecolors="black")
plt.xlim(1, 6)
plt.ylim(40, 100)
plt.show()
plt.figure(figsize=(10, 5))
# even are E-steps and odd are M-steps
for i, (centers, assignments) in enumerate(model.history):
plt.subplot(2, 2, i + 1)
plt.scatter(
old_faithful[:, :1],
old_faithful[:, 1:2],
edgecolors=np.where(assignments == 0, "blue", "red"),
facecolors="none",
s=50,
)
plt.scatter(centers[:, 0], centers[:, 1], color=["blue", "red"], marker="X", s=250, edgecolors="black")
plt.xlim(1, 6)
plt.ylim(40, 100)
if i == 0:
plt.title("E-step (cluster assignments)")
elif i == 1:
plt.title("M-step (centers computation)")
The initial values for the cluster centres are choosen to be equal to a random subset of K data points. It is also worth noting that the K-means algorithm itself is often used to initialize the parameters in a Gaussian mixture model before applying the EM algorithm.
The implementation of the K-means algorithm as discussed here can be relatively slow, because for the E-step it is necessary to compute the Euclidean distance between every prototype vector and every data point. Several schemes have been proposed for speeding up K-means, some of which are based on precomputing a data structure (e.g., k-d tree) such that nearby points are in the same subtree. Other approaches make use of the triangle inequality for distances, thereby avoiding unnecessary distance calculations.
The batch version of K-means requires for the whole dataset to be used for updating the prototype vectors. We can also derive an on-line stochastic algorithm by applying the Robbins-Monro procedure to the problem of finding the roots of the regression function given by the derivatives of J with respect to μk. Decomposing the batch objective function, we obtain,
JN=JN−1+K∑k=1rNk||xN−μk||22Therefore, in the E-step, the N-th data point is still assigned to the closest center. Suppose that is μm. Thus, the expression JN becomes,
JN=JN−1+||xN−μm||22For the M-step, setting the derivative of JN with respect to μk equal to 0, gives,
∂J∂μk=0⇔∂JN−1∂μk+∂||xN−μm||22∂μk=0k=m⇔∂JN−1∂μm+∂||xN−μm||22∂μm=0⇔2N−1∑n=1rnm(xn−μm)+2(xN−μm)=0⇔N−1∑n=1rnm(xn−μm)+xN−μm=0⇔N−1∑n=1rnmxn−N−1∑n=1rnmμm+xN−μm=0⇔N−1∑n=1rnmxn+xN=N−1∑n=1rnmμm+μm⇔N−1∑n=1rnmxn+xN=μm(N−1∑n=1rnm+1)⇔μm=∑N−1n=1rnmxn+xN∑N−1n=1rnm+1Then, we may further decompose the update formula as follows,
μ(τ)m=∑N−1n=1rnmxn+xN∑N−1n=1rnm+1=∑N−1n=1rnmxn∑N−1n=1rnm+xN∑N−1n=1rnm1+1∑N−1n=1rnm=μ(τ−1)m+xN∑N−1n=1rnm1+1∑N−1n=1rnm=μ(τ−1)m+xN∑N−1n=1rnm−μ(τ−1)m∑N−1n=1rnm1+1∑N−1n=1rnm=μ(τ−1)m+xN−μ(τ−1)m1+1∑N−1n=1rnmThis leads to a sequential update in which, for each data point xn in turn, we update the nearest prototype μk using,
μ(τ)k=μ(τ−1)k+ηn(xn−μ(τ−1)k)where ηn is a learning rate parameter that decreases monotonically as more data points are accumulated.
model = KMeans(2)
indices = list(range(len(old_faithful)))
np.random.shuffle(indices)
for i in indices:
model.update(old_faithful[i, :])
classes = model.predict(old_faithful)
plt.scatter(
old_faithful[:, :1], old_faithful[:, 1:2], edgecolors=np.where(classes == 0, "blue", "red"), facecolors="none", s=50
)
plt.scatter(model.centers[:, 0], model.centers[:, 1], color=["blue", "red"], marker="X", s=250, edgecolors="black")
plt.xlim(1, 6)
plt.ylim(40, 100)
plt.show()
plt.figure(figsize=(10, 5))
plot_idx = 0
for i, (centers, assignments) in enumerate(model.history):
if i in [1, 10, 50, 100, 200, 250]:
plot_idx += 1
plt.subplot(2, 3, plot_idx)
plt.scatter(
old_faithful[:, :1],
old_faithful[:, 1:2],
s=25,
)
plt.scatter(centers[:, 0], centers[:, 1], color=["blue", "red"], marker="X", s=250, edgecolors="black")
plt.xlim(1, 6)
plt.ylim(40, 100)
plt.title(f"Step {i}")
plt.tight_layout()
The K-means algorithm is typically based on the squared Euclidean distance for measuring the distance between a data point and a prototype vector. This limits the type of data variables that can be considered (is inappropriate for cases where some or all of the variables represent categorical labels for instance), but it also makes the determination of the cluster means non-robust to outliers. K-means algorithm can be generalized by introducing a more general dissimilarity measure V(x,x′) and then minimizing the following distortion measure,
˜J=N∑n=1M∑m=1rnkV(xn,μk)which gives the K-medoids algorithm. For a general choice of dissimilarity measure, the M step is potentially more complex than for K-means, and so it is common to restrict each cluster prototype to be equal to one of the data vectors assigned to that cluster. Thus, the M step involves, for each cluster k, a discrete search over the Nk points assigned to that cluster, which requires O(N2k) evaluations.
Note that the K-means algorithm assigns every data point uniquely to one, and only one, of the clusters. However, there may be some data points that lie roughly midway between cluster centres. In this case, it is not clear that the hard assignment to the nearest cluster is the most appropriate. By adopting a probabilistic approach, we obtain soft assignments of data points to clusters, reflecting the level of uncertainty over the most appropriate assignment. This probabilistic formulation brings numerous benefits.
As an illustration of the K-means algorithm, consider the problem of image segmentation. The goal is to partition an image into regions each of which has a reasonably homogeneous visual appearance or which corresponds to objects or parts of objects. Each pixel in an image is a point in a 3-dimensional space comprising the intensities of the red, blue, and green channels. The segmentation algorithm treats each pixel as a separate data point. We present the result of running K-means, by re-drawing the image replacing each pixel vector with the RGB intensity triplet given by the centre to which that pixel has been assigned.
from PIL import Image
im = Image.open("../images/gordon_freeman.jpg")
plt.imshow(im)
plt.show()
Note that for a given value of K, the algorithm is representing the image using a palette of only K colours. It should be emphasized that the use of K-means is not a particularly sophisticated approach to image segmentation, because it takes no account of the spatial proximity of different pixels.
im_array = np.asarray(im)
pixels = im_array.reshape(-1, 3)
plt.figure(figsize=(10, 5))
for i, k in enumerate([2, 3, 10]):
model = KMeans(k)
model.fit(pixels)
segments = model.predict(pixels)
segmented_im = model.centers[segments].astype(int).reshape(im_array.shape)
plt.subplot(1, 3, i + 1)
plt.imshow(segmented_im)
plt.title(f"$K={k}$")
In Chapter 2 we discussed the Gaussian mixture model as a simple linear superposition of Gaussian components, leading to a richer class of multi-modal density models than the single Gaussian. Here we turn to a formulation of Gaussian mixtures in terms of discrete latent variables, which motivates the expectation-maximization algorithm.
The Gaussian mixture distribution can be written as a linear superposition of Gaussians in the form,
p(x)=K∑k=1πkN(x|μk,Σk)Then, we introduce a K-dimensional binary random variable z having a 1-of-K representation. The marginal distribution over z is specified in terms of the mixing coefficients πk, such that,
p(zk=1)=πkBecause z uses a 1-of-K representation, we can also write this distribution in the form
p(z)=K∏k=1πzkkThe conditional distribution of x given a particular value for z is a Gaussian,
p(x|zk=1)=N(x|μk,Σk)or
p(x|z)=K∏k=1N(x|μk,Σk)zkThe joint distribution is given by p(z)p(x|z), and the marginal distribution of x is then obtained by summing the joint distribution over all possible states of z to give,
p(x)=∑zp(z)p(x|z)=∑zK∏k=1πzkkN(x|μk,Σk)zk=K∑k=1πkN(x|μk,Σk)Since the summation over z actually consists of K terms and the k-th term corresponds to zk equal to 1. Moreover, for the k-th term, the product will reduce to πkN(x|μk,Σk).
This equivalent formulation of the Gaussian mixture involving an explicit latent variable, allows to work with the joint distribution p(x,z) instead of the marginal distribution p(x), which leads to significant simplifications, through the introduction of the expectation-maximization (EM) algorithm.
Another quantity that plays an important role is the conditional probability p(z|x). We shall use γ(zk) to denote p(zk=1|x), whose value can be found using Bayes theorem,
γ(zk)=p(zk=1|x)=p(zk=1)p(x|zk=1)∑Kj=1p(zj=1)p(x|zj=1)=πkN(x|μk,Σk)∑Kj=1πjN(x|μj,Σj)which can also be viewed as the responsibility that component k takes for explaining the observation x.
Suppose we are given a data set of observations {x1,…,xN}, and we wish to model the data using a mixture of Gaussians. We can formulate the data set as an N×D matrix X. Similarly, the corresponding latent variables mey be denoted by an N×K matrix Z. Assuming that data points are i.i.d., then the log of the likelihood function for the Gaussian mixture model is given by
lnp(X|π,μ,Σ)=N∑n=1ln{K∑k=1πkN(x|μk,Σk)}Maximizing the log likelihood function for a Gaussian mixture model turns out to be a more complex problem than for the case of a single Gaussian. The difficulty arises from the presence of the summation over k that appears inside the logarithm, so that the logarithm function no longer acts directly on the Gaussian. If we attempt to set the derivatives of the log likelihood to zero, we will no longer obtain a closed form solution.
Keep in mind, that one approach is to apply gradient-based optimization, similar to Mixture Density Networks.
An elegant and powerful method for finding maximum likelihood solutions for models with latent variables is called the expectation-maximization algorithm, or EM algorithm. We shall motivate the EM algorithm through a relatively informal treatment in the context of the Gaussian mixture model.
Keep in mind that a more general treatment of EM exists that has broad applicability. Moreover, EM can be generalized to obtain the variational inference framework.
Lets start by setting the derivatives of log likelihood with respect to the means of the Gaussian components to zero, we obtain,
∂∂μklnp(X|π,μ,Z)=0⇔N∑n=1∂∂μkln{K∑j=1πjN(xn|μj,Σj)}=0⇔N∑n=11∑Kk=1πjN(xn|μj,Σj)∂∂μkK∑j=1πjN(xn|μj,Σj)=0⇔N∑n=11∑Kj=1πjN(xn|μj,Σj)∂∂μkπkN(xn|μk,Σk)=0(ez)′=ezz′⇔−N∑n=1πkN(xn|μk,Σk)∑Kj=1πjN(xn|μj,Σj)Σk(xn−μk)=0⇔−N∑n=1γ(znk)Σk(xn−μk)=0×Σ−1k⇔−N∑n=1γ(znk)(xn−μk)=0⇔N∑n=1γ(znk)μk−N∑n=1γ(znk)xn=0⇔μk=1∑Nn=1γ(znk)N∑n=1γ(znk)xnNote that the mean μk for the k-th Gaussian component is obtained by taking a weighted mean of all of the points in the data set, in which the weighting factor for data point xn is given by the posterior probability γ(znk) that component k was responsible for generating xn.
Next, setting the derivative of the log likelihood with respect to Σk to zero, and making use of the result for the maximum likelihood solution for the covariance matrix of a single Gaussian, we obtain,
∂∂Σklnp(X|π,μ,Z)=0⇔N∑n=1∂∂Σkln{K∑j=1πjN(xn|μj,Σj)}=0⇔N∑n=11∑Kk=1πjN(xn|μj,Σj)∂∂ΣkK∑j=1πjN(xn|μj,Σj)=0⇔N∑n=11∑Kj=1πjN(xn|μj,Σj)∂∂ΣkπkN(xn|μk,Σk)=0(ez)′=ezz′⇔−N∑n=1πkN(xn|μk,Σk)∑Kj=1πjN(xn|μj,Σj)(Σk−(xn−μk)(xn−μk)T)=0⇔−N∑n=1γ(znk)(Σk−(xn−μk)(xn−μk)T)=0⇔N∑n=1γ(znk)(xn−μk)(xn−μk)T−N∑n=1γ(znk)Σk=0⇔Σk=1∑Nn=1γ(znk)N∑n=1γ(znk)(xn−μk)(xn−μk)Twhich has the same form as the corresponding result for a single Gaussian, but data points are weighted by the corresponding posterior probability.
Finally, setting the derivative of the log likelihood with respect to πk to zero, requires for the constraint (9.9) to hold (mixing coefficients should sum to one). This can be achieved using a Lagrange multiplier and maximizing, which gives,
pk=NkNNote that these results do not constitute a closed-form solution for the parameters of the mixture model because the responsibilities γ(znk) depend on those parameters in a complex way through (9.13). However, these results suggest a simple iterative scheme for finding the solution, which turns out to be an instance of the EM algorithm for the particular case of the Gaussian mixture model.
EM for Gaussian Mixtures
We first choose initial values for the means, covariances, and mixing coefficients. Then we alternate between two updates, namely the E-step and the M-step. In the expectation (E) step, we use the current values for the parameters to evaluate the posterior probabilities or responsibilities. Then, in the maximization (M) step, we use these probabilities to to re-estimate the means, covariances, and mixing coefficients. Note that for the M-step, we first evaluate the new means, and then use them to find the covariances.
model = MultivariateGaussianMixture(n_components=2)
model.ml(old_faithful)
x, y = np.meshgrid(np.linspace(1, 6, 100), np.linspace(40, 100, 100))
p = np.diag(model.pdf(np.array([x, y]).reshape(2, -1))).reshape(100, 100)
colors = [
(a / np.linalg.norm([a, b]), 0, b / np.linalg.norm([a, b]))
for a, b in zip(*[w * np.diag(c.pdf(old_faithful.T)) for w, c in zip(model._coefficients, model._components)])
]
plt.scatter(old_faithful[:, :1], old_faithful[:, 1:2], c=colors, facecolors="none", s=50)
plt.contour(x, y, p, linewidths=3, colors="black", levels=[0.01])
plt.xlim(1, 6)
plt.ylim(40, 100)
plt.xlabel("Eruption duration (mins)", fontsize=14)
plt.ylabel("Time until next eruption (mins)", fontsize=14)
plt.show()
Note that EM takes much more iterations to converge compared to K-means, and each iteration requires significantly more computation. Is therefore common to run the K-means algorithm in order to find a suitable initialization for the means of a Gaussian mixture model that is subsequently adapted using EM. The covariance matrices, can be initialized to the sample covariances of the clusters found by K-means, and the mixing coefficients to the fraction of data points assigned to the respective clusters.
The goal of EM is to find the maximum likelihood solution for models having latent variables. In a more general form, the log likelihood is given by
lnp(X|θ)=ln{∑zp(X,Z|θ)}where X denotes the set of all observed data, Z denotes the set of all latent variables, and θ denotes the set of all model parameters. Since the latent variables appear inside the logarithm, the presence of the sum prevents the logarithm from acting directly on the joint distribution, resulting in complicated expressions. Given that the latent variables are not known, our state of knowledge about their values Z is only given by their posterior distribution p(Z|X,θ).
According to EM, given a current estimate for the parameters θold, then a pair of succesive E and M steps gives rise to a revised estimate θnew. In the E-step, we use the current estimate θold to define the posterior distribution of the latent variables given by p(Z|X,θold). Then, we use the posterior to find the expectation of the complete-data log-likelihood evaluated for some general parameter value θ,
Q(θ,θold)=∑zp(Z|X,θold)lnp(X,Z|θ)In the M-step, we determine the revised parameter estimate θnew by maximizing the function,
θnew=argmaxθQ(θ,θold)Note that in the definition of Q(θ,θold), the logarithm acts directly on the joint distribution and so the maximization is tractable. The general EM is summarized below:
The General EM Algorithm
Given a joint distribution p(X,Z|θ) over observed variables X and latent (unobserved) variables Z, governed by unknown parameters θ, the goal is to maximize the likelihood function p(X|θ) with respect to θ.
θnew=argmaxθQ(θ,θold)
- Choose an initial value for the parameters θold.
- E-step: Evaluate p(Z|X,θold).
- M-step: Evaluate θnew given by
θold←θnew
- Check for convergence of either the log likelihood or the parameter values. If the convergence criterion is not satisfied, the let
and return to step 2.
The EM algorithm may also be used to find MAP solutions for models in which a prior p(θ) is defined over the parameters. In such cases, the E-step remains identical, whereas in the M-step the quantity to be maximized is given by Q(θ,θold)+lnp(θ). To see that, according to Bayes’ Theorem, we have that,
p(θ|X)=p(X|θ)p(θ)Furthermore utilizing Eq. (9.29), we obtain,
lnp(θ|X)=ln{[∑zp(X,Z|θ)]p(θ)}Therefore, in the E-step, we still need to calculate the posterior p(Z|X,θold), and then for the M-step, by analogy to Eq (9.30), we obtain,
QMAP(θ,θold)=∑zp(Z|X,θold)ln[p(X,Z|θ)p(θ)]=∑zp(Z|X,θold)[lnp(X,Z|θ)+lnp(θ)]=∑zp(Z|X,θold)lnp(X,Z|θ)+∑zp(Z|X,θold)lnp(θ)=Q(θ,θold)+lnp(θ)∑zp(Z|X,θold)=Q(θ,θold)+lnp(θ)Note that EM can also be applied when the unobserved variables correspond to missing values in the data set. The distribution of the observed values is obtained by taking the joint distribution of all the variables and the marginalizing over the missing ones. However, keep in mind this is a valid approach only if the data values are missing at random, meaning that the mechanism causing the values to be missing does not depend on the unobserved values. For instance, a sensor may fail to return a value whenever the quantity it is measuring exceeds some threshold.