Pytorch数据加载和处理教程

官方教程:https://pytorch.org/tutorials/beginner/data_loading_tutorial.html

解决任何机器学习问题都需要花费大量精力来准备数据。

PyTorch提供了许多工具来简化数据加载过程,并使代码更具可读性。

在本教程中,我们将看到如何从有价值的数据集中加载和预处理/增强数据。

要运行本教程,请确保已安装以下软件包:

  • scikit-image:用于图像io和变换  scikit-image   0.14.0以及torch 1.1.0
  • pandas:为了更轻松地解析csv

    以下是bug的解决,跳过:准备工作


先检查一下有没有包,在尝试import的过程中,出现了下面的错误:

  • >>> from skimage import io, transform
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "D:\Anaconda3\lib\site-packages\skimage\io\__init__.py", line 11, in <mod
    ule>
        from ._io import *
      File "D:\Anaconda3\lib\site-packages\skimage\io\_io.py", line 5, in <module>
        from ..color import rgb2gray
      File "D:\Anaconda3\lib\site-packages\skimage\color\__init__.py", line 1, in <m
    odule>
        from .colorconv import (convert_colorspace,
      File "D:\Anaconda3\lib\site-packages\skimage\color\colorconv.py", line 60, in
    <module>
        from ..util import dtype, dtype_limits
      File "D:\Anaconda3\lib\site-packages\skimage\util\__init__.py", line 8, in <mo
    dule>
        from .arraycrop import crop
      File "D:\Anaconda3\lib\site-packages\skimage\util\arraycrop.py", line 8, in <m
    odule>
        from numpy.lib.arraypad import _validate_lengths
    ImportError: cannot import name '_validate_lengths' from 'numpy.lib.arraypad' (D
    :\Anaconda3\lib\site-packages\numpy\lib\arraypad.py)

    解决方案:https://blog.csdn.net/qq_22764813/article/details/86534227

对numpy降级:pip install numpy==1.15.0

正常import,然而torch这边又不行了,可能是降级太狠了,查看一下低于1.16.0(1.16.0,skimage不可以)的版本

那我该1.15.4试试:torch不可以!

尝试升级skimage,选择版本0.14.3,numpy为1.17.0,torch1.1.0通过,不过torchvision又发生了一些问题。

(base) C:\Users\PC>pip install torchvision==0.3.0
Requirement already satisfied: torchvision==0.3.0 in d:\anaconda3\lib\site-packages (0.3.0)
Requirement already satisfied: torch>=1.1.0 in d:\anaconda3\lib\site-packages (from torchvision==0.3.0) (1.1.0)
Requirement already satisfied: pillow>=4.1.1 in d:\anaconda3\lib\site-packages (from torchvision==0.3.0) (5.4.1)
Requirement already satisfied: six in d:\anaconda3\lib\site-packages (from torchvision==0.3.0) (1.12.0)
Requirement already satisfied: numpy in d:\anaconda3\lib\site-packages (from torchvision==0.3.0) (1.17.0)

requirement都满足了我也很疑惑。。要不torchvision降一下版本

(base) C:\Users\PC>pip install --no-deps torchvision==0.2.2

搞定!终于可以愉快地运行了。


准备工作

from __future__ import print_function, division
import os
import torch
import pandas as pd
from skimage import io, transform
import numpy as np
import matplotlib.pyplot as plt
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, utils

# Ignore warnings
import warnings
warnings.filterwarnings("ignore")

plt.ion()   # interactive mode

导入包,我们使用的面部姿势数据集,每个面孔都标注了68个不同的界标点。

从官方教程下载数据集, 图像位于名为“ data / faces /”的目录中(这里的路径是可以更改的,不过在后续的代码中要注意匹配)。该数据集来源于imagenet,选取其中一些被标记为“面部”的图像。

数据集带有一个带注释的face_landmarks.csv文件。

让我们快速阅读CSV并获取(N,2)数组中的注释,其中N是界标的数量。

landmarks_frame = pd.read_csv('data/faces/face_landmarks.csv')# 匹配路径!

n = 65
img_name = landmarks_frame.iloc[n, 0]
landmarks = landmarks_frame.iloc[n, 1:].as_matrix()
landmarks = landmarks.astype('float').reshape(-1, 2)

print('Image name: {}'.format(img_name))
print('Landmarks shape: {}'.format(landmarks.shape))
print('First 4 Landmarks: {}'.format(landmarks[:4]))

(base) C:\Users>cd /d "d:\demo" 更改一下当前的工作路径

(base) d:\demo>python dataload.py 执行demo
Image name: person-7.jpg
Landmarks shape: (68, 2)
First 4 Landmarks: [[32. 65.]
 [33. 76.]
 [34. 86.]
 [34. 97.]]

编写一个简单的辅助函数来显示图像及其地标,并使用它来显示示例。

def show_landmarks(image, landmarks):
    """Show image with landmarks"""
    plt.imshow(image)
    plt.scatter(landmarks[:, 0], landmarks[:, 1], s=10, marker='.', c='r')
    plt.pause(0.001)  # pause a bit so that plots are updated

plt.figure()
show_landmarks(io.imread(os.path.join('data/faces/', img_name)),
               landmarks)
plt.show()

为了方便观察,我增加了

import time

time.sleep(5)

数据集类Dataset

torch.utils.data.Dataset是表示数据集的抽象类。

自定义数据集应继承Dataset并覆盖以下方法:

  • __len__               以便len(dataset)返回数据集的大小。
  • __getitem__  支持索引,以便dataset[i]可以用来获取第i个样本

为面部轮廓数据集创建一个数据集类:

__init__读取csv,将在 __getitem__读取图像。(所有图像不会立即存储在内存中,而是根据需要读取,因此可以提高内存效率。)

数据集样本将是dict 。

{'image': image, 'landmarks': landmarks}

数据集将使用可选参数transform,以便可以对样本进行任何必需的处理。我们将在下一节中看到transform的用处。

class FaceLandmarksDataset(Dataset):
    """Face Landmarks dataset."""

    def __init__(self, csv_file, root_dir, transform=None):
        """
        Args:
            csv_file (string): Path to the csv file with annotations.
            root_dir (string): Directory with all the images.
            transform (callable, optional): Optional transform to be applied
                on a sample.
        """
        self.landmarks_frame = pd.read_csv(csv_file)
        self.root_dir = root_dir
        self.transform = transform

    def __len__(self):
        return len(self.landmarks_frame)

    def __getitem__(self, idx):
        if torch.is_tensor(idx):
            idx = idx.tolist()

        img_name = os.path.join(self.root_dir,
                                self.landmarks_frame.iloc[idx, 0])
        image = io.imread(img_name)
        landmarks = self.landmarks_frame.iloc[idx, 1:]
        landmarks = np.array([landmarks])
        landmarks = landmarks.astype('float').reshape(-1, 2)
        sample = {'image': image, 'landmarks': landmarks}

        if self.transform:
            sample = self.transform(sample)

        return sample

实例化该类并遍历数据样本。我们将打印前4个样本的大小并显示其地标(landmark)。

face_dataset = FaceLandmarksDataset(csv_file='data/faces/face_landmarks.csv',
                                    root_dir='data/faces/')

fig = plt.figure()

for i in range(len(face_dataset)):
    sample = face_dataset[i]

    print(i, sample['image'].shape, sample['landmarks'].shape)

    ax = plt.subplot(1, 4, i + 1)  # 4是人为规定显示的数量
    plt.tight_layout()
    ax.set_title('Sample #{}'.format(i))
    ax.axis('off')
    show_landmarks(**sample)

    if i == 3:  # i的判断值为4-1,坐标从0开始
        plt.show()
        break

输出:

0 (324, 215, 3) (68, 2)
1 (500, 333, 3) (68, 2)
2 (250, 258, 3) (68, 2)
3 (434, 290, 3) (68, 2)

转换Transforms

数据集存在样本的大小不同的问题。

大多数神经网络期望图像的大小固定。因此,需要编写一些前置代码。让我们创建三个转换:

  • Rescale:缩放图像
  • RandomCrop:从图像中随机裁剪。这是数据扩充。
  • ToTensor:将numpy图片转换为torch图片(我们需要交换轴)。

我们将编写为可调用的类,而不是简单的函数,这样就不必每次调用转换时都传递其参数。为此,我们只需要实现__call__方法,如果需要的话,可以实现__init__方法。然后我们可以使用这样的转换:

tsfm = Transform(params)
transformed_sample = tsfm(sample)

以下说明如何将这些变换同时应用于图像和地标

class Rescale(object):
    """Rescale the image in a sample to a given size.

    Args:
        output_size (tuple or int): Desired output size. If tuple, output is
            matched to output_size. If int, smaller of image edges is matched
            to output_size keeping aspect ratio the same.
    """

    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        self.output_size = output_size

    def __call__(self, sample):
        image, landmarks = sample['image'], sample['landmarks']

        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, 'landmarks': landmarks}


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, landmarks = sample['image'], sample['landmarks']

        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, 'landmarks': landmarks}


