그래프 신경망(3) - GAT-pytorch 버전 코드에 대한 자세한 설명


앞글자에 써있는데..
대학원에서 그래프 신경망을 사용해서 그래프 신경망과 관련된 논문이나 코드를 주로 읽습니다. 이 시리즈를 쓰는 목적은 알고리즘의 기본 개념과 프로세스를 다시 이해하는 것입니다. , 비판하고 수정해주세요. !

  • 깃허브: https://github.com/OuYangg/GNNs

1 가트의 기본 소개

  • 논문 제목: 주의 네트워크 그래프
  • 출처:Petar V., William C., Arantxa C., Adriana R., Pietro L., Joshua B.

주의 메커니즘은 기계 번역 및 기계 읽기와 같은 분야에서 큰 성공을 거두었습니다. 주의 메커니즘을 기반으로 하는 GCN은 서로 다른 이웃에 서로 다른 가중치를 할당합니다.

GAT는 전파 단계에 주의 메커니즘을 적용하고 각 노드의 1차 이웃에 주의를 기울여 노드의 숨겨진 계층 상태를 생성합니다.순방향 전파 공식은 다음과 같습니다.

hvt + 1 = p ( ∑ u ∈ N vavu W Hut ) , h^{t+1}_v = p(\sum_{u \in N_v}a_{vu}Wh^t_u),시간V+ 1=(u Nvv_) ,
avu = exp ( 누설 R e LU ( a T [ W hv ∣ ∣ W hk ] ) ) ∑ k ∈ N vexp ( 누설 R e LU ( a T [ W hv ∣ ∣ W hk ] ) ) a_{ vu}=\frac{exp(LeakyReLU(a^T[Wh_v || Wh_u]))}{\sum_{k \in N_v}exp(LeakyReLU(a^T[Wh_v || Wh_k]))}v=k Nv.e x p ( 누설 R e L U ( a _ _ _ _T [Whv_케이] ) )e x p ( 누설 R e L U ( a _ _ _ _T [Whv_] ) )
그 중, WWW 는 매개변수 행렬,aaa 는 각 GAT 레이어의 매개변수 벡터, 즉 주의 계수 벡터입니다. GAT는 멀티 헤드 어텐션을 사용하여 학습 프로세스를 안정화합니다. 이는 K개의 독립 어텐션 헤드 매트릭스를 사용하여 은닉층을 계산한 다음 해당 기능을 결합합니다
. khut ) , h^{t+1}_v =||_{k=1}^{K} \sigma(\sum_{u \in N_v}a^k_{vu}W_kh^t_u),시간V+ 1=케이 = 1케이(u Nvv 케이케이시간) ,
hvt + 1 = σ ( 1K ∑ k = 1K ∑ u ∈ N vavuk W khut ) h^{t+1}_v =\sigma(\frac{1}{K} \sum_{k=1} ^K\sum_{u \in N_v}a^k_{vu}W_kh^t_u)시간V+ 1=(케이1케이 = 1케이u Nvv 케이케이시간)
특징: 1) 노드-이웃 쌍의 계산이 병렬이므로 효율성이 높음, 2) 정도 값이 다른 노드에 사용할 수 있음, 3) 귀납 학습에 사용할 수 있음. 이 다중 헤드는 실제로 다른 관점에서 대상 노드에 대한 1차 이웃 노드의 기여도를 정량화하는 것으로 이해할 수 있습니다.

아래 그림의 왼쪽은 Attention Coefficient 계산을 시각화한 것이고, 아래 그림의 오른쪽은 Multi-Head GAT Layer의 Forward Propagation을 시각화한 것입니다.
여기에 이미지 설명 삽입

2 코드 분석

  • 코드 참조 주소: pyGAT
  • 필요한 라이브러리 가져오기
import time
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import argparse
import numpy as np
import random
import scipy.sparse as 

2.1 데이터 가져오기

def encode_onehot(labels):
    classes = set(labels)
    classes_dict = {
    
    c: np.identity(len(classes))[i, :] for i, c in
                    enumerate(classes)}
    labels_onehot = np.array(list(map(classes_dict.get, labels)),
                             dtype=np.int32)
    return labels_onehot


def normalize_adj(mx):
    """Row-normalize sparse matrix"""
    rowsum = np.array(mx.sum(1))
    r_inv_sqrt = np.power(rowsum, -0.5).flatten()
    r_inv_sqrt[np.isinf(r_inv_sqrt)] = 0.
    r_mat_inv_sqrt = sp.diags(r_inv_sqrt)
    return mx.dot(r_mat_inv_sqrt).transpose().dot(r_mat_inv_sqrt)

