Advertisement

Preserves the local structure of the data

阅读量:

作者:禅与计算机程序设计艺术

1.简介

人类历史长河中一直在探索发现了大量关于数据的基本规律,并将其转化为直观易懂的形式如图表图像或语言表达等

为了更深入地理解和掌握这些数据的本质,在现有研究中缺乏一种能够有效整合这些特征以全面捕捉数据特征的方法。近年来提出的各种学习方法虽然取得了一定成效但主要局限于基于非典型特征之间的相互作用而未能充分揭示数据内部潜在规律。现有的深度学习模型在捕捉数据的整体性和时间连续性方面仍显不足因此在构建深度学习模型时必须同时考虑空间局部位置特征及其相互作用对模型性能的影响至关重要。

本文试图深入探讨一种基于深度学习的新模型——GCN及其在预测任务中的应用效果。该模型能够同时捕捉数据的不同层次特征及其动态变化趋势,并表现出较高的预测精度。

2.基本概念术语说明

2.1 数据集定义

在预测任务中,通常我们会选择一个数据集作为研究对象。这一数据集通常由多个不同类型的样本构成。每个样本一般会包含输入特征(如用户画像)、输出标签(如点击率)以及时间戳等信息。其中输入特征可以由不同维度的特征向量来表示;而输出标签对应于实数值标量。例如,在电商网站的商品推荐系统中,我们可能会收集用户的购买记录、浏览行为、搜索词以及产品相关信息作为输入特征,并以用户的评分或点击率作为输出值。这样的一组数据集合称为训练集或测试集。

2.2 深度学习模型

深度学习模型被划分为两种主要类型——有监督学习与无监督式方法。有监督式方法常用于回归分析与分类任务;而无监督式方法则通常应用于数据聚类、关联规则挖掘以及异常检测等多个领域。在预测任务范畴内,则可将深度神经网络划分为序列处理网络与图神经网络两大类。

序列学习 :此类模型主要应用于时间序列数据领域。它们能够捕捉序列数据中的时间关系与趋势,并基于这些关系进行预测分析。在医疗领域中,我们可以通过分析疾病的传播链条以及流行病的传播路径等信息推断疾病传播的趋势;而在金融领域中,则可基于股票价格指标、汇率变动等因素建立相应的预测模型以辅助投资决策。这类模型主要包括循环神经网络、自编码器以及Transformer架构等多种类型。

图学习:这类模型主要应用于处理具有复杂关系的数据。它能够有效识别节点间的相似性以及它们之间的关联性,并通过特定算法提取这些特征来进行预测任务。例如,在协同过滤推荐系统中,通过构建基于用户的评分矩阵和基于物品的内容矩阵来提取相关特征,并结合这些信息生成个性化推荐结果;而在社交网络分析中,则结合用户与关注者之间的互动记录、消息传播路径以及用户的兴趣标签和地理位置信息来推断其行为模式;此类模型涵盖的算法包括图卷积网络(GCN)、图注意力网络(GAT)以及一般的图神经网络(GNN)。

3.核心算法原理及具体操作步骤以及数学公式讲解

3.1 GCN模型的结构

(1) 模块构成

GCN模型由图卷积网络、图池化网络、全连接层三大模块组成。

图卷积网络(Graph Convolutional Networks, GCNs) 是最基础且广泛应用的核心网络架构之一。它通过系统性地分析节点间的传播机制,在有向图中模拟特征的扩散过程。具体而言,在任意两个节点i和j之间存在一条边的情况下,默认存在信息交互的可能性。为此我们引入邻接矩阵A来表征这一关系网络结构特征:通过引入邻接矩阵A后,在第l层中定义其元素为\hat{A}_{ij}=1当且仅当边(i,j)存在;否则\hat{A}_{ij}=0。为了构建完整的特征表示体系,在第l层中定义中心化后的度矩阵形式为\tilde{D}^{-1/2}\tilde{A}\tilde{D}^{-1/2};其中\tilde{A}=\hat{A}+I代表加入了自连接边后的完整关系图;而\tilde{D}则对应于修正后的度分布情况。为了实现系统的可学习性,在第l层中定义参数变换矩阵为\Theta^{(l)}=[W^{(l)}, B^{(l)}]:其中权重参数子矩阵W^{(l)} \in \mathbb{R}^{d\times f}用于表征各条边的重要性程度;而中心化参数子矩阵B^{(l)} \in \mathbb{R}^{d\times d}则用于调节节点间的相对影响关系。整个GCN模型的学习过程主要围绕着如何优化这两个关键参数子矩阵以达到最佳特征表达效果的目的展开研究

图池化网络(Graph Pooling Network) : 在现实场景中,因为图结构中的顶点数量通常非常大,因此有必要对图形进行采样处理。该网络的主要目标是通过减少计算复杂度来提升模型性能,并主要包含平均池化和最大池化两种基本操作。

