torchvision中调用各种transform源码分享_高斯_标准化等

提示:麻烦点赞,拒绝白嫖

文章目录


前言

torchvision中封装好了多种图片数据增强的操作,调用非常的方便,今天一起来学习以下各个transform的使用方法。

一、数据增强

数据增强是一个很有效地提升模型精度的方法,通过让模型学习一些经过转置,加噪等形式的特征,可以使得模型拥有更好的鲁棒性。
但是需要注意的是,并不是任何时候都适合去无脑的把所有Transform安排上。首先,如果训练集数量非常充足,但特征足够简单,有时候是不需要Transform的。另外,笔者认为应该更加考虑测试集里面的数据集是啥样的,应该通过Transform使得训练集样本向测试集靠拢。

二、Transform

1.基础使用示例

定义一个transform,这里应该和定义的函数放在同级最好。ToTensor方法已经将图片的值从0-255映射到0-1了,所有不要再除以255了。

from torchvision import transforms as transforms

loadfold = transforms.Compose([
    transforms.ToTensor(),
])

以下写在main函数里面,通过继承dataset类,从文件夹读入图片。loadfold即为上面定义的transform,而Y_train_orig该数据集的标签,root是该数据集的相对路径。注意MyData_loadfold是在util下的一个文件,而util是和main同级的一个文件夹,MyData_loadfold文件在以下也提供了。

from util.myData_loadfold import MyData_loadfold
from torch.utils.data import DataLoader

train_dataset = MyData_loadfold(transform=loadfold, Y=Y_train_orig, root=imgtrainfold)
    train_loader = DataLoader(train_dataset, batch_size=1, shuffle=True)

MyData_loadfold.py

import torch
from torch.utils.data import DataLoader, Dataset
from torchvision import transforms
from PIL import Image
import os
import numpy as np
import re


class MyData_loadfold(Dataset):  # 继承Dataset
    def __init__(self, transform=None, Y=0, root='', state='Train', k=0):  # __init__是初始化该类的一些基础参数
        self.transform = transform  # 变换
        imgs = os.listdir(root)
        imgs.sort(key=lambda x: int(re.match('(\d+)\.jpg', x).group(1)))
        self.imgs = [os.path.join(root, i) for i in imgs]
        self.transforms = transform
        self.root = root
        self.state = state
        self.k = k
        self.Y = Y
        self.size = tuple([len(imgs),1])

    def __len__(self):
        return len(self.imgs)

    def __getitem__(self, index):
        img_path = self.imgs[index]
        pil_img = Image.open(img_path)
        if self.transforms:
            data = self.transforms(pil_img)
        else:
            pil_img = np.asarray(pil_img)
            data = torch.from_numpy(pil_img)
        return data, self.Y[index]

2.标准化

除了树模型了,所有数据都需要标准化,标准化效果比归一化效果好一些。虽然toTensor已经将图片的RGB值转换为0-1之间,但是对其进行标准化转为(-1,1)之间效果更好。进行标准化的化首先需要求出样本的均值与方差,对于图片来说,是求其三个通道的均值与方差,imagenet预训练网络已经给了一个均值与方差,但并不符合我们的训练集数据,因此我们需要自己去求训练集的均值与方差,**注意测试集也默认是使用训练集的标准化系数进行标准化的,这是机器学习和深度学习的一个假设前提,测试集符合训练集的分布。**下面是求自己的训练集标准化系数的代码,其中train_loader是上面第一节定义好的。

    for i, data in enumerate(train_loader , 1):
        img, label = data
        labellist.append(label)
        imglist.append(img)
        
    traindata = torch.cat(imglist, dim=0)
    stdRGB = [0, 0, 0]
    avgRGB = [0, 0, 0]
    for i in range(3):
        avgRGB[i] = traindata[:, i, :, :].mean()
        stdRGB[i] = traindata[:, i, :, :].std()

3.其他Transform

这一节就介绍其他transform了,这里只介绍一些常用的。

