Datawhale AI夏令营学习笔记 (3)

        在上一篇Datawhale AI 夏令营学习笔记(2)-CSDN博客中我们尝试使用了机器学习的方法进行回归预测。本篇,我们分为两个方面来探索。一方面,我们将继续使用机器学习的方法,在原有的数据上做更多特征工程,在预测上我们使用更多的算法模型。另一方面,我们尝试使用深度学习的方法来进行电力预测。

1 进阶机器学习

        还是那句话“数据与特征是机器学习的上限,而模型与算法只是逼近这个上限。” 我们想要得到更好的预测效果,在数据已经给好的条件下,我们需要对数据做更多的特征工程。但做多特征工程的取得效果的前提是我们得做有意义的特征工程。要做有意义的特征工程,我们首先需要了解时间序列的特性,也就是我们要知道在时间序列中能得到哪些有价值的特征。

1.1 特征工程

1.1.1 时间序列常见的特征

        对于时间序列,我们通常做的特征工程有:

        1)时间特征

时间特征可以用来捕捉周期性和季节性信息。

        日历特征
  • 年、月、日、小时、分钟、秒:提取这些基本时间单位。
  • 星期几:指示某一天是星期几。
  • 是否周末:二元特征,指示某一天是否是周末。
  • 季度:指示某一天属于哪一个季度。
        周期特征
  • 季节性特征:如一天中的时间段、周末或工作日、假期等。
  • 正弦和余弦变换:将周期性特征(如月份、小时)转换为连续的正弦和余弦值,以捕捉周期性模式。

        2)历史平移特征(Lag Features)

        历史平移特征是时间序列数据的过去值,可以反映序列的自相关性。

  • 前一天的值y(t-1)
  • 前一周的值y(t-7)
  • 前一个月的值y(t-30)
  • 多步滞后特征y(t-1), y(t-2), ..., y(t-n)

        3)滑动窗口特征(Rolling Window Features)

        使用窗口函数计算一段时间内的统计特征,捕捉局部趋势和波动。

  • 滚动平均:过去 n 天的平均值。
  • 滚动最大值:过去 n 天的最大值。
  • 滚动最小值:过去 n 天的最小值。
  • 滚动标准差:过去 n 天的标准差。
  • 滚动和:过去 n 天的总和。

        4)差分特征(Differencing Features)

        差分特征是时间序列当前值与过去值的差异,强调序列的变化率。

  • 一阶差分y(t) - y(t-1)
  • 二阶差分(y(t) - y(t-1)) - (y(t-1) - y(t-2))

        5)统计特征

        使用统计方法从时间序列中提取特征,描述序列整体分布和形状。

  • 均值:整个序列或某个窗口内的均值。
  • 方差:整个序列或某个窗口内的方差。
  • 峰度和偏度:描述分布形状的统计量。

        6)频域特征

        使用傅里叶变换或小波变换将时间序列转换到频域,反映时间序列中的频率成分。

  • 傅里叶变换:提取频率成分的特征。
  • 小波变换:捕捉不同尺度下的频率成分。

        7)外部特征

        加入外部数据源中的特征,这些特征可能对预测目标有外部影响。

  • 天气数据:如温度、降雨量等。
  • 市场数据:如股票价格、交易量等。
  • 社会经济数据:如GDP、失业率等。

1.1.2 特征工程实践

        在上篇中,我们对数据集仅仅做了部分数据做了历史平移特征和窗口统计特征。接下来,我们尝试对原来的数据分别进行更多的特征工程。其中,我们在原有的两种特征工程外,加入了一个新的特征工程——差分特征。

代码如下:

# 使用 read_csv() 函数从文件中读取训练集数据,文件名为 'train.csv'
train = pd.read_csv('./data/train.csv')
# 使用 read_csv() 函数从文件中读取测试集数据,文件名为 'train.csv'
test = pd.read_csv('./data/test.csv')

# 合并训练数据和测试数据
data = pd.concat([train, test], axis=0).reset_index(drop=True)
data = data.sort_values(['id', 'dt'], ascending=False).reset_index(drop=True)

# 历史平移
# 对于每个id的房子进行10-35的历史平移操作
for i in range(10, 36):
    data[f'target_shift{i}'] = data.groupby('id')['target'].shift(i)

