This commit is contained in:
Ubuntu 2019-06-11 08:33:56 +00:00
Родитель 2ce1bcb6bf 2e690979c8
Коммит 0d31ed1583
11 изменённых файлов: 2920 добавлений и 2103 удалений

786
1_CNN_dilated.ipynb Normal file
Просмотреть файл

@ -0,0 +1,786 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Dilated CNN model\n",
"\n",
"In this notebook, we demonstrate how to:\n",
"- prepare time series data for training a Convolutional Neural Network (CNN) forecasting model\n",
"- get data in the required shape for the keras API\n",
"- implement a CNN model in keras to predict the next step ahead (time *t+1*) in the time series\n",
"- enable early stopping to reduce the likelihood of model overfitting\n",
"- evaluate the model on a test dataset\n",
"\n",
"The data in this example is taken from the GEFCom2014 forecasting competition<sup>1</sup>. It consists of 3 years of hourly electricity load and temperature values between 2012 and 2014. The task is to forecast future values of electricity load. In this example, we show how to forecast one time step ahead, using historical load data only.\n",
"\n",
"<sup>1</sup>Tao Hong, Pierre Pinson, Shu Fan, Hamidreza Zareipour, Alberto Troccoli and Rob J. Hyndman, \"Probabilistic energy forecasting: Global Energy Forecasting Competition 2014 and beyond\", International Journal of Forecasting, vol.32, no.3, pp 896-913, July-September, 2016."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import warnings\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"import pandas as pd\n",
"import datetime as dt\n",
"from collections import UserDict\n",
"from glob import glob\n",
"from IPython.display import Image\n",
"%matplotlib inline\n",
"\n",
"from common.utils import load_data, mape\n",
"\n",
"pd.options.display.float_format = '{:,.2f}'.format\n",
"np.set_printoptions(precision=2)\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Load data into Pandas dataframe"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"energy = load_data('data/')[['load']]\n",
"energy.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Create train, validation and test sets\n",
"\n",
"We separate our dataset into train, validation and test sets. We train the model on the train set. The validation set is used to evaluate the model after each training epoch and ensure that the model is not overfitting the training data. After the model has finished training, we evaluate the model on the test set. We must ensure that the validation set and test set cover a later period in time from the training set, to ensure that the model does not gain from information from future time periods.\n",
"\n",
"We will allocate the period 1st November 2014 to 31st December 2014 to the test set. The period 1st September 2014 to 31st October is allocated to validation set. All other time periods are available for the training set."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"valid_start_dt = '2014-09-01 00:00:00'\n",
"test_start_dt = '2014-11-01 00:00:00'"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"energy[energy.index < valid_start_dt][['load']].rename(columns={'load':'train'}) \\\n",
" .join(energy[(energy.index >=valid_start_dt) & (energy.index < test_start_dt)][['load']] \\\n",
" .rename(columns={'load':'validation'}), how='outer') \\\n",
" .join(energy[test_start_dt:][['load']].rename(columns={'load':'test'}), how='outer') \\\n",
" .plot(y=['train', 'validation', 'test'], figsize=(15, 8), fontsize=12)\n",
"plt.xlabel('timestamp', fontsize=12)\n",
"plt.ylabel('load', fontsize=12)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Data preparation - training set\n",
"\n",
"For this example, we will set *T=6*. This means that the input for each sample is a vector of the prevous 6 hours of the energy load. The choice of *T=6* was arbitrary but should be selected through experimentation.\n",
"\n",
"*HORIZON=1* specifies that we have a forecasting horizon of 1 (*t+1*)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Image('./images/one_step_forecast.png')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"T = 6\n",
"HORIZON = 1"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Our data preparation for the training set will involve the following steps:\n",
"\n",
"1. Filter the original dataset to include only that time period reserved for the training set\n",
"2. Scale the time series such that the values fall within the interval (0, 1)\n",
"3. Shift the values of the time series to create a Pandas dataframe containing all the data for a single training example\n",
"4. Discard any samples with missing values\n",
"5. Transform this Pandas dataframe into a numpy array of shape (samples, features) for input into Keras"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 1. Filter the original dataset to include only that time period reserved for the training set\n",
"Create training set containing only the model features"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"train = energy.copy()[energy.index < valid_start_dt][['load']]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 2. Scale the time series such that the values fall within the interval (0, 1)\n",
"Scale data to be in range (0, 1). This transformation should be calibrated on the training set only. This is to prevent information from the validation or test sets leaking into the training data."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from sklearn.preprocessing import MinMaxScaler\n",
"scaler = MinMaxScaler()\n",
"train['load'] = scaler.fit_transform(train)\n",
"train.head(10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Original vs scaled data:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"energy[energy.index < valid_start_dt][['load']].rename(columns={'load':'original load'}).plot.hist(bins=100, fontsize=12)\n",
"train.rename(columns={'load':'scaled load'}).plot.hist(bins=100, fontsize=12)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 3. Shift the values of the time series to create a Pandas dataframe containing all the data for a single training example\n",
"First, we create the target (*y_t+1*) variable. If we use the convention that the dataframe is indexed on time *t*, we need to shift the *load* variable forward one hour in time. Using the freq parameter we can tell Pandas that the frequency of the time series is hourly. This ensures the shift does not jump over any missing periods in the time series."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"train_shifted = train.copy()\n",
"train_shifted['y_t+1'] = train_shifted['load'].shift(-1, freq='H')\n",
"train_shifted.head(10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We also need to shift the load variable back 6 times to create the input sequence:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for t in range(1, T+1):\n",
" train_shifted[str(T-t)] = train_shifted['load'].shift(T-t, freq='H')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"y_col = 'y_t+1'\n",
"X_cols = ['load_t-5',\n",
" 'load_t-4',\n",
" 'load_t-3',\n",
" 'load_t-2',\n",
" 'load_t-1',\n",
" 'load_t']\n",
"train_shifted.columns = ['load_original']+[y_col]+X_cols\n",
"train_shifted.head(10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 4. Discard any samples with missing values\n",
"Notice how we have missing values for the input sequences for the first 5 samples. We will discard these:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"train_shifted = train_shifted.dropna(how='any')\n",
"train_shifted.head(5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 5. Transform into a numpy arrays of shapes (samples, time steps, features) and (samples,1) for input into Keras\n",
"Now convert the target variable into a numpy array. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"y_train = train_shifted[[y_col]].as_matrix()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We now have a vector for target variable of shape:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"y_train.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The target variable for the first 3 samples looks like:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"y_train[:3]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now convert the inputs into a numpy array with shape `(samples, time steps, features)`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"X_train = train_shifted[X_cols].as_matrix()\n",
"X_train = X_train[... , np.newaxis]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The tensor for the input features now has the shape:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"X_train.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And the first 3 samples looks like:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"X_train[:3]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"train_shifted.head(3)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Data preparation - validation set\n",
"Now we follow a similar process for the validation set. We keep *T* hours from the training set in order to construct initial features."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"look_back_dt = dt.datetime.strptime(valid_start_dt, '%Y-%m-%d %H:%M:%S') - dt.timedelta(hours=T-1)\n",
"valid = energy.copy()[(energy.index >=look_back_dt) & (energy.index < test_start_dt)][['load']]\n",
"valid.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Scale the series using the transformer fitted on the training set:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"valid['load'] = scaler.transform(valid)\n",
"valid.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Prepare validation inputs in the same way as the training set:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"valid_shifted = valid.copy()\n",
"valid_shifted['y+1'] = valid_shifted['load'].shift(-1, freq='H')\n",
"for t in range(1, T+1):\n",
" valid_shifted['load_t-'+str(T-t)] = valid_shifted['load'].shift(T-t, freq='H')\n",
"valid_shifted = valid_shifted.dropna(how='any')\n",
"y_valid = valid_shifted['y+1'].as_matrix()\n",
"X_valid = valid_shifted[['load_t-'+str(T-t) for t in range(1, T+1)]].as_matrix()\n",
"X_valid = X_valid[..., np.newaxis]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"y_valid.shape"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"X_valid.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Implement the Convolutional Neural Network\n",
"We implement the convolutional neural network with the 6 inputs, 3 layers, 5 neurons in each layer, a kernel size of 3 in each layer, and dilation rates of 1, 2 and 4 for each successive layer."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from keras.models import Model, Sequential\n",
"from keras.layers import Conv1D, Dense, Flatten\n",
"from keras.callbacks import EarlyStopping, ModelCheckpoint"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"LATENT_DIM = 5\n",
"KERNEL_SIZE = 2\n",
"BATCH_SIZE = 32\n",
"EPOCHS = 10"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"model = Sequential()\n",
"model.add(Conv1D(LATENT_DIM, kernel_size=KERNEL_SIZE, padding='causal', strides=1, activation='relu', dilation_rate=1, input_shape=(T, 1)))\n",
"model.add(Conv1D(LATENT_DIM, kernel_size=KERNEL_SIZE, padding='causal', strides=1, activation='relu', dilation_rate=2))\n",
"model.add(Conv1D(LATENT_DIM, kernel_size=KERNEL_SIZE, padding='causal', strides=1, activation='relu', dilation_rate=4))\n",
"model.add(Flatten())\n",
"model.add(Dense(HORIZON, activation='linear'))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"model.summary()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Use Adam optimizer and mean squared error as the loss function. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"model.compile(optimizer='Adam', loss='mse')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Early stopping trick"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Image('./images/early_stopping.png')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Specify the early stopping criteria. We **monitor** the validation loss (in this case the mean squared error) on the validation set after each training epoch. If the validation loss has not improved by **min_delta** after **patience** epochs, we stop the training."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"earlystop = EarlyStopping(monitor='val_loss', min_delta=0, patience=5)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"best_val = ModelCheckpoint('model_{epoch:02d}.h5', save_best_only=True, mode='min', period=1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"history = model.fit(X_train,\n",
" y_train,\n",
" batch_size=BATCH_SIZE,\n",
" epochs=EPOCHS,\n",
" validation_data=(X_valid, y_valid),\n",
" callbacks=[earlystop, best_val],\n",
" verbose=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Load the model with the smallest mape"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"best_epoch = np.argmin(np.array(history.history['val_loss']))+1\n",
"model.load_weights(\"model_{:02d}.h5\".format(best_epoch))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"plot training and validation losses"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plot_df = pd.DataFrame.from_dict({'train_loss':history.history['loss'], 'val_loss':history.history['val_loss']})\n",
"plot_df.plot(logy=True, figsize=(10,10), fontsize=12)\n",
"plt.xlabel('epoch', fontsize=12)\n",
"plt.ylabel('loss', fontsize=12)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Evaluate the model\n",
"Create the test set"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"look_back_dt = dt.datetime.strptime(test_start_dt, '%Y-%m-%d %H:%M:%S') - dt.timedelta(hours=T-1)\n",
"test = energy.copy()[test_start_dt:][['load']]\n",
"test.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Scale the test data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"test['load'] = scaler.transform(test)\n",
"test.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Create test set features"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"test_shifted = test.copy()\n",
"test_shifted['y_t+1'] = test_shifted['load'].shift(-1, freq='H')\n",
"for t in range(1, T+1):\n",
" test_shifted['load_t-'+str(T-t)] = test_shifted['load'].shift(T-t, freq='H')\n",
"test_shifted = test_shifted.dropna(how='any')\n",
"y_test = test_shifted['y_t+1'].as_matrix()\n",
"X_test = test_shifted[['load_t-'+str(T-t) for t in range(1, T+1)]].as_matrix()\n",
"X_test = X_test[... , np.newaxis]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Make predictions on test set"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"predictions = model.predict(X_test)\n",
"predictions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Compare predictions to actual load"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"eval_df = pd.DataFrame(predictions, columns=['t+'+str(t) for t in range(1, HORIZON+1)])\n",
"eval_df['timestamp'] = test_shifted.index\n",
"eval_df = pd.melt(eval_df, id_vars='timestamp', value_name='prediction', var_name='h')\n",
"eval_df['actual'] = np.transpose(y_test).ravel()\n",
"eval_df[['prediction', 'actual']] = scaler.inverse_transform(eval_df[['prediction', 'actual']])\n",
"eval_df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Compute the mean absolute percentage error over all predictions"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"mape(eval_df['prediction'], eval_df['actual'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Plot the predictions vs the actuals for the first week of the test set"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"eval_df[eval_df.timestamp<'2014-11-08'].plot(x='timestamp', y=['prediction', 'actual'], style=['r', 'b'], figsize=(15, 8))\n",
"plt.xlabel('timestamp', fontsize=12)\n",
"plt.ylabel('load', fontsize=12)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"clean up model files"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for m in glob('model_*.h5'):\n",
" os.remove(m)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "dnntutorial",
"language": "python",
"name": "dnntutorial"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

Просмотреть файл

@ -1405,7 +1405,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"display_name": "Python 3.5",
"language": "python",
"name": "python3"
},
@ -1419,7 +1419,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.6"
"version": "3.5.5"
}
},
"nbformat": 4,

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -855,7 +855,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"display_name": "Python 3.5",
"language": "python",
"name": "python3"
},
@ -869,7 +869,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.2"
"version": "3.5.5"
}
},
"nbformat": 4,

Просмотреть файл

Просмотреть файл

@ -1,4 +1,4 @@
# Deep Neural Networks for Time Series Forecasting
# Deep Learning for Time Series Forecasting
A collection of examples for using DNNs for time series forecasting with Keras. The examples include:

Просмотреть файл

@ -935,7 +935,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"display_name": "Python 3.5",
"language": "python",
"name": "python3"
},
@ -949,7 +949,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.2"
"version": "3.5.5"
}
},
"nbformat": 4,

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -1056,7 +1056,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"display_name": "Python 3.5",
"language": "python",
"name": "python3"
},
@ -1070,7 +1070,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.2"
"version": "3.5.5"
}
},
"nbformat": 4,