Fully Connected Neural Network for Image Classification


Bikash Santra

Indian Statistical Institute, Kolkata


Importing Libraries

In [1]:
# Pytorch libraries
import torch
import torchvision
import torchvision.transforms as transforms

# For displaying images and numpy operations
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline

# For CNN Purpose
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F

# Loss function and optimizer
import torch.optim as optim

import scipy.io

Choosing CUDA Device (GPU) and Clearing GPU Cache Memory

In [2]:
torch.cuda.set_device(1)
torch.cuda.empty_cache()

Downloading and Loading MNIST Dataset

In [3]:
# Dataloader

# For fully connected network 20*20
transform = transforms.Compose(
    [transforms.Resize(20),
     transforms.ToTensor(),
     transforms.Normalize((0.5,), (0.5,))])

trainset = torchvision.datasets.MNIST(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32,
                                          shuffle=True, num_workers=4)

testset = torchvision.datasets.MNIST(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=32,
                                         shuffle=False, num_workers=4)

mnistClasses = ('zero', 'one', 'two', 'three',
           'four', 'five', 'six', 'seven', 'eight', 'nine')

Visualizing a Mini-batch of Train Images of the Dataset

In [4]:
# Visualizing the train images
def imshow(inp, title=None):
    """Imshow for Tensor."""
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.5])
    std = np.array([0.5])
    inp = std * inp + mean
    plt.imshow(inp)
    if title is not None:
        plt.title(title)
    plt.pause(0.001)  # pause a bit so that plots are updated


# Get a batch of training data
inputs, classes = next(iter(trainloader))
print(inputs.shape)

# Make a grid from batch
out = torchvision.utils.make_grid(inputs)

imshow(out, title=[mnistClasses[x] for x in classes])
torch.Size([32, 1, 20, 20])

Defining a Fully Connected Neural Network

In [5]:
# Network
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(20*20, 800)
        self.relu1 = nn.ReLU()
        self.drop1 = nn.Dropout()
        self.fc2 = nn.Linear(800, 800)
        self.relu2 = nn.ReLU()
        self.drop2 = nn.Dropout()
        self.fc3 = nn.Linear(800, 10)
        self.sm = nn.Softmax(dim=1)

    def forward(self, x):
#         print(x.size())
        x = x.view(-1,20*20)
#         print(x.size())
        x = self.fc1(x)
#         print(x.size())
        x = self.relu1(x)
#         print(x.size())
        x = self.drop1(x)
#         print(x.size())
        x = self.fc2(x)
#         print(x.size())
        x = self.relu2(x)
#         print(x.size())
        
        x = self.drop2(x)
#         print(x.size())
        x = self.fc3(x) 
#         print(x.size())
        x = self.sm(x)
#         print(x.shape[1])
        return (x)

Creating and Initialiizing the Network with Random Weights

In [6]:
# Network object Creation    
net = Net()
print(net)
Net(
  (fc1): Linear(in_features=400, out_features=800, bias=True)
  (relu1): ReLU()
  (drop1): Dropout(p=0.5)
  (fc2): Linear(in_features=800, out_features=800, bias=True)
  (relu2): ReLU()
  (drop2): Dropout(p=0.5)
  (fc3): Linear(in_features=800, out_features=10, bias=True)
  (sm): Softmax()
)

Sending Network to the GPU

In [7]:
# Using GPU, if available
use_gpu = torch.cuda.is_available()
print(use_gpu)

if use_gpu:
    net.cuda()
True

Regressing a Mini-batch of Train Images using Randomly Initialized Network

