Main Content

Raspberry Pi におけるウェーブレットおよび深層学習を使用した信号分類器の展開

この例では、連続ウェーブレット変換 (CWT) と深層畳み込みニューラル ネットワーク (CNN) を使用した人の心電図 (ECG) 信号を分類するワークフローを説明します。また、この例では、Raspberry Pi のターゲット (ARM® ベースのデバイス) で予測用のコードと CNN を生成して展開する方法に関する情報も提供します。

SqueezeNet は、当初 1000 のカテゴリにイメージを分類するために設計された深層 CNN です。例ウェーブレット解析と深層学習を使用した時系列の分類では、"スカログラム" に基づいて ECG 波形を分類するよう、SqueezeNet に再学習させます。スカログラムは、信号の時間-周波数表現であり、信号の CWT の絶対値です。この例では、再学習させた SqueezeNet を再利用します。

ECG データの説明

この例では、PhysioNet の ECG データを使用します。ECG データは人の 3 つのグループから取得されます。具体的には、心不整脈の患者 (ARR)、鬱血性心不全の患者 (CHF)、および正常洞調律の患者 (NSR) のグループです。このデータ セットには、AAR の患者の記録が 96 個、CHF の患者の記録が 30 個、NSR の患者の記録が 36 個含まれています。次の 3 つの PhysioNet データベースから 162 個の ECG 記録を使用します。MIT-BIH Arrhythmia Database [2][3]、MIT-BIH Normal Sinus Rhythm Database [3]、The BIDMC Congestive Heart Failure Database [1][3]。上記の参照の短縮された ECG データは、GitHub リポジトリからダウンロードできます。

必要条件

サポートされるライブラリのバージョン、および環境変数の設定の詳細については、MATLAB Coder を使用した深層学習の前提条件 (MATLAB Coder)を参照してください。この例は、MATLAB Online™ ではサポートされていません。

生成されたコードの機能

生成された実行可能ファイル内の主な関数 processECG は、65,536 個の単精度の ECG データ サンプルを入力として使用します。関数は以下を実行します。

  1. ECG データの CWT を受信。

  2. ウェーブレット係数からスカログラムを取得。

  3. スカログラムを寸法が 227×227×3 の RGB イメージに変換。これにより、イメージは SqueezeNet ネットワーク アーキテクチャとの互換性をもちます。

  4. SqueezeNet を使用し、予測を実行してイメージを分類。

type processECG
function [YPred] = processECG(input)
% processECG function - converts 1D ECG to image and predicts the syndrome
% of heart disease
%
% This function is only intended to support the example:
% Signal Classification Code Generation Using Wavelets and
% Deep Learning on Raspberry Pi. It may change or be removed in a
% future release.

% Copyright 2020 The MathWorks, Inc.

    % colourmap for image transformation
    persistent net jetdata;
    if(isempty(jetdata))
        jetdata = colourmap(128,class(input));
    end

    % Squeezenet trained network
    if(isempty(net))
        net = coder.loadDeepLearningNetwork('trainedNet.mat');
    end

    % Wavelet Transformation & Image conversion
    cfs = ecg_to_Image(input);
    image = ind2rgb(im2uint8(rescale(cfs)),single(jetdata));
    image = im2uint8(imresize(image,[227,227]));

    % figure
    if isempty(coder.target)        
        imshow(image);
    end

    % Prediction
    [YPred] = predict(net,image);


    %% ECG to image conversion
    function cfs = ecg_to_Image(input)

        %Wavelet Transformation
        persistent filterBank
        [~,siglen] = size(input);
        if isempty(filterBank)
            filterBank = cwtfilterbank('SignalLength',siglen,'VoicesPerOctave',6);
        end
        %CWT conversion
        cfs = abs(filterBank.wt(input));
    end


    %% Colourmap
    function J = colourmap(m,class)

        n = ceil(m/4);
        u = [(1:1:n)/n ones(1,n-1) (n:-1:1)/n]';
        g = ceil(n/2) - (mod(m,4)==1) + (1:length(u))';
        r = g + n;
        b = g - n;
        r1 = r(r<=128);
        g1 = g(g<=128);
        b1 = b(b >0);
        J = zeros(m,3);
        J(r1,1) = u(1:length(r1));
        J(g1,2) = u(1:length(g1));
        J(b1,3) = u(end-length(b1)+1:end);
        feval = str2func(class);
        J = feval(J);
    end
