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

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

Можно использовать Deep Learning Toolbox в паре с пакетом поддержки Deep Learning Toolbox Model Quantization Library, чтобы уменьшить площадь памяти глубокой нейронной сети путем квантования весов, смещений и активаций слоев свертки до 8-битных масштабированных целочисленных типов данных. Затем можно использовать MATLAB Coder™, чтобы сгенерировать оптимизированный код для квантованной сети. Сгенерированный код использует преимущества процессора ARM ® SIMD при помощи библиотеки ARM Compute. Сгенерированный код может быть интегрирован в ваш проект как исходный код, статические или динамические библиотеки или исполняемые файлы, которые можно развернуть на различных платформах ARM CPU, таких как Raspberry Pi™.

В этом примере показано, как сгенерировать код С++ для сверточной нейронной сети, которая использует ARM Compute Library и выполняет расчеты вывода в 8-битных целых числах.

Этот пример не поддерживается для MATLAB Online.

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

Пример: Классификация изображений с помощью SqueezeNet

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

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

Этот пример состоит из четырех шагов:

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

  2. Квантуйте измененную сеть SqueezeNet.

  3. Сгенерируйте код для квантованной сети с помощью команды codegen. Сгенерированный код запускается на цели Raspberry Pi посредством выполнения PIL.

  4. Выполните сгенерированный PIL MEX на Raspberry Pi.

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

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

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

Разархивируйте и загружайте новые изображения как image datastore. The imageDatastore функция автоматически помечает изображения на основе имен папок и сохраняет данные как ImageDatastore объект. image 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;
analyzeNetwork(net);

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

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

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

Это значение по findLayersToReplace вспомогательная функция:

type findLayersToReplace.m
% findLayersToReplace(lgraph) finds the single classification layer and the
% preceding learnable (fully connected or convolutional) layer of the layer
% graph lgraph.

function [learnableLayer,classLayer] = findLayersToReplace(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
        error('Layer graph must have a single learnable layer preceding the classification layer.')
    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

Чтобы использовать эту функцию для замены конечных слоев, запустите следующие команды:

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);

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

netTransfer = trainNetwork(augimdsTrain,lgraph,options);
classNames = netTransfer.Layers(end).Classes;
save('mySqueezenet.mat','netTransfer');

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

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

quantObj = dlquantizer(netTransfer, 'ExecutionEnvironment', 'CPU');

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

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

Сгенерируйте PIL- MEX-функции

В этом примере вы генерируете код для функции точки входа predict_int8. Эта функция использует coder.loadDeepLearningNetwork функция для загрузки модели глубокого обучения и создания и настройки класса CNN. Затем функция точки входа предсказывает ответы при помощи predict (Deep Learning Toolbox) функция.

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

Чтобы сгенерировать функцию PIL MEX, создайте объект строения кода для статической библиотеки и установите режим верификации равным 'PIL'. Установите целевой язык на C++.

 cfg = coder.config('lib', 'ecoder', true);
 cfg.VerificationMode = 'PIL';
 cfg.TargetLang = 'C++';

Создайте объект строения глубокого обучения для библиотеки ARM Compute и укажите версию библиотеки. В данном примере предположим, что ARM Compute Library в оборудовании Raspberry Pi является версией 20.02.1.

 dlcfg = coder.DeepLearningConfig('arm-compute');
 dlcfg.ArmComputeVersion = '20.02.1';

Установите свойства dlcfg чтобы сгенерировать код для вывода с низким precision/INT8.

 dlcfg.CalibrationResultFile = 'squeezenetQuantObj.mat'; 
 dlcfg.DataType = 'int8';

6. Установите DeepLearningConfig свойство cfg на dlcfg.

 cfg.DeepLearningConfig = dlcfg;

7. Используйте пакет поддержки MATLAB для функции Raspberry Pi, raspi, для создания соединения с Raspberry Pi. В следующем коде замените:

  • raspiname с именем вашего Raspberry Pi

  • username с вашим именем пользователя

  • password с вашим паролем

%  r = raspi('raspiname','username','password');

8. Создайте coder.Hardware объект для Raspberry Pi и присоедините его к объекту строения генерации кода.

% hw = coder.hardware('Raspberry Pi');
% cfg.Hardware = hw;

9. Сгенерируйте функцию PIL MEX при помощи codegen команда

% codegen -config cfg predict_int8 -args {coder.Constant('mySqueezenet.mat'), ones(227,227,3,'uint8')}

Запуск сгенерированных PIL- MEX-функции на Raspberry Pi

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

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

Сравнение предсказаний Deep Learning Toolbox predict функция и сгенерированная функция PIL MEX predict_int8_pilвызовите оба этих функциона на вход изображении отдельно.

% predictScores(:,1) =  predict(netTransfer,testImage)';
% predictScores(:,2) = predict_int8_pil('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','arm-compute 8-bit integer');
% sgtitle('Predictions using Squeezenet')
% saveas(gcf,'SqueeznetPredictionComparison.jpg');
% close(gcf);
imshow('SqueeznetPredictionComparison.jpg');

См. также

Приложения

Функции

Объекты

Похожие темы