Вступление

В прошлой статье мы рассмотрели, как некоторые из самых известных классификационных сетей оцениваются в ImageNet. Мы также закончили создание класса набора оценочных данных PyTorch, а также эффективных функций оценки. Скоро мы увидим, как они пригодятся при проверке структур наших моделей и обучении.

В этой статье мы построим модели AlexNet и VGG. Несмотря на их значительный вклад в компьютерное зрение и глубокое обучение, в ретроспективе их структуры очевидны. Поэтому, помимо их построения, мы также поиграем с «переносом веса» и реализацией скользящего окна со сверточными слоями.

Я лично считаю заимствование гирь из других моделей простой, но полезной техникой для практики. Помимо использования его для двойной проверки структуры нашей модели, его также можно использовать для переноса весов модели между разными фреймворками, а также для инициализации основы детектора или модифицированной сети путем изменения весов (мы попробуем это для наших санитарная проверка).

Круто, так что приступим.

Overveiw

  • Объяснение структуры AlexNet и семейства VGG
  • Создание собственных библиотечных модулей для них
  • Реализация скользящего окна со сверткой
  • Замена полностью связанной головки классификатора на сверточную головку классификатора для «плотной оценки»
  • Обсуждение инициализации веса
  • Проверка работоспособности путем переноса весов из предварительно обученных моделей

AlexNet

AlexNet часто считают моделью, ознаменовавшей начало современной эры глубокого обучения. Он стал победителем ImageNet 2012 с точностью 15.3% для топ-5, опередив занявшего второе место года на колоссальные 10,9%.

Он имеет 60 миллионов параметров и, учитывая ограниченную память графических процессоров почти 10 лет назад, AlexNet пришлось разделить и обучить на двух графических процессорах GTX 580 3 ГБ. По этой причине расшифровка точной структуры AlexNet для одного GPU из оригинальной статьи может вызвать затруднения. Фактически, официальная реализация AlexNet в PyTorch основана на ссылке из этой статьи (см. Сноску 1 на странице 5), хотя реализация PyTorch все еще отличается от статьи тем, что в 4-м сверточном слое используется 256 ядер вместо 384, описанных в документе. (ах!).

Мы также проигнорируем функцию локальной нормализации ответа (LRN) сети. Хотя в документе говорится, что LRN сокращает top1 и top5 на незначительную величину (1,4% и 1,2%), в настоящее время он не часто используется, поскольку его влияние незначительно для большинства сетей. Пакетная нормализация - это схема по умолчанию, которая применяется, если мы хотим, чтобы модель лучше обучалась. Мы добавим это для наших сетей VGG.

В этой статье мы будем следовать структуре AlexNet PyTorch, чтобы можно было использовать ее предварительно обученные веса. Структура, которую мы будем использовать, проиллюстрирована на следующем рисунке. Пространственный размер карт объектов после свертки / максимального объединения можно вычислить как floor(W - F + 2P) / S) + 1, где W - ширина / высота текущей карты объектов, F - размер фильтра, P - размер отступа и S - шаг. floor() означает, что мы округляем до ближайшего целого числа. «Толщина» или «глубина» выходных карт функций зависит от количества ядер (фильтров), которые мы используем на текущем этапе.

С библиотекой PyTorch эту структуру AlexNet довольно легко реализовать. В методе __init__() мы можем сначала проигнорировать аргумент head, который определяет, какой тип заголовка классификации мы хотим использовать.

Как мы видим в коде, и вы, возможно, уже знали, что одно прекрасное свойство сверточных слоев заключается в том, что они не заботятся о вашем размере ввода. При любом размере ввода он сгенерирует соответствующий размер вывода в соответствии с приведенной выше формулой. Однако, если мы добавим сверточные слои к полностью связанным слоям, тогда нам нужно будет учесть все элементы окончательной карты объектов. В AlexNet уплощенная карта функций должна быть вектором с размерностью 9216, прежде чем переходить в полностью связанный классификатор.

