def default_training_closure(model, optimizer, loss_fn):
    """
    This function produces a simple training loop that can be used for many situations. During each loop, the model is applied to the
    current batch, the loss_fn computes a loss, gradients are computed on the loss, and the optimizer updates its parameters using those
    gradients. The update function returns a dictionary containing the loss, the current value of the optimizer (ie. the gradients), and
    the output of the model.
    """
    def update_function(engine, batch):

        model.train()
        optimizer.zero_grad()
        output = model(batch)
        loss = loss_fn(output)
        loss.backward()
        optimizer.step()

        return {'loss': loss.detach().cpu().numpy(), 'optimizer': optimizer.state_dict(), 'state': model.get_state(), 'output': output}

    return update_function