Kaggle比赛记录(三)LANL Earthquake Prediction

       第二次参加Kaggle,是有关地震预测的题目。这一次比赛的成绩并不出彩,具体排名是1878/4540(42%),原因在于后期完全搞错了优化方向,在public LB最高排名达到第5(刚好触碰到3000美金的奖金)的情况下,最终成绩一出,直接shakeup掉了1800+名,连奖牌都没有拿到。这场比赛算是给我这个新手上的一课,最后选择两个模型提交时一定不要盲目相信public LB,最好的做法就是选两个分别在LB和CV表现最好的模型,接下来记录一下我在比赛时的具体做法。

比赛介绍及EDA

       这场比赛的目标是通过声音信号预测地震到来的剩余时间,比赛提供了实验室模拟地震时监测的真实数据。拿到数据后发现,这场比赛的数据集十分庞大,训练数据集train.csv就有8.89GB,凭我实验室的小破电脑是不可能导入成功的,因此只能使用Kaggle提供的计算资源了。
       在Kaggle的Kernel上,导入数据也需要一定的技巧,即限制数据导入的精度。训练数据只有两列,一列是自变量acoustic_data,一列是因变量time_to_failure,也就是地震到来的剩余时间。acoustic_data是整型数据,time_to_failure精确到小数点后10位。在python中,pandas导入的默认精度是float64,把这个精度降低,能够在不影响数据准确度的情况下让导入速度快很多,具体做法是:

train = pd.read_csv('../input/train.csv', dtype = {'acoustic_data': np.int16, 'time_to_failure': np.float32})

       成功导入数据后,来看一看基本的EDA吧。训练数据共有629145480行,我们每隔50个点采样一次,把自变量和因变量都画在一张图上,是这个样子的。
Kaggle比赛记录(三)LANL Earthquake Prediction_第1张图片
       可以看到,蓝线代表地震来临的剩余时间,每次下降到0后会突然升起,然后开始新一轮的下降,这代表实验室模拟的地震是一波接一波的,而我们要做的是根据红线代表的声音信息,去预测地震来临的剩余时间。同时,观察上面的图片可以发现,ttf(time_to_failure,即蓝线)下降到0时,总是伴随着声音信号的突然增强,让我们放大这一部分来看,取上面图片中的前1%部分数据作图。
Kaggle比赛记录(三)LANL Earthquake Prediction_第2张图片
       上图显示,在ttf下降到0之前,声音信号有所增强,而不是ttf到0的同时声音增强,这给预测工作带来了一定的难度。此外还有一个细节,ttf下降过程不是连续的,继续放大可以看到,ttf是呈阶梯状下降的。个人猜测,这可能是因为实验室的声音监控设备无法持续高频的进行监测,所以只能测一段停一段,而时间是平稳流逝的,这就造成了数据集呈现一段一段的现象。
Kaggle比赛记录(三)LANL Earthquake Prediction_第3张图片
       再来看测试集,test文件夹中共有2624个文件,每个文件都有150000行给定的acoustic_data,我们要预测的是地震还有多久到来,也就是最后一行的time_to_failure,取其中12个文件的声音数据,可视化后的是这样的。
Kaggle比赛记录(三)LANL Earthquake Prediction_第4张图片

