Retail Demand Forecasting Pipeline

PyTorchTensorFlowMLflowFastAPIDockerAWS EC2PostgreSQL

The problem

Retail sales are shaped by overlapping factors, seasonality, promotions, competition, that do not move in one predictable pattern, and any model has to forecast using only information genuinely known in advance, not signals that quietly leak from the future. The real difficulty was not fitting a curve to historical sales, it was building something trustworthy enough to forecast a day it had never seen, and then actually serving that forecast through a live API instead of leaving it in a notebook.

My approach

  • Architecture: started with a naive lag-1 baseline and Linear Regression before touching anything complex, so every later model had a real number to beat, not just an assumption that it would help.
  • Data leakage caught: Random Forest's feature importance was dominated by the customers column. On a real prediction day, the number of customers who will show up is not known in advance, so training on it was leakage. It was dropped and the model retrained without it.
  • Technical decision: the lag and rolling features engineered in SQL were deliberately excluded from the LSTM and GRU inputs. A sequence model's job is to learn temporal dependency on its own, feeding it pre-computed lag features would be redundant and would mask whether the model was actually learning anything.
  • Thoroughness before concluding deep learning underperformed: tested four separate implementations, PyTorch with a manual training loop, PyTorch GRU, TensorFlow with a custom GradientTape loop, and Keras using model.fit with early stopping, rather than ruling out deep learning based on a single attempt.
  • Trade-off: also tested a log transform on the right-skewed sales target for Random Forest, to check whether the skew itself was limiting performance before attributing the result purely to model choice.
  • Deployment choice: Random Forest won on MSE, but the LSTM model was the one containerized and shipped, as a deliberate exercise in understanding what packaging a deep learning model into Docker actually costs. It took noticeably longer to build and produced a noticeably larger image than the classical model would have, a real, first-hand data point on the operational cost of shipping deep learning versus classical ML.
  • MLOps: experiments tracked in MLflow, inference served through FastAPI, containerized with Docker, deployed to AWS EC2 with GitHub Actions CI/CD triggering on every push to main.

Results

  • Random Forest on engineered features reached an MSE of 897, versus 2266 for the best LSTM configuration, a 60% improvement.
  • The customers leakage fix changed which features the model could legitimately rely on before any performance comparison was considered valid.
  • Full CI/CD pipeline deploys a Docker container to EC2 automatically on every push, with MLflow preserving every experiment run for comparison.

What I would do differently

The training script currently loads data using a hardcoded local file path. It works fine on my machine, but it means the script cannot run as-is on anyone else's setup or in a CI pipeline without editing the code first. I would replace this with a relative path or an environment variable, so the same script runs identically whether it is on my laptop, a teammate's machine, or an automated pipeline.