返回

解决CNN_M_LSTM数据维度问题:能耗与疫情数据拼接

Ai

CNN_M_LSTM 模型输入数据拼接问题详解与解决

你在尝试将时间戳能耗数据和疫情数据输入到 CNN_M_LSTM 模型时,遇到了数据维度不匹配的问题。 简单说,模型期望的输入数据的维度,跟你处理后的数据维度对不上。下面一步步分析并解决。

一、问题分析

  1. 数据维度 :

    • 原始能耗数据维度: (730, 2)
    • 原始疫情数据维度: (730, 1)
    • 你希望的窗口大小 (window_size): 96
    • 批次大小 (batch_size): 128
    • 模型create_CNN_LSTM_model期望的input1和input2是二维,但你通过tf.data.Dataset.zip得到的是包含三维的嵌套张量.
  2. windowed_dataset 函数 :
    这个函数会将数据切分成窗口,每个窗口包含 window_size 个时间步的数据。 你给函数的 series 是 (730, 2) 和 (730, 1)。 但是,windowed_dataset 在返回的时候只取了window[-1][0]。 也就是说输出的label只取了第二个维度。而我们希望所有的label被保留。

  3. tf.data.Dataset.zip 的使用 : 这个函数会把两个数据集“拉链式”地组合。 也就是第一个数据集的第一个样本, 跟第二个数据集的第一个样本组合, 依次类推。

  4. 模型的输入层定义 :
    create_CNN_LSTM_model里,输入层input1input2 的形状定义得不够灵活,且不适应处理完后的输入。

二、解决方案

1. 修改 windowed_dataset 函数

def windowed_dataset(series, window_size=MAX_LENGTH, batch_size=BATCH_SIZE, shuffle_buffer=TRAIN.SHUFFLE_BUFFER_SIZE):
    """
    切分时间序列数据为窗口,生成特征和标签。
    """
    dataset= tf.data.Dataset.from_tensor_slices(series)
    dataset = dataset.window(window_size + 1, shift=1, drop_remainder=True)
    dataset = dataset.flat_map(lambda window: window.batch(window_size + 1))
    dataset = dataset.shuffle(shuffle_buffer)
    dataset = dataset.map(lambda window: (window[:-1], window[-1])) # 修改这里,保留完整的标签
    dataset = dataset.batch(batch_size, drop_remainder=True).cache()
    return dataset

改进点:

  • window[-1] 改为 window[-1]: 这样输出的 label 会包含时间步的所有特征维度, 而不是仅仅第一个特征.
  • window的时候加入了drop_remainder=True: 使输出的数据都是等长的,能有效解决批处理时的错误。
  • 返回的数据格式是 ((batch_size, window_size, feature), (batch_size, feature)), 符合大部分情况下模型的需求。

2. 调整数据zipmap方式

train_energy_dataset = windowed_dataset(TRAIN.PRE_PROCESSED_SERIES)
train_covid_dataset = windowed_dataset(TRAIN.SERIES_COVID)

# 直接zip 两个数据集.
train_dataset = tf.data.Dataset.zip((train_covid_dataset, train_energy_dataset))

def combine_features_and_labels(data1, data2):
     #将特征与特征结合, 标签与标签结合.
    combined_features = (data1[0], data2[0])  #  ((128, 96, 1) , (128, 96, 2)),  特征维度.
    combined_labels = (data1[1], data2[1])   #  ((128,1) , (128, 2)), label的维度
    return combined_features, combined_labels

train_dataset = train_dataset.map(combine_features_and_labels)
train_dataset = train_dataset.prefetch(tf.data.AUTOTUNE)

改进点:

  • map 从主要处理特征,调整到组合featurelabel.
  • 移除 batch:因为在windowed_dataset中,batch已经被执行了。
  • 加入prefetch: 让模型在训练时, 预先准备下一批数据.

3. 修改模型 create_CNN_LSTM_model

