- 彩色和灰度图片测试和训练的规范写法:封装在函数中
- 展平操作:除第一个维度batchsize外全部展平
- dropout操作:训练阶段随机丢弃神经元,测试阶段eval模式关闭dropout
作业:仔细学习下测试和训练代码的逻辑,这是基础,这个代码框架后续会一直沿用,后续的重点慢慢就是转向模型定义阶段了。
在昨天的学习中,知道了彩色和灰度图片的MLP模型的定义。今天将继续剩下的步骤:训练和测试。与之前相比,最大的不同就是使用dataloader划定batch_size,进行分批次加载。
下面以Mnist手写数据集的单通道图片为例,说明训练和测试的代码逻辑。
训练部分
这里采用了函数进行封装,因为这个流程是可以复用的。
(1)遍历epoch进行训练:但是由于划分了batch,所以在每一个epoch内部需要进行batch的遍历。比如在Mnist中,设置batch_size = 64,训练集总共60000个,那么总共有938个batch(代码内部用iteration代替)。
(2)在每个batch内部,流程与之前类似:梯度清零→前向传播→损失计算→反向传播→更新参数→添加损失值。当然,这里存储的损失值指的是单个batch内部的平均值(64个数据的Loss均值),全局的loss平均值需要手动计算。
(3)额外:全局损失值计算、准确率计算(类似之前的测试集计算);打印epoch结果(可选)
(4)补充:添加了test部分和可视化曲线的功能 → 只要调用train()一个函数就可以完成训练+测试+可视化的功能。注意,函数定义的顺序不影响,调用前定义完成即可。
注:train_loader 或 test_loader 直接遍历的是一个个(data,target)元组(getitem方法返回元组),也就是当前批次的样本数据和对应的标签:
- data:形状为 [batch_size, 1, 28, 28] 的张量,比如[64,1,28,28]
- target:形状为 [batch_size] 的张量,包含对应的数字标签(0-9),比如[64]
- enumerate():返回索引和数值,对于train_loader则为batch_idx(这里为0到937)和(data, target)
- data 和 target 要迁移至同一设备:默认情况下,DataLoader加载的数据都在CPU上
# 5-训练
def train(model,epoch_num,train_loader,test_loader,criterion,optimiser,device):
model.train() # 进入训练模式
all_iter_losses = [] # 存储所有batch对应的loss值,后续可视化用
iter_indices = [] # 存储对应的batch序号
for epoch in range(epoch_num):
running_loss = 0 # 用于累加loss值,便于计算全局平均值
total = 0 # 统计总共的样本数
correct = 0 # 统计正确个数,便于计算准确率
for batch_idx,(data,target) in enumerate(train_loader):
data,target = data.to(device),target.to(device)
# 基本流程
optimiser.zero_grad() # 清零梯度
output = model(data) # 前向传播
loss = criterion(output,target) # 损失值计算
loss.backward() # 反向传播
optimiser.step() # 更新参数
# 添加损失值
iter_loss = loss.item()
all_iter_losses.append(iter_loss)
iter_indices.append(epoch*len(train_loader)+batch_idx+1) # batch序号从1开始
# 统计损失值和准确个数
running_loss += loss.item()
total += target.size(0) # 即batch_size的大小
_,predicted = output.max(1) # 返回每行(即每个样本)的最大值和对应的索引,这里我们只需要索引
correct += (predicted==target).sum().item()
# 打印结果:
if (batch_idx + 1) % 100 == 0:
print(f'Epoch: {epoch+1}/{epoch_num} | Batch: {batch_idx+1}/{len(train_loader)} '
f'| 单Batch损失: {iter_loss:.4f} | 累计平均损失: {running_loss/(batch_idx+1):.4f}')
epoch_train_loss = running_loss / len(train_loader)
epoch_train_acc = 100. * correct / total
epoch_test_loss,epoch_test_acc = test(model,test_loader,criterion,device)
# 打印
print(f'Epoch {epoch+1}/{epoch_num} 完成 | 训练准确率: {epoch_train_acc:.2f}% | 测试准确率: {epoch_test_acc:.2f}%')
# 可视化
plot_iter_losses(all_iter_losses,iter_indices)
return epoch_test_acc # 返回最终测试准确率
测试部分
测试流程
同样使用函数进行封装,流程与之前类似:
(1)流程:遍历batch,前向传播 → 损失值计算 → 全局loss均值 → 准确率计算
(2)注意:data 和 target 要迁移至设备;评估模式关闭梯度计算,节省资源
# 6-测试
def test(model,test_loader,criterion,device):
model.eval() # 进入评估模式
correct = 0
test_losses = 0
total = 0
with torch.no_grad():
for data,target in test_loader:
data,target = data.to(device),target.to(device)
out = model(data)
test_loss = criterion(out,target)
_,predicted = out.max(1)
# 统计
test_losses += test_loss.item()
correct += predicted.eq(target).sum().item()
total += target.size(0)
# 计算
avg_loss = test_losses / len(test_loader)
accuracy = 100. * correct / total
return avg_loss,accuracy # 返回损失和准确率
可视化损失曲线
对于每一个iteration(batch)的损失曲线进行可视化,得到 损失值 - batch序号 曲线。
- loss:每个batch内得到的损失平均值,这里为64个loss后求平均
- batch序号:总共的batch个数 = epoch_num(比如2)
batch_num(这里为938)= 1876
# 7-可视化
def plot_iter_losses(losses,iter_indices):
plt.figure(figsize=(10,4))
plt.plot(iter_indices,losses,alpha=0.7,label='Iteration Loss')
plt.xlabel('Interation(Batch序号)')
plt.ylabel('Loss')
plt.title('每个 Iteration 的训练损失')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

