TensorFlow の実際のケース: CNN-LSTM ハイブリッド ニューラル ネットワークの温度予測 (完全な Python コードを使用)

みなさん、こんにちは。今日は、Tensorflow を使用して、CNN 畳み込みニューラル ネットワークと LSTM リカレント ニューラル ネットワークを組み合わせたハイブリッド ニューラル ネットワーク モデルを構築し、多機能時系列予測を完了する方法を紹介します。

この論文の予測モデルの主な構造は、CNN と LSTM ニューラル ネットワークで構成されています。気温の特性データは空間依存性があります。この論文では、モデルのフロントエンドで CNN 畳み込みニューラル ネットワークを使用して、特徴間の空間関係を抽出することを選択しています。同時に、温度データには明らかな時間依存性があるため、時系列処理用の畳み込みニューラル ネットワークの後に LSTM 長期および短期記憶モデルが追加されます

1. データセットを取得する

データ セットと完全なコードは、記事の最後に記載されています。ブックマークなどを忘れないようにしてください。

この記事では GPU を使用して計算を高速化しています. GPU を持っていない友人は、以下の GPU を呼び出すコードを削除できます.

データセットの最後の 5 つの特徴は温度データで、最初の 3 つは時間で、「実際の」列がラベルとして選択されています。このタスクでは、連続 10 日間の温度データに従って、5 日後の実際の温度値を予測する必要があります。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import pandas as pd

# 调用GPU加速
gpus = tf.config.experimental.list_physical_devices(device_type='GPU')
for gpu in gpus:
    tf.config.experimental.set_memory_growth(gpu, True)

# --------------------------------------- #
#(1)获取数据集
# --------------------------------------- #
filepath = 'temps.csv'  # 数据集位置
data = pd.read_csv(filepath)
print(data.head())  # 查看前五行数据

データセットの情報は次のとおりです。

2. 処理時間データ

上の図に示すように、リストの最初の 3 列は時間情報です.文字列型から日時型に変換するには、年、月、日の情報を組み合わせる必要があります。機能を選択する場合、時間機能は使用されず、最初に処理されます。

import datetime  # 将时间信息组合成datetime类型的数据

# 获取数据中的年月日信息
years = data['year']
months = data['month']
days = data['day']

dates = []  # 存放组合后的时间信息

# 遍历一一对应的年月日信息
for year, month, day in zip(years, months, days):
    # 年月日之间使用字符串拼接
    date = str(year) + '-' + str(month) + '-' + str(day)
    # 将每个时间(字符串类型)保存
    dates.append(date)

# 字符串类型的时间转为datetime类型的时间
times = []

# 遍历所有的字符串类型的时间
for date in dates:
    # 转为datetime类型
    time = datetime.datetime.strptime(date, '%Y-%m-%d')
    # 逐一保存转变类型后的时间数据
    times.append(time)

# 查看转换后的时间数据
print(times[:5])

処理された時間特性は次のとおりです。

3.特徴データの視覚化

各特徴の分布曲線を描き、データを直感的に理解する

import matplotlib.pyplot as plt
# 指定绘图风格
plt.style.use('fivethirtyeight')
# 设置画布,2行2列的画图窗口,第一行画ax1和ax2,第二行画ax3和ax4
fig, ((ax1,ax2), (ax3,ax4)) = plt.subplots(2, 2, figsize=(20, 10))
 
# ==1== actual特征列
ax1.plot(times, data['actual'])
# 设置x轴y轴标签和title标题
ax1.set_xlabel(''); ax1.set_ylabel('Temperature'); ax1.set_title('actual temp')
# ==2== 前一天的温度
ax2.plot(times, data['temp_1'])
# 设置x轴y轴标签和title标题
ax2.set_xlabel(''); ax2.set_ylabel('Temperature'); ax2.set_title('temp_1')
# ==3== 前2天的温度
ax3.plot(times, data['temp_2'])
# 设置x轴y轴标签和title标题
ax3.set_xlabel('Date'); ax3.set_ylabel('Temperature'); ax3.set_title('temp_2')
# ==4== friend
ax4.plot(times, data['friend'])
# 设置x轴y轴标签和title标题
ax4.set_xlabel('Date'); ax4.set_ylabel('Temperature'); ax4.set_title('friend')
# 轻量化布局调整绘图
plt.tight_layout(pad=2)
plt.show()

時間の経過に伴う 4 つの特徴の分布を単純にプロットする

