接上文,本文介绍如何为多变量数据开发多输入通道多步时间序列预测的CNN模型和多子模型异构多步时间序列预测的CNN模型。



文章目录

  • 2. 多输入通道 CNN 模型
  • 2.1 建模
  • 2.2 完整代码
  • 3. 多头(子模型异构)CNN 模型
  • 3.1 建模
  • 3.2 完整代码
  • 总结



2. 多输入通道 CNN 模型

顾名思义,多通道就是有多个时间序列,即多个特征。本部分使用数据集中的八个时间序列变量(八个特征,数据集信息如下图所示)来分别预测下一个标准周的每日总有功功率。可以将每个变量时间序列作为单独的输入通道提供给模型来实现多通道输入。然后,CNN将使用一个单独的内核,并将每个输入序列读取到一组单独的过滤器映射图上,实质上是从每个输入时间序列变量中学习特征。注意,以上过程都是在同一个CNN模型中实现的,不同于下文提到的多头(子模型异构)CNN模型,为每个输入序列都定义一个CNN模型。

使用多输入通道的CNN模型,对于输出序列是预测多个不同特征(不局限于所预测的某个特征)的任务来说很有帮助。目前尚不清楚在电力消耗问题上是否如此,但我们可以对此进行探讨。接下来需要重新划分训练样本和测试样本,使得样本中包含全部八个特征,而不仅仅是日总有有功率这一个特征。先看一下本文使用的数据集信息:

多变量时间序列聚类 Python 多变量时间序列建模_多变量时间序列聚类 Python

2.1 建模

由上图可知,本文使用的数据集shape为(1442,8),采样点为1442个,特征数(features)为8个(8列)。通过滑动窗口和切片相结合的方式来将以上采样数据划分成样本(samples),其中滑动窗口的宽度和 滑动步长(sw_width) 可以设置为超参数,方便后期调参的时候,找到适合自己业务需求的窗口宽度和滑动步长。代码实现:

def sliding_window(train, sw_width=7, n_out=7, in_start=0):
    '''
    该函数实现窗口宽度为7、滑动步长为1的滑动窗口截取序列数据
    截取所有特征
    '''
    data = train.reshape((train.shape[0] * train.shape[1], train.shape[2])) # 将以周为单位的样本展平为以天为单位的序列
    X, y = [], []
    
    for _ in range(len(data)):
        in_end = in_start + sw_width
        out_end = in_end + n_out
        
        # 保证截取样本完整,最大元素索引不超过原序列索引,则截取数据;否则丢弃该样本
        if out_end < len(data):
            # 因为是for循环,所以滑动窗口的滑动步长为1;想调整滑动步长可以通过yield实现,后边的文章会讲;
            X.append(data[in_start:in_end, :]) # 截取窗口宽度数量的采样点的全部8个特征
            y.append(data[in_end:out_end, 0]) # 截取样本之后7个时间步长的总有功功耗(截取一个单列片段,有7个元素)
        in_start += 1
        
    return np.array(X), np.array(y)

如果没有看过之前的文章,代码中第一条语句可能有疑问,这里说明一下:

data = train.reshape((train.shape[0] * train.shape[1], train.shape[2])) # 将以周为单位的样本展平为以天为单位的序列

其实在这个自定义函数之前,还有一个将数据集划分为训练集和测试集的自定义函数 split_dataset(),这个函数通过切片的方式截取,将前三年每天的采样数据作为训练集(共159周,1113天),最后一年的采样数据作为测试集(共46周,322天),本文使用的数据的shape为(1442,8),经过以上划分一共使用了1113+322=1435个采样点数据,第一天和最后六天无法组成完整的标准周(周天开始周六结束),因此丢弃不用。

