PyTorch 实现 CIFAR-10 图像分类:从数据预处理到模型训练与评估
目录
整体说明
1. 导入必要的库
2. 数据预处理
3. 加载数据集
4. 定义类别名称
5. 构建卷积神经网络 (CNN)
6. 训练准备
7. 训练模型
8. 测试模型
9. 查看各类别准确率
10. 可视化预测结果
11. 保存模型
总结
卷积神经网络
一、先搞懂:这个网络是干嘛的?
二、网络的 “零件”:每层都在干嘛?
关键参数解释:
三、网络的 “工作流程”:forward 函数详解
步骤 1:x = self.pool(torch.relu(self.conv1(x)))
步骤 2:x = self.pool(torch.relu(self.conv2(x)))
步骤 3:x = x.view(-1, 16*5*5)
步骤 4:x = torch.relu(self.fc1(x)) 到 x = self.fc3(x)
四、举个例子:网络如何识别 “一只猫”?
五、为什么要这样设计?
总结:这个网络就像一个 “图片识别小助手”
Net、Criterion、Optimizer 的作用及参数
1. net = Net():创建一个 “学生”
2. criterion = nn.CrossEntropyLoss():一个 “评分老师”
3. optimizer = optim.SGD(...):一个 “辅导老师”
4. 关键参数白话解释
整体流程:学生做题→老师评分→辅导老师纠错
总结
模型训练:
整体说明
逐句解释
1. for epoch in range(10) # 训练10个轮次:
2. running_loss = 0.0
3. for i, data in enumerate(trainloader, 0):
4. inputs, labels = data # 获取输入和标签
5. optimizer.zero_grad() # 梯度清零(避免累积)
6. outputs = net(inputs) # 前向传播
7. loss = criterion(outputs, labels) # 计算损失
8. loss.backward() # 反向传播(计算梯度)
9. optimizer.step() # 更新参数
10. running_loss += loss.item()
11. if i % 200 == 199: # 每200个批次打印一次损失
12. print(...)
13. running_loss = 0.0
14. print('Finished Training')
总结:整个过程像极了学生刷题
用测试集评估模型训练效果
整体意思
逐句说明解释
1. correct = 0
2. total = 0
3. with torch.no_grad() # 不计算梯度(测试时不需要):
4. for data in testloader:
5. images, labels = data
6. outputs = net(images)
7. _, predicted = torch.max(outputs.data, 1) # 获取预测的类别索引
8. total += labels.size(0)
9. correct += (predicted == labels).sum().item()
10. print(...)
总结:就像学生期末考
通过分类统计分析模型的“偏科情况”
整体说明
逐句说明
1. class_correct = list(0. for i in range(10))
2. class_total = list(0. for i in range(10))
3. with torch.no_grad():
4. for data in testloader:
5. images, labels = data
6. outputs = net(images)
7. _, predicted = torch.max(outputs, 1)
8. c = (predicted == labels).squeeze()
9. for i in range(len(labels)):
10. label = labels[i]
11. class_correct[label] += c[i].item()
12. class_total[label] += 1
13. for i in range(10):
14. if class_total[i] > 0:
15. print(f'Accuracy of {classes[i]} : ...')
16. else: ...
总结:就像分析每门学科的成绩
通过可视化模型预测结果看模型真实水平
逐句说明
第一部分:定义图片显示工具(def imshow(img):)
第二部分:拿一批测试图片
第三部分:显示图片和真实标签
第四部分:显示模型的预测结果
总结:就像 “贴答案对比”
这里是使用 PyTorch 框架训练了一个卷积神经网络 (CNN) 来识别 CIFAR-10 数据集中的图像的完整例子。
 
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import numpy as np
import torchvision  # 添加torchvision导入用于make_grid# 定义数据预处理
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])# 指定本地数据集路径(使用解压好的数据集)
data_path = r'D:\workspace_py\deeplean\data'  # 修改为你的实际路径# 加载训练集(使用本地解压好的数据集)
trainset = datasets.CIFAR10(root=data_path, train=True, download=False, transform=transform)
trainloader = DataLoader(trainset, batch_size=32, shuffle=True)# 加载测试集(使用本地解压好的数据集)
testset = datasets.CIFAR10(root=data_path, train=False, download=False, transform=transform)
testloader = DataLoader(testset, batch_size=32, shuffle=False)# 定义类别名称
classes = ('plane', 'car', 'bird', 'cat','deer', 'dog', 'frog', 'horse','ship', 'truck')# 定义卷积神经网络模型
class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = nn.Conv2d(3, 6, 5)self.pool = nn.MaxPool2d(2, 2)self.conv2 = nn.Conv2d(6, 16, 5)self.fc1 = nn.Linear(16 * 5 * 5, 120)self.fc2 = nn.Linear(120, 84)self.fc3 = nn.Linear(84, 10)def forward(self, x):x = self.pool(torch.relu(self.conv1(x)))x = self.pool(torch.relu(self.conv2(x)))x = x.view(-1, 16 * 5 * 5)x = torch.relu(self.fc1(x))x = torch.relu(self.fc2(x))x = self.fc3(x)return x# 创建模型实例
net = Net()# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)# 训练模型
for epoch in range(10):running_loss = 0.0for i, data in enumerate(trainloader, 0):inputs, labels = dataoptimizer.zero_grad()outputs = net(inputs)loss = criterion(outputs, labels)loss.backward()optimizer.step()running_loss += loss.item()if i % 200 == 199:print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 200:.3f}')running_loss = 0.0print('Finished Training')# 测试模型
correct = 0
total = 0
with torch.no_grad():for data in testloader:images, labels = dataoutputs = net(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} %')# 查看模型在测试集上对各类别的预测情况
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():for data in testloader:images, labels = dataoutputs = net(images)_, predicted = torch.max(outputs, 1)c = (predicted == labels).squeeze()for i in range(len(labels)):label = labels[i]class_correct[label] += c[i].item()class_total[label] += 1for i in range(10):if class_total[i] > 0:  # 防止除以零print(f'Accuracy of {classes[i]} : {100 * class_correct[i] / class_total[i]} %')else:print(f'Accuracy of {classes[i]} : No samples found')# 显示一些测试图像及其预测结果
def imshow(img):img = img / 2 + 0.5  # 反归一化npimg = img.numpy()plt.imshow(np.transpose(npimg, (1, 2, 0)))  # 从(C, H, W)转换为(H, W, C)plt.axis('off')  # 关闭坐标轴plt.show()# 获取一批测试图像
dataiter = iter(testloader)
images, labels = next(dataiter)  # 使用next()替代已弃用的.next()# 显示图像网格
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))# 进行预测
outputs = net(images)
_, predicted = torch.max(outputs, 1)print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))# 保存模型
torch.save(net.state_dict(), 'cifar10_model.pth')
print("模型已保存为 'cifar10_model.pth'")运行结果:
D:\ProgramData\anaconda3\envs\py01\python.exe D:\workspace_py\deeplean\cifar10_classifier.py 
[1,   200] loss: 2.304
[1,   400] loss: 2.302
[1,   600] loss: 2.300
[1,   800] loss: 2.298
[1,  1000] loss: 2.295
[1,  1200] loss: 2.289
[1,  1400] loss: 2.270
[2,   200] loss: 2.125
[2,   400] loss: 2.032
[2,   600] loss: 1.988
[2,   800] loss: 1.904
[2,  1000] loss: 1.853
[2,  1200] loss: 1.820
[2,  1400] loss: 1.750
[3,   200] loss: 1.681
[3,   400] loss: 1.673
[3,   600] loss: 1.641
[3,   800] loss: 1.604
[3,  1000] loss: 1.598
[3,  1200] loss: 1.575
[3,  1400] loss: 1.547
[4,   200] loss: 1.516
[4,   400] loss: 1.515
[4,   600] loss: 1.509
[4,   800] loss: 1.481
[4,  1000] loss: 1.463
[4,  1200] loss: 1.443
[4,  1400] loss: 1.431
[5,   200] loss: 1.398
[5,   400] loss: 1.410
[5,   600] loss: 1.387
[5,   800] loss: 1.394
[5,  1000] loss: 1.374
[5,  1200] loss: 1.381
[5,  1400] loss: 1.345
[6,   200] loss: 1.344
[6,   400] loss: 1.325
[6,   600] loss: 1.335
[6,   800] loss: 1.295
[6,  1000] loss: 1.326
[6,  1200] loss: 1.288
[6,  1400] loss: 1.292
[7,   200] loss: 1.273
[7,   400] loss: 1.273
[7,   600] loss: 1.261
[7,   800] loss: 1.231
[7,  1000] loss: 1.265
[7,  1200] loss: 1.244
[7,  1400] loss: 1.230
[8,   200] loss: 1.208
[8,   400] loss: 1.226
[8,   600] loss: 1.204
[8,   800] loss: 1.202
[8,  1000] loss: 1.193
[8,  1200] loss: 1.197
[8,  1400] loss: 1.157
[9,   200] loss: 1.152
[9,   400] loss: 1.148
[9,   600] loss: 1.149
[9,   800] loss: 1.144
[9,  1000] loss: 1.141
[9,  1200] loss: 1.140
[9,  1400] loss: 1.147
[10,   200] loss: 1.105
[10,   400] loss: 1.090
[10,   600] loss: 1.115
[10,   800] loss: 1.109
[10,  1000] loss: 1.121
[10,  1200] loss: 1.089
[10,  1400] loss: 1.104
Finished Training
Accuracy of the network on the 10000 test images: 60.27 %
Accuracy of plane : 67.3 %
Accuracy of car : 73.9 %
Accuracy of bird : 52.5 %
Accuracy of cat : 35.3 %
Accuracy of deer : 50.7 %
Accuracy of dog : 47.3 %
Accuracy of frog : 80.7 %
Accuracy of horse : 64.8 %
Accuracy of ship : 65.8 %
Accuracy of truck : 64.4 %
GroundTruth:    cat  ship  ship plane
Predicted:    cat   car plane  ship
模型已保存为 'cifar10_model.pth'Process finished with exit code 0整体说明
1. 导入必要的库
| import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pyplot as plt import numpy as np import torchvision # 添加torchvision导入用于make_grid | 
- torch:PyTorch 的核心库,提供张量计算和自动求导。
- torch.nn:用于构建神经网络的模块。
- torch.optim:包含优化算法(如 SGD、Adam)。
- torchvision:提供常用数据集 (CIFAR-10)、模型和图像变换工具。
- matplotlib:用于可视化图像和结果。
2. 数据预处理
| transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ]) | 
- transforms.Compose:将多个图像变换组合成流水线。 - ToTensor():将 PIL 图像或 NumPy 数组转换为张量,并归一化到 [0.0, 0]。
- Normalize:进一步将像素值归一化到 [-1.0, 1.0](通过 (x-0.5)/0.5)。
 
