.. _sec_fine_tuning: Дообучение ========== В предыдущих главах мы обсуждали, как обучать модели на обучающем наборе данных Fashion-MNIST, содержащем всего 60 000 изображений. Мы также описывали ImageNet — наиболее широко используемый в академической среде крупномасштабный набор данных для изображений, который включает более 10 миллионов изображений и 1000 классов объектов. Однако размер набора данных, с которым мы обычно сталкиваемся, находится между размерами этих двух наборов. Предположим, что мы хотим распознавать различные типы стульев на изображениях, а затем рекомендовать пользователям ссылки для покупки. Один из возможных методов заключается в том, чтобы сначала выделить 100 наиболее распространённых моделей стульев, сделать по 1000 фотографий каждой модели с разных ракурсов, а затем обучить модель классификации на собранном наборе данных. Хотя этот набор данных со стульями может быть больше, чем Fashion-MNIST, количество примеров в нём всё равно составляет менее одной десятой от числа примеров в ImageNet. Это может привести к переобучению сложных моделей, которые хорошо подходят для ImageNet, на данном наборе данных со стульями. Кроме того, из-за ограниченного количества примеров для обучения точность полученной модели может не соответствовать практическим требованиям. Чтобы решить описанные выше проблемы, очевидным решением является сбор дополнительных данных. Однако сбор и разметка данных требуют значительных временных и финансовых затрат. Например, для создания набора данных ImageNet исследователи потратили миллионы долларов исследовательских грантов. Хотя текущая стоимость сбора данных значительно снизилась, эти расходы всё ещё нельзя игнорировать. Другим решением является применение *переноса обучения* для передачи знаний, полученных на *исходном наборе данных*, в *целевой набор данных*. Например, хотя большинство изображений в наборе данных ImageNet не имеют отношения к стульям, модель, обученная на этом наборе, может извлекать более общие признаки, которые помогают выявлять края, текстуры, формы и состав объектов. Эти схожие признаки могут также быть эффективными для распознавания стульев. Шаги ---- В этом разделе мы рассмотрим распространённый метод переноса обучения — *дообучение*. Как показано на рисунке :numref:`fig_finetune`, дообучение состоит из следующих четырёх шагов: 1. Предварительно обучите модель нейронной сети, то есть *исходную модель*, на исходном наборе данных (например, на наборе данных ImageNet). 2. Создайте новую модель нейронной сети, то есть *целевую модель*. При этом копируются все архитектуры и параметры исходной модели, за исключением выходного слоя. Мы предполагаем, что эти параметры содержат знания, полученные на исходном наборе данных, и эти знания применимы и к целевому набору данных. Также предполагается, что выходной слой исходной модели тесно связан с метками исходного набора данных; поэтому он не используется в целевой модели. 3. Добавьте к целевой модели выходной слой, количество выходов которого равно числу категорий в целевом наборе данных. Затем случайным образом инициализируйте параметры этого слоя. 4. Обучите целевую модель на целевом наборе данных, например, на наборе данных со стульями. Слой вывода будет обучаться с нуля, в то время как параметры всех остальных слоёв будут дообучаться на основе параметров исходной модели. .. _fig_finetune: .. figure:: ../img/finetune.svg Дообучение. Когда целевые наборы данных значительно меньше исходных, дообучение помогает улучшить обобщающую способность моделей. Распознавание хот-догов ----------------------- Проиллюстрируем дообучение на конкретном примере: распознавании хот-догов. Мы выполним дообучение модели ResNet, предварительно обученной на наборе данных ImageNet, на небольшом наборе данных. Этот набор данных состоит из тысяч изображений с хот-догами и без них. Мы будем использовать дообученную модель для распознавания хот-догов на изображениях. .. raw:: latex \diilbookstyleinputcell .. code:: python %matplotlib inline import os import torch import torchvision from torch import nn from d2l import torch as d2l Чтение набора данных ~~~~~~~~~~~~~~~~~~~~ Набор данных с хот-догами, который мы используем, был взят из онлайн-изображений. Этот набор данных состоит из 1400 изображений положительного класса, содержащих хот-доги, и такого же количества изображений отрицательного класса, содержащих другие продукты питания. 1000 изображений обоих классов используются для обучения, а остальные — для тестирования. После распаковки загруженного набора данных мы получаем две папки ``hotdog/train`` и ``hotdog/test``. В обеих папках есть подпапки ``hotdog`` и ``not-hotdog``, каждая из которых содержит изображения соответствующего класса. .. raw:: latex \diilbookstyleinputcell .. code:: python #@save d2l.DATA_HUB['hotdog'] = (d2l.DATA_URL + 'hotdog.zip', 'fba480ffa8aa7e0febbb511d181409f899b9baa5') data_dir = d2l.download_extract('hotdog') Мы создаём два экземпляра для чтения всех файлов изображений в обучающем и тестовом наборах данных соответственно. .. raw:: latex \diilbookstyleinputcell .. code:: python train_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train')) test_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'test')) Ниже показаны первые 8 положительных примеров и последние 8 отрицательных изображений. Как видно, **изображения различаются по размеру и соотношению сторон**. .. raw:: latex \diilbookstyleinputcell .. code:: python hotdogs = [train_imgs[i][0] for i in range(8)] not_hotdogs = [train_imgs[-i - 1][0] for i in range(8)] d2l.show_images(hotdogs + not_hotdogs, 2, 8, scale=1.4); Во время обучения мы сначала выделяем случайную область случайного размера и случайного соотношения сторон из изображения, а затем масштабируем эту область до размера входного изображения :math:`224 \times 224`. При тестировании мы масштабируем высоту и ширину изображения до 256 пикселей, а затем выделяем центральную область :math:`224 \times 224` в качестве входа. Кроме того, для трёх каналов цвета RGB (красного, зеленого и синего) мы *стандартизируем* их значения по каждому каналу. Конкретно, из каждого значения канала вычитается среднее значение этого канала, а затем результат делится на стандартное отклонение этого канала. .. raw:: latex \diilbookstyleinputcell .. code:: python # Specify the means and standard deviations of the three RGB channels to # standardize each channel normalize = torchvision.transforms.Normalize( [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) train_augs = torchvision.transforms.Compose([ torchvision.transforms.RandomResizedCrop(224), torchvision.transforms.RandomHorizontalFlip(), torchvision.transforms.ToTensor(), normalize]) test_augs = torchvision.transforms.Compose([ torchvision.transforms.Resize([256, 256]), torchvision.transforms.CenterCrop(224), torchvision.transforms.ToTensor(), normalize]) Определение и инициализация модели ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ В качестве исходной модели мы используем ResNet-18, предварительно обученную на наборе данных ImageNet. Здесь мы указываем ``pretrained=True`` для автоматической загрузки предварительно обученных параметров модели. Если эта модель используется впервые, для загрузки требуется подключение к Интернету. .. raw:: latex \diilbookstyleinputcell .. code:: python pretrained_net = torchvision.models.resnet18(pretrained=True) Объект предобученной исходной модели содержит ряд слоёв признаков и выходной слой ``fc``. Основная цель такого разделения заключается в упрощении дообучения параметров всех слоёв, кроме выходного. Переменная-член ``fc`` исходной модели приведена ниже. .. raw:: latex \diilbookstyleinputcell .. code:: python pretrained_net.fc Как полносвязный слой, он преобразует выходные данные глобального усредняющего пулинга ResNet в 1000 выходов для классов набора данных ImageNet. Затем мы создаём новую нейронную сеть в качестве целевой модели. Она определяется так же, как и предварительно обученная исходная модель, за исключением того, что количество выходов в последнем слое устанавливается равным числу классов в целевом наборе данных (а не 1000). В приведённом ниже коде параметры модели до выходного слоя экземпляра целевой модели ``finetune_net`` инициализируются параметрами соответствующих слоёв исходной модели. Поскольку эти параметры были получены в результате предобучения на ImageNet, они уже эффективны. Следовательно, для *тонкой настройки* таких предобученных параметров достаточно использовать небольшую скорость обучения. Напротив, параметры выходного слоя инициализируются случайным образом, и, как правило, для их обучения с нуля требуется большая скорость обучения. Если базовая скорость обучения равна :math:`\eta`, то для итерации параметров выходного слоя будет использоваться скорость обучения :math:`10\eta`. .. raw:: latex \diilbookstyleinputcell .. code:: python finetune_net = torchvision.models.resnet18(pretrained=True) finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 2) nn.init.xavier_uniform_(finetune_net.fc.weight); Дообучение модели ~~~~~~~~~~~~~~~~~ Сначала мы определяем функцию обучения ``train_fine_tuning``, которая использует дообучение, чтобы её можно было вызывать несколько раз. .. raw:: latex \diilbookstyleinputcell .. code:: python # If `param_group=True`, the model parameters in the output layer will be # updated using a learning rate ten times greater def train_fine_tuning(net, learning_rate, batch_size=128, num_epochs=5, param_group=True): train_iter = torch.utils.data.DataLoader(torchvision.datasets.ImageFolder( os.path.join(data_dir, 'train'), transform=train_augs), batch_size=batch_size, shuffle=True) test_iter = torch.utils.data.DataLoader(torchvision.datasets.ImageFolder( os.path.join(data_dir, 'test'), transform=test_augs), batch_size=batch_size) devices = d2l.try_all_gpus() loss = nn.CrossEntropyLoss(reduction="none") if param_group: params_1x = [param for name, param in net.named_parameters() if name not in ["fc.weight", "fc.bias"]] trainer = torch.optim.SGD([{'params': params_1x}, {'params': net.fc.parameters(), 'lr': learning_rate * 10}], lr=learning_rate, weight_decay=0.001) else: trainer = torch.optim.SGD(net.parameters(), lr=learning_rate, weight_decay=0.001) d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices) Мы **устанавливаем базовую скорость обучения на небольшое значение**, чтобы *дообучить* параметры модели, полученные в ходе предобучения. Исходя из предыдущих настроек, мы будем обучать параметры выходного слоя целевой модели с нуля, используя скорость обучения, в десять раз большую. .. raw:: latex \diilbookstyleinputcell .. code:: python train_fine_tuning(finetune_net, 5e-5) Для сравнения, мы определяем идентичную модель, но инициализируем все её параметры случайными значениями. Поскольку всю модель необходимо обучать с нуля, мы можем использовать большую скорость обучения. .. raw:: latex \diilbookstyleinputcell .. code:: python scratch_net = torchvision.models.resnet18() scratch_net.fc = nn.Linear(scratch_net.fc.in_features, 2) train_fine_tuning(scratch_net, 5e-4, param_group=False) Как мы видим, дообученная модель демонстрирует лучшие результаты на той же эпохе, поскольку её начальные значения параметров оказываются более эффективными. Резюме ------ - Перенос обучения позволяет перенести знания, полученные на исходном наборе данных, на целевой набор данных. Дообучение — распространенный метод переноса обучения. - Целевая модель копирует все архитектуры моделей вместе с их параметрами из исходной модели, за исключением выходного слоя, и дообучает эти параметры на основе целевого набора данных. В отличие от этого, выходной слой целевой модели необходимо обучать с нуля. - Как правило, при дообучении параметров используется меньшая скорость обучения, а при обучении выходного слоя с нуля — более высокая скорость обучения. Упражнения ---------- 1. Продолжайте увеличивать скорость обучения ``finetune_net``. Как изменяется точность модели? 2. Дополнительно настройте гиперпараметры ``finetune_net`` и ``scratch_net`` в сравнительном эксперименте. Сохранится ли разница в точности? 3. Задайте параметры перед выходным слоем ``finetune_net`` такими же, как в исходной модели, и не обновляйте их в процессе обучения. Как изменится точность модели? Вы можете использовать следующий код. .. raw:: latex \diilbookstyleinputcell .. code:: python for param in finetune_net.parameters(): param.requires_grad = False 4. На самом деле в наборе данных ``ImageNet`` существует класс «hotdog». Соответствующий ему параметр веса в выходном слое можно получить с помощью следующего кода. Как можно использовать этот параметр веса? .. raw:: latex \diilbookstyleinputcell .. code:: python weight = pretrained_net.fc.weight hotdog_w = torch.split(weight.data, 1, dim=0)[934] hotdog_w.shape `Обсуждения `__