深度之眼Pytorch打卡(九):Pytorch数据预处理——预处理过程与数据标准化(transforms过程、Normalize原理、常用数据集均值标准差与数据集均值标准差计算)

前言


  前段时间因为一些事情没有时间或者心情学习,现在两个多月过去了,事情结束了,心态也调整好了,所以又来接着学习Pytorch。这篇笔记主要是关于数据预处理过程、数据集标准化与数据集均值标准差计算的,前两者都是使用torchvision中的transforms里的方法来实现。torchvision是一个库,最常用到的主要是其中的datasets、models和transforms。datasets包含了许多数据集如MNIST、COCO、ImageNet、CIFAR、VOC等。models包含了许多模型如AlexNet、VGG、ResNet、DenseNet、GoogLeNet等,其中有些模型还是预训练好的。本笔记的知识框架主要来源于深度之眼,并作了一些相关的拓展,拓展内容主要源自对torch文档的翻译理解。

  数据切分参考:深度之眼Pytorch打卡(六):将数据集切分成训练集、验证集和测试集的方法
  数据读取参考:深度之眼Pytorch打卡(七):Pytorch数据读取机制,DataLoader()和Dataset


预处理过程


  读入的数据都是需要进行预处理的,在数据读取机制这篇笔记中虽然只是记录如何读取数据,但是也用了一种数据预处理方式,即在一次读取完成后,使用transforms.ToTensor()将PIL图像转换成数据范围为[0,1]的张量。实际的数据预处理,会用到很多方法,他们往往不是在要使用单独调用,而是会以组合形式出现。transforms.Compose()中以list的形式存放若干transforms方法,就可以实现这些方法的组合,定义一个全局的变量,以后就可以在任何地方使用用该变量,进而实现这些方法的整体调用。

#  预处理,标准化与图像增强

normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 依通道标准化

train_transformer = transforms.Compose([
    transforms.Resize(256),
    transforms.RandomResizedCrop((224), scale=(0.5, 1.0)),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    normalize
])

val_transformer = transforms.Compose([
    transforms.Resize(224),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    normalize
])

  数据预处理的过程渗透在整个训练迭代过程中,每读入一张图片就会给它用一次预处理组合。通过数据读取机制这篇笔记可以知道,数据读取真正的实现是在我们定义的DataSet类的__getitem__方法中,如果要进行数据预处理就是在这里实现的,读取一张图片,调用一次预处理组合,进行一次预处理,如下所示。

class DataSet(Dataset):

    def __init__(self, , , ,transform=None):
    
         ...
        self.transform = transform
        ...

    def __getitem__(self, idx):
        ...

        if self.transform:
            image = self.transform(image)
            ...


数据标准化


torchvision.transforms.Normalize(mean, std, inplace=False)
output[channel] = (input[channel] - mean[channel]) / std[channel]

  数据标准化的目的,是为了将数据分布的均值化成0,标准差化成1。模型一般被初始化成0均值,所以这样有利于加快训练过程中模型的收敛。原本需要自己计算自己数据集的均值和标准差来进行标准化,但是许多项目直接采用了imagenet的均值和标准差来进行标准化,所以经常见到这样的一组数字:mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225],下表是一些数据集的均值与标准差。

表1.一些数据集均值标准差
数据集 均值 方差
cifar10 [0.491, 0.482, 0.446] [0.247, 0.243, 0.261]
coco [0.471, 0.448, 0.408] [0.234, 0.239, 0.242]
imagenet [0.485, 0.456, 0.406] [0.229, 0.224, 0.225]
MNIST [0.1307] [ 0.3081]
  • 数据集标准差与均值计算

  1.cifar10数据集

  cifar10是Alex Krizhevsky, 和Geoffrey Hinton等做的数据集,10类共6万张图,每类6千张,每张图尺寸为(3,32,32),训练集5万张,测试集1万张。pytorch直接提供了cifar10的接口,调用一个函数就可以直接获得该数据集,而且该数据集比较小,也有人算过它的均值标准差,适合用来做实验。

