基於LSTM實現單變量預測(Tensorflow2實現)

這篇文章的內容來自B站UP主唐國樑Tommy老師的視頻

TensorFlow 2.0 基於LSTM單變量預測_電力消耗案例: https://www.bilibili.com/video/BV1f5411K7qD

案例實現思路:

  • 模塊導入

  • 加載數據集、預處理

  • 特徵工程

  • 構建模型

  • 模型編譯、訓練、驗證

  • 模型測試

  • 結果可視化

1、模塊導入

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import r2_score

import tensorflow as tf
from tensorflow.keras import Sequential, layers, utils

import warnings
warnings.filterwarnings('ignore')

2、加載數據集、預處理

# 讀取數據集
dataset = pd.read_csv('DOM_hourly.csv')
# 顯示shape   
dataset.shape

(116189, 2)

# 默認顯示前5行
dataset.head()
Datetime DOM_MW
0 2005-12-31 01:00:00 9389.0
1 2005-12-31 02:00:00 9070.0
2 2005-12-31 03:00:00 9001.0
3 2005-12-31 04:00:00 9042.0
4 2005-12-31 05:00:00 9132.0
# 顯示數據描述
dataset.describe()
DOM_MW
count 116189.000000
mean 10949.203625
std 2413.946569
min 1253.000000
25% 9322.000000
50% 10501.000000
75% 12378.000000
max 21651.000000
# 將字段Datetime數據類型轉換爲日期類型
dataset['Datetime'] = pd.to_datetime(dataset['Datetime'], format="%Y-%m-%d %H:%M:%S")
# 將字段Datetime設置爲索引列
# 目的:後續基於索引來進行數據集的切分
dataset.index = dataset.Datetime
# 將原始的Datetime字段列刪除
dataset.drop(columns=['datetime'], axis=1, inplace=True)
# 顯示默認前5行
dataset.head()
Datetime DOM_MW
2005-12-31 01:00:00 9389.0
2005-12-31 02:00:00 9070.0
2005-12-31 03:00:00 9001.0
2005-12-31 04:00:00 9042.0
2005-12-31 05:00:00 9132.0
# 可視化顯示DOM_MW的數據分佈情況

dataset['DOM_MW'].plot(figsize=(16,8))
plt.show()

數據分佈情況

# 數據進行歸一化
# 均值爲0,標準差爲1
scaler = MinMaxScaler()
# reshape(-1, 1) 第一個-1不管多少行,第二個1只是1列
dataset['DOM_MW'] = scaler.fit_transform(dataset['DOM_MW'].values.reshape(-1, 1))
# 可視化顯示歸一化後的數據分佈情況

dataset['DOM_MW'].plot(figsize=(16,8))
plt.show()

3

3、特徵工程

# 功能函數:構造特徵數據集和標籤集
def create_new_dataset(dataset, seq_len = 12):
    '''基於原始數據集構造新的序列特徵數據集
    Params:
        dataset : 原始數據集
        seq_len : 序列長度(時間跨度) 滑動窗口
    
    Returns:
        X, y
    '''
    X = [] # 初始特徵數據集爲空列表
    y = [] # 初始標籤數據集爲空列表
    
    start = 0 # 初始位置
    end = dataset.shape[0] - seq_len # 截止位置
    
    for i in range(start, end): # for循環構造特徵數據集
        sample = dataset[i : i+seq_len] # 基於時間跨度seq_len創建樣本
        label = dataset[i+seq_len] # 創建sample對應的標籤
        X.append(sample) # 保存sample
        y.append(label) # 保存label
    
    # 返回特徵數據集和標籤集
    return np.array(X), np.array(y)
    
    
# 功能函數:基於新的特徵的數據集和標籤集,切分:X_train, X_test
# 千萬不能打亂數據 要有時序
def split_dataset(X, y, train_ratio=0.8):
    '''基於X和y,切分爲train和test
    Params:
        X : 特徵數據集
        y : 標籤數據集
        train_ratio : 訓練集佔X的比例
    
    Returns:
        X_train, X_test, y_train, y_test
    '''
    X_len = len(X) # 特徵數據集X的樣本數量
    train_data_len = int(X_len * train_ratio) # 訓練集的樣本數量
    
    X_train = X[:train_data_len] # 訓練集
    y_train = y[:train_data_len] # 訓練標籤集
    
    X_test = X[train_data_len:] # 測試集
    y_test = y[train_data_len:] # 測試集標籤集
    
    # 返回值
    return X_train, X_test, y_train, y_test

