MATLAB Examples

Contents

Image Category Classification Using Deep Learning & CUDA C++ Codegeneration

本プログラムでは、MATLAB上でCNNを構築・学習し、学習済みネットワークを評価するところまでの ワークフローを試行します。また、学習したネットワークを実装するために、CUDA C++コードを生成し、 既存のコードと統合して.exeを作成します。 画像データは血液塗抹標本画像を利用し、写っている寄生虫の種類を分類することを目的とします。 データは米国CDC DPDx Parasite Image Libraryにて公開されているものを利用します。 https://www.cdc.gov/dpdx/malaria/index.html

This example shows how to fine-tune a pretrained AlexNet convolutional neural network to perform classification on a new collection of images and shows how to generate CUDA C++ Code for deplayment. This example uses the images from CDC DPDx Parasite Image Linrary. https://www.cdc.gov/dpdx/malaria/index.html

clear, close all, clc

Setup

%本例題ではAlexNetをベースとした転移学習を試行するため、AlexNetを使えるように
%予めサポートパッケージがインストールされている必要があります。
%インストール手順等につきましてはヘルプドキュメントをご覧ください。(>>doc('alexnet'))
alexnet();

Load Pretrained Network

%事前学習済みネットワークの読み込み
convnet = alexnet;
convnet.Layers
imageSize = convnet.Layers(1).InputSize;
ans = 

  次の層をもつ 25x1 の Layer 配列:

     1   'data'     イメージの入力       'zerocenter' 正規化の 227x227x3 イメージ
     2   'conv1'    たたみ込み         ストライド [4  4] およびパディング [0  0  0  0] の 96 11x11x3 たたみ込み
     3   'relu1'    ReLU          ReLU
     4   'norm1'    クロス チャネル正規化   要素ごとに 5 チャネルを使用したクロス チャネル正規化
     5   'pool1'    最大プーリング       ストライド [2  2] およびパディング [0  0  0  0] の 3x3 最大プーリング
     6   'conv2'    たたみ込み         ストライド [1  1] およびパディング [2  2  2  2] の 256 5x5x48 たたみ込み
     7   'relu2'    ReLU          ReLU
     8   'norm2'    クロス チャネル正規化   要素ごとに 5 チャネルを使用したクロス チャネル正規化
     9   'pool2'    最大プーリング       ストライド [2  2] およびパディング [0  0  0  0] の 3x3 最大プーリング
    10   'conv3'    たたみ込み         ストライド [1  1] およびパディング [1  1  1  1] の 384 3x3x256 たたみ込み
    11   'relu3'    ReLU          ReLU
    12   'conv4'    たたみ込み         ストライド [1  1] およびパディング [1  1  1  1] の 384 3x3x192 たたみ込み
    13   'relu4'    ReLU          ReLU
    14   'conv5'    たたみ込み         ストライド [1  1] およびパディング [1  1  1  1] の 256 3x3x192 たたみ込み
    15   'relu5'    ReLU          ReLU
    16   'pool5'    最大プーリング       ストライド [2  2] およびパディング [0  0  0  0] の 3x3 最大プーリング
    17   'fc6'      全結合           4096 全結合層
    18   'relu6'    ReLU          ReLU
    19   'drop6'    ドロップアウト       50% ドロップアウト
    20   'fc7'      全結合           4096 全結合層
    21   'relu7'    ReLU          ReLU
    22   'drop7'    ドロップアウト       50% ドロップアウト
    23   'fc8'      全結合           1000 全結合層
    24   'prob'     ソフトマックス       ソフトマックス
    25   'output'   分類出力          'tench' および 999 個のその他のクラスの crossentropyex

Load the new images as an image datastore

%imageDatastoreを利用して血液塗抹標本画像をロードします。
imds = imageDatastore(fullfile('BloodSmearImages'), 'IncludeSubfolders', true, ...
    'LabelSource', 'foldernames');
