人工智能/深度学习

使用 NVIDIA Jetson 开发人工智能驱动的数字健康应用程序

 

传统的医疗保健系统有大量的病人数据,包括生理信号、医疗记录、提供者注释和评论。开发数字健康应用程序所面临的最大挑战是分析大量可用数据,得出可操作的见解,以及开发可在嵌入式设备上运行的解决方案。

在开发这样的端到端解决方案时,从事生物医学数据集工作的工程师和数据科学家经常遇到挑战,因为他们必须手动集成应用程序代码,与必要的工具链集成以进行部署,在许多情况下,还必须重写代码以在目标硬件上运行应用程序。如果算法不能产生预期的结果,他们必须调试底层案例,这可能会很耗时。

这篇文章讨论了数据科学家和工程师如何使用 NVIDIA gpu 为生物医学应用开发基于人工智能的数字健康算法原型,并在嵌入式物联网和边缘人工智能平台(如 NVIDIA Jetson 开关 )上部署这些算法。您还可以使用 MathWorks GPU 编码器在 Jetson 上部署预测管道。

解决的目标是训练一个分类器来区分心律失常( ARR )、充血性心力衰竭( CHF )和正常窦性心律( NSR )。本教程使用从以下三个组或类获得的 ECG 数据:

  • 心律失常患者
  • 充血性心力衰竭患者
  • 窦性心律正常的人

该示例使用 来自三个 PhysioNet 数据库的 162 个心电图记录 ,每个记录有 65536 个样本:

  • MIT-BIH 心律失常数据库: 96 次记录
  • BIDMC 充血性心力衰竭数据库: 30 个记录
  • 正常窦性心律数据库: 36 次录音

要运行此示例,您需要以下资源:

  • MATLAB
  • 小波工具箱
  • 讯号处理工具箱
  • 并行计算工具箱
  • GPU 编码器
  • MATLAB 编码器
  • NVIDIA GPU 的 GPU 编码器支持包

请求产品试用 或发送电子邮件至 medical@mathworks.com

开发基于人工智能的数字健康应用程序的总体工作流程

建立心电信号预测模型的方法很多。这篇文章的重点是探索一个简化的工作流程,它结合了信号处理方面的进展,并利用了在开发卷积神经网络( CNNs )和 ECG 信号分类的深度学习体系结构方面已经完成的大量工作。图 1 显示了整个工作流。

Workflow shows training and inference phases going from signal to wavelet time-frequency representation to training deep networks or predicting.
图 1 。基于人工智能的数字健康应用的总体工作流程。

这里的主要思想是从 ECG 信号生成时频表示,并将这些表示保存为图像。 ECG 的时频表示捕获了 spe CTR al 成分随时间的变化。这些图像可以用来训练卷积神经网络( CNNs )。 CNNs 可以从图像中的模式( ECG 信号的时频表示)中提取特征,并且可以建立一个模型来区分属于不同类别的 ECG 信号。

开发模型之后,就可以在嵌入式硬件(如 Jetson )上部署 pipeline : time-frequency 表示和经过训练的模型,以便使用 GPU 编码器对新信号执行推断。 GPU 编码器根据 MATLAB 算法自动生成优化的 CUDA 代码。

使用 NVIDIA Quadro RTX 6000 加速时频表示

下面是如何从 ECG 记录中生成时频表示。虽然 MATLAB 中有几种方法可以从信号中生成时频表示,但我们强烈建议使用连续小波变换( CWT ),因为它简单且能够从信号中生成清晰的时频表示。由此产生的时频表示也被称为 scalogram

CWT 是通过对信号进行加窗处理而得到的,小波经过时间缩放和移位。小波是振荡的,可以是复值的。对原型小波进行了尺度变换和移位运算。 CWT 中使用的缩放可收缩和拉伸原型小波:

  • 缩小原型子波可以产生短时间、高频子波,这些子波能够很好地检测瞬态事件。
  • 拉伸原型子波会产生长持续时间的低频子波,这些子波擅长于隔离长持续时间的低频事件。
图 2 。正常窦性心律( NSR )和充血性心力衰竭( CHF )的标度图。

拥有这样清晰的时频表示可能很有用,因为深度网络或 cnn 擅长从这种表示中提取特征并建立预测模型。使用清晰的时频表示法的另一个好处是,它使您能够构建模型来捕捉细微的变化,这些变化反过来有助于区分许多信号类别,即使这些信号看起来相似。

