{"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":"code","source":"!pip install efficientnet_pytorch torchtoolbox\n!pip install chart_studio","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2021-06-06T10:52:50.676286Z","iopub.execute_input":"2021-06-06T10:52:50.676801Z","iopub.status.idle":"2021-06-06T10:53:01.807845Z","shell.execute_reply.started":"2021-06-06T10:52:50.676762Z","shell.execute_reply":"2021-06-06T10:53:01.806631Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch\nimport torchvision\nimport torch.nn.functional as F\nimport torch.nn as nn\nimport torchtoolbox.transform as transforms\nfrom torch.utils.data import Dataset, DataLoader, Subset\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\nfrom sklearn.metrics import accuracy_score, roc_auc_score\nfrom sklearn.model_selection import StratifiedKFold, GroupKFold, KFold\nimport pandas as pd\nimport numpy as np\nimport gc\nimport os\nimport cv2\nimport time\nimport datetime\nimport warnings\nimport random\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom efficientnet_pytorch import EfficientNet\n%matplotlib inline","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2021-06-06T10:53:01.813018Z","iopub.execute_input":"2021-06-06T10:53:01.815144Z","iopub.status.idle":"2021-06-06T10:53:02.816750Z","shell.execute_reply.started":"2021-06-06T10:53:01.815098Z","shell.execute_reply":"2021-06-06T10:53:02.815995Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"warnings.simplefilter('ignore')\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\nseed_everything(47)","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:02.818830Z","iopub.execute_input":"2021-06-06T10:53:02.819475Z","iopub.status.idle":"2021-06-06T10:53:02.827628Z","shell.execute_reply.started":"2021-06-06T10:53:02.819431Z","shell.execute_reply":"2021-06-06T10:53:02.826945Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:02.829369Z","iopub.execute_input":"2021-06-06T10:53:02.829760Z","iopub.status.idle":"2021-06-06T10:53:02.853503Z","shell.execute_reply.started":"2021-06-06T10:53:02.829720Z","shell.execute_reply":"2021-06-06T10:53:02.852609Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class MelanomaDataset(Dataset):\n    def __init__(\n        self,\n        df: pd.DataFrame,\n        imfolder: str,\n        train: bool = True,\n        transforms=None,\n        meta_features=None,\n    ):\n        self.df = df\n        self.imfolder = imfolder\n        self.transforms = transforms\n        self.train = train\n        self.meta_features = meta_features\n\n    def __getitem__(self, index):\n        im_path = os.path.join(\n            self.imfolder, self.df.iloc[index][\"image_name\"] + \".jpg\"\n        )\n        x = cv2.imread(im_path)\n        x = cv2.cvtColor(x, cv2.COLOR_RGB2GRAY)\n        \n        meta = np.array(\n            self.df.iloc[index][self.meta_features].values, dtype=np.float32\n        )\n\n        if self.transforms:\n            x = self.transforms(x)\n\n        if self.train:\n            y = self.df.iloc[index][\"target\"]\n            return (x, meta), y\n        else:\n            return (x, meta)\n\n    def __len__(self):\n        return len(self.df)\n\n\nclass Net(nn.Module):\n    def __init__(self, arch, n_meta_features: int):\n        super(Net, self).__init__()\n        self.arch = arch\n        if \"ResNet\" in str(arch.__class__):\n            self.arch.fc = nn.Linear(in_features=512, out_features=500, bias=True)\n        if \"EfficientNet\" in str(arch.__class__):\n            self.arch._fc = nn.Linear(in_features=1280, out_features=500, bias=True)\n        self.meta = nn.Sequential(\n            nn.Linear(n_meta_features, 500),\n            nn.BatchNorm1d(500),\n            nn.ReLU(),\n            nn.Dropout(p=0.2),\n            nn.Linear(500, 250),\n            nn.BatchNorm1d(250),\n            nn.ReLU(),\n            nn.Dropout(p=0.2),\n        )\n        self.ouput = nn.Linear(500 + 250, 1)\n\n    def forward(self, inputs):\n        x, meta = inputs\n        cnn_features = self.arch(x)\n        meta_features = self.meta(meta)\n        features = torch.cat((cnn_features, meta_features), dim=1)\n        output = self.ouput(features)\n        return output\n","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:02.854824Z","iopub.execute_input":"2021-06-06T10:53:02.855277Z","iopub.status.idle":"2021-06-06T10:53:02.874840Z","shell.execute_reply.started":"2021-06-06T10:53:02.855250Z","shell.execute_reply":"2021-06-06T10:53:02.873948Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class AdvancedHairAugmentation:\n    def __init__(self, hairs: int = 5, hairs_folder: str = \"\"):\n        self.hairs = hairs\n        self.hairs_folder = hairs_folder\n\n    def __call__(self, img):\n        n_hairs = random.randint(0, self.hairs)\n\n        if not n_hairs:\n            return img\n\n        height, width = img.shape  # target image width and height\n        hair_images = [im for im in os.listdir(self.hairs_folder) if \"png\" in im]\n\n        for _ in range(n_hairs):\n            hair = cv2.imread(\n                os.path.join(self.hairs_folder, random.choice(hair_images))\n            )\n            hair = cv2.cvtColor(hair, cv2.COLOR_BGR2GRAY)\n            hair = cv2.flip(hair, random.choice([-1, 0, 1]))\n            hair = cv2.rotate(hair, random.choice([0, 1, 2]))\n\n            h_height, h_width = hair.shape  # hair image width and height\n            roi_ho = random.randint(0, img.shape[0] - hair.shape[0])\n            roi_wo = random.randint(0, img.shape[1] - hair.shape[1])\n            roi = img[roi_ho : roi_ho + h_height, roi_wo : roi_wo + h_width]\n\n            # Creating a mask and inverse mask\n            #img2gray = cv2.cvtColor(hair, cv2.COLOR_BGR2GRAY)\n            #ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)\n            ret, mask = cv2.threshold(hair, 10, 255, cv2.THRESH_BINARY)\n            mask_inv = cv2.bitwise_not(mask)\n\n            # Now black-out the area of hair in ROI\n            img_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)\n\n            # Take only region of hair from hair image.\n            hair_fg = cv2.bitwise_and(hair, hair, mask=mask)\n\n            # Put hair in ROI and modify the target image\n            dst = cv2.add(img_bg, hair_fg)\n\n            img[roi_ho : roi_ho + h_height, roi_wo : roi_wo + h_width] = dst\n\n        return img\n\n    def __repr__(self):\n        return f'{self.__class__.__name__}(hairs={self.hairs}, hairs_folder=\"{self.hairs_folder}\")'\n","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:02.879502Z","iopub.execute_input":"2021-06-06T10:53:02.879850Z","iopub.status.idle":"2021-06-06T10:53:02.895616Z","shell.execute_reply.started":"2021-06-06T10:53:02.879814Z","shell.execute_reply":"2021-06-06T10:53:02.894861Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class DrawHair:\n    def __init__(self, hairs: int = 4, width: tuple = (1, 2)):\n        self.hairs = hairs\n        self.width = width\n\n    def __call__(self, img):\n        if not self.hairs:\n            return img\n\n        width, height, _ = img.shape\n\n        for _ in range(random.randint(0, self.hairs)):\n            # The origin point of the line will always be at the top half of the image\n            origin = (random.randint(0, width), random.randint(0, height // 2))\n            # The end of the line\n            end = (random.randint(0, width), random.randint(0, height))\n            color = (0, 0, 0)  # color of the hair. Black.\n            cv2.line(\n                img, origin, end, color, random.randint(self.width[0], self.width[1])\n            )\n\n        return img\n\n    def __repr__(self):\n        return f\"{self.__class__.__name__}(hairs={self.hairs}, width={self.width})\"","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:02.897626Z","iopub.execute_input":"2021-06-06T10:53:02.898183Z","iopub.status.idle":"2021-06-06T10:53:02.908827Z","shell.execute_reply.started":"2021-06-06T10:53:02.898147Z","shell.execute_reply":"2021-06-06T10:53:02.907939Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Microscope:\n    def __init__(self, p: float = 0.5):\n        self.p = p\n\n    def __call__(self, img):\n        if random.random() < self.p:\n            circle = cv2.circle(\n                (np.ones(img.shape) * 255).astype(np.uint8),  # image placeholder\n                (img.shape[0] // 2, img.shape[1] // 2),  # center point of circle\n                random.randint(img.shape[0] // 2 - 3, img.shape[0] // 2 + 15),  # radius\n                (0, 0, 0),  # color\n                -1,\n            )\n\n            mask = circle - 255\n            img = np.multiply(img, mask)\n\n        return img\n\n    def __repr__(self):\n        return f\"{self.__class__.__name__}(p={self.p})\"","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:02.910550Z","iopub.execute_input":"2021-06-06T10:53:02.910978Z","iopub.status.idle":"2021-06-06T10:53:02.922225Z","shell.execute_reply.started":"2021-06-06T10:53:02.910942Z","shell.execute_reply":"2021-06-06T10:53:02.921357Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_transform = transforms.Compose([\n    AdvancedHairAugmentation(hairs_folder='/kaggle/input/melanoma-hairs'),\n    transforms.RandomResizedCrop(size=256, scale=(0.8, 1.0)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    Microscope(p=0.5),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])\n])\ntest_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])\n])","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:02.923577Z","iopub.execute_input":"2021-06-06T10:53:02.924019Z","iopub.status.idle":"2021-06-06T10:53:02.932348Z","shell.execute_reply.started":"2021-06-06T10:53:02.923956Z","shell.execute_reply":"2021-06-06T10:53:02.931461Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"arch = EfficientNet.from_pretrained('efficientnet-b1')","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:02.934081Z","iopub.execute_input":"2021-06-06T10:53:02.934573Z","iopub.status.idle":"2021-06-06T10:53:03.113946Z","shell.execute_reply.started":"2021-06-06T10:53:02.934502Z","shell.execute_reply":"2021-06-06T10:53:03.112973Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df = pd.read_csv('/kaggle/input/jpeg-melanoma-256x256/train.csv')","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:03.115272Z","iopub.execute_input":"2021-06-06T10:53:03.115639Z","iopub.status.idle":"2021-06-06T10:53:03.169706Z","shell.execute_reply.started":"2021-06-06T10:53:03.115598Z","shell.execute_reply":"2021-06-06T10:53:03.169003Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# One-hot encoding of anatom_site_general_challenge feature\ndummies = pd.get_dummies(train_df['anatom_site_general_challenge'], dummy_na=True, dtype=np.uint8, prefix='site')\ntrain_df = pd.concat([train_df, dummies.iloc[:train_df.shape[0]]], axis=1)\n\n# Sex features\ntrain_df['sex'] = train_df['sex'].map({'male': 1, 'female': 0})\ntrain_df['sex'] = train_df['sex'].fillna(-1)\n\n# Age features\ntrain_df['age_approx'] /= train_df['age_approx'].max()\ntrain_df['age_approx'] = train_df['age_approx'].fillna(0)\ntrain_df['patient_id'] = train_df['patient_id'].fillna(0)","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:03.171104Z","iopub.execute_input":"2021-06-06T10:53:03.171596Z","iopub.status.idle":"2021-06-06T10:53:03.203374Z","shell.execute_reply.started":"2021-06-06T10:53:03.171557Z","shell.execute_reply":"2021-06-06T10:53:03.202708Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"meta_features = ['sex', 'age_approx'] + [col for col in train_df.columns if 'site_' in col]\nmeta_features.remove('anatom_site_general_challenge')","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:03.204660Z","iopub.execute_input":"2021-06-06T10:53:03.205010Z","iopub.status.idle":"2021-06-06T10:53:03.212160Z","shell.execute_reply.started":"2021-06-06T10:53:03.204972Z","shell.execute_reply":"2021-06-06T10:53:03.211281Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\nX = train_df.drop(\"target\", axis=1)\ny = train_df[\"target\"]\n\nX_train, X_test, y_train, y_test = train_test_split(\n    X, y, test_size=0.2, random_state=42\n)","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:03.215265Z","iopub.execute_input":"2021-06-06T10:53:03.215513Z","iopub.status.idle":"2021-06-06T10:53:03.231659Z","shell.execute_reply.started":"2021-06-06T10:53:03.215489Z","shell.execute_reply":"2021-06-06T10:53:03.230773Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train[\"target\"] = y_train","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:03.233653Z","iopub.execute_input":"2021-06-06T10:53:03.234179Z","iopub.status.idle":"2021-06-06T10:53:03.239336Z","shell.execute_reply.started":"2021-06-06T10:53:03.234140Z","shell.execute_reply":"2021-06-06T10:53:03.238291Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test = MelanomaDataset(\n    df=X_test.reset_index(drop=True),\n    imfolder=\"/kaggle/input/melanoma-external-malignant-256/train/train/\",\n    train=False,\n    transforms=train_transform,  # For TTA\n    meta_features=meta_features,\n)","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:03.240693Z","iopub.execute_input":"2021-06-06T10:53:03.241309Z","iopub.status.idle":"2021-06-06T10:53:03.247594Z","shell.execute_reply.started":"2021-06-06T10:53:03.241231Z","shell.execute_reply":"2021-06-06T10:53:03.246846Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"skf = GroupKFold(n_splits=5)","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:03.248826Z","iopub.execute_input":"2021-06-06T10:53:03.249170Z","iopub.status.idle":"2021-06-06T10:53:03.256466Z","shell.execute_reply.started":"2021-06-06T10:53:03.249144Z","shell.execute_reply":"2021-06-06T10:53:03.255629Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"epochs = 5  # Number of epochs to run\nes_patience = (\n    3  # Early Stopping patience - for how many epochs with no improvements to wait\n)\nTTA = 3  # Test Time Augmentation rounds\n\noof = np.zeros((len(X_train), 1))  # Out Of Fold predictions\npreds = torch.zeros(\n    (len(test), 1), dtype=torch.float32, device=device\n)  # Predictions for test test\n\n# skf = KFold(n_splits=5, shuffle=True, random_state=47)\nfor fold, (train_idx, val_idx) in enumerate(\n    skf.split(\n        X=np.zeros(len(X_train)),\n        y=X_train[\"target\"],\n        groups=X_train[\"patient_id\"].tolist(),\n    ),\n    1,\n):\n    print(\"=\" * 20, \"Fold\", fold, \"=\" * 20)\n\n    model_path = f\"model_{fold}.pth\"  # Path and filename to save model to\n    best_val = 0  # Best validation score within this fold\n    patience = es_patience  # Current patience counter\n    arch = EfficientNet.from_pretrained(\"efficientnet-b1\")\n    model = Net(\n        arch=arch, n_meta_features=len(meta_features)\n    )  # New model for each fold\n    model = model.to(device)\n\n    optim = torch.optim.Adam(model.parameters(), lr=0.001)\n    scheduler = ReduceLROnPlateau(\n        optimizer=optim, mode=\"max\", patience=1, verbose=True, factor=0.2\n    )\n    criterion = nn.BCEWithLogitsLoss()\n\n    train = MelanomaDataset(\n        df=X_train.iloc[train_idx].reset_index(drop=True),\n        imfolder=\"/kaggle/input/melanoma-external-malignant-256/train/train/\",\n        train=True,\n        transforms=train_transform,\n        meta_features=meta_features,\n    )\n    val = MelanomaDataset(\n        df=X_train.iloc[val_idx].reset_index(drop=True),\n        imfolder=\"/kaggle/input/melanoma-external-malignant-256/train/train/\",\n        train=True,\n        transforms=test_transform,\n        meta_features=meta_features,\n    )\n\n    train_loader = DataLoader(dataset=train, batch_size=64, shuffle=True, num_workers=2)\n    val_loader = DataLoader(dataset=val, batch_size=16, shuffle=False, num_workers=2)\n    test_loader = DataLoader(dataset=test, batch_size=16, shuffle=False, num_workers=2)\n\n    for epoch in range(epochs):\n        start_time = time.time()\n        correct = 0\n        epoch_loss = 0\n        model.train()\n\n        for x, y in train_loader:\n            x[0] = torch.tensor(x[0], device=device, dtype=torch.float32)\n            x[1] = torch.tensor(x[1], device=device, dtype=torch.float32)\n            y = torch.tensor(y, device=device, dtype=torch.float32)\n            optim.zero_grad()\n            z = model(x)\n            loss = criterion(z, y.unsqueeze(1))\n            loss.backward()\n            optim.step()\n            pred = torch.round(\n                torch.sigmoid(z)\n            )  # round off sigmoid to obtain predictions\n            correct += (\n                (pred.cpu() == y.cpu().unsqueeze(1)).sum().item()\n            )  # tracking number of correctly predicted samples\n            epoch_loss += loss.item()\n        train_acc = correct / len(train_idx)\n\n        model.eval()  # switch model to the evaluation mode\n        val_preds = torch.zeros((len(val_idx), 1), dtype=torch.float32, device=device)\n        with torch.no_grad():  # Do not calculate gradient since we are only predicting\n            # Predicting on validation set\n            for j, (x_val, y_val) in enumerate(val_loader):\n                x_val[0] = torch.tensor(x_val[0], device=device, dtype=torch.float32)\n                x_val[1] = torch.tensor(x_val[1], device=device, dtype=torch.float32)\n                y_val = torch.tensor(y_val, device=device, dtype=torch.float32)\n                z_val = model(x_val)\n                val_pred = torch.sigmoid(z_val)\n                val_preds[\n                    j * val_loader.batch_size : j * val_loader.batch_size\n                    + x_val[0].shape[0]\n                ] = val_pred\n            val_acc = accuracy_score(\n                X_train.iloc[val_idx][\"target\"].values, torch.round(val_preds.cpu())\n            )\n            val_roc = roc_auc_score(\n                X_train.iloc[val_idx][\"target\"].values, val_preds.cpu()\n            )\n\n            print(\n                \"Epoch {:03}: | Loss: {:.3f} | Train acc: {:.3f} | Val acc: {:.3f} | Val roc_auc: {:.3f} | Training time: {}\".format(\n                    epoch + 1,\n                    epoch_loss,\n                    train_acc,\n                    val_acc,\n                    val_roc,\n                    str(datetime.timedelta(seconds=time.time() - start_time))[:7],\n                )\n            )\n\n            scheduler.step(val_roc)\n\n            if val_roc >= best_val:\n                best_val = val_roc\n                patience = es_patience\n                torch.save(model, model_path)\n            else:\n                patience -= 1\n                if patience == 0:\n                    print(\"Early stopping. Best Val roc_auc: {:.3f}\".format(best_val))\n                    break\n\n    model = torch.load(model_path)\n    model.eval()\n    val_preds = torch.zeros((len(val_idx), 1), dtype=torch.float32, device=device)\n\n    with torch.no_grad():\n        for j, (x_val, y_val) in enumerate(val_loader):\n            x_val[0] = torch.tensor(x_val[0], device=device, dtype=torch.float32)\n            x_val[1] = torch.tensor(x_val[1], device=device, dtype=torch.float32)\n            y_val = torch.tensor(y_val, device=device, dtype=torch.float32)\n            z_val = model(x_val)\n            val_pred = torch.sigmoid(z_val)\n            val_preds[\n                j * val_loader.batch_size : j * val_loader.batch_size\n                + x_val[0].shape[0]\n            ] = val_pred\n        oof[val_idx] = val_preds.cpu().numpy()\n\n        tta_preds = torch.zeros((len(test), 1), dtype=torch.float32, device=device)\n        for _ in range(TTA):\n            for i, x_test in enumerate(test_loader):\n                x_test[0] = torch.tensor(x_test[0], device=device, dtype=torch.float32)\n                x_test[1] = torch.tensor(x_test[1], device=device, dtype=torch.float32)\n                z_test = model(x_test)\n                z_test = torch.sigmoid(z_test)\n                tta_preds[\n                    i * test_loader.batch_size : i * test_loader.batch_size\n                    + x_test[0].shape[0]\n                ] += z_test\n        preds += tta_preds / TTA\n\npreds /= skf.n_splits","metadata":{"execution":{"iopub.status.busy":"2021-06-06T10:53:03.258093Z","iopub.execute_input":"2021-06-06T10:53:03.258517Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print('OOF: {:.3f}'.format(roc_auc_score(y_train, oof)))\nprint(f\"Test Score: {roc_auc_score(preds.cpu().numpy(), y_test):.5f}\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.kdeplot(pd.Series(preds.cpu().numpy().reshape(-1,)));","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Saving OOF predictions so stacking would be easier\npd.Series(oof.reshape(-1,)).to_csv('oof.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub = pd.DataFrame(X_test[\"image_name\"])\nsub['target'] = preds.cpu().numpy().reshape(-1,)\nsub.to_csv('submission.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}