In [1]:
import pickle, gzip, numpy as np
import torch, torch.nn as nn, torch.nn.functional as F
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import tqdm # a package for making progress bar

$\color{Orange}{\text{Load Data}}$

In [2]:
def loadData(src):
    '''
    return: train_x - 2D Numpy array (n, d) where each row is an image
            train_y - 1D Numpy array (n, ) where each row is a label
            test_x  - 2D Numpy array (n, d) where each row is an image
            test_y  - 1D Numpy array (n, ) where each row is a label
    '''
    f = gzip.open(src, 'rb')
    train_set, valid_set, test_set = pickle.load(f, encoding='latin1')
    
    f.close()
    train_x, train_y = train_set
    valid_x, valid_y = valid_set
    test_x, test_y = test_set
    print('Originally, train len (n): '+str(len(train_y))+'  '+'valid len (n): '+str(len(valid_y))+'  '+'test len (n): '+str(len(test_y)))
    train_x = np.vstack((train_x, valid_x))
    train_y = np.append(train_y, valid_y)
    print('Now, train len (n): '+str(len(train_y))+'  '+'test len (n): '+str(len(test_y)))
    print('Each vec len (p): '+str(train_x.shape[1]))
    return (train_x, train_y, test_x, test_y)
In [2]:
def loadOverlapData(path_to_data_dir, use_mini_dataset, img_rows=42, img_cols=28):
    '''
    paras: path_to_data_dir - String
           use_mini_dataset - Boolean
           img_rows, img_cols - int - define the size of an image
    '''
    if use_mini_dataset:
        exten = '_mini'
    else:
        exten = ''
    f = gzip.open(path_to_data_dir + 'train_multi_digit' + exten + '.pkl.gz', 'rb')
    X_train = pickle.load(f, encoding='latin1')
    f.close()
    X_train =  np.reshape(X_train, (len(X_train), 1, img_rows, img_cols))
    
    f = gzip.open(path_to_data_dir + 'test_multi_digit' + exten +'.pkl.gz', 'rb')
    X_test = pickle.load(f, encoding='latin1')
    f.close()
    X_test =  np.reshape(X_test, (len(X_test),1, img_rows, img_cols))
    
    f = gzip.open(path_to_data_dir + 'train_labels' + exten +'.txt.gz', 'rb')
    y_train = np.loadtxt(f)
    f.close()
    
    f = gzip.open(path_to_data_dir +'test_labels' + exten + '.txt.gz', 'rb')
    y_test = np.loadtxt(f)
    f.close()
    
    print('X_Train shape: '+str(X_train.shape)+'  '+'y_train shape: '+str(y_train.shape))
    print('X_Test shape: '+str(X_test.shape)+'  '+'y_test shape: '+str(y_test.shape))
    return X_train, y_train, X_test, y_test
In [3]:
def plotImg(X,n=0,p=0):
    '''
    return: image(s) with n rows & p cols
    '''
    num_images = X.shape[0]
    if n == 0 & p == 0:
        num_rows = np.floor(np.sqrt(num_images)) # floor: 2.5 -> 2
        num_cols = np.ceil(num_images/num_rows) # ceil: 2.5 -> 3
    else:
        if n*p < num_images:
            return print("Wrong n, p values")
        num_rows, num_cols = n, p
    plt.figure(figsize=(20,10))
    for i in range(num_images):
        reshaped_image = X[i,:].reshape(28,28)
        plt.subplot(num_rows, num_cols, i+1)
        plt.imshow(reshaped_image, cmap = cm.Greys_r)
        plt.axis('off')
    plt.show()

$\color{Orange}{\text{Preprocess Data}}$

In [4]:
# Split into train and dev
def splitData(X, y, is_overlap):
    split_index = int(9 * len(X) / 10)
    
    X_train, X_dev = X[:split_index], X[split_index:]        # 9/10 train set -> n = 54000
    if is_overlap:
        y_train = [y[0][:split_index], y[1][:split_index]]
        y_dev = [y[0][split_index:], y[1][split_index:]]
    else:
        y_train, y_dev = y[:split_index], y[split_index:]    # 1/10 dev set -> n = 6000

    permutation = np.array([i for i in range(len(X_train))]) # an array of nums from 0 to 54000
    np.random.shuffle(permutation)                           # shuffle so as to reorder the data points
    X_train = [X_train[i] for i in permutation]
    if is_overlap:
        y_train = [[y_train[0][i] for i in permutation], [y_train[1][i] for i in permutation]] 
    else:
        y_train = [y_train[i] for i in permutation]
    return X_train, y_train, X_dev, y_dev
In [5]:
def batchifyData(X, y, batch_size, is_overlap):
    """
    Takes a set of data points and labels and groups them into batches.
    
    paras: X - a list of inputs; 
           y - a list of outputs; 
           batch_size - String - how many data points in each batch
           is_overlap - Boolean - whether two digits overlap in an img
    
    return: a list of dict 
            [{'x': tensor(mat1,...,matn), 'y': tensor(num1,...,numn)}, ...]
    """
    # Only take batch_size chunks. The remainder will be dropped.
    N = int(len(X) / batch_size) * batch_size # how many batches we have
    batches = []
    for i in range(0, N, batch_size):
        if is_overlap:
            batches.append({
                'x': torch.tensor(X[i:i+batch_size], dtype=torch.float32),
                'y': torch.tensor([y[0][i:i + batch_size],
                                   y[1][i:i + batch_size]],
                                   dtype=torch.int64)
            })
        else:
            batches.append({
                'x': torch.tensor(X[i:i+batch_size], dtype=torch.float32),
                'y': torch.tensor(y[i:i+batch_size], dtype=torch.long
            )})
    return batches
In [10]:
# How a batch looks like
train_batches[0]
Out[10]:
{'x': tensor([[0., 0., 0.,  ..., 0., 0., 0.],
         [0., 0., 0.,  ..., 0., 0., 0.],
         [0., 0., 0.,  ..., 0., 0., 0.],
         ...,
         [0., 0., 0.,  ..., 0., 0., 0.],
         [0., 0., 0.,  ..., 0., 0., 0.],
         [0., 0., 0.,  ..., 0., 0., 0.]]),
 'y': tensor([2, 1, 1, 4, 4, 4, 9, 1, 0, 1, 1, 9, 3, 9, 0, 1, 3, 1, 3, 2, 8, 4, 9, 9,
         8, 7, 1, 6, 3, 0, 3, 9])}

