- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
目录
🍺要求:
- 本地读取并加载数据。
- 测试集accuracy到达93%
🍻拔高:
- 测试集accuracy到达95%
- 调用模型识别一张本地图片
一、创建环境
● 语言环境:Python3.8
● 编译器:jupyter notebook
● 深度学习环境:Pytorch
数据:🔗百度网盘(提取码:hqij )
PS:本次我使用的环境是谷歌的🔗colab,有免费的gpu可以用,需要先注册一个账号,但是国内网站好像无法访问,缺点是上传文件比较慢,不过这次的数据集比较小,几分钟就可以了。具体的使用方法可以百度一下,也是比较好上手的。
如果文中有函数不懂可以翻一下前面2篇,尤其是卷积层和池化层的手动计算,在项目2中有详细说明。
0基础深度学习项目1:基于Pytorch实现mnist手写数字识别
0基础深度学习项目2:基于Pytorch实现CIFAR10彩色图片识别
二、前期准备
2.1 设置GPU
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision
from torchvision import transforms, datasets
import os,PIL,pathlib,random
# 有GPU的切GPU,没GPU的用CPU,大家都有美好的未来
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
结果:
2.2 导入数据
# 导入数据
data_dir = '/content/drive/MyDrive/weather_photos' # 这个地址可以挂载云盘之后直接右键复制地址
# 将字符串类型的文件夹路径转换为pathlib.Path对象
data_dir = pathlib.Path(data_dir)
data_paths = list(data_dir.glob('*')) # 以列表的形式将该路径下所有的子文件的文件路径存储在data_paths 中
classeNames = [str(path).split("/")[-1] for path in data_paths] # 通过split切割文件所属的类别名称
print(classeNames) # 输出类别名称,分别为云、雨、晴、日出
本次使用的数据集是一些天气图片的合集,共有4个文件夹,分别为云、雨、晴、日出。
2.3 数据可视化
# 随机选取24张图片进行展示
import matplotlib.pyplot as plt
from PIL import Image
# 指定图像文件夹路径
image_folder = '/content/drive/MyDrive/weather_photos/cloudy/'
# 获取文件夹中的所有图像文件
image_files = [f for f in os.listdir(image_folder) if f.endswith(('.jpg','.png','.jpeg'))]
# 创建matplotlib图像
fig, axs = plt.subplots(3,8, figsize=(16, 4)) # 3行8列
# 遍历图像文件并显示
for ax, img_file in zip(axs.flat,image_files): # axes换成axs就好了
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()
结果:
2.4 处理图像信息
# 处理一下图片
total_datadir = '/content/drive/MyDrive/weather_photos'
# transforms是pytorch中的图像预处理包。一般用compose把多个步骤整合到一起
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和std从数据集中随机抽样得到
])
total_data = datasets.ImageFolder(total_datadir, transform=train_transforms) # ImageFolder用于创建一个数据集对象,total_data包含所有图像数据的数据集对象,可用于训练神经网络模型
print(total_data)
结果:
2.5 划分数据集
# 划分数据集
train_size = int(0.8 * len(total_data)) # 训练集数据大小为总体数据长度的80%
test_size = len(total_data) - train_size # 测试集数据大小,为总体数据减去训练集数据
# 将总体数据total_data按照指定的大小比例随机划分训练集和测试集
train_dataset, test_dataset = torch.utils.data.random_split(total_data, [train_size, test_size])
train_dataset,test_dataset
结果:
2.6 加载数据集
# 加载数据集
batch_size = 32
train_dl = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True,num_workers=1) # num_workers为数据加载的子进程数量,如果是cpu运行的话要改成num_workers=0
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
结果:
三、构建简单的CNN网络
卷积神经网络CNN一般由__特征提取网络__和__分类网络__构成,特征提取网络用于图片的特征提取,分类网络用于将图片进行分类。
该案例的网络结构为:
上面的网络数据shape变化过程为(上一篇文章中有卷积层和池化层的手动计算公式):
3, 224, 224(输入数据)
-> 12, 220, 220(经过卷积层1)
-> 12, 216, 216(经过卷积层2)
-> 12, 108, 108(经过池化层1)
-> 24, 104, 104(经过卷积层3)
-> 24, 100, 100(经过池化层4)
-> 24, 50, 50(经过池化层2)-> 60000 -> num_classes(4)
import torch.nn.functional as F
class Network_bn(nn.Module):
def __init__(self):
super(Network_bn,self).__init__()
"""
nn.Conv2d()函数:
第一个参数(in_channels):输入图像的通道数
第二个参数(out_channels):输出图像的通道数
第三个参数(kernel_size):卷积核的大小
第四个参数(stride):卷积核的步长,默认为1
第五个参数(padding):卷积核的填充,默认为0
nn.MaxPool2d()函数:
第一个参数(kernel_size):最大的窗口大小
第二个参数(stride):窗口的步幅,默认为kernel_size
第三个参数(padding):卷积核的填充,默认为0
第四个参数(dilation):控制窗口中元素步幅的参数
"""
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.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
self.conv3 = nn.Conv2d(in_channels=12, out_channels=24, kernel_size=5, stride=1, padding=0)
self.bn3 = nn.BatchNorm2d(24)
self.conv4 = nn.Conv2d(in_channels=24, out_channels=24, kernel_size=5, stride=1, padding=0)
self.bn4 = nn.BatchNorm2d(24)
self.pool2 = nn.MaxPool2d(kernel_size=2, stride=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 = self.pool2(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)
print(model)
结果:
四、训练模型
4.1 设置超参数
# 设置超参数
loss_fn = nn.CrossEntropyLoss() # 创建损失函数
learn_rate = 1e-4 # 学习率
opt = torch.optim.SGD(model.parameters(), lr=learn_rate) # 创建优化器
4.2 编写训练函数
# 编写训练函数
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) # 将输入和标签数据发送到GPU
# 计算预测误差
pred = model(X) # 模型预测
loss = loss_fn(pred, y) # 计算损失,即模型预测和真实值之间的差距
# 反向传播
optimizer.zero_grad() # 梯度清零
loss.backward() # 反向传播
optimizer.step() # 每一步自动更新
# 记录acc和loss
train_loss += loss.item() # 将损失添加到总损失中
train_acc += (pred.argmax(1) == y).type(torch.float).sum().item() # 将正确率添加到总正确率中
train_loss /= num_batches # 计算平均损失
train_acc /= size # 计算平均正确率
return train_loss, train_acc
4.3 编写测试函数
# 编写测试函数
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) # 将输入和标签数据发送到GPU
# 计算预测误差
target_pred = model(imgs) # 模型预测
loss = loss_fn(target_pred, target) # 计算损失,即模型预测和真实值之间的差距
# 记录acc和loss
test_loss += loss.item() # 将损失添加到总损失中
test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item() # 将正确率添加到总正确率中
test_loss /= num_batches # 计算平均损失
test_acc /= size # 计算平均正确率
return test_loss, test_acc
4.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')
结果:
4.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 # 设置分辨率
epoch_range = range(epochs)
plt.figure(figsize=(12, 3)) # 设置画布大小
plt.subplot(1, 2, 1) # 一行两列,画第一张图
plt.plot(epoch_range, train_acc, label='Train Acc') # 绘制训练集准确率曲线
plt.plot(epoch_range, test_acc, label='Test Acc') # 绘制测试集准确率曲线
plt.legend(loc='lower right') # 设置图例位置
plt.title('Training and Validation Acc') # (这一行是后面加的,执行时忘写了,所以左图少个标题)
plt.subplot(1, 2, 2) # 一行两列,画第二张图
plt.plot(epoch_range, train_loss, label='Train Loss') # 绘制训练集损失曲线
plt.plot(epoch_range, test_loss, label='Test Loss') # 绘制测试集损失曲线
plt.legend(loc='upper right') # 设置图例位置
plt.title('Training and Validation Loss')
plt.tight_layout() # 自动调整子图布局
plt.show() # 显示
结果:
五、总结
可能提高模型预测精度的办法:
- 调整训练轮数epoch,适当提高epoch可以增加预测准确度;
- 调整超参数,例如修改学习率的大小;
- 修改优化器,例如本例中的SGD优化器可以换成Adam优化器再进行训练。