Одна из самых впечатляющих вещей, которые я видел, - это приложение глубокого обучения с подписью к изображениям. Я хотел реализовать его сам с нуля, чтобы глубже погрузиться в детали архитектуры. Здесь я пытаюсь описать общий алгоритм автоматической подписи к изображениям и построить архитектуру, используя мою любимую библиотеку глубокого обучения - PyTorch.

Описание проблемы.

Сначала нам нужно сформулировать проблему, чтобы ее решить. Утверждение довольно простое:

Учитывая изображение, мы хотим получить предложение, описывающее, из чего оно состоит.

Теперь, когда мы определили, что мы хотим, чтобы наша модель делала, мы можем видеть, что наша модель должна принимать пакет изображений в качестве входных данных и создавать группу предложений в качестве выходных данных. Нейронные сети - идеальное семейство машинного обучения для выполнения такой задачи.

Набор данных.

Я собираюсь использовать набор данных COCO (Общие объекты в контексте) для обучения модели. COCO - это обычно используемый набор данных для таких задач, поскольку одна из целевых групп COCO - это подписи. Каждое изображение имеет 5 разных подписей, созданных разными людьми, поэтому каждая подпись немного (иногда сильно) отличается от других подписей для того же изображения. Вот пример точки данных из набора данных COCO:

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

Определение модели.

Итак, мы установили, что на выходе будут изображения, а на выходе - предложения. Мы можем думать о предложениях как о последовательностях слов. К счастью, последовательные модели могут помочь нам обрабатывать последовательности слов (или символов, или других последовательных данных, таких как временные ряды).

Следовательно, мы можем отобразить наши данные из пространства изображений в какое-то скрытое пространство, а затем отобразить это скрытое пространство в пространство предложений.

Вот обзор архитектуры, которую мы будем использовать для построения нашей модели подписей:

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

Кодировщик.

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

Для этой конкретной модели я использовал в кодере архитектуру DenseNet121. Это относительно легкая и очень хорошо работающая архитектура для приложений компьютерного зрения; однако вы можете использовать любую другую сверточную архитектуру для сопоставления изображений со скрытым пространством. Плотная сеть, которую я использовал, не была предварительно обучена, чтобы избежать переключения между распределениями, генерирующими данные.

Мы можем легко импортировать модель в PyTorch, используя пакет моделей torchvision. Несмотря на то, что импортированная плотная сеть не была предварительно обучена, она по-прежнему поставляется с классификатором для набора данных ImageNet, который имеет 1000 классов. К счастью, заменить классификатор в PyTorch очень просто. Я заменил классификатор сети на двухуровневый персептрон с функцией активации параметрического ReLU и применил dropout для уменьшения переобучения. Вы можете найти реализацию кодировщика в PyTorch ниже:

Одна деталь, на которую следует обратить внимание, - это размерность выходного сигнала сети кодировщика. Обратите внимание, что сеть приводит к 1024-мерному вектору в скрытом пространстве, который мы будем вводить в качестве первого входа в нашу модель LSTM (в момент времени t = 0).

Декодер.

Ячейка LSTM.

Хорошо, до сих пор все довольно просто: у нас есть изображение, и мы пропускаем его через слегка модифицированную плотно связанную нейронную сеть, чтобы получить 1024-мерный выходной вектор. Декодер в архитектуре - это то место, где все может стать беспорядочным и сложным для отладки и понимания, но давайте попробуем.

Как я показал в обзоре архитектуры, декодер состоит из рекуррентной нейронной сети. Мы могли бы использовать Gated Recurrent Unit или Long Short-Term Memory, в нашем конкретном случае я использовал последний. Крайне важно отметить, что до сих пор ведется много споров, какая рекуррентная ячейка лучше GRU или LSTM. Оба показали, что они очень хорошо работают во многих приложениях со смешанным приростом производительности. Различия GRU и LSTM выходят за рамки этой статьи, но эта статья подводит итог некоторым эмпирическим результатам.

Хорошо, снова на ходу. Ячейка LSTM имеет долгосрочную и краткосрочную память (да), как следует из названия. Вот анатомия высокого уровня клетки LSTM:

  • Ячейка LSTM имеет вход для точки данных (в момент времени t = n)
  • Ячейка LSTM имеет вход для состояния ячейки (предыдущее состояние ячейки)
  • Ячейка LSTM имеет вход для скрытого состояния (предыдущее скрытое состояние)
  • Ячейка LSTM имеет вывод для состояния ячейки (текущее состояние ячейки)
  • Ячейка LSTM имеет вывод для скрытого состояния (текущего скрытого состояния)
  • Выходные данные ячейки LSTM - это выходные данные скрытого состояния ячейки LSTM.

