Python:PyTorch 使用 Torchvision 加载数据集 (八十一)

加载图像数据

到目前为止,我们使用的数据都是人工数据集,你很少会在实际项目中用到这样的数据集。相反,在实际项目中,你可能会处理一些全尺寸的图像,比如手机相机拍摄的图片。在这个 notebook 中,我们将会学习如何加载图像,并使用它们来训练神经网络。

我们将用到来自 Kaggle 的猫狗照片数据集。下面是一些图片示例:

file

我们将使用这个数据集来训练一个能够对猫狗图像进行分类的神经网络。如今看来,这似乎并不是什么伟大的成就,但在五年之前,这对计算机视觉系统来说极具挑战性。

%matplotlib inline
%config InlineBackend.figure_format = 'retina'

import matplotlib.pyplot as plt

import torch
from torchvision import datasets, transforms

import helper

加载图像数据最简单是方法是使用 torchvision 中的 datasets.ImageFolder资料)。

dataset = datasets.ImageFolder('path/to/data', transform=transforms)

'path/to/data' 是通往数据目录的文件路径,transforms 是一个处理步骤的列表,使用 torchvision 中的 transforms 模块构建。ImageFolder 中的文件和目录应按以下格式构建:

root/dog/xxx.png
root/dog/xxy.png
root/dog/xxz.png

root/cat/123.png
root/cat/nsdf3.png
root/cat/asd932_.png

每个类都有各自存储图像的目录(catdog)。接着,这些图像将被贴上摘自目录名的标签。所以在这里,图像 123.png 在加载时将被贴上类标签 cat。你可以从这里直接下载我们早已构建好的数据集。我已经将它分成了训练集和测试集。

转换

当你使用 ImageFolder 加载数据后,你需要定义一些转换。举个例子,这些图像的尺寸都不相同,但我们需要统一尺寸以便进行训练。你可以使用 transforms.Resize() 来重新确定图像尺寸,也可以使用 transforms.CenterCrop()transforms.RandomResizedCrop() 等进行切割。我们还需要使用 transforms.ToTensor() 来将图像转换为 PyTorch 张量。通常,你会使用 transforms.Compose() 来将这些转换结合到一条流水线中,这条流水线接收包含转换的列表,并按顺序运行。如下面的例子所示,它首先进行缩放,接着切割,再转换为张量:

transforms = transforms.Compose([transforms.Resize(255),
transforms.CenterCrop(224),
transforms.ToTensor()])

我们可以使用许多种转换,接下来我会逐步讲解,你也可以查看这里的资料

Data Loader

在加载 ImageFolder 后,你需要将它传递给一个 DataLoaderDataLoader 接收数据集(比如你从 ImageFolder 中获取的数据集),并返回不同批次的图像以及对应的标签。你可以设置不同参数,比如批次大小,也可以设置是否在每个阶段后重组数据。

dataloader = torch.utils.data.DataLoader(dataset, batch_size=32, shuffle=True)

在这里,dataloader 是一个生成器。要想从这个生成器中提取数据,你需要遍历这个生成器,或是将它转换为一个迭代器并调用 next()

Looping through it, get a batch on each loop

for images, labels in dataloader:
pass

Get one batch

images, labels = next(iter(dataloader))

练习:Cat_Dog_data/train 文件夹中加载图像,定义一些转换,并构建 dataloader。

data_dir = '../Cat_Dog_data/train'

# TODO: compose transforms here
transforms = transforms.Compose([transforms.Resize(255), transforms.CenterCrop(224), transforms.ToTensor()])
print("transforms2-", transforms)

# TODO: create the ImageFolder
dataset = datasets.ImageFolder(data_dir, transform=transforms)
print("dataset-", dataset)

# TODO: use the ImageFolder dataset to create the DataLoader
dataloader = torch.utils.data.DataLoader(dataset, batch_size=32, shuffle=True)
transforms2- Compose(
    Resize(size=255, interpolation=PIL.Image.BILINEAR)
    CenterCrop(size=(224, 224))
    ToTensor()
)
dataset- Dataset ImageFolder
    Number of datapoints: 22500
    Root Location: ../Cat_Dog_data/train
    Transforms (if any): Compose(
                             Resize(size=255, interpolation=PIL.Image.BILINEAR)
                             CenterCrop(size=(224, 224))
                             ToTensor()
                         )
    Target Transforms (if any): None
# Run this to test your data loader
images, labels = next(iter(dataloader))
helper.imshow(images[0], normalize=False)
<matplotlib.axes._subplots.AxesSubplot at 0x7f1614c437b8>

file

如果你成功加载了数据,你将看到以下内容(你看到的图像将有所不同):

<img src='assets/cat_cropped.png', width=244>

数据增强

训练神经网络的常用策略是在输入数据中添加随机性。举个例子,你可以在训练时随意旋转、镜像、缩放以及/或剪切你的图像。这样一来,你的神经网络在处理位置、大小、方向不同的相同图像时,可以更好地进行泛化。

要想随机旋转、缩放、剪切和翻转图像,你需要按以下格式定义转换:

