├── README.md ├── deepSleepNet.png ├── deepSleepNet.py ├── dsnPreTrain.png ├── readMat.py ├── seqTrain+TestDeepSleepNet.py ├── seqTrainDeepSleepNet.py ├── supPreTrain+TestDeepSleepNet.py ├── supPreTrainDeepSleepNet.py ├── testDeepSleepNet.py └── trainDeepSleepNet.py /README.md: -------------------------------------------------------------------------------- 1 | This is an implementation of DeepSleepNet as described in [Supratak et. al 2017](https://www.researchgate.net/publication/314942973_DeepSleepNet_a_Model_for_Automatic_Sleep_Stage_Scoring_based_on_Raw_Single-Channel_EEG) using Keras+Tensorflow. 2 | 3 | ### Model summary 4 | 5 | The top part of the neural network employs two parallel deep convolutional networks, one on left tries to learn fine temporal features, whereas the one on the right tries to learn coarse temporal features. Features learnt in ultimate layers is concatenated and fed into two Bidirectional LSTM layers [Supratak et. al 2017](https://www.researchgate.net/publication/314942973_DeepSleepNet_a_Model_for_Automatic_Sleep_Stage_Scoring_based_on_Raw_Single-Channel_EEG). 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /deepSleepNet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kedarps/DeepSleepNet/c6ea5e8c7531ca8f0b424e2ba10b1388a667aacd/deepSleepNet.png -------------------------------------------------------------------------------- /deepSleepNet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Feb 2 14:37:39 2018 4 | 5 | @author: ksp6 6 | """ 7 | 8 | from keras.models import Model 9 | from keras.layers import Dense, Conv1D, MaxPool1D, Dropout, Flatten, LSTM, Input, concatenate, Reshape 10 | from keras.layers.wrappers import Bidirectional 11 | 12 | # sampling rate 13 | Fs = 100 14 | 15 | def makeConvLayers(inputLayer): 16 | # two conv-nets in parallel for feature learning, 17 | # one with fine resolution another with coarse resolution 18 | # network to learn fine features 19 | convFine = Conv1D(filters=64, kernel_size=int(Fs/2), strides=int(Fs/16), padding='same', activation='relu', name='fConv1')(inputLayer) 20 | convFine = MaxPool1D(pool_size=8, strides=8, name='fMaxP1')(convFine) 21 | convFine = Dropout(rate=0.5, name='fDrop1')(convFine) 22 | convFine = Conv1D(filters=128, kernel_size=8, padding='same', activation='relu', name='fConv2')(convFine) 23 | convFine = Conv1D(filters=128, kernel_size=8, padding='same', activation='relu', name='fConv3')(convFine) 24 | convFine = Conv1D(filters=128, kernel_size=8, padding='same', activation='relu', name='fConv4')(convFine) 25 | convFine = MaxPool1D(pool_size=4, strides=4, name='fMaxP2')(convFine) 26 | fineShape = convFine.get_shape() 27 | convFine = Flatten(name='fFlat1')(convFine) 28 | 29 | # network to learn coarse features 30 | convCoarse = Conv1D(filters=32, kernel_size=Fs*4, strides=int(Fs/2), padding='same', activation='relu', name='cConv1')(inputLayer) 31 | convCoarse = MaxPool1D(pool_size=4, strides=4, name='cMaxP1')(convCoarse) 32 | convCoarse = Dropout(rate=0.5, name='cDrop1')(convCoarse) 33 | convCoarse = Conv1D(filters=128, kernel_size=6, padding='same', activation='relu', name='cConv2')(convCoarse) 34 | convCoarse = Conv1D(filters=128, kernel_size=6, padding='same', activation='relu', name='cConv3')(convCoarse) 35 | convCoarse = Conv1D(filters=128, kernel_size=6, padding='same', activation='relu', name='cConv4')(convCoarse) 36 | convCoarse = MaxPool1D(pool_size=2, strides=2, name='cMaxP2')(convCoarse) 37 | coarseShape = convCoarse.get_shape() 38 | convCoarse = Flatten(name='cFlat1')(convCoarse) 39 | 40 | # concatenate coarse and fine cnns 41 | mergeLayer = concatenate([convFine, convCoarse], name='merge') 42 | 43 | return mergeLayer, (coarseShape, fineShape) 44 | 45 | def preTrainingNet(n_feats, n_classes): 46 | inLayer = Input(shape=(n_feats, 1), name='inLayer') 47 | mLayer, (_, _) = makeConvLayers(inLayer) 48 | outLayer = Dense(n_classes, activation='softmax', name='outLayer')(mLayer) 49 | #outLayer = Dense(n_feats, activation='sigmoid', name='outLayer')(mLayer) 50 | 51 | network = Model(inLayer, outLayer) 52 | network.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) 53 | #network.compile(loss='mean_squared_error', optimizer='adadelta') 54 | 55 | return network 56 | 57 | def fineTuningNet(n_feats, n_classes, preTrainedNet): 58 | inLayer = Input(shape=(n_feats, 1), name='inLayer') 59 | mLayer, (cShape, fShape) = makeConvLayers(inLayer) 60 | outLayer = Dropout(rate=0.5, name='mDrop1')(mLayer) 61 | 62 | # this is the network that learns temporal dependencies using LSTM 63 | # merge the outputs of last layers 64 | # reshape because LSTM layer needs 3 dims (None, 1, n_feats) 65 | outLayer = Reshape((1, int(fShape[1]*fShape[2] + cShape[1]*cShape[2])))(outLayer) 66 | outLayer = Bidirectional(LSTM(512, activation='relu', dropout=0.5, name='bLstm1'))(outLayer) 67 | outLayer = Reshape((1, int(outLayer.get_shape()[1])))(outLayer) 68 | outLayer = Bidirectional(LSTM(512, activation='relu', dropout=0.5, name='bLstm2'))(outLayer) 69 | outLayer = Dense(n_classes, activation='softmax', name='outLayer')(outLayer) 70 | 71 | network = Model(inLayer, outLayer) 72 | 73 | # now that we have the network, we will copy the weights from the pretrained network into this network 74 | allPreTrainLayers = dict([(layer.name, layer) for layer in preTrainedNet.layers]) 75 | allFineTuneLayers = dict([(layer.name, layer) for layer in network.layers]) 76 | 77 | allPreTrainLayerNames = [layer.name for layer in preTrainedNet.layers] 78 | # we don't need the input and output layers from the pretrained net, so discard them 79 | allPreTrainLayerNames = [l for l in allPreTrainLayerNames if l not in ['inLayer', 'outLayer']] 80 | 81 | # now set weights of fine tune network based on pre train network 82 | for l in allPreTrainLayerNames: 83 | allFineTuneLayers[l].set_weights(allPreTrainLayers[l].get_weights()) 84 | 85 | network.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) 86 | 87 | return network 88 | -------------------------------------------------------------------------------- /dsnPreTrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kedarps/DeepSleepNet/c6ea5e8c7531ca8f0b424e2ba10b1388a667aacd/dsnPreTrain.png -------------------------------------------------------------------------------- /readMat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Aug 21 16:47:17 2017 4 | 5 | Module for importing Training and Testing Data from .mat files in Matlab. 6 | Ensure to save .mat files using the '-v7.3' flag since this usesh5py package 7 | 8 | @author: ksp6 9 | """ 10 | import h5py 11 | import numpy as np 12 | from sklearn.preprocessing import OneHotEncoder 13 | from sklearn.preprocessing import MinMaxScaler 14 | from sklearn.utils import resample 15 | 16 | def getTrainingData(file_name, one_hot_encode=True, min_max_scale=False): 17 | print('Reading mat file...') 18 | # read -v7.3 .mat files using h5py, make sure to save them using this flag in matlab 19 | # this will give array of arrays 20 | with h5py.File(file_name, 'r') as f: 21 | X_train = np.asarray([ f[element][:].transpose() for element in f['data']['X_train'][()][0] ]) 22 | Y_train = np.asarray([ f[element][:].transpose().ravel() for element in f['data']['Y_train'][()][0] ]) 23 | 24 | # concatenate into large arrays 25 | X_train = np.concatenate(X_train) 26 | Y_train = np.concatenate(Y_train) 27 | 28 | print('Consolidating classes N3 and N4...') 29 | # get rid of class 3, merge it with class 4 30 | Y_train[Y_train == 3] = 4 31 | 32 | if min_max_scale: 33 | print('Standardizing dataset...') 34 | X_train = scale_data(X_train) 35 | 36 | if one_hot_encode: 37 | print('Running one-hot-encoding...') 38 | # one hot encoding for output labels 39 | Y_train = one_hot_encode_data(Y_train.reshape(-1, 1)) 40 | 41 | return (X_train, Y_train) 42 | 43 | def appendZeros(inData, num_zeros=10): 44 | outData = np.append(inData, np.zeros((inData.shape[0], num_zeros)), axis=1) 45 | 46 | return outData 47 | 48 | 49 | def getTestingData(file_name, one_hot_encode=True, min_max_scale=False): 50 | print('Reading mat file...') 51 | # read -v7.3 .mat files using h5py, make sure to save them using this flag in matlab 52 | with h5py.File(file_name, 'r') as f: 53 | X_test = f['data']['X_test'][()].transpose() 54 | Y_test = f['data']['Y_test'][()].transpose().ravel() 55 | 56 | # testing index, it is corrected for python's zero-indexing 57 | testIdx = f['data']['TestSubIdx'][()] - 1 58 | 59 | print('Consolidating classes N3 and N4...') 60 | Y_test[Y_test == 3] = 4 61 | 62 | if min_max_scale: 63 | print('Standardizing dataset...') 64 | X_test = scale_data(X_test) 65 | 66 | if one_hot_encode: 67 | print('Running one-hot-encoding...') 68 | # one hot encoding for output labels 69 | Y_test = one_hot_encode_data(Y_test.reshape(-1, 1)) 70 | 71 | return (X_test, Y_test), testIdx 72 | 73 | def scale_data(inData): 74 | scaler = MinMaxScaler() 75 | outData = scaler.fit_transform(inData) 76 | 77 | return outData 78 | 79 | def one_hot_encode_data(inData): 80 | ohe = OneHotEncoder() 81 | outData = ohe.fit_transform(inData).toarray() 82 | 83 | return outData 84 | 85 | def oversample_minority_class(X, Y): 86 | print('Oversampling minority classes based on number of instances in Class 5...') 87 | 88 | # this does one hot decoding, but since we removed class 3, we only have 5 classes 89 | # and now after argmax, 3 corresponds to class 5 90 | Y = np.argmax(Y, axis=1) 91 | 92 | # we will oversample number of samples in class 5, which corresponds to 3 in 93 | # OHD Y 94 | majClass = 3 95 | nObsPerClass = sum( Y == majClass ) 96 | nFeats = X.shape[1] 97 | 98 | # initialize empty arrays with (num_5 * 6) observations 99 | X_out = np.empty( (nObsPerClass*6, nFeats) ) 100 | Y_out = np.empty( (nObsPerClass*6,) ) 101 | 102 | ptr = 0 103 | for l in np.unique(Y): 104 | X_here = X [ Y == l,: ] 105 | 106 | # keep in mind, now 3 corresponds to class 5 107 | if l == majClass: 108 | X_here_ovs = X_here 109 | else: 110 | X_here_ovs = resample(X_here, n_samples=nObsPerClass) 111 | 112 | X_out[ ptr:ptr+nObsPerClass,: ] = X_here_ovs 113 | Y_out[ ptr:ptr+nObsPerClass ] = l * np.ones((nObsPerClass,)) 114 | 115 | ptr += nObsPerClass 116 | 117 | Y_out = one_hot_encode_data(Y_out.reshape(-1, 1)) 118 | return (X_out, Y_out) 119 | -------------------------------------------------------------------------------- /seqTrain+TestDeepSleepNet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun Sep 17 23:30:24 2017 4 | takes supervised pre-trained network and appends the sequential training network to train on sequential data 5 | 6 | @author: ksp6 7 | """ 8 | 9 | import os 10 | import numpy as np 11 | import readMat as mat 12 | import deepSleepNet as dsn 13 | import scipy.io as io 14 | 15 | # ============================================================================= 16 | # Script 17 | # ============================================================================= 18 | # undersampling factor, usr=1 corresponds to no undersampling 19 | usr = '1' 20 | ch = '1' 21 | 22 | # path where files live 23 | dataDir = os.path.join(os.getcwd(), 'data', 'time_domain', 'USR_'+usr, 'CH_'+ch) 24 | seqTrainDir = os.path.join(os.getcwd(), 'seqTrainResults') 25 | files = os.listdir(dataDir) 26 | 27 | for f in files[:1]: 28 | # data file 29 | datFile = os.path.join(dataDir, f) 30 | 31 | (X_test, Y_test), _ = mat.getTestingData(datFile) 32 | testIdx = (f.split('.')[0]).split('_')[1] 33 | print('Testing on sub {}...\n'.format(testIdx)) 34 | 35 | # reshape to keep input to NN consistent 36 | X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1)) 37 | 38 | (_, n_feats, _) = X_test.shape 39 | (_, n_classes) = Y_test.shape 40 | 41 | # first build pre-train net 42 | preTrainNN = dsn.preTrainingNet(n_feats, n_classes) 43 | seqTrainNN = dsn.fineTuningNet(n_feats, n_classes, preTrainNN) 44 | 45 | # load network weights from seq training results 46 | seqFile = os.path.join(seqTrainDir, 'superviseSeqTrainNet_TestSub'+testIdx+'.h5') 47 | seqTrainNN.load_weights(seqFile, by_name=True) 48 | 49 | Y_pred = seqTrainNN.predict(X_test, verbose=1) 50 | io.savemat('predSeqTestSub'+testIdx+'.mat', {'Y_pred' : Y_pred, 'Y_true' : Y_test}) 51 | 52 | -------------------------------------------------------------------------------- /seqTrainDeepSleepNet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun Sep 17 23:30:24 2017 4 | takes supervised pre-trained network and appends the sequential training network to train on sequential data 5 | 6 | @author: ksp6 7 | """ 8 | 9 | import os 10 | import numpy as np 11 | import readMat as mat 12 | import deepSleepNet as dsn 13 | 14 | # ============================================================================= 15 | # Script 16 | # ============================================================================= 17 | # undersampling factor, usr=1 corresponds to no undersampling 18 | usr = '1' 19 | ch = '1' 20 | 21 | # path where files live 22 | dataDir = os.path.join(os.getcwd(), 'data', 'time_domain', 'USR_'+usr, 'CH_'+ch) 23 | preTrainDir = os.path.join(os.getcwd(), 'preTrainResults') 24 | files = os.listdir(dataDir) 25 | 26 | for f in files: 27 | # data file 28 | datFile = os.path.join(dataDir, f) 29 | 30 | (X_train_seq, Y_train_seq) = mat.getTrainingData(datFile) 31 | testIdx = (f.split('.')[0]).split('_')[1] 32 | 33 | # reshape to keep input to NN consistent 34 | X_train_seq = np.reshape(X_train_seq, (X_train_seq.shape[0], X_train_seq.shape[1], 1)) 35 | 36 | (n_samples, n_feats, _) = X_train_seq.shape 37 | (_, n_classes) = Y_train_seq.shape 38 | 39 | # first build pre-train net 40 | preTrainNN = dsn.preTrainingNet(n_feats, n_classes) 41 | # load weights from saved network 42 | psrFile = os.path.join(preTrainDir, 'supervisePreTrainNet_TestSub'+testIdx+'.h5') 43 | preTrainNN.load_weights(psrFile, by_name=True) 44 | 45 | seqTrainNN = dsn.fineTuningNet(n_feats, n_classes, preTrainNN) 46 | 47 | 48 | -------------------------------------------------------------------------------- /supPreTrain+TestDeepSleepNet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Feb 1 06:43:58 2018 4 | Test DeepSleepNet on testing data 5 | 6 | @author: ksp6 7 | """ 8 | 9 | import os 10 | import numpy as np 11 | import deepSleepNet as dsn 12 | import readMat as mat 13 | import scipy.io as io 14 | 15 | usr = '1' 16 | ch = '1' 17 | 18 | # path where files live 19 | dataDir = os.path.join(os.getcwd(), 'data', 'time_domain', 'USR_'+usr, 'CH_'+ch) 20 | nnDir = os.path.join(os.getcwd(), 'preTrainResults') 21 | saveDir = os.path.join(os.getcwd(), 'results', 'supPreTrain+Test') 22 | 23 | # initialize deep sleep net 24 | n_feats = 3000 25 | n_classes = 5 26 | preTrainNN = dsn.preTrainingNet(n_feats, n_classes) 27 | 28 | files = os.listdir(dataDir) 29 | accuracies = [] 30 | 31 | for f in files: 32 | file = os.path.join(dataDir, f) 33 | 34 | (X_test, Y_test), _ = mat.getTestingData(file) 35 | testIdx = (f.split('.')[0]).split('_')[1] 36 | print('Testing on sub {}...\n'.format(testIdx)) 37 | 38 | # reshape to keep input to NN consistent 39 | X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1)) 40 | 41 | # set weights from pre-trained network 42 | psrName = os.path.join(nnDir, 'supervisePreTrainNet_TestSub'+testIdx+'.h5') 43 | preTrainNN.load_weights(psrName, by_name=True) 44 | 45 | Y_pred = preTrainNN.predict(X_test, verbose=1) 46 | io.savemat(os.path.join(saveDir, 'predictionTestSub'+testIdx+'.mat'), {'Y_pred' : Y_pred, 'Y_true' : Y_test}) 47 | -------------------------------------------------------------------------------- /supPreTrainDeepSleepNet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun Sep 17 23:30:24 2017 4 | Trains neural network on oversampled training data 5 | 6 | @author: ksp6 7 | """ 8 | 9 | import os 10 | import numpy as np 11 | import readMat as mat 12 | import deepSleepNet as dsn 13 | 14 | # ============================================================================= 15 | # Script 16 | # ============================================================================= 17 | # undersampling factor, usr=1 corresponds to no undersampling 18 | usr = '1' 19 | ch = '1' 20 | 21 | # path where files live 22 | dataDir = os.path.join(os.getcwd(), 'data', 'time_domain', 'USR_'+usr, 'CH_'+ch) 23 | files = os.listdir(dataDir) 24 | 25 | for f in files: 26 | file = os.path.join(dataDir, f) 27 | 28 | (X_train, Y_train) = mat.getTrainingData(file) 29 | (X_train_ovs, Y_train_ovs) = mat.oversample_minority_class(X_train, Y_train) 30 | testIdx = (f.split('.')[0]).split('_')[1] 31 | 32 | # reshape to keep input to NN consistent 33 | X_train_ovs = np.reshape(X_train_ovs, (X_train_ovs.shape[0], X_train_ovs.shape[1], 1)) 34 | 35 | (n_samples, n_feats, _) = X_train_ovs.shape 36 | (_, n_classes) = Y_train_ovs.shape 37 | 38 | # pre-training phase 39 | preTrain = dsn.preTrainingNet(n_feats, n_classes) 40 | # train this network on oversampled dataset 41 | preTrain.fit(X_train_ovs, Y_train_ovs, epochs=75, batch_size=100) 42 | 43 | # save neural network weights so that we can use them while testing 44 | preTrain.save_weights('supervisePreTrainNet_TestSub'+ testIdx +'.h5') -------------------------------------------------------------------------------- /testDeepSleepNet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Feb 1 06:43:58 2018 4 | Test DeepSleepNet on testing data 5 | 6 | @author: ksp6 7 | """ 8 | 9 | import os 10 | import numpy as np 11 | import deepSleepNet as dsn 12 | import readMat as mat 13 | 14 | usr = '1' 15 | ch = '1' 16 | 17 | # path where files live 18 | dataDir = os.path.join(os.getcwd(), 'data', 'time_domain', 'USR_'+usr, 'CH_'+ch) 19 | nnDir = os.path.join(os.getcwd(), 'preTrainResults') 20 | files = os.listdir(dataDir) 21 | 22 | accuracies = [] 23 | 24 | for f in files: 25 | file = os.path.join(dataDir, f) 26 | 27 | (X_test, Y_test), _ = mat.getTestingData(file) 28 | testIdx = (f.split('.')[0]).split('_')[1] 29 | print('Testing on sub {}...\n'.format(testIdx)) 30 | 31 | # reshape to keep input to NN consistent 32 | X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1)) 33 | 34 | (_, n_feats, _) = X_test.shape 35 | (_, n_classes) = Y_test.shape 36 | 37 | # initialize deep sleep net 38 | preTrainNN = dsn.preTrainingNet(n_feats, n_classes) 39 | 40 | # set weights from pre-trained network 41 | psrName = os.path.join(nnDir, 'supervisePreTrainNet_TestSub'+testIdx+'.h5') 42 | preTrainNN.load_weights(psrName, by_name=True) 43 | 44 | # now with saved network run testing data 45 | scores = preTrainNN.evaluate(X_test, Y_test, verbose=1) 46 | accuracies.append([int(testIdx), scores[1]*100]) 47 | print('Accuracy = {} %...\n'.format(scores[1]*100)) 48 | 49 | np.savetxt('preTrainTestingAccuracies.csv', np.array(accuracies), delimiter=';') 50 | -------------------------------------------------------------------------------- /trainDeepSleepNet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun Sep 17 23:30:24 2017 4 | Trains neural network on oversampled training data 5 | 6 | @author: ksp6 7 | """ 8 | 9 | import os 10 | import numpy as np 11 | import readMat as mat 12 | import deepSleepNet as dsn 13 | 14 | # ============================================================================= 15 | # Script 16 | # ============================================================================= 17 | # undersampling factor, usr=1 corresponds to no undersampling 18 | usr = '1' 19 | ch = '1' 20 | 21 | # path where files live 22 | dataDir = os.path.join(os.getcwd(), 'data', 'time_domain', 'USR_'+usr, 'CH_'+ch) 23 | files = os.listdir(dataDir) 24 | 25 | for f in files: 26 | file = os.path.join(dataDir, f) 27 | 28 | (X_train, Y_train) = mat.getTrainingData(file) 29 | (X_train_ovs, Y_train_ovs) = mat.oversample_minority_class(X_train, Y_train) 30 | testIdx = (f.split('.')[0]).split('_')[1] 31 | 32 | # reshape to keep input to NN consistent 33 | X_train_ovs = np.reshape(X_train_ovs, (X_train_ovs.shape[0], X_train_ovs.shape[1], 1)) 34 | 35 | (n_samples, n_feats, _) = X_train_ovs.shape 36 | (_, n_classes) = Y_train_ovs.shape 37 | 38 | # pre-training phase 39 | preTrain = dsn.preTrainingNet(n_feats, n_classes) 40 | # train this network on oversampled dataset 41 | preTrain.fit(X_train_ovs, Y_train_ovs, epochs=75, batch_size=100) 42 | 43 | # save neural network weights so that we can use them while testing 44 | preTrain.save_weights('supervisePreTrainNet_TestSub'+ testIdx +'.h5') --------------------------------------------------------------------------------