Esta página aún no se ha traducido para esta versión. Puede ver la versión más reciente de esta página en inglés.

Tabla de dispersión de tiempo de wavelet clasificación de datos de fonocardiograma

Este ejemplo muestra cómo clasificar las grabaciones de fonocardiograma humano (PCG) utilizando la dispersión de tiempo de wavelet y un clasificador de máquina de vectores de soporte (SVM). Los fonocardiogramas son grabaciones acústicas de sonidos producidos por las fases sistólicas y diastólicas del corazón. La auscultación del corazón sigue desempeñando un papel importante en el diagnóstico de la evaluación de la salud cardíaca. Desafortunadamente, muchas áreas del mundo carecen de un número suficiente de personal médico capacitado en auscultación cardíaca. En consecuencia, es necesario desarrollar formas automatizadas y fiables de interpretar los datos del fonocardiograma.

Este ejemplo utiliza la dispersión de wavelet como extractor de características para la clasificación PCG. En la dispersión de Wavelet, los datos se propagan a través de una serie de transformaciones de Wavelet, no linealidades y promediación para producir representaciones de baja varianza de los datos. Estas representaciones de baja varianza se utilizan entonces como entradas para un clasificador. Este ejemplo es un problema de clasificación binaria en el que cada registro PCG es "normal" o "anormal".

Descripción de los datos

Este ejemplo utiliza datos de fonocardiograma (PCG) obtenidos de personas con función cardíaca normal y anormal. El conjunto de datos consta de 3829 grabaciones, 2575 de personas con función cardíaca normal y 1254 registros de personas con función cardíaca anormal. Cada grabación tiene una longitud de 10.000 muestras y se muestrea a 2 kHz. Esto representa cinco segundos de datos de fonocardiograma. El DataSet se construye a partir de los datos de entrenamiento y validación utilizados en [1] [2].PhysioNet Computing en cardiología Challenge 2016

Descargar datos

El primer paso es descargar los datos de la.Repositorio de GitHub Para descargar los datos, haga clic y seleccione.Clone or downloadDownload ZIP Guarde el archivo en una carpeta en la que tenga permiso de escritura.physionet_phonocardiogram-master Las instrucciones de este ejemplo suponen que ha descargado el archivo en el directorio temporal (en MATLAB™).tempdir Modifique las instrucciones subsiguientes para descomprimir y cargar los datos si decide descargar los datos de la carpeta diferente.tempdir Si está familiarizado con git, puede descargar la última versión de las herramientas () y obtener los datos de un símbolo del sistema usando.Gitgit clone https://github.com/mathworks/physionet_phonocardiogram

El archivo contienephysionet_phonocardiogram-master.zip

  • PCG_Data. zip

  • README.md

y PCG_Data. zip contiene

  • heartSoundData. MAT

  • extrafiles. MAT

  • Modified_physionet_data. txt

  • License.txt.

contiene los datos y las etiquetas de clase utilizadas en este ejemplo.heartSoundData.mat El archivo. txt, Modified_physionet_data. txt, es requerido por la política de copia de PhysioNet y proporciona las atribuciones de origen para los datos, así como una descripción de cómo cada señal en corresponde a un archivo en los datos de PhysioNet originales. también contiene atribuciones de archivos de origen y se explica en el archivo Modified_physionet_data. txt.heartSoundData.matextrafiles.mat El único archivo necesario para ejecutar el ejemplo es.heartSoundData.mat

Cargar datos

Si ha seguido las instrucciones de descarga en la sección anterior, escriba los siguientes comandos para descomprimir los dos archivos de almacenamiento.

unzip(fullfile(tempdir,'physionet_phonocardiogram-master.zip'),tempdir) unzip(fullfile(tempdir,'physionet_phonocardiogram-master','PCG_Data.zip'),...     fullfile(tempdir,'PCG_Data'))

Después de descomprimir el archivo PCG_Data. zip, cargue los datos en MATLAB.

load(fullfile(tempdir,'PCG_Data','heartSoundData.mat'))

es una matriz de estructura con dos campos: y. es una matriz 10000-by-3829 donde cada columna es una grabación PCG. es una matriz categórica 3829-by-1 de etiquetas de diagnóstico, una para cada columna de.heartSoundDataDataClassesDataClassesData Dado que se trata de un problema de clasificación binaria, las clases son "normales" y "anormales". Como se indicó anteriormente, hay 2575 registros normales y 1254 registros anormales. Equivalentemente, el 67,25% de los ejemplos en los datos provienen de personas con función cardíaca normal, mientras que el 32,75% son de personas con función cardíaca anormal. Puede comprobarlo introduciendo:

