In [ ]:
# Initialize Otter
import otter
grader = otter.Notebook("model_assignment_PCA.ipynb")

Dimensionality Reduction Adventures with Animal Faces¶

Audience and context¶

This assignment is part of the Unsupervised Machine Learning course in our Master of Data Science program (MDS) at the University of British Columbia. MDS is an intensive 10-month professional program designed for a diverse audience, including learners transitioning into data science. The program intentionally has minimal prerequisites: one course in programming, one in statistics and probability, and one in either introductory calculus or linear algebra.

The curriculum follows a block structure, where students complete short 1-credit modules in each block. This course takes place in Block 5, at a point when students are already familiar with deep learning and Convolutional Neural Networks. You can find the courses offered in each block, along with their descriptions, here.

We maintain our lecture notes and teaching materials for the course on GitHub Enterprise. While those internal materials are not publicly accessible, here are publicly available versions of the notes from the 2024–25 offering:

  • PCA introduction
  • More PCA
  • PCA class demo



Learning objectives¶

By completing this assignment, I will be able to:

  • Explain PCA notation by identifying the roles and dimensions of the matrices $X$, $W$, $Z$, and $\hat{X}$ in dimensionality reduction.
  • Analyze reconstruction error for both typical examples and outliers, reasoning about information retention and loss.
  • Implement PCA using Singular Value Decomposition (SVD), including methods for transformation and reconstruction, and validate results against scikit-learn.
  • Apply PCA to real data (Animal Faces), select an appropriate number of components, and interpret the meaning of the learned components.
  • (Stretch goal) Develop and train a convolutional autoencoder on the same dataset, experiment with architectural and training choices, and compare its performance with PCA.



Imports ¶

In [ ]:
import pickle
from hashlib import sha1

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from PIL import Image
from sklearn import datasets
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from utils import *





Instructions¶

rubric={mechanics}

You will earn points for following these instructions and successfully submitting your work on Gradescope.

Before you start¶

  • Read the Use of Generative AI Policy.

  • Review the General Lab Instructions.

  • Check the MDS Rubrics for grading criteria.

Before submitting¶

  • Run all cells (▶▶ button) to ensure the notebook executes cleanly from top to bottom.

    • Execution counts must start at 1 and be sequential.

    • Notebooks with missing outputs or errors may lose marks.

  • Include a clickable link to your GitHub repository below this cell.

  • Make at least 3 commits to your GitHub repository and ensure it's up to date. If Gradescope becomes inaccessible, we'll grade the most recent GitHub version submitted before the deadline.

  • Do not upload or push data files used in this lab to GitHub or Gradescope. (A .gitignore is provided to prevent this.)

Submitting on Gradescope¶

  • Upload only your .ipynb file (with outputs shown) and any required output files. Do not submit extra files.

  • If needed, refer to the Gradescope Student Guide.

  • If your notebook is too large to render, also upload a Web PDF or HTML version.

    • You can create one using File $\rightarrow$ Save and Export Notebook As.

    • If you get an error when creating a PDF, try running the following commands in your lab directory:

      conda install -c conda-forge nbconvert-playwright
      jupyter nbconvert --to webpdf lab1.ipynb
      
    • Ensure all outputs are visible in your PDF or HTML file; TAs cannot grade your work if outputs are missing.

Points: 2

YOUR REPO LINK GOES HERE





Exercise 1: Warm-up¶


This lab is about getting familiar with one of the most widely used dimensionality-reduction techniques: Principal Component Analysis (PCA). PCA has applications across many domains, including genetics, neuroscience, image processing, and computer vision.

Although PCA is now viewed as a standard tool in data science, its development spans more than a century and reflects a rich blend of statistics, psychology, and early computational thinking. If you're curious about the historical journey behind PCA and how the modern formulation emerged, you can read more on the Principal component analysis Wikipedia page.

In this warm-up exercise, we'll begin by getting familiar with the core terminology. In later exercises, you'll explore a classic computer-vision application of PCA known as Eigenfaces.

1.1 Dimensionality reduction notation¶

rubric={accuracy}

Your tasks:

Suppose you apply dimensionality reduction on a data matrix $X_{n \times d}$ using a dimensionality reduction technique such as Principal Component Analysis (PCA), where

  • $n \rightarrow $ number of examples
  • $d \rightarrow $ number of features
  • $k \rightarrow $ number of components

