Развертывание классификатора сигналов с помощью вейвлетов и глубокого обучения на Raspberry Pi

Этот пример показывает рабочий процесс классификации сигналов электрокардиограммы (ЭКГ) человека с помощью Непрерывного Преобразования Вейвлета (CWT) и глубокой сверточной нейронной сети (CNN). Этот пример также предоставляет информацию о том, как сгенерировать и развернуть код и CNN для предсказания на целевой программе Raspberry Pi (устройство на базе ARM ®).

SqueezeNet - глубокий CNN, изначально разработанный для классификации изображений в 1000 категориях. В примере Classify Time Series Using Wavelet Analysis and Глубокое Обучение, SqueezeNet переобучается, чтобы классифицировать формы волны ЭКГ на основе их скалограмм. Скалограмма является временно-частотным представлением сигнала и является абсолютным значением CWT сигнала. Переобученный SqueezeNet используется в этом примере.

Описание данных ЭКГ

В этом примере используются данные ЭКГ от PhysioNet. Данные ЭКГ получены от трех групп людей: лиц с сердечной аритмией (АРР), лиц с застойным сердечным отказом (ХСН) и лиц с нормальными синусовыми ритмами (СМП). Набор данных включает 96 записей от лиц с АРР, 30 записей от лиц с ХСН и 36 записей от лиц с СМП. 162 записи ЭКГ сделаны из трех баз данных PhysioNet: базы данных аритмии MIT-BIH [2] [3], базы данных нормального синусового ритма MIT-BIH [3] и базы данных застойного сердечного Отказа BIDMC [1] [3]. Укороченные данные ЭКГ вышеуказанных ссылок можно загрузить из репозитория GitHub.

Необходимые условия

Для поддерживаемых версий библиотек и для получения информации о настройке переменных окружения смотрите Необходимые условия для глубокого обучения с MATLAB Coder (MATLAB Coder). Этот пример не поддерживается в Online™ MATLAB.

Функциональность сгенерированного кода

Основная функция в сгенерированном исполняемом файле, processECG, использует 65 536 выборок данных ЭКГ с одной точностью в качестве входных данных. Функция:

  1. Принимает CWT данных ECG.

  2. Получает скалограмму из коэффициентов вейвлета.

  3. Преобразует скалограмму в маг RGB размерности 227 227 3. Это делает изображение совместимым с сетевой архитектурой 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

Создайте объект строения генерации кода

Создайте объект строения генерации кода для генерации исполняемой программы. Задайте генерацию кода С++.

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

Настройте объект Строения для генерации кода глубокого обучения

Создайте coder.ARMNEONConfig объект. Укажите ту же версию библиотеки ARM Compute, что и на Raspberry Pi. Задайте архитектуру Raspberry Pi.

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

Присоединение объекта строения глубокого обучения к объекту строения генерации кода

Установите DeepLearningConfig свойство объекта строения генерации кода объекту строения глубокого обучения. Сделайте MATLAB Source Comments видимым в объекте строения во время генерации кода.

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

Создайте соединение с Raspberry Pi

Используйте функцию MATLAB Поддержки Package for Raspberry Pi Поддержки Package, raspi, для создания соединения с Raspberry Pi. В следующем коде замените:

  • 'raspiname' с именем вашего Raspberry Pi

  • 'pi' с вашим именем пользователя

  • 'password' с вашим паролем

r = raspi('172.18.76.69','pi','raspberry');

Сконфигурируйте аппаратные параметры генерации кода для Raspberry Pi

Создайте coder.Hardware объект для Raspberry Pi и присоедините его к объекту строения генерации кода.

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

Укажите папку сборки на Raspberry Pi.

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

Предоставьте основной файл C++ для выполнения кода

Основной файл C++ считывает входные данные ECG, вызывает processECG функция для выполнения предварительной обработки и глубокого обучения с помощью CNN на данных ЭКГ и отображает вероятность классификации.

Укажите основной файл в объекте строения генерации кода. Для получения дополнительной информации о генерации и настройке main_ecg_raspi.cppСм. раздел «Генерация автономных исполняемых файлов C/C + + из кода MATLAB (MATLAB Coder)».

cfg.CustomSource = 'main_ecg_raspi.cpp';

Сгенерируйте исходный код С++ используя codegen

Используйте codegen функция для генерации кода С++. Когда codegen используется с пакетом поддержки MATLAB для оборудования Raspberry Pi, исполняемый файл построен на плате 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 запись содержит сгенерированный код, используйте функцию helper helperFindTargetDir. В противном случае укажите правильную директорию.

directoryUnknown = true;

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

Копирование входного файла в Raspberry Pi

Текстовый файл input_ecg_raspi.csv содержит выборки ЭКГ репрезентативного сигнала ARR. Чтобы скопировать файл, необходимый для запуска исполняемой программы, используйте putFile, который доступен с пакетом поддержки MATLAB для оборудования Raspberry Pi.

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

Для графического представления первые 1000 выборок могут быть построены с помощью этих шагов.

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

Запуск исполняемого файла на Raspberry Pi

Запустите исполняемую программу на Raspberry Pi из MATLAB и направьте выход назад в 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., В. С. Колуччи, Э. С. Монрэд, Х. С. Смит, Р. Ф. Райт, А. Лэноу, Д. Ф. Готье, Б. Дж. Рэнсил, В. Гроссман и Э. Браунвальд. «Выживание пациентов с тяжёлым застойным сердечным отказом, получавших пероральный милринон». Журнал Американского колледжа кардиологов. Том 7, № 3, 1986, стр. 661-670.

  2. Гольдбергер А. Л., Л. А. Н. Амарал, Л. Гласс, Ж. М. Хаусдорф, П. Ч. Иванов, Р. Г. Марк, Ж. Э. Миетус, Г. Б. Муди, К.-К. Пэн и Х. Э. Стэнли. PhysioBank, PhysioToolkit и PhysioNet: компоненты нового исследовательского ресурса комплексных физиологических сигналов. Циркуляция. Том 101, номер 23: e215-e220. [Тиражные электронные страницы; http://circ.ahajournals.org/content/101/23/e215.full]; 2000 (13 июня). doi: 10.1161/01.CIR.101.23.e215.

  3. Moody, G. B., and R. G. Mark. «The влияния of the MIT-BIH Arrhythmia Database». IEEE Engineering in Medicine and Biology Magazine. Том 20. № 3, май-июнь 2001, с. 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