2023.12.3周报

目录

摘要

ABSTRACT

一、文献阅读

1、题目

2、摘要

3、文献解读

1、概括

2、GraphSAGE的优势

3、算法流程 

4、实验

4、结论

二、Graph Net代码实践

1. 配置项 (configs 类):

2、数据加载 (Graph_Data_Loader 类):

3、精度评价指标

4、GNN类

5、GCN类

6、图运行 (graph_run 类)

7、运行结果 

三、傅里叶变换

一、傅里叶变换是什么

二、频域

三、傅里叶级数

总结


摘要

本周,我深入研读了题为《Inductive Representation Learning on Large Graphs》的论文。文中介绍了一种名为GraphSAGE的方法,该方法在学习图嵌入映射时同时利用了节点的特征信息和结构信息。与以往的方法不同,GraphSAGE不仅保存了映射后的结果,还保留了生成嵌入映射的过程,使其具有更强的可扩展性。通过实际代码实践,我对图神经网络(GNN)的理解更为深入。此外,我还初步学习了傅里叶变换,为进一步学习奠定了基础。

ABSTRACT

This week, I delved into the study of the paper titled "Inductive Representation Learning on Large Graphs." The paper introduces a method called GraphSAGE, which simultaneously leverages node feature and structural information when learning the mapping of graph embeddings. Unlike previous approaches, GraphSAGE not only preserves the results of the mapping but also retains the process of generating embedding mappings, enhancing its scalability. Through practical code implementation, my understanding of Graph Neural Networks (GNNs) has deepened. Additionally, I initiated the study of Fourier Transform, laying the groundwork for further exploration in this area.

一、文献阅读

1、题目

题目:Inductive Representation Learning on Large Graphs

期刊/会议:NIPS 2017

论文链接:https://arxiv.org/abs/1706.02216

2、摘要

在大型图中,节点的低维嵌入在从内容推荐到蛋白质功能识别等各种预测任务中被证明极为有用。然而,现存方法要求在训练嵌入时图中的所有节点都必须存在,这些方法本质上是转导性的,难以自然地推广到未见的节点。本文介绍了GraphSAGE,这是一个通用的归纳框架,利用节点特征信息(如文本属性)有效生成先前未见数据的节点嵌入。该算法学习一个函数,通过对节点局部邻居的特征进行采样和聚合来生成嵌入,而不是为每个节点训练独立的嵌入。在三个归纳节点分类基准上,我们的算法优于强基准:使用引文和Reddit发布数据,在信息图中对未见过的节点进行分类,并在蛋白质-蛋白质相互作用的多图数据集上将其泛化为完全未见的图。

In large-scale graphs, the low-dimensional embeddings of nodes have proven to be highly valuable in various prediction tasks, ranging from content recommendation to protein function identification. However, existing methods require the presence of all nodes in the graph during the training of embeddings. These methods are inherently transductive and face challenges in naturally extending to unseen nodes. This paper introduces GraphSAGE, a universal inductive framework that efficiently generates embeddings for previously unseen data utilizing node feature information, such as text attributes. The algorithm learns a function that generates embeddings by sampling and aggregating features from the local neighbors of nodes, rather than training separate embeddings for each node. Our algorithm outperforms strong baselines on three inductive node classification benchmarks: classifying unseen nodes in citation and Reddit post data within evolving information graphs, and generalizing to completely unseen graphs using a multi-graph dataset of protein-protein interactions.

3、文献解读

1、概括

在此文章之前前人的方法本质上是transductive,因为在学习过程中图中所有的顶点都参与进来,不能自然地泛化到未见过的顶点。从而提出了一个inductive的GraphSAGE算法。GraphSAGE同时利用节点特征信息和结构信息得到Graph Embedding的映射,相比之前的方法,之前都是保存了映射后的结果,而GraphSAGE保存了生成embedding的映射,可扩展性更强,对于节点分类和链接预测问题的表现也比较突出。也就是说GraphSAGE是为了学习一种节点表示方法,即如何通过从一个顶点的局部邻居采样并聚合顶点特征,而不是为每个顶点训练单独的embedding。
 

