gepc代码文件 graph.py

gepc.graph

1.graph.py:定义graph类,得到邻接矩阵A

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


2.sagc.py

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

3.pygeoconv.py

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


 

4.st-graph-conv-block

时空图卷积块

	初始化。输入为: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

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值