and you get the following matrices:

  • $W$: the matrix that contains the $k$ principal components,
  • $Z$: the lower-dimensional representation of the data after projection,
  • $\hat{X}$: the reconstruction of the original data from the lower-dimensional representation.

Based on this setup, what are the dimensions of $W$, $Z$, and $\hat{X}$? Please select from the options below.

  • (A) $n \times k$
  • (B) $n \times d$
  • (C) $k \times d$

Solution_1_1

Points: 2

In [ ]:
# Assign the variables to either "A", "B", or "C"
W_dim = ...
Z_dim = ...
X_hat_dim = ...
In [ ]:
grader.check("q1.1")



1.2 PCA by hand¶

Suppose you train the standard PCA algorithm on an already centered data matrix X (not shown) and you get Z and W shown below.

In [ ]:
Z = np.array([[10, 10], [5, 2], [4, 3], [4, 3]])
W = np.array([[0.5, 0.5, -0.5, -0.5], [0.7, 0.1, 0.7, 0.1]])
In [ ]:
Z
In [ ]:
W

1.2.1¶

rubric={accuracy}

Your tasks:

  1. How many rows and columns would be there in the data matrix X?

Solution_1_2_1

Points: 1

In [ ]:
X_rows = ...
X_cols = ...

print("Number of rows:", X_rows)
print("Number of columns:", X_cols)
In [ ]:
grader.check("q1.2.1")



1.2.2¶

rubric={accuracy}

Your tasks:

Fill in the blanks:

  • In this toy example, we are reducing dimensionality from $d$ dimensions to $p$ dimensions.

Solution_1_2_2

Points: 1

In [ ]:
# original dimensions
d = ...

# reduced_dimensions
p = ...
In [ ]:
grader.check("q1.2.2")



1.2.3¶

rubric={accuracy}

Find the low-dimensional representation of the already centered X_new below.

In [ ]:
X_new = np.array([[1, 1, 1, 1], [1, 0, 1, 1]])
In [ ]:
X_new

Solution_1_2_3

Points: 1

In [ ]:
Z_new = ...
In [ ]:
grader.check("q1.2.3")



1.2.4¶

rubric={reasoning}

In our toy example, the third and fourth rows of the transformed matrix Z (the lower dimensional representation) are identical, as are the corresponding rows in the original matrix X

Does this imply that, in general, if two arbitrary data points in a dataset have identical lower-dimensional representations after applying PCA, their original high-dimensional representations must also be identical? Briefly explain your reasoning.

In [ ]:
Z

Solution_1_2_4

Points: 2

Type your answer here, replacing this text.



1.3 Reconstruction error¶

Let's get an intuition for reconstruction error on a toy dataset.

The code below creates a toy dataset with a few outliers. The function get_recon_error_df from utils.py calculates normalized reconstruction errors between the original and reconstructed data points. Run the code and answer the following questions.

In [ ]:
np.random.seed(42)
outliers = np.array([[4, -3], [2.8, -3], [-3, 3]])
x1 = np.random.randn(100)
x2 = x1 + np.random.randn(100) / 2
X = np.stack([x1, x2]).T
X_noise = np.vstack([X, outliers])
X_noise_scaled = StandardScaler().fit_transform(X_noise)
plt.scatter(
    X_noise_scaled[:, 0], X_noise_scaled[:, 1], edgecolors="black", c="xkcd:azure"
);



1.3.1¶

rubric={accuracy}

Your tasks:

  1. Fit PCA with n_components=1 on X_noise_scaled with random_state=123.
  2. Obtain reconstructions X_hat.
  3. Calculate the reconstruction error using the get_recon_error_df function from utils.py and store the returned dataframe in recon_df below.

Solution_1_3_1

Points: 2

In [ ]:
...
In [ ]:
recon_df = ...
recon_df
In [ ]:
grader.check("q1.3.1")



1.3.2¶

rubric={accuracy}

Your tasks:

  1. The last 3 rows (rows from indices 100 to 102) in X_noise_scaled are outliers. Calculate the average reconstruction error for outliers vs. average reconstruction error for other data points (i.e., non-outliers).

Solution_1_3_2

Points: 2

In [ ]:
outliers_avg_recon_error = ...
non_outliers_avg_recon_error = ...
In [ ]:
outliers_avg_recon_error
In [ ]:
non_outliers_avg_recon_error
In [ ]:
grader.check("q1.3.2")
⚠️ Don't forget to git commit. Regular commits will help you track your progress!





Exercise 2: Implementing PCA using SVD¶


rubric={accuracy}

