Day 40 训练和测试的规范写法

今日任务:
  1. 彩色和灰度图片测试和训练的规范写法:封装在函数中
  2. 展平操作:除第一个维度batchsize外全部展平
  3. 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)\times 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))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值