2、GraphSAGE的优势

传统transductive方法的局限性和GraphSAGE的优势:
最初的transductive方法虽然在某些任务上表现不错,但是在现实世界中固定的图较少同时要求快速地对未见过的结点进行嵌入。但是由于之前是将图中所有结点用于训练导致模型的泛化能力较差。但是对于GraphSAGE这种inductive的方法,是通过训练学习一种基于所选择节点邻居节点进行特征提取的模型。可以快速高效的预测未见过的结点。同时对于具有相似结构特点的模型可以快速高效的进行泛化。

transductive learning得到新节点的表示的难处:
要想得到新节点的表示,需要让新的graph或者subgraph去和已经优化好的node embedding去“对齐(align)”。然而每个节点的表示都是受到其他节点的影响,因此添加一个节点,意味着许许多多与之相关的节点的表示都应该调整。这会带来极大的计算开销,即使增加几个节点,也要完全重新训练所有的节点。

GraphSAGE基本思路:
既然新增的节点,一定会改变原有节点的表示,那么为什么一定要得到每个节点的一个固定的表示呢?何不直接学习一种节点的表示方法。去学习一个节点的信息是怎么通过其邻居节点的特征聚合而来的。 学习到了这样的“聚合函数”,而我们本身就已知各个节点的特征和邻居关系,我们就可以很方便地得到一个新节点的表示了。
GCN等transductive的方法,学到的是每个节点的一个唯一确定的embedding; 而GraphSAGE方法学到的node embedding,是根据node的邻居关系的变化而变化的,也就是说,即使是旧的node,如果建立了一些新的link,那么其对应的embedding也会变化,而且也很方便地学到。
同时该方法还可以利用所有图形中都存在的结构特征(例如,节点度)。因此,该算法也可以应用于没有节点特征的图。

GraphSAGE的训练结果:
GraphSAGE的核心:GraphSAGE不是试图学习一个图上所有node的embedding,而是学习一个为每个node产生embedding的映射。
算法训练了一组聚合函数学会从节点的本地邻域聚合特征信息。而不是为每个节点训练不同的嵌入向量。每个聚合函数从一个顶点的不同的hops或者说不同的搜索深度聚合信息。测试或是推断的时候,使用训练好的系统,通过学习到的聚合函数来对完全未见过的顶点生成embedding。

3、算法流程 

1.Embedding generationalgorithm (生成节点embedding的前向传播算法) 

GraphSAGE的前向传播算法如下,前向传播描述了如何使用聚合函数对节点的邻居信息进行聚合,从而生成节点embedding:

伪代码中存在一个问题就是第四行聚合后应该得到的是k − 1层的邻居结点的特征表示。进而第五行也是k−1层的邻居结点的特征表示。

在每次迭代的过程中,顶点从它们的局部邻居聚合信息,并且随着这个过程的迭代,顶点会从越来越远的地方获得信息
算法描述了在整个图上生成embedding的过程,其中


具体流程如下:

Neighborhood definition - 采样邻居顶点
出于对计算效率的考虑,对每个顶点采样一定数量的邻居顶点作为待聚合信息的顶点。设需要的邻居数量,即采样数量为S,若顶点邻居数少于S,则采用有放回的抽样方法,直到采样出S个顶点。若顶点邻居数大于S,则采用无放回的抽样。(即采用有放回的重采样/负采样方法达到S)

当然,若不考虑计算效率,完全可以对每个顶点利用其所有的邻居顶点进行信息聚合,这样是信息无损的。

统一采样一个固定大小的邻域集,以保持每个batch的计算占用空间是固定的(即 graphSAGE并不是使用全部的相邻节点,而是做了固定size的采样)。