epoch = 3 ,最终测试集准确率为97.05 %。

注意事项
展平操作
Day 39 提到在定义MLP模型时,需要将二维图片转换为一维向量,有两个方法:
- Flatten 操作:将张量展平为一维数组,但保留批量维度(即batch_size)。
- view / reshape 操作:调整张量维度,但必须显式保留或指定批量维度。
- 例:输入形状(batch_size, 3 , 32 , 32)。Flatten 操作后变为(batch_size, 3*32*32=2352);view 后变为(batch_size,-1),-1为自动计算维度(这里为2352)
因此,在PyTorch中处理张量时,使用展平、维度调整时,可以发现:
- 批量维度不变性:使用flatten\view\reshape后,第一个维度batch_size保持不变
- 动态维度指定:使用-1自动计算该维度的大小,但需确保其他维度的指定合理,避免形状不匹配错误。
Dropout操作
Dropout,在训练过程随机丢弃一部分神经元,让网络不依赖于任何特定的神经元,从而避免神经网络过拟合。测试的时候使用所有神经元,进入model.eval会自动关闭dropout。
在Pytorch中,使用nn.Dropout(p=0.5),其中p的数值为随机选择一部分神经元设置为0的概率值,也就是丢弃概率(一般取值在 0.2 到 0.5 )。
例子:
class NeuralNet(nn.Module):
def __init__(self, input_size, hidden_size, num_classes, dropout_rate=0.5):
super(NeuralNet, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.relu = nn.ReLU()
self.dropout = nn.Dropout(dropout_rate)
self.fc2 = nn.Linear(hidden_size, num_classes)
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.dropout(out) # 在激活后应用Dropout
out = self.fc2(out)
return out
彩色图片
彩色图片的整个流程与灰度图片类似,以cifar-10为例,完成代码。由于彩色图片设置的参数可能更多,可以加入dropout操作对比效果:
# Cifar-10
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset,DataLoader
from torchvision import datasets,transforms
import matplotlib.pyplot as plt
# 设置中文字体支持
plt.rcParams["font.family"] = ["SimHei"]
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 设置随机种子
torch.manual_seed(42)
# 1-预处理
transform = transforms.Compose([
transforms.ToTensor(), # 归一化
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # 标准化
])
# 2-创建dataset
train_dataset = datasets.CIFAR10(
root='./data',
train=True,
download=True,
transform=transform
)
test_dataset = datasets.CIFAR10(
root='./data',
train=False,
transform=transform
)
# 3-创建dataloader
batch_size = 64
train_loader = DataLoader(train_dataset,batch_size,shuffle=True)
test_loader = DataLoader(test_dataset,batch_size,shuffle=False)
# 4- 模型定义
class MLP(nn.Module):
def __init__(self):
super(MLP,self).__init__()
self.flatten = nn.Flatten()
# 第一层
self.layer1 = nn.Linear(3072,512)
self.relu1 = nn.ReLU()
self.dropout1 = nn.Dropout(0.2) # 添加dropout,随机丢弃神经元
# 第二层
self.layer2 = nn.Linear(512,256)
self.relu2 = nn.ReLU()
self.dropout2 = nn.Dropout(0.2) # 添加dropout,随机丢弃神经元
# 第三层
self.layer3 = nn.Linear(256,10)
def forward(self,x):
# 将输入图像展平为一维
x = self.flatten(x)
# 第一层全连接 + 激活 + dropout
out = self.layer1(x)
out = self.relu1(out)
out = self.dropout1(out)
# 第二层全连接 + 激活 + dropout
out = self.layer2(out)
out = self.relu2(out)
out = self.dropout2(out)
# 第三层全连接
out = self.layer3(out)
return out
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print('使用的设备:{}'.format(device))
# 实例化
model = MLP().to(device)
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimiser = optim.Adam(model.parameters(),lr=0.001)
# 5-训练
def train(model,epoch_num,train_loader,test_loader,criterion,optimiser,device):
model.train() # 进入训练模式
all_iter_losses = [] # 存储所有batch对应的loss值,后续可视化用
iter_indices = [] # 存储对应的batch序号
for epoch in range(epoch_num):
running_loss = 0 # 用于累加loss值,便于计算全局平均值
total = 0 # 统计总共的样本数
correct = 0 # 统计正确个数,便于计算准确率
for batch_idx,(data,target) in enumerate(train_loader):
data,target = data.to(device),target.to(device)
# 基本流程
optimiser.zero_grad() # 清零梯度
output = model(data) # 前向传播
loss = criterion(output,target) # 损失值计算
loss.backward() # 反向传播
optimiser.step() # 更新参数
# 添加损失值
iter_loss = loss.item()
all_iter_losses.append(iter_loss)
iter_indices.append(epoch*len(train_loader)+batch_idx+1) # batch序号从1开始
# 统计损失值和准确个数
running_loss += loss.item()
total += target.size(0) # 即batch_size的大小
_,predicted = output.max(1) # 返回每行(即每个样本)的最大值和对应的索引,这里我们只需要索引
correct += (predicted==target).sum().item()
# 打印结果:
if (batch_idx + 1) % 100 == 0:
print(f'Epoch: {epoch+1}/{epoch_num} | Batch: {batch_idx+1}/{len(train_loader)} '
f'| 单Batch损失: {iter_loss:.4f} | 累计平均损失: {running_loss/(batch_idx+1):.4f}')
epoch_train_loss = running_loss / len(train_loader)
epoch_train_acc = 100. * correct / total
epoch_test_loss,epoch_test_acc = test(model,test_loader,criterion,device)
# 打印
print(f'Epoch {epoch+1}/{epoch_num} 完成 | 训练准确率: {epoch_train_acc:.2f}% | 测试准确率: {epoch_test_acc:.2f}%')
# 可视化
plot_iter_losses(all_iter_losses,iter_indices)
return epoch_test_acc # 返回最终测试准确率
# 6-测试
def test(model,test_loader,criterion,device):
model.eval() # 进入评估模式
correct = 0
test_losses = 0
total = 0
with torch.no_grad():
for data,target in test_loader:
data,target = data.to(device),target.to(device)
out = model(data)
test_loss = criterion(out,target)
_,predicted = out.max(1)
# 统计
test_losses += test_loss.item()
correct += predicted.eq(target).sum().item()
total += target.size(0)
# 计算
avg_loss = test_losses / len(test_loader)
accuracy = 100. * correct / total
return avg_loss,accuracy # 返回损失和准确率
# 7-可视化
def plot_iter_losses(losses,iter_indices):
plt.figure(figsize=(10,4))
plt.plot(iter_indices,losses,alpha=0.7,label='Iteration Loss')
plt.xlabel('Interation(Batch序号)')
plt.ylabel('Loss')
plt.title('每个 Iteration 的训练损失')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# 8-调用
epoch_num = 20
print("开始训练模型...")
final_acc = train(model,epoch_num,train_loader,test_loader,criterion,optimiser,device)
print('训练完成!最终准确率为{:.2f}%'.format(final_acc))


可以看到训练集和测试集相差较大,最终测试集的准确率为52.68%,训练效果较差。

总结
(1)图像数据MLP的整个流程:训练和测试的逻辑
(2)MLP在图像任务上的局限性:
- 图像中相邻像素通常具有强相关性(如边缘、纹理),但 MLP 将所有像素视为独立特征,无法利用局部空间结构。
-
深层 MLP 的参数规模呈指数级增长,容易过拟合
Mnist数据集完整代码
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset,DataLoader
from torchvision import datasets,transforms
import matplotlib.pyplot as plt
# 设置中文字体支持
plt.rcParams["font.family"] = ["SimHei"]
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 设置随机种子
torch.manual_seed(42)
# 1-预处理
transform = transforms.Compose([
transforms.ToTensor(), # 归一化
transforms.Normalize((0.1307,),(0.3081,)) # 标准化
])
# 2-创建dataset
train_dataset = datasets.MNIST(
root='./data',
train=True,
download=True,
transform=transform
)
test_dataset = datasets.MNIST(
root='./data',
train=False,
transform=transform
)
# 3-创建dataloader
train_loader = DataLoader(
dataset=train_dataset,
batch_size=64,
shuffle=True
)
test_loader = DataLoader(
dataset=test_dataset,
batch_size=64,
shuffle=False
)
# 4-定义模型
class MLP(nn.Module):
def __init__(self,input_size,hidden_size,num_classes):
super(MLP,self).__init__()
self.flatten = nn.Flatten()
self.layer1 = nn.Linear(input_size,hidden_size)
self.relu = nn.ReLU()
self.layer2 = nn.Linear(hidden_size,num_classes)
def forward(self,x):
x = self.flatten(x)
out = self.layer1(x)
out = self.relu(out)
out = self.layer2(out)
return out
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print('使用的设备:{}'.format(device))
# 实例化
model = MLP(784,128,10).to(device)
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimiser = optim.Adam(model.parameters(),lr=0.001)
# 5-训练
def train(model,epoch_num,train_loader,test_loader,criterion,optimiser,device):
model.train() # 进入训练模式
all_iter_losses = [] # 存储所有batch对应的loss值,后续可视化用
iter_indices = [] # 存储对应的batch序号
for epoch in range(epoch_num):
running_loss = 0 # 用于累加loss值,便于计算全局平均值
total = 0 # 统计总共的样本数
correct = 0 # 统计正确个数,便于计算准确率
for batch_idx,(data,target) in enumerate(train_loader):
data,target = data.to(device),target.to(device)
# 基本流程
optimiser.zero_grad() # 清零梯度
output = model(data) # 前向传播
loss = criterion(output,target) # 损失值计算
loss.backward() # 反向传播
optimiser.step() # 更新参数
# 添加损失值
iter_loss = loss.item()
all_iter_losses.append(iter_loss)
iter_indices.append(epoch*len(train_loader)+batch_idx+1) # batch序号从1开始
# 统计损失值和准确个数
running_loss += loss.item()
total += target.size(0) # 即batch_size的大小
_,predicted = output.max(1) # 返回每行(即每个样本)的最大值和对应的索引,这里我们只需要索引
correct += (predicted==target).sum().item()
# 打印结果:
if (batch_idx + 1) % 100 == 0:
print(f'Epoch: {epoch+1}/{epoch_num} | Batch: {batch_idx+1}/{len(train_loader)} '
f'| 单Batch损失: {iter_loss:.4f} | 累计平均损失: {running_loss/(batch_idx+1):.4f}')
epoch_train_loss = running_loss / len(train_loader)
epoch_train_acc = 100. * correct / total
epoch_test_loss,epoch_test_acc = test(model,test_loader,criterion,device)
# 打印
print(f'Epoch {epoch+1}/{epoch_num} 完成 | 训练准确率: {epoch_train_acc:.2f}% | 测试准确率: {epoch_test_acc:.2f}%')
# 可视化
plot_iter_losses(all_iter_losses,iter_indices)
return epoch_test_acc # 返回最终测试准确率
# 6-测试
def test(model,test_loader,criterion,device):
model.eval() # 进入评估模式
correct = 0
test_losses = 0
total = 0
with torch.no_grad():
for data,target in test_loader:
data,target = data.to(device),target.to(device)
out = model(data)
test_loss = criterion(out,target)
_,predicted = out.max(1)
# 统计
test_losses += test_loss.item()
correct += predicted.eq(target).sum().item()
total += target.size(0)
# 计算
avg_loss = test_losses / len(test_loader)
accuracy = 100. * correct / total
return avg_loss,accuracy # 返回损失和准确率
# 7-可视化
def plot_iter_losses(losses,iter_indices):
plt.figure(figsize=(10,4))
plt.plot(iter_indices,losses,alpha=0.7,label='Iteration Loss')
plt.xlabel('Interation(Batch序号)')
plt.ylabel('Loss')
plt.title('每个 Iteration 的训练损失')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# 8-调用
epoch_num = 2
print("开始训练模型...")
final_acc = train(model,epoch_num,train_loader,test_loader,criterion,optimiser,device)
print('训练完成!最终准确率为{:.2f}%'.format(final_acc))
1061

被折叠的 条评论
为什么被折叠?



