{"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":"# Importamos los paquetes necesarios","metadata":{"id":"baq7Q5zz1kGU"}},{"cell_type":"code","source":"# Datos\nfrom collections import Counter\n# Functions\nfrom functools import partial\n# Tensorflow/Keras\nimport tensorflow as tf \nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator \nimport tensorflow_datasets as tfds\nfrom tensorflow import keras\n\nfrom tensorflow.keras import Sequential, Model\nfrom tensorflow.keras.layers import (Dense, Input,\n                                     BatchNormalization, GlobalAveragePooling2D,\n                                     Conv2D, Conv2DTranspose, \n                                     Rescaling, RandomFlip, RandomContrast,\n                                     RandomRotation, RandomZoom,RandomTranslation,\n                                     Flatten, Dropout, LeakyReLU, ReLU)\nfrom tensorflow.keras.optimizers import RMSprop, Adam\n# Sklearn\nimport sklearn\nfrom sklearn.model_selection import train_test_split\n# Dataframes/Arrays\nimport numpy as np\nimport pandas as pd\n# Plotting\nimport matplotlib.pyplot as plt\n\n%matplotlib inline\n# time control\nfrom datetime import datetime\n# Check for GPU\n!nvidia-smi\n\nprint(\"GPUs Available: \", len(tf.config.experimental.list_physical_devices('GPU')))\n# print(\"Num GPUs Available: \", len(tf.config.list_physical_devices('GPU')))\n","metadata":{"id":"_nUpOo8t0j_0","outputId":"e077f7e8-fbb4-4640-aff4-f17e478c371d","execution":{"iopub.status.busy":"2022-05-13T00:40:42.143725Z","iopub.execute_input":"2022-05-13T00:40:42.144010Z","iopub.status.idle":"2022-05-13T00:40:42.878800Z","shell.execute_reply.started":"2022-05-13T00:40:42.143978Z","shell.execute_reply":"2022-05-13T00:40:42.877897Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Auxiliary plot functions","metadata":{}},{"cell_type":"code","source":"from functools import partial","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:40:42.881101Z","iopub.execute_input":"2022-05-13T00:40:42.881399Z","iopub.status.idle":"2022-05-13T00:40:42.885762Z","shell.execute_reply.started":"2022-05-13T00:40:42.881359Z","shell.execute_reply":"2022-05-13T00:40:42.884844Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def plot_hist(hist):\n    plt.plot(hist.history[\"accuracy\"])\n    plt.plot(hist.history[\"val_accuracy\"])\n    plt.title(\"model accuracy\")\n    plt.ylabel(\"accuracy\")\n    plt.xlabel(\"epoch\")\n    plt.legend([\"train\", \"validation\"], loc=\"upper left\")\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:40:42.889441Z","iopub.execute_input":"2022-05-13T00:40:42.889680Z","iopub.status.idle":"2022-05-13T00:40:42.895872Z","shell.execute_reply.started":"2022-05-13T00:40:42.889648Z","shell.execute_reply":"2022-05-13T00:40:42.895035Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Data Loading\n","metadata":{"id":"SWzoiQst1cKm"}},{"cell_type":"markdown","source":"# Cargamos los datos con la utilidad de tf Datasets\n","metadata":{"id":"zZifTwJ6TbbR"}},{"cell_type":"markdown","source":"### Version 1: Usando el dataset que proporciona TensorFlow\nEsta es la versión que utilicé cuando la carga desde tensorflow funcionaba sin problema. Estos son los comentarios que escribí al respecto \n> Hemos usado [la documentación](https://keras.io/guides/transfer_learning/) oficial de keras. \n>\n>En primer lugar, extraemos la información. No hemos conseguido partir el dataset y extraer la información a la vez y pensamos que no es posible. Hemos extraído la información porque no hemos encontrado una descripción adecuada del dataset ni [aquí](https://www.tensorflow.org/datasets/catalog/cats_vs_dogs), ni [aquí](https://www.microsoft.com/en-us/download/details.aspx?id=54765), ni [aquí](https://www.kaggle.com/c/dogs-vs-cats). En particular, no se indicaba cuantas imágenes había ni si se encontraba ya dividido en train y test o sólo en train, como ha resultado ser el caso.\n>\n>El efecto sobre el tiempo de ejecución del notebook en su conjunto es despreciable, no sólo porque comparativamente el tiempo de entrenamiento es muchísimo mayor, sino porque la segunda ejecución se hace sobre los datos ya almacenados en [disco](/root/tensorflow_datasets/cats_vs_dogs/4.0.0.incompleteF8YNS3/cats_vs_dogs-train.tfrecord) y no se descarga nuevamente, lo que hace que su ejecución esté en el orden de los milisengundos, lo que le hace despreciable por comparación con la primera ejecución. \n\nPaso a escribir el código dentro del markdown para eliminarno de la ejecución, ya que fallaría y eso violaría la primera de las condiciones del ejercicio.\nAsí pues, el proceso consistiría en lo siguiente\n1. Una primera carga donde exploramos la información del dataset, tal y como se ha descrito en los párrafos de la sección anterior\n```python\nimport tensorflow_datasets as tfds\nds, info = tfds.load(name='cats_vs_dogs', as_supervised=True, with_info=True)\nprint(info)\n```\n2. La eliminación de estas vaeriables para no incurrir en problemas de memoria RAM.\n```python\ndel ds, info\n```\n3. La carga de los datos que se van a utilizar partiéndolos. Utilizamos sólo una parte de todo el dataset para aligerar las pruebas. A fin de cuentas, trabajar con datasets pequeños es uno de los casos de uso de *trasnfer learning*\n```python\nIMG_SIZE= 150\nBATCH_SIZE = 32\n#limits = [10, 30]\n#split = [f\"train[:{limits[0]}%]\", f\"train[{limits[0]}%:{limits[1]}%]\", f\"train[{limits[1]}%:]\"]\n#Extraemos sólo una parte del conjunto de datos \n#que aún así nos dé mucho más de 100 imágenes por categoría \nlimit = 45\nval_size= 15\nsplit=[f\"train[:{limit}%]\", f\"train[{limit}%:{limit+val_size}%]\", f\"train[{limit+val_size}%:{limit+2*val_size}%]\"]\ntrain_ds, validation_ds, test_ds = tfds.load(name='cats_vs_dogs', split=split, as_supervised=True, shuffle_files=True)\n```\n4. Exploramos el conjunto creado en dos fases:\n    a. Exploramos el tamaño de cada uno de los datasets creados\n    ```python\nprint(\"Train set size: \", len(train_ds)) \nprint(\"Valid set size: \", len(validation_ds)) \nprint(\"Test set size: \", len(test_ds))\n```\n    b. Redimensionamos todas las imágenes para que tengan el mismo tamaño. Éste es un dataset heterogéneo que no permite crear batches ya que su primera dimensión varía de unos a otros. En el mismo proceso, metemos en memoría, hacemos los batch y en el caso del conjunto de training también hacemos un mezclado del dataset\n    ```python\ndef preprocess(img, label):\n      return tf.image.resize(img, [IMG_SIZE, IMG_SIZE], method=\"bicubic\"), label\ntrain_ds = train_ds.cache().map(preprocess).shuffle(len(train_ds) // 4).batch(BATCH_SIZE, drop_remainder=True).prefetch(buffer_size=1)\ntest_ds = test_ds.cache().map(preprocess).batch(BATCH_SIZE, drop_remainder=True).prefetch(buffer_size=1)\nvalidation_ds = validation_ds.cache().map(preprocess).batch(BATCH_SIZE, drop_remainder=True).prefetch(buffer_size=1)\n```\n\n5. Comprobamos que las distribuciones de las dos clases son similares entre datasets.\n```python\ndef check_ratio(batched_dataset):\n  labels = np.concatenate([y for x, y in train_ds], axis=0)\n  return  {k: v/len(labels) for k,v in Counter(labels).items()}\n```\n\n```python\nratio_train = check_ratio(train_ds)\nratio_test = check_ratio(test_ds)\nratio_validation = check_ratio(validation_ds)\nprint(ratio_train, ratio_test, ratio_validation)\ndel ratio_train, ratio_test, ratio_validation,\n```\n    Éste código devolvía una proporción muy similar entre ambas clases a lo largo de todos los datasets, con oscilaciones mínimas de entre 0.488 y 0.512.\n6. Formalizamos los pasos que generan los dataset para su uso posterior y descartamos los pasos de comprobación de aquí en adelante. La idea es que sea fácil poder ejecutar sólo parte del código y abstraer pasos del proceso bajo una única denominación. Éste paso, y los de carga de datos pero adaptados, se ha conservado aquí y se puede encontar en el código bajo la denominación `load_dataset`","metadata":{}},{"cell_type":"markdown","source":"## Versión 2: usando un dataset de Kaggle\nAnte el error de descarga con tfds que hemos experimentado y documentado en el [foro](https://2022.cursosvirtuales.uned.es/dotlrn/posgrados/asignaturas/31110111-22/uforums/message-view?message_id=45698777), hemos procesado los datos de Kaggle directamente. para ser más precisos, los datos de Kaggle contenían también imágenes defectuosas y eso produjo un error durante el entrenamiento que lo interrumpió. Por este motivo,\n1. Descargué los datos a mi ordenador.\n2. Busqué una herramienta, un [script de Python](https://github.com/ftarlao/check-media-integrity), que detecta imágenes defectuosas, como por ejemplos aquellas que no tienen ningún contenido.\n3  Eliminé esas imágenes, que resultaron ser dos, la 11702 de perros y la 666 de gatos (lo que me ha resultado muy divertido).\n4. Lo comprimí y lo subí a Kaggle como un dataset privado. Aparece con el nombre cleaneddogsvscats. No sabía que lo estaba subiendo en privado cuando lo hice, porque era mi primera vez usando Kaggle y subiendo mi propio dataset también. \n\nYa que esta no ha sido la versión que finalmewnte hemos ejecutado por resultar muy engorrosa de seguir, pero con un código perfectamente ejecutable, la vamos a encapsular dentro de este markdown. Explicaremos cada uno de los pasos y luego las ventajas y desventajas que ofrecía frente al método que hemos terminado usando el 2.\n\n1. Cargamos un paquete más. Lo hemos indicado en esta sección porque no es necesario para las otras soluciones.\n```python\nimport os,  shutil \nfrom pathlib import Path\n```\n2. Definimos los directorios de datos.\n```python\noriginal_dataset_dir = '../input/cleaneddogsvscats/archive/PetImages'\nbase_dir = 'cats_and_dogs_small'\ntrain_dir = os.path.join(base_dir, 'train')\nvalidation_dir = os.path.join(base_dir, 'validation')\ntest_dir = os.path.join(base_dir, 'test')\n```\n3. Creamos los directorios\n```python\nPath(base_dir).mkdir(parents=True, exist_ok=True)\n# create sub-folders for training, validation and test sets\ntrain_Cat_dir = os.path.join(train_dir, 'Cat')\ntrain_Dog_dir = os.path.join(train_dir, 'Dog')\nPath(train_Cat_dir).mkdir(parents=True, exist_ok=True)\nPath(train_Dog_dir).mkdir(parents=True, exist_ok=True)\n```\n\n```python\nvalidation_Cat_dir = os.path.join(validation_dir, 'Cat')\nvalidation_Dog_dir = os.path.join(validation_dir, 'Dog')\nPath(validation_Cat_dir).mkdir(parents=True, exist_ok=True)\nPath(validation_Dog_dir).mkdir(parents=True, exist_ok=True)\n#\ntest_Cat_dir = os.path.join(test_dir, 'Cat')\ntest_Dog_dir = os.path.join(test_dir, 'Dog')\nPath(test_Cat_dir).mkdir(parents=True, exist_ok=True)\nPath(test_Dog_dir).mkdir(parents=True, exist_ok=True)\n```\n4. Copiamos las imágenes a cada uno de los directorios. Esta es la parte del código más enrevesada y es uno de los motivos por el que no se ha quedado aquí. NO quería invertir tiempo haciendo más ordenado y programáticamente más \n```python \n# copy images from the whole dataset to different folders :\ntrain_ds_size = 10000\nvalidation_ds_size = 3000\ntest_ds_size = 3000\n```\n\n```python\n# copy the first  half of train_ds_size cats images to the folder train_dir\ncat_indices = list(range(train_ds_size // 2))\ncat_indices.remove(666)\nfnames = ['{}.jpg'.format(i) for i in cat_indices]\nfor fname in fnames:\n    src = os.path.join(original_dataset_dir + '/Cat', fname)\n    dst = os.path.join(train_Cat_dir, fname)\n    shutil.copyfile(src, dst)\n```\n```python\n# copy the following  half of validation_ds_size cats images to the folder validation_dir \nfnames = ['{}.jpg'.format(i) for i in range(train_ds_size // 2, (train_ds_size + validation_ds_size) // 2)]\nfor fname in fnames:\n    src = os.path.join(original_dataset_dir + '/Cat', fname)\n    dst = os.path.join(validation_Cat_dir, fname)\n    shutil.copyfile(src, dst)\n# copy the following  half of test_ds_size cats images to the folder test_dir    \nfnames = ['{}.jpg'.format(i) for i in range( (train_ds_size + validation_ds_size) // 2,( train_ds_size + validation_ds_size + test_ds_size) // 2)]\nfor fname in fnames:\n    src = os.path.join(original_dataset_dir + '/Cat', fname)\n    dst = os.path.join(test_Cat_dir, fname)\n    shutil.copyfile(src, dst)\n# Dogs    \n# copy the first half of train_ds_size dogs images to the folder train_dir \nfnames = ['{}.jpg'.format(i) for i in range(train_ds_size // 2)]\nfor fname in fnames:\n    src = os.path.join(original_dataset_dir + '/Dog', fname)\n    dst = os.path.join(train_Dog_dir, fname)\n    shutil.copyfile(src, dst)\n# copy the following  half of validation_ds_size dogs images to the folder validation_dir \ndog_indices = list(range(train_ds_size // 2, (train_ds_size + validation_ds_size) // 2))\nif 11702 in dog_indices:\n    dog_indices.remove(11702)\nfnames = ['{}.jpg'.format(i) for i in dog_indices]\nfor fname in fnames:\n    src = os.path.join(original_dataset_dir + '/Dog', fname)\n    dst = os.path.join(validation_Dog_dir, fname)\n    shutil.copyfile(src, dst)\n# copy the following  half of test_ds_size dogs images to the folder test_dir\ndog_indices = list(range((train_ds_size + validation_ds_size) // 2,( train_ds_size + validation_ds_size + test_ds_size) // 2))\nif 11702 in dog_indices:\n    dog_indices.remove(11702)\nfnames = ['{}.jpg'.format(i) for i in dog_indices]\nfor fname in fnames:\n    src = os.path.join(original_dataset_dir + '/Dog', fname)\n    dst = os.path.join(test_Dog_dir, fname)\n    shutil.copyfile(src, dst)\n```\n\n5. Cramos los datasets usando la antigua API de `ImageDataGenerator` y `.flow_from_directory`\n```python\nimg_size= 150\nbatch_size = 32\ndata_generator = ImageDataGenerator(rescale = 1./255)\ntrain_ds = data_generator.flow_from_directory(train_dir,\n                                        target_size = (img_size,img_size),\n                                        batch_size = batch_size,class_mode='binary')\nvalidation_ds = data_generator.flow_from_directory(validation_dir,\n                                        target_size = (img_size,img_size),\n                                        batch_size = batch_size,class_mode='binary')\ntest_ds = data_generator.flow_from_directory(test_dir,\n                                        target_size = (img_size,img_size),\n                                        batch_size = batch_size,class_mode='binary')\n```\n","metadata":{"_kg_hide-output":true,"_kg_hide-input":false}},{"cell_type":"markdown","source":"## Versión 3: usando el dataset modificado\nAntes de conitnuar, aclarar que este paso se hace para corregir un error 404 durante la carga usando tensorflow_datasets. Usando el enlace suminstrado por D. Jorge Perez Martin en el [foro](https://2022.cursosvirtuales.uned.es/dotlrn/posgrados/asignaturas/31110111-22/uforums/message-view?message_id=45730067), he creado una versión de esta clase de datos, a la que he llamado my_cats_vs_dogs, que corrige el error. Este código se ha subido a Kaggle como si fuera un dataset. Para poder usarlo, debo moverlo al directorio donde se encuentra el notebook. Esto es lo que hace el siguiente comando de la shell de Linux","metadata":{}},{"cell_type":"code","source":"!cp -r ../input/new-dogsvscat-class/new_dogvscats new_dogvscats","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:40:42.898138Z","iopub.execute_input":"2022-05-13T00:40:42.898534Z","iopub.status.idle":"2022-05-13T00:40:43.595222Z","shell.execute_reply.started":"2022-05-13T00:40:42.898498Z","shell.execute_reply":"2022-05-13T00:40:43.594260Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Ahora ya puedo hacer la importación del dataset","metadata":{}},{"cell_type":"code","source":"import tensorflow_datasets as tfds\nimport new_dogvscats.my_dataset  # Register MyDataset","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-05-13T00:40:43.597094Z","iopub.execute_input":"2022-05-13T00:40:43.597377Z","iopub.status.idle":"2022-05-13T00:40:43.601262Z","shell.execute_reply.started":"2022-05-13T00:40:43.597341Z","shell.execute_reply":"2022-05-13T00:40:43.600514Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Importación usando una función ","metadata":{}},{"cell_type":"code","source":"from keras.applications.vgg16 import preprocess_input as preprocess_input_vgg16\nfrom keras.applications.xception import preprocess_input as preprocess_input_xception\nfrom keras.applications.efficientnet import preprocess_input as preprocess_input_efficient\nfrom keras.applications.mobilenet_v2 import preprocess_input as preprocess_input_mobilenetv2\n\nfrom keras.applications import imagenet_utils\n","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:40:43.602957Z","iopub.execute_input":"2022-05-13T00:40:43.603507Z","iopub.status.idle":"2022-05-13T00:40:43.611065Z","shell.execute_reply.started":"2022-05-13T00:40:43.603473Z","shell.execute_reply":"2022-05-13T00:40:43.610405Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Por algún motivo, no encuentra la classe de preprocess input de ResNet50. El código \n```python\nkeras.applications.resnet50.preprocess_input \n```\nDevuelve un error\n```python\nModuleNotFoundError: No module named 'keras.applications.resnet50'\n```\nLo hemos solucionado yendo al [código fuente](https://github.com/keras-team/keras/blob/v2.8.0/keras/applications/resnet.py#L504-L508) del paquete y copiando su definición ","metadata":{}},{"cell_type":"code","source":"def preprocess_input_resnet50(x):\n    return imagenet_utils.preprocess_input(\n      x, data_format=data_format, mode='caffe')","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:40:43.613488Z","iopub.execute_input":"2022-05-13T00:40:43.613751Z","iopub.status.idle":"2022-05-13T00:40:43.622892Z","shell.execute_reply.started":"2022-05-13T00:40:43.613717Z","shell.execute_reply":"2022-05-13T00:40:43.622109Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_size= 150\nbatch_size = 32\nlimit = 45\nval_size= 15\ndef load_dataset(model_name, img_size:int, batch_size:int,\n                limit:int, val_size:int, rescale:bool):\n    def preprocess(img, label, rescale):\n        if rescale:\n            preprocess_input_dict = {\n                \"ResNet50\" : preprocess_input_resnet50,\n                \"VGG16\" : preprocess_input_vgg16,\n                \"Xception\" : preprocess_input_xception,\n                \"EfficientNetB0\" : preprocess_input_efficient,\n                \"MobileNetV2\": preprocess_input_mobilenetv2\n            }\n            preprocess_func = preprocess_input_dict[model_name]\n            return preprocess_func(tf.image.resize(img, [img_size, img_size])), label\n        return tf.image.resize(img, [img_size, img_size]), label\n        # return tf.image.resize(img, [img_size, img_size], method=\"bicubic\") / 255., label\n        \n    #Extraemos sólo una parte del conjunto de datos \n    #que aún así nos dé mucho más de 100 imágenes por categoría \n    split=[f\"train[:{limit}%]\", f\"train[{limit}%:{limit+val_size}%]\", f\"train[{limit+val_size}%:{limit+2*val_size}%]\"]\n    train_ds, validation_ds, test_ds = tfds.load(name='my_cats_vs_dogs', split=split, as_supervised=True, \n                                                 shuffle_files=True, read_config=tfds.ReadConfig(shuffle_seed=44))\n    # Rescale and batch. We remove the cache because we will adding data augmentation later. \n    train_ds = train_ds.map(partial(preprocess, rescale=rescale)).shuffle(len(train_ds) // 4).batch(batch_size, drop_remainder=True).prefetch(buffer_size=1)\n    test_ds = test_ds.map(partial(preprocess, rescale=rescale)).batch(batch_size, drop_remainder=True).prefetch(buffer_size=1)\n    validation_ds = validation_ds.map(partial(preprocess, rescale=rescale)).batch(batch_size, drop_remainder=True).prefetch(buffer_size=1)\n    return train_ds, validation_ds, test_ds","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:40:43.624300Z","iopub.execute_input":"2022-05-13T00:40:43.624532Z","iopub.status.idle":"2022-05-13T00:40:43.636530Z","shell.execute_reply.started":"2022-05-13T00:40:43.624495Z","shell.execute_reply":"2022-05-13T00:40:43.635797Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## ","metadata":{}},{"cell_type":"markdown","source":"# Preparamos Data Augmentation\nPodemos aplicarlo como parte del modelo, es decir, como capas, o aplicarlo en un paso previo usando ImageDataGenerator. Este último procedimiento es más antiguo y ya que hemos optado por usar tfds, vamos a usar las capas. Hemos incluido algunos comentarios sobre los detalles de immplemtación en la siguiente celda de código.\n\nProbamos distintas versiones de la misma con un resultado que era más beneficioso para la configuración que aparece ahí. Que es también la más sencilla. No ha habido problemas de overfitting salvo en un modelo. Hemos implementado también EarlyStopping y una refularización con una capa de Dropout para controlar que esto no ocurra.\n\nTODO: Sí incluimos `Resizing` en este *pipeline* y no `Rescaling`, porque necesitamos previamente homogeneizar las dimensiones para poder hacer batch sobre ellas y los algoritmos se comportan razonablemente bien con el mismo tamaño de las imágenes. Un rescalado al rango $[-1, 1]$ es equivalente al cálculo que hace `preprocess_input`, tal y como puede verse en [su código](https://github.com/keras-team/keras-applications/blob/06fbeb0f16e1304f239b2296578d1c50b15a983a/keras_applications/imagenet_utils.py#L152). Todos los algoritmos esperan imágenes en ese rango, como puede comprobarse [aquí](https://keras.io/api/applications/xception/), ","metadata":{"id":"VaDoAysi4Syp"}},{"cell_type":"code","source":"# We first define the layers as a list two be able to modify it later. \ndef add_scaling_data_augmentation(scale:float, offset:int):\n    data_augmentation_list = [\n            Rescaling(scale, offset),\n            RandomRotation(factor=0.125, seed=44), # factor is a fraction of 2*pi. For the range to be between -pi/4 and pi/4, we need to set it to 1/8\n            #RandomTranslation(height_factor=0.1, width_factor=0.1, seed=44),\n            RandomFlip(\"horizontal\",seed=44),\n            #RandomZoom(0,1, seed=44),\n            #RandomContrast(factor=0.1, seed=44)\n        ]\n\n    data_augmentation = Sequential(\n        data_augmentation_list,\n        name=\"data_augmentation\"\n    )\n    return data_augmentation","metadata":{"id":"6dfAPgbVOtBn","execution":{"iopub.status.busy":"2022-05-13T00:40:43.637456Z","iopub.execute_input":"2022-05-13T00:40:43.637956Z","iopub.status.idle":"2022-05-13T00:40:43.647946Z","shell.execute_reply.started":"2022-05-13T00:40:43.637920Z","shell.execute_reply":"2022-05-13T00:40:43.647315Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Transfer Learning\nHemos seguido el proceso descrito en la [documentación oficial](https://keras.io/guides/transfer_learning/).\nCargamos varios modelos preentrenados para los cuáles vamos a iterar. El orden se explica en las siguientes secciones.","metadata":{"id":"VCIGrtXK4vtl"}},{"cell_type":"code","source":"from tensorflow.keras.applications import EfficientNetB0, Xception, ResNet50, VGG16, MobileNetV2\nfrom keras.models import clone_model","metadata":{"id":"0DIMKgmQ4tI-","execution":{"iopub.status.busy":"2022-05-13T00:40:43.649136Z","iopub.execute_input":"2022-05-13T00:40:43.649404Z","iopub.status.idle":"2022-05-13T00:40:43.663142Z","shell.execute_reply.started":"2022-05-13T00:40:43.649370Z","shell.execute_reply":"2022-05-13T00:40:43.662478Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def unfreeze_model(model, copy:bool=False, n:int=20):\n    if copy:\n        temp = clone_model(model)\n    else:\n        temp = model\n    # We unfreeze the top 20 layers while leaving BatchNorm layers frozen\n    for layer in temp.layers[-n:]:\n        if not isinstance(layer, BatchNormalization):\n            print(layer)\n            layer.trainable = True\n\n    optimizer = tf.keras.optimizers.Adam(learning_rate=1e-4)\n    temp.compile(\n        optimizer=optimizer, loss=\"categorical_crossentropy\", metrics=[\"accuracy\"]\n    )\n    return temp","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:40:43.664512Z","iopub.execute_input":"2022-05-13T00:40:43.664770Z","iopub.status.idle":"2022-05-13T00:40:43.672430Z","shell.execute_reply.started":"2022-05-13T00:40:43.664735Z","shell.execute_reply":"2022-05-13T00:40:43.671621Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def build_model(dropout=0.2, drop_connect_rate=0.2, \n                model_class=Xception,\n                img_size:int=img_size, batch_size:int=batch_size,\n                limit:int=limit, val_size:int=val_size,rescale:bool=False,\n                scale:float=1, offset:int=0):\n    # If scale is false, it can be passed to the data_augmentation list of\n    # transformations\n    data_augmentation=add_scaling_data_augmentation(scale=scale, offset=offset)\n    inputs = Input(shape=(img_size, img_size, 3))\n    x = data_augmentation(inputs)\n    model_name = model_class.__name__\n    if model_name == \"EfficientNetB0\":\n        base_model = model_class(include_top=False, input_tensor=x, weights=\"imagenet\",\n                                 input_shape=(img_size, img_size, 3), \n                                 drop_connect_rate=drop_connect_rate)\n    else:\n        base_model = model_class(include_top=False, input_tensor=x, weights=\"imagenet\",\n                            input_shape=(img_size, img_size, 3))\n    # Freeze the pretrained weights\n    base_model.trainable = False\n    \n    x = base_model(x, training=False)\n    # Rebuild top\n    x = GlobalAveragePooling2D(name=\"avg_pool\")(base_model.output)\n    #x = BatchNormalization()(x)\n\n    x = Dropout(dropout, name=\"dropout\")(x)\n    outputs = Dense(1)(x)\n    # Alternativamente\n    # outputs = Dense(2)(x)\n    # Exige usar loss=tf.losses.SparseCategoricalCrossEntropy(from_logits=True)\n    # Compile\n   \n    model = tf.keras.Model(inputs, outputs, name=model_class.__name__)\n    optimizer = tf.keras.optimizers.Adam(learning_rate=1e-2)\n    model.compile(\n        optimizer=optimizer, loss = tf.losses.BinaryCrossentropy(from_logits=True),\n    metrics=['accuracy']\n    )\n    # Return also the datasets\n    train_ds, validation_ds, test_ds = load_dataset(model_name, img_size=img_size,\n                                                    batch_size=batch_size,\n                                                    limit=limit, \n                                                    val_size=val_size,\n                                                    rescale=rescale)\n    return model, base_model, train_ds, validation_ds, test_ds","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:40:43.724435Z","iopub.execute_input":"2022-05-13T00:40:43.724631Z","iopub.status.idle":"2022-05-13T00:40:43.735504Z","shell.execute_reply.started":"2022-05-13T00:40:43.724602Z","shell.execute_reply":"2022-05-13T00:40:43.734677Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"callback = tf.keras.callbacks.EarlyStopping(monitor='val_accuracy', patience=5, restore_best_weights=True, baseline=0.5)","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:40:43.847290Z","iopub.execute_input":"2022-05-13T00:40:43.847514Z","iopub.status.idle":"2022-05-13T00:40:43.852293Z","shell.execute_reply.started":"2022-05-13T00:40:43.847490Z","shell.execute_reply":"2022-05-13T00:40:43.851360Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Modelo `Xception`\nCronológicamente, comenzamos nuestras pruebas con EfficientNetB7. Encontramos muchos problemas para hacerlo funcionar correctamente, lo que nos llevó a reducir dramáticamente el tamaño del dataset. Más tarde descubrimos que la razón del problema era otra, que explicaremos en la sección correspondiente. Comenzamos la descripción con este modelo porque es el que mejor resultado ha dado en todos sus experimentos, contrariamente a nuestras expectativas. \nTiene 22.9M de parámetros y una profundidad de 81 capas. En este hardware tarda unos 21 segundos por epoch con la configuración establecida","metadata":{}},{"cell_type":"markdown","source":"## Experimento 1\nEntrenaremos primero las nuevas capas y luego permitiremos un fine tuning del modelo completo. Será así para todos los algoritmos","metadata":{}},{"cell_type":"markdown","source":"### Fase 1: convergencia del modelo a los datos nuevos\nPrimero fijamos el modelo, `training=False` para que converja a los nuevos datos con las capas añadidas.","metadata":{"id":"3huFvuKK7KeU"}},{"cell_type":"code","source":"start = datetime.now()\nmodel, base_model, train_ds, validation_ds, test_ds  = build_model(img_size=150,limit=25,\n                                                                   val_size=10, rescale=False,\n                                                                   scale=1./127.5, offset=-1)\n#for img, label in train_ds.take(1):\n#    print(np.min(img[0]), np.max(img[0]))\n#model.summary()\nhistory = model.fit(train_ds, epochs=20,  callbacks=[callback], validation_data=validation_ds)\nduration = datetime.now() - start\nprint(duration)\nplot_hist(history)","metadata":{"id":"w6YjFuZdVUMJ","outputId":"540ac1dc-7db0-4832-e828-f9e9527ece53","execution":{"iopub.status.busy":"2022-05-13T00:40:43.926567Z","iopub.execute_input":"2022-05-13T00:40:43.926849Z","iopub.status.idle":"2022-05-13T00:43:58.267945Z","shell.execute_reply.started":"2022-05-13T00:40:43.926822Z","shell.execute_reply":"2022-05-13T00:43:58.267220Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss, acc = model.evaluate(test_ds)\nprint(f\"Loss: {loss}\", f\"Accuracy: {acc}\")","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:43:58.269794Z","iopub.execute_input":"2022-05-13T00:43:58.270065Z","iopub.status.idle":"2022-05-13T00:44:01.541470Z","shell.execute_reply.started":"2022-05-13T00:43:58.270029Z","shell.execute_reply":"2022-05-13T00:44:01.540663Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**RESUMEN** \nPara el dataset partido al 45, 15 y 15 por ciento para el entrenamiento, validación y test respectivamente. Unos 20 segundos por epoch. Debido a EarlyStopping, el tiempo total varia. Se incluye también. \n- Max `val_accuracy`: 0.9702 (epoch 2)\n- Time (mm:ss): 03:17\n\nPara el dataset partido al 25, 10 y 10 por ciento para el entrenamiento, validación y test respectivamente. Unos 10 segundos por epoch. Debido a EarlyStopping, el tiempo total varia. Se incluye también. \n- Max `val_accuracy`: 0.9770 (epoch 7)\n- Time (mm:ss): 03:35","metadata":{}},{"cell_type":"markdown","source":"### Fase 2: Descongelamos el modelo\nDe las primeras 20 capas, descongelamos aquellas que no sean de BatchNormalization","metadata":{"id":"Tb6_FR60G2oV"}},{"cell_type":"code","source":"epochs = 15\nbase_model.trainable = True\noptimizer = tf.keras.optimizers.Adam(learning_rate=1e-5)\nmodel.compile(\n        optimizer=optimizer, loss=\"categorical_crossentropy\", metrics=[\"accuracy\"]\n    )\nmodel.summary()\n","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:44:01.542804Z","iopub.execute_input":"2022-05-13T00:44:01.543269Z","iopub.status.idle":"2022-05-13T00:44:01.617677Z","shell.execute_reply.started":"2022-05-13T00:44:01.543230Z","shell.execute_reply":"2022-05-13T00:44:01.617037Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"start = datetime.now()\nhistory = model.fit(train_ds, epochs=epochs, validation_data=validation_ds)\nduration = datetime.now() - start\nprint(duration)\nplot_hist(history)","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:44:01.620746Z","iopub.execute_input":"2022-05-13T00:44:01.620934Z","iopub.status.idle":"2022-05-13T00:54:05.000750Z","shell.execute_reply.started":"2022-05-13T00:44:01.620911Z","shell.execute_reply":"2022-05-13T00:54:05.000027Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss, acc = model.evaluate(test_ds)\nprint(f\"Loss: {loss}\", f\"Accuracy: {acc}\")","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:54:05.001952Z","iopub.execute_input":"2022-05-13T00:54:05.002289Z","iopub.status.idle":"2022-05-13T00:54:10.192244Z","shell.execute_reply.started":"2022-05-13T00:54:05.002251Z","shell.execute_reply":"2022-05-13T00:54:10.191421Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**RESUMEN** \nLa ejecución con el dataset al 45, 15, 15 no terminó. La memoria RAM sencillamente no es suficiente. Indicaba unos 70 segundos por epoch frente a los 35-25 (con y sin cache. Estaba muy saturada la RAM en este punto) que ha venido tardando con 25, 10, 10. Hemos quitado la callback para darle tiempo a estabilizarse.\n\nIntento 1.\n- Max `val_accuracy`: 0.8902 (epoch 11)\n- Time (mm:ss): 09:46\n\nIntento 2:\n- Max `val_accuracy`: 0.8906 (epoch 11)\n- Time (mm:ss): 10:03","metadata":{}},{"cell_type":"markdown","source":"## Experimento 2\nIntentaremos descongelar capas individuales del modelo base. Para separar los experimentos, debo volver a entrenar primero las capas añadidas y luego las capas que haya seleccionado del modelo. Evito tocar las capas de `BatchNormalization`","metadata":{}},{"cell_type":"markdown","source":"### Fase 1","metadata":{}},{"cell_type":"code","source":"start = datetime.now()\nmodel, base_model, train_ds, validation_ds, test_ds  = build_model(img_size=150,limit=25,\n                                                                   val_size=10, rescale=False,\n                                                                   scale=1./127.5, offset=-1)\n#for img, label in train_ds.take(1):\n#    print(np.min(img[0]), np.max(img[0]))\n#model.summary()\nhistory = model.fit(train_ds, epochs=20,  callbacks=[callback], validation_data=validation_ds)\nduration = datetime.now() - start\nprint(duration)\nplot_hist(history)","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:54:10.193632Z","iopub.execute_input":"2022-05-13T00:54:10.194046Z","iopub.status.idle":"2022-05-13T00:56:11.822637Z","shell.execute_reply.started":"2022-05-13T00:54:10.194009Z","shell.execute_reply":"2022-05-13T00:56:11.822034Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss, acc = model.evaluate(test_ds)\nprint(f\"Loss: {loss}\", f\"Accuracy: {acc}\")","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:56:11.823967Z","iopub.execute_input":"2022-05-13T00:56:11.824219Z","iopub.status.idle":"2022-05-13T00:56:15.093508Z","shell.execute_reply.started":"2022-05-13T00:56:11.824168Z","shell.execute_reply":"2022-05-13T00:56:15.092852Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**RESUMEN** \nPara el dataset partido al 25, 10 y 10 por ciento para el entrenamiento, validación y test respectivamente. Unos 10-12 (co y sin cache)segundos por epoch. Debido a EarlyStopping, el tiempo total varia. Se incluyen dos intentos.\n\nIntento 1:Con cachce\n- Max `val_accuracy`: 0.9740 (epoch 8)\n- Time (mm:ss): 03:55\n\nIntento 2. Sin cache\n- Max `val_accuracy`: 0.9718 (epoch 3)\n- Time (mm:ss): 02:01","metadata":{}},{"cell_type":"markdown","source":"### Fase 2","metadata":{}},{"cell_type":"code","source":"base_model.trainable = True\nfor layer in model.layers[-6:]:\n        if not isinstance(layer, BatchNormalization):\n            print(layer)\n            layer.trainable = True\nfor layer in model.layers[:-6]:\n     layer.trainable = False       \n#for img, label in train_ds.take(1):\n#    print(np.min(img[0]), np.max(img[0]))\n#model.summary()\noptimizer = tf.keras.optimizers.Adam(learning_rate=1e-5)\nmodel.compile(\n        optimizer=optimizer, loss=\"categorical_crossentropy\", metrics=[\"accuracy\"]\n    )\nmodel.summary()\n","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:56:15.094745Z","iopub.execute_input":"2022-05-13T00:56:15.095168Z","iopub.status.idle":"2022-05-13T00:56:15.177242Z","shell.execute_reply.started":"2022-05-13T00:56:15.095127Z","shell.execute_reply":"2022-05-13T00:56:15.176474Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"start =  datetime.now() \nhistory = model.fit(train_ds, epochs=30,  validation_data=validation_ds)\nduration = datetime.now() - start\nprint(duration)\nplot_hist(history)","metadata":{"execution":{"iopub.status.busy":"2022-05-13T00:56:15.178765Z","iopub.execute_input":"2022-05-13T00:56:15.179150Z","iopub.status.idle":"2022-05-13T01:04:15.001695Z","shell.execute_reply.started":"2022-05-13T00:56:15.179089Z","shell.execute_reply":"2022-05-13T01:04:15.000918Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss, acc = model.evaluate(test_ds)\nprint(f\"Loss: {loss}\", f\"Accuracy: {acc}\")","metadata":{"execution":{"iopub.status.busy":"2022-05-13T01:04:15.004877Z","iopub.execute_input":"2022-05-13T01:04:15.005125Z","iopub.status.idle":"2022-05-13T01:04:20.190715Z","shell.execute_reply.started":"2022-05-13T01:04:15.005086Z","shell.execute_reply":"2022-05-13T01:04:20.189864Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**RESUMEN** \nPara el dataset partido al 25, 10 y 10 por ciento para el entrenamiento, validación y test respectivamente. Unos 6 segundos por epoch. Debido a EarlyStopping, el tiempo total varia. Se incluye también. \n\nIntento 1:\n- Max `val_accuracy`: 0.9640 (múltiples epoch)\n- Time (mm:ss): 07:37\n\nIntento 2:\n- Max `val_accuracy`: 0.579 (múltiples epoch)\n- Time (mm:ss): 07:59","metadata":{}},{"cell_type":"markdown","source":"# Modelo `MobileNetV2`\nEl último de los modelos que probamos y el más exitoso con la configuración usada. Tiene 3.5 millones de parámetros. Espera las imágnes en el rango $[-1, 1]$, como `Xception`. ","metadata":{}},{"cell_type":"code","source":"def build_model(dropout=0.2, drop_connect_rate=0.2, \n                model_class=Xception,\n                img_size:int=img_size, batch_size:int=batch_size,\n                limit:int=limit, val_size:int=val_size,rescale:bool=False,\n                scale:float=1, offset:int=0):\n    # If scale is false, it can be passed to the data_augmentation list of\n    # transformations\n    data_augmentation=add_scaling_data_augmentation(scale=scale, offset=offset)\n    inputs = Input(shape=(img_size, img_size, 3))\n    x = data_augmentation(inputs)\n    model_name = model_class.__name__\n    if model_name == \"EfficientNetB0\":\n        base_model = model_class(include_top=False, input_tensor=x, weights=\"imagenet\",\n                                 input_shape=(img_size, img_size, 3), \n                                 drop_connect_rate=drop_connect_rate)\n    else:\n        base_model = model_class(include_top=False, input_tensor=x, weights=\"imagenet\",\n                            input_shape=(img_size, img_size, 3))\n    # Freeze the pretrained weights\n    base_model.trainable = False\n    \n    x = base_model(x, training=False)\n    # Rebuild top\n    x = GlobalAveragePooling2D(name=\"avg_pool\")(base_model.output)\n    x = BatchNormalization()(x)\n    #outputs = Dense(1)(x)\n    # Alternativamente\n    \n    outputs = Dense(2)(x)\n    # Exige usar loss=tf.losses.SparseCategoricalCrossEntropy(from_logits=True)\n    # Compile\n   \n    model = tf.keras.Model(inputs, outputs, name=model_class.__name__)\n    optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)\n    model.compile(\n        optimizer=optimizer, loss = tf.losses.SparseCategoricalCrossentropy(from_logits=True),\n    metrics=['accuracy']\n    )\n    # Return also the datasets\n    train_ds, validation_ds, test_ds = load_dataset(model_name, img_size=img_size,\n                                                    batch_size=batch_size,\n                                                    limit=limit, \n                                                    val_size=val_size,\n                                                    rescale=rescale)\n    return model, base_model, train_ds, validation_ds, test_ds","metadata":{"execution":{"iopub.status.busy":"2022-05-13T01:04:20.192592Z","iopub.execute_input":"2022-05-13T01:04:20.192944Z","iopub.status.idle":"2022-05-13T01:04:20.207207Z","shell.execute_reply.started":"2022-05-13T01:04:20.192900Z","shell.execute_reply":"2022-05-13T01:04:20.206199Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Experimento 1\nEntrenaremos primero las nuevas capas y luego permitiremos un fine tuning del modelo completo. Será así para todos los algoritmos","metadata":{}},{"cell_type":"markdown","source":"### Fase 1: convergencia del modelo a los datos nuevos\nPrimero fijamos el modelo, `training=False` para que converja a los nuevos datos con las capas añadidas.","metadata":{"id":"3huFvuKK7KeU"}},{"cell_type":"code","source":"start = datetime.now()\nmodel, base_model, train_ds, validation_ds, test_ds  = build_model(model_class = MobileNetV2, img_size=224,limit=45,\n                                                                   val_size=15, rescale=False,\n                                                                   scale=1./127.5, offset=-1)\n#for img, label in train_ds.take(1):\n#    print(np.min(img[0]), np.max(img[0]))\n#model.summary()\nhistory = model.fit(train_ds, epochs=20,  callbacks=[callback], validation_data=validation_ds)\nduration = datetime.now() - start\nprint(duration)\nplot_hist(history)","metadata":{"id":"w6YjFuZdVUMJ","outputId":"540ac1dc-7db0-4832-e828-f9e9527ece53","execution":{"iopub.status.busy":"2022-05-13T02:11:28.363283Z","iopub.execute_input":"2022-05-13T02:11:28.363500Z","iopub.status.idle":"2022-05-13T02:18:03.176554Z","shell.execute_reply.started":"2022-05-13T02:11:28.363466Z","shell.execute_reply":"2022-05-13T02:18:03.175838Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss, acc = model.evaluate(test_ds)\nprint(f\"Loss: {loss}\", f\"Accuracy: {acc}\")","metadata":{"execution":{"iopub.status.busy":"2022-05-13T02:18:03.177927Z","iopub.execute_input":"2022-05-13T02:18:03.178342Z","iopub.status.idle":"2022-05-13T02:18:13.480321Z","shell.execute_reply.started":"2022-05-13T02:18:03.178302Z","shell.execute_reply":"2022-05-13T02:18:13.479504Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**RESUMEN** \nPara el dataset partido al 45, 15 y 15 por ciento para el entrenamiento, validación y test respectivamente. Unos 25 segundos por epoch. Debido a EarlyStopping, el tiempo total varia. Se incluye también. \n\nIntento 1\n- Max `val_accuracy`: 0.9900 (epoch 6)\n- Time (mm:ss): 06:15\n\nIntento 2\n- Max `val_accuracy`: 0.9880 (epoch 7)\n- Time (mm:ss): 06:18","metadata":{}},{"cell_type":"markdown","source":"### Fase 2: Descongelamos el modelo\n","metadata":{"id":"Tb6_FR60G2oV"}},{"cell_type":"code","source":"epochs = 30\nbase_model.trainable = True\noptimizer = tf.keras.optimizers.Adam(learning_rate=1e-5)\nmodel.compile(\n        optimizer=optimizer, loss = tf.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=[\"accuracy\"]\n    )\nmodel.summary()\n","metadata":{"execution":{"iopub.status.busy":"2022-05-13T02:18:13.481578Z","iopub.execute_input":"2022-05-13T02:18:13.481835Z","iopub.status.idle":"2022-05-13T02:18:13.569974Z","shell.execute_reply.started":"2022-05-13T02:18:13.481799Z","shell.execute_reply":"2022-05-13T02:18:13.569329Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"start = datetime.now()\nhistory = model.fit(train_ds, epochs=epochs, callbacks=[callback], validation_data=validation_ds)\nduration = datetime.now() - start\nprint(duration)\nplot_hist(history)","metadata":{"execution":{"iopub.status.busy":"2022-05-13T02:18:13.571089Z","iopub.execute_input":"2022-05-13T02:18:13.571346Z","iopub.status.idle":"2022-05-13T02:27:56.147896Z","shell.execute_reply.started":"2022-05-13T02:18:13.571312Z","shell.execute_reply":"2022-05-13T02:27:56.147217Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss, acc = model.evaluate(test_ds)\nprint(f\"Loss: {loss}\", f\"Accuracy: {acc}\")","metadata":{"execution":{"iopub.status.busy":"2022-05-13T02:27:56.152721Z","iopub.execute_input":"2022-05-13T02:27:56.152925Z","iopub.status.idle":"2022-05-13T02:28:01.801465Z","shell.execute_reply.started":"2022-05-13T02:27:56.152900Z","shell.execute_reply":"2022-05-13T02:28:01.800635Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**RESUMEN** \nLa ejecución con el dataset al 45, 15, 15 ha tardado unos 65 segundos por epoch. Hemos quitado la callback para darle tiempo a estabilizarse en el intento 1, aunque el modelo ya alcanzó 0.9897 en su primer epoch. Ha demostrado ser una estrategia poco efectiva y no la hemos usado en el segundo intento. \n\nIntento 1:\n- Max `val_accuracy`: 0.9897 (epoch 1)\n- Time (mm:ss): 19:40 (no se limitó la ejecución con callbacks)\n\nIntento 1:\n- Max `val_accuracy`: 0.9885 (epoch 3)\n- Time (mm:ss): 09:21 (usando `EarlyStopping`)\n","metadata":{}},{"cell_type":"markdown","source":"## Experimento 2\nIntentaremos descongelar capas individuales del modelo base. Para separar los experimentos, debo volver a entrenar primero las capas añadidas y luego las capas que haya seleccionado del modelo. Evito tocar las capas de `BatchNormalization`","metadata":{}},{"cell_type":"markdown","source":"### Fase 1","metadata":{}},{"cell_type":"code","source":"start = datetime.now()\nmodel, base_model, train_ds, validation_ds, test_ds  = build_model(model_class=MobileNetV2, img_size=224,limit=45,\n                                                                   val_size=15, rescale=False,\n                                                                   scale=1./127.5, offset=-1)\n#for img, label in train_ds.take(1):\n#    print(np.min(img[0]), np.max(img[0]))\n#model.summary()\nhistory = model.fit(train_ds, epochs=30,  callbacks=[callback], validation_data=validation_ds)\nduration = datetime.now() - start\nprint(duration)\nplot_hist(history)","metadata":{"execution":{"iopub.status.busy":"2022-05-13T01:56:39.104009Z","iopub.execute_input":"2022-05-13T01:56:39.104306Z","iopub.status.idle":"2022-05-13T02:02:21.051416Z","shell.execute_reply.started":"2022-05-13T01:56:39.104273Z","shell.execute_reply":"2022-05-13T02:02:21.050681Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss, acc = model.evaluate(test_ds)\nprint(f\"Loss: {loss}\", f\"Accuracy: {acc}\")","metadata":{"execution":{"iopub.status.busy":"2022-05-13T02:02:21.053057Z","iopub.execute_input":"2022-05-13T02:02:21.053285Z","iopub.status.idle":"2022-05-13T02:02:26.235720Z","shell.execute_reply.started":"2022-05-13T02:02:21.053257Z","shell.execute_reply":"2022-05-13T02:02:26.235032Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**RESUMEN** \nPara el dataset partido al 45, 15 y 15 por ciento para el entrenamiento, validación y test respectivamente. Unos 25 segundos por epoch. Debido a EarlyStopping, el tiempo total varia. Se incluye también. \n\nIntento 1\n- Max `val_accuracy`: 0.9903 (epoch 5)\n- Time (mm:ss): 05:18\n\nIntento 2\n- Max `val_accuracy`: 0.9888 (epoch 4)\n- Time (mm:ss): 05:41","metadata":{}},{"cell_type":"markdown","source":"### Fase 2","metadata":{}},{"cell_type":"code","source":"base_model.trainable = True\nfor layer in model.layers[-6:]:\n        if not isinstance(layer, BatchNormalization):\n            print(layer)\n            layer.trainable = True\nfor layer in model.layers[:-6]:\n     layer.trainable = False       \n#for img, label in train_ds.take(1):\n#    print(np.min(img[0]), np.max(img[0]))\n#model.summary()\noptimizer = tf.keras.optimizers.Adam(learning_rate=1e-5)\nmodel.compile(\n        optimizer=optimizer, loss = tf.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=[\"accuracy\"]\n    )\nmodel.summary()\n","metadata":{"execution":{"iopub.status.busy":"2022-05-13T02:02:26.237143Z","iopub.execute_input":"2022-05-13T02:02:26.237412Z","iopub.status.idle":"2022-05-13T02:02:26.327412Z","shell.execute_reply.started":"2022-05-13T02:02:26.237377Z","shell.execute_reply":"2022-05-13T02:02:26.326761Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"start =  datetime.now() \nhistory = model.fit(train_ds, epochs=15,  callbacks=[callback], validation_data=validation_ds)\nduration = datetime.now() - start\nprint(duration)\nplot_hist(history)","metadata":{"execution":{"iopub.status.busy":"2022-05-13T02:02:26.329297Z","iopub.execute_input":"2022-05-13T02:02:26.329543Z","iopub.status.idle":"2022-05-13T02:11:22.528382Z","shell.execute_reply.started":"2022-05-13T02:02:26.329511Z","shell.execute_reply":"2022-05-13T02:11:22.527655Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss, acc = model.evaluate(test_ds)\nprint(f\"Loss: {loss}\", f\"Accuracy: {acc}\")","metadata":{"execution":{"iopub.status.busy":"2022-05-13T02:11:22.529623Z","iopub.execute_input":"2022-05-13T02:11:22.530480Z","iopub.status.idle":"2022-05-13T02:11:28.360456Z","shell.execute_reply.started":"2022-05-13T02:11:22.530439Z","shell.execute_reply":"2022-05-13T02:11:28.359593Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**RESUMEN** \nPara el dataset partido al 45, 15 y 15 por ciento para el entrenamiento, validación y test respectivamente. Unos 25 segundos por epoch. Debido a EarlyStopping, el tiempo total varia. Se incluye también. \n\nIntento 1:\n- Max `val_accuracy`: 0.9900 (epoch 6)\n- Time (mm:ss): 06:00\n\nIntento 2:\n- Max `val_accuracy`: 0.9900 (epochs  11 y 12)\n- Time (mm:ss): 08:56","metadata":{}},{"cell_type":"markdown","source":"# Hardware Usado y Tiempos de Ejecución: Comentario general\nLa verdad es que hemos pasado por todas las opciones disponibles.\nEmpezamos usando un notebook de Google Colab. En el momento de su ejecución se os asignó una GPU modelo \\texttt{Tesla K80} con 11441MiB. Como complemento, diré que el driver usado fue NVIDIA 460.32.03 con una versión de CUDA 11.2.\n\nTuvimo que reducir el dataset para poder acelerar los tiempos de ejecución e incluso para poder ejecutar algunos procesos, como el entramiento del modelo completo (*Fine Tunning*)\n\nHemos probado varios algoritmos y hemos probado también dos procedimientos básicos:\n1. El entrenamiento de la/las últimas capas añadidas al modelo.\n2. El fine tuniing del modelo completo.\n3. El decongelado de unas pocas capas escogidas. \n\nLos tiempos en concreto se han incluido al final de la ejecución y se imprimen en cada celda de código correspondiente. \n\n\n# Dificultades\n## Comunes a las prácticas de Deep Learning\n Aunque el procedimiento está muy bien explicado en la documentación oficial de [Keras](https://keras.io/guides/transfer_learning/#the-typical-transferlearning-workflow), hemos enfrentado los problemas esperables de trabajar en Deep Learning, y es que los tiempos de ejecución son siempre muy altos y esto nos hace muy complicada la iteración. Cuando detectas un error, el tiempo que ha transcurrido ha sido tan grande que te deja poco margen para la maniobra. ALgo también común a estas práctica, pero que en esta coasión ha sido peor, quizá por los problemas generales de acceso a GPUs que está habiendo, ha sido el acceso a los recursos gratuitos de GPU. Sagemaker incluso me eliminó mi acceso a una GPU que tenía concedido en la zona de Virginia del Norte y tuve que volver a solicitar el acceso a una.\n\n## Particulares\nHablo ahora de lo particular de esta práctica.\n\n### Problemas con la carga de las imágenes\nLos problemas de carga están documentados en la sección de carga del notebook y se mencionaron también ene l foro, al que se hace referencia en dicha sección.\n\nEncontramos problemas con la carga de archivos, como por ejemplo\n```\nCorrupt JPEG data: 65 extraneous bytes before marker 0xd9\n```\n\nPero sólo cuando lo cargamos usando tfds y no cuando usamos el dataset que hicimos en la versión 2. \n\n### Cambios de proveedor, tamaño del dataset y memoria RAM\n Nos quedamos sin GPU en Colab y pasamos a Kaggle . Usamos una GPU Tesla P100 con 15.9 GB de RAM. La memoria RAM era de 13 GB, casi igual que en Colab. Al intentar experimentar con otras arquitecturas y modelos, nos quedamos sin RAM. Incluso nos quedábamos sin RAM al hacer varias pruebas. Era fácil observar como la memoria RAM pasaba de 4 Gigas (con toda la RAM de la GPU ocupada) al cargar los datos y crear el modelo a 8 tras la primera epoch, 11 en la segunda y 12.9 en la tercera. Si usábamosun modelo algo más complejo que Xception, como MobileNet o MobileNetV2, el notebook intentaba conseguir más memoria RAM de la que tenía asignada y se  En particular, los problemas con una capa densa de 2 elementos hacía impracticable su uso. Así que decidí pasar a SageMaker, donde he usado una p2.xlarge. Es muy sorprendente que es mucho más lenta que la instancia que nos asigna Kaggle. Es una K80 con 4 CPV pero 61 GB de RAM, por lo que no hemos sufrido problemas. Un problema importante es que la versión de TensorFlow que usa es 2.1 y para la que hemos programado todo es 2.6, por lo que hemos tenido que crear nuestro propio environment en sagemaker y hacerlo persistente entre arranques, lo que exige una configuración extra. Ya habíamos hecho antes y seguimos la guía en [este enlace](https://modelpredict.com/sagemaker-save-your-conda-environments). Sin embargo, el tiempo en ejecutar luego \n ```bash\n conda create -n tf-gpu tensorflow-gpu jupyter\n conda activate tf-gpu\n ```\n es tremendamente largo y desistimos.\\\\\n \nEs por ello que volvimos a Kaggle aunque tuvimos que limitar el tamaño del dataset a 25, 10, 10 para training, validación y test respectivamente. \n\nTras tomar estas medidas, aprendimos que, aunque es buena idea para acerlerar el cálculo, no es recomendable utilizar cache cuando utilizas data augmentation. POr eso, eliminamos ese paso en el pipeline de creación de los datasets. \n\n# Mejoras\nEsta sección trata de las mejoras que podría hacer en mi práctica y no de las mejoras sobre las instrucciones o el diseño que de la misma se puede hacer, aunque algunas se derivan de los problemas comentados en la sección anterior.\n\nUna mejora evidente sería la de probar más modelos. Estos sería particularmente importante si se pretendiera conseguir el mejor modelo posible. En particular, añadir más o menos capas al model preexitente dentro de ciertos limites es algo a probar. Dentro de las pruebas que sí hemos hecho menciono las siguientes:\n1. \n```\n    GlobalAveragePooling2D\n    BatchNormalization\n    Dropout\n    Dense(512)\n    Dense(1)\n```\n2.\n```\n    GlobalAveragePooling2D\n    Dropout\n    Dense(512)\n    Dense(1)\n```\n3. Esto corresponde a la configuración que se uso con Xception\n```\n    GlobalAveragePooling2D\n    Dropout\n    Dense(1)\n```\n4.  Esto correpsonde a la configuración usado con MobileNetv2\n```\n    GlobalAveragePooling2D\n    BatchNormalization\n    Dense(2)\n```\nNo hemos apreciado ninguna ventaja en las combinaciones más complejas, lo que tiene sentido al comparar lo que se añade a lo que ya da el modelo. Aún, podrían haberse hecho más pruebas sobre la arquitectura. \n\nUna medida más cautelosa del efecto que tiene el tamaño de la imagen es otra cosa a estudiar. \n\n# Conclusiones\nAunque es posible, no siempre se le puede sacar un mejor resultado al modelo por dejarlo hacer *Fine Tunning*. Hemos podido ver como unas pocas de epochs ya han conseguido llevar a MobileNetV2 al tope de su rendimiento en la métrica de accuracy sin más que entrar las nuevas capas añadidas. Esto parece ser especialmente importante en los modelos más complejos, pero `MobileNetV2`, con *sólo* 3.5 millones de parámetros, muchos menos que `Xception`, ha mostrado este comportamiento.  \n\nNo debe usarse cache cuando se hace data augmentation, algo que una vez que te pasa o si lo escribes, se vuelve evidente. \n\nLa capa `BatchNormazation` ha tenido mucho impacto en la mejora del rendimiento de `MobileNetV2`. Es uno de los motivos por los que ha tenido tan buen resultado y mejores que `Xception`.\n\nEstos modelos reentrenados parcial o totalmente, han demostrado ser muy resistentes al overfitting. En casi todos los casos, la *accuracy* del dataset de test o de validación estaba por encima del de *training*. Sólo cuando he retirado la `callback` de `EarlyStopping` se ha comenzado a manifestar el overfitting, aunque ha sido en márgenes pequeños.\n\nAunque los algoritmos se pueden adaptar a distintos tamaños de imagen, su rendimiento también varía dependiendo de este parámetro. En el caso de `MobileNetV2`, su rendimiento ha mejorado al pasar de 150 a 224. No lo hemos probado para `Xception`.\n\nEl algoritmo que se use para el reescalado de las imágenes tiene un efecto importante. Pensando en que era una mejora, utilizamos `bicubic` en el apartado correspondiente a uniformizar las dimesiones de las imágenes. El resultado ha sido que utilizaba valores fuera del rango $[0, 255]$, tanto por arriba como por abajo, perjudicando todo el resto de preprocesado. Cuando se pretendía escalar a $[-1, 1]$, el resultado era que había valores por debajo y por encima de los límites, lo que se traducía en unas peores métricas. \n\nPara poder entrenar una capa, es imprescindible que se hagan dos cosas:\n1. Que se marque como entrenable el modelo en sí con `model.trainable = True`\n1. Que se marque como entrenable la capa en cuestión. \n\nEl tiempo estimado dedicado a esta práctica, viendo el tiempo de GPU consumido en Kaggle y estimado todo el tiempo dedicado sin usar esa GPU, es de aproximadamente 40 horas. \n","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}