学习心得

19 阅读5分钟

导入模块

导入我们本次Baseline代码所需的模块

#import 相关库
import numpy as np
import pandas as pd
import lightgbm as lgb
from sklearn.model_selection import StratifiedKFold, KFold, GroupKFold
from sklearn.metrics import mean_squared_error, mean_absolute_error
import matplotlib.pyplot as plt
import tqdm
import sys
import os
import gc
import argparse
import warnings
warnings.filterwarnings('ignore')

数据 探索性分析,是通过了解数据集,了解变量间的相互关系以及变量与预测值之间的关系,对已有数据在尽量少的先验假设下通过作图、制表、方程拟合、计算特征量等手段探索数据的结构和规律的一种数据分析方法,从而帮助我们后期更好地进行特征工程和建立模型,是机器学习中十分重要的一步。

(1)数据准备

在进行数据分析前,先把准备工作做完,如数据读取,合并基本的数据

# 读取数据
train_power_forecast_history = pd.read_csv('./data/train/power_forecast_history.csv')
train_power = pd.read_csv('./data/train/power.csv')
train_stub_info = pd.read_csv('./data/train/stub_info.csv')

test_power_forecast_history = pd.read_csv('./data/test/power_forecast_history.csv')
test_stub_info = pd.read_csv('./data/test/stub_info.csv')

# 聚合数据
train_df = train_power_forecast_history.groupby(['id_encode','ds']).head(1)
del train_df['hour']

test_df = test_power_forecast_history.groupby(['id_encode','ds']).head(1)
del test_df['hour']

tmp_df = train_power.groupby(['id_encode','ds'])['power'].sum()
tmp_df.columns = ['id_encode','ds','power']

# 合并充电量数据
train_df = train_df.merge(tmp_df, on=['id_encode','ds'], how='left')

### 合并数据
train_df = train_df.merge(train_stub_info, on='id_encode', how='left')
test_df = test_df.merge(test_stub_info, on='id_encode', how='left')

(2)数据探索

时间序列预测问题更多的是观测历史数据的分布情况,从分布中分析是否存在周期性、趋势性、相关性和异常性。

cols = ['power']
for ie in [0,1,2,3,4]:
    tmp_df = train_df[train_df['id_encode']==ie].reset_index(drop=True)
    tmp_df = tmp_df.reset_index(drop=True).reset_index()
    for num, col in enumerate(cols):
        plt.figure(figsize=(20,10))

        plt.subplot(4,1,num+1)
        plt.plot(tmp_df['index'],tmp_df[col])
        plt.title(col)
plt.show()
plt.figure(figsize=(20,5))

上面展示了五个站点的历史一年的用电量数据,可以明显看到异常数据的存在,针对异常数据可以进行前后均值填充、删除对应样本等操作。并且不同站点的用电量存在很大的差异,有的峰值达到3000,有的峰值仅有300。

任务2.2:数据清洗

数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。俗话说:garbage in, garbage out。分析完数据后,特征工程前,必不可少的步骤是对数据清洗。

数据清洗的作用是利用有关技术如数理统计、数据挖掘或预定义的清理规则将脏数据转化为满足数据质量要求的数据。主要包括缺失值处理、异常值处理、数据分桶、特征归一化/标准化等流程。

作为baseline部分暂时不进行细致的数据清洗工作,这部分仅进行数据预处理工作,主要为类型转换

### 数据预处理
train_df['flag'] = train_df['flag'].map({'A':0,'B':1})
test_df['flag'] = test_df['flag'].map({'A':0,'B':1})

任务2.3:特征工程

特征工程指的是把原始数据转变为模型训练数据的过程,目的是获取更好的训练数据特征。特征工程能使得模型的性能得到提升,有时甚至在简单的模型上也能取得不错的效果。

这里主要构建了基本的时间特征,即将“时间”变量转换为datetime格式,然后提取月、日、小时、周等相关特征,主要是为了刻画不同时间阶段可能存在的一致性信息。

def get_time_feature(df, col):
    
    df_copy = df.copy()
    prefix = col + "_"
    df_copy['new_'+col] = df_copy[col].astype(str)
    
    col = 'new_'+col
    df_copy[col] = pd.to_datetime(df_copy[col], format='%Y%m%d')
    df_copy[prefix + 'year'] = df_copy[col].dt.year
    df_copy[prefix + 'month'] = df_copy[col].dt.month
    df_copy[prefix + 'day'] = df_copy[col].dt.day
    df_copy[prefix + 'weekofyear'] = df_copy[col].dt.weekofyear
    df_copy[prefix + 'dayofweek'] = df_copy[col].dt.dayofweek
    df_copy[prefix + 'is_wknd'] = df_copy[col].dt.dayofweek // 6
    df_copy[prefix + 'quarter'] = df_copy[col].dt.quarter
    df_copy[prefix + 'is_month_start'] = df_copy[col].dt.is_month_start.astype(int)
    df_copy[prefix + 'is_month_end'] = df_copy[col].dt.is_month_end.astype(int)
    del df_copy[col]
    
    return df_copy   
    
train_df = get_time_feature(train_df, 'ds')
test_df = get_time_feature(test_df, 'ds')

cols = [f for f in test_df.columns if f not in ['ds','power','h3']]

任务2.4:模型训练与验证

特征工程也好,数据清洗也罢,都是为最终的模型来服务的,模型的建立和调参决定了最终的结果。模型的选择决定结果的上限, 如何更好的去达到模型上限取决于模型的调参。

建模的过程需要我们对常见的线性模型、非线性模型有基础的了解。模型构建完成后,需要掌握一定的模型性能验证的方法和技巧。

作为baseline方案使用lightgbm模型,并选择经典的K折交叉验证方法进行离线评估,大体流程如下:

1、K折交叉验证会把样本数据随机的分成K份;

2、每次随机的选择K-1份作为训练集,剩下的1份做验证集;

3、当这一轮完成后,重新随机选择K-1份来训练数据;

4、最后将K折预测结果取平均作为最终提交结果。

具体代码如下:

# 模型训练与验证
def cv_model(clf, train_x, train_y, test_x, seed=2023):
    folds = 5
    kf = KFold(n_splits=folds, shuffle=True, random_state=seed)
    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, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]
        
        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': 'rmse',
            'min_child_weight': 5,
            'num_leaves': 2 ** 7,
            'lambda_l2': 10,
            'feature_fraction': 0.8,
            'bagging_fraction': 0.8,
            'bagging_freq': 4,
            'learning_rate': 0.1,
            'seed': 2023,
            'nthread' : 16,
            'verbose' : -1,
        }
        model = clf.train(params, train_matrix, 3000, valid_sets=[train_matrix, valid_matrix],
                          categorical_feature=[], verbose_eval=500, early_stopping_rounds=200)
        val_pred = model.predict(val_x, num_iteration=model.best_iteration)
        test_pred = model.predict(test_x, num_iteration=model.best_iteration)
        
        oof[valid_index] = val_pred
        test_predict += test_pred / kf.n_splits
        
        score = np.sqrt(mean_squared_error(val_pred, val_y))
        cv_scores.append(score)
        print(cv_scores)
        
    return oof, test_predict
    
lgb_oof, lgb_test = cv_model(lgb, train_df[cols], train_df['power'], test_df[cols])

结果输出

提交需要符合提交样例结果

#输出赛题提交格式的结果
test_df['power'] = lgb_test
test_df['power'] = test_df['power'].apply(lambda x: 0 if x<0 else x)
test_df[['id_encode','ds','power']].to_csv('result.csv', index=False)