3. 加载数据集
| data_path = r'D:\workspace_py\deeplean\data' # 修改为你的实际路径 trainset = datasets.CIFAR10(root=data_path, train=True, download=False, transform=transform) trainloader = DataLoader(trainset, batch_size=32, shuffle=True) testset = datasets.CIFAR10(root=data_path, train=False, download=False, transform=transform) testloader = DataLoader(testset, batch_size=32, shuffle=False) | 
- CIFAR10:从本地路径加载数据集(download=False 表示不重新下载)。 - train=True:加载训练集(50,000 张图像)。
- train=False:加载测试集(10,000 张图像)。
 
- DataLoader:将数据集包装成迭代器,便于批量处理。 - batch_size=32:每次处理 32 张图像。
- shuffle=True:训练时打乱数据顺序,提高模型泛化能力。
 
4. 定义类别名称
| classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse','ship', 'truck') | 
CIFAR-10 数据集包含 10 个类别,每个类别对应一个索引(0-9)。
5. 构建卷积神经网络 (CNN)
| class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(3, 6, 5) # 输入3通道,输出6通道,卷积核5x5 self.pool = nn.MaxPool2d(2, 2) # 池化层,降采样 self.conv2 = nn.Conv2d(6, 16, 5) # 输入6通道,输出16通道 self.fc1 = nn.Linear(16 * 5 * 5, 120) # 全连接层 self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) # 最终输出10个类别 def forward(self, x): x = self.pool(torch.relu(self.conv1(x))) x = self.pool(torch.relu(self.conv2(x))) x = x.view(-1, 16 * 5 * 5) # 展平为一维向量 x = torch.relu(self.fc1(x)) x = torch.relu(self.fc2(x)) x = self.fc3(x) return x | 
- 网络结构: - 卷积层 1:提取低级特征(如边缘、颜色)。
- 池化层:压缩特征图,减少计算量。
- 卷积层 2:提取高级特征(如形状、纹理)。
- 全连接层:将特征映射到 10 个类别。
 
