{"cells":[{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_kg_hide-input":false,"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 pprint import pprint\n\n\n# 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\nroot_train_directory = '/kaggle/input/human-protein-atlas-image-classification/train/'\nroot_test_directory = '/kaggle/input/human-protein-atlas-image-classification/test/'\ninput_shape=(128,128,4)\n\n\n# Any results you write to the current directory are saved as output.\n\nname_label_dict = {\n0:  'Nucleoplasm',\n1:  'Nuclear membrane',\n2:  'Nucleoli',   \n3:  'Nucleoli fibrillar center',\n4:  'Nuclear speckles',\n5:  'Nuclear bodies',\n6:  'Endoplasmic reticulum',   \n7:  'Golgi apparatus',\n8:  'Peroxisomes',\n9:  'Endosomes',\n10:  'Lysosomes',\n11:  'Intermediate filaments',\n12:  'Actin filaments',\n13:  'Focal adhesion sites',   \n14:  'Microtubules',\n15:  'Microtubule ends',  \n16:  'Cytokinetic bridge',   \n17:  'Mitotic spindle',\n18:  'Microtubule organizing center',  \n19:  'Centrosome',\n20:  'Lipid droplets',\n21:  'Plasma membrane',   \n22:  'Cell junctions', \n23:  'Mitochondria',\n24:  'Aggresome',\n25:  'Cytosol',\n26:  'Cytoplasmic bodies',   \n27:  'Rods & rings' }","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Let's load the data"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv('/kaggle/input/human-protein-atlas-image-classification/train.csv')\ntrain_df.set_index('Id',inplace=True)\ntrain = train_df.to_dict()['Target']\ntrain_img_names = list(train.keys())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"For the training to work, we need to merge all the layer of each image and turn them into rgby images. We'll also resize the images to make training and prediction faster."},{"metadata":{"trusted":true},"cell_type":"code","source":"SIZE = (128,128)\ndef get_labels(img):\n    return list(map(int, train[img].split(' ')))\n# We'll fusion images\ndef open_multilayer_image(path, test=False):\n    fullpath = root_train_directory+path\n    if test:\n        fullpath = root_test_directory+path\n    red = plt.imread(fullpath+\"_red.png\")\n    red = cv2.resize(red, SIZE)\n    green = plt.imread(fullpath+\"_green.png\")\n    green = cv2.resize(green, SIZE)\n    blue = plt.imread(fullpath+\"_blue.png\")\n    blue = cv2.resize(blue, SIZE)\n    yellow = plt.imread(fullpath+\"_yellow.png\")\n    yellow = cv2.resize(yellow, SIZE)\n    ni = np.zeros((SIZE[0],SIZE[1],4), 'uint8')\n    ni[..., 0] = red*255\n    ni[..., 1] = green*255\n    ni[..., 2] = blue*255\n    ni[..., 3] = yellow*255\n    return ni","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We can have an idea of what's the date look like by looking at a few samples selected randomly along with their labels."},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"import cv2\nimport matplotlib.pyplot as plt\nfrom random import randrange\nfrom textwrap import wrap\n\n#print('labels:',get_labels(img_name))\n\nfig=plt.figure(figsize=(10, 16))\n\ncolumns = 4\nrows = 5\nfor i in range(1, columns*rows +1):\n    num = randrange(len(train_img_names))\n    img_name = train_img_names[num]\n    img = open_multilayer_image(img_name)\n    sub = fig.add_subplot(rows, columns, i)\n    # make title\n    title = ''\n    for label in get_labels(img_name):\n        title+=name_label_dict[label]+', '\n    sub.set_title(\"\\n\".join(wrap(title[:-2],25)))\n    plt.axis('off')\n    plt.imshow(img[...,:-1])\nplt.show();","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We need to convert every 0-255 value to a 0-1 real value so that the model can train."},{"metadata":{"trusted":true},"cell_type":"code","source":"from __future__ import print_function\nimport keras\nfrom keras.datasets import mnist\nfrom keras.models import Sequential\nfrom keras.layers import Dense, Dropout, Flatten\nfrom keras.layers import Conv2D, MaxPooling2D\nfrom keras import backend as K\nfrom keras.callbacks import EarlyStopping,ModelCheckpoint\n\nbatch_size = 128\nnum_classes = len(name_label_dict)\nepochs = 60\n\ndef preprocess_x(share, train_img_names, test=False):\n    x_train = []\n    for i in range(0,int(len(train_img_names)*share)):\n        x_train.append(open_multilayer_image(train_img_names[i], test))\n    x_train = np.array(x_train)\n    x_train = x_train.astype('float32')\n    x_train /= 255\n    return x_train\n\n\ndef preprocess_y(share, train_img_names):\n    y_train = []\n    for i in range(0,int(len(train_img_names)*share)):\n        y_train.append(get_labels(train_img_names[i]))\n    y_train = np.array(y_train)\n\n    # convert class vectors to binary class matrices\n    y_train_formatted = []\n    for y in y_train:\n        label = np.zeros(num_classes)\n        for j in y:\n            label[j]=1\n        y_train_formatted.append(label)\n    y_train = np.array(y_train_formatted)\n    return y_train\n\n\nx_train  = preprocess_x(0.01, train_img_names)\ny_train  = preprocess_y(0.01, train_img_names)\n# Create a test set\nx_test = x_train[int(len(x_train)*0.8):]\nx_train = x_train[:int(len(x_train)*0.8)]\n\ny_test = y_train[int(len(y_train)*0.8):]\ny_train = y_train[:int(len(y_train)*0.8)]\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We use a data generator to be able to train on all the images without need for a hudge amount of RAM."},{"metadata":{"trusted":true},"cell_type":"code","source":"# Data genetor\n\nimport numpy as np\nimport cv2\nfrom tensorflow.keras.utils import Sequence\n\n\nclass DataGenerator(Sequence):\n    \"\"\"Generates data for Keras\n    Sequence based data generator. Suitable for building data generator for training and prediction.\n    \"\"\"\n    def __init__(self, list_IDs, image_path,\n                 to_fit=True, batch_size=32, dim=SIZE,\n                 n_channels=4, n_classes=10, shuffle=True):\n        \"\"\"Initialization\n        :param list_IDs: list of all 'label' ids to use in the generator\n        :param image_path: path to images location\n        :param to_fit: True to return X and y, False to return X only\n        :param batch_size: batch size at each iteration\n        :param dim: tuple indicating image dimension\n        :param n_channels: number of image channels\n        :param n_classes: number of output masks\n        :param shuffle: True to shuffle label indexes after every epoch\n        \"\"\"\n        self.list_IDs = list_IDs\n        self.image_path = image_path\n        self.to_fit = to_fit\n        self.batch_size = batch_size\n        self.dim = dim\n        self.n_channels = n_channels\n        self.n_classes = n_classes\n        self.shuffle = shuffle\n        self.on_epoch_end()\n\n    def __len__(self):\n        \"\"\"Denotes the number of batches per epoch\n        :return: number of batches per epoch\n        \"\"\"\n        return int(np.floor(len(self.list_IDs) / self.batch_size))\n\n    def __getitem__(self, index):\n        \"\"\"Generate one batch of data\n        :param index: index of the batch\n        :return: X and y when fitting. X only when predicting\n        \"\"\"\n        # Generate indexes of the batch\n        indexes = self.indexes[index * self.batch_size:(index + 1) * self.batch_size]\n\n        # Find list of IDs\n        list_IDs_temp = [self.list_IDs[k] for k in indexes]\n\n        # Generate data\n        X = self._generate_X(list_IDs_temp)\n\n        if self.to_fit:\n            y = self._generate_y(list_IDs_temp)\n            return X, y\n        else:\n            return X\n\n    def on_epoch_end(self):\n        \"\"\"Updates indexes after each epoch\n        \"\"\"\n        self.indexes = np.arange(len(self.list_IDs))\n        if self.shuffle == True:\n            np.random.shuffle(self.indexes)\n\n    def _generate_X(self, list_IDs_temp):\n        \"\"\"Generates data containing batch_size images\n        :param list_IDs_temp: list of label ids to load\n        :return: batch of images\n        \"\"\"\n        # Initialization\n        X = np.empty((self.batch_size, *self.dim, self.n_channels))\n\n        # Generate data\n        for i, ID in enumerate(list_IDs_temp):\n            # Store sample\n            X[i,] = self._load_and_preprocess(self.image_path + ID)\n\n        return X\n\n    def _generate_y(self, list_IDs_temp):\n        \"\"\"Generates data containing batch_size masks\n        :param list_IDs_temp: list of label ids to load\n        :return: batch if masks\n        \"\"\"\n        y_train = []\n        for img_id in list_IDs_temp:\n            y_train.append(get_labels(img_id))\n        y_train = np.array(y_train)\n\n        # convert class vectors to binary class matrices\n        y_train_formatted = []\n        for y in y_train:\n            label = np.zeros(num_classes)\n            for j in y:\n                label[j]=1\n            y_train_formatted.append(label)\n        y_train = np.array(y_train_formatted)\n        return y_train\n\n    def _load_and_preprocess(self, image_path):\n        \"\"\"Load grayscale image\n        :param image_path: path to image to load\n        :return: loaded image\n        \"\"\"\n        fullpath = image_path\n        red = plt.imread(fullpath+\"_red.png\")\n        red = cv2.resize(red, SIZE)\n        green = plt.imread(fullpath+\"_green.png\")\n        green = cv2.resize(green, SIZE)\n        blue = plt.imread(fullpath+\"_blue.png\")\n        blue = cv2.resize(blue, SIZE)\n        yellow = plt.imread(fullpath+\"_yellow.png\")\n        yellow = cv2.resize(yellow, SIZE)\n        ni = np.zeros((SIZE[0],SIZE[1],4), 'uint8')\n        ni[..., 0] = red*255\n        ni[..., 1] = green*255\n        ni[..., 2] = blue*255\n        ni[..., 3] = yellow*255\n        ni = ni.astype('float32')\n        ni /= 255\n        return ni","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We're using a very simple convolutionnal neural network with binary cross entropy and the Adam optmizer, we use the sigmoid as our activation function. We're using 4 convolutionnal layers, I first tried with two, but results were not satisfying as it seemed the model was only learning low level features."},{"metadata":{"trusted":true},"cell_type":"code","source":"import tensorflow as tf\nTHRESHOLD = 0.05 \ndef f1(y_true, y_pred):\n    # credits: https://www.kaggle.com/guglielmocamporese/macro-f1-score-keras\n    #y_pred = K.round(y_pred)\n    y_pred = K.cast(K.greater(K.clip(y_pred, 0, 1), THRESHOLD), K.floatx())\n    tp = K.sum(K.cast(y_true*y_pred, 'float'), axis=0)\n    tn = K.sum(K.cast((1-y_true)*(1-y_pred), 'float'), axis=0)\n    fp = K.sum(K.cast((1-y_true)*y_pred, 'float'), axis=0)\n    fn = K.sum(K.cast(y_true*(1-y_pred), 'float'), axis=0)\n\n    p = tp / (tp + fp + K.epsilon())\n    r = tp / (tp + fn + K.epsilon())\n\n    f1 = 2*p*r / (p+r+K.epsilon())\n    f1 = tf.where(tf.math.is_nan(f1), tf.zeros_like(f1), f1)\n    return K.mean(f1)\n\ndef create_model():\n    model = Sequential()\n    model.add(Conv2D(32, kernel_size=(3, 3),\n                     activation='relu',\n                     input_shape=input_shape))\n    model.add(Conv2D(64, (3, 3), activation='relu'))\n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    model.add(Conv2D(32, kernel_size=(3, 3),\n                     activation='relu',\n                     input_shape=input_shape))\n    model.add(Conv2D(64, (3, 3), activation='relu'))\n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    model.add(Conv2D(32, kernel_size=(3, 3),\n                     activation='relu',\n                     input_shape=input_shape))\n    model.add(Conv2D(64, (3, 3), activation='relu'))\n    model.add(Dropout(0.25))\n    model.add(Flatten())\n    model.add(Dense(128, activation='relu'))\n    model.add(Dropout(0.5))\n    model.add(Dense(num_classes, activation='sigmoid'))\n\n    model.compile(loss=keras.losses.binary_crossentropy,\n                  optimizer=keras.optimizers.Adam(lr=1e-3, decay=1e-3 / epochs),\n                  metrics=['accuracy', f1])\n    return model\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We use early stopping in order not to have to chose a random number of epoch to train with."},{"metadata":{"trusted":true},"cell_type":"code","source":"model = create_model()\n\nimage_path = root_train_directory\ntraining_generator = DataGenerator(train_img_names[:int(len(train_img_names)*0.8)], image_path, dim=SIZE,\n                 n_channels=4)\nvalidation_generator = DataGenerator(train_img_names[int(len(train_img_names)*0.8):], image_path, dim=SIZE,\n                 n_channels=4)\n\nH = model.fit_generator(generator=training_generator, validation_data=validation_generator,\n          epochs=5,\n          verbose=1,\n         callbacks = [EarlyStopping(monitor='val_loss', patience=6, verbose=1), ModelCheckpoint(filepath='/tmp/weights.hdf5', verbose=1, save_best_only=True)])\nmodel = create_model()\nmodel.load_weights('/tmp/weights.hdf5')\nscore = model.evaluate(x_test, y_test, verbose=0)\nprint('Test loss:', score[0])\nprint('Test accuracy:', score[1])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = create_model()\nmodel.load_weights('/tmp/weights.hdf5')\nscore = model.evaluate(x_test, y_test, verbose=0)\nprint('Test loss:', score[0])\nprint('Test accuracy:', score[1])\n\n# plot the training loss and accuracy\nplt.style.use(\"ggplot\")\nplt.figure()\nN = len(H.history['loss'])\nplt.plot(np.arange(0, N), H.history[\"loss\"], label=\"train_loss\")\nplt.plot(np.arange(0, N), H.history[\"val_loss\"], label=\"val_loss\")\nplt.plot(np.arange(0, N), H.history[\"accuracy\"], label=\"train_acc\")\nplt.plot(np.arange(0, N), H.history[\"val_accuracy\"], label=\"val_acc\")\nplt.title(\"Training Loss and Accuracy\")\nplt.xlabel(\"Epoch #\")\nplt.ylabel(\"Loss/Accuracy\")\nplt.legend(loc=\"upper left\")\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def pred_to_textlabels(pred):\n    classes = np.nonzero(pred[0])[0]\n    text = ''\n    for cl in classes:\n        text+=str(name_label_dict[cl])+' '\n    return text\n    ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Prediction examples\nLet's now have a look to a few samples from the validation set to have a more precise idea of how the model b"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Let's do a random predictions\nfig=plt.figure(figsize=(10, 16))\n\ncolumns = 4\nrows = 5\nfor i in range(1, columns*rows +1):\n    num = randrange(len(train_img_names))\n    num = randrange(1200)\n    img_name = train_img_names[num]\n    img = open_multilayer_image(img_name)\n    img_to_predict = cv2.resize(img, SIZE)\n    pred = model.predict(np.array([img_to_predict]))\n    title = pred_to_textlabels(pred)\n    \n    real_label = ''\n    for label in get_labels(img_name):\n        real_label+=name_label_dict[label]+', '\n    print('\\'',real_label, '\\'was predicted as\\'',title,'\\'' )\n    sub = fig.add_subplot(rows, columns, i)\n    # make title\n    sub.set_title(\"\\n\".join(wrap(title,20)))\n    plt.axis('off')\n    plt.imshow(img[...,:-1])\nplt.show();","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Submission file"},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df = pd.read_csv('/kaggle/input/human-protein-atlas-image-classification/sample_submission.csv')\nimgs_test = test_df['Id'].to_list()\nx_test = preprocess_x(1,imgs_test, test=True)\n\npredictions = {}\nfor i in range(0, len(imgs_test)):\n    sparse_vec = model.predict(np.array([x_test[i]]))\n    predictions[imgs_test[i]]= np.nonzero(sparse_vec[0])[0]\npredictions","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df['Predicted'] = list(predictions.values())\ntest_df.to_csv('predictions.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"nvidia-smi","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!nvidia-smi","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}