TF 2.0 - 時間序列預測入門

TF 2.0 - 時間序列預測入門

本文鏈接:https://blog.lucien.ink/archives/483/

最近 Google 正式將 TensorFlow 2.0 作爲默認 TensorFlow 版本了,作爲一名初學者,決定用相對易用的新版的 TensorFlow 來進行實踐。

在接下來的內容中,我將記錄我用 LSTM 和 Beijing PM2.5 Data Set 來進行時間序列預測的過程。

因爲 ipynb 文件裏都包含圖片,所以在文章裏就不上圖了哈。

0. 環境

Package Version
tensorflow 2.0.0
numpy 1.17.3
pandas 0.25.3
matplotlib 3.1.1
sklearn 0.21.3

1. 過程

1.1 數據集

Beijing PM2.5 Data Set 源自位於北京的美國大使館在 2010 ~ 2014 年每小時採集的天氣及空氣污染指數。
  數據集包括日期、PM2.5 濃度、露點、溫度、風向、風速、累積小時雪量和累積小時雨量。

原始數據中完整的特徵如下:

No 編號
year 年
month 月
day 日
hour 小時
pm2.5 PM2.5濃度
DEWP 露點
TEMP 溫度
PRES 大氣壓
cbwd 風向
lws 風速
ls 累積雪量
lr 累積雨量

可以用此數據集搭建預測 PM 2.5 的模型,利用前 x 個小時來預測後 y 個小時的 PM 2.5 數值。

from TensorFlow import random
import TensorFlow.keras as keras
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import preprocessing
from sklearn.metrics import r2_score

# 固定隨機種子
np.random.seed(10086)
random.set_seed(10010)

csv_data = keras.utils.get_file("PRSA_data.csv", "https://archive.ics.uci.edu/ml/machine-learning-databases/00381/PRSA_data_2010.1.1-2014.12.31.csv")

raw_df = pd.read_csv(csv_data)

raw_df.head()

1.2 數據預處理

1.2.1 刪除時間戳

目前的我認爲,時間戳對於連續的時間序列預測來說並不重要,所以在這裏先刪掉。

# 刪除時間戳
df = raw_df.drop(["No", "year", "month", "day", "hour"], axis=1, inplace=False)

print(df.shape)
df.head()

1.2.2 刪除 nan

pm2.5 列有的值是空值,由於數量不多,所以考慮直接將包括 nan 的行刪掉。

# 刪除 pm2.5 列的 nan 值
df = df[pd.notna(df["pm2.5"])]

print(df.shape)
df.head()

1.2.3 打印當前狀態的數據

# 查看每列的 unique
for i in range(df.shape[1]):
    if df.columns[i] in ["pm2.5", "TEMP", "DEWP", "PRES"]:
        continue
    print("{}: {}".format(df.columns[i], df[df.columns[i]].unique()))

# 畫個圖
columns = ["pm2.5", "DEWP", "TEMP", "PRES", "Iws", "Is", "Ir"]

plt.figure(figsize=(15, 15))
for i, each in enumerate(columns):
    plt.subplot(len(columns), 1, i + 1)
    plt.plot(df[each])
    plt.title(each, y=0.5, loc="right")  # center, left, right

plt.show()

1.2.4 將非數值類型的 label 轉化爲數值類型

# 將 label id 化
def label_fit_transform(data_frame, col_name):
    data_frame[col_name] = preprocessing.LabelEncoder().fit_transform(data_frame[col_name])
    return data_frame

label_fit_transform(df, "cbwd").head()

1.2.5 將數值歸一化

歸一化之後模型收斂會快一些,效果大概率會好一些,從感性角度去理解的話,我覺得 知乎上的這個回答 說的非常好。

def standardization(data_frame):
    buffer = data_frame.copy()
    min_max_scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))
    standard_values = min_max_scaler.fit_transform(buffer)
    for i, col_name in enumerate(buffer.columns):
        buffer[col_name] = standard_values[:, i]

    return buffer

standardization(df).head()

1.2.6 將時間序列轉化爲有監督訓練數據

原始的時間序列並不能直接 feed 給模型,需要處理爲 input -> label 形式的數據纔可以。