summary(heartSoundData.Classes)
     normal        2575       abnormal      1254  
countcats(heartSoundData.Classes)./sum(countcats(heartSoundData.Classes))
ans = 2×1

    0.6725
    0.3275

Marco de dispersión wavelet

Se usa para construir un marco de dispersión de tiempo de wavelet.waveletScattering Establezca la escala invariable para que coincida con la longitud de la señal. El marco de dispersión predeterminado tiene dos transformaciones wavelet (bancos de filtros). El primer banco de filtro wavelet tiene ocho longitudes de onda por octava. El segundo banco de filtros tiene una wavelet por octava.

N = 1e4; sn = waveletScattering('SignalLength',N,'InvarianceScale',N);

Crear conjuntos de pruebas y entrenamiento

La función auxiliar,, divide las 3829 observaciones para que 70% (2680) estén en el conjunto de entrenamiento con 1802 normal y 878 anormal.partition_heartsounds Los 1149 registros restantes (773 normales y 376 anormales) se mantienen en el conjunto de pruebas para la predicción. El generador de números aleatorios se siembra dentro de la función auxiliar para que los resultados sean repetibles. El código para y todas las demás funciones auxiliares utilizadas en este ejemplo se dan en la sección funciones auxiliares al final del ejemplo.partition_heartsounds

[trainData, testData, trainLabels, testLabels] = ...     partition_heartsounds(70,heartSoundData.Data,heartSoundData.Classes);

Puede comprobar los números de cada clase en los conjuntos de entrenamiento y prueba.

summary(trainLabels)
     normal        1802       abnormal       878  
summary(testLabels)
     normal        773       abnormal      376  

Tenga en cuenta que los conjuntos de entrenamiento y prueba se han dividido para que la proporción de registros "normales" y "anormales" en los conjuntos de entrenamiento y prueba sean los mismos que sus proporciones en los datos globales. Puede confirmarlo con lo siguiente.

countcats(trainLabels)./sum(countcats(trainLabels))
ans = 2×1

    0.6724
    0.3276

countcats(testLabels)./sum(countcats(testLabels))
ans = 2×1

    0.6728
    0.3272

Características de dispersión

Obtenga la transformación de dispersión de todas las grabaciones 2680 en el conjunto de entrenamiento. Para series temporales multivariadas, la transformación de dispersión asume que cada columna es una señal independiente. Utilice la opción para obtener el logaritmo natural de los coeficientes de dispersión.'log'

scat_features_train = featureMatrix(sn,trainData,'Transform','log');

Para los parámetros de dispersión dados, es una matriz 340-by-5-by-2680.scat_features_train Hay 340 trayectorias de dispersión y cinco ventanas de dispersión para cada una de las señales 2680. Para pasar esto al clasificador SVM, remodele el tensor en una matriz 13400-by-340 donde cada fila representa una sola ventana de dispersión a través de las rutas de dispersión de 340. El número total de filas es igual al producto de 5 y 2680 (número de grabaciones en los datos de entrenamiento).

Nseq = size(scat_features_train,2); scat_features_train = permute(scat_features_train,[2 3 1]); scat_features_train = reshape(scat_features_train,...     size(scat_features_train,1)*size(scat_features_train,2),[]);

Repita el proceso para los datos de prueba.

scat_features_test = featureMatrix(sn,testData,'Transform','log'); scat_features_test = permute(scat_features_test,[2 3 1]); scat_features_test = reshape(scat_features_test,...     size(scat_features_test,1)*size(scat_features_test,2),[]);

Aquí replicamos las etiquetas para que tengamos una etiqueta para cada ventana de tiempo de dispersión.

[sequence_labels_train,sequence_labels_test] = ...     createSequenceLabels_heartsounds(Nseq,trainLabels,testLabels);

Ajuste el SVM a los datos de entrenamiento. En este ejemplo, usamos un kernel polinómico cúbico. Después de ajustar la SVM a los datos de entrenamiento, realizamos una validación cruzada de 5 veces para estimar el error de generalización en los datos de entrenamiento.

rng default; classificationSVM = fitcsvm(...     scat_features_train, ...     sequence_labels_train , ...     'KernelFunction', 'polynomial', ...     'PolynomialOrder', 3, ...     'KernelScale', 'auto', ...     'BoxConstraint', 1, ...     'Standardize', true, ...     'ClassNames', categorical({'normal','abnormal'})); kfoldmodel = crossval(classificationSVM, 'KFold', 5);

