赞
踩
graph.py 的思路为
1.初始化。得到布局、策略、有无头部关键点、节点之间的联系、按照策略获得邻接矩阵。
2.获取数据集的边界数据edge,输入为布局layout。
获取布局(openpose or ntu 数据集节点信息)
得到每个节点自身的link关系
得到数据集中给出的关节点相互之间的link关系
是否加入头部关键点
edge=关节点自身link+ 关节点之间的link
设置姿态中心关节点
3.获取关节点之间的距离
建立一个 (节点数*节点数) 大小的零矩阵,并给矩阵中所有元素赋值为1
计算关节点之间距离步数:
节点之间的距离=零矩阵+正无穷。
对a矩阵中的数值进行d次方运算,对d取遍从0到最大距离1.
将新生成transermat进行堆叠。
对d在1到-1之间以0个步长进行遍历,让d将arrivemat中的d进行赋值。
返回hopdis。
4.获得邻接矩阵A
关节之间的有效距离范围为(0,maxhop=1)。
邻接矩阵=(顶点数目 * 顶点数目) 大小的零矩阵。
在有效距离中进行遍历,得到能使hopdis与hop相等的邻接矩阵,并将这个矩阵赋值为1,下称“有效距离邻接矩阵”。
对这个矩阵进行归一化。
最终获得邻接矩阵A。
如果在uniform策略下。建立一个 (1*顶点数目*顶点数目) 的零矩阵,将归一化的矩阵放在a(0)项,作为graph类的self.A。
如果在distance策略下。建立一个 (有效距离*顶点数目*顶点数目)的零矩阵,把有效距离邻接矩阵放入。
**如果在spatial策略下。把一个数组A放入。(在空间策略下,由于d的不同,对距离中心点远近不同的矩阵按照由近到远的顺序进行依次放入)**
建立零矩阵root,建立零矩阵close,建立零矩阵further。
如果hop为0,就在A数组后面添加root矩阵。
否则,加入root+close矩阵。再加入further矩阵。
对按照以上过程得到的A矩阵进行堆叠,得到邻接矩阵A。
最终返回邻接矩阵A:静态邻接矩阵:使用身体部位的连通性作为节点上的先验关系。
静态邻接A被固定,并由所有层共享。
""" Graph definitions, based on awesome previous work by https://github.com/yysijie/st-gcn 图形定义,基于stgcn """ import numpy as np class Graph: """ The Graph to models the skeletons extracted by the openpose Graph:用于对openpose提取的骨骼进行建模 Args: strategy (string): must be one of the follow candidates 策略(字符串):必须为以下之一 - uniform: Uniform Labeling 统一:统一标签 - distance: Distance Partitioning 距离:距离分区 - spatial: Spatial Configuration 空间:空间配置 For more information, please refer to the section 'Partition Strategies' in our paper (https://arxiv.org/abs/1801.07455). layout (string): must be one of the follow candidates 布局(字符串):必须为以下候选者之一 - openpose: Is consists of 18 joints. For more information, please refer to https://github.com/CMU-Perceptual-Computing-Lab/openpose#output - ntu-rgb+d: Is consists of 25 joints. For more information, please refer to https://github.com/shahroudy/NTURGB-D max_hop (int): the maximal distance between two connected nodes 两个连接的节点之间的最大距离 """ def __init__(self, layout='openpose', strategy='spatial', headless=False, max_hop=1): self.headless = headless self.max_hop = max_hop self.get_edge(layout) self.hop_dis = get_hop_distance(self.num_node, self.edge, max_hop=max_hop) self.get_adjacency(strategy) # 按照策略获得邻接矩阵 def __str__(self): return self.A # a为(38行)得到邻接矩阵时创建的零矩阵 def get_edge(self, layout): # 获取数据集的边界edge if layout == 'openpose': self.num_node = 14 # openpose顶点node为14个(无头) self_link = [(i, i) for i in range(self.num_node)] # 循环14次,得到关节自身的link 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)] # openpose中关节之间的联系 if not self.headless: # if not= none,即headless=none,在有头部的情况下 neighbor_link += [(15, 0), (14, 0), (17, 15), (16, 14)] # 需要加入openpose中头部的关节联系 self.num_node = 18 # openpose顶点node为18个(有头) self.edge = self_link + neighbor_link # edge为关节自身edge加关节之间的联系 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] # 由于不包含0节点 self.edge = self_link + neighbor_link self.center = 21 - 1 else: raise ValueError("Do Not Exist This Layout.") # 如果不为这两个模型就输出 没有出现这个布局 def get_adjacency(self, strategy): # 邻接的获得 valid_hop = range(0, self.max_hop + 1) # 关节之间的有效距离为(0,两个节点的最大距离),从0开始计数到两个节点的最大距离 adjacency = np.zeros((self.num_node, self.num_node)) # 生成一个顶点数目*顶点数目大小的零矩阵 for hop in valid_hop: # 在validhop中进行遍历 adjacency[self.hop_dis == hop] = 1 # 得到maxhop数量的邻接矩阵=1 normalize_adjacency = normalize_digraph(adjacency) # 图像归一化 if strategy == 'uniform': # 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): # 获得索引和对应值的方法,索引为validhop A[i][self.hop_dis == hop] = normalize_adjacency[self.hop_dis == hop] # 获得归一化邻接矩阵 self.A = A elif strategy == 'spatial': # 在空间策略下 A = [] for hop in valid_hop: # 在validhop进行循环 a_root = np.zeros((self.num_node, self.num_node)) # 建立零矩阵root a_close = np.zeros((self.num_node, self.num_node)) # 建立零矩阵close a_further = np.zeros((self.num_node, self.num_node)) # 建立零矩阵further for i in range(self.num_node): # 在顶点数目-1循环 for j in range(self.num_node): if self.hop_dis[j, i] == hop: 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矩阵后面添加root矩阵 else: A.append(a_root + a_close) A.append(a_further) A = np.stack(A) self.A = A else: raise ValueError("Do Not Exist This Strategy") def get_hop_distance(num_node, edge, max_hop=1): # 关节之间的距离 A = np.zeros((num_node, num_node)) # 建立一个 节点*节点 大小的零矩阵 for i, j in edge: # 给矩阵进行赋值为1 A[j, i] = 1 A[i, j] = 1 # compute hop steps # 计算 关节之间距离的步数 hop_dis = np.zeros((num_node, num_node)) + np.inf # 节点之间的距离= 零矩阵+ 正无穷 transfer_mat = [np.linalg.matrix_power(A, d) for d in range(max_hop + 1)] # 对A矩阵中的数值进行d次方运算,d取遍0到最大距离 arrive_mat = (np.stack(transfer_mat) > 0) # 沿着新轴连接数组的序列,axis=0,增加第一个维度 for d in range(max_hop, -1, -1): # 在最大距离到0之间进行循环 hop_dis[arrive_mat[d]] = d return hop_dis def normalize_digraph(A): # 图像归一化 Dl = np.sum(A, 0) # 按行相加 num_node = A.shape[0] # 维度归一 Dn = np.zeros((num_node, num_node)) for i in range(num_node): if Dl[i] > 0: Dn[i, i] = Dl[i]**(-1) AD = np.dot(A, Dn) return AD
sagc.py的主要思路为:
1.初始化sagc模块
初始化及超类设置。按照stgcn的思想,将通道数划分为三个子集。 对A矩阵设置默认值。 使用nturgb或者open pose数据集布局。 将graph类中的布局策略,strategy,layout,headless导入。 把设定好的静态临界矩阵放入statadj变量中。 建立global矩阵(B矩阵),以statadj矩阵为基础。 将其转换为整型矩阵,将矩阵中0的元素填充为1e-6,属性为可进行梯度下降。 将statadj转换为整形矩阵,属性为不进行梯度估计,子集数量为3。 c矩阵的计算。 设计a、b两个卷积模块,用3个2d卷积函数(in、inter、1)对两个卷积模块进行扩充。 设计图卷积模块,用3个图卷积函数(out)进行扩充。 残差层设计。 如果输入输出通道数不相同,进行2d卷积操作和批量patch归一化。否则,x矩阵不变。 批量归一化,求softmax,激活。 取得statadj矩阵弟零项,并乘3作为卷积核大小。(每个单独通道的邻接矩阵不同) 扩大卷积。2d卷积(in、out*卷积核、卷积核、步长=1、没有偏移) 衰减卷积。2d卷积(3*out、out、步长=1、没有偏移)
2.计算c矩阵
x矩阵尺寸输入。包括(n,chan,t,v)
建立一个数组C。
对卷积模块a中的值进行维度0123到0312的转换,再改变tensor的维度(n,v,中心通道数*t),达到转置的目的。
对卷积模块a和b之间进行点积运算,输入进softmax函数,并进行归一化,最后得到 inferred 邻接矩阵(c矩阵)。
3.前向传播
返回C矩阵。得到C矩阵的残差。对x矩阵进行扩大卷积。
把邻接矩阵adj赋值给A矩阵。提取输入x的大小(n,kc,t,v),并转变x矩阵的维度。
定义abc三个矩阵的获得方法。每个gconv获取outchannels作为输入,区别在于他们哦那个有不同的卷积核。
将这三个矩阵获得方法进行连接,步长为1,在给定维度上对输入的张量序列进行连续操作。得出的结果赋值给输入x。
执行衰减卷积。
输出y=x+x的残差。
全局卷积归一化和1*1卷积层
1.全局卷积:主要思想为,对bn和relu过程应用单个邻接图卷积
初始化。定义bn为2d批量归一化函数,激活函数为relu,将计算得到的值直接覆盖之前得到的值。
前向传播。
全局邻接矩阵的情况下。如果邻接矩阵的尺寸长度为3,使用矩阵乘法,改变其维度为nctw。
每个样本矩阵情况下。如果尺寸长度为4,改变维度。
归一化之后进行激活,最终返回x。
2.1*1卷积层:能够将尺寸缩小3倍计算。
初始化。定义组为输入通道数//邻接矩阵的类型数,并赋值给输出通道数。
应用处理视频的3d卷积函数,提取时序信息。
前向传播。
3.统计减少:通过汇总将尺寸减少3倍。
组=输入通道数//邻接矩阵类型数。
前向传播,提取输入矩阵的大小,改变其维度(第一维缩小一倍),去掉第二维。
import torch import torch.nn as nn import numpy as np from models.graph.graph import Graph class SAGC(nn.Module): """ Spatial Attention Graph Convolution 空间注意力图卷积 Applied to K_n adjacency subsets, each with K_a matrices 应用于K_n个邻接子集,每个子集都有K_a个矩阵 Base class provides the data-based C matrix and returns 基类提供基于数据的C矩阵并返回 K_n * K_a results. """ def __init__(self, in_channels, out_channels, stat_adj=None, num_subset=3, coff_embedding=4, ntu=False, headless=False): super().__init__() # 初始化及超类设置 inter_channels = out_channels // coff_embedding # 划分子集的方式,按照stgcn方式。中心通道=输出通道//嵌入coff self.adj_types = 3 # 邻接矩阵的类型=3 self.inter_c = inter_channels # 按照stgcn,一共划分为三个子集 if stat_adj is None: # Default value for A: 对a矩阵设置默认值 如果没有静态临界矩阵 layout = 'ntu-rgb+d' if ntu else 'openpose' # 使用ntu数据集,否则openpose self.graph = Graph(strategy='spatial', layout=layout, headless=headless) # 布局策略,空间、布局、无头 stat_adj = self.graph.A # 把设定好的邻接矩阵a矩阵放入stat(静态邻接)中 self.g_adj = torch·.from_numpy(stat_adj.astype(np.float32)) # 建立global矩阵。 astype是布尔类型,非零为true,零为false,即转换为整型矩阵 nn.init.constant_(self.g_adj, 1e-6) # 用1e-6填充向量,将0的位置填充为1e-6 self.g_adj = nn.Parameter(self.g_adj, requires_grad=True) # 转换gsdj的格式,判断条件为梯度需要 self.stat_adj = torch.from_numpy(stat_adj.astype(np.float32)) # 转换为整型矩阵 self.stat_adj.requires_grad = False # 把stat邻接矩阵转换为不要梯度 self.num_subset = num_subset # 数据集的数量为3 # Convolutions for calculating C adj matrix 计算C矩阵的卷积 self.conv_a = nn.ModuleList() # modulelist内部没有封装forward函数,只是使用append或者extend的操作对list进行扩充。 self.conv_b = nn.ModuleList() for i in range(self.num_subset): # 卷积三次 self.conv_a.append(nn.Conv2d(in_channels, inter_channels, 1)) # conv2d作为二位卷积的实现。先实例化再使用。卷积核大小为1*1 self.conv_b.append(nn.Conv2d(in_channels, inter_channels, 1)) self.gconv = nn.ModuleList() # global矩阵 for i in range(self.adj_types): self.gconv.append(GraphConvBR(out_channels)) # gconv后面加全局卷积生成的x # Residual Layer 残差层 if in_channels != out_channels: # 如果输入输出通道数不相同 self.down = nn.Sequential(nn.Conv2d(in_channels, out_channels, 1), nn.BatchNorm2d(out_channels)) # 进行卷积和批量归一化 # sequential是一个有序的容器,神经网络模块将按照传入构造器的顺序一次被添加到计算图中执行, # 同时以神经网络模块为元素的有序字典也可以传入参数。 # 根据自己的需求,把不同函数组合成一个小模块使用。 else: self.down = lambda x: x # 否则down等于x矩阵不变 self.bn = nn.BatchNorm2d(out_channels) # bn为批量标准化 self.soft = nn.Softmax(-2) # Cols sum to 1.0, perhaps rows should? softmax函数 self.relu = nn.CELU(0.01) # nn.ReLU() self.kernel_size = self.stat_adj.size(0) # Split to two different operators? 卷积核为stat邻接矩阵的第0项 self.kernel_size *= 3 # Separate channels for each adj matrix 每个调整矩阵的单独通道 self.expanding_conv = nn.Conv2d(in_channels, out_channels * self.kernel_size, kernel_size=(1, 1), padding=(0, 0), stride=(1, 1), dilation=(1, 1), bias=False) # 扩大卷积 self.reduction_conv = nn.Conv2d(3 * out_channels, out_channels, 1, bias=False) # 衰减卷积 def forward(self, x, adj): # 前向传播 C = self.calc_C(x) # 返回c矩阵 x_residual = self.down(x) # 剩余层的x矩阵的得到 x = self.expanding_conv(x) # 对x矩阵进行扩大卷积 A = adj # 把邻接矩阵赋给a N, KC, T, V = x.size() # 提取x的大小 x = x.view(N, self.kernel_size, KC//self.kernel_size, T, V) # 转变x矩阵的维度 # Each gconv gets out_channels as input, for sep_sum the channels # are from separate convolution kernels, otherwise they are the same inputs # 每个gconv获取out_channels作为输入,对于sep_sum,这些通道来自单独的卷积内核,否则它们是相同的输入 x_A = self.gconv[0](x[:, 0:3], A) x_B = self.gconv[1](x[:, 3:6], self.g_adj) x_C = self.gconv[2](x[:, 6:9], C) # Summarize over A0,A1,A2, and conv-pool over x_A, x_B, x_C 汇总A0,A1,A2,并汇总x_A,x_B,x_C的转换池 x = torch.cat((x_A, x_B, x_C), dim=1) # 在给定维度上对输入的张量序列进行连接操作,输入为xabc,将这三个列表按照维度为一进行连接。 x = self.reduction_conv(x) # 执行衰减卷积 y = x + x_residual # 最终得到y return y, adj # 返回y和连接 def calc_C(self, x): # c矩阵的计算 N, chan, T, V = x.size() # x矩阵的尺寸的输入 # Calc C, the data-dependent adjacency matrix 数据相关的邻接矩阵 C = [] # 建立一个数组 for i in range(self.num_subset): # (0,2)循环 C1 = self.conv_a[i](x).permute(0, 3, 1, 2).contiguous().view(N, V, self.inter_c * T) # conva函数维度123交换位置。 # 在使用peimute函数之后,数据不再连续,需要使用contiguous函数,在使用view函数改变tensor维度。否则会报错。 C2 = self.conv_b[i](x).view(N, self.inter_c * T, V) C_curr = self.soft(torch.matmul(C1, C2)) # / C1.size(-1)) # N V V 矩阵乘法后,输入进softmax函数 C.append(C_curr) # c矩阵后加入currt函数 del C1, C2 C = torch.stack(C).permute(1, 0, 2, 3).contiguous() # 最终得到c矩阵 return C class GraphConvBR(torch.nn.Module): # 全局卷积 def __init__(self, out_channels): """ Applies a single adjacency graph convolution with BN and Relu 对BN和Relu应用单个邻接图卷积 bn是批量标准化,在cnn中,batch就是训练网络所设定的图片数量batchsize 对于输入的一个batch数据,先计算出每个维度的平均值和方差, 对于一个2*2的灰度图像来说,那么就要计算一个batch内每个像素通道的平均值和方差(共计算四次,得到四个平均值和方差)。 """ super().__init__() self.bn = nn.BatchNorm2d(out_channels) #返回一个shape与num-feature相同的tensor,numfeature为输入batch中图像的Chanel数(按照每一个chanel来做归一化) self.act = nn.ReLU(inplace=True) # 激活函数relu,inplace=true:是否将计算得到的值直接覆盖之前的值 def forward(self, x, adj): # 前向传播 if len(adj.size()) == 3: # Global adj matrix x = torch.einsum('nkctv,kvw->nctw', (x, adj)) elif len(adj.size()) == 4: # Per sample matrix x = torch.einsum('nkctv,nkvw->nctw', (x, adj)) x = self.act(self.bn(x)) # 归一化之后进行激活 return x class Conv1x1(torch.nn.Module): # 1*1卷积层 def __init__(self, in_channels, adj_types=3): """ An operation for reducing dimensions by a factor of 3 using 1x1 convs. 使用1x1转换将尺寸缩小3倍的运算。 Reduces in groups of 3, i.e. (512, 3, 64, 12, 18) -> (512, 1, 64, 12, 18) and (512, 9, 64, 12, 18) -> (512, 3, 64, 12, 18) where channel i of the output 3 is a function of channels (3*i:3*i+2) of the input [Batch, adj, chan, time, vertices] """ super().__init__() groups = in_channels // adj_types # 组=输入通道//3 out_channels = groups # Each group reduces to a single channle 每组减少到一个通道 self.conv = nn.Conv3d(in_channels, out_channels, kernel_size=1, groups=groups) # 在处理视频时使用,目的是为了提取时序信息。输入输出通道数、卷积核大小、group(步长/补位)。 def forward(self, x): # 前向传播 x_out = self.conv(x) return x_out class SumReduce(torch.nn.Module): def __init__(self, in_channels, adj_types=3): """ An operation for reducing dimensions by a factor of 3 by summarizing. 通过汇总将尺寸减小三倍的操作。 Reduces in groups of 3, i.e. (512, 3, 64, 12, 18) -> (512, 1, 64, 12, 18) and (512, 9, 64, 12, 18) -> (512, 3, 64, 12, 18) where channel i of the output 3 is a function of channels (3*i:3*i+2) of the input [Batch, adj, chan, time, vertices] """ super().__init__() self.groups = in_channels // adj_types def forward(self, x): N, adj, Chan, T, V = x.size() # I.e. [512, 6, 32, 12, 18] x = x.view(N, self.groups, adj // self.groups, Chan, T, V) # I.e. [512, 3, 2, 32, 12, 18] x_out = x.sum(dim=2).contiguous() # I.e. [512, 3, 32, 12, 18] return x_out
convtemporalgraphical类
时域图像卷积:应用图卷积的基本模块。
输入卷积核。
通过2d卷积定义卷积内容。
定义前向传播。
确认邻接矩阵的第零项是否等于卷积核大小。
进行卷积,并提取x的尺寸信息。
改变输入矩阵的维度大小。
pygeoconv类
初始化。输入为self类、输入输出通道数、卷积核、卷积操作模式、有无头和dropout。
如果卷积操作为 sagc (2.空间注意力图像卷积),执行sagc.py中的sagc函数。
如果卷积操作为 gcn (图卷积),执行本部分中的convtempral函数。
进行前向传播,最终返回卷积结果。
import torch import torch.nn as nn from models.graph.sagc import SAGC class PyGeoConv(nn.Module): def __init__(self, in_channels, out_channels, kernel_size=1, conv_oper='sagc', headless=False, dropout=0.0, ): super().__init__() self.dropout = dropout self.kernel_size = kernel_size self.in_channels = in_channels self.out_channels = out_channels self.multi_adj = kernel_size > 1 if conv_oper == 'sagc': # 如果卷积操作为sagc self.g_conv = SAGC(in_channels, out_channels, headless=headless) elif conv_oper == 'gcn': # 卷积操作为gcn self.g_conv = ConvTemporalGraphical(in_channels, out_channels, kernel_size) # 使用下面的gcn图卷积基本方法 else: raise Exception('No such Conv oper') def forward(self, inp, adj): # 定义前向传播 return self.g_conv(inp, adj) class ConvTemporalGraphical(nn.Module): """ The basic module for applying a graph convolution. 应用图卷积的基本模块 Args: in_channels (int): Number of channels in the input sequence data 输入序列数据通道数 out_channels (int): Number of channels produced by the convolution 卷积产生的通道数 kernel_size (int): Size of the graph convolving kernel 图卷积核的大小 t_kernel_size (int): Size of the temporal convolving kernel 时域卷积核的大小 t_stride (int, optional): Stride of the temporal convolution. Default: 1 时域卷积的步长,默认为1 t_padding (int, optional): Temporal zero-padding added to both sides of the input. Default: 0 在输入两侧都添加了时域的零padding bias (bool, optional): If ``True``, adds a learnable bias to the output. Default: ``True`` 如果为真,给输出增加可学习的偏见 """ def __init__(self, in_channels, out_channels, kernel_size, t_kernel_size=1, t_stride=1, bias=True): super().__init__() self.kernel_size = kernel_size # 输入卷积核 self.conv = nn.Conv2d( # 定义卷积,in,out,1*1的卷积核,步长为1,有偏见 in_channels, out_channels * kernel_size, kernel_size=(t_kernel_size, 1), stride=(t_stride, 1), dilation=(1, 1), bias=bias) def forward(self, x, adj): # 定义前向传播 assert adj.size(0) == self.kernel_size x = self.conv(x) n, kc, t, v = x.size() x = x.view(n, self.kernel_size, kc//self.kernel_size, t, v) x = torch.einsum('nkctv,kvw->nctw', (x, adj)) return x.contiguous(), adj
时空图卷积块
初始化。输入为:in/out chanels,kernel size,步长,dropout,卷积操作,是否激活,输出归一化,输出激活,残差,有无头。
确定卷积核是否为2,确认卷积核的第零项%2==1.。设置padding的长度。
将无头、输出激活、卷积操作、激活函数进行输入。
然后执行上述pygeoconv模块,执行 sagc函数。进行空间注意力图卷积。
进行输出批量归一化,否则,将函数输入进身份层。
执行时域卷积网络函数(tcn)。
批量归一化,激活,卷积,进入归一化层,dropout(将所有元素中每个元素按照概率0.5更改为0,起到求均值的作用)。
残差处理。
如果没有残差,设置残差=0.
如果有残差,且输出通道数==输出通道数,步长为1,那么就把残差保留。
否则,再次进行卷积核归一化操作。
前向传播。
得到残差。得到输入矩阵和邻接矩阵。
将x输入tcn函数,得到结果+残差,返回输入x的值。
再进行输出激活操作。最终返回x和adj。
import torch.nn as nn from models.graph.pygeoconv import PyGeoConv class ConvBlock(nn.Module): # 卷积块 def __init__(self, in_channels, out_channels, kernel_size, stride=1, dropout=0, conv_oper='sagc', act=None, out_bn=True, out_act=True, residual=True, headless=False,): super().__init__() assert len(kernel_size) == 2 # 确认卷积核是否等于2 assert kernel_size[0] % 2 == 1 # 确人卷积核大小是否 padding = ((kernel_size[0] - 1) // 2, 0) # padding:移动盒子的位置 self.headless = headless # 无头 self.out_act = out_act # 输出激活 self.conv_oper = conv_oper # 卷积操作 self.act = nn.ReLU(inplace=True) if act is None else act # 激活函数,替代原来的值 self.gcn = PyGeoConv(in_channels, out_channels, kernel_size=kernel_size[1], dropout=dropout, headless=self.headless, conv_oper=self.conv_oper) # 执行sagc模块 if out_bn: # 如果存在输出批量归一化 bn_layer = nn.BatchNorm2d(out_channels) # 执行归一化层 else: bn_layer = nn.Identity() # Identity layer shall no BN be used 身份层不能使用bn。 # identify函数建立一个输入模块,什么都不做,通常用在神经网络的输入层。 # 相当于一个容器,把输入保存下来 self.tcn = nn.Sequential(nn.BatchNorm2d(out_channels), self.act, nn.Conv2d(out_channels, out_channels, (kernel_size[0], 1), (stride, 1), padding), bn_layer, nn.Dropout(dropout, inplace=True)) # 时域卷积网络函数。 # 执行如下操作:批量归一化,激活,卷积,进入归一化层,将所有元素中每个元素按照概率0.5更改为0 if not residual: # 如果没有残差 self.residual = lambda x: 0 # 把残差设为0 elif (in_channels == out_channels) and (stride == 1): # 如果输入输出通道数相等且步长为1 self.residual = lambda x: x # 残差设为x else: self.residual = nn.Sequential(nn.Conv2d(in_channels, # 否则再次进行卷积 out_channels, kernel_size=1, stride=(stride, 1)), nn.BatchNorm2d(out_channels)) def forward(self, x, adj): # 前向传播 res = self.residual(x) x, adj = self.gcn(x, adj) x = self.tcn(x) + res if self.out_act: x = self.act(x) return x, adj
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。