0% found this document useful (0 votes)
36 views31 pages

EE24M306 - Programming Assignment - 3

The document outlines a programming assignment for a machine learning course, focusing on implementing a perceptron model for classifying two datasets and designing a feedforward neural network for multi-class classification tasks using the MNIST Fashion dataset and Tiny ImageNet. It includes code snippets for training the perceptron model, plotting decision boundaries, and building a neural network with specified architecture and loss functions. The assignment emphasizes practical implementation and evaluation of machine learning models using Python and TensorFlow.

Uploaded by

princenani634
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
36 views31 pages

EE24M306 - Programming Assignment - 3

The document outlines a programming assignment for a machine learning course, focusing on implementing a perceptron model for classifying two datasets and designing a feedforward neural network for multi-class classification tasks using the MNIST Fashion dataset and Tiny ImageNet. It includes code snippets for training the perceptron model, plotting decision boundaries, and building a neural network with specified architecture and loss functions. The assignment emphasizes practical implementation and evaluation of machine learning models using Python and TensorFlow.

Uploaded by

princenani634
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 31

18/04/2025, 15:41 EE24M306_Programming assignment - 3.

ipynb - Colab

EE567L MACHINE LEARNING FOR SIGNAL INFERENCE

Programming assignment - 3

NAME:JWALA RAJANI

ROLL.NO:EE24M306

keyboard_arrow_down Question - 1
Use the perceptron model and verify whether it can classify the given dataset 1 and dataset 2. Choose the hyperparameters - initial
weights, no. of iterations, and learning rate to achieve the classification and report the same. Plot initial and final decision boundaries over
the data samples.

import numpy as np
import matplotlib.pyplot as plt

# Load the first dataset (dataset-1)


X_train1 = np.load('train_samples1.npy')
y_train1 = np.load('train_labels1.npy')
X_test1 = np.load('test_samples1.npy')
y_train1 = y_train1.flatten()

# Initialize Perceptron parameters


def perceptron(X, y, learning_rate=0.01, max_iterations=1000):
X_augmented = np.c_[np.ones(X.shape[0]), X]
weights = np.zeros(X_augmented.shape[1])

for _ in range(max_iterations):
for i in range(X_augmented.shape[0]):
# Perceptron update rule
prediction = np.sign(np.dot(X_augmented[i], weights))
error = y[i] - prediction
weights += learning_rate * error * X_augmented[i]

return weights

# Train the Perceptron model for dataset-1


weights1 = perceptron(X_train1, y_train1)

# Define a function to plot decision boundary


def plot_decision_boundary(X, y, weights, title='Decision Boundary'):

x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1


y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
np.arange(y_min, y_max, 0.1))
Z = np.sign(np.c_[np.ones(xx.ravel().shape[0]), xx.ravel(), yy.ravel()] @ weights)
Z = Z.reshape(xx.shape)

# Plot the samples and decision boundary


plt.contourf(xx, yy, Z, alpha=0.75)
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolors='k', marker='o')
plt.title(title)
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.colorbar()
plt.show()

plt.figure(figsize=(8, 6))
plot_decision_boundary(X_train1, y_train1, np.zeros(X_train1.shape[1] + 1), title='Initial Decision Boundary (Dataset 1)')

plt.figure(figsize=(8, 6))
plot_decision_boundary(X_train1, y_train1, weights1, title='Final Decision Boundary (Dataset 1)')

def test_perceptron(X, weights):


X_augmented = np.c_[np.ones(X.shape[0]), X]
predictions = np.sign(np.dot(X_augmented, weights))
return predictions

# Test the perceptron on the test data for dataset-1


test_predictions1 = test_perceptron(X_test1, weights1)

# Load the second dataset (dataset-2)


X_train2 = np.load('train_samples2.npy')
y_train2 = np.load('/content/Train_labels2.npy')
X_test2 = np.load('test_samples2.npy')
y_train2 = y_train2.flatten()
https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 1/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

# Train the Perceptron model for dataset-2


weights2 = perceptron(X_train2, y_train2)
plt.figure(figsize=(8, 6))
plot_decision_boundary(X_train2, y_train2, np.zeros(X_train2.shape[1] + 1), title='Initial Decision Boundary (Dataset 2)')
plt.figure(figsize=(8, 6))
plot_decision_boundary(X_train2, y_train2, weights2, title='Final Decision Boundary (Dataset 2)')

test_predictions2 = test_perceptron(X_test2, weights2)

# Optional: Print predictions for dataset-2 test data


print("Predictions on dataset-2 test data:", test_predictions2)

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 2/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 3/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

Predictions on dataset-2 test data: [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]

keyboard_arrow_down Question - 2
Design a feed forward neural network (FNN) for a multi class classification task using the datasets and network design choices given
below.

1) Dataset: (i) MNIST Fashion dataset, (ii) Tiny imagenet

2) Number of hidden layers: 3

3) Final layer activation function: Softmax

4) Loss function: Cross-entropy

5) Train and test data split ratio : 70:30

(i) MNIST Fashion dataset

import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.datasets import fashion_mnist
from tensorflow.keras.utils import to_categorical
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
import pandas as pd

def build_and_train_model_FashionMNIST(learning_rate=0.001, batch_size=32, epochs=10):


# Load Fashion MNIST dataset
(X_train_full, y_train_full), (X_test, y_test) = fashion_mnist.load_data()
X_train_full = X_train_full.astype('float32') / 255.0
X_test = X_test.astype('float32') / 255.0
X_train_full = X_train_full.reshape(X_train_full.shape[0], -1)
X_test = X_test.reshape(X_test.shape[0], -1)
y_train_full = to_categorical(y_train_full, 10)
y_test = to_categorical(y_test, 10)
X_train, X_test_split, y_train, y_test_split = train_test_split(X_train_full, y_train_full, test_size=0.3, random_state=4

model = models.Sequential()

# Input layer: 784 nodes


model.add(layers.InputLayer(input_shape=(784,)))
model.add(layers.Dense(128, activation='relu'))
model.add(layers.Dense(64, activation='relu'))

model.add(layers.Dense(32, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 4/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
y
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
model.compile(optimizer=optimizer, loss='categorical_crossentropy', metrics=['accuracy'])
history = model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size)
test_loss, test_acc = model.evaluate(X_test_split, y_test_split)
print(f"Test accuracy: {test_acc:.4f}")
y_pred = model.predict(X_test_split)
y_pred_classes = tf.argmax(y_pred, axis=1)
y_test_classes = tf.argmax(y_test_split, axis=1)

cm = confusion_matrix(y_test_classes, y_pred_classes)

cm_df = pd.DataFrame(cm, index=[f'True_{i}' for i in range(10)], columns=[f'Pred_{i}' for i in range(10)])


cm_df.to_csv('confusion_matrix_FashionMNIST.csv')
print(f"Confusion matrix saved to confusion_matrix_FashionMNIST.csv")

# Example usage
build_and_train_model_FashionMNIST(learning_rate=0.001, batch_size=128, epochs=10)

/usr/local/lib/python3.11/dist-packages/keras/src/layers/core/input_layer.py:27: UserWarning: Argument `input_shape` is


warnings.warn(
Epoch 1/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.6869 - loss: 0.8989
Epoch 2/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - accuracy: 0.8432 - loss: 0.4428
Epoch 3/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - accuracy: 0.8575 - loss: 0.3995
Epoch 4/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - accuracy: 0.8681 - loss: 0.3627
Epoch 5/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 3s 7ms/step - accuracy: 0.8771 - loss: 0.3352
Epoch 6/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step - accuracy: 0.8865 - loss: 0.3102
Epoch 7/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 3s 8ms/step - accuracy: 0.8890 - loss: 0.2982
Epoch 8/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.8973 - loss: 0.2835
Epoch 9/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - accuracy: 0.8992 - loss: 0.2695
Epoch 10/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - accuracy: 0.8997 - loss: 0.2679
563/563 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - accuracy: 0.8791 - loss: 0.3362
Test accuracy: 0.8802
563/563 ━━━━━━━━━━━━━━━━━━━━ 1s 1ms/step
Confusion matrix saved to confusion_matrix_FashionMNIST.csv

(ii) Tiny imagenet

import os
import zipfile
import urllib.request
import numpy as np
import pandas as pd
from PIL import Image
from tqdm import tqdm
from sklearn.metrics import confusion_matrix
import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.utils import to_categorical

# Dataset URLs and paths


TINY_IMAGENET_URL = "http://cs231n.stanford.edu/tiny-imagenet-200.zip"
TINY_IMAGENET_ZIP = "tiny-imagenet-200.zip"
TINY_IMAGENET_PATH = "tiny-imagenet-200"

# Step 1: Download and extract dataset


def download_tiny_imagenet():
if not os.path.exists(TINY_IMAGENET_PATH):
print("Downloading Tiny ImageNet...")
urllib.request.urlretrieve(TINY_IMAGENET_URL, TINY_IMAGENET_ZIP)
print("Extracting...")
with zipfile.ZipFile(TINY_IMAGENET_ZIP, 'r') as zip_ref:
zip_ref.extractall(".")
print("Done!")
else:
print("Tiny ImageNet already exists.")

# Step 2: Load only first 10 classes


def load_tiny_imagenet_10_classes(split='train', max_classes=10, max_images_per_class=50):
data, labels = [], []
label_map = {}

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 5/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
if split == 'train':
train_dir = os.path.join(TINY_IMAGENET_PATH, 'train')
classes = sorted(os.listdir(train_dir))[:max_classes]
for idx, cls in enumerate(classes):
label_map[cls] = idx
image_dir = os.path.join(train_dir, cls, 'images')
for i, img_file in enumerate(os.listdir(image_dir)):
if i >= max_images_per_class:
break
img_path = os.path.join(image_dir, img_file)
img = Image.open(img_path).resize((64, 64)).convert('RGB')
data.append(np.array(img))
labels.append(idx)

elif split == 'val':


val_dir = os.path.join(TINY_IMAGENET_PATH, 'val')
annotations = {}
with open(os.path.join(val_dir, 'val_annotations.txt')) as f:
for line in f:
parts = line.strip().split('\t')
annotations[parts[0]] = parts[1]

valid_classes = sorted(set(annotations.values()))[:max_classes]
label_map = {cls: idx for idx, cls in enumerate(valid_classes)}

image_dir = os.path.join(val_dir, 'images')


for img_file in os.listdir(image_dir):
cls = annotations.get(img_file)
if cls in label_map:
img_path = os.path.join(image_dir, img_file)
img = Image.open(img_path).resize((64, 64)).convert('RGB')
data.append(np.array(img))
labels.append(label_map[cls])

data = np.array(data).astype('float32') / 255.0


labels = to_categorical(np.array(labels), num_classes=max_classes)
return data, labels

# Step 3: Build and train CNN


def build_and_train_tinyimagenet_cnn_10(learning_rate=0.001, batch_size=64, epochs=10):
download_tiny_imagenet()

print("Loading 10-class training data...")


X_train, y_train = load_tiny_imagenet_10_classes('train', max_classes=10)
print("Loading 10-class validation data...")
X_val, y_val = load_tiny_imagenet_10_classes('val', max_classes=10)

# CNN model
model = models.Sequential([
layers.Conv2D(32, (3, 3), activation='relu', input_shape=(64, 64, 3)),
layers.MaxPooling2D((2, 2)),
layers.Conv2D(64, (3, 3), activation='relu'),
layers.MaxPooling2D((2, 2)),
layers.Conv2D(128, (3, 3), activation='relu'),
layers.MaxPooling2D((2, 2)),
layers.Flatten(),
layers.Dense(256, activation='relu'),
layers.Dropout(0.3),
layers.Dense(10, activation='softmax')
])

model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),
loss='categorical_crossentropy',
metrics=['accuracy'])

model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=2)

