优化方案
在提分实践部分,将在原有Baseline基础上做更多优化,一般优化思路,从特征工程与模型中来思考。
优化方法建议:
-
提取更多特征:在数据挖掘比赛中,特征总是最终制胜法宝,去思考什么信息可以帮助我们提高预测精准度,然后将其转化为特征输入到模型。
- 尝试不同的模型:模型间存在很大的差异,预测结果也会不一样,比赛的过程就是不断的实验和试错的过程,通过不断的实验寻找最佳模型,同时帮助自身加强模型的理解能力。
1.特征优化
这里主要构建了历史平移特征、差分特征、和窗口统计特征;每种特征都是有理可据的,具体说明如下:
(1)历史平移特征:通过历史平移获取上个阶段的信息;
(2)差分特征:可以帮助获取相邻阶段的增长差异,描述数据的涨减变化情况。在此基础上还可以构建相邻数据比值变化、二阶差分等;
(3)窗口统计特征:窗口统计可以构建不同的窗口大小,然后基于窗口范围进统计均值、最大值、最小值、中位数、方差的信息,可以反映最近阶段数据的变化情况。
参考代码如下:
# 合并训练数据和测试数据
data = pd.concat([train, test], axis=0).reset_index(drop=True)
data = data.sort_values(['id','dt'], ascending=False).reset_index(drop=True)
# 历史平移
for i in range(10,36):
data[f'target_shift{i}'] = data.groupby('id')['target'].shift(i)
# 历史平移 + 差分特征
for i in range(1,4):
data[f'target_shift10_diff{i}'] = data.groupby('id')['target_shift10'].diff(i)
# 窗口统计
for win in [15,30,50,70]:
data[f'target_win{win}_mean'] = data.groupby('id')['target'].rolling(window=win, min_periods=3, closed='left').mean().values
data[f'target_win{win}_max'] = data.groupby('id')['target'].rolling(window=win, min_periods=3, closed='left').max().values
data[f'target_win{win}_min'] = data.groupby('id')['target'].rolling(window=win, min_periods=3, closed='left').min().values
data[f'target_win{win}_std'] = data.groupby('id')['target'].rolling(window=win, min_periods=3, closed='left').std().values
# 历史平移 + 窗口统计
for win in [7,14,28,35,50,70]:
data[f'target_shift10_win{win}_mean'] = data.groupby('id')['target_shift10'].rolling(window=win, min_periods=3, closed='left').mean().values
data[f'target_shift10_win{win}_max'] = data.groupby('id')['target_shift10'].rolling(window=win, min_periods=3, closed='left').max().values
data[f'target_shift10_win{win}_min'] = data.groupby('id')['target_shift10'].rolling(window=win, min_periods=3, closed='left').min().values
data[f'target_shift10_win{win}_sum'] = data.groupby('id')['target_shift10'].rolling(window=win, min_periods=3, closed='left').sum().values
data[f'target_shift710win{win}_std'] = data.groupby('id')['target_shift10'].rolling(window=win, min_periods=3, closed='left').std().values
代码解释
- 首先,将训练数据(train)和测试数据(test)合并为一个数据集(data),并重置索引。
- 对合并后的数据集按照'id'和'dt'列进行降序排序,并再次重置索引。
- 对每个'id'分组,计算目标变量(target)的历史平移值,范围从10到35。
- 对每个'id'分组,计算目标变量的历史平移值(target_shift10)的差分特征,范围从1到3。
- 对每个'id'分组,计算目标变量在不同窗口大小(15, 30, 50, 70)下的均值、最大值、最小值和标准差。
- 对每个'id'分组,计算目标变量的历史平移值(target_shift10)在不同窗口大小(7, 14, 28, 35, 50, 70)下的均值、最大值、最小值、总和和标准差。
2.模型融合
2.1多个模型的结果进行加权平均融合
进行模型融合的前提是有多个模型的输出结果,比如使用catboost、xgboost和lightgbm三个模型分别输出三个结果,这时就可以将三个结果进行融合,最常见的是将结果直接进行加权平均融合。
下面我们构建了cv_model函数,内部可以选择使用lightgbm、xgboost和catboost模型,可以依次跑完这三个模型,然后将三个模型的结果进行取平均进行融合。
对于每个模型均选择经典的K折交叉验证方法进行离线评估,大体流程如下:
1、K折交叉验证会把样本数据随机的分成K份;
2、每次随机的选择K-1份作为训练集,剩下的1份做验证集;
3、当这一轮完成后,重新随机选择K-1份来训练数据;
4、最后将K折预测结果取平均作为最终提交结果。
lightgbm、xgboost和catboost都是机器学习中的梯度提升决策树(Gradient Boosting Decision Trees, GBDT)算法的实现。这些模型主要用于分类和回归问题,并且通常在各种数据科学竞赛和实际应用中表现出色。
-
lightgbm:lightgbm是一个基于梯度提升决策树算法的开源项目。它具有高效的内存使用和训练速度,支持并行处理,以及许多优化技术,如特征并行、行采样和列采样等。lightgbm还提供了一种称为直方图的特征选择方法,可以自动选择最佳特征子集。
-
xgboost:xgboost是另一个流行的梯度提升库。xgboost具有高度可扩展性和高效性,支持并行处理和GPU加速。它还提供了一些高级功能,如正则化、缺失值处理和自定义损失函数。
-
catboost:catboost是一种基于梯度提升决策树的机器学习算法。catboost的特点是对类别特征的处理非常有效,它可以自动处理类别特征的顺序关系,并使用对称树来提高性能。此外,catboost还支持GPU加速和自动特征选择。
三种模型的具体原理:lightgbm,xgboost,catboost
2.2stacking融合
stacking是一种分层模型集成框架。以两层为例,第一层由多个基学习器组成,其输入为原始训练集,第二层的模型则是以第一层基学习器的输出作为特征加入训练集进行再训练,从而得到完整的stacking模型。
参考代码如下:
def stack_model(oof_1, oof_2, oof_3, predictions_1, predictions_2, predictions_3, y):
'''
输入的oof_1, oof_2, oof_3可以对应lgb_oof,xgb_oof,cat_oof
predictions_1, predictions_2, predictions_3对应lgb_test,xgb_test,cat_test
'''
train_stack = pd.concat([oof_1, oof_2, oof_3], axis=1)
test_stack = pd.concat([predictions_1, predictions_2, predictions_3], axis=1)
oof = np.zeros((train_stack.shape[0],))
predictions = np.zeros((test_stack.shape[0],))
scores = []
from sklearn.model_selection import RepeatedKFold
folds = RepeatedKFold(n_splits=5, n_repeats=2, random_state=2021)
for fold_, (trn_idx, val_idx) in enumerate(folds.split(train_stack, train_stack)):
print("fold n°{}".format(fold_+1))
trn_data, trn_y = train_stack.loc[trn_idx], y[trn_idx]
val_data, val_y = train_stack.loc[val_idx], y[val_idx]
clf = Ridge(random_state=2021)
clf.fit(trn_data, trn_y)
oof[val_idx] = clf.predict(val_data)
predictions += clf.predict(test_stack) / (5 * 2)
score_single = mean_absolute_error(val_y, oof[val_idx])
scores.append(score_single)
print(f'{fold_+1}/{5}', score_single)
print('mean: ',np.mean(scores))
return oof, predictions
stack_oof, stack_pred = stack_model(pd.DataFrame(lgb_oof), pd.DataFrame(xgb_oof), pd.DataFrame(cat_oof),
pd.DataFrame(lgb_test), pd.DataFrame(xgb_test), pd.DataFrame(cat_test), train['target'])
代码解释:
- 定义函数
stack_model
,接受多个模型的输出(oof_1, oof_2, oof_3)和对应的测试集预测(predictions_1, predictions_2, predictions_3),还有目标变量y。 - 将输入的多个模型的输出沿轴1拼接成新的训练集
train_stack
。 - 将对应的测试集预测结果也沿轴1拼接成新的测试集
test_stack
。 - 初始化两个全零数组
oof
和predictions
,用于存储每个折叠的训练集预测结果和最终的平均预测结果。 - 使用
RepeatedKFold
进行5折交叉验证,每次选择一个训练子集,其余部分作为验证集。 - 在每次折叠中,使用岭回归(Ridge regression)拟合当前训练子集。
- 获取当前验证集的预测结果,并更新
oof
和predictions
。 - 计算当前折叠的平均绝对误差,并打印出来。
- 在所有折叠完成后,打印平均误差。
- 返回最终的
oof
和predictions
。
补充说明:
RepeatedKFold 是一种交叉验证方法,它将数据集重复划分为训练集和测试集,从而多次评估模型的性能。这种方法的优势在于其能够更准确地估计模型的性能,因为它通过多次重复的过程减少了评估结果的随机性。
岭回归(Ridge regression)最早是用来处理特征数多于样本数的情况,现在也用于在估计中加入偏差,从而得到更好的估计。同时也可以解决多重共线性问题,岭回归是一种有偏估计。
从得分上看,stacking融合优于多个模型的结果进行加权平均融合,但得分相差不大,只差了3分左右。
深度学习方案尝试
import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import LSTM, Dense, RepeatVector, TimeDistributed
from keras.optimizers import Adam
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
# 数据预处理
def preprocess_data(df, look_back=100):
# 将数据按照id进行分组
grouped = df.groupby('id')
datasets = {}
for id, group in grouped:
datasets[id] = group.values
# 准备训练数据集
X, Y = [], []
for id, data in datasets.items():
for i in range(10, 15): # 每个id构建5个序列
a = data[i:(i + look_back), 3]
a = np.append(a, np.array([0]*(100-len(a))))
X.append(a[::-1])
Y.append(data[i-10:i, 3][::-1])
# 准备测试数据集
OOT = []
for id, data in datasets.items():
a = data[:100, 3]
a = np.append(a, np.array([0]*(100-len(a))))
OOT.append(a[::-1])
return np.array(X, dtype=np.float64), np.array(Y, dtype=np.float64), np.array(OOT, dtype=np.float64)
# 定义模型
def build_model(look_back, n_features, n_output):
model = Sequential()
model.add(LSTM(50, input_shape=(look_back, n_features)))
model.add(RepeatVector(n_output))
model.add(LSTM(50, return_sequences=True))
model.add(TimeDistributed(Dense(1)))
model.compile(loss='mean_squared_error', optimizer=Adam(0.001))
return model
# 构建和训练模型
look_back = 100 # 序列长度
n_features = 1 # 假设每个时间点只有一个特征
n_output = 10 # 预测未来10个时间单位的值
# 预处理数据
X, Y, OOT = preprocess_data(train, look_back=look_back)
# 构建模型
model = build_model(look_back, n_features, n_output)
# 训练模型
model.fit(X, Y, epochs=10, batch_size=64, verbose=1)
# 进行预测
predicted_values = model.predict(OOT)
以下是代码的详细解释:
-
导入库:代码开始处导入了所需的库,包括数据处理库NumPy和Pandas,以及用于构建LSTM模型的Keras库。
-
读取数据:使用Pandas的
read_csv
函数读取训练数据集train.csv
和测试数据集test.csv
。 -
定义数据预处理函数:
-
preprocess_data
函数用于准备训练和测试数据。 -
数据按
id
列进行分组。 -
对于每个
id
,提取特定列(索引为3的列)的数据作为时间序列。 -
创建多个序列,每个序列长度为
look_back
,默认为100。 -
序列被反转,并且如果序列长度不足100,则用0填充。
-
训练数据集的标签
Y
是序列的一部分,同样进行反转。 -
测试数据集(OOT,Out-Of-Time)也被反转并用0填充以满足序列长度要求。
-
-
定义模型构建函数:
-
build_model
函数用于构建LSTM模型。 -
模型包括一个LSTM层,用于学习时间序列数据的特征,以及一个重复向量层
RepeatVector
,用于复制上一个LSTM层的输出以供下一个LSTM层使用。 -
再次使用LSTM层和时间分布的密集层来预测序列的下一个值。
-
模型使用均方误差作为损失函数,并使用Adam优化器。
-
-
构建和训练模型:
-
设置序列长度
look_back
、特征数n_features
和输出时间单位数n_output
。 -
调用
preprocess_data
函数预处理训练数据。 -
使用
build_model
函数构建模型。 -
使用模型的
fit
方法训练模型,指定迭代次数(epochs)和批量大小(batch_size)。
-
-
进行预测:
-
使用训练好的模型对测试数据集进行预测。
-
这里需要注意注意自行将预测结果转化为提交格式
-
补充说明:Numpy中的shape函数
这里返回的元组表示3个维度各包含的元素的个数。
所谓元素,在一维时就是元素的个数,二维时表示行数和列数,三维时a.shape[0]表示创建的块数,a.shape[1]和a.shape[2]表示每一块(每一块都是二维的)的行数和列数。
方案评价
- 数据预处理部分:代码正确地按照id分组并构建了训练和测试数据集。但是,对于每个id,只构建了5个序列,这可能不足以充分训练模型。可以考虑增加序列的数量或者调整其他参数以获得更好的性能。
- 模型构建部分:模型使用了两个LSTM层和一个输出层,这是一个合理的选择。但是,可以尝试调整LSTM层的神经元数量、优化器的学习率等参数以优化模型性能。
- 训练和预测部分:代码正确地训练了模型并进行了预测。但是,没有评估预测结果的准确性,可以使用均方误差(MSE)或其他指标来评估模型的性能。