4. データの前処理

まず、データの中に分類されたデータである曜日の列があり、このデータはワンホットエンコードする必要があり、各分類に特徴列が追加されます.このデータの列が月曜日の場合、対応する月曜日の列の値は 1 で、他の列の値は 0 です。

次に、ラベル値を選択して 5 日間の気温を予測します。ラベル データは、特徴データの「ラベル」列を取得しこの列をまとめて 5 行分上に移動します。このとき、ラベル データの最後の 5 行には空席値 nan が含まれます。最後の 5 行の特徴データには対応するラベル値がないため、最後の 5 行を特徴データとラベル データから削除する必要があります。

次のステップは、すべての温度データを標準化することです。ワンホットでエンコードされた週次データは標準化しないでください。

# 选择特征, 共6列特征
feats = data.iloc[:,3:]
# 对离散的星期几的数据进行onehot编码
feats = pd.get_dummies(feats)
# 特征列增加到12项
print(feats.shape)  # (348, 12)

# 选择标签数据,一组时间序列预测5天后的真实气温
pre_days = 5
# 选择特征数据中的真实气温'actual'具体向上移动5天的气温信息
targets = feats['actual'].shift(-pre_days)
# 查看标签信息
print(targets.shape)  #(348,)

# 由于特征值最后5行对应的标签是空值nan,将最后5行特征及标签删除
feats = feats[:-pre_days]
targets = targets[:-pre_days]
# 查看数据信息
print('feats.shape:', feats.shape, 'targets.shape:', targets.shape)  # (343, 12) (343,)


# 特征数据标准化处理
from sklearn.preprocessing import StandardScaler  
# 接收标准化方法
scaler = StandardScaler()
# 对特征数据中所有的数值类型的数据进行标准化
feats.iloc[:,:5] = scaler.fit_transform(feats.iloc[:,:5])
# 查看标准化后的信息
print(feats)

前処理されたデータは次のとおりです。

5. 時系列スライディング ウィンドウ

ここでは、先入れ先出しでキュー両端キューが使用されます。指定されたキューの最大長は 10、つまり時系列ウィンドウの長さは 10で、10 日間の特性データに基づいて 5 日後の温度が予測されます。キューの長さが 10 を超える場合、キューは自動的に先頭の要素を削除し、新しい要素をキューの末尾に追加して、新しいシーケンスを形成します

ラベル データの場合、たとえばrange(0,10) 日の特徴は 15 日目の気温を予測し、ラベル値をまとめて 5 行前に移動すると、15 度と気温のラベル値がインデックスに対応します9、つまり [max_series_days-1 ]

import numpy as np
from collections import deque  # 队列,可在两端增删元素

# 将特征数据从df类型转为numpy类型
feats = np.array(feats)

# 定义时间序列窗口是连续10天的特征数据
max_series_days = 10  
# 创建一个队列,队列的最大长度固定为10
deq = deque(maxlen=max_series_days)  # 如果长度超出了10,先从队列头部开始删除

# 创建一个列表,保存处理后的特征序列
x = []
# 遍历每一行数据,包含12项特征
for i in feats:
    # 将每一行数据存入队列中, numpy类型转为list类型
    deq.append(list(i))
    # 如果队列长度等于指定的序列长度,就保存这个序列
    # 如果队列长度大于序列长度,队列会自动删除头端元素,在尾端追加新元素
    if len(deq) == max_series_days:
        # 保存每一组时间序列, 队列类型转为list类型
        x.append(list(deq))

# 保存与特征对应的标签值
y = targets[max_series_days-1:].values

# 保证序列长度和标签长度相同
print(len(x))   # 334
print(len(y))  # 334

# 将list类型转为numpy类型
x, y = np.array(x), np.array(y)

左の画像 x は時系列のスライディング ウィンドウに含まれる 10 行の特徴データで、右の画像ラベル y は各時系列のスライディング ウィンドウに対応する温度ラベル値です。

6. データセットを分割する

最初の 80 個のデータはトレーニング用に分割されたシーケンスから取得され、残りの 20% はそれぞれ検証とテストに使用されます。偶然を避けるために、トレーニング データをランダムにShuffle()します。イテレータ iter()を構築し、 next()関数と組み合わせてトレーニング セットからバッチを取り出し、データ セット情報を表示します。

total_num = len(x)  # 一共有多少组序列
train_num = int(total_num*0.8)  # 前80%的数据用来训练
val_num = int(total_num*0.9)  # 前80%-90%的数据用来训练验证
# 剩余数据用来测试

