赞
踩
本篇博客主要解决以下3个问题:
第一篇:【PyTorch】构造VGG19网络进行本地图片分类(超详细过程)——项目介绍
第二篇:【PyTorch】构造VGG19网络进行本地图片分类(超详细过程)——程序代码
Github:https://github.com/MarvelInSky/vgg_classify
VGG的网络结构如下,本篇博客以VGG19(E列),过多内容不再介绍。
Animal Image Dataset(DOG, CAT and PANDA)
Dataset for Image Classification Practice
下载地址:https://www.kaggle.com/ashishsaxena2209/animal-image-datasetdog-cat-and-panda
该数据集共包含3类目标:狗、猫和熊猫;每种图片各1000张;图片尺寸不固定;大部分图片为RGB图片,少部分图片为灰度图片,所以在处理数据的时候要注意通道数。
将其分为train、test两个文件:
测试环境
Python3.8
Cuda10.1
PyTorch1.7
所需依赖
matplotlib
torch
torchvision
程序名称 | 作用 |
---|---|
vgg_model.py | 继承torch.nn.Module 创建VGG这个类,构造器(__int__() )内创建了模型的结构,并创建前项传播的方法。 |
vgg_dataset.py | 继承torch.utils.data.Dataset 创建MyDataset这个类,用于加载我们所需要的的数据集。 |
vgg_train.py | 设置参数,初始化模型,加载数据集,设置优化器、损失函数进行模型训练,并保存准确率高的模型。 |
vgg_test.py | 调用已训练的模型对测试集进行测试。 |
自定义一个模型——通过继承nn.Module类来实现,需要在__init__
构造函数中申明各层的定义,在forward
中实现层之间的连接关系,实际上就是前向传播的过程。
import torch.nn as nn
class VGG(nn.Module):
# 初始化并定义网络结构
def __init__():
# 定义网络结构
def forward(self, x)
# 前项传播的方法
参考:
https://blog.csdn.net/qq_27825451/article/details/90705328
pytorch包里提供了Dataset来对数据进行加载,通过继承Dataset类得到MyDataset类,我们可以实现对自己数据集的加载。继承的类需要重写__init__
、__len__
、__getitem__
。
__len__
:返回数据内包含的数据总量,可以将所有数据的地址存储在一个列表里,返回字典的长度即刻。
__getitem__
:根据对象的索引,返回(x,y),x为图像的数据,y为标签。
__init__
:根据以上__len__
和__getitem__
的需求,可以将每张数据图片的路径存在一个list中,方便getitem
的调用。
参考:
https://blog.csdn.net/weixin_44168899/article/details/100929727
https://blog.csdn.net/leviopku/article/details/99958182
在上一步中,通过继承Dataset类得到MyDataset获得读取数据的方法,现在使用DataLoader加载这些类。
train_loader = DataLoader(dataset, batch_size, num_workers=2, shuffle=True)
dataset:为实例化MyDataset得到的对象。
batchsize:就是每次从数据中心加载进模型的数量。
num_workers:相当于有两个线程在同时处理加载。
shuffle=True:表示加载数据的时候是乱序。
参考:
https://blog.csdn.net/zw__chen/article/details/82806900
https://pytorch.org/docs/1.1.0/_modules/torch/utils/data/dataloader.html
定义:损失函数使用交叉熵
loss_function = torch.nn.CrossEntropyLoss()
在每一batch中使用,计算误差并进行反向传播
loss = loss_function(outputs, labels) # outputs为模型得到的值,labels为真值
loss.backward() # 将loss进行反向传播
表示使用交叉熵作为损失函数。
定义:使用SGD优化器,scheduler自定义调整学习率
optimizer = torch.optim.SGD(net.parameters(), lr, momentum=0.9, weight_decay=5e-4)
scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones, gamma=0.1)
参考:https://blog.csdn.net/zisuina_2/article/details/103258573
optimizer.zero_grad()
意思是把梯度置零,即将loss关于weight的导数变成0.
参考:https://blog.csdn.net/scut_salmon/article/details/82414730
net.train()
:启用Batch Normalization和Dropout
net.eval()
:不启用Batch Normalization和Dropout
参考:https://blog.csdn.net/qq_38410428/article/details/101102075
# 加载自定义模型
net = vgg_model.VGG(img_siz, input_channel, num_class)
# 训练
...
# 训练完成后保存模型
torch.save(net.state_dict(), weights_path)
net.state_dict()表示只保存模型中的参数,可以减小模型的体积。
# 加载自定义模型,此时里面参数是随机的
net = vgg_model.VGG(img_siz, input_channel, num_class)
# 加载训练好的参数到模型
net.load_state_dict(torch.load(opt.model_path))
tensorboard可用于记录训练时的日志,可以在训练时,实时观看相关数据的变化曲线,了解模型的变化趋势。
from torch.utils.tensorboard import SummaryWriter
# 开启记录
writer = SummaryWriter(log_dir)
# 添加记录数据
for epoch in range(1,epochs)
...
writer.add_scalar('Average loss', loss, epoch)
writer.add_scalar('Accuracy', acc, epoch)
使用方法:
writer.add_scalar('曲线图名称', 纵坐标, 横坐标)
查看方法:
在日志目录下打开终端输入命令:tensorboard --logdir=日志所在目录
启动后,在浏览器打开http://localhost:6006/ 。
希望在每一batch都显示实时当前的epoch、loss和lr,但是又不希望不停的print的这些值,因为这些print会减慢训练速度,也不方便查看其它epoch的效果。所以使用tqdm库的创建动态的进度条。
# 获取数据的总量
nb = len(train_dataset)
# 创建进度条
# train_loader 需要遍历的数据
# total=数据的总量,为总量/batchsize
pbar = tqdm(enumerate(train_loader), total=int(nb / opt.batch_size))
# 在训练时
for step, (images, labels) in pbar:
...
效果如下:
RuntimeError: Input type (torch.cuda.FloatTensor) and weight type (torch.FloatTensor) should be the same
原因:在编写项目的过程中,考虑了是否使用GPU的情况;所以,在传递传递参数时,如果使用GPU,则x=x.cuda()
,然而在初始化模型时,没有使用cuda();这就导致参数的格式和初始化得到的模型中的权重格式不一致。
解决:
net = vgg_model.VGG(input_channel=3, num_class=opt.num_class)
# 改为
if opt.gpu:
net = vgg_model.VGG(input_channel=3, num_class=opt.num_class).cuda()
else:
net = vgg_model.VGG(input_channel=3, num_class=opt.num_class)
# 在卷积层处发生错误
RuntimeError: Given groups=1, weight of size [512, 512, 3, 3], expected input[16, 256, 28, 28] to have 512 channels, but got 256 channels instead
原因:输入的尺寸和某一层的结构不匹配。
解决:在前项传播中,输出每一层的结构和每一层的输出结果,发现到第九层时出现错误,仔细观察每一层的input_channel和output_channel,发现第9成的网络输入输出为(512,512),此处由于疏忽写错了,将其改为(256, 512)即可。
# 在全连接层处发生错误
RuntimeError: mat1 dim 1 must match mat2 dim 0
原因:在参考代码中,第一层全连接的代码:nn.Linear(512, 4096)是将512个数据转为4096个数据,采用的数据集为cifar100,其像素尺寸为32X32,经过16层卷积和5次池化,可将原尺寸[3,32,32]转变为[512,1,1],其中512X1X1=512。然而,我的数据集的像素尺寸已经resize成224X224,经过16个层卷积和5次池化,原尺寸[3,512,512]转变为[512,7,7],其中51277=25088,所以第一层全连接的代码为:nn.Linear(25088, 4096)。
解决:但是这样写兼容性就差了许多,不能兼容各像素的图片,所以将25088这个值改成由输入图片像素构成的: c o n v O u t = 512 × ( i m g S i z e / 24 ) 2 convOut = 512 \times(imgSize / 24)^2 convOut=512×(imgSize/24)2
cuda out of memory
原因:电脑的显存不够。
解决:原本输入图像的尺寸为224224,现改为128128,原本的batchsize为8改为4。
RuntimeError: Given groups=1, weight of size [64, 3, 3, 3], expected input[1, 1, 64, 64] to have 3 channels, but got 1 channels instead
原因:默认数据集中均位3通道图片(RGB图像),但其中有混入的单通道图片(灰度图片)。
解决:在使用Dataset加载数据时,判断是否为单通道,若为单通道图片则转为三通道图片。
Training Epoch: 1 [2/2700] Loss: 1.2244 LR: 0.100000
Training Epoch: 1 [4/2700] Loss: 20.7208 LR: 0.100000
Training Epoch: 1 [6/2700] Loss: 25.6863 LR: 0.100000
Training Epoch: 1 [8/2700] Loss: 0.0000 LR: 0.100000
Training Epoch: 1 [10/2700] Loss: 1070.9552 LR: 0.100000
Training Epoch: 1 [12/2700] Loss: 91453.2031 LR: 0.100000
Training Epoch: 1 [14/2700] Loss: 489251648.0000 LR: 0.100000
Training Epoch: 1 [16/2700] Loss: 372763991191060480.0000 LR: 0.100000
Training Epoch: 1 [18/2700] Loss: nan LR: 0.100000
Training Epoch: 1 [20/2700] Loss: nan LR: 0.100000
之后Loss均为nan
原因:在训练的时候很快就开始发散了,模型未收敛。
解决:将学习率(Learning rate,lr)从0.1改为0.0001。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。