Pytorch基础学习(第二章-Pytorch数据处理)

课程一览表

目录

一、人民币二分类与DataSet与DataLoader

0.人民币二分类模型任务

1. DataLoader

2.Dataset

3.人民币二分类

二、数据预处理transforms模块机制

1.transforms运行机制

2.数据标准化——transformes.normalize

三、transforms图像增强

1.什么是数据增强

2.transforms——裁剪

3.transforms——翻转和旋转

 4.transforms——图像变换

5.transforms——transforms方法选择操作

6.自定义transforms方法

总结:二十二种transforms操作


一、人民币二分类与DataSet与DataLoader

0.人民币二分类模型任务

目标:区分人民币1元和100元 

模型训练步骤

Pytorch基础学习(第二章-Pytorch数据处理)_第1张图片

 数据的处理流程

Pytorch基础学习(第二章-Pytorch数据处理)_第2张图片

1. DataLoader

torch.utils.data.Dataloader

功能:构建可迭代的数据装载器

dataset:Dataset类,决定数据从哪读取及如何读取

batchsize:批大小

num_works:是否多进程读取数据,可以减少数据读取时间,加快训练速度(一般设为4,8,16)

shuffle:每个epoch是否乱序

drop_last:当样本数不能被batchsize整除时,是否舍弃最后一批数据

Pytorch基础学习(第二章-Pytorch数据处理)_第3张图片

特别区分:Epoch、Iteration和Batchsize之间的关系

Epoch:所有训练样本都已输入到模型中,称为一个epoch

Iteration:一批样本输入到模型中,称之为一个Iteration

Batchsize:批大小,决定一个Epoch有多少个Iteration

例 1:样本总数:80  , Batchsize:8

1 Epoch = 10 Iteration

例 2:样本总数 87   , Batchsize:8

1 Epoch = 10 Iteration     ——>        drop_last = True

1 Epoch = 11 Iteration     ——>        drop_last = False

2.Dataset

torch.utils.data.Dataset

功能:用来定义数据从哪里读取以及如何读取。Dataset抽象类,所有自定义的Dataset需要继承它,并且复写_ _getitem_ _()

getitem:接受一个索引,返回一个样本

Pytorch基础学习(第二章-Pytorch数据处理)_第4张图片

:读取数据时,读取哪些数据?从哪读数据?怎么读数据?

Pytorch基础学习(第二章-Pytorch数据处理)_第5张图片

3.人民币二分类

(1)准备数据集:

1 元数据100张

Pytorch基础学习(第二章-Pytorch数据处理)_第6张图片

 100元数据100张

Pytorch基础学习(第二章-Pytorch数据处理)_第7张图片

 (2)数据集划分(训练集、验证集、测试集)

# -*- coding: utf-8 -*-

import os
import random
import shutil

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
"""
__file__ :是模块文件(即 .py 文件)的一个属性,返回当前模块文件所在的路径
os.path.abspath() :是 os 模块当中的一个函数,这个函数接收一个 path 路径对象,返回 path 标准化的绝对路径。
    在 Linux 系统中,路径分隔符为斜杠 “/”,在 Windows 系统下,路径分隔符为反斜杠 “\” 。
os.path.dirname(): 是 os 模块当中的一个函数,这个函数接收一个 path 路径对象,返回路径 path 的父目录名称。
    这是将 path 传入函数 split() 之后,返回的一对值中的第一个元素。
    os.path.split(path)
        将路径 path 拆分为一对,即 (head, tail),其中,tail 是路径的最后一部分,而 head 里是除最后部分外的所有内容。
        tail 部分不会包含斜杠,如果 path 以斜杠结尾,则 tail 将为空。
        如果 path 中没有斜杠,head 将为空。
        如果 path 为空,则 head 和 tail 均为空。
        head 末尾的斜杠会被去掉,除非它是根目录(即它仅包含一个或多个斜杠)。
        简单地说 os.path.split(path) 的作用就是返回 path 的上级目录(末尾不带斜杠)。
        例:
        # os.path.dirname() 调用的时候调用 os.path.split()
        print(os.path.split(os.path.abspath(__file__)))
        print(os.path.dirname(os.path.abspath(__file__)))
        运行结果:
        ('E:\\PythonProject', 'test.py')
        E:\PythonProject
"""

#创建文件夹
def makedir(new_dir):
    if not os.path.exists(new_dir):
        os.makedirs(new_dir)