torchvision.datasets.CIFAR10(root, train=True, transform=None, target_transform=None, download=False)

  CIFAR10是一个继承于抽象类Dataset的一个类,里面不仅复写了__getitem__()__len__()方法,还写有数据集下载,解压和验证数据集完整性等的方法。数据集下载完成后形成的的CIFAR10,就相当于实例化后的Dataset,可以被DataLoader直接使用。
  root:数据集存放的位置。
  train:为True时表示是训练集的Dataset实例化,反之是测试集。
  transform:前面提到数据预处理是在__getitem__()中实现的,所以这里需要有此参数,不过此间目的是算均值标准差,故只需用ToTensor()归一化一下数据便可。
  download:为True,且root目录下没有解压后完整的数据集,则从该处下载数据集并解压到root目录下。

  对于较小的数据集,可以计算整个数据集的均值标准差,但是对于很大的数据集,则采用随机抽取若干样本的均值和标准差来代替整体的标准差。用numpystdmean方法来求标准差与均值,通过axis选择求取均值的维度。对于(50000,3,32,32)的数据,axis=(0, 2, 3)可以理解为先计算维度0的均值与方差,即压缩0维,完成后将得到一幅彩色图像(1,3,32,32)->(3,32,32),然后计算维度2的均值与方差,完成后将得到三个向量,即(1,3,1,32)->(3,32),最后计算维度3的均值与方差,完成后为一向量(3,1)即为所求。

np.mean(data_selected.numpy(), axis=(0, 2, 3))
std = np.std(data_selected.numpy(), axis=(0, 2, 3))

  如下笔者提供的方法。用随机打乱索引的方式来随机选取样本。由于数据集很小,所以ratio设的1,代表用全部数据来计算。注意到这里的dataset里是带有数据的,如果没有数据则不能用。
  代码:

import torch
import random
import time
from torchvision import datasets
from torchvision import transforms
import numpy as np

def get_std_mean(dataset, ratio=1):

    data_x = dataset.data
    data_x = torch.transpose(torch.from_numpy(data_x), dim0=1, dim1=3) #(50000,32,32)->(50000,3,32,32)
    data_num = len(data_x)
    idx = list(range(data_num))
    random.shuffle(idx)                                                #产生随机索引
    data_selected = data_x[idx[0:int(ratio * data_num)]]
    mean = np.mean(data_selected.numpy(), axis=(0, 2, 3)) / 255
    std = np.std(data_selected.numpy(), axis=(0, 2, 3)) / 255
    return mean, std