In this exercise, you'll implement your own version of PCA using SVD. The class MyPCA below implements init and fit methods.

Your tasks:

  1. Complete the get_components method of the class which returns the learned components.
  2. Complete the transform method of the class which returns transformed Z given X.
  1. Complete reconstruct method of the class which returns reconstructed X_hat given transformed Z.

Do not forget to add the mean back after reconstruction.

Before applying transformation, center the data by subtracting the mean.

  1. Run your code and compare results of your PCA and sklearn PCA using the code below.

Solution_2

Points: 8

In [ ]:
class MyPCA:
    """
    Solves the PCA problem min_Z,W (Z*W-X)^2 using SVD
    """

    def __init__(self, k):
        """
        Initializes the MyPCA instance with the specified number of components.

        Parameters
        ----------
        k : int
            The number of principal components to retain.
        """
        self.k = k

    def fit(self, X):
        """
        Computes the principal components from the dataset X using SVD.

        Parameters
        ----------
        X : np.ndarray
            The input dataset from which to compute principal components.

        Returns
        -------
        None
        """
        self.mean = np.mean(X, axis=0)
        X = X - self.mean  # Center the data
        U, S, Vt = np.linalg.svd(
            X
        )  # SVD to get singular values and principal components
        self.W = Vt[: self.k, :]  # store only first k components in self.W

    def get_components(self):
        """
        Returns the principal components obtained after fitting the model.

        Parameters
        ----------
        None

        Returns
        -------
        np.ndarray
            An array containing the top k principal components.
        """
        ### Solution_2_1
        ...

    def transform(self, X):
        """
        Transforms the dataset X into the principal component space (Z) and returns it.

        Parameters
        ----------
        X : np.ndarray
            The dataset to be transformed.

        Returns
        -------
        np.ndarray
            The dataset transformed into the principal component space.
        """
        ### Solution_2_2

        ...

    def reconstruct(self, Z):
        """
        Reconstructs the original dataset from the PCA transformed data Z.

        Parameters
        ----------
        Z : np.ndarray
            The PCA transformed data.

        Returns
        -------
        np.ndarray
            The reconstructed dataset, approximating the original dataset before transformation.
        """
        ### Solution_2_3

        ...
In [ ]:
from sklearn.datasets import make_blobs

X, y = make_blobs(n_samples=100, centers=3, n_features=20)  ## Generating toy data

for i in range(1, X.shape[1] + 1):
    print("PCA implementation with {} components: OK".format((i)))
    pca = PCA(n_components=i)
    pca.fit(X)

    mypca = MyPCA(k=i)
    mypca.fit(X)

    assert np.allclose(
        np.abs(pca.components_), np.abs(mypca.get_components())
    ), "W values do not match"

    Z = pca.transform(X)
    Z_prime = mypca.transform(X)

    assert np.allclose(np.abs(Z), np.abs(Z_prime)), "Z values do not match"

    X_hat = pca.inverse_transform(Z)
    X_hat_prime = mypca.reconstruct(Z_prime)
    assert np.allclose(
        np.abs(X_hat), np.abs(X_hat_prime)
    ), "reconstructed X_hat values do not match"
In [ ]:
grader.check("q2")
⚠️ Don't forget to git commit. Regular commits will help you track your progress!





Exercise 3 Eigenfaces: PCA on animal faces¶


Eigenfaces are a classic PCA-based approach to face recognition: when you run PCA on a set of aligned face photos (uniform size, lighting, and centering), each principal component reshaped as an image looks like a ghostly face: an eigenface. A new face can then be expressed as a weighted combination of these eigenfaces, giving a compact code for comparing or recognizing people in a lower-dimensional "face space" (the high-dimensional pixel space of aligned faces). For the seminal demonstration, see the original Turk and Pentland paper from 1991.

In the next few exercises, you'll apply dimensionality reduction to a curated subset of the Kaggle Animal Faces dataset.

Your tasks:

  • Download the compressed data from here

  • Uncompress animal_faces.pkl.zip file.

  • Put it into the data directory.

  • Unpickle animal_faces.pkl with the code below.

  • Display the first few images to verify the dataset loaded correctly.

In [ ]:
import pickle

animals = pickle.load(open("data/animal_faces.pkl", "rb"))
In [ ]:
import matplotlib as mpl

mpl.rcParams.update(mpl.rcParamsDefault)
plt.rcParams["image.cmap"] = "gray"
In [ ]:
animals.shape
In [ ]:
fig, axes = plt.subplots(2, 5, figsize=(12, 5), subplot_kw={"xticks": (), "yticks": ()})
for image, ax in zip(animals, axes.ravel()):
    ax.imshow(image)