为了加快比例图的生成过程,如果将输入变量转换为 gpuArray 类型,则可以在 gpu 上运行 MATLAB 函数。然后可以使用 gather 函数从 GPU 检索输出数据。为了加快尺度图的生成过程,使用函数 cwtfilterbank 创建一次小波滤波器组,然后使用 cwtfilterbank 的小波变换方法生成时频表示。

我们使用 NVIDIA Quadro RTX 6000 来计算标度图,并且能够实现约 6 . 5 倍的加速。使用的 CPU 是 Intel Xeon CPU E5-1650 v4 @ 3 . 60 GHz ,带有 64 GB RAM 。

有关更多信息,请参阅 cwtcwtfilterbank 函数参考。

load('ECGData.mat');  % Data file downloaded from git

useGPU = true;
tic;
X = helperTimeScalogramConversion(ECGData, useGPU);
Tgpu = toc;
 
useGPU = false;
tic;
X = helperTimeScalogramConversion(ECGData, useGPU);
Tcpu = toc;
 
 
function X = helperTimeScalogramConversion(ECGData, useGPU)
 
data = ECGData.Data;
[Nsamples,signalLength] = size(data);
 
fb = cwtfilterbank('SignalLength',signalLength,'VoicesPerOctave',12);
X = zeros(numel(fb.scales), signalLength, Nsamples);
 
r = size(data,1);
 
if useGPU
    data = single(gpuArray(data));
else
    data = single(data);
end
 
for ii = 1:r
    cfs = abs(fb.wt(data(ii,:)));
    X(:,:,ii) = gather(cfs);
 
end
end
Using GPU: false
Number of signals: 162
-------------------------------------
Total execution time Tcpu =   67.32 s
-------------------------------------
Using GPU: true
Number of signals: 162
-------------------------------------
Total execution time Tgpu =   10.28 s
-------------------------------------
 
图 3 。 CPU 与 GPU 的比例图执行速度。

从命令行输出和生成的绘图中,您可以看到使用 gpu 会导致显著的加速( 6 . 5 倍)。当您有多个信号时,这将非常有用。稍后,您将看到,在 GPU 上生成比例图的能力将是在 Jetson 平台上开发应用程序的关键因素。

利用 Quadro RTX 6000 训练 CNNs 对心电信号进行分类

现在您已经有了 ECG 信号的时频图像,您可以在预训练的深度学习网络上应用转移学习来构建分类器。在这个例子中, retain SqueezeNet 是一个 18 层的深度网络,它训练了超过一百万张图像。这是一个轻量级的网络,具有更低的内存占用和更好的推理速度,这些特性非常适合嵌入式部署。该网络最初训练了 1000 个图像类,但是您可以使用三个 ECG 图像类数据集对网络进行微调,以构建新的分类器。

首先,检查原始网络,它包含 18 层,包括卷积、 ReLU 和 fire block (卷积和 ReLU 层的组合)。图 4 显示了通过运行以下命令在 MATLAB 中显示的层的代码示例:

analyzeNetwork(squeezeNet)
图 4 。挤压网网络结构。

为了重新训练 SqueezeNet 对三类 ECG 信号进行分类,用一个新的卷积层替换最后的 conv10 层,滤波器的数目等于 ECG 类的数目。另外,用一个没有类标签的新层替换分类层。

sqz = squeezenet;
lgraph = layerGraph(sqz);
lgraph.Layers(end-4:end)
图 5 。最后 5 层原挤压网。
numClasses = 3; % Number of ECG signal classes new_conv10_WeightLearnRateFactor = 1;
new_conv10_BiasLearnRateFactor = 1;
newConvLayer = convolution2dLayer(1,numClasses,...
        'Name','new_conv10',...
        'WeightLearnRateFactor',new_conv10_WeightLearnRateFactor,...
        'BiasLearnRateFactor',new_conv10_BiasLearnRateFactor);
lgraph = replaceLayer(lgraph,'conv10',newConvLayer);
newClassLayer = classificationLayer('Name','new_classoutput');
lgraph = replaceLayer(lgraph,'ClassificationLayer_predictions',newClassLayer);
lgraph.Layers(end-4:end)
图 6 。挤压网的改良层。
图 7 。以 jpeg 格式保存的 ECG 信号的时频表示。

对于深度学习培训,现在可以将保存为 JPEG 的 ECG 标度图数据的图像加载到相应的文件夹 ARR 、 CHF 和 NSR 中(图 7 )。有关用于将比例图数据保存为 JPEG 图像的脚本的详细信息,请参阅 基于小波分析和深度学习的时间序列分类

