实验概述

项目

内容

实验名称

STL-10 图像分类器

实验日期

2026-04-01

目标数据集

STL-10 (10类彩色图像)

训练设备

NVIDIA GPU (自动检测)

STL-10是CIFAR-10的"进阶版"数据集,专为学习图像特征设计。相比CIFAR-10,STL-10使用96×96的大尺寸彩色图像,训练集仅5000张(每类500张),更具挑战性。本实验使用自定义轻量级CNN模型,探索在大尺寸图像上的分类效果。

环境配置

硬件环境

项目

配置

CPU

Intel/AMD处理器 (训练控制)

GPU

NVIDIA GeForce RTX 5090 Laptop GPU (模型训练)

内存

8GB+ RAM

存储

10GB+ 可用空间

软件环境

项目

版本/配置

操作系统

Windows 11 Pro

Python

3.13

PyTorch

2.11.0+cu128

torchvision

0.26.0+cu128

CUDA

12.8 (GPU加速)

PyTorch环境说明

声明:本实验使用 conda 环境 myenv 中已安装的 PyTorch (CUDA 13.0) 版本。请勿私自安装其他版本的 torch。

 # 激活已有环境
 conda activate myenv
 ​
 # 验证GPU是否可用
 python -c "import torch; print(f'CUDA available: {torch.cuda.is_available()}'); print(f'GPU: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else None}')"

数据集说明

STL-10 类别标签对照表

标签

类别(中文)

类别(English)

0

飞机

airplane

1

bird

2

汽车

car

3

cat

4

鹿

deer

5

dog

6

horse

7

猴子

monkey

8

ship

9

卡车

truck

数据集属性

属性

图像尺寸

96×96 彩色图 (RGB 3通道)

图像通道

3 (彩色)

训练集样本数

5,000

测试集样本数

8,000

类别数量

10

数据预处理代码

 from torchvision import transforms
 ​
 # 训练集数据增强
 train_transform = transforms.Compose([
     transforms.RandomCrop(96, padding=8),           # 随机裁剪
     transforms.RandomHorizontalFlip(),               # 随机水平翻转
     transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),  # 颜色抖动
     transforms.ToTensor(),                           # 转换为张量
     transforms.Normalize(mean=[0.4467, 0.4328, 0.4141], std=[0.2783, 0.2734, 0.2976])
 ])
 ​
 # 测试集/验证集预处理
 test_transform = transforms.Compose([
     transforms.ToTensor(),
     transforms.Normalize(mean=[0.4467, 0.4328, 0.4141], std=[0.2783, 0.2734, 0.2976])
 ])

模型架构

网络结构描述

STL10Net - 轻量级卷积神经网络

层类型

配置

输出尺寸

Conv Block 1

Conv(3→32)×2 + BN + ReLU + MaxPool

96×96 → 48×48

Conv Block 2

Conv(32→64)×2 + BN + ReLU + MaxPool

48×48 → 24×24

Conv Block 3

Conv(64→128)×2 + BN + ReLU + MaxPool

24×24 → 12×12

Conv Block 4

Conv(128→256)×2 + BN + ReLU + MaxPool

12×12 → 6×6

Flatten

-

256×6×6 = 9216

FC Block

Linear(9216→512) + ReLU + Dropout(0.5)

512

FC Block

Linear(512→256) + ReLU + Dropout(0.5)

256

Output

Linear(256→10)

10

模型参数统计

组件

参数数量

Conv Block 1

32×3×3×3 + 32 + 32×32×3×3 + 32 = 1,664

Conv Block 2

32×64×3×3 + 64 + 64×64×3×3 + 64 = 37,504

Conv Block 3

64×128×3×3 + 128 + 128×128×3×3 + 128 = 148,864

Conv Block 4

128×256×3×3 + 256 + 256×256×3×3 + 256 = 590,080

FC Block

9216×512 + 512 + 512×256 + 256 + 256×10 + 10 = 4,723,722

