def train(data: Dict[(str, np.ndarray)], model_name: str, dest_path: str, sample_size: int, n_classes: int, lr: float, batch_size: int, epochs: int, verbose: int, shuffle: bool, patience: int, seed: int):
    '\n    Function for running experiments on various unmixing models,\n    given a set of hyper parameters.\n\n    :param data: The data dictionary containing\n        the subsets for training and validation.\n        First dimension of the datasets should be the number of samples.\n    :param model_name: Name of the model, it serves as a key in the\n        dictionary holding all functions returning models.\n    :param dest_path: Path to where all experiment runs will be saved as\n        subdirectories in this given directory.\n    :param sample_size: Size of the input sample.\n    :param n_classes: Number of classes.\n    :param lr: Learning rate for the model, i.e., regulates\n        the size of the step in the gradient descent process.\n    :param batch_size: Size of the batch used in training phase,\n        it is the size of samples per gradient step.\n    :param epochs: Number of epochs for model to train.\n    :param verbose: Verbosity mode used in training, (0, 1 or 2).\n    :param shuffle: Boolean indicating whether to shuffle datasets.\n    :param patience: Number of epochs without improvement in order to\n        stop the training phase.\n    :param seed: Seed for training reproducibility.\n    '
    np.random.seed(seed=seed)
    model = _get_model(model_key=model_name, **{'input_size': sample_size, 'n_classes': n_classes})
    model.summary()
    model.compile(optimizer=tf.keras.optimizers.Adam(lr=lr), loss=UNMIXING_LOSSES[model_name], metrics=UNMIXING_TRAIN_METRICS[model_name])
    time_history = time_metrics.TimeHistory()
    mcp_save = tf.keras.callbacks.ModelCheckpoint(os.path.join(dest_path, 'model.h5'), save_best_only=True, monitor='val_loss', mode='min')
    early_stopping = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=patience, mode='min')
    callbacks = [time_history, mcp_save, early_stopping]
    train_dict = data[enums.Dataset.TRAIN].copy()
    val_dict = data[enums.Dataset.VAL].copy()
    (min_, max_) = (data[enums.DataStats.MIN], data[enums.DataStats.MAX])
    transformations = [transforms.MinMaxNormalize(min_=min_, max_=max_)]
    transformations += [t() for t in UNMIXING_TRANSFORMS[model_name]]
    train_dict = transforms.apply_transformations(train_dict, transformations)
    val_dict = transforms.apply_transformations(val_dict, transformations)
    history = model.fit(x=train_dict[enums.Dataset.DATA], y=train_dict[enums.Dataset.LABELS], epochs=epochs, verbose=verbose, shuffle=shuffle, validation_data=(val_dict[enums.Dataset.DATA], val_dict[enums.Dataset.LABELS]), callbacks=callbacks, batch_size=batch_size)
    np.savetxt(os.path.join(dest_path, 'min-max.csv'), np.array([min_, max_]), delimiter=',', fmt='%f')
    history.history[time_metrics.TimeHistory.__name__] = time_history.average
    io.save_metrics(dest_path=dest_path, file_name='training_metrics.csv', metrics=history.history)
Function for running experiments on various unmixing models,
given a set of hyper parameters.

:param data: The data dictionary containing
    the subsets for training and validation.
    First dimension of the datasets should be the number of samples.
:param model_name: Name of the model, it serves as a key in the
    dictionary holding all functions returning models.
:param dest_path: Path to where all experiment runs will be saved as
    subdirectories in this given directory.
:param sample_size: Size of the input sample.
:param n_classes: Number of classes.
:param lr: Learning rate for the model, i.e., regulates
    the size of the step in the gradient descent process.
:param batch_size: Size of the batch used in training phase,
    it is the size of samples per gradient step.
:param epochs: Number of epochs for model to train.
:param verbose: Verbosity mode used in training, (0, 1 or 2).
:param shuffle: Boolean indicating whether to shuffle datasets.
:param patience: Number of epochs without improvement in order to
    stop the training phase.
:param seed: Seed for training reproducibility.
def train(data: Dict[(str, np.ndarray)], model_name: str, dest_path: str, sample_size: int, n_classes: int, lr: float, batch_size: int, epochs: int, verbose: int, shuffle: bool, patience: int, seed: int):
    '\n    Function for running experiments on various unmixing models,\n    given a set of hyper parameters.\n\n    :param data: The data dictionary containing\n        the subsets for training and validation.\n        First dimension of the datasets should be the number of samples.\n    :param model_name: Name of the model, it serves as a key in the\n        dictionary holding all functions returning models.\n    :param dest_path: Path to where all experiment runs will be saved as\n        subdirectories in this given directory.\n    :param sample_size: Size of the input sample.\n    :param n_classes: Number of classes.\n    :param lr: Learning rate for the model, i.e., regulates\n        the size of the step in the gradient descent process.\n    :param batch_size: Size of the batch used in training phase,\n        it is the size of samples per gradient step.\n    :param epochs: Number of epochs for model to train.\n    :param verbose: Verbosity mode used in training, (0, 1 or 2).\n    :param shuffle: Boolean indicating whether to shuffle datasets.\n    :param patience: Number of epochs without improvement in order to\n        stop the training phase.\n    :param seed: Seed for training reproducibility.\n    '
    np.random.seed(seed=seed)
    model = _get_model(model_key=model_name, **{'input_size': sample_size, 'n_classes': n_classes})
    model.summary()
    model.compile(optimizer=tf.keras.optimizers.Adam(lr=lr), loss=UNMIXING_LOSSES[model_name], metrics=UNMIXING_TRAIN_METRICS[model_name])
    time_history = time_metrics.TimeHistory()
    mcp_save = tf.keras.callbacks.ModelCheckpoint(os.path.join(dest_path, 'model.h5'), save_best_only=True, monitor='val_loss', mode='min')
    early_stopping = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=patience, mode='min')
    callbacks = [time_history, mcp_save, early_stopping]
    train_dict = data[enums.Dataset.TRAIN].copy()
    val_dict = data[enums.Dataset.VAL].copy()
    (min_, max_) = (data[enums.DataStats.MIN], data[enums.DataStats.MAX])
    transformations = [transforms.MinMaxNormalize(min_=min_, max_=max_)]
    transformations += [t() for t in UNMIXING_TRANSFORMS[model_name]]
    train_dict = transforms.apply_transformations(train_dict, transformations)
    val_dict = transforms.apply_transformations(val_dict, transformations)
    history = model.fit(x=train_dict[enums.Dataset.DATA], y=train_dict[enums.Dataset.LABELS], epochs=epochs, verbose=verbose, shuffle=shuffle, validation_data=(val_dict[enums.Dataset.DATA], val_dict[enums.Dataset.LABELS]), callbacks=callbacks, batch_size=batch_size)
    np.savetxt(os.path.join(dest_path, 'min-max.csv'), np.array([min_, max_]), delimiter=',', fmt='%f')
    history.history[time_metrics.TimeHistory.__name__] = time_history.average
    io.save_metrics(dest_path=dest_path, file_name='training_metrics.csv', metrics=history.history)