def normalize(mx):
    """Row-normalize sparse matrix"""
    rowsum = np.array(mx.sum(1))
    r_inv = np.power(rowsum, -1).flatten()
    r_inv[np.isinf(r_inv)] = 0.
    r_mat_inv = sp.diags(r_inv)
    mx = r_mat_inv.dot(mx)
    return mx

def accuracy(output, labels):
    preds = output.max(1)[1].type_as(labels)
    correct = preds.eq(labels).double()
    correct = correct.sum()
    return correct / len(labels)

def sparse_mx_to_torch_sparse_tensor(sparse_mx):
    """Convert a scipy sparse matrix to a torch sparse tensor."""
    sparse_mx = sparse_mx.tocoo().astype(np.float32)
    indices = torch.from_numpy(
        np.vstack((sparse_mx.row, sparse_mx.col)).astype(np.int64))
    values = torch.from_numpy(sparse_mx.data)
    shape = torch.Size(sparse_mx.shape)
    return torch.sparse.FloatTensor(indices, values, shape)

def load_data(path="./cora/", dataset="cora"):
    """读取引文网络数据cora"""
    print('Loading {} dataset...'.format(dataset))
    idx_features_labels = np.genfromtxt("{}{}.content".format(path, dataset),
                                        dtype=np.dtype(str)) # 使用numpy读取.txt文件
    features = sp.csr_matrix(idx_features_labels[:, 1:-1], dtype=np.float32) # 获取特征矩阵
    labels = encode_onehot(idx_features_labels[:, -1]) # 获取标签

    # build graph
    idx = np.array(idx_features_labels[:, 0], dtype=np.int32)
    idx_map = {
    
    j: i for i, j in enumerate(idx)}
    edges_unordered = np.genfromtxt("{}{}.cites".format(path, dataset),
                                    dtype=np.int32)
    edges = np.array(list(map(idx_map.get, edges_unordered.flatten())),
                     dtype=np.int32).reshape(edges_unordered.shape)
    adj = sp.coo_matrix((np.ones(edges.shape[0]), (edges[:, 0], edges[:, 1])),
                        shape=(labels.shape[0], labels.shape[0]),
                        dtype=np.float32)

    # build symmetric adjacency matrix
    adj = adj + adj.T.multiply(adj.T > adj) - adj.multiply(adj.T > adj)

    features = normalize(features)
    adj = normalize_adj(adj + sp.eye(adj.shape[0]))

    idx_train = range(140)
    idx_val = range(200, 500)
    idx_test = range(500, 1500)

    features = torch.FloatTensor(np.array(features.todense()))
    labels = torch.LongTensor(np.where(labels)[1])
    adj = torch.FloatTensor(np.array(adj.todense()))

    idx_train = torch.LongTensor(idx_train)
    idx_val = torch.LongTensor(idx_val)
    idx_test = torch.LongTensor(idx_test)

    return adj, features, labels, idx_train, idx_val, idx_test

2.2 가트 모델 프레임워크

class GATLayer(nn.Module):
    """GAT层"""
    def __init__(self,input_feature,output_feature,dropout,alpha,concat=True):
        super(GATLayer,self).__init__()
        self.input_feature = input_feature
        self.output_feature = output_feature
        self.alpha = alpha
        self.dropout = dropout
        self.concat = concat
        self.a = nn.Parameter(torch.empty(size=(2*output_feature,1)))
        self.w = nn.Parameter(torch.empty(size=(input_feature,output_feature)))
        self.leakyrelu = nn.LeakyReLU(self.alpha)
        self.reset_parameters()
    
    def reset_parameters(self):
        nn.init.xavier_uniform_(self.w.data,gain=1.414)
        nn.init.xavier_uniform_(self.a.data,gain=1.414)
    
    def forward(self,h,adj):
        Wh = torch.mm(h,self.w)
        e = self._prepare_attentional_mechanism_input(Wh)
        zero_vec = -9e15*torch.ones_like(e)
        attention = torch.where(adj > 0, e, zero_vec) # adj>0的位置使用e对应位置的值替换,其余都为-9e15,这样设定经过Softmax后每个节点对应的行非邻居都会变为0。
        attention = F.softmax(attention, dim=1) # 每行做Softmax,相当于每个节点做softmax
        attention = F.dropout(attention, self.dropout, training=self.training)
        h_prime = torch.mm(attention, Wh) # 得到下一层的输入
        
        if self.concat:
            return F.elu(h_prime) #激活
        else:
            return h_prime
        
    def _prepare_attentional_mechanism_input(self,Wh):
        
        Wh1 = torch.matmul(Wh,self.a[:self.output_feature,:]) # N*out_size @ out_size*1 = N*1
        
        Wh2 = torch.matmul(Wh,self.a[self.output_feature:,:]) # N*1
        
        e = Wh1+Wh2.T # Wh1的每个原始与Wh2的所有元素相加,生成N*N的矩阵
        return self.leakyrelu(e)
    