特征工程

       了解了比赛给的训练、测试数据后,开始构造训练集和测试集吧。在比赛过程中,我发现大部分人使用的训练集是在原始训练数据上无间隔采样生成的,因为每个测试数据都有150000行,而比赛提供了629145480行训练数据,无间隔采样后,就得到了 629145480 150000 = 4194 \frac{629145480}{150000}=4194 150000629145480=4194个样本,相当于每个样本通过15万行的声音数据构造特征,再把最后一行的ttf作为预测目标。我觉得这种做法是否太浪费数据资源了,毕竟对ttf来说,每15万个数据只用了1个去训练。比赛结束后,高分大神的解决方案证明,我的想法应该是对的,他们基本都用了有重叠的采样。

       基于上述想法,我在比赛初期的训练数据集就跟大多数人的不一样,我将6亿多个数据均分为6部分,然后每部分随机采样4000个样本,这样我就有了24000个样本。之所以要分为6部分,而不是直接采样24000个样本,是考虑到构建特征的时间太长了。分为6部分,可以直接用多线程构建特征,总时长就能大大缩短。当然,这是在有六核以上处理器的情况,对于Kaggle的计算资源,有过kernel使用经验的人应该都知道,commit六次就能达到六核效果了。
       接着是特征工程了。这场比赛一个很大的遗憾是我没有花很多时间在特征工程上,由于特征工程涉及到很多信号处理的知识,一些地方我也是一知半解,所以基本上都在用kernel区公开的特征。首先为了除去信号中的直流分量,对每个样本的15万个声音数据进行0均值处理,然后:
       (1)将信号通过巴特沃斯低通滤波器后,进行快速傅里叶变换,得到实数部分realFFT、虚数部分imagFFT、模magFFT以及相位phzFFT,在多个频带上分别取它们的分位数、均值、方差、最大值、最小值作为特征;
       (2)将信号通过多个巴特沃斯滤波器后,包括低于2500的低通、2500~5000、 ⋯ \cdots 、 17500~20000的带通、高于20000的共计8个滤波器,再分别求信号的均值、方差、分位数、绝对值分位数、平均离差、偏度、峰度等,以及不同范围的相邻信号差值均值之比,线性回归的斜率、Hilbert变换后的均值,hann窗卷积后的均值,短期数据与长期数据之比,移动窗口rolling均值,多个指数均线的均值等等。
       这样一来,总共就生成了868个特征。此外,我在kernel区又找到了一篇信号降噪的例子,跟着这篇kernel的做法把原始信号通过小波降噪和高通滤波处理后,再一次生成特征,这样我们就有了 868 × 2 = 1736 868\times 2=1736 868×2=1736个特征。下面是信号降噪的效果图。
Kaggle比赛记录(三)LANL Earthquake Prediction_第5张图片

特征筛选及模型训练

       生成完特征后,开始进行模型训练。在正式的训练之前,我也对这1700+个特征进行了选择。特征选择的方法就有很多了,包括Lasso回归、相关系数、PCA主成分分析以及用随机森林等模型预训练一遍得到特征重要性等等,在这场比赛里我选择了皮尔森相关系数,因为发现它筛选后的LB分数最好(这也是我在这场比赛里失败的开始)。
       我选取了皮尔森系数的前1000个特征放入模型进行训练,在这场比赛里我只用了两个模型——XGBoost与LightGBM。接着是调参,调参的过程是痛苦的,因为这场比赛的交叉验证(CV)分数与公共排行榜(public LB)分数严重不相关。在数据量很大,训练一次需要好几个小时的情况下,很多时候好不容易通过调参得到了一个满意的CV分数,提交后发现LB得分特别差。

相信CV还是LB?

       面对这种情况,我在讨论区中问了一些大神,发现这是大家都有的问题,但他们都选择相信CV而不是LB,理由是public LB的测试集仅占总测试集的11%,而且他们猜测每一次地震都各有特点,public LB的测试集可能只包含了某一次地震,不具有代表性,因此绝大多数人都认为排行榜最后会有一个比较大的shakeup。
       但我是不信的,当时的我反倒觉得,训练集可能跟测试集不是同分布的,这才导致了CV跟LB有很大的差别,而我们应该要通过LB probing去探寻测试集的真正情况。就是这个与各位大神背道而驰的决策,一度让我在public LB中闯进前5,又一夜之间在private LB中掉了1800+名。
       坚定了要LB probing之后,我就真正开始了繁琐且乏味的调参之路,包括叶子数、最大深度、最小节点样本等等,都在我的调参范围内。某天,我把LGBM的’objective’从’regression’换成了’gamma’,一下子就从60~80名冲到了第6名(LB MAE: 1.289),之后又进行了一些小改动,冲到了第5名(LB MAE: 1.288)。这时候的我已经完全忽视CV分数了,尽管当时的模型CV看起来已经比调参之前的CV差了很多,但我还是坚定的选择了1.288模型作为其中一个提交文件。到了最后一周,我又重新采样了两套数据集,按相同的参数训练了两个模型,再blending一下,得到了另外一个提交文件。
       在比赛结束的那个晚上,我从最终的13名掉到了1878名,神奇的是,如果我选择了CV最好的模型提交,它的最终成绩足以让我们拿到金牌。
这恐怖的shakeup

比赛总结

       这场比赛算是个新手教训,告诉我们最终两个Use for Final Score的选择一定要慎之又慎,最好是一个CV最优,一个LB最优,这样会稳很多。

你可能感兴趣的:(Kaggle比赛之路)