Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 54

PCA with NumPy's SVD

Recommended resources

Welcome back. In our previous lesson, we established a crucial theoretical result: the principal components of a dataset are the eigenvectors of its covariance matrix. This provided a clear "recipe" for PCA.

Today, we transition from theory to practice. While the eigendecomposition of the covariance matrix is a valid approach, modern numerical methods often use a more direct and robust technique: Singular Value Decomposition (SVD).

This lesson will focus on implementing PCA from scratch using SVD. By the end, you will not only understand the deep connection between SVD and PCA but will also have built your own PCA implementation in Python using NumPy's SVD function.

1. Why SVD? A More Stable Path to PCA

From our last lesson, the PCA algorithm is:

  1. Mean-center the data matrix .
  2. Compute the covariance matrix .
  3. Find the eigenvectors of .

While correct, step 2—explicitly forming the matrix —can sometimes lead to a loss of numerical precision. Singular Value Decomposition offers a way to find the same principal components by operating directly on the data matrix , which is generally more numerically stable.

SVD is a fundamental matrix factorization that states any matrix can be decomposed as:

where:

  • is an orthogonal matrix (left singular vectors).
  • is an rectangular diagonal matrix of singular values.
  • is a orthogonal matrix (right singular vectors), so is its transpose.

The key question is: how do these new matrices , , and relate to the principal components we're looking for?

The following video provides an excellent and concise explanation of this relationship.

PCA 6 - Relationship to SVD

This video, 'PCA 6 - Relationship to SVD' by Herman Kamper, directly connects the SVD of the data matrix to the eigendecomposition of the covariance matrix we studied previously.

Please watch the entire video (around 9 minutes). It will walk you through: What SVD is (0:00 - 2:37). How the SVD of the data matrix X relates to the covariance matrix X^T X (2:49 - 5:03). The explicit connection showing that V contains the eigenvectors (principal components) (5:03 - 8:11). Why SVD is convenient and efficient for PCA (8:11 - 9:01).


2. The Algebraic Connection

As the video demonstrated, the connection is beautifully direct. Let's formalize it. Suppose our data matrix is already mean-centered.

The covariance matrix is proportional to . Let's substitute the SVD of into this expression:

Since is an orthogonal matrix, (the identity matrix). This simplifies the equation to:

Now, let's compare this with the eigendecomposition of the covariance matrix , which we know is , where is the matrix of eigenvectors and is the diagonal matrix of eigenvalues.

By direct comparison, we see that:

  1. The columns of (the right singular vectors of ) are the same as the columns of (the eigenvectors of ). Therefore, the right singular vectors are the principal components!
  2. The diagonal matrix of squared singular values, , is the same as the diagonal matrix of eigenvalues (up to the scaling factor ). That is, .

Furthermore, the projected data points (the principal component scores), which we found were , can also be expressed using SVD:

This gives us a very elegant and efficient way to compute both the principal directions and the transformed data.

The following reading provides a fantastic summary of this relationship, including a Python code snippet that we will build upon.

Relationship between SVD and PCA. How to use SVD to perform ...

This StackExchange post, 'Relationship between SVD and PCA', is one of the clearest explanations available online. We will focus on the top-voted answer by 'amoeba'.

Read the first answer by user 'amoeba'. It succinctly summarizes the derivation we just performed and presents the key relationships in a clear table. This will solidify your understanding of the link between the two methods.


3. Implementing PCA with SVD

Now we have a new recipe for PCA that avoids computing the covariance matrix:

  1. Organize Data: Arrange your data into an matrix .
  2. Mean-Center: For each feature, subtract its mean.
  3. Compute SVD: Perform SVD on the centered matrix to get , , and .
  4. Identify Principal Components: The principal components are the columns of , which are the rows of .
  5. Transform Data: The new coordinates are the columns of .

Let's translate this into Python code.


Your Task: Build a PCA Class from Scratch

Your goal is to implement a simple Python class, MyPCA, that performs PCA using numpy.linalg.svd. This task is inspired by the common structure of Scikit-learn models and will test your ability to translate the SVD-based algorithm into working code.

Here is a skeleton for your class. Your job is to fill in the fit and transform methods.

import numpy as np

class MyPCA:
    def __init__(self, n_components):
        """
        Initialize the PCA transformer.
        :param n_components: The number of principal components to keep.
        """
        self.n_components = n_components
        self.components = None
        self.mean = None

    def fit(self, X):
        """
        Fit the model with X.
        :param X: A numpy array of shape (n_samples, n_features).
        """



        # 1. Mean center the data
        #    - Calculate the mean of X along the sample axis.
        #    - Store it in self.mean.
        #    - Subtract the mean from X.
        



        # --- YOUR CODE HERE ---
        self.mean = # ...
        X_centered = # ...



        # ----------------------




        # 2. Perform SVD
        #    - Use np.linalg.svd on the centered data.
        #    - Remember that np.linalg.svd returns U, s, and V_t (V transpose).
        #    - Set full_matrices=False for efficiency, it gives the "thin" SVD.
        



        # --- YOUR CODE HERE ---
        U, s, Vt = # ...



        # ----------------------




        # 3. Store the principal components
        #    - The principal components are the first `n_components` rows of Vt.
        #    - Store them in self.components.
        



        # --- YOUR CODE HERE ---
        self.components = # ...



        # ----------------------


    def transform(self, X):
        """
        Apply dimensionality reduction to X.
        :param X: A numpy array of shape (n_samples, n_features).
        :return: The transformed data, a numpy array of shape (n_samples, n_components).
        """



        # 1. Mean center the data using the mean calculated in `fit`.
        



        # --- YOUR CODE HERE ---
        X_centered = # ...



        # ----------------------


