How Powerful are Spectral Graph Neural Networks?

Wang X. and Zhang M. How powerful are spectral graph neural networks? ICML, 2022.

分析谱图网络的表达能力.

符号说明

  • κ(M)=λmaxλmin, 矩阵 M 的条件数;
  • V={1,2,,n}, node set;
  • EV×V, edge set;
  • XRn×d, node feature matrix;
  • G=(V,E,X), graph;
  • N(i) 结点 i 的一阶邻居;
  • ARn×n 邻接矩阵;
  • D, degree matrix;
  • A^=D1/2AD1/2, normalized adjacency matrix;
  • L^=IA^, normalized Laplacian matrix;
  • L^=UΛUT, 特征分解.
  • X~=UTX, 为信号 X 的 graph Fourier transform, 并有逆变换:

    X=UX~.

Spectral GNN

  • 我们知道, Spectral GNN 的核心是 spectral filter

    (1)Ug(Λ)UTX,

    其中 g:[0,2]R 多为 element-wise 的多项式算子 (注, αk 可以是超参数, 也可以是可学习的参数):

    g(λ):=k=0Kαkλk.

    此时, (1) 可以等价的写为:

    Ug(Λ)UTX=k=0KαkL^kX.

    大部分的 Spectral GNN 通过引入 MLP 和非线性激活函数, 可表达为:

    Z=ϕ(g(L^)φ(X)).

  • 下面是一些常见的 g().

  • 下面我们主要分析 linear GNN

    (2)Z=g(L^)XWRd×d

    的能力. 其中 X 是固定的, WRd×d 是可学习的参数.

  • 注意到,

    (3)Z=g(L^)XW=Ug(Λ)X~W.

  • Theorem 4.1: 对于任意一维信号 zRn, 我们均可以通过 linear GNN (2) 逼近, 若 L^ 不存在重复的特征值, 且 X 包含所有频段的信号 (即不存在 X~ 有一行均为 0).


proof:

  • 我们要证明的是, 对于任意的一维信号 zRd 存在 wRdg() 使得

    Ug(Λ)X~w=zg(Λ)X~w=UTz.

  • g(Λ) 是个对角矩阵, 根据多项式的性质, 它有着极为强大的拟合能力, 以

    g(λ)=k=0n1θkλk

    为例, 此时我们有:

    (4.1.1)g(Λ)=diag(BΘ),

    其中

    B=[λij1]ij,Θ=[θ0,,θn1].

    容易发现, BRn×n 为范德蒙德矩阵, 当 λiλjij 成立的时候, B 可逆. 故

    v=BΘ,v,Θ.

    现在我们把 (4.1.1) 拆解开来, 我们只需要证明:

    vi(X~iTw)=zi,i,

    即可, 倘若 X~iTw0i, 上面的证明就自然成立了. 对于任意 X~ 满足任一行均不全为 0, 我们都能找到这样的 w. 令 Vi 为下列方程的解空间

    X~iw=0.

    容易发现 Vi 的维度为 n1. 只需取

    wRni=1nVi,

    后者不为空集.


  • 很遗憾的是, 这个结论不能推广到多维信号的逼近上. 严格来说, 倘若 X 不是满秩的, 则对于任意的 d>1, 存在 d- 维的信号 Z 不能通过 linear gnn 完全逼近.

  • 故, 倘若想要拟合不同的信号, 最好的办法是应用多个独立的 filters.

  • 此外, 对于条件: X 包含所有频段的信号, 可以这么理解:

    • filter g() 的作用实际上是对信号的缩放, 所以倘若 X 本身不包含那个频段的信息, 自然无法拟合对应频段的信号.
  • 需要一提的是, 倘若我们添加激活函数 σ(X), 某种程度上是能够缓解频段缺失的问题的.

Choice of Basis for Polynomial Filters

  • linear GNN 可以表述为如下的一般形式:

    (7)Z=k=0Kαkgk(L^)XW

    其中 gk 代表多项式基.

  • 这里我们考虑如下的几种:

    • [Monomial]:

      gk(λ)=λk.

    • [Chebyshev]:

      g0(λ)=1,g1(λ)=λ,gn+1(λ)=2λgn(λ)gn1(λ).

    • [Bernstein]:

      gk(λ)=gk,K(λ):=(Kk)λk(1λ)Kk.

    • [Jacobi]:

      g0a,b(λ)=1,g1a,b(λ)=ab2+a+b+22λ,gka,b(λ)=(θkλ+θk)gk1a,b(λ)θkgk2a,b(λ),

      其中

      θk=(2k+a+b)(2k+a+b1)2k(k+a+b),θk=(2k+a+b1)(a2b2)2k(k+a+b)(2k+a+b2),θk=(k+a1)(k+b1)(2k+a+b)k(k+a+b)(2k+a+b2).

  • 下面是作者给的一些例子:

  • 下面是自己做的一些考察:



