{"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":"#### Reference:\n\n1. https://www.kaggle.com/code/itsuki9180/a-segmentation-is-in-reverse-order\n1. https://www.kaggle.com/code/samuelcortinhas/rnsa-3d-model-train-pytorch\n1. https://www.kaggle.com/code/andradaolteanu/rsna-fracture-detection-dicom-images-explore#2.-Image-Data-%5B.dcm%5D\n1. https://www.kaggle.com/code/weixinxu/submit-baseline\n1. https://www.kaggle.com/code/mlwhiz/bilstm-pytorch-and-keras\n\n#### Modeling Reference:\n1. http://dx.doi.org/10.3174/ajnr.A7094\n1. https://blog.devgenius.io/resnet50-6b42934db431\n1. https://www.kaggle.com/code/samuelcortinhas/rnsa-3d-model-train-pytorch#Torch-dataloaders","metadata":{"id":"Ual0RqSlW4F0"}},{"cell_type":"markdown","source":"## Downloads","metadata":{"id":"_qtBuug5W4F3"}},{"cell_type":"code","source":"INTERNET = True\n\nif INTERNET == True:\n    !python --version\n\n    !pip install monai\n    !pip install warmup-scheduler","metadata":{"id":"yu3ndgUrW4F4","outputId":"8deeeda2-d82e-45e3-e279-287271b7ba53","execution":{"iopub.status.busy":"2023-04-16T20:32:19.327197Z","iopub.execute_input":"2023-04-16T20:32:19.327911Z","iopub.status.idle":"2023-04-16T20:32:43.227969Z","shell.execute_reply.started":"2023-04-16T20:32:19.327873Z","shell.execute_reply":"2023-04-16T20:32:43.226771Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Import Libraries","metadata":{"id":"LA_kCUmHW4F5"}},{"cell_type":"code","source":"import os\nimport sys\nimport numpy as np\nimport pandas as pd\nfrom glob import glob\nfrom PIL import Image\nimport cv2\nimport re\nimport gc\nfrom tqdm import tqdm\nimport math\n\nimport matplotlib.pyplot as plt\nfrom matplotlib.patches import Rectangle\n\nimport skimage.transform as skTrans\nfrom skimage import exposure\n\nimport albumentations as alb\nfrom albumentations.pytorch import ToTensorV2\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, Dataset\n\nfrom torch.cuda.amp import autocast, GradScaler\nfrom torch.optim.lr_scheduler import OneCycleLR, CosineAnnealingLR\nfrom warmup_scheduler import GradualWarmupScheduler\n\n\nimport tensorflow as tf\n\nfrom monai.transforms import Resize\nimport monai.transforms as transforms\nfrom monai.networks.nets import resnet18, resnet101\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.model_selection import KFold, StratifiedKFold","metadata":{"id":"q54eI0HrZ8p1","execution":{"iopub.status.busy":"2023-04-16T20:32:43.231583Z","iopub.execute_input":"2023-04-16T20:32:43.23199Z","iopub.status.idle":"2023-04-16T20:32:56.264893Z","shell.execute_reply.started":"2023-04-16T20:32:43.231944Z","shell.execute_reply":"2023-04-16T20:32:56.263732Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Config","metadata":{"id":"wxZqi55oW4F7"}},{"cell_type":"code","source":"SEED = 1927550\nIMG_SIZE = 512\nBATCH = 3\nEPOCH = 30\nCLASS = 7\nhidden1 = 128\nhidden2 = 64\nKAGGLE = True\nchannel_3 = True\ntot_slice = 40\nbest_loss = 1\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\n\ntrainlosslog = []\ntrainacclog = []\nvalidlosslog = []\nvalidacclog = []","metadata":{"id":"OZCsaDR3W4F7","execution":{"iopub.status.busy":"2023-04-16T20:32:56.266856Z","iopub.execute_input":"2023-04-16T20:32:56.267234Z","iopub.status.idle":"2023-04-16T20:32:56.336585Z","shell.execute_reply.started":"2023-04-16T20:32:56.267188Z","shell.execute_reply":"2023-04-16T20:32:56.334785Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"if KAGGLE:\n    work_path = '/kaggle/input'\nelse:\n    work_path = '/content/drive/MyDrive/Colab_Notebooks'\n\nbase_path = f'{work_path}/preprocessing-cropping-voxel-masked-image'\ntrain_df = pd.read_csv(f'{work_path}/rsna-2022-cervical-spine-fracture-detection/train.csv')\nvoxel_df = pd.read_csv(f\"{base_path}/vertebrae_df.csv\")\n\nkf = KFold(5)\nfor fold, (train_idx, valid_idx) in enumerate(kf.split(voxel_df, voxel_df)):\n    voxel_df.loc[valid_idx, 'fold'] = fold\n\ndf_train = voxel_df[voxel_df['fold'] != fold].reset_index(drop=True)\ndf_valid = voxel_df[voxel_df['fold'] == fold].reset_index(drop=True)","metadata":{"id":"ZG2rrXStW4F8","execution":{"iopub.status.busy":"2023-04-16T20:32:56.339813Z","iopub.execute_input":"2023-04-16T20:32:56.340238Z","iopub.status.idle":"2023-04-16T20:32:56.385965Z","shell.execute_reply.started":"2023-04-16T20:32:56.340197Z","shell.execute_reply":"2023-04-16T20:32:56.384912Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"seg_revert = [\n    '1.2.826.0.1.3680043.1363',\n    '1.2.826.0.1.3680043.20120',\n    '1.2.826.0.1.3680043.2243',\n    '1.2.826.0.1.3680043.24606',\n    '1.2.826.0.1.3680043.32071'\n]","metadata":{"execution":{"iopub.status.busy":"2023-04-16T20:32:56.38747Z","iopub.execute_input":"2023-04-16T20:32:56.387871Z","iopub.status.idle":"2023-04-16T20:32:56.39266Z","shell.execute_reply.started":"2023-04-16T20:32:56.387832Z","shell.execute_reply":"2023-04-16T20:32:56.391595Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Useful Functions","metadata":{"id":"r7lLzzm6W4F8"}},{"cell_type":"code","source":"# apply seed\ndef seed_everything(seed):\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    if torch.cuda.is_available():\n        torch.cuda.manual_seed_all(seed)","metadata":{"id":"a_KZzLgBW4F9","execution":{"iopub.status.busy":"2023-04-16T20:32:56.394221Z","iopub.execute_input":"2023-04-16T20:32:56.394925Z","iopub.status.idle":"2023-04-16T20:32:56.405152Z","shell.execute_reply.started":"2023-04-16T20:32:56.394886Z","shell.execute_reply":"2023-04-16T20:32:56.404178Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data_transforms = {\n    'train': alb.Compose([\n                alb.HorizontalFlip(p=0.5),\n                alb.VerticalFlip(p=0.5),\n                alb.ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.05, rotate_limit=10, p=0.5),\n                alb.OneOf([\n                    alb.GridDistortion(num_steps=5, distort_limit=0.05, p=1.0),\n                    alb.OpticalDistortion(distort_limit=0.05, shift_limit=0.05, p=1.0),\n                    alb.ElasticTransform(alpha=1, sigma=50, alpha_affine=50, p=1.0)\n                ], p=0.25),\n                alb.CoarseDropout(max_holes=8, max_height=IMG_SIZE//80, max_width=IMG_SIZE//80, min_holes=5, fill_value=0, mask_fill_value=0, p=0.5),\n             ]),\n    'valid': alb.Compose([])\n}","metadata":{"id":"CTOZzHuWW4F9","execution":{"iopub.status.busy":"2023-04-16T20:32:56.406709Z","iopub.execute_input":"2023-04-16T20:32:56.407118Z","iopub.status.idle":"2023-04-16T20:32:56.417815Z","shell.execute_reply.started":"2023-04-16T20:32:56.40708Z","shell.execute_reply":"2023-04-16T20:32:56.41679Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class GradualWarmupSchedulerV3(GradualWarmupScheduler):\n    def __init__(self, optimizer, multiplier, total_epoch, after_scheduler=None):\n        super(GradualWarmupSchedulerV3, self).__init__(optimizer, multiplier, total_epoch, after_scheduler)\n    def get_lr(self):\n        if self.last_epoch >= self.total_epoch:\n            if self.after_scheduler:\n                if not self.finished:\n                    self.after_scheduler.base_lrs = [base_lr * self.multiplier for base_lr in self.base_lrs]\n                    self.finished = True\n                return self.after_scheduler.get_lr()\n            return [base_lr * self.multiplier for base_lr in self.base_lrs]\n        if self.multiplier == 1.0:\n            return [base_lr * (float(self.last_epoch) / self.total_epoch) for base_lr in self.base_lrs]\n        else:\n            return [base_lr * ((self.multiplier - 1.) * self.last_epoch / self.total_epoch + 1.) for base_lr in self.base_lrs]","metadata":{"execution":{"iopub.status.busy":"2023-04-16T20:32:56.419379Z","iopub.execute_input":"2023-04-16T20:32:56.419833Z","iopub.status.idle":"2023-04-16T20:32:56.429758Z","shell.execute_reply.started":"2023-04-16T20:32:56.419798Z","shell.execute_reply":"2023-04-16T20:32:56.428557Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Drop Bad Scans","metadata":{"id":"e2KLHNYVW4F-"}},{"cell_type":"markdown","source":"https://www.kaggle.com/competitions/rsna-2022-cervical-spine-fracture-detection/discussion/344862\nhttps://www.kaggle.com/code/itsuki9180/a-segmentation-is-in-reverse-order\n\n\n1. 1.2.826.0.1.3680043.20574: does not include a full cervical spine and should be ignored.\n1. 1.2.826.0.1.3680043.29952: the slices are duplicated, meaning that there are 2 scans stiched to each other.\n1. 1.2.826.0.1.3680043.1363: the scans are reversed, if cannot reverse it again, ignore.","metadata":{"id":"shQpqqF0W4F-"}},{"cell_type":"code","source":"bad_scans = ['1.2.826.0.1.3680043.20574','1.2.826.0.1.3680043.29952']\n\nfor uid in bad_scans:\n    train_df.drop(train_df[train_df['StudyInstanceUID']==uid].index, axis=0, inplace=True)","metadata":{"id":"_y90qKzXW4F-","execution":{"iopub.status.busy":"2023-04-16T20:32:56.431426Z","iopub.execute_input":"2023-04-16T20:32:56.431856Z","iopub.status.idle":"2023-04-16T20:32:56.44896Z","shell.execute_reply.started":"2023-04-16T20:32:56.431821Z","shell.execute_reply":"2023-04-16T20:32:56.447932Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Dataset Class","metadata":{"id":"OhCycBpMW4F-"}},{"cell_type":"code","source":"# extract image and label of the given data\nclass CustomDataset(torch.utils.data.Dataset):\n    # Initialize\n    def __init__(self, voxel_df=df_train, train_df=train_df, transform=None, test=False):\n        super().__init__()\n        self.voxel_df = voxel_df\n        self.train_df = train_df\n        self.transform = transform\n        self.test = test\n    \n    def __getitem__(self, index):\n        voxel_UID = self.voxel_df.iloc[index]\n        uid = voxel_UID['StudyInstanceUID']\n        label_df = self.train_df\n        voxel_id = voxel_UID['id_vert']\n        \n        imgs = np.load(f'{base_path}/voxel_image/{voxel_id}.npz')['arr_0'] # 40 X 512 X 512 X 3\n        imgs = np.mean(imgs, axis=3) # monochrome => 40 X 512 X 512\n        \n        vertebrae = voxel_id.split('_')[1] # C1; C2; C3; C4; C5; C6; C7\n        if self.test == True:\n            return torch.tensor(imgs).float(), [uid, vertebrae]\n        \n        label = label_df[label_df['StudyInstanceUID'] == uid][vertebrae].values.astype('float32') # either 0 or 1\n\n        if self.transform is not None:\n            trans = self.transform(image=imgs)\n            imgs = trans['image']\n\n        return torch.tensor(np.array(imgs/255.0, dtype=np.float32)).float(), label\n       \n    # Length of dataset\n    def __len__(self):\n        return len(self.voxel_df)","metadata":{"id":"3AA2ZCh7W4F_","execution":{"iopub.status.busy":"2023-04-16T20:32:56.453005Z","iopub.execute_input":"2023-04-16T20:32:56.453266Z","iopub.status.idle":"2023-04-16T20:32:56.464001Z","shell.execute_reply.started":"2023-04-16T20:32:56.453241Z","shell.execute_reply":"2023-04-16T20:32:56.462648Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## LSTM Model","metadata":{"id":"_TctoXGlW4F_"}},{"cell_type":"code","source":"class LSTMModel(nn.Module):\n    def __init__(self):\n        super(LSTMModel, self).__init__()\n        self.lstm = nn.LSTM( # output = (batch, sequence length, dimension * )\n            input_size=(506*506), # 40(channel) * 506 * 506; reduced from conv2d kernel\n            hidden_size=hidden1, \n            num_layers=2, # num_layer = 2 if bidirectional = True. 1 otherwise\n            dropout=0., \n            bidirectional=True, \n            batch_first=True # (batch_dimension, segment_dimension, feature_dimension)\n        )\n        self.head1 = nn.Sequential(\n            nn.Conv2d(in_channels=tot_slice, out_channels=256, kernel_size=5),\n            nn.BatchNorm2d(256),\n            nn.ReLU(),\n            nn.Conv2d(in_channels=256, out_channels=tot_slice, kernel_size=3),\n            nn.ReLU(),\n            nn.BatchNorm2d(tot_slice)\n        )\n        self.head2 = nn.Sequential(\n            nn.Linear(tot_slice*(hidden1*2), 512), # dimensions, input image\n            nn.BatchNorm1d(512),\n            nn.Dropout(0.3),\n            nn.LeakyReLU(0, 1),\n            nn.Linear(512, 1)\n        )\n        \n    # input: (batch_size, tot_slice, image_size, image_size)\n    def forward(self, x):\n        batch_size = x.shape[0]\n        tot_slice = x.shape[1]\n        img_size = x.shape[2] # or x.shape[3]; equal value\n        \n        x = self.head1(x)\n        x = x.view(batch_size, tot_slice, -1)\n        # input: batch size, sequence length, input image size\n        x,_ = self.lstm(x)\n        x = x.contiguous().view(batch_size, -1)\n        x = self.head2(x)\n        \n        return x","metadata":{"id":"BzWIItbrW4F_","execution":{"iopub.status.busy":"2023-04-16T20:32:56.465583Z","iopub.execute_input":"2023-04-16T20:32:56.466771Z","iopub.status.idle":"2023-04-16T20:32:56.479301Z","shell.execute_reply.started":"2023-04-16T20:32:56.466726Z","shell.execute_reply":"2023-04-16T20:32:56.47828Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Settings","metadata":{"id":"fcCRIwyTW4GA"}},{"cell_type":"code","source":"train_dataset = CustomDataset(voxel_df=df_train, train_df=train_df, transform=data_transforms['train'], test=False)\nvalid_dataset = CustomDataset(voxel_df=df_valid, train_df=train_df, transform=data_transforms['valid'], test=False)\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH, shuffle=True)\nvalid_loader = DataLoader(valid_dataset, batch_size=BATCH, shuffle=True)","metadata":{"id":"nKXQngCaW4GB","execution":{"iopub.status.busy":"2023-04-16T20:32:56.480875Z","iopub.execute_input":"2023-04-16T20:32:56.481231Z","iopub.status.idle":"2023-04-16T20:32:56.493532Z","shell.execute_reply.started":"2023-04-16T20:32:56.481196Z","shell.execute_reply":"2023-04-16T20:32:56.49255Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# model setting\nmodel = LSTMModel()\nmodel.to(device)\n\n# Adam optimizer\noptimizer = optim.AdamW(params=model.parameters(), lr=1e-4, weight_decay=1e-5)","metadata":{"id":"HupXNvGTW4GA","execution":{"iopub.status.busy":"2023-04-16T20:32:56.495347Z","iopub.execute_input":"2023-04-16T20:32:56.495898Z","iopub.status.idle":"2023-04-16T20:33:04.919911Z","shell.execute_reply.started":"2023-04-16T20:32:56.495853Z","shell.execute_reply":"2023-04-16T20:33:04.918803Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Competition Loss","metadata":{"id":"h99jYAB2W4GA"}},{"cell_type":"code","source":"# Replicate competition metric (https://www.kaggle.com/competitions/rsna-2022-cervical-spine-fracture-detection/discussion/341854)\nloss_fn = F.binary_cross_entropy_with_logits\n\n# The logits created from model training is either 0 or 1 for only one vertebrae\ncompetition_weights = {\n    '-' : torch.tensor(1, dtype=torch.float, device=device),\n    '+' : torch.tensor(2, dtype=torch.float, device=device),\n}\n\n# with row-wise weights normalization (https://www.kaggle.com/competitions/rsna-2022-cervical-spine-fracture-detection/discussion/344565)\ndef competiton_loss_row_norm(y_hat, y):\n    loss = loss_fn(y_hat.view(-1), y.to(y_hat.dtype).view(-1))\n    weights = y * competition_weights['+'] + (1 - y) * competition_weights['-']\n    loss = (loss * weights).sum(axis=1)\n    w_sum = weights.sum(axis=1)\n    loss = torch.div(loss, w_sum)\n    return loss.mean()","metadata":{"id":"TzQmF9qlW4GA","execution":{"iopub.status.busy":"2023-04-16T20:33:04.921314Z","iopub.execute_input":"2023-04-16T20:33:04.921806Z","iopub.status.idle":"2023-04-16T20:33:04.933448Z","shell.execute_reply.started":"2023-04-16T20:33:04.921766Z","shell.execute_reply":"2023-04-16T20:33:04.930638Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Training\n","metadata":{"id":"qY4KiVMkW4GA"}},{"cell_type":"code","source":"def train(model, dataloader, optimizer):\n    model.train()\n    scaler = GradScaler()\n    \n    train_loss = []\n    train_acc = []\n    \n    for imgs, label in tqdm(dataloader):\n        #print(imgs.shape)\n        \n        # set the gradient to 0 at initial\n        optimizer.zero_grad()\n\n        # forward data, making sure the data and model are on the same device\n        with autocast(enabled=True):\n            logits = model(imgs.to(device))\n            loss = competiton_loss_row_norm(logits, label.to(device))\n        \n        #print('argmax:',logits.detach().cpu().numpy().argmax(axis=1))\n        #print('max:',logits.detach().cpu().numpy().max(axis=1))\n        #print('truth:',label.numpy())\n        acc = (logits.argmax(dim=-1) == label.to(device)).float().mean()\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        \n        train_loss.append(loss.item())\n        train_acc.append(acc)\n        \n    train_loss = sum(train_loss) / len(train_loss)\n    train_acc = sum(train_acc) / len(train_acc)\n    \n    return train_loss, train_acc","metadata":{"id":"UeR07_B_W4GB","execution":{"iopub.status.busy":"2023-04-16T20:33:04.935048Z","iopub.execute_input":"2023-04-16T20:33:04.935429Z","iopub.status.idle":"2023-04-16T20:33:04.959651Z","shell.execute_reply.started":"2023-04-16T20:33:04.935392Z","shell.execute_reply":"2023-04-16T20:33:04.958544Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def validation(model, dataloader, optimizer):\n    model.eval()\n    valid_loss = []\n    valid_acc = []\n    \n    for imgs, label in tqdm(dataloader):\n        # no need gradient in validation\n        # use torch.no_grad() accelerates the forward process\n        with torch.no_grad():\n            logits = model(imgs.to(device))\n        \n        loss = competiton_loss_row_norm(logits, label.to(device))\n        acc = (logits.argmax(dim=-1) == label.to(device)).float().mean()\n        \n        valid_loss.append(loss.item())\n        valid_acc.append(acc)\n    \n    valid_loss = sum(valid_loss) / len(valid_loss)\n    valid_acc = sum(valid_acc) / len(valid_acc)\n    \n    return valid_loss, valid_acc","metadata":{"id":"Rf1dVk7rW4GB","execution":{"iopub.status.busy":"2023-04-16T20:33:04.961333Z","iopub.execute_input":"2023-04-16T20:33:04.962527Z","iopub.status.idle":"2023-04-16T20:33:04.973786Z","shell.execute_reply.started":"2023-04-16T20:33:04.962471Z","shell.execute_reply":"2023-04-16T20:33:04.972791Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"seed_everything(SEED)\n\n# initialize the best values to save\nbest_train_loss = 0\nbest_train_acc = 0\nbest_valid_loss = 0\nbest_valid_acc = 0\n\nbest_epoch = 0\n# setup scheduler\nscheduler = CosineAnnealingLR(optimizer, T_max=EPOCH-1, eta_min=1e-6, last_epoch=-1)\nscheduler_warmup = GradualWarmupSchedulerV3(optimizer, multiplier=10, total_epoch=1, after_scheduler=scheduler)\n\n# start training\nfor epoch in range(EPOCH):\n    print(f'### Epoch: {epoch+1} ###')\n    train_loss, train_acc = train(model, train_loader, optimizer)\n    print(f'[ Train | {epoch + 1:03d}/{EPOCH:03d} ] loss = {train_loss:.5f}, acc = {train_acc:.5f}')\n    valid_loss, valid_acc = validation(model, valid_loader, optimizer)\n    print(f'[ Valid | {epoch + 1:03d}/{EPOCH:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}')\n    print()\n\n    scheduler_warmup.step()\n    \n    # save train and valid logs\n    trainlosslog.append(train_loss)\n    trainacclog.append(train_acc.cpu().data.numpy())\n    validlosslog.append(valid_loss)\n    validacclog.append(valid_acc.cpu().data.numpy())\n    \n    # save models\n    if valid_loss < best_loss:\n        # save highest values\n        best_train_loss, best_train_acc = train_loss, train_acc\n        best_valid_loss, best_valid_acc = valid_loss, valid_acc\n        best_epoch = epoch\n        \n        # save model\n        torch.save(model.state_dict(), \"stage2_cropping_window_best.ckpt\") # only save best to prevent output memory exceed error\n        # reset values\n        best_loss = valid_loss\n        \n\nprint()\nprint(f\"[ Best Train | {best_epoch+1:03d} / {EPOCH:03d} ] loss = {best_train_loss:.8f}, acc = {best_train_acc:.8f}\")\nprint(f\"[ Best Valid | {best_epoch+1:03d} / {EPOCH:03d} ] loss = {best_valid_loss:.8f}, acc = {best_valid_acc:.8f}\")\n","metadata":{"id":"uXGxSt9lW4GB","_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-04-16T20:33:04.97654Z","iopub.execute_input":"2023-04-16T20:33:04.97772Z","iopub.status.idle":"2023-04-16T20:47:25.967692Z","shell.execute_reply.started":"2023-04-16T20:33:04.977681Z","shell.execute_reply":"2023-04-16T20:47:25.963675Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"log_df = pd.DataFrame(\n    {\n        'trainLoss': trainlosslog,\n        'trainAcc': trainacclog,\n        'validLoss': validlosslog,\n        'validAcc': validacclog\n    })\n\nlog_df.to_csv('Logs.csv')","metadata":{"execution":{"iopub.status.busy":"2023-04-16T20:47:25.971293Z","iopub.status.idle":"2023-04-16T20:47:25.97185Z","shell.execute_reply.started":"2023-04-16T20:47:25.971568Z","shell.execute_reply":"2023-04-16T20:47:25.971597Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Prediction","metadata":{"id":"zF0upgjiW4GB"}}]}