Генерация кода для квантованных нейронных сетей для глубокого обучения

В глубоком обучении используются нейронные сетевые архитектуры, которые содержат много слоев обработки, включая сверточные слои. Модели глубокого обучения обычно работают с большими наборами маркированных данных. Настройка этих моделей и выполнение вывода является вычислительно интенсивным, потребляя значительный объем памяти. Нейронные сети используют память для хранения входных данных, параметров (весов) и активаций с каждого слоя, когда вход распространяется через сеть. Большинство предварительно обученных нейронных сетей и нейронных сетей, обученных с помощью Deep Learning Toolbox™, используют одноточные типы данных с плавающей точкой. Даже малые по размеру сети требуют значительного объема памяти и оборудования для выполнения этих арифметических операций с плавающей точкой. Эти ограничения могут препятствовать развертыванию моделей глубокого обучения на устройствах с низкой вычислительной степенью и меньшими ресурсами памяти. Используя более низкую точность для хранения весов и активаций, можно уменьшить требования сети к памяти.

Можно использовать Deep Learning Toolbox в паре с пакетом поддержки Deep Learning Toolbox Model Quantization Library, чтобы уменьшить площадь памяти глубокой нейронной сети путем квантования весов, смещений и активаций слоев свертки до 8-битных масштабированных целочисленных типов данных. Затем можно использовать GPU Coder™ для генерации оптимизированного CUDA® код для квантованной сети. Сгенерированный код использует преимущества NVIDIA® Библиотека глубоких нейронных сетей CUDA (cuDNN) или TensorRT™ библиотека вывода высокой эффективности. сгенерированный код может быть интегрирован в проект как исходный код, статические или динамические библиотеки или исполняемые файлы, которые можно развернуть на различных платформах графических процессоров NVIDIA.

Классификация изображений на графическом процессоре с помощью квантованной сети

В этом примере вы используете GPU Coder, чтобы сгенерировать код CUDA для квантованной глубокой сверточной нейронной сети и классифицировать изображение. В примере используется предварительно обученная squeezenet (Deep Learning Toolbox) сверточная нейронная сеть для демонстрации передачи обучения, квантования и генерации кода CUDA для квантованной сети.

SqueezeNet обучен на более чем миллионе изображений и может классифицировать изображения в 1000 категорий объектов (таких как клавиатура, кофейная кружка, карандаш и многие животные). Сеть изучила представления богатых функций для широкой области значений изображений. Сеть принимает изображение как вход и выводит метку для объекта в изображении вместе с вероятностями для каждой из категорий объектов.

Необходимые условия для третьих лиц

Необходимый

  • CUDA включает графический процессор NVIDIA ® и совместимый драйвер.

Дополнительный

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

Передача обучения с помощью SqueezeNet

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

Загрузка обучающих данных

Разархивируйте и загружайте новые изображения как image datastore. The imageDatastore функция автоматически помечает изображения на основе имен папок и сохраняет данные как ImageDatastore объект. image datastore позволяет вам хранить большие данные изображения, включая данные, которые не помещаются в памяти, и эффективно считывать пакеты изображений во время обучения сверточной нейронной сети. Разделите данные на наборы данных для обучения и валидации. Используйте 70% изображений для обучения и 30% для валидации. splitEachLabel разделяет imds datastore в два новых хранилища данных.

unzip('MerchData.zip');
imds = imageDatastore('MerchData', ...
    'IncludeSubfolders',true, ...
    'LabelSource','foldernames');
[imdsTrain,imdsValidation] = splitEachLabel(imds,0.7,'randomized');

numTrainImages = numel(imdsTrain.Labels);
idx = randperm(numTrainImages,4);
img = imtile(imds, 'Frames', idx);

figure
imshow(img)
title('Random Images from Training Dataset');

Загрузка предварительно обученной сети

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

net = squeezenet;