end

コード生成構成オブジェクトの作成

実行可能プログラムの生成用にコード生成構成オブジェクトを作成します。C++ コードの生成を指定します。

cfg = coder.config('exe');
cfg.TargetLang = 'C++';

深層学習コード生成用の構成オブジェクトの設定

coder.ARMNEONConfig オブジェクトを作成します。Raspberry Pi にある ARM Compute Library と同じバージョンを指定します。Raspberry Pi のアーキテクチャを指定します。

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

コード生成構成オブジェクトへの深層学習構成オブジェクトの追加

コード生成構成オブジェクトの DeepLearningConfig プロパティに、深層学習構成オブジェクトを設定します。コード生成時の構成オブジェクトに MATLAB ソース コメントが表示されるようにします。

cfg.DeepLearningConfig = dlcfg;
cfg.MATLABSourceComments = 1;

Raspberry Pi への接続の作成

MATLAB Support Package for Raspberry Pi のサポート パッケージ関数 raspi を使用して、Raspberry Pi への接続を作成します。以下のコードで、次を置き換えます。

  • raspiname: 自分の Raspberry Pi の名前または IP アドレス

  • username: 自分のユーザー名

  • password: 自分のパスワード

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

Raspberry Pi 用のコード生成ハードウェア パラメーターの構成

Raspberry Pi 用の coder.Hardware オブジェクトを作成してコード生成構成オブジェクトに追加します。

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

Raspberry Pi 上のビルド フォルダーを指定します。

buildDir = '~/remdirECG';
cfg.Hardware.BuildDir = buildDir;

コード実行用 C++ メイン ファイルの提供

C++ メイン ファイルは、入力の ECG データを読み取り、関数 processECG を呼び出し、ECG データに CNN を使用して前処置と深層学習を実行し、分類確率を表示します。

メイン ファイルをコード生成構成オブジェクトで指定します。main_ecg_raspi.cpp の生成およびカスタマイズの詳細については、MATLAB コードからのスタンドアロン C/C++ 実行可能ファイルの生成 (MATLAB Coder)を参照してください。

cfg.CustomSource = 'main_ecg_raspi.cpp';

codegen を使用した C++ ソース コードの生成

関数 codegen を使用して C++ コードを生成します。codegen と MATLAB Support Package for Raspberry Pi Hardware を使用する場合、実行可能ファイルは Raspberry Pi ボードでビルドされます。

環境変数 ARM_COMPUTELIB および LD_LIBRARY_PATH が Raspberry Pi 上で設定されていることを確認します。MATLAB Coder を使用した深層学習の前提条件 (MATLAB Coder)を参照してください。

codegen -config cfg processECG -args {ones(1,65536,'single')} -d arm_compute
 Deploying code. This may take a few minutes. 

生成された実行可能ファイル ディレクトリの取得

生成されたコードを Raspberry Pi でテストするには、生成されたコードのディレクトリに入力の ECG 信号をコピーします。このディレクトリは手動で、または raspi.utils.getRemoteBuildDirectory API を使用して見つけることができます。この関数は、codegen を使用して生成されたバイナリ ファイルのディレクトリをリストします。

applicationDirPaths = raspi.utils.getRemoteBuildDirectory('applicationName','processECG')
applicationDirPaths=1×4 cell array
    {1×1 struct}    {1×1 struct}    {1×1 struct}    {1×1 struct}