# 功能函數:基於新的X_train, X_test, y_train, y_test創建批數據(batch dataset)
def create_batch_data(X, y, batch_size=32, data_type=1):
    '''基於訓練集和測試集,創建批數據
    Params:
        X : 特徵數據集
        y : 標籤數據集
        batch_size : batch的大小,即一個數據塊裏面有幾個樣本
        data_type : 數據集類型(測試集表示1,訓練集表示2)
   
    Returns:
        train_batch_data 或 test_batch_data
    '''
    if data_type == 1: # 測試集
        dataset = tf.data.Dataset.from_tensor_slices((tf.constant(X), tf.constant(y))) # 封裝X和y,成爲tensor類型 
        test_batch_data = dataset.batch(batch_size) # 構造批數據
        # 返回
        return test_batch_data
    else: # 訓練集
        dataset = tf.data.Dataset.from_tensor_slices((tf.constant(X), tf.constant(y))) # 封裝X和y,一一對應,成爲tensor類型
        # 訓練集數據量較大,可以加載到內存中去 打亂1000 獲得更好地泛化性能
        train_batch_data = dataset.cache().shuffle(1000).batch(batch_size) # 構造批數據
        # 返回
        return train_batch_data
# ① 原始數據集
dataset_original = dataset

# ② 構造特徵數據集和標籤集,seq_len序列長度爲12小時
SEQ_LEN = 12 # 序列長度
X, y = create_new_dataset(dataset_original.values, seq_len = SEQ_LEN)

# ③ 數據集切分
X_train, X_test, y_train, y_test = split_dataset(X, y, train_ratio=0.9)

# 訓練集和數據集的形狀
# X_train.shape (104559, 12, 1)
# y_train.shape (104559, 1)
# X_test.shape  (11618, 12, 1)
# y_test.shape  (11618, 1)

# ④ 基於新的X_train, X_test, y_train, y_test創建批數據(batch dataset)
# 測試批數據
test_batch_dataset = create_batch_data(X_test, y_test, batch_size=256, data_type=1)

# 訓練批數據
train_batch_dataset = create_batch_data(X_train, y_train, batch_size=256, data_type=2)

4、構建模型

model = Sequential([
    # SEQ_LEN = 20 
    layers.LSTM(8, input_shape=(SEQ_LEN, 1)),
    #全連接層只有一個預測值
    layers.Dense(1)
])

# 定義 checkpoint,保存權重文件
file_path = "best_checkpoint.hdf5"
# 最小損失權重
checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(filepath=file_path, 
                                                         monitor='loss', 
                                                         mode='min', 
                                                         save_best_only=True,
                                                         save_weights_only=True)

5、模型編譯、訓練、驗證

# 模型編譯
model.compile(optimizer='adam', loss="mae")

# 模型訓練
history = model.fit(train_batch_dataset,
          epochs=20,
          validation_data=test_batch_dataset,
          callbacks=[checkpoint_callback])

# 顯示 train loss 和 val loss
plt.figure(figsize=(16,8))
plt.plot(history.history['loss'], label='train loss')
plt.plot(history.history['val_loss'], label='val loss')
plt.title("LOSS")
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.legend(loc='best')
plt.show()

# 模型驗證
test_pred = model.predict(X_test, verbose=1)

# 計算r2
score = r2_score(y_test, test_pred)
print("r^2 的值: ", score)

r^2 的值: 0.9615476372915489

# 繪製模型驗證結果

plt.figure(figsize=(16,8))
plt.plot(y_test, label="True label")
plt.plot(test_pred, label="Pred label")
plt.title("True vs Pred")
plt.legend(loc='best')
plt.show()

# 繪製test中前100個點的真值與預測值

y_true = y_test[:1000]
y_pred = test_pred[:1000]
plt.figure(figsize=(16, 8))
plt.plot(y_true, marker='o', color='red')
plt.plot(y_pred, marker='*', color='blue')
plt.show()

6、模型測試

預測1個樣本

# 選擇test中的最後一個樣本
sample = X_test[-1] # (12, 1)
sample = sample.reshape(1, sample.shape[0], 1) # (1, 12, 1)

# 模型預測
sample_pred = model.predict(sample)

array([[0.7783012]], dtype=float32)

預測後續20個點的值

ture_data = X_test[-1] # 真實test的最後20個數據點 (12, 1)

array([[0.73982743],
[0.72595352],
[0.70825571],
[0.69791156],
[0.70619669],
[0.74360231],
[0.81125601],
[0.83321894],
[0.84150407],
[0.84880871],
[0.83606236],
[0.81189332]])

def predict_next(model, sample, epoch=20):
    temp1 = list(sample[:,0])
    for i in range(epoch):
        sample = sample.reshape(1, SEQ_LEN, 1)
        pred = model.predict(sample)
        value = pred.tolist()[0][0]
        temp1.append(value)
        sample = np.array(temp1[i+1 : i+SEQ_LEN+1])
    return temp1

preds = predict_next(model, ture_data, 20)
plt.figure(figsize=(12,6))
plt.plot(preds, color='yellow', label='Prediction')
plt.plot(ture_data, color='blue', label='Truth')
plt.xlabel("Epochs")
plt.ylabel("Value")
plt.legend(loc='best')
plt.show()

