- **🍨 本文为[🔗365天深度学习训练营](https://mp.weixin.qq.com/s/rnFa-IeY93EpjVu0yzzjkw) 中的学习记录博客**
- **🍖 原作者:[K同学啊](https://mtyjkh.blog.csdn.net/)**
前言
本文利用pytorch实现CNN卷积神经网络识别天气,是对上两篇博客的知识点使用,如若基础有缺请返回上两篇博客查看详细内容。本文主要聚焦于搭建卷积神经网络,如何提升预测的性能以及预测准确率,希望本文能对你有所帮助,如有不足,请多多包涵。接下来进入正文。
一.前置准备
1.环境搭建
本文所用环境如下:
编译环境 pytho==3.8
编译器 Jupyter Lab
torch== 2.1.0+cu118
torchvision == 0.16.0+cu118
可用如下代码进行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,random
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device
输出为 device(type='cuda')
2.数据集准备
(一).数据集介绍
数据集为天气识别数据集,可由网上自由下载,这里不提供下载途径了,数据集介绍,数据集中一共有1126张图片,分为cloudy,rain,shine,sunrise四个类别,我们的目标就是通过自主划分训练集和验证集,使得网络在验证集上实现精准分类。
(二).数据读取
将数据集的根文件夹(此时跟文件夹名为data_3)放于代码同目录下,利用如下代码进行数据查看 :
1.获取图像的所在的各个文件夹名: 首先data_dir确定根目录文件夹,利用pathlib.Path转化为WindowPath类,用于将'./data_3/'重新定义为可操作的路径对象,然后利用对应的.glob方法选取文件下的所有文件路径,用list存储,然后利用列表生成式,对data_paths表格的每个元素进行划分,提取出/后面的部分,即将data_3/rain中的rain提取出来,存储在classeNames中,作为将要预测的目标别。
data_dir='./data_3/' #根目录
data_dir=pathlib.Path(data_dir) #获取根目录路径
data_paths=list(data_dir.glob('*')) #list获取路径下对应的所有文件路径
# 结果如[Path('data_3/class1'), Path('data_3/class2'), ...]
classeNames=[str(path).split("\\")[1] for path in data_paths] #利用/进行划分,/前是一段,/后是一段,取第二段
classeNames #获取文件夹名
对文件夹中的其中一类图片进行可视化查看,代码: image_folder为选取的文件夹路径,image_file通过列表生成式选取全部图片,其中os.listdir(image_folder)作用是获取该路径下的所有文件,利用if条件选取以".jpg",".png",".jpeg"三个后缀的文件,fig,axes创建子图,for循环中,axes.flat为将子图数组展平为一维数组,方便位置选取,遍历每个子图和文件中的图像,os.path.join()将文件路径(./data_3/cloudy/)和图像文件名(sunny_001.jpg)拼接起来组成完整的图片路径(./data_3/cloudy/sunny_001.jpg),然后利用Image.open()函数打开图像文件,用ax.imshow显示图像,ax.axis('off')表示关闭坐标轴。
import matplotlib.pyplot as plt
from PIL import Image
#指定文件路径
image_folder='./data_3/cloudy/'
#获取所有的图像文件
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')
#显示图像
plt.tight_layout()
plt.show()
二 . 数据处理
1.标准化
由于数据是图片形式,我们要进行模型训练要转为数据形式,接下来我们要进行图片与数据的转换。代码如下: 首先确定对应数据集根目录data_3,然后利用transforms.Compose方法组合多个图像变换操作,将图像尺寸调整为224,224(默认尺寸),transform转为tensor张量,transforms.Normalize()方法是对张量格式的图像进行标准化处理,均值和方差为该组取值,数值的来源是通过对计算ImageNet数据集中所有训练图像的RGB通道均值和标准差得出的。接着利用datasets.ImageFolder函数扫描data_3下的所有子文件夹,采用刚刚创建的train_trainforms形式的格式对图像进行标准化处理
total_datadir='./data_3/' #文件目录
train_transforms=transforms.Compose([
transforms.Resize([224,224]), #输入图像调整大小为224*224
transforms.ToTensor(), #转换为pytorch张量,归一化到[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
2.数据集划分
由于没有确定的数据集划分要求,这里指定百分之八十的数据为训练集,剩余为测试集,划分代码:其中torch.utils.data.random_split()代码用于数据集划分,total_data为划分的数据集,[train_size,test_size]为划分比例。
#划分数据集
train_size=int(0.8*len(total_data))
test_size=len(total_data)-train_size
print('测试集:{},训练集:{}'.format(train_size,test_size))
#划分数据集大小
train_dataset,test_dataset=torch.utils.data.random_split(total_data,[train_size,test_size])
train_dataset,test_dataset
对数据形状进行查看:
print('每个长度: ',train_size,test_size)
print(len(train_dataset[0]))
imgs,label=train_dataset[0]
print(imgs.shape) #3,224,224
print(label)
批次划分:代码:(之前都有详细介绍,不懂可参考前两篇博客)
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('形状[批次,通道数,高度,宽度]',X.shape)
print('形状y,',y.dtype)
break
三.模型搭建
1.模型代码:
这里的模型代码就不做详细解释了,有不懂的可查阅前两篇。
#构建CNN
import torch
import torch.nn as nn
import torch.nn.functional as F
class Model(nn.Module):
def __init__(self):
super().__init__()
#输入(B,3,224,224)->(B,12,222,222)
self.conv1=nn.Conv2d(in_channels=3,out_channels=12,kernel_size=3,stride=1,padding=0)
self.bn1=nn.BatchNorm2d(12) #归一化操作
#(B,12,222,222)->(B,24,220,220)
self.conv2=nn.Conv2d(in_channels=12,out_channels=24,kernel_size=3,stride=1,padding=0)
self.bn2=nn.BatchNorm2d(24) #归一化
#(B,24,220,220)->(B,24,220,220)
self.pool1=nn.MaxPool2d(2,2) #卷积核,步长
#第三层卷积层
#(B,24,110,110)->(B,24,110,104)
# self.conv3=nn.Conv2d(in_channels=32,out_channels=64,kernel_size=5,stride=1,padding=0)
# self.bn3=nn.BatchNorm2d(64) #归一化
#(B,24,110,110)->(B,24,108,108)
self.conv4=nn.Conv2d(in_channels=24,out_channels=24,kernel_size=3,stride=1,padding=0)
self.bn4=nn.BatchNorm2d(24) #对24特征图归一化
#(B,24,108,108)->(B,24,106,106)
self.conv5=nn.Conv2d(in_channels=24,out_channels=24,kernel_size=3,stride=1,padding=0)
self.bn5=nn.BatchNorm2d(24)
#(B,24,106,106)->(B,24,53,53)
self.pool2=nn.MaxPool2d(2,2)
self.fc1=nn.Linear(24*53*53,len(classeNames))
def forward(self,x):
x=F.relu(self.bn1(self.conv1(x)))
x=F.relu(self.bn2(self.conv2(x)))
x=self.pool1(x)
# x=F.relu(self.bn3(self.conv3(x)))
x=F.relu(self.bn4(self.conv4(x)))
x=F.relu(self.bn5(self.conv5(x)))
x=self.pool2(x)
x=x.view(-1,24*53*53) #类似于全连接层
x=self.fc1(x)
return x
model=Model().to(device)
model
#训练模型
loss_fn=nn.CrossEntropyLoss() #创建损失函数
learn_rate=1e-4
opt=torch.optim.SGD(model.parameters(),lr=learn_rate)
四. 模型训练
1.训练代码:
这里只给出代码,不作细致描述,有需要的可见前面两篇博客:
def train(dataloader,model,loss_fn,optimizer):
size=len(dataloader.dataset)
num_batches=len(dataloader) #批次数目
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)
#反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step() #每一步更新
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)
test_loss,test_acc=0,0
with torch.no_grad():
for imgs,target in dataloader:
imgs,target=imgs.to(device),target.to(device)
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.训练代码
这里只给出代码,不作细致描述,有需要的可见前面两篇博客:
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')
4.模型调参过程:
1.第一轮:
#构建CNN
import torch
import torch.nn as nn
import torch.nn.functional as F
class Model(nn.Module):
def __init__(self):
super().__init__()
#输入(B,3,224,224)->(B,12,222,222)
self.conv1=nn.Conv2d(in_channels=3,out_channels=12,kernel_size=5,stride=1,padding=0)
self.bn1=nn.BatchNorm2d(12) #归一化操作
#(B,12,222,222)->(B,24,220,220)
self.conv2=nn.Conv2d(in_channels=12,out_channels=12,kernel_size=5,stride=1,padding=0)
self.bn2=nn.BatchNorm2d(12) #归一化
#(B,24,220,220)->(B,24,220,220)
self.pool1=nn.MaxPool2d(2,2) #卷积核,步长
#第三层卷积层
#(B,24,110,110)->(B,24,110,104)
# self.conv3=nn.Conv2d(in_channels=32,out_channels=64,kernel_size=5,stride=1,padding=0)
# self.bn3=nn.BatchNorm2d(64) #归一化
#(B,24,110,110)->(B,24,108,108)
self.conv4=nn.Conv2d(in_channels=12,out_channels=24,kernel_size=5,stride=1,padding=0)
self.bn4=nn.BatchNorm2d(24) #对24特征图归一化
#(B,24,108,108)->(B,24,106,106)
self.conv5=nn.Conv2d(in_channels=24,out_channels=24,kernel_size=5,stride=1,padding=0)
self.bn5=nn.BatchNorm2d(24)
#(B,24,106,106)->(B,24,53,53)
self.pool2=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.pool1(x)
# x=F.relu(self.bn3(self.conv3(x)))
x=F.relu(self.bn4(self.conv4(x)))
x=F.relu(self.bn5(self.conv5(x)))
x=self.pool2(x)
x=x.view(-1,24*50*50) #类似于全连接层
x=self.fc1(x)
return x
model=Model().to(device)
model
网络结构下图所示:
结果:
第二轮:
模型参数依照下面:
Model(
(conv1): Conv2d(3, 16, kernel_size=(5, 5), stride=(1, 1))
(bn1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(16, 32, kernel_size=(5, 5), stride=(1, 1))
(bn2): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (conv4): Conv2d(32, 32, kernel_size=(5, 5), stride=(1, 1))
(bn4): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv5): Conv2d(32, 32, kernel_size=(5, 5), stride=(1, 1))
(bn5): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (fc1): Linear(in_features=80000, out_features=4, bias=True) )
第三轮:
模型参数:
Model(
(conv1): Conv2d(3, 12, kernel_size=(3, 3), stride=(1, 1))
(bn1): BatchNorm2d(12, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(12, 24, kernel_size=(3, 3), stride=(1, 1))
(bn2): BatchNorm2d(24, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (conv4): Conv2d(24, 24, kernel_size=(3, 3), stride=(1, 1))
(bn4): BatchNorm2d(24, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv5): Conv2d(24, 24, kernel_size=(3, 3), stride=(1, 1))
(bn5): BatchNorm2d(24, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(fc1): Linear(in_features=67416, out_features=4, bias=True) )
第四轮:
5.结果可视化:
代码:有疑问可看上两篇
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()
总结
比较耗时的调参过程,通过调参过程继续深化对CNN卷积网络的理解。
以上便是该次实践的全部内容,全部代码已经呈现,有需要的一段一段复制运行即可,如有问题,可在评论区指出(也可私聊),博主会欣然改正,有疑问也可私信博主,相信你能看到这里,应该会有点收获,如果给你带来了收获,那至少我会感到很荣幸,博主这么久的心血也没有白费,码字不易,给个三连,球球了。