$\color{Orange}{\text{Modeling}}$

In [104]:
def trainModel(train_data, dev_data, model, opti, is_overlap, n_epochs=30):
    # original paras: train_data, dev_data, model, is_overlap, lr=0.01, momentum=0.9, nesterov=False, n_epochs=30
    """
    Train a model for N epochs given data and hyper-params.
    
    return: a list of dictionary with [{'Train Loss','Validation Loss'},{'Train Accuracy','Validation Accuracy'}]
    """
    # optimize: SGD, Adam
#     if opti == 'sdg':
#         optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, nesterov=True)
#     elif opti == 'adadelta':
#         optimizer = torch.optim.Adadelta(model.parameters(), lr=1.0, rho=0.9, eps=1e-06, weight_decay=0)

#     optimizer = torch.optim.Adadelta(model.parameters(), lr=1.0, rho=0.9, eps=1e-06, weight_decay=0)
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
#     optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, nesterov=False)

#     optimizer = torch.optim.SGD(model.parameters(), lr=lr, momentum=momentum, nesterov=nesterov)
    train_losses, train_accuracies, val_losses, val_accuracies = [],[],[],[]
    
    for epoch in tqdm.tqdm(range(1, n_epochs+1)):
        print("-------------\nEpoch {}:\n".format(epoch))

        # Run **training***
        train_loss, train_acc, train_errors = runEpoch(train_data, model.train(), optimizer, is_overlap)
        # Run **validation**
        val_loss, val_acc, val_errors = runEpoch(dev_data, model.eval(), optimizer, is_overlap)
        
        if is_overlap:
            print('Train | loss1: {:.6f}  accuracy1: {:.6f} | loss2: {:.6f}  accuracy2: {:.6f}'.format(train_loss[0], train_acc[0], train_loss[1], train_acc[1]))
            print('Valid | loss1: {:.6f}  accuracy1: {:.6f} | loss2: {:.6f}  accuracy2: {:.6f}'.format(val_loss[0], val_acc[0], val_loss[1], val_acc[1]))
        else:
            print('Train loss: {:.6f} | Train accuracy: {:.6f}'.format(train_loss, train_acc))
            print('Val loss:   {:.6f} | Val accuracy:   {:.6f}'.format(val_loss, val_acc))
        
        train_losses.append(train_loss)
        train_accuracies.append(train_acc)
        val_losses.append(val_loss)
        val_accuracies.append(val_acc)
        
        # Save model
        torch.save(model, 'mnist_model_fully_connected.pt')
        
    if is_overlap:
        tl, vl = list(map(list, zip(*train_losses))), list(map(list, zip(*val_losses)))
        ta, va = list(map(list, zip(*train_accuracies))), list(map(list, zip(*val_accuracies)))
        return [{'Train loss1':tl[0],'Validation loss1':vl[0]}, {'Train loss2':tl[1],'Validation loss2':vl[1]},
                {'Train accuracy1':ta[0],'Validation accuracy1':va[0]}, {'Train accuracy2':ta[1],'Validation accuracy2':va[1]}]
    else:
        return [{'Train loss':train_losses,'Validation loss':val_losses},
                {'Train accuracy':train_accuracies,'Validation accuracy':val_accuracies}]
In [61]:
def runEpoch(data, model, optimizer, is_overlap):
    """Train model for one pass of train data, and return loss, acccuracy"""
    if is_overlap:
        # Gather losses
        losses_first_label = []
        losses_second_label = []
        batch_accuracies_first = []
        batch_accuracies_second = []
        x_err, y1_err, y2_err, pred1_err, pred2_err = [],[],[],[],[]

        # If model is in train mode, use optimizer.
        is_training = model.training

        # Iterate through batches
        for batch in data:
            # Grab x and y
            x, y = batch['x'], batch['y']

            # Get output predictions
            out1, out2 = model(x)

            # Predict and store accuracy
            predictions_first_label = torch.argmax(out1, dim=1)
            predictions_second_label = torch.argmax(out2, dim=1)

            bool_arr1 = (predictions_first_label == y[0])
            bool_arr2 = (predictions_second_label == y[1])
            bool_arr = torch.tensor([i[0]|i[1] for i in zip(1-bool_arr1, 1-bool_arr2)])#,dtype=torch.uint8)

            # collect data with wrong predicted label
            ## need to convert pytorch tensor to list so as to append
            x_err += x[bool_arr].tolist()
            y1_err += y[0][bool_arr].tolist()
            y2_err += y[1][bool_arr].tolist()
            pred1_err += predictions_first_label[bool_arr].tolist()
            pred2_err += predictions_second_label[bool_arr].tolist()
            
            ## need to convert pytorch tensor to numpy array so as to apply np funcs
            accuracy1 = np.mean(bool_arr1.numpy())
            accuracy2 = np.mean(bool_arr2.numpy())
            batch_accuracies_first.append(accuracy1)
            batch_accuracies_second.append(accuracy2)

            # Compute loss
            loss1 = F.cross_entropy(out1, y[0])
            loss2 = F.cross_entropy(out2, y[1])
            losses_first_label.append(loss1.data.item())
            losses_second_label.append(loss2.data.item())

            # If training, do an update.
            if is_training:
                optimizer.zero_grad()
                joint_loss = 0.5 * (loss1 + loss2)
                joint_loss.backward()
                optimizer.step()

        # Calculate epoch level scores
        errors = (x_err, y1_err, y2_err, pred1_err, pred2_err)
        avg_loss = np.mean(losses_first_label), np.mean(losses_second_label)
        avg_accuracy = np.mean(batch_accuracies_first), np.mean(batch_accuracies_second)
        
    else:
        # Gather losses
        losses = []
        batch_accuracies = []
        x_err, y_err, pred_err = [],[],[]

        # If model is in train mode, use optimizer.
        is_training = model.training

        # Iterate through batches
        for batch in data:
            # Grab x and y
            x, y = batch['x'], batch['y']

            # Get output predictions
            out = model(x)

            # Predict and store accuracy
            predictions = torch.argmax(out, dim=1)

            bool_arr = (predictions == y)

            # collect data with wrong predicted label
            ## need to convert pytorch tensor to list so as to append
            x_err += x[1-bool_arr].tolist()
            y_err += y[1-bool_arr].tolist()
            pred_err += predictions[1-bool_arr].tolist()
            ## need to convert pytorch tensor to numpy array so as to apply np funcs
            accuracy = np.mean(bool_arr.numpy())
            batch_accuracies.append(accuracy)

            # Compute loss
            loss = F.cross_entropy(out, y)
            losses.append(loss.data.item())

            # If training, do an update.
            if is_training:
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()

        # Calculate epoch level scores
        avg_loss = np.mean(losses)
        avg_accuracy = np.mean(batch_accuracies)
        errors = (x_err, y_err, pred_err)
        
    return avg_loss, avg_accuracy, errors