Calcule la pérdida como un porcentaje y visualice la matriz de confusión.

predLabels = kfoldPredict(kfoldmodel); loss = kfoldLoss(kfoldmodel)*100; fprintf('Loss is %2.2f percent\n',loss);
Loss is 0.79 percent 
accuracy = 100-loss; fprintf('Accuracy is %2.2f percent\n',accuracy);
Accuracy is 99.21 percent 
confmatCV = confusionchart(sequence_labels_train,predLabels);

Tenga en cuenta que el marco de dispersión da como resultado 99,21% de precisión cuando cada ventana de tiempo se clasifica por separado. Sin embargo, el rendimiento es realmente mejor que este valor porque tenemos cinco ventanas de dispersión por grabación y la precisión del 99,21 por ciento se basa en clasificar todas las ventanas por separado. En este caso, utilice un voto mayoritario para obtener una asignación de clase única por grabación. El voto de clase corresponde al modo de los votos para las cinco ventanas. Si no se encuentra ningún modo único, clasifique ese conjunto de ventanas de dispersión como para indicar un error de clasificación.helperMajorityVote'NoUniqueMode' Esto da como resultado una columna adicional en la matriz de confusión.

classes = categorical({'abnormal','normal'}); ClassVotes = helperMajorityVote(predLabels,trainLabels,classes); CVaccuracy = sum(eq(ClassVotes,trainLabels))./numel(trainLabels)*100; fprintf('The true cross-validation accuracy is %2.2f percent.\n',CVaccuracy);
The true cross-validation accuracy is 99.93 percent. 

Mostrar la matriz de confusión para las clasificaciones de mayoría de votos.

cmCV = confusionchart(ClassVotes,trainLabels);

La precisión de validación cruzada en los datos de entrenamiento es realmente 99,93 por ciento. Hay dos registros normales, que se clasifican erróneamente como anormales.

Use el modelo SVM apto para los datos de entrenamiento para realizar predicciones de clase en los datos de prueba retenidos.

predTestLabels = predict(classificationSVM,scat_features_test);

Determine la exactitud de las predicciones en el conjunto de pruebas utilizando un voto mayoritario.

ClassVotes = helperMajorityVote(predTestLabels,testLabels,classes); testaccuracy = sum(eq(ClassVotes,testLabels))./numel(testLabels)*100; fprintf('The test accuracy is %2.2f percent.\n',testaccuracy);
The test accuracy is 92.25 percent. 
cmTest = confusionchart(ClassVotes,testLabels);

De los 1149 registros de prueba, 92,25% se clasifican correctamente como "normal" o "anormal". De las 773 grabaciones PCG normales en el conjunto de pruebas, 728 se clasifican correctamente. De las 376 grabaciones anormales en el conjunto de pruebas, 332 se clasifican correctamente.

Precisión, recuperación y puntuación F1

En una tarea de clasificación, la precisión de una clase es el número de resultados positivos correctos dividido entre el número de resultados positivos. En otras palabras, de todos los registros que el clasificador asigna a una etiqueta determinada, qué proporción pertenece realmente a la clase. La recuperación se define como el número de etiquetas correctas dividido entre el número de etiquetas de una clase determinada. Concretamente, de todos los registros pertenecientes a una clase, qué proporción hizo nuestra etiqueta clasificador como esa clase. Al juzgar la precisión de su clasificador, idealmente desea hacerlo bien tanto en precisión como en recuperación. Por ejemplo, supongamos que tenemos un clasificador que etiquetó cada registro PCG como anormal. Entonces nuestro retiro para la clase anormal sería 100%. Todos los registros pertenecientes a la clase anormal serían etiquetados como anormales. Sin embargo, la precisión sería baja. Debido a que nuestro clasificador etiquetó todos los registros como anormales, habría 2575 falsos positivos en este caso para una precisión de 1254/3829, o 32,75%. La puntuación F1 es la media armónica de precisión y recuperación y proporciona una única métrica que resume el rendimiento del clasificador en términos de recuperación y precisión. La función auxiliar, calcula las puntuaciones de precisión, recuperación y F1 para los resultados de la clasificación en el conjunto de pruebas y devuelve los resultados en una tabla.helperF1heartSounds

PRTable = helperF1heartSounds(cmTest.NormalizedValues); disp(PRTable)
                Precision    Recall    F1_Score                 _________    ______    ________      Abnormal     88.298      88.064     88.181      Normal       94.179      94.301     94.239  

