Datawhale AI 夏令营——电力需求挑战赛——Task2学习笔记

        这一期全面介绍 LightGBM 框架下的优化方案,初步探索使用机器学习模型后的结果,与上一期形成对比。

LGBM介绍

        LightGBM(Light Gradient Boosting Machine)是一个实现 GBDT 算法的框架,支持高效率的并行训练,并且具有更快的训练速度、更低的内存消耗、更好的准确率、支持分布式可以快速处理海量数据等优点。

        GBDT (Gradient Boosting Decision Tree) 是机器学习中一个长盛不衰的模型,其主要思想是利用弱分类器(决策树)迭代训练以得到最优模型,该模型具有训练效果好、不易过拟合等优点。

        LightGBM 框架中还包括随机森林和逻辑回归等模型。通常应用于二分类、多分类和排序等场景。

使用机器学习模型解决问题的一般主要步骤为探索性数据分析、数据预处理、提取特征、切分训练集与验证集、训练模型、预测结果。

数据分析(DA)

        数据格式上一期分析过了,这里就画个图进行展示。

import matplotlib.pyplot as plt
# 不同type类型对应target的平均值
type_target_df = train.groupby('type')['target'].mean().reset_index()
# 图片大小为8*4英寸
plt.figure(figsize=(8, 4))
# 绘制柱形图
plt.bar(type_target_df['type'], type_target_df['target'], color=['blue', 'green'])
# 设定x轴和y轴的标签
plt.xlabel('Type')
plt.ylabel('Average Target Value')
# 设定标题
plt.title('Bar Chart of Target by Type')
# 展示
plt.show()

# 筛选特定 ID 的数据
specific_id_df = train[train['id'] == '00037f39cf']
# 设置图形的大小
plt.figure(figsize=(10, 5))
# 折线图
plt.plot(specific_id_df['dt'], specific_id_df['target'], marker='o', linestyle='-')
# 设置x轴、y轴、标题
plt.xlabel('DateTime')
plt.ylabel('Target Value')
plt.title("Line Chart of Target for ID '00037f39cf'")
plt.show()

         第一张图展示不同类型 (type) 对应的目标值 (target) 的平均值,第二张图展示特定 ID 为 '00037f39cf' 的数据随时间的变化情况。使用图表可以帮助我们直观查看数据的变化与分类等特征,在机器学习领域经常使用,高效便捷。

特征工程(提取特征)

  • 历史平移特征:通过历史平移获取上个阶段的信息;如下图所示,可以将d-1时间的信息给到d时间,d时间信息给到d+1时间,这样就实现了平移一个单位的特征构建。

  • 窗口统计特征:窗口统计可以构建不同的窗口大小,然后基于窗口范围进统计均值、最大值、最小值、中位数、方差的信息,可以反映最近阶段数据的变化情况。如下图所示,可以将d时刻之前的三个时间单位的信息进行统计构建特征给我d时刻。

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

# 历史平移
for i in range(10,30):
    data[f'last{i}_target'] = data.groupby(['id'])['target'].shift(i)
    
# 窗口统计
data[f'win3_mean_target'] = (data['last10_target'] + data['last11_target'] + data['last12_target']) / 3

# 进行数据切分
train = data[data.target.notnull()].reset_index(drop=True)
test = data[data.target.isnull()].reset_index(drop=True)

# 确定输入特征
train_cols = [f for f in data.columns if f not in ['id','target']]

         代码能看懂,但是合起来是什么意思呢?

  • 合并训练集和测试集,确保对整个数据集进行统一处理。
  • 创建时间序列特征(历史平移特征和窗口统计特征),为后续的模型训练提供更多的信息。
  • 将处理后的数据集再次拆分为训练集和测试集。
  • 确定用于模型训练的特征列。