- 激活函数:ReLU(修正线性单元),引入非线性,增强模型表达能力。
6. 训练准备
| net = Net() # 创建模型实例 criterion = nn.CrossEntropyLoss() # 交叉熵损失函数(用于分类) optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) # 随机梯度下降优化器 | 
- 损失函数:衡量模型预测与真实标签的差异。
- 优化器:根据损失函数的梯度更新模型参数。 - lr=0.001:学习率(控制参数更新步长)。
- momentum=0.9:动量(加速收敛,减少震荡)。
 
7. 训练模型
| for epoch in range(10): # 训练10个轮次 running_loss = 0.0 for i, data in enumerate(trainloader, 0): inputs, labels = data # 获取输入和标签 optimizer.zero_grad() # 梯度清零(避免累积) outputs = net(inputs) # 前向传播 loss = criterion(outputs, labels) # 计算损失 loss.backward() # 反向传播(计算梯度) optimizer.step() # 更新参数 running_loss += loss.item() if i % 200 == 199: # 每200个批次打印一次损失 print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 200:.3f}') running_loss = 0.0 print('Finished Training') | 
- 训练流程: - 前向传播:输入图像通过网络得到预测结果。
- 计算损失:对比预测结果与真实标签。
- 反向传播:计算损失对参数的梯度。
- 参数更新:优化器根据梯度调整参数。
 
- 轮次 (epoch):整个数据集被训练的次数。
8. 测试模型
| correct = 0 total = 0 with torch.no_grad(): # 不计算梯度(测试时不需要) for data in testloader: images, labels = data outputs = net(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} %') | 
- 准确率计算:模型在测试集上正确预测的样本数占总样本数的比例。
9. 查看各类别准确率
| class_correct = list(0. for i in range(10)) class_total = list(0. for i in range(10)) with torch.no_grad(): for data in testloader: images, labels = data outputs = net(images) _, predicted = torch.max(outputs, 1) c = (predicted == labels).squeeze() for i in range(len(labels)): label = labels[i] class_correct[label] += c[i].item() class_total[label] += 1 for i in range(10): if class_total[i] > 0: # 防止除以零 print(f'Accuracy of {classes[i]} : {100 * class_correct[i] / class_total[i]} %') else: print(f'Accuracy of {classes[i]} : No samples found') | 
- 类别准确率:模型在每个类别上的表现(例如,对 “飞机” 的识别准确率可能高于 “猫”)。
10. 可视化预测结果
| def imshow(img): img = img / 2 + 0.5 # 反归一化(从[-1,1]回到[0,1]) npimg = img.numpy() plt.imshow(np.transpose(npimg, (1, 2, 0))) # 调整维度顺序(C,H,W → H,W,C) plt.axis('off') # 关闭坐标轴 plt.show() # 获取一批测试图像 dataiter = iter(testloader) images, labels = next(dataiter) # 显示图像和真实标签 imshow(torchvision.utils.make_grid(images)) print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4))) # 显示预测结果 outputs = net(images) _, predicted = torch.max(outputs, 1) print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4))) | 
- 图像展示: - 从测试集随机选取一批图像。
- 显示图像的真实标签和模型预测结果。
 
11. 保存模型
| torch.save(net.state_dict(), 'cifar10_model.pth') print("模型已保存为 'cifar10_model.pth'") | 
- 保存参数:将训练好的模型权重保存到文件,便于后续使用。
总结
这个代码实现了一个完整的深度学习流程:
- 数据准备:加载并预处理 CIFAR-10 数据集。
- 模型设计:构建卷积神经网络。
- 模型训练:通过优化器调整参数,最小化损失函数。
- 模型评估:在测试集上验证模型准确率。
- 结果可视化:展示模型预测效果。
作为小白,你可以尝试调整以下参数:
- 学习率 (lr):影响训练速度和稳定性。
- 批次大小 (batch_size):较大的批次可能加速训练,但需要更多内存。
- 网络层数:增加卷积层或全连接层可能提高准确率。
- 训练轮次 (epochs):增加轮次可能提高准确率,但也可能导致过拟合。
卷积神经网络
class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = nn.Conv2d(3, 6, 5)self.pool = nn.MaxPool2d(2, 2)self.conv2 = nn.Conv2d(6, 16, 5)self.fc1 = nn.Linear(16 * 5 * 5, 120)self.fc2 = nn.Linear(120, 84)self.fc3 = nn.Linear(84, 10)def forward(self, x):x = self.pool(torch.relu(self.conv1(x)))x = self.pool(torch.relu(self.conv2(x)))x = x.view(-1, 16 * 5 * 5)x = torch.relu(self.fc1(x))x = torch.relu(self.fc2(x))x = self.fc3(x)return x这段代码定义了一个卷积神经网络(CNN),专门用来处理图像。咱们用 “处理照片” 的生活场景来类比,一步步给你讲明白:
一、先搞懂:这个网络是干嘛的?
就像人眼识别照片里的东西(比如 “这是一只猫”),这个网络的作用是:
输入一张彩色图片 → 经过多层处理 → 输出 “这张图属于 10 个类别中的哪一个”(比如飞机、猫、汽车等)。
它比普通的 “全连接网络” 厉害,因为它能像人眼一样,先看局部细节(比如边缘、颜色),再组合成整体特征(比如 “有耳朵 + 有尾巴 = 可能是猫”),特别适合处理图像。
二、网络的 “零件”:每层都在干嘛?
先看 __init__ 里定义的 “零件”,这些是网络的核心层:
| 零件名 | 白话解释(类比处理照片) | 作用 | 
| self.conv1 | 第一个 “放大镜”,能看到图片里的边缘、颜色块等细节 | 提取低级特征(基础细节) | 
| self.pool | 一个 “缩小镜”,把图片缩小一半,保留关键信息 | 减少计算量,聚焦重要特征 | 
| self.conv2 | 第二个 “放大镜”,能看到更复杂的组合(比如 “边缘 + 颜色 = 眼睛”) | 提取高级特征(组合细节) | 
| self.fc1~fc3 | 三个 “分类器”,把前面看到的特征汇总,判断最终类别 | 从特征到类别的映射 | 
关键参数解释:
- nn.Conv2d(3, 6, 5): - 3:输入是彩色图片(有红、绿、蓝 3 个颜色通道)。
- 6:用 6 种不同的 “滤镜” 去看图片(每种滤镜提取一种细节)。
- 5:每个滤镜的大小是 5×5 像素(相当于用 5×5 的小方块扫过整个图片)。
 