Объект net содержит DAGNetwork объект. Первый слой, входной слой для изображений, требует входа изображений размера 227 227 3, где 3 количество цветовых каналов. Можно использовать analyzeNetwork (Deep Learning Toolbox) функция для отображения интерактивной визуализации сетевой архитектуры, для обнаружения ошибок и проблем в сети, а также для отображения подробной информации о слоях сети. Информация о слое включает размеры активации слоя и настраиваемые параметры, общее количество настраиваемых параметров и размеры параметров состояния рекуррентных слоев.

inputSize = net.Layers(1).InputSize;

Замена конечных слоев

Сверточные слои сети извлекают изображение, функции последний выучиваемый слой и конечный слой классификации используют для классификации входа изображения. Эти два слоя, 'conv10' и 'ClassificationLayer_predictions' в SqueezeNet содержат информацию о том, как объединить функции, которые сеть извлекает в вероятности классов, значение потерь и предсказанные метки.

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

lgraph = layerGraph(net); 
[learnableLayer,classLayer] = findLayersToReplace(lgraph);
numClasses = numel(categories(imdsTrain.Labels));

newConvLayer =  convolution2dLayer([1, 1],numClasses,'WeightLearnRateFactor',...
10,'BiasLearnRateFactor',10,"Name",'new_conv');
lgraph = replaceLayer(lgraph,'conv10',newConvLayer);

newClassificatonLayer = classificationLayer('Name','new_classoutput');
lgraph = replaceLayer(lgraph,'ClassificationLayer_predictions',newClassificatonLayer);

Обучите сеть

Сеть требует входа изображений размера 227 227 3, но изображения в хранилищах данных изображений имеют различные размеры. Используйте хранилище данных дополненных изображений, чтобы автоматически изменить размер обучающих изображений. Задайте дополнительные операции увеличения для выполнения на обучающих изображениях: случайным образом разверните обучающие изображения вдоль вертикальной оси и случайным образом переведите их до 30 пикселей горизонтально и вертикально. Увеличение количества данных помогает предотвратить сверхподбор кривой сети и запоминание точных деталей обучающих изображений.

pixelRange = [-30 30];
imageAugmenter = imageDataAugmenter( ...
    'RandXReflection',true, ...
    'RandXTranslation',pixelRange, ...
    'RandYTranslation',pixelRange);
augimdsTrain = augmentedImageDatastore(inputSize(1:2),imdsTrain, ...
    'DataAugmentation',imageAugmenter);

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

augimdsValidation = augmentedImageDatastore(inputSize(1:2),imdsValidation);

Задайте опции обучения. Для передачи обучения сохраните функции из ранних слоев предварительно обученной сети (веса переданных слоев). Чтобы замедлить обучение в перенесенных слоях, установите начальную скорость обучения на небольшое значение. На предыдущем шаге вы увеличили коэффициенты скорости обучения для сверточного слоя, чтобы ускорить обучение в новых конечных слоях. Эта комбинация настроек скорости обучения приводит к быстрому обучению только в новых слоях и более медленному обучению в других слоях. При выполнении передачи обучения вам не нужно тренироваться на столько эпох. Эпоха - это полный цикл обучения на целом наборе обучающих данных. Задайте размер мини-пакета равным 11, чтобы в каждую эпоху вы учитывали все данные. Программное обеспечение проверяет сеть каждый ValidationFrequency итерации во время обучения.

options = trainingOptions('sgdm', ...
    'MiniBatchSize',11, ...
    'MaxEpochs',7, ...
    'InitialLearnRate',2e-4, ...
    'Shuffle','every-epoch', ...
    'ValidationData',augimdsValidation, ...
    'ValidationFrequency',3, ...
    'Verbose',false, ...
    'Plots','training-progress');

Обучите сеть, которая состоит из переданного и нового слоев.

netTransfer = trainNetwork(augimdsTrain,lgraph,options);

classNames = netTransfer.Layers(end).Classes;
save('mySqueezenet.mat','netTransfer');

Квантование сети

Создайте dlquantizer объект и укажите сеть для квантования.

quantObj = dlquantizer(netTransfer);

Задайте метрическую функцию, которая будет использоваться для сравнения поведения сети до и после квантования.

