>- **🍨 本文为[🔗365天深度学习训练营]中的学习记录博客**
>- **🍖 原作者:[K同学啊]**
🍺要求:
- 训练过程中保存效果最好的模型参数。
- 加载最佳模型参数识别本地的一张图片。
- 调整网络结构使测试集accuracy到达88%(重点)。
🍻拔高(可选):
- 调整模型参数并观察测试集的准确率变化。
- 尝试设置动态学习率。
- 测试集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跑模型,但在首次结果不理想的状态下,仅仅修改模型和调整参数两次就达到理想的结果。但是测试集的损失有居高不下的情况,后来修整了几次依旧未能达到满意状态,希望在以后得学习中能针对损失进行理想的调整。