# 历史平移 + 差分特征
# 在前面所创建的target_shift10列进行1-3的差分操作
for i in range(1, 4):
    data[f'target_shift10_diff{i}'] = data.groupby('id')['target_shift10'].diff(i)

# 窗口统计
# 分别用15 30 50 70大小的窗口来统计窗口内id的target的平均值、最大值、最小值、标准差
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

# 历史平移 + 窗口统计
# 再分别用7 14 28 35 50 70大小的窗口来统计窗口内id的target_shift10的平均值、最大值、最小值、总和、标准差
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

#提取特征列
train_cols = [col for col in data.columns if col not in ['id', 'target']]
#将新的数据集再分割成训练集与测试集
train = data[data['target'].notnull()].reset_index(drop=True)
test = data[data['target'].isnull()].reset_index(drop=True)

1.2 模型训练

        得到全新的特征后,我们开始用其训练模型。除了上一篇中我们使用的LGBM模型之外,我们这次还尝试使用了同样基于GBDT算法的XGBoost模型与CatBoost模型。

1.2.1 XGBoost

XGBoost (eXtreme Gradient Boosting) 是一种增强版的GBDT算法,主要用于高效、灵活和可扩展的梯度提升框架。XGBoost 在多个机器学习竞赛中表现出色,因其速度和性能优势,广泛应用于各类数据挖掘和分析任务。

1.2.1.1 XGBoost的优势

1)高效性:XGBoost 使用优化的算法和硬件支持(如多线程和GPU加速),在计算速度上非常快。

2)正则化:通过 L1 和 L2 正则化来避免过拟合。

3)并行处理:支持并行处理,能够充分利用多核处理器,提高计算效率。

4)处理缺失值:能够自动处理缺失值,确保算法的鲁棒性。

5)可扩展性:支持大规模数据集和分布式计算,适用于大数据场景。

6)灵活性:支持多种损失函数和自定义目标函数,能够处理分类、回归和排序任务。

1.2.1.2 XGBoost的劣势

1)对类别型特征处理不便:XGBoost 并不原生支持类别型特征,需要通过独热编码或标签编码将类别型特征转换为数值型特征,这可能导致特征空间维度爆炸,尤其在类别数很多时。

2)训练时间较长:尽管 XGBoost 进行了很多优化,但在处理非常大规模的数据集时,训练时间仍然可能较长,特别是在使用大量树和深度较深的树时。

3)内存占用高:XGBoost 的内存消耗相对较高,尤其在处理大规模数据时,这可能成为一个瓶颈。

4)需要大量参数调优:为了获得最佳性能,XGBoost 需要进行大量的超参数调优,这可能会花费大量时间和计算资源。

5)不适用于流数据:XGBoost 不适用于流数据或在线学习场景,因为它需要在每次训练时重新构建模型。

1.2.2 CatBoost

CatBoost (Categorical Boosting) 也是一个基于GBDT的算法,特别擅长处理包含大量类别型特征的数据集。CatBoost 在很多情况下能够提供比其他梯度提升算法更好的性能,尤其是在处理类别型特征时。

1.2.2.1 CatBoost的优势

1)处理类别型特征:CatBoost 原生支持类别型特征,无需进行独热编码或标签编码。

2)高效性:通过使用预排序算法和高效的内存使用方式,CatBoost 能够高效处理大规模数据集。

3)避免过拟合:通过使用梯度提升中的随机化技术和内置的正则化,CatBoost 能够有效避免过拟合。

4)支持GPU加速:CatBoost 支持 GPU 加速,能够显著提高训练速度。

5)鲁棒性:在处理含有缺失值的数据时,CatBoost 表现出色。

6)自动化处理:CatBoost 自动处理特征的归一化和类别型特征编码,简化了特征工程的过程。

1.2.2.2 CatBoost的劣势

1)训练时间较长:CatBoost 的训练时间在某些情况下比 XGBoost 更长,尤其在没有使用 GPU 加速的情况下。

2)复杂度高:尽管 CatBoost 在处理类别型特征时非常有效,但其内部实现复杂,初学者可能需要更多时间来理解和掌握其原理和使用方法。

