Main Content

量子化された深層学習ネットワークのコード生成

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

Deep Learning Toolbox と Deep Learning Toolbox Model Quantization Library サポート パッケージを併用して、畳み込み層の重み、バイアス、および活性化を 8 ビットにスケーリングされた整数データ型に量子化することによって、深層ニューラル ネットワークのメモリ フットプリントを削減できます。その後で、GPU Coder™ を使用して、量子化されたネットワークに最適化された CUDA® コードを生成できます。生成されたコードは、NVIDIA® CUDA Deep Neural Network library (cuDNN) または TensorRT™ の高性能な推論ライブラリを利用します。また、生成されたコードは、ソース コード、スタティック ライブラリ、またはダイナミック ライブラリ、あるいは、さまざまな NVIDIA GPU プラットフォームに展開できる実行可能ファイルとしてプロジェクトに統合できます。

量子化ネットワークを使用した GPU での画像分類

この例では、GPU Coder を使用して量子化された深層畳み込みニューラル ネットワークの CUDA コードを生成し、イメージを分類します。この例では、事前学習済みのsqueezenet (Deep Learning Toolbox)畳み込みニューラル ネットワークを使用して、量子化されたネットワークの転送学習、量子化、および CUDA コード生成を行う方法を示します。

SqueezeNet は、100 万枚を超えるイメージで学習しており、イメージを 1000 個のオブジェクト カテゴリ (キーボード、マグ カップ、鉛筆、多くの動物など) に分類できます。このネットワークは広範囲にわたるイメージについての豊富な特徴表現を学習しています。このネットワークは入力としてイメージを取り、イメージ内のオブジェクトのラベルを各オブジェクト カテゴリの確率と共に出力します。

サードパーティの必要条件

必須

  • CUDA 対応 NVIDIA® GPU および互換性のあるドライバー。

オプション

スタティック ライブラリ、ダイナミック ライブラリ、または実行可能ファイルなどの MEX 以外のビルドについて、この例では以下の要件も適用されます。

SqueezeNet を使用した転移学習

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

学習データの読み込み

新しいイメージを解凍してイメージ データストアとして読み込みます。関数 imageDatastore は、フォルダー名に基づいてイメージに自動的にラベルを付け、このデータを ImageDatastore オブジェクトとして格納します。イメージ データストアを使用すると、メモリに収まらないデータなどの大きなイメージ データを格納し、畳み込みニューラル ネットワークの学習中にイメージをバッチ単位で効率的に読み取ることができます。データを学習データセットと検証データセットに分割します。イメージの 70% を学習に使用し、30% を検証に使用します。splitEachLabel は、imds データストアを 2 つの新しいデータストアに分割します。

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 x 227 x 3 の入力イメージが必要です。ここで、3 はカラー チャネルの数です。関数 analyzeNetwork (Deep Learning Toolbox) を使用して、ネットワーク アーキテクチャを対話的に可視化して表示し、ネットワークに関するエラーや問題を検出して、ネットワーク層に関する詳細情報を表示することができます。層の情報には、層の活性化と学習可能なパラメーターのサイズ、学習可能なパラメーターの総数、および再帰層の状態パラメーターのサイズが含まれます。

inputSize = net.Layers(1).InputSize;

最後の層の置き換え

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

新しいイメージを分類するために事前学習済みのネットワークを再学習させるには、これら 2 つの層を新しいデータセットに適応させた新しい層に置き換えます。これは手動で行うことも、補助関数 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 x 227 x 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, ...
    '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 を使用して、サンプル入力でネットワークを実行し、範囲情報を収集します。関数 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

永続オブジェクト mynetDAGNetwork オブジェクトを読み込みます。エントリポイント関数への最初の呼び出しで、永続オブジェクトが作成されて設定されます。後続の関数の呼び出しでは、入力の呼び出し predict に同じオブジェクトが再利用され、ネットワーク オブジェクトの再構成と再読み込みが回避されます。

メモ

キャリブレーション ステップと検証ステップで実行されたすべての前処理演算が設計ファイルに必ず含まれるようにしてください。

codegen を使用したコード生成

出力ファイル名、場所、タイプなどのビルド設定を構成するには、コーダー構成オブジェクトを作成します。このオブジェクトを作成するには、関数 coder.gpuConfig を使用します。たとえば、codegen コマンドを使用して CUDA MEX を生成する場合、cfg = coder.gpuConfig('mex'); を使用します。

cuDNN のコード生成パラメーターを指定するには、DeepLearningConfig プロパティを、coder.DeepLearningConfig を使用して作成した coder.CuDNNConfig オブジェクトに設定します。

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 プロパティを使用して、適切な Compute Capability 値に設定します。

codegen コマンドを実行します。codegen コマンドは、MATLAB エントリポイント関数 predict_int8.m から CUDA コードを生成します。

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

コード生成に成功したら、MATLAB コマンド ウィンドウで [レポートの表示] をクリックすることで、結果のコード生成レポートを表示できます。レポートがレポート ビューアー ウィンドウに表示されます。コード生成時にコード ジェネレーターによりエラーまたは警告が検出されると、レポートでは問題が説明され、問題のある MATLAB コードへのリンクが提供されます。

生成された MEX の実行

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

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

入力イメージに対して SqueezeNet の predict を呼び出します。

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

制限

  • cuDNN version 8.1.0 を使用して INT8 の精度で推論を実行する場合、NVIDIA ライブラリの問題により、パフォーマンスが大幅に低下する可能性があります。

  • NVIDIA CUDA Deep Neural Network library (cuDNN) ライブラリを対象にする場合は、8 ビット整数の量子化で以下の層がサポートされません。

    • leakyReluLayer

    • clippedReluLayer

    • globalAveragePooling2dLayer

参考

アプリ

関数

オブジェクト

関連するトピック