оригинальное название | 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:
Кроме того, полезноdlib
Код для генерации ключевых точек лица:
Кроме того, вы также можете ответить в фоновом режиме публичной учетной записи"PyTorch", чтобы получить набор данных и код для этого руководства.
Добро пожаловать в мой общедоступный аккаунт WeChat--Рост алгоритма обезьяны, или отсканируйте QR-код ниже, чтобы общаться, учиться и развиваться вместе!