Univariate Time Series Prediction with TensorFlow

Univariate Time Series Prediction with TensorFlow
Photo by Vincent Yuan @USA / Unsplash

1 Introduction

This article will use TensorFlow to solve time series prediction problems. There is a very detailed but lengthy tutorial on the TensorFlow official website, so this article will focus on the core content of univariate time series prediction in an easy-to-understand way.

2 Bitcoin Price Dataset

2.1 Get Data

This article uses Bitcoin historical price data (from October 2013 to May 2021) for prediction. Please note that this article does not constitute investment advice!

!wget https://raw.githubusercontent.com/mrdbourke/tensorflow-deep-learning/main/extras/BTC_USD_2013-10-01_2021-05-18-CoinDesk.csv

import pandas as pd
import matplotlib.pyplot as plt
import os
import tensorflow as tf
from tensorflow.keras as layers

df = pd.read_csv("/content/BTC_USD_2013-10-01_2021-05-18-CoinDesk.csv",
                 parse_dates=["Date"],
                 index_col=["Date"]) # Parse the date column

df.tail()

Returns:

Date Currency Closing Price (USD) 24h Open (USD) 24h High (USD) 24h Low (USD)
2021-05-14 BTC 49764.132082 49596.778891 51448.798576 46294.720180
2021-05-15 BTC 50032.693137 49717.354353 51578.312545 48944.346536
2021-05-16 BTC 47885.625255 49926.035067 50690.802950 47005.102292
2021-05-17 BTC 45604.615754 46805.537852 49670.414174 43868.638969
2021-05-18 BTC 43144.471291 46439.336570 46622.853437 42102.346430

Take the closing price for prediction:

bitcoin_prices = pd.DataFrame(df["Closing Price (USD)"]).rename({"Closing Price (USD)":"Price"},axis=1)
bitcoin_prices.head()
Date Price
2013-10-01 123.65499
2013-10-02 125.45500
2013-10-03 108.58483
2013-10-04 118.67466
2013-10-05 121.33866

View the bitcoin price trend with a chart:

plt.plot(bitcoin_prices["Price"])
plt.ylabel("Bitcoin price")
plt.xlabel("Date")
Bitcoin Price Trend

2.2 Create Time Windows

The expected data format is [0,1,2,3,4,5,6] -> [7], that is, use the prices from the past seven days to predict the price for the next day. Here, use the window division provided by TensorFlow, timeseries_dataset_from_array.


timesteps = bitcoin_prices.index.to_numpy()
prices = bitcoin_prices["Price"].to_numpy()

HORIZON  = 1 # Predict the price for the next day
WINDOW_SIZE = 7 # Use prices from the previous 7 days

input_data = prices[:-HORIZON]
targets = prices[WINDOW_SIZE:]
dataset = tf.keras.preprocessing.timeseries_dataset_from_array(
    input_data, targets, sequence_length=WINDOW_SIZE)
for batch in dataset:
  inputs, targets = batch
  assert np.array_equal(inputs[0], prices[:WINDOW_SIZE])   
  assert np.array_equal(targets[0], prices[WINDOW_SIZE])  

  print(f"First Input:{inputs[0]}, Target:{targets[0]}")
  print(f"Second Input:{inputs[1]}, Target:{targets[1]}")

  break

Returns as follows, the data has been correctly transformed as expected.

First Input:[123.65499 125.455   108.58483 118.67466 121.33866 120.65533 121.795  ], Target:123.033
Second Input:[125.455   108.58483 118.67466 121.33866 120.65533 121.795   123.033  ], Target:124.049

2.3 Split Data

Use time to split the training set and validation set. No test set is included as examples only. In actual use, one can operate according to needs.

# Because tf.keras.preprocessing.timeseries_dataset_from_array returns a batched dataset, unbatch first for easy data splitting
dataset = dataset.unbatch()

test_split = 0.2
split_index = int(len(list(dataset)) * (1-test_split))

# After splitting, split the batch again, add one dimension, otherwise it does not meet the data dimension requirements of the model
train_dataset = dataset.take(split_index).batch(batch_size=32)
test_dataset = dataset.skip(split_index).batch(batch_size=32)

3 Modeling

Since this article does not pursue the ultimate prediction accuracy, only a fully connected layer is used to build the model, the code is as follows:

tf.random.set_seed(42)
tf.keras.backend.clear_session()

# 1. Build the model
model = tf.keras.models.Sequential(
[
 layers.Input(WINDOW_SIZE),
 layers.Dense(128, activation="relu"),
 layers.Dense(HORIZON, activation="linear")
]
, name="model_dense_base")

# 2. Compile the model
model.compile(loss='mae',
                optimizer=tf.keras.optimizers.Adam(),
                metrics=['mae'])


# Create callback to save the best performing model checkpoint
def create_model_checkpoint(model_name, save_path="model_checkpoint"):
  return tf.keras.callbacks.ModelCheckpoint(filepath=os.path.join(save_path, model_name),
                                            verbose=0,
                                            save_best_only=True)

# 3. Train the model
model.fit( train_dataset,
            epochs=100,
            verbose=0,
            validation_data=test_dataset,
            callbacks=[create_model_checkpoint(model_name=model.name)]
            )

Load the best performing model back for evaluation:

model = tf.keras.models.load_model("model_checkpoint/model_dense_base")
model.evaluate(test_dataset)

The result shows the average absolute error (MAE), indicating that the predicted price is more than 700 US dollars different from the real price on average.

18/18 [==============================] - 1s 6ms/step - loss: 759.4327 - mae: 759.4327
[759.4326782226562, 759.4326782226562]

Considering that the model structure is very simple, there is room for improvement in the results, and it can be predicted more accurately by optimizing according to this process.

4 Summary

This article did a benchmark for univariate time series prediction tasks, in which the TensorFlow tf.keras.preprocessing.timeseries_dataset_from_array API simplified a lot of work on processing time windows. In the future, we will continue to discuss TensorFlow's prediction of time series tasks.