Forecasting with a neural network using NNETAR

Today let's see how a small neural network forecasts a monthly series, and how to check whether it does the job better than a simpler method.

The series is AirPassengers, the monthly total of international airline passengers, in thousands, from January 1949 to December 1960. That is 144 months. The total starts at 112 in January 1949 and reaches 622 in July 1960.

To score a forecast honestly, we split the series in two. The first 120 months (1949 to 1958) are for fitting the model. The last 24 months (1959 and 1960) are the holdout: months that fitting never sees, kept only to compare against the forecast.

The model is nnetar() from the forecast package. It builds rows of lagged values from the series, fits a small neural network to them, and repeats that fit 20 times from different random starting weights. The question for this lesson is whether this model forecasts the series better than ETS, the exponential smoothing method.

The chart below shows the 144 months as a line, and switches to one boxplot per calendar year.

In the line view, every year peaks in summer and dips in winter, and the swing between the peak and the dip widens as the level rises. In the boxplot view, each year's box sits higher than the one before it, and the later boxes are taller than the early ones.

How nnetar turns a series into rows of lagged values

A neural network learns from rows: a target to predict, and inputs to predict it from. A time series is a single column, so nnetar() builds those rows itself, out of lagged values. A lag is the value from an earlier month: lag 1 is the previous month's total, and lag 12 is the total from the same month one year earlier.

The block below creates the 120 training months and the 24 holdout months.

RInteractive R
# Split AirPassengers into 120 training months and 24 holdout months library(forecast) train <- window(AirPassengers, end = c(1958, 12)) test <- window(AirPassengers, start = c(1959, 1)) length(train) #> [1] 120 length(test) #> [1] 24

  

window() cuts a time series between two dates, so train holds 1949 to 1958 and test holds 1959 and 1960.

Next we build the table of rows, with each month's total as the target and its lag 1 and lag 12 values beside it.

RInteractive R
# Build the table of rows: a target month with its lag 1 and lag 12 values air_train <- as.numeric(train) n_train <- length(air_train) lag_table <- data.frame( target = air_train[13:n_train], lag1 = air_train[12:(n_train - 1)], lag12 = air_train[1:(n_train - 12)] ) head(lag_table, 3) #> target lag1 lag12 #> 1 115 118 112 #> 2 126 115 118 #> 3 141 126 132 nrow(lag_table) #> [1] 108

  

Read the first row like this. The target is 115 thousand passengers, the total for January 1950. Its lag 1 is 118, the total for December 1949, and its lag 12 is 112, the total for January 1949.

The table has 108 rows and not 120. The first 12 months have no value from 12 months earlier, so they cannot be a row.

Now we fit the model. set.seed(1) comes first because each network starts from random weights, so the seed makes your numbers match the ones on this page.

RInteractive R
# Fit nnetar to the 120 training months and print the fitted model set.seed(1) nn_fit <- nnetar(train) nn_fit #> Series: train #> Model: NNAR(1,1,2)[12] #> Call: nnetar(y = train) #> #> Average of 20 networks, each of which is #> a 2-2-1 network with 9 weights #> options were - linear output units #> #> sigma^2 estimated as 196.4

  

The header NNAR(1,1,2)[12] reads in four parts:

  • The first 1 is p, the number of ordinary lags used as inputs. Here it is lag 1. nnetar() chose it by fitting a linear autoregression (a regression of each month on the months before it) to the series with its seasonal pattern removed. It kept the number of lags with the lowest AIC, a score that penalises extra lags.
  • The second 1 is P, the number of seasonal lags. Here it is one seasonal lag, lag 12.
  • The 2 is the number of hidden units. By default it is (number of inputs + 1) / 2, rounded, so 2 inputs give 1.5, which rounds to 2.
  • The 12 in square brackets is the seasonal period, 12 months.

So each network has 2 inputs (lag 1 and lag 12), 2 hidden units and 1 output, which is the 2-2-1 in the printout. It has 9 weights, the numbers it learns during fitting, and we will open one up in a moment. The forecast is the average of 20 such networks.

The last line, sigma^2, is the mean squared residual. A residual is the actual value minus the fitted value, so this is the average squared gap between each of the 108 training rows and the value the network fitted for it: 196.4, in squared thousands of passengers.