这样固定size的采样,每个节点和采样后的邻居的个数都相同,可以把每个节点和它们的邻居拼成一个batch送到GPU中进行批训练。

算法中k从1开始循环,因此看第一层。v是我们选取的顶点,在这里我们设置采样邻居节点的数量为2和采样邻居的阶数也为2.根据算法流程,我们可以获得第一层的h_{N(v)}^{0}
​(由第0层随机选取的顶点v的两个邻居获得)。与此同时v的邻居u根据分布特征也聚合了第0层的两个邻居信息,因此我们可以分别获取第一层中v和u的特征信息(由第0层v和u的两个邻居特征信息和第0层的v和u的特征信息之和所得到)。实际上我们可以看出第k层目标顶点的特征向量就是由第k−1层的目标顶点和邻居顶点的特征向拼接后经过某种操作生成的。
到了第2层,可以看到节点v通过“1层”的节点u和另一邻居得到了第1层v的邻居节点信息,其中借助u扩展到了“0层”的二阶邻居节点。因此,在聚合时,聚合K次,就可以扩展到K阶邻居。第二层仍然满足我们总结的规律,只是在此时k−1层的邻居顶点包含了k−2层的邻居顶点,也就达到了扩展到k阶邻居的目的。实验发现,K不必取很大的值,当K=2时,效果就很好了。至于邻居的个数,文中提到S1⋅S2≤500即两次扩展的邻居数之际小于500,大约每次只需要扩展20来个邻居时获得较高的性能。论文里说固定长度的随机游走其实就是随机选择了固定数量的邻居。

2.聚合函数的选取

在图中顶点的邻居是无序的,所以希望构造出的聚合函数是对称的(即也就是对它输入的各种排列,函数的输出结果不变),同时具有较高的表达能力。 聚合函数的对称性(symmetry property)确保了神经网络模型可以被训练且可以应用于任意顺序的顶点邻居特征集合上。

Mean aggregator
mean aggregator将目标顶点和邻居顶点的第k−1层向量拼接起来,然后对向量的每个维度进行求均值的操作,将得到的结果做一次非线性变换产生目标顶点的第k kk层表示向量。
文中用下面的式子替换算法1中的4行和5行得到GCN的inductive变形:


这里要注意的是伪代码中存在一个问题就是第四行聚合后应该得到的是k−1层的邻居结点的特征表示。进而第五行也是k−1层的邻居结点的特征表示。

修改后的基于均值的聚合器是convolutional的,这个卷积聚合器和文中的其他聚合器的重要不同在于它没有算法1中第5行的CONCAT操作可以看到替换后,是对h_{v}^{k-1} 和集合{h_{u}^{k-1},\forall u\varepsilon N(v)}取并集,然后一起算均值,再乘上权重。
LSTM aggregator

文中也测试了一个基于LSTM的复杂的聚合器[Long short-term memory]。和均值聚合器相比,LSTMs有更强的表达能力。但是,LSTMs不是symmetric的,也就是说不具有排列不变性(permutation invariant),因为它们以一个序列的方式处理输入。因此,需要先对邻居节点随机顺序,然后将邻居序列的embedding作为LSTM的输入。

Pooling aggregator

pooling聚合器,它既是对称的,又是可训练的。Pooling aggregator 先对目标顶点的邻居顶点的embedding向量进行一次非线性变换,之后进行一次pooling操作(max pooling or mean pooling),将得到结果与目标顶点的表示向量拼接,最后再经过一次非线性变换得到目标顶点的第k层表示向量。
一个element-wise max pooling操作应用在邻居集合上来聚合信息:
这里应该得到的也是k−1层的邻居结点的特征表示h_{N(v)}^{k-1}

max表示element-wise最大值操作,取每个特征的最大值,σ是非线性激活函数,所有相邻节点的向量共享权重,先经过一个非线性全连接层,然后做max-pooling,按维度应用 max/mean pooling,可以捕获邻居集上在某一个维度的突出的/综合的表现。

 4、实验

