AI智能
改变未来

图神经网络学习笔记


图神经网络

图(graph)是一种数据结构,常见的图结构由节点(node)和边(edge)构成,节点包含了实体(entity)信息,边包含实体间的关系(relation)信息。图神经网络(Graph Neural Network)是深度学习在图结构数据上的一些模型、方法和应用。GNN主要分为图卷积网络(GCN)、基于注意力更新的图网络(GAT)、基于门控的更新的图网络、具有跳边的图网络。

CNN是GNN起源的首要动机。CNN有能力去抽取多尺度局部空间信息,并将其融合起来构建特征表示。GNN和CNN有三个共同的特点:局部连接、权值共享、多层网络。其中,局部连接是图的最基本的表现形式;权值共享可以减少网络的计算量;多层结构可以让网络捕获不同的特征。

首先,像CNN和RNN这样的标准神经网络无法正确处理图形输入,GNN分别在每个节点上传播,忽略了节点的输入顺序。换句话说,GNN的输出对于节点的输入顺序是不变的。

其次,图中的边表示两个节点之间的依赖关系的信息。在标准神经网络中,依赖信息仅被视为节点的特征。但是,GNN可以通过图形结构进行传播,而不是将其用作要素的一部分。

第三,推理是高级人工智能的一个非常重要的研究课题,标准神经网络无法从大型实验数据中学习推理图。然而,GNN探索从场景图片和故事文档等非结构性数据生成图形,这可以成为进一步高级AI的强大神经模型。

GCN

GCN实际上跟CNN的作用一样,就是一个特征提取器,只不过它的对象是图数据。GCN精妙地设计了一种从图数据中提取特征的方法,从而让我们可以使用这些特征去对图数据进行节点分类、图分类、边预测,还可以顺便得到图的嵌入表示。


假设有一批图数据,其中有N个节点,每个节点都有自己的特征,设这些节点的特征组成一个N×D维的矩阵X,然后各个节点之间的关系也会形成一个N×N维的矩阵A,也称为邻接矩阵。X和A便是模型的输入。GCN层与层之间的传播方式为:
Hl+1=σ(D~−1/2A~D~−1/2H(l)W(l))H^{l+1}=\\sigma (\\widetilde D^{-1/2} \\widetilde A\\widetilde D^{-1/2}H^{(l)}W^{(l)})Hl+1=σ(D−1/2AD−1/2H(l)W(l))
其中A~=A+I\\widetilde A=A+IA=A+I,I是单位矩阵。D~\\widetilde DD是A~\\widetilde AA的度矩阵,D~\\widetilde DD为对角矩阵,对角线上的元素值为该节点的度+1。H是每一层的特征,对于输入层的话,H就是X。WiW^iWi是第i层的权重矩阵,权重矩阵第二个维度的大小决定了下一层的特征数,核心公式有几层,最终就会训练出几个参数矩阵W。σ是非线性激活函数,例如sigmoid、relu等。

GCN采用softmax函数进行分类,softmax函数如下:
pi=eai∑k=1Ceakp_i=\\frac{e^{a_i}}{\\sum_{k=1}{C}e^{a_k}}pi​=∑k=1​Ceak​eai​​

其中,a1、a2……ac是Z中某个节点的特征,pi为该节点属于类别i的概率。通过上式可以得到该节点在每个label下的概率。

综上所述, 可以通过将核心公式计算出来的ZN∗CZ^{N*C}ZN∗C ,用在softmax函数上,实现节点的分类预测。

GAT

由于GCN有两个缺点:1. 这个模型对于同阶的邻域上分配给不同的邻居的权重是完全相同的,无法允许为邻居中的不同节点指定不同的权重。这一点限制了模型对于空间信息的相关性的捕捉能力,这也是在很多任务上不如GAT的根本原因。2. GCN结合临近节点特征的方式和图的结构依依相关,这局限了训练所得模型在其他图结构上的泛化能力。

GAT提出了用注意力机制对邻近节点特征加权求和。 邻近节点特征的权重完全取决于节点特征,独立于图结构。GAT和GCN的核心区别在于如何收集并累和距离为1的邻居节点的特征表示。 GAT用注意力机制替代了GCN中固定的标准化操作。本质上,GAT只是将原本GCN的标准化函数替换为使用注意力权重的邻居节点特征聚合函数。

