如何使用连续小波变换和AlexNet对心电信号进行分类
心电图信号代表了从人体的一个战略点观察到的心脏电活动,其特点是准周期性的电压。AlexNet是一个卷积神经网络,有八个不同的层。这种方法通常用于科学领域和图像研究。
迁移学习(TL)是机器学习中的一个研究问题,主要是储存在解决一个问题时获得的知识,并将其应用于一个不同但相关的问题。
例如,在学习识别汽车时获得的知识可以在试图识别卡车时使用。
本教程将通过Matlab中的迁移学习,使用预先训练好的深度CNN(AlexNet)对心电图信号进行分类。为此,我们利用了容器小波变换的力量,将一维心电信号表示为图像。这使得它有可能被用作AlexNet的输入。
前提条件
要跟上本教程,你需要具备以下条件。
- 安装了[MATLAB]。
- 对[MATLAB]基础知识的正确理解。
用于分类的心电图信号的类型
主要来说,我们采取三种类型的心电图信号。
- ARR:心律失常
- CHF:充血性心力衰竭
- NSR: 正常窦性心律
它们显示如下。

这里的主要目的是训练一个CNN来区分ARR、CHF和NSR。这三个信号(162个心电图记录)是从physionet的心电图信号数据库中获得的。这些数据库包括。
- MIT-BIH心律失常数据库(96条记录)[ARR信号] 。
- MIT-BIH正常窦性心律数据库(30条记录)[NSR信号] 。
- BIDMC充血性心力衰竭数据库(36条记录)[CHF信号] 。
第一步是从github资源库下载数据。接下来,点击code 标签,选择download the zip ,下载数据库。

在这个.zip 文件中,我们只需要ECGData.mat 。这意味着我们unzip 这个文件来获得它。之后,我们把它放在Matlab的当前目录中。然后,以数据库为参数执行load 函数,将其下载到Matlab。
load(ECGData.mat); %load ecg
为了得到你数据中的信号,执行下面的代码。
data=ECGData.Data;
另外,如果你想存储标签,我们使用ECGData.Labels 。这个命令读取所有的信号,并存储这里所有可用的标签。
labels=ECGData.Labels; %getting labels
现在,这个信号是一个大小为162 x 65536 的矩阵。因此,这意味着它承载了162个ECG信号,每个信号的大小为65536个样本。
心电图信号数据库的准备
对于我们的问题,我们对数据库进行预处理。每个记录都是65536 。这意味着我们将把信号分成长度为500个样本的小信号。这样做是为了增加数据库的大小,使其适合训练CNN。
为了这个目的。
- 我们从每种类型的记录中抽取30个,以便有平等的分布。
- 每条记录被分解成10块,每块长度为500个样本。
- 在900条记录中,我们使用750条记录进行训练,150条记录进行测试。
使用CWT将心电图信号转换为图像
现在,我们想把所有的一维信号转换成图像cwt,作为CNN的输入进行分类。
我们正在使用的深度CNN是Alex,它将图像作为输入。为此,我们对每个一维信号进行CWT处理,并将所有的系数排列起来,形成一个cwt scalogram。
每个谱系图在128种颜色的喷气式颜色图中表示。然后将谱系图转换为图像,并保存到对应于每个类别的文件夹中,因为我们为每个信号类型创建了文件夹。每个图像的大小为227x227 ,并采用RGB颜色格式。
对于cwt,使用的小波是analytic Morlet(amor) 。这些小波是具有单边光谱的小波,在时域上是复值的。这些小波适合于使用cwt获得时间-频率分析。小波在时间和频率上具有相等的方差。

通过AlexNet进行转移学习
转移学习被称为对预先训练好的CNN进行微调,以对新的图像集合进行分类。与从头开始训练一个CNN相比,转移学习是快速而简单的,后者需要数百万的输入、大量的训练时间和高速、高效的硬件。
对于心电图信号分类,我们使用一个预先训练好的深度CNN。AlexNet已经在超过一百万张图片上进行了训练,可以将图片分为1000个对象类别。

