{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Simple fastai v2 approach. \nIt works on Digit Recognizer as well as MNIST and MNIST_TINY","metadata":{}},{"cell_type":"code","source":"from fastai.vision.all import *\nimport pandas as pd\n%matplotlib inline\nset_seed(3865)","metadata":{"execution":{"iopub.status.busy":"2022-07-10T00:38:54.635038Z","iopub.execute_input":"2022-07-10T00:38:54.635889Z","iopub.status.idle":"2022-07-10T00:38:58.528040Z","shell.execute_reply.started":"2022-07-10T00:38:54.635787Z","shell.execute_reply":"2022-07-10T00:38:58.526716Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# function to convert csv file into PNG images inside of categorized file tree\n# I need it fo CSV input, don't need for MNIST as it is already in PNG\nimport cv2\ninpSize = 28 # input image size\ndef makeTree( tpath, csvpath, withlabel=True ): # make file tree at tpath from csv dataframe\n    tpd = pd.read_csv(csvpath)\n    # tpd = tpd[:30] # short test\n    os.makedirs(tpath, exist_ok=True) # make train or valid or test folder\n    whiteImg = np.ones((28,28))*255\n    \n    for i in range(len(tpd)):\n        startcell = 1 if withlabel else 0\n        img = np.array(tpd.iloc[i,startcell:]).reshape(28,28)\n        # img2 = whiteImg - img\n        img2 = img # decided not to invert color to keep consistency with MNIST images\n        catpath = tpath\n        if withlabel:\n            categ = str(tpd.iloc[i].label)\n            catpath = tpath + '/' + categ\n            os.makedirs(catpath, exist_ok=True)\n        cv2.imwrite(catpath + '/' + str(i+1) + '.png', img2)\n    \n    return","metadata":{"execution":{"iopub.status.busy":"2022-07-10T00:39:02.209088Z","iopub.execute_input":"2022-07-10T00:39:02.210091Z","iopub.status.idle":"2022-07-10T00:39:02.454075Z","shell.execute_reply.started":"2022-07-10T00:39:02.210048Z","shell.execute_reply":"2022-07-10T00:39:02.453280Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# make train image files\nmakeTree('./digits/train', '../input/digit-recognizer/train.csv', withlabel=True)\n\n# test files are not labeled, \n# so we put them into upper folder, otherwise they will all be auto-labeled as 'test'\nmakeTree('./test', '../input/digit-recognizer/test.csv', withlabel=False)","metadata":{"execution":{"iopub.status.busy":"2022-07-10T00:39:09.678633Z","iopub.execute_input":"2022-07-10T00:39:09.679559Z","iopub.status.idle":"2022-07-10T00:39:35.338898Z","shell.execute_reply.started":"2022-07-10T00:39:09.679516Z","shell.execute_reply":"2022-07-10T00:39:35.337655Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import albumentations as Alb\nclass AlbTransform(Transform):\n    def __init__(self, aug): self.aug = aug\n    def encodes(self, img: PILImage):\n        aug_img = self.aug(image=np.array(img))['image']\n        return PILImage.create(aug_img)\n    \ndef get_augs(): return Alb.Compose([\n    Alb.InvertImg(p=1.), # just because I like it white\n    Alb.ShiftScaleRotate(rotate_limit=20, border_mode=0, value=(255,255,255) ),\n    # Alb.RandomResizedCrop(28,28),\n])\n\nitem_tfms = [AlbTransform(get_augs())] \nbatch_tfms = Normalize.from_stats(*imagenet_stats) ","metadata":{"execution":{"iopub.status.busy":"2022-07-10T00:40:01.049090Z","iopub.execute_input":"2022-07-10T00:40:01.049934Z","iopub.status.idle":"2022-07-10T00:40:02.273776Z","shell.execute_reply.started":"2022-07-10T00:40:01.049897Z","shell.execute_reply":"2022-07-10T00:40:02.272683Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pathlib\n# path = untar_data(URLs.MNIST) # _TINY)\npath = Path('./digits')","metadata":{"execution":{"iopub.status.busy":"2022-07-10T00:40:07.621390Z","iopub.execute_input":"2022-07-10T00:40:07.621769Z","iopub.status.idle":"2022-07-10T00:40:07.627065Z","shell.execute_reply.started":"2022-07-10T00:40:07.621738Z","shell.execute_reply":"2022-07-10T00:40:07.625888Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I want to train on MNIST, but test on competition data to see what will happen.\n\nTraining on competition CSV gave me score of 0.99021\n\nTraining on the full MNIST dataset provided score of 0.99932. However, this is a kind of cheating, because MNIST contains 70K of the labeled images, and Digit Recognizer is a subset of the MNIST with a different split: 28K of the test images with removed labels. I used 69,300 images for training and 700 for validation, so got score a bit less than 1.0, when using all 70K for training - the score should go to 1.0","metadata":{}},{"cell_type":"code","source":"dls = ImageDataLoaders.from_folder( path, train='train', valid_pct=0.01, \n    item_tfms = [AlbTransform(get_augs())], \n    batch_tfms = Normalize.from_stats(*imagenet_stats),\n    bs=1024, shuffle=True )\n\n# for MNIST_TINY use train='train', valid='valid' or just delete those (default)\n# for MNIST: train='training', valid='testing', # valid_pct=0.2, \n# for competition data: path, train='train', valid_pct=0.05, ","metadata":{"execution":{"iopub.status.busy":"2022-07-10T00:40:50.991909Z","iopub.execute_input":"2022-07-10T00:40:50.992376Z","iopub.status.idle":"2022-07-10T00:40:55.501030Z","shell.execute_reply.started":"2022-07-10T00:40:50.992340Z","shell.execute_reply":"2022-07-10T00:40:55.499956Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# uncomment to test data loaders\ndls.train.show_batch(max_n=12)\n# dls.valid.show_batch(max_n=12)\nprint('train items:', len(dls.train.items), 'validation items:', len(dls.valid.items))\ndls.vocab","metadata":{"execution":{"iopub.status.busy":"2022-07-10T00:40:58.758904Z","iopub.execute_input":"2022-07-10T00:40:58.759325Z","iopub.status.idle":"2022-07-10T00:40:59.795521Z","shell.execute_reply.started":"2022-07-10T00:40:58.759290Z","shell.execute_reply":"2022-07-10T00:40:59.794778Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# exploring tricks\nmynet = xse_resnext34(pretrained=False, \n   act_cls=Mish, \n   sa=True,\n   n_out=dls.c)","metadata":{"execution":{"iopub.status.busy":"2022-07-10T00:43:26.773303Z","iopub.execute_input":"2022-07-10T00:43:26.773805Z","iopub.status.idle":"2022-07-10T00:43:27.181219Z","shell.execute_reply.started":"2022-07-10T00:43:26.773773Z","shell.execute_reply":"2022-07-10T00:43:27.180192Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn = Learner(dls, model=mynet, \n    loss_func=LabelSmoothingCrossEntropy(), \n    opt_func=ranger,\n    metrics=[accuracy, F1Score(average='weighted')]).to_fp16()","metadata":{"execution":{"iopub.status.busy":"2022-07-10T00:44:18.877926Z","iopub.execute_input":"2022-07-10T00:44:18.878340Z","iopub.status.idle":"2022-07-10T00:44:18.884660Z","shell.execute_reply.started":"2022-07-10T00:44:18.878299Z","shell.execute_reply":"2022-07-10T00:44:18.883607Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"lrs = learn.lr_find(suggest_funcs=(minimum, steep, valley, slide))\nprint(lrs) # just for reference","metadata":{"execution":{"iopub.status.busy":"2022-07-10T00:44:46.241056Z","iopub.execute_input":"2022-07-10T00:44:46.241477Z","iopub.status.idle":"2022-07-10T00:45:39.886261Z","shell.execute_reply.started":"2022-07-10T00:44:46.241444Z","shell.execute_reply":"2022-07-10T00:45:39.884212Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# play around learning methods\nlearn.fit_one_cycle(5, 1e-2, cbs=[ShowGraphCallback()])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn.fit_flat_cos(5, 4e-3)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn.fit_one_cycle(30, 1e-4, \n     cbs=[EarlyStoppingCallback(patience=5), SaveModelCallback()])","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# make dataloader for test data, our test data is with labels (by parent folder name)\n\n# tfnames = get_image_files(path/'testing') # for MNIST\ntfnames = get_image_files('test')\ntst_dl = dls.test_dl(tfnames, with_labels=False, shuffle=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# uncomment to see if dataloader is working\n# tst_dl.show_batch(max_n=12)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Using TTA may improve results compare to the final validation accuracy and it does. TTA helps **only** when using **random augmentations** during testing. TTA option \"use_max=True\" does not help me, sometimes promoting errors. \n\nEx: TINY after 2 epochs: accuracy=0.8469, after TTA32 acc=0.8784\n\nEx: MNIST after 10 epochs: acc=0.9928, after TTA32 acc=0.9943","metadata":{}},{"cell_type":"code","source":"preds = learn.tta(dl=tst_dl, n=32, use_max=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# build a table with results and check final accuracy\n\npredss = learn.dls.vocab[np.argmax(preds[0], axis=1)] # convert to our classes from probabilities\nidlist = [item.stem for item in tst_dl.items]\n\n# tlabels = [itm.parent.name for itm in tst_dl.items] # list of true labels\n# res_df = pd.DataFrame(list(zip(idlist, tlabels, predss)), columns =['ImageId', 'label', 'pred'])\n# res_df['ok'] = (res_df.label == res_df.pred) # do we have a match?\n# accuracy = res_df.ok.sum() / res_df.shape[0] # count accuracy\n# accuracy","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"subm_df = pd.DataFrame(list(zip(idlist, predss)), columns =['ImageId', 'Label'])\nsubm_df.to_csv('submission.csv', header=True, index=False)\nsubm_df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Clean up image files\nOtherwise it will burn time comitting.","metadata":{}},{"cell_type":"code","source":"from shutil import rmtree\nrmtree('./digits', ignore_errors=True)\nrmtree('./test', ignore_errors=True)","metadata":{},"execution_count":null,"outputs":[]}]}