返回列表

2nd Place Solution | Google - Isolated Sign Language Recognition

546. Google - Isolated Sign Language Recognition | asl-signs

开始: 2023-02-23 结束: 2023-05-01 音视频处理 数据算法赛
第二名解决方案 | Google - 孤立手语识别竞赛

第二名解决方案 | Google - 孤立手语识别竞赛

作者: Kolya Forrat (Kaggle Master)
发布日期: 2023年5月2日
最后更新: 2023年7月26日
得票数: 116票

TLDR(要点概述)

我们采用了类似音频频谱图分类的方法,使用EfficientNet-B0模型,配合多种数据增强和BERT、DeBERTa等Transformer模型作为辅助模型。最终解决方案包括:

  • 一个输入尺寸为160x80的EfficientNet-B0模型,在8个随机划分折中的单折上训练
  • 在完整数据集上训练的DeBERTa和BERT模型

单折EfficientNet模型的交叉验证得分为0.898,排行榜得分约0.8。我们仅使用了比赛提供的数据。

1. 数据预处理

1.1 CNN预处理

  • 提取18个嘴唇关键点、20个姿态关键点(包括手臂、肩膀、眉毛和鼻子)以及所有手部关键点,共80个点
  • 训练期间应用多种数据增强
  • 实施标准化处理
  • NaN值不删除,而是在标准化后用零填充
  • 使用'最近邻'插值法将时间轴插值到160的尺寸:yy = F.interpolate(yy[None, None, :], size=self.new_size, mode='nearest')
  • 最终获得维度为160x80x3的张量,其中3代表(X, Y, Z)坐标轴

预处理示意图

1.2 Transformer预处理

  • 仅保留61个关键点,包括40个嘴唇点和21个手部点。左右手保留NaN值较少的一侧,若保留右手则将其镜像到左手
  • 依次应用数据增强、标准化和NaN填充
  • 长度超过96的序列插值到96,短于96的序列保持不变
  • 除原始位置外,还使用手工特征,包括运动、距离和角度余弦
  • 运动特征包含未来运动和运动历史:
    $$ Motion_{future} = position_{t+1} - position_{t} $$
    $$ Motion_{history} = position_{t} - position_{t-1} $$
  • 包含21个手部点之间的全部210对距离
  • 手指有5个顶点(如拇指为[0,1,2,3,4]),产生3个角度:<0,1,2>, <1,2,3>, <2,3,4>,共15个手指角度
  • 随机选择40个嘴唇点之间的190对距离和8个角度

2. 数据增强

2.1 通用增强

这些增强同时用于CNN和Transformer训练:

  1. 随机仿射:与@hengck23分享的方法相同。在CNN中,全局仿射后还会对各部分单独进行位移-缩放-旋转(如手、嘴唇、身体姿态)
  2. 随机插值:轻微缩放和位移时间维度
  3. 姿态翻转:翻转所有点的x坐标。CNN中使用x_new = x_max - x_old,Transformer中使用x_new = 2 * frame[:,0,0] - x_old
  4. 手指树旋转:5顶点手指有4组父子对(如拇指[0,1,2,3,4]的0-[1,2,3,4]、1-[2,3,4]、2-[3,4]、3-[4])。随机选择部分对,围绕父点以随机小角度旋转子点

2.2 CNN专用增强

  • Mixup:实现基础mixup增强(仅适用于CNN,不适用于Transformer)
  • 替换增强:从同类别其他样本中替换随机部分
  • 时间和频率掩码:基础的torchaudio增强效果异常出色
freq_m = torchaudio.transforms.FrequencyMasking(80)  # 时间轴
time_m = torchaudio.transforms.TimeMasking(18)       # 关键点轴

2.3 增强样例

增强前:

aug1

增强后:

aug2

3. 训练过程

