Forecasting Airline Passenger Numbers with an LSTM Network

Building and training a Long Short-Term Memory network from scratch in PyTorch to forecast monthly airline passenger numbers, and what the results reveal about LSTM capacity and lookback windows.

The dataset: 144 months of airline passengers

The classic airline passengers dataset records the number of international airline passengers (in thousands) for every month from January 1949 to December 1960 — 144 data points in total, with a clear upward trend and yearly seasonal cycles. The task is framed as a regression problem: given the passenger count for recent months, predict the count for the next month.

The single numeric feature is cast to float32 rather than the default float64, halving memory use with negligible loss of precision — a habit that matters more as datasets and models grow, even if it's invisible at 144 rows. The series is then split 67/33 into train and test sets without shuffling, keeping older months (1949-1956) for training and newer months (1957-1960) for testing. This chronological split is non-negotiable for time series: a random shuffle would let the model 'see the future' during training and produce a badly inflated sense of how well it actually forecasts.

From a single time series to training examples

An LSTM needs (input, target) pairs, not a raw sequence. A sliding-window function converts the series into overlapping windows: for a lookback of 1, each training example is one month's passenger count as input (X) and the following month's count as the target (y). Wrapped in PyTorch's TensorDataset and DataLoader with a batch size of 8 and shuffling enabled between epochs, this produces 12 batches of training data from the 95 available windows — small numbers that hint at just how little data 1949-1960 monthly readings actually provide compared to a modern deep learning dataset.

The model: a single LSTM layer plus a linear head

The AirModel architecture is deliberately minimal: one nn.LSTM layer (input size 1, since there's a single feature per timestep) feeding into one nn.Linear layer that maps the LSTM's hidden state down to a single predicted value. The LSTM's batch_first=True setting keeps the tensor shapes in the conventional (batch, sequence, features) order. Trained for 2,000 epochs with the Adam optimiser and mean-squared-error loss, the model's error is checked every 100 epochs on both train and test data using root-mean-squared error (RMSE) — the square root of MSE, chosen because it's expressed in the same units as the original passenger counts, making it directly interpretable.

Results: learning happens, but the gap between train and test is real

With a hidden size of 50, training loss falls by about 90% over 2,000 epochs (from an MSE of roughly 51,000 down to about 5,000), and the loss curve shows the classic shape of successful training: a steep initial drop followed by a long, slow plateau. But the final numbers tell a more nuanced story: train RMSE settles around 71 while test RMSE stays much higher, around 208 — meaning predictions on unseen months are, on average, off by roughly 208,000 passengers. Doubling the hidden size to 100 improves both train and test RMSE somewhat, but the gap between them persists.

That gap is the headline finding of the whole exercise. It doesn't necessarily mean the model is overfitting in the classic sense (train error isn't dramatically lower than test error) — it more likely reflects that a lookback of just 1 previous month gives the model far too little context to capture a 12-month seasonal cycle, so it under-predicts the strength of seasonal swings, especially in the later, higher-volume years of the test period.

What would actually improve this forecast

Several concrete changes follow directly from the RMSE gap: increasing the lookback window to 12 months would let the model see a full seasonal cycle before predicting the next point, rather than extrapolating from a single prior value; normalising the raw passenger counts (with MinMax or StandardScaler) before training would likely make optimisation more stable, since the network is currently learning against raw values in the hundreds; and adding dropout or a second LSTM layer could help the model generalise rather than fit noise in the small training set. None of these fixes are exotic — they're the standard playbook for time-series forecasting with recurrent networks, and this exercise is a clean illustration of why each one exists.

Frequently Asked Questions

Why can't you shuffle time series data before splitting into train and test?

Shuffling would mix future observations into the training set, letting the model implicitly learn from data it should never have seen at prediction time. This is called data leakage, and it produces an unrealistically optimistic performance estimate — the model would appear to forecast well during evaluation but fail in real deployment, where the future genuinely hasn't happened yet.

What does a lookback of 1 actually limit the model to seeing?

With lookback=1, each prediction is based on exactly one previous month's value, with no way to detect a repeating 12-month seasonal pattern. Increasing the lookback to 12 or more gives the LSTM enough history in each training window to potentially recognise 'this looks like last December' patterns, which is essential for a series with strong yearly seasonality like airline passenger counts.

Why use RMSE instead of MSE to report model performance?

MSE is expressed in squared units of the original variable (thousands of passengers, squared), which is hard to interpret intuitively. Taking the square root brings the error back into the original units, so an RMSE of 208 has a direct, meaningful interpretation: predictions are typically off by about 208,000 passengers, a number a non-technical stakeholder can immediately understand.