I am trying to forecast retail sales for a company that have different stores.

I currently use LSTM model which is designed as follows: data includes the info about sales between 2014-2020.

After splitting train test data for all stores, I concatenated them and train the model with combined values.

For now, I have the same architecture but different model weights for all items and that means each items' time series are being combined for training and prediction is made on items' specific model weights.

Anyway, even though my network architecture captures the pattern very well, it gets stuck on peak values and cannot give good results for the peak values. Here are some forecast results, that would be great to hear some advices. All pictures are same item but different stores,

enter image description here enter image description here enter image description here enter image description here

lstm_itemStgt, lstm_itemKoln, and lstm_itemMN refer to different stores, and they are time series data which are multivariate with features: discount, unit price, day of week, season (summer, winter, etc.):

enter image description here

def trainModel(X_train,n_batch=64):
    model = Sequential()
    model.add(LSTM(128,return_sequences = True,activation='relu',
                   input_shape=(X_train.shape[1], X_train.shape[2])))
    model.add(LSTM(units =64,activation='relu',return_sequences = True))
    #model.add(Bidirectional(LSTM(units =50,activation='relu',return_sequences = True)))
    #model.add(Dense(10,kernel_initializer='glorot_normal', activation='relu'))
    #model.add(Dense(10,kernel_initializer='glorot_normal', activation='relu'))
    lr_schedule = optimizers.schedules.ExponentialDecay(
    optimizer = optimizers.Adam(learning_rate=0.00001)
    model.compile(loss='mean_squared_error', optimizer=optimizer)  
    return model

df_history = pd.DataFrame()

df_future = pd.DataFrame()             
temp_dictKoln = pd.DataFrame()
temp_dictStgt = pd.DataFrame()
temp_dictMnhm = pd.DataFrame()
temp_accKoln = pd.DataFrame()
temp_accStgt = pd.DataFrame()
temp_accMnhm = pd.DataFrame()                                                                                   
for index in range(1):
    item_woDummyStgt = grouped_dfStgt.get_group(items[index]).iloc[:,1:]
    item_woDummyKoln = grouped_dfKoln.get_group(items[index]).iloc[:,1:]
    item_woDummyMN = grouped_dfMN.get_group(items[index]).iloc[:,1:]
    lstm_itemStgt = getDFDummies(item_woDummyStgt,drop_first=True)
    lstm_itemKoln =  getDFDummies(item_woDummyKoln,drop_first=True)
    lstm_itemMN =  getDFDummies(item_woDummyMN,drop_first=True)
    train_scaledStgt, test_scaledStgt,item_stdevStgt = scaled_split(lstm_itemStgt)
    train_scaledKoln, test_scaledKoln,item_stdevKoln = scaled_split(lstm_itemKoln)
    train_scaledMN, test_scaledMN,item_stdevMN = scaled_split(lstm_itemMN)
    X_trainStgt, y_trainStgt,X_testStgt,y_testStgt= train_testSplit(train_scaledStgt,test_scaledStgt,120)
    X_trainKoln, y_trainKoln,X_testKoln,y_testKoln= train_testSplit(train_scaledKoln,test_scaledKoln,120)
    X_trainMN, y_trainMN,X_testMN,y_testMN = train_testSplit(train_scaledMN,test_scaledMN,120)
    training_array = np.concatenate((X_trainStgt,X_trainKoln,X_trainMN,X_trainMN))
    output_array = np.concatenate((y_trainStgt,y_trainKoln,y_trainMN,y_trainMN))
    training_val_array = np.concatenate((X_testStgt,X_testKoln,X_testMN,X_testMN))
    output_val_array = np.concatenate((y_testStgt,y_testKoln,y_testMN,y_testMN))

    item_stdevStgt = item_stdevStgt[-len(X_testStgt):]
    item_stdevKoln = item_stdevKoln[-len(X_testKoln):]
    item_stdevMN = item_stdevMN[-len(X_testMN):]
    model = trainModel(training_array)
    #model = load_model('my_model.h5')
    reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.2,patience=3, min_lr=0.00001)
    history = model.fit(training_array, output_array, epochs=50, batch_size=1,validation_data=(training_val_array,output_val_array),
                        verbose=2, shuffle=False)
    #history = pickle.load(open('trainHistoryDict','rb'))
    df_history = df_history.append({"Item No_":str(items[index]),
    #scaler = MinMaxScaler(feature_range=(0, 1))
    #scaled_testItem = scaler.fit_transform(lstm_item[lstm_item.columns].values)
    # make a prediction
         #temp_future = forecast_future(item_woDummy,7,model,int(len(lstm_item)*0.12))
         #df_future = df_future.append({"Item No_":str(items[item]),"FutureForecasts":temp_future},ignore_index=True)        
         storeStgt = storePrediction(inv_yStgt ,inv_yhatStgt ,item_stdevStgt )
         storeKoln = storePrediction(inv_yKoln,inv_yhatKoln,item_stdevKoln)
         storeMN = storePrediction(inv_yMN,inv_yhatMN,item_stdevMN)
         dfAccuracyStgt = accuracyFrame(items[index],inv_yStgt,inv_yhatStgt)
         dfAccuracyKoln = accuracyFrame(items[index],inv_yKoln,inv_yhatKoln)
         dfAccuracyMN = accuracyFrame(items[index],inv_yMN,inv_yhatMN)

  • $\begingroup$ What's the model? It's not possible to make suggestions for improvement without information about the model. $\endgroup$
    – Sycorax
    Nov 30, 2020 at 14:28
  • $\begingroup$ My advice, try a different forecasting path here. I believe your data points are perhaps showing large differences due to possible large real variations in the communities where the stores are located. Your chosen forecasting software is perhaps viewing these only apparently aberrant stores as non-repeating or forecastable background noise and is auto modelling to a mean, a good general (but apparently not always) strategy. $\endgroup$
    – AJKOER
    Nov 30, 2020 at 15:01
  • $\begingroup$ Thanks for your advice. By saying different forecasting path, you mean change your forecast model ? I have tried with SARIMAX models and the prediction results were close to these results actually. $\endgroup$ Nov 30, 2020 at 15:07
  • $\begingroup$ The question appears to be are the outlier stores continuing to show very poor (more competition) or great (competition has closed) results which could perhaps be modeled by adding a -1,0,+1 dummy variable for respectively a failing store, no change in dynamics, or continuing short term great results. This dummy should be included only upon confirming the apparent reason for the change in dynamics in select locations. $\endgroup$
    – AJKOER
    Nov 30, 2020 at 15:47

1 Answer 1


Your data seem to have a strong cycle - probably weekly. I would suggest separating this out:

  • calculate the 7-day sliding average, and try to predict that
  • calculate the weekly variation, and if there is trend in the variation, predict that
  • $\begingroup$ Yes, it is weekly but isnt it better to see the strong cycle, why I should separate and eliminate it ? I tried to predict 7 day sliding average with SARIMAX model, but it was not good enough to be used as forecasting. For your secondsuggestion, I will try that, thanks for the advice. $\endgroup$ Dec 1, 2020 at 6:11
  • 1
    $\begingroup$ "why I should separate and eliminate it ?" So you can identify whether there are other trends that are worth modelling. Then add the weekly cycle back in to your predictions at the end. $\endgroup$ Dec 2, 2020 at 9:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.