Forecasting de cereri de biciclete pe ore, pe datasetul UCI Bike Sharing. Compar trei modele (ARIMA, Holt-Winters, LSTM), fiecare in doua variante: una din librarie si una scrisa de la zero, ca sa inteleg ce e de fapt in spate, nu doar sa apelez o functie.
UCI Bike Sharing, varianta orara (hour.csv), 17379 de ore din 2011-2012.
Target: cnt, cate biciclete s-au inchiriat in ora respectiva.
O capcana peste care am dat pe drum: la inceput am incercat pe un dataset de pe
HuggingFace (bike-sharing-federated), dar ala era amestecat (shuffled pentru
federated learning). Corelatia dintre ora t si ora t-1 era 0.007 (practic
zero), deci ARIMA/HW/LSTM nu aveau ce invata din timp si toate dadeau media.
UCI original e in ordine cronologica reala: aceeasi corelatie iese 0.845.
Morala: verifica intai daca datele chiar sunt o serie temporala.
Split: primele 80% train, ultimele 20% test, cronologic (fara shuffle). Testul cade in 2012, cand cererea crescuse fata de 2011 (media pe train ~175, pe test ~249). De-aia forecasting-ul pe tot orizontul deodata e prost: modelul ramane ancorat la nivelul lui 2011.
Fiecare model are o varianta Lib (din librarie, ca benchmark) si una Scratch
(scrisa de mine):
- ARIMA --
ARIMALib(statsmodels) /ARIMAScratch(diferentiere + AR prin OLS + MA prin Hannan-Rissanen) - Holt-Winters --
HWLib(statsmodels) /HWScratch(nivel/trend/sezon updatate manual, alpha/beta/gamma gasite prin grid search) - LSTM --
LSTMLib(nn.LSTM) /LSTMScratch(cele 4 gates scrise de mana, antrenat tot cu autograd din PyTorch)
Toate modelele sunt evaluate la fel: la fiecare ora din test prezic ora urmatoare folosind istoricul real. E realist (in practica reactualizezi cu ce s-a intamplat) si e singurul protocol in care se vede diferenta dintre modele.
Forecasting pe tot orizontul deodata nu merge aici: ARIMA se stinge la medie, HW repeta o zi tipica, si amandoua rateaza schimbarea de nivel din 2012.
| model | MAE | RMSE |
|---|---|---|
| baseline (media constanta) | 174.98 | 232.61 |
| persistenta (prezice ora trecuta) | 85.18 | 129.65 |
| Holt-Winters scratch (tunat) | 74.27 | 106.07 |
| ARIMA lib | 76.21 | 110.15 |
| ARIMA scratch | 77.35 | 117.64 |
| LSTM lib | 36.18 | 55.67 |
| LSTM scratch | 36.90 | 54.93 |
Ce se vede:
- LSTM castiga clar (MAE ~36 vs ~76 la clasice). Foloseste si istoricul, si
features (ora, vreme, sezon, weekend, an). ARIMA/HW vad doar
cnt-urile trecute. - Variantele scratch reproduc librariile: ARIMA scratch 77.35 vs lib 76.21, LSTM scratch 36.90 vs lib 36.18. Deci implementarile de la zero sunt corecte.
- HW tunat (74.27) bate ARIMA, dupa ce am cautat alpha/beta/gamma prin grid search (era 110 cu parametrii default).
- Toate bat clar persistenta (85) si media (175).
pip install -r requirements.txt
# torch fara GPU: pip install torch --index-url https://download.pytorch.org/whl/cpu
python train_all.py --model lstm --data hourly # antreneaza un model (+ salveaza in models/)
python train_all.py --model lstm --epochs 100 # vezi curba de antrenare (train_mse vs val_mse)
python evaluate.py # tabelul comparativ complet, toate modelele
python plots.py # grafic real vs prezis -> results/lstm_forecast.png
Datele (data/bike_hourly.csv) sunt deja in repo, nu trebuie descarcate.
src/
config.py parametrii (cai, split, ordine ARIMA, perioade HW, hyperparametri LSTM)
data.py load_hourly (UCI, cronologic) / load_daily
arima.py ARIMALib + ARIMAScratch + find_d + grid search
hw_model.py HWLib + HWScratch (cu grid search pe alpha/beta/gamma)
lstm.py LSTMLib + LSTMScratch (celula manuala) + save/load
metrics.py MAE / RMSE / MAPE de la zero
train_all.py antreneaza un model (--model / --data / --epochs)
evaluate.py comparatie one-step a tuturor modelelor
plots.py grafic predictie vs realitate
- Numerele LSTM sunt reproductibile (seed fix 74).
- MAPE e inselator pe datele astea (sunt ore de noapte cu
cnt=1, impartirea la ele umfla procentul). Ne uitam la MAE/RMSE. - TODO: ARIMAScratch pentru
d>=2; early stopping automat la LSTM.