imds.ReadFcn = @(filename)readAndPreprocessImage(filename, imageSize);

Count files in imageDatastore labels

%イメージの数やラベルを表示
countEachLabel(imds)
ans =

  3×2 table

            Label            Count
    _____________________    _____

    babesiosis                16  
    plasmodium-gametocyte     16  
    trypanosomiasis           16  

Divide the data into training and validation data sets

%トレーニング用画像とテスト用画像に分類
rng('default')
[trainingSet, testSet] = splitEachLabel(imds, 0.7, 'randomize');

Display some images

%トレーニング用の画像から16枚抽出し、可視化してみます
numImages = numel(trainingSet.Labels);
idx = randperm(numImages,16);
figure
for i = 1:16
    subplot(4,4,i)
    I = readimage(trainingSet,idx(i));
    imshow(I)
end

Augment images for training

%利用できる画像データの枚数がそれほど多くないため、画像の反転や
%平行移動によってデータの数増しを行います
augmenter = imageDataAugmenter('RandXReflection',true,...
    'RandXTranslation', [-10 10], 'RandYTranslation',[-10 10]);
datasource = augmentedImageSource(imageSize,trainingSet,'DataAugmentation',augmenter);
%datasourceVal = augmentedImageSource(imageSize,testSet,'DataAugmentation',augmenter);

Replace final layers

%学習済みのAlexNetの最終層含む3層は1000クラスの一般物体認識用に構成されているため、
%この3層を取り除いて新規に3層を追加します
layersTransfer = convnet.Layers(1:end-3);
layersTransfer(end+1) = fullyConnectedLayer(3,'Name','fc8','WeightLearnRateFactor',10, ...
    'BiasLearnRateFactor', 20);
layersTransfer(end+1) = softmaxLayer('Name','prob');
layersTransfer(end+1) = classificationLayer('Name','classificationLayer');

Create and plot a layer graph

%比較的シンプルなネットワークですが、可視化してみます
lgraph = layerGraph(layersTransfer);
figure
plot(lgraph)

Train Network

%トレーニング時のオプションを設定し、トレーニングを実行します
optsTransfer = trainingOptions('sgdm', ...
    'InitialLearnRate', 0.001,...
    'ExecutionEnvironment', 'auto',...
    'MiniBatchSize',32,...
    'Plots','training-progress');

[netTransfer, traininfo] = trainNetwork(trainingSet, layersTransfer, optsTransfer);
save('netTransfer.mat', 'netTransfer')
単一の GPU で学習中。
イメージの正規化の初期化中。
|========================================================================================|
|  Epoch  |  Iteration  |  Time Elapsed  |  Mini-batch  |  Mini-batch  |  Base Learning  |
|         |             |   (hh:mm:ss)   |   Accuracy   |     Loss     |      Rate       |
|========================================================================================|
|       1 |           1 |       00:00:03 |       56.25% |       1.0856 |          0.0010 |
|      30 |          30 |       00:00:51 |      100.00% |   3.5131e-06 |          0.0010 |
|========================================================================================|

Classify test images

画像を読み込んで精度の確認

[img info] = readimage(testSet, 1);
figure, imshow(img)
label = classify(netTransfer, img)

if label == info.Label
    disp('推論結果は画像データのラベルと一致しています')
end
label = 

  categorical

     babesiosis 

推論結果は画像データのラベルと一致しています
%全テスト画像に対して実行
Tpred = classify(netTransfer, imds);
accuracy = sum(Tpred == imds.Labels)/numel(imds.Labels)

confMat = confusionmat(imds.Labels, Tpred)
accuracy =

    0.9375


confMat =

    15     0     1
     0    14     2
     0     0    16

Prepare for CUDA C++ code generation

%ここからコード生成用記述

Run myBsnet function created for code generation

