我们基于公开笔记使用了六个一维卷积模型和两种版本的Transformer的集成。关键在于数据预处理、强化增强和集成。
最初,我尝试基于上述公开Transformer开发自己的解决方案,但后来发现模型架构的重要性不如正确的数据处理和集成工作,因此我转向了更简单的架构,如多层一维卷积模型。我使用了Keras,并利用原生tflite层(如DepthwiseConv1D)的所有优势。我的典型模型如下所示:
do = 0.5
model = Sequential()
model.add(InputLayer(input_shape=(max_len, 61, 2)))
model.add(Reshape((max_len, 61*2)))
model.add(Conv1D(64, 1, strides=1, padding='valid', activation='relu'))
model.add(BatchNormalization())
model.add(DepthwiseConv1D(3, strides=1, padding='valid', depth_multiplier=1, activation='relu'))
model.add(BatchNormalization())
model.add(Conv1D(64, 1, strides=1, padding='valid', activation='relu'))
model.add(BatchNormalization())
model.add(DepthwiseConv1D(5, strides=2, padding='valid', depth_multiplier=4, activation='relu'))
model.add(BatchNormalization())
model.add(MaxPool1D(2, 2))
model.add(Conv1D(256, 1, strides=1, padding='valid', activation='relu'))
model.add(BatchNormalization())
model.add(DepthwiseConv1D(3, strides=1, padding='valid', depth_multiplier=1, activation='relu'))
model.add(BatchNormalization())
model.add(Conv1D(256, 1, strides=1, padding='valid', activation='relu'))
model.add(BatchNormalization())
model.add(DepthwiseConv1D(5, strides=2, padding='valid', depth_multiplier=4, activation='relu'))
model.add(BatchNormalization())
model.add(GlobalAvgPool1D())
model.add(Dropout(rate=do))
model.add(Dense(1024, activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(rate=do))
model.add(Dense(1024, activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(rate=do))
model.add(Dense(250, activation='softmax'))
后来,我的两位同事加入了我。我们有很多想法,例如使用合成数据。我们找到了这篇论文,编写了一个脚本,该脚本通过内部参数(关节旋转)和相机模型生成手的合成3D点,并尝试基于3D点使用模型预测手的内部参数。然后使用这个预训练模型来预处理数据。但遗憾的是,我们没有足够的时间完成它。
我们还尝试在额外数据(WLASL)上进行训练,发现该数据集使本地交叉验证分数提高了约0.005-0.007,但后来我们放弃了这个想法,在LB提交中未使用额外数据。
归一化
我们使用这些参考点进行归一化:ref = coords[:, [500, 501, 512, 513, 159, 386, 13,]]。
我们没有使用深度维度。
我们没有丢弃没有手的帧,但在TTA(测试时增强)中考虑了手的存在。
增强
- 对每一帧全局应用随机旋转、平移和缩放
- 为每个点添加随机小偏移
- 部分组合——例如,我们从一个样本中取手,从另一个具有相同标签的样本中取嘴唇。这种增强显著提高了分数
- CutMix——从不同类别的样本中取部分。对于手,我们使用0.7作为标签值,其他部分使用0.3
TTA(测试时增强)
- 对短序列进行随机左右填充
- 对于长帧序列,我们根据帧中手的存在情况以不同概率丢弃帧
对于不同的模型,我们使用了不同的点组合:双手/仅活动手、嘴唇、眼睛、姿态的顶部。对于一些模型,我们只使用了嘴唇/眼睛的每隔一个点。 对于只使用一只手的模型,我们确定视频中哪只手出现得更多,如果是左手,我们就镜像所有点及其坐标。 除了一个模型使用96帧外,其余所有模型都只使用了32帧。
这些一维卷积模型快速且轻量,因此我们可以集成多达六个模型,并且仍有足够的空间和时间用于额外的模型和TTA。六个这样的模型的集成在公共LB上获得了0.7948的分数,在私有LB上获得了0.8711的分数。 然后我们决定与@sqqqqy合作,他一直在改进公开内核中的Transformer。他的解决方案如下:
1. Transformer模型
- 我们的Transformer模型基于公开笔记,使用不同的序列归一化和更小的UNITS尺寸以减少模型参数。
- 我们实现了两种基于Transformer的模型:第一种模型为每个部分学习独立的嵌入(与公开笔记相同),第二种模型使用整个xyz序列输入学习一个嵌入。
- 第一种模型在1个种子下的分数为LB 0.77+,第二种模型在1个种子下的分数为LB 0.768
2. 预处理
- 我们使用20个嘴唇点、32个眼睛点、42个手点(左手和右手)以及8个姿态点。
- 输入序列使用肩膀、臀部、嘴唇和眼睛点进行归一化。
- 将NaN值填充为0.0
- 通过输入序列$(d_x, d_y, \sqrt{(d_x)^2+(d_y)^2})_t$学习运动嵌入,其中 $$(d_x, d_y)_t = xyz_t - xyz_{t-1}$$
- 最终的嵌入是运动嵌入和xyz嵌入的拼接
3. 增强
全局增强(对所有帧应用相同的增强),包括旋转(-10,10)、平移(-0.1,0.1)、缩放(0.8,1.2)、剪切(-1.0,1.0)、翻转(适用于某些手势)
基于时间的增强(对某些帧应用增强),随机选择1-8帧进行仿射增强,随机丢弃帧(用0.0填充)
这些增强显著提高了分数
4. 超参数
- NUM_BLOCKS: 2
- NUM_HEAD: 8
- 学习率: 1e-3
- 优化器: AdamW
- 训练轮数: 100
- 后期Dropout: 0.2-0.3
- 标签平滑: 0.5
六个带有TTA的一维卷积模型与两个Transformer结合,使我们在本次比赛中获得了第三名。