全连接层(Fully Connected Layer) : 用于对输出结果进行推断。该层通过与图卷积网络、图池化网络及其他隐藏层建立并行结构来执行最终推断。

(2) 实现过程

图1. GCN模型实现

采用了节点的特征向量集合X=\{x_1, x_2,\cdots,x_n\}以及邻接关系描述矩阵A=[a_{ij}]作为输入参数;
基于两层图卷积网络模型生成了相应的图谱表示;
对于每个节点v,其特定的特征h_v被定义为所有邻居节点\sum_{u\in N(v)} \frac{1}{\sqrt{|N(v)|}} \odot H^{(k)}(u)之和;
最终通过应用softmax激活函数对各节点的概率分布进行预测。

(3) 多跳网络

与传统的CNN网络不同的是,在GCN模型中通过多层传播逐步累加邻居节点信息,并引入了多跳机制以提升特征表达能力。其中每个卷积核仅关注距离其最近的k个邻居节点(而非所有k步邻居),从而既能显著降低计算开销又能完整保留各节点间的关键信息。

3.2 局部结构与时空结构的融合

为了更有效地捕捉数据的局部特征及其时空关系,我们可以通过构建一个基于GCN的方法来同时提取数据的局部与全局特征。具体而言,在所构建的GCN架构中依次引入层次化特征提取模块,并随后添加全局嵌入模块以整合整体信息模式。如图所示:

图2. 层次特征编码和全局嵌入的组合。

层级特征编码通过整合不同粒度节点特性和超边特性和子图特性和融合不同级别的人工智能模型生成最终表示。基于不同的卷积核层计算出各自对应的嵌入向量序列从而具备多尺度特性。

3.3 时序预测问题的解决方案

(1) 时间卷积网络(Temporal Convolutional Networks)

基于序列预测任务的时间相关性特征分析,在现有研究中发现GCN模型具备有效捕捉节点间时序关系的能力。然而,在现有研究中发现诸多未解决的问题。例如,在实际应用中发现该方法仅局限于当前节点的状态信息进行预测,并且无法有效提取和整合历史状态数据中的深度信息。

为了应对这一问题, 我们可以通过应用时间卷积网络(Temporal Convolutional Networks)来获取历史节点的状态信息. 其本质是运用卷积操作, 基于滑动窗口的技术, 对时序数据中的历史节点特征进行深度提取. 如下图所示, 该模型架构展示了其在网络层中如何完成特征提取过程.

图3. 时序卷积网络(TCN)

左半部分采用了常规的卷积层结构,在空间维度上提取基础特征信息;右半部分采用了循环卷积层结构,在时序维度上展开深度建模过程。通过多时间尺度的空间变换操作将各时间步对应的特征信号映射至独立通道,并完成特征更新以促进跨时空信息的有效融合;随后通过全局平均池化操作获取最终输出表示

(2) 分组时间卷积网络(Grouped Temporal Convolutional Networks)

在训练及推理的过程中

该网络(Grouped Temporal Convolutional Networks)通过依据不同时间段划分数据块来进行卷积运算,在每个时间段内提取特定范围内的特征信息,并将这些特征信息整合形成完整的表征

图4. 分组时间卷积网络(GT-CNN)

如图4所示,在步骤一中将输入数据划分为若干个时间区间段(具体划分间隔可根据实际需求设定)。随后对每一个时间段执行卷积操作以提取多尺度特征,并整合各时间段的所有特征信息。接着通过全局平均池化层对提取到的特征进行压缩以降低维度,并以输出最终预测值的形式结束整个过程。

(3) 时空预测的改进方法

考虑到将局部结构与全局结构相结合之外,还有其他诸多途径能够进一步提高时空预测任务的性能。其中最常见的做法是引入注意力机制。

如图5所示, 引入注意力机制有助于对不同通道上的特征图进行选择, 并重点分析其重要性。

图5. 时空注意力机制

注意力机制能够识别和利用长距离的相关性,并为模型提供有效的特征提取能力;此外,在建模时空关系时还可以考虑采用序列到序列的结构;例如通过LSTM单元捕获历史时间点的状态特征并与RNN层配合使用以预测后续节点的行为模式。

4.具体代码实例及解释说明

代码段落已给出,并且参考文献中引用了论文《Semi-Supervised Classification with Graph Convolutional Networks》

