{"cells":[{"metadata":{"_uuid":"4d8bbea6-941b-4dff-9b31-85c297af08c0","_cell_guid":"f1499a28-6e16-4a8a-a18c-8c3ca19ac555","trusted":true},"cell_type":"markdown","source":"# 0. 뱅골어 손글씨 인식"},{"metadata":{"_uuid":"198212b8-67fb-420d-abcf-e1d9a8bcef3b","_cell_guid":"2fbbda04-9579-47e0-a20d-5f9b6d3e007b","trusted":true},"cell_type":"code","source":"import math\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport gc\nimport tensorflow as tf\nimport time\nimport keras\nimport cv2\nimport scipy.special\nfrom keras.optimizers import SGD\nfrom keras.models import Model, Sequential\nfrom keras.layers import Input, Dense, Activation, Add, GlobalAveragePooling2D, Dropout, Flatten, BatchNormalization, Lambda\nfrom keras.layers.convolutional import Conv2D, MaxPooling2D\nfrom keras.preprocessing.image import ImageDataGenerator\nfrom keras.callbacks import ReduceLROnPlateau\nfrom keras.regularizers import l2\nfrom keras.utils import plot_model\nfrom tqdm.auto import tqdm\n\nfrom PIL import Image\n\n# set max display columns and rows count\npd.set_option('display.max_columns', 10000)\npd.set_option('display.max_rows', 10000)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import os\n__print__ = print\ndef print_log(string):\n    os.system(f'echo \\\"{string}\\\"')\n    __print__(string)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Install EfficientNet\n!pip install '/kaggle/input/kerasefficientnetb3/efficientnet-1.0.0-py3-none-any.whl'\nimport efficientnet.keras as efn","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"3c33b17f-c9f6-4482-927c-69e2961b2f02","_cell_guid":"242c00f4-e155-4b29-9b25-0bf903334dc0","trusted":true},"cell_type":"markdown","source":"# 1. 데이터 분석 및 가공\n\n### 데이터 분석\n* class_map.csv 파일에는 grapheme_root, vowel_diacritic, consonant_diacritic 정보가 포함되어 있다.\n* train.csv와 test.csv 파일에는 train_image_data_x.parquet와 test_image_data.parquet 데이터에 대한 메타 데이터가 포함되어 있다. grapheme_root, vowel_diacritic, consonant_diacritic가 어떻게 조합되어 있는지 class_map에 있는 label로 표현되어 있다. 그리고 최종으로 조합된 글자(grapheme)가 포함되어 있다.\n* train_image_data_x.parquet와 test_image_data.parquet 파일에는 뱅골어 손글씨 이미지가 기록되어 있다. 사이즈는 (137, 236)이다.\n\n### 뱅골어 고찰\n* grapheme_root와 vowel_diacritic 그리고 consonant_diacritic이 조합되어 하나의 자소(grapheme)를 만든다."},{"metadata":{"_uuid":"54213dde-26ef-4d31-8554-3512f301533d","_cell_guid":"c338f1fa-d63c-4910-bc90-9b057421cb1c","trusted":true},"cell_type":"code","source":"# class_map 불러오기(grapheme_root, vowel_diacritic, consonant_diacritic 종류와 딕셔너리가 들어있음)\nclass_map = pd.read_csv('/kaggle/input/bengaliai-cv19/class_map.csv')\n\n# class_map에 있는 grapheme_root, vowel_diacritic, consonant_diacritic 정보를 분리하여 보관\ngrapheme_root = class_map[class_map['component_type'] == 'grapheme_root']['component'].values\nGRAPHEME_ROOT_NUM = grapheme_root.shape[0]\nvowel_diacritic = class_map[class_map['component_type'] == 'vowel_diacritic']['component'].values\nVOWEL_DIACRITIC_NUM = vowel_diacritic.shape[0]\nconsonant_diacritic = class_map[class_map['component_type'] == 'consonant_diacritic']['component'].values\nCONSONANT_DIACRITIC_NUM = consonant_diacritic.shape[0]","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d55a5dee-20f8-4687-b158-77495dab332b","_cell_guid":"9c23b03f-cfd4-44b0-9943-8b90c855d488","trusted":true},"cell_type":"code","source":"# train.csv 파일 경로\nTRAIN_META_PATH = '/kaggle/input/bengaliai-cv19/train.csv'\n\n# train_image_data.parquet 파일 경로\nTRAIN_IMG_PATH = ['/kaggle/input/bengaliai-cv19/train_image_data_0.parquet',\n           '/kaggle/input/bengaliai-cv19/train_image_data_1.parquet',\n           '/kaggle/input/bengaliai-cv19/train_image_data_2.parquet',\n           '/kaggle/input/bengaliai-cv19/train_image_data_3.parquet']\n\n# 이미지 크기 정보(image height, image width)\nRAW_IMG_ROWS, RAW_IMG_COLUMNS = 137, 236","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"95c33a94-543a-4dcf-910f-4c8f741f106d","_cell_guid":"c6ca4866-1e52-47bc-958b-a352024c3280","trusted":true},"cell_type":"code","source":"# 학습 레이블 가져오는 함수\n# split_y 인자를 통해 train_y를 3개의 train_y_grapheme_root, train_y_vowel_diacritic, train_y_consonant_diacritic로 나눌 수 있다\ndef get_train_y(train_meta_path, data_range, split_y=True):\n    train_meta_data = pd.read_csv(train_meta_path)\n    \n    # pandas 내장 함수를 이용해서 one hot encoding 적용\n    train_y_grapheme_root       = pd.get_dummies(train_meta_data['grapheme_root']).to_numpy(dtype='float32')\n    train_y_vowel_diacritic     = pd.get_dummies(train_meta_data['vowel_diacritic']).to_numpy(dtype='float32')\n    train_y_consonant_diacritic = pd.get_dummies(train_meta_data['consonant_diacritic']).to_numpy(dtype='float32')\n    \n    # multiclassification: 마지막 레이어에 들어갈 레이블을 서로 분리할 지, 합칠 지를 적용\n    train_y = [train_y_grapheme_root[data_range], train_y_vowel_diacritic[data_range], train_y_consonant_diacritic[data_range]]\n    if (split_y is False):\n        train_y = np.concatenate(train_y, axis=1)\n        \n    return train_y\n\n# train_image_data_x.parquet을 불러와서 numpy 배열로 되어 있는 이미지와, 시작 Id, 그리고 들어 있는 이미지의 개수를 반환한다.\ndef get_img_data(img_path):\n    data = pd.read_parquet(img_path)\n    \n    # train_image_data의 인덱스 정보와 Image_Id 정보가 일치하지 않을 수 있음(train_image_data_1.parquet의 index가 0일 때에는 Image_Id가 50210이다.)\n    # 따라서 meta data와 상응한 정보를 가져오게 하기 위해서 train_image_data의 첫 id 값과 요소의 개수를 반환하도록 한다.\n    start_id    = int(data.iloc[0, 0].split('_')[1])\n    element_num = data.shape[0]\n    \n    # pandas로 데이터를 불러와 numpy 배열로 바꾸는 작업이 필요하다.\n    # pandas로 데이터를 조작할 때에는 속도가 너무 느리다(특히 iloc). 따라서 numpy 배열로 바꾸어 반환해서 속도 향상을 꾀한다.\n    img_data = data[data.columns[1:]]\n    img_data = img_data.to_numpy(dtype='uint8')\n    img_data = img_data.reshape(-1, RAW_IMG_ROWS, RAW_IMG_COLUMNS)\n    \n    del data\n    gc.collect()\n    \n    return img_data, start_id, element_num","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"09cf46a8-a1f1-48e6-b66e-463706a68d21","_cell_guid":"2d8bbd08-ffde-4b20-9152-1e77167162c8","trusted":true},"cell_type":"markdown","source":"## 데이터 전처리 함수 Version 1\n개별 학습 이미지 데이터(train_image_data_x.parquet)를 pandas로 불러와 <U>가로, 세로 크기를 절반으로 줄인다</U>. 즉, 이미지 크기는 1/4배가 된다. 이렇게 하는 이유는 메모리 문제 때문인데, keras에 학습 데이터를 넣기 위해 float32 형태의 데이터 타입을 가져야 한다. train_image_data_x.parquet을 불러오고 uint8 데이터형 numpy 배열을 만들 때에 3GB 정도 사용하는데, float32 데이터형으로 바꾸면 한 개의 train_image_data_x.parquet 마다 12GB를 소모하는 문제가 생긴다. 그래서 이미지 크기 자체를 줄여 데이터를 전처리하기로 했다."},{"metadata":{"_uuid":"c5d15146-a616-4d43-a3b8-f1f801c5e3e2","_cell_guid":"50482a27-97fe-4f20-b9d6-0d43c806abe4","trusted":true},"cell_type":"code","source":"# 데이터 전처리 함수 Version 1\ndef load_train_data_v1(train_img_path, split_y=True):\n    print('학습 이미지 불러오는 중 ...')\n    print('  경로:', train_img_path)\n    img_data, start_id, element_num = get_img_data(train_img_path)\n    print('  총 ', element_num, '개의 이미지')\n    print('학습 이미지 불러오기 완료 !')\n    \n    # create train_y\n    print('학습 레이블 생성 중 ...')\n    train_y = get_train_y(TRAIN_META_PATH, range(start_id, start_id + element_num), split_y=split_y)\n    print('학습 레이블 생성 완료 !')\n\n    # create train_x\n    print('학습 이미지 생성중 ...')\n    # train_x 정보 생성 및 이미지 크기 조절\n    img_rows = RAW_IMG_ROWS // 2\n    img_columns = RAW_IMG_COLUMNS // 2\n    train_x = np.empty(dtype='uint8', shape=(element_num, img_rows, img_columns))\n    \n    for i in range(train_x.shape[0]): # resize image\n        train_x[i] = cv2.resize(img_data[i], dsize=(img_columns, img_rows)) # width * height\n    print('  학습 이미지 크기:', '(' + str(img_rows) + ', ' + str(img_columns) + ')')\n    \n    # 메모리 관리\n    del img_data # img_data를 더 이상 안 쓰므로 일단 메모리에서 삭제\n    gc.collect()\n\n    # keras CNN 모델에 넣기 위한 차원 및 데이터타입 정리\n    train_x = train_x.reshape((-1, img_rows, img_columns, 1))\n    train_x = train_x.astype('float32')\n    train_x = train_x / 255.0\n    print('학습 이미지 생성 완료 !')\n\n    return train_x, train_y","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d2b3e6d1-842b-47ae-b0e6-23381e5a0311","_cell_guid":"c878e5d9-aabd-49bb-813e-6891444f217a","trusted":true},"cell_type":"markdown","source":"## 데이터 전처리 함수 Version 2\nVersion 1과는 다르게 뱅골어 손글씨에 ROI(Region of Interest)를 적용해 손글씨 부분만 남겨놓고 이미지를 잘라낸다. 그 뒤에 일정한 크기(예를 들어 64x64)로 Resize를 해서 데이터를 전처리 한다."},{"metadata":{"_uuid":"b638eeb1-1620-4da1-bf02-527660033769","_cell_guid":"467b31df-782b-4b3b-9a6d-7d87e90ef51f","trusted":true},"cell_type":"code","source":"# 데이터 전처리 함수 Version 2\ndef load_train_data_v2(train_img_path, output_img_size, split_y=True):\n    print('학습 이미지 불러오는 중 ...')\n    print('  경로:', train_img_path)\n    img_data, start_id, element_num = get_img_data(train_img_path)\n    print('  총 ', element_num, '개의 이미지')\n    print('학습 이미지 불러오기 완료 !')\n    \n    # create train_y\n    print('학습 레이블 생성 중 ...')\n    train_y = get_train_y(TRAIN_META_PATH, range(start_id, start_id + element_num), split_y=split_y)\n    print('학습 레이블 생성 완료 !')\n    \n    # create train_x\n    print('학습 이미지 생성중 ...')\n    img_rows    = output_img_size[1]\n    img_columns = output_img_size[0]\n    train_x = np.empty(dtype='uint8', shape=(element_num, img_rows, img_columns))\n    \n    for i in tqdm(range(train_x.shape[0])):\n        img = img_data[i]\n        # get ROI(region of interest)\n        x1, y1, x2, y2 = get_img_roi(img)\n        # crop image\n        img = img[y1:y2, x1:x2]\n        # resize image\n        img = cv2.resize(img, dsize=(img_columns, img_rows)) # width * height\n        train_x[i] = img\n        \n    print('  학습 이미지 크기:', '(' + str(img_rows) + ', ' + str(img_columns) + ')')\n    \n    # keras CNN 모델에 넣기 위한 차원 및 데이터타입 정리\n    train_x = train_x.reshape((-1, img_rows, img_columns, 1))\n    train_x = train_x.astype('float32')\n    train_x = train_x / 255.0\n    print('학습 이미지 생성 완료!')\n    \n    return train_x, train_y\n\n# 이미지의 관심 영역을 좌표(x1, y1, x2, y2)로 반환하는 함수\ndef get_img_roi(img):\n    _, img_thres = cv2.threshold(img, 200, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)\n        \n    img_thres_sum_x = img_thres.sum(axis=0)\n    for x1 in range(0, RAW_IMG_COLUMNS, 1):\n        if (img_thres_sum_x[x1] > 0):\n            break\n\n    for x2 in range(RAW_IMG_COLUMNS-1, -1, -1):\n        if (img_thres_sum_x[x2] > 0):\n            break\n\n    img_thres_sum_y = img_thres.sum(axis=1)\n    for y1 in range(0, RAW_IMG_ROWS, 1):\n        if (img_thres_sum_y[y1] > 0):\n            break\n\n    for y2 in range(RAW_IMG_ROWS-1, -1, -1):\n        if (img_thres_sum_y[y2] > 0):\n            break\n            \n    return x1, y1, x2, y2","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"94f52d6c-2795-419e-a49f-c006e4406179","_cell_guid":"faf75adb-d582-464c-aa4a-9059be7dd089","trusted":true},"cell_type":"code","source":"IMG_ROWS    = 64\nIMG_COLUMNS = 64\n\ntrain_x, train_y = load_train_data_v2(TRAIN_IMG_PATH[0], output_img_size=(IMG_ROWS, IMG_COLUMNS), split_y=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"2b254083-8d9a-4d4c-90ca-fdddd87e4997","_cell_guid":"3c16845b-d120-4aba-9a11-00b483940408","trusted":true},"cell_type":"markdown","source":"# 2. 데이터 시각화"},{"metadata":{"_uuid":"eb5af25b-b481-41b1-b029-b35e3d918159","_cell_guid":"9a3d053f-f4c0-42a9-a6aa-30ba7b94b9a4","trusted":true},"cell_type":"code","source":"# 특정 벵골어 손글씨 데이터 시각화(train set에서만 사용)\ndef visualize_grapheme(grapheme, train_img_path):\n    meta_data = pd.read_csv(TRAIN_META_PATH)\n    img_data = pd.read_parquet(train_img_path)\n    \n    grapheme_id = meta_data[meta_data['grapheme'] == grapheme]['image_id'].values # train_data에서 표시할 벵골어에 해당하는 image_id 선택\n    grapheme_image = img_data[img_data['image_id'].isin(grapheme_id)] # train_image_data에서 표시할 벵골어에 해당하는 이미지 선택\n    size = len(grapheme_image.index) # 이미지 개수\n\n    # matplotlib figure 설정\n    columns = 8\n    rows = size / columns + 1\n    fig = plt.figure(figsize=(30, rows * 3))\n\n    # figure 그리기(imshow 이용, imshow를 사용할 때 데이터 타입이 uint8이어야 한다.)\n    for i in range(size):\n        image_index = str(grapheme_image.iloc[i, 0]).split('_')[1]\n        image = grapheme_image.iloc[i].values[1:].astype('uint8').reshape(RAW_IMG_ROWS, RAW_IMG_COLUMNS)\n\n        ax = fig.add_subplot(rows, columns, i + 1)\n        ax.imshow(image, cmap='gray')\n        ax.set_xlabel(grapheme_id[i])\n\n    plt.show()\n    del(meta_data)\n    del(img_data)\n    gc.collect()\n            \n# 벵골어 손글씨 데이터 시각화\ndef visualize_bengali(img_path, count, bias = 0):\n    img_data = pd.read_parquet(img_path)\n    \n    # 뱅골어 손글씨 데이터 시각화 (matplotlib 한 화면에 여러개 그래프 그리기를 통해)\n    columns = 8\n    rows = int(count / columns) + 1\n    fig = plt.figure(figsize=(30, int(rows * 3)))\n\n    for i in range(0, count):\n        image_index = str(img_data.iloc[bias + i, 0]).split('_')[1]\n        # imshow를 하기 위해서는 nparray의 type이 uint8이어야 한다.\n        image = img_data.iloc[bias + i].values[1:].astype('uint8').reshape(RAW_IMG_ROWS, RAW_IMG_COLUMNS)\n\n        ax = fig.add_subplot(rows, columns, i + 1)\n        ax.imshow(image, cmap='gray')\n        ax.set_xlabel(image_index)\n\n    plt.show()\n    del(img_data)\n    gc.collect()\n    \n# grapheme를 입력하면 시각화해주고, grapheme의 정보를 출력해준다.\ndef show_grapheme_info(x, y, num, size):\n    plt.imshow(x[num].reshape(size), cmap='gray')\n    if (y is not list):\n        y = np.concatenate(y, axis=1)\n    \n    print(class_map[y[num] == 1])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"9613f784-03f6-4fe5-b10f-327e835ab661","_cell_guid":"bc0ca293-7a61-460e-9152-3024f61f1207","trusted":true},"cell_type":"code","source":"show_grapheme_info(x=train_x, y=train_y, num=100, size=(IMG_ROWS, IMG_COLUMNS))\n# visualize_grapheme('লা', TRAIN_IMG_PATH[0])\n# visualize_bengali(TRAIN_IMG_PATH[2], count=24, bias=11160)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 3-1. 모델 만들기 (ResNet-v1)\n<참조한 문서>\n* ResNet 논문 (Deep Residual Learning for Image Recognition)\n* https://www.researchgate.net/figure/Proposed-Modified-ResNet-18-architecture-for-Bangla-HCR-In-the-diagram-conv-stands-for_fig1_323063171\n![resnet18forbangla](https://www.researchgate.net/profile/Muhammad_Hasan19/publication/323063171/figure/fig1/AS:603178554904576@1520820382219/Proposed-Modified-ResNet-18-architecture-for-Bangla-HCR-In-the-diagram-conv-stands-for.png)"},{"metadata":{"_uuid":"1233e407-b17f-4a3e-9849-87df0e69c2ae","_cell_guid":"22852b25-4c0b-4728-bc6e-5dee93fc2b65","trusted":true},"cell_type":"code","source":"INPUT_SHAPE = (IMG_ROWS, IMG_COLUMNS, 1)\n\n# activation functions\nrelu = lambda x: keras.activations.relu(x, alpha=0.0, max_value=None, threshold=0.0)\nswish = lambda x: x * keras.activations.sigmoid(x)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# resnet convolution unit\ndef resnet_conv2d_unit(filters, kernel_size, strides):\n    return Conv2D(filters=filters,\n                  kernel_size=kernel_size,\n                  strides=strides,\n                  padding='same',\n                  kernel_initializer='he_normal',\n                  kernel_regularizer=l2(1e-4))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_resnet_v1_conv1_layer(x, activation):\n    x = resnet_conv2d_unit(filters=64, kernel_size=(7, 7), strides=(2, 2))(x)\n    x = BatchNormalization()(x)\n    x = Activation(activation)(x)\n    return x\n\ndef get_resnet_v1_conv2_layer(x, activation, count):\n    x = MaxPooling2D(pool_size=(3, 3), strides=(2, 2), padding='same')(x)\n    \n    for i in range(count):\n        shortcut = x\n        \n        x = resnet_conv2d_unit(filters=64, kernel_size=(3, 3), strides=(1, 1))(x)\n        x = BatchNormalization()(x)\n        x = Activation(activation)(x)\n        \n        x = resnet_conv2d_unit(filters=64, kernel_size=(3, 3), strides=(1, 1))(x)\n        x = BatchNormalization()(x)\n        x = Add()([x, shortcut])\n        x = Activation(activation)(x)\n        \n    return x\n\ndef get_resnet_v1_conv3_to_5_layer(x, activation, filters, count):\n    for i in range(count):\n        shortcut = x\n        \n        if i == 0:\n            x = resnet_conv2d_unit(filters=filters, kernel_size=(3, 3), strides=(2, 2))(x)\n        else:\n            x = resnet_conv2d_unit(filters=filters, kernel_size=(3, 3), strides=(1, 1))(x)\n        x = BatchNormalization()(x)\n        x = Activation(activation)(x)\n        \n        x = resnet_conv2d_unit(filters=filters, kernel_size=(3, 3), strides=(1, 1))(x)\n        x = Dropout(rate=0.2)(x)\n        x = BatchNormalization()(x)\n        if i == 0:\n            shortcut = resnet_conv2d_unit(filters=filters, kernel_size=(1, 1), strides=(2, 2))(shortcut)\n            shortcut = BatchNormalization()(shortcut)\n        x = Add()([x, shortcut])\n        x = Activation(activation)(x)\n        \n    return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# ResNet v1 구현\ndef get_resnet_v1_model(layer_num=(2, 2, 2, 2)):\n    input_tensor = Input(shape=INPUT_SHAPE, dtype='float32')\n\n    layer = get_resnet_v1_conv1_layer(input_tensor, activation=swish)\n    layer = get_resnet_v1_conv2_layer(layer, activation=swish, count=layer_num[0]) # filter_size: 64\n    layer = get_resnet_v1_conv3_to_5_layer(layer, activation=swish, filters=128, count=layer_num[1])\n    layer = get_resnet_v1_conv3_to_5_layer(layer, activation=swish, filters=256, count=layer_num[2])\n    layer = get_resnet_v1_conv3_to_5_layer(layer, activation=swish, filters=512, count=layer_num[3])\n\n    layer = Flatten()(layer)\n    layer = BatchNormalization()(layer)\n    layer = Activation(activation=swish)(layer)\n    \n    layer = Dense(1024, activation=swish, kernel_initializer='he_normal', kernel_regularizer=l2(1e-4))(layer)\n    layer = Dropout(0.2)(layer)\n    layer = BatchNormalization()(layer)\n    layer = Activation(activation=swish)(layer)\n    \n    # !! 시행 착오 !!\n    # * 다른 곳에서는 초기값을 설정할 때 he 방법을 사용했는데, 마지막 output network의 가중치를 초기화 할 때\n    #   kernel_initializer='he_normal', kernel_regularizer=l2(1e-4)를 사용해주지 않았다.\n    #   가중치를 검증된 방법을 초기화 하니까 훨씬 더 수렴이 잘 되었다.\n\n    # grapheme_root\n    output_grapheme_root       = Dense(GRAPHEME_ROOT_NUM,\n                                       name='grapheme_root',\n                                       activation='softmax', kernel_initializer='he_normal', kernel_regularizer=l2(1e-4))(layer)\n    # vowel_diacritic\n    output_vowel_diacritic     = Dense(VOWEL_DIACRITIC_NUM,\n                                       name='vowel_diacritic',\n                                       activation='softmax', kernel_initializer='he_normal', kernel_regularizer=l2(1e-4))(layer)\n    # consonant_diacritic\n    output_consonant_diacritic = Dense(CONSONANT_DIACRITIC_NUM,\n                                       name='consonant_diacritic',\n                                       activation='softmax', kernel_initializer='he_normal', kernel_regularizer=l2(1e-4))(layer)\n\n    return Model(inputs=input_tensor,\n                 outputs=[output_grapheme_root, output_vowel_diacritic, output_consonant_diacritic])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"c5404d04-62cd-48c6-bc73-7a0d03c1a647","_cell_guid":"01a3aaeb-0559-451f-a578-8005dd6fdc9b","trusted":true},"cell_type":"markdown","source":"# 3-2. 모델 만들기 (ResNet-v2)\n<참조한 문서>\n* ResNet 논문 (Deep Residual Learning for Image Recognition)\n* https://eremo2002.tistory.com/76\n* https://keras.io/examples/cifar10_resnet/\n![resnetunit](https://t1.daumcdn.net/cfile/tistory/99F0453F5C47F17413)"},{"metadata":{},"cell_type":"markdown","source":"## ResNet v2 모듈\n![resnetv2layer](https://t1.daumcdn.net/cfile/tistory/99167C335C47F0E315)"},{"metadata":{"_uuid":"7f7cee27-1c1a-419d-97ee-83488bddc276","_cell_guid":"b318592e-a0c7-47b6-a2c3-2e8b0681d664","trusted":true},"cell_type":"code","source":"# ResNet v2 first layer\n# (conv1: 7x7_64_stride2)\ndef get_resnet_v2_conv1_layer(x, activation):\n    layer = resnet_conv2d_unit(filters=64, kernel_size=(7, 7), strides=(2, 2))(x)\n    layer = BatchNormalization()(layer)\n    layer = Activation(activation)(layer)\n    return layer\n\n# ResNet v2 second layer\n# (conv2: 3x3maxpooling_stride2 -> 1x1_64 -> 3x3_64 -> 1x1_256)\ndef get_resnet_v2_conv2_layer(x, activation, count):\n    x = MaxPooling2D(pool_size=(3, 3), strides=(2, 2), padding='same')(x)\n    \n    for i in range(count):\n        shortcut = x # for skip connection(스킵 연결)\n        \n        x = resnet_conv2d_unit(filters=64, kernel_size=(1, 1), strides=(1, 1))(x)\n        x = BatchNormalization()(x)\n        x = Activation(activation)(x)\n\n        x = resnet_conv2d_unit(filters=64, kernel_size=(3, 3), strides=(1, 1))(x)\n        x = BatchNormalization()(x)\n        x = Activation(activation)(x)\n\n        x = resnet_conv2d_unit(filters=256, kernel_size=(1, 1), strides=(1, 1))(x)\n        x = BatchNormalization()(x)\n\n        # Skip Connection 할 때에는 출력 텐서의 차원과 shortcut 텐서의 차원이 서로 틀리므로 같게 만들어준다.\n        if (i == 0):\n            shortcut = resnet_conv2d_unit(filters=256, kernel_size=(1, 1), strides=(1, 1))(shortcut) \n        \n        x = Add()([x, shortcut]) # Skip Connection\n        x = Activation(activation)(x)\n\n    return x\n\n# ResNet v2 third layer\n# (conv3: 1x1_128(stride2 at i==0) -> 3x3_128 -> 1x1_512\ndef get_resnet_v2_conv3_layer(x, activation, count):\n    for i in range(count):\n        shortcut = x # for skip connection(스킵 연결)\n        \n        # output size 축소(가로 세로 절반으로)\n        if (i == 0):\n            x = resnet_conv2d_unit(filters=128, kernel_size=(1, 1), strides=(2, 2))(x)\n        else:\n            x = resnet_conv2d_unit(filters=128, kernel_size=(1, 1), strides=(1, 1))(x)\n\n        x = BatchNormalization()(x)\n        x = Activation(activation)(x)\n\n        x = resnet_conv2d_unit(filters=128, kernel_size=(3, 3), strides=(1, 1))(x)\n        x = BatchNormalization()(x)\n        x = Activation(activation)(x)\n\n        x = resnet_conv2d_unit(filters=512, kernel_size=(1, 1), strides=(1, 1))(x)\n        x = BatchNormalization()(x)\n\n        # 처음 Skip Connection 할 때에는 출력 텐서의 차원과 shortcut 텐서의 차원이 서로 틀리므로 같게 만들어준다.\n        # output size도 다르므로 stride를 2로 설정해서 output size의 크기를 1/4로 줄인다(가로와 세로가 둘 다 반씩 줄었으므로)\n        if (i == 0):\n            shortcut = resnet_conv2d_unit(filters=512, kernel_size=(1, 1), strides=(2, 2))(shortcut) \n\n        x = Add()([x, shortcut]) # Skip Connection\n        x = Activation(activation)(x)\n\n    return x\n\n# ResNet v2 fourth layer\n# (conv4: 1x1_256(stride2 at i==0) -> 3x3_256 -> 1x1_1024)\ndef get_resnet_v2_conv4_layer(x, activation, count):\n    for i in range(count):\n        shortcut = x # for skip connection(스킵 연결)\n        \n        # output size 축소(가로 세로 절반으로)\n        if (i == 0):\n            x = resnet_conv2d_unit(filters=256, kernel_size=(1, 1), strides=(2, 2))(x)\n        else:\n            x = resnet_conv2d_unit(filters=256, kernel_size=(1, 1), strides=(1, 1))(x)\n\n        x = BatchNormalization()(x)\n        x = Activation(activation)(x)\n\n        x = resnet_conv2d_unit(filters=256, kernel_size=(3, 3), strides=(1, 1))(x)\n        x = BatchNormalization()(x)\n        x = Activation(activation)(x)\n\n        x = resnet_conv2d_unit(filters=1024, kernel_size=(1, 1), strides=(1, 1))(x)\n        x = BatchNormalization()(x)\n\n        # 처음 Skip Connection 할 때에는 출력 텐서의 차원과 shortcut 텐서의 차원이 서로 틀리므로 같게 만들어준다.\n        # output size도 다르므로 stride를 2로 설정해서 output size의 크기를 1/4로 줄인다(가로와 세로가 둘 다 반씩 줄었으므로)\n        if (i == 0):\n            shortcut = resnet_conv2d_unit(filters=1024, kernel_size=(1, 1), strides=(2, 2))(shortcut) \n\n        x = Add()([x, shortcut]) # Skip Connection\n        x = Activation(activation)(x)\n\n    return x\n\n# ResNet v2 fifth layer\n# (conv4: 1x1_512(stride2 at i==0) -> 3x3_512 -> 1x1_2048)\ndef get_resnet_v2_conv5_layer(x, activation, count):\n    for i in range(count):\n        shortcut = x # for skip connection(스킵 연결)\n        \n        # output size 축소(가로 세로 절반으로)\n        if (i == 0):\n            x = resnet_conv2d_unit(filters=512, kernel_size=(1, 1), strides=(2, 2))(x)\n        else:\n            x = resnet_conv2d_unit(filters=512, kernel_size=(1, 1), strides=(1, 1))(x)\n\n        x = BatchNormalization()(x)\n        x = Activation(activation)(x)\n\n        x = resnet_conv2d_unit(filters=512, kernel_size=(3, 3), strides=(1, 1))(x)\n        x = BatchNormalization()(x)\n        x = Activation(activation)(x)\n\n        x = resnet_conv2d_unit(filters=2048, kernel_size=(1, 1), strides=(1, 1))(x)\n        x = BatchNormalization()(x)\n\n        # 처음 Skip Connection 할 때에는 출력 텐서의 차원과 shortcut 텐서의 차원이 서로 틀리므로 같게 만들어준다.\n        # output size도 다르므로 stride를 2로 설정해서 output size의 크기를 1/4로 줄인다(가로와 세로가 둘 다 반씩 줄었으므로)\n        if (i == 0):\n            shortcut = resnet_conv2d_unit(filters=2048, kernel_size=(1, 1), strides=(2, 2))(shortcut) \n\n        x = Add()([x, shortcut]) # Skip Connection\n        x = Activation(activation)(x)\n\n    return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# ResNet 구현\n# learning rate reduction도 적용하기\ndef get_resnet_v2_50_layer_model():\n    input_tensor = Input(shape=INPUT_SHAPE, dtype='float32')\n\n    layer = get_resnet_v2_conv1_layer(input_tensor, activation=swish)\n    layer = get_resnet_v2_conv2_layer(layer, activation=swish, count=3)\n    layer = get_resnet_v2_conv3_layer(layer, activation=swish, count=4)\n    layer = get_resnet_v2_conv4_layer(layer, activation=swish, count=6)\n    layer = get_resnet_v2_conv5_layer(layer, activation=swish, count=3)\n\n    layer = GlobalAveragePooling2D()(layer)\n\n    # !! 시행 착오 !!\n    # * 다른 곳에서는 초기값을 설정할 때 he 방법을 사용했는데, 마지막 output network의 가중치를 초기화 할 때\n    #   kernel_initializer='he_normal', kernel_regularizer=l2(1e-4)를 사용해주지 않았다.\n    #   가중치를 검증된 방법을 초기화 하니까 훨씬 더 수렴이 잘 되었다.\n\n    # grapheme_root\n    output_grapheme_root       = Dense(GRAPHEME_ROOT_NUM,\n                                       name='grapheme_root',\n                                       activation='softmax', kernel_initializer='he_normal', kernel_regularizer=l2(1e-4))(layer)\n    # vowel_diacritic\n    output_vowel_diacritic     = Dense(VOWEL_DIACRITIC_NUM,\n                                       name='vowel_diacritic',\n                                       activation='softmax', kernel_initializer='he_normal', kernel_regularizer=l2(1e-4))(layer)\n    # consonant_diacritic\n    output_consonant_diacritic = Dense(CONSONANT_DIACRITIC_NUM,\n                                       name='consonant_diacritic',\n                                       activation='softmax', kernel_initializer='he_normal', kernel_regularizer=l2(1e-4))(layer)\n\n    return Model(inputs=input_tensor,\n                 outputs=[output_grapheme_root, output_vowel_diacritic, output_consonant_diacritic])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"c74a6748-789d-4c3f-9ce6-a4e3f465c2d7","_cell_guid":"7056a2d4-ec1e-4fae-b189-eae2b85e8551","trusted":true},"cell_type":"markdown","source":"# 3-3. 모델 만들기 (VGGNet)"},{"metadata":{"_uuid":"efaefe18-b51d-4523-839e-b5bb6dc94d6f","_cell_guid":"cf1cfbcf-f7d1-4343-9c94-0dbb55f9c8a8","trusted":true},"cell_type":"code","source":"# VGGNet 구현\ndef get_vggnet_model():\n    activation = swish\n    input_tensor = Input(shape=INPUT_SHAPE, dtype='float32')\n    \n    # input layer\n    model = Conv2D(filters=32, kernel_size=(3, 3), padding='same', activation=activation)(input_tensor)\n\n    # first block\n    model = Conv2D(filters=32, kernel_size=(3, 3), padding='same', activation=activation)(model)\n    model = MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')(model)\n    model = BatchNormalization(momentum=0.15)(model)\n\n    # second block\n    model = Conv2D(filters=64, kernel_size=(3, 3), padding='same', activation=activation)(model)\n    model = Conv2D(filters=64, kernel_size=(3, 3), padding='same', activation=activation)(model)\n    model = MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')(model)\n    model = BatchNormalization(momentum=0.15)(model)\n    model = Dropout(rate=0.3)(model)\n\n    # third block\n    model = Conv2D(filters=128, kernel_size=(3, 3), padding='same', activation=activation)(model)\n    model = Conv2D(filters=128, kernel_size=(3, 3), padding='same', activation=activation)(model)\n    model = MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')(model)\n    model = BatchNormalization(momentum=0.15)(model)\n    model = Dropout(rate=0.3)(model)\n\n    # forth block\n    model = Conv2D(filters=256, kernel_size=(3, 3), padding='same', activation=activation)(model)\n    model = Conv2D(filters=256, kernel_size=(3, 3), padding='same', activation=activation)(model)\n    model = Conv2D(filters=256, kernel_size=(3, 3), padding='same', activation=activation)(model)\n    model = MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')(model)\n    model = BatchNormalization(momentum=0.15)(model)\n\n    # fifth block\n    model = Conv2D(filters=256, kernel_size=(3, 3), padding='same', activation=activation)(model)\n    model = Conv2D(filters=256, kernel_size=(3, 3), padding='same', activation=activation)(model)\n    model = Conv2D(filters=256, kernel_size=(3, 3), padding='same', activation=activation)(model)\n    model = MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')(model)\n    model = Dropout(rate=0.3)(model)\n\n    # fully connected\n    model = Flatten()(model)\n    model = Dense(2048, activation=activation)(model)\n    model = Dropout(rate=0.2)(model)\n    model = Dense(1024, activation=activation)(model)\n    \n    # classification block\n    output_grapheme_root       = Dense(GRAPHEME_ROOT_NUM, name='grapheme_root', activation='softmax')(model)\n    output_vowel_diacritic     = Dense(VOWEL_DIACRITIC_NUM, name='vowel_diacritic', activation='softmax')(model)\n    output_consonant_diacritic = Dense(CONSONANT_DIACRITIC_NUM, name='consonant_diacritic', activation='softmax')(model)\n\n    return Model(inputs=input_tensor,\n                 outputs=[output_grapheme_root, output_vowel_diacritic, output_consonant_diacritic])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 3-4. 모델 만들기 (EfficientNet)"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Generalized mean pool - GeM\ngm_exp = tf.Variable(3.0, dtype = tf.float32)\ndef generalized_mean_pool_2d(X):\n    pool = (tf.reduce_mean(tf.abs(X**(gm_exp)), \n                        axis = [1, 2], \n                        keepdims = False) + 1.e-7)**(1./gm_exp)\n    return pool","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# EfficientNet 만들기(외부 데이터 불러와서)\ndef get_efficiennet_model():\n    activation = swish\n    input_tensor = Input(shape=INPUT_SHAPE, dtype='float32')\n    \n    # Create and Compile Model and show Summary\n    model = efn.EfficientNetB3(weights=None,\n                               include_top=False,\n                               input_tensor=input_tensor,\n                               pooling=None,\n                               classes=None)\n    \n    # UnFreeze all layers\n    for layer in model.layers:\n        layer.trainable = True\n    \n    # GeM\n    lambda_layer = Lambda(generalized_mean_pool_2d)\n    lambda_layer.trainable_weights.extend([gm_exp])\n    x = lambda_layer(model.output)\n    \n    # multi output\n    output_grapheme_root       = Dense(GRAPHEME_ROOT_NUM, name='grapheme_root', activation='softmax')(x)\n    output_vowel_diacritic     = Dense(VOWEL_DIACRITIC_NUM, name='vowel_diacritic', activation='softmax')(x)\n    output_consonant_diacritic = Dense(CONSONANT_DIACRITIC_NUM, name='consonant_diacritic', activation='softmax')(x)\n\n    return Model(inputs=input_tensor,\n                 outputs=[output_grapheme_root, output_vowel_diacritic, output_consonant_diacritic])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"fcbbdb75-9d30-427c-a9b4-bb398c14505a","_cell_guid":"7b56b324-7e03-43e0-bf43-bd2466cbce60","trusted":true},"cell_type":"markdown","source":"# 4. 모델 학습시키기"},{"metadata":{"_uuid":"004313d8-de1c-4371-8b55-daa2083c550e","_cell_guid":"40b02cea-87ed-4326-9256-9c7989d49fbc","trusted":true},"cell_type":"code","source":"# model = get_resnet_v1_model(layer_num=(2, 2, 2, 2)) # 18-layer\n# model = get_resnet_v1_model(layer_num=(3, 4, 6, 3)) # 34-layer\n# model = get_resnet_v2_50_layer_model() # 54-layer\nmodel = get_efficiennet_model() # efficient-net\nprint('total', model.count_params(), 'parameter(s)')\n\n# optimizer = SGD(lr=0.01, momentum=0.9) # Stochastic gradient descent\noptimizer = 'adam'\nmodel.compile(loss='categorical_crossentropy', optimizer=optimizer, metrics=['accuracy'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plot_model(model, to_file='model.png')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d0998ef3-0f54-466b-92d7-488dc032f229","_cell_guid":"1c7a5ade-1bf7-4e35-88e0-32a1ada31773","trusted":true},"cell_type":"code","source":"batch_size = 256\nepochs = 12\nvalidation_split = 0.2\n\nreduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3, min_lr=0.001,\n                              verbose=1, mode='auto')\ncallbacks = [reduce_lr]\n\ndef start_train(train_x, train_y):\n    hist = model.fit(train_x, train_y,\n                     batch_size=batch_size,\n                     epochs=epochs,\n                     validation_split=validation_split,\n                     callbacks=callbacks,\n                    )\n\ndef start_train_with_augmentation(train_x, train_y):   \n    # 데이터 증강(실시간으로 처리) 요소\n    datagen = ImageDataGenerator(\n        rotation_range=10, # -10~10도 회전\n        zoom_range=0.3     # 0.7~1.3배 확대\n    )\n    \n    # Fit the model\n    history = model.fit_generator(datagen.flow(train_x, {'grapheme_root': train_y[0], 'vowel_diacritic': train_y[1], 'consonant_diacritic': train_y[2]}, batch_size=batch_size),\n                                  epochs = epochs, \n                                  steps_per_epoch=x_train.shape[0] // batch_size, \n                                  callbacks=callbacks)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"9a6222bc-f979-4466-90ae-97c676dce226","_cell_guid":"082e36b4-f951-4bf2-a599-3cb88b7be7d9","trusted":true},"cell_type":"code","source":"# 학습 시작\nprint_log('train 0 start')\nstartTime = time.time()\nstart_train(train_x, train_y)\n# start_train_with_augmentation(train_x, train_y)\ndel(train_x)\ndel(train_y)\ngc.collect()\nprint_log('train 0 end')\n\nfor i in range(1, 4):\n    print_log('train ' + str(i) + ' start')\n    _train_x, _train_y = load_train_data_v2(TRAIN_IMG_PATH[i], output_img_size=(IMG_ROWS, IMG_COLUMNS), split_y=True)\n    start_train(_train_x, _train_y)\n#     start_train_with_augmentation(_train_x, _train_y)\n    del(_train_x)\n    del(_train_y)\n    gc.collect()\n    print_log('train ' + str(i) + ' end')\n\nprint('train elapsed time:', time.time() - startTime)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.save('efficientnet_twotimes.h5')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"87423be9-19db-42a7-a43c-28d2302af5e2","_cell_guid":"ef70346d-88d2-4cb2-9502-fac0ccd4b10a","trusted":true},"cell_type":"markdown","source":"# 5. 예측 및 결과 제출"},{"metadata":{"_uuid":"514de81d-697e-4639-b4f9-4eaad4da060c","_cell_guid":"c108f88d-9964-441d-9dda-f50b8435c43c","trusted":true},"cell_type":"code","source":"# test_image_data.parquet 파일 경로\nTEST_IMG_PATH = ['/kaggle/input/bengaliai-cv19/test_image_data_0.parquet',\n           '/kaggle/input/bengaliai-cv19/test_image_data_1.parquet',\n           '/kaggle/input/bengaliai-cv19/test_image_data_2.parquet',\n           '/kaggle/input/bengaliai-cv19/test_image_data_3.parquet']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# train_image_data_x.parquet을 불러와서 numpy 배열로 되어 있는 이미지와, parquet 파일에 있는 Image_Id를 반환한다.\ndef get_img_data_with_img_id(img_path):\n    data = pd.read_parquet(img_path)\n    \n    img_id = np.empty(shape=data.shape[0], dtype='object')\n    img_id = data['image_id']\n    \n    # pandas로 데이터를 불러와 numpy 배열로 바꾸는 작업이 필요하다.\n    # pandas로 데이터를 조작할 때에는 속도가 너무 느리다(특히 iloc). 따라서 numpy 배열로 바꾸어 반환해서 속도 향상을 꾀한다.\n    img_data = data[data.columns[1:]]\n    img_data = img_data.to_numpy(dtype='uint8')\n    img_data = img_data.reshape(-1, RAW_IMG_ROWS, RAW_IMG_COLUMNS)\n    \n    del data\n    gc.collect()\n    \n    return img_data, img_id","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 테스트 데이터 불러오기 Version 1\ndef load_test_data_v1(test_img_path):\n    img_data, start_id, element_num = get_img_data(test_img_path)\n    indices = np.array(range(start_id, start_id + element_num), dtype='uint32')\n\n    # create test_x\n    img_rows = RAW_IMG_ROWS // 2\n    img_columns = RAW_IMG_COLUMNS // 2\n    test_x = np.empty(dtype='uint8', shape=(element_num, img_rows, img_columns))\n    \n    for i in range(test_x.shape[0]): # resize image\n        test_x[i] = cv2.resize(img_data[i], dsize=(img_columns, img_rows)) # width * height\n    \n    del img_data\n    gc.collect()\n\n    # keras CNN 모델에 넣기 위한 차원 및 데이터타입 정리\n    test_x = test_x.reshape((-1, img_rows, img_columns, 1))\n    test_x = test_x.astype('float32')\n    test_x = test_x / 255.0\n\n    return test_x, indices","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 테스트 데이터 불러오기 Version 2\ndef load_test_data_v2(test_img_path, output_img_size):\n    img_data, img_id = get_img_data_with_img_id(test_img_path)\n    \n    element_num = img_data.shape[0]\n    img_rows    = output_img_size[1]\n    img_columns = output_img_size[0]\n    \n    # create test_x\n    test_x = np.empty(dtype='uint8', shape=(element_num, img_rows, img_columns))\n    for i in range(test_x.shape[0]):\n        img = img_data[i]\n        x1, y1, x2, y2 = get_img_roi(img) # get ROI(region of interest)\n        img = img[y1:y2, x1:x2] # crop image\n        img = cv2.resize(img, dsize=(img_columns, img_rows)) # resize image (width * height)\n        test_x[i] = img\n        \n    # keras CNN 모델에 넣기 위한 차원 및 데이터타입 정리\n    test_x = test_x.reshape((-1, img_rows, img_columns, 1))\n    test_x = test_x.astype('float32')\n    test_x = test_x / 255.0\n        \n    return test_x, img_id","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"749f9927-3a34-4c64-bb07-1808e1babfa3","_cell_guid":"269206b9-aac3-4cbb-88dd-07ee12ac5c58","trusted":true},"cell_type":"markdown","source":"시행착오 (Notebook Exceeded Allowed Compute 문제 해결)\n1. test의 paraquet안에 들어 있는 정보가 얼마 되지 않음. 총 12개.\n1. 그래서 그냥 4개의 데이터 파일(test_image_data_x.parquet)을 모두 하나로 합쳤음.\n1. 커널은 메모리 문제 없이 잘 돌아갔는데, submission 할 때 메모리 초과가 됨.\n1. 생각해보니 submission에서 test data set의 개수는 모름. 아마 엄청 많을 것임.\n1. 그래서 메모리가 부족한 것이었고, 그냥 따로 따로 불러와서 모델로 예측함.\n\n시행착오 (Notebook Timeout 문제 해결)\n1. pandas의 dataframe에서 append 함수를 쓰는 것은 시간 소모가 너무 크다. 약 5만개의 데이터를 append 하는데 거의 15분이 걸렸다.\n1. 하지만, 파이썬에서 제공하는 list()를 이용해서 데이터를 1차원으로 append를 하고, numpy로 2차원 배열로 reshape 해준다.\n1. 그렇게 만든 numpy array를 다시 pandas로 변환해서 csv 파일을 추출하면 훨신 빠르다. 약 5만개의 데이터를 처리하는데 40초 걸림. 23배 시간 절약."},{"metadata":{"trusted":true},"cell_type":"code","source":"def predict_model(y_split=True):\n    # 리스트가 접근 속도가 빠름(pandas의 dataframe은 너무 느림)\n    predict_result = list()\n    \n    for p in range(4):\n        test_x, img_id = load_test_data_v2(TRAIN_IMG_PATH[p], output_img_size=(IMG_ROWS, IMG_COLUMNS))\n        \n        # 테스트 데이터 예측\n        predict = model.predict(test_x)\n        if y_split:\n            predict_consonant_diacritic = predict[2].argmax(axis=1)\n            predict_grapheme_root       = predict[0].argmax(axis=1)\n            predict_vowel_diacritic     = predict[1].argmax(axis=1)\n        else:\n            predict_consonant_diacritic = predict[:, 179:].argmax(axis=1)\n            predict_grapheme_root       = predict[:, :168].argmax(axis=1)\n            predict_vowel_diacritic     = predict[:, 168:179].argmax(axis=1)\n\n        # 예측값 기록\n        for i in range(test_x.shape[0]):\n            row_id_prefix = img_id[i] + '_'\n\n            predict_result.append(row_id_prefix + 'consonant_diacritic')\n            predict_result.append(predict_consonant_diacritic[i])\n\n            predict_result.append(row_id_prefix + 'grapheme_root')\n            predict_result.append(predict_grapheme_root[i])\n\n            predict_result.append(row_id_prefix + 'vowel_diacritic')\n            predict_result.append(predict_vowel_diacritic[i])\n            \n        print('predict test image data:', p)\n\n    submission = pd.DataFrame(np.array(predict_result).reshape(-1, 2), columns=['row_id', 'target'])\n    submission.set_index('row_id', inplace=True)\n    \n    return submission","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"0e319cc1-1b60-451f-a9c1-e7a284e67311","_cell_guid":"396256ab-10ba-44ef-b672-b2b257874bf1","trusted":true},"cell_type":"code","source":"# 시간 측정\nstartTime = time.time()\n\n# 예측 시작\nsubmission = predict_model(y_split=True)\n\nprint('predict elapsed time:', time.time() - startTime)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ca310a90-b6d2-4c82-bb9d-839582aac6dc","_cell_guid":"a122adc9-f1a5-455a-9530-43c00f59cdc6","trusted":true},"cell_type":"code","source":"print(submission)\nsubmission.to_csv('submission.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_x, img_id = load_test_data_v2(TEST_IMG_PATH[p], output_img_size=(IMG_ROWS, IMG_COLUMNS))","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}