机器学习第4周:猴痘病识别 本文为365天深度学习训练营中的学习记录博客原作者语言环境Python3.13编译器jupyter notebook深度学习环境Pytorch一 准备工作设置CPU模式略1 导入数据import os,PIL,random,pathlib data_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]第一步使用pathlib.Path()函数将字符串类型的文件夹路径转换为pathlib.Path对象。第二步使用glob()方法获取data_dir路径下的所有文件路径并以列表形式存储在data_paths中。第三步通过split()函数对data_paths中的每个文件路径执行分割操作获得各个文件所属的类别名称并存储在classeNames中第四步打印classeNames列表显示每个文件所属的类别名称。total_datadir ./4-data/ # 关于transforms.Compose的更多介绍可以参考https://blog.csdn.net/qq_38251616/article/details/124878863 train_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,transformtrain_transforms) total_datamean与std数值来源见上一篇笔记total_data.class_to_idx代码输出{Monkeypox: 0, Others: 1}total_data.class_to_idx是一个存储了数据集类别和对应索引的字典。在PyTorch的ImageFolder数据加载器中根据数据集文件夹的组织结构每个文件夹代表一个类别class_to_idx字典将每个类别名称映射为一个数字索引。具体来说如果数据集文件夹包含两个子文件夹比如Monkeypox和Othersclass_to_idx字典将返回类似以下的映射关系{Monkeypox: 0, Others: 1}2 划分数据集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代码输出(torch.utils.data.dataset.Subset at 0x1487e6a0590, torch.utils.data.dataset.Subset at 0x14820775310)train_size,test_size输出(1713, 429)batch_size 32 train_dl torch.utils.data.DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue, num_workers1) test_dl torch.utils.data.DataLoader(test_dataset, batch_sizebatch_size, shuffleTrue, num_workers1)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.int64torch.utils.data.DataLoader()参数详解torch.utils.data.DataLoader是 PyTorch 中用于加载和管理数据的一个实用工具类。它允许你以小批次的方式迭代你的数据集这对于训练神经网络和其他机器学习任务非常有用。DataLoader构造函数接受多个参数下面是一些常用的参数及其解释dataset必需参数这是你的数据集对象通常是torch.utils.data.Dataset的子类它包含了你的数据样本。batch_size可选参数指定每个小批次中包含的样本数。默认值为 1。shuffle可选参数如果设置为True则在每个 epoch 开始时对数据进行洗牌以随机打乱样本的顺序。这对于训练数据的随机性很重要以避免模型学习到数据的顺序性。默认值为False。num_workers可选参数用于数据加载的子进程数量。通常将其设置为大于 0 的值可以加快数据加载速度特别是当数据集很大时。默认值为 0表示在主进程中加载数据。pin_memory可选参数如果设置为True则数据加载到 GPU 时会将数据存储在 CUDA 的锁页内存中这可以加速数据传输到 GPU。默认值为False。drop_last可选参数如果设置为True则在最后一个小批次可能包含样本数小于batch_size时丢弃该小批次。这在某些情况下很有用以确保所有小批次具有相同的大小。默认值为False。timeout可选参数如果设置为正整数它定义了每个子进程在等待数据加载器传递数据时的超时时间以秒为单位。这可以用于避免子进程卡住的情况。默认值为 0表示没有超时限制。worker_init_fn可选参数一个可选的函数用于初始化每个子进程的状态。这对于设置每个子进程的随机种子或其他初始化操作很有用。二 构建CNN网络import torch.nn.functional as F class 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_channels3, out_channels12, kernel_size5, stride1, padding0) self.bn1 nn.BatchNorm2d(12) self.conv2 nn.Conv2d(in_channels12, out_channels12, kernel_size5, stride1, padding0) self.bn2 nn.BatchNorm2d(12) self.pool nn.MaxPool2d(2,2) self.conv4 nn.Conv2d(in_channels12, out_channels24, kernel_size5, stride1, padding0) self.bn4 nn.BatchNorm2d(24) self.conv5 nn.Conv2d(in_channels24, out_channels24, kernel_size5, stride1, padding0) 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 x device 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, eps1e-05, momentum0.1, affineTrue, biasTrue, track_running_statsTrue) (conv2): Conv2d(12, 12, kernel_size(5, 5), stride(1, 1)) (bn2): BatchNorm2d(12, eps1e-05, momentum0.1, affineTrue, biasTrue, track_running_statsTrue) (pool): MaxPool2d(kernel_size2, stride2, padding0, dilation1, ceil_modeFalse) (conv4): Conv2d(12, 24, kernel_size(5, 5), stride(1, 1)) (bn4): BatchNorm2d(24, eps1e-05, momentum0.1, affineTrue, biasTrue, track_running_statsTrue) (conv5): Conv2d(24, 24, kernel_size(5, 5), stride(1, 1)) (bn5): BatchNorm2d(24, eps1e-05, momentum0.1, affineTrue, biasTrue, track_running_statsTrue) (fc1): Linear(in_features60000, out_features2, biasTrue) )三 训练模型设置超参数loss_fn nn.CrossEntropyLoss() # 创建损失函数 learn_rate 1e-4 # 学习率 opt torch.optim.SGD(model.parameters(),lrlearn_rate)编写训练函数# 训练循环 def train(dataloader, model, loss_fn, optimizer): size len(dataloader.dataset) # 训练集的大小一共60000张图片 num_batches len(dataloader) # 批次数目187560000/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编写测试函数def test (dataloader, model, loss_fn): size len(dataloader.dataset) # 测试集的大小一共10000张图片 num_batches len(dataloader) # 批次数目31310000/32312.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正式训练epochs 20 train_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(epoch1, epoch_train_acc*100, epoch_train_loss, epoch_test_acc*100, epoch_test_loss)) print(Done)输出Epoch: 1, Train_acc:92.4%, Train_loss:0.254, Test_acc:80.4%Test_loss:0.456 Epoch: 2, Train_acc:93.4%, Train_loss:0.244, Test_acc:80.4%Test_loss:0.438 Epoch: 3, Train_acc:93.6%, Train_loss:0.232, Test_acc:79.5%Test_loss:0.451 Epoch: 4, Train_acc:93.8%, Train_loss:0.232, Test_acc:80.9%Test_loss:0.429 Epoch: 5, Train_acc:93.2%, Train_loss:0.229, Test_acc:80.9%Test_loss:0.424 Epoch: 6, Train_acc:94.2%, Train_loss:0.222, Test_acc:81.8%Test_loss:0.422 Epoch: 7, Train_acc:94.2%, Train_loss:0.217, Test_acc:80.7%Test_loss:0.432 Epoch: 8, Train_acc:94.0%, Train_loss:0.215, Test_acc:82.8%Test_loss:0.430 Epoch: 9, Train_acc:94.8%, Train_loss:0.210, Test_acc:82.3%Test_loss:0.426 Epoch:10, Train_acc:95.2%, Train_loss:0.200, Test_acc:81.6%Test_loss:0.418 Epoch:11, Train_acc:95.2%, Train_loss:0.196, Test_acc:82.5%Test_loss:0.407 Epoch:12, Train_acc:95.3%, Train_loss:0.196, Test_acc:83.0%Test_loss:0.413 Epoch:13, Train_acc:96.1%, Train_loss:0.192, Test_acc:82.1%Test_loss:0.427 Epoch:14, Train_acc:95.6%, Train_loss:0.185, Test_acc:83.0%Test_loss:0.400 Epoch:15, Train_acc:96.5%, Train_loss:0.181, Test_acc:83.0%Test_loss:0.396 Epoch:16, Train_acc:96.1%, Train_loss:0.180, Test_acc:83.2%Test_loss:0.399 Epoch:17, Train_acc:96.4%, Train_loss:0.173, Test_acc:80.9%Test_loss:0.431 Epoch:18, Train_acc:96.8%, Train_loss:0.166, Test_acc:82.8%Test_loss:0.402 Epoch:19, Train_acc:96.1%, Train_loss:0.166, Test_acc:82.1%Test_loss:0.398 Epoch:20, Train_acc:97.3%, Train_loss:0.161, Test_acc:82.8%Test_loss:0.414 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, labelTraining Accuracy) plt.plot(epochs_range, test_acc, labelTest Accuracy) plt.legend(loclower right) plt.title(Training and Validation Accuracy) plt.xlabel(current_time) # 打卡请带上时间戳否则代码截图无效 plt.subplot(1, 2, 2) plt.plot(epochs_range, train_loss, labelTraining Loss) plt.plot(epochs_range, test_loss, labelTest Loss) plt.legend(locupper right) plt.title(Training and Validation Loss) plt.show()2 指定图片进行预测⭐torch.squeeze()详解对数据的维度进行压缩去掉维数为1的的维度函数原型torch.squeeze(input, dimNone, *, outNone)关键参数说明input (Tensor)输入Tensordim (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.Size([2, 2, 1, 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})# 预测训练集中的某张照片 predict_one_image(image_path./4-data/Monkeypox/M01_01_00.jpg, modelmodel, transformtrain_transforms, classesclasses)预测结果是Monkeypox五 保存模型# 模型保存 PATH ./model.pth # 保存的参数文件名 torch.save(model.state_dict(), PATH) # 将参数加载到model当中 model.load_state_dict(torch.load(PATH, map_locationdevice))输出All keys matched successfully六 验证模型首先加载模型import torch import torch.nn as nn import torchvision.transforms as transforms import torchvision from torchvision import transforms, datasets import os,PIL,pathlib device torch.device(cuda if torch.cuda.is_available() else cpu) import os,PIL,random,pathlib data_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] total_datadir ./4-data/ # 关于transforms.Compose的更多介绍可以参考https://blog.csdn.net/qq_38251616/article/details/124878863 train_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,transformtrain_transforms) import os,PIL,random,pathlib import torch.nn.functional as F class 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_channels3, out_channels12, kernel_size5, stride1, padding0) self.bn1 nn.BatchNorm2d(12) self.conv2 nn.Conv2d(in_channels12, out_channels12, kernel_size5, stride1, padding0) self.bn2 nn.BatchNorm2d(12) self.pool nn.MaxPool2d(2,2) self.conv4 nn.Conv2d(in_channels12, out_channels24, kernel_size5, stride1, padding0) self.bn4 nn.BatchNorm2d(24) self.conv5 nn.Conv2d(in_channels24, out_channels24, kernel_size5, stride1, padding0) 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 x model Network_bn().to(device) PATH ./model.pth # 保存的参数文件名 model.load_state_dict(torch.load(PATH, map_locationdevice)) 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/Others/NM01_01_00.jpg, modelmodel, transformtrain_transforms, classesclasses)输出预测结果是Others