Серия PyTorch | Руководство по загрузке и предварительной обработке данных

PyTorch

оригинальное название | DATA LOADING AND PROCESSING TUTORIAL

автор | Sasank Chilamkurthy

переводчик | kbsc13("Рост алгоритма обезьяны"Автор публичного номера)

оригинальный | py torch.org/tutorials/ нет…

утверждение| Перевод предназначен для общения и обучения, добро пожаловать на перепечатку, но, пожалуйста, сохраните эту статью для коммерческих или незаконных целей.

Введение

В этом руководстве в основном рассказывается, как загружать, предварительно обрабатывать и улучшать данные.

Сначала обязательно установите следующееpythonБиблиотеки:

  • scikit-image: данные изображения обработки
  • pandas: Обработка CSV-файлов.

Код модуля импорта выглядит следующим образом:

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 ключевых точек грани, которые определяютсяdlibСгенерированная конкретная реализация может проверить ее официальное введение на веб-сайте:

blog.Spree.net/2014/08/Горячие…

Адрес загрузки набора данных:

скачать.py torch.org/tutorial/…

набор данныхcsvФормат файла следующий, имя изображения и координаты каждой ключевой точкиx, y

image_name,part_0_x,part_0_y,part_1_x,part_1_y,part_2_x, ... ,part_67_x,part_67_y
0805personali01.jpg,27,83,27,98, ... 84,134
1084239450_e76e00b7e7.jpg,70,236,71,257, ... ,128,312

Загрузите и разархивируйте набор данных и поместите его в папкуdata/faces, то быстро открываем сначалаface_landmarks.csvфайл, просмотреть содержимое файла, то есть аннотационную информацию, код выглядит следующим образом:

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]))

Вывод выглядит следующим образом:

Затем напишите вспомогательную функцию для отображения изображения лица и его ключевых точек, код такой:

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()

Вывод выглядит следующим образом:

Класс набора данных

torch.utils.data.DatasetЭто абстрактный класс, представляющий набор данных, который необходимо наследовать при настройке собственного набора данных.Datasetкласс и переопределить следующие методы:

  • __len__:перечислитьlen(dataset)Количество наборов данных, которые могут быть возвращены при
  • __getitem__: Для получения данных можно получить доступ к индексу, т.е.dataset[i]может получить доступ кiПример данных

Далее мы настроим категорию для нашего набора данных ключевых точек лица, в__init__Метод будет считывать информацию из набора данных, а в__getitem__Набор данных, полученный вызовом метода, в основном основан на соображениях памяти.Этому методу не нужно считывать и сохранять все данные в памяти за один раз, и он может считывать и загружать данные в память только тогда, когда данные необходимо быть прочитанным.

