时间序列预测12:用电量预测 02 朴素模型多步预测建模

上文,本文使用朴素模型来对家庭用电数据集进行单变量多步预测。主要内容如下:

  • 如何为模型准备数据集;
  • 如何开发指标、划分数据集、评估预测模型;
  • 如何开发和评估和比较朴素模型的性能;


如何开发用于多时间步家庭用电量预测的朴素模型

1. 数据处理

1.1 异常值处理

上一篇文章中得知,数据集中有大量标记为 的异常值,这就需要把这些异常值处理为np.nan的浮点类型,提高数据处理速度。代码如下:

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

# 函数的参数在上篇文章中已经介绍,此处不再赘述。
dataset = pd.read_csv('household_power_consumption.txt', sep=';', header=0, 
                      low_memory=False, infer_datetime_format=True, engine='c',
                      parse_dates={'datetime':[0,1]}, index_col=['datetime'])
                      
dataset.replace('?', np.nan, inplace=True) # 替换异常值
values = dataset.values.astype('float32') # 统一数据类型为float类型,提高精度和速度

1.2 缺失值处理

刚才我们把异常值替换成了缺失值(nan),这一步我们填充这些缺失的值。一个非常简单的方法是复制前一天同一时刻的采样值。使用自定义的 fill_missing() 函数来实现:

def fill_missing(values):
    '''
    该函数实现缺失值填充
    思路:将前一天同一时刻的采样值用来填充缺失值
    '''
    one_day = 60 * 24
    for row in range(values.shape[0]):# 行循环
        for col in range(values.shape[1]): # 列循环
            if np.isnan(values[row, col]):
                values[row, col] = values[row - one_day, col]

fill_missing(dataset.values) # 填充缺失值

处理完成后,添加新列并将处理完成的数据保存,计算公式已经在前一篇文章中解释过,此处不赘述。代码如下:

# 添加剩余用电量的列
values = dataset.values
dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6])
dataset.to_csv('household_power_consumption.csv')

至此,数据处理完成。


2. 模型评估

本节介绍如何开发和评估家庭用电量数据集的预测模型。本节主要包括以下四部分内容:

  • 问题建模
  • 评价指标
  • 训练集测试集划分
  • 前向验证

2.1 问题建模

有许多方法探索家庭用电量数据集。本文使用这些数据来探索一个具体的问题:用最近一段时间的数据来预测未来一周的用电量是多少。这需要建立一个预测模型预测未来七天每天的总有功功率。这类问题被称为多步时间序列预测问题。利用多个输入变量的模型可称为多变量(特征)多步时间序列预测模型。

要达到这样的目的,为了便于处理,先把原数据每分钟的耗电量采样数据重新采样成每日总耗电量。这不是必需的,但是有意义,因为我们关心的是每天的总功率。可以使用 Pandas中的 resample() 函数实现,设置参数 “D” 调用此函数,以允许按日期-时间索引加载数据并按天将数据重新采样分组。然后,可以通过计算每天所有采样值的总和,为这8个变量(特征)创建新的日耗电量数据集。

查看重新采样的结果:

daily_groups = dataset.resample('D')
daily_groups.sum()

在这里插入图片描述
新采样包含的原采样点个数统计:

daily_groups.count()

在这里插入图片描述
重采样:

daily_data = daily_groups.sum()

可以从以上输出信息得知重新采样之后,数据的shape为 (1442, 8)

保存为新的csv文件:

daily_data.to_csv('household_power_consumption_days.csv'

2.2 评价指标

预测输出为七个值组成的向量,每个值代表预测的未来一周中每一天的耗电量。多步预测问题通常是对每一个预测时间步分别进行评价。有如下建议:

  • 每隔一定周期(例如预测1天、预测3天的模型)对技能进行评价;
  • 根据预测日期长短的不同对比模型(例如,擅长预测1天的模型与擅长预测5天的模型);

总功率的单位是千瓦,应该使用具有统一单位的误差度量方法。**均方根误差(RMSE)绝对平均误差(MAE)**都符合这个单位统一的要求,本文采用更常用的 RMSE。与MAE不同,RMSE对预测错误的惩罚更大。此问题的性能指标是从第1天到第7天的每天的的RMSE。用分数来评估模型的表现以帮助模型的选择,是便捷有效的方法。一个可用的评估分数是所有单日的 RMSE。自定义的 evaluate forecast() 函数可以实现这个功能。


2.3 训练集和测试集划分

使用前三年的数据训练预测模型,最后一年的数据评估模型。将数据按照标准周(星期天开始到星期六结束)进行划分。这对模型的选定是一种有效的方法,可以预测未来一周的耗电量。也有助于建模,模型可用于预测特定的一天(如周三)或整个序列。

以周为单位对数据进行处理,先截取测试数据,剩下的为训练数据。数据的最后一年是2010年,2010年的第一个星期天是1月3日,数据在2010年11月26日结束,最近的最后一个星期六是11月20日。一共46周的测试数据。下面提供测试数据集的第一行和最后一行每日数据以供确认。
在这里插入图片描述
由之前的统计可知(上文中的图),每日耗电量数据从2006年12月16日开始。数据集中的第一个周日是12月17日,这是第二行数据。将数据组织成标准周,可提供159个完整的标准周用于训练预测模型。
在这里插入图片描述
自定义的 split_dataset() 函数将每天的数据分成训练集和测试集,并将它们组织成标准周。特定的行偏移量用于使用数据集的知识拆分数据。然后使用NumPy的 split() 函数将分割的数据集组织为每周数据。此处注意 split() 函数的功能是等间距划分,如果数据能整除会报错。


2.4 前向验证

模型使用前向验证进行评估。流程为:先提供给模型一周的数据,预测第二周;接着给第二周的实际数据,用前两周的数据,预测第三周;然后给第三周的实际数据,使用前三周的数据,预测第四周,以此类推…在下面用输入数据和输出/预测数据来演示:

Input,  Predict
[Week1]  Week2
[Week1 + Week2]  Week3
[Week1 + Week2 + Week3]  Week4
...

在此数据集上评估预测模型的前向验证方法通过自定义的 evaluate_model() 函数实现。model_func 参数为函数名。提供的这个函数用来定义模型,拟合模型,并预测一周的用电量。然后,evaluate_model() 函数根据测试数据集对模型所做的预测进行求值。


2.5 朴素预测模型(简单模型)

在任何新的预测问题上,测试朴素预测模型是很重要的。朴素模型的预测结果定量地显示了预测问题的难度,并提供了一个标准性能(baseline),通过它可以评估更复杂的预测方法。本节比较三种针对家庭功率预测问题的简单预测方法:

  • 每日持久性预测;
  • 每周持久性预测;
  • 一年前每周的持久性预测;

2.5.1 每日持久性预测

该模型取预测期前最后一天(如周六)的有功功率,作为预测期内(周日至周六)每天的有功功率值。daily_persistence() 函数实现了该策略。

2.5.2 每周持久性预测

该模型预测取前一周的整个时间作为下一周的预测。这是基于下周将与本周非常相似的想法。weekly_persistence() 函数实现了该策略。

2.5.3 一年前每周的持久性预测

类似于利用上周预测下周的想法,即根据下周将与一年前的同一周相似的想法,使用52周前的观察周作为预测。year_persistence() 函数实现了该策略。


3. 完整代码

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import math
import sklearn.metrics as skm

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

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 evaluate_model(model_func, train, test):
    '''
    该函数实现评估单个模型
    '''
    history = [x for x in train] # # 以周为单位的数据列表
    predictions = [] # 每周的前项预测值
    for i in range(len(test)):
        yhat_sequence = model_func(history) # 预测每周的耗电量
        predictions.append(yhat_sequence)
        history.append(test[i, :]) # 将测试数据中的采样值添加到history列表,以便预测下周的用电量
    predictions = np.array(predictions)
    score, scores = evaluate_forecasts(test[:, :, 0], predictions) # 评估一周中每天的预测损失
    return score, scores

def daily_persistence(history):
    last_week = history[-1] # 获取之前一周七天的总有功功率
    value = last_week[-1, 0] # 获取前一周最后一天的总有功功率
    forecast = [value for _ in range(7)] # 准备7天预测
    return forecast

def weekly_persistence(history):
    last_week = history[-1] # 将之前一周的数据作为预测数据
    return last_week[:, 0]

def week_one_year_ago_persistence(history):
    last_week = history[-52] # 将去年同一周的数据预测数据
    return last_week[:, 0]


def model_predict_plot(dataset, days):
    train, test = split_dataset(dataset.values)
    #定义要评估的模型的名称和函数
    models = dict()
    models['daily'] = daily_persistence
    models['weekly'] = weekly_persistence
    models['week-oya'] = week_one_year_ago_persistence
    
    plt.figure(figsize=(8,6), dpi=150)
    for name, func in models.items():
        score, scores = evaluate_model(func, train, test)
        summarize_scores(name, score, scores)
        plt.plot(days, scores, marker='o', label=name)
    plt.grid(linestyle='--', alpha=0.5)
    plt.ylabel(r'$RMSE$', size=15)
    plt.title('三种模型预测结果比较', color='blue', size=20)
    plt.legend()
    plt.show()

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']
    model_predict_plot(dataset, days)

运行该示例首先打印每个模型的总分数和每日分数。我们可以看到,周策略的表现优于日策略,一年前的一周数据预测当前周的策略表现最好。我们可以在每个模型的总体RMSE得分和每个预测日的得分中看到这一点。一个例外是第一天(星期天)的预测错误,在这一天,每日持久性模型的性能似乎优于两周策略。我们可以使用一年前的一周数据预测当前周的策略,以465.294千瓦的总RMSE作为性能基线,来评估其它复杂模型在该数据集上的表现。

actual.shape[0]:46, actual.shape[1]:7
daily: [511.886] 452.9, 596.4, 532.1, 490.5, 534.3, 481.5, 482.0

actual.shape[0]:46, actual.shape[1]:7
weekly: [469.389] 567.6, 500.3, 411.2, 466.1, 471.9, 358.3, 482.0

actual.shape[0]:46, actual.shape[1]:7
week-oya: [465.294] 550.0, 446.7, 398.6, 487.0, 459.3, 313.5, 555.1

在这里插入图片描述


下一篇文章介绍ARIMA模型实现用电量多步预测。
在这里插入图片描述


参考:
https://machinelearningmastery.com/naive-methods-for-forecasting-household-electricity-consumption/
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.resample.html?highlight=resample#pandas.DataFrame.resample
https://www.statsmodels.org/stable/generated/statsmodels.graphics.tsaplots.plot_acf.html?highlight=plot_acf
https://matplotlib.org/index.html

发布了167 篇原创文章 · 获赞 686 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/weixin_39653948/article/details/105412563