{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":20270,"databundleVersionId":1222630,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-22T17:53:42.115017Z","iopub.execute_input":"2025-11-22T17:53:42.115651Z","iopub.status.idle":"2025-11-22T17:55:22.300881Z","shell.execute_reply.started":"2025-11-22T17:53:42.115624Z","shell.execute_reply":"2025-11-22T17:55:22.299128Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Install dependencies if on Colab:\n# !pip install timm pandas scikit-learn matplotlib tqdm\n\nimport os\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport timm\nfrom torchvision import transforms\nfrom torch.utils.data import DataLoader, Dataset\nfrom PIL import Image\nfrom sklearn.metrics import roc_auc_score, roc_curve, confusion_matrix, accuracy_score\nimport matplotlib.pyplot as plt\nfrom tqdm import tqdm\n\n# ---- Paths ----\n# Set your Kaggle dataset extraction directory here\nIMG_DIR = \"/kaggle/input/siim-isic-melanoma-classification/jpeg/train\"\nCSV_PATH = \"/kaggle/input/siim-isic-melanoma-classification/train.csv\"\n\n# ---- Data Preparation ----\nclass ISICDatasetSSL(Dataset):\n    def __init__(self, img_dir, csv_path=None, transform=None, labeled=False):\n        self.img_dir = img_dir\n        self.transform = transform\n        if labeled:\n            df = pd.read_csv(csv_path)\n            self.images = df['image_name'].tolist()\n            self.labels = df['target'].tolist()\n        else:\n            self.images = [img_name[:-4] for img_name in os.listdir(self.img_dir) if img_name.endswith('.jpg')]\n            self.labels = None\n    def __len__(self):\n        return len(self.images)\n    def __getitem__(self, idx):\n        img_path = os.path.join(self.img_dir, self.images[idx] + \".jpg\")\n        img = Image.open(img_path).convert(\"RGB\")\n        if self.transform:\n            img = self.transform(img)\n        if self.labels is not None:\n            return img, self.labels[idx]\n        return img\n\n# Transforms\nssl_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406], [0.229,0.224,0.225])\n])\neval_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406], [0.229,0.224,0.225])\n])\n\n# Datasets and DataLoaders\nssl_dataset = ISICDatasetSSL(IMG_DIR, transform=ssl_transform, labeled=False)\nN = 2000\nssl_dataset.images = ssl_dataset.images[:N]\n# ssl_loader = DataLoader(ssl_dataset, batch_size=64, shuffle=True, num_workers=2)\nssl_loader = DataLoader(ssl_dataset, batch_size=16, shuffle=True, num_workers=0)\n\n# For downstream: train/test split\ndf = pd.read_csv(CSV_PATH)\nfrom sklearn.model_selection import train_test_split\ntrain_df, val_df = train_test_split(df, test_size=0.2, stratify=df['target'], random_state=42)\ntrain_dataset = ISICDatasetSSL(IMG_DIR, csv_path=CSV_PATH, transform=eval_transform, labeled=True)\nval_dataset   = ISICDatasetSSL(IMG_DIR, csv_path=CSV_PATH, transform=eval_transform, labeled=True)\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)\nval_loader   = DataLoader(val_dataset, batch_size=32)\n\n# ---- SSL Pretrain with DINO-like loss (Simplified Student-Teacher) ----\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\nencoder = timm.create_model('efficientnet_b2', pretrained=False, num_classes=0).to(device)\nhead = nn.Linear(1408, 256).to(device)\noptimizer = torch.optim.Adam(list(encoder.parameters()) + list(head.parameters()), lr=1e-4)\n\ndef dino_loss(student_out):\n    # Simple version: encourage outputs to be uniform, as in entropy maximization\n    p = torch.nn.functional.softmax(student_out, dim=-1)\n    return -(p * torch.log(p + 1e-7)).sum(dim=1).mean()\n\nprint(\"Starting SSL pretraining...\")\nfor epoch in range(10):\n    encoder.train()\n    head.train()\n    running_loss = 0.0\n    for imgs in tqdm(ssl_loader):\n        imgs = imgs.to(device)\n        # Student output\n        feats = encoder(imgs)\n        student_out = head(feats)\n        loss = dino_loss(student_out)\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        running_loss += loss.item()\n    print(f\"Epoch {epoch+1} SSL Loss: {running_loss/len(ssl_loader):.4f}\")\n\n# ---- Feature extraction ----\ndef extract_features(dataloader, encoder, device='cuda'):\n    encoder.eval()\n    all_feats = []\n    all_labels = []\n    with torch.no_grad():\n        for items in tqdm(dataloader):\n            imgs, labels = items\n            imgs = imgs.to(device)\n            feats = encoder(imgs)\n            all_feats.append(feats.cpu())\n            all_labels.extend(labels)\n    return torch.cat(all_feats), np.array(all_labels)\n\ntrain_feats, train_labels = extract_features(train_loader, encoder, device)\nval_feats, val_labels     = extract_features(val_loader, encoder, device)\n\n# ---- Downstream classifier (Logistic Regression) ----\nfrom sklearn.linear_model import LogisticRegression\nclf = LogisticRegression(max_iter=1000)\nclf.fit(train_feats, train_labels)\nval_preds_proba = clf.predict_proba(val_feats)[:,1]\nval_preds = clf.predict(val_feats)\n\n# ---- Evaluation Metrics and Plots ----\nauc_score = roc_auc_score(val_labels, val_preds_proba)\nfpr, tpr, _ = roc_curve(val_labels, val_preds_proba)\ncm = confusion_matrix(val_labels, val_preds, normalize='true')\naccuracy = accuracy_score(val_labels, val_preds)\nsensitivity = cm[1,1]  # Recall/True positive rate for malignant\nspecificity = cm[0,0]  # True negative rate for benign\n\nprint(f\"AUC: {auc_score:.4f} | Accuracy: {accuracy:.4f}\")\nprint(f\"Sensitivity: {sensitivity:.4f}, Specificity: {specificity:.4f}\")\n\n# Plots: Losses, AUC, ROC, Sensitivity vs Specificity, Confusion Matrix\nepochs = np.arange(1, 11)\ndummy_train_losses = np.linspace(1.3, 0.95, 10)    # Replace with real values\ndummy_val_losses = np.linspace(1.3, 1.05, 10)      # Replace with real values\ndummy_aucs = np.linspace(0.5, auc_score, 10)       # Replace with real AUC per epoch\n\nplt.figure(figsize=(10,4))\nplt.subplot(1,2,1)\nplt.plot(epochs, dummy_train_losses, label='Train')\nplt.plot(epochs, dummy_val_losses, label='Val')\nplt.title(\"Losses\")\nplt.legend()\nplt.subplot(1,2,2)\nplt.plot(epochs, dummy_aucs, label='AUC')\nplt.title(f\"AUC (Best: {auc_score:.4f})\")\nplt.legend()\nplt.show()\n\nplt.figure()\nplt.plot(fpr, tpr, label=f'AUC = {auc_score:.4f}')\nplt.plot([0, 1], [0, 1], 'k--')\nplt.title('ROC Curve')\nplt.legend()\nplt.show()\n\nplt.figure()\nplt.plot(epochs, np.clip(np.linspace(1.0, sensitivity, 10), 0, 1), label=\"Sensitivity\")\nplt.plot(epochs, np.clip(np.linspace(0.0, specificity, 10), 0, 1), label=\"Specificity\")\nplt.title(\"Sensitivity vs Specificity\")\nplt.legend()\nplt.show()\n\nplt.figure()\nplt.imshow(cm, cmap='Blues')\nplt.title('Confusion Matrix (Normalized)')\nfor i in range(cm.shape[0]):\n    for j in range(cm.shape[1]):\n        plt.text(j, i, f\"{cm[i, j]*100:.2f}%\", ha=\"center\", va=\"center\",\n                 color=\"white\" if cm[i, j]>0.5 else \"black\")\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"True\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-22T17:55:22.302192Z","iopub.execute_input":"2025-11-22T17:55:22.302529Z","iopub.status.idle":"2025-11-22T21:23:34.790181Z","shell.execute_reply.started":"2025-11-22T17:55:22.302511Z","shell.execute_reply":"2025-11-22T21:23:34.788186Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}