chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:36:30 +08:00
commit 55ab4e4a73
473 changed files with 72932 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
try:
from .convs.common import MLP
from .convs.common import MultiHeadWrapper
from .convs.hypergraphs import HGNNConv
from .convs.hypergraphs import HGNNPConv
from .convs.hypergraphs import HNHNConv
from .convs.hypergraphs import HWNNConv
from .convs.hypergraphs import HyperGCNConv
from .convs.hypergraphs import JHConv
from .convs.hypergraphs import UniGATConv
from .convs.hypergraphs import UniGCNConv
from .convs.hypergraphs import UniGINConv
from .convs.hypergraphs import UniSAGEConv
from .convs.hypergraphs.halfnlh_conv import HalfNLHconv
from .convs.pma import PMA
from .loss import BPRLoss
from .regularization import EmbeddingRegularization
except:
print(
"Warning raise in module:nn. Please install Pytorch, torch_geometric,"
" torch_scatter before you use functions related to AllDeepSet and"
" AllSetTransformer."
)
+1
View File
@@ -0,0 +1 @@
+136
View File
@@ -0,0 +1,136 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
class MultiHeadWrapper(nn.Module):
r"""A wrapper to apply multiple heads to a given layer.
Parameters:
``num_heads`` (``int``): The number of heads.
``readout`` (``bool``): The readout method. Can be ``"mean"``, ``"max"``, ``"sum"``, or ``"concat"``.
``layer`` (``nn.Module``): The layer to apply multiple heads.
``**kwargs``: The keyword arguments for the layer.
"""
def __init__(
self, num_heads: int, readout: str, layer: nn.Module, **kwargs
) -> None:
super().__init__()
self.layers = nn.ModuleList()
for _ in range(num_heads):
self.layers.append(layer(**kwargs))
self.num_heads = num_heads
self.readout = readout
def forward(self, **kwargs) -> torch.Tensor:
r"""The forward function.
.. note::
You must explicitly pass the keyword arguments to the layer. For example, if the layer is ``GATConv``, you must pass ``X=X`` and ``g=g``.
"""
if self.readout == "concat":
return torch.cat([layer(**kwargs) for layer in self.layers], dim=-1)
else:
outs = torch.stack([layer(**kwargs) for layer in self.layers])
if self.readout == "mean":
return outs.mean(dim=0)
elif self.readout == "max":
return outs.max(dim=0)[0]
elif self.readout == "sum":
return outs.sum(dim=0)
else:
raise ValueError("Unknown readout type")
class MLP(nn.Module):
"""adapted from https://github.com/CUAI/CorrectAndSmooth/blob/master/gen_models.py
"""
def __init__(
self,
in_channels,
hidden_channels,
out_channels,
num_layers,
dropout=0.5,
normalization="bn",
InputNorm=False,
):
super(MLP, self).__init__()
self.lins = nn.ModuleList()
self.normalizations = nn.ModuleList()
self.InputNorm = InputNorm
assert normalization in ["bn", "ln", "None"]
if normalization == "bn":
if num_layers == 1:
# just linear layer i.e. logistic regression
if InputNorm:
self.normalizations.append(nn.BatchNorm1d(in_channels))
else:
self.normalizations.append(nn.Identity())
self.lins.append(nn.Linear(in_channels, out_channels))
else:
if InputNorm:
self.normalizations.append(nn.BatchNorm1d(in_channels))
else:
self.normalizations.append(nn.Identity())
self.lins.append(nn.Linear(in_channels, hidden_channels))
self.normalizations.append(nn.BatchNorm1d(hidden_channels))
for _ in range(num_layers - 2):
self.lins.append(nn.Linear(hidden_channels, hidden_channels))
self.normalizations.append(nn.BatchNorm1d(hidden_channels))
self.lins.append(nn.Linear(hidden_channels, out_channels))
elif normalization == "ln":
if num_layers == 1:
# just linear layer i.e. logistic regression
if InputNorm:
self.normalizations.append(nn.LayerNorm(in_channels))
else:
self.normalizations.append(nn.Identity())
self.lins.append(nn.Linear(in_channels, out_channels))
else:
if InputNorm:
self.normalizations.append(nn.LayerNorm(in_channels))
else:
self.normalizations.append(nn.Identity())
self.lins.append(nn.Linear(in_channels, hidden_channels))
self.normalizations.append(nn.LayerNorm(hidden_channels))
for _ in range(num_layers - 2):
self.lins.append(nn.Linear(hidden_channels, hidden_channels))
self.normalizations.append(nn.LayerNorm(hidden_channels))
self.lins.append(nn.Linear(hidden_channels, out_channels))
else:
if num_layers == 1:
# just linear layer i.e. logistic regression
self.normalizations.append(nn.Identity())
self.lins.append(nn.Linear(in_channels, out_channels))
else:
self.normalizations.append(nn.Identity())
self.lins.append(nn.Linear(in_channels, hidden_channels))
self.normalizations.append(nn.Identity())
for _ in range(num_layers - 2):
self.lins.append(nn.Linear(hidden_channels, hidden_channels))
self.normalizations.append(nn.Identity())
self.lins.append(nn.Linear(hidden_channels, out_channels))
self.dropout = dropout
def reset_parameters(self):
for lin in self.lins:
lin.reset_parameters()
for normalization in self.normalizations:
if not (normalization.__class__.__name__ is "Identity"):
normalization.reset_parameters()
def forward(self, x):
x = self.normalizations[0](x)
for i, lin in enumerate(self.lins[:-1]):
x = lin(x)
x = F.relu(x, inplace=True)
x = self.normalizations[i + 1](x)
x = F.dropout(x, p=self.dropout, training=self.training)
x = self.lins[-1](x)
return x
@@ -0,0 +1,10 @@
from .dhcf_conv import JHConv
from .hgnn_conv import HGNNConv
from .hgnnp_conv import HGNNPConv
from .hnhn_conv import HNHNConv
from .hwnn_conv import HWNNConv
from .hypergcn_conv import HyperGCNConv
from .unignn_conv import UniGATConv
from .unignn_conv import UniGCNConv
from .unignn_conv import UniGINConv
from .unignn_conv import UniSAGEConv
@@ -0,0 +1,54 @@
import torch
import torch.nn as nn
from easygraph.classes import Hypergraph
class JHConv(nn.Module):
r"""The Jump Hypergraph Convolution layer proposed in `Dual Channel Hypergraph Collaborative Filtering <https://dl.acm.org/doi/10.1145/3394486.3403253>`_ paper (KDD 2020).
Matrix Format:
.. math::
\mathbf{X}^{\prime} = \sigma \left( \mathbf{D}_v^{-\frac{1}{2}} \mathbf{H} \mathbf{W}_e \mathbf{D}_e^{-1}
\mathbf{H}^\top \mathbf{D}_v^{-\frac{1}{2}} \mathbf{X} \mathbf{\Theta} + \mathbf{X} \right).
Parameters:
``in_channels`` (``int``): :math:`C_{in}` is the number of input channels.
``out_channels`` (int): :math:`C_{out}` is the number of output channels.
``bias`` (``bool``): If set to ``False``, the layer will not learn the bias parameter. Defaults to ``True``.
``use_bn`` (``bool``): If set to ``True``, the layer will use batch normalization. Defaults to ``False``.
``drop_rate`` (``float``): If set to a positive number, the layer will use dropout. Defaults to ``0.5``.
``is_last`` (``bool``): If set to ``True``, the layer will not apply the final activation and dropout functions. Defaults to ``False``.
"""
def __init__(
self,
in_channels: int,
out_channels: int,
bias: bool = True,
use_bn: bool = False,
drop_rate: float = 0.5,
is_last: bool = False,
):
super().__init__()
self.is_last = is_last
self.bn = nn.BatchNorm1d(out_channels) if use_bn else None
self.act = nn.ReLU(inplace=True)
self.drop = nn.Dropout(drop_rate)
self.theta = nn.Linear(in_channels, out_channels, bias=bias)
def forward(self, X: torch.Tensor, hg: Hypergraph) -> torch.Tensor:
r"""The forward function.
Parameters:
X (``torch.Tensor``): Input vertex feature matrix. Size :math:`(N, C_{in})`.
hg (``eg.Hypergraph``): The hypergraph structure that contains :math:`N` vertices.
"""
X = self.theta(X)
if self.bn is not None:
X = self.bn(X)
X = hg.smoothing_with_HGNN(X) + X
if not self.is_last:
X = self.drop(self.act(X))
return X
@@ -0,0 +1,112 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
from easygraph.nn.convs.common import MLP
from easygraph.nn.convs.pma import PMA
from torch_geometric.nn.conv import MessagePassing
from torch_scatter import scatter
class HalfNLHconv(MessagePassing):
r"""The HalfNLHconv model proposed in `YOU ARE ALLSET: A MULTISET LEARNING FRAMEWORK FOR HYPERGRAPH NEURAL NETWORKS <https://openreview.net/pdf?id=hpBTIv2uy_E>`_ paper (ICLR 2022).
Parameters:
``in_dim`` (``int``): : The dimension of input.
``hid_dim`` (``int``): : The dimension of hidden.
``out_dim`` (``int``): : The dimension of output.
``num_layers`` (``int``): : The number of layers.
``dropout`` (``float``): Dropout ratio. Defaults to 0.5.
``normalization`` (``str``): The normalization method. Defaults to ``bn``
``InputNorm`` (``bool``): Defaults to False.
``heads`` (``int``): Defaults to 1
`attention`` (``bool``): Defaults to True
"""
def __init__(
self,
in_dim,
hid_dim,
out_dim,
num_layers,
dropout,
normalization="bn",
InputNorm=False,
heads=1,
attention=True,
):
super(HalfNLHconv, self).__init__()
self.attention = attention
self.dropout = dropout
if self.attention:
self.prop = PMA(in_dim, hid_dim, out_dim, num_layers, heads=heads)
else:
if num_layers > 0:
self.f_enc = MLP(
in_dim,
hid_dim,
hid_dim,
num_layers,
dropout,
normalization,
InputNorm,
)
self.f_dec = MLP(
hid_dim,
hid_dim,
out_dim,
num_layers,
dropout,
normalization,
InputNorm,
)
else:
self.f_enc = nn.Identity()
self.f_dec = nn.Identity()
def reset_parameters(self):
if self.attention:
self.prop.reset_parameters()
else:
if not (self.f_enc.__class__.__name__ is "Identity"):
self.f_enc.reset_parameters()
if not (self.f_dec.__class__.__name__ is "Identity"):
self.f_dec.reset_parameters()
# self.bn.reset_parameters()
def forward(self, x, edge_index, norm, aggr="add"):
"""
input -> MLP -> Prop
"""
if self.attention:
x = self.prop(x, edge_index)
else:
x = F.relu(self.f_enc(x))
x = F.dropout(x, p=self.dropout, training=self.training)
x = self.propagate(edge_index, x=x, norm=norm, aggr=aggr)
x = F.relu(self.f_dec(x))
return x
def message(self, x_j, norm):
return norm.view(-1, 1) * x_j
def aggregate(self, inputs, index, dim_size=None, aggr="sum"):
r"""Aggregates messages from neighbors as
:math:`\square_{j \in \mathcal{N}(i)}`.
Takes in the output of message computation as first argument and any
argument which was initially passed to :meth:`propagate`.
By default, this function will delegate its call to scatter functions
that support "add", "mean" and "max" operations as specified in
:meth:`__init__` by the :obj:`aggr` argument.
"""
# ipdb.set_trace()
return scatter(inputs, index, dim=self.node_dim, reduce=aggr)
@@ -0,0 +1,57 @@
import torch
import torch.nn as nn
from easygraph.classes import Hypergraph
class HGNNConv(nn.Module):
r"""The HGNN convolution layer proposed in `Hypergraph Neural Networks <https://arxiv.org/pdf/1809.09401>`_ paper (AAAI 2019).
Matrix Format:
.. math::
\mathbf{X}^{\prime} = \sigma \left( \mathbf{D}_v^{-\frac{1}{2}} \mathbf{H} \mathbf{W}_e \mathbf{D}_e^{-1}
\mathbf{H}^\top \mathbf{D}_v^{-\frac{1}{2}} \mathbf{X} \mathbf{\Theta} \right).
where :math:`\mathbf{X}` is the input vertex feature matrix, :math:`\mathbf{H}` is the hypergraph incidence matrix,
:math:`\mathbf{W}_e` is a diagonal hyperedge weight matrix, :math:`\mathbf{D}_v` is a diagonal vertex degree matrix,
:math:`\mathbf{D}_e` is a diagonal hyperedge degree matrix, :math:`\mathbf{\Theta}` is the learnable parameters.
Parameters:
``in_channels`` (``int``): :math:`C_{in}` is the number of input channels.
``out_channels`` (int): :math:`C_{out}` is the number of output channels.
``bias`` (``bool``): If set to ``False``, the layer will not learn the bias parameter. Defaults to ``True``.
``use_bn`` (``bool``): If set to ``True``, the layer will use batch normalization. Defaults to ``False``.
``drop_rate`` (``float``): If set to a positive number, the layer will use dropout. Defaults to ``0.5``.
``is_last`` (``bool``): If set to ``True``, the layer will not apply the final activation and dropout functions. Defaults to ``False``.
"""
def __init__(
self,
in_channels: int,
out_channels: int,
bias: bool = True,
use_bn: bool = False,
drop_rate: float = 0.5,
is_last: bool = False,
):
super().__init__()
self.is_last = is_last
self.bn = nn.BatchNorm1d(out_channels) if use_bn else None
self.act = nn.ReLU(inplace=True)
self.drop = nn.Dropout(drop_rate)
self.theta = nn.Linear(in_channels, out_channels, bias=bias)
def forward(self, X: torch.Tensor, hg: Hypergraph) -> torch.Tensor:
r"""The forward function.
Parameters:
X (``torch.Tensor``): Input vertex feature matrix. Size :math:`(N, C_{in})`.
hg (``eg.Hypergraph``): The hypergraph structure that contains :math:`N` vertices.
"""
X = self.theta(X)
if self.bn is not None:
X = self.bn(X)
X = hg.smoothing_with_HGNN(X)
if not self.is_last:
X = self.drop(self.act(X))
return X
@@ -0,0 +1,67 @@
import torch
import torch.nn as nn
from easygraph.classes import Hypergraph
class HGNNPConv(nn.Module):
r"""The HGNN :sup:`+` convolution layer proposed in `HGNN+: General Hypergraph Neural Networks <https://ieeexplore.ieee.org/document/9795251>`_ paper (IEEE T-PAMI 2022).
Sparse Format:
.. math::
\left\{
\begin{aligned}
m_{\beta}^{t} &=\sum_{\alpha \in \mathcal{N}_{v}(\beta)} M_{v}^{t}\left(x_{\alpha}^{t}\right) \\
y_{\beta}^{t} &=U_{e}^{t}\left(w_{\beta}, m_{\beta}^{t}\right) \\
m_{\alpha}^{t+1} &=\sum_{\beta \in \mathcal{N}_{e}(\alpha)} M_{e}^{t}\left(x_{\alpha}^{t}, y_{\beta}^{t}\right) \\
x_{\alpha}^{t+1} &=U_{v}^{t}\left(x_{\alpha}^{t}, m_{\alpha}^{t+1}\right) \\
\end{aligned}
\right.
Matrix Format:
.. math::
\mathbf{X}^{\prime} = \sigma \left( \mathbf{D}_v^{-1} \mathbf{H} \mathbf{W}_e
\mathbf{D}_e^{-1} \mathbf{H}^\top \mathbf{X} \mathbf{\Theta} \right).
Parameters:
``in_channels`` (``int``): :math:`C_{in}` is the number of input channels.
``out_channels`` (int): :math:`C_{out}` is the number of output channels.
``bias`` (``bool``): If set to ``False``, the layer will not learn the bias parameter. Defaults to ``True``.
``use_bn`` (``bool``): If set to ``True``, the layer will use batch normalization. Defaults to ``False``.
``drop_rate`` (``float``): If set to a positive number, the layer will use dropout. Defaults to ``0.5``.
``is_last`` (``bool``): If set to ``True``, the layer will not apply the final activation and dropout functions. Defaults to ``False``.
"""
def __init__(
self,
in_channels: int,
out_channels: int,
bias: bool = True,
use_bn: bool = False,
drop_rate: float = 0.5,
is_last: bool = False,
):
super().__init__()
self.is_last = is_last
self.bn = nn.BatchNorm1d(out_channels) if use_bn else None
self.act = nn.ReLU(inplace=True)
self.drop = nn.Dropout(drop_rate)
self.theta = nn.Linear(in_channels, out_channels, bias=bias)
def forward(self, X: torch.Tensor, hg: Hypergraph) -> torch.Tensor:
r"""The forward function.
Parameters:
X (``torch.Tensor``): Input vertex feature matrix. Size :math:`(|\mathcal{V}|, C_{in})`.
hg (``eg.Hypergraph``): The hypergraph structure that contains :math:`|\mathcal{V}|` vertices.
"""
X = self.theta(X)
if self.bn is not None:
X = self.bn(X)
X = hg.v2v(X, aggr="mean")
if not self.is_last:
X = self.drop(self.act(X))
return X
@@ -0,0 +1,53 @@
import torch
import torch.nn as nn
from easygraph.classes import Hypergraph
class HNHNConv(nn.Module):
r"""The HNHN convolution layer proposed in `HNHN: Hypergraph Networks with Hyperedge Neurons <https://arxiv.org/pdf/2006.12278.pdf>`_ paper (ICML 2020).
Parameters:
``in_channels`` (``int``): :math:`C_{in}` is the number of input channels.
``out_channels`` (int): :math:`C_{out}` is the number of output channels.
``bias`` (``bool``): If set to ``False``, the layer will not learn the bias parameter. Defaults to ``True``.
``use_bn`` (``bool``): If set to ``True``, the layer will use batch normalization. Defaults to ``False``.
``drop_rate`` (``float``): If set to a positive number, the layer will use dropout. Defaults to ``0.5``.
``is_last`` (``bool``): If set to ``True``, the layer will not apply the final activation and dropout functions. Defaults to ``False``.
"""
def __init__(
self,
in_channels: int,
out_channels: int,
bias: bool = True,
use_bn: bool = False,
drop_rate: float = 0.5,
is_last: bool = False,
):
super().__init__()
self.is_last = is_last
self.bn = nn.BatchNorm1d(out_channels) if use_bn else None
self.act = nn.ReLU(inplace=True)
self.drop = nn.Dropout(drop_rate)
self.theta_v2e = nn.Linear(in_channels, out_channels, bias=bias)
self.theta_e2v = nn.Linear(out_channels, out_channels, bias=bias)
def forward(self, X: torch.Tensor, hg: Hypergraph) -> torch.Tensor:
r"""The forward function.
Parameters:
X (``torch.Tensor``): Input vertex feature matrix. Size :math:`(|\mathcal{V}|, C_{in})`.
hg (``eg.Hypergraph``): The hypergraph structure that contains :math:`|\mathcal{V}|` vertices.
"""
# v -> e
X = self.theta_v2e(X)
if self.bn is not None:
X = self.bn(X)
Y = self.act(hg.v2e(X, aggr="mean"))
# e -> v
Y = self.theta_e2v(Y)
X = hg.e2v(Y, aggr="mean")
if not self.is_last:
X = self.drop(self.act(X))
return X
@@ -0,0 +1,58 @@
import torch
import torch.nn as nn
from easygraph.classes import Hypergraph
class HWNNConv(nn.Module):
r"""The HWNNConv model proposed in `Heterogeneous Hypergraph Embedding for Graph Classification <https://arxiv.org/pdf/2010.10728>`_ paper (WSDM 2021).
Parameters:
``in_channels`` (``int``): :math:`C_{in}` is the number of input channels.
``out_channels`` (``int``): :math:`C_{out}` is the number of output channels.
``ncount`` (``int``): The Number of node in the hypergraph.
``K1`` (``int``): Polynomial calculation times.
``K2`` (``int``): Polynomial calculation times.
``approx`` (``bool``): Whether to use polynomial fitting
"""
def __init__(
self,
in_channels: int,
out_channels: int,
ncount: int,
K1: int = 2,
K2: int = 2,
approx: bool = False,
):
super().__init__()
self.in_channels = in_channels
self.out_channels = out_channels
self.K1 = K1
self.K2 = K2
self.ncount = ncount
self.approx = approx
self.W = torch.nn.Parameter(torch.Tensor(self.in_channels, self.out_channels))
self.W_d = torch.nn.Parameter(torch.Tensor(self.ncount))
self.par = torch.nn.Parameter(torch.Tensor(self.K1 + self.K2))
self.init_parameters()
def init_parameters(self):
torch.nn.init.xavier_uniform_(self.W)
torch.nn.init.uniform_(self.W_d, 0.99, 1.01)
torch.nn.init.uniform_(self.par, 0, 0.99)
def forward(self, X: torch.Tensor, hg: Hypergraph) -> torch.Tensor:
r"""The forward function.
Parameters:
X (``torch.Tensor``): Input vertex feature matrix. Size :math:`(N, C_{in})`.
hg (``eg.Hypergraph``): The hypergraph structure that contains :math:`N` vertices.
"""
if self.approx == True:
X = hg.smoothing_with_HWNN_approx(
X, self.par, self.W_d, self.K1, self.K2, self.W
)
else:
X = hg.smoothing_with_HWNN_wavelet(X, self.W_d, self.W)
return X
@@ -0,0 +1,61 @@
from typing import Optional
import torch
import torch.nn as nn
from easygraph.classes import Graph
from easygraph.classes import Hypergraph
class HyperGCNConv(nn.Module):
r"""The HyperGCN convolution layer proposed in `HyperGCN: A New Method of Training Graph Convolutional Networks on Hypergraphs <https://papers.nips.cc/paper/2019/file/1efa39bcaec6f3900149160693694536-Paper.pdf>`_ paper (NeurIPS 2019).
Parameters:
``in_channels`` (``int``): :math:`C_{in}` is the number of input channels.
``out_channels`` (int): :math:`C_{out}` is the number of output channels.
``use_mediator`` (``str``): Whether to use mediator to transform the hyperedges to edges in the graph. Defaults to ``False``.
``bias`` (``bool``): If set to ``False``, the layer will not learn the bias parameter. Defaults to ``True``.
``use_bn`` (``bool``): If set to ``True``, the layer will use batch normalization. Defaults to ``False``.
``drop_rate`` (``float``): If set to a positive number, the layer will use dropout. Defaults to ``0.5``.
``is_last`` (``bool``): If set to ``True``, the layer will not apply the final activation and dropout functions. Defaults to ``False``.
"""
def __init__(
self,
in_channels: int,
out_channels: int,
use_mediator: bool = False,
bias: bool = True,
use_bn: bool = False,
drop_rate: float = 0.5,
is_last: bool = False,
):
super().__init__()
self.is_last = is_last
self.bn = nn.BatchNorm1d(out_channels) if use_bn else None
self.use_mediator = use_mediator
self.act = nn.ReLU(inplace=True)
self.drop = nn.Dropout(drop_rate)
self.theta = nn.Linear(in_channels, out_channels, bias=bias)
def forward(
self, X: torch.Tensor, hg: Hypergraph, cached_g: Optional[Graph] = None
) -> torch.Tensor:
r"""The forward function.
Parameters:
``X`` (``torch.Tensor``): Input vertex feature matrix. Size :math:`(N, C_{in})`.
``hg`` (``eg.Hypergraph``): The hypergraph structure that contains :math:`N` vertices.
``cached_g`` (``eg.Graph``): The pre-transformed graph structure from the hypergraph structure that contains :math:`N` vertices. If not provided, the graph structure will be transformed for each forward time. Defaults to ``None``.
"""
X = self.theta(X)
if self.bn is not None:
X = self.bn(X)
if cached_g is None:
g = Graph.from_hypergraph_hypergcn(hg, X, self.use_mediator)
X = g.smoothing_with_GCN(X)
else:
X = cached_g.smoothing_with_GCN(X)
if not self.is_last:
X = self.drop(self.act(X))
return X
@@ -0,0 +1,289 @@
import torch
import torch.nn as nn
from easygraph.classes import Hypergraph
class UniGCNConv(nn.Module):
r"""The UniGCN convolution layer proposed in `UniGNN: a Unified Framework for Graph and Hypergraph Neural Networks <https://arxiv.org/pdf/2105.00956.pdf>`_ paper (IJCAI 2021).
Sparse Format:
.. math::
\left\{
\begin{aligned}
h_{e} &= \frac{1}{|e|} \sum_{j \in e} x_{j} \\
\tilde{x}_{i} &= \frac{1}{\sqrt{d_{i}}} \sum_{e \in \tilde{E}_{i}} \frac{1}{\sqrt{\tilde{d}_{e}}} W h_{e}
\end{aligned}
\right. .
where :math:`\tilde{d}_{e} = \frac{1}{|e|} \sum_{i \in e} d_{i}`.
Matrix Format:
.. math::
\mathbf{X}^{\prime} = \sigma \left( \mathbf{D}_v^{-\frac{1}{2}} \mathbf{H} \tilde{\mathbf{D}}_e^{-\frac{1}{2}} \cdot \mathbf{D}_e^{-1} \mathbf{H}^\top \mathbf{X} \mathbf{\Theta} \right) .
Parameters:
``in_channels`` (``int``): :math:`C_{in}` is the number of input channels.
``out_channels`` (int): :math:`C_{out}` is the number of output channels.
``bias`` (``bool``): If set to ``False``, the layer will not learn the bias parameter. Defaults to ``True``.
``use_bn`` (``bool``): If set to ``True``, the layer will use batch normalization. Defaults to ``False``.
``drop_rate`` (``float``): If set to a positive number, the layer will use dropout. Defaults to ``0.5``.
``is_last`` (``bool``): If set to ``True``, the layer will not apply the final activation and dropout functions. Defaults to ``False``.
"""
def __init__(
self,
in_channels: int,
out_channels: int,
bias: bool = True,
use_bn: bool = False,
drop_rate: float = 0.5,
is_last: bool = False,
):
super().__init__()
self.is_last = is_last
self.bn = nn.BatchNorm1d(out_channels) if use_bn else None
self.act = nn.ReLU(inplace=True)
self.drop = nn.Dropout(drop_rate)
self.theta = nn.Linear(in_channels, out_channels, bias=bias)
def forward(self, X: torch.Tensor, hg: Hypergraph) -> torch.Tensor:
r"""The forward function.
Parameters:
X (``torch.Tensor``): Input vertex feature matrix. Size :math:`(|\mathcal{V}|, C_{in})`.
hg (``eg.Hypergraph``): The hypergraph structure that contains :math:`|\mathcal{V}|` vertices.
"""
X = self.theta(X)
Y = hg.v2e(X, aggr="mean")
# compute the special degree of hyperedges
# _De = torch.zeros(hg.num_e, device=hg.device)
_De = torch.zeros(hg.num_e)
_Dv = hg.D_v._values()[hg.v2e_src]
_De = (
_De.scatter_reduce(0, index=hg.v2e_dst, src=_Dv, reduce="mean")
/ _De.scatter_reduce(
0, index=hg.v2e_dst, src=(_Dv != 0).float(), reduce="sum"
)
).pow(-0.5)
_De[_De.isinf()] = 1
Y = _De.view(-1, 1) * Y
# ===============================================
X = hg.e2v(Y, aggr="sum")
X = torch.sparse.mm(hg.D_v_neg_1_2, X)
if not self.is_last:
X = self.act(X)
if self.bn is not None:
X = self.bn(X)
X = self.drop(X)
return X
class UniGATConv(nn.Module):
r"""The UniGAT convolution layer proposed in `UniGNN: a Unified Framework for Graph and Hypergraph Neural Networks <https://arxiv.org/pdf/2105.00956.pdf>`_ paper (IJCAI 2021).
Sparse Format:
.. math::
\left\{
\begin{aligned}
\alpha_{i e} &=\sigma\left(a^{T}\left[W h_{\{i\}} ; W h_{e}\right]\right) \\
\tilde{\alpha}_{i e} &=\frac{\exp \left(\alpha_{i e}\right)}{\sum_{e^{\prime} \in \tilde{E}_{i}} \exp \left(\alpha_{i e^{\prime}}\right)} \\
\tilde{x}_{i} &=\sum_{e \in \tilde{E}_{i}} \tilde{\alpha}_{i e} W h_{e}
\end{aligned}
\right. .
Parameters:
``in_channels`` (``int``): :math:`C_{in}` is the number of input channels.
``out_channels`` (int): :math:`C_{out}` is the number of output channels.
``bias`` (``bool``): If set to ``False``, the layer will not learn the bias parameter. Defaults to ``True``.
``use_bn`` (``bool``): If set to ``True``, the layer will use batch normalization. Defaults to ``False``.
``drop_rate`` (``float``): The dropout probability. If ``dropout <= 0``, the layer will not drop values. Defaults to ``0.5``.
``atten_neg_slope`` (``float``): Hyper-parameter of the ``LeakyReLU`` activation of edge attention. Defaults to ``0.2``.
``is_last`` (``bool``): If set to ``True``, the layer will not apply the final activation and dropout functions. Defaults to ``False``.
"""
def __init__(
self,
in_channels: int,
out_channels: int,
bias: bool = True,
use_bn: bool = False,
drop_rate: float = 0.5,
atten_neg_slope: float = 0.2,
is_last: bool = False,
):
super().__init__()
self.is_last = is_last
self.bn = nn.BatchNorm1d(out_channels) if use_bn else None
self.atten_dropout = nn.Dropout(drop_rate)
self.atten_act = nn.LeakyReLU(atten_neg_slope)
self.act = nn.ELU(inplace=True)
self.theta = nn.Linear(in_channels, out_channels, bias=bias)
self.atten_e = nn.Linear(out_channels, 1, bias=False)
self.atten_dst = nn.Linear(out_channels, 1, bias=False)
def forward(self, X: torch.Tensor, hg: Hypergraph) -> torch.Tensor:
r"""The forward function.
Parameters:
X (``torch.Tensor``): Input vertex feature matrix. Size :math:`(|\mathcal{V}|, C_{in})`.
hg (``eg.Hypergraph``): The hypergraph structure that contains :math:`|\mathcal{V}|` vertices.
"""
X = self.theta(X)
Y = hg.v2e(X, aggr="mean")
# ===============================================
# alpha_e = self.atten_e(Y)
# e_atten_score = alpha_e[hg.e2v_src]
# e_atten_score = self.atten_dropout(self.atten_act(e_atten_score).squeeze())
e_atten_score = self.atten_dropout(
self.atten_act(self.atten_e(Y)[hg.e2v_src]).squeeze()
)
# ================================================================================
# We suggest to add a clamp on attention weight to avoid Nan error in training.
e_atten_score.clamp_(min=0.001, max=5)
# ================================================================================
X = hg.e2v(Y, aggr="softmax_then_sum", e2v_weight=e_atten_score)
if not self.is_last:
X = self.act(X)
if self.bn is not None:
X = self.bn(X)
return X
class UniSAGEConv(nn.Module):
r"""The UniSAGE convolution layer proposed in `UniGNN: a Unified Framework for Graph and Hypergraph Neural Networks <https://arxiv.org/pdf/2105.00956.pdf>`_ paper (IJCAI 2021).
Sparse Format:
.. math::
\left\{
\begin{aligned}
h_{e} &= \frac{1}{|e|} \sum_{j \in e} x_{j} \\
\tilde{x}_{i} &= W\left(x_{i}+\text { AGGREGATE }\left(\left\{x_{j}\right\}_{j \in \mathcal{N}_{i}}\right)\right)
\end{aligned}
\right. .
Matrix Format:
.. math::
\mathbf{X}^{\prime} = \sigma \left( \left( \mathbf{I} + \mathbf{H} \mathbf{D}_e^{-1} \mathbf{H}^\top \right) \mathbf{X} \mathbf{\Theta} \right) .
Parameters:
``in_channels`` (``int``): :math:`C_{in}` is the number of input channels.
``out_channels`` (int): :math:`C_{out}` is the number of output channels.
``bias`` (``bool``): If set to ``False``, the layer will not learn the bias parameter. Defaults to ``True``.
``use_bn`` (``bool``): If set to ``True``, the layer will use batch normalization. Defaults to ``False``.
``drop_rate`` (``float``): If set to a positive number, the layer will use dropout. Defaults to ``0.5``.
``is_last`` (``bool``): If set to ``True``, the layer will not apply the final activation and dropout functions. Defaults to ``False``.
"""
def __init__(
self,
in_channels: int,
out_channels: int,
bias: bool = True,
use_bn: bool = False,
drop_rate: float = 0.5,
is_last: bool = False,
):
super().__init__()
self.is_last = is_last
self.bn = nn.BatchNorm1d(out_channels) if use_bn else None
self.act = nn.ReLU(inplace=True)
self.drop = nn.Dropout(drop_rate)
self.theta = nn.Linear(in_channels, out_channels, bias=bias)
def forward(self, X: torch.Tensor, hg: Hypergraph) -> torch.Tensor:
r"""The forward function.
Parameters:
X (``torch.Tensor``): Input vertex feature matrix. Size :math:`(|\mathcal{V}|, C_{in})`.
hg (``eg.Hypergraph``): The hypergraph structure that contains :math:`|\mathcal{V}|` vertices.
"""
X = self.theta(X)
Y = hg.v2e(X, aggr="mean")
X = hg.e2v(Y, aggr="sum") + X
if not self.is_last:
X = self.act(X)
if self.bn is not None:
X = self.bn(X)
X = self.drop(X)
return X
class UniGINConv(nn.Module):
r"""The UniGIN convolution layer proposed in `UniGNN: a Unified Framework for Graph and Hypergraph Neural Networks <https://arxiv.org/pdf/2105.00956.pdf>`_ paper (IJCAI 2021).
Sparse Format:
.. math::
\left\{
\begin{aligned}
h_{e} &= \frac{1}{|e|} \sum_{j \in e} x_{j} \\
\tilde{x}_{i} &= W\left((1+\varepsilon) x_{i}+\sum_{e \in E_{i}} h_{e}\right)
\end{aligned}
\right. .
Matrix Format:
.. math::
\mathbf{X}^{\prime} = \sigma \left( \left( \left( \mathbf{I} + \varepsilon \right) + \mathbf{H} \mathbf{D}_e^{-1} \mathbf{H}^\top \right) \mathbf{X} \mathbf{\Theta} \right) .
Parameters:
``in_channels`` (``int``): :math:`C_{in}` is the number of input channels.
``out_channels`` (int): :math:`C_{out}` is the number of output channels.
``eps`` (``float``): :math:`\varepsilon` is the learnable parameter. Defaults to ``0.0``.
``train_eps`` (``bool``): If set to ``True``, the layer will learn the :math:`\varepsilon` parameter. Defaults to ``False``.
``bias`` (``bool``): If set to ``False``, the layer will not learn the bias parameter. Defaults to ``True``.
``use_bn`` (``bool``): If set to ``True``, the layer will use batch normalization. Defaults to ``False``.
``drop_rate`` (``float``): If set to a positive number, the layer will use dropout. Defaults to ``0.5``.
``is_last`` (``bool``): If set to ``True``, the layer will not apply the final activation and dropout functions. Defaults to ``False``.
"""
def __init__(
self,
in_channels: int,
out_channels: int,
eps: float = 0.0,
train_eps: bool = False,
bias: bool = True,
use_bn: bool = False,
drop_rate: float = 0.5,
is_last: bool = False,
):
super().__init__()
self.is_last = is_last
if train_eps:
self.eps = nn.Parameter(torch.tensor([eps]))
else:
self.eps = eps
self.bn = nn.BatchNorm1d(out_channels) if use_bn else None
self.act = nn.ReLU(inplace=True)
self.drop = nn.Dropout(drop_rate)
self.theta = nn.Linear(in_channels, out_channels, bias=bias)
def forward(self, X: torch.Tensor, hg: Hypergraph) -> torch.Tensor:
r"""The forward function.
Parameters:
X (``torch.Tensor``): Input vertex feature matrix. Size :math:`(|\mathcal{V}|, C_{in})`.
hg (``eg.Hypergraph``): The hypergraph structure that contains :math:`|\mathcal{V}|` vertices.
"""
X = self.theta(X)
Y = hg.v2e(X, aggr="mean")
X = (1 + self.eps) * hg.e2v(Y, aggr="sum") + X
if not self.is_last:
X = self.act(X)
if self.bn is not None:
X = self.bn(X)
X = self.drop(X)
return X
+180
View File
@@ -0,0 +1,180 @@
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
from easygraph.nn.convs.common import MLP
from torch import Tensor
from torch.nn import Linear
from torch.nn import Parameter
from torch_geometric.nn.conv import MessagePassing
from torch_geometric.typing import Adj
from torch_geometric.typing import OptTensor
from torch_geometric.typing import Size
from torch_geometric.typing import SparseTensor
from torch_geometric.utils import softmax
from torch_scatter import scatter
def glorot(tensor):
if tensor is not None:
stdv = math.sqrt(6.0 / (tensor.size(-2) + tensor.size(-1)))
tensor.data.uniform_(-stdv, stdv)
def zeros(tensor):
if tensor is not None:
tensor.data.fill_(0)
class PMA(MessagePassing):
"""
PMA part:
Note that in original PMA, we need to compute the inner product of the seed and neighbor nodes.
i.e. e_ij = a(Wh_i,Wh_j), where a should be the inner product, h_i is the seed and h_j are neightbor nodes.
In GAT, a(x,y) = a^T[x||y]. We use the same logic.
"""
_alpha: OptTensor
def __init__(
self,
in_channels,
hid_dim,
out_channels,
num_layers,
heads=1,
concat=True,
negative_slope=0.2,
dropout=0.0,
bias=False,
**kwargs,
):
super(PMA, self).__init__(node_dim=0, **kwargs)
self.in_channels = in_channels
self.hidden = hid_dim // heads
self.out_channels = out_channels
self.heads = heads
self.concat = concat
self.negative_slope = negative_slope
self.dropout = dropout
self.aggr = "add"
# self.input_seed = input_seed
# This is the encoder part. Where we use 1 layer NN (Theta*x_i in the GATConv description)
# Now, no seed as input. Directly learn the importance weights alpha_ij.
# self.lin_O = Linear(heads*self.hidden, self.hidden) # For heads combining
# For neighbor nodes (source side, key)
self.lin_K = Linear(in_channels, self.heads * self.hidden)
# For neighbor nodes (source side, value)
self.lin_V = Linear(in_channels, self.heads * self.hidden)
self.att_r = Parameter(torch.Tensor(1, heads, self.hidden)) # Seed vector
self.rFF = MLP(
in_channels=self.heads * self.hidden,
hidden_channels=self.heads * self.hidden,
out_channels=out_channels,
num_layers=num_layers,
dropout=0.0,
normalization="None",
)
self.ln0 = nn.LayerNorm(self.heads * self.hidden)
self.ln1 = nn.LayerNorm(self.heads * self.hidden)
# if bias and concat:
# self.bias = Parameter(torch.Tensor(heads * out_channels))
# elif bias and not concat:
# self.bias = Parameter(torch.Tensor(out_channels))
# else:
# Always no bias! (For now)
self.register_parameter("bias", None)
self._alpha = None
self.reset_parameters()
def reset_parameters(self):
glorot(self.lin_K.weight)
glorot(self.lin_V.weight)
self.rFF.reset_parameters()
self.ln0.reset_parameters()
self.ln1.reset_parameters()
# glorot(self.att_l)
nn.init.xavier_uniform_(self.att_r)
# zeros(self.bias)
def forward(
self, x, edge_index: Adj, size: Size = None, return_attention_weights=None
):
r"""
Args:
return_attention_weights (bool, optional): If set to :obj:`True`,
will additionally return the tuple
:obj:`(edge_index, attention_weights)`, holding the computed
attention weights for each edge. (default: :obj:`None`)
"""
H, C = self.heads, self.hidden
x_l: OptTensor = None
x_r: OptTensor = None
alpha_l: OptTensor = None
alpha_r: OptTensor = None
if isinstance(x, Tensor):
assert x.dim() == 2, "Static graphs not supported in `GATConv`."
x_K = self.lin_K(x).view(-1, H, C)
x_V = self.lin_V(x).view(-1, H, C)
alpha_r = (x_K * self.att_r).sum(dim=-1)
out = self.propagate(edge_index, x=x_V, alpha=alpha_r, aggr=self.aggr)
alpha = self._alpha
self._alpha = None
# Note that in the original code of GMT paper, they do not use additional W^O to combine heads.
# This is because O = softmax(QK^T)V and V = V_in*W^V. So W^O can be effectively taken care by W^V!!!
out += self.att_r # This is Seed + Multihead
# concat heads then LayerNorm. Z (rhs of Eq(7)) in GMT paper.
out = self.ln0(out.view(-1, self.heads * self.hidden))
# rFF and skip connection. Lhs of eq(7) in GMT paper.
out = self.ln1(out + F.relu(self.rFF(out)))
if isinstance(return_attention_weights, bool):
assert alpha is not None
if isinstance(edge_index, Tensor):
return out, (edge_index, alpha)
elif isinstance(edge_index, SparseTensor):
return out, edge_index.set_value(alpha, layout="coo")
else:
return out
def message(self, x_j, alpha_j, index, ptr, size_j):
# ipdb.set_trace()
alpha = alpha_j
alpha = F.leaky_relu(alpha, self.negative_slope)
alpha = softmax(alpha, index, ptr, index.max() + 1)
self._alpha = alpha
alpha = F.dropout(alpha, p=self.dropout, training=self.training)
return x_j * alpha.unsqueeze(-1)
def aggregate(self, inputs, index, dim_size=None, aggr="add"):
r"""Aggregates messages from neighbors as
:math:`\square_{j \in \mathcal{N}(i)}`.
Takes in the output of message computation as first argument and any
argument which was initially passed to :meth:`propagate`.
By default, this function will delegate its call to scatter functions
that support "add", "mean" and "max" operations as specified in
:meth:`__init__` by the :obj:`aggr` argument.
"""
# ipdb.set_trace()
if aggr is None:
raise ValueError("aggr was not passed!")
return scatter(inputs, index, dim=self.node_dim, reduce=aggr)
def __repr__(self):
return "{}({}, {}, heads={})".format(
self.__class__.__name__, self.in_channels, self.out_channels, self.heads
)
+46
View File
@@ -0,0 +1,46 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
__all__ = ["BPRLoss"]
class BPRLoss(nn.Module):
r"""This criterion computes the Bayesian Personalized Ranking (BPR) loss between the positive scores and the negative scores.
Parameters:
``alpha`` (``float``, optional): The weight for the positive scores in the BPR loss. Defaults to ``1.0``.
``beta`` (``float``, optional): The weight for the negative scores in the BPR loss. Defaults to ``1.0``.
``activation`` (``str``, optional): The activation function to use can be one of ``"sigmoid_then_log"``, ``"softplus"``. Defaults to ``"sigmoid_then_log"``.
"""
def __init__(
self,
alpha: float = 1.0,
beta: float = 1.0,
activation: str = "sigmoid_then_log",
):
super().__init__()
assert activation in (
"sigmoid_then_log",
"softplus",
), "activation function of BPRLoss must be sigmoid_then_log or softplus."
self.activation = activation
self.alpha = alpha
self.beta = beta
def forward(self, pos_scores: torch.Tensor, neg_scores: torch.Tensor):
r"""The forward function of BPRLoss.
Parameters:
``pos_scores`` (``torch.Tensor``): The positive scores.
``neg_scores`` (``torch.Tensor``): The negative scores.
"""
if self.activation == "sigmoid_then_log":
loss = -(self.alpha * pos_scores - self.beta * neg_scores).sigmoid().log()
elif self.activation == "softplus":
loss = F.softplus(self.beta * neg_scores - self.alpha * pos_scores)
else:
raise NotImplementedError
return loss.mean()
+32
View File
@@ -0,0 +1,32 @@
from typing import List
import torch
import torch.nn as nn
__all__ = ["EmbeddingRegularization"]
class EmbeddingRegularization(nn.Module):
r"""Regularization function for embeddings.
Parameters:
``p`` (``int``): The power to use in the regularization. Defaults to ``2``.
``weight_decay`` (``float``): The weight of the regularization. Defaults to ``1e-4``.
"""
def __init__(self, p: int = 2, weight_decay: float = 1e-4):
super().__init__()
self.p = p
self.weight_decay = weight_decay
def forward(self, *embs: List[torch.Tensor]):
r"""The forward function.
Parameters:
``embs`` (``List[torch.Tensor]``): The input embeddings.
"""
loss = 0
for emb in embs:
loss += 1 / self.p * emb.pow(self.p).sum(dim=1).mean()
return self.weight_decay * loss
View File
View File
View File
+16
View File
@@ -0,0 +1,16 @@
import easygraph as eg
import pytest
import torch
from easygraph.nn.regularization import EmbeddingRegularization
def test_embedding_reg():
print("EmbeddingRegularization" in eg.__dir__())
emb_reg = EmbeddingRegularization(p=2, weight_decay=1e-4)
embs = [torch.randn(10, 3), torch.randn(10, 3)]
loss = emb_reg(*embs)
true_loss = 0
for emb in embs:
true_loss += 1 / 2 * emb.norm(2).pow(2) / 10
assert loss.item() == pytest.approx(1e-4 * true_loss.item())