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: Playing around with PCA on MNIST data.

We’re going look at Principal Component Analysis and the MNIST data today, and an analogy with some of the block transform and image compression stuff we’ve done before.

MNIST is 70,000 small (28×2828 \times 28) grayscale images of handwritten digits, which we’ll use a lot in the machine learning chapter. In the block transform chapter we saw fixed bases (Haar, DCT) for representing image blocks. Here we let PCA learn a basis from 60,000 example digits, and see how few basis images it takes to represent a digit well. (If you haven’t, see PCA in 2D first.)

Import

A lot. We need MNIST data, PCA, and all of our block transform materials.

Loading and Formatting the MNIST Data

torchvision downloads MNIST into your data folder the first time (see data_paths). We then flatten every 28×2828 \times 28 image into a row of 784 numbers in [0,1][0,1]: x_train is 60000×78460000 \times 784 and x_test is 10000×78410000 \times 784.

PCA

We’ll do a PCA analysis of the MNIST training set to see its power on this kind of data. Let’s use pca_spanFaces as a guide. Here each row of x_train for example represents a 28×2828\times28 image. PCA won’t know anything about that, and will just think we have 60K points in a 784-D space.

Here we’ll ask for the 64 most important 784-d vectors in the space of 60K mnist example images. Remember, these are directions that best explain the differences between the mnist images.

These are the 64 directions of greatest variation among the digits, as 28×2828 \times 28 images, in order of importance (left to right, top to bottom). Gray is zero, with lighter and darker showing positive and negative weights. The first few are broad, blobby strokes, like the difference between a “0”-like ring and a “1”-like vertical bar. Later components get finer and more detailed, much like the progression from coarse to fine patterns in the Haar and DCT bases. The difference is that these patterns were learned from the data, so they look like pieces of digits.

PCA also centers the data, so every digit is represented as the mean digit plus a weighted sum of these components.

Expected output (what the cell above should show, if you can’t run it)
expected output

The mean digit is a fuzzy blur of all ten digits on top of each other. The variance curve rises steeply and then flattens: the first 10 components (out of 784 possible) already capture nearly half of all the variation among the digits, and 64 capture well over 80%.

Reconstruction with a few components

Let’s see what that means visually. We’ll take some test digits, which PCA never saw during fitting, project them onto the first kk components, and reconstruct: x≈μ+∑i<kcivi\mathbf{x} \approx \boldsymbol{\mu} + \sum_{i<k} c_i \mathbf{v}_i.

Expected output (what the cell above should show, if you can’t run it)
expected output

With a single component every digit is a variation on the blurry mean. By 16 components most digits are recognizable, and by 64 they’re quite clear, though still a little soft. That’s 64 numbers per digit instead of 784, more than a 12×\times reduction. This is transform coding again, exactly as with JPEG, but with a basis tailored to the data.

The digits in PCA space

Finally, since the first two coefficients are the two most informative numbers about each digit, we can scatter every test digit in the plane by its first two coefficients, colored by its true label.

Expected output (what the cell above should show, if you can’t run it)
expected output

Some digits occupy their own regions. The 0s and 1s, for example, sit at opposite ends of the first component, and remember PCA was never told the labels. Others overlap heavily in these two dimensions. Two numbers per digit just aren’t enough to separate all ten classes. That’s a big part of why we’ll turn to classifiers that learn which directions matter for telling digits apart, rather than for describing them, which is all PCA optimizes.