{"cells":[{"metadata":{"id":"2jjq4r65WPpV"},"cell_type":"markdown","source":"Simple custom ResNet with Colab."},{"metadata":{"id":"Pcl05IKKSU6a"},"cell_type":"markdown","source":"Import Python modules."},{"metadata":{"id":"96ys6SFQSbXy","executionInfo":{"status":"ok","timestamp":1600067929847,"user_tz":-300,"elapsed":820,"user":{"displayName":"Zeon","photoUrl":"","userId":"13928249668567761334"}},"trusted":false},"cell_type":"code","source":"from keras.layers import Dense, Conv2D, BatchNormalization, Activation, Lambda\nfrom keras.layers import AveragePooling2D, Input, Flatten, MaxPooling2D, Add, Dropout\nfrom keras.optimizers import Adam\nfrom keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nfrom keras.preprocessing.image import ImageDataGenerator\nfrom keras.regularizers import l2\nfrom keras.models import Model, load_model\nimport numpy as np\nimport pandas as pd\nimport os\nfrom sklearn.utils import shuffle","execution_count":null,"outputs":[]},{"metadata":{"id":"lbRvZhxmSmIA"},"cell_type":"markdown","source":"Create function for building custom ResNet."},{"metadata":{"id":"_KMGz-XcS2lU","executionInfo":{"status":"ok","timestamp":1600067658803,"user_tz":-300,"elapsed":928,"user":{"displayName":"Zeon","photoUrl":"","userId":"13928249668567761334"}},"trusted":false},"cell_type":"code","source":"def resnet_layer(inputs,\n                 num_filters=16,\n                 kernel_size=3,\n                 strides=1,\n                 activation='relu',\n                 batch_normalization=True,\n                 conv_first=True):\n    \"\"\"2D Convolution-Batch Normalization-Activation stack builder\n\n    # Arguments\n        inputs (tensor): input tensor from input image or previous layer\n        num_filters (int): Conv2D number of filters\n        kernel_size (int): Conv2D square kernel dimensions\n        strides (int): Conv2D square stride dimensions\n        activation (string): activation name\n        batch_normalization (bool): whether to include batch normalization\n        conv_first (bool): conv-bn-activation (True) or\n            bn-activation-conv (False)\n\n    # Returns\n        x (tensor): tensor as input to the next layer\n    \"\"\"\n    conv = Conv2D(num_filters,\n                  kernel_size=kernel_size,\n                  strides=strides,\n                  padding='same',\n                  kernel_initializer='he_normal',\n                  kernel_regularizer=l2(1e-4))\n\n    x = inputs\n    if conv_first:\n        x = conv(x)\n        if batch_normalization:\n            x = BatchNormalization()(x)\n        if activation is not None:\n            x = Activation(activation)(x)\n    else:\n        if batch_normalization:\n            x = BatchNormalization()(x)\n        if activation is not None:\n            x = Activation(activation)(x)\n        x = conv(x)\n    return x\n\n\ndef resnet_v1(input_shape, n_blocks, num_classes=10, n_stages=3,\n                avg_pooling_size=4, dropout_rate=0.2):\n    \"\"\"ResNet Version 1 Model builder [a]\n\n    Stacks of 2 x (3 x 3) Conv2D-BN-ReLU\n    Last ReLU is after the shortcut connection.\n    At the beginning of each stage, the feature map size is halved (downsampled)\n    by a convolutional layer with strides=2, while the number of filters is\n    doubled. Within each stage, the layers have the same number filters and the\n    same number of filters.\n\n    # Arguments\n        input_shape (tensor): shape of input image tensor\n        n_blocks (int): number of blocks in core convolutional layers\n        num_classes (int): number of classes\n        n_stages (int): number of stages\n        avg_pooling_size (int): size of average pooling\n        dropout_rate (float): rate of droput in final connections\n\n    # Returns\n        model (Model): Keras model instance\n    \"\"\"\n    # Computed depth from supplied model parameter n\n    depth = n_blocks * 6 + 2\n    if (depth - 2) % 6 != 0:\n        raise ValueError('depth should be 6n_blocks+2 (eg 20, 32, 44 in [a])')\n    # Start model definition.\n    num_filters = 16\n    num_res_blocks = int((depth - 2) / 6)\n\n    inputs = Input(shape=input_shape)\n    x = resnet_layer(inputs=inputs)\n    # Instantiate the stack of residual units\n    for stack in range(n_stages):\n        for res_block in range(num_res_blocks):\n            strides = 1\n            if stack > 0 and res_block == 0:  # first layer but not first stack\n                strides = 2  # downsample\n\n            y = resnet_layer(inputs=x,\n                             num_filters=num_filters,\n                             strides=strides)\n            y = resnet_layer(inputs=y,\n                             num_filters=num_filters,\n                             activation=None)\n\n            if stack > 0 and res_block == 0:  # first layer but not first stack\n                # linear projection residual shortcut connection to match\n                # changed dims\n                x = resnet_layer(inputs=x,\n                                 num_filters=num_filters,\n                                 kernel_size=1,\n                                 strides=strides,\n                                 activation=None,\n                                 batch_normalization=False)\n            # x = keras.layers.add([x, y])\n            x = Add()([x, y])\n            x = Activation('relu')(x)\n        num_filters *= 2\n\n    # Add classifier on top.\n    x = AveragePooling2D(pool_size=avg_pooling_size)(x)\n    x = Flatten()(x)\n    y = Dropout(dropout_rate)(x)\n    outputs = Dense(num_classes,\n                    activation='softmax',\n                    kernel_initializer='he_normal')(y)\n\n    # Instantiate model.\n    model = Model(inputs=inputs, outputs=outputs)\n    return model","execution_count":null,"outputs":[]},{"metadata":{"id":"b9vBByz7R30H"},"cell_type":"markdown","source":"Copy data directory on your Google Drive. Mount your Google Drive on Colab VM."},{"metadata":{"id":"_qGqloI7RjT3","executionInfo":{"status":"ok","timestamp":1600067687371,"user_tz":-300,"elapsed":25527,"user":{"displayName":"Zeon","photoUrl":"","userId":"13928249668567761334"}},"outputId":"f7851f28-f37c-4fa2-aae4-04e8d8d87b97","trusted":false},"cell_type":"code","source":"from google.colab import drive\ndrive.mount('/content/drive')","execution_count":null,"outputs":[]},{"metadata":{"id":"B_DZ3_jhXFKN"},"cell_type":"markdown","source":"Note your project path."},{"metadata":{"id":"FZBnki8jXx8t","executionInfo":{"status":"ok","timestamp":1600067691508,"user_tz":-300,"elapsed":848,"user":{"displayName":"Zeon","photoUrl":"","userId":"13928249668567761334"}},"trusted":false},"cell_type":"code","source":"# Project path\nroot_path = '/content/drive/My Drive/dog-breed-identification/'\n# root_path = './'\n\n# Pathes of the train and test images\ntrain_path = root_path + 'train/'\ntest_path = root_path + 'test/'","execution_count":null,"outputs":[]},{"metadata":{"id":"5jOsgssNYbHY"},"cell_type":"markdown","source":"Read labels.csv"},{"metadata":{"id":"9dd-VagcYduh","executionInfo":{"status":"ok","timestamp":1600067696052,"user_tz":-300,"elapsed":2103,"user":{"displayName":"Zeon","photoUrl":"","userId":"13928249668567761334"}},"outputId":"cb4dedb3-16bd-478b-fa29-b05e3e8d001d","trusted":false},"cell_type":"code","source":"labels_csv = pd.read_csv(root_path + 'labels.csv')\nlabels_csv.head()","execution_count":null,"outputs":[]},{"metadata":{"id":"O3ix0q-jYi0K"},"cell_type":"markdown","source":"Check whether number of filenames matches number of actual image files."},{"metadata":{"id":"iLbvexrfYsfi","executionInfo":{"status":"ok","timestamp":1600067756364,"user_tz":-300,"elapsed":53855,"user":{"displayName":"Zeon","photoUrl":"","userId":"13928249668567761334"}},"outputId":"4073764c-1525-4b51-9d71-7eec19d4bbee","trusted":false},"cell_type":"code","source":"if len(os.listdir(train_path)) == len(labels_csv):\n    print('Filenames match actual amount of files!')\nelse:\n    print('Filenames do not match actual amount of files, check the target directory.')","execution_count":null,"outputs":[]},{"metadata":{"id":"_12GMztjY0Oz"},"cell_type":"markdown","source":"Create DataFrame whis image file extension."},{"metadata":{"id":"RpinNNqVY5sc","executionInfo":{"status":"ok","timestamp":1600067844955,"user_tz":-300,"elapsed":881,"user":{"displayName":"Zeon","photoUrl":"","userId":"13928249668567761334"}},"outputId":"5fe9ff4b-d73a-45c8-f17f-79ba4168dd67","trusted":false},"cell_type":"code","source":"train_df = labels_csv.assign(img_path=lambda x: x['id'] +'.jpg')\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{"id":"5HCPo9G5Y_c0"},"cell_type":"markdown","source":"Choose your Model parameters."},{"metadata":{"id":"P06BBZ_YZejn","executionInfo":{"status":"ok","timestamp":1600067849486,"user_tz":-300,"elapsed":814,"user":{"displayName":"Zeon","photoUrl":"","userId":"13928249668567761334"}},"trusted":false},"cell_type":"code","source":"n_blocks = 3\nn_stages = 5\navg_pooling_size = 4\ndropout_rate = 0.2\n\n# Input image dimensions\nheight = 320\nwidth = 320\ncolor = 3\ninput_shape = (height, width, color)\n\n# Number of classes\nunique_breeds = labels_csv.breed.unique().tolist()\nnum_classes = len(unique_breeds)\n\n# Model name\nmodel_name = 'dog_breeds'","execution_count":null,"outputs":[]},{"metadata":{"id":"BEFhqoekZpD5"},"cell_type":"markdown","source":"Create new model or load trained model."},{"metadata":{"id":"t8OFKZH-ZuWJ","executionInfo":{"status":"ok","timestamp":1600067941713,"user_tz":-300,"elapsed":1737,"user":{"displayName":"Zeon","photoUrl":"","userId":"13928249668567761334"}},"outputId":"bfb68ea6-2fc1-4d07-c651-ca5aabfbb865","trusted":false},"cell_type":"code","source":"load_path = root_path + model_name + '.h5'\nif os.path.exists(load_path):\n    print(f\"loading the trained model: {load_path}\")\n    model = load_model(load_path)\nelse:\n    model = resnet_v1(input_shape=input_shape,\n                    n_blocks=n_blocks,\n                    num_classes=num_classes,\n                    n_stages=n_stages,\n                    avg_pooling_size=avg_pooling_size,\n                    dropout_rate=dropout_rate)\n\nmodel.compile(loss='categorical_crossentropy',\n            optimizer=Adam(lr=0.0001),\n            metrics=['accuracy'])\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"id":"_9jnPBNaaGPj"},"cell_type":"markdown","source":"Training parameters. Skip the next four steps if you only want make prediction on loaded model."},{"metadata":{"id":"8oH1A9JuaMx8","executionInfo":{"status":"ok","timestamp":1600067962655,"user_tz":-300,"elapsed":831,"user":{"displayName":"Zeon","photoUrl":"","userId":"13928249668567761334"}},"trusted":false},"cell_type":"code","source":"epochs = 200\nbatch_size = 32\nnum_of_train_images = int(0.8 * len(train_df))","execution_count":null,"outputs":[]},{"metadata":{"id":"pmbuwtNjaso_"},"cell_type":"markdown","source":"Create train and validation generators."},{"metadata":{"id":"QTOVR4lna0Xg","executionInfo":{"status":"ok","timestamp":1600067972569,"user_tz":-300,"elapsed":2373,"user":{"displayName":"Zeon","photoUrl":"","userId":"13928249668567761334"}},"outputId":"2f2cb5ab-72e6-41b6-895c-e9af27a60c35","trusted":false},"cell_type":"code","source":"# Shuffle training DataFrame.\ntrain_df = shuffle(train_df)\n\ntrain_datagen = ImageDataGenerator(rotation_range=30,\n                                width_shift_range=0.2,\n                                height_shift_range=0.2,\n                                rescale=1./255,\n                                shear_range=0.2,\n                                zoom_range=0.3,\n                                horizontal_flip=True,\n                                fill_mode='nearest')\n\nval_datagen = ImageDataGenerator(rescale=1./255)\n\ntrain_generator = train_datagen.flow_from_dataframe(dataframe=train_df[:num_of_train_images],\n                                                    shuffle=True,\n                                                    directory=train_path,\n                                                    x_col='img_path',\n                                                    y_col='breed',\n                                                    classes=unique_breeds,\n                                                    class_mode='categorical',\n                                                    target_size=(height, width),\n                                                    batch_size=batch_size)\n\nval_generator = val_datagen.flow_from_dataframe(dataframe=train_df[num_of_train_images:],\n                                                shuffle=False,\n                                                directory=train_path,\n                                                x_col='img_path',\n                                                y_col='breed',\n                                                classes=unique_breeds,\n                                                class_mode='categorical',\n                                                target_size=(height, width),\n                                                batch_size=batch_size)","execution_count":null,"outputs":[]},{"metadata":{"id":"CMMhszSNbEtS"},"cell_type":"markdown","source":"Prepare callbacks for model saving."},{"metadata":{"id":"W9sPRa-YbIbi","executionInfo":{"status":"ok","timestamp":1600067980204,"user_tz":-300,"elapsed":873,"user":{"displayName":"Zeon","photoUrl":"","userId":"13928249668567761334"}},"trusted":false},"cell_type":"code","source":"# We'll stop training if no improvement after some epochs\nearlystopper = EarlyStopping(monitor='val_accuracy', patience=10, verbose=1)\n\n# And reduce learning rate when val_accuracy no improvement after some epochs\nreduce_lr = ReduceLROnPlateau(monitor='val_accuracy', factor=0.2, patience=3, \n                            verbose=1, mode='max', min_lr=0.00001)\n\n# Save the best model during the traning\ncheckpoint = ModelCheckpoint(filepath=root_path + model_name + '.{epoch:02d}-{val_accuracy:.2f}.h5',\n                            monitor='val_accuracy',\n                            verbose=1,\n                            save_best_only=True)\n\ncallbacks = [checkpoint, earlystopper, reduce_lr]","execution_count":null,"outputs":[]},{"metadata":{"id":"AgmHi0zlbOEj"},"cell_type":"markdown","source":"Run training."},{"metadata":{"id":"gNOgvMo5cJ7J","outputId":"17860206-9848-4a54-8415-6ed6121ae826","trusted":false},"cell_type":"code","source":"model.fit_generator(train_generator,\n                    steps_per_epoch=train_generator.n//train_generator.batch_size,\n                    epochs=epochs,\n                    validation_data=val_generator,\n                    validation_steps=val_generator.n//val_generator.batch_size,\n                    verbose=1,\n                    callbacks=callbacks)","execution_count":null,"outputs":[]},{"metadata":{"id":"AWxelz5xcPaJ"},"cell_type":"markdown","source":"Create pandas DataFrame with empty columns."},{"metadata":{"id":"MWv5w8ItcYpq","trusted":false},"cell_type":"code","source":"preds_df = pd.DataFrame(columns=['id'] + list(unique_breeds))\npreds_df.head()","execution_count":null,"outputs":[]},{"metadata":{"id":"u0qvNPIuccpb"},"cell_type":"markdown","source":"Append test image filenames to predictions DataFrame."},{"metadata":{"id":"CfRIdjehch2D","trusted":false},"cell_type":"code","source":"preds_df['id'] = [path for path in os.listdir(test_path)]\npreds_df.head()","execution_count":null,"outputs":[]},{"metadata":{"id":"LW4Kh7BTcnRc"},"cell_type":"markdown","source":"Prepare test generator and start model prediction."},{"metadata":{"id":"2Sfnj7u3c3od","trusted":false},"cell_type":"code","source":"test_datagen = ImageDataGenerator(rescale=1./255)\ntest_generator = test_datagen.flow_from_dataframe(dataframe=preds_df,\n                                                shuffle=False,\n                                                directory=test_path,\n                                                x_col='id',\n                                                y_col=None,\n                                                class_mode=None,\n                                                target_size=(height, width),\n                                                batch_size=1)\n\ntest_predictions = model.predict_generator(test_generator, steps = test_generator.n, verbose=1)","execution_count":null,"outputs":[]},{"metadata":{"id":"HmkM43lXc7zO"},"cell_type":"markdown","source":"Add the prediction probabilities to each dog breed column."},{"metadata":{"id":"n1x_oOsbdAoW","trusted":false},"cell_type":"code","source":"preds_df[list(unique_breeds)] = test_predictions\npreds_df.head()","execution_count":null,"outputs":[]},{"metadata":{"id":"nwEGIwbndGXf"},"cell_type":"markdown","source":"Remove file extension .jpg"},{"metadata":{"id":"BR1drHFudJDH","trusted":false},"cell_type":"code","source":"preds_df['id'] = preds_df['id'].apply(lambda x: x.split('.')[0])\npreds_df.head()","execution_count":null,"outputs":[]},{"metadata":{"id":"h0wEA7C2dK-f"},"cell_type":"markdown","source":"Save DataFrame to submission csv-file."},{"metadata":{"id":"PK1crt34dNQP","trusted":false},"cell_type":"code","source":"preds_df.to_csv(root_path + 'MySubmission.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}