{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Kerasを利用して「RSNA Screening Mammography Breast Cancer Detection」を実施\n\n* ポイント\n    * データの読み込み、学習、予測の一連の流れを実施\n    * Keras の `ImageDataGenerator` を利用してデータ拡張を実施\n    * 点数が悪く原因は調査中\n","metadata":{}},{"cell_type":"markdown","source":"# DICOM データを画像形式に変換\n\n## DICOM データの変換用モジュールを読み込む","metadata":{}},{"cell_type":"code","source":"!pip install /kaggle/input/dicomsdl-offline-installer/dicomsdl-0.109.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl","metadata":{"execution":{"iopub.status.busy":"2023-01-18T15:13:37.414739Z","iopub.execute_input":"2023-01-18T15:13:37.415224Z","iopub.status.idle":"2023-01-18T15:14:10.686976Z","shell.execute_reply.started":"2023-01-18T15:13:37.415116Z","shell.execute_reply":"2023-01-18T15:14:10.68535Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 変換用関数を準備\n\n* `read_xray`: 画像の読み込み\n* `crop_image`: 枠の削除\n* `img2roi`: 不要部分の削除\n* `resize_and_save`: 他の関数を利用してDICOMデータを変換し保存","metadata":{}},{"cell_type":"code","source":"# https://www.kaggle.com/code/awsaf49/rsna-bcd-efficientnet-tf-tpu-1vm-infer#Meta-Data\n\nimport numpy as np\nimport dicomsdl\nfrom tqdm import tqdm\nfrom joblib import Parallel, delayed\nimport cv2\n\n\ndef read_xray(path, fix_monochrome = True):\n    dicom = dicomsdl.open(path)\n    data = dicom.pixelData(storedvalue=False)  # storedvalue = True for int16 return otherwise float32\n    data = data - np.min(data)\n    data = data / np.max(data)\n    if fix_monochrome and dicom.PhotometricInterpretation == \"MONOCHROME1\":\n        data = 1.0 - data\n    return data\n\ndef crop_image(image, crop_size=5):\n    # 画像によっては不要な枠があるので、取り除く\n    image = image[crop_size:-crop_size, crop_size:-crop_size]\n    return image\n\ndef img2roi(image):\n    output= cv2.connectedComponentsWithStats((image > 0.05).astype(np.uint8)[:, :], 8, cv2.CV_32S)\n    stats = output[2] # left, top, width, height, area_size\n    \n    idx = stats[1:, 4].argmax() + 1\n    x1, y1, w, h = stats[idx][:4]\n    x2 = x1 + w\n    y2 = y1 + h\n    \n    image_fit = image[y1: y2, x1: x2]\n    \n    return image_fit\n\n\ndef resize_and_save(file_path):\n    image = read_xray(file_path)\n    h, w = image.shape[:2]  # orig hw\n    if ASPECT_RATIO:\n        r = RESIZE_DIM / max(h, w)  # resize image to img_size\n        interp = cv2.INTER_LINEAR\n        if r != 1:  # always resize down, only resize up if training with augmentation\n            image = cv2.resize(image, (int(w * r), int(h * r)), interpolation=interp)\n    else:\n        image = cv2.resize(image, (RESIZE_DIM, RESIZE_DIM), cv2.INTER_LINEAR)\n    \n    image = crop_image(image)\n    image = img2roi(image)\n    image = cv2.resize(image, TARGET_SIZE[::-1], cv2.INTER_LINEAR)\n    \n    sub_path = file_path.split(\"/\",4)[-1].split('.dcm')[0] + '.png'\n    infos = sub_path.split('/')\n    pid = infos[-2]\n    iid = infos[-1]; iid = iid.replace('.png','')\n    new_path = os.path.join(IMG_DIR, sub_path)\n    os.makedirs(new_path.rsplit('/',1)[0], exist_ok=True)\n    cv2.imwrite(new_path, image)\n    return pid,iid,w,h\n","metadata":{"execution":{"iopub.status.busy":"2023-01-18T15:14:10.68941Z","iopub.execute_input":"2023-01-18T15:14:10.689786Z","iopub.status.idle":"2023-01-18T15:14:10.973105Z","shell.execute_reply.started":"2023-01-18T15:14:10.689749Z","shell.execute_reply":"2023-01-18T15:14:10.972102Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 設定値\n\n* ASPECT_RATIO: 読み込み時にアスペクト比を保持した画像の縮小をするかの指定\n* RESIZE_DIM: 読み込み時に変換する画像サイズ\n* TARGET_SIZE: 出力時に指定する画像サイズ\n* data_dir: 入力データの保存先\n* tmp_output_dir: DICOM データを画像データに変換し、保存する先","metadata":{}},{"cell_type":"code","source":"import os\n\nASPECT_RATIO = True\nRESIZE_DIM = 2048\nTARGET_SIZE = [512, 512]\ndata_dir = '/kaggle/input/rsna-breast-cancer-detection'\ntmp_output_dir = '/kaggle/tmp/output'\n\ntrain_dir = os.path.join(tmp_output_dir, 'train_images')\ntest_dir = os.path.join(tmp_output_dir, 'test_images')\n","metadata":{"execution":{"iopub.status.busy":"2023-01-18T15:14:10.97443Z","iopub.execute_input":"2023-01-18T15:14:10.974763Z","iopub.status.idle":"2023-01-18T15:14:10.980383Z","shell.execute_reply.started":"2023-01-18T15:14:10.974734Z","shell.execute_reply":"2023-01-18T15:14:10.979197Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 学習データの読み込み\n\nCSVファイルを読み込み  \nパスも取得","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\n\ntrain_df = pd.read_csv(f\"{data_dir}/train.csv\")\n\ntrain_df['dcm_path'] = train_df.apply(\n    lambda i: os.path.join(\n        f\"{data_dir}\", 'train_images', str(i['patient_id']), str(i['image_id']) + '.dcm'\n    ), axis=1\n)\n\ntrain_df","metadata":{"execution":{"iopub.status.busy":"2023-01-18T15:14:10.983343Z","iopub.execute_input":"2023-01-18T15:14:10.983822Z","iopub.status.idle":"2023-01-18T15:14:12.150722Z","shell.execute_reply.started":"2023-01-18T15:14:10.983776Z","shell.execute_reply":"2023-01-18T15:14:12.149545Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 画像の変換（乳がんポジティブ）\n\n乳がんポジティブのデータとネガティブのデータをそれぞれ別に保存する  \n`Parallel`を利用して、並列実行する","metadata":{}},{"cell_type":"code","source":"%%time\n\nIMG_DIR = os.path.join(train_dir, 'cancer')\n\nfile_paths = train_df[train_df['cancer']==1]['dcm_path'].tolist()\nimgsize = Parallel(\n    n_jobs=2,backend='threading')(delayed(resize_and_save)(file_path)\\\n    for file_path in tqdm(file_paths, leave=True, position=0))","metadata":{"execution":{"iopub.status.busy":"2023-01-18T15:14:12.152252Z","iopub.execute_input":"2023-01-18T15:14:12.152589Z","iopub.status.idle":"2023-01-18T15:28:28.815428Z","shell.execute_reply.started":"2023-01-18T15:14:12.15256Z","shell.execute_reply":"2023-01-18T15:28:28.813275Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 画像の変換（乳がんネガティブ）\n\n乳がんポジティブのデータとネガティブのデータをそれぞれ別に保存する  \n`Parallel`を利用して、並列実行する","metadata":{}},{"cell_type":"code","source":"%%time\n\nIMG_DIR = os.path.join(train_dir, 'negative')\n\nfile_paths = train_df[train_df['cancer']==0].iloc[:10000]['dcm_path'].tolist()\n# file_paths = train_df[train_df['cancer']==0]['dcm_path'].tolist()\nimgsize = Parallel(\n    n_jobs=2,backend='threading')(delayed(resize_and_save)(file_path)\\\n    for file_path in tqdm(file_paths, leave=True, position=0))","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:57:02.616689Z","iopub.execute_input":"2023-01-18T18:57:02.617996Z","iopub.status.idle":"2023-01-18T18:57:07.867379Z","shell.execute_reply.started":"2023-01-18T18:57:02.617944Z","shell.execute_reply":"2023-01-18T18:57:07.865769Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## テストデータの読み込み\n\nCSVファイルを読み込み  \nパスも取得\nテストデータは、サブミットする時とそうでない時で量が違うので注意  ","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\n\ntest_df = pd.read_csv(f\"{data_dir}/test.csv\")\n\ntest_df['dcm_path'] = test_df.apply(\n    lambda i: os.path.join(\n        f\"{data_dir}\", 'test_images', str(i['patient_id']), str(i['image_id']) + '.dcm'\n    ), axis=1\n)\n\nprint(test_df)\n","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:55:31.292816Z","iopub.execute_input":"2023-01-18T18:55:31.293384Z","iopub.status.idle":"2023-01-18T18:55:31.342033Z","shell.execute_reply.started":"2023-01-18T18:55:31.293312Z","shell.execute_reply":"2023-01-18T18:55:31.340984Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 画像の変換（テストデータ）\n\nテストデータは、サブミットする時とそうでない時で量が違うので注意  \n`Parallel`を利用して、並列実行する","metadata":{}},{"cell_type":"code","source":"%%time\n\nIMG_DIR = test_dir\n\nfile_paths = test_df['dcm_path'].tolist()\nimgsize = Parallel(\n    n_jobs=2,backend='threading')(delayed(resize_and_save)(file_path)\\\n    for file_path in tqdm(file_paths, leave=True, position=0))","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:55:31.343614Z","iopub.execute_input":"2023-01-18T18:55:31.344018Z","iopub.status.idle":"2023-01-18T18:55:31.366703Z","shell.execute_reply.started":"2023-01-18T18:55:31.343982Z","shell.execute_reply":"2023-01-18T18:55:31.365275Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 学習処理\n\n## 設定値\n\n* target_size: 学習予測時の画像サイズ\n* batch_size: 学習時のバッチサイズ（小さすぎると投稿時にタイムアウトする）\n* epochs: 学習回数（多すぎると投稿時にタイムアウトする）","metadata":{}},{"cell_type":"code","source":"target_size = [512, 512]\nbatch_size = 256\nepochs = 10","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## ジェネレータの作成\n\nKeras　の機能を利用して、データのジェネレータの作成  \n`flow_from_directory`で読み込むために、ネガティブとポジティブでディレクトリを分けた","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.preprocessing.image import ImageDataGenerator\n\nclasses = ['cancer', 'negative']\nclass_num = len(classes)\n\ngenerator = ImageDataGenerator(\n    rescale=1./255,\n#     vertical_flip=True,\n    horizontal_flip=True,\n#     rotation_range=30,\n#     zoom_range=0.25,\n    validation_split = 0.25)\ntrain_data = generator.flow_from_directory(\n    train_dir, color_mode='rgb', classes=classes, batch_size=batch_size,\n    target_size=target_size, subset = 'training')\nval_data = generator.flow_from_directory(\n    train_dir, color_mode='rgb', classes=classes, batch_size=batch_size,\n    target_size=target_size, subset = 'validation')","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:55:31.368948Z","iopub.execute_input":"2023-01-18T18:55:31.369606Z","iopub.status.idle":"2023-01-18T18:55:40.573074Z","shell.execute_reply.started":"2023-01-18T18:55:31.36955Z","shell.execute_reply":"2023-01-18T18:55:40.571598Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## probabilistic F1 score (pF1) のメトリックを追加\n\n投稿時の評価にも利用される probabilistic F1 score (pF1) をメトリックとして追加する  \nバリデーションデータに対してもこれのスコアが良ければ、投稿時のスコアもいい（はずだった）","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\n\n# https://www.kaggle.com/code/markwijkhuizen/rsna-convnextv2-training-tensorflow-tpu#pF1-Metric\n# Tensorflow custom metric is just a conventional class object\nclass pF1(tf.keras.metrics.Metric):\n    # Initialize properties\n    def __init__(self, name='pF1', **kwargs):\n        super(pF1, self).__init__(name=name, **kwargs)\n        self.tc = self.add_weight(name='tc', initializer='zeros')\n        self.tp = self.add_weight(name='tp', initializer='zeros')\n        self.fp = self.add_weight(name='fp', initializer='zeros')\n\n    # Update state called on each batch with true and predicted labels\n    def update_state(self, y_true, y_pred, sample_weight=None):\n        self.tc.assign_add(tf.cast(tf.reduce_sum(y_true), tf.float32))\n        self.tp.assign_add(tf.cast(tf.reduce_sum((y_pred[y_true == 1])), tf.float32))\n        self.fp.assign_add(tf.cast(tf.reduce_sum((y_pred[y_true == 0])), tf.float32))\n\n    # Result function is called to obtain result which is printed in progress bar\n    def result(self):\n        if self.tc == 0 or (self.tp + self.fp) == 0:\n            return 0.0\n        else:\n            precision = self.tp / (self.tp + self.fp)\n            recall = self.tp / (self.tc)\n            return 2 * (precision * recall) / (precision + recall)\n\n    # Reset state is called after each epoch to start fresh each epoch\n    def reset_state(self):\n        self.tc.assign(0)\n        self.tp.assign(0)\n        self.fp.assign(0)","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:55:40.602922Z","iopub.execute_input":"2023-01-18T18:55:40.60341Z","iopub.status.idle":"2023-01-18T18:55:40.659873Z","shell.execute_reply.started":"2023-01-18T18:55:40.603342Z","shell.execute_reply":"2023-01-18T18:55:40.65849Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## モデルの定義\n\nKeras の `applications` を利用してモデルを定義  \nインターネットアクセスできないので重みは読み込まなかった","metadata":{}},{"cell_type":"code","source":"# from tensorflow.keras.applications.efficientnet import EfficientNetB1\nfrom tensorflow.keras.applications.inception_resnet_v2 import InceptionResNetV2\n\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Activation\nfrom tensorflow.keras.layers import Flatten\nfrom tensorflow.keras.layers import Dense\nfrom tensorflow.keras.layers import Dropout\nfrom tensorflow.keras.layers import Input\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.optimizers import SGD\n\ninput_tensor = Input(shape=(*target_size, 3))\nbase_model = InceptionResNetV2(include_top=False, weights=None, input_tensor=input_tensor)\n\nbase_model.trainable = False\n\nmodel = Sequential()\nmodel.add(base_model)\nmodel.add(Flatten())\nmodel.add(Dense(128, activation='relu'))\nmodel.add(Dropout(0.5))\nmodel.add(Dense(class_num, activation='sigmoid'))\n\nsdg = SGD(learning_rate=1e-3, momentum=0.9)\nmodel.compile(loss='categorical_crossentropy',\n          optimizer=sdg, metrics=['accuracy', pF1()])","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:55:40.661489Z","iopub.execute_input":"2023-01-18T18:55:40.662316Z","iopub.status.idle":"2023-01-18T18:55:50.567682Z","shell.execute_reply.started":"2023-01-18T18:55:40.662276Z","shell.execute_reply":"2023-01-18T18:55:50.56471Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 学習の実施","metadata":{}},{"cell_type":"code","source":"history = model.fit(\n    train_data, epochs=epochs, validation_data=val_data)","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:55:50.574321Z","iopub.execute_input":"2023-01-18T18:55:50.579099Z","iopub.status.idle":"2023-01-18T18:56:40.795447Z","shell.execute_reply.started":"2023-01-18T18:55:50.578957Z","shell.execute_reply":"2023-01-18T18:56:40.793808Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 学習曲線の表示","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n%matplotlib inline\n\nplt.plot(history.history['loss'])\nplt.plot(history.history['val_loss'])\nplt.title('Model loss')\nplt.ylabel('Loss')\nplt.xlabel('Epoch')\nplt.legend(['Train', 'Validation'], loc='best')\nplt.show()\n\nplt.plot(history.history['accuracy'])\nplt.plot(history.history['val_accuracy'])\nplt.title('Model accuracy')\nplt.ylabel('Accuracy')\nplt.xlabel('Epoch')\nplt.legend(['Train', 'Validation'], loc='best')\nplt.show()\n\nplt.plot(history.history['pF1'])\nplt.plot(history.history['val_pF1'])\nplt.title('Probabilistic F Score')\nplt.ylabel('Probabilistic F Score')\nplt.xlabel('Epoch')\nplt.legend(['Train', 'Validation'], loc='best')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:56:40.796573Z","iopub.status.idle":"2023-01-18T18:56:40.797645Z","shell.execute_reply.started":"2023-01-18T18:56:40.797411Z","shell.execute_reply":"2023-01-18T18:56:40.797436Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## テストデータへの適用に向けてメモリを掃除","metadata":{}},{"cell_type":"code","source":"import sys\nimport pandas as pd\n\nprint(pd.DataFrame([[val for val in dir()], [sys.getsizeof(eval(val)) for val in dir()]],\n                   index=['name','size']).T.sort_values('size', ascending=False).reset_index(drop=True))","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:56:40.798775Z","iopub.status.idle":"2023-01-18T18:56:40.799495Z","shell.execute_reply.started":"2023-01-18T18:56:40.799114Z","shell.execute_reply":"2023-01-18T18:56:40.799147Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import gc\n\ndel train_data, val_data, history\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:56:40.801797Z","iopub.status.idle":"2023-01-18T18:56:40.802755Z","shell.execute_reply.started":"2023-01-18T18:56:40.802505Z","shell.execute_reply":"2023-01-18T18:56:40.802537Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# テストデータの予測\n\n## テストデータの作成\n\nDICOM データを画像形式に変換済みなので、`from_tensor_slices`を利用してテストデータを作成","metadata":{}},{"cell_type":"code","source":"# https://www.kaggle.com/code/awsaf49/rsna-bcd-efficientnet-tf-tpu-1vm-infer#Meta-Data\n\nimport tensorflow as tf\n\ndef decode_fn(path):\n    file_bytes = tf.io.read_file(path)\n    img = tf.image.decode_png(file_bytes, channels=3)\n\n    img = tf.cast(img, tf.float32) / 255.0\n    img = tf.reshape(img, [*target_size, 3])\n\n    return img\n\nBASE_PATH = '/kaggle/input/rsna-breast-cancer-detection'\ntest_df['image_path'] = test_df['dcm_path'].str.replace('.dcm','.png').str.replace(BASE_PATH, IMG_DIR)\n\nAUTO = tf.data.experimental.AUTOTUNE\n\nif len(test_df['image_path'])<=4:\n    test_batch_size = 1\nelse:\n    test_batch_size = 32\n        \ntest_ds = tf.data.Dataset.from_tensor_slices(test_df['image_path'])\ntest_ds = test_ds.map(decode_fn, num_parallel_calls=AUTO)\ntest_ds = test_ds.batch(test_batch_size, drop_remainder=False)\ntest_ds = test_ds.prefetch(AUTO)","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:56:40.803964Z","iopub.status.idle":"2023-01-18T18:56:40.80445Z","shell.execute_reply.started":"2023-01-18T18:56:40.804205Z","shell.execute_reply":"2023-01-18T18:56:40.804226Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 予測を実施","metadata":{}},{"cell_type":"code","source":"test_predict = model.predict(test_ds)\nprint(test_predict.shape)\ntest_predict","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:56:40.806346Z","iopub.status.idle":"2023-01-18T18:56:40.80688Z","shell.execute_reply.started":"2023-01-18T18:56:40.806638Z","shell.execute_reply":"2023-01-18T18:56:40.80666Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 投稿用ファイルの作成\n\n## 閾値を利用して、ネガティブとポジティブを判定","metadata":{}},{"cell_type":"code","source":"threshold = 0.7","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# test_df['cancer'] = test_predict[:,0]\ntest_df['cancer'] = (test_predict[:,0] > threshold).astype(float)\ntest_df","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:56:40.808524Z","iopub.status.idle":"2023-01-18T18:56:40.809025Z","shell.execute_reply.started":"2023-01-18T18:56:40.808783Z","shell.execute_reply":"2023-01-18T18:56:40.808806Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission = test_df[['prediction_id', 'cancer']].groupby(\"prediction_id\").mean().reset_index()\nsubmission","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:56:40.811514Z","iopub.status.idle":"2023-01-18T18:56:40.812276Z","shell.execute_reply.started":"2023-01-18T18:56:40.812055Z","shell.execute_reply":"2023-01-18T18:56:40.812079Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission.to_csv(\"./submission.csv\", index=False)","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:56:40.813938Z","iopub.status.idle":"2023-01-18T18:56:40.814673Z","shell.execute_reply.started":"2023-01-18T18:56:40.814453Z","shell.execute_reply":"2023-01-18T18:56:40.814476Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!head submission.csv","metadata":{"execution":{"iopub.status.busy":"2023-01-18T18:56:40.815823Z","iopub.status.idle":"2023-01-18T18:56:40.816954Z","shell.execute_reply.started":"2023-01-18T18:56:40.81673Z","shell.execute_reply":"2023-01-18T18:56:40.816753Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 参考\n\n* https://www.kaggle.com/code/vslaykovsky/infer-pytorch-aux-targets-weighted-loss-thres\n* https://www.kaggle.com/code/theoviel/dicom-resized-png-jpg/data\n* https://www.kaggle.com/code/awsaf49/rsna-bcd-efficientnet-tf-tpu-1vm-infer","metadata":{}}]}