Есть два способа удовлетворить это ограничение. Мы можем либо использовать операцию изменения размера, чтобы преобразовать входные изображения в размер 224 x 224, либо мы можем изменить размер окончательного объекта, чтобы он стал 256 x 6 x 6, прежде чем помещать его в полностью связанные слои. Второе решение может быть выполнено с nn.AdaptiveAvgPool2d(). Это что-то вроде интерполяции. Хорошее объяснение можно найти здесь. Обладая этими знаниями, мы можем построить наш полностью связанный классификатор.

Реализация раздвижных окон ConvNet

Полносвязные слои в нейронных сетях раздражают при решении задач компьютерного зрения. Он имеет слишком много параметров и требует строгих требований к входным размерам. Итак, вопрос в том, можем ли мы обойтись без этого? К счастью, да. Во многих более поздних сетях полносвязные уровни заменены простым средним пулом. В нашем случае, если мы хотим оставаться верными сетевой структуре, мы можем заменить ее эквивалентной сверточной реализацией, избавившись от необходимости обеспечивать фиксированный размер входных данных.

Как и на приведенной выше диаграмме, окончательная карта объектов размером 256 x 6 x 6 находится слева. Его можно развернуть до 9216-мерного вектора признаков и пропустить через полностью связанный слой из 4096 единиц (вверху справа). Для каждого объекта (зеленая точка) имеется 9216 соединений, связывающих каждый элемент на карте функций. Он создаст вектор признаков 4096 x 1. Таким образом, каждый блок FC представляет собой ядро ​​размером 256 x 6 x 6 (внизу справа). Таким образом, мы можем заменить слой FC из 4096 единиц сверточным слоем из 4096 256 x 6 x 6 ядер. Вместо этого будет создана карта объектов размером 4096 x 1 x 1. Очевидно, что, за исключением дополнительного измерения, два выхода эквивалентны.

С помощью этого вывода мы можем реализовать версию заголовка классификации ConvNet. Продолжая предыдущий фрагмент кода, мы имеем:

На первый взгляд это преобразование кажется избыточным. Однако помните одно прекрасное свойство ConvNet, о котором мы упоминали несколько минут назад? Теперь мы можем позволить входным данным иметь любые размеры, если они больше 224 x 224.

Например, если у нас есть входное изображение размером от 287 x 287 до 318 x 318, мы получим окончательную карту объектов размером 256 x 8 x 8. Поскольку наши полностью связанные слои могут принимать только плоский вектор объектов. что соответствует карте функций 256 x 6 x 6, мы должны применить nn.AdaptiveAvgPool2d(). В качестве альтернативы, мы можем попробовать плотную оценку, то есть разместить скользящие окна по карте объектов и получить 9 кадров карты объектов 256 x 6 x 6 и поместить их в слои FC один за другим, чтобы сгенерировать окончательный результат. выходы (рисунок внизу, вверху). Затем результаты можно усреднить.

Однако, если мы используем реализацию уровней FC в ConvNet, мы, естественно, используем скользящие окна (рисунок вверху, внизу). Выходную карту функций можно усреднить по пространственному измерению, чтобы получить окончательный результат.

С реализацией заголовка классификации ConvNet теперь мы можем снисходительно относиться к размеру нашего входного изображения.

Напишем последний кусок кода, чтобы завершить реализацию AlexNet. forward() метод класса:

В том же скрипте мы можем определить функцию построителя, которая помогает нам сгенерировать указанный AlexNet. Когда у нас есть готовые предварительно обученные веса, мы можем загрузить веса в сеть здесь.

На этом мы закончили нашу модель AlexNet. Теперь мы можем перейти к проверке работоспособности с переносом веса.

Проверка работоспособности с переносом веса

Мы собираемся запустить предварительно обученный AlexNet из torchvision и скопировать его вес в нашу модель. В этом разделе мы рассмотрим, как индексируются веса и как изменять веса для нашей реализации классификатора FC в ConvNet.