x_train, y_train = x[:train_num], y[:train_num]  # 训练集
x_val, y_val = x[train_num: val_num], y[train_num: val_num]  # 验证集
x_test, y_test = x[val_num:], y[val_num:]  # 测试集

# 构造数据集
batch_size = 128  # 每次迭代处理128个序列
# 训练集
train_ds = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_ds = train_ds.batch(batch_size).shuffle(10000)
# 验证集
val_ds = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_ds = val_ds.batch(batch_size)
# 测试集
test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_ds = test_ds.batch(batch_size)

# 查看数据集信息
sample = next(iter(train_ds))  # 取出一个batch的数据
print('x_train.shape:', sample[0].shape)  # (128, 10, 12)
print('y_train.shape:', sample[1].shape)  # (128,)

7. ネットワーク構築

入力レイヤーの形状は [None, 10, 12]です。ここで、None は Batch_Size を書き出す必要がないことを意味し、10 は時系列ウィンドウのサイズを意味し、12 はデータ内の特徴の数を意味します。

ここでは 2 次元畳み込み Conv2Dを使用するため、チャネル次元を入力データ セットに追加する必要があり、形状は [None, 10, 12, 1]になります。画像処理と同様に、3*3 畳み込みを使用してストライド 1、特徴の抽出

プーリング層を使用したダウンサンプリング、そしてもちろんストライド 2 の畳み込み層も可能です。ダウンサンプリングする場合、私が選択するプーリング カーネル サイズは [1,2] です。つまり、特徴次元でのアップおよびダウン サンプリングのみであり、シーケンス ウィンドウは変更されず、ダウンサンプリング方法の特定の問題が詳細に分析されます。

CNN から LSTM に特徴量データを入力する前に、チャネル数を調整し、チャネル情報を融合し、チャネル数を 1 に減らしてからチャネル次元を絞り出す必要があり、形状は 4 次元から三次元、[None,10,6,1] は [None,10,6] になります

次のステップは、LSTM を介してデータの時系列情報を処理し、最終的に全結合層を介して予測結果を出力することです。全結合層のニューロンの数は、予測されたラベル値の数と同じである必要があります.ここでは、将来のある時点のみが予測され、ニューロンの数は 1 です.

# 输入层要和x_train的shape一致,但注意不要batch维度
input_shape = sample[0].shape[1:]  # [10,12]

# 构造输入层
inputs = keras.Input(shape=(input_shape))  # [None, 10, 12]

# 调整维度 [None,10,12]==>[None,10,12,1]
x = layers.Reshape(target_shape=(inputs.shape[1], inputs.shape[2], 1))(inputs)

# 卷积+BN+Relu  [None,10,12,1]==>[None,10,12,8]
x = layers.Conv2D(8, kernel_size=(3,3), strides=1, padding='same', use_bias=False,
                  kernel_regularizer=keras.regularizers.l2(0.01))(x)

x = layers.BatchNormalization()(x)  # 批标准化
x = layers.Activation('relu')(x)  # relu激活函数

# 池化下采样 [None,10,12,8]==>[None,10,6,8]
x = layers.MaxPool2D(pool_size=(1,2))(x)

# 1*1卷积调整通道数 [None,10,6,8]==>[None,10,6,1]
x = layers.Conv2D(1, kernel_size=(3,3), strides=1, padding='same', use_bias=False,
                  kernel_regularizer=keras.regularizers.l2(0.01))(x)

# 把最后一个维度挤压掉 [None,10,6,1]==>[None,10,6]
x = tf.squeeze(x, axis=-1)

# [None,10,6] ==> [None,10,16]
# 第一个LSTM层, 如果下一层还是LSTM层就需要return_sequences=True, 否则就是False
x = layers.LSTM(16, activation='relu', kernel_regularizer=keras.regularizers.l2(0.01))(x)
x = layers.Dropout(0.2)(x)  # 随机杀死神经元防止过拟合

# 输出层 [None,16]==>[None,1]
outputs = layers.Dense(1)(x)

# 构建模型
model = keras.Model(inputs, outputs)

# 查看模型架构
model.summary()

ネットワーク アーキテクチャは次のとおりです。

