Datawhale机器学习夏令营实践赛笔记(一)-JiemouQAQ

Datawhale机器学习夏令营实践赛笔记(一)-JiemouQAQ

学习指导

官方学习手册:https://datawhaler.feishu.cn/docx/YqpgdYp4VolgvYxtE0ncWtvLnTg
ML实践教程:https://datawhaler.feishu.cn/docx/PB2nd23zyoPtvYxEfxncBwJBnDf
课程录播:https://space.bilibili.com/431850986

注:
作为一名机器学习与python小白,明确学习目的是很关键的。
本次比赛注重于实践与理论相结合,采用先练后学、边学边练的模式,解决在python学习中生啃语法而无从训练的难题,我们的学习分为精读baseline,学习算法理论,优化代码与深入三个部分。

赛题简介

官方链接:锂离子电池生产参数调控及生产温度预测挑战赛
https://challenge.xfyun.cn/topic/info?type=lithium-ion-battery&ch=bvSQT7Y
注:必读数据说明部分

一键运行:https://aistudio.baidu.com/aistudio/projectdetail/6512066?sUid=2554132&shared=1&ts=1689841007106
注:每日8G,合理使用免费资源

赛题分析:
本次比赛为数据挖掘类型的比赛,聚焦于工业场景。
本赛题实质上为回归任务,其中会涉及到时序预测相关的知识。
注:回归任务,模型选择与设定以此为准
注:时序预测,特征工程的构建与优化以此进行

通过电炉空间温度推测产品内部温度,设计烧结过程的温度场和浓度场的最优控制律:
任务输入:电炉对应17个温区的实际生产数据,分别是电炉上部17组加热棒设定温度T1-1 ~ T1-17,电炉下部17组加热棒设定温度T2-1~T2-17,底部17组进气口的设定进气流量V1-V17;
任务输出:电炉对应17个温区上部空间和下部空间17个测温点的测量温度值。
值得注意的是预测目标为34个,所以需要我们进行34次模型训练和预测。
注:参考文件,还包括序号,时间两列

同时数据规模比较小,可以快速处理数据和搭建模型,对于机器要求8g内存即可。
本次为结构化赛题,包含电炉烧结每个时间段的流量、上下部设定温度,以及预测目标上下部测量温度值。
注:数据规模是选择模型的重要因素之一

baseline精读

导入所需的库

# 导入所需的库
import pandas as pd # panda库,基础,用于处理数据的工具
import lightgbm as lgb # 机器学习模型 LightGBM,本次使用的非第三方api
from sklearn.metrics import mean_absolute_error # 评分 MAE 的计算函数,常见的评分还有MSE等
from sklearn.model_selection import train_test_split # 拆分训练集与验证集工具,常见工具
from tqdm import tqdm # 显示循环的进度条工具,可视化工具,确认代码运行效率

注:Pandas是一个功能强大且灵活的Python库,用于数据处理和分析。它提供了高性能的数据结构,特别是DataFrame(类似于Excel中的表格),使得数据操作变得简单且高效。
注:LightGBM是一个高效且快速的梯度提升树(Gradient Boosting Decision Tree)框架,由微软亚洲研究院开发。它是一种基于树(Trees)的机器学习算法,可用于分类和回归问题。类似的还有XGBoost。

数据准备

# 数据准备
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() # 定义评分项。

注:如本地运行,应下载好相应的数据文件,并根据路径进行修改
注:MAE即Mean Absolute Error,平均绝对误差,相关的值还有均方根误差(Root,Mean Square Deviation,RMSE )。MAE是预测值减去真实值的绝对值累加再除以个数,也就是预测值减去真实值的绝对值累加求平均。可参考https://blog.csdn.net/qq_36171491/article/details/123286460

模型训练

# 模型训练
pred_labels = list(train_dataset.columns[-34:]) # 需要预测的标签,为倒数34列至最后一列,对应上部温度1-17与下部温度1-17,是我们要预测的值。
train_set, valid_set = train_test_split(train_dataset, test_size=0.2) # 拆分数据集,训练量为20%,常用的有0.15-0.3