总参数量

约 6,027,000

完整模型代码

 class STL10Net(nn.Module):
     """
     STL-10专用轻量级CNN
     结构: 4个卷积块 + 2个全连接层
     输入: 3x96x96 彩色图像
     输出: 10个类别
     """
     def __init__(self, num_classes=10):
         super(STL10Net, self).__init__()
 ​
         # Conv Block 1: 96x96 -> 48x48
         self.conv_block1 = nn.Sequential(
             nn.Conv2d(3, 32, kernel_size=3, padding=1),
             nn.BatchNorm2d(32),
             nn.ReLU(inplace=True),
             nn.Conv2d(32, 32, kernel_size=3, padding=1),
             nn.BatchNorm2d(32),
             nn.ReLU(inplace=True),
             nn.MaxPool2d(2, 2)
         )
 ​
         # Conv Block 2: 48x48 -> 24x24
         self.conv_block2 = nn.Sequential(
             nn.Conv2d(32, 64, kernel_size=3, padding=1),
             nn.BatchNorm2d(64),
             nn.ReLU(inplace=True),
             nn.Conv2d(64, 64, kernel_size=3, padding=1),
             nn.BatchNorm2d(64),
             nn.ReLU(inplace=True),
             nn.MaxPool2d(2, 2)
         )
 ​
         # Conv Block 3: 24x24 -> 12x12
         self.conv_block3 = nn.Sequential(
             nn.Conv2d(64, 128, kernel_size=3, padding=1),
             nn.BatchNorm2d(128),
             nn.ReLU(inplace=True),
             nn.Conv2d(128, 128, kernel_size=3, padding=1),
             nn.BatchNorm2d(128),
             nn.ReLU(inplace=True),
             nn.MaxPool2d(2, 2)
         )
 ​
         # Conv Block 4: 12x12 -> 6x6
         self.conv_block4 = nn.Sequential(
             nn.Conv2d(128, 256, kernel_size=3, padding=1),
             nn.BatchNorm2d(256),
             nn.ReLU(inplace=True),
             nn.Conv2d(256, 256, kernel_size=3, padding=1),
             nn.BatchNorm2d(256),
             nn.ReLU(inplace=True),
             nn.MaxPool2d(2, 2)
         )
 ​
         # 全连接层: 256*6*6 -> 512 -> 256 -> 10
         self.fc_block = nn.Sequential(
             nn.Flatten(),
             nn.Linear(256 * 6 * 6, 512),
             nn.ReLU(inplace=True),
             nn.Dropout(0.5),
             nn.Linear(512, 256),
             nn.ReLU(inplace=True),
             nn.Dropout(0.5),
             nn.Linear(256, num_classes)
         )
 ​
     def forward(self, x):
         x = self.conv_block1(x)
         x = self.conv_block2(x)
         x = self.conv_block3(x)
         x = self.conv_block4(x)
         x = self.fc_block(x)
         return x

训练配置

超参数表格

超参数

说明

批量大小 (batch_size)

64

每批次训练样本数

训练轮数 (num_epochs)

30

完整数据集训练次数

学习率 (learning_rate)

0.001

Adam优化器学习率

优化器

Adam

自适应矩估计优化器

损失函数

CrossEntropyLoss

交叉熵损失函数

Dropout

0.5

防止过拟合

权重衰减

0 (默认)

L2正则化系数

数据增强配置

增强方法

参数

说明

RandomCrop

padding=8

随机裁剪回96×96

RandomHorizontalFlip

-

随机水平翻转

ColorJitter

brightness=0.2, contrast=0.2, saturation=0.2

颜色抖动

Normalize

mean=[0.4467, 0.4328, 0.4141], std=[0.2783, 0.2734, 0.2976]

STL-10标准化参数

训练过程

各Epoch结果表格

Epoch

Train Loss

Train Acc

Test Loss

Test Acc

Time(s)

Best

1

2.2433

17.60%

1.7662

