{"cells":[{"metadata":{},"cell_type":"markdown","source":"<center><h1>2019 3rd ML month with KaKR</h1></center>\n<center>자동차 이미지 데이터셋을 이용한 자동차 차종 분류</center>\n\n<br>\n\n<div>\n    <ul>\n        <li><b>Team Name: PowerTAZO</b></li>\n        <li><b>Team Members: <a href=\"https://www.kaggle.com/shinys\">Yoon</a>, <a href=\"https://www.kaggle.com/devbruce\">Bruce Kim</a></b></li>\n    </ul>\n</div>\n\n<br>\n\n<table>\n    <caption><h2>Kaggle Kerenl Reference</h2></caption>\n    <thead>\n        <th><center>Author</center></th>\n        <th><center>Kernel</center></th>\n    </thead>\n    <tbody>\n        <tr>\n            <td><center><a href=\"https://www.kaggle.com/fulrose\">TaeJin Kim</a></center></td>\n            <td><center><a href=\"https://www.kaggle.com/fulrose/3rd-ml-month-car-model-classification-baseline\">[3rd ML Month] Car Model Classification Baseline</a></center></td>\n        </tr>\n        <tr>\n            <td><center><a href=\"https://www.kaggle.com/tmheo74\">Taemyung Heo</a></center></td>\n            <td><center><a href=\"https://www.kaggle.com/tmheo74/3rd-ml-month-car-image-cropping\">[3rd ML Month] Car Image Cropping</a></center></td>\n        </tr>\n        <tr>\n            <td><center><a href=\"https://www.kaggle.com/janged\">Jang</a></center></td>\n            <td><center><a href=\"https://www.kaggle.com/janged/3rd-ml-month-xception-stratifiedkfold-ensemble\">[3rd ML Month] Xception, StratifiedKFold, Ensemble</a></center></td>\n        </tr>\n    </tbody>\n</table>\n\n<br>\n\n### Used Pre-tained Model\n\nEfficientNetB5 ([EfficientNet](https://github.com/qubvel/efficientnet))\n\n<br>\n\n### Contents\n\n- EDA\n  - Undersampling Class 119 (To Resolve Class imbalance)\n- Image Preprocessing: Histogram Equalization & Padding\n- Image Augmentation: Mixup (Reference: [MixupImageDataGenerator](https://github.com/Tony607/keras_mixup_generator))\n- K-Fold Cross Validation  \n(With `StratifiedKFold` (`n_splits=5` \\-\\-\\> Validation Ratio: 20%)\n\n\n"},{"metadata":{},"cell_type":"markdown","source":"## Install EfficientNet\n\n> **Reference: [EfficientNet](https://github.com/qubvel/efficientnet)**"},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install -U efficientnet","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Model Settings"},{"metadata":{"trusted":true},"cell_type":"code","source":"import os\nfrom efficientnet import EfficientNetB5\n\n\nCV_N = 4  # 0 ~ 4 (n_splits=5)\nPRETAINED_MODEL = EfficientNetB5\nPRETAINED_MODEL_STR = 'EfficientNetB5'\n\n# Required Size Of Pretained Model (299 x 299)\nIMG_SIZE = 299\n\n# Ensemble Mode\nENSEMBLE_MODE = True\n\n# Uploaded Model Path\nINPUT_PATH_ORIGIN = os.path.join('..', 'input')\nUPLOADED_MODEL_PATH = os.path.join(\n    os.path.join(INPUT_PATH_ORIGIN, 'efficientnetb5-kfold-n5'),\n    'efficientnetb5_kfold_n5'\n)\nUPLOADED_MODEL_PATH = os.path.join(UPLOADED_MODEL_PATH, 'EfficientNetB5_kfold_n5')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Initial Settings"},{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true},"cell_type":"code","source":"import random\nimport warnings\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport cv2\nimport PIL\nfrom PIL import ImageDraw\nfrom keras import models, layers, optimizers\nfrom keras import regularizers\nfrom keras.preprocessing.image import ImageDataGenerator\nfrom keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nfrom sklearn.model_selection import train_test_split, StratifiedKFold\n\n\n# Ignore Warnings\nwarnings.filterwarnings('ignore')\n\n# Full Data Set Mode\n# If False, Lightweight Data Mode For Test (Get 20% Of Data)\nFULL_DATA = True\nif FULL_DATA: LIGHT_DATA_TRAIN, LIGHT_DATA_TEST = False, False\nelse: LIGHT_DATA_TRAIN, LIGHT_DATA_TEST = True, True\n\n# Undersampling class 119 for class balance\nUNDERSAMPLING_119 = True\n\n# Constant Variables\nLEARNING_RATE = 1e-4\nOPTIMIZER = optimizers.Adam(lr=LEARNING_RATE)\nEPOCHS = 50 if FULL_DATA else 4\nBATCH_SIZE = 16  # if set high value, it would occur ResourceExhaustedError\nINITIALIZER = 'he_normal'\nREGULARIZER = regularizers.l2(1e-3)\nDROPOUT_RATE = 0.5\nPATIENCE = 8  # Patience Value For Early Stop\nVERBOSE = 2 if FULL_DATA else 1  # Verbosity mode (2: No progress bar)\nRANDOM_SEED = 2019\n    \n# Set Basic Path\nINPUT_PATH = os.path.join('..', 'input')\nif os.path.isdir(os.path.join(INPUT_PATH, '2019-3rd-ml-month-with-kakr')):\n    INPUT_PATH = os.path.join(INPUT_PATH, '2019-3rd-ml-month-with-kakr')\nTRAIN_IMG_PATH = os.path.join(INPUT_PATH, 'train')\nTEST_IMG_PATH = os.path.join(INPUT_PATH, 'test')\n\n# Set Path of Cropped Train Images\nTRAIN_IMG_CROP_PATH = os.path.join('..', 'train_crop')\nif not os.path.exists(TRAIN_IMG_CROP_PATH):\n    os.makedirs(TRAIN_IMG_CROP_PATH, exist_ok=True)\n\n# Set Path of Cropped Test Images\nTEST_IMG_CROP_PATH = os.path.join('..', 'test_crop')\nif not os.path.exists(TEST_IMG_CROP_PATH):\n    os.makedirs(TEST_IMG_CROP_PATH, exist_ok=True)\n\n# Set Path of Preprocessed Train Images\nTRAIN_IMG_PREP_PATH = os.path.join('..', 'train_prep')\nif not os.path.exists(TRAIN_IMG_PREP_PATH):\n    os.makedirs(TRAIN_IMG_PREP_PATH, exist_ok=True)\n\n# Set Path of Preprocessed Test Images\nTEST_IMG_PREP_PATH = os.path.join('..', 'test_prep')\nif not os.path.exists(TEST_IMG_PREP_PATH):\n    os.makedirs(TEST_IMG_PREP_PATH, exist_ok=True)\n\n# Set Save Path of Model\nMODEL_FILE_SAVE_DIR_PATH = '.'\nif not os.path.exists(MODEL_FILE_SAVE_DIR_PATH):\n    os.makedirs(MODEL_FILE_SAVE_PATH, exist_ok=True)\nMODEL_FILE_PATH = os.path.join(\n    MODEL_FILE_SAVE_DIR_PATH,\n    str(CV_N) + '_' + PRETAINED_MODEL_STR + '.hdf5'\n)\n\n# Load DataFrame (Read CSV)\ndf_class = pd.read_csv(os.path.join(INPUT_PATH, 'class.csv'))  # Label of class column (df_train)\ndf_train = pd.read_csv(os.path.join(INPUT_PATH, 'train.csv'))\ndf_test = pd.read_csv(os.path.join(INPUT_PATH, 'test.csv'))\n# df_class: |    id    |  name   |\n# df_train: | img_file | bbox_x1 | bbox_y1 | bbox_x2 | bbox_y2 | class |\n# df_test:  | img_file | bbox_x1 | bbox_y1 | bbox_x2 | bbox_y2 |       |\n\n# Number Of Class\nCLASS_NB = len(df_class)  # == 196","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Lightweight Data Set For Test"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Get 20% Of Original Train Data Set\nif LIGHT_DATA_TRAIN:\n    _, df_train = train_test_split(\n        df_train,\n        train_size=0.8,\n        random_state=RANDOM_SEED,\n        stratify=df_train['class'],\n    )\n\n# Get 10% Of Original Test Data Set\nif LIGHT_DATA_TEST:\n    _, df_test = train_test_split(\n        df_test,\n        train_size=0.8,\n        random_state=RANDOM_SEED,\n    )","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# EDA"},{"metadata":{},"cell_type":"markdown","source":"## DataFrame Information"},{"metadata":{"trusted":true},"cell_type":"code","source":"print('------ df_class ---' + '-' * 15)\ndf_class.info()\nprint('\\n------ df_train ---' + '-' * 15)\ndf_train.info()\nprint('\\n------ df_test ---' + '-' * 15)\ndf_test.info()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"- **There are not null data**\n\n- **There are just two types of data**\n  - object\n  - int64"},{"metadata":{},"cell_type":"markdown","source":"## Check Missing Data"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Check train image files missing\nif set(df_train.img_file) == set(os.listdir(TRAIN_IMG_PATH)):  # Ignore ordering with set\n    print(\"Train image files are no Problem\")\nelse: print(\"There is some missing train image files\")\n\n# Check test image files missing\nif set(df_test.img_file) == set(os.listdir(TEST_IMG_PATH)):\n    print(\"Test image files are no Problem\")\nelse: print(\"There is some missing test image files\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"car_class_nb = df_class.shape[0]\ntrain_class_nb = df_train['class'].nunique()\nprint(f'Number of car class: {car_class_nb}')\nprint(f'Number of train data class: {train_class_nb}')\n\nif car_class_nb == train_class_nb:\n    print(\"No problem\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Class Distribution\n\n- Check balance of number of classes"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_nb_per_class = df_train['class'].value_counts(ascending=True)\ntrain_nb_per_class.describe()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f'Min Count Class: {train_nb_per_class.index[0]}')\nprint(f'Max Count Class: {train_nb_per_class.index[-1]}')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"- Min Count Class: 136 (Value: 30)\n- Max Count Class: 119 (Value: 84)\n- Mean: 51.10\n- Standard Deviation: 5.35"},{"metadata":{},"cell_type":"markdown","source":"### Visualization"},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize=(16,8))\nplt.title('Number of train data per class')\nsns.countplot(df_train['class'])\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Class 119 is high**"},{"metadata":{},"cell_type":"markdown","source":"### Undersampling (Delete Some Class 119 For Class Balance)\n\nDelete 25% rows of class 119.  \nAs a result, the number of class 119 will be balanced"},{"metadata":{"trusted":true},"cell_type":"code","source":"random.seed(RANDOM_SEED)\n\nif UNDERSAMPLING_119:\n    class_119_idx_bool = (df_train['class'] == 119)\n    class_119_idx_nb = sum(class_119_idx_bool)\n    class_119_idx_list = list(df_train[class_119_idx_bool].index)\n    class_119_undersampling_idx = random.sample(class_119_idx_list, int(class_119_idx_nb*0.25))\n\n    # Delete 22 rows(119 Class) For Undersampling\n    for idx in class_119_undersampling_idx:\n        df_train = df_train.drop(idx)\n\n    df_train = df_train.reset_index(drop=True)\n    \n    # Show Undersampling Result\n    plt.figure(figsize=(16,8))\n    plt.title('Number of train data per class After Undersampling')\n    sns.countplot(df_train['class'])\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Load Images"},{"metadata":{"trusted":true},"cell_type":"code","source":"tmp_imgs = df_train['img_file'][:4]  # Load 4 images (0, 1, 2, 3)\nplt.figure(figsize=(14,10))\n\nfor idx, img_file_name in enumerate(tmp_imgs, 1):\n    img = PIL.Image.open(os.path.join(TRAIN_IMG_PATH, img_file_name))\n    plt.subplot(2, 2, idx)\n    plt.title(img_file_name)\n    plt.imshow(img)\n    plt.axis('off')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Image Preprocessing - Cropping & bbox"},{"metadata":{"trusted":true},"cell_type":"code","source":"def img_crop_or_draw_bbox(img_file_name, mode='crop', margin=6):\n    '''\n    mode = 'crop' or 'draw_bbox' (Default: 'crop')\n    '''\n    if img_file_name.split('_')[0] == 'train':\n        IMG_PATH = TRAIN_IMG_PATH\n        data = df_train\n    elif img_file_name.split('_')[0] == 'test':\n        IMG_PATH = TEST_IMG_PATH\n        data = df_test\n        \n    if mode == 'crop':\n        img = PIL.Image.open(os.path.join(IMG_PATH, img_file_name))\n        pos = data.loc[data['img_file'] == img_file_name, ['bbox_x1','bbox_y1', 'bbox_x2', 'bbox_y2']].values.reshape(-1)\n        width, height = img.size\n        x1 = max(0, pos[0] - margin)\n        y1 = max(0, pos[1] - margin)\n        x2 = min(pos[2] + margin, width)\n        y2 = min(pos[3] + margin, height)\n        return img.crop((x1, y1, x2, y2))\n    \n    elif mode == 'draw_bbox':\n        def draw_bbox(img_file_name, pos, outline_color='lightgreen', width=8):\n            x1, y1 = pos[0], pos[1]  # Coordinate: Upper Left\n            x2, y2 = pos[2], pos[3]  # Coordinate: Bottom Right\n            rect_coordinates = (x1, y1), (x2, y1), (x2, y2), (x1, y2), (x1, y1)\n            img_file_name.line(rect_coordinates, fill=outline_color, width=width)\n        \n        img = PIL.Image.open(os.path.join(IMG_PATH, img_file_name))\n        pos = data.loc[data['img_file'] == img_file_name, ['bbox_x1','bbox_y1', 'bbox_x2', 'bbox_y2']].values.reshape(-1)\n        draw = ImageDraw.Draw(img)  # ImageDraw (from PIL)\n        draw_bbox(draw, pos)\n        return img\n\n\n# Define Function For Test\ndef img_test_bbox_crop(img_file_name):\n    # Show Original Image\n    img_origin = PIL.Image.open(os.path.join(TRAIN_IMG_PATH, img_file_name))\n    plt.figure(figsize=(22,14))\n    plt.subplot(1, 3, 1)\n    plt.title(f'Original Image - {img_file_name}')\n    plt.imshow(img_origin)\n    plt.axis('off')\n\n    # Show Image Including bbox\n    img_bbox = img_crop_or_draw_bbox(img_file_name, mode='draw_bbox')\n    plt.subplot(1, 3, 2)\n    plt.title(f'Boxing Image - {img_file_name}')\n    plt.imshow(img_bbox)\n    plt.axis('off')\n    \n    # Show Crop Image\n    img_crop = img_crop_or_draw_bbox(img_file_name, mode=\"crop\")\n    plt.subplot(1, 3, 3)\n    plt.title(f'Cropped Image - {img_file_name}')\n    plt.imshow(img_crop)\n    plt.axis('off')\n    \n    # Show Result\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Test bbox and crop\nimg_test_bbox_crop(img_file_name=df_train['img_file'].iloc[100])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Save Crop Images\n\n> Cropped Train Images Path: `'/kaggle/train_crop'` (`../train_crop`)  \n> Cropped Test Images Path: `'/kaggle/test_crop'` (`../test_crop`)  "},{"metadata":{"trusted":true},"cell_type":"code","source":"# Save Cropped Train Images (Path: /kaggle/working/train_crop)\nif not os.listdir(TRAIN_IMG_CROP_PATH):  # If PATH_IMG_TRAIN_CROP is empty\n    for idx, row in df_train.iterrows():\n        img_file_name = row['img_file']\n        img_crop = img_crop_or_draw_bbox(img_file_name, mode='crop')\n        img_crop.save(os.path.join(TRAIN_IMG_CROP_PATH, img_file_name))\n\n# Save Cropped Test Images (Path: /kaggle/working/test_crop)\nif not os.listdir(TEST_IMG_CROP_PATH):\n    for idx, row in df_test.iterrows():\n        img_file_name = row['img_file']\n        img_crop = img_crop_or_draw_bbox(img_file_name, mode='crop')\n        img_crop.save(os.path.join(TEST_IMG_CROP_PATH, img_file_name))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Image Preprocessing - Histogram Equalization"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Histogram Equalization And Add Padding After Cropping\ndef img_he_pad(img_file_name, add_padding=True):\n    if img_file_name.split('_')[0] == 'train':\n        IMG_CROP_PATH = TRAIN_IMG_CROP_PATH\n        data = df_train\n    elif img_file_name.split('_')[0] == 'test':\n        IMG_CROP_PATH = TEST_IMG_CROP_PATH\n        data = df_test\n        \n    # --- Histogram Equalization --- #\n    img = cv2.imread(os.path.join(IMG_CROP_PATH, img_file_name))\n    img_y_cr_cb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)\n    y, cr, cb = cv2.split(img_y_cr_cb)\n\n    # Equalize y (CLAHE (Contrast Limited Adaptive Histogram Equalization))\n    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(6,6))\n    y_eq = clahe.apply(y)\n    img_y_cr_cb_eq = cv2.merge((y_eq, cr, cb))\n    img_bgr_eq = cv2.cvtColor(img_y_cr_cb_eq, cv2.COLOR_YCR_CB2BGR)\n    img_prep = img_bgr_eq\n\n    # --- Convert BGR To RGB (Just On cv2) ---\n    # b, g, r = cv2.split(img_bgr_eq)\n    # img_prep = cv2.merge((r,g,b))\n    \n    # -------- Add Padding --------- #\n    if add_padding:\n        img_prep_h, img_prep_w = img_prep.shape[0], img_prep.shape[1]  # (height, width)\n        ratio = float(IMG_SIZE) / max(img_prep_h, img_prep_w)\n        shape_no_padding = (int(img_prep_h * ratio), int(img_prep_w * ratio))\n\n        img_prep_no_padding = cv2.resize(img_prep, shape_no_padding[::-1])\n        \n        size_h = IMG_SIZE - shape_no_padding[0]\n        size_w = IMG_SIZE - shape_no_padding[1]\n        \n        top, bottom = size_h // 2, size_h - (size_h // 2)\n        left, right = size_w // 2, size_w - (size_w // 2)\n\n        PADDING_COLOR = (0, 0, 0)  # black\n        img_prep = cv2.copyMakeBorder(\n            img_prep_no_padding,\n            top,\n            bottom,\n            left,\n            right,\n            cv2.BORDER_CONSTANT,\n            value=PADDING_COLOR\n        )\n    \n    return img_prep","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Save Preprocessed Images\n\n> Preprocessed Train Images Path: `'/kaggle/train_prep'` (`../train_prep`)  \n> Preprocessed Test Images Path: `'/kaggle/test_prep'` (`../test_prep`)  "},{"metadata":{"trusted":true},"cell_type":"code","source":"# Save Preprocessed Train Images (Path: ../train_prep)\nif not os.listdir(TRAIN_IMG_PREP_PATH):  # If PATH_IMG_TRAIN_PREP is empty\n    for idx, row in df_train.iterrows():\n        img_file_name = row['img_file']\n        img_prep = img_he_pad(img_file_name, add_padding=True)\n        cv2.imwrite(os.path.join(TRAIN_IMG_PREP_PATH, img_file_name), img_prep)\n\n# Save Preprocessed Test Images (Path: ../test_prep)\nif not os.listdir(TEST_IMG_PREP_PATH):\n    for idx, row in df_test.iterrows():\n        img_file_name = row['img_file']\n        img_prep = img_he_pad(img_file_name, add_padding=True)\n        cv2.imwrite(os.path.join(TEST_IMG_PREP_PATH, img_file_name), img_prep)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Show Preprocessed Images For Test"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Define Function For Test\ndef test_he_padding(img_file_name):\n    # Show Cropped Image\n    img_crop = PIL.Image.open(os.path.join(TRAIN_IMG_CROP_PATH, img_file_name))\n    plt.figure(figsize=(12, 9))\n    plt.subplot(1, 2, 1)\n    plt.title(f'Cropped Image - {img_file_name}')\n    plt.imshow(img_crop)\n    plt.axis('off')\n\n    # Show Preprocessed Image\n    img_he_pad = PIL.Image.open(os.path.join(TRAIN_IMG_PREP_PATH, img_file_name))\n    plt.subplot(1, 2, 2)\n    plt.title(f'Historgram Equalized Cropped Image(Add Padding) - {img_file_name}')\n    plt.imshow(img_he_pad)\n    plt.axis('off')\n    \n    # Show Result\n    plt.show()\n    \n# Test Histogram Equalization & Add Padding\ntest_he_padding(img_file_name=df_train['img_file'].iloc[114])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"---"},{"metadata":{},"cell_type":"markdown","source":"# DataFrame Preprocessing"},{"metadata":{"trusted":true},"cell_type":"code","source":"# class_mode=\"categorical\", y_col=\"class\" column values must be type string (flow method options)\ndf_train['class'] = df_train['class'].astype('str')\n\n# Take Necessary Columns\ndf_train = df_train[['img_file', 'class']]\ndf_test = df_test[['img_file']]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model Definition"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Define Get Model Function\ndef get_model(pretained_model, img_size, optimizer, class_nb):\n    model_options = dict(\n        include_top=False,\n        weights='imagenet',\n        input_shape=(img_size, img_size, 3),\n    )\n    model = models.Sequential()\n    model.add(pretained_model(**model_options))\n    model.add(layers.GlobalAveragePooling2D())\n    model.add(layers.Dense(\n        1024,\n        activation='relu',\n        kernel_initializer=INITIALIZER,\n        kernel_regularizer=REGULARIZER,\n    ))\n    model.add(layers.Dropout(DROPOUT_RATE))\n    model.add(layers.Dense(class_nb, activation='softmax'))\n    model.compile(optimizer=optimizer, loss='categorical_crossentropy', metrics=['acc'])\n    return model\n\n\n# Define Get Step Function\ndef get_steps(num_data, batch_size):\n    quotient, remainder = divmod(num_data, batch_size)\n    return (quotient + 1) if remainder else quotient","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Callback\n\n- Early Stop\n- Reduce Learning Rate\n- Model Check Point (Save Model File)"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Define Get Callback Function\ndef get_callback_list(model_file_path, patience, verbose):\n    early_stop = EarlyStopping(\n        monitor='val_loss',\n        patience=patience,\n        verbose=verbose,\n        mode='min',\n    )\n    reduce_lr = ReduceLROnPlateau(\n        monitor='val_loss',\n        factor=0.5,\n        patience=patience//2,\n        verbose=verbose,\n        mode='min',\n        min_lr=1e-6,\n    )\n    model_chk = ModelCheckpoint(\n        filepath=model_file_path,\n        monitor='val_loss',\n        verbose=verbose,\n        save_best_only=True,\n        mode='min',\n    )\n    return [early_stop, reduce_lr, model_chk]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# K-Fold Cross Validation (With StratifiedKFold)\n\n- `idx_split[n][0]` : Splited Train index\n- `idx_split[n][1]` : Splited Validation Index\n- `n_splits=5`: Validation Ratio 20%"},{"metadata":{"trusted":true},"cell_type":"code","source":"idx_split = list()\nskf = StratifiedKFold(n_splits=5, random_state=RANDOM_SEED)\nfor train_idx, val_idx in skf.split(X=df_train['img_file'], y=df_train['class']):\n    idx_split.append((train_idx, val_idx))\n    \nX_train = df_train.iloc[idx_split[CV_N][0]].reset_index()\nX_val = df_train.iloc[idx_split[CV_N][1]].reset_index()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Generator"},{"metadata":{},"cell_type":"markdown","source":"## MixupImageDataGenerator\n\n> **Reference: [Tony607 - keras_mixup_generator](https://github.com/Tony607/keras_mixup_generator)**\n\n기존코드에서 해당 모델에 적용 가능하도록 변경"},{"metadata":{"trusted":true},"cell_type":"code","source":"class MixupImageDataGenerator():\n    def __init__(self, generator, dataframe, directory, img_size, alpha, subset=None):\n        self.batch_index = 0\n        self.batch_size = BATCH_SIZE\n        self.alpha = alpha\n\n        # First iterator yielding tuples of (x, y)\n        self.generator1 = generator.flow_from_dataframe(\n            dataframe=dataframe,\n            directory=directory,\n            target_size=(img_size, img_size),\n            x_col='img_file',\n            y_col='class',\n            color_mode='rgb',\n            class_mode='categorical',\n            batch_size=BATCH_SIZE,\n        )\n\n        # Second iterator yielding tuples of (x, y)\n        self.generator2 = generator.flow_from_dataframe(\n            dataframe=dataframe,\n            directory=directory,\n            target_size=(img_size, img_size),\n            x_col='img_file',\n            y_col='class',\n            color_mode='rgb',\n            class_mode='categorical',\n            batch_size=BATCH_SIZE,\n        )\n        \n        self.n = self.generator1.samples\n    \n    @property\n    def class_indices(self):\n        return self.generator1.class_indices\n    \n    @property\n    def samples(self):\n        return self.generator1.samples\n    \n    def reset_index(self):\n        self.generator1._set_index_array()\n        self.generator2._set_index_array()\n\n    def on_epoch_end(self):\n        self.reset_index()\n\n    def reset(self):\n        self.batch_index = 0\n\n    def __len__(self):\n        return (self.n + self.batch_size - 1) // self.batch_size\n\n    def get_steps_per_epoch(self):\n        quotient, remainder = divmod(self.n, self.batch_size)\n        return (quotient + 1) if remainder else quotient\n\n    def __next__(self):\n        if self.batch_index == 0:\n            self.reset_index()\n\n        current_index = (self.batch_index * self.batch_size) % self.n\n        if self.n > current_index + self.batch_size:\n            self.batch_index += 1\n        else:\n            self.batch_index = 0\n\n        reshape_size = self.batch_size\n        if current_index == (self.get_steps_per_epoch()-1) * self.batch_size:\n            reshape_size = self.n - ((self.get_steps_per_epoch()-1) * self.batch_size)\n            \n        # random sample the lambda value from beta distribution.\n        l = np.random.beta(a=self.alpha, b=self.alpha, size=reshape_size)\n            \n        X_l = l.reshape(reshape_size, 1, 1, 1)\n        y_l = l.reshape(reshape_size, 1)\n        \n        # Get a pair of inputs and outputs from two iterators.\n        X1, y1 = self.generator1.next()\n        X2, y2 = self.generator2.next()\n\n        # Perform the mixup.\n        X = X1 * X_l + X2 * (1 - X_l)\n        y = y1 * y_l + y2 * (1 - y_l)\n        return X, y\n\n    def __iter__(self):\n        while True:\n            yield next(self)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Generator Definition"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Train Generator (Mixup)\ntrain_datagen = ImageDataGenerator(\n    rotation_range=20,\n    width_shift_range=0.2,\n    height_shift_range=0.2,\n    shear_range=0.2,\n    zoom_range=0.2,\n    horizontal_flip=True,\n    vertical_flip=False,\n    rescale=1./255,\n)\ntrain_generator = MixupImageDataGenerator(\n    generator=train_datagen,\n    dataframe=X_train,\n    directory=TRAIN_IMG_PREP_PATH,\n    img_size=IMG_SIZE,\n    alpha=0.2,\n)\n\n# Validation Generator\nvalid_datagen = ImageDataGenerator(rescale=1./255,)\nvalid_generator = valid_datagen.flow_from_dataframe(\n    dataframe=X_val,\n    directory=TRAIN_IMG_PREP_PATH,\n    target_size=(IMG_SIZE, IMG_SIZE),\n    x_col='img_file',\n    y_col='class',\n    color_mode='rgb',\n    class_mode='categorical',\n    batch_size=BATCH_SIZE,\n)\n\n# Test Generator\ntest_datagen = ImageDataGenerator(rescale=1./255,)\ntest_generator = test_datagen.flow_from_dataframe(\n    dataframe=df_test,\n    directory=TEST_IMG_PREP_PATH,\n    x_col='img_file',\n    y_col=None,\n    target_size=(IMG_SIZE, IMG_SIZE),\n    color_mode='rgb',\n    class_mode=None,\n    batch_size=BATCH_SIZE,\n    shuffle=False,\n)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Train & Save (Not Ensemble Mode)"},{"metadata":{"trusted":true},"cell_type":"code","source":"if not ENSEMBLE_MODE:\n    # Get Model\n    model = get_model(\n        img_size=IMG_SIZE,\n        pretained_model=PRETAINED_MODEL,\n        optimizer=OPTIMIZER,\n        class_nb=CLASS_NB,\n    )\n\n    # Train With Generators\n    history = model.fit_generator(\n        generator=train_generator,\n        steps_per_epoch=train_generator.get_steps_per_epoch(),\n        epochs=EPOCHS,\n        verbose=VERBOSE,\n        callbacks=get_callback_list(\n            model_file_path=MODEL_FILE_PATH,\n            patience=PATIENCE,\n            verbose=VERBOSE\n        ),\n        validation_data=valid_generator,\n        validation_steps=get_steps(valid_generator.samples, BATCH_SIZE),\n        shuffle=False,\n    )\n    \n    # Visualization Loss & Accuracy\n    history_info = history.history\n    loss = history_info['loss']\n    val_loss = history_info['val_loss']\n    acc = history_info['acc']\n    val_acc = history_info['val_acc']\n    epochs = range(1, len(loss)+1)\n\n    # Plot\n    fig = plt.figure(figsize=(20, 4))\n\n    fig.add_subplot(1, 2, 1)\n    plt.plot(epochs, loss, 'b', label=f'Training Loss')\n    plt.plot(epochs, val_loss,'r', label=f'Validation Loss')\n    plt.title(f'Loss Per Epoch ({CV_N}.{PRETAINED_MODEL_STR})')\n    plt.xlabel('Epochs')\n    plt.ylabel('Loss')\n    plt.legend()\n\n    fig.add_subplot(1, 2, 2)\n    plt.plot(epochs, acc, 'b', label='Training Accuracy')\n    plt.plot(epochs, val_acc, 'r', label='Validation Accuracy')\n    plt.title(f'Accuracy Per Epoch ({CV_N}.{PRETAINED_MODEL_STR})')\n    plt.xlabel('Epochs')\n    plt.ylabel('Loss')\n    plt.legend()\n    plt.show()\n    \n    # Prediction\n    pred = model.predict_generator(\n        generator=test_generator,\n        steps=get_steps(len(df_test), BATCH_SIZE),\n        verbose=VERBOSE,\n    )\n    \n    # Get Pred Indices\n    pred_class_indices = np.argmax(pred, axis=1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Ensemble Models (With Saved Model)"},{"metadata":{"trusted":true},"cell_type":"code","source":"if ENSEMBLE_MODE:\n    UPLOADED_MODEL_LIST = sorted(os.listdir(UPLOADED_MODEL_PATH))\n\n    model_pred_list = list()\n    for model_n, model_name in enumerate(UPLOADED_MODEL_LIST):\n        model_file_path = os.path.join(UPLOADED_MODEL_PATH, model_name)\n        model = get_model(\n            img_size=IMG_SIZE,\n            pretained_model=PRETAINED_MODEL,\n            optimizer=OPTIMIZER,\n            class_nb=CLASS_NB,\n        )\n        model.load_weights(model_file_path)\n        test_generator.reset()\n        pred = model.predict_generator(\n            generator=test_generator,\n            steps=get_steps(len(df_test), BATCH_SIZE),\n            verbose=VERBOSE,\n        )\n        model_pred_list.append(pred)\n\n    # Model Ensemble\n    pred_mean = np.mean(model_pred_list, axis=0)\n    \n    # Get Pred Indices\n    pred_class_indices = np.argmax(pred_mean, axis=1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Submission"},{"metadata":{"trusted":true},"cell_type":"code","source":"get_class_indices = train_generator.class_indices\nlabels = {val:key for key, val in get_class_indices.items()}\npred_result = [labels[idx] for idx in pred_class_indices]\n\nsubmission = pd.read_csv(os.path.join(INPUT_PATH, 'sample_submission.csv'))\nsubmission['class'] = pred_result\nsubmission.to_csv('submission.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission.head()","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.6"}},"nbformat":4,"nbformat_minor":1}