import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# Setup device
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("Using device:", device)
# Transformations applied on each image
transform = transforms.Compose([
transforms.ToTensor(), # Convert images to tensor
transforms.Normalize((0.5,), (0.5,)) # Normalize the images
])
# Load the dataset
train_dataset = datasets.FashionMNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.FashionMNIST(root='./data', train=False, download=True, transform=transform)
# DataLoader
train_loader = DataLoader(train_dataset, batch_size=1000, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)
# Define the CNN model
class FashionCNN(nn.Module):
def __init__(self):
super(FashionCNN, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(1, 64, kernel_size=3, padding=1), # Input: 1 x 28 x 28, Output: 64 x 28 x 28
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)) # Output: 64 x 14 x 14
self.layer2 = nn.Sequential(
nn.Conv2d(64, 64, kernel_size=3), # Output: 64 x 12 x 12
nn.ReLU(),
nn.MaxPool2d(2)) # Output: 64 x 6 x 6
self.fc1 = nn.Linear(64 * 6 * 6, 128)
self.fc2 = nn.Linear(128, 10) # 10 classes
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = out.view(out.size(0), -1) # Flatten the output
out = self.fc1(out)
out = self.fc2(out)
return out
# Instantiate the model, loss function, and optimizer
model = FashionCNN().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# Function to train the model
def train(epoch):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
print(f'Train Epoch: {epoch} -- Loss: {loss.item():.6f}')
# Training the model
for epoch in range(1, 50): # Train for 50 epochs
train(epoch)
# Evaluation
model.eval()
with torch.no_grad():
correct = 0
total = 0
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'Accuracy of the network on the 10000 test images: {100 * correct / total}%')这段代码实现了一个基于PyTorch的**卷积神经网络(CNN)**,用于对**Fashion-MNIST数据集**进行图像分类。
下面我将从**实现原理**、**用途**和**注意事项**三个方面为你详细解释这段代码。
---
### 一、 实现原理(代码逐块解析)
这段代码遵循了深度学习图像分类的标准流程:数据准备 -> 模型构建 -> 模型训练 -> 模型评估。
1. 设备配置:
```python
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
```
自动检测是否有可用的NVIDIA GPU。如果有,则使用GPU加速计算;如果没有,则回退到CPU。
2. 数据预处理与加载:
- transforms.ToTensor():将PIL图像或NumPy数组转换为PyTorch张量,并将像素值从 [0, 255] 归一化到 [0.0, 1.0]。
- transforms.Normalize((0.5,), (0.5,)):将像素值进一步缩放到 [-1.0, 1.0] 之间,计算公式为 (x - 0.5) / 0.5。这有助于模型更快收敛。
- datasets.FashionMNIST:下载并加载Fashion-MNIST数据集(包含10类服饰灰度图,每张图大小为 1x28x28)。
- DataLoader:将数据集划分为批次batch_size=1000 表示每次喂给模型1000张图片shuffle=True 表示在训练集上打乱数据顺序,增强模型泛化能力。
3. 定义CNN模型:
- Layer 1:包含一个卷积层(输入通道1,输出通道64,3x3卷积核,padding=1保证尺寸不变)、一个ReLU激活函数和一个2x2最大池化层。图像尺寸变化1x28x28 -> 64x28x28 -> 64x14x14。
- Layer 2:包含一个卷积层(输入通道64,输出通道64,3x3卷积核,无padding)、一个ReLU激活函数和一个2x2最大池化层。图像尺寸变化64x14x14 -> 64x12x12 -> 64x6x6。
- 全连接层(FC)out.view(out.size(0), -1) 将多维特征展平为一维向量(64 6 6 = 2304)fc1 将2304维特征降维至128维fc2 将128维特征映射到10个输出节点,对应10个分类。
4. 实例化模型、损失函数与优化器:
- CrossEntropyLoss:交叉熵损失函数,适用于多分类问题,内部集成了Softmax操作。
- Adam:一种自适应学习率优化器lr=0.001 是默认且常用的学习率。
5. 训练循环:
- model.train():将模型设置为训练模式(启用Dropout和BatchNorm等层的训练行为,虽然本模型没用到)。
- 前向传播计算输出 -> 计算损失 -> 梯度清零zero_grad) -> 反向传播计算梯度backward) -> 更新权重step)。
6. 评估模型:
- model.eval():将模型设置为评估模式。
- torch.no_grad():禁用梯度计算,节省内存并加快计算速度。
- torch.max(outputs.data, 1):获取输出张量在维度1(类别维度)上的最大值及其索引,该索引即为预测的类别。
---
### 二、 用途
1. 图像分类任务:主要用于识别和分类服饰图像,如T恤、裤子、套衫、裙子、包、鞋子等(共10类)。
2. 深度学习入门与基准测试:Fashion-MNIST 是传统 MNIST(手写数字)的现代替代品,难度比手写数字稍大,非常适合用来验证CNN架构的有效性或作为计算机视觉的入门项目。
3. 教学与原型验证:这段代码结构完整且精简,常被用于教学演示,帮助初学者理解PyTorch中数据加载、模型定义、训练和评估的完整Pipeline。
---
### 三、 注意事项与改进建议
1. 注释与代码不一致:
- 代码第46行注释写着 Train for 15 epochs,但实际代码是 for epoch in range(1, 50):,实际训练了49个epoch。建议将注释与代码保持一致。
2. 缺少验证集监控(容易过拟合):
- 当前代码只打印了训练集的Loss,并在训练完全结束后才在测试集上评估。这无法观察模型在训练过程中是否发生过拟合。**建议**:在每个epoch结束后,不仅打印Train Loss,还在测试集上计算Validation Loss/Accuracy,或者引入早停机制。
3. 模型容量与数据量的匹配:
- 网络中使用了64个卷积核,对于Fashion-MNIST这种简单数据集可能参数偏多,训练49个epoch极有可能出现过拟合(训练集准确率极高,测试集准确率不再提升甚至下降)。可以考虑减少通道数(如32)或引入 nn.Dropout 层来防止过拟合。
4. Batch Size 较大:
- batch_size=1000 占用显存较大。虽然大Batch Size训练更快,但有时会导致模型泛化性能下降(陷入局部最优)。如果显存允许可以保留,否则建议设置为 64 或 128,通常能得到更好的泛化效果。
5. 学习率调度器:
- 固定的学习率 lr=0.001 在后期可能导致Loss震荡。建议添加学习率衰减策略,如 torch.optim.lr_scheduler.StepLR,在训练后期降低学习率以精细调整模型权重。
6. 未保存模型:
- 代码训练完毕后直接输出了准确率,但没有将模型参数保存到本地。建议在评估完成后添加 torch.save(model.state_dict(), 'fashion_cnn.pth'),以便后续直接加载使用,避免重复训练。