{"cells":[{"metadata":{},"cell_type":"markdown","source":"### Basic setup"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport seaborn as sns\nfrom sklearn.model_selection import train_test_split\nimport cv2\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nimport json\nimport os\nimport tensorflow as tf","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"ROOT_DIR = '/kaggle/input/cassava-leaf-disease-classification/'\nBATCH_SIZE = 32\nAUTOTUNE = tf.data.experimental.AUTOTUNE","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train = pd.read_csv(ROOT_DIR + 'train.csv')\nsample_submission = pd.read_csv(ROOT_DIR + 'sample_submission.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print('Train size: ', train.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"with open('/kaggle/input/cassava-leaf-disease-classification/label_num_to_disease_map.json', 'r') as f:\n    diseases = json.load(f)\nprint(diseases)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Visualize pictures for each disease"},{"metadata":{},"cell_type":"markdown","source":"For more details about each disease, you can refer to this discussion: https://www.kaggle.com/c/cassava-leaf-disease-classification/discussion/198143"},{"metadata":{},"cell_type":"markdown","source":"#### Cassava Bacterial Blight"},{"metadata":{"trusted":true},"cell_type":"code","source":"def visualize_disease(disease):\n    fig = plt.figure(figsize=(15, 10))\n    columns = 2\n    rows = 2\n    imgnames = list(train[train['label']==disease].iloc[:4]['image_id'])\n    for i in range(len(imgnames)):\n        img_name = imgnames[i]\n        im = cv2.imread(ROOT_DIR + 'train_images/' + img_name)\n        fig.add_subplot(rows, columns, int(i)+1)\n        plt.imshow(cv2.cvtColor(im, cv2.COLOR_BGR2RGB))\n        plt.title(img_name)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"visualize_disease(0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Cassava Brown Streak Disease"},{"metadata":{"trusted":true},"cell_type":"code","source":"visualize_disease(1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Cassava Green Mottle"},{"metadata":{"trusted":true},"cell_type":"code","source":"visualize_disease(2)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Cassava Mosaic Disease"},{"metadata":{"trusted":true},"cell_type":"code","source":"visualize_disease(3)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Healthy"},{"metadata":{"trusted":true},"cell_type":"code","source":"visualize_disease(4)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Check disease distribution"},{"metadata":{"trusted":true},"cell_type":"code","source":"sns.countplot(x='label', data=train)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Check image shape"},{"metadata":{"trusted":true},"cell_type":"code","source":"# image_shapes = [cv2.imread(ROOT_DIR + 'train_images/' + img_name).shape for img_name in train['image_id']]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# np.unique(image_shapes)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"So all the images have the same shape 600x800x3"},{"metadata":{},"cell_type":"markdown","source":"#### Inspect Train TFRecord files"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_filenames = tf.io.gfile.glob(ROOT_DIR + 'train_tfrecords/' + 'ld_train*.tfrec')\ntrain_set = tf.data.TFRecordDataset(train_filenames) \nfor raw_record in train_set.take(1):\n    example = tf.train.Example()\n    example.ParseFromString(raw_record.numpy())\n    print(dict(example.features.feature).keys())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Inspect Test TFRecord files"},{"metadata":{"trusted":true},"cell_type":"code","source":"test_filenames = tf.io.gfile.glob(ROOT_DIR + 'test_tfrecords/' + 'ld_test*.tfrec')\ntest_set = tf.data.TFRecordDataset(test_filenames) \nfor raw_record in test_set.take(1):\n    example = tf.train.Example()\n    example.ParseFromString(raw_record.numpy())\n    print(dict(example.features.feature).keys())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Split into train, validation set"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_filenames, val_filenames = train_test_split(train_filenames, test_size=0.3, random_state=37)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def augment(image, label):\n    if tf.random.uniform([1], dtype='float32') < 0.5:\n        image = tf.image.resize_with_crop_or_pad(image, 224+6, 224+6)\n        image = tf.image.random_crop(image, size=[224, 224, 3])\n        image = tf.image.central_crop(image, 0.6)\n        image = tf.image.resize(image, (224, 224))\n    return image, label\n\ndef _parse_function(example, feature_description):\n    parsed_example = tf.io.parse_single_example(example, feature_description)\n    image = tf.io.decode_jpeg(parsed_example['image'], channels=3)\n    image = tf.cast(image, tf.float32)\n    image = tf.image.resize(image, (224, 224))\n    image = tf.keras.applications.resnet50.preprocess_input(image)\n    if 'target' in feature_description:\n        target = tf.cast(parsed_example['target'], tf.int32)\n        return image, target\n    return image, parsed_example['image_name']\n\n\ndef load_data(filenames, ordered, labeled):\n    options = tf.data.Options()\n    if not ordered:\n        options.experimental_deterministic = False\n    dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE)\n    dataset = dataset.with_options(options)\n    if labeled:\n        feature_description = {\n            'image': tf.io.FixedLenFeature([], tf.string),\n            'target': tf.io.FixedLenFeature([], tf.int64)\n        }\n    else:\n        feature_description = {\n            'image': tf.io.FixedLenFeature([], tf.string),\n            'image_name': tf.io.FixedLenFeature([], tf.string)\n        }\n    parsed_dataset = dataset.map(lambda x: _parse_function(x, feature_description), num_parallel_calls=AUTOTUNE)\n    return parsed_dataset\n\ndef get_train_set(filenames, batch_size=32):\n    dataset = load_data(filenames, ordered=False, labeled=True)\n    dataset = dataset.map(lambda x, y: augment(x, y), num_parallel_calls=AUTOTUNE)\n    dataset = dataset.shuffle(37)\n    dataset = dataset.batch(batch_size)\n    dataset = dataset.prefetch(AUTOTUNE)\n    return dataset\n\ndef get_val_set(filenames, batch_size=32):\n    dataset = load_data(filenames, ordered=True, labeled=True)\n    dataset = dataset.batch(batch_size)\n    dataset = dataset.cache()\n    dataset = dataset.prefetch(AUTOTUNE)\n    return dataset\n\ndef get_test_set(filenames, batch_size=32):\n    dataset = load_data(filenames, ordered=True, labeled=False)\n    dataset = dataset.batch(batch_size)\n    dataset = dataset.prefetch(AUTOTUNE)\n    return dataset","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_set = get_train_set(train_filenames, batch_size=BATCH_SIZE)\nval_set = get_val_set(val_filenames, batch_size=BATCH_SIZE)\ntest_set = get_test_set(test_filenames, batch_size=BATCH_SIZE)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Modelling"},{"metadata":{"trusted":true},"cell_type":"code","source":"base_model = tf.keras.applications.ResNet50(include_top=False, input_shape=(224,224,3), weights='imagenet')\nbase_model.trainable = False # freeze pretrained model's weights\naugmentation_layer = tf.keras.Sequential([\n    tf.keras.layers.experimental.preprocessing.RandomRotation(0.2),\n    tf.keras.layers.experimental.preprocessing.RandomFlip(),\n])\nglobal_average_layer = tf.keras.layers.GlobalAveragePooling2D()\ndropout_layer = tf.keras.layers.Dropout(0.3)\nprediction_layer = tf.keras.layers.Dense(5, activation='softmax')\n\n# create model based on TF's Functional API\ninputs = tf.keras.Input(shape=(224, 224, 3))\nx = augmentation_layer(inputs)\nx = base_model(x, training=False) # remember to set training=False\nx = global_average_layer(x)\nx = dropout_layer(x)\noutputs = prediction_layer(x)\nmodel = tf.keras.Model(inputs, outputs)\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"optimizer = tf.keras.optimizers.Adam(learning_rate=2e-4)\nmodel.compile(optimizer=optimizer, \n              loss='sparse_categorical_crossentropy', \n              metrics=['sparse_categorical_accuracy'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"initial_epochs = 15\nreduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=2, min_lr=1e-5, verbose=1)\nearly_stopping_cb =  tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=4, restore_best_weights=True, verbose=1)\nmodel_checkpoint_cb = model_checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(\n    filepath='resnet50_untuned.h5',\n    monitor='val_sparse_categorical_accuracy',\n    save_best_only=True)\nhistory = model.fit(x=train_set, validation_data=val_set, epochs=initial_epochs, verbose=1,\n                    callbacks=[early_stopping_cb, reduce_lr, model_checkpoint_cb])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"acc = history.history['sparse_categorical_accuracy']\nval_acc = history.history['val_sparse_categorical_accuracy']\n\nloss = history.history['loss']\nval_loss = history.history['val_loss']\n\nplt.figure(figsize=(8, 8))\nplt.subplot(2, 1, 1)\nplt.plot(acc, label='Training Accuracy')\nplt.plot(val_acc, label='Validation Accuracy')\nplt.legend(loc='lower right')\nplt.ylabel('Accuracy')\nplt.ylim([min(plt.ylim()),1])\nplt.title('Training and Validation Accuracy')\n\nplt.subplot(2, 1, 2)\nplt.plot(loss, label='Training Loss')\nplt.plot(val_loss, label='Validation Loss')\nplt.legend(loc='upper right')\nplt.ylabel('Cross Entropy')\nplt.ylim([0,1.0])\nplt.title('Training and Validation Loss')\nplt.xlabel('epoch')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Fine-tune model"},{"metadata":{"trusted":true},"cell_type":"code","source":"# inspect base model\nfor i, layer in enumerate(base_model.layers):\n    print(i, layer.name)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# unfreeze layers of the ResNet50 base model\nbase_model.trainable = True\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"optimizer = tf.keras.optimizers.Adam(learning_rate=1e-5)\nmodel.compile(optimizer=optimizer, \n              loss='sparse_categorical_crossentropy', \n              metrics=['sparse_categorical_accuracy'],\n              )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fine_epochs = 20\ntotal_epochs = len(history.history['loss']) + fine_epochs\nreduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=2, min_lr=1e-6, verbose=1)\nearly_stopping_cb =  tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=4, verbose=1)\nmodel_checkpoint_cb = model_checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(\n    filepath='resnet50_finetuned.h5',\n    monitor='val_sparse_categorical_accuracy',\n    save_best_only=True)\nhistory_fine = model.fit(x=train_set, validation_data=val_set, epochs=total_epochs, initial_epoch=history.epoch[-1]+1, verbose=1,\n                        callbacks=[reduce_lr, early_stopping_cb, model_checkpoint_cb])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"acc += history_fine.history['sparse_categorical_accuracy']\nval_acc += history_fine.history['val_sparse_categorical_accuracy']\n\nloss += history_fine.history['loss']\nval_loss += history_fine.history['val_loss']\n\nplt.figure(figsize=(8, 8))\nplt.subplot(2, 1, 1)\nplt.plot(acc, label='Training Accuracy')\nplt.plot(val_acc, label='Validation Accuracy')\nplt.legend(loc='lower right')\nplt.ylabel('Accuracy')\nplt.ylim([min(plt.ylim()),1])\nplt.plot([initial_epochs-1,initial_epochs-1],\n          plt.ylim(), label='Start Fine Tuning')\nplt.title('Training and Validation Accuracy')\n\nplt.subplot(2, 1, 2)\nplt.plot(loss, label='Training Loss')\nplt.plot(val_loss, label='Validation Loss')\nplt.legend(loc='upper right')\nplt.ylabel('Cross Entropy')\nplt.ylim([0,1.0])\nplt.plot([initial_epochs-1,initial_epochs-1],\n         plt.ylim(), label='Start Fine Tuning')\nplt.title('Training and Validation Loss')\nplt.xlabel('epoch')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"history_fine.epoch","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}