项目文件夹

文件
KounianhuaDu a9e16c177c [Example] GNN-FiLM (#2476)
* gnnfilm

* update

* readme

Co-authored-by: zhjwy9343 <6593865@qq.com>
2021-01-13 22:34:14 +08:00

193 行
8.1 KiB
Python

import torch
import torch.nn as nn
import torch.nn.functional as F
import dgl
import dgl.function as fn
from utils import evaluate_f1_score
from data_loader import load_PPI
import argparse
import numpy as np
import os
class GNNFiLMLayer(nn.Module):
def __init__(self, in_size, out_size, etypes, dropout=0.1):
super(GNNFiLMLayer, self).__init__()
self.in_size = in_size
self.out_size = out_size
#weights for different types of edges
self.W = nn.ModuleDict({
name : nn.Linear(in_size, out_size, bias = False) for name in etypes
})
#hypernets to learn the affine functions for different types of edges
self.film = nn.ModuleDict({
name : nn.Linear(in_size, 2*out_size, bias = False) for name in etypes
})
#layernorm before each propogation
self.layernorm = nn.LayerNorm(out_size)
#dropout layer
self.dropout = nn.Dropout(dropout)
def forward(self, g, feat_dict):
#the input graph is a multi-relational graph, so treated as hetero-graph.
funcs = {} #message and reduce functions dict
#for each type of edges, compute messages and reduce them all
for srctype, etype, dsttype in g.canonical_etypes:
messages = self.W[etype](feat_dict[srctype]) #apply W_l on src feature
film_weights = self.film[etype](feat_dict[dsttype]) #use dst feature to compute affine function paras
gamma = film_weights[:,:self.out_size] #"gamma" for the affine function
beta = film_weights[:,self.out_size:] #"beta" for the affine function
messages = gamma * messages + beta #compute messages
messages = F.relu_(messages)
g.nodes[srctype].data[etype] = messages #store in ndata
funcs[etype] = (fn.copy_u(etype, 'm'), fn.sum('m', 'h')) #define message and reduce functions
g.multi_update_all(funcs, 'sum') #update all, reduce by first type-wisely then across different types
feat_dict={}
for ntype in g.ntypes:
feat_dict[ntype] = self.dropout(self.layernorm(g.nodes[ntype].data['h'])) #apply layernorm and dropout
return feat_dict
class GNNFiLM(nn.Module):
def __init__(self, etypes, in_size, hidden_size, out_size, num_layers, dropout=0.1):
super(GNNFiLM, self).__init__()
self.film_layers = nn.ModuleList()
self.film_layers.append(
GNNFiLMLayer(in_size, hidden_size, etypes, dropout)
)
for i in range(num_layers-1):
self.film_layers.append(
GNNFiLMLayer(hidden_size, hidden_size, etypes, dropout)
)
self.predict = nn.Linear(hidden_size, out_size, bias = True)
def forward(self, g, out_key):
h_dict = {ntype : g.nodes[ntype].data['feat'] for ntype in g.ntypes} #prepare input feature dict
for layer in self.film_layers:
h_dict = layer(g, h_dict)
h = self.predict(h_dict[out_key]) #use the final embed to predict, out_size = num_classes
h = torch.sigmoid(h)
return h
def main(args):
# Step 1: Prepare graph data and retrieve train/validation/test dataloader ============================= #
if args.gpu >= 0 and torch.cuda.is_available():
device = 'cuda:{}'.format(args.gpu)
else:
device = 'cpu'
if args.dataset == 'PPI':
train_set, valid_set, test_set, etypes, in_size, out_size = load_PPI(args.batch_size, device)
# Step 2: Create model and training components=========================================================== #
model = GNNFiLM(etypes, in_size, args.hidden_size, out_size, args.num_layers).to(device)
criterion = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=args.lr, weight_decay=args.wd)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, args.step_size, gamma=args.gamma)
# Step 4: training epoches ============================================================================== #
lastf1 = 0
cnt = 0
best_val_f1 = 0
for epoch in range(args.max_epoch):
train_loss = []
train_f1 = []
val_loss = []
val_f1 = []
model.train()
for batch in train_set:
g = batch.graph
g = g.to(device)
logits = model.forward(g, '_N')
labels = batch.label
loss = criterion(logits, labels)
f1 = evaluate_f1_score(logits.detach().cpu().numpy(), labels.detach().cpu().numpy())
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss.append(loss.item())
train_f1.append(f1)
train_loss = np.mean(train_loss)
train_f1 = np.mean(train_f1)
scheduler.step()
model.eval()
with torch.no_grad():
for batch in valid_set:
g = batch.graph
g = g.to(device)
logits = model.forward(g, '_N')
labels = batch.label
loss = criterion(logits, labels)
f1 = evaluate_f1_score(logits.detach().cpu().numpy(), labels.detach().cpu().numpy())
val_loss.append(loss.item())
val_f1.append(f1)
val_loss = np.mean(val_loss)
val_f1 = np.mean(val_f1)
print('Epoch {:d} | Train Loss {:.4f} | Train F1 {:.4f} | Val Loss {:.4f} | Val F1 {:.4f} |'.format(epoch + 1, train_loss, train_f1, val_loss, val_f1))
if val_f1 > best_val_f1:
best_val_f1 = val_f1
torch.save(model.state_dict(), os.path.join(args.save_dir, args.name))
if val_f1 < lastf1:
cnt += 1
if cnt == args.early_stopping:
print('Early stop.')
break
else:
cnt = 0
lastf1 = val_f1
model.eval()
test_loss = []
test_f1 = []
model.load_state_dict(torch.load(os.path.join(args.save_dir, args.name)))
with torch.no_grad():
for batch in test_set:
g = batch.graph
g = g.to(device)
logits = model.forward(g, '_N')
labels = batch.label
loss = criterion(logits, labels)
f1 = evaluate_f1_score(logits.detach().cpu().numpy(), labels.detach().cpu().numpy())
test_loss.append(loss.item())
test_f1.append(f1)
test_loss = np.mean(test_loss)
test_f1 = np.mean(test_f1)
print("Test F1: {:.4f} | Test loss: {:.4f}".format(test_f1, test_loss))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='GNN-FiLM')
parser.add_argument("--dataset", type=str, default="PPI", help="DGL dataset for this GNN-FiLM")
parser.add_argument("--gpu", type=int, default=-1, help="GPU Index. Default: -1, using CPU.")
parser.add_argument("--in_size", type=int, default=50, help="Input dimensionalities")
parser.add_argument("--hidden_size", type=int, default=320, help="Hidden layer dimensionalities")
parser.add_argument("--out_size", type=int, default=121, help="Output dimensionalities")
parser.add_argument("--num_layers", type=int, default=4, help="Number of GNN layers")
parser.add_argument("--batch_size", type=int, default=5, help="Batch size")
parser.add_argument("--max_epoch", type=int, default=1500, help="The max number of epoches. Default: 500")
parser.add_argument("--early_stopping", type=int, default=80, help="Early stopping. Default: 50")
parser.add_argument("--lr", type=float, default=0.001, help="Learning rate. Default: 3e-1")
parser.add_argument("--wd", type=float, default=0.0009, help="Weight decay. Default: 3e-1")
parser.add_argument('--step-size', type=int, default=40, help='Period of learning rate decay.')
parser.add_argument('--gamma', type=float, default=0.8, help='Multiplicative factor of learning rate decay.')
parser.add_argument("--dropout", type=float, default=0.1, help="Dropout rate. Default: 0.9")
parser.add_argument('--save_dir', type=str, default='./out', help='Path to save the model.')
parser.add_argument("--name", type=str, default='GNN-FiLM', help="Saved model name.")
args = parser.parse_args()
print(args)
if not os.path.exists(args.save_dir):
os.mkdir(args.save_dir)
main(args)