{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Reading the Robot Mind\n## Executive Summary\n* Examines one of the most popular public kernels in this contest \n* Draws pictures of the internal workings of the neural network\n* Explains how these pictures can be used to improve the score \n* Proposes this for the Keras feature set\n\n## Details\nIn this contest, as in others, participants struggle to improve their leaderboard (LB) scores by optimizing parameters and methods. One aspect of this optimization is the ability to peer into the internal representation of the trained neural network to let a human expert determine if important information has been lost or is being misinterpreted somewhere along the way. In a recent \"AI Explainability Whitepaper\" [1] from Google, feature attribution is stressed. This is a wonderful method of determining which features most greatly impact the final classification/coding. Another method uses autoencoders[2] as a data compression means which also can point to flaws in a lossy internal representation of the data.\n\nThis Jupyter notebook proposes a third method to read the robot mind. It uses a reverse calculation and attempts to recreate the original data (in this case, the handwritten grapheme) from an internal state of the neural network. In this way, an expert (or any child with 10+ years of schooling in reading and writing Bengali) can clearly see if important information has been lost, what was lost, and even what layer within the neural network it was lost.\n\n### Please support by upvoting\nIf you feel this is an important area for further research, please upvote this notebook. This will help bring attention to seeing these or similar functions get incorporated within the Keras framework, and wherever it seems it will do the most good for future researchers. Perhaps others have come to the same conclusions and are already working similar efforts; in which case I am also happy to help where I can. I am a full time teacher, and as such, I can only devote spare time to this effort. Nevertheless, please feel free to reach out to me in this regard.\n\n### references\n* [1] - https://cloud.google.com/ml-engine/docs/ai-explanations/overview\n* [2] - https://en.wikipedia.org/wiki/Autoencoder\n\nIt also builds upon work I have published in US Patents and my 2013 PhD Dissertation\n* Method and apparatus for developing a neural network for phoneme recognition US 5,749,066\n* Method and apparatus for interfacing and training a neural network for phoneme recognition US 5,809,462\n* 2013 Signal Processing of EEG for the Detection of Attentiveness towards Short Training Videos https://scholarscompass.vcu.edu/etd/558/ "},{"metadata":{},"cell_type":"markdown","source":"PANv00 - Forked from the notebook \"Bengali Graphemes: Starter EDA+ Multi Output CNN\" https://www.kaggle.com/kaushal2896/bengali-graphemes-starter-eda-multi-output-cnn Courtesy Kaushal Shah [saved trained model for use in this \"no GPU\" analysis kernel] LEADERBOARD SCORE of - 0.9353\n\nPointer to version 00 \nhttps://www.kaggle.com/pnussbaum/grapheme-mind-reader-panv00 \n\nPANv01 through v12 - Added \"mind reader\" code and removed extraneous code. Now the notebook reads in a trained model (from v00) and creates additional neural netowrks that are subsets of the trained NN in order to look into the internal representations of the convolutional layers. Namely... graphical images of the kernels themselves, as well as \"reverse processing\" of the output of internal layers, back to an estimation of the original image - to look for flaws.\n\n* PANv12d-e - Corrected terminology used in comments. Corrected various bugs\n* PANv12f - Simplified code. Now shows all convolutional layers"},{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load in \n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nfrom tqdm.auto import tqdm\nfrom glob import glob\nimport time, gc\nimport cv2\n\nfrom tensorflow import keras\nimport matplotlib.image as mpimg\nfrom keras.preprocessing.image import ImageDataGenerator\nfrom keras.models import *\nfrom keras.layers import *\nfrom keras.optimizers import *\nfrom keras.callbacks import *\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix\nimport PIL.Image as Image, PIL.ImageDraw as ImageDraw, PIL.ImageFont as ImageFont\nfrom matplotlib import pyplot as plt\nimport seaborn as sns\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Input data files are available in the \"../input/\" directory.\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# Any results you write to the current directory are saved as output.","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"train_df_ = pd.read_csv('/kaggle/input/bengaliai-cv19/train.csv')\ntest_df_ = pd.read_csv('/kaggle/input/bengaliai-cv19/test.csv')\nclass_map_df = pd.read_csv('/kaggle/input/bengaliai-cv19/class_map.csv')\nsample_sub_df = pd.read_csv('/kaggle/input/bengaliai-cv19/sample_submission.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f'Size of training data: {train_df_.shape}')\nprint(f'Size of test data: {test_df_.shape}')\nprint(f'Size of class map: {class_map_df.shape}')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# remove the picture of the \"correct\" grapheme - not relevant to the contest\ntrain_df_ = train_df_.drop(['grapheme'], axis=1, inplace=False)\nprint(f'Size of training data: {train_df_.shape}')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Make the correct classifications, unsigned 8-bit integers\ntrain_df_[['grapheme_root', 'vowel_diacritic', 'consonant_diacritic']] = train_df_[['grapheme_root', 'vowel_diacritic', 'consonant_diacritic']].astype('uint8')\nprint(f'Size of training data: {train_df_.shape}')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# the images are resized to 64 pixels square\nIMG_SIZE=64\n# the images have only black and white (one color channel)\nN_CHANNELS=1\nprint(\"image size is \", IMG_SIZE, \" x \", IMG_SIZE, \" with a color depth of \", N_CHANNELS)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def resize(df, size=IMG_SIZE, need_progress_bar=True):\n    resized = {}\n    if need_progress_bar:\n        for i in tqdm(range(df.shape[0])):\n            # read in an image from the storage directory and resize it\n            image = cv2.resize(df.loc[df.index[i]].values.reshape(137,236),(size,size))\n            resized[df.index[i]] = image.reshape(-1)\n    else:\n        for i in range(df.shape[0]):\n            image = cv2.resize(df.loc[df.index[i]].values.reshape(137,236),(size,size))\n            resized[df.index[i]] = image.reshape(-1)\n    resized = pd.DataFrame(resized).T\n    return resized\n\nprint(\"The function resize() reads images from the storage directory and resizes them\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_dummies(df):\n    cols = []\n    for col in df:\n        cols.append(pd.get_dummies(df[col].astype(str)))\n    return pd.concat(cols, axis=1)\n\nprint(\"The function get_dummies() converts categorical variables into dummy/indicator variables.\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Basic Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Load the model trained in version 00 \n# NOTE: You must first select \"File\" then \"add or upload data\" from the public kernel available here:\n# https://www.kaggle.com/pnussbaum/grapheme-mind-reader-panv00\nmodel = load_model('/kaggle/input/grapheme-mind-reader-panv00/Bengali_Graphemes_K_Shah.h5')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Visuallize the network\nfrom keras.utils import plot_model\nplot_model(model, to_file='model.png')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Loading the training set...\")\n# Just take the first Parquet for speed\ntrain_df = pd.merge(pd.read_parquet(f'/kaggle/input/bengaliai-cv19/train_image_data_{0}.parquet'), train_df_, on='image_id').drop(['image_id'], axis=1)\n    \nX_train = train_df.drop(['grapheme_root', 'vowel_diacritic', 'consonant_diacritic'], axis=1)\nX_train = resize(X_train)/255\n    \n# CNN takes images in shape `(batch_size, h, w, channels)`, so reshape the images\nX_train = X_train.values.reshape(-1, IMG_SIZE, IMG_SIZE, N_CHANNELS)\n    \nY_train_root = pd.get_dummies(train_df['grapheme_root']).values\nY_train_vowel = pd.get_dummies(train_df['vowel_diacritic']).values\nY_train_consonant = pd.get_dummies(train_df['consonant_diacritic']).values\n\nprint(\"...done loading the training set.\\n\")\n\nprint(f'Training images: {X_train.shape}')\nprint(f'Training labels root: {Y_train_root.shape}')\nprint(f'Training labels vowel: {Y_train_vowel.shape}')\nprint(f'Training labels consonants: {Y_train_consonant.shape}')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Let's see how the model behaves on the training set\")\n\none_pred = model.predict(X_train)\n\nprint(\"Have classified \", X_train.shape[0], \"training images\")\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Training input shape:\" , X_train.shape)\nprint(\"Root output shape:\" , one_pred[0].shape)\nprint(\"Vowel output shape:\" , one_pred[1].shape)\nprint(\"Consonant output shape:\" , one_pred[2].shape)\nall_correct = []\nall_incorrect = []\nfor i in range(X_train.shape[0]):\n    if (np.argmax(Y_train_root[i]) != np.argmax(one_pred[0][i]) and\n        np.argmax(Y_train_vowel[i]) != np.argmax(one_pred[1][i]) and\n        np.argmax(Y_train_consonant[i]) != np.argmax(one_pred[2][i])) :\n        all_incorrect.append(i)\n    if (np.argmax(Y_train_root[i]) == np.argmax(one_pred[0][i]) and\n        np.argmax(Y_train_vowel[i]) == np.argmax(one_pred[1][i]) and\n        np.argmax(Y_train_consonant[i]) == np.argmax(one_pred[2][i])) :\n        all_correct.append(i)\n\n# for i, j in enumerate(all_incorrect) :\n#     print(i, j)\nprint(\"Total images examined \", X_train.shape[0])\nprint(\"Total images correctly classified in all three areas \", len(all_correct))\nprint(\"Total images incorrectly classified in all three areas \", len(all_incorrect))\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Visualize few samples of current training dataset\nprint(\"Now create a set for mind reading - just some examples from the completely correct and completely incorrect\")\n\nprint(\"A dozen examples of completely correct training samples\")\nfig, ax = plt.subplots(nrows=3, ncols=4, figsize=(16, 8))\ncount=0\nX_correct = np.zeros([12,X_train.shape[1], X_train.shape[2], X_train.shape[3]])\nfor row in ax:\n    for col in row:\n        col.imshow(X_train[all_correct[count],:,:,0], cmap=\"gray\")\n        X_correct[count] = np.copy(X_train[all_correct[count]])\n        count += 1\nplt.show()\n\nprint(\"A dozen examples of completely INCORRECT training samples\")\nfig, ax = plt.subplots(nrows=3, ncols=4, figsize=(16, 8))\ncount=0\nX_incorrect = np.zeros([12,X_train.shape[1], X_train.shape[2], X_train.shape[3]])\nfor row in ax:\n    for col in row:\n        col.imshow(X_train[all_incorrect[count],:,:,0], cmap=\"gray\")\n        X_incorrect[count] = np.copy(X_train[all_incorrect[count]])\n        count += 1\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"These are the images we will examine more closely\", X_correct.shape, X_incorrect.shape)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Now let's do some mind reading\nGiven only the output of one of the convolutional layers - is enough information being kept for subsequent layers to process correctly?\n### To determine this...\nWe need to process only the output of each convolutional layer, and then reverse the process to create an image. We can then compare that to the original image to seee if too much information has been discarded. Also a human expert can determine if sufficient information has been retained to perform the classification task."},{"metadata":{"trusted":true},"cell_type":"code","source":"# PANv12f - cleaned up the code to support any number of convolutional layers\n# NOTE: This \"importing of some weights\" section uses apriori knowledge of the model that we are trying to make subsets of\n# FUTURE WORK: Make this importing of NN subsets automatic (without apriori knowledge)\n# Let's make a new networks whose outputs are the convolution layers:\n# NOTE This is manually configured. Need to make this automated.\n# the seven Convolutional layers are layers:\n# 1, 2, 5, 7, 10, 11, and 14\nnum_conv = 7 # PANv12f - added number of convolutional layers\nconv_layer = [1, 2, 5, 7, 10, 11, 14] # the layer in which the convolutional filter data is stored\nscale_size = [1, 1, 2, 1, 1, 1, 2] # any scaling (due to strides or max pooling) that took place just prior to or at that conv layer (not cumulative)\nmodel_explore = [] # list of models that stop at each convolutional layer\nConv2D_weights = [] # list of filters for convolutional layer\nConv2D_biases = [] # list of filter biases for convolutional layer\n\nfor i in range (num_conv):\n    conv_ind = conv_layer[i]\n    print(\"Conv2D \", i, \"occurs at layer\",conv_ind)\n    model_explore.append(Model(inputs=model.inputs, outputs=model.get_layer(index=conv_ind).output))\n    print(\"Including prior layers, will get an Input Shape of:\",model_explore[i].input_shape)\n    print(\"and have an Output Shape of:\",model_explore[i].output_shape )\n    Conv2D_weights.append(np.copy(model.get_layer(index=conv_ind).get_weights()[0]))\n    Conv2D_biases.append(np.copy(model.get_layer(index=conv_ind).get_weights()[1]))\n    # These are the filters\n    print(\"Final Conv2D has the following rows, columns, color depth, number of filters\", Conv2D_weights[i].shape)\n    # These are the biases\n    print(\"and bias for each filter\", Conv2D_biases[i].shape)\n    print(\"------------------------\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# PANv12f now we use a list of models that can be traversed.\nfor conv_l in range(num_conv):\n    print(\"Overall Explore \",conv_l,\" Model Summary:\", model_explore[conv_l].summary(), \"\\n\")\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# PANv12f - now have a list of filter patches, one for each convolutional layer\nnum_filt = []            # number of filters in each Conv2D layer\nfilt_siz = []            # the size of each filter (assume they are square for now)\nborder_siz = []          # the size of the border around the center pixel of the square (assume filters are odd numbered dimensions like 3, 5, 7, etc.)\ncumulative_border = []   # The cumulative effect of the borders from prior Conv3D layers\nfilter_patches = []      # The \"mind reading\" visualization of each filter - equal to the filter at the first Conv2D layer, but more complex later\n\nfor i in range (num_conv):\n    conv_ind = conv_layer[i]\n    print(\"Show the reconstructed image patch for each filter of convolutional layer \", i, \"which occured at layer \",conv_ind, \"in the original model\")\n    num_filt.append(Conv2D_weights[i].shape[3])\n    # filter Size\n    filt_siz.append(Conv2D_weights[i].shape[0])\n    # Border size\n    border_siz.append(int(filt_siz[i] / 2))\n    cb = 0\n    if i == 0 : \n        cumulative_border.append(0)\n    else:\n        for j in range(i) :\n            cb += border_siz[j]\n        cumulative_border.append(cb)\n    print(\"Conv Layer\", i, \" filter Size:\", filt_siz[i], \" x \", filt_siz[i], \n          \" and border size (padding) of \", border_siz[i], \" and cumulative border from prior layers of \", cumulative_border[i])\n    filter_patches.append(np.zeros((filt_siz[i] + 2 * cumulative_border[i], \n                                    filt_siz[i] + 2 * cumulative_border[i], \n                                    num_filt[i])))\n\n    # For now, draw eight filters on a row for display purposes\n    this_num_filt = num_filt[i]\n    if (this_num_filt % 8 == 0) :\n        drows = int(this_num_filt/8)\n    else :\n        drows = int(this_num_filt/8) + 1\n    dcols = 8\n    width = 24\n    height = int(this_num_filt / 4)\n    fig, ax = plt.subplots(nrows=drows, ncols=dcols, figsize=(width, height))\n    \n    thisrow = 0\n    thiscol = 0\n    # temporary variables for filter size and cumulative border size\n    cb = cumulative_border[i]\n    siz = filt_siz[i]\n    image_patch = np.zeros((siz + 2 * cb, siz + 2 * cb))\n    for this_filter in range(this_num_filt) :\n        if i == 0 :\n            image_patch = np.copy(Conv2D_weights[i][:, :, 0, this_filter])\n        else:\n            image_patch.fill(0)\n            for x in range(cb, siz + cb, 1) :\n                for y in range(cb, siz + cb, 1) :\n                    for depth in range(num_filt[i-1]) :\n                        image_patch[x-cb:x+cb+1, y-cb:y+cb+1] += np.copy((Conv2D_weights[i][x-cb, y-cb, depth, this_filter] * \n                                                                         filter_patches[i-1][:,:,depth]))\n    \n        filter_patches[i][:, :, this_filter] = np.copy(image_patch)\n        \n        # display the filter \n        if (drows > 1) :\n            ax[thisrow, thiscol].imshow(filter_patches[i][:, :, this_filter], cmap=\"gray\")\n        else :\n            ax[thiscol].imshow(filter_patches[i][:, :, this_filter], cmap=\"gray\")\n        thiscol += 1\n        if (thiscol >=8) :\n            thisrow += 1\n            thiscol = 0\n    \n    plt.show()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Now create the output from only the first convolutional layer (using out \"explore\" model), second, and third\nprint(\"First examine twelve CORRECTLY classified examples\")\npred_explore = []\nfor i in range (num_conv):\n    pred_explore.append(model_explore[i].predict(X_correct))\n    print(\"Have processed images through convolutional layer\" ,i, \"of the neural network. Shape is\", pred_explore[i].shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X_test = X_correct\n# In this section we reverse the convolution layer to recreate a picture\nnum_img = X_test.shape[0]\n\n# display the original and re-created images\nfig, ax = plt.subplots(nrows=num_img, ncols=num_conv+1, figsize=(32, 64))\n\nprint(\"\\n original image on the left followed by successive convolution layer recreations \\n\")\n\nfor i in range(num_img) : # \n    col = 0\n    # original image\n    ax[i,col].imshow(X_test[i,:,:,0], cmap=\"gray\")\n    col += 1\n    \n    for j in range(num_conv) :\n        patch = pred_explore[j].shape[1]\n        # Recreate image from combination of this and all of the prior layers of filters, multiplied by the output of this layer\n        tot_border = int((cumulative_border[j] + border_siz[j]))\n        scratch = np.zeros((patch + 2 * tot_border,patch + 2 * tot_border)) # The size of output plus borders\n        for x in range(tot_border, patch+tot_border, 1) :\n            for y in range(tot_border, patch+tot_border, 1) :\n                for this_filter in range(num_filt[j]) :\n                    # Find the color patch attributable to this Filter multiplied by the output value it generated\n                    this_color_patch = np.copy(filter_patches[j][:,:,this_filter])\n                    this_color_patch *= (pred_explore[j][i,x-tot_border,y-tot_border,this_filter])               \n                    scratch[x-tot_border:x+tot_border+1, y-tot_border:y+tot_border+1] += np.copy(this_color_patch)\n        size = scratch.shape[0]\n        # PANv12f6 the border we choose not to display (because it's blank due to padding=\"same\") \n        #      may be too big when things scale down due to strides or max pooling...\n        if scale_size[j] == 1 :\n            tot_border = tot_border\n        else:\n            tot_border = int((tot_border / scale_size[j]) + 1)\n        ax[i,col].imshow(scratch[tot_border:size-tot_border, tot_border:size-tot_border], cmap=\"gray\")    \n#        ax[i,col].imshow(scratch, cmap=\"gray\")   # here is if we want to show the full reconstruction\n        col += 1\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Now create the output from only the first convolutional layer (using out \"explore\" model), second, and third\nprint(\"First examine twelve CORRECTLY classified examples\")\npred_explore = []\nfor i in range (num_conv):\n    pred_explore.append(model_explore[i].predict(X_incorrect))\n    print(\"Have processed images through convolutional layer\" ,i, \"of the neural network. Shape is\", pred_explore[i].shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X_test = X_incorrect\n# In this section we reverse the convolution layer to recreate a picture\nnum_img = X_test.shape[0]\n\n# display the original and re-created images\nfig, ax = plt.subplots(nrows=num_img, ncols=num_conv+1, figsize=(32, 64))\n\nprint(\"\\n original image on the left followed by successive convolution layer recreations \\n\")\n\nfor i in range(num_img) : # \n    col = 0\n    # original image\n    ax[i,col].imshow(X_test[i,:,:,0], cmap=\"gray\")\n    col += 1\n    \n    for j in range(num_conv) :\n        patch = pred_explore[j].shape[1]\n        # Recreate image from combination of this and all of the prior layers of filters, multiplied by the output of this layer\n        tot_border = int((cumulative_border[j] + border_siz[j]))\n        scratch = np.zeros((patch + 2 * tot_border,patch + 2 * tot_border)) # The size of output plus borders\n        for x in range(tot_border, patch+tot_border, 1) :\n            for y in range(tot_border, patch+tot_border, 1) :\n                for this_filter in range(num_filt[j]) :\n                    # Find the color patch attributable to this Filter multiplied by the output value it generated\n                    this_color_patch = np.copy(filter_patches[j][:,:,this_filter])\n                    this_color_patch *= (pred_explore[j][i,x-tot_border,y-tot_border,this_filter])               \n                    scratch[x-tot_border:x+tot_border+1, y-tot_border:y+tot_border+1] += np.copy(this_color_patch)\n        size = scratch.shape[0]\n        # PANv12f6 the border we choose not to display (because it's blank due to padding=\"same\") \n        #      may be too big when things scale down due to strides or max pooling...\n        if scale_size[j] == 1 :\n            tot_border = tot_border\n        else:\n            tot_border = int((tot_border / scale_size[j]) + 1)\n        ax[i,col].imshow(scratch[tot_border:size-tot_border, tot_border:size-tot_border], cmap=\"gray\")    \n#        ax[i,col].imshow(scratch, cmap=\"gray\")   # here is if we want to show the full reconstruction\n        col += 1\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# What can we conclude?\n## Note: Re-evaluated in light of fixes through version PANv12f\nIf you examine the eight (8) columns above, we see the original 64x64 input in the leftmost column, and moving left to right, we see a recreation of the input based on the output of consecutive convolution layers (Conv2D layers) and dense layers.\nSpecifically:\n* Column 1 - Original Input\n* Columns 2 though 8 - Recreation of input based on output of Conv2D layer\n\nIn the case of the above, these recreations are done without any adjustment for biasing, normalization, or even max pooling. The only adjustment made for max pooling is the amount the displayed image is \"zoomed in\" so that it retains the original size of the input for comparison. For example; notice the image degredation when the first max pooling goes from 64x64 to 32x32 (between columns 3 and 4, or equivalently, between the 2nd and 3rd Conv2D). There is a HUGE degredation before the last convolution layer - perhaps due to MaxPooling from 32x32 down to 16x16, perhaps due to the 5x5 filter size, or perhaps something else?\n\n## Big problem with the last Conv2D layer\nIs this because it's closest to the flatten and dense layers (morphed away from the original image becuase of this) or simply due to one-too-many MaxPoolings (leaving it at a 16x16 size from the original 64x64) or the increase in filter size from prior Conv2D at 3x3 to final Conv2D at 5x5??? More investigation is needed\n\n## Impact of Borders\nIn an earlier version of this kernel, I indicated there may be a problem with borders, but improvements to the \"recreation of the original input\" algorithm have shown me that this is not the case. I therefore retract the suggestion that borders need to be added to pad the data.\n## Impact of Resolution\nLooking at the set of twelve \"all correct\" and twelve \"all incorrect\" examples, we see the impact of resolution gets worse and worse with each layer of convolution. This is a natural impact of the initial scaling of images to 64x64, the MaxPooling layers, as well as the strides parameter (not used here). It looks as though the \"all correct\" examples lose some information but enough remains to identify the feature. In the \"all incorrect\" some important detail features are entirely lost by the third convolution (column 4) which occurs after the first \"MaxPooling\" layer. \n\nPossible solutions would be to reduce the use of MaxPooling, and also possibly keep the starting image at a higher resolution. Naturally, these both add to precious resource usage for this contest. \n## Examining the Filters Themselves\nIt is also worthwhile to examine the filters themselves. In all convolutional layers there seem to be no \"duplicate\" filters (no filters in the same convolutional layer that look identical), however, in the later convolutional layers, some look very similar to one another. There may be a method to perform \"pruning\" and use fewer filters.\n"},{"metadata":{},"cell_type":"markdown","source":"# An that's it for the Mind Reader\nThe above diagrams show the original test image, and then, in subsequent pairs of images, shows the output of the first second and 3rd convolutional layer, each followed by a reconstruction of the original image, based on that output.\n\nAn \"expert\" can clearly see if important information has been lost, what was lost, and even what layer within the neural network it was lost.\n\n### Please support by upvoting\nIf you feel this is an important area for further research, please upvote this notebook. This will help bring attention to seeing these or similar functions get incorporated within the Keras framework, and wherever it seems it will do the most good for future researchers. Perhaps others have come to the same conclusions and are already working similar efforts; in which case I am also happy to help where I can. I am a full time teacher, and as such, I can only devote spare time to this effort. Nevertheless, please feel free to reach out to me in this regard.\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"# This structure will be needed to create the contest output\npreds_dict = {\n    'grapheme_root': [],\n    'vowel_diacritic': [],\n    'consonant_diacritic': []\n}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Clean up data to prep for contest entry\ndel train_df\ndel X_correct, X_incorrect, X_train\ndel X_train\ndel Y_train_root, Y_train_vowel, Y_train_consonant\ndel model_explore, model_explore2, model_explore3\ngc.collect()","execution_count":null,"outputs":[]},{"metadata":{"trusted":false},"cell_type":"code","source":"# PANv00 Test on all 4 parquets (to generate output and get a LB score).\ncomponents = ['consonant_diacritic', 'grapheme_root', 'vowel_diacritic']\ntarget=[] # model predictions placeholder\nrow_id=[] # row_id place holder\nfor i in range(4):\n    df_test_img = pd.read_parquet('/kaggle/input/bengaliai-cv19/test_image_data_{}.parquet'.format(i)) \n    df_test_img.set_index('image_id', inplace=True)\n\n    X_test = resize(df_test_img, need_progress_bar=False)/255\n    X_test = X_test.values.reshape(-1, IMG_SIZE, IMG_SIZE, N_CHANNELS)\n    \n    preds = model.predict(X_test)\n\n    for i, p in enumerate(preds_dict):\n        preds_dict[p] = np.argmax(preds[i], axis=1)\n\n    for k,id in enumerate(df_test_img.index.values):  \n        for i,comp in enumerate(components):\n            id_sample=id+'_'+comp\n            row_id.append(id_sample)\n            target.append(preds_dict[comp][k])\n    del df_test_img\n    del X_test\n    gc.collect()\n\ndf_sample = pd.DataFrame(\n    {\n        'row_id': row_id,\n        'target':target\n    },\n    columns = ['row_id','target'] \n)\ndf_sample.to_csv('submission.csv',index=False)\ndf_sample.head()","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.6.6"},"widgets":{"application/vnd.jupyter.widget-state+json":{"state":{"0052c84d689444e3afe0fbf1730e2b3b":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"IntProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"IntProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"","description":" 70%","description_tooltip":null,"layout":"IPY_MODEL_ce1215cee1b04b53a7ddf768812ba88e","max":50210,"min":0,"orientation":"horizontal","style":"IPY_MODEL_06a3232a891047568a3dafe7c94052a8","value":35060}},"06a3232a891047568a3dafe7c94052a8":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":"initial"}},"0bca7d00b2404745b4fda2f78e824568":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_503a9184eec1447e8eb3c2b311514cf0","placeholder":"​","style":"IPY_MODEL_8899b2fb7b334a7b8c33fa1a969d8357","value":" 50210/50210 [00:34&lt;00:00, 1459.87it/s]"}},"0fafae2258ed44819762543511e61c82":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"DescriptionStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"2623f1edf4c7493f8743663b67221a69":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":"initial"}},"31880437cc124e43801b5cdfe0a14165":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_85c90d73df4c441e8c6b7b3cc77ef56c","IPY_MODEL_0bca7d00b2404745b4fda2f78e824568"],"layout":"IPY_MODEL_cc0c354563c641be9e4d5506675670bc"}},"35e1d96d9e96463e97e594691b086c02":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_0052c84d689444e3afe0fbf1730e2b3b","IPY_MODEL_b60b0f26c71c42feb6ceed88f4ce664b"],"layout":"IPY_MODEL_87a63dbcc3a34e2faacb9570daaf24a1"}},"37c61313ae5843db953cb650e2ce9f9b":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"IntProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"IntProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"","description":" 73%","description_tooltip":null,"layout":"IPY_MODEL_a0bd565a8f2249c2a598337cbb42f7f6","max":50210,"min":0,"orientation":"horizontal","style":"IPY_MODEL_a9823562eb4f428c83e444db9616da4c","value":37037}},"503a9184eec1447e8eb3c2b311514cf0":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"7dcac96f2b5645c7b0f39efc43a55cd6":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"85c90d73df4c441e8c6b7b3cc77ef56c":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"IntProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"IntProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"100%","description_tooltip":null,"layout":"IPY_MODEL_f06693e945b64b85879a4e8293ff1615","max":50210,"min":0,"orientation":"horizontal","style":"IPY_MODEL_2623f1edf4c7493f8743663b67221a69","value":50210}},"87a63dbcc3a34e2faacb9570daaf24a1":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"8899b2fb7b334a7b8c33fa1a969d8357":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"DescriptionStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"91f8f1ac583c4f43b52e4b4fed1a6b24":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_7dcac96f2b5645c7b0f39efc43a55cd6","placeholder":"​","style":"IPY_MODEL_0fafae2258ed44819762543511e61c82","value":" 36871/50210 [00:22&lt;00:08, 1648.11it/s]"}},"9c4547d406ed47969c11ab1c58e454da":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"a0bd565a8f2249c2a598337cbb42f7f6":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"a9823562eb4f428c83e444db9616da4c":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":"initial"}},"aeaf9dc33c184d70add3d86fabb5c356":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_37c61313ae5843db953cb650e2ce9f9b","IPY_MODEL_91f8f1ac583c4f43b52e4b4fed1a6b24"],"layout":"IPY_MODEL_b8b8afaa9df14a6685dd4d77592883ea"}},"b28536c096f84542aa62220fbf0d1a1d":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"DescriptionStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"b60b0f26c71c42feb6ceed88f4ce664b":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_9c4547d406ed47969c11ab1c58e454da","placeholder":"​","style":"IPY_MODEL_b28536c096f84542aa62220fbf0d1a1d","value":" 34895/50210 [00:21&lt;00:09, 1644.11it/s]"}},"b8b8afaa9df14a6685dd4d77592883ea":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"cc0c354563c641be9e4d5506675670bc":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"ce1215cee1b04b53a7ddf768812ba88e":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"f06693e945b64b85879a4e8293ff1615":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}}},"version_major":2,"version_minor":0}}},"nbformat":4,"nbformat_minor":1}