1、数据集

Citation论文引用网络(节点分类):这个数据集的节点标签对应于六个不同的领域的标签。该数据集共包含302,424个节点,平均度数为9.15。文中使用2000-2004年的数据集对所有算法进行训练,并使用2005年的数据进行测试(30%用于验证)。

Reddit帖子论坛(节点分类):Reddit是一个大型的在线论坛,用户可以在这里对不同主题社区的内容进行发布和评论。作者在Reddit上对2014年9月发布的帖子构建了一个图形数据集。本例中的节点标签是帖子所属的社区或“subreddit”。文中对50个大型社区进行了抽样,并构建了一个帖子-帖子的图,如果同一个用户评论了两个帖子,就将这两个帖子连接起来。该数据集共包含232,965个帖子,平均度为492。

PPI蛋白质网络(graph分类):文中在各种蛋白质-蛋白质相互作用(PPI)图中对蛋白质角色进行分类,每个图对应一个不同的人体组织。并且使用从Molecular。
Signatures Database中收集的位置基因集、motif基因集和免疫学signatures作为特征,gene ontology作为标签(共121个)。图中平均包含2373个节点,平均度为28.8。文中将所有算法在20个图上训练,然后在两个测试图上预测F1 socres(另外两个图用于验证)。
2、参数设置

1、K=2,聚合两跳内邻居特征
2、S1=25,S2=10: 对一跳邻居抽样25个,二跳邻居抽样10个
3、RELU 激活单元 Adam 优化器(除了DeepWalk,DeepWalk使用梯度下降效果更好)
4、对每个节点进行步长为5的50次随机游走
5、负采样参考word2vec,按平滑degree进行,对每个节点采样20个

 3、实验结果

通过第一组图得到如下结论

可以看到GraphSAGE的性能显著优于baseline方法,三个数据集上的实验结果表明,一般是LSTM或pooling效果比较好,有监督都比无监督好,无监督版本的GraphSAGE-pool对引文数据和Reddit数据的连接(concatenation)性能分别比DeepWalk embeddings和raw features的连接性能好13.8%和29.1%,而有监督版本的连接性能分别提高了19.7%和37.2%。
通过第二组图得到如下结论

LSTM和pool的效果较好,为了更定量地了解这些趋势,实验中将设置六种不同的实验,即(3个数据集)×(非监督vs.监督)),GraphSAGE-LSTM比GraphSAGE-pool慢得多(≈2×),这可能使基于pooling的聚合器在总体上略占优势,LSTM方法和pooling方法之间没有显著差异,文中使用非参数Wilcoxon Signed-Rank检验来量化实验中不同聚合器之间的差异,在适用的情况下报告T-statistic和p-value。

4、结论

我们引入了一种新颖的方法,可以有效地为看不见的节点生成嵌入。 GraphSAGE 始终优于最新的基准,通过对节点邻域进行采样有效地权衡了性能和运行时间,并且我们的理论分析提供了关于我们的方法如何学习局部图结构的见解。许多扩展和潜在的改进都是可能的,例如扩展 GraphSAGE 以合并有向图或多峰图。未来工作的一个特别有趣的方向是探索非均匀邻域采样函数,甚至可能作为GraphSAGE优化的一部分来学习这些函数。

本文所谓学习一个 “聚合器函数”,主要是学习聚合器的参数,如 W,b ,待有新的节点需要预测时,直接用这个聚合函数即可。个人感觉,和GCN的区别是,GCN在预测节点时,需要将整个图训练一遍(即待预测的节点和原来的节点),捕获拓扑结构;而GraphSage则无需这样,只需要将新的节点输入到训练好的聚合器函数里即可实现预测。当然,GraphSage在一定程度上是一种有损的方法。


 

二、Graph Net代码实践

