深度学习第P4周:猴痘病识别

>- **🍨 本文为[🔗365天深度学习训练营]中的学习记录博客**
>- **🍖 原作者:[K同学啊]**

 🍺要求:

  1. 训练过程中保存效果最好的模型参数。
  1. 加载最佳模型参数识别本地的一张图片。
  1. 调整网络结构使测试集accuracy到达88%(重点)。

🍻拔高(可选):

  1. 调整模型参数并观察测试集的准确率变化。
  1. 尝试设置动态学习率。
  1. 测试集accuracy到达90%。

🏡 我的环境:

  • 语言环境:Python3.11.7
  • 编译器:Jupyter Lab
  • 深度学习环境:Pytorch

一、 前期准备

1. 设置GPU

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

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")
device

运行结果:

device(type='cpu')

2. 导入数据

  • 第一步:使用pathlib.Path()函数将字符串类型的文件夹路径转换为pathlib.Path对象。
  • 第二步:使用glob()方法获取data_dir路径下的所有文件路径,并以列表形式存储在data_paths中。
  • 第三步:通过split()函数对data_paths中的每个文件路径执行分割操作,获得各个文件所属的类别名称,并存储在classeNames
  • 第四步:打印classeNames列表,显示每个文件所属的类别名称。
import pathlib

data_dir='D:\THE MNIST DATABASE\P4-data'
data_dir=pathlib.Path(data_dir)

data_paths=list(data_dir.glob('*'))
classeNames=[str(path).split("\\")[3] for path in data_paths]
classeNames

运行结果: 

['Monkeypox', 'Others']

3、 测试获取到的图片

此段内容须谨慎,可能会引起极端不适……

import matplotlib.pyplot as plt
from PIL import Image
import os

#指定图像文件夹路径
image_folder=r'D:\THE MNIST DATABASE\P4-data\Monkeypox'

#获取文件夹中所有图像文件
image_files=[f for f in os.listdir(image_folder) if f.endswith((".jpg",".png",".jpeg"))]

#创建Matplotlib图像
fig,axes=plt.subplots(3,8,figsize=(16,6))

#使用列表推导式加载和显示图像
for ax,img_file in zip(axes.flat,image_files):
    img_path=os.path.join(image_folder,img_file)
    img=Image.open(img_path)
    ax.imshow(img)
    ax.axis('off')

 运行结果:结果图就不放出来了……看了之后着实有点受不了……

4、图像预处理

total_datadir='D:\THE MNIST DATABASE\P4-data'

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])
])

total_data=datasets.ImageFolder(total_datadir,transform=train_transforms)
total_data

运行结果:

Dataset ImageFolder
    Number of datapoints: 2142
    Root location: D:\THE MNIST DATABASE\P4-data
    StandardTransform
Transform: Compose(
               Resize(size=[224, 224], interpolation=bilinear, max_size=None, antialias=True)
               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}

total_data.class_to_idx是一个存储了数据集类别和对应索引的字典。在PyTorch的ImageFolder数据加载器中,根据数据集文件夹的组织结构,每个文件夹代表一个类别,class_to_idx字典将每个类别名称映射为一个数字索引。

具体来说,如果数据集文件夹包含两个子文件夹,比如Monkeypox和Others,class_to_idx字典将返回类似以下的映射关系:{'Monkeypox': 0, 'Others': 1}

5. 划分数据集

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 0x2266b2b50d0>,
 <torch.utils.data.dataset.Subset at 0x2266b2b6ed0>)

 train_size表示训练集大小,通过将总体数据长度的80%转换为整数得到;
test_size表示测试集大小,是总体数据长度减去训练集大小。
使用torch.utils.data.random_split()方法进行数据集划分。该方法将总体数据total_data按照指定的大小比例([train_size, test_size])随机划分为训练集和测试集,并将划分结果分别赋值给train_dataset和test_dataset两个变量。

显示两个数据集的大小:

train_size,test_size

运行结果:

(1713, 429)

6、加载数据集

train_dl=torch.utils.data.DataLoader(train_dataset,
                                     batch_size=32,
                                     shuffle=True,
                                     num_workers=0)   
#注意,Linux系统下因其多线程缘故可设置参数num_workers值为1,
#Windows环境下会报错必须改为0或不写,其默认值即为0
test_dl=torch.utils.data.DataLoader(test_dataset,
                                    batch_size=32,
                                    shuffle=True)
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 F

