Commit f0ade3d4 authored by Ard Kastrati's avatar Ard Kastrati
Browse files

Merge branch 'master' of https://gitlab.ethz.ch/kard/dl-project

parents 9257b834 aa01b2e1
import keras
import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Activation, Permute, Dropout, LSTM
from tensorflow.keras.layers import Conv2D, MaxPooling2D, AveragePooling2D
from tensorflow.keras.layers import SeparableConv2D, DepthwiseConv2D
from tensorflow.keras.layers import BatchNormalization
from tensorflow.keras.constraints import max_norm
from sklearn import preprocessing
from config import config
from utils.utils import *
import os
from keras.callbacks import CSVLogger
import seaborn as sns
sns.set_style('darkgrid')
def run(trainX, trainY):
if config['split']:
config['model']=config['model']+'_cluster'
classifier = Classifier_DEEPEYE_LSTM(output_directory=config['root_dir'],
input_shape=config['deepeye-lstm']['input_shape'])
hist = classifier.fit(trainX, trainY)
plot_loss(hist, config['model_dir'], config['model'], True)
plot_acc(hist, config['model_dir'], config['model'], True)
save_logs(hist, config['model_dir'], config['model'], pytorch=False)
save_model_param(classifier.model, config['model_dir'], config['model'], pytorch=False)
class Classifier_DEEPEYE_LSTM:
"""
Inputs:
nb_classes : int, number of classes to classify
input_shape : shape of the input tensor, in our case: 129 * 500 * 1
use_bottleneck : use Bottleneck layer to select the most informative channels
use_residual : use a shortcut layer (RNN) to try to avoid vanishing gradient
kernel_size : 41
batch_size : 64
epochs : 1500
output_directory: directory where plot weights and results are stored
depth : 6, number of repetion of the inception module
Outputs:
y_pred : class (left/right for nb_class=2) of the given input_tensor
"""
def __init__(self, output_directory, input_shape, dropoutRate=0.4, verbose=True, build=True,
batch_size=64, nb_filters=32, use_residual=True, use_bottleneck=True, depth=6,
kernel_size=41, nb_epochs=300):
self.output_directory = output_directory
self.nb_filters = nb_filters
self.use_residual = use_residual
self.use_bottleneck = use_bottleneck
self.depth = depth
self.kernel_size = kernel_size - 1
self.callbacks = None
self.batch_size = batch_size
self.bottleneck_size = 32
self.nb_epochs = nb_epochs
self.dropoutRate = dropoutRate
self.momentum = 0.85
self.lr = 0.1
self.feature_nb = 32
if build:
# build model
if config['split']:
self.model = self.split_model(input_shape)
else:
self.model = self.build_model(input_shape)
if verbose:
self.model.summary()
print(20*'*')
print("Parameters are: Dropout rate:", self.dropoutRate, " Momentum:", self.momentum,
"Learning rate:", self.lr, "# of features:", self.feature_nb)
print(20*'*')
# self.model.save_weights(self.output_directory + 'model_init.hdf5')
def _LSTM_preprocessing(self, input_tensor, output_feature_nb):
lstm = Sequential()
lstm.add(LSTM(output_feature_nb, return_sequences=True))
lstm.add(Dropout(self.dropoutRate))
lstm.add(LSTM(output_feature_nb, return_sequences=True))
lstm.add(Dropout(self.dropoutRate))
lstm.add(keras.layers.BatchNormalization())
output_tensor = lstm(input_tensor)
return output_tensor
def _inception_module(self, input_tensor, stride=1, activation='linear'):
'''
Inception Network
Input:
input_tensor : input of size (129 * 500 * 1) to be forwarded
stride : 1
F1 : number of filters of the first convolution
kernLength : 25, second dimension of the kernel in the first convolution, the first dimension is 1
D : 2, depth multiplier
F1 : 8,
activation function : linear
Output:
output_tensor : input through the inception network
'''
if self.use_bottleneck and int(input_tensor.shape[-1]) > 1:
input_inception = keras.layers.Conv1D(filters=self.bottleneck_size, kernel_size=1,
padding='same', activation=activation, use_bias=False)(input_tensor)
else:
input_inception = input_tensor
# kernel_size_s = [3, 5, 8, 11, 17]
kernel_size_s = [self.kernel_size // (2 ** i) for i in range(3)]
conv_list = []
for i in range(len(kernel_size_s)):
conv_list.append(keras.layers.Conv1D(filters=self.nb_filters, kernel_size=kernel_size_s[i],
strides=stride, padding='same', activation=activation,
use_bias=False)(input_inception))
max_pool_1 = keras.layers.MaxPool1D(pool_size=3, strides=stride, padding='same')(input_tensor)
conv_6 = keras.layers.Conv1D(filters=self.nb_filters, kernel_size=1, padding='same', activation=activation,
use_bias=False)(max_pool_1)
conv_list.append(conv_6)
x = keras.layers.Concatenate(axis=2)(conv_list)
x = keras.layers.BatchNormalization()(x)
x = keras.layers.Activation(activation='relu')(x)
return x
def _shortcut_layer(self, input_tensor, out_tensor):
'''
implementation of a shortcut layer inspired by the Residual NN
'''
shortcut_y = keras.layers.Conv1D(filters=int(out_tensor.shape[-1]), kernel_size=1,
padding='same', use_bias=False)(input_tensor)
shortcut_y = keras.layers.BatchNormalization()(shortcut_y)
x = keras.layers.Add()([shortcut_y, out_tensor])
x = keras.layers.Activation('relu')(x)
return x
def build_model(self, input_shape):
input_layer = keras.layers.Input((input_shape[0], input_shape[1]))
lstm_tensor = self._LSTM_preprocessing(input_layer, self.feature_nb)
x = lstm_tensor
input_res = lstm_tensor
for d in range(self.depth):
x = self._inception_module(x)
if self.use_residual and d % 3 == 2:
x = self._shortcut_layer(input_res, x)
input_res = x
gap_layer = tf.keras.layers.GlobalAveragePooling1D()(x)
# Add Dropout layer
gap_layer = tf.keras.layers.Dropout(self.dropoutRate)(gap_layer)
output_layer = tf.keras.layers.Dense(1, activation='sigmoid')(gap_layer)
model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer)
return model
def split_model(self, input_shape):
input_layer = keras.layers.Input((input_shape[0], input_shape[1]))
output=[]
# run inception over the cluster
for c in config['cluster'].keys():
output.append(self.build_model(input_shape = None, X = tf.expand_dims(tf.transpose(tf.nn.embedding_lookup(
tf.transpose(input_layer,(1,0,2)),config['cluster'][c]),(1,0,2)),axis=-1), c = c))
# append the results and perform 1 dense layer with last_channel dimension and the output layer
x = tf.keras.layers.Concatenate(axis=1)(output)
dense=tf.keras.layers.Dense(32, activation='relu')(x)
output_layer=tf.keras.layers.Dense(1, activation='sigmoid')(dense)
model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer)
return model
def fit(self, lstm_x, y):
# Add early stopping and reduced learning rata to mitigate overfitting
reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=15,
min_lr=0.0001)
early_stop = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=50)
csv_logger = CSVLogger(config['batches_log'], append=True, separator=';')
if self.batch_size is None:
mini_batch_size = int(min(deepeye_x.shape[0] / 10, 16))
else:
mini_batch_size = self.batch_size
self.model.compile(loss='binary_crossentropy', optimizer=keras.optimizers.Adam(learning_rate=self.lr), metrics=['accuracy'])
#self.model.compile(loss='binary_crossentropy', optimizer=keras.optimizers.SGD(learning_rate=self.lr,
#momentum=self.momentum, nesterov=True), metrics=['accuracy'])
#self.model.compile(loss='binary_crossentropy',
#optimizer=keras.optimizers.RMSprop(learning_rate=self.lr, momentum=self.momentum), metrics=['accuracy'])
hist = self.model.fit(lstm_x, y, batch_size=mini_batch_size, verbose=1, validation_split=0.25,
epochs=self.nb_epochs, shuffle=True, callbacks=[early_stop, csv_logger])
return hist
......@@ -16,10 +16,9 @@ sns.set_style('darkgrid')
def run(trainX, trainY):
if config['split']:
classifier = split_model()
config['model']=config['model']+'_cluster'
else:
classifier = Classifier_DEEPEYE(output_directory=config['root_dir'], input_shape=config['deepeye']['input_shape'])
classifier = Classifier_DEEPEYE(output_directory=config['root_dir'], input_shape=config['deepeye']['input_shape'])
hist = classifier.fit(deepeye_x=trainX, y=trainY)
plot_loss(hist, config['model_dir'], config['model'], True)
......@@ -27,28 +26,7 @@ def run(trainX, trainY):
save_logs(hist, config['model_dir'], config['model'], pytorch=False)
save_model_param(classifier.model, config['model_dir'], config['model'], pytorch=False)
def split_model():
'''
given model apply on each cluster and followed by a dense layer on the concatenate outputs
'''
shape=tuple(list(config['deepeye']['input_shape']).append(1))
input_layer = tf.keras.layers.Input(shape)
output=[]
# run inception over the cluster
for c in config['cluster'].keys():
input_shape = tuple(list(config['deepeye']['input_shape'][0]).append(len(config['cluster'][c])))
output.append(Classifier_INCEPTION(output_directory = config['root_dir'], input_shape = input_shape,
X = input_layer[...,config['cluster'][c]]))
# append the results and perform 1 dense layer with last_channel dimension and the output layer
x = tf.keras.layers.Concatenate(axis=2)(output)
dense=tf.keras.layers.Dense(last_channel=32, activation='relu')(x)
output_layer=tf.keras.layers.Dense(1,activation='sigmoid')(dense)
model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer)
model.compile(loss='binary_crossentropy', optimizer=keras.optimizers.Adam(), metrics=['accuracy'])
return model
class Classifier_DEEPEYE:
"""
......@@ -71,7 +49,7 @@ class Classifier_DEEPEYE:
def __init__(self, output_directory, input_shape, verbose=False, build=True,
batch_size=64, nb_filters=32, use_residual=True, use_bottleneck=True, depth=6,
kernel_size=41, nb_epochs=1500, X = None):
kernel_size=41, nb_epochs=1500):
self.output_directory = output_directory
self.nb_filters = nb_filters
......@@ -85,20 +63,46 @@ class Classifier_DEEPEYE:
self.nb_epochs = nb_epochs
if build:
# build model
self.model = self.build_model(input_shape, X)
if config['split']:
self.model = self.split_model(input_shape)
else:
self.model = self.build_model(input_shape)
if verbose:
self.model.summary()
self.verbose = verbose
# self.model.save_weights(self.output_directory + 'model_init.hdf5')
# self.model.save_weights(self.output_directory + 'model_init.hdf5')
def split_model(self,input_shape):
input_layer = keras.layers.Input((input_shape[0], input_shape[1]))
output=[]
# run inception over the cluster
for c in config['cluster'].keys():
output.append(self.build_model(input_shape = None, X = tf.expand_dims(tf.transpose(tf.nn.embedding_lookup(
tf.transpose(input_layer,(1,0,2)),config['cluster'][c]),(1,0,2)),axis=-1), c = c))
# append the results and perform 1 dense layer with last_channel dimension and the output layer
x = tf.keras.layers.Concatenate(axis=1)(output)
dense=tf.keras.layers.Dense(32, activation='relu')(x)
output_layer=tf.keras.layers.Dense(1,activation='sigmoid')(dense)
model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer)
return model
@staticmethod
def _eeg_preprocessing(input_tensor, F1=8, D=2, kernLength=250):
def _eeg_preprocessing(input_tensor, F1=8, D=2, kernLength=250, c = None):
"""
Static method since this function does not receive any reference argument from this class.
"""
# EEGNet feature extraction
Chans = config['deepeye']['channels']
if config['split']:
Chans=len(config['cluster'][c])
else:
Chans = config['deepeye']['channels']
Samples = config['deepeye']['samples']
# Filter slides horizontally
......@@ -111,10 +115,12 @@ class Classifier_DEEPEYE:
depth_multiplier=D,
depthwise_constraint=max_norm(1.))(horizontal_tensor)
vertical_tensor = BatchNormalization()(vertical_tensor)
eeg_tensor = Activation('elu')(vertical_tensor)
# Reshape the tensor (129, 500, 1) to (129, 500), and feed into the inception module
output_tensor = eeg_tensor[:, 0, :, :]
#output_tensor = tf.transpose(output_tensor, perm=[0, 2, 1])
return output_tensor
......@@ -158,14 +164,16 @@ class Classifier_DEEPEYE:
x = keras.layers.Activation('relu')(x)
return x
def build_model(self, input_shape, nb_filters=32, use_residual=True, use_bottleneck=True, depth=6, kernel_size=40, F1=8,
D=2, kernLength=125, X = None):
def build_model(self, input_shape, X = None, c = None, nb_filters=32, use_residual=True, use_bottleneck=True, depth=6, kernel_size=40, F1=8,
D=2, kernLength=125):
if config['split']:
input_layer = X
else:
input_layer = keras.layers.Input((input_shape[0], input_shape[1], 1))
eeg_tensor = self._eeg_preprocessing(input_layer, F1, D, kernLength)
eeg_tensor = self._eeg_preprocessing(input_layer, F1, D, kernLength, c = c)
x = eeg_tensor
input_res = eeg_tensor
for d in range(depth):
......@@ -177,6 +185,9 @@ class Classifier_DEEPEYE:
input_res = x
gap_layer = tf.keras.layers.GlobalAveragePooling1D()(x)
if config['split']:
return gap_layer
output_layer = tf.keras.layers.Dense(1, activation='sigmoid')(gap_layer)
model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer)
......
......@@ -17,10 +17,10 @@ import logging
def run(trainX, trainY):
if config['split']:
classifier = split_model()
config['model']=config['model']+'_cluster'
else:
classifier = Classifier_EEGNet(output_directory=config['root_dir'])
classifier = Classifier_EEGNet(output_directory=config['root_dir'])
hist = classifier.fit(trainX, trainY)
plot_loss(hist, config['model_dir'], config['model'], True)
......@@ -29,25 +29,7 @@ def run(trainX, trainY):
save_logs(hist, config['model_dir'], config['model'], pytorch=False)
save_model_param(classifier.model, config['model_dir'], config['model'], pytorch=False)
def split_model():
'''
given model apply on each cluster and followed by a dense layer on the concatenate outputs
'''
input_layer = tf.keras.layers.Input(shape=(config['eegnet']['channels'], config['eegnet']['samples'], 1))
output=[]
# run inception over the cluster
for c in config['cluster'].keys():
output.append(Classifier_EEGNet(output_directory = config['root_dir'], chans = len(config['cluster'][c]),
samples = config['eegnet']['samples'],X = input_layer[...,config['cluster'][c]]))
# append the results and perform 1 dense layer with last_channel dimension and the output layer
x = tf.keras.layers.Concatenate()(output)
dense = tf.keras.layers.Dense(last_channel=32, activation='relu')(x)
output_layer = tf.keras.layers.Dense(1,activation='sigmoid')(dense)
model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer)
model.compile(loss='binary_crossentropy', optimizer=keras.optimizers.Adam(), metrics=['accuracy'])
return model
class Classifier_EEGNet:
def __init__(self, output_directory, nb_classes=1, chans = config['eegnet']['channels'], samples = config['eegnet']['samples'], dropoutRate = 0.5, kernLength = 250, F1 = 32,
......@@ -67,12 +49,36 @@ class Classifier_EEGNet:
self.verbose = verbose
if build:
self.model = self.build_model(X)
if self.verbose:
logging.info(self.model.summary())
# self.model.save_weights(self.output_directory + 'model_init.hdf5')
if config['split']:
self.model = self.split_model()
else:
self.model = self.build_model()
if verbose:
self.model.summary()
# self.model.save_weights(self.output_directory + 'model_init.hdf5')
def split_model(self):
input_layer = keras.layers.Input((config['eegnet']['channels'] , config['eegnet']['samples'] ))
output=[]
# run inception over the cluster
for c in config['cluster'].keys():
output.append(self.build_model(X = tf.expand_dims(tf.transpose(tf.nn.embedding_lookup(
tf.transpose(input_layer,(1,0,2)),config['cluster'][c]),(1,0,2)),axis=-1), c = c))
# append the results and perform 1 dense layer with last_channel dimension and the output layer
x = tf.keras.layers.Concatenate(axis=1)(output)
dense=tf.keras.layers.Dense(32, activation='relu')(x)
output_layer=tf.keras.layers.Dense(1,activation='sigmoid')(dense)
model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer)
return model
def build_model(self, X = None):
def build_model(self, X = None, c = None):
if self.dropoutType == 'SpatialDropout2D':
dropoutType = SpatialDropout2D
......@@ -83,6 +89,7 @@ class Classifier_EEGNet:
'or Dropout, passed as a string.')
if config['split']:
input1 = X
self.chans=len(config['cluster'][c])
else:
input1 = Input(shape=(self.chans, self.samples, 1))
......@@ -105,7 +112,7 @@ class Classifier_EEGNet:
block2 = AveragePooling2D((1, 6))(block2)
block2 = dropoutType(self.dropoutRate)(block2)
flatten = Flatten(name='flatten')(block2)
flatten = Flatten(name='flatten_'+c)(block2)
if config['split']:
return flatten
else:
......
......@@ -9,38 +9,21 @@ from keras.callbacks import CSVLogger
def run(trainX, trainY):
logging.info("Starting InceptionTime.")
if config['split']:
classifier = split_model()
config['model']=config['model']+'_cluster'
else:
classifier = Classifier_INCEPTION(output_directory=config['root_dir'], input_shape=config['inception']['input_shape'])
classifier = Classifier_INCEPTION(output_directory=config['root_dir'], input_shape=config['inception']['input_shape'])
hist = classifier.fit(trainX, trainY)
plot_loss(hist, config['model_dir'], config['model'], True)
plot_acc(hist, config['model_dir'], config['model'], True)
save_logs(hist, config['model_dir'], config['model'], pytorch=False)
save_model_param(classifier.model, config['model_dir'], config['model'], pytorch=False)
def split_model():
input_layer = tf.keras.layers.Input(config['inception']['input_shape'])
output=[]
# run inception over the cluster
for c in config['cluster'].keys():
input_shape = tuple(list(config['inception']['input_shape'][0]).append(len(config['cluster'][c])))
output.append(Classifier_INCEPTION(output_directory=config['root_dir'], input_shape=input_shape, X=input_layer[...,config['cluster'][c]]))
# append the results and perform 1 dense layer with last_channel dimension and the output layer
x = tf.keras.layers.Concatenate(axis=2)(output)
dense=tf.keras.layers.Dense(last_channel=32, activation='relu')(x)
output_layer=tf.keras.layers.Dense(1,activation='sigmoid')(dense)
model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer)
model.compile(loss='binary_crossentropy', optimizer=keras.optimizers.Adam(), metrics=['accuracy'])
return model
class Classifier_INCEPTION:
def __init__(self, output_directory, input_shape, verbose=False, build=True, batch_size=64, nb_filters=32,
use_residual=True, use_bottleneck=True, depth=6, kernel_size=40, nb_epochs=1500, X=None):
use_residual=True, use_bottleneck=True, depth=6, kernel_size=40, nb_epochs=1500, X = None):
self.output_directory = output_directory
self.nb_filters = nb_filters
......@@ -55,11 +38,37 @@ class Classifier_INCEPTION:
self.verbose = verbose
if build:
self.model = self._build_model(input_shape, X=None)
if config['split']:
self.model = self.split_model(input_shape)
else:
self.model = self._build_model(input_shape)
if self.verbose:
self.model.summary()
# self.model.save_weights(self.output_directory + 'model_init.hdf5')
def split_model(self,input_shape):
input_layer = tf.keras.layers.Input(input_shape)
output=[]
# run inception over the cluster
for c in config['cluster'].keys():
a=[input_shape[0]]
a.append(len(config['cluster'][c]))
input_shape=tuple(a)
output.append(self._build_model(input_shape,
X= tf.transpose(tf.nn.embedding_lookup(tf.transpose(input_layer),config['cluster'][c]))))
# append the results and perform 1 dense layer with last_channel dimension and the output layer
x = tf.keras.layers.Concatenate()(output)
dense=tf.keras.layers.Dense(32, activation='relu')(x)
output_layer=tf.keras.layers.Dense(1,activation='sigmoid')(dense)
model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer)
model.compile(loss='binary_crossentropy', optimizer=keras.optimizers.Adam(), metrics=['accuracy'])
return model
def _inception_module(self, input_tensor, nb_filters=32, use_bottleneck=True, kernel_size=40, bottleneck_size=32,
stride=1, activation='linear'):
......@@ -99,13 +108,15 @@ class Classifier_INCEPTION:
x = keras.layers.Activation('relu')(x)
return x
def _build_model(self, input_shape, nb_filters=32, use_residual=True, use_bottleneck=True, depth=6, kernel_size=40,X=None):
def _build_model(self, input_shape, X, nb_filters=32, use_residual=True, use_bottleneck=True, depth=6, kernel_size=40):
if config['split']:
input_layer = X
else:
input_layer = tf.keras.layers.Input(input_shape)
x = input_layer
input_res = input_layer
for d in range(depth):
......
import numpy as np
def clustering():
c1=[12,5,4,11,19,18,16,16,15,9,14,17,21,22]
c2=[8,1,2,122,121,116,123,3,124,117,118]
c3=[114,115,109,108,102,98,103,110,111,104,93]
c4=[100,101,97,96,95,89,90,84,91,85,92,86]
c5=[82,74,70,75,83,76,71,67,72,77,78,62,61]
c6=[69,65,64,58,57,50,51,59,66,60,52,53]
c7=[44,39,40,45,46,47,41,35,29,36,42]
c8=[38,33,32,25,26,23,27,34,28,24,20]
c9=[13,6,112,105,106,7,30,37,31,129,80,87,79,55,54]
c1=list(np.array([12,5,4,11,19,18,16,10,15,9,14,17,21,22,126,127])-1)
c2=list(np.array([8,1,2,122,121,116,123,3,124,117,118])-1)
c3=list(np.array([114,115,109,108,102,98,103,110,111,104,93])-1)
c4=list(np.array([100,101,97,96,95,89,90,84,91,85,92,86])-1)
c5=list(np.array([82,74,70,75,83,76,71,67,72,77,78,62,61])-1)
c6=list(np.array([69,65,64,58,57,50,51,59,66,60,52,53])-1)
c7=list(np.array([44,39,40,45,46,47,41,35,29,36,42])-1)
c8=list(np.array([38,33,32,25,26,23,27,34,28,24,20])-1)
c9=list(np.array([13,6,112,105,106,7,30,37,31,129,80,87,79,55,54])-1)
cluster={}
cluster['c1']=c1
cluster['c2']=c2
......
......@@ -53,6 +53,8 @@ config['inception'] = {}
config['deepeye'] = {}
# EEGNet
config['eegnet'] = {}
# LSTM-DeepEye
config['deepeye-lstm'] = {}
config['inception']['input_shape'] = (125, 129) if config['downsampled'] else (500, 129)