リモート ビルド ディレクトリへの絶対パスは、現在の作業ディレクトリから派生します。生成されたコードが含まれる applicationDirPaths エントリがわからない場合は、補助関数 helperFindTargetDir を使用します。そうでない場合は、適切なディレクトリを指定します。

directoryUnknown = true;

if directoryUnknown
    targetDirPath = helperFindTargetDir(applicationDirPaths);
else   
    targetDirPath = applicationDirPaths{1}.directory;
end

Raspberry Pi への入力ファイルのコピー

テキスト ファイル input_ecg_raspi.csv には、代表的な ARR 信号の ECG サンプルが含まれます。実行可能プログラムを実行するのに必要なファイルをコピーするには、putFile を使用します。これは、MATLAB Support Package for Raspberry Pi Hardware で利用可能です。

r.putFile('input_ecg_raspi.csv', targetDirPath);

視覚的な表現とするために、最初の 1000 個のサンプルをこれらの手順でプロットできます。

input = dlmread('input_ecg_raspi.csv');
plot(input(1:1000))
title('ARR Signal')

Raspberry Pi での実行可能ファイルの実行

MATLAB から Raspberry Pi で実行可能プログラムを実行し、出力を MATLAB に戻します。入力ファイル名は、実行可能ファイルのコマンド ライン引数として渡されます。

exeName = 'processECG.elf';           % executable name
fileName = 'input_ecg_raspi.csv';     % Input ECG file that is pushed to target
command = ['cd ' targetDirPath ';./' exeName ' ' fileName];
output = system(r,command)
output = 
    'Predicted Values on the Target Hardware
     ARR            CHF            NSR
     0.806078	0.193609	0.000313103
     '

参考文献

  1. Baim, D. S., W. S. Colucci, E. S. Monrad, H. S. Smith, R. F. Wright, A. Lanoue, D. F. Gauthier, B. J. Ransil, W. Grossman, and E. Braunwald."Survival of patients with severe congestive heart failure treated with oral milrinone."Journal of the American College of Cardiology.Vol. 7, Number 3, 1986, pp. 661–670.

  2. Goldberger A. L., L. A. N. Amaral, L. Glass, J. M. Hausdorff, P. Ch. Ivanov, R. G. Mark, J. E. Mietus, G. B. Moody, C.-K. Peng, and H. E. Stanley. "PhysioBank, PhysioToolkit,and PhysioNet: Components of a New Research Resource for Complex Physiologic Signals." Circulation. Vol. 101, Number 23: e215–e220. [Circulation Electronic Pages; http://circ.ahajournals.org/content/101/23/e215.full]; 2000 (June 13). doi: 10.1161/01.CIR.101.23.e215.

  3. Moody, G. B., and R. G. Mark."The impact of the MIT-BIH Arrhythmia Database."IEEE Engineering in Medicine and Biology Magazine.Vol. 20. Number 3, May-June 2001, pp. 45–50. (PMID: 11446209)

サポート関数

helperFindTargetDir

function targetDir = helperFindTargetDir(dirPaths)
%
% This function is only intended to support wavelet deep learning examples.
% It may change or be removed in a future release.

% find pwd
p = pwd;
if ispc
    % replace blank spaces with underscores
    p = strrep(p,' ','_');
    
    % split path into component folders
    pSplit = regexp(p,filesep,'split');
    
    % Since Windows uses colons, remove any colons that occur
    for k=1:numel(pSplit)
        pSplit{k} = erase(pSplit{k},':');
    end
    
    % now build the path using Linux file separation
    pLinux = '';
    for k=1:numel(pSplit)-1
        pLinux = [pLinux,pSplit{k},'/'];
    end
    pLinux = [pLinux,pSplit{end}];
else
    pLinux = p;
end

targetDir = '';
for k=1:numel(dirPaths)
    d = strfind(dirPaths{k}.directory,pLinux);
    if ~isempty(d)
        targetDir = dirPaths{k}.directory;
        break
    end
end

if numel(targetDir) == 0
    disp('Target directory not found.');
end
end

参考

関連するトピック