{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Cassava Leaf Disease Classification\n## Identify the type of disease present on a Cassava Leaf image"},{"metadata":{},"cell_type":"markdown","source":"### EfficientNet packages\nIn this notebook I'm going to use the EfficientNet B3 model as pre-trained base-model. It is possible to call its packages from internet or to download as a data set and use as a second input data set. "},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install efficientnet","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Librairies"},{"metadata":{"trusted":true},"cell_type":"code","source":"import math, re, os \nimport json\nimport pandas as pd\nimport numpy as np\nimport tensorflow as tf\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport cv2\nfrom kaggle_datasets import KaggleDatasets\nimport albumentations as A\nfrom tensorflow import keras\nimport efficientnet.tfkeras as efn\nfrom keras.callbacks import ModelCheckpoint, EarlyStopping\nfrom keras.models import model_from_json\nfrom functools import partial\nfrom sklearn.model_selection import train_test_split\n\nprint(\"Tensorflow version \" + tf.__version__)","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":0.037444,"end_time":"2020-11-19T21:45:30.195328","exception":false,"start_time":"2020-11-19T21:45:30.157884","status":"completed"},"tags":[]},"cell_type":"markdown","source":"## Detect TPU\nTensor Processing Units (TPUs) are hardware accelerators that are specialized for deep learning tasks. \nThanks to Jesse Mostipak for the \"Getting Started Tutorial\".\nAs output we can expect an 1 if not working on TPU, and a 8 when using."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T21:45:30.373218Z","iopub.status.busy":"2020-11-19T21:45:30.372403Z","iopub.status.idle":"2020-11-19T21:45:34.382672Z","shell.execute_reply":"2020-11-19T21:45:34.382035Z"},"papermill":{"duration":4.150374,"end_time":"2020-11-19T21:45:34.382816","exception":false,"start_time":"2020-11-19T21:45:30.232442","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"try:\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n    print('Device:', tpu.master())\n    tf.config.experimental_connect_to_cluster(tpu)\n    tf.tpu.experimental.initialize_tpu_system(tpu)\n    strategy = tf.distribute.experimental.TPUStrategy(tpu)\nexcept:\n    strategy = tf.distribute.get_strategy()\nprint('Number of replicas:', strategy.num_replicas_in_sync)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Setup\nFor this competition, I'm using the Cassava leaf disease classification dataset, that has been previously associated to the notebook."},{"metadata":{"trusted":true},"cell_type":"code","source":"AUTOTUNE = tf.data.experimental.AUTOTUNE\nmodel_path =  \"/kaggle/working/models/model_EffB3.h5\"\ndatasets_dir = KaggleDatasets().get_gcs_path()\nbatch_size = 64 * strategy.num_replicas_in_sync\nimage_size = [512, 512]\nclasses = ['0', '1', '2', '3', '4']\nepochs = 100","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":0.041086,"end_time":"2020-11-19T21:48:01.478205","exception":false,"start_time":"2020-11-19T21:48:01.437119","status":"completed"},"tags":[]},"cell_type":"markdown","source":"## Exploratory data analysis (EDA)"},{"metadata":{},"cell_type":"markdown","source":"The mapping between each label and the real disease name is in the \"label_num_to_disease_map.json\" file. I'm going to link it to the dataset of images in order to explore how does it look every disease and get a general idea of the kind of images thar are in the dataset. There are 4 types of disease (classes) and an extra class that refers to the healthy plant. "},{"metadata":{"trusted":true},"cell_type":"code","source":"base_dir = \"../input/cassava-leaf-disease-classification/\"\nwith open(os.path.join(base_dir, \"label_num_to_disease_map.json\")) as file:\n    name_classes = json.loads(file.read())\n    name_classes = {int(k) : v for k, v in name_classes.items()}\n    \nprint(json.dumps(name_classes, indent=4))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"input_files = os.listdir(os.path.join(base_dir, \"train_images\"))\nprint(f\"Images for training: {len(input_files)}\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"With a total of more than 21k JPG images, I can explore the kind of images and the distribution of presence of diseases."},{"metadata":{"scrolled":true,"trusted":true},"cell_type":"code","source":"train = pd.read_csv(os.path.join(base_dir, \"train.csv\"))\ntrain[\"disease\"] = train[\"label\"].map(name_classes)\ntrain","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize=(8, 6))\nsns.countplot(y=\"disease\", data=train);\nplt.ylabel(\"Health condition\")\nplt.xlabel(\"Disponible images\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"As we can see, there are a lot of photographs of Cassava mosaic disease in comparison with others diseases. Is this disease more frequent? Is easier to identify? Is a question of how the dataset has been selected? Let's see how looks like this Cassava disease."},{"metadata":{"trusted":true},"cell_type":"code","source":"# For visualize only one categorie\ndef plot_images(label, disease, images_number=3, verbose=0):\n    plot_list = train[train[\"label\"] == label].sample(images_number)['image_id'].tolist()    \n    if verbose:\n        print(plot_list)    \n    labels = [disease for i in range(len(plot_list))]        \n    plt.figure(figsize=(16, 12))    \n    for ind, (image_id, disease) in enumerate(zip(plot_list, labels)):\n        plt.subplot(3, 3, ind + 1)\n        image = cv2.imread(os.path.join(base_dir,\"train_images\",image_id))\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        plt.imshow(image)\n        plt.title(disease, fontsize=12)\n        plt.axis(\"off\")   \n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plot_images(label=0, disease=\"Cassava Bacterial Blight (CBB)\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plot_images(label=3, disease=\"Cassava Mosaic Disease (CMD)\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"CMD is the most important threat to cassava production in some African zones, and also in this dataset is the most common disease."},{"metadata":{"trusted":true},"cell_type":"code","source":"plot_images(label=1, disease=\"Cassava Brown Streak Disease (CBSD)\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plot_images(label=2, disease=\"Cassava Green Mottle (CGM)\")","execution_count":null,"outputs":[]},{"metadata":{"scrolled":true,"trusted":true},"cell_type":"code","source":"plot_images(label=4, disease='Healthy')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Even in the case of healthy plants, the range of posibilities is long. I could find here green, luminous, beatuful, well focused photos, but also low quality photos, damaged or whitered leaves, and a diversity of parts of the plant : roots, steams, leaves, etc."},{"metadata":{},"cell_type":"markdown","source":"## Load the data\nI'm going to use some fonctions to flow the datasets. "},{"metadata":{"trusted":true},"cell_type":"code","source":"def decode_image(image):\n    image = tf.image.decode_jpeg(image, channels=3)\n    image = tf.cast(image, tf.float32) / 255.0\n    image = tf.reshape(image, [*image_size, 3])\n    return image","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The TFRecord format is a simple format for storing a sequence of binary records."},{"metadata":{"trusted":true},"cell_type":"code","source":"def read_tfrecord(example, labeled):\n    tfrecord_format = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"target\": tf.io.FixedLenFeature([], tf.int64)\n    } if labeled else {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"image_name\": tf.io.FixedLenFeature([], tf.string)}\n    \n    example = tf.io.parse_single_example(example, tfrecord_format)\n    image = decode_image(example['image'])\n    \n    if labeled:\n        label = tf.cast(example['target'], tf.int32)\n        return image, label\n    idnum = example['image_name']\n    return image, idnum","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_dataset(filenames, labeled=True, ordered=False):\n    ignore_order = tf.data.Options()\n    if not ordered:\n        # disable order, increase speed\n        ignore_order.experimental_deterministic = False \n    # automatically interleaves reads from multiple files\n    dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE) \n    # uses data as soon as it streams in, rather than in its original order\n    dataset = dataset.with_options(ignore_order) \n    dataset = dataset.map(partial(read_tfrecord, labeled=labeled), \n                          num_parallel_calls=AUTOTUNE)\n    return dataset","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"I'm using 75% of photographs for training and 25% for validation."},{"metadata":{"trusted":true},"cell_type":"code","source":"train_fnames, valid_fnames = train_test_split(\n    tf.io.gfile.glob(datasets_dir + '/train_tfrecords/ld_train*.tfrec'),\n    test_size=0.25, random_state=0)\ntest_fnames = tf.io.gfile.glob(datasets_dir + '/test_tfrecords/ld_test*.tfrec')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"raw","source":"In order to aument the data, I'm using two mainly means: Position variation and HSB representations of color."},{"metadata":{"trusted":true},"cell_type":"code","source":"def data_augment(image, label):\n    image = tf.image.random_flip_left_right(image)\n    image = tf.image.random_flip_up_down(image)\n    image = tf.image.random_brightness(image, 0.2)\n    image = tf.image.random_saturation(image, 0, 2)\n    image = tf.image.random_hue(image, 0.2)\n    return image, label","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"I prefer to use a fonction to specify the way of getting the datasets, since it is faster and cleaner."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T21:48:01.018921Z","iopub.status.busy":"2020-11-19T21:48:01.017912Z","iopub.status.idle":"2020-11-19T21:48:01.02164Z","shell.execute_reply":"2020-11-19T21:48:01.020852Z"},"papermill":{"duration":0.052326,"end_time":"2020-11-19T21:48:01.021791","exception":false,"start_time":"2020-11-19T21:48:00.969465","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"def get_training_dataset():\n    dataset = load_dataset(train_fnames, labeled=True)  \n    dataset = dataset.map(data_augment, num_parallel_calls=AUTOTUNE)  \n    dataset = dataset.repeat()\n    dataset = dataset.shuffle(2048)\n    dataset = dataset.batch(batch_size)\n    dataset = dataset.prefetch(AUTOTUNE)\n    return dataset","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T21:48:01.109616Z","iopub.status.busy":"2020-11-19T21:48:01.108559Z","iopub.status.idle":"2020-11-19T21:48:01.111418Z","shell.execute_reply":"2020-11-19T21:48:01.111986Z"},"papermill":{"duration":0.049787,"end_time":"2020-11-19T21:48:01.112145","exception":false,"start_time":"2020-11-19T21:48:01.062358","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"def get_validation_dataset(ordered=False):\n    dataset = load_dataset(valid_fnames, labeled=True, ordered=ordered) \n    dataset = dataset.batch(batch_size)\n    dataset = dataset.cache()\n    dataset = dataset.prefetch(AUTOTUNE)\n    return dataset","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T21:48:01.204402Z","iopub.status.busy":"2020-11-19T21:48:01.203418Z","iopub.status.idle":"2020-11-19T21:48:01.207545Z","shell.execute_reply":"2020-11-19T21:48:01.20682Z"},"papermill":{"duration":0.050395,"end_time":"2020-11-19T21:48:01.207665","exception":false,"start_time":"2020-11-19T21:48:01.15727","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"def get_test_dataset(ordered=False):\n    dataset = load_dataset(test_fnames, labeled=False, ordered=ordered)\n    dataset = dataset.batch(batch_size)\n    dataset = dataset.prefetch(AUTOTUNE)\n    return dataset","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T21:48:01.301631Z","iopub.status.busy":"2020-11-19T21:48:01.30084Z","iopub.status.idle":"2020-11-19T21:48:01.304479Z","shell.execute_reply":"2020-11-19T21:48:01.303807Z"},"papermill":{"duration":0.05422,"end_time":"2020-11-19T21:48:01.304611","exception":false,"start_time":"2020-11-19T21:48:01.250391","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"def count_data_items(filenames):\n    n = [int(re.compile(r\"-([0-9]*)\\.\").search(filename).group(1)) for filename in filenames]\n    return np.sum(n)","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T21:48:01.39253Z","iopub.status.busy":"2020-11-19T21:48:01.391743Z","iopub.status.idle":"2020-11-19T21:48:01.395039Z","shell.execute_reply":"2020-11-19T21:48:01.395972Z"},"papermill":{"duration":0.051209,"end_time":"2020-11-19T21:48:01.396198","exception":false,"start_time":"2020-11-19T21:48:01.344989","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"print('Dataset: {} training images, {} validation images, {} (unlabeled) test images'.format(\n    count_data_items(train_fnames), count_data_items(valid_fnames), count_data_items(test_fnames)))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Test of apply object detection to dataset as a cleaning traitement before classification."},{"metadata":{"trusted":true},"cell_type":"code","source":"# def run_detector(detector, image_path):\n#     img = tf.io.read_file(image_path)\n#     img = tf.image.decode_jpeg(img, channels=3)\n#     converted_img  = tf.image.convert_image_dtype(img, tf.float32)[tf.newaxis, ...]\n#     result = detector(converted_img)\n#     result = {key:value.numpy() for key,value in result.items()}\n#     return result, img\n\n# def detecting_intruses(image, input_filename, boxes, class_names, scores, max_boxes=5):\n#     plants_options = [\"Plant\",\"Houseplant\",\"Flower\",\"Tree\"]\n#     ch = 0\n#     for i in range(min(boxes.shape[0], max_boxes)):\n#         if class_names[i].decode(\"ascii\") in plants_options:\n#             ch+=1\n#     if ch==0:\n#         print(ch)\n#         print(\"No plants in image:\",input_filename)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# import tensorflow_hub as hub\n# module_handle = \"https://tfhub.dev/google/openimages_v4/ssd/mobilenet_v2/1\"\n# detector = hub.load(module_handle).signatures['default']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# for i in range(len(input_files)):\n#     image_path = datasets_dir+\"/train_images/\"+input_files[i]\n#     result, img = run_detector(detector, image_path)\n#     detecting_intruses(img.numpy(), input_files[i],result[\"detection_boxes\"],\n#               result[\"detection_class_entities\"], result[\"detection_scores\"])","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":0.230199,"end_time":"2020-11-19T21:48:28.353794","exception":false,"start_time":"2020-11-19T21:48:28.123595","status":"completed"},"tags":[]},"cell_type":"markdown","source":"# Building the model"},{"metadata":{},"cell_type":"markdown","source":"\n## Learning rate schedule\nWhen training a model, it is often recommended to lower the learning rate as the training progresses. This schedule applies an exponential decay function to an optimizer step, given a provided initial learning rate."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T21:48:28.8307Z","iopub.status.busy":"2020-11-19T21:48:28.829632Z","iopub.status.idle":"2020-11-19T21:48:28.833152Z","shell.execute_reply":"2020-11-19T21:48:28.832481Z"},"papermill":{"duration":0.248904,"end_time":"2020-11-19T21:48:28.83328","exception":false,"start_time":"2020-11-19T21:48:28.584376","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"lr_scheduler = keras.optimizers.schedules.ExponentialDecay(\n    initial_learning_rate=1e-3, decay_steps=10000, decay_rate=0.9)","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":0.22538,"end_time":"2020-11-19T21:48:29.285377","exception":false,"start_time":"2020-11-19T21:48:29.059997","status":"completed"},"tags":[]},"cell_type":"markdown","source":"## Building the model\nIn order to ensure that the model is trained on the TPU, it is built using `with strategy.scope()`.    \n\nThis model is built using transfer learning, using the pretrained model EfficientNet B3 as base model, and adding the customizable model built using `tf.keras.Sequential`.\n\nNote that we're using `sparse_categorical_crossentropy` as our loss function, because I did not use one-hot-encoder in the labels."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T21:48:29.74622Z","iopub.status.busy":"2020-11-19T21:48:29.745069Z","iopub.status.idle":"2020-11-19T21:48:49.158244Z","shell.execute_reply":"2020-11-19T21:48:49.157378Z"},"papermill":{"duration":19.661572,"end_time":"2020-11-19T21:48:49.158413","exception":false,"start_time":"2020-11-19T21:48:29.496841","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"with strategy.scope():    \n    \n    #     img_adjust_layer = tf.keras.layers.Lambda(tf.keras.applications.mobilenet.preprocess_input, input_shape=[*image_size, 3])\n    #     base_model = tf.keras.applications.MobileNet(weights='imagenet', include_top=False)\n    \n    base_model = efn.EfficientNetB3(weights='imagenet', include_top=False)\n    \n    base_model.trainable = False\n    model = tf.keras.Sequential([\n        tf.keras.layers.BatchNormalization(renorm=True),\n#         img_adjust_layer,\n        base_model,\n        tf.keras.layers.GlobalAveragePooling2D(),\n        tf.keras.layers.Dense(256, activation='relu'),\n#         tf.keras.layers.Dropout(0.4),\n#         tf.keras.layers.Dense(64, activation='relu'),\n        tf.keras.layers.Dropout(0.1),\n        tf.keras.layers.Dense(len(classes), activation='softmax')  \n    ])\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=lr_scheduler, epsilon=0.00001),\n        loss='sparse_categorical_crossentropy',  \n        metrics=['sparse_categorical_accuracy'])\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"In order to save the best model even if after some epochs it has degradeted, I'm going to use a checkpoint that allows me to go back to the best scores. I'm also using an early-stopping callback, that dont allows the training to continue if the monitored score is not improving. A third callback in test is the diminution of learning rate if after some epochs there is not improvement."},{"metadata":{"trusted":true},"cell_type":"code","source":"if not os.path.exists(\"/kaggle/working/models/\"):\n    os.mkdir(\"/kaggle/working/models/\") \nweights_path = \"/kaggle/working/models/weights_EffB3.h5\"\ncallbacks_list = [\n      ModelCheckpoint(weights_path, monitor='val_sparse_categorical_accuracy', \n                  verbose=1, save_best_only=True, save_weights_only=True),\n      EarlyStopping(monitor='val_sparse_categorical_accuracy', patience=10, verbose=0),\n#       ReduceLROnPlateau(monitor='val_sparse_categorical_accuracy', factor=0.2,\n#                               patience=5)\n      ]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Let's take a look to see how many layers are in the base model\nprint(\"Number of layers in the base model: \", len(base_model.layers))\nprint(\"Number of layers in the model: \", len(model.layers))","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":0.174404,"end_time":"2020-11-19T21:48:49.513099","exception":false,"start_time":"2020-11-19T21:48:49.338695","status":"completed"},"tags":[]},"cell_type":"markdown","source":"## Train the model"},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T21:48:49.868188Z","iopub.status.busy":"2020-11-19T21:48:49.867432Z","iopub.status.idle":"2020-11-19T21:48:49.93567Z","shell.execute_reply":"2020-11-19T21:48:49.936263Z"},"papermill":{"duration":0.249051,"end_time":"2020-11-19T21:48:49.936435","exception":false,"start_time":"2020-11-19T21:48:49.687384","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"# load data\ntrain_dataset = get_training_dataset()\nvalid_dataset = get_validation_dataset()","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T21:48:50.304573Z","iopub.status.busy":"2020-11-19T21:48:50.303472Z","iopub.status.idle":"2020-11-19T22:04:46.794408Z","shell.execute_reply":"2020-11-19T22:04:46.795473Z"},"papermill":{"duration":956.681695,"end_time":"2020-11-19T22:04:46.795744","exception":false,"start_time":"2020-11-19T21:48:50.114049","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"steps_per_epoch = count_data_items(train_fnames) // batch_size\nvalid_steps = count_data_items(valid_fnames) // batch_size\n\nhistory = model.fit(train_dataset, \n                    steps_per_epoch=steps_per_epoch, \n                    epochs=epochs,\n                    validation_data=valid_dataset,\n                    validation_steps=valid_steps,\n                    callbacks=[callbacks_list]\n                   )","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":1.26977,"end_time":"2020-11-19T22:04:49.3763","exception":false,"start_time":"2020-11-19T22:04:48.10653","status":"completed"},"tags":[]},"cell_type":"markdown","source":"I'm able to see a printout of each layer, their corresponding shape, as well as the associated number of parameters. \nAt the bottom of the printout it's possible to see information on the total parameters, trainable parameters, and non-trainable parameters. Because I am using a pre-trained model, there is a large number of non-trainable parameters."},{"metadata":{"trusted":true},"cell_type":"code","source":"model.load_weights(weights_path)\nmodel.save(model_path)\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":1.245239,"end_time":"2020-11-19T22:04:54.493139","exception":false,"start_time":"2020-11-19T22:04:53.2479","status":"completed"},"tags":[]},"cell_type":"markdown","source":"# Evaluate the model"},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T22:04:57.050462Z","iopub.status.busy":"2020-11-19T22:04:57.0494Z","iopub.status.idle":"2020-11-19T22:04:57.053166Z","shell.execute_reply":"2020-11-19T22:04:57.053855Z"},"papermill":{"duration":1.31245,"end_time":"2020-11-19T22:04:57.054025","exception":false,"start_time":"2020-11-19T22:04:55.741575","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"# print out variables available to use\nprint(history.history.keys())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"It is useful to see the model performance through a plot of the scores as a fonction of the number of epochs. It gives me information about the moment at which an inflection occurs, about how smooth is the performance evolution, about the relashionship of validation and training datasets evolutions, etc. in a fast and comprehensible way."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T22:04:59.5746Z","iopub.status.busy":"2020-11-19T22:04:59.573814Z","iopub.status.idle":"2020-11-19T22:04:59.983142Z","shell.execute_reply":"2020-11-19T22:04:59.982506Z"},"papermill":{"duration":1.671861,"end_time":"2020-11-19T22:04:59.983272","exception":false,"start_time":"2020-11-19T22:04:58.311411","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"history_frame = pd.DataFrame(history.history)\nhistory_frame.loc[:, ['loss', 'val_loss']].plot()\nhistory_frame.loc[:, ['sparse_categorical_accuracy', 'val_sparse_categorical_accuracy']].plot();","execution_count":null,"outputs":[]},{"metadata":{"papermill":{"duration":1.326243,"end_time":"2020-11-19T22:05:02.628032","exception":false,"start_time":"2020-11-19T22:05:01.301789","status":"completed"},"tags":[]},"cell_type":"markdown","source":"## Make predictions\nNow that the model is trained, I can use it to make predictions."},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T22:05:05.184942Z","iopub.status.busy":"2020-11-19T22:05:05.183725Z","iopub.status.idle":"2020-11-19T22:05:05.18757Z","shell.execute_reply":"2020-11-19T22:05:05.186823Z"},"papermill":{"duration":1.270192,"end_time":"2020-11-19T22:05:05.187694","exception":false,"start_time":"2020-11-19T22:05:03.917502","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"# this code will convert our test image data to a float32 \ndef to_float32(image, label):\n    return tf.cast(image, tf.float32), label","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-11-19T22:05:07.746039Z","iopub.status.busy":"2020-11-19T22:05:07.744935Z","iopub.status.idle":"2020-11-19T22:05:22.234912Z","shell.execute_reply":"2020-11-19T22:05:22.235492Z"},"papermill":{"duration":15.776858,"end_time":"2020-11-19T22:05:22.235661","exception":false,"start_time":"2020-11-19T22:05:06.458803","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"test_ds = get_test_dataset(ordered=True) \ntest_ds = test_ds.map(to_float32)\n\nprint('Predicted label')\ntest_images_ds = test_ds\ntest_images_ds = test_ds.map(lambda image, idnum: image)\nprobabilities = model.predict(test_images_ds)\npredictions = np.argmax(probabilities, axis=-1)\nprint(predictions)","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}