class ToTensor(object):
    """Convert ndarrays in sample to Tensors."""

    def __call__(self, sample):
        image, landmarks = sample['image'], sample['landmarks']

        # 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),
                'landmarks': torch.from_numpy(landmarks)}

撰写变换

将转换应用于样本。

假设我们要将图像的较短边重新缩放为256,然后从中随机裁剪一个尺寸为224的正方形。即,我们要用到 RescaleRandomCrop变换。 torchvision.transforms.Compose是一个简单的可调用类,它使我们可以执行此操作。

scale = Rescale(256)
crop = RandomCrop(128) # 随机裁剪128的正方形
composed = transforms.Compose([Rescale(256),
                               RandomCrop(224)])

# Apply each of the above transforms on sample.
fig = plt.figure()
sample = face_dataset[65]
for i, tsfrm in enumerate([scale, crop, composed]):
    transformed_sample = tsfrm(sample)

    ax = plt.subplot(1, 3, i + 1)
    plt.tight_layout()
    ax.set_title(type(tsfrm).__name__)
    show_landmarks(**transformed_sample)

plt.show()

遍历数据集

将所有放在一起,创建具有组合转换的数据集。总而言之,每次采样此数据集时:

  • 从文件中即时读取图像
  • 转换应用于读取的图像
  • 由于其中一种转换是随机的,因此在采样时会增加数据