Model: "model_12"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_13 (InputLayer)        [(None, 10, 12)]          0         
_________________________________________________________________
reshape_12 (Reshape)         (None, 10, 12, 1)         0         
_________________________________________________________________
conv2d_25 (Conv2D)           (None, 10, 12, 8)         72        
_________________________________________________________________
batch_normalization_13 (Batc (None, 10, 12, 8)         32        
_________________________________________________________________
activation_13 (Activation)   (None, 10, 12, 8)         0         
_________________________________________________________________
max_pooling2d_13 (MaxPooling (None, 10, 6, 8)          0         
_________________________________________________________________
conv2d_26 (Conv2D)           (None, 10, 6, 1)          72        
_________________________________________________________________
tf.compat.v1.squeeze_12 (TFO (None, 10, 6)             0         
_________________________________________________________________
lstm_11 (LSTM)               (None, 16)                1472      
_________________________________________________________________
dropout_14 (Dropout)         (None, 16)                0         
_________________________________________________________________
dense_13 (Dense)             (None, 1)                 17        
=================================================================
Total params: 1,665
Trainable params: 1,649
Non-trainable params: 16
_________________________________________________________________

8. モデルトレーニング

回帰で算出した予測値と真値の平均絶対誤差を損失関数として使用し、予測値と真値の対数平均二乗誤差を学習時の監視指標として使用します

# 网络编译
model.compile(optimizer = keras.optimizers.Adam(0.001),  # adam优化器学习率0.001
              loss = tf.keras.losses.MeanAbsoluteError(),  # 标签和预测之间绝对差异的平均值
              metrics = tf.keras.losses.MeanSquaredLogarithmicError())  # 计算标签和预测之间的对数误差均方值。

epochs = 300  # 迭代300次

# 网络训练, history保存训练时的信息
history = model.fit(train_ds, epochs=epochs, validation_data=val_ds)

履歴には、ネットワーク学習の各イテレーションに含まれる損失情報とインデックス情報が記録されるため、それらを視覚化します。

history_dict = history.history  # 获取训练的数据字典
train_loss = history_dict['loss']  # 训练集损失
val_loss = history_dict['val_loss']  # 验证集损失
train_msle = history_dict['mean_squared_logarithmic_error']  # 训练集的百分比误差
val_msle = history_dict['val_mean_squared_logarithmic_error']  # 验证集的百分比误差
 
#(11)绘制训练损失和验证损失
plt.figure()
plt.plot(range(epochs), train_loss, label='train_loss')  # 训练集损失
plt.plot(range(epochs), val_loss, label='val_loss')  # 验证集损失
plt.legend()  # 显示标签
plt.xlabel('epochs')
plt.ylabel('loss')
plt.show()
 
#(12)绘制训练百分比误差和验证百分比误差
plt.figure()
plt.plot(range(epochs), train_msle, label='train_msle')  # 训练集指标
plt.plot(range(epochs), val_msle, label='val_msle')  # 验证集指标
plt.legend()  # 显示标签
plt.xlabel('epochs')
plt.ylabel('msle')
plt.show()

9. 予測段階

最初に、evaluate()を使用して、テスト セット全体の損失および監視インジケーターを計算し、 predict()関数を使用して、テスト セットの固有値から予測気温を計算します。次に、予測値と真の値を比較する曲線を描きます。

# 对整个测试集评估
model.evaluate(test_ds)

# 预测
y_pred = model.predict(x_test)

# 获取标签值对应的时间
df_time = times[-len(y_test):]

# 绘制对比曲线
fig = plt.figure(figsize=(10,5))  # 画板大小
ax = fig.add_subplot(111)  # 画板上添加一张图
# 绘制真实值曲线
ax.plot(df_time, y_test, 'b-', label='actual')
# 绘制预测值曲线
ax.plot(df_time, y_pred, 'r--', label='predict')
# 设置x轴刻度
ax.set_xticks(df_time[::7])

# 设置xy轴标签和title标题
ax.set_xlabel('Date')
ax.set_ylabel('Temperature'); 
ax.set_title('result')
plt.legend()
plt.show()

予測曲線と実際の曲線は次のように比較されます

完全なコードとデータ

完全なコードとデータはバックグラウンドに配置されています。キーワードで返信するだけです

技術交流に参加したい場合、追加する際にコメントする最良の方法は次のとおりです。

方法①、WeChat ID追加:dkl88191、備考:CSDN+Temperatureから
方法②、WeChat検索公開番号:Python学習とデータマイニング、バックグラウンド返信:温度

おすすめ

転載: blog.csdn.net/m0_59596937/article/details/127216225