Trainer callbacks
# What does this PR do?

This PR does two things: clean up a bit the files supporting `Trainer` and the `Trainer` class, and add callbacks to `Trainer`.

### Callbacks

This PR introduces a new class called `TrainerCallback` that can access the current state of the training loop and make some decisions (shown in the `TrainerControl` object). This allows us to isolate the pieces of code that do log-reporting on the various ML platforms or report progress in another file and clean up the code of the main `train` method of the `Trainer`.

This way, any new platform we want to integrate with for log-reporting or new behavior (like early stopping) can be implemented in a Callback while `Trainer` focuses on the main aspects of actual training, with or without mixed precision, on one or several GPUs/TPUs.

As an example, integrations with TensorBoard, Wandb and ComeML are moved to the `integrations` module in clean callbacks, while the control flow of logs/saves/evaluations as well as progress reporting are moved to the `trainer_callback` file.

Most of the behavior stays the same as this PR essentially moves code around, but there are a few API changes:
- deprecating the `tb_writer` argument in `Trainer` (with full backward compatibility), people should now use the `TensorBoardCallback`.
- a new `callbacks` argument in the `Trainer` init and new `add_callback`, `pop_callback` and `remove_callback` for the `Trainer`. For all of those, you can either pass an instance of a callback or a callback class.
- Cleaned up the progress bars a bit with only one main progress bar over all the steps we will do for training and evaluation bars that disappear after being done

### Progress bars

Here is the new progress bar behavior in console mode (checked in single and multi GPU envs, to make sure only one progress bar is displayed/logs are only printed once):

![](https://i.ibb.co/Fq60bFS/console-progress.png)

and in a jupyter notebook:

![](https://i.ibb.co/kSzs1fC/notebook-progress.png)

### General cleanup

Not directly related to this PR, but related to the general cleanup of `Trainer`, I moved a bit of stuff around: moved the utils at the start of `Trainer` to a new `trainer_utils_pt`. This way `trainer_utils` can be about the general training utils that work on both PyTorch and TensorFlow, and I moved the ones specific to PyTorch to `trainer_utils_pt`.

Also in `Trainer`, the code for logs, save and evaluation ended being duplicated between the end of a training step and the end of an epoch, so I put it in its private method to improve readability.
Finally! Can't wait for this PR to be merged.
I've briefly looked at the code and from my understanding it should support this case, but correct me if I'm wrong:

_When saving checkpoints (including model weights as well as scheduler and optimizer states), I will be able to attach to this process and store the checkpoint in some external repository (i.e GCS / W&B artifact)_,

right?<|||||>Yes, you will be able to inject custom behavior to the saved checkpoint with the `on_save` event.