题目背景及思路
锂离子电池生产参数调控及生产温度预测挑战赛是2023 iFLYTEK A.I.开发者大赛中的一个子竞赛。
原题的链接为2023 iFLYTEK A.I.开发者大赛-讯飞开放平台 (xfyun.cn)
赛题实质上为回归任务,通过电炉空间温度推测产品内部温度,设计烧结过程的温度场和浓度场的最优控制律:
-
任务输入:电炉对应17个温区的实际生产数据,分别是电炉上部17组加热棒设定温度T1-1~T1-17,电炉下部17组加热棒设定温度T2-1~T2-17,底部17组进气口的设定进气流量V1-V17;
-
任务输出:电炉对应17个温区上部空间和下部空间17个测温点的测量温度值。
可使用机器学习模型解决本项目。
1.Baseline
Datawhale举办的2023年暑期AI夏令营提供了本次比赛的baseline。详见AI夏令营 - 飞书云文档 (feishu.cn)
1.1导入所需的库
示例代码
import pandas as pd #读取和处理csv文件的数据
import lightgbm as lgb #机器学习模型LightGBM
from sklearn.metrics import mean_absolute_error #从sklearn.metrics导入回归问题的评价指标平均绝对误差(MAE)的计算函数
from sklearn.model_selection import train_test_split #拆分训练集与验证集的工具
from tqdm import tqdm #显示循环的进度条工具
pandas
Pandas是一个Python数据处理库,用于处理和分析结构化数据,提供了灵活的数据结构和功能强大的工具,简化了数据清洗、预处理和分析的过程。它支持数据的导入、清洗、转换、聚合、统计计算和数据可视化。
lightgbm
LightGBM是一个高效的机器学习库,用于梯度提升树模型(Gradient Boosting Decision Tree)。它的设计目标是在大规模数据集上实现高性能,具有较快的训练速度和较低的内存占用。LightGBM广泛用于分类、回归和排序等机器学习任务,尤其适用于处理高维稀疏数据和大规模数据集。
sklearn
Scikit-learn(sklearn)是一个强大的Python机器学习库,用于构建、训练和评估各种机器学习模型。它提供了丰富的算法和工具,用于分类、回归、聚类、降维等任务。Scikit-learn还包含数据预处理、特征工程、模型选择和评估等功能,适用于解决数据挖掘、数据分析和预测建模等多种数据科学问题。
sklearn.metrics
sklearn.metrics是Scikit-learn库中的一个模块,用于评估机器学习模型的性能和准确度。它提供了一系列常用的性能指标,如MAE、准确率、召回率、F1-score等,用于评估分类和回归模型的预测效果。该模块还支持绘制ROC曲线、学习曲线和混淆矩阵等工具,有助于进行模型选择和调优。
sklearn.model_selection
sklearn.model_selection是Scikit-learn库中的一个模块,用于模型选择和评估的工具集合。它提供了交叉验证、参数调优、数据集划分等功能,帮助用户选择最佳的机器学习模型和参数组合,防止过拟合,并提高模型的泛化能力。这个模块在训练机器学习模型时起到了至关重要的作用,帮助用户更好地优化模型性能和可靠性。
tqdm
tqdm用于在循环迭代过程中添加进度条,方便用户实时了解代码的执行进度。它适用于各种循环结构,如for循环和while循环,以及其他迭代操作,使得处理大规模数据或耗时任务时可以直观地查看任务的完成情况。tqdm的简单接口和实用功能使其成为了数据处理、训练模型等任务中常用的进度条工具。
1.2数据准备
示例代码
train_dataset = pd.read_csv("./data/train.csv") # 原始训练数据。
test_dataset = pd.read_csv("./data/test.csv") # 原始测试数据(用于提交)。
submit = pd.DataFrame() # 定义提交的最终数据。
submit["序号"] = test_dataset["序号"] # 对齐测试数据的序号,保持与原始测试数据的一致性。
MAE_scores = dict() # 定义评分项。
print(train_dataset[:3][:]) # 查看训练集的前三行
运行结果
序号 时间 流量1 流量2 流量3 流量4 流量5 \ 0 1 2022/11/6 9:08 35.668999 36.146000 25.558001 26.195 25.670000 1 2 2022/11/6 9:09 35.995998 36.347000 25.382000 26.348 26.131001 2 3 2022/11/6 9:11 35.340000 36.311001 25.469999 26.093 25.639000 流量6 流量7 流量8 ... 下部温度8 下部温度9 下部温度10 下部温度11 下部温度12 \ 0 15.702 16.690001 15.991 ... 827 827 827 827 827 1 15.523 16.825001 15.871 ... 827 827 827 827 827 2 15.564 15.564000 15.947 ... 827 827 827 827 827 下部温度13 下部温度14 下部温度15 下部温度16 下部温度17 0 827 827 827 827 750 1 827 827 827 827 750 2 827 827 827 827 750 [3 rows x 87 columns]
因为本项目要求我们对上部的17个温度和下部的17个温度进行预测,所以训练集的最后34列是需要预测的目标变量,除去“序号”一列后的其他变量则是特征变量。
1.3模型参数设置
示例代码
pred_labels = list(train_dataset.columns[-34:]) #训练数据集的最后34列是需要预测的目标变量。
train_set, valid_set = train_test_split(train_dataset, test_size=0.2) # 拆分数据集,
# 设定 LightGBM 训练参,查阅参数意义:https://lightgbm.readthedocs.io/en/latest/Parameters.html
lgb_params = {
'boosting_type': 'gbdt', #使用的提升方法,使用梯度提升决策树gbdt。
'objective': 'regression', #优化目标,这里设置为'regression',表示使用回归任务进行优化。
'metric': 'mae', #评估指标,使用MAE,表示使用平均绝对误差作为评估指标。
'min_child_weight': 5, #子节点中样本权重的最小和,用于控制过拟合。
'num_leaves': 2 ** 5, #每棵树上的叶子节点数,影响模型的复杂度。
'lambda_l2': 10, #L2正则化项的权重,用于控制模型的复杂度。
'feature_fraction': 0.8, #随机选择特征的比例,用于防止过拟合。
'bagging_fraction': 0.8, #随机选择数据的比例,用于防止过拟合。
'bagging_freq': 4, # 随机选择数据的频率,用于防止过拟合。
'learning_rate': 0.05, #学习率,控制每次迭代的步长。
'seed': 2023, #随机种子,用于产生随机性,保持结果的可重复性。
'nthread' : 16, #并行线程数,用于加速模型训练。
'verbose' : -1, #控制训练日志输出,-1表示禁用输出。
}
#调整参数是优化模型性能的重要手段
no_info = lgb.callback.log_evaluation(period=-1) # 回调函数no_info:禁用训练日志输出。
#LightGBM通常会输出一些训练过程的信息,通过回调函数可以避免输出这些信息,使得训练过程更简洁。
1.4时间特征提取
示例代码
# 定义时间特征函数
def time_feature(data: pd.DataFrame, pred_labels: list = None) -> pd.DataFrame:
"""提取数据中的时间特征。
输入:
data: Pandas.DataFrame
需要提取时间特征的数据。
pred_labels: list, 默认值: None
需要预测的标签的列表。如果是测试集,不需要填入。
输出: data: Pandas.DataFrame
提取时间特征后的数据。
"""
# 接收数据集DataFrame和可选参数pred_labels,返回处理后的DataFrame
data = data.copy() # 复制数据,避免后续影响原始数据。
data = data.drop(columns=["序号"]) # 去掉”序号“特征。
data["时间"] = pd.to_datetime(data["时间"]) # 将”时间“特征的文本内容转换为 Pandas 可处理的格式。
data["month"] = data["时间"].dt.month # 添加新特征“month”,代表”当前月份“。
data["day"] = data["时间"].dt.day # 添加新特征“day”,代表”当前日期“。
data["hour"] = data["时间"].dt.hour # 添加新特征“hour”,代表”当前小时“。
data["minute"] = data["时间"].dt.minute # 添加新特征“minute”,代表”当前分钟“。
data["weekofyear"] = data["时间"].dt.isocalendar().week.astype(int) # 添加新特征“weekofyear”,代表”当年第几周“,
# 并转换成 int整数类型,否则 LightGBM 无法处理。
data["dayofyear"] = data["时间"].dt.dayofyear # 添加新特征“dayofyear”,代表”当年第几日“。
data["dayofweek"] = data["时间"].dt.dayofweek # 添加新特征“dayofweek”,代表”当周第几日“。
data["is_weekend"] = data["时间"].dt.dayofweek // 6 # 添加新特征“is_weekend”,代表”是否是周末“,1 代表是周末,0 代表不是周末。
data = data.drop(columns=["时间"]) # LightGBM 无法处理这个特征,它已体现在其他特征中,故丢弃。
if pred_labels: # 如果提供了 pred_labels 参数,则执行该代码块。
data = data.drop(columns=[*pred_labels]) # 去掉所有待预测的标签。在模型训练时不使用这些标签。
return data # 返回最后处理的数据。
test_features = time_feature(test_dataset) # 处理测试集的时间特征,无需 pred_labels。
test_features.head(5) # 打印前五行
运行结果
流量1 | 流量2 | 流量3 | 流量4 | 流量5 | 流量6 | 流量7 | 流量8 | 流量9 | 流量10 | ... | 下部温度设定16 | 下部温度设定17 | month | day | hour | minute | weekofyear | dayofyear | day of week | is_weekend | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 24.222000 | 24.070000 | 28.889000 | 25.264999 | 26.827999 | 4.354 | 21.784 | 24.323000 | 16.721001 | 18.652000 | ... | 837 | 750 | 3 | 1 | 4 | 15 | 9 | 60 | 2 | 0 |
1 | 24.138000 | 23.974001 | 28.798000 | 25.517000 | 27.108999 | 4.377 | 21.886 | 24.384001 | 16.709999 | 18.600000 | ... | 837 | 750 | 3 | 1 | 4 | 20 | 9 | 60 | 2 | 0 |
2 | 24.152000 | 24.006001 | 28.827999 | 25.114000 | 26.719999 | 4.293 | 21.611 | 24.099001 | 16.591999 | 18.471001 | ... | 837 | 750 | 3 | 1 | 4 | 26 | 9 | 60 | 2 | 0 |
3 | 24.108999 | 23.974001 | 28.783001 | 25.114000 | 27.047001 | 4.354 | 21.857 | 24.337999 | 16.677999 | 18.561001 | ... | 837 | 750 | 3 | 1 | 4 | 31 | 9 | 60 | 2 | 0 |
4 | 24.350000 | 24.215000 | 29.146999 | 25.416000 | 25.416000 | 4.377 | 21.900 | 24.444000 | 16.775999 | 18.704000 | ... | 837 | 750 | 3 | 1 | 4 | 36 | 9 | 60 | 2 | 0 |
5 rows × 59 columns
1.5训练与预测
示例代码
# 从所有待预测特征中依次取出标签进行训练与预测。
for pred_label in tqdm(pred_labels):
#print("当前的pred_label是:", pred_label)
train_features = time_feature(train_set, pred_labels=pred_labels) # 处理训练集的时间特征。
# 调用time_feature函数处理训练集train_set的时间特征,并将处理后的训练集数据赋值给train_features
train_labels = train_set[pred_label] # 训练集的标签数据。
#print("当前的train_labels是:", train_labels)
# 获取当前特征标签pred_label在训练集中的真实值,并赋值给train_labels。
train_data = lgb.Dataset(train_features, label=train_labels) # 将训练集转换为 LightGBM 可处理的类型。
# 将train_features和train_labels转换为LightGBM可处理的数据类型,创建训练数据集train_data。
valid_features = time_feature(valid_set, pred_labels=pred_labels) # 处理验证集的时间特征。
# 调用time_feature函数处理验证集valid_set的时间特征,并将处理后的验证集数据赋值给valid_features。
valid_labels = valid_set[pred_label] # 验证集的标签数据。
#print("当前的valid_labels是:", valid_labels)
# 获取当前特征标签pred_label在验证集中的真实值,并赋值给valid_labels。
valid_data = lgb.Dataset(valid_features, label=valid_labels) # 将验证集转换为 LightGBM 可处理的类型。
# 训练模型,参数依次为:导入模型设定参数、导入训练集、设定模型迭代次数(5000)、导入验证集、禁止输出日志
model = lgb.train(lgb_params, train_data, 5000, valid_sets=valid_data, callbacks=[no_info])
valid_pred = model.predict(valid_features, num_iteration=model.best_iteration) # 选择效果最好的模型进行验证集预测。
test_pred = model.predict(test_features, num_iteration=model.best_iteration) # 选择效果最好的模型进行测试集预测。
MAE_score = mean_absolute_error(valid_pred, valid_labels) # 计算验证集预测数据与真实数据的 MAE。
MAE_scores[pred_label] = MAE_score # 将对应标签的 MAE 值 存入评分项中。
submit[pred_label] = test_pred # 将测试集预测数据存入最终提交数据中。
print(MAE_scores) # 查看各项的MAE值
print(sum(MAE_scores.values())) # 查看MAE总和
运行结果
100%|██████████████████████████████████████████████████████████████████████████████████| 34/34 [04:59<00:00, 8.82s/it]
{'上部温度1': 3.0007354204561403, '上部温度2': 2.8769845360473867, '上部温度3': 2.1331975731835926, '上部温度4': 1.496321896163088, '上部温度5': 1.7414880068987442, '上部温度6': 1.6522228703240565, '上部温度7': 1.062672203970032, '上部温度8': 0.15500730578287003, '上部温度9': 0.08802446117671768, '上部温度10': 0.0799846388226232, '上部温度11': 0.10613279409730655, '上部温度12': 0.11317730155973206, '上部温度13': 0.12038375264220602, '上部温度14': 0.16357750552491948, '上部温度15': 0.2736541311741262, '上部温度16': 0.35155734632815766, '上部温度17': 0.8035987916809865, '下部温度1': 2.244238251563849, '下部温度2': 1.3729720288294205, '下部温度3': 0.7246732497275274, '下部温度4': 0.42613780618053937, '下部温度5': 0.7370350707972151, '下部温度6': 0.5115162092478198, '下部温度7': 0.3022968269132582, '下部温度8': 0.09583816784017218, '下部温度9': 16.569409135821726, '下部温度10': 0.08869039580783693, '下部温度11': 0.14689112119248152, '下部温度12': 0.15972231603079792, '下部温度13': 0.15577821738687878, '下部温度14': 0.14749604309107325, '下部温度15': 0.232563376644599, '下部温度16': 0.30401002849671765, '下部温度17': 0.3687768162154011}
40.80676559762
1.6保存文件
示例代码
submit.to_csv("submit_result.csv", index=False) # 保存最后的预测结果到 submit_result.csv,可以自定义路径,如果按照本行代码,则该文件保存在同目录的文件夹中,文件名为submit_result.csv.
2.模型优化
2.1优化思路
笔者采用的baseline的优化思路具体有以下几个方面:
- 改变test_size的值
- 设定表现更好的LightGBM参数
- 改变模型迭代次数
- 提取更多特征
- 选取其他的boosting_type如Dart和随机森林(RF)
可以通过观察改变以上三类参数的值各项MAE的值大致判断模型的优劣,一般来说,MAE越小,模型的拟合优度越高。但由于赛事主办方提供的训练数据集和最终用于评分的数据集不同,训练过程中会产生过拟合导致更小的MAE值却跑出更差的分数。因此MAE对于我们建立的模型优劣程度的评估有一定局限性。
为了确定合适的超参数,可以采用贝叶斯优化的方法。
贝叶斯优化是一种用于全局优化的概率建模和优化方法。它在寻找目标函数的最优解时,通过不断地选择具有较高可能性的参数值来指导搜索过程,从而高效地找到全局最优解或接近最优解的点。
2.2导入所需的库
示例代码
from skopt import gp_minimize
from skopt.space import Real, Categorical, Integer
scikit-optimize(skopt)
scikit-optimize(skopt)是一个用于进行贝叶斯优化的python库。它可以帮助解决复杂问题的参数调优、超参数优化和全局最优化任务。通过高效的贝叶斯优化算法,skopt能够在黑盒函数或不可导的情况下寻找全局最优解,并加速模型优化过程。
skopt.space
skopt中的skopt.space模块用于定义参数搜索空间。它提供了一组工具,使用户可以定义和管理参数的搜索范围、类型和分布。通过space模块,可以轻松地创建参数空间,并将其用于贝叶斯优化或其他优化算法,以在指定范围内搜索最优参数配置。
2.3定义参数搜索空间
此处我们从LightGBM的参数中选取五个参数进行优化。
示例代码
hyperparameter_space = [
Integer(1, 100, name='min_child_weight'),
Integer(10, 200, name='num_leaves'),
Real(0.1, 1.0, prior='log-uniform', name='feature_fraction'),
Real(0.1, 1.0, prior='log-uniform', name='bagging_fraction'),
Real(0.01, 0.02, prior='log-uniform', name='learning_rate')
]
-
Real:
Real
类用于定义实数类型的参数。通过指定参数的最小值和最大值,可以定义一个连续的参数空间。这样的参数可以是任意实数值,可以用于优化连续型的超参数,例如学习率、正则化参数等。当使用log-uniform
分布时,参数在搜索空间内将按照对数均匀分布进行采样。对数均匀分布使得在参数较大范围内能够更加均匀地进行搜索,避免了在线性尺度上过于稀疏或密集的采样。 -
Categorical:
Categorical
类用于定义离散类型的参数。通过指定参数的取值列表,可以定义一个离散的参数空间。这样的参数只能从预定义的取值中进行选择,适用于优化分类变量或多选一的超参数。 -
Integer:
Integer
类用于定义整数类型的参数。通过指定参数的最小值和最大值,可以定义一个整数范围的参数空间。这样的参数只能在整数范围内取值,适用于优化整数型的超参数,例如决策树的最大深度、神经网络的隐藏层数等。
2.4定义目标函数与回调函数
def objective_function(hyperparameters): # 以超参数作为函数变量
min_child_weight = hyperparameters[0]
num_leaves = hyperparameters[1]
feature_fraction = hyperparameters[2]
bagging_fraction = hyperparameters[3]
learning_rate = hyperparameters[4] # 超参数空间中的参数
lgb_params = {
'boosting_type':'gbdt',
'objective':'regression',
'metric':'mae',
'min_child_weight':min_child_weight, # 赋值
'num_leaves':num_leaves, # 赋值
'lambda_12':10,
'feature_fraction':feature_fraction, # 赋值
'bagging_fraction':bagging_fraction, # 赋值
'bagging_freq':4,
'learning_rate':learning_rate, # 赋值
'seed':2023,
'nthread':16,
'verbose':-1,
}
no_info = lgb.callback.log_evaluation(period=-1)
test_features = time_feature(test_dataset)
# 训练过程与baseline一致
for pred_label in tqdm(pred_labels):
train_features = time_feature(train_set,pred_labels=pred_labels)
train_labels = train_set[pred_label]
train_data = lgb.Dataset(train_features,label=train_labels)
valid_features = time_feature(valid_set,pred_labels=pred_labels)
valid_labels = valid_set[pred_label]
valid_data = lgb.Dataset(valid_features,label=valid_labels)
model = lgb.train(lgb_params,train_data,5000,valid_sets=valid_data,callbacks=[no_info])
valid_pred = model.predict(valid_features,num_iteration=model.best_iteration)
test_pred = model.predict(test_features, num_iteration=model.best_iteration)
MAE_score = mean_absolute_error(valid_pred,valid_labels)
MAE_scores[pred_label] = MAE_score
submit[pred_label] = test_pred
return sum(MAE_scores.values()) # 对象函数的返回值应当是评价指标,据此对超参数逐步优化
# 定义打印每一次迭代后的超参数和评价指标的函数
def print_status(hyperparameter):
print(hyperparameter)
print(f"Current MAE Score: {sum(MAE_scores.values())}")
2.5优化过程与优化结果
示例代码
# 调用最小化目标函数的gp_minimize函数,参数依次为目标函数,超参数空间,优化迭代的总次数,随机数种子,回调函数。
result = gp_minimize(objective_function, hyperparameter_space, n_calls=20, random_state=42,callback=print_status)
# 打印使目标函数的返回值(在本项目中即为评价指标)最小的超参数和评价指标
best_hyperparameters = result.x
best_evaluation_metric = result.fun
print(best_hyperparameters)
print(best_evaluation_metric)
以上代码实现了LightGBM中的五个超参数的优化,实际上可以根据需求添加test_size,模型迭代次数和LightGBM的其余超参数等更多的超参数进行优化。
对于模型迭代次数的确定,还可以通过打印训练过程中的loss随迭代次数的变化曲线选取合适的模型迭代次数,或者在LightGBM的参数中设置early_stopping_rounds参数。early_stopping_rounds是一个用于梯度提升树的防止过拟合的参数,它可以提前停止模型的训练,从而防止模型在训练集上过度拟合,并在验证集上找到较好的泛化性能。
2.6提取更多特征
在baseline中,只提取了时间特征,实际上,我们还可以提取更多的特征。通过提取更多特征,可以为模型提供更多的信息和细节,帮助模型更好地捕捉数据中的模式和关联,从而提高模型的性能和预测准确度。考虑提取以下几个特征:
(1)交叉特征:主要提取流量、上部温度设定、下部温度设定之间的关系;
(2)历史平移特征:通过历史平移获取上个阶段的信息;
(3)差分特征:可以帮助获取相邻阶段的增长差异,描述数据的涨减变化情况。在此基础上还可以构建相邻数据比值变化、二阶差分等;
(4)窗口统计特征:窗口统计可以构建不同的窗口大小,然后基于窗口范围进统计均值、最大值、最小值、中位数、方差的信息,可以反映最近阶段数据的变化情况。
示例代码
# 交叉特征
for i in range(1,18):
train[f'流量{i}/上部温度设定{i}'] = train[f'流量{i}'] / train[f'上部温度设定{i}']
test[f'流量{i}/上部温度设定{i}'] = test[f'流量{i}'] / test[f'上部温度设定{i}']
train[f'流量{i}/下部温度设定{i}'] = train[f'流量{i}'] / train[f'下部温度设定{i}']
test[f'流量{i}/下部温度设定{i}'] = test[f'流量{i}'] / test[f'下部温度设定{i}']
train[f'上部温度设定{i}/下部温度设定{i}'] = train[f'上部温度设定{i}'] / train[f'下部温度设定{i}']
test[f'上部温度设定{i}/下部温度设定{i}'] = test[f'上部温度设定{i}'] / test[f'下部温度设定{i}']
# 历史平移
for i in range(1,18):
train[f'last1_流量{i}'] = train[f'流量{i}'].shift(1)
train[f'last1_上部温度设定{i}'] = train[f'上部温度设定{i}'].shift(1)
train[f'last1_下部温度设定{i}'] = train[f'下部温度设定{i}'].shift(1)
test[f'last1_流量{i}'] = test[f'流量{i}'].shift(1)
test[f'last1_上部温度设定{i}'] = test[f'上部温度设定{i}'].shift(1)
test[f'last1_下部温度设定{i}'] = test[f'下部温度设定{i}'].shift(1)
# 差分特征
for i in range(1,18):
train[f'last1_diff_流量{i}'] = train[f'流量{i}'].diff(1)
train[f'last1_diff_上部温度设定{i}'] = train[f'上部温度设定{i}'].diff(1)
train[f'last1_diff_下部温度设定{i}'] = train[f'下部温度设定{i}'].diff(1)
test[f'last1_diff_流量{i}'] = test[f'流量{i}'].diff(1)
test[f'last1_diff_上部温度设定{i}'] = test[f'上部温度设定{i}'].diff(1)
test[f'last1_diff_下部温度设定{i}'] = test[f'下部温度设定{i}'].diff(1)
# 窗口统计
for i in range(1,18):
train[f'win3_mean_流量{i}'] = (train[f'流量{i}'].shift(1) + train[f'流量{i}'].shift(2) + train[f'流量{i}'].shift(3)) / 3
train[f'win3_mean_上部温度设定{i}'] = (train[f'上部温度设定{i}'].shift(1) + train[f'上部温度设定{i}'].shift(2) + train[f'上部温度设定{i}'].shift(3)) / 3
train[f'win3_mean_下部温度设定{i}'] = (train[f'下部温度设定{i}'].shift(1) + train[f'下部温度设定{i}'].shift(2) + train[f'下部温度设定{i}'].shift(3)) / 3
test[f'win3_mean_流量{i}'] = (test[f'流量{i}'].shift(1) + test[f'流量{i}'].shift(2) + test[f'流量{i}'].shift(3)) / 3
test[f'win3_mean_上部温度设定{i}'] = (test[f'上部温度设定{i}'].shift(1) + test[f'上部温度设定{i}'].shift(2) + test[f'上部温度设定{i}'].shift(3)) / 3
test[f'win3_mean_下部温度设定{i}'] = (test[f'下部温度设定{i}'].shift(1) + test[f'下部温度设定{i}'].shift(2) + test[f'下部温度设定{i}'].shift(3)) / 3
将这段代码插入baseline的time_feature函数中,feature函数即可提取时间特征、交叉特征、历史平移特征、差分特征和窗口统计特征。
笔者在本地jupyter lab实际运行提取更多特征的代码时,发现会出现性能警告:DataFrame对象内存碎片化。为了避免展示页面的冗余,导入warning库并忽略警告。
import warnings
warnings.filterwarnings("ignore")
2.7选用其他的boosting_type
LightGBM是一种高效的梯度提升框架,它是一种基于树的集成学习算法。在LightGBM中,有三种常用的Boosting类型,它们分别是:Gradient Boosting Decision Tree (GBDT)、Random Forest (RF) 和Dart。
-
Gradient Boosting Decision Tree (GBDT): GBDT是LightGBM的默认Boosting类型,它是一种基于梯度提升的决策树。它的特点包括:
- 顺序构建:GBDT是通过顺序构建决策树来逐步提升模型的预测能力。每个新树都是为了纠正前一轮树的预测误差而构建的。
- 低方差高偏差:由于顺序构建,GBDT倾向于有较高的模型偏差,因此它在处理复杂数据时可能过拟合。
- 较快的训练速度:相对于传统的GBDT,LightGBM的GBDT能够更快地进行训练,这得益于特殊的数据结构和采用直方图算法优化的方法。
-
Random Forest (RF): RF在LightGBM中是通过设置
boosting_type
参数为"rf"来实现的。它是一种基于决策树的集成学习算法,它的特点包括:- 随机特征选择:RF在构建每棵树的过程中,对于每个节点的特征选择是随机的,这有助于减少模型的相关性,防止过拟合。
- Bagging策略:RF采用Bagging策略,通过对训练集进行有放回的随机采样来训练每棵树,进一步增强模型的泛化能力。
- 适用于高维数据:RF在处理高维数据时表现较好,且对于异常值和噪声具有较好的鲁棒性。
-
Dart: Dart在LightGBM中是通过设置
boosting_type
参数为"dart"来实现的。它是一种基于Dropouts的Boosting算法,它的特点包括:- Dropouts正则化:Dart使用Dropouts正则化技术,类似于神经网络中的Dropout层,它可以随机丢弃一部分树和叶节点,有助于减少过拟合。
- 低内存消耗:Dart通过仅保留一部分树来降低内存消耗,从而在处理大规模数据时表现出色。
- 较慢的训练速度:由于Dropouts正则化和对部分树的保留,Dart的训练速度相对较慢。
gbdt
示例代码
lgb_params = {
'boosting_type': 'gbdt',
'objective': 'regression',
'metric': 'mae',
'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': 2023,
'nthread' : 16,
'verbose' : -1,
}
rf
示例代码
lgb_params = {
'boosting_type': 'rf',
'objective': 'regression',
'metric': 'mae',
'min_child_weight': 5,
'num_leaves': 2**5,
'learning_rate': 0.05,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq':4,
'min_data_in_leaf': 50, # 每个叶子节点最少包含的数据个数,用于控制模型的复杂性
'num_trees': 200, # 随机森林中树的个数
'max_depth': 10, # 每棵树的最大深度,用于防止过拟合
'min_gain_to_split': 0.01, # 分裂节点所需的最小增益,用于控制树的生长
'bagging_seed': 2023, # 随机种子,用于每轮迭代的随机抽样
'feature_fraction_seed': 2023, # 随机种子,用于每轮迭代的特征抽样
'random_state': 2023,
'seed': 2023, # 随机种子,用于控制整个模型的随机性
'nthread': 16,
'verbose': -1,
}
dart
示例代码
lgb_params = {
'boosting_type': 'dart',
'objective': 'regression',
'metric': 'mae',
'num_leaves': 2 ** 5,
'lambda_l2': 10,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 4,
'learning_rate': 0.05,
'drop_rate': 0.1, # 每轮迭代中要随机丢弃的树的比例
'skip_drop': 0.5, # 每个树的被留下来的概率
'seed': 2023,
'nthread': 16,
'verbose': -1,
}
关于LightGBM训练参数的意义可进一步查阅Parameters — LightGBM 4.0.0.99 documentation
3.XGBoost与LightGBM
了解XGBoost的数学原理可移步陈天奇博士关于XGBoost的论文:https://arxiv.org/pdf/1603.02754.pdf
3.1XGBoost的优点及问题
优点:
- 二阶泰勒展开提高了精度
- 正则化方法更稳健
- 借鉴了随机森林的方法,在其中引入了列抽样,降低过拟合
- 稀疏自主感知
- 对Cache使用进行了优化,数据块大小提高读写速度
- 近似分枝(level-wise)支持并行化训练
问题:
- 精确贪心算法需要反复迭代反复遍历,计算量和内存消耗都很大
- level-wise策略增长可能带来很多不必要的叶子节点
- 尽管Cache感知,但仍有大量Cache missing,造成页面调度时间过大
3.2LightGBM的改进
主要优化点:
- 基于Histogram的决策树算法
- 带深度限制的Leaf-wise的叶子生长策略
- 直方图做差加速
- 直接支持类别特征(Categorical Feature)
- Cache命中率优化
- 基于直方图的稀疏特征优化
- 多线程优化
主要提出的方法
- 直方图算法
- 数据并行与特征并行
- GOSS梯度采样
- EFB方法