案例實現思路:

  • 模塊導入

  • 加載數據集、預處理

  • 特徵工程

  • 構建模型

  • 模型編譯、訓練、驗證

  • 模型測試

  • 結果可視化

1、模塊導入

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import r2_score

import tensorflow as tf
from tensorflow.keras import Sequential, layers, utils

import warnings
warnings.filterwarnings('ignore')

2、加載數據集、預處理

# 讀取數據集
dataset = pd.read_csv('DOM_hourly.csv')
# 顯示shape   
dataset.shape

(116189, 2)

# 默認顯示前5行
dataset.head()
Datetime DOM_MW
0 2005-12-31 01:00:00 9389.0
1 2005-12-31 02:00:00 9070.0
2 2005-12-31 03:00:00 9001.0
3 2005-12-31 04:00:00 9042.0
4 2005-12-31 05:00:00 9132.0
# 顯示數據描述
dataset.describe()
DOM_MW
count 116189.000000
mean 10949.203625
std 2413.946569
min 1253.000000
25% 9322.000000
50% 10501.000000
75% 12378.000000
max 21651.000000
# 將字段Datetime數據類型轉換爲日期類型
dataset['Datetime'] = pd.to_datetime(dataset['Datetime'], format="%Y-%m-%d %H:%M:%S")
# 將字段Datetime設置爲索引列
# 目的:後續基於索引來進行數據集的切分
dataset.index = dataset.Datetime
# 將原始的Datetime字段列刪除
dataset.drop(columns=['datetime'], axis=1, inplace=True)
# 顯示默認前5行
dataset.head()
Datetime DOM_MW
2005-12-31 01:00:00 9389.0
2005-12-31 02:00:00 9070.0
2005-12-31 03:00:00 9001.0
2005-12-31 04:00:00 9042.0
2005-12-31 05:00:00 9132.0
# 可視化顯示DOM_MW的數據分佈情況

dataset['DOM_MW'].plot(figsize=(16,8))
plt.show()

數據分佈情況

# 數據進行歸一化
# 均值爲0,標準差爲1
scaler = MinMaxScaler()
# reshape(-1, 1) 第一個-1不管多少行,第二個1只是1列
dataset['DOM_MW'] = scaler.fit_transform(dataset['DOM_MW'].values.reshape(-1, 1))
# 可視化顯示歸一化後的數據分佈情況

dataset['DOM_MW'].plot(figsize=(16,8))
plt.show()

3

3、特徵工程

# 功能函數:構造特徵數據集和標籤集
def create_new_dataset(dataset, seq_len = 12):
    '''基於原始數據集構造新的序列特徵數據集
    Params:
        dataset : 原始數據集
        seq_len : 序列長度(時間跨度) 滑動窗口
    
    Returns:
        X, y
    '''
    X = [] # 初始特徵數據集爲空列表
    y = [] # 初始標籤數據集爲空列表
    
    start = 0 # 初始位置
    end = dataset.shape[0] - seq_len # 截止位置
    
    for i in range(start, end): # for循環構造特徵數據集
        sample = dataset[i : i+seq_len] # 基於時間跨度seq_len創建樣本
        label = dataset[i+seq_len] # 創建sample對應的標籤
        X.append(sample) # 保存sample
        y.append(label) # 保存label
    
    # 返回特徵數據集和標籤集
    return np.array(X), np.array(y)
    
    
# 功能函數:基於新的特徵的數據集和標籤集,切分:X_train, X_test
# 千萬不能打亂數據 要有時序
def split_dataset(X, y, train_ratio=0.8):
    '''基於X和y,切分爲train和test
    Params:
        X : 特徵數據集
        y : 標籤數據集
        train_ratio : 訓練集佔X的比例
    
    Returns:
        X_train, X_test, y_train, y_test
    '''
    X_len = len(X) # 特徵數據集X的樣本數量
    train_data_len = int(X_len * train_ratio) # 訓練集的樣本數量
    
    X_train = X[:train_data_len] # 訓練集
    y_train = y[:train_data_len] # 訓練標籤集
    
    X_test = X[train_data_len:] # 測試集
    y_test = y[train_data_len:] # 測試集標籤集
    
    # 返回值
    return X_train, X_test, y_train, y_test

