exponenta event banner

Создание кода для квантованной сети глубокого обучения на Raspberry Pi

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

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

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

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

Предварительные условия для сторонних производителей

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

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

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

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

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

  2. Квантование модифицированной сети SqueeEcNet.

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

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

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

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

Загрузка данных обучения

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

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

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

Загрузите предварительно обученную сеть SqueeEcNet.

net=squeezenet;

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

inputSize = net.Layers(1).InputSize;
analyzeNetwork(net);

Заменить конечные слои

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

Для переподготовки заранее обученной сети для классификации новых изображений замените эти два слоя новыми слоями, адаптированными к новому набору данных. Это можно сделать вручную или с помощью функции помощника 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, но у каждого изображения в хранилищах данных изображения есть differnet размер. Используйте хранилище данных дополненного изображения для автоматического изменения размеров обучающих изображений. Задайте следующие дополнительные операции увеличения, которые должны выполняться на обучающих изображениях: случайным образом переверните обучающие изображения вокруг вертикальной оси и произвольно переместите их до 30 пикселей по горизонтали и вертикали. Увеличение объема данных помогает предотвратить перенапряжение сети и запоминание точных деталей обучающих изображений.

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

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

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 функция для осуществления сети с входами образцов и сбора информации о дальности. 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 и укажите версию библиотеки. В этом примере предположим, что вычислительная библиотека ARM в аппаратном обеспечении 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 Support Package for Raspberry Pi, raspi, чтобы создать соединение с Raspberry Pi. В следующем коде замените:

  • raspiname с именем вашего Малинового Пи

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

См. также

Приложения

Функции

Объекты

Связанные темы