ST-GCN、2s-AGCN:图卷积网络在骨骼动作识别中的应用(一)

一、内容概要

时空图卷积 = (时间 + 空间) * 图卷积 = 时间图卷积 + 空间图卷积
ST-GCN在图卷积GCN中引入时间卷积TCN,处理动作识别
2s-AGCN在ST-GCN的基础上提出双流2s和自适应A

二、图卷积网络GCN

卷积的目的本质是聚合领域信息,而图卷积是卷积向非欧几里得结构泛化得到的

总的来说,数据可以分为以下两类:
欧几里得结构
常见媒介包括图片、视频、语音
“排列整齐”:即具有较好的平移不变性,且各节点邻居节点数量相同
非欧几里得结构
“排列不整齐”:不具有平移不变性

由于非欧几里得结构不具有平移变换性,不能用卷积核去提取相同的信息。故人们引入图神经网络(此处“图”指离散数学中的图,非“图像”中的“图”),用邻接矩阵表示图的信息,进而通过处理邻接矩阵得出结果。

应用

交通网络、社交网络、动作识别(人体骨架)、引文网络(由论文、作者与其关系组成)、推荐系统(将项目与用户作为节点)等

三、时空图卷积网络ST-GCN

【人体骨架、动作识别】
论文:https://arxiv.org/abs/1801.07455
代码:https://github.com/yysijie/st-gcn
时间:2018

1. 背景

一般来说,可以从多种模态中识别人类行为,如外观、深度、光流、身体骨架。这些模态中,动态人体骨骼通常传达重要的信息,然而相较外观和光流却受到相对较少的关注
动态骨架模态
使用骨架进行动作识别的早期方法
简单地采用各个时间步处的关节坐标来形成特征向量
–>没有明确地利用关节之间的空间关系

(1)创新点

此前骨骼建模方法通常依赖于手工制作或遍历规则,导致表达能力有限、泛化困难
–>ST-GCN可自动从数据中学习空间、时间模式
【第一个将图卷积神经网络应用在基于骨架的动作识别任务】

GCN公式:
形式类比CNN公式
在这里插入图片描述
p为样本数据,f(p(x,h,w))指取出p(x,h,w)位置的数据,w为每一个通道的权重
【注意力机制】由于不同躯干对于人体动作的重要性不同,作者应用注意力机制,对不同躯干加权进行训练

(2)划分策略

同一个分区中的节点在卷积计算时,与之内积的权重向量时一样的。有几个分区就有几种权重向量
在这里插入图片描述

红色虚线圈内部分为D = 1卷积核的感受野
a. 单标签划分 Uni-labeling(图b)
邻域内所有节点都用相同标签(绿色)表示
b. 距离划分 Distance partitioning(图c)
两群:距离为0的根结点(绿色)、距离为1的其他邻近点(蓝色)
c. 空间构型划分 Spatial configuration partitioning(图d)
节点根据其到骨架重心的距离标记
paper中将中心点设置在脖子处(黑叉),距离脖子更近的节点称为向心点(蓝色),更远的节点称为离心点(黄色)
三群:根结点、向心点、离心点

2. 网络结构

在这里插入图片描述

第一部分:归一化 (-BN-)
由于关节在不同帧下关节位置变化较大,归一化利于算法收敛
第二部分:ST-GCN单元 (-ATT-GCN-TCN-)*n
交替使用GCN和TCN,对事件空间维度进行变换
ps. 时空图卷积包括时间图卷积和空间图卷积。GCN本意为图卷积,在该代码中GCN和TCN分别代表空间图卷积和时间图卷积。空间维度是关节特征,时间维度是关键帧数
第三部分:平均池化、全连接层(POOL、FC)

3. 输入输出

输入 (batch, channel, frame, joint, person)
输出 (batch, class, output frame, joint, person)

batch:批大小
channel:输入视频的通道数 = 3
frame:输出序列的长度/一个视频的帧数 = 300(paper规定)
joint:图节点的数量(coco为18个节点)
person:帧中的实例数 = 2(paper将人数限定在最大两个)

ps. 输出由哪个类置信度最高,判断人体动作
故ST-GCN将动作分为几类,取决于数据集

4. 数据集

(1) NTU RGB+D