imageDatastore 函数用于为这些图像创建一个图像数据存储,其标签名称来自各个文件夹名称,并分为训练( 70% )、验证( 10% )和测试( 20% )子集。您还将 readFcn 对象与数据存储相关联,以自动将图像大小调整为 227x227x3 ,并满足 squezenet 输入图像的要求。

readFcn = @(imagefilename)imresize(imread(imagefilename), [227 227]); 
imds = imageDatastore('../data', 'LabelSource', 'foldernames', ...             
                     'IncludeSubFolder', true, 'readFcn', readFcn);  
[imgsTrain, imgsValidation, imgsTest ]  = splitEachLabel(imds, 0.7, 0.1, 0.2); 

下一步是定义训练超参数并通过网络进行训练。如果安装了 GPU , MATLAB 会自动在 GPU 上缩放训练。要在没有 GPU 的情况下进行训练(不推荐),请使用多个 GPU 或在云端扩展训练。您可以通过调整 trainingOptions 中的 ExecutionEnvironment 参数来实现。

options = trainingOptions('sgdm',...
    'MiniBatchSize',15,...
    'MaxEpochs',20,...
    'InitialLearnRate',1e-4,...
    'ValidationData',imgsValidation,...
    'ValidationFrequency',10,...
    'Momentum',0.9,...
    'ExecutionEnvironment', 'auto');

trainedModel = trainNetwork(imgsTrain,lgraph,options);

我们用 NVIDIA Quadro RTX 6000 训练这个网络。图 8 显示训练网络只花了 23 秒。与 CPU 上的训练时间相比,这要快得多(~ 6 倍)。当使用 gpu 时, MATLAB 自动使用 CUDA 和 cuDNN 库来加速训练。然而,值得一提的是,我们只处理了 162 幅时频图像,因此,整个训练时间相当少。与 CPU 相比, GPU 的训练时间性能随着训练数据量的增加而提高。

Two screenshots of training progress, showing the elapsed time of 23 seconds for GPU vs. 2 minutes 1 second for CPU.
图 8 。在 GPU ( 23 秒)和单 CPU ( 2 分 1 秒)上训练深度学习模型。

现在你已经有了一个训练过的模型,你可以通过推断测试图像数据集来验证准确度,这是新模型从未见过的。我们训练了一个准确度> 95% 的模型,只有一次错误分类。

predictedLabels = classify(trainedModel, imgsTest);
accuracy = sum(predictedLabels == imgsTest.Labels) / numel(predictedLabels);
confusionchart(imgsTest.Labels, predictedLabels)
title("Confusion Matrix, Accuracy " + accuracy*100 + "%")
图 9 。训练模型上测试数据集的混淆矩阵。

在 Jetson 上部署数字健康物联网应用程序

既然您已经培训并构建了您的 AI 应用程序,现在是部署的时候了。 NVIDIA Jetson 平台提供小型、节能的模块上系统,具有高计算性能和云本地功能,是部署数字健康物联网应用程序的理想选择。您可以在 Jetson 平台上部署整个管道(时频生成和挤压网预测),以便它可以独立运行算法。

在本例中,从 IO 模块读入实时 ECG 信号,使用 CWT 将信号转换为时频图像,最后将图像通过经过训练的深度学习网络进行推理。所有这些步骤都是实时执行的,因此在获取 ECG 信号和获得推断结果之间没有延迟。

利用 GPU 编码器, MATLAB 提供了一个将各种信号处理、小波分析、图像处理和深度学习算法转换为优化的 CUDA 代码的自动化途径,可以直接在 Jetson 平台上投入生产。

第一步,将 AI 应用程序打包成 MATLAB 函数,就像它在部署版本中运行一样。如代码示例所示,函数 model_predict_ecg 接收 65536 个样本的 ECG 信号作为输入,将其传递给函数 cwt_ecg_jetson_ex 将其转换为时频图像,将其传递给经过训练的深度学习网络,最后提供所有不同 ECG 类别的概率分数作为输出。

%% MATLAB functions for deployment
function PredClassProb = model_predict_ecg(TimeSeriesSignal)
 
    coder.gpu.kernelfun();
   
    % parameters
    ModFile = 'ecg_model.mat'; % file that saves neural network
    ImgSize = [227 227]; % input image size for the ML model
   
    % sanity check signal is a row vector of correct length
    assert(isequal(size(TimeSeriesSignal), [1 65536]))
 
    %% cwt transformation for the signal
    im = cwt_ecg_jetson_ex(TimeSeriesSignal, ImgSize);
   
    %% model prediction
    persistent model;
    if isempty(model)
        model = coder.loadDeepLearningNetwork(ModFile, 'mynet');
    end
 
    PredClassProb = predict(model, im);
   
