Main Content

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

Raspberry Pi での深層学習ネットワークの INT8 コードの生成

深層学習では、畳み込み層などのさまざまな処理層を含むニューラル ネットワーク アーキテクチャが使用されます。深層学習モデルは通常、ラベル付きデータの大量のセットに対して動作します。これらのモデルで推論を実行すると、計算量が多くなり、大量のメモリが消費されます。ニューラル ネットワークでは、入力がネットワーク経由で伝播するときに、各層からの入力データ、パラメーター (重み)、および活性化を保存するためにメモリが使用されます。MATLAB で学習させた深層ニューラル ネットワークは、単精度浮動小数点データ型を使用します。小規模なネットワークでも、これらの浮動小数点算術演算を実行するには大量のメモリとハードウェアが必要です。このような制限は、計算能力が低く、メモリ リソースが少ない場合にデバイスへの深層学習モデルの展開を妨げる可能性があります。重みと活性化を保存するための精度を下げることによって、ネットワークのメモリ要件を緩和させることができます。

Deep Learning Toolbox を Deep Learning Toolbox Model Quantization Library サポート パッケージと共に使用して、畳み込み層の重み、バイアス、および活性化を 8 ビットにスケーリングされた整数データ型に量子化することで、深層ニューラル ネットワークのメモリ フットプリントを削減できます。この量子化は、関数calibrate (Deep Learning Toolbox)によって生成されたキャリブレーション結果ファイルをcodegenコマンドに渡すことで行われます。その後、MATLAB Coder™ を使用して、このネットワークの最適化されたコードを生成できます。生成コードでは、ARM Compute Library を使用して ARM® プロセッサの SIMD を利用します。この生成コードは、Raspberry Pi™ などの各種の ARM CPU プラットフォームに展開できるソース コード、スタティック ライブラリ、ダイナミック ライブラリ、または実行可能ファイルとしてプロジェクトに統合できます。

この例では、ARM Compute Library を使用して 8 ビット整数で推論計算を実行する畳み込みニューラル ネットワークの C++ コードを生成する方法を示します。

この例は MATLAB Online ではサポートされていません。

サードパーティの前提条件

例: SqueezeNet を使用したイメージの分類

この例では、MATLAB Coder を使用して深層畳み込みニューラル ネットワークの最適化された C++ コードを生成し、イメージを分類します。生成されたコードは、8 ビット整数データ型を使用して畳み込み層の推論計算を実行します。この例では、事前学習済みのsqueezenet (Deep Learning Toolbox)畳み込みニューラル ネットワークを使用します。

SqueezeNet は、1000 個のオブジェクト カテゴリのイメージを含む ImageNet データセットで学習させたものです。このネットワークは広範囲にわたるイメージについての豊富な特徴表現を学習しています。このネットワークは入力としてイメージを取り、イメージ内のオブジェクトのラベルを各オブジェクト カテゴリの確率と共に出力します。

この例は、4 つの手順で構成されます。

  1. SqueezeNet ニューラル ネットワークを変更し、転移学習を使用して 5 つのオブジェクト カテゴリを含むイメージの小さなサブセットを分類します。

  2. 関数 calibrate を使用して、サンプル入力でネットワークを実行し、範囲情報を収集してキャリブレーション結果ファイルを生成します。

  3. codegen コマンドとキャリブレーション結果ファイルを使用して、ネットワークの最適化されたコードを生成します。生成されたコードは PIL 実行によって Raspberry Pi ターゲットで実行されます。

  4. 生成された PIL MEX を Raspberry Pi で実行します。

SqueezeNet を使用した転移学習

新しいイメージのセットに対して分類を実行するには、事前学習済みの SqueezeNet 畳み込みニューラル ネットワークを転移学習を使用して微調整する必要があります。転移学習では、事前学習済みのネットワークを取得して、新しいタスクの学習の開始点として使用します。通常は、転移学習を使用してネットワークを微調整する方が、ランダムに初期化された重みでゼロからネットワークに学習させるよりもはるかに簡単で時間がかかりません。少ない数の学習イメージを使用して、新しいタスクに学習済みの特徴を高速に転移できます。

学習データの読み込み

新しいイメージを解凍してイメージ データストアとして読み込みます。関数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');

事前学習済みのネットワークの読み込み

事前学習済みの SqueezeNet ネットワークを読み込みます。

net=squeezenet;

オブジェクト net には DAGNetwork オブジェクトが格納されています。最初の層は、227×227×3 のサイズの入力イメージを受け入れるイメージ入力層です。ここで、3 はカラー チャネルの数です。関数analyzeNetwork (Deep Learning Toolbox)を使用して、ネットワーク アーキテクチャを対話的に可視化して表示し、ネットワークに関するエラーや問題を検出して、ネットワーク層についての詳細情報を表示します。層の情報には、層の活性化と学習可能なパラメーターのサイズ、学習可能なパラメーターの総数、および再帰層の状態パラメーターのサイズが含まれます。

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

最後の層の置き換え

ネットワークの畳み込み層は、入力イメージを分類するために、最後の学習可能な層と最終分類層が使用するイメージの特徴を抽出します。SqueezeNet のこれらの 2 つの層 'conv10' および 'ClassificationLayer_predictions' は、ネットワークによって抽出された特徴を組み合わせてクラス確率、損失値、および予測ラベルにまとめる方法に関する情報を含んでいます。

新しいイメージを分類するために事前学習済みのネットワークを再学習させるには、これらの 2 つの層を新しいデータ セットに適応させた新しい層に置き換えます。これは手動で行うことも、補助関数 findLayersToReplace を使用してこれらの層を自動的に見つけることもできます。

補助関数 findLayersToReplace は次のとおりです。

type findLayersToReplace.m
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.

% Copyright 2021 The MathWorks, Inc.

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); %#ok<FNDSB>
    
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);

他のデータ拡張を実行せずに検証イメージのサイズを自動的に変更するには、追加の前処理演算を指定せずに拡張イメージ データストアを使用します。

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 オブジェクトを作成し、ネットワークを指定します。コード生成では、関数quantize (Deep Learning Toolbox)によって生成される量子化された深層ニューラル ネットワークはサポートされないことに注意してください。

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 Compute Library 用の深層学習構成オブジェクトを作成します。ライブラリのバージョンと ARM アーキテクチャを指定します。この例では、Raspberry Pi ハードウェアの ARM Compute Library が Version 20.02.1 であると仮定します。

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

低精度/INT8 推論用のコードを生成するように dlcfg のプロパティを設定します。

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

6. cfgDeepLearningConfig プロパティを dlcfg に設定します。

 cfg.DeepLearningConfig = dlcfg;

7. MATLAB Support Package for Raspberry Pi の関数 raspi を使用して、Raspberry Pi への接続を作成します。次のコードで、置換を行います。

  • raspiname を Raspberry Pi の名前で置き換える

  • username をユーザー名で置き換える

  • password をパスワードで置き換える

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

8. Raspberry Pi 用の coder.Hardware オブジェクトを作成し、それをコード生成構成オブジェクトに付加します。

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

9. codegen コマンドを使用して PIL MEX 関数を生成します。

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

Raspberry Pi での生成された PIL MEX 関数の実行

入力イメージはネットワークの入力サイズと同じサイズでなければなりません。分類するイメージを読み込み、そのサイズをネットワークの入力サイズに合わせて変更します。このサイズ変更により、イメージの縦横比が多少変化します。

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

参考

アプリ

関数

オブジェクト

関連するトピック