# 随机放缩裁剪,先进行随机放缩,然后按imgsize*imgsize进行裁剪。
# 每个epoch之间也都是随机裁剪的。
RRC = transforms.Compose([
    transforms.RandomResizedCrop(imgsize),
    transforms.ToTensor(),
])
# 随机裁剪,按imgsize*imgsize进行裁剪。
# 每个epoch之间也都是随机裁剪的。
CC = transforms.Compose([
    transforms.CenterCrop(imgsize),
    transforms.ToTensor(),
])

# 这里是标准化,第一个是Imagenet的参数,第二个是笔者参数,不要自己用
# 去第二节求了再填入。
Normal_transform = transforms.Compose([
    # transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),# Imagenet
    # transforms.Normalize(mean=[0.4772, 0.5595, 0.3851], std=[0.2871, 0.2960, 0.2952])  # all train
])

# 这里是加曝光,这些参数不用太改,反正笔者改了也没啥用
# 但是加了曝光有点用。
CJ_transform = transforms.Compose([
    ResizeImage(imgsize),
    transforms.ColorJitter(brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5),
    transforms.ToTensor(),
])
# 这里是旋转,角度为30,其他参数可以不看
RR_transform = transforms.Compose([
    ResizeImage(imgsize),
    transforms.RandomRotation(30, resample=False, expand=False, fill=0),
    transforms.ToTensor(),
])

# 这里是将图片直接左右颠倒的意思,Horizontal,p是概率
RHF_transform = transforms.Compose([
    ResizeImage(imgsize),
    transforms.RandomHorizontalFlip(p=1),
    transforms.ToTensor(),
])
# 这里是将图片直接上下颠倒的意思,Vertical,p是概率
RVF_transform = transforms.Compose([
    ResizeImage(imgsize),
    # transforms.RandomResizedCrop(imgsize),
    transforms.RandomVerticalFlip(p=1),
    transforms.ToTensor(),
])
# 这里是增加高斯噪声,需要一个单独的类,后面提供。
from util import AddGaussianNoise
GN_transform = transforms.Compose([
    ResizeImage(imgsize),
    # transforms.RandomResizedCrop(imgsize),
    AddGaussianNoise.AddGaussianNoise(mean=1, variance=1, amplitude=10),
    transforms.ToTensor(),
])

AddGaussianNoise.py

import PIL.Image
import numpy as np

class AddGaussianNoise(object):

    def __init__(self, mean=0.0, variance=1.0, amplitude=1.0):

        self.mean = mean
        self.variance = variance
        self.amplitude = amplitude

    def __call__(self, img):
        img = np.array(img)
        h, w, c = img.shape
        N = self.amplitude * np.random.normal(loc=self.mean, scale=self.variance, size=(h, w, 1))
        N = np.repeat(N, c, axis=2)
        img = N + img
        img[img > 255] = 255                       # 避免有值超过255而反转
        img = PIL.Image.fromarray(img.astype('uint8')).convert('RGB')
        return img

总结

笔者和同学参加图片分类的一些比赛,同学认为直接在训练集加transform就行了, 结果精度反而比不加更低。笔者认为更重要的是分析训练集和测试集的图片到底是什么状态。这次比赛实际上测试集大部分图片是正常的,包括训练集也一样大部分都是正常的,也就是没有颠倒,没有噪声等。如果在训练集源样本就直接加transform,会使得模型每个epoch都丢失一部分正常的源样本。因此,笔者通过list等操作实现了累加transform,最终将400张图片的扩充到3600张图片,其中3份源样本,2份随机裁剪,以及曝光,左右颠倒,上下颠倒,旋转30度各一份。这样操作下来一个epoch训练的时间是更慢了,但是收敛也更快了,重要的是这样效果好一点。最后,我想说的是不能一成不变得套用transform,要不断尝试噢。

上一篇:torchvision.transforms进行图像缩放以及类型转换 记录


下一篇:2021-09-08