MATTHIJS HOLLEMANS,Оригинальная ссылка, исходная дата: 2017-02-07
Переводчик:TonyHan; вычитка:зимняя дыня,liberalisman;Конечно:CMB
Начиная с iOS 10, Apple представила две платформы глубокого обучения на платформе iOS: BNNS и MPSCNN.
-
BNNS: Полное название — бананы (бананы, прим. переводчика: здесь шутят), основные подпрограммы нейронной сети, даУскорить фреймворкчасть. Эта структура в полной мере использует быстрые векторные инструкции ЦП и предоставляет ряд математических функций.
-
MPSCNN этоMetal Performance Shadersчасть. Metal Performance Shaders — это структура вычислительных ядер, оптимизированная для работы на GPU (а не на CPU).
Итак, как разработчик iOS, есть две платформы для глубокого обучения, которые имеют много общего.
какой я должен выбрать?
В этом посте мы сравним BNNS и MPSCNN, чтобы показать разницу. И мы проверим скорость этих двух API, чтобы увидеть, какой из них быстрее.
Почему лучше использовать BNNS или MPSCNN?
Сначала мы обсудим роль этих двух структур.
В настоящее время BNNS и MPSCNN находятся вСверточная нейронная сетьиспользуется в полевых условияхвариационный вывод.
с подобнымTensorFlow(Используя эту схему, вы можете построить свою нейронную сеть с нуля, построив вычислительный граф.) По сравнению с BNNS и MPSCNN, BNNS и MPSCNN предоставляют API более высокого уровня, и вам не нужно беспокоиться о вашей математике.
У этого есть и обратная сторона: BNNS и MPSCNN гораздо менее эффективны, чем другие фреймворки, такие как TensorFlow. С ними легче начать, но в то же время они ограничивают возможности глубокого обучения.
Фреймворк глубокого обучения Apple служит только одной цели: через сетевую иерархиюпередавать данные как можно быстрее.
Все дело в иерархии
Вы можете думать о нейронной сети как о конвейере, по которому проходят данные. Различные этапы конвейера представляют собой сетьИерархия. Эти слои преобразуют ваши данные по-разному. Одновременно с глубоким обучением мы можем использовать до 10 слоев или даже 100 слоев нейронных сетей.
Существуют различные виды уровней. BNNS и MPSCNN обеспечивают: сверточный уровень, уровень объединения, полносвязный уровень и уровень нормализации.
В BNNS и MPSCNN,Иерархия является основным строительным блоком. Вы можете создавать объекты иерархии, помещать данные в иерархию, а затем считывать результаты из иерархии. Кстати, BNNS называет их «фильтрами», а не иерархиями: данные входят в фильтр в одном виде, а выходят из фильтра в другом.
Чтобы проиллюстрировать идею слоев как строительных блоков, ниже описывается, как данные проходят через BNNS через простую нейронную сеть:
// 为中间结果和最终结果分配内存。
var tempBuffer1: [Float] = . . .
var tempBuffer2: [Float] = . . .
var results: [Float] = . . .
// 对输入的数据(比如说一张图片)应用第一层级。
BNNSFilterApply(convLayer, inputData, &tempBuffer1)
// 对第一层级的输出应用第二层级。
BNNSFilterApply(poolLayer, tempBuffer1, &tempBuffer2)
// 应用第三和最后的层级。结果通常是概率分布。
BNNSFilterApply(fcLayer, tempBuffer2, &results)
Чтобы построить нейронную сеть с BNNS и MPSCNN, вам просто нужно настроить слои и отправить на них данные. Фреймворк обрабатывает иерархиюВнутриЧто происходит, но что вам нужно сделать, это соединить слои.
К сожалению, это может быть немного скучно. Например, загрузивПредварительно обученный файл caffemodelполучить полностью настроенную «нейронную сеть» не получится. Вы должны писать код вручную, тщательно создавать слои и настраивать их для воспроизведения дизайна сети. Легко сделать ошибки.
BNNS и MPSCNN не обучены
Прежде чем вы сможете использовать нейронные сети, вы должны сначалатренироватьсяЭто. Обучение требует большого количества данных и терпения — по крайней мере, несколько часов или даже дней или недель, в зависимости от того, какую вычислительную мощность вы можете на это задействовать. Вы определенно не хотите тренироваться на своем телефоне (он может поджечь ваш телефон).
Когда обученная сеть получена, ее можно использовать дляпредсказывать. Это называется «вывод». Для обучения должны были потребоваться сверхмощные компьютеры, но логические выводы на современных мобильных телефонах вполне возможны.
Это именно то, для чего предназначены BNNS и MPSCNN.
Только сверточные сети
Но оба API имеют ограничения. В настоящее время BNNS и MPSCNN поддерживают только один тип глубокого обучения: сверточные нейронные сети (CNN). Основные сценарии применения CNN:машинное зрениеЗадача. Например, вы можете использовать CNN дляОпишите объект на данной фотографии.
Хотя CNN великолепны, другие архитектуры глубокого обучения (например, рекуррентные нейронные сети) не могут поддерживаться в BNNS или MPSCNN.
Однако уже предоставленные строительные блоки (сверточные слои, объединяющие слои и полносвязные слои) эффективны и дают возможность создавать более сложные нейронные сети.хорошая основа, даже если вам придется написать код вручную, чтобы заполнить пробелы в API.
- Примечание. Платформа Metal Performance Shaders также поставляется с вычислительными ядрами для быстрого умножения матриц на графическом процессоре. Между тем, инфраструктура Accelerate включает библиотеку BLAS для того, чтобы делать то же самое на ЦП. Таким образом, даже если BNNS или MPSCNN не содержат всех типов слоев, необходимых для архитектур глубокого обучения, вы можете использовать эти матричные процедуры для получения собственных типов слоев. И, при необходимости, вы можете написать свой собственный код графического процессора на языке Metal Shading Language.
разница
Если они делают одно и то же, зачем Apple предоставлять нам два API?
Просто: BNNS работает на процессоре, MPSCNN работает на графическом процессоре. Иногда быстрее использовать ЦП, иногда быстрее использовать ГП.
- «Подождите минутку… разве графические процессоры не являются монстрами высокопараллельных вычислений? Разве мы не должны постоянно запускать наши глубокие нейронные сети на графических процессорах?!»
нисколько. Для обучения вам определенно нужны графические процессоры для массовых параллельных вычислений (даже если это просто кластер из множества графических процессоров), но для вывода, вероятно, быстрее использовать скучный старый 2- или 4-ядерный процессор.
Я подробно расскажу о различиях в скорости ниже, но сначала давайте посмотрим, чем отличаются два API.
- Примечание. Платформа Metal Performance Shaders доступна только для iOS и tvOS, но не для Mac. BNNS также работает на macOS 10.12 и более поздних версиях. Если вам нужна гарантированная переносимость вашего кода глубокого обучения между iOS и MacOS, BNNS — ваш единственный вариант (или используйте стороннюю платформу).
Это Свифти?
BNNS на самом деле является API на основе C. Это нормально, если вы используете Objective-C, но его немного сложнее использовать со Swift. И наоборот, MPSCNN более совместим со Swift.
Однако вы должны принять тот факт, что эти API являются более низким уровнем, чем так называемый UIKit. Swift не абстрагирует все в простые типы. Вам часто нужно использовать SwiftUnsafeRawPointer
указатель для обработки необработанных байтов.
Swift также не имеет нативного16-битный тип с плавающей запятой, но BNNS и MPSCNN наиболее эффективны при использовании таких чисел с плавающей запятой половинной точности. Вам придется использовать платформу Accelerate для преобразования между обычными типами и числами с плавающей запятой половинной точности.
Теоретически, при использовании MPSCNN вам не нужно самостоятельно писать какой-либо код GPU, но на практике я обнаружил, что некоторые этапы предварительной обработки — такие как вычитание среднего значения RGB из каждого пикселя изображения, использование языка Metal Shading Language (на основе C++) вычислительное ядро в реализации) реализовать проще всего.
Так что, даже если вы используете эти два фреймворка в Swift, будьте готовы использовать эти два API для какого-то низкоуровневого хакерского поведения.
функция активации
По мере того, как данные передаются от одного уровня к другому в нейронной сети, данные каким-то образом преобразуются на каждом уровне. Иерархия применяет функцию активации как часть этого преобразования. Без этих функций активации нейронные сети не могут научиться очень интересным вещам.
Существует множество вариантов функций активации, и BNNS, и MPSCNN поддерживают наиболее часто используемые функции:
- Исправленная линейная единица (ReLU) и модифицированная линейная единица с утечкой (Leaky ReLU)
- логистическая сигмовидная
- Функция гиперболического тангенса (tanh) и расширенная функция гиперболического тангенса (масштабированнаяtanh)
- абсолютная величина
- функция идентификации, которая передает данные, не изменяя их
- Линейный (только на MPSCNN)
Вы могли бы подумать, что это будет так же просто, как API, но, как ни странно, BNNS имеет другой способ определения этих функций активации по сравнению с MPSCNN.
Например, BNNS определяет два типа:BNNSActivationFunctionRectifiedLinear
иBNNSActivationFunctionLeakyRectifiedLinear
, но в MPSCNN есть только одинMPSCNNNeuronReLU
тип, использованиеalpha
параметр, чтобы отметить, является ли это линейным блоком с утечкой (Leaky ReLU). То же самое верно для функции гиперболического тангенса (tanh) и
Расширенная функция гиперболического тангенса (масштабированный тангенс).
Можно с уверенностью сказать, что MPSCNN использует более гибкий и настраиваемый подход, чем BNNS. Это верно для всего уровня API.
Например: MPSCNN позволяет передавать наследованиеMPSCNNNeuron
И напишите немного кода для графического процессора, чтобы создать собственную функцию активации. Это невозможно с BNNS, потому что нет API для пользовательских функций активации, предоставляются только перечисления. Если нужной функции активации нет в списке, то использование BNNS приведет к большой дыре.
-
Обновление от 10.02.17: вышеизложенное немного вводит в заблуждение, поэтому я должен уточнить. Поскольку BNNS работает на ЦП, вы можете просто взять вывод иерархии и изменить его по своему вкусу. Если вам нужна особая функция активации, вы можете реализовать ее самостоятельно в Swift (желательно с помощью фреймворка Accelerate) и применить ее к выходу предыдущего уровня перед переходом на следующий уровень. Так что BNNS в этом отношении не менее способен, чем Metal.
-
Обновление от 29.06.17: О программе
MPSCNNNeuron
Уточнение для подкласса: если вы сделаете это, вы не сможете использоватьMPSCNNConvolution
подкласс . Это связано с тем, что MPS использует трюк при выполнении функции активации в ядре графического процессора, но это работает только с собственным подклассом Apple MPSCNNNeuron, а не с любыми подклассами, которые вы создаете сами.
Фактически, в MPSCNN,всеобаMPSCNNKernel
подкласс . Это означает, что вы можете использовать только функцию активации, напримерMPSCNNNeuronLinear
, как если бы это была отдельная иерархия. Это полезно для масштабирования данных по константе на этапе предварительной обработки. (Кстати, в BNNS нет «линейной» функции активации.)
- Примечание. Мне кажется, что BNNS и MPSCNN были созданы разными группами внутри Apple. У них очень похожая функциональность, но между их API есть странные различия. Я не работаю в Apple, поэтому не знаю, почему существуют эти различия. Возможно, по техническим или эксплуатационным причинам. Но вы должны знать, что BNNS и MPSCNN не поддерживают «горячее подключение». Если вы хотите узнать, какой метод является самым быстрым для вывода на ЦП или ГП, вам придется реализовать сеть глубокого обучения дважды.
Тип иерархии
Ранее я упоминал, что глубокие нейронные сети состоят из разных типов слоев:
- сверточный
- Объединение, максимальное и среднее значение
- Полностью подключен
И BNNS, и MPSCNN реализуют эти три типа слоев, но есть небольшие различия в том, как реализован каждый API.
Например, BNNS может использовать функции активации в слоях пула, а MPSCNN — нет. Однако в MPSCNN вы можете добавить функцию активации в качестве отдельного уровня после уровня пула, поэтому в итоге два API реализуют одну и ту же функциональность, но используют разные пути.
В MPSCNN полносвязный слой рассматривается как частный случай свертки, а в BNNS он реализуется как умножение матрицы на вектор. На практике это не имеет значения, но показывает, что две платформы используют разные подходы к одной и той же проблеме.
Я думаю, для разработчиков,MPSCNN проще в использовании.
При использовании свертки на изображении выходное изображение немного уменьшается, если не добавляются «заполняющие» пиксели. С MPSCNN вам не нужно беспокоиться об этом: вы просто указываете, насколько большими должны быть входные и выходные изображения. С BNNS вы должны сами рассчитать заполнение. Подобные детали делают MPSCNN более простым в использовании API.
В дополнение к базовому уровню MPSCNN также предоставляет следующие уровни:
- Нормализация (нормализация признаков, межканальная нормализация (ослабление), локальная нормализация контраста)
- Softmax, также известная как нормализованная экспоненциальная функция
- Логарифмический Softmax, то есть использование функции Softmax с функцией стоимости логарифмического правдоподобия.
- уровень функции активации
Эти дополнительные типы слоев не могут быть найдены в BNNS.
Для слоев нормализации это может не иметь большого значения, поскольку я не думаю, что они распространены, но softmax — это то, что большинству сверточных сетей нужно делать в какой-то момент (обычно в конце).
Функция softmax преобразует вывод нейронной сети в распределение вероятностей: «Я на 95% уверен, что на этом фото кошка, но только на 5% уверен, что это кошка».Pokémon
. "
Немного странно, что softmax не предусмотрен в BNNS. Писать код с использованием функций vDSP во фреймворке Accelerate несложно, но не очень удобно.
параметры обучения
При обучении нейронной сети процесс обучения настраивает набор чисел, чтобы представить то, что сеть изучает. Эти числа называютсяпараметры обучения.
Параметры обучения состоят из так называемых значений веса и смещения, которые представляют собой просто числа с плавающей запятой. Когда вы отправляете данные в нейронную сеть, слои фактически умножают ваши данные на эти веса, добавляют смещение, а затем применяют функцию активации.
При создании слоев необходимо указать значения веса и смещения для каждого слоя. Для обоих API требуется только необработанный указатель на буфер значений с плавающей запятой. Это зависит от вас, чтобы убедиться, что числа организованы в правильном порядке. Если это сделать неправильно, нейросеть выдаст мусорные данные.
Вы, наверное, догадались: BNNS и MPSCNN используют разное распределение памяти для весов. ?
Для MPSCNN массив весов выглядит так:
weights[ outputChannel ][ kernelY ][ kernelX ][ inputChannel ]
Но для BNNS порядок другой:
weights[ outputChannel ][ inputChannel ][ kernelY ][ kernelX ]
Я думаю, что причина, по которой MPSCNN помещает входной канал последним, заключается в том, что он хорошо сопоставляется с местом хранения данных.MTLTexture
Пиксели RGBA в с. Но для векторных инструкций ЦП, используемых BNNS, более эффективно обрабатывать входные каналы как отдельные блоки памяти.
Эта разница не является большой проблемой для разработчиков, но при импорте обученной модели нужноЗнайте распределение памяти весов.
Примечание. Возможно, вам потребуется написать сценарий преобразования, чтобы экспортировать данные из обучающего инструмента, такого как TensorFlow или Caffe, и преобразовать их в формат, ожидаемый BNNS или MPSCNN. Ни один API не может читать модели, сохраненные этими инструментами, они принимают только буферизованные данные необработанных значений с плавающей запятой.
MPSCNN всегда копирует значения веса и смещения и сохраняет их внутри в виде 16-битной плавающей запятой. Поскольку вы должны предоставить их как числа с плавающей запятой одинарной точности, это фактически вдвое снижает точность ваших изученных параметров.
BNNS здесь немного более открыт: он позволяет вам выбрать формат, в котором вы хотите хранить изученные параметры, и позволяет вам отказаться от копирования.
Загрузка весов в сеть имеет значение только тогда, когда приложение запускается при создании сети. Однако, если у вас много весов, вам все равно нужно относиться к этому серьезно. мойVGGNet implementationНе работает на iPhone 6, потому что приложению не хватает памяти, когда оно пытается загрузить все веса в MPSCNN сразу. (Вы можете сначала создать большие слои, а затем создать слои меньшего размера.)
Входные данные
После того, как вы создали все иерархические объекты, вы, наконец, можете начать делать выводы с помощью нейронной сети!
Как видите, ни BNNS, ни MPSCNN не имеют реального понятия «нейронная сеть», они видят только каждый уровень. Вам нужно поместить данные в каждый из этих слоев отдельно.
Как пользователь нейронной сети вас интересуют данные, поступающие в первый слой (например, изображение), и выходные данные, поступающие из последнего слоя (вероятность того, что это изображение — кошка). Другие данные, передаваемые между слоями, являются лишь временными промежуточными результатами.
Итак, какой формат данных вам нужно ввести?
MPSCNN требует, чтобы все данные были помещены в специальныйMPSImage
Внутри объекта этот объект фактически представляет собой набор 2D-текстур. Это имеет смысл, если вы работаете с изображениями, но если ваши данные не являются изображением, вам нужно преобразовать их в металлическую текстуру. Это потребляет производительность процессора. (Вы можете использовать платформу Accelerate для решения этой проблемы.)
Примечание. Устройства iOS используют унифицированную модель памяти, что означает, что ЦП и ГП обращаются к одному и тому же чипу ОЗУ. В отличие от настольного компьютера или сервера, вам не нужно копировать данные в GPU. Так что, по крайней мере, ваше приложение для iOS не будет иметь таких затрат на производительность.
BNNS, с другой стороны, нужен только указатель на буфер значений с плавающей запятой. Нет необходимости загружать данные в конкретный объект. Так что это кажется быстрее, чем использование текстур ... а?
У этого есть важное ограничение: в BNNS входы в разные «каналы» не могут чередоваться.
Если вашим входом является изображение, то оно имеет три канала: один для красных пикселей, один для зеленых пикселей и один для синих пикселей. Проблема в том, что файлы изображений, такие как PNG или JPEG, загружаются в память как чередующиеся значения RGBA. BNNS не примет эту ситуацию.
В настоящее время нет способа указать BNNS использовать значения красных пикселей в качестве канала 0, значения зеленых пикселей в качестве канала 1, значения синих пикселей в качестве канала 2 и пропустить альфа-канал. Вместо этого вам придется переупорядочивать данные пикселей, чтобы входной буфер содержал сначала все значения R, затем все значения G, затем все значения B.
Если мы делаем такую предварительную обработку, это занимает ценное вычислительное время. Во-вторых, возможно, эти ограничения позволяют BNNS оптимизировать то, как его слои выполняют свои вычисления, что делает все это чистой выгодой. Но этого никто не знает.
В любом случае, если вы используете BNNS для обработки изображений (основная цель CNN), вам может потребоваться внести некоторые коррективы во входные данные, чтобы получить правильный формат.
итип данныхПроблема.
И BNNS, и MPSCNN позволяют указывать входные данные как значения с плавающей запятой (16 и 32 бита) или целые числа (8, 16 или 32 бита). Если вы хотите использовать данные с плавающей запятой в качестве входных данных для сети, возможно, вы не сможете выбрать формат входных данных.
Как правило, когда вы загружаете изображение в формате PNG или JPEG или снимаете неподвижное изображение с камеры телефона, вы получаете 8-битную текстуру, в которой в качестве значения RGBA пикселя используется 8-битное целое число без знака. С MPSCNN это не проблема: текстуры автоматически преобразуются в значения с плавающей запятой.
С BNNS вы можете указатьInt8
Как тип данных изображения, но после практики я обнаружил, что это не работает. На самом деле, может быть, это потому, что я не тратил на это много времени. Поскольку я собираюсь повторно изменить каналы входного изображения, кстати, легко преобразовать данные пикселей в числа с плавающей запятой.
Примечание. Несмотря на то, что BNNS позволяет указать целые числа в качестве типа данных для данных и весов, он внутренне преобразует их в данные с плавающей запятой, выполняет расчет, а затем преобразует результат в целое число. Для лучшей скорости вы можете пропустить этот шаг преобразования и всегда работать напрямую с данными с плавающей запятой, даже если они занимают в 2-4 раза больше памяти.
временные данные
В BNNS и MPSCNN необходимо обрабатывать каждый слой. Вы помещаете данные в иерархию и получаете данные из иерархии.
Глубокие сети будут иметь много слоев. Нас интересует только вывод последнего слоя, а не вывод всех остальных слоев. Но нам все равно нужно где-то хранить эти промежуточные результаты, даже если они используются недолго.
MPSCNN имеет для этого специальный объект,MPSTemporaryImage
. это какMPSImage
, но только один раз. Запишите данные один раз, прочитайте данные один раз. После этого его память будет восстановлена. (Если вы знакомы с Metal, они реализованы с использованием кучи ресурсов Metal.)
Вы должны использовать как можно большеMPSTemporaryImage
, так как это позволяет избежать выделения и освобождения большого количества памяти.
Вы можете использовать BNNS. Вам нужно самостоятельно управлять временным буфером данных. К счастью, это довольно просто: вы можете выделить один или два больших массива и повторно использовать их на этих уровнях.
Многопоточность
Возможно, вы захотите построить сетевую иерархию в фоновом потоке. Загрузка всех данных для изученных параметров может занять несколько секунд.
Также рекомендуется выполнять вывод в фоновом потоке.
При достаточно глубокой нейросети вывод может занять от 0,1 до 0,5 секунды, такая задержка заметна пользователю.
Используйте MPSCNN для создания очереди команд и буфера команд, затем сообщите всем слоям о необходимости кодирования в буфер команд и, наконец, отправьте работу на графический процессор. Когда GPU закончит работу, вы будете уведомлены обратным вызовом.
Кодирование каждого задания можно выполнять в фоновом потоке, для синхронизации ничего делать не нужно.
Примечание. В ситуациях реального времени (например, при передаче видеокадров в режиме реального времени с камеры в нейронную сеть) необходимо, чтобы GPU оставался занятым и избегал ситуаций, когда CPU и GPU ожидают друг друга. В то время как GPU все еще обрабатывает предыдущий кадр, CPU должен уже закодировать следующий видеокадр. вам нужно использоватьMPSImage
Массивы объектов и гарантированный синхронизированный доступ к ним через семафоры — но, честно говоря, я был бы очень удивлен, если бы сегодняшние мобильные устройства могли выполнять глубокое обучение в режиме реального времени.
BNNS работает на ЦП, поэтому вы можете начать работу в фоновом потоке, а затем заблокировать, пока BNNS не завершится.
Лучше всего позволить BNNS разобраться, как разделить работу между доступными ядрами ЦП, но есть параметр конфигурации, который сообщает BNNS, сколько потоков он может использовать для выполнения вычислений. (MPSCNN в этом не нуждается, он будет использовать как можно больше потоков графического процессора.)
Примечание. Не следует совместно использовать объекты MPSCNN или объекты BNNS между несколькими потоками. Их можно использовать в одном фоновом потоке, но не в нескольких потоках одновременно.
проблема со скоростью
Решение об использовании BNNS или MPSCNN основано на компромиссе:Данные CPU быстрее или быстрее GPU?
Не все данные подходят для обработки GPU. Изображения или видео очень подходят, но данные, такие как временные ряды, могут не подойти.
Загрузка данных в GPU стоит денег, потому что вам нужно инкапсулировать их вMTLTexture
в объекте. После завершения работы графического процессора результат чтения необходимо снова получить из объекта текстуры.
С BNNS на основе ЦП у вас нет этих накладных расходов, но вы также не можете воспользоваться преимуществами массивного параллелизма графических процессоров для вычислений.
На практике разработчики могутПопробуйте оба метода и посмотрите, какой из них быстрее. Однако, как показано выше, поскольку BNNS и MPSCNN имеют разные API, код необходимо писать дважды.
Поскольку мне было любопытно, я решил построить очень простую свёрточную нейронную сеть, используя соответственно BNNS и MPSCNN, чтобы определить, какая из них быстрее.
Мой дизайн нейронной сети выглядит так (щелкните изображение, чтобы увеличить):
The
convolutional neural network used for the speed test
Этот дизайн сети можно использовать для классификации изображений. Сеть принимает изображение RGB 256×256 (без альфа-канала) в качестве входных данных и создает изображение со 100浮点值
массив . Выходные данные будут представлять распределение вероятностей объектов более чем 100 возможных классов.
На самом деле нейронные сети должны иметь гораздо больше слоев, чтобы быть действительно полезными. Он также должен иметь слой softmax в конце, но, поскольку BNNS не использует функцию softmax, я удалил его.
На самом деле я не обучаю эту нейронную сеть чему-то полезному, а скорее инициализирую ее разумными случайными значениями. Это бесполезная нейронная сеть. Тем не менее, это позволяет нам сравнить, что требуется для построения одной и той же нейронной сети в BNNS и MPSCNN, и как быстро работает каждая сеть.
Если вы хотите заниматься вместе,Вот код на GitHub. Откройте проект в Xcode и запустите его на совместимом с iOS 10 устройстве как минимум с одним процессором A8 (на симуляторе он не запустится).
После нажатия кнопки приложение зависает на несколько секунд, выполняя 100 независимых выводов в каждой нейронной сети. Приложение показывает, сколько времени требуется для создания сети (не очень интересно) и сколько времени требуется для завершения вывода для 100 итераций.
Приложение также распечатывает результаты каждого расчета сети. Поскольку сеть не обучалась, эти цифры не имеют смысла и предназначены только для целей отладки. Я хочу убедиться, что две сети на самом деле вычисляют одно и то же, чтобы тест был честным.
Небольшие различия в ответах связаны с округлением с плавающей запятой (мы получаем только 3 десятичных знака точности из-за 16-битной с плавающей запятой, используемой Metal внутри), а также могут быть связаны с различиями в том, как каждая платформа выполняет вычисления конкретно. . Но результаты достаточно близки.
Как работает приложение
Нейронная сеть, созданная этим приложением, имеет 2 сверточных слоя, 1 слой максимального пула, 1 слой среднего пула и 1 полносвязный слой. Затем он измеряет, сколько времени требуется для отправки одного и того же изображения в сеть 100 раз.
Основными исходными файлами для этого являютсяBNNSTest.swiftиMetalTest.swift.
Вы угадали,BNNSTest
класс для создания нейронной сети с использованием функций BNNS. Вот небольшой фрагмент кода, необходимый для создания первого сверточного слоя:
inputImgDesc = BNNSImageStackDescriptor(width: 256, height: 256, channels: 3,
row_stride: 256, image_stride: 256*256,
data_type: dataType, data_scale: 0, data_bias: 0)
conv1imgDesc = BNNSImageStackDescriptor(width: 256, height: 256, channels: 16,
row_stride: 256, image_stride: 256*256,
data_type: dataType, data_scale: 0, data_bias: 0)
let relu = BNNSActivation(function: BNNSActivationFunctionRectifiedLinear,
alpha: 0, beta: 0)
let conv1weightsData = BNNSLayerData(data: conv1weights, data_type: dataType,
data_scale: 0, data_bias: 0, data_table: nil)
let conv1biasData = BNNSLayerData(data: conv1bias, data_type: dataType,
data_scale: 0, data_bias: 0, data_table: nil)
var conv1desc = BNNSConvolutionLayerParameters(x_stride: 1, y_stride: 1,
x_padding: 2, y_padding: 2, k_width: 5, k_height: 5,
in_channels: 3, out_channels: 16,
weights: conv1weightsData, bias: conv1biasData,
activation: relu)
conv1 = BNNSFilterCreateConvolutionLayer(&inputImgDesc, &conv1imgDesc,
&conv1desc, &filterParams)
С BNNS вам нужно создать множество вспомогательных «дескрипторов» для описания данных, которые вы собираетесь использовать, а также свойств и весов иерархии. Эта операция повторяется и для других уровней. Теперь вы понимаете, почему я сказал ранее, что это будет скучно.
MetalTest
использование классаMPSCNN
Сделайте то же самое:
conv1imgDesc = MPSImageDescriptor(channelFormat: channelFormat, width: 256,
height: 256, featureChannels: 16)
let relu = MPSCNNNeuronReLU(device: device, a: 0)
let conv1desc = MPSCNNConvolutionDescriptor(kernelWidth: 5, kernelHeight: 5,
inputFeatureChannels: 3, outputFeatureChannels: 16,
neuronFilter: relu)
conv1 = MPSCNNConvolution(device: device, convolutionDescriptor: conv1desc,
kernelWeights: conv1weights, biasTerms: conv1bias, flags: .none)
Здесь также можно создавать различные объекты-дескрипторы, но код будет короче.
Вы видели, как использовать BNNS для логического вывода: вы вызываете один раз на уровнеBNNSFilterApply()
:
if BNNSFilterApply(conv1, imagePointer, &temp1) != 0 {
print("BNNSFilterApply failed on layer conv1")
}
if BNNSFilterApply(pool1, temp1, &temp2) != 0 {
print("BNNSFilterApply failed on layer pool1")
}
if BNNSFilterApply(conv2, temp2, &temp1) != 0 {
print("BNNSFilterApply failed on layer conv2")
}
if BNNSFilterApply(pool2, temp1, &temp2) != 0 {
print("BNNSFilterApply failed on layer pool2")
}
if BNNSFilterApply(fc3, temp2, &results) != 0 {
print("BNNSFilterApply failed on layer fc3")
}
это здесь,imagePointer
указать на浮点值
Быстрый массив . такой же,temp1
иtemp2
обычныйSwift
浮点值
множество. Мы продолжаем повторно использовать эти массивы для хранения промежуточных результатов. Окончательный вывод сети записывается[Float]
Типresults
середина. Как только сеть закончит вычисления, мы можем немедленно прочитать результат этого массива и использовать его в нашей работе.
Используйте их в другом месте приложения.
Процесс использования MPSCNN очень похож:
let commandBuffer = commandQueue.makeCommandBuffer()
let conv1img = MPSTemporaryImage(commandBuffer: commandBuffer,
imageDescriptor: conv1imgDesc)
conv1.encode(commandBuffer: commandBuffer, sourceImage: inputImage,
destinationImage: conv1img)
let pool1img = MPSTemporaryImage(commandBuffer: commandBuffer,
imageDescriptor: pool1imgDesc)
pool1.encode(commandBuffer: commandBuffer, sourceImage: conv1img,
destinationImage: pool1img)
. . .
fc3.encode(commandBuffer: commandBuffer, sourceImage: pool2img,
destinationImage: outputImage)
commandBuffer.commit()
вы создаетеMPSTemporaryImage
объект для хранения результатов текущего уровня, а затем уведомить уровень, чтобы использовать его на себеencode()
и добавлен в командный буфер Metal. ЭтиMPSTemporaryImage
объект у насBNNS
используется в кодеtemp1
иtemp2
эквивалентны. MPSCNN управляет собственным хранилищем за кулисами.
inputImage
иoutputImage
являются входом и выходом сети соответственно, поэтому они хранятся в постоянныхMPSImage
в объекте.
Обратите внимание, что если вы не вызываете командный буферcommit()
, иначе GPU ничего не сделает. Используя BNNS, каждый звонокBNNSFilterApply()
обработка начнется немедленно. Но в MPSCNNlayer.encode(...)
Создаются только команды GPU, они не выполняются сразу. вызовcommit()
После этого графический процессор начинает обрабатывать данные, а центральный процессор может выполнять другие действия.
На самом деле мы хотим, чтобы на выходе нейронной сети浮点值
массив . BNNS уже умеет работать с обычными массивами Swift, поэтому здесь ничего особенного делать не нужно. Но для MPSCNN нам нужно преобразовать выводMPSImage
Текстура объекта преобразуется во что-то, что мы можем использовать в Swift. в приложенииMPSImage + Floats.swift
Файл содержит вспомогательный код.
Примечание. Если вы используете 16-битные числа с плавающей запятой BNNS (что, скорее всего, и произойдет), то в какой-то момент вам нужно будет преобразовать их обратно в 32-битные числа с плавающей запятой. В демонстрационном приложении это делается перед последним слоем, а не после, потому что полностью подключенный слой не может обрабатывать 16-битные числа с плавающей запятой.
Показатели теста
Я хотел бы провести честное сравнение времени работы одной и той же нейронной сети, созданной в BNNS и MPSCNN.
Я не проверял, сколько времени требуется для преобразования входных данных в правильный формат. Если входными данными является изображение, и с помощью MPSCNN вы можете загрузить его в текстуру и забыть об этом. Но BNNS этого не делает: сначала вам нужно переупорядочить данные изображения в памяти, что может занять очень много времени.
Однако это действительно зависит от того, какую нейронную сеть вы используете, поэтому я не хочу ее измерять. Но это дает BNNS небольшое преимущество в наших тестах скорости, поскольку BNNS медленнее получает входные данные в правильной форме.
Для выходных данных я измерил, сколько времени потребовалось, чтобы преобразовать их обратно в массив Swift. Здесь MPSCNN медленнее, а BNNS вообще не имеет стоимости (если используются 32-битные числа с плавающей запятой). Так что это также приносит пользу BNNS.
Тем не менее, я думаю, что в этом случае справедливо включить преобразования в измерения, поскольку преобразование вывода сети — это то, что вы почти всегда делаете. Это недостаток использования графических процессоров для обычных вычислений, что снижает прирост производительности от использования графических процессоров.
Для честного теста я хотел бы использовать 16-битные числа с плавающей запятой в MPSCNN и BNNS. MPSCNN внутренне всегда сохраняет веса какfloat16
type, поэтому, чтобы быть справедливым, мы также должны позволить BNNS использовать 16-битные числа с плавающей запятой. Недостатком является то, что Swift не имеет типа «половина числа с плавающей запятой», поэтому нам всегда нужно преобразовывать туда и обратно с «настоящими» 32-битными числами с плавающей запятой, даже при использовании BNNS.
Примечания: вViewController.swiftВ файле есть несколько опций, которые позволяют вам изменить то, что тестируется. В частности, он позволяет изменить тип данных параметров обучения и тип данных, которые иерархия использует для выполнения вычислений. Существует также возможность расширить сеть, что увеличивает объем необходимых вычислений, поскольку исходная сеть мала и не обязательно представляет собой настоящую архитектуру глубокого обучения.
Результаты теста
вы готовы?
Для базовой 5-слойной сверточной сети BNNS примерно на 25% быстрее, чем MPSCNN на моем iPhone 6s, используя 16-битные числа с плавающей запятой.
Так что это победа процессора.
Однако, если мы обеспечим больше каналов обработки на каждом уровне (изменив乘数值
), чтобы сделать сеть больше, MPSCNN легко превзойдет BNNS.
MPSCNN также быстрее, чем BNNS, при использовании 32-битных чисел с плавающей запятой. (Возможно, потому что MPSCNN всегда использует внутри себя 16-битные числа с плавающей запятой, но теперь у BNNS вдвое больше работы.)
В качестве всеобъемлющего руководства, если вывод, отправленный в сеть, необходимо сделатьБолее 300 миллионов операций с плавающей запятой, то лучше перейти на MPSCNN.
Я пришел к этой цифре следующим образом:
Number of flops per layer = 2 × kernelWidth × kernelHeight ×
inputChannels × outputChannels ×
outputWidth × outputHeight
Затем я добавил триггеры на каждый уровень и поэкспериментировал с размером сети, чтобы проверить переломный момент, когда MPSCNN становится быстрее, чем BNNS.
Предупреждение: это сверхненаучный эксперимент, и мои расчеты могут оказаться ошибочными. Но если вы выполните фоновые вычисления для глубокой сети и обнаружите, что для этого требуется 1 гигафлопс (1 миллиард операций с плавающей запятой в секунду) или больше, то очевидно, что BNNS не будет работать.
Обратите внимание, однако, что это зависит от многих факторов:
-
Тип оборудования. Я тестировал его только на iPhone 6s. Производительность может отличаться на более медленном iPhone 6 или более быстром iPhone 7.
-
ваши данные. Как я уже говорил, MPSCNN может легко загружать изображения в текстуры, но с BNNS вам нужно сначала полностью переупорядочить данные пикселей. Предварительная обработка, которую необходимо выполнить, влияет на производительность.
-
Точно так же любое преобразование данных, выводимых сетью, для использования в Swift может замедлить обработку.
-
пропускная способность памяти. в моемРеализация VGGNet, параметры обучения занимают около 260 МБ оперативной памяти. Для каждого вывода нейронная сеть должна не только выполнять множество вычислений, но и получать доступ к миллионам ячеек памяти. Вы можете столкнуться с узкими местами пропускной способности в любое время.
Я пытался тестировать как можно более честно, но из-за ошибок и других странностей в обоих фреймворках процесс не был идеальным.
Например, полносвязный слой BNNS не может принимать 16-битные числа с плавающей запятой, поэтому я должен сначала преобразовать данные обратно в 32-битные числа с плавающей запятой. Поскольку полностью подключенный уровень выполняет много вычислений, BNNS может работать быстрее, если поддерживаются эти числа с плавающей запятой половинной точности. Некоторые уровни MPSCNN также имеют свои особенности (см.исходный код).
Примечание. Я не тестировал партии. Оба API могут обрабатывать несколько входных изображений одновременно. Это только увеличивает количество данных, отправляемых в сеть за один раз. Однако у графических процессоров здесь может быть преимущество, поскольку пакетная обработка может лучше использовать пропускную способность графического процессора.
Суммировать
Итак, какой API мне следует использовать? Это зависит от реальной ситуации.
Оба этих API имеют ограниченные функциональные возможности и оставляют желать лучшего. BNNS быстрее для небольших сетей, но медленнее для больших сетей. BNNS также менее функционален, и вам придется писать больше кода самостоятельно. В целом, API BNNS немного уродливее, чем MPSCNN, вероятно, потому, что это C API, импортированный в Swift.
Однако у BNNS есть одно преимущество перед MPSCNN: он также работает на macOS.
намекать:Используйте 16-битные числа с плавающей запятой. Хотя 16-битные числа с плавающей запятой не являются родными для Swift, они позволяют BNNS работать более эффективно, даже если это означает, что вам нужно преобразовать обычные массивы в 16-битные числа с плавающей запятой и обратно.
Лично я, вероятно, остановился бы на MPSCNN. Он более гибкий, и вы можете комбинировать его с быстрыми процедурами умножения матриц Metal Performance Shaders и его собственными вычислительными ядрами.
Самое главное — это то, насколько быстро работает ваше приложение и насколько хорошо работает вывод.
Если ваш проект срочный и вам нужно поторопиться, используйте MPSCNN. Но если вы можете сэкономить время, лучше всего реализовать свою нейронную сеть отдельно, используя эти два API, и сравнить их для достижения наилучшей скорости.
Эта статья была переведена командой переводчиков SwiftGG и была авторизована автором для перевода. Последние статьи можно найти на сайтеswift.gg.