# 设定 LightGBM 训练参数,查阅参数意义:https://lightgbm.readthedocs.io/en/latest/Parameters.html
lgb_params = {
   'boosting_type': 'gbdt',     # 使用的提升方法,使用梯度提升决策树gbdt,默认。还有随机森林'rf',正则化'dart'。
        'objective': 'regression',   # 优化目标,这里设置为'regression',表示使用回归任务进行优化。分类问题应设定为其他参数。
        'metric': 'mae',             # 评估指标,使用MAE,表示使用平均绝对误差作为评估指标。对于回归方法还有'mse''rmse'。
        'min_child_weight': 5,       # 子节点中样本权重的最小和,用于控制过拟合。
        'num_leaves': 2 ** 5,        # 每棵树上的叶子节点数,影响模型的复杂度。默认31,此处为2的5次方32。
        'lambda_l2': 10,             # L2正则化项的权重,用于控制模型的复杂度。增加此值将使模型更加保守。推荐的候选值为:[0, 0.1, 0.5, 1]
        'feature_fraction': 0.8,     # 随机选择特征的比例,用于防止过拟合。
        'bagging_fraction': 0.8,     # 随机选择数据的比例,用于防止过拟合。默认值1,指定采样出 subsample * n_samples 个样本用于训练弱学习器。注意这里的子采样和随机森林不一样,随机森林使用的是放回抽样,而这里是不放回抽样。 取值在(0, 1)之间,设置为1表示使用所有数据训练弱学习器。如果取值小于1,则只有一部分样本会去做GBDT的决策树拟合。选择小于1的比例可以减少方差,即防止过拟合,但是会增加样本拟合的偏差,因此取值不能太低。推荐的候选值为:[0.6, 0.7, 0.8, 0.9, 1]
        'bagging_freq': 4,           # 随机选择数据的频率,用于防止过拟合。数值型,默认值0,表示禁用样本采样。如果设置为整数 z ,则每迭代 k 次执行一次采样。
        'learning_rate': 0.05,       # 学习率,控制每次迭代的步长。LightGBM 不完全信任每个弱学习器学到的残差值,为此需要给每个弱学习器拟合的残差值都乘上取值范围在(0, 1] 的 eta,设置较小的 eta 就可以多学习几个弱学习器来弥补不足的残差。推荐的候选值为:[0.01, 0.015, 0.025, 0.05, 0.1]
        'seed': 2023,                # 随机种子,用于产生随机性,保持结果的可重复性。
        'nthread' : 16,              # 并行线程数,用于加速模型训练。
        'verbose' : -1,              # 控制训练日志输出,-1表示禁用输出。
        """
        其他参数
        'max_depth':20,    #决定了树的深度,即每个叶节点所达到的最大深度。默认设置为-1,表示不限制树的深度,可能导致过拟合,建议20及以下。
        'n_estimators':200,    #表示迭代的次数(即树的数量),默认值为100,在后面有其他参数控制
        'min_child_samples':30,    #叶节点样本的最少数量,默认值20,用于防止过拟合。
        'early_stopping_rounds':6,    #指定迭代多少次没有得到优化则停止训练,默认值为None,表示不提前停止训练。
        """
    }

no_info = lgb.callback.log_evaluation(period=-1) # 禁用训练日志输出。

注:可参考https://juejin.cn/post/6844903661160628231,https://tianchi.aliyun.com/notebook/170914

特征工程

def data_feature(data: pd.DataFrame, pred_labels: list=None) -> pd.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 无法处理这个特征,它已体现在其他特征中,故丢弃。

    # 交叉特征
    for i in range(1,18):
        data[f'流量{i}/上部温度设定{i}'] = data[f'流量{i}'] / data[f'上部温度设定{i}']   
        data[f'流量{i}/下部温度设定{i}'] = data[f'流量{i}'] / data[f'下部温度设定{i}']
        data[f'上部温度设定{i}/下部温度设定{i}'] = data[f'上部温度设定{i}'] / data[f'下部温度设定{i}']
        
    # 历史平移
        data[f'last1_流量{i}'] = data[f'流量{i}'].shift(1)
        data[f'last1_上部温度设定{i}'] = data[f'上部温度设定{i}'].shift(1)
        data[f'last1_下部温度设定{i}'] = data[f'下部温度设定{i}'].shift(1)

    # 差分特征
        data[f'last1_diff_流量{i}'] = data[f'流量{i}'].diff(1)
        data[f'last1_diff_上部温度设定{i}'] = data[f'上部温度设定{i}'].diff(1)
        data[f'last1_diff_下部温度设定{i}'] = data[f'下部温度设定{i}'].diff(1)
        
    # 窗口统计
    for i in range(1,18):
        data[f'win3_mean_流量{i}'] = (data[f'流量{i}'].shift(1) + data[f'流量{i}'].shift(2) + data[f'流量{i}'].shift(3)) / 3
        data[f'win3_mean_上部温度设定{i}'] = (data[f'上部温度设定{i}'].shift(1) + data[f'上部温度设定{i}'].shift(2) + data[f'上部温度设定{i}'].shift(3)) / 3
        data[f'win3_mean_下部温度设定{i}'] = (data[f'下部温度设定{i}'].shift(1) + data[f'下部温度设定{i}'].shift(2) + data[f'下部温度设定{i}'].shift(3)) / 3

    if pred_labels: # 如果提供了 pred_labels 参数,则执行该代码块。
        data = data.drop(columns=[*pred_labels]) # 去掉所有待预测的标签。
    
    return data # 返回最后处理的数据。

