{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":10418,"databundleVersionId":862236,"sourceType":"competition"}],"dockerImageVersionId":30919,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch.nn.functional as F\nfrom torchvision import models, transforms\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom sklearn.metrics import average_precision_score, f1_score\nfrom sklearn.model_selection import train_test_split\nimport random\n\n# CutMix Bounding Box\ndef rand_bbox(size, lam):\n    W = size[2]\n    H = size[3]\n    cut_rat = np.sqrt(1. - lam)\n    cut_w = int(W * cut_rat)\n    cut_h = int(H * cut_rat)\n\n    # Random center\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\n    return bbx1, bby1, bbx2, bby2\n\n# Paths\nDATA_DIR = \"/kaggle/input/human-protein-atlas-image-classification\"\n# DATA_DIR = \"/kaggle/input/humanatlas/d\"\nTRAIN_DIR = os.path.join(DATA_DIR, \"train\")\nCSV_PATH = os.path.join(DATA_DIR, \"train.csv\")\n\n# Load CSV\ndf = pd.read_csv(CSV_PATH)\ndf['Target'] = df['Target'].apply(lambda x: list(map(int, str(x).split())) if pd.notna(x) else [])\n\n# Train-validation split\ntrain_df, val_df = train_test_split(df, test_size=0.2, random_state=42)\n\n# Define Custom Dataset\nclass ProteinDataset(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        self.num_classes = 28\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img_id = self.df.iloc[idx]['Id']\n        img_paths = [os.path.join(self.img_dir, f\"{img_id}_{color}.png\") for color in [\"red\", \"green\", \"blue\", \"yellow\"]]\n\n        # Load 4-channel image\n        channels = [np.array(Image.open(img_path), dtype=np.float32) if os.path.exists(img_path) else np.zeros((512, 512), dtype=np.float32) for img_path in img_paths]\n        image = np.stack(channels, axis=-1)  # Shape: (H, W, 4)\n\n        # Normalize to 0-1\n        image = image / 255.0\n\n        # Apply transforms\n        if self.transform:\n            augmented = self.transform(image=image)\n            image = augmented[\"image\"]\n\n        # Multi-label target\n        target = np.zeros(self.num_classes, dtype=np.float32)\n        for label in self.df.iloc[idx]['Target']:\n            target[label] = 1.0\n        return image, torch.tensor(target, dtype=torch.float32)\n\n# Define Data Augmentation\ntrain_transform = A.Compose([\n    A.RandomResizedCrop(224, 224),\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.5),\n    A.Rotate(limit=30),\n    A.Normalize(mean=[0.5]*4, std=[0.5]*4, max_pixel_value=1.0),\n    ToTensorV2()\n])\n\nval_transform = A.Compose([\n    A.Resize(224, 224),\n    A.Normalize(mean=[0.5]*4, std=[0.5]*4, max_pixel_value=1.0),\n    ToTensorV2()\n])\n\n# Dataloaders\ntrain_dataset = ProteinDataset(train_df, TRAIN_DIR, transform=train_transform)\nval_dataset = ProteinDataset(val_df, TRAIN_DIR, transform=val_transform)\n\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2, pin_memory=True, persistent_workers=True)\nval_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=2, pin_memory=True, persistent_workers=True)\n\n# Modify ResNet to accept 4 channels\nclass CustomResNet(nn.Module):\n    def __init__(self, num_classes=28):\n        super().__init__()\n        from torchvision.models import ResNet50_Weights\n        self.model = models.resnet50(weights=ResNet50_Weights.DEFAULT)\n\n        # Modify first conv layer for 4-channel input\n        self.model.conv1 = nn.Conv2d(4, 64, kernel_size=7, stride=2, padding=3, bias=False)\n\n        # Modify output layer\n        self.model.fc = nn.Linear(self.model.fc.in_features, num_classes)\n\n    def forward(self, x):\n        return self.model(x)\n\nclass FocalLoss(nn.Module):\n    def __init__(self, alpha=1.0, gamma=2.0, reduction='mean'):\n        super().__init__()\n        self.alpha = alpha\n        self.gamma = gamma\n        self.reduction = reduction\n\n    def forward(self, inputs, targets):\n        BCE_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduction='none')\n        probs = torch.sigmoid(inputs)\n        targets = targets.type(inputs.type())\n\n        pt = torch.where(targets == 1, probs, 1 - probs)\n        focal_weight = (self.alpha * (1 - pt) ** self.gamma)\n\n        loss = focal_weight * BCE_loss\n\n        if self.reduction == 'mean':\n            return loss.mean()\n        elif self.reduction == 'sum':\n            return loss.sum()\n        else:\n            return loss\n\n# Training Loop\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nmodel = CustomResNet().to(device)\ncriterion = FocalLoss(alpha=1.0, gamma=2.0).to(device)\noptimizer = optim.Adam(model.parameters(), lr=1e-4)\n\nnum_epochs = 50\n\n# Evaluation Metrics\ndef mean_average_precision(y_true, y_pred):\n    ap_per_class = []\n    for i in range(y_true.shape[1]):\n        ap = average_precision_score(y_true[:, i], y_pred[:, i])\n        ap_per_class.append(ap)\n    return np.mean(ap_per_class)\n\ndef f1_metric(y_true, y_pred, threshold=0.5):\n    y_pred = (y_pred > threshold).astype(int)\n    return f1_score(y_true, y_pred, average=\"macro\")\n\nfor epoch in range(num_epochs):\n    model.train()\n    running_loss = 0.0\n\n    for images, targets in train_loader:\n        images, targets = images.to(device), targets.to(device)\n\n        r = np.random.rand(1)\n        cutmix_prob = 0.5  # Probability to apply CutMix\n        optimizer.zero_grad()\n\n        if r < cutmix_prob:\n            # Apply CutMix\n            lam = np.random.beta(1.0, 1.0)\n            rand_index = torch.randperm(images.size()[0]).to(device)\n            target_a = targets\n            target_b = targets[rand_index]\n            bbx1, bby1, bbx2, bby2 = rand_bbox(images.size(), lam)\n            images[:, :, bbx1:bbx2, bby1:bby2] = images[rand_index, :, bbx1:bbx2, bby1:bby2]\n\n            # Adjust lambda\n            lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (images.size()[-1] * images.size()[-2]))\n\n            outputs = model(images)\n            loss = criterion(outputs, target_a) * lam + criterion(outputs, target_b) * (1. - lam)\n        else:\n            outputs = model(images)\n            loss = criterion(outputs, targets)\n\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n\n    # Validation Loop\n    model.eval()\n    y_true_list, y_pred_list = [], []\n\n    with torch.no_grad():\n        for images, targets in val_loader:\n            images, targets = images.to(device), targets.to(device)\n            outputs = model(images)\n            y_true_list.append(targets.cpu().numpy())\n            y_pred_list.append(torch.sigmoid(outputs).cpu().numpy())\n\n    y_true = np.vstack(y_true_list)\n    y_pred = np.vstack(y_pred_list)\n\n    map_score = mean_average_precision(y_true, y_pred)\n    f1_score_macro = f1_metric(y_true, y_pred)\n\n    print(f\"Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss / len(train_loader):.4f}, mAP: {map_score:.4f}, F1 Score: {f1_score_macro:.4f}\")\n\nprint(\"Training Completed.\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-03-11T00:40:30.153173Z","iopub.execute_input":"2025-03-11T00:40:30.153459Z","execution_failed":"2025-03-11T00:44:36.526Z"}},"outputs":[],"execution_count":null}]}