【源码复现】图神经网络之PPNP/APPNH

本文介绍了《PREDICTTHENPROPAGATE》论文,提出了一种结合图神经网络和个性化PageRank的传播方案,以解决现有方法的局限,如仅依赖局部邻域信息和过平滑问题。该方案通过分离神经网络与信息传播,实现深层邻域信息的利用,且不会导致性能下降。APPNP和MLP模型在文中详细阐述并提供了DGL的实现示例。
摘要由CSDN通过智能技术生成

1、论文简介

2、论文核心介绍

2.1、现有方法局限

 现有的方法,仅仅使用了局部有限的邻域信息,更大的邻域信息并没有考虑到。例如,GCN,它采用平均的方法来聚合一阶邻域信息,通过堆叠多层来考虑到更高阶的邻域信息(论文中实际是两层);GAT则是采用注意力机制来学习不同邻域结点信息对当前结点的重要性,也就是说它是对周围邻域结点信息的加权平均。上述方法仍然是浅层的网络,并没有利用到深层邻域信息。
 现有方法的另外一个缺点就是过平滑现象(oversmoothing),这也是GCN不能堆叠多层的原因所在。另有作者,通过建立GCN和随机游走(random walk)的关系,发现当GCN的层数增加,GCN会收敛到随机游走的极限分布,会使不同类的结点之间变得不可分,导致GCN性能下降。
 为了解决上述的问题,作者提出了一个新的传播方案,这个方案的灵感来自于个性化PageRank算法,它平衡了局部邻域信息与更大的邻域信息的需要,允许更多的传播步骤而不会导致过平滑现象。此外,作者将神经网络从信息传播中分开来,允许去实现更大范围的传播而不用改变神经网络结构,由于这种特性,也可以将SOTA预测方法与文中的传播方案进行融合。

2.2、PageRank&Personalized PageRank

 PageRank算法通过网页链接重要性得分计算。重要性可认为是网页链接点击。PageRank算法给定一个概率值,定义为网页访问的概率。一般地, 1 N \frac{1}{N} N1 表示为每个网页节点初始化的概率, P R \rm{PR} PR也是一个初始化的概率值。PageRank 是一个迭代算法,因此 P R \rm{PR} PR 值初始化 1 N \frac{1}{N} N1 N N N表示为节点的数量。 P R \rm{PR} PR值的总和一般为1,当 P R {\rm{PR}} PR越大,说明重要性越大。
给定节点 v v v,求节点 v v v P R {\rm{PR}} PR值,
P R ( v ) = ∑ u ∈ N v P R ( u ) O ( u ) PR(v) = \sum_{u \in \mathcal{N}_v }\frac{PR(u)}{O(u)} PR(v)=uNvO(u)PR(u)
N v \mathcal{N}_v Nv表示所有链接到节点 v v v的集合。 O ( u ) O(u) O(u)表示节点 u u u的对外链接数。最早提出的PageRank算法存在着一些缺点,例如当一些节点存在自链接,或者是一些节点的出链节点形成循环圈时,PageRank在迭代过程中会出现 P R {\rm{PR}} PR持续增大,不会减小的情况。对于上述问题,PageRank算法被重新进行改进
P R ( v ) = α ∑ u ∈ N v P R ( u ) O ( u ) + ( 1 − α ) N \mathrm{PR(v)=}\alpha\sum_{\mathrm{u}\in\mathcal{N}_v}\frac{\mathrm{PR(u)}}{\mathrm{O(u)}}+\frac{(1-\alpha)}{\mathrm{N}} PR(v)=αuNvO(u)PR(u)+N(1α)
α \alpha α是一个超参数,取值一般为0.85。 α \alpha α表示节点跳转时的概率,不依据节点之间的链接进行跳转。
 PageRank算法衍生出的模型个性化的PageRank算法,主要利用图中节点的链接关系来迭代计算节点的权重。PageRank算法使用随机游走的策略来访问图中节点。PageRank算法与个性化Page Rank算法的区别在于随机游走时的跳转行为不同。个性化的PageRank算法对跳转行为进行约束,指定调转到的对外链接为特定的节点。例如在个性化排序时,用户只能跳转到一些特定的节点,这些节点表示用户偏好的那些节点。

