在这一阶段,我给自己的学习任务是:
1.注释所有的baseline
2.异常值处理
baseline注释
说明:这里的注释是我参照文心一言给的解释完成的,也有些是自己的思考
通过这种整篇代码详细注释的方法,确实有更加帮助我学习baseline部分
对于lgb模型训练部分的理解
这部分即使辅助了文心一言,我的理解也仍然比较困难。我最后的理解是,他对于其他所有部分信息都int化输入,然后输出了一个预测结果。这套程序似乎是比较固定的,比如早期暂停法,当然也还有寻找最好权重等方法,但是在大体上,lgb能调的参数比较固定,就是param呈现的那些~
#import 相关库
import numpy as np
import pandas as pd
#数据分析常用库:numpy矩阵运算相关库,pandas文件处理相关库
import lightgbm as lgb
#挖个坑,待会浅填一下
from sklearn.model_selection import StratifiedKFold, KFold, GroupKFold
from sklearn.metrics import mean_squared_error, mean_absolute_error
'''机器学习相关库:model_selection导入的三个库都是用于数据集的交叉验证。
StratifiedKFold:这是一个用于分层抽样的K折交叉验证,它能够保证在每次折叠中,数据集的各个类别的样本比例大致相同。
KFold:这是一个用于非分层抽样的K折交叉验证,它将数据集随机划分为K个子集,然后使用其中K-1个子集作为训练集,剩下的一个子集作为测试集。
GroupKFold:这是一个用于具有非均匀大小组的分层K折交叉验证,对于每个组,它使用一个独立的分层采样策略。
机器学习相关库:metrics的两个库用于评估回归模型的性能
mean_squared_error:这是一个计算均方误差的函数,它计算的是模型预测值与实际值之间的平方差的平均值。
mean_absolute_error:这是一个计算平均绝对误差的函数,它计算的是模型预测值与实际值之间绝对值的平均值。'''
import matplotlib.pyplot as plt
#matplotlib的绘图库
import tqdm
import sys
import os
import gc
'''tqdm:这是一个用于显示进度条的库,可以在Python程序中添加一个进度提示信息,使用户能够更好地了解代码运行进度。
sys:这是一个标准的Python库,提供了许多与Python解释器和它的环境交互的功能。比如,你可以使用它来获取命令行参数,或者与Python的垃圾回收机制交互。
os:这是一个标准库,提供了许多与操作系统交互的功能。例如,你可以使用它来读写文件,创建或删除目录,获取环境变量等。
gc:这是Python的垃圾回收模块,可以用来控制垃圾回收机制。比如,你可以通过这个模块来手动触发垃圾回收,或者查看垃圾回收的信息。'''
import argparse
import warnings
warnings.filterwarnings('ignore')
'''argparse库是Python的标准库之一,用于编写命令行接口。你可以使用它来定义你的命令行参数和选项,以及处理命令行输入。
warnings库提供了一个API来发送警告消息给用户。这些消息通常用于指出代码中的某些问题,但这些问题并不会阻止代码运行。
warnings.filterwarnings('ignore')会使得所有的警告信息被忽略。这在你确定忽略的警告不会导致你的程序出现问题时很有用。'''
# 聚合数据
train_df = train_power_forecast_history.groupby(['id_encode','ds']).head(1)
del train_df['hour']
#groupby函数被用于根据'id_encode'和'ds'字段对数据进行分组,然后使用head函数选择每个分组的前1条记录。然后删除了列名为'hour'的列。
#实质上就是原本对于一个场站某天24小时的电价进行合并,因为24小时电价都一样,只保留一个,最后再去掉hour列
#groupby起到了一个分组筛选的作用
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']
#在这个代码中,groupby(['id_encode','ds']).sum()首先会将数据根据'id_encode'和'ds'进行分组,并对每个组中的'power'列求和。然后reset_index()将结果DataFrame的index转化为新的列。最后,tmp_df.columns = ['id_encode','ds','power']更改了DataFrame的列名。
#文心一言在sum函数后又补充了reset_index函数,这样又能使tmp_df回到dataframe格式,不至于造成id_encode列部分缺失。以下是文心一言的解释
#文心一言“然而,这里可能存在一个问题,就是在groupby和sum操作之后,你可能需要将index('id_encode','ds')转化为列。这个可以通过使用reset_index()函数实现。这个函数将当前的index转化为一个新的列,并返回新的DataFrame。”
#最后,对列重命名。
#总体来说,就是合并了训练集所有信息到一张表,测试集所有信息到一张表上
# 合并充电量数据
train_df = train_df.merge(tmp_df, on=['id_encode','ds'], how='left')
#👆合并两个数据框(DataFrame)。这段代码将train_df和tmp_df根据'id_encode'和'ds'列进行合并,合并方式为左连接(left)。
#如果train_df和tmp_df中有相同的行,那么merge函数会根据'id_encode'和'ds'列进行匹配,如果在train_df中存在但在tmp_df中不存在的行,那么tmp_df中对应的列将被填充为NaN。
### 合并数据
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')
# 定义要绘制的列
cols = ['power']
# 遍历id_encode的五个值
for ie in [0,1,2,3,4]:
# 获取train_df中id_encode为当前值ie的所有行,并重置索引
tmp_df = train_df[train_df['id_encode']==ie].reset_index(drop=True)
# 再次重置索引,并为新索引添加一个名为'index'的列
tmp_df = tmp_df.reset_index(drop=True).reset_index()
# 遍历要绘制的列
for num, col in enumerate(cols):
# 设置图的大小
plt.figure(figsize=(20,10))
# 创建子图,总共有4行1列,当前为第num+1个子图
plt.subplot(4,1,num+1)
# 绘制图形:x轴为'index',y轴为当前列的值
plt.plot(tmp_df['index'],tmp_df[col])
# 为当前子图设置标题,标题为当前列的名称
plt.title(col)
# 显示图形
plt.show()
# 创建一个新的图,大小为20x5
plt.figure(figsize=(20,5))
def get_time_feature(df, col):
#创建了一个DataFrame的副本(这是为了避免直接修改原始DataFrame),并给新的列命名为'new_col'
df_copy = df.copy()
prefix = col + "_"
#更改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)
#删除了原始的'new_col'列(因为你已经提取了所有需要的信息),并返回新的DataFrame
del df_copy[col]
return df_copy
#调用了这个函数两次,一次用于训练数据集,一次用于测试数据集
train_df = get_time_feature(train_df, 'ds')
test_df = get_time_feature(test_df, 'ds')
#创建了一个列表,该列表包含了测试数据集中所有不是'ds'、'power'和'h3'的列。
cols = [f for f in test_df.columns if f not in ['ds','power','h3']]
# 模型训练与验证
#定义了一个名为cv_model的函数,该函数接受五个参数:一个分类器clf,训练数据的特征train_x,训练数据的目标值train_y,测试数据的特征test_x,以及一个可选的随机种子seed。
def cv_model(clf, train_x, train_y, test_x, seed=2023):
#这行设置了K折交叉验证的折数,即folds = 5表示将数据集分成5个子集,然后使用其中4个子集作为
#训练数据,剩下的一个子集作为验证数据。这个过程会重复5次,每个子集都会有一次作为验证数据的机会。
folds = 5
#这行创建了一个KFold对象,用于进行K折交叉验证。参数n_splits=folds指定了要分成多少个子集,shuffle
#=True表示在划分数据集时要随机打乱顺序,random_state=seed设置了随机种子以使结果可重复。
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循环,用于进行K折交叉验证。每次循环都会将数据集划分为一个新的训练集和验证集。
#enumerate(kf.split(train_x, train_y))同时返回每个子集的索引和对应的训练/验证索引。
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,
}
#在每次循环中,代码将训练数据和验证数据分别提取出来,然后使用LightGBM模型进行训练。这里使用了早停机制(early_stopping_rounds=200),如果在连续200轮的迭代中模型的性能没有提升,就会停止训练。
model = clf.train(params, train_matrix, 3000, valid_sets=[train_matrix, valid_matrix],
categorical_feature=[], verbose_eval=500, early_stopping_rounds=200)
#在每次循环结束后,代码会使用训练得到的模型对验证数据进行预测,然后将预测结果存储在val_pred中,并且将这些预测结果加到oof(out-of-fold predictions)中。
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中,并且将每个折的验证数据的预测结果存储在cv_scores中以便后续评估。
test_predict += test_pred / kf.n_splits
score = np.sqrt(mean_squared_error(val_pred, val_y))
cv_scores.append(score)
print(cv_scores)
#函数返回了K折交叉验证的预测结果和测试数据的预测结果。
return oof, test_predict
#最后两行是调用这个函数,传入训练数据、测试数据和随机种子(如果提供的话)
lgb_oof, lgb_test = cv_model(lgb, train_df[cols], train_df['power'], test_df[cols])
#这里的训练数据和测试数据是上面选的排除了ds,power和h3的列
#输出赛题提交格式的结果
#将LightGBM模型预测的结果(lgb_test)赋值给test_df的'power'列。
#对'power'列中的每个值进行了检查,如果值小于0,将其替换为0。这可能是一个数据清理步骤,以确保所有预测值都是非负的。
#最后,将'id_encode'、'ds'和'power'这三列的数据保存到一个名为'result.csv'的CSV文件中,其中index=False表示在输出中不包含索引列。
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)
异常值处理
思路:根据绘出的图可以明显看出缺失值的范围,于是选择了一个笨思路,用范围找出异常值,再用剩下的值计算均值,最后完成替换。其他处理是对于不同图的异常值不太一样的区别
对了,均值替换的结果好像并不好哎,没有我第一次训练的效果好,猜测异常值对这个模型影响并不大
if ie!=4:
yuzhi=0;
if ie==0:
yuzhi=1000
elif ie==2:
yuzhi=100
tmp_df['power']=tmp_df['power'].astype('int')
# 确定异常值的范围,这里我们设定大于5的值为异常值
outliers = tmp_df['power'][tmp_df['power']<yuzhi]
# 计算剩余数据的均值,这里我们设定小于或等于5的值为正常数据
mean_val = np.mean(tmp_df['power'][tmp_df['power'] >= yuzhi])
# 将异常值替换为均值
tmp_df['power'][tmp_df['power']<yuzhi] = mean_val
else:
dingdian=500
tmp_df['power']=tmp_df['power'].astype('int')
# 确定异常值的范围,这里我们设定大于5的值为异常值
outliers = tmp_df['power'][tmp_df['power']>500]
# 计算剩余数据的均值,这里我们设定小于或等于5的值为正常数据
mean_val = np.mean(tmp_df['power'][tmp_df['power'] <= 500])
# 将异常值替换为均值
tmp_df['power'][tmp_df['power']>500] = mean_val
遇到的bug
1.findfont: Generic family 'sans-serif' not found because none of the following families were found: SimHei
文心一言解答