Сначала мы запускаем все три сети:

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

Torch name: features.0.weight         torch.Size([64, 3, 11, 11])
FC name   : features.0.weight         torch.Size([64, 3, 11, 11])
Conv name : features.0.weight         torch.Size([64, 3, 11, 11])
Torch name: features.0.bias           torch.Size([64])
FC name   : features.0.bias           torch.Size([64])
Conv name : features.0.bias           torch.Size([64])
Torch name: features.3.weight         torch.Size([192, 64, 5, 5])
FC name   : features.3.weight         torch.Size([192, 64, 5, 5])
Conv name : features.3.weight         torch.Size([192, 64, 5, 5])
...
Torch name: classifier.0.weight       torch.Size([4096, 25088])
FC name   : classifier.2.weight       torch.Size([4096, 25088])
Conv name : classifier.0.weight       torch.Size([4096, 512, 7, 7])
Torch name: classifier.0.bias         torch.Size([4096])
FC name   : classifier.2.bias         torch.Size([4096])
Conv name : classifier.0.bias         torch.Size([4096])
Torch name: classifier.3.weight       torch.Size([4096, 4096])
FC name   : classifier.5.weight       torch.Size([4096, 4096])
Conv name : classifier.3.weight       torch.Size([4096, 4096, 1, 1])
...

Чтобы передать веса из предварительно обученного AlexNet PyTorch в нашу AlexNet с классификационной головкой FC, мы можем создать OrderedDict () и сохранить предварительно обученные веса AlexNet с именем параметра нашей модели. Затем мы загрузим этот OrderedDict в нашу модель.

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

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

Предварительно обученные AlexNet torchvision и наш AlexNet имеют одинаковую точность, что говорит о том, что мы все сделали правильно.

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

Затем мы можем передать обернутую модель нашей функции оценки, чтобы получить результат.

Наконец, мы можем протестировать «плотную оценку», передав изображения размером более 224 x 224 и усредняя результат для прогнозирования.

Как видно, оценка плотности примерно на 1,3% точнее оценки центральной культуры. Тем не менее, поскольку большая часть вычислений экстрактора сверточных признаков выполняется совместно, время оценки не слишком велико.

Ура! теперь у нас есть собственный улучшенный AlexNet.

VGG

Когда я впервые изучал нейронные сети, я был ошеломлен огромными комбинаторными возможностями сетевой структуры. Насколько глубоко мне нужно идти? Какие бы размеры ядра были? Сколько ядер я должен использовать? Какими должны быть шаги?… AlexNet не помог с моими вопросами. В AlexNet не так уж много закономерностей, которым мы могли бы следовать. Тогда на помощь приходят сети VGG!

Семейство VGG является важной вехой в области глубокого обучения не только потому, что оно заняло второе место в конкурсе ImageNet в 2014 году (с впечатляющим уровнем ошибок в пятерке первых 5% в 7,3%), но и помогло стандартизировать структуру сетей.

Паттерны в VGG

  1. Используйте сверточные ядра 3 x 3 в сети. Два ядра 3 x 3, сложенные вместе, имеют рецептивное поле 5 x 5 (т. е. один элемент на его выходной карте функций получен из области 5 x 5 на входное изображение). Три из них, сложенные вместе, будут иметь рецептивное поле 7 x 7. Если мы используем стек из трех ядер 3 x 3 вместо ядра 7 x 7, у нас не только будет такое же рецептивное поле, мы можем наполнить его в три раза большей нелинейностью (с ReLU), а также использовать намного меньше параметров (3x (3x3xCxC) против 7 x 7 x C x C, при условии, что входная карта и выходная карта имеют C-каналы).
  2. Используйте только max-pooling размера 2 и шага 2 для субдискретизации карты объектов. В отличие от AlexNet, в котором некоторые сверточные слои также понижают дискретизацию карты признаков, VGG только понижает дискретизацию карты признаков с максимальным объединением. Это означает, что все сверточные слои имеют шаг 1.
  3. Удваивайте каналы после каждого понижения дискретизации. Поскольку ширина и высота карты объектов уменьшаются вдвое, ее каналы удваиваются, и на каждом сверточном слое применяется в два раза больше ядер.

