第P7周:马铃薯病害识别(VGG-16复现)
- 🍨 本文为🔗365天深度学习训练营中的学习记录博客
- 🍖 原作者:K同学啊
一.前期准备
1.设置GPU
import torch import torch.nn as nn import torchvision.transforms as transforms import torchvision from torchvision import transforms,datasets import os,PIL,pathlib,warnings warnings.filterwarnings('ignore') device=torch.device('cuda' if torch.cuda.is_available() else 'cpu') device
2.导入数据
data_dir='../data/PotatoPlants/PotatoPlants' data_dir=pathlib.Path(data_dir) data_paths=list(data_dir.glob('*')) classeNames=[str(path).split('\\')[4] for path in data_paths] classeNames
train_transforms=transforms.Compose([ transforms.Resize([224,224]), transforms.ToTensor(), transforms.Normalize( mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225] ) ]) test_transforms=transforms.Compose([ transforms.Resize([224,224]), transforms.ToTensor(), transforms.Normalize( mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225] ) ]) total_data=datasets.ImageFolder('../data/PotatoPlants/PotatoPlants',transform=train_transforms)
total_data.class_to_idx
3.划分数据集
train_size=int(0.8*len(total_data)) test_size=len(total_data)-train_size train_dataset,test_dataset=torch.utils.data.random_split(total_data,[train_size,test_size]) train_dataset,test_dataset
batch_size=32 train_dl=torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True) test_dl=torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=True)
for x,y in test_dl: print('shape of [N,C,W,H]',x.shape) print('shape of y',y.shape,y.dtype) break
二.VGG-16模型
1.搭建模型
import torch.nn.functional as F class vgg16(nn.Module): def __init__(self): super(vgg16, self).__init__() # 卷积块1 self.block1 = nn.Sequential( nn.Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)) ) # 卷积块2 self.block2 = nn.Sequential( nn.Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)) ) # 卷积块3 self.block3 = nn.Sequential( nn.Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)) ) # 卷积块4 self.block4 = nn.Sequential( nn.Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)) ) # 卷积块5 self.block5 = nn.Sequential( nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)) ) # 全连接网络层,用于分类 self.classifier = nn.Sequential( nn.Linear(in_features=512*7*7, out_features=4096), nn.ReLU(), nn.Linear(in_features=4096, out_features=4096), nn.ReLU(), nn.Linear(in_features=4096, out_features=3) ) def forward(self, x): x = self.block1(x) x = self.block2(x) x = self.block3(x) x = self.block4(x) x = self.block5(x) x = torch.flatten(x, start_dim=1) x = self.classifier(x) return x device = "cuda" if torch.cuda.is_available() else "cpu" print("Using {} device".format(device)) model = vgg16().to(device) model
2.查看模型详情
import torchsummary as summary summary.summary(model,(3,224,224))
三.训练模型
1.编写训练函数
# 训练循环 def train(dataloader, model, loss_fn, optimizer): size = len(dataloader.dataset) # 训练集的大小 num_batches = len(dataloader) # 批次数目, (size/batch_size,向上取整) train_loss, train_acc = 0, 0 # 初始化训练损失和正确率 for X, y in dataloader: # 获取图片及其标签 X, y = X.to(device), y.to(device) # 计算预测误差 pred = model(X) # 网络输出 loss = loss_fn(pred, y) # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值即为损失 # 反向传播 optimizer.zero_grad() # grad属性归零 loss.backward() # 反向传播 optimizer.step() # 每一步自动更新 # 记录acc与loss train_acc += (pred.argmax(1) == y).type(torch.float).sum().item() train_loss += loss.item() train_acc /= size train_loss /= num_batches return train_acc, train_loss
2.编写测试函数
def test (dataloader, model, loss_fn): size = len(dataloader.dataset) # 测试集的大小 num_batches = len(dataloader) # 批次数目, (size/batch_size,向上取整) test_loss, test_acc = 0, 0 # 当不进行训练时,停止梯度更新,节省计算内存消耗 with torch.no_grad(): for imgs, target in dataloader: imgs, target = imgs.to(device), target.to(device) # 计算loss target_pred = model(imgs) loss = loss_fn(target_pred, target) test_loss += loss.item() test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item() test_acc /= size test_loss /= num_batches return test_acc, test_loss
3.正式训练
import copy optimizer = torch.optim.Adam(model.parameters(), lr= 1e-4) loss_fn = nn.CrossEntropyLoss() # 创建损失函数 epochs = 40 train_loss = [] train_acc = [] test_loss = [] test_acc = [] best_acc = 0 # 设置一个最佳准确率,作为最佳模型的判别指标 for epoch in range(epochs): model.train() epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, optimizer) model.eval() epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn) # 保存最佳模型到 best_model if epoch_test_acc > best_acc: best_acc = epoch_test_acc best_model = copy.deepcopy(model) train_acc.append(epoch_train_acc) train_loss.append(epoch_train_loss) test_acc.append(epoch_test_acc) test_loss.append(epoch_test_loss) # 获取当前的学习率 lr = optimizer.state_dict()['param_groups'][0]['lr'] template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}') print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss, epoch_test_acc*100, epoch_test_loss, lr)) # 保存最佳模型到文件中 PATH = './best_model.pth' # 保存的参数文件名 torch.save(model.state_dict(), PATH) print('Done')
四.结果可视化
1.Loss与Accuracy图
import matplotlib.pyplot as plt #隐藏警告 import warnings warnings.filterwarnings("ignore") #忽略警告信息 plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 plt.rcParams['figure.dpi'] = 100 #分辨率 from datetime import datetime current_time = datetime.now() # 获取当前时间 epochs_range = range(epochs) plt.figure(figsize=(12, 3)) plt.subplot(1, 2, 1) plt.plot(epochs_range, train_acc, label='Training Accuracy') plt.plot(epochs_range, test_acc, label='Test Accuracy') plt.legend(loc='lower right') plt.title('Training and Validation Accuracy') plt.xlabel(current_time) # 打卡请带上时间戳,否则代码截图无效 plt.subplot(1, 2, 2) plt.plot(epochs_range, train_loss, label='Training Loss') plt.plot(epochs_range, test_loss, label='Test Loss') plt.legend(loc='upper right') plt.title('Training and Validation Loss') plt.show()
2.指定图片进行预测
from PIL import Image classes = list(total_data.class_to_idx) def predict_one_image(image_path, model, transform, classes): test_img = Image.open(image_path).convert('RGB') plt.imshow(test_img) # 展示预测的图片 test_img = transform(test_img) img = test_img.to(device).unsqueeze(0) model.eval() output = model(img) _,pred = torch.max(output,1) pred_class = classes[pred] print(f'预测结果是:{pred_class}')
3.模型评估
# 预测训练集中的某张照片 predict_one_image(image_path=r'D:\AI_Learning\deep_learning\data\PotatoPlants\PotatoPlants\Early_blight\0a8a68ee-f587-4dea-beec-79d02e7d3fa4___RS_Early.B 8461.JPG', model=model, transform=train_transforms, classes=classes)
基于 VGG16 的马铃薯病害分类模型,完成了数据预处理、模型训练、测试评估和可视化过程,并最终保存了最优模型,实现了单张图片的病害预测。