philipperemy / n-beats

Keras/Pytorch implementation of N-BEATS: Neural basis expansion analysis for interpretable time series forecasting.
MIT License
864 stars 166 forks source link

Problem in reimplement m4 result in origin parper #57

Closed fecet closed 2 years ago

fecet commented 2 years ago

I'm trying to reproduce the results for m4 dataset and have done most work, but there still exist some problems, with frequency and lookback increase, the model tend to output large value and smape loss stop at something like 199.02. As the input size is determined by horizon(forcast_length) and lookback:

backcast_length=forecast_length*lookback

I guess the problem result from large input_size, but I have no idea how to fix it. Here is my smape loss

def smape_loss(y_true,y_pred):
    """
    sMAPE loss as defined in "Appendix A" of
    http://www.forecastingprinciples.com/files/pdf/Makridakia-The%20M3%20Competition.pdf
    :param forecast: Forecast values. Shape: batch, time
    :param target: Target values. Shape: batch, time
    :param mask: 0/1 mask. Shape: batch, time
    :return: Loss value
    """
    # mask=tf.where(y_true,1.,0.)
    mask=tf.cast(y_true,tf.bool)
    mask=tf.cast(mask,tf.float32)
    sym_sum= tf.abs(y_true)+tf.abs(y_pred) 
    condition=tf.cast(sym_sum,tf.bool)
    weights=tf.where(condition,1./( sym_sum + 1e-8),0.0)
    return 200 * tnp.nanmean(tf.abs(y_pred - y_true)*weights * mask)
    # return 200 * tnp.nanmean(tf.abs(y_pred - y_true)*weights )

and my model config

net = NBeatsNet(
        # stack_types=(NBeatsNet.GENERIC_BLOCK, NBeatsNet.GENERIC_BLOCK),
        stack_types=(NBeatsNet.TREND_BLOCK, NBeatsNet.SEASONALITY_BLOCK),
        nb_blocks_per_stack=3,
        forecast_length=outsample_size,
        backcast_length=insample_size,
        hidden_layer_units=512,
        thetas_dim=(4,4),
        share_weights_in_stack=True,
        nb_harmonics=1
        )
lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(
        initial_learning_rate,
        decay_steps=epoch // 3,
        decay_rate=0.5,
        staircase=True)

net.compile(loss=smape_loss, 
        # optimizer='adam',
        optimizer=tf.keras.optimizers.Adam(
            learning_rate=lr_schedule,
            clipnorm=1.0,
            clipvalue=0.5
        ),
    )

If someone interested I will post the full code here.

fecet commented 2 years ago

[UPDATE] I find the origin paper mentioned that the stop gradient in denominator, but I dont see this trick in his code and that may result in relative bad performance. I will keep investigating it.

fecet commented 2 years ago

The linear space function should use an adapted horizon,

def linear_space(backcast_length, forecast_length, is_forecast=True):
    # ls = K.arange(-float(backcast_length), float(forecast_length), 1) / forecast_length
    # return ls[backcast_length:] if is_forecast else K.abs(K.reverse(ls[:backcast_length], axes=0))
    horizon = forecast_length if is_forecast else backcast_length
    return K.arange(0,horizon)/horizon
philipperemy commented 2 years ago

@fecet thank you very much!! Can you detail a bit more what you found? Is it just changing the linear_space and it works?

philipperemy commented 2 years ago

And yes please post the full code it's very interesting!

fecet commented 2 years ago

@fecet thank you very much!! Can you detail a bit more what you found? Is it just changing the linear_space and it works?

Yes, in former code, the denominator is always forecast_length, when backcast_lenghth is large, like 7*forecast_length the value in linear_space will reach up to 7, result in output explode in trend block since it will be multiplied by 7*p several times. And smape_loss's own property make it become 200 as y_pred dominate.

Thanks for your interest, I will create a repository for my reimplement later. BTW, I found this behaviour by your keract tool!

philipperemy commented 2 years ago

@fecet Very happy to hear that!! Please post your results and code. Thank you for using keract. I'm happy it helps ;)

fecet commented 2 years ago

Here is my repo, any comments would be highly appreciated.

philipperemy commented 2 years ago

thank you so much I'm going to have a look shortly

philipperemy commented 2 years ago

I added a link to your repo in the README btw

philipperemy commented 2 years ago

Fixed in https://github.com/philipperemy/n-beats/commit/fb1f6127c5030eaecf3b647c349b2c6c7d12cbbf