In [8]:
class Flatten(nn.Module):
    """A custom layer that views an input as 1D."""
    def forward(self, input):
        return input.view(input.size(0), -1)

$\color{Orange}{\text{Utilities}}$

In [19]:
def processData(algo):
    '''
    return: a list of dictionary with [{'Train Loss','Validation Loss'},{'Train Accuracy','Validation Accuracy'}]
            a float - test loss
            a float - test accuracy
            a tuple - 3 lists - test x errors, test y errors, test pred errors
    '''
    if algo == 'FFNN':  # each image is a flattened vector
        X_train_dev_ = X_train_dev
        X_test_ = X_test
    elif algo == 'CNN':  # need to rehape the data back into a 1x28x28 image
        X_train_dev_ = np.reshape(X_train_dev, (X_train_dev.shape[0], 1, 28, 28))
        X_test_ = np.reshape(X_test, (X_test.shape[0], 1, 28, 28))
    
    # Split into train and dev
    X_train, y_train, X_dev, y_dev = splitData(X_train_dev_,y_train_dev)
    
    # Split dataset into batches
    train_batches = batchifyData(X_train, y_train, batch_size)
    dev_batches = batchifyData(X_dev, y_dev, batch_size)
    test_batches = batchifyData(X_test_, y_test, batch_size)
    
    # train model
    train_dev_loss_acc = train_model(train_batches, dev_batches, model, lr=lr, momentum=momentum, nesterov=nesterov, n_epochs=n_epochs)
    
    # Evaluate the model on test data
    test_loss, test_acc, test_errors = run_epoch(test_batches, model.eval(), None)
    print()
    print("Loss on test set:"  + str(test_loss) + " Accuracy on test set: " + str(test_acc))
    return train_dev_loss_acc, test_loss, test_acc, test_errors
In [77]:
# def plotRes(res_dict,title):
#     for key, values in res_dict.items():
#         plt.plot(values, marker='o', markersize=5, label=key)
#     plt.legend()
#     plt.title(title)
def plotRes(train_dev_loss_acc, title_size):
    list_len = len(train_dev_loss_acc)
    fig, axs = plt.subplots(1, list_len, figsize=(20,5))
    for i in range(list_len):
        title = []
        for key, values in train_dev_loss_acc[i].items():
            axs[i].plot(values, marker='o', markersize=5, label=key)
            title.append(key)
        axs[i].set_title(' vs '.join(title), fontsize=title_size)
        axs[i].set_xlabel('n epochs',fontsize=12)
        axs[i].legend(fontsize=15)

$\color{Orange}{\text{Set Parameters & Run Model}}$

In [77]:
np.random.seed(12321)  # for reproducibility
torch.manual_seed(12321)  # for reproducibility
X_train_dev, y_train_dev, X_test, y_test = loadData('mnist.pkl.gz')
Originally, train len (n): 50000  valid len (n): 10000  test len (n): 10000
Now, train len (n): 60000  test len (n): 10000
Each vec len (p): 784
In [158]:
plotImg(X_train_dev[:10],1,10)
In [13]:
# Model specification
batch_size = 128

## Hidden Layer Representation Size
unit_size = 128

## FFNN Model
ffnn = nn.Sequential(
    nn.Linear(784, unit_size),
    nn.LeakyReLU(),
    nn.Linear(unit_size, 10)
)

## CNN Model
cnn = nn.Sequential(          #  1  28  28
    nn.Conv2d(1, 32, (3, 3)), # 32  26  26
    nn.ReLU(),
    nn.MaxPool2d((2, 2)),     # 32  13  13
    nn.Conv2d(32, 64, (3,3)), # 64  11  11
    nn.ReLU(),
    nn.MaxPool2d((2,2)),      # 64   5   5
    Flatten(),                # 64*5*5   1
    nn.Linear(1600,unit_size),# 1600   128
    nn.Dropout(),
    nn.Linear(unit_size,10)   # 128     10
)

model = cnn        
lr = 0.1
momentum = 0.9
nesterov = False
n_epochs = 20
In [20]:
# test accuracy = 0.9204727564102564 when batch size = 128 lr = 0.1
train_dev_loss_acc, test_loss, test_acc, test_errors = processData('CNN')
  0%|          | 0/20 [00:00<?, ?it/s]
-------------
Epoch 1:

Train loss: 0.237640 | Train accuracy: 0.923972
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/torch/serialization.py:256: UserWarning: Couldn't retrieve source code for container of type Flatten. It won't be checked for correctness upon loading.
  "type " + obj.__name__ + ". It won't be checked "
  5%|▌         | 1/20 [00:45<14:29, 45.76s/it]
Val loss:   0.059037 | Val accuracy:   0.982167
-------------
Epoch 2:

Train loss: 0.071837 | Train accuracy: 0.978492
 10%|â–ˆ         | 2/20 [01:32<13:50, 46.16s/it]
Val loss:   0.051500 | Val accuracy:   0.986073
-------------
Epoch 3:

Train loss: 0.057147 | Train accuracy: 0.982594
 15%|█▌        | 3/20 [02:23<13:29, 47.62s/it]
