机器学习笔记八-----------------Prophet模型实战:家用电量数据的时间序列分析与预测
1. 为什么选择Prophet来做家庭用电量预测?
如果你手头有一份记录了家庭每分钟用电量的数据,想预测未来几天的用电情况,你会怎么做?我猜很多人的第一反应是:“这听起来好复杂,是不是得用很高深的模型?” 其实不然,Facebook开源的 Prophet 模型,就是专门为这类“带明显周期性规律”的时间序列预测而生的,它最大的特点就是对使用者极其友好,你不需要是时间序列专家,也能快速上手得到不错的预测结果。
我最初接触这个家用电量数据集时,也尝试过ARIMA等传统模型,但过程挺折腾的,光是定阶、差分、检验平稳性就够喝一壶的。而Prophet把很多复杂的东西都封装好了,它本质上是一个可加性模型,把时间序列分解成几个直观的部分:趋势、季节性和节假日效应。你可以把它想象成“搭积木”:
- 趋势项:描述数据长期的上升或下降走向,比如家庭用电量随着家电增多可能缓慢增长。
- 季节性项:捕捉重复的周期模式,比如天周期(白天用电多,深夜用电少)、周周期(工作日和周末模式不同)、年周期(夏季空调和冬季取暖导致的用电高峰)。
- 节假日项:处理那些不规律但可预知的影响,比如法定长假、特殊节日,家里没人用电量会骤降。
对于家庭用电数据来说,它的天周期和周周期通常非常明显,这正是Prophet最擅长捕捉的。而且Prophet对缺失值和异常值不那么敏感,拟合速度快,还自带方便的可视化功能,对于快速分析和验证想法特别有帮助。当然,它也不是万能的,对于受非常多外部变量(比如突发天气、电价变动)剧烈影响的序列,可能需要更复杂的模型。但对于我们手头这个“家庭用电量预测”的入门到进阶需求,Prophet绝对是一把趁手的好工具。
2. 数据准备与预处理:为Prophet铺好路
Prophet模型要求输入的数据格式非常简单:一个两列的Pandas DataFrame,一列是时间戳 ds,一列是观测值 y。我们的原始数据比这丰富得多,所以预处理的关键,就是从多变量中提取出我们想预测的那个序列,并转换成Prophet需要的格式。
2.1 加载与清洗数据
首先,我们加载经过初步处理的数据集(假设你已经按照上一篇笔记的步骤,将数据清洗并保存为CSV文件)。这里我们重点关注有功功率作为预测目标。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from prophet import Prophet
# 加载数据
df = pd.read_csv('household_power_consumption.csv', index_col=0, parse_dates=True)
print(df.head())
print(f"数据形状: {df.shape}")
接下来,我们创建Prophet所需的DataFrame。Prophet的 ds 列必须是日期时间格式,y 列必须是数值。我们选择 Global_active_power(总有功功率)作为 y。
# 重置索引,将日期时间列变为普通列
df_prophet = df.reset_index()[['datetime', 'Global_active_power']].copy()
# 重命名列以符合Prophet要求
df_prophet.columns = ['ds', 'y']
# 检查是否有缺失值
print(f"缺失值数量: {df_prophet['y'].isna().sum()}")
# 如果有缺失值,可以进行前向填充或插值,Prophet本身也能处理一定缺失
# df_prophet['y'].fillna(method='ffill', inplace=True)
print(df_prophet.head())
2.2 数据重采样:从分钟级到小时级
原始数据是每分钟一条记录,四年的数据量超过200万条。虽然Prophet能处理,但这么高的频率可能包含大量噪声,且训练速度会变慢。对于用电量预测,小时级或日级数据通常更具实际意义,也更能体现宏观规律。我们这里演示降到小时级。
# 将数据重采样为小时平均值
df_hourly = df_prophet.set_index('ds').resample('H').mean().reset_index()
print(f"重采样后数据形状: {df_hourly.shape}")
print(df_hourly.head())
# 可视化一小段数据,看看重采样效果
plt.figure(figsize=(14, 5))
plt.plot(df_hourly['ds'].iloc[:168], df_hourly['y'].iloc[:168]) # 显示第一周的数据
plt.title('家庭有功功率 - 小时平均值(第一周)')
plt.xlabel('日期时间')
plt.ylabel('有功功率 (kW)')
plt.grid(True, linestyle='--', alpha=0.5)
plt.tight_layout()
plt.show()
重采样后,数据量从200多万条减少到约3.5万条(四年的小时数),趋势和季节模式会更加清晰。这一步不是必须的,你可以根据预测需求(比如预测未来一天每小时的用电量)来决定使用原始分钟数据还是重采样数据。我实测下来,对于初次建模,用小时数据更容易快速看到效果,减少干扰。
3. 构建与训练第一个Prophet模型
数据准备好了,现在可以开始训练模型了。Prophet的API设计得非常简洁,基本模式是“创建模型 -> 拟合数据 -> 预测未来”。
3.1 基础模型训练
我们先使用所有默认参数来训练一个模型,看看基线效果。
# 初始化Prophet模型
model = Prophet()
# 拟合数据
model.fit(df_hourly)
# 创建未来时间框架:预测未来7天(168小时)
future = model.make_future_dataframe(periods=168, freq='H') # freq='H' 表示小时频率
print(future.tail())
# 进行预测
forecast = model.predict(future)
print(forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail())
forecast 这个DataFrame包含了很多列,其中最重要的几列是:
ds: 日期时间yhat: 预测值yhat_lower和yhat_upper: 预测值的置信区间下界和上界(默认80%置信区间)
3.2 可视化预测结果
Prophet内置了方便的绘图函数,可以一次性展示预测结果、趋势和季节性成分。
# 绘制预测结果
fig1 = model.plot(forecast)
plt.title('Prophet 预测结果 - 家庭有功功率')
plt.xlabel('日期')
plt.ylabel('有功功率 (kW)')
plt.show()
# 绘制预测的各个组件(趋势、周季节性、年季节性)
fig2 = model.plot_components(forecast)
plt.show()
通过 plot_components 图,你可以直观地看到:
- 整体趋势:几年内家庭用电量的长期变化。
- 周季节性:一周七天中每天的典型用电模式。通常工作日和周末会有明显差异。
- 年季节性:一年中不同月份的用电模式。可能会看到夏季和冬季的用电高峰。
第一次运行看到这些图,你可能会发现年季节性不明显,或者趋势线有点奇怪。别急,这是因为我们还没开始调优。默认模型可能没有捕捉到数据的所有特性,接下来我们就来调整模型参数。
4. Prophet核心参数调优指南
Prophet的强大之处在于它提供了许多直观的参数,让你可以根据对数据的理解来调整模型。调优不是瞎试,而是有逻辑的。下面我结合家用电量数据的特点,分享几个最常用、最有效的参数。
4.1 调整趋势灵活性:changepoint_prior_scale
趋势项默认假设时间序列中有一些“变点”,趋势在这些点上可能改变方向或斜率。changepoint_prior_scale 参数控制趋势变化的灵活度:
- 值越大(如0.05, 0.1),模型更倾向于让趋势线“弯曲”去拟合数据的波动,可能过拟合。
- 值越小(如0.001, 0.005),趋势线更平滑、更刚性,可能欠拟合。
对于用电量数据,通常不会有非常剧烈、频繁的趋势突变,所以建议从一个较小的值开始尝试。
# 尝试一个更平滑的趋势
model_smooth = Prophet(changepoint_prior_scale=0.005)
model_smooth.fit(df_hourly)
forecast_smooth = model_smooth.predict(future)
fig = model_smooth.plot(forecast_smooth)
plt.title('趋势平滑度调整后预测')
plt.show()
4.2 调整季节性强度:seasonality_prior_scale
这个参数控制季节性成分的强度。如果你觉得模型捕捉的周波动或年波动太强或太弱,可以调整它。
- 值越大,季节性效应越强。
- 值越小,季节性效应越弱。
用电量的周周期性通常很强,我们可以适当增强周季节性。
# 增强周季节性,保持年季节性默认
model_season = Prophet(seasonality_prior_scale=10.0) # 默认是10,可以尝试增大到15或20
model_season.fit(df_hourly)
forecast_season = model_season.predict(future)
fig = model_season.plot_components(forecast_season)
plt.show()
4.3 自定义季节性:添加额外周期
除了内置的年、周、日季节性,你还可以添加自定义的季节性周期。比如,家庭用电在一天内有非常明显的模式(午休、晚餐后高峰),我们可以显式地添加一个“日周期”季节性,即使我们的数据是小时级的。
# 添加日季节性(周期为24小时)
model_custom = Prophet()
model_custom.add_seasonality(name='daily', period=1, fourier_order=8) # period=1表示1天,因为我们的ds单位是天
model_custom.fit(df_hourly)
forecast_custom = model_custom.predict(future)
fig = model_custom.plot_components(forecast_custom)
plt.show()
这里 fourier_order 是控制季节性形状复杂度的参数,值越高越能拟合复杂的波形。对于日周期,8到12是一个常用范围。
4.4 处理节假日效应
节假日(如国庆长假、春节)对家庭用电模式影响巨大。Prophet允许你传入一个节假日的数据框来建模这种效应。
# 创建一个节假日数据框(示例:法国的节假日,可根据实际情况修改)
holidays = pd.DataFrame({
'holiday': 'french_holiday',
'ds': pd.to_datetime(['2007-01-01', '2007-05-01', '2007-07-14', '2008-01-01', '2008-05-01', '2008-07-14']), # 示例日期
'lower_window': -1, # 节假日前一天也受影响
'upper_window': 1, # 节假日后一天也受影响
})
model_holiday = Prophet(holidays=holidays)
model_holiday.fit(df_hourly)
forecast_holiday = model_holiday.predict(future)
fig = model_holiday.plot_components(forecast_holiday)
plt.show()
在实际项目中,你需要一份完整的节假日列表。这个功能对于预测节假日期间的用电低谷特别有用。
5. 模型评估与性能对比
模型调优不能光靠眼睛看,我们需要定量评估。Prophet本身不内置复杂的评估函数,但我们可以用历史数据做“模拟预测”,然后用常见的指标来评估。
5.1 模拟历史预测与评估指标
Prophet提供了 cross_validation 函数,可以方便地进行时间序列交叉验证。
from prophet.diagnostics import cross_validation, performance_metrics
from prophet.plot import plot_cross_validation_metric
# 进行交叉验证:初始训练期2年,每180天预测一次未来7天
df_cv = cross_validation(model, initial='730 days', period='180 days', horizon='7 days')
df_cv.head()
# 计算性能指标
df_p = performance_metrics(df_cv)
print(df_p.head())
# 可视化某个评估指标,例如均方根误差(RMSE)
fig = plot_cross_validation_metric(df_cv, metric='rmse')
plt.title('交叉验证 RMSE')
plt.show()
performance_metrics 会返回在不同预测水平(horizon)下的多个评估指标,包括:
- mse: 均方误差
- rmse: 均方根误差
- mae: 平均绝对误差
- mape: 平均绝对百分比误差
- mdape: 中位数绝对百分比误差
- smape: 对称平均绝对百分比误差
- coverage: 预测区间覆盖率
对于用电量预测,我比较关注 RMSE(衡量预测值与真实值的绝对偏差)和 MAPE(衡量相对误差百分比)。MAPE能让我们直观了解预测的平均误差水平,比如MAPE=5%,意味着平均预测误差在5%左右。
5.2 不同参数模型的对比
我们可以用循环来测试一组参数,并比较它们的性能。
import itertools
param_grid = {
'changepoint_prior_scale': [0.001, 0.01, 0.1],
'seasonality_prior_scale': [5.0, 10.0, 15.0],
}
# 生成所有参数组合
all_params = [dict(zip(param_grid.keys(), v)) for v in itertools.product(*param_grid.values())]
rmses = [] # 存储每个参数组合的RMSE
# 使用交叉验证评估每个参数组合
for params in all_params:
m = Prophet(**params).fit(df_hourly)
df_cv = cross_validation(m, initial='730 days', period='180 days', horizon='7 days', parallel="processes")
df_p = performance_metrics(df_cv, rolling_window=1)
rmses.append(df_p['rmse'].values[0])
# 找出最佳参数
tuning_results = pd.DataFrame(all_params)
tuning_results['rmse'] = rmses
print(tuning_results.sort_values('rmse').head())
通过这样的网格搜索,你可以找到在历史数据上表现最好的一组参数。但切记,时间序列交叉验证的结果只是参考,最终模型要在“未来”数据上表现好才行,要小心过拟合历史数据中的特殊模式。
6. 多变量时间序列预测的进阶尝试
到目前为止,我们只用了有功功率 (Global_active_power) 这一个变量。但原始数据集有多个变量(电压、电流、各子电路用电量)。Prophet虽然主要是单变量模型,但它支持添加额外的回归量,这为我们进行多变量预测提供了可能。
6.1 准备额外回归量
假设我们认为 电压 和 厨房用电量 可能对总有功功率有影响,我们可以把它们作为额外特征加入模型。
# 准备额外回归量数据框
df_regressors = df.reset_index()[['datetime', 'Voltage', 'Sub_metering_1']].copy()
df_regressors.columns = ['ds', 'voltage', 'kitchen_power']
# 将回归量与主要数据合并(确保时间戳对齐)
df_prophet_with_reg = pd.merge(df_prophet, df_regressors, on='ds', how='inner')
# 注意:对于预测未来的数据,你还需要提供未来时间的回归量值!
# 这通常需要你先对回归量本身进行预测,或使用已知/假设的未来值。
# 这里为演示,我们只使用历史数据拟合。
6.2 添加回归量并训练模型
model_reg = Prophet()
# 添加额外回归量
model_reg.add_regressor('voltage')
model_reg.add_regressor('kitchen_power')
# 拟合数据
model_reg.fit(df_prophet_with_reg)
# 注意:创建future数据框时,也必须包含这些回归量的未来值
# future_reg = model_reg.make_future_dataframe(periods=168, freq='H')
# future_reg['voltage'] = ... # 需要填充未来电压值
# future_reg['kitchen_power'] = ... # 需要填充未来厨房用电值
# forecast_reg = model_reg.predict(future_reg)
使用额外回归量是一把双刃剑。如果这些变量与目标变量有强相关性且未来值可准确获知或预测,那么模型精度可能会提升。但如果未来值不确定,或者引入了无关噪声,反而可能降低预测稳定性。对于家庭用电量,像“星期几”、“是否节假日”这类确定性信息作为回归量往往更可靠。我个人的经验是,先做好单变量模型,再谨慎地加入一两个你认为最重要的外部变量进行尝试。
7. 实战:预测未来一周用电量并解读结果
经过调优和评估,我们选定一个最终模型,来预测未来一周(168小时)的家庭用电量。
# 假设我们选择了以下参数作为最终模型
final_model = Prophet(
changepoint_prior_scale=0.01,
seasonality_prior_scale=12.0,
holidays=holidays, # 使用之前定义的节假日
seasonality_mode='additive',
daily_seasonality=True, # 启用内置日季节性(24小时周期)
weekly_seasonality=True,
yearly_seasonality=True
)
final_model.fit(df_hourly)
# 创建未来一周的时间框架
future_final = final_model.make_future_dataframe(periods=168, freq='H')
forecast_final = final_model.predict(future_final)
# 绘制最终预测图
fig = final_model.plot(forecast_final)
plt.title('家庭有功功率 - 未来一周预测')
plt.xlabel('日期时间')
plt.ylabel('有功功率 (kW)')
# 可以在图上标记出预测区间
ax = fig.gca()
ax.fill_between(forecast_final['ds'].iloc[-168:],
forecast_final['yhat_lower'].iloc[-168:],
forecast_final['yhat_upper'].iloc[-168:],
alpha=0.3, color='gray', label='80% 预测区间')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.5)
plt.tight_layout()
plt.show()
# 输出未来几小时的预测值
print(forecast_final[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail(24))
如何解读预测结果?
- 预测值 (
yhat):模型认为最可能出现的用电功率。 - 预测区间 (
yhat_lower,yhat_upper):给出了预测的不确定性范围。实际值落在这个区间内的概率(默认80%)。区间越宽,说明模型对那个时间点的预测把握越小。 - 从组件图中,你可以分析未来一周哪几天是用电高峰(通常对应周末或特定时段),一天中哪个小时负荷最高。这些信息对于家庭能源管理或电网侧的需求响应都有参考价值。
8. 避坑指南与经验分享
在多次使用Prophet做时间序列预测后,我总结了一些容易踩的坑和实用技巧:
- 数据频率要一致:确保
ds列是均匀间隔的时间戳。如果有缺失的日期时间,最好用重采样或插值补全,否则Prophet可能会报错或产生奇怪的结果。 - 小心“未来信息泄露”:当使用额外回归量时,绝对不能用未来的
y值去预测过去的y。在交叉验证和最终预测时,都要确保回归量的值在对应时间点是“已知”的。 - 预测区间不是万能的:Prophet给出的预测区间是基于模型假设和历史波动性的估计。如果未来出现了历史上从未有过的极端事件(比如突然的长时间停电),这个区间是无法覆盖的。
- 趋势外推的风险:Prophet默认的趋势项会延续历史最后一个趋势。如果历史数据末尾恰好处于一个特殊时期(比如疫情封控期用电模式),直接外推可能导致趋势判断错误。可以通过调整
changepoint_range参数(默认为0.8,即只使用前80%的数据来估计变点)来让模型更关注近期趋势。 - 日志转换处理负值:如果预测值可能出现负值(用电量一般不会,但有些场景会),而物理上不允许负值,可以在拟合前对
y列做对数转换 (np.log1p),预测后再转换回来。Prophet也支持指定growth='logistic'来设置饱和增长上限。 - 可视化是最好的调试工具:多使用
model.plot_components()函数。如果季节性图形状看起来不对劲(比如周季节性波动杂乱无章),可能意味着数据本身周期性不强,或者参数需要调整。
最后,记住Prophet是一个强大的基线模型和快速原型工具。对于家庭用电量预测,它往往能给出令人满意的结果。但如果追求极致的预测精度,可能需要融合更复杂的模型(如LSTM、XGBoost),或者集成多个模型的预测结果。不过,在大多数实际应用场景下,一个经过良好调优的Prophet模型,其表现已经足够支撑很多决策了。
更多推荐
所有评论(0)