{"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":"# Introduction\n\nAs the second-largest provider of carbohydrates in Africa, cassava is a key food security crop grown by smallholder farmers because it can withstand harsh conditions. At least 80% of household farms in Sub-Saharan Africa grow this starchy root, but viral diseases are major sources of poor yields. With the help of data science, it may be possible to identify common diseases so they can be treated. Our dataset consists of 21,367 labeled images collected during a regular survey in Uganda. Our goal is to classify each cassava image into four disease categories or a fifth category indicating a healthy leaf. With our help, farmers may be able to quickly identify diseased plants, potentially saving their crops before they inflict irreparable damage.","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","papermill":{"duration":0.039382,"end_time":"2020-11-19T21:45:23.042097","exception":false,"start_time":"2020-11-19T21:45:23.002715","status":"completed"},"tags":[]}},{"cell_type":"code","source":"#shutil.rmtree(\"/kaggle/working/train_data\")\n#shutil.rmtree(\"/kaggle/working/valid_data\")\n#os.remove(\"/kaggle/working/0\")\n#os.remove(\"/kaggle/working/2\")\n#os.remove(\"/kaggle/working/3\")\n#os.remove(\"/kaggle/working/4\")\n#os.remove(\"/kaggle/working/1\")","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Set up environment\n## Load libraries","metadata":{}},{"cell_type":"code","source":"# Imports\nimport pandas as pd \nimport os \nimport numpy as np \nimport shutil \nimport math, re, os\nimport matplotlib.pyplot as plt\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom functools import partial\nimport tensorflow as tf\nprint(\"Tensorflow version \" + tf.__version__)\nimport os, warnings\nimport matplotlib.pyplot as plt\nfrom matplotlib import gridspec\nfrom tensorflow.keras.preprocessing import image_dataset_from_directory\nfrom kaggle_datasets import KaggleDatasets\nfrom sklearn.model_selection import train_test_split\n#pip install --upgrade tensorflow\n\nimport numpy as np\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\n\n# Set Matplotlib defaults\nplt.rc('figure', autolayout=True)\nplt.rc('axes', labelweight='bold', labelsize='large',\n       titleweight='bold', titlesize=18, titlepad=10)\nplt.rc('image', cmap='magma')\nwarnings.filterwarnings(\"ignore\") # to clean up output cells","metadata":{"papermill":{"duration":6.890298,"end_time":"2020-11-19T21:45:30.119979","exception":false,"start_time":"2020-11-19T21:45:23.229681","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2021-12-15T09:32:32.237753Z","iopub.execute_input":"2021-12-15T09:32:32.238593Z","iopub.status.idle":"2021-12-15T09:32:32.249377Z","shell.execute_reply.started":"2021-12-15T09:32:32.238544Z","shell.execute_reply":"2021-12-15T09:32:32.248681Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Set up variables\nWe'll set up some of our variables for our notebook here.\n\n* Use `KaggleDatasets().get_gcs_path()` to retrieve public GCS paths from a public Kaggle dataset.\n* `BATCH_SIZE`: The amount of data included in each sub-epoch weight change.\n* `IMAGE_SIZE`: Dimensions of the images in the dataset in pixels.\n* `CLASSES`: Four disease categories and a fifth category indicating a healthy leaf.\n* `EPOCHS`: The number of times the whole training dataset is passed through the model.","metadata":{"papermill":{"duration":0.038122,"end_time":"2020-11-19T21:45:34.458722","exception":false,"start_time":"2020-11-19T21:45:34.4206","status":"completed"},"tags":[]}},{"cell_type":"code","source":"GCS_PATH = KaggleDatasets().get_gcs_path()\nBATCH_SIZE = 48  #1024\nIMAGE_SIZE = [512, 512]\nCLASSES = ['0', '1', '2', '3', '4']\nEPOCHS = 25","metadata":{"papermill":{"duration":145.219568,"end_time":"2020-11-19T21:47:59.715925","exception":false,"start_time":"2020-11-19T21:45:34.496357","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2021-12-15T09:33:10.101389Z","iopub.execute_input":"2021-12-15T09:33:10.101708Z","iopub.status.idle":"2021-12-15T09:33:10.601589Z","shell.execute_reply.started":"2021-12-15T09:33:10.101662Z","shell.execute_reply":"2021-12-15T09:33:10.600798Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Create output folders for each of the 5 classes","metadata":{}},{"cell_type":"code","source":"# Creat training and validation folder\nos.mkdir('/kaggle/working/train_data/')\nos.mkdir('/kaggle/working/valid_data/')\n\n# Open dataset file \ndataset = pd.read_csv(\"../input/cassava-leaf-disease-classification/train.csv\")\n\n# Split training images in training and validation images\ntraining_data, validation_data = train_test_split(dataset, test_size=0.33)\n\ntraining_file_names = list(training_data['image_id'].values) \ntraining_img_labels = list(training_data['label'].values) \nvalidation_file_names = list(validation_data['image_id'].values) \nvalidation_img_labels = list(validation_data['label'].values) \n\n# Create folders of labels\nfolders_to_be_created = np.unique(list(dataset['label'])) #.values \nsource = \"../input/cassava-leaf-disease-classification/train_images\"\ntraining_destination = '/kaggle/working/train_data'\nvalidation_destination = '/kaggle/working/valid_data'\n\nfor new_path in folders_to_be_created: \n    if not os.path.exists(\".//\" + str(new_path)):\n        train_map = os.path.join('/kaggle/working/train_data/', str(new_path))\n        valid_map = os.path.join('/kaggle/working/valid_data/', str(new_path))\n        os.makedirs(train_map)\n        os.makedirs(valid_map)\n        \nfolders = folders_to_be_created.copy() \n\nfor f in range(len(training_file_names)): \n    tr_current_img = training_file_names[f] \n    tr_current_label = training_img_labels[f] \n    src = os.path.join(source, tr_current_img)\n    dst = os.path.join(training_destination, str(tr_current_label))\n    os.path.exists(dst)\n    shutil.copy(src, dst)\n    \nfor f in range(len(validation_file_names)): \n    va_current_img = validation_file_names[f] \n    va_current_label = validation_img_labels[f] \n    src = os.path.join(source, va_current_img)\n    dst = os.path.join(validation_destination, str(va_current_label))\n    os.path.exists(dst)\n    shutil.copy(src, dst)","metadata":{"execution":{"iopub.status.busy":"2021-12-15T09:33:12.677365Z","iopub.execute_input":"2021-12-15T09:33:12.677691Z","iopub.status.idle":"2021-12-15T09:34:52.133284Z","shell.execute_reply.started":"2021-12-15T09:33:12.677652Z","shell.execute_reply":"2021-12-15T09:34:52.132309Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Load the data","metadata":{}},{"cell_type":"code","source":"TEST_FILENAMES = tf.io.gfile.glob(GCS_PATH + '/test_tfrecords/ld_test*.tfrec')\n\n# Load training data\nTRAIN_PATH = \"/kaggle/working/train_data\"\n\nds_train_ = image_dataset_from_directory(\n    TRAIN_PATH,\n    labels='inferred',\n    label_mode='int',\n    image_size=[512, 512],\n    interpolation='nearest',\n    batch_size=1024,\n    shuffle=True,\n)\n\n# Load validation data\nVALID_PATH = \"/kaggle/working/valid_data\"\n\nds_valid_ = image_dataset_from_directory(\n    VALID_PATH,\n    labels='inferred',\n    label_mode='int',\n    image_size=[512, 512],\n    interpolation='nearest',\n    batch_size=1024,\n    shuffle=True,\n)\n\n# Data Pipeline\ndef convert_to_float(image, label):\n    image = tf.image.convert_image_dtype(image, dtype=tf.float32)\n    return image, label\n\nAUTOTUNE = tf.data.experimental.AUTOTUNE\nds_train = (\n    ds_train_\n    .map(convert_to_float)\n    .cache()\n    .prefetch(buffer_size=AUTOTUNE)\n)\n\nds_valid = (\n    ds_valid_\n    .map(convert_to_float)\n    .cache()\n    .prefetch(buffer_size=AUTOTUNE)\n)","metadata":{"execution":{"iopub.status.busy":"2021-12-15T09:20:57.087823Z","iopub.execute_input":"2021-12-15T09:20:57.088895Z","iopub.status.idle":"2021-12-15T09:20:58.385081Z","shell.execute_reply.started":"2021-12-15T09:20:57.088841Z","shell.execute_reply":"2021-12-15T09:20:58.384433Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Brief exploratory data analysis (EDA)","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":"# Building the model\n\nWe are using `sparse_categorical_crossentropy` as our loss function, because we did _not_ one-hot encode our labels. The four disease categories and the fifth category indicating a healthy leaf are mutually exclusive (e.g. each image belongs to one of the classes).","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":"## Our own model","metadata":{}},{"cell_type":"code","source":"type(ds_train)","metadata":{"execution":{"iopub.status.busy":"2021-12-15T09:35:47.607913Z","iopub.execute_input":"2021-12-15T09:35:47.608542Z","iopub.status.idle":"2021-12-15T09:35:47.614608Z","shell.execute_reply.started":"2021-12-15T09:35:47.608494Z","shell.execute_reply":"2021-12-15T09:35:47.613709Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(ds_train)","metadata":{"execution":{"iopub.status.busy":"2021-12-15T09:36:27.333578Z","iopub.execute_input":"2021-12-15T09:36:27.333879Z","iopub.status.idle":"2021-12-15T09:36:27.341966Z","shell.execute_reply.started":"2021-12-15T09:36:27.333846Z","shell.execute_reply":"2021-12-15T09:36:27.341080Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#tfds.as_dataframe(ds_train)","metadata":{"execution":{"iopub.status.busy":"2021-12-15T10:18:08.883839Z","iopub.execute_input":"2021-12-15T10:18:08.884589Z","iopub.status.idle":"2021-12-15T10:18:08.912245Z","shell.execute_reply.started":"2021-12-15T10:18:08.884535Z","shell.execute_reply":"2021-12-15T10:18:08.911044Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tf.data.Dataset.from_tensor_slices(list(ds_train))","metadata":{"execution":{"iopub.status.busy":"2021-12-15T09:59:17.844149Z","iopub.execute_input":"2021-12-15T09:59:17.845051Z","iopub.status.idle":"2021-12-15T09:59:17.998798Z","shell.execute_reply.started":"2021-12-15T09:59:17.845003Z","shell.execute_reply":"2021-12-15T09:59:17.995975Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = keras.Sequential([\n    layers.InputLayer(input_shape=[512, 512, 3]),\n    \n    # Data Augmentation\n    # ____,\n    #preprocessing.RandomContrast(factor=0.1),\n    #preprocessing.RandomFlip(mode='horizontal'), \n    #preprocessing.RandomRotation(factor=0.1),\n\n    # Block One\n    layers.BatchNormalization(renorm=True),\n    layers.Conv2D(filters=32, kernel_size=3, activation='relu', padding='same'),\n    layers.MaxPool2D(),\n\n    # Block Two\n    #layers.BatchNormalization(renorm=True),\n    #layers.Conv2D(filters=64, kernel_size=3, activation='relu', padding='same'),\n    #layers.MaxPool2D(),\n\n    # Block Three\n    #layers.BatchNormalization(renorm=True),\n    #layers.Conv2D(filters=256, kernel_size=3, activation='relu', padding='same'),\n    #layers.Conv2D(filters=256, kernel_size=3, activation='relu', padding='same'),\n    #layers.MaxPool2D(),\n\n    # Head\n    layers.BatchNormalization(renorm=True),\n    layers.Flatten(),\n    layers.Dense(8, activation='relu'),\n    layers.Dense(len(CLASSES), activation='softmax'),\n])\n\nmodel.compile(\n    optimizer='adam',\n    loss='sparse_categorical_crossentropy',\n    metrics=['sparse_categorical_accuracy'], \n)\n\nhistory = model.fit(\n    ds_train,\n    validation_data=ds_valid,\n    epochs=30,\n    verbose=0,\n)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# custom filter\ndef my_filter(shape, dtype=None):\n\n    f = np.array([\n            [[[1]], [[0]], [[-1]]],\n            [[[1]], [[0]], [[-1]]],\n            [[[1]], [[0]], [[-1]]]\n        ])\n    assert f.shape == shape\n    return keras.backend.variable(f, dtype='float32')\n\n\nmodel = keras.Sequential([\n    layers.InputLayer(input_shape=[512, 512, 3]),\n    \n    # Data Augmentation\n    # ____,\n    #preprocessing.RandomContrast(factor=0.1),\n    #preprocessing.RandomFlip(mode='horizontal'), \n    #preprocessing.RandomRotation(factor=0.1),\n\n    # Block One\n    layers.BatchNormalization(renorm=True),\n    layers.Conv2D(filters=64, kernel_size=3, activation='relu', padding='same'),\n    layers.MaxPool2D(),\n\n    # Block Two\n    layers.BatchNormalization(renorm=True),\n    layers.Conv2D(filters=128, kernel_size=3, activation='relu', padding='same'),\n    layers.MaxPool2D(),\n\n    # Block Three\n    #layers.BatchNormalization(renorm=True),\n    #layers.Conv2D(filters=256, kernel_size=3, activation='relu', padding='same'),\n    #layers.Conv2D(filters=256, kernel_size=3, activation='relu', padding='same'),\n    #layers.MaxPool2D(),\n\n    # Head\n    layers.BatchNormalization(renorm=True),\n    layers.Flatten(),\n    layers.Dense(8, activation='relu'),\n    layers.Dense(len(CLASSES), activation='softmax'),\n])\n\nmodel.compile(\n    optimizer='adam',\n    loss='sparse_categorical_crossentropy',\n    metrics=['sparse_categorical_accuracy'], \n)\n\nhistory = model.fit(\n    ds_train,\n    validation_data=ds_valid,\n    epochs=30,\n    verbose=0,\n)","metadata":{"execution":{"iopub.status.busy":"2021-12-15T09:15:06.916640Z","iopub.execute_input":"2021-12-15T09:15:06.917139Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Code from kaggle courses\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\n\n# Define a kernel\nkernel = tf.constant([ \n    [-1, -1, -1],\n    [-1,  8, -1],\n    [-1, -1, -1],\n])\n\n# Apply convolution\nimage_filter = tf.nn.conv2d(\n    input=ds_train,\n    filters=kernel,\n    strides=1, # or (1, 1)\n    padding='SAME',\n)\n\n# Apply ReLU\nimage_detect = tf.nn.relu(image_filter)\n\n# Apply pooling\nimage_condense = tf.nn.pool(\n    input=image_detect,\n    window_shape=(2,2),\n    pooling_type='MAX',\n    strides=(2, 2),\n    padding='SAME',\n)\n\n# Compile the model to prepare for training\nmodel.compile(\n    optimizer='adam',\n    loss='sparse_categorical_crossentropy',\n    metrics=['sparse_categorical_accuracy'], \n)\n\n# All together\nmodel = keras.Sequential([\n    layers.InputLayer(input_shape=[128, 128, 3]),\n    \n    # Data Augmentation\n    # ____,\n    preprocessing.RandomContrast(factor=0.1),\n    preprocessing.RandomFlip(mode='horizontal'), \n    preprocessing.RandomRotation(factor=0.1),\n\n    # Block One\n    layers.BatchNormalization(renorm=True),\n    layers.Conv2D(filters=64, kernel_size=3, activation='relu', padding='same'),\n    layers.MaxPool2D(),\n\n    # Block Two\n    layers.BatchNormalization(renorm=True),\n    layers.Conv2D(filters=128, kernel_size=3, activation='relu', padding='same'),\n    layers.MaxPool2D(),\n\n    # Block Three\n    layers.BatchNormalization(renorm=True),\n    layers.Conv2D(filters=256, kernel_size=3, activation='relu', padding='same'),\n    layers.Conv2D(filters=256, kernel_size=3, activation='relu', padding='same'),\n    layers.MaxPool2D(),\n\n    # Head\n    layers.BatchNormalization(renorm=True),\n    layers.Flatten(),\n    layers.Dense(8, activation='relu'),\n    layers.Dense(1, activation='sigmoid'),\n])\n\nhistory = model.fit(\n    ds_train,\n    validation_data=ds_valid,\n    epochs=30,\n    verbose=0,\n)","metadata":{"execution":{"iopub.status.busy":"2021-12-14T14:50:45.449041Z","iopub.execute_input":"2021-12-14T14:50:45.449467Z","iopub.status.idle":"2021-12-14T14:50:45.546009Z","shell.execute_reply.started":"2021-12-14T14:50:45.449426Z","shell.execute_reply":"2021-12-14T14:50:45.544732Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Model with a pre-trained base (ResNet50)","metadata":{}},{"cell_type":"markdown","source":"## Model with a pre-trained base (VGG-16)","metadata":{}},{"cell_type":"markdown","source":"## Model with a pre-trained base (InceptionV3)","metadata":{}},{"cell_type":"markdown","source":"# Train the model","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":"With model.summary() we'll see a printout of each of our layers, their corresponding shape, as well as the associated number of parameters. Notice that at the bottom of the printout we'll see information on the total parameters, trainable parameters, and non-trainable parameters. Because we're using a pre-trained model, we expect there to be a large number of non-trainable parameters (because the weights have already been assigned in the pre-trained model).","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":"code","source":"model.summary()","metadata":{"papermill":{"duration":1.344562,"end_time":"2020-11-19T22:04:51.988755","exception":false,"start_time":"2020-11-19T22:04:50.644193","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2021-12-14T16:00:35.350514Z","iopub.execute_input":"2021-12-14T16:00:35.351505Z","iopub.status.idle":"2021-12-14T16:00:35.366984Z","shell.execute_reply.started":"2021-12-14T16:00:35.351457Z","shell.execute_reply":"2021-12-14T16:00:35.365852Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Evaluating our model","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":"code","source":"# print out variables available to us\nprint(history.history.keys())","metadata":{"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":[],"execution":{"iopub.status.busy":"2021-12-14T16:00:27.955668Z","iopub.execute_input":"2021-12-14T16:00:27.956037Z","iopub.status.idle":"2021-12-14T16:00:28.021134Z","shell.execute_reply.started":"2021-12-14T16:00:27.955916Z","shell.execute_reply":"2021-12-14T16:00:28.020235Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# create learning curves to evaluate model performance\nhistory_frame = pd.DataFrame(history.history)\nhistory_frame.loc[:, ['loss', 'val_loss']].plot()\nhistory_frame.loc[:, ['sparse_categorical_accuracy', 'val_sparse_categorical_accuracy']].plot();","metadata":{"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":[],"execution":{"iopub.status.busy":"2021-12-14T16:00:30.722419Z","iopub.execute_input":"2021-12-14T16:00:30.723776Z","iopub.status.idle":"2021-12-14T16:00:30.740448Z","shell.execute_reply.started":"2021-12-14T16:00:30.723727Z","shell.execute_reply":"2021-12-14T16:00:30.739354Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Making predictions\nNow that we've trained our model we can use it to make predictions! ","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":"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","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":[]},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_ds = get_test_dataset(ordered=True) \ntest_ds = test_ds.map(to_float32)\n\nprint('Computing predictions...')\ntest_images_ds = testing_dataset\ntest_images_ds = test_ds.map(lambda image, idnum: image)\nprobabilities = model.predict(test_images_ds)\npredictions = np.argmax(probabilities, axis=-1)\nprint(predictions)","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":[]},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Creating a submission file","metadata":{"papermill":{"duration":1.271799,"end_time":"2020-11-19T22:05:24.759257","exception":false,"start_time":"2020-11-19T22:05:23.487458","status":"completed"},"tags":[]}},{"cell_type":"code","source":"print('Generating submission.csv file...')\ntest_ids_ds = test_ds.map(lambda image, idnum: idnum).unbatch()\ntest_ids = next(iter(test_ids_ds.batch(NUM_TEST_IMAGES))).numpy().astype('U') # all in one batch\nnp.savetxt('submission.csv', np.rec.fromarrays([test_ids, predictions]), fmt=['%s', '%d'], delimiter=',', header='id,label', comments='')\n!head submission.csv","metadata":{"execution":{"iopub.execute_input":"2020-11-19T22:05:27.316025Z","iopub.status.busy":"2020-11-19T22:05:27.315202Z","iopub.status.idle":"2020-11-19T22:05:28.241598Z","shell.execute_reply":"2020-11-19T22:05:28.24078Z"},"papermill":{"duration":2.185537,"end_time":"2020-11-19T22:05:28.241723","exception":false,"start_time":"2020-11-19T22:05:26.056186","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]}]}