To receive notifications about scheduled maintenance, please subscribe to the mailing-list gitlab-operations@sympa.ethz.ch. You can subscribe to the mailing-list at https://sympa.ethz.ch

Commit dc30070a authored by Lukas Wolf's avatar Lukas Wolf
Browse files

commit for merge

parent d1f5b879
......@@ -30,4 +30,7 @@ conv_analysis.ipynb
_MAX_FINAL_RUNS/
Compute_Ensemble_Metrics.py
_FINAL_EXP/
model_load.ipynb
\ No newline at end of file
model_load.ipynb
max_runtimes.csv
min_runtimes.csv
stats.ipynb
\ No newline at end of file
......@@ -40,7 +40,7 @@ config['root_dir'] = '.'
# Choose task and dataset
##################################################################
config['preprocessing'] = 'min' # options: min and max
config['preprocessing'] = 'max' # options: min and max
#config['task'] = 'prosaccade-clf'
config['task'] = 'gaze-reg'
......@@ -137,7 +137,8 @@ def create_folder(config):
if config['downsampled']:
model_folder_name += '_downsampled'
if config['ensemble']>1:
model_folder_name += '_ensemble'
num_models = str(config['ensemble'])
model_folder_name += f'_ensemble_{num_models}'
config['model_dir'] = os.path.abspath(os.path.join(config['log_dir'], model_folder_name))
if not os.path.exists(config['model_dir']):
......
......@@ -34,7 +34,7 @@ def main():
num_benchmarks = 1
for i in range(num_benchmarks):
benchmark_task('prosaccade-clf')
#benchmark_task('prosaccade-clf')
benchmark_task('gaze-reg')
#benchmark_task('angle-reg')
#benchmark_task('amplitude-reg')
......@@ -48,8 +48,6 @@ def main():
def benchmark_task(task):
# 1 group: 1 learning rate with 5 models and 4 tasks = 20
##################################################################
# Task 1 Prosaccade
##################################################################
......
from os import path
from torch_models.torch_utils.plot import plot_metrics
import torch
import sys
......@@ -16,19 +15,18 @@ from memory_profiler import profile
class Prediction_history:
"""
Collect predictions of the given validation set after each epoch
predhis is a list of lists (one for each epoch) of tensors (one for each batch)
Collect predictions on the dataset in the given dataloader
Called after each epoch by BaseNet
"""
def __init__(self, dataloader, model) -> None:
"""
predhis: a list of lists (one for each epoch) of tensors (one for each batch of length batch_size)
"""
self.dataloader = dataloader
self.predhis = []
self.model = model
#@timing_decorator
#@profile
def on_epoch_end(self):
#print("Enter test on epoch end:")
#get_gpu_memory()
with torch.no_grad():
y_pred = []
for batch, (x, y) in enumerate(self.dataloader):
......@@ -50,7 +48,7 @@ class BaseNet(nn.Module):
"""
def __init__(self, input_shape, epochs=50, verbose=True, model_number=0, batch_size=64):
"""
Initialize common variables of models based on BaseNet
Initialize common variables of models based on BaseNet, e.g. ConvNet or EEGNET
Create the common output layer dependent on the task to run
"""
super().__init__()
......@@ -81,11 +79,13 @@ class BaseNet(nn.Module):
self.output_layer = nn.Sequential(
nn.Linear(in_features=self.get_nb_features_output_layer(), out_features=1)
)
else: # elif config['task'] == 'amplitude-reg':
elif config['task'] == 'amplitude-reg':
self.loss_fn = nn.MSELoss()
self.output_layer = nn.Sequential(
nn.Linear(in_features=self.get_nb_features_output_layer(), out_features=1)
)
else:
raise ValueError("Choose a valid task")
if verbose and self.model_number == 0:
logging.info(f"Using loss fct: {self.loss_fn}")
......@@ -94,6 +94,7 @@ class BaseNet(nn.Module):
def forward(self, x):
"""
Implements a forward pass of the network
This method has to be implemented by models based on BaseNet
"""
pass
......@@ -101,6 +102,7 @@ class BaseNet(nn.Module):
def get_nb_features_output_layer(self):
"""
Return the number of features that the output layer should take as input
This method has to be implemented by models based on BaseNet to compute the number of hidden neurons that the output layer takes as input.
"""
pass
......@@ -110,9 +112,15 @@ class BaseNet(nn.Module):
#@profile
#@timing_decorator
def fit(self, train_dataloader, validation_dataloader, test_dataloader=None, subjectID=None):
def fit(self, train_dataloader, validation_dataloader, test_dataloader, subjectID=None):
"""
Fit the model on the dataset defined by data x and labels y
Inputs:
training, validation and test dataloader containing the respective datasets
Output:
prediction_ensemble containing the predictions on the test data, prediction on test data when model stops is used to compute the ensemble metrics
"""
# Move the model to GPU
if torch.cuda.is_available():
......@@ -120,36 +128,28 @@ class BaseNet(nn.Module):
logging.info(f"Model moved to cuda")
# Create the optimizer
optimizer = torch.optim.Adam(list(self.parameters()), lr=config['learning_rate'])
# Create a history to track ensemble performance
# Create a history to track ensemble performance, similar to tensorflow.keras callbacks
prediction_ensemble = Prediction_history(dataloader=test_dataloader, model=self)
# Train the model
# Create datastructures to collect metrics and implement early stopping
epochs = config['epochs']
metrics = {'train_loss':[], 'val_loss':[], 'train_acc':[], 'val_acc':[]} if config['task'] == 'prosaccade-clf' else {'train_loss':[], 'val_loss':[]}
best_val_loss = sys.maxsize # For early stopping
patience = 0
# Train the model
for t in range(epochs):
logging.info("-------------------------------")
logging.info(f"Epoch {t+1}")
# print(f"Start EPOCH: Free GPU memory: {get_gpu_memory()}")
# print(f"memory {psutil.virtual_memory()}")
# Run through training and test set
# Run through training and validation set
if not self.early_stopped:
train_loss_epoch, train_acc_epoch = train_loop(train_dataloader, self.float(), self.loss_fn, optimizer)
# print(f"Free GPU mem after train loop: {get_gpu_memory()}")
# print(f"memory {psutil.virtual_memory()}")
val_loss_epoch, val_acc_epoch = validation_loop(validation_dataloader, self.float(), self.loss_fn)
# Add them for later printout
metrics['train_loss'].append(train_loss_epoch)
metrics['val_loss'].append(val_loss_epoch)
if config['task'] == 'prosaccade-clf':
metrics['train_acc'].append(train_acc_epoch)
metrics['val_acc'].append(val_acc_epoch)
# print("Free GPU mem after test loop:")
# print(f"memory {psutil.virtual_memory()}")
# Add the predictions on the validation set, even if model was early stopped
metrics['val_acc'].append(val_acc_epoch)
# Add the predictions on the validation set, even if model was early stopped, later we will compute ensemble metrics on them
prediction_ensemble.on_epoch_end()
# print("Free GPU mem after prediction hist:")
# print(f"memory {psutil.virtual_memory()}")
# Impementation of early stopping
if config['early_stopping'] and not self.early_stopped:
if patience > config['patience']:
......@@ -163,7 +163,7 @@ class BaseNet(nn.Module):
logging.info(f"Improved validation loss to: {best_val_loss}")
patience = 0
# Plot and save metrics
# Plot and save metrics
plot_metrics(metrics['train_loss'], metrics['val_loss'], output_dir=config['model_dir'], metric='loss', model_number=self.model_number)
if config['task'] == 'prosaccade-clf':
plot_metrics(metrics['train_acc'], metrics['val_acc'], output_dir=config['model_dir'], metric='accuracy', model_number=self.model_number)
......
......@@ -8,6 +8,9 @@ class CNN(ConvNet):
"""
def __init__(self, input_shape, kernel_size=64, epochs = 50, nb_filters=16, verbose=True, batch_size=64,
use_residual=True, depth=12, regularization=0, model_number=0):
"""
nb_features: specifies number of channels before the output layer
"""
self.regularization = regularization
self.nb_features = nb_filters # For CNN simply the number of filters inside the network
super().__init__(input_shape, kernel_size=kernel_size, epochs=epochs,
......@@ -16,8 +19,8 @@ class CNN(ConvNet):
def _module(self, depth):
"""
The module of CNN is made of a simple convolution with batch normalization and ReLu activation. Finally, MaxPooling is also used.
We use two custom padding modules such that keras-like padding='same' is achieved, i.e. tensor shape stays constant.
The module of CNN is made of a simple convolution with batch normalization and ReLu activation. Finally, MaxPooling is used.
We use two custom padding modules such that keras-like padding='same' is achieved, i.e. tensor shape stays constant when passed through the module.
"""
return nn.Sequential(
Pad_Conv(kernel_size=self.kernel_size, value=0),
......@@ -27,12 +30,4 @@ class CNN(ConvNet):
nn.ReLU(),
Pad_Pool(left=0, right=1, value=0),
nn.MaxPool1d(kernel_size=2, stride=1)
)
"""
Tensorflow code:
x = tf.keras.layers.Conv1D(filters=self.nb_filters, kernel_size=self.kernel_size,
padding='same', use_bias=False)(input_tensor)
x = tf.keras.layers.BatchNormalization()(x)
x = tf.keras.layers.Activation(activation='relu')(x)
x = tf.keras.layers.MaxPool1D(pool_size=2, strides=1, padding='same')(x)
"""
\ No newline at end of file
)
\ No newline at end of file
......@@ -13,7 +13,7 @@ class ConvNet(ABC, BaseNet):
"""
This class defines all the common functionality for convolutional nets
Inherit from this class and only implement _module() and _get_nb_features_output_layer() methods
Modules are then stacked in the forward() method
Modules are then stacked in the forward() pass of the model
"""
def __init__(self, input_shape, kernel_size=32, nb_filters=32, verbose=True, batch_size=64,
use_residual=False, depth=6, epochs=2, preprocessing = False, model_number=0):
......@@ -25,7 +25,6 @@ class ConvNet(ABC, BaseNet):
self.use_residual = use_residual
self.depth = depth
self.callbacks = None
self.kernel_size = kernel_size
self.nb_filters = nb_filters
self.preprocessing = preprocessing
......@@ -52,43 +51,32 @@ class ConvNet(ABC, BaseNet):
def forward(self, x):
"""
Implements the forward pass of the network
Note that every layer used here must be defined in __init__ to be persistent and able to be trained
Modules defined in a class implementing ConvNet are stacked and shortcut connections are used if specified.
"""
if self.preprocessing:
x = self._preprocessing(x)
input_res = x # residual shortcut connection
# Stack the modules
input_res = x # set for the residual shortcut connection
# Stack the modules and residual connection
shortcut_cnt = 0
for d in range(self.depth):
#print(f"x after block {d} {x.size()}")
x = self.conv_blocks[d](x)
if self.use_residual and d % 3 == 2:
res = self.shortcuts[shortcut_cnt](input_res)
shortcut_cnt += 1
#print(f"x before add {x.size()}")
#print(f"res before add {res.size()}")
x = torch.add(x, res)
x = nn.functional.relu(x)
input_res = x
x = self.gap_layer_pad(x)
x = self.gap_layer(x)
#print(f"x after gap {x.size()}")
x = x.view(self.batch_size, -1)
#print(f"x before output {x.size()}")
output = self.output_layer(x) # Defined in BaseNet
return output
def _shortcut(self, depth):
"""
Implements a shortcut with a convolution and batch norm
This is the same for all our convolutional models
Padding before convolution for constant tensor shape, similar to Keras padding=same
This is the same for all models implementing ConvNet, therefore defined here
Padding before convolution for constant tensor shape, similar to tensorflow.keras padding=same
"""
return nn.Sequential(
Pad_Conv(kernel_size=self.kernel_size, value=0),
......
......@@ -10,14 +10,21 @@ from torch_models.torch_utils.utils import get_gpu_memory
class EEGNet(BaseNet):
"""
The EEGNet architecture used as baseline. This is the architecture explained in the paper
'EEGNet: A Compact Convolutional Network for EEG-based Brain-Computer Interfaces' with authors
Vernon J. Lawhern, Amelia J. Solon, Nicholas R. Waytowich, Stephen M. Gordon, Chou P. Hung, Brent J. Lance
In our implementation it is built on BaseNet and can therefore use the same interface for training as models based on ConvNet.
We only define the layers we need, the forward pass, and a method that returns the number of hidden units before the output layer, which is accessed by BaseNet to create the same output layer as for ConvNet models.
"""
def __init__(self, input_shape, epochs=50, model_number=0,
F1=16, F2=256, verbose=True, D=4, kernel_size=256,
dropout_rate=0.5, batch_size=64):
"""
nb_features: specifies number of channels before the output layer
Padding=same
"""
self.kernel_size = kernel_size
self.timesamples = input_shape[0]
self.channels = input_shape[1]
......@@ -80,6 +87,9 @@ class EEGNet(BaseNet):
print(self)
def forward(self, x):
"""
Implements a forward pass of the eegnet.
"""
# Block 1
x = self.padconv1(x)
x = self.conv1(x)
......@@ -108,28 +118,6 @@ class EEGNet(BaseNet):
return x
"""
Tensorflow code:
block1 = Conv2D(self.F1, (1, self.kernLength), padding='same',
input_shape=(self.chans, self.samples, 1),
use_bias=False)(input1)
block1 = BatchNormalization()(block1)
block1 = DepthwiseConv2D((self.chans, 1), use_bias=False,
depth_multiplier=self.D,
depthwise_constraint=max_norm(1.))(block1)
block1 = BatchNormalization()(block1)
block1 = Activation('elu')(block1)
block1 = AveragePooling2D((1, 16))(block1)
block1 = dropoutType(self.dropoutRate)(block1)
block2 = SeparableConv2D(self.F2, (1, 64),
use_bias=False, padding='same')(block1)
block2 = BatchNormalization()(block2)
block2 = Activation('elu')(block2)
block2 = AveragePooling2D((1, 6))(block2)
block2 = dropoutType(self.dropoutRate)(block2)
"""
def get_nb_features_output_layer(self):
"""
Return number of features passed into the output layer of the network
......
......@@ -20,34 +20,34 @@ from torch_models.PyramidalCNN.PyramidalCNN import PyramidalCNN
class Ensemble_torch:
"""
The Ensemble is a model itself, which contains a number of models whose prediction is averaged.
Default: nb_models of the model_type model
Optional: Initialize with a model list, create a more versatile Ensemble model
The Ensemble is a model itself, which contains a number of models whose prediction is averaged (majority decision in case of a classifier).
"""
def __init__(self, nb_models=1, model_type='cnn', model_list=[]):
def __init__(self, nb_models=1, model_type='cnn'):
"""
nb_models: Number of models to run in the ensemble
model_type: Default model to run
model_type: type of model that is used in the ensemble, e.g. 5 cnn models
model_list: optional, give a list of models that should be contained in the Ensemble
"""
self.nb_models = nb_models
self.model_type = model_type
self.model_list = model_list
self.models = []
# Set the loss function depending on the task we run
if config['task'] == 'prosaccade-clf':
self.loss_fn = nn.BCELoss()
elif config['task'] == 'angle-reg':
self.loss_fn = angle_loss
elif config['task'] == 'gaze-reg':
self.loss_fn = nn.MSELoss()
else: # amplitude-task
self.loss_fn = nn.MSELoss() # can also try MAE
elif config['task'] == 'amplitude-task':
self.loss_fn = nn.MSELoss()
else:
raise ValueError("Choose a valid task")
def run(self, x, y):
"""
Fit all the models in the ensemble and save them to the run directory
Also save and plot metrics metrics computed accross the ensemble
Inputs:
x: dataset as np.array
y: labels as np.array
......@@ -55,16 +55,18 @@ class Ensemble_torch:
# Create a split
x = np.transpose(x, (0, 2, 1)) # (batch_size, samples, channels) to (bs, ch, samples) as torch conv layers want it
X_train, y_train, X_val, y_val, X_test, y_test = generate_split(x, y)
# Remove the subject counter from the labels and extract only angle for direction task
# Remove the subject counter from the labels and extract the proper label for angle and amplitude task. structure of direction data: [subjectid, amplitude, angle]
if config['task'] == 'amplitude-reg':
y_train = y_train[:, 1:2]
y_val = y_val[:, 1:2] # only extract the amplitude, not the subjectid or angle
y_val = y_val[:, 1:2]
y_test = y_test[:, 1:2]
else:
offset = 2 if config['task'] == 'angle-reg' else 1
offset = 2 if config['task'] == 'angle-reg' else 1 # for the other datasets it is [subjectid, label]
y_train = y_train[:, offset:]
y_val = y_val[:, offset:]
y_test = y_test[:, offset:]
# Log shapes
logging.info(f"Training data shapes X, y: {X_train.shape, y_train.shape}")
logging.info(f"Test data shapes X, y: {X_test.shape, y_test.shape}")
......@@ -76,37 +78,22 @@ class Ensemble_torch:
# Metrics to save across the ensemble
loss=[]
accuracy=[]
prediction_list = [] # saves the predicted batches in the test dataloader across models
# Fit the models
for i in range(config['ensemble']):
logging.info("------------------------------------------------------------------------------------")
logging.info('Start training model number {}/{} ...'.format(i+1, self.nb_models))
model = create_model(model_type=self.model_type, model_number=i)
pred_ensemble = model.fit(train_dataloader, validation_dataloader, test_dataloader)
# Collect the prediction on the test set
#prediction_list = sum_predictions(test_dataloader, model, model_number=i, prediction_list=prediction_list)
if i == 0:
pred = pred_ensemble.predhis
else:
for j, pred_epoch in enumerate(pred_ensemble.predhis):
for batch, predictions in enumerate(pred_epoch):
pred[j][batch] = pred[j][batch] + predictions
logging.info('Finished training model number {}/{} ...'.format(i+1, self.nb_models))
logging.info("------------------------------------------------------------------------------------")
"""
#Divide prediction list by number of models and compute the final loss and accuracy of the ensemble
ensemble_loss = compute_loss(loss_fn=self.loss_fn, dataloader=test_dataloader, pred_list=prediction_list, nb_models=self.nb_models)
if config['task'] == 'prosaccade-clf':
ensemble_acc = compute_accuracy(dataloader=test_dataloader, pred_list=prediction_list, nb_models=self.nb_models)
logging.info(f"ENSEMBLE ACCURACY ON TEST SET: {ensemble_acc}")
logging.info(f"ENSEMBLE LOSS ON TEST SET: {ensemble_loss}")
"""
# Create the ensemble metrics
logging.info("Finished training the Ensemble, computing ensemble metrics...")
for j, pred_epoch in enumerate(pred):
loss.append(compute_loss(loss_fn=self.loss_fn, dataloader=test_dataloader, pred_list=pred_epoch, nb_models=config['ensemble']))
if config['task'] == 'prosaccade-clf':
......@@ -127,7 +114,7 @@ class Ensemble_torch:
def create_model(model_type, model_number):
"""
Returns the specified torch model as nn.Module built on BaseNet
Returns the specified torch model as nn.Module built on BaseNet, BaseNet is the common denominator of ConvNet models and the EEGNET model
"""
if model_type == 'cnn':
model = CNN(input_shape=config['input_shape'], kernel_size=64, epochs = config['epochs'], nb_filters=16, batch_size=config['batch_size'],
......
......@@ -10,13 +10,16 @@ from torch_models.Modules import Pad_Conv, Pad_Pool
class Inception(ConvNet):
"""
The InceptionTime architecture used as baseline. This is the architecture explained in the paper
'InceptionTime: Finding AlexNet for Time Series Classification' with authors
Hassan Ismail Fawaz, Benjamin Lucas, Germain Forestier, Charlotte Pelletier,
Daniel F. Schmidt, Jonathan Weber, Geoffrey I. Webb, Lhassane Idoumghar, Pierre-Alain Muller, François Petitjean
"""
def __init__(self, input_shape, kernel_size=64, epochs = 50, nb_filters=16, verbose=True,
batch_size=64, use_residual=True, depth=12, bottleneck_size=16, model_number=0):
"""
nb_features: specifies number of channels before the output layer
"""
self.bottleneck_size = bottleneck_size
self.nb_features = 4 * nb_filters # these are the 4 concatenated parallel convolutions, width of the inner tensort passed through network
logging.info('--------------- bottleneck_size : ' + str(self.bottleneck_size))
......@@ -31,35 +34,16 @@ class Inception(ConvNet):
Then it uses 3 filters with different kernel sizes (Default values are 40, 20, and 10)
In parallel it uses a simple convolution with kernel size 1 with max pooling for stability during training.
The outputs of each convolution are concatenated, followed by batch normalization and a ReLu activation.
Padding=same
"""
return Inception_module(self.kernel_size, self.nb_features, self.nb_channels,
self.nb_filters, self.bottleneck_size, depth)
"""
Tensorflow code:
if int(input_tensor.shape[-1]) > 1:
input_inception = tf.keras.layers.Conv1D(filters=self.bottleneck_size, kernel_size=1, padding='same', 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(
tf.keras.layers.Conv1D(filters=self.nb_filters, kernel_size=kernel_size_s[i], padding='same', use_bias=False)(input_inception))
max_pool_1 = tf.keras.layers.MaxPool1D(pool_size=3, strides=1, padding='same')(input_tensor)
conv_6 = tf.keras.layers.Conv1D(filters=self.nb_filters, kernel_size=1, padding='same', use_bias=False)(max_pool_1)
conv_list.append(conv_6)
x = tf.keras.layers.Concatenate(axis=2)(conv_list)
x = tf.keras.layers.BatchNormalization()(x)
x = tf.keras.layers.Activation(activation='relu')(x)
return x
"""
class Inception_module(nn.Module):
"""
This class implements one inception module descirbed above as torch.nn.Module, which can then be stacked into a model by ConvNet
"""
def __init__(self, kernel_size, nb_features, nb_channels, nb_filters, bottleneck_size, depth):
super().__init__()
kernel_size_s = [kernel_size // (2 ** i) for i in range(3)]
......
......@@ -87,15 +87,15 @@ class TCSConv1d(nn.Module):
def forward(self, x):
x = self.pad_depthwise(x)
#print(f"tensor after pad depthwise: {x.size()}")
x = self.depthwise(x)
#print(f"tensor after conv depthwise: {x.size()}")
x = self.pointwise(x)
#print(f"tensor after conv pointwise: {x.size()}")
return x
class SeparableConv2d(nn.Module):
"""
Implements a 2d separable convolution
Not used in our default models
"""
def __init__(self, in_channels, out_channels, kernel_size, padding=0, bias=False):
super(SeparableConv2d, self).__init__()
self.depthwise = nn.Conv2d(in_channels,
......
......@@ -7,10 +7,12 @@ class PyramidalCNN(ConvNet):
"""
The Classifier_PyramidalCNN is one of the simplest classifiers. It implements the class ConvNet, which is made of modules with a
specific depth, where for each depth the number of filters is increased.
nb_features: specifies number of channels before the output layer
"""
def __init__(self, input_shape, kernel_size=16, epochs = 50, nb_filters=16, verbose=True, batch_size=64,
use_residual=False, depth=6, model_number=0, regularization=0):
"""
nb_features: specifies number of channels before the output layer
"""
self.regularization = regularization
self.nb_features = depth * nb_filters # For pyramidal we increase the nbfilters each depth layer
super().__init__(input_shape, kernel_size=kernel_size, epochs=epochs, nb_filters=nb_filters,
......@@ -20,6 +22,8 @@ class PyramidalCNN(ConvNet):
def _module(self, depth):
"""
The module of CNN is made of a simple convolution with batch normalization and ReLu activation. Finally, MaxPooling is also used.
The number of filters / output channels is increases with the depth of the model.
Padding=same
"""
return nn.Sequential(
Pad_Conv(kernel_size=self.kernel_size),
......@@ -31,11 +35,4 @@ class PyramidalCNN(ConvNet):
nn.ReLU(),
Pad_Pool(left=0, right=1),
nn.MaxPool1d(kernel_size=2, stride=1)
)
"""
Tensorflow code:
x = tf.keras.layers.Conv1D(filters=self.nb_filters*(current_depth + 1), kernel_size=self.kernel_size, padding='same', use_bias=False)(input_tensor)
x = tf.keras.layers.BatchNormalization()(x)
x = tf.keras.layers.Activation(activation='relu')(x)
x = tf.keras.layers.MaxPool1D(pool_size=2, strides=2)(x)
"""
)
\ No newline at end of file
......@@ -13,6 +13,9 @@ class XCEPTION(ConvNet):
"""
def __init__(self, input_shape, kernel_size=40, nb_filters=64, verbose=True, epochs=1, batch_size=64,
use_residual=True, depth=6, model_number=0, regularization=0):
"""
nb_features: specifies number of channels before the output layer
"""
self.regularization = regularization
self.nb_features = nb_filters # Exception passes a tensor of shape (timesamples, nb_filters) through the network
super(XCEPTION, self).__init__(input_shape, kernel_size=kernel_size, nb_filters=nb_filters,
......@@ -22,16 +25,10 @@ class XCEPTION(ConvNet):
def _module(self, depth):
"""
The module of Xception. Consists of a separable convolution followed by batch normalization and a ReLu activation function.
Padding=same
"""
return nn.Sequential(
TCSConv1d(mother=self, depth=depth, bias=False),
nn.BatchNorm1d(num_features=self.nb_features),
nn.ReLU()
)
"""
Tensorflow code:
x = tf.keras.layers.SeparableConv1D(filters=self.nb_filters, kernel_size=self.kernel_size, padding='same',
use_bias=False, depth_multiplier=1)(input_tensor)
x = tf.keras.layers.BatchNormalization()(x)
x = tf.keras.layers.Activation(activation='relu')(x)
"""
\ No newline at end of file
)
\ No newline at end of file
......@@ -9,5 +9,11 @@ def angle_loss(a, b):
Angles -pi and pi should lead to 0 loss, since this is actually the same angle on the unit circle
Angles pi/2 and -pi/2 should lead to a large loss, since this is a difference by pi on the unit circle
Therefore we compute the absolute error of the "shorter" direction on the unit circle
Inputs: