Машинное обучение - это больше, чем просто область, это революция. В последние годы произошел качественный скачок в количестве компаний и людей, вовлеченных в исследования и разработку программного обеспечения, сосредоточенного на технологиях машинного обучения. Крупные гиганты в мире технологий пытались сделать эту область более доступной для студентов, исследователей и профессионалов, разрабатывая и открывая исходные коды своих фреймворков машинного обучения, таких как TensorFlow, PyTorch, Caffe2, CoreML Просто назвать несколько.

Но наличие такого количества фреймворков поднимает простой, но важный вопрос: можно ли сделать трансфертное обучение кроссплатформенным? Можно ли избежать изобретения колеса для разработки сетей с использованием единой структуры? Как говорится, необходимость - мать изобретательности. Естественно, люди начали работать в этом направлении. Их цель была проста: разработать формат для взаимозаменяемых моделей машинного обучения. Удалите специфическое ограничение framework из мира машинного обучения. Так родился ONNX.

ONNX или Open Neural Network eXchange - это платформа для взаимозаменяемых моделей машинного обучения. Он предоставляет простой интерфейс для запуска моделей, созданных в различных средах, в любых других средах. Он использует буферы протокола Google для сериализации моделей в общий формат и набор спецификаций для чтения и загрузки в другой. Летом я работал над реализацией серверной части ONNX для фреймворка Julian Flux в рамках моего проекта GSoC. В этой статье я попытаюсь перечислите цели, которых я смог достичь, препятствия на моем пути и работу, которую необходимо проделать в этом направлении в будущем.

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

  • Обработка необработанных данных ProtoBuf. Необработанные данные действительно сложно понять, и с ними сложно работать. Это означает, что мы, по сути, создаем новые структуры, подобные примитивным структурам, сгенерированным ProtoBuf, но которые хранят только полезную информацию в более простом, прямолинейном и юлианском формате.
  • Извлечение веса из этих моделей. Веса обычно хранятся в структуре TensorProto. Здесь следует иметь в виду, что некоторые модели могут предпочитать хранить веса в тензоре Constant, а не в обычной структуре.
  • Узлы, присутствующие в графе, необходимо преобразовать в операторы потока. DataFlow.jl используется для получения графика потока данных из структуры GraphProto. Каждый оператор должен быть сопоставлен с соответствующим оператором Flux. Файл ops.jl содержит это сопоставление узлов ONNX с операциями Flux.
  • Выпуск кода - следующий важный шаг в этом процессе. DataFlow.jl можно использовать для чтения графа вычислений из структуры ONNX, генерации потока данных юлианского графа. и, наконец, выпустить код. В результате были созданы два файла: файл model.jl, который содержит представление Flux всей модели, и файл weights.bson, в котором хранятся веса тензоров модели. Оба они должны быть импортированы извне для запуска модели.

Итак, чтобы резюмировать этот раздел, мы по существу преобразовали модель ONNX в код Julia вместе с отдельным файлом веса. Вкратце, это весь процесс, заимствованный у меня ONNX.jl.

Текущие проблемы

(Настоящее время)

ONNX.jl теперь можно использовать для запуска нескольких моделей. Мне удалось загрузить предварительно обученные модели MNIST, VGG19, SqueezeNet и Emotion Ferplus и запустить их для получения точных результатов. Но я также столкнулся с довольно большим количеством проблем, в основном из-за разницы в реализации операторов в других фреймворках по сравнению с Flux, а также из-за отсутствия различных операторов в Flux. Это ограничивало загрузку большего количества моделей в Flux. Некоторые из этих проблем перечислены ниже:

  • AveragePool:

Различное поведение AveragePool. Я столкнулся с этим при тестировании операторов объединения. Судя по всему, тесты AveragePool не прошли даже после прямой реализации в Flux. Чтобы проверить результаты, я решил сравнить результаты одного и того же слоя на одном входе, но из двух разных фреймворков: Flux и Keras-TensorFlow. Именно тогда я понял, что обе эти структуры имеют разные реализации для AveragePool. В то время как Flux заполняет входной тензор, а затем объединяет его, Keras объединяет, а затем дополняет его, игнорируя включение дополненных элементов во время объединения. В результате мы получаем неверные результаты на границе выходных тензоров.

  • Асимметричные подушечки:

Асимметричная набивка - не очень распространенное явление. Тем не менее, многие модели ONNX, как правило, экспортированные из таких фреймворков, как Caffe2, как правило, используют его. В Flux мы указываем заполнение как (a, b), которое расширяется до (a, b, a, b) в четырех направлениях. . Следовательно, отступы в противоположных направлениях всегда одинаковы. Таким образом, заполнение ONNX типа (2,2,0,0) невозможно реализовать в Flux. Один из способов обойти это - преобразовать это в симметричный отступ. Итак, (2,2,0,0) преобразуется в (1,1,1,1), который поддерживается в Flux. Однако у этого подхода есть два недостатка:

Происходит снижение производительности, что довольно очевидно.

Не все отступы можно преобразовать в симметричные без нарушения ожидаемой формы вывода. Например, (3,3,0,0) нельзя преобразовать в симметричное заполнение без изменения выходной формы с идеально ожидаемой, что, скорее всего, приведет к какой-то ошибке DimensionError на более поздних этапах. .

  • Сгруппированные свертки

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

  • Нормализация местного отклика

Flux снова не поддерживает LRN прямо сейчас. Хотя я открыл для этого Pull Request, так что на более поздних этапах это не должно быть большой проблемой.

  • Интерфейс пакетной нормализации

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

  • Разница в типах входов и выходов

Flux в большинстве случаев имеет тенденцию изменять тип данных. В качестве примера рассмотрим небольшой слой Dense, Dense (2,4). Таким образом, он принимает вектор с двумя элементами на входе и возвращает вектор с четырьмя элементами. Естественно, если мы введем вектор типа Array {Float64, 1}, мы ожидаем, что выходные данные также будут иметь тип Array {Float64,1}. Но если входные данные являются вектором любых других типов, например, таким как Float32, возвращаемый результат по-прежнему содержит элементы типа Float64. В результате, если все операции, которые мы выполняем, состоят из значений Float32, и внезапно тип одного вывода меняется на Float64 во время прямого прохода, мы ' вы получите ошибки.

  • заполнение «SAME_UPPER» / «SAME_LOWER»

Это заполнение фактически подразумевает «заполнить ввод так, чтобы форма вывода оставалась такой же, как и форма ввода». Это устаревший атрибут, но он все еще используется во многих моделях ONNX.

Что дальше?

Цели и дорожная карта на будущее (будущее)

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

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

Примечания. Особая благодарность philtor за обзор.