# 轉化爲監督序列
def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):
    """
    Frame a time series as a supervised learning dataset.
    Arguments:
        data: Sequence of observations as a list or NumPy array.
        n_in: Number of lag observations as input (X).
        n_out: Number of observations as output (y).
        dropnan: Boolean whether or not to drop rows with NaN values.
    Returns:
        Pandas DataFrame of series framed for supervised learning.
    """
    from pandas import DataFrame, concat

    n_vars = 1 if type(data) is list else data.shape[1]
    df = DataFrame(data)
    cols, names = list(), list()
    
    # input sequence (t-n, ... t-1)
    for i in range(n_in, 0, -1):
        cols.append(df.shift(i))
        names += [('%s(t-%d)' % (data.columns[j], i)) for j in range(n_vars)]

    # forecast sequence (t, t+1, ... t+n)
    for i in range(0, n_out):
        cols.append(df.shift(-i))
        if i == 0:
            names += [('%s(t)' % (data.columns[j])) for j in range(n_vars)]
        else:
            names += [('%s(t+%d)' % (data.coumns[j], i)) for j in range(n_vars)]

    # put it all together
    agg = concat(cols, axis=1)
    agg.columns = names

    # drop rows with NaN values
    if dropnan:
        agg.dropna(inplace=True)
    return agg

# 通過過去 2 小時的數據來預測未來 1 小時的數據
look_back = 2
predict_forward = 1

# standard (supervised) data frame
sdf = series_to_supervised(
    standardization(
        label_fit_transform(df, "cbwd")), look_back, predict_forward).drop(
        [
         "DEWP(t)", "TEMP(t)", "PRES(t)", "cbwd(t)", "Iws(t)", "Is(t)", "Ir(t)"
         ], axis=1, inplace=False).astype('float32')

sdf.head()

sdf.info()

1.2.7 劃分數據集

從網上了解到,traintrainvalidvalidtesttest 三個集合的比例一般爲 6:2:26:2:2

# train, valid, test 6:2:2 劃分
total = sdf.shape[0]
split_point = [total * 60 // 100, total * 80 // 100]

print("total = {}, split_point = {}".format(total, split_point))

def transform(values):
    return values.reshape(values.shape[0], 1, values.shape[1])

train_data = sdf[:split_point[0]].values

valid_data = sdf[split_point[0]: split_point[1]].values

test_data = sdf[split_point[1]: ].values

print("train_data.shape = {}, valid_data.shape = {}, test_data.shape = {}".format(
    train_data.shape, valid_data.shape, test_data.shape))

train_x, train_y = transform(train_data[:, : -1]), train_data[:, -1]

valid_x, valid_y = transform(valid_data[:, : -1]), valid_data[:, -1]

test_x, test_y = transform(test_data[:, : -1]), test_data[:, -1]

print("train_x.shape = {}, train_y = {}".format(train_x.shape, train_y.shape))
print("valid_x.shape = {}, valid_y = {}".format(valid_x.shape, valid_y.shape))
print("test_x.shape = {}, test_y = {}".format(test_x.shape, test_y.shape))

1.3 模型

1.3.1 構建網絡

model = keras.Sequential()
model.add(keras.layers.LSTM(64, input_shape=(train_x.shape[1], train_x.shape[2])))
model.add(keras.layers.Dense(1))

model.compile(loss="mae", optimizer="adam")

1.3.2 訓練並記錄歷史

history = model.fit(train_x,
                    train_y,
                    validation_data=(valid_x, valid_y),
                    epochs=32,
                    batch_size=32,
                    verbose=1,
                    shuffle=False)

1.4 模型效果評估

1.4.1 loss 圖

先畫一下 train 和 valid 數據集的 loss 圖,看起來沒有 overfitting。

plt.plot(history.history["loss"], label="train loss")
plt.plot(history.history["val_loss"], label="valid loss")
plt.legend()
plt.show()

1.4.2 在 test 數據集上進行評估

1.4.2.1 loss
# test 集上的 loss
model.evaluate(test_x, test_y, verbose=0)

看起來很低的樣子。

1.4.2.2 將預測值和真值進行比較
1.4.2.2.1 獲取預測結果
prediction = model.predict(test_x)
1.4.2.2.2 對預測出來的結果進行反歸一化

由於用的是 MinMaxScaler,所以直接按照公式逆着計算一下就可以。

max_value = np.max(df["pm2.5"])
min_value = np.min(df["pm2.5"])
prediction = prediction[:, 0] * (max_value - min_value) + min_value
1.4.2.2.3 評估擬合能力
# 因爲 look_back 處理時會去掉值爲 nan 的 input,所以這裏要加上 look_back
expectation = df["pm2.5"][split_point[1] + look_back: ].values

print("prediction's shape = {}, expectation's shape = {}".format(prediction.shape, expectation.shape))

# 計算一下 R-square
print(r2_score(expectation, prediction, multioutput="raw_values"))

plt.figure(figsize=(30, 17))
plt.plot(expectation, label="expectation")
plt.plot(prediction, label="predict", color="yellow", alpha=0.5)
plt.legend()
plt.show()

2. All in One

ipynb 文件可在 我的 GitHub 或者 Google CoLab 看到。

py 文件可在 pasteme.cn/21403 查看。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章