if __name__ == '__main__':

    dataset_dir = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "data", "RMB_data", "RMB_data"))
    split_dir = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "data","RMB_data", "rmb_split"))
    train_dir = os.path.join(split_dir, "train")
    valid_dir = os.path.join(split_dir, "valid")
    test_dir = os.path.join(split_dir, "test")

    """
    os.path.join()函数:连接两个或更多的路径名组件
        1.如果各组件名首字母不包含’/’,则函数会自动加上
        2.如果有一个组件是一个绝对路径,则在它之前的所有组件均会被舍弃
        3.如果最后一个组件为空,则生成的路径以一个’/’分隔符结尾
    ".." 表示返回上一个文件夹
    """

    #判断人民币数据文件是否存在
    if not os.path.exists(dataset_dir):
        raise Exception("\n{} 不存在,请下载 02-01-数据-RMB_data.rar 放到\n{} 下,并解压即可".format(
            dataset_dir, os.path.dirname(dataset_dir)))

    #数据集划分比例
    train_pct = 0.8
    valid_pct = 0.1
    test_pct = 0.1

    for root, dirs, files in os.walk(dataset_dir):
        """
        root保存的就是当前遍历的文件夹的绝对路径;
        dirs保存当前文件夹下的所有 子文件夹 的名称(仅一层,孙子文件夹不包括)
        files保存当前文件夹下的所有 文件 的名称
        """
        for sub_dir in dirs:
            #os.listdir(path)返回path路径下的文件和文件夹列表
            imgs = os.listdir(os.path.join(root, sub_dir))
            #filter(function, itetable):生成一个迭代器从这些可迭代对象元素里面筛选符合函数返回值为真的元素。
            #lambda 变量: 表达式, 输入
            #endswith():判断是否以。。结尾
            imgs = list(filter(lambda x: x.endswith('.jpg'), imgs))

            #对图片进行打乱操作
            random.shuffle(imgs)
            img_count = len(imgs)

            #定义指针,遍历截止位置
            train_point = int(img_count * train_pct)
            valid_point = int(img_count * (train_pct + valid_pct))

            if img_count == 0:
                print("{}目录下,无图片,请检查".format(os.path.join(root, sub_dir)))
                import sys
                sys.exit(0)
            for i in range(img_count):
                if i < train_point:
                    out_dir = os.path.join(train_dir, sub_dir)
                elif i < valid_point:
                    out_dir = os.path.join(valid_dir, sub_dir)
                else:
                    out_dir = os.path.join(test_dir, sub_dir)

                makedir(out_dir)

                #目标路径
                target_path = os.path.join(out_dir, imgs[i])
                #源路径
                src_path = os.path.join(dataset_dir, sub_dir, imgs[i])

                #shutil.copy(src, dst)是为了复制文件内容从src到dst
                shutil.copy(src_path, target_path)

            print('Class:{}, train:{}, valid:{}, test:{}'.format(sub_dir, train_point, valid_point-train_point,
                                                                 img_count-valid_point))
            print("已在 {} 创建划分好的数据\n".format(split_dir))

(3)Dataset和Dataloader的构建

自己创建RMBDataset函数,传入两个参数,训练集/验证集所在文件地址和训练集/验证集的数据预处理

# 构建MyDataset实例
train_data = RMBDataset(data_dir=train_dir, transform=train_transform)
valid_data = RMBDataset(data_dir=valid_dir, transform=valid_transform)

# 构建DataLoder
#训练集shuffle = True表示每个epoch中的样本都是乱序的
train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)

(4)Dataset和Dataloader的操作原理

debug—单步调试(看数据走向):

训练数据时调用train_loader,进入库函数dataloader

执行该函数:是否使用多线程

    def _get_iterator(self) -> '_BaseDataLoaderIter':
        if self.num_workers == 0:
            return _SingleProcessDataLoaderIter(self)
        else:
            return _MultiProcessingDataLoaderIter(self)

以单线程为例,进入SingleProcessDataLoaderIter()函数

class _SingleProcessDataLoaderIter(_BaseDataLoaderIter):
    def __init__(self, loader):
        super(_SingleProcessDataLoaderIter, self).__init__(loader)
        assert self._timeout == 0
        assert self._num_workers == 0

        self._dataset_fetcher = _DatasetKind.create_fetcher(
            self._dataset_kind, self._dataset, self._auto_collation, self._collate_fn, self._drop_last)

    def _next_data(self):
        #获取数据索引
        index = self._next_index()  # may raise StopIteration
        #获取数据
        data = self._dataset_fetcher.fetch(index)  # may raise StopIteration
        if self._pin_memory:
            data = _utils.pin_memory.pin_memory(data)
        return data

进入_next_index()函数看看是如何获取index的

sampler是一个采样器,用来说明每个batchsize该读取哪些数据,从而取出每一个batchsize的图片索引,跳出该函数

    def __iter__(self):
        batch = []
        for idx in self.sampler:
            batch.append(idx)
            if len(batch) == self.batch_size:
                yield batch
                batch = []
        if len(batch) > 0 and not self.drop_last:
            yield batch

将获取的index索引传入数据的获取函数self._dataset_fetcher.fetch(index)

