diff --git a/mlair/keras_legacy/__init__.py b/mlair/keras_legacy/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/mlair/keras_legacy/conv_utils.py b/mlair/keras_legacy/conv_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5ee50e3f260fdf41f90c58654f82cfb8b35dfe8
--- /dev/null
+++ b/mlair/keras_legacy/conv_utils.py
@@ -0,0 +1,180 @@
+"""Utilities used in convolutional layers.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import numpy as np
+from tensorflow.keras import backend as K
+
+
+def normalize_tuple(value, n, name):
+    """Transforms a single int or iterable of ints into an int tuple.
+
+    # Arguments
+        value: The value to validate and convert. Could be an int, or any iterable
+          of ints.
+        n: The size of the tuple to be returned.
+        name: The name of the argument being validated, e.g. `strides` or
+          `kernel_size`. This is only used to format error messages.
+
+    # Returns
+        A tuple of n integers.
+
+    # Raises
+        ValueError: If something else than an int/long or iterable thereof was
+        passed.
+    """
+    if isinstance(value, int):
+        return (value,) * n
+    else:
+        try:
+            value_tuple = tuple(value)
+        except TypeError:
+            raise ValueError('The `' + name + '` argument must be a tuple of ' +
+                             str(n) + ' integers. Received: ' + str(value))
+        if len(value_tuple) != n:
+            raise ValueError('The `' + name + '` argument must be a tuple of ' +
+                             str(n) + ' integers. Received: ' + str(value))
+        for single_value in value_tuple:
+            try:
+                int(single_value)
+            except ValueError:
+                raise ValueError('The `' + name + '` argument must be a tuple of ' +
+                                 str(n) + ' integers. Received: ' + str(value) + ' '
+                                 'including element ' + str(single_value) + ' of '
+                                 'type ' + str(type(single_value)))
+    return value_tuple
+
+
+def normalize_padding(value):
+    padding = value.lower()
+    allowed = {'valid', 'same', 'causal'}
+    if K.backend() == 'theano':
+        allowed.add('full')
+    if padding not in allowed:
+        raise ValueError('The `padding` argument must be one of "valid", "same" '
+                         '(or "causal" for Conv1D). Received: ' + str(padding))
+    return padding
+
+
+def convert_kernel(kernel):
+    """Converts a Numpy kernel matrix from Theano format to TensorFlow format.
+
+    Also works reciprocally, since the transformation is its own inverse.
+
+    # Arguments
+        kernel: Numpy array (3D, 4D or 5D).
+
+    # Returns
+        The converted kernel.
+
+    # Raises
+        ValueError: in case of invalid kernel shape or invalid data_format.
+    """
+    kernel = np.asarray(kernel)
+    if not 3 <= kernel.ndim <= 5:
+        raise ValueError('Invalid kernel shape:', kernel.shape)
+    slices = [slice(None, None, -1) for _ in range(kernel.ndim)]
+    no_flip = (slice(None, None), slice(None, None))
+    slices[-2:] = no_flip
+    return np.copy(kernel[slices])
+
+
+def conv_output_length(input_length, filter_size,
+                       padding, stride, dilation=1):
+    """Determines output length of a convolution given input length.
+
+    # Arguments
+        input_length: integer.
+        filter_size: integer.
+        padding: one of `"same"`, `"valid"`, `"full"`.
+        stride: integer.
+        dilation: dilation rate, integer.
+
+    # Returns
+        The output length (integer).
+    """
+    if input_length is None:
+        return None
+    assert padding in {'same', 'valid', 'full', 'causal'}
+    dilated_filter_size = filter_size + (filter_size - 1) * (dilation - 1)
+    if padding == 'same':
+        output_length = input_length
+    elif padding == 'valid':
+        output_length = input_length - dilated_filter_size + 1
+    elif padding == 'causal':
+        output_length = input_length
+    elif padding == 'full':
+        output_length = input_length + dilated_filter_size - 1
+    return (output_length + stride - 1) // stride
+
+
+def conv_input_length(output_length, filter_size, padding, stride):
+    """Determines input length of a convolution given output length.
+
+    # Arguments
+        output_length: integer.
+        filter_size: integer.
+        padding: one of `"same"`, `"valid"`, `"full"`.
+        stride: integer.
+
+    # Returns
+        The input length (integer).
+    """
+    if output_length is None:
+        return None
+    assert padding in {'same', 'valid', 'full'}
+    if padding == 'same':
+        pad = filter_size // 2
+    elif padding == 'valid':
+        pad = 0
+    elif padding == 'full':
+        pad = filter_size - 1
+    return (output_length - 1) * stride - 2 * pad + filter_size
+
+
+def deconv_length(dim_size, stride_size, kernel_size, padding,
+                  output_padding, dilation=1):
+    """Determines output length of a transposed convolution given input length.
+
+    # Arguments
+        dim_size: Integer, the input length.
+        stride_size: Integer, the stride along the dimension of `dim_size`.
+        kernel_size: Integer, the kernel size along the dimension of
+            `dim_size`.
+        padding: One of `"same"`, `"valid"`, `"full"`.
+        output_padding: Integer, amount of padding along the output dimension,
+            Can be set to `None` in which case the output length is inferred.
+        dilation: dilation rate, integer.
+
+    # Returns
+        The output length (integer).
+    """
+    assert padding in {'same', 'valid', 'full'}
+    if dim_size is None:
+        return None
+
+    # Get the dilated kernel size
+    kernel_size = kernel_size + (kernel_size - 1) * (dilation - 1)
+
+    # Infer length if output padding is None, else compute the exact length
+    if output_padding is None:
+        if padding == 'valid':
+            dim_size = dim_size * stride_size + max(kernel_size - stride_size, 0)
+        elif padding == 'full':
+            dim_size = dim_size * stride_size - (stride_size + kernel_size - 2)
+        elif padding == 'same':
+            dim_size = dim_size * stride_size
+    else:
+        if padding == 'same':
+            pad = kernel_size // 2
+        elif padding == 'valid':
+            pad = 0
+        elif padding == 'full':
+            pad = kernel_size - 1
+
+        dim_size = ((dim_size - 1) * stride_size + kernel_size - 2 * pad +
+                    output_padding)
+
+    return dim_size
diff --git a/mlair/keras_legacy/interfaces.py b/mlair/keras_legacy/interfaces.py
new file mode 100644
index 0000000000000000000000000000000000000000..45a0e310cda87df3b3af238dc83405878b0d4746
--- /dev/null
+++ b/mlair/keras_legacy/interfaces.py
@@ -0,0 +1,668 @@
+"""Interface converters for Keras 1 support in Keras 2.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import six
+import warnings
+import functools
+import numpy as np
+
+
+def generate_legacy_interface(allowed_positional_args=None,
+                              conversions=None,
+                              preprocessor=None,
+                              value_conversions=None,
+                              object_type='class'):
+    if allowed_positional_args is None:
+        check_positional_args = False
+    else:
+        check_positional_args = True
+    allowed_positional_args = allowed_positional_args or []
+    conversions = conversions or []
+    value_conversions = value_conversions or []
+
+    def legacy_support(func):
+        @six.wraps(func)
+        def wrapper(*args, **kwargs):
+            if object_type == 'class':
+                object_name = args[0].__class__.__name__
+            else:
+                object_name = func.__name__
+            if preprocessor:
+                args, kwargs, converted = preprocessor(args, kwargs)
+            else:
+                converted = []
+            if check_positional_args:
+                if len(args) > len(allowed_positional_args) + 1:
+                    raise TypeError('`' + object_name +
+                                    '` can accept only ' +
+                                    str(len(allowed_positional_args)) +
+                                    ' positional arguments ' +
+                                    str(tuple(allowed_positional_args)) +
+                                    ', but you passed the following '
+                                    'positional arguments: ' +
+                                    str(list(args[1:])))
+            for key in value_conversions:
+                if key in kwargs:
+                    old_value = kwargs[key]
+                    if old_value in value_conversions[key]:
+                        kwargs[key] = value_conversions[key][old_value]
+            for old_name, new_name in conversions:
+                if old_name in kwargs:
+                    value = kwargs.pop(old_name)
+                    if new_name in kwargs:
+                        raise_duplicate_arg_error(old_name, new_name)
+                    kwargs[new_name] = value
+                    converted.append((new_name, old_name))
+            if converted:
+                signature = '`' + object_name + '('
+                for i, value in enumerate(args[1:]):
+                    if isinstance(value, six.string_types):
+                        signature += '"' + value + '"'
+                    else:
+                        if isinstance(value, np.ndarray):
+                            str_val = 'array'
+                        else:
+                            str_val = str(value)
+                        if len(str_val) > 10:
+                            str_val = str_val[:10] + '...'
+                        signature += str_val
+                    if i < len(args[1:]) - 1 or kwargs:
+                        signature += ', '
+                for i, (name, value) in enumerate(kwargs.items()):
+                    signature += name + '='
+                    if isinstance(value, six.string_types):
+                        signature += '"' + value + '"'
+                    else:
+                        if isinstance(value, np.ndarray):
+                            str_val = 'array'
+                        else:
+                            str_val = str(value)
+                        if len(str_val) > 10:
+                            str_val = str_val[:10] + '...'
+                        signature += str_val
+                    if i < len(kwargs) - 1:
+                        signature += ', '
+                signature += ')`'
+                warnings.warn('Update your `' + object_name + '` call to the ' +
+                              'Keras 2 API: ' + signature, stacklevel=2)
+            return func(*args, **kwargs)
+        wrapper._original_function = func
+        return wrapper
+    return legacy_support
+
+
+generate_legacy_method_interface = functools.partial(generate_legacy_interface,
+                                                     object_type='method')
+
+
+def raise_duplicate_arg_error(old_arg, new_arg):
+    raise TypeError('For the `' + new_arg + '` argument, '
+                    'the layer received both '
+                    'the legacy keyword argument '
+                    '`' + old_arg + '` and the Keras 2 keyword argument '
+                    '`' + new_arg + '`. Stick to the latter!')
+
+
+legacy_dense_support = generate_legacy_interface(
+    allowed_positional_args=['units'],
+    conversions=[('output_dim', 'units'),
+                 ('init', 'kernel_initializer'),
+                 ('W_regularizer', 'kernel_regularizer'),
+                 ('b_regularizer', 'bias_regularizer'),
+                 ('W_constraint', 'kernel_constraint'),
+                 ('b_constraint', 'bias_constraint'),
+                 ('bias', 'use_bias')])
+
+legacy_dropout_support = generate_legacy_interface(
+    allowed_positional_args=['rate', 'noise_shape', 'seed'],
+    conversions=[('p', 'rate')])
+
+
+def embedding_kwargs_preprocessor(args, kwargs):
+    converted = []
+    if 'dropout' in kwargs:
+        kwargs.pop('dropout')
+        warnings.warn('The `dropout` argument is no longer support in `Embedding`. '
+                      'You can apply a `keras.layers.SpatialDropout1D` layer '
+                      'right after the `Embedding` layer to get the same behavior.',
+                      stacklevel=3)
+    return args, kwargs, converted
+
+legacy_embedding_support = generate_legacy_interface(
+    allowed_positional_args=['input_dim', 'output_dim'],
+    conversions=[('init', 'embeddings_initializer'),
+                 ('W_regularizer', 'embeddings_regularizer'),
+                 ('W_constraint', 'embeddings_constraint')],
+    preprocessor=embedding_kwargs_preprocessor)
+
+legacy_pooling1d_support = generate_legacy_interface(
+    allowed_positional_args=['pool_size', 'strides', 'padding'],
+    conversions=[('pool_length', 'pool_size'),
+                 ('stride', 'strides'),
+                 ('border_mode', 'padding')])
+
+legacy_prelu_support = generate_legacy_interface(
+    allowed_positional_args=['alpha_initializer'],
+    conversions=[('init', 'alpha_initializer')])
+
+
+legacy_gaussiannoise_support = generate_legacy_interface(
+    allowed_positional_args=['stddev'],
+    conversions=[('sigma', 'stddev')])
+
+
+def recurrent_args_preprocessor(args, kwargs):
+    converted = []
+    if 'forget_bias_init' in kwargs:
+        if kwargs['forget_bias_init'] == 'one':
+            kwargs.pop('forget_bias_init')
+            kwargs['unit_forget_bias'] = True
+            converted.append(('forget_bias_init', 'unit_forget_bias'))
+        else:
+            kwargs.pop('forget_bias_init')
+            warnings.warn('The `forget_bias_init` argument '
+                          'has been ignored. Use `unit_forget_bias=True` '
+                          'instead to initialize with ones.', stacklevel=3)
+    if 'input_dim' in kwargs:
+        input_length = kwargs.pop('input_length', None)
+        input_dim = kwargs.pop('input_dim')
+        input_shape = (input_length, input_dim)
+        kwargs['input_shape'] = input_shape
+        converted.append(('input_dim', 'input_shape'))
+        warnings.warn('The `input_dim` and `input_length` arguments '
+                      'in recurrent layers are deprecated. '
+                      'Use `input_shape` instead.', stacklevel=3)
+    return args, kwargs, converted
+
+legacy_recurrent_support = generate_legacy_interface(
+    allowed_positional_args=['units'],
+    conversions=[('output_dim', 'units'),
+                 ('init', 'kernel_initializer'),
+                 ('inner_init', 'recurrent_initializer'),
+                 ('inner_activation', 'recurrent_activation'),
+                 ('W_regularizer', 'kernel_regularizer'),
+                 ('b_regularizer', 'bias_regularizer'),
+                 ('U_regularizer', 'recurrent_regularizer'),
+                 ('dropout_W', 'dropout'),
+                 ('dropout_U', 'recurrent_dropout'),
+                 ('consume_less', 'implementation')],
+    value_conversions={'consume_less': {'cpu': 0,
+                                        'mem': 1,
+                                        'gpu': 2}},
+    preprocessor=recurrent_args_preprocessor)
+
+legacy_gaussiandropout_support = generate_legacy_interface(
+    allowed_positional_args=['rate'],
+    conversions=[('p', 'rate')])
+
+legacy_pooling2d_support = generate_legacy_interface(
+    allowed_positional_args=['pool_size', 'strides', 'padding'],
+    conversions=[('border_mode', 'padding'),
+                 ('dim_ordering', 'data_format')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}})
+
+legacy_pooling3d_support = generate_legacy_interface(
+    allowed_positional_args=['pool_size', 'strides', 'padding'],
+    conversions=[('border_mode', 'padding'),
+                 ('dim_ordering', 'data_format')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}})
+
+legacy_global_pooling_support = generate_legacy_interface(
+    conversions=[('dim_ordering', 'data_format')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}})
+
+legacy_upsampling1d_support = generate_legacy_interface(
+    allowed_positional_args=['size'],
+    conversions=[('length', 'size')])
+
+legacy_upsampling2d_support = generate_legacy_interface(
+    allowed_positional_args=['size'],
+    conversions=[('dim_ordering', 'data_format')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}})
+
+legacy_upsampling3d_support = generate_legacy_interface(
+    allowed_positional_args=['size'],
+    conversions=[('dim_ordering', 'data_format')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}})
+
+
+def conv1d_args_preprocessor(args, kwargs):
+    converted = []
+    if 'input_dim' in kwargs:
+        if 'input_length' in kwargs:
+            length = kwargs.pop('input_length')
+        else:
+            length = None
+        input_shape = (length, kwargs.pop('input_dim'))
+        kwargs['input_shape'] = input_shape
+        converted.append(('input_shape', 'input_dim'))
+    return args, kwargs, converted
+
+legacy_conv1d_support = generate_legacy_interface(
+    allowed_positional_args=['filters', 'kernel_size'],
+    conversions=[('nb_filter', 'filters'),
+                 ('filter_length', 'kernel_size'),
+                 ('subsample_length', 'strides'),
+                 ('border_mode', 'padding'),
+                 ('init', 'kernel_initializer'),
+                 ('W_regularizer', 'kernel_regularizer'),
+                 ('b_regularizer', 'bias_regularizer'),
+                 ('W_constraint', 'kernel_constraint'),
+                 ('b_constraint', 'bias_constraint'),
+                 ('bias', 'use_bias')],
+    preprocessor=conv1d_args_preprocessor)
+
+
+def conv2d_args_preprocessor(args, kwargs):
+    converted = []
+    if len(args) > 4:
+        raise TypeError('Layer can receive at most 3 positional arguments.')
+    elif len(args) == 4:
+        if isinstance(args[2], int) and isinstance(args[3], int):
+            new_keywords = ['padding', 'strides', 'data_format']
+            for kwd in new_keywords:
+                if kwd in kwargs:
+                    raise ValueError(
+                        'It seems that you are using the Keras 2 '
+                        'and you are passing both `kernel_size` and `strides` '
+                        'as integer positional arguments. For safety reasons, '
+                        'this is disallowed. Pass `strides` '
+                        'as a keyword argument instead.')
+            kernel_size = (args[2], args[3])
+            args = [args[0], args[1], kernel_size]
+            converted.append(('kernel_size', 'nb_row/nb_col'))
+    elif len(args) == 3 and isinstance(args[2], int):
+        if 'nb_col' in kwargs:
+            kernel_size = (args[2], kwargs.pop('nb_col'))
+            args = [args[0], args[1], kernel_size]
+            converted.append(('kernel_size', 'nb_row/nb_col'))
+    elif len(args) == 2:
+        if 'nb_row' in kwargs and 'nb_col' in kwargs:
+            kernel_size = (kwargs.pop('nb_row'), kwargs.pop('nb_col'))
+            args = [args[0], args[1], kernel_size]
+            converted.append(('kernel_size', 'nb_row/nb_col'))
+    elif len(args) == 1:
+        if 'nb_row' in kwargs and 'nb_col' in kwargs:
+            kernel_size = (kwargs.pop('nb_row'), kwargs.pop('nb_col'))
+            kwargs['kernel_size'] = kernel_size
+            converted.append(('kernel_size', 'nb_row/nb_col'))
+    return args, kwargs, converted
+
+legacy_conv2d_support = generate_legacy_interface(
+    allowed_positional_args=['filters', 'kernel_size'],
+    conversions=[('nb_filter', 'filters'),
+                 ('subsample', 'strides'),
+                 ('border_mode', 'padding'),
+                 ('dim_ordering', 'data_format'),
+                 ('init', 'kernel_initializer'),
+                 ('W_regularizer', 'kernel_regularizer'),
+                 ('b_regularizer', 'bias_regularizer'),
+                 ('W_constraint', 'kernel_constraint'),
+                 ('b_constraint', 'bias_constraint'),
+                 ('bias', 'use_bias')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}},
+    preprocessor=conv2d_args_preprocessor)
+
+
+def separable_conv2d_args_preprocessor(args, kwargs):
+    converted = []
+    if 'init' in kwargs:
+        init = kwargs.pop('init')
+        kwargs['depthwise_initializer'] = init
+        kwargs['pointwise_initializer'] = init
+        converted.append(('init', 'depthwise_initializer/pointwise_initializer'))
+    args, kwargs, _converted = conv2d_args_preprocessor(args, kwargs)
+    return args, kwargs, converted + _converted
+
+legacy_separable_conv2d_support = generate_legacy_interface(
+    allowed_positional_args=['filters', 'kernel_size'],
+    conversions=[('nb_filter', 'filters'),
+                 ('subsample', 'strides'),
+                 ('border_mode', 'padding'),
+                 ('dim_ordering', 'data_format'),
+                 ('b_regularizer', 'bias_regularizer'),
+                 ('b_constraint', 'bias_constraint'),
+                 ('bias', 'use_bias')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}},
+    preprocessor=separable_conv2d_args_preprocessor)
+
+
+def deconv2d_args_preprocessor(args, kwargs):
+    converted = []
+    if len(args) == 5:
+        if isinstance(args[4], tuple):
+            args = args[:-1]
+            converted.append(('output_shape', None))
+    if 'output_shape' in kwargs:
+        kwargs.pop('output_shape')
+        converted.append(('output_shape', None))
+    args, kwargs, _converted = conv2d_args_preprocessor(args, kwargs)
+    return args, kwargs, converted + _converted
+
+legacy_deconv2d_support = generate_legacy_interface(
+    allowed_positional_args=['filters', 'kernel_size'],
+    conversions=[('nb_filter', 'filters'),
+                 ('subsample', 'strides'),
+                 ('border_mode', 'padding'),
+                 ('dim_ordering', 'data_format'),
+                 ('init', 'kernel_initializer'),
+                 ('W_regularizer', 'kernel_regularizer'),
+                 ('b_regularizer', 'bias_regularizer'),
+                 ('W_constraint', 'kernel_constraint'),
+                 ('b_constraint', 'bias_constraint'),
+                 ('bias', 'use_bias')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}},
+    preprocessor=deconv2d_args_preprocessor)
+
+
+def conv3d_args_preprocessor(args, kwargs):
+    converted = []
+    if len(args) > 5:
+        raise TypeError('Layer can receive at most 4 positional arguments.')
+    if len(args) == 5:
+        if all([isinstance(x, int) for x in args[2:5]]):
+            kernel_size = (args[2], args[3], args[4])
+            args = [args[0], args[1], kernel_size]
+            converted.append(('kernel_size', 'kernel_dim*'))
+    elif len(args) == 4 and isinstance(args[3], int):
+        if isinstance(args[2], int) and isinstance(args[3], int):
+            new_keywords = ['padding', 'strides', 'data_format']
+            for kwd in new_keywords:
+                if kwd in kwargs:
+                    raise ValueError(
+                        'It seems that you are using the Keras 2 '
+                        'and you are passing both `kernel_size` and `strides` '
+                        'as integer positional arguments. For safety reasons, '
+                        'this is disallowed. Pass `strides` '
+                        'as a keyword argument instead.')
+        if 'kernel_dim3' in kwargs:
+            kernel_size = (args[2], args[3], kwargs.pop('kernel_dim3'))
+            args = [args[0], args[1], kernel_size]
+            converted.append(('kernel_size', 'kernel_dim*'))
+    elif len(args) == 3:
+        if all([x in kwargs for x in ['kernel_dim2', 'kernel_dim3']]):
+            kernel_size = (args[2],
+                           kwargs.pop('kernel_dim2'),
+                           kwargs.pop('kernel_dim3'))
+            args = [args[0], args[1], kernel_size]
+            converted.append(('kernel_size', 'kernel_dim*'))
+    elif len(args) == 2:
+        if all([x in kwargs for x in ['kernel_dim1', 'kernel_dim2', 'kernel_dim3']]):
+            kernel_size = (kwargs.pop('kernel_dim1'),
+                           kwargs.pop('kernel_dim2'),
+                           kwargs.pop('kernel_dim3'))
+            args = [args[0], args[1], kernel_size]
+            converted.append(('kernel_size', 'kernel_dim*'))
+    elif len(args) == 1:
+        if all([x in kwargs for x in ['kernel_dim1', 'kernel_dim2', 'kernel_dim3']]):
+            kernel_size = (kwargs.pop('kernel_dim1'),
+                           kwargs.pop('kernel_dim2'),
+                           kwargs.pop('kernel_dim3'))
+            kwargs['kernel_size'] = kernel_size
+            converted.append(('kernel_size', 'nb_row/nb_col'))
+    return args, kwargs, converted
+
+legacy_conv3d_support = generate_legacy_interface(
+    allowed_positional_args=['filters', 'kernel_size'],
+    conversions=[('nb_filter', 'filters'),
+                 ('subsample', 'strides'),
+                 ('border_mode', 'padding'),
+                 ('dim_ordering', 'data_format'),
+                 ('init', 'kernel_initializer'),
+                 ('W_regularizer', 'kernel_regularizer'),
+                 ('b_regularizer', 'bias_regularizer'),
+                 ('W_constraint', 'kernel_constraint'),
+                 ('b_constraint', 'bias_constraint'),
+                 ('bias', 'use_bias')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}},
+    preprocessor=conv3d_args_preprocessor)
+
+
+def batchnorm_args_preprocessor(args, kwargs):
+    converted = []
+    if len(args) > 1:
+        raise TypeError('The `BatchNormalization` layer '
+                        'does not accept positional arguments. '
+                        'Use keyword arguments instead.')
+    if 'mode' in kwargs:
+        value = kwargs.pop('mode')
+        if value != 0:
+            raise TypeError('The `mode` argument of `BatchNormalization` '
+                            'no longer exists. `mode=1` and `mode=2` '
+                            'are no longer supported.')
+        converted.append(('mode', None))
+    return args, kwargs, converted
+
+
+def convlstm2d_args_preprocessor(args, kwargs):
+    converted = []
+    if 'forget_bias_init' in kwargs:
+        value = kwargs.pop('forget_bias_init')
+        if value == 'one':
+            kwargs['unit_forget_bias'] = True
+            converted.append(('forget_bias_init', 'unit_forget_bias'))
+        else:
+            warnings.warn('The `forget_bias_init` argument '
+                          'has been ignored. Use `unit_forget_bias=True` '
+                          'instead to initialize with ones.', stacklevel=3)
+    args, kwargs, _converted = conv2d_args_preprocessor(args, kwargs)
+    return args, kwargs, converted + _converted
+
+legacy_convlstm2d_support = generate_legacy_interface(
+    allowed_positional_args=['filters', 'kernel_size'],
+    conversions=[('nb_filter', 'filters'),
+                 ('subsample', 'strides'),
+                 ('border_mode', 'padding'),
+                 ('dim_ordering', 'data_format'),
+                 ('init', 'kernel_initializer'),
+                 ('inner_init', 'recurrent_initializer'),
+                 ('W_regularizer', 'kernel_regularizer'),
+                 ('U_regularizer', 'recurrent_regularizer'),
+                 ('b_regularizer', 'bias_regularizer'),
+                 ('inner_activation', 'recurrent_activation'),
+                 ('dropout_W', 'dropout'),
+                 ('dropout_U', 'recurrent_dropout'),
+                 ('bias', 'use_bias')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}},
+    preprocessor=convlstm2d_args_preprocessor)
+
+legacy_batchnorm_support = generate_legacy_interface(
+    allowed_positional_args=[],
+    conversions=[('beta_init', 'beta_initializer'),
+                 ('gamma_init', 'gamma_initializer')],
+    preprocessor=batchnorm_args_preprocessor)
+
+
+def zeropadding2d_args_preprocessor(args, kwargs):
+    converted = []
+    if 'padding' in kwargs and isinstance(kwargs['padding'], dict):
+        if set(kwargs['padding'].keys()) <= {'top_pad', 'bottom_pad',
+                                             'left_pad', 'right_pad'}:
+            top_pad = kwargs['padding'].get('top_pad', 0)
+            bottom_pad = kwargs['padding'].get('bottom_pad', 0)
+            left_pad = kwargs['padding'].get('left_pad', 0)
+            right_pad = kwargs['padding'].get('right_pad', 0)
+            kwargs['padding'] = ((top_pad, bottom_pad), (left_pad, right_pad))
+            warnings.warn('The `padding` argument in the Keras 2 API no longer'
+                          'accepts dict types. You can now input argument as: '
+                          '`padding=(top_pad, bottom_pad, left_pad, right_pad)`.',
+                          stacklevel=3)
+    elif len(args) == 2 and isinstance(args[1], dict):
+        if set(args[1].keys()) <= {'top_pad', 'bottom_pad',
+                                   'left_pad', 'right_pad'}:
+            top_pad = args[1].get('top_pad', 0)
+            bottom_pad = args[1].get('bottom_pad', 0)
+            left_pad = args[1].get('left_pad', 0)
+            right_pad = args[1].get('right_pad', 0)
+            args = (args[0], ((top_pad, bottom_pad), (left_pad, right_pad)))
+            warnings.warn('The `padding` argument in the Keras 2 API no longer'
+                          'accepts dict types. You can now input argument as: '
+                          '`padding=((top_pad, bottom_pad), (left_pad, right_pad))`',
+                          stacklevel=3)
+    return args, kwargs, converted
+
+legacy_zeropadding2d_support = generate_legacy_interface(
+    allowed_positional_args=['padding'],
+    conversions=[('dim_ordering', 'data_format')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}},
+    preprocessor=zeropadding2d_args_preprocessor)
+
+legacy_zeropadding3d_support = generate_legacy_interface(
+    allowed_positional_args=['padding'],
+    conversions=[('dim_ordering', 'data_format')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}})
+
+legacy_cropping2d_support = generate_legacy_interface(
+    allowed_positional_args=['cropping'],
+    conversions=[('dim_ordering', 'data_format')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}})
+
+legacy_cropping3d_support = generate_legacy_interface(
+    allowed_positional_args=['cropping'],
+    conversions=[('dim_ordering', 'data_format')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}})
+
+legacy_spatialdropout1d_support = generate_legacy_interface(
+    allowed_positional_args=['rate'],
+    conversions=[('p', 'rate')])
+
+legacy_spatialdropoutNd_support = generate_legacy_interface(
+    allowed_positional_args=['rate'],
+    conversions=[('p', 'rate'),
+                 ('dim_ordering', 'data_format')],
+    value_conversions={'dim_ordering': {'tf': 'channels_last',
+                                        'th': 'channels_first',
+                                        'default': None}})
+
+legacy_lambda_support = generate_legacy_interface(
+    allowed_positional_args=['function', 'output_shape'])
+
+
+# Model methods
+
+def generator_methods_args_preprocessor(args, kwargs):
+    converted = []
+    if len(args) < 3:
+        if 'samples_per_epoch' in kwargs:
+            samples_per_epoch = kwargs.pop('samples_per_epoch')
+            if len(args) > 1:
+                generator = args[1]
+            else:
+                generator = kwargs['generator']
+            if hasattr(generator, 'batch_size'):
+                kwargs['steps_per_epoch'] = samples_per_epoch // generator.batch_size
+            else:
+                kwargs['steps_per_epoch'] = samples_per_epoch
+            converted.append(('samples_per_epoch', 'steps_per_epoch'))
+
+    keras1_args = {'samples_per_epoch', 'val_samples',
+                   'nb_epoch', 'nb_val_samples', 'nb_worker'}
+    if keras1_args.intersection(kwargs.keys()):
+        warnings.warn('The semantics of the Keras 2 argument '
+                      '`steps_per_epoch` is not the same as the '
+                      'Keras 1 argument `samples_per_epoch`. '
+                      '`steps_per_epoch` is the number of batches '
+                      'to draw from the generator at each epoch. '
+                      'Basically steps_per_epoch = samples_per_epoch/batch_size. '
+                      'Similarly `nb_val_samples`->`validation_steps` and '
+                      '`val_samples`->`steps` arguments have changed. '
+                      'Update your method calls accordingly.', stacklevel=3)
+
+    return args, kwargs, converted
+
+
+legacy_generator_methods_support = generate_legacy_method_interface(
+    allowed_positional_args=['generator', 'steps_per_epoch', 'epochs'],
+    conversions=[('samples_per_epoch', 'steps_per_epoch'),
+                 ('val_samples', 'steps'),
+                 ('nb_epoch', 'epochs'),
+                 ('nb_val_samples', 'validation_steps'),
+                 ('nb_worker', 'workers'),
+                 ('pickle_safe', 'use_multiprocessing'),
+                 ('max_q_size', 'max_queue_size')],
+    preprocessor=generator_methods_args_preprocessor)
+
+
+legacy_model_constructor_support = generate_legacy_interface(
+    allowed_positional_args=None,
+    conversions=[('input', 'inputs'),
+                 ('output', 'outputs')])
+
+legacy_input_support = generate_legacy_interface(
+    allowed_positional_args=None,
+    conversions=[('input_dtype', 'dtype')])
+
+
+def add_weight_args_preprocessing(args, kwargs):
+    if len(args) > 1:
+        if isinstance(args[1], (tuple, list)):
+            kwargs['shape'] = args[1]
+            args = (args[0],) + args[2:]
+            if len(args) > 1:
+                if isinstance(args[1], six.string_types):
+                    kwargs['name'] = args[1]
+                    args = (args[0],) + args[2:]
+    return args, kwargs, []
+
+
+legacy_add_weight_support = generate_legacy_interface(
+    allowed_positional_args=['name', 'shape'],
+    preprocessor=add_weight_args_preprocessing)
+
+
+def get_updates_arg_preprocessing(args, kwargs):
+    # Old interface: (params, constraints, loss)
+    # New interface: (loss, params)
+    if len(args) > 4:
+        raise TypeError('`get_update` call received more arguments '
+                        'than expected.')
+    elif len(args) == 4:
+        # Assuming old interface.
+        opt, params, _, loss = args
+        kwargs['loss'] = loss
+        kwargs['params'] = params
+        return [opt], kwargs, []
+    elif len(args) == 3:
+        if isinstance(args[1], (list, tuple)):
+            assert isinstance(args[2], dict)
+            assert 'loss' in kwargs
+            opt, params, _ = args
+            kwargs['params'] = params
+            return [opt], kwargs, []
+    return args, kwargs, []
+
+legacy_get_updates_support = generate_legacy_interface(
+    allowed_positional_args=None,
+    conversions=[],
+    preprocessor=get_updates_arg_preprocessing)
diff --git a/mlair/model_modules/advanced_paddings.py b/mlair/model_modules/advanced_paddings.py
index e30002e73ada07933bf0339d96ffaa433cf23d83..b0f6c9c25dfd4eacb65e5cb59a267296a7f1ab72 100644
--- a/mlair/model_modules/advanced_paddings.py
+++ b/mlair/model_modules/advanced_paddings.py
@@ -8,12 +8,88 @@ from typing import Union, Tuple
 
 import numpy as np
 import tensorflow as tf