- nn.MaxPool2d(2, 2): - 把图片的高和宽都缩小一半(比如 32×32 的图→16×16),只保留每个小区域里最亮的像素(关键信息)。
 
- nn.Linear(16*5*5, 120): - 全连接层,就像 “汇总报告”:把前面提取的所有特征(16×5×5 个数字)转换成 120 个 “关键特征值”。
- 最后 fc3 输出 10 个值,对应 10 个类别的 “可能性分数”(分数最高的就是网络认为的类别)。
 
三、网络的 “工作流程”:forward 函数详解
forward 函数定义了图片从输入到输出的 “处理步骤”,就像工厂的流水线:
步骤 1:x = self.pool(torch.relu(self.conv1(x)))
- 先用 conv1 这个 “放大镜” 扫一遍图片,提取边缘、颜色块等基础细节(得到 6 个特征图)。
- 再用 torch.relu 这个 “过滤器”:只保留有用的特征(把负数变成 0,相当于 “不重要的细节忽略掉”)。
- 最后用 pool 这个 “缩小镜” 把特征图缩小一半(6 个特征图都变小,减少计算量)。
步骤 2:x = self.pool(torch.relu(self.conv2(x)))
- 用 conv2 这个 “高级放大镜” 看前面缩小后的特征图:把基础细节组合成更复杂的特征(比如 “边缘 + 颜色 = 圆形的眼睛”),得到 16 个更抽象的特征图。
- 再经过 relu 过滤和 pool 缩小,特征图变得更小,但保留的都是关键信息。
步骤 3:x = x.view(-1, 16*5*5)
- 前面的特征图是 “二维的”(像小图片),这一步把它 “摊平” 成一维的数字列表(比如 16 个 5×5 的特征图→16×5×5=400 个数字)。
- 就像把一堆照片剪成小碎片,再排成一条直线,方便后面的 “分类器” 处理。
步骤 4:x = torch.relu(self.fc1(x)) 到 x = self.fc3(x)
- fc1 把 400 个数字转换成 120 个 “关键特征值”(比如 “眼睛特征值”“尾巴特征值”)。
- fc2 再把 120 个值浓缩成 84 个更核心的特征。
- fc3 最后输出 10 个值,分别对应 10 个类别的 “可能性”(比如 “猫的可能性 = 90 分,狗的可能性 = 5 分”)。
四、举个例子:网络如何识别 “一只猫”?
- 输入:一张 32×32 的猫的彩色图片。
- conv1+pool:看到猫的耳朵边缘、毛色块(低级特征),图片缩小到 14×14(因为 5×5 滤镜扫描后会变小)。
- conv2+pool:把边缘和颜色组合,认出 “三角形的耳朵”“椭圆形的眼睛”(高级特征),图片缩小到 5×5。
- 摊平:把 5×5 的特征图变成 400 个数字。
- 全连接层:汇总这些数字,计算出 “猫” 的可能性最高,最终输出 “这是猫”。
五、为什么要这样设计?
- 卷积层(conv):只看局部细节,不用处理整个图片,计算量小,还能识别不同位置的相同特征(比如不管猫在图片左边还是右边,都能认出是猫)。
- 池化层(pool):缩小图片,减少计算量,同时让网络对图片的微小变形不敏感(比如猫稍微歪一点也能认出来)。
- ReLU 激活函数:给网络加入 “非线性” 能力,让它能识别复杂的特征(如果没有它,网络就是简单的线性模型,连曲线都认不出来)。
- 全连接层(fc):把前面学到的所有特征汇总,最终 “投票” 决定图片的类别。
总结:这个网络就像一个 “图片识别小助手”
它的工作逻辑和人眼很像:先看细节,再组合成整体,最后判断是什么。
你可以把它想象成一个 “流水线工厂”:
- 卷积层是 “采摘工”,挑出有用的细节;
- 池化层是 “打包工”,压缩信息;
- 全连接层是 “质检员”,最终判断产品类别。
这样设计的好处是:识别图片又快又准,比普通的神经网络更适合处理图像任务~
Net、Criterion、Optimizer 的作用及参数
# 创建模型实例
net = Net()# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)咱们用 “学生做题” 的场景来类比:
1. net = Net():创建一个 “学生”
- 这个 “学生”(模型)的大脑里装着咱们之前设计的神经网络(卷积层、全连接层等),就像一个刚开始学认图片的小孩,脑子里的 “知识”(网络参数)都是随机的,一开始啥也认不出来。
- 咱们的目标是:通过训练,让这个 “学生” 学会看图片识物(比如准确区分猫、飞机等)。
2. criterion = nn.CrossEntropyLoss():一个 “评分老师”
- 这个 “老师” 的作用是:给学生的答案打分,判断学生做得对不对、错得有多离谱。
- 比如: - 学生把 “猫” 认成了 “狗”,老师会说 “错了,扣 5 分”;
- 学生把 “猫” 认成了 “飞机”,老师会说 “错得更离谱,扣 10 分”;
- 学生答对了,老师说 “没错,扣 0 分”。
 
- 这个 “扣多少分” 就是损失值,损失值越小,说明学生做得越好。
3. optimizer = optim.SGD(...):一个 “辅导老师”
- 这个 “辅导老师” 的作用是:根据评分老师的反馈(损失值),帮学生改错题,让学生下次做得更好。
- 具体来说,它会告诉学生:“上次这道题错了,下次应该往哪个方向改(调整大脑里的知识),改多大力度”。
4. 关键参数白话解释
- lr=0.001(学习率):
相当于 “改题的幅度”。比如学生把 “猫” 认成了 “狗”,辅导老师会说:“下次看图片时,多注意‘猫有尖耳朵’这个特征,调整的幅度小一点(0.001),别一下子改太猛”。
-  - 如果学习率太大(比如 10),学生可能 “矫枉过正”(这次认成狗,下次直接认成飞机);
- 如果学习率太小(比如 0.00001),学生改得太慢,学半天没进步。
 
- momentum=0.9(动量):
相当于 “惯性”。比如学生连续 3 次都把 “猫” 认错,而且每次辅导老师都指出 “应该多注意耳朵”,动量就会让学生 “顺着这个方向继续改”,加速进步。
-  - 就像推箱子:一开始推不动,推一会儿有了惯性,就越推越顺,不容易来回晃(减少训练时的 “震荡”)。
 
