Base implementation of unsupervised GraphSAGE
| 185 | ]) |
| 186 | |
| 187 | class SampleAndAggregate(GeneralizedModel): |
| 188 | """ |
| 189 | Base implementation of unsupervised GraphSAGE |
| 190 | """ |
| 191 | |
| 192 | def __init__(self, placeholders, features, adj, degrees, |
| 193 | layer_infos, concat=True, aggregator_type="mean", |
| 194 | model_size="small", identity_dim=0, |
| 195 | **kwargs): |
| 196 | ''' |
| 197 | Args: |
| 198 | - placeholders: Stanford TensorFlow placeholder object. |
| 199 | - features: Numpy array with node features. |
| 200 | NOTE: Pass a None object to train in featureless mode (identity features for nodes)! |
| 201 | - adj: Numpy array with adjacency lists (padded with random re-samples) |
| 202 | - degrees: Numpy array with node degrees. |
| 203 | - layer_infos: List of SAGEInfo namedtuples that describe the parameters of all |
| 204 | the recursive layers. See SAGEInfo definition above. |
| 205 | - concat: whether to concatenate during recursive iterations |
| 206 | - aggregator_type: how to aggregate neighbor information |
| 207 | - model_size: one of "small" and "big" |
| 208 | - identity_dim: Set to positive int to use identity features (slow and cannot generalize, but better accuracy) |
| 209 | ''' |
| 210 | super(SampleAndAggregate, self).__init__(**kwargs) |
| 211 | if aggregator_type == "mean": |
| 212 | self.aggregator_cls = MeanAggregator |
| 213 | elif aggregator_type == "seq": |
| 214 | self.aggregator_cls = SeqAggregator |
| 215 | elif aggregator_type == "maxpool": |
| 216 | self.aggregator_cls = MaxPoolingAggregator |
| 217 | elif aggregator_type == "meanpool": |
| 218 | self.aggregator_cls = MeanPoolingAggregator |
| 219 | elif aggregator_type == "gcn": |
| 220 | self.aggregator_cls = GCNAggregator |
| 221 | else: |
| 222 | raise Exception("Unknown aggregator: ", self.aggregator_cls) |
| 223 | |
| 224 | # get info from placeholders... |
| 225 | self.inputs1 = placeholders["batch1"] |
| 226 | self.inputs2 = placeholders["batch2"] |
| 227 | self.model_size = model_size |
| 228 | self.adj_info = adj |
| 229 | if identity_dim > 0: |
| 230 | self.embeds = tf.get_variable("node_embeddings", [adj.get_shape().as_list()[0], identity_dim]) |
| 231 | else: |
| 232 | self.embeds = None |
| 233 | if features is None: |
| 234 | if identity_dim == 0: |
| 235 | raise Exception("Must have a positive value for identity feature dimension if no input features given.") |
| 236 | self.features = self.embeds |
| 237 | else: |
| 238 | self.features = tf.Variable(tf.constant(features, dtype=tf.float32), trainable=False) |
| 239 | if not self.embeds is None: |
| 240 | self.features = tf.concat([self.embeds, self.features], axis=1) |
| 241 | self.degrees = degrees |
| 242 | self.concat = concat |
| 243 | |
| 244 | self.dims = [(0 if features is None else features.shape[1]) + identity_dim] |