#hide
!pip install -Uqq fastbook
import fastbook
fastbook.setup_book()
#hide
from fastbook import *
Обучение современной модели
В этой главе представлены более сложные методы обучения модели классификации изображений и получения новейших результатов. Вы можете пропустить его, если хотите узнать больше о других разделах глубокого обучения и вернуться к нему позже - знание этого материала не предполагается в последующих главах.
Мы рассмотрим, что такое нормализация, мощный метод увеличения данных, называемый смешиванием(mixup), прогрессивный подход к изменению размера и увеличение времени тестирования. Чтобы показать все это, мы собираемся обучить модель с нуля (без использования трансферного обучения), используя подмножество ImageNet под названием Imagenette . Он содержит подмножество из 10 очень разных категорий из набора данных ImageNet, что ускоряет обучение, когда мы хотим поэкспериментировать.
Сделать это будет намного сложнее, потому что мы используем полноразмерные полноцветные изображения, которые представляют собой фотографии объектов разных размеров, в разной ориентации, при разном освещении и так далее. Итак, в этой главе мы собираемся представить некоторые важные методы для получения максимальной отдачи от вашего набора данных, особенно когда вы тренируетесь с нуля или используете трансферное обучение для обучения модели на совершенно ином типе набора данных, чем используемая предварительно обученная модель.
Imagenette
Когда fast.ai только начинал, было три основных набора данных, которые люди использовали для построения и тестирования моделей компьютерного зрения:
- ImageNet :: 1,3 миллиона изображений различных размеров размером около 500 пикселей в 1000 категорий, обучение которых заняло несколько дней.
- MNIST :: 50 000 рукописных цифр в оттенках серого 28 × 28 пикселей
- CIFAR10 :: 60 000 цветных изображений размером 32 × 32 пикселя в 10 классах
Проблема заключалась в том, что меньшие наборы данных фактически не были эффективно обобщены для большого набора данных ImageNet. Подходы, которые хорошо работали в ImageNet, нужно было разрабатывать и тренировать в ImageNet. Это привело к тому, что многие люди считали, что только исследователи, имеющие доступ к гигантским вычислительным ресурсам, могут эффективно способствовать разработке алгоритмов классификации изображений.
Нам показалось это маловероятно. Мы никогда не видели исследования, которое показало бы, что ImageNet имеет именно тот размер, и что другие наборы данных не могут быть использованны, чтобы дать полезную информацию. Поэтому мы подумали, что попытаемся создать новый набор данных, на котором исследователи могли бы быстро и дешево протестировать свои алгоритмы, но который также предоставил бы понимание как ImageNet.
Примерно через три часа мы создали Imagenette.Выбрали 10 классов из ImageNet, которые сильно отличались друг от друга.Мы надеялись,что смогли быстро и дешево создать классификатор, способный распознавать эти классы. Затем мы опробовали несколько алгоритмических настроек, чтобы увидеть, как они повлияли на Imagenette. Некоторые из них, работали очень хорошо. И протестировав их в ImageNet - и были рады обнаружить, что наши настройки хорошо работают и в ImageNet!
Здесь есть важное сообщение: набор данных, который вы получаете, не обязательно является набором данных,который вам нужен. Особенно маловероятно, что это будет набор данных, в котором вы хотите выполнить разработку и создание прототипов. Вы должны стремиться иметь скорость итерации не более пары минут - то есть, когда вы придумываете новую идею, которую хотите опробовать, вы должны иметь возможность тренировать модель и видеть, как она обучается в течение пары минут. Если для проведения эксперимента требуется больше времени, подумайте о том, как можно сократить набор данных или упростить модель, чтобы повысить скорость экспериментов. Чем больше экспериментов вы сможете сделать, тем лучше!
Начнем с этого набора данных:
from fastai.vision.all import *
path = untar_data(URLs.IMAGENETTE)
Сначала мы перенесем наш набор данных в DataLoaders объект, используя трюк предварительного изменения размера, представленный в < >:
dblock = DataBlock(blocks=(ImageBlock(), CategoryBlock()),
get_items=get_image_files,
get_y=parent_label,
item_tfms=Resize(460),
batch_tfms=aug_transforms(size=224, min_scale=0.75))
dls = dblock.dataloaders(path, bs=64)
и выполним тренировочный прогон, который будет служить основой:
model = xresnet50(n_out=dls.c)
learn = Learner(dls, model, loss_func=CrossEntropyLossFlat(), metrics=accuracy)
learn.fit_one_cycle(5, 3e-3)
Это хороший базовый уровень, так как мы не используем заранее подготовленную модель, но мы можем сделать лучше. При работе с моделями, которые обучаются с нуля или точно настроены на другой набор данных, чем тот, который используется для предварительного обучения, существуют некоторые дополнительные методы, которые действительно важны. В остальной части главы мы рассмотрим некоторые из ключевых подходов, с которыми вам нужно будет ознакомиться. Первый - нормализация данных.
Нормализация
При обучении модели это помогает, если входные данные нормализованы - то есть имеют среднее значение 0 и стандартное отклонение 1. Но большинство изображений и библиотек компьютерного зрения используют значения от 0 до 255 для пикселей или от 0 до 1; в любом случае данные не будут иметь среднее значение 0 и стандартное отклонение 1.
Давайте возьмем пакет наших данных и посмотрим на эти значения, усреднив по всем осям, кроме оси канала, которая является осью 1:
x,y = dls.one_batch()
x.mean(dim=[0,2,3]),x.std(dim=[0,2,3])
Как мы и ожидали, среднее и стандартное отклонения не очень близки к желаемым значениям. К счастью, нормализацию данных легко выполнить в fastai, добавив преобразование Нормализовать (Normalize transform). Это действует сразу на целый мини-пакет, поэтому его можно добавить в batch_tfms - раздел блока данных. Вам нужно передать этому преобразованию среднее значение и стандартное отклонение, которое вы хотите использовать; fastai поставляется с уже определенными стандартными средними и стандартными отклонениями ImageNet. (Если вы не передадите значение Нормализовать (Normalize transform), fastai автоматически сделает это.)
Давайте добавим это преобразование (используя imagenet_stats, поскольку Imagenette является подмножеством ImageNet) и рассмотрим один пакет:
def get_dls(bs, size):
dblock = DataBlock(blocks=(ImageBlock, CategoryBlock),
get_items=get_image_files,
get_y=parent_label,
item_tfms=Resize(460),
batch_tfms=[*aug_transforms(size=size, min_scale=0.75),
Normalize.from_stats(*imagenet_stats)])
return dblock.dataloaders(path, bs=bs)
dls = get_dls(64, 224)
x,y = dls.one_batch()
x.mean(dim=[0,2,3]),x.std(dim=[0,2,3])
Давайте проверим, как это повлияло на обучение нашей модели:
model = xresnet50(n_out=dls.c)
learn = Learner(dls, model, loss_func=CrossEntropyLossFlat(), metrics=accuracy)
learn.fit_one_cycle(5, 3e-3)
Хотя здесь это помогло лишь немного, нормализация становится особенно важной при использовании предварительно обученных моделей. Предварительно обученная модель знает, как работать с данными только того типа, который она видела раньше. Если среднее значение пикселя было 0 в данных, с которыми она была обучена, но ваши данные имеют 0 как минимально возможное значение пикселя, то модель будет видеть что-то очень отличное от того, что предполагается!
Это означает, что при распространении модели необходимо также передавать параметры, используемую для нормализации, поскольку любой, кто использует ее для вывода или переноса обучения, должен будет использовать те же параметры. Точно так же, если вы используете модель, которую обучил кто-то другой, убедитесь, что вы узнали, какую нормализации они использовали, и сопоставьте ее.
Нам не приходилось обрабатывать нормализацию в предыдущих главах, потому что при использовании предварительно обученной модели через cnn_learnerбиблиотека fastai автоматически добавляет соответствующее преобразование Нормализовать (Normalize); модель была предварительно обучена с определенными параметрами Normalize (обычно поступающей из набора данных ImageNet), поэтому библиотека может выполнить их за вас. Обратите внимание, что это относится только к предварительно обученным моделям, поэтому нам нужно добавить эту информацию здесь вручную при обучении с нуля.
Все наши тренировки до сих пор проводились с размером 224. Прежде чем начать тренировки,стоит уменьшить размер изображений. Это называется прогрессивным изменением размера .
Прогрессивное изменение размера
Когда в 2018 году fast.ai и его команда студентов победили в конкурсе DAWNBench , одним из самых важных нововведений было очень простое: начать обучение с использованием маленьких изображений и завершить обучение с использованием больших изображений. Проведение большей части эпох на тренировках с небольшими изображениями помогает тренировкам завершаться намного быстрее. Завершение обучения с использованием больших изображений значительно повышает конечную точность. Мы называем этот подход прогрессивным изменением размера .
жаргон: прогрессивное изменение размера: постепенно используйте все большие изображения во время тренировки.
Как мы уже видели, типы признаков, которые изучаются сверточными нейронными сетями, никоим образом не зависят от размера изображения—ранние слои находят такие вещи, как края и градиенты, а более поздние слои могут находить такие вещи, как носы и закаты. Поэтому, когда мы меняем размер изображения в середине обучения, это не означает, что мы должны найти совершенно другие параметры для нашей модели.
Но очевидно, что есть некоторые различия между маленькими изображениями и большими, поэтому мы не должны ожидать, что наша модель будет продолжать работать точно так же, без каких-либо изменений. Это вам что-то напоминает? Когда мы разработали эту идею, она напомнила нам о трансферном обучении! Мы пытаемся заставить нашу модель научиться делать что-то немного отличное от того, что она научилась делать раньше. Поэтому мы должны иметь возможность использовать метод fine_tuneпосле изменения размера наших изображений.
У прогрессивного изменения размера есть еще одно преимущество: это еще одна форма увеличения данных. Поэтому вы должны ожидать лучшего обобщения ваших моделей, которые обучаются с прогрессивным изменением размера.
Для реализации прогрессивного изменения размера наиболее удобно, если вы сначала создадите функцию get_dls, которая принимает размер изображения и размер пакета, как мы это делали в предыдущем разделе, и возвращает ваши загрузчики данных:
Теперь вы можете создать DataLoaders с небольшим размером и использовать fit_one_cycle обычным способом, обучение на несколько меньше эпох, чем вы могли бы сделать в противном случае:
dls = get_dls(128, 128)
learn = Learner(dls, xresnet50(n_out=dls.c), loss_func=CrossEntropyLossFlat(),
metrics=accuracy)
learn.fit_one_cycle(4, 3e-3)
Затем можно заменить DataLoaders внутри Learner и выполнить точную настройку:
learn.dls = get_dls(64, 224)
learn.fine_tune(5, 1e-3)
Как видите, мы получаем намного лучшую производительность, и начальное обучение на маленьких изображениях происходило намного быстрее в каждую эпоху.
Вы можете повторять процесс увеличения размера и обучать больше эпох столько раз, сколько захотите, для изображения любого размера, но, конечно, вы не получите никакой пользы, используя размер изображения больше, чем размер ваших изображений. на диске.
Обратите внимание, что в трансферном обучении, прогрессивное изменение размера может на самом деле снизить производительность. Это наиболее вероятно, если ваша предварительно обученная модель была очень похожа на вашу и была обучена на изображениях аналогичного размера, поэтому веса не нужно сильно менять. В этом случае тренировка с меньшими изображениями может повредить предварительно натренированные веса.
С другой стороны, если задача трансферного обучения будет использовать изображения, которые имеют разные размеры, формы или стили, чем те, которые используются в задаче предварительного обучения, прогрессивное изменение размера, вероятно, поможет. Как всегда, ответ на вопрос "Поможет?" это "Попробуйте!"
Еще мы могли бы попробовать применить увеличение данных к проверочному набору. До сих пор мы применяли его только к обучающей выборке.Проверочный набор всегда получает одни и те же изображения. Но, мы могли сделать прогнозы для нескольких расширенных версий проверочного набора и усреднить их. Мы рассмотрим этот подход позже.
Увеличение времени тестирования
Мы используем случайное обрезание как способ увеличения данных, что приводит к лучшему обобщению и как следствие, меньшего количества обучающих данных. Когда мы используем случайную обрезку, fastai автоматически использует центральную обрезку для проверочного набора - то есть он выберет наибольшую квадратную площадь, которая может быть в центре изображения, не выходя за края изображения.
Это часто может быть проблематичным. Например, в наборе данных с несколькими метками иногда есть небольшие объекты по краям изображения; они могут быть полностью обрезаны центральным кадрированием. Даже для таких задач, как наш пример классификации пород домашних животных, возможно, что некоторые важные особенности, необходимые для определения правильной породы, такие как цвет носа, могут быть вырезаны.
Одним из решений этой проблемы является полное исключение случайного обрезания. Вместо этого мы могли бы просто сдвинуть или растянуть прямоугольные изображения, чтобы поместиться в квадратное пространство.Но тогда мы упускаем полезное дополнение данных, и усложняем распознавание изображений для нашей модели, потому что она должна научиться распознавать сдавленные и сжатые изображения, а не пропорциональные изображения.
Другое решение - не просто центрировать кадрирование, а вместо этого выбрать несколько областей для кадрирования из исходного прямоугольного изображения, пропустить каждую из них через нашу модель и взять максимум или среднее значение из прогнозов. Фактически, мы могли бы сделать это для разных значений во всех наших параметрах. Это известно как увеличение времени тестирования (TTA).
жаргон: увеличение времени тестирования (TTA): Во время вывода или проверки создается несколько версий каждого изображения, используя увеличение данных.Затем берется среднее или максимальное значение прогнозов для каждой расширенной версии изображения.
В зависимости от набора данных увеличение времени тестирования может привести к резкому повышению точности. Это не изменяет время, необходимое для обучения, но увеличивает время, необходимое для проверки или вывода, на количество запрошенных изображений с увеличенным временем тестирования. По умолчанию fastai использует неаугментированное кадрирование центра изображения, плюс четыре случайно дополненных изображения.
Вы можете передать любой tta метод DataLoader, по умолчанию используется проверочный набор:
preds,targs = learn.tta()
accuracy(preds, targs).item()
Как мы видим, использование TTA дает нам хороший прирост производительности без необходимости в дополнительном обучении. Однако это делает вывод медленнее - если вы усредняете пять изображений для TTA, вывод будет в пять раз медленнее.
Мы видели примеры того, как увеличение данных помогает обучать лучшие модели. Давайте теперь сосредоточимся на новой технике увеличения данных, называемой Mixup .
Смешивание
Mixup, представленный в статье 2017 года « Mixup : Beyond Empirical Risk Minimization»от Hongyi Zhang et al., представляет собой очень мощный метод увеличения данных, который может обеспечить значительно более высокую точность, особенно когда у вас мало данных и нет предварительно обученной модели на данных, аналогичных вашему набору. В документе поясняется: «Хотя увеличение данных последовательно ведет к улучшенному обобщению, процедура зависит от набора данных и, следовательно, требует использования экспертных знаний». Например, обычно переворачивают изображения как часть увеличения данных, но следует ли переворачивать только горизонтально или также вертикально? Ответ в том, что это зависит от вашего набора данных. Кроме того, если переворачивание (например) не обеспечивает достаточного увеличения данных для вас, вы не можете «перевернуть больше». Полезно иметь методы увеличения данных, где вы можете "прибавить" или "убрать" количество изменений, чтобы увидеть, что работает лучше для вас.
Mixup работает следующим образом для каждого изображения:
- Выберите случайным образом ещё одно изображение из набора данных.
- Выберите вес наугад.
- Возьмите средневзвешенное значение (используя вес из шага 2) выбранного изображения с вашим изображением; это будет ваша независимая переменная.
- Возьмите средневзвешенное значение (с тем же весом) меток этого изображения с метками вашего изображения; это будет ваша зависимая переменная.
В псевдокоде мы делаем это (где t - вес для нашего средневзвешенного значения):
image2,target2 = dataset[randint(0,len(dataset)]
t = random_float(0.5,1.0)
new_image = t * image1 + (1-t) * image2
new_target = t * target1 + (1-t) * target2
Чтобы это работало, наши цели должны быть закодированы в one-hot encoded. В статье это описывается с помощью уравнений, показанных в < > где λ является таким же, как t в нашем псевдокоде:
Боковая панель: документы и математика
С этого момента в книге мы будем просматривать все больше и больше исследовательских работ. Теперь, когда у вас есть базовый жаргон, вы можете быть удивлены, обнаружив, сколько из них вы можете понять, немного попрактиковавшись! Вы заметите одну проблему: греческие буквы, такие как λ, появляются в большинстве статей. Очень хорошая идея - выучить названия всех греческих букв, поскольку в противном случае очень трудно прочесть документы и запомнить их (или прочитать код, основанный на них, поскольку код часто использует имена написанных из греческих букв, например lambda ).
Большая проблема со статьями заключается в том, что они используют математику, а не код, чтобы объяснить, что происходит. Если вы не очень хорошо разбираетесь в математике, то поначалу это будет пугать и сбивать с толку. Но помните: то, что показано в математике, - это то, что будет реализовано в коде. Это просто другой способ говорить об одном и том же! Прочитав несколько статей, вы поймете все больше и больше обозначений. Если вы не знаете, что такое символ, попробуйте найти его в списке математических символов Википедии или нарисовать его в Detexify, который (используя машинное обучение!) найдет имя вашего нарисованного от руки символа. Затем вы можете поискать это имя в Интернете, чтобы узнать, для чего оно предназначено.
Конечная боковая панель
< > показывает, как это выглядит, когда мы берем линейную комбинацию изображений, как это сделано в Mixup.
#hide_input
#id mixup_example
#caption Mixing a church and a gas station
#alt An image of a church, a gas station and the two mixed up.
church = PILImage.create(get_image_files_sorted(path/'train'/'n03028079')[0])
gas = PILImage.create(get_image_files_sorted(path/'train'/'n03425413')[0])
church = church.resize((256,256))
gas = gas.resize((256,256))
tchurch = tensor(church).float() / 255.
tgas = tensor(gas).float() / 255.
_,axs = plt.subplots(1, 3, figsize=(12,4))
show_image(tchurch, ax=axs[0]);
show_image(tgas, ax=axs[1]);
show_image((0.3*tchurch + 0.7*tgas), ax=axs[2]);
Третье изображение строится путем сложения 0,3 раза первого и 0,7 раза второго. В этом примере должна ли модель предсказывать "церковь" или "заправочную станцию"? Правильный ответ-30% церкви и 70% бензоколонки, так как именно это мы получим, если возьмем линейную комбинацию целей с одним горячим кодированием (one-hot-encoded). Например, предположим, что у нас есть 10 классов, и "церковь" представлена индексом 2, а "заправочная станция" представлена индексом 7, one-hot-encoded представления: [0, 0, 1, 0, 0, 0, 0, 0, 0, 0] and [0, 0, 0, 0, 0, 0, 0, 1, 0, 0] итак, наша конечная цель: [0, 0, 0.3, 0, 0, 0, 0, 0.7, 0, 0]
Все это сделано внутри fastai, добавив обратный вызов(callback) в наш Learner. Callbacks - это то, что используется внутри fastai для внедрения пользовательского поведения в цикл обучения (например, расписание скорости обучения или обучение со смешанной точностью). Мы узнаем все об обратных вызовах, в том числе о том, как сделать свои собственные, в < >. На данный момент все, что вам нужно знать, это то, что вы используете параметр cbs для передачи обратных вызовов Learner .
Вот как мы обучаем модель с помощью Mixup:
model = xresnet50(n_out=dls.c)
learn = Learner(dls, model, loss_func=CrossEntropyLossFlat(),
metrics=accuracy, cbs=MixUp())
learn.fit_one_cycle(5, 3e-3)
Что происходит, когда мы обучаем модель с данными, которые "смешаны" таким образом? Понятно, что тренироваться будет труднее, потому что сложнее увидеть, что на каждом изображении. И модель должна предсказывать две метки для каждого изображения, а не только одну, а также выяснять, насколько каждая из них взвешена. Однако переоснащение кажется менее вероятным, потому что мы не показываем одно и то же изображение в каждую эпоху, а вместо этого показываем случайную комбинацию двух изображений.
Смешивание требует гораздо большего количества эпох для тренировки, чтобы получить лучшую точность, по сравнению с другими подходами к увеличению, которые мы видели. Вы можете попробовать обучить Imagenette с использованием Mixup и без него, используя сценарий examples / train_imagenette.py в репозитории fastai . На момент написания, таблица лидеров в репозитории Imagenette показывает, что Mixup используется для всех ведущих результатов тренировок более 80 эпох, а для меньшего количества эпох Mixup не используется. Это также соответствует нашему опыту использования Mixup.
Одна из причин, по которой Mixup так интересен, заключается в том, что его можно применять не только к фотографиям, но и к другим типам данных. Фактически, некоторые люди даже показали хорошие результаты, используя Mixup для активации внутри своих моделей, а не только для входных данных - это позволяет использовать Mixup для NLP и других типов данных.
Есть еще одна тонкая проблема, которую Mixup решает для нас: на самом деле невозможно с моделями, которые мы видели раньше, чтобы наша потеря когда-либо была идеальной. Проблема в том, что наши метки - это единицы и нули, но выходы softmax и sigmoid никогда не могут равняться 1 или 0. Это означает, что обучение нашей модели приближает наши активации к этим значениям, так что чем больше эпох мы делаем, тем более экстремальными наши активации становятся.
С Mixup у нас больше нет этой проблемы, потому что наши метки будут ровно 1 или 0, только если мы «смешаемся» с другим изображением того же класса. В остальное время наши метки будут представлять собой линейную комбинацию, такую как 0,7 и 0,3, которые мы получили ранее в примере с церковью и заправочной станцией.
Однако одна проблема с этим состоит в том, что Mixup «случайно» делает метки больше 0 или меньше 1. То есть мы явно не говорим нашей модели, что хотим изменить метки таким образом. Итак, если мы хотим сделать метки ближе или дальше от 0 и 1, мы должны изменить количество Mixup, что также изменит объем увеличения данных, что может быть не тем, что нам нужно. Однако есть способ справиться с этим более прямым образом - использовать сглаживание меток .
Сглаживание меток
Раскрыть комментарии 0
Чтобы оставить комментарий , Вам необходимо Авторизоваться или пройти Регистрацию