本文为365天深度学习训练营 内部限免文章
参考本文所写记录性文章,请在文章开头注明以下内容,复制粘贴即可

  • 本文为365天深度学习训练营 中的学习记录博客
  • 参考文章:Pytorch实战 | 第P4周:猴痘病识别(训练营内部可读)
  • 原作者:K同学啊|接辅导、项目定制

本周的代码相对于上周增加指定图片预测保存并加载模型这个两个模块,在学习这个两知识点后,时间有余的同学请自由探索更佳的模型结构以提升模型是识别准确率,模型的搭建是深度学习程度的重点。

我的环境:

  • 语言环境:Python3.8
  • 编译器:Jupyter Lab
  • 深度学习环境:Pytorch
  • 数据集:公众号(K同学啊)回复:DL+45

文章目录

  • 一、 前期准备
    • 1. 设置GPU
    • 2. 导入数据
    • 3. 划分数据集
  • 二、构建简单的CNN网络
  • 三、 训练模型
    • 1. 设置超参数
    • 2. 编写训练函数
    • 3. 编写测试函数
    • 4. 正式训练
  • 四、 结果可视化
    • 1. Loss与Accuracy图
    • 2. 指定图片进行预测
  • 五、保存并加载模型

一、 前期准备

1. 设置GPU

如果设备上支持GPU就使用GPU,否则使用CPU

import torchimport torch.nn as nnimport torchvision.transforms as transformsimport torchvisionfrom torchvision import transforms, datasetsimport os,PIL,pathlibdevice = torch.device("cuda" if torch.cuda.is_available() else "cpu")device
device(type='cuda')

2. 导入数据

import os,PIL,random,pathlibdata_dir = './4-data/'data_dir = pathlib.Path(data_dir)data_paths = list(data_dir.glob('*'))classeNames = [str(path).split("\\")[1] for path in data_paths]classeNames
['Monkeypox', 'Others']
total_datadir = './4-data/'# 关于transforms.Compose的更多介绍可以参考:https://blog.csdn.net/qq_38251616/article/details/124878863train_transforms = transforms.Compose([    transforms.Resize([224, 224]),  # 将输入图片resize成统一尺寸    transforms.ToTensor(),          # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间    transforms.Normalize(           # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛        mean=[0.485, 0.456, 0.406],         std=[0.229, 0.224, 0.225])  # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。])total_data = datasets.ImageFolder(total_datadir,transform=train_transforms)total_data
Dataset ImageFolder    Number of datapoints: 2142    Root location: ./4-data/    StandardTransformTransform: Compose(               Resize(size=[224, 224], interpolation=bilinear, max_size=None, antialias=None)               ToTensor()               Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])           )
total_data.class_to_idx
{'Monkeypox': 0, 'Others': 1}

3. 划分数据集

train_size = int(0.8 * len(total_data))test_size  = len(total_data) - train_sizetrain_dataset, test_dataset = torch.utils.data.random_split(total_data, [train_size, test_size])train_dataset, test_dataset
(, )
train_size,test_size
(1713, 429)
batch_size = 32train_dl = torch.utils.data.DataLoader(train_dataset,                                           batch_size=batch_size,                                           shuffle=True,                                           num_workers=1)test_dl = torch.utils.data.DataLoader(test_dataset,                                          batch_size=batch_size,                                          shuffle=True,                                          num_workers=1)
for X, y in test_dl:    print("Shape of X [N, C, H, W]: ", X.shape)    print("Shape of y: ", y.shape, y.dtype)    break
Shape of X [N, C, H, W]:  torch.Size([32, 3, 224, 224])Shape of y:  torch.Size([32]) torch.int64

二、构建简单的CNN网络

import torch.nn.functional as Fclass Network_bn(nn.Module):    def __init__(self):        super(Network_bn, self).__init__()        """        nn.Conv2d()函数:        第一个参数(in_channels)是输入的channel数量        第二个参数(out_channels)是输出的channel数量        第三个参数(kernel_size)是卷积核大小        第四个参数(stride)是步长,默认为1        第五个参数(padding)是填充大小,默认为0        """        self.conv1 = nn.Conv2d(in_channels=3, out_channels=12, kernel_size=5, stride=1, padding=0)        self.bn1 = nn.BatchNorm2d(12)        self.conv2 = nn.Conv2d(in_channels=12, out_channels=12, kernel_size=5, stride=1, padding=0)        self.bn2 = nn.BatchNorm2d(12)        self.pool = nn.MaxPool2d(2,2)        self.conv4 = nn.Conv2d(in_channels=12, out_channels=24, kernel_size=5, stride=1, padding=0)        self.bn4 = nn.BatchNorm2d(24)        self.conv5 = nn.Conv2d(in_channels=24, out_channels=24, kernel_size=5, stride=1, padding=0)        self.bn5 = nn.BatchNorm2d(24)        self.fc1 = nn.Linear(24*50*50, len(classeNames))    def forward(self, x):        x = F.relu(self.bn1(self.conv1(x)))              x = F.relu(self.bn2(self.conv2(x)))             x = self.pool(x)                                x = F.relu(self.bn4(self.conv4(x)))             x = F.relu(self.bn5(self.conv5(x)))          x = self.pool(x)                                x = x.view(-1, 24*50*50)        x = self.fc1(x)        return xdevice = "cuda" if torch.cuda.is_available() else "cpu"print("Using {} device".format(device))model = Network_bn().to(device)model
Using cuda deviceNetwork_bn(  (conv1): Conv2d(3, 12, kernel_size=(5, 5), stride=(1, 1))  (bn1): BatchNorm2d(12, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)  (conv2): Conv2d(12, 12, kernel_size=(5, 5), stride=(1, 1))  (bn2): BatchNorm2d(12, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)  (conv4): Conv2d(12, 24, kernel_size=(5, 5), stride=(1, 1))  (bn4): BatchNorm2d(24, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)  (conv5): Conv2d(24, 24, kernel_size=(5, 5), stride=(1, 1))  (bn5): BatchNorm2d(24, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)  (fc1): Linear(in_features=60000, out_features=2, bias=True))