plt.show()

Let's flatten the images.

In [ ]:
X_anims = animals.reshape(len(animals), -1)
X_anims.shape

The flattened representation of the data is of shape $1500 \times 10,000$; Each image is represented with $10,000$ features, each feature representing a pixel from the image. Let's define image_shape variable which will be handy later when we want to display images.

In [ ]:
image_shape = (100, 100)

Use this flattened representation (X_anims) in the rest of the lab.

3.1 Varying the number of components¶

rubric={accuracy,viz,reasoning}

Recall that PCA finds principal components such that the first principal component has the highest variance, the second component has the next highest variance and so on. In this exercise, you will experiment with how varying the number of components changes reconstructions and the explained variance ratio.

Your tasks:

  1. Train scikit-learn's PCA model on X_anims using random_state=42 and n_components=1000. Then, plot the number of components (for $k = 1, \ldots, 1000$) against the proportion of total variance explained by the first $k$ components.
  2. Visualize and examine the reconstructions for 10, 50, 100, 200, 300, 500 components.
  3. Briefly discuss your observations.

Note that scikit-learn's PCA does the data centering for you. Do not scale the data in this exercise and the next exercises.

Solution_3_1

Points: 8

Type your answer here, replacing this text.

In [ ]:
...
In [ ]:
...
In [ ]:
...



3.2 Discussion: outliers and off-distribution faces¶

rubric={reasoning}

For this animal-face PCA model, what would qualify as an outlier? Use the cartoon cat below (clearly off the training distribution) as a concrete example.

Your tasks:

  • Fit a PCA model with 300 components and project and reconstruct the cartoon cat

  • Describe visible artifacts (e.g., blurring toward an "average" animal face).

  • Based on this behaviour, what are the risks of using this PCA model as a preprocessing step in downstream tasks? When should you trust the projections, and when should you flag or reject inputs as off-distribution?

In [ ]:
cartoon_cat = 'img/cartoon_cat.png'
cartoon_img = plt.imread(cartoon_cat)
plt.imshow(cartoon_img)
plt.axis("off")
plt.show()

Solution_3_2

Points: 4

Type your answer here, replacing this text.

In [ ]:
...
⚠️ Don't forget to git commit. Regular commits will help you track your progress!





Exercise 4: Interpreting the components learned by PCA¶


PCA is a linear transformation that reduces dimensionality while preserving as much variance as possible. Because PCA learns meaningful patterns in the data, we can visualize and interpret its principal components.

In this exercise, you will analyze the components learned by PCA. The code below trains a PCA model on X_anims with n_components=300 and random_state=42, producing:

  • $Z$ (the transformed data in the lower-dimensional space),
  • $W$ (the principal components, describing the directions of maximum variance), and
  • $X_{hat}$ (the reconstructed data from these components).
In [ ]:
n_components = 300

pca = PCA(n_components=n_components, random_state=42)
pca.fit(X_anims)

Z = pca.transform(X_anims)
W = pca.components_
X_hat = pca.inverse_transform(Z)
In [ ]:
Z.shape
In [ ]:
W.shape
In [ ]:
X_hat.shape

You will be using these in the following exercises.



4.1 Visualizing strong component images¶

rubric={accuracy, reasoning}

The principal component matrix returned by PCA ($W$ matrix) has a shape of (number of components) $\times$ (number of features). Since each component corresponds to a learned feature, you can reshape it into an image of size $100 \times 100$ for visualization.

Your tasks:

  1. Visualize the first 15 principal components by reshaping them into $100 \times 100$ images and displaying them in a grid with 3 rows and 5 columns.
  2. Identify at least four principal components where you observe clear semantic patterns or themes. For each selected component, use the plot_strong_comp_images function from utils.py to extract and display a few example images:
    • Where the component values are large positive values (positive_direction=True (default value)).
    • Where the component values are large negative values (positive_direction=False).
  3. Observe the extracted images and their corresponding components and identify and describe any possible semantic themes that these components seem to capture.

Feel free to use code from lecture notes with appropriate attributions. The function to extract and show strong component images is provided below. Feel free to modify the provided code if necessary.

Solution_4_1

Points: 6

Type your answer here, replacing this text.

In [ ]:
...
In [ ]:
...
In [ ]:
...



4.2 Visualization of first two dimensions¶

