# Introduction
/ Hilaf Hasson

This work wraps the [winning solution in the M5 Accuracy competition](https://github.com/Mcompetitions/M5-methods/tree/master/Code%20of%20Winning%20Methods/A1) (by YeonJun Im) with [QRX](https://github.com/awslabs/gluonts/blob/dev/src/gluonts/model/rotbaum/_model.py).

QRX is an algorithm that takes a point forecaster as input and outputs a probabilistic forecaster. I designed it originally for the purpose of being the engine for [Rotbaum](https://github.com/awslabs/gluonts/tree/dev/src/gluonts/model/rotbaum), the flagship tree-based algorithm in [gluonts](https://github.com/awslabs/gluonts).

The rough idea behind the logic of QRX is that it uses the point forecaster's predictions to create bins of true values that are being sampled at inference. We will outline the exact algorithm in a future paper.

# Rough Sketch of Winning M5 Accuracy Competition Solution

The solution trains ensembles (simply average) 6 models, 3 of which are "recursive" and 3 are "non-recursive". Each single model is really made of many sub-models, each trained on a different training set. There are 220 models overall. For more details see `ORIGINAL_README.md`, written by the winner.

# Adjustments

## Preprocessing 
No changes made.

## Training
Wrapping lightgbm with QRX is quite easy:

```
from lightgbm import LGBMRegressor
from gluonts.model.rotbaum._model import QRX

estimator = QRX(model=LGBMRegressor(**lgb_params),
                    min_bin_size=200)
    estimator.fit(
        grid_df[train_mask][features_columns], 
        grid_df[train_mask][TARGET],
        max_sample_size=1000000, 
        seed=SEED,
        eval_set=(
            grid_df[valid_mask][features_columns], 
            grid_df[valid_mask][TARGET]
        ),
        verbose=100,
        x_train_is_dataframe=True
    )
```

Here `max_sample_size` creates sample from the training data to train on. It is optional, but reduces training time. `x_train_is_dataframe` should be `True` if the base model is lightgbm as it is here, but `False` if one uses XGBoost. The parameter `min_bin_size` is the minimal size of the bins of true values created by QRX. I have chosen `200` for the simple reason that this is the minimal number that allows for the quantiles considered in the M5 Uncertainty competition, where the quantiles considered are `0.005, 0.025, 0.165, 0.250, 0.500, 0.750, 0.835, 0.975, 0.995`. It may very well be that a different choice would yield better results. Finally, `eval_set` is optional, and I use it only whenever the winning M5 solution prescribes it.

Note that only model is being trained regardless of the number of quantiles on which there will be inference.

### The analogous code in the original

```
train_data = lgb.Dataset(grid_df[train_mask][features_columns], 
                       label=grid_df[train_mask][TARGET])
    
valid_data = lgb.Dataset(grid_df[valid_mask][features_columns], 
                       label=grid_df[valid_mask][TARGET])

estimator = lgb.train(lgb_params,
                          train_data,
                          valid_sets = [valid_data],
                          verbose_eval = 100,
                          )
```

## Inference

To make inferences, simply specify the quantile. The relevant line in the code is:

`base_test[TARGET][mask] = estimator.predict(grid_df[mask][MODEL_FEATURES], float(quantile))`

### The analogous code in the original

`base_test[TARGET][mask] = estimator.predict(grid_df[mask][MODEL_FEATURES])`