Я не собираюсь бросать вам в лицо кучу математических уравнений, поскольку они не важны для задач реализации, но если вас интересует подробная анатомия ячейки LSTM, обратитесь к исходной статье. Что более важно для реализации, так это то, что для каждого шага в последовательности мы используем одну и ту же ячейку LSTM (или GRU), поэтому цель оптимизируемой ячейки - найти правильный набор весов для размещения всего словаря слов. (символы в моделях char-to-char). Это означает, что для каждого слова в нашем предложении (которое является последовательностью) мы собираемся использовать это слово в качестве входных данных и получить некоторый результат, который обычно представляет собой распределение вероятностей по всему словарю слов. Таким образом мы можем получить слово, которое, по мнению модели, наиболее соответствует предыдущему слову.

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

Словарь / словарный запас.

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

Теперь мы знаем, что существует ограниченное количество значимых слов, которые наше распределение, генерирующее данные, может генерировать как часть целевых предложений. Итак, что мы хотим сделать, так это взять каждое слово, которое присутствует во всех заголовках в нашем наборе обучающих данных, и пронумеровать его, чтобы получить отображение между словами и целыми числами. Мы на полпути к использованию слов в нашем декодере. Теперь мы можем построить сопоставление 1 к K (обычно с использованием перцептрона одного слоя) между целочисленными представлениями слов в K-мерное пространство, которое мы можем использовать в качестве входных данных для нашей ячейки LSTM. Однако есть способ сделать это лучше; мы можем получить вложения целочисленных представлений, используя встроенный слой встраивания в PyTorch. Пожалуйста, обратитесь к этому замечательному руководству по PyTorch для получения более подробной информации.

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

Teacher Forcer.

Вот типичный сценарий использования повторяющихся сетей:

  • Подайте токен ‹start› (начало предложения или слова) в ячейку LSTM в качестве входных данных в момент времени t = 0.
  • Получите вектор размера словаря на выходе LSTM в момент времени t = 0.
  • Найдите индекс наиболее вероятного символа (слова) в момент времени t = 0, используя argmax.
  • Вставьте наиболее вероятный символ (слово).
  • Передайте полученное вложение в качестве входных данных в ячейку LSTM в момент времени t = 1.
  • Повторяйте до тех пор, пока на выходе ячейки не будет получен токен ‹end›.

Подводя итог вышеупомянутому алгоритму, мы передаем наиболее вероятное слово или символ в качестве входных данных в ячейку LSTM на следующем временном шаге и повторяем процедуру.

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

Алгоритм подготовки учителей можно резюмировать следующим образом:

  • Подайте токен ‹start› (начало предложения или слова) в ячейку LSMT в качестве входных данных в момент времени t = 0.
  • Найдите индекс наиболее вероятного символа (слова) в момент времени t = 0, используя argmax.
  • Подайте следующий токен (следующее встроенное слово от нашей цели) в ячейку LSMT в качестве входных данных в момент времени t = 1.
  • Повторяйте до тех пор, пока на выходе ячейки не будет получен токен ‹end›.

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

Собираем декодер.

Вот общий обзор структуры LSTM для первых двух временных шагов:

В декодирующей части модели очень много нюансов и я постараюсь быть максимально подробным.

Во-первых, мы собираемся использовать однослойный LSTM для сопоставления вектора скрытого пространства с пространством слов.

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

Прямой проход довольно прост, если у вас есть хотя бы некоторый опыт работы с повторяющимися нейронными сетями. Если нет, я считаю очень полезным прочитать документацию или руководства PyTorch, чтобы понять, какие размеры ожидает ячейка LSTM для скрытого состояния и состояния ячейки и ввода. Фактически, подготовка данных и обсуждение занимают 90% времени при построении таких моделей.

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

Обучение.

Цикл обучения очень прост:

Обратите внимание, что, несмотря на то, что у нас есть два компонента модели, то есть кодер и декодер, мы обучаем их вместе, передавая выходной сигнал кодировщика, который является вектором скрытого пространства, декодеру, который, в свою очередь, является рекуррентной нейронной сетью.

Я тренировал модель на NVIDIA GTX 1080Ti с размером пакета 48 за 3 эпохи, что заняло около 1 дня. Спустя 3 эпохи результаты модели были уже довольно хорошими, что свидетельствует о сходимости модели.

Результаты.

Вот некоторые результаты запуска модели в проверочной части набора данных COCO:

А вот несколько подписей к фотографиям из моего Facebook:

Стоит отметить, что этап выборки может быть реализован с помощью Beam Search для большего разнообразия подписей. Кроме того, существует механика внимания, которая может помочь сформировать лучшие подписи, поскольку механизм внимания уделяет разную степень внимания разным частям изображения. Я надеюсь написать больше о моделях внимания в будущем.

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

Часть кода для обработки данных была взята из Udacity, однако вы можете использовать встроенные в PyTorch утилиты, чтобы вручную упаковать последовательности слов.