Keras CNN_M_LSTM 多输入合并:电能、时间戳与 Covid 数据
2025-02-27 15:19:29
Keras 中如何将三个输入参数连接到 CNN_M_LSTM 模型?
在使用 Keras 构建 CNN_M_LSTM 模型并输入多个数据源时,经常会遇到数据整合的问题。比如,像你的这样,想把电能消耗和时间戳数据(大小为 70082, 2)以及 Covid 数据集(大小为 744, 1)一起输入到模型,就会有些棘手。你的目标是将这三个数据集合并,并保持窗口大小为 96,批大小为 128。你尝试用 tensor slicing 和 tensor zip 的方法,并且在创建模型时定义了两个输入。但遇到了错误:模型期望 2 个输入,但只接收到 1 个输入张量。下面,咱们来捋一捋如何解决这个问题。
问题原因分析
问题的根源在于如何正确地将三个输入数据在送入模型之前进行合并处理。 你当前的代码尝试通过 tf.data.Dataset.zip
将电能消耗、时间戳和 Covid 数据结合,然后创建窗口数据集。但这种方式创建的数据集结构与模型定义的两个输入(input1
和 input2
)不匹配。 模型期望的是两个独立的输入张量,而不是一个合并后的数据集。
解决方案
要解决这个问题,需要重新设计数据的处理和输入方式,确保模型接收到它期望的三个独立输入。下面是具体的操作步骤和代码示例。
1. 数据预处理函数的修改
首先,我们需要修改 windowed_dataset
函数。 目标是将三个数据源分开处理,并为每个数据源创建独立的窗口。最后,将这三个经过窗口处理的数据集组合成一个适合模型训练的数据集。
def windowed_dataset(series_energy, series_covid, window_size=96, batch_size=128, shuffle_buffer=1000):
# 分别处理电能消耗和时间戳数据
dataset_energy = tf.data.Dataset.from_tensor_slices(series_energy)
dataset_energy = dataset_energy.window(window_size + 1, shift=1, drop_remainder=True)
dataset_energy = dataset_energy.flat_map(lambda window: window.batch(window_size + 1))
# energy_data shape is(96,2)
# 处理 Covid 数据
dataset_covid = tf.data.Dataset.from_tensor_slices(series_covid)
dataset_covid = dataset_covid.window(window_size + 1, shift=1, drop_remainder=True)
dataset_covid = dataset_covid.flat_map(lambda window: window.batch(window_size + 1))
# covid_data shape is(96,1)
# 合并三个数据集,使用元组
dataset = tf.data.Dataset.zip((dataset_energy, dataset_covid))
# 打乱数据集
dataset = dataset.shuffle(shuffle_buffer)
# 分离特征和标签, 并调整形状以适应模型
dataset = dataset.map(lambda x, y: (
{
"input1": y[:-1], # Covid 数据
"input2": x[:-1] # 电能和时间戳数据
},
x[-1:, 0] # 电能数据的第一列作为标签. 注意这里变成了(1,1),代表在最后的时间点的能源消耗值
))
# 注意:这里的x[-1][0]改成x[-1:,0]比较合理
# 批处理数据集
dataset = dataset.batch(batch_size, drop_remainder=True).cache()
# drop_remainder=True很重要!少了这行,BatchDataset的shape是不完整的
return dataset
代码解释:
- 独立窗口化: 对
series_energy
(电能和时间戳),series_covid
(covid数据) 分别进行窗口化处理, 不再合并处理。 - 数据集zip:
tf.data.Dataset.zip((dataset_energy, dataset_covid))
将处理过的三个数据集打包,确保时间步对齐. 使用元组进行合并。 - 数据映射和拆分: 使用
dataset.map
将窗口化的数据拆分为模型的输入和标签.input1
接收 Covid 数据,input2
接收电能与时间戳数据. 标签是能源数据的第一个维度的最后一个数据点。 - 批次与缓存 : 设置合适的
batch_size
, 并通过drop_remainder=True
确保每个批次都有相同的形状, 用.cache()
提高效率。
2. 模型构建
模型定义部分需要保持原样,因为你已经正确定义了两个输入层。关键在于训练时,数据的输入格式要和模型定义的输入层匹配。
def create_CNN_LSTM_model():
# Define the inputs
input1 = tf.keras.layers.Input(shape=(96, 1), name="input1")
input2 = tf.keras.layers.Input(shape=(96, 2), name="input2")
# Define the CNN-LSTM part of the model
x = tf.keras.layers.Conv1D(filters=128, kernel_size=3, activation='relu', strides=1, padding="causal")(input1)
x = tf.keras.layers.MaxPooling1D(pool_size=2)(x)
x = tf.keras.layers.Conv1D(filters=64, kernel_size=3, activation='relu', strides=1, padding="causal")(x)
x = tf.keras.layers.MaxPooling1D(pool_size=2)(x)
x = tf.keras.layers.Dropout(0.5)(x)
x = tf.keras.layers.LSTM(16, return_sequences=True)(x)
x = tf.keras.layers.LSTM(8, return_sequences=True)(x)
x = tf.keras.layers.Flatten()(x)
output_lstm = tf.keras.layers.Dense(1)(x)
# Define the dense part of the model
output_dense_1 = tf.keras.layers.Dense(1)(input2[:, -1, :])
# Concatenate the outputs of LSTM and Dense layers
concatenated = tf.keras.layers.Concatenate()([output_dense_1, output_lstm])
# Add further dense layers
x = tf.keras.layers.Dense(6, activation=tf.nn.leaky_relu)(concatenated)
output = tf.keras.layers.Dense(4)(x)
model_final = tf.keras.Model(inputs=[input1, input2], outputs=output)
# Define the final model
return model_final
3. 模型训练
模型编译可以保持不变,但模型训练部分需要使用修改后的 windowed_dataset
函数。
model_cnn_m_lstm = create_CNN_LSTM_model()
# Compile the model
model_cnn_m_lstm.compile(
loss=tf.keras.losses.Huber(),
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
metrics=["mse"]
)
model_cnn_m_lstm.summary()
# 假设 train_series_energy, train_series_covid 已经定义
train_dataset = windowed_dataset(train_series_energy, train_series_covid) #注意这里替换成新的数据
model_cnn_m_lstm.fit(train_dataset, epochs=100) #注意这里去掉了batch_size
这里有个重点:model.fit()
中的batch_size
参数应该去掉。因为train_dataset
里面已经包含了batch
的操作,如果这里再次设定batch_size
会报错。
进阶使用技巧:
-
特征工程: 除了简单地合并数据,可以尝试更复杂的特征工程。例如,可以对 Covid 数据进行一些统计特征的提取(例如,过去 7 天的平均新增病例数),或者对时间戳数据进行特征分解(例如,提取星期几、是否节假日等信息)。这些额外的特征可能会提升模型性能。
-
动态形状处理: 如果输入数据的形状可能在不同批次之间变化(尽管这种情况不太常见),可以在模型中使用
None
来表示可变维度。例如,input1 = tf.keras.layers.Input(shape=(None, 1), name="input1")
允许输入的时间序列长度在不同批次中不同。但是需要做数据填充!保证一个batch里面的长度一样。 -
更复杂的Concat层应用: 虽然这里我们只是把
output_dense_1
和CNN_M_LSTM模型的输出output_lstm
做了拼接,但事实上我们可以对input的tensor做各种操作,只要最终维度对得上就行。 甚至把两个input先做一次concat再过CNN_M_LSTM都可以. 只需要把CNN_M_LSTM模型部分的input修改为input2
,删掉后面的output_dense_1
即可。
安全建议
-
数据标准化/归一化: 在将数据输入模型之前,强烈建议对数据进行标准化或归一化处理,防止梯度爆炸、消失,加快训练速度。 这有助于提高模型训练的稳定性和效率. 不同数据源的尺度差异可能会影响模型性能, 标准化/归一化能缓解这个问题.
-
处理缺失值: 一定要检查原始数据里面有没有缺失值。有缺失值要提前处理!可以使用插值、填充等方法处理数据中的缺失值,保证数据的完整性。
-
超参数调整: 模型表现不好,记得调超参数. 比如学习率, 卷积核大小等等。