end
 
function im = cwt_ecg_jetson_ex(TimeSeriesSignal, ImgSize)
 
coder.gpu.kernelfun();
 
%% Create scalogram
cfs = cwt(TimeSeriesSignal, 'morse', 1, 'VoicesPerOctave', 12);
cfs = abs(cfs);
 
%% Image generation
 
% Load the jet colormap generated and saved earlier using the commands:
% >> cmapj128 = jet(128); save(‘cmapj128.mat’, ‘cmapj128’);
 
cmapj128 = coder.load('cmapj128');
imx = ind2rgb_custom_ecg_jetson_ex(round(255*rescale(cfs))+1,cmapj128.cmapj128);
 
% Resize to proper size and convert to uint8 data type
im = im2uint8(imresize(imx, ImgSize));
 
end
 
function out = ind2rgb_custom_ecg_jetson_ex(a, cm)
  
   indexedImage = a;
  
   % Make sure that indexedImage is in the range from 1 to number of colormap
   % entries
   numColormapEntries = size(cm,1);
   indexedImage = max(1, min(indexedImage, numColormapEntries) );
 
   height = size(indexedImage, 1);
   width = size(indexedImage, 2);
  
   rgb = coder.nullcopy(zeros(height,width,3));
   rgb(1:height, 1:width, 1) = reshape(cm(indexedImage, 1), [height width]);
   rgb(1:height, 1:width, 2) = reshape(cm(indexedImage, 2), [height width]);
   rgb(1:height, 1:width, 3) = reshape(cm(indexedImage, 3), [height width]);
  
   out = rgb;
 
end

现在您已经设置好了函数,从 MATLAB 连接到 Jetson 平台。 NVIDIA GPU 的 GPU 编码器支持包支持以下开发工具包:

  • NVIDIA Jetson TK1 型
  • Jetson TX1 型
  • Jetson TX2 型
  • Jetson AGX Xavier
  • Jetson Xavier NX 型
  • Jetson 纳米

它还支持 NVIDIA 驱动平台 。下面是连接到 Jetson Nano 平台并设置配置参数的代码示例,用于从前面的函数生成 CUDA 代码。

hwobj = jetson('gpucoder-nano-2','username','password');
cfg = coder.gpuConfig('exe');
cfg.Hardware = coder.hardware('NVIDIA Jetson');
cfg.Hardware.BuildDir = '~/remoteBuildDir';
cfg.DeepLearningConfig = coder.DeepLearningConfig('cudnn');
cfg.CustomSource = fullfile('main_ecg_jetson_ex.cu');

指定的代码示例在代码生成中包含自定义源文件 main_ecg_jetson_ex.cu 作为参数。这样做是为了指定输入/输出管道,以便在 Jetson 平台上测试部署的算法。首先,通过选择 coder.gpuConfig 对象到 coder.gpuConfigGenerateExampleMain 属性,为该应用程序生成了示例 main.cu 模板。接下来,您修改了模板 main.cu 文件,包括从文本文件 signalData.txt 中读取样本 ECG 信号数据,并将算法结果写入另一个文本文件 predClassProb.txt 。修改后的 main.cu 文件另存为 main_ecg_jetson_ex.cu ,仅供参考。

//
// File: main_ecg_jetson_ex.cu
//       
//***********************************************************************
// Include Files
#include "rt_nonfinite.h"
#include "model_predict_ecg.h"
#include "main_ecg_jetson_ex.h"
#include "model_predict_ecg_terminate.h"
#include "model_predict_ecg_initialize.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
// Function Definitions
 
