{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "数据集总大小: 3298\n", "训练集大小: 2308\n", "验证集大小: 494\n", "测试集大小: 496\n", "类别数量: 20\n", "类别: ['ANHINGA', 'ANTBIRD', 'African_hunting_dog', 'BANDED PITA', 'BEARDED BELLBIRD', 'BLUE GRAY GNATCATCHER', 'CLARKS NUTCRACKER', 'Cardigan', 'Doberman', 'Eskimo_dog', 'HOOPOES', 'NOISY FRIARBIRD', 'NORTHERN JACANA', 'Newfoundland', 'PALM NUT VULTURE', 'Sealyham_terrier', 'bloodhound', 'cocker_spaniel', 'papillon', 'schipperke']\n" ] } ], "source": [ "dataset_path=\"/hdd_16T/Zirui/work/CNNa2/dataset/dataset_42028assg2_24902417/Image_Classification/dataset_24902417\"\n", "\n", "import os\n", "import numpy as np\n", "import random\n", "import torch\n", "from torchvision import datasets, transforms\n", "from torch.utils.data import DataLoader, random_split\n", "from sklearn.model_selection import train_test_split\n", "\n", "# 设置随机种子以确保可重复性\n", "seed = 24902417\n", "random.seed(seed)\n", "np.random.seed(seed)\n", "torch.manual_seed(seed)\n", "if torch.cuda.is_available():\n", " torch.cuda.manual_seed(seed)\n", " torch.cuda.manual_seed_all(seed)\n", "torch.backends.cudnn.deterministic = True\n", "torch.backends.cudnn.benchmark = False\n", "\n", "# 定义数据转换\n", "transform = transforms.Compose([\n", " transforms.Resize((224, 224)),\n", " transforms.ToTensor(),\n", " transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n", "])\n", "\n", "# 加载数据集\n", "dataset = datasets.ImageFolder(root=dataset_path, transform=transform)\n", "\n", "# 划分数据集为训练集(70%)、验证集(15%)和测试集(15%)\n", "total_size = len(dataset)\n", "train_size = int(0.7 * total_size)\n", "val_size = int(0.15 * total_size)\n", "test_size = total_size - train_size - val_size\n", "\n", "train_dataset, val_dataset, test_dataset = random_split(\n", " dataset, \n", " [train_size, val_size, test_size],\n", " generator=torch.Generator().manual_seed(seed)\n", ")\n", "\n", "# 创建数据加载器\n", "batch_size = 32\n", "train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n", "val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)\n", "test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n", "\n", "print(f\"数据集总大小: {total_size}\")\n", "print(f\"训练集大小: {len(train_dataset)}\")\n", "print(f\"验证集大小: {len(val_dataset)}\")\n", "print(f\"测试集大小: {len(test_dataset)}\")\n", "print(f\"类别数量: {len(dataset.classes)}\")\n", "print(f\"类别: {dataset.classes}\")\n", "\n", "# 定义模型架构" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "使用设备: cuda\n", "实验1: 简化版ResNet模型\n", "SimpleResNet(\n", " (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)\n", " (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", " (relu): ReLU(inplace=True)\n", " (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)\n", " (layer1): Sequential(\n", " (0): ResidualBlock(\n", " (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n", " (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", " (relu): ReLU(inplace=True)\n", " (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", " (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", " (shortcut): Sequential(\n", " (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)\n", " (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", " )\n", " )\n", " (1): ResidualBlock(\n", " (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", " (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", " (relu): ReLU(inplace=True)\n", " (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", " (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", " (shortcut): Sequential()\n", " )\n", " )\n", " (layer2): Sequential(\n", " (0): ResidualBlock(\n", " (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n", " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", " (relu): ReLU(inplace=True)\n", " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", " (shortcut): Sequential(\n", " (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)\n", " (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", " )\n", " )\n", " (1): ResidualBlock(\n", " (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", " (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", " (relu): ReLU(inplace=True)\n", " (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n", " (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", " (shortcut): Sequential()\n", " )\n", " )\n", " (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))\n", " (fc): Linear(in_features=256, out_features=20, bias=True)\n", ")\n", "\n", "实验2: 修改版AlexNet模型\n", "ModifiedAlexNet(\n", " (features): Sequential(\n", " (0): Conv2d(3, 48, kernel_size=(11, 11), stride=(4, 4), padding=(2, 2))\n", " (1): ReLU(inplace=True)\n", " (2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)\n", " (3): Conv2d(48, 128, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))\n", " (4): ReLU(inplace=True)\n", " (5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)\n", " (6): Conv2d(128, 192, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", " (7): ReLU(inplace=True)\n", " (8): Conv2d(192, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", " (9): ReLU(inplace=True)\n", " (10): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)\n", " )\n", " (avgpool): AdaptiveAvgPool2d(output_size=(6, 6))\n", " (classifier): Sequential(\n", " (0): Dropout(p=0.5, inplace=False)\n", " (1): Linear(in_features=4608, out_features=1024, bias=True)\n", " (2): ReLU(inplace=True)\n", " (3): Dropout(p=0.5, inplace=False)\n", " (4): Linear(in_features=1024, out_features=512, bias=True)\n", " (5): ReLU(inplace=True)\n", " (6): Linear(in_features=512, out_features=20, bias=True)\n", " )\n", ")\n" ] } ], "source": [ "\n", "import torch.nn as nn\n", "# 实验1: 基于ResNet的简化模型\n", "class SimpleResNet(nn.Module):\n", " def __init__(self, num_classes):\n", " super(SimpleResNet, self).__init__()\n", " # 初始卷积层\n", " self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)\n", " self.bn1 = nn.BatchNorm2d(64)\n", " self.relu = nn.ReLU(inplace=True)\n", " self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)\n", " \n", " # 简化的ResNet结构 - 只使用两个残差块\n", " self.layer1 = self._make_layer(64, 128, 2)\n", " self.layer2 = self._make_layer(128, 256, 2)\n", " \n", " # 全局平均池化和分类器\n", " self.avgpool = nn.AdaptiveAvgPool2d((1, 1))\n", " self.fc = nn.Linear(256, num_classes)\n", " \n", " def _make_layer(self, in_channels, out_channels, blocks):\n", " layers = []\n", " # 第一个块可能需要下采样\n", " layers.append(ResidualBlock(in_channels, out_channels, stride=2))\n", " # 添加剩余的块\n", " for _ in range(1, blocks):\n", " layers.append(ResidualBlock(out_channels, out_channels))\n", " return nn.Sequential(*layers)\n", " \n", " def forward(self, x):\n", " x = self.conv1(x)\n", " x = self.bn1(x)\n", " x = self.relu(x)\n", " x = self.maxpool(x)\n", " \n", " x = self.layer1(x)\n", " x = self.layer2(x)\n", " \n", " x = self.avgpool(x)\n", " x = torch.flatten(x, 1)\n", " x = self.fc(x)\n", " return x\n", "\n", "# 残差块定义\n", "class ResidualBlock(nn.Module):\n", " def __init__(self, in_channels, out_channels, stride=1):\n", " super(ResidualBlock, self).__init__()\n", " self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)\n", " self.bn1 = nn.BatchNorm2d(out_channels)\n", " self.relu = nn.ReLU(inplace=True)\n", " self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False)\n", " self.bn2 = nn.BatchNorm2d(out_channels)\n", " \n", " # 如果输入和输出通道数不同,需要使用1x1卷积进行调整\n", " self.shortcut = nn.Sequential()\n", " if stride != 1 or in_channels != out_channels:\n", " self.shortcut = nn.Sequential(\n", " nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),\n", " nn.BatchNorm2d(out_channels)\n", " )\n", " \n", " def forward(self, x):\n", " identity = x\n", " \n", " out = self.conv1(x)\n", " out = self.bn1(out)\n", " out = self.relu(out)\n", " \n", " out = self.conv2(out)\n", " out = self.bn2(out)\n", " \n", " out += self.shortcut(identity)\n", " out = self.relu(out)\n", " \n", " return out\n", "\n", "# 实验2: 基于AlexNet的修改版本\n", "class ModifiedAlexNet(nn.Module):\n", " def __init__(self, num_classes):\n", " super(ModifiedAlexNet, self).__init__()\n", " self.features = nn.Sequential(\n", " # 第一层卷积\n", " nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=2),\n", " nn.ReLU(inplace=True),\n", " nn.MaxPool2d(kernel_size=3, stride=2),\n", " \n", " # 第二层卷积\n", " nn.Conv2d(48, 128, kernel_size=5, padding=2),\n", " nn.ReLU(inplace=True),\n", " nn.MaxPool2d(kernel_size=3, stride=2),\n", " \n", " # 第三层卷积 (减少了原AlexNet的两个卷积层)\n", " nn.Conv2d(128, 192, kernel_size=3, padding=1),\n", " nn.ReLU(inplace=True),\n", " \n", " # 第四层卷积\n", " nn.Conv2d(192, 128, kernel_size=3, padding=1),\n", " nn.ReLU(inplace=True),\n", " nn.MaxPool2d(kernel_size=3, stride=2),\n", " )\n", " \n", " # 计算特征图大小\n", " self.avgpool = nn.AdaptiveAvgPool2d((6, 6))\n", " \n", " # 分类器\n", " self.classifier = nn.Sequential(\n", " nn.Dropout(),\n", " nn.Linear(128 * 6 * 6, 1024),\n", " nn.ReLU(inplace=True),\n", " nn.Dropout(),\n", " nn.Linear(1024, 512),\n", " nn.ReLU(inplace=True),\n", " nn.Linear(512, num_classes),\n", " )\n", " \n", " def forward(self, x):\n", " x = self.features(x)\n", " x = self.avgpool(x)\n", " x = torch.flatten(x, 1)\n", " x = self.classifier(x)\n", " return x\n", "\n", "# 创建模型实例\n", "num_classes = len(dataset.classes)\n", "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", "print(f\"使用设备: {device}\")\n", "\n", "# 实例化两个模型用于实验\n", "model1 = SimpleResNet(num_classes).to(device)\n", "model2 = ModifiedAlexNet(num_classes).to(device)\n", "\n", "print(\"实验1: 简化版ResNet模型\")\n", "print(model1)\n", "print(\"\\n实验2: 修改版AlexNet模型\")\n", "print(model2)\n" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "开始训练模型1 (SimpleResNet)...\n", "Epoch 0/14\n", "----------\n", "train Loss: 2.6223 Acc: 0.1954\n", "val Loss: 2.7181 Acc: 0.1842\n", "\n", "Epoch 1/14\n", "----------\n", "train Loss: 2.2823 Acc: 0.2803\n", "val Loss: 3.2717 Acc: 0.1619\n", "\n", "Epoch 2/14\n", "----------\n", "train Loss: 2.1028 Acc: 0.3336\n", "val Loss: 2.9713 Acc: 0.2530\n", "\n", "Epoch 3/14\n", "----------\n", "train Loss: 1.8960 Acc: 0.3917\n", "val Loss: 2.0564 Acc: 0.3421\n", "\n", "Epoch 4/14\n", "----------\n", "train Loss: 1.7319 Acc: 0.4328\n", "val Loss: 2.2584 Acc: 0.2955\n", "\n", "Epoch 5/14\n", "----------\n", "train Loss: 1.6317 Acc: 0.4679\n", "val Loss: 2.0198 Acc: 0.3826\n", "\n", "Epoch 6/14\n", "----------\n", "train Loss: 1.4990 Acc: 0.5195\n", "val Loss: 1.6943 Acc: 0.4494\n", "\n", "Epoch 7/14\n", "----------\n", "train Loss: 1.2234 Acc: 0.6114\n", "val Loss: 1.4618 Acc: 0.5223\n", "\n", "Epoch 8/14\n", "----------\n", "train Loss: 1.1370 Acc: 0.6490\n", "val Loss: 1.4342 Acc: 0.5304\n", "\n", "Epoch 9/14\n", "----------\n", "train Loss: 1.1088 Acc: 0.6551\n", "val Loss: 1.3917 Acc: 0.5445\n", "\n", "Epoch 10/14\n", "----------\n", "train Loss: 1.0647 Acc: 0.6707\n", "val Loss: 1.3901 Acc: 0.5688\n", "\n", "Epoch 11/14\n", "----------\n", "train Loss: 1.0425 Acc: 0.6833\n", "val Loss: 1.3501 Acc: 0.5668\n", "\n", "Epoch 12/14\n", "----------\n", "train Loss: 1.0212 Acc: 0.6902\n", "val Loss: 1.3294 Acc: 0.5830\n", "\n", "Epoch 13/14\n", "----------\n", "train Loss: 0.9809 Acc: 0.6950\n", "val Loss: 1.3069 Acc: 0.5769\n", "\n", "Epoch 14/14\n", "----------\n", "train Loss: 0.9322 Acc: 0.7179\n", "val Loss: 1.2833 Acc: 0.5992\n", "\n", "最佳验证准确率: 0.5992\n", "\n", "开始训练模型2 (ModifiedAlexNet)...\n", "Epoch 0/14\n", "----------\n", "train Loss: 2.9949 Acc: 0.0585\n", "val Loss: 3.0515 Acc: 0.0668\n", "\n", "Epoch 1/14\n", "----------\n", "train Loss: 2.9327 Acc: 0.0927\n", "val Loss: 2.8403 Acc: 0.1296\n", "\n", "Epoch 2/14\n", "----------\n", "train Loss: 2.7412 Acc: 0.1464\n", "val Loss: 2.6951 Acc: 0.1336\n", "\n", "Epoch 3/14\n", "----------\n", "train Loss: 2.6302 Acc: 0.1737\n", "val Loss: 2.8378 Acc: 0.1437\n", "\n", "Epoch 4/14\n", "----------\n", "train Loss: 2.5738 Acc: 0.1954\n", "val Loss: 2.5791 Acc: 0.1822\n", "\n", "Epoch 5/14\n", "----------\n", "train Loss: 2.4614 Acc: 0.2314\n", "val Loss: 2.6599 Acc: 0.2045\n", "\n", "Epoch 6/14\n", "----------\n", "train Loss: 2.3624 Acc: 0.2556\n", "val Loss: 2.4495 Acc: 0.2247\n", "\n", "Epoch 7/14\n", "----------\n", "train Loss: 2.1477 Acc: 0.3094\n", "val Loss: 2.3717 Acc: 0.2409\n", "\n", "Epoch 8/14\n", "----------\n", "train Loss: 2.0964 Acc: 0.3211\n", "val Loss: 2.3568 Acc: 0.2368\n", "\n", "Epoch 9/14\n", "----------\n", "train Loss: 2.0533 Acc: 0.3432\n", "val Loss: 2.3462 Acc: 0.2652\n", "\n", "Epoch 10/14\n", "----------\n", "train Loss: 2.0178 Acc: 0.3484\n", "val Loss: 2.3558 Acc: 0.2713\n", "\n", "Epoch 11/14\n", "----------\n", "train Loss: 1.9797 Acc: 0.3614\n", "val Loss: 2.3268 Acc: 0.2753\n", "\n", "Epoch 12/14\n", "----------\n", "train Loss: 1.9409 Acc: 0.3713\n", "val Loss: 2.3457 Acc: 0.2834\n", "\n", "Epoch 13/14\n", "----------\n", "train Loss: 1.8955 Acc: 0.3878\n", "val Loss: 2.3186 Acc: 0.2895\n", "\n", "Epoch 14/14\n", "----------\n", "train Loss: 1.8640 Acc: 0.4008\n", "val Loss: 2.3196 Acc: 0.2874\n", "\n", "最佳验证准确率: 0.2895\n", "在测试集上评估模型...\n", "SimpleResNet 测试准确率: 0.6149\n", "ModifiedAlexNet 测试准确率: 0.2843\n" ] } ], "source": [ "# 模型训练与评估\n", "\n", "## 定义训练函数\n", "def train_model(model, criterion, optimizer, scheduler, num_epochs=25):\n", " best_model_wts = copy.deepcopy(model.state_dict())\n", " best_acc = 0.0\n", " \n", " for epoch in range(num_epochs):\n", " print(f'Epoch {epoch}/{num_epochs - 1}')\n", " print('-' * 10)\n", " \n", " # 每个epoch有训练和验证阶段\n", " for phase in ['train', 'val']:\n", " if phase == 'train':\n", " model.train() # 设置模型为训练模式\n", " else:\n", " model.eval() # 设置模型为评估模式\n", " \n", " running_loss = 0.0\n", " running_corrects = 0\n", " \n", " # 遍历数据\n", " for inputs, labels in dataloaders[phase]:\n", " inputs = inputs.to(device)\n", " labels = labels.to(device)\n", " \n", " # 梯度清零\n", " optimizer.zero_grad()\n", " \n", " # 前向传播\n", " with torch.set_grad_enabled(phase == 'train'):\n", " outputs = model(inputs)\n", " _, preds = torch.max(outputs, 1)\n", " loss = criterion(outputs, labels)\n", " \n", " # 如果是训练阶段,则反向传播和优化\n", " if phase == 'train':\n", " loss.backward()\n", " optimizer.step()\n", " \n", " # 统计\n", " running_loss += loss.item() * inputs.size(0)\n", " running_corrects += torch.sum(preds == labels.data)\n", " \n", " if phase == 'train' and scheduler is not None:\n", " scheduler.step()\n", " \n", " epoch_loss = running_loss / dataset_sizes[phase]\n", " epoch_acc = running_corrects.double() / dataset_sizes[phase]\n", " \n", " print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')\n", " \n", " # 如果是验证阶段且准确率提高,保存模型\n", " if phase == 'val' and epoch_acc > best_acc:\n", " best_acc = epoch_acc\n", " best_model_wts = copy.deepcopy(model.state_dict())\n", " \n", " print()\n", " \n", " print(f'最佳验证准确率: {best_acc:.4f}')\n", " \n", " # 加载最佳模型权重\n", " model.load_state_dict(best_model_wts)\n", " return model\n", "\n", "## 准备数据加载器\n", "# 定义数据转换和批次大小\n", "batch_size = 32\n", "\n", "# 创建数据加载器\n", "dataloaders = {\n", " 'train': torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=4),\n", " 'val': torch.utils.data.DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=4)\n", "}\n", "\n", "dataset_sizes = {\n", " 'train': len(train_dataset),\n", " 'val': len(val_dataset)\n", "}\n", "\n", "## 设置损失函数和优化器\n", "import copy\n", "import time\n", "\n", "# 为两个模型分别定义损失函数和优化器\n", "criterion = nn.CrossEntropyLoss()\n", "\n", "# 模型1的优化器\n", "optimizer1 = torch.optim.Adam(model1.parameters(), lr=0.001)\n", "# 学习率调度器\n", "scheduler1 = torch.optim.lr_scheduler.StepLR(optimizer1, step_size=7, gamma=0.1)\n", "\n", "# 模型2的优化器\n", "optimizer2 = torch.optim.Adam(model2.parameters(), lr=0.001)\n", "# 学习率调度器\n", "scheduler2 = torch.optim.lr_scheduler.StepLR(optimizer2, step_size=7, gamma=0.1)\n", "\n", "## 训练模型\n", "print(\"Training model1 (SimpleResNet)...\")\n", "model1 = train_model(model1, criterion, optimizer1, scheduler1, num_epochs=15)\n", "\n", "print(\"\\Training model2 (ModifiedAlexNet)...\")\n", "model2 = train_model(model2, criterion, optimizer2, scheduler2, num_epochs=15)\n", "\n", "## 模型评估\n", "def evaluate_model(model, dataloader):\n", " model.eval()\n", " running_corrects = 0\n", " \n", " # 不计算梯度\n", " with torch.no_grad():\n", " for inputs, labels in dataloader:\n", " inputs = inputs.to(device)\n", " labels = labels.to(device)\n", " \n", " # 前向传播\n", " outputs = model(inputs)\n", " _, preds = torch.max(outputs, 1)\n", " \n", " # 统计正确预测的数量\n", " running_corrects += torch.sum(preds == labels.data)\n", " \n", " # 计算准确率\n", " acc = running_corrects.double() / len(dataloader.dataset)\n", " return acc\n", "\n", "# 在测试集上评估两个模型\n", "test_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=4)\n", "\n", "print(\"在测试集上评估模型...\")\n", "model1_acc = evaluate_model(model1, test_dataloader)\n", "model2_acc = evaluate_model(model2, test_dataloader)\n", "\n", "print(f\"SimpleResNet 测试准确率: {model1_acc:.4f}\")\n", "print(f\"ModifiedAlexNet 测试准确率: {model2_acc:.4f}\")\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "加载官方预训练模型...\n", "training official AlexNet model...\n", "Epoch 0/14\n", "----------\n", "train Loss: 0.8559 Acc: 0.7470\n", "val Loss: 0.3852 Acc: 0.8785\n", "\n", "Epoch 1/14\n", "----------\n", "train Loss: 0.1529 Acc: 0.9536\n", "val Loss: 0.3174 Acc: 0.8968\n", "\n", "Epoch 2/14\n", "----------\n", "train Loss: 0.0646 Acc: 0.9805\n", "val Loss: 0.3177 Acc: 0.9008\n", "\n", "Epoch 3/14\n", "----------\n", "train Loss: 0.0284 Acc: 0.9948\n", "val Loss: 0.2529 Acc: 0.9089\n", "\n", "Epoch 4/14\n", "----------\n", "train Loss: 0.0243 Acc: 0.9957\n", "val Loss: 0.2655 Acc: 0.9190\n", "\n", "Epoch 5/14\n", "----------\n", "train Loss: 0.0145 Acc: 0.9970\n", "val Loss: 0.2938 Acc: 0.9109\n", "\n", "Epoch 6/14\n", "----------\n", "train Loss: 0.0115 Acc: 0.9974\n", "val Loss: 0.2840 Acc: 0.9109\n", "\n", "Epoch 7/14\n", "----------\n", "train Loss: 0.0108 Acc: 0.9978\n", "val Loss: 0.2491 Acc: 0.9271\n", "\n", "Epoch 8/14\n", "----------\n", "train Loss: 0.0058 Acc: 0.9996\n", "val Loss: 0.2522 Acc: 0.9251\n", "\n", "Epoch 9/14\n", "----------\n", "train Loss: 0.0040 Acc: 1.0000\n", "val Loss: 0.2533 Acc: 0.9231\n", "\n", "Epoch 10/14\n", "----------\n", "train Loss: 0.0028 Acc: 1.0000\n", "val Loss: 0.2523 Acc: 0.9231\n", "\n", "Epoch 11/14\n", "----------\n", "train Loss: 0.0038 Acc: 0.9996\n", "val Loss: 0.2518 Acc: 0.9251\n", "\n", "Epoch 12/14\n", "----------\n", "train Loss: 0.0033 Acc: 1.0000\n", "val Loss: 0.2521 Acc: 0.9231\n", "\n", "Epoch 13/14\n", "----------\n", "train Loss: 0.0029 Acc: 1.0000\n", "val Loss: 0.2544 Acc: 0.9231\n", "\n", "Epoch 14/14\n", "----------\n", "train Loss: 0.0035 Acc: 0.9991\n", "val Loss: 0.2546 Acc: 0.9231\n", "\n", "最佳验证准确率: 0.9271\n", "\n", "Training official ResNet model...\n", "Evaluating official model on test set...\n", "Official AlexNet test accuracy: 0.9133\n", "Official ResNet test accuracy: 0.0544\n", "\n", "Model performance comparison:\n", "Modified AlexNet vs Official AlexNet: 0.2843 vs 0.9133\n", "Modified ResNet vs Official ResNet: 0.6149 vs 0.0544\n" ] }, { "data": { "image/png": "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tJ8/f14+Pj4ONX7//fe6fPmyChcunO18rFmzRufOnct2b3fGXr9rr1Sdm6MH/vzzT8XExCgqKkrDhw+3t2fsTc5QqlQpeXp65ugHrrZt26pUqVKaP3++GjdurIsXL6p79+45runaaRtjdODAAXsgq1ChgqS/f3S70fJzvfewefPmSk1N1cKFC3XixAl7uL733nvtobty5cr28J0x7aSkpBtON7fL4c0qW7asYmJilJSU5PBDw759+/J8WhUqVNAPP/ygVq1a3dQpDqVKlVKxYsWUlpZ20+t9Vv7J6Ra5ldPtZsa/1y7LGf2u9k+XlaJFi6pLly7q0qWLUlNT9eijj2r06NEaPHgwt2gDCjDO6QZwW3n99ddVtGhR9enTR/Hx8ZmeP3jwoKZMmSJJeuCBByRJkydPduiTseemffv2eV7f1KlT7f83xmjq1KkqXLiwWrVqJenvPSs2m81h7+SRI0e0YsWKm55mWlqa/bDgDKVLl1ZAQID98PUGDRqodOnSmjFjhsMh7f/5z3+0d+/ePHstQkND5eLionfffddhb9Hs2bOVkJBw09PJuL/0tYGzc+fOSktLsx/afLUrV67Y+//555+Z9l5l7DXNeD2KFCmS5TSy4+zsnGmcS5YsyXQroU6dOunMmTMOy0aGjOE7deokY0yWe6sy+nh6esrHx8d+HnGG6dOn56jejJqvHmeGa9cRJycndezYUV9++aX9lmVZ1ST9fT/orl276rPPPtO8efNUq1atTHswr+fjjz92OGXk888/18mTJ+1HaISEhKhChQoaP368kpKSMg1/+vRp+/+zW06kv8/XLVy4sMaNG6cSJUqoRo0akv4O4999953++9//Ouzllv5evrZu3ao1a9ZkGt/58+d15coVe7+cLIf/1AMPPKArV67o/ffft7elpaXpvffey5PxX61z5846ceKEZs2alem5v/76S8nJydcd3tnZWZ06ddLSpUuz/PHm6vctN4oWLZppe2eVnG43/f39VbduXX300UcOta1bt06//PKLwzj/ybJy7W39XFxcVL16dRlj8uxaFQDyB3u6AdxWKlSooAULFqhLly6qVq2awsPDVbNmTaWmpmrLli1asmSJevbsKUmqU6eOevTooZkzZ+r8+fNq0aKFtm3bpo8++kgdO3ZUy5Yt87Q2Nzc3RUdHq0ePHmrcuLH+85//aNWqVXrzzTfthwy2b99eEydOVNu2bdWtWzedOnVK06ZNU8WKFbVnz56bmu6FCxd011136bHHHlOdOnXk4eGh9evXa/v27ZowYYIk2cNGr1691KJFC3Xt2lXx8fGaMmWKgoOD9corr+TJa1CqVCkNHjxYUVFRatu2rR566CHt27dP06dPV8OGDXN8ca1rhYSESJJeeuklhYWFydnZWU888YRatGihZ599VmPGjFFsbKzatGmjwoUL67ffftOSJUs0ZcoUPfbYY/roo480ffp0PfLII6pQoYIuXLigWbNmydPT0/7jjLu7u6pXr67FixercuXKKlGihGrWrJntec0PPvigRo4cqV69eqlp06b68ccfNX/+/Ezn9YeHh+vjjz9WRESEtm3bpubNmys5OVnr16/XCy+8oIcfflgtW7ZU9+7d9e677+q3336zH+q9efNmtWzZ0n6Bvj59+mjs2LHq06ePGjRooE2bNtmPpsgJT09P3XvvvXr77bd1+fJlBQYGau3atTp8+HCmvm+99ZbWrl2rFi1a6JlnnlG1atV08uRJLVmyRN9884394nQZ8/juu+9qw4YNGjduXI7rkaQSJUronnvuUa9evRQfH6/JkyerYsWK6tu3r6S/fwD48MMP1a5dO9WoUUO9evVSYGCgTpw4oQ0bNsjT01NffvmlpP8tJ0OGDNETTzyhwoULq0OHDipatKiKFCmikJAQfffdd/Z7dEt/7+lOTk5WcnJyptA9cOBArVy5Ug8++KB69uypkJAQJScn68cff9Tnn3+uI0eOyMfHJ8fL4T/VoUMHNWvWTIMGDdKRI0dUvXp1LVu2zJIQ2r17d3322Wd67rnntGHDBjVr1kxpaWn69ddf9dlnn2nNmjVZXmTvamPHjtWGDRvUuHFj9e3bV9WrV9e5c+e0a9curV+/XufOnct1XSEhIVq8eLEiIiLUsGFDeXh4qEOHDjc7m9eVm+3mmDFj1L59e91zzz16+umnde7cOfs9ta/+seifLCtt2rSRn5+fmjVrJl9fX+3du1dTp05V+/btc3yBUQC3qVt9uXQAyIn9+/ebvn37muDgYOPi4mKKFStmmjVrZt577z1z6dIle7/Lly+bqKgoU65cOVO4cGETFBRkBg8e7NDHmKxvmWPM37ebufZWXBm3bnrnnXfsbT169DBFixY1Bw8etN/b1tfX10RGRma6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biYAAAAAALiOfAvdLi4uCgkJUUxMjL0tPT1dMTExatKkSZbDNGvWTAcOHFB6erq9bf/+/fL395eLi0uWw7i6usrT09PhDwAAAACAWyFfDy+PiIjQrFmz9NFHH2nv3r16/vnnlZycbL+aeXh4uAYPHmzv//zzz+vcuXMaMGCA9u/fr1WrVumtt95Sv3798msWAAAAAADIVr7eMqxLly46ffq0hg8frri4ONWtW1fR0dH2i6sdO3ZMTk7/+10gKChIa9as0SuvvKLatWsrMDBQAwYM0BtvvJFfswAAAAAAQLZsxhiT30XcSomJifLy8lJCQgKHmgOwW7bvZH6XANy0R6v453cJuWKLsuV3CcBNMZH/qq/NAG4gp9myQF29HAAAAACAgiTXoTs4OFgjR47UsWPHrKgHAAAAAIA7Rq5D98svv6xly5apfPnyat26tRYtWqSUlBQragMAAAAAoEC7qdAdGxurbdu2qVq1anrxxRfl7++v/v37a9euXVbUCAAAAABAgXTT53TXr19f7777rv744w9FRkbqww8/VMOGDVW3bl3NmTNH/7LrswEAAAAAkMlN3zLs8uXLWr58uebOnat169bp7rvvVu/evfX777/rzTff1Pr167VgwYK8rBUAAAAAgAIl16F7165dmjt3rhYuXCgnJyeFh4dr0qRJqlq1qr3PI488ooYNG+ZpoQAAAAAAFDS5Dt0NGzZU69at9f7776tjx44qXLhwpj7lypXTE088kScFAgAAAABQUOU6dB86dEhly5a9bp+iRYtq7ty5N10UAAAAAAB3glxfSO3UqVP6/vvvM7V///332rFjR54UBQAAAADAnSDXobtfv346fvx4pvYTJ06oX79+eVIUAAAAAAB3glyH7l9++UX169fP1F6vXj398ssveVIUAAAAAAB3glyHbldXV8XHx2dqP3nypAoVuuk7kAEAAAAAcMfJdehu06aNBg8erISEBHvb+fPn9eabb6p169Z5WhwAAAAAAAVZrndNjx8/Xvfee6/Kli2revXqSZJiY2Pl6+urTz75JM8LBAAAAACgoMp16A4MDNSePXs0f/58/fDDD3J3d1evXr3UtWvXLO/ZDQAAAADAv9VNnYRdtGhRPfPMM3ldCwAAAAAAd5SbvvLZL7/8omPHjik1NdWh/aGHHvrHRQEAAAAAcCfIdeg+dOiQHnnk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aNAgVa5c2eF1yM4XX3yh+vXry83NTeXLl1dUVJSuXLkiSRo5cqQCAgIcfr1t3769WrZsqfT0dHvbM888o++++06rV6++7rSuN9/lypWTJNWrV082my1Xv3zi9tCvXz+5uLho7dq1atGihcqUKaN27dpp/fr1OnHihIYMGSLp771VEyZM0KZNm+zvdVZtUua9tOfPn9ezzz4rX19fubm5qWbNmvrqq68kZd6zdPDgQT388MPy9fWVh4eHGjZsqPXr1+d6vowxmjt3rrp3765u3bpp9uzZNxzmp59+Urt27eTh4SFfX191795dZ86ckfT34bsuLi7avHmzvf/bb7+t0qVLKz4+3t4WGhoqPz8/jRkz5rrT+uabb9S8eXO5u7srKChIL730kpKTkyX9/VofPXpUr7zyin1PBgq2f/vnXJEiReTn56eyZcuqV69eql27ttatW+cwz6+99poCAwNVtGhRNW7cWBs3brQ/f/ToUXXo0EHe3t4qWrSoatSokemz68UXX9TEiRN16tSpbOu43nQ2btyoXr16KSEhwb7e5WYvOW5fd+rnXKFCheTn56fAwECFhobq8ccfd1ivpOuv06mpqerfv7/8/f3l5uamsmXLZvrs4rtiAWWAa/To0cM8/PDDZuLEiaZVq1b29latWplJkyaZhx9+2PTo0cPe/tJLL5mAgACzevVq8/PPP5sePXoYb29vc/bsWWOMMceOHTOurq4mIiLC/Prrr+bTTz81vr6+RpL5888/jTHGzJ0713h5eRljjLl48aJZv369kWS2bdtmTp48aa5cuWIiIyNNnTp17NP99NNPjb+/v1m6dKk5dOiQWbp0qSlRooSZN2+eMcaYCxcumFKlSplu3bqZn376yXz55ZemfPnyRpLZvXv3dV+DYcOGmccee8wYY8x7771n7r//fofnr67XGGM2bdpkPD09zbx588zBgwfN2rVrTXBwsBkxYoQxxpgrV66YJk2amI4dOxpjjJk6daopXry4OXr0qH0cZcuWNZMmTTIvvfSSqV27tklLSzPGGDNgwADTokWLHM/3tm3bjCSzfv16c/LkSfv7gILh7NmzxmazmbfeeivL5/v27Wu8vb1Nenq6OXv2rOnbt69p0qSJ/b3Oqs2Y/y1fxhiTlpZm7r77blOjRg2zdu1ac/DgQfPll1+a1atXG2MyL9+xsbFmxowZ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Hz6s//znP1q/fr06d+6s0NBQff7555mmxXfFgoULqeG62rZtq9TUVF2+fFlhYWGZnq9QoYJcXFz07bff2tsuX76s7du3q3r16pKkatWqadu2bQ7DXXurg9zy9fVVQECADh06pIoVKzr8ZVwcolq1atqzZ4/DlcdvNN2zZ8/qiy++0KJFixQbG2v/2717t/7880+tXbs2y+Hq16+vffv2ZaqlYsWK9pC+ePFiLVu2TBs3btSxY8ey3Wsu/f1lY9iwYRo9erTD7VZyMt8uLi6S/t7TjoKnZMmSat26taZPn+5wVWVJiouL0/z589WlS5d/dCGv2rVr6/fff8/x7VK+/fZb9ezZU4888ohq1aolPz+/TBeJupHZs2fr1VdfdVivfvjhBzVv3lxz5szJcpj69evr559/VnBwcKblPSNYHzx4UK+88or9y0WPHj0cLk54rbFjx+rLL7/U1q1bM03rl19+yXIdzlinXFxcWK/uQP+2z7mseHh4aMCAAXrttddkjFG9evWUlpamU6dOZZq2n5+ffbigoCA999xzWrZsmV599VXNmjUry/E3atRIjz76qAYNGuTQnpPpsN7dee7Uz7msDB06VOPHj9cff/yRo3Vakjw9PdWlSxfNmjVLixcv1tKlS7O87RjfFQsWQjeuy9nZWXv37tUvv/yS5a9jRYsW1fPPP6+BAwcqOjpav/zyi/r27auLFy+qd+/ekqTnnntOv/32mwYOHKh9+/ZpwYIFmjdv3j+uLSoqSmPGjNG7776r/fv368cff9TcuXM1ceJESVK3bt1ks9nUt29f/fLLL1q9evUNb13yySefqGTJkurcubNq1qxp/6tTp44eeOCBbK+2PHz4cH388ceKiorSzz//rL1792rRokUaOnSopL/vTfz8889r3LhxuueeezR37ly99dZb1/1y9Mwzz8jLyyvT4Xg3mu/SpUvL3d1d0dHRio+PV0JCQo5fU9wepk6dqpSUFIWFhWnTpk06fvy4oqOj1bp1awUGBjqcxnAzWrRooXvvvVedOnXSunXr7L+qR0dHZ9m/UqVKWrZsmT0od+vW7brB9lqxsbHatWuX+vTp47Be1axZU127dtVHH31kv9L/1fr166dz586pa9eu2r59uw4ePKg1a9aoV69eSktLU1pamp566imFhYWpV69emjt3rvbs2aMJEyZkW0utWrX05JNP6t1333Vof+ONN7Rlyxb1799fsbGx+u233/TFF1/Y94BKf18Zd9OmTTpx4oT9Cuoo+P5tn3PZefbZZ7V//34tXbpUlStX1pNPPqnw8HAtW7ZMhw8f1rZt2zRmzBitWrVK0t/3BV6zZo0OHz6sXbt2acOGDdc9DWP06NH6+uuvtW/fPntbTqYTHByspKQkxcTE6MyZM7p48eJNzR9uL3fa51x2mjRpotq1a+utt96SdON1euLEiVq4cKF+/fVX7d+/X0uWLJGfn1+296rnu2LBQejGDXl6esrT0zPb58eOHatOnTqpe/fuql+/vg4cOKA1a9bI29tb0t+HZi5dulQrVqxQnTp1NGPGDPvG55/o06ePPvzwQ82dO1e1atVSixYtNG/ePPuveB4eHvryyy/1448/ql69ehoyZEiWh6lebc6cOXrkkUey/HW1U6dOWrlyZZZftsPCwvTVV19p7dq1atiwoe6++25NmjRJZcuWlTFGPXv2VKNGjexf4MPCwvT888/rqaeesh8GdK3ChQtr1KhRme4RfqP5LlSokN5991198MEHCggI0MMPP3zjFxO3lUqVKmnHjh0qX768OnfurAoVKuiZZ55Ry5YttXXrVpUoUeIfT2Pp0qVq2LChunbtqurVq+v111/P9hfviRMnytvbW02bNlWHDh0UFham+vXr53has2fPVvXq1VW1atVMzz3yyCM6depUlrc+CQgI0Lfffqu0tDS1adNGtWrV0ssvv6zixYvLyclJo0eP1tGjR/XBBx9I+vuQ85kzZ2ro0KH64Ycfsq1n5MiRmb5M1a5dW//973+1f/9+NW/eXPXq1dPw4cMVEBDgMNyRI0dUoUKFf3TYI24//6bPueyUKFFC4eHhGjFihNLT0zV37lyFh4fr1VdfVZUqVdSxY0dt377dfrpFWlqa+vXrp2rVqqlt27aqXLlypsNcr1a5cmU9/fTTmT7TbjSdpk2b6rnnnlOXLl1UqlQpvf322zc1f7i93Gmfc9fzyiuv6MMPP9Tx48dvuE4XK1ZMb7/9tho0aKCGDRvqyJEjWr16dZanNkp8VyxIbMbKqxgAAAAAAPAvxp5uAAAAAAAsQugGAAAAAMAihG4AAAAAACxC6AYAAAAAwCKEbgAAAAAALELoBgAAAADAIoRuAAAAAAAsQugGAAAAAMAihG4AAAAAACxC6AYAAAAAwCKEbgAAAAAALELoBgAAAADAIv8P6CzxPrVnM/0AAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "## 使用官方预训练模型进行对比实验\n", "\n", "# 导入预训练模型\n", "import torchvision.models as models\n", "\n", "# 加载预训练的AlexNet和ResNet模型\n", "print(\"加载官方预训练模型...\")\n", "official_alexnet = models.alexnet(pretrained=True)\n", "official_resnet = models.resnet18(pretrained=True)\n", "\n", "for param in official_alexnet.features.parameters():\n", " param.requires_grad = False\n", "\n", "for param in official_resnet.parameters():\n", " param.requires_grad = False\n", "\n", "# 修改最后的全连接层以适应我们的分类任务\n", "\n", "num_classes = len(dataset.classes)\n", "\n", "# 修改AlexNet的分类器\n", "official_alexnet.classifier[6] = nn.Linear(4096, num_classes)\n", "\n", "# 修改ResNet的全连接层\n", "official_resnet.fc = nn.Linear(512, num_classes)\n", "\n", "# 将模型移至GPU\n", "official_alexnet = official_alexnet.to(device)\n", "official_resnet = official_resnet.to(device)\n", "\n", "# 定义损失函数和优化器\n", "criterion = nn.CrossEntropyLoss()\n", "\n", "# 官方AlexNet的优化器和学习率调度器\n", "optimizer_alexnet = torch.optim.Adam(official_alexnet.parameters(), lr=0.0001)\n", "scheduler_alexnet = torch.optim.lr_scheduler.StepLR(optimizer_alexnet, step_size=7, gamma=0.1)\n", "\n", "# 官方ResNet的优化器和学习率调度器\n", "optimizer_resnet = torch.optim.Adam(official_resnet.parameters(), lr=0.001)\n", "scheduler_resnet = torch.optim.lr_scheduler.StepLR(optimizer_resnet, step_size=7, gamma=0.1)\n", "\n", "# 训练官方模型\n", "print(\"training official AlexNet model...\")\n", "official_alexnet = train_model(official_alexnet, criterion, optimizer_alexnet, scheduler_alexnet, num_epochs=15)\n", "\n", "print(\"\\nTraining official ResNet model...\")\n", "official_resnet = train_model(official_resnet, criterion, optimizer_resnet, scheduler_resnet, num_epochs=15)\n", "\n", "# 在测试集上评估官方模型\n", "print(\"Evaluating official model on test set...\")\n", "official_alexnet_acc = evaluate_model(official_alexnet, test_dataloader)\n", "official_resnet_acc = evaluate_model(official_resnet, test_dataloader)\n", "\n", "print(f\"Official AlexNet test accuracy: {official_alexnet_acc:.4f}\")\n", "print(f\"Official ResNet test accuracy: {official_resnet_acc:.4f}\")\n", "\n", "# 结果对比\n", "print(\"\\nModel performance comparison:\")\n", "print(f\"Modified AlexNet vs Official AlexNet: {model2_acc:.4f} vs {official_alexnet_acc:.4f}\")\n", "print(f\"Modified ResNet vs Official ResNet: {model1_acc:.4f} vs {official_resnet_acc:.4f}\")\n", "\n", "# 可视化对比结果\n", "import matplotlib.pyplot as plt\n", "\n", "models_names = ['Modified AlexNet', 'Official AlexNet', 'Modified ResNet', 'Official ResNet']\n", "accuracies = [model2_acc.item(), official_alexnet_acc.item(), model1_acc.item(), official_resnet_acc.item()]\n", "\n", "plt.figure(figsize=(10, 6))\n", "bars = plt.bar(models_names, accuracies, color=['blue', 'lightblue', 'green', 'lightgreen'])\n", "plt.ylim(0, 1.0)\n", "plt.ylabel('Test accuracy')\n", "plt.title('Comparison of test accuracy between different models')\n", "\n", "# 在柱状图上添加具体数值\n", "for bar in bars:\n", " height = bar.get_height()\n", " plt.text(bar.get_x() + bar.get_width()/2., height + 0.01,\n", " f'{height:.4f}', ha='center', va='bottom')\n", "\n", "plt.tight_layout()\n", "plt.show()\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.14" } }, "nbformat": 4, "nbformat_minor": 2 }