26.99%

10.0

*

2

1.8394

26.78%

1.6835

33.65%

8.6

*

3

1.7669

28.24%

1.6531

32.79%

8.7

4

1.7152

30.72%

1.5668

35.98%

8.5

*

5

1.6807

31.78%

1.6520

37.15%

8.7

*

6

1.6422

33.52%

1.7044

33.20%

8.7

7

1.6455

32.84%

1.5602

37.86%

8.7

*

8

1.5952

35.76%

1.5976

37.26%

8.7

9

1.5724

36.60%

1.4599

45.10%

8.7

*

10

1.5712

36.68%

1.4827

43.41%

8.9

11

1.5404

38.18%

1.4570

43.39%

8.9

12

1.5061

39.62%

1.4909

43.73%

9.2

13

1.4946

40.16%

1.4275

44.55%

8.9

14

1.4656

41.54%

1.5042

45.27%

8.8

*

15

1.4435

43.48%

1.3500

50.17%

8.9

*

16

1.3703

46.98%

1.3394

48.21%

8.9

17

1.3748

46.92%

1.3509

47.73%

9.0

18

1.3532

48.22%

1.2584

53.20%

8.8

*

19

1.3318

48.88%

1.3168

50.94%

9.0

20

1.3020

50.66%

1.3166

53.35%

9.1

*

21

1.2549

52.40%

1.1562

57.04%

9.3

*

22

1.2276

54.20%

1.2031

56.34%

9.7

23

1.2029

55.06%

1.1718

56.98%

10.3

24

1.2111

55.26%

1.1777

57.83%

11.7

*

25

1.1871

56.82%

1.1243

60.98%

11.5

*

26

1.1432

58.02%

1.1190

60.23%

11.4

27

1.1173

58.66%

1.0548

62.64%

11.2

*

28

1.0741

60.10%

1.0956

61.00%

11.5

29

1.0885

60.14%

1.0492

62.11%

11.5

30

1.0800

59.72%

1.1034

60.64%

10.6

注: * 表示该Epoch刷新最佳测试准确率

关键指标

指标

最佳测试准确率

62.64% (Epoch 27)

最终训练准确率

59.72%

最终测试准确率

60.64%

最低测试Loss

1.0492 (Epoch 29)

总训练时间

约 285 秒 (约4分45秒)

平均每Epoch时间

约 9.5 秒

训练曲线分析

  1. 收敛速度: 模型在前10个Epoch快速收敛,测试准确率从26.99%提升至43.41%

  2. 稳定期: Epoch 10-20期间,模型进入稳定训练阶段,测试准确率在43%-53%之间波动

  3. 过拟合迹象: 训练准确率(59.72%)与测试准确率(60.64%)接近,未出现过拟合现象

  4. Loss下降: 测试Loss从1.7662持续下降至1.0492,表明模型持续学习

  5. 后期加速: Epoch 20后测试准确率提升明显,从53%提升至62%

GPU加速效果

指标

GPU型号

NVIDIA GeForce RTX 5090 Laptop GPU

总参数数量

6,027,178

GPU内存已分配

109.25 MB

GPU内存已预留

1342.00 MB

GPU内存最大分配

798.53 MB

平均每Epoch时间

~9.5秒

30 epochs总训练时间

~285秒 (~4分45秒)

RTX 5090 Laptop GPU提供了高效的加速能力,使得训练大尺寸图像模型成为可能。