3.1 CNN训练

  • 使用最佳参数在随机划分(共8折)的单折或完整数据集上训练
  • 采用0.1预热比例的OneCycle学习率调度器
  • 使用加权CrossEntropyLoss,增加对预测较差类别和语义相似类别(如kitty和cat)的权重
  • 为EfficientNet实现包含5个块的hypercolumn结构

3.2 Transformer训练

  • 使用最佳参数在随机划分(共8折)的单折或完整数据集上训练
  • 使用Ranger优化器,60%平直期+40%余弦退火的学习率调度
  • 训练4层、256隐藏尺寸、512中间尺寸的Transformer
  • 3层模型初始化使用4层模型的前3层权重,在训练中使用知识蒸馏,4层模型作为教师

3.3 超参数调优

由于只训练单折且使用较小模型,我们使用Optuna调整了大多数参数。CNN训练参数列表如下(Transformer类似):

  • 所有增强概率(0.1 - 0.5+)
  • 学习率(2e-3 - 3e-3)
  • Dropout率(0.1 - 0.25)
  • 训练轮数(170-185)
  • 损失权重指数(0.75 - 2)
  • 优化器(Lookahead_RAdamRAdam
  • 标签平滑(0.5 - 0.7)

4. 提交、转换与集成

  1. 我们将所有模型用Keras重写并迁移PyTorch权重,速度提升约30%。对于Transformer模型,pytorch-onnx-tf-tflite会产生过多无用的张量形状操作,完全重写可手动减少这些操作。对于CNN模型,我们用硬编码方式重写DepthwiseConv2D,速度达到原tflite版本的200%~300%
  2. 之后将所有模型聚合在tf.Module类中。直接从Keras转换会导致速度下降(原因不明)
  3. 我们根据第0折的本地得分计算各模型的集成权重,并将这些权重应用于完整数据集训练的模型

EfficientNet-B0单模型排行榜得分约0.8,Transformer模型提升至0.81。最终集成包含:

  1. Efficientnet-B0(第0折)
  2. BERT(完整数据训练)
  3. DeBERTa(完整数据训练)

有趣的是,不使用softmax的集成方式持续带来约0.01的提升。

5. 附注:需要更好的TFlite DepthwiseConv2D实现

深度可分离卷积模型在这些任务上表现优异,超越其他CNN和ViT模型(rexnet_100也表现良好)。我们在DepthwiseConv2D转换上花费了大量时间,得到一些奇怪的结果:

对于82x42x32(HWC)的输入,Keras中有两种实现3x3深度可分离卷积的方式:Conv2D(32, 3, groups=32)DepthwiseConv2D(3)。转换为tflite后,Conv2D运行时间为5.05ms,DepthwiseConv2D为3.70ms。更奇怪的是,FLOPs=HWC²的普通卷积Conv2D(32, 3, groups=1)仅需2.09ms,比前述两种FLOPs=HWC的更快。

我们重写depthwise-conv如下:

def call(self, x):
    out = x[:,0:self.H_out:self.strides,0:self.W_out:self.strides] * self.weight[0,0]
    for i in range(self.kernel_size):
        for j in range(self.kernel_size):
            if i == 0 and j == 0:
                continue
            out += x[:,i:self.H_out + i:self.strides,j:self.W_out + j:self.strides] * self.weight[i,j]
    if self.bias is not None:
        out = out + self.bias
    return out

运行时间为1.24ms。

总结:我们版本(1.24ms) > 大FLOPs的普通Conv2D(2.09ms) > DepthwiseConv2D(3.70ms) > Conv2D(C, groups=C)(5.05ms)。

然而,我们的版本在tflite图中引入了过多节点,导致运行时间不稳定。如果TensorFlow团队能优化DepthwiseConv2D实现,我们甚至可以集成两个CNN模型,预计可达到0.82的排行榜得分。

另外,EfficientNet通过ONNX比TFLite快约5倍。

衷心感谢队友@artemtprv和@carnozhao,祝贺他们获得Master和GrandMaster称号!

同比赛其他方案