背景知识
本篇blog和我的上一篇blog介绍tensorflow加载数据形成对比这两篇blog所用到的数据完全一致,只不过一个是使用tensorflow进行加载数据,一个使用pytorch进行加载数据。
在 深度学习中, 数据的预处理占据了我们在训练过程中的一大部分,如何利用不同的库加载我们的数据显得尤为重要。因此本次blog将结合pytorch==1.3.0中数据加载部分官方文档讲述如何加载我们自己标注的数据和标签。
这里为什么要以pytorch加载数据,是因为最近发现一个比较有趣的事情,在更新pytorch==1.3.0以后,发现在pytorch官方演示中可以直接得到向量对向量的导数,在之前版本只能是离散点(标量)对向量求导,可能是官方优化了变量的fn属性吧,官方介绍了是求解雅可比矩阵的形式。
# 这个代码在pytorch < 1.3.0时,会报错
import torch
x = torch.ones(3,requires_grad=True)
a = torch.tensor([1.,2,3],requires_grad=True)
y = torch.mul(x,a)
y.backward(x)
print(x.grad)
测试环境
- 操作系统:Windows10
- 深度学习框架:pytorch ==1.3.0 or plus
- IDE: Pycharm
自定义数据格式介绍
在 深度学习中, 本人感兴趣的是图像这一块的领域,因此数据将以图像分类为例,进行演示和说明。
数据下载的链接为 演示数据下载
简单看一下数据的存储的方式,数据存储的方式的目录结构为:
这里和上一篇blog的数据形式完全一致
可以看到每张图片的父节点目录就是他们分类的结果,而每个文件夹里面的内容就是分类的图片。在我们准备好数据之后就可以开始我们的数据加载部分的操作了。
自定义数据加载
在pytorch == 1.3.0中需要使用到的数据加载类/函数有:
# 需要继承自pytorch设置的类的Dataset
from torch.utils.data import Dataset
# 设置打乱顺序和batch的DataLoader
from torch.utils.data import DataLoader
# 对图像处理中的预处理类对象设置(这里我们不是用这个,我们自定义的函数预处理函数)
from torchvision import transforms
这里注意一点,在继承Dataset时,一定需要实现两个虚函数
class CustomerDataset(Dataset):
"""Face Landmarks dataset."""
def __init__(self):
pass
def __len__(self):
#return
pass
def __getitem__(self, idx):
# return
pass
有了上面的函数介绍,现在开始介绍操作步骤:
- 得到图像的路径和标签,
- 实现数据读取sample操作
- 打乱得到的数据并设置
batch_size
from __future__ import print_function,absolute_import,unicode_literals,division
import torch
from torch.utils.data import Dataset,DataLoader
from torchvision import transforms,utils
from skimage import io,transform
import pathlib
import numpy as np
# 自定数据集的设置
class CustomerDataset(Dataset):
# 初始化路径,并得到相对应的图片路径 self.all_image_paths 和标签self.all_image_labels
def __init__(self, root_dir,transform=None):
self.root_dir = root_dir
self.transform = transform
data_root = pathlib.Path(self.root_dir)
self.all_image_paths = list(data_root.glob("*/*"))
self.all_image_paths = [str(path) for path in self.all_image_paths]
label_names = sorted(item.name for item in data_root.glob("*/") if item.is_dir())
label_to_index = dict((name, index) for index, name in enumerate(label_names))
self.all_image_labels = [label_to_index[pathlib.Path(path).parent.name] for path in self.all_image_paths]
def __len__(self):
return len(self.all_image_paths)
#读取图片并返回一个字典的sample
def __getitem__(self, idx):
if torch.is_tensor(idx):
idx = idx.tolist()
image = io.imread(self.all_image_paths[idx])
label = self.all_image_labels[idx]
label = np.array([label])
sample = {'image':image,'label':label}
if self.transform:
sample = self.transform(sample)
return sample
data_root = "C:\\Users\wu\\.keras\\datasets\\flower_photos"
dataset = CustomerDataset(root_dir=data_root)
'''
for i in range(len(dataset)):
sample = dataset[i]
print(i,sample['image'].shape,sample['label'].shape)
'''
# 图片放缩
class Rescale(object):
def __init__(self, output_size):
assert isinstance(output_size, (int, tuple))
self.output_size = output_size
def __call__(self, sample):
image, label = sample['image'], sample['label']
h, w = image.shape[:2]
if isinstance(self.output_size, int):
if h > w:
new_h, new_w = self.output_size * h / w, self.output_size
else:
new_h, new_w = self.output_size, self.output_size * w / h
else:
new_h, new_w = self.output_size
new_h, new_w = int(new_h), int(new_w)
img = transform.resize(image, (new_h, new_w))
# h and w are swapped for landmarks because for images,
# x and y axes are axis 1 and 0 respectively
#landmarks = landmarks * [new_w / w, new_h / h]
return {'image': img, 'label': label}
# 图片随机裁剪
class RandomCrop(object):
"""Crop randomly the image in a sample.
Args:
output_size (tuple or int): Desired output size. If int, square crop
is made.
"""
def __init__(self, output_size):
assert isinstance(output_size, (int, tuple))
if isinstance(output_size, int):
self.output_size = (output_size, output_size)
else:
assert len(output_size) == 2
self.output_size = output_size
def __call__(self, sample):
image, label = sample['image'], sample['label']
h, w = image.shape[:2]
new_h, new_w = self.output_size
top = np.random.randint(0, h - new_h)
left = np.random.randint(0, w - new_w)
image = image[top: top + new_h,
left: left + new_w]
#landmarks = landmarks - [left, top]
return {'image': image, 'label': label}
# 将图片转化成pytorch中的tensor
class ToTensor(object):
"""Convert ndarrays in sample to Tensors."""
def __call__(self, sample):
image, label = sample['image'], sample['label']
# swap color axis because
# numpy image: H x W x C
# torch image: C X H X W
#image = image.transpose((2, 0, 1))
return {'image': torch.from_numpy(image),
'label': torch.from_numpy(label)}
dataset = CustomerDataset(root_dir=data_root,transform=
transforms.Compose([Rescale(256),RandomCrop(224),ToTensor()]))
'''
for i in range(len(dataset)):
sample = dataset[i]
print(i,sample['image'].shape,sample['label'].shape)
'''
# 这里注意下windows下num_workers=0才能运行,在linux中无任何影响,大家课自行测试
dataloader = DataLoader(dataset, batch_size=4,
shuffle=True, num_workers=0)
for i_batch, sample_batched in enumerate(dataloader):
print(i_batch,sample_batched['image'].shape,sample_batched['label'].shape)
注意两点
- 使用自定义的类
RandomCrop(object)
,Rescale(object)
和ToTensor(object)
,这些过程需要自己编写,但是pytorch中提供了一些易用的这种函数在torchvision
中
import torch
from torchvision import transforms, datasets
data_transform = transforms.Compose([
transforms.RandomSizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
dataset = CustomerDataset(root_dir=data_root,transform=
data_transform))
- 由于pytorch中函数封装比较完善,不需要用户编写过多的函数,因此可以借助pytorch封装好的函数进行数据导入操作。比如你可能使用到的深度学习框架不是pytorch,你也可以使用pytorch的数据导入操作,只需要把ToTensor改成ToArray类,然后强制转换成普遍使用到的Numpy数据格式。再把Numpy类型的数据转到你所需要输入的深度学习框架的类型
1.https://pytorch.org/tutorials/beginner/data_loading_tutorial.html