class Network_bn(nn.Module):
    def __init__(self):
        super(Network_bn,self).__init__()
        #设置卷积层
        self.conv1=nn.Conv2d(3,12,5)
        self.conv2=nn.Conv2d(12,12,5)
        self.conv3=nn.Conv2d(12,24,5)
        self.conv4=nn.Conv2d(24,24,5)
        
        self.bn1=nn.BatchNorm2d(12)
        self.bn2=nn.BatchNorm2d(12)
        self.bn3=nn.BatchNorm2d(24)
        self.bn4=nn.BatchNorm2d(24)
        
        self.pool=nn.MaxPool2d(2,2)
        
        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.bn3(self.conv3(x)))
        x=F.relu(self.bn4(self.conv4(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 cpu device
Network_bn(
  (conv1): Conv2d(3, 12, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(12, 12, kernel_size=(5, 5), stride=(1, 1))
  (conv3): Conv2d(12, 24, kernel_size=(5, 5), stride=(1, 1))
  (conv4): Conv2d(24, 24, kernel_size=(5, 5), stride=(1, 1))
  (bn1): BatchNorm2d(12, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (bn2): BatchNorm2d(12, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (bn3): BatchNorm2d(24, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (bn4): BatchNorm2d(24, 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)
  (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)    #训练集的大小,一共1713张图片
    num_batches=len(dataloader)    #批次数目,54(1713/32=53.53125,向上取整)
    
    train_loss,train_acc=0,0   #初始化训练损失和正确率
    
    for x,y in dataloader:    #获取图片及其标签
        x,y=x.to(devicei),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)   #测试集的大小,一共429张图片
    num_batches=len(dataloader)   #批次数目,14(429/32=13.40625,向上取整)
    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=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(epoch+1,epoch_train_acc*100,epoch_train_loss,epoch_test_acc*100,epoch_test_loss))
print('Done')

运行结果:

Epoch: 1,Train_acc:58.4%,Train_loss:0.694,Test_acc:68.1%,Test_loss:0.629
Epoch: 2,Train_acc:68.7%,Train_loss:0.609,Test_acc:68.8%,Test_loss:0.618
Epoch: 3,Train_acc:72.2%,Train_loss:0.563,Test_acc:73.2%,Test_loss:0.561
Epoch: 4,Train_acc:75.2%,Train_loss:0.519,Test_acc:65.5%,Test_loss:0.751
Epoch: 5,Train_acc:76.0%,Train_loss:0.496,Test_acc:74.8%,Test_loss:0.522
Epoch: 6,Train_acc:80.2%,Train_loss:0.447,Test_acc:76.5%,Test_loss:0.491
Epoch: 7,Train_acc:81.6%,Train_loss:0.422,Test_acc:76.2%,Test_loss:0.478
Epoch: 8,Train_acc:82.5%,Train_loss:0.405,Test_acc:77.2%,Test_loss:0.461
Epoch: 9,Train_acc:84.1%,Train_loss:0.385,Test_acc:80.2%,Test_loss:0.447
Epoch:10,Train_acc:84.4%,Train_loss:0.364,Test_acc:80.4%,Test_loss:0.447
Epoch:11,Train_acc:86.8%,Train_loss:0.346,Test_acc:83.0%,Test_loss:0.427
Epoch:12,Train_acc:86.8%,Train_loss:0.330,Test_acc:81.8%,Test_loss:0.445
Epoch:13,Train_acc:88.3%,Train_loss:0.308,Test_acc:80.2%,Test_loss:0.460
Epoch:14,Train_acc:88.1%,Train_loss:0.310,Test_acc:82.3%,Test_loss:0.422
Epoch:15,Train_acc:89.3%,Train_loss:0.287,Test_acc:85.1%,Test_loss:0.381
Epoch:16,Train_acc:90.2%,Train_loss:0.273,Test_acc:84.1%,Test_loss:0.392
Epoch:17,Train_acc:91.0%,Train_loss:0.261,Test_acc:83.7%,Test_loss:0.390
Epoch:18,Train_acc:90.9%,Train_loss:0.247,Test_acc:85.1%,Test_loss:0.376
Epoch:19,Train_acc:91.7%,Train_loss:0.243,Test_acc:81.6%,Test_loss:0.474
Epoch:20,Train_acc:90.9%,Train_loss:0.243,Test_acc:85.5%,Test_loss:0.370
Done

根据要求,测试集accuracy达到88%,甚至达到90%。调整卷积核的大小及通道数,改变池化层的方法,添加Dropout方法防止过拟合,修改全连接层。调整后的模型如下:

import torch.nn.functional as F

class Network_bn(nn.Module):
    def __init__(self):
        super(Network_bn,self).__init__()
        #设置卷积层
        self.conv1=nn.Conv2d(3,16,3)
        self.conv2=nn.Conv2d(16,32,3)
        self.conv3=nn.Conv2d(32,64,3)
        self.conv4=nn.Conv2d(64,64,3)
        
        self.bn1=nn.BatchNorm2d(16)
        self.bn2=nn.BatchNorm2d(32)
        self.bn3=nn.BatchNorm2d(64)
        self.bn4=nn.BatchNorm2d(64)
        
        self.maxpool=nn.MaxPool2d(2,2)
        self.avgpool=nn.AvgPool2d(2,2)
        
        self.dropout=nn.Dropout(0.5)
        
        self.fc1=nn.Linear(64*25*25,128)
        self.fc2=nn.Linear(128,len(classeNames))
    
    def forward(self,x):
        x=F.leaky_relu(self.bn1(self.conv1(x)))
        x=self.maxpool(x)
        x=F.leaky_relu(self.bn2(self.conv2(x)))
        x=self.avgpool(x)
        x=F.leaky_relu(self.bn3(self.conv3(x)))
        x=F.leaky_relu(self.bn4(self.conv4(x)))
        x=self.avgpool(x)
        
        x=x.view(x.size(0),-1)
        x=self.dropout(x)
        x=F.leaky_relu(self.fc1(x))
        x=self.dropout(x)
        x=self.fc2(x)
        
        return x
device="cuda" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device))

model=Network_bn().to(device)
model  

同时,修改优化器,调整学习率

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

 运行后结果:

Epoch: 1,Train_acc:55.9%,Train_loss:1.156,Test_acc:67.8%,Test_loss:0.628
Epoch: 2,Train_acc:64.7%,Train_loss:0.635,Test_acc:71.8%,Test_loss:0.586
Epoch: 3,Train_acc:69.6%,Train_loss:0.591,Test_acc:70.6%,Test_loss:0.564
Epoch: 4,Train_acc:74.7%,Train_loss:0.511,Test_acc:78.8%,Test_loss:0.458
Epoch: 5,Train_acc:82.8%,Train_loss:0.407,Test_acc:83.0%,Test_loss:0.415
Epoch: 6,Train_acc:85.7%,Train_loss:0.356,Test_acc:77.6%,Test_loss:0.496
Epoch: 7,Train_acc:86.5%,Train_loss:0.333,Test_acc:82.5%,Test_loss:0.431
Epoch: 8,Train_acc:90.8%,Train_loss:0.229,Test_acc:86.7%,Test_loss:0.336
Epoch: 9,Train_acc:92.4%,Train_loss:0.187,Test_acc:88.3%,Test_loss:0.346
Epoch:10,Train_acc:92.9%,Train_loss:0.186,Test_acc:87.6%,Test_loss:0.398
Epoch:11,Train_acc:94.8%,Train_loss:0.145,Test_acc:86.7%,Test_loss:0.370
Epoch:12,Train_acc:94.0%,Train_loss:0.144,Test_acc:87.2%,Test_loss:0.381
Epoch:13,Train_acc:96.7%,Train_loss:0.093,Test_acc:87.9%,Test_loss:0.467
Epoch:14,Train_acc:97.3%,Train_loss:0.074,Test_acc:86.7%,Test_loss:0.411
Epoch:15,Train_acc:97.0%,Train_loss:0.084,Test_acc:88.8%,Test_loss:0.352
Epoch:16,Train_acc:98.4%,Train_loss:0.049,Test_acc:90.4%,Test_loss:0.458
Epoch:17,Train_acc:98.5%,Train_loss:0.048,Test_acc:87.4%,Test_loss:0.545
Epoch:18,Train_acc:97.1%,Train_loss:0.098,Test_acc:87.2%,Test_loss:0.433
Epoch:19,Train_acc:98.2%,Train_loss:0.052,Test_acc:89.0%,Test_loss:0.496
Epoch:20,Train_acc:99.1%,Train_loss:0.028,Test_acc:87.2%,Test_loss:0.686
Done
​

 结果比较满意,在第16轮时已经达到90%。

四、 结果可视化

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']=300   #分辨率

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. 指定图片进行预测

建立预测模型

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='D:\THE MNIST DATABASE\P4-data\Monkeypox\M01_01_00.jpg',
                  model=model,
                  transform=train_transforms,
                  classes=classes)

运行结果:

预测结果是:Monkeypox

五、保存并加载模型

#保存模型
path=r'C:\Users\Administrator\PycharmProjects\pytorchProject1\P4周:猴痘病识别\model-p4.pth'
torch.save(model.state_dict(),path)

#将参数加载到model当中
model.load_state_dict(torch.load(path,map_location=device))

运行结果:

<All keys matched successfully>

六、个人总结

本周项目完成的比较顺利,虽然是依旧拉胯的cpu跑模型,但在首次结果不理想的状态下,仅仅修改模型和调整参数两次就达到理想的结果。但是测试集的损失有居高不下的情况,后来修整了几次依旧未能达到满意状态,希望在以后得学习中能针对损失进行理想的调整。

  • 33
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值