type('hComputeModelAccuracy.m');
function accuracy = hComputeModelAccuracy(predictionScores, net, dataStore)
%% Computes model-level accuracy statistics
    
    % Load ground truth
    tmp = readall(dataStore);
    groundTruth = tmp.response;
    
    % Compare with predicted label with actual ground truth 
    predictionError = {};
    for idx=1:numel(groundTruth)
        [~, idy] = max(predictionScores(idx,:)); 
        yActual = net.Layers(end).Classes(idy);
        predictionError{end+1} = (yActual == groundTruth(idx)); %#ok
    end
    
    % Sum all prediction errors.
    predictionError = [predictionError{:}];
    accuracy = sum(predictionError)/numel(predictionError);
end

Задайте метрическую функцию в dlquantizationOptions объект.

quantOpts = dlquantizationOptions('MetricFcn', ...
    {@(x)hComputeModelAccuracy(x,netTransfer,augimdsValidation)});

Используйте calibrate функция для упражнения сети с выборочными входами и сбора информации о области значений. The calibrate функция реализует сеть и собирает динамические области значений весов и смещений в свертках и полносвязных слоях сети и динамические области значений активаций во всех слоях сети. Функция возвращает таблицу. Каждая строка таблицы содержит информацию о области значений для настраиваемого параметра оптимизированной сети.

calResults = calibrate(quantObj,augimdsTrain);
save('squeezenetCalResults.mat','calResults');
save('squeezenetQuantObj.mat','quantObj');

Можно использовать validate функция для квантования настраиваемых параметров в слоях свертки сети и осуществления сети. Функция использует метрическую функцию, определенную в dlquantizationOptions объект для сравнения результатов сети до и после квантования.

valResults = validate(quantObj,augimdsValidation,quantOpts);

Создайте функцию точки входа

Напишите функцию точки входа в MATLAB, которая:

type('predict_int8.m');
function out = predict_int8(netFile, in)

    persistent mynet;
    if isempty(mynet)
        mynet = coder.loadDeepLearningNetwork(netFile);
    end
    out = predict(mynet,in);
end

Постоянный объект mynet загружает DAGNetwork объект. При первом вызове функции точки входа создается и настраивается постоянный объект. При последующих вызовах функции тот же объект повторно используется для вызова predict на входах, избегая восстановления и перезагрузки объекта сети.

Примечание

Убедитесь, что все операции предварительной обработки, выполненные в шагах калибровки и валидации, включены в файл проекта.

Генерация кода при помощи codegen

Чтобы сконфигурировать настройки сборки, такие как имя выходного файла, расположение и тип, вы создаете объекты строения кодера. Чтобы создать объекты, используйте coder.gpuConfig функция. Для примера при генерации CUDA MEX с помощью codegen команда, использование cfg = coder.gpuConfig('mex');

Чтобы задать параметры генерации кода для cuDNN, установите DeepLearningConfig свойство для coder.CuDNNConfig объект, который вы создаете при помощи coder.DeepLearningConfig.

cfg = coder.gpuConfig('mex');
cfg.TargetLang = 'C++';
cfg.GpuConfig.ComputeCapability = '6.1';
cfg.DeepLearningConfig = coder.DeepLearningConfig('cudnn');
cfg.DeepLearningConfig.AutoTuning = true;
cfg.DeepLearningConfig.CalibrationResultFile = 'squeezenetQuantObj.mat';
cfg.DeepLearningConfig.DataType = 'int8';

Укажите местоположение MAT-файла, содержащего данные калибровки.

Задайте точность расчетов вывода в поддерживаемых слоях при помощи DataType свойство. Для 8-битного целого числа используйте 'int8'. Используйте ComputeCapability свойство объекта строения кода, чтобы задать соответствующее вычислительное значение возможности.

Запуск codegen команда. The codegen команда генерирует код CUDA из predict_int8.m Функция точки входа MATLAB.