然后通过 numpy 的 split 函数将单日数据重采样成以周为单位的训练样本,因此,训练数据的shape变为(159, 7, 8),测试数据的shape变为(46, 7, 8) 。三维数组怎么理解,这里解释一下,第一个维度“159”表示159周,每周的数据作为一个样本;第二个维度“7”表示一个样本有七个采样点的值,即包含7个时间步长的数据;第三个维度“8”表示有八个特征(也可理解为八个变量,八列可用数据)。训练集的shape同理。讲到这里,应该可以明白上边拿出来的语句的功能了,即将三维数组重塑成二维数组,这样就还原回以每日为单位的采样序列,便于做滑动窗口。当然也可以不用这么麻烦,直接重写划分训练集和测试集的函数即可。

可能会问为什么要按照每周来划分,其实这是针对本系列文章所提出的问题的;原问题是用历史数据预测下一周该家庭的总有功功率的情况,因此处理成按周划分的数据,方便演示和说明问题。当然可以使用更大的窗口宽度,比如用两个星期的数据预测下一个星期的数据,其实这些都是超参数,后期需要针对不同的业务场景进行调整和超参数搜索,来找到最佳的先验时间步长。之后的文章再介绍如何调参。

还有几点需要注意,这里三维数组的各个维度所代表的含义,只是针对本系列文章中提出的问题;在使用的过程中,根据自己的需求修改就可以了。主要是弄明白滑动窗口、滑动窗口的宽度、滑动步长、样本、特征这些概念和实现方法,这样在处理自己遇到的问题的时候就能随机应变,得心应手了。


上一篇文章中提到的预测函数也需要进一步修改。完整代码如下:

def forecast(model, pred_seq, sw_width):
    '''
    该函数实现对输入数据的预测
    多个特征
    '''
    data = np.array(pred_seq)
    data = data.reshape((data.shape[0]*data.shape[1], data.shape[2]))
    
    input_x = data[-sw_width:, :] # 获取输入数据的最后一周的数据
    input_x = input_x.reshape((1, input_x.shape[0], input_x.shape[1])) # 重塑形状为[1, sw_width, n]
    
    yhat = model.predict(input_x, verbose=0) # 预测下周数据
    yhat = yhat[0] # 获取预测向量
    return yhat

此处使用一个稍复杂的模型,包含三层卷积,两层池化,一个flatten层和两个全连接层。使用均方误差损失函数mse,使用随机梯度下降adam进行优化。

2.2 完整代码

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

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei']
plt.rcParams['axes.unicode_minus'] = False

import math
import sklearn.metrics as skm
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.layers import Conv1D,MaxPooling1D

def split_dataset(data):
    '''
    该函数实现以周为单位切分训练数据和测试数据
    '''
    # data为按天的耗电量统计数据,shape为(1442, 8)
    # 测试集取最后一年的46周(322天)数据,剩下的159周(1113天)数据为训练集,以下的切片实现此功能。
    train, test = data[1:-328], data[-328:-6]
    train = np.array(np.split(train, len(train)/7)) # 将数据划分为按周为单位的数据
    test = np.array(np.split(test, len(test)/7))
    return train, test

def evaluate_forecasts(actual, predicted):
    '''
    该函数实现根据预期值评估一个或多个周预测损失
    思路:统计所有单日预测的 RMSE
    '''
    scores = list()
    for i in range(actual.shape[1]):
        mse = skm.mean_squared_error(actual[:, i], predicted[:, i])
        rmse = math.sqrt(mse)
        scores.append(rmse)
    
    s = 0 # 计算总的 RMSE
    for row in range(actual.shape[0]):
        for col in range(actual.shape[1]):
            s += (actual[row, col] - predicted[row, col]) ** 2
    score = math.sqrt(s / (actual.shape[0] * actual.shape[1]))
    print('actual.shape[0]:{}, actual.shape[1]:{}'.format(actual.shape[0], actual.shape[1]))
    return score, scores

def summarize_scores(name, score, scores):
    s_scores = ', '.join(['%.1f' % s for s in scores])
    print('%s: [%.3f] %s\n' % (name, score, s_scores))
    
