Всех приветствую, меня зовут Антон Рябых, работаю в Doubletapp. Вместе с коллегой Данилом Гальпериным мы написали статью про важный этап в процессе обучения нейронных сетей и получения необходимых нам результатов — оптимизацию модели. Зачем нужно оптимизировать модель, если и так все работает? Но как только вы начнете разворачивать модель на устройстве, которое будет ее обрабатывать, перед вами встанет множество проблем.
Более крупные модели занимают больше места для хранения, что затрудняет их распространение. Более крупные модели требуют больше времени для работы и могут потребовать более дорогого оборудования. Это особенно важно, если вы создаете модель для приложения, работающего в реальном времени.
Оптимизация моделей направлена на уменьшение размера моделей при минимизации потерь в точности и производительности.
Сценарии использования
Увеличение пропускной способности (снижение задержки), полезно как для облачных сервисов, так и для edge-девайсов в виде мобильных устройств, интернета вещей.
Развертывание моделей на edge-устройствах с ограничениями по обработке, памяти и / или энергопотреблению.
Уменьшение размера модели для ускоренного обновления модели и снижения затрат на хранение моделей.
Оптимизация модели полезна для оборудования с ограничениями или оптимизацией для операций с фиксированной точкой.
А также для аппаратных ускорителей специального назначения.
Методы оптимизации
В статье обзорно рассмотрим следующие методы оптимизации:
Pruning — устранение части параметров нейронной сети.
Quantization — уменьшение точности обрабатываемых типов данных.
Knowledge distillation — обновление топологии исходной модели до более эффективной, с уменьшенным количеством параметров и более быстрым выполнением.
Weight clustering — сокращение количества уникальных параметров в весах модели.
OpenVino, TensorRT — фреймворки, с помощью которых можно оптимизировать модели.
Pruning
Устранение части параметров нейронной сети — это метод сжатия, при котором происходит удаление весов из обученной модели. Удаление может производиться как на целых нейронах, так и на отдельных весах. Мы рассмотрим основные методы прунинга нейронной сети.
Одним из способом является прунинг весов, когда некоторым параметрам устанавливается значение в ноль, тем самым создается разреженная сеть. Это уменьшает количество параметров модели, при этом сохраняется целостность архитектуры.Мы получаем сеть с меньшим количеством параметров, но для ее эффективности требуются разреженные вычисления, для которых необходима поддержка оборудования.
Вторым способом является удаление из сети целых узлов (нейронов). Такой способ уменьшает архитектуру сети и позволяет выполнять плотные, более оптимизированные вычисления. Можно работать без разреженных вычислений, и такие вычисления лучше поддерживаются на оборудовании. Однако такой прунинг способен навредить нейронной сети — удалить важные нейроны.
Во время процесса оптимизации модели главное — поддерживать уровень точности на том же уровне или хотя бы ненамного хуже, чем было. Это можно сделать, удалив те элементы модели, которые в меньшей степени влияют на результат работы сети. Существует множество методов для решения поставленной задачи, но для большего понимания подойдут эвристические алгоритмы.
Интуитивно понятно, что можно обрезать те веса модели (свести их к нулю), которые и так имеют достаточно низкое значение по модулю. Для преднамеренного обучения модели заглушать незначимые веса используют регуляризацию L1 или L2.
Подобную логику имеет и удаление нейронов из сети. При запуске набора данных мы можем собрать некоторую статистику активаций. Те нейроны, которые не выдают высокие значения, — редко используются сетью и следовательно могут быть удалены. Помимо величины весов проверяется схожесть с другими выходами текущего слоя, если значения двух выходов статистически повторяются, то можно предположить, что они делают одно и то же. Следовательно, можно удалить один из них, и функциональность при этом не изменится.
В идеале все параметры и активации модели были бы уникальными, и тогда бы не было избыточность сети.
В качества примера можно рассчитать, как изменится сложность небольшой нейронной сети с одним скрытым слоем.
У нас имеется 3 слоя. 1-й слой имеет 6 узлов, 2-й слой — 4 узла и 3-й слой — 2 выходных узла. Для возможности вычисления активации скрытого слоя необходимо произвести 6 операций умножения-накопления (Multiply accumulate — MLA) для каждого узла. Всего необходимо произвести [6*4] + [4*2] = 24 + 8 = 32 MLA операции и держать в памяти 32 параметра.
Предположим, что по каким-то критериям решили удалить красный нейрон. Тогда количество операций изменится на [6*3] + [3*2] = 18 + 6 = 24 MLA операции и также нужно хранить в памяти 24 параметра. Удаление одного нейрона в такой простой сети способствовало снижению вычислительной мощности и объема потребляемой памяти на 25%.
Имея представление об условии удалении весов или узлов, можно применить этот подход к сверточным сетям. Если значения параметров матрицы ядра свертки достаточно малы, то предположительно его активация тоже мала и следовательно оказывает малое влияние на дальнейший результат, а значит, от такого канала можно отказаться.
Теперь вы спросите: «А зачем создавать избыточную архитектуру, которую впоследствии нужно как-то сокращать? Почему бы сразу не построить заведомо меньшую архитектуру без дальнейших оптимизаций?»
Но на самом деле очень сложно обучить меньшую модель с той же точностью, что и более крупную. У крупной модели имеется большее количество пространств поиска оптимального решения, и многое упирается в начальную инициализацию весов.
Quantization
Квантование модели — это популярный метод оптимизации глубокого обучения, при котором данные модели — как параметры сети, так и активации — преобразуются из представления с плавающей запятой в представление с более низкой точностью, например, с использованием 8-битных целых чисел
Это дает несколько преимуществ:
При обработке 8-битных целочисленных данных графические процессоры NVIDIA используют более быстрые и дешевые 8-битные тензорные ядра для вычисления операций свертки и умножения матриц. Это дает большую пропускную способность вычислений.
Перемещение данных из памяти в вычислительные элементы (потоковые мультипроцессоры в графических процессорах NVIDIA) требует времени и энергии, а также выделяет тепло. Снижение точности данных активации и параметров с 32-битных чисел с плавающей запятой до 8-битных целых чисел приводит к 4-кратному сокращению данных, что экономит электроэнергию и снижает выделяемое тепло.
Уменьшение объема памяти означает, что модель требует меньше места для хранения, меньше параметров для обновления, использование кэша выше и т.д.
Методы квантования
Квантование имеет много преимуществ, но снижение точности параметров может легко навредить точности модели. 32-битный тип с плавающей точкой может представлять примерно 4 миллиарда чисел в интервале [-3.4e38, 3.40e38]. Этот интервал представимых чисел также известен как динамический диапазон. Расстояние между двумя соседними представляемыми числами — это точность представления.
В моделях глубокого обучения параметры и данные имеют высокую массу распределения в диапазоне [-1, 1], вероятность, что значение входит в этот диапазон, достаточно высока.
Используя 8-битное целочисленное представление, вы можете представить только 256 различных значений. Эти 256 значений могут быть распределены равномерно или неравномерно, например, для более высокой точности около нуля.
Чтобы преобразовать представление тензора с плавающей запятой x в 8-битное представление xq, необходимо вычислить коэффициент масштабирования (s) и смещения (z). В итоге квантованное значение будет иметь следующий вид:
Теперь поймем, как получить эти коэффициенты. Для этого обозначим, какой диапазон значений имеет изначальный тип x и диапазон квантованных значений xq. Нам нужно решить систему линейных уравнений:
Где β — это верхняя граница диапазона х, βq — верхняя граница диапазона xq, α — нижняя граница диапазона x, αq — нижняя граница диапазона xq.
Отсюда,
Посмотрим на примере: допустим входные значения лежат в диапазоне x [-500, 2050] с типом данных fp32. Нам необходимо преобразовать в signed int8, который лежит в диапазоне xq [-128, 127]. Отсюда s = (2050 + 500)/(127 + 128) = 10,
а z = (-500*127-2050*(-128))/(2050+500)=78.
Когда мы имеем коэффициенты преобразования, мы можем рассчитать любое входное значение:
На практике при процессе квантования есть шанс, что исходное значение находится за пределами допустимого диапазона, таким образом, квантованное значение xq также будет вне диапазона. Поэтому нам необходима операция отсечения значений, не входящих в квантованный диапазон значений:
Использование разных типов для квантования
Тип квантования в основном зависит от операции. Переход от float32 к int8 — не единственный вариант, есть и другие, например, от float32 к float16. Их также можно комбинировать. Например, вы можете квантовать умножения матриц до int8, а активации — до float16.
Квантование — это приближение. В целом, чем ближе приближение, тем меньшее снижение точности можно ожидать. Если вы все квантуете до float16, вы сократите память вдвое и, вероятно, не потеряете точность, но и не получите очень сильного ускорения. С другой стороны, квантование с помощью int8 может привести к гораздо более быстрой обработке, но точность результатов, вероятно, будет хуже. В крайних случаях это даже не сработает и может потребоваться обучение с учетом квантования.
Квантование на практике
Чтобы устранить влияние квантования на качество модели, были разработаны различные методы квантования. Эти методы можно классифицировать как принадлежащие к одной из двух категорий: квантование после обучения (PTQ) или обучение с учетом квантования (QAT).
Как следует из названия, PTQ выполняется после обучения высокоточной модели. С помощью PTQ квантовать веса очень просто — у вас есть доступ к тензорам весов, и вы можете измерить их распределения.
Количественное определение активаций является более сложной задачей, поскольку распределения активаций необходимо измерять с использованием реальных входных данных. Для этого обученная модель с плавающей запятой оценивается с использованием небольшого набора данных, представляющего реальные входные данные задачи, и собирается статистика о распределениях активаций. На заключительном этапе масштабы квантования тензоров активации модели определяются с использованием одной из нескольких целей оптимизации. Этот процесс является калибровкой, и используемый репрезентативный набор данных — набором данных калибровки.
Иногда PTQ не может достичь приемлемой точности на задаче. Тогда вы можете подумать об использовании QAT. Идея QAT проста: вы можете повысить точность квантованных моделей, если включите ошибку квантования в фазу обучения. Это позволяет сети адаптироваться к квантованным весам и активациям.
Существуют различные подходы выполнения QAT — от начала с необученной модели до начала с предварительно обученной модели. Все подходы изменяют режим обучения, чтобы включить ошибку квантования в потери при обучении, вставляя операции ложного квантования в обучающий граф для имитации квантования данных и параметров. Эти операции называются «фальшивыми», потому что они квантуют данные, но затем немедленно деквантовывают данные, чтобы вычисление операции оставалось с точностью до числа с плавающей запятой. Этот трюк добавляет квантование без особых изменений в структуре глубокого обучения.
PTQ — более популярный метод из двух, потому что он прост и является более быстрым методом. Однако QAT почти всегда дает лучшую точность, а иногда это единственный приемлемый метод.
Knowledge distillation
Перенос чрезвычайно огромной модели с миллионами или миллиардами параметров, обученной с помощью высокопроизводительных графических процессоров, на устройство обработки реальных данных может быть невозможен из-за ограничений в ресурсах периферийного устройства.
Поэтому был разработан метод извлечения знаний из большой модели с большим количеством параметров в более легковесную модель. Такая модель учится повторять поведение крупной модели, ее выходные результаты на каждом слое. Обычно такую комбинацию называют «ученик — учитель».
Посмотрим на примере задачи классификации. При передаче знаний от учителя к ученику минимизируется функция потерь распределения классов, предсказанных моделью учителя. Обычно, в случае точных моделей, когда предсказание вероятности одного из классов (верного) близко к 1, а всех остальных — приближены к 0, такие данные мало помогут сети ученика, так как они практически не отличаются от исходной разметки. Поэтому был придуман softmax temperature, который помогает сети ученика повторять не разметку классификации, а вероятностное распределение, что позволяет модели ученика лучше перенять поведение учителя.
Отличия от обучения с нуля
Очевидно, что при использовании более сложных моделей теоретическое пространство поиска больше, чем у меньшей сети. Однако если мы предположим, что такая же (или даже похожая) сходимость может быть достигнута с использованием меньшей сети, то пространство сходимости сети учителя должно перекрываться с пространством решений сети ученика.
К сожалению, это само по себе не гарантирует сходимость сети ученика. Сеть-ученик может иметь сходимость, которая может сильно отличаться от сходимости сети учителя. Однако если сеть-ученик направлена на то, чтобы воспроизвести поведение сети учителя (которая уже провела поиск в большем пространстве решений), ожидается, что ее пространство сходимости перекроется с исходным пространством сходимости учительской сети.
Сети учителей и учеников — как это реализовать?
Обучите сеть-учитель. Очень сложная сеть-учитель сначала обучается отдельно с использованием полного набора данных. Это может быть очень сложная и глубокая сеть, которую можно использовать в качестве сети учителя.
Установите соответствие. При проектировании сети-ученика необходимо установить соответствие между промежуточными выходами сети-ученика и учительской сети. Это соответствие может включать в себя непосредственную передачу результата слоя в сети учителя в сеть ученика или выполнение некоторого преобразования данных перед их передачей в сеть ученика.
Forward pass через сеть учителя. Пропустите данные через сеть учителя, чтобы получить все промежуточные результаты.
Back propogation через ученическую сеть. Теперь используйте выходные данные из учительской сети и отношение соответствия для обратного распространения ошибки в ученической сети, чтобы она могла научиться воспроизводить поведение учительской сети.
Weight clustering
Кластеризация весов — это метод уменьшения объема хранения вашей модели путем замены многих уникальных значений параметров меньшим количеством уникальных значений. Наряду с поддержкой платформы и аппаратного обеспечения, кластеризация весов может дополнительно уменьшить требуемые объем памяти и увеличить скорость обработки.
Вот объяснение схемы. Представьте, например, что слой в вашей модели содержит матрицу весов 4×4. Каждый вес сохраняется с использованием значения float32. Когда вы сохраняете модель, вы сохраняете на диск 16 уникальных значений float32.
Кластеризация весов уменьшает размер вашей модели, заменяя аналогичные веса в слое с тем же значением. Эти значения находятся путем запуска алгоритма кластеризации по обученным весам модели. Пользователь может указать количество кластеров (в данном случае 4). Этот шаг показан в разделе «Get centroids» на диаграмме выше, а 4 значения центроидов показаны в таблице «Центроиды». Каждое значение центроида имеет индекс (0–3).
Затем каждый вес в весовой матрице заменяется индексом его центроида. Этот шаг показан в разделе «Assign indices». Теперь вместо сохранения исходной матрицы весов алгоритм кластеризации весов может сохранять модифицированную матрицу, показанную в «Pull indices» (содержащие индекс значений центроидов), и сами значения центроидов.
В этом случае мы уменьшили размер с 16 уникальных чисел с плавающей запятой до 4 чисел с плавающей запятой и 16 2-битных индексов. Экономия увеличивается с увеличением размера матрицы.
Обратите внимание, что даже если мы все еще сохранили 16 чисел с плавающей запятой, теперь у них есть только 4 различных значения. Общие инструменты сжатия (например, zip) теперь могут использовать преимущества избыточности данных для достижения более высокого сжатия.
Преимущества кластеризации весов
Кластеризация весов имеет непосредственное преимущество в сокращении веса модели и размера передачи между форматами сериализации. После кластеризации модели можно дополнительно уменьшить ее размер, пропустив ее через любой обычный инструмент сжатия.
Результаты сжатия и точности
Эксперименты проводились на нескольких популярных моделях, демонстрирующих преимущества сжатия при кластеризации веса. Могут применяться более агрессивные оптимизации, но они уменьшат точность. Хотя в таблице ниже приведены измерения для моделей TensorFlow Lite, аналогичные преимущества наблюдаются и для других форматов сериализации.
В таблице ниже показано, как была настроена кластеризация для достижения результатов. Некоторые модели были более склонны к снижению точности из-за агрессивной кластеризации, и в этом случае выборочная кластеризация использовалась на слоях, которые более устойчивы к оптимизации.
Фреймворки для оптимизации под конечное устройство
OpenVINO
OpenVINO toolkit (или Intel Distribution of OpenVINO Toolkit) — это открытый бесплатный набор инструментов, который помогает ускорить разработку высокопроизводительных решений для использования в различных видеосистемах.
Этот комплексный набор инструментов поддерживает весь спектр решений для компьютерного зрения, который оптимизирует развертывание глубокого обучения и обеспечивает простое исполнение на различных платформах Intel.
OpenVINO решает самые разнообразные задачи, включая детектирование лица, автоматическое распознавание объектов, текста и речи, обработку изображений и многое другое.
Производительность OpenVINO при вычислении сетей на платформах Intel в разы выше по сравнению с популярными фреймворками. Также значительно ниже требования по используемой памяти, что актуально для ряда приложений: на некоторых платформах невозможно запустить сеть с использованием фреймворков по причине нехватки памяти.
Какие есть инструменты в OpenVINO
-
Deep Learning Model Optimizer (Оптимизатор моделей глубокого обучения) — кроссплатформенный инструмент для импорта моделей и подготовки их к оптимизированному выполнению. Оптимизатор моделей конвертирует и оптимизирует модели популярных фреймворков (таких как Caffe, TensorFlow, MXNet, Kaldi и ONNX) во внутренний формат IR, который используется для представления модели внутри OpenVINO.
Оптимизатор моделей глубокого обучения включает два компонента:Model Optimizer — компонент для конвертации предварительно обученных моделей из формата какого-либо обучающего фреймворка в промежуточный формат (Intermediate Representation, IR) OpenVINO. Поддерживаемые форматы моделей: ONNX, TensorFlow, Caffe, MXNet, Kaldi
Inference Engine — компонент для эффективного инференса (запуска) моделей.
Open Model Zoo — открытый репозиторий обученных моделей для решения различных задач. Содержит набор широко известных публичных моделей (более 20) и моделей, решающих различные задачи компьютерного зрения и обученных сотрудниками компании Intel (более 100). В составе можно обнаружить множество примеров и демоприложений, демонстрирующих использование доступных моделей.
Предсобранный OpenCV — версия OpenCV, скомпилированная для оборудования Intel.
Post-training Optimization tool — инструмент для калибровки модели и последующего ее инференса с точностью INT8.
Deep Learning Workbench — веб-графическая среда, позволяющая легко использовать различные сложные компоненты набора инструментов OpenVINO toolkit.
Demo applications — набор примеров.
TensorRT
TensorRT — специальный фреймворк, который максимально утилизирует мощь видеокарты для нейронных сетей.
Приложения на основе TensorRT работают до 40 раз быстрее, чем платформы, использующие только CPU. С помощью TensorRT вы можете оптимизировать модели нейронных сетей, обученные во всех основных средах, провести квантование и развернуть решение в гипермасштабируемых центрах обработки данных, edge-девайсах или автомобильных платформах.
TensorRT построен на CUDA, модели параллельного программирования NVIDIA, и позволяет оптимизировать операции, используя библиотеки, инструменты разработки и технологии CUDA-X для искусственного интеллекта, автономных машин, высокопроизводительных вычислений и графики. С новыми графическим процессорами с архитектурой NVIDIA Ampere TensorRT также использует разреженные тензорные ядра, обеспечивая дополнительный прирост производительности.
TensorRT предоставляет INT8 вычисления с использованием Quantization Aware Training и Post Training Quantization, а также оптимизацию FP16 для производственных развертываний приложений для глубокого обучения, таких как потоковое видео, распознавание речи, рекомендации, обнаружение фрода, генерация текста и обработка естественного языка. Квантование значительно снижает время обработки, что является требованием для многих сервисов, работающих в реальном времени, а также для встроенных приложений.
TensorRT интегрирован с PyTorch и TensorFlow, поэтому вы можете добиться ускорения работы сетей в кратчайшие сроки.
Заключение
В заключение можно сказать, что область оптимизации нейронных сетей значительно продвинулась за последние годы благодаря развитию усовершенствованных методов, таких как прунинг, квантование, дистилляция знаний и кластеризация весов. Эти методы позволяют улучшить производительность и эффективность нейронных сетей, а также уменьшить их размер и вычислительные требования. Сочетая эти методы, мы можем создавать модели, которые одновременно точные и легкие, что делает их идеальными для развертывания на edge-устройствах и других ресурсно-ограниченных средах.
Поскольку область машинного обучения продолжает развиваться, ясно, что оптимизация нейронных сетей останется ключевой областью исследований и разработок. Следуя новейшим методам и лучшим практикам, мы можем продолжать улучшать точность и эффективность наших моделей, что делает искусственный интеллект более доступным и значимым, чем когда-либо ранее.
pagin
Большое спасибо за статью, очень люблю эту тематику. Иногда полезно почитать такие общие статьи для повторения и актуализации информации в голове.
Дополню насчет float32 -> float16. К сожалению, на практике после такой конвертации теряется довольно важная часть точности. Она может слабо выражаться в метриках, но иногда после такого сети непригодны для моментальной отправки в прод. Не рекламируюсь ни в коем случае, но вот в своей статье описывал, к чему это иногда ведет -https://habr.com/ru/post/558406/. Желательно даже при таком квантовании использовать Quantization Aware Training, который, благо, реализуется в 1 строчку в TF или PyTorch.
Ну и насчет скорости работы - смотря что считать большим ускорением. У нас получилось около 2х к скорости обработки. Учитывая относительную простоту float32 -> float16 получается очень высокий КПД.
А вот при квантовании float32 -> int8 нужно потратить довольно много усилий, чтобы это заработало с достаточной точностью. Знаю, что такое часто делают для мобилок в различных GANах и дифьюзерах, т.к. визуальный результат клиента удовлетворяет. А вот если стоит задача детекции, классификации и тд, где наша метрика точности более осязаема - возникают большие проблемы. Ну и тут сложнее с Quantization Aware Training - методики вроде как есть, но это уже не одна строчка и часто ломаются оптимизаторы. Если вы знакомы с хорошими методиками Quantization Aware Training для int8, то поделитесь. Я уже полгода не смотрел новых работ по этой теме
И насчет Pruning - там тоже есть 2 варианта: Post Training и During Training. Первый вариант на моей практике был не очень полезным, без потери точности получалось вырезать лишь малую часть. А вот второй вариант пока руки не дошли попробовать. Подскажите, может был опыт? Очень интересны практические результаты
lenant Автор
Спасибо, что поделились своим практическим опытом! Pruning During Training на практике я не использовал, так что по нему подсказать не смогу, к сожалению.
vshampor
https://github.com/openvinotoolkit/nncf