{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.6.6","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},"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},"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},"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},"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},"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},"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},"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},"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        # Close the image file immediately after loading it\n        with Image.open(image_path) as image_file:\n            image = image_file.convert(\"RGB\")\n\n        if self.transform is not None:\n            image = self.transform(image)\n\n        return image, target\n#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= False,\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= False,\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},"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Custom ConvNet designed for this challenge\nclass StochasticDepth(nn.Module):\n    \"\"\"\n    Randomly skips some residual branches during training.\n    \"\"\"\n\n    def __init__(self, drop_probability=0.0):\n        super().__init__()\n        self.drop_probability = drop_probability\n\n    def forward(self, inputs):\n        if (\n            not self.training or\n            self.drop_probability == 0.0\n        ):\n            return inputs\n\n        keep_probability = (\n            1.0 - self.drop_probability\n        )\n\n        mask_shape = (\n            inputs.shape[0],\n            1,\n            1,\n            1\n        )\n\n        random_mask = (\n            keep_probability +\n            torch.rand(\n                mask_shape,\n                dtype=inputs.dtype,\n                device=inputs.device\n            )\n        )\n\n        random_mask.floor_()\n\n        return (\n            inputs *\n            random_mask /\n            keep_probability\n        )\n\n\nclass ChannelAttention(nn.Module):\n    \"\"\"\n    Learns which feature channels are most useful.\n    \"\"\"\n\n    def __init__(\n        self,\n        number_of_channels,\n        reduction=8\n    ):\n        super().__init__()\n\n        hidden_channels = max(\n            number_of_channels // reduction,\n            8\n        )\n\n        self.average_pool = (\n            nn.AdaptiveAvgPool2d(1)\n        )\n\n        self.attention = nn.Sequential(\n            nn.Conv2d(\n                number_of_channels,\n                hidden_channels,\n                kernel_size=1,\n                bias=True\n            ),\n\n            nn.SiLU(),\n\n            nn.Conv2d(\n                hidden_channels,\n                number_of_channels,\n                kernel_size=1,\n                bias=True\n            ),\n\n            nn.Sigmoid()\n        )\n\n    def forward(self, inputs):\n        channel_weights = self.attention(\n            self.average_pool(inputs)\n        )\n\n        return inputs * channel_weights\n\n\nclass CustomResidualAttentionBlock(nn.Module):\n    \"\"\"\n    Custom block using convolutions, a residual connection,\n    channel attention and stochastic depth.\n    \"\"\"\n\n    def __init__(\n        self,\n        input_channels,\n        output_channels,\n        stride=1,\n        dropout_rate=0.0,\n        stochastic_depth_rate=0.0\n    ):\n        super().__init__()\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_1 = nn.BatchNorm2d(\n            output_channels\n        )\n\n        self.activation_1 = nn.SiLU(\n            inplace=True\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        self.batch_norm_2 = nn.BatchNorm2d(\n            output_channels\n        )\n\n        self.channel_attention = ChannelAttention(\n            number_of_channels=output_channels,\n            reduction=8\n        )\n\n        self.feature_dropout = nn.Dropout2d(\n            p=dropout_rate\n        )\n\n        self.stochastic_depth = StochasticDepth(\n            drop_probability=stochastic_depth_rate\n        )\n\n        if (\n            stride != 1 or\n            input_channels != output_channels\n        ):\n            self.shortcut = nn.Sequential(\n                nn.Conv2d(\n                    input_channels,\n                    output_channels,\n                    kernel_size=1,\n                    stride=stride,\n                    bias=False\n                ),\n\n                nn.BatchNorm2d(\n                    output_channels\n                )\n            )\n        else:\n            self.shortcut = nn.Identity()\n\n        self.final_activation = nn.SiLU(\n            inplace=True\n        )\n\n    def forward(self, inputs):\n        shortcut = self.shortcut(inputs)\n\n        output = self.conv_1(inputs)\n        output = self.batch_norm_1(output)\n        output = self.activation_1(output)\n\n        output = self.feature_dropout(output)\n\n        output = self.conv_2(output)\n        output = self.batch_norm_2(output)\n\n        output = self.channel_attention(output)\n        output = self.stochastic_depth(output)\n\n        output = output + shortcut\n        output = self.final_activation(output)\n\n        return output\n\n\nclass CustomCIFARNet(nn.Module):\n    \"\"\"\n    Custom four-stage ConvNet created for CIFAR-10.\n\n    Stage 1: 3 blocks with 64 channels\n    Stage 2: 4 blocks with 128 channels\n    Stage 3: 6 blocks with 256 channels\n    Stage 4: 3 blocks with 384 channels\n    \"\"\"\n\n    def __init__(\n        self,\n        number_of_classes=10\n    ):\n        super().__init__()\n\n        stage_channels = [\n            64,\n            128,\n            256,\n            384\n        ]\n\n        blocks_per_stage = [\n            3,\n            4,\n            6,\n            3\n        ]\n\n        total_blocks = sum(\n            blocks_per_stage\n        )\n\n        stochastic_depth_rates = (\n            torch.linspace(\n                0.0,\n                0.15,\n                total_blocks\n            ).tolist()\n        )\n\n        self.current_channels = (\n            stage_channels[0]\n        )\n\n        self.stem = nn.Sequential(\n            nn.Conv2d(\n                3,\n                stage_channels[0],\n                kernel_size=3,\n                stride=1,\n                padding=1,\n                bias=False\n            ),\n\n            nn.BatchNorm2d(\n                stage_channels[0]\n            ),\n\n            nn.SiLU(\n                inplace=True\n            )\n        )\n\n        current_block = 0\n\n        self.stage_1, current_block = (\n            self._make_stage(\n                output_channels=64,\n                number_of_blocks=3,\n                first_stride=1,\n                dropout_rate=0.05,\n                stochastic_depth_rates=\n                    stochastic_depth_rates,\n                starting_block=current_block\n            )\n        )\n\n        self.stage_2, current_block = (\n            self._make_stage(\n                output_channels=128,\n                number_of_blocks=4,\n                first_stride=2,\n                dropout_rate=0.05,\n                stochastic_depth_rates=\n                    stochastic_depth_rates,\n                starting_block=current_block\n            )\n        )\n\n        self.stage_3, current_block = (\n            self._make_stage(\n                output_channels=256,\n                number_of_blocks=6,\n                first_stride=2,\n                dropout_rate=0.10,\n                stochastic_depth_rates=\n                    stochastic_depth_rates,\n                starting_block=current_block\n            )\n        )\n\n        self.stage_4, current_block = (\n            self._make_stage(\n                output_channels=384,\n                number_of_blocks=3,\n                first_stride=2,\n                dropout_rate=0.10,\n                stochastic_depth_rates=\n                    stochastic_depth_rates,\n                starting_block=current_block\n            )\n        )\n\n        self.final_batch_norm = nn.BatchNorm2d(\n            384\n        )\n\n        self.final_activation = nn.SiLU(\n            inplace=True\n        )\n\n        self.global_average_pool = (\n            nn.AdaptiveAvgPool2d(1)\n        )\n\n        self.classifier_dropout = nn.Dropout(\n            p=0.20\n        )\n\n        self.classifier = nn.Linear(\n            384,\n            number_of_classes\n        )\n\n        # Explicit random initialization\n        self.apply(\n            self._initialize_weights\n        )\n\n    def _make_stage(\n        self,\n        output_channels,\n        number_of_blocks,\n        first_stride,\n        dropout_rate,\n        stochastic_depth_rates,\n        starting_block\n    ):\n        layers = []\n\n        for block_index in range(\n            number_of_blocks\n        ):\n            stride = (\n                first_stride\n                if block_index == 0\n                else 1\n            )\n\n            drop_rate = (\n                stochastic_depth_rates[\n                    starting_block +\n                    block_index\n                ]\n            )\n\n            layers.append(\n                CustomResidualAttentionBlock(\n                    input_channels=\n                        self.current_channels,\n                    output_channels=\n                        output_channels,\n                    stride=stride,\n                    dropout_rate=dropout_rate,\n                    stochastic_depth_rate=\n                        drop_rate\n                )\n            )\n\n            self.current_channels = (\n                output_channels\n            )\n\n        next_block = (\n            starting_block +\n            number_of_blocks\n        )\n\n        return (\n            nn.Sequential(*layers),\n            next_block\n        )\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            if module.bias is not None:\n                nn.init.zeros_(\n                    module.bias\n                )\n\n        elif isinstance(module, nn.BatchNorm2d):\n            nn.init.ones_(\n                module.weight\n            )\n\n            nn.init.zeros_(\n                module.bias\n            )\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_(\n                module.bias\n            )\n\n    def forward(self, inputs):\n        output = self.stem(inputs)\n\n        output = self.stage_1(output)\n        output = self.stage_2(output)\n        output = self.stage_3(output)\n        output = self.stage_4(output)\n\n        output = self.final_batch_norm(\n            output\n        )\n\n        output = self.final_activation(\n            output\n        )\n\n        output = self.global_average_pool(\n            output\n        )\n\n        output = torch.flatten(\n            output,\n            start_dim=1\n        )\n\n        output = self.classifier_dropout(\n            output\n        )\n\n        return self.classifier(output)\n\n\nprint(\"Custom model classes created successfully.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create the model using random initialization\nmodel = CustomCIFARNet(\n    number_of_classes=10\n)\n\nmodel = model.to(device)\n\n# Use both Tesla T4 GPUs simultaneously\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: CustomCIFARNet\")\nprint(\"Architecture: Custom design\")\nprint(\"Pretrained weights: None\")\nprint(\n    \"Initialization: \"\n    \"Random Kaiming initialization\"\n)\nprint(\n    f\"Trainable parameters: \"\n    f\"{number_of_parameters:,}\"\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Verify that the custom model works\nmodel.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(\"Forward pass: Successful\")\n\nmodel.train()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training configuration\nEPOCHS = 120\nWARMUP_EPOCHS = 5\n\n# Classification loss\ncriterion = nn.CrossEntropyLoss(\n    label_smoothing=0.1\n)\n\n# Required advanced optimizer\noptimizer = optim.AdamW(\n    model.parameters(),\n    lr=0.001,\n    betas=(0.9, 0.999),\n    weight_decay=0.01\n)\n\n# Gradually increase the learning rate first\nwarmup_scheduler = optim.lr_scheduler.LinearLR(\n    optimizer,\n    start_factor=0.1,\n    end_factor=1.0,\n    total_iters=WARMUP_EPOCHS\n)\n\n# Then gradually reduce it using cosine annealing\ncosine_scheduler = (\n    optim.lr_scheduler.CosineAnnealingLR(\n        optimizer,\n        T_max=EPOCHS - WARMUP_EPOCHS,\n        eta_min=1e-6\n    )\n)\n\n# Combine both schedulers\nscheduler = optim.lr_scheduler.SequentialLR(\n    optimizer,\n    schedulers=[\n        warmup_scheduler,\n        cosine_scheduler\n    ],\n    milestones=[\n        WARMUP_EPOCHS\n    ]\n)\n\n# Mixed precision\nscaler = torch.amp.GradScaler(\n    \"cuda\",\n    enabled=torch.cuda.is_available()\n)\n\nBEST_MODEL_PATH = (\n    \"/kaggle/working/\"\n    \"best_custom_cifar_net.pth\"\n)\n\nprint(\"Maximum epochs:\", EPOCHS)\nprint(\"Warmup epochs:\", WARMUP_EPOCHS)\nprint(\"Optimizer: AdamW\")\nprint(\n    \"Starting learning rate:\",\n    optimizer.param_groups[0][\"lr\"]\n)\nprint(\"Target learning rate:\", 0.001)\nprint(\"Weight decay:\", 0.01)\nprint(\n    \"Scheduler: \"\n    \"Linear warmup + cosine annealing\"\n)\nprint(\n    \"Mixed precision enabled:\",\n    scaler.is_enabled()\n)\nprint(\"Best model path:\", BEST_MODEL_PATH)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Required advanced data augmentation\nCUTMIX_ALPHA = 1.0\nCUTMIX_PROBABILITY = 0.5\n\n\ndef create_cutmix_box(\n    image_height,\n    image_width,\n    mixing_value\n):\n    cut_ratio = np.sqrt(\n        1.0 - mixing_value\n    )\n\n    cut_width = int(\n        image_width * cut_ratio\n    )\n\n    cut_height = int(\n        image_height * cut_ratio\n    )\n\n    center_x = np.random.randint(\n        0,\n        image_width\n    )\n\n    center_y = np.random.randint(\n        0,\n        image_height\n    )\n\n    x_1 = np.clip(\n        center_x - cut_width // 2,\n        0,\n        image_width\n    )\n\n    x_2 = np.clip(\n        center_x + cut_width // 2,\n        0,\n        image_width\n    )\n\n    y_1 = np.clip(\n        center_y - cut_height // 2,\n        0,\n        image_height\n    )\n\n    y_2 = np.clip(\n        center_y + cut_height // 2,\n        0,\n        image_height\n    )\n\n    return (\n        int(x_1),\n        int(y_1),\n        int(x_2),\n        int(y_2)\n    )\n\n\ndef apply_cutmix(\n    images,\n    targets\n):\n    # Use the original batch sometimes\n    if random.random() > CUTMIX_PROBABILITY:\n        return (\n            images,\n            targets,\n            targets,\n            1.0\n        )\n\n    mixing_value = np.random.beta(\n        CUTMIX_ALPHA,\n        CUTMIX_ALPHA\n    )\n\n    permutation = torch.randperm(\n        images.size(0),\n        device=images.device\n    )\n\n    image_height = images.size(2)\n    image_width = images.size(3)\n\n    (\n        x_1,\n        y_1,\n        x_2,\n        y_2\n    ) = create_cutmix_box(\n        image_height,\n        image_width,\n        mixing_value\n    )\n\n    mixed_images = images.clone()\n\n    mixed_images[\n        :,\n        :,\n        y_1:y_2,\n        x_1:x_2\n    ] = images[\n        permutation,\n        :,\n        y_1:y_2,\n        x_1:x_2\n    ]\n\n    replaced_area = (\n        (x_2 - x_1) *\n        (y_2 - y_1)\n    )\n\n    total_area = (\n        image_height *\n        image_width\n    )\n\n    adjusted_mixing_value = (\n        1.0 -\n        replaced_area /\n        total_area\n    )\n\n    targets_a = targets\n    targets_b = targets[permutation]\n\n    return (\n        mixed_images,\n        targets_a,\n        targets_b,\n        adjusted_mixing_value\n    )\n\n\ndef cutmix_loss(\n    outputs,\n    targets_a,\n    targets_b,\n    mixing_value\n):\n    return (\n        mixing_value *\n        criterion(\n            outputs,\n            targets_a\n        ) +\n        (1.0 - mixing_value) *\n        criterion(\n            outputs,\n            targets_b\n        )\n    )\n\n\nprint(\"Advanced augmentation: CutMix\")\nprint(\"CutMix alpha:\", CUTMIX_ALPHA)\nprint(\n    \"CutMix probability:\",\n    CUTMIX_PROBABILITY\n)","metadata":{"trusted":true},"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        (\n            mixed_images,\n            targets_a,\n            targets_b,\n            mixing_value\n        ) = apply_cutmix(\n            images,\n            targets\n        )\n\n        optimizer.zero_grad(\n            set_to_none=True\n        )\n\n        with torch.amp.autocast(\n            \"cuda\",\n            enabled=torch.cuda.is_available()\n        ):\n            outputs = model(\n                mixed_images\n            )\n\n            loss = cutmix_loss(\n                outputs,\n                targets_a,\n                targets_b,\n                mixing_value\n            )\n\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() *\n            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 /\n        total_images\n    )\n\n    training_accuracy = (\n        100.0 *\n        weighted_correct /\n        total_images\n    )\n\n    return (\n        average_loss,\n        training_accuracy\n    )","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def 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\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() *\n                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 /\n        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    )","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Test CutMix before starting full training\ntest_images, test_targets = next(\n    iter(train_loader)\n)\n\ntest_images = test_images.to(device)\ntest_targets = test_targets.to(device)\n\n(\n    cutmix_test_images,\n    test_targets_a,\n    test_targets_b,\n    test_mixing_value\n) = apply_cutmix(\n    test_images,\n    test_targets\n)\n\nmodel.eval()\n\nwith torch.no_grad():\n    with torch.amp.autocast(\n        \"cuda\",\n        enabled=torch.cuda.is_available()\n    ):\n        test_outputs = model(\n            cutmix_test_images\n        )\n\n        test_loss = cutmix_loss(\n            test_outputs,\n            test_targets_a,\n            test_targets_b,\n            test_mixing_value\n        )\n\nmodel.train()\n\nprint(\n    \"Input shape:\",\n    cutmix_test_images.shape\n)\n\nprint(\n    \"Output shape:\",\n    test_outputs.shape\n)\n\nprint(\n    \"Test loss:\",\n    round(test_loss.item(), 4)\n)\n\nprint(\n    \"CutMix lambda:\",\n    round(\n        float(test_mixing_value),\n        4\n    )\n)\n\nprint(\n    \"CutMix and forward-pass test: \"\n    \"Successful\"\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train CustomCIFARNet\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 = (\n        time.perf_counter()\n    )\n\n    current_learning_rate = (\n        optimizer.param_groups[0][\"lr\"]\n    )\n\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    (\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    epoch_time = (\n        time.perf_counter() -\n        epoch_start_time\n    )\n\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    if (\n        validation_accuracy >\n        best_validation_accuracy\n    ):\n        best_validation_accuracy = (\n            validation_accuracy\n        )\n\n        best_epoch = epoch\n\n        model_to_save = (\n            model.module\n            if isinstance(\n                model,\n                nn.DataParallel\n            )\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                \"model_name\":\n                    \"CustomCIFARNet\",\n                \"optimizer\":\n                    \"AdamW\",\n                \"scheduler\":\n                    \"Linear warmup + \"\n                    \"cosine annealing\",\n                \"augmentation\":\n                    \"CutMix\",\n                \"pretrained_weights\":\n                    False\n            },\n            BEST_MODEL_PATH\n        )\n\n        saved_message = (\n            \" | Best model saved\"\n        )\n    else:\n        saved_message = \"\"\n\n    training_phase = (\n        \"Warmup\"\n        if epoch <= WARMUP_EPOCHS\n        else \"Cosine\"\n    )\n\n    print(\n        f\"Epoch {epoch:03d}/{EPOCHS} | \"\n        f\"Phase: {training_phase} | \"\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: \"\n        f\"{current_learning_rate:.7f} | \"\n        f\"Time: {epoch_time:.1f}s\"\n        f\"{saved_message}\"\n    )\n\n    scheduler.step()\n\ntotal_training_time = (\n    time.perf_counter() -\n    training_start_time\n)\n\nprint(\"\\nTraining completed\")\nprint(\"=\" * 55)\nprint(\"Model: CustomCIFARNet\")\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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Display training results\ncompleted_epochs = range(\n    1,\n    len(history[\"train_loss\"]) + 1\n)\n\nfig, axes = plt.subplots(\n    1,\n    3,\n    figsize=(18, 5)\n)\n\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(\"Loss\")\naxes[0].set_xlabel(\"Epoch\")\naxes[0].set_ylabel(\"Loss\")\naxes[0].legend()\naxes[0].grid(alpha=0.3)\n\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].set_title(\"Accuracy\")\naxes[1].set_xlabel(\"Epoch\")\naxes[1].set_ylabel(\"Accuracy (%)\")\naxes[1].legend()\naxes[1].grid(alpha=0.3)\n\naxes[2].plot(\n    completed_epochs,\n    history[\"learning_rate\"]\n)\n\naxes[2].set_title(\n    \"Warmup and Cosine Learning Rate\"\n)\n\naxes[2].set_xlabel(\"Epoch\")\naxes[2].set_ylabel(\"Learning rate\")\naxes[2].grid(alpha=0.3)\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":"# Recreate the custom architecture\nbest_model = CustomCIFARNet(\n    number_of_classes=10\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\nif torch.cuda.device_count() > 1:\n    best_model = nn.DataParallel(\n        best_model\n    )\n\nprint(\n    \"Best checkpoint loaded successfully.\"\n)\n\nprint(\n    \"Saved epoch:\",\n    checkpoint[\"epoch\"]\n)\n\nprint(\n    \"Saved validation accuracy:\",\n    f\"{checkpoint['validation_accuracy']:.2f}%\"\n)\n\nprint(\n    \"Pretrained weights:\",\n    checkpoint[\"pretrained_weights\"]\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"(\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(\"=\" * 50)\nprint(\"Model: CustomCIFARNet\")\nprint(\"Architecture: Custom design\")\nprint(\"Optimizer: AdamW\")\nprint(\n    \"Scheduler: \"\n    \"Linear warmup + cosine annealing\"\n)\nprint(\"Advanced augmentation: CutMix\")\nprint(\"Pretrained weights: None\")\nprint(\n    \"Initialization: \"\n    \"Random Kaiming initialization\"\n)\nprint(\n    \"Saved epoch:\",\n    checkpoint[\"epoch\"]\n)\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":"class CIFAR10TestDataset(Dataset):\n    def __init__(\n        self,\n        submission_dataframe,\n        image_directory,\n        transform=None\n    ):\n        self.submission_dataframe = submission_dataframe.copy()\n        self.image_directory = Path(image_directory)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.submission_dataframe)\n\n    def __getitem__(self, index):\n        image_id = int(\n            self.submission_dataframe.iloc[index][\"id\"]\n        )\n\n        image_path = (\n            self.image_directory /\n            f\"{image_id}.png\"\n        )\n\n        # Close every image file immediately after reading it\n        with Image.open(image_path) as image_file:\n            image = image_file.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\n# Smaller batch to reduce memory usage\nTEST_BATCH_SIZE = 256\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=TEST_BATCH_SIZE,\n    shuffle=False,\n\n    # Most stable setting when reading 300,000 small files\n    num_workers=0,\n\n    pin_memory=True,\n    persistent_workers=False\n)\n\nprint(\"Kaggle test images:\", len(test_dataset))\nprint(\"Test batches:\", len(test_loader))\nprint(\"Test batch size:\", TEST_BATCH_SIZE)\nprint(\"Test DataLoader workers: 0\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Generate predictions using one forward pass per image.\n# TTA is disabled here to make prediction faster and more stable.\n\nbest_model.eval()\n\npredicted_ids = []\npredicted_classes = []\n\nprediction_start_time = time.perf_counter()\n\nwith torch.inference_mode():\n    for batch_number, (images, image_ids) in enumerate(\n        test_loader,\n        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            outputs = best_model(images)\n\n        predictions = outputs.argmax(dim=1)\n\n        predicted_ids.extend(\n            image_ids.tolist()\n        )\n\n        predicted_classes.extend(\n            predictions.cpu().tolist()\n        )\n\n        # Explicitly release GPU tensors\n        del images\n        del outputs\n        del predictions\n\n        if batch_number % 100 == 0:\n            torch.cuda.empty_cache()\n\n            print(\n                f\"Processed batch \"\n                f\"{batch_number}/{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(f\"Prediction time: {prediction_time:.2f} seconds\")\n\nassert len(predicted_classes) == len(test_dataset)\nassert len(predicted_ids) == len(test_dataset)\n\nprint(\"All Kaggle test images were processed successfully.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"predicted_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\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(\n    \"Submission created:\",\n    SUBMISSION_PATH\n)\n\nprint(\n    \"Submission shape:\",\n    submission.shape\n)\n\ndisplay(\n    submission.head()\n)\n\ndisplay(\n    submission.tail()\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"expected_labels = set(\n    class_names\n)\n\nsubmission_labels = set(\n    submission[\"label\"].unique()\n)\n\nprint(\"SUBMISSION VERIFICATION\")\nprint(\"=\" * 45)\n\nprint(\n    \"Rows:\",\n    len(submission)\n)\n\nprint(\n    \"Columns:\",\n    submission.columns.tolist()\n)\n\nprint(\n    \"Missing values:\",\n    submission.isna().sum().sum()\n)\n\nprint(\n    \"Duplicate IDs:\",\n    submission[\"id\"].duplicated().sum()\n)\n\nprint(\n    \"Smallest ID:\",\n    submission[\"id\"].min()\n)\n\nprint(\n    \"Largest ID:\",\n    submission[\"id\"].max()\n)\n\nprint(\n    \"Predicted classes:\",\n    sorted(submission_labels)\n)\n\nassert len(submission) == 300000\n\nassert (\n    submission.columns.tolist() ==\n    [\"id\", \"label\"]\n)\n\nassert (\n    submission.isna().sum().sum() ==\n    0\n)\n\nassert (\n    submission[\"id\"].duplicated().sum() ==\n    0\n)\n\nassert (\n    submission_labels.issubset(\n        expected_labels\n    )\n)\n\nassert submission[\"id\"].min() == 1\nassert submission[\"id\"].max() == 300000\n\nprint(\n    \"\\nSubmission verification: PASSED\"\n)\n\nprint(\n    \"The file is ready for Kaggle.\"\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"FINAL EXPERIMENT SUMMARY\")\nprint(\"=\" * 55)\n\nprint(\"Framework: PyTorch\")\nprint(\"Model: CustomCIFARNet\")\nprint(\"Architecture: Custom design\")\nprint(\"Optimizer: AdamW\")\n\nprint(\n    \"Scheduler: \"\n    \"Linear warmup + cosine annealing\"\n)\n\nprint(\"Advanced augmentation: CutMix\")\n\nprint(\n    \"Additional augmentation: \"\n    \"Random crop, horizontal flip, \"\n    \"RandAugment and random erasing\"\n)\n\nprint(\"Pretrained weights: None\")\n\nprint(\n    \"Initialization: \"\n    \"Random Kaiming initialization\"\n)\n\nprint(\n    \"Number of GPUs:\",\n    torch.cuda.device_count()\n)\n\nfor gpu_index in range(\n    torch.cuda.device_count()\n):\n    print(\n        f\"GPU {gpu_index}:\",\n        torch.cuda.get_device_name(\n            gpu_index\n        )\n    )\n\nprint(\n    \"Training images:\",\n    len(train_dataset)\n)\n\nprint(\n    \"Validation images:\",\n    len(validation_dataset)\n)\n\nprint(\n    \"Kaggle test images:\",\n    len(test_dataset)\n)\n\nprint(\n    \"Completed epochs:\",\n    EPOCHS\n)\n\nprint(\n    \"Best epoch:\",\n    checkpoint[\"epoch\"]\n)\n\nprint(\n    f\"Best validation accuracy: \"\n    f\"{confirmed_validation_accuracy:.2f}%\"\n)\n\nprint(\n    f\"Training time: \"\n    f\"{total_training_time:.2f} seconds\"\n)\n\nprint(\n    f\"Prediction time: \"\n    f\"{prediction_time:.2f} seconds\"\n)\n\nprint(\n    \"Submission file:\",\n    SUBMISSION_PATH\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}