pytorch数据处理
在解决深度学习问题的过程中,往往需要花费大量的精力去处理数据,包括图像、文本、语音或其它二进制数据等。数据的处理对训练神经网络来说十分重要,良好的数据处理不仅会加速模型训练,更会提高模型效果。PyTorch提供了几个高效便捷的工具,以便使用者进行数据处理或增强等操作,同时可通过并行化加速数据加载。
数据加载
在PyTorch中,数据加载可通过自定义的数据集对象。数据集对象被抽象为Dataset类,实现自定义的数据集需要继承Dataset,并实现两个Python魔法方法:
getitem:返回一条数据,或一个样本。obj[index]等价于obj.getitem(index)
len:返回样本的数量。len(obj)等价于obj.len()
Kaggle经典挑战赛"Dogs vs. Cat"的数据为例,来详细讲解如何处理数据。"Dogs vs. Cats"是一个分类问题,判断一张图片是狗还是猫,其所有图片都存放在一个文件夹下,根据文件名的前缀判断是狗还是猫。
data/dogcat/
|-- cat.12484.jpg
|-- cat.12485.jpg
|-- cat.12486.jpg
|-- cat.12487.jpg
|-- dog.12496.jpg
|-- dog.12497.jpg
|-- dog.12498.jpg
`-- dog.12499.jpg
import torch as t
from torch.utils import data
import os
from PIL import Image
import numpy as np
class DogCat(data.Dataset):
def __init__(self, root):
imgs = os.listdir(root)
# 所有图片的绝对路径
# 这里不实际加载图片,只是指定路径,当调用__getitem__时才会真正读图片
self.imgs = [os.path.join(root, img) for img in imgs]
def __getitem__(self, index):
img_path = self.imgs[index]
# dog->1, cat->0
label = 1 if 'dog' in img_path.split('/')[-1] else 0
pil_img = Image.open(img_path)
array = np.asarray(pil_img)
data = t.from_numpy(array)
return data, label
def __len__(self):
return len(self.imgs)
调用该程序
dataset = DogCat('./data/dogcat/')
img, label = dataset[0] # 相当于调用dataset.__getitem__(0)
for img, label in dataset:
print(img.size(), img.float().mean(), label)
(javascript:void(0))
该程序如何自定义自己的数据集,并可以依次获取。但这里返回的数据不适合实际使用,因其具有如下两方面问题:
**
- 返回样本的形状不一,因每张图片的大小不一样,这对于需要取batch训练的神经网络来说很不友好
- 返回样本的数值较大,未归一化至[-1, 1]
**
针对上述问题,PyTorch提供了torchvision^1。它是一个视觉工具包,提供了很多视觉图像处理的工具,其中transforms模块提供了对PIL Image对象和Tensor对象的常用操作。
对PIL Image的操作包括:
- Scale:调整图片尺寸,长宽比保持不变
- CenterCrop、RandomCrop、RandomSizedCrop: 裁剪图片
- Pad:填充 ToTensor:将PIL Image对象转成Tensor,会自动将[0, 255]归一化至[0, 1]
对Tensor的操作包括:
**Normalize:**标准化,即减均值,除以标准差
**ToPILImage:**将Tensor转为PIL Image对象
如果要对图片进行多个操作,可通过Compose函数将这些操作拼接起来,类似于nn.Sequential。注意,这些操作定义后是以函数的形式存在,真正使用时需调用它的__call__方法,这点类似于nn.Module。例如要将图片调整为 224×224 ,首先应构建这个操作trans = Resize((224, 224)),然后调用trans(img)。下面我们就用transforms的这些操作来优化上面实现的dataset。
import os
from PIL import Image
import numpy as np
from torchvision import transforms as T
transform = T.Compose([
T.Resize(224), # 缩放图片(Image),保持长宽比不变,最短边为224像素
T.CenterCrop(224), # 从图片中间切出224*224的图片
T.ToTensor(), # 将图片(Image)转成Tensor,归一化至[0, 1]
T.Normalize(mean=[.5, .5, .5], std=[.5, .5, .5]) # 标准化至[-1, 1],规定均值和标准差
])
class DogCat(data.Dataset):
def __init__(self, root, transforms=None):
imgs = os.listdir(root)
self.imgs = [os.path.join(root, img) for img in imgs]
self.transforms=transforms
def __getitem__(self, index):
img_path = self.imgs[index]
label = 0 if 'dog' in img_path.split('/')[-1] else 1
data = Image.open(img_path)
if self.transforms:
data = self.transforms(data)
return data, label
def __len__(self):
return len(self.imgs)
dataset = DogCat('./data/dogcat/', transforms=transform)
img, label = dataset[0]
for img, label in dataset:
print(img.size(), label)
- torchvision.transforms.Compose(transforms)
功能:将多个transform组合起来使用
transforms: 由transform构成的列表
使用例子:
transforms.Compose([
transforms.CenterCrop(224),
transforms.ToTensor(),
])
2
CenterCrop(size)
功能:将给定的PIL.Image进行中心切割,得到给定的size。
size可以是tuple,(target_height, target_width)。
size也可以是一个Integer,在这种情况下,切出来的图片的形状是正方形。
3、RandomCrop(size, padding=0)
功能:切割中心点的位置随机选取。size可以是tuple也可以是Integer。
4、RandomHorizontalFlip
功能:随机水平翻转给定的PIL.Image,概率为0.5。即:一半的概率翻转,一半的概率不翻转。
5、RandomSizedCrop(size, interpolation=2)
功能:先将给定的PIL.Image随机切,然后再resize成给定的size大小。
6、Pad(padding, fill=0)
功能:将给定的PIL.Image的所有边用给定的pad value填充。
对Tensor进行变换
7、Normalize(mean, std)
功能:给定均值:(R,G,B) 方差:(R,G,B),将会把Tensor正则化。即:Normalized_image=(image-mean)/std。
8、ToTensor
功能:把一个取值范围是[0,255]的PIL.Image或者shape为(H,W,C)的numpy.ndarray,转换成形状为[C,H,W],取值范围是[0,1.0]的torch.FloadTensor
9、ToPILImage
功能:将shape为(C,H,W)的Tensor或shape为(H,W,C)的numpy.ndarray转换成PIL.Image,值不变。
10、Lambda(lambd)
功能:使用lambd作为转换器。
11、Scale(size, interpolation=2)
功能:将输入的Image重新改变大小成给定的size,size是最小边的边长。举个例子,如果原图的height>width,那么改变大小后的图片大小是(size*height/width, size)。
- pytorch中tensor和numpy的通道位置不同,numpy的通道在H,W之后,即(H,W,C),用np.shape(object)可以查看;而tensor的通道在H和W之前,即(C,H,W),用np.shape(object)或者object.shape可以查看;
所以,读取图像后得到numpy数组,要变成tensor进行数据处理,需要transpose操作,改变通道的位置;
- 处理针对的是tensor格式,需要通过from_numpy等方法,将输入图像时的numpy格式转换为tensor格式
- 进行Normalize等处理,需要数据类型为float
1.torchvision.transforms.ToTensor()
将PILImage或者numpy的ndarray转化成Tensor
对于PILImage转化的Tensor,其数据类型是torch.FloatTensor
对于ndarray的数据类型没有限制,但转化成的Tensor的数据类型是由ndarray的数据类型决定的。
把一个取值范围是[0,255]的PIL.Image 转换成 Tensor
from PIL import Image
img1 = Image.open('./1.jpg')
t_out = transforms.ToTensor()(img1)
shape为(H,W,C)的numpy.ndarray,转换成形状为[C,H,W],取值范围是[0,1.0]的Tensor
n_out = np.random.rand(100,100,3)
print(n_out.dtype)
t_out = transforms.ToTensor()(n_out)
print(t_out.type())
输出
float64
torch.DoubleTensor
2.torchvision.transforms.ToPILImage()
将Numpy的ndarray或者Tensor转化成PILImage类型【在数据类型上,两者都有明确的要求】
ndarray的数据类型要求dtype=uint8, range[0, 255] and shape H x W x C
Tensor 的shape为 C x H x W 要求是FloadTensor的,不允许DoubleTensor或者其他类型
# to a PIL.Image of range [0, 255]
将ndarray转化成PILImage
#初始化随机数种子
np.random.seed(0)
data = np.random.randint(0, 255, 300)
print(data.dtype)
n_out = data.reshape(10,10,3)
#强制类型转换
n_out = n_out.astype(np.uint8)
print(n_out.dtype)
img2 = transforms.ToPILImage()(n_out)
img2.show()
将Tensor转化成PILImage
t_out = torch.randn(3,10,10)
img1 = transforms.ToPILImage()(t_out)
img1.show()
因为要求是FloatTensor类型,所以最好是不管此时的tensor是不是FloatTensor类型,都加一个强制转换再传进去。
t_out = torch.randn(3,10,10)
img1 = transforms.ToPILImage()(t_out.float())
img1.show()
参考陈云深度学习框架pytorch入门与实践源代码