rubric={viz}

One of the use cases of PCA is visualization. It's not possible to visualize high dimensional data. That said, since the first couple of components of PCA usually capture a lot of information from the data, we can plot the first two dimensions to get an intuition about the patterns in the data.

Your tasks:

  1. Make a scatterplot of the first two dimensions in the transformed data $Z$.

Solution_4_2

Points: 2

In [ ]:
...



4.3 Image tiling¶

rubric={accuracy,quality,reasoning}

In this exercise you will attempt to interpret the first two dimensions of the transformed data. One way to interpret these dimensions is as follows:

  • Create an $m \times m$ grid which roughly spans scatterplot region from the previous exercise. For example, if Z is your transformed data, the following range of values will get you five points spanning the first dimension.
    np.linspace(np.min(Z[:, 0]), np.max(Z[:, 0]), 5))
    
  • Once you have representative points which span the first dimension, get the indices of the data points closest to these points.
  • Plot the images corresponding to these indices as a grid and observe whether you see any pattern as the first dimension increases (left to right of the grid) and as the second dimension increases (bottom to top of the grid).

Let's try this out with $m=5$, i.e., a $5 \times 5$ grid.

Your tasks:

  1. Make a $5 \times 5$ grid that roughly spans the scatterplot region from the previous exercise (but stays within it). For each point on that grid, select the animal face whose first two dimensions are closest to the grid point. Plot a $5 \times 5$ tiling of these animal faces, corresponding to the $5 \times 5$ grid using the img_tiling function from below.
  2. What happens to the images as the first dimension increases (i.e., go from left to right of the grid)? What about the second dimension? Briefly discuss your observations and try to label your axes based on your interpretation.
In [ ]:
def img_tiling(animals, idx, size=10, image_shape=(100, 100)):
    """
    Plots a 5x5 grid of animal faces, arranged from bottom to top and left to right.

    Parameters:
    -----------
    animals : numpy.ndarray
        A collection of animal face images, where each image is represented as a flattened array.

    idx : numpy.ndarray
        A 5x5 matrix of indices, where each element corresponds to an index in `animals` representing
        the closest matching face for that grid position. The grid layout follows:
            - `idx[0, 0]` corresponds to bottom-left corner
            - `idx[0, 4]` corresponds to top-left corner
            - `idx[4, 0]` corresponds to bottom-right corner
            - `idx[4, 4]` corresponds to top-right corner

    size : int, optional (default=10)
        The figure size for the plotted grid.

    image_shape : tuple, optional (default=(100, 100))
        The expected shape of each image before plotting.

    Returns:
    --------
    None
        Displays the 5x5 grid of images.

    Notes:
    ------
    - Each selected face is reshaped to `image_shape` before being plotted.
    """
    idx = np.array(idx, dtype="int32")  # Just making sure the indexes are int

    plt.figure(figsize=(size, size))  # Creating the image with the desired size

    tile_size = 5
    # Ploting the 5x5 tiling
    for i in range(tile_size):
        for j in range(tile_size):
            face = np.reshape(
                animals[idx[i, j]], (image_shape)
            )  # Obtain the closest face
            plt.imshow(
                face, extent=(i * 32, (i + 1) * 32, j * 32, (j + 1) * 32)
            )  # Plot the closest animal face
    plt.xlim((0, 160))
    plt.ylim((0, 160))
    plt.xticks([])
    plt.yticks([])

Solution_4_3

Points: 8

Type your answer here, replacing this text.

In [ ]:
...
⚠️ Don't forget to git commit. Regular commits will help you track your progress!





Exercise 6: Food for thought¶


As you know, each lab has a few challenging questions. These are usually low-risk questions and will contribute to maximum 5% of the lab grade. The main purpose here is to challenge yourself, dig deeper in a particular area, and going beyond what we explicitly discussed in the class. When you start working on labs, attempt all other questions before moving to these challenging questions. If you are running out of time, please skip the challenging questions.

We will be more strict with the marking of these questions. There might not be model answers. If you want to get full points in these questions, your answers need to

  • be thorough, thoughtful, and well-written
  • provide convincing justification and appropriate evidence for the claims you make
  • impress the reader of your lab with your understanding of the material, your analytical and critical reasoning skills, and your ability to think on your own



(Challenging) 6.1 CNN Autoencoder on the animal faces dataset¶

rubric={reasoning}

