【时间序列预测/分类】 全系列60篇由浅入深的博文汇总:传送门
通过之前有关LSTM的8遍基础教程和10篇处理时间序列预测任务的教程介绍,使用简单的序列数据示例,已经把LSTM的原理,数据处理流程,模型架构,Keras实现都讲清楚了。从这篇文章开始,将介绍有关时间序列预测和时间序列分类任务在真实数据集上的应用,你可以以此为模板,针对自己的业务需求进行二次开发。在本系列文章的最后会尝试通过自动调参脚本来辅助优化模型。
代码环境:
本文所有代码在 jupyter notebook 中编写。
其实家庭用电量预测仅仅是个“引子”,如果有电网数据的话,可以开发适合业务需求的模型,比如通过预测各时段各区域的用电量来协助电网更好地实现电能调度;除此之外,还可以用于发电量预测,比如光伏电站、风力发电站、水电站发电量预测…等等。模型一般不是问题,关键在数据和数据处理。
本文将介绍使用Pandas和Matplotlib对UCI上的家庭用电量数据集进行可视化,针对用电量预测问题,探究不同的网络架构、数据准备方式以及建模方法。
数据集名称为:Individual household electric power consumption Data Set(点击跳转数据集下载页面),该数据集是一个多变量时间序列数据集,采集了法国巴黎一个家庭近四年(2006年12月至2010年11月)的用电量,采样周期为1分钟。数据集的属性信息如下:
date
:格式dd/mm/yyyy;time
:格式hh:mm:ss;global_active_power
:每分钟的有功功率(千瓦);global_reactive_power
:每分钟的无功功率(千瓦);voltage
:每分钟的平均电压(伏特);global_intensity
:每分钟的平均电流强度(安培);sub_metering_1
:厨房有功电能(瓦时),主要包含洗碗机,烤箱和微波炉;sub_metering_2
:于洗衣房有功电能(瓦时),包含洗衣机,滚筒式烘干机,冰箱和电灯;sub_metering_3
:电热水器和空调有功电能(瓦时)。为了避免翻译错误引起歧义,现贴出数据集属性的英文介绍如下文所示:
date
: Date in format dd/mm/yyyytime
: time in format hh:mm:ssglobal_active_power
: household global minute-averaged active power (in kilowatt)global_reactive_power
: household global minute-averaged reactive power (in kilowatt)voltage
: minute-averaged voltage (in volt)global_intensity
: household global minute-averaged current intensity (in ampere)sub_metering_1
: energy sub-metering No. 1 (in watt-hour of active energy). It corresponds to the kitchen, containing mainly a dishwasher, an oven and a microwave (hot plates are not electric but gas powered).sub_metering_2
: energy sub-metering No. 2 (in watt-hour of active energy). It corresponds to the laundry room, containing a washing-machine, a tumble-drier, a refrigerator and a light.sub_metering_3
: energy sub-metering No. 3 (in watt-hour of active energy). It corresponds to an electric water-heater and an air-conditioner.以上九个属性中,可以作为特征的有七个,去掉的两个是日期和时间,因为序列化数据已经包含先后顺序了,所以不需要时间和日期数据,因此可以使用的数据为一个由七个变量(特征)组成的多元序列。
最后三个属性统计的电能消耗并不是家里所有的电路电能消耗。其它的电能消耗可以通过下式计算:
r e m a i n d e r = g l o b a l _ a c t i v e _ p o w e r × 1000 60 − ( s u b _ m e t e r i n g _ 1 + s u b _ m e t e r i n g _ 2 + s u b _ m e t e r i n g _ 3 ) remainder = \frac {global\_active\_power \times 1000}{60}− (sub\_metering\_1 + sub\_metering\_2 + sub\_metering\_3) remainder=60global_active_power×1000−(sub_metering_1+sub_metering_2+sub_metering_3)
这里注意:有功功率单位为 K W KW KW,有功电能的单位为瓦时( w a t t − h o u r watt-hour watt−hour),以上数据都是间隔一分钟测得的,因此公式的前半部分就是通过有功率乘以时间(1/60小时,也就是一分钟)计算得到总的有功电能消耗,单位是瓦时。减去公式的后半部分(后三个属性的加和),得到了家庭中其他电路的电能消耗。
该数据集已成为评估时间序列预测和多步预测(特别是预测有功功率)的机器学习方法的标准,接下来我们来探究该数据集。
在加载之前,先查看数据详情信息:
查看是否有异常数据:
使用Pandas中的 read_csv()
函数加载数据,如果是excel或者csv文件通常不用考虑分隔方式,用默认的配置就可以加载;但是对于 txt 文件则需要考虑,可以看到数据集是用 ;
来分隔数据的,下面加载数据:
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'])
read_csv()
参数说明:
seq
参数:指定列之间的分隔符为 ';'
,字符串格式,默认为 ‘,’
;header
参数:指定哪一行作为列名,header=0
表示第一行数据作为列名,而不是文件的第一行作为列名;low_memory
:在内部对文件进行分块处理,从而在解析时减少了内存使用,但可能是混合类型推断。默认为 True
,设置为 False
确保没有混合类型;infer_datetime_format
:设置该参数为 True
和 parse_dates
参数,pandas会推断列中日期时间字符串的格式,如果可以推断出,则切换到更快方法来解析它们。在某些情况下,这可以使解析速度提高5-10倍;engine
:要使用的解析器引擎。C引擎速度更快,而python引擎当前功能更完善;parse_dates
:{'datetime':[0,1]}
将原数据中的第1、2列作为新的列名为 ‘datatime’
的列,即将原来的日期列、时间列合并为日期时间一列;index_col
:指定’datetime’列为索引列;更多参数配置,请查看官方文档:点开它带走我
1. 查看数据的shape:
dataset.shape
输出:
(2075259, 7)
2. 查看数据前10行:
dataset.head(10)
dataset.isna().sum()
输出:
Global_active_power 0
Global_reactive_power 0
Voltage 0
Global_intensity 0
Sub_metering_1 0
Sub_metering_2 0
Sub_metering_3 25979
dtype: int64
4. 查看有默认标记的异常值:
dataset.iloc[dataset.values == '?'].count()
输出:
Global_active_power 155874
Global_reactive_power 155874
Voltage 155874
Global_intensity 155874
Sub_metering_1 155874
Sub_metering_2 155874
Sub_metering_3 0
dtype: int64
5. 有默认标记的异常值处理
为了提高数据处理效率,将所有标记为’?'的异常值用 np.nan
替换,将数据作为一个浮点值数组来处理。
dataset.replace('?', np.nan, inplace=True)
再查看异常值:
dataset.iloc[dataset.values == '?'].count()
输出:
Global_active_power 0
Global_reactive_power 0
Voltage 0
Global_intensity 0
Sub_metering_1 0
Sub_metering_2 0
Sub_metering_3 0
dtype: int64
再查看缺失值:
dataset.isna().sum()
输出:
Global_active_power 25979
Global_reactive_power 25979
Voltage 25979
Global_intensity 25979
Sub_metering_1 25979
Sub_metering_2 25979
Sub_metering_3 25979
dtype: int64
6. 添加新列
使用上一节中计算剩余用电量的计算公式,添加新列作为新的特征序列:
values = dataset.values.astype('float32')
dataset['sub_metering_4'] = (values[:,0] * 1000 / 60) - (values[:,4] + values[:,5] + values[:,6]))
查看新的数据shape:
dataset.shape
输出:
(2075259, 8)
查看数据前十行:
dataset.head(10)
7. 保存为新的文件:
dataset.to_csv('household_power_consumption.csv')
1. 首先读取新保存的数据:
dataset = pd.read_csv('household_power_consumption.csv', header=0,
infer_datetime_format=True, engine='c',
parse_dates=['datetime'], index_col=['datetime'])
2.绘制不同特征的子图:
创建一个包含八个子图的图像,每个子图对应一个变量。完整代码如下:
def plot_features(dataset):
plt.figure(figsize=(16,12), dpi=200)
for i in range(len(dataset.columns)):
plt.subplot(len(dataset.columns), 1, i+1)
feature_name = dataset.columns[i]
plt.plot(dataset[feature_name])
plt.title(feature_name, y=0)
plt.grid(linestyle='--', alpha=0.5)
plt.tight_layout()
plt.show()
plot_features(dataset)
3. 每年的有功功率变化图
为每一年创建一个有功功率图,观察是否有相同的模式。因为2006年只有不到一个月的数据,所以不绘制该年的子图。完整代码如下:
def plot_year_gap(dataset, years_list):
plt.figure(figsize=(16,12), dpi=150)
for i in range(len(years_list)):
ax = plt.subplot(len(years_list), 1, i+1)
ax.set_ylabel(r'$KW$')
year = years_list[i]
year_data = dataset[str(year)]
plt.plot(year_data['Global_active_power'])
plt.title(str(year), y=0, loc='left')
plt.grid(linestyle='--', alpha=0.5)
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()
years = ['2007', '2008', '2009', '2010']
plot_year_gap(dataset, years)
输出:
因为设置了 infer_datetime_format
参数,所以可直接使用年份索引进行截取数据。通过对以上数据进行分析可知,每年2月和8月内的某段时间耗电量有明显下降。我们似乎也看到夏季(6、7、8月)的用电量呈下降趋势,我们还可以在第以、第三和第四个图中有一些缺失的数据。
接下来,进一步查看每个月的用电情况。比如查看2008年每个月的有功功率,可能有助于梳理出几个月的变化规律,如每日和每周用电状况规律。完整代码如下:
def plot_month_gap(dataset, year, months_list):
plt.figure(figsize=(16,12), dpi=150)
for i in range(len(months_list)):
ax = plt.subplot(len(months_list), 1, i+1)
ax.set_ylabel(r'$KW$')
month = str(year) + '-' + str(months_list[i])
month_data = dataset[month]
plt.plot(month_data['Global_active_power'])
plt.title(month, y=0, loc='left')
plt.grid(linestyle='--', alpha=0.5)
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()
year = 2008
months = [i for i in range(1, 13)]
plot_month_gap(dataset, year, months)
接下来进一步查看每日的用电情况。完整代码如下:
def plot_day_gap(dataset, year, month, days_list):
plt.figure(figsize=(20,24), dpi=150)
for i in range(len(days_list)):
ax = plt.subplot(len(days_list), 1, i+1)
ax.set_ylabel(r'$KW$',size=6)
day = str(year) + '-0' + str(month) + '-' + str(days_list[i])
day_data = dataset[day]
gcp_data = day_data['Global_active_power']
plt.plot(gcp_data)
plt.title(day, y=0, loc='left', size=6)
plt.grid(linestyle='--', alpha=0.5)
plt.xticks(rotation=0)
plt.show()
year = 2008
month = 8
days = [i for i in range(1, 32)]
plot_day_gap(dataset, year, month, days)
输出:
另一个需要考虑的重要方面是变量的分布。例如,了解观测值的分布是高斯分布还是其他分布。可以通过为每个特征创建一个直方图来研究数据分布。完整代码如下:
def dataset_distribution(dataset):
plt.figure(figsize=(16,12), dpi=150)
for i in range(len(dataset.columns)):
ax = plt.subplot(len(dataset.columns), 1, i+1)
ax.set_ylabel(r'$numbers$',size=10)
feature_name = dataset.columns[i]
dataset[feature_name].hist(bins=100)
plt.title(feature_name, y=0, loc='right', size=20)
plt.grid(linestyle='--', alpha=0.5)
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()
dataset_distribution(dataset)
输出:
可以看到有功和无功功率、强度以及分表功率都是向瓦时或千瓦倾斜的分布,电压数据呈高斯分布。有功功率的分布似乎是双峰的,这意味着它看起来有两组观测值。可以通过查看四年来的数据的有功功率分布来验证,完整代码如下:
def plot_year_dist(dataset, years_list):
plt.figure(figsize=(16,12), dpi=150)
for i in range(len(years_list)):
ax = plt.subplot(len(years_list), 1, i+1)
ax.set_ylabel(r'$numbers$')
ax.set_xlim(0, 5) # 设置x轴显示限制,保证每个子图x刻度相同
year = years_list[i]
year_data = dataset[str(year)]
year_data['Global_active_power'].hist(bins=100, histtype='bar')
plt.title(str(year), y=0, loc='right', size=10)
plt.grid(linestyle='--', alpha=0.5)
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()
years = ['2007', '2008', '2009', '2010']
plot_year_dist(dataset, years)
可以看到,有功功率分布看起来非常相似。这种分布确实是双峰的,一个峰值约为0.3kw,另一个峰值约为1.3kw。随着有功功率(x轴)的增加,高功率用电时间点的数量越来越少。
所确定的群体可能在一年中的不同季节有所不同。可以通过查看一年中每个月的有功功率分布来对此进行调查。完整代码如下:
def plot_month_dist(dataset, year, months_list):
plt.figure(figsize=(16,12), dpi=150)
for i in range(len(months_list)):
ax = plt.subplot(len(months_list), 1, i+1)
ax.set_ylabel(r'$KW$')
ax.set_xlim(0, 5)
month = str(year) + '-' + str(months_list[i])
month_data = dataset[month]
month_data['Global_active_power'].hist(bins=100, histtype='bar')
plt.title(month, y=0, loc='right', size=10)
plt.grid(linestyle='--', alpha=0.5)
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()
year = 2008
months = [i for i in range(1, 13)]
plot_month_dist(dataset, year, months)
输出:
可以看到,在北半球较暖的月份(法国巴黎),有功功率比较大的点少,而在较冷的月份,有功功率比较大的点多。在12月到3月的较冷月份,可以看到有更大的千瓦值(大约 3.7 − 4 K W 3.7-4KW 3.7−4KW)。
对于我们使用的家庭用电量数据集来说,可以提出很多建模问题,比如:
以上四类预测问题称为多步预测。利用所有特征进行预测的模型称为多变量多步预测模型。每个模型都不局限于日期的大小,还可以根据需求对更细粒度的问题进行建模,比如一天内某各时段每分钟的耗电量预测问题。这有助于电力公司进行电能调度,是一个广泛研究的重要问题。
在为建模准备这些数据时有很大的灵活性。具体的数据准备方法及其效益实际上取决于问题的框架选择和建模方法。然而,以下是可能有用的一般数据准备方法的列表:
有许多简单的人为因素可能有助于从数据中提取工程特征,从而使特定日期更易于预测。一些例子包括:
对于这个问题,可能有四类方法值得探讨:
朴素的方法包括一些非常简单但通常非常有效的假设。例如:
经典的线性方法适用于单变量时间序列预测问题。常用的方法有:
机器学习方法要求将问题构造为有监督学习问题。这将要求将序列的滞后观测值作为输入特征,丢弃数据中的时间关系。常用的非线性和集成方法有:
为了确保模型拟合和评估,要保留数据中的时间结构,需要做大量的特征工程,来增加特征,专业程度较高。当有多个特征时,可能变得对业务需求不适用。
一般来说,神经网络在自回归型问题上并没有被证明是非常有效的。然而,卷积神经网络等技术能够从原始数据(包括一维信号数据)中自动学习复杂特征。而递归神经网络,例如LSTM,能够直接在多个输入数据的并行序列中学习。此外,这些方法的组合如CNN-LSTM和ConvLSTM等方法,在时间序列分类任务中被证明是有效的。这些方法可以处理大量数据和多个输入变量(特征)任务。
之后文章会介绍以上提到的的建模方法。
参考:
https://matplotlib.org/index.html
https://matplotlib.org/api/_as_gen/matplotlib.pyplot.tight_layout.html#matplotlib.pyplot.tight_layout
https://matplotlib.org/api/_as_gen/matplotlib.pyplot.subplot.html?highlight=subplot#matplotlib.pyplot.subplot
http://www.imooc.com/wenda/detail/574859
https://machinelearningmastery.com/how-to-load-and-explore-household-electricity-usage-data/