{"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":"import os\nimport time\nimport random\nimport platform\nimport subprocess\nimport glob\n\n# Find how many CPU cores Kaggle gives us\nif hasattr(os, \"sched_getaffinity\"):\n    AVAILABLE_CPU_CORES = len(os.sched_getaffinity(0))\nelse:\n    AVAILABLE_CPU_CORES = os.cpu_count()\n\n# Configure common CPU libraries before importing PyTorch\nos.environ[\"OMP_NUM_THREADS\"] = str(AVAILABLE_CPU_CORES)\nos.environ[\"MKL_NUM_THREADS\"] = str(AVAILABLE_CPU_CORES)\n\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\n\nfrom PIL import Image\nfrom torch.utils.data import Dataset, DataLoader, random_split\nfrom torchvision import transforms\n\n# Make the experiment repeatable\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# Configure PyTorch to use all available CPU cores\ntorch.set_num_threads(AVAILABLE_CPU_CORES)\n\ntry:\n    torch.set_num_interop_threads(\n        min(4, AVAILABLE_CPU_CORES)\n    )\nexcept RuntimeError:\n    # This may appear if the cell is run again without restarting\n    pass\n\nprint(\"PyTorch version:\", torch.__version__)\nprint(\"Python version:\", platform.python_version())\n\nprint(\"\\nCPU information\")\nprint(\"----------------\")\nprint(\"Available logical CPU cores:\", AVAILABLE_CPU_CORES)\nprint(\"CPU cores configured for PyTorch:\", torch.get_num_threads())\nprint(\"PyTorch inter-operation threads:\", torch.get_num_interop_threads())\n\nprint(\"\\nGPU information\")\nprint(\"----------------\")\nprint(\"CUDA available:\", torch.cuda.is_available())\nprint(\"Number of GPUs:\", torch.cuda.device_count())\n\nfor i in range(torch.cuda.device_count()):\n    print(f\"GPU {i}: {torch.cuda.get_device_name(i)}\")\n\nif torch.cuda.is_available():\n    subprocess.run([\"nvidia-smi\"])\nelse:\n    print(\"\\nThe benchmark is running on the CPU.\")\n    print(\n        f\"PyTorch is configured to use all \"\n        f\"{torch.get_num_threads()} available CPU cores.\"\n    )","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:16:32.260093Z","iopub.execute_input":"2026-09-07T13:16:32.260461Z","iopub.status.idle":"2026-09-07T13:16:32.292712Z","shell.execute_reply.started":"2026-09-07T13:16:32.260422Z","shell.execute_reply":"2026-09-07T13:16:32.291778Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BATCH_SIZE = 128\nEPOCHS = 5\nLEARNING_RATE = 0.001\n\n# DataLoader workers help prepare images while the model is training\nNUM_WORKERS = max(1, min(4, AVAILABLE_CPU_CORES))\n\n# Automatically select a GPU if one is available\ndevice = torch.device(\n    \"cuda:0\" if torch.cuda.is_available() else \"cpu\"\n)\n\nprint(\"Benchmark settings\")\nprint(\"------------------\")\nprint(\"Selected device:\", device)\nprint(\"Batch size:\", BATCH_SIZE)\nprint(\"Epochs:\", EPOCHS)\nprint(\"Learning rate:\", LEARNING_RATE)\nprint(\"DataLoader workers:\", NUM_WORKERS)\nprint(\"Available CPU cores:\", AVAILABLE_CPU_CORES)\nprint(\"PyTorch CPU threads:\", torch.get_num_threads())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:16:32.294191Z","iopub.execute_input":"2026-09-07T13:16:32.294482Z","iopub.status.idle":"2026-09-07T13:16:32.301061Z","shell.execute_reply.started":"2026-09-07T13:16:32.294455Z","shell.execute_reply":"2026-09-07T13:16:32.300084Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Find the CIFAR-10 competition files attached under Kaggle Input\ntrain_archives = glob.glob(\n    \"/kaggle/input/**/train.7z\",\n    recursive=True\n)\n\nlabel_files = glob.glob(\n    \"/kaggle/input/**/trainLabels.csv\",\n    recursive=True\n)\n\nif not train_archives:\n    raise FileNotFoundError(\n        \"train.7z was not found. Add the CIFAR-10 competition \"\n        \"dataset under Kaggle Input.\"\n    )\n\nif not label_files:\n    raise FileNotFoundError(\n        \"trainLabels.csv was not found. Add the CIFAR-10 \"\n        \"competition dataset under Kaggle Input.\"\n    )\n\ntrain_archive = train_archives[0]\nlabels_file = label_files[0]\n\nextract_directory = \"/kaggle/working/cifar10\"\n\n# Check whether all 50,000 images have already been extracted\nexisting_images = glob.glob(\n    f\"{extract_directory}/**/*.png\",\n    recursive=True\n)\n\nif len(existing_images) < 50000:\n    print(\"Extracting the attached CIFAR-10 dataset...\")\n\n    os.makedirs(extract_directory, exist_ok=True)\n\n    subprocess.run(\n        [\n            \"7z\",\n            \"x\",\n            train_archive,\n            f\"-o{extract_directory}\",\n            \"-y\"\n        ],\n        check=True,\n        stdout=subprocess.DEVNULL\n    )\n\n    print(\"Extraction completed.\")\nelse:\n    print(\"The dataset is already extracted in this session.\")\n\n# Find the folder containing 1.png, 2.png, and the other images\nfirst_image_matches = glob.glob(\n    f\"{extract_directory}/**/1.png\",\n    recursive=True\n)\n\nif not first_image_matches:\n    raise FileNotFoundError(\n        \"The images could not be found after extraction.\"\n    )\n\nimage_directory = os.path.dirname(first_image_matches[0])\n\nprint(\"Training archive:\", train_archive)\nprint(\"Labels file:\", labels_file)\nprint(\"Image directory:\", image_directory)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:16:32.302421Z","iopub.execute_input":"2026-09-07T13:16:32.302777Z","iopub.status.idle":"2026-09-07T13:16:32.479495Z","shell.execute_reply.started":"2026-09-07T13:16:32.302739Z","shell.execute_reply":"2026-09-07T13:16:32.478595Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Convert each image into a PyTorch tensor and normalize its colours\ntransform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=(0.4914, 0.4822, 0.4465),\n        std=(0.2470, 0.2435, 0.2616)\n    )\n])\n\n# Read the labels\nlabels_dataframe = pd.read_csv(labels_file)\n\n# Create a number for each class name\nclasses = sorted(labels_dataframe[\"label\"].unique())\n\nclass_to_index = {\n    class_name: index\n    for index, class_name in enumerate(classes)\n}\n\n\nclass KaggleCIFAR10Dataset(Dataset):\n    def __init__(self, dataframe, image_directory, transform=None):\n        self.image_directory = image_directory\n        self.transform = transform\n\n        self.image_ids = dataframe[\"id\"].tolist()\n        self.labels = [\n            class_to_index[label]\n            for label in dataframe[\"label\"].tolist()\n        ]\n\n    def __len__(self):\n        return len(self.image_ids)\n\n    def __getitem__(self, index):\n        image_id = self.image_ids[index]\n        label = self.labels[index]\n\n        image_path = os.path.join(\n            self.image_directory,\n            f\"{image_id}.png\"\n        )\n\n        image = Image.open(image_path).convert(\"RGB\")\n\n        if self.transform is not None:\n            image = self.transform(image)\n\n        return image, label\n\n\n# Create the complete dataset\nfull_dataset = KaggleCIFAR10Dataset(\n    labels_dataframe,\n    image_directory,\n    transform\n)\n\n# Use the same random split on every device\nsplit_generator = torch.Generator().manual_seed(SEED)\n\ntrain_dataset, test_dataset = random_split(\n    full_dataset,\n    [40000, 10000],\n    generator=split_generator\n)\n\n# Prepare the batches\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=True,\n    num_workers=NUM_WORKERS,\n    pin_memory=torch.cuda.is_available()\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=False,\n    num_workers=NUM_WORKERS,\n    pin_memory=torch.cuda.is_available()\n)\n\nprint(\"Dataset prepared\")\nprint(\"----------------\")\nprint(\"Training images:\", len(train_dataset))\nprint(\"Test images:\", len(test_dataset))\nprint(\"Number of classes:\", len(classes))\nprint(\"Classes:\", classes)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:16:32.481265Z","iopub.execute_input":"2026-09-07T13:16:32.481517Z","iopub.status.idle":"2026-09-07T13:16:32.525871Z","shell.execute_reply.started":"2026-09-07T13:16:32.481496Z","shell.execute_reply":"2026-09-07T13:16:32.524989Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CIFAR10CNN(nn.Module):\n    def __init__(self):\n        super().__init__()\n\n        # These layers learn visual features from the images\n        self.features = nn.Sequential(\n            nn.Conv2d(\n                in_channels=3,\n                out_channels=32,\n                kernel_size=3,\n                padding=1\n            ),\n            nn.ReLU(),\n\n            nn.Conv2d(\n                in_channels=32,\n                out_channels=32,\n                kernel_size=3,\n                padding=1\n            ),\n            nn.ReLU(),\n\n            nn.MaxPool2d(kernel_size=2),\n\n            nn.Conv2d(\n                in_channels=32,\n                out_channels=64,\n                kernel_size=3,\n                padding=1\n            ),\n            nn.ReLU(),\n\n            nn.Conv2d(\n                in_channels=64,\n                out_channels=64,\n                kernel_size=3,\n                padding=1\n            ),\n            nn.ReLU(),\n\n            nn.MaxPool2d(kernel_size=2),\n\n            nn.Conv2d(\n                in_channels=64,\n                out_channels=128,\n                kernel_size=3,\n                padding=1\n            ),\n            nn.ReLU(),\n\n            nn.MaxPool2d(kernel_size=2)\n        )\n\n        # These layers choose one of the 10 classes\n        self.classifier = nn.Sequential(\n            nn.Flatten(),\n            nn.Linear(128 * 4 * 4, 256),\n            nn.ReLU(),\n            nn.Linear(256, 10)\n        )\n\n    def forward(self, x):\n        x = self.features(x)\n        x = self.classifier(x)\n        return x\n\n\nmodel = CIFAR10CNN()\n\n# Use both GPUs at the same time when two GPUs are available\nif torch.cuda.device_count() >= 2:\n    print(\"Using two GPUs simultaneously with DataParallel.\")\n    model = nn.DataParallel(\n        model,\n        device_ids=[0, 1]\n    )\nelse:\n    print(\"Using one computing device.\")\n\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\n\noptimizer = optim.Adam(\n    model.parameters(),\n    lr=LEARNING_RATE\n)\n\nprint(\"\\nModel created successfully.\")\nprint(\"Model device:\", device)\nif torch.cuda.is_available():\n    print(\"Number of GPUs used:\", torch.cuda.device_count())\nelse:\n    print(\"Computing device used: CPU\")\n    print(\"CPU cores used:\", torch.get_num_threads())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:57:07.687272Z","iopub.execute_input":"2026-09-07T13:57:07.688566Z","iopub.status.idle":"2026-09-07T13:57:07.715295Z","shell.execute_reply.started":"2026-09-07T13:57:07.688492Z","shell.execute_reply":"2026-09-07T13:57:07.714366Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"epoch_times = []\n\n# Wait for any earlier GPU work to finish before starting the timer\nif torch.cuda.is_available():\n    torch.cuda.synchronize()\n\ntraining_start = time.perf_counter()\n\nfor epoch in range(EPOCHS):\n    model.train()\n\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    if torch.cuda.is_available():\n        torch.cuda.synchronize()\n\n    epoch_start = time.perf_counter()\n\n    for images, labels in train_loader:\n        images = images.to(\n            device,\n            non_blocking=True\n        )\n\n        labels = labels.to(\n            device,\n            non_blocking=True\n        )\n\n        # Remove gradients from the previous batch\n        optimizer.zero_grad()\n\n        # Make predictions\n        outputs = model(images)\n\n        # Calculate the model's error\n        loss = criterion(outputs, labels)\n\n        # Update the model\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item() * images.size(0)\n\n        predictions = outputs.argmax(dim=1)\n        correct += (predictions == labels).sum().item()\n        total += labels.size(0)\n\n    # Wait until the GPU finishes before stopping the timer\n    if torch.cuda.is_available():\n        torch.cuda.synchronize()\n\n    epoch_time = time.perf_counter() - epoch_start\n    epoch_times.append(epoch_time)\n\n    epoch_loss = running_loss / total\n    epoch_accuracy = 100 * correct / total\n\n    print(\n        f\"Epoch {epoch + 1}/{EPOCHS} | \"\n        f\"Loss: {epoch_loss:.4f} | \"\n        f\"Accuracy: {epoch_accuracy:.2f}% | \"\n        f\"Time: {epoch_time:.2f} seconds\"\n    )\n\nif torch.cuda.is_available():\n    torch.cuda.synchronize()\n\ntotal_training_time = time.perf_counter() - training_start\naverage_epoch_time = sum(epoch_times) / len(epoch_times)\n\nprint(\"\\nTraining completed\")\nprint(\"------------------\")\nprint(f\"Total training time: {total_training_time:.2f} seconds\")\nprint(f\"Average time per epoch: {average_epoch_time:.2f} seconds\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:16:32.550833Z","iopub.execute_input":"2026-09-07T13:16:32.551184Z","iopub.status.idle":"2026-09-07T13:24:45.60595Z","shell.execute_reply.started":"2026-09-07T13:16:32.551156Z","shell.execute_reply":"2026-09-07T13:24:45.604887Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\n\ntest_loss = 0.0\ncorrect = 0\ntotal = 0\n\nevaluation_start = time.perf_counter()\n\nwith torch.no_grad():\n    for images, labels in test_loader:\n        images = images.to(\n            device,\n            non_blocking=True\n        )\n\n        labels = labels.to(\n            device,\n            non_blocking=True\n        )\n\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n\n        test_loss += loss.item() * images.size(0)\n\n        predictions = outputs.argmax(dim=1)\n        correct += (predictions == labels).sum().item()\n        total += labels.size(0)\n\nif torch.cuda.is_available():\n    torch.cuda.synchronize()\n\nevaluation_time = time.perf_counter() - evaluation_start\n\nfinal_test_loss = test_loss / total\nfinal_test_accuracy = 100 * correct / total\n\nprint(\"Final benchmark results\")\nprint(\"=======================\")\n\nif torch.cuda.is_available():\n    print(\"Hardware type: GPU\")\n    print(\"Number of GPUs available:\", torch.cuda.device_count())\n\n    for i in range(torch.cuda.device_count()):\n        print(f\"GPU {i}: {torch.cuda.get_device_name(i)}\")\n\n    if torch.cuda.device_count() >= 2:\n        print(\"Multi-GPU method: PyTorch DataParallel\")\n        print(\"Both GPUs were used simultaneously.\")\nelse:\n    print(\"Hardware type: CPU\")\n    print(\"Available logical CPU cores:\", AVAILABLE_CPU_CORES)\n    print(\"CPU cores configured for PyTorch:\", torch.get_num_threads())\n    print(\"All available CPU cores were configured for the benchmark.\")\n\nprint(\"\\nDataset: CIFAR-10\")\nprint(\"Training images:\", len(train_dataset))\nprint(\"Test images:\", len(test_dataset))\nprint(\"Epochs:\", EPOCHS)\nprint(\"Batch size:\", BATCH_SIZE)\n\nprint(f\"\\nTotal training time: {total_training_time:.2f} seconds\")\nprint(f\"Average time per epoch: {average_epoch_time:.2f} seconds\")\nprint(f\"Evaluation time: {evaluation_time:.2f} seconds\")\nprint(f\"Test loss: {final_test_loss:.4f}\")\nprint(f\"Test accuracy: {final_test_accuracy:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-07T13:24:45.607688Z","iopub.execute_input":"2026-09-07T13:24:45.608067Z","iopub.status.idle":"2026-09-07T13:24:56.943925Z","shell.execute_reply.started":"2026-09-07T13:24:45.608023Z","shell.execute_reply":"2026-09-07T13:24:56.942973Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}}]}