{"cells":[{"metadata":{},"cell_type":"markdown","source":"**Version 4: 0.812 =>** StratifiedKFold, no fancy augmentation, no weight sampling, efficientnet-b1\n\n**Version 6: 0.902 =>** StratifiedKFold, hair augementation(10), no weight sampling, efficientnet-b1, 256x256\n\n**Version 7: 0.887 =>** same, with 512x512\n\n**Version 8: 0.913 =>** same as v6, with efficientnet-b3","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Preparing the ground\n\nIn this section, we will be setting up the necessary packages, and handle the inconsistencies in the tabular metadata given.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"## Installing additional packages","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install efficientnet_pytorch torchtoolbox","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Importing necessary packages","execution_count":null},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\nimport gc\nimport math\nimport time\nimport random\nimport datetime\nimport warnings\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torchvision\nimport torchtoolbox.transform as transforms\nimport matplotlib.pyplot as plt\nfrom torch.utils.data import Dataset, DataLoader, WeightedRandomSampler\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import accuracy_score, roc_auc_score\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom efficientnet_pytorch import EfficientNet\n%matplotlib inline","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Setting random seeds","execution_count":null},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"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)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Setting device","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Manipulating tabular metadata\n\nIn this section, we will be encoding the categorical data, handle missing data and select the necessary columns for further modelling.\n\nFor more EDA, check out my other notebook [here](https://www.kaggle.com/sayantankarmakar/siim-isic-melanoma-starter-pack).","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"basepath = \"../input/jpeg-melanoma-256x256/\"\ntrain_csv = basepath + \"train.csv\"\ntest_csv = basepath + \"test.csv\"\ntrain_img_path = '../input/jpeg-melanoma-256x256/train/'\ntest_img_path = '../input/jpeg-melanoma-256x256/test/'\n\nprint(train_csv)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!ls ../input/jpeg-melanoma-256x256","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv(train_csv)\ntest_df = pd.read_csv(test_csv)\n\ndisplay(train_df.head())\nprint(\"Train shape: \", train_df.shape)\nprint(\"Test shape: \",test_df.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"location = 'anatom_site_general_challenge'\nconcat = pd.concat([train_df[location], test_df[location]], ignore_index=True)\ndummies = pd.get_dummies(concat, dummy_na=True, dtype=np.uint8, prefix='site')\ntrain_df = pd.concat([train_df, dummies.iloc[:train_df.shape[0]].reset_index(drop=True)], axis=1)\ntest_df = pd.concat([test_df, dummies.iloc[train_df.shape[0]:].reset_index(drop=True)], axis=1)\n\ntrain_df['sex'] = train_df['sex'].map({'male': 1, 'female': 0})\ntrain_df['sex'] = train_df['sex'].fillna(-1)\ntest_df['sex'] = test_df['sex'].map({'male': 1, 'female': 0})\ntest_df['sex'] = test_df['sex'].fillna(-1)\n\ntrain_df['age_approx'] /= train_df['age_approx'].max()\ntrain_df['age_approx'] = train_df['age_approx'].fillna(0)\ntest_df['age_approx'] /= test_df['age_approx'].max()\ntest_df['age_approx'] = test_df['age_approx'].fillna(0)\n\nprint(\"Train shape: \", train_df.shape)\nprint(\"Test shape: \", test_df.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"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')\nmeta_features","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Preparing Data for PyTorch\n\nHere, we'll be preparing our custom `Dataset` for our PyTorch model. Every custom `Dataset` extends the `torch.utils.data.Dataset` class and has mainly **three** functions to implement, which are as follows:\n\n* **\\_\\_init\\_\\_** : initialises the required components to your dataset.\n* **\\_\\_len\\_\\_** : returns the total length of your dataset.\n* **\\_\\_getitem\\_\\_** : given an index in the parameter, it fetches the required data for that index, after processing it however needed.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"class SIIMDataset(Dataset):\n    def __init__(self, df: pd.DataFrame, imgfolder: str, train: bool = True, transforms = None, meta_features = None):\n        self.df = df\n        self.imgfolder = imgfolder\n        self.transforms = transforms\n        self.train = train\n        self.meta_features = meta_features\n    \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        im_path = os.path.join(self.imgfolder, self.df.iloc[idx]['image_name'] + '.jpg')\n        img = cv2.imread(im_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        \n        meta = np.array(self.df.iloc[idx][self.meta_features].values, dtype=np.float32)\n        meta = torch.from_numpy(meta)\n        \n        if self.transforms:\n            img = self.transforms(img)\n        \n        if self.train:\n            target = torch.tensor(self.df.loc[idx, 'target'], dtype=torch.float)\n            return (img, meta), target\n        else:\n            return (img, meta)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Weighted Sampling\n\nSince the given data is highly imbalanced, as you could see in my [EDA Notebook](https://www.kaggle.com/sayantankarmakar/siim-isic-melanoma-starter-pack), it would be ideal to sample them with weights to allow our model to focus more on the minority class. Our weights will be the inverse of the frequency of occurence of each label.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"ones = len(train_df.query('target == 1'))\nzeros = len(train_df.query('target == 0'))\n\nweightage_fn = {0: 1./zeros, 1: 1./ones}\nprint(weightage_fn)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_sampler(df, idx):\n    targets = df['target'][idx].values\n    weights = [weightage_fn[x] for x in targets]\n    sampler = torch.utils.data.WeightedRandomSampler(weights=weights, num_samples=len(weights), replacement=True)\n    return sampler","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Data Augmentation\n\nIn this section, apart from the built in torch transformations available, we also implement a **Hair Augmentation** since it was noticed that most of the images have hair present in them. This will allow our model to be more robust to such situations and improve performance.\n\n*Note: this augmentation was implemented by [Roman](nroman), and he discussed it [here](https://www.kaggle.com/c/siim-isic-melanoma-classification/discussion/159176).*","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"class AdvancedHairAugmentation:\n    \"\"\"\n    Impose an image of a hair to the target image\n\n    Args:\n        hairs (int): maximum number of hairs to impose\n        hairs_folder (str): path to the folder with hairs images\n    \"\"\"\n\n    def __init__(self, hairs: int = 10, hairs_folder: str = \"\"):\n        self.hairs = hairs\n        self.hairs_folder = hairs_folder\n\n    def __call__(self, img):\n        \"\"\"\n        Args:\n            img (PIL Image): Image to draw hairs on.\n\n        Returns:\n            PIL Image: Image with drawn hairs.\n        \"\"\"\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(os.path.join(self.hairs_folder, random.choice(hair_images)))\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            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}\")'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Transformations\n\ntrain_transforms = transforms.Compose([\n    #transforms.ToPILImage(),\n    AdvancedHairAugmentation(hairs_folder='/kaggle/input/melanoma-hairs'),\n    transforms.RandomResizedCrop(size=224, scale=(0.8, 1.0)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.ColorJitter(brightness=24. / 255.,saturation=0.3),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])\n])\n\ntest_transforms = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])\n])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Visualizing augmentations","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def show_images(img):\n    plt.figure(figsize=(18,15))\n    img = img.numpy()\n    plt.imshow(np.transpose(img, (1,2,0)))\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"dataset = SIIMDataset(df=train_df, imgfolder=train_img_path, train=True, transforms=train_transforms, meta_features=meta_features)\nloader = DataLoader(dataset=dataset, batch_size=16, shuffle=True)\ndata = iter(loader)\nimages = data.next()\nshow_images(torchvision.utils.make_grid(images[0][0]))\ndel dataset, loader","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Modelling\n\nHere we finally implement our model, where we use *transfer learning* with an **EfficientNet** model, pretrained on the ImageNet dataset. We also adjust the final fully-connected layer to suit our model and needs. For the metadata, we use the basic Neural Network, with 2 hidden layers, along with dropout and batch normalisation.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"class SIIMNet(nn.Module):\n    def __init__(self, base1, base2, n_meta_features: int):\n        super(SIIMNet, self).__init__()\n        self.base1 = base1\n        self.base1._fc = nn.Linear(in_features=1536, out_features=500, bias=True)\n        self.base2 = base2\n        self.base2._fc = nn.Linear(in_features=1280, out_features=500, bias=True)\n        self.fc1 = nn.Linear(1000, 500)\n        self.bn1 = nn.BatchNorm1d(500)\n        self.meta_net = nn.Sequential(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        self.out = nn.Linear(500+250, 1)\n        \n    def forward(self, inputs):\n        img, meta = inputs\n        cnn1_ = self.base1(img)\n        cnn2_ = self.base2(img)\n        cnn_ = torch.cat((cnn1_, cnn2_), 1)\n        cnn_ = self.fc1(cnn_)\n        cnn_ = self.bn1(cnn_)\n        meta_ = self.meta_net(meta)\n        features = torch.cat((cnn_, meta_), dim=1)\n        output = self.out(features)\n        return output","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# dataset = SIIMDataset(df=train_df, imgfolder=train_img_path, train=True, transforms=train_transforms, meta_features=meta_features)\n# loader = DataLoader(dataset=dataset, batch_size=16, shuffle=True)\n# data = iter(loader)\n# images = data.next()\n\n# base1 = EfficientNet.from_pretrained('efficientnet-b3')\n# base2 = EfficientNet.from_pretrained('efficientnet-b1')\n# model = SIIMNet(base1=base1, base2=base2, n_meta_features=len(meta_features))\n\n# model(images[0])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Config Variables","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# CONFIG \nepochs = 10\nmodel_path = 'model.pth'\nes_patience = 3\nTTA = 3 # Test Time Augmentation\n\nskf = StratifiedKFold(n_splits=5)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# base = EfficientNet.from_pretrained('efficientnet-b1')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# features = base.extract_features(images[0][0])\n# features.shape","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Training and Evaluation\n\nIn this section we'll define functions to train and evaluate our model, and then run a training loop to train our model.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"## Training Function\n\nIn this function, we go through every batch for an epoch and train our model accordingly.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def train_fn(model, train_loader, opt, criterion):\n    batch = 1\n    epoch_loss = 0\n    correct = 0\n    for x, y in train_loader:\n        if(batch % 40 == 0):\n            print(\"=\", end=\"\")\n        x[0] = x[0].to(device)\n        x[1] = x[1].to(device)\n        y = y.to(device)\n\n        opt.zero_grad()\n        z = model(x)\n\n        loss = criterion(z, y.unsqueeze(1))\n        loss.backward()\n        opt.step()\n\n        pred = torch.round(torch.sigmoid(z))\n        correct += (pred.cpu() == y.cpu().unsqueeze(1)).sum().item()\n        epoch_loss += loss.item()\n\n        batch += 1\n    return correct, epoch_loss","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Validation function\n\nIn this function, we go through the validation fold to validate our model and infer the model to get predictions and evaluate using the necessary metrics.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def validate_fn(model, val_loader, val_idx):\n    batch = 1\n    val_preds = torch.zeros((len(val_idx), 1), device=device, dtype=torch.float32)\n    with torch.no_grad():\n        for j, (x_val, y_val) in enumerate(val_loader):\n            if(batch % 20 == 0):\n                print(\"=\", end=\"\")\n            x_val[0] = x_val[0].to(device)\n            x_val[1] = x_val[1].to(device)\n            y_val = y_val.to(device)\n\n            z_val = model(x_val)\n            val_pred = torch.sigmoid(z_val)\n\n            val_preds[j*val_loader.batch_size : j*val_loader.batch_size + x_val[0].shape[0]] = val_pred\n\n            batch += 1\n\n        val_acc = accuracy_score(train_df.iloc[val_idx]['target'].values, torch.round(val_preds.cpu()))\n        val_roc = roc_auc_score(train_df.iloc[val_idx]['target'].values, val_preds.cpu())\n    return val_acc, val_roc","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Engine function\n\nIn this function, we carry out the whole training and validation process using the function mentioned above. We also use **Stratified K-Fold Cross Validation** which allows us to get equal distribution of both class labels in every fold.\n\nAt the end of every fold, we make our model use the **OOF(Out-of-Fold)** validation to get the best version of our model, which is then used to predict on the given test set.\n\nThen at the very end, we divide the predictions by the number of folds to get an average value, which we use as our final predictions.\n\n### Further details\n\n* We use an **Adam** optimizer with an initial learning rate of 0.001\n* We also use a **LRScheduler** which reduces our learning rate as our metrics start to plateau.\n* We also use **TTA(Test-Time-Augmentation)** which allows us to make multiple predictions for each image in the test set, using various augmentations, and then average the predictions.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def engine():\n    \n    oof = np.zeros((len(train_df), 1))\n    preds = torch.zeros((len(test_df), 1), dtype=torch.float32, device=device)\n    \n    test_ds = SIIMDataset(\n        df = test_df,\n        imgfolder = test_img_path,\n        train = False,\n        transforms = train_transforms,\n        meta_features = meta_features\n    )\n    \n    for fold, (train_idx, val_idx) in enumerate(skf.split(X=np.zeros(len(train_df)), y=train_df['target'], groups=train_df['patient_id'].tolist()), start=1):\n        print(\"\\n\")\n        print(\"=\"*15, \"FOLD \", fold, \"=\"*15)\n        \n        best_val = None\n        patience = es_patience\n\n        base1 = EfficientNet.from_pretrained('efficientnet-b3')\n        base2 = EfficientNet.from_pretrained('efficientnet-b1')\n        model = SIIMNet(base1=base1, base2=base2, n_meta_features=len(meta_features))\n        model.to(device)\n\n        opt = torch.optim.Adam(model.parameters(), lr=0.001)\n        scheduler = ReduceLROnPlateau(optimizer=opt, mode='max', patience=1, factor=0.2, verbose=True)\n        criterion = nn.BCEWithLogitsLoss()\n\n        \n        train_ds = SIIMDataset(\n            df = train_df.iloc[train_idx].reset_index(drop=True),\n            imgfolder = train_img_path,\n            train = True,\n            transforms = train_transforms,\n            meta_features = meta_features\n        )\n        sampler = get_sampler(train_df, train_idx)\n        train_loader = DataLoader(dataset=train_ds, batch_size=32, sampler=sampler, num_workers=2)\n\n        val_ds = SIIMDataset(\n            df = train_df.iloc[val_idx].reset_index(drop=True),\n            imgfolder = train_img_path,\n            train = True,\n            transforms = test_transforms,\n            meta_features = meta_features\n        )\n        val_loader = DataLoader(dataset=val_ds, batch_size=16, shuffle=False, num_workers=2)\n\n        test_loader = DataLoader(dataset=test_ds, batch_size=16, shuffle=False, num_workers=2)\n        \n        for epoch in range(epochs):\n            start_time = time.time()\n\n            print(\"Training:\")\n            model.train()\n            correct, epoch_loss = train_fn(model, train_loader, opt, criterion)\n            train_acc = correct / len(train_idx)\n\n            print(\"\\nValidating:\")\n            model.eval()            \n            val_acc, val_roc = validate_fn(model, val_loader, val_idx)\n            scheduler.step(val_roc)\n        \n            print('\\nEpoch {: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            if not best_val:\n                best_val = val_roc\n                torch.save(model, model_path)\n                continue\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        val_preds = torch.zeros((len(val_idx), 1), device=device, dtype=torch.float32)\n\n        batch = 1\n\n        print(\"Validating model for FOLD {}:\".format(fold))\n        model.eval()\n        with torch.no_grad():\n            for j, (x_val, y_val) in enumerate(val_loader):\n                if(batch % 20 == 0):\n                    print(\"=\", end=\"\")\n                x_val[0] = x_val[0].to(device)\n                x_val[1] = x_val[1].to(device)\n                y_val = torch.tensor(y_val, device=device, dtype=torch.float32)\n\n                z_val = model(x_val)\n                val_pred = torch.sigmoid(z_val)\n\n                val_preds[j*val_loader.batch_size : j*val_loader.batch_size + x_val[0].shape[0]] = val_pred\n\n                batch += 1\n\n            oof[val_idx] = val_preds.cpu().numpy()\n\n\n            batch = 1\n\n            print(\"\\nTesting:\")\n            for _ in range(TTA):\n                for i, x_test in enumerate(test_loader):\n                    if(batch % 60 == 0):\n                        print(\"=\", end=\"\")\n                    x_test[0] = x_test[0].to(device)\n                    x_test[1] = x_test[1].to(device)\n\n                    z_test = model(x_test)\n                    z_test = torch.sigmoid(z_test)\n\n                    preds[i*test_loader.batch_size : i*test_loader.batch_size + x_test[0].shape[0]] += z_test\n\n                    batch += 1\n            preds /= TTA\n\n        del train_ds, val_ds, train_loader, val_loader, x_val, y_val\n        gc.collect()\n\n    preds /= skf.n_splits\n    return preds, oof","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"preds, oof = engine()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print('OOF: {:.3f}'.format(roc_auc_score(train_df['target'], oof)))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"pd.Series(oof.reshape(-1,)).to_csv('oof.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sub = pd.read_csv('/kaggle/input/siim-isic-melanoma-classification/sample_submission.csv')\nsub['target'] = preds.cpu().numpy().reshape(-1,)\nsub.to_csv('submission-v16.csv', index=False)","execution_count":null,"outputs":[]}],"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":4,"nbformat_minor":4}