Heartbeat Anomaly Detection¶
Credit: AITS Cainvas Community
Photo on Gifer
According to WHO 17.9 million people die each year due to Cardiovascular Diseases.Over the years it has been found that these deaths can be prevented if the diseases are diagnosed in early stages.AI has brought a major development in the field of healthcare for early diagnosis of these diseases.¶
This model if coupled with digital stethoscopes or some similar IoT Device can help in the detection of anomalies in the Heartbeat Sounds of an individual.¶
Importing the Dataset¶
!wget -N "https://cainvas-static.s3.amazonaws.com/media/user_data/cainvas-admin/heart.zip"
!unzip -qo heart.zip
!rm heart.zip
Importing Necessary Libraries¶
# Pandas
import pandas as pd
# Scikit learn
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score, confusion_matrix
from sklearn.preprocessing import LabelEncoder
from sklearn.utils import shuffle
from sklearn.utils import class_weight
# Keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Convolution2D, Conv2D, MaxPooling2D, GlobalAveragePooling2D
from keras.utils import to_categorical
from keras.optimizers import Adam
# Audio
import librosa
import librosa.display
# Plot
%matplotlib inline
%pylab inline
import matplotlib.pyplot as plt
%config InlineBackend.figure_format = 'retina'
# Utility
import os
import glob
import numpy as np
from tqdm import tqdm
import itertools
# To ignore any warnings
import warnings
warnings.filterwarnings("ignore")
# gather software versions
import tensorflow as tf; print('tensorflow version: ', tf.__version__)
import keras; print('keras version: ',keras.__version__)
# If any warning pops up run the cell again.There is nothing to worry about.
Build Dataset¶
dataset = []
for folder in ["heart/set_a/**"]:
for filename in glob.iglob(folder):
if os.path.exists(filename):
label = os.path.basename(filename).split("_")[0]
# skip audio smaller than 4 secs
if librosa.get_duration(filename=filename)>=4:
if label not in ["Aunlabelledtest"]:
dataset.append({
"filename": filename,
"label": label
})
dataset = pd.DataFrame(dataset)
Exploratory Data Analysis¶
dataset.info()
plt.figure(figsize=(12,6))
dataset.label.value_counts().plot(kind='bar', title="Dataset distribution")
plt.show()
# parent folder of sound files
INPUT_DIR="heart"
# 16 KHz
SAMPLE_RATE = 16000
# seconds
MAX_SOUND_CLIP_DURATION=12
set_a=pd.read_csv(INPUT_DIR+"/set_a.csv")
set_a.head()
set_a.info()
train_ab=set_a
train_ab.describe()
#get all unique labels
nb_classes=train_ab.label.unique()
print("Number of training examples=", train_ab.shape[0], " Number of classes=", len(train_ab.label.unique()))
print (nb_classes)
print('Minimum samples per category = ', min(train_ab.label.value_counts()))
print('Maximum samples per category = ', max(train_ab.label.value_counts()))
Normal Case¶
In the Normal category there are normal, healthy heart sounds. A normal heart sound has a clear “lub dub, lub dub” pattern, with the time from “lub” to “dub” shorter than the time from “dub” to the next “lub”.
normal_file=INPUT_DIR+"/set_a/normal__201106111136.wav"
# hear it
import IPython.display as ipd
ipd.Audio(normal_file)
# Load use wave
import wave
wav = wave.open(normal_file)
print("Sampling (frame) rate = ", wav.getframerate())
print("Total samples (frames) = ", wav.getnframes())
print("Duration = ", wav.getnframes()/wav.getframerate())
# Load using Librosa
y, sr = librosa.load(normal_file, duration=5) #default sampling rate is 22 HZ
dur=librosa.get_duration(y)
print ("duration:", dur)
print(y.shape, sr)
# librosa plot
plt.figure(figsize=(16, 3))
librosa.display.waveplot(y, sr=sr)
Murmur Case¶
Heart murmurs sound as though there is a “whooshing, roaring, rumbling, or turbulent fluid” noise in one of two temporal locations: (1) between “lub” and “dub”, or (2) between “dub” and “lub”. They can be a symptom of many heart disorders, some serious. There will still be a “lub” and a “dub”.
# murmur case
murmur_file=INPUT_DIR+"/set_a/murmur__201108222231.wav"
y2, sr2 = librosa.load(murmur_file,duration=5)
dur=librosa.get_duration(y)
print ("duration:", dur)
print(y2.shape,sr2)
# heart it
import IPython.display as ipd
ipd.Audio(murmur_file)
# show it
plt.figure(figsize=(16, 3))
librosa.display.waveplot(y2, sr=sr2)
Artifact¶
In the Artifact category there are a wide range of different sounds, including feedback squeals and echoes, speech, music and noise. There are usually no discernable heart sounds, and thus little or no temporal periodicity at frequencies below 195 Hz.
# sample file
artifact_file=INPUT_DIR+"/set_a/artifact__201012172012.wav"
y4, sr4 = librosa.load(artifact_file, duration=5)
dur=librosa.get_duration(y)
print ("duration:", dur)
print(y4.shape,sr4)
# heart it
import IPython.display as ipd
ipd.Audio(artifact_file)
# show it
plt.figure(figsize=(16, 3))
librosa.display.waveplot(y4, sr=sr4)
Extrahls¶
Extrahls sounds may appear occasionally and can be identified because there is a heart sound that is out of rhythm involving extra or skipped heartbeats, e.g. a “lub-lub dub” or a “lub dub-dub”.It can be sign of a disease.
# sample file
extrahls_file=INPUT_DIR+"/set_a/extrahls__201101070953.wav"
y5, sr5 = librosa.load(extrahls_file, duration=5)
dur=librosa.get_duration(y)
print ("duration:", dur)
print(y5.shape,sr5)
# heart it
import IPython.display as ipd
ipd.Audio(extrahls_file)
# show it
plt.figure(figsize=(16, 3))
librosa.display.waveplot(y5, sr=sr5)
Split dataset in train and test¶
train, test = train_test_split(dataset, test_size=0.2, random_state=42)
print("Train: %i" % len(train))
print("Test: %i" % len(test))
Show Audio info¶
plt.figure(figsize=(20,20))
idx = 0
for label in dataset.label.unique():
y, sr = librosa.load(dataset[dataset.label==label].filename.iloc[0], duration=4)
idx+=1
plt.subplot(5, 3, idx)
plt.title("%s wave" % label)
librosa.display.waveplot(y, sr=sr)
idx+=1
plt.subplot(5, 3, idx)
D = librosa.amplitude_to_db(np.abs(librosa.stft(y)), ref=np.max)
librosa.display.specshow(D, y_axis='linear')
plt.title("%s spectogram" % label)
idx+=1
mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=40)
plt.subplot(5, 3, idx)
librosa.display.specshow(mfccs, x_axis='time')
plt.title("%s mfcc" % label)
plt.show()
Extract features from audio¶
def extract_features(audio_path):
y, sr = librosa.load(audio_path, duration=4)
mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=40)
return mfccs
%%time
x_train, x_test = [], []
print("Extract features from TRAIN and TEST dataset")
for idx in tqdm(range(len(train))):
x_train.append(extract_features(train.filename.iloc[idx]))
for idx in tqdm(range(len(test))):
x_test.append(extract_features(test.filename.iloc[idx]))
x_test = np.asarray(x_test)
x_train = np.asarray(x_train)
print("X train:", x_train.shape)
print("X test:", x_test.shape)
Encode labels¶
encoder = LabelEncoder()
encoder.fit(train.label)
y_train = encoder.transform(train.label)
y_test = encoder.transform(test.label)
Input shapes¶
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], x_train.shape[2], 1)
x_test = x_test.reshape(x_test.shape[0], x_test.shape[1], x_test.shape[2], 1)
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
print("X train:", x_train.shape)
print("Y train:", y_train.shape)
print()
print("X test:", x_test.shape)
print("Y test:", y_test.shape)
Build Model¶
# Model architecture
model = Sequential()
model.add(Conv2D(filters=16, kernel_size=2, input_shape=(x_train.shape[1],x_train.shape[2],x_train.shape[3]), activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Dropout(0.3))
model.add(Conv2D(filters=32, kernel_size=2, activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Dropout(0.3))
model.add(Conv2D(filters=64, kernel_size=2, activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Dropout(0.3))
model.add(Conv2D(filters=128, kernel_size=2, activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Dropout(0.3))
model.add(GlobalAveragePooling2D())
model.add(Dense(256, activation='relu'))
model.add(Dense(128, activation='relu'))
model.add(Dense(len(encoder.classes_), activation='softmax'))
model.summary()
Compile model¶
model.compile(loss='categorical_crossentropy', metrics=['accuracy'], optimizer=Adam(lr = 0.001))
Fit model¶
history = model.fit(x_train, y_train,
batch_size=256,
epochs=200,
validation_data=(x_test, y_test),
shuffle=True)
Model performance can be increased by increasing number of epochs, training data and creating custom call-backs¶
Training Plot¶
# Loss Curves
plt.figure(figsize=[14,10])
plt.subplot(211)
plt.plot(history.history['loss'],'r',linewidth=3.0)
plt.plot(history.history['val_loss'],'b',linewidth=3.0)
plt.legend(['Training loss', 'Validation Loss'],fontsize=18)
plt.xlabel('Epochs ',fontsize=16)
plt.ylabel('Loss',fontsize=16)
plt.title('Loss Curves',fontsize=16)
# Accuracy Curves
plt.figure(figsize=[14,10])
plt.subplot(212)
plt.plot(history.history['accuracy'],'r',linewidth=3.0)
plt.plot(history.history['val_accuracy'],'b',linewidth=3.0)
plt.legend(['Training Accuracy', 'Validation Accuracy'],fontsize=18)
plt.xlabel('Epochs ',fontsize=16)
plt.ylabel('Accuracy',fontsize=16)
plt.title('Accuracy Curves',fontsize=16)
Save model¶
# Save model and weights
model_name = "HAD.h5"
model.save(model_name)
print('Saved trained model at %s ' % model_name)
Evaluate model¶
scores = model.evaluate(x_test, y_test, verbose=1)
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])
Accessing Model Performance¶
predictions = model.predict(x_test, verbose=1)
y_preds = predictions.argmax(axis=-1)
y_test = y_test.argmax(axis=-1)
y_pred = encoder.inverse_transform(y_preds)
y_test = encoder.inverse_transform(y_test)
df = pd.DataFrame(columns=['Predicted Labels', 'Actual Labels'])
df['Predicted Labels'] = y_pred.flatten()
df['Actual Labels'] = y_test.flatten()
df.head(19)
Compiling model with DeepC¶
!deepCC HAD.h5