chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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."
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user