当顺利导入数据后,我们就可以依据图像的具体情况对图像进行预处理了。与机器学习中较为固定的预处理流程不同,图像的预处理基本完全与数据本身有关。从数据采集的瞬间开始,我们就需要考虑预处理的事项。如果我们的数据是自行从网络爬取或搜索引擎采集,我们可能需要对图像进行去重、删除无效样本等 *** 作,如果数据是自行拍摄、实验提取,那可能也需要根据实验要求进行一些删除、增加的处理。当我们将所有有效数据导入后,我们至少需要确保:
- 1)全部样本的尺寸是一致的(同时,全部样本的通道数是一致的)
- 2)图像最终以Tensor形式被输入卷积网络
- 3)图像被恰当地归一化
其中,前两项是为了卷积神经网络能够顺利地运行起来,第三项是为了让训练过程变得更加流畅快速。在PyTorch中,所有的数据预处理都可以在导入数据的时候,通过transform参数来完成,我们通常在transform参数中填写torchvision.transforms这个模块下的类。在预处理时,我们需要使用的常规类如下所示:
首先是用来调整尺寸的两个类:中心裁剪 transforms.CenterCrop() 以及 transforms.Resize() 。
无论使用怎样的卷积网络,我们都倾向于将图像调整到接近28x28或224x224的尺寸。当原图尺寸与目标尺寸较为接近时,我们可以使用“裁剪”功能。裁剪是会按照我们输入的目标尺寸,将大于目标尺寸的像素点丢弃的功能,因此使用裁剪必然会导致信息损失,过多的信息损失会导致卷积网络的结果变差。当需要检测或识别的对象位于图像的中心时,可以使用中心裁剪。中心裁剪会以图像中心点为参照,按照输入的尺寸从外向内进行裁剪,被裁剪掉的像素会被直接丢弃。如果输入的尺寸大于原始图像尺寸,则在原始图像外侧填充0,再进行中心裁剪。
当图像的尺寸与目标尺寸相差较大,我们不能接受如此多的信息被丢弃的时候,就需要使用尺寸调整的类Resize。Resize是使用像素聚类、像素插补等一定程度上对信息进行提取或选择、并按要求的尺寸重排像素点的功能。一般来说,Resize过后的图片会呈现出与原图较为相似的信息,但图片尺寸会得到缩放。如果原始图像尺寸很大,目标尺寸很小,我们一般会优先使用Resize将图像尺寸缩小到接近目标尺寸的程度,再用裁剪让图片尺寸完全等于目标尺寸。例如,对于600*800的图像,先Resize将尺寸降到256x256,再裁剪至224x224。
import torch
import torchvision
from torchvision import transforms
from torch import nn
transform1 = transforms.Compose([transforms.Resize(256)
,transforms.CenterCrop(224)])
#等价于
transform2 = nn.Sequential(transforms.Resize(256)
,transforms.CenterCrop(224))
transform1
#Compose(
# Resize(size=256, interpolation=bilinear, max_size=None, antialias=None)
# CenterCrop(size=(224, 224))
#)
transform2
#Sequential(
# (0): Resize(size=256, interpolation=bilinear, max_size=None, antialias=None)
# (1): CenterCrop(size=(224, 224))
#)
调整完尺寸之后,我们需要对数据进行归一化,在这里使用的类是 transforms.Normalize() 。从理论上来说,图像数据的归一化不是必须的,但历史的经验告诉我们,归一化能够非常有效地改善整体训练过程速度,并对最终模型的结果造成巨大的影响,因此各大经典架构的论文和PyTorch官方都强烈建议我们进行归一化。这里的归一化与BN等训练过程中存在的归一化有较大的区别,这里的归一化主要是让像素值减去一个数(默认为均值)、再除以另一个数(默认是标准差),以实现对像素值大小的改变,让模型在一个较高的起点上训练,但并不能像BN一样改变数据的分布。
对表格数据而言,归一化是以特征为单位进行的,每个特征会单独减去自己这个特征的均值,再除以这个特征的标准差。对任意图像而言,归一化都是以通道为单位进行的,每个通道上的全部样本的全部像素点会减去通道像素的均值,再除以通道像素的标准差。为了能够对通道上的全部像素进行计算,图像在被归一化之前必须被转化为Tensor。因此在实际中,我们常常将 transforms.Normalize() 常常和transforms.ToTensor() 连用,具体如下:
transform = transforms.Compose([transforms.ToTensor()
,transforms.Normalize(0.5,0.5)])
但许多人没有注意到的是,类 transforms.ToTensor() 已经带有归一化的功能:这个类会按照最大值255,最小值0对图片数据进行归一化,将所有图像的像素值压缩到[0,1]之间。
data_val = torchvision.datasets.LSUN(root="/Users/zhucan/Desktop/lsun-master/data"
,classes=["church_outdoor_val","classroom_val"]
,transform = transforms.ToTensor()
)
data_val
#Dataset LSUN
# Number of datapoints: 600
# Root location: F:\datasets2\lsun-master\data
# Classes: ['church_outdoor_val', 'classroom_val']
# StandardTransform
#Transform: ToTensor()
data_val[0][0]
# tensor([[[0.4627, 0.4627, 0.4667, ..., 0.7137, 0.7098, 0.7098],
# [0.4627, 0.4627, 0.4667, ..., 0.7255, 0.7176, 0.7098],
# [0.4627, 0.4627, 0.4667, ..., 0.7333, 0.7255, 0.7176],
# ...,
# [0.3137, 0.3412, 0.3216, ..., 0.6471, 0.6392, 0.6353],
# [0.1961, 0.2353, 0.2078, ..., 0.6471, 0.6392, 0.6314],
# [0.1843, 0.2235, 0.1765, ..., 0.6510, 0.6392, 0.6314]],
# [[0.5647, 0.5647, 0.5686, ..., 0.7059, 0.7020, 0.7020],
# [0.5647, 0.5647, 0.5686, ..., 0.7176, 0.7098, 0.7020],
# [0.5647, 0.5647, 0.5686, ..., 0.7255, 0.7176, 0.7098],
# ...,
# [0.3176, 0.3451, 0.3216, ..., 0.6510, 0.6431, 0.6392],
# [0.2000, 0.2392, 0.2118, ..., 0.6510, 0.6431, 0.6353],
# [0.1882, 0.2275, 0.1804, ..., 0.6549, 0.6431, 0.6353]],
# [[0.7882, 0.7882, 0.7922, ..., 0.7098, 0.7059, 0.7059],
# [0.7882, 0.7882, 0.7922, ..., 0.7216, 0.7137, 0.7059],
# [0.7882, 0.7882, 0.7922, ..., 0.7294, 0.7216, 0.7137],
# ...,
# [0.2980, 0.3255, 0.3137, ..., 0.6706, 0.6627, 0.6588],
# [0.1765, 0.2196, 0.1922, ..., 0.6706, 0.6627, 0.6549],
# [0.1647, 0.2039, 0.1569, ..., 0.6745, 0.6627, 0.6549]]])
data_val[0][0].max()
#tensor(1.)
data_val[0][0].min() #所有值都在0-1之间
#tensor(0.)
因此类 transforms.Normalize() 往往是在[0,1]区间上执行。唯一的例外可能是表格数据,如果输入transforms.ToTensor() 的数据原本是二维表,那其最大值可能会远远超出255,那经过归一化后数字范围也不会在[0,1]之间。为了避免这种情况的出现,我们可以提前将二维表的数据压缩到[0,255]之间。
在类 transforms.Normalize() 中有两个参数,一个是mean,另一个是std,分别代表需要减去的值和需要除以的值。比较常见的填写方式有以下三种:
#1) 常见且通用的做法,该写法只适用于三通道图像
transforms.Normalize(mean=[0.5, 0.5, 0.5], #代表三个通道上需要减去的值分别是0.5
std=[0.5, 0.5, 0.5]) #代表三个通道上需要除以的值分别是0.5
#在保证数据范围在[0,1]的前提下,使用这个值可以令数据范围拓展到[-1,1]
#Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
#也可写作:
transforms.Normalize(mean = 0.5, std = 0.5)
#这种写法中,Normalize类会根据通道数进行相应的计算,任意通道数的图像都可以使用
#Normalize(mean=0.5, std=0.5)
#注意区分,这种写法只能用于单通道(灰度)图像
transforms.Normalize([0.5],[0.5])
#Normalize(mean=[0.5], std=[0.5])
#2) ImageNet数据集上的均值和方差,可被用于任意实物照片分类
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
#Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
#3) MNIST数据集上的均值和方差,可被用于MNIST系列
transforms.Normalize((0.1307), (0.3081))
#Normalize(mean=0.1307, std=0.3081)
#你也可以根据自己的数据集和自己希望实现的数值范围,来计算放入Normalize的值
#在LSUN数据集上尝试一下
transform = transforms.Compose([transforms.ToTensor()
,transforms.Normalize(mean=[0.485, 0.456, 0.406]
,std=[0.229, 0.224, 0.225])])
transform1 = transforms.Compose([transforms.ToTensor()
,transforms.Normalize(0.5,0.5)])
data_val = torchvision.datasets.LSUN(root="/Users/zhucan/Desktop/lsun-master/data"
,classes=["church_outdoor_val","classroom_val"]
,transform = transform1
)
data_val[1][0].max()
#tensor(1.)
data_val[1][0].min()
#tensor(-0.8824)
对图像而言,必须完成的预处理就只有尺寸调整和归一化而已。接下来我们来看数据增强(data augmentation)。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)