Val loss:   0.049102 | Val accuracy:   0.986923
-------------
Epoch 4:

Train loss: 0.049655 | Train accuracy: 0.984208
 20%|██        | 4/20 [03:12<12:45, 47.83s/it]
Val loss:   0.044436 | Val accuracy:   0.986753
-------------
Epoch 5:

Train loss: 0.042500 | Train accuracy: 0.985804
 25%|██▌       | 5/20 [03:59<11:56, 47.77s/it]
Val loss:   0.040059 | Val accuracy:   0.989300
-------------
Epoch 6:

Train loss: 0.041394 | Train accuracy: 0.987270
 30%|███       | 6/20 [04:47<11:10, 47.87s/it]
Val loss:   0.054284 | Val accuracy:   0.985904
-------------
Epoch 7:

Train loss: 0.041760 | Train accuracy: 0.987047
 35%|███▌      | 7/20 [05:41<10:42, 49.44s/it]
Val loss:   0.043529 | Val accuracy:   0.988281
-------------
Epoch 8:

Train loss: 0.038384 | Train accuracy: 0.987585
 40%|████      | 8/20 [06:27<09:42, 48.53s/it]
Val loss:   0.049260 | Val accuracy:   0.987942
-------------
Epoch 9:

Train loss: 0.037918 | Train accuracy: 0.988272
 45%|████▌     | 9/20 [07:19<09:06, 49.64s/it]
Val loss:   0.049413 | Val accuracy:   0.988621
-------------
Epoch 10:

Train loss: 0.038242 | Train accuracy: 0.988569
 50%|█████     | 10/20 [08:12<08:25, 50.54s/it]
Val loss:   0.051074 | Val accuracy:   0.988111
-------------
Epoch 11:

Train loss: 0.035417 | Train accuracy: 0.989311
 55%|█████▌    | 11/20 [08:57<07:20, 48.98s/it]
Val loss:   0.062016 | Val accuracy:   0.986413
-------------
Epoch 12:

Train loss: 0.037502 | Train accuracy: 0.988847
 60%|██████    | 12/20 [09:49<06:38, 49.78s/it]
Val loss:   0.050170 | Val accuracy:   0.988791
-------------
Epoch 13:

Train loss: 0.033152 | Train accuracy: 0.990035
 65%|██████▌   | 13/20 [10:38<05:47, 49.65s/it]
Val loss:   0.042065 | Val accuracy:   0.989470
-------------
Epoch 14:

Train loss: 0.029969 | Train accuracy: 0.991297
 70%|███████   | 14/20 [11:28<04:59, 49.86s/it]
Val loss:   0.068852 | Val accuracy:   0.984885
-------------
Epoch 15:

Train loss: 0.033406 | Train accuracy: 0.989924
 75%|███████▌  | 15/20 [12:18<04:08, 49.68s/it]
Val loss:   0.074907 | Val accuracy:   0.987772
-------------
Epoch 16:

Train loss: 0.037003 | Train accuracy: 0.989627
 80%|████████  | 16/20 [13:05<03:15, 48.96s/it]
Val loss:   0.070794 | Val accuracy:   0.987432
-------------
Epoch 17:

Train loss: 0.037166 | Train accuracy: 0.989719
 85%|████████▌ | 17/20 [13:55<02:28, 49.34s/it]
Val loss:   0.057875 | Val accuracy:   0.988451
-------------
Epoch 18:

Train loss: 0.042767 | Train accuracy: 0.988476
 90%|█████████ | 18/20 [14:43<01:37, 48.93s/it]
Val loss:   0.057928 | Val accuracy:   0.989470
-------------
Epoch 19:

Train loss: 0.037192 | Train accuracy: 0.989812
 95%|█████████▌| 19/20 [15:29<00:48, 48.04s/it]
Val loss:   0.056149 | Val accuracy:   0.988621
-------------
Epoch 20:

Train loss: 0.036644 | Train accuracy: 0.989552
100%|██████████| 20/20 [16:17<00:00, 47.96s/it]
Val loss:   0.076878 | Val accuracy:   0.987432

Loss on test set:0.06167009219718285 Accuracy on test set: 0.9871794871794872
In [61]:
plotRes(train_dev_loss_acc) 
In [70]:
plotImg(np.array(test_errors[0])[:10],1,10)
print('True labels:              '+str(np.array(test_errors[1])[:10]))
print('Wrongly Predicted labels: '+str(np.array(test_errors[2])[:10]))
print('Wrong labels in total:    '+str(len(test_errors[2])))
True labels:              [9 2 9 3 3 2 2 2 0 9]
Wrongly Predicted labels: [8 7 4 7 5 7 6 1 4 7]
Wrong labels in total:    128
In [71]:
plotImg(np.array(test_errors[0]))
print('True labels:              '+str(np.array(test_errors[1])))
print('Wrongly Predicted labels: '+str(np.array(test_errors[2])))
print('Wrong labels in total:    '+str(len(test_errors[2])))
True labels:              [9 2 9 3 3 2 2 2 0 9 3 8 6 7 4 3 4 6 9 9 7 5 7 1 0 4 3 9 8 5 2 7 5 9 1 5 2
 8 7 4 9 0 1 5 9 9 9 2 2 6 8 3 3 9 3 9 1 7 2 1 9 6 5 1 8 2 5 7 7 7 2 9 9 9
 6 6 8 3 9 5 4 8 9 7 4 4 7 1 4 2 7 4 8 4 5 3 9 8 7 9 7 0 5 8 0 0 4 3 8 3 0
 8 0 8 7 4 2 9 0 9 2 6 9 6 5 5 6 3]
Wrongly Predicted labels: [8 7 4 7 5 7 6 1 4 7 5 9 5 1 6 2 7 5 4 5 1 7 1 5 6 7 7 5 0 7 7 1 0 4 2 3 7
 4 9 9 4 2 6 7 1 4 4 0 7 1 0 8 7 5 5 5 7 1 6 6 1 4 0 6 0 7 0 2 1 1 7 7 7 1
 5 8 9 5 4 6 9 0 4 2 9 9 2 6 1 8 9 7 5 7 3 8 5 5 1 7 1 7 2 2 8 6 1 2 5 5 6
 5 2 5 2 8 7 7 1 7 7 1 7 1 6 0 0 7]
