来源:AAAI 2022
作者:张梦玟 北邮博士
石川老师团队
北邮和蚂蚁金服合作
一.文章部分
1.背景
图数据无处不在,例如社交网络、论文引用。图数据分为同质图和异质图,后者包含丰富的高阶的语义关系。
而图神经网络是现在的分析图数据的好的手段之一,本质上来讲就是逐层聚合邻居。
考虑到图神经网络应用的广泛性,则其安全性则变得愈加重要。
对抗攻击是挖掘模型漏洞,研究模型安全性、鲁棒性的重要手段。来源于计算机视觉。
攻击者设计一层肉眼不可见的扰动叠加进照片中,模型就很容易被它欺骗,现有的一些工作发现深度学习模型大概率会把蝴蝶识别为猫,原因可能是模型设计上存在一些漏洞,或者捕捉了与人识别过程中不同的特征。
扰动方式:增、删边,扰动结点特征、反转结点的标签。
现有的对抗工作主要是在同质图上进行,异质图非常脆弱,所以本文的价值便得以体现。
2.模型
Ps:在图中加入a3这个邻居
异质图神经网络普遍存在两个漏洞:
-
扰动放大效应
(1) 同质图:加入a3邻居之后,P4-P66要先融合到a3上(逐层融合),a3的嵌入再融合到P1上,但影响不会超过1/3。
(2) 异质图:因为模型经常是基于元路径直接聚合远距离的邻居,例如P4-P66,现有的很多工作往往不考虑转移的概率。结果就是P4-P66会占据主导地位,最后会使得p1被误分类为红色。 -
Soft Attention机制的问题
现有的异质图神经网络往往会设计一套Soft Attention机制,利用P1与它的邻居算特征相似度。但Soft Attention机制是让p1的每一个邻居对p1的影响都是正数,即注意力值都是大于0的,但P4-P66的注意力值应该为0才对。所以Soft Attention机制就没有起到过滤邻居的目的,不够鲁棒。
针对上述两个问题,设计了RoHe模型
(1) 首先给定一张异质图,现有的方法会抽出基于给定的不同元路径的邻居,例如PAP,PSP.
(2) 结点级聚合。对于不同的元路径做基于结点级的聚合。
(3) 语义级聚合。将(2)中所有结果再进行语义级的聚合。
防御就主要集中在结点级聚合部分。
选择confidence最高的Top T个邻居保留,剩下的都mask,目的是得到一个比较clean的attention,这p4-p66对比结果的影响就很小。
Node-level聚合具体流程
1.投影到同一个空间
2.计算特征的相似性
3. Transiting prior
在此处本文设计了一个“优先过渡”,思想类似与随机游走,例如图中从a1到p66的概率就会很小,从而p66对a1的影响就会减小,即解决了扰动放大问题。
4.Confidence Socre
把基于feature的相似性(e)与基于Transiting prior的相似性§结合起来,通过激活函数,为v结点的每个邻居u得到一个confidence score(固定了元路径)。
5.Purification mask(净化面罩)
设计了一种方法选择Top T个confidence score,即如果u不在前Top T个,其m的值就为负无穷,丢入softmax后其值就会无限趋近于0。
再把mask矩阵和原始的attention矩阵做一个结合得到pure attention。
6.最后再做结点的集合
语义级聚合
因为元路径具有多样性,所以要聚合不同的元路径。
3.实验部分
数据设置
基线方法(同质图防御模型,前三个)
Δ:扰动边的数目
对抗噪音
4.贡献
二.部分代码
import torch
import torch.nn as nn
import torch.nn.functional as F
import dgl
from dgl.nn.pytorch import GATConv
class SemanticAttention(nn.Module):
def __init__(self, in_size, hidden_size=128):
super(SemanticAttention, self).__init__()
self.project = nn.Sequential(
nn.Linear(in_size, hidden_size),
nn.Tanh(),
nn.Linear(hidden_size, 1, bias=False)
)
def forward(self, z):
w = self.project(z).mean(0) # (M, 1)
beta = torch.softmax(w, dim=0) # (M, 1)
beta = beta.expand((z.shape[0],) + beta.shape) # (N, M, 1)
return (beta * z).sum(1) # (N, D * K)
class HANLayer(nn.Module):
def __init__(self, meta_paths, in_size, out_size, layer_num_heads, dropout):
super(HANLayer, self).__init__()
# One GAT layer for each meta path based adjacency matrix
self.gat_layers = nn.ModuleList()
for i in range(len(meta_paths)):
self.gat_layers.append(GATConv(in_size, out_size, layer_num_heads,
dropout, dropout, activation=F.elu))
self.semantic_attention = SemanticAttention(in_size=out_size * layer_num_heads)
self.meta_paths = list(tuple(meta_path) for meta_path in meta_paths)
self._cached_graph = None
self._cached_coalesced_graph = {}
def forward(self, g, h):
semantic_embeddings = []
if self._cached_graph is None or self._cached_graph is not g:
self._cached_graph = g
self._cached_coalesced_graph.clear()
for meta_path in self.meta_paths:
self._cached_coalesced_graph[meta_path] = dgl.metapath_reachable_graph(
g, meta_path)
for i, meta_path in enumerate(self.meta_paths):
new_g = self._cached_coalesced_graph[meta_path]
semantic_embeddings.append(self.gat_layers[i](new_g, h).flatten(1))
semantic_embeddings = torch.stack(semantic_embeddings, dim=1) # (N, M, D * K)
return self.semantic_attention(semantic_embeddings) # (N, D * K)
class HAN(nn.Module):
def __init__(self, meta_paths, in_size, hidden_size, out_size, num_heads, dropout):
super(HAN, self).__init__()
self.layers = nn.ModuleList()
self.layers.append(HANLayer(meta_paths, in_size, hidden_size, num_heads[0], dropout))
for l in range(1, len(num_heads)):
self.layers.append(HANLayer(meta_paths, hidden_size * num_heads[l-1],
hidden_size, num_heads[l], dropout))
self.predict = nn.Linear(hidden_size * num_heads[-1], out_size)
def forward(self, g, h):
for gnn in self.layers:
h = gnn(g, h)
return self.predict(h)