val_loss, val_acc = model.evaluate(X_val, y_val, verbose=0)


print(f"Validation accuracy: {val_acc:.4f}")

y_pred = model.predict(X_val)
y_pred_classes = tf.argmax(y_pred, axis=1)
y_val_classes = tf.argmax(y_val, axis=1)

cm = confusion_matrix(y_val_classes, y_pred_classes)
cm_df = pd.DataFrame(cm)
cm_df.to_csv("confusion_matrix_TinyImageNet_10_classes_CNN.csv")
print("Confusion matrix saved to 'confusion_matrix_TinyImageNet_10_classes_CNN.csv'")

# Run it
build_and_train_tinyimagenet_cnn_10()

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 6/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

Tiny ImageNet already exists.


Loading 10-class training data...
Loading 10-class validation data...
/usr/local/lib/python3.11/dist-packages/keras/src/layers/convolutional/base_conv.py:107: UserWarning: Do not pass an `in
super().__init__(activity_regularizer=activity_regularizer, **kwargs)
Epoch 1/10
8/8 - 8s - 972ms/step - accuracy: 0.1240 - loss: 2.3233
Epoch 2/10
8/8 - 5s - 622ms/step - accuracy: 0.1740 - loss: 2.2857
Epoch 3/10
8/8 - 5s - 635ms/step - accuracy: 0.2160 - loss: 2.1853
Epoch 4/10
8/8 - 3s - 335ms/step - accuracy: 0.2280 - loss: 2.0896
Epoch 5/10
8/8 - 3s - 335ms/step - accuracy: 0.2760 - loss: 2.0261
Epoch 6/10
8/8 - 7s - 922ms/step - accuracy: 0.3280 - loss: 1.8604
Epoch 7/10
8/8 - 3s - 361ms/step - accuracy: 0.3860 - loss: 1.7688
Epoch 8/10
8/8 - 6s - 810ms/step - accuracy: 0.4180 - loss: 1.6735
Epoch 9/10
8/8 - 3s - 334ms/step - accuracy: 0.5040 - loss: 1.5197
Epoch 10/10
8/8 - 3s - 339ms/step - accuracy: 0.4700 - loss: 1.5191
Validation accuracy: 0.4100
16/16 ━━━━━━━━━━━━━━━━━━━━ 1s 84ms/step
Confusion matrix saved to 'confusion_matrix_TinyImageNet_10_classes_CNN.csv'

NOTE:

The Tiny ImageNet dataset contains 200 classes, making full training computationally expensive and slow. To simplify the task and speed up
experimentation, we use only the first 10 classes. This reduces data loading time, training duration, and allows easier debugging.

A. What dimensions will you choose for each hidden layer? Why?

(i) Fashion MNIST classification

First hidden layer: 128 neurons

––128 neurons in the first hidden layer to capture fine-grained patterns such as edges, textures, and basic shapes from the flattened 784-
dimensional grayscale clothing images.

Second hidden layer: 64 neurons

––64 neurons in the second layer to combine low-level features into more meaningful mid-level representations like parts of clothing items
(e.g., sleeves, collars).

Third hidden layer: 32 neurons

––32 neurons in the third layer to abstract the information further and distill the essential patterns necessary for final class discrimination
among the 10 fashion categories.

ii) Tiny ImageNet (10 classes)

First hidden layer: 1024 neurons

Second hidden layer: 512 neurons

Third hidden layer: 256 neurons

Reason: The chosen dimensions (1024, 512, 256 neurons) are designed to handle the increased complexity of Tiny ImageNet images
(64×64×3). The first layer (1024 neurons) captures rich and detailed spatial features, the second layer (512 neurons) refines the extracted
patterns while managing model size, and the third layer (256 neurons) helps in abstraction and generalization before classification.

B. How do you initialize the FNN? Why?

(i) MNIST Fashion dataset

The FNN (Feed-Forward Neural Network) is initialized using a Sequential model in Keras. It starts with an InputLayer for 28×28 grayscale
images, followed by a Flatten layer to convert the 2D input into a 1D vector. Several Dense layers are then added to learn abstract
representations. This setup is simple yet effective for the relatively low-resolution and less complex Fashion MNIST images.

(ii) Tiny ImageNet

The FNN is initialized as a Sequential model in Keras. An InputLayer is used for 64×64×3 RGB images, followed by a Flatten layer to convert
the 3D image into a 1D vector. Dense layers are added to learn complex feature representations. Since Tiny ImageNet images have higher

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 7/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
resolution and more visual variety, deeper and wider layers are required to effectively capture the data's complexity.

C. What are the choices of activation function? Which activation functions will you use for the hidden layers?

choices of activation function are:

ReLU: Most commonly used in hidden layers. It helps in faster training and reduces vanishing gradients.

Sigmoid & Tanh: Less common in hidden layers due to vanishing gradients.

Softmax: Used in the output layer for multi-class classification.

For hidden layers: ReLU is used because it speeds up training and reduces the vanishing gradient issue.

D. What is the learning rate used to train the FNN? How do you fix it?

The learning rate is 0.001, which is commonly used in deep learning. It controls how much the model's weights are updated during training.
In this case, it's fixed when initializing the Adam optimizer (tf.keras.optimizers.Ada (learning_rate=0.001)). If we change the learning rate to
0.0001, we might observe different results. A smaller learning rate (like 0.0001) will make the training process slower, as the weights are
updated more gradually. This may improve stability but could also increase training time and potentially lead to the model getting stuck in a
local minimum.

#MNIST Fashion dataset,


#learning_rate=0.0001
build_and_train_model_FashionMNIST(learning_rate=0.0001, batch_size=32, epochs=10)

/usr/local/lib/python3.11/dist-packages/keras/src/layers/core/input_layer.py:27: UserWarning: Argument `input_shape` is


warnings.warn(
Epoch 1/10
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 7s 4ms/step - accuracy: 0.6326 - loss: 1.1612
Epoch 2/10
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 4s 3ms/step - accuracy: 0.8241 - loss: 0.5081
Epoch 3/10
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.8444 - loss: 0.4468
Epoch 4/10
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 4s 3ms/step - accuracy: 0.8569 - loss: 0.4069
Epoch 5/10
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.8620 - loss: 0.3921
Epoch 6/10
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.8708 - loss: 0.3694
Epoch 7/10
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 10s 4ms/step - accuracy: 0.8736 - loss: 0.3558
Epoch 8/10
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 4s 3ms/step - accuracy: 0.8810 - loss: 0.3403
Epoch 9/10
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.8818 - loss: 0.3303
Epoch 10/10
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 7s 5ms/step - accuracy: 0.8851 - loss: 0.3188
563/563 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - accuracy: 0.8762 - loss: 0.3577
Test accuracy: 0.8756
563/563 ━━━━━━━━━━━━━━━━━━━━ 1s 1ms/step
Confusion matrix saved to confusion_matrix_FashionMNIST.csv

Observation: lr=0.001 and lr=0.0001

With a learning rate of 0.001, the model achieved a test accuracy of 0.8802 after 10 epochs.

With a learning rate of 0.0001, the model achieved a test accuracy of 0.8756 after 10 epochs.

This suggests that a learning rate of 0.001 led to faster convergence and better performance within the 10 epochs. A smaller learning rate
(0.0001) caused the model to update the weights more slowly, leading to a slightly lower accuracy after the same number of epochs.

E. What is the batch size? How does the batch size affect the performance of FNN?

The batch size is set to 128, as specified in the function call (batch_size=128). The batch size determines how many training samples are
processed before the model’s weights are updated.

Small batch sizes lead to more frequent updates, which can result in faster convergence but may also be noisier.

Large batch sizes result in more stable and accurate estimates of the gradient but may require more memory and lead to slower
convergence.

#MNIST Fashion dataset,


#batch_size=128
build_and_train_model_FashionMNIST(learning_rate=0.001, batch_size=128, epochs=10)

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 8/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

/usr/local/lib/python3.11/dist-packages/keras/src/layers/core/input_layer.py:27: UserWarning: Argument `input_shape` is


warnings.warn(
Epoch 1/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.7206 - loss: 0.8431
Epoch 2/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.8509 - loss: 0.4244
Epoch 3/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - accuracy: 0.8623 - loss: 0.3808
Epoch 4/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.8703 - loss: 0.3597
Epoch 5/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - accuracy: 0.8834 - loss: 0.3185
Epoch 6/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 2s 5ms/step - accuracy: 0.8898 - loss: 0.3017
Epoch 7/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 2s 4ms/step - accuracy: 0.8935 - loss: 0.2916
Epoch 8/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - accuracy: 0.8997 - loss: 0.2787
Epoch 9/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.8992 - loss: 0.2687
Epoch 10/10
329/329 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - accuracy: 0.9037 - loss: 0.2642
563/563 ━━━━━━━━━━━━━━━━━━━━ 2s 3ms/step - accuracy: 0.8812 - loss: 0.3390
Test accuracy: 0.8803
563/563 ━━━━━━━━━━━━━━━━━━━━ 1s 1ms/step
Confusion matrix saved to confusion_matrix_FashionMNIST.csv

#MNIST Fashion dataset,


#batch_size=64
build_and_train_model_FashionMNIST(learning_rate=0.001, batch_size=64, epochs=10)

/usr/local/lib/python3.11/dist-packages/keras/src/layers/core/input_layer.py:27: UserWarning: Argument `input_shape` is


warnings.warn(
Epoch 1/10
657/657 ━━━━━━━━━━━━━━━━━━━━ 8s 9ms/step - accuracy: 0.7320 - loss: 0.7883
Epoch 2/10
657/657 ━━━━━━━━━━━━━━━━━━━━ 2s 4ms/step - accuracy: 0.8493 - loss: 0.4145
Epoch 3/10
657/657 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.8683 - loss: 0.3607
Epoch 4/10
657/657 ━━━━━━━━━━━━━━━━━━━━ 3s 3ms/step - accuracy: 0.8765 - loss: 0.3333
Epoch 5/10
657/657 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.8858 - loss: 0.3111
Epoch 6/10
657/657 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8915 - loss: 0.2979
Epoch 7/10
657/657 ━━━━━━━━━━━━━━━━━━━━ 4s 4ms/step - accuracy: 0.8981 - loss: 0.2759
Epoch 8/10
657/657 ━━━━━━━━━━━━━━━━━━━━ 2s 4ms/step - accuracy: 0.9007 - loss: 0.2703
Epoch 9/10
657/657 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8995 - loss: 0.2656
Epoch 10/10
657/657 ━━━━━━━━━━━━━━━━━━━━ 2s 3ms/step - accuracy: 0.9036 - loss: 0.2542
563/563 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - accuracy: 0.8776 - loss: 0.3427
Test accuracy: 0.8799
563/563 ━━━━━━━━━━━━━━━━━━━━ 1s 1ms/step
Confusion matrix saved to confusion_matrix_FashionMNIST.csv

Observation:batch_size 32,64,128

With a batch size of 32, the model achieved a test accuracy of 0.8802.

With a batch size of 64, the model achieved a test accuracy of 0.8799.

With a batch size of 128, the model achieved a test accuracy of 0.8803.

This suggests that increasing the batch size to 128 slightly improved the model's accuracy, reaching 0.8803.

F. Mention the optimization technique?

The optimization technique used is Adam (Adaptive Moment Estimation), specified by tf.keras.optimizers.Adam. Adam is a popular optimizer
because it combines the benefits of two other extensions of stochastic gradient descent, AdaGrad and RMSProp, by maintaining both a
moving average of the gradient and the squared gradient.

G. Compute the evaluation metrics (i) accuracy (ii) precision (iii) recall using the confusion matrix, to assess the performance of the FNN
classification model. Explain the importance of each metric and the inference we draw.

Accuracy gives a quick snapshot but can be misleading when classes are imbalanced.

Precision focuses on minimizing false positives, making it crucial when false alarms are costly.

Recall emphasizes minimizing false negatives, important when missing positive instances is more critical.

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 9/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

(i)MNIST Fashion dataset

import pandas as pd
import numpy as np
from sklearn.metrics import precision_score, recall_score, accuracy_score

# Load the confusion matrix CSV file


cm_df = pd.read_csv('/content/confusion_matrix_FashionMNIST.csv', index_col=0)
confusion_matrix = cm_df.values

# Compute Accuracy
accuracy = np.trace(confusion_matrix) / np.sum(confusion_matrix)
print(f"Accuracy: {accuracy:.4f}")
precision = np.diagonal(confusion_matrix) / np.sum(confusion_matrix, axis=0)
print(f"Precision for each class: {precision}")
recall = np.diagonal(confusion_matrix) / np.sum(confusion_matrix, axis=1)
print(f"Recall for each class: {recall}")
precision_macro = np.mean(precision)
recall_macro = np.mean(recall)

print(f"Macro Precision: {precision_macro:.4f}")


print(f"Macro Recall: {recall_macro:.4f}")

Accuracy: 0.8799
Precision for each class: [0.79336735 0.99550562 0.83402001 0.88117712 0.72232143 0.96235955
0.78406709 0.95005945 0.98668211 0.92210526]
Recall for each class: [0.8790277 0.96566757 0.78678512 0.88708776 0.89146006 0.96074033
0.61954721 0.90847072 0.93369863 0.96635411]
Macro Precision: 0.8832
Macro Recall: 0.8799

(ii) Tiny imagenet

import pandas as pd
import numpy as np
from sklearn.metrics import precision_score, recall_score, accuracy_score

# Load the confusion matrix CSV file


cm_df = pd.read_csv('/content/confusion_matrix_TinyImageNet_10_classes_CNN.csv', index_col=0)

# Convert the confusion matrix dataframe to a numpy array


confusion_matrix = cm_df.values
accuracy = np.trace(confusion_matrix) / np.sum(confusion_matrix)
print(f"Accuracy: {accuracy:.4f}")
precision = np.diagonal(confusion_matrix) / np.sum(confusion_matrix, axis=0)
print(f"Precision for each class: {precision}")
recall = np.diagonal(confusion_matrix) / np.sum(confusion_matrix, axis=1)
print(f"Recall for each class: {recall}")

precision_macro = np.mean(precision)
recall_macro = np.mean(recall)

print(f"Macro Precision: {precision_macro:.4f}")


print(f"Macro Recall: {recall_macro:.4f}")

Accuracy: 0.4100
Precision for each class: [0.71428571 0.65306122 0.26666667 0.26086957 0.28571429 0.4
0.28235294 0.66666667 0.53521127 0.33333333]
Recall for each class: [0.8 0.64 0.16 0.24 0.68 0.16 0.48 0.04 0.76 0.14]
Macro Precision: 0.4398
Macro Recall: 0.4100

H. Compute another metric other than the above mentioned 3 metrics using the confusion matrix to assess the performance of the FNN
classification model.

The F1-Score is particularly valuable when both false positives and false negatives carry significant weight, and when dealing with
imbalanced datasets.

(i)MNIST Fashion dataset

import pandas as pd
import numpy as np

# Load the confusion matrix CSV file


cm_df = pd.read_csv('/content/confusion_matrix_FashionMNIST.csv', index_col=0)

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 10/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

confusion_matrix = cm_df.values

precision = np.diagonal(confusion_matrix) / np.sum(confusion_matrix, axis=0)


recall = np.diagonal(confusion_matrix) / np.sum(confusion_matrix, axis=1)
f1_score = 2 * (precision * recall) / (precision + recall)

print(f"F1-Score for each class: {f1_score}")

f1_score_macro = np.mean(f1_score)
print(f"Macro F1-Score: {f1_score_macro:.4f}")

F1-Score for each class: [0.8393866 0.97979518 0.80258398 0.88754915 0.79807435 0.95591518
0.63958692 0.93794276 0.96984786 0.95599225]
Macro F1-Score: 0.8767

(ii) Tiny imagenet

import pandas as pd
import numpy as np

# Load the confusion matrix CSV file


cm_df = pd.read_csv('/content/confusion_matrix_TinyImageNet_10_classes_CNN.csv', index_col=0)

# Convert the confusion matrix dataframe to a numpy array


confusion_matrix = cm_df.values
precision = np.diagonal(confusion_matrix) / np.sum(confusion_matrix, axis=0)
recall = np.diagonal(confusion_matrix) / np.sum(confusion_matrix, axis=1)
f1_score = 2 * (precision * recall) / (precision + recall)

print(f"F1-Score for each class: {f1_score}")

f1_score_macro = np.mean(f1_score)
print(f"Macro F1-Score: {f1_score_macro:.4f}")

F1-Score for each class: [0.75471698 0.64646465 0.2 0.25 0.40236686 0.22857143
0.35555556 0.0754717 0.62809917 0.1971831 ]
Macro F1-Score: 0.3738

I. For both the datasets, mention the input feature vector dimension and number of classes.

(i) MNIST Fashion dataset

Input Feature Vector Dimension: 784 (each image is 28×28 pixels, flattened into a 1D vector). Number of Classes: 10 (each representing a
clothing category such as T-shirt, trouser, etc.).

(ii) Tiny ImageNet (10 classes)

Input Feature Vector Dimension: Each image is 64×64 pixels with 3 color channels (RGB), resulting in 64 × 64 × 3 = 12,288 features when
flattened into a 1D vector. Therefore, the input feature vector dimension is 12,288.

Number of Classes: We are using a subset of 10 classes from the Tiny ImageNet dataset for simplified and faster training.

Question - 3

Build a CNN model with LeNet, VGG architectures using library functions to perform classification task. Consider the two image
classification datasets i.e, MNIST digit classification and CIFAR10. Split the entire dataset into Train-validation-Test in the ratio
80%-10%-10%.

keyboard_arrow_down MNIST digit classification


import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import random_split, DataLoader
import matplotlib.pyplot as plt
import numpy as np
import random

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 11/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
import torchvision

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# MNIST transforms
transform = transforms.Compose([
transforms.ToTensor(),
])

# Load dataset
full_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)

# Train-validation-test split
train_size = int(0.8 * len(full_dataset))
val_size = int(0.1 * len(full_dataset))
partial_test_size = len(full_dataset) - train_size - val_size
train_data, val_data, _ = random_split(full_dataset, [train_size, val_size, partial_test_size])
test_data = test_dataset

train_loader = DataLoader(train_data, batch_size=64, shuffle=True)


val_loader = DataLoader(val_data, batch_size=64, shuffle=False)
test_loader = DataLoader(test_data, batch_size=64, shuffle=False)

100%|██████████| 9.91M/9.91M [00:00<00:00, 18.1MB/s]


100%|██████████| 28.9k/28.9k [00:00<00:00, 515kB/s]
100%|██████████| 1.65M/1.65M [00:00<00:00, 4.48MB/s]
100%|██████████| 4.54k/4.54k [00:00<00:00, 13.0MB/s]

A. Plot training & validation loss curves.

# Function to define the model with configurable parameters


def create_model(conv_layers=3, num_filters=[32, 64, 128], kernel_size=3, pool_size=2, fc_units=[512, 256], lr=0.0005):
class ModifiedLeNet(nn.Module):
def __init__(self):
super(ModifiedLeNet, self).__init__()
self.conv_layers = nn.ModuleList()
in_channels = 1

for i in range(conv_layers):
self.conv_layers.append(nn.Conv2d(in_channels, num_filters[i], kernel_size, padding=1))
in_channels = num_filters[i]

self.pool = nn.MaxPool2d(pool_size, pool_size)


self.fc1 = nn.Linear(num_filters[-1] * 3 * 3, fc_units[0])
self.fc2 = nn.Linear(fc_units[0], fc_units[1])
self.fc3 = nn.Linear(fc_units[1], 10)

def forward(self, x):


for conv_layer in self.conv_layers:
x = self.pool(F.relu(conv_layer(x)))
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return self.fc3(x)

def num_flat_features(self, x):


size = x.size()[1:]
num_features = 1
for s in size:
num_features *= s
return num_features

# Initialize the model, optimizer, and loss function


model = ModifiedLeNet().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=lr)

return model, criterion, optimizer

# Create model with custom parameters


model, criterion, optimizer = create_model(conv_layers=3, num_filters=[32, 64, 128], kernel_size=3, pool_size=2, fc_units=[5

# Training and evaluation


train_losses, val_losses = [], []

for epoch in range(10):


model.train()
train_loss = 0
for images, labels in train_loader:

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 12/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
out = model(images)
loss = criterion(out, labels)
loss.backward()
optimizer.step()
train_loss += loss.item()

model.eval()
val_loss = 0
with torch.no_grad():
for images, labels in val_loader:
images, labels = images.to(device), labels.to(device)
out = model(images)
val_loss += criterion(out, labels).item()

train_losses.append(train_loss / len(train_loader))
val_losses.append(val_loss / len(val_loader))
print(f"Epoch {epoch+1}: Train Loss = {train_losses[-1]}, Val Loss = {val_losses[-1]}")

# Plotting Loss
plt.plot(train_losses, label='Train Loss')
plt.plot(val_losses, label='Validation Loss')
plt.title("Loss Curves")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.show()

Epoch 1: Train Loss = 0.2919079356665413, Val Loss = 0.12479071309195554


Epoch 2: Train Loss = 0.07500159726788601, Val Loss = 0.09018200484322424
Epoch 3: Train Loss = 0.05020395797630772, Val Loss = 0.052347577761541655
Epoch 4: Train Loss = 0.03645288854294146, Val Loss = 0.04193673856145246
Epoch 5: Train Loss = 0.02954696543320703, Val Loss = 0.04258235686636986
Epoch 6: Train Loss = 0.024663478725589813, Val Loss = 0.046609342380605163
Epoch 7: Train Loss = 0.02022675603069365, Val Loss = 0.03762871053619707
Epoch 8: Train Loss = 0.015635276165727796, Val Loss = 0.04529855110183447
Epoch 9: Train Loss = 0.01501882970953496, Val Loss = 0.037634885105944704
Epoch 10: Train Loss = 0.0124825428812231, Val Loss = 0.038936723885312184

B. Visualize each filter in the first convolutional layer.

# Function to plot filters from the first convolutional layer


def plot_filters(layer):
filters = layer.weight.data.clone().cpu()
fig, axes = plt.subplots(1, len(filters), figsize=(100, 4))
for i, f in enumerate(filters):
axes[i].imshow(f[0], cmap='gray')
axes[i].axis('off')
plt.suptitle("Filters from First Convolution Layer")
plt.show()

plot_filters(model.conv_layers[0])

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 13/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

C. For a test sample image, display the feature maps produced by each layer

# Function to visualize feature maps


def visualize_feature_maps(img):
model.eval()
img = img.unsqueeze(0).to(device)
with torch.no_grad():
x = F.relu(model.conv_layers[0](img))
x = model.pool(x)

fig, axs = plt.subplots(1, x.shape[1], figsize=(100, 4))


for i in range(x.shape[1]):
axs[i].imshow(x[0, i].cpu(), cmap='gray')
axs[i].axis('off')
plt.suptitle("Feature Maps from First Conv Layer")
plt.show()

sample_img, _ = test_dataset[0]
visualize_feature_maps(sample_img)

D. Manipulate the input by:

a. Adding noise to different regions of the image

b. Blurring specific parts of the digit

c. Rotating the digit slightly

# Function to manipulate image (noise, blur, rotate)


import cv2

def manipulate_image(img, mode):


img = img.numpy().squeeze()
if mode == "noise":
img[10:18, 10:18] += 0.4 * np.random.randn(8, 8)
elif mode == "blur":
img[10:18, 10:18] = cv2.GaussianBlur(img[10:18, 10:18], (3,3), 0)
elif mode == "rotate":
center = (14, 14)
rot mat = cv2.getRotationMatrix2D(center, 15, 1.0)
https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 14/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
_ g
img = cv2.warpAffine(img, rot_mat, (28, 28))
return torch.tensor(img).unsqueeze(0)

def predict(img):
model.eval()
img = img.unsqueeze(0).to(device)
out = model(img)
probs = torch.softmax(out, dim=1).squeeze().detach().cpu().numpy()
return probs

manipulations = ["noise", "blur", "rotate"]


original_img, label = test_dataset[8]
print("True Label:", label)
original_probs = predict(original_img)

for mode in manipulations:


mod_img = manipulate_image(original_img, mode)
probs = predict(mod_img)
print(f"\nMode: {mode}")
print("Prediction Probabilities:", probs)

True Label: 5

Mode: noise
Prediction Probabilities: [1.3102228e-08 7.8930451e-10 1.1436720e-10 7.2339708e-09 7.7014759e-09
9.9976403e-01 1.7280533e-04 1.7906793e-10 6.2076455e-05 1.1256362e-06]

Mode: blur
Prediction Probabilities: [4.4677698e-08 3.1434793e-09 4.1774778e-10 2.3429708e-08 1.5480332e-08
9.9988794e-01 8.9936933e-05 1.8822555e-09 2.0232228e-05 1.7909904e-06]

Mode: rotate
Prediction Probabilities: [1.5248754e-05 2.0697157e-06 1.0765802e-06 9.7494092e-07 2.6310647e-05
7.2298664e-01 2.7679503e-01 2.7336756e-07 7.5871023e-05 9.6558906e-05]

E. Report the metrics accuracy, confusion matrix.

# Evaluate function to compute accuracy and confusion matrix


from sklearn.metrics import confusion_matrix, accuracy_score
import seaborn as sns

def evaluate(model, dataloader):


model.eval()
all_preds, all_labels = [], []
with torch.no_grad():
for imgs, lbls in dataloader:
imgs, lbls = imgs.to(device), lbls.to(device)
out = model(imgs)
preds = torch.argmax(out, dim=1)
all_preds.extend(preds.cpu().numpy())
all_labels.extend(lbls.cpu().numpy())
acc = accuracy_score(all_labels, all_preds)
cm = confusion_matrix(all_labels, all_preds)
return acc, cm

acc, cm = evaluate(model, test_loader)


print("Test Accuracy:", acc)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.title("Confusion Matrix")
plt.xlabel("Predicted")
plt.ylabel("True")
plt.show()

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 15/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

Test Accuracy: 0.9904

F. Consider 10 random test samples in each dataset and visualize their corresponding predictions.

# Show random predictions


def show_predictions():
model.eval()
indices = random.sample(range(len(test_dataset)), 10)
fig, axs = plt.subplots(2, 5, figsize=(12, 5))
for i, idx in enumerate(indices):
img, label = test_dataset[idx]
pred = torch.argmax(model(img.unsqueeze(0).to(device))).item()
axs[i//5, i%5].imshow(img.squeeze(), cmap='gray')
axs[i//5, i%5].set_title(f"True: {label}, Pred: {pred}")
axs[i//5, i%5].axis('off')
plt.suptitle("Random Test Samples and Predictions")
plt.show()

show_predictions()

G. Vary the number of convolutional layers, sub-sampling layers, also modify the hyperparameters and report the accuracies for the best
three cases.

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from sklearn.metrics import confusion_matrix, accuracy_score
import seaborn as sns

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 16/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
import matplotlib.pyplot as plt

def create_model(conv_layers, num_filters, kernel_size, pool_size, fc_units, lr):


class ModifiedLeNet(nn.Module):
def __init__(self):
super(ModifiedLeNet, self).__init__()
self.conv_layers = nn.ModuleList()
in_channels = 1

for i in range(conv_layers):
self.conv_layers.append(nn.Conv2d(in_channels, num_filters[i], kernel_size, padding=1))
in_channels = num_filters[i]

self.pool = nn.MaxPool2d(pool_size, pool_size)

with torch.no_grad():
dummy_input = torch.zeros(1, 1, 28, 28)
x = dummy_input
for conv_layer in self.conv_layers:
x = self.pool(F.relu(conv_layer(x)))
self.flattened_size = x.view(1, -1).size(1)

self.fc1 = nn.Linear(self.flattened_size, fc_units[0])


self.fc2 = nn.Linear(fc_units[0], fc_units[1])
self.fc3 = nn.Linear(fc_units[1], 10)

def forward(self, x):


for conv_layer in self.conv_layers:
x = self.pool(F.relu(conv_layer(x)))
x = x.view(-1, self.flattened_size)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return self.fc3(x)

model = ModifiedLeNet().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=lr)
return model, criterion, optimizer

def evaluate(model, dataloader):


model.eval()
all_preds, all_labels = [], []
with torch.no_grad():
for imgs, lbls in dataloader:
imgs, lbls = imgs.to(device), lbls.to(device)
out = model(imgs)
preds = torch.argmax(out, dim=1)
all_preds.extend(preds.cpu().numpy())
all_labels.extend(lbls.cpu().numpy())
acc = accuracy_score(all_labels, all_preds)
cm = confusion_matrix(all_labels, all_preds)
return acc, cm

configs = [
{'conv_layers': 3, 'num_filters': [32, 64, 128], 'kernel_size': 3, 'pool_size': 2, 'fc_units': [512, 256], 'lr': 0.0005}
{'conv_layers': 2, 'num_filters': [16, 32], 'kernel_size': 5, 'pool_size': 2, 'fc_units': [256, 128], 'lr': 0.001},
{'conv_layers': 4, 'num_filters': [16, 32, 64, 128], 'kernel_size': 3, 'pool_size': 2, 'fc_units': [512, 256], 'lr': 0.0
]

best_accuracy = 0
best_model_id = -1

for idx, config in enumerate(configs):


print(f"\n=== Training model {idx + 1} with config: {config} ===")
model, criterion, optimizer = create_model(**config)

train_losses, val_losses = [], []

for epoch in range(10):


model.train()
train_loss = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
out = model(images)
loss = criterion(out, labels)
loss.backward()
optimizer.step()
train_loss += loss.item()

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 17/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
model.eval()
val_loss = 0
with torch.no_grad():
for images, labels in val_loader:
images, labels = images.to(device), labels.to(device)
out = model(images)
val_loss += criterion(out, labels).item()

train_losses.append(train_loss / len(train_loader))
val_losses.append(val_loss / len(val_loader))
print(f"Epoch {epoch+1}: Train Loss = {train_losses[-1]:.4f}, Val Loss = {val_losses[-1]:.4f}")


acc, cm = evaluate(model, test_loader)
print(f" Test Accuracy (Model {idx + 1}): {acc:.4f}")
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.title(f"Confusion Matrix (Model {idx+1})")
plt.xlabel("Predicted")
plt.ylabel("True")
plt.show()

if acc > best_accuracy:


best_accuracy = acc
best_model_id = idx + 1

print(f"\n 🏆 Best model is Model {best_model_id} with accuracy: {best_accuracy:.4f}")

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 18/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

=== Training model 1 with config: {'conv_layers': 3, 'num_filters': [32, 64, 128], 'kernel_size': 3, 'pool_size': 2, 'fc
Epoch 1: Train Loss = 0.2727, Val Loss = 0.0822
Epoch 2: Train Loss = 0.0670, Val Loss = 0.0479
Epoch 3: Train Loss = 0.0429, Val Loss = 0.0441
Epoch 4: Train Loss = 0.0341, Val Loss = 0.0408
Epoch 5: Train Loss = 0.0258, Val Loss = 0.0506
Epoch 6: Train Loss = 0.0208, Val Loss = 0.0313
Epoch 7: Train Loss = 0.0170, Val Loss = 0.0391
Epoch 8: Train Loss = 0.0144, Val Loss = 0.0315
Epoch 9: Train Loss = 0.0123, Val Loss = 0.0337


Epoch 10: Train Loss = 0.0117, Val Loss = 0.0464
Test Accuracy (Model 1): 0.9901

=== Training model 2 with config: {'conv_layers': 2, 'num_filters': [16, 32], 'kernel_size': 5, 'pool_size': 2, 'fc_unit
Epoch 1: Train Loss = 0.2410, Val Loss = 0.0846
Epoch 2: Train Loss = 0.0628, Val Loss = 0.0446
Epoch 3: Train Loss = 0.0427, Val Loss = 0.0418
Epoch 4: Train Loss = 0.0322, Val Loss = 0.0412
Epoch 5: Train Loss = 0.0267, Val Loss = 0.0474
Epoch 6: Train Loss = 0.0204, Val Loss = 0.0505
Epoch 7: Train Loss = 0.0161, Val Loss = 0.0384
Epoch 8: Train Loss = 0.0156, Val Loss = 0.0602
Epoch 9: Train Loss = 0.0131, Val Loss = 0.0395


Epoch 10: Train Loss = 0.0114, Val Loss = 0.0374
Test Accuracy (Model 2): 0.9911

=== Training model 3 with config: {'conv_layers': 4, 'num_filters': [16, 32, 64, 128], 'kernel_size': 3, 'pool_size': 2,
Epoch 1: Train Loss = 0.7847, Val Loss = 0.2519
Epoch 2: Train Loss = 0.1826, Val Loss = 0.1414
Epoch 3: Train Loss = 0.1298, Val Loss = 0.1310
Epoch 4: Train Loss = 0.1064, Val Loss = 0.1115
Epoch 5: Train Loss = 0.0905, Val Loss = 0.0859
Epoch 6: Train Loss = 0.0793, Val Loss = 0.0731
Epoch 7: Train Loss = 0.0722, Val Loss = 0.0851
Epoch 8: Train Loss = 0.0640, Val Loss = 0.0685
Epoch 9: Train Loss = 0.0591, Val Loss = 0.0661
Epoch 10: Train Loss = 0.0518, Val Loss = 0.0709
https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 19/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

Epoch 10: Train Loss 0.0518, Val Loss 0.0709
Test Accuracy (Model 3): 0.9807

🏆 Best model is Model 2 with accuracy: 0.9911

keyboard_arrow_down Question - 3
CIFAR10.

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import random_split, DataLoader
import matplotlib.pyplot as plt
import numpy as np
import random
import torchvision

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# CIFAR-10 transforms
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465),
(0.2470, 0.2435, 0.2616))
])

# Load CIFAR-10 dataset


full_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)

# Train-validation-test split
train_size = int(0.8 * len(full_dataset))
val_size = int(0.1 * len(full_dataset))
partial_test_size = len(full_dataset) - train_size - val_size
train_data, val_data, _ = random_split(full_dataset, [train_size, val_size, partial_test_size])
test_data = test_dataset

# Data loaders
train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
val_loader = DataLoader(val_data, batch_size=64, shuffle=False)
test_loader = DataLoader(test_data, batch_size=64, shuffle=False)

100%|██████████| 170M/170M [00:03<00:00, 46.9MB/s]

A. Plot training & validation loss curves.

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 20/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import matplotlib.pyplot as plt

# Function to define the model with configurable parameters


def create_model(conv_layers=3, num_filters=[32, 64, 128], kernel_size=3, pool_size=2, fc_units=[512, 256], lr=0.0005):
class ModifiedLeNet(nn.Module):
def __init__(self):
super(ModifiedLeNet, self).__init__()
self.conv_layers = nn.ModuleList()
in_channels = 3

# Create convolutional layers dynamically based on the input parameters


for i in range(conv_layers):
self.conv_layers.append(nn.Conv2d(in_channels, num_filters[i], kernel_size, padding=1))
in_channels = num_filters[i]

self.pool = nn.MaxPool2d(pool_size, pool_size)

self.fc1 = nn.Linear(num_filters[-1] * 4 * 4, fc_units[0])


self.fc2 = nn.Linear(fc_units[0], fc_units[1])
self.fc3 = nn.Linear(fc_units[1], 10)

def forward(self, x):


for conv_layer in self.conv_layers:
x = self.pool(F.relu(conv_layer(x)))
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return self.fc3(x)

def num_flat_features(self, x):


size = x.size()[1:]
num_features = 1
for s in size:
num_features *= s
return num_features

# Initialize the model, optimizer, and loss function


model = ModifiedLeNet().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=lr)

return model, criterion, optimizer

# Create model with custom parameters


model, criterion, optimizer = create_model(conv_layers=3, num_filters=[32, 64, 128], kernel_size=3, pool_size=2, fc_units=[5

# Training and evaluation


train_losses, val_losses = [], []

for epoch in range(10):


model.train()
train_loss = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
out = model(images)
loss = criterion(out, labels)
loss.backward()
optimizer.step()
train_loss += loss.item()

model.eval()
val_loss = 0
with torch.no_grad():
for images, labels in val_loader:
images, labels = images.to(device), labels.to(device)
out = model(images)
val_loss += criterion(out, labels).item()

train_losses.append(train_loss / len(train_loader))
val_losses.append(val_loss / len(val_loader))
print(f"Epoch {epoch+1}: Train Loss = {train_losses[-1]}, Val Loss = {val_losses[-1]}")

# Plotting Loss
plt.plot(train_losses, label='Train Loss')
plt.plot(val_losses, label='Validation Loss')
plt.title("Loss Curves")
plt.xlabel("Epoch")

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 21/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
plt.ylabel("Loss")
plt.legend()
plt.show()

Epoch 1: Train Loss = 1.4978283979415894, Val Loss = 1.2334589075438585


Epoch 2: Train Loss = 1.0800540143966675, Val Loss = 0.9903581881824928
Epoch 3: Train Loss = 0.8643898788452149, Val Loss = 0.8288117164297949
Epoch 4: Train Loss = 0.7198264911651612, Val Loss = 0.7742827447909343
Epoch 5: Train Loss = 0.5987737037658691, Val Loss = 0.7710346047636829
Epoch 6: Train Loss = 0.48563608696460725, Val Loss = 0.7855005849011337
Epoch 7: Train Loss = 0.3819044842004776, Val Loss = 0.8096481218368192
Epoch 8: Train Loss = 0.2923629284977913, Val Loss = 0.9012144060829018
Epoch 9: Train Loss = 0.2098124260365963, Val Loss = 1.0703011230577397
Epoch 10: Train Loss = 0.15248911373615265, Val Loss = 1.0843838838082325

B. Visualize each filter in the first convolutional layer.

import matplotlib.pyplot as plt

# Function to plot filters from the first convolutional layer


def plot_filters(layer):
filters = layer.weight.data.clone().cpu()
num_filters = filters.shape[0]
filter_size = filters.shape[2]

# Set up the plot for displaying filters


fig, axes = plt.subplots(1, num_filters, figsize=(num_filters * 2, 4))
if num_filters == 1:
axes = [axes]

for i, f in enumerate(filters):
if f.shape[0] == 3:
f = f.mean(dim=0)
axes[i].imshow(f.detach().numpy(), cmap='gray')
axes[i].axis('off')

plt.suptitle("Filters from First Convolution Layer")


plt.show()

# Plot filters from the first convolution layer of the model


plot_filters(model.conv_layers[0])

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 22/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

C. For a test sample image, display the feature maps produced by each layer

import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt

# Function to visualize feature maps


def visualize_feature_maps(img):
model.eval()
img = img.unsqueeze(0).to(device)

with torch.no_grad():

x = F.relu(model.conv_layers[0](img))
x = model.pool(x)

# Set up the figure for displaying feature maps


fig, axs = plt.subplots(1, x.shape[1], figsize=(x.shape[1] * 2, 4))

# If there's only one filter, make axs iterable


if x.shape[1] == 1:
axs = [axs]

for i in range(x.shape[1]):
axs[i].imshow(x[0, i].cpu(), cmap='gray')
axs[i].axis('off')

plt.suptitle("Feature Maps from First Conv Layer")


plt.show()

sample_img, _ = test_dataset[0]
visualize_feature_maps(sample_img)

D. Manipulate the input by:

a. Adding noise to different regions of the image

b. Blurring specific parts of the digit

c. Rotating the digit slightly

import cv2
import numpy as np
import torch
import torch.nn.functional as F

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 23/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
from torchvision import transforms

# Function to manipulate image (noise, blur, rotate)


def manipulate_image(img, mode):
img = img.numpy().transpose(1, 2, 0)

if mode == "noise":
# Add Gaussian noise to a small region of the image
img[10:18, 10:18, :] += 0.4 * np.random.randn(8, 8, 3)
elif mode == "blur":
# Apply Gaussian blur to a region of the image
img[10:18, 10:18, :] = cv2.GaussianBlur(img[10:18, 10:18, :], (3, 3), 0)
elif mode == "rotate":

center = (14, 14)


rot_mat = cv2.getRotationMatrix2D(center, 15, 1.0)
img = cv2.warpAffine(img, rot_mat, (32, 32))

img = np.clip(img, 0, 255)


img = torch.tensor(img).permute(2, 0, 1)
return img.float() / 255.0

# Function to predict the class using the model


def predict(img, model, device):
model.eval()
img = img.unsqueeze(0).to(device)
out = model(img)
probs = torch.softmax(out, dim=1).squeeze().detach().cpu().numpy()
return probs

# Sample image manipulation and prediction


manipulations = ["noise", "blur", "rotate"]
original_img, label = test_dataset[8]
print("True Label:", label)

# Assuming you have the model trained on CIFAR-10


original_probs = predict(original_img, model, device)

print("Original Image Prediction Probabilities:", original_probs)

for mode in manipulations:


mod_img = manipulate_image(original_img, mode)
probs = predict(mod_img, model, device)
print(f"\nMode: {mode}")
print("Prediction Probabilities:", probs)

True Label: 3
Original Image Prediction Probabilities: [1.6800972e-07 6.1611257e-07 8.4793493e-05 9.9465758e-01 1.2911787e-03
2.8534168e-03 1.0606776e-03 5.1260820e-05 7.4985543e-08 1.9104161e-07]

Mode: noise
Prediction Probabilities: [1.7982727e-01 1.7654325e-05 1.0244109e-01 1.3019058e-02 5.8725649e-01
2.9313546e-02 1.1017997e-03 1.3212665e-04 8.3206937e-02 3.6840788e-03]

Mode: blur
Prediction Probabilities: [1.80031568e-01 1.76791855e-05 1.02136604e-01 1.30106555e-02
5.87525189e-01 2.92738657e-02 1.09828799e-03 1.32296831e-04
8.30881745e-02 3.68571328e-03]

Mode: rotate
Prediction Probabilities: [1.8149871e-01 1.7627286e-05 1.0185424e-01 1.2813091e-02 5.8555746e-01
2.9116735e-02 1.0882209e-03 1.3150575e-04 8.4206358e-02 3.7160548e-03]

E. Report the metrics accuracy, confusion matrix.

from sklearn.metrics import confusion_matrix, accuracy_score


import seaborn as sns
import matplotlib.pyplot as plt

# Function to evaluate the model on the dataset


def evaluate(model, dataloader, device):
model.eval()
all_preds, all_labels = [], []
with torch.no_grad():
for imgs, lbls in dataloader:
imgs, lbls = imgs.to(device), lbls.to(device)
out = model(imgs)
preds = torch.argmax(out, dim=1)
all_preds.extend(preds.cpu().numpy())
all_labels.extend(lbls.cpu().numpy())

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 24/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
# Compute accuracy and confusion matrix
acc = accuracy_score(all_labels, all_preds)
cm = confusion_matrix(all_labels, all_preds)
return acc, cm

# Evaluate on the test set


acc, cm = evaluate(model, test_loader, device)
print("Test Accuracy:", acc)

# Plot confusion matrix using seaborn


plt.figure(figsize=(10, 8))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=range(10), yticklabels=range(10))
plt.title("Confusion Matrix")
plt.xlabel("Predicted")
plt.ylabel("True")
plt.show()

Test Accuracy: 0.7468

F. Consider 10 random test samples in each dataset and visualize their corresponding predictions.

def show_predictions(model, test_dataset, device):


model.eval()
indices = random.sample(range(len(test_dataset)), 10)
fig, axs = plt.subplots(2, 5, figsize=(12, 5))

for i, idx in enumerate(indices):


img, label = test_dataset[idx]
img_input = img.unsqueeze(0).to(device)

with torch.no_grad():
pred = torch.argmax(model(img_input)).item()

# Convert from CHW to HWC for visualization


img_np = img.permute(1, 2, 0).cpu().numpy()

axs[i//5, i%5].imshow(img_np)
axs[i//5, i%5].set_title(f"True: {label}, Pred: {pred}")
axs[i//5, i%5].axis('off')

plt.suptitle("Random Test Samples and Predictions (CIFAR-10)")


plt.tight_layout()
plt.show()

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 25/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

# Call the function


show_predictions(model, test_dataset, device)

WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255]
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255]
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255]
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255]
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255]
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255]
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255]
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255]
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255]
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255]

G. Vary the number of convolutional layers, sub-sampling layers, also modify the hyperparameters and report the accuracies for the best
three cases.

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import confusion_matrix, accuracy_score
import random

def create_model(conv_layers, num_filters, kernel_size, pool_size, fc_units, lr):


class ModifiedLeNet(nn.Module):
def __init__(self):
super(ModifiedLeNet, self).__init__()
self.conv_layers = nn.ModuleList()
in_channels = 3

for i in range(conv_layers):
self.conv_layers.append(nn.Conv2d(in_channels, num_filters[i], kernel_size, padding=1))
in_channels = num_filters[i]

self.pool = nn.MaxPool2d(pool_size, pool_size)


self.to(device)
# Dynamically compute flattened size
with torch.no_grad():
dummy_input = torch.zeros(1, 3, 32, 32).to(device)
x = dummy_input
for conv_layer in self.conv_layers:
x = self.pool(F.relu(conv_layer(x)))
self.flattened_size = x.view(1, -1).size(1)

self.fc1 = nn.Linear(self.flattened_size, fc_units[0])


self.fc2 = nn.Linear(fc_units[0], fc_units[1])
self.fc3 = nn.Linear(fc_units[1], 10)

def forward(self, x):

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 26/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
for conv_layer in self.conv_layers:
x = self.pool(F.relu(conv_layer(x)))
x = x.view(-1, self.flattened_size)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return self.fc3(x)

model = ModifiedLeNet().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=lr)
return model, criterion, optimizer

def evaluate(model, dataloader):


model.eval()
all_preds, all_labels = [], []
with torch.no_grad():
for imgs, lbls in dataloader:
imgs, lbls = imgs.to(device), lbls.to(device)
out = model(imgs)
preds = torch.argmax(out, dim=1)
all_preds.extend(preds.cpu().numpy())
all_labels.extend(lbls.cpu().numpy())
acc = accuracy_score(all_labels, all_preds)
cm = confusion_matrix(all_labels, all_preds)
return acc, cm

configs = [
{'conv_layers': 3, 'num_filters': [32, 64, 128], 'kernel_size': 3, 'pool_size': 2, 'fc_units': [512, 256], 'lr': 0.0005}
{'conv_layers': 2, 'num_filters': [16, 32], 'kernel_size': 5, 'pool_size': 2, 'fc_units': [256, 128], 'lr': 0.001},
{'conv_layers': 4, 'num_filters': [16, 32, 64, 128], 'kernel_size': 3, 'pool_size': 2, 'fc_units': [512, 256], 'lr': 0.0
]

best_accuracy = 0
best_model_id = -1

for idx, config in enumerate(configs):


print(f"\n=== Training model {idx + 1} with config: {config} ===")
model, criterion, optimizer = create_model(**config)

train_losses, val_losses = [], []

for epoch in range(10):


model.train()
train_loss = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
out = model(images)
loss = criterion(out, labels)
loss.backward()
optimizer.step()
train_loss += loss.item()

model.eval()
val_loss = 0
with torch.no_grad():
for images, labels in val_loader:
images, labels = images.to(device), labels.to(device)
out = model(images)
val_loss += criterion(out, labels).item()

train_losses.append(train_loss / len(train_loader))
val_losses.append(val_loss / len(val_loader))
print(f"Epoch {epoch+1}: Train Loss = {train_losses[-1]:.4f}, Val Loss = {val_losses[-1]:.4f}")

# Evaluate on test set


acc, cm = evaluate(model, test_loader)
print(f" Test Accuracy (Model {idx + 1}): {acc:.4f}")
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.title(f"Confusion Matrix (Model {idx+1})")
plt.xlabel("Predicted")
plt.ylabel("True")
plt.show()

# Keep track of best model


if acc > best_accuracy:
best_accuracy = acc
best_model_id = idx + 1

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 27/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
print(f"\n 🏆 Best model is Model {best_model_id} with accuracy: {best_accuracy:.4f}")

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 28/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

=== Training model 1 with config: {'conv_layers': 3, 'num_filters': [32, 64, 128], 'kernel_size': 3, 'pool_size': 2, 'fc
Epoch 1: Train Loss = 1.5049, Val Loss = 1.2024
Epoch 2: Train Loss = 1.0885, Val Loss = 0.9798
Epoch 3: Train Loss = 0.8811, Val Loss = 0.8805
Epoch 4: Train Loss = 0.7294, Val Loss = 0.8168
Epoch 5: Train Loss = 0.6098, Val Loss = 0.7791
Epoch 6: Train Loss = 0.5063, Val Loss = 0.7410
Epoch 7: Train Loss = 0.4013, Val Loss = 0.8177
Epoch 8: Train Loss = 0.3080, Val Loss = 0.8581
Epoch 9: Train Loss = 0.2239, Val Loss = 0.9416


Epoch 10: Train Loss = 0.1572, Val Loss = 1.0282
Test Accuracy (Model 1): 0.7450

=== Training model 2 with config: {'conv_layers': 2, 'num_filters': [16, 32], 'kernel_size': 5, 'pool_size': 2, 'fc_unit
Epoch 1: Train Loss = 1.5156, Val Loss = 1.2191
Epoch 2: Train Loss = 1.1273, Val Loss = 1.0878
Epoch 3: Train Loss = 0.9578, Val Loss = 0.9795
Epoch 4: Train Loss = 0.8278, Val Loss = 0.9674
Epoch 5: Train Loss = 0.7187, Val Loss = 0.9315
Epoch 6: Train Loss = 0.6231, Val Loss = 0.9855
Epoch 7: Train Loss = 0.5389, Val Loss = 0.9852
Epoch 8: Train Loss = 0.4502, Val Loss = 1.0727
Epoch 9: Train Loss = 0.3815, Val Loss = 1.1674


Epoch 10: Train Loss = 0.3162, Val Loss = 1.2403
Test Accuracy (Model 2): 0.6775

=== Training model 3 with config: {'conv_layers': 4, 'num_filters': [16, 32, 64, 128], 'kernel_size': 3, 'pool_size': 2,
Epoch 1: Train Loss = 1.8493, Val Loss = 1.6419
Epoch 2: Train Loss = 1.5716, Val Loss = 1.5152
Epoch 3: Train Loss = 1.4698, Val Loss = 1.4160
Epoch 4: Train Loss = 1.3935, Val Loss = 1.3476
Epoch 5: Train Loss = 1.3230, Val Loss = 1.3299
Epoch 6: Train Loss = 1.2658, Val Loss = 1.2370
Epoch 7: Train Loss = 1.2152, Val Loss = 1.2022
Epoch 8: Train Loss = 1.1727, Val Loss = 1.1651
Epoch 9: Train Loss = 1.1280, Val Loss = 1.1706
Epoch 10: Train Loss = 1.0877, Val Loss = 1.1514
https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 29/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab

Epoch 10: Train Loss 1.0877, Val Loss 1.1514
Test Accuracy (Model 3): 0.5906

🏆 Best model is Model 1 with accuracy: 0.7450

Q4) Explain overfitting in a neural network?

A. Train a FNN using MNIST dataset deliberately to overfit.

B. Plot training and testing loss curves.

import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
import pandas as pd
import matplotlib.pyplot as plt

def build_and_train_model_MNIST(learning_rate=0.001, batch_size=32, epochs=20):


# Load MNIST dataset
(X_train_full, y_train_full), (X_test, y_test) = mnist.load_data()
X_train_full = X_train_full.astype('float32') / 255.0
X_test = X_test.astype('float32') / 255.0

X_train_full = X_train_full.reshape(X_train_full.shape[0], -1)


X_test = X_test.reshape(X_test.shape[0], -1)
y_train_full = to_categorical(y_train_full, 10)
y_test = to_categorical(y_test, 10)
X_train, X_test, y_train, y_test = train_test_split(X_train_full, y_train_full, test_size=0.3, random_state=42)

model = models.Sequential()
model.add(layers.InputLayer(input_shape=(784,)))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(128, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
model.compile(optimizer=optimizer, loss='categorical_crossentropy', metrics=['accuracy'])
history = model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, validation_data=(X_test, y_test))

test_loss, test_acc = model.evaluate(X_test, y_test)


print(f"Test accuracy: {test_acc:.4f}")
y_pred = model.predict(X_test)
y_pred_classes = tf.argmax(y_pred, axis=1)
y_test_classes = tf.argmax(y_test, axis=1)
cm = confusion_matrix(y_test_classes, y_pred_classes)

# Save confusion matrix as a CSV file


cm_df = pd.DataFrame(cm, index=[f'True_{i}' for i in range(10)], columns=[f'Pred_{i}' for i in range(10)])
cm_df.to_csv('confusion_matrix_MNIST.csv')
print(f"Confusion matrix saved to confusion_matrix.csv")

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 30/31
18/04/2025, 15:41 EE24M306_Programming assignment - 3.ipynb - Colab
# Plot training and testing loss curves
plt.figure(figsize=(10, 6))
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Testing Loss')
plt.title('Training and Testing Loss Curves')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.grid(True)
plt.show()

build_and_train_model_MNIST(learning_rate=0.001, batch_size=32, epochs=20)

Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz


11490434/11490434 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step
/usr/local/lib/python3.11/dist-packages/keras/src/layers/core/input_layer.py:27: UserWarning: Argument `input_shape` is
warnings.warn(
Epoch 1/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 40s 26ms/step - accuracy: 0.8795 - loss: 0.3868 - val_accuracy: 0.9614 - val_loss: 0.1249
Epoch 2/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 37s 24ms/step - accuracy: 0.9696 - loss: 0.0985 - val_accuracy: 0.9697 - val_loss: 0.1036
Epoch 3/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 31s 24ms/step - accuracy: 0.9791 - loss: 0.0653 - val_accuracy: 0.9730 - val_loss: 0.0937
Epoch 4/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 39s 22ms/step - accuracy: 0.9832 - loss: 0.0498 - val_accuracy: 0.9676 - val_loss: 0.1207
Epoch 5/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 42s 23ms/step - accuracy: 0.9880 - loss: 0.0384 - val_accuracy: 0.9726 - val_loss: 0.1086
Epoch 6/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 23s 18ms/step - accuracy: 0.9909 - loss: 0.0296 - val_accuracy: 0.9718 - val_loss: 0.1177
Epoch 7/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 41s 18ms/step - accuracy: 0.9908 - loss: 0.0301 - val_accuracy: 0.9775 - val_loss: 0.0880
Epoch 8/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 21s 16ms/step - accuracy: 0.9927 - loss: 0.0233 - val_accuracy: 0.9777 - val_loss: 0.0989
Epoch 9/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 41s 16ms/step - accuracy: 0.9927 - loss: 0.0242 - val_accuracy: 0.9745 - val_loss: 0.1207
Epoch 10/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 21s 16ms/step - accuracy: 0.9929 - loss: 0.0185 - val_accuracy: 0.9778 - val_loss: 0.0995
Epoch 11/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 42s 17ms/step - accuracy: 0.9952 - loss: 0.0160 - val_accuracy: 0.9768 - val_loss: 0.1070
Epoch 12/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 21s 16ms/step - accuracy: 0.9957 - loss: 0.0148 - val_accuracy: 0.9793 - val_loss: 0.0969
Epoch 13/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 42s 17ms/step - accuracy: 0.9958 - loss: 0.0145 - val_accuracy: 0.9776 - val_loss: 0.1221
Epoch 14/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 39s 16ms/step - accuracy: 0.9952 - loss: 0.0153 - val_accuracy: 0.9767 - val_loss: 0.1307
Epoch 15/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 40s 15ms/step - accuracy: 0.9947 - loss: 0.0164 - val_accuracy: 0.9804 - val_loss: 0.1188
Epoch 16/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 22s 16ms/step - accuracy: 0.9973 - loss: 0.0087 - val_accuracy: 0.9774 - val_loss: 0.1430
Epoch 17/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 20s 15ms/step - accuracy: 0.9944 - loss: 0.0176 - val_accuracy: 0.9784 - val_loss: 0.1257
Epoch 18/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 24s 18ms/step - accuracy: 0.9967 - loss: 0.0105 - val_accuracy: 0.9803 - val_loss: 0.1090
Epoch 19/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 39s 16ms/step - accuracy: 0.9972 - loss: 0.0093 - val_accuracy: 0.9803 - val_loss: 0.1082
Epoch 20/20
1313/1313 ━━━━━━━━━━━━━━━━━━━━ 40s 15ms/step - accuracy: 0.9972 - loss: 0.0102 - val_accuracy: 0.9769 - val_loss: 0.1501
563/563 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.9774 - loss: 0.1519
Test accuracy: 0.9769
563/563 ━━━━━━━━━━━━━━━━━━━━ 2s 4ms/step
Confusion matrix saved to confusion_matrix.csv

https://colab.research.google.com/drive/117fmCnbOusTtHwL_eseuesaHT7W4EBsB#scrollTo=BYLcfi2lX9ST&printMode=true 31/31

You might also like