PPR ′ ( v ) = α ∑ u ∈ N v P R ( u ) O ( u ) + ( 1 − α ) r v \text{PPR}^{'}(\mathrm{v})=\alpha\sum_{\mathrm{u}\in\mathcal{N}_v}\frac{\mathrm{PR(u)}}{\mathrm{O(u)}}+(1-\alpha)\mathrm{r}_\mathrm{v} PPR(v)=αuNvO(u)PR(u)+(1α)rv
r v = { 1   v = u 0   v ≠ u \mathrm r_\mathrm{v}=\begin{cases}1&\mathrm{~v=u}\\0&\mathrm{~v\neq u}\end{cases} rv={10 v=u v=u
个性化PageRank算法中,用户的偏好表示为 r ∣ v ∣ = 1 \mathrm r|\mathrm{v}| = 1 rv=1,原始的PageRank采用的计算方式为 Π p r = A r w Π p r \Pi_{pr} = A_{rw}\Pi_{pr} Πpr=ArwΠpr, Π p r 是 A r w \Pi_{pr}是A_{rw} ΠprArw的特征向量, A r w = A D − 1 A_{rw}=AD^{-1} Arw=AD1。类似的,个性化的PageRank 算法可以表示为

Π p p r ( i x ) = ( 1 − α ) A ~ Π p p r ( i x ) + α i x \Pi_{\mathrm{ppr}}(\mathbf{i_x})=(1-\alpha)\tilde{{A}}\Pi_{\mathrm{ppr}}(\mathbf{i_x})+\alpha\mathbf{i_x} Πppr(ix)=(1α)A~Πppr(ix)+αix
参考连接

2.3、PPNP&APPNP

 上一节,我们知道了Personalized PageRank算法及其他的表达式,对上式进行求解,求得 Π p p r \Pi_{\mathrm{ppr}} Πppr
Π p p r ( i x ) = α ( I n − ( 1 − α ) A ~ ) − 1 i x \Pi_{\mathrm{ppr}}(\mathbf{i_{x}})=\alpha(\mathbf{I_n}-(1-\alpha)\tilde{\mathbf{A}})^{-1}\mathbf{i_{x}} Πppr(ix)=α(In(1α)A~)1ix
其中, A ~ = D ~ − 1 2 A ^ D ~ − 1 2 , A ^ = A + I , i x 是传送向量 \tilde{A}=\tilde{D}^{-\frac{1}{2}}\hat{A}\tilde{D}^{-\frac{1}{2}},\hat{A} = A+I,\mathrm{i_x}是传送向量 A~=D~21A^D~21A^=A+Iix是传送向量。最终的PPNP算法公式表达如下:
Z p p n p = s o f t m a x ( α ( I n − ( 1 − α ) A ~ ) − 1 H ) Z_{\mathrm{ppnp}} = \mathrm{softmax}(\alpha(\mathbf{I_n}-(1-\alpha)\tilde{\mathbf{A}})^{-1}\mathbf{H}) Zppnp=softmax(α(In(1α)A~)1H)
H i , : = f θ ( X i , : ) \mathbf{H}_{i,:} = f_{\theta}(\mathbf{X}_{i,:}) Hi,:=fθ(Xi,:)
其中 X \mathbf{X} X是特征向量矩阵, f θ f_{\theta} fθ是具有参数集合 θ \theta θ的神经网络, H ∈ R n × c \mathbf{H} \in R^{n \times c} HRn×c
 由于在计算上式的时候,需要求矩阵的逆运算,这是一个耗时的操作,为了加速PPNP的训练速度,作者采用一种近似操作来求解,称为APPNP。
Z ( 0 ) = H = f θ ( X ) , Z ( k + 1 ) = ( 1 − α ) A ~ Z ( k ) + α H , Z ( K ) = s o f t m a x ( ( 1 − α ) A ~ Z ( K − 1 ) + α H ) Z^{(0)}=H=f_\theta(\mathbf{X}),\\ Z^{(k+1)} =(1-\alpha)\tilde{A}Z^{(k)}+\alpha H,\\ Z^{(K)}=\mathrm{softmax}((1-\alpha)\tilde{A}Z^{(K-1)}+\alpha H) Z(0)=H=fθ(X),Z(k+1)=(1α)A~Z(k)+αH,Z(K)=softmax((1α)A~Z(K1)+αH)
其中, K K K是迭代次数。作者也在后面的附录中也证明了APPNP当 K ⟶ ∞ K \longrightarrow \infty K时,收敛到PPNP,所以APPNP可以看作PPNP的迭代解。
模型的框架如下图所示:
在这里插入图片描述

3、源码复现

模型复现源码链接链接:点我点我 提取码:6666

3.1、模型总体框架

import torch
from torch.nn import Module
import torch.nn as nn
from torch.nn import functional as F
import numpy as np

class PPNP(nn.Module):
    def __init__(self,model,propagation):
        super(PPNP,self).__init__()
        self.model = model
        self.propagation = propagation
    def forward(self,feature,adj):
        #Generate Prediction
        #用于生成预测
        if self.model.__class__.__name__ =='MLP':
            output = self.model(feature)
        else:
            output = self.model(feature,adj)
        #通过个性化PageRank传播
        if self.propagation is not None:
            output = self.propagation(output)
        #返回最后一层的结果
        return F.log_softmax(output,dim=1)

3.2、PPNP

class PPNPExtract(Module):
    def __init__(self,alpha,adj,dropout):
        super(PPNPExtract,self).__init__()
        self.alpha = alpha
        self.adj = adj
        self.dropout = dropout
        pass
    def forward(self,H):
        inv = self.PPR()
        inv = F.dropout(inv,self.dropout,training=self.training)
        return self.alpha * torch.mm(inv,H) 
    def PPR(self):
        if isinstance(self.adj,torch.Tensor):
            ADJ = self.adj.to_dense().numpy()
        I_n = np.eye(self.adj.shape[0])
        M = I_n-(1-self.alpha)*ADJ
        inv_M = np.linalg.inv(M)
        return torch.Tensor(inv_M)

3.3、APPNP

class PowerIteration(Module):
    def __init__(self,adj,alpha,k,dropout):
        super(PowerIteration,self).__init__()
     
        self.adj = adj
        self.alpha = alpha
        self.k = k
        self.dropout = dropout
    def forward(self,H):
        Z = H
        for _ in range(self.k):
            Z = F.dropout(Z,self.dropout,training=self.training)
            Z = (1-self.alpha)*torch.mm(self.adj,Z) + self.alpha * H
        return Z

3.4、MLP(两层)

class MLP(Module):
    def __init__(self,input_dim,hid_dim,output_dim,dropout):
        super(MLP,self).__init__()
        self.input_dim = input_dim
        self.hid_dim = hid_dim
        self.output_dim = output_dim
        self.dropout = dropout
        self.layer1 = nn.Linear(input_dim,hid_dim,bias=False)
        self.layer2 = nn.Linear(hid_dim,output_dim,bias=False)

    def forward(self,X):
        X = F.dropout(X,self.dropout,training=self.training)
        X = self.layer1(X)
        X = F.relu(X)
        X = F.dropout(X,self.dropout,training=self.training)
        X = self.layer2(X)
        return X
    def __repr__(self) -> str:
        return self.__class__.__name__

四、DGL复现APPNP


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.nn.pytorch.conv import APPNPConv


class APPNPLayer(nn.Module):
    def __init__(self) -> None:
        super(APPNPLayer,self).__init__()
    def forward(self,g,feat):
        with g.local_scope():
            g = dgl.add_self_loop(g)
            degs = g.out_degrees().to(feat).clamp(min=1)
            norm = torch.pow(degs,-0.5)
            shp = norm.shape + (1,) * (feat.dim() -1 )
            norm = torch.reshape(norm,shp)
    
            feat = feat * norm
            g.ndata['z'] = feat
            g.update_all(fn.copy_u('z','m'),fn.sum('m','z'))
            feat = g.ndata.pop('z')
            feat = feat * norm
            return feat

class APPNP(nn.Module):
    def __init__(self,infeat,hidfeat,outfeat ,dropout,k,alpha) -> None:
        super(APPNP,self).__init__()
        self.dropout = dropout
        self.alpha = alpha
        self.layer1 = nn.Linear(infeat,hidfeat,bias=False)
        self.layer2 = nn.Linear(hidfeat,outfeat,bias=False)
        self.convs = nn.ModuleList()
        self.convs.append(self.layer1)
        self.convs.append(self.layer2)
        for _ in range(k):
            self.convs.append(APPNPLayer(alpha))        
        self.init_param()
    def init_param(self):
        self.convs[0].reset_parameters()
        self.convs[1].reset_parameters()
    def forward(self,x,g):
        x = F.dropout(x,self.dropout,training=self.training)
        x = F.relu(self.convs[0](x))
        x = F.dropout(x,self.dropout,training=self.training)
        x = F.relu(self.convs[1](x))
        x0 = x
        for layer in self.convs[2:]:
            x = layer(g,x)
            x = (1-self.alpha)*x + self.alpha *x0
        return F.log_softmax(x,dim=1)
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

鲸可落

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

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

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

打赏作者

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

抵扣说明:

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

余额充值