3)对GPU的依赖:CatBoost 的速度优势在很大程度上依赖于 GPU 加速。如果没有 GPU 支持,CatBoost 的训练时间可能显著增加。

4)较新的工具:相较于 XGBoost,CatBoost 是相对较新的工具,因此在文档、社区支持和第三方工具集成方面可能不如 XGBoost 成熟。

5)内存使用问题:虽然 CatBoost 进行了优化,但在处理非常大规模的数据集时,内存使用仍然可能成为一个问题。

1.2.3 训练模型

        由于有不同的模型,我们应当增加代码的通用性,于是我们定义了一个cv_model函数来训练这三个模型。

        代码如下:

def cv_model(clf, train_x, train_y, test_x, clf_name, seed=2024):
    '''
    实现多种模型的交叉验证和预测

    参数:
    clf:调用的模型类(如 lightgbm、xgboost、catboost)
    train_x:训练数据的特征
    train_y:训练数据的标签
    test_x:测试数据的特征
    clf_name:模型的名称,用于选择不同的模型配置
    seed:随机种子,保证结果的可重复性
    '''
    folds = 5  # 交叉验证的折数,默认为5
    kf = KFold(n_splits=folds, shuffle=True, random_state=seed)  # 创建 KFold 交叉验证对象,指定分成几折,并打乱数据顺序
    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 = train_x.iloc[train_index], train_y[train_index]
        val_x, val_y = train_x.iloc[valid_index], train_y[valid_index]

        # 如果使用 LightGBM 模型
        if clf_name == "lgb":
            # 创建 LightGBM 的数据集格式
            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,  # L2 正则化系数
                'feature_fraction': 0.8,  # 每次分裂时用到的特征比例
                'bagging_fraction': 0.8,  # 每次迭代时用的数据比例
                'bagging_freq': 4,  # 每四次迭代执行一次重采样
                'learning_rate': 0.1,  # 学习率
                'seed': 2023,  # 随机种子
                'nthread': 16,  # 线程数
                'verbose': -1,  # 日志记录等级
            }
            # 设置早停回调函数,100轮内若模型效果没有提升,则停止训练
            callback = [lgb.early_stopping(stopping_rounds=100, verbose=200)]
            # 训练模型
            model = clf.train(params, train_matrix, 1000, valid_sets=[train_matrix, valid_matrix],
                              categorical_feature=[], callbacks=callback)
            # 对验证集进行预测
            val_pred = model.predict(val_x, num_iteration=model.best_iteration)
            # 对测试集进行预测
            test_pred = model.predict(test_x, num_iteration=model.best_iteration)

        # 如果使用 XGBoost 模型
        elif clf_name == "xgb":
            xgb_params = {
                'booster': 'gbtree',  # 使用基于树的模型
                'objective': 'reg:squarederror',  # 任务类型:回归
                'eval_metric': 'mae',  # 评估指标:平均绝对误差
                'max_depth': 5,  # 树的最大深度
                'lambda': 10,  # L2 正则化系数
                'subsample': 0.7,  # 训练每棵树时用的数据比例
                'colsample_bytree': 0.7,  # 构建每棵树时用的特征比例
                'colsample_bylevel': 0.7,  # 构建树的每一层时用的特征比例
                'eta': 0.1,  # 学习率
                'tree_method': 'hist',  # 构建树的方法
                'seed': 520,  # 随机种子
                'nthread': 16  # 线程数
            }
            # 创建 XGBoost 的数据集格式
            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)

        # 如果使用 CatBoost 模型
        elif clf_name == "cat":
            params = {
                'learning_rate': 0.1,  # 学习率
                'depth': 5,  # 树的深度
                'bootstrap_type': 'Bernoulli',  # 使用 Bernoulli bootstrap
                'random_seed': 2023,  # 随机种子
                'od_type': 'Iter',  # 使用迭代次数进行过拟合检测
                'od_wait': 100,  # 提前停止的次数
                'allow_writing_files': False  # 禁止写文件
            }
            # 初始化 CatBoost 模型
            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

其中,我们采用了一种常用的模型评估方法——K折交叉检验。

