import numpy as np import tensorflow as tf class Model(object): def __init__(self, **kwargs): allowed_kwargs = {'name', 'logging'} for kwarg in kwargs.keys(): assert kwarg in allowed_kwargs, 'Invalid keyword argument: ' + kwarg for kwarg in kwargs.keys(): assert kwarg in allowed_kwargs, 'Invalid keyword argument: ' + kwarg name = kwargs.get('name') if not name: name = self.__class__.__name__.lower() self.name = name logging = kwargs.get('logging', False) self.logging = logging self.vars = {} def _build(self): raise NotImplementedError def build(self): """ Wrapper for _build() """ with tf.variable_scope(self.name): self._build() variables = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope=self.name) self.vars = {var.name: var for var in variables} def fit(self): pass def predict(self): pass _LAYER_UIDS = {} def get_layer_uid(layer_name=''): """Helper function, assigns unique layer IDs """ if layer_name not in _LAYER_UIDS: _LAYER_UIDS[layer_name] = 1 return 1 else: _LAYER_UIDS[layer_name] += 1 return _LAYER_UIDS[layer_name] class Layer(object): """Base layer class. Defines basic API for all layer objects. # Properties name: String, defines the variable scope of the layer. # Methods _call(inputs): Defines computation graph of layer (i.e. takes input, returns output) __call__(inputs): Wrapper for _call() """ def __init__(self, **kwargs): allowed_kwargs = {'name', 'logging'} for kwarg in kwargs.keys(): assert kwarg in allowed_kwargs, 'Invalid keyword argument: ' + kwarg name = kwargs.get('name') if not name: layer = self.__class__.__name__.lower() name = layer + '_' + str(get_layer_uid(layer)) self.name = name self.vars = {} logging = kwargs.get('logging', False) self.logging = logging self.issparse = False def _call(self, inputs): return inputs def __call__(self, inputs): with tf.name_scope(self.name): outputs = self._call(inputs) return outputs def weight_variable_glorot(input_dim, output_dim, name=""): """Create a weight variable with Glorot & Bengio (AISTATS 2010) initialization. """ init_range = np.sqrt(6.0 / (input_dim + output_dim)) initial = tf.random_uniform([input_dim, output_dim], minval=-init_range, maxval=init_range, dtype=tf.float32) return tf.Variable(initial, name=name) def dropout_sparse(x, keep_prob, num_nonzero_elems): """ Dropout for sparse tensors. Currently fails for very large sparse tensors (>1M elements) num_nonzero_elems: The number of non-zero elements in the sparse matrix keep_prob: x: input """ noise_shape = [num_nonzero_elems] random_tensor = keep_prob random_tensor += tf.random_uniform(noise_shape) dropout_mask = tf.cast(tf.floor(random_tensor), dtype=tf.bool) pre_out = tf.sparse_retain(x, dropout_mask) return pre_out * (1. / keep_prob) class GraphConvolutionSparse(Layer): """ Graph convolution layer for sparse inputs. 多了一个features_nonzero """ def __init__(self, input_dim, output_dim, adj, features_nonzero, dropout=0., act=tf.nn.relu, **kwargs): super(GraphConvolutionSparse, self).__init__(**kwargs) with tf.variable_scope(self.name + '_vars'): self.vars['weights'] = weight_variable_glorot(input_dim, output_dim, name="weights") self.dropout = dropout self.adj = adj self.act = act self.issparse = True self.features_nonzero = features_nonzero def _call(self, inputs): x = inputs x = dropout_sparse(x, 1 - self.dropout, self.features_nonzero) x = tf.sparse_tensor_dense_matmul(x, self.vars['weights']) x = tf.sparse_tensor_dense_matmul(self.adj, x) outputs = self.act(x) return outputs def gaussian_noise_layer(input_layer, std): noise = tf.random_normal(shape=tf.shape(input_layer), mean=0.0, stddev=std, dtype=tf.float32) return input_layer + noise class GraphConvolution(Layer): """Basic graph convolution layer for undirected graph without edge labels.""" def __init__(self, input_dim, output_dim, adj, dropout=0., act=tf.nn.relu, **kwargs): super(GraphConvolution, self).__init__(**kwargs) with tf.variable_scope(self.name + '_vars'): self.vars['weights'] = weight_variable_glorot(input_dim, output_dim, name="weights") self.dropout = dropout self.adj = adj self.act = act def _call(self, inputs): x = inputs x = tf.nn.dropout(x, 1 - self.dropout) x = tf.matmul(x, self.vars['weights']) x = tf.sparse_tensor_dense_matmul(self.adj, x) outputs = self.act(x) return outputs class InnerProductDecoder(Layer): """Decoder model layer for link prediction.""" def __init__(self, input_dim, dropout=0., act=tf.nn.sigmoid, **kwargs): super(InnerProductDecoder, self).__init__(**kwargs) self.dropout = dropout self.act = act def _call(self, inputs): """ 这个decoder部分实际上就只是input的转置再乘input """ inputs = tf.nn.dropout(inputs, 1 - self.dropout) x = tf.transpose(inputs) x = tf.matmul(inputs, x) x = tf.reshape(x, [-1]) outputs = self.act(x) return outputs class GCN(Model): def __init__(self, placeholders, num_features, features_nonzero, settings, **kwargs): super(GCN, self).__init__(**kwargs) """ inputs: Input features input_dim: dimensionality feature_nonzero:Non-zero feature number adj: adjacency matrix dropout:dropout """ self.inputs = placeholders['features'] self.input_dim = num_features self.features_nonzero = features_nonzero self.adj = placeholders['adj'] self.dropout = placeholders['dropout'] self.settings = settings def construct(self, inputs=None, hidden=None, reuse=False): if inputs == None: inputs = self.inputs with tf.variable_scope('Encoder', reuse=reuse): self.hidden1 = GraphConvolutionSparse(input_dim=self.input_dim, output_dim=self.settings.hidden1, adj=self.adj, features_nonzero=self.features_nonzero, act=tf.nn.relu, dropout=self.dropout, logging=self.logging, name='e_dense_1')(inputs) self.noise = gaussian_noise_layer(self.hidden1, 0.1) if hidden == None: hidden = self.hidden1 self.embeddings = GraphConvolution(input_dim=self.settings.hidden1, output_dim=self.settings.hidden2, adj=self.adj, act=lambda x: x, dropout=self.dropout, logging=self.logging, name='e_dense_2')(hidden) self.z_mean = self.embeddings self.reconstructions = InnerProductDecoder(input_dim=self.settings.hidden2, act=lambda x: x, logging=self.logging)(self.embeddings) return self.z_mean, self.reconstructions def dense(x, n1, n2, name): """ Used to create a dense layer. :param x: input tensor to the dense layer :param n1: no. of input neurons :param n2: no. of output neurons :param name: name of the entire dense layer.i.e, variable scope name. :return: tensor with shape [batch_size, n2] """ with tf.variable_scope(name, reuse=None): # np.random.seed(1) tf.set_random_seed(1) weights = tf.get_variable("weights", shape=[n1, n2], initializer=tf.random_normal_initializer(mean=0., stddev=0.01)) bias = tf.get_variable("bias", shape=[n2], initializer=tf.constant_initializer(0.0)) out = tf.add(tf.matmul(x, weights), bias, name='matmul') return out class Discriminator(Model): def __init__(self, settings, **kwargs): super(Discriminator, self).__init__(**kwargs) self.act = tf.nn.relu self.settings = settings def construct(self, inputs, reuse=False): with tf.variable_scope('Discriminator'): if reuse: tf.get_variable_scope().reuse_variables() tf.set_random_seed(1) dc_den1 = tf.nn.relu(dense(inputs, self.settings.hidden2, self.settings.hidden3, name='dc_den1')) dc_den2 = tf.nn.relu(dense(dc_den1, self.settings.hidden3, self.settings.hidden1, name='dc_den2')) output = dense(dc_den2, self.settings.hidden1, 1, name='dc_output') return output class D_graph(Model): def __init__(self, num_features, **kwargs): super(D_graph, self).__init__(**kwargs) self.act = tf.nn.relu self.num_features = num_features def construct(self, inputs, reuse=False): # input是一张Graph的adj,把每一列当成一个通道,所以input的通道数是num_nodes with tf.variable_scope('D_Graph'): if reuse: tf.get_variable_scope().reuse_variables() # np.random.seed(1) # tf.set_random_seed(1) dc_den1 = tf.nn.relu(dense(inputs, self.num_features, 512, name='GD_den1')) # (bs,num_nodes,512) dc_den2 = tf.nn.relu(dense(dc_den1, 512, 128, name='GD_den2')) # (bs, num_nodes, 128) output = dense(dc_den2, 128, 1, name='GD_output') # (bs,num_nodes,1) return output class Generator_z2g(Model): def __init__(self, placeholders, num_features, features_nonzero, settings, **kwargs): super(Generator_z2g, self).__init__(**kwargs) """ inputs:输入 input_dim:feature的数量,即input的维度? feature_nonzero:非0的特征 adj:邻接矩阵 dropout:dropout """ self.inputs = placeholders['real_distribution'] self.input_dim = num_features self.features_nonzero = features_nonzero self.adj = placeholders['adj'] self.dropout = placeholders['dropout'] self.settings = settings def construct(self, inputs=None, reuse=False): if inputs == None: inputs = self.inputs with tf.variable_scope('Decoder', reuse=reuse): self.hidden1 = GraphConvolution(input_dim=self.settings.hidden2, output_dim=self.settings.hidden1, adj=self.adj, act=tf.nn.relu, dropout=self.dropout, logging=self.logging, name='GG_dense_1')(inputs) self.embeddings = GraphConvolution(input_dim=self.settings.hidden1, output_dim=self.input_dim, adj=self.adj, act=lambda x: x, dropout=self.dropout, logging=self.logging, name='GG_dense_2')(self.hidden1) self.z_mean = self.embeddings return self.z_mean, self.hidden1 class BGAN(object): def __init__(self, placeholders, num_features, num_nodes, features_nonzero, settings): self.discriminator = Discriminator(settings) self.D_Graph = D_graph(num_features) self.d_real = self.discriminator.construct(placeholders['real_distribution']) self.GD_real = self.D_Graph.construct(placeholders['features_dense']) self.ae_model = GCN(placeholders, num_features, features_nonzero, settings) self.model_z2g = Generator_z2g(placeholders, num_features, features_nonzero, settings)