class _MapDatasetFetcher(_BaseDatasetFetcher):
    def __init__(self, dataset, auto_collation, collate_fn, drop_last):
        super(_MapDatasetFetcher, self).__init__(dataset, auto_collation, collate_fn, drop_last)

    def fetch(self, possibly_batched_index):
        if self.auto_collation:
            #根据索引返回data
            data = [self.dataset[idx] for idx in possibly_batched_index]
        else:
            data = self.dataset[possibly_batched_index]
        #将读取的数据整理成一个batch的形式
        return self.collate_fn(data)

进入dataset函数

getitem函数中:

data_info来获取数据的路径和标签

image.open来读取数据 

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.data_info = self.get_img_info(data_dir)  # data_info存储所有图片路径和标签,在DataLoader中通过index读取样本
        self.transform = transform

    #根据索引返回图片和标签
    def __getitem__(self, index):
        #根据index获取图片和标签
        path_img, label = self.data_info[index]
        img = Image.open(path_img).convert('RGB')     # 0~255

        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

这就是读取数据的全过程啦!

Pytorch基础学习(第二章-Pytorch数据处理)_第8张图片

完整代码:

# -*- coding: utf-8 -*-

import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
import torch.optim as optim
from matplotlib import pyplot as plt

path_lenet = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "model", "lenet.py"))
path_tools = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "tools", "common_tools.py"))
assert os.path.exists(path_lenet), "{}不存在,请将lenet.py文件放到 {}".format(path_lenet, os.path.dirname(path_lenet))
assert os.path.exists(path_tools), "{}不存在,请将common_tools.py文件放到 {}".format(path_tools, os.path.dirname(path_tools))

import sys
hello_pytorch_DIR = os.path.abspath(os.path.dirname(__file__)+os.path.sep+".."+os.path.sep+"..")
sys.path.append(hello_pytorch_DIR)

from model.lenet import LeNet
from tools.my_dataset import RMBDataset
from tools.common_tools import set_seed


set_seed()  # 设置随机种子
rmb_label = {"1": 0, "100": 1}

# 参数设置
MAX_EPOCH = 10
BATCH_SIZE = 16
LR = 0.01
log_interval = 10
val_interval = 1

# ============================ step 1/5 数据 ============================
split_dir = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "data","RMB_data", "rmb_split"))
if not os.path.exists(split_dir):
    raise Exception(r"数据 {} 不存在, 回到lesson-06\1_split_dataset.py生成数据".format(split_dir))
train_dir = os.path.join(split_dir, "train")
valid_dir = os.path.join(split_dir, "valid")

#设置均值和标准差
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
])

#验证集数据预处理
valid_transform = transforms.Compose([  #测试时不需要数据增强
    transforms.Resize((32, 32)),
    transforms.ToTensor(),
    transforms.Normalize(norm_mean, norm_std),
])

# 构建MyDataset实例
train_data = RMBDataset(data_dir=train_dir, transform=train_transform)
valid_data = RMBDataset(data_dir=valid_dir, transform=valid_transform)

# 构建DataLoder
#训练集shuffle = True表示每个epoch中的样本都是乱序的
train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)

# ============================ step 2/5 模型 ============================
#调用网络
net = LeNet(classes=2)
#初始化权重
net.initialize_weights()

# ============================ step 3/5 损失函数 ============================
criterion = nn.CrossEntropyLoss()                                                   # 选择损失函数

# ============================ step 4/5 优化器 ============================
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9)                        # 选择优化器 SGD随机梯度下降
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)     # 设置学习率下降策略  StepLR:等间隔调整学习率

# ============================ step 5/5 训练 ============================
#定义损失曲线列表
train_curve = list()
valid_curve = list()