我们可以像以前一样通过 for i in range 循环遍历创建的数据集。

transformed_dataset = FaceLandmarksDataset(csv_file='data/faces/face_landmarks.csv',
                                           root_dir='data/faces/',
                                           transform=transforms.Compose([
                                               Rescale(256),
                                               RandomCrop(224),
                                               ToTensor()
                                           ]))

for i in range(len(transformed_dataset)):
    sample = transformed_dataset[i]

    print(i, sample['image'].size(), sample['landmarks'].size())

    if i == 3:
        break

输出:

0 torch.Size([3, 224, 224]) torch.Size([68, 2])
1 torch.Size([3, 224, 224]) torch.Size([68, 2])
2 torch.Size([3, 224, 224]) torch.Size([68, 2])
3 torch.Size([3, 224, 224]) torch.Size([68, 2])

但是,通过使用简单的for循环迭代数据,我们失去了很多功能。特别是:

  • 批量处理数据
  • 整理数据
  • 使用multiprocessing并行工作加载数据。

torch.utils.data.DataLoader是提供以上所有功能的迭代器。下面使用的方法及参数应当明确。

collate_fn是一个比较重要的参数。可以使用collate_fn指定要精确批处理样品的数量。但是,在大多数情况下,默认排序规则可以正常工作。

