{"cells":[{"metadata":{"_uuid":"4d369c8b-04ee-4997-8d7a-3d254627a17e","_cell_guid":"943d0649-8aed-4db1-9950-9bbfb9960695","trusted":true},"cell_type":"code","source":"import numpy as np\nimport tensorflow as tf\nfrom tensorflow import keras\nimport pandas as pd\nimport os\nimport random\nimport matplotlib.pyplot as plt\nimport cv2\nimport imgaug as ia\nfrom imgaug import augmenters as iaa","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# SEED EVERYTHING\nnp.random.seed(123)\nrandom.seed(123)\ntf.random.set_seed(1234)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8e4847d8-7f04-447a-9ae2-eca25eb5f25a","_cell_guid":"7e5bcc90-963b-424c-ba29-64922759f3e4","trusted":true},"cell_type":"markdown","source":"# Check the content"},{"metadata":{"_uuid":"49d8bcca-05bd-48b3-b125-32b3091fb2a8","_cell_guid":"07942267-af45-4450-b732-49f7ff9b3af3","trusted":true},"cell_type":"code","source":"ROOT_PATH = '../input/cassava-leaf-disease-classification'\ndf = pd.read_csv(os.path.join(ROOT_PATH, 'train.csv'))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"79bb8c12-83bf-442c-bdce-c23d6e705ee8","_cell_guid":"1d6dc157-0705-4e0d-9c09-c8617dad5bcd","trusted":true},"cell_type":"code","source":"images_paths = '../input/cassava-leaf-disease-classification/train_images'\ndf['path'] = [os.path.join(images_paths, id) for id in df['image_id']]\n# df['label'] = list(map(str, df['label']))\ndf","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df.describe()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Split `df` into training and validation sets**"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_size = round(df.index.stop * 0.8)\n\ntrain_df = df[:train_size]\nvalid_df = df[train_size:]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.describe()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"valid_df.describe()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d83b5ac3-8322-4863-a552-58b94742f001","_cell_guid":"a62d4207-2f22-440b-a876-b9c9e909659b","trusted":true},"cell_type":"code","source":"img = plt.imread(train_df['path'][np.random.randint(len(train_df['path']))])\nplt.imshow(img)\nprint('Input shape:', img.shape)\nprint('Number of classes:', len(set(train_df['label'])))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"97770f8e-76be-430f-a708-466476d558e8","_cell_guid":"fdc44f6e-4364-4ea2-9995-f86f95f7d91d","trusted":true},"cell_type":"markdown","source":"# Create data generator"},{"metadata":{"trusted":true},"cell_type":"code","source":"INPUT_SIZE = (224, 224)\nINPUT_SHAPE = (224, 224, 3)\nBATCH_SHAPE = (None, 224, 224, 3)\nBATCH_SIZE = 128\nN_EPOCHS = 32\nN_CLASSES = 5","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class DataGeneratorFromDataframe(tf.keras.utils.Sequence):\n    def __init__(self, dataframe, x_col, y_col, \n                 dim=(128, 128, 3), \n                 batch_size=32, \n                 num_classes=None, \n                 shuffle=True, \n                 augment=True):\n        self.dim = dim\n        self.batch_size = batch_size\n        self.df = dataframe\n        self.indices = self.df.index.tolist()\n        self.num_classes = num_classes\n        self.shuffle = shuffle\n        self.augment = augment\n        self.x_col = x_col\n        self.y_col = y_col\n        self.on_epoch_end()\n\n    def __len__(self):\n        return len(self.indices) // self.batch_size\n\n    def __getitem__(self, index):\n        index = self.index[index * self.batch_size:(index + 1) * self.batch_size]\n        batch = [self.indices[k] for k in index]\n        \n        X, y = self.__get_data(batch)\n        return X, y\n\n    def on_epoch_end(self):\n        self.index = np.arange(len(self.indices))\n        if self.shuffle:\n            np.random.shuffle(self.index)\n\n    def __get_data(self, batch):\n        X = np.empty((self.batch_size, *self.dim))\n        y = []\n        \n        for i, id in enumerate(batch):\n            # Input size: (600, 800, 3)\n            # ---> crop and resize to INPUT_SIZE\n            src = cv2.imread(self.df[self.x_col][id])[:, 100:700, :]\n            X[i] = cv2.resize(src, INPUT_SIZE, interpolation=cv2.INTER_AREA)\n            \n            y.append(self.df[self.y_col][id])\n\n        X = X.astype(np.float32)\n        if self.augment:\n            X = self.augmentor(X)\n        X = X / 255.0\n        return X, keras.utils.to_categorical(y, self.num_classes)\n        \n    def augmentor(self, images):\n        'Apply data augmentation'\n        seq = iaa.Sequential([\n            iaa.Fliplr(0.5),\n            iaa.Crop(percent=(0, 0.1)),\n            iaa.Sometimes(\n                0.5,\n                iaa.GaussianBlur(sigma=(0, 0.5))\n            ),\n            iaa.LinearContrast((0.75, 1.5)),\n            iaa.AdditiveGaussianNoise(loc=0, scale=(0.0, 0.05*255), per_channel=0.5),\n            iaa.Multiply((0.8, 1.2), per_channel=0.2)\n        ], random_order=True)\n\n        return seq(images=images)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"f69b2362-703c-4cba-ae0e-d70ad2986fe1","_cell_guid":"c8d67e15-f315-4423-85ba-2dde30db8143","trusted":true},"cell_type":"code","source":"train_generator = DataGeneratorFromDataframe(train_df, \n                                             'path', \n                                             'label', \n                                             dim=INPUT_SHAPE, \n                                             batch_size=BATCH_SIZE, \n                                             num_classes=N_CLASSES)\n\nvalid_generator = DataGeneratorFromDataframe(valid_df, \n                                             'path', \n                                             'label', \n                                             dim=INPUT_SHAPE, \n                                             batch_size=BATCH_SIZE, \n                                             num_classes=N_CLASSES)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8cd3cecd-2984-4a61-9bdb-f76125b29277","_cell_guid":"30498900-873e-46c8-bc85-9e094fcb75db","trusted":true},"cell_type":"markdown","source":"# Construct model"},{"metadata":{"_uuid":"d9bcfe9f-7b73-4236-b679-048525716c24","_cell_guid":"432a3aff-7209-410a-92ea-fe549a7078b0","trusted":true},"cell_type":"code","source":"from keras import Input, Model\nfrom keras.layers import Conv2D, DepthwiseConv2D, SeparableConv2D, GlobalAveragePooling2D, \\\n                        MaxPooling2D, BatchNormalization, Activation, Add, Flatten, Dense\nfrom keras.callbacks import ReduceLROnPlateau, LearningRateScheduler, ModelCheckpoint\nfrom keras.losses import CategoricalCrossentropy","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"f7e892f4-25f3-4f2e-b832-ba2ecfbbe8d4","_cell_guid":"1a453487-edc8-4bab-97fb-1c262bf9ad5c","trusted":true},"cell_type":"code","source":"class StemBlock(Model):\n    def __init__(self, f, ksize):\n        super(StemBlock, self).__init__()\n        self.conv = Conv2D(filters=f, kernel_size=ksize, padding='same', kernel_initializer='he_normal')\n        self.bn = BatchNormalization()        \n        self.act = Activation(tf.nn.relu)\n        \n    def call(self, input_tensor):\n        x = self.conv(input_tensor)\n        x = self.bn(x)\n        x = self.act(x)\n\n        return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class Module1(Model):\n    def __init__(self, f, ksize):\n        super(Module1, self).__init__()\n        self.sconv = SeparableConv2D(f, kernel_size=ksize, padding='same', \n                                     kernel_initializer='he_normal',\n                                     activation='relu')\n        self.conv = Conv2D(f, ksize, padding='same', kernel_initializer='he_normal')\n        self.bn = BatchNormalization()\n        self.act = Activation(tf.nn.relu)\n        \n    def call(self, input_tensor):\n        x = self.sconv(input_tensor)\n        x = self.conv(x)\n        x = self.bn(x)\n        x = self.act(x)\n\n        return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class Module2(Model):\n    def __init__(self, f):\n        super(Module2, self).__init__()\n        self.conv1 = Conv2D(f, 1, kernel_initializer='he_normal', activation='relu')\n        self.conv2 = Conv2D(f, 3, padding='same', kernel_initializer='he_normal', activation='relu')\n        self.conv3 = Conv2D(f, 5, padding='same', kernel_initializer='he_normal', activation='relu')\n        self.add = Add()\n        self.bn = BatchNormalization()\n        self.act = Activation(tf.nn.relu)\n        self.conv = Conv2D(f, 5, padding='same', kernel_initializer='he_normal', activation='relu')\n\n    def call(self, input_tensor):\n        x1 = self.conv1(input_tensor)\n        x2 = self.conv2(input_tensor)\n        x3 = self.conv3(input_tensor)\n        x_add = self.add([x1, x2, x3])\n        x = self.bn(x_add)\n        x = self.act(x)\n        x = self.conv(x)\n        return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class FinalBlock(Model):\n    def __init__(self, f, n_classes, ksize, s):\n        super(FinalBlock, self).__init__()\n        self.conv = Conv2D(f, ksize, s, padding='same', kernel_initializer='he_normal')\n        self.bn = BatchNormalization()\n        self.act = Activation(tf.nn.relu)\n        self.gap = GlobalAveragePooling2D()\n        self.flatten = Flatten()\n        self.dense1 = Dense(f//4, activation='relu', kernel_initializer='he_normal')\n        self.classifier = Dense(n_classes, activation='softmax', kernel_initializer='he_normal')\n    \n    def call(self, input_tensor):\n        x = self.conv(input_tensor)\n        x = self.bn(x)\n        x = self.act(x)\n        x = self.gap(x)\n        x = self.flatten(x)\n        x = self.dense1(x)\n        x_final = self.classifier(x)\n\n        return x_final","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class MyModel(Model):\n    def __init__(self, shape, n_classes=5):\n        super(MyModel, self).__init__()\n        # Block 1\n        self.stem = StemBlock(16, 5)\n        self.m2_1 = Module2(32)\n\n        self.conv1 = Conv2D(64, 5, 2, padding='same', activation='relu', name='contract1')\n        \n        # Block 2\n        self.m2_2 = Module2(64)\n        self.add_1 = Add()\n\n        self.conv2 = Conv2D(128, 5, 2, padding='same', activation='relu', name='contract2')\n        \n        # Block 3\n        self.m2_3 = Module2(128)\n        self.m1_1 = Module1(128, 5)\n        self.add_2 = Add()\n\n        self.conv3 = Conv2D(256, 5, 2, padding='same', activation='relu', name='contract3')\n        \n        # Block 4\n        self.m2_4 = Module2(256)\n        self.m1_2 = Module1(256, 5)\n        self.add_3 = Add()\n\n        self.conv4 = Conv2D(512, 3, 2, padding='same', activation='relu', name='contract4')\n\n        # Final block\n        self.final = FinalBlock(512, n_classes, 3, 2)\n    \n    def call(self, input_tensor):\n        x = self.stem(input_tensor)\n        x = self.m2_1(x)\n\n        x = self.conv1(x)\n        x_res1 = x\n        x = self.m2_2(x)\n        x = self.add_1([x, x_res1])\n\n        x = self.conv2(x)\n        x_res2 = x\n        x = self.m2_3(x)\n        x  = self.m1_1(x)\n        x = self.add_2([x, x_res2])\n\n        x = self.conv3(x)\n        x_res3 = x\n        x = self.m2_4(x)\n        x = self.m1_2(x)\n        x = self.add_3([x, x_res3])\n\n        x = self.conv4(x)\n        x_final = self.final(x)\n        \n        return x_final","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = MyModel(shape=INPUT_SHAPE)\nmodel.compile(optimizer='adam', loss=keras.losses.CategoricalCrossentropy(label_smoothing=0.2), metrics=['accuracy'])\nmodel.build(BATCH_SHAPE)\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def lrfunc(epoch, lr):\n    if epoch < 10:\n        return lr\n    else:\n        return lr * tf.math.exp(-0.1)\n\nscheduler = LearningRateScheduler(lrfunc)\n\nreduceLR = ReduceLROnPlateau(monitor='val_loss',\n                             factor=0.2,\n                             min_lr=0.0005)\n\ncheckpoint = ModelCheckpoint(\n    \"cassava_best_model.h5\",\n    save_best_only=True,\n    monitor='val_loss',\n    mode='min',\n)\n\n\nhistory = model.fit(train_generator, \n                    epochs=N_EPOCHS, \n                    validation_data=valid_generator, \n                    callbacks=[checkpoint, scheduler, reduceLR])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, axes = plt.subplots(2, 1, figsize=(10, 10))\n\naxes[0].set_title('Loss and Accuracy')\naxes[0].plot(history.history['loss'], '-g', label='Train losses')\naxes[0].plot(history.history['val_loss'], ':r', label='Valid losses')\naxes[0].legend()\naxes[1].plot(history.history['accuracy'], '-b', label='Train accuracy')\naxes[1].plot(history.history['val_accuracy'], ':k', label='Valid accuracy')\naxes[1].legend()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Test model"},{"metadata":{"trusted":true},"cell_type":"code","source":"submit = pd.read_csv(os.path.join(ROOT_PATH, 'sample_submission.csv'))\nsubmit","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_img = plt.imread(os.path.join(ROOT_PATH, 'test_images', submit['image_id'].values[0]))\ntest_img = test_img[:, 100:700, :]\ntest_img = cv2.resize(test_img, INPUT_SIZE, interpolation=cv2.INTER_AREA)\ntest_img = test_img.astype(np.float32) / 255.0\n\nplt.imshow(test_img)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"pred = np.argmax(model.predict(np.array([test_img])))\nsubmit['label'] = pred\nsubmit.to_csv('submission.csv')\nsubmit","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}