电力需求预测挑战赛——Datawhale 2024 AI 夏令营第2期【从零入门AI竞赛之机器学习】
链接👇
赛题分析
时间序列问题
时间序列问题是指对按时间顺序排列的数据点进行分析和预测的问题,往往用来做未来的趋势预测。常见的时间序列场景有:
- 金融领域:股票价格预测、利率变动、汇率预测等。
- 气象领域:温度、降水量、风速等气候指标的预测。
- 销售预测:产品或服务的未来销售额预测。
- 库存预测:预测库存需求,优化库存水平。
- 能源领域:电力需求预测、石油价格预测等。
- 医疗领域:疾病爆发趋势预测、医疗资源需求预测。
时间序列问题的数据特点
- 时间依赖性:数据点之间存在时间上的连续性和依赖性。
- 非平稳性:数据的统计特性(如均值、方差)随时间变化。
- 季节性:数据表现出周期性的模式,如年度、月度或周度。
- 趋势:数据随时间推移呈现长期上升或下降的趋势。
- 周期性:数据可能存在非固定周期的波动。
- 随机波动:数据可能受到随机事件的影响,表现出不确定性。
时间序列预测问题建模方法
- 传统的时间序列模型
基于时间序列数据的 统计特性 ,如自相关性、季节性等,使用ARIMA、季节性ARIMA(SARIMA)、指数平滑等模型,通过识别数据的趋势和季节性成分来构建模型。 - 机器学习模型
将时间序列数据转换为 监督学习问题 ,使用历史数据作为特征,未来值作为标签。使用决策树、随机森林、梯度提升树等模型。通过特征工程来提取时间序列数据中的有用信息。 - 深度学习模型
使用循环神经网络(RNN)、长短期记忆网络(LSTM)或一维卷积神经网络(1D-CNN)等模型。能够捕捉时间序列数据中的长期依赖关系。通过 训练大量的参数 来学习数据的复杂模式。
本赛题目标
训练时间序列预测模型,助力电力需求预测。
Task1——传统时序模型(2024/7/14)【难度非常低】
阶段要求
- 根据文档跑通baseline,提交并拿下第一个分数,体验机器学习解决问题的流程;
- 学习相关知识点,基本了解赛题要求,理解赛题场景;
- 基于经验模型(使用均值作为结果数据)来解决问题。
学习文件
开营直播
从零入门机器学习竞赛
【AI Studio】baseline项目
baseline代码
# 1. 导入需要用到的相关库
# 导入 pandas 库,用于数据处理和分析
import pandas as pd
# 导入 numpy 库,用于科学计算和多维数组操作
import numpy as np
# 2. 读取训练集和测试集
# 使用 read_csv() 函数从文件中读取训练集数据,文件名为 'train.csv'
train = pd.read_csv('./data/data283931/train.csv')
# 使用 read_csv() 函数从文件中读取测试集数据,文件名为 'train.csv'
test = pd.read_csv('./data/data283931/test.csv')
# 3. 计算最近时间的用电均值
# 计算训练数据最近11-20单位时间内对应id的目标均值,可以用来反映最近的用电情况
target_mean = train[train['dt']<=20].groupby(['id'])['target'].mean().reset_index()
# 4. 将用电均值直接作为预测结果进行合并
# 使用merge函数根据'id'列将test和target_mean两个DataFrame进行左连接,测试集的所有行都会保留
test = test.merge(target_mean, on=['id'], how='left')
# 5. 保存结果文件到本地
# 将测试集的'id'、'dt'和'target'列保存为CSV文件,index=None参数表示在保存时不包含行索引
test[['id','dt','target']].to_csv('submit.csv', index=None)
到竞赛官网提交submit.csv
文件,baseline得分373.89846
Task2——机器学习模型(2024/7/15-17)【难度⭐】
阶段要求
通过baseline代码和进阶代码分析数据、学习特征构建方式,分数初步提高,入门机器学习建模。使用机器学习模型如LightGBM、XGBoost等解决本次回归预测问题。
- 理解竞赛通用流程;
- 精读baseline代码,与竞赛流程一一对应;
- 学习进阶代码并实践,对赛题数据进行分析;
- 学习进阶代码并实践,了解如何构建特征;
- 学习进阶代码并实践,了解如何选择机器学习模型、建模;
- 使用机器学习模型如LightGBM、XGBoost等解决本次回归预测问题。
基础概念
机器学习方法主要需要从获取数据&增强、特征提取和模型三个方面下手。使用机器学习模型解决问题的主要步骤为探索性数据分析、数据预处理、提取特征、切分训练集与验证集、训练模型、预测结果。
-
GBDT
GBDT (Gradient Boosting Decision Tree) 是机器学习中一个长盛不衰的模型,其主要思想是利用弱分类器(决策树)迭代训练以得到最优模型,该模型具有训练效果好、不易过拟合等优点。
GBDT 不仅在工业界应用广泛,通常被用于多分类、点击率预测、搜索排序等任务;在各种数据挖掘竞赛中也是致命武器。 据统计Kaggle上的比赛有一半以上的冠军方案都是基于GBDT! -
LightGBM
LightGBM (Light Gradient Boosting Machine) 是一个实现 GBDT 算法的框架,支持高效率的并行训练,并且具有更快的训练速度、更低的内存消耗、更好的准确率、支持分布式可以快速处理海量数据等优点。
LightGBM 框架中还包括随机森林和逻辑回归等模型。通常应用于二分类、多分类和排序等场景。
学习文件
baseline进阶直播1
LightGBM 中文文档
LightGBM 英文文档
进阶代码详解
(1)导入模块
# 导入需要用到的相关库
import numpy as np
import pandas as pd
import lightgbm as lgb
# 导入三个评估模型性能的函数:对数误差的均方、平均绝对误差(mae)和均方误差(mse)
from sklearn.metrics import mean_squared_log_error, mean_absolute_error, mean_squared_error
import tqdm # 显示进度条
import sys # 提供与Python解释器和其环境交互的功能
import os # 操作系统功能,如文件和目录管理
import gc # 垃圾回收,帮助Python管理内存
import argparse # 解析命令行参数
import warnings # 显示警告信息
warnings.filterwarnings('ignore') # 将所有的警告信息设置为忽略模式,隐藏一些不重要的警告信息
(2)探索性数据分析(EDA)
# 在数据准备阶段,主要读取训练数据和测试数据,并进行基本的数据展示
train = pd.read_csv('./train.csv')
test = pd.read_csv('./test.csv')
# 进行简单的可视化分析,对数据有个简单的了解
# 不同type类型对应target的柱状图
import matplotlib.pyplot as plt
# 使用groupby方法按照type列对训练数据进行分组,然后计算每个组的target列的平均值
# 最后使用reset_index方法将分组后的数据转换回一个DataFrame,其中包含原始的type列和计算出的平均值
type_target_df = train.groupby('type')['target'].mean().reset_index()
plt.figure(figsize=(8, 4))
plt.bar(type_target_df['type'], type_target_df['target'], color=['blue'])
plt.xlabel('Type')
plt.ylabel('Average Target Value')
plt.title('Bar Chart of Target by Type')
plt.show()
# id为00037f39cf的按dt为序列关于target的折线图
specific_id_df = train[train['id'] == '00037f39cf']
plt.figure(figsize=(10, 5))
plt.plot(specific_id_df['dt'], specific_id_df['target'], color='m', linestyle='-')
plt.xlabel('DateTime')
plt.ylabel('Target Value')
plt.title("Line Chart of Target for ID '00037f39cf'")
plt.show()
(3)特征工程
# 主要构建了历史平移特征和窗口统计特征
# 合并训练数据和测试数据,并进行排序
# 将测试数据集test和训练数据集train沿行方向合并成一个单一的数据集data,axis=0表示沿行方向合并,ignore_index=True表示重置索引
data = pd.concat([test, train], axis=0, ignore_index=True)
# 按照id和dt列对数据进行降序排序,ascending=False表示降序排序,重置索引,drop=True表示不将旧索引添加为列
data = data.sort_values(['id','dt'], ascending=False).reset_index(drop=True)
# 添加历史平移特征
# 对于每个i(从10到29),创建一个新列last{i}_target,该列表示每个id组内target值的第i个历史值
# groupby(['id'])按id列对数据进行分组,shift(i)将每个组内的target值向下移动i行
for i in range(10,30):
data[f'last{i}_target'] = data.groupby(['id'])['target'].shift(i)
# 窗口统计特征
# 计算了last10_target、last11_target和last12_target这三个历史平移特征的平均值,并将其存储在新列win3_mean_target中
data[f'win3_mean_target'] = (data['last10_target'] + data['last11_target'] + data['last12_target']) / 3
# 进行数据切分
train = data[data.target.notnull()].reset_index(drop=True) # 从合并的数据集中筛选出target值非空的行,这些行属于训练数据集
test = data[data.target.isnull()].reset_index(drop=True) # 筛选出target值为空的行,这些行属于测试数据集
# 确定输入特征
train_cols = [f for f in data.columns if f not in ['id','target']] # 从数据集的所有列中排除id和target列,因为这些列通常不作为模型的输入特征
# 展示数据集data中id为'fff81139a7',且last29_target列中包含NaN值的行
data[data['id']=='fff81139a7'][data['last29_target'].isna()].to_csv('id_fff81139a7_shift', index=None)
# 展示数据切分后测试集test中id为'fff81139a7'的所有行
print(test[test['id']=='fff81139a7'])
(4)模型训练与测试集预测
# 选择使用Lightgbm模型
# 选择原始给出训练数据集中dt为30之后的数据作为训练数据,之前的数据作为验证数据,保证了数据不存在穿越问题(不使用未来数据预测历史数据),可以避免数据泄露问题
from lightgbm import log_evaluation,early_stopping
def time_model(lgb, train_df, test_df, cols):
# 训练集和验证集切分
trn_x, trn_y = train_df[train_df.dt>=31][cols], train_df[train_df.dt>=31]['target']
val_x, val_y = train_df[train_df.dt<=30][cols], train_df[train_df.dt<=30]['target']
# 构建模型输入数据
# 使用LightGBM的Dataset类创建训练集和验证集的数据结构,这有助于LightGBM更高效地处理数据
train_matrix = lgb.Dataset(trn_x, label=trn_y)
valid_matrix = lgb.Dataset(val_x, label=val_y)
# lightgbm参数
lgb_params = {
'boosting_type': 'gbdt', # 弱学习器类型,默认为'gbdt'
'objective': 'regression', # 目标函数,回归问题
'metric': 'mse', # 模型训练过程中用于评估模型性能的指标,'mse'是回归问题中常用的指标
'min_child_weight': 5, # 每个叶子节点的最小权重,较大的值可以增加模型的正则化,防止过拟合
'num_leaves': 2 ** 5, # 叶子节点数目,默认为31
'lambda_l2': 10, # L2正则化参数(权重的平方和)
'feature_fraction': 0.8, # 每次分裂时,随机选择的特征比例
'bagging_fraction': 0.8, # 每次分裂时,用于构建树的样本比例
'bagging_freq': 4, # 进行bagging的频率
'learning_rate': 0.05, # 学习率,默认0.1
'seed': 2024, # 随机数种子,用于确保结果的可重复性
'nthread' : 16, # 指定LightGBM训练过程中使用的线程数,表示使用16个线程并行处理
'verbose' : -1, # 控制训练过程中的输出信息,-1表示在训练过程中不打印任何信息
}
# 定义回调函数,log_evaluation记录训练过程中的评估指标,early_stopping在验证集上评估指标不再提升时提前终止训练
callbacks = [log_evaluation(period=500), early_stopping(stopping_rounds=500)]
# 训练模型
# 训练迭代次数50000,valid_sets指定用于验证的数据集
model = lgb.train(lgb_params, train_matrix, 50000, valid_sets=[train_matrix, valid_matrix],
categorical_feature=[],callbacks=callbacks)
# 验证集和测试集结果预测
# num_iteration=model.best_iteration确保使用模型在验证集上表现最佳的迭代次数进行预测
val_pred = model.predict(val_x, num_iteration=model.best_iteration)
test_pred = model.predict(test_df[cols], num_iteration=model.best_iteration)
# 离线分数评估
# 使用均方误差(MSE)评估模型在验证集上的性能
val_score = mean_squared_error(val_pred, val_y)
print(f'验证集mse得分: {val_score}')
return val_pred, test_pred
# 调用time_model函数,传入LightGBM库的引用、训练数据集、测试数据集和特征列名列表
lgb_oof, lgb_test = time_model(lgb, train, test, train_cols)
# 保存结果文件到本地
# 将测试集的预测结果添加到测试数据集中,然后将包含id、dt和target列的数据集保存为CSV文件
test['target'] = lgb_test
test[['id','dt','target']].to_csv('result_lgb_baseline.csv', index=None)
到竞赛官网提交result_lgb_baseline.csv
文件,lgb_baseline得分2742.43654
本地跑分数差,还需要进行优化上分!
遇到的问题与解决优化方案
一些优化上分思路——在原有baseline基础上做更多优化,一般优化思路,从特征工程与模型中来思考
https://blog.csdn.net/chrisleequeen/article/details/140337118
优化方法建议:
- 提取更多特征:将可以帮助提高预测精准度的信息转化为特征输入。
- 尝试不同模型:模型间差异很大。不断实验寻找最佳模型。
优化尝试
🔔Try 1——特征优化
主要构建了历史平移特征、差分特征和窗口统计特征。
- 历史平移特征
通过历史平移获取上一阶段信息; - 差分特征
获取相邻阶段的增长差异,构建相邻数据比值变化、二阶差分等; - 窗口统计特征
构建不同窗口大小,基于窗口范围进行统计均值、最大值、最小值、中位数、方差等信息,反映最近数据的变化情况。
# 合并训练数据和测试数据
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_shift710win{win}_std'] = data.groupby('id')['target_shift10'].rolling(window=win, min_periods=3, closed='left').std().values
🔔Try 2——模型融合
进行模型融合的前提是有多个模型的输出结果。
(1) 最常见的是将结果直接进行加权平均融合
- 构建 cv_model 函数;
- 内部选择使用 lightgbm、xgboost 和 catboost 模型;
- 依次跑完这三个模型,对于每个模型均选择经典的K折交叉验证方法进行离线评估;
- 将三个模型的结果进行取平均进行融合。
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
(2) stacking 融合
stacking是一种分层模型集成框架。以两层为例,第一层由多个基学习器组成,其输入为原始训练集,第二层的模型则是以第一层基学习器的输出作为特征加入训练集进行再训练,从而得到完整的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(pd.DataFrame(lgb_oof), pd.DataFrame(xgb_oof), pd.DataFrame(cat_oof),
pd.DataFrame(lgb_test), pd.DataFrame(xgb_test), pd.DataFrame(cat_test), train['target'])
Task3——深度学习模型(2024/7/18-20)【难度⭐⭐】
LSTM 模型
import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense, RepeatVector, TimeDistributed
from keras.optimizers import Adam
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
# 数据预处理
def preprocess_data(df, look_back=100):
# 将数据按照id进行分组
grouped = df.groupby('id')
datasets = {}
for id, group in grouped:
datasets[id] = group.values
# 准备训练数据集
X, Y = [], []
for id, data in datasets.items():
for i in range(10, 15): # 每个id构建5个序列
a = data[i:(i + look_back), 3]
a = np.append(a, np.array([0]*(100-len(a))))
X.append(a[::-1])
Y.append(data[i-10:i, 3][::-1])
# 准备测试数据集
OOT = []
for id, data in datasets.items():
a = data[:100, 3]
a = np.append(a, np.array([0]*(100-len(a))))
OOT.append(a[::-1])
return np.array(X, dtype=np.float64), np.array(Y, dtype=np.float64), np.array(OOT, dtype=np.float64)
# 定义模型
def build_model(look_back, n_features, n_output):
model = Sequential()
model.add(LSTM(50, input_shape=(look_back, n_features)))
model.add(RepeatVector(n_output))
model.add(LSTM(50, return_sequences=True))
model.add(TimeDistributed(Dense(1)))
model.compile(loss='mean_squared_error', optimizer=Adam(0.001))
return model
# 构建和训练模型
look_back = 100 # 序列长度
n_features = 1 # 假设每个时间点只有一个特征
n_output = 10 # 预测未来10个时间单位的值
# 预处理数据
X, Y, OOT = preprocess_data(train, look_back=look_back)
# 构建模型
model = build_model(look_back, n_features, n_output)
# 训练模型
model.fit(X, Y, epochs=10, batch_size=64, verbose=1)
# 进行预测
predicted_values = model.predict(OOT)
本次夏令营优秀笔记推荐
https://exn8g66dnwu.feishu.cn/sheets/M4LFsR8oAhnFKZtGdZHcDJn4ncg?sheet=A31F0P&range=QjM1
https://blog.csdn.net/m0_63566347/article/details/140554999
https://zhuanlan.zhihu.com/p/708680971
posted on 2024-07-15 22:45 xiaoyufuture 阅读(85) 评论(0) 编辑 收藏 举报