#迭代epoch
for epoch in range(MAX_EPOCH):

    loss_mean = 0.
    correct = 0.
    total = 0.

    net.train()
    #迭代batch
    for i, data in enumerate(train_loader):   #i = 0,索引从0开始    enumerate(train_loader,1)  索引从1开始

        # forward
        #获取图像和标签
        inputs, labels = data
        outputs = net(inputs)

        # backward
        #梯度归零
        optimizer.zero_grad()
        #调用损失函数
        loss = criterion(outputs, labels)
        #反向传播计算得到每个参数的梯度值
        loss.backward()

        # update weights
        optimizer.step()

        # 统计分类情况
        # "_"表示max输出的value即最大值,predicted表示最大值的索引,“1”表示维度为1。
        #  dim=1时,按行返回最大值所在索引
        #  dim=0时,按列返回最大值所在索引
        # 此处是将最大概率的预测值索引返回
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)     #统计图片总个数
        correct += (predicted == labels).squeeze().sum().numpy()

        # 打印训练信息
        #item()返回loss的值,叠加之后算出总loss。(最后再除以mini-batches的数量,取loss平均值)
        loss_mean += loss.item()
        train_curve.append(loss.item())
        #每十个迭代间隔打印一次训练数据
        if (i+1) % log_interval == 0:
            #除以mini-batches的数量,取loss平均值
            loss_mean = loss_mean / log_interval
            print("Training:Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(
                epoch, MAX_EPOCH, i+1, len(train_loader), loss_mean, correct / total))
            loss_mean = 0.

    scheduler.step()  # 更新学习率

    # validate the model
    #每个epoch都进行一次验证
    if (epoch+1) % val_interval == 0:

        correct_val = 0.
        total_val = 0.
        loss_val = 0.
        net.eval()
        with torch.no_grad():   #不进行计算图构建
            for j, data in enumerate(valid_loader):
                inputs, labels = data
                outputs = net(inputs)
                loss = criterion(outputs, labels)

                _, predicted = torch.max(outputs.data, 1)
                total_val += labels.size(0)
                correct_val += (predicted == labels).squeeze().sum().numpy()

                loss_val += loss.item()

            loss_val_epoch = loss_val / len(valid_loader)
            valid_curve.append(loss_val_epoch)
            print("Valid:\t Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(
                epoch, MAX_EPOCH, j+1, len(valid_loader), loss_val_epoch, correct_val / total_val))


train_x = range(len(train_curve))
train_y = train_curve

train_iters = len(train_loader)
valid_x = np.arange(1, len(valid_curve)+1) * train_iters*val_interval - 1  # 由于valid中记录的是epochloss,需要对记录点进行转换到iterations
valid_y = valid_curve

plt.plot(train_x, train_y, label='Train')
plt.plot(valid_x, valid_y, label='Valid')

plt.legend(loc='upper right')   #图例位置
plt.ylabel('loss value')
plt.xlabel('Iteration')
plt.show()

# ============================ inference ============================

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
test_dir = os.path.join(BASE_DIR, "test_data")

test_data = RMBDataset(data_dir=test_dir, transform=valid_transform)
valid_loader = DataLoader(dataset=test_data, batch_size=1)

for i, data in enumerate(valid_loader):
    # forward
    inputs, labels = data
    outputs = net(inputs)
    _, predicted = torch.max(outputs.data, 1)

    rmb = 1 if predicted.numpy()[0] == 0 else 100
    print("模型获得{}元".format(rmb))

二、数据预处理transforms模块机制

1.transforms运行机制

torchvision:计算机视觉工具包

torchvision.transforms:常用的图像预处理方法

torchvision.datasets:常用数据集的dataset实现,MNIST,CIFAR-10,ImageNet等

torchvision.model:常用的模型预训练,AlexNet,VGG,ResNet,GoogLeNet等

(1)torchvision.transforms:常用的图像预处理方法

数据预处理方法:数据中心化;数据标准化;缩放;裁剪;旋转;填充;噪声添加;灰度变换;线性变换;仿射变换;亮度、饱和度及对比度变换等

compose将一系列transforms方法进行有序组合包装,依次按顺序的对图像进行操作

#训练集数据预处理
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
])

#验证集数据预处理
valid_transform = transforms.Compose([  #测试时不需要数据增强
    transforms.Resize((32, 32)),
    transforms.ToTensor(),
    transforms.Normalize(norm_mean, norm_std),
])

(2)transforms运行原理

在mydata.py文件中的getitem函数,判断是否进行transforms

    #根据索引返回图片和标签
    def __getitem__(self, index):
        #根据index获取图片和标签
        path_img, label = self.data_info[index]
        img = Image.open(path_img).convert('RGB')     # 0~255

        if self.transform is not None:
            img = self.transform(img)   # 在这里做transform,转为tensor等等

        return img, label

进入transforms,跳转到transforms的call函数

依次有序的从compose中调用数据处理方法

    def __call__(self, img):
        for t in self.transforms:
            img = t(img)
        return img

transforms运行流程图:

Pytorch基础学习(第二章-Pytorch数据处理)_第9张图片

2.数据标准化——transformes.normalize

功能:逐channel的对图像进行标准化

output = (input - mean)/ std

mean:各通道的均值

std:各通道的标准差

inplace:是否原地操作

此处直接调用的torch中的normalize函数

class Normalize(torch.nn.Module):
 

    def __init__(self, mean, std, inplace=False):
        super().__init__()
        self.mean = mean
        self.std = std
        self.inplace = inplace

    def forward(self, tensor: Tensor) -> Tensor:
        """
        Args:
            tensor (Tensor): Tensor image to be normalized.

        Returns:
            Tensor: Normalized Tensor image.
        """
        return F.normalize(tensor, self.mean, self.std, self.inplace)

    def __repr__(self):
        return self.__class__.__name__ + '(mean={0}, std={1})'.format(self.mean, self.std)

