{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Introduction\n\nThis notebook shows how to predict the image classes with the use of TensorFlow.\n\nIt uses a simple home-made neural network model.\n\nThe description of the model is added with the explanation on how to compute the output shapes and number of parameters for the different layers. \nIt can be used by someone intersted on how the layers are actually populated with neurons.\n\nIf you find it useful, please feel free to upvote it !\n\nThanks to Praveen for his notebook https://www.kaggle.com/prvnkmr/ranzcr-tf-baseline-lb-0-908, that helped me with to build the input datasets.\n\nThe notebook is divided into :\n\n\n1) Load the csv\n\n2) A small exploration\n\n3) Create the train, validation and test datasets\n\n4) Create the model\n\n5) Train the model\n\n6) Make previsions on the test data\n\n7) Submit the model\n\n"},{"metadata":{},"cell_type":"markdown","source":"# 0) Import modules"},{"metadata":{"trusted":true},"cell_type":"code","source":"import tensorflow as tf\n\nfrom tensorflow.keras.models import Sequential, load_model\nfrom tensorflow.keras.layers import Activation, Dropout, Flatten, Dense, Conv2D, MaxPooling2D\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint\n\nimport matplotlib.pyplot as plt\n\nfrom sklearn.model_selection import train_test_split\n\nimport seaborn as sns\n\nfrom PIL import Image\n\nimport numpy as np\nimport pandas as pd\nimport os","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 1) Load csv"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\n\n#for dirname, _, filenames in os.walk('/kaggle/input'):\n#    print(dirname)\n#    for filename in filenames:\n#        if dirname == '/kaggle/input/ranzcr-clip-catheter-line-classification':\n#            print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"# useful paths\ncatherer_path = '/kaggle/input/ranzcr-clip-catheter-line-classification'\ntrain_path = os.path.join(catherer_path,'train')\ntest_path = os.path.join(catherer_path,'test')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Load train.csv data\ntrain_csv_path = os.path.join(catherer_path,'train.csv')\ntrain_df = pd.read_csv(train_csv_path)\n\n# classes to predict\nclasses = [col for col in train_df.columns if col not in ['StudyInstanceUID','PatientID']]\nprint(classes)\n\n# add the .jpg to the file names in the dataset\ntrain_df['path_name'] = train_df['StudyInstanceUID'].apply(lambda x:os.path.join(train_path,x+'.jpg'))\nprint(\"Example of a path name : {}\".format(train_df['path_name'][0]))\n# shape of the train data\nprint(\"\\nShape of train dataframe : {}\\n\".format(train_df.shape))\nprint(\"check for null values :\")\nprint(train_df.isnull().sum())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Load test.csv data\ntest_csv_path = os.path.join(catherer_path,'sample_submission.csv')\ntest_df = pd.read_csv(test_csv_path)\n\n# add the .jpg to the file names in the test dataset\ntest_df['path_name'] = test_df['StudyInstanceUID'].apply(lambda x:os.path.join(test_path,x+'.jpg'))\nprint(\"\\nShape of test dataframe : {}\\n\".format(test_df.shape))\nprint(\"check for null values :\")\nprint(test_df.isnull().sum())\n#test_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Print one image\nim_path = train_df['path_name'].iloc[0]\nim_example = Image.open(im_path)\nprint(\"Image size = {}\".format(im_example.size))\nplt.figure(figsize=(12,8))\nplt.imshow(im_example,cmap='Greys')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# print the same image with less pixels\n# We see that on a 256 X 256 pixels, some details are still visible\ndim1 = 256\ndim2 = 256\nim_example_red = im_example.resize((dim1,dim2))\nplt.figure(figsize=(12,8))\nplt.imshow(im_example_red,cmap='Greys')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 2) Exploration"},{"metadata":{"trusted":true},"cell_type":"code","source":"# True if you want to do the exploration steps, False otherwise\nexploration = False ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Size of the images in the train dataset\n# It can take a few minutes\nif exploration:\n    dim1 = []\n    dim2 = []\n    counter = 0\n    for image_filename in os.listdir(train_path):\n        counter+=1\n        if np.mod(counter,1000) == 0:\n            print(\"counter : {}\".format(counter))\n        img = Image.open(os.path.join(train_path,image_filename))\n        d1,d2 = img.size\n        dim1.append(d1)\n        dim2.append(d2)\n    sns.jointplot(dim1,dim2)\n    print(\"Dimension 1 : {}\".format(np.mean(dim1)))\n    print(\"Dimension 2 : {}\".format(np.mean(dim2)))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Number of images for each class\n# We see that the ETT abnormal, NGT abnormal and NGT borderline classes have few images\nif exploration:\n    plt.figure(figsize=(10,6))\n    graph = sns.barplot(x=classes,y=train_df[classes].sum())\n    graph.set_xticklabels(graph.get_xticklabels(), rotation=90)\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 3) Create the train, validation and test data sets"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Some parameters\n\n# image size\nim_width= 256\nim_height = 256\n# batch size\nbatch_size = 32\n\n#steps_per_epoch = len(X_train) // batch_size\n#print(steps_per_epoch)\n#print(X_valid.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Limit the number of train samples if you want to accelerate the training\n# Can be used to test your model - see there are no bugs\n\nlim = True\nif lim:\n    red_train_df = train_df.sample(frac=0.1)\nelse:\n    red_train_df = train_df.copy()\nprint(red_train_df.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# copy the lines for the classes with few images and add them to the dataframe so that it is less imbalanced\n# Note that an image can belong to multiple class, so that we also increase the number of images for the \n# classes with a lot of images\n\n# minimal number of samples per class\nn_min = 100\ncount_classes = red_train_df[classes].sum()\next_train_df = [red_train_df]\nfor pred_class in classes:\n    if count_classes[pred_class] < n_min:\n        new_df = red_train_df[red_train_df[pred_class]==1].sample(n_min,replace=True)\n        ext_train_df.append(new_df)\n        \next_train_df = pd.concat(ext_train_df)\nprint(ext_train_df.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Show the number of images per class after adding new ones\nplt.figure(figsize=(10,6))\ngraph = sns.barplot(x=classes,y=ext_train_df[classes].sum())\ngraph.set_xticklabels(graph.get_xticklabels(), rotation=90)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Split the train dataset into a train and a validation datasets\nX_train, X_valid = train_test_split(ext_train_df,test_size=0.2,shuffle=True)\nprint(X_train.shape)\nprint(X_valid.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Create datasets from the dataframes for the train, validation and test data\n# For train and validation data, a slice is made up of the image path name and its labels\n# For the test data, a slice is the image path name\n\nTrain_df = tf.data.Dataset.from_tensor_slices((X_train['path_name'].values, X_train[classes].values))\n\nValid_df = tf.data.Dataset.from_tensor_slices((X_valid['path_name'].values, X_valid[classes].values))\n\nTest_df = tf.data.Dataset.from_tensor_slices((test_df['path_name'].values))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Show a slice\nfor path, label in Train_df.take(5):\n    print ('Path: {}, Label: {}'.format(path, label))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def process_data_train(image_path, label):\n    # returns an image (type EagerTensor) and its labels\n    # decode_jpeg : if channels = 3, same values on 3 planes, here I chose channel = 1\n    img = tf.io.read_file(image_path)\n    img = tf.image.decode_jpeg(img, channels=1)\n    img = tf.image.random_brightness(img, 0.3)\n    img = tf.image.random_flip_left_right(img)\n    img = tf.image.resize(img, [im_height,im_width])\n    \n    return img, label","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def process_data_valid(image_path, label):\n    # No image modification for the vaidation data\n    img = tf.io.read_file(image_path)\n    img = tf.image.decode_jpeg(img, channels=1) # !! idem\n    img = tf.image.resize(img, [im_height,im_width])\n    \n    return img, label","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def process_data_test(image_path):\n    # No labels for the test data\n    img = tf.io.read_file(image_path)\n    img = tf.image.decode_jpeg(img, channels=1) # !! idem\n    img = tf.image.resize(img, [im_height,im_width])\n    \n    return img","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# For each slice, replace the path name with the image data\nTrain_df = Train_df.map(process_data_train, num_parallel_calls=tf.data.experimental.AUTOTUNE)\nValid_df = Valid_df.map(process_data_valid, num_parallel_calls=tf.data.experimental.AUTOTUNE)\nTest_df = Test_df.map(process_data_test, num_parallel_calls=tf.data.experimental.AUTOTUNE)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Show a slice\nfor image, label in Train_df.take(1):\n    print ('Image: {}, Label: {}'.format(tf.reshape(image,(256,256)), label))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def configure_for_performance(ds, batch_size = 32):\n    \n    ds = ds.cache('/kaggle/dump.tfcache') \n    #ds = ds.repeat()\n    ds = ds.shuffle(buffer_size=1024)\n    ds = ds.batch(batch_size)\n    ds = ds.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)\n    \n    return ds\n\ntrain_ds_batch = configure_for_performance(Train_df)\nvalid_ds_batch = Valid_df.batch(32*2)\ntest_ds_batch = Test_df.batch(32*2)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 4) Create the model"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Create the model\n# For the selection of the parameters of the convolutional layers, you can see on :\n#https://stats.stackexchange.com/questions/148139/rules-for-selecting-convolutional-neural-network-hyperparameters\n\n# Add one dimension to the image size, needed for Tensorflow\nimage_shape = (im_width,im_height,1) \n# number of classes\nn_classes = len(classes)\nprint(\"There are {} classes to predict\".format(n_classes))\n\n# model\nmodel = Sequential()\n#model.add(tf.keras.layers.Input(shape=image_shape))\n#model.add(tf.keras.layers.experimental.preprocessing.RandomRotation(0.05, interpolation='nearest'))\n\n# Convolutional layers to filter the data and MaxPooling layers to reduce the model size\nmodel.add(Conv2D(filters=32, kernel_size=(3,3),input_shape=image_shape, activation='relu',padding='valid'))\nmodel.add(MaxPooling2D(pool_size=(2, 2)))\n\nmodel.add(Conv2D(filters=64, kernel_size=(3,3),input_shape=image_shape, activation='relu',padding='valid'))\nmodel.add(MaxPooling2D(pool_size=(2, 2)))\n\nmodel.add(Conv2D(filters=64, kernel_size=(3,3),input_shape=image_shape, activation='relu',padding='valid'))\nmodel.add(Conv2D(filters=64, kernel_size=(3,3),input_shape=image_shape, activation='relu',padding='valid'))\nmodel.add(MaxPooling2D(pool_size=(2, 2)))\n\n# Flat layer\nmodel.add(Flatten())\n\n# Dense layer with 24 neurons\nmodel.add(Dense(240, activation='relu'))\n\n# Dense layer with 24 neurons\n#model.add(Dense(30, activation='relu'))\n\n# Drop out layer to reduce overfitting.\n# 50 % of the neurons are randomly deactivated during training\nmodel.add(Dropout(0.3))\n\n# Dense class, activation = sigmoid because for each field to predict\n# we have a binary choice\nmodel.add(Dense(n_classes, activation='sigmoid'))\n\n# compile the model\nadam_opt = tf.keras.optimizers.Adam(learning_rate=0.0001)\nmodel.compile(loss='binary_crossentropy',\n              optimizer=adam_opt,\n              metrics=[tf.keras.metrics.AUC(multi_label=True)]\n             )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Summary of the model\n# \n#  1 convolutional layer :\n#      output shape = 254 X 254 X 32 \n#                     254 X 254 because input shape = 256 X 256 and we have a kernel size = (3,3)\n#                         with padding  = 'valid'\n#                     32 because there are 32 filters  \n#      param = 320 : kernel_size = (3,3) => 9 parameters by filter. Plus 1 bias => 10 parameters for a filter. 32 filters\n#                    => 32 X 10 = 320\n#\n# 1 MaxPooling (2,2):\n#      output shape = (127,127,32) because input size is divided by 2 in width and height, with still 32 filters\n#\n# 1 convolutional layer :\n#      output shape = 125 X 125 X 64 (same explanation as before and 64 filters) \n#      param = 18496 : 64 filters in output, each applied on an input cell of 3 X 3 X 32\n#                      (in each of the 32 input filters, the is kernel size = (3,3))\n#                      Plus 64 bias (one for each filter)\n#                      => 18496 = 64 X 32 X 9 + 64\n#\n# 1 MaxPooling (2,2):\n#      output shape = (62,62,64) because input size is divided by 2 in width and height, with still 64 filters\n#\n# 1 convolutional layer :\n#      output shape = 60 X 60 X 64 (same explanation as before and 64 filters) \n#      param = 36928 : 64 filters in output, each applied on an input cell of 3 X 3 X 64\n#                      (in each of the 64 input filters, the kernel size = (3,3))\n#                      Plus 64 bias (one for each filter)\n#                      => 36928 = 64 X 64 X 9 + 64\n#\n# 1 convolutional layer :\n#      output shape = 58 X 58 X 64 (same explanation as before and 64 filters) \n#      param = 36928 : 64 filters in output, each applied on an input cell of 3 X 3 X 64\n#                      (in each of the 64 input filters, the kernel size = (3,3))\n#                      Plus 64 bias (one for each filter)\n#                      => 36928 = 64 X 64 X 9 + 64\n#\n# 1 MaxPooling (2,2):\n#      output shape = (29,29,64) because input size is divided by 2 in width and height, with still 64 filters\n# \n# 1 Flatten layer :\n#      ouput shape = 53 824 = 29 X 29 X 64\n#\n# 1 Dense layer with 240 neurons\n#      param = 12 918 000 : 240 neurons linked to 53 824 neurons, plus 240 bias\n#               => 12 918 000 = 240 X 53 824 + 240\n#\n# 1 Drop out layer : 50 % of the neurons in the previous Dense layer are not selected in training\n#\n# 1 Dense layer :\n#      output shape = 11 because 11 classes to predict\n#      param = 2651 = 11 X 240 + 11 \n# \n# We see that 12 000 000 parameters out of 13 000 000 come from the first Dense layer alone\n\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Early stopping\n# If 2 (parameter patience) epochs are run with a decrease in the validation loss, \n# stop the training\nearly_stop = EarlyStopping(monitor='val_loss',patience=2)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Checkpoint to save the \"best\" model parameters\ncheckpoint = ModelCheckpoint(\n    'best_model.hdf5', monitor='val_loss', save_best_only=True,\n    save_weights_only=False, mode='auto'\n)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 5) Train the model"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Train the model\nresults = model.fit(train_ds_batch,#train_generator,\n                    epochs=1,\n                    batch_size=32,\n                    validation_data=valid_ds_batch,#valid_generator,\n                    callbacks=[early_stop, checkpoint],\n                    verbose=True,\n   # steps_per_epoch=steps_per_epoch\n                   )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# History of the loss throughout the epochs\nlosses = pd.DataFrame(model.history.history)\nprint(model.metrics_names)\nlosses[['loss','val_loss','auc','val_auc']].plot()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Load the best weights\nmodel.load_weights('best_model.hdf5') ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# save the model in case you want to reuse it\n#model.save('my_model')\n# load model\n#saved_model = load_model('my_model')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 6) Make previsions on the test data"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Predict on the test data\npred_probabilities = model.predict(test_ds_batch)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Some info\nprint(pred_probabilities.shape)\nprint(pred_probabilities[0])\nprint(pred_probabilities[100])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 7) Submit the model"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Create a dataframe with the predicted probabilities for the test images\npred_df = pd.DataFrame(columns=classes,data=pred_probabilities, index=test_df.index)\npred_df = pd.concat([test_df['StudyInstanceUID'],pred_df],axis=1)\npred_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Create the csv\npred_df.to_csv('submission.csv',index=False)","execution_count":null,"outputs":[]}],"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":4,"nbformat_minor":4}