项目文件夹

文件
Quan (Andy) Gan 6066fee935 [Model][Feature] PinSage & Random Walk with Restart (#453)
* random walk traces generation

* remove outdated comments

* oops put in the wrong place

* explicit inline

* moving rand_r to util

* pinsage-like model on movielens

* the code runs now

* support cuda

* using readonly graph

* moving random walk to public function

* per-thread seed and openmp support

* pinsage-like model on movielens

* the code runs now

* support cuda

* using readonly graph

* using C random walk

* removing profile decorators

* param initialization

* no grad

* leaky relu fixes everything

* train and save

* WIP

* WIP

* WIP

* seems to work

* evaluation output

* swapping order of val/test and train

* debug

* hyperparam tuning

* prior/training dataset split changes

* random walk reorg

* random walk with restart

* signed comparison fix

* migrating random walk to nodeflow

* Revert "migrating random walk to nodeflow"

This reverts commit f2565347cced7c912a58a529b257c033d9f375b7.

* add README and remove dataset

* new endpoint

* lint

* lint x2

* oops forgot test

* including bpr - better for baseline

* addressing fixes

* throwing random walks out from SamplerOp class

* forgot to move RandomWalk; why did this even work?

* removing legacy garbage

* add todo

* address comments

* stupid bug fix

* call ndarrayvector converter to handle traces
2019-03-29 13:01:18 +08:00

150 行
5.3 KiB
Python

import torch
import torch.nn as nn
import torch.nn.functional as F
import dgl
from .. import randomwalk
from ..utils import cuda
def create_embeddings(n_nodes, n_features):
return nn.Parameter(torch.randn(n_nodes, n_features))
def mix_embeddings(h, ndata, emb, proj):
'''Combine node-specific trainable embedding ``h`` with categorical inputs
(projected by ``emb``) and numeric inputs (projected by ``proj``).
'''
e = []
for key, value in ndata.items():
if value.dtype == torch.int64:
e.append(emb[key](value))
elif value.dtype == torch.float32:
e.append(proj[key](value))
return h + torch.stack(e, 0).sum(0)
def get_embeddings(h, nodeset):
return h[nodeset]
def put_embeddings(h, nodeset, new_embeddings):
n_nodes = nodeset.shape[0]
n_features = h.shape[1]
return h.scatter(0, nodeset[:, None].expand(n_nodes, n_features), new_embeddings)
def safediv(a, b):
b = torch.where(b == 0, torch.ones_like(b), b)
return a / b
def init_weight(w, func_name, nonlinearity):
getattr(nn.init, func_name)(w, gain=nn.init.calculate_gain(nonlinearity))
def init_bias(w):
nn.init.constant_(w, 0)
class PinSageConv(nn.Module):
def __init__(self, in_features, out_features, hidden_features):
super(PinSageConv, self).__init__()
self.in_features = in_features
self.out_features = out_features
self.hidden_features = hidden_features
self.Q = nn.Linear(in_features, hidden_features)
self.W = nn.Linear(in_features + hidden_features, out_features)
init_weight(self.Q.weight, 'xavier_uniform_', 'leaky_relu')
init_weight(self.W.weight, 'xavier_uniform_', 'leaky_relu')
init_bias(self.Q.bias)
init_bias(self.W.bias)
def forward(self, h, nodeset, nb_nodes, nb_weights):
'''
h: node embeddings (num_total_nodes, in_features), or a container
of the node embeddings (for distributed computing)
nodeset: node IDs in this minibatch (num_nodes,)
nb_nodes: neighbor node IDs of each node in nodeset (num_nodes, num_neighbors)
nb_weights: weight of each neighbor node (num_nodes, num_neighbors)
return: new node embeddings (num_nodes, out_features)
'''
n_nodes, T = nb_nodes.shape
h_nodeset = get_embeddings(h, nodeset) # (n_nodes, in_features)
h_neighbors = get_embeddings(h, nb_nodes.view(-1)).view(n_nodes, T, self.in_features)
h_neighbors = F.leaky_relu(self.Q(h_neighbors))
h_agg = safediv(
(nb_weights[:, :, None] * h_neighbors).sum(1),
nb_weights.sum(1, keepdim=True))
h_concat = torch.cat([h_nodeset, h_agg], 1)
h_new = F.leaky_relu(self.W(h_concat))
h_new = safediv(h_new, h_new.norm(dim=1, keepdim=True))
return h_new
class PinSage(nn.Module):
'''
Completes a multi-layer PinSage convolution
G: DGLGraph
feature_sizes: the dimensionality of input/hidden/output features
T: number of neighbors we pick for each node
restart_prob: restart probability
max_nodes: max number of nodes visited for each seed
'''
def __init__(self, num_nodes, feature_sizes, T, restart_prob, max_nodes,
use_feature=False, G=None):
super(PinSage, self).__init__()
self.T = T
self.restart_prob = restart_prob
self.max_nodes = max_nodes
self.in_features = feature_sizes[0]
self.out_features = feature_sizes[-1]
self.n_layers = len(feature_sizes) - 1
self.convs = nn.ModuleList()
for i in range(self.n_layers):
self.convs.append(PinSageConv(
feature_sizes[i], feature_sizes[i+1], feature_sizes[i+1]))
self.h = create_embeddings(num_nodes, self.in_features)
self.use_feature = use_feature
if use_feature:
self.emb = nn.ModuleDict()
self.proj = nn.ModuleDict()
for key, scheme in G.node_attr_schemes().items():
if scheme.dtype == torch.int64:
self.emb[key] = nn.Embedding(
G.ndata[key].max().item() + 1,
self.in_features,
padding_idx=0)
elif scheme.dtype == torch.float32:
self.proj[key] = nn.Sequential(
nn.Linear(scheme.shape[0], self.in_features),
nn.LeakyReLU(),
)
def forward(self, G, nodeset):
'''
Given a complete embedding matrix h and a list of node IDs, return
the output embeddings of these node IDs.
nodeset: node IDs in this minibatch (num_nodes,)
return: new node embeddings (num_nodes, out_features)
'''
if self.use_feature:
h = mix_embeddings(self.h, G.ndata, self.emb, self.proj)
else:
h = self.h
nodeflow = randomwalk.random_walk_nodeflow(
G, nodeset, self.n_layers, self.restart_prob, self.max_nodes, self.T)
for i, (nodeset, nb_weights, nb_nodes) in enumerate(nodeflow):
new_embeddings = self.convs[i](h, nodeset, nb_nodes, nb_weights)
h = put_embeddings(h, nodeset, new_embeddings)
h_new = get_embeddings(h, nodeset)
return h_new