```grasp
{
  "type": "exercise",
  "id": "52198123-9f4f-4def-a37d-05147522bed5"
}
    # 2. Project the data onto the principal components.
    #    - This is a dot product between the centered data and the components.
    #    - Be mindful of the shape of self.components. You may need to transpose it.
    



    # --- YOUR CODE HERE ---
    X_projected = # ...
    return X_projected



    # ----------------------

Take about 20-25 minutes to complete this implementation. Don't worry about getting it perfect; the goal is to work through the logic.

<details>
<summary>Hint</summary>
*   For the dot product in `transform`, if `self.components` has shape `(n_components, n_features)`, the projection is `X_centered @ self.components.T`.
*   The result of `np.linalg.svd` is already sorted by the importance of the singular values, so you can just take the first `n_components` rows from `Vt`.
</details>




### 4. Verifying the Implementation

Once you have attempted the implementation, let's study a complete, step-by-step example that also verifies the correctness of the SVD approach against the eigendecomposition method.

```grasp
{
  "type": "reading",
  "title": "PCA-SVD: Singular Value Decomposition with Python Step-by-Step",
  "id": "[LINK](https://github.com/christianversloot/machine-learning-articles/blob/main/introducing-pca-with-python-and-scikit-learn-for-machine-learning.md)",
  "url": "https://github.com/christianversloot/machine-learning-articles/blob/main/introducing-pca-with-python-and-scikit-learn-for-machine-learning.md",
  "relevant_section_indices": [
    1
  ],
  "par_intro": "This article, 'PCA-SVD: Singular Value Decomposition with Python Step-by-Step', provides a clean, native Python implementation that mirrors the task you just undertook.",
  "par_directions": "Please read the section 'PCA-SVD: Singular Value Decomposition with Python Step-by-Step'. Compare its logic with your own implementation. Pay close attention to how it performs SVD on the transposed data matrix and builds the projection matrix from the result (`vh`).",
  "estimated_time": "10 minutes"
}

The code in the LINK StackExchange answer provides an even more rigorous validation by using assert statements to numerically prove the equivalence of the two methods.

Relationship between SVD and PCA - Code Example

Let's revisit the StackExchange answer and look closely at the code snippet.

Study the Python code block in the answer. Notice how it: Calculates PCA using la.eig on the covariance matrix. Calculates PCA using la.svd on the data matrix. Uses np.allclose to assert that the principal axes (eigenvectors vs. right singular vectors) and principal components (projected data) are identical.

Here is the completed MyPCA class based on these principles:

Click to see the solution
import numpy as np

class MyPCA:
    def __init__(self, n_components):
        self.n_components = n_components
        self.components = None
        self.mean = None

    def fit(self, X):



        # 1. Mean center the data
        self.mean = np.mean(X, axis=0)
        X_centered = X - self.mean




        # 2. Perform SVD
        # Note: We work with features as columns, so we don't transpose X.
        U, s, Vt = np.linalg.svd(X_centered, full_matrices=False)




        # 3. Store the principal components
        # The principal components are the rows of Vt.
        self.components = Vt[:self.n_components]

    def transform(self, X):



        # 1. Mean center the data
        X_centered = X - self.mean




        # 2. Project the data
        # X_centered is (n_samples, n_features)
        # self.components is (n_components, n_features)
        # We need to dot X_centered with the transpose of self.components
        X_projected = X_centered @ self.components.T
        return X_projected




# Example Usage:
from sklearn.datasets import load_iris




# Load data
iris = load_iris()
X = iris.data




# Instantiate and use our PCA
pca = MyPCA(n_components=2)
pca.fit(X)
X_transformed = pca.transform(X)

print("Shape of original data:", X.shape)
print("Shape of transformed data:", X_transformed.shape)




# Compare with Scikit-learn
from sklearn.decomposition import PCA as SklearnPCA
from sklearn.preprocessing import StandardScaler




# Scikit-learn's PCA often assumes standardized data
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

sklearn_pca = SklearnPCA(n_components=2)
X_sklearn_transformed = sklearn_pca.fit_transform(X_scaled)




# Our implementation on scaled data
my_pca_scaled = MyPCA(n_components=2)
my_pca_scaled.fit(X_scaled)
X_my_transformed_scaled = my_pca_scaled.transform(X_scaled)




# The results should be very close (potentially with a sign flip)
print("\nOur PCA result on scaled data (first 5 rows):\n", X_my_transformed_scaled[:5])
print("\nScikit-learn PCA result (first 5 rows):\n", X_sklearn_transformed[:5])


Conclusion

Congratulations! You have successfully built a working PCA implementation from scratch. This exercise solidifies the deep connection between a core machine learning technique and a fundamental theorem of linear algebra.

Key Takeaways:

  • PCA can be performed by applying Singular Value Decomposition directly to the mean-centered data matrix .
  • This approach is numerically more stable than forming the covariance matrix .
  • The right singular vectors (columns of , or rows of ) are the principal components.
  • The transformed data (principal component scores) can be found by calculating or, equivalently, .

Preview of the Next Lesson

Now that you have built and understood the PCA algorithm, it's time to apply it. In our next lesson, we will undertake a project: applying PCA to a high-dimensional dataset of human faces. We will use our implementation to reduce the dimensionality and visualize the principal components, which in this context are famously known as "Eigenfaces".

Eigenfaces - PCA using randomized SVD
These are examples of eigenfaces, which are the principal components of a face dataset. Each image represents a direction of high variance in the 'face space'. We will generate these ourselves in the next lesson.