def sliding_window(train, sw_width=7, n_out=7, in_start=0):
    '''
    该函数实现窗口宽度为7、滑动步长为1的滑动窗口截取序列数据
    截取所有特征
    '''
    data = train.reshape((train.shape[0] * train.shape[1], train.shape[2])) # 将以周为单位的样本展平为以天为单位的序列
    X, y = [], []
    
    for _ in range(len(data)):
        in_end = in_start + sw_width
        out_end = in_end + n_out
        
        # 保证截取样本完整,最大元素索引不超过原序列索引,则截取数据;否则丢弃该样本
        if out_end < len(data):
            # 因为是for循环,所以滑动窗口的滑动步长为1;想调整滑动步长可以通过yield实现,后边的文章会讲;
            X.append(data[in_start:in_end, :]) # 截取窗口宽度数量的采样点的全部8个特征
            y.append(data[in_end:out_end, 0]) # 截取样本之后7个时间步长的总有功功耗(截取一个单列片段,有7个元素)
        in_start += 1
        
    return np.array(X), np.array(y)

def multi_input_cnn_model(train, sw_width, in_start=0, verbose=0, epochs=20, batch_size=16):
    '''
    该函数定义 多输入序列 CNN 模型
    '''
    train_x, train_y = sliding_window(train, sw_width, in_start=0)
    
    n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1]
    
    model = Sequential()
    model.add(Conv1D(filters=32, kernel_size=3, activation='relu', 
                     input_shape=(n_timesteps, n_features)))
    model.add(Conv1D(filters=32, kernel_size=3, activation='relu'))
    model.add(MaxPooling1D(pool_size=2))
    model.add(Conv1D(filters=16, kernel_size=3, activation='relu'))
    model.add(MaxPooling1D(pool_size=2))
    model.add(Flatten())
    model.add(Dense(100, activation='relu'))
    model.add(Dense(units=n_outputs))
    
    model.compile(loss='mse', optimizer='adam', metrics=['accuracy'])
    print(model.summary())
    
    model.fit(train_x, train_y,
              epochs=epochs, batch_size=batch_size, verbose=verbose)
    return model


def forecast(model, pred_seq, sw_width):
    '''
    该函数实现对输入数据的预测
    多个特征
    '''
    data = np.array(pred_seq)
    data = data.reshape((data.shape[0]*data.shape[1], data.shape[2]))
    
    input_x = data[-sw_width:, :] # 获取输入数据的最后一周的数据
    input_x = input_x.reshape((1, input_x.shape[0], input_x.shape[1])) # 重塑形状为[1, sw_width, n]
    
    yhat = model.predict(input_x, verbose=0) # 预测下周数据
    yhat = yhat[0] # 获取预测向量
    return yhat

def evaluate_model(model, train, test, sd_width):
    '''
    该函数实现模型评估
    '''
    history_fore = [x for x in train]
    predictions = list() # 用于保存每周的前向验证结果;
    for i in range(len(test)):
        yhat_sequence = forecast(model, history_fore, sd_width) # 预测下周的数据
        predictions.append(yhat_sequence) # 保存预测结果
        history_fore.append(test[i, :]) # 得到真实的观察结果并添加到历史中以预测下周
    
    predictions = np.array(predictions) # 评估一周中每天的预测结果
    score, scores = evaluate_forecasts(test[:, :, 0], predictions)
    return score, scores

def model_plot(score, scores, days, name):
    '''
    该函数实现绘制RMSE曲线图
    '''
    plt.figure(figsize=(8,6), dpi=150)
    plt.plot(days, scores, marker='o', label=name)
    plt.grid(linestyle='--', alpha=0.5)
    plt.ylabel(r'$RMSE$', size=15)
    plt.title('多输入序列 CNN 模型预测结果',  size=18)
    plt.legend()
    plt.show()
    