test_features = data_feature(test_dataset) # 处理测试集的时间特征,无需 pred_labels。

注:
(1)交叉特征:主要提取流量、上部温度设定、下部温度设定之间的关系;
(2)历史平移特征:通过历史平移获取上个阶段的信息;
(3)差分特征:可以帮助获取相邻阶段的增长差异,描述数据的涨减变化情况。在此基础上还可以构建相邻数据比值变化、二阶差分等;
(4)窗口统计特征:窗口统计可以构建不同的窗口大小,然后基于窗口范围进统计均值、最大值、最小值、中位数、方差的信息,可以反映最近阶段数据的变化情况。

训练预测

# 从所有待预测特征中依次取出标签进行训练与预测。
for pred_label in tqdm(pred_labels):
    train_features = data_feature(train_set, pred_labels=pred_labels) # 处理训练集的时间特征。
    train_labels = train_set[pred_label] # 训练集的标签数据。
    train_data = lgb.Dataset(train_features, label=train_labels) # 将训练集转换为 LightGBM 可处理的类型。

    valid_features = data_feature(valid_set, pred_labels=pred_labels) # 处理验证集的时间特征。
    valid_labels = valid_set[pred_label] # 验证集的标签数据。
    valid_data = lgb.Dataset(valid_features, label=valid_labels) # 将验证集转换为 LightGBM 可处理的类型。

    # 训练模型,参数依次为:导入模型设定参数、导入训练集、设定模型迭代次数(200)、导入验证集、禁止输出日志
    model = lgb.train(lgb_params, train_data, 200, 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 # 将测试集预测数据存入最终提交数据中。
     
submit.to_csv('submit_result_new.csv', index=False) # 保存最后的预测结果到 submit_result_new.csv。
print(MAE_scores) # 查看各项的 MAE 值。

提交得分

得分截图

改进思路

1、过拟合现象
在代码运行的时候可以将callbacks=[no_info]去除,以查看模型运行的日志,运行结束后也可以利用代码print(MAE_scores)来打印出最终的损失,观察不难看出,模型会在一定时间后进入过拟合,因此可以尝试以一些措施来缓解这种现象。
利用lightgbm自带的early_stopping_rounds参数。
采用多折验证的方式扩大数据量,使得模型泛化能力增强。

2、 参数设定
此次基线没有做精确的调参处理,因此可以调参的范围还是挺大的,常用的搜索参数策略有两种:
网格搜索(Grid Search):这是一种传统的参数调整方法,它会测试指定参数的所有可能组合来找出最佳参数。但是,当参数空间较大时,这种方法可能会消耗大量计算资源和时间。
随机搜索(Random Search):与网格搜索相比,随机搜索不会测试所有的参数组合,而是在参数空间中随机选择一定数量的参数组合进行测试。尽管随机搜索可能无法找到最优的参数组合,但在计算资源有限的情况下,它是一个有效的选择。

3、 模型设定
我们此次基线采用的是LightGBM,我们也可以试试XGBoost, Adaboost, Catboost等等传统的模型,并且也可以使用深度学习的方法构建循环神经网络来处理此次任务,因为本质是一个时序任务,在采用机器学习的同时也可以保存模型的参数配置,尝试进行模型集成的训练。

4、 特征
在特征选择方面,目前给出的特征仅仅是单独对时间处理,与对流量\上\下\部温度设定进行处理,并未结合时间与其他特征的关系,可以尝试自己构建合理的新特征。

5、 后处理
对已经给出的csv文件仍然可以分析其趋势,抓住评估的关键来调整文件内容使得结果更加精确。

6、 迭代步数
目前设置的迭代步数为200轮,其实这对于某些预测数据来说是不够的,可以尝试自己增大迭代步数

原理讲解

优化思路与实践

后续内容将在下一篇文章讲解,感谢阅读!

你可能感兴趣的:(机器学习,笔记)