Узнайте, как создавать базовые модели, такие как UNet, и внесите свой вклад в Metalhead.jl в Flux, пакете глубокого обучения Julia!

Flux великолепен, и это гораздо больше, чем просто ML-фреймворк. Дифференцируемое программирование и Zygote, первоклассная поддержка графических процессоров — функции, которые выделяют его среди систем машинного обучения. Лучшее, что мне нравится в потоке, — это смешивание нейронных сетей с дифференциальными уравнениями, чтобы получить лучшее от черного ящика и механистического моделирования, это то, что делает SciML.
Flux довольно новый и требует внимания сообщества! В этой статье мы узнаем, как реализовать модель в Flux.jl. Мы рассмотрим реализацию UNet, над которой я работал в последнее время, чтобы внести свой вклад в зоопарк моделей Flux Metalhead.jl.
Вы узнаете
- Что такое Юнет?
- Краткий обзор Flux API
- Повторная реализация моделей PyTorch в Julia
после прочтения этой статьи.
ЮНет 🥅
UNet — это модель глубокого обучения, которая была опубликована в статье U-Net: сверточные сети для сегментации биомедицинских изображений. Архитектура сети выглядит следующим образом:

Мы не будем вдаваться в подробности теории UNet, так как в статье она объяснена наилучшим образом, да и объяснение здесь в любом случае было бы излишним.
Этот урок будет посвящен реализации.
Давайте сразу же погрузимся в код! Статья написана исходя из того, что у вас есть знания о реализации нейронных сетей в PyTorch ! Мы просто создаем класс, который наследует nn.Module . Теперь мы создаем слои и назначаем их классу с помощью self . Когда эта модель будет создана, эти слои станут атрибутом объекта модели.
>>> exec(open("basic_model.py").read())
torch.Size([1, 5, 8, 8])
Посмотрим, как это выглядит у Юли.
julia> include("basic_model.jl");
(8, 8, 5, 1)
Вот некоторые ключевые моменты, на которые следует обратить внимание:
- Flux соответствует стандарту (H, W, C, N) для изображений, в то время как PyTorch использует (N, C, H, W)
- Функция Chain аналогична методу nn.Sequential из PyTorch.
- Julia реализует Multiple Dispatch, в отличие от PyThon, который разработан на основе парадигмы Single Dispatch. Короче говоря, множественная диспетчеризация позволяет нам реализовать один метод для различных комбинаций аргументов разных типов, в отличие от python, который ограничивает методы, которые должны быть привязаны к одному объекту и повторно реализованы для разных классов. Подробнее о мультидиспетчерской рассылке здесь.
Давайте посмотрим, как выглядит официальная реализация UNet от Torchhub.
Автор оригинальной реализации (далее просто «автор») создал вспомогательный метод для создания сверточного блока. Это хорошая практика, называемая СУХОЙ (не повторяйтесь).

Это позволяет нам создавать эти конверсионные блоки (рис. 2 увеличивает рис. 1, чтобы показать эти конверсионные блоки), просто передавая входные и выходные каналы.
Чтобы реализовать это с помощью Flux, мы повторно используем наши знания из gist 1:
Довольно просто, не так ли?
Здесь следует отметить одну вещь: Flux не позволяет нам явно называть слои, причину можно найти в этой проблеме github. Теперь давайте продолжим и посмотрим, как создавались слои автором.
Теперь мы видим, что у нас есть кодировщик, узкое место, декодер и повышающий слой.
- Кодер: четыре нижних этажа на рис. 1 образуют блок кодировщика, и он кодирует изображение с помощью последовательных сверток.
- Узкое место: Слой между кодером и декодером называется узким местом. Выход декодера передается в блок декодера и преобразования.
- Блок декодера и преобразования: блок преобразования повышает дискретизацию входной матрицы, т. е. преобразует входные данные в выходные данные большего размера (В x Ш), чем входные данные. Декодер следует за повышающим уровнем и увеличивает количество каналов, выполняя свертку.
Давайте сначала создадим класс модели в Flux. У Юли нет классов, у нее есть structs . Таким образом, структура модели будет выглядеть так.
чтобы уменьшить избыточность кода, я реализую слои в виде массива слоев, это позволит нам позже написать чистый прямой проход.
Не забудьте обратить внимание на соотношение между количеством каналов разных блоков модели UNet.
unet_block — это сверточный блок, который мы определили ранее в Gist 4, — это простая цепочка слоев Convolutional и BatchNorm. Далее мы связываем эти блоки с учетом функций ввода и вывода, используя функцию Chain. Видеть ! Как легко создать модель во Flux.
Теперь у нас осталась последняя вещь, проход forward. Мы делаем это как
в ПиТорч. Операция cat управляет соединением между кодировщиком и декодером, показанным copy and crop, представленным серой стрелкой на рис. 1.
Но вы не находите приведенный выше код беспорядочным? Вот где вступает в действие наше определение слоев массивов. Давайте посмотрим, как записывается прямой проход для UNet.
Довольно просто и понятно, нам удалось сохранить всю логику.
Здесь следует отметить несколько ключевых моментов:
- σ есть не что иное, как сигмовидная функция! Джулия позволяет нам использовать все математические символы в качестве переменных. Таким образом, поток определил сигмовидную как σ, а не
sigmoid() - Джулия использует матлаб-подобный синтаксис для диапазонов (см. 1:4 для перебора 1, 2, 3, 4 и 4:-1:1 для 4, 3, 2, 1).
- Джулия использует индексацию на основе 1.
- Любая функция Julia с конечным
!сообщает, что операция будет на месте (вспомните передачу по ссылке из C++ с помощью операции &). - Ключевое слово
returnв последней строке является избыточным, просто напишите σ(u.final_conv(out)), поскольку Джулия всегда возвращает вывод последней строки кода из любого блока кода.
Это подводит нас к концу этого урока. Спасибо, что прочитали!
Это вторая статья из моей первой серии блогов Julia For the Win. Вы можете найти предыдущую статью Kaggle x Julia: Advanced House Price Prediction: EDA.