Теперь у нас есть роскошь ограниченного выбора при построении наших сетей. Фактически, многие более поздние сети также принимают эти шаблоны в своей структуре. В настоящее время мы редко видим использование больших ядер, и эвристика только удвоения каналов после понижающей дискретизации дает начало концепции «модуля», набора сверточных слоев с одинаковым количеством ядер.

С помощью описанной выше эвристики структуру всего семейства VGG можно резюмировать в одной таблице, как в статье.

Благодаря такому структурированному дизайну мы можем легко кодировать все сети в семействе VGG.

Код здесь сильно отсылает к официальному коду моделей VGG torchvision. Я добавил ценность (надеюсь) подробными замечаниями.

Сначала мы определим структуру сетей VGG. По сути, это помещает приведенную выше таблицу в словарь.

Затем мы можем определить __init__() и forward() методы класса:

Как и наш AlexNet, мы также реализуем голову сверточной классификации для VGG (в конце концов, «плотная оценка» происходит из статьи VGG). Аргумент bn (пакетная нормализация) определяет, будем ли мы включать пакетную нормализацию в нашу сеть. Пакетная нормализация - это метод, появившийся после VGG, мы модернизируем его в VGG. Это увеличивает как точность, так и скорость обучения.

Теперь перейдем к методу _get_conv_layers().

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

Код классификатора тоже очень прост. Следует отметить, что выпадение, в отличие от пакетной нормализации, обычно добавляется после активации до свертки.

Инициализация веса

Мы почти закончили. Однако, в отличие от AlexNet, веса которого могут быть более небрежно инициализированы нормальным распределением с нулевым значением со стандартным отклонением 0,01, следует соблюдать осторожность при инициализации весов сетей VGG, поскольку они намного глубже и не сходятся легко. Фактически, для соревнований ImageNet авторы VGG сначала обучили более мелкие версии сети, а затем медленно добавили больше слоев, чтобы сделать ее глубже.

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

Однако есть несколько непонятных решений. Во-первых, следует ли для каждого типа инициализации использовать гауссово распределение или равномерное распределение? В этом обсуждении stackexchange упоминается, что для инициализации Xavier равномерное распределение кажется немного лучше, в то время как для инициализации Kaiming распределение Гаусса используется для всех слоев в исходной статье ResNet, поэтому я думаю, мы можем пойти с нормальным распределением Kaiming и равномерным распределением Xavier. .

Второй вопрос заключается в том, что для инициализации Kaiming есть два режима разветвление и разветвление, какой из них использовать? В этом Обсуждении форума PyTorch говорится, что режим fan-in должен быть режимом по умолчанию. Звучит хорошо, за исключением случаев, упомянутых в обсуждении на форуме, ResNet torchvision, а также VGG использовали разветвление. Я довольно много поискал в Интернете, но не смог найти объяснения этому выбору.

В этом скрипте я решил использовать режим «fan-in», поэтому код выглядит так:

Как и в AlexNet, мы можем написать некоторые функции-конструкторы. Ниже приведены два примера:

На этом мы завершили реализацию семейства VGG.

Престижность!

Санитарная проверка

Проверка работоспособности для VGG такая же, как и та, которую мы написали для AlexNet выше. Как и в случае с AlexNet, «плотная оценка» дает более высокие результаты.

Заполненные коды моделей можно найти в этом репозитории.

Заключение

В этой статье мы реализовали AlexNet и семейство VGG. Сами сети несложно реализовать, но идея использования сверточных слоев для реализации скользящих окон, а также инициализации и переноса весов может быть непростой для понимания.

В следующей статье мы напишем сценарий обучения. Мы обсудим увеличение обучающих данных, параллелизм данных PyTorch и параллелизм распределенных данных.