Source code for tgp.poolers.kmis

from typing import Optional

import torch
from torch import Tensor
from torch_geometric.typing import Adj

from tgp.connect import SparseConnect
from tgp.lift import BaseLift
from tgp.reduce import BaseReduce
from tgp.select.kmis_select import KMISSelect, SelectOutput
from tgp.src import BasePrecoarseningMixin, PoolingOutput, SRCPooling
from tgp.utils.typing import ConnectionType, LiftType, ReduceType, SinvType


[docs] class KMISPooling(BasePrecoarseningMixin, SRCPooling): r"""The Maximal :math:`k`-Independent Set (:math:`k`-MIS) pooling operator from the paper `"Generalizing Downsampling from Regular Data to Graphs" <https://arxiv.org/abs/2208.03523>`_ (Bacciu et al., AAAI 2023). The :math:`k`-MIS pooling method selects a subset of nodes based on their score and a maximum independent set strategy. The pooling operates by first scoring nodes and then selecting a maximal independent set of nodes, where the score of each node is computed using one of the provided methods in the attribute ``scorer``. The selected nodes are then pooled using the specified aggregation functions, with options to lift the node features using different matrix inversion strategies. + The :math:`\texttt{select}` operator is implemented with :class:`~tgp.select.KMISSelect`. + The :math:`\texttt{reduce}` operator is implemented with :class:`~tgp.reduce.BaseReduce`. + The :math:`\texttt{connect}` operator is implemented with :class:`~tgp.connect.SparseConnect`. + The :math:`\texttt{lift}` operator is implemented with :class:`~tgp.lift.BaseLift`. Args: in_channels (int, optional): Size of each input sample. Ignored if ``scorer`` is not ``"linear"``. (default: :obj:`None`) order_k (int): The :math:`k`-th order for the independent set. (default: ``1``) scorer (str or Callable): A function that computes a score for each node. Nodes with higher score have a higher chance of being selected for pooling. It can be one of: - ``"linear"`` (default): Uses a sigmoid-activated linear layer to compute the scores. ``in_channels`` and ``score_passthrough`` must be set when using this option. - ``"random"``: Assigns a random score in :math:`[0, 1]` to each node. - ``"constant"``: Assigns a constant score of :math:`1` to each node. - ``"canonical"``: Assigns the score :math:`-i` to the :math:`i`-th node. - ``"first"`` (or ``"last"``): Uses the first (or last) feature dimension of :math:`\mathbf{X}` as the node scores. - ``"degree"``: Uses the degree of each node as the score. - A custom function: Accepts the arguments ``(x, edge_index, edge_weight, batch)`` and must return a one-dimensional :class:`~torch.Tensor`. score_heuristic (str, optional): Heuristic to increase the total score of selected nodes. Given an initial score vector :math:`\mathbf{s} \in \mathbb{R}^n`, options include: - :obj:`None`: No heuristic applied. - ``"greedy"`` (default): Computes the updated score :math:`\mathbf{s}'` as .. math:: \mathbf{s}' = \mathbf{s} \oslash (\mathbf{A} + \mathbf{I})^k \mathbf{1} where :math:`\oslash` is element-wise division. - ``"w-greedy"``: Computes the updated score :math:`\mathbf{s}'` as .. math:: \mathbf{s}' = \mathbf{s} \oslash (\mathbf{A} + \mathbf{I})^k \mathbf{s} force_undirected (bool, optional): Whether to force the input graph to be undirected. (default: :obj:`False`) lift (~tgp.utils.typing.LiftType, optional): Defines how to compute the matrix :math:`\mathbf{S}_\text{inv}` to lift the pooled node features. - ``"precomputed"`` (default): Use as :math:`\mathbf{S}_\text{inv}` what is already stored in the ``"s_inv"`` attribute of the :class:`~tgp.select.SelectOutput`. - ``"transpose"``: Recomputes :math:`\mathbf{S}_\text{inv}` as :math:`\mathbf{S}^\top`, the transpose of :math:`\mathbf{S}`. - ``"inverse"``: Recomputes :math:`\mathbf{S}_\text{inv}` as :math:`\mathbf{S}^+`, the Moore-Penrose pseudoinverse of :math:`\mathbf{S}`. s_inv_op (~tgp.utils.typing.SinvType, optional): The operation used to compute :math:`\mathbf{S}_\text{inv}` from the select matrix :math:`\mathbf{S}`. :math:`\mathbf{S}_\text{inv}` is stored in the ``"s_inv"`` attribute of the :class:`~tgp.select.SelectOutput`. It can be one of: - ``"transpose"`` (default): Computes :math:`\mathbf{S}_\text{inv}` as :math:`\mathbf{S}^\top`, the transpose of :math:`\mathbf{S}`. - ``"inverse"``: Computes :math:`\mathbf{S}_\text{inv}` as :math:`\mathbf{S}^+`, the Moore-Penrose pseudoinverse of :math:`\mathbf{S}`. reduce_red_op (~tgp.utils.typing.ReduceType, optional): If :obj:`None`, node features are taken by indexing the MIS nodes (no reduction). Otherwise the reducer is used; the reduce step always computes :math:`\mathbf{S}^\top \mathbf{X}`. (default: ``"sum"``) connect_red_op (~tgp.utils.typing.ConnectionType, optional): The aggregation function to be applied to all edges connecting nodes assigned to supernodes :math:`i` and :math:`j`. Can be any string of class :class:`~tgp.utils.typing.ConnectionType` admitted by :obj:`~torch_geometric.utils.coalesce`, e.g., ``'sum'``, ``'mean'``, ``'max'``) (default: ``"sum"``) lift_red_op (~tgp.utils.typing.ReduceType, optional): The aggregation function to be applied to the lifted node features. Can be any string of class :class:`~tgp.utils.typing.ReduceType` admitted by :obj:`~torch_geometric.utils.scatter`, e.g., ``'sum'``, ``'mean'``, ``'max'``) (default: ``"sum"``) remove_self_loops (bool, optional): Whether to remove self-loops from the graph after coarsening. (default: :obj:`True`) degree_norm (bool, optional): If :obj:`True`, the adjacency matrix will be symmetrically normalized. (default: :obj:`False`) edge_weight_norm (bool, optional): Whether to normalize the edge weights by dividing by the maximum absolute value per graph. (default: :obj:`False`) cached (bool, optional): If set to :obj:`True`, the output of the :math:`\texttt{select}` and :math:`\texttt{select}` operations will be cached, so that they do not need to be recomputed. If :obj:`True`, the scorer cannot be ``"linear"``. (default: :obj:`False`) """ def __init__( self, in_channels: Optional[int] = None, order_k: int = 1, scorer: str = "linear", score_heuristic: Optional[str] = "greedy", force_undirected: bool = False, lift: LiftType = "precomputed", s_inv_op: SinvType = "transpose", reduce_red_op: Optional[ReduceType] = "sum", connect_red_op: ConnectionType = "sum", lift_red_op: ReduceType = "sum", remove_self_loops: bool = True, degree_norm: bool = False, edge_weight_norm: bool = False, cached: bool = False, ): super().__init__( selector=KMISSelect( in_channels=in_channels, order_k=order_k, scorer=scorer, score_heuristic=score_heuristic, force_undirected=force_undirected, s_inv_op=s_inv_op, ), reducer=BaseReduce(), lifter=BaseLift(matrix_op=lift, reduce_op=lift_red_op), connector=SparseConnect( reduce_op=connect_red_op, remove_self_loops=remove_self_loops, degree_norm=degree_norm, edge_weight_norm=edge_weight_norm, ), cached=cached, ) self.reduce_red_op = reduce_red_op self.cached = cached self.precoarsenable = scorer in ["random", "constant", "canonical", "degree"] if cached and scorer == "linear" or callable(scorer): raise Exception( "Caching should be disabled when using a linear scorer or a callable scorer." )
[docs] def forward( self, x: Tensor, adj: Optional[Adj] = None, edge_weight: Optional[Tensor] = None, so: Optional[SelectOutput] = None, batch: Optional[Tensor] = None, lifting: bool = False, **kwargs, ) -> PoolingOutput: r"""Forward pass. Args: x (~torch.Tensor): The node feature matrix of shape :math:`[N, F]`, where :math:`N` is the number of nodes in the batch and :math:`F` is the number of node features. adj (~torch_geometric.typing.Adj, optional): The connectivity matrix. It can either be a ``torch_sparse.SparseTensor`` of (sparse) shape :math:`[N, N]`, where :math:`N` is the number of nodes in the batch or a :obj:`~torch.Tensor` of shape :math:`[2, E]`, where :math:`E` is the number of edges in the batch. If ``lifting`` is :obj:`False`, it cannot be :obj:`None`. (default: :obj:`None`) edge_weight (~torch.Tensor, optional): A vector of shape :math:`[E]` containing the weights of the edges. (default: :obj:`None`) so (~tgp.select.SelectOutput, optional): The output of the :math:`\texttt{select}` operator. (default: :obj:`None`) batch (torch.Tensor, optional): The batch vector :math:`\mathbf{b} \in {\{ 0, \ldots, B-1\}}^N`, which indicates to which graph in the batch each node belongs. (default: :obj:`None`) lifting (bool, optional): If set to :obj:`True`, the :math:`\texttt{lift}` operation is performed. (default: :obj:`False`) Returns: PoolingOutput: The output of the pooling operator. """ if lifting: # Lift x_lifted = self.lift(x_pool=x, so=so) return x_lifted else: # Select so = self.select(x=x, edge_index=adj, edge_weight=edge_weight, batch=batch) # Reduce if self.reduce_red_op is None: x_pooled = torch.index_select(x, index=so.mis, dim=-2) x_pooled = x_pooled * so.weight[so.mis].view(-1, 1) batch_pooled = None if batch is None else batch[so.mis] else: x_pooled, batch_pooled = self.reduce(x=x, so=so, batch=batch) # Connect edge_index_pooled, edge_weight_pooled = self.connect( edge_index=adj, so=so, edge_weight=edge_weight, batch_pooled=batch_pooled, ) out = PoolingOutput( x=x_pooled, edge_index=edge_index_pooled, edge_weight=edge_weight_pooled, batch=batch_pooled, so=so, ) return out
def extra_repr_args(self) -> dict: return {"cached": self.cached}