1. 配置项 (configs 类):

  • save_model_dir: 模型保存路径
  • model_name: 模型名称 ('GCN' 或 'GNN')
  • seed: 随机种子
  • device: 训练设备 ('cuda' 或 'cpu')
  • batch_size: 批处理大小
  • epoch: 训练轮数
  • in_features: 输入特征维度
  • hidden_features: 隐藏层特征维度
  • output_features: 输出特征维度
  • learning_rate: 学习率
  • dropout: Dropout 概率
  • istrain: 是否进行训练
  • istest: 是否进行测试
class configs():
    def __init__(self):
        # Data
        self.data_path = r'./data'
        self.save_model_dir = r'./'

        self.model_name = r'GCN'  # GNN/GCN
        self.seed = 2023

        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        self.batch_size = 64
        self.epoch = 200
        self.in_features = 1433  # core ~ feature:1433
        self.hidden_features = 16  # 隐层数量
        self.output_features = 8  # core~paper-point~ 8类

        self.learning_rate = 0.01
        self.dropout = 0.5

        self.istrain = True
        self.istest = True


cfg = configs()

2、数据加载 (Graph_Data_Loader 类):

Graph_Data_Loader 类负责高效加载图数据,主要用于处理节点特征、标签以及构建图的邻接矩阵。在数据加载的过程中,它首先从指定路径加载节点的特征、标签以及相互引用的节点关系。对于标签,采用了一种有效的 one-hot 编码方式,将其转换为模型可接受的形式。

构建图的邻接矩阵是图神经网络中至关重要的一步。该类会处理原始的边信息,通过索引映射将节点之间的关系转换为邻接矩阵,同时保证矩阵的对称性。这一步骤是为了充分利用图的拓扑结构信息,提供给模型更全面的上下文信息。

另外,为了保证模型的稳定性和训练效果,节点特征和邻接矩阵都会经过归一化处理。这有助于防止梯度爆炸或梯度消失的问题,并且提升模型的泛化性能。

最后,为了进行模型的训练和测试,将处理后的节点特征、标签、邻接矩阵等数据转换为 PyTorch 张量,并根据配置项将数据移动到指定的硬件设备上,例如 GPU 或 CPU。这确保了在不同硬件上都能够高效地执行训练和测试过程。整个数据加载的过程经过这一系列的步骤,为后续的图神经网络模型提供了可靠的输入。

