555. Parkinsons Freezing of Gait Prediction | tlvmc-parkinsons-freezing-gait-prediction
首先,感谢竞赛主办方提供了数据有趣的 meaningful 竞赛。其次,祝贺获奖者!我们团队经历了一些波动,最终以一名之差与获奖区失之交臂。总的来说,我仍然感到高兴,因为我们成功度过了波动期。我也很期待看到前5名团队是如何创造出与我们之间的显著差距的。
我们的最终集成方案结合了频谱图模型、小波模型和1D卷积模型,得分0.369/0.462。下面我将讨论这些模型以及其他重要的技术细节。概览图如下:
由于数据噪声较大,验证设置非常重要。我最终采用了嵌套CV(交叉验证)设置。具体流程如下:
通过这种设置,我们可以更准确地模拟拥有250个测试序列并平均各折模型预测的情况。之后,我改为在完整的内折数据上训练4次(无验证),并使用最后epoch的模型在外折集上评估。
我们所有模型都使用3个波形波和pct_time特征。此外,Ahmet的模型还使用了一些元数据特征。
当我第一次看到数据时,觉得它看起来像某种波形,比如音频数据,因此认为使用频谱图建模可能效果不错。三维波形波通过STFT(短时傅里叶变换)转换为2D频谱图。重要的是,转换为频谱图会显著降低时间维度的数据量,由于我使用64/50的hop length,每个帧代表0.5秒窗口,基本上我是在对0.5秒窗口进行预测。训练期间,标签使用torchvision的resize功能调整大小以匹配频谱图的时间维度,推理时再将模型输出调整回完整维度。序列被切割为128秒(256个频谱图帧)的块来生成频谱图。
使用频谱图的另一个重要问题是,如果使用常规的2D卷积模型(如resnet18),无法保留频谱图的完整维度(例如256x256经过resnet18后变为8x8)。为了解决这个问题,我考虑使用UNet在卷积网络后上采样小特征图。之后,频谱图沿频率维度池化,得到1D序列,然后输入Transformer网络输出预测。
最佳单频谱图模型提交得分为0.432/0.372。频谱图模型擅长预测StartHesitation和Turn,但在Walking上表现较差。
小波与频谱图类似,但也有不同之处:小波在不同频率下具有不同的频率/时间分辨率。将波形转换为小波也不会降低尺度图(scaleogram)的维度(我认为这是小波变换后图像的术语)。由于时间维度没有下采样,不再需要UNet,我直接使用resnet18/34,它们将尺度图下采样至与频谱图模型经过UNet后相同的时间分辨率。同样,我也在对0.5秒窗口进行分类。类似地,序列被切割为128秒(256帧)的块来生成小波。
最佳单小波模型提交得分为0.386/0.345。小波模型擅长预测Walking,但在StartHesitation和Turn上表现较差,因此与频谱图模型形成很好的互补。
直接插入Transformer实际上效果不佳,因为它容易过拟合,因此我使用一个浮点偏置掩码来偏置Transformer的自注意力机制,使每个预测都依赖于相邻时间点,这有助于模型更好地预测长事件。
浮点偏置掩码如下所示:
def get_distance_mask(L, power):
m = torch.zeros((L, L))
for i in range(L):
for j in range(L):
if i != j:
m[i, j] = (1 - abs(i - j) / L) ** 2
if i == j:
m[i, i] = 1.0
return m
我对频谱图/小波模型使用了以下数据增强(主要来自audiomentations):
self.augment = Compose([
AddGaussianNoise(min_amplitude=0.001, max_amplitude=0.015, p=0.5),
TimeStretch(min_rate=0.8, max_rate=1.25, p=0.5, leave_length_unchanged=False, n_fft=n_fft, hop_length=hop_length),
PitchShift(min_semitones=-4, max_semitones=4, p=0.5, n_fft=n_fft, hop_length=hop_length),
])
# 波形缩放增强
if np.random.uniform() > 0.5:
data['wave'] *= np.random.uniform(0.75, 1.5)
# 时间特征偏移增强
if self.train and np.random.uniform() > 0.5:
data['time'] = data['time'] + np.random.uniform(-0.1, 0.1)
不使用傅里叶变换的高频部分非常重要,因此我直接舍弃它们,只保留前64个bin(对应0-15 Hz)。对于频谱图模型,我还使用torch.linspace(0,15,n_bins)对频率bin进行编码,并在时间和通道维度上扩展,然后拼接,因此2D卷积网络的输入有4个通道(3个方向的频谱图+频率编码)。将波形重采样至较低频率也很有用,我认为这能降低噪声水平。频谱图模型使用32、64和128 Hz,小波模型使用64 Hz。defog波形被重采样以匹配tdcsfog波形的采样率。
if self.df.loc[idx, 'data_type'] == 'defog':
data['wave'] = FA.resample(data['wave'], 100, self.sample_rate)
else:
data['wave'] = FA.resample(data['wave'], 128, self.sample_rate)
1D卷积模型是Ahmet的解决方案。详情如下:
1D卷积模型相比其他两种较弱,得分为0.373/0.293,但仍然是集成的好补充。有趣的是,1D卷积模型和频谱图模型在公开和私有排行榜之间的差距相似,均为0.09,而小波模型的差距仅为0.04。我们认为这是由于公开/私有排行榜之间类别平衡的变化:公开排行榜有更多start hesitation,私有排行榜有更多walking。
对于我们的集成,权重首先手动调整作为起点,然后使用GP_minimize来最大化CV分数。我们最终使用了两种权重调整方案:
1. 4折的map + 排除Subject 2d57c2的完整数据map
2. 排除包含Subject 2d57c2的3折的map + 排除Subject 2d57c2的完整数据map
我们这样做是因为我们认为Subject 2d57c2是一个异常值。
results = gp_minimize(get_score, boundaries, x0=w, verbose=1, n_jobs=48, acq_optimizer='lbfgs', random_state=0)
我们的模型权重如下(我们在其中一些模型中将2d57c2的损失权重降至0.2):
如有疑问请随时询问,如果有些细节忘记提及也请不要惊讶。代码目前有些混乱,但我很快会清理并发布。