异构图
包含不同类型节点和链接的异构图
异构图的定义:节点类别数量和边的类别数量加起来大于2就叫异构图。
meta-path元路径的定义:连接两个对象的复合关系,比如,节点类型A和节点类型B,A-B-A和B-A-B都是一种元路径。
meta-path下的邻居节点的定义:如下图所示。
其中m1-a1-m2,m1-a3-m3都是一种meta-path,所以m1的邻居有m2、m3以及本身m1
节点级别的attention和语义级别的attention
节点级别:简单来说就是单种meta-path求得节点embeddings,比如对于M-D-M,Terminator2的embeddings通过M-D-M的元路径即可求的另一个M(Termintor)的embeddings。
语义级别:对于Terminator的embeddings不再是根据一种meta-path进行获取,而是根据两种meta-path进行权重的分配相加得到。
节点级别:
举例子:
如上图所示,对于异构图,一种meta-path为蓝-黄-蓝,对于节点x1-xa-x2,所以x1与x2通过meta-path元路径,同理每一对节点,构成上图中的第二个图的连接方式。
对于节点x1,与节点x2、x3、x6相连,所以x2、x3、x6都是节点x1的邻居节点,也就是公式2。
对于公式三,分子将i和j节点拼接在一起以后乘以一个可学习的参数然后再通过激活函数,再通过exp。分母就是他的邻居节点的。
对后求的节点级别下的embeddings。
语义级别:
简单来说语义级别就是多种meta-path呗,只需要把每种meta-path下面的求出来进行加权就可以了。
如上图所示,通过节点级别的求解方法,求出来对于每一种metapath下面的embeddings,然后最后进行加权求和。
知道了上面的HAN的原理,下面讲解一下model代码。
在讲解原理的时候分为语义级别和节点级别,在代码的时候会分为给定已经处理好的邻接矩阵和直接输入异构图。
异构图直接输入(异构图模型。):
需要将meta-path转化为邻接矩阵即元组形式。
实现了Heterogeneous Graph Attention Network(HAN)模型,用于处理异构图数据。HAN是一种深度学习模型,用于在异构图中进行节点分类任务
import torch
import torch.nn as nn
import torch.nn.functional as Ffrom dgl.nn.pytorch import GATConv
首先,导入了PyTorch库以及用于图神经网络的相关模块。
class SemanticAttention(nn.Module):def __init__(self, in_size, hidden_size=128):super(SemanticAttention, self).__init__()# input:[Node, metapath, in_size]; output:[None, metapath, 1]; 所有节点在每个meta-path上的重要性值self.project = nn.Sequential(nn.Linear(in_size, hidden_size),nn.Tanh(),nn.Linear(hidden_size, 1, bias=False))
这里定义了一个名为SemanticAttention
的PyTorch模型类,它用于计算每个节点在不同元路径(metapath)上的重要性。SemanticAttention
类有以下成员:
__init__
方法:初始化模型。它接受输入特征的维度in_size
以及可选的隐藏层维度hidden_size
。在初始化过程中,它创建了一个神经网络模块self.project
,该模块包括两个线性层和一个Tanh激活函数,最后一个线性层没有偏差。
def forward(self, z):w = self.project(z).mean(0)#每个节点在metapath维度的均值; mean(0): 每个meta-path上的均值(/|V|); (MetaPath, 1)beta = torch.softmax(w, dim=0) # 归一化 # (M, 1)beta = beta.expand((z.shape[0],) + beta.shape) # 拓展到N个节点上的metapath的值 (N, M, 1)return (beta * z).sum(1)#(beta*z)=>所有节点,在metapath上的attention值;(beta*z).sum(1)=>节点最终的值(N,D*K)
forward
方法:用于计算每个节点在不同元路径上的重要性。首先,将输入特征z
通过self.project
模块传递,然后计算每个元路径上的重要性均值w
。接着,使用softmax函数对这些均值进行归一化,以获得每个元路径上的注意力权重beta
。最后,将注意力权重与输入特征相乘,并对所有元路径求和,得到最终的节点表示。
这个SemanticAttention
模块的目的是计算每个节点在不同元路径上的权重,以便后续的元路径级别的注意力聚合。
接下来,定义了另一个模型类HANLayer
:
class HANLayer(nn.Module):def __init__(self, num_meta_paths, in_size, out_size, layer_num_heads, dropout):super(HANLayer, self).__init__()self.gat_layers = nn.ModuleList()for i in range(num_meta_paths): # meta-path Layers; 两个meta-path的维度是一致的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) # 语义attention; out-size*layersself.num_meta_paths = num_meta_paths
HANLayer
类代表了HAN模型中的一个层次。每个HANLayer
层包括以下成员:
__init__
方法:初始化层。它接受以下参数:num_meta_paths
:元路径的数量。in_size
:输入特征的维度。out_size
:输出特征的维度。layer_num_heads
:每个GAT层中的注意力头的数量。dropout
:用于正则化的dropout率。
在初始化过程中,它首先创建了多个GATConv层,每个GATConv层对应一个元路径,这些层将用于图注意力聚合。然后,创建了一个SemanticAttention
模块,用于计算每个节点在不同元路径上的语义级别的注意力。
接下来,定义了整个HAN模型类HAN
:
class HAN(nn.Module):def __init__(self, num_meta_paths, in_size, hidden_size, out_size, num_heads, dropout):super(HAN, self).__init__()self.layers = nn.ModuleList()self.layers.append(HANLayer(num_meta_paths, in_size, hidden_size, num_heads[0], dropout)) # meta-path数量 + semantic_attentionfor l in range(1, len(num_heads)): # 多层多头,目前是没有self.layers.append(HANLayer(num_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) # hidden*heads, classes; HAN->classes
HAN
类是整个HAN模型的定义。它接受以下参数:
num_meta_paths
:元路径的数量。in_size
:输入特征的维度。hidden_size
:隐藏层的维度。out_size
:输出特征的维度(通常是类别数量)。num_heads
:一个列表,指定每个HANLayer
层中的注意力头数量。dropout
:用于正则化的dropout率。
在初始化过程中,它首先创建了多个HANLayer
层,每个HANLayer
层包括一个或多个GATConv层和一个SemanticAttention
层。
输入处理好的异构图,即邻接矩阵(普通图模型。):
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))
这里定义了一个名为SemanticAttention
的PyTorch模型类,它用于计算每个节点在不同元路径上的语义级别的重要性。和第一个代码段的SemanticAttention
类相似,这个类也包括以下成员:
__init__
方法:初始化模型。它接受输入特征的维度in_size
以及可选的隐藏层维度hidden_size
。在初始化过程中,它创建了一个神经网络模块self.project
,该模块包括两个线性层和一个Tanh激活函数,最后一个线性层没有偏差。
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)
forward
方法:用于计算每个节点在不同元路径上的语义级别的重要性。首先,将输入特征z
通过self.project
模块传递,然后计算每个元路径上的语义级别的均值权重w
。接着,使用softmax函数对这些均值进行归一化,得到每个元路径上的注意力权重beta
,将这些权重与输入特征相乘,并对所有元路径求和,得到最终的节点表示。
接下来,定义了另一个模型类HANLayer
,它代表HAN模型中的一个层次。
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 matrixself.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,allow_zero_in_degree=True))self.semantic_attention = SemanticAttention(in_size=out_size * layer_num_heads)self.meta_paths = list(tuple(meta_path) for meta_path in meta_paths) # 将meta-path转换成元组形式self._cached_graph = Noneself._cached_coalesced_graph = {}def forward(self, g, h):semantic_embeddings = []if self._cached_graph is None or self._cached_graph is not g: # 第一次,建立一张metapath下的异构图self._cached_graph = gself._cached_coalesced_graph.clear()for meta_path in self.meta_paths:self._cached_coalesced_graph[meta_path] = dgl.metapath_reachable_graph(g, meta_path) # 构建异构图的邻居;# self._cached_coalesced_graph 多个metapath下的异构图for i, meta_path in enumerate(self.meta_paths):new_g = self._cached_coalesced_graph[meta_path] # meta-path下的节点邻居图semantic_embeddings.append(self.gat_layers[i](new_g, h).flatten(1)) # 图attentionsemantic_embeddings = torch.stack(semantic_embeddings, dim=1) # (N, M, D * K)return self.semantic_attention(semantic_embeddings) # (N, D * K)
HANLayer
类包括以下主要部分:
-
__init__
方法:初始化HAN层,它包括多个GATConv层以及一个语义注意力模块。每个GATConv层对应一个元路径,用于处理节点在该元路径上的信息。语义注意力模块用于计算节点在不同元路径上的语义级别的注意力。 -
forward
方法:执行HAN层的前向传播。对于每个元路径,首先获取该元路径的邻居图,然后通过GATConv层计算节点的注意力表示。最后,通过语义注意力模块将不同元路径上的表示进行加权求和,得到最终的节点表示。
最后,定义了整个HAN模型类HAN
:
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)
HAN
类定义了整个HAN模型,包括多个HANLayer层以及最后的预测层。
__init__
方法:初始化HAN模型,它包括多个HANLayer层,每个HANLayer层用于处理一个元路径的信息。最后,添加一个线性预测层,将最终的节点表示映
射到输出特征(通常是类别数量)。
forward
方法:执行HAN模型的前向传播。它依次通过多个HANLayer层来计算最终的输出,每个HANLayer层都包括元路径信息的处理和注意力聚合。
训练代码train
训练代码就是常规的套路。
-
引入必要的库和模块:
- 导入了PyTorch库和sklearn库,用于深度学习和评估模型性能。
- 导入了自定义的
load_data
和EarlyStopping
函数,以及其他必要的模块。
-
score
函数:- 这个函数用于计算模型的性能指标,包括准确率(accuracy)、微平均F1分数(micro_f1),和宏平均F1分数(macro_f1)。
- 它接受模型的预测结果(logits)和真实标签(labels),然后计算这些性能指标。
- 准确率表示正确分类的样本比例,微平均F1分数和宏平均F1分数是一种综合的评估指标,用于度量分类模型的性能。
-
evaluate
函数:- 这个函数用于评估模型在验证集上的性能。
- 它接受模型(model)、图数据(g)、特征数据(features)、标签数据(labels)、掩码数据(mask),以及损失函数(loss_func)作为输入。
- 在评估过程中,模型处于评估模式(
model.eval()
),不会更新梯度。 - 通过模型预测验证集上的结果,并计算损失、准确率、微平均F1分数和宏平均F1分数。
- 最后返回这些评估指标。
-
main
函数:- 这是主要的训练和评估逻辑所在的函数。
- 首先,加载数据(包括图数据、特征数据、标签数据等)并将其移动到指定的计算设备(CPU或GPU)上。
- 根据参数
args
中的'hetero'
标志,选择不同的模型和数据处理方式。如果'hetero'
为True,则使用异构图模型;否则,使用普通图模型。 - 定义了模型的损失函数、优化器和早停(EarlyStopping)对象。
- 开始训练循环,每个epoch进行一次训练和验证。在训练过程中,计算损失、准确率和F1分数等指标,并打印出来。如果验证集上的性能不再提升,会触发早停(early stopping)。
- 最后,在测试集上评估模型的性能,并打印出测试集上的损失、准确率、微平均F1分数和宏平均F1分数。
-
if __name__ == '__main__':
部分:- 这个部分用于设置命令行参数,并调用
main
函数来运行训练和评估过程。 - 可以通过命令行传递参数来配置模型的训练和数据处理方式。
- 这个部分用于设置命令行参数,并调用
rlyStopping)对象。
- 开始训练循环,每个epoch进行一次训练和验证。在训练过程中,计算损失、准确率和F1分数等指标,并打印出来。如果验证集上的性能不再提升,会触发早停(early stopping)。
- 最后,在测试集上评估模型的性能,并打印出测试集上的损失、准确率、微平均F1分数和宏平均F1分数。
-
if __name__ == '__main__':
部分:- 这个部分用于设置命令行参数,并调用
main
函数来运行训练和评估过程。 - 可以通过命令行传递参数来配置模型的训练和数据处理方式。
- 这个部分用于设置命令行参数,并调用
总体来说,这段代码实现了一个用于异构图数据或普通图数据的节点分类任务的训练和评估流程。它加载数据、选择模型、进行训练和验证,最后在测试集上评估模型性能。