数据读取Dataloader,Dataset|图像预处理Transforms
基本概念
- 数据
- 数据收集:Img,Label
- 数据划分:train valid test, valid用于挑选没有过拟合的模型,test 测试挑选出来的模型的性能
- 数据读取:DataLoader
- Sampler: 生成索引index
- DataSet: 根据Img读取标签
- 数据预处理:transforms
- 模型
- 损失函数
- 优化器
- 迭代训练
1. DataLoader
torch.utils.data.DataLoader(dataset,batch_size=1,num_works=0,shuffle=False,drop_last=False)
- dataset:数据从哪里读取及如何读取
- batchsize:批大小
- num_works:是否多进程读取数据
- drop_last:当样本数不能被batchsize整除时,是否舍弃最后一批数据
Epoch:所有训练样本都已输入到模型中1次
Itertion:一批样本输入到模型中
Batchsize:批大小,决定一个Epoch有多少个Iteration
例子
样本总数87,BatachSize 8
1 Epoch = 10 Iteration,如果drop_last=False
1 Epoch = 11 Iteration,如果drop_last=True
2. DataSets
torch.utils.data.Dataset
功能:Dataset抽象类,所有自定义的Dataset都需要继承它,并且复写
__getitem__()
,接受一个索引,返回一个样本
class Dataset(object):
def __getitem__(self,index):
raise NotImplementedError
def __add__(self,other):
return ConcatDataset([self,other])
DataSet,DataLoader例子:
class RMBDataset(Dataset):
def __init__(self, data_dir, transform=None):
"""
rmb面额分类任务的Dataset
:param data_dir: str, 数据集所在路径
:param transform: torch.transform,数据预处理
"""
self.label_name = {"1": 0, "100": 1}
self.transform = transform
self.data_info = self.get_img_info(data_dir)
# data_info存储所有图片路径和对应的标签,在DataLoader中通过index读取样本
def __getitem__(self, index):
path_img, label = self.data_info[index]
img = Image.open(path_img).convert('RGB') # 0~255
# 如果初始化的时候制定了一种transform的方法
if self.transform is not None:
img = self.transform(img) # 在这里做transform,转为tensor等等
return img, label
def __len__(self):
return len(self.data_info)
@staticmethod
def get_img_info(data_dir):
data_info = list()
for root, dirs, _ in os.walk(data_dir):
# 遍历类别
for sub_dir in dirs:
img_names = os.listdir(os.path.join(root, sub_dir))
img_names = list(filter(lambda x: x.endswith('.jpg'), img_names))
# 遍历图片
for i in range(len(img_names)):
img_name = img_names[i]
path_img = os.path.join(root, sub_dir, img_name)
label = rmb_label[sub_dir]
data_info.append((path_img, int(label)))
return data_info
# 构建MyDataset实例
train_data = RMBDataset(data_dir=train_dir, transform=train_transform)
# 构建DataLoader
train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
# data是一个含有input和labels的list
for i, data in enumerate(train_loader):
具体流程:
第一步:for i, data in enumerate(train_loader)
第二步:DataLoader类判断num_workers是否大于0,这里等于0,所以运行return self._get_iterator()
第三步:DataLoaderIter __next__函数
3. Transform
3.1 Transform的运行机制
torchvision
:计算机视觉工具包,它包括
torchvision.transforms
:常用的图像预处理方法torchvision.datasets
:常用数据集的dataset实现,MNIST,CIFAR-10,ImageNet等,即得到数据的方法torchvision.model
:常用的模型预训练,AlexNet,VGG,ResNet,GoogleLeNet等。
3.2 Transforms 例子
类似5年高考3年模拟😂.,模拟的就是transform的
常用的图像预处理方法:
- 数据中心化
- 数据标准化
- 缩放
- 裁剪
- 旋转
- 翻转
- 填充
- 噪声添加
- 灰度变换
例子:
norm_mean = [0.485, 0.456, 0.406]
norm_std = [0.229, 0.224, 0.225]
# 按照顺序对图像进行操作
train_transform = transforms.Compose([
transforms.Resize((32, 32)), # 将图像进行缩放
transforms.RandomCrop(32, padding=4), # 将图像进行裁剪
transforms.ToTensor(), # 变成Tensor,值从0,255变为0,1
transforms.Normalize(norm_mean, norm_std), # 将均值变为0,标准差变为1
])
# 去除了RandomCrop这个裁剪增强擦操作
valid_transform = transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor(),
transforms.Normalize(norm_mean, norm_std),
])
# 将构建好的Transorm传入DataSet的初始化过程
# transform在Dataset的__getitem__方法中被调用
train_data = RMBDataset(data_dir=train_dir, transform=train_transform)
valid_data = RMBDataset(data_dir=valid_dir, transform=valid_transform)
# DataSet -> DataLoader
train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)
3.3 transforms.Normalize
transforms.Normalize(mean,std,inplace=False)
- mean:各通道的均值
- std:各通道的标准差
- inplace:是否原地操作
Rq:
tensor.sub_
中的_
表示inplace操作。- 可以加速模型收敛
3.4 transfrom中其他数据增强方法
目的:让模型更具泛化能力
以下函数直接填在transforms.Compose()
里面即可
transforms.CenterCrop(196)
将图像变为196196的,如果比原图大,则多出部分填充空白。196196的中心是。
transforms.RandomCrop(size,padding=None,pad)
从图片中随机裁剪出尺寸为size的图片。
- size:如224意味着224*224
- padding:设置填充大小
- 当为a时,上下左右均填充 a 个像素
- 当为(a, b)时,上下填充 b 个像素,左右填充 a 个像素
- 当为(a, b, c, d) 时,左,上,右,下分别填充 a, b, c, d。
- pad_if_need:如我们设置的size,则要把它改为True,否则会报错
- fill:在paddig_mode为constant时,设置填充的像素值,如(R,G,B)=(255,0,0)
- padding_mode:填充模式
- constant:像素值由fill决定
- edge:像素值由图像边缘像素决定
- reflect:镜像填充,最后一个像素不镜像,如[1,2,3,4] -> [3,2,1,2,3,4,3,2]
- symmetric:镜像填充,最后一个像素镜像,如[1,2,3,4] -> [2,1,1,2,3,4,4,3]
例子:
transfroms.RandomCrop(224,padding=16)
最后输出的图像为尺寸为224+16+16=256,然后我们随机在上面选取224*224的区域放上我们的裁剪过后的图像,剩下的就是padding。
transforms.RandomResizedCrop(size,scale=(0.08,1.0),ratio=(3/4,4/3),interpolation)
- size:所需裁剪图片尺寸,最后resize到这个尺寸
- scale:随机裁剪面积比例,默认是从0.08到1中间随机选取一个数字
- ratio:随机长宽比,默认是(3/4,4/3),长比宽的大小
- interpolation:插值方法
- PIL.Image.NEAREST
- PIL.Image.BILINEAR
- PIL.Image.BICUBIC
transforms.FiveCrop(size)
功能:在图像的上下左右以及中心裁剪出尺寸为 size 的 5 张图片。
返回的是一个tuple,transform.Lambda(lambda crops: torch.stack([(transforms.ToTensor()(crop)) for crop in crops]))
- size: 所需裁剪图片尺寸
transforms.TenCrop(size,vertical_flip=False)
功能:在图像的上下左右以及中心裁剪出尺寸为 size 的 5 张图片, TenCrop 对这 5 张图片进行水平或者垂直镜像获得 10 张图片
- vertical_flip = False,不进行垂直翻转,但进行水平翻转。
transforms.RandomHorizontalFlip(p)
- 有p的概率进行水平翻转
transforms.RandomVerticalFlip(p)
- 由p的概率进行垂直翻转
transforms.RondomRotation(degrees,resample=False,expand=False)
- degrees:旋转角度。为a时,在(-a,a)之间选择旋转角度,为(a,b)时,在(a,b)之间选择旋转角度
- resample:重采样方法
- expand:是否扩大图片,以保持原图信息。因为旋转之后有可能超过原图的尺寸。如果要expand,需要resize,从而将所有图片的尺寸变为一样。expand是对于center计算的,如果旋转中心不是中心,不一定会完全保留所有的原图信息。
- center:默认是中心,可以指定为左上角
3.5 图像变换
transform.Pad(padding,fill=0,padding_mode='constant')
- padding,padding_mode,fill:见上面
transforms.RandomCrop
的介绍。
transforms.ColorJotter(brightness=0,contrast=0,saturation=0,hue=0)
功能:调整亮度、对比度、饱和度和色相
- brightness: 亮度调整因子,当为a时,从[max(0,1-a),1+a]中随机选择,小于1变暗。当为(a,b)时,从[a,b]中随机选择
- contrast:对比度参数,同brightness
- saturation:饱和度,同brightness
- hue:色相参数,若为a,从[-a,a]中选择,当为(a,b)时,从[a,b]中选择,最大区间为[-0.5,0.5]
转为灰度图
transforms.Grayscale(num_output_channels)
transforms.RandomGrayscale(num_output_channels,p=0.1)
功能:一定概率将图片转换为灰度图
- num_output_channels:输出通道数1或者3
仿射变换
RandomAffine(degrees,translate=None,scale=None,shear=None,resample=False,fillcolor=0)
功能:对图像进行仿射变换,由5种基本原子变换构成,分别是旋转、平移、缩放、错切和翻转。
- degrees: 中心旋转角度设置
- translate: 平移区间设置,如(a,b),a设置宽(width),b设置高(height)。图像在宽维度平移的区间为-img_width*a < dx < img_width * a
- scale:缩放比例(以面积为单位)
- fill_color:填充颜色设置
- shear:错切角度设置,有水平、垂直错切。水平错切类似于把一堆书水平摞在桌子上,然后每一本书相右都移动一点。
- a,仅在x轴错切,错切角度在(-a,a)之间
- (a,b),则a设置x轴角度,b设置y的角度
- (a,b,c,d),则a,b设置x轴角度,c,d设置y轴角度。
- resample:重采样方法,有NEAREST、BILINEAR、BICUBIC。
随机遮挡
RandomErasing(p=0.5,scale=(0.02,0.33),ratio=(0.3,3.3),value=0,inplace=False)
功能:对图像进行随机遮挡
- p:概率值,执行该操作的概率
- scale:遮挡区域的面积
- ratio:遮挡区域长宽比
- value:设置遮挡区域的值,如(R,G,B)或者(Gray),如果是一个字符串就是随机噪声(类似于电视雪花屏)
⚠️ 它接受的是一个Tensor,不像之前的函数是输入一个PIL Image。
transform.Lambda(lambd)
功能:用户自定义lambda方法
transforms.TenCrop(200,vertical_flip=False)
# crops是上一个函数的输出
# 我们把Stack一个Tensor的list (C,H,W)
# 然后stack成为一个(10,C,H,W)的Tensor
transforms.Lambda(lambda crops : torch.stack([transforms.Totensor()(crop) for crop in crops]))
3.6 随机挑选一些transform
transforms.RandomChoice([transforms1,transforms2,transforms3])
功能:从一系列transforms操作中随机挑选一个
transforms.RandomApply([transforms1,transforms2,transforms3])
功能:依据概率执行一组transforms操作
transforms.RandomOrder([transforms1,transforms2,transforms3])
功能:对一组transforms操作打乱顺序
3.7 自定义transforms
回顾:
transform
方法是在Compose这个class里面的__call__
函数被调用的。
class Compose(object):
def __call__(self,img):
for t in self.transforms:
img = t(img)
return img
- 只能一个输入,一个输出
Sol:
通过类实现多参数传入:
class YourTransforms(object):
def __init__(self,...):
# 参数传入
def __call__(self,img):
# 实现自己要的功能
return img
例子:自定义椒盐噪声
白点盐噪声,黑色椒噪声。
信噪比(Signal-Noise Rate, SNR):图像中图像像素的占比,1的话就是原图。
class AddPepperNoise(object):
def __init__(self,snr,p=0.9):
self.snr = snr
self.p = p
def __call__(self,img):
if random.uniform(0,1)<self.p:
img_ = np.array(img).copy()
h,w,c = img_.shape
signal_pct = self.snr
noise_pct = 1-self.snr
maksk = np.random.choice = np.random.choice((0,1,2),size=(h,w,1),p=[signal_pct,noise_pct/2.,noise_pct/2.])
mask = np.repeat(mask,c,axis=2) #对另外两个通道也同样处理,即一个像素在一个通道上为椒噪声,在其他两个通道上也是椒噪声
img_[mask == 1] = 255 # 盐噪声
img_[mask == 2] = 0 # 椒噪声
return Image.fromarray(img_.astype('uint8').convert('RGB'))
else:
return img
# 之后正常用AddPepperNoise(0.8,p=0.9)加入到tranform.Compose里面即可。