动作由40个年龄从10岁到35岁的人完成
使用了三个不同角度的摄像机
a. 四种形式
RGB视频、深度图序列、3D骨骼数据、红外视频
b. 动作类型
60个种类的动作,共56880个样本。其中40类为日常行为动作,9类为与健康相关的动作,11类为双人相互动作

【日常行为动作】
“喝水,吃饭/零食,刷牙,梳头,掉落,捡起,扔,坐下,站起来(从坐姿),拍手,阅读,写作,撕纸,穿夹克,脱下夹克,穿鞋,脱鞋,戴眼镜,摘下眼镜,戴上帽子/帽子,摘下帽子/帽子,振作起来, 挥手,踢东西,把东西放在口袋里/从口袋里拿出东西,跳跃(一只脚跳),跳起来,打电话/接听电话,玩手机/平板电脑,在键盘上打字,用手指指着东西,自拍,检查时间(从手表),搓两只手,点头/鞠躬,摇头,擦脸, 敬礼,双手合十,双手交叉在前面(说停止)”
【健康相关动作】
“打喷嚏/咳嗽,踉跄,跌倒,触摸头部(头痛),触摸胸部(胃痛/心痛),触摸背部(背痛),触摸颈部(颈部疼痛),恶心或呕吐状况”
【双人相互动作】
“使用风扇(用手或纸)/感觉温暖,拳打/拍打他人,踢他人,推他人,拍拍他人的背, 指着对方,拥抱对方,给别人东西,摸别人的口袋,握手,走向对方,彼此分开”

c. 数据格式
第1行: 样本的帧数

skeleton_sequence['numFrame'] = int(f.readline())

第2行: 执行动作的人数

frame_info['numBody'] = int(f.readline())

第3行: 10个数据分别代表’bodyID’, ‘clipedEdges’, ‘handLeftConfidence’, ‘handLeftState’, ‘handRightConfidence’, ‘handRightState’, ‘isResticted’, ‘leanX’, ‘leanY’, ‘trackingState’

body_info_key = [
    'bodyID', 'clipedEdges', 'handLeftConfidence',
    'handLeftState', 'handRightConfidence', 'handRightState',
    'isResticted', 'leanX', 'leanY', 'trackingState'
]
body_info = {
    k: float(v)
    for k, v in zip(body_info_key, f.readline().split())
}

第4行: 关节点数

body_info['numJoint'] = int(f.readline())

第5-29行: 25个关节点的信息,每个关节点有12个数据,分别代表’x’, ‘y’, ‘z’, ‘depthX’, ‘depthY’, ‘colorX’, ‘colorY’,‘orientationW’, ‘orientationX’, ‘orientationY’, ‘orientationZ’, ‘trackingState’

for v in range(body_info['numJoint']):
    joint_info_key = [
        'x', 'y', 'z', 'depthX', 'depthY', 'colorX', 'colorY',
        'orientationW', 'orientationX', 'orientationY',
        'orientationZ', 'trackingState'
    ]
    joint_info = {
        k: float(v)
        for k, v in zip(joint_info_key, f.readline().split())
    }
    body_info['jointInfo'].append(joint_info)

(2) kinetics-skeleton

包含了从YouTube上检索到的大约30万个视频片段,每个片段持续约10秒。仅提供原始视频剪辑,不提供骨架数据

5. 部分代码

(1) 数据集读入

(见前文数据集部分)

(2) 网络结构

net\st_gcn.py
一共10层st_gcn层(单流),第一层没有残差结构residual,故又通常被称为9层

self.st_gcn_networks = nn.ModuleList((
     st_gcn(in_channels, 64, kernel_size, 1, residual=False, **kwargs0),
     st_gcn(64, 64, kernel_size, 1, **kwargs),
     st_gcn(64, 64, kernel_size, 1, **kwargs),
     st_gcn(64, 64, kernel_size, 1, **kwargs),
     st_gcn(64, 128, kernel_size, 2, **kwargs),
     st_gcn(128, 128, kernel_size, 1, **kwargs),
     st_gcn(128, 128, kernel_size, 1, **kwargs),
     st_gcn(128, 256, kernel_size, 2, **kwargs),
     st_gcn(256, 256, kernel_size, 1, **kwargs),
     st_gcn(256, 256, kernel_size, 1, **kwargs),
 ))

(3) GCN模块

net\utils\tgcn.py

