Cas pratique TensorFlow : Prédiction de la température du réseau de neurones hybride CNN-LSTM (avec code Python complet)

Bonjour à tous, aujourd'hui, je vais partager avec vous comment utiliser Tensorflow pour construire un modèle de réseau neuronal hybride combinant le réseau neuronal convolutif CNN et le réseau neuronal récurrent LSTM pour compléter la prédiction de séries chronologiques multi-fonctions.

La structure principale du modèle de prédiction dans cet article est composée de réseaux de neurones CNN et LSTM. Les données caractéristiques de la température de l'air dépendent de l'espace. Cet article choisit d' extraire la relation spatiale entre les caractéristiques en utilisant un réseau de neurones convolutionnels CNN dans le frontal du modèle . Dans le même temps, les données de température ont une dépendance temporelle évidente, de sorte que le modèle de mémoire à long et à court terme LSTM est ajouté après le réseau neuronal convolutif pour le traitement des séries chronologiques .

1. Obtenir le jeu de données

L'ensemble de données et le code complet sont fournis à la fin de l'article, si vous aimez vous rappeler de mettre en signet et d'aimer.

Cet article utilise le GPU pour accélérer le calcul. Les amis qui n'ont pas de GPU peuvent supprimer le code qui appelle le GPU ci-dessous.

Les 5 dernières caractéristiques de l'ensemble de données sont des données de température, les trois premières sont des données de temps et la colonne "réelle" est sélectionnée comme étiquette. La tâche nécessite que, selon les données de température de 10 jours consécutifs, prédise la valeur de température réelle après 5 jours

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())  # 查看前五行数据

Les informations sur l'ensemble de données sont les suivantes :

2. Données de temps de traitement

Comme illustré dans la figure ci-dessus, les trois premières colonnes de la liste contiennent des informations sur l'heure. Vous devez combiner les informations sur l'année, le mois et le jour pour convertir le type chaîne en type date/heure. Lors de la sélection de fonctions, la fonction de temps n'est pas utilisée et elle est traitée en premier.

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])

Les caractéristiques temporelles traitées sont les suivantes

3. Visualisation des données des fonctionnalités

Dessinez la courbe de distribution de chaque caractéristique et ayez une compréhension intuitive des données

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()

Tracez simplement la distribution des quatre caractéristiques dans le temps

4. Prétraitement des données

Tout d'abord, il y a une colonne dans les données qui sont des données classifiées, le jour de la semaine, ces données doivent être encodées à chaud et une colonne de caractéristiques est ajoutée à chaque classification. Si cette ligne de données est lundi, alors la valeur de la colonne Lundi correspondante est 1, la valeur des autres colonnes est 0.

Choisissez ensuite la valeur de l'étiquette pour prédire la température dans 5 jours. Les données d'étiquette prennent la colonne 'label' dans les données d' entité et déplacent cette colonne collectivement vers le haut de 5 lignes , puis à ce moment, les 5 dernières lignes des données d'étiquette auront une valeur d'inoccupation nan . Les données d'entité des 5 dernières lignes n'ont pas de valeur d'étiquette correspondante, de sorte que les 5 dernières lignes doivent être supprimées des données d'entité et des données d'étiquette.

L'étape suivante consiste à standardiser toutes les données de température , et non pas les données hebdomadaires encodées onehot .

# 选择特征, 共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)

Les données prétraitées sont les suivantes :

5. Fenêtre glissante des séries chronologiques

La file d'attente deque est utilisée ici , premier entré, premier sorti . La longueur maximale de la file d'attente spécifiée est de 10, c'est-à-dire que la longueur de la fenêtre de série chronologique est de 10 et la température après 5 jours est prédite sur la base des données caractéristiques de 10 jours. Si la longueur de la file d'attente dépasse 10, la file d'attente supprimera automatiquement l'élément en tête et ajoutera le nouvel élément à la fin de la file d'attente pour former une nouvelle séquence .

Pour les données d'étiquette , par exemple, la fonctionnalité range(0,10) days prédit la température le 15e jour, et la valeur de l'étiquette a été collectivement augmentée de 5 lignes auparavant, puis le 15e degré et la valeur de l'étiquette de température correspondent à index 9 , c'est-à-dire [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)