Wrong labels in total:    128
In [78]:
# Model specification
batch_size = 32

unit_size = 10

## FFNN Model
ffnn = nn.Sequential(
    nn.Linear(784, unit_size),
    nn.LeakyReLU(),
    nn.Linear(unit_size, 10)
)

model = ffnn        
lr = 0.1
n_epochs = 10
In [79]:
# Baseline
train_dev_loss_acc, test_loss, test_acc, test_errors = processData('FFNN')
  0%|          | 0/10 [00:00<?, ?it/s]
-------------
Epoch 1:

 10%|â–ˆ         | 1/10 [00:03<00:28,  3.16s/it]
Train loss: 0.582855 | Train accuracy: 0.836063
Val loss:   0.426959 | Val accuracy:   0.890374
-------------
Epoch 2:

 20%|██        | 2/10 [00:06<00:26,  3.32s/it]
Train loss: 0.441273 | Train accuracy: 0.883039
Val loss:   0.411276 | Val accuracy:   0.894218
-------------
Epoch 3:

 30%|███       | 3/10 [00:09<00:22,  3.15s/it]
Train loss: 0.406651 | Train accuracy: 0.892579
Val loss:   0.378749 | Val accuracy:   0.900735
-------------
Epoch 4:

 40%|████      | 4/10 [00:12<00:18,  3.03s/it]
Train loss: 0.392491 | Train accuracy: 0.897951
Val loss:   0.439507 | Val accuracy:   0.894886
-------------
Epoch 5:

Train loss: 0.374905 | Train accuracy: 0.903823
 50%|█████     | 5/10 [00:15<00:16,  3.21s/it]
Val loss:   0.446073 | Val accuracy:   0.905414
-------------
Epoch 6:

 60%|██████    | 6/10 [00:19<00:12,  3.19s/it]
Train loss: 0.372095 | Train accuracy: 0.905880
Val loss:   0.389105 | Val accuracy:   0.901738
-------------
Epoch 7:

 70%|███████   | 7/10 [00:22<00:09,  3.11s/it]
Train loss: 0.367164 | Train accuracy: 0.906750
Val loss:   0.373776 | Val accuracy:   0.905080
-------------
Epoch 8:

 80%|████████  | 8/10 [00:25<00:06,  3.15s/it]
Train loss: 0.356523 | Train accuracy: 0.908899
Val loss:   0.382020 | Val accuracy:   0.911932
-------------
Epoch 9:

 90%|█████████ | 9/10 [00:27<00:03,  3.01s/it]
Train loss: 0.364375 | Train accuracy: 0.907750
Val loss:   0.575103 | Val accuracy:   0.894552
-------------
Epoch 10:

100%|██████████| 10/10 [00:30<00:00,  2.92s/it]
Train loss: 0.352388 | Train accuracy: 0.912270
Val loss:   0.312012 | Val accuracy:   0.923964

Loss on test set:0.3792180856689811 Accuracy on test set: 0.9125600961538461
In [80]:
plotRes(train_dev_loss_acc) 
In [81]:
plotImg(np.array(test_errors[0])[:10],1,10)
print('True labels:              '+str(np.array(test_errors[1])[:10]))
print('Wrongly Predicted labels: '+str(np.array(test_errors[2])[:10]))
print('Wrong labels in total:    '+str(len(test_errors[2])))
True labels:              [5 3 2 6 2 9 1 7 2 7]
Wrongly Predicted labels: [6 8 3 7 8 4 9 3 8 4]
Wrong labels in total:    873






$\color{Orange}{\text{Overlapping Digits}}$

In [17]:
num_classes = 10
img_rows, img_cols = 42, 28
In [10]:
class MLP(nn.Module):

    def __init__(self, input_dimension):
        super(MLP, self).__init__()
        self.flatten = Flatten()
        # initialize model layers here
        self.linear1 = nn.Linear(input_dimension, 64)
        self.linear2 = nn.Linear(64,64)
        self.linear_first_digit = nn.Linear(64, num_classes)
        self.linear_second_digit = nn.Linear(64, num_classes)

    def forward(self, x):
        xf = self.flatten(x)

        # use model layers to predict the two digits
        out1 = F.relu(self.linear1(xf))
        out2 = F.relu(self.linear2(out1))
        out_first_digit = self.linear_first_digit(out2)
        out_second_digit = self.linear_second_digit(out2)

        return out_first_digit, out_second_digit
In [11]:
class CNN(nn.Module):

    def __init__(self, input_dimension):
        super(CNN, self).__init__()
        # initialize model layers here
        self.linear1 = nn.Linear(input_dimension, 64)
        self.linear2 = nn.Linear(64,64)
        self.linear_first_digit = nn.Linear(64,num_classes)
        self.linear_second_digit = nn.Linear(64, num_classes)
        
        self.encoder = nn.Sequential(
            nn.Conv2d(1,8,(3,3)),
            nn.ReLU(),
            nn.MaxPool2d((2,2)),
            nn.Conv2d(8,16,(3,3)),
            nn.ReLU(),
            nn.MaxPool2d((2,2)),
            Flatten(),
            nn.Linear(720,128),
            nn.Dropout(0.5)
        )
        
        self.first_digit_class = nn.Linear(128,10)
        self.second_digit_class = nn.Linear(128,10)

    def forward(self, x):

        # use model layers to predict the two digits
        out = self.encoder(x)
        out_first_digit = self.first_digit_class(out)
        out_second_digit = self.second_digit_class(out)

        return out_first_digit, out_second_digit
In [92]:
np.random.seed(12321)  # for reproducibility
torch.manual_seed(12321)  # for reproducibility

X_train_dev, y_train_dev, X_test, y_test = loadOverlapData('', True)
X_Train shape: (40000, 1, 42, 28)  y_train shape: (2, 40000)
X_Test shape: (4000, 1, 42, 28)  y_test shape: (2, 4000)
In [13]:
def plotImg(X,n=0,p=0):
    '''
    return: image(s) with n rows & p cols
    '''
    num_images = X.shape[0]
    
    if n*p < num_images:
        return print("Wrong n, p values")
    plt.figure(figsize=(20,10))
    for i in range(num_images): 
        plt.subplot(n, p, i+1)
        plt.imshow(X[i,0], cmap = cm.Greys_r)
        plt.axis('off')
    plt.show()
