.. _sec_kaggle_cifar10: Классификация изображений (CIFAR-10) на Kaggle ============================================== До сих пор мы использовали высокоуровневые API фреймворков глубокого обучения для непосредственного получения наборов данных изображений в формате тензоров. Однако пользовательские наборы данных изображений часто представлены в виде файлов изображений. В этом разделе мы начнем с исходных файлов изображений и шаг за шагом организуем их, прочитаем и преобразуем в формат тензоров. В разделе :numref:`sec_image_augmentation` мы экспериментировали с набором данных CIFAR-10, который является важным набором данных в области компьютерного зрения. В этом разделе мы применим знания, полученные в предыдущих разделах, для участия в соревновании Kaggle по классификации изображений CIFAR-10. Ссылка на соревнование: https://www.kaggle.com/c/cifar-10 :numref:`fig_kaggle_cifar10` содержит информацию о соревновании на его веб-странице. Для отправки результатов необходимо зарегистрировать аккаунт на Kaggle. .. _fig_kaggle_cifar10: .. figure:: ../img/kaggle-cifar10.png :width: 600px Информация о соревновании по классификации изображений CIFAR-10 на веб-странице. Набор данных для соревнования можно получить, перейдя на вкладку «Data». .. raw:: latex \diilbookstyleinputcell .. code:: python import collections import math import os import shutil import pandas as pd import torch import torchvision from torch import nn from d2l import torch as d2l Получение и организация набора данных ------------------------------------- Набор данных соревнования разделен на обучающую выборку и тестовую выборку, содержащие 50 000 и 300 000 изображений соответственно. В тестовой выборке 10 000 изображений будут использоваться для оценки, тогда как оставшиеся 290 000 изображений оцениваться не будут: они включены лишь для того, чтобы затруднить мошенничество с *ручной* разметкой тестовой выборки. Все изображения в этом наборе данных являются цветными файлами в формате PNG (каналы RGB), высота и ширина которых составляют 32 пикселя. Изображения охватывают всего 10 категорий, а именно: самолеты, автомобили, птицы, кошки, олени, собаки, лягушки, лошади, лодки и грузовики. В верхнем левом углу :numref:`fig_kaggle_cifar10` показаны некоторые изображения самолетов, автомобилей и птиц из набора данных. Скачивание набора данных ~~~~~~~~~~~~~~~~~~~~~~~~ После входа в Kaggle мы можем перейти на страницу соревнования по классификации изображений CIFAR-10, показанного на рисунке :numref:`fig_kaggle_cifar10`, и загрузить набор данных, нажав кнопку «Download All». После распаковки загруженного файла в ``../data``, а также распаковки архивов ``train.7z`` и ``test.7z``, содержащихся в нём, мы найдём весь набор данных в следующих путях: - ``../data/cifar-10/train/[1-50000].png`` - ``../data/cifar-10/test/[1-300000].png`` - ``../data/cifar-10/trainLabels.csv`` - ``../data/cifar-10/sampleSubmission.csv`` где каталоги ``train`` и ``test`` содержат обучающие и тестовые изображения соответственно, ``trainLabels.csv`` содержит метки для обучающих изображений, а ``sample_submission.csv`` — файл примера отправки. Чтобы облегчить начало работы, мы предоставляем небольшой набор данных, содержащий первые 1000 обучающих изображений и 5 случайных тестовых примеров. Для использования полного набора данных соревнования Kaggle необходимо установить следующую переменную ``demo`` в значение ``False``. .. raw:: latex \diilbookstyleinputcell .. code:: python #@save d2l.DATA_HUB['cifar10_tiny'] = (d2l.DATA_URL + 'kaggle_cifar10_tiny.zip', '2068874e4b9a9f0fb07ebe0ad2b29754449ccacd') # If you use the full dataset downloaded for the Kaggle competition, set # `demo` to False demo = True if demo: data_dir = d2l.download_extract('cifar10_tiny') else: data_dir = '../data/cifar-10/' Организация набора данных ~~~~~~~~~~~~~~~~~~~~~~~~~ Для облегчения обучения и тестирования моделей необходимо организовать наборы данных. Сначала прочитаем метки из CSV-файла. Функция ниже возвращает словарь, в котором ключом является часть имени файла до расширения, а значением — соответствующая метка. .. raw:: latex \diilbookstyleinputcell .. code:: python #@save def read_csv_labels(fname): """Read `fname` to return a filename to label dictionary.""" with open(fname, 'r') as f: # Skip the file header line (column name) lines = f.readlines()[1:] tokens = [l.rstrip().split(',') for l in lines] return dict(((name, label) for name, label in tokens)) labels = read_csv_labels(os.path.join(data_dir, 'trainLabels.csv')) print('# training examples:', len(labels)) print('# classes:', len(set(labels.values()))) Далее мы определяем функцию ``reorg_train_valid`` для выделения валидационной выборки из исходной обучающей выборки. Аргумент ``valid_ratio`` этой функции задаёт долю примеров в валидационной выборке относительно числа примеров в исходной обучающей выборке. Более конкретно, пусть :math:`n` обозначает количество изображений класса с наименьшим числом примеров, а :math:`r` — заданную долю. Тогда валидационная выборка будет содержать по :math:`\max(\lfloor nr\rfloor,1)` изображений для каждого класса. Рассмотрим в качестве примера ``valid_ratio=0.1``. Поскольку исходная обучающая выборка содержит 50000 изображений, в каталоге ``train_valid_test/train`` будет использоваться 45000 изображений для обучения, а остальные 5000 изображений будут выделены в валидационную выборку в каталоге ``train_valid_test/valid``. После организации набора данных изображения одного и того же класса будут помещены в одну папку. .. raw:: latex \diilbookstyleinputcell .. code:: python #@save def copyfile(filename, target_dir): """Copy a file into a target directory.""" os.makedirs(target_dir, exist_ok=True) shutil.copy(filename, target_dir) #@save def reorg_train_valid(data_dir, labels, valid_ratio): """Split the validation set out of the original training set.""" # The number of examples of the class that has the fewest examples in the # training dataset n = collections.Counter(labels.values()).most_common()[-1][1] # The number of examples per class for the validation set n_valid_per_label = max(1, math.floor(n * valid_ratio)) label_count = {} for train_file in os.listdir(os.path.join(data_dir, 'train')): label = labels[train_file.split('.')[0]] fname = os.path.join(data_dir, 'train', train_file) copyfile(fname, os.path.join(data_dir, 'train_valid_test', 'train_valid', label)) if label not in label_count or label_count[label] < n_valid_per_label: copyfile(fname, os.path.join(data_dir, 'train_valid_test', 'valid', label)) label_count[label] = label_count.get(label, 0) + 1 else: copyfile(fname, os.path.join(data_dir, 'train_valid_test', 'train', label)) return n_valid_per_label Функция ``reorg_test`` ниже организует тестовую выборку для загрузки данных при прогнозировании. .. raw:: latex \diilbookstyleinputcell .. code:: python #@save def reorg_test(data_dir): """Organize the testing set for data loading during prediction.""" for test_file in os.listdir(os.path.join(data_dir, 'test')): copyfile(os.path.join(data_dir, 'test', test_file), os.path.join(data_dir, 'train_valid_test', 'test', 'unknown')) Наконец, мы используем функцию для вызова ``read_csv_labels``, ``reorg_train_valid`` и ``reorg_test`` функций, определённых выше. .. raw:: latex \diilbookstyleinputcell .. code:: python def reorg_cifar10_data(data_dir, valid_ratio): labels = read_csv_labels(os.path.join(data_dir, 'trainLabels.csv')) reorg_train_valid(data_dir, labels, valid_ratio) reorg_test(data_dir) Здесь мы устанавливаем размер батча равным 32 для небольшого набора данных. При обучении и тестировании полного набора данных соревнования Kaggle ``batch_size`` следует задать в виде большего целого числа, например 128. Мы выделяем 10% обучающих примеров в качестве валидационной выборки для настройки гиперпараметров. .. raw:: latex \diilbookstyleinputcell .. code:: python batch_size = 32 if demo else 128 valid_ratio = 0.1 reorg_cifar10_data(data_dir, valid_ratio) Аугментация изображений ----------------------- Мы используем аугментацию изображений для борьбы с переобучением. Например, во время обучения изображения можно случайно отражать по горизонтали. Мы также можем выполнять стандартизацию для трёх каналов RGB цветных изображений. Ниже приведены некоторые из этих операций, которые вы можете настраивать. .. raw:: latex \diilbookstyleinputcell .. code:: python transform_train = torchvision.transforms.Compose([ # Scale the image up to a square of 40 pixels in both height and width torchvision.transforms.Resize(40), # Randomly crop a square image of 40 pixels in both height and width to # produce a small square of 0.64 to 1 times the area of the original # image, and then scale it to a square of 32 pixels in both height and # width torchvision.transforms.RandomResizedCrop(32, scale=(0.64, 1.0), ratio=(1.0, 1.0)), torchvision.transforms.RandomHorizontalFlip(), torchvision.transforms.ToTensor(), # Standardize each channel of the image torchvision.transforms.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010])]) Во время тестирования мы применяем к изображениям только стандартизацию, чтобы исключить случайность в результатах оценки. .. raw:: latex \diilbookstyleinputcell .. code:: python transform_test = torchvision.transforms.Compose([ torchvision.transforms.ToTensor(), torchvision.transforms.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010])]) Чтение набора данных -------------------- Далее мы **загружаем организованный набор данных, состоящий из необработанных файлов изображений**. Каждый пример включает изображение и метку. .. raw:: latex \diilbookstyleinputcell .. code:: python train_ds, train_valid_ds = [torchvision.datasets.ImageFolder( os.path.join(data_dir, 'train_valid_test', folder), transform=transform_train) for folder in ['train', 'train_valid']] valid_ds, test_ds = [torchvision.datasets.ImageFolder( os.path.join(data_dir, 'train_valid_test', folder), transform=transform_test) for folder in ['valid', 'test']] Во время обучения нам необходимо задать все описанные выше операции аугментации изображений. При использовании валидационной выборки для оценки модели в процессе подбора гиперпараметров не следует вносить случайность, связанную с аугментацией изображений. Перед финальным прогнозированием мы обучаем модель на объединенной обучающей выборке и валидационной выборке, чтобы максимально использовать все размеченные данные. .. raw:: latex \diilbookstyleinputcell .. code:: python train_iter, train_valid_iter = [torch.utils.data.DataLoader( dataset, batch_size, shuffle=True, drop_last=True) for dataset in (train_ds, train_valid_ds)] valid_iter = torch.utils.data.DataLoader(valid_ds, batch_size, shuffle=False, drop_last=True) test_iter = torch.utils.data.DataLoader(test_ds, batch_size, shuffle=False, drop_last=False) Определение модели ------------------ Мы определяем модель ResNet-18, описанную в :numref:`sec_resnet`. .. raw:: latex \diilbookstyleinputcell .. code:: python def get_net(): num_classes = 10 net = d2l.resnet18(num_classes, 3) return net loss = nn.CrossEntropyLoss(reduction="none") Определение функции обучения ---------------------------- Мы будем выбирать модели и настраивать гиперпараметры, ориентируясь на качество модели на валидационной выборке. В дальнейшем мы определим функцию обучения модели ``train``. .. raw:: latex \diilbookstyleinputcell .. code:: python def train(net, train_iter, valid_iter, num_epochs, lr, wd, devices, lr_period, lr_decay): trainer = torch.optim.SGD(net.parameters(), lr=lr, momentum=0.9, weight_decay=wd) scheduler = torch.optim.lr_scheduler.StepLR(trainer, lr_period, lr_decay) num_batches, timer = len(train_iter), d2l.Timer() legend = ['train loss', 'train acc'] if valid_iter is not None: legend.append('valid acc') animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs], legend=legend) net = nn.DataParallel(net, device_ids=devices).to(devices[0]) for epoch in range(num_epochs): net.train() metric = d2l.Accumulator(3) for i, (features, labels) in enumerate(train_iter): timer.start() l, acc = d2l.train_batch_ch13(net, features, labels, loss, trainer, devices) metric.add(l, acc, labels.shape[0]) timer.stop() if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1: animator.add(epoch + (i + 1) / num_batches, (metric[0] / metric[2], metric[1] / metric[2], None)) if valid_iter is not None: valid_acc = d2l.evaluate_accuracy_gpu(net, valid_iter) animator.add(epoch + 1, (None, None, valid_acc)) scheduler.step() measures = (f'train loss {metric[0] / metric[2]:.3f}, ' f'train acc {metric[1] / metric[2]:.3f}') if valid_iter is not None: measures += f', valid acc {valid_acc:.3f}' print(measures + f'\n{metric[2] * num_epochs / timer.sum():.1f}' f' examples/sec on {str(devices)}') Обучение и проверка модели -------------------------- Теперь мы можем обучить и проверить модель. Все указанные гиперпараметры можно настроить. Например, можно увеличить количество эпох. Если значения ``lr_period`` и ``lr_decay`` равны 4 и 0,9 соответственно, скорость обучения алгоритма оптимизации будет умножаться на 0,9 после каждых 4 эпох. Для удобства демонстрации мы здесь обучаем модель всего 20 эпох. .. raw:: latex \diilbookstyleinputcell .. code:: python devices, num_epochs, lr, wd = d2l.try_all_gpus(), 20, 2e-4, 5e-4 lr_period, lr_decay, net = 4, 0.9, get_net() net(next(iter(train_iter))[0]) train(net, train_iter, valid_iter, num_epochs, lr, wd, devices, lr_period, lr_decay) Классификация тестовой выборки и отправка результатов на Kaggle --------------------------------------------------------------- Получив перспективную модель с подобранными гиперпараметрами, мы используем все размеченные данные (включая валидационную выборку) для повторного обучения модели и классификации тестовой выборки. .. raw:: latex \diilbookstyleinputcell .. code:: python net, preds = get_net(), [] net(next(iter(train_valid_iter))[0]) train(net, train_valid_iter, None, num_epochs, lr, wd, devices, lr_period, lr_decay) for X, _ in test_iter: y_hat = net(X.to(devices[0])) preds.extend(y_hat.argmax(dim=1).type(torch.int32).cpu().numpy()) sorted_ids = list(range(1, len(test_ds) + 1)) sorted_ids.sort(key=lambda x: str(x)) df = pd.DataFrame({'id': sorted_ids, 'label': preds}) df['label'] = df['label'].apply(lambda x: train_valid_ds.classes[x]) df.to_csv('submission.csv', index=False) Приведённый выше код создаст файл ``submission.csv``, формат которого соответствует требованиям соревнования Kaggle. Метод отправки результатов на Kaggle аналогичен описанному в :numref:`sec_kaggle_house`. Резюме ------ - Мы можем читать наборы данных, содержащие необработанные изображения, после того как они будут организованы в требуемом формате. - Мы можем использовать свёрточные нейронные сети и аугментацию изображений на соревнованиях по классификации изображений. Упражнения ---------- 1. Для участия в соревновании на Kaggle используйте полный набор данных CIFAR-10. Задайте гиперпараметры как ``batch_size = 128``, ``num_epochs = 100``, ``lr = 0.1``, ``lr_period = 50`` и ``lr_decay = 0.1``. Посмотрите, какие точность и место в рейтинге вы сможете достичь в этом соревновании. Можете ли вы улучшить эти показатели? 2. Какую точность можно получить, не используя аугментацию изображений? `Обсуждения `__