L' image de gauche x représente les 10 rangées de données de caractéristiques contenues dans une fenêtre glissante de séries chronologiques , et l'étiquette d'image de droite y est une valeur d'étiquette de température correspondant à chaque fenêtre glissante de séries chronologiques .

6. Diviser le jeu de données

Les 80 premières données sont extraites des séquences divisées pour la formation, et les 20% restants sont utilisés respectivement pour la validation et les tests. Mélangez () les données d'entraînement au hasard pour éviter le hasard. Construisez un itérateur iter() , combinez-le avec la fonction next() pour extraire un lot de l'ensemble d'apprentissage et affichez les informations sur l'ensemble de données.

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. Construction du réseau

La forme de la couche d'entrée est [None, 10, 12] , où None signifie que Batch_Size n'a pas besoin d'être écrit, 10 signifie la taille de la fenêtre de série chronologique et 12 signifie le nombre d'entités dans les données.

Puisque j'utilise ici une convolution bidimensionnelle Conv2D , je dois ajouter la dimension du canal à l'ensemble de données d'entrée , et la forme devient [None, 10, 12, 1] , similaire au traitement d'image, en utilisant une convolution 3*3 avec un foulée de 1, Extraire les fonctionnalités

Le sous-échantillonnage à l'aide de couches de regroupement et, bien sûr , de couches convolutives avec la foulée 2 est également possible . Lors du sous-échantillonnage, la taille du noyau de regroupement que je choisis est [1,2] , c'est-à-dire uniquement un échantillonnage ascendant et descendant dans la dimension de la caractéristique, la fenêtre de séquence reste inchangée et les problèmes spécifiques de la méthode de sous-échantillonnage sont analysés en détail.

Avant d'entrer les données de caractéristiques de CNN à LSTM, il est nécessaire d'ajuster le nombre de canaux , de fusionner les informations de canal, de réduire le nombre de canaux à 1, puis de presser la dimension du canal , et la forme passe de quatre dimensions à tridimensionnel, [Aucun,10,6,1] devient [Aucun,10,6]

L'étape suivante consiste à traiter les informations de série chronologique des données via LSTM et enfin à produire le résultat de la prédiction via une couche entièrement connectée. Le nombre de neurones dans la couche entièrement connectée doit être le même que le nombre de valeurs d'étiquettes prédites. Ici, seul un certain point dans le temps dans le futur est prédit, et le nombre de neurones est de 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()

L'architecture du réseau est la suivante :

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. Formation modèle

Utilisez l'erreur absolue moyenne entre la valeur prédite et la valeur réelle calculée par la régression comme fonction de perte, et utilisez le logarithme de l'erreur quadratique moyenne entre la valeur prédite et la valeur réelle comme indicateur de surveillance pendant la formation

# 网络编译
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)

Étant donné que l'historique enregistre les informations de perte et les informations d'index contenues dans chaque itération lors de la formation du réseau, visualisez-les

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. Étape de prédiction

Tout d'abord, utilisez évalue() pour calculer les indicateurs de perte et de surveillance pour l'ensemble de test et utilisez la fonction prédire() pour calculer la température de l'air prévue à travers les valeurs propres de l'ensemble de test. Dessinez ensuite une courbe comparant la valeur prédite et la valeur réelle.

# 对整个测试集评估
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()

La courbe prédite et la courbe réelle sont comparées comme suit

Code complet et données

Le code complet et les données ont été placés en arrière-plan, il suffit de répondre par mot-clé

Si vous souhaitez rejoindre l'échange technique, la meilleure façon de faire une remarque lors de l'ajout est : source + direction d'intérêt, ce qui est pratique pour trouver des amis partageant les mêmes idées

Méthode ①, ajouter l'ID WeChat : dkl88191, remarques : à partir de
la méthode CSDN + température ②, numéro public de recherche WeChat : apprentissage Python et exploration de données, réponse en arrière-plan : température

Je suppose que tu aimes

Origine blog.csdn.net/m0_59596937/article/details/127216225
conseillé
Classement