模型的训练与预测

        使用 Lightgbm模型 作为回归模型进行预测。因为数据存在时序关系,所以需要严格按照时序进行切分。

        这里选择原始给出训练数据集中 dt 为 30 之后的数据作为训练数据,之前的数据作为验证数据,这样保证了数据不存在 穿越问题(不使用未来数据预测历史数据)

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']
    # 构建模型输入数据
    train_matrix = lgb.Dataset(trn_x, label=trn_y)
    valid_matrix = lgb.Dataset(val_x, label=val_y)
    # lightgbm参数
    lgb_params = {
        'boosting_type': 'gbdt',
        'objective': 'regression',
        'metric': 'mse',
        'min_child_weight': 5,
        'num_leaves': 2 ** 5,
        'lambda_l2': 10,
        'feature_fraction': 0.8,
        'bagging_fraction': 0.8,
        'bagging_freq': 4,
        'learning_rate': 0.05,
        'seed': 2024,
        'nthread' : 16,
        'verbose' : -1,
    }
    # 训练模型
    model = lgb.train(lgb_params, train_matrix, 50000, valid_sets=[train_matrix, valid_matrix], 
                      categorical_feature=[], verbose_eval=500, early_stopping_rounds=500)
    # 验证集和测试集结果预测
    val_pred = model.predict(val_x, num_iteration=model.best_iteration)
    test_pred = model.predict(test_df[cols], num_iteration=model.best_iteration)
    # 离线分数评估
    score = mean_squared_error(val_pred, val_y)
    print(score)
       
    return val_pred, test_pred
    
lgb_oof, lgb_test = time_model(lgb, train, test, train_cols)

# 保存结果文件到本地
test['target'] = lgb_test
test[['id','dt','target']].to_csv('submit.csv', index=None)

        流程很清晰:将训练数据 划分 成训练集和验证集 -> 使用 LightGBM 训练 模型 -> 对验证集和测试集进行 预测 -> 计算验证集上的均方误差(MSE) -> 返回 验证集和测试集的预测结果 -> 保存 文件。

        实际运行,出现报错。

报错信息:

        使用早停轮次 “early_stopping_rounds” 参数和打印log间隔 “verbose_eval” 参数后出现 UserWarning。

报错位置:

# 训练模型
model = lgb.train(lgb_params, train_matrix, 50000, valid_sets=[train_matrix, valid_matrix],
                      categorical_feature=[], verbose_eval=500, early_stopping_rounds=500)

原因:

        由于 LightGBM 库在更新之后将老版本的函数进行了改动,导致需要 传入的参数 或 传入参数的方式发生了改变。参数 'early_stopping_rounds' 和 'verbose_eval' 已被弃用,改为通过 “callbacks” 参数传入 “early_stopping” 和 “log_evaluation”。

修改为:

from lightgbm import log_evaluation, early_stopping
callbacks = [log_evaluation(period=500), early_stopping(stopping_rounds=500)]

model = lgb.train(lgb_params, train_matrix, 50000, valid_sets=[train_matrix, valid_matrix],
                      categorical_feature=[], callbacks=callbacks)

        其中 period=500 指每迭代 500 次打印一次日志;stopping_rounds=500 指如果验证集的误差在 500 次迭代内没有降低,则停止迭代。

训练后效果:

        这里的提示信息表明在 956 轮后得到最佳迭代,此时的训练集评分为171.445,验证集的评分为183.57,离线分数评估为 183.57。

        我的 lgbm 版本是最新的 4.4.0,函数包的下架导致无法使用某些参数。所以上面要么和我一样修改代码,要么降到 3.3 以下。 

提交官网,查看结果:

总结

        [验证集] 用的是 [训练集+验证集],可以让模型训练过程中分别输出训练集和验证集的拟合程度,如果训练集的分数表现很好,而在验证集上的误差开始增加,那么有可能这是一个过拟合的信号,此刻就需要考虑调整训练参数等方法来优化模型了。这里基本差不多,说明拟合程度不错。

        上面的内容仅仅只是初始代码,很好理解,帮助我们熟悉流程用的,后续对着它进行优化即可。入门 lightgbm 成功,学习了特征工程,继续向前!