class Graph_Data_Loader():
    def __init__(self):
        self.adj, self.features, self.labels, self.idx_train, self.idx_val, self.idx_test = self.load_data()
        self.adj = self.adj.to(cfg.device)
        self.features = self.features.to(cfg.device)
        self.labels = self.labels.to(cfg.device)
        self.idx_train = self.idx_train.to(cfg.device)
        self.idx_val = self.idx_val.to(cfg.device)
        self.idx_test = self.idx_test.to(cfg.device)

    def load_data(self, path=cfg.data_path, dataset="cora"):
        """Load citation network dataset (cora only for now)"""
        print('Loading {} dataset...'.format(dataset))

        idx_features_labels = np.genfromtxt(os.path.join(path, dataset, dataset + '.content'),
                                            dtype=np.dtype(str))
        features = sp.csr_matrix(idx_features_labels[:, 1:-1], dtype=np.float32)
        labels = self.encode_onehot(idx_features_labels[:, -1])

        # build graph
        idx = np.array(idx_features_labels[:, 0], dtype=np.int32)
        idx_map = {j: i for i, j in enumerate(idx)}
        edges_unordered = np.genfromtxt(os.path.join(path, dataset, dataset + '.cites'),
                                        dtype=np.int32)
        edges = np.array(list(map(idx_map.get, edges_unordered.flatten())),
                         dtype=np.int32).reshape(edges_unordered.shape)
        adj = sp.coo_matrix((np.ones(edges.shape[0]), (edges[:, 0], edges[:, 1])),
                            shape=(labels.shape[0], labels.shape[0]),
                            dtype=np.float32)

        # build symmetric adjacency matrix
        adj = adj + adj.T.multiply(adj.T > adj) - adj.multiply(adj.T > adj)

        features = self.normalize(features)
        adj = self.normalize(adj + sp.eye(adj.shape[0]))

        idx_train = range(140)
        idx_val = range(200, 500)
        idx_test = range(500, 1500)

        features = torch.FloatTensor(np.array(features.todense()))
        labels = torch.LongTensor(np.where(labels)[1])
        adj = self.sparse_mx_to_torch_sparse_tensor(adj)

        idx_train = torch.LongTensor(idx_train)
        idx_val = torch.LongTensor(idx_val)
        idx_test = torch.LongTensor(idx_test)
        return adj, features, labels, idx_train, idx_val, idx_test

    def encode_onehot(self, labels):
        classes = set(labels)
        classes_dict = {c: np.identity(len(classes))[i, :] for i, c in
                        enumerate(classes)}
        labels_onehot = np.array(list(map(classes_dict.get, labels)),
                                 dtype=np.int32)
        return labels_onehot

    def normalize(self, mx):
        """Row-normalize sparse matrix"""
        rowsum = np.array(mx.sum(1))
        r_inv = np.power(rowsum, -1).flatten()
        r_inv[np.isinf(r_inv)] = 0.
        r_mat_inv = sp.diags(r_inv)
        mx = r_mat_inv.dot(mx)
        return mx

    def sparse_mx_to_torch_sparse_tensor(self, sparse_mx):
        """Convert a scipy sparse matrix to a torch sparse tensor."""
        sparse_mx = sparse_mx.tocoo().astype(np.float32)
        indices = torch.from_numpy(
            np.vstack((sparse_mx.row, sparse_mx.col)).astype(np.int64))
        values = torch.from_numpy(sparse_mx.data)
        shape = torch.Size(sparse_mx.shape)
        return torch.sparse.FloatTensor(indices, values, shape)


 3、精度评价指标

accuracy 函数是一个用于评估分类模型性能的精度指标计算方法。给定模型的输出结果 output 和真实标签 labels,该函数首先通过取预测概率分布中的最大值确定每个样本的预测类别,然后将预测的类别与真实标签进行比较。通过计算正确预测的样本数量并将其除以总样本数,得到模型在给定数据集上的分类准确性。这个精度指标直观地反映了模型对于输入样本的正确分类程度,是评估分类模型性能的重要参考指标。

# 精度评价指标
def accuracy(output, labels):
    preds = output.max(1)[1].type_as(labels)
    correct = preds.eq(labels).double()
    correct = correct.sum()
    return correct / len(labels)

4、GNN类

GNN 模型是图神经网络的一种,它由多个 GNNLayer 组成,每个 GNNLayer 负责将邻接矩阵与节点特征进行线性变换和ReLU激活,以捕捉节点之间的关系。整个模型通过堆叠多个这样的层,逐渐提取图中的高级抽象特征。最终,通过输出层进行分类,使用log-softmax作为激活函数,以预测每个节点属于不同类别的概率分布。该模型在训练时使用负对数似然损失函数进行监督学习,通过梯度下降优化参数,以实现对图中节点的有效分类任务。这种 GNN 模型通过学习节点之间的局部邻居关系,能够在图数据上表现出色,具有广泛的应用,例如社交网络分析、推荐系统等。

class GNNLayer(nn.Module):
    def __init__(self, in_features, output_features):
        super(GNNLayer, self).__init__()
        self.linear = nn.Linear(in_features, output_features)

    def forward(self, adj_matrix, features):
        hidden_features = torch.matmul(adj_matrix, features)  # GNN公式:H' = A * H
        hidden_features = self.linear(hidden_features)  # 使用线性变换
        hidden_features = F.relu(hidden_features)  # 使用ReLU作为激活函数

        return hidden_features

5、GCN类