进入torch的nomalize函数

def normalize(tensor: Tensor, mean: List[float], std: List[float], inplace: bool = False) -> Tensor:
    #判断是否是tensor
    if not isinstance(tensor, torch.Tensor):
        raise TypeError('Input tensor should be a torch tensor. Got {}.'.format(type(tensor)))

    if tensor.ndim < 3:
        raise ValueError('Expected tensor to be a tensor image of size (..., C, H, W). Got tensor.size() = '
                         '{}.'.format(tensor.size()))
    #是否进行原位操作,False则对tensor进行clone
    if not inplace:
        tensor = tensor.clone()

    dtype = tensor.dtype
    #将均值和标准差由列表格式转换为tensor格式
    mean = torch.as_tensor(mean, dtype=dtype, device=tensor.device)
    std = torch.as_tensor(std, dtype=dtype, device=tensor.device)
    if (std == 0).any():
        raise ValueError('std evaluated to zero after conversion to {}, leading to division by zero.'.format(dtype))
    if mean.ndim == 1:
        mean = mean.view(-1, 1, 1)
    if std.ndim == 1:
        std = std.view(-1, 1, 1)
    tensor.sub_(mean).div_(std)
    return tensor

三、transforms图像增强

1.什么是数据增强

数据增强(Data AUGmention)又称为数据增广,数据扩增,它是对训练集进行变换,使训练集更丰富,从而让模型更具泛化能力。

2.transforms——裁剪

(1)transforms.CentorCrop

功能:从图像中心裁剪蹄片

size:所需裁剪图片尺寸

(2)transforms.RandomCrop

  • 功能:从图片中随机裁剪出尺寸为size的图片
  • size:所需裁剪图片尺寸
  • padding:设置填充大小
    •  当为a时,上下左右均填充a个像素
    • 当为(a,b)时,上下填充b个像素,左右填充a个像素
    • 当为(a,b,c,d)时,左,上,右,下分别填充a,b,c,d
  • pad_if_need:若图像小于设定size,则填充(当size大于图像时,该参数必须设为True,否则报错)
  • padding_mode:填充模式,有四种模式
    • constant:像素值由fill设定
    • edge:像素值由图像边缘像素决定
    • reflect:镜像填充,最后一个像素不镜像    [1,2,3,4]—>[3,2,|1,2,3,4,|3,2] (对两边填充两个像素,1和4不镜像)
    • symmetric:镜像填充,最后一个像素镜像    [1,2,3,4]—>[2,1,|1,2,3,4,|4,3](对两边填充两个像素,1和4镜像)
  • fill:constant时,设置填充的像素值      eg:fill = (255,0,0)

Pytorch基础学习(第二章-Pytorch数据处理)_第10张图片

(3)RandomResizedCrop

功能:随机大小、长宽比裁剪图片

  • size:所需裁剪图片尺寸
  • scale:随机裁剪面积比例,默认(0.08,1)    (在0.08-1之间选择一个比例进行裁剪)
  • ratio:随机长宽比,默认(3/4,4/3)
  • interpolation:插值方法        (由于裁剪之后的图片可能会小于size,故进行插值操作)
    • PIL.Image.NEAREST        
    • PIL.Image.BILINEAR
    • PIL.Image.BICUBIC 

Pytorch基础学习(第二章-Pytorch数据处理)_第11张图片

(4)FiveCrop

(5)TenCrop

功能:在图像的上下左右以及中心裁剪出尺寸为size的5张图片,TenCrop对这5张图片进行水平或者垂直镜像获得10张图片

size:所需裁剪图片尺寸

vertical_flip:是否垂直翻转

Pytorch基础学习(第二章-Pytorch数据处理)_第12张图片

3.transforms——翻转和旋转

(1)RandomHorizontalFlip

(2)RandomVerticalFlip

功能:依概率水平(左右)或垂直(上下)翻转图片

  • p:翻转概率

 (3)RandomRotation

功能:随机旋转图片

  • degrees:旋转角度
    • 当为a时,在(-a,a)之间选择旋转角度
    • 当为(a,b)时,在(a,b)之间选择旋转角度
  • resample:重采样方法,一般默认就好
  • expand:是否扩大图片,以保持原图信息
  • center:旋转点设置,默认中心旋转,其他旋转图片损失会变大

注意:expand为True时,后面应对图片进行缩放操作,以保持图片大小统一

完整代码:

# -*- coding: utf-8 -*-

import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
import numpy as np
import torch
import random
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
from PIL import Image
from matplotlib import pyplot as plt
path_lenet = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "model", "lenet.py"))
path_tools = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "tools", "common_tools.py"))
assert os.path.exists(path_lenet), "{}不存在,请将lenet.py文件放到 {}".format(path_lenet, os.path.dirname(path_lenet))
assert os.path.exists(path_tools), "{}不存在,请将common_tools.py文件放到 {}".format(path_tools, os.path.dirname(path_tools))