def create_CNN_LSTM_model():
    # input1 处理 covid 数据。
    input1 = tf.keras.layers.Input(shape=(MAX_LENGTH, 1), name="input1")
    # CNN 部分, 处理 input1.
    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)
    # 注意, 这里先不 Flatten. 因为可能需要跟 input2 的输出拼接.
    x = tf.keras.layers.LSTM(8)(x)  #如果只关心最后一个时间步,可移除return_sequences=True
     
    output_lstm = tf.keras.layers.Dense(1)(x)

    # input2 处理 energy 数据.
    input2 = tf.keras.layers.Input(shape=(MAX_LENGTH, 2), name="input2")
    #直接使用一个dense或加入一个lstm层来对时间进行降维处理。
    output_dense_1 = tf.keras.layers.LSTM(8)(input2)
    #可以继续处理
    output_dense_2 = tf.keras.layers.Dense(1)(output_dense_1)

    # 拼接两个输入对应的输出.
    concatenated = tf.keras.layers.Concatenate()([output_lstm, output_dense_2])

    # 添加全连接层做最终预测
    x = tf.keras.layers.Dense(6, activation=tf.nn.leaky_relu)(concatenated)
    #因为我们预测energy的维度是2维, 而covid是一维. 根据你想要的结果来调整最后一层的输出。
    output = tf.keras.layers.Dense(2)(x) # 假定最后你想预测energy的二维.
    model_final = tf.keras.Model(inputs=[input1, input2], outputs=output)

    return model_final

改进点:

  • input1 形状改为 (MAX_LENGTH, 1):对应疫情数据的窗口和特征维度。
  • input2 形状改为 (MAX_LENGTH, 2):对应能耗数据的窗口和特征维度。
  • 移除了CNN部分不必要的Flatten操作: 输出直接送入Dense层前,可能还需要和另一个input2做合并, 合并时机根据实际需要来。
  • output的Dense的unit数量与能耗维度对应,原来是能耗维度是2,原来label的获取代码window[-1][0]只会拿到一个,因此output层会发生问题,要修改为和能耗维度匹配。
  • input2 (energy)的数据加入处理层。
  • 根据自己对数据处理的认知来修改模型组合方式和结构, 例如对数据先各自lstm再合并,或者先合并再一起处理。

4. 编译与训练

model_cnn_m_lstm = create_CNN_LSTM_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()

model_cnn_m_lstm.fit(train_dataset, epochs=100)

改进:

  • 去除 batch_size 参数: batching 在 tf.data 处理时已经完成了。

三、进阶技巧(可选)

  1. 数据归一化/标准化: 强烈建议在数据输入模型前, 对能耗和疫情数据进行归一化或标准化。 可以使用 sklearn.preprocessing 中的 MinMaxScalerStandardScaler

  2. 处理标签维度不一致问题: 在修改版的 windowed_dataset 中,能保证特征的输出,但由于能耗与疫情数据的标签维度不同,你可以:

    • 考虑预测其中一项,舍弃另一项。(如本例只预测能耗).
    • 分别构建不同的输出头(output heads), 每个负责一个数据集的标签预测,总损失是多个输出头损失的加权和。
  3. 处理时间特征: 你的能耗数据包含时间戳, 考虑提取时间特征(年、月、日、星期几、小时等),并把它们作为额外的特征。

  4. 早停 (Early Stopping): 在训练过程中监控验证集上的性能, 如果模型性能不再提升,则提前停止训练, 防止过拟合。

  5. 学习率调整策略: 可以尝试ReduceLROnPlateau, OneCycleLR 等.

  6. 尝试其他的模型拼接方式: 也可以使用其他拼接数据的方法,例如可以在合并后再进入LSTM.

        # 先合并两个input,把两个input想象成不同的feature,在一个时间维度进行扩展后进行concat
        input1 = tf.keras.layers.Input(shape=(96, 1), name="input1")  # 假设的输入维度
        input2 = tf.keras.layers.Input(shape=(96, 2), name="input2")
        concatenated_inputs = tf.keras.layers.Concatenate(axis=-1)([input1, input2])#变成96,3
    

通过上面步骤,你应该能够成功解决数据维度不匹配的问题, 并把数据喂给 CNN_M_LSTM 模型。 记得根据你的数据特点和需求, 对代码细节进行微调。