%コード生成用に作成した関数、myBsnetを実行します
%まず、関数の中身を確認します
type('myBsnet.m')
function out = myBsnet(in)
%血液塗沫検査画像の分類用ネットワーク
persistent mynet;
if isempty(mynet)
    mynet = coder.loadDeepLearningNetwork('netTransfer.mat', 'netTransfer');
end

out = mynet.predict(in);
%先ほど検証用にロードした画像データで推論実行します
scores = myBsnet(img)
scores =

  1×3 の single 行ベクトル

    1.0000    0.0000    0.0000

Map prediction scores to words in the label dictionary and Display

%myBsnet関数ではコード生成用にpredictメソッドを利用しているため、
%ネットワークの出力はスコアとなり、ラベル情報とは別途紐付ける必要があります
%txtファイルに保存してあるラベルを読み込み、スコアと対比させます
%ラベル情報の読み込み
fid = fopen('cnn_netTransfer_labels.txt');
synsetOut = textscan(fid,'%s', 'delimiter', '\n');
synsetOut = synsetOut{1};
fclose(fid);
%スコア順に並び替えて結果を書き込み、表示します
img2 = drawScores(scores, img, synsetOut);
figure, imshow(img2)

Run MEX Code-generation for 'myBsnet' function

%myBsnet関数の動作確認ができたので、コード生成してMEXファイルを作成します
cfg = coder.gpuConfig('mex');
cfg.TargetLang = 'C++';
codegen -config cfg myBsnet -args {ones(227,227,3,'single')} -report
Note: Non-coalesced access to variable inputdata detected.  Consider transposing
variable or interchanging the surrounding loops, if possible.

コードの生成が成功しました (注意あり): レポートを表示するには、open('codegen\mex\myBsnet\html\report.mldatx') を実行します。

Run generated MEX

%生成したMEXで推論実行
scores = myBsnet_mex(single(img))

%結果の書き込み&表示
img3 = drawScores(scores, img, synsetOut);
figure, imshow(img3)
scores =

  1×3 の single 行ベクトル

    1.0000    0.0000    0.0000

Run LIB Code-generation for 'myBsnet' function

%MEXファイルで動作確認ができたので、次にスタティックライブラリを生成して
%既存のCコードと統合し、.exeアプリケーションを作成します
%ビルドタイプを'lib'に変更し、再度コード生成を行います
cfg = coder.gpuConfig('lib');
cfg.TargetLang = 'C++';
codegen -config cfg myBsnet -args {ones(227,227,3,'single')} -report
Note: Non-coalesced access to variable inputdata detected.  Consider transposing
variable or interchanging the surrounding loops, if possible.

コードの生成が成功しました (注意あり): レポートを表示するには、open('codegen\lib\myBsnet\html\report.mldatx') を実行します。

Main File

%メインのファイルではOpenCVで提供されるメソッドを利用して画像ファイルを読み込み、
%推論した結果を可視化します。まず、ファイルの中身を確認します
type('main_bsvideo_feed.cpp')
/* Copyright 2018 The MathWorks, Inc. */

#include "myBsnet_types.h"

#include <stdio.h>
#include <cuda.h>
#include "opencv2/opencv.hpp"

using namespace cv;

void readData(float *input, const Mat & orig, Mat & im)
{
    /* RGB画像データを読み込んでリサイズする*/
    Size size(227,227);
    resize(orig,im,size);
    if (!im.data) {
        printf(" No image data \n ");
        exit(1);
    }
    	
    for(int j=0;j<227*227;j++)
    {		
        //BGR to RGB
        input[2*227*227+j]=(float)(im.data[j*3+0]);
        input[1*227*227+j]=(float)(im.data[j*3+1]);
        input[0*227*227+j]=(float)(im.data[j*3+2]);
    }	
}

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(_WIN64)

int cmpfunc(void* r, const void * a, const void * b)
{
	float x =  ((float*)r)[*(int*)b] - ((float*)r)[*(int*)a] ;
	return ( x > 0 ? ceil(x) : floor(x) );
}
#else