整体流程:学生做题→老师评分→辅导老师纠错
- 学生做题:net(inputs) 让模型看图片,输出一个预测结果(比如 “这是狗”)。
- 老师评分:criterion(outputs, labels) 对比预测结果和真实标签(比如真实是 “猫”),算出损失值(错得有多离谱)。
- 辅导老师纠错:optimizer.step() 根据损失值的 “梯度”(错的方向和程度),调整模型里的参数(学生大脑里的知识),让下次预测更准。
- 重复以上步骤:做 10 轮题(10 个 epoch)后,学生就慢慢学会准确认图片了。
总结
- net 是 “学生”,负责做题(预测);
- criterion 是 “评分老师”,负责判断对错(算损失);
- optimizer 是 “辅导老师”,负责帮学生改题(调参数);
- lr 和 momentum 是辅导老师的 “教学技巧”,让学生学得又快又稳。
整个过程就像:一个啥也不会的小孩,在两位老师的帮助下,通过反复做题纠错,最终学会了准确识别图片~
模型训练:
for epoch in range(10):  # 训练10个轮次running_loss = 0.0for i, data in enumerate(trainloader, 0):inputs, labels = data  # 获取输入和标签optimizer.zero_grad()  # 梯度清零(避免累积)outputs = net(inputs)  # 前向传播loss = criterion(outputs, labels)  # 计算损失loss.backward()  # 反向传播(计算梯度)optimizer.step()  # 更新参数running_loss += loss.item()if i % 200 == 199:  # 每200个批次打印一次损失print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 200:.3f}')running_loss = 0.0print('Finished Training')D:\ProgramData\anaconda3\envs\py01\python.exe D:\workspace_py\deeplean\cifar10_classifier.py 
[1,   200] loss: 2.304
[1,   400] loss: 2.302
[1,   600] loss: 2.300
[1,   800] loss: 2.298
[1,  1000] loss: 2.295
[1,  1200] loss: 2.289
[1,  1400] loss: 2.270
[2,   200] loss: 2.125
[2,   400] loss: 2.032
[2,   600] loss: 1.988
[2,   800] loss: 1.904
[2,  1000] loss: 1.853
[2,  1200] loss: 1.820
[2,  1400] loss: 1.750
[3,   200] loss: 1.681
[3,   400] loss: 1.673
[3,   600] loss: 1.641
[3,   800] loss: 1.604
[3,  1000] loss: 1.598
[3,  1200] loss: 1.575
[3,  1400] loss: 1.547
[4,   200] loss: 1.516
[4,   400] loss: 1.515
[4,   600] loss: 1.509
[4,   800] loss: 1.481
[4,  1000] loss: 1.463
[4,  1200] loss: 1.443
[4,  1400] loss: 1.431
[5,   200] loss: 1.398
[5,   400] loss: 1.410
[5,   600] loss: 1.387
[5,   800] loss: 1.394
[5,  1000] loss: 1.374
[5,  1200] loss: 1.381
[5,  1400] loss: 1.345
[6,   200] loss: 1.344
[6,   400] loss: 1.325
[6,   600] loss: 1.335
[6,   800] loss: 1.295
[6,  1000] loss: 1.326
[6,  1200] loss: 1.288
[6,  1400] loss: 1.292
[7,   200] loss: 1.273
[7,   400] loss: 1.273
[7,   600] loss: 1.261
[7,   800] loss: 1.231
[7,  1000] loss: 1.265
[7,  1200] loss: 1.244
[7,  1400] loss: 1.230
[8,   200] loss: 1.208
[8,   400] loss: 1.226
[8,   600] loss: 1.204
[8,   800] loss: 1.202
[8,  1000] loss: 1.193
[8,  1200] loss: 1.197
[8,  1400] loss: 1.157
[9,   200] loss: 1.152
[9,   400] loss: 1.148
[9,   600] loss: 1.149
[9,   800] loss: 1.144
[9,  1000] loss: 1.141
[9,  1200] loss: 1.140
[9,  1400] loss: 1.147
[10,   200] loss: 1.105
[10,   400] loss: 1.090
[10,   600] loss: 1.115
[10,   800] loss: 1.109
[10,  1000] loss: 1.121
[10,  1200] loss: 1.089
[10,  1400] loss: 1.104
Finished Training这段咱们还是用 “学生做题” 来类比,把这段 10 行代码想象成 “学生刷题进步的过程”
整体说明
这段段代码是让 “模型学生” 通过 10 轮练习(做题),从啥也不会慢慢变成 “图像识别高手” 的过程。就像学生刷 10 遍题库,每遍都查漏补缺,最后成绩越来越高。
逐句解释
1. for epoch in range(10) # 训练10个轮次:
- 说明:让学生把整个题库(训练集)做 10 遍,每遍叫 1 个 “轮次(epoch)”。
- 为啥要做 10 遍?就像你刷数学题,第一遍错很多,第二遍少一点,多刷几遍才能记牢。这里 10 遍是经验值,也可以改成 20 遍(但可能做太多遍会 “死记硬背”,反而考不好)。
2. running_loss = 0.0
- 说明:准备一个 “错题本”,记录这一轮做题的总错误量(损失值),刚开始是空的(0.0)。
3. for i, data in enumerate(trainloader, 0):
- 说明:把题库里的题分成一小批一小批(每批 32 张图片,之前设置的batch_size=32),让学生一道一道做。
- i是题号(从 0 开始),data是这道题的 “图片和正确答案”。
4. inputs, labels = data # 获取输入和标签
- 说明:从data里拿出 “题目(图片,inputs)” 和 “正确答案(标签,labels)”。
- 比如:题目是一张猫的图片,正确答案是 “cat”。
5. optimizer.zero_grad() # 梯度清零(避免累积)
- 说明:让 “辅导老师” 先清空上一道题的纠错记录(梯度),避免影响这道题的判断。
- 就像改作业时,先擦掉上一题的批改痕迹,再改新题。
6. outputs = net(inputs) # 前向传播
- 说明:让 “学生(net)” 看题目(图片),写出自己的答案(outputs)。
- 比如学生看到猫的图片,猜是 “dog”(一开始可能乱猜)。
7. loss = criterion(outputs, labels) # 计算损失
- 说明:让 “评分老师(criterion)” 对比学生答案和正确答案,算出这道题的错误程度(loss,损失值)。
- 比如学生把 “cat” 猜成 “dog”,错误程度是 3 分;猜成 “plane”,错误程度是 8 分(错得越离谱,loss 越大)。
8. loss.backward() # 反向传播(计算梯度)
- 说明:让 “评分老师” 分析错误原因,告诉 “辅导老师”:“学生哪里错了,应该往哪个方向改(计算梯度)”。
- 比如:“学生没注意猫的尖耳朵,下次要多关注这个特征”。
9. optimizer.step() # 更新参数
- 说明:“辅导老师” 根据错误原因,帮学生调整脑子里的知识(更新模型参数)。
- 比如:“把‘尖耳朵’这个特征的重要性调高点,下次看到就不容易认错了”。
10. running_loss += loss.item()
- 说明:把这道题的错误分(loss)记到 “错题本” 上,累加起来。
11. if i % 200 == 199: # 每200个批次打印一次损失
- 说明:每做完 200 道题,就看看错题本,总结一下最近的错误情况。
- 比如每做 200 道题,算一下平均每道题错多少分,方便了解进步情况。
12. print(...)
- 说明:打印出 “第几个轮次、第几道题、最近 200 道题的平均错误分”。
- 比如:[2, 400] loss: 1.234 表示 “第 2 轮,第 400 道题,最近 200 道题平均错 1.234 分”。
- 正常情况下,这个数会越来越小,说明学生进步了。
13. running_loss = 0.0
- 说明:总结完后,清空错题本,准备记录下 200 道题的错误。
14. print('Finished Training')
- 说明:10 轮题全做完了,训练结束!学生现在已经是 “图像识别小能手” 了。
总结:整个过程像极了学生刷题
- 多轮练习:刷 10 遍题库(10 个 epoch),反复巩固。
- 批量做题:一次做 32 道题(batch_size),效率更高。
- 即时纠错:每道题做完就评分、分析错误、改正(前向传播→算损失→反向传播→更新参数)。
- 定期总结:每 200 道题看一次平均错误,了解进步情况。
最后,随着训练进行,错误分(loss)越来越小,说明模型越来越会认图片了~
用测试集评估模型训练效果
correct = 0total = 0with torch.no_grad():  # 不计算梯度(测试时不需要)for data in testloader:images, labels = dataoutputs = net(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} %')这段代码是用来检查模型训练得好不好的,就像考完试后算分数一样。咱们还是用 “学生考试” 来类比,保证你一听就懂:
整体意思
让训练好的 “模型学生” 做一套 “期末测试卷(测试集)”,然后算出它的 “正确率”,看看它到底学会了多少。
逐句说明解释
1. correct = 0
- 说明:准备一个 “计数器”,记录学生做对了多少道题,一开始是 0。
2. total = 0
- 说明:再准备一个 “总题数计数器”,记录这套试卷一共有多少道题,一开始是 0。
3. with torch.no_grad() # 不计算梯度(测试时不需要):
- 说明:“考试的时候不让学生改答案”。
- 因为测试只是为了看成绩,不需要像训练时那样 “纠错改题”,所以关闭 “梯度计算”(节省时间和内存)。
4. for data in testloader:
- 说明:把测试卷的题目分成一小批一小批(和训练时一样,每批 32 道题),让学生一道一道做。
5. images, labels = data
- 说明:从试卷里拿出 “题目(图片,images)” 和 “正确答案(标签,labels)”。
- 比如:题目是一张汽车的图片,正确答案是 “car”。
6. outputs = net(images)
- 说明:让 “学生(net)” 看题目写答案(outputs)。
- 这里的答案不是直接说 “是 car”,而是给 10 个类别打分(比如 “car 得 90 分,plane 得 5 分”),分数越高表示越可能是这个类别。
7. _, predicted = torch.max(outputs.data, 1) # 获取预测的类别索引
- 说明:从学生给的 10 个分数里,挑出最高分对应的类别,作为最终答案。
- 比如:10 个分数里 “car” 最高,那就预测这道题的答案是 “car”。
- 前面的_是个占位符,意思是 “我们只关心最高分对应的类别,不关心具体分数是多少”。
8. total += labels.size(0)
- 说明:把这一批题的数量加到 “总题数计数器” 里。
- 比如这一批有 32 道题,就给total加 32,最后算出来的总题数是 10000(CIFAR-10 测试集的总图片数)。
9. correct += (predicted == labels).sum().item()
- 说明:数一数这一批题里学生做对了多少道,加到 “正确计数器” 里。
- predicted == labels:对比学生答案和正确答案,对的打勾(True),错的打叉(False)predicted : 预报。
- .sum():把打勾的数量加起来(比如这一批对了 25 道)。
- .item():把这个数量转换成普通数字,加到correct里。
10. print(...)
- 说明:最后算 “正确率” 并打印出来。
- 正确率 = 做对的题数 ÷ 总题数 × 100%
- 比如输出 “65%”,表示模型在 10000 张测试图片中,有 65% 的图片都认对了。
总结:就像学生期末考
- 闭卷考试:with torch.no_grad() 确保只做题不纠错。
- 批量做题:一批批做完整套测试卷(10000 道题)。
- 打分规则:选最高分的类别作为答案,对的加分,错的不加分。
- 算正确率:最后用 “做对的题数 ÷ 总题数” 判断模型学得多好。
这个正确率越高(比如从 60% 升到 80%),说明模型训练得越好,越会认图片~
通过分类统计分析模型的“偏科情况”
# 查看模型在测试集上对各类别的预测情况
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():for data in testloader:images, labels = dataoutputs = net(images)_, predicted = torch.max(outputs, 1)c = (predicted == labels).squeeze()for i in range(len(labels)):label = labels[i]class_correct[label] += c[i].item()class_total[label] += 1for i in range(10):if class_total[i] > 0:  # 防止除以零print(f'Accuracy of {classes[i]} : {100 * class_correct[i] / class_total[i]} %')else:print(f'Accuracy of {classes[i]} : No samples found')这段代码是更细致地检查模型的 “偏科情况”,就像考完试后不仅算总分,还要看每门学科的得分 —— 看看模型对哪些类别的图片特别擅长,对哪些又容易认错。咱们还是用 “学生考试” 来类比:
整体说明
算完 “总分(整体正确率)” 后,再细分到每个类别(比如 “猫”“飞机”“汽车”),单独算正确率。就像考完试后,不仅知道总分 65 分,还知道 “数学考了 80 分,语文只考了 50 分”,方便找到薄弱环节。
逐句说明
1. class_correct = list(0. for i in range(10))
- 说明:准备 10 个 “小计数器”,分别记录模型在 10 个类别上做对的题数(初始都是 0)。
- 比如索引 0 对应 “plane”,索引 1 对应 “car”,以此类推。
2. class_total = list(0. for i in range(10))
- 说明:再准备 10 个 “小总题数计数器”,记录每个类别总共有多少道题(初始都是 0)。
- 比如 “cat” 这个类别在测试集里有 1000 张图片,最后这个计数器就会变成 1000。
3. with torch.no_grad():
- 说明:和之前一样,“考试时只做题不改答案”,关闭梯度计算(节省资源)。
4. for data in testloader:
- 说明:把测试卷的题目分批拿出来,让模型一道一道做(每批 32 道题)。
5. images, labels = data
- 说明:拿出 “题目(图片)” 和 “正确答案(每个图片属于哪个类别)”。
- 比如这一批有 32 张图,对应的正确答案可能是 “cat、car、plane……”。
6. outputs = net(images)
- 说明:模型看图片后,给 10 个类别打分(比如 “这张图是 cat 的可能性 90 分,dog10 分”)。
7. _, predicted = torch.max(outputs, 1)
- 说明:模型从 10 个分数里挑最高的,作为这张图的预测类别(比如选 90 分的 cat)。
8. c = (predicted == labels).squeeze()
- 说明:对比 “模型预测的类别” 和 “正确类别”,对的打勾(True),错的打叉(False),再把结果整理成简单的列表。
- 比如这一批 32 道题,对比后得到 [True, False, True, ...](对、错、对……)。
9. for i in range(len(labels)):
- 说明:逐个检查这一批里的每一道题(比如 32 道题就循环 32 次)。
10. label = labels[i]
- 说明:拿出第 i 道题的正确类别(比如正确答案是 “cat”,对应索引 3)。
11. class_correct[label] += c[i].item()
- 说明:如果这道题做对了(c [i] 是 True),就给对应类别的 “正确计数器” 加 1。
- 比如第 i 道题正确类别是 “cat”(索引 3),且做对了,就给class_correct[3]加 1。
12. class_total[label] += 1
- 说明:不管做对做错,都给对应类别的 “总题数计数器” 加 1(统计这个类别共有多少题)。
- 比如第 i 道题是 “cat”,就给class_total[3]加 1。
13. for i in range(10):
- 说明:循环 10 个类别,分别算每个类别的正确率。
14. if class_total[i] > 0:
- 说明:如果这个类别有题(避免除以 0 的错误)。
15. print(f'Accuracy of {classes[i]} : ...')
- 说明:打印 “某个类别” 的正确率(做对的题数 ÷ 总题数 ×100%)。
- 比如输出 “Accuracy of cat : 55%”,表示模型对 “猫” 的图片,只有 55% 能认对。
16. else: ...
- 说明:如果某个类别没题(实际 CIFAR-10 每个类别都有题,这里是保险措施)。
总结:就像分析每门学科的成绩
[10,  1200] loss: 1.089
[10,  1400] loss: 1.104
Finished Training
Accuracy of the network on the 10000 test images: 60.27 %
Accuracy of plane : 67.3 %
Accuracy of car : 73.9 %
Accuracy of bird : 52.5 %
Accuracy of cat : 35.3 %
Accuracy of deer : 50.7 %
Accuracy of dog : 47.3 %
Accuracy of frog : 80.7 %
Accuracy of horse : 64.8 %
Accuracy of ship : 65.8 %
Accuracy of truck : 64.4 %
GroundTruth:    cat  ship  ship plane
Predicted:    cat   car plane  ship
模型已保存为 'cifar10_model.pth'Process finished with exit code 0- 分类统计:给 10 个类别各准备一个 “错题本”,记录每个类别做对多少、总共有多少题。
- 逐题核对:每道题做完后,不仅算对错,还要记到对应类别的 “错题本” 里。
- 单独打分:最后按类别算正确率,比如 “飞机 90%、猫 50%”,一眼看出模型擅长什么、不擅长什么。
这样就能发现模型的 “偏科” 问题 —— 比如可能对 “汽车”“飞机” 这类形状规则的识别率高,对 “猫”“狗” 这类毛茸茸的识别率低,方便后续针对性改进~
通过可视化模型预测结果看模型真实水平
def imshow(img):img = img / 2 + 0.5  # 反归一化(从[-1,1]回到[0,1])npimg = img.numpy()plt.imshow(np.transpose(npimg, (1, 2, 0)))  # 调整维度顺序(C,H,W → H,W,C)plt.axis('off')  # 关闭坐标轴plt.show()# 获取一批测试图像dataiter = iter(testloader)images, labels = next(dataiter)# 显示图像和真实标签imshow(torchvision.utils.make_grid(images))print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))# 显示预测结果outputs = net(images)_, predicted = torch.max(outputs, 1)print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))这段代码的作用是把模型的预测结果 “可视化”,让你直观地看到:模型到底把图片认成了什么,和真实结果对不对得上。就像考试后,老师把你的答卷和标准答案一起贴出来给你看,一目了然。
逐句说明
第一部分:定义图片显示工具(def imshow(img):)
这是一个 “图片查看器” 函数,专门用来把电脑里的图片数据正确显示出来。
- def imshow(img): - 定义一个叫imshow的工具,专门用来显示图片(img就是要显示的图片数据)。
 