def main_run(dataset, sw_width, days, name, in_start, verbose, epochs, batch_size):
    '''
    主函数:数据处理、模型训练流程
    '''
    # 划分训练集和测试集
    train, test = split_dataset(dataset.values)
    # 训练模型
    model = multi_input_cnn_model(train, sw_width, in_start, verbose, epochs, batch_size)
    # 计算RMSE
    score, scores = evaluate_model(model, train, test, sw_width)
    # 打印分数
    summarize_scores(name, score, scores)
    # 绘图
    model_plot(score, scores, days, name)
        
if __name__ == '__main__':
    
    dataset = pd.read_csv('household_power_consumption_days.csv', header=0, 
                   infer_datetime_format=True, engine='c',
                   parse_dates=['datetime'], index_col=['datetime'])
    
    days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat']
    name = 'cnn'
    
    sliding_window_width=14
    input_sequence_start=0
    
    epochs_num=80
    batch_size_set=16
    verbose_set=0
    
    
    main_run(dataset, sliding_window_width, days, name, input_sequence_start,
             verbose_set, epochs_num, batch_size_set)

输出:

Model: "sequential_4"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv1d_4 (Conv1D)            (None, 12, 32)            800       
_________________________________________________________________
conv1d_5 (Conv1D)            (None, 10, 32)            3104      
_________________________________________________________________
max_pooling1d_4 (MaxPooling1 (None, 5, 32)             0         
_________________________________________________________________
conv1d_6 (Conv1D)            (None, 3, 16)             1552      
_________________________________________________________________
max_pooling1d_5 (MaxPooling1 (None, 1, 16)             0         
_________________________________________________________________
flatten_4 (Flatten)          (None, 16)                0         
_________________________________________________________________
dense_8 (Dense)              (None, 100)               1700      
_________________________________________________________________
dense_9 (Dense)              (None, 7)                 707       
=================================================================
Total params: 7,863
Trainable params: 7,863
Non-trainable params: 0
_________________________________________________________________
None
actual.shape[0]:46, actual.shape[1]:7
cnn: [399.443] 407.8, 388.4, 396.2, 388.1, 370.9, 321.5, 501.1

与上一节中的单变量CNN相比,有些更好,有些更差。最后一天,星期六,仍然是充满挑战的一天,而星期五则是容易预测的一天。设计模型以专注于减少较难预测的日期的误差可能会有帮助。使用调整后的模型或多个不同模型的组合是否可以进一步降低每日得分之间的差异是值得探索的。输出的RMSE如图所示:

多变量时间序列聚类 Python 多变量时间序列建模_多变量时间序列聚类 Python_02


3. 多头(子模型异构)CNN 模型

进一步扩展CNN模型,使每个输入变量具有一个单独的子CNN模型,可以将其称为多头CNN模型。这需要重新划分训练集和测试集数据。从建模开始,需要为八个输入变量分别定义一个单独的CNN模型。模型的配置(包括层数及其超参数)也进行了修改,以更好地适应新方法。模型配置通过多次试验验证获得,可能并非最佳配置。

3.1 建模

我们可以循环遍历每个变量,创建一个子模型,该子模型接受一个14天的一维数据序列,并输出一个包含序列学习特征信息的平面向量。这些向量中的每一个都可以通过级联合并成一个非常长的向量,然后在进行预测之前通过全连接层进行解释。这样就可以在模型的定义中指定输入,并在合并层中使用平坦层组成的列表。

训练模型时,将需要八个数组分别输入八个子模型。可以通过创建3D数组列表来实现,其中每个3D数组的shape都为:[samples,timesteps,1]

对测试数据集进行预测时,必须将 [1,14,8] 的输入数组转换为八个3D数组的列表,每个数组的shape都为 [1,14,1]


3.2 完整代码

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

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['Microsoft JhengHei']
plt.rcParams['axes.unicode_minus'] = False

import math
import sklearn.metrics as skm
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, Flatten, Input
from tensorflow.keras.layers import Conv1D, MaxPooling1D
from tensorflow.keras.layers import concatenate
from tensorflow.keras.utils import plot_model

def split_dataset(data):
    '''
    该函数实现以周为单位切分训练数据和测试数据
    '''
    # data为按天的耗电量统计数据,shape为(1442, 8)
    # 测试集取最后一年的46周(322天)数据,剩下的159周(1113天)数据为训练集,以下的切片实现此功能。
    train, test = data[1:-328], data[-328:-6]
    train = np.array(np.split(train, len(train)/7)) # 将数据划分为按周为单位的数据
    test = np.array(np.split(test, len(test)/7))
    print('train.shape:{}, test.shape:{}\n'.format(train.shape, test.shape))
    return train, test

def evaluate_forecasts(actual, predicted):
    '''
    该函数实现根据预期值评估一个或多个周预测损失
    思路:统计所有单日预测的 RMSE
    '''
    scores = list()
    for i in range(actual.shape[1]):
        mse = skm.mean_squared_error(actual[:, i], predicted[:, i])
        rmse = math.sqrt(mse)
        scores.append(rmse)
    
    s = 0 # 计算总的 RMSE
    for row in range(actual.shape[0]):
        for col in range(actual.shape[1]):
            s += (actual[row, col] - predicted[row, col]) ** 2
    score = math.sqrt(s / (actual.shape[0] * actual.shape[1]))
    print('actual.shape[0]:{}, actual.shape[1]:{}'.format(actual.shape[0], actual.shape[1]))
    return score, scores

def summarize_scores(name, score, scores):
    s_scores = ', '.join(['%.1f' % s for s in scores])
    print('%s: [%.3f] %s\n' % (name, score, s_scores))
    
def sliding_window(train, sw_width=7, n_out=7, in_start=0):
    '''
    该函数实现窗口宽度为7、滑动步长为1的滑动窗口截取序列数据
    截取所有特征
    '''
    data = train.reshape((train.shape[0] * train.shape[1], train.shape[2])) # 将以周为单位的样本展平为以天为单位的序列
    X, y = [], []
    
    for _ in range(len(data)):
        in_end = in_start + sw_width
        out_end = in_end + n_out
        
        # 保证截取样本完整,最大元素索引不超过原序列索引,则截取数据;否则丢弃该样本
        if out_end <= len(data):
            # 因为是for循环,所以滑动窗口的滑动步长为1;想调整滑动步长可以通过yield实现,后边的文章会讲;
            X.append(data[in_start:in_end, :]) # 截取窗口宽度数量的采样点的全部8个特征
            y.append(data[in_end:out_end, 0]) # 截取样本之后7个时间步长的总有功功耗(截取一个单列片段,有7个元素)
        
        in_start += 1 # 实现简单的滑动窗口,滑动步长为1
        
    return np.array(X), np.array(y)

def multi_head_cnn_model(train, sw_width, in_start=0, verbose=0, epochs=20, batch_size=16):
    '''
    该函数定义 Multi-head CNN 模型
    '''
    train_x, train_y = sliding_window(train, sw_width)
    n_timesteps, n_features, n_outputs = train_x.shape[1], train_x.shape[2], train_y.shape[1]
    in_layers, out_layers = [], [] # 用于存放每个特征序列的CNN子模型
    for i in range(n_features):
        inputs = Input(shape=(n_timesteps, 1))
        
        conv1 = Conv1D(filters=32, kernel_size=3, activation='relu')(inputs)
        conv2 = Conv1D(filters=32, kernel_size=3, activation='relu')(conv1)
        pool1 = MaxPooling1D(pool_size=2)(conv2)
        flat = Flatten()(pool1)
        in_layers.append(inputs)
        out_layers.append(flat)

    merged = concatenate(out_layers) # 合并八个CNN子模型
    
    dense1 = Dense(200, activation='relu')(merged) # 全连接层对上一层输出特征进行解释
    dense2 = Dense(100, activation='relu')(dense1)
    outputs = Dense(n_outputs)(dense2)
    model = Model(inputs=in_layers, outputs=outputs)

    model.compile(loss='mse', optimizer='adam', metrics=['accuracy'])
    print(model.summary())
    
    plot_model(model, to_file='multi-head-cnn-energy-usage-prediction.png', show_shapes=True, show_layer_names=True, dpi=300)
    
    input_data = [train_x[:,:,i].reshape((train_x.shape[0], n_timesteps, 1)) for i in range(n_features)]
    
    # 这里只是为了方便演示和输出loss曲线,不建议这么做,这样其实是训练了2倍的epoch;
    # 可以保存模型,再加载预测;或者直接将预测函数定影在这里,省去调用步骤。
    model.fit(input_data, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose)
    history = model.fit(input_data, train_y, epochs=epochs, batch_size=batch_size, verbose=verbose)
    
    return model, history 

def forecast(model, pred_seq, sw_width):
    '''
    该函数实现对输入数据的预测
    多个特征
    '''
    data = np.array(pred_seq)
    data = data.reshape((data.shape[0]*data.shape[1], data.shape[2]))
    
    input_x = data[-sw_width:, :] # 获取输入数据的最后一周的数据
    input_x = [input_x[:,i].reshape((1,input_x.shape[0],1)) for i in range(input_x.shape[1])]# 8个形状为[1, sw_width, 1]的列表
    
    yhat = model.predict(input_x, verbose=0) # 预测下周数据
    yhat = yhat[0] # 获取预测向量
    return yhat

def evaluate_model(model, train, test, sd_width):
    '''
    该函数实现模型评估
    '''
    history_fore = [x for x in train]
    predictions = list() # 用于保存每周的前向验证结果;
    for i in range(len(test)):
        yhat_sequence = forecast(model, history_fore, sd_width) # 预测下周的数据
        predictions.append(yhat_sequence) # 保存预测结果
        history_fore.append(test[i, :]) # 得到真实的观察结果并添加到历史中以预测下周
    
    predictions = np.array(predictions) # 评估一周中每天的预测结果
    score, scores = evaluate_forecasts(test[:, :, 0], predictions)
    return score, scores

def model_plot(score, scores, days, name, history):
    '''
    该函数实现绘制RMSE曲线图和训练损失图
    '''
    plt.figure(figsize=(8,6), dpi=150)
    plt.subplot(2,1,1)
    plt.plot(days, scores, marker='o', label=name)
    plt.grid(linestyle='--', alpha=0.5)
    plt.xlabel(r'$weekday$', size=15)
    plt.ylabel(r'$RMSE$', size=15)
    plt.title('Multi-head CNN 模型预测结果',  size=18)
    
    plt.subplot(2,1,2)
    plt.plot(history.history['loss'], label='train')
    plt.title('loss', y=0, loc='center')
    plt.xlabel('$epochs$', size=10)
    plt.ylabel('$loss$', size=10)
    plt.legend()
    plt.grid(linestyle='--', alpha=0.5)
    plt.tight_layout()
    plt.show()

    
def main_run(dataset, sw_width, days, name, in_start, verbose, epochs, batch_size):
    '''
    主函数:数据处理、模型训练流程
    '''
    # 划分训练集和测试集
    train, test = split_dataset(dataset.values)
    # 训练模型 
    model, history = multi_head_cnn_model(train, sw_width, in_start, verbose, epochs, batch_size)
    # 计算RMSE
    score, scores = evaluate_model(model, train, test, sw_width)
    # 打印分数
    summarize_scores(name, score, scores)
    # 绘制RMSE图和训练损失图
    model_plot(score, scores, days, name, history)
    
if __name__ == '__main__':
    
    dataset = pd.read_csv('household_power_consumption_days.csv', header=0, 
                   infer_datetime_format=True, engine='c',
                   parse_dates=['datetime'], index_col=['datetime'])
    
    days = ['sun', 'mon', 'tue', 'wed', 'thr', 'fri', 'sat']
    name = 'cnn'
    
    sliding_window_width=14
    input_sequence_start=0
    
    epochs_num=80 #25
    batch_size_set=16
    verbose_set=0
    
    
    main_run(dataset, sliding_window_width, days, name, input_sequence_start,
             verbose_set, epochs_num, batch_size_set)

输出:

train.shape:(159, 7, 8), test.shape:(46, 7, 8)

Model: "model"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            [(None, 14, 1)]      0                                            
__________________________________________________________________________________________________
input_2 (InputLayer)            [(None, 14, 1)]      0                                            
__________________________________________________________________________________________________
input_3 (InputLayer)            [(None, 14, 1)]      0                                            
__________________________________________________________________________________________________
input_4 (InputLayer)            [(None, 14, 1)]      0                                            
__________________________________________________________________________________________________
input_5 (InputLayer)            [(None, 14, 1)]      0                                            
__________________________________________________________________________________________________
input_6 (InputLayer)            [(None, 14, 1)]      0                                            
__________________________________________________________________________________________________
input_7 (InputLayer)            [(None, 14, 1)]      0                                            
__________________________________________________________________________________________________
input_8 (InputLayer)            [(None, 14, 1)]      0                                            
__________________________________________________________________________________________________
conv1d_7 (Conv1D)               (None, 12, 32)       128         input_1[0][0]                    
__________________________________________________________________________________________________
conv1d_9 (Conv1D)               (None, 12, 32)       128         input_2[0][0]                    
__________________________________________________________________________________________________
conv1d_11 (Conv1D)              (None, 12, 32)       128         input_3[0][0]                    
__________________________________________________________________________________________________
conv1d_13 (Conv1D)              (None, 12, 32)       128         input_4[0][0]                    
__________________________________________________________________________________________________
conv1d_15 (Conv1D)              (None, 12, 32)       128         input_5[0][0]                    
__________________________________________________________________________________________________
conv1d_17 (Conv1D)              (None, 12, 32)       128         input_6[0][0]                    
__________________________________________________________________________________________________
conv1d_19 (Conv1D)              (None, 12, 32)       128         input_7[0][0]                    
__________________________________________________________________________________________________
conv1d_21 (Conv1D)              (None, 12, 32)       128         input_8[0][0]                    
__________________________________________________________________________________________________
conv1d_8 (Conv1D)               (None, 10, 32)       3104        conv1d_7[0][0]                   
__________________________________________________________________________________________________
conv1d_10 (Conv1D)              (None, 10, 32)       3104        conv1d_9[0][0]                   
__________________________________________________________________________________________________
conv1d_12 (Conv1D)              (None, 10, 32)       3104        conv1d_11[0][0]                  
__________________________________________________________________________________________________
conv1d_14 (Conv1D)              (None, 10, 32)       3104        conv1d_13[0][0]                  
__________________________________________________________________________________________________
conv1d_16 (Conv1D)              (None, 10, 32)       3104        conv1d_15[0][0]                  
__________________________________________________________________________________________________
conv1d_18 (Conv1D)              (None, 10, 32)       3104        conv1d_17[0][0]                  
__________________________________________________________________________________________________
conv1d_20 (Conv1D)              (None, 10, 32)       3104        conv1d_19[0][0]                  
__________________________________________________________________________________________________
conv1d_22 (Conv1D)              (None, 10, 32)       3104        conv1d_21[0][0]                  
__________________________________________________________________________________________________
max_pooling1d_6 (MaxPooling1D)  (None, 5, 32)        0           conv1d_8[0][0]                   
__________________________________________________________________________________________________
max_pooling1d_7 (MaxPooling1D)  (None, 5, 32)        0           conv1d_10[0][0]                  
__________________________________________________________________________________________________
max_pooling1d_8 (MaxPooling1D)  (None, 5, 32)        0           conv1d_12[0][0]                  
__________________________________________________________________________________________________
max_pooling1d_9 (MaxPooling1D)  (None, 5, 32)        0           conv1d_14[0][0]                  
__________________________________________________________________________________________________
max_pooling1d_10 (MaxPooling1D) (None, 5, 32)        0           conv1d_16[0][0]                  
__________________________________________________________________________________________________
max_pooling1d_11 (MaxPooling1D) (None, 5, 32)        0           conv1d_18[0][0]                  
__________________________________________________________________________________________________
max_pooling1d_12 (MaxPooling1D) (None, 5, 32)        0           conv1d_20[0][0]                  
__________________________________________________________________________________________________
max_pooling1d_13 (MaxPooling1D) (None, 5, 32)        0           conv1d_22[0][0]                  
__________________________________________________________________________________________________
flatten_5 (Flatten)             (None, 160)          0           max_pooling1d_6[0][0]            
__________________________________________________________________________________________________
flatten_6 (Flatten)             (None, 160)          0           max_pooling1d_7[0][0]            
__________________________________________________________________________________________________
flatten_7 (Flatten)             (None, 160)          0           max_pooling1d_8[0][0]            
__________________________________________________________________________________________________
flatten_8 (Flatten)             (None, 160)          0           max_pooling1d_9[0][0]            
__________________________________________________________________________________________________
flatten_9 (Flatten)             (None, 160)          0           max_pooling1d_10[0][0]           
__________________________________________________________________________________________________
flatten_10 (Flatten)            (None, 160)          0           max_pooling1d_11[0][0]           
__________________________________________________________________________________________________
flatten_11 (Flatten)            (None, 160)          0           max_pooling1d_12[0][0]           
__________________________________________________________________________________________________
flatten_12 (Flatten)            (None, 160)          0           max_pooling1d_13[0][0]           
__________________________________________________________________________________________________
concatenate (Concatenate)       (None, 1280)         0           flatten_5[0][0]                  
                                                                 flatten_6[0][0]                  
                                                                 flatten_7[0][0]                  
                                                                 flatten_8[0][0]                  
                                                                 flatten_9[0][0]                  
                                                                 flatten_10[0][0]                 
                                                                 flatten_11[0][0]                 
                                                                 flatten_12[0][0]                 
__________________________________________________________________________________________________
dense_10 (Dense)                (None, 200)          256200      concatenate[0][0]                
__________________________________________________________________________________________________
dense_11 (Dense)                (None, 100)          20100       dense_10[0][0]                   
__________________________________________________________________________________________________
dense_12 (Dense)                (None, 7)            707         dense_11[0][0]                   
==================================================================================================
Total params: 302,863
Trainable params: 302,863
Non-trainable params: 0
__________________________________________________________________________________________________
None
actual.shape[0]:46, actual.shape[1]:7
cnn: [492.451] 575.9, 437.3, 498.6, 473.7, 511.1, 417.1, 516.2

输出RMSE曲线和训练损失曲线:

多变量时间序列聚类 Python 多变量时间序列建模_数据挖掘_03


保存的模型结构图:

多变量时间序列聚类 Python 多变量时间序列建模_深度学习_04


总结

在这两篇关于CNN做时间序列预测任务的文章中,主要介绍了如下内容:

  • 如何为单变量数据开发多步时间序列预测的CNN模型;
  • 如何为多变量数据开发多通道多步时间序列预测的CNN模型;
  • 如何为多变量数据开发多头多步时间序列预测的CNN模型。

下一篇文章开始介绍LSTM实现时间序列预测任务。


多变量时间序列聚类 Python 多变量时间序列建模_多变量时间序列聚类 Python_05