int cmpfunc(const void * a, const void * b, void * r)
{
	float x =  ((float*)r)[*(int*)b] - ((float*)r)[*(int*)a] ;
	return ( x > 0 ? ceil(x) : floor(x) );
}

#endif


void top( float* r, int* top3 )
{
    /*スコア順にラベルを並び替え*/
    int n = 3;
    int t[3];
    for(int i=0; i<3; i++)
        t[i]=i;
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(_WIN64)
	qsort_s(t, 3, sizeof(int), cmpfunc, r);
#else
	qsort_r(t, 3, sizeof(int), cmpfunc, r);
#endif
    top3[0]=t[0];
    top3[1]=t[1];
    top3[2]=t[2];
    return;
}

void writeData(float *output,  char synsetWords[3][100], Mat & frame)
{
    /*スコア表示*/
	int top3[3];
	top(output, top3);
	
	copyMakeBorder(frame, frame, 0, 0, 400, 0, BORDER_CONSTANT, CV_RGB(0,0,0));
	char strbuf[50];
	sprintf(strbuf, "%4.1f%% %s", output[top3[0]]*100, synsetWords[top3[0]]);
	putText(frame, strbuf, cvPoint(30,80), CV_FONT_HERSHEY_DUPLEX, 0.6, CV_RGB(220,220,220), 1);
	sprintf(strbuf, "%4.1f%% %s", output[top3[1]]*100, synsetWords[top3[1]]);
	putText(frame, strbuf, cvPoint(30,130), CV_FONT_HERSHEY_DUPLEX, 0.6, CV_RGB(220,220,220), 1);
	sprintf(strbuf, "%4.1f%% %s", output[top3[2]]*100, synsetWords[top3[2]]);
	putText(frame, strbuf, cvPoint(30,180), CV_FONT_HERSHEY_DUPLEX, 0.6, CV_RGB(220,220,220), 1);
	
	imshow("BloodSmear Images Demo", frame);
    imwrite("mainOutput.png",frame);
}

int prepareSynset(char synsets[3][100])
{
    FILE* fp1 = fopen("cnn_netTransfer_labels.txt", "r");
	if (fp1 == 0) 
	{
		return -1;
	}
		
    for(int i=0; i<3; i++)
    {
        fgets(synsets[i], 100, fp1);
        strtok(synsets[i], "\n");
    }
    fclose(fp1);
	return 0;
}

// Main function
int main(int argc, char* argv[])
{
    int n = 1;
    Mat orig, im;
    
    if (argc != 2) {
        printf("Input image missing \nSample Usage-./myBsnet image.png\n");
        exit(1);
    }
	
	float *inputBuffer = (float*)calloc(sizeof(float),227*227*3*n);
	float *outputBuffer = (float*)calloc(sizeof(float),3);
	if ((inputBuffer == NULL) || (outputBuffer == NULL)) {
		printf("ERROR: Input/Output buffers could not be allocated!\n");
		exit(-1);
	}
	
    b_netTransfer* net = new b_netTransfer;
    net->batchSize = n;
    net->setup();
	char synsetWords[3][100];
	if (prepareSynset(synsetWords) == -1)
	{
		printf("ERROR: Unable to find BloodsmearWords.txt\n");
		return -1;
	}
    
    namedWindow("BloodSmear Images Demo",CV_WINDOW_NORMAL);
	resizeWindow("BloodSmear Images Demo", 1700,500);
	
    orig = imread(argv[1], 1);
    readData(inputBuffer, orig, im);
	
    cudaMemcpy( net->inputData, inputBuffer, sizeof(float)*227*227*3*n, cudaMemcpyHostToDevice );
    net->predict();
    cudaMemcpy( outputBuffer, net->outputData, sizeof(float)*3*n, cudaMemcpyDeviceToHost );
	
    writeData(outputBuffer, synsetWords, orig);
    
    for(;;){
        if(waitKey(30)%256 == 27 ) break; // ESCボタンで停止
    }
    
    destroyWindow("BloodSmear Images Demo");
	 
    net->cleanup();
    
    free(inputBuffer);
    free(outputBuffer);
        
    return 0;
}

