{"cells":[{"metadata":{},"cell_type":"markdown","source":"Based on http://arxiv.org/abs/1512.04150\n\"Learning Deep Features for Discriminative Localization\" by Zhou et al.\n\nand\n\nBased on https://arxiv.org/abs/2012.04846\n\"SnapMix - Semantically Proportional Mixing for Augmentation\" by Huang et al."},{"metadata":{},"cell_type":"markdown","source":"# # Using ResNet50"},{"metadata":{"trusted":true},"cell_type":"code","source":"import cv2\nimport pandas as pd\nimport numpy as np\nimport os\nfrom pathlib import Path\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers, models\nimport tensorflow.keras.preprocessing.image\n\nbase_path = Path('../input/cassava-leaf-disease-classification')\ntrain_directory = os.path.join(base_path,'train_images')\ntest_directory = os.path.join(base_path,'test_images')\n\ntrain_images = os.listdir(train_directory)\ntest_images = os.listdir(test_directory)\n\ndata_df = pd.read_csv(os.path.join(base_path,'train.csv'))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#define input-parameters for snapmix:\n\n# Model input parameters:\nbatch_size = 32\nimage_width = 256\nimage_height = 256\ninput_shape=(image_width, image_height, 3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\ndef snapmix_batch_loss(is_augmented, label_batch, y_pred, label_batch2 = None, box_weights1 = None, box_weights2 = None):\n    \"\"\"\n    Calculates the loss for snap-mix algorithm if is_augmented = True, calculates sparse-categorical-crossentropy loss, if is_augmented = False\n    \n    Args:\n        is_augmented (bool) : determines if snap-mix loss function is used or not\n        label_batch : true labels\n        y_pred : predicted labels\n        label_batch2 : labels of patched-in images\n        box_weights1 : semantic box weights of patched-into images\n        box_weights2 : semantic box weights of patched-in images\n    \n    Returns:\n        snap-mix loss or sparse-categorical-crossentropy loss\n    \"\"\"\n    if is_augmented:\n        loss1 = tf.keras.losses.sparse_categorical_crossentropy(label_batch, y_pred)\n        loss2 = tf.keras.losses.sparse_categorical_crossentropy(label_batch2, y_pred)\n        \n        return tf.math.reduce_mean(tf.math.multiply(loss1, (1 - box_weights1)) + tf.math.multiply(loss2, box_weights2),\n                                   axis=0)\n\n    return tf.math.reduce_mean(tf.keras.losses.sparse_categorical_crossentropy(label_batch, y_pred))\n\n\ndef snapmix_batch_augmentation(class_activation_model, model, img_batch, label_batch, output_layer_name, alpha=0.2):\n    \"\"\"\n    Applies, the SnapMix-augmentation to the images and labels within a data batch with respect to a model.\n\n    Args:\n        class_activation_model (model) :\n        model (model) :\n        img_batch (tf.tensor) : batch with images, all the same shape\n        label_batch (numpy list) : batch with labels for the images\n        output_layer_name (string) : name of the final output-layer\n        alpha (float), optional: parameter for beta-distribution generating image shrinking-factor for box-area\n\n    Returns:\n        augmented_images : the augmented input-images\n        label_batch2 : the labels of the images that have been patched into the input-images\n        box_weights1 : batch of semantic weights of cut-out-boxes\n        box_weights2 : batch of semantic weights of patched-in-boxes\n    \"\"\"\n\n    batch_size = img_batch.shape[0]\n    img_width = img_batch.shape[1]\n    img_height = img_batch.shape[2]\n    \n    # get classificator weights:\n    classificator_weights = model.get_layer(output_layer_name).get_weights() # returns: (weights, biases)\n    classificator_weights = classificator_weights[0] \n    \n    box1 = random_box(img_width, img_height, alpha=alpha)\n    box2 = random_box(img_width, img_height, alpha=alpha)\n\n    # build another image batch from the input batch:\n    rng = np.random.default_rng()\n    permutation = rng.permutation(batch_size)\n    label_batch = label_batch.numpy().astype(int)\n\n    img_batch2 = np.copy(img_batch)\n    img_batch2 = img_batch2[permutation]\n    label_batch2 = np.copy(label_batch)\n    label_batch2 = label_batch2[permutation]\n\n    # get spm and calculate boxweights:\n    SPM1 = batch_semantic_percentage_map(\n        class_activation_model=class_activation_model,\n        classificator_weights=classificator_weights,\n        img_batch=img_batch,\n        label_batch=label_batch)\n\n    SPM2 = np.copy(SPM1)\n    SPM2 = SPM2[permutation, :, :]\n    x11, y11, x12, y12 = box1\n    x21, y21, x22, y22 = box2\n\n    cropped_SPM1 = SPM1[:, x11:(x12 + 1), y11:(y12 + 1)]\n    #box_weights1 = tf.reduce_sum(cropped_SPM1, axis=[1, 2]).numpy()\n    box_weights1 = np.sum(cropped_SPM1, axis=(1, 2))\n    cropped_SPM2 = SPM2[:, x21:(x22 + 1), y21:(y22 + 1)]\n    #box_weights2 = tf.reduce_sum(cropped_SPM2, axis=[1, 2]).numpy()\n    box_weights2 = np.sum(cropped_SPM2, axis=(1, 2))\n    \n    # some normalization for patching with equal labels:\n    same_label = label_batch == label_batch2\n    tmp = np.copy(box_weights1)\n    box_weights1[same_label] += box_weights2[same_label]\n    box_weights2[same_label] += tmp[same_label]\n\n    # fix for cases where box_weights are not well defined:\n    rel_area1 = (y12 - y11) * (x12 - x11) /  (img_width * img_height)\n    rel_area2 = (y22 - y21) * (x22 - x21) / (img_width * img_height)\n    box_weights1[np.isnan(box_weights1)] = rel_area1\n    box_weights2[np.isnan(box_weights2)] = rel_area2\n\n    #crop and paste images:\n    #cropped = img_batch2[:, x21: x22, y21: y22]\n    cropped = img_batch2[:, x21: x22, y21: y22,:]\n    resized_cropped = np.zeros((cropped.shape[0], x12 - x11, y12 - y11, cropped.shape[3]))\n    #print(\"cropped.shape: {}\".format(cropped.shape))\n    #print(\"resized_cropped.shape: {}\".format(resized_cropped.shape))\n    for i in range(batch_size):\n        resized_cropped[i] = cv2.resize(cropped[i,:,:], (y12 - y11, x12 - x11), interpolation=cv2.INTER_CUBIC)\n    #cropped = tf.image.resize(cropped, (x12 - x11, y12 - y11)).numpy()\n    # copy images otherwise originals are spoiled:\n    augmented_images = np.copy(img_batch)\n    augmented_images[:, x11: x12, y11:y12] = resized_cropped\n\n    return augmented_images, label_batch2, box_weights1, box_weights2\n\n\ndef batch_semantic_percentage_map(class_activation_model, classificator_weights, img_batch, label_batch):\n    \"\"\"\n    Calculates the SPM - Semantic Percentage Map of a batch of images.\n\n    Args:\n        class_activation_model : the part of the model to calculate the class-activations from (the part before the classifier)\n        classificator_weights : the weights of the last layer of the classifier, i.e. for a softmax-layer:\n            classificator_weights = model.get_layer(\"SoftMaxLayerName\").get_weights()\n\n    Returns:\n        the SPMs (Semantic Percentage Maps) for a batch of images.\n    \"\"\"\n    feature_maps_batch = class_activation_model.predict(img_batch)\n\n    # Calculate Class Activation Map (CAM):\n    batch_size = feature_maps_batch.shape[0]\n    feature_map_width = feature_maps_batch.shape[1]\n    feature_map_height = feature_maps_batch.shape[2]\n    CAM_batch = np.zeros((batch_size, feature_map_width, feature_map_height))\n    clw_matrix = classificator_weights[:, label_batch]\n    for i in range(batch_size):\n        #CAM_batch[i, :, :] = tf.tensordot(clw_matrix[:, i], feature_maps_batch[i, :, :, :], axes=[[0], [2]])\n        CAM_batch[i, :, :] = np.tensordot(clw_matrix[:, i], feature_maps_batch[i, :, :, :], axes=([0], [2]))\n\n    # upsampling feature map to size of image:\n    image_width = img_batch.shape[1]\n    image_height = img_batch.shape[2]\n    resized_CAM_batch = np.zeros((batch_size, image_width, image_height))\n    for i in range(batch_size):\n        resized_CAM_batch[i,:,:] = cv2.resize(CAM_batch[i, :, :], (image_width, image_height), interpolation=cv2.INTER_CUBIC)\n        \n    #CAM_batch = np.expand_dims(CAM_batch, axis=-1)\n    #CAM_batch = tf.image.resize(images=CAM_batch, size=(image_width, image_height), method=\"bilinear\")\n    #CAM_batch = np.squeeze(CAM_batch, axis=-1)\n\n    #CAM_batch -= tf.math.reduce_min(CAM_batch)\n    resized_CAM_batch -= np.amin(resized_CAM_batch)\n    #normalization_factor = tf.reduce_sum(CAM_batch).numpy() + 1e-8\n    normalization_factor = np.sum(resized_CAM_batch) + 1e-8\n    resized_CAM_batch /= normalization_factor\n\n    return resized_CAM_batch\n\n\ndef random_box(im_width, im_height, alpha, minimal_width=2, minimal_height=2):\n    \"\"\"\n    Returns a random box=(x1, y1, x2, y2) with 0 < x1, x2 < im_width\n    and 0< y1, y2, < im_height that spans an area equal to\n    lambda_img * (x2 - x1) * (y2 - y1), where lambda_img is randomly drawn from a beta-distribution\n    beta(alpha, alpha)\n    \"\"\"\n    rng = np.random.default_rng()\n    random_width = im_width + 1\n    random_height = 0\n\n    while random_width > im_width or random_height > im_height or random_height < minimal_height or \\\n            random_width < minimal_width:\n        lambda_img = rng.beta(alpha, alpha)\n        if (lambda_img < 1 and lambda_img > 0):\n            random_width = int(rng.integers(minimal_width, im_width) * np.sqrt(lambda_img) // 1)\n            #random_width = random_width.astype(int)\n\n            random_height = int(rng.integers(minimal_height, im_height) * np.sqrt(lambda_img) // 1)\n            #random_height = random_height.astype(int)\n\n    left_upper_x = rng.integers(0, im_width - random_width, endpoint=True)\n    left_upper_y = rng.integers(0, im_height - random_height, endpoint=True)\n\n    box = (left_upper_x,\n           left_upper_y,\n           left_upper_x + random_width - 1,\n           left_upper_y + random_height - 1)\n\n    return box\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Define Data Generators as this DataSequence-Class:\n\nfrom tensorflow.keras.preprocessing.image import load_img\nfrom tensorflow.keras.preprocessing.image import img_to_array\nfrom tensorflow.keras.utils import Sequence\nimport math\nimport random\nimport numpy as np\n\n\nclass DataSequence(Sequence):\n    \"\"\"\n    Keras Sequence object reading data-files (images) from a directory, while file-names and labels are provided by a data-frame.\n    Providing data-label pairs in batches.\n    \"\"\"\n\n    def __init__(self, df, image_path, batch_size, img_size, shuffle=True, preprocessing_function = None):\n        \"\"\"\n        Initialization\n        Args:\n            df (pandas data-frame) : to be read from, containing image-name-column and label-column\n            image_path (string): path to images location (directory)\n            batch_size (int): batch size at each iteration\n            img_size (list): image-size, ex. [28, 28]\n            shuffle (bool): True to shuffle label indexes after every epoch\n            preprocessing_function: \n\n        Returns:\n            batch of images - (batch_size, img_size[0], img_size[1], channels)  resized to img_size and rescaled with 1./255,\n            batch of labels - (batch_Size)\n        \"\"\"\n        self.df = df\n        self.batch_size = batch_size\n        self.img_size = img_size\n        self.shuffle = shuffle\n        rng = np.random.default_rng()\n        if preprocessing_function:\n            self.preprocessing_function = preprocessing_function\n        else:\n            self.preprocessing_function = lambda x: x\n        \n        # Take labels and a list of image locations in memory:\n        self.label_column = df.columns[1]\n        self.image_column = df.columns[0]\n        self.labels = self.df[self.label_column].values\n        self.im_list = self.df[self.image_column].apply(lambda x: os.path.join(image_path, x)).tolist()\n\n    def __len__(self):\n        \"\"\"returns number of full batches available\"\"\"\n        return int(math.ceil(len(self.df) / float(self.batch_size)))\n\n    def on_epoch_end(self):\n        pass\n        #if self.shuffle:\n        #    rng.shuffle(self.labels)\n        #    rng.shuffle(self.im_list)\n\n    def get_batch_labels(self, idx):\n        # Fetch a batch or what is left of labels:\n        if len(self.df) >= (idx + 1) * self.batch_size:\n            return self.labels[idx * self.batch_size: (idx + 1) * self.batch_size]\n        else:\n            return self.labels[idx * self.batch_size: len(self.df)]\n\n    def get_batch_features(self, idx):\n        # Fetch a batch or what is left of images:\n        if len(self.df) >= (idx + 1) * self.batch_size:\n            return [\n                self.preprocessing_function(tf.image.resize(tf.keras.preprocessing.image.img_to_array(load_img(im)) * 1. / 255, size=self.img_size))\n                for im in self.im_list[idx * self.batch_size: (1 + idx) * self.batch_size]]\n        else:\n            return [\n                self.preprocessing_function(tf.image.resize(tf.keras.preprocessing.image.img_to_array(load_img(im)) * 1. / 255, size=self.img_size))\n                for im in self.im_list[idx * self.batch_size: len(self.df)]]\n\n    def __getitem__(self, idx):\n        batch_images = tf.stack(self.get_batch_features(idx), axis=0)\n        batch_labels = tf.stack(self.get_batch_labels(idx), axis=0)\n        \n        return batch_images, batch_labels\n\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Load the ResNet50 pre-trained from the added data-set \"tf-keras-resnet\":\n\nhttps://www.kaggle.com/xhlulu/tf-keras-resnet\n\n(other weight files did not match format)\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"from tensorflow.keras.applications import ResNet50\n\nconv_base = ResNet50(include_top=False, weights=\"../input/tf-keras-resnet/resnet50_notop.h5\", input_shape=input_shape)\n\nconv_base.trainable = False\nconv_base.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Define the Conv Base Model/ class_activation_model - the pre-trained model without it's classificator part/ the model to read the class-activations from:\n# Since we have a big enough convolutional layer at the end of our conv_base, we skip the resolution-increasing conv2d(1024) layer:\n\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Input, GlobalAveragePooling2D, Dense, Conv2D\n\n# Conv Base Model:\n\n# keep all layers from the conv_base up to the last convolution:\nconv_base_model = Model(conv_base.input, conv_base.layers[-4].output, name=\"ConvBaseModel\")\n\n#for layer in conv_base_model.layers:\n#    layer.trainable = True\n    \nmodel_input = tf.keras.Input(input_shape)\n\n# with additional Conv2D layer for increased resolution as in research-paper\n#model_output = Conv2D(1024, (3,3), padding=\"same\", trainable=True, name=\"HighResolutionLayer\")(conv_base_model(model_input)) \nmodel_output = Conv2D(512, (3,3), padding=\"same\", trainable=True, name=\"HighResolutionLayer\")(conv_base_model(model_input)) \n\n# without additional Conv2D layer -> uncomment following line:\n#model_output = conv_base_model(model_input) \nclass_activation_model = Model(inputs=[model_input], outputs=[model_output], name=\"ClassActivationModel\")\n\nclass_activation_model.summary()\nconv_base_model.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Freeze/ Thaw layers of the Conv Base Model for fine-tuning:\n\nfor layer in class_activation_model.layers:\n    if layer.name == \"conv5_block3_3_conv\":\n        #layer.trainable = True # uncomment when fine-tuning\n        layer.trainable = False # comment when fine-tuning\n    else:\n        layer.trainable = False\n        \n    if layer.name == \"HighResolutionLayer\":\n        layer.trainable = True\n\nclass_activation_model.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Define the model \"SnapMixOnResNet50Model\":\n\noutput_layer_name=\"SoftMaxClassifier\"\nmodel_name = \"SnapMixOnResNet50Model\"\n\nmodel_input = tf.keras.Input(input_shape) #\nclass_activation_output = class_activation_model(model_input) #\noutput_ = GlobalAveragePooling2D(name=\"GlobalAverageLayer\")(class_activation_output)\nmodel_output = Dense(5, activation=\"softmax\", name=output_layer_name, trainable=True)(output_)\n\nmodel = Model(inputs=[model_input,], outputs=[model_output,], name=model_name)\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Parameters for SnapMix training-loop:\n\n# parameter for beta-distribution:\nalpha = 0.20\nkfolds= 5\n\n# Training parameters:\n##pretraining\nepochs = 1 # pretraining -> learning_rate decrease and ClassActivationModel last layer training\nlearning_rate = 1e-3 # initial learning_rate\n\n## training: - make last conv layer in ResNet50 trainable first!\n#epochs = 4\n#learning_rate = 1e-4\nsnapmix_augmentation_probability = 0.6 # for algorithm with x% snapmix\n#snapmix_augmentation_probability = 0.0 # for algorithm WITHOUT snapmix\n\n# Optimizers and metrics and scheduler:\n#optimizer = keras.optimizers.SGD(learning_rate=lr,)\n#optimizer = tf.keras.optimizers.RMSprop(learning_rate=lr_schedule,) \noptimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate,)\n\ntrain_acc_metric = tf.keras.metrics.SparseCategoricalAccuracy()\nval_acc_metric = tf.keras.metrics.SparseCategoricalAccuracy()\n\n# logging activities:\n#log_batches = False\nlog_batches = False\nlog_after_n_batches = 100\n\n# minimal accuricy to save model while training:\nmin_val_acc = 0.2 # pre-training: one in five is base-line success-probability\n#min_val_acc = 0.5 # fine-tuning: set value after pre-training\nbest_model_name = \"Cassava_SnapMix_ResNet50_subm2\" # pre-training ResNet50\n\n# preprocessing function if required:\n#preprocessing_function = tf.keras.applications.efficientnet.preprocess_input # preproc for EfficientNetB3\n#preprocessing_function = None # preproc for  VGG16 \nfrom tensorflow.keras.applications.resnet50 import preprocess_input\npreprocessing_function = preprocess_input # ResNet50","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# define augmentation for non-snapmix augmented data:\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\n\ndata_augmentation = tf.keras.Sequential(\n    [\n        tf.keras.layers.experimental.preprocessing.RandomFlip(\"horizontal\"),\n        tf.keras.layers.experimental.preprocessing.RandomRotation(0.3),\n        tf.keras.layers.experimental.preprocessing.RandomZoom(0.3),\n    ]\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# steps in the training-loop:\n\n# accelerate the training by tensorflow graph building using @tf.function decorators:\n\n# !!! ... unfortunately this leads to memory leaks.... !!!\n\n#@tf.function\ndef train_step(is_augmented, optimizer, aug_image_batch, y_batch_train, label_batch2=None, box_weights1=None, box_weights2=None):\n    with tf.GradientTape() as tape:\n        y_pred =  model(aug_image_batch, training=True) \n        loss_value = snapmix_batch_loss(is_augmented, y_batch_train, y_pred, label_batch2, box_weights1, box_weights2)\n\n    grads = tape.gradient(loss_value, model.trainable_weights)\n    optimizer.apply_gradients(zip(grads, model.trainable_weights))\n    \n    # Update train_acc_metric:\n    train_acc_metric.update_state(y_batch_train, y_pred)\n    \n    return loss_value\n\n\n#@tf.function\ndef validation_step(val_dataset):\n    for x_batch_val, y_batch_val in val_dataset:\n        y_val = model(x_batch_val, training=False)\n        # Update val_acc_metric:\n        val_acc_metric.update_state(y_batch_val, y_val)\n        \n\ndef save_best_model(val_acc, model_name, min_val_acc=0):\n    if val_acc > min_val_acc + 0.01: # save if model improved 1 percent\n        min_val_acc = val_acc\n        model.save(\"./\"+ model_name)\n        print(\"Model saved to {}\".format(model_name))\n    \n    return min_val_acc\n\n\ndef reduceLROnPlateau(learning_rate):\n    learning_rate *= 0.1\n    return tf.keras.optimizers.Adam(learning_rate=learning_rate,), learning_rate\n\n\ndef kFold(data_df, fold, k=1):\n    fold_length = data_df.shape[0]//k\n    val_df = data_df[fold * fold_length: (fold+1) * fold_length]\n    train_df = pd.concat([data_df[:fold*fold_length], data_df[(fold+1)*fold_length:]], axis=0)\n        \n    return train_df, val_df\n\n\ndef find_batch_size(number_of_samples, min_batch_size, max_batch_size):\n    \"\"\"\n    Finds the smalest batch_size between a min and a max batch-size dividing a number of samples\n    without remainder (if possible). If the returned rest is not zero, no batch-size within\n    the bounds could be found.\n    Example: find_batch_size(number, min, number) finds a batch size in any case.\n\n    :param number_of_samples: number of samples to be divided into batches\n    :param min_batch_size: minimal desired number of samples in one batch\n    :param max_batch_size: maximal desired number of samples in one batch\n    :return: batch_size, steps (number of batch-iterations), rest (if not zero, no batch_size could be found)\n    \"\"\"\n    batch_size = min_batch_size\n    rest = number_of_samples % batch_size\n    while rest != 0 and batch_size <= max_batch_size:\n        batch_size += 1\n        rest = number_of_samples % batch_size\n\n    steps = number_of_samples / batch_size\n    return batch_size, steps, rest","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# SnapMix training-loop: #---from here #---to here\n# Code For Kaggle Submission: move to separate cell after submission\n\n#---from here\nimport time\n\n# collect training-/ evaluation-results:\nfold_val_accuracies = []\nval_accuracies = []\nbatch_losses = []\n\nrng = np.random.default_rng()\n\nfor epoch in range(epochs):\n    print(\"\\nStart of epoch %d\" % (epoch,))\n    start_time = time.time()\n    \n    #shuffle dataframe:\n    data_df = data_df.sample(frac=1)\n    \n    for fold in range(kfolds):\n        train_df, val_df = kFold(data_df, fold=fold, k=kfolds)\n        # Instantiate the data generators:\n        data_train = DataSequence(df=train_df,\n                                  image_path = train_directory,                          \n                                  img_size=[image_width, image_height],\n                                  batch_size=batch_size,\n                                  preprocessing_function=preprocessing_function,)\n        data_val = DataSequence(df=val_df, \n                                image_path=train_directory,\n                                img_size=[image_width, image_height],\n                                batch_size=batch_size, \n                                preprocessing_function=preprocessing_function,)\n    \n        # Iterate over the batches of the dataset:\n        for step, (x_batch_train, y_batch_train) in enumerate(data_train):\n            r = rng.uniform()\n            if r < snapmix_augmentation_probability:\n                is_augmented = True\n                aug_image_batch, label_batch2, box_weights1, box_weights2 = snapmix_batch_augmentation(\n                    class_activation_model = class_activation_model,\n                    model = model,\n                    img_batch= x_batch_train, \n                    label_batch= y_batch_train,\n                    output_layer_name = output_layer_name, \n                    alpha = alpha)\n            else:\n                label_batch2, box_weights1, box_weights2 = None, None, None\n                is_augmented = False\n                r2 = rng.uniform()\n                if r2 < 0.5: # augment half of the time\n                    aug_image_batch = data_augmentation(x_batch_train)\n                else:\n                    aug_image_batch = x_batch_train\n\n\n            # TODO: as a @tf.function this causes a memory leak...:\n            loss_value = train_step(is_augmented, optimizer, aug_image_batch, y_batch_train, \n                                    label_batch2, box_weights1, box_weights2)                        \n            if log_batches:\n                # Log every log_after_n_batches batches.\n                    if step % log_after_n_batches == 0:\n                        print(\"Samples seen : %d samples\" % ((step + 1) * batch_size))\n                        print(\"Epoch: {0}, Fold: {1}\".format(epoch, fold))\n                        print(\"Training loss (for one batch) at step %d: %.4f\" % (step, float(loss_value)))\n                        # Display metrics at the end of each batch cycle:\n                        print(\"Training acc over batch-cycle: %.4f\" % (float(train_acc_metric.result()),))\n        \n    \n        # Run a validation loop at the end of each fold:\n        validation_step(data_val) # TODO: as a @tf.function this causes a memory leak...      \n        fold_val_acc = val_acc_metric.result()\n        fold_val_accuracies.append(fold_val_acc)\n        val_acc_metric.reset_states()\n    \n    optimizer, learning_rate = reduceLROnPlateau(learning_rate)\n    \n    # calculate average accuracy over the epochs:\n    epoch_val_accuracy = np.mean(fold_val_accuracies)\n    # Display metrics at the end of each epoch:\n    print(\"Epoch: {}\".format(epoch))\n    print(\"Training acc over epoch: %.4f\" % (float(train_acc_metric.result())))\n    print(\"Evaluation acc over epoch: %.4f\" % (float(epoch_val_accuracy)))\n    # Reset training metrics at the end of each epoch:\n    train_acc_metric.reset_states()\n    min_val_acc = save_best_model(epoch_val_accuracy, best_model_name, min_val_acc= min_val_acc)\n    print(\"Time taken: %.2fs\" % (time.time() - start_time))\n\n#---to here\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Code For Kaggle Submission:\n\n# load best performing model:\nmodel = keras.models.load_model(\"./\"+ best_model_name)\n\n# load data from test directory, predict and write csv-file for submission:\nfrom tensorflow.keras.preprocessing.image import load_img\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\n\n\nimage_width = 256\nimage_height = 256\nbatch_size = 32\n\nbase_path = Path('../input/cassava-leaf-disease-classification')\ntest_directory = os.path.join(base_path,'test_images')\nsample_submission_df = pd.read_csv(os.path.join(base_path, \"sample_submission.csv\"))\n\ntest_generator_factory = ImageDataGenerator(rescale=1./255)\n\ntest_data_generator = test_generator_factory.flow_from_dataframe(\n    dataframe=sample_submission_df,\n    directory=test_directory,\n    x_col='image_id',\n    seed=42,\n    target_size = (image_width, image_height),\n    class_mode=None,\n    interpolation='bilinear',\n    shuffle=True,\n    batch_size=batch_size,    \n)\n\nstep_size_test=find_batch_size(test_data_generator.n, 1, test_data_generator.batch_size)[1]\n\ntest_data_generator.reset()\n\n# the following needs to be fixed: DataSequence relies on having images=df[0] AND labels=df[1]\n# which is not the case for test data - though in the sample_submission.csv ... blablabla\n#test_dataset = DataSequence(df=sample_submission_df,\n#                            image_path = test_directory,                          \n#                            img_size=[image_width, image_height],\n#                            batch_size=batch_size,\n#                            preprocessing_function=preprocessing_function,)\n\npredictions_=model.predict(\n    test_data_generator,\n    steps=step_size_test,\n    verbose=1)\n\npredictions=np.argmax(predictions_,axis=1)\nimage_ids=test_data_generator.filenames\n#image_ids = test_dataset.im_list # code, when DataSequence is used\n\nsubmission_df=pd.DataFrame({\"image_id\":image_ids, \"label\":predictions})\nsubmission_df.to_csv(\"submission.csv\",index=False)","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}