【基于 CNN 的食物图片分类:数据增强、最优模型保存与学习率调整实战】
目录一、项目概述二、核心技术点三、完整实现代码1.无数据增强2.数据增强3.最优模型保存4.调整学习率函数四、项目总结一、项目概述本项目基于 ** 卷积神经网络CNN** 实现食物图片分类任务核心完成三大实验需求对比有无数据增强对模型训练效果的影响实现最优模型自动保存与加载加入动态学习率调整优化训练过程二、核心技术点CNN 网络结构3 组卷积 池化层提取图片特征全连接层完成 20 分类数据增强随机旋转、翻转、裁剪、颜色抖动提升模型泛化能力最优模型保存实时监测测试准确率保存性能最好的模型学习率调整使用 StepLR 动态衰减学习率让模型收敛更稳定结果可视化绘制准确率 / 损失曲线直观对比训练效果三、完整实现代码1.无数据增强import torch from torch.utils.data import Dataset,DataLoader import numpy as np from PIL import Image from torchvision import transforms data_transforms { train: transforms.Compose([ transforms.Resize([256, 256]), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), valid: transforms.Compose([ transforms.Resize([256, 256]), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) } #做了数据增强不代表 训练效果一定会变好只能说大概率上会变好 class food_dataset(Dataset): #food_dataset是自己创建的类名称可以改为你需要的名称 def __init__(self, file_path,transformNone): #类的初始化 self.file_path file_path self.imgs [] self.labels [] self.transform transform with open(self.file_path) as f: samples [x.strip().split( ) for x in f.readlines()] for img_path, label in samples: self.imgs.append(img_path) self.labels.append(label) def __len__(self): #类实例化对象后可以使用len函数测量对象的个数 return len(self.imgs) def __getitem__(self, idx): #关键可通过索引的形式获取每一个图片数据及标签 image Image.open(self.imgs[idx]) # if self.transform: image self.transform(image) label self.labels[idx] label torch.from_numpy(np.array(label,dtype np.int64)) return image, label training_data food_dataset(file_path ./train.txt,transform data_transforms[train]) test_data food_dataset(file_path ./test.txt,transform data_transforms[valid]) train_dataloader DataLoader(training_data, batch_size64, shuffleTrue) # 64张图片为一个包 test_dataloader DataLoader(test_data, batch_size64, shuffleTrue) # 展示训练数据集中的图片 # from matplotlib import pyplot as plt # image, label iter(train_dataloader).__next__() #iter是一个迭代器函数。__next__()用于获取下一个数据 # sample image[2] #image # sample sample.permute((1, 2, 0)).numpy() #tensor数据的维度转换 # plt.imshow(sample) # plt.show() # print(Label is: {}.format(label[2].numpy())) -------------cnn卷积神经网络部分---------------------- device cuda if torch.cuda.is_available() else mps if torch.backends.mps.is_available() else cpu print(fUsing {device} device) 定义神经网络 from torch import nn class CNN(nn.Module): def __init__(self): # 输入大小 (3, 288, 288) super(CNN, self).__init__() self.conv1 nn.Sequential( #将多个层组合成一起。 nn.Conv2d( #2d一般用于图像3d用于视频数据多一个时间维度1d一般用于结构化的序列数据 in_channels3, # 图像通道个数1表示灰度图确定了卷积核 组中的个数 out_channels16,# 要得到几多少个特征图卷积核的个数 kernel_size 5, # 卷积核大小5*5 stride1, # 步长 padding2, # 一般希望卷积核处理后的结果大小与处理前的数据大小相同效果会比较好。那padding改如何设计呢建议stride为1kernel_size 2*padding1 ), # 输出的特征图为 (16, 288, 288) nn.ReLU(), # relu层 nn.MaxPool2d(kernel_size2), # 进行池化操作2x2 区域, 输出结果为 (16, 144, 144) ) self.conv2 nn.Sequential( #输入 (16, 144, 144) nn.Conv2d(16, 32, 5, 1, 2), # 输出 (32, 144, 144) nn.ReLU(), # relu层 nn.Conv2d(32, 32, 5, 1, 2), # 输出 (32, 144, 144) nn.ReLU(), nn.MaxPool2d(2), # 输出 (32, 72, 72) ) self.conv3 nn.Sequential( #输入 (32, 72, 72) nn.Conv2d(32, 128, 5, 1, 2), nn.ReLU(), # 输出 (128, 72, 72) ) self.out nn.Linear(128 * 64 * 64, 20) # 全连接层得到的结果 def forward(self, x): x self.conv1(x) x self.conv2(x) x self.conv3(x)# 输出 (64,64, 32, 32) x x.view(x.size(0), -1) # flatten操作结果为(batch_size, 64 * 32 * 32) output self.out(x) return output model CNN().to(device) print(model) def train(dataloader, model, loss_fn, optimizer): model.train() #pytorch提供2种方式来切换训练和测试的模式分别是model.train() 和 model.eval()。 # 一般用法是在训练开始之前写上model.trian()在测试时写上 model.eval() 。 batch_size_num 1 for X, y in dataloader: #其中batch为每一个数据的编号 X, y X.to(device), y.to(device) #把训练数据集和标签传入cpu或GPU pred model.forward(X) #自动初始化 w权值 loss loss_fn(pred, y) #通过交叉熵损失函数计算损失值loss # Backpropagation 进来一个batch的数据计算一次梯度更新一次网络 optimizer.zero_grad() #梯度值清零 loss.backward() #反向传播计算得到每个参数的梯度值 optimizer.step() #根据梯度更新网络参数 train_loss loss.item() #获取损失值 print(floss: {train_loss:7f} [number:{batch_size_num}]) batch_size_num 1 def test(dataloader, model, loss_fn): size len(dataloader.dataset) num_batches len(dataloader) model.eval() # test_loss, correct 0, 0 with torch.no_grad(): #一个上下文管理器关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所用内存消耗。 for X, y in dataloader:#一个批次的循环 X, y X.to(device), y.to(device) pred model.forward(X) test_loss loss_fn(pred, y).item() # correct (pred.argmax(1) y).type(torch.float).sum().item() a (pred.argmax(1) y) #dim1表示每一行中的最大值对应的索引号dim0表示每一列中的最大值对应的索引号 b (pred.argmax(1) y).type(torch.float) test_loss / num_batches correct / size print(fTest result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}) acc_s.append(correct) loss_s.append(test_loss) loss_fn nn.CrossEntropyLoss() #创建交叉熵损失函数对象因为手写字识别中一共有10个数字输出会有10个结果 optimizer torch.optim.Adam(model.parameters(), lr0.001)#创建一个优化器SGD为随机梯度下降算法 训练模型 epochs 10 acc_s []#用来保存测试集每一轮的acc loss_s []#用来报错测试集每一轮的loss for t in range(epochs): print(fEpoch {t1}\n-------------------------------) train(train_dataloader, model, loss_fn, optimizer) test(test_dataloader, model, loss_fn) #什么时候可以确定模型训练好了 #1、loss已经稳定在一定区间内震荡 #2、调整参数 #3、改进算法 绘制训练效果曲线 from matplotlib import pyplot as plt plt.subplot(1,2,1) plt.plot(range(0,epochs),acc_s) plt.xlabel(epoch) plt.ylabel(accuracy) plt.subplot(1,2,2) plt.plot(range(0,epochs),loss_s) plt.xlabel(epoch) plt.ylabel(loss) plt.show() print(Done!) #运行结果2.数据增强import torch from torch.utils.data import Dataset,DataLoader import numpy as np from PIL import Image from torchvision import transforms data_transforms { #机器学习的时候数据进行归一化 图片做归一化 -0~1 train: transforms.Compose([ transforms.Resize([300,300]), #是图像变换大小 opencv int8 0~ transforms.RandomRotation(45),#随机旋转-45到45度之间随机选 transforms.CenterCrop(256),#从中心开始裁剪[256,256] transforms.RandomHorizontalFlip(p0.5),#随机水平翻转 选择一个概率概率 transforms.RandomVerticalFlip(p0.5),#随机垂直翻转 transforms.ColorJitter(brightness0.2, contrast0.1, saturation0.1, hue0.1),#参数1为亮度参数2为对比度参数3为饱和度参数4为色相 transforms.RandomGrayscale(p0.1),#概率转换成灰度率3通道就是RGB, transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])#标准化均值标准差, ]), valid:#验证集 不需要对图像进行数据增强 transforms.Compose([ transforms.Resize([256,256]), # transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), } #做了数据增强不代表 训练效果一定会变好只能说大概率上会变好 class food_dataset(Dataset): #food_dataset是自己创建的类名称可以改为你需要的名称 def __init__(self, file_path,transformNone): #类的初始化 self.file_path file_path self.imgs [] self.labels [] self.transform transform with open(self.file_path) as f: samples [x.strip().split( ) for x in f.readlines()] for img_path, label in samples: self.imgs.append(img_path) self.labels.append(label) def __len__(self): #类实例化对象后可以使用len函数测量对象的个数 return len(self.imgs) def __getitem__(self, idx): #关键可通过索引的形式获取每一个图片数据及标签 image Image.open(self.imgs[idx]) # if self.transform: image self.transform(image) label self.labels[idx] label torch.from_numpy(np.array(label,dtype np.int64)) return image, label training_data food_dataset(file_path ./train.txt,transform data_transforms[train]) test_data food_dataset(file_path ./test.txt,transform data_transforms[valid]) train_dataloader DataLoader(training_data, batch_size64, shuffleTrue) # 64张图片为一个包 test_dataloader DataLoader(test_data, batch_size64, shuffleTrue) # 展示训练数据集中的图片 # from matplotlib import pyplot as plt # image, label iter(train_dataloader).__next__() #iter是一个迭代器函数。__next__()用于获取下一个数据 # sample image[2] #image # sample sample.permute((1, 2, 0)).numpy() #tensor数据的维度转换 # plt.imshow(sample) # plt.show() # print(Label is: {}.format(label[2].numpy())) -------------cnn卷积神经网络部分---------------------- device cuda if torch.cuda.is_available() else mps if torch.backends.mps.is_available() else cpu print(fUsing {device} device) 定义神经网络 from torch import nn class CNN(nn.Module): def __init__(self): # 输入大小 (3, 288, 288) super(CNN, self).__init__() self.conv1 nn.Sequential( #将多个层组合成一起。 nn.Conv2d( #2d一般用于图像3d用于视频数据多一个时间维度1d一般用于结构化的序列数据 in_channels3, # 图像通道个数1表示灰度图确定了卷积核 组中的个数 out_channels16,# 要得到几多少个特征图卷积核的个数 kernel_size 5, # 卷积核大小5*5 stride1, # 步长 padding2, # 一般希望卷积核处理后的结果大小与处理前的数据大小相同效果会比较好。那padding改如何设计呢建议stride为1kernel_size 2*padding1 ), # 输出的特征图为 (16, 288, 288) nn.ReLU(), # relu层 nn.MaxPool2d(kernel_size2), # 进行池化操作2x2 区域, 输出结果为 (16, 144, 144) ) self.conv2 nn.Sequential( #输入 (16, 144, 144) nn.Conv2d(16, 32, 5, 1, 2), # 输出 (32, 144, 144) nn.ReLU(), # relu层 nn.Conv2d(32, 32, 5, 1, 2), # 输出 (32, 144, 144) nn.ReLU(), nn.MaxPool2d(2), # 输出 (32, 72, 72) ) self.conv3 nn.Sequential( #输入 (32, 72, 72) nn.Conv2d(32, 128, 5, 1, 2), nn.ReLU(), # 输出 (128, 72, 72) ) self.out nn.Linear(128 * 64 * 64, 20) # 全连接层得到的结果 def forward(self, x): x self.conv1(x) x self.conv2(x) x self.conv3(x)# 输出 (64,64, 32, 32) x x.view(x.size(0), -1) # flatten操作结果为(batch_size, 64 * 32 * 32) output self.out(x) return output model CNN().to(device) print(model) def train(dataloader, model, loss_fn, optimizer): model.train() #pytorch提供2种方式来切换训练和测试的模式分别是model.train() 和 model.eval()。 # 一般用法是在训练开始之前写上model.trian()在测试时写上 model.eval() 。 batch_size_num 1 for X, y in dataloader: #其中batch为每一个数据的编号 X, y X.to(device), y.to(device) #把训练数据集和标签传入cpu或GPU pred model.forward(X) #自动初始化 w权值 loss loss_fn(pred, y) #通过交叉熵损失函数计算损失值loss # Backpropagation 进来一个batch的数据计算一次梯度更新一次网络 optimizer.zero_grad() #梯度值清零 loss.backward() #反向传播计算得到每个参数的梯度值 optimizer.step() #根据梯度更新网络参数 train_loss loss.item() #获取损失值 print(floss: {train_loss:7f} [number:{batch_size_num}]) batch_size_num 1 def test(dataloader, model, loss_fn): size len(dataloader.dataset) num_batches len(dataloader) model.eval() # test_loss, correct 0, 0 with torch.no_grad(): #一个上下文管理器关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所用内存消耗。 for X, y in dataloader:#一个批次的循环 X, y X.to(device), y.to(device) pred model.forward(X) test_loss loss_fn(pred, y).item() # correct (pred.argmax(1) y).type(torch.float).sum().item() a (pred.argmax(1) y) #dim1表示每一行中的最大值对应的索引号dim0表示每一列中的最大值对应的索引号 b (pred.argmax(1) y).type(torch.float) test_loss / num_batches correct / size print(fTest result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}) acc_s.append(correct) loss_s.append(test_loss) loss_fn nn.CrossEntropyLoss() #创建交叉熵损失函数对象因为手写字识别中一共有10个数字输出会有10个结果 optimizer torch.optim.Adam(model.parameters(), lr0.001)#创建一个优化器SGD为随机梯度下降算法 训练模型 epochs 10 acc_s []#用来保存测试集每一轮的acc loss_s []#用来报错测试集每一轮的loss for t in range(epochs): print(fEpoch {t1}\n-------------------------------) train(train_dataloader, model, loss_fn, optimizer) test(test_dataloader, model, loss_fn) #什么时候可以确定模型训练好了 #1、loss已经稳定在一定区间内震荡 #2、调整参数 #3、改进算法 绘制训练效果曲线 from matplotlib import pyplot as plt plt.subplot(1,2,1) plt.plot(range(0,epochs),acc_s) plt.xlabel(epoch) plt.ylabel(accuracy) plt.subplot(1,2,2) plt.plot(range(0,epochs),loss_s) plt.xlabel(epoch) plt.ylabel(loss) plt.show() print(Done!) #运行结果1. 有无数据增强对比• 无数据增强准确率波动大损失容易回升模型泛化能力差• 有数据增强准确率稳步上升曲线更平滑有效缓解过拟合3.最优模型保存训练过程中自动保存测试准确率最高的模型避免训练后期模型退化方便直接用于预测。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import Dataset, DataLoader from torchvision import transforms from PIL import Image import warnings warnings.filterwarnings(ignore) # 1. 基础配置 device torch.device(cuda if torch.cuda.is_available() else cpu) print(f使用设备: {device}) # 图像预处理 transform transforms.Compose([ transforms.Resize((256, 256)), transforms.ToTensor(), transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) ]) # 2. 数据集类 class FoodDataset(Dataset): def __init__(self, txt_path, transformNone): self.transform transform self.imgs [] self.labels [] with open(txt_path, r, encodingutf-8) as f: for line in f.readlines(): line line.strip() if not line: continue img_path, label line.split() self.imgs.append(img_path) self.labels.append(int(label)) def __len__(self): return len(self.imgs) def __getitem__(self, idx): img Image.open(self.imgs[idx]).convert(RGB) if self.transform: img self.transform(img) label torch.tensor(self.labels[idx], dtypetorch.long) return img, label # 3. CNN 模型定义 class CNN(nn.Module): def __init__(self, num_classes5): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 16, kernel_size3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(16, 32, kernel_size3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(), nn.MaxPool2d(2) ) self.classifier nn.Linear(64 * 32 * 32, num_classes) def forward(self, x): x self.features(x) x x.flatten(1) x self.classifier(x) return x # 4. 训练函数你的原代码 def train(dataloader, model, loss_fn, optimizer): model.train() for X, y in dataloader: X, y X.to(device), y.to(device) pred model.forward(X) loss loss_fn(pred, y) optimizer.zero_grad() loss.backward() optimizer.step() # 5. 测试函数你的原代码 保存最优模型 best_acc 0 acc_s [] loss_s [] def test(dataloader, model, loss_fn): global best_acc size len(dataloader.dataset) num_batches len(dataloader) model.eval() test_loss, correct 0, 0 with torch.no_grad(): for X, y in dataloader: X, y X.to(device), y.to(device) pred model.forward(X) test_loss loss_fn(pred, y).item() correct (pred.argmax(1) y).type(torch.float).sum().item() test_loss / num_batches correct / size print(fTest result: \n Accuracy: {(100*correct):0.1f}%, Avg loss: {test_loss:8f}) acc_s.append(correct) loss_s.append(test_loss) # 保存最优模型的2种方法你的原代码 if correct best_acc: best_acc correct # 1、保存模型参数方法 # torch.save(model.state_dict(), best2026-01.pth) # 2、保存完整模型 torch.save(model, best2026-01.pt) print(faccuracy: {100*best_acc:.1f}%) # 6. 训练主循环你的原代码 if __name__ __main__: # 加载数据 batch_size 8 train_dataset FoodDataset(trainda.txt, transformtransform) test_dataset FoodDataset(testda.txt, transformtransform) train_dataloader DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue) test_dataloader DataLoader(test_dataset, batch_sizebatch_size, shuffleFalse) # 自动识别类别数 all_labels [] with open(trainda.txt, encodingutf-8) as f: for line in f: if line.strip(): all_labels.append(int(line.strip().split()[-1])) num_classes max(all_labels) 1 # 初始化模型、损失函数、优化器 model CNN(num_classes).to(device) loss_fn nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-4) # 训练模型你的原代码 epochs 150 for t in range(epochs): print(fEpoch {t1}\n-------------------------------) train(train_dataloader, model, loss_fn, optimizer) test(test_dataloader, model, loss_fn) print(f\nDone!Best accurcy: {100*best_acc:.1f}%) # 7. 加载最优模型你的原代码 def load_best_model(): # 方法1读取参数的方法需要先定义模型结构 # model CNN().to(device) # model.load_state_dict(torch.load(best.pth)) # 方法2读取完整模型的方法无需提前创建model model CNN().to(device) model torch.load(best2026-01.pt) model.eval() # 固定模型参数和数据防止后面被修改 return model # 示例加载模型并预测 if __name__ __main__: model load_best_model() print(模型加载成功)运行结果4.调整学习率函数每 5 轮学习率衰减为原来的 1/2让模型前期快速收敛后期精细优化提升最终精度。import torch from torch.utils.data import Dataset,DataLoader import numpy as np from PIL import Image from torchvision import transforms data_transforms { #机器学习的时候数据进行归一化 图片做归一化 -0~1 train: transforms.Compose([ transforms.Resize([300,300]), #是图像变换大小 opencv int8 0~ transforms.RandomRotation(45),#随机旋转-45到45度之间随机选 transforms.CenterCrop(256),#从中心开始裁剪[256,256] transforms.RandomHorizontalFlip(p0.5),#随机水平翻转 选择一个概率概率 transforms.RandomVerticalFlip(p0.5),#随机垂直翻转 transforms.ColorJitter(brightness0.2, contrast0.1, saturation0.1, hue0.1),#参数1为亮度参数2为对比度参数3为饱和度参数4为色相 transforms.RandomGrayscale(p0.1),#概率转换成灰度率3通道就是RGB, transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])#标准化均值标准差, ]), valid:#验证集 不需要对图像进行数据增强 transforms.Compose([ transforms.Resize([256,256]), # transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), } #做了数据增强不代表 训练效果一定会变好只能说大概率上会变好 class food_dataset(Dataset): #food_dataset是自己创建的类名称可以改为你需要的名称 def __init__(self, file_path,transformNone): #类的初始化 self.file_path file_path self.imgs [] self.labels [] self.transform transform with open(self.file_path) as f: samples [x.strip().split( ) for x in f.readlines()] for img_path, label in samples: self.imgs.append(img_path) self.labels.append(label) def __len__(self): #类实例化对象后可以使用len函数测量对象的个数 return len(self.imgs) def __getitem__(self, idx): #关键可通过索引的形式获取每一个图片数据及标签 image Image.open(self.imgs[idx]) # if self.transform: image self.transform(image) label self.labels[idx] label torch.from_numpy(np.array(label,dtype np.int64)) return image, label training_data food_dataset(file_path ./train.txt,transform data_transforms[train]) test_data food_dataset(file_path ./test.txt,transform data_transforms[valid]) train_dataloader DataLoader(training_data, batch_size64, shuffleTrue) # 64张图片为一个包 test_dataloader DataLoader(test_data, batch_size64, shuffleTrue) 展示训练数据集中的图片 # from matplotlib import pyplot as plt # image, label iter(train_dataloader).__next__() #iter是一个迭代器函数。__next__()用于获取下一个数据 # sample image[2] #image # sample sample.permute((1, 2, 0)).numpy() #tensor数据的维度转换 # plt.imshow(sample) # plt.show() # print(Label is: {}.format(label[2].numpy())) -------------cnn卷积神经网络部分---------------------- device cuda if torch.cuda.is_available() else mps if torch.backends.mps.is_available() else cpu print(fUsing {device} device) 定义神经网络 from torch import nn class CNN(nn.Module): def __init__(self): # 输入大小 (3, 288, 288) super(CNN, self).__init__() self.conv1 nn.Sequential( #将多个层组合成一起。 nn.Conv2d( #2d一般用于图像3d用于视频数据多一个时间维度1d一般用于结构化的序列数据 in_channels3, # 图像通道个数1表示灰度图确定了卷积核 组中的个数 out_channels16,# 要得到几多少个特征图卷积核的个数 kernel_size 5, # 卷积核大小5*5 stride1, # 步长 padding2, # 一般希望卷积核处理后的结果大小与处理前的数据大小相同效果会比较好。那padding改如何设计呢建议stride为1kernel_size 2*padding1 ), # 输出的特征图为 (16, 288, 288) nn.ReLU(), # relu层 nn.MaxPool2d(kernel_size2), # 进行池化操作2x2 区域, 输出结果为 (16, 144, 144) ) self.conv2 nn.Sequential( #输入 (16, 144, 144) nn.Conv2d(16, 32, 5, 1, 2), # 输出 (32, 144, 144) nn.ReLU(), # relu层 nn.Conv2d(32, 32, 5, 1, 2), # 输出 (32, 144, 144) nn.ReLU(), nn.MaxPool2d(2), # 输出 (32, 72, 72) ) self.conv3 nn.Sequential( #输入 (32, 72, 72) nn.Conv2d(32, 128, 5, 1, 2), nn.ReLU(), # 输出 (128, 72, 72) ) self.out nn.Linear(128 * 64 * 64, 20) # 全连接层得到的结果 def forward(self, x): x self.conv1(x) x self.conv2(x) x self.conv3(x)# 输出 (64,64, 32, 32) x x.view(x.size(0), -1) # flatten操作结果为(batch_size, 64 * 32 * 32) output self.out(x) return output model CNN().to(device) print(model) def train(dataloader, model, loss_fn, optimizer): model.train() #pytorch提供2种方式来切换训练和测试的模式分别是model.train() 和 model.eval()。 # 一般用法是在训练开始之前写上model.trian()在测试时写上 model.eval() 。 batch_size_num 1 for X, y in dataloader: #其中batch为每一个数据的编号 X, y X.to(device), y.to(device) #把训练数据集和标签传入cpu或GPU pred model.forward(X) #自动初始化 w权值 loss loss_fn(pred, y) #通过交叉熵损失函数计算损失值loss # Backpropagation 进来一个batch的数据计算一次梯度更新一次网络 optimizer.zero_grad() #梯度值清零 loss.backward() #反向传播计算得到每个参数的梯度值 optimizer.step() #根据梯度更新网络参数 train_loss loss.item() #获取损失值 print(floss: {train_loss:7f} [number:{batch_size_num}]) batch_size_num 1 best_acc 0.0 acc_s [] loss_s [] def test(dataloader, model, loss_fn): size len(dataloader.dataset) num_batches len(dataloader) model.eval() # test_loss, correct 0, 0 with torch.no_grad(): #一个上下文管理器关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所用内存消耗。 for X, y in dataloader:#一个批次的循环 X, y X.to(device), y.to(device) pred model.forward(X) test_loss loss_fn(pred, y).item() # correct (pred.argmax(1) y).type(torch.float).sum().item() a (pred.argmax(1) y) #dim1表示每一行中的最大值对应的索引号dim0表示每一列中的最大值对应的索引号 b (pred.argmax(1) y).type(torch.float) test_loss / num_batches correct / size print(fTest result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}) acc_s.append(correct) loss_s.append(test_loss) loss_fn nn.CrossEntropyLoss() #创建交叉熵损失函数对象因为手写字识别中一共有10个数字输出会有10个结果 optimizer torch.optim.Adam(model.parameters(), lr0.001)#创建一个优化器SGD为随机梯度下降算法 scheduler torch.optim.lr_scheduler.StepLR(optimizer,step_size5,gamma0.5)#调整学习率函数 训练模型 epochs 10 acc_s []#用来保存测试集每一轮的acc loss_s []#用来报错测试集每一轮的loss for t in range(epochs): print(fEpoch {t1}\n-------------------------------) train(train_dataloader, model, loss_fn, optimizer) scheduler.step()#在每个epoch的训练中使用scheduler.step()语句进行学习率更新 print(best_acc) test(test_dataloader, model, loss_fn) #什么时候可以确定模型训练好了 #1、loss已经稳定在一定区间内震荡 #2、调整参数 #3、改进算法 绘制训练效果曲线 from matplotlib import pyplot as plt plt.subplot(1,2,1) plt.plot(range(0,epochs),acc_s) plt.xlabel(epoch) plt.ylabel(accuracy) plt.subplot(1,2,2) plt.plot(range(0,epochs),loss_s) plt.xlabel(epoch) plt.ylabel(loss) plt.show() print(Done!) #运行结果四、项目总结本项目完整实现了食物图片分类的深度学习流程掌握三大核心技能数据增强是提升模型泛化能力的关键手段最优模型保存保证实战中使用最佳性能模型动态学习率调整让训练更稳定、精度更高