import sys
hello_pytorch_DIR = os.path.abspath(os.path.dirname(__file__)+os.path.sep+".."+os.path.sep+"..")
sys.path.append(hello_pytorch_DIR)

from tools.my_dataset import RMBDataset
from tools.common_tools import set_seed, transform_invert

set_seed(1)  # 设置随机种子

# 参数设置
MAX_EPOCH = 10
BATCH_SIZE = 1
LR = 0.01
log_interval = 10
val_interval = 1
rmb_label = {"1": 0, "100": 1}

# ============================ step 1/5 数据 ============================
split_dir = os.path.abspath(os.path.join("..", "..", "data", "RMB_data", "rmb_split"))
if not os.path.exists(split_dir):
    raise Exception(r"数据 {} 不存在, 回到lesson-06\1_split_dataset.py生成数据".format(split_dir))
train_dir = os.path.join(split_dir, "train")
valid_dir = os.path.join(split_dir, "valid")

norm_mean = [0.485, 0.456, 0.406]
norm_std = [0.229, 0.224, 0.225]


train_transform = transforms.Compose([
    transforms.Resize((224, 224)),  #统一图片尺寸

    # 1 CenterCrop
    # transforms.CenterCrop(196),     # 512

    # 2 RandomCrop
    # transforms.RandomCrop(224, padding=16),
    # transforms.RandomCrop(224, padding=(16, 64)),
    # transforms.RandomCrop(224, padding=16, fill=(255, 0, 0)),
    # transforms.RandomCrop(512, pad_if_needed=True),   # pad_if_needed=True
    # transforms.RandomCrop(224, padding=64, padding_mode='edge'),
    # transforms.RandomCrop(224, padding=64, padding_mode='reflect'),
    # transforms.RandomCrop(1024, padding=1024, padding_mode='symmetric'),

    # 3 RandomResizedCrop
    # transforms.RandomResizedCrop(size=224, scale=(0.5, 0.5)),

    # 4 FiveCrop
    # transforms.FiveCrop(112),
    # transforms.Lambda(lambda crops: torch.stack([(transforms.ToTensor()(crop)) for crop in crops])),

    # 5 TenCrop
    # transforms.TenCrop(112, vertical_flip=False),
    # transforms.Lambda(lambda crops: torch.stack([(transforms.ToTensor()(crop)) for crop in crops])),

    # 1 Horizontal Flip
    # transforms.RandomHorizontalFlip(p=1),

    # 2 Vertical Flip
    # transforms.RandomVerticalFlip(p=0.5),

    # 3 RandomRotation
    # transforms.RandomRotation(90),
    # transforms.RandomRotation((90), expand=True),
    # transforms.RandomRotation(30, center=(0, 0)),
    # transforms.RandomRotation(30, center=(0, 0), expand=True),   # expand only for center rotation

    transforms.ToTensor(),
    transforms.Normalize(norm_mean, norm_std),
])

valid_transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(norm_mean, norm_std)
])

# 构建MyDataset实例
train_data = RMBDataset(data_dir=train_dir, transform=train_transform)
valid_data = RMBDataset(data_dir=valid_dir, transform=valid_transform)

# 构建DataLoder
train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)


# ============================ step 5/5 训练 ============================
for epoch in range(MAX_EPOCH):
    for i, data in enumerate(train_loader):

        inputs, labels = data   # B C H W

        img_tensor = inputs[0, ...]     # C H W
        #invert函数对transforms进行逆操作,可以将浮点数据转为img,便于观察
        img = transform_invert(img_tensor, train_transform)
        plt.imshow(img)
        plt.show()
        plt.pause(0.5)
        plt.close()

        # FiveCrop 和 TenCrop的可视化操作,因为输出为5维
        # bs, ncrops, c, h, w = inputs.shape
        # for n in range(ncrops):
        #     img_tensor = inputs[0, n, ...]  # C H W
        #     img = transform_invert(img_tensor, train_transform)
        #     plt.imshow(img)
        #     plt.show()
        #     plt.pause(1)






 4.transforms——图像变换

(1)pad

功能:对图片边缘进行填充

  • padding:设置填充大小
    •  当为a时,上下左右均填充a个像素
    • 当为(a,b)时,上下填充b个像素,左右填充a个像素
    • 当为(a,b,c,d)时,左,上,右,下分别填充a,b,c,d
  • padding_mode:填充模式,有四种模式,constant、edge、reflect和symmetric(具体请见三.2.(2)节)
  • fill:constant时, 设置填充的像素值,(R,G,B)or(Gray)

padding_mode优先级高于fill

 (2)ColorJitter

