Datawhale Al夏令营-A simple introduction of time series forecasting

通过以上两篇的简单介绍,相信大家已经对本电力预测赛题有一定的了解。
今天这篇主要是聊一聊在以后面对时间序列问题时,基本的思路。(以下为个人见解,极为浅薄,勿喷)

  1. 首先我们要了解到数据的背景,即了解各个特征的含义,数据类型,具体数据格式,比如:房屋的类型,房屋的特定id,房屋的总面积,房屋是几居室。尽管这些在模型当中只是数字,但由于各种原因的限制,我们面对的数据很难达到直接利用深度学习进行建模的水平。而机器学习当中,特征工程对于最终模型好坏的评估相当重要(好的特征工程对比直接输入原始数据有的情况差距非常非常大),因此理解数据本身对于我们在机器学习-特征工程这一步骤提供人工构造特征的思路。
  2. 了解数据的背景以及现实意义|场景之后,可以通过pandas.head, pandas.info, pandas.describe, pandas.isnull.sum等函数进行简单查看。
    同样也可以查看具体的数据分布特征,构思应当采用的建模方法。
data = pd.read_csv('train.csv')
# 查看前5行数据,结合数据背景介绍,了解数据格式
data.head()
"""
比如我们这次的电力预测,尽管带有时间属性的特征,但经过脱敏之后仅剩余1,2,3,4等天数,并没有具体的日期。
了解到该情况之后,常用的提取年、月、日属性以及相应的构造日期特征可能失效。
如果想使用的话,可以自己根据dt对应的天数重新构建一个新的日期序列,比如dt=1对应2020.01.01,dt=2对应2020.01.02等。
(我没有尝试过,感兴趣可以自己试一下)
这样构建完成之后,就可以进行日期相关特征的构建工作。
"""
# 查看数据是否存在缺失
data.isnull().sum()
"""
网上有很对针对不同缺失程度的处理办法,如:设定一个阈值,当某一个特征缺失比例达到阈值时,将该特征删除;或者将缺失值本身作为一个单独的特征值考虑;进行缺失值填充(均值、众数、中位数、甚至你可以自己建一个简单的预测模型进行填充)等等
"""

将数据的含义、格式、内容了解之后,在进行特征工程的过程中,可以考虑以下几个方面(网上、LLM或其他来源):

  • 数据转换:
    • 标准化和归一化:将数据转换到相同的尺度,减少量纲影响
    • 对数变换:消除异方差性,使数据分布更接近正态
    • 差分:对数据进行差分处理,以消除趋势和季节性,达到平稳状态
  • 时间序列分解:
    • 分解时间序列数据为趋势、季节性和剩余成分:使用如STL(Seasonal-Trend decomposition using Loess)或X-13ARIMA-SEATS等方法
    • 从时间戳中提取日期、小时、分钟等,创建新的特征,如:年、月、日、周、小时、分钟、秒
      季节性(如季度、半年);是否节假日、工作日/周末;时间段(如上午、下午、晚上)等
  • 特征构建:
    • 滞后特征(Lag Features):使用过去的观测值作为输入特征
    • 滚动统计特征:计算滚动窗口上的统计量,如均值、标准差、最小值、最大值等
    • 时间周期特征:从时间戳中提取日期、时间、工作日、节假日等信息
    • 循环特征:将时间周期(如小时、月份)转换为正弦和余弦值,以反映周期性
  • 外部变量:
    • 引入可能影响序列的其他变量,如天气数据、节假日、经济指标等
  • 趋势和季节性检测:
    • 使用单位根检验(如ADF、KPSS)来检测序列的平稳性
      ( 检查是否存在趋势或季节性模式,并采取适当措施处理
  • 特征选择:
    • 使用相关性分析、特征重要性评分、递归特征消除(RFE)等技术来选择最相关的特征

下面构建历史平移特征、差分特征、和窗口统计特征:

  • 历史平移特征:通过历史平移获取上个阶段的信息;
  • 差分特征:可以帮助获取相邻阶段的增长差异,描述数据的涨减变化情况。在此基础上还可以构建相邻数据比值变化、二阶差分等;
  • 窗口统计特征:窗口统计可以构建不同的窗口大小,然后基于窗口范围进统计均值、最大值、最小值、中位数、方差的信息,可以反映最近阶段数据的变化情况。
# 合并训练数据和测试数据
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

在具体的模型构建时,在kaggle机器学习竞赛中,排名靠前的大佬一般都会进行大量的特征工程构工作,这一点再次提醒:至关重要!!!!!!!!!!!
毕竟:Garbage in, garbage out !!!

  1. 在具体的预测时,我们并不一定只能使用一个模型进行预测
  • 我们可以采用多个模型预测结果的均值(当然可以是非均值:比如加权重参数[注意力机制????])。
  • 我们也可以构建多个模型,使用第一个模型进行预测之后,第二个模型将第一个模型的输出结果当中特征的一部分再进行训练,以此类推,这大概就是(Stacking)方法??
    • (stacking是一种分层模型集成框架。以两层为例,第一层由多个基学习器组成,其输入为原始训练集,第二层的模型则是以第一层基学习器的输出作为特征加入训练集进行再训练,从而得到完整的stacking模型。)
# 使用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

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'])

感兴趣可以在网上找资料,看图可以一下子知道两者的差异。(我的理解是一个是多分枝并行结构,一个是葫芦娃接爷爷,一个一个来)

对于数据集的划分,本篇并没有详细的讲解,感兴趣希望自己去搜一下,K折交叉验证、2-8分、3-7分、1.5-1.5-7分等等(看数据量,并不是每个都适用,有个只是没办法了)

  1. 对于深度学习进行时间序列预测之一块,可以去网上找顶刊或者顶会的文章(很多都是深度学习预测,并且大佬们有的直接把代码给你)
    这里只给出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)

本篇最后提醒一下诸位:没有万能的模型,没有万能的方法,要根据具体的问题、数据特征进行分析。比如这次的预测,使用LSTM深度学习预测的Baseline结果反而没有简单的平滑移动、树模型等结果好。
并不是模型越复杂、网络构建的越深,结果就越好。
一定要有自己的思考,想清楚是什么?为什么?该怎么做?

datawhale教程总结

时间序列预测是一个不断发展的领域,随着技术的进步,我们可以期待更多的优化方法和模型的出现。深度学习模型,特别是LSTM和其变体,已经在许多时间序列预测任务中显示出了优越的性能。未来的研究可能会集中在以下几个方面:

  • 更复杂的模型结构:如引入注意力机制的LSTM模型,以更好地捕捉时间序列中的长期依赖关系。
  • 多模态数据融合:结合时间序列数据和其他类型的数据,如文本或图像,以提供更全面的分析。
  • 模型解释性:提高模型的可解释性,以便更好地理解预测结果。
  • 自动化特征工程:开发自动化的特征工程工具,以减少手动特征提取的工作量。
  • 实时预测:提高模型在实时数据流上的预测能力。
  • 模型鲁棒性:提高模型对异常值和噪声的鲁棒性。
    随着技术的不断发展,我们可以期待时间序列预测在准确性、效率和应用范围上都会有显著的提升。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值