{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":132732,"databundleVersionId":16583342}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Vis","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nfrom torchvision import transforms\n\n# ============================================================\n# 1. LOAD AN IMAGE\n# ------------------------------------------------------------\n# Replace this path with any image you have locally\n# ============================================================\nimg_path = \"/kaggle/input/competitions/dlmmdd-workshop-synthetic-source-attribution-challenge/Data/Data/Training/0.png\"  # <-- change this\n\nimg = Image.open(img_path).convert(\"RGB\")\n\n# Show original image\nplt.imshow(img)\nplt.title(\"Original Image (PIL)\")\nplt.axis(\"off\")\nplt.show()\n\n# ============================================================\n# 2. CONVERT IMAGE TO NUMPY ARRAY\n# ------------------------------------------------------------\n# This is what the image REALLY is:\n# A 3D array: (height, width, channels)\n# ============================================================\nimg_np = np.array(img)\n\nimg_np.shape\n\nimg_np[:, :, 0]\n\nprint(\"\\n--- NUMPY ARRAY REPRESENTATION ---\")\nprint(\"Shape:\", img_np.shape)   # (H, W, 3)\nprint(\"Dtype:\", img_np.dtype)   # usually uint8\nprint(\"Min value:\", img_np.min())\nprint(\"Max value:\", img_np.max())\n\n# Show a small patch of pixel values\nprint(\"\\nSample pixel values (top-left corner):\")\nprint(img_np[:5, :5, :])\n\n# ============================================================\n# 3. VISUALIZE EACH COLOR CHANNEL\n# ------------------------------------------------------------\n# This helps students understand RGB structure\n# ============================================================\nfig, axes = plt.subplots(1, 3, figsize=(12, 4))\n\naxes[0].imshow(img_np[:, :, 0], cmap='Reds')\naxes[0].set_title(\"Red Channel\")\n\naxes[1].imshow(img_np[:, :, 1], cmap='Greens')\naxes[1].set_title(\"Green Channel\")\n\naxes[2].imshow(img_np[:, :, 2], cmap='Blues')\naxes[2].set_title(\"Blue Channel\")\n\nfor ax in axes:\n    ax.axis(\"off\")\n\nplt.suptitle(\"Each channel is just numbers!\")\nplt.show()\n\n# ============================================================\n# 4. DEFINE TRAINING TRANSFORM (same idea as your pipeline)\n# ------------------------------------------------------------\n# Converts:\n# - PIL image → Tensor\n# - values from [0, 255] → [0, 1]\n# - then normalizes to [-1, 1]\n# ============================================================\ntransform = transforms.Compose([\n    transforms.ToTensor(),                     # (H, W, C) → (C, H, W), scale to [0,1]\n    transforms.Normalize([0.5]*3, [0.5]*3)     # normalize\n])\n\n\n# ============================================================\n# 5. APPLY TRANSFORM\n# ============================================================\nimg_tensor = transform(img)\n\nprint(\"\\n--- AFTER TRANSFORM ---\")\nprint(\"Shape:\", img_tensor.shape)   # (3, H, W)\nprint(\"Dtype:\", img_tensor.dtype)\nprint(\"Min value:\", img_tensor.min().item())\nprint(\"Max value:\", img_tensor.max().item())\n\n# Show small tensor patch\nprint(\"\\nSample tensor values (channel 0):\")\nprint(img_tensor[0, :5, :5])\n\n# ============================================================\n# 6. VISUALIZE TRANSFORMED IMAGE\n# ------------------------------------------------------------\n# We need to \"undo normalization\" to display properly\n# ============================================================\nimg_vis = img_tensor.clone()\n\n# Undo normalization: x * std + mean\n#img_vis = img_vis * 0.5 + 0.5\n\n# Convert (C, H, W) → (H, W, C)\nimg_vis = img_vis.permute(1, 2, 0).numpy()\n\nplt.imshow(img_vis)\nplt.title(\"After Transform (visualized)\")\nplt.axis(\"off\")\nplt.show()\n\n# ============================================================\n# 7. CONVOLUTIONS — HOW COMPUTERS \"LOOK\" FOR PATTERNS\n# ------------------------------------------------------------\n# A convolution uses a small matrix called a KERNEL or FILTER.\n# The filter slides over the image and computes a weighted sum\n# of neighboring pixels.\n#\n# This allows the model to detect:\n# - edges\n# - blur\n# - sharpening\n# - outlines\n# - texture\n#\n# In CNNs (convolutional neural networks), the model learns these filters automatically.\n# Here we will define some by hand to show the idea.\n# ============================================================\n\nimport torch\nimport torch.nn.functional as F\n\nprint(\"\\n--- WHAT IS A CONVOLUTION? ---\")\nprint(\"A convolution takes a small filter (kernel), slides it over the image,\")\nprint(\"and at each position computes a weighted sum of nearby pixels.\")\nprint(\"This helps detect simple visual patterns like edges and lines.\")\n\n# ------------------------------------------------------------\n# Helper function:\n# Apply a 2D convolution kernel to each RGB channel separately\n# ------------------------------------------------------------\ndef apply_kernel_rgb(image_tensor, kernel):\n    \"\"\"\n    image_tensor: torch.Tensor of shape (3, H, W)\n    kernel: torch.Tensor of shape (k, k)\n    returns: torch.Tensor of shape (3, H, W)\n    \"\"\"\n    # Add batch dimension -> (1, 3, H, W)\n    x = image_tensor.unsqueeze(0)\n\n    # Prepare kernel for grouped convolution:\n    # one copy of the same kernel for each channel\n    k = kernel.shape[0]\n    weight = kernel.view(1, 1, k, k).repeat(3, 1, 1, 1)  # (3,1,k,k)\n\n    # grouped conv: each channel convolved independently\n    out = F.conv2d(x, weight, padding=k // 2, groups=3)\n\n    return out.squeeze(0)\n\n# ------------------------------------------------------------\n# For visualization:\n# Normalize tensor to [0,1] so matplotlib can display it\n# ------------------------------------------------------------\ndef tensor_to_display(img_t):\n    img_disp = img_t.clone().detach()\n\n    # (C, H, W) → (H, W, C)\n    img_disp = img_disp.permute(1, 2, 0).numpy()\n\n    # Normalize to [0,1]\n    img_min = img_disp.min()\n    img_max = img_disp.max()\n\n    if img_max - img_min > 1e-5:\n        img_disp = (img_disp - img_min) / (img_max - img_min)\n    else:\n        img_disp = np.zeros_like(img_disp)\n\n    return img_disp\n\n# ------------------------------------------------------------\n# Use a [0,1] tensor for easier interpretation\n# ------------------------------------------------------------\nimg_tensor_01 = transforms.ToTensor()(img)\n\nprint(\"\\nImage tensor for convolution:\")\nprint(\"Shape:\", img_tensor_01.shape)\nprint(\"Value range:\", (img_tensor_01.min().item(), img_tensor_01.max().item()))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-11T15:12:23.417494Z","iopub.execute_input":"2026-05-11T15:12:23.418210Z","iopub.status.idle":"2026-05-11T15:12:33.609718Z","shell.execute_reply.started":"2026-05-11T15:12:23.418166Z","shell.execute_reply":"2026-05-11T15:12:33.608930Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 8. FIRST EXAMPLE: A SIMPLE EDGE DETECTOR\n# ============================================================\n\n# Convert image to grayscale for a cleaner convolution demo\nimg_gray = img.convert(\"L\")\nimg_gray_tensor = transforms.ToTensor()(img_gray)   # shape: (1, H, W)\n\ndef apply_kernel_gray(image_tensor, kernel):\n    \"\"\"\n    image_tensor: torch.Tensor of shape (1, H, W)\n    kernel: torch.Tensor of shape (k, k)\n    returns: torch.Tensor of shape (1, H, W)\n    \"\"\"\n    x = image_tensor.unsqueeze(0)  # (1,1,H,W)\n    k = kernel.shape[0]\n    weight = kernel.view(1, 1, k, k)\n    out = F.conv2d(x, weight, padding=k // 2)\n    return out.squeeze(0)\n\ndef gray_tensor_to_display(img_t):\n    img_disp = img_t.clone().detach().squeeze(0).numpy()\n\n    img_min = img_disp.min()\n    img_max = img_disp.max()\n\n    if img_max - img_min > 1e-5:\n        img_disp = (img_disp - img_min) / (img_max - img_min)\n    else:\n        img_disp = np.zeros_like(img_disp)\n\n    return img_disp\n\nsimple_kernel = torch.tensor([\n    [-1., -1., -1.],\n    [ 0.,  0.,  0.],\n    [ 1.,  1.,  1.]\n])\n\nsimple_out = apply_kernel_gray(img_gray_tensor, simple_kernel)\n\nprint(\"\\n--- SIMPLE CONVOLUTION FILTER ---\")\nprint(\"Kernel:\")\nprint(simple_kernel)\nprint(\"Min:\", simple_out.min().item())\nprint(\"Max:\", simple_out.max().item())\n\nfig, axes = plt.subplots(1, 2, figsize=(10, 4))\n\naxes[0].imshow(img_gray, cmap=\"gray\")\naxes[0].set_title(\"Original Grayscale Image\")\naxes[0].axis(\"off\")\n\naxes[1].imshow(gray_tensor_to_display(simple_out), cmap=\"gray\")\naxes[1].set_title(\"After Simple Edge Filter\")\naxes[1].axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-11T15:12:33.611217Z","iopub.execute_input":"2026-05-11T15:12:33.611715Z","iopub.status.idle":"2026-05-11T15:12:34.273677Z","shell.execute_reply.started":"2026-05-11T15:12:33.611686Z","shell.execute_reply":"2026-05-11T15:12:34.272796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 9. CLASSIC FILTERS IN GRAYSCALE\n# ============================================================\n\nfilters = {\n    \"Identity\": torch.tensor([\n        [0., 0., 0.],\n        [0., 1., 0.],\n        [0., 0., 0.]\n    ]),\n\n    \"Box Blur\": (1/2) * torch.tensor([\n        [1., 1., 1.],\n        [1., 1., 1.],\n        [1., 1., 1.]\n    ]),\n\n    \"Sharpen\": torch.tensor([\n        [ 0., -1.,  0.],\n        [-1.,  5., -1.],\n        [ 0., -1.,  0.]\n    ]),\n\n    \"Edge Detect\": torch.tensor([\n        [-1., -1., -1.],\n        [-1.,  8., -1.],\n        [-1., -1., -1.]\n    ]),\n\n    \"Horizontal Edges\": torch.tensor([\n        [-1., -1., -1.],\n        [ 0.,  0.,  0.],\n        [ 1.,  1.,  1.]\n    ]),\n\n    \"Vertical Edges\": torch.tensor([\n        [-1.,  0.,  1.],\n        [-1.,  0.,  1.],\n        [-1.,  0.,  1.]\n    ]),\n\n    \"Emboss\": torch.tensor([\n        [-2., -1.,  0.],\n        [-1.,  1.,  1.],\n        [ 0.,  1.,  2.]\n    ])\n}\n\nn_filters = len(filters)\nfig, axes = plt.subplots(n_filters, 3, figsize=(12, 4 * n_filters))\n\nfor row, (name, kernel) in enumerate(filters.items()):\n    out = apply_kernel_gray(img_gray_tensor, kernel)\n\n    axes[row, 0].imshow(img_gray, cmap=\"gray\")\n    axes[row, 0].set_title(\"Original\")\n    axes[row, 0].axis(\"off\")\n\n    axes[row, 1].imshow(kernel.numpy(), cmap=\"coolwarm\")\n    axes[row, 1].set_title(f\"{name} Kernel\")\n    axes[row, 1].set_xticks(range(kernel.shape[1]))\n    axes[row, 1].set_yticks(range(kernel.shape[0]))\n\n    for i in range(kernel.shape[0]):\n        for j in range(kernel.shape[1]):\n            axes[row, 1].text(j, i, f\"{kernel[i,j]:.1f}\",\n                              ha=\"center\", va=\"center\", color=\"black\", fontsize=10)\n\n    axes[row, 2].imshow(gray_tensor_to_display(out), cmap=\"gray\")\n    axes[row, 2].set_title(\"Filtered Image\")\n    axes[row, 2].axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-11T15:12:34.274508Z","iopub.execute_input":"2026-05-11T15:12:34.274798Z","iopub.status.idle":"2026-05-11T15:12:38.721783Z","shell.execute_reply.started":"2026-05-11T15:12:34.274774Z","shell.execute_reply":"2026-05-11T15:12:38.720941Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model training ","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# IMAGE CLASSIFICATION PIPELINE IN PYTORCH\n# ------------------------------------------------------------\n# This script:\n# 1. Reads training and test metadata from CSV files\n# 2. Loads images from disk\n# 3. Applies preprocessing transforms\n# 4. Builds an EfficientNet-B0 model\n# 5. Trains the model on the training split\n# 6. Evaluates it on a validation split\n# 7. Saves the best model\n# 8. Predicts labels for the test set\n# 9. Writes predictions to submission.csv\n# ============================================================\n\nimport os\nimport numpy as np\nimport pandas as pd\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\n\nfrom torch.utils.data import Dataset, DataLoader\nfrom PIL import Image\nfrom torchvision import transforms, models\nfrom tqdm import tqdm\nfrom sklearn.model_selection import train_test_split\nfrom collections import OrderedDict","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-11T15:12:38.723779Z","iopub.execute_input":"2026-05-11T15:12:38.724304Z","iopub.status.idle":"2026-05-11T15:12:39.960231Z","shell.execute_reply.started":"2026-05-11T15:12:38.724277Z","shell.execute_reply":"2026-05-11T15:12:39.959341Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CONFIGURATION CLASS\n# ------------------------------------------------------------\n# This class stores all important paths and hyperparameters\n# in one place, so they are easy to change.\n# ============================================================\nclass CFG:\n    # Root directory containing all competition data\n    root = \"/kaggle/input/competitions/dlmmdd-workshop-synthetic-source-attribution-challenge/Data/Data\"\n\n    # CSV files with metadata\n    train_csv = os.path.join(root, \"training.csv\")\n    test_csv = os.path.join(root, \"test.csv\")\n\n    # Folders containing actual images\n    train_dir = os.path.join(root, \"Training\")\n    test_dir = os.path.join(root, \"Test\")\n\n    # Use GPU if available, otherwise CPU\n    device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\n    # Training hyperparameters\n    batch_size = 8\n    epochs = 5\n    lr = 3e-4 #0.0003 https://x.com/karpathy/status/801621764144971776\n\n    # Number of output classes for classification\n    num_classes = 10\n\n    # All images will be resized to this size\n    img_size = 224","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-11T15:12:39.961331Z","iopub.execute_input":"2026-05-11T15:12:39.961856Z","iopub.status.idle":"2026-05-11T15:12:40.211946Z","shell.execute_reply.started":"2026-05-11T15:12:39.961820Z","shell.execute_reply":"2026-05-11T15:12:40.211198Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# PATH RESOLUTION FUNCTION\n# ------------------------------------------------------------\n# The CSV may store image paths in different formats.\n# This helper function tries a few possible locations and\n# returns the first valid path it finds.\n# ============================================================\ndef resolve_path(p):\n    \"\"\"\n    Try multiple possible file locations for an image path.\n\n    Parameters\n    ----------\n    p : str\n        Path string stored in the CSV.\n\n    Returns\n    -------\n    str or None\n        A valid file path if found, otherwise None.\n    \"\"\"\n    candidates = [\n        os.path.join(CFG.root, p),\n        os.path.join(CFG.train_dir, os.path.basename(p)),\n        os.path.join(CFG.test_dir, os.path.basename(p)),\n        p\n    ]\n\n    for candidate in candidates:\n        if os.path.exists(candidate):\n            return candidate\n\n    return None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-11T15:12:40.213088Z","iopub.execute_input":"2026-05-11T15:12:40.213431Z","iopub.status.idle":"2026-05-11T15:12:40.237137Z","shell.execute_reply.started":"2026-05-11T15:12:40.213403Z","shell.execute_reply":"2026-05-11T15:12:40.236284Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# TRAIN DATASET\n# ------------------------------------------------------------\n# A PyTorch Dataset defines how to:\n# - get the total number of examples\n# - load one example by index\n#\n# This dataset is used for both training and validation\n# because both have images and labels.\n# ============================================================\nclass TrainDataset(Dataset):\n    def __init__(self, df, tfm):\n        \"\"\"\n        Parameters\n        ----------\n        df : pandas.DataFrame\n            DataFrame with at least columns: 'path' and 'y'\n        tfm : torchvision.transforms\n            Transform pipeline to apply to each image\n        \"\"\"\n        self.df = df.reset_index(drop=True)\n        self.tfm = tfm\n\n    def __len__(self):\n        \"\"\"Return number of samples in the dataset.\"\"\"\n        return len(self.df)\n\n    def __getitem__(self, i):\n        \"\"\"\n        Load one image and its label.\n\n        Steps:\n        1. Read one row from the DataFrame\n        2. Resolve the full path to the image\n        3. Open the image with PIL\n        4. Convert to RGB\n        5. Resize to the chosen image size\n        6. Apply transforms\n        7. Return image tensor and label tensor\n        \"\"\"\n        row = self.df.iloc[i]\n\n        # Find the actual file path on disk\n        path = resolve_path(row[\"path\"])\n        if path is None:\n            raise FileNotFoundError(f\"Could not find image for row {i}: {row['path']}\")\n\n        # PIL is used here to read image files\n        img = Image.open(path).convert(\"RGB\").resize((CFG.img_size, CFG.img_size))\n\n        # Convert label to a PyTorch tensor of type long\n        # CrossEntropyLoss expects class indices as long integers\n        label = torch.tensor(row[\"y\"]).long()\n\n        return self.tfm(img), label\n\n\n# ============================================================\n# TEST DATASET\n# ------------------------------------------------------------\n# Very similar to TrainDataset, but the test set does not\n# contain labels. Instead, we return image tensor + sample ID.\n# ============================================================\nclass TestDataset(Dataset):\n    def __init__(self, df, tfm):\n        \"\"\"\n        Parameters\n        ----------\n        df : pandas.DataFrame\n            DataFrame with at least columns: 'path' and 'ID'\n        tfm : torchvision.transforms\n            Transform pipeline to apply to each image\n        \"\"\"\n        self.df = df.reset_index(drop=True)\n        self.tfm = tfm\n\n    def __len__(self):\n        \"\"\"Return number of test samples.\"\"\"\n        return len(self.df)\n\n    def __getitem__(self, i):\n        \"\"\"\n        Load one test image and its ID.\n\n        Returns\n        -------\n        image_tensor, sample_id\n        \"\"\"\n        row = self.df.iloc[i]\n\n        path = resolve_path(row[\"path\"])\n        if path is None:\n            raise FileNotFoundError(f\"Could not find image for row {i}: {row['path']}\")\n\n        img = Image.open(path).convert(\"RGB\").resize((CFG.img_size, CFG.img_size))\n\n        return self.tfm(img), row[\"ID\"]\n\n\n# ============================================================\n# IMAGE TRANSFORMS\n# ------------------------------------------------------------\n# Transforms are applied to every image before it is fed into\n# the neural network.\n#\n# Here we:\n# - convert PIL image -> PyTorch tensor\n# - normalize pixel values\n#\n# Note:\n# EfficientNet pretrained weights are often used with ImageNet\n# normalization values:\n# mean = [0.485, 0.456, 0.406]\n# std  = [0.229, 0.224, 0.225]\n#\n# Your original code uses [0.5, 0.5, 0.5], which is still valid,\n# but not exactly matched to ImageNet pretraining.\n# ============================================================\ndef get_tfm():\n    \"\"\"\n    Return the preprocessing pipeline applied to each image.\n    \"\"\"\n    return transforms.Compose([\n        transforms.ToTensor(),\n        transforms.Normalize([0.5] * 3, [0.5] * 3)\n    ])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-11T15:12:40.238095Z","iopub.execute_input":"2026-05-11T15:12:40.238445Z","iopub.status.idle":"2026-05-11T15:12:40.254403Z","shell.execute_reply.started":"2026-05-11T15:12:40.238420Z","shell.execute_reply":"2026-05-11T15:12:40.253763Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# MODEL DEFINITION\n# ------------------------------------------------------------\n# We use EfficientNet-B0 pretrained on ImageNet.\n#\n# Transfer learning idea:\n# - keep the powerful feature extractor learned from ImageNet\n# - replace the final classifier so it predicts our own classes\n# ============================================================\nclass Model(nn.Module):\n    def __init__(self):\n        super().__init__()\n\n        # Load EfficientNet-B0 with pretrained weights\n        # self.net = models.efficientnet_b0(\n        #     weights=models.EfficientNet_B0_Weights.DEFAULT\n        # )\n        #self.net.classifier = nn.Linear(1280, CFG.num_classes)\n\n        # self.net = models.efficientnet_b3(\n        #     weights=models.EfficientNet_B3_Weights.DEFAULT\n        # )\n        # self.net.classifier = nn.Linear(1536, CFG.num_classes)\n\n        self.net = models.efficientnet_b7(\n            weights=models.EfficientNet_B7_Weights.DEFAULT\n        )\n        self.net.classifier = nn.Linear(2560, CFG.num_classes)\n    \n        # Replace the final classification layer.\n        # EfficientNet-B0 outputs 1280 features before the classifier.\n        # We want the final output size to match CFG.num_classes.\n\n    def forward(self, x):\n        \"\"\"\n        Forward pass:\n        input batch of images -> output logits\n        \"\"\"\n        return self.net(x)\n\nmodel = Model()\n\ndef print_model_architecture():\n    model = Model().to(CFG.device)\n    print(model)\n\nprint_model_architecture()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-11T15:16:03.718178Z","iopub.execute_input":"2026-05-11T15:16:03.719129Z","iopub.status.idle":"2026-05-11T15:16:06.073961Z","shell.execute_reply.started":"2026-05-11T15:16:03.719096Z","shell.execute_reply":"2026-05-11T15:16:06.072994Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport matplotlib.pyplot as plt\nimport math\nimport numpy as np\n\n# ============================================================\n# 10. VISUALIZE FIRST EFFICIENTNET CONVOLUTIONAL LAYER\n# ============================================================\n\ndef get_first_conv_layer(model):\n    \"\"\"\n    EfficientNet first convolution is:\n    model.net.features[0][0]\n    \"\"\"\n    return model.net.features[0][0]\n\n\ndef normalize_for_display(x):\n    \"\"\"\n    Normalize tensor/image to [0, 1] for matplotlib.\n    This is ONLY for visualization.\n    It does not change the real filter values.\n    \"\"\"\n    x = x.detach().cpu()\n    x_min = x.min()\n    x_max = x.max()\n\n    if x_max - x_min < 1e-8:\n        return torch.zeros_like(x)\n\n    return (x - x_min) / (x_max - x_min)\n\n\ndef show_all_first_layer_filters_with_numbers(model):\n    \"\"\"\n    Shows all 64 learned RGB filters from the first convolutional layer.\n\n    EfficientNet-B7 first conv:\n    weight shape = [64, 3, 3, 3]\n\n    Meaning:\n    64 filters\n    3 input channels: R, G, B\n    3x3 numbers per channel\n    \"\"\"\n    conv = get_first_conv_layer(model)\n    weights = conv.weight.data.detach().cpu()\n\n    print(\"First conv layer:\")\n    print(conv)\n    print()\n    print(\"Weight tensor shape:\", weights.shape)\n    print()\n\n    n_filters = weights.shape[0]\n    n_cols = 8\n    n_rows = math.ceil(n_filters / n_cols)\n\n    fig, axes = plt.subplots(n_rows, n_cols, figsize=(18, 18))\n    axes = axes.flatten()\n\n    for i in range(n_filters):\n        filt = weights[i]                 # [3, 3, 3]\n        filt_img = filt.permute(1, 2, 0)  # [H, W, C]\n        filt_img = normalize_for_display(filt_img)\n\n        axes[i].imshow(filt_img)\n        axes[i].set_title(f\"Filter {i}\")\n        axes[i].axis(\"off\")\n\n    plt.suptitle(\"All 64 learned RGB filters in the first convolutional layer\", fontsize=18)\n    plt.tight_layout()\n    plt.show()\n\n    # Print exact numerical arrays\n    for i in range(n_filters):\n        filt = weights[i].numpy()\n\n        print(\"=\" * 70)\n        print(f\"FILTER {i}\")\n        print(\"Shape:\", filt.shape)\n        print()\n        print(\"Channel 0 / Red:\")\n        print(np.array2string(filt[0], precision=4, suppress_small=False))\n        print()\n        print(\"Channel 1 / Green:\")\n        print(np.array2string(filt[1], precision=4, suppress_small=False))\n        print()\n        print(\"Channel 2 / Blue:\")\n        print(np.array2string(filt[2], precision=4, suppress_small=False))\n        print()\n\n\nshow_all_first_layer_filters_with_numbers(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-11T15:21:00.465535Z","iopub.execute_input":"2026-05-11T15:21:00.466504Z","iopub.status.idle":"2026-05-11T15:21:03.083243Z","shell.execute_reply.started":"2026-05-11T15:21:00.466457Z","shell.execute_reply":"2026-05-11T15:21:03.082676Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 11. IMAGE -> LEARNED FILTER -> ACTIVATION MAP + NUMBERS\n# ============================================================\n\ndef unnormalize_imagenet(x):\n    \"\"\"\n    Use this only if your image was normalized with ImageNet mean/std.\n    \"\"\"\n    mean = torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1)\n    std = torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1)\n    return x.cpu() * std + mean\n\n\ndef show_image_filter_activation_with_numbers(model, image_tensor, filter_ids=None):\n    \"\"\"\n    Shows selected filters and their activation maps on one image.\n    Also prints exact numerical arrays for those filters.\n    \"\"\"\n    if filter_ids is None:\n        filter_ids = list(range(8))\n\n    model.eval()\n\n    if image_tensor.ndim == 3:\n        image_batch = image_tensor.unsqueeze(0)\n    else:\n        image_batch = image_tensor\n\n    image_batch = image_batch.to(next(model.parameters()).device)\n\n    conv = get_first_conv_layer(model)\n    first_block = model.net.features[0]\n\n    with torch.no_grad():\n        activations = first_block(image_batch)[0].detach().cpu()\n\n    weights = conv.weight.data.detach().cpu()\n\n    img_display = image_tensor.detach().cpu()\n\n    if img_display.ndim == 4:\n        img_display = img_display[0]\n\n    # If your image_tensor was normalized with ImageNet mean/std, uncomment this:\n    # img_display = unnormalize_imagenet(img_display)\n\n    img_display = img_display.permute(1, 2, 0)\n    img_display = normalize_for_display(img_display)\n\n    n_rows = len(filter_ids)\n    fig, axes = plt.subplots(n_rows, 3, figsize=(11, 3 * n_rows))\n\n    if n_rows == 1:\n        axes = axes.reshape(1, 3)\n\n    for row, filter_id in enumerate(filter_ids):\n        filt = weights[filter_id]                 # [3, 3, 3]\n        filt_img = filt.permute(1, 2, 0)          # [H, W, C]\n        filt_img = normalize_for_display(filt_img)\n\n        act = normalize_for_display(activations[filter_id])\n\n        axes[row, 0].imshow(img_display)\n        axes[row, 0].set_title(\"Original image\")\n        axes[row, 0].axis(\"off\")\n\n        axes[row, 1].imshow(filt_img)\n        axes[row, 1].set_title(f\"Filter {filter_id}\")\n        axes[row, 1].axis(\"off\")\n\n        axes[row, 2].imshow(act, cmap=\"gray\")\n        axes[row, 2].set_title(f\"Activation map {filter_id}\")\n        axes[row, 2].axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()\n\n    for filter_id in filter_ids:\n        filt = weights[filter_id].numpy()\n\n        print(\"=\" * 70)\n        print(f\"FILTER {filter_id}\")\n        print(\"Shape:\", filt.shape)\n        print()\n        print(\"Channel 0 / Red:\")\n        print(np.array2string(filt[0], precision=4, suppress_small=False))\n        print()\n        print(\"Channel 1 / Green:\")\n        print(np.array2string(filt[1], precision=4, suppress_small=False))\n        print()\n        print(\"Channel 2 / Blue:\")\n        print(np.array2string(filt[2], precision=4, suppress_small=False))\n        print()\n\n\n# Example:\nshow_image_filter_activation_with_numbers(\n    model,\n    img_tensor,\n    filter_ids=list(range(8))\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-11T15:30:33.545400Z","iopub.execute_input":"2026-05-11T15:30:33.545871Z","iopub.status.idle":"2026-05-11T15:30:36.481628Z","shell.execute_reply.started":"2026-05-11T15:30:33.545837Z","shell.execute_reply":"2026-05-11T15:30:36.480748Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#RElu vs SILU\nx = torch.linspace(-10, 10, 1000)\n\nrelu = torch.relu(x)\nsilu = torch.nn.functional.silu(x)\n\nplt.figure(figsize=(8,5))\nplt.plot(x.numpy(), relu.numpy(), label=\"ReLU\")\nplt.plot(x.numpy(), silu.numpy(), label=\"SiLU\")\nplt.axhline(0, color=\"black\", linewidth=0.5)\nplt.axvline(0, color=\"black\", linewidth=0.5)\n\nplt.legend()\nplt.title(\"ReLU vs SiLU\")\nplt.xlabel(\"x\")\nplt.ylabel(\"activation\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-04T15:16:25.266014Z","iopub.execute_input":"2026-05-04T15:16:25.266646Z","iopub.status.idle":"2026-05-04T15:16:25.410999Z","shell.execute_reply.started":"2026-05-04T15:16:25.266613Z","shell.execute_reply":"2026-05-04T15:16:25.410386Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# TRAINING FUNCTION\n# ------------------------------------------------------------\n# This function runs one full training epoch.\n#\n# For every batch:\n# 1. Move data to device\n# 2. Zero gradients\n# 3. Forward pass\n# 4. Compute loss\n# 5. Backpropagate\n# 6. Update weights\n# 7. Track running loss and accuracy\n# ============================================================\ndef train(model, loader, opt):\n    \"\"\"\n    Train the model for one epoch.\n\n    Parameters\n    ----------\n    model : nn.Module\n        Neural network model\n    loader : DataLoader\n        Training data loader\n    opt : torch.optim.Optimizer\n        Optimizer\n\n    Returns\n    -------\n    avg_loss : float\n    avg_acc : float\n    \"\"\"\n    model.train()  # important: enables dropout/batchnorm training behavior\n    loss_fn = nn.CrossEntropyLoss()\n\n    total_loss = 0.0\n    correct = 0\n    total = 0\n\n    pbar = tqdm(loader, desc=\"Train\")\n\n    for x, y in pbar:\n        # Move batch to GPU/CPU\n        x = x.to(CFG.device)\n        y = y.to(CFG.device)\n\n        # Clear gradients from previous step\n        opt.zero_grad()\n\n        # Forward pass: get raw class scores (logits)\n        logits = model(x)\n\n        # Compute classification loss\n        loss = loss_fn(logits, y)\n\n        # Backward pass: compute gradients\n        loss.backward()\n\n        # Update model weights\n        opt.step()\n\n        # Accumulate total loss (weighted by batch size)\n        total_loss += loss.item() * x.size(0)\n\n        # Predicted class = index of largest logit\n        preds = logits.argmax(dim=1)\n\n        # Count correct predictions\n        correct += (preds == y).sum().item()\n        total += y.size(0)\n\n        # Show running statistics in progress bar\n        pbar.set_postfix({\n            \"loss\": total_loss / total,\n            \"acc\": correct / total\n        })\n\n    avg_loss = total_loss / total\n    avg_acc = correct / total\n    return avg_loss, avg_acc\n\n\n# ============================================================\n# VALIDATION FUNCTION\n# ------------------------------------------------------------\n# This is similar to training, but:\n# - model is in eval mode\n# - gradients are disabled\n# - no optimizer step is performed\n#\n# This gives us an unbiased estimate of performance on data\n# not used for parameter updates.\n# ============================================================\ndef valid(model, loader):\n    \"\"\"\n    Evaluate the model on validation data.\n\n    Parameters\n    ----------\n    model : nn.Module\n        Neural network model\n    loader : DataLoader\n        Validation data loader\n\n    Returns\n    -------\n    avg_loss : float\n    avg_acc : float\n    \"\"\"\n    model.eval()  # important: disables training-specific behavior\n    loss_fn = nn.CrossEntropyLoss()\n\n    total_loss = 0.0\n    correct = 0\n    total = 0\n\n    pbar = tqdm(loader, desc=\"Valid\")\n\n    # No gradient tracking needed during validation\n    with torch.no_grad():\n        for x, y in pbar:\n            x = x.to(CFG.device)\n            y = y.to(CFG.device)\n\n            logits = model(x)\n            loss = loss_fn(logits, y)\n\n            total_loss += loss.item() * x.size(0)\n\n            preds = logits.argmax(dim=1)\n            correct += (preds == y).sum().item()\n            total += y.size(0)\n\n            pbar.set_postfix({\n                \"val_loss\": total_loss / total,\n                \"val_acc\": correct / total\n            })\n\n    avg_loss = total_loss / total\n    avg_acc = correct / total\n    return avg_loss, avg_acc\n\n\n# ============================================================\n# PREDICTION FUNCTION\n# ------------------------------------------------------------\n# This is used for the unlabeled test set.\n#\n# We only need:\n# - test sample IDs\n# - predicted class for each sample\n# ============================================================\ndef predict(model, loader):\n    \"\"\"\n    Generate predictions for the test set.\n\n    Parameters\n    ----------\n    model : nn.Module\n        Trained neural network\n    loader : DataLoader\n        Test data loader\n\n    Returns\n    -------\n    ids : list\n        Test sample IDs\n    preds : list\n        Predicted class indices\n    \"\"\"\n    model.eval()\n\n    ids = []\n    preds = []\n\n    with torch.no_grad():\n        for x, sample_ids in loader:\n            x = x.to(CFG.device)\n\n            # Get predicted class indices\n            batch_preds = model(x).argmax(dim=1).cpu().numpy()\n\n            # Store IDs and predictions\n            # sample_ids may already be a tensor or another iterable type\n            if torch.is_tensor(sample_ids):\n                ids.extend(sample_ids.cpu().numpy().tolist())\n            else:\n                ids.extend(list(sample_ids))\n\n            preds.extend(batch_preds.tolist())\n\n    return ids, preds\n\n\n# ============================================================\n# MAIN FUNCTION\n# ------------------------------------------------------------\n# This is the full pipeline controller.\n# It:\n# - reads data\n# - creates train/validation split\n# - builds datasets and dataloaders\n# - initializes model and optimizer\n# - trains for multiple epochs\n# - saves best model\n# - predicts on test set\n# - writes submission file\n# ============================================================\ndef main():\n    print(f\"Using device: {CFG.device}\")\n\n    # --------------------------------------------------------\n    # 1. LOAD CSV FILES\n    # --------------------------------------------------------\n    # training.csv should contain training image paths + labels\n    # test.csv should contain test image paths + IDs\n    # --------------------------------------------------------\n    train_df = pd.read_csv(CFG.train_csv)\n    test_df = pd.read_csv(CFG.test_csv)\n\n    # --------------------------------------------------------\n    # 2. SPLIT TRAINING DATA INTO TRAIN + VALIDATION\n    # --------------------------------------------------------\n    # stratify=train_df[\"y\"] means class proportions are preserved\n    # in both splits, which is especially important for\n    # classification tasks.\n    # --------------------------------------------------------\n    train_df, val_df = train_test_split(\n        train_df,\n        test_size=0.2,\n        stratify=train_df[\"y\"],\n        random_state=42\n    )\n\n    # --------------------------------------------------------\n    # 3. CREATE TRANSFORM PIPELINE\n    # --------------------------------------------------------\n    tfm = get_tfm()\n\n    # --------------------------------------------------------\n    # 4. CREATE DATALOADERS\n    # --------------------------------------------------------\n    # DataLoader handles batching, shuffling, and parallel loading.\n    #\n    # shuffle=True for training:\n    #   randomizes sample order each epoch\n    #\n    # shuffle=False for validation/test:\n    #   keeps order fixed\n    #\n    # num_workers=2:\n    #   uses 2 subprocesses to load data faster\n    #\n    # pin_memory=True:\n    #   can speed up transfer to GPU\n    # --------------------------------------------------------\n    train_loader = DataLoader(\n        TrainDataset(train_df, tfm),\n        batch_size=CFG.batch_size,\n        shuffle=True,\n        num_workers=2,\n        pin_memory=True\n    )\n\n    val_loader = DataLoader(\n        TrainDataset(val_df, tfm),\n        batch_size=CFG.batch_size,\n        shuffle=False,\n        num_workers=2,\n        pin_memory=True\n    )\n\n    test_loader = DataLoader(\n        TestDataset(test_df, tfm),\n        batch_size=CFG.batch_size,\n        shuffle=False,\n        num_workers=2,\n        pin_memory=True\n    )\n\n    # --------------------------------------------------------\n    # 5. INITIALIZE MODEL AND OPTIMIZER\n    # --------------------------------------------------------\n    # AdamW is a popular optimizer for deep learning.\n    # --------------------------------------------------------\n    model = Model().to(CFG.device)\n    opt = optim.AdamW(model.parameters(), lr=CFG.lr)\n\n    # Track best validation accuracy so we can save the best model\n    best_acc = 0.0\n\n    # --------------------------------------------------------\n    # 6. TRAINING LOOP\n    # --------------------------------------------------------\n    # For each epoch:\n    # - train on the training set\n    # - evaluate on validation set\n    # - save model if validation accuracy improves\n    # --------------------------------------------------------\n    for epoch in range(CFG.epochs):\n        print(f\"\\nEpoch {epoch + 1}/{CFG.epochs}\")\n\n        train_loss, train_acc = train(model, train_loader, opt)\n        print(f\"Train: loss={train_loss:.4f}, acc={train_acc:.4f}\")\n\n        val_loss, val_acc = valid(model, val_loader)\n        print(f\"Valid: loss={val_loss:.4f}, acc={val_acc:.4f}\")\n\n        # Save only the best-performing model on validation data\n        if val_acc > best_acc:\n            best_acc = val_acc\n            torch.save(model.state_dict(), \"best_model.pth\")\n            print(\"Saved new best model.\")\n\n    # --------------------------------------------------------\n    # 7. LOAD BEST MODEL\n    # --------------------------------------------------------\n    # After training, reload the checkpoint with the best\n    # validation accuracy instead of using the last epoch.\n    # --------------------------------------------------------\n    model.load_state_dict(\n        torch.load(\"best_model.pth\", map_location=CFG.device)\n    )\n\n    # --------------------------------------------------------\n    # 8. PREDICT ON TEST SET\n    # --------------------------------------------------------\n    ids, preds = predict(model, test_loader)\n\n    # --------------------------------------------------------\n    # 9. SAVE SUBMISSION FILE\n    # --------------------------------------------------------\n    # The competition expects a CSV with:\n    # ID, TARGET\n    # --------------------------------------------------------\n    submission = pd.DataFrame({\n        \"ID\": ids,\n        \"TARGET\": preds\n    })\n    submission.to_csv(\"submission.csv\", index=False)\n\n    print(\"\\nFinished.\")\n    print(\"Best validation accuracy:\", best_acc)\n    print(\"Submission saved to submission.csv\")\n\n\n# ============================================================\n# PYTHON ENTRY POINT\n# ------------------------------------------------------------\n# This makes sure main() only runs when this file is executed\n# directly, not when imported as a module.\n# ============================================================\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Source","metadata":{}},{"cell_type":"code","source":"# import os\n# import numpy as np\n# import pandas as pd\n# import torch\n# import torch.nn as nn\n# import torch.optim as optim\n# from torch.utils.data import Dataset, DataLoader\n# from PIL import Image\n# from torchvision import transforms, models\n# from tqdm import tqdm\n# from sklearn.model_selection import train_test_split\n\n# class CFG:\n#     root = \"/kaggle/input/competitions/dlmmdd-workshop-synthetic-source-attribution-challenge/Data/Data\"\n#     train_csv = os.path.join(root, \"training.csv\")\n#     test_csv = os.path.join(root, \"test.csv\")\n#     train_dir = os.path.join(root, \"Training\")\n#     test_dir = os.path.join(root, \"Test\")\n#     device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n#     batch_size = 64\n#     epochs = 5\n#     lr = 3e-4\n#     num_classes = 10\n#     img_size = 224\n\n# def resolve_path(p):\n#     for c in [\n#         os.path.join(CFG.root, p),\n#         os.path.join(CFG.train_dir, os.path.basename(p)),\n#         os.path.join(CFG.test_dir, os.path.basename(p)),\n#         p\n#     ]:\n#         if os.path.exists(c):\n#             return c\n#     return None\n\n# class TrainDataset(Dataset):\n#     def __init__(self, df, tfm):\n#         self.df = df\n#         self.tfm = tfm\n\n#     def __len__(self):\n#         return len(self.df)\n\n#     def __getitem__(self, i):\n#         r = self.df.iloc[i]\n#         path = resolve_path(r[\"path\"])\n#         img = Image.open(path).convert(\"RGB\").resize((CFG.img_size, CFG.img_size))\n#         return self.tfm(img), torch.tensor(r[\"y\"]).long()\n\n# class TestDataset(Dataset):\n#     def __init__(self, df, tfm):\n#         self.df = df\n#         self.tfm = tfm\n\n#     def __len__(self):\n#         return len(self.df)\n\n#     def __getitem__(self, i):\n#         r = self.df.iloc[i]\n#         path = resolve_path(r[\"path\"])\n#         img = Image.open(path).convert(\"RGB\").resize((CFG.img_size, CFG.img_size))\n#         return self.tfm(img), r[\"ID\"]\n\n# def get_tfm():\n#     return transforms.Compose([\n#         transforms.ToTensor(),\n#         transforms.Normalize([0.5]*3, [0.5]*3)\n#     ])\n\n# class Model(nn.Module):\n#     def __init__(self):\n#         super().__init__()\n#         self.net = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.DEFAULT)\n#         self.net.classifier = nn.Linear(1280, CFG.num_classes)\n\n#     def forward(self, x):\n#         return self.net(x)\n\n# def train(model, loader, opt):\n#     model.train()\n#     loss_fn = nn.CrossEntropyLoss()\n    \n#     total_loss = 0\n#     correct = 0\n#     total = 0\n\n#     pbar = tqdm(loader, desc=\"Train\")\n\n#     for x, y in pbar:\n#         x, y = x.to(CFG.device), y.to(CFG.device)\n\n#         opt.zero_grad()\n#         logits = model(x)\n#         loss = loss_fn(logits, y)\n#         loss.backward()\n#         opt.step()\n\n#         total_loss += loss.item() * x.size(0)\n\n#         preds = logits.argmax(1)\n#         correct += (preds == y).sum().item()\n#         total += y.size(0)\n\n#         pbar.set_postfix({\n#             \"loss\": total_loss / total,\n#             \"acc\": correct / total\n#         })\n\n#     return total_loss / total, correct / total\n\n# def valid(model, loader):\n#     model.eval()\n#     loss_fn = nn.CrossEntropyLoss()\n\n#     total_loss = 0\n#     correct = 0\n#     total = 0\n\n#     pbar = tqdm(loader, desc=\"Valid\")\n\n#     with torch.no_grad():\n#         for x, y in pbar:\n#             x, y = x.to(CFG.device), y.to(CFG.device)\n\n#             logits = model(x)\n#             loss = loss_fn(logits, y)\n\n#             total_loss += loss.item() * x.size(0)\n\n#             preds = logits.argmax(1)\n#             correct += (preds == y).sum().item()\n#             total += y.size(0)\n\n#             pbar.set_postfix({\n#                 \"val_loss\": total_loss / total,\n#                 \"val_acc\": correct / total\n#             })\n\n#     return total_loss / total, correct / total\n\n# def predict(model, loader):\n#     model.eval()\n#     ids, preds = [], []\n#     with torch.no_grad():\n#         for x, i in loader:\n#             x = x.to(CFG.device)\n#             p = model(x).argmax(1).cpu().numpy()\n#             ids.extend(i.numpy())\n#             preds.extend(p)\n#     return ids, preds\n\n# def main():\n\n#     print(f\"Using device: {CFG.device}\")\n    \n#     train_df = pd.read_csv(CFG.train_csv)\n#     train_df, val_df = train_test_split(\n#         train_df,\n#         test_size=0.2,\n#         stratify=train_df[\"y\"],\n#         random_state=42\n#     )\n#     test_df = pd.read_csv(CFG.test_csv)\n\n#     tfm = get_tfm()\n\n#     train_loader = DataLoader(\n#         TrainDataset(train_df, tfm),\n#         batch_size=CFG.batch_size,\n#         shuffle=True,\n#         num_workers=2,\n#         pin_memory=True\n#     )\n    \n#     val_loader = DataLoader(\n#         TrainDataset(val_df, tfm),\n#         batch_size=CFG.batch_size,\n#         shuffle=False,\n#         num_workers=2,\n#         pin_memory=True\n#     )\n\n#     test_loader = DataLoader(\n#         TestDataset(test_df, tfm),\n#         batch_size=CFG.batch_size,\n#         shuffle=False,\n#         num_workers=2,\n#         pin_memory=True\n#     )\n\n#     model = Model().to(CFG.device)\n#     opt = optim.AdamW(model.parameters(), lr=CFG.lr)\n#     best_acc = 0.0\n\n#     for epoch in range(CFG.epochs):\n#         print(f\"\\nEpoch {epoch+1}/{CFG.epochs}\")\n#         train_loss, train_acc = train(model, train_loader, opt)\n#         print(f\"Train: loss={train_loss:.4f}, acc={train_acc:.4f}\")\n#         val_loss, val_acc = valid(model, val_loader)\n#         print(f\"Valid: loss={val_loss:.4f}, acc={val_acc:.4f}\")\n#         if val_acc > best_acc:\n#             best_acc = val_acc\n#             torch.save(model.state_dict(), \"best_model.pth\")\n\n#     model.load_state_dict(torch.load(\"best_model.pth\"))\n#     ids, preds = predict(model, test_loader)\n\n#     pd.DataFrame({\"ID\": ids, \"TARGET\": preds}).to_csv(\"submission.csv\", index=False)\n\n# if __name__ == \"__main__\":\n#     main()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-04-11T12:05:32.90652Z","iopub.execute_input":"2026-04-11T12:05:32.907308Z","iopub.status.idle":"2026-04-11T12:19:10.717932Z","shell.execute_reply.started":"2026-04-11T12:05:32.907268Z","shell.execute_reply":"2026-04-11T12:19:10.716847Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null}]}