In [100]:
def plotErrors():
    # test_errors = x_err, y1_err, y2_err, pred1_err, pred2_err
    plotImg(np.array(test_errors[0])[:10],1,10)
    print('True upper labels:              '+str(np.array(test_errors[1])[:10]))
    print('True lower labels:              '+str(np.array(test_errors[2])[:10]))
    print()
    print('Wrongly Predicted upper labels: '+str(np.array(test_errors[3])[:10]))
    print('Wrongly Predicted lower labels: '+str(np.array(test_errors[4])[:10]))
    print()
    print('Wrong labels in total:          '+str(len(test_errors[0]))+'  Percentage: '+'{:.1%}'.format(len(test_errors[0])/len(y_test[0])))
In [14]:
plotImg(X_train_dev[:10],1,10)
In [93]:
X_train, y_train, X_dev, y_dev = splitData(X_train_dev, y_train_dev, True)
In [105]:
# Paras:
batch_size = 64
n_epoch = 30

# model:
mlp = MLP(img_rows * img_cols)
cnn = CNN(img_rows * img_cols)
In [95]:
# Split dataset into batches
train_batches = batchifyData(X_train, y_train, batch_size, True)
dev_batches = batchifyData(X_dev, y_dev, batch_size, True)
test_batches = batchifyData(X_test, y_test, batch_size, True)
In [97]:
def runModel(model, opti, is_overlap): 
    model_ = model
    # train model
    train_dev_loss_acc = trainModel(train_batches, dev_batches, model_, opti, is_overlap, n_epoch)
    
    # Evaluate the model on test data
    test_loss, test_acc, test_errors = runEpoch(test_batches, model_.eval(), None, is_overlap)
    print()
    if is_overlap:
        print('Test loss1: {:.6f}  accuracy1: {:.6f}  loss2: {:.6f}   accuracy2: {:.6f}'.format(test_loss[0], test_acc[0], test_loss[1], test_acc[1]))
    else:
        print("Loss on test set:"  + str(test_loss) + " Accuracy on test set: " + str(test_acc))
    return train_dev_loss_acc, test_loss, test_acc, test_errors
In [98]:
# Baseline
train_dev_loss_acc, test_loss, test_acc, test_errors = runModel(mlp, 'sgd', True)
  0%|          | 0/10 [00:00<?, ?it/s]
-------------
Epoch 1:

 10%|â–ˆ         | 1/10 [00:07<01:06,  7.43s/it]
Train | loss1: 0.684747  accuracy1: 0.778303 | loss2: 0.713584  accuracy2: 0.764179
Valid | loss1: 0.445656  accuracy1: 0.861643 | loss2: 0.477014  accuracy2: 0.858619
-------------
Epoch 2:

 20%|██        | 2/10 [00:14<00:58,  7.28s/it]
Train | loss1: 0.364849  accuracy1: 0.888734 | loss2: 0.404147  accuracy2: 0.870607
Valid | loss1: 0.333992  accuracy1: 0.894909 | loss2: 0.413425  accuracy2: 0.879536
-------------
Epoch 3:

 30%|███       | 3/10 [00:19<00:46,  6.70s/it]
Train | loss1: 0.277905  accuracy1: 0.915897 | loss2: 0.319190  accuracy2: 0.897520
Valid | loss1: 0.291715  accuracy1: 0.910786 | loss2: 0.357847  accuracy2: 0.893901
-------------
Epoch 4:

 40%|████      | 4/10 [00:25<00:38,  6.48s/it]
Train | loss1: 0.222627  accuracy1: 0.932857 | loss2: 0.262769  accuracy2: 0.917037
Valid | loss1: 0.258354  accuracy1: 0.922631 | loss2: 0.310097  accuracy2: 0.909274
-------------
Epoch 5:

 50%|█████     | 5/10 [00:30<00:30,  6.09s/it]
Train | loss1: 0.184160  accuracy1: 0.943978 | loss2: 0.222548  accuracy2: 0.928992
Valid | loss1: 0.248120  accuracy1: 0.924143 | loss2: 0.283647  accuracy2: 0.917339
-------------
Epoch 6:

 60%|██████    | 6/10 [00:35<00:23,  5.76s/it]
Train | loss1: 0.156033  accuracy1: 0.951874 | loss2: 0.192693  accuracy2: 0.938028
Valid | loss1: 0.246888  accuracy1: 0.927167 | loss2: 0.269054  accuracy2: 0.923135
-------------
Epoch 7:

 70%|███████   | 7/10 [00:42<00:17,  5.91s/it]
Train | loss1: 0.134566  accuracy1: 0.959631 | loss2: 0.169121  accuracy2: 0.944979
Valid | loss1: 0.251788  accuracy1: 0.931704 | loss2: 0.256508  accuracy2: 0.925655
-------------
Epoch 8:

 80%|████████  | 8/10 [00:47<00:11,  5.83s/it]
Train | loss1: 0.117830  accuracy1: 0.964302 | loss2: 0.149417  accuracy2: 0.951985
Valid | loss1: 0.250305  accuracy1: 0.931452 | loss2: 0.261937  accuracy2: 0.921875
-------------
Epoch 9:

 90%|█████████ | 9/10 [00:52<00:05,  5.37s/it]
Train | loss1: 0.102236  accuracy1: 0.969195 | loss2: 0.132840  accuracy2: 0.956823
Valid | loss1: 0.271152  accuracy1: 0.929940 | loss2: 0.266830  accuracy2: 0.922883
-------------
Epoch 10:

100%|██████████| 10/10 [00:56<00:00,  5.07s/it]
Train | loss1: 0.092528  accuracy1: 0.972309 | loss2: 0.118715  accuracy2: 0.961716
Valid | loss1: 0.289313  accuracy1: 0.927419 | loss2: 0.281194  accuracy2: 0.920111