优化

        (1)增加更多的特征,例如更长的历史平移特征、多种窗口统计特征(如最大值、最小值、标准差等),并引入一些新的特征。

        这种方法一定产生积极效果吗?不一定。有些特征可能只是噪声,新特征与已有特征高度相关时会导致多重共线性问题......

# 历史平移,增加到40
for i in range(10, 40):
    data[f'last{i}_target'] = data.groupby(['id'])['target'].shift(i)

# 窗口统计
window_sizes = [3, 5, 7, 10]  # 增加不同窗口大小

for window in window_sizes:
    data[f'win{window}_mean_target'] = data[[f'last{i}_target' for i in range(10, 10 + window)]].mean(axis=1)
    data[f'win{window}_std_target'] = data[[f'last{i}_target' for i in range(10, 10 + window)]].std(axis=1)
    data[f'win{window}_max_target'] = data[[f'last{i}_target' for i in range(10, 10 + window)]].max(axis=1)
    data[f'win{window}_min_target'] = data[[f'last{i}_target' for i in range(10, 10 + window)]].min(axis=1)

# 增加一些新的特征
# 日期时间特征
data['month'] = pd.to_datetime(data['dt']).dt.month
data['day'] = pd.to_datetime(data['dt']).dt.day
data['weekday'] = pd.to_datetime(data['dt']).dt.weekday

# 滞后特征的变化率
for i in range(10, 30):
    data[f'last{i}_rate'] = data[f'last{i}_target'] / (data[f'last{i + 1}_target'] + 1e-5)

        这里就尝试增加了几个特征。可以看到,效果反而更 low 了。

        为了解决这些问题,可以考虑进行 特征选择、正则化、交叉验证、特征评估

        特征选择:

from sklearn.linear_model import LassoCV
from sklearn.feature_selection import SelectFromModel

# 使用Lasso进行特征选择
lasso = LassoCV(cv=5).fit(train[train_cols], train['target'])
model = SelectFromModel(lasso, prefit=True)
selected_features = train_cols[model.get_support()]

print(f"Selected features: {selected_features}")

        交叉验证:

from sklearn.model_selection import cross_val_score
import lightgbm as lgb

# 重新定义模型,使用选择的特征
lgb_model = lgb.LGBMRegressor(random_state=42)
tscv = TimeSeriesSplit(n_splits=5)

# 交叉验证评估
scores = cross_val_score(lgb_model, train[selected_features], train['target'], cv=tscv, scoring='neg_mean_squared_error', n_jobs=-1)
print(f"Cross-validated MSE: {-np.mean(scores)}")

        正则化:

# 使用L2正则化
lgb_model = lgb.LGBMRegressor(random_state=42, lambda_l2=10)

# 进行交叉验证
random_search = RandomizedSearchCV(lgb_model, param_distributions=param_dist,
                                   n_iter=100, cv=tscv, verbose=1,
                                   scoring='neg_mean_squared_error', n_jobs=-1)
random_search.fit(train[selected_features], train['target'])

print("Best parameters found: ", random_search.best_params_)
print("Best MSE found: ", -random_search.best_score_)

        特征评估:

import matplotlib.pyplot as plt

# 训练最终模型
final_model = lgb.LGBMRegressor(random_state=42, **random_search.best_params_)
final_model.fit(train[selected_features], train['target'])

