import numpy as np
import pandas as pd
import torch
from fastai.data.core import Datasets
from fastai.vision.all import *
Creating DataBlock from Numpy Array
Feed list of dict into DataBlock
TLDR; Need to prepare our data into list of dictionaries for each sample, eg L([{x: feature1, y: label1}, {x: feature2, y: label2}, {x: featuren, y: labeln} ])
format and feed the function into get_image param of DataBlock.
Load data
# ref: https://www.kaggle.com/code/drkaggle22/digit-recognizer-solution-99-accuracy?scriptVersionId=181451739&cellId=3
import struct
def read_idx(filename):
with open(filename, 'rb') as f:
= struct.unpack('>HBB', f.read(4))
zero, data_type, dims= tuple(struct.unpack('>I', f.read(4))[0] for d in range(dims))
shape
return np.frombuffer(f.read(), dtype=np.uint8).reshape(shape)
def load_mnist(image_path, label_path):
= read_idx(image_path)
images = read_idx(label_path)
labels return images, labels
= '/kaggle/input/mnist-dataset/train-images-idx3-ubyte/train-images-idx3-ubyte'
train_image_path = '/kaggle/input/mnist-dataset/train-labels-idx1-ubyte/train-labels-idx1-ubyte'
train_label_path = '/kaggle/input/mnist-dataset/t10k-images-idx3-ubyte/t10k-images-idx3-ubyte'
test_image_path = '/kaggle/input/mnist-dataset/t10k-labels-idx1-ubyte/t10k-labels-idx1-ubyte' test_label_path
= load_mnist(train_image_path, train_label_path)
train_images, train_labels = load_mnist(test_image_path, test_label_path)
test_images, test_labels print(f'Train images shape: {train_images.shape}')
print(f'Train labels shape: {train_labels.shape}')
print(f'Test images shape: {test_images.shape}')
print(f'Test labels shape: {test_labels.shape}')
Train images shape: (60000, 28, 28)
Train labels shape: (60000,)
Test images shape: (10000, 28, 28)
Test labels shape: (10000,)
from collections import Counter
print(Counter(train_labels))
= len(Counter(train_labels))
n_classes print('n_classes:', n_classes)
Counter({1: 6742, 7: 6265, 3: 6131, 2: 5958, 9: 5949, 0: 5923, 6: 5918, 8: 5851, 4: 5842, 5: 5421})
n_classes: 10
def tensor_to_labelled_pil_image(tensor: np.ndarray, labels=None) -> list:
''' ref: https://www.kaggle.com/code/pemtaira/digit-recognizer-fastai-v2-2020
shape image shape (total sample, height, width) into (total sample, 3, height, width),
save into dictionary (x: reshaped img, y: label). Append dictionary to list. return list.
'''
= tensor.reshape(-1, 28, 28) # (total sample, 28, 28) --> (total sample, 28, 28)
reshaped = np.stack((reshaped,) *3, axis = 1) # (total sample, 28, 28) --> (total sample, 3, 28, 28)
reshaped = []
image_arr
# loop each reshaped images, convert to float tensor, convert to PILImage, save as dictionary, append to list
for idx, current_image in enumerate(reshaped):
= torch.tensor(current_image, dtype=torch.float) / 255.
img = PILImage(to_image(img))
img
= None
final_data
if (labels is None):
= {'x': img, 'y': None}
final_data else:
= {'x': img, 'y': labels[idx]}
final_data
image_arr.append(final_data)
return image_arr
def get_image(l:list) -> L:
"""
returns list of [{'x': feature tensor, 'y': class label},
{...}, {...} ]
L is fastai's implementation of list
"""
= l[0]
features = l[1]
labels = tensor_to_labelled_pil_image(features, labels)
all_imgs return L(all_imgs)
def get_y_fromdict(item):
"""get y from each sample dictionary returned from get_image()"""
return item['y']
def get_x_fromdict(item):
"""get x from each sample dictionary returned from get_image()"""
return item['x']
Initialize DataBlock
blocks=(ImageBlock(cls=PILImage), CategoryBlock)
> Here we specify that our input data is an image and of class PILImage, our label is categorical
get_items=get_image
> Function where we return list of {x:features, y:label} dictionary for all our samples
splitter=RandomSplitter(valid_pct=0.2, seed=42)
> Describe how we want to split our data; Here we want to split train and test data into 80-20 split randomly. We specify seed to have reproducible result for each run.
get_x=get_x_fromdict
> Function to extract features from list returned from get_image() function. Note, we could also use lambda function here get_x = (lambda item: item['x'])
.
get_y=get_y_fromdict
> Function to extract label from list returned from get_image() function. Note, we could also use lambda function here get_y = (lambda item: item['y'])
.
Note that if we use lambda function when initializing DataBlock, we might need to use dill
library to export our model.
= DataBlock(
mnist_db =(ImageBlock(cls=PILImage), CategoryBlock),
blocks=get_image,
get_items=RandomSplitter(valid_pct=0.2, seed=42),
splitter=get_y_fromdict,#(lambda item: item['y']),
get_y=get_x_fromdict #(lambda item: item['x'])
get_x )
# checking featues and labels shapes
print(train_images.shape)
print(train_labels.shape)
(60000, 28, 28)
(60000,)
# stacking both train and test sets' features
print(train_images.shape)
print(test_images.shape)
np.vstack([train_images, test_images]).shape
(60000, 28, 28)
(10000, 28, 28)
(70000, 28, 28)
# stacking both train and test sets
print(train_labels.shape)
print(test_labels.shape)
np.hstack([train_labels, test_labels]).shape
(60000,)
(10000,)
(70000,)
This is how our data is reshaped in tensor_to_labelled_pil_image() function.
= np.vstack([train_images, test_images])
features = features.reshape(-1, 28, 28)
features_reshaped = np.stack((features_reshaped,) *3, axis = 1)
features_reshaped_stacked
print('features.shape', features.shape)
print('features_reshaped.shape', features_reshaped.shape)
print('features_reshaped_stacked.shape', features_reshaped_stacked.shape)
features.shape (70000, 28, 28)
features_reshaped.shape (70000, 28, 28)
features_reshaped_stacked.shape (70000, 3, 28, 28)
Quick plot
# ref: https://stackoverflow.com/a/59296746
import matplotlib.pyplot as plt
= plt.subplots(10,10, figsize=(28,28))
fig, axes for i,ax in enumerate(axes.flat):
ax.imshow(features_reshaped[i])
Load our source data
= mnist_db.dataloaders([np.vstack([train_images, test_images]),
dls np.hstack([train_labels, test_labels])])
dls.show_batch()
Train model
= vision_learner(dls, resnet18, metrics=[error_rate, accuracy])
learn 10) learn.fine_tune(
Downloading: "https://download.pytorch.org/models/resnet18-f37072fd.pth" to /root/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth
100%|██████████| 44.7M/44.7M [00:00<00:00, 146MB/s]
epoch | train_loss | valid_loss | error_rate | accuracy | time |
---|---|---|---|---|---|
0 | 0.714643 | 0.483854 | 0.155143 | 0.844857 | 04:06 |
epoch | train_loss | valid_loss | error_rate | accuracy | time |
---|---|---|---|---|---|
0 | 0.170800 | 0.091101 | 0.026429 | 0.973571 | 08:18 |
1 | 0.098211 | 0.057546 | 0.018071 | 0.981929 | 07:50 |
2 | 0.070756 | 0.043570 | 0.013071 | 0.986929 | 07:54 |
3 | 0.045105 | 0.036998 | 0.010214 | 0.989786 | 07:51 |
4 | 0.034318 | 0.037484 | 0.010214 | 0.989786 | 07:51 |
5 | 0.032253 | 0.031844 | 0.007857 | 0.992143 | 07:49 |
6 | 0.013959 | 0.029695 | 0.006714 | 0.993286 | 07:47 |
7 | 0.006643 | 0.028861 | 0.006643 | 0.993357 | 07:48 |
8 | 0.002887 | 0.027575 | 0.006143 | 0.993857 | 07:48 |
IOPub message rate exceeded.
The notebook server will temporarily stop sending output
to the client in order to avoid crashing it.
To change this limit, set the config variable
`--NotebookApp.iopub_msg_rate_limit`.
Current values:
NotebookApp.iopub_msg_rate_limit=1000.0 (msgs/sec)
NotebookApp.rate_limit_window=3.0 (secs)
Save model
'model2.pkl') learn.export(
If we use lambda function when initializing DataBlock, we can use dill to save model. Eg:
import dill
'model2.pkl', pickle_module=dill) learn.export(