dataloader = DataLoader(transformed_dataset, batch_size=4,
                        shuffle=True, num_workers=4)


# Helper function to show a batch
def show_landmarks_batch(sample_batched):
    """Show image with landmarks for a batch of samples."""
    images_batch, landmarks_batch = \
            sample_batched['image'], sample_batched['landmarks']
    batch_size = len(images_batch)
    im_size = images_batch.size(2)
    grid_border_size = 2

    grid = utils.make_grid(images_batch)
    plt.imshow(grid.numpy().transpose((1, 2, 0)))

    for i in range(batch_size):
        plt.scatter(landmarks_batch[i, :, 0].numpy() + i * im_size + (i + 1) * grid_border_size,
                    landmarks_batch[i, :, 1].numpy() + grid_border_size,
                    s=10, marker='.', c='r')

        plt.title('Batch from dataloader')

for i_batch, sample_batched in enumerate(dataloader):
    print(i_batch, sample_batched['image'].size(),
          sample_batched['landmarks'].size())

    # observe 4th batch and stop.
    if i_batch == 3:
        plt.figure()
        show_landmarks_batch(sample_batched)
        plt.axis('off')
        plt.ioff()
        plt.show()
        break

输出:

0 torch.Size([4, 3, 224, 224]) torch.Size([4, 68, 2])
1 torch.Size([4, 3, 224, 224]) torch.Size([4, 68, 2])
2 torch.Size([4, 3, 224, 224]) torch.Size([4, 68, 2])
3 torch.Size([4, 3, 224, 224]) torch.Size([4, 68, 2])


注意报错:

RuntimeError:
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

原因:在windows中, 创建进程由于没有folk创建进程, 所以是通过重载自身模块来创建的, 如果代码放在if __name__ == "__main__"外部, 就会进行无限递归模块(参考:https://www.cnblogs.com/hpcm/p/9779966.html

多进程需要在main函数中运行,

解决方法1:

加main函数,在main中调用,不过main之外的代码会随着每个进程的创建而重新执行。

(我之前显示图片的代码执行了四遍,差点死机,微笑)

if __name__=='__main__':
	for i_batch, sample_batched in enumerate(dataloader):
		print(i_batch, sample_batched['image'].size(),
          sample_batched['landmarks'].size())

    # observe 4th batch and stop.
		if i_batch == 3:
			plt.figure()
			show_landmarks_batch(sample_batched)
			plt.axis('off')
			plt.ioff()
			plt.show()
			break

解决方法2:

去掉num_workers参数,或者num_workers改为0,单进程加载

dataloader = DataLoader(transformed_dataset, batch_size=4,
                        shuffle=True)#, num_workers=0)


后记:torchvision

在本教程中,我们已经看到了如何编写使用:数据集,转换和数据加载器。

torchvision软件包提供了一些常见的数据集和转换。不必编写自定义类。

torchvision中可用的更通用的数据集之一是ImageFolder。假定图像的组织方式如下:

root/ants/xxx.png
root/ants/xxy.jpeg
root/ants/xxz.png
.
.
.
root/bees/123.jpg
root/bees/nsdf3.png
root/bees/asd932_.png

其中“蚂蚁”,“蜜蜂”等是类标签。同样,也可以使用类似 RandomHorizontalFlip、Scale的操作PIL.Image进行通用转换。

可以使用以下代码编写数据加载器:

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])
    ])
hymenoptera_dataset = datasets.ImageFolder(root='hymenoptera_data/train',#注意路径是根文件夹
                                           transform=data_transform)
dataset_loader = torch.utils.data.DataLoader(hymenoptera_dataset,
                                             batch_size=4, shuffle=True,
                                             num_workers=4)

以上代码是在根目录下寻找子文件夹中的图片,如蜜蜂示例root=”root“,上文提到的人脸示例就是”data“(查找子文件夹faces下的图片)
支持的扩展名: .jpg,.jpeg,.png,.ppm,.bmp,.pgm,.tif,.tiff,webp

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值