{"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"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":20270,"databundleVersionId":1222630,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Complete K-Fold Cross-Validation Workflow for SIIM-ISIC Melanoma Classification\n# Using EfficientNet in PyTorch with StratifiedKFold\n\nimport os\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import roc_auc_score\nimport timm\nfrom tqdm import tqdm\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Configuration\nclass Config:\n    seed = 42\n    n_folds = 5\n    img_size = 384\n    batch_size = 16\n    num_epochs = 10\n    learning_rate = 1e-4\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    model_name = 'tf_efficientnet_b3_ns'\n    num_classes = 1\n    save_path = '/kaggle/working/'\n\n# Set random seeds for reproducibility\ndef set_seed(seed):\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nset_seed(Config.seed)\n\n# Custom Dataset Class\nclass MelanomaDataset(Dataset):\n    def __init__(self, df, img_dir, transform=None):\n        self.df = df\n        self.img_dir = img_dir\n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_path = os.path.join(self.img_dir, f\"{row['image_name']}.jpg\")\n        \n        # Load image\n        image = Image.open(img_path).convert('RGB')\n        \n        if self.transform:\n            image = self.transform(image)\n        \n        label = torch.tensor(row['target'], dtype=torch.float32)\n        \n        return image, label\n\n# Data Transforms\ntrain_transform = transforms.Compose([\n    transforms.Resize((Config.img_size, Config.img_size)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.5),\n    transforms.RandomRotation(20),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\nval_transform = transforms.Compose([\n    transforms.Resize((Config.img_size, Config.img_size)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\n# Model Definition\nclass EfficientNetModel(nn.Module):\n    def __init__(self, model_name=Config.model_name, num_classes=Config.num_classes, pretrained=True):\n        super(EfficientNetModel, self).__init__()\n        self.model = timm.create_model(model_name, pretrained=pretrained)\n        in_features = self.model.classifier.in_features\n        self.model.classifier = nn.Linear(in_features, num_classes)\n        \n    def forward(self, x):\n        return self.model(x)\n\n# Training Function\ndef train_one_epoch(model, dataloader, criterion, optimizer, device):\n    model.train()\n    running_loss = 0.0\n    \n    for images, labels in tqdm(dataloader, desc='Training'):\n        images, labels = images.to(device), labels.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(images).squeeze()\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        \n        running_loss += loss.item() * images.size(0)\n    \n    epoch_loss = running_loss / len(dataloader.dataset)\n    return epoch_loss\n\n# Validation Function\ndef validate(model, dataloader, criterion, device):\n    model.eval()\n    running_loss = 0.0\n    all_preds = []\n    all_labels = []\n    \n    with torch.no_grad():\n        for images, labels in tqdm(dataloader, desc='Validation'):\n            images, labels = images.to(device), labels.to(device)\n            \n            outputs = model(images).squeeze()\n            loss = criterion(outputs, labels)\n            \n            running_loss += loss.item() * images.size(0)\n            \n            preds = torch.sigmoid(outputs).cpu().numpy()\n            all_preds.extend(preds)\n            all_labels.extend(labels.cpu().numpy())\n    \n    epoch_loss = running_loss / len(dataloader.dataset)\n    auc_score = roc_auc_score(all_labels, all_preds)\n    \n    return epoch_loss, auc_score\n\n# Main K-Fold Cross-Validation\ndef main():\n    # Load training data\n    train_df = pd.read_csv('/kaggle/input/siim-isic-melanoma-classification/train.csv')\n    img_dir = '/kaggle/input/siim-isic-melanoma-classification/jpeg/train'\n    \n    print(f\"Training data shape: {train_df.shape}\")\n    print(f\"Target distribution:\\n{train_df['target'].value_counts()}\")\n    \n    # Initialize StratifiedKFold\n    skf = StratifiedKFold(n_splits=Config.n_folds, shuffle=True, random_state=Config.seed)\n    fold_scores = []\n    \n    # K-Fold Cross-Validation Loop\n    for fold, (train_idx, val_idx) in enumerate(skf.split(train_df, train_df['target'])):\n        print(f\"\\n{'='*60}\")\n        print(f\"Fold {fold + 1}/{Config.n_folds}\")\n        print(f\"{'='*60}\")\n        \n        # Split data\n        train_fold_df = train_df.iloc[train_idx].reset_index(drop=True)\n        val_fold_df = train_df.iloc[val_idx].reset_index(drop=True)\n        \n        print(f\"Train size: {len(train_fold_df)}, Val size: {len(val_fold_df)}\")\n        \n        # Create datasets and dataloaders\n        train_dataset = MelanomaDataset(train_fold_df, img_dir, transform=train_transform)\n        val_dataset = MelanomaDataset(val_fold_df, img_dir, transform=val_transform)\n        \n        train_loader = DataLoader(train_dataset, batch_size=Config.batch_size, \n                                 shuffle=True, num_workers=2, pin_memory=True)\n        val_loader = DataLoader(val_dataset, batch_size=Config.batch_size, \n                               shuffle=False, num_workers=2, pin_memory=True)\n        \n        # Initialize model, criterion, optimizer\n        model = EfficientNetModel().to(Config.device)\n        criterion = nn.BCEWithLogitsLoss()\n        optimizer = optim.Adam(model.parameters(), lr=Config.learning_rate)\n        scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', \n                                                         factor=0.5, patience=2, verbose=True)\n        \n        # Training loop for this fold\n        best_auc = 0.0\n        best_model_path = os.path.join(Config.save_path, f'efficientnet_fold{fold+1}_best.pth')\n        \n        for epoch in range(Config.num_epochs):\n            print(f\"\\nEpoch {epoch + 1}/{Config.num_epochs}\")\n            \n            train_loss = train_one_epoch(model, train_loader, criterion, optimizer, Config.device)\n            val_loss, val_auc = validate(model, val_loader, criterion, Config.device)\n            \n            print(f\"Train Loss: {train_loss:.4f}\")\n            print(f\"Val Loss: {val_loss:.4f}, Val AUC: {val_auc:.4f}\")\n            \n            # Save best model for this fold\n            if val_auc > best_auc:\n                best_auc = val_auc\n                torch.save({\n                    'epoch': epoch,\n                    'model_state_dict': model.state_dict(),\n                    'optimizer_state_dict': optimizer.state_dict(),\n                    'val_auc': val_auc,\n                    'fold': fold\n                }, best_model_path)\n                print(f\"Best model saved with AUC: {best_auc:.4f}\")\n            \n            scheduler.step(val_auc)\n        \n        print(f\"\\nFold {fold + 1} Best AUC: {best_auc:.4f}\")\n        fold_scores.append(best_auc)\n        \n        # Clean up memory\n        del model, optimizer, train_loader, val_loader\n        torch.cuda.empty_cache()\n    \n    # Print final results\n    print(f\"\\n{'='*60}\")\n    print(\"K-Fold Cross-Validation Results\")\n    print(f\"{'='*60}\")\n    for i, score in enumerate(fold_scores):\n        print(f\"Fold {i + 1} AUC: {score:.4f}\")\n    print(f\"\\nMean AUC across {Config.n_folds} folds: {np.mean(fold_scores):.4f} ± {np.std(fold_scores):.4f}\")\n    print(f\"\\nAll fold model weights saved to: {Config.save_path}\")\n\nif __name__ == '__main__':\n    main()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null}]}