首先,我要感谢乌克兰武装部队、乌克兰安全局、乌克兰国防情报局和乌克兰国家紧急服务局,感谢他们为参与这场伟大的竞赛、完成这项工作提供了安全保障,并帮助科技和商业不停滞、持续前进。
TLDR:使用基于线性层嵌入的Transformer模型处理姿态、嘴唇、眼睛,以及用于目标手的STGCN + 线性层。
预处理
在时间维度上通过NaN值找到目标手,必要时进行翻转,选择目标手不为NaN的姿态,在时间维度上调整至16帧,最后进行归一化:
xyz = (xyz - np.nanmean(xyz[:,FACE,:],axis=(0,1))) / np.nanstd(xyz[:,FACE,:],axis=(0,1))
建模
(所有验证分数均基于 @hengck23 按参与者ID划分的数据)在卡在0.75 lb(约0.7验证分数)之后,我意识到需要改变策略。我回到仅对目标手进行建模的方式。在使用面部找到最佳归一化策略后,仅用手部数据我获得了约0.69的验证分数。接着我尝试加入姿态、嘴唇和眼睛,验证分数提升至约0.71。因此我很清楚,大部分信息都来自惯用手,于是尝试获取不同的表示方法并进行集成。首先我尝试绘制目标手,然后运行2.5D CNN或基于Transformer CNN的模型,仅用手部数据获得了约0.6的验证分数。随后我尝试对目标手运行STGCN(时空图卷积网络),获得了约0.66的分数。由于CNN处理时间过长,我决定放弃它。最终,在将姿态、嘴唇、眼睛、手部以及STGCN(手部)通过注意力块集成后,我获得了约0.72的分数。
x = torch.cat([
self.pose_emb(x[:,:,:8,:].view(B,L,-1), x_mask)[0],
self.lip_emb(x[:,:,8:20+8,:].view(B,L,-1), x_mask)[0],
self.reye_emb(x[:, :, 20+8 :20+8+16, :].view(B, L, -1), x_mask)[0],
self.leye_emb(x[:, :, 20+8+16 :20+8+16+16, :].view(B, L, -1), x_mask)[0],
self.hand_emb(x[:, :, 20+8+16+16 :20+8+16+16+21, :].view(B, L, -1), x_mask)[0],
self.graph_emb(x[:, :,20+8+16+16:20+8+16+16+21, :].permute(0, 3, 1, 2).unsqueeze(-1)),
self.pos_embed[:L, :].unsqueeze(0).repeat(B,1,1)],axis=-1)
以下是我进行集成的示例。
随后我开始加入不同的数据增强方法(这里我本应投入更多时间来进一步提升分数),我获得的最大提升来自:
- Mixup
if np.random.random() < self.args.mixup:
indices = torch.randperm(batch["xyz"].size(0), device=batch["xyz"].device, dtype=torch.long)
beta = np.random.beta(0.2, 0.2)
batch["xyz"] = beta * batch["xyz"] + (1 - beta) * batch["xyz"][indices]
batch = self.run_logits(batch)
batch["loss"] = beta * self.args.loss(logits=batch["logits"], labels=batch["labels"]) + (
1 - beta) * self.args.loss(logits=batch["logits"],
labels=batch["labels"][indices])
- Copy paste
if np.random.random() < self.args.cutout:
indices_to_shuffle = torch.randperm(batch["xyz"].size(0), device=batch["xyz"].device, dtype=torch.long)
cutout_len = self.args.cutout_len
if type(cutout_len) == list:
cutout_len = random.uniform(*cutout_len)
sz = int(batch["xyz"].size(1) * cutout_len)
ind = np.random.randint(0, batch["xyz"].size(1) - sz)
indices_to_cut = torch.range(ind, ind+sz, device=batch["xyz"].device, dtype=torch.long)
beta = 1 - cutout_len
batch["xyz"][:, indices_to_cut] = batch["xyz"][indices_to_shuffle][:,indices_to_cut]
batch["mask"][:, indices_to_cut] = batch["mask"][indices_to_shuffle][:,indices_to_cut]
batch = self.run_logits(batch)
batch["loss"] = beta * self.args.loss(logits=batch["logits"], labels=batch["labels"]) + (
1 - beta) * self.args.loss(logits=batch["logits"],
labels=batch["labels"][indices_to_shuffle])
在此之后,我得到了最终分数:验证分数约为0.735-0.74,单模型在公开排行榜上的得分为0.77,集成模型为0.79。
未生效的方法:
- 对惯用手使用CNN,未带来明显提升。
- 基于词嵌入的不同损失函数。
- 有趣的想法:使用ArcFace标签(参与者ID+手势,接近5230个标签)训练模型,基于标签的验证划分使验证分数提升了约0.02,因为排行榜中也包含来自训练集的样本。我尝试将其加入集成,但未获得显著提升,因此决定放弃。
再次感谢所有在艰难时期支持乌克兰的人们。
荣耀归于乌克兰。