HW5 - Principal Component Analysis and Autoencoders
1. Question 1
Let
- The first
columns of are eigenvectors of corresponding to nonzero eigenvalues. - The first
columns of are eigenvectors of corresponding to nonzero eigenvalues.
1. The Background You Need to Know
To solve this, we need to understand a few fundamental linear algebra rules and definitions:
-
The Transpose (
): Taking the transpose of a matrix simply means swapping its rows and columns. A crucial rule you need to know is that when you take the transpose of matrices multiplied together, you flip their order: . -
Orthogonal Matrices (
and ): The problem states that and are orthogonal matrices. An orthogonal matrix is a special square matrix where its columns (and rows) are perpendicular to each other and have a length of 1. The magic property of an orthogonal matrix is that multiplying it by its transpose results in the Identity matrix (a matrix of all 1s on the diagonal and 0s elsewhere). Mathematically, this means and . Multiplying by is like multiplying by 1 in regular math; it leaves the other matrix unchanged. -
Eigen-Decomposition: If you have a symmetric square matrix
, you can factor it into three parts: . In this specific format, the columns of the matrix exactly represent the eigenvectors of , and the diagonal elements of the middle matrix represent the corresponding eigenvalues. -
The Rank (
): The rank of the matrix simply tells us how many of the singular values ( ) in the diagonal matrix are strictly positive (greater than 0). Any singular values after the -th one are exactly zero. - What exactly is the "Rank" (r)?
- In linear algebra, the rank of a matrix is the maximum number of linearly independent rows (or equivalently the maximum number of linearly independent columns).
- To put it simply, "linearly independent" means that a row (or column) contains truly unique information that cannot be created just by mixing or scaling the other rows. For example, if you have a dataset where column A is "Age in Years" and column B is "Age in Months", column B is completely redundant because it is just column A multiplied by 12. It does not add any new dimension of information, so it does not increase the rank.
- Therefore, the rank (r) simply tells you exactly how many unique, non-redundant dimensions of information are hidden inside your matrix.
- How SVD acts as building blocks
- When you perform SVD on a matrix
, you are decomposing it into three matrices: , , and . - A very intuitive way to look at this mathematical multiplication is that it breaks the original matrix down into a sequence of simpler, "rank-1" matrices summed together. The SVD formula can actually be rewritten as a sum of individual components:
- In this view:
- The terms like
are the structural building blocks (representing specific directions or patterns in your data). - The singular values (
) on the diagonal of the matrix act as the "scales" or "weights" for each of these building blocks.
- The terms like
- When you perform SVD on a matrix
- Why the first
singular values are greater than zero - By mathematical convention, the elements in the
matrix are always non-negative real numbers, and we always arrange them in sorted order from largest to smallest ( ). - Here is where the two concepts perfectly connect:
- If your original matrix has a rank of
, it means it only possesses unique dimensions of information. - Therefore, SVD only needs
building blocks to perfectly reconstruct your matrix. - To "turn on" these
necessary building blocks, the first singular values ( ) must be strictly greater than zero. - Because the matrix has no more unique information left to capture beyond those
dimensions, any remaining building blocks are completely useless. To "turn them off", all subsequent singular values ( ) are set to exactly 0.
- If your original matrix has a rank of
- In fact, the relationship is so direct that the number of positive singular values directly tells you the rank of that matrix.
- Because everything after the
-th singular value is multiplied by zero, they contribute absolutely nothing to the final matrix. This is why in a technique called "Compact SVD", we can literally just throw away all the rows and columns corresponding to the 0 elements without losing a single piece of data.
- By mathematical convention, the elements in the
- What exactly is the "Rank" (r)?
2. Solving Part 1: Proving contains the eigenvectors of
We are asked to look at the matrix
Step 1: Substitute
Step 2: Apply the transpose rule to the second part. Using our rule
Step 3: Put it back together. $$A A^T = U \Sigma V^T V \Sigma U^T$$.
Step 4: Use the orthogonal matrix magic. Notice that
Step 5: The Conclusion. Look at our final equation:
3. Solving Part 2: Proving contains the eigenvectors of
This part is almost perfectly identical to Part 1, but in reverse. We will look at
Step 1: Substitute
Step 2: Apply the transpose rule to the first part. Just like before,
Step 3: Use the orthogonal matrix magic. This time, we have
Step 4: The Conclusion. Once again,
2. Question 2
Given a symmetric matrix
What is the singular value decomposition of this matrix?
This is a fantastic question that perfectly bridges the gap between Eigen-decomposition and Singular Value Decomposition (SVD). It tests a very specific and fundamental rule of linear algebra.
1. Identifying What We Have
First, let's look at the equation you were given. Because the matrix
is the orthogonal matrix on the left containing the eigenvectors. (Lambda) is the diagonal matrix in the middle containing the eigenvalues: 3, -2, and 1. is the transpose of the first matrix on the right.
2. The Core Problem: Eigen-Decomposition vs. SVD
The question asks you to convert this into a Singular Value Decomposition (SVD).
Recall that the formula for SVD is: $$A = U \Sigma V^T$$Where
At first glance, the given eigen-decomposition
- Non-Negative Rule: All singular values on the diagonal of
must be non-negative real numbers (greater than or equal to zero). Our current matrix has a -2, which is illegal in SVD. - Sorted Rule: The singular values must be sorted in descending order from largest to smallest (
).
If a symmetric matrix is "positive semi-definite" (meaning all its eigenvalues are naturally positive), its eigen-decomposition and its SVD are perfectly identical. But because of that -2, we have to do a little bit of math to fix it.
3. The Mathematical Trick (Absorbing the Negative)
We need to turn that -2 into a +2 to satisfy the SVD rules. However, we cannot just erase a negative sign, because that would completely change the underlying matrix
Instead, we use a simple linear algebra trick: we factor the negative sign out of the diagonal matrix and absorb it into one of the other matrices.
In an eigen-decomposition (
Here is the step-by-step math: We can split our diagonal matrix
Now, let's substitute this back into the original equation: $$A = Q (\Sigma D) Q^T$$Because matrix multiplication is associative, we can group the sign matrix
4. Constructing the Final SVD Answer
Now we simply map our new pieces to the SVD format
- The
Matrix: This remains exactly the same as the original matrix. - The
Matrix: This is our new diagonal matrix with strictly positive values. Conveniently, the values 3, 2, and 1 are already in the correct sorted descending order ( )! - The
Matrix: This is the product of . Multiplying a matrix by a diagonal matrix of signs simply flips the signs of the corresponding rows. Because the -1 is in the second row of , it will multiply every element in the second row of by -1. (Note: Changing the sign of an entire row of an orthogonal matrix keeps it completely orthogonal, so is still a valid orthogonal matrix!).
Answer
You can write out your final answer clearly like this:
"To find the Singular Value Decomposition
Where
3. Question 3
In this homework, you will apply the principal component analysis to a collection of handwritten digit images from the USPS dataset. The USPS dataset is in the "data" folder: USPS.mat. The starting code is in the "code" folder. The whole data has already been loaded into the matrix A. The matrix A has shape
3.1 do_pca()
- (In Code) Complete the _do_pca() method. Your code will be tested on
, a total of four different numbers of the principal components.
I would be happy to help you implement your PCA (Principal Component Analysis) code and explain how it works from scratch!
PCA can seem intimidating, but at its core, it is just a method for dimensionality reduction. In your dataset, each handwritten digit image is
Here is the step-by-step breakdown of how PCA works from scratch, how the math aligns with your class notes, and exactly how to implement it in your code.
1. How PCA Works (The Theory)
Step A: Center the Data If we want to find the directions where the data varies the most, we first need to center the data around the origin
Step B: Find the Covariance and Eigenvectors Next, we need to find the "Principal Components." These are mathematically defined as the eigenvectors of the covariance matrix of your data.
- A covariance matrix tells us how much the pixels vary with one another.
- The eigenvectors of this matrix give us the best directional axes to project our data onto to maximize variance.
Step C: The SVD Shortcut Computing a huge covariance matrix and finding its eigenvectors directly is computationally very expensive. However, linear algebra gives us a magical shortcut: Singular Value Decomposition (SVD). If we take the SVD of our centered data matrix (
Step D: Truncate and Project SVD will give us 256 components, sorted from most important to least important. To reduce the dimensionality, we simply throw away the less important ones and keep the first
2. Implementing _do_pca(self)
In your class notes, the data matrix [features, samples]. However, in Python/NumPy and your assignment instructions, self.X is transposed to have shape [n_samples, n_features] (3000 x 256). We just have to adapt our NumPy code to match this shape!
Here is the code to place inside your _do_pca method:
def _do_pca(self):
'''
To do PCA decomposition.
Returns:
Up: Principal components (transform matrix) of shape [n_features, n_components].
Xp: The reduced data matrix after PCA of shape [n_samples, n_components].
'''
### YOUR CODE HERE
# 1. Center the Data
# Calculate the mean of each feature (column) across all samples.
mean_vec = np.mean(self.X, axis=0)
# Subtract the mean from the data to center it.
# (Numpy automatically broadcasts this subtraction across all rows)
X_centered = self.X - mean_vec
# 2. Compute the Singular Value Decomposition (SVD)
# SVD decomposes X_centered into U, S, and V^T
# full_matrices=False is standard practice to save memory
U, S, Vt = np.linalg.svd(X_centered, full_matrices=False)
# 3. Extract the Principal Components (Up)
# np.linalg.svd returns V-transpose (Vt). We need to transpose it back to V.
V = np.transpose(Vt)
# The principal components are the first 'n_components' columns of V
Up = V[:, :self.n_components]
# 4. Project the Data (Xp)
# Multiply the centered data by the principal components to reduce dimensionality
Xp = np.dot(X_centered, Up)
return Up, Xp
### END YOUR CODE
3. Detailed Code Explanation
np.mean(self.X, axis=0): This collapses the 3,000 samples and returns an array of 256 values, representing the "average" digit image.self.X - mean_vec: This subtracts the average image from every single image in the dataset, effectively centering our data at zero. (Note: The README hints at usingnp.ones(), which is a more manual, mathematical way to broadcast the subtraction, e.g.,self.X - np.dot(np.ones((n_samples, 1)), [mean_vec]). However, standard NumPy broadcastingself.X - mean_vecachieves the exact same thing much more efficiently).np.linalg.svd(...): This applies the SVD shortcut discussed in your notes. It returnsU,S(the singular values), and Vt(). V = np.transpose(Vt): Because SVD natively outputs the transpose of V (), we must transpose it back to to access the eigenvectors as columns. V[:, :self.n_components]: This is where the dimensionality reduction actually happens. The notation:means "take all 256 feature rows", and:self.n_componentsmeans "only take the firstpcolumns".np.dot(X_centered, Up): Finally, we take our3000 x 256dataset and matrix-multiply it by our256 x pcomponent matrix. The inner dimensions cancel out, leaving us with a reduced datasetXpof shape3000 x p!
Once you implement this and run python main.py, the test_pca() function will loop through p = 10, 50, 100, 200 to automatically test your implementation and save reconstructed images of the handwritten digits so you can visualize how much data is preserved at different compression sizes! Let me know if you need help with the reconstruction error function next!
3.2 reconstruction()
Complete the reconstruction() method to reconstruct the reduced data.
1. What is the Reconstruction Process?
In the _do_pca function, we took 256-dimensional images and compressed them down into a much smaller number of dimensions (like 10 or 50). This compressed data is your Xp matrix (which your class notes refer to as
The reconstruction method is the "decompression" step. We want to take those 10 numbers and blow them back up into the original 256 pixels so we can actually look at the image and see how much quality we lost during the compression.
2. The Math Behind Reconstruction
According to your class notes, if your principal components matrix is
Because our data matrices in Python are transposed (the samples are rows, not columns), the order of our multiplication flips:
- The Projection: We multiply our reduced data
Xpby the transpose of our principal componentsself.Up. This projects our 10-dimensional data back into the 256-dimensional space. - Un-centering: There is one crucial final step. Remember that in
_do_pca, we subtracted the "average image" (the mean) from every single image to center our data at the origin. Because PCA only reconstructs this centered data, we must add that exact same mean back to shift the pixels back to their original brightness levels.
3. Implementing the Code
Here is the step-by-step code you can paste into your solution.py file:
def reconstruction(self, Xp):
'''
To reconstruct reduced data given principal components Up.
Args:
Xp: The reduced data matrix after PCA of shape [n_samples, n_components].
Return:
X_re: The reconstructed matrix of shape [n_samples, n_features].
'''
### YOUR CODE HERE
# 1. Transpose the principal components matrix
# self.Up has shape [n_features, n_components].
# We transpose it to [n_components, n_features] for multiplication.
Up_T = np.transpose(self.Up)
# 2. Decompress the data
# We multiply the reduced data (Xp) by the transposed components (Up_T).
# Shape math: [n_samples, n_components] @ [n_components, n_features] = [n_samples, n_features]
X_re_centered = np.dot(Xp, Up_T)
# 3. Calculate the mean of the original data
# We need the original mean to un-center our reconstructed data
mean_vec = np.mean(self.X, axis=0)
# 4. Add the mean back
# This restores the original pixel intensity values
X_re = X_re_centered + mean_vec
return X_re
### END YOUR CODE
What is happening at each step?
np.transpose(self.Up): We take ourself.Upmatrix (which holds our best directional axes) and transpose it so the matrix multiplication aligns properly.np.dot(Xp, Up_T): We matrix-multiply the compressed data by the axes. This takes the coordinates in the small space and maps them back to the coordinates in the large, 256-dimensional pixel space.np.mean(self.X, axis=0): We recalculate the average digit image across all 3,000 samples.X_re_centered + mean_vec: By adding the average image back, we reverse the centering step we performed during encoding. If we forgot this step, our reconstructed images would have negative pixel values and would look completely wrong when we tried to save them!
3.3 reconstruct_error()
Complete the reconstruct_error() function to measuring the reconstruction error.
1. What is the Reconstruction Error?
When we performed PCA in the _do_pca step, we threw away the less important principal components to compress our images from 256 pixels down to a smaller number like 10 or 50. Because we threw away information, the reconstructed images will not be 100% identical to the original images.
The reconstruction error is a mathematical way to measure exactly how much information we lost. We do this by comparing the original image matrix (A) directly against the decompressed/reconstructed image matrix (B). We want to know the total difference between them.
2. The Math: What is the "Square of the Frobenius Norm"?
The assignment asks you to return the square of the Frobenius norm of the matrix
While the name sounds intimidating, the Frobenius norm is simply the matrix equivalent of the standard Euclidean distance (the Pythagorean theorem) used for vectors. Here is how it breaks down in plain English:
: You subtract the reconstructed matrix from the original matrix. This gives you a new "difference" matrix containing the exact pixel-by-pixel errors. - The Frobenius Norm (
): You square every single individual error value in that difference matrix, sum all of those squared values together, and then take the square root of that total sum. - The Square (
): The instructions explicitly ask for the square of the Frobenius norm. This simply means we cancel out the square root from the previous step. We are just left with the total sum of the squared pixel errors across the entire dataset.
3. Implementing the Code
In your assignment's Readme.txt file, your professor provided a hint to use the helper function np.linalg.norm(). By default, when you pass a 2D matrix into this NumPy function, it automatically calculates the Frobenius norm for you.
def reconstruct_error(A, B):
'''
To compute the reconstruction error.
Args:
A & B: Two matrices needed to be compared with. Should be of same shape.
Return:
error: the Frobenius norm's square of the matrix A-B. A scaler number.
'''
### YOUR CODE HERE
# 1. Calculate the difference matrix between the original (A) and reconstructed (B) data
difference_matrix = A - B
# 2. Compute the Frobenius norm of this difference matrix
# np.linalg.norm calculates the Frobenius norm by default for 2D arrays
frobenius_norm = np.linalg.norm(difference_matrix)
# 3. Square the Frobenius norm as requested by the instructions
error = frobenius_norm ** 2
return error
### END YOUR CODE
What happens when you run this?
Now that all three functions (_do_pca, get_reduced, and reconstruct_error) are complete, you can run python main.py in your terminal!
The test_pca() function in main.py will automatically pass your original 3000 images (A) into your PCA class, compress them down to p=10, 50, 100, and 200 components, reconstruct them (A_re), and print the reconstruction error using this final function you just wrote.
As you watch the terminal output, you should see that as the number of principal components (
3.4 Report
Run "main.py" to see the reconstruction results and summarize your observations from the results into a short report. When you run the "main.py" file, a subset (the first two) of the reconstructed images based on
PCA Image Reconstruction Report
1. Quantitative Observations (Reconstruction Error) When applying Principal Component Analysis (PCA) to the USPS dataset (which consists of
Based on the algorithm's output, the squared Frobenius norm of the reconstruction errors are:
: 155,351.47 : 41,024.86 : 14,285.81 : 1,371.41
Mathematical Justification: This behavior perfectly aligns with the mathematical theory of PCA. The goal of PCA is to project high-dimensional features to a lower-dimensional space while simultaneously minimizing the reconstruction error. When we reconstruct the data using the first
2. Qualitative Observations (Visual Image Quality) (Note: Attach your generated images for
The mathematical drop in reconstruction error is directly reflected in the visual quality of the reconstructed digit images:
- At
: The images are highly compressed. Because we are only keeping 10 out of 256 dimensions, the reconstruction only captures the most dominant statistical features of the digits (like overall intensity and basic stroke location). The resulting images will look highly blurry, blocky, and lack fine details. - At
and : As we retain more principal components, the digits become much more distinguishable and the edges become sharper. The structural shape of the specific handwritten numbers is mostly restored. - At
: Because 200 components is very close to the original 256 dimensions of the data, nearly all of the original variance is retained. The reconstructed images at this stage will appear exceptionally sharp and nearly identical to the original uncompressed data, which corresponds to the very small reconstruction error of .
3. Conclusion The results illustrate the fundamental approximation-generalization tradeoff of PCA dimensionality reduction. By keeping only the first