完整代码

 """
 STL-10 图像分类器训练脚本
 基于PyTorch的深度学习实验
 ​
 数据集: STL-10 (96x96彩色图像, 10类)
 模型: STL10Net (轻量级CNN)
 """
 ​
 import os
 import time
 import logging
 from datetime import datetime
 ​
 import torch
 import torch.nn as nn
 import torch.optim as optim
 from torch.utils.data import DataLoader
 from torchvision import transforms
 from torchvision.datasets import STL10
 ​
 ​
 def get_device():
     """自动检测并选择最佳计算设备"""
     if torch.cuda.is_available():
         device = torch.device("cuda")
         gpu_name = torch.cuda.get_device_name(0)
         gpu_count = torch.cuda.device_count()
         print(f"[GPU] 使用CUDA设备: {gpu_name}")
         print(f"[GPU] GPU数量: {gpu_count}")
         print(f"[GPU] CUDA版本: {torch.version.cuda}")
         mem_allocated = torch.cuda.memory_allocated(0) / 1024**2
         mem_reserved = torch.cuda.memory_reserved(0) / 1024**2
         print(f"[GPU] 已分配内存: {mem_allocated:.2f} MB, 预留: {mem_reserved:.2f} MB")
     elif torch.backends.mps.is_available():
         device = torch.device("mps")
         print("[GPU] 使用Apple MPS设备")
     else:
         device = torch.device("cpu")
         print("[CPU] 使用CPU设备")
     return device
 ​
 ​
 def setup_logger(log_file):
     """配置日志:同时输出到文件和控制台"""
     logger = logging.getLogger(__name__)
     logger.setLevel(logging.INFO)
     logger.handlers.clear()
 ​
     file_handler = logging.FileHandler(log_file, encoding='utf-8')
     file_handler.setLevel(logging.INFO)
     file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
     file_handler.setFormatter(file_formatter)
 ​
     console_handler = logging.StreamHandler()
     console_handler.setLevel(logging.INFO)
     console_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
     console_handler.setFormatter(console_formatter)
 ​
     logger.addHandler(file_handler)
     logger.addHandler(console_handler)
     return logger
 ​
 ​
 class STL10Net(nn.Module):
     """STL-10专用轻量级CNN"""
     def __init__(self, num_classes=10):
         super(STL10Net, self).__init__()
 ​
         self.conv_block1 = nn.Sequential(
             nn.Conv2d(3, 32, kernel_size=3, padding=1),
             nn.BatchNorm2d(32),
             nn.ReLU(inplace=True),
             nn.Conv2d(32, 32, kernel_size=3, padding=1),
             nn.BatchNorm2d(32),
             nn.ReLU(inplace=True),
             nn.MaxPool2d(2, 2)
         )
 ​
         self.conv_block2 = nn.Sequential(
             nn.Conv2d(32, 64, kernel_size=3, padding=1),
             nn.BatchNorm2d(64),
             nn.ReLU(inplace=True),
             nn.Conv2d(64, 64, kernel_size=3, padding=1),
             nn.BatchNorm2d(64),
             nn.ReLU(inplace=True),
             nn.MaxPool2d(2, 2)
         )
 ​
         self.conv_block3 = nn.Sequential(
             nn.Conv2d(64, 128, kernel_size=3, padding=1),
             nn.BatchNorm2d(128),
             nn.ReLU(inplace=True),
             nn.Conv2d(128, 128, kernel_size=3, padding=1),
             nn.BatchNorm2d(128),
             nn.ReLU(inplace=True),
             nn.MaxPool2d(2, 2)
         )
 ​
         self.conv_block4 = nn.Sequential(
             nn.Conv2d(128, 256, kernel_size=3, padding=1),
             nn.BatchNorm2d(256),
             nn.ReLU(inplace=True),
             nn.Conv2d(256, 256, kernel_size=3, padding=1),
             nn.BatchNorm2d(256),
             nn.ReLU(inplace=True),
             nn.MaxPool2d(2, 2)
         )
 ​
         self.fc_block = nn.Sequential(
             nn.Flatten(),
             nn.Linear(256 * 6 * 6, 512),
             nn.ReLU(inplace=True),
             nn.Dropout(0.5),
             nn.Linear(512, 256),
             nn.ReLU(inplace=True),
             nn.Dropout(0.5),
             nn.Linear(256, num_classes)
         )
 ​
     def forward(self, x):
         x = self.conv_block1(x)
         x = self.conv_block2(x)
         x = self.conv_block3(x)
         x = self.conv_block4(x)
         x = self.fc_block(x)
         return x
 ​
 ​
 def train_one_epoch(model, train_loader, criterion, optimizer, device, epoch):
     """训练一个epoch"""
     model.train()
     running_loss = 0.0
     correct = 0
     total = 0
 ​
     for batch_idx, (data, target) in enumerate(train_loader):
         data, target = data.to(device), target.to(device)
 ​
         optimizer.zero_grad()
         outputs = model(data)
         loss = criterion(outputs, target)
         loss.backward()
         optimizer.step()
 ​
         running_loss += loss.item()
         _, predicted = outputs.max(1)
         total += target.size(0)
         correct += predicted.eq(target).sum().item()
 ​
         if (batch_idx + 1) % 50 == 0:
             print(f'  Epoch {epoch} - Batch {batch_idx + 1}/{len(train_loader)}: '
                   f'Loss={loss.item():.4f}, Acc={100.*correct/total:.2f}%')
 ​
     epoch_loss = running_loss / len(train_loader)
     epoch_acc = 100. * correct / total
     return epoch_loss, epoch_acc
 ​
 ​
 def evaluate(model, test_loader, criterion, device):
     """评估函数"""
     model.eval()
     test_loss = 0.0
     correct = 0
     total = 0
 ​
     with torch.no_grad():
         for data, target in test_loader:
             data, target = data.to(device), target.to(device)
             outputs = model(data)
             test_loss += criterion(outputs, target).item()
             _, predicted = outputs.max(1)
             total += target.size(0)
             correct += predicted.eq(target).sum().item()
 ​
     test_loss = test_loss / len(test_loader)
     test_acc = 100. * correct / total
     return test_loss, test_acc
 ​
 ​
 def main():
     os.makedirs('./data', exist_ok=True)
     os.makedirs('./logs', exist_ok=True)
 ​
     timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
     log_file = f'./logs/training_log_stl10_{timestamp}.txt'
 ​
     logger = setup_logger(log_file)
     logger.info("=" * 60)
     logger.info("STL-10 图像分类器训练开始")
     logger.info("=" * 60)
 ​
     device = get_device()
     logger.info(f"使用设备: {device}")
 ​
     train_transform = transforms.Compose([
         transforms.RandomCrop(96, padding=8),
         transforms.RandomHorizontalFlip(),
         transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),
         transforms.ToTensor(),
         transforms.Normalize(mean=[0.4467, 0.4328, 0.4141], std=[0.2783, 0.2734, 0.2976])
     ])
 ​
     test_transform = transforms.Compose([
         transforms.ToTensor(),
         transforms.Normalize(mean=[0.4467, 0.4328, 0.4141], std=[0.2783, 0.2734, 0.2976])
     ])
 ​
     logger.info("加载STL-10数据集...")
 ​
     train_dataset = STL10(
         root='./data',
         split='train',
         transform=train_transform,
         download=True
     )
 ​
     test_dataset = STL10(
         root='./data',
         split='test',
         transform=test_transform,
         download=True
     )
 ​
     logger.info(f"训练集大小: {len(train_dataset)}")
     logger.info(f"测试集大小: {len(test_dataset)}")
     logger.info(f"类别数量: 10")
     logger.info(f"类别名称: {test_dataset.classes}")
 ​
     train_loader = DataLoader(
         train_dataset, batch_size=64, shuffle=True, num_workers=0, pin_memory=True
     )
 ​
     test_loader = DataLoader(
         test_dataset, batch_size=128, shuffle=False, num_workers=0, pin_memory=True
     )
 ​
     model = STL10Net(num_classes=10).to(device)
     logger.info(f"\n模型结构:\n{model}")
 ​
     total_params = sum(p.numel() for p in model.parameters())
     trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
     logger.info(f"\n总参数量: {total_params:,}")
     logger.info(f"可训练参数量: {trainable_params:,}")
 ​
     criterion = nn.CrossEntropyLoss()
     optimizer = optim.Adam(model.parameters(), lr=0.001)
 ​
     num_epochs = 30
     best_acc = 0.0
 ​
     logger.info(f"\n开始训练: {num_epochs} epochs, batch_size=64")
     logger.info("-" * 60)
 ​
     for epoch in range(1, num_epochs + 1):
         epoch_start_time = time.time()
 ​
         train_loss, train_acc = train_one_epoch(
             model, train_loader, criterion, optimizer, device, epoch
         )
 ​
         test_loss, test_acc = evaluate(
             model, test_loader, criterion, device
         )
 ​
         epoch_time = time.time() - epoch_start_time
 ​
         logger.info(
             f"Epoch {epoch}/{num_epochs} - "
             f"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}% - "
             f"Test Loss: {test_loss:.4f}, Test Acc: {test_acc:.2f}% - "
             f"Time: {epoch_time:.1f}s"
         )
 ​
         if test_acc > best_acc:
             best_acc = test_acc
             model_path = f'./logs/stl10_best_model_{timestamp}.pth'
             torch.save(model.state_dict(), model_path)
             logger.info(f"*** 新最佳模型已保存! Test Acc: {best_acc:.2f}% ***")
 ​
     logger.info("-" * 60)
     logger.info(f"训练完成! 最佳测试准确率: {best_acc:.2f}%")
     logger.info(f"模型已保存到: stl10_best_model_{timestamp}.pth")
     logger.info(f"日志已保存到: {log_file}")
     logger.info("=" * 60)
 ​
     if torch.cuda.is_available():
         logger.info(f"\nGPU内存统计:")
         logger.info(f"  - 已分配: {torch.cuda.memory_allocated(0) / 1024**2:.2f} MB")
         logger.info(f"  - 已预留: {torch.cuda.memory_reserved(0) / 1024**2:.2f} MB")
         logger.info(f"  - 最大分配: {torch.cuda.max_memory_allocated(0) / 1024**2:.2f} MB")
 ​
 ​
 if __name__ == '__main__':
     main()

