{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Standard Python libraries\nimport os\nimport glob\nimport time\nimport random\nimport subprocess\nfrom pathlib import Path\n\n# Data-processing libraries\nimport numpy as np\nimport pandas as pd\n\n# Image and plotting libraries\nimport matplotlib.pyplot as plt\nfrom PIL import Image\n\n# PyTorch libraries\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\n\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\n# Used to create a balanced training and validation split\nfrom sklearn.model_selection import train_test_split\n\n# Fixed seed makes the experiment more reproducible\nSEED = 42\n\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\n\nif torch.cuda.is_available():\n    torch.cuda.manual_seed_all(SEED)\n\n# Improves training speed when image sizes stay unchanged\ntorch.backends.cudnn.benchmark = True\n\n# Select GPU when available\ndevice = torch.device(\n    \"cuda\" if torch.cuda.is_available() else \"cpu\"\n)\n\nprint(\"PyTorch version:\", torch.__version__)\nprint(\"Selected device:\", device)\nprint(\"CUDA available:\", torch.cuda.is_available())\nprint(\"Number of GPUs:\", torch.cuda.device_count())\n\nif torch.cuda.is_available():\n    for gpu_index in range(torch.cuda.device_count()):\n        print(\n            f\"GPU {gpu_index}:\",\n            torch.cuda.get_device_name(gpu_index)\n        )\n\n    # Perform a small calculation to verify GPU compatibility\n    test_tensor = torch.randn(\n        100,\n        100,\n        device=device\n    )\n\n    test_result = test_tensor @ test_tensor\n\n    print(\"GPU calculation: Successful\")\nelse:\n    print(\"Warning: No GPU was detected\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:44:41.368215Z","iopub.execute_input":"2026-09-21T10:44:41.368666Z","iopub.status.idle":"2026-09-21T10:44:41.379832Z","shell.execute_reply.started":"2026-09-21T10:44:41.368623Z","shell.execute_reply":"2026-09-21T10:44:41.37912Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Competition files\ndef find_input_file(filename):\n    \"\"\"\n    Search all Kaggle input folders for a specific filename.\n    \"\"\"\n\n    matches = glob.glob(\n        f\"/kaggle/input/**/{filename}\",\n        recursive=True\n    )\n\n    if not matches:\n        raise FileNotFoundError(\n            f\"Could not find {filename}. \"\n            \"Make sure the CIFAR-10 competition data is attached.\"\n        )\n\n    return matches[0]\n\n\nTRAIN_ARCHIVE = find_input_file(\"train.7z\")\nTEST_ARCHIVE = find_input_file(\"test.7z\")\nTRAIN_LABELS_PATH = find_input_file(\"trainLabels.csv\")\nSAMPLE_SUBMISSION_PATH = find_input_file(\n    \"sampleSubmission.csv\"\n)\n\nprint(\"Competition files found successfully.\")\nprint()\nprint(\"Training archive:\", TRAIN_ARCHIVE)\nprint(\"Test archive:\", TEST_ARCHIVE)\nprint(\"Training labels:\", TRAIN_LABELS_PATH)\nprint(\"Sample submission:\", SAMPLE_SUBMISSION_PATH)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:44:41.381155Z","iopub.execute_input":"2026-09-21T10:44:41.381503Z","iopub.status.idle":"2026-09-21T10:44:41.408256Z","shell.execute_reply.started":"2026-09-21T10:44:41.381468Z","shell.execute_reply":"2026-09-21T10:44:41.407652Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Extracting the images \n# Folder where the extracted images will be stored\nEXTRACTION_ROOT = Path(\n    \"/kaggle/working/cifar10\"\n)\n\nTRAIN_DIRECTORY = EXTRACTION_ROOT / \"train\"\nTEST_DIRECTORY = EXTRACTION_ROOT / \"test\"\n\nEXTRACTION_ROOT.mkdir(\n    parents=True,\n    exist_ok=True\n)\n\n# Extract the training images\nif not TRAIN_DIRECTORY.exists():\n    print(\"Extracting 50,000 training images...\")\n\n    subprocess.run(\n        [\n            \"7z\",\n            \"x\",\n            TRAIN_ARCHIVE,\n            f\"-o{EXTRACTION_ROOT}\",\n            \"-y\"\n        ],\n        check=True,\n        stdout=subprocess.DEVNULL\n    )\n\n    print(\"Training extraction completed.\")\nelse:\n    print(\"Training images are already extracted.\")\n\n# Extract the test images\nif not TEST_DIRECTORY.exists():\n    print(\"Extracting 300,000 test images...\")\n\n    subprocess.run(\n        [\n            \"7z\",\n            \"x\",\n            TEST_ARCHIVE,\n            f\"-o{EXTRACTION_ROOT}\",\n            \"-y\"\n        ],\n        check=True,\n        stdout=subprocess.DEVNULL\n    )\n\n    print(\"Test extraction completed.\")\nelse:\n    print(\"Test images are already extracted.\")\n\n# Count the extracted images\ntraining_image_paths = glob.glob(\n    str(TRAIN_DIRECTORY / \"*.png\")\n)\n\ntest_image_paths = glob.glob(\n    str(TEST_DIRECTORY / \"*.png\")\n)\n\nprint()\nprint(\"Training images found:\", len(training_image_paths))\nprint(\"Test images found:\", len(test_image_paths))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:44:41.409004Z","iopub.execute_input":"2026-09-21T10:44:41.409358Z","iopub.status.idle":"2026-09-21T10:44:41.804623Z","shell.execute_reply.started":"2026-09-21T10:44:41.409335Z","shell.execute_reply":"2026-09-21T10:44:41.803647Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# loading the labels\nlabels_dataframe = pd.read_csv(\n    TRAIN_LABELS_PATH\n)\n\nsample_submission = pd.read_csv(\n    SAMPLE_SUBMISSION_PATH\n)\n\nprint(\"Training-label table:\", labels_dataframe.shape)\nprint(\"Sample-submission table:\", sample_submission.shape)\n\nprint(\"\\nFirst five training labels:\")\ndisplay(labels_dataframe.head())\n\nprint(\"\\nFirst five submission rows:\")\ndisplay(sample_submission.head())\n\nprint(\"\\nNumber of images in each class:\")\nclass_counts = (\n    labels_dataframe[\"label\"]\n    .value_counts()\n    .sort_index()\n)\n\ndisplay(class_counts)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:44:41.806235Z","iopub.execute_input":"2026-09-21T10:44:41.806541Z","iopub.status.idle":"2026-09-21T10:44:42.04837Z","shell.execute_reply.started":"2026-09-21T10:44:41.806518Z","shell.execute_reply":"2026-09-21T10:44:42.047704Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Show some examples\nsample_rows = labels_dataframe.sample(\n    10,\n    random_state=SEED\n)\n\nfig, axes = plt.subplots(\n    2,\n    5,\n    figsize=(13, 6)\n)\n\nfor axis, (_, row) in zip(\n    axes.flat,\n    sample_rows.iterrows()\n):\n    image_path = (\n        TRAIN_DIRECTORY /\n        f\"{row['id']}.png\"\n    )\n\n    image = Image.open(\n        image_path\n    ).convert(\"RGB\")\n\n    axis.imshow(image)\n    axis.set_title(\n        f\"Class: {row['label']}\"\n    )\n    axis.axis(\"off\")\n\nplt.suptitle(\n    \"Examples from the CIFAR-10 Training Set\",\n    fontsize=15\n)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:44:42.049239Z","iopub.execute_input":"2026-09-21T10:44:42.049464Z","iopub.status.idle":"2026-09-21T10:44:42.597419Z","shell.execute_reply.started":"2026-09-21T10:44:42.049443Z","shell.execute_reply":"2026-09-21T10:44:42.596706Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# class mapping\n# Standard CIFAR-10 class order\nclass_names = [\n    \"airplane\",\n    \"automobile\",\n    \"bird\",\n    \"cat\",\n    \"deer\",\n    \"dog\",\n    \"frog\",\n    \"horse\",\n    \"ship\",\n    \"truck\"\n]\n\n# Convert a class name into a number\nclass_to_index = {\n    class_name: index\n    for index, class_name in enumerate(class_names)\n}\n\n# Convert a number back into a class name\nindex_to_class = {\n    index: class_name\n    for class_name, index in class_to_index.items()\n}\n\n# Add numerical labels to the table\nlabels_dataframe[\"target\"] = (\n    labels_dataframe[\"label\"]\n    .map(class_to_index)\n)\n\nprint(\"Class mapping:\")\n\nfor class_name, class_index in class_to_index.items():\n    print(f\"{class_index}: {class_name}\")\n\nprint(\n    \"\\nMissing numerical labels:\",\n    labels_dataframe[\"target\"].isna().sum()\n)\n\ndisplay(labels_dataframe.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:54:15.217411Z","iopub.execute_input":"2026-09-21T10:54:15.217901Z","iopub.status.idle":"2026-09-21T10:54:15.235349Z","shell.execute_reply.started":"2026-09-21T10:54:15.217854Z","shell.execute_reply":"2026-09-21T10:54:15.234443Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Creating the split\ntrain_dataframe, validation_dataframe = train_test_split(\n    labels_dataframe,\n    test_size=0.10,\n    random_state=SEED,\n    stratify=labels_dataframe[\"target\"]\n)\n\n# Reset the row numbers\ntrain_dataframe = train_dataframe.reset_index(\n    drop=True\n)\n\nvalidation_dataframe = validation_dataframe.reset_index(\n    drop=True\n)\n\nprint(\"Training images:\", len(train_dataframe))\nprint(\"Validation images:\", len(validation_dataframe))\n\nprint(\"\\nTraining class distribution:\")\ndisplay(\n    train_dataframe[\"label\"]\n    .value_counts()\n    .sort_index()\n)\n\nprint(\"\\nValidation class distribution:\")\ndisplay(\n    validation_dataframe[\"label\"]\n    .value_counts()\n    .sort_index()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:54:47.139212Z","iopub.execute_input":"2026-09-21T10:54:47.139838Z","iopub.status.idle":"2026-09-21T10:54:47.186523Z","shell.execute_reply.started":"2026-09-21T10:54:47.13981Z","shell.execute_reply":"2026-09-21T10:54:47.185567Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Defining the transformation\n# Standard CIFAR-10 channel statistics\nCIFAR10_MEAN = (\n    0.4914,\n    0.4822,\n    0.4465\n)\n\nCIFAR10_STANDARD_DEVIATION = (\n    0.2470,\n    0.2435,\n    0.2616\n)\n\n# Applied only to training images\ntrain_transform = transforms.Compose([\n    transforms.RandomCrop(\n        size=32,\n        padding=4\n    ),\n\n    transforms.RandomHorizontalFlip(\n        p=0.5\n    ),\n\n    transforms.RandAugment(\n        num_ops=2,\n        magnitude=9\n    ),\n\n    transforms.ToTensor(),\n\n    transforms.Normalize(\n        CIFAR10_MEAN,\n        CIFAR10_STANDARD_DEVIATION\n    ),\n\n    transforms.RandomErasing(\n        p=0.25,\n        scale=(0.02, 0.20),\n        ratio=(0.3, 3.3),\n        value=\"random\"\n    )\n])\n\n# Applied to validation and test images\nevaluation_transform = transforms.Compose([\n    transforms.ToTensor(),\n\n    transforms.Normalize(\n        CIFAR10_MEAN,\n        CIFAR10_STANDARD_DEVIATION\n    )\n])\n\nprint(\"Training transformations:\")\nprint(train_transform)\n\nprint(\"\\nEvaluation transformations:\")\nprint(evaluation_transform)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:55:17.184964Z","iopub.execute_input":"2026-09-21T10:55:17.185422Z","iopub.status.idle":"2026-09-21T10:55:17.192622Z","shell.execute_reply.started":"2026-09-21T10:55:17.185393Z","shell.execute_reply":"2026-09-21T10:55:17.191708Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Dataset class\nclass CIFAR10CompetitionDataset(Dataset):\n    def __init__(\n        self,\n        dataframe,\n        image_directory,\n        transform=None\n    ):\n        self.dataframe = dataframe.copy()\n        self.image_directory = Path(image_directory)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.dataframe)\n\n    def __getitem__(self, index):\n        row = self.dataframe.iloc[index]\n\n        image_id = int(row[\"id\"])\n        target = int(row[\"target\"])\n\n        image_path = (\n            self.image_directory /\n            f\"{image_id}.png\"\n        )\n\n        image = Image.open(\n            image_path\n        ).convert(\"RGB\")\n\n        if self.transform is not None:\n            image = self.transform(image)\n\n        return image, target","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:55:47.475275Z","iopub.execute_input":"2026-09-21T10:55:47.475885Z","iopub.status.idle":"2026-09-21T10:55:47.482228Z","shell.execute_reply.started":"2026-09-21T10:55:47.475857Z","shell.execute_reply":"2026-09-21T10:55:47.481222Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Data loaders\ntrain_dataset = CIFAR10CompetitionDataset(\n    dataframe=train_dataframe,\n    image_directory=TRAIN_DIRECTORY,\n    transform=train_transform\n)\n\nvalidation_dataset = CIFAR10CompetitionDataset(\n    dataframe=validation_dataframe,\n    image_directory=TRAIN_DIRECTORY,\n    transform=evaluation_transform\n)\n\n# Total batch size across both GPUs\nBATCH_SIZE = 256\n\n# Number of CPU processes that load images\nNUM_WORKERS = min(\n    4,\n    os.cpu_count() or 1\n)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=True,\n    num_workers=NUM_WORKERS,\n    pin_memory=True,\n    persistent_workers=NUM_WORKERS > 0,\n    drop_last=True\n)\n\nvalidation_loader = DataLoader(\n    validation_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=False,\n    num_workers=NUM_WORKERS,\n    pin_memory=True,\n    persistent_workers=NUM_WORKERS > 0\n)\n\nprint(\"Training dataset:\", len(train_dataset))\nprint(\"Validation dataset:\", len(validation_dataset))\nprint(\"Batch size:\", BATCH_SIZE)\nprint(\"DataLoader workers:\", NUM_WORKERS)\nprint(\"Training batches:\", len(train_loader))\nprint(\"Validation batches:\", len(validation_loader))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:56:04.609614Z","iopub.execute_input":"2026-09-21T10:56:04.610411Z","iopub.status.idle":"2026-09-21T10:56:04.618978Z","shell.execute_reply.started":"2026-09-21T10:56:04.610381Z","shell.execute_reply":"2026-09-21T10:56:04.618368Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Testing 1 batch\nimages, targets = next(\n    iter(train_loader)\n)\n\nprint(\"Image batch shape:\", images.shape)\nprint(\"Label batch shape:\", targets.shape)\nprint(\"Image data type:\", images.dtype)\nprint(\"Label data type:\", targets.dtype)\nprint(\"Smallest pixel value:\", images.min().item())\nprint(\"Largest pixel value:\", images.max().item())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:56:25.074754Z","iopub.execute_input":"2026-09-21T10:56:25.075648Z","iopub.status.idle":"2026-09-21T10:56:25.966288Z","shell.execute_reply.started":"2026-09-21T10:56:25.075614Z","shell.execute_reply":"2026-09-21T10:56:25.965124Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Using WideResNet\nclass WideBasicBlock(nn.Module):\n    def __init__(\n        self,\n        input_channels,\n        output_channels,\n        dropout_rate,\n        stride\n    ):\n        super().__init__()\n\n        self.batch_norm_1 = nn.BatchNorm2d(\n            input_channels\n        )\n\n        self.relu_1 = nn.ReLU(\n            inplace=True\n        )\n\n        self.conv_1 = nn.Conv2d(\n            input_channels,\n            output_channels,\n            kernel_size=3,\n            stride=stride,\n            padding=1,\n            bias=False\n        )\n\n        self.batch_norm_2 = nn.BatchNorm2d(\n            output_channels\n        )\n\n        self.relu_2 = nn.ReLU(\n            inplace=True\n        )\n\n        self.dropout = nn.Dropout(\n            p=dropout_rate\n        )\n\n        self.conv_2 = nn.Conv2d(\n            output_channels,\n            output_channels,\n            kernel_size=3,\n            stride=1,\n            padding=1,\n            bias=False\n        )\n\n        # A shortcut is required when dimensions change\n        if (\n            stride != 1 or\n            input_channels != output_channels\n        ):\n            self.shortcut = nn.Conv2d(\n                input_channels,\n                output_channels,\n                kernel_size=1,\n                stride=stride,\n                padding=0,\n                bias=False\n            )\n        else:\n            self.shortcut = nn.Identity()\n\n    def forward(self, inputs):\n        output = self.relu_1(\n            self.batch_norm_1(inputs)\n        )\n\n        shortcut = self.shortcut(inputs)\n\n        output = self.conv_1(output)\n\n        output = self.relu_2(\n            self.batch_norm_2(output)\n        )\n\n        output = self.dropout(output)\n        output = self.conv_2(output)\n\n        return output + shortcut","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:56:42.76279Z","iopub.execute_input":"2026-09-21T10:56:42.763903Z","iopub.status.idle":"2026-09-21T10:56:42.77378Z","shell.execute_reply.started":"2026-09-21T10:56:42.763847Z","shell.execute_reply":"2026-09-21T10:56:42.77284Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class WideResNet(nn.Module):\n    def __init__(\n        self,\n        depth=28,\n        widen_factor=10,\n        number_of_classes=10,\n        dropout_rate=0.3\n    ):\n        super().__init__()\n\n        if (depth - 4) % 6 != 0:\n            raise ValueError(\n                \"WideResNet depth must satisfy \"\n                \"(depth - 4) % 6 == 0\"\n            )\n\n        blocks_per_group = (\n            depth - 4\n        ) // 6\n\n        channels = [\n            16,\n            16 * widen_factor,\n            32 * widen_factor,\n            64 * widen_factor\n        ]\n\n        self.current_channels = channels[0]\n\n        self.initial_conv = nn.Conv2d(\n            3,\n            channels[0],\n            kernel_size=3,\n            stride=1,\n            padding=1,\n            bias=False\n        )\n\n        self.group_1 = self._make_group(\n            output_channels=channels[1],\n            number_of_blocks=blocks_per_group,\n            dropout_rate=dropout_rate,\n            first_stride=1\n        )\n\n        self.group_2 = self._make_group(\n            output_channels=channels[2],\n            number_of_blocks=blocks_per_group,\n            dropout_rate=dropout_rate,\n            first_stride=2\n        )\n\n        self.group_3 = self._make_group(\n            output_channels=channels[3],\n            number_of_blocks=blocks_per_group,\n            dropout_rate=dropout_rate,\n            first_stride=2\n        )\n\n        self.final_batch_norm = nn.BatchNorm2d(\n            channels[3]\n        )\n\n        self.final_relu = nn.ReLU(\n            inplace=True\n        )\n\n        self.classifier = nn.Linear(\n            channels[3],\n            number_of_classes\n        )\n\n        # Explicit random initialization\n        self.apply(\n            self._initialize_weights\n        )\n\n    def _make_group(\n        self,\n        output_channels,\n        number_of_blocks,\n        dropout_rate,\n        first_stride\n    ):\n        layers = []\n\n        for block_index in range(number_of_blocks):\n            stride = (\n                first_stride\n                if block_index == 0\n                else 1\n            )\n\n            layers.append(\n                WideBasicBlock(\n                    input_channels=self.current_channels,\n                    output_channels=output_channels,\n                    dropout_rate=dropout_rate,\n                    stride=stride\n                )\n            )\n\n            self.current_channels = output_channels\n\n        return nn.Sequential(*layers)\n\n    @staticmethod\n    def _initialize_weights(module):\n        if isinstance(module, nn.Conv2d):\n            nn.init.kaiming_normal_(\n                module.weight,\n                mode=\"fan_out\",\n                nonlinearity=\"relu\"\n            )\n\n        elif isinstance(module, nn.BatchNorm2d):\n            nn.init.ones_(module.weight)\n            nn.init.zeros_(module.bias)\n\n        elif isinstance(module, nn.Linear):\n            nn.init.normal_(\n                module.weight,\n                mean=0.0,\n                std=0.01\n            )\n\n            nn.init.zeros_(module.bias)\n\n    def forward(self, inputs):\n        output = self.initial_conv(inputs)\n        output = self.group_1(output)\n        output = self.group_2(output)\n        output = self.group_3(output)\n\n        output = self.final_relu(\n            self.final_batch_norm(output)\n        )\n\n        output = F.adaptive_avg_pool2d(\n            output,\n            output_size=1\n        )\n\n        output = torch.flatten(\n            output,\n            start_dim=1\n        )\n\n        return self.classifier(output)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:56:53.871878Z","iopub.execute_input":"2026-09-21T10:56:53.872781Z","iopub.status.idle":"2026-09-21T10:56:53.885212Z","shell.execute_reply.started":"2026-09-21T10:56:53.872752Z","shell.execute_reply":"2026-09-21T10:56:53.884438Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = WideResNet(\n    depth=28,\n    widen_factor=10,\n    number_of_classes=10,\n    dropout_rate=0.3\n)\n\nmodel = model.to(device)\n\n# DataParallel divides every batch across both T4 GPUs\nif torch.cuda.device_count() > 1:\n    model = nn.DataParallel(model)\n\n    print(\n        f\"Using {torch.cuda.device_count()} \"\n        \"GPUs simultaneously\"\n    )\nelse:\n    print(\"Using one GPU\")\n\nnumber_of_parameters = sum(\n    parameter.numel()\n    for parameter in model.parameters()\n    if parameter.requires_grad\n)\n\nprint(\"Model: WideResNet-28-10\")\nprint(\"Pretrained weights: None\")\nprint(\"Initialization: Random\")\nprint(\n    f\"Trainable parameters: \"\n    f\"{number_of_parameters:,}\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:57:01.459687Z","iopub.execute_input":"2026-09-21T10:57:01.460189Z","iopub.status.idle":"2026-09-21T10:57:02.046934Z","shell.execute_reply.started":"2026-09-21T10:57:01.460162Z","shell.execute_reply":"2026-09-21T10:57:02.046272Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\n\nwith torch.no_grad():\n    example_images = images[:8].to(\n        device\n    )\n\n    example_outputs = model(\n        example_images\n    )\n\nprint(\n    \"Input shape:\",\n    example_images.shape\n)\n\nprint(\n    \"Output shape:\",\n    example_outputs.shape\n)\n\nprint(\n    \"Forward pass: Successful\"\n)\n\nmodel.train()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:57:14.93449Z","iopub.execute_input":"2026-09-21T10:57:14.934804Z","iopub.status.idle":"2026-09-21T10:57:17.25051Z","shell.execute_reply.started":"2026-09-21T10:57:14.934778Z","shell.execute_reply":"2026-09-21T10:57:17.249919Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Training Configuration\n# Maximum number of training epochs\nEPOCHS = 150\n\n# Cross-entropy is used for ten-class classification\ncriterion = nn.CrossEntropyLoss(\n    label_smoothing=0.1\n)\n\n# SGD is commonly used for training WideResNet\noptimizer = optim.SGD(\n    model.parameters(),\n    lr=0.1,\n    momentum=0.9,\n    weight_decay=5e-4,\n    nesterov=True\n)\n\n# Gradually reduces the learning rate during training\nscheduler = optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=EPOCHS,\n    eta_min=1e-5\n)\n\n# Mixed precision reduces memory use and speeds up GPU training\nscaler = torch.amp.GradScaler(\n    \"cuda\",\n    enabled=torch.cuda.is_available()\n)\n\n# Location of the best saved model\nBEST_MODEL_PATH = (\n    \"/kaggle/working/\"\n    \"best_wideresnet_cifar10.pth\"\n)\n\nprint(\"Maximum epochs:\", EPOCHS)\nprint(\"Optimizer: SGD with Nesterov momentum\")\nprint(\"Initial learning rate:\", optimizer.param_groups[0][\"lr\"])\nprint(\"Weight decay:\", optimizer.param_groups[0][\"weight_decay\"])\nprint(\"Loss: Cross-entropy with label smoothing\")\nprint(\"Mixed precision enabled:\", scaler.is_enabled())\nprint(\"Best model path:\", BEST_MODEL_PATH)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:59:39.294746Z","iopub.execute_input":"2026-09-21T10:59:39.295392Z","iopub.status.idle":"2026-09-21T10:59:39.303857Z","shell.execute_reply.started":"2026-09-21T10:59:39.295359Z","shell.execute_reply":"2026-09-21T10:59:39.302839Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"MIXUP_ALPHA = 0.2\nMIXUP_PROBABILITY = 0.5\n\n\ndef apply_mixup(images, targets):\n    \"\"\"\n    Combine images and labels within the current batch.\n    \"\"\"\n\n    # Sometimes return the original batch\n    if random.random() > MIXUP_PROBABILITY:\n        return (\n            images,\n            targets,\n            targets,\n            1.0\n        )\n\n    mixing_value = np.random.beta(\n        MIXUP_ALPHA,\n        MIXUP_ALPHA\n    )\n\n    permutation = torch.randperm(\n        images.size(0),\n        device=images.device\n    )\n\n    mixed_images = (\n        mixing_value * images +\n        (1.0 - mixing_value) * images[permutation]\n    )\n\n    targets_a = targets\n    targets_b = targets[permutation]\n\n    return (\n        mixed_images,\n        targets_a,\n        targets_b,\n        mixing_value\n    )\n\n\ndef mixup_loss(\n    outputs,\n    targets_a,\n    targets_b,\n    mixing_value\n):\n    \"\"\"\n    Calculate the weighted loss for both MixUp labels.\n    \"\"\"\n\n    return (\n        mixing_value * criterion(outputs, targets_a) +\n        (1.0 - mixing_value) *\n        criterion(outputs, targets_b)\n    )\n\n\nprint(\"MixUp alpha:\", MIXUP_ALPHA)\nprint(\"MixUp probability:\", MIXUP_PROBABILITY)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:59:48.048312Z","iopub.execute_input":"2026-09-21T10:59:48.048717Z","iopub.status.idle":"2026-09-21T10:59:48.055946Z","shell.execute_reply.started":"2026-09-21T10:59:48.048688Z","shell.execute_reply":"2026-09-21T10:59:48.055131Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch(\n    model,\n    data_loader,\n    optimizer,\n    scaler\n):\n    model.train()\n\n    total_loss = 0.0\n    weighted_correct = 0.0\n    total_images = 0\n\n    for images, targets in data_loader:\n        images = images.to(\n            device,\n            non_blocking=True\n        )\n\n        targets = targets.to(\n            device,\n            non_blocking=True\n        )\n\n        # Apply MixUp to this batch\n        (\n            mixed_images,\n            targets_a,\n            targets_b,\n            mixing_value\n        ) = apply_mixup(\n            images,\n            targets\n        )\n\n        optimizer.zero_grad(\n            set_to_none=True\n        )\n\n        # Mixed-precision forward pass\n        with torch.amp.autocast(\n            \"cuda\",\n            enabled=torch.cuda.is_available()\n        ):\n            outputs = model(\n                mixed_images\n            )\n\n            loss = mixup_loss(\n                outputs,\n                targets_a,\n                targets_b,\n                mixing_value\n            )\n\n        # Mixed-precision backward pass\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        batch_size = images.size(0)\n\n        total_loss += (\n            loss.item() * batch_size\n        )\n\n        predictions = outputs.argmax(\n            dim=1\n        )\n\n        correct_a = (\n            predictions == targets_a\n        ).sum().item()\n\n        correct_b = (\n            predictions == targets_b\n        ).sum().item()\n\n        weighted_correct += (\n            mixing_value * correct_a +\n            (1.0 - mixing_value) * correct_b\n        )\n\n        total_images += batch_size\n\n    average_loss = (\n        total_loss / total_images\n    )\n\n    mixed_training_accuracy = (\n        100.0 *\n        weighted_correct /\n        total_images\n    )\n\n    return (\n        average_loss,\n        mixed_training_accuracy\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T10:59:58.229197Z","iopub.execute_input":"2026-09-21T10:59:58.229659Z","iopub.status.idle":"2026-09-21T10:59:58.237693Z","shell.execute_reply.started":"2026-09-21T10:59:58.229629Z","shell.execute_reply":"2026-09-21T10:59:58.236866Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# validation - defination\ndef evaluate_model(\n    model,\n    data_loader\n):\n    model.eval()\n\n    total_loss = 0.0\n    total_correct = 0\n    total_images = 0\n\n    all_predictions = []\n    all_targets = []\n\n    with torch.no_grad():\n        for images, targets in data_loader:\n            images = images.to(\n                device,\n                non_blocking=True\n            )\n\n            targets = targets.to(\n                device,\n                non_blocking=True\n            )\n\n            with torch.amp.autocast(\n                \"cuda\",\n                enabled=torch.cuda.is_available()\n            ):\n                outputs = model(images)\n                loss = criterion(\n                    outputs,\n                    targets\n                )\n\n            predictions = outputs.argmax(\n                dim=1\n            )\n\n            batch_size = images.size(0)\n\n            total_loss += (\n                loss.item() * batch_size\n            )\n\n            total_correct += (\n                predictions == targets\n            ).sum().item()\n\n            total_images += batch_size\n\n            all_predictions.extend(\n                predictions.cpu().numpy()\n            )\n\n            all_targets.extend(\n                targets.cpu().numpy()\n            )\n\n    average_loss = (\n        total_loss / total_images\n    )\n\n    accuracy = (\n        100.0 *\n        total_correct /\n        total_images\n    )\n\n    return (\n        average_loss,\n        accuracy,\n        np.array(all_predictions),\n        np.array(all_targets)\n    )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T11:00:20.551953Z","iopub.execute_input":"2026-09-21T11:00:20.55236Z","iopub.status.idle":"2026-09-21T11:00:20.56Z","shell.execute_reply.started":"2026-09-21T11:00:20.552332Z","shell.execute_reply":"2026-09-21T11:00:20.559274Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Testing the training and evaluation functions\nprint(\"Testing one training batch...\")\n\nmodel.train()\n\ntest_images, test_targets = next(\n    iter(train_loader)\n)\n\ntest_images = test_images.to(\n    device\n)\n\ntest_targets = test_targets.to(\n    device\n)\n\n(\n    mixed_images,\n    targets_a,\n    targets_b,\n    mixing_value\n) = apply_mixup(\n    test_images,\n    test_targets\n)\n\noptimizer.zero_grad(\n    set_to_none=True\n)\n\nwith torch.amp.autocast(\n    \"cuda\",\n    enabled=torch.cuda.is_available()\n):\n    test_outputs = model(\n        mixed_images\n    )\n\n    test_loss = mixup_loss(\n        test_outputs,\n        targets_a,\n        targets_b,\n        mixing_value\n    )\n\nscaler.scale(test_loss).backward()\nscaler.step(optimizer)\nscaler.update()\n\nprint(\"Test batch loss:\", test_loss.item())\nprint(\"Output shape:\", test_outputs.shape)\nprint(\"Training-function test: Successful\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T11:00:29.326245Z","iopub.execute_input":"2026-09-21T11:00:29.32666Z","iopub.status.idle":"2026-09-21T11:00:41.969096Z","shell.execute_reply.started":"2026-09-21T11:00:29.326631Z","shell.execute_reply":"2026-09-21T11:00:41.968279Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train the WideResNet Model\nhistory = {\n    \"train_loss\": [],\n    \"train_accuracy\": [],\n    \"validation_loss\": [],\n    \"validation_accuracy\": [],\n    \"learning_rate\": [],\n    \"epoch_time\": []\n}\n\nbest_validation_accuracy = 0.0\nbest_epoch = 0\n\ntraining_start_time = time.perf_counter()\n\nfor epoch in range(1, EPOCHS + 1):\n    epoch_start_time = time.perf_counter()\n\n    # Train for one complete epoch\n    (\n        train_loss,\n        train_accuracy\n    ) = train_one_epoch(\n        model=model,\n        data_loader=train_loader,\n        optimizer=optimizer,\n        scaler=scaler\n    )\n\n    # Evaluate on the validation set\n    (\n        validation_loss,\n        validation_accuracy,\n        validation_predictions,\n        validation_targets\n    ) = evaluate_model(\n        model=model,\n        data_loader=validation_loader\n    )\n\n    current_learning_rate = (\n        optimizer.param_groups[0][\"lr\"]\n    )\n\n    epoch_time = (\n        time.perf_counter() -\n        epoch_start_time\n    )\n\n    # Save results for graphs\n    history[\"train_loss\"].append(\n        train_loss\n    )\n\n    history[\"train_accuracy\"].append(\n        train_accuracy\n    )\n\n    history[\"validation_loss\"].append(\n        validation_loss\n    )\n\n    history[\"validation_accuracy\"].append(\n        validation_accuracy\n    )\n\n    history[\"learning_rate\"].append(\n        current_learning_rate\n    )\n\n    history[\"epoch_time\"].append(\n        epoch_time\n    )\n\n    # Save the best model\n    if (\n        validation_accuracy >\n        best_validation_accuracy\n    ):\n        best_validation_accuracy = (\n            validation_accuracy\n        )\n\n        best_epoch = epoch\n\n        # Remove the DataParallel wrapper before saving\n        model_to_save = (\n            model.module\n            if isinstance(model, nn.DataParallel)\n            else model\n        )\n\n        torch.save(\n            {\n                \"epoch\": epoch,\n                \"model_state_dict\":\n                    model_to_save.state_dict(),\n                \"validation_accuracy\":\n                    validation_accuracy,\n                \"class_names\":\n                    class_names\n            },\n            BEST_MODEL_PATH\n        )\n\n        saved_message = \" | Best model saved\"\n    else:\n        saved_message = \"\"\n\n    print(\n        f\"Epoch {epoch:03d}/{EPOCHS} | \"\n        f\"Train loss: {train_loss:.4f} | \"\n        f\"Mixed train accuracy: \"\n        f\"{train_accuracy:.2f}% | \"\n        f\"Validation loss: \"\n        f\"{validation_loss:.4f} | \"\n        f\"Validation accuracy: \"\n        f\"{validation_accuracy:.2f}% | \"\n        f\"LR: {current_learning_rate:.6f} | \"\n        f\"Time: {epoch_time:.1f}s\"\n        f\"{saved_message}\"\n    )\n\n    # Update the learning rate\n    scheduler.step()\n\ntotal_training_time = (\n    time.perf_counter() -\n    training_start_time\n)\n\nprint(\"\\nTraining completed\")\nprint(\"=\" * 50)\nprint(\"Completed epochs:\", EPOCHS)\nprint(\"Best epoch:\", best_epoch)\nprint(\n    f\"Best validation accuracy: \"\n    f\"{best_validation_accuracy:.2f}%\"\n)\nprint(\n    f\"Total training time: \"\n    f\"{total_training_time:.2f} seconds\"\n)\nprint(\"Best model:\", BEST_MODEL_PATH)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T11:00:57.693589Z","iopub.execute_input":"2026-09-21T11:00:57.694273Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training results\ncompleted_epochs = range(\n    1,\n    len(history[\"train_loss\"]) + 1\n)\n\nfig, axes = plt.subplots(\n    1,\n    2,\n    figsize=(15, 5)\n)\n\n# Loss graph\naxes[0].plot(\n    completed_epochs,\n    history[\"train_loss\"],\n    label=\"Training loss\"\n)\n\naxes[0].plot(\n    completed_epochs,\n    history[\"validation_loss\"],\n    label=\"Validation loss\"\n)\n\naxes[0].set_title(\"Training and Validation Loss\")\naxes[0].set_xlabel(\"Epoch\")\naxes[0].set_ylabel(\"Loss\")\naxes[0].grid(alpha=0.3)\naxes[0].legend()\n\n# Accuracy graph\naxes[1].plot(\n    completed_epochs,\n    history[\"train_accuracy\"],\n    label=\"Mixed training accuracy\"\n)\n\naxes[1].plot(\n    completed_epochs,\n    history[\"validation_accuracy\"],\n    label=\"Validation accuracy\"\n)\n\naxes[1].axhline(\n    y=88,\n    color=\"red\",\n    linestyle=\"--\",\n    label=\"Full-mark target: 88%\"\n)\n\naxes[1].set_title(\"Training and Validation Accuracy\")\naxes[1].set_xlabel(\"Epoch\")\naxes[1].set_ylabel(\"Accuracy (%)\")\naxes[1].grid(alpha=0.3)\naxes[1].legend()\n\nplt.tight_layout()\nplt.show()\n\nprint(\"Best epoch:\", best_epoch)\nprint(\n    f\"Best validation accuracy: \"\n    f\"{best_validation_accuracy:.2f}%\"\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Loading best checkpoint\n# Create a new model with the same architecture\nbest_model = WideResNet(\n    depth=28,\n    widen_factor=10,\n    number_of_classes=10,\n    dropout_rate=0.3\n)\n\ncheckpoint = torch.load(\n    BEST_MODEL_PATH,\n    map_location=\"cpu\",\n    weights_only=False\n)\n\nbest_model.load_state_dict(\n    checkpoint[\"model_state_dict\"]\n)\n\nbest_model = best_model.to(device)\n\n# Use both GPUs during evaluation and prediction\nif torch.cuda.device_count() > 1:\n    best_model = nn.DataParallel(\n        best_model\n    )\n\nprint(\"Best checkpoint loaded successfully.\")\nprint(\"Saved epoch:\", checkpoint[\"epoch\"])\nprint(\n    \"Saved validation accuracy:\",\n    f\"{checkpoint['validation_accuracy']:.2f}%\"\n)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Evaluating\n(\n    best_validation_loss,\n    confirmed_validation_accuracy,\n    validation_predictions,\n    validation_targets\n) = evaluate_model(\n    model=best_model,\n    data_loader=validation_loader\n)\n\nprint(\"BEST MODEL VALIDATION RESULTS\")\nprint(\"=\" * 45)\nprint(\"Model: WideResNet-28-10\")\nprint(\"Pretrained weights: None\")\nprint(\"Initialization: Random\")\nprint(\"Saved epoch:\", checkpoint[\"epoch\"])\nprint(\n    f\"Validation loss: \"\n    f\"{best_validation_loss:.4f}\"\n)\nprint(\n    f\"Validation accuracy: \"\n    f\"{confirmed_validation_accuracy:.2f}%\"\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Creating the test dataset\nclass CIFAR10TestDataset(Dataset):\n    def __init__(\n        self,\n        submission_dataframe,\n        image_directory,\n        transform=None\n    ):\n        self.submission_dataframe = (\n            submission_dataframe.copy()\n        )\n\n        self.image_directory = Path(\n            image_directory\n        )\n\n        self.transform = transform\n\n    def __len__(self):\n        return len(\n            self.submission_dataframe\n        )\n\n    def __getitem__(self, index):\n        image_id = int(\n            self.submission_dataframe\n            .iloc[index][\"id\"]\n        )\n\n        image_path = (\n            self.image_directory /\n            f\"{image_id}.png\"\n        )\n\n        image = Image.open(\n            image_path\n        ).convert(\"RGB\")\n\n        if self.transform is not None:\n            image = self.transform(image)\n\n        return image, image_id\n\n\ntest_dataset = CIFAR10TestDataset(\n    submission_dataframe=sample_submission,\n    image_directory=TEST_DIRECTORY,\n    transform=evaluation_transform\n)\n\nTEST_BATCH_SIZE = 512\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=TEST_BATCH_SIZE,\n    shuffle=False,\n    num_workers=NUM_WORKERS,\n    pin_memory=True,\n    persistent_workers=NUM_WORKERS > 0\n)\n\nprint(\"Kaggle test images:\", len(test_dataset))\nprint(\"Test batches:\", len(test_loader))\nprint(\"Test batch size:\", TEST_BATCH_SIZE)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Generating prediction\nbest_model.eval()\n\npredicted_ids = []\npredicted_classes = []\n\nprediction_start_time = time.perf_counter()\n\nwith torch.no_grad():\n    for batch_number, (\n        images,\n        image_ids\n    ) in enumerate(test_loader, start=1):\n\n        images = images.to(\n            device,\n            non_blocking=True\n        )\n\n        with torch.amp.autocast(\n            \"cuda\",\n            enabled=torch.cuda.is_available()\n        ):\n            # Prediction using the original images\n            original_outputs = best_model(\n                images\n            )\n\n            # Prediction using horizontally flipped images\n            flipped_outputs = best_model(\n                torch.flip(\n                    images,\n                    dims=[3]\n                )\n            )\n\n            # Average both predictions\n            averaged_outputs = (\n                original_outputs +\n                flipped_outputs\n            ) / 2.0\n\n        predictions = averaged_outputs.argmax(\n            dim=1\n        )\n\n        predicted_ids.extend(\n            image_ids.numpy().tolist()\n        )\n\n        predicted_classes.extend(\n            predictions.cpu().numpy().tolist()\n        )\n\n        if (\n            batch_number % 100 == 0 or\n            batch_number == len(test_loader)\n        ):\n            print(\n                f\"Processed batch \"\n                f\"{batch_number}/\"\n                f\"{len(test_loader)}\"\n            )\n\nprediction_time = (\n    time.perf_counter() -\n    prediction_start_time\n)\n\nprint(\"\\nPrediction completed.\")\nprint(\"Predictions created:\", len(predicted_classes))\nprint(\n    f\"Prediction time: \"\n    f\"{prediction_time:.2f} seconds\"\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# creating the submission.csv\npredicted_labels = [\n    index_to_class[class_index]\n    for class_index in predicted_classes\n]\n\nsubmission = pd.DataFrame({\n    \"id\": predicted_ids,\n    \"label\": predicted_labels\n})\n\n# Sort by image ID to match Kaggle's required order\nsubmission = submission.sort_values(\n    by=\"id\"\n).reset_index(\n    drop=True\n)\n\nSUBMISSION_PATH = (\n    \"/kaggle/working/submission.csv\"\n)\n\nsubmission.to_csv(\n    SUBMISSION_PATH,\n    index=False\n)\n\nprint(\"Submission created:\", SUBMISSION_PATH)\nprint(\"Submission shape:\", submission.shape)\n\ndisplay(submission.head())\ndisplay(submission.tail())","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Verifying submission\nexpected_labels = set(class_names)\nsubmission_labels = set(\n    submission[\"label\"].unique()\n)\n\nprint(\"SUBMISSION VERIFICATION\")\nprint(\"=\" * 45)\nprint(\"Rows:\", len(submission))\nprint(\"Columns:\", submission.columns.tolist())\nprint(\"Missing values:\", submission.isna().sum().sum())\nprint(\"Duplicate IDs:\", submission[\"id\"].duplicated().sum())\nprint(\"Smallest ID:\", submission[\"id\"].min())\nprint(\"Largest ID:\", submission[\"id\"].max())\nprint(\"Predicted classes:\", sorted(submission_labels))\n\nassert len(submission) == 300000\nassert submission.columns.tolist() == [\"id\", \"label\"]\nassert submission.isna().sum().sum() == 0\nassert submission[\"id\"].duplicated().sum() == 0\nassert submission_labels.issubset(expected_labels)\n\nprint(\"\\nSubmission verification: PASSED\")\nprint(\"The file is ready for Kaggle.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"FINAL EXPERIMENT SUMMARY\")\nprint(\"=\" * 50)\nprint(\"Framework: PyTorch\")\nprint(\"Model: WideResNet-28-10\")\nprint(\"Pretrained weights: None\")\nprint(\"Initialization: Random\")\nprint(\"Hardware: 2 x Tesla T4\")\nprint(\"Training images:\", len(train_dataset))\nprint(\"Validation images:\", len(validation_dataset))\nprint(\"Kaggle test images:\", len(test_dataset))\nprint(\"Completed epochs:\", EPOCHS)\nprint(\"Best epoch:\", checkpoint[\"epoch\"])\nprint(\n    f\"Best validation accuracy: \"\n    f\"{confirmed_validation_accuracy:.2f}%\"\n)\nprint(\n    f\"Training time: \"\n    f\"{total_training_time:.2f} seconds\"\n)\nprint(\n    f\"Prediction time: \"\n    f\"{prediction_time:.2f} seconds\"\n)\nprint(\"Submission file:\", SUBMISSION_PATH)\nprint(\"Submission status: Ready\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}