{"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":"none","dataSources":[{"sourceId":117377,"databundleVersionId":14038316,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 基线模型（PyTorch）— CIFAR-10 图像分类（CNN）\n\n本 Notebook 使用 `train.zip / train_labels.csv / test.zip` 进行训练与预测，并生成 `submission.csv`。\n你只需在**配置区域**调整少量超参数即可完成实验。图片文件会解压到工作目录后再加载。\n\n## 提交格式\n`submission.csv` 需包含两列：`Id,Label`，其中 `Id` 与测试图片文件名一致（不含扩展名），如 `00001`。","metadata":{}},{"cell_type":"code","source":"import os, zipfile, random\nfrom pathlib import Path\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom sklearn.model_selection import train_test_split","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T09:08:53.906771Z","iopub.execute_input":"2025-10-11T09:08:53.907389Z","iopub.status.idle":"2025-10-11T09:08:57.331829Z","shell.execute_reply.started":"2025-10-11T09:08:53.907366Z","shell.execute_reply":"2025-10-11T09:08:57.331193Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"COMP = \"cifar-image-classification\"  # 你的数据集路径\nINPUT_DIR = Path(f\"/kaggle/input/{COMP}\")\nTRAIN_DIR = INPUT_DIR / \"train\" / \"train\"\nTEST_DIR  = INPUT_DIR / \"test\" / \"test\"\n\nprint(\"✅ 检查路径：\")\nprint(\"TRAIN_DIR:\", TRAIN_DIR.exists(), TRAIN_DIR)\nprint(\"TEST_DIR:\", TEST_DIR.exists(), TEST_DIR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T09:09:04.158411Z","iopub.execute_input":"2025-10-11T09:09:04.159211Z","iopub.status.idle":"2025-10-11T09:09:04.167377Z","shell.execute_reply.started":"2025-10-11T09:09:04.159187Z","shell.execute_reply":"2025-10-11T09:09:04.166783Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== 读取标签文件 ====\ntrain_csv = pd.read_csv(INPUT_DIR/\"train_labels.csv\")  # Id,Label\ntrain_csv[\"path\"] = train_csv[\"Id\"].apply(lambda x: TRAIN_DIR / f\"{int(x):05d}.png\")\n\n# ==== 生成测试集 DataFrame ====\ntest_ids = sorted([p.stem for p in TEST_DIR.glob(\"*.png\")])\ntest_df = pd.DataFrame({\"Id\": test_ids})\ntest_df[\"path\"] = test_df[\"Id\"].apply(lambda x: TEST_DIR / f\"{int(x):05d}.png\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T09:09:07.690331Z","iopub.execute_input":"2025-10-11T09:09:07.690721Z","iopub.status.idle":"2025-10-11T09:09:08.059714Z","shell.execute_reply.started":"2025-10-11T09:09:07.690698Z","shell.execute_reply":"2025-10-11T09:09:08.058867Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== 数据增强 ====\ntransform_train = transforms.Compose([\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomCrop(32, padding=4),\n    transforms.ToTensor(),\n])\ntransform_test = transforms.Compose([\n    transforms.ToTensor(),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T09:09:10.867289Z","iopub.execute_input":"2025-10-11T09:09:10.867767Z","iopub.status.idle":"2025-10-11T09:09:10.871795Z","shell.execute_reply.started":"2025-10-11T09:09:10.867742Z","shell.execute_reply":"2025-10-11T09:09:10.871Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== 数据集类 ====\nclass ImageDataset(Dataset):\n    def __init__(self, df, transform=None, has_label=True):\n        self.df = df.reset_index(drop=True)\n        self.transform = transform\n        self.has_label = has_label\n    def __len__(self):\n        return len(self.df)\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img = Image.open(row[\"path\"]).convert(\"RGB\")\n        if self.transform:\n            img = self.transform(img)\n        if self.has_label:\n            return img, int(row[\"Label\"])  # 0~9\n        else:\n            return img, row[\"Id\"]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T09:09:14.72231Z","iopub.execute_input":"2025-10-11T09:09:14.722993Z","iopub.status.idle":"2025-10-11T09:09:14.728295Z","shell.execute_reply.started":"2025-10-11T09:09:14.722967Z","shell.execute_reply":"2025-10-11T09:09:14.727511Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===================== 配置（学生主要改这里） =====================\nSEED = 2025             # 随机种子，保证结果可复现（影响数据划分、初始化、shuffle 等）\nEPOCHS = 10             # 训练轮数（模型完整看一遍数据的次数，越多可能精度越高但训练更久）\nBATCH_SIZE = 128        # 每次训练读取的样本数量（越大训练越快但显存占用更高）\nLR = 1e-3               # 学习率（控制每次参数更新的幅度，太大会震荡，太小收敛慢）\nWEIGHT_DECAY = 1e-4     # 权重衰减（L2正则化系数，用于防止过拟合，使权重更平滑）\n# ================================================================","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T09:09:16.977237Z","iopub.execute_input":"2025-10-11T09:09:16.977498Z","iopub.status.idle":"2025-10-11T09:09:16.981676Z","shell.execute_reply.started":"2025-10-11T09:09:16.977479Z","shell.execute_reply":"2025-10-11T09:09:16.980806Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== 划分训练/验证集 ====\ntr_df, val_df = train_test_split(train_csv, test_size=0.1, random_state=SEED, stratify=train_csv[\"Label\"])\ntrain_ds = ImageDataset(tr_df, transform=transform_train, has_label=True)\nval_ds   = ImageDataset(val_df, transform=transform_test, has_label=True)\ntest_ds  = ImageDataset(test_df, transform=transform_test, has_label=False)\n\ntrain_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader   = DataLoader(val_ds,   batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\ntest_loader  = DataLoader(test_ds,  batch_size=BATCH_SIZE, shuffle=False, num_workers=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T09:09:18.844659Z","iopub.execute_input":"2025-10-11T09:09:18.845217Z","iopub.status.idle":"2025-10-11T09:09:18.874676Z","shell.execute_reply.started":"2025-10-11T09:09:18.845193Z","shell.execute_reply":"2025-10-11T09:09:18.873915Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"![卷积网络结构](/kaggle/input/cifar-image-classification/convnet.png)","metadata":{}},{"cell_type":"code","source":"# ======= CNN 模型 =======\nclass SimpleCNN(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.features = nn.Sequential(\n            nn.Conv2d(3, 32, 3, padding=1), nn.ReLU(), nn.BatchNorm2d(32), nn.MaxPool2d(2),\n            nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(), nn.BatchNorm2d(64), nn.MaxPool2d(2),\n            # nn.Conv2d(64, 128, 3, padding=1), nn.ReLU(), nn.BatchNorm2d(128), nn.MaxPool2d(2),  # 第三层卷积层\n        )\n        self.classifier = nn.Sequential(\n            nn.Flatten(),\n            nn.Linear(64*8*8, 128), nn.ReLU(), nn.Dropout(0.2),   # 如果打开第三层卷积层，就注释这一行，打开下一行\n            # nn.Linear(128*4*4, 128), nn.ReLU(), nn.Dropout(0.2),  \n            nn.Linear(128, 10)\n        )\n    def forward(self, x):\n        x = self.features(x)\n        x = self.classifier(x)\n        return x\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T09:09:29.058304Z","iopub.execute_input":"2025-10-11T09:09:29.058604Z","iopub.status.idle":"2025-10-11T09:09:29.064298Z","shell.execute_reply.started":"2025-10-11T09:09:29.058582Z","shell.execute_reply":"2025-10-11T09:09:29.063553Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🧠 模型整体结构\n\n`SimpleCNN` 是一个典型的用于 **CIFAR-10 图像分类** 的浅层卷积神经网络（Convolutional Neural Network, CNN）。\n结构分为两部分：\n\n1. **特征提取模块 (`self.features`)**：用若干卷积层 + 非线性激活 + 池化层提取图片的空间特征；\n2. **分类器模块 (`self.classifier`)**：将卷积输出展平为向量，通过全连接层映射到 10 个类别。\n\n输入图片大小：`3×32×32`（RGB 彩色图）\n输出维度：`10`（CIFAR-10 的十个类别）\n\n---\n\n## 🧩 详细层级说明\n\n### 🔹 1. 第一层卷积块\n\n```python\nnn.Conv2d(3, 32, 3, padding=1), \nnn.ReLU(),\nnn.BatchNorm2d(32),\nnn.MaxPool2d(2)\n```\n\n| 层                                | 参数解释                                             | 输出尺寸变化                    | 说明                        |\n| -------------------------------- | ------------------------------------------------ | ------------------------- | ------------------------- |\n| `nn.Conv2d(3, 32, 3, padding=1)` | 输入通道 3（RGB），输出通道 32，卷积核大小 3×3，`padding=1` 保持空间尺寸 | 从 `3×32×32` → `32×32×32`  | 提取局部 3×3 区域特征（颜色边缘、简单纹理）  |\n| `nn.ReLU()`                      | 无参数                                              | 不改变尺寸                     | 非线性激活函数，增加模型表达能力          |\n| `nn.BatchNorm2d(32)`             | 有 32 个可学习缩放/偏移参数                                 | 不改变尺寸                     | 对每个通道归一化，稳定训练、加快收敛        |\n| `nn.MaxPool2d(2)`                | 窗口大小 2×2，步长 2                                    | 从 `32×32×32` → `32×16×16` | 空间下采样，将每 2×2 区域取最大值，减少计算量 |\n\n---\n\n### 🔹 2. 第二层卷积块\n\n```python\nnn.Conv2d(32, 64, 3, padding=1),\nnn.ReLU(),\nnn.BatchNorm2d(64),\nnn.MaxPool2d(2)\n```\n\n| 层                                 | 参数解释                      | 输出尺寸变化                  | 说明                  |\n| --------------------------------- | ------------------------- | ----------------------- | ------------------- |\n| `nn.Conv2d(32, 64, 3, padding=1)` | 输入通道 32，输出通道 64，卷积核大小 3×3 | `32×16×16` → `64×16×16` | 提取更高级别特征（边缘组合、形状模式） |\n| `nn.ReLU()`                       | 无参数                       | 不改变尺寸                   | 保留非线性特征             |\n| `nn.BatchNorm2d(64)`              | 64 个可学习参数                 | 不改变尺寸                   | 进一步稳定训练             |\n| `nn.MaxPool2d(2)`                 | 2×2 池化                    | `64×16×16` → `64×8×8`   | 再次降采样，提取全局语义特征      |\n\n---\n\n### 🔹 3. 分类器部分\n\n```python\nnn.Flatten(),\nnn.Linear(64*8*8, 128), nn.ReLU(), nn.Dropout(0.2),\nnn.Linear(128, 10)\n```\n\n| 层                        | 参数解释               | 输入/输出维度           | 说明                         |\n| ------------------------ | ------------------ | ----------------- | -------------------------- |\n| `nn.Flatten()`           | 无参数                | `64×8×8` → `4096` | 把卷积输出展平成向量                 |\n| `nn.Linear(64*8*8, 128)` | 输入维度 4096，输出维度 128 | `4096 → 128`      | 第一个全连接层，将高维特征压缩为 128 维     |\n| `nn.ReLU()`              | 无参数                | 不改变维度             | 非线性激活                      |\n| `nn.Dropout(0.2)`        | 随机丢弃 20% 神经元       | 不改变维度             | 防止过拟合                      |\n| `nn.Linear(128, 10)`     | 输入 128，输出 10       | `128 → 10`        | 最终分类层，对应 CIFAR-10 的 10 个类别 |\n\n---\n\n## ⚙️ 参数统计（大致规模）\n\n| 层类型                | 可训练参数量（约）                |\n| ------------------ | ------------------------ |\n| Conv1 (3→32, 3×3)  | 3×32×3×3 + 32 = 896      |\n| Conv2 (32→64, 3×3) | 32×64×3×3 + 64 = 18,496  |\n| FC1 (4096→128)     | 4096×128 + 128 = 524,416 |\n| FC2 (128→10)       | 128×10 + 10 = 1,290      |\n| **总计**             | ≈ 545,000 参数             |\n\n---\n\n## 📊 数据尺寸流动总结\n\n| 阶段      | 张量形状 (通道×高×宽) | 操作说明           |\n| ------- | ------------- | -------------- |\n| 输入图像    | 3×32×32       | RGB 图像         |\n| 卷积块 1 后 | 32×16×16      | 提取基础特征         |\n| 卷积块 2 后 | 64×8×8        | 提取高级特征         |\n| 展平后     | 4096          | 转为向量           |\n| 全连接层后   | 128           | 抽象特征           |\n| 输出层     | 10            | 10 类概率分布（未归一化） |\n\n---\n\n## 🧠 Forward 函数工作流程\n\n```python\ndef forward(self, x):\n    x = self.features(x)      # 卷积与池化提取空间特征\n    x = self.classifier(x)    # 全连接层分类\n    return x\n```\n\n> 最终输出 `x` 的形状是 `[batch_size, 10]`，\n> 每一行对应一个样本的 10 类得分（之后在 `CrossEntropyLoss` 内部自动做 softmax）。\n\n","metadata":{}},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(device)\nmodel = SimpleCNN().to(device)\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T09:09:40.51713Z","iopub.execute_input":"2025-10-11T09:09:40.517397Z","iopub.status.idle":"2025-10-11T09:09:40.675247Z","shell.execute_reply.started":"2025-10-11T09:09:40.517375Z","shell.execute_reply":"2025-10-11T09:09:40.674427Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======= 训练与验证 =======\ndef evaluate():\n    model.eval()\n    correct = total = 0\n    with torch.no_grad():\n        for imgs, labels in val_loader:\n            imgs, labels = imgs.to(device), labels.to(device)\n            logits = model(imgs)\n            preds = logits.argmax(1)\n            correct += (preds == labels).sum().item()\n            total   += labels.size(0)\n    return correct / total","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T09:09:42.928816Z","iopub.execute_input":"2025-10-11T09:09:42.929083Z","iopub.status.idle":"2025-10-11T09:09:42.934073Z","shell.execute_reply.started":"2025-10-11T09:09:42.929062Z","shell.execute_reply":"2025-10-11T09:09:42.933285Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_acc = 0.0\nfor epoch in range(1, EPOCHS+1):\n    model.train()\n    for imgs, labels in train_loader:\n        imgs, labels = imgs.to(device), labels.to(device)\n        logits = model(imgs)\n        loss = criterion(logits, labels)\n        optimizer.zero_grad(); loss.backward(); optimizer.step()\n    acc = evaluate()\n    if acc > best_acc: best_acc = acc\n    print(f\"Epoch {epoch}/{EPOCHS} - val_acc={acc:.4f} (best={best_acc:.4f})\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T09:09:45.727057Z","iopub.execute_input":"2025-10-11T09:09:45.727323Z","iopub.status.idle":"2025-10-11T09:15:18.924977Z","shell.execute_reply.started":"2025-10-11T09:09:45.727304Z","shell.execute_reply":"2025-10-11T09:15:18.924099Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======= 预测与保存提交文件 =======\nmodel.eval()\npred_ids, pred_labels = [], []\nwith torch.no_grad():\n    for imgs, ids in test_loader:\n        imgs = imgs.to(device)\n        logits = model(imgs)\n        preds = logits.argmax(1).cpu().numpy()\n        pred_labels.extend(preds.tolist())\n        pred_ids.extend(ids)\n\nsub = pd.DataFrame({\"Id\": pred_ids, \"Label\": pred_labels}).sort_values(\"Id\").reset_index(drop=True)\nsub.to_csv(\"submission.csv\", index=False)\nprint(\"✅ 已保存: submission.csv\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T09:15:18.926479Z","iopub.execute_input":"2025-10-11T09:15:18.926746Z","iopub.status.idle":"2025-10-11T09:15:24.876232Z","shell.execute_reply.started":"2025-10-11T09:15:18.926724Z","shell.execute_reply":"2025-10-11T09:15:24.875508Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}