如何使用连续小波变换和AlexNet对心电图信号进行分类

574 阅读12分钟

如何使用连续小波变换和AlexNet对心电信号进行分类

心电图信号代表了从人体的一个战略点观察到的心脏电活动,其特点是准周期性的电压。AlexNet是一个卷积神经网络,有八个不同的层。这种方法通常用于科学领域和图像研究。

迁移学习(TL)是机器学习中的一个研究问题,主要是储存在解决一个问题时获得的知识,并将其应用于一个不同但相关的问题。

例如,在学习识别汽车时获得的知识可以在试图识别卡车时使用。

本教程将通过Matlab中的迁移学习,使用预先训练好的深度CNN(AlexNet)对心电图信号进行分类。为此,我们利用了容器小波变换的力量,将一维心电信号表示为图像。这使得它有可能被用作AlexNet的输入。

前提条件

要跟上本教程,你需要具备以下条件。

  • 安装了[MATLAB]。
  • 对[MATLAB]基础知识的正确理解。

用于分类的心电图信号的类型

主要来说,我们采取三种类型的心电图信号。

  • ARR:心律失常
  • CHF:充血性心力衰竭
  • NSR: 正常窦性心律

它们显示如下。

types of signals

这里的主要目的是训练一个CNN来区分ARR、CHF和NSR。这三个信号(162个心电图记录)是从physionet的心电图信号数据库中获得的。这些数据库包括。

  1. MIT-BIH心律失常数据库(96条记录)[ARR信号] 。
  2. MIT-BIH正常窦性心律数据库(30条记录)[NSR信号] 。
  3. BIDMC充血性心力衰竭数据库(36条记录)[CHF信号] 。

第一步是从github资源库下载数据。接下来,点击code 标签,选择download the zip ,下载数据库。

github repository

在这个.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获得时间-频率分析。小波在时间和频率上具有相等的方差。

ecg signal vs image

通过AlexNet进行转移学习

转移学习被称为对预先训练好的CNN进行微调,以对新的图像集合进行分类。与从头开始训练一个CNN相比,转移学习是快速而简单的,后者需要数百万的输入、大量的训练时间和高速、高效的硬件。

对于心电图信号分类,我们使用一个预先训练好的深度CNN。AlexNet已经在超过一百万张图片上进行了训练,可以将图片分为1000个对象类别。

transfer learning procedure

创建数据库的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 ,如下图所示。

show current directory 主文件夹

current directory 子文件夹

current directory 在子文件夹内

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) ,信号的长度保持不变。

这个函数有一个部分是重复的。elseelseif 语句是重复的,只是改变了信号类型。

让我们看看其中的一个部分。

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 用于从数据库中读取多个图像。它包括folderIncludeSubfolders' 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

现在,让我们定义我们要修改的三个层,即fullyConnectedLayersoftmaxLayerclassificationLayer

%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)

现在我们来执行我们的程序。

当我们运行该程序时,我们会看到训练的进展,如下图所示。

training progress

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

最终的训练图如下所示。

final training

在最后的训练中,我们看到达到的准确率是96.67%,这很好。

让我们看一下混淆矩阵。

confusion matrix

混淆矩阵显示了输出类和目标类之间关于分类和错误分类的关系。例如,在arr ,我们看到在50张测试图像中,48张被正确分类,2张被错误分类为chf 。另一方面,对于chf ,49张图片被正确分类,1张被错误分类为nsr

结论

这就是我们如何通过迁移学习来训练我们的深度CNN,一个预训练的网络。这也是我们如何使用cwt来表示二维图像形式的维度信号。

有趣的是,这种方法的输出或准确性取决于信号的长度和你选择的颜色映射。虽然我们使用的是jet(128) ,但如果你选择其他的颜色映射,你会得到不同的结果。