{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":13836,"databundleVersionId":1718836,"sourceType":"competition"},{"sourceId":1490626,"sourceType":"datasetVersion","datasetId":875062},{"sourceId":1669366,"sourceType":"datasetVersion","datasetId":988673},{"sourceId":2660070,"sourceType":"datasetVersion","datasetId":686792}],"dockerImageVersionId":30034,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"package_paths = [\n    '../input/pytorch-image-models/pytorch-image-models-master', #'../input/efficientnet-pytorch-07/efficientnet_pytorch-0.7.0'\n]\nimport sys; \n\nfor pth in package_paths:\n    sys.path.append(pth)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T14:53:07.911529Z","iopub.execute_input":"2025-03-18T14:53:07.911881Z","iopub.status.idle":"2025-03-18T14:53:07.915943Z","shell.execute_reply.started":"2025-03-18T14:53:07.91185Z","shell.execute_reply":"2025-03-18T14:53:07.915086Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from glob import glob\nfrom sklearn.model_selection import KFold\nimport cv2\nfrom skimage import io\nimport torch\nfrom torch import nn\nimport os\nfrom datetime import datetime\nimport time\nimport random\nimport cv2\nimport torchvision\nfrom torchvision import transforms\nimport pandas as pd\nimport numpy as np\nfrom tqdm import tqdm\n\nimport matplotlib.pyplot as plt\nfrom torch.utils.data import Dataset,DataLoader\nfrom torch.utils.data.sampler import SequentialSampler, RandomSampler\nfrom torch.cuda.amp import autocast, GradScaler\nfrom torch.nn.modules.loss import _WeightedLoss\nimport torch.nn.functional as F\n\nimport timm\n\nimport sklearn\nimport warnings\nimport joblib\nfrom sklearn.metrics import roc_auc_score, log_loss\nfrom sklearn import metrics\nimport warnings\nimport cv2\nimport pydicom\n#from efficientnet_pytorch import EfficientNet\nfrom scipy.ndimage.interpolation import zoom","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-03-18T14:53:07.917886Z","iopub.execute_input":"2025-03-18T14:53:07.918137Z","iopub.status.idle":"2025-03-18T14:53:09.4326Z","shell.execute_reply.started":"2025-03-18T14:53:07.918115Z","shell.execute_reply":"2025-03-18T14:53:09.431518Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 今回の実験の設定 (configration)\nCFG = {\n    'fold_num': 5,\n    'seed': 719,\n    'model_arch': 'simple_cnn',\n    'img_size': 224,\n    'epochs': 1,\n    'train_bs': 32,\n    'valid_bs': 64,\n    'lr': 1e-4,\n    'weight_decay':1e-6,\n    'num_workers': 4,\n    'accum_iter': 2, # suppoprt to do batch accumulation for backprop with effectively larger batch size\n    'verbose_step': 1,\n    'device': 'cuda:0'\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T14:53:09.434318Z","iopub.execute_input":"2025-03-18T14:53:09.434716Z","iopub.status.idle":"2025-03-18T14:53:09.440556Z","shell.execute_reply.started":"2025-03-18T14:53:09.434669Z","shell.execute_reply":"2025-03-18T14:53:09.439523Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = pd.read_csv('../input/cassava-leaf-disease-classification/train.csv')\ntrain.head()","metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true,"execution":{"iopub.status.busy":"2025-03-18T14:53:09.441766Z","iopub.execute_input":"2025-03-18T14:53:09.442084Z","iopub.status.idle":"2025-03-18T14:53:09.47953Z","shell.execute_reply.started":"2025-03-18T14:53:09.442056Z","shell.execute_reply":"2025-03-18T14:53:09.478713Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 各クラスに所属するサンプル数を確認する\ntrain.label.value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T14:53:09.481513Z","iopub.execute_input":"2025-03-18T14:53:09.481787Z","iopub.status.idle":"2025-03-18T14:53:09.488327Z","shell.execute_reply.started":"2025-03-18T14:53:09.481761Z","shell.execute_reply":"2025-03-18T14:53:09.487429Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 提出用ファイル（このnotebookでは使用しません）\nsubmission = pd.read_csv('../input/cassava-leaf-disease-classification/sample_submission.csv')\nsubmission.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T14:53:09.489743Z","iopub.execute_input":"2025-03-18T14:53:09.490085Z","iopub.status.idle":"2025-03-18T14:53:09.505542Z","shell.execute_reply.started":"2025-03-18T14:53:09.49005Z","shell.execute_reply":"2025-03-18T14:53:09.504678Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Helper Functions","metadata":{}},{"cell_type":"code","source":"# 乱数を固定\ndef seed_everything(seed):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = True\n\n# 画像の読み込み\ndef get_img(path):\n    im_bgr = cv2.imread(path)\n    im_rgb = im_bgr[:, :, ::-1]\n    return im_rgb\n\nimg = get_img('../input/cassava-leaf-disease-classification/train_images/1000015157.jpg')\nplt.imshow(img)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T14:53:09.50666Z","iopub.execute_input":"2025-03-18T14:53:09.506988Z","iopub.status.idle":"2025-03-18T14:53:09.716817Z","shell.execute_reply.started":"2025-03-18T14:53:09.506963Z","shell.execute_reply":"2025-03-18T14:53:09.716032Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dataset","metadata":{}},{"cell_type":"code","source":"# ランダムに画像をsizeの大きさになるように切り抜く\ndef rand_bbox(size, lam):\n    W = size[0]\n    H = size[1]\n    cut_rat = np.sqrt(1. - lam)\n    cut_w = np.int(W * cut_rat)\n    cut_h = np.int(H * cut_rat)\n\n    # uniform\n    cx = np.random.randint(W)\n    cy = np.random.randint(H)\n\n    bbx1 = np.clip(cx - cut_w // 2, 0, W)\n    bby1 = np.clip(cy - cut_h // 2, 0, H)\n    bbx2 = np.clip(cx + cut_w // 2, 0, W)\n    bby2 = np.clip(cy + cut_h // 2, 0, H)\n    return bbx1, bby1, bbx2, bby2\n\n\n# 画像と対応するラベルを順番に読み込むためのクラス, 訓練時=> ランダム, 検証時=>元の順番\nclass CassavaDataset(Dataset):\n    def __init__(self, df, data_root, \n                 transforms=None, \n                 output_label=True, \n                 one_hot_label=False,\n                ):\n        \n        super().__init__()\n        self.df = df.reset_index(drop=True).copy()\n        self.transforms = transforms\n        self.data_root = data_root\n        self.output_label = output_label\n        self.one_hot_label = one_hot_label\n        \n        if output_label == True:\n            self.labels = self.df['label'].values\n            \n            if one_hot_label is True:\n                self.labels = np.eye(self.df['label'].max()+1)[self.labels]\n            \n    def __len__(self):\n        return self.df.shape[0]\n    \n    def __getitem__(self, index: int):\n        \n        # get labels\n        if self.output_label:\n            target = self.labels[index]\n          \n        img  = get_img(\"{}/{}\".format(self.data_root, self.df.loc[index]['image_id']))\n\n        if self.transforms:\n            img = self.transforms(image=img)['image']\n        if self.output_label == True:\n            return img, target\n        else:\n            return img","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T14:53:09.718111Z","iopub.execute_input":"2025-03-18T14:53:09.718524Z","iopub.status.idle":"2025-03-18T14:53:09.7331Z","shell.execute_reply.started":"2025-03-18T14:53:09.718488Z","shell.execute_reply":"2025-03-18T14:53:09.732044Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Define Train\\Validation Image Augmentations","metadata":{}},{"cell_type":"code","source":"from albumentations import (\n    RandomResizedCrop,Compose, Normalize, Resize, CenterCrop\n)\n\nfrom albumentations.pytorch import ToTensorV2\n\ndef get_train_transforms():\n    return Compose([\n            RandomResizedCrop(CFG['img_size'], CFG['img_size']),\n            Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225], max_pixel_value=255.0, p=1.0),\n            ToTensorV2(p=1.0),\n        ], p=1.)\n  \n        \ndef get_valid_transforms():\n    return Compose([\n            CenterCrop(CFG['img_size'], CFG['img_size'], p=1.),\n            Resize(CFG['img_size'], CFG['img_size']),\n            Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225], max_pixel_value=255.0, p=1.0),\n            ToTensorV2(p=1.0),\n        ], p=1.)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T14:53:09.734196Z","iopub.execute_input":"2025-03-18T14:53:09.734444Z","iopub.status.idle":"2025-03-18T14:53:10.069353Z","shell.execute_reply.started":"2025-03-18T14:53:09.734419Z","shell.execute_reply":"2025-03-18T14:53:10.068745Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"class CassvaImgClassifier(nn.Module):\n    def __init__(self, n_class):\n        super().__init__()\n        # 第1層: Conv2d → ReLU\n        self.conv1 = nn.Conv2d(in_channels=3, \n                               out_channels=16, \n                               kernel_size=3, \n                               stride=1, \n                               padding=1)\n        self.relu1 = nn.ReLU()\n\n        self.pool_1 = nn.MaxPool2d(kernel_size=2, stride=2)\n\n        # 第2層: Conv2d → ReLU\n        self.conv2 = nn.Conv2d(in_channels=16, \n                               out_channels=32, \n                               kernel_size=3, \n                               stride=1, \n                               padding=1)\n        self.relu2 = nn.ReLU()\n\n        # 第2層: Conv2d → ReLU\n        self.conv3 = nn.Conv2d(in_channels=32, \n                               out_channels=128, \n                               kernel_size=3, \n                               stride=1, \n                               padding=1)\n        self.relu3 = nn.ReLU()\n\n        # Global Average Pooling (空間方向を1×1に潰す)\n        self.pool_2 = nn.AdaptiveAvgPool2d((1, 1))\n\n        # 全結合層（出力次元 = n_class）\n        self.fc = nn.Linear(in_features=128, out_features=n_class)\n\n    def forward(self, x):\n        # Conv1 → ReLU\n        x = self.conv1(x)\n        x = self.relu1(x)\n        x = self.pool_1(x)\n\n        # Conv2 → ReLU\n        x = self.conv2(x)\n        x = self.relu2(x)\n        x = self.pool_1(x)\n\n        x = self.conv3(x)\n        x = self.relu3(x)\n\n        # Global Average Poolingで空間次元を1×1に\n        x = self.pool_2(x)\n\n        # バッチ次元を残してFlatten\n        x = x.view(x.size(0), -1)\n\n        # 全結合層を通して最終出力(n_class次元)\n        x = self.fc(x)\n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T14:53:10.070426Z","iopub.execute_input":"2025-03-18T14:53:10.070676Z","iopub.status.idle":"2025-03-18T14:53:10.081011Z","shell.execute_reply.started":"2025-03-18T14:53:10.07063Z","shell.execute_reply":"2025-03-18T14:53:10.080246Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training APIs","metadata":{}},{"cell_type":"code","source":"def prepare_dataloader(df, trn_idx, val_idx, data_root='../input/cassava-leaf-disease-classification/train_images/'):\n    \n    from catalyst.data.sampler import BalanceClassSampler\n    \n    train_ = df.loc[trn_idx,:].reset_index(drop=True)\n    valid_ = df.loc[val_idx,:].reset_index(drop=True)\n        \n    train_ds = CassavaDataset(train_, data_root, transforms=get_train_transforms(), output_label=True, one_hot_label=False)\n    valid_ds = CassavaDataset(valid_, data_root, transforms=get_valid_transforms(), output_label=True)\n    \n    train_loader = torch.utils.data.DataLoader(\n        train_ds,\n        batch_size=CFG['train_bs'],\n        pin_memory=False,\n        drop_last=False,\n        shuffle=True,        \n        num_workers=CFG['num_workers'],\n        #sampler=BalanceClassSampler(labels=train_['label'].values, mode=\"downsampling\")\n    )\n    val_loader = torch.utils.data.DataLoader(\n        valid_ds, \n        batch_size=CFG['valid_bs'],\n        num_workers=CFG['num_workers'],\n        shuffle=False,\n        pin_memory=False,\n    )\n    return train_loader, val_loader\n\ndef train_one_epoch(epoch, model, loss_fn, optimizer, train_loader, device, scheduler=None, schd_batch_update=False):\n    model.train()\n\n    t = time.time()\n    running_loss = None\n\n    pbar = tqdm(enumerate(train_loader), total=len(train_loader))\n    for step, (imgs, image_labels) in pbar:\n        imgs = imgs.to(device).float()\n        image_labels = image_labels.to(device).long()\n\n        #print(image_labels.shape, exam_label.shape)\n        with autocast():\n            image_preds = model(imgs)   #output = model(input)\n            #print(image_preds.shape, exam_pred.shape)\n\n            loss = loss_fn(image_preds, image_labels)\n            \n            scaler.scale(loss).backward()\n\n            if running_loss is None:\n                running_loss = loss.item()\n            else:\n                running_loss = running_loss * .99 + loss.item() * .01\n\n            if ((step + 1) %  CFG['accum_iter'] == 0) or ((step + 1) == len(train_loader)):\n                # may unscale_ here if desired (e.g., to allow clipping unscaled gradients)\n\n                scaler.step(optimizer)\n                scaler.update()\n                optimizer.zero_grad() \n                \n                if scheduler is not None and schd_batch_update:\n                    scheduler.step()\n\n            if ((step + 1) % CFG['verbose_step'] == 0) or ((step + 1) == len(train_loader)):\n                description = f'epoch {epoch} loss: {running_loss:.4f}'\n                \n                pbar.set_description(description)\n                \n    if scheduler is not None and not schd_batch_update:\n        scheduler.step()\n        \ndef valid_one_epoch(epoch, model, loss_fn, val_loader, device, scheduler=None, schd_loss_update=False):\n    model.eval()\n\n    t = time.time()\n    loss_sum = 0\n    sample_num = 0\n    image_preds_all = []\n    image_targets_all = []\n    \n    pbar = tqdm(enumerate(val_loader), total=len(val_loader))\n    for step, (imgs, image_labels) in pbar:\n        imgs = imgs.to(device).float()\n        image_labels = image_labels.to(device).long()\n        \n        image_preds = model(imgs)   #output = model(input)\n        #print(image_preds.shape, exam_pred.shape)\n        image_preds_all += [torch.argmax(image_preds, 1).detach().cpu().numpy()]\n        image_targets_all += [image_labels.detach().cpu().numpy()]\n        \n        loss = loss_fn(image_preds, image_labels)\n        \n        loss_sum += loss.item()*image_labels.shape[0]\n        sample_num += image_labels.shape[0]  \n\n        if ((step + 1) % CFG['verbose_step'] == 0) or ((step + 1) == len(val_loader)):\n            description = f'epoch {epoch} loss: {loss_sum/sample_num:.4f}'\n            pbar.set_description(description)\n    \n    image_preds_all = np.concatenate(image_preds_all)\n    image_targets_all = np.concatenate(image_targets_all)\n    print('validation multi-class accuracy = {:.4f}'.format((image_preds_all==image_targets_all).mean()))\n    \n    if scheduler is not None:\n        if schd_loss_update:\n            scheduler.step(loss_sum/sample_num)\n        else:\n            scheduler.step()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T14:53:10.082413Z","iopub.execute_input":"2025-03-18T14:53:10.082737Z","iopub.status.idle":"2025-03-18T14:53:10.103956Z","shell.execute_reply.started":"2025-03-18T14:53:10.082713Z","shell.execute_reply":"2025-03-18T14:53:10.103244Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# reference: https://www.kaggle.com/c/siim-isic-melanoma-classification/discussion/173733\nclass MyCrossEntropyLoss(_WeightedLoss):\n    def __init__(self, weight=None, reduction='mean'):\n        super().__init__(weight=weight, reduction=reduction)\n        self.weight = weight\n        self.reduction = reduction\n\n    def forward(self, inputs, targets):\n        lsm = F.log_softmax(inputs, -1)\n\n        if self.weight is not None:\n            lsm = lsm * self.weight.unsqueeze(0)\n\n        loss = -(targets * lsm).sum(-1)\n\n        if  self.reduction == 'sum':\n            loss = loss.sum()\n        elif  self.reduction == 'mean':\n            loss = loss.mean()\n\n        return loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T14:53:10.104943Z","iopub.execute_input":"2025-03-18T14:53:10.105156Z","iopub.status.idle":"2025-03-18T14:53:10.119138Z","shell.execute_reply.started":"2025-03-18T14:53:10.105136Z","shell.execute_reply":"2025-03-18T14:53:10.118481Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Main Loop","metadata":{}},{"cell_type":"code","source":"if __name__ == '__main__':\n     # for training only, need nightly build pytorch\n\n    seed_everything(CFG['seed'])\n    \n    folds = KFold(n_splits=CFG['fold_num'], shuffle=True, random_state=CFG['seed']).split(np.arange(train.shape[0]), train.label.values)\n    \n    for fold, (trn_idx, val_idx) in enumerate(folds):\n        # we'll train fold 0 first\n        if fold > 0:\n            break \n\n        print('Training with {} started'.format(fold))\n\n        print(len(trn_idx), len(val_idx))\n        train_loader, val_loader = prepare_dataloader(train, trn_idx, val_idx, data_root='../input/cassava-leaf-disease-classification/train_images/')\n\n        device = torch.device(CFG['device'])\n        \n        model = CassvaImgClassifier(train.label.nunique()).to(device)\n        scaler = GradScaler()   \n        optimizer = torch.optim.Adam(model.parameters(), lr=CFG['lr'], weight_decay=CFG['weight_decay'])\n        loss_tr = nn.CrossEntropyLoss().to(device) #MyCrossEntropyLoss().to(device)\n        loss_fn = nn.CrossEntropyLoss().to(device)\n        \n        for epoch in range(CFG['epochs']):\n            train_one_epoch(epoch, model, loss_tr, optimizer, train_loader, device, scheduler=None, schd_batch_update=False)\n\n            with torch.no_grad():\n                valid_one_epoch(epoch, model, loss_fn, val_loader, device, scheduler=None, schd_loss_update=False)\n\n            torch.save(model.state_dict(),'{}_fold_{}_{}'.format(CFG['model_arch'], fold, epoch))\n            \n        #torch.save(model.cnn_model.state_dict(),'{}/cnn_model_fold_{}_{}'.format(CFG['model_path'], fold, CFG['tag']))\n        del model, optimizer, train_loader, val_loader, scaler, scheduler\n        torch.cuda.empty_cache()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-18T14:53:10.120002Z","iopub.execute_input":"2025-03-18T14:53:10.120207Z"}},"outputs":[],"execution_count":null}]}