GCN 类是一个实现图卷积网络(Graph Convolutional Network)的 PyTorch 模型,用于节点分类任务。通过堆叠两个图卷积层,该模型能够学习图结构中节点之间的关系,逐步提取高级抽象特征。在训练阶段,采用负对数似然损失函数进行监督学习,并通过梯度下降优化参数。每个图卷积层使用 ReLU 激活函数,整个模型在输出层通过 log-softmax 函数产生节点分类的概率分布。GCN 类的设计使其能够有效处理图数据,具有广泛的应用领域,如社交网络分析和推荐系统。

class GCN(nn.Module):
    def __init__(self, in_features, hidden_features, output_features, dropout=cfg.dropout):
        super(GCN, self).__init__()
        self.gc1 = GraphConvolution(in_features, hidden_features)
        self.gc2 = GraphConvolution(hidden_features, output_features)
        self.dropout = dropout

    def forward(self, adj_matrix, features):
        x = F.relu(self.gc1(features, adj_matrix))
        x = F.dropout(x, self.dropout, training=self.training)
        x = self.gc2(x, adj_matrix)
        return F.log_softmax(x, dim=1)

 6、图运行 (graph_run 类)

graph_run 类承担了整个图神经网络模型的训练和测试流程。在训练阶段,它通过创建 Graph_Data_Loader 实例加载并处理图数据,然后初始化相应的 GNN 模型(根据配置项选择 GNN 或 GCN 模型),并使用 Adam 优化器以负对数似然损失为目标进行模型训练。在每个训练周期内,输出训练和验证集上的损失以及准确度,并最终保存训练后的模型。在测试阶段,通过加载保存的模型,进行模型评估,输出测试集上的损失和准确度。整个流程通过 Graph_Data_Loader 类高效加载和处理数据,保证了模型在图数据上的训练和测试的稳健性。这个类的设计使得图神经网络的使用变得简单而灵活,方便快速地进行图数据的分类任务。

class graph_run():
    def train(self):
        t = time.time()
        # Create Train Processing
        all_data = Graph_Data_Loader()

        # 创建一个模型
        model = eval(cfg.model_name)(in_features=cfg.in_features,
                                     hidden_features=cfg.hidden_features,
                                     output_features=cfg.output_features).to(cfg.device)
        optimizer = optim.Adam(model.parameters(),
                               lr=cfg.learning_rate, weight_decay=5e-4)

        # Train
        model.train()
        for epoch in range(cfg.epoch):
            optimizer.zero_grad()
            output = model(all_data.adj, all_data.features)
            loss_train = F.nll_loss(output[all_data.idx_train], all_data.labels[all_data.idx_train])
            acc_train = accuracy(output[all_data.idx_train], all_data.labels[all_data.idx_train])
            loss_train.backward()
            optimizer.step()
            loss_val = F.nll_loss(output[all_data.idx_val], all_data.labels[all_data.idx_val])
            acc_val = accuracy(output[all_data.idx_val], all_data.labels[all_data.idx_val])
            print('Epoch: {:04d}'.format(epoch + 1),
                  'loss_train: {:.4f}'.format(loss_train.item()),
                  'acc_train: {:.4f}'.format(acc_train.item()),
                  'loss_val: {:.4f}'.format(loss_val.item()),
                  'acc_val: {:.4f}'.format(acc_val.item()),
                  'time: {:.4f}s'.format(time.time() - t))
        torch.save(model, os.path.join(cfg.save_model_dir, 'latest.pth'))  # 模型保存

    def infer(self):
        # Create Test Processing
        all_data = Graph_Data_Loader()
        model_path = os.path.join(cfg.save_model_dir, 'latest.pth')
        model = torch.load(model_path, map_location=torch.device(cfg.device))
        model.eval()
        output = model(all_data.adj, all_data.features)
        loss_test = F.nll_loss(output[all_data.idx_test], all_data.labels[all_data.idx_test])
        acc_test = accuracy(output[all_data.idx_test], all_data.labels[all_data.idx_test])
        print("Test set results:",
              "loss= {:.4f}".format(loss_test.item()),
              "accuracy= {:.4f}".format(acc_test.item()))