def __init__(self, in_channels, out_channels, kernel_size,  t_kernel_size=1, t_stride=1, t_padding=0, t_dilation=1, bias=True):
   super().__init__()

   self.kernel_size = kernel_size
   # 该kernel_size指空间上的kernel size,为3,也等于区分策略划分的子集数k
   self.conv = nn.Conv2d(
       in_channels,
       out_channels * kernel_size,
       kernel_size=(t_kernel_size, 1),  # Conv(1, 1)
       padding=(t_padding, 0),
       stride=(t_stride, 1),
       dilation=(t_dilation, 1),
       bias=bias)
       
def forward(self, x, A):
    assert A.size(0) == self.kernel_size

    x = self.conv(x)
    # 输入x为(N,C,T,V),经过conv(x)之后变为(N,C*kernel_size,T,V)
    # 卷积核的真正大小为(t_kernel_size,1),t_kernel_size预设值为1

    n, kc, t, v = x.size()
    # 把kernel_size的维度拿出来,变成(N,K,C,T,V)
    x = x.view(n, self.kernel_size, kc//self.kernel_size, t, v)
    x = torch.einsum('nkctv,kvw->nctw', (x, A))  # 爱因斯坦约定求和法
    # A为(K,V,V)的邻接矩阵
    # 利用einsum()对A和x进行维度融合

    return x.contiguous(), A  # 输出变回(N*M,C,T,V)格式,进入TCN(时间卷积网络)

(4) TCN模块

net\st_gcn.py

# 让网络在时域上进行特征提取,类似LSTM
# 要整合不同时间上的节点特征,就是要对其进行卷积
self.tcn = nn.Sequential(
    nn.BatchNorm2d(out_channels),
    nn.ReLU(inplace=True),
    nn.Conv2d(
        out_channels,
        out_channels,
        (kernel_size[0], 1),
        (stride, 1),
        padding,
    ),
    # 利用(temporal_kernel_size, 1)的卷积核对t维度进行卷积运算
    nn.BatchNorm2d(out_channels),
    nn.Dropout(dropout, inplace=True),
)

(5) st-gcn单元

net\st_gcn.py

def __init__(self, in_channels, out_channels, kernel_size, stride=1, dropout=0, residual=True):
    super().__init__()

    assert len(kernel_size) == 2
    assert kernel_size[0] % 2 == 1
    padding = ((kernel_size[0] - 1) // 2, 0)

    # GCN模块
    self.gcn = ConvTemporalGraphical(in_channels, out_channels, kernel_size[1])

    # TCN模块
    # 让网络在时域上进行特征提取,类似LSTM
    # 要整合不同时间上的节点特征,就是要对其进行卷积
    self.tcn = ... # 详细参见前文TCN模块

    if not residual:
        self.residual = lambda x: 0

    elif (in_channels == out_channels) and (stride == 1):
        self.residual = lambda x: x

    else:
        self.residual = nn.Sequential(
            nn.Conv2d(
                in_channels,
                out_channels,
                kernel_size=1,
                stride=(stride, 1)),
            nn.BatchNorm2d(out_channels),
        )

    self.relu = nn.ReLU(inplace=True)

def forward(self, x, A):

    res = self.residual(x)
    x, A = self.gcn(x, A)
    x = self.tcn(x) + res

    return self.relu(x), A

(6) 图结构

net\utils\graph.py
通过连接节点,得到边,即骨架
根据layout为openpose还是ntu-rgb+d,决定用18个关键点还是25个关键点的骨架图

if layout == 'openpose':
    self.num_node = 18
    self_link = [(i, i) for i in range(self.num_node)]
    # neighbor_link描述关键点之间的连接情况
    # 该连接顺序与OpenPose提供的输出格式连接顺序不同,但对结果无影响
    neighbor_link = [(4, 3), (3, 2), (7, 6), (6, 5), (13, 12), (12, 11),
                     (10, 9), (9, 8), (11, 5), (8, 2), (5, 1), (2, 1),
                     (0, 1), (15, 0), (14, 0), (17, 15), (16, 14)]
    self.edge = self_link + neighbor_link
    self.center = 1  # 中心点为1号点,即脖子
elif layout == 'ntu-rgb+d':
    self.num_node = 25
    self_link = [(i, i) for i in range(self.num_node)]
    neighbor_1base = [(1, 2), (2, 21), (3, 21), (4, 3), (5, 21),
                      (6, 5), (7, 6), (8, 7), (9, 21), (10, 9),
                      (11, 10), (12, 11), (13, 1), (14, 13), (15, 14),
                      (16, 15), (17, 1), (18, 17), (19, 18), (20, 19),
                      (22, 23), (23, 8), (24, 25), (25, 12)]
    neighbor_link = [(i - 1, j - 1) for (i, j) in neighbor_1base]
    self.edge = self_link + neighbor_link
    self.center = 21 - 1
elif layout == 'ntu_edge':
    self.num_node = 24
    self_link = [(i, i) for i in range(self.num_node)]
    neighbor_1base = [(1, 2), (3, 2), (4, 3), (5, 2), (6, 5), (7, 6),
                      (8, 7), (9, 2), (10, 9), (11, 10), (12, 11),
                      (13, 1), (14, 13), (15, 14), (16, 15), (17, 1),
                      (18, 17), (19, 18), (20, 19), (21, 22), (22, 8),
                      (23, 24), (24, 12)]
    neighbor_link = [(i - 1, j - 1) for (i, j) in neighbor_1base]
    self.edge = self_link + neighbor_link
    self.center = 2

paper中的三种分区策略:

if strategy == 'uniform':
    A = np.zeros((1, self.num_node, self.num_node))
    A[0] = normalize_adjacency
    self.A = A
elif strategy == 'distance':
    A = np.zeros((len(valid_hop), self.num_node, self.num_node))
    for i, hop in enumerate(valid_hop):
        A[i][self.hop_dis == hop] = normalize_adjacency[self.hop_dis ==
                                                        hop]
    self.A = A
elif strategy == 'spatial':
    A = []
    for hop in valid_hop:
        a_root = np.zeros((self.num_node, self.num_node))
        a_close = np.zeros((self.num_node, self.num_node))
        a_further = np.zeros((self.num_node, self.num_node))
        for i in range(self.num_node):
            for j in range(self.num_node):
                if self.hop_dis[j, i] == hop:  # 若i和j是邻接节点
                    # 比较节点i和j分别到中心点的距离,center点为1号点(脖子)
                    if self.hop_dis[j, self.center] == self.hop_dis[
                            i, self.center]:
                        a_root[j, i] = normalize_adjacency[j, i]
                    elif self.hop_dis[j, self.
                                      center] > self.hop_dis[i, self.
                                                             center]:
                        a_close[j, i] = normalize_adjacency[j, i]
                    else:
                        a_further[j, i] = normalize_adjacency[j, i]
        if hop == 0:
            A.append(a_root)  # A的第一维第一个矩阵:自身节点组
        else:
            A.append(a_root + a_close)
            # A的第一维第二个矩阵:向心组矩阵(列对应节点到中心点的距离比行对应节点到中心点距离近或者相等)
            A.append(a_further)
            # A的第一维第三个矩阵:离心组矩阵(列对应节点到中心点的距离比行对应节点到中心点距离远)
    A = np.stack(A)
    self.A = A

四、双流自适应图卷积网络2s-AGCN

【人体骨架、动作识别】
论文:https://arxiv.org/abs/1805.07694
代码:https://github.com/lshiwjx/2s-AGCN
时间:2019

作者根据ST-GCN进行改进,以端到端的方式自适应地学习不同GCN层和骨架样本的图形拓扑
(与ST-GCN采用相同数据集)

1. ST-GCN问题

(1) 注意力机制灵活度较差
在这里插入图片描述
ps. 其中A决定两节点间是否有联系,M决定联系的强度
权重与邻接矩阵直接相乘(即按元素相乘非矩阵相乘)。若邻接矩阵中某元素为0,则无论权重的取值,结果均为0
(2)未利用骨骼的长度和方向
仅利用关节坐标

2. 创新点

【自适应】
神经网络中,自适应指能自动适应输入数据、任务或环境的不同方面。此模型中,其主要体现在以下方面:

  1. 利用自适应的方式构建图结构
    不是事先定义好的固定图结构,而是根据输入数据中的关系自动构建
  2. 使用自适应的权重机制
    允许模型根据数据特点动态学习和调整权重,以便更好地捕捉数据中的关系和特征(尤其是在处理多模态数据或多通道信息时)

(1)双流网络
在这里插入图片描述

a. 全局图(global graph)
表示所有数据的公共模式,用于描述骨骼(图中第一排)
b. 独立图(individual graph)
表示每个数据的独特模式,用于描述关节(图中第二排)
–>2s-AGCN对两种类型的图进行参数化,其结构与模型的卷积参数一起进行训练和更新
两种类型的图都针对不同层进行了单独优化,使可以更好地拟合模型的层次结构
(2) 矢量序列表示骨架数据
融入关节长度、方向,表示为源关节指向目标关节的向量。其中,靠近重心的关节看作源关节,远离重心的关节看作目标关节

【AGCN公式】
在这里插入图片描述
(1)A(与前式中A相同)
(2)B(与前式中M有相同作用)
可训练权重,能与其他参数一起被参数化和优化
完全从数据学习来的参数。不仅可以表示两个节点间有无联系,还可表示联系的强弱
ps. A与B采取相加的方式,使原不存在的联系可以产生
(3)C 依赖数据图
为每个样本生成唯一的图
利用高斯嵌入函数,捕捉关节间相似性
在这里插入图片描述
在这里插入图片描述
–>自适应图卷积层每一层中总共有三种类型的图,即Ak、Bk和Ck

3. 网络结构

(1) 层

在这里插入图片描述
其中橙色框为可学习参数,蓝色框为不可学习参数。(1 × 1)表示卷积核大小,Kv表示子集的数量。只有的当Cin和Cout不一致的时候才需要用到残差框。

(2) 块

在这里插入图片描述
Convs表示空间图卷积,Convt表示时间图卷积,二者基本结构均为前述层结构。

【BN层】
全称Batch Normalization,批量样本归一化

  1. 加快网络训练、收敛速度
  2. 控制梯度爆照,防止梯度消失
  3. 防止过拟合

【Dropout层】
若模型参数太多,训练样本太少,模型易产生过拟合的现象
–> 向前传播时,让某个神经元激活值以一定概率p(伯努利分布)停止工作。使模型泛化性更强,不会太依赖某些局部特征

(3) 流

在这里插入图片描述
GAP表示全局平均池化层
总共有9个块(B1-B 9),每个块三个数字分别表示输入通道数、输出通道数和步幅

(4) 整体

在这里插入图片描述
由两条流构成,代表骨架和关节,其结构均为前述流结构

4. 输入输出

输入 (Batch Size * People’s Number, Channels, Frames’Number, Joins’ Number)

5. 部分代码

存在agcn和aagcn两个版本的模型

【agcn】

model\agcn.py

(1) 网络结构
A = self.graph.A
self.data_bn = nn.BatchNorm1d(num_person * in_channels * num_point)

self.l1 = TCN_GCN_unit(3, 64, A, residual=False) //B1
self.l2 = TCN_GCN_unit(64, 64, A)  //B2
self.l3 = TCN_GCN_unit(64, 64, A)  //B3
self.l4 = TCN_GCN_unit(64, 64, A)
self.l5 = TCN_GCN_unit(64, 128, A, stride=2)  //B4
self.l6 = TCN_GCN_unit(128, 128, A)  //B5
self.l7 = TCN_GCN_unit(128, 128, A)  //B6
self.l8 = TCN_GCN_unit(128, 256, A, stride=2)  //B7
self.l9 = TCN_GCN_unit(256, 256, A)  //B8
self.l10 = TCN_GCN_unit(256, 256, A)  //B9

self.fc = nn.Linear(256, num_class)
nn.init.normal_(self.fc.weight, 0, math.sqrt(2. / num_class))
bn_init(self.data_bn, 1)

相较论文,代码多了一层l4

(2) GCN模块
def __init__(self, in_channels, out_channels, A, coff_embedding=4, num_subset=3):
	super(unit_gcn, self).__init__()
	inter_channels = out_channels
	self.inter_c = inter_channels
	self.PA = nn.Parameter(torch.from_numpy(A.astype(np.float32)))
	nn.init.constant_(self.PA, 1e-6)
	self.A = Variable(torch.from_numpy(A.astype(np.float32)), requires_grad=False)
	self.num_subset = num_subset
	
	self.conv_a = nn.ModuleList()
	self.conv_b = nn.ModuleList()
	self.conv_d = nn.ModuleList()
	for i in range(self.num_subset):
	    self.conv_a.append(nn.Conv2d(in_channels, inter_channels, 1))
	    self.conv_b.append(nn.Conv2d(in_channels, inter_channels, 1))
	    self.conv_d.append(nn.Conv2d(in_channels, out_channels, 1))
	
	if in_channels != out_channels:
	    self.down = nn.Sequential(
	        nn.Conv2d(in_channels, out_channels, 1),
	        nn.BatchNorm2d(out_channels)
	    )
	else:
	    self.down = lambda x: x
	
	self.bn = nn.BatchNorm2d(out_channels)
	self.soft = nn.Softmax(-2)
	self.relu = nn.ReLU()
	
	for m in self.modules():
	    if isinstance(m, nn.Conv2d):
	        conv_init(m)
	    elif isinstance(m, nn.BatchNorm2d):
	        bn_init(m, 1)
	bn_init(self.bn, 1e-6)
	for i in range(self.num_subset):
	    conv_branch_init(self.conv_d[i], self.num_subset)

coff_embedding:
通道缩减系数,表示输出通道数和中间通道数的比例,用于减少计算量和参数量。中间通道数是用于计算注意力矩阵Ck的特征维度

self.PA:
一个可学习参数,表示邻接矩阵中Bk

self.A:
一个不可学习参数,表示邻接矩阵中Ak,不需要梯度更新

self.conv_a: 得到θ分支的输出,θk
self.conv_b: 得到φ分支的输出,φk
以上两卷积层用于对输入数据进行通道缩减和特征转换

self.conv_d: Wk,用于将输入特征与自适应邻接矩阵相乘后的结构映射到输出特征空间

def forward(self, x):
	N, C, T, V = x.size()
	A = self.A.cuda(x.get_device())
	A = A + self.PA
	
	y = None
	for i in range(self.num_subset):
	    A1 = self.conv_a[i](x).permute(0, 3, 1, 2).contiguous().view(N, V, self.inter_c * T)
	    A2 = self.conv_b[i](x).view(N, self.inter_c * T, V)
	    A1 = self.soft(torch.matmul(A1, A2) / A1.size(-1))  # N V V
	    A1 = A1 + A[i]
	    A2 = x.view(N, C * T, V)
	    z = self.conv_d[i](torch.matmul(A2, A1).view(N, C, T, V))
	    y = z + y if y is not None else z
	
	y = self.bn(y)
	y += self.down(x)  //残差连接
	//用于增加网络深度,避免出现梯度爆炸或梯度消失
	//原理为在每个模块的输入输出间添加一个跳跃连接,使得输出等于输入加上一个残差项
	return self.relu(y)

其中A1表示θ分支,A2表示φ分支

(3) TCN模块
def __init__(self, in_channels, out_channels, kernel_size=9, stride=1):
    super(unit_tcn, self).__init__()
    pad = int((kernel_size - 1) / 2)
    self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=(kernel_size, 1), padding=(pad, 0),
                          stride=(stride, 1))

    self.bn = nn.BatchNorm2d(out_channels)
    self.relu = nn.ReLU()
    conv_init(self.conv)
    bn_init(self.bn, 1)

def forward(self, x):
    x = self.bn(self.conv(x))
    return x
(4) GCN-TCN单元
def __init__(self, in_channels, out_channels, A, stride=1, residual=True):
    super(TCN_GCN_unit, self).__init__()
    self.gcn1 = unit_gcn(in_channels, out_channels, A)
    self.tcn1 = unit_tcn(out_channels, out_channels, stride=stride)
    self.relu = nn.ReLU()
    if not residual:
        self.residual = lambda x: 0

    elif (in_channels == out_channels) and (stride == 1):
        self.residual = lambda x: x

    else:
        self.residual = unit_tcn(in_channels, out_channels, kernel_size=1, stride=stride)

def forward(self, x):
    x = self.tcn1(self.gcn1(x)) + self.residual(x)
    return self.relu(x)

【aagcn】

model\aagcn.py
相较agcn,主要添加adaptive(是否使用自适应图卷积,即邻接矩阵是否可以学习)和attention(是否使用注意力机制,即对每个节点赋予不同权重)

class unit_gcn(nn.Module):
def init: 内添加以下代码

if adaptive:
    self.PA = nn.Parameter(torch.from_numpy(A.astype(np.float32)))
    self.alpha = nn.Parameter(torch.zeros(1))
    # self.beta = nn.Parameter(torch.ones(1))
    # nn.init.constant_(self.PA, 1e-6)
    # self.A = Variable(torch.from_numpy(A.astype(np.float32)), requires_grad=False)
    # self.A = self.PA
    self.conv_a = nn.ModuleList()
    self.conv_b = nn.ModuleList()
    for i in range(self.num_subset):
        self.conv_a.append(nn.Conv2d(in_channels, inter_channels, 1))
        self.conv_b.append(nn.Conv2d(in_channels, inter_channels, 1))
else:
    self.A = Variable(torch.from_numpy(A.astype(np.float32)), requires_grad=False)
self.adaptive = adaptive

if attention:
    # self.beta = nn.Parameter(torch.zeros(1))
    # self.gamma = nn.Parameter(torch.zeros(1))
    # unified attention
    # self.Attention = nn.Parameter(torch.ones(num_jpts))

    # temporal attention
    self.conv_ta = nn.Conv1d(out_channels, 1, 9, padding=4)
    nn.init.constant_(self.conv_ta.weight, 0)
    nn.init.constant_(self.conv_ta.bias, 0)

    # s attention
    ker_jpt = num_jpts - 1 if not num_jpts % 2 else num_jpts
    pad = (ker_jpt - 1) // 2
    self.conv_sa = nn.Conv1d(out_channels, 1, ker_jpt, padding=pad)
    nn.init.xavier_normal_(self.conv_sa.weight)
    nn.init.constant_(self.conv_sa.bias, 0)

    # channel attention
    rr = 2
    self.fc1c = nn.Linear(out_channels, out_channels // rr)
    self.fc2c = nn.Linear(out_channels // rr, out_channels)
    nn.init.kaiming_normal_(self.fc1c.weight)
    nn.init.constant_(self.fc1c.bias, 0)
    nn.init.constant_(self.fc2c.weight, 0)
    nn.init.constant_(self.fc2c.bias, 0)

    # self.bn = nn.BatchNorm2d(out_channels)
    # bn_init(self.bn, 1)
self.attention = attention

def forward(self, x): 内添加以下代码

if self.adaptive:
    A = self.PA
    # A = A + self.PA
    for i in range(self.num_subset):
        A1 = self.conv_a[i](x).permute(0, 3, 1, 2).contiguous().view(N, V, self.inter_c * T)
        A2 = self.conv_b[i](x).view(N, self.inter_c * T, V)
        A1 = self.tan(torch.matmul(A1, A2) / A1.size(-1))  # N V V
        A1 = A[i] + A1 * self.alpha
        A2 = x.view(N, C * T, V)
        z = self.conv_d[i](torch.matmul(A2, A1).view(N, C, T, V))
        y = z + y if y is not None else z
else:
    A = self.A.cuda(x.get_device()) * self.mask
    for i in range(self.num_subset):
        A1 = A[i]
        A2 = x.view(N, C * T, V)
        z = self.conv_d[i](torch.matmul(A2, A1).view(N, C, T, V))
        y = z + y if y is not None else z

y = self.bn(y)
y += self.down(x)
y = self.relu(y)

if self.attention:
    # spatial attention
    se = y.mean(-2)  # N C V
    se1 = self.sigmoid(self.conv_sa(se))
    y = y * se1.unsqueeze(-2) + y
    # a1 = se1.unsqueeze(-2)

    # temporal attention
    se = y.mean(-1)
    se1 = self.sigmoid(self.conv_ta(se))
    y = y * se1.unsqueeze(-1) + y
    # a2 = se1.unsqueeze(-1)

    # channel attention
    se = y.mean(-1).mean(-1)
    se1 = self.relu(self.fc1c(se))
    se2 = self.sigmoid(self.fc2c(se1))
    y = y * se2.unsqueeze(-1).unsqueeze(-1) + y
    # a3 = se2.unsqueeze(-1).unsqueeze(-1)

    # unified attention
    # y = y * self.Attention + y
    # y = y + y * ((a2 + a3) / 2)
    # y = self.bn(y)
return y

五、总结

ST-GCN:只处理关节,时间空间在同一流轮次进行
2s-AGCN:双流分别描述骨架和关节,各流选用与ST-GCN类似的方式

  • 7
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sarrey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值