创建数据库的Matlab代码(图像信号到scalogram图像的转换)
我们首先使用load 命令加载ecg信号,并将标签赋予数据一个.Label 扩展。这个扩展可以提取数据的标签。
% program to create CWT image database from ecg signal
load('ECGData.mat'); %loading ECG database
data = ECGData.Data; %getting database
labels = ECGData.Labels; %getting labels
现在我们将对每个信号进行30次记录。
ARR = data(1:30,:); %Taken first 30 recordings
CHF = data(97:126,:);
NSR = data(127:156,:);
signallength = 500;
信号的长度是在数据库中指定的,对所有的信号都是一样的。
定义cwt滤波器cwtfilterbank ,用于寻找cwt系数。这个函数接收信号长度、小波类型amor ,以及小波带通滤波器VoicesPerOctave 。
%Defining filters for CWT with Amor wavelet and 12 filtering per octave
fb = cwtfilterbank('SignalLength', signal length, 'Wavelet', 'amor', 'VoicesPerOctave', 12);
在当前目录下为每个标签建立一个文件夹。为了使用matlab命令窗口建立一个文件夹,我们使用mkdir 函数。这个函数会自动创建文件夹。该函数使用文件夹的名称作为参数。
要在主文件夹内创建一个子文件夹,你可以用mainfolder\subfolder 重新运行该命令,如下图所示。
mkdir('ecgdataset'); %main folder
mkdir('ecgdataset\arr'); %sub folder
mkdir('ecgdataset\chf');
mkdir('ecgdataset\nsr');
ecgtype = {'ARR', 'CHF', 'NSR'};
ecgtype 是一个单元格数组,通过字符串承载信号的名称。
现在我们要将这些心电图信号转换为图像。这是用ecg2cwtscg 函数完成的。
%Function to convert ECG to image
ecg2cwtscg(ARR, fb, ecgtype{1});
ecg2cwtscg(CHF, fb, ecgtype{2});
ecg2cwtscg(NSR, fb, ecgtype{3});
这个函数把信号类型和滤波器库fb 作为输入。它接收信号,使用fb ,得到其系数,然后将其转换为图像。
ecgtype 是字符串类型。 代表 , 代表 , 代表 。ecgtype{1} ARR ecgtype{2} CHF ecgtype{3} NSR
这就是我们创建数据库的方式。我们在执行这个程序时,创建了主文件夹dataset ,子文件夹arr,nsr, 和chf ,如下图所示。
主文件夹
子文件夹
在子文件夹内
ecg2cwtscg 不是一个matlab函数。它是我们为转换目的而创建的一个自定义函数。该函数如图所示。
function ecg2cwtscg(ecgdata, cwtfb, ecgtype)
nos = 10; %number of signals
no1 = 500; %signal length
colormap = jet(128);
if ecgtype == 'ARR'
folderpath = strcat('mainpathfolder/arr/');
findx = 0;
for i = 1:30
indx = 0;
for k = 1:nos
ecgsignal = ecgdata(i, indx+1: indx+no1);
cfs = abs(cwtfb.wt(ecgsignal));
im = ind2rgb(im2uint8(rescale(cfs)), colormap);
filenameindex = findx + k;
filename = strcat(folderpath, sprintf('%d.jpg', filenameindex));
imwrite(imresize(im, [227 227]), filename);
indx = indx + no1;
end
findx = findx + nos;
end
elseif ecgtype == 'CHF'
folderpath = strcat('mainfolderpath/chf/');
findx = 0;
for i = 1:30
indx = 0;
for k = 1:nos
ecgsignal = ecgdata(i, indx+1: indx+no1);
cfs = abs(cwtfb.wt(ecgsignal));
im = ind2rgb(im2uint8(rescale(cfs)), colormap);
filenameindex = findx + k;
filename = strcat(folderpath, sprintf('%d.jpg', filenameindex));
imwrite(imresize(im, [227 227]), filename);
indx = indx + no1;
end
findx = findx + nos;
end
else ecgtype == 'NSR'
folderpath = strcat('mainfolderpath/nsr/');
findx = 0;
for i = 1:30
indx = 0;
for k = 1:nos
ecgsignal = ecgdata(i, indx+1: indx+no1);
cfs = abs(cwtfb.wt(ecgsignal));
im = ind2rgb(im2uint8(rescale(cfs)), colormap);
filenameindex = findx + k;
filename = strcat(folderpath, sprintf('%d.jpg', filenameindex));
imwrite(imresize(im, [227 227]), filename);
indx = indx + no1;
end
findx = findx + nos;
end
end
end
信号nos 的数量是10。因为我们有三个信号,10乘以3就得到了我们说的记录数,即30。我们使用的彩色地图是jet(128) ,信号的长度保持不变。
这个函数有一个部分是重复的。else 和elseif 语句是重复的,只是改变了信号类型。
让我们看看其中的一个部分。
if ecgtype == 'ARR'
folderpath = strcat('mainfolderpath/arr/');
findx = 0;
for i = 1:30
indx = 0;
for k = 1:nos
ecgsignal = ecgdata(i, indx+1: indx+no1); %extracting signals
cfs = abs(cwtfb.wt(ecgsignal));
im = ind2rgb(im2uint8(rescale(cfs)), colormap);
filenameindex = findx + k;
filename = strcat(folderpath, sprintf('%d.jpg', filenameindex));
imwrite(imresize(im, [227 227]), filename);
indx = indx + no1;
end
findx = findx + nos;
end
这个程序的意思是,如果信号是ARR 类型的,就创建一个文件夹arr ,并将图像存储在那里。for 循环定义了迭代的次数,对于前30个记录来说是30次。
在for 循环中,我们使用ecgdata(i, indx+1: indx+no1) 来提取信号。然后我们用过滤器fb 找到小波系数。
abs(cwtfb.wt(ecgsignal)) 命令给出了存储在变量cfs 中的小波系数。abs 意味着取绝对值,因为系数是复数。
im = ind2rgb(im2uint8(rescale(cfs)), colormap);
使用ind2rgb 将系数转换为图像,但首先使用rescale 函数对这些系数进行重新缩放。im2uint8 将信号转换为uint8 类型,从而成为图像。colormap 定义了颜色,因为图像是以rgb 形式存在的。
filenameindex = findx + k;
filename = strcat(folderpath, sprintf('%d.jpg', filenameindex));
imwrite(imresize(im, [227 227]), filename);
indx = indx + no1;
图像在调整为227 x 227 像素后,使用imwrite 函数以适当的名称保存。图像以索引形式保存,即使用1.jpg,2.jpg 等,使用indx 函数。
这个程序对其他信号来说也是一样的。我们唯一改变的是信号类型,因此上面的函数很长。
训练AlexNet(转移学习)的Matlab代码
为此,我们需要下载一个工具箱(AlexNet网络的深度学习工具箱)。点击adds on ,在主页上选择get add on ,得到工具箱。当你点击时,你会得到一个窗口,要求你的Mathworks账户。登录后搜索deep learning toolbox for alexnet network ,并下载它。
之后,开始训练过程。第一步是读取数据库文件夹中的图像(我们转换的图像)。
% Training and validation using AlexNet
DatasetPath = 'folderpath/ecgdataset';
% reading images from the image database folder
images = imageDatastore(DatasetPath, 'IncludeSubfolders', true, 'LabelSource', 'foldernames');
DatasetPath 存储了我们的数据集的路径。imageDatastore 用于从数据库中读取多个图像。它包括folder 和IncludeSubfolders' subfolder 中的图像。
我们需要将图像分配到训练和测试中。我们将从每个文件夹中抽取250张图片,我们将有总共750张图片用于训练。由于每个子文件夹有300张图片,用于测试的图片数量将是每个文件夹的50张,总共有150张图片。
% Distributing images in the set of training and testing
numTrainFiles = 250;
[TrainImages, TestImages] = splitEachLabel(images, numTrainFiles, 'randomsize');
所有的训练图像都存储在TrainImages 变量中,测试图像存储在TestImages 。分割是由splitEachLabel 函数完成的,即randomized 。
加载预训练的网络alexnet 。为了加载这个网络,我们执行命令alexnet 。这个命令在你下载工具箱之前是不起作用的。
net = alexnet; %importing pre-trained alexnet(requires support package)
layersTransfer = net. Layers(1:end-3); %preserving all layers except last three layers
net.Layers 加载网络的所有层。我们在变量 中保留所有的层,并修改最后三个,因此 。layersTransfer 1:end-3
定义输出类的数量。
numClasses = 3; %Number of output classes: ARR, CHF, NSR
现在,让我们定义我们要修改的三个层,即fullyConnectedLayer ,softmaxLayer 和classificationLayer 。
%defining layers of alexnet
layers = [layersTransfer
fullyConnectedLayer(numClasses, 'WeightLearnRateFactor', 20, 'BiasLearnRateFactor', 20)
softmaxLayer
classificationLayer];
现在,我们要定义训练选项。它给出了应该进行训练的方向。我们定义解算器,sgdm ,学习率,1e-4 ,验证频率训练进度,以图表形式。
%training options
options = trainingOptions('sgdm', 'MiniBatchSize', 20, 'MaxEpochs', 8, ...
'InitialLearnRate', 1e-4, 'Shuffle', 'every-epoch', 'ValidationData', TestImages, ...
'ValidationFrequency', 10, 'Verbose', false, 'Plots', 'training-progress');
在定义了所有的层和传输选项后,我们开始训练。开始时,我们使用命令trainNetwork 函数。该函数接收图像并根据options 进行训练。这意味着该函数的参数是训练图像、层和选项。
% training the Alexnet
netTransfer = trainNetwork(TrainImages, layers, options);
现在让我们使用classify 命令对图像进行分类。
%Classifying images
YPred = classify(netTransfer, TestImages);
YValidation = TestImages.Labels;
accuracy = sum(YPred == YValidation)/numel(YValidation);
classify 命令将训练好的网络和测试图像存储在YPred 变量中进行分类。它们是预测的分类结果。
Yvalidation 将存储图像的实际验证或标签,因此 。当比较预测的分类 和实际的验证 ,我们可以得到准确率。TestImages.Labels YPred YValidation
为了适当的可视化,绘制混淆矩阵。这个图显示了正确分类的数据数量和错误分类的数量。
%plotting Confusion Matrix
plotconfusion(YValidation, YPred)
现在我们来执行我们的程序。
当我们运行该程序时,我们会看到训练的进展,如下图所示。

我们这里有两张图。上面的图显示的是准确率,而下面的图显示的是损失。随着准确率的提高,损失也在减少。另外,我们还有训练图和验证图。
最终的训练图如下所示。

在最后的训练中,我们看到达到的准确率是96.67%,这很好。
让我们看一下混淆矩阵。

混淆矩阵显示了输出类和目标类之间关于分类和错误分类的关系。例如,在arr ,我们看到在50张测试图像中,48张被正确分类,2张被错误分类为chf 。另一方面,对于chf ,49张图片被正确分类,1张被错误分类为nsr 。
结论
这就是我们如何通过迁移学习来训练我们的深度CNN,一个预训练的网络。这也是我们如何使用cwt来表示二维图像形式的维度信号。
有趣的是,这种方法的输出或准确性取决于信号的长度和你选择的颜色映射。虽然我们使用的是jet(128) ,但如果你选择其他的颜色映射,你会得到不同的结果。