if __name__ == '__main__':
    
    train_dataset = datasets.CIFAR10('./data',
                                             train=True, download=False,
                                             transform=transforms.ToTensor())

    test_dataset = datasets.CIFAR10('./data',
                                           train=False, download=False,
                                            transform=transforms.ToTensor()
                                            
    time0 = time.time()
    train_mean, train_std = get_std_mean(train_dataset)
    test_mean, test_std = get_std_mean(test_dataset)
    time1 = time.time()
    time = time1 - time0
    print(time)
    print(train_mean, train_std)
    print(test_mean, test_std)

  结果:符合上表数据。

1.7163739204406738
[0.49139968 0.48215841 0.44653091] [0.24703223 0.24348513 0.26158784]
[0.49421428 0.48513139 0.45040909] [0.24665252 0.24289226 0.26159238]

  改自这篇博客提供的方法。其巧妙的运用了DataLoader来随机选择一个batch的数据,而使得代码非常简洁,但是耗时却长了很多。由下图可知,DataLoader也是通过打乱的索引来随机获取数据的,但是由于其此处用循环来得到一个batch索引,加上还要进行如transform的其他许多操作,故而慢了不少。该方法无论dataset是否带数据都可以使用。
在这里插入图片描述
  代码:

import torch
import random
import time
from torchvision import datasets
from torchvision import transforms
import numpy as np

def get_mean_std(dataset, ratio=1):

    dataloader = torch.utils.data.DataLoader(dataset, batch_size=int(len(dataset)*ratio),
                                             shuffle=True, num_workers=0)
    data = iter(dataloader).next()[0]   # 一个batch的数据

    mean = np.mean(data.numpy(), axis=(0,2,3))
    std = np.std(data.numpy(), axis=(0,2,3))
    return mean, std

if __name__ == '__main__':
    
    train_dataset = datasets.CIFAR10('./data',
                                             train=True, download=False,
                                             transform=transforms.ToTensor())

    test_dataset = datasets.CIFAR10('./data',
                                           train=False, download=False,
                                            transform=transforms.ToTensor())
                                            
    time0 = time.time()
    train_mean, train_std = get_mean_std(train_dataset)
    test_mean, test_std = get_mean_std(test_dataset)
    time1 = time.time()
    time = time1 - time0
    print(time)
    print(train_mean, train_std)
    print(test_mean, test_std)

  结果:符合上表数据,时间要长4-5倍。

7.437113046646118
[0.49140078 0.48215902 0.44653085] [0.24703279 0.24348494 0.26158768]
[0.49421415 0.48513135 0.4504101 ] [0.24665275 0.24289201 0.26159224]

  2.自定义的数据集

  自定义的数据集,在数据读取机制这篇笔记中所述的方法获得的实例化的Dataset中没有数据,只有数据的地址信息。在使用时,需要一个batch一个batch的从数据地址读取数据,所以用上述第二种方法更方便一些。
  main.py

from tools.dataload import DataSet
from tools.get_mean_std import get_mean_std
import os

label_name = {
    
    'unmasking': 0, 'masking': 1}

if __name__ == '__main__':

    train_set_path = os.path.join('data', 'train_set')
    val_set_path = os.path.join('data', 'val_set')
    test_set_path = os.path.join('data', 'test_set')
    train_set = DataSet(data_path=train_set_path, label_name=label_name)
    val_set = DataSet(data_path=val_set_path, label_name=label_name)
    test_set = DataSet(data_path=test_set_path, label_name=label_name)

    train_mean, train_std = get_mean_std(train_set)
    val_mean, val_std = get_mean_std(val_set)

    print('train_set:', train_mean, train_std)
    print('val_set:', val_mean, val_std)

  get_mean_std.py

import torch
import random
import time
from torchvision import datasets
from torchvision import transforms
import numpy as np


def get_mean_std(dataset, ratio=1):
    dataloader = torch.utils.data.DataLoader(dataset, batch_size=int(len(dataset) * ratio),
                                             shuffle=True, num_workers=0)
    data = iter(dataloader).next()[0]  # 一个batch的数据

    mean = np.mean(data.numpy(), axis=(0, 2, 3))
    std = np.std(data.numpy(), axis=(0, 2, 3))
    return mean, std


def get_std_mean(dataset, ratio=1):
    data_x = dataset.data
    data_x = torch.transpose(torch.from_numpy(data_x), dim0=1, dim1=3)  # (50000,32,32)->(50000,3,32,32)
    data_num = len(data_x)
    idx = list(range(data_num))
    random.shuffle(idx)  # 产生随机索引
    data_selected = data_x[idx[0:int(ratio * data_num)]]
    mean = np.mean(data_selected.numpy(), axis=(0, 2, 3)) / 255
    std = np.std(data_selected.numpy(), axis=(0, 2, 3)) / 255
    return mean, std

  dataload.py

import os
from PIL import Image
from torch.utils.data import DataLoader
from torchvision import transforms
from torch.utils.data import Dataset

label_name = {
    
    }


class DataSet(Dataset):
    def __init__(self, data_path, label_name):  # 除了这两个参数之外,还可以设置其它参数
        self.label_name = label_name
        #self.data_info = get_info_list(data_path)
        self.data_info = get_info(data_path, label_name)

    def __getitem__(self, index):
        label, img_path = self.data_info[index]
        pil_img = Image.open(img_path).convert('RGB')  # 读数据
        re_img = transforms.Resize((32, 32))(pil_img)
        img = transforms.ToTensor()(re_img)  # PIL转张量

        return img, label

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


def get_info(data_path, label_name):
    data_info = list()
    for root_dir, sub_dirs, _ in os.walk(data_path):
        for sub_dir in sub_dirs:
            file_names = os.listdir(os.path.join(root_dir, sub_dir))
            img_names = list(filter(lambda x: x.endswith('.jpg'), file_names))
            for i in range(len(img_names)):
                img_path = os.path.join(root_dir, sub_dir, img_names[i])
                img_label = label_name[sub_dir]
                data_info.append((img_label, img_path))

    return data_info


def get_info_list(list_path):
    data_info = list()
    with open(list_path, mode='r') as f:
        lines = f.readlines()
        for j in range(len(lines)):
            img_label = int(lines[j].split(',')[0])
            img_path = lines[j].split(',')[1].replace('\n', '')
            data_info.append((img_label, img_path))
    return data_info

  结果:

train_set: [0.58094215 0.53512526 0.5137592 ] [0.29900312 0.2990571  0.30414605]
val_set: [0.57392234 0.5163339  0.49625048] [0.2890443  0.2879651  0.29451588]

参考


  https://blog.csdn.net/weixin_38533896/article/details/85951903
  https://blog.csdn.net/cvsvsvsvsvs/article/details/94149896

猜你喜欢

转载自blog.csdn.net/sinat_35907936/article/details/107055187