In [8]:
inputs, classes = next(iter(trainloader))
inputs = inputs.cuda()
out = net(inputs)
print(out)
tensor([[0.1016, 0.1065, 0.1143, 0.0826, 0.1225, 0.0850, 0.1162, 0.1147, 0.0750,
         0.0817],
        [0.1085, 0.0703, 0.1123, 0.1035, 0.0791, 0.1194, 0.1079, 0.0827, 0.1034,
         0.1128],
        [0.0977, 0.0955, 0.1128, 0.0844, 0.0778, 0.0832, 0.1537, 0.0850, 0.0938,
         0.1162],
        [0.0960, 0.0703, 0.1172, 0.0775, 0.1003, 0.1167, 0.1358, 0.1079, 0.0644,
         0.1141],
        [0.1002, 0.1052, 0.0989, 0.1057, 0.0843, 0.1013, 0.1147, 0.1048, 0.0993,
         0.0856],
        [0.1170, 0.1246, 0.0870, 0.0816, 0.0793, 0.1048, 0.1199, 0.0928, 0.0812,
         0.1117],
        [0.0935, 0.1566, 0.1029, 0.0900, 0.0869, 0.0993, 0.0948, 0.1005, 0.0930,
         0.0823],
        [0.1024, 0.0824, 0.1044, 0.0907, 0.1026, 0.0971, 0.1402, 0.1053, 0.1045,
         0.0705],
        [0.1156, 0.1347, 0.0993, 0.0760, 0.0830, 0.0900, 0.1341, 0.1021, 0.0772,
         0.0880],
        [0.1055, 0.1230, 0.1003, 0.0861, 0.0790, 0.0863, 0.1137, 0.1073, 0.0732,
         0.1258],
        [0.1067, 0.1148, 0.0761, 0.0804, 0.0920, 0.1209, 0.0880, 0.1028, 0.0933,
         0.1250],
        [0.1081, 0.0818, 0.1127, 0.1007, 0.0944, 0.1032, 0.1410, 0.0772, 0.0936,
         0.0875],
        [0.1119, 0.1061, 0.1039, 0.0859, 0.0915, 0.0886, 0.1195, 0.0704, 0.0840,
         0.1383],
        [0.0960, 0.0841, 0.1257, 0.0985, 0.0864, 0.0959, 0.1230, 0.1084, 0.0846,
         0.0974],
        [0.0961, 0.1109, 0.0959, 0.0880, 0.0938, 0.1024, 0.0947, 0.0898, 0.1119,
         0.1165],
        [0.1033, 0.1063, 0.1021, 0.0767, 0.1105, 0.0970, 0.1134, 0.0939, 0.0910,
         0.1058],
        [0.0922, 0.1068, 0.1503, 0.0794, 0.1105, 0.0913, 0.1012, 0.0870, 0.0937,
         0.0875],
        [0.1193, 0.1104, 0.1256, 0.0825, 0.0837, 0.1027, 0.1071, 0.0922, 0.0820,
         0.0944],
        [0.1080, 0.0961, 0.1100, 0.0835, 0.0717, 0.1086, 0.1147, 0.1017, 0.1074,
         0.0982],
        [0.1036, 0.0876, 0.0995, 0.0763, 0.0837, 0.1079, 0.1399, 0.0772, 0.1193,
         0.1050],
        [0.1057, 0.1161, 0.1123, 0.0736, 0.0935, 0.1087, 0.0983, 0.0865, 0.1133,
         0.0919],
        [0.1030, 0.0806, 0.1095, 0.0783, 0.0851, 0.1146, 0.1079, 0.1004, 0.1096,
         0.1108],
        [0.1006, 0.1177, 0.1114, 0.0818, 0.1111, 0.1064, 0.1079, 0.1027, 0.0828,
         0.0775],
        [0.1111, 0.0964, 0.1653, 0.0649, 0.0963, 0.1161, 0.1065, 0.0906, 0.0880,
         0.0649],
        [0.0891, 0.1062, 0.1223, 0.0924, 0.0972, 0.0839, 0.1323, 0.0953, 0.0891,
         0.0921],
        [0.1367, 0.1034, 0.0976, 0.0864, 0.0923, 0.1209, 0.1129, 0.0926, 0.0772,
         0.0801],
        [0.0847, 0.1100, 0.0972, 0.1043, 0.0905, 0.0990, 0.1209, 0.0792, 0.1034,
         0.1108],
        [0.0948, 0.0967, 0.1094, 0.1091, 0.0855, 0.1048, 0.1623, 0.0608, 0.0842,
         0.0923],
        [0.1032, 0.1083, 0.1089, 0.0903, 0.0845, 0.1090, 0.1320, 0.0914, 0.0854,
         0.0871],
        [0.0841, 0.1297, 0.1052, 0.0849, 0.0895, 0.1296, 0.0865, 0.0887, 0.0935,
         0.1082],
        [0.1276, 0.0836, 0.1000, 0.0603, 0.0941, 0.1184, 0.1192, 0.0919, 0.0828,
         0.1220],
        [0.0767, 0.0913, 0.1382, 0.0988, 0.0726, 0.1057, 0.1485, 0.0853, 0.1028,
         0.0801]], device='cuda:1', grad_fn=<SoftmaxBackward>)

Defining Loss Function and Optimizer

In [9]:
# Let’s use a Classification Cross-Entropy loss and SGD with momentum
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1)