功能:调整亮度、对比度、饱和度和色相

  • brightness:亮度调整因子
    • 当为a时,从[max(0,1-a),1+a]中随机选择
    • 当为(a,b)时,从[a,b]中选择
  • contrast:对比度参数,同brightness
  • saturation:饱和度参数,同brightness
  • hue:色相参数
    • 当为a时,从[-a,a]中选择参数,注:0<=a<=0.5
    • 当为(a,b)时,从[a,b]中选择参数,注:-0.5<=a<=b<=0.5

 (3)Greyscale

(4)RandomGreyscale

功能:依概率将图片转换为灰度图

  • num_output_channels:输出通道数,只能设1或3
  • p:概率值,图像被转换为灰度图的概率,当p=1,则等价于Greyscale

 (5)RandomAffine

功能:对图像进行仿射变换,仿射变换是二维的线性变换,由五种基本原子变换构成,分别是旋转,平移,缩放,错切和翻转

  • 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),则设置x轴角度,b设置y的角度
    • 若为(a,b,c,d),则a,b设置x轴角度,c,d设置y轴角度
  • resample:重采样方式,有NEAREST、BILINEAR、BICUBIC

Pytorch基础学习(第二章-Pytorch数据处理)_第13张图片

错切效果:

Pytorch基础学习(第二章-Pytorch数据处理)_第14张图片

 (6)RandomErasing

功能:对图像进行随机遮挡

  • p:概率值,执行该操作的概率
  • scale:遮挡区域的面积
  • ratio:遮挡区域长宽比
  • value:设置遮挡区域的像素值,(R,G,B)or(Grey)

Pytorch基础学习(第二章-Pytorch数据处理)_第15张图片

注意事项:执行Erasing是对tensor进行操作的,故需要把输入转为张量的类型 ,transforms.ToTensor()

遮挡效果如下:Pytorch基础学习(第二章-Pytorch数据处理)_第16张图片

 (7)transforms.lambda

功能:用户自定义lambda方法

  • lambd:lambda匿名函数
    • lambda [arg1[,arg2,...,argn]] : expression 

TenCrop输出的结果是tuple类型,故需要对输出结果转换为tensor,就可以用到lambda函数

stack将返回的张量进行拼接,输出为4D的张量,stack会创建一个维度将张量进行拼接

5.transforms——transforms方法选择操作

选择操作方法

(1)transforms.RandomChoice

功能:从一系列transforms方法中随机挑选一个

 (2)transforms.RandomApply

功能:依据概率执行一组transforms操作

(3)transforms.RandomOrder

功能:对一组transforms操作打乱顺序

完整代码:

# -*- coding: utf-8 -*-

import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
from matplotlib import pyplot as plt
from torch.utils.data import DataLoader
import torchvision.transforms as transforms

path_lenet = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "model", "lenet.py"))
path_tools = os.path.abspath(os.path.join(BASE_DIR, "..", "..", "tools", "common_tools.py"))
assert os.path.exists(path_lenet), "{}不存在,请将lenet.py文件放到 {}".format(path_lenet, os.path.dirname(path_lenet))
assert os.path.exists(path_tools), "{}不存在,请将common_tools.py文件放到 {}".format(path_tools, os.path.dirname(path_tools))

import sys
hello_pytorch_DIR = os.path.abspath(os.path.dirname(__file__)+os.path.sep+".."+os.path.sep+"..")
sys.path.append(hello_pytorch_DIR)

from tools.my_dataset import RMBDataset
from tools.common_tools import set_seed, transform_invert

set_seed(1)  # 设置随机种子

# 参数设置
MAX_EPOCH = 10
BATCH_SIZE = 1
LR = 0.01
log_interval = 10
val_interval = 1
rmb_label = {"1": 0, "100": 1}


# ============================ step 1/5 数据 ============================
split_dir = os.path.abspath(os.path.join("..", "..", "data", "rmb_split"))
if not os.path.exists(split_dir):
    raise Exception(r"数据 {} 不存在, 回到lesson-06\1_split_dataset.py生成数据".format(split_dir))
train_dir = os.path.join(split_dir, "train")
valid_dir = os.path.join(split_dir, "valid")

norm_mean = [0.485, 0.456, 0.406]
norm_std = [0.229, 0.224, 0.225]


