{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":71549,"databundleVersionId":8561470,"sourceType":"competition"},{"sourceId":183439689,"sourceType":"kernelVersion"}],"dockerImageVersionId":30733,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# RSNA2024 LSDC Training Baseline\nIn the [previous notebook](https://www.kaggle.com/code/itsuki9180/rsna2024-lsdc-making-dataset), We selected the images we wanted to use and exported them to png.\n\nThis notebook will use those images for training.\n\n- version 1 to 5 are old versions\n- version 6 is fixed something about validation metrics\n- version 7 is used efn-b3\n\n### My other Notebooks\n- [RSNA2024 LSDC Making Dataset](https://www.kaggle.com/code/itsuki9180/rsna2024-lsdc-making-dataset) \n- [RSNA2024 LSDC Training Baseline](https://www.kaggle.com/code/itsuki9180/rsna2024-lsdc-training-baseline) <- you're reading now\n- [RSNA2024 LSDC Submission Baseline](https://www.kaggle.com/code/itsuki9180/rsna2024-lsdc-submission-baseline)","metadata":{}},{"cell_type":"markdown","source":"# Import Libralies","metadata":{}},{"cell_type":"code","source":"!unzip -q /kaggle/input/rsna2024-lsdc-making-dataset/_output_.zip ","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:06:51.821528Z","iopub.execute_input":"2024-06-19T10:06:51.82182Z","iopub.status.idle":"2024-06-19T10:11:11.73857Z","shell.execute_reply.started":"2024-06-19T10:06:51.821795Z","shell.execute_reply":"2024-06-19T10:11:11.737393Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# オペレーティングシステムとの対話を行うための標準ライブラリ\nimport os\n\n# ガベージコレクション（メモリ管理）を制御するための標準ライブラリ\nimport gc\n\n# Pythonのシステムパラメータや関数にアクセスするための標準ライブラリ\nimport sys\n\n# Python Imaging Library（PIL）から画像処理のための Image モジュール\nfrom PIL import Image\n\n# OpenCVライブラリをインポートし、コンピュータビジョンの機能を提供\nimport cv2\n\n# 数学的な関数と乱数生成のための標準ライブラリ\nimport math, random\n\n# 数値計算や配列操作のためのNumPyライブラリ\nimport numpy as np\n\n# データ操作や分析のためのPandasライブラリ\nimport pandas as pd\n\n# ファイルパスのパターンマッチングのための glob 関数\nfrom glob import glob\n\n# 進捗バーの表示のための tqdm ライブラリ\nfrom tqdm import tqdm\n\n# グラフや図の描画のためのMatplotlibライブラリ\nimport matplotlib.pyplot as plt\n\n# 交差検証のためのKFoldクラスをScikit-learnからインポート\nfrom sklearn.model_selection import KFold\n\n# 順序付き辞書のための OrderedDict クラス\nfrom collections import OrderedDict\n\n# PyTorchライブラリをインポートし、深層学習の機能を提供\nimport torch\n\n# PyTorchのニューラルネットワークに関する関数\nimport torch.nn.functional as F\n\n# PyTorchのニューラルネットワークの基盤となるモジュール\nfrom torch import nn\n\n# データローダーとデータセットのクラスをPyTorchからインポート\nfrom torch.utils.data import DataLoader, Dataset\n\n# PyTorchのAdamW最適化アルゴリズム\nfrom torch.optim import AdamW\n\n# TIMM（PyTorch Image Models）ライブラリをインポートし、様々な画像モデルを利用\nimport timm\n\n# トランスフォーマーライブラリからコサインスケジュールを取得するための関数\nfrom transformers import get_cosine_schedule_with_warmup\n\n# 画像のデータ拡張のためのAlbumentationsライブラリ\nimport albumentations as A\n\n# 交差検証のためのKFoldクラスをScikit-learnからインポート（重複している）\nfrom sklearn.model_selection import KFold\n","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:11:11.740397Z","iopub.execute_input":"2024-06-19T10:11:11.740725Z","iopub.status.idle":"2024-06-19T10:11:22.683387Z","shell.execute_reply.started":"2024-06-19T10:11:11.740692Z","shell.execute_reply":"2024-06-19T10:11:22.682656Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nos.listdir('../input/rsna-2024-lumbar-spine-degenerative-classification/')","metadata":{"execution":{"iopub.status.busy":"2024-08-14T05:21:38.405276Z","iopub.execute_input":"2024-08-14T05:21:38.405868Z","iopub.status.idle":"2024-08-14T05:21:38.414168Z","shell.execute_reply.started":"2024-08-14T05:21:38.405835Z","shell.execute_reply":"2024-08-14T05:21:38.413473Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# csvや訓練、テストデータなどが入ったフォルダ\nrd = '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification'","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:11:22.684495Z","iopub.execute_input":"2024-06-19T10:11:22.684936Z","iopub.status.idle":"2024-06-19T10:11:22.689107Z","shell.execute_reply.started":"2024-06-19T10:11:22.684908Z","shell.execute_reply":"2024-06-19T10:11:22.688159Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Config","metadata":{}},{"cell_type":"code","source":"# デバッグモードで実行するかどうかを示すフラグ。Trueは通常モード、Falseはデバッグモード（計算コストが低い）\nNOT_DEBUG = True\n\n# 結果を保存するディレクトリの名前\nOUTPUT_DIR = f'rsna24-results'\n\n# 使用するデバイスを指定。CUDAが利用可能ならばGPU、そうでなければCPU\ndevice = 'cuda:0' if torch.cuda.is_available() else 'cpu'\n\n# データローダーで使用するワーカーの数。CPUコア数に基づく\nN_WORKERS = os.cpu_count()\n\n# 自動混合精度（AMP）を使用するかどうかのフラグ。T4またはそれ以降のGPUを使用する場合はTrueにする\nUSE_AMP = True\n\n# ランダムシードの設定。再現性のある結果を得るために使用\nSEED = 8620\n\n# 画像のサイズ（幅×高さ）\nIMG_SIZE = [512, 512]\n\n# 入力チャネルの数\nIN_CHANS = 30\n\n# ラベルの数\nN_LABELS = 25\n\n# クラス数。ラベル数に3を掛けた値\nN_CLASSES = 3 * N_LABELS\n\n# データ拡張の確率\nAUG_PROB = 0.75\n\n# 交差検証の折りたたみ数。デバッグモードの場合は2、通常モードでは5\nN_FOLDS = 5 if NOT_DEBUG else 2\n\n# エポック数。デバッグモードの場合は2、通常モードでは20\nEPOCHS = 20 if NOT_DEBUG else 2\n\n# 使用するモデルの名前。デバッグモードの場合は小さいモデル、通常モードでは大きなモデル\nMODEL_NAME = \"tf_efficientnet_b3.ns_jft_in1k\" if NOT_DEBUG else \"tf_efficientnet_b0.ns_jft_in1k\"\n\n# 勾配累積のステップ数\nGRAD_ACC = 2\n\n# ターゲットバッチサイズ\nTGT_BATCH_SIZE = 32\n\n# バッチサイズ。勾配累積を考慮して設定\nBATCH_SIZE = TGT_BATCH_SIZE // GRAD_ACC\n\n# 勾配ノルムの最大値。Noneは制限なし\nMAX_GRAD_NORM = None\n\n# 早期停止のエポック数。指定されたエポック数で検証損失が改善しない場合に訓練を停止\nEARLY_STOPPING_EPOCH = 3\n\n# 学習率。バッチサイズに基づいてスケーリングされる\nLR = 2e-4 * TGT_BATCH_SIZE / 32\n\n# 重み減衰の値\nWD = 1e-2\n\n# データ拡張を使用するかどうかのフラグ\nAUG = True","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:44:13.132602Z","iopub.execute_input":"2024-06-19T10:44:13.132997Z","iopub.status.idle":"2024-06-19T10:44:13.141388Z","shell.execute_reply.started":"2024-06-19T10:44:13.132954Z","shell.execute_reply":"2024-06-19T10:44:13.140493Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"os.makedirs(OUTPUT_DIR, exist_ok=True)","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:41:40.523135Z","iopub.execute_input":"2024-06-19T10:41:40.523477Z","iopub.status.idle":"2024-06-19T10:41:40.52805Z","shell.execute_reply.started":"2024-06-19T10:41:40.523448Z","shell.execute_reply":"2024-06-19T10:41:40.527107Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def set_random_seed(seed: int = 8620, deterministic: bool = False):\n    \"\"\"Set seeds to ensure reproducibility\"\"\"\n    \n    # Pythonの乱数生成器にシードを設定\n    random.seed(seed)\n    \n    # NumPyの乱数生成器にシードを設定\n    np.random.seed(seed)\n    \n    # Pythonのハッシュシードを設定（ハッシュの再現性を確保）\n    os.environ[\"PYTHONHASHSEED\"] = str(seed)\n    \n    # PyTorchのCPU乱数生成器にシードを設定\n    torch.manual_seed(seed)\n    \n    # PyTorchのGPU乱数生成器にシードを設定（GPUを使用する場合）\n    torch.cuda.manual_seed(seed)  # type: ignore\n    \n    # cuDNNのベンチマーク機能を有効にし、最適化を行う\n    torch.backends.cudnn.benchmark = True\n    \n    # cuDNNの決定論的アルゴリズムを使用するかどうかを設定\n    torch.backends.cudnn.deterministic = deterministic  # type: ignore\n\n# SEEDで定義されたシード値を使用して乱数生成器を初期化\nset_random_seed(SEED)\n","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:41:40.591807Z","iopub.execute_input":"2024-06-19T10:41:40.592076Z","iopub.status.idle":"2024-06-19T10:41:40.602117Z","shell.execute_reply.started":"2024-06-19T10:41:40.592051Z","shell.execute_reply":"2024-06-19T10:41:40.601315Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Open Dataframes","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(f'{rd}/train.csv')\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:41:40.740611Z","iopub.execute_input":"2024-06-19T10:41:40.740926Z","iopub.status.idle":"2024-06-19T10:41:40.810943Z","shell.execute_reply.started":"2024-06-19T10:41:40.740898Z","shell.execute_reply":"2024-06-19T10:41:40.810038Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Change the state to Label.\n\nThe dataframe contains some Nans, which we will replace with -100 so that We and function can ignore them when calculating the loss and score.","metadata":{}},{"cell_type":"code","source":"# モデルの入力:\n#     多くの機械学習モデルやアルゴリズムは、\n#     欠損値を処理する機能がないため、欠損値を数値（例：-100）で埋めることが一般的です。\n#     ただし、この方法が適切かどうかはデータの性質や分析の目的によります。\ndf = df.fillna(-100)","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:41:40.859426Z","iopub.execute_input":"2024-06-19T10:41:40.859741Z","iopub.status.idle":"2024-06-19T10:41:40.872019Z","shell.execute_reply.started":"2024-06-19T10:41:40.859715Z","shell.execute_reply":"2024-06-19T10:41:40.871003Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# label2id: ラベルとその対応する整数値のマッピングを示す辞書です。\n\n\nlabel2id = {'Normal/Mild': 0, 'Moderate':1, 'Severe':2}\ndf = df.replace(label2id)\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:41:41.341775Z","iopub.execute_input":"2024-06-19T10:41:41.342116Z","iopub.status.idle":"2024-06-19T10:41:41.397763Z","shell.execute_reply.started":"2024-06-19T10:41:41.342086Z","shell.execute_reply":"2024-06-19T10:41:41.396642Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 脊椎管狭窄症や神経根管狭窄症など、診断条件のリスト\nCONDITIONS = [\n    'Spinal Canal Stenosis',  # 脊椎管狭窄症\n    'Left Neural Foraminal Narrowing',  # 左神経根管狭窄症\n    'Right Neural Foraminal Narrowing',  # 右神経根管狭窄症\n    'Left Subarticular Stenosis',  # 左側の下関節狭窄症\n    'Right Subarticular Stenosis'  # 右側の下関節狭窄症\n]\n\n# 脊椎の各レベルを示すリスト\nLEVELS = [\n    'L1/L2',  # 脊椎のL1とL2の間のレベル\n    'L2/L3',  # 脊椎のL2とL3の間のレベル\n    'L3/L4',  # 脊椎のL3とL4の間のレベル\n    'L4/L5',  # 脊椎のL4とL5の間のレベル\n    'L5/S1'   # 脊椎のL5とS1の間のレベル\n]","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:41:41.518668Z","iopub.execute_input":"2024-06-19T10:41:41.518985Z","iopub.status.idle":"2024-06-19T10:41:41.524091Z","shell.execute_reply.started":"2024-06-19T10:41:41.518957Z","shell.execute_reply":"2024-06-19T10:41:41.523058Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Define Dataset\n\nThis implementation is very slow and leaves a lot of room for improvement.","metadata":{}},{"cell_type":"code","source":"# PyTorchのDatasetクラスを継承したカスタムデータセットクラス\nclass RSNA24Dataset(Dataset):\n    # コンストラクタ。データフレーム、フェーズ、および変換を初期化\n    def __init__(self, df, phase='train', transform=None):\n        self.df = df  # データフレームを保存\n        self.transform = transform  # 画像変換（データ拡張など）\n        self.phase = phase  # データセットのフェーズ（例：トレーニングまたはテスト）\n\n    # データセットのサイズを返すメソッド\n    def __len__(self):\n        return len(self.df)  # データフレームの行数を返す\n\n    # インデックスに基づいてデータアイテムを取得するメソッド\n    def __getitem__(self, idx):\n        # 初期画像テンプレートを作成（512x512のチャネル数分の配列）\n        x = np.zeros((512, 512, IN_CHANS), dtype=np.uint8)\n        \n        # 指定されたインデックスの行を取得\n        t = self.df.iloc[idx]\n        st_id = int(t['study_id'])  # スタディIDを整数に変換\n        label = t[1:].values.astype(np.int64)  # ラベルを整数配列に変換\n\n        # Sagittal T1画像を読み込んでテンプレートに追加\n        for i in range(0, 10, 1):\n            try:\n                p = f'./cvt_png/{st_id}/Sagittal T1/{i:03d}.png'  # 画像パスを構築\n                img = Image.open(p).convert('L')  # グレースケールで画像を開く\n                img = np.array(img)  # 画像をNumPy配列に変換\n                x[..., i] = img.astype(np.uint8)  # テンプレートに画像を追加\n            except:\n                # 画像の読み込みに失敗した場合は何もしない\n                pass\n            \n        # Sagittal T2/STIR画像を読み込んでテンプレートに追加\n        for i in range(0, 10, 1):\n            try:\n                p = f'./cvt_png/{st_id}/Sagittal T2_STIR/{i:03d}.png'  # 画像パスを構築\n                img = Image.open(p).convert('L')  # グレースケールで画像を開く\n                img = np.array(img)  # 画像をNumPy配列に変換\n                x[..., i+10] = img.astype(np.uint8)  # テンプレートに画像を追加\n            except:\n                # 画像の読み込みに失敗した場合は何もしない\n                pass\n            \n        # Axial T2画像を読み込み、テンプレートに追加\n        axt2 = glob(f'./cvt_png/{st_id}/Axial T2/*.png')  # 画像パスのリストを取得\n        axt2 = sorted(axt2)  # 画像パスをソート\n        \n        step = len(axt2) / 10.0  # ステップサイズを計算\n        st = len(axt2)/2.0 - 4.0*step  # 画像範囲の開始位置を計算\n        end = len(axt2)+0.0001  # 画像範囲の終了位置を計算\n                \n        for i, j in enumerate(np.arange(st, end, step)):\n            try:\n                p = axt2[max(0, int((j-0.5001).round()))]  # 画像パスを選択\n                img = Image.open(p).convert('L')  # グレースケールで画像を開く\n                img = np.array(img)  # 画像をNumPy配列に変換\n                x[..., i+20] = img.astype(np.uint8)  # テンプレートに画像を追加\n            except:\n                # 画像の読み込みに失敗した場合は何もしない\n                pass  \n            \n        assert np.sum(x) > 0  # 画像テンプレートにデータがあることを確認\n            \n        if self.transform is not None:\n            x = self.transform(image=x)['image']  # 画像変換を適用（存在する場合）\n\n        x = x.transpose(2, 0, 1)  # チャネル、幅、高さの順にテンプレートを並べ替え\n                \n        return x, label  # 画像テンプレートとラベルを返す\n","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:41:42.040696Z","iopub.execute_input":"2024-06-19T10:41:42.041564Z","iopub.status.idle":"2024-06-19T10:41:42.055284Z","shell.execute_reply.started":"2024-06-19T10:41:42.041531Z","shell.execute_reply":"2024-06-19T10:41:42.054384Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Define Data Augmentaion\nData augmentation is important because the number of images used for training is extremely small.\nSee [this notebook](https://www.kaggle.com/code/haqishen/1st-place-soluiton-code-small-ver) by [Qishen Ha](https://www.kaggle.com/haqishen) for help setting up this augmentation.","metadata":{}},{"cell_type":"code","source":"# トレーニングデータに対するデータ拡張の設定を定義\ntransforms_train = A.Compose([\n    # ランダムに明るさとコントラストを調整する変換\n    A.RandomBrightnessContrast(brightness_limit=(-0.2, 0.2), contrast_limit=(-0.2, 0.2), p=AUG_PROB),\n    \n    # 複数の画像ぼかしまたはノイズの変換のいずれかをランダムに適用\n    A.OneOf([\n        A.MotionBlur(blur_limit=5),  # 動きによるぼかし\n        A.MedianBlur(blur_limit=5),  # 中央値ぼかし\n        A.GaussianBlur(blur_limit=5),  # ガウスぼかし\n        A.GaussNoise(var_limit=(5.0, 30.0)),  # ガウスノイズ\n    ], p=AUG_PROB),\n\n    # 複数の歪み変換のいずれかをランダムに適用\n    A.OneOf([\n        A.OpticalDistortion(distort_limit=1.0),  # 光学的歪み\n        A.GridDistortion(num_steps=5, distort_limit=1.),  # グリッド歪み\n        A.ElasticTransform(alpha=3),  # 弾性変換\n    ], p=AUG_PROB),\n\n    # シフト、スケール、回転の変換をランダムに適用\n    A.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.1, rotate_limit=15, border_mode=0, p=AUG_PROB),\n    \n    # 画像サイズを指定したサイズにリサイズ\n    A.Resize(IMG_SIZE[0], IMG_SIZE[1]),\n    \n    # ランダムにコースドロップアウトを適用して画像の一部を削除\n    A.CoarseDropout(max_holes=16, max_height=64, max_width=64, min_holes=1, min_height=8, min_width=8, p=AUG_PROB),    \n    \n    # 画像を正規化\n    A.Normalize(mean=0.5, std=0.5)\n])\n\n# 検証データに対するデータ拡張の設定を定義\ntransforms_val = A.Compose([\n    # 画像サイズを指定したサイズにリサイズ\n    A.Resize(IMG_SIZE[0], IMG_SIZE[1]),\n    \n    # 画像を正規化\n    A.Normalize(mean=0.5, std=0.5)\n])\n\n# デバッグモードまたはデータ拡張を使用しない設定の場合は、トレーニング変換を検証変換に置き換える\nif not NOT_DEBUG or not AUG:\n    transforms_train = transforms_val\n","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:41:42.531743Z","iopub.execute_input":"2024-06-19T10:41:42.532665Z","iopub.status.idle":"2024-06-19T10:41:42.542815Z","shell.execute_reply.started":"2024-06-19T10:41:42.532625Z","shell.execute_reply":"2024-06-19T10:41:42.541878Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Trying Data Loader\nChecking if the data loader works properly.","metadata":{}},{"cell_type":"code","source":"# RSNA24Datasetクラスのインスタンスを作成し、トレーニング用変換を適用\ntmp_ds = RSNA24Dataset(df, phase='train', transform=transforms_train)\n\n# DataLoaderを作成し、バッチサイズ1でデータをシャッフルせずにロード\ntmp_dl = DataLoader(\n            tmp_ds,  # データセット\n            batch_size=1,  # バッチサイズ\n            shuffle=False,  # データをシャッフルしない\n            pin_memory=True,  # GPUメモリにピン留め\n            drop_last=False,  # 最後のバッチをドロップしない\n            num_workers=0  # データローディングに使用するワーカースレッド数\n            )\n\n# DataLoaderからデータを取得し、5回までループ\nfor i, (x, t) in enumerate(tmp_dl):\n    if i == 5:  # 5回目でループを終了\n        break\n    # 取得した画像データの統計を出力\n    print('x stat:', x.shape, x.min(), x.max(), x.mean(), x.std())\n    # ラベルの情報と形状を出力\n    print(t, t.shape)\n    # NumPy配列に変換し、画像の形式を調整して表示\n    y = x.numpy().transpose(0, 2, 3, 1)[0, ..., :3]\n    y = (y + 1) / 2  # 画像データを0-1の範囲にスケーリング\n    plt.imshow(y)  # 画像を表示\n    plt.show()  # 画像を画面に表示\n    # 画像データの統計を出力\n    print('y stat:', y.shape, y.min(), y.max(), y.mean(), y.std())\n    print()\n\n# プロットを閉じてリソースを解放\nplt.close()\n\n# データセットとデータローダーを削除してメモリを解放\ndel tmp_ds, tmp_dl\n","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:41:42.989799Z","iopub.execute_input":"2024-06-19T10:41:42.990428Z","iopub.status.idle":"2024-06-19T10:41:47.64035Z","shell.execute_reply.started":"2024-06-19T10:41:42.990397Z","shell.execute_reply":"2024-06-19T10:41:47.639493Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Define Model\nWe use timm, which is commonly used for image classification.","metadata":{}},{"cell_type":"code","source":"# PyTorchのニューラルネットワークモデルを定義するクラス\nclass RSNA24Model(nn.Module):\n    # コンストラクタ。モデルの初期化を行う\n    def __init__(self, model_name, in_c=30, n_classes=75, pretrained=True, features_only=False):\n        super().__init__()  # 親クラスのコンストラクタを呼び出す\n        \n        # timmライブラリを使用して指定されたモデルを作成\n        self.model = timm.create_model(\n                                    model_name,  # 使用するモデルの名前\n                                    pretrained=pretrained,  # 事前学習済みの重みをロードするかどうか\n                                    features_only=features_only,  # 特徴量のみを取得するかどうか\n                                    in_chans=in_c,  # 入力画像のチャンネル数\n                                    num_classes=n_classes,  # 出力クラスの数\n                                    global_pool='avg'  # グローバルプーリングのタイプ\n                                    )\n    \n    # フォワードパス。入力データをモデルに通して出力を取得\n    def forward(self, x):\n        y = self.model(x)  # モデルにデータを通す\n        return y  # 出力を返す\n","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:41:47.642235Z","iopub.execute_input":"2024-06-19T10:41:47.642708Z","iopub.status.idle":"2024-06-19T10:41:47.649548Z","shell.execute_reply.started":"2024-06-19T10:41:47.642672Z","shell.execute_reply":"2024-06-19T10:41:47.64851Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Testing Model\nChecking if the model works properly.","metadata":{}},{"cell_type":"code","source":"# RSNA24Model クラスのインスタンスを作成。指定されたモデル名、入力チャンネル数、クラス数で初期化し、事前学習済みの重みは使用しない\nm = RSNA24Model(MODEL_NAME, in_c=IN_CHANS, n_classes=N_CLASSES, pretrained=False)\n\n# サイズが (2, IN_CHANS, 512, 512) のランダムなテンソルを作成。バッチサイズ2、チャンネル数はIN_CHANS、画像サイズ512x512\ni = torch.randn(2, IN_CHANS, 512, 512)\n\n# モデルにテンソルを通して出力を取得\nout = m(i)\n\n# 出力の各テンソルについて、その形状、最小値、最大値を表示\nfor o in out:\n    print(o.shape, o.min(), o.max())","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:41:47.650883Z","iopub.execute_input":"2024-06-19T10:41:47.651356Z","iopub.status.idle":"2024-06-19T10:41:49.576115Z","shell.execute_reply.started":"2024-06-19T10:41:47.651322Z","shell.execute_reply":"2024-06-19T10:41:49.57507Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del m, i, out","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:41:49.578788Z","iopub.execute_input":"2024-06-19T10:41:49.579445Z","iopub.status.idle":"2024-06-19T10:41:49.5899Z","shell.execute_reply.started":"2024-06-19T10:41:49.579401Z","shell.execute_reply":"2024-06-19T10:41:49.588991Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train loop","metadata":{}},{"cell_type":"code","source":"# 自動混合精度を使用するための設定。Ampere世代以上のGPUでは bfloat16 を、T4 GPU では half precision を使用\nautocast = torch.cuda.amp.autocast(enabled=USE_AMP, dtype=torch.half) \n\n# 勾配スケーリングを使用するためのスケーラーを初期化。自動混合精度の使用を指定\nscaler = torch.cuda.amp.GradScaler(enabled=USE_AMP, init_scale=4096)\n\n# KFold クロスバリデーションのためのインスタンスを作成。指定された分割数でシャッフルし、乱数シードを設定\nskf = KFold(n_splits=N_FOLDS, shuffle=True, random_state=SEED)\n\n# 各フォールドのトレーニングとバリデーションのインデックスを取得してループ\nfor fold, (trn_idx, val_idx) in enumerate(skf.split(range(len(df)))):\n    print('#'*30)\n    print(f'start fold{fold}')  # 現在のフォールドの開始を表示\n    print('#'*30)\n    print(len(trn_idx), len(val_idx))  # トレーニングとバリデーションのサンプル数を表示\n\n    # トレーニングとバリデーションのデータフレームを作成\n    df_train = df.iloc[trn_idx]\n    df_valid = df.iloc[val_idx]\n\n    # トレーニングデータセットとデータローダーを作成\n    train_ds = RSNA24Dataset(df_train, phase='train', transform=transforms_train)\n    train_dl = DataLoader(\n                train_ds,\n                batch_size=BATCH_SIZE,\n                shuffle=True,\n                pin_memory=True,\n                drop_last=True,\n                num_workers=N_WORKERS\n                )\n\n    # バリデーションデータセットとデータローダーを作成\n    valid_ds = RSNA24Dataset(df_valid, phase='valid', transform=transforms_val)\n    valid_dl = DataLoader(\n                valid_ds,\n                batch_size=BATCH_SIZE*2,\n                shuffle=False,\n                pin_memory=True,\n                drop_last=False,\n                num_workers=N_WORKERS\n                )\n\n    # モデルを初期化し、指定されたデバイスに移動\n    model = RSNA24Model(MODEL_NAME, IN_CHANS, N_CLASSES, pretrained=True)\n    model.to(device)\n    \n    # AdamW オプティマイザーを初期化\n    optimizer = AdamW(model.parameters(), lr=LR, weight_decay=WD)\n\n    # 学習率スケジューラのためのステップ数とサイクル数を計算し、スケジューラを初期化\n    warmup_steps = EPOCHS/10 * len(train_dl) // GRAD_ACC\n    num_total_steps = EPOCHS * len(train_dl) // GRAD_ACC\n    num_cycles = 0.475\n    scheduler = get_cosine_schedule_with_warmup(optimizer,\n                                                num_warmup_steps=warmup_steps,\n                                                num_training_steps=num_total_steps,\n                                                num_cycles=num_cycles)\n\n    # クラス重みを設定し、損失関数を初期化\n    weights = torch.tensor([1.0, 2.0, 4.0])\n    criterion = nn.CrossEntropyLoss(weight=weights.to(device))\n    criterion2 = nn.CrossEntropyLoss(weight=weights)\n\n    # 初期の最良損失値と最良ウェイトロスレベル、早期停止のステップ数を設定\n    best_loss = 1.2\n    best_wll = 1.2\n    es_step = 0\n\n    # エポックごとにトレーニングとバリデーションを行うループ\n    for epoch in range(1, EPOCHS+1):\n        print(f'start epoch {epoch}')  # エポックの開始を表示\n        model.train()  # モデルをトレーニングモードに設定\n        total_loss = 0  # トレーニングロスを初期化\n        \n        # トレーニングデータローダーを使用してトレーニング\n        with tqdm(train_dl, leave=True) as pbar:\n            optimizer.zero_grad()  # オプティマイザーの勾配を初期化\n            for idx, (x, t) in enumerate(pbar):  \n                x = x.to(device)  # 入力データをデバイスに移動\n                t = t.to(device)  # ラベルデータをデバイスに移動\n                \n                with autocast:  # 自動混合精度を適用\n                    loss = 0  # ロスを初期化\n                    y = model(x)  # モデルの予測を取得\n                    for col in range(N_LABELS):  # 各ラベルについてロスを計算\n                        pred = y[:,col*3:col*3+3]\n                        gt = t[:,col]\n                        loss = loss + criterion(pred, gt) / N_LABELS\n                        \n                    total_loss += loss.item()  # トレーニングロスを更新\n                    if GRAD_ACC > 1:\n                        loss = loss / GRAD_ACC  # 勾配累積のためにロスをスケール\n    \n                if not math.isfinite(loss):  # ロスが非数値の場合はトレーニングを停止\n                    print(f\"Loss is {loss}, stopping training\")\n                    sys.exit(1)\n    \n                pbar.set_postfix(\n                    OrderedDict(\n                        loss=f'{loss.item()*GRAD_ACC:.6f}',  # ロスを表示\n                        lr=f'{optimizer.param_groups[0][\"lr\"]:.3e}'  # 学習率を表示\n                    )\n                )\n                scaler.scale(loss).backward()  # スケーラーでロスをスケーリングして逆伝播\n                \n                torch.nn.utils.clip_grad_norm_(model.parameters(), MAX_GRAD_NORM or 1e9)  # 勾配のノルムをクリップ\n                \n                if (idx + 1) % GRAD_ACC == 0:  # 勾配累積のためにステップを実行\n                    scaler.step(optimizer)  # スケーラーでオプティマイザーのステップを実行\n                    scaler.update()  # スケーラーを更新\n                    optimizer.zero_grad()  # 勾配を初期化\n                    if scheduler is not None:\n                        scheduler.step()  # 学習率スケジューラをステップ\n    \n        train_loss = total_loss/len(train_dl)  # 平均トレーニングロスを計算\n        print(f'train_loss:{train_loss:.6f}')  # トレーニングロスを表示\n\n        total_loss = 0  # バリデーションロスを初期化\n        y_preds = []  # 予測値を保存するリスト\n        labels = []  # ラベルを保存するリスト\n        \n        model.eval()  # モデルを評価モードに設定\n        with tqdm(valid_dl, leave=True) as pbar:\n            with torch.no_grad():  # 勾配の計算を無効化\n                for idx, (x, t) in enumerate(pbar):\n                    x = x.to(device)  # 入力データをデバイスに移動\n                    t = t.to(device)  # ラベルデータをデバイスに移動\n                        \n                    with autocast:  # 自動混合精度を適用\n                        loss = 0  # バリデーションロスを初期化\n                        loss_ema = 0  # EMAロスを初期化（使用していないが準備中）\n                        y = model(x)  # モデルの予測を取得\n                        for col in range(N_LABELS):  # 各ラベルについてロスを計算\n                            pred = y[:,col*3:col*3+3]\n                            gt = t[:,col]\n \n                            loss = loss + criterion(pred, gt) / N_LABELS\n                            y_pred = pred.float()  # 予測値をfloat型に変換\n                            y_preds.append(y_pred.cpu())  # CPUに移動してリストに追加\n                            labels.append(gt.cpu())  # CPUに移動してリストに追加\n                        \n                        total_loss += loss.item()  # バリデーションロスを更新\n    \n        val_loss = total_loss/len(valid_dl)  # 平均バリデーションロスを計算\n        \n        y_preds = torch.cat(y_preds, dim=0)  # 予測値を連結\n        labels = torch.cat(labels)  # ラベルを連結\n        val_wll = criterion2(y_preds, labels)  # バリデーション損失とウェイトロスレベルを計算\n        \n        print(f'val_loss:{val_loss:.6f}, val_wll:{val_wll:.6f}')  # バリデーションロスとウェイトロスレベルを表示\n\n        if val_loss <\n","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:44:18.903151Z","iopub.execute_input":"2024-06-19T10:44:18.903577Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# ","metadata":{}},{"cell_type":"markdown","source":"# Calculation CV","metadata":{}},{"cell_type":"code","source":"cv = 0  # クロスバリデーションのカウンタを初期化\ny_preds = []  # 予測値を保存するリストを初期化\nlabels = []  # ラベルを保存するリストを初期化\nweights = torch.tensor([1.0, 2.0, 4.0])  # クロスエントロピーロスのためのクラス重みを設定\ncriterion2 = nn.CrossEntropyLoss(weight=weights)  # 重み付きクロスエントロピーロスを初期化\n\n# 各フォールドに対してループ\nfor fold, (trn_idx, val_idx) in enumerate(skf.split(range(len(df)))):\n    print('#'*30)\n    print(f'start fold{fold}')  # 現在のフォールドの開始を表示\n    print('#'*30)\n    \n    # バリデーションデータフレームを作成\n    df_valid = df.iloc[val_idx]\n    \n    # バリデーションデータセットとデータローダーを作成\n    valid_ds = RSNA24Dataset(df_valid, phase='valid', transform=transforms_val)\n    valid_dl = DataLoader(\n                valid_ds,\n                batch_size=1,\n                shuffle=False,\n                pin_memory=True,\n                drop_last=False,\n                num_workers=N_WORKERS\n                )\n\n    # モデルを初期化し、保存された重みをロード\n    model = RSNA24Model(MODEL_NAME, IN_CHANS, N_CLASSES, pretrained=False)\n    fname = f'{OUTPUT_DIR}/best_wll_model_fold-{fold}.pt'\n    model.load_state_dict(torch.load(fname))  # モデルの重みをファイルからロード\n    model.to(device)  # モデルをデバイスに移動\n    \n    model.eval()  # モデルを評価モードに設定\n    with tqdm(valid_dl, leave=True) as pbar:\n        with torch.no_grad():  # 勾配の計算を無効化\n            for idx, (x, t) in enumerate(pbar):\n                x = x.to(device)  # 入力データをデバイスに移動\n                t = t.to(device)  # ラベルデータをデバイスに移動\n                \n                with autocast:  # 自動混合精度を適用\n                    y = model(x)  # モデルの予測を取得\n                    for col in range(N_LABELS):  # 各ラベルについて予測とラベルを保存\n                        pred = y[:,col*3:col*3+3]\n                        gt = t[:,col] \n                        y_pred = pred.float()  # 予測値をfloat型に変換\n                        y_preds.append(y_pred.cpu())  # CPUに移動してリストに追加\n                        labels.append(gt.cpu())  # CPUに移動してリストに追加\n\n# 予測値とラベルを連結\ny_preds = torch.cat(y_preds)\nlabels = torch.cat(labels)\n","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:44:10.828064Z","iopub.status.idle":"2024-06-19T10:44:10.828691Z","shell.execute_reply.started":"2024-06-19T10:44:10.828354Z","shell.execute_reply":"2024-06-19T10:44:10.828386Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cv = criterion2(y_preds, labels)  # `y_preds` と `labels` を使ってクロスエントロピーロスを計算し、結果を `cv` に保存\nprint('cv score:', cv.item())  # 計算されたロススコアを表示","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:44:10.830873Z","iopub.status.idle":"2024-06-19T10:44:10.831948Z","shell.execute_reply.started":"2024-06-19T10:44:10.831688Z","shell.execute_reply":"2024-06-19T10:44:10.831711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Calculation Competition Metrics\nThis will give a slightly different score, probably due to the different behavior for nan.","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import log_loss  # `log_loss` 関数をインポート\n\ny_pred_np = y_preds.softmax(1).numpy()  # `y_preds` に対してソフトマックスを適用し、NumPy 配列に変換\n\nlabels_np = labels.numpy()  # `labels` を NumPy 配列に変換\n\ny_pred_nan = np.zeros((y_preds.shape[0], 1))  # `y_preds` と同じサンプル数でゼロの配列を作成\n\ny_pred2 = np.concatenate([y_pred_nan, y_pred_np], axis=1)  # `y_pred_nan` を `y_pred_np` の前に追加して結合\n\nweights = []  # 重みを保存するリストを初期化\n\nfor l in labels:  # 各ラベルについて\n    if l == 0: weights.append(1)  # ラベルが0の場合、重みを1に設定\n    elif l == 1: weights.append(2)  # ラベルが1の場合、重みを2に設定\n    elif l == 2: weights.append(4)  # ラベルが2の場合、重みを4に設定\n    else: weights.append(0)  # その他の場合、重みを0に設定\n\ncv2 = log_loss(labels_np, y_pred2, normalize=True, sample_weight=weights)  # `log_loss` 関数を使って損失を計算\n\nprint('cv score:', cv2)  # 計算されたクロスエントロピーロスを表示\n","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:44:10.83327Z","iopub.status.idle":"2024-06-19T10:44:10.834366Z","shell.execute_reply.started":"2024-06-19T10:44:10.834107Z","shell.execute_reply":"2024-06-19T10:44:10.83413Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"np.save(f'{OUTPUT_DIR}/labels.npy', labels_np)\nnp.save(f'{OUTPUT_DIR}/final_oof.npy', y_pred2)","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:44:10.835719Z","iopub.status.idle":"2024-06-19T10:44:10.836182Z","shell.execute_reply.started":"2024-06-19T10:44:10.835945Z","shell.execute_reply":"2024-06-19T10:44:10.835966Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# When predictions are random\ngets around 1.1.","metadata":{}},{"cell_type":"code","source":"random_pred = np.ones((y_preds.shape[0], 3)) / 3.0  # 各サンプルに対して均等な確率の予測を作成\n\ny_pred3 = np.concatenate([y_pred_nan, random_pred], axis=1)  # `y_pred_nan` を `random_pred` の前に追加して結合\n\ncv3 = log_loss(labels, y_pred3, normalize=True, sample_weight=weights)  # `log_loss` 関数を使って損失を計算（ランダム予測用）\n\nprint('random score:', cv3)  # 計算されたクロスエントロピーロスを表示（ランダム予測のスコア）\n","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:15:48.27336Z","iopub.status.idle":"2024-06-19T10:15:48.273817Z","shell.execute_reply.started":"2024-06-19T10:15:48.273594Z","shell.execute_reply":"2024-06-19T10:15:48.273613Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!rm -r cvt_png","metadata":{"execution":{"iopub.status.busy":"2024-06-19T10:15:48.27496Z","iopub.status.idle":"2024-06-19T10:15:48.275284Z","shell.execute_reply.started":"2024-06-19T10:15:48.275125Z","shell.execute_reply":"2024-06-19T10:15:48.275139Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Continuing with the [Submission Baseline...](https://www.kaggle.com/code/itsuki9180/rsna2024-lsdc-submission-baseline)","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}