class GAT(nn.Module):
	"""GAT模型"""
    def __init__(self,input_size,hidden_size,output_size,dropout,alpha,nheads,concat=True):
        super(GAT,self).__init__()
        self.dropout= dropout
        self.attention = [GATLayer(input_size, hidden_size, dropout=dropout, alpha=alpha,concat=True) for _ in range(nheads)]
        for i,attention in enumerate(self.attention):
            self.add_module('attention_{}'.format(i),attention)
        
        self.out_att = GATLayer(hidden_size*nheads, output_size, dropout=dropout, alpha=alpha,concat=False)
        
    def forward(self,x,adj):
        x = F.dropout(x,self.dropout,training=self.training)
        x = torch.cat([att(x,adj) for att in self.attention],dim=1)
        x = F.dropout(x,self.dropout,training=self.training)
        x = F.elu(self.out_att(x,adj))
        
        return F.log_softmax(x,dim=1)

2.3 평가 및 교육

def train(epoch):
    t = time.time()
    model.train()
    optimizer.zero_grad()
    output = model(features,adj)
    loss_train = F.nll_loss(output[idx_train],labels[idx_train])
    acc_train = accuracy(output[idx_train], labels[idx_train])
    loss_train.backward()
    optimizer.step()
    model.eval()
    output = model(features, adj)
    acc_val = accuracy(output[idx_val],labels[idx_val])
    loss_val = F.nll_loss(output[idx_val], labels[idx_val])
    print('Epoch: {:04d}'.format(epoch+1),
          'loss_train: {:.4f}'.format(loss_train.data.item()),
          'acc_train: {:.4f}'.format(acc_train.data.item()),
          'loss_val: {:.4f}'.format(loss_val.data.item()),
          'acc_val: {:.4f}'.format(acc_val.data.item()),
          'time: {:.4f}s'.format(time.time() - t))
    return loss_val.data.item()

def compute_test():
    model.eval()
    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("Test set results:",
          "loss= {:.4f}".format(loss_test.data.item()),
          "accuracy= {:.4f}".format(acc_test.data.item()))

if __name__ == '__main__':

    parser = argparse.ArgumentParser()
    parser.add_argument('--lr',type=float,default=0.005,help='learning rate')
    parser.add_argument('--hidden',type=int,default=8,help='hidden size')
    parser.add_argument('--epochs',type=int,default=1000,help='Number of training epochs')
    parser.add_argument('--weight_decay',type=float,default=5e-4,help='Weight decay')
    parser.add_argument('--nheads',type=int,default=8,help='Number of head attentions')
    parser.add_argument('--dropout', type=float, default=0.6, help='Dropout rate (1 - keep probability).')
    parser.add_argument('--alpha', type=float, default=0.2, help='Alpha for the leaky_relu.')
    parser.add_argument('--patience', type=int, default=100, help='Patience')
    parser.add_argument('--seed',type=int,default=17,help='Seed number')
    args = parser.parse_args()
    random.seed(args.seed)
    np.random.seed(args.seed)
    torch.manual_seed(args.seed)
    adj, features, labels, idx_train, idx_val, idx_test = load_data()
    model = GAT(input_size=features.shape[1],hidden_size=args.hidden,output_size=int(labels.max())+1,dropout=args.dropout,nheads=8,alpha=args.alpha)
    optimizer = optim.Adam(model.parameters(),lr=args.lr,weight_decay=args.weight_decay)
    
    t_total = time.time()
    loss_values = []
    bad_counter = 0
    best = 1000+1
    best_epoch = 0


    for epoch in range(1000):
        loss_values.append(train(epoch))
        if loss_values[-1] < best:
            best = loss_values[-1]
            best_epoch = epoch
            bad_counter = 0
        else:
            bad_counter += 1
        
        if bad_counter == args.patience:
            break
    print("Optimization Finished!")
    print("Total time elapsed: {:.4f}s".format(time.time() - t_total))
    compute_test()

결과는 다음과 같습니다.
여기에 이미지 설명 삽입

참조

[1] 그래프 어텐션 네트워크
[2] Pytorch-GAT

おすすめ

転載: blog.csdn.net/weixin_44027006/article/details/124101720