{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Import"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import cv2\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport json\nimport os\nimport random\nimport warnings\nimport glob\nimport shutil\nfrom pathlib import Path\nfrom tensorflow.random import set_seed\nfrom tqdm import tqdm, tqdm_notebook\nfrom tensorflow.python.client import device_lib\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Dropout, Flatten, Dense, GlobalAveragePooling2D, GlobalMaxPooling2D, concatenate, BatchNormalization\nfrom tensorflow.keras import optimizers\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import EarlyStopping, TensorBoard, ModelCheckpoint\nfrom tensorflow.keras.utils import plot_model\nfrom tensorflow.keras.preprocessing.image import array_to_img, img_to_array, load_img\nfrom tensorflow.keras.models import model_from_json\nfrom sklearn.model_selection import train_test_split\nimport tensorflow.keras.backend as K\nimport tensorflow as tf\nimport tensorflow.keras.applications.efficientnet as efn","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Settings"},{"metadata":{"trusted":true},"cell_type":"code","source":"CFG = {\n    'load_dir':\"/kaggle/input/ranzcr-clip-catheter-line-classification/\",\n    'train_csv_name':\"train.csv\",\n    'submit_csv_name':\"sample_submission.csv\",\n    'batch_size':32,\n    'epoch':20,\n    'seed':20210222,\n    'size_x':(224, 240, 260, 300, 380, 456, 528, 600),\n    'size_y':(224, 240, 260, 300, 380, 456, 528, 600),\n    'channel':3,\n}","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Functions"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def seed_set(seed):\n    \"\"\"\n    Seed値の固定\n    \"\"\"\n    # tendorflow seed\n    set_seed(seed)\n    # for numpy.random\n    np.random.seed(seed)\n    # for built-in random\n    random.seed(seed)\n    # for hash seed\n    os.environ[\"PYTHONHASHSEED\"] = str(seed)\n\ndef build_decoder(with_labels=True, target_size=(300, 300), ext='jpg'):\n    \"\"\"\n    ジェネレータ内で画像パスから読み込んで前処理する\n    \"\"\"\n    def decode(path):\n        file_bytes = tf.io.read_file(path) # 入力ファイル名の内容全体を読み込んで出力\n\n        if ext == 'png':\n            img = tf.image.decode_png(file_bytes, channels=3)  # PNGでエンコードされた画像を uint8 または uint16 のテンソルにデコード\n        elif ext in ['jpg', 'jpeg']:\n            img = tf.image.decode_jpeg(file_bytes, channels=3)  # JPEGでエンコードされた画像をuint8テンソルにデコード\n        else:\n            raise ValueError(\"Image extension not supported\")\n        \n        img = tf.cast(img, tf.float32) / 255.0 # Floate型に変換して正規化\n        img = tf.image.resize(img, target_size) # リサイズ処理\n        return img\n    \n    def decode_with_labels(path, label):\n        return decode(path), label\n    \n    return decode_with_labels if with_labels else decode\n\n# データ増強\ndef build_augmenter(with_labels=True):\n    \"\"\"\n    ジェネレータ内でデータ増強関数\n    \"\"\"\n    def augment(img):\n        img = tf.image.random_flip_left_right(img)  # ランダムで水平方向に反転\n        img = tf.image.random_flip_up_down(img)  # ランダムで垂直方向に反転\n        img = tf.image.random_saturation(img, 0.8, 1.2)  # RGB画像の彩度を調整\n        img = tf.image.random_brightness(img, 0.2) # 画像の明るさを調整\n        img = tf.image.random_contrast(img, 0.8, 1.2)  # 画像のコントラストを調整\n        img = tf.image.random_hue(img, 0.2)  # RGB画像の色相を調整\n        return img\n    \n    def augment_with_labels(img, label):\n        return augment(img), label\n    \n    return augment_with_labels if with_labels else augment\n\ndef build_dataset(paths, labels=None, bsize=32, cache=True,\n                  decode_fn=None, augment_fn=None,\n                  augment=True, repeat=True, shuffle=1024, \n                  cache_dir=\"\"):\n    \"\"\"\n    パイプラインを構築してデータを吐き出すジェネレータを作成する関数。\n    \"\"\"\n    if cache_dir != \"\" and cache is True:\n        os.makedirs(cache_dir, exist_ok=True)\n    \n    if decode_fn is None:\n        decode_fn = build_decoder(labels is not None)\n    \n    if augment_fn is None:\n        augment_fn = build_augmenter(labels is not None)\n    \n    AUTO = tf.data.experimental.AUTOTUNE\n    slices = paths if labels is None else (paths, labels)\n    \n    dset = tf.data.Dataset.from_tensor_slices(slices)  # データセット作成\n    dset = dset.map(decode_fn, num_parallel_calls=AUTO)  # 値を直接変えるパイプライン(※画像の前処理(リサイズ・正規化)を行う)\n    dset = dset.cache(cache_dir) if cache else dset  # 高速化のため前処理後のデータをキャッシュして保持しておく。(2エポック目からキャッシュからデータ取得)\n    dset = dset.map(augment_fn, num_parallel_calls=AUTO) if augment else dset  # 値を直接変えるパイプライン(データ増強)\n    dset = dset.repeat() if repeat else dset  # データを指定回数Repeatする\n    dset = dset.shuffle(shuffle) if shuffle else dset  # データをshuffleする\n    dset = dset.batch(bsize).prefetch(AUTO) # モデルの訓練中にバックグラウンドでデータセットがバッチを取得できます。\n    return dset\n\ndef load_model(h_size_x, h_size_y, h_channel, h_n_labels, h_trainable=True):\n    \"\"\"\n    imagenet学習済のEfficientNetB2を構築する。\n    \"\"\"\n    model_path = '../input/tfkeras-efficientnet-weights/efficientnetb2_notop.h5'  # imagenet\n    n_labels = h_n_labels\n    eff_net = efn.EfficientNetB2(weights=model_path, include_top=False, input_shape=(h_size_x, h_size_y, h_channel))\n    eff_net.trainable = h_trainable\n    x = eff_net.output\n    x1 = GlobalAveragePooling2D()(x)\n    x2 = GlobalMaxPooling2D()(x)\n    x = concatenate([x1, x2])\n    x = BatchNormalization()(x)\n    x = Dense(512, activation='elu')(x)\n    x = Dropout(0.5)(x)\n    x = BatchNormalization()(x)\n    x = Dense(252, activation='elu')(x)\n    x = Dropout(0.5)(x)\n    \"\"\"\n    #If you don't need to learn alpha , you can choose below way too.\n    alpha = 30\n    def l2_constrain(x):\n        return alpha * K.l2_normalize(x, axis=1)\n    model.add(layers.Lambda(l2_constrain))\n    \"\"\"\n\n    #This is custom layer way\n    # If you use trainable variables, you should write this way\n    # ref : https://www.tensorflow.org/api_docs/python/tf/keras/layers/Lambda#variables\n    class L2ConstrainLayer(tf.keras.layers.Layer):\n        def __init__(self):\n            super(L2ConstrainLayer, self).__init__()\n            self.alpha = tf.Variable(30.)\n\n        def call(self, inputs):\n            #about l2_normalize https://www.tensorflow.org/api_docs/python/tf/keras/backend/l2_normalize?hl=ja\n            return K.l2_normalize(inputs, axis=1) * self.alpha\n\n    x = L2ConstrainLayer()(x)\n    # マルチラベルの場合はsigmoidを設定する\n    predictions = Dense(n_labels, activation=\"sigmoid\")(x)\n    model = Model(inputs = eff_net.input, outputs = predictions)\n    \n    print(model.summary())\n\n    return model","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 各種パス取得とラベル生成"},{"metadata":{"trusted":true},"cell_type":"code","source":"# データの読み込み\nload_dir = CFG['load_dir']\n\n# trainデータのパスを読み込み\ndf = pd.read_csv(CFG['load_dir'] + CFG['train_csv_name'])\ntrain_paths = CFG['load_dir'] + \"train/\" + df['StudyInstanceUID'] + '.jpg'\n\n# testデータのパスを読み込み\nsub_df = pd.read_csv(CFG['load_dir'] + CFG['submit_csv_name'])\ntest_paths = CFG['load_dir'] + \"test/\" + sub_df['StudyInstanceUID'] + '.jpg'\n\n# labelデータを生成\nlabel_cols = sub_df.columns[1:]\nlabels = df[label_cols].values","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 学習データセットを生成する"},{"metadata":{"trusted":true},"cell_type":"code","source":"# seed値を固定する\nseed_set(CFG[\"seed\"])\n\n# train_test_split\n(train_paths, valid_paths, train_labels, valid_labels) = train_test_split(train_paths, labels, test_size=0.2, random_state=CFG[\"seed\"])\n\n# 学習データの前処置の定義\ndecoder = build_decoder(with_labels=True, target_size=(CFG[\"size_x\"][2], CFG[\"size_y\"][2]))\n\n# テストデータの前処置の定義\ntest_decoder = build_decoder(with_labels=False, target_size=(CFG[\"size_x\"][2], CFG[\"size_y\"][2]))\n\n# trainデータgeneratorを生成\ndtrain = build_dataset(\n    train_paths, train_labels, bsize=CFG['batch_size'], augment=True,\n    cache_dir='/kaggle/tf_cache', decode_fn=decoder\n)\n\n# validtionデータgeneratorを生成\ndvalid = build_dataset(\n    valid_paths, valid_labels, bsize=CFG['batch_size'], \n    repeat=False, shuffle=False, augment=False, \n    cache_dir='/kaggle/tf_cache', decode_fn=decoder\n)\n\n# testデータgeneratorを生成\ndtest = build_dataset(\n    test_paths, bsize=CFG['batch_size'], repeat=False, \n    shuffle=False, augment=False, cache=False, \n    decode_fn=test_decoder\n)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 学習"},{"metadata":{"trusted":true},"cell_type":"code","source":"# １エポックで全画像を処置する\nsteps_per_epoch = train_paths.shape[0] // CFG['batch_size']\n\n# imagenet学習済のモデルを取得\nmodel = load_model(CFG[\"size_x\"][2], CFG[\"size_y\"][2], CFG[\"channel\"], labels.shape[1], h_trainable=True)\n\n# モデルコンパイル（マルチラベルのため、lossはbinary_crossentropy）\nmodel.compile(\n    optimizer=optimizers.RMSprop(),\n    loss='binary_crossentropy', \n    metrics=[tf.keras.metrics.AUC(multi_label=True)]\n)\n\n# checkpointファイルを生成するコールバック\ncheckpoint = tf.keras.callbacks.ModelCheckpoint('model.h5', save_best_only=True, monitor='val_auc', mode='max')\n\n# 学習が停滞したら、学習率を半減するコールバック\nlr_reducer = tf.keras.callbacks.ReduceLROnPlateau(monitor=\"val_auc\", patience=3, min_lr=1e-6, mode='max')\n\n# 学習\nhistory = model.fit(\n    dtrain, \n    epochs=CFG[\"epoch\"],\n    verbose=1,\n    callbacks=[checkpoint, lr_reducer],\n    steps_per_epoch=steps_per_epoch,\n    validation_data=dvalid)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 後処理"},{"metadata":{"trusted":true},"cell_type":"code","source":"# 最も精度がよいEpocの重みで更新する。\nmodel.load_weights('model.h5')\n# モデルの学習状況を保存する\nhist_df = pd.DataFrame(history.history)\nhist_df.to_csv('history.csv')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# 推論"},{"metadata":{"trusted":true},"cell_type":"code","source":"sub_df[label_cols] = model.predict(dtest, verbose=1)\nsub_df.to_csv('submission.csv', index=False)\n\nsub_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","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}