1.2.3.1 K折交叉检验的工作原理

        K折交叉验证 (K-Fold Cross Validation) 是一种常用的模型评估方法,用于衡量机器学习模型的性能。它通过将数据集划分为 K 个子集(即 "折"),并在每个子集上进行模型训练和验证,确保每个子集都作为验证集被使用一次,其余子集作为训练集。

        工作原理:

        1)将数据集随机分成 K 个子集(即折),每个子集的大小尽量相等;

        2)进行 K 次训练和验证。在第 i 次迭代中,使用第 i 个子集作为验证集,其余 K-1 个子集作为训练集;

        3)在每次迭代中,训练模型并评估模型在验证集上的性能(如准确率、均方误差等);

        4)将 K 次验证的结果进行平均或其他统计处理,以获得模型的整体性能指标;

1.3 模型应用

        最后,我们对三个模型的预测结果取平均值并保存提交。


# 选择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_pred = (lgb_test+xgb_test+cat_test) / 3

#保存文件
test['target'] = final_pred
test[['id', 'dt', 'target']].to_csv('submit.csv', index=None)

        得到一个新的分数:

由上述代码可知,我们只是简单的对所使用的三个模型的预测结果进行平均取值作为最终结果。其实,我们能在这最后一步将其预测结果进一步迭代。接下来,我们将使用stacking方法来构建一个新的、泛化能力更强的融合模型。

1.3.1 Stacking

        Stacking(堆叠泛化)是一种集成学习技术,通过结合多个不同的基础模型(也称为初级模型)的预测结果来构建一个更强大的终级模型(也称为次级模型或元模型)。该方法旨在提高模型的泛化能力和预测性能。

        工作原理:

        1)训练初级模型:使用整个训练数据集训练多个不同的初级模型。这些模型可以是同种类型的模型(如多个不同参数的决策树),也可以是不同类型的模型(如决策树、线性回归、神经网络等)。

        2)生成初级模型的预测:对每个初级模型,使用交叉验证来生成预测。将这些预测作为新特征,构建一个新的训练数据集。

        3)训练次级模型:使用初级模型生成的新特征数据集,训练一个次级模型(元模型)。次级模型通常是一个简单的模型,如线性回归或逻辑回归,但也可以是任何机器学习模型。

        4)组合预测:在预测阶段,初级模型对测试数据进行预测,然后将这些预测作为次级模型的输入,最终生成预测结果。

        我们在之前已基本实现1)与  2)的步骤,因此我们接下来尝试实现3)4)步骤。

        代码如下:

def stack_model(oof_1, oof_2, oof_3, predictions_1, predictions_2, predictions_3, y):
    '''
    该函数使用Stacking方法结合三个初级模型的预测结果,训练一个次级模型进行最终预测。
    
    参数:
    oof_1, oof_2, oof_3:训练集上的初级模型的预测结果,可以分别对应lgb_oof, xgb_oof, cat_oof。
    predictions_1, predictions_2, predictions_3:测试集上的初级模型的预测结果,可以分别对应lgb_test, xgb_test, cat_test。
    y:训练集的真实标签。

    返回:
    oof:次级模型在训练集上的预测结果。
    predictions:次级模型在测试集上的预测结果。
    '''

    # 将三个初级模型在训练集上的预测结果拼接成新的训练数据集
    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和predictions数组,用于存储次级模型的预测结果
    oof = np.zeros((train_stack.shape[0],))
    predictions = np.zeros((test_stack.shape[0],))
    scores = []  # 用于存储每次折叠的评估分数

    from sklearn.model_selection import RepeatedKFold
    from sklearn.linear_model import Ridge
    from sklearn.metrics import mean_absolute_error

    # 定义重复K折交叉验证,使用5折交叉验证,重复2次
    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

1.3.2 模型再应用

        经过stacking方法,我们得到了更强大的次级模型,我们将利用次级模型的预测结果作为最终答案。

        


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

test['target'] = stack_pred
test[['id', 'dt', 'target']].to_csv('submit.csv', index=None)

        提交结果,我们再次得到了一个全新的分数!说明使用stacking集成LGBM、XGBoost和CatBoost的次级模型让我们的预测效果更好了!

 2 深度学习

        接下来我们尝试使用深度学习的办法进行预测。