train_transforms = transforms.Compose([transforms.RandomRotation(30),
                                       transforms.RandomResizedCrop(100),
                                       transforms.RandomHorizontalFlip(),
                                       transforms.ToTensor(),
                                       transforms.Normalize([0.5, 0.5, 0.5], 
                                                            [0.5, 0.5, 0.5])])
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-4-6f7399ba7364> in <module>()
----> 1 train_transforms = transforms.Compose([transforms.RandomRotation(30),
      2                                        transforms.RandomResizedCrop(100),
      3                                        transforms.RandomHorizontalFlip(),
      4                                        transforms.ToTensor(),
      5                                        transforms.Normalize([0.5, 0.5, 0.5], 

AttributeError: 'Compose' object has no attribute 'Compose'

你通常还需要使用 transforms.Normalize 来标准化图像。在传入均值和标准差的列表后,颜色通道将按以下方法进行标准化:

减去 mean 能让数据以 0 为中心,除以 std 能够将值集中在 -1 和 1 之间。标准化有助于神经网络使权重接近 0,这能使反向传播更为稳定。倘若没有标准化,网络往往无法进行学习。

你可以在这里找到所有可用的转换。然而,当你在进行测试时,你也许想使用没有转换过的图像(除非你需要按同样的方法进行标准化)。因此,对于验证/测试图片,你往往只需要改变大小或进行剪切。

练习: 为下方的训练数据和测试数据定义转换。

```python
data_dir = 'Cat_Dog_data'

# TODO: Define transforms for the training data and testing data
train_transforms = 

test_transforms = 

# Pass transforms in here, then run the next cell to see how the transforms look
train_data = datasets.ImageFolder(data_dir + '/train', transform=train_transforms)
test_data = datasets.ImageFolder(data_dir + '/test', transform=test_transforms)

trainloader = torch.utils.data.DataLoader(train_data, batch_size=32)
testloader = torch.utils.data.DataLoader(test_data, batch_size=32)
# change this to the trainloader or testloader 
data_iter = iter(testloader)

images, labels = next(data_iter)
fig, axes = plt.subplots(figsize=(10,4), ncols=4)
for ii in range(4):
    ax = axes[ii]
    helper.imshow(images[ii], ax=ax)

转换后的图像应该像下面这样:

训练示例: swAVzr42Yo.png


测试示例: o6DPr5Zl5s.png

学到这里,你应该已经了解了如何加载数据进行训练和测试了。现在,你应该尝试自己构建一个能够分辨猫狗图像的神经网络了。这比之前使用 MNIST 和 Fashion-MNIST 数据集完成的任务更加复杂。事实上,无论深度如何,你也许无法顺利完成一个全连接网络。这些图像有三个色彩通道,分辨率也更高(你在之前见过的 28x28 像素的图像都非常小)。

在下一部分,我将为你展示如何使用预先训练的网络构建一个解决此问题的模型。

# Optional TODO: Attempt to build a network to classify cats vs dogs from this dataset

help.py 文件

import matplotlib.pyplot as plt
import numpy as np
from torch import nn, optim
from torch.autograd import Variable

def test_network(net, trainloader):

    criterion = nn.MSELoss()
    optimizer = optim.Adam(net.parameters(), lr=0.001)

    dataiter = iter(trainloader)
    images, labels = dataiter.next()

    # Create Variables for the inputs and targets
    inputs = Variable(images)
    targets = Variable(images)

    # Clear the gradients from all Variables
    optimizer.zero_grad()

    # Forward pass, then backward pass, then update weights
    output = net.forward(inputs)
    loss = criterion(output, targets)
    loss.backward()
    optimizer.step()

    return True

def imshow(image, ax=None, title=None, normalize=True):
    """Imshow for Tensor."""
    if ax is None:
        fig, ax = plt.subplots()
    image = image.numpy().transpose((1, 2, 0))

    if normalize:
        mean = np.array([0.485, 0.456, 0.406])
        std = np.array([0.229, 0.224, 0.225])
        image = std * image + mean
        image = np.clip(image, 0, 1)

    ax.imshow(image)
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
    ax.spines['left'].set_visible(False)
    ax.spines['bottom'].set_visible(False)
    ax.tick_params(axis='both', length=0)
    ax.set_xticklabels('')
    ax.set_yticklabels('')

    return ax

def view_recon(img, recon):
    ''' Function for displaying an image (as a PyTorch Tensor) and its
        reconstruction also a PyTorch Tensor
    '''

    fig, axes = plt.subplots(ncols=2, sharex=True, sharey=True)
    axes[0].imshow(img.numpy().squeeze())
    axes[1].imshow(recon.data.numpy().squeeze())
    for ax in axes:
        ax.axis('off')
        ax.set_adjustable('box-forced')

def view_classify(img, ps, version="MNIST"):
    ''' Function for viewing an image and it's predicted classes.
    '''
    ps = ps.data.numpy().squeeze()

    fig, (ax1, ax2) = plt.subplots(figsize=(6,9), ncols=2)
    ax1.imshow(img.resize_(1, 28, 28).numpy().squeeze())
    ax1.axis('off')
    ax2.barh(np.arange(10), ps)
    ax2.set_aspect(0.1)
    ax2.set_yticks(np.arange(10))
    if version == "MNIST":
        ax2.set_yticklabels(np.arange(10))
    elif version == "Fashion":
        ax2.set_yticklabels(['T-shirt/top',
                            'Trouser',
                            'Pullover',
                            'Dress',
                            'Coat',
                            'Sandal',
                            'Shirt',
                            'Sneaker',
                            'Bag',
                            'Ankle Boot'], size='small');
    ax2.set_title('Class Probability')
    ax2.set_xlim(0, 1.1)

    plt.tight_layout()

为者常成,行者常至