三、 训练模型

1. 设置超参数

loss_fn    = nn.CrossEntropyLoss() # 创建损失函数learn_rate = 1e-4 # 学习率opt        = torch.optim.SGD(model.parameters(),lr=learn_rate)

2. 编写训练函数

# 训练循环def train(dataloader, model, loss_fn, optimizer):    size = len(dataloader.dataset)  # 训练集的大小,一共60000张图片    num_batches = len(dataloader)   # 批次数目,1875(60000/32)    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

3. 编写测试函数

测试函数和训练函数大致相同,但是由于不进行梯度下降对网络权重进行更新,所以不需要传入优化器

def test (dataloader, model, loss_fn):    size        = len(dataloader.dataset)  # 测试集的大小,一共10000张图片    num_batches = len(dataloader)          # 批次数目,313(10000/32=312.5,向上取整)    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

4. 正式训练

epochs     = 20train_loss = []train_acc  = []test_loss  = []test_acc   = []for epoch in range(epochs):    model.train()    epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)        model.eval()    epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)        train_acc.append(epoch_train_acc)    train_loss.append(epoch_train_loss)    test_acc.append(epoch_test_acc)    test_loss.append(epoch_test_loss)        template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%,Test_loss:{:.3f}')    print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss, epoch_test_acc*100, epoch_test_loss))print('Done')
Epoch: 1, Train_acc:60.8%, Train_loss:0.655, Test_acc:60.6%,Test_loss:0.668Epoch: 2, Train_acc:70.2%, Train_loss:0.575, Test_acc:72.7%,Test_loss:0.560Epoch: 3, Train_acc:74.5%, Train_loss:0.527, Test_acc:71.3%,Test_loss:0.549Epoch: 4, Train_acc:78.4%, Train_loss:0.483, Test_acc:73.4%,Test_loss:0.519....Epoch:18, Train_acc:91.4%, Train_loss:0.271, Test_acc:83.0%,Test_loss:0.382Epoch:19, Train_acc:92.6%, Train_loss:0.260, Test_acc:83.7%,Test_loss:0.381Epoch:20, Train_acc:92.1%, Train_loss:0.260, Test_acc:82.3%,Test_loss:0.396Done

四、 结果可视化

1. Loss与Accuracy图

import matplotlib.pyplot as plt#隐藏警告import warningswarnings.filterwarnings("ignore")               #忽略警告信息plt.rcParams['font.sans-serif']    = ['SimHei'] # 用来正常显示中文标签plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号plt.rcParams['figure.dpi']         = 100        #分辨率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.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. 指定图片进行预测

⭐torch.squeeze()详解

对数据的维度进行压缩,去掉维数为1的的维度

函数原型:

torch.squeeze(input, dim=None, *, out=None)

关键参数说明:

  • input (Tensor):输入Tensor
  • dim (int, optional):如果给定,输入将只在这个维度上被压缩

实战案例:

>>> x = torch.zeros(2, 1, 2, 1, 2)>>> x.size()torch.Size([2, 1, 2, 1, 2])>>> y = torch.squeeze(x)>>> y.size()torch.Size([2, 2, 2])>>> y = torch.squeeze(x, 0)>>> y.size()torch.Size([2, 1, 2, 1, 2])>>> y = torch.squeeze(x, 1)>>> y.size()torch.Size([2, 2, 1, 2])

⭐torch.unsqueeze()

对数据维度进行扩充。给指定位置加上维数为一的维度

函数原型:

torch.unsqueeze(input, dim)

关键参数说明:

  • input (Tensor):输入Tensor
  • dim (int):插入单例维度的索引

实战案例:

>>> x = torch.tensor([1, 2, 3, 4])>>> torch.unsqueeze(x, 0)tensor([[ 1,  2,  3,  4]])>>> torch.unsqueeze(x, 1)tensor([[ 1],        [ 2],        [ 3],        [ 4]])
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}')
# 预测训练集中的某张照片predict_one_image(image_path='./4-data/Monkeypox/M01_01_00.jpg',                   model=model,                   transform=train_transforms,                   classes=classes)
预测结果是:Monkeypox

五、保存并加载模型

# 模型保存PATH = './model.pth'  # 保存的参数文件名torch.save(model.state_dict(), PATH)# 将参数加载到model当中model.load_state_dict(torch.load(PATH, map_location=device))