文章目录
Abstract
关注两个问题:边界模糊与过度分割。
ETSN的第一步是Efficient Temporal Series Pyramid Networks(ETSPNet),能够捕捉局部和全局的逐帧特征并且提供精确的边界预测。
第二步是一种无监督的方法,称之为Local Burr Suppression(LBS),很大程度减少了过度分割的错误。
Introduction
第一个问题是边界的错误。这是因为标签变化是突然的,但是动作的转变是渐进的。
当前比较流行的解决方案是以提升感受野来建模(MS-TCN),以此来识别动作边界周围难以识别的帧。
MSTCN的缺陷在于高层中:1. 局部信息的损失;2. 长距离依赖关系的缺失。另外,只依赖基于长距离依赖的特征的话更会在边界周围的模糊帧上输出低置信度甚至错误的预测。
第二个问题是过度分割错误。当前的模型往往会给出错误的片段,由于视频中的噪声。
解决毛刺的方式目前有两种,分别是:平滑损失函数和全局平滑网络。
ETSPNet使用了基于膨胀卷积的时间序列金字塔实现感受野的扩大,以此提高模糊帧的置信度。
我们认为毛刺只来源于局部的过度分割,所以提出了称之为LBS的后处理网络来定位和移除毛刺。
Technical approach
ETSPNet
只建立长距离的依赖导致细粒度信息的丢失以及预测错误边界。
在图像分割领域,采用金字塔的结构在识别图像分割边界。本文进行了方法的移植,在时间序列上使用金字塔结构。使得ETSPNet能捕获更精确的时间序列分割边界(说白了,就是在边界附近的逐帧准确率高了)。
第一层是一个1x1卷积来适应维度的变化,该层的输出是网络的起始特征。
多层平行的卷积,膨胀因子不一样,卷积滤波器以及核大小是一样的。
之后再跟着激活函数ReLU以及1x1卷积(用于和起始特征保持一样的维度)。
为了精修初步的预测,我们使用了多阶段时间卷积网络的结构,但是每一阶段都采用了ETSPNet。
ETSPNet包含了多尺度的特征并且保留了输入序列的时间分辨率。
长期依赖区分相邻片段的类别,短期依赖识别边界。
class Single_ETSPNet(nn.Module):
def __init__(self, in_channel, n_features, n_classes, n_layers):
super().__init__()
self.conv_in = nn.Conv1d(in_channel, n_features, 1)
layers = [
DilatedResidualLayer(2**i, n_features, n_features) for i in range(n_layers)]
self.layers = nn.ModuleList(layers)
self.act = nn.PReLU(n_features*n_layers) # 针对连接后的特征维度的激活函数
self.conv1_1 = nn.Conv1d(n_features*n_layers, n_features, 1) # 连接后的特征维度 —> 初始特征维度
self.conv_out = nn.Conv1d(n_features, n_classes, 1) # 特征 -> 类别
def forward(self, x):
out = []
# 初始特征
in_feat = self.conv_in(x)
# 如果out非空,那把左层的输出加给当前层的输入
for layer in self.layers:
out.append(layer(in_feat + out[-1] if len(out) != 0 else in_feat))
# 连接并激活
out_cat = self.act(torch.cat(out, dim=1))
# 降维
output = self.conv1_1(out_cat)
# 输入与输出连接后输出分类结果
output = self.conv_out(output + in_feat)
return output
因为只使用四层卷积的话,对于一些困难帧会发生欠拟合的问题,所以ETSPNet将左层的输出加到了当前层的输入当中。
关于层内结构,用的还是MSTCN 的膨胀残差模块。
class DilatedResidualLayer(nn.Module):
# Originally written by yabufarha
# https://github.com/yabufarha/ms-tcn/blob/master/model.py
def __init__(self, dilation, in_channel, out_channels):
super().__init__()
self.conv_dilated = nn.Conv1d(
in_channel, out_channels, 3, padding=dilation, dilation=dilation)
self.conv_in = nn.Conv1d(out_channels, out_channels, 1)
self.dropout = nn.Dropout()
def forward(self, x):
out = F.relu(self.conv_dilated(x))
out = self.conv_in(out)
out = self.dropout(out)
return x + out
ETSPNet之后是多层SSTCN,这也是依照的MSTCN++中提出的精修模块添加的,起到了平滑的作用。
class MultiStageTCN(nn.Module):
def __init__(self, in_channel, n_classes, stages,
n_features=64, dilated_n_layers=10, kernel_size=15):
super().__init__()
if stages[0] == 'dilated':
self.stage1 = SingleStageTCN(in_channel, n_features, n_classes, dilated_n_layers)
elif stages[0] == 'etspnet':
self.stage1 = Single_ETSPNet(in_channel, n_features, n_classes, dilated_n_layers)
elif stages[0] == 'ed':
self.stage1 = EDTCN(in_channel, n_classes)
else:
print("Invalid values as stages in Mixed Multi Stage TCN")
sys.exit(1)
if len(stages) == 1:
self.stages = None
else:
self.stages = []
for stage in stages[1:]:
if stage == 'dilated':
self.stages.append(SingleStageTCN(
n_classes, n_features, n_classes, dilated_n_layers))
elif stage == 'ed':
self.stages.append(
EDTCN(n_classes, n_classes, kernel_size=kernel_size))
else:
print("Invalid values as stages in Mixed Multi Stage TCN")
sys.exit(1)
self.stages = nn.ModuleList(self.stages)
def forward(self, x):
if self.training:
# # for training
outputs = []
out = self.stage1(x)
outputs.append(out)
if self.stages is not None:
for stage in self.stages:
out = stage(F.softmax(out, dim=1))
outputs.append(out)
return outputs
else:
# for evaluation
out = self.stage1(x)
if self.stages is not None:
for stage in self.stages:
out = stage(F.softmax(out, dim=1))
return out
Local Burr suppression
ETSPNet提升了这个分割的性能,但是没能够解决毛刺。
时间平滑损失函数(TMSE)人仍然会造成过度分割。BCN的LBP代价又太大了。
LBS的前提是认为毛刺的来源是过度分割。LBS的灵感来源是NMS。
LBS分为两个步骤:
- 根据长度和平均置信度定位毛刺;
- 根据毛刺类型消除毛刺。
Locating the burrs
首先先得到片段的长度n,过于短的片段直接消除。
将 n ≤ L n\le L n≤L的片段列为可疑毛刺。
再根据平均置信度P,从可疑毛刺中选出待处理的毛刺。因为真实片段的高置信度,让LBS无法对他们下手。
Removing the burrs
更加连续毛刺的数量,分为连续毛刺和孤立毛刺。
对于连续毛刺而言,我们首先认可他们之间的边界是成立的,因为信赖ETSPNet的边界预测能力。
于是,我们采用相邻的非毛刺片段来代替毛刺的类别。
对于奇数个连续的毛刺,忽视中间毛刺,将他们转化为偶数连续毛刺来处理。
对于孤立的毛刺,将其分为两个连续的毛刺(这个划分过程基于前后两个非毛刺片段的长度),然后执行偶数连续毛刺来处理。