Test loss1: 0.315268  accuracy1: 0.927671  loss2: 0.319292   accuracy2: 0.915323
In [102]:
plotErrors()
True upper labels:              [4 8 3 1 3 5 8 7 3 7]
True lower labels:              [8 8 0 9 0 5 7 6 0 9]

Wrongly Predicted upper labels: [4 8 5 1 5 5 7 1 3 7]
Wrongly Predicted lower labels: [3 7 0 1 0 0 7 6 5 4]

Wrong labels in total:          607  Percentage: 15.2%
In [103]:
plotRes(train_dev_loss_acc,14) 
In [106]:
# Adam
train_dev_loss_acc, test_loss, test_acc, test_errors = runModel(cnn, 'adam', True)
  0%|          | 0/30 [00:00<?, ?it/s]
-------------
Epoch 1:

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/torch/serialization.py:256: UserWarning: Couldn't retrieve source code for container of type CNN. It won't be checked for correctness upon loading.
  "type " + obj.__name__ + ". It won't be checked "
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/torch/serialization.py:256: UserWarning: Couldn't retrieve source code for container of type Flatten. It won't be checked for correctness upon loading.
  "type " + obj.__name__ + ". It won't be checked "
  3%|â–Ž         | 1/30 [00:26<12:49, 26.55s/it]
Train | loss1: 0.542334  accuracy1: 0.826290 | loss2: 0.604681  accuracy2: 0.797375
Valid | loss1: 0.183126  accuracy1: 0.945060 | loss2: 0.217140  accuracy2: 0.929940
-------------
Epoch 2:

  7%|â–‹         | 2/30 [00:46<11:28, 24.59s/it]
Train | loss1: 0.220333  accuracy1: 0.931078 | loss2: 0.267469  accuracy2: 0.911727
Valid | loss1: 0.135933  accuracy1: 0.957913 | loss2: 0.167467  accuracy2: 0.942540
-------------
Epoch 3:

 10%|â–ˆ         | 3/30 [01:03<09:58, 22.16s/it]
Train | loss1: 0.172331  accuracy1: 0.945396 | loss2: 0.217251  accuracy2: 0.928575
Valid | loss1: 0.118754  accuracy1: 0.963458 | loss2: 0.135169  accuracy2: 0.953377
-------------
Epoch 4:

 13%|█▎        | 4/30 [01:20<08:55, 20.61s/it]
Train | loss1: 0.151903  accuracy1: 0.951902 | loss2: 0.180863  accuracy2: 0.939724
Valid | loss1: 0.113200  accuracy1: 0.964970 | loss2: 0.122569  accuracy2: 0.957409
-------------
Epoch 5:

 17%|█▋        | 5/30 [01:37<08:10, 19.62s/it]
Train | loss1: 0.131405  accuracy1: 0.958602 | loss2: 0.165099  accuracy2: 0.943366
Valid | loss1: 0.096702  accuracy1: 0.969506 | loss2: 0.110012  accuracy2: 0.961190
-------------
Epoch 6:

 20%|██        | 6/30 [01:57<07:54, 19.76s/it]
Train | loss1: 0.117350  accuracy1: 0.962661 | loss2: 0.145643  accuracy2: 0.950289
Valid | loss1: 0.093887  accuracy1: 0.972026 | loss2: 0.104563  accuracy2: 0.961694
-------------
Epoch 7:

 23%|██▎       | 7/30 [02:17<07:35, 19.79s/it]
Train | loss1: 0.108595  accuracy1: 0.965080 | loss2: 0.131797  accuracy2: 0.955210
Valid | loss1: 0.097662  accuracy1: 0.970010 | loss2: 0.094494  accuracy2: 0.966734
-------------
Epoch 8:

 27%|██▋       | 8/30 [02:34<07:01, 19.15s/it]
Train | loss1: 0.100562  accuracy1: 0.967749 | loss2: 0.124516  accuracy2: 0.957879
Valid | loss1: 0.094207  accuracy1: 0.970514 | loss2: 0.094382  accuracy2: 0.969254
-------------
Epoch 9:

 30%|███       | 9/30 [02:55<06:49, 19.51s/it]
Train | loss1: 0.093442  accuracy1: 0.969556 | loss2: 0.118547  accuracy2: 0.959492
Valid | loss1: 0.089532  accuracy1: 0.971270 | loss2: 0.088763  accuracy2: 0.968246
-------------
Epoch 10:

 33%|███▎      | 10/30 [03:14<06:25, 19.26s/it]
Train | loss1: 0.086681  accuracy1: 0.971475 | loss2: 0.108084  accuracy2: 0.963857
Valid | loss1: 0.082309  accuracy1: 0.973790 | loss2: 0.087477  accuracy2: 0.971774
-------------
Epoch 11:

 37%|███▋      | 11/30 [03:34<06:15, 19.77s/it]
Train | loss1: 0.083399  accuracy1: 0.972587 | loss2: 0.107603  accuracy2: 0.963273
Valid | loss1: 0.086842  accuracy1: 0.970010 | loss2: 0.085404  accuracy2: 0.970262
-------------
Epoch 12:

 40%|████      | 12/30 [03:54<05:52, 19.59s/it]
Train | loss1: 0.078756  accuracy1: 0.974255 | loss2: 0.097359  accuracy2: 0.966387
Valid | loss1: 0.081332  accuracy1: 0.974546 | loss2: 0.085262  accuracy2: 0.971522
-------------
Epoch 13:

 43%|████▎     | 13/30 [04:15<05:40, 20.01s/it]
Train | loss1: 0.073177  accuracy1: 0.975728 | loss2: 0.089118  accuracy2: 0.968722
Valid | loss1: 0.083785  accuracy1: 0.975050 | loss2: 0.084359  accuracy2: 0.971522
-------------
Epoch 14:

 47%|████▋     | 14/30 [04:34<05:15, 19.75s/it]
Train | loss1: 0.069536  accuracy1: 0.977397 | loss2: 0.085658  accuracy2: 0.971363
Valid | loss1: 0.083650  accuracy1: 0.972278 | loss2: 0.077232  accuracy2: 0.972782
-------------
Epoch 15:

 50%|█████     | 15/30 [04:52<04:49, 19.32s/it]