# 绘制特征重要性
lgb.plot_importance(final_model, max_num_features=20, importance_type='gain')
plt.show()

        测试中。。。

        具体的方案放在下一期吧。

        (2)调整模型参数

        首先理解参数含义:

  • boosting_type:提升类型,LightGBM支持以下几种提升方式:

    • 'gbdt':梯度提升决策树(默认)。
    • 'rf':随机森林。
    • 'dart':带dropout的梯度提升决策树。
    • 'goss':梯度单边抽样。
  • objective:目标函数,用于指定要优化的目标。'regression'表示回归问题,适用于连续数值预测。

  • metric:评估指标,用于评估模型性能。'mse'(均方误差)是回归问题常用的评估指标。

  • min_child_weight:最小子节点样本权重和。这个参数用于控制叶子节点中最小的样本权重和,避免过拟合。

  • num_leaves:叶子节点数,控制树的复杂度。较大的叶子节点数可能会导致过拟合,较小的叶子节点数可能会导致欠拟合。

  • lambda_l2:L2正则化参数,用于防止过拟合。值越大,正则化效果越强。

  • feature_fraction:特征采样比例,在每次迭代中随机选择一定比例的特征用于构建树,防止过拟合。值为0.8表示每次迭代使用80%的特征。

  • bagging_fraction:样本采样比例,在每次迭代中随机选择一定比例的样本用于构建树,防止过拟合。值为0.8表示每次迭代使用80%的样本。

  • bagging_freq:Bagging的频率,表示每隔多少次迭代进行一次bagging。值为4表示每4次迭代进行一次样本采样。

  • learning_rate:学习率,控制每次迭代的步长。较小的学习率使得模型学习更加稳定,但需要更多的迭代次数。

  • seed:随机种子,用于保证结果的可重复性。设置为2024可以确保每次运行结果一致。

  • nthread:线程数,控制LightGBM并行计算时使用的线程数。值为16表示使用16个线程进行计算。

  • verbose:控制输出的详细程度。-1表示不输出训练过程中的信息。

        优化点:更换提升方式 boosting_type,调整权重 min_child_weight,调整叶子数 num_leaves,缩小学习率 learning_rate,设置线程数 nthread等。假定这些参数的范围,然后放入代码中进行迭代测试,比较后得出最优参数,再用它进行模型优化。

# 假定参数的取值范围,一一测试
param_dist = {
    'num_leaves': [31, 127],
    'max_depth': [-1, 5, 8],
    'learning_rate': [0.01, 0.1],
    'n_estimators': [100, 1000],
    'min_child_samples': [5, 10, 20],
    'subsample': [0.6, 0.8, 1.0],
    'colsample_bytree': [0.6, 0.8, 1.0],
}
# 创建了一个时间序列分割器,将数据分成5个分割用于交叉验证
tscv = TimeSeriesSplit(n_splits=5)
# 初始化模型
lgb_model = lgb.LGBMRegressor(random_state=42)
# 随机搜索的迭代次数为100,使用所有可用的CPU核心进行并行计算
random_search = RandomizedSearchCV(lgb_model, param_distributions=param_dist,
                                   n_iter=100, cv=tscv, verbose=1,
                                   scoring='neg_mean_squared_error', n_jobs=-1)

# 在训练数据上进行参数搜索和模型训练
random_search.fit(train[train_cols], train['target'])

print("Best parameters found: ", random_search.best_params_)
print("Best MSE found: ", -random_search.best_score_)

# 使用最佳参数更新模型参数
lgb_params.update(random_search.best_params_)

        跑了 2 小时,得到结果。最佳参数为:Best parameters found:  {'subsample': 1.0, 'num_leaves': 31, 'n_estimators': 1000, 'min_child_samples': 20, 'max_depth': -1, 'learning_rate': 0.1, 'colsample_bytree': 1.0}

        提交官网测试,得到结果。

        分数反而降低了,是什么原因?

  • 这里的训练集分数降低,可能是数据分布导致区别,或者欠拟合
  • 超参数搜索空间过大或不够合理,随机搜索可能会选择到一个在交叉验证中表现较好的组合,但实际应用时并不理想
  • 即使设置了随机种子,训练过程中的一些随机因素也可能影响结果

        优化方向:

  • 增加参数,更细的粒度
  • 增加交叉验证分数
  • 使用早停法,通过监控验证集的表现来防止过拟合
  • 进一步优化特征工程,确保输入特征的质量和相关性。尝试不同的特征选择方法或构造新的特征
  • 尝试模型集成(如堆叠、混合或投票)来提高模型的泛化能力。例如,结合多个LightGBM模型或不同类型的模型(如随机森林、XGBoost等)

        尝试上述的优化点,不断调试。。。

        下面这个分数低是因为训练轮数少了,再优化吧。。。

参考链接:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值