# %%

from typing import List
import torch
from freeplot import FreePlot


# %%


def mul(x, y):
    return x * y

def power_conv(
    zs: List[torch.Tensor], x, l: int
):
    if l == 0:
        return zs[0]

    assert len(zs) == l, "len(zs) != l for l != 0"

    return mul(x, zs[-1])

def legendre_conv(
    zs: List[torch.Tensor], x, l: int
):
    if l == 0:
        return zs[0]

    assert len(zs) == l, "len(zs) != l for l != 0"

    if l == 1:    
        return mul(x, zs[-1])
    else:
        part1 = (2 - 1 / l) * mul(x, zs[-1])
        part2 = (1 - 1 / l) * zs[-2]
        return part1 - part2

def chebyshev_conv(
    zs: List[torch.Tensor], x, l: int
):
    if l == 0:
        return zs[0]

    assert len(zs) == l, "len(zs) != l for l != 0"

    if l == 1:
        return mul(x, zs[-1])
    else:
        part1 = 2 * mul(x, zs[-1])
        part2 = zs[-2]
        return part1 - part2

def jacobi_conv(
    zs: List[torch.Tensor], x, l: int, 
    alpha: float = 1., beta: float = 1.
):
    if l == 0:
        return zs[0]

    assert len(zs) == l, "len(zs) != l for l != 0"

    if l == 1:
        c = (alpha - beta) / 2
        return c * zs[-1] + (alpha + beta + 2) / 2 * mul(x, zs[-1])
    else:
        c0 = 2 * l \
                * (l + alpha + beta) \
                * (2 * l + alpha + beta - 2)
        c1 = (2 * l + alpha + beta - 1) \
                * (alpha ** 2 - beta ** 2)
        c2 = (2 * l + alpha + beta - 1) \
                * (2 * l + alpha + beta) \
                * (2 * l + alpha + beta - 2)
        c3 = 2 * (l + alpha - 1) \
                * (l + beta - 1) \
                * (2 * l + alpha + beta)
        
        part1 = c1 * zs[-1]
        part2 = c2 * mul(x, zs[-1])
        part3 = c3 * zs[-2]

        return (part1 + part2 - part3) / c0

def run(x, conv_fn, L):
    zs = [torch.ones_like(x)]
    for l in range(1, L + 1):
        zs.append(conv_fn(zs, x, l))
    return zs

# %%

from functools import partial

L = 5
x = torch.linspace(-1, 1, 100)
alpha = 1
beta = 1

fp = FreePlot(
    (1, 4),
    titles=("Monomial", "Legendre", "Chebyshev", "Jacobi"),
    sharey=False
)

for l, z in enumerate(run(x, power_conv, L)[1:], start=1):
    fp.lineplot(x, z, label=f"Layer: {l}", index=(0, 0), marker='')

for l, z in enumerate(run(x, legendre_conv, L)[1:], start=1):
    fp.lineplot(x, z, label=f"Layer: {l}", index=(0, 1), marker='')

for l, z in enumerate(run(x, chebyshev_conv, L)[1:], start=1):
    fp.lineplot(x, z, label=f"Layer: {l}", index=(0, 2), marker='')

jacobi_conv_ = partial(jacobi_conv, alpha=alpha, beta=beta)
for l, z in enumerate(run(x, jacobi_conv_, L)[1:], start=1):
    fp.lineplot(x, z, label=f"Layer: {l}", index=(0, 3), marker='')

fp.legend(0.2, 0.99, ncol=L)
fp.set_title()
fp.show()

  • 对于 Jacobi 多项式基, 我们可以调节 α,β 来调节对高频低频的一个侧重度:

  • 作者证明了, 在特殊的情况下 (也不算特别特殊), Jacobi 多项式基的系数 αk 相较于其它的多项式基的系数更容易收敛.

JacobiConv

  • 一般来说, αk 随着 k 增加是逐渐减小的 (绝对值), 故而直接学习可能得不到想要的结果. 所以作者把它们重参数化为:

    αk=βki=1kγi,

    其中 γi=γtanh(ηi) 用来保证:

    γi[γ,γ].

  • 最终的公式可以改写为:

    Pka,b(A^)X^=γkθkA^Pk1a,b(A^)X^+γkθkPk1a,b(A^)X^γkγk1θkPk2a,b(A^)X^.

代码

[official]

posted @   馒头and花卷  阅读(117)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示