torchvision.transforms
transforms
我们在安装PyTorch
时,还安装了torchvision
,这是一个计算机视觉工具包。有 3 个主要的模块:
torchvision.transforms
: 里面包括常用的图像预处理方法torchvision.datasets
: 里面包括常用数据集如 mnist、CIFAR-10、Image-Net 等torchvision.models
: 里面包括常用的预训练好的模型,如 AlexNet、VGG、ResNet、GoogleNet 等
深度学习模型是由数据驱动的,数据的数量和分布对模型训练的结果起到决定性作用。所以我们需要对数据进行预处理和数据增强。下面是用数据增强,从一张图片经过各种变换生成 64 张图片,增加了数据的多样性,这可以提高模型的泛化能力。
常用的图像预处理方法有:
数据中心化 | 数据标准化 | 缩放 | 裁剪 | 旋转 | 翻转 | 填充 | 噪声 | 添加灰度 | 变换 | 线性变换 | 仿射变换 | 亮度、饱和度以及对比度变换。
在人民币图片二分类实验 (opens new window)中,我们对数据进行了一定的增强。
# 设置训练集的数据增强和转化
train_transform = transforms.Compose([
transforms.Resize((32, 32)),# 缩放
transforms.RandomCrop(32, padding=4), #裁剪
transforms.ToTensor(), # 转为张量,同时归一化
transforms.Normalize(norm_mean, norm_std),# 标准化
])
# 设置验证集的数据增强和转化,不需要 RandomCrop
valid_transform = transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor(),
transforms.Normalize(norm_mean, norm_std),
])
2
3
4
5
6
7
8
9
10
11
12
13
14
当我们需要多个transforms
操作时,需要作为一个list
放在transforms.Compose
中。需要注意的是transforms.ToTensor()
是把图片转换为张量,同时进行归一化操作,把每个通道 0~255 的值归一化为 0~1。在验证集的数据增强中,不再需要transforms.RandomCrop()
操作。然后把这两个transform
操作作为参数传给Dataset
,在Dataset
的__getitem__()
方法中做图像增强。
def __getitem__(self, index):
# 通过 index 读取样本
path_img, label = self.data_info[index]
# 注意这里需要 convert('RGB')
img = Image.open(path_img).convert('RGB') # 0~255
if self.transform is not None:
img = self.transform(img) # 在这里做transform,转为tensor等等
# 返回是样本和标签
return img, label
2
3
4
5
6
7
8
9
其中self.transform(img)
会调用Compose
的__call__()
函数:
def __call__(self, img):
for t in self.transforms:
img = t(img)
return img
2
3
4
可以看到,这里是遍历transforms
中的函数,按顺序应用到 img 中。
transforms.Normalize
torchvision.transforms.Normalize(mean, std, inplace=False)
功能:逐 channel 地对图像进行标准化
output = ( input - mean ) / std
- mean: 各通道的均值
- std: 各通道的标准差
- inplace: 是否原地操作
该方法调用的是F.normalize(tensor, self.mean, self.std, self.inplace)
而``F.normalize()`方法如下:
def normalize(tensor, mean, std, inplace=False):
if not _is_tensor_image(tensor):
raise TypeError('tensor is not a torch image.')
if not inplace:
tensor = tensor.clone()
dtype = tensor.dtype
mean = torch.as_tensor(mean, dtype=dtype, device=tensor.device)
std = torch.as_tensor(std, dtype=dtype, device=tensor.device)
tensor.sub_(mean[:, None, None]).div_(std[:, None, None])
return tensor
2
3
4
5
6
7
8
9
10
11
12
首先判断是否为 tensor,如果不是 tensor 则抛出异常。然后根据inplace
是否为 true 进行 clone,接着把mean 和 std 都转换为tensor (原本是 list),最后减去均值除以方差:tensor.sub_(mean[:, None, None]).div_(std[:, None, None])
对数据进行均值为 0,标准差为 1 的标准化,可以加快模型的收敛。
在逻辑回归的实验 (opens new window)中,我们的数据生成代码如下:
sample_nums = 100
mean_value = 1.7
bias = 1
n_data = torch.ones(sample_nums, 2)
# 使用正态分布随机生成样本,均值为张量,方差为标量
x0 = torch.normal(mean_value * n_data, 1) + bias # 类别0 数据 shape=(100, 2)
# 生成对应标签
y0 = torch.zeros(sample_nums) # 类别0 标签 shape=(100, 1)
# 使用正态分布随机生成样本,均值为张量,方差为标量
x1 = torch.normal(-mean_value * n_data, 1) + bias # 类别1 数据 shape=(100, 2)
# 生成对应标签
y1 = torch.ones(sample_nums) # 类别1 标签 shape=(100, 1)
train_x = torch.cat((x0, x1), 0)
train_y = torch.cat((y0, y1), 0)
2
3
4
5
6
7
8
9
10
11
12
13
14
生成的数据均值是mean_value+bias=1.7+1=2.7
,比较靠近 0 均值。模型在 380 次迭代时,准确率就超过了 99.5%。
如果我们把 bias 修改为 5。那么数据的均值变成了 6.7,偏离 0 均值较远,这时模型训练需要更多次才能收敛 (准确率达到 99.5%)。
二十二种 transforms 图片数据预处理方法 (opens new window)
transforms.Compose
transforms.Compose类看作一种容器,它能够同时对多种数据变换进行组合。传入的参数是一个列表,列表中的元素就是对载入的数据进行的各种变换操作。
transforms.Normalize(mean, std)
这里使用的是标准正态分布变换,这种方法需要使用原始数据的均值(Mean)和标准差(Standard Deviation)来进行数据的标准化,在经过标准化变换之后,数据全部符合均值为0、标准差为1的标准正态分布。计算公式如下:
一般来说,mean和std是实现从原始数据计算出来的,对于计算机视觉,更常用的方法是从样本中抽样算出来的或者是事先从相似的样本预估一个标准差和均值。如下代码,对三通道的图片进行标准化:
# 标准化是把图片3个通道中的数据整理到规范区间 x = (x - mean(x))/stddev(x)# [0.485, 0.456, 0.406]这一组平均值是从imagenet训练集中抽样算出来的normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
transforms.Resize(size)
对载入的图片数据按照我们的需要进行缩放,传递给这个类的size可以是一个整型数据,也可以是一个类似于 (h ,w) 的序列。如果输入是个(h,w)的序列,h代表高度,w代表宽度,h和w都是int,则直接将输入图像resize到这个(h,w)尺寸,相当于force。如果使用的是一个整型数据,则将图像的短边resize到这个int数,长边则根据对应比例调整,图像的长宽比不变。
transforms.Scale(size)
对载入的图片数据我们的需要进行缩放,用法和torchvision.transforms.Resize类似。传入的size只能是一个整型数据,size是指缩放后图片最小边的边长。举个例子,如果原图的height>width,那么改变大小后的图片大小是(size*height/width, size)。
transforms.CenterCrop(size)
以输入图的中心点为参考点,按我们需要的大小进行裁剪。传递给这个类的参数可以是一个整型数据,也可以是一个类似于(h,w)的序列。如果输入的是一个整型数据,那么裁剪的长和宽都是这个数值。
transforms.RandomCrop(size)
用于对载入的图片按我们需要的大小进行随机裁剪。传递给这个类的参数可以是一个整型数据,也可以是一个类似于(h,w)的序列。如果输入的是一个整型数据,那么裁剪的长和宽都是这个数值。
transforms.RandomResizedCrop(size,scale)
先将给定图像随机裁剪为不同的大小和宽高比,然后缩放所裁剪得到的图像为size的大小。即先随机采集,然后对裁剪得到的图像安装要求缩放,默认scale=(0.08, 1.0)。scale是一个面积采样的范围,假如是一个100100的图片,scale = (0.5,1.0),采样面积最小是0.5100100=5000,最大面积就是原图大小100100=10000。先按照scale将给定图像裁剪,然后再按照给定的输出大小进行缩放。
transforms.ToTensor
用于对载入的图片数据进行类型转换,将之前构成PIL图片的数据转换成Tensor数据类型的变量,让PyTorch能够对其进行计算和处理。
transforms.ToPILImage
用于将Tensor变量的数据转换成PIL图片数据,主要是为了方便图片内容的显示。
Reference
- https://pytorch.org/docs/stable/index.html
- https://www.w3cschool.cn/pytorch/pytorch-5ubt3bby.html
- https://zhuanlan.zhihu.com/p/103630393
- torchvision库简介 (opens new window)
- PyTorch之torchvision.transforms详解 (opens new window)
- https://pytorch-cn.readthedocs.io/zh/latest/torchvision/torchvision-transform/