{"cells":[{"metadata":{},"cell_type":"markdown","source":"\n# Intro\n## Disclaimer\nThis is a beginners view and observation. I might missed some points in the framework or may have some wrong assumptions. So please double check if you have doubts and let me know if I got some things wrong.\n\n## What the kernel is about\nThis kernel shows how you can speed up Image Processing in Fastai when you are using a small image dataset.\n\nBecause I didn't want to bother with converting the data into images, I started the competition with this nice [kernel](https://www.kaggle.com/melissarajaram/fastai-pytorch-with-best-original-mnist-arch). That kernel trains pretty fast (about 5 sec per epoch). Unfortunately the kernel doesn't convert the images into *Fastai Images* (which are usefull for ClassificationInterpretation), instead it passes the data directly as a Dataset to pytorch as discribed [here](https://docs.fast.ai/basic_data.html#Using-a-custom-Dataset-in-fastai). \n\nTo overcome this shortcoming I tried another nice [kernel](https://www.kaggle.com/melissarajaram/fastai-mixup-training-aug-tta) which uses Fastai ImageList. Now I got the full functionalities I wanted. Unfortunately this time the training time want down (more then 2 minutes! per epoch).\n\n## Analysing\nAfter some time crawling through the Fastai code I figuered out, that when you set up a databunch with the the default ImageList the data is not beeing preprocessed (here: converting 784 valued into 28x28 Image) until you use it. Everytime you grab an ImageList entry the get()-function gets called, which furthermore calls open(). \n\nIn [kernel 2](https://www.kaggle.com/melissarajaram/fastai-mixup-training-aug-tta)  the customized open() fetches the image data from a DataFrame and converts it into an Image. I suppose the frequent Dataframe lookup and preprocessing takes up a lot of time.\n\nBut why is [kernel 1](https://www.kaggle.com/melissarajaram/fastai-pytorch-with-best-original-mnist-arch) faster? In kernel 1 all the data gets preprocessed in advance and passed to the custom Dataset initialy. There wan't be fetching and preprocessing necessary while training.\n\n## Task\nSince I want the full functionality of Fastai Images and also decent runtime I'm writing my own customized ImageList with initial loading and preprocessing.\n\n**Note:** I guess in the framework the initial loading is not done because of memory consumption. But for this small dataset it works fine.\n\n## Credits\nPlease visit @melissarajaram kernels and upvote if you like them. There is a lot more in there.\n* [Pytorch Dataset kernel 1](https://www.kaggle.com/melissarajaram/fastai-pytorch-with-best-original-mnist-arch)\n* [ImageList kernel 2](https://www.kaggle.com/melissarajaram/fastai-mixup-training-aug-tta)\n\nOthers\n* [numpy to Image conversion](https://www.kaggle.com/heye0507/fastai-1-0-with-customized-itemlist)\n\n"},{"metadata":{},"cell_type":"markdown","source":"# Preprocessing"},{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"from fastai import *\nfrom fastai.vision import *\nfrom fastai.callbacks import *\nDATAPATH = Path('/kaggle/input/Kannada-MNIST/')\nfrom matplotlib import pyplot\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"df_train = pd.read_csv(DATAPATH/'train.csv')\ndf_train['fn'] = df_train.index\ndf_train.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"I'm using the from_df() function to do the preprocessing and loading of the images. Therefore the init() needs a variable (myimages). It is also passed in as a new parameter otherwise the classmethodes *label_from_df* and *split_by_rand_pct* (used later on) would override the myimages dictionary.\n\nI also added an additional channel with a blurred representation of the image."},{"metadata":{"trusted":true},"cell_type":"code","source":"class PixelImageItemList(ImageList):\n    \n    def __init__(self, myimages = {}, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.myimages = myimages \n    \n    def open(self,fn):\n        return self.myimages.get(fn)\n    \n    @classmethod\n    def from_df(cls, df:DataFrame, path:PathOrStr, cols:IntsOrStrs=0, folder:PathOrStr=None, suffix:str='', **kwargs)->'ItemList':\n        \"Get the filenames in `cols` of `df` with `folder` in front of them, `suffix` at the end.\"\n        res = super().from_df(df, path=path, cols=cols, **kwargs)\n        \n        # full load of all images\n        for i, row in df.drop(labels=['label','fn'],axis=1).iterrows():\n            # Numpy to Image conversion from\n            # https://www.kaggle.com/heye0507/fastai-1-0-with-customized-itemlist\n            img_pixel = row.values.reshape(28,28)\n            img_pixel = np.stack((img_pixel,)*1,axis=-1)\n            \n            # add channel with blured image\n            x=pil2tensor(img_pixel,np.float32).div_(255).unsqueeze(0)\n            \n            ## Blur with gaussien kernel \n            b = torch.Tensor([[1, 1, 1],\n                              [1, 2, 1],\n                              [1, 1, 1]])\n            b = b.view((1,1,3,3))\n            blurred_ch = F.conv2d(x, b, padding=1).div_(2)\n            \n            # construct a 3 channel image (3rd channel doesn't contain additional info here. It is just added for the show case.)\n            res.myimages[res.items[i]]=vision.Image(torch.cat((x.squeeze(0), blurred_ch.squeeze(0), x.squeeze(0))))\n\n        return res","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\npiil = PixelImageItemList.from_df(df=df_train,path='.',cols='fn')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"piil.get(4)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"src = (piil\n      .split_by_rand_pct()\n      .label_from_df(cols='label')\n      )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"src","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Creating a databunch"},{"metadata":{"trusted":true},"cell_type":"code","source":"data = (src.databunch(bs=128))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"len(data.myimages)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"data.show_batch(rows=3,figsize=(10,7))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model Architecture\nThe model is taken from [here](https://www.kaggle.com/melissarajaram/fastai-pytorch-with-best-original-mnist-arch)."},{"metadata":{},"cell_type":"markdown","source":"## Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"leak = 0.1\n\nbest_architecture = nn.Sequential(\n   \n    conv_layer(3,32,stride=1,ks=3,leaky=leak),\n    conv_layer(32,32,stride=1,ks=3,leaky=leak),\n    conv_layer(32,32,stride=2,ks=5,leaky=leak),\n    nn.Dropout(0.2),\n    \n    conv_layer(32,64,stride=1,ks=3,leaky=leak),\n    conv_layer(64,64,stride=1,ks=3,leaky=leak),\n    conv_layer(64,64,stride=2,ks=5,leaky=leak),\n    nn.Dropout(0.2),\n    \n    Flatten(),\n    nn.Linear(3136,256), \n    relu(inplace=True,leaky=0.1),\n    nn.BatchNorm1d(256),\n    nn.Dropout(0.4),\n    nn.Linear(256,10)\n)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Learner"},{"metadata":{"trusted":true},"cell_type":"code","source":"learn = Learner(data, best_architecture, loss_func = nn.CrossEntropyLoss(), metrics=[accuracy] ).mixup()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#learn","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Train"},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.lr_find()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.recorder.plot(suggestion=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"lr = 1e-3\nn_cycle = 5\n\nreduceLR = ReduceLROnPlateauCallback(learn=learn, monitor = 'valid_loss', mode = 'auto', patience = 2, factor = 0.2, min_delta = 0)\n\nlearn.fit_one_cycle(n_cycle, slice(lr), callbacks=[reduceLR])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**It worked! 8 sec per epoch!**"},{"metadata":{},"cell_type":"markdown","source":"# Interpretation"},{"metadata":{"trusted":true},"cell_type":"code","source":"interp = ClassificationInterpretation.from_learner(learn)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"interp.plot_top_losses(9, figsize=(6,6))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"interp.plot_confusion_matrix()","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.7.3"}},"nbformat":4,"nbformat_minor":1}