En este caso, las puntuaciones F1 para los grupos anormales y normales confirman que nuestro modelo tiene una buena precisión y un recuerdo. En la clasificación binaria es fácil determinar la precisión y la recuperación directamente desde la matriz de confusión. Para ver esto, trace la matriz de confusión de nuevo para mayor comodidad.

cmTest = confusionchart(ClassVotes,testLabels);

El retiro de la clase anormal es el número de registros anormales identificados como anormales, que es la entrada en la primera fila y la primera columna de la matriz de confusión dividida por la suma de las entradas en la primera fila. La precisión de la clase anormal es la proporción de registros anormales verdaderos en el número total identificado como anormal por el clasificador. Eso corresponde a la entrada en la primera fila y la primera columna de la matriz de confusión dividida por la suma de entradas en la primera columna. La puntuación F1 es la media armónica de los dos.

RecallAbnormal = cmTest.NormalizedValues(1,1)/sum(cmTest.NormalizedValues(1,:)); PrecisionAbnormal = cmTest.NormalizedValues(1,1)/sum(cmTest.NormalizedValues(:,1)); F1Abnormal = harmmean([RecallAbnormal PrecisionAbnormal]); fprintf('RecallAbnormal = %2.3f\nPrecisionAbnormal = %2.3f\nF1Abnormal = %2.3f\n',...     100*RecallAbnormal,100*PrecisionAbnormal,100*F1Abnormal);
RecallAbnormal = 88.064 PrecisionAbnormal = 88.298 F1Abnormal = 88.181 

Repita lo anterior para la clase normal.

RecallNormal = cmTest.NormalizedValues(2,2)/sum(cmTest.NormalizedValues(2,:)); PrecisionNormal = cmTest.NormalizedValues(2,2)/sum(cmTest.NormalizedValues(:,2)); F1Normal = harmmean([RecallNormal PrecisionNormal]); fprintf('RecallNormal = %2.3f\nPrecisionNormal = %2.3f\nF1Normal = %2.3f\n',...     100*RecallNormal,100*PrecisionNormal,100*F1Normal);
RecallNormal = 94.301 PrecisionNormal = 94.179 F1Normal = 94.239 

Resumen

Este ejemplo utilizaba la dispersión de tiempo de wavelet para identificar con firmeza las grabaciones de fonocardiogramas humanos como normales o anormales en un problema de clasificación binaria. La dispersión de wavelet solo requería la especificación de un único parámetro, la longitud de la escala invariable, con el fin de producir representaciones de baja varianza de los datos PCG que permitían al clasificador de la máquina de vectores de soporte modelar con precisión la diferencia entre los dos grupos. El clasificador de la máquina de vectores de soporte con dispersión de wavelet fue capaz de lograr un rendimiento superior tanto en precisión como en recuperación para ambos grupos a pesar de un número significativamente desequilibrado de grabaciones PCG normales y anormales en el conjunto de entrenamiento y prueba.

Referencias

  1. Goldberger, A. L., l. n. Amaral, L. Glass, j. m. Hausdorff, P. ch. Ivanov, r. g. Mark, j. e. Mietus, g. b. Moody, C.-K. Peng, y h. e. Stanley. "PhysioBank, PhysioToolkit y PhysioNet: Componentes de un nuevo recurso de investigación para señales fisiológicas complejas ". .Circulation Vol. 101, no. 23, 13 de junio de 2000, PP. e215-E220.http://circ.ahajournals.org/content/101/23/e215.full

  2. Liu et al. "Una base de datos de acceso abierto para la evaluación de algoritmos de sonido cardíaco". .Physiological Measurement Vol. 37, no. 12, 21 de noviembre de 2016, págs. 2181-2213.https://www.ncbi.nlm.nih.gov/pubmed/27869105

Funciones de soporte

partition_heartsounds Crea conjuntos de entrenamiento y pruebas que constan de proporciones especificadas de los datos. La función también preserva la proporción de grabaciones PCG anormales y normales en cada conjunto.