结论

实验总结

指标

结果

最佳测试准确率

62.64%

最终测试准确率

60.64%

训练集准确率

59.72%

总训练时间

285秒 (约4分45秒)

模型大小

~24MB (6,027,178参数)

关键发现

  1. 大尺寸图像挑战: 相比CIFAR-10 (32×32),STL-10 (96×96)的图像尺寸大了约9倍,像素多了9倍,模型需要更强的特征提取能力

  2. 小样本学习: STL-10训练集仅5000张(每类500张),远少于CIFAR-10的50000张,数据增强对防止过拟合至关重要

  3. 收敛特性: 模型在Epoch 20后进入快速提升期,测试准确率从53%提升至62%,说明网络需要更多epoch才能充分学习

  4. 无过拟合: 训练准确率与测试准确率接近,说明数据增强和Dropout有效防止了过拟合

  5. GPU加速效果: RTX 5090使得训练大尺寸图像模型成为可能,平均每epoch仅需9.5秒

改进建议

  1. 使用更深网络: 如ResNet18/34,利用残差连接提升大图像特征提取能力

  2. 学习率调度: 使用ReduceLROnPlateau或CosineAnnealing,在后期获得更细粒度的收敛

  3. 更强数据增强: 添加RandomErasing、AutoAugment等策略

  4. 尝试预训练模型: 使用ImageNet预训练的ResNet,迁移学习到STL-10

  5. 利用无标签数据: STL-10提供100000张无标签图像,可用于半监督学习

输出文件清单

文件名

说明

stl10_classifier.py

完整训练代码

training_log_stl10_20260401_144054.txt

训练日志

stl10_best_model_20260401_144054.pth

最佳模型权重

STL10_实验报告.md

本实验报告

./data/

STL-10数据集缓存目录

./logs/

训练日志和模型保存目录