{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# CLASIFICADOR DE RETINOPATÍA DIABÉTICA A COLOR","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nimport gc\nfrom sklearn.model_selection import train_test_split\nfrom keras.callbacks import EarlyStopping\nfrom keras.callbacks import ModelCheckpoint\nfrom keras.optimizers import Adam\nimport tensorflow as tf\nfrom keras.models import Model\nfrom keras.models import load_model\nfrom xgboost import XGBClassifier","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-08-02T14:08:35.975529Z","iopub.execute_input":"2023-08-02T14:08:35.976027Z","iopub.status.idle":"2023-08-02T14:08:48.672463Z","shell.execute_reply.started":"2023-08-02T14:08:35.975988Z","shell.execute_reply":"2023-08-02T14:08:48.671157Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 1. Almacenamiento de los datos\n\nEn primer lugar, ES NECESARIO importar los datasets de entrenamiento y testeo que contienen la información de los Ids de las imágenes y el diagnósticos de severidad con números del 0 al 4. Información rescatada de la competencia: https://www.kaggle.com/competitions/aptos2019-blindness-detection/overview\n\n### 1.1 Datos de entrenamiento","metadata":{}},{"cell_type":"code","source":"train_ds = pd.read_csv('/kaggle/input/train-data/train_Data.csv')\n\nprint(\"INFORMACIÓN IMPORTADA\")","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:08:49.194230Z","iopub.execute_input":"2023-08-02T14:08:49.195276Z","iopub.status.idle":"2023-08-02T14:08:49.224357Z","shell.execute_reply.started":"2023-08-02T14:08:49.195228Z","shell.execute_reply":"2023-08-02T14:08:49.223474Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#VISUALIZACIÓN DE INFORMACIÓN\nprint('   TAMAÑO DE LOS DATOS:')\nprint(train_ds.shape)\ntrain_ds['diagnosis'].plot(kind='hist')\nprint(train_ds.diagnosis.value_counts())","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:08:50.382226Z","iopub.execute_input":"2023-08-02T14:08:50.382657Z","iopub.status.idle":"2023-08-02T14:08:50.788674Z","shell.execute_reply.started":"2023-08-02T14:08:50.382623Z","shell.execute_reply":"2023-08-02T14:08:50.787855Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Ahora, se debe de guardar las columnas de IDs y de diagnósticos en variables (\"X_IDs\" y \"y\").","metadata":{}},{"cell_type":"code","source":"#DATOS DEL DATASET\nX_IDs = train_ds['id_code']\ny = train_ds['diagnosis']\n\nprint(\"INFORMACIÓN ALMACENADA\")","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:08:52.117186Z","iopub.execute_input":"2023-08-02T14:08:52.118026Z","iopub.status.idle":"2023-08-02T14:08:52.125389Z","shell.execute_reply.started":"2023-08-02T14:08:52.117982Z","shell.execute_reply":"2023-08-02T14:08:52.123711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 1.2 Datos de prueba","metadata":{}},{"cell_type":"code","source":"test_ds = pd.read_csv('/kaggle/input/test-data/test_Data.csv')\n\nprint(\"INFORMACIÓN IMPORTADA\")","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:08:55.325648Z","iopub.execute_input":"2023-08-02T14:08:55.326111Z","iopub.status.idle":"2023-08-02T14:08:55.345907Z","shell.execute_reply.started":"2023-08-02T14:08:55.326076Z","shell.execute_reply":"2023-08-02T14:08:55.343828Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#VISUALIZACIÓN DE INFORMACIÓN\nprint('   TAMAÑO DE LOS DATOS:')\nprint(test_ds.shape)\ntest_ds['diagnosis'].plot(kind='hist')\nprint(test_ds.diagnosis.value_counts())","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:08:56.686266Z","iopub.execute_input":"2023-08-02T14:08:56.687104Z","iopub.status.idle":"2023-08-02T14:08:57.050118Z","shell.execute_reply.started":"2023-08-02T14:08:56.687067Z","shell.execute_reply":"2023-08-02T14:08:57.048782Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#DATOS DEL DATASET\nX_test_IDs = test_ds['id_code']\ny_test = test_ds['diagnosis']\n\nprint(\"INFORMACIÓN ALMACENADA\")","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:08:59.735516Z","iopub.execute_input":"2023-08-02T14:08:59.737447Z","iopub.status.idle":"2023-08-02T14:08:59.745836Z","shell.execute_reply.started":"2023-08-02T14:08:59.737383Z","shell.execute_reply":"2023-08-02T14:08:59.744031Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 2. Lectura y procesamiento de las imágenes\nLas siguiente son las funciones que se encargan de que todas las imágenes se vean de manera similar dimensionalmente. Hay que ejecutarlas para poder utilizarlas adelante.","metadata":{}},{"cell_type":"code","source":"def crop_image_from_gray(img,low_bound=7):\n    if img.ndim ==2:\n        mask = img>low_bound\n        return img[np.ix_(mask.any(1),mask.any(0))]\n    elif img.ndim==3:\n        gray_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n        mask = gray_img>low_bound\n        \n        check_shape = img[:,:,0][np.ix_(mask.any(1),mask.any(0))].shape[0]\n        if (check_shape == 0): # image is too dark so that we crop out everything,\n            return img # return original image\n        else:\n            img1=img[:,:,0][np.ix_(mask.any(1),mask.any(0))]\n            img2=img[:,:,1][np.ix_(mask.any(1),mask.any(0))]\n            img3=img[:,:,2][np.ix_(mask.any(1),mask.any(0))]\n    #         print(img1.shape,img2.shape,img3.shape)\n            img = np.stack([img1,img2,img3],axis=-1)\n    #         print(img.shape)\n        return img\ndef circle_crop(img, sigmaX=30):   \n    \"\"\"\n    Create circular crop around image centre    \n    \"\"\"    \n    img = crop_image_from_gray(img)    \n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    \n    height, width, depth = img.shape    \n    \n    x = int(width/2)\n    y = int(height/2)\n    r = np.amin((x,y))\n    \n    circle_img = np.zeros((height, width), np.uint8)\n    cv2.circle(circle_img, (x,y), int(r), 1, thickness=-1)\n    img = cv2.bitwise_and(img, img, mask=circle_img)\n    img = crop_image_from_gray(img)\n    #img=cv2.addWeighted(img,4, cv2.GaussianBlur( img , (0,0) , sigmaX) ,-4 ,128) #Difuminación gausiana (USAR EN CASO DE SER DESEADO)\n    return img ","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:09:02.207594Z","iopub.execute_input":"2023-08-02T14:09:02.208181Z","iopub.status.idle":"2023-08-02T14:09:02.225795Z","shell.execute_reply.started":"2023-08-02T14:09:02.208135Z","shell.execute_reply":"2023-08-02T14:09:02.224588Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"A continuación, se utilizan los IDs, tanto de entrenamiento como de testeo, para guardar en una lista todas las imagenes que son las que se utilizarán en el algoritmo. Indicando el tamaño en pixeles, el color y canales de las imagenes, además de añadir las funciones de procesamiento que se requieran, ya sea crop_image_from_gray, circle_crop o ambas, también se puede usar la difuminación gausiana que se encuentra comentada en circle_crop.","metadata":{}},{"cell_type":"markdown","source":"**PARA LAS IMÁGENES DE ENTRENAMIENTO**","metadata":{}},{"cell_type":"code","source":"#CONVERSIÓN DE LOS IDs EN IMAGENES\nX = []\ntamano_img = 224\nfor i in range(X_IDs.shape[0]):\n  img_path = \"/kaggle/input/aptos2019-blindness-detection/train_images/\"+X_IDs.iloc[i]+\".png\"\n  #print(img_path)\n  img = cv2.imread(img_path)\n  #print(img)\n  if (img is not None):\n      #img = crop_image_from_gray(img)\n      #img = circle_crop(img)\n      img = cv2.resize(img, (tamano_img, tamano_img)) #rediensionar las imgs\n      img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n      img = img.reshape(tamano_img, tamano_img, 3) #indicar el tamaño de la imagen y los canales\n      X.append(img)\n  #plt.imshow(img)\n  #print(i)\n\nprint(\"LAS IMAGENES YA HAN SIDO GUARDADAS EN LISTAS\")\nprint(len(X))\nprint(len(y))","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:09:11.697932Z","iopub.execute_input":"2023-08-02T14:09:11.698383Z","iopub.status.idle":"2023-08-02T14:15:43.318692Z","shell.execute_reply.started":"2023-08-02T14:09:11.698348Z","shell.execute_reply":"2023-08-02T14:15:43.317132Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**PARA LAS IMÁGENES DE PRUEBA**","metadata":{}},{"cell_type":"code","source":"#CONVERSIÓN DE LOS IDs EN IMAGENES\nX_test = []\ntamano_img = 224\nfor i in range(X_test_IDs.shape[0]):\n  img_path = \"/kaggle/input/aptos2019-blindness-detection/train_images/\"+X_test_IDs.iloc[i]+\".png\"\n  img = cv2.imread(img_path)\n  if (img is not None):\n      #img = crop_image_from_gray(img)\n      #img = circle_crop(img)\n      img = cv2.resize(img, (tamano_img, tamano_img)) #rediensionar las imgs\n      img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n      img = img.reshape(tamano_img, tamano_img, 3) #indicar el tamaño de la imagen y los canales\n      X_test.append(img)\n  #plt.imshow(img)\n  #print(i)\n\nprint(\"LAS IMAGENES YA HAN SIDO GUARDADAS EN LISTAS\")\nprint(len(X_test))\nprint(len(y_test))","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:15:43.322405Z","iopub.execute_input":"2023-08-02T14:15:43.322974Z","iopub.status.idle":"2023-08-02T14:17:02.765878Z","shell.execute_reply.started":"2023-08-02T14:15:43.322928Z","shell.execute_reply":"2023-08-02T14:17:02.763309Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Procesando las imágenes para la detección de exudados o mejor identificaxión de grises, siguiendo el procedimiento descrito en: https://www.researchgate.net/publication/343981785 **USAR EN LUGAR DE LO ANTERIOR**.","metadata":{}},{"cell_type":"code","source":"#CONVERSIÓN DE LOS IDs EN IMAGENES\nX = []\ntamano_img = 224\nfor i in range(X_IDs.shape[0]):\n  img_path = \"/kaggle/input/aptos2019-blindness-detection/train_images/\"+X_IDs.iloc[i]+\".png\"\n  img = cv2.imread(img_path)\n  if (img is not None):\n      img = crop_image_from_gray(img)\n      img = circle_crop(img)\n      img = cv2.resize(img, (tamano_img, tamano_img)) #rediensionar las imgs\n      img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n      img = img.reshape(tamano_img, tamano_img, 1) #indicar el tamaño de la imagen y los canales\n      \n      Fmax = img.max()\n      Fmin = img.min()\n      df = Fmax - Fmin\n      T = 0.95 * Fmax \n      m = (img-Fmin) / df #Fuzzification 0-1\n      mp = np.where(m < 0.5, 2*(m**2), 1-(2*((1-m)**2))) #Memebership modification\n    \n      X.append(mp)\n\nprint(\"LAS IMAGENES YA HAN SIDO GUARDADAS EN LISTAS\")\nprint(len(X))\nprint(len(y))\n\n#CONVERSIÓN DE LOS IDs DE TEST EN IMAGENES\nX_test = []\ntamano_img = 224\nfor i in range(X_test_IDs.shape[0]):\n  img_path = \"/kaggle/input/aptos2019-blindness-detection/train_images/\"+X_test_IDs.iloc[i]+\".png\"\n  img = cv2.imread(img_path)\n  if (img is not None):\n      img = crop_image_from_gray(img)\n      img = circle_crop(img)\n      img = cv2.resize(img, (tamano_img, tamano_img)) #rediensionar las imgs\n      img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n      img = img.reshape(tamano_img, tamano_img, 1) #indicar el tamaño de la imagen y los canales\n      \n      Fmax = img.max()\n      Fmin = img.min()\n      df = Fmax - Fmin\n      T = 0.95 * Fmax \n      m = (img-Fmin) / df #Fuzzification 0-1\n      mp = np.where(m < 0.5, 2*(m**2), 1-(2*((1-m)**2))) #Memebership modification\n    \n      X_test.append(mp)\n\nprint(\"LAS IMAGENES DE PRUEBA YA HAN SIDO GUARDADAS EN LISTAS\")\nprint(len(X_test))\nprint(len(y_test))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Agregando imágenes de dataset extra 2015/2019. **SOLO EN CASO DE SER REQUERIDO.** Esto puede ser útil para disponer d euna mayor cantidad d einformación para entrenamiento, no obstante también puede generar ruido o confusión por la calidad d elas fotografías.","metadata":{}},{"cell_type":"code","source":"y = list(y)\ntrain_ds2 = pd.read_csv('/kaggle/input/resized-2015-2019-diabetic-retinopathy-detection/labels/traintestLabels15_trainLabels19.csv')\n\nprint(\"INFORMACIÓN IMPORTADA\")\n\nX_ds2 = train_ds2['image']\ny_ds2 = train_ds2['level']\nprint(X_ds2.shape)\nprint(y_ds2.shape)\n\ncount4 = 0\n\ntamano_img = 224\ncomp = [1024, 1024, 3]\ncomp2 = [1023, 1024, 3]\ncomp3 = [1021, 1024, 3]\ncomp4 = [1024, 1023, 3]\ncomp5 = [1023, 1023, 3]\ncomp6 = [1022, 1024, 3]\ncomp7 = [1024, 1973, 3]\ncountG = 0\nfor i in range(X_ds2.shape[0]):\n  img_path = \"/kaggle/input/resized-2015-2019-diabetic-retinopathy-detection/resized_traintest15_train19/\"+X_ds2.iloc[i]+\".jpg\"\n  img = cv2.imread(img_path)\n  ex = img.shape\n  ex = list(ex)\n  if((ex == comp) or (ex == comp2) or (ex == comp3) or (ex == comp4) or (ex == comp5) or (ex == comp6) or (ex == comp7)):\n    print(count4)\n    if(y_ds2[i]== 4 and count4 < 250):\n        img = crop_image_from_gray(img)\n        img = circle_crop(img)\n        img = cv2.resize(img, (tamano_img, tamano_img))\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = img.reshape(tamano_img, tamano_img, 3)\n        X.append(img)\n        y.append(4)\n        #print(y_ds2[i])\n        count4+=1\n    elif(count4 == 250):\n        break","metadata":{"scrolled":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Agregando imágenes con aumento de datos. **SOLO EN CASO DE SER REQUERIDO.** Esto es similar a lo anterior, para aumentar la cantidad de datos. Aplicando rotación de imágenes, modificación de brillo.","metadata":{}},{"cell_type":"code","source":"#PRIMERO SE SECCIONAN LAS IMÁGENES QUE SE QUIEREN AUMENTAR\ncount = 0\nX4_original = [] #Lista con diagnósticos 4\nfor i in range(len(X)):\n    #Guardan las imgs con diagnóstico 4 por que son las que queremos aumentar (se puede aplicar para los otros tipos)\n    if (count < 250 and y[i] == 4):\n        X4_original.append(X[i])\n    elif (count == 250):\n        break\nprint(len(X4_original))\nprint('IMÁGENES SECCIONADAS Y GUARDADAS')\n\n#AHORA SE ENTRENA EL AUMENTO DE DATOS CON DICHAS IMÁGENES\n#X4_original = np.array(X4_original).astype(float) / 255\ndatagen_E4 = ImageDataGenerator(\n  horizontal_flip=True,\n  vertical_flip=True\n)\ndatagen_E4.fit(X4_original)\nprint(\"OBJETO DE AUMENTO DE DATOS ENTRENADO CON LAS IMÁGENES ELECTAS\")\n\n#FINALMENTE SE APLICA EL AUMENTO DE DATOS A LAS IMÁGENES Y SE ANEXAN AL CONJUNTO \"X\"\ncount = 0\nprint(len(X))\nfor imagen in datagen_E4.flow(X4_original, y = None, shuffle=False, batch_size = 2):\n if(count<200):\n     X.append(imagen[0])\n     y.append(4)\n     count+=1\n else: break \nprint(len(X))\nprint(len(y))\nprint(\"IMÁGENES AUMENTADAS ALMACENADAS EN EL CONJUNTO TOTAL X, LOS DIAGNÓSTICOS CORRESPONDIENTES A ELLAS TAMBIÉN HAN SIDO AGREGADOS\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**UNA VEZ ALMACENADAS LAS IMÁGENES Y DIAGNÓSTICOS**. Ahora, para poder trabajar con esta información, se convierta la lista a un arreglo numpy y se dividen los valores que conforman las matrices de imagenes entre 255, para que los números se encuentren entre un valor de 0 y 1.","metadata":{}},{"cell_type":"code","source":"#CONVERTIR DE LISTA A ARREGLO DE NUMPY\nX = np.array(X).astype(float) / 255\ny = np.array(y)\nX_test = np.array(X_test).astype(float) / 255\ny_test = np.array(y_test)\nprint(\"CONVERSIÓN A ARREGLO NUMPY FINALIZADA\")\nprint(X.shape)\nprint(y.shape)\nprint(X_test.shape)\nprint(y_test.shape)","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:17:02.769703Z","iopub.execute_input":"2023-08-02T14:17:02.770363Z","iopub.status.idle":"2023-08-02T14:17:05.301389Z","shell.execute_reply.started":"2023-08-02T14:17:02.770303Z","shell.execute_reply":"2023-08-02T14:17:05.299840Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#VER IMAGENES\nplt.figure(figsize=(10, 15))\ncount=0\nfor i in range(X_IDs.shape[0]):\n if(y[i]==4):\n  plt.subplot(5, 4, count+1)\n  plt.xticks([]) #quitar los ejes x y y\n  plt.yticks([])\n  plt.xlabel(y[i])\n  plt.imshow(X[i], cmap = 'gray')\n  count+=1\n  if(count>=12):\n   break","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:17:05.304592Z","iopub.execute_input":"2023-08-02T14:17:05.305007Z","iopub.status.idle":"2023-08-02T14:17:06.616195Z","shell.execute_reply.started":"2023-08-02T14:17:05.304976Z","shell.execute_reply":"2023-08-02T14:17:06.614726Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:17:06.619373Z","iopub.execute_input":"2023-08-02T14:17:06.620135Z","iopub.status.idle":"2023-08-02T14:17:07.085065Z","shell.execute_reply.started":"2023-08-02T14:17:06.620036Z","shell.execute_reply":"2023-08-02T14:17:07.083216Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 3. Procedimiento sin cross-validation y con aumento de datos\nEn este caso, se realiza un aumento de datos modificando las características de las imagenes. En primer lugar, se declara el objeto de DataGen con las cualidades a alterar y se entrena con el arreglo de numpy que contiene las imagenes.","metadata":{}},{"cell_type":"code","source":"#AUMENTO DE DATOS\ndatagen = ImageDataGenerator(\n  horizontal_flip=True,\n  vertical_flip=True\n)\n\ndatagen.fit(X)\n\nprint(\"OBJETO DE AUMENTO DE DATOS ENTRENADO\")\nprint(X.shape)\nprint(y.shape)","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:17:07.086577Z","iopub.execute_input":"2023-08-02T14:17:07.087066Z","iopub.status.idle":"2023-08-02T14:17:09.046890Z","shell.execute_reply.started":"2023-08-02T14:17:07.087025Z","shell.execute_reply":"2023-08-02T14:17:09.045805Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Ahora, se dividen las imagenes para obtener un arreglo con información de entrenamiento y otro de validación.","metadata":{}},{"cell_type":"code","source":"X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.15, stratify=y, random_state=42)\nval_label = y_valid #Para el XGBoost\ntrain_label = y_train\nprint(\"DATOS DE ENTRENAMIENTO Y VALIDACIÓN DIVIDIDOS\")","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:17:09.048904Z","iopub.execute_input":"2023-08-02T14:17:09.049800Z","iopub.status.idle":"2023-08-02T14:17:10.228186Z","shell.execute_reply.started":"2023-08-02T14:17:09.049758Z","shell.execute_reply":"2023-08-02T14:17:10.227203Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Una vez divididas las imágenes, se prepara el generador de datos transformados para entrenamiento.","metadata":{}},{"cell_type":"code","source":"#PREPARAR GENERADOR DE DATOS TRANSFORMADOS PARA TRAINING\n#Usar la funcion flow del generador para crear un iterador que podamos enviar como entrenamiento a la funcion FIT del modelo\ndata_gen_train = datagen.flow(X_train, y_train, batch_size=32)\n\nprint(\"GENERADOR DE DATOS DE ENTRENAMIENTO PREPARADO\")","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:17:10.230063Z","iopub.execute_input":"2023-08-02T14:17:10.230822Z","iopub.status.idle":"2023-08-02T14:17:11.341293Z","shell.execute_reply.started":"2023-08-02T14:17:10.230784Z","shell.execute_reply":"2023-08-02T14:17:11.339837Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Después, se generan callbacks para guardar el modelo en la época donde se muestra mayor precisión y para detener el entrenamiento cuando ya no dismunuya la pérdida del conjunto de validación.","metadata":{}},{"cell_type":"code","source":"#Early stop callback\nes = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=7)\nmc = ModelCheckpoint('best_model_V1_null.h5', monitor='val_accuracy', mode='max', verbose=1, save_best_only=True)","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:23:04.506205Z","iopub.execute_input":"2023-08-02T14:23:04.508134Z","iopub.status.idle":"2023-08-02T14:23:04.516473Z","shell.execute_reply.started":"2023-08-02T14:23:04.508065Z","shell.execute_reply":"2023-08-02T14:23:04.514957Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"A continuación, se crea el modelo con todas sus capas neuronales para posteriormente ser compilado.","metadata":{}},{"cell_type":"code","source":"modeloCNN = tf.keras.models.Sequential([\n  tf.keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=(224, 224, 3)),\n  tf.keras.layers.MaxPooling2D(2, 2),\n  tf.keras.layers.Conv2D(64, (3,3), activation='relu'),\n  tf.keras.layers.MaxPooling2D(2, 2),\n  tf.keras.layers.Conv2D(128, (3,3), activation='relu'),\n  tf.keras.layers.MaxPooling2D(2, 2),\n  tf.keras.layers.Conv2D(256, (3,3), activation='relu'),\n  tf.keras.layers.MaxPooling2D(2, 2),\n\n  tf.keras.layers.Dropout(0.3),\n  tf.keras.layers.Flatten(),\n  tf.keras.layers.Dense(256, activation='relu'),\n  tf.keras.layers.Dense(512, activation='relu'),\n  tf.keras.layers.Dense(5, activation='softmax')\n])\n\nopt = Adam(learning_rate = 0.0001)\nmodeloCNN.compile(optimizer=opt,\n                    loss='sparse_categorical_crossentropy',\n                    metrics=['accuracy'])\n'''\nmodeloCNN.compile(optimizer='adam',\n                    loss='sparse_categorical_crossentropy',\n                    metrics=['accuracy'])\n'''\n\nprint(\"MODELO COMPILADO\")","metadata":{"execution":{"iopub.status.busy":"2023-08-02T14:23:10.527256Z","iopub.execute_input":"2023-08-02T14:23:10.527737Z","iopub.status.idle":"2023-08-02T14:23:11.049670Z","shell.execute_reply.started":"2023-08-02T14:23:10.527683Z","shell.execute_reply":"2023-08-02T14:23:11.048648Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Finalmente se entrena el modelo con el generador de datos, indicando el número de épocas, los datos de validación y los pasos por época.","metadata":{}},{"cell_type":"code","source":"modeloCNN.fit(data_gen_train, #X & y\n    epochs=100, batch_size=32, #Épocas y tamaño de lote\n    validation_data=(X_valid, y_valid),#datos de validación\n    steps_per_epoch=int(np.ceil(len(X_train) / float(32))), #Cantidad total de datos entre tamaño del lote\n    validation_steps=int(np.ceil(len(X_valid) / float(32))),\n    verbose = 1,\n    callbacks=[es, mc]\n)\n\nprint(\"ENTRENAMIENTO FINALIZADO\")","metadata":{"scrolled":true,"execution":{"iopub.status.busy":"2023-08-02T14:23:17.763170Z","iopub.execute_input":"2023-08-02T14:23:17.763649Z","iopub.status.idle":"2023-08-02T16:53:57.828442Z","shell.execute_reply.started":"2023-08-02T14:23:17.763614Z","shell.execute_reply":"2023-08-02T16:53:57.825911Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Para graficar el comportaiento de la precisión del modelo:","metadata":{}},{"cell_type":"code","source":"plt.plot(modeloCNN.history.history['accuracy'])\nplt.plot(modeloCNN.history.history['val_accuracy'])\nplt.title('Model accuracy')\nplt.ylabel('Accuracy')\nplt.xlabel('Epoch')\nplt.legend(['Train', 'Valid'], loc='upper left')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:06:18.536891Z","iopub.execute_input":"2023-08-02T17:06:18.538008Z","iopub.status.idle":"2023-08-02T17:06:51.130199Z","shell.execute_reply.started":"2023-08-02T17:06:18.537888Z","shell.execute_reply":"2023-08-02T17:06:51.128110Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.plot(modeloCNN.history.history['loss'])\nplt.plot(modeloCNN.history.history['val_loss'])\nplt.title('Model loss')\nplt.ylabel('Loss')\nplt.xlabel('Epoch')\nplt.legend(['Train', 'Valid'], loc='upper left')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:06:51.133772Z","iopub.execute_input":"2023-08-02T17:06:51.135117Z","iopub.status.idle":"2023-08-02T17:06:51.559328Z","shell.execute_reply.started":"2023-08-02T17:06:51.135059Z","shell.execute_reply":"2023-08-02T17:06:51.557824Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 4. Procedimiento utilizando cross-validation\n\nEn este caso, se entrena el modelo pero utilizando la validación cruzada, **REALIZAR EN LUGAR DEL ANTERIOR PROCEDIMIENTO SOLO EN CASO DE ASÍ DESEARSE**.\n\nEn primer lugar, se declara el objeto de DataGen con las cualidades a alterar y se entrena con el arreglo de numpy que contiene las imagenes.","metadata":{}},{"cell_type":"code","source":"#AUMENTO DE DATOS\ndatagen = ImageDataGenerator(\n  horizontal_flip=True,\n  vertical_flip=True\n)\n\ndatagen.fit(X)\n\nprint(\"OBJETO DE AUMENTO DE DATOS ENTRENADO\")\nprint(X.shape)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Se definen las siguientes listas para almacenar la información de precisión y pérdida realizada por cada fold del cross-validation.","metadata":{}},{"cell_type":"code","source":"# Define per-fold score containers\nacc_per_fold = []\nloss_per_fold = []\n\nprint(\"VARIABLES DE ALMACENAMIENTO PREPARADAS\")","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Después, se establece el objeto de cross-validation y el número de folds con el que se trabajará.","metadata":{}},{"cell_type":"code","source":"# Define the K-fold Cross Validator\nnum_folds = 10\nfrom sklearn.model_selection import KFold\nkfold = KFold(n_splits=num_folds, shuffle=True)\n\nprint(\"OBJETO DE KFOLD LISTO\")","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Early stop callback\nes = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=7)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Finalmente, ya es posible crear el modelo con todas sus capas neuronales; compilarlo con el optimizador, función de pérdida y métrica de precisión deseada; y evaluar las red neuronal. Este procedimiento se repetirá según el número de folds declaradas para el cross-validation.","metadata":{}},{"cell_type":"code","source":"fold_no = 1\nbatch_size = 32\nno_epochs = 100\nverbosity = 1\n\nfor train, test in kfold.split(X, y):\n    #CREAR MODELO\n    mc = ModelCheckpoint('best_model_V1_crop_cross'+str(fold_no)+'.h5', monitor='val_accuracy', mode='max', verbose=1, save_best_only=True)\n    modeloCNN = tf.keras.models.Sequential([\n      tf.keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=(224, 224, 3)),,\n      tf.keras.layers.MaxPooling2D(2, 2),\n      tf.keras.layers.Conv2D(64, (3,3), activation='relu'),\n      tf.keras.layers.MaxPooling2D(2, 2),\n      tf.keras.layers.Conv2D(128, (3,3), activation='relu'),\n      tf.keras.layers.MaxPooling2D(2, 2),\n      tf.keras.layers.Conv2D(256, (3,3), activation='relu'),\n      tf.keras.layers.MaxPooling2D(2, 2),\n    \n      tf.keras.layers.Dropout(0.3),\n      tf.keras.layers.Flatten(),\n      tf.keras.layers.Dense(256, activation='relu'),\n      tf.keras.layers.Dense(512, activation='relu'),\n      tf.keras.layers.Dense(5, activation='softmax')\n    ])\n    \n    \n    #COMPILAR MODELO\n    \n    from keras.optimizers import Adam\n    opt = Adam(learning_rate = 0.0001)\n    modeloCNN.compile(optimizer=opt,\n                        loss='sparse_categorical_crossentropy',\n                        metrics=['accuracy'])\n    '''\n    modeloCNN.compile(optimizer='adam',\n                        loss='sparse_categorical_crossentropy',\n                        metrics=['accuracy'])\n    '''\n    \n    #ENTRENAR EL MODELO\n        # Generate a print\n    print('------------------------------------------------------------------------')\n    print(f'Training for fold {fold_no} ...')\n\n    data_gen_train = datagen.flow(X[train], y[train], batch_size=32)\n    print(\"GENERADOR DE DATOS DE ENTRENAMIENTO PREPARADO\")\n    \n        # Fit data to model\n    history = modeloCNN.fit(data_gen_train,\n                batch_size=batch_size,\n                validation_data=(X[test], y[test]),\n                epochs=no_epochs,\n                verbose=verbosity,\n                callbacks=[es, mc])\n    saved_model = load_model('best_model_V1_crop_cross'+str(fold_no)+'.h5')\n    #EVALUACIÓN DE LA RED NEURONAL\n        # Generate generalization metrics\n    scores = saved_model.evaluate(X_test, y_test, verbose=0)\n    print(f'Score for fold {fold_no}: {modeloCNN.metrics_names[0]} of {scores[0]}; {modeloCNN.metrics_names[1]} of {scores[1]*100}%')\n    acc_per_fold.append(scores[1] * 100)\n    loss_per_fold.append(scores[0])\n\n\n        # Increase fold number\n    fold_no = fold_no + 1\n    gc.collect()\n\nprint(\"CROSS VALIDATION FINALIZADO\")","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 5. Create XGB model and training it for intermediate values\n\nA continuación, se puede entrenar el modelo para que se extraigan las características después de las capas convolucionales y mediante un árbol de clasificacións e intente mejorar la precisión de las predicciones de los diagnósticos.\n\nExtract the intermediate output from CNN","metadata":{}},{"cell_type":"code","source":"saved_model = load_model('best_model_V1_null.h5')\nintermediate_layer_model = Model(inputs=saved_model.input,\n                                 outputs=saved_model.get_layer('dense').output)\n\nintermediate_layer_model.summary()","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:06:51.561564Z","iopub.execute_input":"2023-08-02T17:06:51.563406Z","iopub.status.idle":"2023-08-02T17:06:52.351243Z","shell.execute_reply.started":"2023-08-02T17:06:51.563341Z","shell.execute_reply":"2023-08-02T17:06:52.349166Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Salida intermedia, del CNN, para entrenar el XGBoost\nintermediate_output = intermediate_layer_model.predict(X_train) \nintermediate_output = pd.DataFrame(data=intermediate_output)","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:10:00.406737Z","iopub.execute_input":"2023-08-02T17:10:00.407268Z","iopub.status.idle":"2023-08-02T17:11:00.506790Z","shell.execute_reply.started":"2023-08-02T17:10:00.407225Z","shell.execute_reply":"2023-08-02T17:11:00.505346Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Salida intermedia, del CNN, para validar el XGBoost\nval_data = intermediate_layer_model.predict(X_valid) \nval_data = pd.DataFrame(data=val_data)","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:11:00.509564Z","iopub.execute_input":"2023-08-02T17:11:00.510051Z","iopub.status.idle":"2023-08-02T17:11:10.866167Z","shell.execute_reply.started":"2023-08-02T17:11:00.510015Z","shell.execute_reply":"2023-08-02T17:11:10.864839Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Precciones realizadas a la salida del modelo CNN\ny_preds = saved_model.predict(X_test)\ny_preds=np.argmax(y_preds,axis=1)\nsaved_model.evaluate(X_test, y_test)\nprint(y_preds)\nprint(y_test)","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:08:41.708175Z","iopub.execute_input":"2023-08-02T17:08:41.708661Z","iopub.status.idle":"2023-08-02T17:09:24.279862Z","shell.execute_reply.started":"2023-08-02T17:08:41.708611Z","shell.execute_reply":"2023-08-02T17:09:24.278782Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Salida intermedia, del CNN, para probar el XGBoost\nintermediate_test_output = intermediate_layer_model.predict(X_test)\nintermediate_test_output = pd.DataFrame(data=intermediate_test_output)","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:11:10.867990Z","iopub.execute_input":"2023-08-02T17:11:10.868404Z","iopub.status.idle":"2023-08-02T17:11:31.948990Z","shell.execute_reply.started":"2023-08-02T17:11:10.868364Z","shell.execute_reply":"2023-08-02T17:11:31.947383Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:11:31.951756Z","iopub.execute_input":"2023-08-02T17:11:31.952211Z","iopub.status.idle":"2023-08-02T17:11:33.010247Z","shell.execute_reply.started":"2023-08-02T17:11:31.952175Z","shell.execute_reply":"2023-08-02T17:11:33.009012Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Creación y entrenamiento del modelo, visualización de su precisión con el conjunto de validación.","metadata":{}},{"cell_type":"code","source":"xgbmodel = XGBClassifier(objective='multi:softprob', booster='gbtree', learning_rate=0.001,\n                      num_class= 5, n_estimators=5000)\nxgbmodel.fit(intermediate_output, train_label)\nxgbmodel.score(val_data, val_label)","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:11:33.012269Z","iopub.execute_input":"2023-08-02T17:11:33.012976Z","iopub.status.idle":"2023-08-02T17:23:41.275469Z","shell.execute_reply.started":"2023-08-02T17:11:33.012933Z","shell.execute_reply":"2023-08-02T17:23:41.274266Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 6. Predicciones","metadata":{}},{"cell_type":"markdown","source":"Se pueden realizar las predicciones con el modelo entrenado únicamente con las capas convolucionales y densas o aplicando el modelo de XGBoost.","metadata":{}},{"cell_type":"code","source":"#PREDECIR PREVIO A XGBOOST:\n#y_preds = saved_model.predict(X_test)\n#y_preds=np.argmax(y_preds,axis=1)\n\n#PREDECIR DESPUÉS DEL XGBOOST:\ny_preds = xgbmodel.predict(intermediate_test_output)\nxgbmodel.score(intermediate_test_output, y_test)","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:23:41.280667Z","iopub.execute_input":"2023-08-02T17:23:41.283428Z","iopub.status.idle":"2023-08-02T17:23:41.791830Z","shell.execute_reply.started":"2023-08-02T17:23:41.283343Z","shell.execute_reply":"2023-08-02T17:23:41.790808Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 7. Evaluación Kappa\n\nLa siguiente es la métrica que se utiliza en la competencia APTOS 2019 para el leaderboard de los modelos.\n\nStep-1: Confusion Matrix. e shall be calculating a confusion_matrix between the Predicted and Actual values.","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\nfrom sklearn.metrics import ConfusionMatrixDisplay\nO = confusion_matrix(y_test, y_preds)\nprint(O)\n   \n\n# Compute confusion matrix\ncm = confusion_matrix(y_test, y_preds)\n# Only use the labels that appear in the data\nclasses=[0, 1, 2, 3, 4]\n'''\nif normalize:\n    cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]\n    print(\"Normalized confusion matrix\")\nelse:\n    print('Confusion matrix, without normalization')\n'''\n\nfig, ax = plt.subplots()\nim = ax.imshow(cm, interpolation='nearest')\nax.figure.colorbar(im, ax=ax)\n# We want to show all ticks...\nax.set(xticks=np.arange(cm.shape[1]),\n        yticks=np.arange(cm.shape[0]),\n        # ... and label them with the respective list entries\n        xticklabels=classes, yticklabels=classes,\n        #title=title,\n        ylabel='True label',\n        xlabel='Predicted label')\n\n# Rotate the tick labels and set their alignment.\nplt.setp(ax.get_xticklabels(), rotation=45, ha=\"right\",\n            rotation_mode=\"anchor\")\n\n# Loop over data dimensions and create text annotations.\n#fmt = '.2f' if normalize else 'd'\nthresh = cm.max() / 2.\nfor i in range(cm.shape[0]):\n    for j in range(cm.shape[1]):\n        ax.text(j, i, format(cm[i, j]),\n                ha=\"center\", va=\"center\",\n                color=\"white\" if cm[i, j] > thresh else \"black\")\nfig.tight_layout()","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:24:02.148993Z","iopub.execute_input":"2023-08-02T17:24:02.149491Z","iopub.status.idle":"2023-08-02T17:24:02.814392Z","shell.execute_reply.started":"2023-08-02T17:24:02.149455Z","shell.execute_reply":"2023-08-02T17:24:02.812030Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Step-2: Weighted Matrix. An N-by-N matrix of weights, w, is calculated based on the difference between actual and predicted rating scores.","metadata":{}},{"cell_type":"code","source":"w = np.zeros((5,5))\n\nfor i in range(len(w)):\n    for j in range(len(w)):\n        w[i][j] = float(((i-j)**2)/16) #as per formula, for this competition, N=5\n        \nprint(w)","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:24:15.189388Z","iopub.execute_input":"2023-08-02T17:24:15.189870Z","iopub.status.idle":"2023-08-02T17:24:15.199576Z","shell.execute_reply.started":"2023-08-02T17:24:15.189832Z","shell.execute_reply":"2023-08-02T17:24:15.198057Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Step-3: Histogram. We create two vectors, one for preds and one for actuals, which tells us how many values of each rating exist in both vectors.","metadata":{}},{"cell_type":"code","source":"N=5\nact_hist=np.zeros([N])\nfor item in y_test: \n    act_hist[item]+=1\n    \npred_hist=np.zeros([N])\nfor item in y_preds: \n    pred_hist[item]+=1\n    \nprint(f'Actuals value counts:{act_hist}, Prediction value counts:{pred_hist}')","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:24:16.343968Z","iopub.execute_input":"2023-08-02T17:24:16.344859Z","iopub.status.idle":"2023-08-02T17:24:16.356666Z","shell.execute_reply.started":"2023-08-02T17:24:16.344813Z","shell.execute_reply":"2023-08-02T17:24:16.355022Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Step-4: Expected Value (Outer product of histograms). Expected matrix is calculated as the outer product between the actual rating's histogram vector of ratings and the predicted rating's histogram vector of ratings","metadata":{}},{"cell_type":"code","source":"E = np.outer(act_hist, pred_hist)\nprint(E)","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:24:17.449251Z","iopub.execute_input":"2023-08-02T17:24:17.449738Z","iopub.status.idle":"2023-08-02T17:24:17.458979Z","shell.execute_reply.started":"2023-08-02T17:24:17.449688Z","shell.execute_reply":"2023-08-02T17:24:17.456678Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Step-5: Normalise E and O matrix. E and O are normalized such that E and O have the same sum","metadata":{}},{"cell_type":"code","source":"E = E/E.sum()\nprint(E.sum())\nprint(E)\n\nO = O/O.sum()\nprint(O.sum())\nprint(O)","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:24:18.487534Z","iopub.execute_input":"2023-08-02T17:24:18.488021Z","iopub.status.idle":"2023-08-02T17:24:18.496380Z","shell.execute_reply.started":"2023-08-02T17:24:18.487986Z","shell.execute_reply":"2023-08-02T17:24:18.495345Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Step-6: Calculate Weighted Kappa.","metadata":{}},{"cell_type":"code","source":"num=0\nden=0\nfor i in range(len(w)):\n    for j in range(len(w)):\n        num+=w[i][j]*O[i][j]\n        den+=w[i][j]*E[i][j]\n \nweighted_kappa = (1 - (num/den));\n\nprint(weighted_kappa)","metadata":{"execution":{"iopub.status.busy":"2023-08-02T17:24:19.607192Z","iopub.execute_input":"2023-08-02T17:24:19.607668Z","iopub.status.idle":"2023-08-02T17:24:19.616651Z","shell.execute_reply.started":"2023-08-02T17:24:19.607629Z","shell.execute_reply":"2023-08-02T17:24:19.615265Z"},"trusted":true},"execution_count":null,"outputs":[]}]}