【源码复现】图神经网络之vanilla-GCN

一、论文简介

二、论文核心简述

 本文作者从谱图卷积的角度出发,首先提出了在傅里叶域上对于图上信号 x x x的图卷积操作,然而传统的图卷积操作,采用矩阵乘积的形式,计算量大,而且,对于庞大的图结构,计算图拉普拉斯矩阵的特征值分解,计算复杂度大。为了避免巨大的计算复杂度开销,作者采用一阶切比雪夫多项式来巧妙地近似,并采用重整正则化的技巧,得到最后的分层结构的图卷积神经网络,GCN的分层传播规则如下:

H ( l + 1 ) = σ ( A ^ H ( l ) W ( l ) ) H^{(l+1)} = \sigma(\hat AH^{(l)}W^{(l)}) H(l+1)=σ(A^H(l)W(l))
其中 A ^ = D ~ − 1 2 A ~ D ~ − 1 2 \hat A= \tilde D^{-\frac{1}{2}} \tilde A \tilde D^{-\frac{1}{2}} A^=D~21A~D~21 A ~ \tilde A A~为带有自环的邻接矩阵,即 A ~ = A + I \tilde A =A+I A~=A+I D ~ \tilde D D~ A ~ \tilde A A~的度对角矩阵; W ( l ) W^{(l)} W(l)是第 l l l层可学习的线性变换矩阵; H ( l ) H^{(l)} H(l)是第 l l l层的输出结果, H ( 0 ) = X H^{(0)}=X H(0)=X σ ( . ) \sigma(.) σ(.)为非线性激活函数 R e L U ReLU ReLU
 在GCN的每个图卷积层,结点特征主要按照以下三种方式更新:

  • 特征传播
  • 线性变换
  • 非线性激活

特征传播:每个结点的特征来自当前结点特征和自己邻域结点特征加权之和,类似于一种局部平滑操作。
线性变化和非线性激活:类似于传统的MLP,对于平滑后的矩阵进行线性变化和非线性激活操作。
 最后一层的结点分类采用 s o f t m a x softmax softmax分类器。最终得到的GCN模型如下:
Z = f ( X , A ) = s o f t m a x ( A ^    R e L U ( A ^ X W ( 0 ) ) W ( 1 ) ) Z=f(X,A)=softmax(\hat A \; ReLU(\hat AXW^{(0)})W^{(1)}) Z=f(X,A)=softmax(A^ReLU(A^XW(0))W(1))

三、源码

1、utils.py

import pandas as pd
import numpy as np
import torch

def load_data(filename = 'cora'):

    dataset = pd.read_csv('./data/{}.content'.format(filename),sep='\t', header=None, low_memory=False)
    #结点数量
    node_num = dataset.shape[0]
    index_list = list(dataset.index)
    #数据集中
    id_list = list(dataset[0])

    #建立论文编号到重新索引的字典
    id_to_index = dict(zip(id_list,index_list))

    #从第一列到倒数第二列定义为结点的特征向量
    #data.iloc  取某行某列数据
    feature = np.array(dataset.iloc[:,1:-1])

    str_label = np.array(dataset.iloc[:,-1])
    st = set()
    for str in str_label:
        st.add(str)
    #将字符str标签转化为[0~6]标签
    label_to_num = dict(zip(st,range(len(st))))
    #0~6
    labels = [label_to_num[i] for i in str_label]

    #读取结点间的引用关系
    data_cites = pd.read_csv(f'./data/{filename}.cites', sep='\t', header=None)
    #首先创建零矩阵
    adj_matrix = np.zeros((node_num,node_num))
    #构建邻接矩阵
    for node,cite_node in zip(data_cites[0],data_cites[1]):
        node_idx,cite_node_idx = id_to_index[node],id_to_index[cite_node]
        adj_matrix[node_idx][cite_node_idx] = adj_matrix[cite_node_idx][node_idx] =1

    #正则化
    adj_matrix = normlization(adj_matrix)
    #print(adj_matrix)

    #转化为torch可操作的类型
    adj_matrix = torch.FloatTensor(adj_matrix)
    feature = torch.FloatTensor(feature)
    labels = torch.LongTensor(labels)

    return feature,labels , adj_matrix
#对邻接矩阵重整正则化
def normlization(adj):
    adj = adj + np.eye(adj.shape[0])
    #对每一行求和并拉平
    row_sum = adj.sum(1).flatten()
    #对每个元素求a**(-0.5)
    D_inv_sqrt = np.power(row_sum,-0.5)
    #将无穷值转化为0
    D_inv_sqrt[np.isinf(D_inv_sqrt)] = 0
    #转换为对角矩阵
    D_mat_inv_sqrt =np.diag(D_inv_sqrt)
    #D~^(-0.5)*A~*D~(-0.5)
    return D_mat_inv_sqrt.dot(adj).dot(D_mat_inv_sqrt)
#计算ACC
def accuracy(output, labels):
    
    preds = output.max(1)[1].type_as(labels)
    correct = preds.eq(labels).double()
    correct = correct.sum()
    return correct / len(labels)

2、model.py

import torch 
from torch import nn
from torch.nn import Module
from torch.nn import Parameter


class GraphConvolution(Module):
    def __init__(self,input_dim,output_dim):
        super(GraphConvolution,self).__init__()
        self.input_feature = input_dim
        self.output_feature = output_dim
    
        self.weight = Parameter(torch.empty(size=(input_dim,output_dim)))
        self.init_param()
	def init_param(self):
		nn.init.xavier_uniform_(self.weight.data,gain=1.414)
    def forward(self,feature,adj):
    	#特征传播
        output = torch.mm(adj,feature)
        #线性变化
        output = torch.mm(output,self.weight)
        return output
