Task2中对“基于lightgbm的baseline”的优化分析
提示:此处仅有作者对标题内容的简析
Task2中对“基于lightgbm的baseline”的优化分析
前言
在阅读“Task2:深入理解赛题,入门RNN和特征工程”文档后,平台给出了数据的特征工程的分析方法,并根据序列模式提取特征。在得到了表格数据之后,平台使用了适用于表格数据的机器学习回归模型lightgbm模型来进行预测。本文章在此方法基础上给出分析和优化。
提示:以下是本篇文章正文内容
基于lightgbm的baseline
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
1.数据加载和预处理
代码如下:
df_original = pd.read_csv("train_data.csv")
n_original = df_original.shape[0]
df_submit = pd.read_csv("sample_submission.csv")
df = pd.concat([df_original, df_submit], axis=0).reset_index(drop=True)
其中,
df将训练数据和提交样本数据合并,为后续的特征工程和模型训练提供完整的数据集。
2.特征工程
代码如下:
def siRNA_feat_builder(s: pd.Series, anti: bool = False):
name = "anti" if anti else "sense"
df = s.to_frame()
# 序列长度
df[f"feat_siRNA_{name}_seq_len"] = s.str.len()
for pos in [0, -1]:
for c in list("AUGC"):
# 第一个和最后一个是否是A/U/G/C
df[f"feat_siRNA_{name}_seq_{c}_{'front' if pos == 0 else 'back'}"] = (
s.str[pos] == c
)
# 是否已某一对碱基开头和某一对碱基结尾
df[f"feat_siRNA_{name}_seq_pattern_1"] = s.str.startswith("AA") & s.str.endswith(
"UU"
)
df[f"feat_siRNA_{name}_seq_pattern_2"] = s.str.startswith("GA") & s.str.endswith(
"UU"
)
df[f"feat_siRNA_{name}_seq_pattern_3"] = s.str.startswith("CA") & s.str.endswith(
"UU"
)
df[f"feat_siRNA_{name}_seq_pattern_4"] = s.str.startswith("UA") & s.str.endswith(
"UU"
)
df[f"feat_siRNA_{name}_seq_pattern_5"] = s.str.startswith("UU") & s.str.endswith(
"AA"
)
df[f"feat_siRNA_{name}_seq_pattern_6"] = s.str.startswith("UU") & s.str.endswith(
"GA"
)
df[f"feat_siRNA_{name}_seq_pattern_7"] = s.str.startswith("UU") & s.str.endswith(
"CA"
)
df[f"feat_siRNA_{name}_seq_pattern_8"] = s.str.startswith("UU") & s.str.endswith(
"UA"
)
# 第二位和倒数第二位是否为A
df[f"feat_siRNA_{name}_seq_pattern_9"] = s.str[1] == "A"
df[f"feat_siRNA_{name}_seq_pattern_10"] = s.str[-2] == "A"
# GC占整体长度的比例
df[f"feat_siRNA_{name}_seq_pattern_GC_frac"] = (
s.str.contains("G") + s.str.contains("C")
) / s.str.len()
return df.iloc[:, 1:]
其中,
siRNA_feat_builder函数对siRNA序列进行特征构造,生成多个新特征:
feat_siRNA_{name}seq_len:序列长度。
feat_siRNA{name}seq{c}{‘front’ if pos == 0 else ‘back’}:序列首尾位置是否为特定碱基。
feat_siRNA{name}seq_pattern*:序列特定模式匹配。
feat_siRNA_{name}_seq_pattern_GC_frac:序列中G和C碱基的比例。
3.类别型变量处理
代码如下:
df_publication_id = pd.get_dummies(df.publication_id)
df_publication_id.columns = [
f"feat_publication_id_{c}" for c in df_publication_id.columns
]
df_gene_target_symbol_name = pd.get_dummies(df.gene_target_symbol_name)
df_gene_target_symbol_name.columns = [
f"feat_gene_target_symbol_name_{c}" for c in df_gene_target_symbol_name.columns
]
df_gene_target_ncbi_id = pd.get_dummies(df.gene_target_ncbi_id)
df_gene_target_ncbi_id.columns = [
f"feat_gene_target_ncbi_id_{c}" for c in df_gene_target_ncbi_id.columns
]
df_gene_target_species = pd.get_dummies(df.gene_target_species)
df_gene_target_species.columns = [
f"feat_gene_target_species_{c}" for c in df_gene_target_species.columns
]
df_siRNA_duplex_id = pd.get_dummies(df.siRNA_duplex_id)
df_siRNA_duplex_id.columns = [
f"feat_siRNA_duplex_id_{c}" for c in df_siRNA_duplex_id.columns
]
df_cell_line_donor = pd.get_dummies(df.cell_line_donor)
df_cell_line_donor.columns = [
f"feat_cell_line_donor_{c}" for c in df_cell_line_donor.columns
]
df_cell_line_donor["feat_cell_line_donor_hepatocytes"] = (
(df.cell_line_donor.str.contains("Hepatocytes")).fillna(False).astype("int")
)
df_cell_line_donor["feat_cell_line_donor_cells"] = (
df.cell_line_donor.str.contains("Cells").fillna(False).astype("int")
)
其中,
使用pd.get_dummies对多个类别型变量进行独热编码。
生成的新特征列名以feat_开头,后接原变量名和类别值。
通过字符串匹配,提取特定词汇,并生成对应的二进制特征。
4.数值型变量处理
代码如下:
df_siRNA_concentration = df.siRNA_concentration.to_frame()
df_Transfection_method = pd.get_dummies(df.Transfection_method)
df_Transfection_method.columns = [
f"feat_Transfection_method_{c}" for c in df_Transfection_method.columns
]
df_Duration_after_transfection_h = pd.get_dummies(df.Duration_after_transfection_h)
df_Duration_after_transfection_h.columns = [
f"feat_Duration_after_transfection_h_{c}"
for c in df_Duration_after_transfection_h.columns
]
其中,
对siRNA浓度变量进行处理,将其转化为DataFrame格式。
对转染方法和转染持续时间变量进行独热编码,生成新的特征列。
5.特征合并
代码如下:
feats = pd.concat(
[
df_publication_id,
df_gene_target_symbol_name,
df_gene_target_ncbi_id,
df_gene_target_species,
df_siRNA_duplex_id,
df_cell_line_donor,
df_siRNA_concentration,
df_Transfection_method,
df_Duration_after_transfection_h,
siRNA_feat_builder(df.siRNA_sense_seq, False),
siRNA_feat_builder(df.siRNA_antisense_seq, True),
df.iloc[:, -1].to_frame(),
],
axis=1,
)
其中,
将所有生成的新特征进行合并,构成最终的特征集feats。
包括独热编码后的类别特征、数值特征和通过siRNA_feat_builder函数构建的序列特征。
5.数据划分与转换格式
代码如下:
X_train, X_test, y_train, y_test = train_test_split(
feats.iloc[:n_original, :-1],
feats.iloc[:n_original, -1],
test_size=0.2,
random_state=42,
)
train_data = lgb.Dataset(X_train, label=y_train)
test_data = lgb.Dataset(X_test, label=y_test, reference=train_data)
其中,
在划分完训练集和测试集后,将训练集和测试集转换为LightGBM的Dataset格式,方便模型训练和评估。
6.定义模型参数和训练
代码如下:
# 定义一个回调函数来打印验证集的结果
def print_validation_result(env):
result = env.evaluation_result_list[-1]
print(f"[{env.iteration}] {result[1]}'s {result[0]}: {result[2]}")
params = {
"boosting_type": "gbdt",
"objective": "regression",
"metric": "root_mean_squared_error",
"max_depth": 7,
"learning_rate": 0.02,
"verbose": 0,
}
gbm = lgb.train(
params,
train_data,
num_boost_round=15000,
valid_sets=[test_data],
callbacks=[print_validation_result],
)
其中,
定义LightGBM模型的参数,包括提升类型、目标任务、评估指标、树的最大深度和学习率。
定义回调函数print_validation_result,用于打印每次迭代后的验证结果。
使用lgb.train进行模型训练,设置提升迭代次数为15000,并提供验证集和回调函数。
7.优化方向
在阅读Task2后,选定了几个优化方向:
特征工程角度来说,增加更多有意义的特征,如序列的理化性质、结构特征等等等。
模型角度来说,用不同的模型做对比实验,也集成多种模型的预测结果。同时,用网格搜索进行超参数调优来一步提升模型性能。
特别是在阅读Task1的优秀博客:https://hqusends.feishu.cn/docx/D10Ld9jzgoMWYkxROnkcMbGWnGd,其中提到“将 LightGBMLarge、CatBoost、NeuralNetFastAI、XGBoost、RandomForestMSE按照特定权重加权后效果也很好。这方面有人专门分析过,应该是自动搜参总结出来的。”后,我便一直在测试多模型的最优加权权重,目前还在调试和优化中,应该会在后续的笔记汇报结果。而已跑通且达到最优效果的的就是下面暂时完成的lightgbm超参数调优代码。
8.超参数调优
代码如下:
# 定义一个回调函数来打印验证集的结果
class CustomLoggerCallback:
def __init__(self, period=1):
self.period = period
def __call__(self, env):
if env.iteration % self.period == 0:
result = env.evaluation_result_list[-1]
print(f"[{env.iteration}] {result[1]}'s {result[0]}: {result[2]}")
# 定义Optuna优化的目标函数
def objective(trial):
# 定义LightGBM的超参数空间
param = {
"objective": "regression", # 回归
"metric": "mae", # 评估指标:平均绝对误差
"verbosity": -1, # 不输出训练信息
"boosting_type": "gbdt", # 梯度提升树
"lambda_l1": trial.suggest_float("lambda_l1", 1e-8, 10.0, log=True), # L1正则化
"lambda_l2": trial.suggest_float("lambda_l2", 1e-8, 10.0, log=True), # L2正则化
"num_leaves": trial.suggest_int("num_leaves", 20, 300), # 叶子节点数
"feature_fraction": trial.suggest_float("feature_fraction", 0.4, 1.0), # 特征采样比例
"bagging_fraction": trial.suggest_float("bagging_fraction", 0.4, 1.0), # 数据采样比例
"bagging_freq": trial.suggest_int("bagging_freq", 1, 7), # 采样频率
"min_child_samples": trial.suggest_int("min_child_samples", 5, 100), # 最小样本数
"feature_pre_filter": False # 不预先过滤特征
}
# 训练LightGBM模型
gbm = lgb.train(
param,
train_data,
valid_sets=[test_data],
num_boost_round=1000,
callbacks=[lgb.early_stopping(stopping_rounds=50), CustomLoggerCallback(period=100)] # 早停(为了加速训练)和自定义日志回调(看着方便)
)
# 预测测试集
preds = gbm.predict(X_test)
# 计算平均绝对误差
mae = mean_absolute_error(y_test, preds)
return mae
# 创建一个Optuna的Study对象,目标为最小化评估指标
study = optuna.create_study(direction="minimize")
# 运行超参数优化,共100次试验
study.optimize(objective, n_trials=100)
# 获取最佳参数
best_params = study.best_params
# 使用最佳参数训练最终模型
model = lgb.LGBMRegressor(**best_params)
model.fit(X_train, y_train)
其中,
使用Optuna进行超参数优化。
(Optuna是一个用于超参数优化的开源自动化调参框架,能帮助用户在机器学习模型训练过程中自动搜索最佳的超参数组合,从而提高模型的性能和泛化能力。其github主页:https://github.com/optuna/optuna)
objective函数定义了LightGBM模型的参数空间,并通过交叉验证计算模型的MAE。
最终结果如下:
最佳超参数:
lambda_l1: 2.9034541808996163e-07
lambda_l2: 0.0015926189116005033
num_leaves: 175
feature_fraction: 0.6628426546211686
bagging_fraction: 0.9696331544527079
bagging_freq: 4
min_child_samples: 14
最佳试验的MAE:
13.04939961067063
最终模型评估结果:
平均绝对误差(MAE):14.2566
特定范围内的平均绝对误差(Range MAE):11.3074
F1分数:0.7373
综合评分(Score):0.7557
分析和总结
可以看出,对lightgbm进行超参数调优后,模型的MAE和Range MAE都在可接受的范围内,表明模型在整体和特定范围内都有较好的预测性能。
而F1分数和综合评分较高,表明模型在低Remaining区间的分类和预测效果较好。
后续我将利用多模型的加权,即模型集成,结合特征工程,来进行更有效的优化。