在电力需求挑战赛中,时间序列预测是一项重要的任务,特征提取直接影响到模型的性能。本文将介绍一些关键的时间序列特征提取和分析方法,并结合具体代码示例进行详细说明。
时间序列预测特征提取和分析方法
- 日期变量:时间序列数据通常包含日期或时间信息,可以将其细分为年、月、周、日、小时、分钟等,并将这些日期变量转换为数值型特征。
- 周期性:识别和利用周期性特征(如一天中的小时、一周中的天数、一年中的月份等)可以帮助模型捕捉数据的内在规律。
- 趋势性:通过移动平均或线性回归等方法提取趋势性特征,反映数据随时间推移的总体模式。
- 距离某天的时间差:计算与特定日期(如产品发布日、重要事件日等)的时间差,帮助模型了解数据点与特定事件的相对位置。
- 时间特征组合:将不同的时间单位组合起来(如年和周、月和日),提供更丰富的时间上下文信息。
- 特殊日期:识别时间序列中的特殊日期或事件(如节假日、促销活动等)并将其作为特征。
- 异常点:正确识别并处理时间序列中的异常点,提高预测精度。
- 时序相关特征:
- 历史平移:将过去的值作为当前值的函数,例如,使用前一天的值来预测后一天的值。
- 滑窗统计:使用时间窗口内的统计数据(如平均值、中位数、标准差等)作为特征。
- 强相关特征:识别与目标变量强烈相关的特征,并利用这些特征构建预测模型。
这张图片提供了一系列关于时间序列预测中构建关键特征的要点,包括强相关性特征、趋势性特征、周期性特征和异常点特征。以下是对这些特征的详细介绍:
-
强相关性特征:
-
强相关性特征是指与目标变量有明显线性或非线性关系的输入特征。在时间序列中,这些特征可能包括:
-
滞后特征:使用过去的值作为当前预测的特征,例如,使用前一天的销售数据来预测后一天的销售。
-
滚动统计特征:计算时间序列的滚动窗口内的统计量,如平均值、最大值、最小值、总和等。
-
-
-
趋势性特征:
-
趋势性特征反映了时间序列随时间推移的长期变化方向或速率。
-
构建趋势性特征的方法包括:
-
时间戳转换:将时间戳转换为数值,如从时间戳中提取年份、月份、星期等。
-
移动平均:使用时间序列的移动平均值来平滑短期波动,突出长期趋势。
-
多项式拟合:拟合一个多项式模型来捕捉趋势。
-
-
-
周期性特征:
-
周期性特征反映了时间序列数据在固定时间间隔内重复出现的模式。
-
构建周期性特征的方法包括:
-
时间戳的周期性转换:将时间戳转换为周期性变量,如一周中的星期几、一月中的日子等。
-
季节性分解:使用季节性分解方法来识别和提取时间序列的季节性成分。
-
周期性函数:使用正弦和余弦函数来模拟周期性变化。
-
-
-
异常点特征:
-
异常点是指与正常数据模式显著不同的数据点,它们可能由错误、突变或特殊事件引起。
-
处理异常点的方法包括:
-
简单标注:在数据集中标记异常点,以便在分析时考虑。
-
剔除:从数据集中删除异常点,特别是在它们可能影响模型训练的情况下。
-
修正:更正异常点,如果它们是由于可识别的错误造成的。
-
-
-
特殊事件特征:
-
特殊事件如“双十一”、“618”、“春节”等,会在时间序列中产生显著的峰值。
-
构建特殊事件特征的方法包括:
-
事件指示器:创建一个二进制特征,当时间序列中的点与特殊事件对应时,该特征值为1,否则为0。
-
事件前后的时间窗口:考虑事件前后的时间窗口,以捕捉事件的影响。
-
-
-
上下时段信息:
-
上下时段信息指的是在时间序列中,特定时间段之前或之后的数据点。
-
这可以通过创建特征来表示数据点与特定时间段的距离来实现。
-
-
存在峰值与峰值距离:
-
识别时间序列中的峰值点,并计算其他数据点与这些峰值点的距离,可以作为特征输入模型。
-
-
时间尺度特征:
-
根据需要预测的时间尺度(如1天、3天、5天等),创建相应的滞后特征和滚动统计特征。
-
通过结合这些特征,可以构建一个强大的时间序列预测模型。在实际应用中,需要根据具体问题和数据特性来选择和调整特征工程的策略。此外,特征的选择和构建应该与模型的选择和调优过程相结合,以实现最佳的预测性能。
优化方案详解
在提分实践部分,将在原有Baseline基础上做更多优化,一般优化思路,从特征工程与模型中来思考。
优化方法建议:
-
提取更多特征:在数据挖掘比赛中,特征总是最终制胜法宝,去思考什么信息可以帮助我们提高预测精准度,然后将其转化为特征输入到模型。
-
尝试不同的模型:模型间存在很大的差异,预测结果也会不一样,比赛的过程就是不断的实验和试错的过程,通过不断的实验寻找最佳模型,同时帮助自身加强模型的理解能力。
特征优化
这里主要构建了历史平移特征、差分特征、和窗口统计特征;每种特征都是有理可据的,具体说明如下:
(1)历史平移特征:通过历史平移获取上个阶段的信息;
(2)差分特征:可以帮助获取相邻阶段的增长差异,描述数据的涨减变化情况。在此基础上还可以构建相邻数据比值变化、二阶差分等;
(3)窗口统计特征:窗口统计可以构建不同的窗口大小,然后基于窗口范围进统计均值、最大值、最小值、中位数、方差的信息,可以反映最近阶段数据的变化情况。
代码示例
# 合并训练数据和测试数据
data = pd.concat([train, test], axis=0).reset_index(drop=True)
data = data.sort_values(['id','dt'], ascending=False).reset_index(drop=True)
# 历史平移
for i in range(10, 36):
data[f'target_shift{i}'] = data.groupby('id')['target'].shift(i)
# 历史平移 + 差分特征
for i in range(1, 4):
data[f'target_shift10_diff{i}'] = data.groupby('id')['target_shift10'].diff(i)
# 窗口统计
for win in [15, 30, 50, 70]:
data[f'target_win{win}_mean'] = data.groupby('id')['target'].rolling(window=win, min_periods=3, closed='left').mean().values
data[f'target_win{win}_max'] = data.groupby('id')['target'].rolling(window=win, min_periods=3, closed='left').max().values
data[f'target_win{win}_min'] = data.groupby('id')['target'].rolling(window=win, min_periods=3, closed='left').min().values
data[f'target_win{win}_std'] = data.groupby('id')['target'].rolling(window=win, min_periods=3, closed='left').std().values
# 历史平移 + 窗口统计
for win in [7, 14, 28, 35, 50, 70]:
data[f'target_shift10_win{win}_mean'] = data.groupby('id')['target_shift10'].rolling(window=win, min_periods=3, closed='left').mean().values
data[f'target_shift10_win{win}_max'] = data.groupby('id')['target_shift10'].rolling(window=win, min_periods=3, closed='left').max().values
data[f'target_shift10_win{win}_min'] = data.groupby('id')['target_shift10'].rolling(window=win, min_periods=3, closed='left').min().values
data[f'target_shift10_win{win}_sum'] = data.groupby('id')['target_shift10'].rolling(window=win, min_periods=3, closed='left').sum().values
data[f'target_shift10_win{win}_std'] = data.groupby('id')['target_shift10'].rolling(window=win, min_periods=3, closed='left').std().values
模型融合
进行模型融合的前提是有多个模型的输出结果,比如使用catboost、xgboost和lightgbm三个模型分别输出三个结果,这时就可以将三个结果进行融合,最常见的是将结果直接进行加权平均融合。
下面我们构建了cv_model函数,内部可以选择使用lightgbm、xgboost和catboost模型,可以依次跑完这三个模型,然后将三个模型的结果进行取平均进行融合。
代码示例
from sklearn.model_selection import StratifiedKFold, KFold, GroupKFold
import lightgbm as lgb
import xgboost as xgb
from catboost import CatBoostRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error
def cv_model(clf, train_x, train_y, test_x, clf_name, seed = 2024):
'''
clf:调用模型
train_x:训练数据
train_y:训练数据对应标签
test_x:测试数据
clf_name:选择使用模型名
seed:随机种子
'''
folds = 5
kf = KFold(n_splits=folds, shuffle=True, random_state=seed)
oof = np.zeros(train_x.shape[0])
test_predict = np.zeros(test_x.shape[0])
cv_scores = []
for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)):
print('************************************ {} ************************************'.format(str(i+1)))
trn_x, trn_y, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]
if clf_name == "lgb":
train_matrix = clf.Dataset(trn_x, label=trn_y)
valid_matrix = clf.Dataset(val_x, label=val_y)
params = {
'boosting_type': 'gbdt',
'objective': 'regression',
'metric': 'mae',
'min_child_weight': 6,
'num_leaves': 2 ** 6,
'lambda_l2': 10,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 4,
'learning_rate': 0.1,
'seed': 2023,
'nthread' : 16,
'verbose' : -1,
}
model = clf.train(params, train_matrix, 1000, valid_sets=[train_matrix, valid_matrix],
categorical_feature=[], verbose_eval=200, early_stopping_rounds=100)
val_pred = model.predict(val_x, num_iteration=model.best_iteration)
test_pred = model.predict(test_x, num_iteration=model.best_iteration)
if clf_name == "xgb":
xgb_params = {
'booster': 'gbtree',
'objective': 'reg:squarederror',
'eval_metric': 'mae',
'max_depth': 5,
'lambda': 10,
'subsample': 0.7,
'colsample_bytree': 0.7,
'colsample_bylevel': 0.7,
'eta': 0.1,
'tree_method': 'hist',
'seed': 520,
'nthread': 16
}
train_matrix = clf.DMatrix(trn_x , label=trn_y)
valid_matrix = clf.DMatrix(val_x , label=val_y)
test_matrix = clf.DMatrix(test_x)
watchlist = [(train_matrix, 'train'),(valid_matrix, 'eval')]
model = clf.train(xgb_params, train_matrix, num_boost_round=1000, evals=watchlist, verbose_eval=200, early_stopping_rounds=100)
val_pred = model.predict(valid_matrix)
test_pred = model.predict(test_matrix)
if clf_name == "cat":
params = {'learning_rate': 0.1, 'depth': 5, 'bootstrap_type':'Bernoulli','random_seed':2023,
'od_type': 'Iter', 'od_wait': 100, 'random_seed': 11, 'allow_writing_files': False}
model = clf(iterations=1000, **params)
model.fit(trn_x, trn_y, eval_set=(val_x, val_y),
metric_period=200,
use_best_model=True,
cat_features=[],
verbose=1)
val_pred = model.predict(val_x)
test_pred = model.predict(test_x)
oof[valid_index] = val_pred
test_predict += test_pred / kf.n_splits
score = mean_absolute_error(val_y, val_pred)
cv_scores.append(score)
print(cv_scores)
return oof, test_predict
# 选择lightgbm模型
lgb_oof, lgb_test = cv_model(lgb, train[train_cols], train['target'], test[train_cols], 'lgb')
# 选择xgboost模型
xgb_oof, xgb_test = cv_model(xgb, train[train_cols], train['target'], test[train_cols], 'xgb')
# 选择catboost模型
cat_oof, cat_test = cv_model(CatBoostRegressor, train[train_cols], train['target'], test[train_cols], 'cat')
# 进行取平均融合
final_test = (lgb_test + xgb_test + cat_test) / 3
另外一种就是stacking融合,stacking是一种分层模型集成框架。以两层为例,第一层由多个基学习器组成,其输入为原始训练集,第二层的模型则是以第一层基学习器的输出作为特征加入训练集进行再训练,从而得到完整的stacking模型。
第一层:(类比cv_model函数)
-
划分训练数据为K折(5折为例,每次选择其中四份作为训练集,一份作为验证集);
-
针对各个模型RF、ET、GBDT、XGB,分别进行5次训练,每次训练保留一份样本用作训练时的验证,训练完成后分别对Validation set,Test set进行预测,对于Test set一个模型会对应5个预测结果,将这5个结果取平均;对于Validation set一个模型经过5次交叉验证后,所有验证集数据都含有一个标签。此步骤结束后:5个验证集(总数相当于训练集全部)在每个模型下分别有一个预测标签,每行数据共有4个标签(4个算法模型),测试集每行数据也拥有四个标签(4个模型分别预测得到的)
第二层:(类比stack_model函数)
-
将训练集中的四个标签外加真实标签当作五列新的特征作为新的训练集,选取一个训练模型,根据新的训练集进行训练,然后应用测试集的四个标签组成的测试集进行预测作为最终的result。
Stacking参考代码:
def stack_model(oof_1, oof_2, oof_3, predictions_1, predictions_2, predictions_3, y):
'''
输入的oof_1, oof_2, oof_3可以对应lgb_oof,xgb_oof,cat_oof
predictions_1, predictions_2, predictions_3对应lgb_test,xgb_test,cat_test
'''
train_stack = pd.concat([oof_1, oof_2, oof_3], axis=1)
test_stack = pd.concat([predictions_1, predictions_2, predictions_3], axis=1)
oof = np.zeros((train_stack.shape[0],))
predictions = np.zeros((test_stack.shape[0],))
scores = []
from sklearn.model_selection import RepeatedKFold
folds = RepeatedKFold(n_splits=5, n_repeats=2, random_state=2021)
for fold_, (trn_idx, val_idx) in enumerate(folds.split(train_stack, train_stack)):
print("fold n°{}".format(fold_+1))
trn_data, trn_y = train_stack.loc[trn_idx], y[trn_idx]
val_data, val_y = train_stack.loc[val_idx], y[val_idx]
clf = Ridge(random_state=2021)
clf.fit(trn_data, trn_y)
oof[val_idx] = clf.predict(val_data)
predictions += clf.predict(test_stack) / (5 * 2)
score_single = mean_absolute_error(val_y, oof[val_idx])
scores.append(score_single)
print(f'{fold_+1}/{5}', score_single)
print('mean: ',np.mean(scores))
return oof, predictions
stack_oof, stack_pred = stack_model(lgb_oof, xgb_oof, cat_oof, lgb_test, xgb_test, cat_test, traindata['age'])
深度学习解决方案
在电力需求预测中,深度学习模型如LSTM(长短期记忆网络)和GRU(门控循环单元)非常适合处理时间序列数据。它们能够捕捉数据的时间依赖性和复杂的模式。下面介绍一个基于LSTM的深度学习解决方案,并附上相关代码示例。
- 数据准备:将时间序列数据转换为适合LSTM输入的格式,通常是三维张量(样本数、时间步、特征数)。
- 构建LSTM模型:定义一个LSTM模型,并设置适当的层数、单元数和其他超参数。
- 模型训练:使用训练数据训练LSTM模型,并在验证集上评估模型性能。
- 预测与评估:使用训练好的模型对测试数据进行预测,并评估预测结果。
代码示例
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
# 读取训练集和测试集
print("读取训练集和测试集...")
train = pd.read_csv('/CACC/电力/train.csv')
test = pd.read_csv('/CACC/电力/test.csv')
print("数据读取完成")
# 特征工程
def feature_engineering(df, is_train=True):
print("开始特征工程...")
df['day'] = df['dt'] % 7 # 周期性特征:一周中的哪一天
df['month'] = (df['dt'] // 30) % 12 # 周期性特征:一年中的哪个月
df['quarter'] = (df['dt'] // 90) % 4 # 季度特征
df['season'] = (df['dt'] // 90) % 4 # 季节特征
if is_train:
lags = range(1, 8)
windows = [3, 7, 14]
# 滞后特征
for lag in lags: # 过去一周的滞后特征
df[f'target_lag_{lag}'] = df.groupby('id')['target'].shift(lag)
# 滾动统计特征
for window in windows: # 3天、7天、14天的滚动统计特征
df[f'target_roll_mean_{window}'] = df.groupby('id')['target'].rolling(window).mean().reset_index(0, drop=True)
df[f'target_roll_max_{window}'] = df.groupby('id')['target'].rolling(window).max().reset_index(0, drop=True)
df[f'target_roll_min_{window}'] = df.groupby('id')['target'].rolling(window).min().reset_index(0, drop=True)
df.fillna(0, inplace=True) # 填充缺失值
else:
# 使用相同的滞后特征和滚动窗口
for lag in range(1, 8):
df[f'target_lag_{lag}'] = 0
for window in [3, 7, 14]:
df[f'target_roll_mean_{window}'] = 0
df[f'target_roll_max_{window}'] = 0
df[f'target_roll_min_{window}'] = 0
print("特征工程完成")
return df
train = feature_engineering(train, is_train=True)
test = feature_engineering(test, is_train=False)
# 删除非数值列
print("删除非数值列...")
X = train.drop(columns=['target', 'id', 'type'])
y = train['target']
X_test = test.drop(columns=['id', 'type']) # 这里不删除 'target' 列,因为测试集中没有 'target' 列
# 确保测试集和训练集的特征一致
X_test = X_test.reindex(columns=X.columns, fill_value=0)
# 标准化特征
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_test_scaled = scaler.transform(X_test)
# 重塑数据以适应LSTM输入格式 (samples, timesteps, features)
def reshape_for_lstm(data, timesteps=7):
data_reshaped = []
for i in range(len(data) - timesteps):
data_reshaped.append(data[i:i+timesteps])
return np.array(data_reshaped)
timesteps = 7
X_lstm = reshape_for_lstm(X_scaled, timesteps)
y_lstm = y[timesteps:]
# 划分训练集和验证集
print("划分训练集和验证集...")
X_train, X_val, y_train, y_val = train_test_split(X_lstm, y_lstm, test_size=0.2, random_state=42)
# 构建LSTM模型
model = Sequential()
model.add(LSTM(50, return_sequences=True, input_shape=(timesteps, X_train.shape[2])))
model.add(Dropout(0.2))
model.add(LSTM(50))
model.add(Dropout(0.2))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mean_squared_error')
# 训练模型
print("训练LSTM模型...")
model.fit(X_train, y_train, epochs=20, batch_size=32, validation_data=(X_val, y_val))
# 验证模型
y_pred_val = model.predict(X_val)
print(f'LSTM模型验证集均方误差: {mean_squared_error(y_val, y_pred_val)}')
# 预测测试集
X_test_lstm = reshape_for_lstm(X_test_scaled, timesteps)
y_pred_test = model.predict(X_test_lstm)
# 保存结果文件到本地
print("保存结果文件到本地...")
test['target'] = np.concatenate((np.zeros(timesteps), y_pred_test.flatten()))
test[['id', 'dt', 'target']].to_csv('submit.csv', index=False)
print("所有步骤完成")
结语
在电力需求挑战赛中,我们通过模型堆叠方法有效地提升了预测性能。堆叠方法通过结合多种模型的优势,生成更为精确的预测结果。此外,尝试深度学习方案也为我们提供了新的思路和方法。电力需求预测是一项复杂的任务,通过不断地探索和实践,我们可以持续改进模型的性能,最终在比赛中取得优异的成绩。希望大家能够从本文中有所收获,并在实际项目中灵活应用这些技术。继续努力,向冠军进发!
如果你觉得这篇博文对你有帮助,请点赞、收藏、关注我,并且可以打赏支持我!
欢迎关注我的后续博文,我将分享更多关于人工智能、自然语言处理和计算机视觉的精彩内容。
谢谢大家的支持!