Epileptic Seizure Recognition¶
Credit: AITS Cainvas Community
Photo by Epilepsy Foundation on YouTube
A sudden rush of electric activity in the brain is called a seizure. Epilepsy is a chronic neurological disorder causing involuntory, recurrent seizures.
Seizures can either be generalized (affecting the whole brain) or focussed (affecting one part of the brain).
Deep learning can be used to detect and monitor seizures in patients and IoT provides an ease of integration with the existing health system and patient wearability.
import numpy as np
import pandas as pd
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, LSTM
from tensorflow.keras.optimizers import Adam, SGD
from tensorflow.keras.callbacks import EarlyStopping
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import confusion_matrix
import random
import matplotlib.pyplot as plt
The dataset¶
Andrzejak RG, Lehnertz K, Rieke C, Mormann F, David P, Elger CE (2001) Indications of nonlinear deterministic and finite dimensional structures in time series of brain electrical activity: Dependence on recording region and brain state, Phys. Rev. E, 64, 061907
The CSV file consists of processed EEG recordings of patients at different points of time.
The original dataset has 5 different folders, each with 100 files, with each file representing a single subject/person. Each file is a recording of brain activity for 23.6 seconds. The corresponding time-series is sampled into 4097 data points. This was then divided and shuffled into 23 chunks, each chunk containing 178 data points for 1 second, and each data point is the value of the EEG recording at a different point in time. Thus, we have 23 x 500 = 11500 pieces of information, each information contains 178 data points for 1 second.
The last column contains the categorical variable with the following values -
5 - Recording the EEG signal of the brain when the patient had their eyes open
4 - Recording the EEG signal when the patient had their eyes closed
3 - Recording the EEG activity from the healthy brain area
2 - Recordering the EEG from the area where the tumor was located
1 - Recording of seizure activity
Here, only label 1 corresponds to seizure activity.
We will be training the model to identify patients with seizure activity against the rest of the classes.
df = pd.read_csv('https://cainvas-static.s3.amazonaws.com/media/user_data/cainvas-admin/Epileptic_Seizure_Recognition.csv')
df
As we will be classifying samples into two categories, epileptic (label 1) and non-epileptic (labels 2, 3, 4, 5), we will change the labels in the dataframe.
df['y'] = (df['y'] ==1).astype('int')
df['y']
# The spread of labels in the dataframe
df['y'].value_counts()
This is an unbalanced dataest.
# Defining a list with class names corresponding to list indices
class_names = ['Non-epileptic', 'Epileptic']
Preprocessing¶
Resampling¶
In order to balance the dataset, there are two options,
- upsampling - resample the values to make their count equal to the class label with the higher count (here, 9200).
- downsampling - pick n samples from each class label where n = number of samples in class with least count (here, 2300)
Here, we will be upsampling.
# separating into 2 dataframes, one for each class
df1 = df[df['y'] == 1]
df0 = df[df['y'] == 0]
print("Number of samples in:")
print("Class label 0 - ", len(df0))
print("Class label 1 - ", len(df1))
# Upsampling
df1 = df1.sample(len(df0), replace = True) # replace = True enables resampling
print('\nAfter resampling - ')
print("Number of samples in:")
print("Class label 0 - ", len(df0))
print("Class label 1 - ", len(df1))
# concatente to form a single dataframe
df = df0.append(df1)
print('Total number of samples - ', len(df))
# defining the input and output columns to separate the dataset in the later cells.
input_columns = list(df.columns[1:-1]) # exculding the first 'Unnamed' column
output_columns = list(df.columns[-1])
print("Number of input columns: ", len(input_columns))
#print("Input columns: ", ', '.join(input_columns))
print("Number of output columns: ", len(output_columns))
#print("Output columns: ", ', '.join(output_columns))
Train test split¶
# Splitting into train, val and test set -- 80-10-10 split
# First, an 80-20 split
train_df, val_test_df = train_test_split(df, test_size = 0.2)
# Then split the 20% into half
val_df, test_df = train_test_split(val_test_df, test_size = 0.5)
print("Number of samples in...")
print("Training set: ", len(train_df))
print("Validation set: ", len(val_df))
print("Testing set: ", len(test_df))
# Splitting into X (input) and y (output)
Xtrain, ytrain = np.array(train_df[input_columns]), np.array(train_df[output_columns])
Xval, yval = np.array(val_df[input_columns]), np.array(val_df[output_columns])
Xtest, ytest = np.array(test_df[input_columns]), np.array(test_df[output_columns])
Scaling¶
df.describe()
All the features have similar range of values. But they are skewed differently as their mean values indicate.
# Using standard scaler to standardize them to values with mean = 0 and variance = 1.
standard_scaler = StandardScaler()
# Fit on training set alone
Xtrain = standard_scaler.fit_transform(Xtrain)
# Use it to transform val and test input
Xval = standard_scaler.transform(Xval)
Xtest = standard_scaler.transform(Xtest)
The model¶
model = Sequential([
Dense(512, activation = 'relu', input_shape = Xtrain[0].shape),
Dense(256, activation = 'relu'),
Dense(128, activation = 'relu'),
Dense(1, activation = 'sigmoid')
])
cb = [EarlyStopping(monitor = 'val_loss', patience = 3, restore_best_weights = True)]
model.summary()
model.compile(optimizer=Adam(0.01), loss='binary_crossentropy', metrics=['accuracy'])
history1 = model.fit(Xtrain, ytrain, validation_data = (Xval, yval), epochs=16, callbacks = cb)
model.compile(optimizer=Adam(0.001), loss='binary_crossentropy', metrics=['accuracy'])
history2 = model.fit(Xtrain, ytrain, validation_data = (Xval, yval), epochs=16, callbacks = cb)
model.evaluate(Xtest, ytest)
Plotting the metrics¶
def plot(history1, history2, variable1, variable2):
# combining metrics from both trainings
var1_history = history1[variable1]
var1_history.extend(history2[variable1])
var2_history = history1[variable2]
var2_history.extend(history2[variable2])
# plotting them
plt.plot(range(len(var1_history)), var1_history)
plt.plot(range(len(var2_history)), var2_history)
plt.legend([variable1, variable2])
plt.title(variable1)
plot(history1.history, history2.history, "accuracy", 'val_accuracy')
plot(history1.history, history2.history, "loss", 'val_loss')
Model evaluation¶
cm = confusion_matrix(ytest, (model.predict(Xtest)>0.5).astype('int'))
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
for i in range(cm.shape[1]):
for j in range(cm.shape[0]):
plt.text(j, i, format(cm[i, j], '.2f'), horizontalalignment="center", color="black")
plt.imshow(cm, cmap=plt.cm.Blues)
Balancing the dataset is an important step in achieving high performance.
It is important to note that good results, if not the best can be achieved with an unbalanced dataset too! Try running the same notebook, but skip the resampling section code and see the results!
Prediction¶
# pick random test data sample from one batch
x = random.randint(0, len(Xtest) - 1)
output_true = np.array(ytest)[x][0]
print("True: ", class_names[output_true])
output = model.predict(Xtest[x].reshape(1, -1))[0][0]
pred = int(output>0.5) # finding max
print("Predicted: ", class_names[pred], "(",output, "-->", pred, ")") # Picking the label from class_names base don the model output
deepC¶
model.save('epileptic_seizure.h5')
!deepCC epileptic_seizure.h5
# pick random test data sample from one batch
x = random.randint(0, len(Xtest) - 1)
output_true = np.array(ytest)[x][0]
print("True: ", class_names[output_true])
np.savetxt('sample.data', Xtest[i])
# run exe with input
!epileptic_seizure_deepC/epileptic_seizure.exe sample.data
# Picking the label from class_names base don the model output