Lab 11: Convolutional Neural Networks
MPS311/439 - Machine Learning
Week 11 Lab Session | Duration: 50 minutes
Introduction
Welcome to Lab 11! Today you’ll build your first Convolutional Neural Network (CNN) - the architecture that revolutionized computer vision!
What you’ll learn today: - How to build a CNN using Keras (Conv2D, MaxPooling2D, Flatten, Dense) - How to train a CNN on image data - What patterns CNN filters actually learn - How architecture choices affect performance - How to use pre-trained models (MPS439)
Remember: This lab uses a fill-in-the-blanks approach. Don’t write code from scratch - just fill in the blanks marked with ____. Focus on understanding CNNs!
Dataset: We’ll use Fashion-MNIST - 70,000 images of clothing items (28×28 grayscale). It has 10 categories: T-shirt, Trouser, Pullover, Dress, Coat, Sandal, Shirt, Sneaker, Bag, Ankle boot.
Setup: Import Libraries and Load Data
CNNs expect image data in a specific format: (height, width, channels). For grayscale images, channels = 1. For color images, channels = 3 (RGB).
IMPORTANT: The critical step for CNNs is reshaping data to add the channel dimension!
Copy and run this code:
# Import packages
import numpy as np
import matplotlib.pyplot as plt
from tensorflow import keras
from tensorflow.keras import layers, models
# Load Fashion-MNIST
(X_train, y_train), (X_test, y_test) = keras.datasets.fashion_mnist.load_data()
# Class names
class_names = ['T-shirt', 'Trouser', 'Pullover', 'Dress', 'Coat',
'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
# Normalize pixel values to [0, 1]
X_train = X_train / 255.0
X_test = X_test / 255.0
# CRITICAL: Reshape to add channel dimension
X_train = X_train.reshape(-1, 28, 28, 1)
X_test = X_test.reshape(-1, 28, 28, 1)
print(f"Training samples: {X_train.shape[0]}")
print(f"Test samples: {X_test.shape[0]}")
print(f"Image shape: {X_train.shape[1:]}")
print(f"Number of classes: {len(class_names)}")
print(f"\nData ready! Shape is now (samples, height, width, channels)")What’s happening here? - We loaded 60,000 training images and 10,000 test images - Each image is 28×28 pixels, grayscale - We normalized pixels from [0, 255] to [0, 1] for better training - We reshaped from (28, 28) to (28, 28, 1) - CNNs need the channel dimension!
Let’s look at some examples:
# Show first 10 images
plt.figure(figsize=(12, 3))
for i in range(10):
plt.subplot(2, 5, i+1)
plt.imshow(X_train[i].reshape(28, 28), cmap='gray')
plt.title(class_names[y_train[i]])
plt.axis('off')
plt.tight_layout()
plt.show()Part 1: Build Your First CNN (10 minutes)
Background: A CNN has three types of layers: 1. Conv2D - applies filters to detect patterns (edges, textures) 2. MaxPooling2D - reduces dimensions, keeps important features 3. Dense - final classification layers (same as before!)
The typical pattern is: Conv → Pool → Conv → Pool → Flatten → Dense → Output
Task 1.1: Create the CNN architecture
Fill in the blanks below:
# Create CNN model
model = models.Sequential([
# First convolutional block
layers.Conv2D(____, (3, 3), activation='relu', input_shape=(28, 28, 1)),
layers.MaxPooling2D((2, 2)),
# Second convolutional block
layers.Conv2D(____, (3, 3), activation='____'),
layers.MaxPooling2D((2, 2)),
# Flatten and dense layers
layers.____(),
layers.Dense(128, activation='relu'),
layers.Dense(____, activation='softmax')
])
print("Model created!")
model.summary()Hints: - First Conv2D: use 32 filters - Second Conv2D: use 64 filters (double after pooling) - Second Conv2D activation: use ‘relu’ - Layer to flatten: Flatten - Output layer: 10 units (one per class)
AI Help: Ask ChatGPT: “What does Conv2D and MaxPooling2D do in a CNN?”
Question: Look at the model summary. How many parameters are in the first Conv2D layer?
Number of parameters: ____________
Task 1.2: Understand the architecture
Fill in the blanks by reading the model summary:
# After first Conv2D layer, shape is: (____, ____, ____)
# After first MaxPooling2D, shape is: (____, ____, ____)
# After second Conv2D layer, shape is: (____, ____, ____)
# After second MaxPooling2D, shape is: (____, ____, ____)
# After Flatten, shape is: (____)
print("Check your answers by looking at the model.summary() output above!")Hint: The summary shows “Output Shape” for each layer
Question: Why do the spatial dimensions (height, width) get smaller while the channels get larger?
Your answer: _______________________________________________
___________________________________________________________
Part 2: Train the CNN (8 minutes)
Background: Training a CNN is the same as training a regular neural network - we use an optimizer, a loss function, and train for multiple epochs.
Task 2.1: Compile the model
Fill in the blanks:
# Compile model
model.compile(
optimizer='____',
loss='____',
metrics=['accuracy']
)
print("Model compiled!")Hints: - Optimizer: use ‘adam’ (works well for most cases) - Loss: use ‘sparse_categorical_crossentropy’ (for integer labels)
AI Help: Ask ChatGPT: “What’s the difference between categorical_crossentropy and sparse_categorical_crossentropy?”
Task 2.2: Train the model
Fill in the blanks:
# Train model
history = model.fit(
X_train, y_train,
epochs=____,
validation_split=0.2,
batch_size=128,
verbose=1
)
print("\nTraining complete!")Hint: - Use 5 epochs (more takes too long for lab)
What’s happening? - The model sees 80% of training data, keeps 20% for validation - Each epoch processes all training data once - Batch size = 128 means we update weights after every 128 images
Question: Watch the training output. Does validation accuracy increase each epoch?
Your observation: _______________________________________________
Task 2.3: Plot training curves
Fill in the blanks:
# Plot accuracy and loss
plt.figure(figsize=(12, 4))
# Accuracy
plt.subplot(1, 2, 1)
plt.plot(history.history['____'], label='Training')
plt.plot(history.history['____'], label='Validation')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Model Accuracy')
plt.legend()
plt.grid(True)
# Loss
plt.subplot(1, 2, 2)
plt.plot(history.history['____'], label='Training')
plt.plot(history.history['____'], label='Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Model Loss')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()Hints: - Training accuracy: ‘accuracy’ - Validation accuracy: ‘val_accuracy’ - Training loss: ‘loss’ - Validation loss: ‘val_loss’
Questions:
- Is there a gap between training and validation accuracy? Is the model overfitting?
Your answer: _______________________________________________
- What would you do if the gap was very large?
Your answer: _______________________________________________
Part 3: Make Predictions (8 minutes)
Background: Now let’s test our trained CNN! We’ll make predictions on the test set and visualize some results.
Task 3.1: Evaluate on test set
Fill in the blanks:
# Evaluate on test data
test_loss, test_acc = model.____(X_test, y_test, verbose=0)
print(f"Test accuracy: {test_acc:.4f}")
print(f"Test loss: {test_loss:.4f}")Hint: - Method to evaluate: .evaluate()
Question: How does test accuracy compare to validation accuracy? Are they similar?
Your answer: _______________________________________________
Task 3.2: Make predictions
Fill in the blanks:
# Predict on test set
predictions = model.____(X_test)
# Get predicted class (highest probability)
predicted_classes = np.argmax(____, axis=1)
print(f"Predictions shape: {predictions.shape}")
print(f"First prediction probabilities: {predictions[0]}")
print(f"Predicted class: {predicted_classes[0]} ({class_names[predicted_classes[0]]})")
print(f"True class: {y_test[0]} ({class_names[y_test[0]]})")Hints: - Method to predict: .predict() - Find max along axis=1 (across classes)
AI Help: Ask ChatGPT: “What does argmax do in NumPy?”
Task 3.3: Visualize correct predictions
Copy and run this code:
# Show 10 correct predictions
correct_indices = np.where(predicted_classes == y_test)[0]
plt.figure(figsize=(12, 3))
for i in range(10):
idx = correct_indices[i]
plt.subplot(2, 5, i+1)
plt.imshow(X_test[idx].reshape(28, 28), cmap='gray')
plt.title(f'True: {class_names[y_test[idx]]}\nPred: {class_names[predicted_classes[idx]]}')
plt.axis('off')
plt.suptitle('Correct Predictions', fontsize=14, y=1.02)
plt.tight_layout()
plt.show()Task 3.4: Visualize incorrect predictions
Fill in the blanks:
# Show 10 incorrect predictions
incorrect_indices = np.where(____ != y_test)[0]
plt.figure(figsize=(12, 3))
for i in range(10):
idx = incorrect_indices[i]
plt.subplot(2, 5, i+1)
plt.imshow(X_test[idx].reshape(28, 28), cmap='gray')
plt.title(f'True: {class_names[y_test[idx]]}\nPred: {class_names[____[idx]]}')
plt.axis('off')
plt.suptitle('Incorrect Predictions', fontsize=14, y=1.02)
plt.tight_layout()
plt.show()Hints: - Compare predicted_classes with y_test - Use predicted_classes for predicted labels
Questions:
- What types of clothing does the model confuse? (e.g., Shirt vs T-shirt?)
Your answer: _______________________________________________
___________________________________________________________
- Can you understand why it made these mistakes by looking at the images?
Your answer: _______________________________________________
Part 4: Understanding Filters (8 minutes)
Background: The first convolutional layer learns to detect basic patterns like edges and simple shapes. Let’s visualize what the 32 filters in our first layer actually learned!
Task 4.1: Extract filter weights
Fill in the blanks:
# Get weights from first Conv2D layer
first_layer = model.layers[____]
filters, biases = first_layer.____
print(f"Filter shape: {filters.shape}")
print(f"Number of filters: {filters.shape[3]}")
print(f"Filter size: {filters.shape[0]}x{filters.shape[1]}")Hints: - First Conv2D is layer index 0 - Method to get weights: .get_weights()
AI Help: Ask ChatGPT: “How do I extract and visualize CNN filter weights in Keras?”
Task 4.2: Visualize the filters
Fill in the blanks:
# Plot first 32 filters
plt.figure(figsize=(12, 6))
for i in range(32):
plt.subplot(4, 8, i+1)
# Get the i-th filter
f = filters[:, :, ____, ____]
plt.imshow(f, cmap='gray')
plt.axis('off')
plt.suptitle('32 Learned Filters from First Conv2D Layer', fontsize=14)
plt.tight_layout()
plt.show()Hints: - Input channel index: 0 (grayscale has only 1 input channel) - Filter index: i
Questions:
- Can you see edge detectors (vertical, horizontal, diagonal) in some filters?
Your answer: _______________________________________________
- Some filters look like noise. Why might that happen?
Your answer: _______________________________________________
Task 4.3: Apply a filter to an image
Let’s manually apply one filter to see what it detects!
Fill in the blanks:
# Pick a test image
test_img = X_test[0]
# Get one filter (let's use filter 5)
one_filter = filters[:, :, 0, 5]
# Apply convolution manually using correlation (approximate)
from scipy.ndimage import correlate
feature_map = correlate(test_img.reshape(28, 28), ____, mode='constant')
# Show results
plt.figure(figsize=(12, 4))
plt.subplot(1, 3, 1)
plt.imshow(test_img.reshape(28, 28), cmap='gray')
plt.title('Original Image')
plt.axis('off')
plt.subplot(1, 3, 2)
plt.imshow(____, cmap='gray')
plt.title('Filter (3x3)')
plt.axis('off')
plt.subplot(1, 3, 3)
plt.imshow(____, cmap='viridis')
plt.title('Feature Map\n(What filter detected)')
plt.axis('off')
plt.colorbar()
plt.tight_layout()
plt.show()Hints: - Correlate with one_filter - Plot one_filter for the filter - Plot feature_map for the output
Question: What pattern did this filter detect in the image? (edges, textures, etc.)
Your answer: _______________________________________________
Part 5: Architecture Experiments (8 minutes)
Background: CNN performance depends on architecture choices. Let’s experiment with different configurations and see what happens!
Task 5.1: Try fewer filters
Fill in the blanks:
# Create smaller CNN (16 and 32 filters instead of 32 and 64)
model_small = models.Sequential([
layers.Conv2D(____, (3, 3), activation='relu', input_shape=(28, 28, 1)),
layers.MaxPooling2D((2, 2)),
layers.Conv2D(____, (3, 3), activation='relu'),
layers.MaxPooling2D((2, 2)),
layers.Flatten(),
layers.Dense(128, activation='relu'),
layers.Dense(10, activation='softmax')
])
model_small.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# Train
history_small = model_small.fit(X_train, y_train, epochs=____,
validation_split=0.2, batch_size=128, verbose=0)
# Evaluate
_, test_acc_small = model_small.evaluate(X_test, y_test, verbose=0)
print(f"Small model test accuracy: {test_acc_small:.4f}")
print(f"Original model test accuracy: {test_acc:.4f}")Hints: - Use 16 filters in first Conv2D - Use 32 filters in second Conv2D - Train for 5 epochs
Task 5.2: Try more filters
Fill in the blanks:
# Create larger CNN (64 and 128 filters)
model_large = models.Sequential([
layers.Conv2D(____, (3, 3), activation='relu', input_shape=(28, 28, 1)),
layers.MaxPooling2D((2, 2)),
layers.Conv2D(____, (3, 3), activation='relu'),
layers.MaxPooling2D((2, 2)),
layers.Flatten(),
layers.Dense(128, activation='relu'),
layers.Dense(10, activation='softmax')
])
model_large.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# Train
history_large = model_large.fit(X_train, y_train, epochs=5,
validation_split=0.2, batch_size=128, verbose=0)
# Evaluate
_, test_acc_large = model_large.evaluate(X_test, y_test, verbose=0)
print(f"Large model test accuracy: {test_acc_large:.4f}")Hints: - Use 64 filters in first Conv2D - Use 128 filters in second Conv2D
Task 5.3: Compare all models
Copy and run this code:
# Compare parameter counts
print("\nModel Comparison:")
print(f"Small model - Parameters: {model_small.count_params():,} - Test Acc: {test_acc_small:.4f}")
print(f"Original model - Parameters: {model.count_params():,} - Test Acc: {test_acc:.4f}")
print(f"Large model - Parameters: {model_large.count_params():,} - Test Acc: {test_acc_large:.4f}")
# Plot comparison
models_names = ['Small\n(16,32)', 'Original\n(32,64)', 'Large\n(64,128)']
accuracies = [test_acc_small, test_acc, test_acc_large]
params = [model_small.count_params(), model.count_params(), model_large.count_params()]
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].bar(models_names, accuracies, color=['blue', 'green', 'red'])
axes[0].set_ylabel('Test Accuracy')
axes[0].set_title('Model Accuracy Comparison')
axes[0].set_ylim(0.8, 0.95)
axes[0].grid(True, alpha=0.3)
axes[1].bar(models_names, params, color=['blue', 'green', 'red'])
axes[1].set_ylabel('Number of Parameters')
axes[1].set_title('Model Size Comparison')
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()Questions:
- Does more parameters always mean better accuracy?
Your answer: _______________________________________________
- If you had limited compute resources, which model would you choose and why?
Your answer: _______________________________________________
___________________________________________________________
- What’s the trade-off between model size and accuracy?
Your answer: _______________________________________________
Reflection Questions (5 minutes)
Answer these questions based on what you’ve learned today:
Question 1: Why do we add the channel dimension when reshaping data for CNNs? What would happen if we forgot?
Your answer: _______________________________________________
___________________________________________________________
Question 2: Explain in your own words what MaxPooling2D does and why it’s useful.
Your answer: _______________________________________________
___________________________________________________________
Question 3: The first Conv2D layer has 320 parameters (32 filters × (3×3 + 1 bias)). Why is this so much fewer than a fully-connected layer?
Your answer: _______________________________________________
___________________________________________________________
Question 4: You trained a CNN that gets 95% training accuracy but only 75% validation accuracy. What’s happening and what would you do?
Your answer: _______________________________________________
___________________________________________________________
Question 5: If you had color images (RGB) instead of grayscale, how would the architecture change?
Your answer: _______________________________________________
___________________________________________________________
Summary
What you learned today: - ✅ How to build CNNs using Conv2D, MaxPooling2D, and Dense layers - ✅ How to reshape image data for CNNs (adding channel dimension) - ✅ How to train and evaluate CNNs on Fashion-MNIST - ✅ What filters actually learn (edges, patterns, textures) - ✅ How architecture choices (number of filters) affect performance
Key takeaways: - CNNs need data shaped as (height, width, channels) - Conv2D applies learned filters to detect patterns - MaxPooling2D reduces dimensions and provides translation invariance - More filters = more capacity but also more parameters - First layers learn simple patterns (edges), deeper layers learn complex patterns - Always normalize pixel values to [0, 1] for better training!
Part 6: Advanced Challenge - Transfer Learning (For MPS439 Students Only) (30 minutes)
Background: Training large CNNs from scratch requires lots of data and time. Transfer learning lets us use a model pre-trained on millions of images (ImageNet) and adapt it to our task. This is how real-world CNN applications work!
We’ll use VGG16, a famous CNN architecture trained on ImageNet. We’ll: 1. Load the pre-trained VGG16 (without the top classification layer) 2. Freeze its weights (we won’t retrain the feature extraction) 3. Add our own classification head for Fashion-MNIST 4. Train only the new layers
Task 6.1: Prepare data for VGG16
VGG16 expects 224×224 RGB images, but Fashion-MNIST is 28×28 grayscale. We need to resize and convert to RGB.
Fill in the blanks:
# VGG16 expects 224x224 RGB images
from tensorflow.keras.applications.vgg16 import VGG16, preprocess_input
# Resize images to 224x224 and convert to RGB
def prepare_for_vgg(images):
# Resize to 224x224
from tensorflow.image import resize
images_resized = resize(images, [____, ____])
# Convert grayscale to RGB by repeating channel 3 times
images_rgb = np.repeat(images_resized, ____, axis=-1)
# VGG16 preprocessing
images_processed = preprocess_input(images_rgb)
return images_processed
# Use subset for speed (VGG16 is large!)
X_train_vgg = prepare_for_vgg(X_train[:5000])
X_test_vgg = prepare_for_vgg(X_test[:1000])
y_train_vgg = y_train[:5000]
y_test_vgg = y_test[:1000]
print(f"Training samples: {X_train_vgg.shape[0]}")
print(f"Image shape: {X_train_vgg.shape[1:]}")
print(f"Ready for VGG16!")Hints: - Resize to 224 by 224 - Repeat 3 times for RGB
AI Help: Ask ChatGPT: “How do I use transfer learning with VGG16 in Keras?”
Task 6.2: Load pre-trained VGG16
Fill in the blanks:
# Load VGG16 without top layers (no classification head)
base_model = VGG16(
weights='____',
include_top=____,
input_shape=(224, 224, 3)
)
# Freeze base model weights
base_model.trainable = ____
print(f"VGG16 loaded!")
print(f"Number of layers: {len(base_model.layers)}")
print(f"Trainable: {base_model.trainable}")Hints: - Use ‘imagenet’ weights - Set include_top=False (we’ll add our own) - Set trainable=False (freeze the weights)
Task 6.3: Add custom classification head
Fill in the blanks:
# Build model with VGG16 base and custom head
model_vgg = models.Sequential([
base_model,
layers.GlobalAveragePooling2D(),
layers.Dense(____, activation='relu'),
layers.Dense(____, activation='softmax')
])
model_vgg.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
print("\nModel architecture:")
model_vgg.summary()Hints: - Dense layer: use 256 units - Output layer: 10 units (10 classes)
Question: How many parameters are trainable vs non-trainable?
Trainable parameters: ____________
Non-trainable parameters: ____________
Task 6.4: Train with transfer learning
Fill in the blanks:
# Train only the new layers
history_vgg = model_vgg.fit(
X_train_vgg, y_train_vgg,
epochs=____,
validation_split=0.2,
batch_size=32,
verbose=1
)
# Evaluate
_, test_acc_vgg = model_vgg.evaluate(X_test_vgg, y_test_vgg, verbose=0)
print(f"\nTransfer learning test accuracy: {test_acc_vgg:.4f}")Hint: - Use 3 epochs (VGG16 is slow!)
Question: How does this accuracy compare to training from scratch? Is it better or worse with only 5000 training samples?
Your answer: _______________________________________________
___________________________________________________________
Task 6.5: Fine-tuning (optional - if time permits)
Now let’s unfreeze the last few layers of VGG16 and fine-tune them.
Fill in the blanks:
# Unfreeze last 4 layers of VGG16
base_model.trainable = True
for layer in base_model.layers[:-4]:
layer.trainable = ____
# Recompile with lower learning rate
model_vgg.compile(
optimizer=keras.optimizers.Adam(learning_rate=____),
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
print(f"Trainable layers: {sum([1 for layer in model_vgg.layers if layer.trainable])}")
# Fine-tune
history_finetune = model_vgg.fit(
X_train_vgg, y_train_vgg,
epochs=2,
validation_split=0.2,
batch_size=32,
verbose=1
)
# Evaluate
_, test_acc_finetune = model_vgg.evaluate(X_test_vgg, y_test_vgg, verbose=0)
print(f"\nFine-tuned test accuracy: {test_acc_finetune:.4f}")Hints: - Keep first layers frozen: trainable=False - Use low learning rate: 1e-5 (or 0.00001)
Task 6.6: Compare all approaches
Copy and run this code:
# Compare training from scratch vs transfer learning
print("\n" + "="*60)
print("COMPARISON OF APPROACHES")
print("="*60)
print(f"Small CNN (from scratch, 60k samples): {test_acc:.4f}")
print(f"Transfer Learning (5k samples): {test_acc_vgg:.4f}")
if 'test_acc_finetune' in locals():
print(f"Fine-tuned (5k samples): {test_acc_finetune:.4f}")
print("="*60)
# Plot training curves
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(history_vgg.history['accuracy'], label='Training')
plt.plot(history_vgg.history['val_accuracy'], label='Validation')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Transfer Learning - Accuracy')
plt.legend()
plt.grid(True)
plt.subplot(1, 2, 2)
plt.plot(history_vgg.history['loss'], label='Training')
plt.plot(history_vgg.history['val_loss'], label='Validation')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Transfer Learning - Loss')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()Task 6.7: Visualize VGG16 predictions
Fill in the blanks:
# Make predictions with VGG16
predictions_vgg = model_vgg.____(X_test_vgg)
predicted_classes_vgg = np.argmax(____, axis=1)
# Show some predictions
plt.figure(figsize=(12, 6))
for i in range(12):
plt.subplot(3, 4, i+1)
# Get original image (before resizing)
orig_img = X_test[i].reshape(28, 28)
plt.imshow(orig_img, cmap='gray')
true_label = class_names[y_test_vgg[i]]
pred_label = class_names[____[i]]
correct = y_test_vgg[i] == predicted_classes_vgg[i]
color = 'green' if correct else 'red'
plt.title(f'True: {true_label}\nPred: {pred_label}', color=color, fontsize=9)
plt.axis('off')
plt.suptitle('VGG16 Transfer Learning Predictions', fontsize=14)
plt.tight_layout()
plt.show()Hints: - Use .predict() method - Use predictions_vgg for argmax - Use predicted_classes_vgg for predicted label
Task 6.8: Advanced reflection
Question 1: Why did we freeze the VGG16 weights initially instead of training all layers?
Your answer: _______________________________________________
___________________________________________________________
Question 2: Transfer learning worked well with only 5000 samples. Why is this possible?
Your answer: _______________________________________________
___________________________________________________________
Question 3: VGG16 was trained on ImageNet (real-world objects). Fashion-MNIST is quite different (simple clothing). Why does transfer learning still help?
Your answer: _______________________________________________
___________________________________________________________
Question 4: When would you NOT want to use transfer learning?
Your answer: _______________________________________________
___________________________________________________________
Question 5: What are the trade-offs between training from scratch vs transfer learning?
Your answer: _______________________________________________
___________________________________________________________
Congratulations! You’ve used a state-of-the-art pre-trained CNN and adapted it to a new task. This is exactly how modern computer vision applications are built!
End of Lab Worksheet
30-second feedback
Scan the code to share anonymous feedback or post a question for this lab. It opens the correct Week 11 lab record automatically.