inputs = {coder.Constant('mySqueezenet.mat'),ones(inputSize,'uint8')};
codegen -config cfg -args inputs predict_int8
Code generation successful.

Когда генерация кода успешна, можно просмотреть результат отчета генерации кода, нажав View Report в Командном Окне MATLAB. Отчет отображается в окне Средство Просмотра. Если генератор кода обнаруживает ошибки или предупреждения во время генерации кода, отчет описывает проблемы и предоставляет ссылки на проблемный код MATLAB.

Запуск сгенерированного MEX

Изображение, которое вы хотите классифицировать, должно иметь тот же размер, что и вход сигнала сети. Считайте изображение, которое вы хотите классифицировать, и измените его размер на вход сети. Это изменение размера немного изменяет соотношение сторон изображения.

testImage = imread("MerchDataTest.jpg");
testImage = imresize(testImage,inputSize(1:2));

Вызов SqueezeNet предсказать на вход изображении.

predictScores(:,1) =  predict(netTransfer,testImage)';
predictScores(:,2) = predict_int8_mex('mySqueezenet.mat',testImage);

Отобразите предсказанные метки и связанные с ними вероятности в виде гистограммы.

h = figure;
h.Position(3) = 2*h.Position(3);
ax1 = subplot(1,2,1);
ax2 = subplot(1,2,2);

image(ax1,testImage);
barh(ax2,predictScores)
xlabel(ax2,'Probability')
yticklabels(ax2,classNames)
ax2.XLim = [0 1.1];
ax2.YAxisLocation = 'left';
legend('Matlab Single','cuDNN 8-bit integer');
sgtitle('Predictions using Squeezenet')

Вспомогательные функции

function [learnableLayer,classLayer] = findLayersToReplace(lgraph)
% findLayersToReplace(lgraph) finds the single classification layer and the
% preceding learnable (fully connected or convolutional) layer of the layer
% graph lgraph.

if ~isa(lgraph,'nnet.cnn.LayerGraph')
    error('Argument must be a LayerGraph object.')
end

% Get source, destination, and layer names.
src = string(lgraph.Connections.Source);
dst = string(lgraph.Connections.Destination);
layerNames = string({lgraph.Layers.Name}');

% Find the classification layer. The layer graph must have a single
% classification layer.
isClassificationLayer = arrayfun(@(l) ...
    (isa(l,'nnet.cnn.layer.ClassificationOutputLayer') ...
|isa(l,'nnet.layer.ClassificationLayer')), ...
    lgraph.Layers);

if sum(isClassificationLayer) ~= 1
    error('Layer graph must have a single classification layer.')
end
classLayer = lgraph.Layers(isClassificationLayer);


% Traverse the layer graph in reverse starting from the classification
% layer. If the network branches, throw an error.
currentLayerIdx = find(isClassificationLayer);
while true
    
    if numel(currentLayerIdx) ~= 1
        msg = ['Layer graph must have a single learnable ' ...
            'layer preceding the classification layer.'];
        error(msg);
    end
    
    currentLayerType = class(lgraph.Layers(currentLayerIdx));
    isLearnableLayer = ismember(currentLayerType, ...
        ['nnet.cnn.layer.FullyConnectedLayer','nnet.cnn.layer.Convolution2DLayer']);
    
    if isLearnableLayer
        learnableLayer =  lgraph.Layers(currentLayerIdx);
        return
    end
    
    currentDstIdx = find(layerNames(currentLayerIdx) == dst);
    currentLayerIdx = find(src(currentDstIdx) == layerNames);
    
end

end

Ограничения

  • При выполнении вывода в INT8 точность использования cuDNN версии 8.1.0, проблемы в библиотеке NVIDIA могут привести к значительному снижению эффективности.

  • Следующие слои не поддерживаются для 8-битного целочисленного квантования при нацеливании на библиотеку глубоких нейронных сетей (cuDNN) NVIDIA CUDA.

    • leakyReluLayer

    • clippedReluLayer

    • globalAveragePooling2dLayer

См. также

Приложения

Функции

Объекты

Похожие темы