Train | loss1: 0.062781  accuracy1: 0.979343 | loss2: 0.084876  accuracy2: 0.970557
Valid | loss1: 0.080616  accuracy1: 0.973286 | loss2: 0.080825  accuracy2: 0.972530
-------------
Epoch 16:

 53%|█████▎    | 16/30 [05:10<04:24, 18.87s/it]
Train | loss1: 0.066607  accuracy1: 0.978370 | loss2: 0.080642  accuracy2: 0.971391
Valid | loss1: 0.082505  accuracy1: 0.973790 | loss2: 0.084716  accuracy2: 0.973034
-------------
Epoch 17:

 57%|█████▋    | 17/30 [05:27<03:59, 18.40s/it]
Train | loss1: 0.058300  accuracy1: 0.980594 | loss2: 0.072521  accuracy2: 0.974672
Valid | loss1: 0.093459  accuracy1: 0.972782 | loss2: 0.080141  accuracy2: 0.974042
-------------
Epoch 18:

 60%|██████    | 18/30 [05:44<03:35, 17.92s/it]
Train | loss1: 0.059346  accuracy1: 0.979954 | loss2: 0.076681  accuracy2: 0.972448
Valid | loss1: 0.078871  accuracy1: 0.977067 | loss2: 0.081785  accuracy2: 0.972278
-------------
Epoch 19:

 63%|██████▎   | 19/30 [06:00<03:11, 17.39s/it]
Train | loss1: 0.055471  accuracy1: 0.981150 | loss2: 0.071520  accuracy2: 0.974978
Valid | loss1: 0.078819  accuracy1: 0.976562 | loss2: 0.082083  accuracy2: 0.973790
-------------
Epoch 20:

 67%|██████▋   | 20/30 [06:18<02:54, 17.46s/it]
Train | loss1: 0.056622  accuracy1: 0.980594 | loss2: 0.067210  accuracy2: 0.976201
Valid | loss1: 0.078682  accuracy1: 0.975050 | loss2: 0.083902  accuracy2: 0.973286
-------------
Epoch 21:

 70%|███████   | 21/30 [06:38<02:45, 18.34s/it]
Train | loss1: 0.054266  accuracy1: 0.981845 | loss2: 0.065696  accuracy2: 0.976257
Valid | loss1: 0.078461  accuracy1: 0.976815 | loss2: 0.079065  accuracy2: 0.974294
-------------
Epoch 22:

 73%|███████▎  | 22/30 [06:56<02:24, 18.05s/it]
Train | loss1: 0.050729  accuracy1: 0.983291 | loss2: 0.062652  accuracy2: 0.977786
Valid | loss1: 0.083731  accuracy1: 0.974798 | loss2: 0.085368  accuracy2: 0.973538
-------------
Epoch 23:

 77%|███████▋  | 23/30 [07:12<02:03, 17.66s/it]
Train | loss1: 0.050611  accuracy1: 0.982457 | loss2: 0.063234  accuracy2: 0.977369
Valid | loss1: 0.081787  accuracy1: 0.976058 | loss2: 0.079904  accuracy2: 0.976058
-------------
Epoch 24:

 80%|████████  | 24/30 [07:31<01:48, 18.08s/it]
Train | loss1: 0.049926  accuracy1: 0.982373 | loss2: 0.061533  accuracy2: 0.978286
Valid | loss1: 0.085372  accuracy1: 0.975050 | loss2: 0.082280  accuracy2: 0.974294
-------------
Epoch 25:

 83%|████████▎ | 25/30 [07:48<01:27, 17.59s/it]
Train | loss1: 0.048262  accuracy1: 0.983986 | loss2: 0.059610  accuracy2: 0.979148
Valid | loss1: 0.080006  accuracy1: 0.976310 | loss2: 0.078130  accuracy2: 0.977823
-------------
Epoch 26:

 87%|████████▋ | 26/30 [08:05<01:10, 17.60s/it]
Train | loss1: 0.043585  accuracy1: 0.984931 | loss2: 0.059476  accuracy2: 0.978787
Valid | loss1: 0.084322  accuracy1: 0.975554 | loss2: 0.076678  accuracy2: 0.976815
-------------
Epoch 27:

 90%|█████████ | 27/30 [08:25<00:54, 18.10s/it]
Train | loss1: 0.044626  accuracy1: 0.984903 | loss2: 0.057232  accuracy2: 0.979676
Valid | loss1: 0.079111  accuracy1: 0.976815 | loss2: 0.080451  accuracy2: 0.974294
-------------
Epoch 28:

 93%|█████████▎| 28/30 [08:41<00:34, 17.46s/it]
Train | loss1: 0.042881  accuracy1: 0.985181 | loss2: 0.054792  accuracy2: 0.980149
Valid | loss1: 0.078783  accuracy1: 0.975806 | loss2: 0.077081  accuracy2: 0.977571
-------------
Epoch 29:

 97%|█████████▋| 29/30 [08:59<00:17, 17.74s/it]
Train | loss1: 0.043432  accuracy1: 0.985682 | loss2: 0.053063  accuracy2: 0.980816
Valid | loss1: 0.082366  accuracy1: 0.978075 | loss2: 0.080843  accuracy2: 0.977823
-------------
Epoch 30:

100%|██████████| 30/30 [09:15<00:00, 17.28s/it]
Train | loss1: 0.042184  accuracy1: 0.985793 | loss2: 0.052165  accuracy2: 0.981456
Valid | loss1: 0.080012  accuracy1: 0.977319 | loss2: 0.080944  accuracy2: 0.977067

Test loss1: 0.114392  accuracy1: 0.968498  loss2: 0.097581   accuracy2: 0.970766
In [107]:
plotErrors()
True upper labels:              [1 1 8 7 8 2 9 9 2 8]
True lower labels:              [9 5 9 6 1 2 8 5 9 7]

Wrongly Predicted upper labels: [1 1 8 2 9 3 5 4 0 3]
Wrongly Predicted lower labels: [1 3 4 6 1 2 8 5 9 7]

Wrong labels in total:          239  Percentage: 6.0%
In [108]:
plotRes(train_dev_loss_acc,14)