GAN的输入是一个节点特征向量集,其中N为节点个数,F为节点特征的个数。矩阵h的大小是N×F,代表了所有节点的特征,而R只代表了某一个节点的特征,所以它的大小为F×1,输出同理。
h={h1⃗,h2⃗,…,hN⃗}h=\\{\\vec{h_1},\\vec{h_2},…,\\vec{h_N}\\}h={h1​​,h2​​,…,hN​​}
为了得到相应的输入与输出的转换,需要根据输入的特征至少进行一次线性变换得到输出的特征,所以需要对所有节点训练一个权重矩阵W,这个权重矩阵就是输入的F个特征与输出的F′个特征之间的关系。针对每个节点实行自注意力机制,注意力系数为:
eij=a(Whi⃗,Whj⃗)e_{ij}=a(W\\vec{h_i}, W\\vec{h_j})eij​=a(Whi​​,Whj​​)
通过上述运算得到了正则化后的不同节点之间的注意力系数,可以用来预测每个节点的输出特征:
h⃗i′=σ(∑αijWh⃗j)\\vec h\’_i=\\sigma (\\sum \\alpha _{ij}W\\vec h_j)hi′​=σ(∑αij​Whj​)
W为与特征相乘的权重矩阵,α为前面计算得到的注意力互相关系数,σ为非线性激活函数,j表示所有与i相邻的节点。这个公式表示就是:该节点的输出特征与和它相邻的所有节点有关,是它们的线性和的非线性激活后得到的。

实验

利用GCN模型实现分类任务。首先导入数据并进行处理

import numpy as npimport scipy.sparse as spimport torchdef encode_onehot(labels):classes = set(labels)classes_dict = {c: np.identity(len(classes))[i, :] for i, c inenumerate(classes)}labels_onehot = np.array(list(map(classes_dict.get, labels)),dtype=np.int32)return labels_onehotdef load_data(path=\"\", dataset=\"cora\"):\"\"\"Load citation network dataset (cora only for now)\"\"\"print(\'Loading {} dataset...\'.format(dataset))idx_features_labels = np.genfromtxt(\"{}{}.content\".format(path, dataset),dtype=np.dtype(str))features = sp.csr_matrix(idx_features_labels[:, 1:-1], dtype=np.float32)#print(idx_features_labels.shape)print(type(features))labels = encode_onehot(idx_features_labels[:, -1])print(labels.shape)# build graphidx = np.array(idx_features_labels[:, 0], dtype=np.int32)idx_map = {j: i for i, j in enumerate(idx)}edges_unordered = np.genfromtxt(\"{}{}.cites\".format(path, dataset),dtype=np.int32)print(edges_unordered)edges = np.array(list(map(idx_map.get, edges_unordered.flatten())),dtype=np.int32).reshape(edges_unordered.shape)print(edges.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 matrixadj = adj + adj.T.multiply(adj.T > adj) - adj.multiply(adj.T > adj)features = normalize(features)adj = 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 = 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_testdef normalize(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 mxdef sparse_mx_to_torch_sparse_tensor(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)adj, features, labels, idx_train, idx_val, idx_test = load_data()

模型的训练及测试

from models import GCNfrom __future__ import divisionfrom __future__ import print_functionimport timeimport argparseimport numpy as npimport torchimport torch.nn.functional as Fimport torch.optim as optimadj, features, labels, idx_train, idx_val, idx_test = load_data()# Model and optimizermodel = GCN(nfeat=features.shape[1],nhid=16,nclass=labels.max().item() + 1,dropout=0.5)lr = 0.005weight_decay = 5e-4optimizer = optim.Adam(model.parameters(),lr=lr, weight_decay=weight_decay)def train(epoch):t = time.time()model.train()optimizer.zero_grad()output = model(features, adj)loss_train = F.nll_loss(output[idx_train], labels[idx_train])acc_train = accuracy(output[idx_train], labels[idx_train])loss_train.backward()optimizer.step()fastmode = Falseif not fastmode:model.eval()output = model(features, adj)loss_val = F.nll_loss(output[idx_val], labels[idx_val])acc_val = accuracy(output[idx_val], labels[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))def test():model.eval()output = model(features, adj)loss_test = F.nll_loss(output[idx_test], labels[idx_test])acc_test = accuracy(output[idx_test], labels[idx_test])print(\"Test set results:\",\"loss= {:.4f}\".format(loss_test.item()),\"accuracy= {:.4f}\".format(acc_test.item()))# Train modelt_total = time.time()epochs = 150for epoch in range(epochs):train(epoch)print(\"Optimization Finished!\")print(\"Total time elapsed: {:.4f}s\".format(time.time() - t_total))# Testingtest()

最终实验结果截图如下所示

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » 图神经网络学习笔记