2.1 数据预处理+特征工程

def preprocess_data(df, look_back=100):
    """
    数据预处理函数,用于将时间序列数据转换为模型训练和预测所需的格式。

    参数:
    df:包含时间序列数据的DataFrame
    look_back:窗口大小,即每个样本的时间步数

    返回:
    X:训练数据的输入特征,形状为 (样本数, look_back)
    Y:训练数据的目标值,形状为 (样本数, look_back)
    OOT:测试数据的输入特征,形状为 (样本数, look_back)
    """
     # 历史平移
    # 对于每个id的房子进行10-35的历史平移操作
    for i in range(10, 36):
        df[f'target_shift{i}'] = df.groupby('id')['target'].shift(i)

    # 历史平移 + 差分特征
    # 在前面所创建的target_shift10列进行1-3的差分操作
    for i in range(1, 4):
        df[f'target_shift10_diff{i}'] = df.groupby('id')['target_shift10'].diff(i)

    # 窗口统计
    # 分别用15 30 50 70大小的窗口来统计窗口内id的target的平均值、最大值、最小值、标准差
    for win in [15, 30, 50, 70]:
        df[f'target_win{win}_mean'] = df.groupby('id')['target'].rolling(window=win, min_periods=3,
                                                                             closed='left').mean().values
        df[f'target_win{win}_max'] = df.groupby('id')['target'].rolling(window=win, min_periods=3,
                                                                            closed='left').max().values
        df[f'target_win{win}_min'] = df.groupby('id')['target'].rolling(window=win, min_periods=3,
                                                                            closed='left').min().values
        df[f'target_win{win}_std'] = df.groupby('id')['target'].rolling(window=win, min_periods=3,
                                                                            closed='left').std().values

    # 历史平移 + 窗口统计
    # 再分别用7 14 28 35 50 70大小的窗口来统计窗口内id的target_shift10的平均值、最大值、最小值、总和、标准差
    for win in [7, 14, 28, 35, 50, 70]:
        df[f'target_shift10_win{win}_mean'] = df.groupby('id')['target_shift10'].rolling(window=win, min_periods=3,
                                                                                             closed='left').mean().values
        df[f'target_shift10_win{win}_max'] = df.groupby('id')['target_shift10'].rolling(window=win, min_periods=3,
                                                                                            closed='left').max().values
        df[f'target_shift10_win{win}_min'] = df.groupby('id')['target_shift10'].rolling(window=win, min_periods=3,
                                                                                            closed='left').min().values
        df[f'target_shift10_win{win}_sum'] = df.groupby('id')['target_shift10'].rolling(window=win, min_periods=3,
                                                                                            closed='left').sum().values
        df[f'target_shift710win{win}_std'] = df.groupby('id')['target_shift10'].rolling(window=win, min_periods=3,
                                                                                            closed='left').std().values

    # 归一化处理
    scaler = MinMaxScaler()
    feature_cols = df.columns.difference(['id', 'dt', 'target'])
    df[feature_cols] = scaler.fit_transform(df[feature_cols])

    # 将数据按照id进行分组
    grouped = df.groupby('id')
    datasets = {}
    
    # 将每个id的分组数据存入字典
    for id, group in grouped:
        datasets[id] = group.values

    # 准备训练数据集
    X, Y = [], []
    for id, data in datasets.items():
        # 每个id构建5个序列,每个序列从索引10到14开始,总共look_back长度
        for i in range(10, 15):
            # 提取从i到i+look_back的序列
            a = data[i:(i + look_back), 3]
            # 如果序列长度不足look_back,用0填充
            a = np.append(a, np.array([0] * (100 - len(a))))
            # 将序列翻转后添加到X
            X.append(a[::-1])
            # 提取从i-10到i的目标值序列,并翻转后添加到Y
            Y.append(data[i - 10:i, 3][::-1])

    # 准备测试数据集
    OOT = []
    for id, data in datasets.items():
        # 提取前look_back个时间步的数据
        a = data[:100, 3]
        # 如果序列长度不足look_back,用0填充
        a = np.append(a, np.array([0] * (100 - len(a))))
        # 将序列翻转后添加到OOT
        OOT.append(a[::-1])

    # 将X, Y, OOT转换为numpy数组并返回
    return np.array(X, dtype=np.float64), np.array(Y, dtype=np.float64), np.array(OOT, dtype=np.float64)