/* Read data from a file*/
int readData_real32_T(const char * const file_in, real32_T data[65536])
{
  FILE* fp1 = fopen(file_in, "r");
  if (fp1 == 0)
  {
    printf("ERROR: Unable to read data from %s
", file_in);
    exit(0);
  }
  for(int i=0; i<65536; i++)
  {
      fscanf(fp1, "%f", &data[i]);
  }
  fclose(fp1);
  return 0;
}
 
/* Write data to a file*/
int writeData_real32_T(const char * const file_out, real32_T data[3])
{
  FILE* fp1 = fopen(file_out, "w");
  if (fp1 == 0)
  {
    printf("ERROR: Unable to write data to %s
", file_out);
    exit(0);
  }
  for(int i=0; i<3; i++)
  {
    fprintf(fp1, "%f
", data[i]);
  }
  fclose(fp1);
  return 0;
}
 
// model predict function
static void main_model_predict_ecg(const char * const file_in, const char * const file_out)
{
  real32_T PredClassProb[3];
  //  real_T b[65536];
  real32_T b[65536];
 
  // readData_real_T(file_in, b);
  readData_real32_T(file_in, b);
      
  model_predict_ecg(b, PredClassProb);
 
  writeData_real32_T(file_out, PredClassProb);
 
}
 
// main function
int32_T main(int32_T argc, const char * const argv[])
{
  const char * const file_out = "predClassProb.txt";
  // Initialize the application.
  model_predict_ecg_initialize();
 
  // Run prediction function
  main_model_predict_ecg(argv[1], file_out); // argv[1] = file_in
 
  // Terminate the application.
  model_predict_ecg_terminate();
  return 0;
}
 
//
// End of file
//

然后执行代码生成命令生成并构建 CUDA 代码,并将其部署到 cfg.Hardware.BuildDir 设备上。此命令生成编译的 model_predict_ecg.elf 文件,并将其放在代码生成配置参数 Jetson 中指定的 Jetson 生成目录路径上。

inputSignal = coder.newtype('single', [1 65536], [0 0]);
codegen model_predict_ecg.m -args {inputSignal} – config cfg -report

首先,检查以下自动生成的代码。自动创建了 30 多个 CUDA 内核,用于执行时频图像转换和深度学习预测步骤。 GPU 编码器自动调用优化的 NVIDIA CUDA cuDNN 库进行深度学习,并调用 cuBLAS 、 cuFFT 和 cuSolver 库进行其他矩阵计算。图 10 显示了生成的报告 GUI ,它可以用来检查自动生成的 CUDA 文件,并将生成的 CUDA 代码追溯到相应的 MATLAB 函数。

图 10 。 GPU 代码生成报告。

现在已经生成了代码,请在 Jetson 上测试算法的性能。您可以直接从 Jetson 运行生成的可执行文件,也可以使用 MATLAB 接口直接从 MATLAB 执行应用程序。在本例中,将示例 ECG 数据文件 signalData.txt 写入 Jetson 的工作区目录,并在主板的 Linux 终端上执行编译后的应用程序。

sampleIndx = 113; % Chose any ECG record from the ECGData struct for testing
signal_data = ECGData.Data(sampleIndx, :);
ECGType = ECGData.Labels(sampleIndx);
 
fid = fopen('signalData.txt','w');
for i = 1:length(signal_data)
   fprintf(fid,'%f
',signal_data(i));
end
fclose(fid);
 
% Copy the text file to the workspace directory on the Jetson board
hw.putFile('signalData.txt', hwobj.workspaceDir)
图 11 。在 Jetson Nano 终端上执行编译后的应用程序。

打开 Jetson 端子。在工作区内,您可以找到复制的 signalData.txt 文件以及编译的 model_predict_ecg.elf 和文件夹 codegen 。当您多次执行应用程序以记录执行速度时,应用程序输出创建了 predClassProb.txt 输出文件,其中包含应用程序对输入信号进行分类的概率分数。

输出的文本文件可以在 MATLAB 中重新加载。比较 Jetson 与 MATLAB 在 CPU 上的推理结果。您可以看到,测试信号是正常的窦性心律, Jetson 和 MATLAB 在 NSR 类中的概率得分最高。

% Fetch the result file from Jetson
resultFile = 'predClassProb.txt';
resultFile_hw = fullfile(hwobj.workspaceDir,resultFile);
hwobj.getFile(resultFile_hw)
PredClassProb = readmatrix(resultFile);
PredTableJetson = array2table(PredClassProb(:)','VariableNames',matlab.lang.makeValidName(PredCat));
 
% Execute the function in MALTAB
ModPredProb = model_predict_ecg(signal_data);  
PredTableMATLAB = array2table(ModPredProb(:)','VariableNames',matlab.lang.makeValidName(PredCat));
 
% Display the probability scores from Jetson
disp(PredTableJetson)
 
      ARR         CHF        NSR 
    ________    _______    _______
 
    0.026817    0.17381    0.79937
 
 
% Display the probability scores from MATLAB
disp(PredTableMATLAB)
 
      ARR         CHF        NSR 
    ________    _______    _______
 
    0.026863    0.17401    0.79913

Conclusion

在本文中,我们介绍了如何轻松开发基于人工智能的数字健康应用程序,并将这些算法部署到嵌入式物联网和边缘人工智能平台上,如 NVIDIA Jetson 。该工作流程简化了使用转移学习技术开发生理信号(如 ECG 信号) AI 应用程序的过程。它为开发此类算法提供了一个良好的起点。

有关更多信息,请参阅以下参考资料:

 

Tags