复制代码
    import torch
    from torch.nn import ModuleList, Linear, ReLU, Sequential, Conv1d, MaxPool1d, AvgPool1d
    import numpy as np
    
    
    class TCN(torch.nn.Module):
    """
    The temporal convolutional network for time series prediction problem.
    
    Parameters
    ----------
    in_channels : int
        Input channels.
    out_channels : list or tuple
        Output channels for each layer.
    kernel_size : int or tuple
        Kernel size for each layer.
    dropout : float, optional
        Dropout rate for each layer. Default: ``0``
    activation : func of nn, optional
        Activation function after each layer. Default: `ReLU()`
    pool_type : str, optional
        Type of pooling layers used after every block. Choose from'max', 'avg'. Default: `'max'`
    pool_sizes : list or tuple, optional
        Sizes of window for pooling operations. Each element is the corresponding output length after pooling operation. Default: `(1, )`
    residual : bool, optional
        If use residual connection between blocks. Default: `True`.
    bias : bool, optional
        Whether to add a learnable bias to the output. Default: `False`.
    logger : logging object, optional
        To record training process information. Default: `None`.
    """
    
    def __init__(self,
                 in_channels,
                 out_channels,
                 kernel_size=3,
                 dropout=0.,
                 activation=ReLU(),
                 pool_type='max',
                 pool_sizes=(1,),
                 residual=True,
                 bias=False,
                 logger=None):
    
        super().__init__()
        self._logger = logger
    
        if not isinstance(kernel_size, (tuple, list)):
            kernel_size = [kernel_size] * len(out_channels)
    
        assert all([isinstance(ks, int) and ks > 0 for ks in kernel_size]), "Kernel sizes must be positive integers."
        assert all([oc % 2 == 1 for oc in out_channels]), "Output channel numbers should be odd integers"
        assert all([(pool_size <= i) and ((len(set(pool_sizes)) == 1) or (pool_size >= max(pool_sizes)))
                    for pool_size in set(pool_sizes)]), "Pooling sizes should less than input length and not repeated."
    
        self.num_layers = len(out_channels)
        self.dropouts = torch.nn.Dropout(p=dropout)
        self.blocks = ModuleList()
        for i in range(self.num_layers):
            dilation = 2 ** i
    
            padding = [(ks - 1) // 2 * dilation for ks in kernel_size[:i]]
            conv1ds = []
            for j in range(i + 1):
                inc = in_channels[min(j, len(in_channels) - 1)] if type(in_channels) in (list, tuple) else in_channels
                outc = out_channels[i]
    
                conv1d = Conv1d(inc,
                                outc,
                                kernel_size=kernel_size[min(j, len(kernel_size) - 1)],
                                dilation=dilation,
                                padding=padding[j],
                                groups=inc if i!= 0 else None,
                                bias=bias)
                conv1ds.append(conv1d)
    
            block = Sequential(*conv1ds[:-1])
    
            setattr(self, f'block_{i}', block)
            act = deepcopy(activation) if i!= self.num_layers - 1 else None
            self.blocks.append((block, act))
    
        self.pools = ModuleList()
        for ps in reversed(pool_sizes):
            if pool_type =='max':
                pool = MaxPool1d(ps)
            elif pool_type == 'avg':
                pool = AvgPool1d(ps)
            else:
                raise ValueError("Unsupported pooling method.")
            self.pools.insert(0, pool)
    
        self.residual = residual
    
    @property
    def logger(self):
        return self._logger
    
    @logger.setter
    def logger(self, value):
        self._logger = value
    
    def forward(self, inputs):
        x = inputs
        outputs = []
        for i in range(self.num_layers):
            b, act = self.blocks[i]
    
            res_x = x if self.residual and i > 0 else None
            x = b(x)
            if i < self.num_layers - 1:
                x = self.dropouts(x)
                if act is not None:
                    x = act(x)
    
            x += res_x if self.residual and i > 0 else 0
    
            if len(self.pools) > 0:
                op, idx = min((op, idx) for (idx, op) in enumerate(outputs)
                              if op['shape'][2] == x.shape[2] // op['pool'].stride[0])
    
                x = op['pool'](x.unsqueeze(-1)).squeeze(-1).index_select(0, idx)
            else:
                op = {'shape': x.shape, 'pool': None}
    
            outputs.append({'shape': x.shape, 'pool': op['pool']})
    
            self.debug(f"{i}-th block shape:{x.shape}")
            self.debug(f"output {i}:")
            for o in outputs[-2::-1]:
                s = '-'.join(['x'.join(str(si) for si in s_)
                               for s_ in zip(*(o['shape'], op['shape']))])
                p = ','.join('x'.join(str(pi) for pi in p_) for p_ in zip(*(op['pool'].stride, op['shape']))) \
                    if op['pool'] is not None else '-'
                self.debug(f"\ts={s},p={p},act={not o['shape'][1]==inputs.shape[1]}")
    
        x = x[:, :, ::int(np.prod(outputs[-1]['shape'][2:]) / np.prod(x.shape[2:]))].mean(dim=-1)
        return x
    
    def debug(self, message):
        if self.logger is not None:
            self.logger.info(message)
    
    def main():
    model = TCN(in_channels=1, 
                out_channels=[16, 32, 64], 
                kernel_size=[3, 5, 7], 
                dropout=0.5, 
                pool_type='avg', 
                pool_sizes=[2, 4])
    
    print(model)
    
    x = torch.randn(1, 1, 100)
    y = model(x)
    print(y.shape)
    
    
    if __name__ == '__main__':
    main()
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

全部评论 (0)

还没有任何评论哟~