- img = img / 2 + 0.5 - 反归一化:之前预处理时,为了方便模型训练,把图片的像素值压缩到了[-1, 1](就像把照片调暗了)。这一步是把图片 “还原” 成我们人眼习惯的亮度(像素值变回[0, 1]),否则显示出来会很暗。
 
- npimg = img.numpy() - 把 PyTorch 专用的 “张量” 格式图片,转换成普通的 NumPy 数组(因为显示图片的工具只认识这种格式)。
 
- plt.imshow(np.transpose(npimg, (1, 2, 0))) - 调整图片格式: - 电脑存储图片的格式是(颜色通道, 高度, 宽度)(比如 “红 / 绿 / 蓝,28 像素高,28 像素宽”)。
- 但人眼看图片的习惯是(高度, 宽度, 颜色通道)(先看有多高多宽,再看颜色)。
- 这一步就是把格式转过来,让图片能正常显示(否则会变成奇怪的色块)。
 
 
- 调整图片格式: 
- plt.axis('off') - 关闭图片周围的坐标轴(数字和线条),让图片看起来更干净。
 
- plt.show() - 弹出窗口,把处理好的图片显示出来。
 
第二部分:拿一批测试图片
- dataiter = iter(testloader) - 把测试集(testloader)变成一个 “图片迭代器”,就像把一堆照片整理成一本 “相册”,可以一页一页往后翻。
 