class GCN(Module):
    def __init__(self,input_dim,hid_dim,output_dim,dropout):
        super(GCN,self).__init__()
        self.layer1 = GraphConvolution(input_dim,hid_dim)
        self.layer2 = GraphConvolution(hid_dim,output_dim)
        self.dropout = dropout

    def forward(self,feature,adj):
    	#第一层
        output = self.layer1(feature,adj)
        #非线性激活
        output = F.relu_(output)
        #output = F.relu(self.layer1(feature,adj))
        #dropout
        if self.dropout is not None:
            output = F.dropout(output,self.dropout,self.training)
        #第二层
        output = self.layer2(output,adj)
        #预测结果
        return F.log_softmax(output,dim=1)

3、train.py

import torch
import os
from torch.optim import Adam
from torch.nn import functional as F
import numpy as np
from model import GCN
from utils import load_data,accuracy
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'

#超参数配置
epochs = 100
lr = 0.01
hid_dim = 16#32
weight_decay = 5e-4
dropout = 0.5
#device ='cpu'#'cuda' if torch.cuda.is_available() else 'cpu'
dataset_name = 'cora'
features,labels , adj_matrix = load_data(dataset_name)
input_dim = features.shape[1]
output_dim = 7
#加载模型
model = GCN(input_dim,hid_dim,output_dim,dropout)
#数据集划分
idx_train = range(140)
idx_valid = range(300,300+500)
idx_test = range(1708,2708)
idx_train = torch.LongTensor(idx_train)
idx_valid = torch.LongTensor(idx_valid)
idx_test = torch.LongTensor(idx_test)
#优化器
opt = Adam(model.parameters(),lr=lr,weight_decay=weight_decay)
def train(model,opt,epochs,best_acc):
    model.train()
    for epoch in range(epochs):
        output = model(features,adj_matrix)
        '''
        F.nll_loss计算方式是下式,在函数内部不含有提前使用softmax转化的部分;
        nn.CrossEntropyLoss内部先将输出使用softmax方式转化为概率的形式,后使用F.nll_loss函数计算交叉熵。
        '''
        opt.zero_grad()
        loss_train = F.nll_loss(output[idx_train],labels[idx_train])
        acc_train = accuracy(output[idx_train],labels[idx_train])
        loss_train.backward()
        opt.step()
        loss_val = F.nll_loss(output[idx_valid],labels[idx_valid])
        acc_val = accuracy(output[idx_valid],labels[idx_valid])
        
        print(f"Epoch {epoch}, train loss = {loss_train:.4f},train accuracy = {acc_train:.4f}")
        print(f"valid loss = {loss_val:.4f},valid accuracy = {acc_val:.4f}")   
def test(model,features,adj):
    model.eval()
    model.to(device)
    features.to(device)
    adj.to(device)
    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(f"test loss = {loss_test:.4f},test accuracy = {acc_test:.4f}")
train(model,opt,epochs)
test(model,features,adj_matrix)

4、DGL复现

这里只列出了模型

import os
os.environ["DGLBACKEND"] = "pytorch"
import dgl
import dgl.function as fn
import torch 
import torch.nn as nn
import torch.nn.functional as F
from dgl import DGLGraph

class GCNLayer(nn.Module):
    def __init__(self,infeat,outfeat) -> None:
        super(GCNLayer,self).__init__()
        self.layer = nn.Linear(infeat,outfeat,bias=False)
        self._in_feats = infeat
        self._out_feats = outfeat
        self.weight = nn.Parameter(torch.Tensor(infeat, outfeat))
        nn.init.xavier_uniform_(self.weight)
    def forward(self,g,feature):
        with g.local_scope():
            g = dgl.add_self_loop(g)
            feat_src,feat_dst = dgl.utils.expand_as_pair(feature, g)
            degs = g.out_degrees().to(feat_src).clamp(min=1)
            norm = torch.pow(degs,-0.5)
            shp = norm.shape + (1,) * (feat_src.dim() -1 )
            norm = torch.reshape(norm,shp)

            feat_src = feat_src * norm
            feat_src = torch.matmul(feat_src, self.weight)
            g.srcdata["u"] = feat_src
            g.dstdata['v'] = feat_dst
            g.update_all(
                fn.copy_u("u", "m"), 
                fn.sum(msg="m", out="h")
                )
            rst = g.ndata['h']

            degs = g.in_degrees().to(feat_dst).clamp(min=1)
            norm = torch.pow(degs, -0.5)
            shp = norm.shape + (1,) * (feat_dst.dim() - 1)
            norm = torch.reshape(norm, shp)
            rst = rst * norm

            return rst

class GCN(nn.Module):
    def __init__(self,infeat,hidfeat,outfeat ,dropout) -> None:
        super(GCN,self).__init__()
        self.dropout = dropout
        self.layer1 = GCNLayer(infeat,hidfeat)
        self.layer2 = GCNLayer(hidfeat,outfeat)
    
    def forward(self,g,features):
        x = F.dropout(features,self.dropout,training=self.training)
        x = F.relu(self.layer1(g,x))
        #print(x.shape)
        x = F.dropout(x,self.dropout,training=self.training)
        x = self.layer2(g,x)
        return F.log_softmax(x,dim=1)
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鲸可落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值