返回列表

12th solution: single model wavenet + lstm

378. University of Liverpool - Ion Switching | liverpool-ion-switching

开始: 2020-02-24 结束: 2020-05-25 药物研发 数据算法赛
第12名方案:单模型 WaveNet + LSTM

第12名方案:单模型 WaveNet + LSTM

作者: Jie Lu | 比赛排名: 12th

首先非常感谢我的队友和这个伟大的社区,感谢所有坚持分享的人,我们在这次比赛中学到了很多。(在这次比赛之前,我甚至没听说过 WaveNet,也没想过用 CNN 来解决时间序列问题)

昨天凌晨 2 点私有排行榜揭晓,之后我太兴奋了。今天早上我只睡了 2 个小时,尽管今天还要工作,这是为了我们要一个月的辛勤工作,最终我获得了我的第一枚金牌。

一些想法

  • 我们主要是在那个著名的 0.944 分的 WaveNet RFC kernel 基础上做额外的工作。
  • 我们发现分组大小为 4000 的 groupKFold 交叉验证(CV)与公共排行榜是一致的,但并非完全一致。后来我们发现测试集中类别 10 的数量非常有参考价值,我们只在测试集类别 10 的数量超过 7000(理想情况下约为 7020)时才提交。通过这样做,我们的 CV 策略变得相当一致,这使我们能够在本地安全地进行各种实验。不同的 batch size 或 group size 没有帮助。
  • 我们最后的两次提交是这一个(CV 分数最高)和三次提交的投票(LB 分数最高),这个提交最终在私有排行榜上也是最高的,而投票的分数则剧烈下降。这反过来证明了我们的 CV 策略效果很好。
  • 翻转有帮助,但添加高斯噪声没有,我们没有尝试测试时的增强。
  • 我们没有花时间在 HMM 建模上。
  • 我们最好的 LGB 模型在公共 LB 上是 0.941,正如有人在帖子中提到 RFC 因为不同的 CV 分割策略而泄漏,我们尝试用同样的 groupKFold 替换为 LGB/RFC/LSTM,但没成功。

预处理

  • 我们使用了漂移清洗和卡尔曼滤波后的信号。
  • 我们从 3,640,000 到 3,840,000 的行中删除了 200,000 行,以去除尖峰噪声。这对公共 LB 分数没有影响。

特征

我们尝试了很多特征,但很少有效,然后我们决定坚持调整模型结构,希望它能自己找到有用的特征。
尽管如此,除了 RFC 概率外,一些特征还是有效的,虽然没有带来很大的提升。
所以我们在这个模型中使用了这些特征:

  • RFC 概率
  • signal ** 2(信号的平方)
  • 滞后范围 3 和 -11, -15: [-15, -11, -3, -2, -1, 1, 2, 3]

所有特征都是在标准缩放信号后创建的。

模型结构

class Wave_Block(nn.Module):

    def __init__(self, in_channels, out_channels, dilation_rates, kernel_size):
        super(Wave_Block, self).__init__()
        self.num_rates = dilation_rates
        self.convs = nn.ModuleList()
        self.filter_convs = nn.ModuleList()
        self.gate_convs = nn.ModuleList()

        self.convs.append(nn.Conv1d(in_channels, out_channels, kernel_size=1))
        dilation_rates = [2 ** i for i in range(dilation_rates)]
        for dilation_rate in dilation_rates:
            self.filter_convs.append(
                nn.Conv1d(out_channels, out_channels, kernel_size=kernel_size, padding=int((dilation_rate*(kernel_size-1))/2), dilation=dilation_rate, padding_mode='replicate'))
            self.gate_convs.append(
                nn.Conv1d(out_channels, out_channels, kernel_size=kernel_size, padding=int((dilation_rate*(kernel_size-1))/2), dilation=dilation_rate, padding_mode='replicate'))
            self.convs.append(nn.Conv1d(out_channels, out_channels, kernel_size=1))

    def forward(self, x):
        x = self.convs[0](x)
        res = x
        for i in range(self.num_rates):
            x = torch.tanh(self.filter_convs[i](x)) * torch.sigmoid(self.gate_convs[i](x))
            x = self.convs[i + 1](x)
            res = res + x
        return res
class SEModule(nn.Module):

    def __init__(self, in_channels, reduction=2):
        super(SEModule, self).__init__()
        self.conv = nn.Conv1d(in_channels, in_channels, kernel_size=1, padding=0)
        
    def forward(self, x):
        s = F.adaptive_avg_pool1d(x, 1)
        s = self.conv(s)
        x *= torch.sigmoid(s)
        return x
class Classifier(nn.Module):
    
    def __init__(self, inch=8, kernel_size=3):
        super().__init__()
        dropout_rate = 0.1
        
        self.conv1d_1 = nn.Conv1d(inch, 32, kernel_size=1, stride=1, dilation=1, padding=0, padding_mode='replicate')
        self.batch_norm_conv_1 = nn.BatchNorm1d(32)
        self.dropout_conv_1 = nn.Dropout(dropout_rate)
        
        self.conv1d_2 = nn.Conv1d(inch+16+32+64+128, 32, kernel_size=1, stride=1, dilation=1, padding=0, padding_mode='replicate')
        self.batch_norm_conv_2 = nn.BatchNorm1d(32)
        self.dropout_conv_2 = nn.Dropout(dropout_rate)
        
        self.wave_block1 = Wave_Block(32, 16, 12, kernel_size)
        self.wave_block2 = Wave_Block(inch+16, 32, 8, kernel_size)
        self.wave_block3 = Wave_Block(inch+16+32, 64, 4, kernel_size)
        self.wave_block4 = Wave_Block(inch+16+32+64,