7、运行结果 

得到的结果是对图神经网络模型在节点分类任务上的性能评价。在节点分类任务中,模型的目标是根据图结构和节点特征将节点分到不同的类别。因此,评价结果反映了模型对图中节点进行分类的准确性和泛化能力。

三、傅里叶变换

一、傅里叶变换是什么

在连续信号的分析中,傅里叶变换为人们深入理解和分析各种信号的性质提供了种强有力的手段。为了能进行定量数值的细致处理,可以通过抽样使原来连续分布的信号变成离散信号,并借助分析工具对离散信号进行分析。傅里叶变换建立在所处理的信号是平稳信号的假设基础上。一般来说,一个复杂的连续平稳信号总是可以分解为许多简单的正、余弦信号的叠加。变换的实质是将图像函数展开成具有不同空间频率的正、余弦函数的线性组合,即任何图像都可以分解为若干个频率不同的亮度呈正弦变化的图像之和。将空间域的图像数据变换到频率域后,能够对图像数据实施不同频率成分的提取。图像可以看成是一个平稳的随机场。因此我们可以将图像信号进行傅里叶变换,以便进行进一步的分析和处理.
尊里叶变换将图像灰度值形成的空间域与其频率域联系起来,它起到了不同外理域间桥梁的作用,并使得从事图像处理的分析者在解决某一问题时会在空间域和频率域来回切换
对频谱图像中的各种频率成分进行有针对性的分析和处理,可以实现有选择性的滤波处理。如针对平滑图像或边缘增强而进行低通滤波或者高通滤波等。

二、频域

从我们出生,我们看到的世界都以时间贯穿,股票的走势、人的身高、汽车的轨迹都会随着时间发生改变。这种以时间作为参照来观察动态世界的方法我们称其为时域分析。而我们也想当然的认为,世间万物都在随着时间不停的改变,并且永远不会静止下来。另一种方法来观察世界的话,你会发现世界是永恒不变的,这个静止的世界就叫做频域。

一段音乐是什么呢?

一个随着时间变化的震动。但我相信对于乐器小能手们来说,音乐更直观的理解是这样的:

将以上两图简化:

时域:

频域:

在时域,我们观察到钢琴的琴弦一会上一会下的摆动,就如同一支股票的走势;而在频域,只有那一个永恒的音符。

三、傅里叶级数

如果我说我能用前面说的正弦曲线波叠加出一个带90度角的矩形波来,如下图:

第一幅图是一个郁闷的正弦波cos(x)

第二幅图是2个卖萌的正弦波的叠加cos(x)+a.cos(3x)

第三幅图是4个发春的正弦波的叠加

第四幅图是10个便秘的正弦波的叠加

随着正弦波数量逐渐的增长,他们最终会叠加成一个标准的矩形,大家从中体会到了什么道理?

(只要努力,弯的都能掰直!)

随着叠加的递增,所有正弦波中上升的部分逐渐让原本缓慢增加的曲线不断变陡,而所有正弦波中下降的部分又抵消了上升到最高处时继续上升的部分使其变为水平线。一个矩形就这么叠加而成了。但是要多少个正弦波叠加起来才能形成一个标准90度角的矩形波呢?不幸的告诉大家,答案是无穷多个。不仅仅是矩形,你能想到的任何波形都是可以如此方法用正弦波叠加起来的

总结

这周学习的主要收获包括深入研读了《Inductive Representation Learning on Large Graphs》一文,对其中介绍的GraphSAGE方法有了更清晰的理解。通过实际代码实践,我对图神经网络(GNN)的应用和实现有了更深入的认识。另外,初步涉足了傅里叶变换领域,为进一步的学习打下了基础。

下周的学习计划包括继续深入图表示学习领域,探索更多先进的技术和方法。我还计划深入学习傅里叶变换,理解其在信号处理和频域分析中的应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值