传统的医疗保健系统有大量的病人数据,包括生理信号、医疗记录、提供者注释和评论。开发数字健康应用程序所面临的最大挑战是分析大量可用数据,得出可操作的见解,以及开发可在嵌入式设备上运行的解决方案。
在开发这样的端到端解决方案时,从事生物医学数据集工作的工程师和数据科学家经常遇到挑战,因为他们必须手动集成应用程序代码,与必要的工具链集成以进行部署,在许多情况下,还必须重写代码以在目标硬件上运行应用程序。如果算法不能产生预期的结果,他们必须调试底层案例,这可能会很耗时。
这篇文章讨论了数据科学家和工程师如何使用 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 显示了整个工作流。
图 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 。
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 -------------------------------------
从命令行输出和生成的绘图中,您可以看到使用 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)
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 的训练时间性能随着训练数据量的增加而提高。
图 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.gpuConfig 的 GenerateExampleMain 属性,为该应用程序生成了示例 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#include #include // 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 应用程序的过程。它为开发此类算法提供了一个良好的起点。
关于作者
Akhilesh Mishra 是 MathWorks 医疗器械和医疗保健行业的高级应用工程师。他擅长于信号/数据处理、人工智能和 GPU 计算工作流程。他在 MathWorks 工作了 4 年多。 Akhilesh 拥有堪萨斯大学的硕士学位,在那里他是一个研究探测格陵兰和南极洲冰原的雷达系统的小组的信号处理负责人,以研究全球海平面上升。
Kirthi K Devleker 是 MathWorks 的全球医疗器械行业经理。 Kirthi Devleker 在目前的职位上与开发数字健康和医疗设备的客户、学术研究人员和监管机构密切合作,帮助他们了解建模和仿真的价值,以及人们如何利用 AI 等最新趋势来构建下一代医疗设备。在此之前, Kirthi 担任 MathWorks 的高级产品经理,主要负责制定人工智能和信号处理产品的路线图和长期战略。 Kirthi 拥有信号和图像处理方面的背景,并在 MathWorks 工作了 10 年。他拥有加州圣何塞州立大学电气工程硕士学位。
审核编辑:郭婷
全部0条评论
快来发表一下你的评论吧 !