- images, labels = next(dataiter) - 从 “相册” 里翻出第一页,得到: - images:这一页的图片(默认一次拿 4 张,后面只看前 4 张)。
- labels:这些图片的 “真实标签”(比如第 1 张是猫,第 2 张是汽车)。
 
 
- 从 “相册” 里翻出第一页,得到: 
第三部分:显示图片和真实标签
- imshow(torchvision.utils.make_grid(images)) - torchvision.utils.make_grid(images):把这 4 张图片拼成一张 “拼图”(方便一次性看)。
- imshow():用上面定义的工具,把这张拼图显示出来(你会看到 4 张测试图片)。
 
- print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4))) - 打印这 4 张图片的 “真实答案”。
- 比如输出 GroundTruth: cat car plane dog,意思是 “这 4 张图实际分别是猫、汽车、飞机、狗”。
- %5s 是为了让文字对齐,看起来整齐。
 
第四部分:显示模型的预测结果
- outputs = net(images) - 让训练好的模型(net)“看” 这 4 张图片,输出 “预测分数”(每个类别打个分,分数越高,模型越觉得是这个类别)。
 
- _, predicted = torch.max(outputs, 1) - 从每个图片的 10 个分数里,挑出最高分对应的类别,作为模型的 “最终预测结果”。
- 比如第 1 张图 “猫” 的分数最高,就预测它是猫。
 
- print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4))) - 打印模型的 “预测答案”。
- 比如输出 Predicted: cat car bird dog,意思是 “模型认为这 4 张图分别是猫、汽车、鸟、狗”。
- 和上面的 “真实答案” 对比,就能看出哪里对了(猫、汽车、狗),哪里错了(飞机被认成了鸟)。
 
总结:就像 “贴答案对比”
这部分代码做的事,相当于:
- 从测试卷里随机抽 4 道题(图片)。
- 把题目(图片)和标准答案(真实标签)贴出来。
- 再把学生(模型)的答案(预测结果)贴在旁边。
- 你一眼就能看出:模型哪些题做对了,哪些题做错了,错得离谱吗?
这样就不用只看干巴巴的正确率数字,能直观感受到模型的 “真实水平”~
实际运行效果:
有点悲剧 只有50% 准确率:

GroundTruth:    cat  ship  ship plane
 Predicted:    cat   car plane plane
这要通过多种方法的综合应用,来提高 CIFAR-10 模型的准确性,这就是另外的话题了 本文只是举例说明从数据预处理到模型训练与评估的过程~~~
