1Dataset定义
1.1Dataset
自定义Dataset继承torch.utils.data.Dataset类,创建这个类需要实现三个方法:
** __init __**:
主要用于创建对象,在此方法中一般需要传入图片的路径,通过os模块读取所有图片的路径,同时完成对训练、实验、测试集的划分;
#读取所有图片的路径
imgs=[os.path.join(root,img) for img in os.listdir(root)]
__getitem __:
主要实现对data、label的获取,此方法中通过os模块、PIL模块、numpy模块、transforms模块实现对图片的提取、变形、转化为Tensor数据,同时通过文件名或标签文件提取与图片对应的label,并将两者同步返回.
#提取图片
image = Image.open(self.imgs[index])
#图片变换,并转化为Tensor
data=self.transform(image)
#返回图片数据和标签
return data,label
__len __:
主要用于返回训练集的大小,一般直接返回len(imgs)即可
def __len__(self):
return len(self.imgs)
1.2Transforms
原始获取的图片大小不统一,神经网络无法处理,且需要对图片进行随机裁剪、旋转等变换提升神经网络的鲁棒性。另外图片传入神经网络需要转换为Tensor数据,同时进行标准化可以增加神经网络的迭代速度。因此在Dataset中需要对图片进行Transforms变换,Transforms的变换可以参考这篇博客。
https://blog.csdn.net/weixin_38533896/article/details/86028509
多个Transforms变换可以通过Transforms.Compose组合起来使用。
self.transform = transforms.Compose(
[
transforms.Resize(224),
transforms.RandomResizedCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406]
, std=[0.229, 0.224, 0.225])
]
)
1.3整体代码
####数据集作用:加载本地文件,划分训练集、测试集,读取图片转化为tensor,并获取对应label
import torchvision
import torch
import numpy
from PIL import Image
import os
from torch.utils import data
import torchvision.transforms as transforms
#创建猫狗分类数据集
class DogCat(data.Dataset):
#目的:获取所有图片的地址,并划分训练、测试、开发集
def __init__(self,root,transform=None,train=True,test=False):
#三种集
#01.test=True测试集
#02.test=False,train=True训练集
#03.test=False,train=False验证集
#三种集对应不同的transforms转换方式
#测试集从test文件夹读取,训练集和验证集从train文件夹读取,且比例为:7:3
self.test=test
#读取所有图片的路径
imgs=[os.path.join(root,img) for img in os.listdir(root)]
if(self.test):
imgs=sorted(imgs,key=lambda x:int(x.split('.')[-2].split('/'[-1])))
else:
imgs = sorted(imgs, key=lambda x: int(x.split('.')[-2]))
imgs_num=len(imgs)
##分配测试集、训练集、验证集
if(self.test):
self.imgs=imgs
elif(train):
self.imgs=imgs[:int(0.7*imgs_num)]
else:
self.imgs=imgs[int(0.7*imgs_num):]
##设置测试、训练、验证集的变换方式
if transform==None:
if(test or not train):
self.transform=transforms.Compose(
[
transforms.Resize(224),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485,0.456,0.406]
,std=[0.229,0.224,0.225])
]
)
else:
self.transform = transforms.Compose(
[
transforms.Resize(224),
transforms.RandomResizedCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406]
, std=[0.229, 0.224, 0.225])
]
)
#针对训练、测试、验证集返回不同的label
#训练、验证集来自train,其格式自带cat,dog标致,提取即可
#test没有猫狗标志,直接返回其编号值
def __getitem__(self, index):
#提取label
if(self.test):
label=int(self.imgs[index].split('.')[-2].split('/')[-1])
else:
label=1 if 'dog' in self.imgs[index].split('.')[-1] else 0
#提取图片
image = Image.open(self.imgs[index])
#图片变换,并转化为Tensor
data=self.transform(image)
#返回图片数据和标签
return data,label
def __len__(self):
return len(self.imgs)
2利用Sequential实现神经网络代码的精简化
神经网络的Conv2d、Pool、BatchNorm2d本质上都是Module的子类,因此神经网络的创建过程实质是Module中嵌套子Module的过程。而Sequential同样是Module的子类,其内部可以存放多个Conv2d、Pool、BatchNorm2d子类对其进行封装。因此可以通过Sequential很方便的对神经网络进行再次封装,一个Sequential内部存放重复的Conv2d→Pool→BatchNorm2d→ReLU操作,然后神经网络本体内部重复调用Sequential即可完成整体的搭建。这种操作可以将神经网络的代码精简,同时也符合神经网络层/模块(ResNet/Incepion/VGG等)的定义,同时对与不同层使用不同训练步长也比较容易实现。测试的demo为使用Sequential实现一个残差块,完成resnet34网络的编写。代码如下图所示:
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.models as models
#设置残差块
class ResidualBlock(nn.Module):
def __init__(self,inChannels,outChannels,stride=1,shortcut=None):
super(ResidualBlock, self).__init__()
#左侧传输路径
self.left=nn.Sequential(
nn.Conv2d(inChannels,outChannels,3,stride,1,bias=False),
nn.BatchNorm2d(outChannels),
nn.ReLU(inplace=True),
nn.Conv2d(outChannels,outChannels,3,1,1,bias=False),
nn.BatchNorm2d(outChannels))
#右侧路径
self.right=shortcut
def forward(self,x):
out=self.left(x)
#如果右侧直连为None则默认自身传过去,反之传入设置的参数
residual=x if self.right is None else self.right(x)
out+=residual
return F.relu(out)
#利用残差块设置残差网络
class ResNet(nn.Module):
def __init__(self,num_classes=1000):
super(ResNet, self).__init__()
#设置前几层
self.pre=nn.Sequential(
nn.Conv2d(3,64,7,2,3,bias=False),
#批标准化,也就是归一化,防止由于神经网络太深而导致的梯度消失和梯度爆炸的问题
#输入的参数为传入的通道数
nn.BatchNorm2d(64),
nn.ReLU(inplace=True))
#设置残差层
self.layer1=self._make_layer(64,128,3)
self.layer2 =self._make_layer(128,256,4,stride=2)
self.layer1=self._make_layer(256,512,6,stride=2)
self.layer1=self._make_layer(512,512,3,stride=2)
#全连接层
self.fc=nn.Linear(512,num_classes)
def _make_layer(self,inChannels,outChannels,block_num,stride=1):
shortcut=nn.Sequential(
#尺寸不变通道数增加
nn.Conv2d(inChannels,outChannels,1,stride,bias=False),
nn.BatchNorm2d(outChannels))
layers=[]
#一次通道增加残差块
layers.append(ResidualBlock(inChannels,outChannels,stride,shortcut))
#多次通道不变残差块
for i in range(1,block_num):
layers.append(ResidualBlock(outChannels,outChannels))
#*表示迭代传入列表中的元素,否则汇报数据类型错误
return nn.Sequential(*layers)
def forward(self,x):
x=self.pre(x)
x=self.layer1(x)
x=self.layer2(x)
x=self.layer3(x)
x=self.layer4(x)
x=F.avg_pool2d(x,7)
x=x.view(x.size(0),-1)
return self.fc(x)
#自建ResNet
model1=ResNet()
print(model1)
#网络模块
model2=models.resnet34()
print(model2)