def _train_ff_network(
        hyperparameter_dict: dict,
        data: SignalData) -> Tuple[FeedForward, List, List, List, List]:
    """Trains a feed-forward network using the specified hyperparameters.
    """
    # Ensure reproducibility by giving PyTorch the same seed every time we train.
    torch.manual_seed(1)

    # Print hyperparameters.
    print(f'Hyperparameters: {hyperparameter_dict}')

    # Get hyperparameters.
    learning_rate = hyperparameter_dict['learning_rate']
    batch_size = hyperparameter_dict['batch_size']
    optimizer_str = hyperparameter_dict['optimizer']

    # There are 6 labels, and Pytorch expects them to go from 0 to 5.
    full_train_labels = data.train_labels - 1

    # Get generators.
    signal_dataset = SignalDataset(data.train_signals, full_train_labels)
    (training_generator,
     validation_generator) = utils_nn.get_trainval_generators(
         signal_dataset, batch_size, num_workers=0, training_fraction=0.8)

    # Crete feed forward network.
    input_size = data.num_timesteps * data.num_components
    feed_forward = FeedForward(input_size, input_size,
                               data.num_activity_labels)
    print(feed_forward)

    # Parameters should be moved to GPU before constructing the optimizer.
    device = torch.device('cuda:0' if USE_CUDA else 'cpu')
    feed_forward = feed_forward.to(device)

    # Get optimizer.
    optimizer = None
    if optimizer_str == 'adam':
        optimizer = torch.optim.Adam(feed_forward.parameters(),
                                     lr=learning_rate)
    elif optimizer_str == 'sgd':
        optimizer = torch.optim.SGD(feed_forward.parameters(),
                                    lr=learning_rate)
    else:
        raise Exception(f'Specified optimizer not valid: {optimizer_str}')

    training_accuracy_list = []
    training_loss_list = []
    validation_accuracy_list = []
    validation_loss_list = []
    max_epochs = 10
    for epoch in range(max_epochs):
        print(f'Epoch {epoch}')

        # Training data.
        (training_accuracy,
         training_loss) = utils_nn.fit(feed_forward, training_generator,
                                       optimizer, USE_CUDA)
        training_accuracy_list.append(training_accuracy)
        training_loss_list.append(training_loss)

        # Validation data.
        (validation_accuracy,
         validation_loss) = utils_nn.evaluate(feed_forward,
                                              validation_generator,
                                              'Validation', USE_CUDA)
        validation_accuracy_list.append(validation_accuracy)
        validation_loss_list.append(validation_loss)

    return (feed_forward, training_accuracy_list, training_loss_list,
            validation_accuracy_list, validation_loss_list)