Training the Network

In [10]:
# This is when things start to get interesting. 
# We simply have to loop over our data iterator, and feed the inputs to the network and optimize
net.train(True)

epochs = 10
lossInEpochs = np.array([])
xaxis = range(epochs)

for epoch in range(epochs):  # loop over the dataset multiple times
    
    running_loss = 0.0
    total_loss = 0.0
    miniBatchEpoch = 0
    
    scheduler.step()
    
    for i, data in enumerate(trainloader, 0):
        # get the inputs
        inputs, labels = data
        # wrap them in Variable
        if use_gpu:
            inputs, labels =inputs.cuda(), labels.cuda()

        # zero the parameter gradients
        optimizer.zero_grad()
        
        # forward + backward + optimize
        outputs = net(inputs)
        
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        
        # print statistics
        running_loss += loss.item()
        total_loss += loss.item()
        miniBatchEpoch += 1
        if i % 1000 == 999:    # print every 100 mini-batches
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 1000))
            running_loss = 0.0
            
            for param_group in optimizer.param_groups:
                print(param_group['lr'])
            
    lossInEpochs = np.hstack([lossInEpochs, total_loss/miniBatchEpoch])
    
print('Finished Training')
[1,  1000] loss: 1.896
0.01
[2,  1000] loss: 1.631
0.01
[3,  1000] loss: 1.567
0.01
[4,  1000] loss: 1.546
0.01
[5,  1000] loss: 1.535
0.01
[6,  1000] loss: 1.522
0.001
[7,  1000] loss: 1.516
0.001
[8,  1000] loss: 1.514
0.001
[9,  1000] loss: 1.514
0.001
[10,  1000] loss: 1.513
0.001
Finished Training

Saving the Epoch Error

In [11]:
filePath = './epochWiseTrainError/epochError_FCNN_MNIST.mat'
scipy.io.savemat(filePath, mdict={'lossInEpochs': lossInEpochs})

Saving the Trained Model

In [12]:
torch.save(net.state_dict(), './trainedModels/model_FCNN_MNIST.pth')

Plotting Loss vs. Epoch

In [13]:
plt.plot(xaxis,lossInEpochs)
Out[13]:
[<matplotlib.lines.Line2D at 0x7f666041ed10>]

Analyzing Performance

In [14]:
# Set model to evaluation mode
net.eval()
Out[14]:
Net(
  (fc1): Linear(in_features=400, out_features=800, bias=True)
  (relu1): ReLU()
  (drop1): Dropout(p=0.5)
  (fc2): Linear(in_features=800, out_features=800, bias=True)
  (relu2): ReLU()
  (drop2): Dropout(p=0.5)
  (fc3): Linear(in_features=800, out_features=10, bias=True)
  (sm): Softmax()
)

Dataset-wise Accuracy

In [15]:
correctR = np.array([0], dtype='float')
total = np.array([0], dtype='float')

for data in testloader:
    images, labels = data
    
    outputs = net(images.cuda())
    outputs = outputs.cpu()
    _, predicted = torch.max(outputs.data, 1)
    total += labels.size(0)
    predicted = predicted.data.cpu().numpy()
    labels = labels.data.numpy()
    correctR += (predicted == labels).sum()

print('correctR, totalAccuracy of the network on the 10000 test images: %.4f%%' % (
    100 * correctR / total))
correctR, totalAccuracy of the network on the 10000 test images: 96.0600%

Class-wise Accuracy

In [16]:
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
for data in testloader:
    images, labels = data
    
    outputs = net(images.cuda())
    outputs = outputs.cpu()
    _, predicted = torch.max(outputs.data, 1)
    predicted = predicted.data.cpu().numpy()
    labels = labels.data.numpy()
    c = (predicted == labels).squeeze()
    for i in range(4):
        label = labels[i]
        class_correct[label] += c[i]
        class_total[label] += 1

for i in range(10):
    print('Accuracy of %5s : %.4f%%' % (
        mnistClasses[i], 100 * class_correct[i] / class_total[i]))
Accuracy of  zero : 97.6562%
Accuracy of   one : 98.5185%
Accuracy of   two : 95.0355%
Accuracy of three : 98.3871%
Accuracy of  four : 95.6522%
Accuracy of  five : 91.0112%
Accuracy of   six : 96.9231%
Accuracy of seven : 94.7826%
Accuracy of eight : 96.4602%
Accuracy of  nine : 92.8058%