Образцы набора данных будут представлены словарем:{'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): 带有标注信息的 csv 文件路径
            root_dir (string): 图片所在文件夹
            transform (callable, optional): 可选的用于预处理图片的方法
        """
        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):
        # 读取图片
        img_name = os.path.join(self.root_dir,
                                self.landmarks_frame.iloc[idx, 0])
        image = io.imread(img_name)
        # 读取关键点并转换为 numpy 数组
        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 образца:

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

fig = plt.figure()
# 读取前 4 张图片并展示
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)
    plt.tight_layout()
    ax.set_title('Sample #{}'.format(i))
    ax.axis('off')
    show_landmarks(**sample)

    if i == 3:
        plt.show()
        break

Результат выглядит следующим образом:

Transforms

Проблема видна из структуры вывода вышеприведенного примера, размер изображений несовместим, но большинство нейронных сетей требуют, чтобы размер входных изображений был фиксированным. Следовательно, следующим шагом будет предоставление некоторого кода предварительной обработки, в основном следующих трех методов предварительной обработки:

  • Rescale: изменить размер изображения
  • RandomCrop: случайное кадрирование изображения, что является методом увеличения данных.
  • ToTensor:будетnumpyФормат изображений преобразуется вpytorchформат данныхtensors, здесь нужно обменяться координатами.

Все эти методы будут написаны как вызываемые классы, а не простые функции, поэтому вам не нужно каждый раз передавать параметры. Поэтому нам необходимо реализовать__call__метод, а при необходимости__init__также реализованы методы, которые затем можно вызвать следующим образом:

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

RescaleКод реализации метода выглядит следующим образом:

class Rescale(object):
    """将图片调整为给定的大小.

    Args:
        output_size (tuple or int): 期望输出的图片大小. 如果是 tuple 类型,输出图片大小就是给定的 output_size;
                                    如果是 int 类型,则图片最短边将匹配给的大小,然后调整最大边以保持相同的比例。
    """

    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]
        # 判断给定大小的形式,tuple 还是 int 类型
        if isinstance(self.output_size, int):
            # 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))

        # 根据调整前后的尺寸比例,调整关键点的坐标位置,并且 x 对应 w,y 对应 h
        landmarks = landmarks * [new_w / w, new_h / h]

        return {'image': img, 'landmarks': landmarks}

RandomCropРеализация кода:

class RandomCrop(object):
    """给定图片,随机裁剪其任意一个和给定大小一样大的区域.

    Args:
        output_size (tuple or int): 期望裁剪的图片大小。如果是 int,将得到一个正方形大小的图片.
    """

    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
        # 随机选择裁剪区域的左上角,即起点,(left, top),范围是由原始大小-输出大小
        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}

ToTensorреализация метода:

class ToTensor(object):
    """将 ndarrays 转换为 tensors."""

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

        # 调整坐标尺寸,numpy 的维度是 H x W x C,而 torch 的图片维度是 C X H X W
        image = image.transpose((2, 0, 1))
        return {'image': torch.from_numpy(image),
                'landmarks': torch.from_numpy(landmarks)}

Комбинирование методов предварительной обработки

Ниже приведен пример использования описанного выше пользовательского метода предварительной обработки.

Предположим, мы хотим настроить длину самой короткой стороны изображения на 256, а затем случайным образом обрезать область изображения размером 224*224, то есть нам нужно объединить вызовыRescaleиRandomCropметод предварительной обработки.

torchvision.transforms.ComposeЭто класс, который может реализовать комбинированный метод для вызова метода, подлежащего обработке.Код реализации выглядит следующим образом:

scale = Rescale(256)
crop = RandomCrop(128)
composed = transforms.Compose([Rescale(256),
                               RandomCrop(224)])

# 对图片数据调用上述 3 种形式预处理方法,即单独使用 Rescale,RandomCrop,组合使用 Rescale和 RandomCrop
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()

Выходная структура:

перебрать весь набор данных

Теперь, когда мы определили класс для обработки наборов данных и три класса для предварительной обработки данных, мы можем интегрировать их вместе, чтобы реализовать процесс загрузки и предварительной обработки данных, Процесс выглядит следующим образом:

  • Сначала прочитайте изображение в соответствии с путем изображения
  • Вызов методов предварительной обработки изображений
  • Методы предварительной обработки также могут обеспечить увеличение данных.

Реализованный код выглядит так:

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

Выходной результат:

Вышеупомянутое является просто процессом обработки.На самом деле, при обработке и загрузке данных мы обычно выполняем следующую обработку данных:

  • Разделить данные на пакеты данных заданного размера
  • перетасовать порядок данных
  • использоватьmultiprocessingзагружать данные параллельно

torch.utils.data.DataLoaderэто итератор, который реализует вышеуказанные операции. Необходимые параметры показаны в следующем коде, один из параметровcollate_fn— это операция, которая указывает, как пакетировать данные, но также может принимать функцию по умолчанию.

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


# 辅助函数,用于展示一个 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

Выходной результат:

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

здесьants,beesи т. д. являются метками категорий, в дополнение кPIL.Imageметоды предварительной обработки, такие какRandomHorizontalFlip,Scaleвключены в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])
    ])
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)

резюме

В этом руководстве в основном рассказывается, как настроить класс для загрузки собственного набора данных, а также о методе предварительной обработки, а также в концеPyTorchсерединаtorchvision,torch.utils.data.DataLoaderметод.

Код для этой статьи загружен на Github:

GitHub.com/CCC013/глубокий…

Кроме того, полезноdlibКод для генерации ключевых точек лица:

GitHub.com/CCC013/глубокий…

Кроме того, вы также можете ответить в фоновом режиме публичной учетной записи"PyTorch", чтобы получить набор данных и код для этого руководства.


Добро пожаловать в мой общедоступный аккаунт WeChat--Рост алгоритма обезьяны, или отсканируйте QR-код ниже, чтобы общаться, учиться и развиваться вместе!