# 功能函數:基於新的X_train, X_test, y_train, y_test創建批數據(batch dataset)
def create_batch_data(X, y, batch_size=32, data_type=1):
    '''基於訓練集和測試集,創建批數據
    Params:
        X : 特徵數據集
        y : 標籤數據集
        batch_size : batch的大小,即一個數據塊裏面有幾個樣本
        data_type : 數據集類型(測試集表示1,訓練集表示2)
   
    Returns:
        train_batch_data 或 test_batch_data
    '''
    if data_type == 1: # 測試集
        dataset = tf.data.Dataset.from_tensor_slices((tf.constant(X), tf.constant(y))) # 封裝X和y,成爲tensor類型 
        test_batch_data = dataset.batch(batch_size) # 構造批數據
        # 返回
        return test_batch_data
    else: # 訓練集
        dataset = tf.data.Dataset.from_tensor_slices((tf.constant(X), tf.constant(y))) # 封裝X和y,一一對應,成爲tensor類型
        # 訓練集數據量較大,可以加載到內存中去 打亂1000 獲得更好地泛化性能
        train_batch_data = dataset.cache().shuffle(1000).batch(batch_size) # 構造批數據
        # 返回
        return train_batch_data
# ① 原始數據集
dataset_original = dataset

# ② 構造特徵數據集和標籤集,seq_len序列長度爲12小時
SEQ_LEN = 12 # 序列長度
X, y = create_new_dataset(dataset_original.values, seq_len = SEQ_LEN)

# ③ 數據集切分
X_train, X_test, y_train, y_test = split_dataset(X, y, train_ratio=0.9)

# 訓練集和數據集的形狀
# X_train.shape (104559, 12, 1)
# y_train.shape (104559, 1)
# X_test.shape  (11618, 12, 1)
# y_test.shape  (11618, 1)

# ④ 基於新的X_train, X_test, y_train, y_test創建批數據(batch dataset)
# 測試批數據
test_batch_dataset = create_batch_data(X_test, y_test, batch_size=256, data_type=1)

# 訓練批數據
train_batch_dataset = create_batch_data(X_train, y_train, batch_size=256, data_type=2)

4、構建模型

model = Sequential([
    # SEQ_LEN = 20 
    layers.LSTM(8, input_shape=(SEQ_LEN, 1)),
    #全連接層只有一個預測值
    layers.Dense(1)
])

# 定義 checkpoint,保存權重文件
file_path = "best_checkpoint.hdf5"
# 最小損失權重
checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(filepath=file_path, 
                                                         monitor='loss', 
                                                         mode='min', 
                                                         save_best_only=True,
                                                         save_weights_only=True)

5、模型編譯、訓練、驗證

# 模型編譯
model.compile(optimizer='adam', loss="mae")

# 模型訓練
history = model.fit(train_batch_dataset,
          epochs=20,
          validation_data=test_batch_dataset,
          callbacks=[checkpoint_callback])

# 顯示 train loss 和 val loss
plt.figure(figsize=(16,8))
plt.plot(history.history['loss'], label='train loss')
plt.plot(history.history['val_loss'], label='val loss')
plt.title("LOSS")
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.legend(loc='best')
plt.show()

# 模型驗證
test_pred = model.predict(X_test, verbose=1)

# 計算r2
score = r2_score(y_test, test_pred)
print("r^2 的值: ", score)

r^2 的值: 0.9615476372915489

# 繪製模型驗證結果

plt.figure(figsize=(16,8))
plt.plot(y_test, label="True label")
plt.plot(test_pred, label="Pred label")
plt.title("True vs Pred")
plt.legend(loc='best')
plt.show()

# 繪製test中前100個點的真值與預測值

y_true = y_test[:1000]
y_pred = test_pred[:1000]
plt.figure(figsize=(16, 8))
plt.plot(y_true, marker='o', color='red')
plt.plot(y_pred, marker='*', color='blue')
plt.show()

6、模型測試

預測1個樣本

# 選擇test中的最後一個樣本
sample = X_test[-1] # (12, 1)
sample = sample.reshape(1, sample.shape[0], 1) # (1, 12, 1)

# 模型預測
sample_pred = model.predict(sample)

array([[0.7783012]], dtype=float32)

預測後續20個點的值

ture_data = X_test[-1] # 真實test的最後20個數據點 (12, 1)

array([[0.73982743],
[0.72595352],
[0.70825571],
[0.69791156],
[0.70619669],
[0.74360231],
[0.81125601],
[0.83321894],
[0.84150407],
[0.84880871],
[0.83606236],
[0.81189332]])

def predict_next(model, sample, epoch=20):
    temp1 = list(sample[:,0])
    for i in range(epoch):
        sample = sample.reshape(1, SEQ_LEN, 1)
        pred = model.predict(sample)
        value = pred.tolist()[0][0]
        temp1.append(value)
        sample = np.array(temp1[i+1 : i+SEQ_LEN+1])
    return temp1

preds = predict_next(model, ture_data, 20)
plt.figure(figsize=(12,6))
plt.plot(preds, color='yellow', label='Prediction')
plt.plot(ture_data, color='blue', label='Truth')
plt.xlabel("Epochs")
plt.ylabel("Value")
plt.legend(loc='best')
plt.show()

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