Skip to content

Commit 8cef1de

Browse files
committed
update docs and add models
1 parent de92e72 commit 8cef1de

4 files changed

Lines changed: 336 additions & 0 deletions

File tree

docs/models/DGSDA.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
::: pygda.models.dgsda
2+
options:
3+
docstring_style: numpy
4+
show_source: false
5+
merge_init_into_class: true
6+
ignore_init_summary: true
7+
heading_level: 6

docs/models/TDSS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
::: pygda.models.tdss
2+
options:
3+
docstring_style: numpy
4+
show_source: false
5+
merge_init_into_class: true
6+
ignore_init_summary: true
7+
heading_level: 6

docs/nn/DGSDABase.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
::: pygda.nn.dgsda_base
2+
options:
3+
docstring_style: numpy
4+
show_source: false
5+
merge_init_into_class: true
6+
ignore_init_summary: true
7+
heading_level: 6

pygda/nn/dgsda_base.py

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
import torch
2+
from torch import nn
3+
import torch.nn.functional as F
4+
5+
from torch_geometric.nn.conv import MessagePassing
6+
from torch_geometric.utils import add_self_loops
7+
from torch_geometric.utils import get_laplacian
8+
from scipy.special import comb
9+
10+
11+
class BernProp(MessagePassing):
12+
"""
13+
K-order Bernstein polynomial approximation.
14+
15+
Parameters
16+
----------
17+
K : int
18+
Order of the polynomial filter. Determines the complexity of the spectral filter.
19+
is_source_domain : bool, optional
20+
Whether this layer is used for source domain. If True, temperature parameters
21+
are learnable. If False, they are fixed to a linear interpolation from 1 to 0.
22+
Default: ``True``.
23+
bias : bool, optional
24+
Whether to add bias. Currently not used but kept for compatibility.
25+
Default: ``True``.
26+
**kwargs : optional
27+
Additional keyword arguments passed to the MessagePassing parent class.
28+
29+
Notes
30+
-----
31+
This class implements a graph neural network layer that performs spectral domain
32+
adaptation. It uses a polynomial filter approach with learnable temperature parameters
33+
to adapt the spectral characteristics of the graph.
34+
35+
The layer computes a polynomial filter of order K and applies it to the graph
36+
signal through message passing operations. The filter coefficients are determined
37+
by learnable temperature parameters that differ between source and target domains.
38+
39+
Attributes
40+
----------
41+
K : int
42+
Order of the polynomial filter.
43+
is_source_domain : bool
44+
Whether this layer is for source domain.
45+
cached_terms : torch.Tensor, optional
46+
Cached polynomial terms for filter computation.
47+
cached_coefs : torch.Tensor, optional
48+
Cached polynomial coefficients.
49+
temp : nn.Parameter
50+
Learnable temperature parameters for the filter.
51+
"""
52+
53+
def __init__(self, K, is_source_domain=True, bias=True, **kwargs):
54+
super(BernProp, self).__init__(aggr='add', **kwargs)
55+
56+
self.K = K
57+
self.is_source_domain = is_source_domain
58+
self.cached_terms = None
59+
self.cached_coefs = None
60+
self.temp = nn.Parameter(torch.Tensor(self.K + 1), requires_grad=is_source_domain)
61+
self.reset_parameters()
62+
63+
def reset_parameters(self):
64+
"""
65+
Reset the learnable parameters of the layer.
66+
67+
Notes
68+
-----
69+
- For source domain layers, temperature parameters are initialized to 1.
70+
71+
- For target domain layers, temperature parameters are set to a linear
72+
interpolation from 1 to 0 over K+1 values.
73+
"""
74+
if self.is_source_domain:
75+
self.temp.data.fill_(1)
76+
else:
77+
self.temp.data = torch.linspace(1, 0, self.K + 1)
78+
79+
def get_filter(self):
80+
"""
81+
Compute the spectral filter using cached terms and coefficients.
82+
83+
Returns
84+
-------
85+
torch.Tensor
86+
The computed spectral filter H.
87+
88+
Notes
89+
-----
90+
This method requires cached_terms and cached_coefs to be set before calling.
91+
The filter is computed as a weighted sum of polynomial terms.
92+
"""
93+
TEMP = F.relu(self.temp)
94+
H = 0
95+
96+
for k in range(self.K + 1):
97+
H = H + TEMP[k] * self.cached_coefs[k] * self.cached_terms[k]
98+
99+
return H
100+
101+
def forward(self, x, edge_index, edge_weight=None):
102+
"""
103+
Forward pass of the DGSD layer.
104+
105+
Parameters
106+
----------
107+
x : torch.Tensor
108+
Node feature matrix of shape [num_nodes, num_features].
109+
edge_index : torch.LongTensor
110+
Graph connectivity in COO format with shape [2, num_edges].
111+
edge_weight : torch.Tensor, optional
112+
Edge weights of shape [num_edges]. If None, all edges are assumed
113+
to have weight 1. Default: ``None``.
114+
115+
Returns
116+
-------
117+
torch.Tensor
118+
Updated node features after spectral domain adaptation.
119+
120+
Notes
121+
-----
122+
This method implements the spectral domain adaptation by:
123+
124+
- Computing the symmetric normalized Laplacian
125+
126+
- Adding self-loops with appropriate weights
127+
128+
- Propagating messages through the graph using polynomial filters
129+
130+
- Combining the results using binomial coefficients and temperature parameters
131+
"""
132+
TEMP = F.relu(self.temp)
133+
134+
edge_index1, norm1 = get_laplacian(edge_index, edge_weight, normalization='sym', dtype=x.dtype,
135+
num_nodes=x.size(self.node_dim))
136+
edge_index2, norm2 = add_self_loops(edge_index1, -norm1, fill_value=2., num_nodes=x.size(self.node_dim))
137+
138+
tmp = []
139+
tmp.append(x)
140+
for i in range(self.K):
141+
x = self.propagate(edge_index2, x=x, norm=norm2, size=None)
142+
tmp.append(x)
143+
144+
out = (comb(self.K, 0) / (2 ** self.K)) * TEMP[0] * tmp[self.K]
145+
146+
for i in range(self.K):
147+
x = tmp[self.K - i - 1]
148+
x = self.propagate(edge_index1, x=x, norm=norm1, size=None)
149+
for j in range(i):
150+
x = self.propagate(edge_index1, x=x, norm=norm1, size=None)
151+
152+
out = out + (comb(self.K, i + 1) / (2 ** self.K)) * TEMP[i + 1] * x
153+
return out
154+
155+
def message(self, x_j, norm):
156+
"""
157+
Message function for message passing.
158+
159+
Parameters
160+
----------
161+
x_j : torch.Tensor
162+
Source node features of shape [num_edges, num_features].
163+
norm : torch.Tensor
164+
Normalized edge weights of shape [num_edges].
165+
166+
Returns
167+
-------
168+
torch.Tensor
169+
Messages of shape [num_edges, num_features].
170+
"""
171+
return norm.view(-1, 1) * x_j
172+
173+
def __repr__(self):
174+
"""
175+
String representation of the layer.
176+
177+
Returns
178+
-------
179+
str
180+
String representation showing the class name, filter order K, and
181+
temperature parameters.
182+
"""
183+
return '{}(K={}, temp={})'.format(self.__class__.__name__, self.K, self.temp)
184+
185+
186+
class DGSDABase(nn.Module):
187+
"""
188+
Base class for DGSDA.
189+
190+
Parameters
191+
----------
192+
features : int
193+
Input feature dimension.
194+
hidden : int
195+
Hidden layer dimension.
196+
classes : int
197+
Number of output classes.
198+
dprate : float, optional
199+
Dropout rate for propagation layers. Default: ``0.0``.
200+
K : int, optional
201+
Order of the polynomial filter for propagation layers. Default: ``15``.
202+
203+
Notes
204+
-----
205+
This class implements a domain generalization model using spectral domain
206+
adaptation. It uses separate propagation layers for source and target domains
207+
to adapt the spectral characteristics of the graph data.
208+
209+
The model consists of:
210+
211+
- Linear transformation layers
212+
213+
- Bernoulli propagation layers with polynomial filters
214+
215+
- Domain-specific propagation paths
216+
"""
217+
218+
def __init__(self, features, hidden, classes, dprate=0.0, K=15):
219+
super(DGSDABase, self).__init__()
220+
self.lin1 = nn.Linear(features, hidden)
221+
self.lin2 = nn.Linear(hidden, classes)
222+
self.prop1 = BernProp(K)
223+
self.prop2 = BernProp(K)
224+
self.prop3 = BernProp(K)
225+
226+
self.dprate = dprate
227+
228+
def reset_parameters(self):
229+
"""
230+
Reset the learnable parameters of the model.
231+
232+
Notes
233+
-----
234+
Currently only resets the first propagation layer (prop1).
235+
"""
236+
self.prop1.reset_parameters()
237+
238+
def forward(self, data, is_source_domain=True):
239+
"""
240+
Forward pass of the DGSDA model.
241+
242+
Parameters
243+
----------
244+
data : torch_geometric.data.Data
245+
Input graph data containing node features and edge indices.
246+
is_source_domain : bool, optional
247+
Whether the input is from source domain. Determines which
248+
propagation layer to use. Default: ``True``.
249+
250+
Returns
251+
-------
252+
torch.Tensor
253+
Model predictions for node classification.
254+
255+
Notes
256+
-----
257+
The forward pass consists of:
258+
259+
- Domain-specific feature propagation
260+
261+
- Dropout regularization
262+
263+
- Final linear classification
264+
265+
- Final propagation step
266+
"""
267+
x, edge_index = data.x, data.edge_index
268+
269+
x = self.get_props(x, edge_index, is_source_domain)
270+
271+
x = F.dropout(x, p=self.dprate, training=self.training)
272+
x = self.lin2(x)
273+
274+
x = F.dropout(x, p=self.dprate, training=self.training)
275+
x = self.prop3(x, edge_index)
276+
return x
277+
278+
def get_props(self, x, edge_index, is_source_domain=True):
279+
"""
280+
Apply domain-specific feature propagation.
281+
282+
Parameters
283+
----------
284+
x : torch.Tensor
285+
Node features of shape [num_nodes, num_features].
286+
edge_index : torch.LongTensor
287+
Graph connectivity in COO format with shape [2, num_edges].
288+
is_source_domain : bool, optional
289+
Whether to use source domain propagation layer. If True, uses prop1;
290+
if False, uses prop2. Default: ``True``.
291+
292+
Returns
293+
-------
294+
torch.Tensor
295+
Propagated node features.
296+
297+
Notes
298+
-----
299+
This method implements the domain-specific feature processing:
300+
301+
- Initial dropout and ReLU activation
302+
303+
- Domain-specific propagation using Bernoulli polynomial filters
304+
305+
- Separate propagation paths for source and target domains
306+
"""
307+
x = F.dropout(x, p=self.dprate, training=self.training)
308+
x = F.relu(self.lin1(x))
309+
310+
x = F.dropout(x, p=self.dprate, training=self.training)
311+
if is_source_domain:
312+
x = self.prop1(x, edge_index)
313+
else:
314+
x = self.prop2(x, edge_index)
315+
return x

0 commit comments

Comments
 (0)