As we briefly discussed in class, an autoencoder is a neural network that learns to compress input data into a lower-dimensional representation (the encoder) and then reconstruct the original input from that representation (the decoder). Like PCA, an autoencoder supports reconstruction from a compact code. In fact, if an autoencoder has only one hidden layer and no nonlinear activation functions, it becomes essentially equivalent to PCA.

However, because autoencoders allow much more architectural flexibility, they can learn richer representations than PCA. Unlike PCA's strictly linear subspace, autoencoders can capture nonlinear structure in the data. And when built with convolutional layers, they can take advantage of spatial patterns in images, making them effective for image reconstruction tasks.

In this exercise, you will implement a CNN autoencoder for the animal faces dataset and visualize your model's reconstructions.

The starter code resizes images to 64x64 and provides PyTorch trainloader and validloader.

Your tasks:

  • Build a CNN autoencoder for Animal Faces:
    • Define a CNNAutoencoder with an encoder and decoder.
    • Instantiate the model (use the correct in_channels).
    • Choose a suitable loss (e.g., MSE) and optimizer.
    • Write the training loop and train your model.
  • Visualize several original vs reconstructed images and evaluate reconstruction quality.
  • In 3–5 sentences, connect to PCA: compare the latent code to PCA components (similarities/differences; when one might outperform the other on images).
  • Propose two concrete improvements you didn't try but could potentially improve the reconstructions.
In [ ]:
import pickle
import numpy as np
import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms

resize_to = (64, 64)
# Load animal faces array: shape (N, H, W) or (N, H, W, C)
animals = pickle.load(open("data/animal_faces.pkl", "rb"))

if animals.ndim == 3:
    # Add a channel dimension (N, H, W) --> (N, H, W, 1) 
    animals = animals[:, :, :, None]
elif animals.ndim != 4:
    raise ValueError("Expected animals to be (N,H,W) or (N,H,W,C).")

N, H, W, C = animals.shape # C = 1 (grayscale) or 3 (RGB)

# Transform: to float [0,1] if needed , CHW tensor, and resize to 64x64
def _maybe_scale(x):
    if x.dtype == np.uint8:
        return (x / 255.0).astype(np.float32)
    return x.astype(np.float32)

# Numpy (H, W, C) --> Torch(C, H, W) float32
to_tensor = transforms.Lambda(lambda x: torch.from_numpy(x.transpose(2,0,1)).float())

# Resize to 64x64 for faster training and clean down/up-sampling steps
_resize = transforms.Resize(resize_to)


class AnimalFacesDataset(Dataset):
    def __init__(self, arr):
        self.arr = arr
    def __len__(self):
        return self.arr.shape[0]
    def __getitem__(self, idx):
        x = _maybe_scale(self.arr[idx])      # (H,W,C) in [0,1]
        x = to_tensor(x)                     # (C,H,W) float32
        x = _resize(x)                       # (C,64,64)
        return x

# Train/valid split

rng = np.random.default_rng(42)
perm = rng.permutation(N)
split = int(0.9 * N)
train_idx, valid_idx = perm[:split], perm[split:]

train_ds = AnimalFacesDataset(animals[train_idx])
valid_ds = AnimalFacesDataset(animals[valid_idx])

BATCH_SIZE = 64
trainloader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, drop_last=True)
validloader = DataLoader(valid_ds, batch_size=BATCH_SIZE, shuffle=False, drop_last=False)

in_channels = C  # 1 for grayscale, 3 for RGB
print(f"Inferred channels={in_channels}, original size=({H},{W}), resized to {resize_to}.")
In [ ]:
# Sample image
X = next(iter(trainloader))
plt.figure(figsize=(3, 3))
plt.axis("off")
plt.imshow(X[0, 0, :, :], cmap="gray")
plt.show()

Solution_6_1

Points: 2

In [ ]:
...
In [ ]:
...
In [ ]:
...
In [ ]:
...
⚠️ Don't forget to git commit. Regular commits will help you track your progress!





Before submitting your assignment, please ensure you have followed all the steps in the Instructions section at the top.

Submission checklist¶

  • Restart the kernel and run all cells (▶▶ button)
  • Make at least three commits to your Github repository.
  • The .ipynb file runs without errors and shows all outputs.
  • Only the .ipynb file and required output files are uploaded (no extra files).
  • If the .ipynb file is too large to render on Gradescope, upload a Web PDF and/or HTML version as well.
  • Include the link to your lab GitHub repository below the instructions.

Well done!! Have a wonderful weekend!

In [ ]:
from IPython.display import Image

Image("img/eva-happy-caturday.png")