Build and Run Executable

%予め準備してあるMakefileを利用して、スタンドアロン実行可能な
%exeファイルをビルド&実行します。
%OpenCVのインストールパス等、お使いの環境に従ってMakefileを修正してお使いください
system('make_win.bat');
system(['myBsnet.exe ', info.Filename]);
 
C:\work\001_Seminar\webinarGPC\FEX_new>call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools\..\..\VC\vcvarsall.bat" AMD64  
nvcc -o myBsnet.exe main_bsvideo_feed.cpp \ 
         -L"codegen/lib/myBsnet" myBsnet.lib \ 
         -L"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\cuDNN70\lib\x64" -lcudnn \ 
         -I"codegen/lib/myBsnet" \ 
         -lcublas \ 
         -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\cuDNN70/include" \ 
         -I"C:\MATLAB\R2018a/extern/include" \ 
         -I"C:\CUDA\openCV\source\opencv-3.1.0\build\install\include" -L"C:\CUDA\openCV\source\opencv-3.1.0\build\lib\Release" -lopencv_imgproc310 -lopencv_core310 -lopencv_highgui310 -lopencv_video310 -lopencv_videoio310 -lopencv_objdetect310 -lopencv_imgcodecs310 -Xcompiler "/MD" \ 
         -Wno-deprecated-gpu-targets 
main_bsvideo_feed.cpp 
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.1\bin/../include\cuda_runtime.h: warning C4819: ファイルは、現在のコード ページ (932) で表示できない文字を含んでいます。データの損失を防ぐために、ファイルを Unicode 形式で保存してください。 
c:\program files\nvidia gpu computing toolkit\cuda\v9.1\include\cuda_runtime_api.h(1950): warning C4819: ファイルは、現在のコード ページ (932) で表示できない文字を含んでいます。データの損失を防ぐために、ファイルを Unicode 形式で保存してください。 
c:\program files\nvidia gpu computing toolkit\cuda\v9.1\include\cuda_runtime_api.h(1950): warning C4819: ファイルは、現在のコード ページ (932) で表示できない文字を含んでいます。データの損失を防ぐために、ファイルを Unicode 形式で保存してください。 
C:/CUDA/openCV/source/opencv-3.1.0/build/install/include\opencv2/core/mat.hpp(1965): warning C4819: ファイルは、現在のコード ページ (932) で表示できない文字を含んでいます。データの損失を防ぐために、ファイルを Unicode 形式で保存してください。 
C:/CUDA/openCV/source/opencv-3.1.0/build/install/include\opencv2/core/persistence.hpp: warning C4819: ファイルは、現在のコード ページ (932) で表示できない文字を含んでいます。データの損失を防ぐために、ファイルを Unicode 形式で保存してください。 
C:/CUDA/openCV/source/opencv-3.1.0/build/install/include\opencv2/core/utility.hpp: warning C4819: ファイルは、現在のコード ページ (932) で表示できない文字を含んでいます。データの損失を防ぐために、ファイルを Unicode 形式で保存してください。 
C:/CUDA/openCV/source/opencv-3.1.0/build/install/include\opencv2/core/utility.hpp: warning C4819: ファイルは、現在のコード ページ (932) で表示できない文字を含んでいます。データの損失を防ぐために、ファイルを Unicode 形式で保存してください。 
myBsnet.lib 
   ライブラリ myBsnet.lib とオブジェクト myBsnet.exp を作成中 

Display output image

%実行ファイルの出力画像がpng形式で保存されていますので、結果を確認します
img = imread('mainOutput.png');
figure, imshow(img)