{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Imports"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nimport cv2\nfrom keras.preprocessing import image\nfrom keras.preprocessing.image import ImageDataGenerator\nfrom numpy import expand_dims\nfrom keras.preprocessing.image import load_img\nfrom keras.preprocessing.image import img_to_array\nfrom sklearn.model_selection import train_test_split\nimport gc\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models\nfrom sklearn.metrics import auc\nfrom sklearn.metrics import roc_curve\nprint('Done')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Dataset exploration"},{"metadata":{"trusted":true},"cell_type":"code","source":"#Let's explore the directory segmentation\nos.listdir('/kaggle/input/siim-isic-melanoma-classification')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#How many training images do we have?\nlen(os.listdir('/kaggle/input/siim-isic-melanoma-classification/jpeg/train'))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#How many test images do we have?\nlen(os.listdir('/kaggle/input/siim-isic-melanoma-classification/jpeg/test'))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"train_metadata = pd.read_csv('/kaggle/input/siim-isic-melanoma-classification/train.csv')\ntrain_metadata.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Dataset length\nlen(train_metadata)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Sex segmentation\nmale = train_metadata[train_metadata['sex']=='male']['sex'].count()\nfemale = train_metadata[train_metadata['sex']=='female']['sex'].count()\nprint('The number of male patients: ',male)\nprint('The number of female patients: ',female)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"benign = train_metadata[train_metadata['benign_malignant']=='benign']['benign_malignant'].count()\nmalignant = train_metadata[train_metadata['benign_malignant']=='malignant']['benign_malignant'].count()\nprint('The number of benign cases: ',benign)\nprint('The number of malign cases: ',malignant)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#We will definitely will need to augment the malignant cases","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Let's explore some images - Let's study a benign case\n\n%matplotlib inline\nplt.figure()\nimage = cv2.imread('/kaggle/input/siim-isic-melanoma-classification/jpeg/train/ISIC_2637011.jpg')\nimage = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\nimage = cv2.resize(image, (128, 128))\nimage = image.astype('float32')\nimage /= 255.0\nplt.imshow(image)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_metadata[train_metadata['benign_malignant']=='malignant'].head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Let's explore a malignant case\n%matplotlib inline\nplt.figure()\nimage = cv2.imread('/kaggle/input/siim-isic-melanoma-classification/jpeg/train/ISIC_0232101.jpg')\nimage = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n#image = cv2.resize(image, (128, 128))\nplt.imshow(image)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Is there any relationship between age/anatom site and diagnosis?\ntrain_metadata['anatom_site_general_challenge'].unique()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Is there any particular lesion location more abundant than others?\n\n%matplotlib inline\nplt.figure(figsize=(30,10))\n\nax1 = plt.subplot(131)\nlabels1 = list(train_metadata['anatom_site_general_challenge'].value_counts().index.values)\nsizes1 = list(train_metadata['anatom_site_general_challenge'].value_counts().values)\nexplode1 = (0, 0.1, 0, 0)\nax1.pie(sizes1, labels=labels1, autopct='%1.1f%%',shadow=True, startangle=45)\nax1.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle.\n\nax2 = plt.subplot(132)\nlabels2 = list(train_metadata[train_metadata['benign_malignant']=='benign']['anatom_site_general_challenge'].value_counts().index.values)\nsizes2 = list(train_metadata[train_metadata['benign_malignant']=='benign']['anatom_site_general_challenge'].value_counts().values)\nexplode2 = (0, 0.1, 0, 0)\nax2.pie(sizes2, labels=labels2, autopct='%1.1f%%',shadow=True, startangle=45)\nax1.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle.\n\nax3 = plt.subplot(133)\nlabels3 = list(train_metadata[train_metadata['benign_malignant']=='malignant']['anatom_site_general_challenge'].value_counts().index.values)\nsizes3 = list(train_metadata[train_metadata['benign_malignant']=='malignant']['anatom_site_general_challenge'].value_counts().values)\nexplode3 = (0, 0.1, 0, 0)\nax3.pie(sizes3, labels=labels3, autopct='%1.1f%%',shadow=True, startangle=45)\nax3.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle.\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Looks like all lesion images have same amount proportion, so locations should not be a relevant factor. Let's study age.","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import seaborn as sns\n%matplotlib inline\n\nplt.figure(figsize=(10,5))\nsex = ['benign','malignant']\nvalues = [train_metadata[train_metadata['benign_malignant']=='benign']['age_approx'].mean(),train_metadata[train_metadata['benign_malignant']=='malignant']['age_approx'].mean()]\nsns.barplot(sex,values)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Looks like age is an important feature. We'll see if consider this is crucial when predicting labels for each case.","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#What about sex?\n\n#Is there any particular lesion location more abundant than others?\n\n%matplotlib inline\nplt.figure(figsize=(30,10))\n\nax1 = plt.subplot(131)\nlabels1 = list(train_metadata['sex'].value_counts().index.values)\nsizes1 = list(train_metadata['sex'].value_counts().values)\nax1.pie(sizes1, labels=labels1, autopct='%1.1f%%',shadow=True, startangle=45)\nax1.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle.\n\nax2 = plt.subplot(132)\nlabels2 = list(train_metadata[train_metadata['benign_malignant']=='benign']['sex'].value_counts().index.values)\nsizes2 = list(train_metadata[train_metadata['benign_malignant']=='benign']['sex'].value_counts().values)\nax2.pie(sizes2, labels=labels2, autopct='%1.1f%%',shadow=True, startangle=45)\nax2.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle.\n\nax3 = plt.subplot(133)\nlabels3 = list(train_metadata[train_metadata['benign_malignant']=='malignant']['sex'].value_counts().index.values)\nsizes3 = list(train_metadata[train_metadata['benign_malignant']=='malignant']['sex'].value_counts().values)\nax3.pie(sizes3, labels=labels3, autopct='%1.1f%%',shadow=True, startangle=45)\nax3.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle.\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Looks like case is also different but when talking about malignant cases.","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Image preprocessing"},{"metadata":{"trusted":true},"cell_type":"code","source":"#We will reshape every image to 128*128 pixels\n\ndef data_augmentation(dirname,filename):\n    \n    \"\"\"\n    This function will perform data augmentation: \n    for each one of the images, will create shifted, expanded/reduced, darker/lighter, rotated images. 5 for every modification type. \n    In total, we will create 20 extra images for every one in the original dataset.\n    \"\"\"\n    \n    image_data = []\n    #reading the image\n    image = cv2.imread(os.path.join(dirname, filename))\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    image = cv2.resize(image, (128, 128))\n    #expanding the image dimension to one sample\n    samples = expand_dims(image, 0)\n    # creating the image data augmentation generators\n    datagen1 = ImageDataGenerator(width_shift_range=[-10,10])\n    datagen2 = ImageDataGenerator(zoom_range=[0.7,1.2])\n    datagen3 = ImageDataGenerator(brightness_range=[0.2,1.1])\n    datagen4 = ImageDataGenerator(rotation_range=20)\n    # preparing iterators\n    it1 = datagen1.flow(samples, batch_size=1)\n    it2 = datagen2.flow(samples, batch_size=1)\n    it3 = datagen3.flow(samples, batch_size=1)\n    it4 = datagen4.flow(samples, batch_size=1)\n    image_data.append(image)\n    for i in range(5):\n        # generating batch of images\n        batch1 = it1.next()\n        batch2 = it2.next()\n        batch3 = it3.next()\n        batch4 = it4.next()\n        # convert to unsigned integers\n        image1 = batch1[0].astype('uint8')\n        image2 = batch2[0].astype('uint8')\n        image3 = batch3[0].astype('uint8')\n        image4 = batch4[0].astype('uint8')\n        #appending to the list of images\n        image_data.append(image1)\n        image_data.append(image2)\n        image_data.append(image3)\n        image_data.append(image4)\n        \n    return image_data","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Let's test our function.\nresult = data_augmentation('/kaggle/input/siim-isic-melanoma-classification/jpeg/train','ISIC_0207268.jpg')\n#Let's plot an image\n%matplotlib inline\nplt.figure()\nimage = result[5]\nplt.imshow(image)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"len(result)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def data_transformation(case_type):\n    \n    \"\"\"\n    This function receives a keyword as parameter to determine the kind of set it's going to process.\n    It uses data_augmentation function to expand the image quantity, then resizes the images and finally returns a list containing the images already processed.\n    IMPORTANT: Maybe you'll notice we don't use any Keras method which could make easier the image processing. Instead, we decided to process the images using our own functions.\n    \"\"\"\n    \n    images = []\n    dirname = '/kaggle/input/siim-isic-melanoma-classification/jpeg/train'\n    counter = 0\n    \n    if case_type ==0: # If we need to transform malignant images\n        \n        image_names = train_metadata[train_metadata['benign_malignant']=='malignant']['image_name'].reset_index(drop=True)+'.jpg'\n    \n        for i in range(len(image_names)):\n            filename = image_names[i]\n            image = cv2.imread(os.path.join(dirname, filename))\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n            image = cv2.resize(image, (128, 128))\n            images.append(image)\n            counter += 1\n        for i in range(len(image_names)):\n            filename = image_names[i]\n            result = data_augmentation(dirname,filename)\n            for i in range(len(result)):\n                if i==0: #If the image is the one used to augmentate the dataset, then continue bc it has been appended already\n                    continue\n                else:\n                    images.append(result[i])\n                    counter += 1  \n    \n    if case_type ==1: #If we need to transform benign images (only reshape)\n        \n        images = []\n        image_names = train_metadata[train_metadata['benign_malignant']=='benign']['image_name'][:10000].reset_index(drop=True)+'.jpg'\n        \n        for i in range(len(image_names)):\n            filename = image_names[i]\n            image = cv2.imread(os.path.join(dirname, filename))\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n            image = cv2.resize(image, (128, 128))\n            images.append(image)\n            \n    return images","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"malignant_images = data_transformation(0)[:10000] #Let's transform all malignant images\nprint('Done')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"gc.collect()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"malignant_images[0].shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"len(malignant_images)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"%matplotlib inline\nplt.figure()\nimage = malignant_images[900]\nplt.imshow(image)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"benign_images = data_transformation(1) #Let's transform all benign images\nprint('Done')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"len(benign_images)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"gc.collect()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Dataset splitting"},{"metadata":{"trusted":true},"cell_type":"code","source":"#Now we need to create our training/testing dataset\ny = []\nfor i in range(10000):\n    #Appending benign labels\n    y.append(0)\nfor i in range(10000):\n    #Appending benign labels\n    y.append(1)\nlen(y)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X = benign_images + malignant_images\nX = np.array(X)\ntype(X)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X[0]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"! mkdir train","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"! mkdir images","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"cv2.imwrite('train/'+str(0)+'.JPEG', cv2.cvtColor(X[0], cv2.COLOR_BGR2RGB))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"for i in range(len(X)):\n    cv2.imwrite('train/'+str(i)+'.JPEG', cv2.cvtColor(X[i], cv2.COLOR_BGR2RGB))\nprint('Done')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"y = np.array(y)\ntype(y)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"gc.collect()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X_train, X_test, y_train, y_test = train_test_split(X, y,random_state=0,shuffle=True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model building"},{"metadata":{"trusted":true},"cell_type":"code","source":"#batch_size = 50\n#from keras.preprocessing.image import ImageDataGenerator\n#datagen = ImageDataGenerator(rescale=1./255)\n#train_generator = datagen.flow(X_train,y_train,batch_size=batch_size)\n#test_generator = datagen.flow(X_test,y_test,batch_size=batch_size)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"img_shape = (128,128,3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X_train[0].shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = models.Sequential()\nmodel.add(layers.Conv2D(64, (3, 3), activation='relu', input_shape=(128, 128, 3)))\nmodel.add(layers.MaxPooling2D((2, 2)))\nmodel.add(layers.Conv2D(128, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\nmodel.add(layers.Conv2D(256, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\nmodel.add(layers.Conv2D(256, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\nmodel.add(layers.Conv2D(256, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\nmodel.add(layers.Flatten())\nmodel.add(layers.Dropout(0.7)) #Because the model was overfitting, so I'm forcing it to learn more meaningful patters in the data\nmodel.add(layers.Dense(512, activation='relu'))\nmodel.add(layers.Dense(1,activation='sigmoid'))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.summary()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model compilation"},{"metadata":{"trusted":true},"cell_type":"code","source":"#Training the model\nmodel.compile(loss='binary_crossentropy', optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4), metrics=['acc']) # I had to avoid the default LR because it was getting stuck in a local minima.","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model training"},{"metadata":{"trusted":true},"cell_type":"code","source":"history = model.fit(X_train, y_train, epochs=36,validation_data=(X_test, y_test)) #Using this way because the dataset is not big enough to train by batches\n#history = model.fit_generator(train_generator, steps_per_epoch=ntrain//50,epochs=30,validation_data=test_generator,validation_steps=ntest//50)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model evaluation"},{"metadata":{"trusted":true},"cell_type":"code","source":"acc = history.history['acc']\nval_acc = history.history['val_acc']\nloss = history.history['loss']\nval_loss = history.history['val_loss']\n\nepochs = range(1,len(acc)+1)\n\n\n%matplotlib inline\nplt.figure(figsize=(30,10))\n\nax1 = plt.subplot(121)\nax1.plot(epochs,acc,'b',label='Training accuracy')\nax1.plot(epochs,val_acc,'r',label='Validation accuracy')\nplt.title('Training and validation accuracy')\nax1.legend()\n\nax2 = plt.subplot(122)\nax2.plot(epochs,loss,'b',label='Training loss')\nax2.plot(epochs,val_loss,'r',label='Validation loss')\nplt.title('Training and validation loss')\nax2.legend()\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#test_loss, test_acc = model.evaluate_generator(test_generator, steps=ntest//50, verbose=2)\ntest_loss, test_acc = model.evaluate(X_test, y_test, verbose=2)\nprint('Model accuracy: ',test_acc)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## This is a very good number! let's see what the confusion matrix shows."},{"metadata":{},"cell_type":"markdown","source":"# Model validation"},{"metadata":{"trusted":true},"cell_type":"code","source":"y_pred = model.predict(X_test)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Saving the model"},{"metadata":{"trusted":true},"cell_type":"code","source":"model.save(\"CNN_Melanoma.h5\")  # Saving the model to reload it in the future","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\nmatrix = confusion_matrix(y_test, np.around(y_pred, decimals=0))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import seaborn as sns\nconf_matrix = pd.DataFrame(matrix, index = ['Benign','Malignant'],columns = ['Benign','Malignant'])\n#Normalizing\nconf_matrix = conf_matrix.astype('float') / conf_matrix.sum(axis=1)[:, np.newaxis]\nplt.figure(figsize = (15,15))\nsns.heatmap(conf_matrix, annot=True, annot_kws={\"size\": 15})","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Fantastic! It's reaching very good score for both classes, the recall must be very high when developing models for the medical field so let's see what it shows."},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.metrics import classification_report\nprint(classification_report(y_test, np.around(y_pred, decimals=0)))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Conclusions\nAlright, we've reached the end of this notebook, but there are a few things to highlight though. First of all, even when the dataset is really small the model reached a very good metric. To get even higher metrics we have to get more data - in this particular notebook we couldn't use more due to RAM issues, but in more powerful notebook instances it wouldn't be a problem. Another good approach would be to implement even more regularization and train for much more epochs, but the model tuning depends on every ML requirement, generally it's a long process all of us have different methods to go through.\n\nThanks for reading!"},{"metadata":{"trusted":true},"cell_type":"code","source":"","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}