function [trainData, testData, trainLabels, testLabels] = partition_heartsounds(percent_train_split,Data,Labels) % This function is only in support of the Wavelet Time Scattering  % Classification of Phonocardiogram Data example. It may change or be % removed in a future release.  % Labels in heart sound data are not sequential. percent_train_split = percent_train_split/100; % Each column is an observation NormalData = Data(:,Labels == 'normal'); AbnormalData = Data(:,Labels == 'abnormal'); LabelsNormal = Labels(Labels == 'normal'); LabelsAbnormal = Labels(Labels == 'abnormal'); Nnormal = size(NormalData,2); Nabnormal = size(AbnormalData,2); num_train_normal = round(percent_train_split*Nnormal); num_train_abnormal = round(percent_train_split*Nabnormal); rng default; Pnormal = randperm(Nnormal,num_train_normal); Pabnormal = randperm(Nabnormal,num_train_abnormal); notPnormal = setdiff(1:Nnormal,Pnormal); notPabnormal = setdiff(1:Nabnormal,Pabnormal); trainNormalData = NormalData(:,Pnormal); trainNormalLabels = LabelsNormal(Pnormal); trainAbnormalData = AbnormalData(:,Pabnormal); trainAbnormalLabels = LabelsAbnormal(Pabnormal); testNormalData = NormalData(:,notPnormal); testNormalLabels = LabelsNormal(notPnormal); testAbnormalData = AbnormalData(:,notPabnormal); testAbnormalLabels = LabelsAbnormal(notPabnormal); trainData = [trainNormalData trainAbnormalData]; trainData = (trainData-mean(trainData))./std(trainData,1); trainLabels = [trainNormalLabels;  trainAbnormalLabels]; testData = [testNormalData  testAbnormalData]; testData = (testData-mean(testData))./std(testData,1); testLabels = [testNormalLabels; testAbnormalLabels];     end

createSequenceLabels_heartsounds Crea etiquetas de clase para las secuencias de dispersión de tiempo de wavelet.

function [sequence_labels_train,sequence_labels_test] = createSequenceLabels_heartsounds(Nseq,trainLabels,testLabels) % This function is only in support of the Wavelet Time Scattering  % Classification of Phonocardiogram Data example. It may change or be % removed in a future release. Ntrain = numel(trainLabels); trainLabels = repmat(trainLabels',Nseq,1); sequence_labels_train = reshape(trainLabels,Nseq*Ntrain,1); Ntest = numel(testLabels); testLabels = repmat(testLabels',Nseq,1); sequence_labels_test = reshape(testLabels,Ntest*Nseq,1); end

helperMajorityVote implementa un voto mayoritario para una clasificación basada en el modo. Si no hay ningún modo único presente, se devuelve un voto para garantizar que se registre un error de clasificación.NoUniqueMode

function [ClassVotes,ClassCounts] = helperMajorityVote(predLabels,origLabels,classes) % This function is in support of ECGWaveletTimeScatteringExample. It may % change or be removed in a future release.  % Make categorical arrays if the labels are not already categorical predLabels = categorical(predLabels); origLabels = categorical(origLabels); % Expects both predLabels and origLabels to be categorical vectors Npred = numel(predLabels); Norig = numel(origLabels); Nwin = Npred/Norig; predLabels = reshape(predLabels,Nwin,Norig); ClassCounts = countcats(predLabels); [mxcount,idx] = max(ClassCounts); ClassVotes = classes(idx); % Check for any ties in the maximum values and ensure they are marked as % error if the mode occurs more than once modecnt = modecount(ClassCounts,mxcount); ClassVotes(modecnt>1) = categorical({'NoUniqueMode'}); ClassVotes = ClassVotes(:);  %------------------------------------------------------------------------- function modecnt = modecount(ClassCounts,mxcount) modecnt = Inf(size(ClassCounts,2),1); for nc = 1:size(ClassCounts,2)     modecnt(nc) = histc(ClassCounts(:,nc),mxcount(nc)); end  end  % EOF end

helperF1heartSounds calcular la precisión, la recuperación y las puntuaciones F1 para los resultados del clasificador.

function PRTable = helperF1heartSounds(confmat) % This function is only in support of the Wavelet Time Scattering  % Classification of Phonocardiogram Data example. It may change or be % removed in a future release.  precisionAB = confmat(1,1)/sum(confmat(:,1))*100; precisionNR = confmat(2,2)/sum(confmat(:,2))*100 ; recallAB = confmat(1,1)/sum(confmat(1,:))*100; recallNR = confmat(2,2)/sum(confmat(2,:))*100; F1AB = 2*(precisionAB*recallAB)/(precisionAB+recallAB); F1NR = 2*(precisionNR*recallNR)/(precisionNR+recallNR); % Construct a MATLAB Table to display the results. PRTable = array2table([precisionAB recallAB F1AB;...     precisionNR recallNR F1NR],...     'VariableNames',{'Precision','Recall','F1_Score'},'RowNames',...     {'Abnormal','Normal'});  end