-from tensorflow.keras.backend.common import normalize_data_format
+# from tensorflow.keras.backend.common import normalize_data_format
 from tensorflow.keras.layers import ZeroPadding2D
-from tensorflow.keras.layers.convolutional import _ZeroPadding
-from tensorflow.keras.legacy import interfaces
-from tensorflow.keras.utils import conv_utils
-from tensorflow.keras.utils.generic_utils import transpose_shape
+# from tensorflow.keras.layers.convolutional import _ZeroPadding
+from tensorflow.keras.layers import Layer
+# from tensorflow.keras.legacy import interfaces
+from mlair.keras_legacy import interfaces
+# from tensorflow.keras.utils import conv_utils
+from mlair.keras_legacy import conv_utils
+# from tensorflow.keras.utils.generic_utils import transpose_shape
+# from mlair.keras_legacy.generic_utils import transpose_shape
+
+
+
+def transpose_shape(shape, target_format, spatial_axes):
+    """Converts a tuple or a list to the correct `data_format`.
+    It does so by switching the positions of its elements.
+    # Arguments
+        shape: Tuple or list, often representing shape,
+            corresponding to `'channels_last'`.
+        target_format: A string, either `'channels_first'` or `'channels_last'`.
+        spatial_axes: A tuple of integers.
+            Correspond to the indexes of the spatial axes.
+            For example, if you pass a shape
+            representing (batch_size, timesteps, rows, cols, channels),
+            then `spatial_axes=(2, 3)`.
+    # Returns
+        A tuple or list, with the elements permuted according
+        to `target_format`.
+    # Example
+    ```python
+        >>> # from keras.utils.generic_utils import transpose_shape
+        >>> transpose_shape((16, 128, 128, 32),'channels_first', spatial_axes=(1, 2))
+        (16, 32, 128, 128)
+        >>> transpose_shape((16, 128, 128, 32), 'channels_last', spatial_axes=(1, 2))
+        (16, 128, 128, 32)
+        >>> transpose_shape((128, 128, 32), 'channels_first', spatial_axes=(0, 1))
+        (32, 128, 128)
+    ```
+    # Raises
+        ValueError: if `value` or the global `data_format` invalid.
+    """
+    if target_format == 'channels_first':
+        new_values = shape[:spatial_axes[0]]
+        new_values += (shape[-1],)
+        new_values += tuple(shape[x] for x in spatial_axes)
+
+        if isinstance(shape, list):
+            return list(new_values)
+        return new_values
+    elif target_format == 'channels_last':
+        return shape
+    else:
+        raise ValueError('The `data_format` argument must be one of '
+                         '"channels_first", "channels_last". Received: ' +
+                         str(target_format))
+
+
+def normalize_data_format(value):
+    """Checks that the value correspond to a valid data format.
+    # Arguments
+        value: String or None. `'channels_first'` or `'channels_last'`.
+    # Returns
+        A string, either `'channels_first'` or `'channels_last'`
+    # Example
+    ```python
+        >>> from tensorflow.keras import backend as K
+        >>> K.normalize_data_format(None)
+        'channels_first'
+        >>> K.normalize_data_format('channels_last')
+        'channels_last'
+    ```
+    # Raises
+        ValueError: if `value` or the global `data_format` invalid.
+    """
+    if value is None:
+        value = 'channels_last'
+    data_format = value.lower()
+    if data_format not in {'channels_first', 'channels_last'}:
+        raise ValueError('The `data_format` argument must be one of '
+                         '"channels_first", "channels_last". Received: ' +
+                         str(value))
+    return data_format
 
 
 class PadUtils:
@@ -118,6 +194,51 @@ class PadUtils:
         return normalized_padding
 
 
+class _ZeroPadding(Layer):
+    """Abstract nD ZeroPadding layer (private, used as implementation base).
+    # Arguments
+        padding: Tuple of tuples of two ints. Can be a tuple of ints when
+            rank is 1.
+        data_format: A string,
+            one of `"channels_last"` or `"channels_first"`.
+            The ordering of the dimensions in the inputs.
+            `"channels_last"` corresponds to inputs with shape
+            `(batch, ..., channels)` while `"channels_first"` corresponds to
+            inputs with shape `(batch, channels, ...)`.
+            It defaults to the `image_data_format` value found in your
+            Keras config file at `~/.keras/keras.json`.
+            If you never set it, then it will be "channels_last".
+    """
+    def __init__(self, padding, data_format=None, **kwargs):
+        # self.rank is 1 for ZeroPadding1D, 2 for ZeroPadding2D.
+        self.rank = len(padding)
+        self.padding = padding
+        self.data_format = K.normalize_data_format(data_format)
+        self.input_spec = InputSpec(ndim=self.rank + 2)
+        super(_ZeroPadding, self).__init__(**kwargs)
+
+    def call(self, inputs):
+        raise NotImplementedError
+
+    def compute_output_shape(self, input_shape):
+        padding_all_dims = ((0, 0),) + self.padding + ((0, 0),)
+        spatial_axes = list(range(1, 1 + self.rank))
+        padding_all_dims = transpose_shape(padding_all_dims,
+                                           self.data_format,
+                                           spatial_axes)
+        output_shape = list(input_shape)
+        for dim in range(len(output_shape)):
+            if output_shape[dim] is not None:
+                output_shape[dim] += sum(padding_all_dims[dim])
+        return tuple(output_shape)
+
+    def get_config(self):
+        config = {'padding': self.padding,
+                  'data_format': self.data_format}
+        base_config = super(_ZeroPadding, self).get_config()
+        return dict(list(base_config.items()) + list(config.items()))
+
+
 class ReflectionPadding2D(_ZeroPadding):
     """
     Reflection padding layer for 2D input.
@@ -321,8 +442,8 @@ class Padding2D:
 
 
 if __name__ == '__main__':
-    from keras.models import Model
-    from keras.layers import Conv2D, Flatten, Dense, Input
+    from tensorflow.keras.models import Model
+    from tensorflow.keras.layers import Conv2D, Flatten, Dense, Input
 
     kernel_1 = (3, 3)
     kernel_2 = (5, 5)
diff --git a/mlair/model_modules/convolutional_networks.py b/mlair/model_modules/convolutional_networks.py
index 294e31aff582fbdcb7643863790f51363389d8c3..be047eb7a1c92cbb8847328c157c874bfeca93ca 100644
--- a/mlair/model_modules/convolutional_networks.py
+++ b/mlair/model_modules/convolutional_networks.py
@@ -21,7 +21,7 @@ class CNN(AbstractModelClass):
     _initializer = {"tanh": "glorot_uniform", "sigmoid": "glorot_uniform", "linear": "glorot_uniform",
                     "relu": keras.initializers.he_normal(), "selu": keras.initializers.lecun_normal(),
                     "prelu": keras.initializers.he_normal()}
-    _optimizer = {"adam": keras.optimizers.adam, "sgd": keras.optimizers.SGD}
+    _optimizer = {"adam": keras.optimizers.Adam, "sgd": keras.optimizers.SGD}
     _regularizer = {"l1": keras.regularizers.l1, "l2": keras.regularizers.l2, "l1_l2": keras.regularizers.l1_l2}
     _requirements = ["lr", "beta_1", "beta_2", "epsilon", "decay", "amsgrad", "momentum", "nesterov", "l1", "l2"]
     _dropout = {"selu": keras.layers.AlphaDropout}
diff --git a/run.py b/run.py
index 49537c8fe595984114cf80bf250bda7d71de4f67..1d607acfc8a789080a38da9ee546e516be845797 100644
--- a/run.py
+++ b/run.py
@@ -3,7 +3,7 @@ __date__ = '2020-06-29'
 
 import argparse
 from mlair.workflows import DefaultWorkflow
-from mlair.model_modules.model_class import MyLittleModelHourly as chosen_model
+# from mlair.model_modules.recurrent_networks import RNN as chosen_model
 from mlair.helpers import remove_items
 from mlair.configuration.defaults import DEFAULT_PLOT_LIST
 import os
@@ -29,7 +29,7 @@ def main(parser_args):
         stations=["DEBW013", "DEBW087", "DEBW107", "DEBW076"],
         train_model=False, create_new_model=True, network="UBA",
         evaluate_bootstraps=False,  # plot_list=["PlotCompetitiveSkillScore"],
-        competitors=["test_model", "test_model2"], model=chosen_model,
+        competitors=["test_model", "test_model2"], # model=chosen_model,
         competitor_path=os.path.join(os.getcwd(), "data", "comp_test"),
         **parser_args.__dict__, start_script=__file__)
     workflow.run()