2.2 构建与训练模型

        此次我们采用的模型是LSTM模型

2.2.1 LSTM

        LSTM(长短期记忆网络,Long Short-Term Memory network)是一种特殊类型的递归神经网络(RNN),专门用于解决传统RNN在处理长序列时的梯度消失和梯度爆炸问题。LSTM通过引入门机制来控制信息的流动,能够更好地捕捉长时间依赖关系,因此在时间序列预测、自然语言处理、语音识别等领域得到了广泛应用。

        LSTM的结构与工作原理

        一个典型的LSTM单元由三个门和一个记忆单元组成:

  1. 输入门(Input Gate):控制新信息进入记忆单元的程度。选择遗忘哪些信息。接收上一个时间步的隐藏状态和当前时间步的输入,输出一个0到1之间的值,表示要遗忘信息的比例。
  2. 遗忘门(Forget Gate):控制当前记忆单元中哪些信息将被遗忘。选择哪些新信息将写入记忆单元。包括输入门和候选记忆单元。接着更新记忆单元,综合遗忘门和输入门的信息。
  3. 输出门(Output Gate):控制当前记忆单元的输出。选择输出什么信息。接收上一个时间步的隐藏状态和当前时间步的输入。

2.2.2 构建与训练模型

        

# 定义构建模型的函数
def build_model(look_back, n_features, n_output):
    """
    构建并返回一个LSTM模型

    参数:
    look_back: 序列长度
    n_features: 特征数量
    n_output: 输出序列的长度

    返回:
    构建好的LSTM模型
    """
    model = Sequential()
    # 第一层LSTM,50个单元
    model.add(LSTM(50, input_shape=(look_back, n_features)))
    # RepeatVector层,将输入重复n_output次,适用于序列到序列的模型
    model.add(RepeatVector(n_output))
    # 第二层LSTM,50个单元,并返回整个序列
    model.add(LSTM(50, return_sequences=True))
    # TimeDistributed层,对每个时间步应用一个全连接层
    model.add(TimeDistributed(Dense(1)))
    # 编译模型,使用均方误差损失函数和Adam优化器
    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)

2.3 模型应用

# 进行预测
predicted_values = model.predict(OOT)

final = predicted_values.reshape(-1, 1)

test['target'] = final
test[['id', 'dt', 'target']].to_csv('submit.csv', index=None)

        最后,我们提交此次深度学习的结果,得到不同的分数:

        1:特征工程+LSTM模型+reshape预测结果

        2: LSTM模型,选取每个id的最后一个时间步作为所有预测值

        3:LSTM模型,reshape预测结果

        我们经过特征工程与结果选取的优化后,分数并未上升太多。

        我初步推断LSTM模型在此次误差过大的可能原因是:

        1)数据预处理不充分

  • 数据中存在异常值或噪声。
  • 数据未正确归一化或标准化。
  • 特征工程不充分,没有捕捉到重要的特征。

        2)模型架构不合适

  • 模型层数太少,无法捕捉复杂的模式。
  • 模型层数太多,导致过拟合。
  • LSTM单元数设置不合理。

        3) 超参数设置不当

  • 学习率过高或过低。
  • 批量大小设置不合理。
  • 训练轮数过多或过少。

        4)训练数据不足或数据分布不均匀

  • 训练数据量不足,导致模型无法学习到数据中的模式。
  • 数据分布不均匀,模型偏向于某些类别。

        5) 过拟合或欠拟合

  • 训练数据表现很好,但测试数据表现差(过拟合)。
  • 模型无法在训练数据上表现良好(欠拟合)。

        6) 时间序列特征未充分利用

  • 时间序列数据的时间特征未充分利用,如周期性、趋势性等。

3 总结

        本篇中我们完成了对机器学习的优化与深度学习的尝试,我们在此过程中学到了很多东西!未来,也许我们还会对这两部分进行优化......

        

        再次感谢Datawhale为广大学习者提供的一切!!! 

    #机器学习 #电力预测赛 #Datawhale AI夏令营

  • 30
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值