What happens in the E step and the M step when EM fits a Gaussian mixture?
answer
- the labels are the missing data
- one step fixes parameters, one fixes weights
- responsibility-weighted sample formulas
- effective counts need not be integers
- monotone, but only to a local optimum
basics
~20 sThe E step fixes the parameters and computes each point's responsibility: the posterior probability that each component generated it. The M step fixes those responsibilities and re-estimates every component's weight, mean and covariance as responsibility-weighted averages.
solid answer
~50 sEM alternates two closed-form steps because the component labels are hidden, so maximum likelihood has no direct solution. **E step**: with the current weights, means and covariances fixed, compute `r_ik = pi_k * N(x_i | mu_k, Sigma_k)` normalised over components — a soft assignment of every point to every component. **M step**: with the responsibilities fixed, each component gets `N_k = sum_i r_ik` effective points, and you set `pi_k = N_k / N`, `mu_k = (1/N_k) * sum_i r_ik * x_i`, and `Sigma_k = (1/N_k) * sum_i r_ik * (x_i - mu_k)(x_i - mu_k)^T`. These are the ordinary sample formulas with each row weighted by its responsibility. Each full sweep provably never decreases the observed-data log-likelihood, so it converges — but to a local optimum, which is why the result depends on initialisation and multiple runs are standard.
code
python · 26 linesimport math
xs = [1.0, 1.5, 4.0, 4.5, 5.0]
# current parameters per component: (weight, mean, variance)
params = [(0.5, 1.0, 1.0), (0.5, 5.0, 1.0)]
def density(x, mu, var):
return math.exp(-((x - mu) ** 2) / (2 * var)) / math.sqrt(2 * math.pi * var)
# E step: responsibility of each component for each point
resp = []
for x in xs:
weighted = [w * density(x, mu, var) for (w, mu, var) in params]
total = sum(weighted)
resp.append([v / total for v in weighted])
# M step: re-estimate weight, mean, variance from those responsibilities
new_params = []
for k in range(2):
nk = sum(r[k] for r in resp)
mu = sum(r[k] * x for r, x in zip(resp, xs)) / nk
var = sum(r[k] * (x - mu) ** 2 for r, x in zip(resp, xs)) / nk
new_params.append((nk / len(xs), mu, var))
print([[round(v, 3) for v in r] for r in resp])
print([tuple(round(v, 3) for v in p) for p in new_params])go deeper
Recall the loop shape: guess which component made each point, refit the components to those soft guesses, repeat. Know that the assignment is probabilistic and that the algorithm stops when the fit stops improving.
Write down the responsibility formula and the three M-step updates, and say explicitly what is held fixed in each step. State the guarantee correctly: the log-likelihood never decreases, and convergence is to a local optimum.
Talk about operating it — multiple initialisations, tracking the log-likelihood as a health metric, log-space computation to avoid underflow, and treating a decreasing likelihood as a bug rather than noise.
Frame the cost side: EM converges linearly and scales with points times components times dimension per sweep. Decide when the soft model earns that over a cheaper hard partition, and what the retraining and reproducibility story is.
## Why an iterative algorithm at all The log-likelihood of a Gaussian mixture is ``` log L = sum_i log( sum_k pi_k * N(x_i | mu_k, Sigma_k) ) ``` The sum sits *inside* the logarithm. That is what breaks the usual trick of setting derivatives to zero and solving: the parameters of every component are tangled together in every term. If you knew which component produced each point, the problem would collapse — you would just fit one Gaussian per group with the ordinary sample mean and sample covariance. Expectation-Maximisation exploits exactly that: it invents the missing labels probabilistically, then does the easy fit. ## The E step: expectation Hold the current parameters `(pi, mu, Sigma)` fixed. For every point compute the posterior over components: ``` r_ik = pi_k * N(x_i | mu_k, Sigma_k) / sum_j ( pi_j * N(x_i | mu_j, Sigma_j) ) ``` That is the whole E step: one number per point per component, each row summing to 1. Formally, you are computing the expected value of the hidden indicator variable that says which component produced point `i` — hence "expectation". Nothing about the model changes here; only your belief about the hidden labels does. Practical note: the densities can underflow to zero in high dimensions, so real implementations work in log space and subtract the row maximum before exponentiating. That is a numerical detail, not a change to the algorithm. ## The M step: maximisation Now freeze the responsibilities and maximise. Because the responsibilities are treated as known weights, the maximisation decouples across components and every update has a closed form. Define the **effective count** ``` N_k = sum_i r_ik ``` which is how many points component `k` owns, counted fractionally — it need not be an integer, and the counts sum to `N`. Then: ``` pi_k = N_k / N mu_k = (1 / N_k) * sum_i r_ik * x_i Sigma_k = (1 / N_k) * sum_i r_ik * (x_i - mu_k)(x_i - mu_k)^T ``` Every one of these is the familiar sample estimator with each observation weighted by how much this component claims it. A point with responsibility 0.6 contributes 0.6 of a row to that component's mean and covariance. Use the *new* `mu_k` when forming the covariance. The order matters — weights, then means, then covariances about those means. ## The convergence guarantee, stated precisely Each full E-then-M sweep is guaranteed **never to decrease** the observed-data log-likelihood. This is the key theoretical property and the thing candidates most often overstate. What it does *not* say: - It does not say the likelihood strictly increases — at a fixed point it stays put. - It does not say you reach the global maximum. EM climbs to a local optimum or a saddle point, and mixture likelihoods are riddled with local optima. - It does not guarantee fast convergence. EM converges linearly and can crawl when components overlap heavily. Because of the local-optimum problem, you run EM several times from different starting parameters and keep the run with the highest final log-likelihood. Monitoring is straightforward: print the log-likelihood each iteration and stop when its improvement falls below a tolerance, or when a maximum iteration count is hit. If you ever see the log-likelihood *drop*, the implementation is buggy — that is a genuine invariant, not a heuristic. ## A worked 1-D trace Take five points `1.0, 1.5, 4.0, 4.5, 5.0` and start with two components at means 1.0 and 5.0, both with variance 1 and weight 0.5. The E step gives the two leftmost points responsibilities of essentially 1 on component 1 and the three rightmost essentially 1 on component 2 — the starting means are far apart, so the posterior is already sharp. The M step then pulls component 1's mean to about 1.28 with weight about 0.40, and component 2's to 4.5 with weight about 0.60, and shrinks both variances to match the spread each component now owns. One more sweep and the split has essentially settled. The attached snippet performs exactly this, in plain arithmetic. ## The relationship to hard partitioning If you replace the E step's soft responsibilities with a hard 1-for-the-best-component, 0-otherwise assignment and constrain all components to share one spherical covariance, the M step's weighted means become plain group means and the algorithm becomes the classic assign-then-recentre loop. EM is the soft, probabilistic generalisation: same alternating structure, but the assignment is a posterior rather than a winner-take-all, and the M step estimates covariance shape as well as location. ## What interviewers listen for The complete answer names both steps, states what is *held fixed* in each, gives the responsibility formula and at least the weighted-mean update, and gets the guarantee right — monotone non-decreasing likelihood, local optimum, initialisation-dependent. Saying "EM finds the maximum likelihood solution" without the word *local* is the single most common slip.
- EM is guaranteed to increase the likelihood — is that a guarantee of finding the best fit?No. The guarantee is that each sweep never *decreases* the observed-data log-likelihood, which makes the sequence monotone and convergent. Where it converges is a local optimum or a saddle point, and mixture likelihoods have many. That is why you run several initialisations and keep the highest final log-likelihood, and why a fit that looks poor is often an initialisation problem rather than a modelling one.
- Why must the covariance update use the newly computed mean rather than the old one?The M step maximises the expected complete-data log-likelihood jointly, and the maximising covariance is the responsibility-weighted scatter about the maximising mean. Using the stale mean measures spread about the wrong centre, inflates the covariance, and breaks the monotonicity guarantee — you can then see the log-likelihood fall between iterations, which is the classic symptom of this bug.
- What is a sensible stopping rule for EM?Track the observed-data log-likelihood each sweep and stop when its increase falls below a small tolerance, with a hard cap on iterations as a backstop. Relative rather than absolute tolerance travels better across datasets. Also assert the log-likelihood never decreases: if it does, you have an implementation bug, not a convergence issue.
- Can the effective count N_k for a component be a non-integer like 12.4?Yes, and it usually is. `N_k` is the sum of fractional responsibilities, so it measures how many points the component owns in expectation. That is what makes the M step's weighted averages sensible. A component whose effective count drifts toward zero is dying out and is worth inspecting — it often signals too many components or an incipient degenerate fit.
saying these in an interview costs you the question
- Says EM finds the global maximum likelihood solution
- Describes the E step as assigning each point to its nearest component
- Claims the M step recomputes responsibilities as well
- Uses unweighted sample means, ignoring the responsibilities
- Computes the covariance about the previous iteration's mean
- Asserts the log-likelihood strictly increases every iteration