train_transform = transforms.Compose([
    transforms.Resize((224, 224)),

    # 1 Pad
    # transforms.Pad(padding=32, fill=(255, 0, 0), padding_mode='constant'),
    # transforms.Pad(padding=(8, 64), fill=(255, 0, 0), padding_mode='constant'),
    # transforms.Pad(padding=(8, 16, 32, 64), fill=(255, 0, 0), padding_mode='constant'),
    # transforms.Pad(padding=(8, 16, 32, 64), fill=(255, 0, 0), padding_mode='symmetric'),

    # 2 ColorJitter
    # transforms.ColorJitter(brightness=0.5),
    # transforms.ColorJitter(contrast=0.5),
    # transforms.ColorJitter(saturation=0.5),
    # transforms.ColorJitter(hue=0.3),

    # 3 Grayscale
    # transforms.Grayscale(num_output_channels=3),

    # 4 Affine
    # transforms.RandomAffine(degrees=30),
    # transforms.RandomAffine(degrees=0, translate=(0.2, 0.2), fillcolor=(255, 0, 0)),
    # transforms.RandomAffine(degrees=0, scale=(0.7, 0.7)),
    # transforms.RandomAffine(degrees=0, shear=(0, 0, 0, 45)),
    # transforms.RandomAffine(degrees=0, shear=90, fillcolor=(255, 0, 0)),

    # 5 Erasing
    # transforms.ToTensor(),
    # transforms.RandomErasing(p=1, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=(254/255, 0, 0)),
    # transforms.RandomErasing(p=1, scale=(0.02, 0.33), ratio=(0.3, 3.3), value='1234'),

    # 1 RandomChoice
    # transforms.RandomChoice([transforms.RandomVerticalFlip(p=1), transforms.RandomHorizontalFlip(p=1)]),

    # 2 RandomApply
    # transforms.RandomApply([transforms.RandomAffine(degrees=0, shear=45, fillcolor=(255, 0, 0)),
    #                         transforms.Grayscale(num_output_channels=3)], p=0.5),
    # 3 RandomOrder
    # transforms.RandomOrder([transforms.RandomRotation(15),
    #                         transforms.Pad(padding=32),
    #                         transforms.RandomAffine(degrees=0, translate=(0.01, 0.1), scale=(0.9, 1.1))]),

    transforms.ToTensor(),
    transforms.Normalize(norm_mean, norm_std),
])

valid_transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(norm_mean, norm_std)
])

# 构建MyDataset实例
train_data = RMBDataset(data_dir=train_dir, transform=train_transform)
valid_data = RMBDataset(data_dir=valid_dir, transform=valid_transform)

# 构建DataLoder
train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)


# ============================ step 5/5 训练 ============================
for epoch in range(MAX_EPOCH):
    for i, data in enumerate(train_loader):

        inputs, labels = data   # B C H W

        img_tensor = inputs[0, ...]     # C H W
        img = transform_invert(img_tensor, train_transform)
        plt.imshow(img)
        plt.show()
        plt.pause(0.5)
        plt.close()

6.自定义transforms方法

自定义transforms要素:

  • 仅接收一个参数,返回一个参数
  • 注意上下游的输入与输出

Pytorch基础学习(第二章-Pytorch数据处理)_第17张图片

如果概率取值等多参数时, 通过类实现多参数传入:

call函数中具体实现功能 

Pytorch基础学习(第二章-Pytorch数据处理)_第18张图片

例:以增加椒盐噪声为例自定义自己的transforms方法

椒盐噪声又称为脉冲噪声,是一种随机出现的白点或者黑点,白点称为盐噪声,黑色称为椒噪声

信噪比(signal-Noise Rate,SNR)是衡量噪声的比例,图像中为图像像素的占比

Pytorch基础学习(第二章-Pytorch数据处理)_第19张图片

Pytorch基础学习(第二章-Pytorch数据处理)_第20张图片

Pytorch基础学习(第二章-Pytorch数据处理)_第21张图片

 示例代码如下:

class AddPepperNoise(object):
    """增加椒盐噪声
    Args:
        snr (float): Signal Noise Rate
        p (float): 概率值,依概率执行该操作
    """

    def __init__(self, snr, p=0.9):
        assert isinstance(snr, float) and (isinstance(p, float))    # 2020 07 26 or --> and
        self.snr = snr
        self.p = p

    def __call__(self, img):
        """
        Args:
            img (PIL Image): PIL Image
        Returns:
            PIL Image: PIL image.
        """
        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)
            #choice创建一个mask,0表示原图,1表示盐噪声,2表示椒噪声。p按比例划分噪声出现的概率
            mask = 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     # 椒噪声
            #将输入转为tensor
            return Image.fromarray(img_.astype('uint8')).convert('RGB')
        else:
            return img

调用自定义函数:

train_transform = transforms.Compose([
    transforms.Resize((224, 224)),
    AddPepperNoise(0.9, p=0.5),
    transforms.ToTensor(),
    transforms.Normalize(norm_mean, norm_std),
])

总结:二十二种transforms操作

Pytorch基础学习(第二章-Pytorch数据处理)_第22张图片

=========================================================================

转载请注明出处

代码、数据联系作者QQ:1727177407

你可能感兴趣的:(Pytorch基础知识学习,pytorch)