{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":136640,"databundleVersionId":16417516}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.status.busy":"2026-04-03T12:56:47.592311Z","iopub.execute_input":"2026-04-03T12:56:47.592603Z","iopub.status.idle":"2026-04-03T12:56:47.862798Z","shell.execute_reply.started":"2026-04-03T12:56:47.592576Z","shell.execute_reply":"2026-04-03T12:56:47.861906Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, Dataset\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\n\nROOT_PATH = \"/kaggle/input/competitions/sjtu-2026spring-cnn/\"\n\nclass MyDataset(Dataset):\n    def __init__(self, x, y):\n        self._x = x\n        self._y = y\n\n    @staticmethod\n    def _to_chw(image: torch.Tensor) -> torch.Tensor:\n        if image.ndim == 1:\n            if image.numel() == 28 * 28:\n                return image.view(1, 28, 28)\n            if image.numel() == 32 * 32:\n                return image.view(1, 32, 32)\n            if image.numel() == 3 * 32 * 32:\n                return image.view(3, 32, 32)\n            side = int(image.numel() ** 0.5)\n            if side * side == image.numel():\n                return image.view(1, side, side)\n            raise ValueError(f\"Unsupported flat image length: {image.numel()}\")\n\n        if image.ndim == 2:\n            return image.unsqueeze(0)\n\n        if image.ndim == 3 and image.shape[0] not in (1, 3) and image.shape[-1] in (1, 3):\n            return image.permute(2, 0, 1)\n\n        return image\n\n    @staticmethod\n    def _preprocess(image: torch.Tensor) -> torch.Tensor:\n        # 支持单张图像 [C, H, W] 和批量图像 [B, C, H, W]\n        if image.ndim == 4:\n            return torch.stack([MyDataset._preprocess(item) for item in image], dim=0)\n\n        image = MyDataset._to_chw(image)\n\n        # 像素值统一到 [0, 1]\n        if image.max() > 1.0:\n            image = image / 255.0\n\n        # 按通道标准化：1通道走CIFAR10统计量，3通道走CIFAR10统计量\n        if image.shape[0] == 1:\n            mean = torch.tensor([0.1307], dtype=image.dtype, device=image.device).view(-1, 1, 1)\n            std = torch.tensor([0.3081], dtype=image.dtype, device=image.device).view(-1, 1, 1)\n        else:\n            mean = torch.tensor([0.4914, 0.4822, 0.4465], dtype=image.dtype, device=image.device).view(-1, 1, 1)\n            std = torch.tensor([0.2470, 0.2435, 0.2616], dtype=image.dtype, device=image.device).view(-1, 1, 1)\n\n        image = (image - mean) / (std + 1e-8)\n        return image\n\n    def __getitem__(self, idx):\n        image = torch.tensor(self._x[idx], dtype=torch.float32)\n        label = torch.tensor(self._y[idx], dtype=torch.long)\n\n        # 先统一成 [C, H, W]\n        image = self._to_chw(image)\n\n        image = self._preprocess(image)\n        return image, label\n\n    def __len__(self):\n        return len(self._x)\n\ndef prepare_data_loader(\n    path: str,\n    ratio: float = None,\n    train_batch_size: int = 1,\n    is_train: bool = True,\n) -> dict:\n    \"\"\"\n    参数:\n        path (str): .npz格式的数据集文件路径\n        ratio (float): 训练集比例\n        train_batch_size (int): 批次大小\n        num_workers (int): 数据加载的工作进程数\n    返回:\n        dict: 包含训练和测试数据加载器的字典\n    \"\"\"\n    print(\"开始加载数据...\")  # 添加调试信息\n    train_dataset = torch.load(path, weights_only = False)\n    train_dataset = MyDataset(train_dataset[0], train_dataset[1])\n    if is_train:\n        train_dataset, val_dataset = torch.utils.data.random_split(train_dataset, [int(len(train_dataset) * ratio), len(train_dataset) - int(len(train_dataset) * ratio)])\n        train_loader = torch.utils.data.DataLoader(\n                                                    dataset=train_dataset, \n                                                batch_size=train_batch_size, \n                                                shuffle=True,\n                                                drop_last=True,\n                                                pin_memory=True  \n                                                )\n        val_loader = torch.utils.data.DataLoader(\n                                                dataset=val_dataset, \n                                                batch_size=train_batch_size, \n                                                shuffle=False,\n                                                pin_memory=True  \n        )\n        print(\"数据加载器创建完成\")  # 添加调试信息\n\n        return {\"train\": train_loader, \"val\": val_loader}\n    else:\n        test_loader = torch.utils.data.DataLoader(\n                                                dataset=train_dataset, \n                                                batch_size=train_batch_size, \n                                                shuffle=False,\n                                                pin_memory=True  \n        )\n        print(\"测试数据加载器创建完成\")  # 添加调试信息\n\n        return {\"test\": test_loader}\n","metadata":{"execution":{"iopub.status.busy":"2026-04-03T12:56:47.863689Z","iopub.execute_input":"2026-04-03T12:56:47.864075Z","iopub.status.idle":"2026-04-03T12:56:51.567232Z","shell.execute_reply.started":"2026-04-03T12:56:47.864049Z","shell.execute_reply":"2026-04-03T12:56:51.566633Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SimpleCNN(nn.Module):\n    \"\"\"\n    可配置的CNN模型\n    \n    参数:\n        in_channels (int): 输入通道数\n        num_classes (int): 分类类别数\n        conv_layers (list): 每个卷积层的输出通道数列表\n        fc_layers (list): 每个全连接层的输出维度列表\n        kernel_size (int): 卷积核大小\n        dropout_rate (float): Dropout比率\n    \"\"\"\n    def __init__(\n        self,\n        in_channels: int = 3,\n        num_classes: int = 10,\n        conv_layers: list = [32, 64],  # 默认两层卷积\n        fc_layers: list = [128, 64],   # 默认两层全连接\n        kernel_size: int = 3,\n        dropout_rate: float = 0.1\n    ):\n        super().__init__()\n        \n        # 构建卷积层\n        self.conv_blocks = nn.ModuleList()\n        current_channels = in_channels\n        \n        for i, out_channels in enumerate(conv_layers):\n            conv_block = nn.Sequential(\n                nn.Conv2d(\n                    in_channels=current_channels,\n                    out_channels=out_channels,\n                    kernel_size=kernel_size,\n                    stride=1,\n                    padding='same'\n                ),\n                # nn.BatchNorm2d(out_channels), 可选择是否添加BatchNorm层\n                nn.ReLU(),\n                nn.MaxPool2d(kernel_size=2)\n            )\n            self.conv_blocks.append(conv_block)\n            current_channels = out_channels\n           \n            \n        # 计算展平后的特征维度\n        # 每经过一次MaxPool2d或AvgPool2d，特征图尺寸减半\n        feature_size = current_channels * (32 // (2 ** len(conv_layers))) ** 2\n        \n        # 构建全连接层\n        self.fc_blocks = nn.ModuleList()\n        current_dim = feature_size\n        \n        for fc_dim in fc_layers:\n            fc_block = nn.Sequential(\n                nn.Linear(current_dim, fc_dim),\n                nn.ReLU(),\n                nn.Dropout(dropout_rate)\n            )\n            self.fc_blocks.append(fc_block)\n            current_dim = fc_dim\n            \n        # 输出层\n        self.output_layer = nn.Linear(current_dim, num_classes)\n\n    def forward(self, x):\n         # 通过所有卷积层\n         for conv_block in self.conv_blocks:\n             x = conv_block(x)\n    \n         # 展平\n         x = torch.flatten(x, start_dim=1)\n    \n         # 通过所有全连接层\n         for fc_block in self.fc_blocks:\n              x = fc_block(x)\n        \n         # 输出层\n         x = self.output_layer(x)\n         return x \n        \n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-03T12:56:51.568881Z","iopub.execute_input":"2026-04-03T12:56:51.569250Z","iopub.status.idle":"2026-04-03T12:56:51.576931Z","shell.execute_reply.started":"2026-04-03T12:56:51.569205Z","shell.execute_reply":"2026-04-03T12:56:51.576236Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.optim as optim\nimport matplotlib.pyplot as plt\nimport time \n\nloss_fn = nn.CrossEntropyLoss()\ntime0 = time.time()\ndef train_step(\n    model: nn.Module, optimizer, batch: dict, device: torch.device\n):\n    \"\"\"\n    单步训练\n    \"\"\"\n    batch_images, labels = batch\n    batch_images = batch_images.to(device)\n    labels = labels.to(device)\n        \n    optimizer.zero_grad()\n\n    logits = model(batch_images) # 模型正向过程\n        \n    loss = loss_fn(logits, labels) # 计算总损失\n        \n    loss.backward() # 反向传播\n        \n    torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # 添加梯度裁剪，防止梯度爆炸\n        \n    optimizer.step()\n\n    return loss.item(), logits, labels\n        \n\ndef eval_step(model: nn.Module, batch: dict, device: torch.device):\n    # 单步评估\n    model.eval()\n    with torch.no_grad():\n        batch_images, labels = batch\n        batch_images = batch_images.to(device)\n        labels = labels.to(device) \n        logits = model(batch_images) # 模型正向过程\n        loss = loss_fn(logits, labels) # 计算总损失\n        return loss.item(), logits, labels\n    \ndef train_per_epoch(\n    model: nn.Module,\n    optimizer: optim.Optimizer,\n    batch_size: int,\n    train_loader: DataLoader,\n    device: torch.device,\n    global_batch_offset: int,\n):\n    model.train()\n    num_data = len(train_loader.dataset)\n    num_batches = len(train_loader)\n    correct = 0\n    total_loss = 0\n    print(f\"开始训练 - 总样本数: {num_data}\")\n    print(f\"总批次数: {len(train_loader)}\")\n\n    train_loss_list = []\n    train_acc_list = []\n\n    test_loss_list = []\n    test_acc_list = []\n    test_batch_idx = []\n    for batch_idx, batch in enumerate(train_loader):\n        loss, logits, labels = train_step(model, optimizer, batch, device)\n        total_loss += loss\n        \n        # 计算当前批次的预测值\n        _, predicted = torch.max(logits, 1)\n        correct += (predicted == labels).sum().item()\n        \n        # ✅ 计算当前批次的准确率\n        batch_correct = (predicted == labels).sum().item()\n        batch_total = len(labels)\n        batch_acc = batch_correct / batch_total  # 当前批次的准确率\n    \n        # 每5个批次打印一次\n        if batch_idx % 5 == 0:\n            current = batch_idx * batch_size + len(batch[0])\n            print(f\"批次 {batch_idx} Train: Loss: {loss:>6.4f}, Acc: {100*batch_acc:.4f}%, 进度: {current:>5d}/{num_data:>5d}\")            \n            print(f\"批次 {batch_idx}: Finished in {time.time() - time0:.2f} seconds\")\n            \n        if batch_idx % 15 == 0:\n            test_loss, test_acc = val_per_epoch(model, val_loader, device)\n            test_loss_list.append(test_loss) \n            test_acc_list.append(test_acc)\n            test_batch_idx.append(global_batch_offset + batch_idx)\n            print(f\"批次 {batch_idx} Val : Loss: {test_loss:>6.4f}, Acc: {100*test_acc:.4f}%\")\n            print(f\"批次 {batch_idx}: Finished in {time.time() - time0:.2f} seconds\")\n        \n        # ✅ 记录每个批次的 loss 和 acc\n        train_loss_list.append(loss) \n        train_acc_list.append(batch_acc)\n        \n    test_loss, test_acc = val_per_epoch(model, val_loader, device)\n    test_loss_list.append(test_loss) \n    test_acc_list.append(test_acc)\n    test_batch_idx.append(global_batch_offset + batch_idx)\n    print(f\"批次 {batch_idx} Validation : Loss: {test_loss:>6.4f}, Acc: {100*test_acc:.4f}%\")\n    print(f\"批次 {batch_idx}: Finished in {time.time() - time0:.2f} seconds\")\n    accuracy = correct / num_data\n    avg_loss = total_loss / num_batches\n    print(f\"Train Error: \\n Accuracy: {(100*accuracy):>0.1f}%, Avg loss: {avg_loss:>.8f} \\n\")\n    return train_loss_list, train_acc_list, test_loss_list, test_acc_list, test_batch_idx\n        \ndef val_per_epoch(\n    model: nn.Module,\n    val_loader: DataLoader,\n    device: torch.device,\n):\n    \"\"\"\n    每轮测试\n    \"\"\"\n    model.eval()\n    total_loss = 0.0\n    correct = 0\n    num_batches = len(val_loader)\n    num_data = len(val_loader.dataset)\n    with torch.no_grad():\n        for batch in val_loader:\n            loss, logits, labels = eval_step(model, batch, device)\n            total_loss += loss\n            _, predicted = torch.max(logits, 1)\n            correct += (predicted == labels).sum().item()\n\n    avg_loss = total_loss / num_batches\n    accuracy = correct / num_data\n    return avg_loss, accuracy\n\ndef draw_loss_accuracy_curves(train_losses, train_acc_list, test_batch_idx_list, test_loss_list, test_acc_list):\n    plt.figure(figsize=(18, 5))\n\n    # 绘制 loss 曲线\n    plt.subplot(1, 3, 1)\n    plt.plot(train_losses, label='Train Loss', alpha=0.5)\n    plt.plot(test_batch_idx_list, test_loss_list, label='Validation Loss', marker='o', markersize=3)\n    plt.title('Loss vs Batch Index')\n    plt.xlabel('Batch Index')\n    plt.ylabel('Loss')\n    plt.legend()\n\n    # 绘制 train acc 和 val acc\n    plt.subplot(1, 2, 2)\n    plt.plot(train_acc_list, label='Train Acc', alpha=0.5)\n    plt.plot(test_batch_idx_list, test_acc_list, label='Validation Acc', marker='o', markersize=3)\n    plt.title('Accuracy vs Batch Index')\n    plt.xlabel('Batch Index')\n    plt.ylabel('Accuracy')\n    plt.legend()\n\n    # # 绘制 validation accuracy 单独\n    # plt.subplot(1, 3, 3)\n    # plt.plot(test_batch_idx_list, test_acc_list, label='Validation Accuracy', marker='o', markersize=3)\n    # plt.title('Validation Accuracy vs Batch Index')\n    # plt.xlabel('Batch Index')\n    # plt.ylabel('Accuracy')\n    # plt.legend()\n\n    plt.tight_layout()\n    plt.show()\n\n\ndef controller(\n    train_loader,\n    val_loader,\n    seed: int,\n    # CNN特有参数\n    in_channels: int = None,\n    conv_layers: list = None,\n    kernel_size: int = None,\n    fc_layers  : list = None,\n    # 通用参数\n    num_classes: int = 10,\n    dropout_rate: float = 0.1,\n    ratio: float = 0.8,\n    train_batch_size: int = 64,\n    num_workers: int = 4,\n    epochs: int = 10,\n    learning_rate: float = 0.001,\n    weight_decay: float = 0.004\n):\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    torch.manual_seed(seed)\n    model = SimpleCNN(\n            in_channels=in_channels,\n            num_classes=num_classes,\n            conv_layers=conv_layers,\n            fc_layers=fc_layers,  \n            kernel_size=kernel_size,\n            dropout_rate=dropout_rate\n        ).to(device)\n\n    optimizer = optim.Adam(model.parameters(), lr=learning_rate)\n    scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)\n    # print(f\"Total Epoch: {epochs}\")\n    best_acc = 0.0\n    train_loss_list = []\n    train_acc_list = []\n    test_loss_list = []\n    test_acc_list = []\n    test_batch_idx_list = []\n\n    # 初始 validation（对应 batch index 0）\n    test_loss, test_acc = val_per_epoch(model, val_loader, device)\n    test_loss_list.append(test_loss)\n    test_acc_list.append(test_acc)\n    test_batch_idx_list.append(0)\n\n    num_batches_per_epoch = len(train_loader)\n\n    for epoch in range(epochs):\n        print(f\"Epoch {epoch + 1}/{epochs} strated in {time.time() - time0:.2f} seconds\")\n        global_batch_offset = epoch * num_batches_per_epoch\n\n        train_loss, train_acc, test_loss, test_acc, test_batch_idx = train_per_epoch(\n            model, optimizer, train_batch_size, train_loader, device, global_batch_offset\n        )\n        scheduler.step()\n        if test_acc[-1] > best_acc:\n            best_acc = test_acc[-1]\n            torch.save(model.state_dict(), 'best_model.pth')\n        train_loss_list.extend(train_loss)\n        train_acc_list.extend(train_acc)\n        test_loss_list.extend(test_loss)\n        test_acc_list.extend(test_acc)\n        test_batch_idx_list.extend(test_batch_idx)\n        \n        print(f\"Epoch {epoch + 1} Finished in {time.time() - time0:.2f} seconds\")\n        print(f\"Validation accuracy: {100*test_acc[-1]:.2f}%\")\n        draw_loss_accuracy_curves(train_loss_list, train_acc_list, test_batch_idx_list, test_loss_list, test_acc_list)\n        \n    print(f\"Training completed! Best accuracy: {100*best_acc:.2f}%\")\n    \n    return model\n\ndef show_sample_images(sample, title):\n    images, labels = sample\n    images = images.cpu().numpy()\n    labels = labels.cpu().numpy()\n    \n    plt.figure(figsize=(8, 8))\n    for i in range(9):\n        plt.subplot(3, 3, i + 1)\n        if images.shape[1] == 1:  # 灰度图像\n            plt.imshow(images[i][0], cmap='gray')\n        else:  # 彩色图像\n            img = np.transpose(images[i], (1, 2, 0))  # 转换为HWC格式\n            img = (img * np.array([0.2470, 0.2435, 0.2616]) + np.array([0.4914, 0.4822, 0.4465])) * 255.0  # 反标准化\n            img = np.clip(img.astype(np.uint8), 0, 255)  # 确保像素值在有效范围内\n            plt.imshow(img)\n        plt.title(f\"Label: {labels[i]}\")\n        plt.axis('off')\n    plt.suptitle(title)\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-03T12:56:51.580028Z","iopub.execute_input":"2026-04-03T12:56:51.580205Z","iopub.status.idle":"2026-04-03T12:56:51.601884Z","shell.execute_reply.started":"2026-04-03T12:56:51.580189Z","shell.execute_reply":"2026-04-03T12:56:51.601188Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 随机数种子：确保每次运行代码得到相同的结果\n# 请同学把随机种子改成自己的学号，方便助教后期验证结果的真实性。\nseed = 【这里改成学号后八位】\n# 例如：\n# seed = 12345678\n# activation 是一个字符串，指定了隐藏层使用的激活函数类型。常见的激活函数包括 'relu'（修正线性单元）、'tanh'（双曲正切）和 'sigmoid'（S形函数）。例如，activation='relu' 表示使用ReLU激活函数。\nactivation = 'relu'\n# num_classes 是一个整数，表示分类任务中的类别数量。例如，对于CIFAR10数据集，num_classes=10，因为CIFAR10包含10个不同的类别。\nnum_classes = 10\n# dropout_rate 是一个浮点数，表示在训练过程中应用的Dropout正则化的比率。Dropout是一种防止过拟合的技术，通过在训练过程中随机丢弃神经元来实现。例如，dropout_rate=0.1 表示每个神经元有10%的概率被丢弃。\ndropout_rate = 0.2\n# train_batch_size 是一个整数，表示在训练过程中每个批次中包含的样本数量。较大的批次大小可以加速训练，但可能需要更多的内存。例如，train_batch_size=64 表示每个批次包含64个样本。\ntrain_batch_size: int = 1024\n# epochs 是一个整数，表示整个训练数据集要被训练的次数。每个epoch表示模型已经看过整个训练数据集一次。例如，epochs=10 表示模型将对整个训练数据集进行10次迭代。\nepochs: int = 1\n# learning_rate 是一个浮点数，表示模型在训练过程中调整参数的步长。较大的学习率可能导致训练不稳定，而较小的学习率可能导致训练过慢。例如，\nlearning_rate: float = 0.001\n# weight_decay 是一个浮点数，表示L2正则化的强度。L2正则化通过在损失函数中添加模型参数的平方和来防止过拟合。例如，\nweight_decay: float = 0.004\n# in_channels 是一个整数，表示输入图像的通道数。例如，对于CIFAR10数据集，in_channels=3\nin_channels: int = 3\n# conv_layers 是一个列表，定义了每个卷积层的输出通道数。例如，conv_layers=[32, 64] 表示有两个卷积层，第一层输出32个通道，第二层输出64个通道。\nconv_layers: list = [32, 64]\n# conv_layers: list = [4, 4]\n# kernel_size 是一个整数，表示卷积核的大小。例如，kernel_size=3\nkernel_size: int = 3\n# fc_layers 是一个列表，定义了每个全连接层的输出维度。例如，fc_layers=[128, 64] 表示有两个全连接层，第一层输出128维，第二层输出64维。\nfc_layers: list = [64, 64]\n# fc_layers: list = [5, 5]\n\n#ratio比率：训练集中用于训练的数据量/总数据量\n#比率0.8表示50000张图片中有40000张用于训练，10000用于测试\nratio = 0.8\n\nloader_dict = prepare_data_loader(path=Path(f\"{ROOT_PATH}/training.pt\"), ratio=ratio, train_batch_size=train_batch_size,)\ntrain_loader = loader_dict[\"train\"]\nval_loader = loader_dict[\"val\"]\n# 增加一个 train 和 val 的图像示意图\ntrain_sample = next(iter(train_loader))\nval_sample = next(iter(val_loader))\n\nshow_sample_images(train_sample, \"Train Sample Images\")\nshow_sample_images(val_sample, \"Validation Sample Images\")\n# print(f\"Start Total Epoch: {epochs}\")\nmodel = controller(\n    train_loader=train_loader,\n    val_loader=val_loader,\n    seed=seed,\n    in_channels=in_channels,\n    conv_layers=conv_layers,\n    kernel_size=kernel_size,\n    fc_layers=fc_layers,\n    num_classes=num_classes,\n    dropout_rate=dropout_rate,\n    ratio = ratio,\n    train_batch_size=train_batch_size,\n    epochs=epochs,  \n    learning_rate=learning_rate,\n    weight_decay = weight_decay\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-03T12:56:51.602729Z","iopub.execute_input":"2026-04-03T12:56:51.602908Z","iopub.status.idle":"2026-04-03T12:57:08.233342Z","shell.execute_reply.started":"2026-04-03T12:56:51.602886Z","shell.execute_reply":"2026-04-03T12:57:08.232699Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\ndef evaluater_with_dataloader_and_save(model, test_path, device, solution_path=None):\n    \"\"\"\n    使用 val_dataloader 计算准确率并可选择保存预测结果\n    \n    参数:\n    - model: 需要评估的模型\n    - val_loader: 测试数据加载器\n    - device: 计算设备 (CPU 或 GPU)\n    - solution_path: 保存预测结果的路径(可选)\n    \n    返回:\n    - accuracy: 模型在测试集上的准确率\n    \"\"\"\n    model.eval()  # 设置为评估模式\n    all_predictions = []\n    test_dataset = torch.load(test_path, weights_only = False)\n    \n    test_dataset = MyDataset(test_dataset, torch.ones(len(test_dataset), dtype=torch.long))  # 创建一个假的标签数组，实际不会使用\n    test_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size=128, shuffle=False, pin_memory=True)\n    all_ids = []\n    with torch.no_grad():\n        for batch_idx, (images, _) in enumerate(test_dataloader):\n            images = images.to(device)\n            logits = model(images)\n            predictions = torch.argmax(logits, dim=1).cpu().numpy()\n            all_predictions.extend(predictions)\n            all_ids.extend(range(batch_idx * 128, batch_idx * 128 + len(predictions)))\n    \n    # 保存预测结果\n    if solution_path is not None:\n        predictions_df = pd.DataFrame({\"ID\": all_ids, \"label\": all_predictions})\n        predictions_df.to_csv(solution_path, index=False)\n        print(f'预测结果已保存至 {solution_path}')\n\n# test_dataloader = prepare_data_loader(path=Path(f\"{ROOT_PATH}/test-shuffled-nolabel.pt\"),train_batch_size=128, is_train=False)['test']\nevaluater_with_dataloader_and_save(\n    model,\n    test_path=Path(f\"{ROOT_PATH}/test-shuffled-nolabel.pt\"),\n    solution_path=Path(\"submission.csv\"),\n    device = device\n    )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-03T12:57:08.234134Z","iopub.execute_input":"2026-04-03T12:57:08.234559Z","iopub.status.idle":"2026-04-03T12:57:10.409819Z","shell.execute_reply.started":"2026-04-03T12:57:08.234539Z","shell.execute_reply":"2026-04-03T12:57:10.409121Z"}},"outputs":[],"execution_count":null}]}