586. Child Mind Institute - Detect Sleep States | child-mind-institute-detect-sleep-states
首先,非常感谢我的队友 @ryotayoshinobu 的出色表现。请阅读他的解决方案部分。
我很高兴能与他组队,并从中学到了很多。
也要特别感谢主办方组织了如此有趣的竞赛,这确实是一次非常艰难的挑战。
你可以在这里找到解决方案的完整代码:
https://github.com/nikhilmishradevelop/kaggle-child-mind-institute-detect-sleep-states
验证策略:按Series ID分组的GroupKFold交叉验证
模型输入:17280 × n_features长度的序列(17280 = 12步/分钟 × 60分钟 × 24小时)
模型输出:17280 × 2(一个用于入睡时刻,一个用于醒来时刻)
模型类型:回归模型
损失函数:三次方损失,即 abs(y_true-y_pred)**3
对于长度小于17280的剩余序列,通过填充使其长度统一为17280。
我的解决方案采用改进版UNET(共4个模型平均,2个LSTM和2个GRU),使用归一化高斯目标,类似于@tolgadincer描述的方法。非常感谢他早期分享的有效方法。
好的特征有助于更快收敛并获得更好的分数,因此添加有效特征非常重要(请查阅penguin的解决方案了解其他优秀特征)。
我最终4模型集成结果:
CV:0.828
Public LB:0.789
Private LB:0.841
.png?generation=1701841137052567&alt=media)
由于17280是一个非常长的序列长度(导致训练非常缓慢且困难),我们通过分块处理来缩短序列长度。
输入:17280 × n_features(34维)
分块大小:在不同模型中分别使用3、4、5或6
修改后序列长度:从17280降至17280//分块大小
修改后特征大小:k × 4 × 特征数量(k为Dense层输出维度)
UNET编码器 → 瓶颈层 → Transformer → GRU或LSTM → UNET解码器
UNET编码器的每一层都与第一层进行了拼接连接
初始输出大小:17280//分块大小, 2 × 分块大小
重塑后输出大小:(17280, 2)
最初当我独自参赛时,只是简单地使用峰值检测。
我曾想用lightgbm重新排序模型预测,但未能找到好的方法,@kmat2019的帖子对此很有参考价值。
多亏了@ryotayoshinobu,我开始应用NMS(非极大值抑制),并最终试验了一种WBF(加权框融合)算法并使其生效。该WBF算法在最后一天带来了0.79到0.793的分数提升。
但在Private LB上它反而降低了0.001分,所以WBF实际上是有害的。
WBF工作流程
初始化和卷积:函数首先使用指定的卷积核对数据进行平滑处理。
峰值检测循环:迭代搜索数据中的峰值。循环持续执行,直到达到最大数量(max_count)或峰值低于阈值(max_thresh)。
自适应窗口和权重计算:
加权平均和分数计算:对每个检测到的峰值计算加权平均以确定其分数。这是通过考虑峰值本身及其k个邻近值完成的。该分数受权重计算方法和其他超参数(如log_base、log_scale、weight_coeff)的影响。
抑制和更新预测:
我将分享WBF部分的实现代码,你可以查看调整这些超参数是否对你的模型也有帮助。该函数接受每个series id的预测。
(我通过手动和自动超参数调优相结合的方式找到了这些超参数)
def wbf_nikhil(preds_orig, max_thresh=0.1, max_count=700, hyperparams=None):
k_dist = hyperparams['k_dist']
log_base = hyperparams['log_base']
log_scale = hyperparams['log_scale']
curr_max_power = hyperparams['curr_max_power']
weight_coeff = hyperparams['weight_coeff']
convolution_kernel = hyperparams['convolution_kernel']
section_weight_method = hyperparams['section_weight_method']
preds_reduction_power = hyperparams['preds_reduction_power']
overlap_coeff = hyperparams['overlap_coeff']
min_distance = hyperparams['min_distance']
preds = preds_orig.copy()
preds = np.convolve(preds, convolution_kernel, mode='same')
count = 0
indices = []
scores = []
while count < max_count:
curr_max_idx = np.argmax(preds)
curr_max = preds[curr_max_idx]
if curr_max < max_thresh:
break
k = int(k_dist - max(min_distance, (curr_max**curr_max_power)))
start_idx = max(curr_max_idx - k, 0)
end_idx = min(curr_max_idx + k + 1, len(preds))
section = preds[start_idx:end_idx]
# Different weight calculation methods
distances = np.abs(np.arange(len(section)) - k)
if section_weight_method == 'logarithmic':
weights = 1 / (log_base ** (distances / (k * log_scale)))
elif section_weight_method == 'linear':
weights = 1 - (distances / k) * weight_coeff
# Add more methods as needed
weighted_avg = np.sum(section * weights) / np.sum(weights)
scores.append(weighted_avg)
indices.append(curr_max_idx)
preds[start_idx:end_idx] *= ((1 - weights * overlap_coeff))**preds_reduction_power
count += 1
return indices, scores
最终集成是融合我和Penguin的回归预测结果,并使用WBF进行后处理
Final_Sub = WBF(Penguins_Predictions * 0.25 + Nikhil's Predictions * 0.75)
由于我们的预测在回归目标上略有不同的尺度,Penguin的预测首先通过简单的幂变换进行缩放:Penguin's_Predictions ** 0.7
集成结果
CV:0.835
Public LB:0.793
Private LB:0.845
P.S.:这是我第一次没有使用梯度提升(gradient boosting)参加竞赛,即使我知道它效果如此之好,作为表格数据专家的我,这可能是个错误。我热爱lightgbm,下次竞赛一定会使用它。
祝贺所有表现优异的选手,这是一场精彩的较量,直到最后一刻!